diff --git a/.codespellrc b/.codespellrc deleted file mode 100644 index a51e6239549..00000000000 --- a/.codespellrc +++ /dev/null @@ -1,7 +0,0 @@ -[codespell] -# Ref: https://github.com/codespell-project/codespell#using-a-config-file -skip = .git*,go.sum,*.lock,.codespellrc,vendor,translations,Keybindings_*.md -check-hidden = true -# camel-cased -ignore-regex = (\b[A-Za-z][a-z]*[A-Z]\S+\b|\.edn\b|\S+…|\\nd\b) -ignore-words-list = fomrat,inbetween diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 6e7b5fd5825..00000000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -# adapted from https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/Dockerfile - -# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster -ARG VARIANT=1-bullseye -FROM golang:${VARIANT} - -RUN go install mvdan.cc/gofumpt@latest - -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 73f37e3ecca..00000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,69 +0,0 @@ -// adapted from https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/devcontainer.json -{ - "build": { - "dockerfile": "./Dockerfile", - "context": "." - }, - "features": { - "ghcr.io/devcontainers/features/common-utils:1": { - "installZsh": "true", - "username": "vscode", - "uid": "1000", - "gid": "1000", - "upgradePackages": "true" - }, - "ghcr.io/devcontainers/features/go:1": { - "version": "none" - }, - "ghcr.io/devcontainers/features/git:1": { - "version": "latest", - "ppa": "false" - } - }, - "overrideFeatureInstallOrder": [ - "ghcr.io/devcontainers/features/common-utils" - ], - // not sure if we actually need these - "runArgs": [ - "--cap-add=SYS_PTRACE", - "--security-opt", - "seccomp=unconfined" - ], - // Configure tool-specific properties. - "customizations": { - // Configure properties specific to VS Code. - "vscode": { - // Set *default* container specific settings.json values on container create. - "settings": { - "go.toolsManagement.checkForUpdates": "local", - "go.useLanguageServer": true, - "go.gopath": "/go", - "[go]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": true - } - }, - "go.lintTool": "golangci-lint", - "gopls": { - "formatting.gofumpt": true, - "usePlaceholders": false // add parameter placeholders when completing a function - }, - "files.eol": "\n" - }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "golang.Go" - ] - } - }, - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "go version", - - // See https://www.kenmuse.com/blog/avoiding-dubious-ownership-in-dev-containers/ for the safe.directory part - // The defaultBranch part is required for our deprecated integration tests. - "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && git config --global init.defaultBranch master", - - // Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode" -} diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 62f9670c83d..00000000000 --- a/.editorconfig +++ /dev/null @@ -1,4 +0,0 @@ -root = true - -[*.go] -indent_style = tab diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 8143bb75f7c..00000000000 --- a/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -*.go text -*.md text eol=lf -*.json text eol=lf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index ed009fc5e9b..00000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,4 +0,0 @@ -# These are supported funding model platforms - -github: [jesseduffield] -custom: ['https://donorbox.org/lazygit'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index caeb15ba21c..00000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' ---- - -### Describe the bug -A clear and concise description of what the bug is. - -### To Reproduce -Steps to reproduce the behavior: - -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -### Expected behavior -A clear and concise description of what you expected to happen. - -### Screenshots -If applicable, add screenshots to help explain your problem. - -### Version info: - -* _Run `lazygit --version` and paste the result here_ -* _Run `git --version` and paste the result here_ - -### Additional context -Add any other context about the problem here. - -> [!NOTE] -> Please try updating to the latest version or [manually building](https://github.com/jesseduffield/lazygit/#manual) the latest `master` to see if the issue still occurs. - - diff --git a/.github/ISSUE_TEMPLATE/discussion.md b/.github/ISSUE_TEMPLATE/discussion.md deleted file mode 100644 index 511266107bb..00000000000 --- a/.github/ISSUE_TEMPLATE/discussion.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Discussion -about: Begin a discussion -title: '' -labels: discussion -assignees: '' - ---- - -### Topic -A clear and concise description of what you want to discuss. - -### Your thoughts -What you have to say about the topic. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index b47bbf68dc0..00000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' ---- - -### Is your feature request related to a problem? Please describe. -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -### Describe the solution you'd like -A clear and concise description of what you want to happen. - -### Describe alternatives you've considered -A clear and concise description of any alternative solutions or features you've considered. - -### Additional context -Add any other context or screenshots about the feature request here. - - diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 71e2bd80d5a..00000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "gomod" - directory: "/" - schedule: - interval: "weekly" - allowed_updates: - - match: - update_type: "security" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index a8b0386b065..00000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,18 +0,0 @@ -### PR Description - -### Please check if the PR fulfills these requirements - -* [ ] Cheatsheets are up-to-date (run `go generate ./...`) -* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting)) -* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide) -* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation)) -* [ ] If a new UserConfig entry was added, make sure it can be hot-reloaded (see [here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig)) -* [ ] Docs have been updated if necessary -* [ ] You've read through your own file changes for silly mistakes etc - - diff --git a/.github/release.yml b/.github/release.yml deleted file mode 100644 index e32d8103b05..00000000000 --- a/.github/release.yml +++ /dev/null @@ -1,29 +0,0 @@ -changelog: - exclude: - labels: - - ignore-for-release - categories: - - title: Features ✨ - labels: - - feature - - title: Enhancements 🔥 - labels: - - enhancement - - title: Fixes 🔧 - labels: - - bug - - title: Maintenance ⚙️ - labels: - - maintenance - - title: Docs 📖 - labels: - - docs - - title: I18n 🌎 - labels: - - i18n - - title: Performance Improvements 📊 - labels: - - performance - - title: Other Changes - labels: - - "*" diff --git a/.github/workflows/check-required-label.yml b/.github/workflows/check-required-label.yml deleted file mode 100644 index 8a2090da7a1..00000000000 --- a/.github/workflows/check-required-label.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Check Required Labels - -on: - pull_request: - types: [opened, labeled, unlabeled, synchronize] - -jobs: - check-required-label: - runs-on: ubuntu-latest - steps: - - uses: mheap/github-action-required-labels@v5 - with: - mode: exactly - count: 1 - labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n, performance" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index a7612deb7e8..00000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,230 +0,0 @@ -name: Continuous Integration - -env: - GO_VERSION: 1.25 - -on: - push: - branches: - - master - pull_request: - -jobs: - unit-tests: - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - windows-latest - include: - - os: ubuntu-latest - cache_path: ~/.cache/go-build - - os: windows-latest - cache_path: ~\AppData\Local\go-build - name: ci - ${{matrix.os}} - runs-on: ${{matrix.os}} - env: - GOFLAGS: -mod=vendor - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.25.x - - name: Test code - # we're passing -short so that we skip the integration tests, which will be run in parallel below - run: | - mkdir -p /tmp/code_coverage - go test ./... -short -cover -args "-test.gocoverdir=/tmp/code_coverage" - - name: Upload code coverage artifacts - uses: actions/upload-artifact@v4 - with: - name: coverage-unit-${{ matrix.os }}-${{ github.run_id }} - path: /tmp/code_coverage - - integration-tests: - strategy: - fail-fast: false - matrix: - git-version: - - 2.32.0 # oldest supported version - - 2.38.2 # first version that supports the rebase.updateRefs config - - 2.44.0 - - latest # We rely on github to have the latest version installed on their VMs - runs-on: ubuntu-latest - name: "Integration Tests - git ${{matrix.git-version}}" - env: - GOFLAGS: -mod=vendor - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Restore Git cache - if: matrix.git-version != 'latest' - id: cache-git-restore - uses: actions/cache/restore@v4 - with: - path: ~/git-${{matrix.git-version}} - key: ${{runner.os}}-git-${{matrix.git-version}} - - name: Build Git ${{matrix.git-version}} - if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest' - run: > - sudo apt-get update && sudo apt-get install --no-install-recommends -y build-essential ca-certificates curl gettext libexpat1-dev libssl-dev libz-dev openssl - && curl -sL "https://mirrors.edge.kernel.org/pub/software/scm/git/git-$%7B%7Bmatrix.git-version%7D%7D.tar.xz" -o - | tar xJ -C "$HOME" - && cd "$HOME/git-${{matrix.git-version}}" - && ./configure - && make -j - - name: Install Git ${{matrix.git-version}} - if: matrix.git-version != 'latest' - run: sudo make -C "$HOME/git-${{matrix.git-version}}" -j install - - name: Save Git cache - if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest' - uses: actions/cache/save@v4 - with: - path: ~/git-${{matrix.git-version}} - key: ${{runner.os}}-git-${{matrix.git-version}} - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.25.x - - name: Print git version - run: git --version - - name: Test code - env: - # See https://go.dev/blog/integration-test-coverage - LAZYGIT_GOCOVERDIR: /tmp/code_coverage - run: | - mkdir -p /tmp/code_coverage - ./scripts/run_integration_tests.sh - - name: Upload code coverage artifacts - uses: actions/upload-artifact@v4 - with: - name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }} - path: /tmp/code_coverage - build: - runs-on: ubuntu-latest - env: - GOFLAGS: -mod=vendor - GOARCH: amd64 - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.25.x - - name: Build linux binary - run: | - GOOS=linux go build - - name: Build windows binary - run: | - GOOS=windows go build - - name: Build darwin binary - run: | - GOOS=darwin go build - - name: Build integration test binary - run: | - GOOS=linux go build cmd/integration_test/main.go - - name: Build integration test injector - run: | - GOOS=linux go build pkg/integration/clients/injector/main.go - check-codebase: - runs-on: ubuntu-latest - env: - GOFLAGS: -mod=vendor - GOARCH: amd64 - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.25.x - - name: Check Vendor Directory - # ensure our vendor directory matches up with our go modules - run: | - go mod vendor && git diff --exit-code || (echo "Unexpected change to vendor directory. Run 'go mod vendor' locally and commit the changes" && exit 1) - - name: Check go.mod file - # ensure our go.mod file is clean - run: | - go mod tidy && git diff --exit-code || (echo "go.mod file is not clean. Run 'go mod tidy' locally and commit the changes" && exit 1) - - name: Check All Auto-Generated Files - # ensure all our auto-generated files are up to date - run: | - go generate ./... && git diff --quiet || (git status -s; echo "Auto-generated files not up to date. Run 'go generate ./...' locally and commit the changes" && exit 1) - shell: bash # needed so that we get "-o pipefail" - - name: Check Filenames - run: scripts/check_filenames.sh - lint: - runs-on: ubuntu-latest - env: - GOFLAGS: -mod=vendor - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.25.x - - name: Lint - uses: golangci/golangci-lint-action@v8 - with: - # If you change this, make sure to also update scripts/golangci-lint-shim.sh - version: v2.4.0 - - name: errors - run: golangci-lint run - if: ${{ failure() }} - upload-coverage: - # List all jobs that produce coverage files - needs: [unit-tests, integration-tests] - if: github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.25.x - - - name: Download all coverage artifacts - uses: actions/download-artifact@v4 - with: - path: /tmp/code_coverage - - - name: Combine coverage files - run: | - # Find all directories in /tmp/code_coverage and create a comma-separated list - COVERAGE_DIRS=$(find /tmp/code_coverage -mindepth 1 -maxdepth 1 -type d -printf '/tmp/code_coverage/%f,' | sed 's/,$//') - echo "Coverage directories: $COVERAGE_DIRS" - # Run the combine command with the generated list - go tool covdata textfmt -i=$COVERAGE_DIRS -o coverage.out - echo "Combined coverage:" - go tool cover -func coverage.out | tail -1 | awk '{print $3}' - - - name: Upload to Codacy - run: | - CODACY_PROJECT_TOKEN=${{ secrets.CODACY_PROJECT_TOKEN }} \ - bash <(curl -Ls https://coverage.codacy.com/get.sh) report \ - --force-coverage-parser go -r coverage.out - - check-for-fixups: - runs-on: ubuntu-latest - if: github.ref != 'refs/heads/master' - steps: - # See https://github.com/actions/checkout/issues/552#issuecomment-1167086216 - - name: "PR commits" - run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} ))" >> "${GITHUB_ENV}" - - - name: "Checkout PR branch and all PR commits" - uses: actions/checkout@v4 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: ${{ env.PR_FETCH_DEPTH }} - - - name: Check for fixups - run: | - ./scripts/check_for_fixups.sh ${{ github.event.pull_request.base.ref }} diff --git a/.github/workflows/close-issues.yml b/.github/workflows/close-issues.yml deleted file mode 100644 index 5a171348a9a..00000000000 --- a/.github/workflows/close-issues.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Close Issues - -on: - issue_comment: - types: [created] - -permissions: - issues: write - -jobs: - close_issue: - runs-on: ubuntu-latest - if: ${{ github.event.issue.pull_request == null && startsWith(github.event.comment.body, '/close') }} - steps: - - uses: actions/github-script@v7 - with: - script: | - const trustedUsers = ['ChrisMcD1', 'jesseduffield', 'stefanhaller'] - const commenter = context.payload.comment.user.login - - console.log(`Commenter: ${commenter}`) - - if (!trustedUsers.includes(commenter)) { - console.log(`User ${commenter} is not trusted. Ignoring.`) - return - } - - const issueNumber = context.payload.issue.number - const owner = context.repo.owner - const repo = context.repo.repo - - await github.rest.issues.update({ - owner, - repo, - issue_number: issueNumber, - state: 'closed' - }) - - console.log(`Closed issue #${issueNumber} by request from ${commenter}.`) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml deleted file mode 100644 index b026c855dd4..00000000000 --- a/.github/workflows/codespell.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Codespell configuration is within .codespellrc ---- -name: Codespell - -on: - push: - branches: [master] - pull_request: - branches: [master] - -permissions: - contents: read - -jobs: - codespell: - name: Check for spelling errors - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Annotate locations with typos - uses: codespell-project/codespell-problem-matcher@v1 - - name: Codespell - uses: codespell-project/actions-codespell@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 4eb2a5a2072..00000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,151 +0,0 @@ -name: Release - -on: - schedule: - # Runs at 8:00 AM UTC on every Saturday - # We'll check below if it's the first Saturday of the month, and fail if not - - cron: '0 8 * * 6' - # Allow manual triggering of the workflow - workflow_dispatch: - inputs: - version_bump: - description: 'Version bump type' - type: choice - required: true - default: 'patch' - options: - - minor - - patch - ignore_blocks: - description: 'Ignore blocking PRs/issues' - type: boolean - required: true - default: false - -defaults: - run: - shell: bash - -jobs: - check-and-release: - runs-on: ubuntu-latest - steps: - - name: Check for correct repository - if: ${{ github.event_name != 'workflow_dispatch' && github.repository != 'stefanhaller/lazygit' }} - run: | - echo "Should only run in the stefanhaller/lazygit repository" - exit 1 - - - name: Check for first Saturday of the month - if: ${{ github.event_name != 'workflow_dispatch' }} - run: | - if (( $(date +%e) > 7 )); then - echo "This is not the first Saturday of the month" - exit 1 - fi - - - name: Checkout Code - uses: actions/checkout@v4 - with: - repository: jesseduffield/lazygit - token: ${{ secrets.LAZYGIT_RELEASE_PAT }} - fetch-depth: 0 - - - name: Get Latest Tag - run: | - latest_tag=$(git describe --tags $(git rev-list --tags --max-count=1) || echo "v0.0.0") - - if ! [[ $latest_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Error: Tag format is invalid. Expected format: vX.X.X" - exit 1 - fi - - echo "Latest tag: $latest_tag" - echo "latest_tag=$latest_tag" >> $GITHUB_ENV - - - name: Check for changes since last release - run: | - if [ -z "$(git diff --name-only ${{ env.latest_tag }})" ]; then - echo "No changes detected since last release" - exit 1 - fi - - - name: Check for Blocking Issues/PRs - if: ${{ !inputs.ignore_blocks }} - id: check_blocks - run: | - gh auth setup-git - gh auth status - - echo "Checking for blocking issues and PRs..." - - # Check for blocking issues - blocking_issues=$(gh issue list -l blocks-release --json number,title --jq '.[] | "- \(.title) (#\(.number))"') - - # Check for blocking PRs - blocking_prs=$(gh pr list -l blocks-release --json number,title --jq '.[] | "- \(.title) (#\(.number)) (PR)"') - - # Combine the results - blocking_items="$blocking_issues"$'\n'"$blocking_prs" - - # Remove empty lines - blocking_items=$(echo "$blocking_items" | grep . || true) - - if [ -n "$blocking_items" ]; then - echo "Blocking issues/PRs detected:" - echo "$blocking_items" - exit 1 - fi - env: - GITHUB_TOKEN: ${{ secrets.LAZYGIT_RELEASE_PAT }} - - - name: Calculate next version - run: | - echo "Latest tag: ${{ env.latest_tag }}" - IFS='.' read -r major minor patch <<< "${{ env.latest_tag }}" - - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - if [[ "${{ inputs.version_bump }}" == "patch" ]]; then - patch=$((patch + 1)) - else - minor=$((minor + 1)) - patch=0 - fi - else - # Default behavior for scheduled runs - minor=$((minor + 1)) - patch=0 - fi - - new_tag="$major.$minor.$patch" - - if ! [[ $new_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Error: New tag's format is invalid. Expected format: vX.X.X" - exit 1 - fi - - echo "New tag: $new_tag" - echo "new_tag=$new_tag" >> $GITHUB_ENV - - - name: Create and Push Tag - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag ${{ env.new_tag }} -a -m "Release ${{ env.new_tag }}" - git push origin ${{ env.new_tag }} - env: - GITHUB_TOKEN: ${{ secrets.LAZYGIT_RELEASE_PAT }} - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.25.x - - - name: Run goreleaser - uses: goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser - version: v2 - args: release --clean - env: - GITHUB_TOKEN: ${{secrets.LAZYGIT_RELEASE_PAT}} diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yml deleted file mode 100644 index ce31c44e00d..00000000000 --- a/.github/workflows/sponsors.yml +++ /dev/null @@ -1,29 +0,0 @@ -# see https://github.com/JamesIves/github-sponsors-readme-action -name: Generate Sponsors README -on: - push: - branches: - - master -jobs: - deploy: - runs-on: ubuntu-latest - if: ${{ github.repository == 'jesseduffield/lazygit' }} - steps: - - name: Checkout 🛎️ - uses: actions/checkout@v4 - - - name: Generate Sponsors 💖 - uses: JamesIves/github-sponsors-readme-action@v1.2.2 - with: - token: ${{ secrets.SPONSORS_TOKEN }} - file: "README.md" - - - name: Create Pull Request 🚀 - uses: peter-evans/create-pull-request@v7 - with: - commit-message: "README.md: Update Sponsors" - title: "README.md: Update Sponsors" - author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" - labels: "ignore-for-release" - delete-branch: true - token: ${{ secrets.SPONSORS_PR_TOKEN }} diff --git a/.gitignore b/.gitignore deleted file mode 100644 index ef4dfcac65e..00000000000 --- a/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# Please do not add personal files - -# Logs -*.log - -# Notes -*.notes - -# Tests -test/repos/repo -coverage.txt - -# JetBrains stuff -.idea/ - -# Binaries -lazygit -lazygit.exe - -test/git_server/data - -test/_results/** - -oryxBuildBinary -__debug_bin* - -.worktrees -demo/output/* - -coverage.out - -# Nix -result -result-* -.direnv -.envrc diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index c13f7b9f39e..00000000000 --- a/.golangci.yml +++ /dev/null @@ -1,110 +0,0 @@ -version: "2" -run: - go: "1.25" -linters: - enable: - - copyloopvar - - errorlint - - exhaustive - - intrange - - makezero - - nakedret - - nolintlint - - prealloc - - revive - - thelper - - tparallel - - unconvert - - unparam - - wastedassign - settings: - copyloopvar: - check-alias: true - exhaustive: - default-signifies-exhaustive: true - nakedret: - # the gods will judge me but I just don't like naked returns at all - max-func-lines: 0 - staticcheck: - checks: - - all - - # SA1019 is for checking that we're not using fields marked as - # deprecated in a comment. It decides this in a loose way so I'm - # silencing it. Also because it's tripping on our own structs. - - -SA1019 - - # ST1003 complains about names like remoteUrl or itemId (should be - # remoteURL and itemID). While I like these suggestions, it also - # complains about enum constants that are all caps, and we use these and - # I like them, and also about camelCase identifiers that contain an - # underscore, which we also use in a few places. Since it can't be - # configured to ignore specific cases, and I don't want to use nolint - # comments in the code, we have to disable it altogether. - - -ST1003 # Poorly chosen identifier - - # Probably a good idea, but we first have to review our error reporting - # strategy to be able to use it everywhere. - - -ST1005 # Error strings should not be capitalized - - # Many of our classes use self as a receiver name, and we think that's fine. - - -ST1006 # Use of self or this as receiver name - - # De Morgan's law suggests to replace `!(a && b)` with `!a || !b`; but - # sometimes I find one more readable than the other, so I want to decide - # that myself. - - -QF1001 # De Morgan's law - - # QF1003 is about using a tagged switch instead of an if-else chain. In - # many cases this is a useful suggestion; however, sometimes the change - # is only possible by adding a default case to the switch (when there - # was no `else` block in the original code), in which case I don't find - # it to be an improvement. - - -QF1003 # Could replace with tagged switch - - # We need to review our use of embedded fields. I suspect that in some - # cases the fix is not to remove the selector for the embedded field, - # but to turn the embedded field into a named field. - - -QF1008 # Could remove embedded field from selector - - # The following checks are all disabled by default in golangci-lint, but - # we disable them again explicitly here to make it easier to keep this - # list in sync with the gopls config in .vscode/settings.json. - - -ST1000, # At least one file in a package should have a package comment - - -ST1020, # The documentation of an exported function should start with the function's name - - -ST1021, # The documentation of an exported type should start with type's name - - -ST1022, # The documentation of an exported variable or constant should start with variable's name - - dot-import-whitelist: - - github.com/jesseduffield/lazygit/pkg/integration/components - revive: - severity: warning - rules: - - name: atomic - - name: context-as-argument - - name: context-keys-type - - name: error-naming - - name: var-declaration - - name: package-comments - - name: range - - name: time-naming - - name: indent-error-flow - - name: errorf - - name: superfluous-else - exclusions: - generated: lax - presets: - - comments - - common-false-positives - - legacy - - std-error-handling - paths: - - vendor/ -formatters: - enable: - - gofumpt - - goimports - exclusions: - generated: lax - paths: - - vendor/ diff --git a/.goreleaser.yml b/.goreleaser.yml deleted file mode 100644 index 3b9997ecd10..00000000000 --- a/.goreleaser.yml +++ /dev/null @@ -1,38 +0,0 @@ -version: 2 - -builds: - - env: - - CGO_ENABLED=0 - goos: - - freebsd - - windows - - darwin - - linux - goarch: - - amd64 - - arm - - arm64 - - '386' - # Default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}`. - ldflags: - - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.buildSource=binaryRelease - -archives: - - name_template: >- - {{- .ProjectName }}_ - {{- .Version }}_ - {{- .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}32-bit - {{- else if eq .Arch "arm" }}armv6 - {{- else }}{{ .Arch }}{{ end }} - format_overrides: - - goos: windows - formats: [ zip ] -checksum: - name_template: 'checksums.txt' -snapshot: - version_template: '{{ .Tag }}-next' -changelog: - use: github-native - sort: asc diff --git a/.vscode/debugger_config.yml b/.vscode/debugger_config.yml deleted file mode 100644 index dc8bd1faa78..00000000000 --- a/.vscode/debugger_config.yml +++ /dev/null @@ -1 +0,0 @@ -disableStartupPopups: true diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 3bedda76659..00000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Debug Lazygit", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "main.go", - "args": [ - "--debug", - "--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml" - ], - "hideSystemGoroutines": true, - "console": "integratedTerminal", - }, - { - "name": "Tail Lazygit logs", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "main.go", - "args": [ - "--logs", - "--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml" - ], - "console": "integratedTerminal", - }, - { - "name": "JSON Schema generator", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${workspaceFolder}/pkg/jsonschema/generator.go", - "cwd": "${workspaceFolder}/pkg/jsonschema", - "console": "integratedTerminal", - }, - { - "name": "Attach to a running Lazygit", - "type": "go", - "request": "attach", - "mode": "local", - "processId": "lazygit", - "hideSystemGoroutines": true, - "console": "integratedTerminal", - }, - { - // To use this, first start an integration test with the "cli" runner and - // use the -debug option; e.g. - // $ make integration-test-cli -- -debug tag/reset.go - "name": "Attach to integration test runner", - "type": "go", - "request": "attach", - "mode": "local", - "processId": "test_lazygit", - "hideSystemGoroutines": true, - "console": "integratedTerminal", - }, - ], - "compounds": [ - { - "name": "Run with logs", - "configurations": [ - "Tail Lazygit logs", - "Debug Lazygit" - ], - "stopAll": true - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index dd4398af90b..00000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "gopls": { - "formatting.gofumpt": true, - "ui.diagnostic.staticcheck": true, - "ui.diagnostic.analyses": { - // This list must match the one in .golangci.yml - "SA1019": false, - "ST1003": false, - "ST1005": false, - "ST1006": false, - "QF1001": false, - "QF1003": false, - "QF1008": false, - "ST1000": false, - "ST1020": false, - "ST1021": false, - "ST1022": false, - // Dot imports; this warning is enabled in .golangci.yml, but with an - // extra dot-import-whitelist config. Because I couldn't figure out how to - // specify that extra config for gopls, I'm disabling the check altogether - // here. - "ST1001": false, - }, - }, - "go.alternateTools": { - "golangci-lint-v2": "${workspaceFolder}/scripts/golangci-lint-shim.sh", - }, - "go.lintTool": "golangci-lint-v2", -} diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 43627539469..00000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "Generate cheatsheet", - "type": "shell", - "command": "go run scripts/cheatsheet/main.go generate", - "problemMatcher": [], - }, - { - "label": "Bump gocui", - "type": "shell", - "command": "./scripts/bump_gocui.sh", - "problemMatcher": [], - }, - { - "label": "Bump lazycore", - "type": "shell", - "command": "./scripts/bump_lazycore.sh", - "problemMatcher": [], - }, - { - "label": "Run current file integration test", - "type": "shell", - "command": "go run cmd/integration_test/main.go cli ${relativeFile}", - "problemMatcher": [], - "group": { - "kind": "test", - "isDefault": true - }, - "presentation": { - "clear": true, - "focus": true - } - }, - { - "label": "Run current file integration test (slow)", - "type": "shell", - "command": "go run cmd/integration_test/main.go cli --slow ${relativeFile}", - "problemMatcher": [], - "group": { - "clear": true, - "kind": "test", - }, - "presentation": { - "focus": true - } - }, - { - "label": "Run current file integration test (sandbox)", - "type": "shell", - "command": "go run cmd/integration_test/main.go cli --sandbox ${relativeFile}", - "problemMatcher": [], - "group": { - "kind": "test", - }, - "presentation": { - "clear": true, - "focus": true - } - }, - { - "label": "Open deprecated test TUI", - "type": "shell", - "command": "go run pkg/integration/deprecated/cmd/tui/main.go", - "problemMatcher": [], - "group": { - "kind": "test", - }, - "presentation": { - "focus": true - } - }, - { - "label": "Sync tests list", - "type": "shell", - "command": "go generate pkg/integration/tests/tests.go", - "problemMatcher": [], - "group": { - "kind": "test", - }, - "presentation": { - "focus": true - } - }, - ], -} diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md deleted file mode 100644 index 931c6eb69ed..00000000000 --- a/CODE-OF-CONDUCT.md +++ /dev/null @@ -1,3 +0,0 @@ -# Lazygit Code of Conduct - -Be nice, or face the wrath of the maintainer. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 0c99f4af694..00000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,240 +0,0 @@ -# Contributing - -♥ We love pull requests from everyone ! - -When contributing to this repository, please first discuss the change you wish -to make via issue, email, or any other method with the owners of this repository -before making a change. - -## PR walkthrough - -[This video](https://www.youtube.com/watch?v=kNavnhzZHtk) walks through the process of adding a small feature to lazygit. If you have no idea where to start, watching that video is a good first step. - -## Design principles - -See [here](./VISION.md) for a set of design principles that we want to consider when building a feature or making a change. - -## Codebase guide - -[This doc](./docs/dev/Codebase_Guide.md) explains: -* what the different packages in the codebase are for -* where important files live -* important concepts in the code -* how the event loop works -* other useful information - -## All code changes happen through Pull Requests - -Pull requests are the best way to propose changes to the codebase. We actively -welcome your pull requests: - -1. Fork the repo and create your branch from `master`. -2. If you've added code that should be tested, add tests. -3. If you've added code that need documentation, update the documentation. -4. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). -5. Issue that pull request! - -Please do not raise pull request from your fork's master branch: make a feature branch instead. Lazygit maintainers will sometimes push changes to your branch when reviewing a PR and we often can't do this if you use your master branch. - -If you've never written Go in your life, then join the club! Lazygit was the maintainer's first Go program, and most contributors have never used Go before. Go is widely considered an easy-to-learn language, so if you're looking for an open source project to gain dev experience, you've come to the right place. - -## Running in a VSCode dev container - -If you want to spare yourself the hassle of setting up your dev environment yourself (i.e. installing Go, extensions, and extra tools), you can run the Lazygit code in a VSCode dev container like so: - -![image](https://user-images.githubusercontent.com/8456633/201500508-0d55f99f-5035-4a6f-a0f8-eaea5c003e5d.png) - -This requires that: -* you have docker installed -* you have the dev containers extension installed in VSCode - -See [here](https://code.visualstudio.com/docs/devcontainers/containers) for more info about dev containers. - -## Running in a Github Codespace - -If you want to start contributing to Lazygit with the click of a button, you can open the lazygit codebase in a Codespace. First fork the repo, then click to create a codespace: - -![image](https://user-images.githubusercontent.com/8456633/201500566-ffe9105d-6030-4cc7-a525-6570b0b413a2.png) - -To run lazygit from within the integrated terminal just go `go run main.go` - -This allows you to contribute to Lazygit without needing to install anything on your local machine. The Codespace has all the necessary tools and extensions pre-installed. - -## Using Nix for development - -If you use Nix, you can leverage the included flake to set up a complete development environment with all necessary dependencies: - -```sh -nix develop -``` - -This will drop you into a development shell that includes: -* Latest Go toolchain -* golangci-lint for code linting -* git and make - -You can also build and run lazygit using nix: - -```sh -# Build lazygit -nix build - -# Run lazygit directly -nix run -``` - -The nix flake supports multiple architectures (x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin) and provides a consistent development environment across different systems. - -## Code of conduct - -Please note by participating in this project, you agree to abide by the [code of conduct]. - -[code of conduct]: https://github.com/jesseduffield/lazygit/blob/master/CODE-OF-CONDUCT.md - -## Any contributions you make will be under the MIT Software License - -In short, when you submit code changes, your submissions are understood to be -under the same [MIT License](http://choosealicense.com/licenses/mit/) that -covers the project. Feel free to contact the maintainers if that's a concern. - -## Report bugs using Github's [issues](https://github.com/jesseduffield/lazygit/issues) - -We use GitHub issues to track public bugs. Report a bug by [opening a new -issue](https://github.com/jesseduffield/lazygit/issues/new); it's that easy! - -## Go - -This project is written in Go. Go is an opinionated language with strict idioms, but some of those idioms are a little extreme. Some things we do differently: - -1. There is no shame in using `self` as a receiver name in a struct method. In fact we encourage it -2. There is no shame in prefixing an interface with 'I' instead of suffixing with 'er' when there are several methods on the interface. -3. If a struct implements an interface, we make it explicit with something like: - -```go -var _ MyInterface = &MyStruct{} -``` - -This makes the intent clearer and means that if we fail to satisfy the interface we'll get an error in the file that needs fixing. - -### Code Formatting - -To check code formatting [gofumpt](https://pkg.go.dev/mvdan.cc/gofumpt#section-readme) (which is a bit stricter than [gofmt](https://pkg.go.dev/cmd/gofmt)) is used. -VSCode will format the code correctly if you tell the Go extension to use `gofumpt` via your [`settings.json`](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson) -by setting [`formatting.gofumpt`](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#gofumpt-bool) to `true`: - -```jsonc -// .vscode/settings.json -{ - "gopls": { - "formatting.gofumpt": true - } -} -``` - -To run gofumpt from your terminal go: - -``` -go install mvdan.cc/gofumpt@latest && gofumpt -l -w . -``` - -## Programming Font - -Lazygit supports [Nerd Fonts](https://www.nerdfonts.com) to render certain icons. Sometimes we use some of these icons verbatim in string literals in the code (mainly in tests), so you need to set your development environment to use a nerd font to see these. - -## Internationalisation - -Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. - -### For developers adding new text - -If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `self.c.Tr.YourNewText`, etc). - -Note, we use 'Sentence case' for everything (so no 'Title Case' or 'whatever-it's-called-when-there's-no-capital-letters-case') - -### For translators - -Lazygit translations are managed through [Crowdin](https://crowdin.com/project/lazygit/). If you'd like to contribute translations: - -1. Join the Crowdin project at https://crowdin.com/project/lazygit/ -2. Select your target language and help translate missing strings -3. The translation files in `pkg/i18n/translations/` are managed by the maintainers - please don't edit them directly - -For detailed information about the translation process, including how maintainers sync translations, see `pkg/i18n/translations/README.md`. - -## Debugging - -The easiest way to debug lazygit is to have two terminal tabs open at once: one for running lazygit (via `go run main.go -debug` in the project root) and one for viewing lazygit's logs (which can be done via `go run main.go --logs` or just `lazygit --logs`). - -From most places in the codebase you have access to a logger e.g. `gui.Log.Warn("blah")` or `self.c.Log.Warn("blah")`. - -If you find that the existing logs are too noisy, you can set the log level with e.g. `LOG_LEVEL=warn go run main.go -debug` and then only use `Warn` logs yourself. - -If you need to log from code in the vendor directory (e.g. the `gocui` package), you won't have access to the logger, but you can easily add logging support by setting the `LAZYGIT_LOG_PATH` environment variable and using `logs.Global.Warn("blah")`. This is a global logger that's only intended for development purposes. - -If you keep having to do some setup steps to reproduce an issue, read the Testing section below to see how to create an integration test by recording a lazygit session. It's pretty easy! - -### VSCode debugger - -If you want to trigger a debug session from VSCode, you can use the following snippet. Note that the `console` key is, at the time of writing, still an experimental feature. - -```jsonc -// .vscode/launch.json -{ - "version": "0.2.0", - "configurations": [ - { - "name": "debug lazygit", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "main.go", - "args": ["--debug"], - "console": "externalTerminal" // <-- you need this to actually see the lazygit UI in a window while debugging - } - ] -} -``` - -## Profiling - -If you want to investigate what's contributing to CPU or memory usage, see [this separate document](docs/dev/Profiling.md). - -## Testing - -Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go in files that end in `_test.go`, and are written in Go. For integration tests, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) - -## Updating Gocui - -Sometimes you will need to make a change in the gocui fork (https://github.com/jesseduffield/gocui). Gocui is the package responsible for rendering windows and handling user input. Here's the typical process to follow: - -1. Make the changes in gocui inside lazygit's vendor directory so it's easy to test against lazygit -2. Copy the changes over to the actual gocui repo (clone it if you haven't already, and use the `awesome` branch, not `master`) -3. Raise a PR on the gocui repo with your changes -4. After that PR is merged, make a PR in lazygit bumping the gocui version. You can bump the version by running the following at the lazygit repo root: - -```sh -./scripts/bump_gocui.sh -``` - -5. Raise a PR in lazygit with those changes - -## Updating Lazycore - -[Lazycore](https://github.com/jesseduffield/lazycore) is a repo containing shared functionality between lazygit and lazydocker. Sometimes you will need to make a change to that repo and import the changes into lazygit. Similar to updating Gocui, here's what you do: - -1. Make the changes in lazycore inside lazygit's vendor directory so it's easy to test against lazygit -2. Copy the changes over to the actual lazycore repo (clone it if you haven't already, and use the `master` branch) -3. Raise a PR on the lazycore repo with your changes -4. After that PR is merged, make a PR in lazygit bumping the lazycore version. You can bump the version by running the following at the lazygit repo root: - -```sh -./scripts/bump_lazycore.sh -``` - -Or if you're using VSCode, there is a bump lazycore task you can find by going `cmd+shift+p` and typing 'Run task' - -5. Raise a PR in lazygit with those changes - -## Improvements - -If you can think of any way to improve these docs let us know. diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 8a18ad8d32d..00000000000 --- a/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -# run with: -# docker build -t lazygit . -# docker run -it lazygit:latest /bin/sh - -FROM golang:1.25 as build -WORKDIR /go/src/github.com/jesseduffield/lazygit/ -COPY go.mod go.sum ./ -RUN go mod download -COPY . . -RUN CGO_ENABLED=0 GOOS=linux go build - -FROM alpine:3.19 -RUN apk add --no-cache -U git xdg-utils -WORKDIR /go/src/github.com/jesseduffield/lazygit/ -COPY --from=build /go/src/github.com/jesseduffield/lazygit ./ -COPY --from=build /go/src/github.com/jesseduffield/lazygit/lazygit /bin/ -RUN echo "alias gg=lazygit" >> ~/.profile - -ENTRYPOINT [ "lazygit" ] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 83121f5a650..00000000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Jesse Duffield - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Makefile b/Makefile deleted file mode 100644 index 38dc118cb82..00000000000 --- a/Makefile +++ /dev/null @@ -1,72 +0,0 @@ -.PHONY: all -all: build - -.PHONY: build -build: - go build -gcflags='all=-N -l' - -.PHONY: install -install: - go install - -.PHONY: run -run: build - ./lazygit - -# Run `make run-debug` in one terminal tab and `make print-log` in another to view the program and its log output side by side -.PHONY: run-debug -run-debug: - go run main.go -debug - -.PHONY: print-log -print-log: - go run main.go --logs - -.PHONY: unit-test -unit-test: - go test ./... -short - -.PHONY: test -test: unit-test integration-test-all - -# Generate all our auto-generated files (test list, cheatsheets, maybe other things in the future) -.PHONY: generate -generate: - go generate ./... - -.PHONY: format -format: - gofumpt -l -w . - -.PHONY: lint -lint: - ./scripts/golangci-lint-shim.sh run - -# For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md. -.PHONY: integration-test-tui -integration-test-tui: - go run cmd/integration_test/main.go tui $(filter-out $@,$(MAKECMDGOALS)) - -.PHONY: integration-test-cli -integration-test-cli: - go run cmd/integration_test/main.go cli $(filter-out $@,$(MAKECMDGOALS)) - -.PHONY: integration-test-all -integration-test-all: - go test pkg/integration/clients/*.go - -.PHONY: bump-gocui -bump-gocui: - scripts/bump_gocui.sh - -.PHONY: bump-lazycore -bump-lazycore: - scripts/bump_lazycore.sh - -.PHONY: record-demo -record-demo: - demo/record_demo.sh $(filter-out $@,$(MAKECMDGOALS)) - -.PHONY: vendor -vendor: - go mod vendor && go mod tidy diff --git a/README.md b/README.md deleted file mode 100644 index e3bebd8ffad..00000000000 --- a/README.md +++ /dev/null @@ -1,613 +0,0 @@ -
-Special thanks to: -
-
- -
- Warp -
- Warp, the intelligent terminal -
- Available for MacOS and Linux -
-
- Visit warp.dev to learn more. -
-
-
-
- -
- Tuple -
- Tuple, the premier screen sharing app for developers on macOS and Windows. -
-
-
-
- -
- Subble -
- I (Jesse) co-founded Subble to save your company time and money by finding unused and over-provisioned SaaS licences. Check it out! -
-
- -
-
- -

- -

- -
- -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) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/f46416b715d74622895657935fcada21)](https://app.codacy.com/gh/jesseduffield/lazygit/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/f46416b715d74622895657935fcada21)](https://app.codacy.com/gh/jesseduffield/lazygit/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage) [![golangci-lint](https://img.shields.io/badge/linted%20by-golangci--lint-brightgreen)](https://golangci-lint.run/) [![GitHub tag](https://img.shields.io/github/v/tag/jesseduffield/lazygit?color=blue)](https://github.com/jesseduffield/lazygit/releases/latest) [![homebrew](https://img.shields.io/homebrew/v/lazygit?color=blue)](https://formulae.brew.sh/formula/lazygit) - -![commit_and_push](../assets/demo/commit_and_push-compressed.gif) - -
- -## Sponsors - -

- Maintenance of this project is made possible by all the contributors and sponsors. If you'd like to sponsor this project and have your avatar or company logo appear below click here. 💙 -

- -

-Mark LussierDean HerbertPeter BjorklundReilly WoodOliver GüntherPawan DhananjayBartłomiej DachCarsten GehlingCEUKHolden LucasChau TranmatejciktheAverageDev (Luca Tumedei)Nicholas CloudAliaksandr StelmachonakBurgy BenjaminJoe KlemmerTobias LütkeBen BeaumontHollyCasey BoettcherJeff ForcierMaciej T. NowakYuryAndreas KurthBraden SteffaniakAlex MeddinJordan GillardSebastianAndy SlezakMartin KockJesse AlamaDaniel KokottJan HeijmansKevin NowaldOmar LuqEthan LiMaxiJan ZenknerFrederick MorlockMaximilian LangenfeldDavis BulsNeil LambertDavid Heinemeier HanssonEthan FischerTerry TaiAdam RoesnerTim MorganMax ShypulniakKovács ÁdámPatricio SerranoKiriJohn Even BjørnevikMichael OberstStian HegglundAdam TrepanierKenth FagerlundJulien TardotAaron ArredondoEllord TayagEdgar Post-Buijssbc64Pierre SpringZac ClayThomas MüllerCarl AssmannSergey OgnevMoody LiuAlex GMichael HowardLasse Bloch LauritsenLarry MarburgerDavid BrockmanAlexander SlavschikAidan GaulandMaksym BieńkowskiJoshua WootonnGurbinder SinghSimon Sandvik LeeThomas GilbertSzymon MuchaTim ShilovUnnawut LeepaisalsuwannaBret WortmanAndré LameirinhasScott VelezjustinMayfieldSoma Holiday -

- -## Elevator Pitch - -Rant time: You've heard it before, git is _powerful_, but what good is that power when everything is so damn hard to do? Interactive rebasing requires you to edit a goddamn TODO file in your editor? _Are you kidding me?_ To stage part of a file you need to use a command line program to step through each hunk and if a hunk can't be split down any further but contains code you don't want to stage, you have to edit an arcane patch file _by hand_? _Are you KIDDING me?!_ Sometimes you get asked to stash your changes when switching branches only to realise that after you switch and unstash that there weren't even any conflicts and it would have been fine to just checkout the branch directly? _YOU HAVE GOT TO BE KIDDING ME!_ - -If you're a mere mortal like me and you're tired of hearing how powerful git is when in your daily life it's a powerful pain in your ass, lazygit might be for you. - -## Table of contents - -- [Sponsors](#sponsors) -- [Elevator Pitch](#elevator-pitch) -- [Table of contents](#table-of-contents) -- [Features](#features) - - [Stage individual lines](#stage-individual-lines) - - [Interactive Rebase](#interactive-rebase) - - [Cherry-pick](#cherry-pick) - - [Bisect](#bisect) - - [Nuke the working tree](#nuke-the-working-tree) - - [Amend an old commit](#amend-an-old-commit) - - [Filter](#filter) - - [Invoke a custom command](#invoke-a-custom-command) - - [Worktrees](#worktrees) - - [Rebase magic (custom patches)](#rebase-magic-custom-patches) - - [Rebase from marked base commit](#rebase-from-marked-base-commit) - - [Undo](#undo) - - [Commit graph](#commit-graph) - - [Compare two commits](#compare-two-commits) -- [Tutorials](#tutorials) -- [Installation](#installation) - - [Binary Releases](#binary-releases) - - [Dev container](#dev-container-feature) - - [Homebrew](#homebrew) - - [MacPorts](#macports) - - [Void Linux](#void-linux) - - [Scoop (Windows)](#scoop-windows) - - [Arch Linux](#arch-linux) - - [Fedora and RHEL](#fedora-and-rhel) - - [Solus Linux](#solus-linux) - - [Debian and Ubuntu](#debian-and-ubuntu) - - [Funtoo Linux](#funtoo-linux) - - [Gentoo Linux](#gentoo-linux) - - [FreeBSD](#freebsd) - - [Termux](#termux) - - [Conda](#conda) - - [Go](#go) - - [Chocolatey (Windows)](#chocolatey-windows) - - [Winget (Windows 10 1709 or later)](#winget-windows-10-1709-or-later) - - [Manual](#manual) -- [Usage](#usage) - - [Keybindings](#keybindings) - - [Changing Directory On Exit](#changing-directory-on-exit) - - [Undo/Redo](#undoredo) -- [Configuration](#configuration) - - [Custom Pagers](#custom-pagers) - - [Custom Commands](#custom-commands) - - [Git flow support](#git-flow-support) -- [Contributing](#contributing) - - [Debugging Locally](#debugging-locally) -- [Donate](#donate) -- [FAQ](#faq) - - [What do the commit colors represent?](#what-do-the-commit-colors-represent) -- [Shameless Plug](#shameless-plug) -- [Alternatives](#alternatives) - -Lazygit is not my fulltime job but it is a hefty part time job so if you want to support the project please consider [sponsoring me](https://github.com/sponsors/jesseduffield) - -## Features - -### Stage individual lines - -Press space on the selected line to stage it, or press `v` to start selecting a range of lines. You can also press `a` to select the entirety of the current hunk. - -![stage_lines](../assets/demo/stage_lines-compressed.gif) - -### Interactive Rebase - -Press `i` to start an interactive rebase. Then squash (`s`), fixup (`f`), drop (`d`), edit (`e`), move up (`ctrl+k`) or move down (`ctrl+j`) any of TODO commits, before continuing the rebase by bringing up the rebase options menu with `m` and then selecting `continue`. - -You can also perform any these actions as a once-off (e.g. pressing `s` on a commit to squash it) without explicitly starting a rebase. - -This demo also uses shift+down to select a range of commits to move and fixup. - -![interactive_rebase](../assets/demo/interactive_rebase-compressed.gif) - -### Cherry-pick - -Press `shift+c` on a commit to copy it and press `shift+v` to paste (cherry-pick) it. - -![cherry_pick](../assets/demo/cherry_pick-compressed.gif) - -### Bisect - -Press `b` in the commits view to mark a commit as good/bad in order to begin a git bisect. - -![bisect](../assets/demo/bisect-compressed.gif) - -### Nuke 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=N4E2B_k2Bss), press `shift+d` to bring up the reset options menu and then select the 'nuke' option. - -![Nuke working tree](../assets/demo/nuke_working_tree-compressed.gif) - -### Amend an old commit - -Pressing `shift+a` on any commit will amend that commit with the currently staged changes (running an interactive rebase in the background). - -![amend_old_commit](../assets/demo/amend_old_commit-compressed.gif) - -### Filter - -You can filter a view with `/`. Here we filter down our branches view and then hit `enter` to view its commits. - -![filter](../assets/demo/filter-compressed.gif) - -### Invoke a custom command - -Lazygit has a very flexible [custom command system](docs/Custom_Command_Keybindings.md). In this example a custom command is defined which emulates the built-in branch checkout action. - -![custom_command](../assets/demo/custom_command-compressed.gif) - -### Worktrees - -You can create worktrees to have multiple branches going at once without the need for stashing or creating WIP commits when switching between them. Press `w` in the branches view to create a worktree from the selected branch and switch to it. - -![worktree_create_from_branches](../assets/demo/worktree_create_from_branches-compressed.gif) - -### Rebase magic (custom patches) - -You can build a custom patch from an old commit and then remove the patch from the commit, split out a new commit, apply the patch in reverse to the index, and more. - -In this example we have a redundant comment that we want to remove from an old commit. We hit `` on the commit to view its files, then `` on a file to focus the patch, then `` to add the comment line to our custom patch, and then `ctrl+p` to view the custom patch options; selecting to remove the patch from the current commit. - -Learn more in the [Rebase magic Youtube tutorial](https://youtu.be/4XaToVut_hs). - -![custom_patch](../assets/demo/custom_patch-compressed.gif) - -### Rebase from marked base commit - -Say you're on a feature branch that was itself branched off of the develop branch, and you've decided you'd rather be branching off the master branch. You need a way to rebase only the commits from your feature branch. In this demo we check to see which was the last commit on the develop branch, then press `shift+b` to mark that commit as our base commit, then press `r` on the master branch to rebase onto it, only bringing across the commits from our feature branch. Then we push our changes with `shift+p`. - -![rebase_onto](../assets/demo/rebase_onto-compressed.gif) - -### Undo - -You can undo the last action by pressing `z` and redo with `ctrl+z`. Here we drop a couple of commits and then undo the actions. -Undo uses the reflog which is specific to commits and branches so we can't undo changes to the working tree or stash. - -[More info](/docs/Undoing.md) - -![undo](../assets/demo/undo-compressed.gif) - -### Commit graph - -When viewing the commit graph in an enlarged window (use `+` and `_` to cycle screen modes), the commit graph is shown. Colours correspond to the commit authors, and as you navigate down the graph, the parent commits of the selected commit are highlighted. - -![commit_graph](../assets/demo/commit_graph-compressed.gif) - -### Compare two commits - -If you press `shift+w` on a commit (or branch/ref) a menu will open that allows you to mark that commit so that any other commit you select will be diffed against it. Once you've selected the second commit, you'll see the diff in the main view and if you press `` you'll see the files of the diff. You can press `shift+w` to view the diff menu again to see options like reversing the diff direction or exiting diff mode. You can also exit diff mode by pressing ``. - -![diff_commits](../assets/demo/diff_commits-compressed.gif) - -## Tutorials - -[](https://youtu.be/CPLdltN7wgE) - -- [15 Lazygit Features in 15 Minutes](https://youtu.be/CPLdltN7wgE) -- [Basics Tutorial](https://youtu.be/VDXvbHZYeKY) -- [Rebase Magic Tutorial](https://youtu.be/4XaToVut_hs) - -## Installation - -[![Packaging status](https://repology.org/badge/vertical-allrepos/lazygit.svg?columns=3)](https://repology.org/project/lazygit/versions) - -_Most of the above packages are maintained by third parties so be sure to vet them yourself and confirm that the maintainer is a trustworthy looking person who attends local sports games and gives back to their communities with barbeque fundraisers etc_ - -### Binary Releases - -For Windows, Mac OS(10.12+) or Linux, you can download a binary release [here](../../releases). - -### Dev container feature - -If you want to use lazygit in e.g. one of your GitHub Codespaces, there is a third-party [dev container feature](https://github.com/GeorgOfenbeck/features/tree/main/src/lazygit-linuxbinary) based on the binary releases mentioned above. - -### Homebrew - -It works with Linux, too. - -```sh -brew install lazygit -``` - -### MacPorts - -Latest version built from github releases. -Tap: - -``` -sudo port install lazygit -``` - -### Void Linux - -Packages for Void Linux are available in the distro repo - -They follow upstream latest releases - -```sh -sudo xbps-install -S lazygit -``` - -### Scoop (Windows) - -You can install `lazygit` using [scoop](https://scoop.sh/). It's in the `extras` bucket: - -```sh -# Add the extras bucket -scoop bucket add extras - -# Install lazygit -scoop install lazygit -``` - -### gah (Linux and Mac OS) - -You can install `lazygit` using [gah](https://github.com/marverix/gah/): - -```sh -gah install lazygit -``` - -### Arch Linux - -Packages for Arch Linux are available via pacman and AUR (Arch User Repository). - -There are two packages. The stable one which is built with the latest release -and the git version which builds from the most recent commit. - -- Stable: `sudo pacman -S lazygit` -- Development: - -Instruction of how to install AUR content can be found here: - - -### Fedora / Amazon Linux 2023 / CentOS Stream - -Packages for Fedora, Amazon Linux 2023 and CentOS Stream are available via -[Copr](https://copr.fedorainfracloud.org/coprs/dejan/lazygit/) (Cool Other Package Repo). - -```sh -sudo dnf copr enable dejan/lazygit -sudo dnf install lazygit -``` - -These packages are built using the RPM spec file located here: https://codeberg.org/dejan/rpm-lazygit - -You should be able to build RPMs for Fedora 41 or older, and other Fedora derivatives using the -SRPM (Source RPM) file that you can grab from the latest COPR build. - -### Solus Linux - -```sh -sudo eopkg install lazygit -``` - -### Debian and Ubuntu - -For **Debian 13 "Trixie", Sid**, and later, or **Ubuntu 25.10 "Questing Quokka"** and later: - -```sh -sudo apt install lazygit -``` - -For **Debian 12 "Bookworm", Ubuntu 25.04 "Plucky Puffin"** and earlier: - -```sh -LAZYGIT_VERSION=$(curl -s "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | \grep -Po '"tag_name": *"v\K[^"]*') -curl -Lo lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/download/v$%7BLAZYGIT_VERSION%7D/lazygit_$%7BLAZYGIT_VERSION%7D_Linux_x86_64.tar.gz" -tar xf lazygit.tar.gz lazygit -sudo install lazygit -D -t /usr/local/bin/ -``` - -Verify the correct installation of lazygit: - -```sh -lazygit --version -``` - -### Funtoo Linux - -Funtoo Linux has an autogenerated lazygit package in [dev-kit](https://github.com/funtoo/dev-kit/tree/1.4-release/dev-vcs/lazygit): - -```sh -sudo emerge dev-vcs/lazygit -``` - -### Gentoo Linux - -Lazygit is not (yet) in main Gentoo portage, however an ebuild is available in [GURU overlay](https://github.com/gentoo-mirror/guru/tree/master/dev-vcs/lazygit) - -You can either add the overlay to your system and install lazygit as usual: - -```sh -sudo eselect repository enable guru -sudo emaint sync -r guru -sudo emerge dev-vcs/lazygit -``` - -### openSUSE - -The lazygit package is currently built in [devel:languages:go/lazygit](https://build.opensuse.org/package/show/devel:languages:go/lazygit). - -To install lazygit on openSUSE Tumbleweed run: - -```sh -sudo zypper ar https://download.opensuse.org/repositories/devel:/languages:/go/openSUSE_Factory/devel:languages:go.repo -sudo zypper ref && sudo zypper in lazygit -``` - -To install lazygit on openSUSE Leap run: - -```sh -source /etc/os-release -sudo zypper ar https://download.opensuse.org/repositories/devel:/languages:/go/$VERSION_ID/devel:languages:go.repo -sudo zypper ref && sudo zypper in lazygit -``` - -### NixOS - -#### Using lazygit from nixpkgs - -On NixOS, lazygit is packaged with nix and distributed via nixpkgs. -You can try lazygit without installing it with: - -```sh -nix-shell -p lazygit -# or with flakes enabled -nix run nixpkgs#lazygit -``` -Or you can add lazygit to your `configuration.nix` using the `environment.systemPackages` option. -More details can be found via NixOS search [page](https://search.nixos.org/). - -#### Using the official lazygit flake - -This repository includes a nix flake that provides the latest development version and additional development tools: - -**Run lazygit directly from the repository:** -```sh -nix run github:jesseduffield/lazygit -# or from a local clone -nix run . -``` - -**Build lazygit from source:** -```sh -nix build github:jesseduffield/lazygit -# or from a local clone -nix build . -``` - -**Development environment:** -For contributors, the flake provides a development shell with Go toolchain, development tools, and dependencies: -```sh -nix develop github:jesseduffield/lazygit -# or from a local clone -nix develop -``` - -The development shell includes: -- Go toolchain -- git and make -- Proper environment variables for development - -**Using in other flakes:** -The flake also provides an overlay for easy integration into other flake-based projects: -```nix -{ - inputs.lazygit.url = "github:jesseduffield/lazygit"; - - outputs = { self, nixpkgs, lazygit }: { - # Use the overlay - nixpkgs.overlays = [ lazygit.overlays.default ]; - }; -} -``` - -### Flox - -Lazygit can be installed into a Flox environment as follows. - -```sh -flox install lazygit -``` - -More details about Flox can be found on [their website](https://flox.dev/). - -### FreeBSD - -```sh -pkg install lazygit -``` - -### Termux - -```sh -apt install lazygit -``` - -### Conda - -Released versions are available for different platforms, see - -```sh -conda install -c conda-forge lazygit -``` - -### Go - -```sh -go install github.com/jesseduffield/lazygit@latest -``` - -Please note: -If you get an error claiming that lazygit cannot be found or is not defined, you -may need to add `~/go/bin` to your $PATH (MacOS/Linux), or `%HOME%\go\bin` -(Windows). Not to be mistaken for `C:\Go\bin` (which is for Go's own binaries, -not apps like lazygit). - -### Chocolatey (Windows) - -You can install `lazygit` using [Chocolatey](https://chocolatey.org/): - -```sh -choco install lazygit -``` - -### Winget (Windows 10 1709 or later) - -You can install `lazygit` using the `winget` command in the Windows Terminal with the following command: - -```powershell -winget install -e --id=JesseDuffield.lazygit -``` - -### Manual - -You'll need to [install Go](https://golang.org/doc/install) - -``` -git clone https://github.com/jesseduffield/lazygit.git -cd lazygit -go install -``` - -You can also use `go run main.go` to compile and run in one go (pun definitely intended) - -## Usage - -Call `lazygit` in your terminal inside a git repository. - -```sh -$ lazygit -``` - -If you want, you can -also add an alias for this with `echo "alias lg='lazygit'" >> ~/.zshrc` (or -whichever rc file you're using). - -### Keybindings - -You can check out the list of keybindings [here](/docs/keybindings). - -### Changing Directory On Exit - -If you change repos in lazygit and want your shell to change directory into that repo on exiting lazygit, add this to your `~/.zshrc` (or other rc file): - -``` -lg() -{ - export LAZYGIT_NEW_DIR_FILE=~/.lazygit/newdir - - lazygit "$@" - - if [ -f $LAZYGIT_NEW_DIR_FILE ]; then - cd "$(cat $LAZYGIT_NEW_DIR_FILE)" - rm -f $LAZYGIT_NEW_DIR_FILE > /dev/null - fi -} -``` - -Then `source ~/.zshrc` and from now on when you call `lg` and exit you'll switch directories to whatever you were in inside lazygit. To override this behaviour you can exit using `shift+Q` rather than just `q`. - -### Undo/Redo - -See the [docs](/docs/Undoing.md) - -## Configuration - -Check out the [configuration docs](docs/Config.md). - -### Custom Pagers - -See the [docs](docs/Custom_Pagers.md) - -### Custom Commands - -If lazygit is missing a feature, there's a good chance you can implement it yourself with a custom command! - -See the [docs](docs/Custom_Command_Keybindings.md) - -### Git flow support - -Lazygit supports [Gitflow](https://github.com/nvie/gitflow) if you have it installed. To understand how the Gitflow model works check out Vincent Driessen's original [post](https://nvie.com/posts/a-successful-git-branching-model/) explaining it. To view Gitflow options from within Lazygit, press `i` from within the branches view. - -## Contributing - -We love your input! Please check out the [contributing guide](CONTRIBUTING.md). -For contributor discussion about things not better discussed here in the repo, join the [discord channel](https://discord.gg/ehwFt2t4wt) - - - -Check out this [video](https://www.youtube.com/watch?v=kNavnhzZHtk) walking through the creation of a small feature in lazygit if you want an idea of where to get started. - -### Debugging Locally - -Run `lazygit --debug` in one terminal tab and `lazygit --logs` in another to view the program and its log output side by side - -## Donate - -If you would like to support the development of lazygit, consider [sponsoring me](https://github.com/sponsors/jesseduffield) (github is matching all donations dollar-for-dollar for 12 months) - -## FAQ - -### What do the commit colors represent? - -- Green: the commit is included in the master branch -- Yellow: the commit is not included in the master branch -- Red: the commit has not been pushed to the upstream branch - -## Shameless Plug - -If you want to see what I (Jesse) am up to in terms of development, follow me on -[twitter](https://twitter.com/DuffieldJesse) or check out my [blog](https://jesseduffield.com/) - -## Alternatives - -If you find that lazygit doesn't quite satisfy your requirements, these may be a better fit: - -- [GitUI](https://github.com/Extrawurst/gitui) -- [tig](https://github.com/jonas/tig) diff --git a/VISION.md b/VISION.md deleted file mode 100644 index 9ce4c999e24..00000000000 --- a/VISION.md +++ /dev/null @@ -1,104 +0,0 @@ -# Vision and Design Principles - -## Vision - -Lazygit's vision is to be the most enjoyable UI for git. - -## Design Principles - -There are seven (sometimes contradictory) design principles we follow: - -- Discoverability -- Simplicity -- Safety -- Power -- Speed -- Conformity with git -- Think of the codebase - -### Discoverability - -TUI's are notoriously hard to learn, thanks to limited screen real-estate to provide contextual help and a general lack of effort on the part of developers to make things obvious. We want Lazygit to buck the trend and be easy for a new user to grok. - -Examples: - -- Clearly document all the features/configuration options - - e.g. gifs in the README -- Document how to solve various git problems with Lazygit - - This is something we don't have yet but should: a section in the docs explaining how Lazygit can help you in various scenarios -- Use tooltips to explain what actions will do -- Make it easy for users to ask questions and get answers from the community -- Make it easy to find entities and actions from within Lazygit -- Use visual elements to make things obvious - - e.g. '<-- YOU ARE HERE' label when rebasing -- Don't require the user to memorise keybindings - - e.g. when the user is mid-rebase, we prominently show that the keybinding for viewing rebase options is 'm' -- When the user performs an action in Lazygit, make the impact obvious - - If the affected entity isn't visible, show a toast notification -- If a keybinding is disabled, give a reason why - -### Simplicity - -The git CLI is very complex but most git use cases are simple. Lazygit needs to ensure that simple use cases are easy to satisfy. - -- Make the most common use cases dead-simple (staging files, committing, pulling/pushing) -- Don't overwhelm the user with options -- Use sensible defaults -- We already have too many configuration options: think hard before adding any new ones - -### Safety - -It's easy to screw things up in git so Lazygit should try to protect the user from screwing things up. - -- Prompt for a confirmation before doing anything that's hard to reverse -- Make it easy to correct mistakes - - e.g. undo action - - the escape key should get you out of most transient situations (rebasing, diffing, etc) - -## Power - -Users shouldn't have to drop down the CLI _too_ often. Lazygit should be able to handle some complex use cases. - -- Make complex (but common) CLI flows simple - - e.g. interactive rebasing -- Use the custom commands system to handle the really rare complex edge-cases - -### Speed - -Pro users should be able to move at lightning speed with Lazygit. - -- Always think about the number of keypresses involved in a given UX flow -- Make lazygit performant and responsive -- Think about the individual commands being run and how fast they are -- Startup should be FAST. If you want to run something at startup that is slow, make it non-blocking. -- Support muscle-memory - - Prefer disabling menu items instead of hiding them so that muscle memory can be used to select the desired menu item - - Try to make keybinding intuitions to transfer across contexts (e.g. 'd' for destroy) - - When changing keybindings in a new release, always consider what will happen if a user does not read the release notes and relies on muscle memory. - -### Conformity with git - -Satisfying the use-cases of git users is more important than perfectly conforming to git's API, but even obscure parts of git's API were motivated by real use-cases. - -- Users should only have to drop down to the git CLI in rare circumstances -- Honour the git config - - Don't override anything set in the git config without the user's permission -- Work with git, not against it. - - Too much magic will get us into trouble -- Avoid storing Lazygit-specific session state that could instead be stored in git -- Ensure that Lazygit can represent the state of any repo -- Sometimes git's default behaviour is just silly and we'll make the call to override but it should be a well-considered decision. - -### Think of the codebase - -Will somebody PLEASE think of the codebase! - -Some features are not worth the added complexity in the codebase. The more this codebase grows, the harder it will be to make the changes that everybody wants. - -## Resolving conflicts - -Many of the above objectives are directly antithetical to one another. If you add an extra confirmation prompt for the sake of _safety_, you're sacrificing _speed_. If you support toggling various git flags in the name of _power_, you're sacrificing _simplicity_. There are a few things to say here. - -When there are conflicts, we need to make a judgement call. In general we should err on the side of safety and simplicity as the default, with the ability for users to make things faster / more powerful either through configuration or separate keybindings. - -This does not mean for example that force pushes should be impossible without being manually enabled: force pushes are table stakes for anybody who rebases. But it does mean that a confirmation popup should appear when force pushing. diff --git a/cmd/i18n/main.go b/cmd/i18n/main.go deleted file mode 100644 index f388c396b13..00000000000 --- a/cmd/i18n/main.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "encoding/json" - "log" - "os" - - "github.com/jesseduffield/lazygit/pkg/i18n" -) - -func saveLanguageFileToJson(tr *i18n.TranslationSet, filepath string) error { - jsonData, err := json.MarshalIndent(tr, "", " ") - if err != nil { - return err - } - - jsonData = append(jsonData, '\n') - return os.WriteFile(filepath, jsonData, 0o644) -} - -func main() { - err := saveLanguageFileToJson(i18n.EnglishTranslationSet(), "en.json") - if err != nil { - log.Fatal(err) - } -} diff --git a/cmd/integration_test/main.go b/cmd/integration_test/main.go deleted file mode 100644 index 711ce8b3370..00000000000 --- a/cmd/integration_test/main.go +++ /dev/null @@ -1,84 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - - "github.com/jesseduffield/lazygit/pkg/integration/clients" -) - -var usage = ` -Usage: - See https://github.com/jesseduffield/lazygit/tree/master/pkg/integration/README.md - - CLI mode: - > go run cmd/integration_test/main.go cli [--slow] [--sandbox] ... - If you pass no test names, it runs all tests - Accepted environment variables: - INPUT_DELAY (e.g. 200): the number of milliseconds to wait between keypresses or mouse clicks - - TUI mode: - > go run cmd/integration_test/main.go tui - This will open up a terminal UI where you can run tests - - Help: - > go run cmd/integration_test/main.go help -` - -type flagInfo struct { - name string // name of the flag; can be used with "-" or "--" - flag *bool // a pointer to the variable that should be set to true when this flag is passed -} - -// Takes the args that you want to parse (excluding the program name and any -// subcommands), and returns the remaining args with the flags removed -func parseFlags(args []string, flags []flagInfo) []string { -outer: - for len(args) > 0 { - for _, f := range flags { - if args[0] == "-"+f.name || args[0] == "--"+f.name { - *f.flag = true - args = args[1:] - continue outer - } - } - break - } - - return args -} - -func main() { - if len(os.Args) < 2 { - log.Fatal(usage) - } - - switch os.Args[1] { - case "help": - fmt.Println(usage) - case "cli": - slow := false - sandbox := false - waitForDebugger := false - raceDetector := false - testNames := parseFlags(os.Args[2:], []flagInfo{ - {"slow", &slow}, - {"sandbox", &sandbox}, - {"debug", &waitForDebugger}, - {"race", &raceDetector}, - }) - clients.RunCLI(testNames, slow, sandbox, waitForDebugger, raceDetector) - case "tui": - raceDetector := false - remainingArgs := parseFlags(os.Args[2:], []flagInfo{ - {"race", &raceDetector}, - }) - if len(remainingArgs) > 0 { - log.Fatal("tui only supports the -race argument.") - } - clients.RunTUI(raceDetector) - default: - log.Fatal(usage) - } -} diff --git a/colored-border-example.png b/colored-border-example.png new file mode 100644 index 00000000000..06bb7bf8b82 Binary files /dev/null and b/colored-border-example.png differ diff --git a/custom-command-keybindings.gif b/custom-command-keybindings.gif new file mode 100644 index 00000000000..76d832df884 Binary files /dev/null and b/custom-command-keybindings.gif differ diff --git a/default.nix b/default.nix deleted file mode 100644 index c48b1fb8cbb..00000000000 --- a/default.nix +++ /dev/null @@ -1,12 +0,0 @@ -(import ( - let - lock = builtins.fromJSON (builtins.readFile ./flake.lock); - nodeName = lock.nodes.root.inputs.flake-compat; - in - fetchTarball { - url = - lock.nodes.${nodeName}.locked.url - or "https://github.com/edolstra/flake-compat/archive/$%7Block.nodes.$%7BnodeName%7D.locked.rev%7D.tar.gz"; - sha256 = lock.nodes.${nodeName}.locked.narHash; - } -) { src = ./.; }).defaultNix diff --git a/demo/README.md b/demo/README.md deleted file mode 100644 index 422ab37a8f4..00000000000 --- a/demo/README.md +++ /dev/null @@ -1,2 +0,0 @@ -This directory contains stuff for recording lazygit demos. - diff --git a/demo/amend_old_commit-compressed.gif b/demo/amend_old_commit-compressed.gif new file mode 100644 index 00000000000..beaa9cbba7a Binary files /dev/null and b/demo/amend_old_commit-compressed.gif differ diff --git a/demo/amend_old_commit.gif b/demo/amend_old_commit.gif new file mode 100644 index 00000000000..e283cf3ea17 Binary files /dev/null and b/demo/amend_old_commit.gif differ diff --git a/demo/amend_old_commit.yml b/demo/amend_old_commit.yml new file mode 100644 index 00000000000..25e71c28347 --- /dev/null +++ b/demo/amend_old_commit.yml @@ -0,0 +1,211 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/amend_old_commit.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + fontFamily: "DejaVuSansMono 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" + +# Records, feel free to edit them +records: + - delay: 6140 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 68 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Diff─────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\No changed files \e(B\e[m\e[30m\e]8;;\e\\│\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────────────┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ שׂ master \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────────────────1 of 2─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\033e4142\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Add a user-friendly 404 \e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d46b9cc0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Set up CI/CD pipeline us\e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\6791ada9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Enable gzip compression \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fda754e2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor error messages \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a1ee6f39\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Improve accessibility of\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\38e097eb\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Add loading indicators t\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5f05f638\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Add end-to-end tests for\e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H└──────────────────────────────1 of 60─┘└──────────────────────────────────────────────────────────────────────────────┘\e[32;1H┌─Stash────────────────────────────────┐┌─Command log──────────────────────────────────────────────────────────────────┐\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Unstaged changes\e[5;2H\e(B\e[m\e[91;44m\e[1m\e]8;;\e\\ M  navigation/site_navigation.go\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e[6;2H\e(B\e[m\e[31m\e]8;;\e\\??  docs/README.md \e[13;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1 of 2\e[?25l" + - delay: 9 + content: "\e[?25l\e[2;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/navigation/site_navigation.go b/navigation/site_navigation.go\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[1m\e]8;;\e\\index 644e7ed..ba10353 100644\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e[1m\e]8;;\e\\--- a/navigation/site_navigation.go\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/navigation/site_navigation.go\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -1 +1,5 @@\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;42H\e(B\e[m\e[31m\e]8;;\e\\-package navigation\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;42H\e(B\e[m\e]8;;\e\\\\\e[8;44HNo\e[8;47Hnewline\e[8;55Hat\e[8;58Hend\e[8;62Hof\e[8;65Hfile\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;42H\e(B\e[m\e[32m\e]8;;\e\\+package navigation\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[10;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[11;42H\e(B\e[m\e[32m\e]8;;\e\\+func Navigate() {\e[11;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[12;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[12;46Hpanic(\"unimplemented\")\e[12;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[13;42H\e(B\e[m\e[32m\e]8;;\e\\+}\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;42H\e(B\e[m\e]8;;\e\\\\\e[14;44HNo\e[14;47Hnewline\e[14;55Hat\e[14;58Hend\e[14;62Hof\e[14;65Hfile\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;120H█\e[16;120H█\e[17;120H█\e[18;120H█\e[19;120H█\e[20;120H█\e[30;120H▼\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Amend\e[35;7Han\e[35;10Hold\e[35;14Hcommit\e[?25l" + - delay: 1003 + content: "\e[?25l\e[35;21H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 28 + content: "\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Staged changes──\e[2;42H\e(B\e[m\e]8;;\e\\ \e[2;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[3;42H\e(B\e[m\e]8;;\e\\ \e[3;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[4;42H\e(B\e[m\e]8;;\e\\ \e[4;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[5;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\M  navigation/site_navigation.go\e[5;42H\e(B\e[m\e]8;;\e\\ \e[5;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[6;42H\e(B\e[m\e]8;;\e\\ \e[6;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[7;42H\e(B\e[m\e]8;;\e\\ \e[7;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[8;42H\e(B\e[m\e]8;;\e\\ \e[8;44H \e[8;47H \e[8;55H \e[8;58H \e[8;62H \e[8;65H \e[8;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[9;42H\e(B\e[m\e]8;;\e\\ \e[9;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[10;42H\e(B\e[m\e]8;;\e\\ \e[10;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[11;42H\e(B\e[m\e]8;;\e\\ \e[11;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[12;42H\e(B\e[m\e]8;;\e\\ \e[12;46H \e[12;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[13;42H\e(B\e[m\e]8;;\e\\ \e[13;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[14;42H\e(B\e[m\e]8;;\e\\ \e[14;44H \e[14;47H \e[14;55H \e[14;58H \e[14;62H \e[14;65H \e[14;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[15;120H│\e[16;120H│\e[17;120H│\e[18;120H│\e[19;120H│\e[20;120H│\e[30;120H│\e[33;44H\e(B\e[m\e]8;;\e\\git\e[33;48Hadd\e[33;52H--\e[33;55Hnavigation/site_navigation.go\e[?25l\e[?25l\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/navigation/site_navigation.go b/navigation/site_navigation.go\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[1m\e]8;;\e\\index 644e7ed..ba10353 100644\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e[1m\e]8;;\e\\--- a/navigation/site_navigation.go\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/navigation/site_navigation.go\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -1 +1,5 @@\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;42H\e(B\e[m\e[31m\e]8;;\e\\-package navigation\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;42H\e(B\e[m\e]8;;\e\\\\\e[8;44HNo\e[8;47Hnewline\e[8;55Hat\e[8;58Hend\e[8;62Hof\e[8;65Hfile\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;42H\e(B\e[m\e[32m\e]8;;\e\\+package navigation\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[10;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[11;42H\e(B\e[m\e[32m\e]8;;\e\\+func Navigate() {\e[11;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[12;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[12;46Hpanic(\"unimplemented\")\e[12;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[13;42H\e(B\e[m\e[32m\e]8;;\e\\+}\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;42H\e(B\e[m\e]8;;\e\\\\\e[14;44HNo\e[14;47Hnewline\e[14;55Hat\e[14;58Hend\e[14;62Hof\e[14;65Hfile\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;120H█\e[16;120H█\e[17;120H█\e[18;120H█\e[19;120H█\e[20;120H█\e[30;120H▼\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;30H\e(B\e[m\e[36m\e[1m\e]8;;\e\\4 \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Patch─────────\e[4;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e(B\e[m\e[32m\e]8;;\e\\M  navigation/site_navigation.go\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────1 of 2─┘\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\033e4142\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add a user-friendly 404 \e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 60─┘\e[?25l" + - delay: 11 + content: "\e[?25l\e[2;42H\e(B\e[m\e[33m\e]8;;\e\\commit 033e4142f587e156f8ce52ea0b30fc7a813e7133 (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/demo\e(B\e[m\e[33m\e]8;;\e\\, \e[3;42H\e(B\e[m\e[31m\e[1m\e]8;;\e\\origin/master\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[31m\e[1m\e]8;;\e\\origin/feature/demo\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\)\e[4;42H\e(B\e[m\e]8;;\e\\Author: CI \e[5;42HDate: Thu Aug 3 17:38:47 2023 +1000\e[6;42H \e[7;42H Add a user-friendly\e[7;66H404\e[7;70Hpage\e[8;42H--- \e[8;47H \e[8;55H \e[8;58H \e[8;62H \e[8;65H \e[9;42H pages/404.html | 1\e[9;62H\e(B\e[m\e[32m\e]8;;\e\\+\e[10;42H\e(B\e[m\e]8;;\e\\ 1\e[10;45Hfile\e[10;50Hchanged,\e[10;59H1\e[10;61Hinsertion(+)\e[11;42H \e[12;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/pages/404.html b/pages/404.html\e[13;42Hnew file mode 100644\e[14;42Hindex 0000000..6c70bcf\e[14;65H\e(B\e[m\e]8;;\e\\ \e[15;42H\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[16;42H+++ b/pages/404.html\e[17;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[18;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[19;42H\e(B\e[m\e]8;;\e\\\\\e[19;44HNo\e[19;47Hnewline\e[19;55Hat\e[19;58Hend\e[19;62Hof\e[19;65Hfile\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;120H│\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;21H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[24;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\033e4142\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Add a user-friendly 404 \e[25;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\d46b9cc0\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Set up CI/CD pipeline us\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 10 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\d46b9cc0444e7a05\e[2;66He759c3610a6e72f99\e[2;84Ha1df6\e(B\e[m\e]8;;\e\\ \e[3;42HAuthor: CI \e[4;42HDa\e[4;45He: \e[4;50HThu Aug 3 17:38:47 2023\e[4;74H+1000\e[5;42H \e[5;50H \e[5;54H \e[5;58H \e[5;60H \e[5;69H \e[5;74H \e[6;46HSet\e[6;50Hup\e[6;53HCI/CD\e[6;59Hpipeline\e[6;68Husing\e[6;74HGitHub\e[6;81Hactions\e[7;42H---\e[7;46H \e[7;50H \e[7;52H \e[7;66H \e[7;70H \e[8;42H .github/workflows/ci.yml\e[8;68H|\e[8;70H1\e[8;72H\e(B\e[m\e[32m\e]8;;\e\\+\e[9;43H\e(B\e[m\e]8;;\e\\1 file changed, 1 insertion(+)\e[10;43H \e[10;45H \e[10;50H \e[10;59H \e[10;61H \e[11;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml\e[12;42Hnew file mode 10064\e[12;62H\e(B\e[m\e]8;;\e\\ \e[13;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..29f9c6d\e[14;42H--- /dev/null\e(B\e[m\e]8;;\e\\ \e[15;42H\e(B\e[m\e[1m\e]8;;\e\\+++\e[15;46Hb/.github/workflows/ci.yml\e[16;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[17;42H\e(B\e[m\e[32m\e]8;;\e\\+name: CI\e(B\e[m\e]8;;\e\\ \e[18;42H\\ No newline at\e[18;58Hend\e[18;62Hof\e[18;65Hfile\e[19;42H \e[19;44H \e[19;47H \e[19;55H \e[19;58H \e[19;62H \e[19;65H \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l\e[?25l\e[25;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d46b9cc0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Set up CI/CD pipeline us\e[26;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\6791ada9\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Enable gzip compression \e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[?25l" + - delay: 12 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\6791ada915bbdff3da174ad3\e[2;74H37c85e9\e[2;82H84c4c65\e[6;46H\e(B\e[m\e]8;;\e\\Enable\e[6;53Hgzip com\e[6;62Hression for\e[6;74Hfaster\e[6;81Hpage loads\e[8;43Hperformance/gzip_compression.go\e[8;75H|\e[8;77H1\e[8;79H\e(B\e[m\e[32m\e]8;;\e\\+\e[11;55H\e(B\e[m\e[1m\e]8;;\e\\performance/gzip_compression.go b/performance/gzip_compression.go\e[13;57H513fb44\e[15;48Hperformance/gzip_compression.go\e[17;43H\e(B\e[m\e[32m\e]8;;\e\\p\e[17;45Hckage performance\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l\e[?25l\e[26;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\6791ada9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Enable gzip compression \e[27;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fda754e2\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Refactor error messages \e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[?25l" + - delay: 10 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\fda754e261e36c8\e[2;65H6deb\e[2;70H40bda6df0d6691\e[2;85Hc007\e[6;46H\e(B\e[m\e]8;;\e\\Refactor error mes\e[6;65Hages\e[6;74Hbet\e[6;81Hclarity \e[8;43Her\e[8;46Hor/error_messages.go | 1 \e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e[8;75H \e[8;77H \e[8;79H \e[11;55H\e(B\e[m\e[1m\e]8;;\e\\er\e[11;58Hor/error_messages.go b/error/erro\e[11;92H_messag\e[11;100Hs.go\e(B\e[m\e]8;;\e\\ \e[13;57H\e(B\e[m\e[1m\e]8;;\e\\c68838f\e[15;48Her\e[15;51Hor/error_messages.go\e(B\e[m\e]8;;\e\\ \e[17;51H\e(B\e[m\e[32m\e]8;;\e\\er\e[17;54Hor\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 124 + content: "\e[?25l\e[?25l\e[?25l\e[27;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fda754e2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor error messages \e[28;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\a1ee6f39\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Improve accessibility of\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\5\e[?25l" + - delay: 11 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\a1ee6f395d89e947f91e1ab0b\e[2;75H980754ce839a54\e[6;46H\e(B\e[m\e]8;;\e\\Improve accessibility of site navigation \e[8;43Hnavigation/site_navigation.go\e[8;73H|\e[8;75H1\e[8;77H\e(B\e[m\e[32m\e]8;;\e\\+\e[11;55H\e(B\e[m\e[1m\e]8;;\e\\navigation/site_navigation.go b\e[11;87Hnavigation/site_navigation.go\e[13;57H644e7ed\e[15;48Hnavigation/site_navigation.go\e[17;51H\e(B\e[m\e[32m\e]8;;\e\\navigation\e[?25l" + - delay: 624 + content: "\e[?25l\e[35;21H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing A \e[?25l\e[?25l\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Amend commit──────────────────────────────────────────────────────────────────┐\e[17;21H│\e(B\e[m\e[1m\e]8;;\e\\Are you sure you want to amend this commit with your staged files? \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H└───────────────────────────────────────────────────────────────────────────────┘\e[23;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────5 of 60─┘\e[?25l" + - delay: 1602 + content: "\e[?25l\e[35;30H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 5 + content: "\e[?25l\e[16;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[17;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+package navigation\e(B\e[m\e]8;;\e\\ \e[18;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\\\ No newline at end of file \e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────5 of 60─┘\e[33;48H\e(B\e[m\e]8;;\e\\commit\e[33;55H--f\e[33;59Hxup=a1ee6f395d89e947f91e1ab0ba980754ce839a54\e[?25l" + - delay: 10 + content: "\e[?25l\e[?25l" + - delay: 39 + content: "\e[?25l\e[33;42H\e(B\e[m\e]8;;\e\\merges 38e097eb8088573a2244ba87e5736180a43fd640 \e[?25l" + - delay: 49 + content: "\e[?25l\e[35;104H\e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset) \e[?25l" + - delay: 49 + content: "\e[?25l\e[?25l" + - delay: 55 + content: "\e[?25l\e[35;104H\e(B\e[m\e[36m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[5;2H\e(B\e[m\e[31m\e]8;;\e\\??  docs/README.md \e[6;2H\e(B\e[m\e]8;;\e\\ \e[13;38H\e(B\e[m\e[30m\e]8;;\e\\1\e[?25l\e[?25l\e[?25l\e[?25l\e[15;21H\e(B\e[m\e[33m\e]8;;\e\\↑5↓5\e[16;2H\e(B\e[m\e[36m\e]8;;\e\\6\e[?25l\e[?25l\e[2;2H\e(B\e[m\e[33m\e]8;;\e\\↑5↓5\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\featur\e[2;21H/demo\e[24;2H\e(B\e[m\e[31m\e]8;;\e\\ﰖ\e[24;4H2d01f207\e[25;2Hﰖ\e[25;4H4328f7f3\e[26;2Hﰖ\e[26;4H1c43ca6a\e[27;2Hﰖ\e[27;4H59703a65\e[28;2H\e(B\e[m\e[91;44m\e[1m\e]8;;\e\\ﰖ\e[28;4H8a33fc99\e[?25l\e[?25l\e[?25l" + - delay: 9 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\8a33fc9\e[2;57H87706\e[2;63H6154e3d5ab15d3d598aa09b2a\e[8;75H\e(B\e[m\e]8;;\e\\5\e[8;78H\e(B\e[m\e[32m\e]8;;\e\\++++\e[9;59H\e(B\e[m\e]8;;\e\\5\e[9;70Hs(+)\e[13;57H\e(B\e[m\e[1m\e]8;;\e\\ba10353\e[16;52H\e(B\e[m\e[36m\e]8;;\e\\,5 @@\e[18;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[18;44H\e(B\e[m\e]8;;\e\\ \e[18;47H \e[18;55H \e[18;58H \e[18;62H \e[18;65H \e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;42H\e(B\e[m\e[32m\e]8;;\e\\+func Navigate() {\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[20;46Hpanic(\"unimplemented\")\e[21;42H+}\e[22;42H\e(B\e[m\e]8;;\e\\\\\e[22;44HNo\e[22;47Hnewline\e[22;55Hat\e[22;58Hend\e[22;62Hof\e[22;65Hfile\e[?25l" + - delay: 15 + content: "\e[?25l\e[?25l" + - delay: 1604 + content: "\e[?25l\e[35;30H\e(B\e[m\e[36m\e[1m\e]8;;\e\\P \e[?25l\e[?25l\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Force push────────────────────────────────────────────────────────────────────┐\e[17;21H│\e(B\e[m\e[1m\e]8;;\e\\Your branch has diverged from the remote branch. Press to cancel, or \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H│\e(B\e[m\e[1m\e]8;;\e\\ to force push. \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;21H└───────────────────────────────────────────────────────────────────────────────┘\e[23;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────5 of 60─┘\e[?25l" + - delay: 1603 + content: "\e[?25l\e[35;30H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l\e[?25l\e[16;23H\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────────\e[17;22H\e(B\e[m\e[1m\e]8;;\e\\Pushing...\e(B\e[m\e]8;;\e\\ / \e[18;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└───────────────────────────────────────────────────────────────────────────────┘\e[19;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+func Navigate() {\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 10 + content: "\e[?25l\e[?25l" + - delay: 40 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\-\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\\\\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\|\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\/\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\-\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\\\\e[?25l" + - delay: 49 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\|\e[?25l" + - delay: 51 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\/\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\-\e[?25l" + - delay: 51 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\\\\e[33;42H git push --force-with-lease \e[?25l" + - delay: 48 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\|\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\/\e[?25l" + - delay: 20 + content: "\e[?25l\e[33;43H\e(B\e[m\e]8;;\e\\+ 033e414...2d01f20 feature/demo\e[33;76H->\e[33;79Hfeature/demo\e[33;92H(forced\e[33;100Hupdate)\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[?25l\e[?25l\e[15;21H\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e[16;2H\e(B\e[m\e[36m\e]8;;\e\\10s\e[?25l\e[?25l\e[2;2H\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/d\e[2;21Hmo\e(B\e[m\e]8;;\e\\ \e[24;2H\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e[24;4H2d01f207\e[25;2Hﰖ\e[25;4H4328f7f3\e[26;2Hﰖ\e[26;4H1c43ca6a\e[27;2Hﰖ\e[27;4H59703a65\e[28;2H\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\ﰖ\e[28;4H8a33fc99\e[?25l" + - delay: 7 + content: "\e[?25l\e[16;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1,5 @@\e(B\e[m\e]8;;\e\\ \e[17;21H \e[17;33H \e[17;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+package navigation\e[17;101H\e(B\e[m\e]8;;\e\\ \e[18;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────5 of 60─┘\e[?25l" + - delay: 11 + content: "\e[?25l\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;21H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;7H \e[35;10H \e[35;14H \e[?25l" + - delay: 2001 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/bisect-compressed.gif b/demo/bisect-compressed.gif new file mode 100644 index 00000000000..482b2819b3d Binary files /dev/null and b/demo/bisect-compressed.gif differ diff --git a/demo/bisect.gif b/demo/bisect.gif new file mode 100644 index 00000000000..3158ba18689 Binary files /dev/null and b/demo/bisect.gif differ diff --git a/demo/bisect.yml b/demo/bisect.yml new file mode 100644 index 00000000000..a7e49b536bd --- /dev/null +++ b/demo/bisect.yml @@ -0,0 +1,243 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/bisect.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + fontFamily: "DejaVuSansMono 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" + +# Records, feel free to edit them +records: + - delay: 8724 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J" + - delay: 10 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 94 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\a5cc9c45\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/master, origin/feature/demo, master)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add a user-fr\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[3;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\41e20bf9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Set up CI/CD pipeline using GitHub actions \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[4;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e1c8e57d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enable gzip compression for faster page loads \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[5;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\72e34e08\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor error messages for better clarity \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[6;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d1993a44\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve accessibility of site navigation \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[7;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cf82ae63\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add loading indicators to improve UX \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[8;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\099ca1d0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add end-to-end tests for checkout flow \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[9;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\76ff9ddd\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix broken links on the help page \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[10;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\421d5c71\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Implement automated backups for database \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[11;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d6348a27\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add TypeScript types to User module \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\23dc2660\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Move constants to a separate config file \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\c0988fbd\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enhance user search with fuzzy matching \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\abdbae31\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session management using JWT \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\c6224692\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a2bbeadf\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e7503830\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\2d3dba16\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Replace deprecated lifecycle methods in React components \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\635edf39\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\25c5d1fe\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle edge case for zero quantity in cart \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fcf47a26\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\af171bab\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d40aed30\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\9f6bf535\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b99c073b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update UX of password reset feature \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5a71317e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\163d7f2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4075d17b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Harden security of user password storage \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\975c391b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\f94a9251\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure CSRF protection for all forms \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\bc63a4f0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce Redis for session management \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\f038a61c\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve Dockerfile for more efficient builds \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4d8a221a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Implement user blocking functionality \e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 60─┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Git\e[35;5Hbisect\e[?25l" + - delay: 1004 + content: "\e[?25l\e[35;12H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing b \e[?25l" + - delay: 10 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e[2;120H▲\e[3;1H│\e[3;120H█\e[4;1H│\e[4;120H█\e[5;1H│\e[5;120H█\e[6;1H│\e[6;120H█\e[7;1H│\e[7;120H█\e[8;1H│\e[8;120H█\e[9;1H│\e[9;120H█\e[10;1H│\e[10;120H█\e[11;1H│\e[11;120H█\e[12;1H│\e[12;120H█\e[13;1H│\e[13;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Bisect────────────────────────────────────────────────────────────────────────┐\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;1H│\e[14;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Mark a5cc9c45 as bad (start bisect) \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;1H│\e[15;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\g\e(B\e[m\e]8;;\e\\ Mark a5cc9c45\e[15;38Has good (s\e[15;49Hart bisect) \e[15;73H \e[15;76H \e[15;84H \e[15;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;1H│\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\t\e(B\e[m\e]8;;\e\\ Choose bisect\e[16;38Hterms \e[16;47H \e[16;56H \e[16;67H \e[16;76H \e[16;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;1H│\e[17;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Cancel \e[17;38H \e[17;40H \e[17;47H \e[17;54H \e[17;64H \e[17;67H \e[17;71H \e[17;78H \e[17;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;1H│\e[18;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└────────────────────────────────────────────────────────────────────────1 of 4─┘\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e[19;120H│\e[20;1H│\e[20;120H│\e[21;1H│\e[21;120H│\e[22;1H│\e[22;120H│\e[23;1H│\e[23;120H│\e[24;1H│\e[24;120H│\e[25;1H│\e[25;120H│\e[26;1H│\e[26;120H│\e[27;1H│\e[27;120H│\e[28;1H│\e[28;120H│\e[29;1H│\e[29;120H│\e[30;1H│\e[30;120H│\e[31;1H│\e[31;120H│\e[32;1H│\e[32;120H│\e[33;1H│\e[33;120H▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 60─┘\e[?25l" + - delay: 604 + content: "\e[?25l\e[35;21H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 87 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e[91;44m\e[1m\e]8;;\e\\ﰖ\e[2;4Ha5cc9c45\e[2;13H<-- bad\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e[2;37H \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> f\e[2;59Hature/demo, origin/master, origin\e[2;93Hf\e[2;95Hature/demo, master)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add a\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[3;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[3;4H41e20bf9\e[3;13H\e(B\e[m\e]8;;\e\\ \e[3;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[3;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Set u\e[3;54H CI/CD pipeline using GitHub\e[3;83Hactions\e[3;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[4;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[4;4He1c8e57d\e[4;13H\e(B\e[m\e]8;;\e\\ \e[4;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[4;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enable gzip c\e[4;62Hmpression for faster page\e[4;88Hloads\e[4;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[5;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[5;4H72e34e08\e[5;13H\e(B\e[m\e]8;;\e\\ \e[5;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[5;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ R\e[5;50Hfactor error messag\e[5;70Hs for better\e[5;83Hclarity\e[5;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[6;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[6;4Hd1993a44\e[6;13H\e(B\e[m\e]8;;\e\\ \e[6;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[6;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e[6;48H\e(B\e[m\e]8;;\e\\Improve accessibil\e[6;68Hy\e[6;70Hof site navigation\e[6;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[7;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[7;4Hcf82ae63\e[7;13H\e(B\e[m\e]8;;\e\\ \e[7;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[7;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add\e[7;52Hloading indicators to\e[7;74Himprove\e[7;82HUX\e[7;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[8;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[8;4H099ca1d0\e[8;13H\e(B\e[m\e]8;;\e\\ \e[8;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[8;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add end-to-end tests f\e[8;71Hr checkout\e[8;82Hflow\e[8;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[9;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[9;4H76ff9ddd\e[9;13H\e(B\e[m\e]8;;\e\\ \e[9;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[9;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix broken links on the help\e[9;77Hpage\e[9;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[10;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[10;4H421d5c71\e[10;13H\e(B\e[m\e]8;;\e\\ \e[10;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[10;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Implemen\e[10;57H automated\e[10;68Hbackups for database\e[10;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[11;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[11;4Hd6348a27\e[11;13H\e(B\e[m\e]8;;\e\\ \e[11;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[11;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add TypeScrip\e[11;62H typ\e[11;67Hs\e[11;69Ht\e[11;71H Us\e[11;75Hr\e[11;77Hmodule\e[11;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[12;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[12;4H23dc2660\e[12;13H\e(B\e[m\e]8;;\e\\ \e[12;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[12;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Move const\e[12;59Hnts to a separate config\e[12;84Hfile\e[12;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[13;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[13;4Hc0988fbd\e[13;13H\e(B\e[m\e]8;;\e\\ \e[13;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enhance user search with fuzzy matching \e[13;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[14;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[14;4Habdbae31\e[14;13H\e(B\e[m\e]8;;\e\\ \e[14;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session management using JWT \e[14;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[15;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[15;4Hc6224692\e[15;13H\e(B\e[m\e]8;;\e\\ \e[15;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[15;46H◯\e(B\e[m\e]8;;\e\\ Ensure atomicity\e[15;65Hof\e[15;68Htransactions\e[15;81Hin\e[15;84Hpayment\e[15;92Hsystem\e[15;101H \e[15;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[16;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[16;4Ha2bbeadf\e[16;13H\e(B\e[m\e]8;;\e\\ \e[16;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[16;46H◯\e[16;48H\e(B\e[m\e]8;;\e\\Handle\e[16;55Hdatabase\e[16;64Hconnection\e[16;75Hfailures\e[16;84Hgracefully\e[16;101H \e[16;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[17;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[17;4He7503830\e[17;13H\e(B\e[m\e]8;;\e\\ \e[17;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[17;46H◯\e[17;48H\e(B\e[m\e]8;;\e\\Update\e[17;55Hstyles\e[17;62Haccording\e[17;72Hto\e[17;75Hnew\e[17;79Hdesign\e[17;86Hguidelines\e[17;101H \e[17;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[18;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[18;4H2d3dba16\e[18;13H\e(B\e[m\e]8;;\e\\ \e[18;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Replace deprecated lifecycle methods in React components\e[18;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[19;4H635edf39\e[19;13H\e(B\e[m\e]8;;\e\\ \e[19;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[19;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error ha\e[19;64Hdling\e[19;70Hin u\e[19;75He\e[19;77H registration\e[19;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[20;4H25c5d1fe\e[20;13H\e(B\e[m\e]8;;\e\\ \e[20;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[20;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle \e[20;56Hdge case for zero quantity\e[20;83Hin\e[20;86Hcart\e[20;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[21;4Hfcf47a26\e[21;13H\e(B\e[m\e]8;;\e\\ \e[21;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[21;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce retry \e[21;65Hecha\e[21;70Hism in\e[21;77Hnetwork\e[21;85Hcalls\e[21;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[22;4Haf171bab\e[22;13H\e(B\e[m\e]8;;\e\\ \e[22;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[22;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Remove hardcoded values from\e[22;77Hpayment\e[22;85Hmodule\e[22;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[23;4Hd40aed30\e[23;13H\e(B\e[m\e]8;;\e\\ \e[23;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[23;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e[23;48H\e(B\e[m\e]8;;\e\\Enhance\e[23;56Hlogging in producti\e[23;77H \e[23;80Hvironment\e[23;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[24;4H9f6bf535\e[24;13H\e(B\e[m\e]8;;\e\\ \e[24;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[24;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add \e[24;53Hnternationalization\e[24;73Hsupport for\e[24;85HGerman\e[24;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[25;4Hb99c073b\e[25;13H\e(B\e[m\e]8;;\e\\ \e[25;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[25;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update UX \e[25;59Hf pas\e[25;65Hword reset\e[25;76Hfeature\e[25;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[26;4H5a71317e\e[26;13H\e(B\e[m\e]8;;\e\\ \e[26;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[26;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e[26;48H\e(B\e[m\e]8;;\e\\Mi\e[26;51Hrate legacy codebas\e[26;71H to Typescript\e[26;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[27;4H163d7f2a\e[27;13H\e(B\e[m\e]8;;\e\\ \e[27;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[27;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e[27;48H\e(B\e[m\e]8;;\e\\Resolve race condi\e[27;67Hio\e[27;70H in trans\e[27;80Hctio\e[27;85H handling\e[27;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[28;4H4075d17b\e[28;13H\e(B\e[m\e]8;;\e\\ \e[28;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[28;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Harden security\e[28;64Hof use\e[28;71H password\e[28;81Hstorage\e[28;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[29;4H975c391b\e[29;13H\e(B\e[m\e]8;;\e\\ \e[29;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[29;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Implement bulk delet\e[29;70Hfeature in admin\e[29;87Hpanel\e[29;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[30;4Hf94a9251\e[30;13H\e(B\e[m\e]8;;\e\\ \e[30;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[30;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure CSRF protection\e[30;74H all\e[30;79Hforms\e[30;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[31;4Hbc63a4f0\e[31;13H\e(B\e[m\e]8;;\e\\ \e[31;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[31;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce Redi\e[31;63H f\e[31;66Hr\e[31;68Hsession management\e[31;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[32;4Hf038a61c\e[32;13H\e(B\e[m\e]8;;\e\\ \e[32;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[32;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e[32;48H\e(B\e[m\e]8;;\e\\Improve Docke\e[32;62Hfile for mor\e[32;75H efficient\e[32;86Hbuilds\e[32;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[33;4H4d8a221a\e[33;13H\e(B\e[m\e]8;;\e\\ \e[33;20H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[33;37H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Impl\e[33;53Hment user blocking functionality\e[33;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 60─┘\e[35;103H\e(B\e[m\e[32m\e]8;;\e\\Bisecting \e(B\e[m\e[32m\e[4m\e]8;;\e\\(Reset) \e[?25l\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[?25l" + - delay: 605 + content: "\e[?25l\e[35;12H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l" + - delay: 7 + content: "\e[?25l\e[2;2H\e(B\e[m\e[31m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a5cc9c45\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\<-- bad\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/master, origin/feature/demo, master)\e(B\e[m\e]8;;\e\\ Add a\e[3;2H\e(B\e[m\e[90;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[90;44m\e[1m\e]8;;\e\\41e20bf9\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Set up CI/CD pipeline using GitHub actions \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 13 + content: "\e[?25l\e[?25l" + - delay: 121 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[3;2H\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\41e20bf9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Set up CI/CD pipeline using GitHub actions \e[4;2H\e(B\e[m\e[90;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[90;44m\e[1m\e]8;;\e\\e1c8e57d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Enable gzip compression for faster page loads \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[?25l" + - delay: 12 + content: "\e[?25l\e[?25l" + - delay: 125 + content: "\e[?25l\e[?25l" + - delay: 9 + content: "\e[?25l\e[4;2H\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\e1c8e57d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enable gzip compression for faster page loads \e[5;2H\e(B\e[m\e[90;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[90;44m\e[1m\e]8;;\e\\72e34e08\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Refactor error messages for better clarity \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[?25l" + - delay: 14 + content: "\e[?25l\e[?25l" + - delay: 122 + content: "\e[?25l\e[?25l" + - delay: 7 + content: "\e[?25l\e[5;2H\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\72e34e08\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor error messages for better clarity \e[6;2H\e(B\e[m\e[90;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[90;44m\e[1m\e]8;;\e\\d1993a44\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Improve accessibility of site navigation \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\5\e[?25l" + - delay: 13 + content: "\e[?25l\e[?25l" + - delay: 124 + content: "\e[?25l\e[?25l" + - delay: 9 + content: "\e[?25l\e[6;2H\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\d1993a44\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve accessibility of site navigation \e[7;2H\e(B\e[m\e[90;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[90;44m\e[1m\e]8;;\e\\cf82ae63\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add loading indicators to improve UX \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\6\e[?25l" + - delay: 14 + content: "\e[?25l\e[?25l" + - delay: 122 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[7;2H\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\cf82ae63\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add loading indicators to improve UX \e[8;2H\e(B\e[m\e[90;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[90;44m\e[1m\e]8;;\e\\099ca1d0\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add end-to-end tests for checkout flow \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\7\e[?25l" + - delay: 16 + content: "\e[?25l\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[8;2H\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\099ca1d0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add end-to-end tests for checkout flow \e[9;2H\e(B\e[m\e[90;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[90;44m\e[1m\e]8;;\e\\76ff9ddd\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix broken links on the help page \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\8\e[?25l" + - delay: 11 + content: "\e[?25l\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[9;2H\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\76ff9ddd\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix broken links on the help page \e[10;2H\e(B\e[m\e[90;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[90;44m\e[1m\e]8;;\e\\421d5c71\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Implement automated backups for database \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\9\e[?25l" + - delay: 15 + content: "\e[?25l\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l" + - delay: 8 + content: "\e[?25l\e[10;2H\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\421d5c71\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Implement automated backups for database \e[11;2H\e(B\e[m\e[90;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[90;44m\e[1m\e]8;;\e\\d6348a27\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add TypeScript types to User module \e[34;111H\e(B\e[m\e[32m\e[1m\e]8;;\e\\10\e[?25l" + - delay: 12 + content: "\e[?25l\e[?25l" + - delay: 122 + content: "\e[?25l\e[35;12H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing b \e[?25l" + - delay: 18 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e[2;120H▲\e[3;1H│\e[3;120H█\e[4;1H│\e[4;120H█\e[5;1H│\e[5;120H█\e[6;1H│\e[6;120H█\e[7;1H│\e[7;120H█\e[8;1H│\e[8;120H█\e[9;1H│\e[9;120H█\e[10;1H│\e[10;120H█\e[11;1H│\e[11;120H█\e[12;1H│\e[12;120H█\e[13;1H│\e[13;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Bisect────────────────────────────────────────────────────────────────────────┐\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;1H│\e[14;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Mark current commit (d6348a27) as bad \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;1H│\e[15;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\g\e(B\e[m\e]8;;\e\\ Mark current commit (d6348a27)\e[15;56Hs good \e[15;65H \e[15;68H \e[15;81H \e[15;84H \e[15;92H \e[15;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;1H│\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\s\e(B\e[m\e]8;;\e\\ Skip current commit (d6348a27)\e[16;55H \e[16;64H \e[16;75H \e[16;84H \e[16;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;1H│\e[17;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\r\e(B\e[m\e]8;;\e\\ Reset bisect \e[17;46H \e[17;48H \e[17;55H \e[17;62H \e[17;72H \e[17;75H \e[17;79H \e[17;86H \e[17;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;1H│\e[18;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Cancel \e[18;46H \e[18;48H \e[18;56H \e[18;67H \e[18;77H \e[18;85H \e[18;88H \e[18;94H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e[19;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└────────────────────────────────────────────────────────────────────────1 of 5─┘\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e[20;120H│\e[21;1H│\e[21;120H│\e[22;1H│\e[22;120H│\e[23;1H│\e[23;120H│\e[24;1H│\e[24;120H│\e[25;1H│\e[25;120H│\e[26;1H│\e[26;120H│\e[27;1H│\e[27;120H│\e[28;1H│\e[28;120H│\e[29;1H│\e[29;120H│\e[30;1H│\e[30;120H│\e[31;1H│\e[31;120H│\e[32;1H│\e[32;120H│\e[33;1H│\e[33;120H▼\e[34;1H└─────────────────────────────────────────────────────────────────────────────────────────────────────────────10 of 60─┘\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;12H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[14;22H\e(B\e[m\e[36m\e]8;;\e\\b\e(B\e[m\e]8;;\e\\ Mark current commit (d6348a27) as bad \e[15;22H\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\g\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Mark current commit (d6348a27) as good \e[19;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 123 + content: "\e[?25l\e[35;12H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 126 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e[2;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[2;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, \e[2;69Hrigin/feature/dem\e[2;87H, master, feature/de\e[2;108Ho)\e(B\e[m\e]8;;\e\\ Add\e[2;115Ha use\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\ﰖ\e[3;4H41e20bf9\e[3;13H?\e[3;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[3;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e[3;52H\e(B\e[m\e]8;;\e\\Set up CI/CD p\e[3;67Hp\e[3;69Hline using GitHub actions\e[3;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\ﰖ\e[4;4He1c8e57d\e[4;13H?\e[4;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[4;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enable gzip compressi\e[4;74Hn\e[4;77Hor faster page loads\e[4;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\ﰖ\e[5;4H72e34e08\e[5;13H?\e[5;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[5;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refacto\e[5;60H error mes\e[5;71Hages\e[5;76Hfor better clarity\e[5;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\ﰖ\e[6;4Hd1993a44\e[6;13H?\e[6;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[6;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve access\e[6;67Hbility of site n\e[6;84Hv\e[6;86Hgation\e[6;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[7;1H│\e(B\e[m\e[95;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\cf82ae63\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\<-- current\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add loading indicators to improve UX \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\ﰖ\e[8;4H099ca1d0\e[8;13H?\e[8;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[8;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e[8;52H\e(B\e[m\e]8;;\e\\Ad\e[8;55H end-to-end tests\e[8;73Hfor checkout flow\e[8;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\ﰖ\e[9;4H76ff9ddd\e[9;13H?\e[9;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[9;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e[9;52H\e(B\e[m\e]8;;\e\\Fix broke\e[9;62H links on\e[9;72Hthe help page\e[9;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\ﰖ\e[10;4H421d5c71\e[10;13H?\e[10;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[10;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ I\e[10;54Hplemen\e[10;61H au\e[10;65Homated backups\e[10;80Hfor d\e[10;86Htabase\e[10;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[11;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d6348a27\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\<-- good\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add TypeScript types to User module \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[12;1H│\e[12;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[12;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Move co\e[12;60Hstants to a separate con\e[12;86Hg file\e[12;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[13;1H│\e[13;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enhance user search with fuzzy matching \e[13;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[14;1H│\e[14;21H\e(B\e[m\e]8;;\e\\ \e[14;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ref\e[14;56Hctor session\e[14;69Hmanagement\e[14;80Husing\e[14;86HJWT\e[14;101H \e[14;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[15;1H│\e[15;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system\e[15;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[16;1H│\e[16;21H\e(B\e[m\e]8;;\e\\ \e[16;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle\e[16;59Hdatabase\e[16;68Hconnection\e[16;79Hfailures\e[16;88Hgracefully\e[16;101H \e[16;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[17;1H│\e[17;21H\e(B\e[m\e]8;;\e\\ \e[17;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[17;50H◯\e[17;52H\e(B\e[m\e]8;;\e\\Update\e[17;59Hstyles\e[17;66Haccording\e[17;76Hto\e[17;79Hnew\e[17;83Hdesign\e[17;90Hguidelines\e[17;101H \e[17;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[18;1H│\e[18;21H\e(B\e[m\e]8;;\e\\ \e[18;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[18;32H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[18;50H◯\e[18;52H\e(B\e[m\e]8;;\e\\Replace\e[18;60Hdeprecated\e[18;71Hlifecycle\e[18;81Hmethods\e[18;89Hin\e[18;92HReact\e[18;98Hcomponents\e[18;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e[19;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e[19;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e[20;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[20;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle edge case\e[20;69Hfo\e[20;72H zero quant\e[20;84Hty in cart\e[20;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e[21;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[21;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce retry mechan\e[21;75Hsm in network calls\e[21;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e[22;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[22;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ R\e[22;54Hmove hardcoded values from payment module\e[22;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e[23;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[23;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enhance lo\e[23;63Hg\e[23;66Hg in production environment\e[23;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e[24;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[24;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e[24;52H\e(B\e[m\e]8;;\e\\Add i\e[24;58Hternation\e[24;68Hl\e[24;70Hzation support\e[24;85Hfo\e[24;88H German\e[24;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e[25;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[25;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update UX of password res\e[25;78Ht feature\e[25;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e[26;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[26;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy cod\e[26;71Hbase to Typescript\e[26;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e[27;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[27;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condit\e[27;72Hon in transactio\e[27;89H ha\e[27;93Hdling\e[27;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e[28;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[28;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Hard\e[28;57Hn security of user password storage\e[28;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e[29;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[29;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ I\e[29;54Hplement bulk d\e[29;69Hlete feature in admi\e[29;90H panel\e[29;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e[30;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[30;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure CSRF protecti\e[30;73Hn\e[30;75Hfor\e[30;79Hall forms\e[30;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e[31;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[31;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce Redis\e[31;68Hfor sessio\e[31;79H management\e[31;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e[32;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[32;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve Dockerfile\e[32;71Hf\e[32;74H more efficient builds\e[32;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e[33;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[33;45H \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ I\e[33;54Hplement user blocking functionality\e[33;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────6 of 60─┘\e[?25l\e[?25l\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;21H\e(B\e[m\e[36m\e[1m\e]8;;\e\\b \e[?25l" + - delay: 16 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e[2;120H▲\e[3;1H│\e[3;120H█\e[4;1H│\e[4;120H█\e[5;1H│\e[5;120H█\e[6;1H│\e[6;120H█\e[7;1H│\e[7;120H█\e[8;1H│\e[8;120H█\e[9;1H│\e[9;120H█\e[10;1H│\e[10;120H█\e[11;1H│\e[11;120H█\e[12;1H│\e[12;120H█\e[13;1H│\e[13;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Bisect────────────────────────────────────────────────────────────────────────┐\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;1H│\e[14;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Mark current commit (cf82ae63) as bad \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;1H│\e[15;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\g\e[15;24H\e(B\e[m\e]8;;\e\\Mark current commit (cf82ae63) as good \e[15;69H \e[15;72H \e[15;85H \e[15;88H \e[15;96H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;1H│\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\s\e[16;24H\e(B\e[m\e]8;;\e\\Skip current commit (cf82ae63) \e[16;59H \e[16;68H \e[16;79H \e[16;88H \e[16;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;1H│\e[17;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\r\e[17;24H\e(B\e[m\e]8;;\e\\Reset bisect \e[17;50H \e[17;52H \e[17;59H \e[17;66H \e[17;76H \e[17;79H \e[17;83H \e[17;90H \e[17;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;1H│\e[18;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;24H\e(B\e[m\e]8;;\e\\Cancel \e[18;32H \e[18;50H \e[18;52H \e[18;60H \e[18;71H \e[18;81H \e[18;89H \e[18;92H \e[18;98H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e[19;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└────────────────────────────────────────────────────────────────────────1 of 5─┘\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e[20;120H│\e[21;1H│\e[21;120H│\e[22;1H│\e[22;120H│\e[23;1H│\e[23;120H│\e[24;1H│\e[24;120H│\e[25;1H│\e[25;120H│\e[26;1H│\e[26;120H│\e[27;1H│\e[27;120H│\e[28;1H│\e[28;120H│\e[29;1H│\e[29;120H│\e[30;1H│\e[30;120H│\e[31;1H│\e[31;120H│\e[32;1H│\e[32;120H│\e[33;1H│\e[33;120H▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────6 of 60─┘\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;21H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 116 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[2;4Ha5cc9c45\e[2;13H\e(B\e[m\e]8;;\e\\ \e[2;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[3;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[3;4H41e20bf9\e[3;13H\e(B\e[m\e]8;;\e\\ \e[3;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[4;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[4;4He1c8e57d\e[4;13H\e(B\e[m\e]8;;\e\\ \e[4;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[5;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[5;4H72e34e08\e[5;13H\e(B\e[m\e]8;;\e\\ \e[5;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[6;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[6;4Hd1993a44\e[6;13H\e(B\e[m\e]8;;\e\\ \e[6;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[7;1H│\e(B\e[m\e[31m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\cf82ae63\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\<-- bad\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add loading indicators to improve UX \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[8;1H│\e[8;120H█\e[9;1H│\e(B\e[m\e[95;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\76ff9ddd\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\<-- current\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix broken links on the help page \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[10;1H│\e[10;120H█\e[11;1H│\e[11;120H█\e[12;1H│\e[12;120H█\e[13;1H│\e[13;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enhance user search with fuzzy matching \e[13;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[14;1H│\e[14;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session management using JWT \e[14;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[15;1H│\e[15;21H\e(B\e[m\e]8;;\e\\ \e[15;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity\e[15;69Hof\e[15;72Htransactions\e[15;85Hin\e[15;88Hpayment\e[15;96Hsystem\e[15;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[16;1H│\e[16;21H\e(B\e[m\e]8;;\e\\ \e[16;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle\e[16;59Hdatabase\e[16;68Hconnection\e[16;79Hfailures\e[16;88Hgracefully\e[16;101H \e[16;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[17;1H│\e[17;21H\e(B\e[m\e]8;;\e\\ \e[17;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[17;50H◯\e[17;52H\e(B\e[m\e]8;;\e\\Update\e[17;59Hstyles\e[17;66Haccording\e[17;76Hto\e[17;79Hnew\e[17;83Hdesign\e[17;90Hguidelines\e[17;101H \e[17;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[18;1H│\e[18;21H\e(B\e[m\e]8;;\e\\ \e[18;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[18;32H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[18;50H◯\e[18;52H\e(B\e[m\e]8;;\e\\Replace\e[18;60Hdeprecated\e[18;71Hlifecycle\e[18;81Hmethods\e[18;89Hin\e[18;92HReact\e[18;98Hcomp\e[18;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e[19;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e[19;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e[20;120H│\e[21;1H│\e[21;120H│\e[22;1H│\e[22;120H│\e[23;1H│\e[23;120H│\e[24;1H│\e[24;120H│\e[25;1H│\e[25;120H│\e[26;1H│\e[26;120H│\e[27;1H│\e[27;120H│\e[28;1H│\e[28;120H│\e[29;1H│\e[29;120H│\e[30;1H│\e[30;120H│\e[31;1H│\e[31;120H│\e[32;1H│\e[32;120H│\e[33;1H│\e[33;120H▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 60─┘\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 604 + content: "\e[?25l\e[35;21H\e(B\e[m\e[36m\e[1m\e]8;;\e\\b \e[?25l" + - delay: 16 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e[2;120H▲\e[3;1H│\e[3;120H█\e[4;1H│\e[4;120H█\e[5;1H│\e[5;120H█\e[6;1H│\e[6;120H█\e[7;1H│\e[7;120H█\e[8;1H│\e[8;120H█\e[9;1H│\e[9;120H█\e[10;1H│\e[10;120H█\e[11;1H│\e[11;120H█\e[12;1H│\e[12;120H█\e[13;1H│\e[13;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Bisect────────────────────────────────────────────────────────────────────────┐\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;1H│\e[14;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Mark current commit (76ff9ddd) as bad \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;1H│\e[15;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\g\e[15;24H\e(B\e[m\e]8;;\e\\Mark current commit (76ff9ddd) as good \e[15;69H \e[15;72H \e[15;85H \e[15;88H \e[15;96H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;1H│\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\s\e[16;24H\e(B\e[m\e]8;;\e\\Skip current commit (76ff9ddd) \e[16;59H \e[16;68H \e[16;79H \e[16;88H \e[16;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;1H│\e[17;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\r\e[17;24H\e(B\e[m\e]8;;\e\\Reset bisect \e[17;50H \e[17;52H \e[17;59H \e[17;66H \e[17;76H \e[17;79H \e[17;83H \e[17;90H \e[17;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;1H│\e[18;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;24H\e(B\e[m\e]8;;\e\\Cancel \e[18;32H \e[18;50H \e[18;52H \e[18;60H \e[18;71H \e[18;81H \e[18;89H \e[18;92H \e[18;98H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e[19;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└────────────────────────────────────────────────────────────────────────1 of 5─┘\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e[20;120H│\e[21;1H│\e[21;120H│\e[22;1H│\e[22;120H│\e[23;1H│\e[23;120H│\e[24;1H│\e[24;120H│\e[25;1H│\e[25;120H│\e[26;1H│\e[26;120H│\e[27;1H│\e[27;120H│\e[28;1H│\e[28;120H│\e[29;1H│\e[29;120H│\e[30;1H│\e[30;120H│\e[31;1H│\e[31;120H│\e[32;1H│\e[32;120H│\e[33;1H│\e[33;120H▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 60─┘\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;12H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[14;22H\e(B\e[m\e[36m\e]8;;\e\\b\e(B\e[m\e]8;;\e\\ Mark current commit (76ff9ddd) as bad \e[15;22H\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\g\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Mark current commit (76ff9ddd) as good \e[19;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 124 + content: "\e[?25l\e[35;12H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 124 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e[2;120H▲\e[3;1H│\e[3;120H█\e[4;1H│\e[4;120H█\e[5;1H│\e[5;120H█\e[6;1H│\e[6;120H█\e[7;1H│\e[7;120H█\e[8;1H│\e(B\e[m\e[95;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\099ca1d0\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\<-- current\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add end-to-end tests for checkout flow \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[9;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\76ff9ddd\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\<-- good\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix broken links on the help page \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[10;1H│\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[10;4H421d5c71\e[10;13H\e(B\e[m\e]8;;\e\\ \e[10;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[11;1H│\e[11;120H█\e[12;1H│\e[12;120H█\e[13;1H│\e[13;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enhance user search with fuzzy matching \e[13;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[14;1H│\e[14;21H\e(B\e[m\e]8;;\e\\ \e[14;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ref\e[14;56Hctor session\e[14;69Hmanagement\e[14;80Husing\e[14;86HJWT\e[14;101H \e[14;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[15;1H│\e[15;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system\e[15;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[16;1H│\e[16;21H\e(B\e[m\e]8;;\e\\ \e[16;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle\e[16;59Hdatabase\e[16;68Hconnection\e[16;79Hfailures\e[16;88Hgracefully\e[16;101H \e[16;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[17;1H│\e[17;21H\e(B\e[m\e]8;;\e\\ \e[17;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[17;50H◯\e[17;52H\e(B\e[m\e]8;;\e\\Update\e[17;59Hstyles\e[17;66Haccording\e[17;76Hto\e[17;79Hnew\e[17;83Hdesign\e[17;90Hguidelines\e[17;101H \e[17;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[18;1H│\e[18;21H\e(B\e[m\e]8;;\e\\ \e[18;24H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[18;32H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[18;50H◯\e[18;52H\e(B\e[m\e]8;;\e\\Replace\e[18;60Hdeprecated\e[18;71Hlifecycle\e[18;81Hmethods\e[18;89Hin\e[18;92HReact\e[18;98Hcomp\e[18;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e[19;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e[19;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e[20;120H│\e[21;1H│\e[21;120H│\e[22;1H│\e[22;120H│\e[23;1H│\e[23;120H│\e[24;1H│\e[24;120H│\e[25;1H│\e[25;120H│\e[26;1H│\e[26;120H│\e[27;1H│\e[27;120H│\e[28;1H│\e[28;120H│\e[29;1H│\e[29;120H│\e[30;1H│\e[30;120H│\e[31;1H│\e[31;120H│\e[32;1H│\e[32;120H│\e[33;1H│\e[33;120H▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────7 of 60─┘\e[?25l\e[?25l\e[?25l" + - delay: 604 + content: "\e[?25l\e[35;21H\e(B\e[m\e[36m\e[1m\e]8;;\e\\b \e[?25l" + - delay: 16 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e[2;120H▲\e[3;1H│\e[3;120H█\e[4;1H│\e[4;120H█\e[5;1H│\e[5;120H█\e[6;1H│\e[6;120H█\e[7;1H│\e[7;120H█\e[8;1H│\e[8;120H█\e[9;1H│\e[9;120H█\e[10;1H│\e[10;120H█\e[11;1H│\e[11;120H█\e[12;1H│\e[12;120H█\e[13;1H│\e[13;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Bisect────────────────────────────────────────────────────────────────────────┐\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;1H│\e[14;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Mark current commit (099ca1d0) as bad \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;1H│\e[15;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\g\e[15;24H\e(B\e[m\e]8;;\e\\Mark current commit (099ca1d0) as good \e[15;69H \e[15;72H \e[15;85H \e[15;88H \e[15;96H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;1H│\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\s\e[16;24H\e(B\e[m\e]8;;\e\\Skip current commit (099ca1d0) \e[16;59H \e[16;68H \e[16;79H \e[16;88H \e[16;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;1H│\e[17;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\r\e[17;24H\e(B\e[m\e]8;;\e\\Reset bisect \e[17;50H \e[17;52H \e[17;59H \e[17;66H \e[17;76H \e[17;79H \e[17;83H \e[17;90H \e[17;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;1H│\e[18;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;24H\e(B\e[m\e]8;;\e\\Cancel \e[18;32H \e[18;50H \e[18;52H \e[18;60H \e[18;71H \e[18;81H \e[18;89H \e[18;92H \e[18;98H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e[19;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└────────────────────────────────────────────────────────────────────────1 of 5─┘\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e[20;120H│\e[21;1H│\e[21;120H│\e[22;1H│\e[22;120H│\e[23;1H│\e[23;120H│\e[24;1H│\e[24;120H│\e[25;1H│\e[25;120H│\e[26;1H│\e[26;120H│\e[27;1H│\e[27;120H│\e[28;1H│\e[28;120H│\e[29;1H│\e[29;120H│\e[30;1H│\e[30;120H│\e[31;1H│\e[31;120H│\e[32;1H│\e[32;120H│\e[33;1H│\e[33;120H▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────7 of 60─┘\e[?25l" + - delay: 604 + content: "\e[?25l\e[35;21H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 135 + content: "\e[?25l\e[7;2H\e(B\e[m\e[30m\e]8;;\e\\ﰖ\e[7;4Hcf82ae63\e[7;13H\e(B\e[m\e]8;;\e\\ \e[13;21H \e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enhance user search with fuzzy matching \e[14;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Bisect complete───────────────────────────────────────────────────────────────┐\e[15;22H\e(B\e[m\e[1m\e]8;;\e\\Bisect complete! The following commit introduced the change:\e[16;22H\e(B\e[m\e]8;;\e\\ \e[16;24H \e[16;29H \e[16;37H \e[16;44H \e[17;22H\e(B\e[m\e[1m\e]8;;\e\\099ca1d Add end-to-end tests for checkout flow\e[18;24H\e(B\e[m\e]8;;\e\\ \e[19;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\Do you want to reset 'git bisect' now? \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;21H└───────────────────────────────────────────────────────────────────────────────┘\e[?25l" + - delay: 2604 + content: "\e[?25l\e[?25l" + - delay: 95 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[2;4Ha5cc9c45\e[2;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[2;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[2;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD -> featu\e[2;55He/demo, origin/master, origin/feature/demo, mast\e[2;104Hr)\e(B\e[m\e]8;;\e\\ Add\e[2;111Ha user-fr\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[3;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[3;4H41e20bf9\e[3;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[3;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[3;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Set up CI/CD pipeline using GitHub actions \e[3;87H \e[3;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[4;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[4;4He1c8e57d\e[4;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[4;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[4;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enable gzip\e[4;52Hcompr\e[4;58Hssion\e[4;64Hf\e[4;66Hr fa\e[4;71Hter page\e[4;80Hloads \e[4;87H \e[4;92H \e[4;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[5;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[5;4H72e34e08\e[5;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[5;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[5;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor error messag\e[5;62Hs for better cla\e[5;79Hity \e[5;87H \e[5;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[6;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[6;4Hd1993a44\e[6;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[6;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[6;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve accessibility of \e[6;67Hte navigation \e[6;82H \e[6;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[7;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[7;4Hcf82ae63\e[7;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[7;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[7;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add loading\e[7;52Hin\e[7;55Hicators to improve UX \e[7;78H \e[7;86H \e[7;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[8;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ﰖ\e[8;4H099ca1d0\e[8;13H\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\9:55PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI \e[8;37H\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add end-to-end tests for checkout\e[8;74Hflow \e[8;80H \e[8;84H \e[8;93H \e[8;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[9;1H│\e[9;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[9;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix broken links on the help\e[9;69Hpage \e[9;76H \e[9;81H \e[9;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[10;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[10;4H421d5c71\e[10;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[10;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[10;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Implement\e[10;50Hautomated backups for\e[10;72Hd\e[10;74Htaba\e[10;79He \e[10;84H \e[10;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[11;1H│\e[11;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[11;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add TypeScript types to User module\e[11;76H \e[11;81H \e[11;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[12;4H23dc2660\e[12;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[12;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[12;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Move constants to a \e[12;61Heparate config fil\e[12;81H \e[12;88H \e[12;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[13;4Hc0988fbd\e[13;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[13;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[13;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enhance user se\e[13;56Hr\e[13;58Hh\e[13;60Hwith\e[13;65Hfuzzy matc\e[13;76Hing \e[13;83H \e[13;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[14;4Habdbae31\e[14;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[14;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session management using JWT \e[14;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[15;4Hc6224692\e[15;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[15;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment\e[15;84Hsystem\e[15;101H \e[15;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[16;4Ha2bbeadf\e[16;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[16;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[16;38H◯\e[16;40H\e(B\e[m\e]8;;\e\\Handle\e[16;47Hdatabase\e[16;56Hconnection\e[16;67Hfailures\e[16;76Hgracefully\e[16;101H \e[16;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[17;4He7503830\e[17;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[17;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new\e[17;71Hdesign\e[17;78Hguidelines\e[17;101H \e[17;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[18;4H2d3dba16\e[18;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[18;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[18;38H◯\e[18;40H\e(B\e[m\e]8;;\e\\Replace\e[18;48Hdeprecated\e[18;59Hlifecycle\e[18;69Hmethods\e[18;77Hin\e[18;80HReact\e[18;86Hcomponents\e[18;101H \e[18;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[19;4H635edf39\e[19;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[19;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e[19;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[20;4H25c5d1fe\e[20;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[20;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle edge case for zero quantity in cart \e[20;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[21;4Hfcf47a26\e[21;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[21;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[21;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce\e[21;50Hretry mechanism in network calls \e[21;89H \e[21;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[22;4Haf171bab\e[22;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[22;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[22;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Remove hardcoded values from\e[22;69Hp\e[22;71Hym\e[22;74Hnt m\e[22;79Hdule \e[22;89H \e[22;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[23;4Hd40aed30\e[23;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[23;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[23;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Enhance loggi\e[23;54Hg in pr\e[23;62Hduction environment\e[23;82H \e[23;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[24;4H9f6bf535\e[24;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[24;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[24;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add internationalization support f\e[24;75Hr\e[24;77HGerman \e[24;85H \e[24;89H \e[24;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[25;4Hb99c073b\e[25;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[25;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[25;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update UX\e[25;50Hof \e[25;54Hassword\e[25;62Hreset feature \e[25;80H \e[25;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[26;4H5a71317e\e[26;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[26;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[26;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy cod\e[26;59Hbase to\e[26;67HTyp\e[26;71Hscrip\e[26;77H \e[26;79H \e[26;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[27;4H163d7f2a\e[27;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[27;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[27;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transactio\e[27;78Hhandling \e[27;90H \e[27;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[28;4H4075d17b\e[28;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[28;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[28;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Harden security of\e[28;59Huser password stor\e[28;78Hge \e[28;85H \e[28;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[29;4H975c391b\e[29;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[29;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[29;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Implement\e[29;50Hbulk d\e[29;57Hl\e[29;59Hte\e[29;62Hfeatur\e[29;69H in admin panel\e[29;85H \e[29;91H \e[29;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[30;4Hf94a9251\e[30;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[30;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[30;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure CSRF\e[30;52Hprotection fo\e[30;66H all f\e[30;73Hrms \e[30;79H \e[30;83H \e[30;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[31;4Hbc63a4f0\e[31;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[31;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[31;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce\e[31;50HRedis for session\e[31;68Hmanag\e[31;74Hment \e[31;80H \e[31;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[32;4Hf038a61c\e[32;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[32;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[32;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve Dockerfile for mo\e[32;66He efficient builds \e[32;90H \e[32;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[33;4H4d8a221a\e[33;13H\e(B\e[m\e[34m\e]8;;\e\\9:55PM\e[33;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[33;37H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Implement\e[33;50Huser blocking functionality \e[33;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────7 of 60─┘\e[35;103H\e(B\e[m\e[36m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Inspect problematic commit \e[?25l" + - delay: 502 + content: "\e[?25l\e[35;28H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing _ \e[?25l" + - delay: 9 + content: "\e[?25l\e[1;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┐\e(B\e[m\e[30m\e]8;;\e\\┌─Patch────────────────────────────────────────────────────┐\e[2;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\commit 099ca1d062203605c4fd983338c7fe8a15850489\e(B\e[m\e]8;;\e\\ \e[2;111H \e[2;113H \e(B\e[m\e[30m\e]8;;\e\\▲\e[3;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\Author: CI \e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\Date:\e[4;68H Wed Aug 2\e[4;80H21:55:40\e[4;89H2023\e[4;94H+1000\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[5;64H \e[5;68H \e[5;75H \e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[6;65H Add\e[6;70Hend-to-end\e[6;81Htests\e[6;87Hfor\e[6;91Hcheckout\e[6;100Hflow\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\---\e[7;66H \e[7;74H \e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ tests/checkout_test.sql | 1 \e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[9;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ 1 file changed,\e[9;79H1\e[9;81Hinsertion(+)\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[10;68H \e[10;72H \e[10;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[11;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\diff --git a/tests/checkout_test.sql \e(B\e[m\e[30m\e]8;;\e\\█\e[12;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\b/tests/checkout_test.sql\e[12;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[13;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\index 0000000..21b2b98\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[15;76H\e(B\e[m\e]8;;\e\\ \e[15;84H \e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\+++ b/tests/checkout_test.sql\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[17;78H \e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\+DELETE ALL TABLES;\e(B\e[m\e]8;;\e\\ \e[18;86H \e[18;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[19;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\\\ No newline at end of\e[19;85Hfile\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[20;66H \e[20;75H \e[20;78H \e[20;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[21;66H \e[21;69H \e[21;77H \e[21;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[22;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[22;64H \e[22;69H \e[22;77H \e[22;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[23;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[23;70H \e[23;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[24;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[24;65H \e[24;73H \e[24;77H \e[24;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[25;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[25;68H \e[25;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[26;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[26;64H \e[26;67H \e[26;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[27;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e[27;63H\e(B\e[m\e]8;;\e\\ \e[27;66H \e[27;78H \e[27;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[28;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[28;64H \e[28;73H \e[28;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[29;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[29;70H \e[29;73H \e[29;79H \e[29;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[30;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e[30;63H\e(B\e[m\e]8;;\e\\ \e[30;67H \e[30;71H \e[30;120H\e(B\e[m\e[30m\e]8;;\e\\▼\e[31;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\└──────────────────────────────────────────────────────────┘\e[32;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\┌─Command log──────────────────────────────────────────────┐\e[33;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\Your branch \e[33;75Hs up\e[33;80Hto\e[33;83Hdate\e[33;88Hwith\e[33;93H'origin/feature/demo'.\e[33;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[34;52H\e(B\e[m\e[32m\e[1m\e]8;;\e\\7 of 60\e[34;60H┘\e(B\e[m\e[30m\e]8;;\e\\└──────────────────────────────────────────────────────────┘\e[?25l\e[?25l\e[?25l" + - delay: 1103 + content: "\e[?25l\e[35;37H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 19 + content: "\e[?25l\e[1;3H\e(B\e[m\e[32m\e[1m\e]8;;\e\\Diff files (099ca1d Add end-to-end tests for checkout fl\e[2;2H\e(B\e[m\e[44m\e[1m\e]8;;\e\\▼  tests \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;2H\e(B\e[m\e]8;;\e\\ \e[3;4H\e(B\e[m\e[32m\e]8;;\e\\A\e(B\e[m\e]8;;\e\\  checkout_test.sql \e[3;38H \e[3;40H \e[3;44H \e[3;47H \e[3;53H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;2H\e(B\e[m\e]8;;\e\\ \e[4;4H \e[4;13H \e[4;20H \e[4;38H \e[4;40H \e[4;47H \e[4;52H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;2H\e(B\e[m\e]8;;\e\\ \e[5;4H \e[5;13H \e[5;20H \e[5;38H \e[5;40H \e[5;49H \e[5;55H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;2H\e(B\e[m\e]8;;\e\\ \e[6;4H \e[6;13H \e[6;20H \e[6;38H \e[6;40H \e[6;48H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;2H\e(B\e[m\e]8;;\e\\ \e[7;4H \e[7;13H \e[7;20H \e[7;38H \e[7;40H \e[7;44H \e[7;52H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;2H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;2H\e(B\e[m\e]8;;\e\\ \e[9;4H \e[9;13H \e[9;20H \e[9;38H \e[9;40H \e[9;44H \e[9;51H \e[9;57H \e[9;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;2H\e(B\e[m\e]8;;\e\\ \e[10;4H \e[10;13H \e[10;20H \e[10;38H \e[10;40H \e[10;50H \e[10;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;2H\e(B\e[m\e]8;;\e\\ \e[11;4H \e[11;13H \e[11;20H \e[11;38H \e[11;40H \e[11;44H \e[11;55H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;2H\e(B\e[m\e]8;;\e\\ \e[12;4H \e[12;13H \e[12;20H \e[12;38H \e[12;40H \e[12;45H \e[12;55H \e[12;58H \e[12;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;2H\e(B\e[m\e]8;;\e\\ \e[13;4H \e[13;13H \e[13;20H \e[13;38H \e[13;40H \e[13;48H \e[13;53H \e[13;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;2H\e(B\e[m\e]8;;\e\\ \e[14;4H \e[14;13H \e[14;20H \e[14;38H \e[14;40H \e[14;49H \e[14;57H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;2H\e(B\e[m\e]8;;\e\\ \e[15;4H \e[15;13H \e[15;20H \e[15;38H \e[15;40H \e[15;47H \e[15;57H \e[15;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;2H\e(B\e[m\e]8;;\e\\ \e[16;4H \e[16;13H \e[16;20H \e[16;38H \e[16;40H \e[16;47H \e[16;56H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;2H\e(B\e[m\e]8;;\e\\ \e[17;4H \e[17;13H \e[17;20H \e[17;38H \e[17;40H \e[17;47H \e[17;54H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;2H\e(B\e[m\e]8;;\e\\ \e[18;4H \e[18;13H \e[18;20H \e[18;38H \e[18;40H \e[18;48H \e[18;59H \e[19;2H \e[19;4H \e[19;13H \e[19;20H \e[19;38H \e[19;40H \e[19;47H \e[19;53H \e[20;2H \e[20;4H \e[20;13H \e[20;20H \e[20;38H \e[20;40H \e[20;47H \e[20;52H \e[20;57H \e[21;2H \e[21;4H \e[21;13H \e[21;20H \e[21;38H \e[21;40H \e[21;50H \e[21;56H \e[22;2H \e[22;4H \e[22;13H \e[22;20H \e[22;38H \e[22;40H \e[22;47H \e[22;57H \e[23;2H \e[23;4H \e[23;13H \e[23;20H \e[23;38H \e[23;40H \e[23;48H \e[23;56H \e[23;59H \e[24;2H \e[24;4H \e[24;13H \e[24;20H \e[24;38H \e[24;40H \e[24;44H \e[25;2H \e[25;4H \e[25;13H \e[25;20H \e[25;38H \e[25;40H \e[25;47H \e[25;50H \e[25;53H \e[26;2H \e[26;4H \e[26;13H \e[26;20H \e[26;38H \e[26;40H \e[26;48H \e[26;55H \e[27;2H \e[27;4H \e[27;13H \e[27;20H \e[27;38H \e[27;40H \e[27;48H \e[27;53H \e[28;2H \e[28;4H \e[28;13H \e[28;20H \e[28;38H \e[28;40H \e[28;47H \e[28;56H \e[28;59H \e[29;2H \e[29;4H \e[29;13H \e[29;20H \e[29;38H \e[29;40H \e[29;50H \e[29;55H \e[30;2H \e[30;4H \e[30;13H \e[30;20H \e[30;38H \e[30;40H \e[30;47H \e[30;52H \e[31;2H \e[31;4H \e[31;13H \e[31;20H \e[31;38H \e[31;40H \e[31;50H \e[31;56H \e[32;2H \e[32;4H \e[32;13H \e[32;20H \e[32;38H \e[32;40H \e[32;48H \e[32;59H \e[33;2H \e[33;4H \e[33;13H \e[33;20H \e[33;38H \e[33;40H \e[33;50H \e[33;55H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;52H─1 of 2\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;62H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/tests/checkout_test.sql \e[3;62Hb/tests/checkout_test.sql\e(B\e[m\e]8;;\e\\ \e[4;62H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e(B\e[m\e]8;;\e\\ \e[4;89H \e[4;94H \e[5;62H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..21b2b98\e[6;62H--- /dev/null\e(B\e[m\e]8;;\e\\ \e[6;81H \e[6;87H \e[6;91H \e[6;100H \e[7;62H\e(B\e[m\e[1m\e]8;;\e\\+++ b/tests/checkout_test.sql\e[8;62H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[8;87H \e[8;89H \e[8;91H \e[9;62H\e(B\e[m\e[32m\e]8;;\e\\+DELETE ALL TABLES;\e(B\e[m\e]8;;\e\\ \e[10;62H\\\e[10;64HNo\e[10;67Hnewline\e[10;75Hat\e[10;78Hend\e[10;82Hof\e[10;85Hfile\e[11;62H \e[12;62H \e[13;62H \e[14;62H \e[15;62H \e[16;62H \e[17;62H \e[18;62H \e[19;62H \e[19;64H \e[19;67H \e[19;75H \e[19;78H \e[19;82H \e[19;85H \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[20;120H█\e[21;120H█\e[22;120H█\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;28H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;9H \e[35;21H \e[?25l" + - delay: 2000 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/cherry_pick-compressed.gif b/demo/cherry_pick-compressed.gif new file mode 100644 index 00000000000..91bc835b1ba Binary files /dev/null and b/demo/cherry_pick-compressed.gif differ diff --git a/demo/cherry_pick.gif b/demo/cherry_pick.gif new file mode 100644 index 00000000000..844d50092b7 Binary files /dev/null and b/demo/cherry_pick.gif differ diff --git a/demo/cherry_pick.yml b/demo/cherry_pick.yml new file mode 100644 index 00000000000..5e70ecb93c1 --- /dev/null +++ b/demo/cherry_pick.yml @@ -0,0 +1,181 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/cherry_pick.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + fontFamily: "DejaVuSansMono 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" + +# Records, feel free to edit them +records: + - delay: 5793 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 166 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Diff─────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[31m\e]8;;\e\\hotfix/fix-bug\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\No changed files \e(B\e[m\e[30m\e]8;;\e\\│\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────────────┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\hotfix/fix-bug\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\0s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/user-module\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ שׂ master \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────────────────1 of 3─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\aed338c9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Remove unused code and l\e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\764b32a3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Integrate support for ma\e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\9bf9e12a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Fix bug in timezone conv\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fc3cb1bf\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Move constants to a sepa\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ab144ffa\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Enhance user search with\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0434f817\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor session managem\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\f7918fec\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Ensure atomicity of tran\e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H└──────────────────────────────1 of 53─┘└──────────────────────────────────────────────────────────────────────────────┘\e[32;1H┌─Stash────────────────────────────────┐┌─Command log──────────────────────────────────────────────────────────────────┐\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[13;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\0 of 0\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Cherry\e[35;8Hpick\e[35;13Hcommits\e[35;21Hfrom\e[35;26Hanother\e[35;34Hbranch\e[?25l" + - delay: 1002 + content: "\e[?25l\e[35;41H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing 3 \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Log─\e[4;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e[5;40H│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────0 of 0─┘\e[14;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Local branches \e(B\e[m\e]8;;\e\\- Remotes - Tags\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────┐\e[15;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ *\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[91;44m\e[1m\e]8;;\e\\שׂ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[91;44m\e[1m\e]8;;\e\\hotfix/fix-bug\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e[16;40H│\e[17;1H│\e[17;40H│\e[18;1H│\e[18;40H│\e[19;1H│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H│\e[22;1H└───────────────────────────────1 of 3─┘\e[?25l" + - delay: 13 + content: "\e[?25l\e[2;42H\e(B\e[m\e]8;;\e\\* \e(B\e[m\e[33m\e]8;;\e\\commit aed338c (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\hotfix/fix-bug\e(B\e[m\e[33m\e]8;;\e\\)\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[3;44H\e(B\e[m\e]8;;\e\\Author:\e[3;52HCI\e[3;55H\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[4;44H\e(B\e[m\e]8;;\e\\Date:\e[4;52H1\e[4;54Hsecond\e[4;61Hago\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[6;48H\e(B\e[m\e]8;;\e\\Remove\e[6;55Hunused\e[6;62Hcode\e[6;67Hand\e[6;71Hlibraries\e[7;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[8;42H\e(B\e[m\e]8;;\e\\*\e[8;44H\e(B\e[m\e[33m\e]8;;\e\\commit 764b32a\e[9;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[9;44H\e(B\e[m\e]8;;\e\\Author:\e[9;52HCI\e[9;55H\e[10;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[10;44H\e(B\e[m\e]8;;\e\\Date:\e[10;52H1\e[10;54Hsecond\e[10;61Hago\e[11;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[12;42H|\e[12;48H\e(B\e[m\e]8;;\e\\Integrate\e[12;58Hsupport\e[12;66Hfor\e[12;70Hmarkdown\e[12;79Hin\e[12;82Huser\e[12;87Hposts\e[13;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[14;42H\e(B\e[m\e]8;;\e\\*\e[14;44H\e(B\e[m\e[33m\e]8;;\e\\commit 9bf9e12 (\e(B\e[m\e[32m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\)\e[15;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[15;44H\e(B\e[m\e]8;;\e\\Author:\e[15;52HCI\e[15;55H\e[16;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[16;44H\e(B\e[m\e]8;;\e\\Date:\e[16;52H2\e[16;54Hseconds\e[16;62Hago\e[17;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[18;42H|\e[18;48H\e(B\e[m\e]8;;\e\\Fix\e[18;52Hbug\e[18;56Hin\e[18;59Htimezone\e[18;68Hconversion.\e[19;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[20;42H\e(B\e[m\e]8;;\e\\*\e[20;44H\e(B\e[m\e[33m\e]8;;\e\\commit fc3cb1b\e[21;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[21;44H\e(B\e[m\e]8;;\e\\Author:\e[21;52HCI\e[21;55H\e[22;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[22;44H\e(B\e[m\e]8;;\e\\Date:\e[22;52H2\e[22;54Hseconds\e[22;62Hago\e[23;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[24;42H|\e[24;48H\e(B\e[m\e]8;;\e\\Move\e[24;53Hconstants\e[24;63Hto\e[24;66Ha\e[24;68Hseparate\e[24;77Hconfig\e[24;84Hfile\e[25;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[26;42H\e(B\e[m\e]8;;\e\\*\e[26;44H\e(B\e[m\e[33m\e]8;;\e\\commit ab144ff\e[27;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[27;44H\e(B\e[m\e]8;;\e\\Author:\e[27;52HCI\e[27;55H\e[28;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[28;44H\e(B\e[m\e]8;;\e\\Date:\e[28;52H2\e[28;54Hseconds\e[28;62Hago\e[29;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[30;42H|\e[30;48H\e(B\e[m\e]8;;\e\\Enhance\e[30;56Huser\e[30;61Hsearch\e[30;68Hwith\e[30;73Hfuzzy\e[30;79Hmatching\e[30;120H\e(B\e[m\e[30m\e]8;;\e\\▼\e[?25l\e[?25l\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[5;120H│\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;41H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[15;2H\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\hotfix/fix-bug\e(B\e[m\e]8;;\e\\ \e[16;2H\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\0s\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\שׂ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\feature/user-module\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e[22;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 12 + content: "\e[?25l\e[2;51H\e(B\e[m\e[33m\e]8;;\e\\d8b5214\e[2;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/user-module\e(B\e[m\e[33m\e]8;;\e\\)\e(B\e[m\e]8;;\e\\ \e[4;52H2\e[4;60Hs ago\e[6;48HAdd W\e[6;54Hbpack for asset bundling \e[8;51H\e(B\e[m\e[33m\e]8;;\e\\bc\e[8;54H88a8\e[10;52H\e(B\e[m\e]8;;\e\\2\e[10;60Hs ago\e[12;48HHandle s\e[12;57Hs\e[12;59Hion time\e[12;68Hut g\e[12;73Hacefully\e[12;82H \e[12;87H \e[16;52H3\e[22;52H3\e[28;52H3\e[?25l\e[?25l\e[?25l" + - delay: 421 + content: "\e[?25l\e[35;41H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 21 + content: "\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\C\e[1;45Hmmit\e[14;3H\e(B\e[m\e[32m\e[1m\e]8;;\e\\C\e[14;5Hmmits (feature/user-module)──\e[15;2H\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\d8b52140\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add Webpack for asset bu\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[16;2H\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bc488a81\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Handle session timeout g\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[17;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\9bf9e12a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[17;16H\e(B\e[m\e]8;;\e\\Fix\e[17;20Hbug\e[17;24Hin\e[17;27Htimezone\e[17;36Hconv\e[18;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[18;4Hfc3cb1bf\e[18;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[18;16H\e(B\e[m\e]8;;\e\\Move\e[18;21Hconstants\e[18;31Hto\e[18;34Ha\e[18;36Hsepa\e[19;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[19;4Hab144ffa\e[19;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[19;16H\e(B\e[m\e]8;;\e\\Enhance\e[19;24Huser\e[19;29Hsearch\e[19;36Hwith\e[20;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[20;4H0434f817\e[20;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[20;16H\e(B\e[m\e]8;;\e\\Refactor\e[20;25Hsession\e[20;33Hmanagem\e[21;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[21;4Hf7918fec\e[21;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[21;16H\e(B\e[m\e]8;;\e\\Ensure\e[21;23Hatomicity\e[21;33Hof\e[21;36Htran\e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e[22;32H1 of 5\e[?25l" + - delay: 9 + content: "\e[?25l\e[2;42H\e(B\e[m\e[33m\e]8;;\e\\commit d8b521405cdeba45cbe7ec84807c641b047694e3 (\e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/user-module\e(B\e[m\e[33m\e]8;;\e\\)\e[3;42H\e(B\e[m\e]8;;\e\\Author: CI \e[4;42HDate: \e[4;50HThu\e[4;54HAug 3 17:35:10\e[4;69H2023\e[4;74H+1000\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e]8;;\e\\ \e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e]8;;\e\\ \e[6;46HAdd Webpack for asset bundling \e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;42H\e(B\e[m\e]8;;\e\\ \e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;42H\e(B\e[m\e]8;;\e\\ \e[8;44H \e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;42H\e(B\e[m\e]8;;\e\\ \e[9;44H \e[9;52H \e[9;55H \e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;42H\e(B\e[m\e]8;;\e\\ \e[10;44H \e[10;52H \e[10;54H \e[10;62H \e[10;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[11;42H\e(B\e[m\e]8;;\e\\ \e[11;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[12;42H\e(B\e[m\e]8;;\e\\ \e[12;48H \e[12;55H \e[12;63H \e[12;71H \e[12;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[13;42H\e(B\e[m\e]8;;\e\\ \e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;42H\e(B\e[m\e]8;;\e\\ \e[14;44H \e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;42H\e(B\e[m\e]8;;\e\\ \e[15;44H \e[15;52H \e[15;55H \e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;42H\e(B\e[m\e]8;;\e\\ \e[16;44H \e[16;52H \e[16;54H \e[16;62H \e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;42H\e(B\e[m\e]8;;\e\\ \e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;42H\e(B\e[m\e]8;;\e\\ \e[18;48H \e[18;52H \e[18;56H \e[18;59H \e[18;68H \e[18;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[19;42H\e(B\e[m\e]8;;\e\\ \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[20;42H\e(B\e[m\e]8;;\e\\ \e[20;44H \e[20;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[21;42H\e(B\e[m\e]8;;\e\\ \e[21;44H \e[21;52H \e[21;55H \e[21;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[22;42H\e(B\e[m\e]8;;\e\\ \e[22;44H \e[22;52H \e[22;54H \e[22;62H \e[22;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[23;42H\e(B\e[m\e]8;;\e\\ \e[23;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[24;42H\e(B\e[m\e]8;;\e\\ \e[24;48H \e[24;53H \e[24;63H \e[24;66H \e[24;68H \e[24;77H \e[24;84H \e[24;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[25;42H\e(B\e[m\e]8;;\e\\ \e[25;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[26;42H\e(B\e[m\e]8;;\e\\ \e[26;44H \e[27;42H \e[27;44H \e[27;52H \e[27;55H \e[28;42H \e[28;44H \e[28;52H \e[28;54H \e[28;62H \e[29;42H \e[30;42H \e[30;48H \e[30;56H \e[30;61H \e[30;68H \e[30;73H \e[30;79H \e[?25l" + - delay: 604 + content: "\e[?25l\e[35;50H\e(B\e[m\e[36m\e[1m\e]8;;\e\\c \e[?25l" + - delay: 7 + content: "\e[?25l\e[15;2H\e(B\e[m\e[94;46m\e[1m\e]8;;\e\\ﰖ\e[15;4Hd8b52140\e[35;97H\e(B\e[m\e[36m\e]8;;\e\\1 commit copied \e(B\e[m\e[36m\e[4m\e]8;;\e\\(Reset) \e[?25l" + - delay: 10 + content: "\e[?25l\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;41H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[15;2H\e(B\e[m\e[34;46m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34;46m\e]8;;\e\\d8b52140\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Add Webpack for asset bu\e[16;2H\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\bc488a81\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Handle session timeout g\e[22;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 9 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\bc488a8170ad70cb70ce34600cb13aa2a710a59e\e(B\e[m\e]8;;\e\\ \e[6;46HHandl\e[6;52H session timeout gracefully\e[?25l" + - delay: 124 + content: "\e[?25l\e[35;41H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing c \e[?25l" + - delay: 8 + content: "\e[?25l\e[16;2H\e(B\e[m\e[94;46m\e[1m\e]8;;\e\\ﰖ\e[16;4Hbc488a81\e[35;96H\e(B\e[m\e[36m\e]8;;\e\\2 com\e[35;102Hits\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;50H\e(B\e[m\e[36m\e[1m\e]8;;\e\\4\e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Patch─\e[14;1H┌─Commits (feature/user-module)────────┐\e[15;1H│\e[15;40H▲\e[16;1H│\e(B\e[m\e[34;46m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34;46m\e]8;;\e\\bc488a81\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Handle session timeout g\e(B\e[m\e[30m\e]8;;\e\\█\e[17;1H│\e[17;40H│\e[18;1H│\e[18;40H│\e[19;1H│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H▼\e[22;1H└──────────────────────────────2 of 53─┘\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\aed338c9\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove unused code and l\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 53─┘\e[?25l" + - delay: 10 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\aed338c9\e[2;58He5e66f85773dfe869574cf9e5ce4632 (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\hotfix/fix-bug\e(B\e[m\e[33m\e]8;;\e\\)\e[6;46H\e(B\e[m\e]8;;\e\\Remov\e[6;53Hunu\e[6;57Hed code and\e[6;69Hlibraries \e[?25l" + - delay: 605 + content: "\e[?25l\e[35;50H\e(B\e[m\e[36m\e[1m\e]8;;\e\\v\e[?25l\e[?25l\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Cherry-pick───────────────────────────────────────────────────────────────────┐\e[17;21H│\e(B\e[m\e[1m\e]8;;\e\\Are you sure you want to cherry-pick the copied commits onto this branch? \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H└───────────────────────────────────────────────────────────────────────────────┘\e[23;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 53─┘\e[?25l" + - delay: 1603 + content: "\e[?25l\e[35;50H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 6 + content: "\e[?25l\e[16;21H\e(B\e[m\e]8;;\e\\e session timeout g\e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e[17;21Hug in timezone conv\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[18;21Hconstants to a sepa\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 53─┘\e[33;42H\e(B\e[m\e]8;;\e\\merges\e[33;49HHEAD\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 38 + content: "\e[?25l\e[?25l" + - delay: 73 + content: "\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[24;4H\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\fc0669\e[24;11Ha\e[24;16H\e(B\e[m\e[44m\e[1m\e]8;;\e\\Add W\e[24;22Hbpack for asset bu\e[25;4H\e(B\e[m\e[33m\e]8;;\e\\9dd3e414\e[25;16H\e(B\e[m\e]8;;\e\\Handle s\e[25;25Hs\e[25;27Hion time\e[25;36Hut g\e[26;2H\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e[26;4Haed338c9\e[26;16H\e(B\e[m\e]8;;\e\\Remove unused code and l\e[27;2H\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e[27;4H764b32a3\e[27;16H\e(B\e[m\e]8;;\e\\Int\e[27;20Hgrate support\e[27;34Hfor m\e[28;4H\e(B\e[m\e[32m\e]8;;\e\\9\e[28;6Hf9e12\e[28;16H\e(B\e[m\e]8;;\e\\Fix bug\e[28;24Hin tim\e[28;31Hzone\e[28;36Hconv\e[29;4H\e(B\e[m\e[32m\e]8;;\e\\fc\e[29;7Hcb1bf\e[29;16H\e(B\e[m\e]8;;\e\\Move c\e[29;23Hnstants to \e[29;35H sepa\e[30;4H\e(B\e[m\e[32m\e]8;;\e\\ab144\e[30;10Hfa\e[30;18H\e(B\e[m\e]8;;\e\\hance user search\e[30;36Hwith\e[31;38H\e(B\e[m\e[32m\e[1m\e]8;;\e\\5\e[?25l\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\fc0669\e[2;56Ha8cd3\e[2;62Hb0c13086b64fb95f9e1\e[2;82H31c04d7\e[6;46H\e(B\e[m\e]8;;\e\\Add W\e[6;52Hbpack for asset bundling \e[?25l" + - delay: 604 + content: "\e[?25l\e[35;52H\e(B\e[m\e[36m\e[1m\e]8;;\e\\sc> \e[?25l" + - delay: 10 + content: "\e[?25l\e[15;2H\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e[15;4Hd8b52140\e[16;2Hﰖ\e[16;4Hbc488a81\e[35;96H\e(B\e[m\e[36m\e[1m\e]8;;\e\\ \e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;41H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;8H \e[35;13H \e[35;21H \e[35;26H \e[35;34H \e[?25l" + - delay: 2002 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/commit_and_push-compressed.gif b/demo/commit_and_push-compressed.gif new file mode 100644 index 00000000000..2aca154c8c2 Binary files /dev/null and b/demo/commit_and_push-compressed.gif differ diff --git a/demo/commit_and_push.gif b/demo/commit_and_push.gif new file mode 100644 index 00000000000..920b07eab0c Binary files /dev/null and b/demo/commit_and_push.gif differ diff --git a/demo/commit_and_push.yml b/demo/commit_and_push.yml new file mode 100644 index 00000000000..6efdc745760 --- /dev/null +++ b/demo/commit_and_push.yml @@ -0,0 +1,261 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/commit_and_push.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + fontFamily: "DejaVuSansMono 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" + +# Records, feel free to edit them +records: + - delay: 5233 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 87 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Diff─────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\No changed files \e(B\e[m\e[30m\e]8;;\e\\│\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────────────┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ שׂ master \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────────────────1 of 2─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\bed3ebce\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Improve Dockerfile for m\e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\2a50f5ae\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Implement user blocking \e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\c91f9fa2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor user notificati\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\da4a8746\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Upgrade Rails version to\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\c3208fec\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Move global variables to\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3524d2e0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Fix typos in documentati\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\63658a28\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Optimize Lazygit startup\e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H└──────────────────────────────1 of 30─┘└──────────────────────────────────────────────────────────────────────────────┘\e[32;1H┌─Stash────────────────────────────────┐┌─Command log──────────────────────────────────────────────────────────────────┐\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Unstaged changes\e[5;2H\e(B\e[m\e[91;44m\e[1m\e]8;;\e\\??  my-file.txt\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e[6;2H\e(B\e[m\e[31m\e]8;;\e\\??  my-other-file.rb \e[13;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1 of 2\e[?25l" + - delay: 11 + content: "\e[?25l\e[2;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/my-file.txt b/my-file.txt\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..ada5661\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/my-file.txt\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;42H\e(B\e[m\e[32m\e]8;;\e\\+myfile content\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;42H\e(B\e[m\e]8;;\e\\\\\e[9;44HNo\e[9;47Hnewline\e[9;55Hat\e[9;58Hend\e[9;62Hof\e[9;65Hfile\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;120H█\e[11;120H█\e[12;120H█\e[13;120H█\e[14;120H█\e[15;120H█\e[16;120H█\e[17;120H█\e[18;120H█\e[19;120H█\e[20;120H█\e[21;120H█\e[22;120H█\e[23;120H█\e[30;120H▼\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Stage\e[35;7Ha\e[35;9Hfile\e[?25l" + - delay: 1002 + content: "\e[?25l\e[35;14H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 75 + content: "\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Staged changes──\e[2;42H\e(B\e[m\e]8;;\e\\ \e[2;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[3;42H\e(B\e[m\e]8;;\e\\ \e[3;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[4;42H\e(B\e[m\e]8;;\e\\ \e[4;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[5;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\A  my-file.txt\e[5;42H\e(B\e[m\e]8;;\e\\ \e[5;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[6;42H\e(B\e[m\e]8;;\e\\ \e[6;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[7;42H\e(B\e[m\e]8;;\e\\ \e[7;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[8;42H\e(B\e[m\e]8;;\e\\ \e[8;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[9;42H\e(B\e[m\e]8;;\e\\ \e[9;44H \e[9;47H \e[9;55H \e[9;58H \e[9;62H \e[9;65H \e[9;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[10;120H│\e[11;120H│\e[12;120H│\e[13;120H│\e[14;120H│\e[15;120H│\e[16;120H│\e[17;120H│\e[18;120H│\e[19;120H│\e[20;120H│\e[21;120H│\e[22;120H│\e[23;120H│\e[30;120H│\e[33;44H\e(B\e[m\e]8;;\e\\git\e[33;48Hadd\e[33;52H--\e[33;55Hmy-file.txt\e[?25l" + - delay: 5 + content: "\e[?25l\e[?25l" + - delay: 17 + content: "\e[?25l\e[2;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/my-file.txt b/my-file.txt\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..ada5661\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/my-file.txt\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;42H\e(B\e[m\e[32m\e]8;;\e\\+myfile content\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;42H\e(B\e[m\e]8;;\e\\\\\e[9;44HNo\e[9;47Hnewline\e[9;55Hat\e[9;58Hend\e[9;62Hof\e[9;65Hfile\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;120H█\e[11;120H█\e[12;120H█\e[13;120H█\e[14;120H█\e[15;120H█\e[16;120H█\e[17;120H█\e[18;120H█\e[19;120H█\e[20;120H█\e[21;120H█\e[22;120H█\e[23;120H█\e[30;120H▼\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Commit our changes \e[?25l\e[?25l\e[35;20H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing c \e[?25l\e[?25l\e[4;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e[5;40H│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌\e[13;23HCommit summary──\e[13;40H───────────────────────────────────────────────────── 0 ─────┐\e[14;21H│\e(B\e[m\e]8;;\e\\ \e[14;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;21H└───────────────────────────────────────────────────────────────────────────────┘\e[16;21H\e(B\e[m\e[30m\e]8;;\e\\┌─Commit description────────────────────────────Press to toggle focus─────┐\e[17;21H│\e[17;40H\e(B\e[m\e]8;;\e\\ \e[17;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[18;21H│\e[18;40H\e(B\e[m\e]8;;\e\\ \e[18;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;21H│\e[19;40H\e(B\e[m\e]8;;\e\\ \e[19;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;21H│\e[20;40H\e(B\e[m\e]8;;\e\\ \e[20;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;21H│\e[21;40H\e(B\e[m\e]8;;\e\\ \e[21;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[22;21H│\e(B\e[m\e]8;;\e\\ \e[22;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[23;21H│\e(B\e[m\e]8;;\e\\ \e[23;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[24;21H└───────────────────────────────────────────────────────────────────────────────┘\e[14;22H\e[?12l\e[?25h\e[0 q" + - delay: 603 + content: "\e[?25l\e[35;20H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[14;22H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1\e[14;22H\e(B\e[m\e]8;;\e\\m\e[14;23H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[14;23H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[14;23H\e(B\e[m\e]8;;\e\\y\e[14;24H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;24H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[14;25H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;25H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[14;25H\e(B\e[m\e]8;;\e\\c\e[14;26H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;26H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\5\e[14;26H\e(B\e[m\e]8;;\e\\o\e[14;27H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[14;27H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\6\e[14;27H\e(B\e[m\e]8;;\e\\m\e[14;28H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;28H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\7\e[14;28H\e(B\e[m\e]8;;\e\\m\e[14;29H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[14;29H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\8\e[14;29H\e(B\e[m\e]8;;\e\\i\e[14;30H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;30H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\9\e[14;30H\e(B\e[m\e]8;;\e\\t\e[14;31H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[14;31H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;92H\e(B\e[m\e[32m\e[1m\e]8;;\e\\ 10\e[14;32H\e[?12l\e[?25h\e[0 q" + - delay: 121 + content: "\e[?25l\e[14;32H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1\e[14;32H\e(B\e[m\e]8;;\e\\s\e[14;33H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[14;33H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[14;33H\e(B\e[m\e]8;;\e\\u\e[14;34H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;34H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[14;34H\e(B\e[m\e]8;;\e\\m\e[14;35H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[14;35H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[14;35H\e(B\e[m\e]8;;\e\\m\e[14;36H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[14;36H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\5\e[14;36H\e(B\e[m\e]8;;\e\\a\e[14;37H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;37H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\6\e[14;37H\e(B\e[m\e]8;;\e\\r\e[14;38H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[14;38H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\7\e[14;38H\e(B\e[m\e]8;;\e\\y\e[14;39H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[35;20H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[14;39H\e[?12l\e[?25h\e[0 q\e[?25l\e[5;2H\e(B\e[m\e[32m\e]8;;\e\\A  my-file.txt\e(B\e[m\e]8;;\e\\ \e[13;21H\e(B\e[m\e[30m\e]8;;\e\\┌─Commit summary─────────────────────────────────────────────────────── 17 ─────┐\e[14;21H│\e[14;101H│\e[15;21H└───────────────────────────────────────────────────────────────────────────────┘\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commit description────────────────────────────Press to toggle focus─────┐\e[17;21H│\e[17;101H│\e[18;21H│\e[18;101H│\e[19;21H│\e[19;101H│\e[20;21H│\e[20;101H│\e[21;21H│\e[21;101H│\e[22;21H│\e[22;101H│\e[23;21H│\e[23;101H│\e[24;21H└───────────────────────────────────────────────────────────────────────────────┘\e[17;22H\e[?12l\e[?25h\e[0 q" + - delay: 602 + content: "\e[?25l\e[35;20H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[17;22H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;22H\e(B\e[m\e]8;;\e\\m\e[17;23H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;23H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;23H\e(B\e[m\e]8;;\e\\y\e[17;24H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;24H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;25H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;25H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;25H\e(B\e[m\e]8;;\e\\c\e[17;26H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[17;26H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;26H\e(B\e[m\e]8;;\e\\o\e[17;27H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;27H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;27H\e(B\e[m\e]8;;\e\\m\e[17;28H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[17;28H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;28H\e(B\e[m\e]8;;\e\\m\e[17;29H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;29H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;29H\e(B\e[m\e]8;;\e\\i\e[17;30H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;30H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;30H\e(B\e[m\e]8;;\e\\t\e[17;31H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;31H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;32H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;32H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;32H\e(B\e[m\e]8;;\e\\d\e[17;33H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;33H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\e\e[17;34H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;34H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;34H\e(B\e[m\e]8;;\e\\s\e[17;35H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;35H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;35H\e(B\e[m\e]8;;\e\\c\e[17;36H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;36H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;36H\e(B\e[m\e]8;;\e\\r\e[17;37H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;37H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;37H\e(B\e[m\e]8;;\e\\i\e[17;38H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;38H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;38H\e(B\e[m\e]8;;\e\\p\e[17;39H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[17;39H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;39H\e(B\e[m\e]8;;\e\\t\e[17;40H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[17;40H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;40H\e(B\e[m\e]8;;\e\\i\e[17;41H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;41H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;41H\e(B\e[m\e]8;;\e\\o\e[17;42H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;42H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;42H\e(B\e[m\e]8;;\e\\n\e[17;43H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[35;20H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[17;43H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commit summary─────────────────────────────────────────────────────── 17 ─────┐\e[14;21H│\e[14;101H│\e[15;21H└───────────────────────────────────────────────────────────────────────────────┘\e[16;21H\e(B\e[m\e[30m\e]8;;\e\\┌─Commit description────────────────────────────Press to toggle focus─────┐\e[17;21H│\e[17;101H│\e[18;21H│\e[18;101H│\e[19;21H│\e[19;101H│\e[20;21H│\e[20;101H│\e[21;21H│\e[21;101H│\e[22;21H│\e[22;101H│\e[23;21H│\e[23;101H│\e[24;21H└───────────────────────────────────────────────────────────────────────────────┘\e[14;39H\e[?12l\e[?25h\e[0 q" + - delay: 602 + content: "\e[?25l\e[35;30H\e(B\e[m\e[36m\e[1m\e]8;;\e\\enter>\e[14;39H\e[?12l\e[?25h\e[0 q" + - delay: 14 + content: "\e[?25l\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e[5;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\A  my-file.txt\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└────────────────────\e[13;23H──────────1 of 2\e[13;40H┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[14;21H\e(B\e[m\e[30m\e]8;;\e\\emotes - Tags──────┐│\e[14;101H\e(B\e[m\e]8;;\e\\ \e[15;21H\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[16;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[17;21H \e[17;25H \e[17;32H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[17;101H \e[18;21H \e[18;40H\e(B\e[m\e[30m\e]8;;\e\\││\e[18;101H\e(B\e[m\e]8;;\e\\ \e[19;21H \e[19;40H\e(B\e[m\e[30m\e]8;;\e\\││\e[19;101H\e(B\e[m\e]8;;\e\\ \e[20;21H \e[20;40H\e(B\e[m\e[30m\e]8;;\e\\││\e[20;101H\e(B\e[m\e]8;;\e\\ \e[21;21H \e[21;40H\e(B\e[m\e[30m\e]8;;\e\\││\e[21;101H\e(B\e[m\e]8;;\e\\ \e[22;21H\e(B\e[m\e[30m\e]8;;\e\\────────────1 of 2─┘│\e[22;101H\e(B\e[m\e]8;;\e\\ \e[23;21H\e(B\e[m\e[30m\e]8;;\e\\───────────────────┐│\e[23;101H\e(B\e[m\e]8;;\e\\ \e[24;21Hve Dockerfile for m\e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e[33;48Hcommit\e[33;55H-m \"my commit\e[33;69Hsummary\"\e[33;78H-m\e[33;81H\"my\e[33;85Hcommit\e[33;92Hdescription\"\e[?25l\e[?25l\e[?25l" + - delay: 50 + content: "\e[?25l\e[33;43H\e(B\e[m\e]8;;\e\\create \e[33;51Hode\e[33;55H100644\e[33;62Hmy-f\e[33;67Hle.txt \e[33;78H \e[33;81H \e[33;85H \e[33;92H \e[?25l\e[?25l\e[?25l" + - delay: 7 + content: "\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[22;33H\e(B\e[m\e[30m\e]8;;\e\\──────\e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Unstaged changes\e[5;2H\e(B\e[m\e[91;44m\e[1m\e]8;;\e\\??  my-other-file.rb \e[6;2H\e(B\e[m\e]8;;\e\\ \e[13;38H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1\e[22;33H\e(B\e[m\e[30m\e]8;;\e\\1 of 2\e[?25l" + - delay: 5 + content: "\e[?25l\e[15;21H\e(B\e[m\e[33m\e]8;;\e\\↑1\e[16;2H\e(B\e[m\e[36m\e]8;;\e\\9\e[24;2H\e(B\e[m\e[31m\e]8;;\e\\ﰖ\e[24;4H0853c88f\e[24;16H\e(B\e[m\e]8;;\e\\my c\e[24;21Hmmit summary \e[24;35H \e[24;39H \e[25;4H\e(B\e[m\e[32m\e]8;;\e\\bed3ebc\e[25;19H\e(B\e[m\e]8;;\e\\rov\e[25;23H Dock\e[25;30Hfi\e[25;33He for m\e[26;4H\e(B\e[m\e[32m\e]8;;\e\\2a50f5\e[26;11He\e[26;16H\e(B\e[m\e]8;;\e\\Implement user blocking \e[27;4H\e(B\e[m\e[32m\e]8;;\e\\c91f9fa2\e[27;16H\e(B\e[m\e]8;;\e\\Refactor user\e[27;30Hnotifica\e[27;39Hi\e[28;4H\e(B\e[m\e[32m\e]8;;\e\\da4a\e[28;9H746\e[28;16H\e(B\e[m\e]8;;\e\\Upgrade R\e[28;26Hils version\e[29;4H\e(B\e[m\e[32m\e]8;;\e\\c3\e[29;7H08f\e[29;11Hc\e[29;16H\e(B\e[m\e]8;;\e\\Move gl\e[29;24Hbal variables \e[29;39Ho\e[30;4H\e(B\e[m\e[32m\e]8;;\e\\3524d2e0\e[30;16H\e(B\e[m\e]8;;\e\\Fix typos in documentati\e[31;38H\e(B\e[m\e[30m\e]8;;\e\\1\e[?25l\e[?25l\e[2;2H\e(B\e[m\e[33m\e]8;;\e\\↑1\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e[?25l\e[?25l\e[2;58H\e(B\e[m\e[1m\e]8;;\e\\oth\e[2;62Hr-file.rb b/my-other-file.rb\e[4;57Hb34702a\e[6;51Hoth\e[6;55Hr-file.rb\e[8;45H\e(B\e[m\e[32m\e]8;;\e\\-other-file content\e[?25l" + - delay: 21 + content: "\e[?25l\e[?25l" + - delay: 605 + content: "\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push \e[35;7Ho the remote\e[35;20H \e[?25l\e[?25l\e[35;20H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing P \e[?25l\e[?25l\e[4;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e[5;40H│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────1 of 1─┘\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌───────────────────────────────────────────────────────────────────────────────┐\e[17;21H│\e(B\e[m\e[1m\e]8;;\e\\Pushing...\e[17;33H\e(B\e[m\e]8;;\e\\|\e[17;40H \e[17;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H└───────────────────────────────────────────────────────────────────────────────┘\e[?25l" + - delay: 51 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\/\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\-\e[?25l" + - delay: 49 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\\\\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\|\e[?25l" + - delay: 51 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\/\e[?25l" + - delay: 49 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\-\e[?25l" + - delay: 51 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\\\\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\|\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\/\e[?25l" + - delay: 52 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\-\e[33;43H git push \e[33;55H \e[33;62H \e[?25l" + - delay: 47 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\\\\e[?25l" + - delay: 52 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\|\e[?25l" + - delay: 41 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\/\e[33;44H bed3ebc..0853c88\e[33;63Hfeature/demo\e[33;76H->\e[33;79Hfeature/demo\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[?25l\e[?25l\e[15;21H\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e[16;2H\e(B\e[m\e[36m\e]8;;\e\\11s\e[24;2H\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e[24;4H0853c88f\e[?25l\e[?25l\e[2;2H\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e[16;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[17;21H \e[17;33H \e[17;40H\e(B\e[m\e[30m\e]8;;\e\\││\e[17;101H\e(B\e[m\e]8;;\e\\ \e[18;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[?25l\e[?25l\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e[5;1H│\e[5;40H│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────1 of 1─┘\e[?25l" + - delay: 11 + content: "\e[?25l\e[?25l" + - delay: 601 + content: "\e[?25l\e[35;20H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;6H \e[35;9H \e[35;13H \e[?25l" + - delay: 2000 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/commit_graph-compressed.gif b/demo/commit_graph-compressed.gif new file mode 100644 index 00000000000..bcf2f78ae6a Binary files /dev/null and b/demo/commit_graph-compressed.gif differ diff --git a/demo/commit_graph.gif b/demo/commit_graph.gif new file mode 100644 index 00000000000..ac7a892259f Binary files /dev/null and b/demo/commit_graph.gif differ diff --git a/demo/commit_graph.yml b/demo/commit_graph.yml new file mode 100644 index 00000000000..eb5ce61a5ff --- /dev/null +++ b/demo/commit_graph.yml @@ -0,0 +1,261 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/commit_graph.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + 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" + +# Records, feel free to edit them +records: + - delay: 3899 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J" + - delay: 7 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 74 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F062D\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\52bde32a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\17 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\⏣─╮\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> master)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[3;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\00659d59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[97m\e[1m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Replace deprecated lifecycle methods in React c\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[4;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\814465df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[97m\e[1m\e]8;;\e\\⏣\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[5;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5db95cc7\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[6;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\86b699c6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle edge case for zero quantity in cart \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[7;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\f274dee2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[8;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0c069234\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(feature/peace-time)\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[9;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d9d2dd9f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[10;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e8db965b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[11;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\beefc2b0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\28da29ad\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8884800f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update UX of password reset feature \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\39aa771c\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\abab506c\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\08 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(quash-rebellion)\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7984ca92\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\07 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\109961e4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\06 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Harden security of user password storage \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\89c7c90b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\80252fcd\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\─\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\─\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\─\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a71af8e3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\04 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(terra-investigation)\e(B\e[m\e]8;;\e\\ Ensure CSRF protection for all forms \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d157f334\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\04 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\─│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─╯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge hotfix/fezzan-corridor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a8ba68d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\03 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\╭─╯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(hotfix/fezzan-corridor)\e(B\e[m\e]8;;\e\\ Introduce Redis for session management \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\2bf84013\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\02 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\╭─╯\e(B\e[m\e]8;;\e\\ Improve Dockerfile for more efficient builds \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\05082d94\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\01 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement user blocking functionality \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\70796477\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\31 May 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Refactor user notifications system \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a9bde740\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\31 May 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge iserlohn-build into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\9c91074d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\30 May 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(iserlohn-build)\e(B\e[m\e]8;;\e\\ Upgrade Rails version to 6.1.4 \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\af45c53e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\29 May 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Move global variables to environment config \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\1b7bb211\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\28 May 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix typos in documentation \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\1faec5a4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\27 May 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Optimize Lazygit startup time \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\38c97533\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26 May 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor logic in Lazygit Diff view \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\137270f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26 May 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─╯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge r-u-fkn-srs into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7d48b05d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\25 May 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\╭─╯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(r-u-fkn-srs)\e(B\e[m\e]8;;\e\\ Add sorting feature to product listing page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 54─┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\View\e[35;6Hcommit\e[35;13Hlog\e[?25l" + - delay: 1004 + content: "\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[2;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\52bde32a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD -> master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e[3;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\00659d59\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\16 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(feature/iserlohn-backdoor)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Replace deprecated lifecycle methods in React c\e[4;41H\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\⏣─\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[5;43H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[6;43H│\e[7;43H│\e(B\e[m\e]8;;\e\\ \e[8;43H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[9;43H│\e[10;43H│\e(B\e[m\e]8;;\e\\ \e[11;43H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[12;43H│\e[13;43H│\e[14;41H⏣─╯\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 10 + content: "\e[?25l\e[?25l" + - delay: 225 + content: "\e[?25l\e[?25l" + - delay: 7 + content: "\e[?25l\e[3;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\00659d59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Replace deprecated lifecycle methods in React c\e[4;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F062D\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\814465df\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\16 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\⏣───╮\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Merge feature/repair-brunhild into master \e[5;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[5;43H\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e[5;45H\e(B\e[m\e[97m\e[1m\e]8;;\e\\◯\e[6;41H│\e[6;43H\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e[7;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\⏣\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─\e[8;43H\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e[9;43H│\e[10;43H│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─\e[11;43H\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e[12;43H│\e[13;43H│\e[14;41H\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 223 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[4;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\814465df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e[5;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\5db95cc7\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\15 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(feature/repair-brunhild)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp error handling in user registration \e[6;41H\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e[7;41H\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\⏣─\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 224 + content: "\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[5;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5db95cc7\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e[6;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\86b699c6\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\14 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Handle edge case for zero quantity in cart \e[7;45H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[8;45H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[9;45H│\e[10;45H│\e(B\e[m\e]8;;\e\\ \e[11;45H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[12;45H│\e[13;45H│\e[14;45H│\e[15;45H│\e[16;45H│\e[17;45H│\e[18;45H│\e[19;45H│\e(B\e[m\e]8;;\e\\ \e[20;45H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[21;41H⏣───╯\e(B\e[m\e]8;;\e\\ \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\5\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 223 + content: "\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[6;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\86b699c6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle edge case for zero quantity in cart \e[7;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F062D\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\f274dee2\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\14 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\Paul Oberstein \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\⏣─────╮\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Merge feature/peace-time into master \e[8;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[8;45H\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e[8;47H\e(B\e[m\e[97m\e[1m\e]8;;\e\\◯\e[9;41H│\e[9;45H\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e[10;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\⏣\e(B\e[m\e]8;;\e\\ \e[10;45H\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─\e[11;45H\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e[12;45H│\e[13;45H│\e[14;45H│\e[15;45H│\e[16;45H│\e[17;45H│\e[18;45H│\e[19;45H│\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\─\e[20;45H\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e[21;41H⏣─\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\─│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\6\e[?25l" + - delay: 10 + content: "\e[?25l\e[?25l" + - delay: 225 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[7;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\f274dee2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e[8;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\0c069234\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\13 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\Paul Oberstein \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(feature/peace-time)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce retry mechanism in network calls \e[9;41H\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e[10;41H\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\⏣─\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\7\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 224 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[8;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0c069234\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(feature/peace-time)\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e[9;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\d9d2dd9f\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\12 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\Paul Oberstein \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove hardcoded values from payment module \e[10;47H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[11;47H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[12;47H│\e[13;47H│\e[14;47H│\e[15;47H│\e[16;47H│\e[17;47H│\e[18;47H│\e[19;47H│\e(B\e[m\e]8;;\e\\ \e[20;47H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[21;41H⏣─────╯\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\8\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 224 + content: "\e[?25l\e[?25l" + - delay: 8 + content: "\e[?25l\e[9;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d9d2dd9f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e[10;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F062D\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\e8db965b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\12 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\⏣───────╮\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Merge feature/attack-on-odin into master \e[11;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[11;47H\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e[11;49H\e(B\e[m\e[97m\e[1m\e]8;;\e\\◯\e[12;41H│\e[12;47H\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e[13;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[13;47H\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e[14;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\⏣\e(B\e[m\e]8;;\e\\ \e[14;47H\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e[15;47H│\e[16;47H│\e[17;47H│\e[18;47H│\e[19;47H│\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\─\e[20;47H\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e[21;41H\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\─│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─╯\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\9\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 223 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[10;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e8db965b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e[11;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\beefc2b0\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(feature/attack-on-odin)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Enhance logging in production environment \e[12;41H\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e[13;41H│\e[14;41H\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\⏣─\e[34;111H\e(B\e[m\e[32m\e[1m\e]8;;\e\\10\e[?25l" + - delay: 10 + content: "\e[?25l\e[?25l" + - delay: 225 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[11;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\beefc2b0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e[12;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\28da29ad\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\10 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add internationalization support for German \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 224 + content: "\e[?25l\e[?25l\e[?25l\e[12;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\28da29ad\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e[13;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\8884800f\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\09 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Update UX of password reset feature \e[14;49H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[15;49H│\e[16;49H│\e[17;49H│\e[18;49H│\e[19;49H│\e(B\e[m\e]8;;\e\\ \e[20;49H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[21;49H│\e[22;47H╭─╯\e[23;47H│\e[24;47H│\e[25;47H│\e[26;47H│\e(B\e[m\e]8;;\e\\ \e[27;47H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[28;47H│\e[29;47H│\e[30;47H│\e[31;47H│\e[32;41H⏣─────╯\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 223 + content: "\e[?25l\e[?25l" + - delay: 7 + content: "\e[?25l\e[13;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8884800f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update UX of password reset feature \e[14;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F062D\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\39aa771c\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\09 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\Yang Wen-li \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\⏣─╮\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Merge quash-rebellion into master \e[15;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[15;43H◯\e[15;49H\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e[16;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[16;49H\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e[17;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[17;49H\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e[18;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[18;49H\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e[19;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\⏣\e(B\e[m\e]8;;\e\\ \e[19;49H\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\─\e[20;49H\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e[21;49H│\e[22;47H╭─╯\e[23;47H│\e[24;47H│\e[25;47H│\e[26;47H│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─\e[27;47H\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e[28;47H│\e[29;47H│\e[30;47H│\e[31;47H│\e[32;41H⏣─\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─╯\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 224 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[14;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\39aa771c\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e[15;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\abab506c\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\08 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\Yang Wen-li \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(quash-rebellion)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Migrate legacy codebase to Typescript \e[16;41H\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e[17;41H│\e[18;41H│\e[19;41H\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\⏣─\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[?25l" + - delay: 10 + content: "\e[?25l\e[?25l" + - delay: 223 + content: "\e[?25l\e[?25l" + - delay: 7 + content: "\e[?25l\e[15;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\abab506c\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\08 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(quash-rebellion)\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e[16;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\7984ca92\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\07 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\Yang Wen-li \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Resolve race condition in transaction handling \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\5\e[?25l" + - delay: 12 + content: "\e[?25l\e[?25l" + - delay: 222 + content: "\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[16;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7984ca92\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\07 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e[17;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\109961e4\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\06 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\Yang Wen-li \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Harden security of user password storage \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\6\e[?25l" + - delay: 8 + content: "\e[?25l\e[?25l" + - delay: 222 + content: "\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[17;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\109961e4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\06 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Harden security of user password storage \e[18;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\89c7c90b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\05 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\Yang Wen-li \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Implement bulk delete feature in admin panel \e[19;43H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[20;43H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[21;43H│\e(B\e[m\e]8;;\e\\ \e[22;43H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[23;43H│\e[24;43H│\e[25;43H│\e[26;43H│\e(B\e[m\e]8;;\e\\ \e[27;43H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[28;43H│\e[29;43H│\e[30;43H│\e[31;43H│\e[32;41H⏣─╯\e(B\e[m\e]8;;\e\\ \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\7\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 225 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[18;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\89c7c90b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e[19;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F062D\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\80252fcd\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\05 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\⏣─────────╮\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Merge terra-investigation into master \e[20;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[20;43H\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e[20;51H\e(B\e[m\e[97m\e[1m\e]8;;\e\\◯\e[21;41H⏣\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\─\e[22;43H\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e[23;43H│\e[24;43H│\e[25;43H│\e[26;43H│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─\e[27;43H\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e[28;43H│\e[29;43H│\e[30;43H│\e[31;43H│\e[32;41H\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\─\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\8\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 224 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[19;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\80252fcd\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\─\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\─\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\─\e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e[20;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\a71af8e3\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\04 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(terra-investigation)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure CSRF protection for all forms \e[21;41H\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\⏣─\e[21;51H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[22;51H│\e[23;49H╭─╯\e[24;49H│\e[25;49H│\e[26;49H│\e(B\e[m\e]8;;\e\\ \e[27;49H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[28;49H│\e[29;49H│\e[30;49H│\e[31;49H│\e[32;49H│\e[33;47H╭─╯\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\9\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 225 + content: "\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[20;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a71af8e3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\04 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(terra-investigation)\e(B\e[m\e]8;;\e\\ Ensure CSRF protection for all forms \e[21;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F062D\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\d157f334\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\04 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\⏣───╮\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;255;213;0m\e[44m\e[1m\e]8;;\e\\╯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Merge hotfix/fezzan-corridor into master \e[22;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[22;45H◯\e[22;51H\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e[23;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[23;49H\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\╭─╯\e[24;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[24;49H\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e[25;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[25;49H\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e[26;41H\e(B\e[m\e[97m\e[1m\e]8;;\e\\⏣\e(B\e[m\e]8;;\e\\ \e[26;49H\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─\e[27;49H\e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e[28;49H│\e[29;49H│\e[30;49H│\e[31;49H│\e[32;49H│\e[33;47H╭─╯\e[34;111H\e(B\e[m\e[32m\e[1m\e]8;;\e\\20\e[?25l" + - delay: 10 + content: "\e[?25l\e[?25l" + - delay: 224 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[21;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d157f334\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\04 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\⏣─\e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\─│\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\─╯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge hotfix/fezzan-corridor into master \e[22;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\8a8ba68d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\03 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\╭─╯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(hotfix/fezzan-corridor)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce Redis for session management \e[23;41H\e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e[24;41H│\e[25;41H│\e[26;41H\e(B\e[m\e[38;2;255;213;0m\e]8;;\e\\⏣─\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 225 + content: "\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[22;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a8ba68d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\03 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\╭─╯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(hotfix/fezzan-corridor)\e(B\e[m\e]8;;\e\\ Introduce Redis for session management \e[23;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\2bf84013\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\02 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\╭─╯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Improve Dockerfile for more efficient builds \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 224 + content: "\e[?25l\e[?25l" + - delay: 7 + content: "\e[?25l\e[23;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\2bf84013\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\02 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\╭─╯\e(B\e[m\e]8;;\e\\ Improve Dockerfile for more efficient builds \e[24;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\05082d94\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\01 Jun 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Implement user blocking functionality \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[?25l" + - delay: 10 + content: "\e[?25l\e[?25l" + - delay: 224 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[24;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\05082d94\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\01 Jun 23\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement user blocking functionality \e[25;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\70796477\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\31 May 23\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;134;200;47m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;142;60;203m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;254;126;17m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;251;90;163m\e[44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Refactor user notifications system \e[26;45H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[27;45H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[28;45H│\e[29;45H│\e[30;45H│\e[31;45H│\e[32;45H│\e(B\e[m\e]8;;\e\\ \e[33;45H\e(B\e[m\e[97m\e[1m\e]8;;\e\\│\e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 225 + content: "\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;6H \e[35;13H \e[?25l" + - delay: 2001 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/config.yml b/demo/config.yml deleted file mode 100644 index defe50a5bc1..00000000000 --- a/demo/config.yml +++ /dev/null @@ -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" diff --git a/demo/custom_command-compressed.gif b/demo/custom_command-compressed.gif new file mode 100644 index 00000000000..9b0fe41d5c6 Binary files /dev/null and b/demo/custom_command-compressed.gif differ diff --git a/demo/custom_command.gif b/demo/custom_command.gif new file mode 100644 index 00000000000..d7a95edaafe Binary files /dev/null and b/demo/custom_command.gif differ diff --git a/demo/custom_command.yml b/demo/custom_command.yml new file mode 100644 index 00000000000..4cfef3de8c9 --- /dev/null +++ b/demo/custom_command.yml @@ -0,0 +1,161 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/custom_command.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + fontFamily: "DejaVuSansMono 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" + +# Records, feel free to edit them +records: + - delay: 5419 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 88 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Diff─────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[33m\e]8;;\e\\bugfix/fix-crash-bug\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\No changed files \e(B\e[m\e[30m\e]8;;\e\\│\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────────────┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bugfix/fix-crash-bug\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\0s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bugfix/fix-login-issue\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\0s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/mobile-responsive\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\0s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/search-functionality\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\0s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/payment-processing\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\0s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/user-authentication\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\0s\e(B\e[m\e]8;;\e\\ שׂ master \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────────────────1 of 7─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\04be8ec7\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Improve Dockerfile for m\e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a4efad28\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Implement user blocking \e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\6c2cac4f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor user notificati\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0d15591f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Upgrade Rails version to\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4a2a1937\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Move global variables to\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\984e5618\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Fix typos in documentati\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\60326b21\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Optimize Lazygit startup\e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H└──────────────────────────────1 of 30─┘└──────────────────────────────────────────────────────────────────────────────┘\e[32;1H┌─Stash────────────────────────────────┐┌─Command log──────────────────────────────────────────────────────────────────┐\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Unstaged changes\e[5;2H\e(B\e[m\e[91;44m\e[1m\e]8;;\e\\??  custom_commands_example.yml \e[13;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1 of 1\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/custom_commands_example.yml b/custom_commands_example.yml\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..758d20d\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/custom_commands_example.yml\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1,11 @@\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;42H\e(B\e[m\e[32m\e]8;;\e\\+customCommands:\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;42H\e(B\e[m\e[32m\e]8;;\e\\+ - key: 'a'\e[10;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[11;42H\e(B\e[m\e[32m\e]8;;\e\\+ command: 'git checkout {{.Form.Branch}}'\e[11;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[12;42H\e(B\e[m\e[32m\e]8;;\e\\+ context: 'localBranches'\e[12;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[13;42H\e(B\e[m\e[32m\e]8;;\e\\+ prompts:\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;42H\e(B\e[m\e[32m\e]8;;\e\\+ - type: 'input'\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;42H\e(B\e[m\e[32m\e]8;;\e\\+ title: 'Enter a branch name to checkout:'\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;42H\e(B\e[m\e[32m\e]8;;\e\\+ key: 'Branch'\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[17;54Hsuggestions:\e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[18;58Hpreset: 'branches'\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[19;120H█\e[30;120H▼\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Invoke\e[35;8Ha\e[35;10Hcustom\e[35;17Hcommand\e[?25l" + - delay: 1503 + content: "\e[?25l\e[35;25H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing 3 \e[?25l" + - delay: 5 + content: "\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Log─────────────\e[4;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e(B\e[m\e[31m\e]8;;\e\\??  custom_commands_example.yml \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────1 of 1─┘\e[14;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Local branches \e(B\e[m\e]8;;\e\\- Remotes - Tags\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────┐\e[15;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ *\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\שׂ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\bugfix/fix-crash-bug\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e[16;40H│\e[17;1H│\e[17;40H│\e[18;1H│\e[18;40H│\e[19;1H│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H│\e[22;1H└───────────────────────────────1 of 7─┘\e[?25l" + - delay: 10 + content: "\e[?25l\e[2;42H\e(B\e[m\e]8;;\e\\* \e(B\e[m\e[33m\e]8;;\e\\commit 04be8ec (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\bugfix/fix-crash-bug\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/user- \e[3;42Hauthentication\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/search-functionality\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/payment-processing\e(B\e[m\e[33m\e]8;;\e\\, \e[4;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\bugfix/fix-login-issue\e(B\e[m\e[33m\e]8;;\e\\)\e[5;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Author: CI \e[5;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[6;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Date: 2 seconds ago \e[6;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[7;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[7;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[8;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[8;48H\e(B\e[m\e]8;;\e\\Improve\e[8;56HDockerfile\e[8;67Hfor\e[8;71Hmore\e[8;76Hefficient\e[8;86Hbuilds\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[9;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[9;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[10;42H\e(B\e[m\e]8;;\e\\* \e(B\e[m\e[33m\e]8;;\e\\commit a4efad2\e[10;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[11;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Author: CI \e[11;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[12;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Date: 3 seconds ago \e[12;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[13;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[13;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[14;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Implement user\e[14;63Hblocking\e[14;72Hfunctionality\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[15;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[15;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[16;42H\e(B\e[m\e]8;;\e\\* \e(B\e[m\e[33m\e]8;;\e\\commit 6c2cac4\e(B\e[m\e]8;;\e\\ \e[16;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[17;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[17;44H\e(B\e[m\e]8;;\e\\Author:\e[17;52HCI \e[17;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[18;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[18;44H\e(B\e[m\e]8;;\e\\Date:\e[18;52H3\e[18;54Hseconds ago \e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[20;48H\e(B\e[m\e]8;;\e\\Refactor\e[20;57Huser\e[20;62Hnotifications\e[20;76Hsystem\e[21;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[22;42H\e(B\e[m\e]8;;\e\\*\e[22;44H\e(B\e[m\e[33m\e]8;;\e\\commit 0d15591\e[23;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[23;44H\e(B\e[m\e]8;;\e\\Author:\e[23;52HCI\e[23;55H\e[24;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[24;44H\e(B\e[m\e]8;;\e\\Date:\e[24;52H3\e[24;54Hseconds\e[24;62Hago\e[25;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[26;42H|\e[26;48H\e(B\e[m\e]8;;\e\\Upgrade\e[26;56HRails\e[26;62Hversion\e[26;70Hto\e[26;73H6.1.4\e[27;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[28;42H\e(B\e[m\e]8;;\e\\*\e[28;44H\e(B\e[m\e[33m\e]8;;\e\\commit 4a2a193\e[29;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[29;44H\e(B\e[m\e]8;;\e\\Author:\e[29;52HCI\e[29;55H\e[30;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[30;44H\e(B\e[m\e]8;;\e\\Date:\e[30;52H3\e[30;54Hseconds\e[30;62Hago\e[?25l\e[?25l\e[?25l" + - delay: 1104 + content: "\e[?25l\e[35;34H\e(B\e[m\e[36m\e[1m\e]8;;\e\\a\e[?25l\e[?25l\e[11;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Enter a branch name to checkout───────────────────────────────────────────────┐\e[12;21H│\e[12;40H\e(B\e[m\e]8;;\e\\ \e[12;44H \e[12;52H \e[12;54H \e[12;62H \e[12;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;21H└───────────────────────────────────────────────────────────────────────────────┘\e[14;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- R┌─Suggestions (press to focus)────────────────────────────────────────────┐\e[15;1H│\e[15;21H│\e(B\e[m\e[33m\e]8;;\e\\bugfix/fix-crash-bug\e(B\e[m\e]8;;\e\\ \e[15;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e[16;21H│\e(B\e[m\e[33m\e]8;;\e\\bugfix/fix-login-issue\e(B\e[m\e]8;;\e\\ \e[16;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e[17;21H│\e(B\e[m\e[32m\e]8;;\e\\feature/mobile-responsive\e(B\e[m\e]8;;\e\\ \e[17;52H \e[17;55H \e[17;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e[18;21H│\e(B\e[m\e[32m\e]8;;\e\\feature/search-functionality\e[18;52H\e(B\e[m\e]8;;\e\\ \e[18;54H \e[18;62H \e[18;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e[19;21H│\e(B\e[m\e[32m\e]8;;\e\\featur\e[19;29H/payment-processing\e[19;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e[20;21H│\e(B\e[m\e[32m\e]8;;\e\\feature/user-authentication\e(B\e[m\e]8;;\e\\ \e[20;57H \e[20;62H \e[20;76H \e[20;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e[21;21H│\e(B\e[m\e]8;;\e\\master\e[21;40H \e[21;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────│\e(B\e[m\e]8;;\e\\ \e[22;44H \e[22;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[23;21H│\e(B\e[m\e]8;;\e\\ \e[23;44H \e[23;52H \e[23;55H \e[23;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[24;21H│\e(B\e[m\e]8;;\e\\ \e[24;24H \e[24;35H \e[24;39H \e[24;44H \e[24;52H \e[24;54H \e[24;62H \e[24;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[25;21H└────────────────────────────────────────────────────────────────────────1 of 7─┘\e[12;22H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;22H\e[?12l\e[?25h\e[0 q" + - delay: 1103 + content: "\e[?25l\e[35;25H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[12;22H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;22H\e(B\e[m\e]8;;\e\\m\e[15;22Hmaster \e[16;22H\e(B\e[m\e[32m\e]8;;\e\\feature/mobile-responsive\e[17;30Hpayment-proce\e[17;44Hsing \e[18;22H\e(B\e[m\e]8;;\e\\ \e[19;22H \e[20;22H \e[21;22H \e[25;99H\e(B\e[m\e[30m\e]8;;\e\\3\e[12;23H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;23H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[12;23H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;23H\e(B\e[m\e]8;;\e\\o\e[15;22H\e(B\e[m\e[32m\e]8;;\e\\feature/mobile-responsive\e[16;30Hpayment-proce\e[16;44Hsing \e[17;22H\e(B\e[m\e]8;;\e\\ \e[25;99H\e(B\e[m\e[30m\e]8;;\e\\2\e[12;24H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;24H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[12;24H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;24H\e(B\e[m\e]8;;\e\\b\e[15;47H\e(B\e[m\e[32m\e]8;;\e\\ \e[16;22H\e(B\e[m\e]8;;\e\\ \e[25;99H\e(B\e[m\e[30m\e]8;;\e\\1\e[12;25H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;25H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[12;25H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;25H\e(B\e[m\e]8;;\e\\i\e[12;26H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;26H\e[?12l\e[?25h\e[0 q" + - delay: 121 + content: "\e[?25l\e[12;26H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;26H\e(B\e[m\e]8;;\e\\l\e[12;27H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;27H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[12;27H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;27H\e(B\e[m\e]8;;\e\\e\e[12;28H\e[?12l\e[?25h\e[0 q\e[?25l\e[12;28H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[35;25H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[12;28H\e[?12l\e[?25h\e[0 q\e[?25l\e[11;21H\e(B\e[m\e[30m\e]8;;\e\\┌─Enter a branch name to checkout───────────────────────────────────────────────┐\e[12;21H│\e[12;101H│\e[13;21H└───────────────────────────────────────────────────────────────────────────────┘\e[14;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Suggestions (press to focus)────────────────────────────────────────────┐\e[15;2H\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bugfix/fix-cr\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\feature/mobile-responsive \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;21H│\e[16;101H│\e[17;21H│\e[17;101H│\e[18;21H│\e[18;101H│\e[19;21H│\e[19;101H│\e[20;21H│\e[20;101H│\e[21;21H│\e[21;101H│\e[22;21H│\e[22;101H│\e[23;21H│\e[23;101H│\e[24;21H│\e[24;101H│\e[25;21H└────────────────────────────────────────────────────────────────────────1 of 1─┘\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;35H\e(B\e[m\e[36m\e[1m\e]8;;\e\\enter>\e[?25l" + - delay: 5 + content: "\e[?25l\e[11;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Author: CI \e[12;21H \e[12;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[31m\e]8;;\e\\|\e[12;44H\e(B\e[m\e]8;;\e\\Date:\e[12;52H3\e[12;54Hseconds\e[12;62Hago\e[12;101H \e[13;21H\e(B\e[m\e[30m\e]8;;\e\\─\e[13;33H1 of 1\e[13;40H┘│\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[14;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Local branches \e(B\e[m\e]8;;\e\\- Remotes - Tags\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Implement user blocking functionality \e[15;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ *\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\שׂ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\bugfix/fix-crash-bug\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[16;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;21H\e(B\e[m\e[33m\e]8;;\e\\gin-issue\e[16;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\*\e[16;44H\e(B\e[m\e[33m\e]8;;\e\\commit 6c2cac4\e[16;101H\e(B\e[m\e]8;;\e\\ \e[17;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;21H\e(B\e[m\e[32m\e]8;;\e\\e-responsive\e[17;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e[17;44H\e(B\e[m\e]8;;\e\\Author:\e[17;52HCI\e[17;55H\e[17;101H \e[18;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H\e(B\e[m\e[32m\e]8;;\e\\h-functionality\e[18;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e[18;44H\e(B\e[m\e]8;;\e\\Date:\e[18;52H3\e[18;54Hseconds\e[18;62Hago\e[18;101H \e[19;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;21H\e(B\e[m\e[32m\e]8;;\e\\nt-processing\e[19;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e[19;101H\e(B\e[m\e]8;;\e\\ \e[20;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;21H\e(B\e[m\e[32m\e]8;;\e\\authentication\e[20;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e[20;48H\e(B\e[m\e]8;;\e\\Refactor\e[20;57Huser\e[20;62Hnotifications\e[20;76Hsystem\e[20;101H \e[21;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;21H\e(B\e[m\e]8;;\e\\ \e[21;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e[21;101H\e(B\e[m\e]8;;\e\\ \e[22;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└───────────────────────────────1 of 7─┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\*\e[22;44H\e(B\e[m\e[33m\e]8;;\e\\commit 0d15591\e[22;101H\e(B\e[m\e]8;;\e\\ \e[23;21H\e(B\e[m\e[30m\e]8;;\e\\───────────────────┐│\e(B\e[m\e[31m\e]8;;\e\\|\e[23;44H\e(B\e[m\e]8;;\e\\Author:\e[23;52HCI\e[23;55H\e[23;101H \e[24;21Hve\e[24;24HDockerfile\e[24;35Hfor\e[24;39Hm\e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e[31m\e]8;;\e\\|\e[24;44H\e(B\e[m\e]8;;\e\\Date:\e[24;52H3\e[24;54Hseconds\e[24;62Hago\e[24;101H \e[25;21Hment user blocking \e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[33;44Hbash\e[33;49H-c\e[33;52H\"git\e[33;57Hcheckout\e[33;66Hfeature/mobile-responsive\"\e[?25l" + - delay: 10 + content: "\e[?25l\e[6;52H\e(B\e[m\e]8;;\e\\5\e[12;52H6\e[18;52H6\e[24;52H6\e[30;52H6\e[?25l\e[?25l\e[?25l" + - delay: 23 + content: "\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[15;6H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\שׂ\e[15;8Hfeature/mobile-responsive\e[16;2H\e(B\e[m\e[36m\e]8;;\e\\5\e[16;19H\e(B\e[m\e[33m\e]8;;\e\\crash\e[16;25Hbug\e(B\e[m\e]8;;\e\\ \e[17;2H\e(B\e[m\e[36m\e]8;;\e\\5\e[17;6H\e(B\e[m\e[33m\e]8;;\e\\שׂ\e[17;8Hbugfix/fix-login-issue\e(B\e[m\e]8;;\e\\ \e[18;2H\e(B\e[m\e[36m\e]8;;\e\\5\e[19;2H5\e[20;2H5\e[21;2H5\e[?25l\e[?25l\e[2;10H\e(B\e[m\e[32m\e]8;;\e\\feature/mobile-responsive\e[24;2H\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e[24;4Hffe5f9ec\e[24;16H\e(B\e[m\e]8;;\e\\Make mobile response \e[24;39H \e[25;4H\e(B\e[m\e[32m\e]8;;\e\\0\e[25;6Hbe8ec7\e[25;19H\e(B\e[m\e]8;;\e\\rov\e[25;23H Dock\e[25;30Hfi\e[25;33He for m\e[26;4H\e(B\e[m\e[32m\e]8;;\e\\a4ef\e[26;9Hd28\e[26;16H\e(B\e[m\e]8;;\e\\Implement user blocking \e[27;4H\e(B\e[m\e[32m\e]8;;\e\\6c2cac4\e[27;16H\e(B\e[m\e]8;;\e\\Refactor user\e[27;30Hnotifica\e[27;39Hi\e[28;4H\e(B\e[m\e[32m\e]8;;\e\\0d155\e[28;10H1f\e[28;16H\e(B\e[m\e]8;;\e\\Upgrade R\e[28;26Hils version\e[29;4H\e(B\e[m\e[32m\e]8;;\e\\4a2a1937\e[29;16H\e(B\e[m\e]8;;\e\\Move gl\e[29;24Hbal variables \e[29;39Ho\e[30;4H\e(B\e[m\e[32m\e]8;;\e\\984e5618\e[30;16H\e(B\e[m\e]8;;\e\\Fix typos in documentati\e[31;38H\e(B\e[m\e[30m\e]8;;\e\\1\e[?25l\e[?25l\e[?25l" + - delay: 10 + content: "\e[?25l\e[2;51H\e(B\e[m\e[33m\e]8;;\e\\ffe5f9e\e[2;68H\e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/mobile-responsive\e(B\e[m\e[33m\e]8;;\e\\)\e(B\e[m\e]8;;\e\\ \e[3;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Author: CI \e[4;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Date: 5 seconds ago\e[5;44H \e[5;52H \e[5;55H \e[6;44H Make mobile resp\e[6;65Hnse\e[8;42H*\e[8;44H\e(B\e[m\e[33m\e]8;;\e\\commit 04be8ec (\e(B\e[m\e[32m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/user-authentication\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/search- \e[9;42Hfunctionality\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/payment-processing\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\bugfix/fix-login-issue\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\bugfix/fix-\e[10;42Hcrash-bug\e(B\e[m\e[33m\e]8;;\e\\)\e(B\e[m\e]8;;\e\\ \e[12;52H5\e[14;51Hrov\e[14;55H Dock\e[14;62Hfi\e[14;65He for more efficient\e[14;86Hbuilds\e[16;51H\e(B\e[m\e[33m\e]8;;\e\\a4ef\e[16;56Hd2\e[20;48H\e(B\e[m\e]8;;\e\\Implement user blocking functionality\e[22;51H\e(B\e[m\e[33m\e]8;;\e\\6c2cac4\e[26;48H\e(B\e[m\e]8;;\e\\Refactor user\e[26;62Hnotifica\e[26;71Hions system\e[28;51H\e(B\e[m\e[33m\e]8;;\e\\0d155\e[28;57H1\e[?25l\e[?25l\e[?25l" + - delay: 36 + content: "\e[?25l\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;25H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;8H \e[35;10H \e[35;17H \e[?25l" + - delay: 2001 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/custom_patch-compressed.gif b/demo/custom_patch-compressed.gif new file mode 100644 index 00000000000..bfe7212647d Binary files /dev/null and b/demo/custom_patch-compressed.gif differ diff --git a/demo/custom_patch.gif b/demo/custom_patch.gif new file mode 100644 index 00000000000..0d4206580a2 Binary files /dev/null and b/demo/custom_patch.gif differ diff --git a/demo/custom_patch.yml b/demo/custom_patch.yml new file mode 100644 index 00000000000..bb5f72a743a --- /dev/null +++ b/demo/custom_patch.yml @@ -0,0 +1,197 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/custom_patch.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + fontFamily: "DejaVuSansM Nerd Font Mono" + + # 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" + +# Records, feel free to edit them +records: + - delay: 4284 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 72 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Diff─────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/user-authentication\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\No changed files \e(B\e[m\e[30m\e]8;;\e\\│\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────────────┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/user-authentication\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────────────────1 of 2─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\5ec45615\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Enhance user authenticat\e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\b0000454\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Stop using shims \e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\0cddf5e2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Fix local session storag\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\8b541ef2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Add user authentication \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ddf14a16\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Improve Dockerfile for m\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\281fde17\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Implement user blocking \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d5c6c303\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor user notificati\e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H└──────────────────────────────1 of 34─┘└──────────────────────────────────────────────────────────────────────────────┘\e[32;1H┌─Stash────────────────────────────────┐┌─Command log──────────────────────────────────────────────────────────────────┐\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[13;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\0 of 0\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Remove\e[35;8Ha\e[35;10Hline\e[35;15Hfrom\e[35;20Han\e[35;23Hold\e[35;27Hcommit\e[?25l" + - delay: 1003 + content: "\e[?25l\e[35;34H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing 4 \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Patch\e[4;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e[5;40H│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────0 of 0─┘\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\5ec45615\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Enhance user authenticat\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 34─┘\e[?25l" + - delay: 10 + content: "\e[?25l\e[2;42H\e(B\e[m\e[33m\e]8;;\e\\commit 5ec45615392849d9a9522e50d707e9a38d338895 (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/user- \e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\authentication\e(B\e[m\e[33m\e]8;;\e\\)\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e]8;;\e\\Author:\e[4;50HCI\e[4;53H\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e]8;;\e\\Date:\e[5;50HMon\e[5;54HAug\e[5;58H7\e[5;60H16:25:53\e[5;69H2023\e[5;74H+1000\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;120H█\e[7;46H\e(B\e[m\e]8;;\e\\Enhance\e[7;54Huser\e[7;59Hauthentication\e[7;74Hfeature\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;42H\e(B\e[m\e]8;;\e\\---\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;43H\e(B\e[m\e]8;;\e\\src/authentication.go\e[9;65H|\e[9;67H1\e[9;69H\e(B\e[m\e[32m\e]8;;\e\\+\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;43H\e(B\e[m\e]8;;\e\\src/session.go\e[10;65H|\e[10;67H1\e[10;69H\e(B\e[m\e[32m\e]8;;\e\\+\e[10;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[11;43H\e(B\e[m\e]8;;\e\\2\e[11;45Hfiles\e[11;51Hchanged,\e[11;60H2\e[11;62Hinsertions(+)\e[11;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[12;120H█\e[13;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/src/authentication.go b/src/authentication.go\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..fef5a5e\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;42H\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[17;42H+++ b/src/authentication.go\e[18;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[19;42H\e(B\e[m\e[32m\e]8;;\e\\+package authentication\e[20;42H\e(B\e[m\e]8;;\e\\\\\e[20;44HNo\e[20;47Hnewline\e[20;55Hat\e[20;58Hend\e[20;62Hof\e[20;65Hfile\e[21;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/src/session.go b/src/session.go\e[22;42Hnew file mode 100644\e[23;42Hindex 0000000..e71c79f\e[24;42H--- /dev/null\e[25;42H+++ b/src/session.go\e[26;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[27;42H\e(B\e[m\e[32m\e]8;;\e\\+package session\e[28;42H\e(B\e[m\e]8;;\e\\\\\e[28;44HNo\e[28;47Hnewline\e[28;55Hat\e[28;58Hend\e[28;62Hof\e[28;65Hfile\e[30;120H\e(B\e[m\e[30m\e]8;;\e\\▼\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;34H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l" + - delay: 5 + content: "\e[?25l\e[24;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\5ec45615\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Enhance user authenticat\e[25;2H\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\b0000454\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Stop using shims \e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\b0000454febada\e[2;64Hdd486b1a4aa1a840074eddbd\e[2;89H\e(B\e[m\e]8;;\e\\ \e[3;42HAuthor: CI \e[4;42HDa\e[4;45He: \e[4;50HMon Aug 7 16:25:53 2023\e[4;74H+1000\e[5;42H \e[5;50H \e[5;54H \e[5;58H \e[5;60H \e[5;69H \e[5;74H \e[6;46HStop\e[6;51Husing\e[6;57Hshims\e[7;42H---\e[7;46H \e[7;54H \e[7;59H \e[7;74H \e[8;42H src/users.go\e[8;56H|\e[8;58H7\e[8;60H\e(B\e[m\e[32m\e]8;;\e\\+++++++\e[9;43H\e(B\e[m\e]8;;\e\\1 file changed, 7 insertions(+)\e[10;43H \e[10;65H \e[10;67H \e[10;69H \e[11;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/src/users.go b/src/users.go\e[12;42Hindex 06ab7d0..843aa86 100644\e[13;42H--- a/src/users.go\e(B\e[m\e]8;;\e\\ \e[14;42H\e(B\e[m\e[1m\e]8;;\e\\+++\e[14;46Hb/src/us\e[14;55Hrs.go\e(B\e[m\e]8;;\e\\ \e[15;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -1 +1,8 @@\e(B\e[m\e]8;;\e\\ \e[16;42H package main\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;42H\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;42H\e(B\e[m\e[32m\e]8;;\e\\+import \"fmt\"\e[19;43H\e(B\e[m\e]8;;\e\\ \e[20;42H\e(B\e[m\e[32m\e]8;;\e\\+func main() {\e(B\e[m\e]8;;\e\\ \e[20;58H \e[20;62H \e[20;65H \e[21;42H\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\// TODO: verify that this actuall works\e(B\e[m\e]8;;\e\\ \e[22;42H\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fmt.Println(\"hello world\")\e[23;42H+}\e(B\e[m\e]8;;\e\\ \e[24;42H \e[25;42H \e[26;42H \e[27;42H \e[28;42H \e[28;44H \e[28;47H \e[28;55H \e[28;58H \e[28;62H \e[28;65H \e[?25l" + - delay: 1124 + content: "\e[?25l\e[35;34H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 13 + content: "\e[?25l\e[23;3H\e(B\e[m\e[32m\e[1m\e]8;;\e\\Diff files (b000045 Stop using shims\e[24;2H\e(B\e[m\e[44m\e[1m\e]8;;\e\\▼  src \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;2H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\M\e(B\e[m\e]8;;\e\\  users.go \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;2H\e(B\e[m\e]8;;\e\\ \e[26;4H \e[26;13H \e[26;16H \e[26;20H \e[26;26H \e[26;34H \e[27;2H \e[27;4H \e[27;13H \e[27;16H \e[27;20H \e[27;25H \e[28;2H \e[28;4H \e[28;13H \e[28;16H \e[28;24H \e[28;35H \e[28;39H \e[29;2H \e[29;4H \e[29;13H \e[29;16H \e[29;26H \e[29;31H \e[30;2H \e[30;4H \e[30;13H \e[30;16H \e[30;25H \e[30;30H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;32H─1 of 2\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/src/users.go b/src/users.go\e(B\e[m\e]8;;\e\\ \e[3;42H\e(B\e[m\e[1m\e]8;;\e\\index 06ab7d0..843aa86 100644\e[4;42H--- a/src/users.go\e(B\e[m\e]8;;\e\\ \e[4;69H \e[4;74H \e[5;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/src/users.go\e[6;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -1 +1,8 @@\e(B\e[m\e]8;;\e\\ \e[6;57H \e[7;42H package\e[7;51Hmain\e[8;42H\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e[8;56H \e[8;58H \e[8;60H \e[9;42H\e(B\e[m\e[32m\e]8;;\e\\+import \"fmt\"\e(B\e[m\e]8;;\e\\ \e[9;59H \e[9;61H \e[10;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[11;42H+func main() {\e(B\e[m\e]8;;\e\\ \e[12;42H\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\// TODO: verify that this actuall works\e[13;42H+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fmt.Println(\"hello world\")\e[14;42H+}\e(B\e[m\e]8;;\e\\ \e[15;42H \e[16;43H \e[16;51H \e[17;42H \e[18;42H \e[18;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[19;42H\e(B\e[m\e]8;;\e\\ \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[20;42H\e(B\e[m\e]8;;\e\\ \e[20;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[21;42H\e(B\e[m\e]8;;\e\\ \e[21;46H \e[22;42H \e[22;46H \e[23;42H \e[?25l" + - delay: 603 + content: "\e[?25l\e[35;34H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[24;2H\e(B\e[m\e]8;;\e\\▼  src \e[25;2H\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\M\e(B\e[m\e[44m\e[1m\e]8;;\e\\  users.go \e[31;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 8 + content: "\e[?25l\e[?25l" + - delay: 1124 + content: "\e[?25l\e[35;34H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 21 + content: "\e[?25l\e[1;41H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Patch────────────────────────────────────────────────────────────────────────┐\e[2;41H│\e[2;120H▲\e[3;41H│\e[3;120H█\e[4;41H│\e[4;120H█\e[5;41H│\e[5;120H█\e[6;41H│\e[6;47H\e(B\e[m\e[36m\e]8;;\e\\,1 +1,8 @@\e[6;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[7;41H│\e[7;120H█\e[8;41H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\+\e(B\e[m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[9;41H│\e[9;120H│\e[10;41H│\e[10;120H│\e[11;41H│\e[11;120H│\e[12;41H│\e[12;43H\e(B\e[m\e[32m\e]8;;\e\\ \e[12;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;41H│\e[13;43H\e(B\e[m\e[32m\e]8;;\e\\ \e[13;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;41H│\e[14;120H│\e[15;41H│\e[15;120H▼\e[16;41H└──────────────────────────────────────────────────────────────────────────────┘\e[17;41H\e(B\e[m\e[30m\e]8;;\e\\┌─Custom patch─────────────────────────────────────────────────────────────────┐\e[18;120H│\e[19;120H│\e[20;120H│\e[23;1H┌─Diff files (b000045 Stop using shims─┐\e[24;1H│\e[24;40H│\e[25;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\M\e(B\e[m\e]8;;\e\\  users.go \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H│\e[30;120H│\e[31;1H└───────────────────────────────2 of 2─┘\e[35;98H\e(B\e[m\e[33m\e[1m\e]8;;\e\\Building patch \e(B\e[m\e[33m\e[1m\e[4m\e]8;;\e\\(Reset) \e[?25l" + - delay: 602 + content: "\e[?25l\e[35;34H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[8;42H\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e[9;42H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\+import \"fmt\"\e(B\e[m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l" + - delay: 124 + content: "\e[?25l\e[?25l\e[?25l\e[9;42H\e(B\e[m\e[32m\e]8;;\e\\+import \"fmt\"\e(B\e[m\e]8;;\e\\ \e[10;42H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\+\e(B\e[m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l" + - delay: 122 + content: "\e[?25l\e[?25l\e[?25l\e[10;42H\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e[11;42H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\+func main() {\e(B\e[m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l\e[?25l\e[11;42H\e(B\e[m\e[32m\e]8;;\e\\+func main() {\e(B\e[m\e]8;;\e\\ \e[12;42H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\+ // TODO: verify that this actuall works\e(B\e[m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l" + - delay: 624 + content: "\e[?25l\e[35;34H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 15 + content: "\e[?25l\e[12;42H\e(B\e[m\e[92;42m\e[1m\e]8;;\e\\+\e[18;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/src/users.go b/src/users.go\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[19;42H\e(B\e[m\e[1m\e]8;;\e\\index 06ab7d0..843aa86 100644\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[20;42H\e(B\e[m\e[1m\e]8;;\e\\--- a/src/users.go\e[20;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[21;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/src/users.go\e[21;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[22;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -1,1 +1,2 @@\e[22;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[23;43H\e(B\e[m\e]8;;\e\\package\e[23;51Hmain\e[23;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[24;4H\e(B\e[m\e[33m\e]8;;\e\\ src\e[24;42H\e(B\e[m\e[32m\e]8;;\e\\+ // TODO: verify that this actuall works\e[24;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[25;6H\e(B\e[m\e[33m\e]8;;\e\\ users.go \e[25;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[30;120H▼\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;44H\e(B\e[m\e[36m\e[1m\e]8;;\e\\esc> \e[?25l\e[?25l\e[1;41H\e(B\e[m\e[30m\e]8;;\e\\┌─Patch────────────────────────────────────────────────────────────────────────┐\e[2;41H│\e[2;120H▲\e[3;41H│\e[3;120H█\e[4;41H│\e[4;120H█\e[5;41H│\e[5;120H█\e[6;41H│\e[6;120H█\e[7;41H│\e[7;120H█\e[8;41H│\e[8;120H█\e[9;41H│\e[9;120H│\e[10;41H│\e[10;120H│\e[11;41H│\e[11;120H│\e[12;41H│\e(B\e[m\e[32;42m\e]8;;\e\\+\e(B\e[m\e[32;44m\e]8;;\e\\ // TODO: verify that this actuall works\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;41H│\e[13;120H│\e[14;41H│\e[14;120H│\e[15;41H│\e[15;120H▼\e[16;41H└──────────────────────────────────────────────────────────────────────────────┘\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Diff files (b000045 Stop using shims─┐\e[24;1H│\e[24;40H│\e[25;1H│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\M\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\ users.go \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H│\e[31;1H└───────────────────────────────2 of 2─┘\e[?25l" + - delay: 8 + content: "\e[?25l\e[6;47H\e(B\e[m\e[36m\e]8;;\e\\ +1,8 @@\e(B\e[m\e]8;;\e\\ \e[12;42H\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\// TODO: verify that this actuall works\e[13;43H\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 602 + content: "\e[?25l\e[35;44H\e(B\e[m\e[36m\e[1m\e]8;;\e\\c-p\e[?25l\e[?25l\e[11;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Patch options─────────────────────────────────────────────────────────────────┐\e[12;21H│\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\c\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Reset patch \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;21H│\e(B\e[m\e[36m\e]8;;\e\\a\e(B\e[m\e]8;;\e\\ Apply patch \e[13;46H \e[13;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;21H│\e(B\e[m\e[36m\e]8;;\e\\r\e(B\e[m\e]8;;\e\\ Apply patch in reverse\e[14;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;21H│\e(B\e[m\e[36m\e]8;;\e\\d\e(B\e[m\e]8;;\e\\ Remove patch\e[15;37Hfrom original\e[15;51Hcommit\e[15;58H(b0000454febadaddd486b1a4aa1a840074eddbd5)\e[15;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;21H│\e(B\e[m\e[36m\e]8;;\e\\i\e[16;24H\e(B\e[m\e]8;;\e\\Move\e[16;29Hpatch\e[16;35Hout\e[16;39Hinto index \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;21H│\e(B\e[m\e[36m\e]8;;\e\\n\e[17;24H\e(B\e[m\e]8;;\e\\Move\e[17;29Hpatch\e[17;35Hinto\e[17;40Hnew commit \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H│\e(B\e[m\e[36m\e]8;;\e\\y\e[18;24H\e(B\e[m\e]8;;\e\\Copy\e[18;29Hpatch\e[18;35Hto\e[18;38Hclipboard \e[18;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;21H│\e[19;24H\e(B\e[m\e]8;;\e\\Cancel\e[19;40H \e[19;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;21H└────────────────────────────────────────────────────────────────────────1 of 8─┘\e[23;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Diff files (b000045 Stop using shims─┐\e[24;1H│\e[24;40H│\e[25;1H│\e[25;40H│\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H│\e[31;1H└───────────────────────────────2 of 2─┘\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;34H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[12;22H\e(B\e[m\e[36m\e]8;;\e\\c\e(B\e[m\e]8;;\e\\ Reset patch \e[13;22H\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Apply patch \e[20;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l\e[?25l\e[13;22H\e(B\e[m\e[36m\e]8;;\e\\a\e(B\e[m\e]8;;\e\\ Apply patch \e[14;22H\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\r\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Apply patch in reverse \e[20;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l\e[?25l\e[14;22H\e(B\e[m\e[36m\e]8;;\e\\r\e(B\e[m\e]8;;\e\\ Apply patch in reverse \e[15;22H\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove patch from original commit (b0000454febadaddd486b1a4aa1a840074eddbd5) \e[20;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[?25l" + - delay: 625 + content: "\e[?25l\e[35;34H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 5 + content: "\e[?25l\e[11;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+func main() {\e(B\e[m\e]8;;\e\\ \e[12;21H \e[12;24H \e[12;30H \e[12;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+\e[12;46H// TODO: verify that this actuall works\e[12;101H\e(B\e[m\e]8;;\e\\ \e[13;21H\e(B\e[m\e[30m\e]8;;\e\\────────────0 of 0─┘│\e(B\e[m\e[32m\e]8;;\e\\+\e[13;46Hfmt.Println(\"hello world\")\e[13;101H\e(B\e[m\e]8;;\e\\ \e[14;21H\e(B\e[m\e[30m\e]8;;\e\\emotes - Tags──────┐│\e(B\e[m\e[32m\e]8;;\e\\+}\e(B\e[m\e]8;;\e\\ \e[14;101H \e[15;21H\e(B\e[m\e[32m\e]8;;\e\\authentication\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[16;21H \e[16;24H \e[16;29H \e[16;35H \e[16;39H \e(B\e[m\e[30m\e]8;;\e\\│└────────────────────────────────────────────────────────────\e[17;21H\e(B\e[m\e]8;;\e\\ \e[17;24H \e[17;29H \e[17;35H \e[17;40H\e(B\e[m\e[30m\e]8;;\e\\│┌─Custom patch───────────────────────────────────────────────\e[18;21H\e(B\e[m\e]8;;\e\\ \e[18;24H \e[18;29H \e[18;35H \e[18;38H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\diff --git a/src/users.go b/src/users.go\e[18;101H\e(B\e[m\e]8;;\e\\ \e[19;21H \e[19;24H \e[19;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\index 06ab7d0..843aa86 100644\e[19;101H\e(B\e[m\e]8;;\e\\ \e[20;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\--- a/src/users.go\e(B\e[m\e]8;;\e\\ \e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Diff files (b000045 Stop using shims─┐\e[24;1H│\e[24;40H│\e[25;1H│\e[25;40H│\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H│\e[31;1H└───────────────────────────────2 of 2─┘\e[33;42H\e(B\e[m\e[33m\e]8;;\e\\Remove patch from commit \e[?25l" + - delay: 8 + content: "\e[?25l\e[33;42H\e(B\e[m\e]8;;\e\\merges 0cddf5e2b93a22b5283445d35f694f073828872e \e[?25l" + - delay: 40 + content: "\e[?25l\e[?25l" + - delay: 51 + content: "\e[?25l\e[33;42H\e(B\e[m\e]8;;\e\\ git rebase --continue \e[?25l" + - delay: 37 + content: "\e[?25l\e[35;98H\e(B\e[m\e[36m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l" + - delay: 7 + content: "\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[16;3H\e(B\e[m\e[36m\e]8;;\e\\0s\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[18;42H\e(B\e[m\e]8;;\e\\ \e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;42H\e(B\e[m\e]8;;\e\\ \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;42H\e(B\e[m\e]8;;\e\\ \e[20;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;42H\e(B\e[m\e]8;;\e\\ \e[21;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[22;42H\e(B\e[m\e]8;;\e\\ \e[22;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[23;43H\e(B\e[m\e]8;;\e\\ \e[23;51H \e[23;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[24;4H\e(B\e[m\e]8;;\e\\ src\e[24;42H \e[24;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[25;6H\e(B\e[m\e[44m\e[1m\e]8;;\e\\ users.go \e[25;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[30;120H│\e[?25l" + - delay: 6 + content: "\e[?25l\e[3;57H\e(B\e[m\e[1m\e]8;;\e\\c048119\e[6;51H\e(B\e[m\e[36m\e]8;;\e\\7\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;120H█\e[11;120H█\e[12;46H\e(B\e[m\e[32m\e]8;;\e\\fmt.Println(\"hello world\")\e(B\e[m\e]8;;\e\\ \e[12;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[13;43H\e(B\e[m\e[32m\e]8;;\e\\}\e[13;46H\e(B\e[m\e]8;;\e\\ \e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;42H\e(B\e[m\e]8;;\e\\ \e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;120H█\e[16;41H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[17;41H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[18;120H█\e[19;120H█\e[20;120H█\e[21;120H█\e[30;120H▼\e[?25l" + - delay: 28 + content: "\e[?25l\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;45H\e(B\e[m\e[36m\e[1m\e]8;;\e\\sc> \e[?25l\e[?25l\e[23;3H\e(B\e[m\e[32m\e[1m\e]8;;\e\\Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\────────────────────\e[24;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[24;4H2372cf3c\e[24;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[24;16H\e(B\e[m\e]8;;\e\\Enhance\e[24;24Huser\e[24;29Hauthenticat\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[25;2H\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e[25;4He853aa9f\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Stop\e[25;21Husing\e[25;27Hshims\e[25;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[26;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[26;4H0cddf5e2\e[26;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[26;16H\e(B\e[m\e]8;;\e\\Fix\e[26;20Hlocal\e[26;26Hsession\e[26;34Hstorag\e[27;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[27;4H8b541ef2\e[27;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[27;16H\e(B\e[m\e]8;;\e\\Add\e[27;20Huser\e[27;25Hauthentication\e[28;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[28;4Hddf14a16\e[28;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[28;16H\e(B\e[m\e]8;;\e\\Improve\e[28;24HDockerfile\e[28;35Hfor\e[28;39Hm\e[29;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[29;4H281fde17\e[29;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[29;16H\e(B\e[m\e]8;;\e\\Implement\e[29;26Huser\e[29;31Hblocking\e[30;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[30;4Hd5c6c303\e[30;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[30;16H\e(B\e[m\e]8;;\e\\Refactor\e[30;25Huser\e[30;30Hnotificati\e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e[31;32H2 of 34\e[?25l" + - delay: 13 + content: "\e[?25l\e[2;42H\e(B\e[m\e[33m\e]8;;\e\\commit e853aa9ffbf841498ebf20fdc867ade0af735cb5\e[3;42H\e(B\e[m\e]8;;\e\\Author: CI \e[4;42HDate: Mon Aug 7 16:25:53\e[4;69H2023\e[4;74H+1000\e[5;42H \e[6;42H Stop using\e[6;57Hshims\e[7;42H--- \e[7;51H \e[8;42H src/users.go\e[8;56H|\e[8;58H6\e[8;60H\e(B\e[m\e[32m\e]8;;\e\\++++++\e[9;42H\e(B\e[m\e]8;;\e\\ 1 file changed,\e[9;59H6\e[9;61Hinsertions(+)\e[10;42H \e[11;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/src/users.go b/src/users.go\e[12;42Hindex 06ab7d0..c048119 100644\e(B\e[m\e]8;;\e\\ \e[13;42H\e(B\e[m\e[1m\e]8;;\e\\--- a/src/users.go\e[14;42H+++ b/src/users.go\e[15;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -1 +1,7 @@\e[16;43H\e(B\e[m\e]8;;\e\\package\e[16;51Hmain\e[17;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[18;42H+import \"fmt\"\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;42H\e(B\e[m\e[32m\e]8;;\e\\+func main() {\e[20;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[21;46Hfmt.Println(\"hello world\")\e[21;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[22;42H\e(B\e[m\e[32m\e]8;;\e\\+}\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;34H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;8H \e[35;10H \e[35;15H \e[35;20H \e[35;23H \e[35;27H \e[?25l" + - delay: 2000 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/diff_commits-compressed.gif b/demo/diff_commits-compressed.gif new file mode 100644 index 00000000000..0ae19353cf0 Binary files /dev/null and b/demo/diff_commits-compressed.gif differ diff --git a/demo/diff_commits.gif b/demo/diff_commits.gif new file mode 100644 index 00000000000..eed7329e8f6 Binary files /dev/null and b/demo/diff_commits.gif differ diff --git a/demo/diff_commits.yml b/demo/diff_commits.yml new file mode 100644 index 00000000000..dbfaa3dc71d --- /dev/null +++ b/demo/diff_commits.yml @@ -0,0 +1,192 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/diff_commits.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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" + +# Records, feel free to edit them +records: + - delay: 3692 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 85 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Diff─────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e]8;;\e\\ repo → master \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\No changed files \e(B\e[m\e[30m\e]8;;\e\\│\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────────────┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────────────────1 of 1─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\c07b975a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Move constants to a sepa\e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5e33f1ab\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Enhance user search with\e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b68775b2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor session managem\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\6c8a8673\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Ensure atomicity of tran\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\bbff0066\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Handle database connecti\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\03cac2ca\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Update styles according \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\6d7425d2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Replace deprecated lifec\e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H└──────────────────────────────1 of 50─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[32;1H┌─Stash────────────────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[13;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\0 of 0\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Compare\e[35;9Htwo\e[35;13Hcommits\e[?25l" + - delay: 1002 + content: "\e[?25l\e[35;21H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing 4 \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Patch\e[4;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e[5;40H│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────0 of 0─┘\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\c07b975a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Move constants to a sepa\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 50─┘\e[?25l" + - delay: 10 + content: "\e[?25l\e[2;42H\e(B\e[m\e[33m\e]8;;\e\\commit c07b975a65a8d00b4a7a6d0d80e265be68c1e8db (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\)\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e]8;;\e\\Author:\e[3;50HCI\e[3;53H\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e]8;;\e\\Date:\e[4;50HSat\e[4;54HAug\e[4;58H12\e[4;61H17:17:44\e[4;70H2023\e[4;75H+1000\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;120H█\e[6;46H\e(B\e[m\e]8;;\e\\Move\e[6;51Hconstants\e[6;61Hto\e[6;64Ha\e[6;66Hseparate\e[6;75Hconfig\e[6;82Hfile\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;42H\e(B\e[m\e]8;;\e\\---\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;43H\e(B\e[m\e]8;;\e\\config/constants.go\e[8;63H|\e[8;65H1\e[8;67H\e(B\e[m\e[32m\e]8;;\e\\+\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;43H\e(B\e[m\e]8;;\e\\1\e[9;45Hfile\e[9;50Hchanged,\e[9;59H1\e[9;61Hinsertion(+)\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;120H█\e[11;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/config/constants.go b/config/constants.go\e[11;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[12;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e[12;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[13;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..63acfdd\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;42H\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/config/constants.go\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;42H\e(B\e[m\e[32m\e]8;;\e\\+package config\e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;42H\e(B\e[m\e]8;;\e\\\\\e[18;44HNo\e[18;47Hnewline\e[18;55Hat\e[18;58Hend\e[18;62Hof\e[18;65Hfile\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[19;120H█\e[20;120H█\e[21;120H█\e[33;120H▼\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;21H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[24;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\c07b975a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Move constants to a sepa\e[25;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\5e33f1ab\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Enhance user search with\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 10 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\5e33f1abdc1c7fb98f2eaf5eb\e[2;75H74bd97c\e[2;83H98aebc\e(B\e[m\e]8;;\e\\ \e[6;46HEnhan\e[6;52He user \e[6;60Hearch\e[6;66Hwith fuzzy matching \e[8;43Hsearch\e[8;50Hfuzzy_matching.go |\e[8;70H1\e[8;72H\e(B\e[m\e[32m\e]8;;\e\\+\e[11;55H\e(B\e[m\e[1m\e]8;;\e\\search\e[11;62Hfuzzy_matching.g\e[11;79H b/search/fuzzy_matching.go\e[13;57H9bf\e[13;61Hc54\e[15;48Hsearch\e[15;55Hfuzzy_matching.go\e[17;51H\e(B\e[m\e[32m\e]8;;\e\\search\e[?25l" + - delay: 121 + content: "\e[?25l\e[?25l\e[?25l\e[25;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5e33f1ab\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Enhance user search with\e[26;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\b68775b2\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Refactor session managem\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[?25l" + - delay: 9 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\b68775b289\e[2;60H78232dc8861adf\e[2;76Ha\e[2;78H0348f261540\e[6;46H\e(B\e[m\e]8;;\e\\Ref\e[6;50Hctor \e[6;57Hssion management using JWT \e[8;45Hssion/jwt_management.go | 1 \e(B\e[m\e[32m\e]8;;\e\\+\e[11;57H\e(B\e[m\e[1m\e]8;;\e\\ssion/jwt_management.go b/session/jwt\e[11;97Hnagement.go\e[13;57He71\e[13;61H79f\e[15;50Hssion/jwt_management.go\e[17;53H\e(B\e[m\e[32m\e]8;;\e\\ssion\e[?25l" + - delay: 121 + content: "\e[?25l\e[?25l\e[?25l\e[26;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b68775b2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor session managem\e[27;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\6c8a8673\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of tran\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\6c\e[2;52Ha8673a0933ebb6eabd34e248483e735734d1d\e[6;46H\e(B\e[m\e]8;;\e\\Ensure atomic\e[6;60Hty\e[6;63Hof transactions in payment\e[6;90Hsystem\e[8;43Hpayment\e[8;51Htrans\e[8;57Hction_a\e[8;65Homicity.go\e[8;76H|\e[8;78H1\e[8;80H\e(B\e[m\e[32m\e]8;;\e\\+\e[11;55H\e(B\e[m\e[1m\e]8;;\e\\payment\e[11;63Htrans\e[11;69Hction_a\e[11;77Homicity.go \e[12;42Hb/payment/transaction_atomicity.go\e[13;42Hnew file mode 100644\e(B\e[m\e]8;;\e\\ \e[14;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..6c1763b\e[15;42H---\e[15;46H/dev/null\e(B\e[m\e]8;;\e\\ \e[16;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/payment/transaction_atomicity.go\e[17;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[18;42H\e(B\e[m\e[32m\e]8;;\e\\+package payment\e(B\e[m\e]8;;\e\\ \e[18;62H \e[18;65H \e[19;42H\\\e[19;44HNo\e[19;47Hnewline\e[19;55Hat\e[19;58Hend\e[19;62Hof\e[19;65Hfile\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l\e[?25l\e[27;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\6c8a8673\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Ensure atomicity of tran\e[28;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\bbff0066\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Handle database connecti\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\5\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\bbff0066761c811678\e[2;68H8a9ca\e[2;74H27cd88660ca0f57\e[6;46H\e(B\e[m\e]8;;\e\\Handl\e[6;53Hdatabase c\e[6;64Hnnection failures\e[6;82Hgrac\e[6;87Hfull\e[6;92H \e[8;43Hdb/co\e[8;49Hnection_fa\e[8;60Hlure.go | 1 \e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e[8;76H \e[8;78H \e[8;80H \e[11;55H\e(B\e[m\e[1m\e]8;;\e\\db/co\e[11;61Hnection_fa\e[11;72Hlure.go b/db/c\e[11;87Hnnection_failure.go\e(B\e[m\e]8;;\e\\ \e[12;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e(B\e[m\e]8;;\e\\ \e[13;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..1\e[13;59H060e9\e[14;42H--- /dev/null\e(B\e[m\e]8;;\e\\ \e[15;42H\e(B\e[m\e[1m\e]8;;\e\\+++\e[15;46Hb/db\e[15;51Hconnection_failure.go\e[16;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[17;42H\e(B\e[m\e[32m\e]8;;\e\\+package db\e(B\e[m\e]8;;\e\\ \e[18;42H\\ No newline at end\e[18;62Hof\e[18;65Hfile\e[19;42H \e[19;44H \e[19;47H \e[19;55H \e[19;58H \e[19;62H \e[19;65H \e[?25l" + - delay: 124 + content: "\e[?25l\e[?25l\e[?25l\e[28;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\bbff0066\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Handle database connecti\e[29;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\03cac2ca\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Update styles according \e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\6\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\03cac2ca97\e[2;60H1\e[2;63H0a\e[2;66Hf9ee4780ea0611145d547cf\e[6;46H\e(B\e[m\e]8;;\e\\Updat\e[6;53Hstyles ac\e[6;64Hrding to\e[6;73Hnew design guide\e[6;90Hines\e[8;43Hstyles/new_guidelines\e[8;65Hcss | 1 \e(B\e[m\e[32m\e]8;;\e\\+\e[11;55H\e(B\e[m\e[1m\e]8;;\e\\styles/new_guidelines\e[11;77Hcss b/styles/new_guide\e[11;100Hin\e[11;103Hs.css\e[13;57H9\e[13;59H99\e[13;62Ha2\e[15;48Hstyles/new_guidelines\e[15;70Hcss\e[17;43H\e(B\e[m\e[32m\e]8;;\e\\.class {}\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 122 + content: "\e[?25l\e[?25l\e[?25l\e[29;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\03cac2ca\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Update styles according \e[30;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\6d7425d2\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Replace deprecated lifec\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\7\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\6d7425d2ebb3499e922b\e[2;70H25c144\e[2;77H9860e74\e[2;85H650\e[6;46H\e(B\e[m\e]8;;\e\\Replace deprec\e[6;61Hted l\e[6;67Hfecycle methods in React components\e[8;43Hcompone\e[8;51Hts/depr\e[8;59Hcat\e[8;63Hd_methods.jsx\e[8;77H|\e[8;79H1\e[8;81H\e(B\e[m\e[32m\e]8;;\e\\+\e[11;55H\e(B\e[m\e[1m\e]8;;\e\\compone\e[11;63Hts/depr\e[11;71Hcat\e[11;75Hd_method\e[11;84H.jsx \e[12;42Hb/components/deprecated_methods.jsx\e[13;42Hnew file mode 10\e[13;59H644\e(B\e[m\e]8;;\e\\ \e[14;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..278a3f1\e[15;42H---\e[15;46H/dev/null\e(B\e[m\e]8;;\e\\ \e[16;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/components/deprecated_methods.jsx\e[17;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[18;42H\e(B\e[m\e[32m\e]8;;\e\\+import React\e(B\e[m\e]8;;\e\\ \e[18;58H \e[18;62H \e[18;65H \e[19;42H\\\e[19;44HNo\e[19;47Hnewline\e[19;55Hat\e[19;58Hend\e[19;62Hof\e[19;65Hfile\e[?25l" + - delay: 1124 + content: "\e[?25l\e[35;21H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing W \e[?25l\e[?25l\e[14;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Diffing───────────────────────────────────────────────────────────────────────┐\e[15;21H│\e(B\e[m\e[44m\e[1m\e]8;;\e\\Diff 6d7425d2ebb3499e922be25c14409860e745650f \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;21H│\e(B\e[m\e]8;;\e\\Enter\e[16;28Href\e[16;32Hto\e[16;35Hdiff\e[16;40H \e[16;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;21H│\e(B\e[m\e]8;;\e\\Cancel\e[17;40H \e[17;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H└────────────────────────────────────────────────────────────────────────1 of 3─┘\e[23;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────7 of 50─┘\e[?25l" + - delay: 1103 + content: "\e[?25l\e[35;30H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 42 + content: "\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Diff─\e[14;21Hemotes - Tags──────┐│\e(B\e[m\e[1m\e]8;;\e\\index 0000000..278a3f1\e(B\e[m\e]8;;\e\\ \e[15;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e(B\e[m\e]8;;\e\\ \e[16;21H \e[16;28H \e[16;32H \e[16;35H \e[16;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\+++ b/components/deprecated_methods.jsx\e[16;101H\e(B\e[m\e]8;;\e\\ \e[17;21H \e[17;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[17;101H\e(B\e[m\e]8;;\e\\ \e[18;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+import React\e(B\e[m\e]8;;\e\\ \e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e(B\e[m\e[95;44m\e[1m\e]8;;\e\\\U000F0718\e[30;4H6d7425d2\e[30;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e[31;1H└──────────────────────────────7 of 50─┘\e[35;1H\e(B\e[m\e[35m\e]8;;\e\\Showing output for: git diff 6d7425d2ebb3499e922be25c14409860e745650f 6d7425d2ebb3499e922be25c14409860e745650f -- \e(B\e[m\e[35m\e[4m\e]8;;\e\\(Reset\e[?25l" + - delay: 7 + content: "\e[?25l\e[2;42H\e(B\e[m\e]8;;\e\\ \e[2;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[3;42H\e(B\e[m\e]8;;\e\\ \e[3;50H \e[3;53H \e[3;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[4;42H\e(B\e[m\e]8;;\e\\ \e[4;50H \e[4;54H \e[4;58H \e[4;61H \e[4;70H \e[4;75H \e[4;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[5;120H│\e[6;46H\e(B\e[m\e]8;;\e\\ \e[6;54H \e[6;65H \e[6;75H \e[6;83H \e[6;86H \e[6;92H \e[6;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[7;42H\e(B\e[m\e]8;;\e\\ \e[7;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[8;43H\e(B\e[m\e]8;;\e\\ \e[8;77H \e[8;79H \e[8;81H \e[8;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[9;43H\e(B\e[m\e]8;;\e\\ \e[9;45H \e[9;50H \e[9;59H \e[9;61H \e[9;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[10;120H│\e[11;42H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;42H\e(B\e[m\e]8;;\e\\ \e[12;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[13;42H\e(B\e[m\e]8;;\e\\ \e[13;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[14;42H\e(B\e[m\e]8;;\e\\ \e[14;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[15;42H\e(B\e[m\e]8;;\e\\ \e[15;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[16;42H\e(B\e[m\e]8;;\e\\ \e[16;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[17;42H\e(B\e[m\e]8;;\e\\ \e[17;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[18;42H\e(B\e[m\e]8;;\e\\ \e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;42H\e(B\e[m\e]8;;\e\\ \e[19;44H \e[19;47H \e[19;55H \e[19;58H \e[19;62H \e[19;65H \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;120H│\e[21;120H│\e[33;120H│\e[?25l" + - delay: 603 + content: "\e[?25l\e[?25l\e[?25l\e[29;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\03cac2ca\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Update styles according \e[30;2H\e(B\e[m\e[35m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\6d7425d2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Replace deprecated lifec\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\6\e[35;71H\e(B\e[m\e[35m\e]8;;\e\\03cac2ca9711810a7f9e\e[35;92H4780ea\e[35;99H611145d\e[35;107H47c\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/styles/new_guidelines.css b/styles/new_guidelines.css\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..90990a2\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/styles/new_guidelines.css\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;42H\e(B\e[m\e[32m\e]8;;\e\\+.class {}\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;42H\e(B\e[m\e]8;;\e\\\\\e[9;44HNo\e[9;47Hnewline\e[9;55Hat\e[9;58Hend\e[9;62Hof\e[9;65Hfile\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;120H█\e[11;120H█\e[12;120H█\e[13;120H█\e[14;120H█\e[15;120H█\e[16;120H█\e[17;120H█\e[18;120H█\e[19;120H█\e[20;120H█\e[21;120H█\e[22;120H█\e[23;120H█\e[24;120H█\e[25;120H█\e[26;120H█\e[33;120H▼\e[?25l" + - delay: 122 + content: "\e[?25l\e[?25l\e[?25l\e[28;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\bbff0066\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Handle database connecti\e[29;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\03cac2ca\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Update styles according \e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\5\e[35;71H\e(B\e[m\e[35m\e]8;;\e\\bbff006676\e[35;82Hc\e[35;85H16\e[35;88H8a8a9ca227cd88660ca0f57\e[?25l" + - delay: 10 + content: "\e[?25l\e[2;55H\e(B\e[m\e[1m\e]8;;\e\\db/connection_failure\e[2;77Hgo b/db/connection_fai\e[2;100Hur\e[2;103H.go\e(B\e[m\e]8;;\e\\ \e[4;57H\e(B\e[m\e[1m\e]8;;\e\\1\e[4;59H06\e[4;62He9\e[6;48Hdb/connection_failure\e[6;70Hgo\e(B\e[m\e]8;;\e\\ \e[8;43H\e(B\e[m\e[32m\e]8;;\e\\package db\e[10;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/styles/new_guidelines.css b/styles/new_guidelines.css\e[11;42Hnew file mode 100644\e[12;42Hindex 0000000..90990a2\e[13;42H--- /dev/null\e[14;42H+++ b/styles/new_guidelines.css\e[15;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[16;42H\e(B\e[m\e[32m\e]8;;\e\\+.class {}\e[17;42H\e(B\e[m\e]8;;\e\\\\\e[17;44HNo\e[17;47Hnewline\e[17;55Hat\e[17;58Hend\e[17;62Hof\e[17;65Hfile\e[23;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[24;120H│\e[25;120H│\e[26;120H│\e[?25l" + - delay: 122 + content: "\e[?25l\e[?25l\e[?25l\e[27;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\6c8a8673\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of tran\e[28;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\bbff0066\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Handle database connecti\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[35;71H\e(B\e[m\e[35m\e]8;;\e\\6c8a8673a0933ebb6e\e[35;90Hbd34e\e[35;96H48483e735734d1d\e[?25l" + - delay: 9 + content: "\e[?25l\e[10;55H\e(B\e[m\e[1m\e]8;;\e\\pa\e[10;58Hm\e[10;60Hnt/transactio\e[10;74H_atomicity.go \e[11;42Hb/payment/transaction_atomicity.go\e[12;42Hnew file mode 10\e[12;59H644\e(B\e[m\e]8;;\e\\ \e[13;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..6c1763b\e[14;42H---\e[14;46H/dev/null\e(B\e[m\e]8;;\e\\ \e[15;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/payment/transaction_atomicity.go\e[16;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[17;42H\e(B\e[m\e[32m\e]8;;\e\\+package payment\e(B\e[m\e]8;;\e\\ \e[17;62H \e[17;65H \e[18;42H\\\e[18;44HNo\e[18;47Hnewline\e[18;55Hat\e[18;58Hend\e[18;62Hof\e[18;65Hfile\e[19;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/styles/new_guidelines.css b/styles/new_guidelines.css\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e[20;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..90990a2\e[21;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[22;42H\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[22;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[23;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/styles/new_guidelines.css\e[24;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[25;42H\e(B\e[m\e[32m\e]8;;\e\\+.class {}\e[26;42H\e(B\e[m\e]8;;\e\\\\\e[26;44HNo\e[26;47Hnewline\e[26;55Hat\e[26;58Hend\e[26;62Hof\e[26;65Hfile\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l\e[?25l\e[26;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\b68775b2\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Refactor session managem\e[27;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\6c8a8673\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Ensure atomicity of tran\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[35;71H\e(B\e[m\e[35m\e]8;;\e\\b6\e[35;74H775b289178232dc8861adf07ab0348f261540\e[?25l" + - delay: 10 + content: "\e[?25l\e[17;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[18;120H│\e[19;56H\e(B\e[m\e[1m\e]8;;\e\\ession/j\e[19;65Ht_managem\e[19;75Hnt.go\e[19;84Hession/j\e[19;93Ht_managem\e[19;103Hnt.go\e[21;57He71c79f\e[23;49Hession/j\e[23;58Ht_managem\e[23;68Hnt.go\e[25;43H\e(B\e[m\e[32m\e]8;;\e\\package session\e[27;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/styles/new_guidelines.css b/styles/new_guidelines.css\e[28;42Hnew file mode 100644\e[29;42Hindex 0000000..90990a2\e[30;42H--- /dev/null\e[31;42H+++ b/styles/new_guidelines.css\e[32;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[33;42H\e(B\e[m\e[32m\e]8;;\e\\+.class {}\e[?25l" + - delay: 122 + content: "\e[?25l\e[?25l\e[?25l\e[25;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\5e33f1ab\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Enhance user search with\e[26;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b68775b2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor session managem\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[35;71H\e(B\e[m\e[35m\e]8;;\e\\5e33f1abdc\e[35;82Hc7fb98f2eaf5eb\e[35;98H4\e[35;100Hd97c898aebc\e[?25l" + - delay: 9 + content: "\e[?25l\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[16;120H│\e[19;57H\e(B\e[m\e[1m\e]8;;\e\\arch/fuzzy_matching.go b/search/fuzzy\e[19;97Htching.go\e(B\e[m\e]8;;\e\\ \e[21;57H\e(B\e[m\e[1m\e]8;;\e\\9bf\e[21;61Hc54\e[23;50Harch/fuzzy_matching.go\e(B\e[m\e]8;;\e\\ \e[25;53H\e(B\e[m\e[32m\e]8;;\e\\arch\e(B\e[m\e]8;;\e\\ \e[27;56H\e(B\e[m\e[1m\e]8;;\e\\ession/j\e[27;65Ht_managem\e[27;75Hnt.go\e[27;84Hession/j\e[27;93Ht_managem\e[27;103Hnt.go\e[29;57He71c79f\e[31;49Hession/j\e[31;58Ht_managem\e[31;68Hnt.go\e[33;43H\e(B\e[m\e[32m\e]8;;\e\\package session\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l\e[?25l\e[24;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\c07b975a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Move constants to a sepa\e[25;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5e33f1ab\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Enhance user search with\e[31;32H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1\e[35;71H\e(B\e[m\e[35m\e]8;;\e\\c07b975a65a8d00b4a7a6d0d8\e[35;97He265be6\e[35;105Hc1e8db\e[?25l" + - delay: 9 + content: "\e[?25l\e[2;55H\e(B\e[m\e[1m\e]8;;\e\\config/constants.go b/c\e[2;79Hnfig/constan\e[2;92Hs.go\e(B\e[m\e]8;;\e\\ \e[4;57H\e(B\e[m\e[1m\e]8;;\e\\63acfdd\e[6;48Hconfig/constants.go\e(B\e[m\e]8;;\e\\ \e[8;51H\e(B\e[m\e[32m\e]8;;\e\\config\e[10;55H\e(B\e[m\e[1m\e]8;;\e\\db/co\e[10;61Hnection_fa\e[10;72Hlure.go b/db/c\e[10;87Hnnection_failure.go\e(B\e[m\e]8;;\e\\ \e[11;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e(B\e[m\e]8;;\e\\ \e[12;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..1\e[12;59H060e9\e[13;42H--- /dev/null\e(B\e[m\e]8;;\e\\ \e[14;42H\e(B\e[m\e[1m\e]8;;\e\\+++\e[14;46Hb/db\e[14;51Hconnection_failure.go\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[15;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[16;42H\e(B\e[m\e[32m\e]8;;\e\\+package db\e(B\e[m\e]8;;\e\\ \e[17;42H\\ No newline at end\e[17;62Hof\e[17;65Hfile\e[18;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/payment/transaction_atomicity.go \e[19;42Hb/payment/trans\e[19;58Hction_atomicity.go\e(B\e[m\e]8;;\e\\ \e[21;57H\e(B\e[m\e[1m\e]8;;\e\\6c1763b\e[23;48Hpayment/transac\e[23;64Hion_atomicity.go\e[25;51H\e(B\e[m\e[32m\e]8;;\e\\payment\e[27;57H\e(B\e[m\e[1m\e]8;;\e\\arch/fuzzy_matching.go b/search/fuzzy\e[27;97Htching.go\e(B\e[m\e]8;;\e\\ \e[29;57H\e(B\e[m\e[1m\e]8;;\e\\9bf\e[29;61Hc54\e[31;50Harch/fuzzy_matching.go\e(B\e[m\e]8;;\e\\ \e[33;53H\e(B\e[m\e[32m\e]8;;\e\\arch\e(B\e[m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l" + - delay: 1124 + content: "\e[?25l\e[?25l" + - delay: 14 + content: "\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Patch\e[23;3H\e(B\e[m\e[32m\e[1m\e]8;;\e\\Diff files (c07b975 Move constants t\e[24;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\A\e[24;4H\e(B\e[m\e[44m\e[1m\e]8;;\e\\ config/constants.go \e[24;31H \e[24;34H \e[24;36H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;2H\e(B\e[m\e[32m\e]8;;\e\\A\e[25;4H\e(B\e[m\e]8;;\e\\ db/connection_fail\e[25;25Hr\e[25;27H.go \e[25;36H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;2H\e(B\e[m\e[32m\e]8;;\e\\A\e[26;4H\e(B\e[m\e]8;;\e\\ payment/trans\e[26;22Hion_atomicity.go \e[27;2H\e(B\e[m\e[32m\e]8;;\e\\A\e[27;4H\e(B\e[m\e]8;;\e\\ search/fuzzy_matching.go \e[27;33H \e[27;36H \e[28;2H\e(B\e[m\e[32m\e]8;;\e\\A\e[28;4H\e(B\e[m\e]8;;\e\\ session/jwt_management.go\e[28;32H \e[29;2H\e(B\e[m\e[32m\e]8;;\e\\A\e[29;4H\e(B\e[m\e]8;;\e\\ styles/new_guid\e[29;22Hlines.css \e[30;2H \e[30;4H \e[30;13H \e[30;16H \e[30;24H \e[30;35H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;32H─1 of 6\e[35;115H\e(B\e[m\e[35m\e]8;;\e\\config\e[?25l" + - delay: 9 + content: "\e[?25l\e[10;42H\e(B\e[m\e]8;;\e\\ \e[11;42H \e[12;42H \e[13;42H \e[14;42H \e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;42H\e(B\e[m\e]8;;\e\\ \e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;42H\e(B\e[m\e]8;;\e\\ \e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;42H\e(B\e[m\e]8;;\e\\ \e[17;44H \e[17;47H \e[17;55H \e[17;58H \e[17;62H \e[17;65H \e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;42H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[19;42H\e(B\e[m\e]8;;\e\\ \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[20;42H\e(B\e[m\e]8;;\e\\ \e[20;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[21;42H\e(B\e[m\e]8;;\e\\ \e[21;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[22;42H\e(B\e[m\e]8;;\e\\ \e[22;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[23;42H\e(B\e[m\e]8;;\e\\ \e[23;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[24;42H\e(B\e[m\e]8;;\e\\ \e[24;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[25;42H\e(B\e[m\e]8;;\e\\ \e[25;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[26;42H\e(B\e[m\e]8;;\e\\ \e[26;44H \e[26;47H \e[26;55H \e[26;58H \e[26;62H \e[26;65H \e[26;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[27;42H\e(B\e[m\e]8;;\e\\ \e[28;42H \e[29;42H \e[30;42H \e[31;42H \e[32;42H \e[33;42H \e[?25l" + - delay: 604 + content: "\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 2002 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/filter-compressed.gif b/demo/filter-compressed.gif new file mode 100644 index 00000000000..e3b7c3287de Binary files /dev/null and b/demo/filter-compressed.gif differ diff --git a/demo/filter.gif b/demo/filter.gif new file mode 100644 index 00000000000..9dba5080ecb Binary files /dev/null and b/demo/filter.gif differ diff --git a/demo/filter.yml b/demo/filter.yml new file mode 100644 index 00000000000..a4b05014d62 --- /dev/null +++ b/demo/filter.yml @@ -0,0 +1,157 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/filter.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + fontFamily: "DejaVuSansMono 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" + +# Records, feel free to edit them +records: + - delay: 6554 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 85 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Diff─────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e]8;;\e\\ repo → docs/add-faq-section \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\No changed files \e(B\e[m\e[30m\e]8;;\e\\│\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────────────┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ שׂ docs/add-faq-section \e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ שׂ chore/update-environment-configu\e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ שׂ chore/clean-up-git-history \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ שׂ experiment/try-different-archite\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ שׂ experiment/implement-new-design \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ שׂ docs/update-api-reference \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ שׂ docs/add-changelog \e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└──────────────────────────────1 of 52─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0a3ce55a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Improve Dockerfile for m\e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5df73fe1\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Implement user blocking \e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3d8cc758\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor user notificati\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\02e1adb0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Upgrade Rails version to\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ebf32b72\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Move global variables to\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\d7edc228\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Fix typos in documentati\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\1b1684d4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Optimize Lazygit startup\e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H└──────────────────────────────1 of 30─┘└──────────────────────────────────────────────────────────────────────────────┘\e[32;1H┌─Stash────────────────────────────────┐┌─Command log──────────────────────────────────────────────────────────────────┐\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[13;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\0 of 0\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Fuzzy\e[35;7Hfilter\e[35;14Hbranches\e[?25l" + - delay: 1003 + content: "\e[?25l\e[35;23H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing 3 \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Log─\e[4;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e[5;40H│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────0 of 0─┘\e[14;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Local branches \e(B\e[m\e]8;;\e\\- Remotes - Tags\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────┐\e[15;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ *\e(B\e[m\e[44m\e[1m\e]8;;\e\\ שׂ docs/add-faq-section \e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[16;1H│\e[16;40H█\e[17;1H│\e[17;40H│\e[18;1H│\e[18;40H│\e[19;1H│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H▼\e[22;1H└──────────────────────────────1 of 52─┘\e[?25l" + - delay: 18 + content: "\e[?25l\e[2;42H\e(B\e[m\e]8;;\e\\* \e(B\e[m\e[33m\e]8;;\e\\commit 0a3ce55 (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/add-faq-section\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\refactor/optimize-database- \e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\queries\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\refactor/improve-performance\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\refactor/improve-logging\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\refactor/improve-error-handling\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\refactor/extract-reusable-component\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\refactor/extract-method\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\refactor/code-cleanup\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\hotfix/security-patch\e(B\e[m\e[33m\e]8;;\e\\,\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\hotfix/fix-production-issue\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\hotfix/critical-security-vulnerability\e(B\e[m\e[33m\e]8;;\e\\, \e[7;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\hotfix/critical-bug\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/user-authentication\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/social-media- \e[8;42Hintegration\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/search-functionality\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/payment-processing\e(B\e[m\e[33m\e]8;;\e\\, \e[9;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/mobile-responsive\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/localization-support\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/integrate- \e[10;42Hthird-party-api\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/image-upload-functionality\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/email- \e[11;42Hnotifications\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/chat-feature\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/analytics-dashboard\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/admi\e[12;42H-panel\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/try-new-library\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/try-different-architecture\e(B\e[m\e[33m\e]8;;\e\\, \e[13;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/try-alternative-algorithm\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/new-feature-idea\e(B\e[m\e[33m\e]8;;\e\\, \e[14;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/implement-new-design\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/implement-design-concept\e(B\e[m\e[33m\e]8;;\e\\, \e[15;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/update-readme\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/update-api-reference\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/improve-tutorials\e(B\e[m\e[33m\e]8;;\e\\, \e[16;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/api-documentation\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/add-user-guide\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/add-changelog\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/update-\e[17;42Hdocumentation\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/update-dependencies\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/migrate-database\e(B\e[m\e[33m\e]8;;\e\\, \e[18;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/improve-test-coverage\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/cleanup-codebase\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/clean-up-git- \e[19;42Hhistory\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/add-test-cases\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\bugfix/fix-validation-error\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\bugfix/fix- \e[20;42Hregistration-flow\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\bugfix/fix-payment-bug\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\bugfix/fix-login-issue\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\bugfix/fix-\e[21;42Hcss-styling\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\bugfix/fix-crash-bug\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\bugfix/fix-broken-link\e(B\e[m\e[33m\e]8;;\e\\)\e[22;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[22;44H\e(B\e[m\e]8;;\e\\Author:\e[22;52HCI\e[22;55H\e[23;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[23;44H\e(B\e[m\e]8;;\e\\Date:\e[23;52H2\e[23;54Hseconds\e[23;62Hago\e[24;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[25;42H|\e[25;48H\e(B\e[m\e]8;;\e\\Improve\e[25;56HDockerfile\e[25;67Hfor\e[25;71Hmore\e[25;76Hefficient\e[25;86Hbuilds\e[26;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[27;42H\e(B\e[m\e]8;;\e\\*\e[27;44H\e(B\e[m\e[33m\e]8;;\e\\commit 5df73fe\e[28;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[28;44H\e(B\e[m\e]8;;\e\\Author:\e[28;52HCI\e[28;55H\e[29;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[29;44H\e(B\e[m\e]8;;\e\\Date:\e[29;52H2\e[29;54Hseconds\e[29;62Hago\e[30;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[30;120H\e(B\e[m\e[30m\e]8;;\e\\▼\e[?25l\e[?25l\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[?25l" + - delay: 1104 + content: "\e[?25l\e[35;32H\e(B\e[m\e[36m\e[1m\e]8;;\e\\/\e[?25l\e[?25l\e[14;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ שׂ docs/add-faq-section \e(B\e[m\e[30m\e]8;;\e\\▲\e[16;1H│\e[16;40H█\e[17;1H│\e[17;40H│\e[18;1H│\e[18;40H│\e[19;1H│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H▼\e[22;1H└──────────────────────────────1 of 52─┘\e[35;1H\e(B\e[m\e[36m\e]8;;\e\\Filter: \e[35;9H\e[?12l\e[?25h\e[0 q" + - delay: 1103 + content: "\e[?25l\e[35;9H\e[?12l\e[?25h\e[0 q\e[?25l\e[15;2H\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ \e[15;8Hexperiment/try-new-library\e[16;8Hexperiment/new-featur\e[16;30H-idea \e[17;8Hrefactor/extract-method \e[18;8Hmaster \e[20;6H\e(B\e[m\e[33m\e]8;;\e\\שׂ\e[20;8Hbugfix/fix-validation-error\e[21;6H\e(B\e[m\e[32m\e]8;;\e\\שׂ\e[21;8Hfeature/email-notifications\e[22;37H\e(B\e[m\e[30m\e]8;;\e\\49\e[35;9H\e(B\e[m\e[36m\e]8;;\e\\e\e[35;10H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[35;10H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;8H\e(B\e[m\e]8;;\e\\experiment/implemen\e[17;28H-new-design\e[18;6H\e(B\e[m\e[32m\e]8;;\e\\שׂ\e[18;8Hfeature/email-notifications\e[19;6Hשׂ\e[19;8Hfeature/admin-panel\e(B\e[m\e]8;;\e\\ \e[20;6Hשׂ\e[20;8Hexperiment/implement-design-conc\e[21;6Hשׂ\e[21;8Hrefactor/code-cleanup \e[22;37H\e(B\e[m\e[30m\e]8;;\e\\34\e[35;10H\e(B\e[m\e[36m\e]8;;\e\\n\e[35;11H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[35;11H\e[?12l\e[?25h\e[0 q\e[?25l\e[15;5H\e(B\e[m\e]8;;\e\\שׂ chor\e[15;12H/upda\e[15;18He-environment-configur\e(B\e[m\e[30m\e]8;;\e\\│\e[16;5H\e(B\e[m\e]8;;\e\\שׂ experiment/try-alternative-\e[16;35Hlgori\e(B\e[m\e[30m\e]8;;\e\\│\e[17;5H\e(B\e[m\e[32m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/mobile-responsive\e(B\e[m\e]8;;\e\\ \e[18;2H \e[18;6H \e[18;8H \e[19;2H \e[19;6H \e[19;8H \e[20;2H \e[20;6H \e[20;8H \e[21;2H \e[21;6H \e[21;8H \e[21;40H\e(B\e[m\e[30m\e]8;;\e\\│\e[22;32H─1 of 3\e[35;11H\e(B\e[m\e[36m\e]8;;\e\\v\e[35;12H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[35;12H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;2H\e(B\e[m\e]8;;\e\\ \e[17;5H \e[17;7H \e[22;38H\e(B\e[m\e[30m\e]8;;\e\\2\e[35;12H\e(B\e[m\e[36m\e]8;;\e\\i\e[35;13H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[35;13H\e[?12l\e[?25h\e[0 q\e[?25l\e[16;2H\e(B\e[m\e]8;;\e\\ \e[16;5H \e[16;7H \e[22;38H\e(B\e[m\e[30m\e]8;;\e\\1\e[35;13H\e(B\e[m\e[36m\e]8;;\e\\r\e[35;14H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[35;14H\e[?12l\e[?25h\e[0 q" + - delay: 10 + content: "\e[?25l\e[35;14H\e(B\e[m\e[36m\e]8;;\e\\o\e[35;15H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[35;15H\e[?12l\e[?25h\e[0 q\e[?25l\e[35;15H\e(B\e[m\e[36m\e]8;;\e\\n\e[35;16H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[35;16H\e[?12l\e[?25h\e[0 q" + - delay: 5 + content: "\e[?25l\e[14;1H\e(B\e[m\e[36m\e[1m\e]8;;\e\\┌─\e(B\e[m\e[32m\e[1m\e]8;;\e\\Local branches \e(B\e[m\e]8;;\e\\- Remotes - Tags\e(B\e[m\e[36m\e[1m\e]8;;\e\\──────┐\e[15;1H│\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\1s\e(B\e[m\e[44m\e[1m\e]8;;\e\\ שׂ chore/update-environment-configur\e(B\e[m\e[36m\e[1m\e]8;;\e\\│\e[16;1H│\e[16;40H│\e[17;1H│\e[17;40H│\e[18;1H│\e[18;40H│\e[19;1H│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H│\e[22;1H└───────────────────────────────1 of 1─┘\e[35;9H\e(B\e[m\e[36m\e]8;;\e\\matches\e[35;17Hfor\e[35;21H'environ'\e[35;31H\e(B\e[m\e[34m\e]8;;\e\\: Exit filter mode \e[?25l" + - delay: 17 + content: "\e[?25l\e[2;51H\e(B\e[m\e[33m\e]8;;\e\\6fd0fd7\e[2;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/up\e[2;69Hate-environment-c\e[2;88Hfigur\e[2;94Hti\e[2;97Hn\e(B\e[m\e[33m\e]8;;\e\\)\e(B\e[m\e]8;;\e\\ \e[3;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Author: CI \e[4;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Date: 5 seconds ago \e[5;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[6;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Fix env config issue \e[7;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[8;42H* \e(B\e[m\e[33m\e]8;;\e\\commit 10536af\e(B\e[m\e]8;;\e\\ \e[9;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Author: CI \e[10;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Date: 5 seconds ago \e[11;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[12;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Update env config \e[13;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[14;42H* \e(B\e[m\e[33m\e]8;;\e\\commit 0a3ce55 (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/add-faq-s\e[14;83Hc\e[14;85Hion\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\refactor/optimize-database- \e[15;42Hqueries\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\r\e[15;53Hfactor/improve-\e[15;69Herformance\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\refa\e[15;86Htor/improve-logging\e(B\e[m\e[33m\e]8;;\e\\, \e[16;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\refactor/improve-err\e[16;63Hr-handling\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\refactor/\e[16;85Hxtra\e[16;90Ht-reusable-component\e(B\e[m\e[33m\e]8;;\e\\, \e[17;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\refactor/extract-method\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\r\e[17;69Hfactor/code-cleanup\e(B\e[m\e[33m\e]8;;\e\\, \e[17;91H\e(B\e[m\e[32m\e[1m\e]8;;\e\\aster\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\ho\e[17;101Hfix/security-patch\e(B\e[m\e[33m\e]8;;\e\\,\e[18;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\hotfix/fix-produc\e[18;60Hion-issu\e[18;71Hhotfix/critical-s\e[18;89Hcurity-vulnerability\e(B\e[m\e[33m\e]8;;\e\\, \e[19;43H\e(B\e[m\e[32m\e[1m\e]8;;\e\\otfix/critical-bug\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\featur\e[19;70H/user-authentic\e[19;86Ht\e[19;88Hon\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\featu\e[19;98He/social-med\e[19;111Ha\e[20;42Hintegration\e(B\e[m\e[33m\e]8;;\e\\, \e[20;56H\e(B\e[m\e[32m\e[1m\e]8;;\e\\eature/search-functio\e[20;78Hality\e[20;85Hfeature/payment-processing\e(B\e[m\e[33m\e]8;;\e\\, \e[21;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/mobile-responsive\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feat\e[21;74Hre/localization-support\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/integrate- \e[22;42Hthird-party-api\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/image-upload-functionality\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/email- \e[23;42Hnotifications\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/chat-feature\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/analytics-dashboard\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/admi\e[24;42H-panel\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/try-new-library\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/try-different-architecture\e(B\e[m\e[33m\e]8;;\e\\, \e[25;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/try-alternative-algorithm\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/new-feature-idea\e(B\e[m\e[33m\e]8;;\e\\, \e[26;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/implement-new-design\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\experiment/implement-design-concept\e(B\e[m\e[33m\e]8;;\e\\, \e[27;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/update-readme\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/update-api-reference\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/improve-tutorials\e(B\e[m\e[33m\e]8;;\e\\, \e[28;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/api-documentation\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/add-user-guide\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\docs/add-changelog\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/update-\e[29;42Hdocumentation\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/update-dependencies\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/migrate-database\e(B\e[m\e[33m\e]8;;\e\\, \e[30;42H\e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/improve-test-coverage\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/cleanup-codebase\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/clean-up-git- \e[?25l\e[?25l\e[?25l" + - delay: 1104 + content: "\e[?25l\e[?25l" + - delay: 33 + content: "\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\C\e[1;45Hmmit\e[14;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─C\e[14;5Hmmits (chore/update-environment-co─┐\e[15;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\6fd0fd76\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix \e[15;23H c\e[15;27Hfig issue \e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[16;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e[16;4H10536af9\e[16;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[16;16H\e(B\e[m\e]8;;\e\\Update\e[16;23Henv\e[16;27Hconfig\e[16;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[17;4H0a3ce55a\e[17;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[17;16H\e(B\e[m\e]8;;\e\\Improve\e[17;24HDockerfile\e[17;35Hfor\e[17;39Hm\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[18;4H5df73fe1\e[18;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[18;16H\e(B\e[m\e]8;;\e\\Implement\e[18;26Huser\e[18;31Hblocking\e[18;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[19;4H3d8cc758\e[19;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[19;16H\e(B\e[m\e]8;;\e\\Refactor\e[19;25Huser\e[19;30Hnotificati\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[20;4H02e1adb0\e[20;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[20;16H\e(B\e[m\e]8;;\e\\Upgrade\e[20;24HRails\e[20;30Hversion\e[20;38Hto\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[21;4Hebf32b72\e[21;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[21;16H\e(B\e[m\e]8;;\e\\Move\e[21;21Hglobal\e[21;28Hvariables\e[21;38Hto\e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e[22;1H└──────────────────────────────1 of 32─┘\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Fuzzy filter branches \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 16 + content: "\e[?25l\e[2;42H\e(B\e[m\e[33m\e]8;;\e\\commit 6fd0fd76995966c75ef00e85bf6da6ecef4332be (\e(B\e[m\e[32m\e[1m\e]8;;\e\\chore/update-environment- \e[3;42Hconfiguration\e(B\e[m\e[33m\e]8;;\e\\)\e(B\e[m\e]8;;\e\\ \e[4;42HAuthor\e[4;50HCI \e[5;42HDate:\e[5;50HWed\e[5;54HAug\e[5;58H2\e[5;60H22:37:50\e[5;69H2023\e[5;74H+1000\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e]8;;\e\\ \e[6;48H \e[6;52H \e[6;56H \e[6;63H \e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;42H\e(B\e[m\e]8;;\e\\ \e[7;46HFix\e[7;50Henv\e[7;54Hconfig\e[7;61Hissue\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;42H\e(B\e[m\e]8;;\e\\--- \e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;42H\e(B\e[m\e]8;;\e\\ env_c\e[9;49Hnfig.rb | 3 \e(B\e[m\e[32m\e]8;;\e\\++\e(B\e[m\e[31m\e]8;;\e\\-\e(B\e[m\e]8;;\e\\ \e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;42H\e(B\e[m\e]8;;\e\\ 1 file\e[10;50Hchang\e[10;56Hd, 2 insertions(+),\e[10;76H1\e[10;78Hdeletion(-)\e[10;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[11;42H\e(B\e[m\e]8;;\e\\ \e[11;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[12;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/env_config.rb b/env_config.rb\e[12;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[13;42H\e(B\e[m\e[1m\e]8;;\e\\index b4bc6c0..0521207 100644\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;42H\e(B\e[m\e[1m\e]8;;\e\\--- a/env_config.rb\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[15;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/env_config.rb\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[16;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -1 +1,2 @@\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[17;42H\e(B\e[m\e[31m\e]8;;\e\\-EnvConfig.call(false)\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[18;42H\e(B\e[m\e[32m\e]8;;\e\\+# Turns out we need to pass true for this to work\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[19;42H\e(B\e[m\e[32m\e]8;;\e\\+EnvConfig.call(true)\e(B\e[m\e]8;;\e\\ \e[20;42H \e[21;42H \e[22;42H \e[23;42H \e[24;42H \e[25;42H \e[26;42H \e[27;42H \e[28;42H \e[29;42H \e[30;42H \e[?25l" + - delay: 602 + content: "\e[?25l\e[35;23H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;7H \e[35;14H \e[?25l" + - delay: 2001 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/interactive_rebase-compressed.gif b/demo/interactive_rebase-compressed.gif new file mode 100644 index 00000000000..bda3ca027ef Binary files /dev/null and b/demo/interactive_rebase-compressed.gif differ diff --git a/demo/interactive_rebase.gif b/demo/interactive_rebase.gif new file mode 100644 index 00000000000..04b7063004b Binary files /dev/null and b/demo/interactive_rebase.gif differ diff --git a/demo/interactive_rebase.yml b/demo/interactive_rebase.yml new file mode 100644 index 00000000000..999ebab06b0 --- /dev/null +++ b/demo/interactive_rebase.yml @@ -0,0 +1,240 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/interactive_rebase.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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" + +# Records, feel free to edit them +records: + - delay: 11286 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[?7l\e[H\e[2J" + - delay: 46 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 30 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────0 of 0─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────0 of 0─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 16 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────0 of 0─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 7 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────0 of 0─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 7 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────0 of 0─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────0 of 0─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────0 of 0─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 43 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────0 of 0─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────0 of 0─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 44 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api end\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api end\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 20 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api end\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api end\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api end\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e]8;;\e\\ \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api end\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api end\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api end\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e[?25l" + - delay: 1002 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api end\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing i \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api end\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing i \e[?25l" + - delay: 47 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api end\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing i \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 187 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoin\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iser\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refacto\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace d\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case fo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing i \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoin\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iser\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refacto\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace d\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case fo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing i \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 607 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoin\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iser\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refacto\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace d\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case fo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 6 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoin\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iser\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refacto\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace d\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case fo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────2 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 23 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoin\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iser\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refacto\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace d\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case fo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────2 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 125 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoin\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iser\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refacto\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace d\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case fo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────2 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 7 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoin\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iser\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refacto\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace d\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case fo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────3 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 23 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoin\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iser\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refacto\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace d\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case fo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────3 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 125 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoin\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iser\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refacto\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace d\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case fo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────3 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing f \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 41 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────3 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing f \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 22 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────3 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing f \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 605 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────3 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 42 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────4 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 22 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────4 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 125 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────4 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 42 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────5 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 22 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────5 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 726 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\311a661b\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\f260483d\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\789fd342\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\fixup\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────5 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 8 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\18f558f3\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────6 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 22 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\18f558f3\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────6 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 126 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\18f558f3\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────6 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 6 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\57657410\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────7 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 23 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\57657410\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────7 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 123 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\57657410\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────7 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing d \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 41 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\57657410\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[91;44m\e[1m\e]8;;\e\\drop\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────7 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing d \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 23 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\57657410\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[91;44m\e[1m\e]8;;\e\\drop\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────7 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing d \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 603 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\57657410\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[91;44m\e[1m\e]8;;\e\\drop\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4715aa2a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────7 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 7 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\drop\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4715aa2a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 23 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\drop\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4715aa2a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 125 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpoi\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\drop\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4715aa2a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[96;44m\e[1m\e]8;;\e\\pick\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/ise\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refact\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update U\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing s \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 40 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\drop\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4715aa2a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\squash\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/is\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refac\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of us\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing s \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 22 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\drop\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4715aa2a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\squash\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/is\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refac\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of us\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing s \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 604 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\drop\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4715aa2a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\squash\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/is\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refac\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of us\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing m \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[4]─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpo\e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\drop\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4715aa2a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\squash\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/is\e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 2\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─Rebase options────────────────────────────────────────────────────────────────╮\e(B\e[m\e[35m\e[1m\e]8;;\e\\hn-backdoor)\e(B\e[m\e]8;;\e\\ Refac\e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 2\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\c\e(B\e[m\e[44m\e[1m\e]8;;\e\\ continue \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\m \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 2\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\a\e(B\e[m\e]8;;\e\\ abort \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 2\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\s\e(B\e[m\e]8;;\e\\ skip \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Cancel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 2\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰────────────────────────────────────────────────────────────────────────1 of 4─╯\e(B\e[m\e[35m\e[1m\e]8;;\e\\-brunhild)\e(B\e[m\e]8;;\e\\ Replace\e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of us\e(B\e[m\e[30m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[30m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing m \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 605 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[4]─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpo\e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\drop\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4715aa2a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\squash\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/is\e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 2\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─Rebase options────────────────────────────────────────────────────────────────╮\e(B\e[m\e[35m\e[1m\e]8;;\e\\hn-backdoor)\e(B\e[m\e]8;;\e\\ Refac\e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 2\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\c\e(B\e[m\e[44m\e[1m\e]8;;\e\\ continue \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\m \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 2\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\a\e(B\e[m\e]8;;\e\\ abort \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 2\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\s\e(B\e[m\e]8;;\e\\ skip \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Cancel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 2\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰────────────────────────────────────────────────────────────────────────1 of 4─╯\e(B\e[m\e[35m\e[1m\e]8;;\e\\-brunhild)\e(B\e[m\e]8;;\e\\ Replace\e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of us\e(B\e[m\e[30m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[30m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset)\e[?25l" + - delay: 194 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\drop\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4715aa2a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\squash\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/is\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refac\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of us\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 33 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\drop\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4715aa2a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\squash\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/is\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refac\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of us\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\26d8ab57\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\cfaebd78\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\311a661b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/demo, feature/demo)\e(B\e[m\e]8;;\e\\ Remove deprecated uses of api endpo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\f260483d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Revamp User Interface of the settings page \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\789fd342\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e]8;;\e\\fixup\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Introduce cache layer for product images \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18f558f3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\57657410\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\drop\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Create initial setup for postgres database \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[94;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4715aa2a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\11:42PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\squash\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Fix incorrect type in updateUser function \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\bf197767\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[34m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\pick\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\<-- YOU ARE HERE ---\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD, origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/is\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refac\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\fe9bda50\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\23 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of us\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 68─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 30 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\f67c9920\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD -> feature/demo)\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a8ef1db6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\51aab580\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\b9bbca51\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\23 Jan 24\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5181e6d6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\08 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/terra-investigation, terra-investigation)\e(B\e[m\e]8;;\e\\ Ensure CSRF protectio\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\41f3f3c8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\07 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce Redis for session management \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7412ac88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\06 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Improve Dockerfile for more efficient builds \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a0bfaa7a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge hotfix/fezzan-corridor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\9e6d126f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/hotfix/fezzan-corridor, hotfix/fezzan-corridor)\e(B\e[m\e]8;;\e\\ Implement use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 63─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 21 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\f67c9920\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD -> feature/demo)\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a8ef1db6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\51aab580\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\b9bbca51\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\23 Jan 24\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5181e6d6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\08 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/terra-investigation, terra-investigation)\e(B\e[m\e]8;;\e\\ Ensure CSRF protectio\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\41f3f3c8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\07 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce Redis for session management \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7412ac88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\06 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Improve Dockerfile for more efficient builds \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a0bfaa7a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge hotfix/fezzan-corridor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\9e6d126f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/hotfix/fezzan-corridor, hotfix/fezzan-corridor)\e(B\e[m\e]8;;\e\\ Implement use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 63─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\f67c9920\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD -> feature/demo)\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a8ef1db6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\51aab580\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\b9bbca51\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\23 Jan 24\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5181e6d6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\08 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/terra-investigation, terra-investigation)\e(B\e[m\e]8;;\e\\ Ensure CSRF protectio\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\41f3f3c8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\07 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce Redis for session management \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7412ac88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\06 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Improve Dockerfile for more efficient builds \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a0bfaa7a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge hotfix/fezzan-corridor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\9e6d126f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/hotfix/fezzan-corridor, hotfix/fezzan-corridor)\e(B\e[m\e]8;;\e\\ Implement use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 63─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\f67c9920\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD -> feature/demo)\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a8ef1db6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\51aab580\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\b9bbca51\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\23 Jan 24\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5181e6d6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\08 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/terra-investigation, terra-investigation)\e(B\e[m\e]8;;\e\\ Ensure CSRF protectio\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\41f3f3c8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\07 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce Redis for session management \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7412ac88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\06 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Improve Dockerfile for more efficient builds \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a0bfaa7a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge hotfix/fezzan-corridor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\9e6d126f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/hotfix/fezzan-corridor, hotfix/fezzan-corridor)\e(B\e[m\e]8;;\e\\ Implement use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 63─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Interactive rebase \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 605 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\f67c9920\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD -> feature/demo)\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a8ef1db6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\51aab580\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\b9bbca51\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\23 Jan 24\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5181e6d6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\08 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/terra-investigation, terra-investigation)\e(B\e[m\e]8;;\e\\ Ensure CSRF protectio\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\41f3f3c8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\07 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce Redis for session management \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7412ac88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\06 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Improve Dockerfile for more efficient builds \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a0bfaa7a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge hotfix/fezzan-corridor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\9e6d126f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/hotfix/fezzan-corridor, hotfix/fezzan-corridor)\e(B\e[m\e]8;;\e\\ Implement use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 63─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\f67c9920\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD -> feature/demo)\e(B\e[m\e]8;;\e\\ Resolve intermittent test failure in CartTest \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[3;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a8ef1db6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of sorting algorithm in util package \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[4;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\51aab580\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for authentication service \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\b9bbca51\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination in user listings \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client for better error handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e(B\e[m\e]8;;\e\\ Merge feature/iserlohn-backdoor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\24 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/iserlohn-backdoor, feature/iserlohn-backdoor)\e(B\e[m\e]8;;\e\\ Refactor sess\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\23 Jan 24\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\Fredrica Green...\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\22 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database connection failures gracefully \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\21 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according to new design guidelines \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repair-brunhild into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\082eac59\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\20 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/repair-brunhild, feature/repair-brunhild)\e(B\e[m\e]8;;\e\\ Replace depreca\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\084da091\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\19 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Revamp error handling in user registration \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\673db4f4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge feature/peace-time into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a61c3586\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\18 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/peace-time, feature/peace-time)\e(B\e[m\e]8;;\e\\ Handle edge case for zero\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a879c882\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\17 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce retry mechanism in network calls \e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\973f7aed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\16 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Remove hardcoded values from payment module \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ec5a9718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\15 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Enhance logging in production environment \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\3eedd3df\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\14 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\Paul Oberstein \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Add internationalization support for German \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\398671fc\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e[38;5;34m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/attack-on-odin into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4f607415\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\13 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/feature/attack-on-odin, feature/attack-on-odin)\e(B\e[m\e]8;;\e\\ Update UX of pa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e5b87693\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\12 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8e66da88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\Siegfried Kirc...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve race condition in transaction handling \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cc329ec3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;162m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge quash-rebellion into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7a21831a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\10 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/quash-rebellion, quash-rebellion)\e(B\e[m\e]8;;\e\\ Harden security of user passw\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\15dd829d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\09 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\Yang Wen-li \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Implement bulk delete feature in admin panel \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4ea6e519\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;207m\e]8;;\e\\─\e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Merge terra-investigation into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\5181e6d6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\08 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/terra-investigation, terra-investigation)\e(B\e[m\e]8;;\e\\ Ensure CSRF protectio\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\41f3f3c8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\07 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Introduce Redis for session management \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7412ac88\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\06 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\Fredrica Green...\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Improve Dockerfile for more efficient builds \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\a0bfaa7a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\11:42PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─\e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge hotfix/fezzan-corridor into master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\9e6d126f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\05 Jan 24\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\Oscar Reuenthal \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;184m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;34m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/hotfix/fezzan-corridor, hotfix/fezzan-corridor)\e(B\e[m\e]8;;\e\\ Implement use\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────8 of 63─╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing + \e[?25l" + - delay: 6 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\f67c9920\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent t\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a8ef1db6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\51aab580\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for aut\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\b9bbca51\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination i\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ git rebase --continue \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing + \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\f67c9920\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent t\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a8ef1db6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\51aab580\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for aut\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\b9bbca51\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination i\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ git rebase --continue \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing + \e[?25l" + - delay: 605 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\f67c9920\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent t\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a8ef1db6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\51aab580\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for aut\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\b9bbca51\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination i\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ git rebase --continue \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing P \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─Force push────────────────────────────────────────────────────────────────────╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserl\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\Your branch has diverged from the remote branch. Press to cancel, or \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repai\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\ to force push. \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰───────────────────────────────────────────────────────────────────────────────╯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H╭─[4]─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\f67c9920\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent t\e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a8ef1db6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\51aab580\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for aut\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\b9bbca51\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination i\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H╰──────────────────────────────8 of 63─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ git rebase --continue \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing P \e[?25l" + - delay: 606 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─Force push────────────────────────────────────────────────────────────────────╮\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserl\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\Your branch has diverged from the remote branch. Press to cancel, or \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repai\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\ to force push. \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰───────────────────────────────────────────────────────────────────────────────╯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H╭─[4]─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\f67c9920\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Resolve intermittent t\e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\a8ef1db6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Improve efficiency of \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\51aab580\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add unit tests for aut\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H│\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[31m\e]8;;\e\\b9bbca51\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Integrate pagination i\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H╰──────────────────────────────8 of 63─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ git rebase --continue \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 7 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ git push --force-with-lease \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 22 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ git push --force-with-lease \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 23 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\Pushing |\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ git push --force-with-lease \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\Pushing |\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ git push --force-with-lease \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 42 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\Pushing /\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ git push --force-with-lease \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\Pushing /\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ git push --force-with-lease \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 55 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\Pushing /\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ + 311a661...f67c992 feature/demo -> feature/demo (forced update) \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\Pushing /\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ + 311a661...f67c992 feature/demo -> feature/demo (forced update) \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\Pushing /\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ + 311a661...f67c992 feature/demo -> feature/demo (forced update) \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 28 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\Pushing /\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ + 311a661...f67c992 feature/demo -> feature/demo (forced update) \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 22 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e]8;;\e\\Pushing /\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\8s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\9s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ + 311a661...f67c992 feature/demo -> feature/demo (forced update) \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[33m\e]8;;\e\\↑4↓9\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\11s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\11s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ + 311a661...f67c992 feature/demo -> feature/demo (forced update) \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\11s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\11s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ + 311a661...f67c992 feature/demo -> feature/demo (forced update) \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 606 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\11s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\11s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ + 311a661...f67c992 feature/demo -> feature/demo (forced update) \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Push to remote \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\╭─[1]─Status───────────────────────────╮╭─Patch────────────────────────────────────────────────────────────────────────╮\e[2;1H│\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit fe9bda5071893de658e3abd33102510de5ffcce8\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[3;1H╰──────────────────────────────────────╯│\e(B\e[m\e]8;;\e\\Author: Fredrica Greenhill <> \e(B\e[m\e[30m\e]8;;\e\\▐\e[4;1H╭─[2]─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───╮│\e(B\e[m\e]8;;\e\\Date: Tue Jan 23 23:42:44 2024 +1100 \e(B\e[m\e[30m\e]8;;\e\\▐\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Ensure atomicity of transactions in payment system \e(B\e[m\e[30m\e]8;;\e\\▐\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[13;1H╰───────────────────────────────0 of 0─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[14;1H╭─[3]─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──╮│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\10s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\11s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/iserlohn-backdoor\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[18;1H│\e(B\e[m\e[36m\e]8;;\e\\11s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/repair-brunhild\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[19;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/peace-time\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[20;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/attack-on-odin\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[21;1H│\e(B\e[m\e[36m\e]8;;\e\\12s\e(B\e[m\e]8;;\e\\ \U000F062C quash-rebellion \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[22;1H╰──────────────────────────────1 of 12─╯│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╭─[4]─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────╮\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[24;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\05213b8e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor HTTP client f\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[25;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b4b2001b\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\⏣─╮\e(B\e[m\e]8;;\e\\ Merge feature/iserlo\e(B\e[m\e[32m\e[1m\e]8;;\e\\▐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[26;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\0e2fadb3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Refactor session man\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\▐\e[27;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\fe9bda50\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\FG\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;5;207;44m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Ensure atomicity of \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\90368cd5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database conn\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8a00d245\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\FG\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;207m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles accord\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\\U000F062D\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\13f2a707\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\OR\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;5;77m\e]8;;\e\\⏣─\e(B\e[m\e[38;5;207m\e]8;;\e\\│\e(B\e[m\e[38;5;77m\e]8;;\e\\─╮\e(B\e[m\e]8;;\e\\ Merge feature/repa\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\╰──────────────────────────────8 of 63─╯\e(B\e[m\e[30m\e]8;;\e\\╰──────────────────────────────────────────────────────────────────────────────╯\e[32;1H╭─[5]─Stash────────────────────────────╮╭─Command log──────────────────────────────────────────────────────────────────╮\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ + 311a661...f67c992 feature/demo -> feature/demo (forced update) \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H╰───────────────────────────────0 of 0─╯╰──────────────────────────────────────────────────────────────────────────────╯\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l" + - delay: 2000 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[?1l\e>\e[?7h\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/nuke_working_tree-compressed.gif b/demo/nuke_working_tree-compressed.gif new file mode 100644 index 00000000000..08a752793ad Binary files /dev/null and b/demo/nuke_working_tree-compressed.gif differ diff --git a/demo/nuke_working_tree.gif b/demo/nuke_working_tree.gif new file mode 100644 index 00000000000..edded47adfe Binary files /dev/null and b/demo/nuke_working_tree.gif differ diff --git a/demo/nuke_working_tree.mp4 b/demo/nuke_working_tree.mp4 new file mode 100644 index 00000000000..ba244e4bd4e Binary files /dev/null and b/demo/nuke_working_tree.mp4 differ diff --git a/demo/nuke_working_tree.yml b/demo/nuke_working_tree.yml new file mode 100644 index 00000000000..38cd77449f9 --- /dev/null +++ b/demo/nuke_working_tree.yml @@ -0,0 +1,219 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/nuke_working_tree.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + fontFamily: "DejaVuSansMono 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" + +# Records, feel free to edit them +records: + - delay: 2594 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 83 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l" + - delay: 8 + content: "\e[?25l\e[2;2H\e(B\e[m\e[44m\e[1m\e]8;;\e\\▼ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\ controllers\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e[3;4H\e(B\e[m\e[31m\e]8;;\e\\??  README.md\e[4;4H\e(B\e[m\e[32m\e]8;;\e\\A  blue_controller.rb\e[5;4H\e(B\e[m\e[31m\e]8;;\e\\??  green_controller.rb\e[6;4H??  red_controller.rb\e[7;2H\e(B\e[m\e]8;;\e\\▼\e[7;4H\e(B\e[m\e[33m\e]8;;\e\\ views\e[8;4H\e(B\e[m\e]8;;\e\\▼\e[8;6H\e(B\e[m\e[33m\e]8;;\e\\ helpers\e[9;6H\e(B\e[m\e[32m\e]8;;\e\\A  list.rb\e[10;6H\e(B\e[m\e[31m\e]8;;\e\\??  sort.rb\e[11;4H\e(B\e[m\e[32m\e]8;;\e\\A  users_view.rb \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1 of 10\e[?25l" + - delay: 6 + content: "\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Nuke\e[35;6Hthe\e[35;10Hworking\e[35;18Htree\e[?25l" + - delay: 2002 + content: "\e[?25l\e[35;23H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing D \e[?25l\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e[2;120H│\e[3;1H│\e[3;120H│\e[4;1H│\e[4;120H│\e[5;1H│\e[5;120H│\e[6;1H│\e[6;120H│\e[7;1H│\e[7;120H│\e[8;1H│\e[8;120H│\e[9;1H│\e[9;120H│\e[10;1H│\e[10;120H│\e[11;1H│\e[11;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌───────────────────────────────────────────────────────────────────────────────┐\e[11;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H│\e[12;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\x\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Nuke working tree \e(B\e[m\e[91;44m\e[1m\e]8;;\e\\git reset --hard HEAD && git clean -fd\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H│\e[13;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\u\e[13;24H\e(B\e[m\e]8;;\e\\Discard\e[13;32Hunstaged\e[13;41Hchanges\e[13;49H\e(B\e[m\e[31m\e]8;;\e\\git checkout -- .\e[13;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H│\e[14;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\c\e[14;24H\e(B\e[m\e]8;;\e\\Discard\e[14;32Huntracked\e[14;42Hfiles\e[14;49H\e(B\e[m\e[31m\e]8;;\e\\git clean -fd\e[14;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e[15;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\S\e[15;24H\e(B\e[m\e]8;;\e\\Discard\e[15;32Hstaged\e[15;39Hchanges\e[15;49H\e(B\e[m\e[31m\e]8;;\e\\stash staged and drop stash\e[15;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\s\e[16;24H\e(B\e[m\e]8;;\e\\Soft\e[16;29Hreset\e[16;49H\e(B\e[m\e[31m\e]8;;\e\\git reset --soft HEAD\e[16;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e[17;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\m\e[17;24H\e(B\e[m\e]8;;\e\\mixed\e[17;30Hreset\e[17;49H\e(B\e[m\e[31m\e]8;;\e\\git reset --mixed HEAD\e[17;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e[18;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\h\e[18;24H\e(B\e[m\e]8;;\e\\Hard\e[18;29Hreset\e[18;49H\e(B\e[m\e[31m\e]8;;\e\\git reset --hard HEAD\e[18;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e[19;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;24H\e(B\e[m\e]8;;\e\\Cancel\e[19;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e[20;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└────────────────────────────────────────────────────────────────────────1 of 8─┘\e[20;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e[21;21H┌───────────────────────────────────────────────────────────────────────────────┐\e[21;120H│\e[22;1H│\e[22;21H│\e(B\e[m\e]8;;\e\\If\e[22;25Hyou\e[22;29Hwant\e[22;34Hto\e[22;37Hmake\e[22;42Hall\e[22;46Hthe\e[22;50Hchanges\e[22;58Hin\e[22;61Hthe\e[22;65Hworktree\e[22;74Hgo\e[22;77Haway,\e[22;83Hthis\e[22;88His\e[22;91Hthe\e[22;95Hway\e[22;99Hto\e(B\e[m\e[30m\e]8;;\e\\│\e[22;120H│\e[23;1H│\e[23;21H│\e(B\e[m\e]8;;\e\\do\e[23;25Hit.\e[23;29HIf\e[23;32Hthere\e[23;38Hare\e[23;42Hdirty\e[23;48Hsubmodule\e[23;58Hchanges\e[23;66Hthis\e[23;71Hwill\e[23;76Hstash\e[23;82Hthose\e[23;88Hchanges\e[23;96Hin\e[23;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[23;120H│\e[24;1H│\e[24;21H│\e(B\e[m\e]8;;\e\\the\e[24;26Hsubmodule(s).\e[24;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[24;120H│\e[25;1H│\e[25;21H└───────────────────────────────────────────────────────────────────────────────┘\e[25;120H│\e[26;1H│\e[26;120H│\e[27;1H│\e[27;120H│\e[28;1H│\e[28;120H│\e[29;1H│\e[29;120H│\e[30;1H│\e[30;120H│\e[31;1H│\e[31;120H│\e[32;1H│\e[32;120H│\e[33;1H│\e[33;120H│\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 10─┘\e[?25l" + - delay: 1603 + content: "\e[?25l\e[35;32H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 37 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[3;1H│\e[3;4H\e(B\e[m\e]8;;\e\\ \e[3;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;1H│\e[4;4H\e(B\e[m\e]8;;\e\\ \e[4;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;1H│\e[5;4H\e(B\e[m\e]8;;\e\\ \e[5;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;1H│\e[6;4H\e(B\e[m\e]8;;\e\\ \e[6;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;1H│\e(B\e[m\e]8;;\e\\ \e[7;4H \e[7;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;1H│\e[8;4H\e(B\e[m\e]8;;\e\\ \e[8;6H \e[8;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;1H│\e[9;6H\e(B\e[m\e]8;;\e\\ \e[9;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H│\e[10;6H\e(B\e[m\e]8;;\e\\ \e[10;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;1H│\e[11;4H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;1H│\e[12;21H\e(B\e[m\e]8;;\e\\ \e[12;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;1H│\e[13;21H\e(B\e[m\e]8;;\e\\ \e[13;24H \e[13;32H \e[13;41H \e[13;49H \e[13;101H \e[13;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[14;1H│\e[14;21H\e(B\e[m\e]8;;\e\\ \e[14;24H \e[14;32H \e[14;42H \e[14;49H \e[14;101H \e[14;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;1H│\e[15;21H\e(B\e[m\e]8;;\e\\ \e[15;24H \e[15;32H \e[15;39H \e[15;49H \e[15;101H \e[15;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e[16;21H\e(B\e[m\e]8;;\e\\ \e[16;24H \e[16;29H \e[16;49H \e[16;101H \e[16;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H│\e[17;21H\e(B\e[m\e]8;;\e\\ \e[17;24H \e[17;30H \e[17;49H \e[17;101H \e[17;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e[18;21H\e(B\e[m\e]8;;\e\\ \e[18;24H \e[18;29H \e[18;49H \e[18;101H \e[18;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e[19;21H\e(B\e[m\e]8;;\e\\ \e[19;24H \e[19;101H \e[19;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e[20;21H\e(B\e[m\e]8;;\e\\ \e[20;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e[21;21H\e(B\e[m\e]8;;\e\\ \e[21;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e[22;21H\e(B\e[m\e]8;;\e\\ \e[22;25H \e[22;29H \e[22;34H \e[22;37H \e[22;42H \e[22;46H \e[22;50H \e[22;58H \e[22;61H \e[22;65H \e[22;74H \e[22;77H \e[22;83H \e[22;88H \e[22;91H \e[22;95H \e[22;99H \e[22;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e[23;21H\e(B\e[m\e]8;;\e\\ \e[23;25H \e[23;29H \e[23;32H \e[23;38H \e[23;42H \e[23;48H \e[23;58H \e[23;66H \e[23;71H \e[23;76H \e[23;82H \e[23;88H \e[23;96H \e[23;101H \e[23;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;1H│\e[24;21H\e(B\e[m\e]8;;\e\\ \e[24;26H \e[24;101H \e[24;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;1H│\e[25;21H\e(B\e[m\e]8;;\e\\ \e[25;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;1H│\e[26;120H│\e[27;1H│\e[27;120H│\e[28;1H│\e[28;120H│\e[29;1H│\e[29;120H│\e[30;1H│\e[30;120H│\e[31;1H│\e[31;120H│\e[32;1H│\e[32;120H│\e[33;1H│\e[33;120H│\e[34;1H└───────────────────────────────────────────────────────────────────────────────────────────────────────────────0 of 0─┘\e[?25l" + - delay: 11 + content: "\e[?25l\e[2;2H\e(B\e[m\e[97m\e[1m\e]8;;\e\\ \e[3;2H \e[4;2H \e[5;2H \e[6;2H +& \e[7;2H . @ *# %+%#&*+#* % \e[8;2H % &@ *##%+*&**++%% +&*# &# \e[9;2H ###@# #@+%* &**+ ...% * +&#* \e[10;2H *&.++#.&&.%@#@ %++ + # % && &+#.. \e[11;2H ** &@.. .@&*++* @.@%+.&*##.#.@@%+** && \e[12;2H .*% + .#&& ## @.%*#%+++%& %% *#% .% % \e[13;2H & .+ +# %.@%%%.% &@@ @&..& %#+%@*@%*% \e[14;2H @%# +#+@# %.#.#%#+ . +%#+ %.%. # &+@& * + @ \e[15;2H %+%@ ++&@##%&. %&.##.@+#*+ @@.+& .++#++& + + # \e[16;2H + . .#&&@@&### @*.#%. #*.%*#+# % &#+@% %% %*% \e[17;2H .&**@+ @@*# @ &@*+%@ .% +*%% .%&.%%# ## ##. % \e[18;2H &@%@. +*#*#%+ @#&.#@*# *& &*+&+% #.& %+ @+%&*%#@# \e[19;2H +.##@.@ +#@&*#%@%+. *.+ *+.*&&*&+* %%+& +%#+&.% \e[20;2H *+&@ *%.+%# ##@+*+ #+@@. % #* ..#&* & & +#. \e[21;2H . %%+@. ++.%## *.#+% @+.+. *@@++%&*@*@%.#&@@@+ \e[22;2H .# + @ ##%++ +*@+** @% + ###@.+..* *. .& # \e[23;2H % *#+ # #&&&%%*# .% @+*&#% # *@#& . %*+.+ \e[24;2H @*&*+#%%+. .@#+ %.&*@%.& #.. # #.% +&+# \e[25;2H @ % %@* +**. *%+.%+#%%*&###&*% &**%# \e[26;2H +@&#*@*%@ %%+++. .@@@*...%@&%.#%%@# \e[27;2H @%.@ #* * #+ .+&#*+*** +% ###+ \e[28;2H #. @+* #.@ @%+@@ &.&+#@%* \e[29;2H #*@& @@% ***%@ &** \e[30;2H +@@ \e[31;2H \e[32;2H \e[33;2H \e[?25l" + - delay: 21 + content: "\e[?25l\e[2;50H\e(B\e[m\e[97m\e[1m\e]8;;\e\\+@&*%@&#.\e[2;63H&.*\e[2;67H*\e[2;69H@%\e[3;45H%&\e[3;48H@\e[3;53H#&&*@*\e[3;60H@#+\e[3;64H+@*&\e[3;70H%##%\e[3;75H@@\e[4;42H@.%\e[4;46H&&..#\e[4;53H+@@#&#%\e[4;61H#%%*#\e[4;67H+@@&+*\e[4;74H#+\e[4;77H@#\e[5;39H%++\e[5;44H+*.@\e[5;49H+\e[5;51H#++\e[5;55H@*\e[5;59H@#+.\e[5;64H%.#**\e[5;70H*\e[5;72H*&&#*&.\e[5;81H+\e[6;37H*+\e[6;41H#&\e[6;44H%\e[6;46H##*\e[6;50H@*\e[6;53H%&\e[6;58H%+.#@.\e[6;65H&\e[6;67H&\e[6;69H*#\e[6;72H*+*##+.\e[6;80H&*\e[6;83H%*\e[7;35H**\e[7;39H@%\e[7;42H%\e[7;44H&%+@@\e[7;50H@\e[7;53H*.. #&& @##%\e[7;67H&@@@.\e[7;73H@%+\e[7;79H@*#%\e[7;85H#\e[8;34H+\e[8;37H*.##@*#..\e[8;47H@*%#+.\e[8;54H ** *% &\e[8;65H*#&%#\e[8;72H +@.\e[8;78H*+&*&\e[8;84H@#+@\e[9;32H.#\e[9;35H&\e[9;37H+\e[9;39H.&&&+\e[9;45H%@ \e[9;49H #\e[9;52H#% @@%..%#.%#+\e[9;69H @#\e[9;73H&#\e[9;76H+@%@@#@%*&\e[10;30H&&@#@@.\e[10;38H+\e[10;40H##+% #.* \e[10;50H%\e[10;52H# . \e[10;58H. *@*%&\e[10;67H*.*+& @. *+.#\e[10;81H**@*+\e[10;87H@\e[10;89H&\e[11;29H..&\e[11;35H+#%*%## .+&& .&\e[11;51H@ %. @# #\e[11;64H%@+\e[11;68H+\e[11;70H*.+*% +@\e[11;79H \e[11;81H**%\e[11;85H+\e[11;87H++.\e[12;29H&\e[12;31H&%.&&&%+& \e[12;42H \e[12;44H @#++\e[12;50H*%&% %%* .. *@ &%+@## #\e[12;77H \e[12;79H@#%%%@+\e[12;87H.#\e[12;90H.\e[13;28H&#%.*.\e[13;35H#\e[13;37H+*++\e[13;42H@@\e[13;46H%*\e[13;50H #\e[13;53H& #&*%%+.*.\e[13;65H.\e[13;67H& #\e[13;71H@#* \e[13;77H. ++@.\e[13;86H&&.\e[13;90H%&#\e[14;27H@\e[14;29H%%&.%&+@@ +&\e[14;43H@ .\e[14;47H* \e[14;50H&# \e[14;57H&\e[14;59H&. &% %+@# *+.##%\e[14;80H.*+\e[14;84H@%#\e[14;88H@\e[14;91H@@.\e[15;29H%\e[15;31H+*\e[15;37H@** \e[15;42H.@@.%&+ *@@@&*\e[15;57H *@\e[15;61H %&\e[15;66H @. \e[15;71H&\e[15;73H *#@*.%*&%&\e[15;85H#++\e[15;89H.\e[15;92H+#\e[16;27H%%\e[16;31H@\e[16;34H.&\e[16;37H@*@\e[16;41H*+\e[16;44H@ #%+\e[16;50H \e[16;52H% +\e[16;57H &%+@* \e[16;65H%#+\e[16;70H%. %\e[16;76H#*#*++%&%+&@*+*#+.\e[17;27H&#\e[17;30H.###+\e[17;36H@*.&..&.\e[17;45H \e[17;47H#.+ \e[17;52H+#+ \e[17;59H \e[17;61H. #*@+*++*@\e[17;75H .%%\e[17;80H&\e[17;82H%@\e[17;86H#\e[17;89H@\e[17;91H*++\e[18;26H.**\e[18;30H#+\e[18;33H#\e[18;36H@& +#\e[18;42H@%.. &%*+. *\e[18;56H&+\e[18;59H.@@ \e[18;64H @#++@.#&*#\e[18;77H*@ &@ .@*.#\e[18;90H&+#\e[18;94H+\e[19;27H@&#*%+\e[19;34H.+* +%@+&%\e[19;45H% \e[19;48H %@.+&&\e[19;56H+%+%\e[19;61H #* \e[19;66H #. # **#\e[19;77H @+ #+%\e[19;88H*..\e[19;92H@%\e[20;28H*\e[20;32H@\e[20;35H*+ \e[20;39H@*\e[20;42H &%# \e[20;48H&+@ %%\e[20;57H && %&** .+*@&& \e[20;74H##*.&\e[20;81H +\e[20;86H##+%%\e[20;92H@%\e[21;27H*\e[21;29H+&@*#**\e[21;37H .* # #\e[21;45H* &#*\e[21;51H@+\e[21;54H.. #% # *#+\e[21;68H#\e[21;70H#@ \e[21;74H&+** * * @%\e[21;90H*\e[21;93H#\e[22;28H.\e[22;31H&\e[22;34H&\e[22;36H%#@&&&* ## +@\e[22;52H.. %.#%\e[22;60H. #%@\e[22;66H +@.%%%*%..\e[22;78H% \e[22;82H%@\e[22;86H+*\e[22;89H&\e[22;91H#*\e[23;29H+\e[23;31H+++&@\e[23;37H&&+*#\e[23;43H .+ \e[23;48H#\e[23;50H%%# *&@\e[23;58H @%.\e[23;64H &&@% * &@+@@* &\e[23;83H.\e[23;85H#*\e[23;88H*+.\e[24;30H#+*@#\e[24;36H*.\e[24;39H& \e[24;42H#\e[24;44H.& &# \e[24;51H \e[24;53H% &+#\e[24;60H+* \e[24;66H +* .% +\e[24;76H%#&%\e[24;81H#\e[24;83H...@#@&#+\e[25;29H%&+@.+&\e[25;37H*\e[25;39H@@% \e[25;45H@\e[25;47H@&+\e[25;51H@&\e[25;54H&*&+@#+@..@\e[25;66H# @@@%+*\e[25;75H#\e[25;77H + %*%#\e[25;85H#+#%&\e[25;91H%\e[26;30H#...&\e[26;36H*%.@\e[26;41H%#@* * #+& . .%@ +@&. + \e[26;67H+ # &\e[26;74H .\e[26;77H *\e[26;80H%#\e[26;83H&&*&%%#\e[27;33H&&#&&\e[27;39H%%+%&\e[27;45H *# +@+@&+@#\e[27;58H@@% & #\e[27;66H%##. ..@ &+@&%\e[27;81H#.#\e[27;85H%&&.\e[28;33H+\e[28;36H%\e[28;38H*\e[28;41H#.\e[28;44H@&.@#+#*\e[28;53H% \e[28;56H #.+ &*\e[28;64H&\e[28;66H ..+ # \e[28;75H.&#%+##+\e[28;84H.&\e[28;87H*\e[29;37H&#\e[29;40H+\e[29;42H&.#.*.%#\e[29;51H*. & \e[29;57H \e[29;59H#& %\e[29;64H+&@\e[29;68H +\e[29;71H.\e[29;75H@*\e[29;80H&\e[29;83H&.#\e[30;37H%**\e[30;41H&..&#*#\e[30;49H+#%\e[30;53H++\e[30;57H&\e[30;59H* \e[30;63H&@#%\e[30;68H+\e[30;70H#.+\e[30;74H+#@%\e[30;80H+*@\e[30;84H.\e[31;40H@\e[31;42H#*\e[31;47H.*#*%%\e[31;54H*\e[31;59H+@&*&@\e[31;66H&@%%%\e[31;72H%#*#+*&%+.\e[32;42H%\e[32;44H.&\e[32;47H&\e[32;49H+&@#@#.\e[32;58H#&.+.#.\e[32;67H&@\e[32;70H@\e[32;72H*\e[32;74H&++\e[32;78H#.\e[33;44H*#\e[33;47H%#%\e[33;52H#*\e[33;55H#&.+#+@\e[33;63H#*.+\e[33;68H%&\e[33;71H@.+.+#\e[?25l" + - delay: 21 + content: "\e[?25l\e[2;34H\e(B\e[m\e[97m\e[1m\e]8;;\e\\+**\e[2;39H&#\e[2;42H+.\e[2;45H&+\e[2;48H*\e[2;50H@#*+ . & *\e[2;61H&@\e[2;64H* \e[2;67H \e[2;69H%\e[2;71H+*@\e[2;75H%#\e[2;78H@.\e[2;81H%++.&.\e[3;32H#\e[3;35H+\e[3;37H@#*@@.\e[3;44H+#+\e[3;48H .*%\e[3;54H *%*@\e[3;60H# &\e[3;64H \e[3;68H%% *. ++\e[3;77H*\e[3;80H%%@\e[3;85H&%*#\e[4;30H*\e[4;32H%.@\e[4;37H&&\e[4;40H+&#@+. . \e[4;50H%@% \e[4;56H&.* \e[4;61H%@.%.+ **% @**@@% +*\e[4;83H++#\e[4;87H&*##\e[5;32H+\e[5;34H&%\e[5;37H*\e[5;39H& %@ +&%\e[5;49H@&. .%&&.\e[5;59H @.&*\e[5;65H +\e[5;68H \e[5;70H+.#+# . ++ %&&\e[5;86H.@@\e[5;91H*#\e[6;26H#\e[6;28H*+#.+\e[6;34H*#& \e[6;40H# .\e[6;44H&. &@@\e[6;51H &&++\e[6;57H%*%#%*&\e[6;65H \e[6;67H *@ +% \e[6;75H+*@@@* \e[6;83H +..*&\e[6;90H#\e[6;92H.+\e[7;28H&.\e[7;33H+%%.#\e[7;39H&.&\e[7;44H**\e[7;47H&&#*@. \e[7;58H #\e[7;61H@++\e[7;65H* % +*+ * +@\e[7;79H @*\e[7;84H+&\e[7;90H*\e[7;92H%\e[7;94H#+\e[8;24H*@\e[8;28H+&+&@. %@ +@ .\e[8;44H %@& +\e[8;51H@+%\e[8;55H*. # +.\e[8;63H. %#@ *. **&&@&\e[8;81H. +. @++@*+@.\e[8;96H%\e[9;25H%\e[9;27H@\e[9;29H.\e[9;32H@*\e[9;35H#@&* \e[9;41H*+%\e[9;45H@%% @\e[9;51H. &\e[9;55H \e[9;59H %#+ @\e[9;67H.#&*\e[9;72H@@%%..@\e[9;80H #+@.&*&%+*\e[9;93H#\e[10;22H#\e[10;24H@.*%\e[10;31H*+ . %& \e[10;40H . .&@.\e[10;49H& * \e[10;54H .\e[10;58H+@* %+#\e[10;67H +#%\e[10;75H.%% &&## %\e[10;86H%&\e[10;89H @&@&%+&\e[10;98H*\e[11;21H#+\e[11;24H%%\e[11;27H++* %\e[11;36H& &*&\e[11;42H+ @ #& *&.+ \e[11;56H#@\e[11;59H#+# #& . @& \e[11;75H*&#\e[11;79H#\e[11;81H+@\e[11;85H %#* \e[11;93H+*&\e[11;98H+\e[12;25H@..* @ . %\e[12;36H ##.+\e[12;42H&\e[12;44H%*@. +.& @\e[12;55H+@@+ @ &+\e[12;65H \e[12;67H&+ &\e[12;72H%&%..+\e[12;79H %#. &#\e[12;87H @\e[12;91H###\e[12;96H..\e[12;99H&\e[13;20H@*&&&.\e[13;27H&%@* & @+@ +% @* \e[13;45H# \e[13;48H.&\e[13;51H.#*%*@%* \e[13;61H+\e[13;63H +& %@@+ %%\e[13;75H#&\e[13;78H+%\e[13;82H .%# + ++*@\e[13;94H%\e[13;96H&@\e[13;100H#\e[14;21H%%+\e[14;25H+\e[14;27H*.@ ***\e[14;36H +@&***%*#+ . \e[14;52H+\e[14;55H%\e[14;57H \e[14;59H%\e[14;62H*@.*\e[14;67H@\e[14;69H @\e[14;72H+\e[14;74H& .@\e[14;81H& \e[14;84H# * \e[14;90H* \e[14;93H &&@\e[14;98H@\e[14;100H#\e[15;21H*%*\e[15;26H+*#@\e[15;32H@%@%* &\e[15;40H+*@++\e[15;46H .#+. # +&&@++#@%. @&+@&+& +%@# #.. &#%+\e[15;91H*%*\e[15;96H@.*@#+\e[16;19H*\e[16;21H@+\e[16;24H.\e[16;26H%&#.@&\e[16;33H*&+. #&+ \e[16;46H%@#++.*%%\e[16;57H*@.%% \e[16;64H+&&*\e[16;69H&#%\e[16;73H@@@\e[16;77H + @&@\e[16;85H&+ %& +&\e[16;94H.+\e[16;97H+.*\e[16;101H*\e[17;20H#&.\e[17;24H..\e[17;27H.%\e[17;30H * @.+ . + \e[17;45H#% + \e[17;51H** .\e[17;56H@.@\e[17;60H @#* \e[17;70H* +\e[17;76H & \e[17;86H%#\e[17;89H&\e[17;91H+ \e[17;95H#\e[17;98H+@&*\e[18;19H+\e[18;21H@@\e[18;24H*\e[18;26H %%*@ .%.\e[18;36H&##&.& ##\e[18;48H.. &&#% #% \e[18;63H@&%.. @\e[18;71H + .\e[18;78H %@*+# *\e[18;87H@@.+#*@ **%\e[18;99H%&#\e[19;20H%%&\e[19;27H.%+ *%#+ *&\e[19;40H& ++@@+#\e[19;49H +@ +%&\e[19;57H@\e[19;59H+\e[19;61H@ +**#%@\e[19;71H*\e[19;73H% .@\e[19;78H #\e[19;82H&%#\e[19;86H#% **##.&@%\e[19;98H#.%\e[20;20H.\e[20;23H@+\e[20;26H%\e[20;28H%*\e[20;32H+%+##\e[20;38H.* +\e[20;43H%& .%# \e[20;52H@\e[20;54H&#%\e[20;58H% # #%# @ +\e[20;71H#\e[20;75H&&+\e[20;79H.\e[20;82H***\e[20;86H%*% .* &\e[20;95H#@&\e[20;99H.#+\e[21;19H.#&\e[21;23H*&@% +@%*@+ \e[21;37H@%+\e[21;41H%\e[21;43H+@#\e[21;47H %\e[21;52H@++@#+ . &&#. * \e[21;70H**++\e[21;75H%. \e[21;79H&##&&# \e[21;87H&#.%.+%+&#\e[21;98H.#\e[21;101H.\e[22;21H#\e[22;23H@*\e[22;27H.&#@#%\e[22;34H %* . +# \e[22;47H%&*&\e[22;52H \e[22;54H* \e[22;60H#*.@+.\e[22;67H**& @&% \e[22;76H@*@#&*. \e[22;87H &#*.&\e[22;94H++&*&\e[22;100H*\e[23;20H&@%%&+#%\e[23;29H@\e[23;31H *%%&#+ \e[23;40H. \e[23;44H+*\e[23;48H+ . \e[23;54H *+@#*# @@*& & \e[23;71H++\e[23;74H *. \e[23;80H..\e[23;83H @.+@ *%\e[23;92H.\e[23;96H+*\e[24;22H.\e[24;24H#\e[24;26H@@@#% & \e[24;36H.\e[24;38H*+& @ \e[24;45H #%*#.\e[24;52H% \e[24;55H .@.+ %*#+@@ \e[24;70H %& #** *.@* \e[24;86H %%@&@%*.\e[24;97H&#\e[25;21H%#*\e[25;25H+#+++** . *@\e[25;39H+&#\e[25;43H&# \e[25;47H&% \e[25;51H.@+ .% *.\e[25;61H.+% & \e[25;68H # %\e[25;75H+\e[25;77H+ %+#**@+%.. \e[25;91H*#\e[25;94H%\e[25;97H.*\e[26;22H#.+\e[26;26H#*&&\e[26;31H @\e[26;36H++++%@\e[26;43H @+@&@+* +*\e[26;56H+\e[26;58H *.@\e[26;63H#@#\e[26;67H%% .%.%\e[26;75H .\e[26;78H%+* \e[26;83H * \e[26;88H %*\e[26;93H%\e[26;95H.@*#\e[27;23H&*%\e[27;27H&\e[27;29H%.@@@% # \e[27;39H*@@+%\e[27;46H& & # \e[27;53H +@ &\e[27;62H && @*\e[27;69H@\e[27;71H #+\e[27;75H * & *% .@@.\e[27;91H@\e[27;94H@\e[28;24H*#++%\e[28;30H.\e[28;32H+ \e[28;35H# .\e[28;39H@\e[28;41H@@@\e[28;45H. +.\e[28;50H@@.@\e[28;56H*&@ \e[28;61H %.%++@ * \e[28;72H@#.%..&.& &# .@%+&.#\e[28;93H#+%.\e[29;25H++@*@&.#\e[29;34H#+%%+%@\e[29;42H ++\e[29;50H+@#\e[29;54H #\e[29;57H# ##++ \e[29;69H & .\e[29;75H %+\e[29;80H#@+. &%\e[29;92H@.%.\e[30;26H+*@.&**\e[30;34H*\e[30;36H* %\e[30;40H.. *%\e[30;46H .\e[30;49H.+#. *\e[30;56H*.\e[30;61H..+\e[30;65H*&@ %& &\e[30;74H .\e[30;77H+\e[30;79H% \e[30;83H% &\e[30;91H#%\e[30;94H&\e[31;29H*&*\e[31;34H+%\e[31;37H.+\e[31;40H+#. \e[31;45H.\e[31;47H *#& . +&**** %&. +\e[31;69H..+ *& \e[31;78H + \e[31;83H+@#*##+\e[31;91H#@\e[32;31H@#.@\e[32;37H+\e[32;39H#&\e[32;42H % @.+**%. & \e[32;58H+ \e[32;62H*%&&\e[32;67H \e[32;70H*+ .@* *\e[32;79H \e[32;81H%\e[32;83H**\e[32;86H+**%#\e[33;33H*\e[33;35H#%\e[33;38H&\e[33;43H&\e[33;45H +*%@\e[33;51H*@#+ **. .#& &. .\e[33;71H#+@ @.#\e[33;79H&#.\e[33;85H@\e[33;88H%\e[?25l" + - delay: 21 + content: "\e[?25l\e[2;26H\e(B\e[m\e[97m\e[1m\e]8;;\e\\@+&\e[2;30H*+\e[2;34H \e[2;36H#\e[2;38H&. \e[2;42H #+**&\e[2;49H@%& &\e[2;55H +*\e[2;59H%\e[2;61H@ % @#@& *\e[2;72H +\e[2;75H&\e[2;77H@%&\e[2;82H \e[2;84H@#*\e[2;89H%@\e[2;92H.\e[2;94H.%\e[3;23H&\e[3;27H&\e[3;31H. &@#*%@ ##&@ &\e[3;47H.*&&.* + + \e[3;60H%& .&#\e[3;67H## +& # % +@ ... & &\e[3;90H#.#%*\e[3;97H.\e[4;21H.\e[4;24H#&\e[4;27H.\e[4;29H& .*@ %. @*\e[4;42H.+##. ##.\e[4;52H *+& %\e[4;61H #+ %. ##\e[4;72H . ## &\e[4;82H@ . \e[4;87H % @.@+\e[4;97H&.@\e[5;21H#%#\e[5;26H.\e[5;30H*\e[5;32H&#@@\e[5;37H * \e[5;42H*\e[5;44H%.##& .\e[5;52H+# * +%. *+\e[5;66H%.## # @+.# & % \e[5;84H#&@+*@\e[5;91H@@.%&\e[5;99H.&\e[6;20H%+\e[6;24H+\e[6;26H+* .@ *@++%\e[6;40H \e[6;42H+*.+%%+++\e[6;52H **% # %..@%#\e[6;67H*#%@\e[6;72H ..&\e[6;77H.\e[6;80H@\e[6;82H.@ \e[6;86H \e[6;90H \e[6;92H@%\e[6;95H#\e[6;97H.#%@+\e[7;18H.&%*&\e[7;25H%@&@\e[7;32H##\e[7;36H#\e[7;39H@&.+#+ \e[7;50H #+\e[7;56H+\e[7;58H..@ %* &#\e[7;70H&. & @ &@%+ # .\e[7;87H%%#%\e[7;92H+@& \e[7;97H.&&%%+\e[8;18H*#\e[8;24H&#\e[8;27H# #\e[8;31H%\e[8;34H*@ %*@ *#\e[8;45H@# + +\e[8;52H@+\e[8;55H &&. &\e[8;63H %\e[8;67H#&+* +\e[8;74H+\e[8;76H#%## .@ @# * @%@&\e[8;96H \e[8;98H*#@\e[8;102H&\e[9;17H*\e[9;19H+&%\e[9;23H.\e[9;25H.&#**%\e[9;32H #\e[9;35H. \e[9;39H%.@&.\e[9;45H @@.*+#\e[9;53H#\e[9;55H+&@&@& & + @\e[9;70H **# +.++#& . @@ % \e[9;93H .+\e[9;100H&\e[9;103H#+\e[10;16H&\e[10;18H&@#&%\e[10;24H&@+ *# #&\e[10;34H \e[10;36H+ \e[10;39H&\e[10;41H *++@ ## \e[10;51H *\e[10;54H%+%\e[10;58H* #@& &\e[10;68H.\e[10;71H&@@#\e[10;76H +\e[10;79H##&@&*\e[10;86H& @.\e[10;91H. \e[10;94H ##\e[10;98H .+\e[10;103H+\e[11;14H&.@&\e[11;20H*@\e[11;23H. &\e[11;27H @.+\e[11;34H+. # &.%&%%@\e[11;50H *@&\e[11;55H@ \e[11;59H &@&.@@%+\e[11;69H%# @\e[11;74H@&.@. @ #\e[11;84H###\e[11;89H&\e[11;91H&% @\e[11;98H@&*%\e[11;105H+.\e[12;14H.&\e[12;17H@.\e[12;21H#++@+ \e[12;28H&%*\e[12;32H .@%&\e[12;38H && %%+ \e[12;47H**@+ \e[12;53H % *& . \e[12;62H@.\e[12;65H@* \e[12;70H+&+* @+ +@ #\e[12;84H ++&.+*+.+\e[12;96H# \e[12;99H.&&+\e[12;104H@\e[12;106H@\e[13;14H*%.%## ..+%& +\e[13;30H%\e[13;32H .# &@ &\e[13;41H.+\e[13;45H +##*\e[13;51H \e[13;53H @.%@+\e[13;60H@\e[13;62H \e[13;64H \e[13;67H #%.* *# %& &#\e[13;83H&& \e[13;87H +& .*. %% \e[13;99H% %\e[13;103H@..&&\e[14;18H#@& + \e[14;27H@ &@%\e[14;33H+% * \e[14;39H @ *%@#\e[14;50H \e[14;55H #%@ &+#% \e[14;67H %+#\e[14;72H@..\e[14;77H@*& +.@\e[14;86H%+@+#\e[14;92H#%.@ % \e[14;100H \e[14;102H@#*@#\e[15;15H&\e[15;17H*..\e[15;21H+ &*% +\e[15;31H & & %\e[15;38H@+\e[15;41H.% \e[15;47H \e[15;49H &. *\e[15;57H%*\e[15;60H* *\e[15;64H *@ @*@& #* \e[15;82H%\e[15;84H .#.* +\e[15;92H*&+# &+#\e[15;101H \e[15;105H+\e[16;14H&.+%*%+* #%\e[16;26H.# \e[16;30H# \e[16;34H &@.\e[16;39H+* \e[16;43H&.%+\e[16;48H @#+@. *&.&#.&\e[16;64H*@ \e[16;68H* . \e[16;73H# & \e[16;78H%.\e[16;81H%%&@ *&*\e[16;91H&.+@.\e[16;97H&%%@%.\e[16;104H*.\e[17;12H##\e[17;15H.\e[17;17H&#* %\e[17;24H*+\e[17;27H *\e[17;30H@.\e[17;33H @\e[17;36H%%*%\e[17;42H*\e[17;45H &# @& #\e[17;54H@% \e[17;58H+@*# \e[17;65H@&*\e[17;69H +\e[17;72H#+\e[17;75H+.++\e[17;80H%\e[17;82H*\e[17;84H.+\e[17;87H&% @ \e[17;93H#\e[17;95H+\e[17;97H@%\e[17;100H \e[17;102H.#\e[17;106H.@*\e[18;12H@\e[18;14H@\e[18;16H.&\e[18;19H.\e[18;22H * \e[18;27H .## .+@ @%\e[18;40H \e[18;43H*@&\e[18;47H%*@. + +@+\e[18;60H+\e[18;63H&\e[18;65H @# #@**.\e[18;76H+%&&# &%\e[18;85H # .\e[18;96H * \e[18;101H \e[18;103H.@\e[18;106H%#\e[19;12H.+\e[19;15H*&\e[19;19H% &.+&+ ## @% \e[19;36H% *@ \e[19;45H %&\e[19;50H #\e[19;53H&&.\e[19;57H % \e[19;61H#\e[19;63H .\e[19;68H.%\e[19;71H#* ** \e[19;78H.\e[19;80H +\e[19;83H .#+*#+&&. \e[19;97H@ #..\e[19;103H*\e[19;105H@@*&\e[20;13H@@\e[20;16H@*\e[20;19H. %% @\e[20;26H@@ \e[20;32H* .&%# \e[20;41H & # \e[20;48H%@\e[20;51H+*&@ #\e[20;58H&* +\e[20;63H .\e[20;66H# \e[20;70H@\e[20;73H ** +\e[20;82H + * %*\e[20;90H# *## % *% **..%%\e[21;13H*.@.\e[21;19H *+#.&*#.#@ ..\e[21;35H *..&+ +\e[21;44H. +%\e[21;49H&\e[21;51H * %+\e[21;57H.& . &%& \e[21;69H.+..* . \e[21;78H* * + *&% % . %@*# #\e[21;102H@\e[21;105H&.\e[22;15H@#\e[22;18H+#\e[22;21H \e[22;23H* \e[22;26H# * *#+&&+#%\e[22;41H@*\e[22;45H*\e[22;48H*@@\e[22;52H#+&\e[22;57H#\e[22;59H+\e[22;61H%* \e[22;65H \e[22;67H *@. \e[22;74H* #+&@@ \e[22;84H*\e[22;86H@* \e[22;90H#* \e[22;94H# \e[22;100H#*\e[22;103H*\e[22;106H@&\e[23;13H*+++.\e[23;19H% **. *\e[23;29H \e[23;32H%.&+ \e[23;39H &#&+ %\e[23;47H.#.*\e[23;52H*\e[23;54H+@ \e[23;59H# @# %\e[23;67H* .@ \e[23;74H%%%.+ @++&+ #.\e[23;89H @\e[23;92H \e[23;95H* &\e[23;100H@%\e[23;105H%%&\e[24;17H*\e[24;19H.+\e[24;22H + %. &#&&\e[24;34H.\e[24;36H+ . \e[24;42H.% # # +@ \e[24;55H%@% \e[24;61H @+ .\e[24;69H*\e[24;72H*@\e[24;75H% \e[24;79H@&# %\e[24;85H%\e[24;87H+ \e[24;90H** *\e[24;95H & \e[24;100H.%\e[25;14H#\e[25;16H@+%\e[25;20H++*#\e[25;25H@\e[25;27H@ . @\e[25;34H .+\e[25;38H% +#\e[25;44H \e[25;47H#@\e[25;51H% *.@ & .+# +.\e[25;67H@\e[25;70H&\e[25;75H#.*\e[25;79H@ &+%. *+%*&.& &\e[25;97H+\e[25;99H&#.@\e[25;104H+%.\e[26;15H*&\e[26;21H@&+*\e[26;26H+ @.+\e[26;34H &\e[26;37H* .\e[26;45H \e[26;48H*+. \e[26;53H# #*+\e[26;59H &+% .@ \e[26;70H++ &.\e[26;77H&* @&&\e[26;86H+ \e[26;89H##&\e[26;93H \e[26;95H*%....#.\e[26;104H@\e[27;16H**\e[27;20H@\e[27;22H@\e[27;25H \e[27;27H*\e[27;29H**\e[27;32H& .\e[27;36H*@*. #\e[27;43H+\e[27;45H# . # & \e[27;54H+***.##\e[27;62H* #\e[27;66H.& ##\e[27;73H #\e[27;76H %+\e[27;80H *\e[27;83H &\e[27;86H%##&& \e[27;94H%+&#\e[27;100H&\e[27;103H#%\e[28;18H&@#%+\e[28;24H *@\e[28;28H \e[28;30H \e[28;32H.&#@#* .\e[28;41H * %#\e[28;47H & %. +@# *+\e[28;60H*& # @* % *\e[28;72H.@% @ &\e[28;82H@ \e[28;85H+%.&.++@%\e[28;95H *&@@&.#%\e[29;19H+%\e[29;22H.\e[29;25H#& .& *&\e[29;35H@& #. \e[29;42H@%%*. * % \e[29;54H.+\e[29;57H #\e[29;60H. %%\e[29;65H+\e[29;68H*%%@+&*\e[29;76H.. \e[29;80H@\e[29;82H* * &\e[29;92H##+%@\e[29;98H.\e[29;100H&.\e[30;19H.\e[30;23H#+% % \e[30;31H & % % @ .\e[30;48H# @& \e[30;54H%\e[30;56H.@& @ #&&++\e[30;69H #. \e[30;75H# \e[30;79H*@\e[30;82H + *.&\e[30;91H @*&&%.+.@\e[31;20H#**.*\e[31;26H..# \e[31;31H#@\e[31;34H #* + @\e[31;44H#+\e[31;47H+\e[31;50H \e[31;53H #**+&&&&. ##\e[31;67H \e[31;70H *% *\e[31;76H%.\e[31;79H ..& * % \e[31;90H&+ #\e[31;96H+\e[31;98H#@%\e[32;22H**%.&*\e[32;31H* %%\e[32;36H%&@. \e[32;42H%#+ \e[32;47H @% +&@\e[32;55H%\e[32;57H&&&\e[32;61H @#+\e[32;68H.\e[32;70H&&\e[32;73H+% \e[32;77H %@\e[32;81H@\e[32;83H&+\e[32;86H && \e[32;92H&\e[32;96H&*\e[32;99H.\e[33;23H@&\e[33;30H@#\e[33;33H . +\e[33;38H &\e[33;41H@# #% . %%#*.%# \e[33;60H @#+\e[33;66H# %%*@. #* +% +\e[33;85H \e[33;87H.#.&*&\e[33;94H#\e[33;96H&#\e[?25l" + - delay: 14 + content: "\e[?25l\e[2;2H\e(B\e[m\e[33m\e[1m\e]8;;\e\\ @ * &.+ * &+.& + # . * # .& +*+*. %% %#& & + * +@# .*% & %# *#.%*&%% # \e[3;2H &##&% #&%%& * % # + .+@.@ @ . & . #%.& **+&+ + *%+. %# &* # + +% + \e[4;2H . % .* & @# #+& .%% .+ %. + @**%# +%& @ % +#+ *@%+. * * @ @ # &# % % \e[5;2H && % @*% *&# + &#%@ ##*%+ @ .@+ * .*# + % & . %@ # % # % % & %@&@* *+ %. & #+&#& \e[6;2H &# .** @ %.% #+ * & * % +.* &+#@ %.@%** ** . #*& +% *.++@& %+. %& . # # &%@ +++**.. \e[7;2H %+ # *&& * ** +#* **** + %& & .+& @ *.@ +. #@ @+ +#*%@#*&& & * @%& +%+ & \e[8;2H %@% %& .+#@ &+ & ... . #@. %& *%%. ## +% % .+ & @ @& . *&* @* ## *@+ +@&#@ @ *.+ +& \e[9;2H *& &&+& %& . & &@+ .*+ @ #&. @ +&*%+ @ @*. . .# *&# +@*###&* &*& @ & + @ *%** # %. \e[10;2H . @+# % . @##*&%+# .# # % %+ .@ +% . +&&#* @@ % % *@ %+&%. @ + & %.& @. . %% #.. .& & \e[11;2H *. %% % & ++@*. * &%#.# & *# % & +%+% %@ . #% @@#@& * + . # .% @ # . @&.+ # ++ + % * \e[12;2H *& . +** * ** .% . +&+.+.&.++ +* #++.@ *.#& **% @#&@# #@ #+ # @*& * # %% ##*# % *& *##@ \e[13;2H + .@+ *# . @@. @ % * % & ## +&% @ # * @ * + *& @ @ % &* &++ # . .* @% .+ % & % \e[14;2H %* @ % %%%.# ++# @# &.&@% *+ @ %*@ %&#@& &@ %#* %* % &@ @.# +@+ # %*& % & % @#+ % \e[15;2H * # .. & #@ * .%& %* .* *# & *.%*&.@ ..& #* .#@ .. +% & %&%@% +*& . @.&&#&. + \e[16;2H .% &&# %+*@ %. @ ** * @#% &+ * &%% %%* & * ###+*+&%@ ##+ .& & . # % @@ %@* @ * @ &#@. \e[17;2H @ . %@*+* %+ & #... %&+. .@ +@@.%##*@* . % + *# &#&.% *+%+ % & %# * & @.@ * . * . + # # \e[18;2H #%++ #.& @. *#.#. + .&#%@ # +##* + + & % @%. + * *&@ %% **#+# & %* @ &@ &%.++. +#. . %& +% \e[19;2H %.+% + *@ % #% *% &* % * %*%# ### .%* %*&.# +* . @## .& . *%.@+#+#* *. .# +@ &.+ .+ + & #+ .% \e[20;2H +% # *# . #+ + % %+ * . %.** @ @% @+&&+ & * @ +% &@# *. ++#. @@# #%#@. & & \e[21;2H %@@@ *+ %@ * *%% # * @#@#% # #*%& &*@+ #&& @@ ** &+ *%& %% #. % . #&@@ #+ . & \e[22;2H ++& &* * ++% % .@ ##@*@## .#% .&+& @&% &+@& +@. . &* *.%* . ##@ #. ##@#** & % #*.. #&% #* @.%. \e[23;2H @ . # @* .&+ &+ @# @ @&.* +% * .*.. . .#*# *#+ @* +& %*+# % .@%.% + & %##* * \e[24;2H & +#& % + . +.& * &%+*+@& + %.+%+ @ %. #+ *. ++&& * .* # # &@% #& #% & %#+.# @% .* * @ #@@ \e[25;2H * &. %+#@*# & # +*& + %& &% % % .&#+ # .& + . * % % + # & * + &.#&& + %**@ * %. * \e[26;2H *. & &.#**+ #% # #@ @ + @ .*%@+@ . * @& @ *. #* &#+ * & @+# *+* . @ @ # \e[27;2H +@@#+ # *%&+@@.. .#% +%. .# * @% @& & * . ++ * &%*+ * +++ . &# # +@##& +#& &#+% # * \e[28;2H * % #& &## .& #%+ %% . @* . ** *@ @ .*&#+ &. %@ #*# &#+ *+& % @ #.& * @ % +@ %& \e[29;2H .@ % # @* & #.+.% &+ *# *@*% &% . +.@ &@ .# +&*. *& &# @@ % % # +%+& *% + #* @# %+ #* # \e[30;2H &@*.%&*&& * + @ * . @%# #&* & .+.@@ + %.@.& %* &@& &.&+++*@ & & *@*+&.#+ @ & \e[31;2H ### &%. * + + # * % + *%# @* %+ &@+ &@# @@% *& ## # &*++%# + .&++ %*& %* \e[32;2H + &* %# + * % + *& # #%+ @* & ..& * # #& @+ + % &@ * &# &+.# * %& @&&.* # # &@& \e[33;2H # # . & &+#.## + %*.%@.& * & %&*@# + *# +@& %++*@ % + .%.@+@ @ *+&% *.+ \e[?25l" + - delay: 16 + content: "\e[?25l\e[2;11H\e(B\e[m\e[33m\e[1m\e]8;;\e\\#\e[2;13H+\e[2;15H+.+\e[2;19H \e[2;21H%+@ .\e[2;27H%.\e[2;30H@* \e[2;35H \e[2;37H& .*\e[2;42H+\e[2;44H \e[2;46H \e[2;48H#%# & \e[2;57H \e[2;61H% .+%+ \e[2;69H \e[2;72H. @% \e[2;79H.\e[2;81H@ %\e[2;88H@.\e[2;92H *@+@ @&\e[2;103H+..#\e[2;108H&#\e[3;10H&%\e[3;13H+\e[3;17H.% \e[3;22H#\e[3;24H # \e[3;30H \e[3;33H.@.%%#\e[3;40H \e[3;44H+@ *\e[3;50H.\e[3;52H%\e[3;54H \e[3;56H.\e[3;62H%&*%&+&*\e[3;71H&@\e[3;74H#@+ \e[3;82H@ \e[3;87H.\e[3;89H \e[3;92H% . #\e[3;98H#\e[3;100H. @\e[3;104H.\e[3;106H.\e[3;109H&\e[4;14H@\e[4;17H+.\e[4;20H \e[4;22H*\e[4;24H&*\e[4;27H @ . \e[4;34H&&@.\e[4;39H%\e[4;41H%@% &+&\e[4;49H.@\e[4;52H. & #+* \e[4;63H&.\e[4;66H+* . % ..* %\e[4;80H& \e[4;83H \e[4;86H . +@ \e[4;93H*\e[4;96H+ &+\e[4;101H.\e[4;104H \e[4;106H&\e[4;108H*++\e[5;8H+\e[5;11H*\e[5;13H \e[5;16H ** @ \e[5;23H &+ \e[5;30H% *\e[5;34H+\e[5;36H +\e[5;39H \e[5;42H ++ &.%.\e[5;51H+ &*%\e[5;58H.++ * * & \e[5;70H@\e[5;73H*+\e[5;76H .\e[5;79H&#\e[5;82H + \e[5;86H*%#\e[5;90H% #\e[5;95H *+ \e[5;100H# \e[5;103H+*\e[5;106H+\e[6;7H@\e[6;9H&@% +*\e[6;16H%& # &%+\e[6;25H \e[6;27H \e[6;29H \e[6;33H \e[6;35H@+&#\e[6;40H % * \e[6;47H. &*# + . +& \e[6;62H %& @**%#@+ &+@ * .@ \e[6;86H*\e[6;88H*\e[6;90H.+\e[6;93H*%\e[6;98H.# \e[6;102H& \e[6;111H*#+\e[7;8H#+\e[7;11H & +* @.@ \e[7;25H &\e[7;30H \e[7;32H \e[7;34H# &.\e[7;41H \e[7;43H&. %\e[7;49H .\e[7;53H* @+\e[7;61H&#%*. &## %\e[7;73H@&\e[7;78H+%\e[7;81H @+@ #\e[7;88H %% @\e[7;94H %#\e[7;99H#\e[7;101H# \e[7;104H. #\e[7;108H@ \e[7;111H%\e[8;6H++\e[8;11H&\e[8;13H .@+.\e[8;20H#@@+\e[8;25H \e[8;27H* &\e[8;32H+ #%@&#\e[8;40H* \e[8;44H*\e[8;46H@ @ @ %.%%@%+\e[8;60H+\e[8;62H%\e[8;64H&#\e[8;67H @%&*\e[8;74H@. *+\e[8;80H \e[8;83H &\e[8;86H##\e[8;89H% \e[8;92H #\e[8;96H & . * %.\e[8;109H \e[8;112H#\e[8;114H&&\e[9;5H.%\e[9;9H#@#+\e[9;14H%.#+ \e[9;21H@\e[9;23H#@*+\e[9;28H.\e[9;30H # \e[9;34H&\e[9;36H&\e[9;38H & \e[9;43H \e[9;46H&.@ \e[9;51H **\e[9;55H \e[9;57H %%*\e[9;63H+ &@.+. \e[9;72H#\e[9;74H . .. &# \e[9;87H & @. \e[9;94H*\e[9;96H %\e[9;101H+*@\e[9;106H@*#\e[9;112H.&+\e[9;116H.\e[10;4H*\e[10;7H.% +\e[10;12H% & *\e[10;20H *@\e[10;24H++\e[10;27H % * .@\e[10;35H*+\e[10;38H% * @*\e[10;45H *@+ % .\e[10;54H+\e[10;56H% % @ \e[10;65H \e[10;67H .# %\e[10;73H #\e[10;79H&\e[10;81H .*&# \e[10;88H*\e[10;90H %&\e[10;95H++ @. \e[10;104H%\e[10;106H \e[10;109H+*.\e[10;114H%@+\e[11;9H# .### \e[11;17H# \e[11;20H .*& \e[11;26H@@&\e[11;30H % *%*#\e[11;40H..\e[11;44H*+*+*\e[11;51H&&%\e[11;55H#\e[11;57H@#* \e[11;62H \e[11;65H++\e[11;68H + \e[11;74H+ \e[11;78H*\e[11;80H+\e[11;82H \e[11;84H \e[11;87H \e[11;89H \e[11;91H &\e[11;94H .*.\e[11;99H \e[11;101H&%@\e[11;106H%\e[11;108H# \e[11;111H &+\e[11;117H%\e[12;3H#&.%\e[12;8H *@\e[12;12H% % \e[12;20H%+% \e[12;25H% ###\e[12;32H% & &+ .#% #\e[12;47H%#.\e[12;51H&\e[12;53H * %*&%#+# % .\e[12;69H@\e[12;71H. \e[12;75H &\e[12;78H%\e[12;80H \e[12;83H.\e[12;86H@\e[12;88H \e[12;90H@+& %&*#\e[12;100H&\e[12;102H ..\e[12;106H%+*..@&\e[12;115H.+\e[13;8H&% ## ** + %+#\e[13;25H& \e[13;28H \e[13;30H \e[13;32H& #\e[13;36H. \e[13;39H%+#. @&\e[13;47H&.+\e[13;51H+@ \e[13;56H +.\e[13;61H&*\e[13;67H@.\e[13;70H.@ \e[13;75H@\e[13;77H %#+&\e[13;83H &@ * +\e[13;92H+ +\e[13;96H#&\e[13;99H& +*\e[13;104H*@#*\e[13;109H &\e[13;112H %\e[13;116H.%\e[14;3H@*\e[14;6H&\e[14;8H +\e[14;12H \e[14;17H+++\e[14;22H& \e[14;28H*% &# @+#\e[14;38H# # .% + % & \e[14;54H % \e[14;61H&\e[14;65H. * +\e[14;71H \e[14;76H.\e[14;78H&\e[14;81H+ \e[14;84H*\e[14;86H&+\e[14;89H% &\e[14;94H@% @\e[14;100H&@\e[14;103H \e[14;106H \e[14;108H.+@@% \e[14;115H#\e[14;117H&\e[15;2H@+&\e[15;6H.#\e[15;9H &* \e[15;14H % %\e[15;19H \e[15;22H *+\e[15;27H#*%#\e[15;33H \e[15;37H.@@.%\e[15;43H&%\e[15;47H @% *\e[15;55H*.\e[15;59H # *\e[15;64H@\e[15;66H&+\e[15;70H \e[15;73H# \e[15;76H \e[15;81H&+\e[15;86H &+@\e[15;92H #+\e[15;96H%\e[15;98H#+&\e[15;102H*&* +\e[15;109H* +%\e[15;114H%\e[16;2H%\e[16;9H \e[16;12H*+ \e[16;16H @ ##+\e[16;23H %%\e[16;28H \e[16;32H+\e[16;34H+# .\e[16;41H*\e[16;44H&\e[16;46H&## *+.@ \e[16;58H *\e[16;61H \e[16;63H &*. . \e[16;73H%+\e[16;76H&& #%%@ \e[16;87H . + \e[16;93H #@.+. @## \e[16;106H#\e[16;108H \e[16;110H+* +\e[16;117H&+\e[17;3H+\e[17;5H@\e[17;7H++#\e[17;11H % .#@&#@@&%+ *\e[17;28H+ + \e[17;35H+\e[17;37H &@&+ \e[17;48H# \e[17;51H+*\e[17;54H.\e[17;56H@ \e[17;60H& + ** &#@.% \e[17;76H*\e[17;79H+\e[17;81H%@\e[17;84H@ \e[17;87H# & @\e[17;93H* \e[17;97H %**&**\e[17;106H@\e[17;11" + - delay: 6 + content: "0H \e[17;112H.#\e[17;116H*\e[17;118H#\e[18;2H#\e[18;5H**& @ #+@@.** @@ ** \e[18;27H \e[18;29H % +\e[18;36H \e[18;38H . \e[18;43H@.\e[18;46H%.@ + + *\e[18;58H \e[18;60H \e[18;62H# * \e[18;67H &\e[18;71H % .\e[18;76H#+# %.\e[18;83H% +#\e[18;88H \e[18;92H##& \e[18;99H+\e[18;101H ..\e[18;105H@ \e[18;108H%\e[18;110H&&\e[18;114H&.\e[18;117H#\e[19;2H*#+\e[19;6H* \e[19;9H +& \e[19;15H *.& \e[19;21H @@ \e[19;29H \e[19;31H \e[19;33H@\e[19;36H +# +\e[19;47H%& &\e[19;53H* %+#\e[19;61H# @ \e[19;66H &\e[19;71H +*\e[19;76H@ \e[19;79H *&++. & \e[19;92H%&\e[19;95H# #*\e[19;100H &#*. % \e[19;111H&&@\e[19;116H.*@\e[20;2H.&%@&\e[20;9H @&\e[20;13H#\e[20;15H.@ &.+\e[20;22H&%.% \e[20;28H* \e[20;31H++\e[20;34H#\e[20;36H +\e[20;40H \e[20;42H \e[20;45H& +\e[20;49H%\e[20;51H+& +\e[20;57H. * \e[20;64H*\e[20;66H %.%%# \e[20;76H%\e[20;78H@ .\e[20;82H+ \e[20;86H+\e[20;88H# \e[20;91H@@& \e[20;98H \e[20;100H.@&\e[20;104H#@ &* \e[20;111H \e[20;113H*#\e[20;117H*\e[21;6H*\e[21;8H&\e[21;10H. *#& @\e[21;20H \e[21;23H%&*+&+#@ # \e[21;36H@@\e[21;39H%* . \e[21;45H \e[21;47H.\e[21;49H**\e[21;52H@# .#+\e[21;60H + @+ \e[21;67H&@\e[21;72H \e[21;75H+* \e[21;80H% *\e[21;87H \e[21;91H \e[21;94H@\e[21;96H&&*@ +* \e[21;106H \e[21;110H @ +*%@\e[22;3H@#*\e[22;7H \e[22;11H \e[22;14H+\e[22;16H++\e[22;19H#. & \e[22;26H@ **&& %@#*+\e[22;39H@ . .\e[22;49H& # # \e[22;58H \e[22;60H#*\e[22;64H .\e[22;67H * @#+ \e[22;75H %\e[22;79H +\e[22;84H&\e[22;86H@%+%\e[22;91H \e[22;93H .&\e[22;97H . #\e[22;103H %@ \e[22;110H+& \e[22;117H+\e[23;3H@\e[23;6H#++ #%\e[23;14H \e[23;16H#.. %\e[23;22H \e[23;24H*\e[23;26H%+\e[23;29H #& %% + @\e[23;42H% &%@\e[23;50H#\e[23;52H# @\e[23;59H+#&%+ \e[23;69H %..\e[23;74H \e[23;77H *\e[23;80H \e[23;85H%\e[23;87H*\e[23;89H %&# \e[23;96H%\e[23;99H \e[23;101H&*+ %\e[23;108H+ \e[23;112H.%\e[23;116H@\e[24;4H@\e[24;6H@*#\e[24;10H# *\e[24;15H.@ \e[24;19H+\e[24;22H \e[24;24H%\e[24;26H#+*++\e[24;32H \e[24;34H \e[24;38H \e[24;40H@@@.\e[24;45H& \e[24;51H ++ @\e[24;57H* \e[24;60H. #* \e[24;68H @\e[24;71H+@\e[24;74H \e[24;76H%\e[24;78H \e[24;82H. &&\e[24;89H**+* *+.\e[24;99H%+\e[24;102H&#\e[24;105H@%+%*+.&\e[24;115H&\e[25;5H%%.\e[25;9H%&\e[25;12H*+@\e[25;17H@ *% @+ # %" + - delay: 5 + content: "#.\e[25;33H \e[25;35H@ %\e[25;40H \e[25;42H.@\e[25;46H +&\e[25;51H+\e[25;53H**\e[25;56H @\e[25;59H.#*\e[25;63H# \e[25;66H %@\e[25;70H%\e[25;72H \e[25;74H#&+*\e[25;79H*# \e[25;83H * \e[25;88H *%\e[25;97H&\e[25;99H. %\e[25;103H++&*+%@\e[25;111H \e[25;114H#+%#\e[26;4H#\e[26;6H.+*+ @#@% @%& #@\e[26;23H \e[26;26H # #@\e[26;33H*\e[26;36H@#\e[26;39H%+ \e[26;45H \e[26;47H+* &+\e[26;53H%\e[26;56H*\e[26;59H@ \e[26;63H \e[26;66H*\e[26;69H &\e[26;72H@ &\e[26;77H#*# #&\e[26;86H@+ \e[26;92H &\e[26;98H #%\e[26;102H#\e[26;104H \e[26;106H \e[26;108H.\e[26;111H \e[26;113H*\e[26;116H#\e[27;4H%\e[27;10H*.+ .\e[27;16H.% ##&\e[27;28H \e[27;31H &\e[27;35H@ \e[27;38H&\e[27;40H&&++ %\e[27;47H* #+& @. *\e[27;59H &*+#\e[27;65H +\e[27;68H \e[27;70H* %\e[27;74H&** \e[27;79H* * \e[27;86H*. %* %\e[27;94H * \e[27;100H & .\e[27;107H@& \e[27;111H.*+&*\e[28;5H&*&\e[28;9H@#++\e[28;15H* #\e[28;19H *\e[28;23H \e[28;27H@ +.\e[28;33H##\e[28;36H%++&&\e[28;43H+%& @.#.% \e[28;54H%* @\e[28;62H ## \e[28;69H \e[28;71H@*#% %%& \e[28;82H# \e[28;86H &&%%\e[28;93H+\e[28;96H ##%%%#\e[28;105H &\e[28;108H* \e[28;114H+\e[29;" + - delay: 6 + content: "8H.&#\e[29;12H+\e[29;14H +\e[29;17H+ \e[29;21H%\e[29;23H +% \e[29;29H #\e[29;33H%*& .&**+\e[29;44H%%&\e[29;48H+ & &*.\e[29;56H \e[29;59H@&.% %.\e[29;68H@+\e[29;72H*++#\e[29;77H@#&& \e[29;83H # . &.\e[29;92H+@&@ & \e[29;102H @ +\e[29;109H @\e[29;114H*\e[30;7H@%*#% * # @+\e[30;24H#\e[30;26H#\e[30;28H&@.\e[30;32H*% #\e[30;37H \e[30;39H*\e[30;41H. +\e[30;48H@% ++\e[30;55H# #\e[30;61H#\e[30;63H@+# %\e[30;71H \e[30;73H&\e[30;75H+ %# \e[30;82H% %#.\e[30;89H*\e[30;91H% +@ # # %\e[30;103H.#\e[30;106H&% +\e[30;113H*\e[31;9H*+*# &&\e[31;20H@ #\e[31;25H.@++\e[31;30H@\e[31;33H \e[31;36H \e[31;39H+ \e[31;42H %+.%@ \e[31;51H #+\e[31;55H &&\e[31;59H \e[31;63H . \e[31;71H.%\e[31;75H \e[31;81H. %%\e[31;87H %%* *+ \e[31;97H* #\e[31;102H &%% \e[31;110H++\e[32;11H@\e[32;14H+.%\e[32;20H @#+\e[32;26H.\e[32;28H.\e[32;30H *\e[32;33H \e[32;35H@\e[32;37H . ++\e[32;44H *\e[32;47H @@.\e[32;53H#* \e[32;57H + . .. &\e[32;68H@\e[32;70H \e[32;72H+\e[32;74H# *\e[32;78H %\e[32;82H \e[32;85H*% \e[32;89H&+\e[32;92H. .\e[32;98H&\e[32;100H \e[32;103H# #*.+\e[32;110H@\e[33;10H#%&\e[33;15H#\e[33;1" + - delay: 8 + content: "8H%*\e[33;22H%+\e[33;25H \e[33;27H @+ *\e[33;34H# .@\e[33;39H % &*\e[33;47H@ \e[33;50H+& #&\e[33;56H#@ *%.+%#&\e[33;68H. @.& @ \e[33;78H@+%.\e[33;83H. * ***\e[33;97H %\e[33;100H++& #\e[33;106H+%\e[33;109H&@\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;5H\e(B\e[m\e[33m\e[1m\e]8;;\e\\.\e[2;7H#\e[2;10H.@@&# * +\e[2;20H*.&\e[2;25H+\e[2;27H&\e[2;30H* + \e[2;35H%. \e[2;39H+ \e[2;42H%\e[2;44H.\e[2;48H.@ \e[2;52H%*%\e[2;57H&\e[2;60H %@ *\e[2;70H.\e[2;72H#@#\e[2;77H& &+\e[2;83H+%\e[2;86H@\e[2;89H#\e[2;94H @ *# @\e[2;103H @ \e[2;108H% \e[2;111H*\e[2;113H+*\e[3;8H.## \e[3;13H**\e[3;17H +.@.*\e[3;26H@\e[3;28H@\e[3;30H@%+ + \e[3;42H## \e[3;48H \e[3;50H*@ @*\e[3;56H &@\e[3;62H @.\e[3;68H*+\e[3;71H #.\e[3;79H%\e[3;81H+ \e[3;85H&\e[3;87H&++\e[3;92H \e[3;94H +@\e[3;98H *&+ \e[3;104H \e[3;106H&&\e[3;109H \e[3;111H@\e[3;113H#\e[3;115H*&\e[4;8H.\e[4;10H+\e[4;12H@\e[4;14H \e[4;17H.*\e[4;20H&& \e[4;27H#.*\e[4;31H +@@% \e[4;39H@\e[4;41H...\e[4;45H \e[4;49H% * \e[4;54H** %.# . %%@\e[4;69H @ + &\e[4;78H @%+&#\e[4;87H+\e[4;89H #\e[4;93H&\e[4;95H@ \e[4;98H@ \e[4;101H #@\e[4;105H* \e[4;108H@ \e[5;5H+\e[5;7H. \e[5;11H@\e[5;17H.@\e[5;20H *+\e[5;26H %.\e[5;30H@\e[5;32H +\e[5;37H. . .# &\e[5;46H #\e[5;49H @#\e[5;53H + @# & \e[5;62H#%+&# %#\e[5;71H..& \e[5;76H%&\e[5;79H@ #\e[5;83H \e[5;86H& \e[5;93H \e[5;96H ##@*\e[5;103H +\e[5;106H #%%%@\e[5;113H#+@\e[5;117H..\e[6;2H#+@%\e[6;7H &# \e[6;13H \e[6;15H&@+\e[6;22H@.\e[6;26H @\e[6;29H#%\e[6;35H \e[6;37H \e[6;41H & \e[6;45H%*@@@& &&\e[6;55H#. @\e[6;63H #%\e[6;68H& %\e[6;75H. %\e[6;80H.\e[6;82H # \e[6;88H & * \e[6;96H % \e[6;101H+ \e[6;104H+&.%@%& \e[6;116H%\e[7;2H@@#&.\e[7;8H+ +\e[7;12H* \e[7;15H%@& # ..@*#*#\e[7;29H#& \e[7;34H @*\e[7;38H \e[7;41H.@ \e[7;46H* *&#.\e[7;53H \e[7;55H..\e[7;58H+&\e[7;61H &\e[7;65H & @\e[7;73H *\e[7;77H%% *\e[7;83H \e[7;86H%\e[7;88H@ \e[7;92H+%\e[7;95H +\e[7;99H@*\e[7;102H++ .& \e[7;110H* \e[7;113H.&\e[7;116H#\e[8;6H +@. \e[8;15H& & \e[8;20H* \e[8;26H \e[8;29H.%\e[8;32H \e[8;34H @ +%\e[8;40H@\e[8;42H*\e[8;44H *&. \e[8;50H \e[8;52H# && + .+\e[8;64H %.\e[8;68H.% ##+ \e[8;77H@ \e[8;80H%\e[8;82H#& @*@\e[8;89H \e[8;93H .\e[8;96H%\e[8;99H * *%\e[8;105H \e[8;108H \e[8;112H \e[8;114H% \e[8;117H#\e[9;2H&\e[9;4H@+.@*& *# \e[9;17H@\e[9;19H#\e[9;21H % # &*+\e[9;30H. \e[9;34H .+%\e[9;39H.+\e[9;44H + \e[9;50H \e[9;52H% \e[9;55H@%\e[9;59H + \e[9;63H \e[9;65H ** \e[9;71H.%%@ * *\e[9;80H@***+ \e[9;87H.+\e[9;90H%\e[9;92H*+.\e[9;97H*\e[9;99H \e[9;101H& \e[9;104H+\e[9;1" + - delay: 6 + content: "06H #\e[9;112H \e[9;117H.\e[10;2H@. *\e[10;7H . \e[10;12H@##+#% *\e[10;21H \e[10;24H. +\e[10;28H \e[10;30H@\e[10;32H . % \e[10;40H \e[10;42H \e[10;45H@ @\e[10;50H.*#\e[10;55H# \e[10;58H*@\e[10;61H*\e[10;64H&\e[10;67H@\e[10;69H +.&.%\e[10;76H@ \e[10;79H \e[10;81H* # \e[10;87H% \e[10;91H \e[10;95H* \e[10;98H@ \e[10;102H%+ \e[10;108H@\e[10;110H #\e[10;113H+@ \e[10;118H&\e[11;3H@\e[11;6H#\e[11;8H& \e[11;11H &+ \e[11;16H%+#\e[11;21H&. \e[11;26H* \e[11;31H* %+& .%&&% . +#.@& # # \e[11;57H.+%&\e[11;63H+\e[11;65H # \e[11;69H \e[11;71H.%\e[11;74H \e[11;76H@%+* \e[11;82H&\e[11;84H.#\e[11;87H@%\e[11;90H+* +. +*#\e[11;101H &+&.+\e[11;108H&\e[11;111H%* *\e[11;117H##\e[12;2H@. #@ %@ \e[12;14H@\e[12;18H@* \e[12;25H \e[12;27H.@+ * \e[12;35H \e[12;37H * \e[12;44H*\e[12;47H +#&.\e[12;54H@%\e[12;57H % . %% \e[12;67H \e[12;70H. \e[12;73H.\e[12;75H+ \e[12;78H*\e[12;81H%@##% .\e[12;89H.\e[12;91H \e[12;94H& %\e[12;98H*\e[12;100H%%\e[12;103H@ \e[12;107H * @\e[12;115H*&\e[12;118H%\e[13;5H*+\e[13;8H &# \e[13;16H% \e[13;19H @ *\e[13;25H \e[13;27H#\e[13;29H@\e[13;31H% * \e[13;36H#@\e[13;39H + \e[" + - delay: 11 + content: "13;44H&#+ % &* \e[13;54H*\e[13;57H \e[13;59H \e[13;61H \e[13;64H#%+\e[13;68H \e[13;70H# @%\e[13;75H#.\e[13;78H % .\e[13;85H %\e[13;88H#\e[13;92H \e[13;94H \e[13;96H ..#@ \e[13;104H+#\e[13;107H \e[13;109H. \e[13;113H %@@@*\e[14;3H +\e[14;6H *\e[14;10H#\e[14;12H%*\e[14;15H+* *& \e[14;22H%\e[14;24H.&%\e[14;28H \e[14;31H# @+\e[14;36H & \e[14;40H.%\e[14;43H* +\e[14;47H*& # %\e[14;55H@\e[14;57H&*.% @**+#@\e[14;69H +\e[14;72H#\e[14;74H+ *\e[14;78H%\e[14;81H*\e[14;83H@# #%\e[14;89H+## \e[14;94H+\e[14;96H&@ +@ @\e[14;104H&.\e[14;107H& * *\e[14;113H%&%+ @\e[15;2H& && *\e[15;10H &%\e[15;14H* +#\e[15;23H%# \e[15;27H .*\e[15;31H##\e[15;34H+\e[15;37H+.. *@@&.\e[15;47H&# *\e[15;53H \e[15;56H%#&\e[15;60H \e[15;62H *+\e[15;66H %\e[15;69H \e[15;71H#+@\e[15;75H*\e[15;77H@#\e[15;81H# \e[15;87H* \e[15;93H \e[15;96H \e[15;98H # \e[15;102H.+ %\e[15;107H %+%#\e[15;114H&.#\e[16;2H %\e[16;7H@\e[16;9H@\e[16;11H# \e[16;15H%\e[16;17H \e[16;19H \e[16;23H%\e[16;25H*\e[16;29H#\e[16;32H*+% #% @ .&\e[16;46H@ * @#+\e[16;55H@ %\e[16;59H%\e[16;62H+\e[16;64H %#@@\e[16;71H&#* \e[16;77H &. @++*\e[16;88H+\e[16;90H \e[16;93H* *& +% @.%\e[16;110H .@\e[16;114H#\e[16;116H+ \e[17;3H \e[17;5H \e[17;7H*# \e[17;13H*% % @ * &#% @&@@++&@.\e[17;39H .\e[17;47H% #.%.#&\e[17;56H \e[17;60H \e[17;64H \e[17;66H %*.\e[17;71H& +\e[17;76H @ &@ .&\e[17;86H% &+\e[17;91H#\e[17;93H+\e[17;95H#+\e[17;98H@ # @\e[17;105H. #&#\e[17;112H %+\e[17;116H@\e[17;118H+\e[18;2H \e[18;5H%% \e[18;9H*\e[18;11H # & @ #. +%\e[18;26H#\e[18;29H#\e[18;31H #\e[18;34H.@\e[18;38H@% \e[18;42H% \e[18;46H & \e[18;50H @*&&@ \e[18;62H \e[18;64H \e[18;67H**% * \e[18;74H & &\e[18;80H \e[18;83H \e[18;85H &@++ @ \e[18;96H@ \e[18;99H \e[18;102H @& \e[18;107H&@\e[18;110H @\e[18;114H@ #+\e[19;2H & * \e[19;8H \e[19;10H +\e[19;13H#\e[19;16H %\e[19;19H*\e[19;22H* \e[19;26H*\e[19;28H@\e[19;30H.\e[19;33H%\e[19;37H*. \e[19;43H \e[19;47H+ # \e[19;53H \e[19;56H.+ \e[19;61H \e[19;63H&*%@\e[19;69H \e[19;71H#\e[19;73H*#% \e[19;78H \e[19;80H@ *% &\e[19;88H \e[19;92H *+%& .@\e[19;101H +\e[19;104H # *\e[19;109H &*\e[19;113H+*\e[19;116H *\e[20;2H . +%\e[20;8H * \e[20;15H .+**\e[20;22H +#\e[20;28H \e[20;31H * %*\e[20;38H@*%.\e[20;45H+\e[20;47H%\e[20;49H \e[20;51H&" + - delay: 6 + content: " \e[20;54H@ \e[20;57H*\e[20;59H+%\e[20;62H+@ \e[20;66H@ *+ .&\e[20;76H % \e[20;80H \e[20;82H*%&@ & \e[20;91H .#@&@\e[20;99H&% # # #\e[20;112H@ %#+ *\e[21;2H.@#& \e[21;8H \e[21;10H +\e[21;13H+ \e[21;18H #\e[21;23H & *\e[21;32H. &@ &\e[21;40H \e[21;47H &+%+*\e[21;56H &\e[21;61H%\e[21;63H %\e[21;67H%%\e[21;71H%\e[21;73H## %%\e[21;80H .\e[21;83H \e[21;85H+\e[21;87H+\e[21;90H*#\e[21;94H \e[21;97H#@&* @\e[21;104H%%.&##* &@ & #\e[22;3H&@@\e[22;8H% \e[22;11H%\e[22;13H+&&@%\e[22;19H \e[22;22H@\e[22;24H@% .& % + \e[22;35H&#%\e[22;39H %#&*@*\e[22;47H \e[22;49H %@ \e[22;54H&\e[22;56H+#% &\e[22;65H \e[22;68H @% #\e[22;76H@ @\e[22;80H#@\e[22;83H+ @ @\e[22;89H.#\e[22;94H \e[22;98H#\e[22;100H+ \e[22;103H#+&* .& #\e[22;115H@\e[22;117H*@\e[23;2H+ \e[23;5H&& @\e[23;10H \e[23;16H @@\e[23;20H.#@ . \e[23;30H #\e[23;33H &#\e[23;38H+\e[23;40H \e[23;42H &\e[23;45H **#.+\e[23;52H \e[23;55H& \e[23;59H*@+ &. \e[23;68H+\e[23;70H#& \e[23;77H@%+\e[23;81H%.\e[23;86H+ \e[23;90H@\e[23;92H \e[23;94H&\e[23;96H+\e[23;98H#&\e[23;101H \e[23;105H@\e[23;108H \e[23;111H%+ *%.@\e[24;4H \e[24;6H. %\e[24;10H+*+ @& #\e[" + - delay: 5 + content: "24;19H \e[24;21H#.% \e[24;26H* *@%*@\e[24;36H.#+. \e[24;45H \e[24;47H* \e[24;51H+ \e[24;55H \e[24;57H +* \e[24;62H *\e[24;66H*\e[24;69H + \e[24;76H.\e[24;82H +\e[24;85H @ . @\e[24;93H& . \e[24;99H#** %\e[24;105H. * * %+\e[24;115H %\e[25;5H @ %##. \e[25;17H#\e[25;20H.\e[25;23H .\e[25;26H \e[25;28H* .\e[25;33H@@+% @#*& \e[25;47H & #+++\e[25;57H*& \e[25;63H #@\e[25;67H@ + \e[25;72H&. **%* \e[25;82H%\e[25;84H %\e[25;87H+\e[25;89H.\e[25;92H%@+\e[25;97H%%&&# *@+# &&\e[25;112H*.&%+%#\e[26;3H& \e[26;6H \e[26;8H& *.* .\e[26;16H+\e[26;18H \e[26;20H ++&\e[26;26H. \e[26;30H \e[26;33H \e[26;35H& %# %\e[26;43H+@\e[26;46H % \e[26;50H*@+&%\e[26;56H \e[26;58H&\e[26;61H#\e[26;63H#\e[26;66H \e[26;68H+\e[26;70H \e[26;72H#%% \e[26;77H \e[26;79H*\e[26;82H \e[26;86H* \e[26;94H% # @.@# .\e[26;105H#*@\e[26;109H+\e[26;113H&\e[26;116H.\e[27;3H. \e[27;6H.\e[27;8H+\e[27;10H.& . + *\e[27;19H@&\e[27;22H@ *& \e[27;30H*@\e[27;33H \e[27;35H #\e[27;38H.** *\e[27;45H.\e[27;47H+\e[27;49H .\e[27;53H+&. \e[27;59H%\e[27;61H \e[27;66H \e[27;68H#+ \e[27;72H @\e[27;79H *% +#\e[27;86H%%\e[27;89H . +\e[27;95H@" + - delay: 11 + content: "%\e[27;99H#\e[27;101H% \e[27;104H%+\e[27;107H %+ &&#+\e[28;2H#\e[28;4H#@ +& \e[28;14H + +&\e[28;21H&\e[28;23H.\e[28;27H \e[28;30H &% \e[28;37H@ \e[28;43H %+\e[28;48H@ *##\e[28;54H +#@.\e[28;60H *#*& @+%\e[28;70H# % .*\e[28;77H \e[28;82H@\e[28;84H@\e[28;86H&+% %\e[28;93H*&.\e[28;97H # &\e[28;104H#\e[28;106H@\e[28;108H *\e[28;111H&\e[28;114H \e[28;116H%\e[29;6H%. . % #\e[29;15H % \e[29;19H&#.@%\e[29;25H \e[29;28H@@@%+& \e[29;38H% % @ \e[29;48H.@@@ + \e[29;56H@ \e[29;59H&* \e[29;64H %@* %%\e[29;72H @\e[29;77H + +*\e[29;84H+\e[29;87H \e[29;89H ** \e[29;96H*\e[29;98H &\e[29;102H+.\e[29;105H.\e[29;107H \e[29;109H# .\e[29;114H.@*\e[30;2H%\e[30;4H@*\e[30;7H& # \e[30;15H *\e[30;18H+ \e[30;21H+\e[30;24H . # \e[30;32H@@\e[30;35H+%\e[30;38H& * \e[30;43H&\e[30;45H#\e[30;47H%&\e[30;52H .\e[30;55H #.\e[30;59H@\e[30;61H &# %+\e[30;69H *%&.*#\e[30;78H \e[30;81H *\e[30;84H @%#\e[30;89H \e[30;91H \e[30;93H# % \e[30;98H \e[30;100H. \e[30;103H +\e[30;106H* % .\e[30;112H+.\e[31;2H#\e[31;4H*@@\e[31;9H . *\e[31;14H#@ ***\e[31;22H& \e[31;25H \e[31;27H \e[31;30H &\e[31;34H*\e[31;36H&\e[31;38H.%." + - delay: 6 + content: "\e[31;43H . *\e[31;49H.*\e[31;52H+@\e[31;56H +\e[31;66H %\e[31;70H& &\e[31;76H&\e[31;78H... @ .\e[31;89H \e[31;93H# %& \e[31;100H \e[31;103H \e[31;107H%.+\e[31;111H #%#\e[31;116H.\e[31;118H+\e[32;3H%%\e[32;7H*\e[32;9H.@ .& \e[32;16H+\e[32;18H++\e[32;21H# #\e[32;28H \e[32;30H+\e[32;33H*\e[32;35H+\e[32;37H+@#\e[32;41H # \e[32;45H%\e[32;47H+@\e[32;50H #% &\e[32;58H \e[32;60H \e[32;62H+ \e[32;68H \e[32;70H*@*% # #\e[32;79H#@+%+# \e[32;89H##\e[32;92H@\e[32;94H@.&\e[32;99H#&\e[32;103H \e[32;105H %% \e[32;110H*\e[32;114H&\e[32;116H#+\e[33;5H#+\e[33;10H . %\e[33;15H++\e[33;18H @+. \e[33;25H.@#. \e[33;31H% # \e[33;37H& \e[33;41H#\e[33;43H% \e[33;47H&\e[33;50H% \e[33;53H+ \e[33;61H.* *+@% \e[33;70H+* \e[33;75H \e[33;78H @ % &#&\e[33;88H&\e[33;91H% %.\e[33;96H@+ * # ..\e[33;106H &\e[33;109H \e[33;113H@\e[33;115H#\e[?25l\e[?25l\e[2;2H\e(B\e[m\e[33m\e[1m\e]8;;\e\\.+\e[2;5H@\e[2;7H@\e[2;9H# #&+%@ \e[2;18H \e[2;20H@ ..\e[2;27H*#\e[2;30H ##\e[2;35H &@&.\e[2;41H& .&\e[2;48H *\e[2;52H* %\e[2;58H+\e[2;62H \e[2;67H+@\e[2;70H*\e[2;72H * \e[2;77H \e[2;80H & \e[2;86H.\e[2;88H *\e[2;93H.. \e[2;98H \e[2;100H# " + - delay: 5 + content: "\e[2;105H \e[2;107H**&\e[2;111H \e[2;113H. \e[3;3H*+**% \e[3;13H@ \e[3;18H #+@ \e[3;24H*\e[3;26H \e[3;28H*\e[3;31H #\e[3;34H &\e[3;42H +@%\e[3;50H +%. \e[3;56H& \e[3;61H&&\e[3;65H \e[3;73H@.@&\e[3;78H* \e[3;83H*+ \e[3;87H@#@#\e[3;94H%.+\e[3;99H \e[3;105H%+ \e[3;113H \e[3;115H# \e[3;118H@\e[4;2H@\e[4;4H+*\e[4;8H+* @#\e[4;14H+\e[4;17H# \e[4;20H% \e[4;24H.\e[4;26H% .\e[4;32H \e[4;34H.&\e[4;38H+ \e[4;41H*@ +\e[4;49H \e[4;51H .\e[4;54H \e[4;58H @+% + @+&&\e[4;70H*% #\e[4;75H #\e[4;78H+*+ \e[4;83H*#*\e[4;87H \e[4;90H% \e[4;93H*% @\e[4;98H \e[4;100H+.* \e[4;105H@\e[4;107H@*.\e[4;112H%\e[4;114H#\e[4;116H@*\e[5;5H*\e[5;7H+\e[5;11H \e[5;17H \e[5;20H% %##+@+@ \e[5;35H@\e[5;37H & \e[5;41H@@# +@\e[5;48H @ &\e[5;54H % *\e[5;61H%&. +* *# \e[5;74H.\e[5;76H#.%%\e[5;81H \e[5;84H.\e[5;86H%\e[5;89H.\e[5;92H*%\e[5;98H+ \e[5;105H@\e[5;107H% @+*+\e[5;114H #\e[5;117H %\e[6;2H \e[6;7H+ \e[6;14H @+ @ & \e[6;23H%\e[6;26H+ \e[6;30H@\e[6;34H*\e[6;36H%@&\e[6;41H.*\e[6;44H.+ * \e[6;52H.#\e[6;55H& %*#%\e[6;63H*% \e[6;67H \e[6;73H%\e[6;75H \e[6;77H \e[6;80H %\e[6;84H&\e[6;87H@# .# \e[6;94H&*& \e[6;100H#.\e[6;103H% " + - delay: 8 + content: "+ \e[6;109H .#\e[6;114H#+.@\e[7;2H& \e[7;5H \e[7;8H#\e[7;10H.%+\e[7;15H@ +\e[7;19H \e[7;22H *\e[7;29H \e[7;34H@ \e[7;39H+\e[7;41H *\e[7;46H % &\e[7;55H +. &%\e[7;63H \e[7;66H *&. \e[7;73H*&#\e[7;77H \e[7;80H@+ \e[7;84H \e[7;86H %\e[7;92H* \e[7;95H& \e[7;98H@ \e[7;102H \e[7;105H \e[7;109H. .\e[7;113H \e[7;116H *\e[8;3H*\e[8;6H*\e[8;8H \e[8;15H \e[8;17H \e[8;19H* @%@%.%\e[8;28H&&\e[8;31H&@\e[8;35H.\e[8;37H# .%# \e[8;44H+ @#\e[8;52H+*&@@\e[8;59H \e[8;61H \e[8;65H. \e[8;68H% ++ \e[8;75H&\e[8;77H%\e[8;80H \e[8;82H *\e[8;85H &\e[8;91H#\e[8;93H%%\e[8;96H&\e[8;99H+\e[8;102H% \e[8;105H%*\e[8;108H*\e[8;111H&@\e[8;114H &\e[8;117H &\e[9;2H & %% \e[9;11H +\e[9;14H@ %*\e[9;19H &# \e[9;24H & #\e[9;32H#\e[9;34H#& +\e[9;39H \e[9;41H.\e[9;43H@+\e[9;47H \e[9;49H%\e[9;52H &\e[9;55H%.\e[9;60H \e[9;63H&\e[9;66H%+%%&% . \e[9;76H \e[9;78H++.#& &\e[9;87H# \e[9;90H %\e[9;97H \e[9;99H*.@\e[9;103H \e[9;106H*+ \e[9;110H@\e[9;116H@\e[9;118H%\e[10;2H \e[10;5H.\e[10;8H&+\e[10;12H+ *+ \e[10;19H #\e[10;24H \e[10;26H*\e[10;28H.\e[10;30H \e[10;34H \e[10;37H+\e[10;39H%\e[10;41H%.&\e[10;46H+\e[10;48H @ % .... " + - delay: 15 + content: "# \e[10;64H +**#\e[10;70H&%%&@\e[10;76H*#\e[10;79H#\e[10;81H.\e[10;83H \e[10;85H \e[10;87H \e[10;90H#\e[10;94H. +*&%*\e[10;102H**\e[10;107H@ \e[10;111H \e[10;113H %+# \e[11;3H \e[11;5H+.\e[11;8H %\e[11;11H&@ +# .\e[11;21H@%\e[11;24H&\e[11;26H+&%\e[11;30H+&\e[11;33H \e[11;38H@ # \e[11;44H+* +\e[11;52H \e[11;54H \e[11;56H& . *\e[11;63H \e[11;66H \e[11;69H#%\e[11;72H+\e[11;76H * \e[11;81H. #@ &. \e[11;90H \e[11;93H @&+ # #\e[11;102H@ \e[11;108H#@%\e[11;112H \e[11;114H.\e[11;117H% \e[12;2H&+\e[12;6H.% . \e[12;13H%*\e[12;18H& #\e[12;23H+\e[12;27H @ *%\e[12;37H#\e[12;39H \e[12;44H \e[12;47H@.. @\e[12;54H .\e[12;58H*\e[12;60H &* \e[12;66H*+\e[12;69H *%\e[12;75H \e[12;78H \e[12;81H. \e[12;87H%\e[12;89H#\e[12;91H#\e[12;93H& \e[12;96H \e[12;99H@ + *\e[12;106H+&&& . \e[12;114H@ \e[12;118H+\e[13;4H*. .\e[13;9H* @.\e[13;15H@.\e[13;18H.\e[13;21H+\e[13;23H%*\e[13;26H& \e[13;29H \e[13;31H \e[13;33H \e[13;36H% @\e[13;40H# \e[13;43H*%@ & \e[13;50H \e[13;52H%\e[13;54H \e[13;58H \e[13;64H *\e[13;69H+&@ & +%\e[13;80H + \e[13;86H \e[13;88H \e[13;90H#\e[13;92H+\e[13;94H%+\e[13;97H @ @\e[13;104H# *\e[13;108H@ \e[13;114H& . \e[14;4H @% \e[14;10H \e[14;12H . @\e[14;18H# \e[14;22H \e[14;24H \e[14;28H%\e[14;31H&\e[14;33H @ %\e[14;40H +\e[14;43H # &% \e[14;50H \e[14;52H \e[14;55H \e[14;57H \e[14;62H \e[14;70H&\e[14;72H \e[14;74H \e[14;76H + \e[14;81H \e[14;83H \e[14;86H @\e[14;90H+%\e[14;93H.\e[14;95H %.\e[14;99H \e[14;102H \e[14;104H @@\e[14;109H.\e[14;111H&# &+\e[15;2H#&\e[15;5H+#%%\e[15;10H# \e[15;14H & \e[15;19H.\e[15;23H.\e[15;25H*\e[15;28H+ \e[15;31H % *\e[15;37H * \e[15;41H . #*# \e[15;50H \e[15;55H \e[15;63H \e[15;67H \e[15;71H &\e[15;76H%& &\e[15;81H&+*\e[15;87H \e[15;89H%.%\e[15;95H.+\e[15;99H%\e[15;101H%# \e[15;105H \e[15;108H+ % \e[15;114H@ \e[15;117H#\e[16;2H%@\e[16;6H& *+\e[16;11H \e[16;15H \e[16;22H@ #% *. \e[16;31H# .. @ \e[16;41H \e[16;44H \e[16;46H &\e[16;49H \e[16;51H \e[16;55H \e[16;57H \e[16;59H \e[16;62H \e[16;65H \e[16;71H &@\e[16;76H+@ *\e[16;82H# \e[16;86H \e[16;88H%#%.\e[16;93H +\e[16;96H \e[16;99H \e[16;104H \e[16;108H#\e[16;111H* \e[16;114H%\e[16;116H#\e[17;6H.+@\e[17;11H*\e[17;13H.\e[17;16H. \e[17;19H%\e[17;23H%. \e[17;29H*+ .*#+&%\e[17;41H@ \e[17;47H+@ \e[17;67H \e[17;73H@\e[17;77H \e[17;79H#@*@ **&\e[17;88H # #&\e[17;95H *\e[17;98H \e[17;100H*\e[17;103H*\e[17;105H+\e[17;107H&. .\e[17;113H \e[17;116H%\e[17;118H@\e[18;5H*+\e[18;8H@.\e[18;11H# \e[18;15H \e[18;18H#\e[18;20H & *& .\e[18;29H \e[18;32H % #\e[18;37H& %# \e[18;45H+\e[18;47H \e[18;51H \e[18;67H \e[18;71H \e[18;75H %\e[18;78H@\e[18;80H.%\e[18;84H%\e[18;87H .## \e[18;94H.% \e[18;101H@\e[18;103H+*&\e[18;107H. &+\e[18;112H%\e[18;114H \e[18;116H &\e[19;3H @ \e[19;7H%\e[19;9H&\e[19;11H \e[19;13H %\e[19;17H@ %#\e[19;26H#\e[19;28H \e[19;30H%\e[19;33H \e[19;35H@\e[19;37H %+ *\e[19;45H%\e[19;47H \e[19;49H \e[19;56H \e[19;63H \e[19;71H *+ *#\e[19;80H \e[19;82H% \e[19;86H \e[19;88H.@\e[19;93H % \e[19;98H& \e[19;102H \e[19;105H \e[19;107H #\e[19;110H \e[19;117H* \e[20;2H%*@ *\e[20;8H.#&.\e[20;13H#@#% *+ \e[20;22H#+ *&\e[20;28H%*\e[20;31H.. #& % @\e[20;43H@ \e[20;47H \e[20;51H \e[20;54H \e[20;57H \e[20;59H \e[20;62H \e[20;66H \e[20;68H \e[20;72H& \e[20;77H@*\e[20;82H @% \e[20;87H \e[20;89H&+\e[20;92H* \e[20;99H# \e[20;103H@\e[20;106H@+\e[20" + - delay: 5 + content: ";109H &\e[20;112H \e[20;114H @\e[20;118H \e[21;2H & \e[21;11H +#+ %&\e[21;19H+ \e[21;22H .\e[21;25H*\e[21;27H%.\e[21;30H * \e[21;34H .& \e[21;39H&\e[21;42H #\e[21;47H@\e[21;49H \e[21;58H \e[21;61H \e[21;64H \e[21;67H \e[21;71H++ #\e[21;77H. \e[21;81H .@\e[21;85H \e[21;87H#*+ +\e[21;94H%\e[21;96H * %@\e[21;102H \e[21;104H .&\e[21;112H \e[21;115H \e[21;117H \e[22;3H@\e[22;5H @\e[22;8H&#\e[22;11H \e[22;14H+ @*\e[22;20H&\e[22;22H \e[22;24H+ \e[22;27H \e[22;30H \e[22;32H ## .*# \e[22;49H% \e[22;54H \e[22;56H \e[22;61H \e[22;69H \e[22;72H%#\e[22;76H&@ &\e[22;81H \e[22;83H % @ \e[22;95H*%\e[22;98H@\e[22;102H% & \e[22;108H @\e[22;112H%\e[22;115H @ &\e[23;2H \e[23;4H&+ \e[23;13H@#+\e[23;17H ++ \e[23;25H \e[23;28H+%+ %#\e[23;35H* \e[23;38H%\e[23;41H%#%&\e[23;46H# \e[23;55H \e[23;59H \e[23;63H \e[23;67H#.& @\e[23;76H++ \e[23;81H+%\e[23;84H@* \e[23;90H#@+\e[23;94H \e[23;96H \e[23;98H \e[23;103H+\e[23;105H \e[23;111H *\e[23;114H+ *.+\e[24;2H*.\e[24;5H@ \e[24;8H \e[24;11H@@&& \e[24;19H*** \e[24;26H \e[24;30H # \e[24;36H& \e[24;39H%.\e[24;42H#*+\e[24;47H @@\e[24;51H \e[24;53" + - delay: 12 + content: "H&\e[24;56H.\e[24;58H& \e[24;61H*\e[24;64H \e[24;66H@\e[24;69H* * .%. #+*\e[24;83H &@ \e[24;88H@\e[24;90H.. %#*.\e[24;98H@ \e[24;103H \e[24;105H \e[24;107H.\e[24;110H \e[24;112H ++*\e[24;118H@\e[25;2H%\e[25;4H#\e[25;6H # @*\e[25;15H&#+&* #. *\e[25;28H @+ \e[25;33H . @ @ @.\e[25;45H.\e[25;48H*@.. @ %# \e[25;63H#. @ \e[25;69H \e[25;71H% \e[25;75H + \e[25;80H++ \e[25;84H. ..* \e[25;92H& &%\e[25;97H .\e[25;103H & &+ \e[25;112H \e[25;116H @\e[26;2H% .\e[26;7H*.\e[26;10H &%& \e[26;16H &\e[26;20H.\e[26;22H \e[26;26H*\e[26;30H#\e[26;35H%\e[26;37H+ \e[26;40H*+\e[26;43H& \e[26;47H &%+ .*\e[26;55H@\e[26;58H \e[26;61H+\e[26;63H +\e[26;68H \e[26;72H.\e[26;74H \e[26;78H +\e[26;81H#%\e[26;85H#+\e[26;88H#*\e[26;92H.@ \e[26;96H+\e[26;98H # \e[26;103H \e[26;105H&& + \e[26;111H&@ \e[26;116H#\e[26;118H.\e[27;2H@%%\e[27;6H++\e[27;10H+ \e[27;13H*\e[27;15H \e[27;17H \e[27;19H* \e[27;22H*#\e[27;25H @*+% #\e[27;35H***@ @\e[27;44H. \e[27;47H .\e[27;50H*\e[27;53H *\e[27;58H++ @*\e[27;68H ++\e[27;73H%\e[27;76H& %\e[27;80H+ @ \e[27;86H& %%\e[27;91H & \e[27;95H \e[27;98H% \e[27;101H \e[27;104H#*&\e[27;10" + - delay: 6 + content: "8H% \e[27;112H \e[27;117H#*\e[28;2H*& #\e[28;8H +\e[28;13H.\e[28;15H.\e[28;17H*# . \e[28;23H \e[28;25H%\e[28;29H@. +\e[28;36H @\e[28;40H+\e[28;42H&#%\e[28;46H \e[28;50H \e[28;52H %\e[28;55H % \e[28;60H.@ +.*# &\e[28;70H \e[28;72H@\e[28;74H %\e[28;79H ...# \e[28;86H% \e[28;90H*@\e[28;93H \e[28;95H #\e[28;100H @ \e[28;104H @ *%#%\e[28;112H*+\e[28;115H& \e[29;3H#\e[29;6H \e[29;8H&#\e[29;11H \e[29;13H%\e[29;15H#&.@ \e[29;21H * \e[29;27H* ..%*&\e[29;37H+ \e[29;40H %\e[29;43H \e[29;46H&\e[29;48H & \e[29;53H@\e[29;55H# \e[29;59H %\e[29;63H&\e[29;65H* +* \e[29;75H \e[29;78H@*@@ \e[29;84H \e[29;86H.@\e[29;90H+#+#\e[29;95H+\e[29;98H+\e[29;102H + \e[29;108H. #\e[29;112H&+% *\e[30;2H @. \e[30;7H.\e[30;9H+%\e[30;16H \e[30;18H@&. \e[30;24H@ * \e[30;30H.+& .* \e[30;38H \e[30;40H @\e[30;43H+\e[30;45H&\e[30;47H @@\e[30;51H@\e[30;53H +# #% \e[30;62H@+\e[30;66H &\e[30;70H%# @\e[30;78H#\e[30;80H+\e[30;82H \e[30;85H %\e[30;90H.+#&\e[30;95H+\e[30;97H#&*+\e[30;102H&\e[30;104H@\e[30;106H \e[30;108H % \e[30;112H*#%\e[30;116H#\e[30;118H#\e[31;2H%#++ \e[31;8H#\e[31;10H@\e[31;14H* \e[31;17H@.. *%\e[31;25H*" + - delay: 14 + content: " +\e[31;29H*#@\e[31;33H+ \e[31;36H &&\e[31;40H %\e[31;44H%\e[31;47H.\e[31;49H@ \e[31;52H* %#\e[31;57H %\e[31;61H&#\e[31;67H@\e[31;69H*#\e[31;73H+%\e[31;76H \e[31;78H *\e[31;82H&\e[31;85H \e[31;87H# \e[31;90H.\e[31;92H@ + \e[31;101H@\e[31;103H&\e[31;105H@\e[31;107H \e[31;112H* %\e[31;116H \e[31;118H \e[32;2H+ &\e[32;7H \e[32;9H%*% @&**%#@\e[32;24H ++\e[32;29H# \e[32;33H & \e[32;37H %\e[32;42H \e[32;45H * % \e[32;51H+#&\e[32;55H+\e[32;57H%\e[32;60H%\e[32;62H#\e[32;66H \e[32;70H %.+.+\e[32;77H \e[32;79H # &%\e[32;88H* \e[32;92H &% \e[32;98H. @\e[32;104H#\e[32;106H*..*&\e[32;112H*\e[32;114H % \e[33;3H&\e[33;6H#\e[33;10H& &*\e[33;15H %%+ +\e[33;23H..**@#% + .\e[33;36H .%\e[33;41H \e[33;43H#\e[33;45H#\e[33;47H .\e[33;50H \e[33;53H&%@.\e[33;60H &.. &+ *\e[33;77H***.* \e[33;84H .+\e[33;88H+\e[33;90H&+\e[33;94H \e[33;96H #\e[33;99H \e[33;101H & #*&\e[33;109H*\e[33;113H#\e[33;115H@\e[33;117H%\e[?25l\e[?25l\e[2;2H\e(B\e[m\e[31m\e[1m\e]8;;\e\\ + @ # % # ..#*# + * & @ # @ @ .% * % %& ++& @ + @ @# # +*@ \e[3;2H * **@ @ & % " + - delay: 12 + content: " @ + * % +. #&% @* & &% # * @ @ * % @\e[4;2H . & @%.* % %+% & * * %* @ * @ # @ &@ + + @ #%+ .#. & @ # \e[5;2H % + . & * % % + % . * &+&#&% * @ # @# & % @ @&&\e[6;2H +# #@* &#. # @ @ .@ & + % * & # & + & # #@\e[7;2H #@ %& + # & & + *% @ & * +* # & # % \e[8;2H *.# * & + %% +& * .& @ % % @ & @ . + + @ \e[9;2H . + . @% * & @ +. .&# .# *@% +@ @ & # * * * *@@\e[10;2H . @ % . .& *@ &@+%++&* @&* @ .% *. . # + .#+ \e[11;2H +* *. # % . * @+@ " + - delay: 9 + content: " . %@ @ # # % .\e[12;2H# +%@ * @ . # # % @ @ \e[13;2H %#@& +& @ % * & # %+ +. * * \e[14;2H% %+** %@ +*& %# && . # * .. % \e[15;2H & & %& * @& % * + # + + # \e[16;2H & %% %. # % ## + @ . * . %\e[17;2H .* %* . + # . * \e[18;2H# &+ . & & &# # # # %+ . * ** . \e[19;2H+ .#. @ @ & . .* * * . @* %* @" + - delay: 5 + content: " @ %* @ % #&@&%\e[20;2H & *@ @ &# + *@ &* + + . % &\e[21;2H %* &% % + &%% + * & . + &. +*..#\e[22;2H* . *@ + * . * & # @* ## @* . .* #% \e[23;2H &% & & +* . # #* +. . @& # &* @ %.@.\e[24;2H # .+ . @ % @ + %# # * % @*+ #* \e[25;2H. * & +@ . # ** * &% @ # . # @. % & ..\e[26;2H +. # %@+ %%% + + * + # & . \e[27;2H + +*#% #. # # % + . # @ % * .& * * ##\e[28;2H @ " + - delay: 6 + content: ". * * %. % @ & + @ . * # # &@&+ + * ++ # . % \e[29;2H &# + .% * % #& & %& * % * +&+ @ * &*@ * % * @ \e[30;2H# % @ % # % @ % @ . . &# * # . * & %@ . .\e[31;2H *& % & . ++# &..% # + %% *. . + # % @ &% # \e[32;2H@ % # # *.+ # **. & *& & .& + @. + * . %. . . @@ @ &# & . +. \e[33;2H +** @@ & . . + % % @ * + @* . . % + #+#+ % %* \e[?25l\e[?25l\e[2;2H\e(B\e[m\e[34m\e[1m\e]8;;\e\\ @ *& % @ .# # &* #+@# @ * * @ + # + @. . \e[3;2H * * + @ &% * @ *.@ +%* # * # % \e[4;2H " + - delay: 5 + content: "* . # @ & . # @ & . % . + \e[5;2H %& . * # @ .. + & . # # @* & . @* @& . \e[6;2H# %# . . . . % . % & .@ & && *+ @ \e[7;2H&% . @ # * . + % +# %@ #. \e[8;2H + * #& & % % * @ &* . +&\e[9;2H@ & *@ + . . #& % ## & % @\e[10;2H@ #+ @ @ . @ @ * @ & && +\e[11;2H @ % + # @ . % # @ @ \e[12;2H % @ . # +. " + - delay: 6 + content: " # ..% @ % . + *\e[13;2H @ ** # * # * & * \e[14;2H ** + % ##* #& # & * % %. * \e[15;2H # * * . .@ % + & @+% # ## \e[16;2H . # % *@ *& *& @\e[17;2H % + * + @ @ @ . @ \e[18;2H @ % % *&+ * + .# + . \e[19;2H . # @ # # & & & + * \e[20;2H # @+ *@@% % @ @ . . @ * + @\e[21;2H *% & * & . * & . % + .# % \e[22;2H . & @ .* & #& + + * # * + #& \e[23;2H + & % + % . % # && % # \e[24;2H @ # @ . & & @ # @ * \e[25;2H@ * @ @ & \e[26;2H& @#& # @ %# # & * * .# #+%. % & \e[27;2H# @ + &&# @ % * . # @ @ &+ \e[28;2H & *+ % # @ + # . # %*. + * * &&# .*+ + *\e[29;2H . + * * & + % .% % &. % @ \e[30;2H + @& .* & @ + @ & + *+ . .@ +& + @\e[31;2H + % +@ # + + @% . @ ++.@% # \e[32;2H * .* & * .* + * @@ +* . @ . + # +@ \e[33;2H%# % * & % * % * %@ #% * @ .+ * * * . .& \e[?25l\e[?25l\e[2;3H\e(B\e[m\e[34m\e[1m\e]8;;\e\\%\e[2;9H# .@ \e[2;18H%\e[2;25H#\e[2;30H \e[2;32H.\e[2;35H \e[2;45H \e[2;69H \e[2;72H \e[2;74H \e[2;77H%\e[2;88H \e[2;98H \e[2;101H &&\e[2;107H \e[2;111H \e[3;3H%#\e[3;8H \e[3;11H#\e[3;13H \e[3;15H @\e[3;18H \e[3;27H%#\e[3;30H \e[3;37H \e[3;39H \e[3;44H \e[3;83H+\e[3;85H&\e[3;88H \e[3;95H@\e[3;101H.\e[3;104H#@\e[3;107H \e[3;109H \e[3;112H@%\e[4;5H \e[4;7H+\e[4;11H@\e[4;14H \e[4;16H+\e[4;20H&.+% \e[4;27H \e[4;29H *\e[4;34H#@\e[4;37H \e[4;42H \e[4;82H @+\e[4;91H \e[4;95H \e[4;98H \e[4;103H.\e[4;108H#\e[4;110H+%\e[4;116H@&@\e[5;3H* \e[5;8H \e[5;10H \e[5;12H \e[5;16H \e[5;20H *\e[5;25H \e[5;29H@\e[5;34H \e[5;36H \e[5;83H \e[5;86H. &\e[5;90H&\e[5;93H \e[5;96H#\e[5;98H \e[5;101H&\e[5;105H \e[5;108H \e[5;117H *\e[6;2H \e[6;4H&\e[6;7H+\e[6;13H. \e[6;16H %&\e[6;23H \e[6;25H@\e[6;27H %\e[6;31H \e[6;33H* \e[6;88H@\e[6;91H \e[6;93H# \e[6;97H% *\e[6;101H \e[6;106H \e[6;110H.# \e[6;116H*\e[7;2H +\e[7;7H#\e[7;9H@@\e[7;13H+&&\e[7;19H% %.\e[7;25H \e[7;27H+\e[7;31H \e[7;33H \e[7;88H \e[7;93H@\e[7;96H&\e[7;99H+\e[7;103H%#\e[7;106H@ \e[7;110H \e[7;116H. \e[8;2H&\e[8;4H.\e[8;7H \e[8;12H.\e[8;16H \e[8;21H \e[8;24H+\e[8;28H% \e[8;32H \e[8;90H \e[8;94H \e[8;97H \e[8;101H% \e[8;105H* \e[8;117H \e[9;2H \e[9;4H*\e[9;6H .\e[9;9H #\e[9;18H@\e[9;24H \e[9;29H \e[9;90H \e[9;96H%\e[9;98H \e[9;103H#\e[9;106H*@\e[9;110H.\e[9;113H&\e[9;116H \e[9;118H*\e[10;2H \e[10;5H@ \e[10;9H# \e[10;15H#@\e[10;19H \e[10;21H&\e[10;27H \e[10;31H \e[10;91H \e[10;93H \e[10;96H \e[10;101H%\e[10;108H&\e[10;110H#*\e[10;115H.\e[10;118H \e[11;5H.\e[11;16H#\e[11;20H#\e[11;23H \e[11;26H \e[11;92H \e[11;98H.\e[11;101H%\e[11;106H*\e[11;108H% * *@\e[11;115H \e[12;2H#\e[12;4H@\e[12;8H \e[12;13H +\e[12;17H@ \e[12;20H \e[12;23H *\e[12;93H \e[12;96H & \e[12;100H % \e[12;104H#\e[12;111H \e[12;115H \e[12;118H \e[13;9H&\e[13;12H# \e[13;15H+\e[13;17H \e[13;22H \e[13;26H \e[13;95H.\e[13;100H \e[13;102H%\e[13;107H \e[13;110H.\e[13;117H \e[14;4H \e[14;8H##%\e[14;13H \e[14;15H \e[14;19H*+\e[14;22H%\e[14;94H \e[14;98H \e[14;101H \e[14;104H \e[14;108H*\e[14;110H \e[14;115H#\e[14;117H&\e[15;3H \e[15;11H%\e[15;13H \e[15;17H \e[15;19H \e[15;22H \e[15;25H \e[15;98H \e[15;101H+\e[15;106H \e[15;108H \e[15;112H \e[15;115H \e[15;118H+\e[16;4H*\e[16;7H # \e[16;11H \e[16;14H &\e[16;20H+\e[16;25H \e[16;100H#\e[16;105H \e[16;108H..\e[16;118H \e[17;3H@ \e[17;6H&\e[17;8H \e[17;16H \e[17;22H@ \e[17;26H \e[17;97H \e[17;101H \e[17;106H \e[18;5H+\e[18;8H \e[18;11H \e[18;20H@\e[18;98H +#\e[18;102H \e[18;104H%\e[18;106H &\e[18;110H%\e[18;112H%\e[18;116H \e[19;5H%\e[19;9H \e[19;12H \e[19;14H \e[19;24H \e[19;94H \e[19;98H \e[19;101H#.\e[19;110H*\e[19;112H \e[19;117H \e[20;3H + &*% \e[20;14H+\e[20;18H@*+* \e[20;24H%\e[20;95H \e[20;97H \e[20;103H+@\e[20;106H \e[20;111H \e[20;114H \e[20;118H%\e[21;4H% \e[21;7H \e[21;9H \e[21;15H \e[21;17H #\e[21;23H \e[21;94H \e[21;98H%\e[21;100H. .\e[21;105H&%\e[21;108H#@\e[21;113H \e[21;116H \e[22;3H \e[22;5H@\e[22;7H \e[22;9H #\e[22;12H \e[22;18H.@\e[22;23H \e[22;26H \e[22;93H \e[22;95H \e[22;97H+ \e[22;100H \e[22;106H \e[22;110H* \e[22;114H. \e[23;4H \e[23;8H*\e[23;12H \e[23;14H \e[23;17H \e[23;22H \e[23;26H \e[23;93H \e[23;96H*\e[23;98H#*.\e[23;105H%@ \e[23;115H \e[24;4H \e[24;6H \e[24;9H*\e[24;15H*\e[24;20H#\e[24;23H \e[24;26H \e[24;28H \e[24;93H \e[24;95H+\e[24;102H +@%@\e[24;110H @\e[24;116H+ \e[25;4H@%\e[25;16H #\e[25;23H \e[25;99H \e[25;102H%\e[26;2H \e[26;4H#\e[26;6H \e[26;14H \e[26;20H.\e[26;26H+ \e[26;31H \e[26;89H \e[26;91H \e[26;99H \e[26;101H. \e[26;106H * \e[26;111H \e[26;114H \e[26;117H%\e[27;2H \e[27;4H #\e[27;8H \e[27;14H.\e[27;16H+ \e[27;20H \e[27;23H \e[27;25H \e[27;30H \e[27;97H*\e[27;100H \e[27;104H \e[27;106H&#&\e[27;110H%.\e[27;113H+\e[27;115H \e[27;118H.\e[28;3H+\e[28;5H \e[28;8H \e[28;10H#\e[28;12H+&.\e[28;17H+\e[28;19H \e[28;21H+@*\e[28;26H%@\e[28;29H@\e[28;33H \e[28;89H +# \e[28;97H* \e[28;100H @\e[28;106H& . \e[28;112H \e[28;116H +\e[29;2H+\e[29;4H.+\e[29;7H \e[29;9H%\e[29;12H \e[29;14H \e[29;17H \e[29;23H#\e[29;25H .\e[29;30H \e[29;86H \e[29;89H* \e[29;95H& \e[29;98H \e[29;106H#\e[29;109H \e[29;113H+\e[29;115H%\e[29;117H&\e[30;4H+ \e[30;8H \e[30;12H \e[30;17H \e[30;19H#\e[30;22H \e[30;26H \e[30;29H *\e[30;84H \e[30;88H&\e[30;90H@\e[30;92H \e[30;95H \e[30;99H \e[30;103H+% \e[30;107H.\e[30;110H%\e[30;113H+ %\e[30;118H.\e[31;5H%+*\e[31;11H%\e[31;14H \e[31;19H@\e[31;26H+\e[31;29H%\e[31;32H&*%\e[31;81H \e[31;85H \e[31;90H.\e[31;92H&#\e[31;96H& \e[31;101H \e[31;104H+ \e[31;108H & \e[31;116H \e[32;3H \e[32;7H \e[32;10H \e[32;12H # #\e[32;20H \e[32;25H \e[32;28H .*\e[32;33H *\e[32;83H%*%\e[32;90H \e[32;92H& \e[32;95H ++ \e[32;104H...\e[32;110H*&\e[32;115H \e[33;2H \e[33;5H++\e[33;9H \e[33;11H&+\e[33;15H%\e[33;18H +\e[33;23H \e[33;25H \e[33;32H \e[33;36H#\e[33;40H \e[33;42H \e[33;45H \e[33;75H \e[33;77H \e[33;81H%\e[33;83H#\e[33;86H@#\e[33;92H @\e[33;100H \e[33;103H %\e[33;108H \e[33;110H+\e[33;112H+\e[33;116H #\e[?25l" + - delay: 14 + content: "\e[?25l\e[2;2H\e(B\e[m\e[34m\e[1m\e]8;;\e\\* \e[2;5H%\e[2;9H % &\e[2;17H&*\e[2;20H+\e[2;25H &\e[2;29H \e[2;32H&#\e[2;77H \e[2;98H&\e[2;101H% \e[2;109H.#\e[2;112H.\e[3;3H .\e[3;7H%\e[3;11H \e[3;16H \e[3;18H#\e[3;22H#\e[3;27H \e[3;83H \e[3;85H \e[3;92H@\e[3;95H *\e[3;98H+\e[3;101H \e[3;104H \e[3;111H*% \e[4;6H@ \e[4;11H \e[4;16H \e[4;20H \e[4;28H%\e[4;30H \e[4;32H.\e[4;34H \e[4;83H \e[4;92H*\e[4;96H.\e[4;103H \e[4;106H+\e[4;108H \e[4;110H .*\e[4;114H%\e[4;116H %*\e[5;2H%+\e[5;5H*\e[5;7H&&\e[5;17H.\e[5;21H \e[5;24H#&\e[5;29H \e[5;86H \e[5;88H \e[5;90H @%\e[5;96H \e[5;101H*\e[5;106H@\e[5;109H%\e[5;114H%\e[5;118H \e[6;4H%\e[6;7H \e[6;13H \e[6;17H &\e[6;21H*#\e[6;27H& \e[6;33H \e[6;88H \e[6;93H \e[6;97H \e[6;99H %\e[6;110H \e[6;113H+\e[6;115H* \e[7;3H \e[7;5H*\e[7;7H \e[7;9H \e[7;12H@ **\e[7;18H@ \e[7;21H \e[7;26H@ \e[7;93H+\e[7;96H+\e[7;99H *\e[7;103H **\e[7;113H+\e[7;116H \e[8;2H \e[8;4H \e[8;11H&%\e[8;23H* \e[8;26H#\e[8;28H \e[8;96H*\e[8;101H \e[8;105H #\e[9;4H \e[9;7H \e[9;10H% \e[9;13H.\e[9;15H.\e[9;18H \e[9;27H \e[9;96H \e[9;101H*. \e[9;106H +#\e[9;110H \e[9;113H#\e[9;118H \e[10;4H@ @ \e[10;9H @\e[10;15H \e[10;21H \e[10;24H#\e[10;97H@\e[10;101H&\e[10;106H#* \e[10;110H%.\e[10;114H% \e[11;4H* \e[11;9H*\e[11;11H*\e[11;16H \e[11;20H \e[11;97H* \e[11;100H@ \e[11;106H \e[11;108H \e[11;110H.\e[11;112H %%\e[11;116H&\e[12;2H \e[12;4H \e[12;6H&\e[12;14H*\e[12;17H \e[12;24H \e[12;97H *\e[12;101H \e[12;103H% \e[13;3H#\e[13;9H \e[13;12H@\e[13;15H \e[13;21H*\e[13;95H \e[13;102H \e[13;105H*\e[13;110H \e[13;116H.\e[14;6H%\e[14;8H \e[14;16H*\e[14;19H.*\e[14;22H \e[14;105H%\e[14;108H \e[14;112H+\e[14;114H+ \e[14;117H+.\e[15;9H#\e[15;11H \e[15;16H.\e[15;101H \e[15;108H#&#\e[15;117H@ \e[16;4H \e[16;8H \e[16;15H \e[16;17H#\e[16;20H \e[16;100H \e[16;104H.&+. +\e[16;118H%\e[17;3H @\e[17;6H*\e[17;12H*\e[17;18H@\e[17;22H \e[17;110H..+\e[17;114H&\e[17;118H&\e[18;5H \e[18;9H*\e[18;17H%\e[18;20H \e[18;99H #&* \e[18;107H \e[18;109H @ \e[18;118H&\e[19;2H*\e[19;5H .%\e[19;10H#\e[19;12H..\e[19;15H%\e[19;18H#\e[19;20H \e[19;101H \e[19;110H \e[19;116H+\e[20;4H \e[20;7H . \e[20;12H#\e[20;14H \e[20;16H%& #\e[20;24H \e[20;99H#@\e[20;103H \e[20;109H.\e[20;116H#\e[20;118H \e[21;4H \e[21;12H@\e[21;18H \e[21;98H \e[21;100H * \e[21;105H \e[21;108H \e[22;2H%\e[22;5H \e[22;10H #+.&\e[22;18H \e[22;97H \e[22;106H.\e[22;110H \e[22;114H \e[23;8H \e[23;19H *\e[23;22H+\e[23;96H \e[23;98H \e[23;102H#&% #@%\e[23;116H+\e[23;118H%\e[24;7H*\e[24;9H %\e[24;15H#\e[24;17H*\e[24;20H \e[24;95H \e[24;101H&\e[24;103H. \e[24;111H \e[24;116H&%\e[25;2H \e[25;5H \e[25;15H%\e[25;17H \e[25;20H*\e[25;97H&\e[25;100H&* \e[25;105H#\e[25;111H#\e[25;117H+\e[26;4H \e[26;10H*#\e[26;13H&\e[26;17H#\e[26;20H %&\e[26;26H \e[26;101H \e[26;103H+\e[26;108H%\e[26;112H.\e[26;117H \e[27;5H \e[27;8H+\e[27;12H.\e[27;14H \e[27;16H \e[27;96H& &\e[27;106H @ \e[27;110H +\e[27;113H \e[27;116H.\e[27;118H \e[28;2H+ \e[28;9H. \e[28;12H@ \e[28;17H \e[28;21H .\e[28;26H \e[28;29H \e[28;91H \e[28;94H+\e[28;96H% \e[28;101H+\e[28;106H \e[28;117H@ \e[29;2H \e[29;4H @\e[29;9H \e[29;14H@\e[29;21H&\e[29;23H \e[29;26H \e[29;89H \e[29;95H \e[29;101H.\e[29;103H&\e[29;106H \e[29;109H+\e[29;113H@\e[29;115H # \e[30;4H \e[30;7H \e[30;12H. \e[30;19H \e[30;22H*\e[30;30H \e[30;88H \e[30;90H \e[30;94H+@\e[30;103H .\e[30;106H* \e[30;109H% \e[30;113H&\e[30;115H \e[30;117H&&\e[31;3H@\e[31;5H& \e[31;11H@\e[31;14H#@#\e[31;18H& #\e[31;26H \e[31;29H \e[31;32H \e[31;90H \e[31;92H# \e[31;96H #\e[31;101H.\e[31;104H#\e[31;106H+\e[31;110H \e[32;6H%\e[32;10H.\e[32;13H \e[32;15H .\e[32;18H+\e[32;22H%\e[32;28H% \e[32;35H \e[32;83H \e[32;89H+\e[32;92H+\e[32;96H \e[32;104H \e[32;109H+ \e[32;113H%\e[32;115H*\e[33;5H& \e[33;11H \e[33;15H&\e[33;19H \e[33;22H+#\e[33;26H&\e[33;36H \e[33;81H \e[33;83H \e[33;86H \e[33;93H #\e[33;104H \e[33;108H@\e[33;110H \e[33;112H \e[33;115H*\e[33;117H *\e[?25l" + - delay: 16 + content: "\e[?25l\e[2;5H\e(B\e[m\e[34m\e[1m\e]8;;\e\\ \e[2;9H+*\e[2;12H% \e[2;15H+\e[2;17H \e[2;20H \e[2;26H \e[2;28H%#\e[2;32H \e[2;90H.\e[2;98H \e[2;101H \e[2;104H%+\e[2;109H & \e[3;2H.\e[3;4H \e[3;7H \e[3;18H \e[3;21H% \e[3;24H*\e[3;92H \e[3;96H \e[3;98H&\e[3;106H@*\e[3;109H*\e[3;111H \e[4;6H \e[4;9H&\e[4;16H#\e[4;18H*\e[4;20H+\e[4;25H@\e[4;28H \e[4;32H \e[4;92H \e[4;95H. \e[4;103H#\e[4;106H \e[4;110H* % \e[4;117H \e[5;2H @\e[5;5H \e[5;7H @\e[5;17H #\e[5;20H.\e[5;24H \e[5;91H #\e[5;101H \e[5;106H \e[5;109H #\e[5;112H+# \e[6;4H \e[6;9H%\e[6;19H \e[6;21H \e[6;24H+ \e[6;27H \e[6;95H+\e[6;100H \e[6;113H \e[6;115H \e[6;118H.\e[7;5H \e[7;12H#\e[7;14H@ \e[7;18H \e[7;26H \e[7;93H \e[7;96H \e[7;104H. %\e[7;115H.\e[7;117H#\e[8;2H*\e[8;7H+\e[8;11H% \e[8;18H#\e[8;23H@\e[8;26H \e[8;96H \e[8;101H+\e[8;103H&\e[8;106H \e[8;108H*\e[9;10H&\e[9;15H \e[9;22H%\e[9;100H@ \e[9;105H@\e[9;107H. \e[9;113H \e[10;4H#\e[10;6H \e[10;8H#\e[10;10H \e[10;21H*\e[10;24H \e[10;97H \e[10;101H \e[10;104H*\e[10;106H& \e[10;110H \e[10;114H \e[11;4H &\e[11;9H \e[11;11H \e[11;13H#\e[11;17H@\e[11;97H \e[11;100H \e[11;108H+\e[11;110H \e[11;113H@ \e[11;116H#\e[12;6H \e[12;8H..+\e[12;14H .\e[12;19H%\e[12;98H \e[12;103H \e[12;106H+\e[12;117H.\e[13;3H \e[13;5H&\e[13;9H*\e[13;12H +\e[13;15H.\e[13;18H%\e[13;21H \e[13;102H+\e[13;105H \e[13;116H \e[14;4H*\e[14;14H.+ \e[14;19H \e[14;105H \e[14;108H%\e[14;110H&\e[14;112H \e[14;114H \e[14;117H \e[15;8H* \e[15;12H#\e[15;16H +\e[15;107H+ *%\e[15;117H \e[16;5H%\e[16;8H#+\e[16;11H+\e[16;17H \e[16;104H \e[16;110H \e[16;118H \e[17;4H .@\e[17;12H \e[17;18H \e[17;102H+\e[17;105H*\e[17;110H* \e[17;113H% \e[17;118H \e[18;8H+ \e[18;12H#\e[18;15H.\e[18;17H \e[18;101H %\e[18;110H&%\e[18;113H&\e[18;118H \e[19;2H *\e[19;6H @\e[19;10H \e[19;12H \e[19;15H \e[19;18H \e[19;105H.%\e[19;113H#\e[19;116H \e[20;4H#\e[20;8H \e[20;12H \e[20;16H \e[20;20H \e[20;99H \e[20;109H \e[20;111H%%\e[20;116H \e[21;12H%\e[21;15H+\e[21;17H.\e[21;101H @\e[21;115H+\e[22;2H \e[22;10H@ \e[22;102H%\e[22;106H \e[23;4H+\e[23;20H \e[23;22H \e[23;102H \e[23;106H \e[23;116H \e[23;118H \e[24;7H \e[24;10H#\e[24;15H \e[24;17H @\e[24;101H \e[24;103H \e[24;112H*\e[24;114H&\e[24;116H \e[25;4H*\e[25;15H \e[25;20H.\e[25;97H \e[25;100H @\e[25;105H@\e[25;111H @\e[25;115H+\e[2" + - delay: 6 + content: "5;117H \e[26;3H%\e[26;9H% \e[26;13H \e[26;17H \e[26;21H \e[26;100H#\e[26;103H \e[26;107H% \e[26;112H \e[27;8H \e[27;12H *\e[27;18H*\e[27;96H \e[27;98H \e[27;101H%\e[27;107H @\e[27;111H \e[27;116H \e[28;2H \e[28;5H@%\e[28;9H \e[28;24H \e[28;94H \e[28;96H \e[28;99H@+ #\e[28;104H@\e[28;108H \e[28;115H@\e[28;117H+\e[29;6H#\e[29;9H.\e[29;14H%@#\e[29;20H* \e[29;23H.\e[29;100H@ \e[29;103H \e[29;108H& #\e[29;112H. \e[29;116H \e[30;7H+\e[30;12H \e[30;22H \e[30;24H@\e[30;94H \e[30;102H*\e[30;104H & \e[30;109H \e[30;113H \e[30;117H@ \e[31;3H \e[31;5H \e[31;8H#.\e[31;11H \e[31;14H &.\e[31;20H \e[31;92H \e[31;97H \e[31;99H*&*\e[31;103H+ \e[31;106H &\e[31;109H*\e[31;113H&\e[32;5H. \e[32;9H* \e[32;12H&*\e[32;16H \e[32;18H \e[32;22H \e[32;26H#\e[32;28H \e[32;89H \e[32;92H%\e[32;94H.\e[32;102H@\e[32;109H \e[32;115H \e[33;5H \e[33;7H++\e[33;12H%\e[33;15H*\e[33;22H \e[33;26H *\e[33;30H#\e[33;99H+\e[33;108H \e[33;112H&#\e[33;115H \e[33;118H \e[?25l" + - delay: 12 + content: "\e[?25l\e[2;2H\e(B\e[m\e[34m\e[1m\e]8;;\e\\ \e[2;9H \e[2;12H \e[2;15H \e[2;27H& \e[2;90H \e[2;96H.\e[2;103H# \e[2;111H \e[2;114H#\e[2;116H#\e[3;2H \e[3;4H#\e[3;18H+\e[3;21H \e[3;24H \e[3;98H \e[3;102H&.\e[3;105H. & \e[4;9H \e[4;13H#.\e[4;16H \e[4;18H \e[4;20H@.*\e[4;25H \e[4;99H@\e[4;103H \e[4;109H% \e[4;113H \e[5;3H \e[5;9H#\e[5;18H \e[5;20H %\e[5;25H \e[5;93H \e[5;103H*\e[5;107H#\e[5;110H \e[5;112H \e[6;9H+\e[6;14H+\e[6;24H \e[6;95H \e[6;110H+\e[6;117H@ \e[7;2H@\e[7;6H+\e[7;11H% \e[7;14H \e[7;99H. \e[7;104H \e[7;106H \e[7;113H \e[7;115H \e[7;117H +\e[8;2H \e[8;4H*\e[8;7H%\e[8;11H \e[8;16H&\e[8;18H \e[8;23H \e[8;101H . \e[8;108H \e[8;112H.\e[8;118H@\e[9;10H \e[9;13H \e[9;17H&\e[9;19H*\e[9;22H \e[9;100H \e[9;105H @%\e[10;4H \e[10;8H \e[10;15H*\e[10;21H \e[10;103H* \e[10;106H#\e[10;109H%\e[11;5H \e[11;13H \e[11;15H%\e[11;108H \e[11;110H&+\e[11;113H \e[11;115H* \e[12;8H + *\e[12;15H #\e[12;19H \e[12;105H%%+\e[12;117H @\e[13;5H \e[13;7H@\e[13;9H \e[13;13H \e[13;15H \e[13;18H \e[13;102H \e[13;105H&\e[13;109H&\e[14;2H*\e[14;4H \e[14;6H \e[14;11H#\e[14;14H \e[14;108H \e[14;110H \e[14;112H%\e[14;117H*\e[15;8H \e[15;12H+\e[15;17H \e[15;107H % &\e[15;117H@\e[16;3H@#&\e[16;8H \e[16;11H.\e[16;106H*\e[17;5H \e[17;14H@+\e[17;102H \e[17;105H@\e[17;110H \e[17;112H \e[17;118H@\e[18;8H #@\e[18;12H +\e[18;15H \e[18;102H \e[18;107H.\e[18;110H \e[18;113H \e[19;6H*\e[19;8H \e[19;105H \e[19;113H \e[19;115H%\e[20;4H \e[20;9H.\e[20;11H.\e[20;111H \e[20;115H&\e[21;3H&\e[21;7H+\e[21;10H@\e[21;12H \e[21;15H \e[21;17H \e[21;102H \e[21;109H%\e[21;115H&\e[22;10H \e[22;102H \e[22;115H%\e[22;118H*\e[23;4H \e[23;12H&\e[23;106H*+\e[24;10H \e[24;14H%\e[24;18H \e[24;108H%\e[24;112H \e[24;114H \e[25;4H&\e[25;7H+@\e[25;12H.\e[25;20H \e[25;101H \e[25;105H *\e[25;112H \e[25;115H \e[25;117H*\e[26;3H \e[26;6H%\e[26;9H .#\e[26;16H*\e[26;100H \e[26;107H \e[26;110H#\e[26;115H%\e[27;8H%\e[27;13H %%\e[27;18H \e[27;101H \e[27;108H #\e[27;118H%\e[28;3H%\e[28;5H \e[28;12H \e[28;99H \e[28;102H \e[28;104H \e[28;113H&. \e[28;117H \e[29;6H \e[29;9H \e[29;14H \e[29;20H .\e[29;23H \e[29;100H \e[29;108H \e[29;110H \e[29;112H \e[30;4H%\e[30;7H \e[30;9H@\e[30;11H%\e[30;14H+@\e[30;24H \e[30;102H.\e[30;105H \e[30;116H@ \e[31;6H@\e[31;8H \e[31;12H#\e[31;17H \e[31;99H \e[31;103" + - delay: 7 + content: "H \e[31;106H% \e[31;109H &\e[31;113H \e[32;5H \e[32;7H@\e[32;9H \e[32;12H \e[32;26H \e[32;92H \e[32;94H @\e[32;97H%\e[32;99H@\e[32;102H \e[32;109H.\e[32;113H \e[33;6H* \e[33;12H \e[33;15H ##&\e[33;20H@\e[33;24H&\e[33;27H \e[33;30H \e[33;94H+\e[33;99H@\e[33;112H \e[?25l" + - delay: 13 + content: "\e[?25l\e[2;2H\e(B\e[m\e[30m\e[1m\e]8;;\e\\ # &@ & % .\e[3;2H + # + & . #* \e[4;2H * \e[5;2H& . \e[6;2H * . \e[7;2H+ + . .+. & # \e[8;2H % \e[9;2H # * # % \e[10;2H %#% \e[11;2H * # % \e[12;2H + +.\e[13;2H % + & \e[14;2H @ \e[15;2H \e[16;2H + \e[17;2H @ \e[18;2H & " + - delay: 6 + content: " # * \e[19;2H & # \e[20;2H * @ # # \e[21;2H . & \e[22;2H \e[23;2H + \e[24;2H & \e[25;2H . % @\e[26;2H@+ " + - delay: 12 + content: " + \e[27;2H % + @ .@ \e[28;2H ## # \e[29;2H + % \e[30;2H @ . . & .@ \e[31;2H # * & # \e[32;2H% %% + + % \e[33;2H % # + # . @* \e[?25l" + - delay: 5 + content: "\e[?25l\e[2;3H\e(B\e[m\e[30m\e[1m\e]8;;\e\\#\e[2;9H +\e[2;16H \e[2;21H \e[2;106H *\e[2;110H@#\e[2;117H. \e[3;3H \e[3;13H \e[3;15H ++\e[3;98H \e[3;101H \e[3;104H+.\e[3;113H*\e[4;12H*\e[4;100H \e[4;105H.\e[4;107H&\e[5;2H \e[5;5H \e[5;12H#\e[5;15H+\e[5;112H%\e[6;104H %\e[6;107H%\e[6;109H&\e[6;112H \e[7;2H \e[7;4H \e[7;8H \e[7;14H \e[7;105H \e[7;109H#\e[7;111H@\e[7;118H.\e[8;14H&\e[8;106H \e[8;112H.\e[8;117H#\e[9;7H \e[9;104H \e[9;109H& \e[9;115H *#\e[10;12H.\e[10;109H \e[11;9H*\e[11;13H \e[11;106H \e[11;113H \e[12;2H+\e[12;110H %\e[12;117H \e[13;2H@ \e[13;10H \e[13;110H \e[13;118H%\e[14;8H+\e[14;10H \e[14;118H.\e[16;9H#\e[16;108H \e[16;112H&\e[16;115H+\e[17;7H+\e[17;9H \e[18;12H \e[18;110H \e[18;115H@\e[18;117H \e[19;6H \e[19;113H \e[20;9H \e[20;12H \e[20;115H @ \e[21;5H*@\e[21;108H \e[21;113H+\e[22;118H+\e[23;2H#\e[23;115H \e[24;106H \e[24;114H.\e[25;9H \e[25;14H \e[25;118H \e[26;2H \e[26;113H \e[27;2H@\e[27;5H \e[27;7H&\e[27;11H.\e[27;14H \e[27;104H \e[27;107H&\e[27;109H \e[27;112H@\e[28;11H \e[28;108H.\e[28;110H#\e[28;117H \e[29;4H \e[29;108H \e[30;4H \e[30;13H \e[30;101H \e[30;107H@\e[30;109H \e[30;115H \e[31;9H \e[31;12H \e[31;15H%\e[31;19H \e[31;108H \e[32;2H \e[32;17H \e[32;20H \e[32;102H \e[32;109H \e[33;2H& \e[33;7H \e[33;12H&\e[33;21H \e[33;23H \e[33;97H \e[33;105H \e[33;111H.\e[?25l" + - delay: 17 + content: "\e[?25l\e[2;2H\e(B\e[m\e[30m\e[1m\e]8;;\e\\# \e[2;10H \e[2;16H&\e[2;103H@\e[2;107H \e[2;110H \e[2;117H \e[3;4H.\e[3;9H&\e[3;16H \e[3;104H @\e[3;113H \e[3;117H@\e[4;12H \e[4;105H \e[4;107H \e[4;111H@&\e[5;3H#\e[5;12H*\e[5;15H \e[5;112H \e[6;3H%\e[6;7H#\e[6;105H \e[6;107H \e[6;109H \e[6;113H*\e[7;109H.\e[7;111H &\e[7;118H \e[8;14H \e[8;112H+\e[8;117H \e[9;5H@\e[9;109H \e[9;116H \e[10;12H \e[10;111H&\e[11;9H \e[12;2H &\e[12;111H \e[12;114H.\e[13;2H \e[13;112H@.\e[13;118H \e[14;8H \e[14;118H \e[16;9H \e[16;112H \e[16;115H \e[17;7H \e[17;115H&\e[18;5H%\e[18;115H \e[19;5H%\e[19;113H@\e[19;116H%\e[20;116H \e[21;2H@\e[21;5H \e[21;112H \e[21;116H#\e[22;118H \e[23;2H \e[23;113H%\e[24;114H \e[25;114H*\e[25;116H#\e[26;111H+\e[27;2H \e[27;7H \e[27;9H#\e[27;11H \e[27;107H \e[27;112H \e[27;116H.\e[28;3H*\e[28;108H \e[28;110H \e[29;11H#\e[30;107H \e[30;110H%#\e[31;15H \e[32;4H#\e[32;115H+\e[33;2H \e[33;12H \e[33;105H@\e[33;109H+\e[33;111H \e[?25l" + - delay: 21 + content: "\e[?25l\e[2;2H\e(B\e[m\e[30m\e[1m\e]8;;\e\\ \e[2;16H \e[2;103H \e[2;112H%\e[3;4H \e[3;9H \e[3;106H \e[3;117H \e[4;111H \e[5;3H \e[5;12H \e[5;112H#\e[5;114H%\e[6;3H \e[6;7H \e[6;113H @\e[7;109H \e[7;112H \e[8;112H \e[9;5H \e[9;117H%\e[10;111H \e[12;3H \e[12;5H*\e[12;114H \e[13;112H \e[17;115H \e[18;5H \e[19;5H \e[19;113H \e[19;116H \e[21;2H \e[21;116H \e[22;116H+\e[23;113H \e[24;117H#\e[25;114H \e[25;116H \e[26;111H \e[27;4H*\e[27;9H \e[27;114H*\e[27;116H \e[28;3H \e[29;11H \e[30;110H \e[30;117H+\e[31;117H@\e[32;4H \e[32;11H@\e[32;115H \e[33;4H@\e[33;10H&\e[33;105H \e[33;109H \e[?25l" + - delay: 21 + content: "\e[?25l\e[2;112H\e(B\e[m\e[30m\e[1m\e]8;;\e\\ \e[5;112H \e[5;114H \e[6;114H \e[9;117H \e[12;5H \e[15;118H%\e[22;116H \e[24;117H \e[25;3H#\e[27;4H \e[27;114H \e[30;117H \e[31;117H \e[32;11H \e[33;4H \e[33;10H \e[?25l" + - delay: 20 + content: "\e[?25l\e[2;2H\e(B\e[m\e]8;;\e\\ \e[3;2H \e[4;2H \e[5;2H \e[6;2H \e[7;2H \e[8;2H \e[9;2H \e[10;2H \e[11;2H \e[12;2H \e[13;2H \e[14;2H \e[15;2H \e[16;2H \e[17;2H \e[18;2H \e[19;2H \e[20;2H \e[21;2H \e[22;2H \e[23;2H \e[24;2H \e[25;2H \e[26;2H \e[27;2H \e[28;2H \e[29;2H \e[30;2H \e[31;2H \e[32;2H \e[33;2H \e[?25l" + - delay: 602 + content: "\e[?25l\e[35;23H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;6H \e[35;10H \e[35;18H \e[?25l" + - delay: 2001 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/rebase_onto-compressed.gif b/demo/rebase_onto-compressed.gif new file mode 100644 index 00000000000..3a497d23217 Binary files /dev/null and b/demo/rebase_onto-compressed.gif differ diff --git a/demo/rebase_onto.gif b/demo/rebase_onto.gif new file mode 100644 index 00000000000..d10af66c498 Binary files /dev/null and b/demo/rebase_onto.gif differ diff --git a/demo/rebase_onto.yml b/demo/rebase_onto.yml new file mode 100644 index 00000000000..67a01e4680b --- /dev/null +++ b/demo/rebase_onto.yml @@ -0,0 +1,226 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/rebase_onto.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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" + +# Records, feel free to edit them +records: + - delay: 5393 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 55 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Diff─────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\No changed files \e(B\e[m\e[30m\e]8;;\e\\│\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────────────┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\0s\e(B\e[m\e]8;;\e\\ \U000F062C develop \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ \U000F062C master \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────────────────1 of 3─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\adde205f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\JD\e(B\e[m\e]8;;\e\\ Feature commit 3 \e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\7c320608\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\JD\e(B\e[m\e]8;;\e\\ Feature commit 2 \e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\8beb3fdb\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\JD\e(B\e[m\e]8;;\e\\ Feature commit 1 \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\2c2c99b0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;177;89;26m\e]8;;\e\\JB\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[36m\e[1m\e]8;;\e\\\U000F062C \e(B\e[m\e]8;;\e\\Develop commit 3 \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\8f0a7bed\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;177;89;26m\e]8;;\e\\JB\e(B\e[m\e]8;;\e\\ Develop commit 2 \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\25fe8f62\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;177;89;26m\e]8;;\e\\JB\e(B\e[m\e]8;;\e\\ Develop commit 1 \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\969e98e4\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Add a user-friendly 404 \e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H└──────────────────────────────1 of 66─┘└──────────────────────────────────────────────────────────────────────────────┘\e[32;1H┌─Stash────────────────────────────────┐┌─Command log──────────────────────────────────────────────────────────────────┐\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[13;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\0 of 0\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Rebase\e[35;8Hfrom\e[35;13Hmarked\e[35;20Hbase\e[35;25Hcommit\e[?25l" + - delay: 1003 + content: "\e[?25l\e[35;32H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing 4 \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Patch\e[4;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e[5;40H│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────0 of 0─┘\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\adde205f\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e[44m\e[1m\e]8;;\e\\JD\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Feature commit 3 \e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 66─┘\e[?25l" + - delay: 10 + content: "\e[?25l\e[2;42H\e(B\e[m\e[33m\e]8;;\e\\commit adde205fd09891856f8e705b41527b657e013faf (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/demo\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[31m\e[1m\e]8;;\e\\origin/feature/demo\e(B\e[m\e[33m\e]8;;\e\\)\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e]8;;\e\\Author:\e[4;50HJesse\e[4;56HDuffield\e[4;65H\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e]8;;\e\\Date:\e[5;50HSat\e[5;54HAug\e[5;58H12\e[5;61H16:34:20\e[5;70H2023\e[5;75H+1000\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;120H█\e[7;46H\e(B\e[m\e]8;;\e\\Feature\e[7;54Hcommit\e[7;61H3\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;42H\e(B\e[m\e]8;;\e\\---\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;43H\e(B\e[m\e]8;;\e\\random-5.go\e[9;55H|\e[9;57H17\e[9;60H\e(B\e[m\e[32m\e]8;;\e\\+++++++++++++++++\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;43H\e(B\e[m\e]8;;\e\\1\e[10;45Hfile\e[10;50Hchanged,\e[10;59H17\e[10;62Hinsertions(+)\e[10;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[11;120H█\e[12;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/random-5.go b/random-5.go\e[12;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[13;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..fe838f0\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;42H\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[16;42H+++ b/random-5.go\e[17;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1,17 @@\e[18;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[19;42H+package utils\e[20;42H+\e[21;42H+import (\e[22;42H+\e[22;46H\"fmt\"\e[23;42H+\e[23;46H\"regexp\"\e[24;42H+\e[24;46H\"strings\"\e[25;42H+)\e[26;42H+\e[27;42H+// IsValidEmail checks if an email address is valid\e[28;42H+func IsValidEmail(email string) bool {\e[29;42H+\e[29;46H// Using a regex pattern to validate email addresses\e[30;42H+\e[30;46H// This is a simple example and might not cover all edge cases\e[30;120H\e(B\e[m\e[30m\e]8;;\e\\▼\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;41H\e(B\e[m\e[36m\e[1m\e]8;;\e\\_\e[?25l" + - delay: 9 + content: "\e[?25l\e[1;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────────────────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\adde205f\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4:34PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e[44m\e[1m\e]8;;\e\\Jesse Duffield \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Feature commit 3 \e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[3;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\7c320608\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\Jesse Duffield \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Feature commit 2 \e[3;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[4;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\8beb3fdb\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\Jesse Duffield \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Feature commit 1 \e[4;65H \e[4;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[5;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[5;4H2c2c99b0\e[5;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[5;20H\e(B\e[m\e[38;2;177;89;26m\e]8;;\e\\Joe Blow \e[5;38H◯\e[5;40H\e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/develop, develop)\e(B\e[m\e]8;;\e\\ Develop commit\e[5;81H3\e[5;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[6;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[6;4H8f0a7bed\e[6;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[6;20H\e(B\e[m\e[38;2;177;89;26m\e]8;;\e\\Joe Blow \e[6;38H◯\e[6;40H\e(B\e[m\e]8;;\e\\Develop\e[6;48Hcommit\e[6;55H2\e[6;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[7;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[7;4H25fe8f62\e[7;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[7;20H\e(B\e[m\e[38;2;177;89;26m\e]8;;\e\\Joe Blow \e[7;38H◯\e[7;40H\e(B\e[m\e]8;;\e\\Develop commit 1 \e[7;61H \e[7;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[8;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[8;4H969e98e4\e[8;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[8;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[8;38H◯\e[8;40H\e(B\e[m\e[35m\e[1m\e]8;;\e\\(origin/master, master)\e[8;64H\e(B\e[m\e]8;;\e\\Add\e[8;68Ha\e[8;70Huser-friendly\e[8;84H404\e[8;88Hpage\e[8;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[9;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[9;4H6adf5c44\e[9;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[9;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[9;38H◯\e[9;40H\e(B\e[m\e]8;;\e\\Set up CI/CD pipeline using GitHub actions\e[9;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[10;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[10;4H0379b486\e[10;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[10;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[10;38H◯\e[10;40H\e(B\e[m\e]8;;\e\\Enable gzip compression fo\e[10;67H faster page\e[10;80Hloads\e[10;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[11;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[11;4Hbf7a6086\e[11;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[11;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[11;38H◯\e[11;40H\e(B\e[m\e]8;;\e\\Refactor\e[11;49Herror\e[11;55Hmessages\e[11;64Hfor\e[11;68Hbetter\e[11;75Hclarity\e[11;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[12;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[12;4Hd44b7666\e[12;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[12;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[12;38H◯\e[12;40H\e(B\e[m\e]8;;\e\\Improve accessibility of site navigation\e[12;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[13;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\f4c647b0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add loading indicators\e[13;63Hto\e[13;66Himprove\e[13;74HUX\e[13;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[14;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\4bfc121d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Add end-to-end tests for\e[14;65Hcheckout\e[14;74Hflow\e[14;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e7f83425\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[15;38H◯\e[15;40H\e(B\e[m\e]8;;\e\\Fix broken links\e[15;57Hon\e[15;60Hthe\e[15;64Hhelp\e[15;69Hpage\e[15;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[16;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\605818c2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[16;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[16;38H◯\e[16;40H\e(B\e[m\e]8;;\e\\Implement automated\e[16;60Hbackups\e[16;68Hfor\e[16;72Hdatabase\e[16;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[17;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\53fc2709\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[17;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[17;38H◯\e[17;40H\e(B\e[m\e]8;;\e\\Add TypeScript types\e[17;61Hto\e[17;64HUser\e[17;69Hmodule\e[17;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[18;4H54219ca1\e[18;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[18;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[18;38H◯\e[18;40H\e(B\e[m\e]8;;\e\\Move\e[18;45Hconstants\e[18;55Hto\e[18;58Ha\e[18;60Hseparate\e[18;69Hconfig\e[18;76Hfile\e[18;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[19;4H3f1b228f\e[19;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[19;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[19;38H◯\e[19;40H\e(B\e[m\e]8;;\e\\Enhance user search\e[19;60Hwith\e[19;65Hfuzzy\e[19;71Hmatching\e[19;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[20;4Hcf8c66c1\e[20;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[20;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[20;38H◯\e[20;40H\e(B\e[m\e]8;;\e\\Refactor\e[20;49Hsession\e[20;57Hmanagement\e[20;68Husing\e[20;74HJWT\e[20;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[21;4H4b7aefd2\e[21;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[21;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[21;38H◯\e[21;40H\e(B\e[m\e]8;;\e\\Ensure atomicity\e[21;57Hof\e[21;60Htransactions\e[21;73Hin\e[21;76Hpayment\e[21;84Hsystem\e[21;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[22;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\1e01d788\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Handle database\e[22;56Hconnection\e[22;67Hfailures\e[22;76Hgracefully\e[22;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[23;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\94de4582\e[23;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update styles according\e[23;64Hto\e[23;67Hnew\e[23;71Hdesign\e[23;78Hguidelines\e[23;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[24;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\044d95d2\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Replace deprecated\e[24;59Hlifecycle\e[24;69Hmethods\e[24;77Hin\e[24;80HReact\e[24;86Hcomponents\e[24;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[25;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[25;4Hd47ad869\e[25;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[25;38H◯\e[25;40H\e(B\e[m\e]8;;\e\\Revamp\e[25;47Herror\e[25;53Hhandling\e[25;62Hin\e[25;65Huser\e[25;70Hregistration\e[25;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[26;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[26;4Hb9c59aa0\e[26;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[26;38H◯\e[26;40H\e(B\e[m\e]8;;\e\\Handle\e[26;47Hedge\e[26;52Hcase\e[26;57Hfor\e[26;61Hzero\e[26;66Hquantity\e[26;75Hin\e[26;78Hcart\e[26;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[27;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[27;4H17d2d11e\e[27;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[27;38H◯\e[27;40H\e(B\e[m\e]8;;\e\\Introduce retry mechanism in network calls \e[27;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[28;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[28;4H6eeb4b6a\e[28;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[28;38H◯\e[28;40H\e(B\e[m\e]8;;\e\\Remove hardcoded values from payment module\e[28;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[29;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[29;4H7ce50374\e[29;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[29;38H◯\e[29;40H\e(B\e[m\e]8;;\e\\Enhance logging in production environment \e[29;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[30;4H\e(B\e[m\e[32m\e]8;;\e\\c\e[30;6H3a21ca\e[30;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[30;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e[30;40H\e(B\e[m\e]8;;\e\\Add\e[30;44Hinternationalization support for German \e[30;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[31;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\856b0d56\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Update UX of password reset feature \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[32;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\b41edacb\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Migrate legacy codebase to Typescript \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[33;4H78badcf1\e[33;13H\e(B\e[m\e[34m\e]8;;\e\\4:34PM\e[33;20H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI \e[33;38H◯\e[33;40H\e(B\e[m\e]8;;\e\\Resolve\e[33;48Hrace\e[33;53Hcondition\e[33;63Hin\e[33;66Htransaction\e[33;78Hhandling\e[33;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e[34;1H└──────────────────────────────────────────────────────────────────────────────────────────────────────────────1 of 66─┘\e[?25l\e[?25l\e[?25l" + - delay: 1105 + content: "\e[?25l\e[35;32H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l" + - delay: 5 + content: "\e[?25l\e[2;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\adde205f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\Jesse Duffield \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD -> feature/demo, origin/feature/demo)\e(B\e[m\e]8;;\e\\ Feature commit 3 \e[3;2H\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\7c320608\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4:34PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e[44m\e[1m\e]8;;\e\\Jesse Duffield \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Feature commit 2 \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 121 + content: "\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[3;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\7c320608\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\Jesse Duffield \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Feature commit 2 \e[4;2H\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\8beb3fdb\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4:34PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e[44m\e[1m\e]8;;\e\\Jesse Duffield \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Feature commit 1 \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[?25l" + - delay: 11 + content: "\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 123 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[4;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\8beb3fdb\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[34m\e]8;;\e\\4:34PM\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\Jesse Duffield \e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\◯\e(B\e[m\e]8;;\e\\ Feature commit 1 \e[5;2H\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\2c2c99b0\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[94;44m\e[1m\e]8;;\e\\4:34PM\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;177;89;26m\e[44m\e[1m\e]8;;\e\\Joe Blow \e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[97;44m\e[1m\e]8;;\e\\◯\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/develop, develop)\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Develop commit 3 \e[34;112H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[?25l" + - delay: 10 + content: "\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 625 + content: "\e[?25l\e[35;32H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing B \e[?25l" + - delay: 8 + content: "\e[?25l\e[2;40H\e(B\e[m\e[33m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[35m\e[1m\e]8;;\e\\(HEAD -> feature/demo, or\e[2;68Hgin/feature/demo)\e(B\e[m\e]8;;\e\\ Feature commit\e[2;101H3\e[3;40H\e(B\e[m\e[33m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ Feature commit\e[3;57H2\e[4;40H\e(B\e[m\e[33m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ Feature commit\e[4;57H1\e[5;40H\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\↑↑↑ Will rebase from here ↑↑↑\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[95;44m\e[1m\e]8;;\e\\(origin/develop, develop)\e[5;96H\e(B\e[m\e[44m\e[1m\e]8;;\e\\Develop\e[5;104Hcommit\e[5;111H3\e[35;81H\e(B\e[m\e[36m\e]8;;\e\\Marked a base commit for rebase \e(B\e[m\e[36m\e[4m\e]8;;\e\\(Reset) \e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 1603 + content: "\e[?25l\e[35;41H\e(B\e[m\e[36m\e[1m\e]8;;\e\\+\e[?25l" + - delay: 6 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Patch────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e[32m\e]8;;\e\\✓\e[2;4H\e(B\e[m\e]8;;\e\\repo → \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e[2;38H \e[2;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[33m\e]8;;\e\\commit 2c2c99b03f9732be1bc45fe748001cdf0269886d (\e(B\e[m\e[31m\e[1m\e]8;;\e\\origin/develop\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\develop\e(B\e[m\e[33m\e]8;;\e\\)\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\Autho\e[3;48H:\e[3;50HJ\e[3;52He Blow\e[3;59H\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐│\e(B\e[m\e]8;;\e\\Date: \e[4;50HSat Aug 12\e[4;61H16:34:20\e[4;70H2023\e[4;75H+1000\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[6;1H│\e(B\e[m\e]8;;\e\\ \e[6;4H \e[6;13H \e[6;20H \e[6;38H \e[6;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ Develop commit\e[6;61H3\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;1H│\e(B\e[m\e]8;;\e\\ \e[7;4H \e[7;13H \e[7;20H \e[7;38H \e[7;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\--- \e[7;48H \e[7;55H \e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;1H│\e(B\e[m\e]8;;\e\\ \e[8;4H \e[8;13H \e[8;20H \e[8;38H \e[8;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ random-2.go | 25 \e(B\e[m\e[32m\e]8;;\e\\+++++++++++++++++++++++++\e(B\e[m\e]8;;\e\\ \e[8;88H \e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;1H│\e(B\e[m\e]8;;\e\\ \e[9;4H \e[9;13H \e[9;20H \e[9;38H \e[9;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ 1 file changed, 25\e[9;62Hinsertions(+) \e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;1H│\e(B\e[m\e]8;;\e\\ \e[10;4H \e[10;13H \e[10;20H \e[10;38H \e[10;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[10;47H \e[10;52H \e[10;64H \e[10;68H \e[10;75H \e[10;80H \e[10;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[11;1H│\e(B\e[m\e]8;;\e\\ \e[11;4H \e[11;13H \e[11;20H \e[11;38H \e[11;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\diff --git a/random-2.go b/random-2.go\e(B\e[m\e]8;;\e\\ \e[11;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[12;1H│\e(B\e[m\e]8;;\e\\ \e[12;4H \e[12;13H \e[12;20H \e[12;38H \e[12;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e(B\e[m\e]8;;\e\\ \e[12;65H \e[12;70H \e[12;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[13;1H└───────────────────────────────0 of 0─┘│\e(B\e[m\e[1m\e]8;;\e\\index 0000000..3654596\e(B\e[m\e]8;;\e\\ \e[13;66H \e[13;74H \e[13;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e(B\e[m\e]8;;\e\\ \e[14;61H \e[14;65H \e[14;74H \e[14;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e[15;38H \e[15;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\+++ b/random-2.go\e[15;60H\e(B\e[m\e]8;;\e\\ \e[15;64H \e[15;69H \e[15;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\0s\e(B\e[m\e]8;;\e\\ \U000F062C develop \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e[16;20H \e[16;38H \e[16;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1,25 @@\e(B\e[m\e]8;;\e\\ \e[16;60H \e[16;68H \e[16;72H \e[16;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e[36m\e]8;;\e\\1s\e(B\e[m\e]8;;\e\\ \U000F062C master \e[17;20H \e[17;38H \e[17;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+\e[17;44H\e(B\e[m\e]8;;\e\\ \e[17;55H \e[17;61H \e[17;64H \e[17;69H \e[17;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e[18;4H \e[18;13H \e[18;20H \e[18;38H \e[18;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+package components\e(B\e[m\e]8;;\e\\ \e[18;69H \e[18;76H \e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e[19;4H \e[19;13H \e[19;20H \e[19;38H \e[19;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e[19;48H \e[19;53H \e[19;60H \e[19;65H \e[19;71H \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e[20;4H \e[20;13H \e[20;20H \e[20;38H \e[20;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+import (\e(B\e[m\e]8;;\e\\ \e[20;57H \e[20;68H \e[20;74H \e[20;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e[21;4H \e[21;13H \e[21;20H \e[21;38H \e[21;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\"os\"\e(B\e[m\e]8;;\e\\ \e[21;57H \e[21;60H \e[21;73H \e[21;76H \e[21;84H \e[21;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────────────────1 of 3─┘│\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\"strconv\"\e[22;56H\e(B\e[m\e]8;;\e\\ \e[22;67H \e[22;76H \e[22;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\-\e[23;13HReflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\"strings\"\e(B\e[m\e]8;;\e\\ \e[23;64H \e[23;67H \e[23;71H \e[23;78H \e[23;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[24;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[24;4Hadde205f\e[24;13H\e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\JD\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ Feature commit 3 \e[24;38H \e[24;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e[24;48H \e[24;59H \e[24;69H \e[24;77H \e[24;80H \e[24;86H \e[24;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[25;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[25;4H7c320608\e[25;13H\e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\JD\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ Feature commit 2 \e[25;38H \e[25;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\"github.com/jesseduffield/lazygit/pkg/commands/git_commands\"\e[25;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[26;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[26;4H8beb3fdb\e[26;13H\e(B\e[m\e[38;2;47;228;2m\e]8;;\e\\JD\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ Feature commit 1 \e[26;38H \e[26;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\"github.com/jesseduffield/lazygit/pkg/config\"\e[26;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[27;2H\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\\U000F0718\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\2c2c99b0\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;177;89;26m\e[44m\e[1m\e]8;;\e\\JB\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\↑↑↑ Will rebase from her\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\integrationTypes \"github.com/jesseduffield/lazygit/pkg/integration/types\"\e[27;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[28;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[28;4H8f0a7bed\e[28;13H\e(B\e[m\e[38;2;177;89;26m\e]8;;\e\\JB\e(B\e[m\e]8;;\e\\ Develop commit 2 \e[28;38H \e[28;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\"github.com/jesseduffield/lazygit/pkg/utils\"\e[28;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[29;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[29;4H25fe8f62\e[29;13H\e(B\e[m\e[38;2;177;89;26m\e]8;;\e\\JB\e(B\e[m\e]8;;\e\\ Develop commit 1 \e[29;38H \e[29;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\"github.com/samber/lo\"\e(B\e[m\e]8;;\e\\ \e[29;70H \e[29;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[30;4H\e(B\e[m\e[32m\e]8;;\e\\9\e[30;6H9e98e4\e[30;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Add\e[30;20Ha user-friendly 404\e[30;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\▼\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[32m\e]8;;\e\\+)\e(B\e[m\e]8;;\e\\ \e[30;65H \e[30;73H \e[30;77H \e[30;120H\e(B\e[m\e[30m\e]8;;\e\\▼\e[31;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────4 of 66─┘\e(B\e[m\e[30m\e]8;;\e\\└──────────────────────────────────────────────────────────────────────────────┘\e[32;1H┌─Stash────────────────────────────────┐┌─Command log──────────────────────────────────────────────────────────────────┐\e[33;1H│\e(B\e[m\e]8;;\e\\ \e[33;4H \e[33;13H \e[33;20H \e[33;38H \e[33;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[33;48H \e[33;53H \e[33;63H \e[33;66H \e[33;78H \e[33;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[?25l\e[?25l\e[?25l" + - delay: 1104 + content: "\e[?25l\e[35;41H\e(B\e[m\e[36m\e[1m\e]8;;\e\\3\e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Log──\e[14;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Local branches \e(B\e[m\e]8;;\e\\- Remotes - Tags\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────┐\e[15;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ *\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\\U000F062C\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\feature/demo\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\✓\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e[16;40H│\e[17;1H│\e[17;40H│\e[18;1H│\e[18;40H│\e[19;1H│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H│\e[22;1H└───────────────────────────────1 of 3─┘\e[23;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\2c2c99b0\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;177;89;26m\e]8;;\e\\JB\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\↑↑↑ Will rebase from her\e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────4 of 66─┘\e[?25l" + - delay: 14 + content: "\e[?25l\e[2;42H\e(B\e[m\e]8;;\e\\* \e(B\e[m\e[33m\e]8;;\e\\commit adde2\e[2;57H5 (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/demo\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[31m\e[1m\e]8;;\e\\origin/feature/demo\e(B\e[m\e[33m\e]8;;\e\\)\e(B\e[m\e]8;;\e\\ \e[3;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Author: Jesse Duffie\e[3;65Hd \e[4;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Date:\e[4;50H 7\e[4;54Hseconds ago \e[4;70H \e[4;75H \e[5;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[6;42H|\e[6;46H\e(B\e[m\e]8;;\e\\ F\e[6;50Hature commit\e[6;63H3\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[7;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[7;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[8;42H\e(B\e[m\e]8;;\e\\* \e(B\e[m\e[33m\e]8;;\e\\commit 7c32060\e(B\e[m\e]8;;\e\\ \e[8;60H \e[8;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[9;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Author: Jesse Duff\e[9;63Held \e[9;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[10;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[10;44H\e(B\e[m\e]8;;\e\\Date:\e[10;52H7\e[10;54Hseconds\e[10;62Hago\e[10;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[11;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[11;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[12;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Feature commit\e[12;63H2\e[12;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[13;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[14;42H* \e(B\e[m\e[33m\e]8;;\e\\commit 8beb3fd\e[15;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Author: Jesse Duffield\e[15;67H\e[16;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Date: 7 seconds\e[16;62Hago\e[17;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[18;42H|\e(B\e[m\e]8;;\e\\ Feature commit\e[18;63H1\e[19;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[20;42H\e(B\e[m\e]8;;\e\\* \e(B\e[m\e[33m\e]8;;\e\\commit 2c2c99b (\e(B\e[m\e[31m\e[1m\e]8;;\e\\origin/develop\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\develop\e(B\e[m\e[33m\e]8;;\e\\)\e[21;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[21;44H\e(B\e[m\e]8;;\e\\Author:\e[21;52HJoe\e[21;56HBlow\e[21;61H\e[22;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[22;44H\e(B\e[m\e]8;;\e\\Date: 7 seconds\e[22;62Hago\e[23;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[23;46H\e(B\e[m\e]8;;\e\\ \e[24;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[24;48H\e(B\e[m\e]8;;\e\\Develop\e[24;56Hcommit\e[24;63H3\e[25;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[25;46H\e(B\e[m\e]8;;\e\\ \e[26;42H*\e[26;44H\e(B\e[m\e[33m\e]8;;\e\\commit 8f0a7be\e(B\e[m\e]8;;\e\\ \e[27;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[27;44H\e(B\e[m\e]8;;\e\\Author: Joe Blow \e[28;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[28;44H\e(B\e[m\e]8;;\e\\Date: 8 seconds ago \e[29;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[29;46H\e(B\e[m\e]8;;\e\\ \e[30;42H\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[30;48HDevelop\e[30;56Hcommit\e[30;63H2\e[?25l\e[?25l\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[5;120H│\e[?25l" + - delay: 1103 + content: "\e[?25l\e[35;32H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[15;2H\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\\U000F062C\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/demo\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e[16;2H\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\0s\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \U000F062C develop \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\✓\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e[22;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 14 + content: "\e[?25l\e[2;51H\e(B\e[m\e[33m\e]8;;\e\\2c2c99b\e[2;60H\e(B\e[m\e[31m\e[1m\e]8;;\e\\origin/develop\e(B\e[m\e[33m\e]8;;\e\\, \e[2;78H\e(B\e[m\e[32m\e[1m\e]8;;\e\\velop\e(B\e[m\e[33m\e]8;;\e\\)\e(B\e[m\e]8;;\e\\ \e[3;53Hoe Blow \e[4;52H8\e[6;48HD\e[6;50Hvelop\e[8;51H\e(B\e[m\e[33m\e]8;;\e\\8f0a7be\e[9;53H\e(B\e[m\e]8;;\e\\oe Blow \e[10;52H9\e[12;48HD\e[12;50Hvelop\e[14;51H\e(B\e[m\e[33m\e]8;;\e\\25fe8\e[14;57H6\e[15;53H\e(B\e[m\e]8;;\e\\oe Blow \e[16;52H9\e[18;48HD\e[18;50Hvelop\e[20;51H\e(B\e[m\e[33m\e]8;;\e\\969e\e[20;56H8e\e[20;67H\e(B\e[m\e[31m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\mast\e[20;80Hr\e(B\e[m\e[33m\e]8;;\e\\)\e(B\e[m\e]8;;\e\\ \e[21;52HCI \e[22;52H9\e[24;48HAdd a user-friendly\e[24;68H404\e[24;72Hpage\e[26;51H\e(B\e[m\e[33m\e]8;;\e\\6adf5c4\e[27;52H\e(B\e[m\e]8;;\e\\CI \e[28;52H9\e[30;48HS\e[30;50Ht up CI/CD pipeline\e[30;70Husing\e[30;76HGitHub\e[30;83Hactions\e[?25l\e[?25l\e[?25l" + - delay: 122 + content: "\e[?25l\e[?25l\e[?25l\e[16;2H\e(B\e[m\e[36m\e]8;;\e\\0s\e(B\e[m\e]8;;\e\\ \U000F062C develop \e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e[17;2H\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\1s\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \U000F062C master \e[22;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[?25l" + - delay: 12 + content: "\e[?25l\e[2;51H\e(B\e[m\e[33m\e]8;;\e\\969e\e[2;56H8e\e[2;67H\e(B\e[m\e[31m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\mast\e[2;80Hr\e(B\e[m\e[33m\e]8;;\e\\)\e(B\e[m\e]8;;\e\\ \e[3;52HCI \e[4;52H9\e[6;48HAdd a user-friendly\e[6;68H404\e[6;72Hpage\e[8;51H\e(B\e[m\e[33m\e]8;;\e\\6adf5c4\e[9;52H\e(B\e[m\e]8;;\e\\CI \e[12;48HS\e[12;50Ht up CI/CD pipeline\e[12;70Husing\e[12;76HGitHub\e[12;83Hactions\e[14;51H\e(B\e[m\e[33m\e]8;;\e\\0379b48\e[15;52H\e(B\e[m\e]8;;\e\\CI \e[18;48HEnab\e[18;53He gzip compression\e[18;72Hfor\e[18;76Hfaster\e[18;83Hpage\e[18;88Hloads\e[20;51H\e(B\e[m\e[33m\e]8;;\e\\bf7a608\e(B\e[m\e]8;;\e\\ \e[24;48HRefactor error messages\e[24;72Hfor better\e[24;83Hclarity\e[26;51H\e(B\e[m\e[33m\e]8;;\e\\d44b766\e[30;48H\e(B\e[m\e]8;;\e\\Improve access\e[30;63Hbi\e[30;67Hty\e[30;70Hof site navig\e[30;84Htion \e[?25l\e[?25l\e[?25l" + - delay: 624 + content: "\e[?25l\e[35;32H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing r \e[?25l\e[?25l\e[14;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- R\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Rebase 'feature/demo' from marked base onto 'master'──────────────────────────┐\e[15;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[15;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\s\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Simple rebase \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[36m\e]8;;\e\\i\e[16;24H\e(B\e[m\e]8;;\e\\Interactive\e[16;36Hrebase \e[16;44H \e[16;52H \e[16;54H \e[16;62H \e[16;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[17;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ Cancel \e[17;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[18;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└────────────────────────────────────────────────────────────────────────1 of 3─┘\e[19;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H│\e[22;1H└───────────────────────────────3 of 3─┘\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;41H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 5 + content: "\e[?25l\e[14;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Local branches \e(B\e[m\e]8;;\e\\- Remotes - Tags\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\* \e(B\e[m\e[33m\e]8;;\e\\commit 0379b48\e(B\e[m\e]8;;\e\\ \e[15;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;21H\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Author: CI \e[16;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;21H\e(B\e[m\e]8;;\e\\ \e[16;24H \e[16;36H \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e[16;44H\e(B\e[m\e]8;;\e\\Date:\e[16;52H9\e[16;54Hseconds\e[16;62Hago\e[16;101H \e[17;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;21H\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e[17;101H\e(B\e[m\e]8;;\e\\ \e[18;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Enable gzip compression for faster page loads \e[19;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H│\e[22;1H└───────────────────────────────3 of 3─┘\e[33;42H\e(B\e[m\e]8;;\e\\merges\e[33;49H--onto\e[33;56Hmaster\e[33;63H2c2c99b03f9732be1bc45fe748001cdf0269886d\e[?25l" + - delay: 12 + content: "\e[?25l\e[4;52H\e(B\e[m\e]8;;\e\\10 seconds ago\e[10;52H10 seconds ago\e[16;52H10 seconds ago\e[22;52H10 seconds ago\e[28;52H10 seconds ago\e[?25l\e[?25l\e[?25l" + - delay: 31 + content: "\e[?25l\e[?25l" + - delay: 33 + content: "\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 7 + content: "\e[?25l\e[15;21H\e(B\e[m\e[33m\e]8;;\e\\↑3↓6\e[16;2H\e(B\e[m\e[36m\e]8;;\e\\9\e[17;3H\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\0s\e[?25l\e[?25l\e[24;2H\e(B\e[m\e[31m\e]8;;\e\\\U000F0718\e[24;4H09a51cea\e[25;2H\U000F0718\e[25;4H2ce1c601\e[26;2H\U000F0718\e[26;4H4fbea898\e[27;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[27;4H969e98e4\e[27;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[27;16H\e(B\e[m\e[33m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ Add a user-friendly 40\e[28;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[28;4H6adf5c44\e[28;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[28;16H\e(B\e[m\e[33m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ S\e[28;20Ht up CI/CD\e[28;31Hpipeline\e[29;2H\e(B\e[m\e[32m\e]8;;\e\\\U000F0718\e[29;4H0379b486\e[29;13H\e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e[29;16H\e(B\e[m\e[33m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ Enable gzip compressio\e[30;4H\e(B\e[m\e[32m\e]8;;\e\\bf7a6086\e[30;16H\e(B\e[m\e[33m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ Refacto\e[30;26H e\e[30;29Hror message\e[31;38H\e(B\e[m\e[30m\e]8;;\e\\3\e[?25l\e[?25l\e[2;2H\e(B\e[m\e[33m\e]8;;\e\\↑3↓6\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\featur\e[2;21H/demo\e[24;16H\e(B\e[m\e]8;;\e\\Feature commit 3\e[24;33H \e[25;16HFeature commit 2\e[25;33H \e[26;16HFeature commit 1\e[26;33H \e[27;16HAdd a\e[27;22Huser-friendly 40\e[27;39H \e[28;16HSet up CI/CD pi\e[28;32Heline us\e[29;16HEnable gzip compression \e[30;16HRefactor erro\e[30;30H messages \e[35;81H\e(B\e[m\e[36m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l" + - delay: 8 + content: "\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 34 + content: "\e[?25l\e[?25l" + - delay: 1604 + content: "\e[?25l\e[35;41H\e(B\e[m\e[36m\e[1m\e]8;;\e\\P \e[?25l\e[?25l\e[14;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐\e[15;1H│\e[15;40H│\e[16;1H│\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Force push────────────────────────────────────────────────────────────────────┐\e[17;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[17;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\Your branch has diverged from the remote branch. Press to cancel, or \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[18;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[1m\e]8;;\e\\ to force push. \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└───────────────────────────────────────────────────────────────────────────────┘\e[20;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;40H│\e[21;1H│\e[21;40H│\e[22;1H└───────────────────────────────3 of 3─┘\e[?25l" + - delay: 1105 + content: "\e[?25l\e[35;41H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l\e[?25l\e[16;23H\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────────\e[17;22H\e(B\e[m\e[1m\e]8;;\e\\Pushing...\e(B\e[m\e]8;;\e\\ \\ \e[18;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└───────────────────────────────────────────────────────────────────────────────┘\e[19;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 12 + content: "\e[?25l\e[4;53H\e(B\e[m\e]8;;\e\\3\e[10;53H3\e[22;53H3\e[28;53H3\e[?25l\e[?25l\e[?25l" + - delay: 34 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\|\e[?25l" + - delay: 52 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\/\e[?25l" + - delay: 49 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\-\e[?25l" + - delay: 48 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\\\\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\|\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\/\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\-\e[?25l" + - delay: 51 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\\\\e[?25l" + - delay: 50 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\|\e[?25l" + - delay: 51 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\/\e[33;42H git push --force-with-lease \e[?25l" + - delay: 48 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\-\e[?25l" + - delay: 45 + content: "\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\\\\e[33;43H+ adde205...09a51ce feature/demo\e[33;76H->\e[33;79Hfeature/demo\e[33;92H(forced\e[33;100Hupdate)\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[15;21H\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ \e[16;2H\e(B\e[m\e[36m\e]8;;\e\\13s\e[17;3H\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\4\e[?25l\e[?25l\e[2;2H\e(B\e[m\e[32m\e]8;;\e\\✓\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/d\e[2;21Hmo\e(B\e[m\e]8;;\e\\ \e[24;2H\e(B\e[m\e[33m\e]8;;\e\\\U000F0718\e[24;4H09a51cea\e[25;2H\U000F0718\e[25;4H2ce1c601\e[26;2H\U000F0718\e[26;4H4fbea898\e[?25l" + - delay: 6 + content: "\e[?25l\e[14;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Local branches \e(B\e[m\e]8;;\e\\- Remotes - Tags\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────┐\e[15;1H│\e[15;40H│\e[16;1H│\e[16;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Date: 13 seconds ago \e[17;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;21H\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e[17;101H\e(B\e[m\e]8;;\e\\ \e[18;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Enable gzip compression for faster page loads \e[19;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H│\e[22;1H└───────────────────────────────3 of 3─┘\e[?25l" + - delay: 13 + content: "\e[?25l\e[4;53H\e(B\e[m\e]8;;\e\\4\e[10;53H4\e[16;53H4\e[22;53H4\e[28;53H4\e[?25l\e[?25l\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;32H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;8H \e[35;13H \e[35;20H \e[35;25H \e[?25l" + - delay: 2000 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/record_demo.sh b/demo/record_demo.sh deleted file mode 100755 index 97d5c2f3631..00000000000 --- a/demo/record_demo.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/sh - -set -e - -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 - 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" - 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 -fi - -# 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 "$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" - -# 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/demo/stage_lines-compressed.gif b/demo/stage_lines-compressed.gif new file mode 100644 index 00000000000..8f2c663bdc4 Binary files /dev/null and b/demo/stage_lines-compressed.gif differ diff --git a/demo/stage_lines.gif b/demo/stage_lines.gif new file mode 100644 index 00000000000..c67ac0c0c30 Binary files /dev/null and b/demo/stage_lines.gif differ diff --git a/demo/stage_lines.yml b/demo/stage_lines.yml new file mode 100644 index 00000000000..896b9897651 --- /dev/null +++ b/demo/stage_lines.yml @@ -0,0 +1,193 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/stage_lines.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + fontFamily: "DejaVuSansMono 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" + +# Records, feel free to edit them +records: + - delay: 5609 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 69 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Diff─────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e]8;;\e\\ repo → docs-fix \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\No changed files \e(B\e[m\e[30m\e]8;;\e\\│\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────────────┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ שׂ docs-fix \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────────────────1 of 1─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\bb9157d3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Update docs/README \e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\a8649fb5\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Improve Dockerfile for m\e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\8b1f52bb\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Implement user blocking \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\23ee5047\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor user notificati\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\adc975e3\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Upgrade Rails version to\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\1c4955f6\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Move global variables to\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\54de9b3d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Fix typos in documentati\e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H└──────────────────────────────1 of 31─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[32;1H┌─Stash────────────────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Unstaged changes\e[5;2H\e(B\e[m\e[91;44m\e[1m\e]8;;\e\\ M  docs/README.md \e[13;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1 of 1\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/docs/README.md b/docs/README.md\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[1m\e]8;;\e\\index 71e6544..6b7fdc7 100644\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e[1m\e]8;;\e\\--- a/docs/README.md\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/docs/README.md\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -1,6 +1,7 @@\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;43H\e(B\e[m\e]8;;\e\\#\e[7;45HLazygit\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;120H█\e[9;42H\e(B\e[m\e[31m\e]8;;\e\\-Simple terminal UI for git commands\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;42H\e(B\e[m\e[32m\e]8;;\e\\+Simple terminal UI for git\e[10;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[11;42H\e(B\e[m\e[32m\e]8;;\e\\+(Not too simple though)\e[11;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[12;120H█\e[13;43H\e(B\e[m\e]8;;\e\\![demo](https://user-images.gh.com/demo.gif)\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;120H█\e[15;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -8,3 +9,6 @@\e[15;58H\e(B\e[m\e]8;;\e\\Simple\e[15;65Hterminal\e[15;74HUI\e[15;77Hfor\e[15;81Hgit\e[15;85Hcommands\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;120H█\e[17;43H\e(B\e[m\e]8;;\e\\###\e[17;47HHomebrew\e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;120H█\e[19;42H\e(B\e[m\e[32m\e]8;;\e\\+Just do brew install lazygit and bada bing bada\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[20;42H\e(B\e[m\e[32m\e]8;;\e\\+boom you have begun on the path of laziness.\e[20;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[21;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[33;120H\e(B\e[m\e[30m\e]8;;\e\\▼\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Stage\e[35;7Hindividual\e[35;18Hlines\e[?25l" + - delay: 1003 + content: "\e[?25l\e[35;24H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 23 + content: "\e[?25l\e[1;41H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Unstaged changes─────────────────────────────────────────────────────────────┐\e[2;41H│\e[2;120H▲\e[3;41H│\e[3;120H█\e[4;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[4;120H█\e[5;1H\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\ M  docs/README.md \e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[5;120H█\e[6;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[6;40H│\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;120H█\e[7;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[7;40H│\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;120H█\e[8;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[8;40H│\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;120H│\e[9;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[9;40H│\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[91;44m\e[1m\e]8;;\e\\-Simple terminal UI for git commands\e(B\e[m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[10;40H│\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;120H│\e[11;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[11;40H│\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;120H│\e[12;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[12;40H│\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;120H│\e[13;1H\e(B\e[m\e[30m\e]8;;\e\\└───────────────────────────────1 of 1─┘\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[13;120H│\e[14;41H│\e[14;120H│\e[15;41H│\e[15;120H│\e[16;41H│\e[16;120H▼\e[17;41H└──────────────────────────────────────────────────────────────────────────────┘\e[18;41H\e(B\e[m\e[30m\e]8;;\e\\┌─Staged changes───────────────────────────────────────────────────────────────┐\e[19;42H\e(B\e[m\e]8;;\e\\ \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;42H\e(B\e[m\e]8;;\e\\ \e[20;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;42H\e(B\e[m\e]8;;\e\\ \e[33;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[?25l" + - delay: 601 + content: "\e[?25l\e[35;33H\e(B\e[m\e[36m\e[1m\e]8;;\e\\v \e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;24H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[9;42H\e(B\e[m\e[31;44m\e]8;;\e\\-Simple terminal UI for git commands\e(B\e[m\e]8;;\e\\ \e[10;42H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\+Simple terminal UI for git\e(B\e[m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l" + - delay: 122 + content: "\e[?25l\e[?25l\e[?25l\e[10;42H\e(B\e[m\e[32;44m\e]8;;\e\\+Simple terminal UI for git\e(B\e[m\e]8;;\e\\ \e[11;42H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\+(Not too simple though)\e(B\e[m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l" + - delay: 623 + content: "\e[?25l\e[35;24H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[?25l" + - delay: 44 + content: "\e[?25l\e[3;48H\e(B\e[m\e[1m\e]8;;\e\\32d7787\e[5;2H\e(B\e[m\e[32m\e]8;;\e\\M\e[6;46H\e(B\e[m\e[36m\e]8;;\e\\9\e[6;48H3\e[6;51H9\e[6;53H6\e[6;58H\e(B\e[m\e]8;;\e\\Simple\e[6;65Hterminal\e[6;74HUI\e[6;77Hfor\e[6;81Hgit\e[7;43H \e[7;45H \e[8;43H###\e[8;47HHomebrew\e[8;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[9;42H\e(B\e[m\e]8;;\e\\ \e[9;120H\e(B\e[m\e[32m\e[1m\e]8;;\e\\█\e[10;42H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\+Just do brew install lazygit and bada bing bada\e(B\e[m\e[1m\e]8;;\e\\ \e[11;42H\e(B\e[m\e[32m\e]8;;\e\\+boom you have begun on the path of laziness.\e(B\e[m\e]8;;\e\\ \e[12;42H\e(B\e[m\e[32m\e]8;;\e\\+\e[13;43H\e(B\e[m\e]8;;\e\\ \e[15;42H \e[15;58H \e[15;65H \e[15;74H \e[15;77H \e[15;81H \e[15;85H \e[19;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/docs/README.md b/docs/README.md\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[20;42H\e(B\e[m\e[1m\e]8;;\e\\index 71e6544..32d7787 100644\e[20;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[21;42H\e(B\e[m\e[1m\e]8;;\e\\--- a/docs/README.md\e[21;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[22;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/docs/README.md\e[22;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[23;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -1,6 +1,7 @@\e[23;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[24;43H\e(B\e[m\e]8;;\e\\#\e[24;45HLazygit\e[24;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[25;120H█\e[26;42H\e(B\e[m\e[31m\e]8;;\e\\-Simple terminal UI for git commands\e[27;42H\e(B\e[m\e[32m\e]8;;\e\\+Simple terminal UI for git\e[28;42H+(Not too simple though)\e[30;43H\e(B\e[m\e]8;;\e\\![demo](https://user-images.gh.com/demo.gif)\e[33;120H\e(B\e[m\e[30m\e]8;;\e\\▼\e[?25l" + - delay: 1104 + content: "\e[?25l\e[35;34H\e(B\e[m\e[36m\e[1m\e]8;;\e\\esc> \e[?25l\e[?25l\e[1;41H\e(B\e[m\e[30m\e]8;;\e\\┌─Unstaged changes─────────────────────────────────────────────────────────────┐\e[2;41H│\e(B\e[m\e]8;;\e\\ \e[2;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[3;41H│\e(B\e[m\e]8;;\e\\ \e[3;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[4;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\M\e(B\e[m\e[91;44m\e[1m\e]8;;\e\\M  docs/README.md \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[5;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[6;40H│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[6;58H \e[6;65H \e[6;74H \e[6;77H \e[6;81H \e[6;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[7;40H│\e(B\e[m\e[30m\e]8;;\e\\│\e[7;120H│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[8;40H│\e(B\e[m\e[30m\e]8;;\e\\│\e[8;43H\e(B\e[m\e]8;;\e\\ \e[8;47H \e[8;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[9;40H│\e(B\e[m\e[30m\e]8;;\e\\│\e[9;120H│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[10;40H│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[11;40H│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[11;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[12;40H│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[12;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└───────────────────────────────1 of 1─┘\e(B\e[m\e[30m\e]8;;\e\\│\e[13;120H│\e[14;41H│\e[14;120H│\e[15;41H│\e[15;120H│\e[16;41H│\e[16;120H│\e[17;41H└──────────────────────────────────────────────────────────────────────────────┘\e[?25l" + - delay: 7 + content: "\e[?25l\e[2;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/docs/README.md b/docs/README.md\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[1m\e]8;;\e\\index 32d7787..6b7fdc7 100644\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e[1m\e]8;;\e\\--- a/docs/README.md\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/docs/README.md\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -9,3 +9,6 @@\e[6;58H\e(B\e[m\e]8;;\e\\Simple\e[6;65Hterminal\e[6;74HUI\e[6;77Hfor\e[6;81Hgit\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;120H█\e[8;43H\e(B\e[m\e]8;;\e\\###\e[8;47HHomebrew\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;120H█\e[10;42H\e(B\e[m\e[32m\e]8;;\e\\+Just do brew install lazygit and bada bing bada\e[11;42H+boom you have begun on the path of laziness.\e[12;42H+\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\▼\e[?25l\e[?25l\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;33H\e(B\e[m\e[36m\e[1m\e]8;;\e\\c \e[?25l\e[?25l\e[4;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e[5;40H│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌\e[13;23HCommit summary──\e[13;40H───────────────────────────────────────────────────── 0 ─────┐\e[14;21H│\e(B\e[m\e]8;;\e\\ \e[14;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[15;21H└───────────────────────────────────────────────────────────────────────────────┘\e[16;21H\e(B\e[m\e[30m\e]8;;\e\\┌─Commit description────────────────────────────Press to toggle focus─────┐\e[17;21H│\e[17;40H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;21H│\e[18;40H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;21H│\e[19;40H\e(B\e[m\e]8;;\e\\ \e[19;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;21H│\e[20;40H\e(B\e[m\e]8;;\e\\ \e[20;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;21H│\e[21;40H\e(B\e[m\e]8;;\e\\ \e[21;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[22;21H│\e(B\e[m\e]8;;\e\\ \e[22;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[23;21H│\e(B\e[m\e]8;;\e\\ \e[23;101H\e(B\e[m\e[30m\e]8;;\e\\│\e[24;21H└───────────────────────────────────────────────────────────────────────────────┘\e[14;22H\e[?12l\e[?25h\e[0 q" + - delay: 602 + content: "\e[?25l\e[35;24H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[14;22H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1\e[14;22H\e(B\e[m\e]8;;\e\\U\e[14;23H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[14;23H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[14;23H\e(B\e[m\e]8;;\e\\p\e[14;24H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;24H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[14;24H\e(B\e[m\e]8;;\e\\d\e[14;25H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[14;25H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[14;25H\e(B\e[m\e]8;;\e\\a\e[14;26H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;26H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\5\e[14;26H\e(B\e[m\e]8;;\e\\t\e[14;27H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[14;27H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\6\e[14;27H\e(B\e[m\e]8;;\e\\e\e[14;28H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;28H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\7\e[14;29H\e[?12l\e[?25h\e[0 q" + - delay: 121 + content: "\e[?25l\e[14;29H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\8\e[14;29H\e(B\e[m\e]8;;\e\\t\e[14;30H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;30H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\9\e[14;30H\e(B\e[m\e]8;;\e\\a\e[14;31H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;31H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;92H\e(B\e[m\e[32m\e[1m\e]8;;\e\\ 10\e[14;31H\e(B\e[m\e]8;;\e\\g\e[14;32H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[14;32H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1\e[14;32H\e(B\e[m\e]8;;\e\\l\e[14;33H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;33H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[14;33H\e(B\e[m\e]8;;\e\\i\e[14;34H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;34H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\3\e[14;34H\e(B\e[m\e]8;;\e\\n\e[14;35H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[14;35H\e[?12l\e[?25h\e[0 q\e[?25l\e[13;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\4\e[14;35H\e(B\e[m\e]8;;\e\\e\e[14;36H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[35;24H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[14;36H\e[?12l\e[?25h\e[0 q" + - delay: 14 + content: "\e[?25l\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e[5;1H│\e[5;40H│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└────────────────────\e[13;23H──────────1 of 1\e[13;40H┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e[14;21H\e(B\e[m\e[30m\e]8;;\e\\emotes - Tags──────┐│\e[14;101H\e(B\e[m\e]8;;\e\\ \e[15;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[16;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[17;21H \e[17;40H\e(B\e[m\e[30m\e]8;;\e\\│└────────────────────────────────────────────────────────────\e[18;21H\e(B\e[m\e]8;;\e\\ \e[18;40H\e(B\e[m\e[30m\e]8;;\e\\│┌─Staged changes─────────────────────────────────────────────\e[19;21H\e(B\e[m\e]8;;\e\\ \e[19;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\diff --git a/docs/README.md b/docs/README.md\e[19;101H\e(B\e[m\e]8;;\e\\ \e[20;21H \e[20;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\index 71e6544..32d7787 100644\e[20;101H\e(B\e[m\e]8;;\e\\ \e[21;21H \e[21;40H\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\--- a/docs/README.md\e[21;101H\e(B\e[m\e]8;;\e\\ \e[22;21H\e(B\e[m\e[30m\e]8;;\e\\────────────1 of 1─┘│\e(B\e[m\e[1m\e]8;;\e\\+++ b/docs/README.md\e[22;101H\e(B\e[m\e]8;;\e\\ \e[23;21H\e(B\e[m\e[30m\e]8;;\e\\───────────────────┐│\e(B\e[m\e[36m\e]8;;\e\\@@ -1,6 +1,7 @@\e[23;101H\e(B\e[m\e]8;;\e\\ \e[24;21He docs/README \e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ # Lazygit \e[?25l\e[?25l\e[?25l" + - delay: 53 + content: "\e[?25l\e[?25l" + - delay: 40 + content: "\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 7 + content: "\e[?25l\e[?25l" + - delay: 6 + content: "\e[?25l\e[5;2H\e(B\e[m\e[91;44m\e[1m\e]8;;\e\\ \e[10;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[11;120H█\e[12;120H█\e[13;120H█\e[14;120H█\e[15;120H█\e[16;120H█\e[17;41H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[18;41H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\█\e[19;42H\e(B\e[m\e]8;;\e\\ \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[20;42H\e(B\e[m\e]8;;\e\\ \e[21;42H \e[22;42H \e[23;42H \e[24;43H \e[24;45H \e[25;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[26;42H\e(B\e[m\e]8;;\e\\ \e[27;42H \e[28;42H \e[30;43H \e[?25l" + - delay: 6 + content: "\e[?25l\e[?25l\e[?25l\e[24;4H\e(B\e[m\e[33m\e]8;;\e\\da1d221c\e[24;23H\e(B\e[m\e]8;;\e\\tagline \e[25;4H\e(B\e[m\e[33m\e]8;;\e\\bb9157d3\e[25;16H\e(B\e[m\e]8;;\e\\Update docs/README\e[25;35H \e[25;39H \e[26;4H\e(B\e[m\e[33m\e]8;;\e\\a8649f\e[26;11H5\e[26;19H\e(B\e[m\e]8;;\e\\rov\e[26;23H Dock\e[26;30Hfi\e[26;33He for m\e[27;4H\e(B\e[m\e[33m\e]8;;\e\\8b1f\e[27;9H2bb\e[27;16H\e(B\e[m\e]8;;\e\\Implement user blocking \e[28;4H\e(B\e[m\e[33m\e]8;;\e\\23ee5047\e[28;16H\e(B\e[m\e]8;;\e\\Refactor user\e[28;30Hnotifica\e[28;39Hi\e[29;4H\e(B\e[m\e[33m\e]8;;\e\\adc\e[29;8H7\e[29;10He3\e[29;16H\e(B\e[m\e]8;;\e\\Upgrade R\e[29;26Hils version\e[30;4H\e(B\e[m\e[33m\e]8;;\e\\1c4955f6\e[30;16H\e(B\e[m\e]8;;\e\\Move gl\e[30;24Hbal variables \e[30;39Ho\e[31;38H\e(B\e[m\e[30m\e]8;;\e\\2\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 23 + content: "\e[?25l\e[?25l" + - delay: 601 + content: "\e[?25l\e[35;33H\e(B\e[m\e[36m\e[1m\e]8;;\e\\4 \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Patch───────────\e[4;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e(B\e[m\e[31m\e]8;;\e\\ M  docs/README.md \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────1 of 1─┘\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e(B\e[m\e[93;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\da1d221c\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Update tagline \e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 32─┘\e[?25l" + - delay: 16 + content: "\e[?25l\e[2;42H\e(B\e[m\e[33m\e]8;;\e\\commit da1d221cece8df5f562424cfa76cfa397978c778 (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\docs-fix\e(B\e[m\e[33m\e]8;;\e\\)\e[3;42H\e(B\e[m\e]8;;\e\\Author: CI \e[4;42HDate: Thu Aug 3 12:04:29\e[4;69H2023\e[4;74H+1000\e[5;42H \e[6;42H Update tagline \e[6;65H \e[6;74H \e[6;77H \e[6;81H \e[7;42H---\e[8;43Hdocs/README.md\e[8;58H|\e[8;60H3\e[8;62H\e(B\e[m\e[32m\e]8;;\e\\++\e(B\e[m\e[31m\e]8;;\e\\-\e[9;43H\e(B\e[m\e]8;;\e\\1\e[9;45Hfile\e[9;50Hchanged,\e[9;59H2\e[9;61Hinsertions(+),\e[9;76H1\e[9;78Hdeletion(-)\e[10;42H \e[11;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/docs/README.md b/docs/README.md\e(B\e[m\e]8;;\e\\ \e[12;42H\e(B\e[m\e[1m\e]8;;\e\\index 71e6544..32d7787 100644\e[13;42H--- a/docs/README.md\e[14;42H+++ b/docs/README.md\e[15;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -1,6 +1,7 @@\e[16;43H\e(B\e[m\e]8;;\e\\#\e[16;45HLazygit\e[18;42H\e(B\e[m\e[31m\e]8;;\e\\-Simple terminal UI for git commands\e[19;42H\e(B\e[m\e[32m\e]8;;\e\\+Simple terminal UI for git\e[20;42H+(Not too simple though)\e[20;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;120H│\e[22;43H\e(B\e[m\e]8;;\e\\![demo](https://user-images.gh.com/demo.gif)\e[22;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[23;120H│\e[24;120H│\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;24H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;7H \e[35;18H \e[?25l" + - delay: 2002 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/undo-compressed.gif b/demo/undo-compressed.gif new file mode 100644 index 00000000000..f579df9b0b5 Binary files /dev/null and b/demo/undo-compressed.gif differ diff --git a/demo/undo.gif b/demo/undo.gif new file mode 100644 index 00000000000..6ae940ce851 Binary files /dev/null and b/demo/undo.gif differ diff --git a/demo/undo.yml b/demo/undo.yml new file mode 100644 index 00000000000..67d6c4f6e67 --- /dev/null +++ b/demo/undo.yml @@ -0,0 +1,191 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/undo.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + fontFamily: "DejaVuSansMono 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" + +# Records, feel free to edit them +records: + - delay: 3040 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 60 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Diff─────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e]8;;\e\\ repo → master \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\No changed files \e(B\e[m\e[30m\e]8;;\e\\│\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────────────┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ שׂ master \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────────────────1 of 1─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\25b5642a\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Improve Dockerfile for m\e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\98912d2e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Implement user blocking \e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\cdfecb7c\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor user notificati\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\84b8b95f\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Upgrade Rails version to\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\ffae195d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Move global variables to\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\e968247e\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Fix typos in documentati\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\2c1df171\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Optimize Lazygit startup\e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H└──────────────────────────────1 of 30─┘└──────────────────────────────────────────────────────────────────────────────┘\e[32;1H┌─Stash────────────────────────────────┐┌─Command log──────────────────────────────────────────────────────────────────┐\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[13;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\0 of 0\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Undo\e[35;6Hcommands\e[?25l" + - delay: 1003 + content: "\e[?25l\e[35;15H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing 4 \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Patch\e[4;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e[5;40H│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────0 of 0─┘\e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ﰖ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\25b5642a\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e[44m\e[1m\e]8;;\e\\CI\e(B\e[m\e[44m\e[1m\e]8;;\e\\ Improve Dockerfile for m\e(B\e[m\e[32m\e[1m\e]8;;\e\\▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 30─┘\e[?25l" + - delay: 9 + content: "\e[?25l\e[2;42H\e(B\e[m\e[33m\e]8;;\e\\commit 25b5642a05887d784b85383cfcf0d55a60bae921 (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\)\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e]8;;\e\\Author:\e[3;50HCI\e[3;53H\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e]8;;\e\\Date:\e[4;50HSun\e[4;54HAug\e[4;58H6\e[4;60H19:27:01\e[4;69H2023\e[4;74H+1000\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;120H█\e[6;46H\e(B\e[m\e]8;;\e\\Improve\e[6;54HDockerfile\e[6;65Hfor\e[6;69Hmore\e[6;74Hefficient\e[6;84Hbuilds\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;42H\e(B\e[m\e]8;;\e\\---\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;43H\e(B\e[m\e]8;;\e\\Dockerfile\e[8;54H|\e[8;56H1\e[8;58H\e(B\e[m\e[32m\e]8;;\e\\+\e[8;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[9;43H\e(B\e[m\e]8;;\e\\1\e[9;45Hfile\e[9;50Hchanged,\e[9;59H1\e[9;61Hinsertion(+)\e[9;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[10;120H█\e[11;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/Dockerfile b/Dockerfile\e[11;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[12;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e[12;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[13;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..311a4a0\e[13;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[14;42H\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[14;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[15;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/Dockerfile\e[15;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[16;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[16;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[17;42H\e(B\e[m\e[32m\e]8;;\e\\+FROM ubuntu:18.04\e[17;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[18;42H\e(B\e[m\e]8;;\e\\\\\e[18;44HNo\e[18;47Hnewline\e[18;55Hat\e[18;58Hend\e[18;62Hof\e[18;65Hfile\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[19;120H█\e[30;120H▼\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Drop\e[35;6Htwo commits \e[?25l" + - delay: 1003 + content: "\e[?25l\e[35;18H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing d \e[?25l\e[?25l\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Delete commit─────────────────────────────────────────────────────────────────┐\e[17;21H│\e(B\e[m\e[1m\e]8;;\e\\Are you sure you want to delete this commit? \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H└───────────────────────────────────────────────────────────────────────────────┘\e[23;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 30─┘\e[?25l" + - delay: 1104 + content: "\e[?25l\e[35;27H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 5 + content: "\e[?25l\e[16;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[17;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+FROM ubuntu:18.04\e(B\e[m\e]8;;\e\\ \e[18;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\\\ No newline at end of file \e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 30─┘\e[33;42H\e(B\e[m\e]8;;\e\\merges\e[33;49H98912d2e259c9c0aecf52dccd4a859477821e804\e[?25l" + - delay: 8 + content: "\e[?25l\e[?25l" + - delay: 41 + content: "\e[?25l\e[35;104H\e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset) \e[?25l" + - delay: 46 + content: "\e[?25l\e[35;104H\e(B\e[m\e[36m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[24;4H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\98912d\e[24;11He\e[24;19H\e(B\e[m\e[44m\e[1m\e]8;;\e\\lem\e[24;23Hnt us\e[24;30H b\e[24;33Hocking \e[25;4H\e(B\e[m\e[32m\e]8;;\e\\cdfecb7c\e[25;16H\e(B\e[m\e]8;;\e\\Refactor user notificati\e[26;4H\e(B\e[m\e[32m\e]8;;\e\\84b8b95f\e[26;16H\e(B\e[m\e]8;;\e\\Upgrade Rails\e[26;30Hversion \e[26;39Ho\e[27;4H\e(B\e[m\e[32m\e]8;;\e\\ffae1\e[27;11Hd\e[27;16H\e(B\e[m\e]8;;\e\\Move glob\e[27;26Hl variables\e[28;4H\e(B\e[m\e[32m\e]8;;\e\\e968247e\e[28;16H\e(B\e[m\e]8;;\e\\Fix typ\e[28;24Hs in documenta\e[28;39Hi\e[29;4H\e(B\e[m\e[32m\e]8;;\e\\2c1df1\e[29;11H1\e[29;16H\e(B\e[m\e]8;;\e\\Optimize Lazygit startup\e[30;4H\e(B\e[m\e[32m\e]8;;\e\\3ff3c03d\e[30;16H\e(B\e[m\e]8;;\e\\Refactor\e[30;25Hlogic in L\e[30;36Hzygi\e[31;37H\e(B\e[m\e[32m\e[1m\e]8;;\e\\29\e[?25l" + - delay: 6 + content: "\e[?25l\e[?25l" + - delay: 9 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\98912d\e[2;56He2\e[2;59H9c9c0aecf\e[2;69H2dc\e[2;73Hd4a859477821\e[2;86H804\e[6;49H\e(B\e[m\e]8;;\e\\lem\e[6;53Hnt us\e[6;60H b\e[6;63Hocking functionality\e[6;84H \e[8;43Huser/blocking.go\e[8;60H|\e[8;62H1\e[8;64H\e(B\e[m\e[32m\e]8;;\e\\+\e[11;55H\e(B\e[m\e[1m\e]8;;\e\\user/blocking.go b/user/blocking.go\e[13;57H47068eb\e[15;48Huser/blocking.go\e[17;43H\e(B\e[m\e[32m\e]8;;\e\\package user\e(B\e[m\e]8;;\e\\ \e[?25l" + - delay: 30 + content: "\e[?25l\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;27H\e(B\e[m\e[36m\e[1m\e]8;;\e\\d \e[?25l\e[?25l\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Delete commit─────────────────────────────────────────────────────────────────┐\e[17;21H│\e(B\e[m\e[1m\e]8;;\e\\Are you sure you want to delete this commit? \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H└───────────────────────────────────────────────────────────────────────────────┘\e[23;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 29─┘\e[?25l" + - delay: 1104 + content: "\e[?25l\e[35;27H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 5 + content: "\e[?25l\e[16;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[17;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+package user\e(B\e[m\e]8;;\e\\ \e[18;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\\\ No newline at end of file \e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 29─┘\e[33;49H\e(B\e[m\e]8;;\e\\cdfecb7c4de5\e[33;62H08e362de45a936ef5c38f8\e[33;85Hfb9d\e[?25l" + - delay: 8 + content: "\e[?25l\e[?25l" + - delay: 39 + content: "\e[?25l\e[35;104H\e(B\e[m\e[33m\e]8;;\e\\Rebasing \e(B\e[m\e[33m\e[4m\e]8;;\e\\(Reset) \e[?25l" + - delay: 46 + content: "\e[?25l\e[35;104H\e(B\e[m\e[36m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l\e[24;4H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\cdfecb7c\e[24;16H\e(B\e[m\e[44m\e[1m\e]8;;\e\\Refactor user notificati\e[25;4H\e(B\e[m\e[32m\e]8;;\e\\84b8b95f\e[25;16H\e(B\e[m\e]8;;\e\\Upgrade Rails\e[25;30Hversion \e[25;39Ho\e[26;4H\e(B\e[m\e[32m\e]8;;\e\\ffae1\e[26;11Hd\e[26;16H\e(B\e[m\e]8;;\e\\Move glob\e[26;26Hl variables\e[27;4H\e(B\e[m\e[32m\e]8;;\e\\e968247e\e[27;16H\e(B\e[m\e]8;;\e\\Fix typ\e[27;24Hs in documenta\e[27;39Hi\e[28;4H\e(B\e[m\e[32m\e]8;;\e\\2c1df1\e[28;11H1\e[28;16H\e(B\e[m\e]8;;\e\\Optimize Lazygit startup\e[29;4H\e(B\e[m\e[32m\e]8;;\e\\3ff3c03d\e[29;16H\e(B\e[m\e]8;;\e\\Refactor\e[29;25Hlogic in L\e[29;36Hzygi\e[30;4H\e(B\e[m\e[32m\e]8;;\e\\50122ff\e[30;16H\e(B\e[m\e]8;;\e\\Add sorting feature to p\e[31;38H\e(B\e[m\e[32m\e[1m\e]8;;\e\\8\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 9 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\cdfecb7c4de5\e[2;62H08e362de45a936ef5c38f8\e[2;85Hfb9d\e[6;46H\e(B\e[m\e]8;;\e\\Refactor user notifications system \e[8;43Hnotificatio\e[8;55H/user_notification.go\e[8;77H|\e[8;79H1\e[8;81H\e(B\e[m\e[32m\e]8;;\e\\+\e[11;55H\e(B\e[m\e[1m\e]8;;\e\\notificatio\e[11;67H/user_notification.\e[11;87Ho \e[12;42Hb/not\e[12;48Hfication/user_notification.go\e[13;42Hnew file mode 100644\e(B\e[m\e]8;;\e\\ \e[14;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..2f4b99f\e[15;42H---\e[15;46H/dev/nul\e[15;55H\e(B\e[m\e]8;;\e\\ \e[16;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/notification/user_notification.go\e[17;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[18;42H\e(B\e[m\e[32m\e]8;;\e\\+package notification\e(B\e[m\e]8;;\e\\ \e[18;65H \e[19;42H\\\e[19;44HNo\e[19;47Hnewline\e[19;55Hat\e[19;58Hend\e[19;62Hof\e[19;65Hfile\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[?25l" + - delay: 33 + content: "\e[?25l\e[?25l" + - delay: 604 + content: "\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Undo\e[35;7Hhe\e[35;10Hdrops \e[35;18H \e[?25l" + - delay: 1003 + content: "\e[?25l\e[35;16H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing z \e[?25l\e[?25l\e[15;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Undo──────────────────────────────────────────────────────────────────────────┐\e[16;21H│\e(B\e[m\e[1m\e]8;;\e\\Are you sure you want to hard rese\e[16;57H to \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;21H│\e(B\e[m\e[1m\e]8;;\e\\'98912d2e259c9c0aecf52dccd4a859477821e804'? An auto-stash will be performed if \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H│\e(B\e[m\e[1m\e]8;;\e\\necessary. \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;21H└───────────────────────────────────────────────────────────────────────────────┘\e[23;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 28─┘\e[?25l" + - delay: 1104 + content: "\e[?25l\e[35;25H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l\e[?25l\e[15;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e(B\e[m\e]8;;\e\\ \e[16;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\+++ b/notifica\e[16;57Hion/user_notification.go\e(B\e[m\e]8;;\e\\ \e[17;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[18;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+package notification\e(B\e[m\e]8;;\e\\ \e[19;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\\\ No newline at end of file \e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 28─┘\e[33;42H\e(B\e[m\e]8;;\e\\ git reset --hard \e[33;62H8912d\e[33;68He259c\e[33;74Hc0aecf52dccd4a859477821e804\e[?25l" + - delay: 9 + content: "\e[?25l\e[?25l" + - delay: 18 + content: "\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 8 + content: "\e[?25l\e[24;4H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\98912d2e\e[24;16H\e(B\e[m\e[44m\e[1m\e]8;;\e\\Implement user blocking \e[25;4H\e(B\e[m\e[32m\e]8;;\e\\cdfecb7c\e[25;16H\e(B\e[m\e]8;;\e\\Refactor user\e[25;30Hnotifica\e[25;39Hi\e[26;4H\e(B\e[m\e[32m\e]8;;\e\\84b8b\e[26;11Hf\e[26;16H\e(B\e[m\e]8;;\e\\Upgrade R\e[26;26Hils version\e[27;4H\e(B\e[m\e[32m\e]8;;\e\\ffae195d\e[27;16H\e(B\e[m\e]8;;\e\\Move gl\e[27;24Hbal variables \e[27;39Ho\e[28;4H\e(B\e[m\e[32m\e]8;;\e\\e96824\e[28;11He\e[28;16H\e(B\e[m\e]8;;\e\\Fix typos in documentati\e[29;4H\e(B\e[m\e[32m\e]8;;\e\\2c1df171\e[29;16H\e(B\e[m\e]8;;\e\\Optimize\e[29;25HLazygit st\e[29;36Hrtup\e[30;4H\e(B\e[m\e[32m\e]8;;\e\\3ff3c03\e[30;16H\e(B\e[m\e]8;;\e\\Refactor logic in Lazygi\e[31;38H\e(B\e[m\e[32m\e[1m\e]8;;\e\\9\e[?25l\e[?25l\e[?25l" + - delay: 9 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\98912d2e259c\e[2;62Hc0aecf52dccd4a85947782\e[2;85He804\e[6;46H\e(B\e[m\e]8;;\e\\Implement user blocking functionality\e[8;43Huser/blocki\e[8;55Hg.go | 1 \e(B\e[m\e[32m\e]8;;\e\\+\e(B\e[m\e]8;;\e\\ \e[8;77H \e[8;79H \e[8;81H \e[11;55H\e(B\e[m\e[1m\e]8;;\e\\user/blocki\e[11;67Hg.go b/user/blockin\e[11;87H.go\e(B\e[m\e]8;;\e\\ \e[12;42H\e(B\e[m\e[1m\e]8;;\e\\new f\e[12;48Hle mode 100644\e(B\e[m\e]8;;\e\\ \e[13;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..47068eb\e[14;42H--- /dev/null\e(B\e[m\e]8;;\e\\ \e[15;42H\e(B\e[m\e[1m\e]8;;\e\\+++\e[15;46Hb/user/b\e[15;55Hocking.go\e[16;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[17;42H\e(B\e[m\e[32m\e]8;;\e\\+package user\e[18;42H\e(B\e[m\e]8;;\e\\\\ No newline at end of\e[18;65Hfile\e[19;42H \e[19;44H \e[19;47H \e[19;55H \e[19;58H \e[19;62H \e[19;65H \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[?25l\e[?25l\e[?25l" + - delay: 603 + content: "\e[?25l\e[35;25H\e(B\e[m\e[36m\e[1m\e]8;;\e\\z \e[?25l\e[?25l\e[15;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Undo──────────────────────────────────────────────────────────────────────────┐\e[16;21H│\e(B\e[m\e[1m\e]8;;\e\\Are you sure you want to hard reset to \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;21H│\e(B\e[m\e[1m\e]8;;\e\\'25b5642a05887d784b85383cfcf0d55a60bae921'? An auto-stash will be performed if \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H│\e(B\e[m\e[1m\e]8;;\e\\necessary. \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;21H└───────────────────────────────────────────────────────────────────────────────┘\e[23;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 29─┘\e[?25l" + - delay: 1104 + content: "\e[?25l\e[35;25H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l" + - delay: 5 + content: "\e[?25l\e[15;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[1m\e]8;;\e\\+++ b/user/blocking.go\e(B\e[m\e]8;;\e\\ \e[16;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e(B\e[m\e]8;;\e\\ \e[17;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[32m\e]8;;\e\\+package user\e(B\e[m\e]8;;\e\\ \e[18;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\\\ No newline at end of file \e[19;21H \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e[23;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Commits \e(B\e[m\e]8;;\e\\- Reflog\e(B\e[m\e[32m\e[1m\e]8;;\e\\─────────────────────┐\e[24;1H│\e[24;40H▲\e[25;1H│\e[25;40H█\e[26;1H│\e[26;40H│\e[27;1H│\e[27;40H│\e[28;1H│\e[28;40H│\e[29;1H│\e[29;40H│\e[30;1H│\e[30;40H▼\e[31;1H└──────────────────────────────1 of 29─┘\e[33;61H\e(B\e[m\e]8;;\e\\25b564\e[33;68Ha0\e[33;71H887d784b8\e[33;81H383\e[33;85Hfcf0d55a60ba\e[33;98H921\e[?25l" + - delay: 8 + content: "\e[?25l\e[?25l" + - delay: 16 + content: "\e[?25l\e[?25l" + - delay: 5 + content: "\e[?25l\e[?25l\e[?25l\e[24;4H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\25b564\e[24;11Ha\e[24;19H\e(B\e[m\e[44m\e[1m\e]8;;\e\\rov\e[24;23H Dock\e[24;30Hfi\e[24;33He for m\e[25;4H\e(B\e[m\e[32m\e]8;;\e\\98912d2e\e[25;16H\e(B\e[m\e]8;;\e\\Implement user blocking \e[26;4H\e(B\e[m\e[32m\e]8;;\e\\cdfecb7c\e[26;16H\e(B\e[m\e]8;;\e\\Refactor user\e[26;30Hnotifica\e[26;39Hi\e[27;4H\e(B\e[m\e[32m\e]8;;\e\\84b8b\e[27;11Hf\e[27;16H\e(B\e[m\e]8;;\e\\Upgrade R\e[27;26Hils version\e[28;4H\e(B\e[m\e[32m\e]8;;\e\\ffae195d\e[28;16H\e(B\e[m\e]8;;\e\\Move gl\e[28;24Hbal variables \e[28;39Ho\e[29;4H\e(B\e[m\e[32m\e]8;;\e\\e96824\e[29;11He\e[29;16H\e(B\e[m\e]8;;\e\\Fix typos in documentati\e[30;4H\e(B\e[m\e[32m\e]8;;\e\\2c1df171\e[30;16H\e(B\e[m\e]8;;\e\\Optimize\e[30;25HLazygit st\e[30;36Hrtup\e[31;37H\e(B\e[m\e[32m\e[1m\e]8;;\e\\30\e[?25l\e[?25l\e[?25l\e[?25l\e[?25l" + - delay: 8 + content: "\e[?25l\e[2;49H\e(B\e[m\e[33m\e]8;;\e\\25b564\e[2;56Ha0\e[2;59H887d784b8\e[2;69H383\e[2;73Hfcf0d55a60ba\e[2;86H921\e[6;49H\e(B\e[m\e]8;;\e\\rov\e[6;53H Dock\e[6;60Hfi\e[6;63He for more efficient\e[6;84Hbuilds\e[8;43HDockerfile | 1 \e(B\e[m\e[32m\e]8;;\e\\+\e[8;60H\e(B\e[m\e]8;;\e\\ \e[8;62H \e[8;64H \e[11;55H\e(B\e[m\e[1m\e]8;;\e\\Dockerfile b/Dockerfile\e(B\e[m\e]8;;\e\\ \e[13;57H\e(B\e[m\e[1m\e]8;;\e\\311a4a0\e[15;48HDockerfile\e(B\e[m\e]8;;\e\\ \e[17;43H\e(B\e[m\e[32m\e]8;;\e\\FROM ubuntu:18.04\e[?25l\e[?25l\e[?25l" + - delay: 602 + content: "\e[?25l\e[35;16H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;6H \e[35;10H \e[?25l" + - delay: 2001 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/demo/worktree_create_from_branches-compressed.gif b/demo/worktree_create_from_branches-compressed.gif new file mode 100644 index 00000000000..f7c7e30f8dd Binary files /dev/null and b/demo/worktree_create_from_branches-compressed.gif differ diff --git a/demo/worktree_create_from_branches.gif b/demo/worktree_create_from_branches.gif new file mode 100644 index 00000000000..147cf36d2b8 Binary files /dev/null and b/demo/worktree_create_from_branches.gif differ diff --git a/demo/worktree_create_from_branches.yml b/demo/worktree_create_from_branches.yml new file mode 100644 index 00000000000..f003e830d1b --- /dev/null +++ b/demo/worktree_create_from_branches.yml @@ -0,0 +1,207 @@ +# The configurations that used for the recording, feel free to edit them +config: + + # 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: go run cmd/integration_test/main.go cli --slow pkg/integration/tests/demo/worktree_create_from_branches.go + + # Specify the current working directory path + # the default is the current working directory path + cwd: /Users/jesseduffieldduffield/repos/lazygit + + # 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 + fontFamily: "DejaVuSansMono 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" + +# Records, feel free to edit them +records: + - delay: 4012 + content: "\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1049h\e[22;0;0t\e[?1h\e=\e[?25l\e[H\e[2J\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1000h\e[?1002h\e[?1003h\e[?1006h\e[?1004h" + - delay: 72 + content: "\e[?25l\e[1;1H\e(B\e[m\e[30m\e]8;;\e\\┌─Status───────────────────────────────┐┌─Diff─────────────────────────────────────────────────────────────────────────┐\e[2;1H│\e(B\e[m\e]8;;\e\\ repo → \e(B\e[m\e[32m\e]8;;\e\\feature/user-authentication\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\No changed files \e(B\e[m\e[30m\e]8;;\e\\│\e[3;1H└──────────────────────────────────────┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[4;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Files \e(B\e[m\e]8;;\e\\- Worktrees - Submodules\e(B\e[m\e[32m\e[1m\e]8;;\e\\───────┐\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[5;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[7;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[8;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[9;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[10;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[11;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[12;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[13;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└──────────────────────────────────────┘\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[14;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- Remotes - Tags──────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[15;1H│\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/user-authentication\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[16;1H│\e(B\e[m\e[36m\e]8;;\e\\0s\e(B\e[m\e]8;;\e\\ שׂ master \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H└───────────────────────────────1 of 2─┘│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[23;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Commits \e(B\e[m\e[30m\e]8;;\e\\- Reflog─────────────────────┐│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[24;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\7d5ec0ec\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Stop using shims \e(B\e[m\e[30m\e]8;;\e\\▲│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[25;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\b95d4894\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Fix local session storag\e(B\e[m\e[30m\e]8;;\e\\█│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[26;1H│\e(B\e[m\e[33m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[33m\e]8;;\e\\634b8954\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Add user authentication \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[27;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\7c37a089\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Improve Dockerfile for m\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[28;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\9817f309\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Implement user blocking \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[29;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\27f67e6d\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Refactor user notificati\e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[30;1H│\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\8fb31c27\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[38;2;73;40;205m\e]8;;\e\\CI\e(B\e[m\e]8;;\e\\ Upgrade Rails version to\e(B\e[m\e[30m\e]8;;\e\\▼│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[31;1H└──────────────────────────────1 of 33─┘└──────────────────────────────────────────────────────────────────────────────┘\e[32;1H┌─Stash────────────────────────────────┐┌─Command log──────────────────────────────────────────────────────────────────┐\e[33;1H│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[34;1H└───────────────────────────────0 of 0─┘└──────────────────────────────────────────────────────────────────────────────┘\e[35;1H\e(B\e[m\e[34m\e]8;;\e\\/: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-5: Jump to panel, H/L: Scroll left/right \e[?25l\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Unstaged changes\e[5;2H\e(B\e[m\e[44m\e[1m\e]8;;\e\\▼ \e(B\e[m\e[93;44m\e[1m\e]8;;\e\\ src\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e[6;4H\e(B\e[m\e[31m\e]8;;\e\\??  authentication.go\e[7;4H??  session.go\e[8;4H\e(B\e[m\e[32m\e]8;;\e\\A  shims.go \e[13;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1 of 4\e[16;41H\e(B\e[m\e[30m\e]8;;\e\\└──────────────────────────────────────────────────────────────────────────────┘\e[17;41H┌─Staged changes───────────────────────────────────────────────────────────────┐\e[?25l" + - delay: 7 + content: "\e[?25l\e[2;42H\e(B\e[m\e]8;;\e\\ \e[2;45H \e[2;53H \e[18;42H\e(B\e[m\e[1m\e]8;;\e\\diff --git a/src/shims.go b/src/shims.go\e[18;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[19;42H\e(B\e[m\e[1m\e]8;;\e\\new file mode 100644\e[19;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[20;42H\e(B\e[m\e[1m\e]8;;\e\\index 0000000..69dd5b3\e[20;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[21;42H\e(B\e[m\e[1m\e]8;;\e\\--- /dev/null\e[21;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[22;42H\e(B\e[m\e[1m\e]8;;\e\\+++ b/src/shims.go\e[22;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[23;42H\e(B\e[m\e[36m\e]8;;\e\\@@ -0,0 +1 @@\e[23;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[24;42H\e(B\e[m\e[32m\e]8;;\e\\+// removing for now\e[24;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[25;42H\e(B\e[m\e]8;;\e\\\\\e[25;44HNo\e[25;47Hnewline\e[25;55Hat\e[25;58Hend\e[25;62Hof\e[25;65Hfile\e[30;120H\e(B\e[m\e[30m\e]8;;\e\\▼\e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\Create\e[35;8Ha\e[35;10Hworktree\e[35;19Hfrom\e[35;24Ha\e[35;26Hbranch\e[?25l" + - delay: 1002 + content: "\e[?25l\e[35;33H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing 3 \e[?25l" + - delay: 5 + content: "\e[?25l\e[1;43H\e(B\e[m\e[30m\e]8;;\e\\Log─────────────\e[4;1H┌─\e(B\e[m\e[32m\e]8;;\e\\Files \e(B\e[m\e[30m\e]8;;\e\\- Worktrees - Submodules───────┐\e[5;1H│\e(B\e[m\e]8;;\e\\▼ \e(B\e[m\e[33m\e]8;;\e\\ src\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[6;1H│\e[6;40H│\e[7;1H│\e[7;40H│\e[8;1H│\e[8;40H│\e[9;1H│\e[9;40H│\e[10;1H│\e[10;40H│\e[11;1H│\e[11;40H│\e[12;1H│\e[12;40H│\e[13;1H└───────────────────────────────1 of 4─┘\e[14;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Local branches \e(B\e[m\e]8;;\e\\- Remotes - Tags\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────┐\e[15;1H│\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ *\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\שׂ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[92;44m\e[1m\e]8;;\e\\feature/user-authentication\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H│\e[16;40H│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[17;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;40H│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\│\e[18;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;40H│\e[18;42H\e(B\e[m\e]8;;\e\\ \e[18;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;40H│\e[19;42H\e(B\e[m\e]8;;\e\\ \e[19;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[20;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[20;40H│\e[20;42H\e(B\e[m\e]8;;\e\\ \e[20;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[21;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[21;40H│\e[21;42H\e(B\e[m\e]8;;\e\\ \e[21;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[22;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└───────────────────────────────1 of 2─┘\e[22;42H\e(B\e[m\e]8;;\e\\ \e[22;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[23;42H\e(B\e[m\e]8;;\e\\ \e[23;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[24;42H\e(B\e[m\e]8;;\e\\ \e[24;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[25;42H\e(B\e[m\e]8;;\e\\ \e[25;44H \e[25;47H \e[25;55H \e[25;58H \e[25;62H \e[25;65H \e[30;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[?25l" + - delay: 11 + content: "\e[?25l\e[2;42H\e(B\e[m\e]8;;\e\\*\e[2;44H\e(B\e[m\e[33m\e]8;;\e\\commit 7d5ec0e (\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\feature/user-authentication\e(B\e[m\e[33m\e]8;;\e\\)\e[2;120H\e(B\e[m\e[30m\e]8;;\e\\▲\e[3;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[3;44H\e(B\e[m\e]8;;\e\\Author:\e[3;52HCI\e[3;55H\e[3;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[4;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[4;44H\e(B\e[m\e]8;;\e\\Date:\e[4;52H1\e[4;54Hsecond\e[4;61Hago\e[4;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[5;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[6;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[6;48H\e(B\e[m\e]8;;\e\\Stop\e[6;53Husing\e[6;59Hshims\e[6;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[7;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\█\e[8;42H\e(B\e[m\e]8;;\e\\*\e[8;44H\e(B\e[m\e[33m\e]8;;\e\\commit b95d489\e[9;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[9;44H\e(B\e[m\e]8;;\e\\Author:\e[9;52HCI\e[9;55H\e[10;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[10;44H\e(B\e[m\e]8;;\e\\Date:\e[10;52H1\e[10;54Hsecond\e[10;61Hago\e[11;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[12;42H|\e[12;48H\e(B\e[m\e]8;;\e\\Fix\e[12;52Hlocal\e[12;58Hsession\e[12;66Hstorage\e[13;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[14;42H\e(B\e[m\e]8;;\e\\*\e[14;44H\e(B\e[m\e[33m\e]8;;\e\\commit 634b895\e[15;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[15;44H\e(B\e[m\e]8;;\e\\Author:\e[15;52HCI\e[15;55H\e[16;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[16;44H\e(B\e[m\e]8;;\e\\Date:\e[16;52H1\e[16;54Hsecond\e[16;61Hago\e[17;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[18;42H|\e[18;48H\e(B\e[m\e]8;;\e\\Add\e[18;52Huser\e[18;57Hauthentication\e[18;72Hfeature\e[19;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[20;42H\e(B\e[m\e]8;;\e\\*\e[20;44H\e(B\e[m\e[33m\e]8;;\e\\commit 7c37a08 (\e(B\e[m\e[32m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\)\e[21;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[21;44H\e(B\e[m\e]8;;\e\\Author:\e[21;52HCI\e[21;55H\e[22;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[22;44H\e(B\e[m\e]8;;\e\\Date:\e[22;52H1\e[22;54Hsecond\e[22;61Hago\e[23;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[24;42H|\e[24;48H\e(B\e[m\e]8;;\e\\Improve\e[24;56HDockerfile\e[24;67Hfor\e[24;71Hmore\e[24;76Hefficient\e[24;86Hbuilds\e[25;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[26;42H\e(B\e[m\e]8;;\e\\*\e[26;44H\e(B\e[m\e[33m\e]8;;\e\\commit 9817f30\e[27;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[27;44H\e(B\e[m\e]8;;\e\\Author:\e[27;52HCI\e[27;55H\e[28;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[28;44H\e(B\e[m\e]8;;\e\\Date:\e[28;52H1\e[28;54Hsecond\e[28;61Hago\e[29;42H\e(B\e[m\e[31m\e]8;;\e\\|\e[30;42H|\e[30;48H\e(B\e[m\e]8;;\e\\Implement\e[30;58Huser\e[30;63Hblocking\e[30;72Hfunctionality\e[30;120H\e(B\e[m\e[30m\e]8;;\e\\▼\e[?25l\e[?25l\e[?25l" + - delay: 604 + content: "\e[?25l\e[35;33H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[15;2H\e(B\e[m\e[32m\e]8;;\e\\ *\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/user-authentication\e(B\e[m\e]8;;\e\\ \e[16;2H\e(B\e[m\e[96;44m\e[1m\e]8;;\e\\0s\e(B\e[m\e[44m\e[1m\e]8;;\e\\ שׂ master \e[22;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\2\e[?25l" + - delay: 10 + content: "\e[?25l\e[2;52H\e(B\e[m\e[33m\e]8;;\e\\c37a\e[2;57H8\e[2;60H\e(B\e[m\e[32m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\)\e(B\e[m\e]8;;\e\\ \e[4;52H2\e[4;60Hs ago\e[6;48HImprove Dockerfile\e[6;67Hfor\e[6;71Hmore\e[6;76Hefficient\e[6;86Hbuilds\e[7;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[8;51H\e(B\e[m\e[33m\e]8;;\e\\9817f30\e[10;52H\e(B\e[m\e]8;;\e\\2\e[10;60Hs ago\e[12;48HImplement\e[12;58Huser blocking functionality\e[14;51H\e(B\e[m\e[33m\e]8;;\e\\27f67e6\e[16;52H\e(B\e[m\e]8;;\e\\2\e[16;60Hs ago\e[18;48HRefacto\e[18;57Huser \e[18;63Hotifications system\e[20;51H\e(B\e[m\e[33m\e]8;;\e\\8fb31c2\e(B\e[m\e]8;;\e\\ \e[22;52H2\e[22;60Hs ago\e[24;48HUpg\e[24;52Had\e[24;56HRails version to 6.1.4 \e[24;86H \e[26;51H\e(B\e[m\e[33m\e]8;;\e\\d60547\e[28;52H\e(B\e[m\e]8;;\e\\2\e[28;60Hs ago\e[30;48HMove global variables to e\e[30;75Hvir\e[30;80Hmen\e[30;84H config\e[?25l\e[?25l\e[5;120H\e(B\e[m\e[30m\e]8;;\e\\│\e[6;120H│\e[?25l" + - delay: 625 + content: "\e[?25l\e[35;33H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing w \e[?25l\e[?25l\e[14;1H\e(B\e[m\e[30m\e]8;;\e\\┌─\e(B\e[m\e[32m\e]8;;\e\\Local branches \e(B\e[m\e[30m\e]8;;\e\\- R\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Worktree───\e[14;40H─────────────────────────────────────────────────────────────┐\e[15;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[15;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[44m\e[1m\e]8;;\e\\Create worktree from master \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[16;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\Create worktree from mas\e[16;48Hr\e[16;50H(detached) \e[16;62H \e[16;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[17;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e]8;;\e\\Cancel\e[17;40H \e[17;101H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[18;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\└────────────────────────────────────────────────────────────────────────1 of 3─┘\e[19;1H\e(B\e[m\e[30m\e]8;;\e\\│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H│\e[22;1H└───────────────────────────────2 of 2─┘\e[?25l" + - delay: 1103 + content: "\e[?25l\e[35;42H\e(B\e[m\e[36m\e[1m\e]8;;\e\\\e[?25l\e[?25l\e[14;21H\e(B\e[m\e[30m\e]8;;\e\\emotes - Tags──────┐│\e(B\e[m\e]8;;\e\\* \e(B\e[m\e[33m\e]8;;\e\\commit 27f67e6\e(B\e[m\e]8;;\e\\ \e[15;21H\e(B\e[m\e[32m\e]8;;\e\\authentication\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[30m\e]8;;\e\\││\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Author: CI \e[16;21H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─New worktree path─────────────────────────────────────────────────────────────┐\e[17;22H\e(B\e[m\e]8;;\e\\ \e[18;94H\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────\e[17;22H\e[?12l\e[?25h\e[0 q" + - delay: 8 + content: "\e[?25l\e[4;52H\e(B\e[m\e]8;;\e\\4\e[10;52H4\e[22;52H4\e[28;52H4\e[17;22H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;22H\e[?12l\e[?25h\e[0 q" + - delay: 602 + content: "\e[?25l\e[35;33H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[17;22H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;22H\e(B\e[m\e]8;;\e\\.\e[17;23H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;23H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;23H\e(B\e[m\e]8;;\e\\.\e[17;24H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[17;24H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;24H\e(B\e[m\e]8;;\e\\/\e[17;25H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;25H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;25H\e(B\e[m\e]8;;\e\\h\e[17;26H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;26H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;26H\e(B\e[m\e]8;;\e\\o\e[17;27H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;27H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;27H\e(B\e[m\e]8;;\e\\t\e[17;28H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[17;28H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;28H\e(B\e[m\e]8;;\e\\f\e[17;29H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;29H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;29H\e(B\e[m\e]8;;\e\\i\e[17;30H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;30H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;30H\e(B\e[m\e]8;;\e\\x\e[17;31H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[35;33H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[17;31H\e[?12l\e[?25h\e[0 q\e[?25l\e[16;27H\e(B\e[m\e[32m\e[1m\e]8;;\e\\branch name (leave blank to checkout master)\e[17;22H\e(B\e[m\e]8;;\e\\ \e[17;22H\e[?12l\e[?25h\e[0 q" + - delay: 11 + content: "\e[?25l\e[4;52H\e(B\e[m\e]8;;\e\\6\e[10;52H6\e[22;52H6\e[28;52H6\e[17;22H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;22H\e[?12l\e[?25h\e[0 q" + - delay: 604 + content: "\e[?25l\e[35;33H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[17;22H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;22H\e(B\e[m\e]8;;\e\\h\e[17;23H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;23H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;23H\e(B\e[m\e]8;;\e\\o\e[17;24H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;24H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;24H\e(B\e[m\e]8;;\e\\t\e[17;25H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;25H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;25H\e(B\e[m\e]8;;\e\\f\e[17;26H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;26H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;26H\e(B\e[m\e]8;;\e\\i\e[17;27H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;27H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;27H\e(B\e[m\e]8;;\e\\x\e[17;28H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[17;28H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;28H\e(B\e[m\e]8;;\e\\/\e[17;29H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[17;29H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;29H\e(B\e[m\e]8;;\e\\d\e[17;30H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;30H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;30H\e(B\e[m\e]8;;\e\\b\e[17;31H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;31H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;31H\e(B\e[m\e]8;;\e\\-\e[17;32H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;32H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;32H\e(B\e[m\e]8;;\e\\o\e[17;33H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;33H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;33H\e(B\e[m\e]8;;\e\\n\e[17;34H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[17;34H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;34H\e(B\e[m\e]8;;\e\\-\e[17;35H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;35H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;35H\e(B\e[m\e]8;;\e\\f\e[17;36H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[17;36H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;36H\e(B\e[m\e]8;;\e\\i\e[17;37H\e[?12l\e[?25h\e[0 q" + - delay: 123 + content: "\e[?25l\e[17;37H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;37H\e(B\e[m\e]8;;\e\\r\e[17;38H\e[?12l\e[?25h\e[0 q" + - delay: 122 + content: "\e[?25l\e[17;38H\e[?12l\e[?25h\e[0 q\e[?25l\e[17;38H\e(B\e[m\e]8;;\e\\e\e[17;39H\e[?12l\e[?25h\e[0 q" + - delay: 124 + content: "\e[?25l\e[35;33H\e(B\e[m\e[36m\e[1m\e]8;;\e\\Pressing \e[17;39H\e[?12l\e[?25h\e[0 q" + - delay: 5 + content: "\e[?25l\e[14;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\┌─Local branches \e(B\e[m\e]8;;\e\\- Remotes - Tags\e(B\e[m\e[32m\e[1m\e]8;;\e\\──────┐\e[15;1H│\e[15;40H│\e[16;1H│\e[16;21H\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Date: 6 seconds ago \e[17;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[17;21H\e(B\e[m\e]8;;\e\\ \e[17;40H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e[17;101H\e(B\e[m\e]8;;\e\\ \e[18;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[18;21H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e(B\e[m\e[30m\e]8;;\e\\│\e(B\e[m\e[31m\e]8;;\e\\|\e(B\e[m\e]8;;\e\\ Refactor user notifications system \e[19;1H\e(B\e[m\e[32m\e[1m\e]8;;\e\\│\e[19;40H│\e[20;1H│\e[20;40H│\e[21;1H│\e[21;40H│\e[22;1H└───────────────────────────────2 of 2─┘\e[33;44H\e(B\e[m\e]8;;\e\\git\e[33;48Hworktree\e[33;57Hadd\e[33;61H-b\e[33;64Hhotfix/db-on-fire\e[33;82H../hotfix\e[33;92Hmaster\e[?25l" + - delay: 9 + content: "\e[?25l\e[4;52H\e(B\e[m\e]8;;\e\\8\e[10;52H8\e[16;52H8\e[22;52H8\e[28;52H8\e[?25l\e[?25l\e[?25l" + - delay: 37 + content: "\e[?25l\e[?25l" + - delay: 119 + content: "\e[?25l\e[2;7H\e(B\e[m\e]8;;\e\\(\e[2;10H\e(B\e[m\e[36m\e]8;;\e\\hotfix\e(B\e[m\e]8;;\e\\) → \e(B\e[m\e[31m\e]8;;\e\\hotfix/db-on-fire\e[2;60H\e(B\e[m\e[36m\e[1m\e]8;;\e\\HEAD -> \e(B\e[m\e[32m\e[1m\e]8;;\e\\hotfix/db-on-fire\e(B\e[m\e[33m\e]8;;\e\\, \e(B\e[m\e[32m\e[1m\e]8;;\e\\master\e(B\e[m\e[33m\e]8;;\e\\)\e[15;2H\e(B\e[m\e[92;44m\e[1m\e]8;;\e\\ *\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[91;44m\e[1m\e]8;;\e\\שׂ\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e(B\e[m\e[91;44m\e[1m\e]8;;\e\\hotfix/db-on-fire\e(B\e[m\e[44m\e[1m\e]8;;\e\\ \e[16;2H\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\שׂ\e(B\e[m\e]8;;\e\\ \e(B\e[m\e[32m\e]8;;\e\\feature/user-authentication\e(B\e[m\e]8;;\e\\  \e[17;6Hשׂ\e[17;8Hmaster\e[22;33H\e(B\e[m\e[32m\e[1m\e]8;;\e\\1\e[22;38H3\e[24;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[24;4H7c37a089\e[24;16H\e(B\e[m\e]8;;\e\\Improve Dockerfile\e[24;35Hfor\e[24;39Hm\e[25;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[25;4H9817f309\e[25;16H\e(B\e[m\e]8;;\e\\Implement\e[25;26Huser blocking \e[26;2H\e(B\e[m\e[32m\e]8;;\e\\ﰖ\e[26;4H27f67e6d\e[26;16H\e(B\e[m\e]8;;\e\\Refacto\e[26;25Huser \e[26;31Hotificati\e[27;4H\e(B\e[m\e[32m\e]8;;\e\\8fb31c27\e[27;16H\e(B\e[m\e]8;;\e\\Upg\e[27;20Had\e[27;24HRails version to\e[28;4H\e(B\e[m\e[32m\e]8;;\e\\d60547\e[28;11H8\e[28;16H\e(B\e[m\e]8;;\e\\Move global variables to\e[29;4H\e(B\e[m\e[32m\e]8;;\e\\7111\e[29;10H39\e[29;16H\e(B\e[m\e]8;;\e\\Fix typos in document\e[30;4H\e(B\e[m\e[32m\e]8;;\e\\2d9089cb\e[30;16H\e(B\e[m\e]8;;\e\\O\e[30;18Htimize Lazygit \e[30;34Htartup\e[31;38H\e(B\e[m\e[30m\e]8;;\e\\0\e[33;42H\e(B\e[m\e[35m\e]8;;\e\\ Changing directory to ../hotfix \e[?25l\e[?25l\e[5;2H\e(B\e[m\e]8;;\e\\ \e[5;4H \e[6;4H \e[7;4H \e[8;4H \e[13;33H\e(B\e[m\e[30m\e]8;;\e\\0\e[13;38H0\e[?25l" + - delay: 604 + content: "\e[?25l\e[35;33H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[?25l\e[?25l\e[35;1H\e(B\e[m\e[37m\e[1m\e]8;;\e\\ \e[35;8H \e[35;10H \e[35;19H \e[35;24H \e[35;26H \e[?25l" + - delay: 2001 + content: "\e[?12l\e[?25h\e[39;49m\e(B\e[m\e[H\e[2J\e[?1049l\e[23;0;0t\e[?1l\e>\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l\e[?1004l" diff --git a/discord.png b/discord.png new file mode 100644 index 00000000000..c49cc20a020 Binary files /dev/null and b/discord.png differ diff --git a/docs/Config.md b/docs/Config.md deleted file mode 100644 index 614d8d79a64..00000000000 --- a/docs/Config.md +++ /dev/null @@ -1,1190 +0,0 @@ -# User Config - -Default path for the global config file: - -- Linux: `~/.config/lazygit/config.yml` -- MacOS: `~/Library/Application\ Support/lazygit/config.yml` -- Windows: `%LOCALAPPDATA%\lazygit\config.yml` (default location, but it will also be found in `%APPDATA%\lazygit\config.yml` - -For old installations (slightly embarrassing: I didn't realise at the time that you didn't need to supply a vendor name to the path so I just used my name): - -- Linux: `~/.config/jesseduffield/lazygit/config.yml` -- MacOS: `~/Library/Application\ Support/jesseduffield/lazygit/config.yml` -- Windows: `%APPDATA%\jesseduffield\lazygit\config.yml` - -If you want to change the config directory: - -- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"` - -In addition to the global config file you can create repo-specific config files in `/.git/lazygit.yml`. Settings in these files override settings in the global config file. In addition, files called `.lazygit.yml` in any of the parent directories of a repo will also be loaded; this can be useful if you have settings that you want to apply to a group of repositories. - -JSON schema is available for `config.yml` so that IntelliSense in Visual Studio Code (completion and error checking) is automatically enabled when the [YAML Red Hat][yaml] extension is installed. However, note that automatic schema detection only works if your config file is in one of the standard paths mentioned above. If you override the path to the file, you can still make IntelliSense work by adding - -```yaml -# yaml-language-server: $schema=https://raw.githubusercontent.com/jesseduffield/lazygit/master/schema/config.json -``` - -to the top of your config file or via [Visual Studio Code settings.json config][settings]. - -[yaml]: https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml -[settings]: https://github.com/redhat-developer/vscode-yaml#associating-a-schema-to-a-glob-pattern-via-yamlschemas - -## Default - -This is only meant as a reference for what config options exist, and what their default values are. It is not meant to be copied and pasted into your config file as a whole; that's not a good idea for several reasons. It is recommended to include only those settings in your config file that you actually want to change. - - -```yaml -# Config relating to the Lazygit UI -gui: - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color - authorColors: {} - - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color - branchColorPatterns: {} - - # Custom icons for filenames and file extensions - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-files-icon--color - customIcons: - # Map of filenames to icon properties (icon and color) - filenames: {} - - # Map of file extensions (including the dot) to icon properties (icon and color) - extensions: {} - - # The number of lines you scroll by when scrolling the main window - scrollHeight: 2 - - # If true, allow scrolling past the bottom of the content in the main window - scrollPastBottom: true - - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#scroll-off-margin - scrollOffMargin: 2 - - # One of: 'margin' (default) | 'jump' - scrollOffBehavior: margin - - # The number of spaces per tab; used for everything that's shown in the main - # view, but probably mostly relevant for diffs. - # Note that when using a pager, the pager has its own tab width setting, so you - # need to pass it separately in the pager command. - tabWidth: 4 - - # If true, capture mouse events. - # When mouse events are captured, it's a little harder to select text: e.g. - # requiring you to hold the option key when on macOS. - mouseEvents: true - - # If true, do not show a warning when amending a commit. - skipAmendWarning: false - - # If true, do not show a warning when discarding changes in the staging view. - skipDiscardChangeWarning: false - - # If true, do not show warning when applying/popping the stash - skipStashWarning: false - - # If true, do not show a warning when attempting to commit without any staged - # files; instead stage all unstaged files. - skipNoStagedFilesWarning: false - - # If true, do not show a warning when rewording a commit via an external editor - skipRewordInEditorWarning: false - - # If true, switch to a different worktree without confirmation when checking out - # a branch that is checked out in that worktree - skipSwitchWorktreeOnCheckoutWarning: false - - # Fraction of the total screen width to use for the left side section. You may - # want to pick a small number (e.g. 0.2) if you're using a narrow screen, so - # that you can see more of the main section. - # Number from 0 to 1.0. - sidePanelWidth: 0.3333 - - # If true, increase the height of the focused side window; creating an accordion - # effect. - expandFocusedSidePanel: false - - # The weight of the expanded side panel, relative to the other panels. 2 means - # twice as tall as the other panels. Only relevant if `expandFocusedSidePanel` - # is true. - expandedSidePanelWeight: 2 - - # Sometimes the main window is split in two (e.g. when the selected file has - # both staged and unstaged changes). This setting controls how the two sections - # are split. - # Options are: - # - 'horizontal': split the window horizontally - # - 'vertical': split the window vertically - # - 'flexible': (default) split the window horizontally if the window is wide - # enough, otherwise split vertically - mainPanelSplitMode: flexible - - # How the window is split when in half screen mode (i.e. after hitting '+' - # once). - # Possible values: - # - 'left': split the window horizontally (side panel on the left, main view on - # the right) - # - 'top': split the window vertically (side panel on top, main view below) - enlargedSideViewLocation: left - - # If true, wrap lines in the staging view to the width of the view. This makes - # it much easier to work with diffs that have long lines, e.g. paragraphs of - # markdown text. - wrapLinesInStagingView: true - - # If true, hunk selection mode will be enabled by default when entering the - # staging view. - useHunkModeInStagingView: true - - # One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' - # | 'ru' | 'pt' - language: auto - - # Format used when displaying time e.g. commit time. - # Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format - timeFormat: 02 Jan 06 - - # Format used when displaying time if the time is less than 24 hours ago. - # Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format - shortTimeFormat: 3:04PM - - # Config relating to colors and styles. - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes - theme: - # Border color of focused window - activeBorderColor: - - green - - bold - - # Border color of non-focused windows - inactiveBorderColor: - - default - - # Border color of focused window when searching in that window - searchingActiveBorderColor: - - cyan - - bold - - # Color of keybindings help text in the bottom line - optionsTextColor: - - blue - - # Background color of selected line. - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line - selectedLineBgColor: - - blue - - # Background color of selected line when view doesn't have focus. - inactiveViewSelectedLineBgColor: - - bold - - # Foreground color of copied commit - cherryPickedCommitFgColor: - - blue - - # Background color of copied commit - cherryPickedCommitBgColor: - - cyan - - # Foreground color of marked base commit (for rebase) - markedBaseCommitFgColor: - - blue - - # Background color of marked base commit (for rebase) - markedBaseCommitBgColor: - - yellow - - # Color for file with unstaged changes - unstagedChangesColor: - - red - - # Default text color - defaultFgColor: - - default - - # Config relating to the commit length indicator - commitLength: - # If true, show an indicator of commit message length - show: true - - # If true, show the '5 of 20' footer at the bottom of list views - showListFooter: true - - # If true, display the files in the file views as a tree. If false, display the - # files as a flat list. - # This can be toggled from within Lazygit with the '`' key, but that will not - # change the default. - showFileTree: true - - # If true, add a "/" root item in the file tree representing the root of the - # repository. It is only added when necessary, i.e. when there is more than one - # item at top level. - showRootItemInFileTree: true - - # If true, show the number of lines changed per file in the Files view - showNumstatInFilesView: false - - # If true, show a random tip in the command log when Lazygit starts - showRandomTip: true - - # If true, show the command log - showCommandLog: true - - # If true, show the bottom line that contains keybinding info and useful - # buttons. If false, this line will be hidden except to display a loader for an - # in-progress action. - showBottomLine: true - - # If true, show jump-to-window keybindings in window titles. - showPanelJumps: true - - # Nerd fonts version to use. - # One of: '2' | '3' | empty string (default) - # If empty, do not show icons. - nerdFontsVersion: "" - - # If true (default), file icons are shown in the file views. Only relevant if - # NerdFontsVersion is not empty. - showFileIcons: true - - # Length of author name in (non-expanded) commits view. 2 means show initials - # only. - commitAuthorShortLength: 2 - - # Length of author name in expanded commits view. 2 means show initials only. - commitAuthorLongLength: 17 - - # Length of commit hash in commits view. 0 shows '*' if NF icons aren't on. - commitHashLength: 8 - - # If true, show commit hashes alongside branch names in the branches view. - showBranchCommitHash: false - - # Whether to show the divergence from the base branch in the branches view. - # One of: 'none' | 'onlyArrow' | 'arrowAndNumber' - showDivergenceFromBaseBranch: none - - # Height of the command log view - commandLogSize: 8 - - # Whether to split the main window when viewing file changes. - # One of: 'auto' | 'always' - # If 'auto', only split the main window when a file has both staged and unstaged - # changes - splitDiff: auto - - # Default size for focused window. Can be changed from within Lazygit with '+' - # and '_' (but this won't change the default). - # One of: 'normal' (default) | 'half' | 'full' - screenMode: normal - - # Window border style. - # One of 'rounded' (default) | 'single' | 'double' | 'hidden' | 'bold' - border: rounded - - # If true, show a seriously epic explosion animation when nuking the working - # tree. - animateExplosion: true - - # Whether to stack UI components on top of each other. - # One of 'auto' (default) | 'always' | 'never' - portraitMode: auto - - # How things are filtered when typing '/'. - # One of 'substring' (default) | 'fuzzy' - filterMode: substring - - # Config relating to the spinner. - spinner: - # The frames of the spinner animation. - frames: - - '|' - - / - - '-' - - \ - - # The "speed" of the spinner in milliseconds. - rate: 50 - - # Status panel view. - # One of 'dashboard' (default) | 'allBranchesLog' - statusPanelView: dashboard - - # If true, jump to the Files panel after popping a stash - switchToFilesAfterStashPop: true - - # If true, jump to the Files panel after applying a stash - switchToFilesAfterStashApply: true - - # If true, when using the panel jump keys (default 1 through 5) and target panel - # is already active, go to next tab instead - switchTabsWithPanelJumpKeys: false - -# Config relating to git -git: - # Array of pagers. Each entry has the following format: - # - # # Value of the --color arg in the git diff command. Some pagers want - # # this to be set to 'always' and some want it set to 'never' - # colorArg: "always" - # - # # e.g. - # # diff-so-fancy - # # delta --dark --paging=never - # # ydiff -p cat -s --wrap --width={{columnWidth}} - # pager: "" - # - # # e.g. 'difft --color=always' - # externalDiffCommand: "" - # - # # If true, Lazygit will use git's `diff.external` config for paging. - # # The advantage over `externalDiffCommand` is that this can be - # # configured per file type in .gitattributes; see - # # https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver. - # useExternalDiffGitConfig: false - # - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md - # for more information. - pagers: [] - - # Config relating to committing - commit: - # If true, pass '--signoff' flag when committing - signOff: false - - # Automatic WYSIWYG wrapping of the commit message as you type - autoWrapCommitMessage: true - - # If autoWrapCommitMessage is true, the width to wrap to - autoWrapWidth: 72 - - # Config relating to merging - merging: - # If true, run merges in a subprocess so that if a commit message is required, - # Lazygit will not hang - # Only applicable to unix users. - manualCommit: false - - # Extra args passed to `git merge`, e.g. --no-ff - args: "" - - # The commit message to use for a squash merge commit. Can contain - # "{{selectedRef}}" and "{{currentBranch}}" placeholders. - squashMergeMessage: Squash merge {{selectedRef}} into {{currentBranch}} - - # list of branches that are considered 'main' branches, used when displaying - # commits - mainBranches: - - master - - main - - # Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks - # will be skipped when the commit message starts with 'WIP' - skipHookPrefix: WIP - - # If true, periodically fetch from remote - autoFetch: true - - # If true, periodically refresh files and submodules - autoRefresh: true - - # If not "none", lazygit will automatically fast-forward local branches to match - # their upstream after fetching. Applies to branches that are not the currently - # checked out branch, and only to those that are strictly behind their upstream - # (as opposed to diverged). - # Possible values: 'none' | 'onlyMainBranches' | 'allBranches' - autoForwardBranches: onlyMainBranches - - # If true, pass the --all arg to git fetch - fetchAll: true - - # If true, lazygit will automatically stage files that used to have merge - # conflicts but no longer do; and it will also ask you if you want to continue a - # merge or rebase if you've resolved all conflicts. If false, it won't do either - # of these things. - autoStageResolvedConflicts: true - - # Command used when displaying the current branch git log in the main window - branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} -- - - # Commands used to display git log of all branches in the main window, they will - # be cycled in order of appearance (array of strings) - allBranchesLogCmds: - - git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium - - # If true, git diffs are rendered with the `--ignore-all-space` flag, which - # ignores whitespace changes. Can be toggled from within Lazygit with ``. - ignoreWhitespaceInDiffView: false - - # The number of lines of context to show around each diff hunk. Can be changed - # from within Lazygit with the `{` and `}` keys. - diffContextSize: 3 - - # The threshold for considering a file to be renamed, in percent. Can be changed - # from within Lazygit with the `(` and `)` keys. - renameSimilarityThreshold: 50 - - # If true, do not spawn a separate process when using GPG - overrideGpg: false - - # If true, do not allow force pushes - disableForcePushing: false - - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix - commitPrefix: [] - - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix - commitPrefixes: {} - - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix - branchPrefix: "" - - # If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀 - # (This should really be under 'gui', not 'git') - parseEmoji: false - - # Config for showing the log in the commits view - log: - # One of: 'date-order' | 'author-date-order' | 'topo-order' | 'default' - # 'topo-order' makes it easier to read the git log graph, but commits may not - # appear chronologically. See https://git-scm.com/docs/ - # - # Can be changed from within Lazygit with `Log menu -> Commit sort order` - # (`` in the commits window by default). - order: topo-order - - # This determines whether the git graph is rendered in the commits panel - # One of 'always' | 'never' | 'when-maximised' - # - # Can be toggled from within lazygit with `Log menu -> Show git graph` (`` - # in the commits window by default). - showGraph: always - - # displays the whole git graph by default in the commits view (equivalent to - # passing the `--all` argument to `git log`) - showWholeGraph: false - - # How branches are sorted in the local branches view. - # One of: 'date' (default) | 'recency' | 'alphabetical' - # Can be changed from within Lazygit with the Sort Order menu (`s`) in the - # branches panel. - localBranchSortOrder: date - - # How branches are sorted in the remote branches view. - # One of: 'date' (default) | 'alphabetical' - # Can be changed from within Lazygit with the Sort Order menu (`s`) in the - # remote branches panel. - remoteBranchSortOrder: date - - # When copying commit hashes to the clipboard, truncate them to this length. Set - # to 40 to disable truncation. - truncateCopiedCommitHashesTo: 12 - -# Periodic update checks -update: - # One of: 'prompt' (default) | 'background' | 'never' - method: prompt - - # Period in days between update checks - days: 14 - -# Background refreshes -refresher: - # File/submodule refresh interval in seconds. - # Auto-refresh can be disabled via option 'git.autoRefresh'. - refreshInterval: 10 - - # Re-fetch interval in seconds. - # Auto-fetch can be disabled via option 'git.autoFetch'. - fetchInterval: 60 - -# If true, show a confirmation popup before quitting Lazygit -confirmOnQuit: false - -# If true, exit Lazygit when the user presses escape in a context where there is -# nothing to cancel/close -quitOnTopLevelReturn: false - -# Config relating to things outside of Lazygit like how files are opened, -# copying to clipboard, etc -os: - # Command for editing a file. Should contain "{{filename}}". - edit: "" - - # Command for editing a file at a given line number. Should contain - # "{{filename}}", and may optionally contain "{{line}}". - editAtLine: "" - - # Same as EditAtLine, except that the command needs to wait until the window is - # closed. - editAtLineAndWait: "" - - # Whether lazygit suspends until an edit process returns - editInTerminal: false - - # For opening a directory in an editor - openDirInEditor: "" - - # A built-in preset that sets all of the above settings. Supported presets are - # defined in the getPreset function in editor_presets.go. - editPreset: "" - - # Command for opening a file, as if the file is double-clicked. Should contain - # "{{filename}}", but doesn't support "{{line}}". - open: "" - - # Command for opening a link. Should contain "{{link}}". - openLink: "" - - # CopyToClipboardCmd is the command for copying to clipboard. - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard - copyToClipboardCmd: "" - - # ReadFromClipboardCmd is the command for reading the clipboard. - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard - readFromClipboardCmd: "" - - # A shell startup file containing shell aliases or shell functions. This will be - # sourced before running any shell commands, so that shell functions are - # available in the `:` command prompt or even in custom commands. - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#using-aliases-or-functions-in-shell-commands - shellFunctionsFile: "" - -# If true, don't display introductory popups upon opening Lazygit. -disableStartupPopups: false - -# User-configured commands that can be invoked from within Lazygit -# See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Command_Keybindings.md -customCommands: [] - -# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-pull-request-urls -services: {} - -# What to do when opening Lazygit outside of a git repo. -# - 'prompt': (default) ask whether to initialize a new repo or open in the most -# recent repo -# - 'create': initialize a new repo -# - 'skip': open most recent repo -# - 'quit': exit Lazygit -notARepository: prompt - -# If true, display a confirmation when subprocess terminates. This allows you to -# view the output of the subprocess before returning to Lazygit. -promptToReturnFromSubprocess: true - -# Keybindings -keybinding: - universal: - quit: q - quit-alt1: - suspendApp: - return: - quitWithoutChangingDirectory: Q - togglePanel: - prevItem: - nextItem: - prevItem-alt: k - nextItem-alt: j - prevPage: ',' - nextPage: . - scrollLeft: H - scrollRight: L - gotoTop: < - gotoBottom: '>' - gotoTop-alt: - gotoBottom-alt: - toggleRangeSelect: v - rangeSelectDown: - rangeSelectUp: - prevBlock: - nextBlock: - prevBlock-alt: h - nextBlock-alt: l - nextBlock-alt2: - prevBlock-alt2: - jumpToBlock: - - "1" - - "2" - - "3" - - "4" - - "5" - focusMainView: "0" - nextMatch: "n" - prevMatch: "N" - startSearch: / - optionMenu: - optionMenu-alt1: '?' - select: - goInto: - confirm: - confirmMenu: - confirmSuggestion: - confirmInEditor: - confirmInEditor-alt: - remove: d - new: "n" - edit: e - openFile: o - scrollUpMain: - scrollDownMain: - scrollUpMain-alt1: K - scrollDownMain-alt1: J - scrollUpMain-alt2: - scrollDownMain-alt2: - executeShellCommand: ':' - createRebaseOptionsMenu: m - - # 'Files' appended for legacy reasons - pushFiles: P - - # 'Files' appended for legacy reasons - pullFiles: p - refresh: R - createPatchOptionsMenu: - nextTab: ']' - prevTab: '[' - nextScreenMode: + - prevScreenMode: _ - cyclePagers: '|' - undo: z - redo: Z - filteringMenu: - diffingMenu: W - diffingMenu-alt: - copyToClipboard: - openRecentRepos: - submitEditorText: - extrasMenu: '@' - toggleWhitespaceInDiffView: - increaseContextInDiffView: '}' - decreaseContextInDiffView: '{' - increaseRenameSimilarityThreshold: ) - decreaseRenameSimilarityThreshold: ( - openDiffTool: - status: - checkForUpdate: u - recentRepos: - allBranchesLogGraph: a - files: - commitChanges: c - commitChangesWithoutHook: w - amendLastCommit: A - commitChangesWithEditor: C - findBaseCommitForFixup: - confirmDiscard: x - ignoreFile: i - refreshFiles: r - stashAllChanges: s - viewStashOptions: S - toggleStagedAll: a - viewResetOptions: D - fetch: f - toggleTreeView: '`' - openMergeOptions: M - openStatusFilter: - copyFileInfoToClipboard: "y" - collapseAll: '-' - expandAll: = - branches: - createPullRequest: o - viewPullRequestOptions: O - copyPullRequestURL: - checkoutBranchByName: c - forceCheckoutBranch: F - checkoutPreviousBranch: '-' - rebaseBranch: r - renameBranch: R - mergeIntoCurrentBranch: M - moveCommitsToNewBranch: "N" - viewGitFlowOptions: i - fastForward: f - createTag: T - pushTag: P - setUpstream: u - fetchRemote: f - sortOrder: s - worktrees: - viewWorktreeOptions: w - commits: - squashDown: s - renameCommit: r - renameCommitWithEditor: R - viewResetOptions: g - markCommitAsFixup: f - createFixupCommit: F - squashAboveCommits: S - moveDownCommit: - moveUpCommit: - amendToCommit: A - resetCommitAuthor: a - pickCommit: p - revertCommit: t - cherryPickCopy: C - pasteCommits: V - markCommitAsBaseForRebase: B - tagCommit: T - checkoutCommit: - resetCherryPick: - copyCommitAttributeToClipboard: "y" - openLogMenu: - openInBrowser: o - viewBisectOptions: b - startInteractiveRebase: i - selectCommitsOfCurrentBranch: '*' - amendAttribute: - resetAuthor: a - setAuthor: A - addCoAuthor: c - stash: - popStash: g - renameStash: r - commitFiles: - checkoutCommitFile: c - main: - toggleSelectHunk: a - pickBothHunks: b - editSelectHunk: E - submodules: - init: i - update: u - bulkMenu: b - commitMessage: - commitMenu: -``` - - -## Platform Defaults - -### Windows - -```yaml -os: - open: 'start "" {{filename}}' -``` - -### Linux - -```yaml -os: - open: 'xdg-open {{filename}} >/dev/null' -``` - -### OSX - -```yaml -os: - open: 'open {{filename}}' -``` - -## Custom Command for Opening a Link - -```yaml -os: - openLink: 'bash -C /path/to/your/shell-script.sh {{link}}' -``` - -Specify the external command to invoke when opening URL links (i.e. creating MR/PR in GitLab, BitBucket or GitHub). `{{link}}` will be replaced by the URL to be opened. A simple shell script can be used to further mangle the passed URL. - -## Custom Command for Copying to and Pasting from Clipboard - -```yaml -os: - copyToClipboardCmd: '' -``` - -Specify an external command to invoke when copying to clipboard is requested. `{{text}` will be replaced by text to be copied. Default is to copy to system clipboard. - -If you are working on a terminal that supports OSC52, the following command will let you take advantage of it: - -```yaml -os: - copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64 -w 0)\a" > /dev/tty -``` - -For tmux you need to wrap it with the [tmux escape sequence](https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it), and enable passthrough in tmux config with `set -g allow-passthrough on`: - -```yaml -os: - copyToClipboardCmd: printf "\033Ptmux;\033\033]52;c;$(printf {{text}} | base64 -w 0)\a\033\\" > /dev/tty -``` - -For the best of both worlds, we can let the command determine if we are running in a tmux session and send the correct sequence: - -```yaml -os: - copyToClipboardCmd: > - if [[ "$TERM" =~ ^(screen|tmux) ]]; then - printf "\033Ptmux;\033\033]52;c;$(printf {{text}} | base64 -w 0)\a\033\\" > /dev/tty - else - printf "\033]52;c;$(printf {{text}} | base64 -w 0)\a" > /dev/tty - fi -``` - -A custom command for reading from the clipboard can be set using - -```yaml -os: - readFromClipboardCmd: '' -``` - -It is used, for example, when pasting a commit message into the commit message panel. The command is supposed to output the clipboard content to stdout. - -## Configuring File Editing - -There are two commands for opening files, `o` for "open" and `e` for "edit". `o` acts as if the file was double-clicked in the Finder/Explorer, so it also works for non-text files, whereas `e` opens the file in an editor. `e` can also jump to the right line in the file if you invoke it from the staging panel, for example. - -To tell lazygit which editor to use for the `e` command, the easiest way to do that is to provide an editPreset config, e.g. - -```yaml -os: - editPreset: 'vscode' -``` - -Supported presets are `vim`, `nvim`, `nvim-remote`, `lvim`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, `xcode`, `zed` and `acme`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR. - -`nvim-remote` is an experimental preset for when you have invoked lazygit from within a neovim process, allowing lazygit to open the file from within the parent process rather than spawning a new one. - -If for some reason you are not happy with the default commands from a preset, or there simply is no preset for your editor, you can customize the commands by setting the `edit`, `editAtLine`, and `editAtLineAndWait` options, e.g.: - -```yaml -os: - edit: 'myeditor {{filename}}' - editAtLine: 'myeditor --line={{line}} {{filename}}' - editAtLineAndWait: 'myeditor --block --line={{line}} {{filename}}' - editInTerminal: true - openDirInEditor: 'myeditor {{dir}}' -``` - -The `editInTerminal` option is used to decide whether lazygit needs to suspend itself to the background before calling the editor. It should really be named `suspend` because for some cases like when lazygit is opened from within a neovim session and you're using the `nvim-remote` preset, you're technically still in a terminal. Nonetheless we're sticking with the name `editInTerminal` for backwards compatibility. - -Contributions of new editor presets are welcome; see the `getPreset` function in [`editor_presets.go`](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/editor_presets.go). - -## Using aliases or functions in shell commands - -Lazygit has a command prompt (`:`) for quickly executing shell commands without having to quit lazygit or switch to a different terminal. Most people find it convenient to have their usual shell aliases or shell functions available at this prompt. To achieve this, put your alias definitions in a separate shell startup file (which you source from your normal startup file, i.e. from `.bashrc` or `.zshrc`), and then tell lazygit about this file like so: - -```yml -os: - shellFunctionsFile: ~/.my_aliases.sh -``` - -For many people it might work well enough to use their entire shell config file (`~/.bashrc` or `~/.zshrc`) as the `shellFunctionsFile`, but these config files typically do a lot more than defining aliases (e.g. initialize the completion system, start an ssh-agent, etc.) and this may unnecessarily delay execution of shell commands. - -When using zsh, aliases can't be used here, but functions can. It is easy to convert your existing aliases into functions, just change `alias l="ls -la"` to `l() ls -la`, for example. This way it will work as before both in the shell and in lazygit. - -Note that the shell aliases file is not only used when executing shell commands, but also for [custom commands](Custom_Command_Keybindings.md), and when opening a file in the editor. - -## Overriding default config file location - -To override the default config directory, use `CONFIG_DIR="$HOME/.config/lazygit"`. This directory contains the config file in addition to some other files lazygit uses to keep track of state across sessions. - -To override the individual config file used, use the `--use-config-file` arg or the `LG_CONFIG_FILE` env var. - -If you want to merge a specific config file into a more general config file, perhaps for the sake of setting some theme-specific options, you can supply a list of comma-separated config file paths, like so: - -```sh -lazygit --use-config-file="$HOME/.base_lg_conf,$HOME/.light_theme_lg_conf" -or -LG_CONFIG_FILE="$HOME/.base_lg_conf,$HOME/.light_theme_lg_conf" lazygit -``` - -## Scroll-off Margin - -When the selected line gets close to the bottom of the window and you hit down-arrow, there's a feature called "scroll-off margin" that lets the view scroll a little earlier so that you can see a bit of what's coming in the direction that you are moving. This is controlled by the `gui.scrollOffMargin` setting (default: 2), so it keeps 2 lines below the selection visible as you scroll down. It can be set to 0 to scroll only when the selection reaches the bottom of the window. - -That's the behavior when `gui.scrollOffBehavior` is set to "margin" (the default). If you set `gui.scrollOffBehavior` to "jump", then upon reaching the last line of a view and hitting down-arrow the view will scroll by half a page so that the selection ends up in the middle of the view. This may feel a little jarring because the cursor jumps around when continuously moving down, but it has the advantage that the view doesn't scroll as often. - -This setting applies both to all list views (e.g. commits and branches etc), and to the staging view. - -## Filtering - -We have two ways to filter things, substring matching (the default) and fuzzy searching. With substring matching, the text you enter gets searched for verbatim (usually case-insensitive, except when your filter string contains uppercase letters, in which case we search case-sensitively). You can search for multiple non-contiguous substrings by separating them with spaces; for example, "int test" will match "integration-testing". All substrings have to match, but not necessarily in the given order. - -Fuzzy searching is smarter in that it allows every letter of the filter string to match anywhere in the text (only in order though), assigning a weight to the quality of the match and sorting by that order. This has the advantage that it allows typing "clt" to match "commit_loader_test" (letters at the beginning of subwords get more weight); but it has the disadvantage that it tends to return lots of irrelevant results, especially with short filter strings. - -## Color Attributes - -For color attributes you can choose an array of attributes (with max one color attribute) -The available attributes are: - -**Colors** - -- black -- red -- green -- yellow -- blue -- magenta -- cyan -- white -- '#ff00ff' - -**Modifiers** - -- bold -- default -- reverse # useful for high-contrast -- underline -- strikethrough - -## Highlighting the selected line - -If you don't like the default behaviour of highlighting the selected line with a blue background, you can use the `selectedLineBgColor` key to customise the behaviour. If you just want to embolden the selected line (this was the original default), you can do the following: - -```yaml -gui: - theme: - selectedLineBgColor: - - default -``` - -You can also use the reverse attribute like so: - -```yaml -gui: - theme: - selectedLineBgColor: - - reverse -``` - -## Custom Author Color - -Lazygit will assign a random color for every commit author in the commits pane by default. - -You can customize the color in case you're not happy with the randomly assigned one: - -```yaml -gui: - authorColors: - 'John Smith': 'red' # use red for John Smith - 'Alan Smithee': '#00ff00' # use green for Alan Smithee -``` - -You can use wildcard to set a unified color in case your are lazy to customize the color for every author or you just want a single color for all/other authors: - -```yaml -gui: - authorColors: - # use red for John Smith - 'John Smith': 'red' - # use blue for other authors - '*': '#0000ff' -``` - -## Custom Branch Color - -You can customize the color of branches based on branch patterns (regular expressions): - -```yaml -gui: - branchColorPatterns: - '^docs/': '#11aaff' # use a light blue for branches beginning with 'docs/' - 'ISSUE-\d+': '#ff5733' # use a bright orange for branches containing 'ISSUE-' -``` - -Note that the regular expressions are not implicitly anchored to the beginning/end of the branch name. If you want to do that, add leading `^` and/or trailing `$` as needed. - -## Custom Files Icon & Color - -You can customize the icon and color of files based on filenames or extensions: - -```yaml -gui: - customIcons: - filenames: - "CONTRIBUTING.md": { icon: "\uede2", color: "#FEDDEF" } - "HACKING.md": { icon: "\uede2", color: "#FEDDEF" } - extensions: - ".cat": - icon: "\U000f011b" - color: "#BC4009" - ".dog": - icon: "\U000f0a43" - color: "#B6977E" -``` - -Note that there is no support for regular expressions. - -## Example Coloring - -![border example](../../assets/colored-border-example.png) - -## Display Nerd Fonts Icons - -If you are using [Nerd Fonts](https://www.nerdfonts.com), you can display icons. - -```yaml -gui: - nerdFontsVersion: "3" -``` - -Supported versions are "2" and "3". The deprecated config `showIcons` sets the version to "2" for backwards compatibility. - -## Keybindings - -For all possible keybinding options, check [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md) - -You can disable certain key bindings by specifying ``. - -```yaml -keybinding: - universal: - edit: # disable 'edit file' -``` - -### Example Keybindings For Colemak Users - -```yaml -keybinding: - universal: - prevItem-alt: 'u' - nextItem-alt: 'e' - prevBlock-alt: 'n' - nextBlock-alt: 'i' - nextMatch: '=' - prevMatch: '-' - new: 'k' - edit: 'o' - openFile: 'O' - scrollUpMain-alt1: 'U' - scrollDownMain-alt1: 'E' - scrollUpMain-alt2: '' - scrollDownMain-alt2: '' - undo: 'l' - redo: '' - diffingMenu: 'M' - filteringMenu: '' - files: - ignoreFile: 'I' - commits: - moveDownCommit: '' - moveUpCommit: '' - branches: - viewGitFlowOptions: 'I' - setUpstream: 'U' -``` - -## Custom pull request URLs - -Some git provider setups (e.g. on-premises GitLab) can have distinct URLs for git-related calls and the web interface/API itself. To work with those, Lazygit needs to know where it needs to create the pull request. You can do so on your `config.yml` file using the following syntax: - -```yaml -services: - '': ':' -``` - -Where: - -- `gitDomain` stands for the domain used by git itself (i.e. the one present on clone URLs), e.g. `git.work.com` -- `provider` is one of `github`, `bitbucket`, `bitbucketServer`, `azuredevops`, `gitlab` or `gitea` -- `webDomain` is the URL where your git service exposes a web interface and APIs, e.g. `gitservice.work.com` - -## Predefined commit message prefix - -In situations where certain naming pattern is used for branches and commits, pattern can be used to populate commit message with prefix that is parsed from the branch name. -If you define multiple naming patterns, they will be attempted in order until one matches. - -Example hitting first match: - -- Branch name: feature/AB-123 -- Generated commit message prefix: [AB-123] - -Example hitting second match: - -- Branch name: CD-456_fix_problem -- Generated commit message prefix: (CD-456) - -```yaml -git: - commitPrefix: - - pattern: "^\\w+\\/(\\w+-\\w+).*" - replace: '[$1] ' - - pattern: "^([^_]+)_.*" # Take all text prior to the first underscore - replace: '($1) ' -``` - -If you want repository-specific prefixes, you can map them with `commitPrefixes`. If you have both entries in `commitPrefix` defined and an repository match in `commitPrefixes` for the current repo, the `commitPrefixes` entries will be attempted first. Repository folder names must be an exact match. - -```yaml -git: - commitPrefixes: - my_project: # This is repository folder name - - pattern: "^\\w+\\/(\\w+-\\w+).*" - replace: '[$1] ' - commitPrefix: - - pattern: "^(\\w+)-.*" # A more general match for any leading word - replace : '[$1] ' - - pattern: ".*" # The final fallthrough regex that copies over the whole branch name - replace : '[$0] ' -``` - -> [!IMPORTANT] -> The way golang regex works is when you use `$n` in the replacement string, where `n` is a number, it puts the nth captured subgroup at that place. If `n` is out of range because there aren't that many capture groups in the regex, it puts an empty string there. -> -> So make sure you are capturing group or groups in your regex. -> -> For example `^[A-Z]+-\d+$` won't work on branch name like BRANCH-1111 -> But `^([A-Z]+-\d+)$` will - -## Predefined branch name prefix - -In situations where certain naming pattern is used for branches, this can be used to populate new branch creation with a static prefix. - -Example: - -Some branches: - -- jsmith/AB-123 -- cwilson/AB-125 - -```yaml -git: - branchPrefix: "firstlast/" -``` - -It's possible to use a dynamic prefix by using the `runCommand` function: - -```yaml -git: - branchPrefix: "firstlast/{{ runCommand "date +\"%Y/%-m\"" }}/" -``` - -This would produce something like: `firstlast/2025/4/` - -## Custom git log command - -You can override the `git log` command that's used to render the log of the selected branch like so: - -``` -git: - branchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium --oneline {{branchName}} --" -``` - -Result: - -![](https://i.imgur.com/Nibq35B.png) - -## Launching not in a repository behaviour - -By default, when launching lazygit from a directory that is not a repository, you will be prompted to choose if you would like to initialize a repo. You can override this behaviour in the config with one of the following: - -```yaml -# for default prompting behaviour -notARepository: 'prompt' -``` - -```yaml -# to skip and initialize a new repo -notARepository: 'create' -``` - -```yaml -# to skip without creating a new repo -notARepository: 'skip' -``` - -```yaml -# to exit immediately if run outside of the Git repository -notARepository: 'quit' -``` diff --git a/docs/Custom_Command_Keybindings.md b/docs/Custom_Command_Keybindings.md deleted file mode 100644 index aef9c350052..00000000000 --- a/docs/Custom_Command_Keybindings.md +++ /dev/null @@ -1,370 +0,0 @@ -# Custom Command Keybindings - -You can add custom command keybindings in your config.yml (accessible by pressing 'e' on the status panel from within lazygit) like so: - -```yml -customCommands: - - key: '' - context: 'commits' - command: 'hub browse -- "commit/{{.SelectedLocalCommit.Hash}}"' - - key: 'a' - context: 'files' - command: "git {{if .SelectedFile.HasUnstagedChanges}} add {{else}} reset {{end}} {{.SelectedFile.Name | quote}}" - description: 'Toggle file staged' - - key: 'C' - context: 'global' - command: "git commit" - output: terminal - - key: 'n' - context: 'localBranches' - prompts: - - type: 'menu' - title: 'What kind of branch is it?' - key: 'BranchType' - options: - - name: 'feature' - description: 'a feature branch' - value: 'feature' - - name: 'hotfix' - description: 'a hotfix branch' - value: 'hotfix' - - name: 'release' - description: 'a release branch' - value: 'release' - - type: 'input' - title: 'What is the new branch name?' - key: 'BranchName' - initialValue: '' - command: "git flow {{.Form.BranchType}} start {{.Form.BranchName}}" - loadingText: 'Creating branch' -``` - -Looking at the command assigned to the 'n' key, here's what the result looks like: - -![](../../assets/custom-command-keybindings.gif) - -Custom command keybindings will appear alongside inbuilt keybindings when you view the keybindings menu by pressing '?': - -![](https://i.imgur.com/QB21FPx.png) - -For a given custom command, here are the allowed fields: -| _field_ | _description_ | required | -|-----------------|----------------------|-| -| key | The key to trigger the command. Use a single letter or one of the values from [here](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md). Custom commands without a key specified can be triggered by selecting them from the keybindings (`?`) menu | no | -| command | The command to run (using Go template syntax for placeholder values) | yes | -| context | The context in which to listen for the key (see [below](#contexts)) | yes | -| prompts | A list of prompts that will request user input before running the final command | no | -| loadingText | Text to display while waiting for command to finish | no | -| description | Label for the custom command when displayed in the keybindings menu | no | -| output | Where the output of the command should go. 'none' discards it, 'terminal' suspends lazygit and runs the command in the terminal (useful for commands that require user input), 'log' streams it to the command log, 'logWithPty' is like 'log' but runs the command in a pseudo terminal (can be useful for commands that produce colored output when the output is a terminal), and 'popup' shows it in a popup. | no | -| outputTitle | The title to display in the popup panel if output is set to 'popup'. If left unset, the command will be used as the title. | no | -| after | Actions to take after the command has completed | no | - -Here are the options for the `after` key: -| _field_ | _description_ | required | -|-----------------|----------------------|-| -| checkForConflicts | true/false. If true, check for merge conflicts | no | - -## Contexts - -The permitted contexts are: - -| _context_ | _description_ | -| -------------- | -------------------------------------------------------------------------------------------------------- | -| status | The 'Status' tab | -| files | The 'Files' tab | -| worktrees | The 'Worktrees' tab | -| localBranches | The 'Local Branches' tab | -| remotes | The 'Remotes' tab | -| remoteBranches | The context you get when pressing enter on a remote in the remotes tab | -| tags | The 'Tags' tab | -| commits | The 'Commits' tab | -| reflogCommits | The 'Reflog' tab | -| subCommits | The context you see when pressing enter on a branch | -| commitFiles | The context you see when pressing enter on a commit or stash entry (warning, might be renamed in future) | -| stash | The 'Stash' tab | -| global | This keybinding will take affect everywhere | - -> **Bonus** -> -> You can use a comma-separated string, such as `context: 'commits, subCommits'`, to make it effective in multiple contexts. - - -## Prompts - -### Common fields - -These fields are applicable to all prompts. - -| _field_ | _description_ | _required_ | -| ------------ | -----------------------------------------------------------------------------------------------| ---------- | -| type | One of 'input', 'confirm', 'menu', 'menuFromCommand' | yes | -| title | The title to display in the popup panel | no | -| key | Used to reference the entered value from within the custom command. E.g. a prompt with `key: 'Branch'` can be referred to as `{{.Form.Branch}}` in the command | yes | - -### Input - -| _field_ | _description_ | _required_ | -| ------------ | -----------------------------------------------------------------------------------------------| ---------- | -| initialValue | The initial value to appear in the text box | no | -| suggestions | Shows suggestions as the input is entered. See below for details | no | - -The permitted suggestions fields are: -| _field_ | _description_ | _required_ | -|-----------------|----------------------|-| -| preset | Uses built-in logic to obtain the suggestions. One of 'authors', 'branches', 'files', 'refs', 'remotes', 'remoteBranches', 'tags' | no | -| command | Command to run such that each line in the output becomes a suggestion. Mutually exclusive with 'preset' field. | no | - -Here's an example of passing a preset: - -```yml -customCommands: - - key: 'a' - command: 'echo {{.Form.Branch | quote}}' - context: 'commits' - prompts: - - type: 'input' - title: 'Which branch?' - key: 'Branch' - suggestions: - preset: 'branches' # use built-in logic for obtaining branches -``` - -Here's an example of passing a command directly: - -```yml -customCommands: - - key: 'a' - command: 'echo {{.Form.Branch | quote}}' - context: 'commits' - prompts: - - type: 'input' - title: 'Which branch?' - key: 'Branch' - suggestions: - command: "git branch --format='%(refname:short)'" -``` - - -Here's an example of passing an initial value for the input: - -```yml -customCommands: - - key: 'a' - command: 'echo {{.Form.Remote | quote}}' - context: 'commits' - prompts: - - type: 'input' - title: 'Remote:' - key: 'Remote' - initialValue: "{{.SelectedRemote.Name}}" -``` - -### Confirm - -| _field_ | _description_ | _required_ | -| ------------ | -----------------------------------------------------------------------------------------------| ---------- | -| body | The immutable body text to appear in the text box | no | - -Example: - -```yml -customCommands: - - key: 'a' - command: 'echo "pushing to remote"' - context: 'commits' - prompts: - - type: 'confirm' - title: 'Push to remote' - body: 'Are you sure you want to push to the remote?' -``` - -### Menu - -| _field_ | _description_ | _required_ | -| ------------ | -----------------------------------------------------------------------------------------------| ---------- | -| options | The options to display in the menu | yes | - -The permitted option fields are: -| _field_ | _description_ | _required_ | -|-----------------|----------------------|-| -| name | The first part of the label | no | -| description | The second part of the label | no | -| value | the value that will be used in the command | yes | - -If an option has no name the value will be displayed to the user in place of the name, so you're allowed to only include the value like so: - -```yml -customCommands: - - key: 'a' - command: 'echo {{.Form.BranchType | quote}}' - context: 'commits' - prompts: - - type: 'menu' - title: 'What kind of branch is it?' - key: 'BranchType' - options: - - value: 'feature' - - value: 'hotfix' - - value: 'release' -``` - -Here's an example of supplying more detail for each option: - -```yml -customCommands: - - key: 'a' - command: 'echo {{.Form.BranchType | quote}}' - context: 'commits' - prompts: - - type: 'menu' - title: 'What kind of branch is it?' - key: 'BranchType' - options: - - value: 'feature' - name: 'feature branch' - description: 'branch based off develop' - - value: 'hotfix' - name: 'hotfix branch' - description: 'branch based off main for fast bug fixes' - - value: 'release' - name: 'release branch' - description: 'branch for a release' -``` - -### Menu-from-command - -| _field_ | _description_ | _required_ | -| ------------ | -----------------------------------------------------------------------------------------------| ---------- | -| command | The command to run to generate menu options | yes | -| filter | The regexp to run specifying groups which are going to be kept from the command's output | no | -| valueFormat | How to format matched groups from the filter to construct a menu item's value | no | -| labelFormat | Like valueFormat but for the labels. If `labelFormat` is not specified, `valueFormat` is shown instead. | no | - -Here's an example using named groups in the regex. Notice how we can pipe the label to a colour function for coloured output (available colours [here](https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md)) - -```yml - - key : 'a' - description: 'Checkout a remote branch as FETCH_HEAD' - command: "git fetch {{.Form.Remote}} {{.Form.Branch}} && git checkout FETCH_HEAD" - context: 'remotes' - prompts: - - type: 'menuFromCommand' - title: 'Remote branch:' - key: 'Branch' - command: 'git branch -r --list {{.SelectedRemote.Name }}/*' - filter: '.*{{.SelectedRemote.Name }}/(?P.*)' - valueFormat: '{{ .branch }}' - labelFormat: '{{ .branch | green }}' -``` - -Here's an example using unnamed groups: - -```yml - - key : 'a' - description: 'Checkout a remote branch as FETCH_HEAD' - command: "git fetch {{.Form.Remote}} {{.Form.Branch}} && git checkout FETCH_HEAD" - context: 'remotes' - prompts: - - type: 'menuFromCommand' - title: 'Remote branch:' - key: 'Branch' - command: 'git branch -r --list {{.SelectedRemote.Name }}/*' - filter: '.*{{.SelectedRemote.Name }}/(.*)' - valueFormat: '{{ .group_1 }}' - labelFormat: '{{ .group_1 | green }}' -``` - -Here's an example using a command but not specifying anything else: so each line from the command becomes the value and label of the menu items - -```yml - - key : 'a' - description: 'Checkout a remote branch as FETCH_HEAD' - command: "open {{.Form.File | quote}}" - context: 'global' - prompts: - - type: 'menuFromCommand' - title: 'File:' - key: 'File' - command: 'ls' -``` - -## Placeholder values - -Your commands can contain placeholder strings using Go's [template syntax](https://jan.newmarch.name/golang/template/chapter-template.html). The template syntax is pretty powerful, letting you do things like conditionals if you want, but for the most part you'll simply want to be accessing the fields on the following objects: - -``` -SelectedCommit -SelectedCommitRange -SelectedFile -SelectedPath -SelectedLocalBranch -SelectedRemoteBranch -SelectedRemote -SelectedTag -SelectedStashEntry -SelectedCommitFile -SelectedWorktree -CheckedOutBranch -``` - -(For legacy reasons, `SelectedLocalCommit`, `SelectedReflogCommit`, and `SelectedSubCommit` are also available, but they are deprecated.) - - -To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/models.go) (all the modelling lives in the same file). - -We don't support accessing all elements of a range selection yet. We might add this in the future, but as a special case you can access the range of selected commits by using `SelectedCommitRange`, which has two properties `.To` and `.From` which are the hashes of the bottom and top selected commits, respectively. This is useful for passing them to a git command that operates on a range of commits. For example, to create patches for all selected commits, you might use -```yml - command: "git format-patch {{.SelectedCommitRange.From}}^..{{.SelectedCommitRange.To}}" -``` - -We support the following functions: - -### Quoting - -Quote wraps a string in quotes with necessary escaping for the current platform. - -``` -git {{.SelectedFile.Name | quote}} -``` - -### Running a command - -Runs a command and returns the output. If the command outputs more than a single line, it will produce an error. - -``` -initialValue: "username/{{ runCommand "date +\"%Y/%-m\"" }}/" -``` - -## Keybinding collisions - -If your custom keybinding collides with an inbuilt keybinding that is defined for the same context, only the custom keybinding will be executed. This also applies to the global context. However, one caveat is that if you have a custom keybinding defined on the global context for some key, and there is an in-built keybinding defined for the same key and for a specific context (say the 'files' context), then the in-built keybinding will take precedence. See how to change in-built keybindings [here](https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#keybindings) - -## Menus of custom commands - -For custom commands that are not used very frequently it may be preferable to hide them in a menu; you can assign a key to open the menu, and the commands will appear inside. This has the advantage that you don't have to come up with individual unique keybindings for all those commands that you don't use often; the keybindings for the commands in the menu only need to be unique within the menu. Here is an example: - -```yml -customCommands: -- key: X - description: "Copy/paste commits across repos" - commandMenu: - - key: c - command: 'git format-patch --stdout {{.SelectedCommitRange.From}}^..{{.SelectedCommitRange.To}} | pbcopy' - context: commits, subCommits - description: "Copy selected commits to clipboard" - - key: v - command: 'pbpaste | git am' - context: "commits" - description: "Paste selected commits from clipboard" -``` - -If you use the commandMenu property, none of the other properties except key and description can be used. - -## Debugging - -If you want to verify that your command actually does what you expect, you can wrap it in an 'echo' call and set `output: popup` so that it doesn't actually execute the command but you can see how the placeholders were resolved. - -## More Examples - -See the [wiki](https://github.com/jesseduffield/lazygit/wiki/Custom-Commands-Compendium) page for more examples, and feel free to add your own custom commands to this page so others can benefit! diff --git a/docs/Custom_Pagers.md b/docs/Custom_Pagers.md deleted file mode 100644 index c6616cecf7e..00000000000 --- a/docs/Custom_Pagers.md +++ /dev/null @@ -1,116 +0,0 @@ -# Custom Pagers - -Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing `e` in the Status panel). - -Support does not extend to Windows users, because we're making use of a package which doesn't have Windows support. However, see [below](#emulating-custom-pagers-on-windows) for a workaround. - -Multiple pagers are supported; you can cycle through them with the `|` key. This can be useful if you usually prefer a particular pager, but want to use a different one for certain kinds of diffs. - -Pagers are configured with the `pagers` array in the git section; here's an example for a multi-pager setup: - -```yaml -git: - pagers: - - pager: delta --dark --paging=never - - pager: ydiff -p cat -s --wrap --width={{columnWidth}} - colorArg: never - - externalDiffCommand: difft --color=always -``` - -The `colorArg` key is for whether you want the `--color=always` arg in your `git diff` command. Some pagers want it set to `always`, others want it set to `never`. The default is `always`, since that's what most pagers need. - -## Delta: - -```yaml -git: - pagers: - - pager: delta --dark --paging=never -``` - -![](https://i.imgur.com/QJpQkF3.png) - -A cool feature of delta is --hyperlinks, which renders clickable links for the line numbers in the left margin, and lazygit supports these. To use them, set the `pager:` config to `delta --dark --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"`; this allows you to click on an underlined line number in the diff to jump right to that same line in your editor. - -## Diff-so-fancy - -```yaml -git: - pagers: - - pager: diff-so-fancy -``` - -![](https://i.imgur.com/rjH1TpT.png) - -## ydiff - -```yaml -gui: - sidePanelWidth: 0.2 # gives you more space to show things side-by-side -git: - pagers: - - colorArg: never - pager: ydiff -p cat -s --wrap --width={{columnWidth}} -``` - -![](https://i.imgur.com/vaa8z0H.png) - -Be careful with this one, I think the homebrew and pip versions are behind master. I needed to directly download the ydiff script to get the no-pager functionality working. - -## Using external diff commands - -Some diff tools can't work as a simple pager like the ones above do, because they need access to the entire diff, so just post-processing git's diff is not enough for them. The most notable example is probably [difftastic](https://difftastic.wilfred.me.uk). - -These can be used in lazygit by using the `externalDiffCommand` config; in the case of difftastic, that could be - -```yaml -git: - pagers: - - externalDiffCommand: difft --color=always -``` - -The `colorArg` and `pager` options are not used in this case. - -You can add whatever extra arguments you prefer for your difftool; for instance - -```yaml -git: - pagers: - - externalDiffCommand: difft --color=always --display=inline --syntax-highlight=off -``` - -Instead of setting this command in lazygit's `externalDiffCommand` config, you can also tell lazygit to use the external diff command that is configured in git itself (`diff.external`), by using - -```yaml -git: - pagers: - - useExternalDiffGitConfig: true -``` - -This can be useful if you also want to use it for diffs on the command line, and it also has the advantage that you can configure it per file type in `.gitattributes`; see https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver. - -## Emulating custom pagers on Windows - -There is a trick to emulate custom pagers on Windows using a Powershell script configured as an external diff command. It's not perfect, but certainly better than nothing. To do this, save the following script as `lazygit-pager.ps1` at a convenient place on your disk: - -```pwsh -#!/usr/bin/env pwsh - -$old = $args[1].Replace('\', '/') -$new = $args[4].Replace('\', '/') -$path = $args[0] -git diff --no-index --no-ext-diff $old $new - | %{ $_.Replace($old, $path).Replace($new, $path) } - | delta --width=$env:LAZYGIT_COLUMNS -``` - -Use the pager of your choice with the arguments you like in the last line of the script. Personally I wouldn't want to use lazygit anymore without delta's `--hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"` args, see [above](#delta). - -In your lazygit config, use - -```yml -git: - pagers: - - externalDiffCommand: "C:/wherever/lazygit-pager.ps1" -``` - -The main limitation of this approach compared to a "real" pager is that renames are not displayed correctly; they are shown as if they were modifications of the old file. (This affects only the hunk headers; the diff itself is always correct.) diff --git a/docs/Fixup_Commits.md b/docs/Fixup_Commits.md deleted file mode 100644 index d08914f334a..00000000000 --- a/docs/Fixup_Commits.md +++ /dev/null @@ -1,65 +0,0 @@ -# Fixup Commits - -## Background - -There's this common scenario that you have a PR in review, the reviewer is -requesting some changes, and you make those changes and would normally simply -squash them into the original commit that they came from. If you do that, -however, there's no way for the reviewer to see what you changed. You could just -make a separate commit with those changes at the end of the branch, but this is -not ideal because it results in a git history that is not very clean. - -To help with this, git has a concept of fixup commits: you do make a separate -commit, but the subject of this commit is the string "fixup! " followed by the -original commit subject. This both tells the reviewer what's going on (you are -making a change that you later will squash into the designated commit), and it -provides an easy way to actually perform this squash operation when you are -ready to do that (before merging). - -## Creating fixup commits - -You could of course create fixup commits manually by typing in the commit -message with the prefix yourself. But lazygit has an easier way to do that: -in the Commits view, select the commit that you want to create a fixup for, and -press shift-F (for "Create fixup commit for this commit"). This automatically -creates a commit with the appropriate subject line. - -Don't confuse this with the lowercase "f" command ("Fixup commit"); that one -squashes the selected commit into its parent, this is not what we want here. - -## Creating amend commits - -There's a special type of fixup commit that uses "amend!" instead of "fixup!" in -the commit message subject; in addition to fixing up the original commit with -changes it allows you to also (or only) change the commit message of the -original commit. The menu that appears when pressing shift-F has options for -both of these; they bring up a commit message panel similar to when you reword a -commit, but then create the "amend!" commit containing the new message. Note -that in that panel you only type the new message as you want it to be -eventually; lazygit then takes care of formatting the "amend!" commit -appropriately for you (with the subject of your new message moving into the body -of the "amend!" commit). - -## Squashing fixup commits - -When you're ready to merge the branch and want to squash all these fixup commits -that you created, that's very easy to do: select the first commit of your branch -and hit shift-S (for "Squash all 'fixup!' commits above selected commit -(autosquash)"). Boom, done. - -## Finding the commit to create a fixup for - -When you are making changes to code that you changed earlier in a long branch, -it can be tedious to find the commit to squash it into. Lazygit has a command to -help you with this, too: in the Files view, press ctrl-f to select the right -base commit in the Commits view automatically. From there, you can either press -shift-F to create a fixup commit for it, or shift-A to amend your changes into -the commit if you haven't published your branch yet. - -If you have many modifications in your working copy, it is a good idea to stage -related changes that are meant to go into the same fixup commit; if no changes -are staged, ctrl-f works on all unstaged modifications, and then it might show -an error if it finds multiple different base commits. If you are interested in -what the command does to do its magic, and how you can help it work better, you -may want to read the [design document](dev/Find_Base_Commit_For_Fixup_Design.md) -that describes this. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 1bc0bb6be2e..00000000000 --- a/docs/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Documentation Overview - -* [Configuration](./Config.md). -* [Custom Commands](./Custom_Command_Keybindings.md) -* [Custom Pagers](./Custom_Pagers.md) -* [Dev docs](./dev) -* [Keybindings](./keybindings) -* [Undo/Redo](./Undoing.md) -* [Range Select](./Range_Select.md) -* [Searching/Filtering](./Searching.md) -* [Stacked Branches](./Stacked_Branches.md) diff --git a/docs/Range_Select.md b/docs/Range_Select.md deleted file mode 100644 index e46c2689725..00000000000 --- a/docs/Range_Select.md +++ /dev/null @@ -1,14 +0,0 @@ -# Range Select - -Some actions can be performed on a range of contiguous items. For example: -* staging multiple files at once -* squashing multiple commits at once -* copying (for cherry-pick) multiple commits at once - -There are two ways to select a range of items: -1. Sticky range select: Press 'v' to toggle range select, then expand the selection using the up/down arrow key. To reset the selection, press 'v' again. -2. Non-sticky range select: Press shift+up or shift+down to expand the selection. To reset the selection, press up/down without shift. - -The sticky option will be more familiar to vim users, and the second option will feel more natural to users who aren't used to doing things in a modal way. - -In order to perform an action on a range of items, simply press the normal key for that action. If the action only works on individual items, it will raise an error. This is a new feature and the plan is to incrementally support range select for more and more actions. If there is an action you would like to support range select which currently does not, please raise an issue in the repo. diff --git a/docs/Searching.md b/docs/Searching.md deleted file mode 100644 index 589831c55e3..00000000000 --- a/docs/Searching.md +++ /dev/null @@ -1,21 +0,0 @@ -# Searching/Filtering - -## View searching/filtering - -Depending on the currently focused view, hitting '/' will bring up a filter or search prompt. When filtering, the contents of the view will be filtered down to only those lines which match the query string. When searching, the contents of the view are not filtered, but matching lines are highlighted and you can iterate through matches with `n`/`N`. - -We intend to support filtering for the files view soon, but at the moment it uses searching. We intend to continue using search for the commits view because you typically care about the commits that come before/after a matching commit. - -If you would like both filtering and searching to be enabled on a given view, please raise an issue for this. - -## Filtering files by status - -You can filter the files view to only show staged/unstaged files by pressing `` in the files view. - -## Filtering commits by file path - -You can filter the commits view to only show commits which contain changes to a given file path. - -You can do this in a couple of ways: -1) Start lazygit with the -f flag e.g. `lazygit -f my/path` -2) From within lazygit, press `` and then enter the path of the file you want to filter by diff --git a/docs/Stacked_Branches.md b/docs/Stacked_Branches.md deleted file mode 100644 index cd573be2623..00000000000 --- a/docs/Stacked_Branches.md +++ /dev/null @@ -1,18 +0,0 @@ -# Working with stacked branches - -When working on a large branch it can often be useful to break it down into -smaller pieces, and it can help to create separate branches for each independent -chunk of changes. For example, you could have one branch for preparatory -refactorings, one for backend changes, and one for frontend changes. Those -branches would then all be stacked onto each other. - -Git has support for rebasing such a stack as a whole; you can enable it by -setting the git config `rebase.updateRefs` to true. If you then rebase the -topmost branch of the stack, the other ones in the stack will follow. This -includes interactive rebases, so for example amending a commit in the first -branch of the stack will "just work" in the sense that it keeps the other -branches properly stacked onto it. - -Lazygit visualizes the individual branch heads in the stack by marking them with a -cyan asterisk (or a cyan branch symbol if you are using [nerd -fonts](Config.md#display-nerd-fonts-icons)). diff --git a/docs/Undoing.md b/docs/Undoing.md deleted file mode 100644 index 0a4c2f381a2..00000000000 --- a/docs/Undoing.md +++ /dev/null @@ -1,24 +0,0 @@ -# Undo/Redo in lazygit - -You can undo the last action by pressing 'z' and redo with `ctrl+z`. Here we drop a couple of commits and then undo the actions. -Undo uses the reflog which is specific to commits and branches so we can't undo changes to the working tree or stash. - -![undo](../../assets/demo/undo-compressed.gif) - -## How it works - -If you're as clumsy as me you'll probably have felt the pain of botching an interactive rebase or doing a hard reset onto the wrong commit. Luckily, the reflog allows you to trace your steps and make things right again, but I personally can't stand trying to make sense of the reflog. - -Lazygit can read through your reflog for you and walk back action by action so that you don't even need to read the reflog. If lazygit finds a reflog entry where you checked out a branch, we'll checkout the original branch. If the entry is from a commit being applied, we'll go back to the commit before that. If we hit an interactive rebase, we'll go back to the commit you were on just before you started it. - -## You can even undo things you did outside of lazygit! - -Because lazygit just uses the reflog to keep track of things, it doesn't matter whether you're trying to undo something you did in lazygit or directly on the command line. You can open lazygit for the first time and start undoing thing in your repo! Likewise, lazygit marks its undos/redos in the reflog so if you quit the application and come back, lazygit still knows where you're up to. - -## Limitations - -There are limitations: firstly, lazygit can only undo things that are recorded in the reflog. That means changes to your working tree or stash aren't covered. Secondly, anything permanent you do like pushing to a remote can't be undone. Thirdly, actions like creating a branch won't be undone, because they're not stored in the reflog. - -If you are mid-rebase, undo/redo is not supported, because the reflog doesn't contain enough information about what specific things have happened inside that rebase. If you want to undo out of a rebase, it's best to abort the rebase (the default keybinding for bringing up rebase options is 'm'). - -Undo/Redo is a new feature so if you find a bug let us know. The worst case scenario is that you'll just need to look at your reflog and manually put yourself back on track. diff --git a/docs/dev/Busy.md b/docs/dev/Busy.md deleted file mode 100644 index 6aa3b7bb59b..00000000000 --- a/docs/dev/Busy.md +++ /dev/null @@ -1,78 +0,0 @@ -# Knowing when Lazygit is busy/idle - -## The use-case - -This topic deserves its own doc because there are a few touch points for it. We have a use-case for knowing when Lazygit is idle or busy because integration tests follow the following process: -1) press a key -2) wait until Lazygit is idle -3) run assertion / press another key -4) repeat - -In the past the process was: -1) press a key -2) run assertion -3) if assertion fails, wait a bit and retry -4) repeat - -The old process was problematic because an assertion may give a false positive due to the contents of some view not yet having changed since the last key was pressed. - -## The solution - -First, it's important to distinguish three different types of goroutines: -* The UI goroutine, of which there is only one, which infinitely processes a queue of events -* Worker goroutines, which do some work and then typically enqueue an event in the UI goroutine to display the results -* Background goroutines, which periodically spawn worker goroutines (e.g. doing a git fetch every minute) - -The point of distinguishing worker goroutines from background goroutines is that when any worker goroutine is running, we consider Lazygit to be 'busy', whereas this is not the case with background goroutines. It would be pointless to have background goroutines be considered 'busy' because then Lazygit would be considered busy for the entire duration of the program! - -In gocui, the underlying package we use for managing the UI and events, we keep track of how many busy goroutines there are using the `Task` type. A task represents some work being done by lazygit. The gocui Gui struct holds a map of tasks and allows creating a new task (which adds it to the map), pausing/continuing a task, and marking a task as done (which removes it from the map). Lazygit is considered to be busy so long as there is at least one busy task in the map; otherwise it's considered idle. When Lazygit goes from busy to idle, it notifies the integration test. - -It's important that we play by the rules below to ensure that after the user does anything, all the processing that follows happens in a contiguous block of busy-ness with no gaps. - -### Spawning a worker goroutine - -Here's the basic implementation of `OnWorker` (using the same flow as `WaitGroup`s): - -```go -func (g *Gui) OnWorker(f func(*Task)) { - task := g.NewTask() - go func() { - f(task) - task.Done() - }() -} -``` - -The crucial thing here is that we create the task _before_ spawning the goroutine, because it means that we'll have at least one busy task in the map until the completion of the goroutine. If we created the task within the goroutine, the current function could exit and Lazygit would be considered idle before the goroutine starts, leading to our integration test prematurely progressing. - -You typically invoke this with `self.c.OnWorker(f)`. Note that the callback function receives the task. This allows the callback to pause/continue the task (see below). - -### Spawning a background goroutine - -Spawning a background goroutine is as simple as: - -```go -go utils.Safe(f) -``` - -Where `utils.Safe` is a helper function that ensures we clean up the gui if the goroutine panics. - -### Programmatically enqueueing a UI event - -This is invoked with `self.c.OnUIThread(f)`. Internally, it creates a task before enqueuing the function as an event (including the task in the event struct) and once that event is processed by the event queue (and any other pending events are processed) the task is removed from the map by calling `task.Done()`. - -### Pressing a key - -If the user presses a key, an event will be enqueued automatically and a task will be created before (and `Done`'d after) the event is processed. - -## Special cases - -There are a couple of special cases where we manually pause/continue the task directly in the client code. These are subject to change but for the sake of completeness: - -### Writing to the main view(s) - -If the user focuses a file in the files panel, we run a `git diff` command for that file and write the output to the main view. But we only read enough of the command's output to fill the view's viewport: further loading only happens if the user scrolls. Given that we have a background goroutine for running the command and writing more output upon scrolling, we create our own task and call `Done` on it as soon as the viewport is filled. - -### Requesting credentials from a git command - -Some git commands (e.g. git push) may request credentials. This is the same deal as above; we use a worker goroutine and manually pause continue its task as we go from waiting on the git command to waiting on user input. This requires passing the task through to the `Push` method so that it can be paused/continued. diff --git a/docs/dev/Codebase_Guide.md b/docs/dev/Codebase_Guide.md deleted file mode 100644 index 1692be33ea6..00000000000 --- a/docs/dev/Codebase_Guide.md +++ /dev/null @@ -1,100 +0,0 @@ -# Lazygit Codebase Guide - -## Packages - -* `pkg/app`: Contains startup code, initialises a bunch of stuff like logging, the user config, etc, before starting the gui. Catches and handles some errors that the gui raises. -* `pkg/app/daemon`: Contains code relating to the lazygit daemon. This could be better named: it's is not a daemon in the sense that it's a long-running background process; rather it's a short-lived background process that we pass to git for certain tasks, like GIT_EDITOR for when we want to set the TODO file for an interactive rebase. -* `pkg/cheatsheet`: Generates the keybinding cheatsheets in `docs/keybindings`. -* `pkg/commands/git_commands`: All communication to the git binary happens here. So for example there's a `Checkout` method which calls `git checkout`. -* `pkg/commands/oscommands`: Contains code for talking to the OS, and for invoking commands in general -* `pkg/commands/git_config`: Reading of the git config all happens here. -* `pkg/commands/hosting_service`: Contains code that is specific to git hosting services (aka forges). -* `pkg/commands/models`: Contains model structs that represent commits, branches, files, etc. -* `pkg/commands/patch`: Contains code for parsing and working with git patches -* `pkg/common`: Contains the `Common` struct which holds common dependencies like the logger, i18n, and the user config. Most structs in the code will have a field named `c` which holds a common struct (or a derivative of the common struct). -* `pkg/config`: Contains code relating to the Lazygit user config. Specifically `pkg/config/user_config/go` defines the user config struct and its default values. See [below](#using-userconfig) for some important information about using it. -* `pkg/constants`: Contains some constant strings (e.g. links to docs) -* `pkg/env`: Contains code relating to setting/getting environment variables -* `pkg/i18n`: Contains internationalised strings -* `pkg/integration`: Contains end-to-end tests -* `pkg/jsonschema`: Contains generator for user config JSON schema. -* `pkg/logs`: Contains code for instantiating the logger and for tailing the logs via `lazygit --logs` -* `pkg/tasks`: Contains code for running asynchronous tasks: mostly related to efficiently rendering command output to the main window. -* `pkg/theme`: Contains code related to colour themes. -* `pkg/updates`: Contains code related to Lazygit updates (checking for update, download and installing the update) -* `pkg/utils`: Contains lots of low-level helper functions -* `pkg/gui`: Contains code related to the gui. We've still got a God Struct in the form of our Gui struct, but over time code has been moved out into contexts, controllers, and helpers, and we intend to continue moving more code out over time. -* `pkg/gui/context`: Contains code relating to contexts. There is a context for each view e.g. a branches context, a tags context, etc. Contexts manage state related to the view and receive keypresses. -* `pkg/gui/controllers`: Contains code relating to controllers. Controllers define a list of keybindings and their associated handlers. One controller can be assigned to multiple contexts, and one context can contain multiple controllers. -* `pkg/gui/controllers/helpers`: Contains code that is shared between multiple controllers. -* `pkg/gui/filetree`: Contains code relating to the representation of filetrees. -* `pkg/gui/keybindings`: Contains code for mapping between keybindings and their labels -* `pkg/gui/mergeconflicts`: Contains code relating to the handling of merge conflicts -* `pkg/gui/modes`: Contains code relating to the state of different modes e.g. cherry picking mode, rebase mode. -* `pkg/gui/patch_exploring`: Contains code relating to the state of patch-oriented views like the staging view. -* `pkg/gui/popup`: Contains code that lets you easily raise popups -* `pkg/gui/presentation`: Contains presentation code i.e. code concerned with rendering content inside views -* `pkg/gui/services/custom_commands`: Contains code related to user-defined custom commands. -* `pkg/gui/status`: Contains code for invoking loaders and toasts -* `pkg/gui/style`: Contains code for specifying text styles (colour, bold, etc) -* `pkg/gui/types`: Contains various gui-specific types and interfaces. Lots of code lives here to avoid circular dependencies -* `vendor/github.com/jesseduffield/gocui`: Gocui is the underlying library used for handling the gui event loop, handling keypresses, and rendering the UI. It defines the View struct which our own context structs build upon. - -## Important files - -* `pkg/config/user_config.go`: defines the user config and default values -* `pkg/gui/keybindings.go`: defines keybindings which have not yet been moved into a controller (originally all keybindings were defined here) -* `pkg/gui/controllers.go`: links up controllers with contexts -* `pkg/gui/controllers/helpers/helpers.go`: defines all the different helper structs -* `pkg/commands/git.go`: defines all the different git command structs -* `pkg/gui/gui.go`: defines the top-level gui state and gui initialisation/run code -* `pkg/gui/layout.go`: defines what happens on each render -* `pkg/gui/controllers/helpers/window_arrangement_helper.go`: defines the layout of the UI and the size/position of each window -* `pkg/gui/context/context.go`: defines the different contexts -* `pkg/gui/context/setup.go`: defines initialisation code for all contexts -* `pkg/gui/context.go`: manages the lifecycle of contexts, the context stack, and focus changes. -* `pkg/gui/types/views.go`: defines views -* `pkg/gui/views.go`: defines the ordering of views (front to back) and their initialisation code -* `pkg/gui/gui_common.go`: defines gui-specific methods that all controllers and helpers have access to -* `pkg/i18n/english.go`: defines the set of i18n strings and their English values -* `pkg/gui/controllers/helpers/refresh_helper.go`: manages refreshing of models. The refresh helper is typically invoked at the end of an action to re-load affected models from git (e.g. re-load branches after doing a git pull) -* `pkg/gui/controllers/quit_actions.go`: contains code that runs when you hit 'escape' on a view (assuming the view doesn't define its own escape handler) -* `vendor/github.com/jesseduffield/gocui/gui.go`: defines the gocui gui struct -* `vendor/github.com/jesseduffield/gocui/view.go`: defines the gocui view struct - -## Concepts - -* **View**: Views are defined in the gocui package, and they maintain an internal buffer of content which is rendered each time the screen is drawn. -* **Context**: A context is tied to a view and contains some additional state and logic specific to that view e.g. the branches context has code relating specifically to branches, and writes the list of branches to the branches view. Views and contexts share some responsibilities for historical reasons. -* **Controller**: A controller defined keybindings with associated handlers. One controller can be assigned to multiple contexts and one context can have multiple controllers. For example the list controller handles keybindings relating to navigating a list, and is assigned to all list contexts (e.g. the branches context). -* **Helper**: A helper defines shared code used by controllers, or used by some other parts of the application. Often a controller will have a method that ends up needing to be used by another controller, so in that case we move the method out into a helper so that both controllers can use it. We need to do this because controllers cannot refer to other controllers' methods. - -In terms of dependencies, controllers sit at the highest level, so they can refer to helpers, contexts, and views (although it's preferable for view-specific code to live in contexts). Helpers can refer to contexts and views, and contexts can only refer to views. Views can't refer to contexts, controllers, or helpers. - -* **Window**: A window is a section of the screen which will render a view. Windows are named after the default view that appears there, so for example there is a 'stash' window that is so named because by default the stash view appears there. But if you press enter on a stash entry, the stash entry's files will be shown in a different view, but in the same window. -* **Panel**: The term 'panel' is still used in a few places to refer to either a view or a window, and it's a term that is now deprecated in favour of 'view' and 'window'. -* **Tab**: Each tab in a window (e.g. Files, Worktrees, Submodules) actually has a corresponding view which we bring to the front upon changing tabs. -* **Model**: Representation of a git object e.g. commits, branches, files. -* **ViewModel**: Used by a context to maintain state related to the view. -* **Keybinding**: A keybinding associates a _key_ with an _action_. For example if you press the 'down' arrow, the action performed will be your cursor moving down a list by one. -* **Action**: An action is the thing that happens when you press a key. Often an action will invoke a git command, but not always: for example, navigation actions don't involve git. -* **Common structs**: Most structs have a field named `c` which contains a 'common' struct: a struct containing a bag of dependencies that most structs of the same layer require. For example if you want to access a helper from a controller you can do so with `self.c.Helpers.MyHelper`. - -## Event loop and threads - -The event loop is managed in the `MainLoop` function of `vendor/github.com/jesseduffield/gocui/gui.go`. Any time there is an event like a key press or a window resize, the event will be processed and then the screen will be redrawn. This involves calling the `layout` function defined in `pkg/gui/layout.go`, which lays out the windows and invokes some on-render hooks. - -Often, as part of handling a keypress, we'll want to run some code asynchronously so that it doesn't block the UI thread. For this we'll typically run `self.c.OnWorker(myFunc)`. If the worker wants to then do something on the UI thread again it can call `self.c.OnUIThread(myOtherFunc)`. - -## Using UserConfig - -The UserConfig struct is loaded from lazygit's global config file (and possibly repo-specific config files). It can be re-loaded while lazygit is running, e.g. when the user edits one of the config files. In this case we should make sure that any new or changed config values take effect immediately. The easiest way to achieve this is what we do in most controllers or helpers: these have a pointer to the `common.Common` struct, which contains the UserConfig, and access it from there. Since the UserConfig instance in `common.Common` is updated whenever we reload the config, the code can be sure that it always uses an up-to-date value, and there's nothing else to do. - -If that's not possible for some reason, see if you can add code to `Gui.onUserConfigLoaded` to update things from the new config; there are some examples in that function to use as a guide. If that's too hard to do too, add the config to the list in `Gui.checkForChangedConfigsThatDontAutoReload` so that the user is asked to quit and restart lazygit. - -## Legacy code structure - -Before we had controllers and contexts, all the code lived directly in the gui package under a gui God Struct. This was fairly bloated and so we split things out to have a better separation of concerns. Nonetheless, it's a big effort to migrate all the code so we still have some logic in the gui struct that ought to live somewhere else. Likewise, we have some keybindings defined in `pkg/gui/keybindings.go` that ought to live on a controller (all keybindings used to be defined in that one file). - -The new structure has its own problems: we don't have a clear guide on whether code should live in a controller or helper. The current approach is to put code in a controller until it's needed by another controller, and to then extract it out into a helper. We may be better off just putting code in helpers to start with and leaving controllers super-thin, with the responsibility of just pairing keys with corresponding helper functions. But it's not clear to me if that would be better than the current approach. - diff --git a/docs/dev/Demo_Recordings.md b/docs/dev/Demo_Recordings.md deleted file mode 100644 index 1068e688f47..00000000000 --- a/docs/dev/Demo_Recordings.md +++ /dev/null @@ -1,82 +0,0 @@ -# Demo Recordings - -We want our demo recordings to be consistent and easy to update if we make changes to Lazygit's UI. Luckily for us, we have an existing recording system for the sake of our integration tests, so we can piggyback on that. - -You'll want to familiarise yourself with how integration tests are written: see [here](../../pkg/integration/README.md). - -## Prerequisites - -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 - -# 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 -``` - -## Creating a demo - -Demos are found in `pkg/integration/tests/demo/`. They are like regular integration tests but have `IsDemo: true` which has a few effects: -* The bottom row of the UI is quieter so that we can render captions -* Fetch/Push/Pull have artificial latency to mimic a network request -* The loader at the bottom-right does not appear - -In demos, we don't need to be as strict in our assertions as we are in tests. But it's still good to have some basic assertions so that if we automate the process of updating demos we'll know if one of them has broken. - -You can use the same flow as we use with integration tests when you're writing a demo: -* Setup the repo -* Run the demo in sandbox mode to get a feel of what needs to happen -* Come back and write the code to make it happen - -### Adding captions - -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 [gif|mp4] -# e.g. -scripts/record_demo.sh gif 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).~~ - -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/docs/dev/Find_Base_Commit_For_Fixup_Design.md b/docs/dev/Find_Base_Commit_For_Fixup_Design.md deleted file mode 100644 index cf28bdae288..00000000000 --- a/docs/dev/Find_Base_Commit_For_Fixup_Design.md +++ /dev/null @@ -1,229 +0,0 @@ -# About the mechanics of lazygit's "Find base commit for fixup" command - -## Background - -Lazygit has a command called "Find base commit for fixup" that helps with -creating fixup commits. (It is bound to "ctrl-f" by default, and I'll call it -simply "the ctrl-f command" throughout the rest of this text for brevity.) - -It's a heuristic that needs to make a few assumptions; it tends to work well in -practice if users are aware of its limitations. The user-facing side of the -topic is explained [here](../Fixup_Commits.md). In this document we describe how -it works internally, and the design decisions behind it. - -It is also interesting to compare it to the standalone tool -[git-absorb](https://github.com/tummychow/git-absorb) which does a very similar -thing, but made different decisions in some cases. We'll explore these -differences in this document. - -## Design goals - -I'll start with git-absorb's design goals (my interpretation, since I can't -speak for git-absorb's maintainer of course): its main goal seems to be minimum -user interaction required. The idea is that you have a PR in review, the -reviewer requested a bunch of changes, you make all these changes, so you have a -working copy with lots of modified files, and then you fire up git-absorb and it -creates all the necessary fixup commits automatically with no further user -intervention. - -While this sounds attractive, it conflicts with ctrl-f's main design goal, which -is to support creating high-quality fixups. My philosophy is that fixup commits -should have the same high quality standards as normal commits; in particular: - -- they should be atomic. This means that multiple diff hunks that belong - together to form one logical change should be in the same fixup commit. (Not - always possible if the logical change needs to be fixed up into several - different base commits.) -- they should be minimal. Every fixup commit should ideally contain only one - logical change, not several unrelated ones. - -Why is this important? Because fixup commits are mainly a tool for reviewing (if -they weren't, you might as well squash the changes into their base commits right -away). And reviewing fixup commits is easier if they are well-structured, just -like normal commits. - -The only way to achieve this with git-absorb is to set the `oneFixupPerCommit` -config option (for the first goal), and then manually stage the changes that -belong together (for the second). This is close to what you have to do with -ctrl-f, with one exception that we'll get to below. - -But ctrl-f enforces this by refusing to do the job if the staged hunks belong to -more than one base commit. Git-absorb will happily create multiple fixup commits -in this case; ctrl-f doesn't, to enforce that you pay attention to how you group -the changes. There's another reason for this behavior: ctrl-f doesn't create -fixup commits itself (unlike git-absorb), instead it just selects the found base -commit so that the user can decide whether to amend the changes right in, or -create a fixup commit from there (both are single-key commands in lazygit). And -lazygit doesn't support non-contiguous multiselections of commits, but even if -it did, it wouldn't help much in this case. - -## The mechanics - -### General approach - -Git-absorb uses a relatively simple approach, and the benefit is of course that -it is easy to understand: it looks at every diff hunk separately, and for every -hunk it looks at all commits (starting from the newest one backwards) to find -the earliest commit that the change can be amended to without conflicts. - -It is important to realize that "diff hunk" doesn't necessarily mean what you -see in the diff view. Git-absorb and ctrl-f both use a context of 0 when diffing -your code, so they often see more and smaller hunks than users do. For example, -moving a line of code down by one line is a single hunk for users, but it's two -separate hunks for git-absorb and ctrl-f; one for deleting the line at the old -place, and another one for adding the line at the new place, even if it's only -one line further down. - -From this, it follows that there's one big problem with git-absorb's approach: -when moving code, it doesn't realize that the two related hunks of deleting the -code from the old place and inserting it at the new place belong together, and -often it will manage to create a fixup commit for the first hunk, but leave the -other hunk in your working copy as "don't know what to do with this". As an -example, suppose your PR is adding a line of code to an existing function, maybe -one that declares a new variable, and a reviewer suggests to move this line down -a bit, closer to where some other related variables are declared. Moving the -line down results in two diff hunks (from the perspective of git-absorb and -ctrl-f, as they both use a context of 0 when diffing), and when looking at the -second diff hunk in isolation there's no way to find a base commit in your PR -for it, because the surrounding code is already on main. - -To solve this, the ctrl-f command makes a distinction between hunks that have -deleted lines and hunks that have only added lines. If the whole diff contains -any hunks that have deleted lines, it uses only those hunks to determine the -base commit, and then assumes that all the hunks that have only added lines -belong into the same commit. This nicely solves the above example of moving -code, but also other examples such as the following: - -
-Click to show example - -Suppose you have a PR in which you added the following function: - -```go -func findCommit(hash string) (*models.Commit, int, bool) { - for i, commit := range self.c.Model().Commits { - if commit.Hash == hash { - return commit, i, true - } - } - - return nil, -1, false -} -``` - -A reviewer suggests to replace the manual `for` loop with a call to -`lo.FindIndexOf` since that's less code and more idiomatic. So your modification -is this: - -```diff ---- a/my_file.go -+++ b/my_file.go -@@ -12,2 +12,3 @@ import ( - "github.com/jesseduffield/lazygit/pkg/utils" -+ "github.com/samber/lo" - "golang.org/x/sync/errgroup" -@@ -308,9 +309,5 @@ func (self *FixupHelper) blameAddedLines(addedLineHunks []*hunk) ([]string, error - func findCommit(hash string) (*models.Commit, int, bool) { -- for i, commit := range self.c.Model().Commits { -- if commit.Hash == hash { -- return commit, i, true -- } -- } -- -- return nil, -1, false -+ return lo.FindIndexOf(self.c.Model().Commits, func(commit *models.Commit) bool { -+ return commit.Hash == hash -+ }) - } -``` - -If we were to look at these two hunks separately, we'd easily find the base -commit for the second one, but we wouldn't find the one for the first hunk -because the imports around the added import have been on main for a long time. -In fact, git-absorb leaves this hunk in the working copy because it doesn't know -what to do with it. - -
- -Only if there are no hunks with deleted lines does ctrl-f look at the hunks with -only added lines and determines the base commit for them. This solves cases like -adding a comment above a function that you added in your PR. - -The downside of this more complicated approach is that it relies on the user -staging related hunks correctly. However, in my experience this is easy to do -and not very error-prone, as long as users are aware of this behavior. Lazygit -tries to help making them aware of it by showing a warning whenever there are -hunks with only added lines in addition to hunks with deleted lines. - -### Finding the base commit for a given hunk - -As explained above, git-absorb finds the base commit by walking the commits -backwards until it finds one that conflicts with the hunk, and then the found -base commit is the one just before that one. This works reliably, but it is -slow. - -Ctrl-f uses a different approach that is usually much faster, but should always -yield the same result. Again, it makes a distinction between hunks with deleted -lines and hunks with only added lines. For hunks with deleted lines it performs -a line range blame for all the deleted lines (e.g. `git blame -L42,+3 -- -filename`), and if the result is the same for all deleted lines, then that's the -base commit; otherwise it returns an error. - -For hunks with only added lines, it gets a little more complicated. We blame the -single lines just before and just after the hunk (I'll ignore the edge cases of -either of those not existing because the hunk is at the beginning or end of the -file; read the code to see how we handle these cases). If the blame result is -the same for both, then that's the base commit. This is the case of adding a -line in the middle of a block of code that was added in the PR. Otherwise, the -base commit is the more recent of the two (and in this case it doesn't matter if -the other one is an earlier commit in the current branch, or a possibly very old -commit that's already on main). This covers the common case of adding a comment -to a function that was added in the PR, but also adding another line at the end -of a block of code that was added in the base commit. - -It's interesting to discuss what "more recent" means here. You could say if -commit A is an ancestor of commit B (or in other words, A is reachable from B) -then B is the more recent one. And if none of the two commits is reachable from -the other, you have an error case because it's unclear which of the two should -be considered the base commit. The scenario in which this happens is a commit -history like this: - -``` - C---D - / \ -A---B---E---F---G -``` - -where, for instance, D and E are the two blame results. - -Unfortunately, determining the ancestry relationship between two commits using -git commands is a bit expensive and not totally straightforward. Fortunately, -it's not necessary in lazygit because lazygit has the most recent 300 commits -cached in memory, and can simply search its linear list of commits to see which -one is closer to the beginning of the list. If only one of the two commits is -found within those 300 commits, then that's the more recent one; if neither is -found, we assume that both commits are on main and error out. In the merge -scenario pictured above, we arbitrarily return one of the two commits (this will -depend on the log order), but that's probably fine as this scenario should be -extremely rare in practice; in most cases, feature branches are simply linear. - -### Knowing where to stop searching - -Git-absorb needs to know when to stop walking backwards searching for commits, -since it doesn't make sense to create fixups for commits that are already on -main. However, it doesn't know where the current branch ends and main starts, so -it needs to rely on user input for this. By default it searches the most recent -10 commits, but this can be overridden with a config setting. In longer branches -this is often not enough for finding the base commit; but setting it to a higher -value causes the command to take longer to complete when the base commit can't -be found. - -Lazygit doesn't have this problem. For a given blame result it needs to -determine whether that commit is already on main, and if it can find the commit -in its cached list of the first 300 commits it can get that information from -there, because lazygit knows what the user's configured main branches are -(`master` and `main` by default, but it could also include branches like `devel` -or `1.0-hotfixes`), and so it can tell for each commit whether it's contained in -one of those main branches. And if it can't find it among the first 300 commits, -it assumes the commit already on main, on the assumption that no feature branch -has more than 300 commits. diff --git a/docs/dev/Integration_Tests.md b/docs/dev/Integration_Tests.md deleted file mode 100644 index df10c3f8fba..00000000000 --- a/docs/dev/Integration_Tests.md +++ /dev/null @@ -1 +0,0 @@ -see new docs [here](../../pkg/integration/README.md) diff --git a/docs/dev/Profiling.md b/docs/dev/Profiling.md deleted file mode 100644 index bfdffe4f909..00000000000 --- a/docs/dev/Profiling.md +++ /dev/null @@ -1,69 +0,0 @@ -# Profiling Lazygit - -If you want to investigate what's contributing to CPU or memory usage, start -lazygit with the `-profile` command line flag. This tells it to start an -integrated web server that listens for profiling requests. - -## Save profile data - -### CPU - -While lazygit is running with the `-profile` flag, perform a CPU profile and -save it to a file by running this command in another terminal window: - -```sh -curl -o cpu.out http://127.0.0.1:6060/debug/pprof/profile -``` - -By default, it profiles for 30 seconds. To change the duration, use - -```sh -curl -o cpu.out 'http://127.0.0.1:6060/debug/pprof/profile?seconds=60' -``` - -### Memory - -To save a heap profile (containing information about all memory allocated so -far since startup), use - -```sh -curl -o mem.out http://127.0.0.1:6060/debug/pprof/heap -``` - -Sometimes it can be useful to get a delta log, i.e. to see how memory usage -developed from one point in time to another. For that, use - -```sh -curl -o mem.out 'http://127.0.0.1:6060/debug/pprof/heap?seconds=20' -``` - -This will log the memory usage difference between now and 20 seconds later, so -it gives you 20 seconds to perform the action in lazygit that you are interested -in measuring. - -## View profile data - -To display the profile data, you can either use speedscope.app, or the pprof -tool that comes with go. I prefer the former because it has a nicer UI and is a -little more powerful; however, I have seen cases where it wasn't able to load a -profile for some reason, in which case it's good to have the pprof tool as a -fallback. - -### Speedscope.app - -Go to https://www.speedscope.app/ in your browser, and drag the saved profile -onto the browser window. Refer to [the -documentation](https://github.com/jlfwong/speedscope?tab=readme-ov-file#usage) -for how to navigate the data. - -### Pprof tool - -To view a profile that you saved as `cpu.out`, use - -```sh -go tool pprof -http=:8080 cpu.out -``` - -By default this shows the graph view, which I don't find very useful myself. -Choose "Flame Graph" from the View menu to show a much more useful -representation of the data. diff --git a/docs/dev/README.md b/docs/dev/README.md deleted file mode 100644 index fcfcf2741ea..00000000000 --- a/docs/dev/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Dev Documentation Overview - -* [Codebase Guide](./Codebase_Guide.md) -* [Busy/Idle Tracking](./Busy.md) -* [Integration Tests](../../pkg/integration/README.md) -* [Demo Recordings](./Demo_Recordings.md) -* [Find base commit for fixup design](Find_Base_Commit_For_Fixup_Design.md) -* [Profiling](Profiling.md) diff --git a/docs/keybindings/Custom_Keybindings.md b/docs/keybindings/Custom_Keybindings.md deleted file mode 100644 index a2537f0696c..00000000000 --- a/docs/keybindings/Custom_Keybindings.md +++ /dev/null @@ -1,63 +0,0 @@ -## Possible keybindings -| Put in | You will get | -|---------------|----------------| -| `` | F1 | -| `` | F2 | -| `` | F3 | -| `` | F4 | -| `` | F5 | -| `` | F6 | -| `` | F7 | -| `` | F8 | -| `` | F9 | -| `` | F10 | -| `` | F11 | -| `` | F12 | -| `` | Insert | -| `` | Delete | -| `` | Home | -| `` | End | -| `` | Pgup | -| `` | Pgdn | -| `` | ArrowUp | -| `` | ShiftArrowUp | -| `` | ArrowDown | -| `` | ShiftArrowDown | -| `` | ArrowLeft | -| `` | ArrowRight | -| `` | Tab | -| `` | Backtab | -| `` | Enter | -| `` | AltEnter | -| `` | Esc | -| `` | Backspace | -| `` | CtrlSpace | -| `` | CtrlSlash | -| `` | Space | -| `` | CtrlA | -| `` | CtrlB | -| `` | CtrlC | -| `` | CtrlD | -| `` | CtrlE | -| `` | CtrlF | -| `` | CtrlG | -| `` | CtrlJ | -| `` | CtrlK | -| `` | CtrlL | -| `` | CtrlN | -| `` | CtrlO | -| `` | CtrlP | -| `` | CtrlQ | -| `` | CtrlR | -| `` | CtrlS | -| `` | CtrlT | -| `` | CtrlU | -| `` | CtrlV | -| `` | CtrlW | -| `` | CtrlX | -| `` | CtrlY | -| `` | CtrlZ | -| `` | Ctrl4 | -| `` | Ctrl5 | -| `` | Ctrl6 | -| `` | Ctrl8 | diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md deleted file mode 100644 index cf78b8574a8..00000000000 --- a/docs/keybindings/Keybindings_en.md +++ /dev/null @@ -1,413 +0,0 @@ -_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ - -# Lazygit Keybindings - -_Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ - -## Global keybindings - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Switch to a recent repo | | -| `` (fn+up/shift+k) `` | Scroll up main window | | -| `` (fn+down/shift+j) `` | Scroll down main window | | -| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. | -| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | -| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | -| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. | -| `` `` | View custom patch options | | -| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. | -| `` R `` | Refresh | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | -| `` + `` | Next screen mode (normal/half/fullscreen) | | -| `` _ `` | Prev screen mode | | -| `` | `` | Cycle pagers | Choose the next pager in the list of configured pagers | -| `` `` | Cancel | | -| `` ? `` | Open keybindings menu | | -| `` `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. | -| `` W `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` q `` | Quit | | -| `` `` | Suspend the application | | -| `` `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.

The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | -| `` z `` | Undo | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | -| `` Z `` | Redo | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | - -## List panel navigation - -| Key | Action | Info | -|-----|--------|-------------| -| `` , `` | Previous page | | -| `` . `` | Next page | | -| `` < () `` | Scroll to top | | -| `` > () `` | Scroll to bottom | | -| `` v `` | Toggle range select | | -| `` `` | Range select down | | -| `` `` | Range select up | | -| `` / `` | Search the current view by text | | -| `` H `` | Scroll left | | -| `` L `` | Scroll right | | -| `` ] `` | Next tab | | -| `` [ `` | Previous tab | | - -## Commit files - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy path to clipboard | | -| `` y `` | Copy to clipboard | | -| `` c `` | Checkout | Checkout file. This replaces the file in your working tree with the version from the selected commit. | -| `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. | -| `` o `` | Open file | Open file in default application. | -| `` e `` | Edit | Open file in external editor. | -| `` `` | Open external diff tool (git difftool) | | -| `` `` | Toggle file included in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` `` | Enter file / Toggle directory collapsed | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | -| `` ` `` | Toggle file tree view | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | Search the current view by text | | - -## Commit summary - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Confirm | | -| `` `` | Close | | - -## Commits - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy commit hash to clipboard | | -| `` `` | Reset copied (cherry-picked) commits selection | | -| `` b `` | View bisect options | | -| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | -| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | -| `` r `` | Reword | Reword the selected commit's message. | -| `` R `` | Reword with editor | | -| `` d `` | Drop | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | -| `` e `` | Edit (start interactive rebase) | Edit the selected commit. Use this to start an interactive rebase from the selected commit. When already mid-rebase, this will mark the selected commit for editing, which means that upon continuing the rebase, the rebase will pause at the selected commit to allow you to make changes. | -| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
If you would instead like to start an interactive rebase from the selected commit, press `e`. | -| `` p `` | Pick | Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase. | -| `` F `` | Create fixup commit | Create 'fixup!' commit for the selected commit. Later on, you can press `S` on this same commit to apply all above fixup commits. | -| `` S `` | Apply fixup commits | Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash). | -| `` `` | Move commit down one | | -| `` `` | Move commit up one | | -| `` V `` | Paste (cherry-pick) | | -| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | -| `` A `` | Amend | Amend commit with staged changes. If the selected commit is the HEAD commit, this will perform `git commit --amend`. Otherwise the commit will be amended via a rebase. | -| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. | -| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | -| `` T `` | Tag commit | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | -| `` `` | View log options | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | -| `` `` | Checkout | Checkout the selected commit as a detached HEAD. | -| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Open commit in browser | | -| `` n `` | Create new branch off of commit | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | View files | | -| `` w `` | View worktree options | | -| `` / `` | Search the current view by text | | - -## Confirmation panel - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Confirm | | -| `` `` | Close/Cancel | | -| `` `` | Copy to clipboard | | - -## Files - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy path to clipboard | | -| `` `` | Stage | Toggle staged for selected file. | -| `` `` | Filter files by status | | -| `` y `` | Copy to clipboard | | -| `` c `` | Commit | Commit staged changes. | -| `` w `` | Commit changes without pre-commit hook | | -| `` A `` | Amend last commit | | -| `` C `` | Commit changes using git editor | | -| `` `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: | -| `` e `` | Edit | Open file in external editor. | -| `` o `` | Open file | Open file in default application. | -| `` i `` | Ignore or exclude file | | -| `` r `` | Refresh files | | -| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | -| `` S `` | View stash options | View stash options (e.g. stash all, stash staged, stash unstaged). | -| `` a `` | Stage all | Toggle staged/unstaged for all files in working tree. | -| `` `` | Stage lines / Collapse directory | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | -| `` d `` | Discard | View options for discarding changes to the selected file. | -| `` g `` | View upstream reset options | | -| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | -| `` ` `` | Toggle file tree view | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` `` | Open external diff tool (git difftool) | | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` f `` | Fetch | Fetch changes from remote. | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | Search the current view by text | | - -## Input prompt - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Confirm | | -| `` `` | Close/Cancel | | - -## Local branches - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy branch name to clipboard | | -| `` i `` | Show git-flow options | | -| `` `` | Checkout | Checkout selected item. | -| `` n `` | New branch | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` o `` | Create pull request | | -| `` O `` | View create pull request options | | -| `` `` | Copy pull request URL to clipboard | | -| `` c `` | Checkout by name | Checkout by name. In the input box you can enter '-' to switch to the previous branch. | -| `` - `` | Checkout previous branch | | -| `` F `` | Force checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | -| `` d `` | Delete | View delete options for local/remote branch. | -| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. | -| `` M `` | Merge | View options for merging the selected item into the current branch (regular merge, squash merge) | -| `` f `` | Fast-forward | Fast-forward selected branch from its upstream. | -| `` T `` | New tag | | -| `` s `` | Sort order | | -| `` g `` | Reset | | -| `` R `` | Rename branch | | -| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | View commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Main panel (merging) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Pick hunk | | -| `` b `` | Pick all hunks | | -| `` `` | Previous hunk | | -| `` `` | Next hunk | | -| `` `` | Previous conflict | | -| `` `` | Next conflict | | -| `` z `` | Undo | Undo last merge conflict resolution. | -| `` e `` | Edit file | Open file in external editor. | -| `` o `` | Open file | Open file in default application. | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` `` | Return to files panel | | - -## Main panel (normal) - -| Key | Action | Info | -|-----|--------|-------------| -| `` mouse wheel down (fn+up) `` | Scroll down | | -| `` mouse wheel up (fn+down) `` | Scroll up | | -| `` `` | Switch view | Switch to other view (staged/unstaged changes). | -| `` `` | Exit back to side panel | | -| `` / `` | Search the current view by text | | - -## Main panel (patch building) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Go to previous hunk | | -| `` `` | Go to next hunk | | -| `` v `` | Toggle range select | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | Copy selected text to clipboard | | -| `` o `` | Open file | Open file in default application. | -| `` e `` | Edit file | Open file in external editor. | -| `` `` | Toggle lines in patch | | -| `` `` | Exit custom patch builder | | -| `` / `` | Search the current view by text | | - -## Main panel (staging) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Go to previous hunk | | -| `` `` | Go to next hunk | | -| `` v `` | Toggle range select | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | Copy selected text to clipboard | | -| `` `` | Stage | Toggle selection staged / unstaged. | -| `` d `` | Discard | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | -| `` o `` | Open file | Open file in default application. | -| `` e `` | Edit file | Open file in external editor. | -| `` `` | Return to files panel | | -| `` `` | Switch view | Switch to other view (staged/unstaged changes). | -| `` E `` | Edit hunk | Edit selected hunk in external editor. | -| `` c `` | Commit | Commit staged changes. | -| `` w `` | Commit changes without pre-commit hook | | -| `` C `` | Commit changes using git editor | | -| `` `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: | -| `` / `` | Search the current view by text | | - -## Menu - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Execute | | -| `` `` | Close/Cancel | | -| `` / `` | Filter the current view by text | | - -## Reflog - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy commit hash to clipboard | | -| `` `` | Checkout | Checkout the selected commit as a detached HEAD. | -| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Open commit in browser | | -| `` n `` | Create new branch off of commit | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Reset copied (cherry-picked) commits selection | | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | View commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Remote branches - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy branch name to clipboard | | -| `` `` | Checkout | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | -| `` n `` | New branch | | -| `` M `` | Merge | View options for merging the selected item into the current branch (regular merge, squash merge) | -| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. | -| `` d `` | Delete | Delete the remote branch from the remote. | -| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. | -| `` s `` | Sort order | | -| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | View commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Remotes - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | View branches | | -| `` n `` | New remote | | -| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | -| `` e `` | Edit | Edit the selected remote's name or URL. | -| `` f `` | Fetch | Fetch updates from the remote repository. This retrieves new commits and branches without merging them into your local branches. | -| `` / `` | Filter the current view by text | | - -## Secondary - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Switch view | Switch to other view (staged/unstaged changes). | -| `` `` | Exit back to side panel | | -| `` / `` | Search the current view by text | | - -## Stash - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Apply | Apply the stash entry to your working directory. | -| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | -| `` d `` | Drop | Remove the stash entry from the stash list. | -| `` n `` | New branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | -| `` r `` | Rename stash | | -| `` 0 `` | Focus main view | | -| `` `` | View files | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Status - -| Key | Action | Info | -|-----|--------|-------------| -| `` o `` | Open config file | Open file in default application. | -| `` e `` | Edit config file | Open file in external editor. | -| `` u `` | Check for update | | -| `` `` | Switch to a recent repo | | -| `` a `` | Show/cycle all branch logs | | -| `` 0 `` | Focus main view | | - -## Sub-commits - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy commit hash to clipboard | | -| `` `` | Checkout | Checkout the selected commit as a detached HEAD. | -| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Open commit in browser | | -| `` n `` | Create new branch off of commit | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Reset copied (cherry-picked) commits selection | | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | View files | | -| `` w `` | View worktree options | | -| `` / `` | Search the current view by text | | - -## Submodules - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy submodule name to clipboard | | -| `` `` | Enter | Enter submodule. After entering the submodule, you can press `` to escape back to the parent repo. | -| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | -| `` u `` | Update | Update selected submodule. | -| `` n `` | New submodule | | -| `` e `` | Update submodule URL | | -| `` i `` | Initialize | Initialize the selected submodule to prepare for fetching. You probably want to follow this up by invoking the 'update' action to fetch the submodule. | -| `` b `` | View bulk submodule options | | -| `` / `` | Filter the current view by text | | - -## Tags - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy tag to clipboard | | -| `` `` | Checkout | Checkout the selected tag as a detached HEAD. | -| `` n `` | New tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | -| `` d `` | Delete | View delete options for local/remote tag. | -| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. | -| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | View commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Worktrees - -| Key | Action | Info | -|-----|--------|-------------| -| `` n `` | New worktree | | -| `` `` | Switch | Switch to the selected worktree. | -| `` o `` | Open in editor | | -| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. | -| `` / `` | Filter the current view by text | | diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md deleted file mode 100644 index 198578d0929..00000000000 --- a/docs/keybindings/Keybindings_ja.md +++ /dev/null @@ -1,413 +0,0 @@ -_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ - -# Lazygit キーバインディング - -_凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味します_ - -## グローバルキーバインド - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 最近のリポジトリをチェックアウト | | -| `` (fn+up/shift+k) `` | メインウィンドウを上にスクロール | | -| `` (fn+down/shift+j) `` | メインウィンドウを下にスクロール | | -| `` @ `` | コマンドログオプションを表示 | コマンドログのオプションを表示します(例:コマンドログの表示/非表示、コマンドログへのフォーカスなど)。 | -| `` P `` | プッシュ | 現在のブランチを対応するアップストリームブランチにプッシュします。アップストリームが設定されていない場合、アップストリームブランチの設定を求められます。 | -| `` p `` | プル | 現在のブランチのリモートから変更をプルします。アップストリームが設定されていない場合、アップストリームブランチの設定を求められます。 | -| `` ) `` | リネーム検出の類似度しきい値を上げる | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` ( `` | リネーム検出の類似度しきい値を下げる | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` } `` | 差分コンテキストサイズを増やす | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` { `` | 差分コンテキストサイズを減らす | Decrease the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` : `` | シェルコマンドを実行 | 実行するシェルコマンドを入力するプロンプトを表示します。 | -| `` `` | カスタムパッチオプションを表示 | | -| `` m `` | マージ/リベースオプションを表示 | 現在のマージ/リベースを中止/継続/スキップするオプションを表示します。 | -| `` R `` | 更新 | Gitの状態を更新します(`git status`、`git branch`などをバックグラウンドで実行してパネルの内容を更新します)。これは`git fetch`を実行しません。 | -| `` + `` | 次の画面モード(通常/半分/全画面) | | -| `` _ `` | 前の画面モード | | -| `` | `` | Cycle pagers | Choose the next pager in the list of configured pagers | -| `` `` | キャンセル | | -| `` ? `` | キーバインディングメニューを開く | | -| `` `` | フィルターオプションを表示 | コミットログのフィルタリングオプションを表示し、フィルタに一致するコミットのみを表示します。 | -| `` W `` | 差分オプションを表示 | 2つのrefの差分に関連するオプションを表示します(例:選択したrefとの差分表示、差分を取るrefの入力、差分方向の反転など)。 | -| `` `` | 差分オプションを表示 | 2つのrefの差分に関連するオプションを表示します(例:選択したrefとの差分表示、差分を取るrefの入力、差分方向の反転など)。 | -| `` q `` | 終了 | | -| `` `` | Suspend the application | | -| `` `` | 空白表示の切り替え | Toggle whether or not whitespace changes are shown in the diff view.

The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | -| `` z `` | 元に戻す | 最後のgitコマンドを元に戻すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 | -| `` Z `` | やり直す | 最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 | - -## リストパネルのナビゲーション - -| Key | Action | Info | -|-----|--------|-------------| -| `` , `` | 前のページ | | -| `` . `` | 次のページ | | -| `` < () `` | 先頭にスクロール | | -| `` > () `` | 末尾にスクロール | | -| `` v `` | 範囲選択を切り替え | | -| `` `` | 範囲選択を下に | | -| `` `` | 範囲選択を上に | | -| `` / `` | 現在のビューをテキストで検索 | | -| `` H `` | 左にスクロール | | -| `` L `` | 右にスクロール | | -| `` ] `` | 次のタブ | | -| `` [ `` | 前のタブ | | - -## Input prompt - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 確認 | | -| `` `` | 閉じる/キャンセル | | - -## コミット - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | コミットハッシュをクリップボードにコピー | | -| `` `` | コピーされた(チェリーピックされた)コミットの選択をリセット | | -| `` b `` | bisectオプションを表示 | | -| `` s `` | スカッシュ | 選択したコミットをその下のコミットにスカッシュします。スカッシュとは複数のコミットを1つにまとめる操作です。選択したコミットのメッセージが下のコミットに追加されます。 | -| `` f `` | フィックスアップ | 選択したコミットをその下のコミットにマージします。フィックスアップはスカッシュと似ていますが、選択したコミットのメッセージは破棄され、下のコミットのメッセージのみが保持されます。 | -| `` r `` | メッセージ変更 | 選択したコミットのメッセージを変更します。 | -| `` R `` | エディタでメッセージ変更 | | -| `` d `` | 削除 | 選択したコミットを削除します。これはリベースを通じてブランチからコミットを削除します。コミットが後続のコミットが依存する変更を行っている場合、マージコンフリクトを解決する必要があるかもしれません。 | -| `` e `` | 編集(対話型リベースを開始) | 選択したコミットを編集します。これを使用して、選択したコミットから対話型リベースを開始します。すでにリベース中の場合、これは選択したコミットを編集用にマークし、リベースを続行すると、リベースは選択したコミットで一時停止して変更を行えるようにします。 | -| `` i `` | 対話的リベースを開始 | ブランチ上のコミットの対話的リベースを開始します。これには、HEADコミットから最初のマージコミットまたはメインブランチのコミットまでのすべてのコミットが含まれます。
選択したコミットから対話的リベースを開始したい場合は、代わりに `e` を押してください。 | -| `` p `` | ピック | 選択したコミットをピックするようにマークします(リベース中)。これは、リベースを続行すると、コミットが保持されることを意味します。 | -| `` F `` | fixupコミットを作成 | 選択したコミットに対する「fixup!」コミットを作成します。fixupコミットは、選択したコミットの修正用コミットです。後で、同じコミットで `S` を押すと、上記のすべてのfixupコミットが適用されます。 | -| `` S `` | fixupコミットを適用 | すべての「fixup!」コミットを、選択したコミットの上部または現在のブランチ内のすべてをスカッシュします(autosquash)。 | -| `` `` | コミットを1つ下に移動 | | -| `` `` | コミットを1つ上に移動 | | -| `` V `` | ペースト(チェリーピック) | | -| `` B `` | リベース用のベースコミットとしてマーク | 次のリベース用のベースコミットを選択します。ブランチにリベースするとき、ベースコミットより上のコミットのみが持ち込まれます。これは `git rebase --onto` コマンドを使用します。 | -| `` A `` | 修正 | ステージされた変更でコミットを修正します。選択したコミットがHEADコミットの場合、これは `git commit --amend` を実行します。それ以外の場合、コミットはリベースを通じて修正されます。 | -| `` a `` | コミット属性を修正 | コミット作者の設定/リセットまたは共同作者の設定を行います。 | -| `` t `` | リバート | 選択したコミットの変更を逆に適用する、リバートコミットを作成します。 | -| `` T `` | コミットにタグを付ける | 選択したコミットを指すタグを新規作成します。タグ名とオプションの説明を入力するよう促されます。 | -| `` `` | ログオプションを表示 | コミットログのオプションを表示します(例:並び順の変更、Gitグラフの非表示、Gitグラフ全体の表示)。 | -| `` `` | チェックアウト(ブランチの切り替え) | 選択したコミットをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。 | -| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 | -| `` o `` | ブラウザでコミットを開く | | -| `` n `` | コミットから新しいブランチを作成 | | -| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
- ソフトリセット:変更を保持し、ステージされた状態にします
- ミックスリセット:変更を保持し、ステージされていない状態にします
- ハードリセット:すべての変更を破棄します | -| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `` を押して選択をキャンセルできます。 | -| `` `` | 外部差分ツールを開く(git difftool) | | -| `` * `` | 現在のブランチのコミットを選択 | | -| `` 0 `` | メインビューにフォーカス | | -| `` `` | ファイルを表示 | | -| `` w `` | ワークツリーオプションを表示 | | -| `` / `` | 現在のビューをテキストで検索 | | - -## コミットファイル - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | パスをクリップボードにコピー | | -| `` y `` | クリップボードにコピー | | -| `` c `` | チェックアウト(ブランチの切り替え) | ファイルをチェックアウトします。これにより、作業ツリー内のファイルが選択したコミットのバージョンに置き換えられます。 | -| `` d `` | 削除 | このコミットのこのファイルへの変更を破棄します。これはバックグラウンドで対話的なリベースを実行するため、後のコミットでもこのファイルが変更されている場合、マージコンフリクトが発生する可能性があります。 | -| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 | -| `` e `` | 編集 | 外部エディタでファイルを開きます。 | -| `` `` | 外部差分ツールを開く(git difftool) | | -| `` `` | パッチに含めるファイルを切り替え | ファイルがカスタムパッチに含まれるかどうかを切り替えます。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 | -| `` a `` | すべてのファイルを切り替え | コミットのすべてのファイルをカスタムパッチに追加/削除します。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 | -| `` `` | ファイルに入る / ディレクトリの折りたたみを切り替える | ファイルが選択されている場合、そのファイルに入ってカスタムパッチに個々の行を追加/削除できます。ディレクトリが選択されている場合、ディレクトリを切り替えます。 | -| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます | -| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します | -| `` 0 `` | メインビューにフォーカス | | -| `` / `` | 現在のビューをテキストで検索 | | - -## コミット概要 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 確認 | | -| `` `` | 閉じる | | - -## サブコミット - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | コミットハッシュをクリップボードにコピー | | -| `` `` | チェックアウト(ブランチの切り替え) | 選択したコミットをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。 | -| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 | -| `` o `` | ブラウザでコミットを開く | | -| `` n `` | コミットから新しいブランチを作成 | | -| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
- ソフトリセット:変更を保持し、ステージされた状態にします
- ミックスリセット:変更を保持し、ステージされていない状態にします
- ハードリセット:すべての変更を破棄します | -| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `` を押して選択をキャンセルできます。 | -| `` `` | コピーされた(チェリーピックされた)コミットの選択をリセット | | -| `` `` | 外部差分ツールを開く(git difftool) | | -| `` * `` | 現在のブランチのコミットを選択 | | -| `` 0 `` | メインビューにフォーカス | | -| `` `` | ファイルを表示 | | -| `` w `` | ワークツリーオプションを表示 | | -| `` / `` | 現在のビューをテキストで検索 | | - -## サブモジュール - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | サブモジュール名をクリップボードにコピー | | -| `` `` | 入る | サブモジュールに入ります。サブモジュールに入った後、``を押して親リポジトリに戻ることができます。 | -| `` d `` | 削除 | 選択したサブモジュールとそれに対応するディレクトリを削除します。 | -| `` u `` | 更新 | 選択したサブモジュールを更新します。 | -| `` n `` | 新しいサブモジュール | | -| `` e `` | サブモジュールURLを更新 | | -| `` i `` | 初期化 | 選択したサブモジュールを初期化してフェッチの準備をします。おそらく、続いて「更新」アクションを呼び出してサブモジュールをフェッチしたいでしょう。 | -| `` b `` | 一括サブモジュールオプションを表示 | | -| `` / `` | 現在のビューをテキストでフィルタリング | | - -## スタッシュ - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 適用 | スタッシュエントリをワーキングディレクトリに適用します。 | -| `` g `` | ポップ | スタッシュエントリをワーキングディレクトリに適用し、スタッシュエントリを削除します。 | -| `` d `` | 削除 | スタッシュリストからスタッシュエントリを削除します。 | -| `` n `` | 新しいブランチ | 選択したスタッシュエントリから新しいブランチを作成します。これは、スタッシュエントリが作成されたコミットをgitがチェックアウトし、そのコミットから新しいブランチを作成した後、スタッシュエントリを追加のコミットとして新しいブランチに適用することで機能します。 | -| `` r `` | スタッシュの名前を変更 | | -| `` 0 `` | メインビューにフォーカス | | -| `` `` | ファイルを表示 | | -| `` w `` | ワークツリーオプションを表示 | | -| `` / `` | 現在のビューをテキストでフィルタリング | | - -## ステータス - -| Key | Action | Info | -|-----|--------|-------------| -| `` o `` | 設定ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 | -| `` e `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 | -| `` u `` | 更新を確認 | | -| `` `` | 最近のリポジトリをチェックアウト | | -| `` a `` | ブランチログの表示モードを順に切り替え | | -| `` 0 `` | メインビューにフォーカス | | - -## セカンダリ - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 | -| `` `` | サイドパネルに戻る | | -| `` / `` | 現在のビューをテキストで検索 | | - -## タグ - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | タグをクリップボードにコピー | | -| `` `` | チェックアウト(ブランチの切り替え) | 選択したタグをデタッチドHEADとしてチェックアウトします。 | -| `` n `` | 新しいタグを作成 | 現在のコミットから新しいタグを作成します。タグ名とオプションの説明を入力するよう促されます。 | -| `` d `` | 削除 | ローカル/リモートタグの削除オプションを表示します。 | -| `` P `` | タグをプッシュ | 選択したタグをリモートにプッシュします。リモートを選択するよう促されます。 | -| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
- ソフトリセット:変更を保持し、ステージされた状態にします
- ミックスリセット:変更を保持し、ステージされていない状態にします
- ハードリセット:すべての変更を破棄します | -| `` `` | 外部差分ツールを開く(git difftool) | | -| `` 0 `` | メインビューにフォーカス | | -| `` `` | コミットを表示 | | -| `` w `` | ワークツリーオプションを表示 | | -| `` / `` | 現在のビューをテキストでフィルタリング | | - -## ファイル - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | パスをクリップボードにコピー | | -| `` `` | ステージ | 選択したファイルのステージ状態を切り替えます。 | -| `` `` | ステータスでファイルをフィルタリング | | -| `` y `` | クリップボードにコピー | | -| `` c `` | コミット | ステージされた変更をコミットします。 | -| `` w `` | pre-commitフックなしで変更をコミット | | -| `` A `` | 直前のコミットを修正 | | -| `` C `` | Gitエディタを使用して変更をコミット | | -| `` `` | フィックスアップのベースコミットを検索 | 現在の変更が基づいているコミットを見つけて、コミットの修正/フィックスアップを行います。これにより、ブランチのコミットを一つずつ確認して、どのコミットを修正/フィックスアップすべきかを調べる手間が省けます。詳細はドキュメントを参照: | -| `` e `` | 編集 | 外部エディタでファイルを開きます。 | -| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 | -| `` i `` | ファイルを無視または除外 | | -| `` r `` | ファイルを更新 | | -| `` s `` | スタッシュ | すべての変更をスタッシュします。スタッシュの他のバリエーションについては、スタッシュオプションを表示するキーバインディングを使用してください。 | -| `` S `` | スタッシュオプションを表示 | スタッシュオプション(すべてをスタッシュ、ステージされた変更をスタッシュ、ステージされていない変更をスタッシュなど)を表示します。 | -| `` a `` | すべてステージ | ワーキングツリー内のすべてのファイルのステージ/アンステージを切り替えます。 | -| `` `` | 行をステージ / ディレクトリを折りたたむ | 選択された項目がファイルの場合、個々のハンク/行をステージできるようにステージングビューにフォーカスします。選択された項目がディレクトリの場合、ディレクトリを折りたたむ/展開します。 | -| `` d `` | 破棄 | 選択したファイルの変更を破棄するオプションを表示します。 | -| `` g `` | アップストリームへのリセットオプションを表示 | | -| `` D `` | リセット | 作業ツリーのリセットオプション(例:作業ツリーの完全破棄)を表示します。 | -| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` `` | 外部差分ツールを開く(git difftool) | | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` f `` | フェッチ | リモートから変更をフェッチします。 | -| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます | -| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します | -| `` 0 `` | メインビューにフォーカス | | -| `` / `` | 現在のビューをテキストで検索 | | - -## メインパネル(ステージング) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 前のハンクに移動 | | -| `` `` | 次のハンクに移動 | | -| `` v `` | 範囲選択を切り替え | | -| `` a `` | ハンクの選択を切り替える | Toggle line-by-line vs. hunk selection mode. | -| `` `` | 選択したテキストをクリップボードにコピー | | -| `` `` | ステージ | 選択された部分のステージ / アンステージを切り替えます。 | -| `` d `` | 破棄 | ステージされていない変更が選択されている場合、`git reset`を使用して変更を破棄します。ステージされた変更が選択されている場合、変更をアンステージします。 | -| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 | -| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 | -| `` `` | ファイルパネルに戻る | | -| `` `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 | -| `` E `` | ハンクを編集 | 選択したハンクを外部エディタで編集します。 | -| `` c `` | コミット | ステージされた変更をコミットします。 | -| `` w `` | pre-commitフックなしで変更をコミット | | -| `` C `` | Gitエディタを使用して変更をコミット | | -| `` `` | フィックスアップのベースコミットを検索 | 現在の変更が基づいているコミットを見つけて、コミットの修正/フィックスアップを行います。これにより、ブランチのコミットを一つずつ確認して、どのコミットを修正/フィックスアップすべきかを調べる手間が省けます。詳細はドキュメントを参照: | -| `` / `` | 現在のビューをテキストで検索 | | - -## メインパネル(パッチ作成) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 前のハンクに移動 | | -| `` `` | 次のハンクに移動 | | -| `` v `` | 範囲選択を切り替え | | -| `` a `` | ハンクの選択を切り替える | Toggle line-by-line vs. hunk selection mode. | -| `` `` | 選択したテキストをクリップボードにコピー | | -| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 | -| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 | -| `` `` | パッチ内の行を切り替え | | -| `` `` | カスタムパッチビルダーを終了 | | -| `` / `` | 現在のビューをテキストで検索 | | - -## メインパネル(マージ中) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | ハンクを選択 | | -| `` b `` | すべてのハンクを選択 | | -| `` `` | 前のハンク | | -| `` `` | 次のハンク | | -| `` `` | 前のコンフリクト | | -| `` `` | 次のコンフリクト | | -| `` z `` | 元に戻す | 最後のマージコンフリクト解決を元に戻します。 | -| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 | -| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` `` | ファイルパネルに戻る | | - -## メインパネル(通常) - -| Key | Action | Info | -|-----|--------|-------------| -| `` mouse wheel down (fn+up) `` | 下にスクロール | | -| `` mouse wheel up (fn+down) `` | 上にスクロール | | -| `` `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 | -| `` `` | サイドパネルに戻る | | -| `` / `` | 現在のビューをテキストで検索 | | - -## メニュー - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 実行 | | -| `` `` | 閉じる/キャンセル | | -| `` / `` | 現在のビューをテキストでフィルタリング | | - -## リフログ - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | コミットハッシュをクリップボードにコピー | | -| `` `` | チェックアウト(ブランチの切り替え) | 選択したコミットをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。 | -| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 | -| `` o `` | ブラウザでコミットを開く | | -| `` n `` | コミットから新しいブランチを作成 | | -| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
- ソフトリセット:変更を保持し、ステージされた状態にします
- ミックスリセット:変更を保持し、ステージされていない状態にします
- ハードリセット:すべての変更を破棄します | -| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `` を押して選択をキャンセルできます。 | -| `` `` | コピーされた(チェリーピックされた)コミットの選択をリセット | | -| `` `` | 外部差分ツールを開く(git difftool) | | -| `` * `` | 現在のブランチのコミットを選択 | | -| `` 0 `` | メインビューにフォーカス | | -| `` `` | コミットを表示 | | -| `` w `` | ワークツリーオプションを表示 | | -| `` / `` | 現在のビューをテキストでフィルタリング | | - -## リモート - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | ブランチを表示 | | -| `` n `` | 新しいリモート | | -| `` d `` | 削除 | 選択したリモートを削除します。そのリモートからのリモートブランチを追跡しているローカルブランチは影響を受けません。 | -| `` e `` | 編集 | 選択したリモートの名前またはURLを編集します。 | -| `` f `` | フェッチ | リモートリポジトリから更新をフェッチします。これにより、ローカルブランチにマージせずに新しいコミットとブランチを取得します。 | -| `` / `` | 現在のビューをテキストでフィルタリング | | - -## リモートブランチ - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | ブランチ名をクリップボードにコピー | | -| `` `` | チェックアウト(ブランチの切り替え) | 選択したリモートブランチに基づいて新しいローカルブランチをチェックアウトするか、リモートブランチをデタッチドヘッドとしてチェックアウトします。 | -| `` n `` | 新しいブランチ | | -| `` M `` | マージ | 選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ) | -| `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 | -| `` d `` | 削除 | リモートからリモートブランチを削除します。 | -| `` u `` | アップストリームとして設定 | 選択したリモートブランチをチェックアウトされたブランチのアップストリームとして設定します。 | -| `` s `` | 並び順 | | -| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
- ソフトリセット:変更を保持し、ステージされた状態にします
- ミックスリセット:変更を保持し、ステージされていない状態にします
- ハードリセット:すべての変更を破棄します | -| `` `` | 外部差分ツールを開く(git difftool) | | -| `` 0 `` | メインビューにフォーカス | | -| `` `` | コミットを表示 | | -| `` w `` | ワークツリーオプションを表示 | | -| `` / `` | 現在のビューをテキストでフィルタリング | | - -## ローカルブランチ - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | ブランチ名をクリップボードにコピー | | -| `` i `` | git-flowオプションを表示 | | -| `` `` | チェックアウト(ブランチの切り替え) | 選択した項目をチェックアウトします。 | -| `` n `` | 新しいブランチ | | -| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` o `` | プルリクエストを作成 | | -| `` O `` | プルリクエスト作成オプションを表示 | | -| `` `` | プルリクエストURLをクリップボードにコピー | | -| `` c `` | 名前でチェックアウト | 名前でチェックアウトします。入力ボックスに「-」を入力すると、最後のブランチをチェックアウトすることができます。 | -| `` - `` | 直前のブランチにチェックアウト | | -| `` F `` | 強制チェックアウト | 選択したブランチを強制的にチェックアウトします。これにより、選択したブランチをチェックアウトする前にワーキングディレクトリ内のすべてのローカル変更が破棄されます。 | -| `` d `` | 削除 | ローカル/リモートブランチの削除オプションを表示します。 | -| `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 | -| `` M `` | マージ | 選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ) | -| `` f `` | ブランチを最新化(fast-forward) | 選択したブランチを対応するアップストリームの最新状態に追いつかせます(fast-forward)。 | -| `` T `` | 新しいタグを作成 | | -| `` s `` | 並び順 | | -| `` g `` | リセット | | -| `` R `` | ブランチ名を変更 | | -| `` u `` | アップストリームオプションを表示 | ブランチのアップストリームに関連するオプションを表示します(例:アップストリームの設定/解除やアップストリームへのリセット)。 | -| `` `` | 外部差分ツールを開く(git difftool) | | -| `` 0 `` | メインビューにフォーカス | | -| `` `` | コミットを表示 | | -| `` w `` | ワークツリーオプションを表示 | | -| `` / `` | 現在のビューをテキストでフィルタリング | | - -## ワークツリー - -| Key | Action | Info | -|-----|--------|-------------| -| `` n `` | 新しいワークツリー | | -| `` `` | チェックアウト(切り替え) | 選択したワークツリーをチェックアウト(切り替え)します。 | -| `` o `` | エディタで開く | | -| `` d `` | 削除 | 選択したワークツリーを削除します。これはワークツリーのディレクトリとワークツリーに関するメタデータの両方を.gitディレクトリから削除します。 | -| `` / `` | 現在のビューをテキストでフィルタリング | | - -## 確認パネル - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 確認 | | -| `` `` | 閉じる/キャンセル | | -| `` `` | クリップボードにコピー | | diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md deleted file mode 100644 index 5c4f1ad7b3a..00000000000 --- a/docs/keybindings/Keybindings_ko.md +++ /dev/null @@ -1,413 +0,0 @@ -_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ - -# Lazygit 키 바인딩 - -_Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ - -## 글로벌 키 바인딩 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 최근에 사용한 저장소로 전환 | | -| `` (fn+up/shift+k) `` | 메인 패널을 위로 스크롤 | | -| `` (fn+down/shift+j) `` | 메인 패널을 아래로로 스크롤 | | -| `` @ `` | 명령어 로그 메뉴 열기 | View options for the command log e.g. show/hide the command log and focus the command log. | -| `` P `` | 푸시 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | -| `` p `` | 업데이트 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | -| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` } `` | Diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기 | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` { `` | Diff 보기의 변경 사항 주위에 표시되는 컨텍스트 크기 줄이기 | Decrease the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. | -| `` `` | 커스텀 Patch 옵션 보기 | | -| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. | -| `` R `` | 새로고침 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | -| `` + `` | 다음 스크린 모드 (normal/half/fullscreen) | | -| `` _ `` | 이전 스크린 모드 | | -| `` | `` | Cycle pagers | Choose the next pager in the list of configured pagers | -| `` `` | 취소 | | -| `` ? `` | 매뉴 열기 | | -| `` `` | View filter-by-path options | View options for filtering the commit log, so that only commits matching the filter are shown. | -| `` W `` | Diff 메뉴 열기 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` `` | Diff 메뉴 열기 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` q `` | 종료 | | -| `` `` | Suspend the application | | -| `` `` | 공백문자를 Diff 뷰에서 표시 여부 전환 | Toggle whether or not whitespace changes are shown in the diff view.

The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | -| `` z `` | 되돌리기 (reflog) (실험적) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | -| `` Z `` | 다시 실행 (reflog) (실험적) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | - -## List panel navigation - -| Key | Action | Info | -|-----|--------|-------------| -| `` , `` | 이전 페이지 | | -| `` . `` | 다음 페이지 | | -| `` < () `` | 맨 위로 스크롤 | | -| `` > () `` | 맨 아래로 스크롤 | | -| `` v `` | 드래그 선택 전환 | | -| `` `` | Range select down | | -| `` `` | Range select up | | -| `` / `` | 검색 시작 | | -| `` H `` | 우 스크롤 | | -| `` L `` | 좌 스크롤 | | -| `` ] `` | 이전 탭 | | -| `` [ `` | 다음 탭 | | - -## Input prompt - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 확인 | | -| `` `` | 닫기/취소 | | - -## Reflog - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 커밋 해시를 클립보드에 복사 | | -| `` `` | 체크아웃 | Checkout the selected commit as a detached HEAD. | -| `` y `` | 커밋 attribute 복사 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | 브라우저에서 커밋 열기 | | -| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Reset cherry-picked (copied) commits selection | | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | 커밋 보기 | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Secondary - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 패널 전환 | Switch to other view (staged/unstaged changes). | -| `` `` | Exit back to side panel | | -| `` / `` | 검색 시작 | | - -## Stash - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 적용 | Apply the stash entry to your working directory. | -| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | -| `` d `` | Drop | Remove the stash entry from the stash list. | -| `` n `` | 새 브랜치 생성 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | -| `` r `` | Rename stash | | -| `` 0 `` | Focus main view | | -| `` `` | View selected item's files | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Sub-commits - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 커밋 해시를 클립보드에 복사 | | -| `` `` | 체크아웃 | Checkout the selected commit as a detached HEAD. | -| `` y `` | 커밋 attribute 복사 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | 브라우저에서 커밋 열기 | | -| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Reset cherry-picked (copied) commits selection | | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | View selected item's files | | -| `` w `` | View worktree options | | -| `` / `` | 검색 시작 | | - -## Worktrees - -| Key | Action | Info | -|-----|--------|-------------| -| `` n `` | New worktree | | -| `` `` | Switch | Switch to the selected worktree. | -| `` o `` | Open in editor | | -| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. | -| `` / `` | Filter the current view by text | | - -## 메뉴 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 실행 | | -| `` `` | 닫기/취소 | | -| `` / `` | Filter the current view by text | | - -## 메인 패널 (Merging) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Pick hunk | | -| `` b `` | Pick all hunks | | -| `` `` | 이전 hunk를 선택 | | -| `` `` | 다음 hunk를 선택 | | -| `` `` | 이전 충돌을 선택 | | -| `` `` | 다음 충돌을 선택 | | -| `` z `` | 되돌리기 | Undo last merge conflict resolution. | -| `` e `` | 파일 편집 | Open file in external editor. | -| `` o `` | 파일 닫기 | Open file in default application. | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` `` | 파일 목록으로 돌아가기 | | - -## 메인 패널 (Normal) - -| Key | Action | Info | -|-----|--------|-------------| -| `` mouse wheel down (fn+up) `` | 아래로 스크롤 | | -| `` mouse wheel up (fn+down) `` | 위로 스크롤 | | -| `` `` | 패널 전환 | Switch to other view (staged/unstaged changes). | -| `` `` | Exit back to side panel | | -| `` / `` | 검색 시작 | | - -## 메인 패널 (Patch Building) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 이전 hunk를 선택 | | -| `` `` | 다음 hunk를 선택 | | -| `` v `` | 드래그 선택 전환 | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | 선택한 텍스트를 클립보드에 복사 | | -| `` o `` | 파일 닫기 | Open file in default application. | -| `` e `` | 파일 편집 | Open file in external editor. | -| `` `` | Line(s)을 패치에 추가/삭제 | | -| `` `` | Exit custom patch builder | | -| `` / `` | 검색 시작 | | - -## 메인 패널 (Staging) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 이전 hunk를 선택 | | -| `` `` | 다음 hunk를 선택 | | -| `` v `` | 드래그 선택 전환 | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | 선택한 텍스트를 클립보드에 복사 | | -| `` `` | Staged 전환 | 선택한 행을 staged / unstaged | -| `` d `` | 변경을 삭제 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | -| `` o `` | 파일 닫기 | Open file in default application. | -| `` e `` | 파일 편집 | Open file in external editor. | -| `` `` | 파일 목록으로 돌아가기 | | -| `` `` | 패널 전환 | Switch to other view (staged/unstaged changes). | -| `` E `` | Edit hunk | Edit selected hunk in external editor. | -| `` c `` | 커밋 변경내용 | Commit staged changes. | -| `` w `` | Commit changes without pre-commit hook | | -| `` C `` | Git 편집기를 사용하여 변경 내용을 커밋합니다. | | -| `` `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: | -| `` / `` | 검색 시작 | | - -## 브랜치 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 브랜치명을 클립보드에 복사 | | -| `` i `` | Git-flow 옵션 보기 | | -| `` `` | 체크아웃 | Checkout selected item. | -| `` n `` | 새 브랜치 생성 | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` o `` | 풀 리퀘스트 생성 | | -| `` O `` | 풀 리퀘스트 생성 옵션 | | -| `` `` | 풀 리퀘스트 URL을 클립보드에 복사 | | -| `` c `` | 이름으로 체크아웃 | Checkout by name. In the input box you can enter '-' to switch to the previous branch. | -| `` - `` | Checkout previous branch | | -| `` F `` | 강제 체크아웃 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | -| `` d `` | 삭제 | View delete options for local/remote branch. | -| `` r `` | 체크아웃된 브랜치를 이 브랜치에 리베이스 | Rebase the checked-out branch onto the selected branch. | -| `` M `` | 현재 브랜치에 병합 | View options for merging the selected item into the current branch (regular merge, squash merge) | -| `` f `` | Fast-forward this branch from its upstream | Fast-forward selected branch from its upstream. | -| `` T `` | 태그를 생성 | | -| `` s `` | Sort order | | -| `` g `` | View reset options | | -| `` R `` | 브랜치 이름 변경 | | -| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | 커밋 보기 | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## 상태 - -| Key | Action | Info | -|-----|--------|-------------| -| `` o `` | 설정 파일 열기 | Open file in default application. | -| `` e `` | 설정 파일 수정 | Open file in external editor. | -| `` u `` | 업데이트 확인 | | -| `` `` | 최근에 사용한 저장소로 전환 | | -| `` a `` | Show/cycle all branch logs | | -| `` 0 `` | Focus main view | | - -## 서브모듈 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 서브모듈 이름을 클립보드에 복사 | | -| `` `` | Enter | 서브모듈 열기 | -| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | -| `` u `` | Update | 서브모듈 업데이트 | -| `` n `` | 새로운 서브모듈 추가 | | -| `` e `` | 서브모듈의 URL을 수정 | | -| `` i `` | Initialize | 서브모듈 초기화 | -| `` b `` | View bulk submodule options | | -| `` / `` | Filter the current view by text | | - -## 원격 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | View branches | | -| `` n `` | 새로운 Remote 추가 | | -| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | -| `` e `` | Edit | Remote를 수정 | -| `` f `` | Fetch | 원격을 업데이트 | -| `` / `` | Filter the current view by text | | - -## 원격 브랜치 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 브랜치명을 클립보드에 복사 | | -| `` `` | 체크아웃 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | -| `` n `` | 새 브랜치 생성 | | -| `` M `` | 현재 브랜치에 병합 | View options for merging the selected item into the current branch (regular merge, squash merge) | -| `` r `` | 체크아웃된 브랜치를 이 브랜치에 리베이스 | Rebase the checked-out branch onto the selected branch. | -| `` d `` | 삭제 | Delete the remote branch from the remote. | -| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. | -| `` s `` | Sort order | | -| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | 커밋 보기 | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## 커밋 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 커밋 해시를 클립보드에 복사 | | -| `` `` | Reset cherry-picked (copied) commits selection | | -| `` b `` | Bisect 옵션 보기 | | -| `` s `` | 스쿼시 | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | -| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | -| `` r `` | 커밋메시지 변경 | Reword the selected commit's message. | -| `` R `` | 에디터에서 커밋메시지 수정 | | -| `` d `` | 커밋 삭제 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | -| `` e `` | Edit (start interactive rebase) | 커밋을 편집 | -| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
If you would instead like to start an interactive rebase from the selected commit, press `e`. | -| `` p `` | Pick | Pick commit (when mid-rebase) | -| `` F `` | Create fixup commit | Create fixup commit for this commit | -| `` S `` | Apply fixup commits | Squash all 'fixup!' commits above selected commit (autosquash) | -| `` `` | 커밋을 1개 아래로 이동 | | -| `` `` | 커밋을 1개 위로 이동 | | -| `` V `` | 커밋을 붙여넣기 (cherry-pick) | | -| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | -| `` A `` | Amend | Amend commit with staged changes | -| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. | -| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | -| `` T `` | Tag commit | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | -| `` `` | 로그 메뉴 열기 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | -| `` `` | 체크아웃 | Checkout the selected commit as a detached HEAD. | -| `` y `` | 커밋 attribute 복사 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | 브라우저에서 커밋 열기 | | -| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | View selected item's files | | -| `` w `` | View worktree options | | -| `` / `` | 검색 시작 | | - -## 커밋 파일 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 파일명을 클립보드에 복사 | | -| `` y `` | 클립보드에 복사 | | -| `` c `` | 체크아웃 | Checkout file | -| `` d `` | Remove | Discard this commit's changes to this file | -| `` o `` | 파일 닫기 | Open file in default application. | -| `` e `` | Edit | Open file in external editor. | -| `` `` | Open external diff tool (git difftool) | | -| `` `` | Toggle file included in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` a `` | Toggle all files included in patch | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` `` | Enter file to add selected lines to the patch (or toggle directory collapsed) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | -| `` ` `` | 파일 트리뷰로 전환 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | 검색 시작 | | - -## 커밋메시지 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 확인 | | -| `` `` | 닫기 | | - -## 태그 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy tag to clipboard | | -| `` `` | 체크아웃 | Checkout the selected tag as a detached HEAD. | -| `` n `` | 태그를 생성 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | -| `` d `` | 삭제 | View delete options for local/remote tag. | -| `` P `` | 태그를 push | Push the selected tag to a remote. You'll be prompted to select a remote. | -| `` g `` | 초기화 | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | 커밋 보기 | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## 파일 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 파일명을 클립보드에 복사 | | -| `` `` | Staged 전환 | Toggle staged for selected file. | -| `` `` | 파일을 필터하기 (Staged/unstaged) | | -| `` y `` | 클립보드에 복사 | | -| `` c `` | 커밋 변경내용 | Commit staged changes. | -| `` w `` | Commit changes without pre-commit hook | | -| `` A `` | 마지맛 커밋 수정 | | -| `` C `` | Git 편집기를 사용하여 변경 내용을 커밋합니다. | | -| `` `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: | -| `` e `` | Edit | Open file in external editor. | -| `` o `` | 파일 닫기 | Open file in default application. | -| `` i `` | Ignore file | | -| `` r `` | 파일 새로고침 | | -| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | -| `` S `` | Stash 옵션 보기 | View stash options (e.g. stash all, stash staged, stash unstaged). | -| `` a `` | 모든 변경을 Staged/unstaged으로 전환 | Toggle staged/unstaged for all files in working tree. | -| `` `` | Stage individual hunks/lines for file, or collapse/expand for directory | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | -| `` d `` | View 'discard changes' options | View options for discarding changes to the selected file. | -| `` g `` | View upstream reset options | | -| `` D `` | 초기화 | View reset options for working tree (e.g. nuking the working tree). | -| `` ` `` | 파일 트리뷰로 전환 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` `` | Open external diff tool (git difftool) | | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` f `` | Fetch | Fetch changes from remote. | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | 검색 시작 | | - -## 확인 패널 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 확인 | | -| `` `` | 닫기/취소 | | -| `` `` | 클립보드에 복사 | | diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md deleted file mode 100644 index d74247d717d..00000000000 --- a/docs/keybindings/Keybindings_nl.md +++ /dev/null @@ -1,413 +0,0 @@ -_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ - -# Lazygit Sneltoetsen - -_Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ - -## Globale sneltoetsen - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Wissel naar een recente repo | | -| `` (fn+up/shift+k) `` | Scroll naar beneden vanaf hoofdpaneel | | -| `` (fn+down/shift+j) `` | Scroll naar beneden vanaf hoofdpaneel | | -| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. | -| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | -| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | -| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. | -| `` `` | Bekijk aangepaste patch opties | | -| `` m `` | Bekijk merge/rebase opties | View options to abort/continue/skip the current merge/rebase. | -| `` R `` | Verversen | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | -| `` + `` | Volgende scherm modus (normaal/half/groot) | | -| `` _ `` | Vorige scherm modus | | -| `` | `` | Cycle pagers | Choose the next pager in the list of configured pagers | -| `` `` | Annuleren | | -| `` ? `` | Open menu | | -| `` `` | Bekijk scoping opties | View options for filtering the commit log, so that only commits matching the filter are shown. | -| `` W `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` q `` | Quit | | -| `` `` | Suspend the application | | -| `` `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.

The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | -| `` z `` | Ongedaan maken (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | -| `` Z `` | Redo (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | - -## Lijstpaneel navigatie - -| Key | Action | Info | -|-----|--------|-------------| -| `` , `` | Vorige pagina | | -| `` . `` | Volgende pagina | | -| `` < () `` | Scroll naar boven | | -| `` > () `` | Scroll naar beneden | | -| `` v `` | Toggle drag selecteer | | -| `` `` | Range select down | | -| `` `` | Range select up | | -| `` / `` | Start met zoeken | | -| `` H `` | Scroll left | | -| `` L `` | Scroll right | | -| `` ] `` | Volgende tabblad | | -| `` [ `` | Vorige tabblad | | - -## Bestanden - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopieer de bestandsnaam naar het klembord | | -| `` `` | Toggle staged | Toggle staged for selected file. | -| `` `` | Filter files by status | | -| `` y `` | Copy to clipboard | | -| `` c `` | Commit veranderingen | Commit staged changes. | -| `` w `` | Commit veranderingen zonder pre-commit hook | | -| `` A `` | Wijzig laatste commit | | -| `` C `` | Commit veranderingen met de git editor | | -| `` `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: | -| `` e `` | Edit | Open file in external editor. | -| `` o `` | Open bestand | Open file in default application. | -| `` i `` | Ignore or exclude file | | -| `` r `` | Refresh bestanden | | -| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | -| `` S `` | Bekijk stash opties | View stash options (e.g. stash all, stash staged, stash unstaged). | -| `` a `` | Toggle staged alle | Toggle staged/unstaged for all files in working tree. | -| `` `` | Stage individuele hunks/lijnen | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | -| `` d `` | Bekijk 'veranderingen ongedaan maken' opties | View options for discarding changes to the selected file. | -| `` g `` | Bekijk upstream reset opties | | -| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | -| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` `` | Open external diff tool (git difftool) | | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` f `` | Fetch | Fetch changes from remote. | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | Start met zoeken | | - -## Bevestigingspaneel - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Bevestig | | -| `` `` | Sluiten | | -| `` `` | Copy to clipboard | | - -## Branches - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopieer branch name naar klembord | | -| `` i `` | Laat git-flow opties zien | | -| `` `` | Uitchecken | Checkout selected item. | -| `` n `` | Nieuwe branch | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` o `` | Maak een pull-request | | -| `` O `` | Bekijk opties voor pull-aanvraag | | -| `` `` | Kopieer de URL van het pull-verzoek naar het klembord | | -| `` c `` | Uitchecken bij naam | Checkout by name. In the input box you can enter '-' to switch to the previous branch. | -| `` - `` | Checkout previous branch | | -| `` F `` | Forceer checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | -| `` d `` | Delete | View delete options for local/remote branch. | -| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. | -| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) | -| `` f `` | Fast-forward deze branch vanaf zijn upstream | Fast-forward selected branch from its upstream. | -| `` T `` | Creëer tag | | -| `` s `` | Sort order | | -| `` g `` | Bekijk reset opties | | -| `` R `` | Hernoem branch | | -| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | Bekijk commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Commit bericht - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Bevestig | | -| `` `` | Sluiten | | - -## Commit bestanden - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopieer de bestandsnaam naar het klembord | | -| `` y `` | Copy to clipboard | | -| `` c `` | Uitchecken | Bestand uitchecken | -| `` d `` | Remove | Uitsluit deze commit zijn veranderingen aan dit bestand | -| `` o `` | Open bestand | Open file in default application. | -| `` e `` | Edit | Open file in external editor. | -| `` `` | Open external diff tool (git difftool) | | -| `` `` | Toggle bestand inbegrepen in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` `` | Enter bestand om geselecteerde regels toe te voegen aan de patch | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | -| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | Start met zoeken | | - -## Commits - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopieer commit hash naar klembord | | -| `` `` | Reset cherry-picked (gekopieerde) commits selectie | | -| `` b `` | View bisect options | | -| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | -| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | -| `` r `` | Hernoem commit | Reword the selected commit's message. | -| `` R `` | Hernoem commit met editor | | -| `` d `` | Verwijder commit | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | -| `` e `` | Edit (start interactive rebase) | Wijzig commit | -| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
If you would instead like to start an interactive rebase from the selected commit, press `e`. | -| `` p `` | Pick | Kies commit (wanneer midden in rebase) | -| `` F `` | Creëer fixup commit | Creëer fixup commit | -| `` S `` | Apply fixup commits | Squash bovenstaande commits | -| `` `` | Verplaats commit 1 naar beneden | | -| `` `` | Verplaats commit 1 naar boven | | -| `` V `` | Plak commits (cherry-pick) | | -| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | -| `` A `` | Amend | Wijzig commit met staged veranderingen | -| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. | -| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | -| `` T `` | Tag commit | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | -| `` `` | View log options | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | -| `` `` | Uitchecken | Checkout the selected commit as a detached HEAD. | -| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Open commit in browser | | -| `` n `` | Creëer nieuwe branch van commit | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | Bekijk gecommite bestanden | | -| `` w `` | View worktree options | | -| `` / `` | Start met zoeken | | - -## Input prompt - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Bevestig | | -| `` `` | Sluiten | | - -## Menu - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Uitvoeren | | -| `` `` | Sluiten | | -| `` / `` | Filter the current view by text | | - -## Mergen - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kies stuk | | -| `` b `` | Kies beide stukken | | -| `` `` | Selecteer bovenste hunk | | -| `` `` | Selecteer onderste hunk | | -| `` `` | Selecteer voorgaand conflict | | -| `` `` | Selecteer volgende conflict | | -| `` z `` | Ongedaan maken | Undo last merge conflict resolution. | -| `` e `` | Verander bestand | Open file in external editor. | -| `` o `` | Open bestand | Open file in default application. | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` `` | Ga terug naar het bestanden paneel | | - -## Normaal - -| Key | Action | Info | -|-----|--------|-------------| -| `` mouse wheel down (fn+up) `` | Scroll omlaag | | -| `` mouse wheel up (fn+down) `` | Scroll omhoog | | -| `` `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). | -| `` `` | Exit back to side panel | | -| `` / `` | Start met zoeken | | - -## Patch bouwen - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Selecteer de vorige hunk | | -| `` `` | Selecteer de volgende hunk | | -| `` v `` | Toggle drag selecteer | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | Copy selected text to clipboard | | -| `` o `` | Open bestand | Open file in default application. | -| `` e `` | Verander bestand | Open file in external editor. | -| `` `` | Voeg toe/verwijder lijn(en) in patch | | -| `` `` | Sluit lijn-bij-lijn modus | | -| `` / `` | Start met zoeken | | - -## Reflog - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopieer commit hash naar klembord | | -| `` `` | Uitchecken | Checkout the selected commit as a detached HEAD. | -| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Open commit in browser | | -| `` n `` | Creëer nieuwe branch van commit | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Reset cherry-picked (gekopieerde) commits selectie | | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | Bekijk commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Remote branches - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopieer branch name naar klembord | | -| `` `` | Uitchecken | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | -| `` n `` | Nieuwe branch | | -| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) | -| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. | -| `` d `` | Delete | Delete the remote branch from the remote. | -| `` u `` | Set as upstream | Stel in als upstream van uitgecheckte branch | -| `` s `` | Sort order | | -| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | Bekijk commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Remotes - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | View branches | | -| `` n `` | Voeg een nieuwe remote toe | | -| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | -| `` e `` | Edit | Wijzig remote | -| `` f `` | Fetch | Fetch remote | -| `` / `` | Filter the current view by text | | - -## Secondary - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). | -| `` `` | Exit back to side panel | | -| `` / `` | Start met zoeken | | - -## Staging - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Selecteer de vorige hunk | | -| `` `` | Selecteer de volgende hunk | | -| `` v `` | Toggle drag selecteer | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | Copy selected text to clipboard | | -| `` `` | Toggle staged | Toggle lijnen staged / unstaged | -| `` d `` | Verwijdert change (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | -| `` o `` | Open bestand | Open file in default application. | -| `` e `` | Verander bestand | Open file in external editor. | -| `` `` | Ga terug naar het bestanden paneel | | -| `` `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). | -| `` E `` | Edit hunk | Edit selected hunk in external editor. | -| `` c `` | Commit veranderingen | Commit staged changes. | -| `` w `` | Commit veranderingen zonder pre-commit hook | | -| `` C `` | Commit veranderingen met de git editor | | -| `` `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: | -| `` / `` | Start met zoeken | | - -## Stash - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Toepassen | Apply the stash entry to your working directory. | -| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | -| `` d `` | Laten vallen | Remove the stash entry from the stash list. | -| `` n `` | Nieuwe branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | -| `` r `` | Rename stash | | -| `` 0 `` | Focus main view | | -| `` `` | Bekijk gecommite bestanden | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Status - -| Key | Action | Info | -|-----|--------|-------------| -| `` o `` | Open config bestand | Open file in default application. | -| `` e `` | Verander config bestand | Open file in external editor. | -| `` u `` | Check voor updates | | -| `` `` | Wissel naar een recente repo | | -| `` a `` | Show/cycle all branch logs | | -| `` 0 `` | Focus main view | | - -## Sub-commits - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopieer commit hash naar klembord | | -| `` `` | Uitchecken | Checkout the selected commit as a detached HEAD. | -| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Open commit in browser | | -| `` n `` | Creëer nieuwe branch van commit | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Reset cherry-picked (gekopieerde) commits selectie | | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | Bekijk gecommite bestanden | | -| `` w `` | View worktree options | | -| `` / `` | Start met zoeken | | - -## Submodules - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopieer submodule naam naar klembord | | -| `` `` | Enter | Enter submodule | -| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | -| `` u `` | Update | Update selected submodule. | -| `` n `` | Voeg nieuwe submodule toe | | -| `` e `` | Update submodule URL | | -| `` i `` | Initialize | Initialiseer submodule | -| `` b `` | Bekijk bulk submodule opties | | -| `` / `` | Filter the current view by text | | - -## Tags - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy tag to clipboard | | -| `` `` | Uitchecken | Checkout the selected tag as a detached HEAD. | -| `` n `` | Creëer tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | -| `` d `` | Delete | View delete options for local/remote tag. | -| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. | -| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | Bekijk commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Worktrees - -| Key | Action | Info | -|-----|--------|-------------| -| `` n `` | New worktree | | -| `` `` | Switch | Switch to the selected worktree. | -| `` o `` | Open in editor | | -| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. | -| `` / `` | Filter the current view by text | | diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md deleted file mode 100644 index 5d5d53609d3..00000000000 --- a/docs/keybindings/Keybindings_pl.md +++ /dev/null @@ -1,413 +0,0 @@ -_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ - -# Lazygit Skróty klawiszowe - -_Legenda: `` oznacza ctrl+b, `` oznacza alt+b, `B` oznacza shift+b_ - -## Globalne skróty klawiszowe - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Przełącz na ostatnie repozytorium | | -| `` (fn+up/shift+k) `` | Przewiń główne okno w górę | | -| `` (fn+down/shift+j) `` | Przewiń główne okno w dół | | -| `` @ `` | Pokaż opcje dziennika poleceń | Pokaż opcje dla dziennika poleceń, np. pokazywanie/ukrywanie dziennika poleceń i skupienie na dzienniku poleceń. | -| `` P `` | Wypchnij | Wypchnij bieżącą gałąź do jej gałęzi nadrzędnej. Jeśli nie skonfigurowano gałęzi nadrzędnej, zostaniesz poproszony o skonfigurowanie gałęzi nadrzędnej. | -| `` p `` | Pociągnij | Pociągnij zmiany z zdalnego dla bieżącej gałęzi. Jeśli nie skonfigurowano gałęzi nadrzędnej, zostaniesz poproszony o skonfigurowanie gałęzi nadrzędnej. | -| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` } `` | Zwiększ rozmiar kontekstu w widoku różnic | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` { `` | Zmniejsz rozmiar kontekstu w widoku różnic | Decrease the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. | -| `` `` | Wyświetl opcje niestandardowej łatki | | -| `` m `` | Pokaż opcje scalania/rebase | Pokaż opcje do przerwania/kontynuowania/pominięcia bieżącego scalania/rebase. | -| `` R `` | Odśwież | Odśwież stan git (tj. uruchom `git status`, `git branch`, itp. w tle, aby zaktualizować zawartość paneli). To nie uruchamia `git fetch`. | -| `` + `` | Następny tryb ekranu (normalny/półpełny/pełnoekranowy) | | -| `` _ `` | Poprzedni tryb ekranu | | -| `` | `` | Cycle pagers | Choose the next pager in the list of configured pagers | -| `` `` | Anuluj | | -| `` ? `` | Otwórz menu przypisań klawiszy | | -| `` `` | Pokaż opcje filtrowania | Pokaż opcje filtrowania dziennika commitów, tak aby pokazywane były tylko commity pasujące do filtra. | -| `` W `` | Pokaż opcje różnicowania | Pokaż opcje dotyczące różnicowania dwóch refów, np. różnicowanie względem wybranego refa, wprowadzanie refa do różnicowania i odwracanie kierunku różnic. | -| `` `` | Pokaż opcje różnicowania | Pokaż opcje dotyczące różnicowania dwóch refów, np. różnicowanie względem wybranego refa, wprowadzanie refa do różnicowania i odwracanie kierunku różnic. | -| `` q `` | Wyjdź | | -| `` `` | Suspend the application | | -| `` `` | Przełącz białe znaki | Toggle whether or not whitespace changes are shown in the diff view.

The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | -| `` z `` | Cofnij | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby cofnąć ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. | -| `` Z `` | Ponów | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby ponowić ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. | - -## Nawigacja panelu listy - -| Key | Action | Info | -|-----|--------|-------------| -| `` , `` | Poprzednia strona | | -| `` . `` | Następna strona | | -| `` < () `` | Przewiń do góry | | -| `` > () `` | Przewiń do dołu | | -| `` v `` | Przełącz zaznaczenie zakresu | | -| `` `` | Zaznacz zakres w dół | | -| `` `` | Zaznacz zakres w górę | | -| `` / `` | Szukaj w bieżącym widoku po tekście | | -| `` H `` | Przewiń w lewo | | -| `` L `` | Przewiń w prawo | | -| `` ] `` | Następna zakładka | | -| `` [ `` | Poprzednia zakładka | | - -## Commity - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopiuj hash commita do schowka | | -| `` `` | Resetuj wybrane (cherry-picked) commity | | -| `` b `` | Zobacz opcje bisect | | -| `` s `` | Scal | Scal wybrany commit z commitami poniżej. Wiadomość wybranego commita zostanie dołączona do commita poniżej. | -| `` f `` | Poprawka | Włącz wybrany commit do commita poniżej. Podobnie do fixup, ale wiadomość wybranego commita zostanie odrzucona. | -| `` r `` | Przeformułuj | Przeformułuj wiadomość wybranego commita. | -| `` R `` | Przeformułuj za pomocą edytora | | -| `` d `` | Usuń | Usuń wybrany commit. To usunie commit z gałęzi za pomocą rebazowania. Jeśli commit wprowadza zmiany, od których zależą późniejsze commity, być może będziesz musiał rozwiązać konflikty scalania. | -| `` e `` | Edytuj (rozpocznij interaktywne rebazowanie) | Edytuj wybrany commit. Użyj tego, aby rozpocząć interaktywne rebazowanie od wybranego commita. Podczas trwania rebazowania, to oznaczy wybrany commit do edycji, co oznacza, że po kontynuacji rebazowania, rebazowanie zostanie wstrzymane na wybranym commicie, aby umożliwić wprowadzenie zmian. | -| `` i `` | Rozpocznij interaktywny rebase | Rozpocznij interaktywny rebase dla commitów na twoim branchu. To będzie zawierać wszystkie commity od HEAD do pierwszego commita scalenia lub commita głównego brancha.
Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita, naciśnij `e`. | -| `` p `` | Wybierz | Oznacz wybrany commit do wybrania (podczas rebazowania). Oznacza to, że commit zostanie zachowany po kontynuacji rebazowania. | -| `` F `` | Utwórz commit fixup | Utwórz commit 'fixup!' dla wybranego commita. Później możesz nacisnąć `S` na tym samym commicie, aby zastosować wszystkie powyższe commity fixup. | -| `` S `` | Zastosuj commity fixup | Scal wszystkie commity 'fixup!', albo powyżej wybranego commita, albo wszystkie w bieżącej gałęzi (autosquash). | -| `` `` | Przesuń commit w dół | | -| `` `` | Przesuń commit w górę | | -| `` V `` | Wklej (cherry-pick) | | -| `` B `` | Oznacz jako bazowy commit dla rebase | Wybierz bazowy commit dla następnego rebase. Kiedy robisz rebase na branch, tylko commity powyżej bazowego commita zostaną przeniesione. Używa to polecenia `git rebase --onto`. | -| `` A `` | Popraw | Popraw commit ze zmianami zatwierdzonymi. Jeśli wybrany commit jest commit HEAD, to wykona `git commit --amend`. W przeciwnym razie commit zostanie poprawiony za pomocą rebazowania. | -| `` a `` | Popraw atrybut commita | Ustaw/Resetuj autora commita lub ustaw współautora. | -| `` t `` | Cofnij | Utwórz commit cofający dla wybranego commita, który stosuje zmiany wybranego commita w odwrotnej kolejności. | -| `` T `` | Otaguj commit | Utwórz nowy tag wskazujący na wybrany commit. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu. | -| `` `` | Zobacz opcje logów | Zobacz opcje dla logów commitów, np. zmiana kolejności sortowania, ukrywanie grafu gita, pokazywanie całego grafu gita. | -| `` `` | Przełącz | Przełącz wybrany commit jako odłączoną HEAD. | -| `` y `` | Kopiuj atrybut commita do schowka | Kopiuj atrybut commita do schowka (np. hash, URL, różnice, wiadomość, autor). | -| `` o `` | Otwórz commit w przeglądarce | | -| `` n `` | Utwórz nową gałąź z commita | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | -| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć ``, aby anulować zaznaczenie. | -| `` `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | Wyświetl pliki | | -| `` w `` | Zobacz opcje drzewa pracy | | -| `` / `` | Szukaj w bieżącym widoku po tekście | | - -## Dodatkowy - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Przełącz widok | Przełącz na inny widok (zatwierdzone/niezatwierdzone zmiany). | -| `` `` | Exit back to side panel | | -| `` / `` | Szukaj w bieżącym widoku po tekście | | - -## Drzewa pracy - -| Key | Action | Info | -|-----|--------|-------------| -| `` n `` | Nowe drzewo pracy | | -| `` `` | Przełącz | Przełącz do wybranego drzewa pracy. | -| `` o `` | Otwórz w edytorze | | -| `` d `` | Usuń | Usuń wybrane drzewo pracy. To usunie zarówno katalog drzewa pracy, jak i metadane o drzewie pracy w katalogu .git. | -| `` / `` | Filtruj bieżący widok po tekście | | - -## Główny panel (budowanie łatki) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Idź do poprzedniego fragmentu | | -| `` `` | Idź do następnego fragmentu | | -| `` v `` | Przełącz zaznaczenie zakresu | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | Kopiuj zaznaczony tekst do schowka | | -| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. | -| `` e `` | Edytuj plik | Otwórz plik w zewnętrznym edytorze. | -| `` `` | Przełącz linie w łatce | | -| `` `` | Wyjdź z budowniczego niestandardowej łatki | | -| `` / `` | Szukaj w bieżącym widoku po tekście | | - -## Input prompt - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Potwierdź | | -| `` `` | Zamknij/Anuluj | | - -## Lokalne gałęzie - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopiuj nazwę gałęzi do schowka | | -| `` i `` | Pokaż opcje git-flow | | -| `` `` | Przełącz | Przełącz wybrany element. | -| `` n `` | Nowa gałąź | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` o `` | Utwórz żądanie ściągnięcia | | -| `` O `` | Zobacz opcje tworzenia pull requesta | | -| `` `` | Kopiuj adres URL żądania ściągnięcia do schowka | | -| `` c `` | Przełącz według nazwy | Przełącz według nazwy. W polu wprowadzania możesz wpisać '-' aby przełączyć się na ostatnią gałąź. | -| `` - `` | Checkout previous branch | | -| `` F `` | Wymuś przełączenie | Wymuś przełączenie wybranej gałęzi. To spowoduje odrzucenie wszystkich lokalnych zmian w drzewie roboczym przed przełączeniem na wybraną gałąź. | -| `` d `` | Usuń | Wyświetl opcje usuwania lokalnej/odległej gałęzi. | -| `` r `` | Przebazuj | Przebazuj przełączoną gałąź na wybraną gałąź. | -| `` M `` | Scal | Scal wybraną gałąź z aktualnie sprawdzoną gałęzią. | -| `` f `` | Szybkie przewijanie | Szybkie przewijanie wybranej gałęzi z jej źródła. | -| `` T `` | Nowy tag | | -| `` s `` | Kolejność sortowania | | -| `` g `` | Reset | | -| `` R `` | Zmień nazwę gałęzi | | -| `` u `` | Pokaż opcje upstream | Pokaż opcje dotyczące upstream gałęzi, np. ustawianie/usuwanie upstream i resetowanie do upstream. | -| `` `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | Pokaż commity | | -| `` w `` | Zobacz opcje drzewa pracy | | -| `` / `` | Filtruj bieżący widok po tekście | | - -## Menu - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Wykonaj | | -| `` `` | Zamknij/Anuluj | | -| `` / `` | Filtruj bieżący widok po tekście | | - -## Panel główny (normalny) - -| Key | Action | Info | -|-----|--------|-------------| -| `` mouse wheel down (fn+up) `` | Przewiń w dół | | -| `` mouse wheel up (fn+down) `` | Przewiń w górę | | -| `` `` | Przełącz widok | Przełącz na inny widok (zatwierdzone/niezatwierdzone zmiany). | -| `` `` | Exit back to side panel | | -| `` / `` | Szukaj w bieżącym widoku po tekście | | - -## Panel główny (scalanie) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Wybierz fragment | | -| `` b `` | Wybierz wszystkie fragmenty | | -| `` `` | Poprzedni fragment | | -| `` `` | Następny fragment | | -| `` `` | Poprzedni konflikt | | -| `` `` | Następny konflikt | | -| `` z `` | Cofnij | Cofnij ostatnie rozwiązanie konfliktu scalania. | -| `` e `` | Edytuj plik | Otwórz plik w zewnętrznym edytorze. | -| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` `` | Wróć do panelu plików | | - -## Panel główny (zatwierdzanie) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Idź do poprzedniego fragmentu | | -| `` `` | Idź do następnego fragmentu | | -| `` v `` | Przełącz zaznaczenie zakresu | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | Kopiuj zaznaczony tekst do schowka | | -| `` `` | Zatwierdź | Przełącz zaznaczenie zatwierdzone/niezatwierdzone. | -| `` d `` | Odrzuć | Gdy zaznaczona jest niezatwierdzona zmiana, odrzuć ją używając `git reset`. Gdy zaznaczona jest zatwierdzona zmiana, cofnij zatwierdzenie. | -| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. | -| `` e `` | Edytuj plik | Otwórz plik w zewnętrznym edytorze. | -| `` `` | Wróć do panelu plików | | -| `` `` | Przełącz widok | Przełącz na inny widok (zatwierdzone/niezatwierdzone zmiany). | -| `` E `` | Edytuj fragment | Edytuj wybrany fragment w zewnętrznym edytorze. | -| `` c `` | Commit | Zatwierdź zmiany zatwierdzone. | -| `` w `` | Zatwierdź zmiany bez hooka pre-commit | | -| `` C `` | Zatwierdź zmiany używając edytora git | | -| `` `` | Znajdź bazowy commit do poprawki | Znajdź commit, na którym opierają się Twoje obecne zmiany, w celu poprawienia/zmiany commita. To pozwala Ci uniknąć przeglądania commitów w Twojej gałęzi jeden po drugim, aby zobaczyć, który commit powinien być poprawiony/zmieniony. Zobacz dokumentację: | -| `` / `` | Szukaj w bieżącym widoku po tekście | | - -## Panel potwierdzenia - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Potwierdź | | -| `` `` | Zamknij/Anuluj | | -| `` `` | Kopiuj do schowka | | - -## Pliki - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopiuj ścieżkę do schowka | | -| `` `` | Zatwierdź | Przełącz zatwierdzenie dla wybranego pliku. | -| `` `` | Filtruj pliki według statusu | | -| `` y `` | Kopiuj do schowka | | -| `` c `` | Commit | Zatwierdź zmiany zatwierdzone. | -| `` w `` | Zatwierdź zmiany bez hooka pre-commit | | -| `` A `` | Popraw ostatni commit | | -| `` C `` | Zatwierdź zmiany używając edytora git | | -| `` `` | Znajdź bazowy commit do poprawki | Znajdź commit, na którym opierają się Twoje obecne zmiany, w celu poprawienia/zmiany commita. To pozwala Ci uniknąć przeglądania commitów w Twojej gałęzi jeden po drugim, aby zobaczyć, który commit powinien być poprawiony/zmieniony. Zobacz dokumentację: | -| `` e `` | Edytuj | Otwórz plik w zewnętrznym edytorze. | -| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. | -| `` i `` | Ignoruj lub wyklucz plik | | -| `` r `` | Odśwież pliki | | -| `` s `` | Schowaj | Schowaj wszystkie zmiany. Dla innych wariantów schowania, użyj klawisza wyświetlania opcji schowka. | -| `` S `` | Wyświetl opcje schowka | Wyświetl opcje schowka (np. schowaj wszystko, schowaj zatwierdzone, schowaj niezatwierdzone). | -| `` a `` | Zatwierdź wszystko | Przełącz zatwierdzenie/odznaczenie dla wszystkich plików w drzewie roboczym. | -| `` `` | Zatwierdź linie / Zwiń katalog | Jeśli wybrany element jest plikiem, skup się na widoku zatwierdzania, aby móc zatwierdzać poszczególne fragmenty/linie. Jeśli wybrany element jest katalogiem, zwiń/rozwiń go. | -| `` d `` | Odrzuć | Wyświetl opcje odrzucania zmian w wybranym pliku. | -| `` g `` | Pokaż opcje resetowania do upstream | | -| `` D `` | Reset | Wyświetl opcje resetu dla drzewa roboczego (np. zniszczenie drzewa roboczego). | -| `` ` `` | Przełącz widok drzewa plików | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` f `` | Pobierz | Pobierz zmiany ze zdalnego serwera. | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | Szukaj w bieżącym widoku po tekście | | - -## Pliki commita - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopiuj ścieżkę do schowka | | -| `` y `` | Kopiuj do schowka | | -| `` c `` | Przełącz | Przełącz plik. Zastępuje plik w twoim drzewie roboczym wersją z wybranego commita. | -| `` d `` | Usuń | Odrzuć zmiany w tym pliku z tego commita. Uruchamia interaktywny rebase w tle, więc możesz otrzymać konflikt scalania, jeśli późniejszy commit również zmienia ten plik. | -| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. | -| `` e `` | Edytuj | Otwórz plik w zewnętrznym edytorze. | -| `` `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | -| `` `` | Przełącz plik włączony w łatkę | Przełącz, czy plik jest włączony w niestandardową łatkę. Zobacz https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` a `` | Przełącz wszystkie pliki | Dodaj/usuń wszystkie pliki commita do niestandardowej łatki. Zobacz https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` `` | Wejdź do pliku / Przełącz zwiń katalog | Jeśli plik jest wybrany, wejdź do pliku, aby móc dodawać/usuwać poszczególne linie do niestandardowej łatki. Jeśli wybrany jest katalog, przełącz katalog. | -| `` ` `` | Przełącz widok drzewa plików | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | Szukaj w bieżącym widoku po tekście | | - -## Podsumowanie commita - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Potwierdź | | -| `` `` | Zamknij | | - -## Reflog - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopiuj hash commita do schowka | | -| `` `` | Przełącz | Przełącz wybrany commit jako odłączoną HEAD. | -| `` y `` | Kopiuj atrybut commita do schowka | Kopiuj atrybut commita do schowka (np. hash, URL, różnice, wiadomość, autor). | -| `` o `` | Otwórz commit w przeglądarce | | -| `` n `` | Utwórz nową gałąź z commita | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | -| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć ``, aby anulować zaznaczenie. | -| `` `` | Resetuj wybrane (cherry-picked) commity | | -| `` `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | Pokaż commity | | -| `` w `` | Zobacz opcje drzewa pracy | | -| `` / `` | Filtruj bieżący widok po tekście | | - -## Schowek - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Zastosuj | Zastosuj wpis schowka do katalogu roboczego. | -| `` g `` | Wyciągnij | Zastosuj wpis schowka do katalogu roboczego i usuń wpis schowka. | -| `` d `` | Usuń | Usuń wpis schowka z listy schowka. | -| `` n `` | Nowa gałąź | Utwórz nową gałąź z wybranego wpisu schowka. Działa poprzez przełączenie git na commit, na którym wpis schowka został utworzony, tworzenie nowej gałęzi z tego commita, a następnie zastosowanie wpisu schowka do nowej gałęzi jako dodatkowego commita. | -| `` r `` | Zmień nazwę schowka | | -| `` 0 `` | Focus main view | | -| `` `` | Wyświetl pliki | | -| `` w `` | Zobacz opcje drzewa pracy | | -| `` / `` | Filtruj bieżący widok po tekście | | - -## Status - -| Key | Action | Info | -|-----|--------|-------------| -| `` o `` | Otwórz plik konfiguracyjny | Otwórz plik w domyślnej aplikacji. | -| `` e `` | Edytuj plik konfiguracyjny | Otwórz plik w zewnętrznym edytorze. | -| `` u `` | Sprawdź aktualizacje | | -| `` `` | Przełącz na ostatnie repozytorium | | -| `` a `` | Show/cycle all branch logs | | -| `` 0 `` | Focus main view | | - -## Sub-commity - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopiuj hash commita do schowka | | -| `` `` | Przełącz | Przełącz wybrany commit jako odłączoną HEAD. | -| `` y `` | Kopiuj atrybut commita do schowka | Kopiuj atrybut commita do schowka (np. hash, URL, różnice, wiadomość, autor). | -| `` o `` | Otwórz commit w przeglądarce | | -| `` n `` | Utwórz nową gałąź z commita | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | -| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć ``, aby anulować zaznaczenie. | -| `` `` | Resetuj wybrane (cherry-picked) commity | | -| `` `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | Wyświetl pliki | | -| `` w `` | Zobacz opcje drzewa pracy | | -| `` / `` | Szukaj w bieżącym widoku po tekście | | - -## Submoduły - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopiuj nazwę submodułu do schowka | | -| `` `` | Wejdź | Wejdź do submodułu. Po wejściu do submodułu możesz nacisnąć ``, aby wrócić do repozytorium nadrzędnego. | -| `` d `` | Usuń | Usuń wybrany submoduł i odpowiadający mu katalog. | -| `` u `` | Aktualizuj | Aktualizuj wybrany submoduł. | -| `` n `` | Nowy submoduł | | -| `` e `` | Zaktualizuj URL submodułu | | -| `` i `` | Zainicjuj | Zainicjuj wybrany submoduł, aby przygotować do pobrania. Prawdopodobnie chcesz to kontynuować, wywołując akcję 'update', aby pobrać submoduł. | -| `` b `` | Pokaż opcje masowych operacji na submodułach | | -| `` / `` | Filtruj bieżący widok po tekście | | - -## Tagi - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy tag to clipboard | | -| `` `` | Przełącz | Przełącz wybrany tag jako odłączoną głowę (detached HEAD). | -| `` n `` | Nowy tag | Utwórz nowy tag z bieżącego commita. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu. | -| `` d `` | Usuń | Wyświetl opcje usuwania lokalnego/odległego tagu. | -| `` P `` | Wyślij tag | Wyślij wybrany tag do zdalnego. Zostaniesz poproszony o wybranie zdalnego. | -| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | -| `` `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | Pokaż commity | | -| `` w `` | Zobacz opcje drzewa pracy | | -| `` / `` | Filtruj bieżący widok po tekście | | - -## Zdalne - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Wyświetl gałęzie | | -| `` n `` | Nowy zdalny | | -| `` d `` | Usuń | Usuń wybrany zdalny. Wszelkie lokalne gałęzie śledzące gałąź zdalną z tego zdalnego nie zostaną dotknięte. | -| `` e `` | Edytuj | Edytuj nazwę lub URL wybranego zdalnego. | -| `` f `` | Pobierz | Pobierz aktualizacje z zdalnego repozytorium. Pobiera nowe commity i gałęzie bez scalania ich z lokalnymi gałęziami. | -| `` / `` | Filtruj bieżący widok po tekście | | - -## Zdalne gałęzie - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Kopiuj nazwę gałęzi do schowka | | -| `` `` | Przełącz | Przełącz na nową lokalną gałąź na podstawie wybranej gałęzi zdalnej. Nowa gałąź będzie śledzić gałąź zdalną. | -| `` n `` | Nowa gałąź | | -| `` M `` | Scal | Scal wybraną gałąź z aktualnie sprawdzoną gałęzią. | -| `` r `` | Przebazuj | Przebazuj przełączoną gałąź na wybraną gałąź. | -| `` d `` | Usuń | Usuń gałąź zdalną ze zdalnego. | -| `` u `` | Ustaw jako upstream | Ustaw wybraną gałąź zdalną jako upstream sprawdzonej gałęzi. | -| `` s `` | Kolejność sortowania | | -| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | -| `` `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | Pokaż commity | | -| `` w `` | Zobacz opcje drzewa pracy | | -| `` / `` | Filtruj bieżący widok po tekście | | diff --git a/docs/keybindings/Keybindings_pt.md b/docs/keybindings/Keybindings_pt.md deleted file mode 100644 index 34094ba2bb2..00000000000 --- a/docs/keybindings/Keybindings_pt.md +++ /dev/null @@ -1,413 +0,0 @@ -_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ - -# Lazygit Keybindings - -_Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ - -## Combinações globais de teclas - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Mudar para um repositório recente | | -| `` (fn+up/shift+k) `` | Rolar janela principal para cima | | -| `` (fn+down/shift+j) `` | Rolar a janela principal para baixo | | -| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. | -| `` P `` | Empurre (Push) | Faça push do branch atual para o seu branch upstream. Se nenhum upstream estiver configurado, você será solicitado a configurar um branch a montante. | -| `` p `` | Puxar (Pull) | Puxe alterações do controle remoto para o ramo atual. Se nenhum upstream estiver configurado, será solicitado configurar um ramo a montante. | -| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` : `` | Executar comando da shell | Traga um prompt onde você pode digitar um comando shell para executar. | -| `` `` | Ver opções de patch personalizadas | | -| `` m `` | Ver opções de mesclar/rebase | Ver opções para abortar/continuar/pular o merge/rebase atual. | -| `` R `` | Atualizar | Atualize o estado do git (ou seja, execute `git status`, `git branch`, etc em segundo plano para atualizar o conteúdo de painéis). Isso não executa `git fetch`. | -| `` + `` | Next screen mode (normal/half/fullscreen) | | -| `` _ `` | Prev screen mode | | -| `` | `` | Cycle pagers | Choose the next pager in the list of configured pagers | -| `` `` | Cancelar | | -| `` ? `` | Open keybindings menu | | -| `` `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. | -| `` W `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` q `` | Sair | | -| `` `` | Suspend the application | | -| `` `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.

The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | -| `` z `` | Desfazer | O reflog será usado para determinar qual comando git para executar para desfazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. | -| `` Z `` | Refazer | O reflog será usado para determinar qual comando git para executar para refazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. | - -## List panel navigation - -| Key | Action | Info | -|-----|--------|-------------| -| `` , `` | Previous page | | -| `` . `` | Next page | | -| `` < () `` | Scroll to top | | -| `` > () `` | Scroll to bottom | | -| `` v `` | Toggle range select | | -| `` `` | Range select down | | -| `` `` | Range select up | | -| `` / `` | Search the current view by text | | -| `` H `` | Rolar à esquerda | | -| `` L `` | Scroll para a direita | | -| `` ] `` | Next tab | | -| `` [ `` | Previous tab | | - -## Arquivos - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy path to clipboard | | -| `` `` | Etapa | Alternar para staging para o arquivo selecionado. | -| `` `` | Filtrar arquivos por status | | -| `` y `` | Copy to clipboard | | -| `` c `` | Commit | Submeter mudanças em staging | -| `` w `` | Fazer commit de alterações sem pré-commit | | -| `` A `` | Alterar último commit | | -| `` C `` | Enviar alteração usando um editor Git | | -| `` `` | Encontrar commit da base para consertar | Encontre o commit em que as suas mudanças atuais estão se baseando, para alterar/consertar o commit. Isso poupa-te você de ter que olhar pelos commits da sua branch um por um para ver qual commit deve ser alterado/consertado
Veja a documentação:
| -| `` e `` | Editar | Abrir arquivo no editor externo. | -| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. | -| `` i `` | Ignore or exclude file | | -| `` r `` | Atualizar arquivos | | -| `` s `` | Stash | Stash todas as alterações. Para outras variações de armazenamento, use a fixação de teclas de armazenamento. | -| `` S `` | Ver opções de stash | Ver opções de stash (por exemplo, trash all, stash staged, stash unsttued). | -| `` a `` | Stage completo | Alternar para todos os arquivos na árvore de trabalho | -| `` `` | Stage lines / Colapso diretório | Se o item selecionado for um arquivo, o foco na exibição de preparo para o estágio de cenas/linhas individuais. Se o item selecionado for um diretório, recolher/expandi-lo. | -| `` d `` | Descartar | Exibir opções para descartar alterações para o arquivo selecionado. | -| `` g `` | View upstream reset options | | -| `` D `` | Restaurar | Opções de redefinição de exibição para árvore de trabalho (por exemplo, nukando a árvore de trabalho). | -| `` ` `` | Alternar exibição de árvore de arquivo | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` `` | Abrir ferramenta de diff externa (git difftool) | | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` f `` | Buscar | Buscar alterações do controle remoto. | -| `` - `` | Recolher todos os arquivos | Recolher todos os diretórios na árvore de arquivos | -| `` = `` | Expandir todos os arquivos | Expandir todos os diretórios na árvore do arquivo | -| `` 0 `` | Focus main view | | -| `` / `` | Search the current view by text | | - -## Branches locais - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy branch name to clipboard | | -| `` i `` | Exibir opções do git-flow | | -| `` `` | Verificar | Checar item selecionado | -| `` n `` | Nova branch | | -| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` o `` | Create pull request | | -| `` O `` | View create pull request options | | -| `` `` | Copiar URL do pull request para área de transferência | | -| `` c `` | Checar por nome | Checar por nome. Na caixa de entrada você pode inserir '-' para trocar para a última branch | -| `` - `` | Checkout da branch anterior | | -| `` F `` | Forçar checagem | Forçar checagem da branch selecionada. Isso irá descartar todas as mudanças no seu diretório de trabalho antes cheque a branch selecionada | -| `` d `` | Apagar | Ver opções de exclusão para a branch local/remoto. | -| `` r `` | Refazer | Refazer a branch checada na branch selecionada | -| `` M `` | Mesclar | Ver opções para mesclar o item selecionado no branch atual (mesclar regularmente, mesclar squash) | -| `` f `` | Avanço rápido | Encaminhamento rápido de branch selecionada a partir do upstream. | -| `` T `` | New tag | | -| `` s `` | Sort order | | -| `` g `` | Restaurar | | -| `` R `` | Rename branch | | -| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | -| `` `` | Abrir ferramenta de diff externa (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | View commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Branches remotos - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy branch name to clipboard | | -| `` `` | Verificar | Checar a nova branch baseada na brach remota selecionada, ou a branch remota como HEAD, desanexado | -| `` n `` | Nova branch | | -| `` M `` | Mesclar | Ver opções para mesclar o item selecionado no branch atual (mesclar regularmente, mesclar squash) | -| `` r `` | Refazer | Refazer a branch checada na branch selecionada | -| `` d `` | Apagar | Excluir o branch remoto do controle remoto. | -| `` u `` | Definir como upstream | Definir o ramo remoto selecionado como fluxo do branch check-out. | -| `` s `` | Sort order | | -| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | -| `` `` | Abrir ferramenta de diff externa (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | View commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Commit arquivos - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy path to clipboard | | -| `` y `` | Copy to clipboard | | -| `` c `` | Verificar | Arquivo de check-out. Isso substitui o arquivo em sua árvore de trabalho com a versão do commit selecionado. | -| `` d `` | Remover | Descartar as alterações desse commit para este arquivo. Isso executa uma rebase interativa em segundo plano, então você pode ter um conflito de merge se um commit posterior também alterar este arquivo. | -| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. | -| `` e `` | Editar | Abrir arquivo no editor externo. | -| `` `` | Abrir ferramenta de diff externa (git difftool) | | -| `` `` | Alternar entre o arquivo incluído no patch | Alternar se o arquivo está incluído no patch personalizado. Veja https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` a `` | Alternar todos os arquivos | Adicionar/remover todos os arquivos de commit para atualização personalizada. Consulte https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` `` | Insira o arquivo / Alternar diretório recolhido | Se um arquivo estiver selecionado, insira o arquivo para que você possa adicionar/remover linhas individuais no patch personalizado. Se um diretório for selecionado, ative o diretório. | -| `` ` `` | Alternar exibição de árvore de arquivo | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` - `` | Recolher todos os arquivos | Recolher todos os diretórios na árvore de arquivos | -| `` = `` | Expandir todos os arquivos | Expandir todos os diretórios na árvore do arquivo | -| `` 0 `` | Focus main view | | -| `` / `` | Search the current view by text | | - -## Commits - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy commit hash to clipboard | | -| `` `` | Reset copied (cherry-picked) commits selection | | -| `` b `` | View bisect options | | -| `` s `` | Squash | Squash o commit selecionado no commit abaixo dele. A mensagem do commit selecionado será anexada ao commit abaixo dele. | -| `` f `` | Fixup | Faça o commit selecionado no commit abaixo dele. Semelhante para o squash, mas a mensagem do commit selecionado será descartada. | -| `` r `` | Reword | Repetir a mensagem de submissão selecionada. | -| `` R `` | Republicar com o editor | | -| `` d `` | Descartar | Solte o commit selecionado. Isso irá remover o commit do branch através de uma rebase. Se o commit faz com que as alterações em commits posteriores dependem, você pode precisar resolver conflitos de merge. | -| `` e `` | Editar (iniciar rebase interativa) | Editar o commit selecionado. Use isto para iniciar uma rebase interativa a partir do commit selecionado. Quando já estiver no meio da reconstrução, isto irá marcar o commit selecionado para edição, o que significa que ao continuar com a reformulação. a rebase irá pausar no commit selecionado para permitir que você faça alterações. | -| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
If you would instead like to start an interactive rebase from the selected commit, press `e`. | -| `` p `` | Escolher | Marque o commit selecionado para ser escolhido (quando meados da base). Isso significa que o commit será mantido ao continuar o rebase. | -| `` F `` | Criar commit de correção | Crie o commit 'correção!' para o commit selecionado. Mais tarde, você pode pressionar `S` neste mesmo commit para aplicar todas os commits de correção acima. | -| `` S `` | Aplicar commits de correções | Aplicar Squash all 'correção!', seja acima do commit selecionado, ou tudo no branch atual (autosquash). | -| `` `` | Mover commit um para baixo | | -| `` `` | Mover o commit um para cima | | -| `` V `` | Colar (cherry-pick) | | -| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | -| `` A `` | Modificar | Alterar o commit com mudanças em sted. Se o commit selecionado for o commit HEAD, ele executará o `git commit --amend`. Caso contrário, o compromisso será alterado por meio de uma base de apoio. | -| `` a `` | Alterar atributo de commit | Definir/Redefinir autor de submissão ou co-autor definido. | -| `` t `` | Reverter | Crie um commit reverter para o commit selecionado, que aplica as alterações do commit selecionado em reverso. | -| `` T `` | Tag commit | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | -| `` `` | View log options | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | -| `` `` | Verificar | Checkout the selected commit as a detached HEAD. | -| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Open commit in browser | | -| `` n `` | Create new branch off of commit | | -| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | -| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `` para cancelar a seleção. | -| `` `` | Abrir ferramenta de diff externa (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | Ver arquivos | | -| `` w `` | View worktree options | | -| `` / `` | Search the current view by text | | - -## Confirmation panel - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Confirmar | | -| `` `` | Fechar/Cancelar | | -| `` `` | Copy to clipboard | | - -## Etiquetas - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy tag to clipboard | | -| `` `` | Verificar | Checar a tag selecionada como um HEAD, desanexado | -| `` n `` | New tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | -| `` d `` | Apagar | Ver opções de exclusão para tag local/remoto. | -| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. | -| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | -| `` `` | Abrir ferramenta de diff externa (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | View commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Input prompt - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Confirmar | | -| `` `` | Fechar/Cancelar | | - -## Menu - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Executar | | -| `` `` | Fechar/Cancelar | | -| `` / `` | Filter the current view by text | | - -## Painel Principal (Normal) - -| Key | Action | Info | -|-----|--------|-------------| -| `` mouse wheel down (fn+up) `` | Rolar para baixo | | -| `` mouse wheel up (fn+down) `` | Rolar para cima | | -| `` `` | Mudar de visão | Alternar para outra visão (staged/não processadas alterações). | -| `` `` | Exit back to side panel | | -| `` / `` | Search the current view by text | | - -## Painel Principal (preparação) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Ir para o local anterior | | -| `` `` | Ir para o próximo trecho | | -| `` v `` | Toggle range select | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | Copy selected text to clipboard | | -| `` `` | Etapa | Ativar/desativar seleção em staged/unstaged | -| `` d `` | Descartar | Quando a mudança não desejada for selecionada, descarte a mudança usando `git reset`. Quando a mudança em fase é selecionada, despare a mudança. | -| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. | -| `` e `` | Editar arquivo | Abrir arquivo no editor externo. | -| `` `` | Retornar ao painel de arquivos | | -| `` `` | Mudar de visão | Alternar para outra visão (staged/não processadas alterações). | -| `` E `` | Editar hunk | Editar o local selecionado no editor externo. | -| `` c `` | Commit | Submeter mudanças em staging | -| `` w `` | Fazer commit de alterações sem pré-commit | | -| `` C `` | Enviar alteração usando um editor Git | | -| `` `` | Encontrar commit da base para consertar | Encontre o commit em que as suas mudanças atuais estão se baseando, para alterar/consertar o commit. Isso poupa-te você de ter que olhar pelos commits da sua branch um por um para ver qual commit deve ser alterado/consertado
Veja a documentação:
| -| `` / `` | Search the current view by text | | - -## Painel principal (mesclagem) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Escolha o local | | -| `` b `` | Pegar todos os pedaços | | -| `` `` | Trecho anterior | | -| `` `` | Próximo trecho | | -| `` `` | Conflito anterior | | -| `` `` | Próximo conflito | | -| `` z `` | Desfazer | Desfazer resolução de conflitos de última mesclagem. | -| `` e `` | Editar arquivo | Abrir arquivo no editor externo. | -| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` `` | Retornar ao painel de arquivos | | - -## Painel principal (patch build) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Ir para o local anterior | | -| `` `` | Ir para o próximo trecho | | -| `` v `` | Toggle range select | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | Copy selected text to clipboard | | -| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. | -| `` e `` | Editar arquivo | Abrir arquivo no editor externo. | -| `` `` | Alternar linhas no caminho | | -| `` `` | Sair do construtor de patch personalizado | | -| `` / `` | Search the current view by text | | - -## Reflog - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy commit hash to clipboard | | -| `` `` | Verificar | Checkout the selected commit as a detached HEAD. | -| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Open commit in browser | | -| `` n `` | Create new branch off of commit | | -| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | -| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `` para cancelar a seleção. | -| `` `` | Reset copied (cherry-picked) commits selection | | -| `` `` | Abrir ferramenta de diff externa (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | View commits | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Remotes - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Ver branches | | -| `` n `` | Novo controle | | -| `` d `` | Remover | Remover o controle remoto. Quaisquer ramificações locais de rastreamento de um ramo remoto do controle não serão afetadas. | -| `` e `` | Editar | Edit the selected remote's name or URL. | -| `` f `` | Buscar | Fetch updates from the remote repository. This retrieves new commits and branches without merging them into your local branches. | -| `` / `` | Filter the current view by text | | - -## Secundário - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Mudar de visão | Alternar para outra visão (staged/não processadas alterações). | -| `` `` | Exit back to side panel | | -| `` / `` | Search the current view by text | | - -## Stash - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Aplicar | Aplique o stash no seu diretório de trabalho. | -| `` g `` | Pop | Aplique a entrada de stash no seu diretório de trabalho e remova a entrada de stash. | -| `` d `` | Descartar | Remova a entrada do stash da lista de armazenamento. | -| `` n `` | Nova branch | Criar um novo ramo a partir da entrada de lixo selecionada. Isso funciona verificando o commit do qual a entrada de lixo foi criada, criar um novo branch a partir desse commit e, em seguida, aplicar a entrada de lixo ao novo branch como um commit adicional. | -| `` r `` | Renomear o stasj | | -| `` 0 `` | Focus main view | | -| `` `` | Ver arquivos | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Status - -| Key | Action | Info | -|-----|--------|-------------| -| `` o `` | Abrir o ficheiro de config | Abrir arquivo no aplicativo padrão. | -| `` e `` | Editar arquivo de configuração | Abrir arquivo no editor externo. | -| `` u `` | Verificar atualização | | -| `` `` | Mudar para um repositório recente | | -| `` a `` | Mostrar/ciclo todos os logs de filiais | | -| `` 0 `` | Focus main view | | - -## Sub-commits - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy commit hash to clipboard | | -| `` `` | Verificar | Checkout the selected commit as a detached HEAD. | -| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Open commit in browser | | -| `` n `` | Create new branch off of commit | | -| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | -| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `` para cancelar a seleção. | -| `` `` | Reset copied (cherry-picked) commits selection | | -| `` `` | Abrir ferramenta de diff externa (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | Ver arquivos | | -| `` w `` | View worktree options | | -| `` / `` | Search the current view by text | | - -## Submodules - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy submodule name to clipboard | | -| `` `` | Enter | Enter submodule. After entering the submodule, you can press `` to escape back to the parent repo. | -| `` d `` | Remover | Remove the selected submodule and its corresponding directory. | -| `` u `` | Update | Update selected submodule. | -| `` n `` | New submodule | | -| `` e `` | Update submodule URL | | -| `` i `` | Initialize | Initialize the selected submodule to prepare for fetching. You probably want to follow this up by invoking the 'update' action to fetch the submodule. | -| `` b `` | View bulk submodule options | | -| `` / `` | Filter the current view by text | | - -## Sumário do commit - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Confirmar | | -| `` `` | Fechar | | - -## Worktrees - -| Key | Action | Info | -|-----|--------|-------------| -| `` n `` | New worktree | | -| `` `` | Switch | Switch to the selected worktree. | -| `` o `` | Abrir no editor | | -| `` d `` | Remover | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. | -| `` / `` | Filter the current view by text | | diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md deleted file mode 100644 index c1cd6f53c13..00000000000 --- a/docs/keybindings/Keybindings_ru.md +++ /dev/null @@ -1,413 +0,0 @@ -_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ - -# Lazygit Связки клавиш - -_Связки клавиш_ - -## Глобальные сочетания клавиш - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Переключиться на последний репозиторий | | -| `` (fn+up/shift+k) `` | Прокрутить вверх главную панель | | -| `` (fn+down/shift+j) `` | Прокрутить вниз главную панель | | -| `` @ `` | Открыть меню журнала команд | View options for the command log e.g. show/hide the command log and focus the command log. | -| `` P `` | Отправить изменения | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | -| `` p `` | Получить и слить изменения | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | -| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` } `` | Увеличить размер контекста, отображаемого вокруг изменений в просмотрщике сравнении | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` { `` | Уменьшите размер контекста, отображаемого вокруг изменений в просмотрщике сравнении | Decrease the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. | -| `` `` | Просмотреть пользовательские параметры патча | | -| `` m `` | Просмотреть параметры слияния/перебазирования | View options to abort/continue/skip the current merge/rebase. | -| `` R `` | Обновить | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | -| `` + `` | Следующий режим экрана (нормальный/полуэкранный/полноэкранный) | | -| `` _ `` | Предыдущий режим экрана | | -| `` | `` | Cycle pagers | Choose the next pager in the list of configured pagers | -| `` `` | Отменить | | -| `` ? `` | Открыть меню | | -| `` `` | Просмотреть параметры фильтрации по пути | View options for filtering the commit log, so that only commits matching the filter are shown. | -| `` W `` | Открыть меню сравнении | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` `` | Открыть меню сравнении | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` q `` | Выйти | | -| `` `` | Suspend the application | | -| `` `` | Переключить отображение изменении пробелов в просмотрщике сравнении | Toggle whether or not whitespace changes are shown in the diff view.

The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | -| `` z `` | Отменить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git запустить, чтобы отменить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. | -| `` Z `` | Повторить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git нужно запустить, чтобы повторить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. | - -## Навигация по панели списка - -| Key | Action | Info | -|-----|--------|-------------| -| `` , `` | Предыдущая страница | | -| `` . `` | Следующая страница | | -| `` < () `` | Пролистать наверх | | -| `` > () `` | Прокрутить вниз | | -| `` v `` | Переключить выборку перетаскивания | | -| `` `` | Range select down | | -| `` `` | Range select up | | -| `` / `` | Найти | | -| `` H `` | Прокрутить влево | | -| `` L `` | Прокрутить вправо | | -| `` ] `` | Следующая вкладка | | -| `` [ `` | Предыдущая вкладка | | - -## Input prompt - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Подтвердить | | -| `` `` | Закрыть/отменить | | - -## Worktrees - -| Key | Action | Info | -|-----|--------|-------------| -| `` n `` | New worktree | | -| `` `` | Switch | Switch to the selected worktree. | -| `` o `` | Open in editor | | -| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. | -| `` / `` | Filter the current view by text | | - -## Вторичный - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Переключиться на другую панель (проиндексированные/непроиндексированные изменения) | Switch to other view (staged/unstaged changes). | -| `` `` | Exit back to side panel | | -| `` / `` | Найти | | - -## Главная панель (Индексирование) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Выбрать предыдущую часть | | -| `` `` | Выбрать следующую часть | | -| `` v `` | Переключить выборку перетаскивания | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | Скопировать выделенный текст в буфер обмена | | -| `` `` | Переключить индекс | Переключить строку в проиндексированные / непроиндексированные | -| `` d `` | Отменить изменение (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | -| `` o `` | Открыть файл | Open file in default application. | -| `` e `` | Редактировать файл | Open file in external editor. | -| `` `` | Вернуться к панели файлов | | -| `` `` | Переключиться на другую панель (проиндексированные/непроиндексированные изменения) | Switch to other view (staged/unstaged changes). | -| `` E `` | Изменить эту часть | Edit selected hunk in external editor. | -| `` c `` | Сохранить изменения | Commit staged changes. | -| `` w `` | Закоммитить изменения без предварительного хука коммита | | -| `` C `` | Сохранить изменения с помощью редактора git | | -| `` `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: | -| `` / `` | Найти | | - -## Главная панель (Обычный) - -| Key | Action | Info | -|-----|--------|-------------| -| `` mouse wheel down (fn+up) `` | Прокрутить вниз | | -| `` mouse wheel up (fn+down) `` | Прокрутить вверх | | -| `` `` | Переключиться на другую панель (проиндексированные/непроиндексированные изменения) | Switch to other view (staged/unstaged changes). | -| `` `` | Exit back to side panel | | -| `` / `` | Найти | | - -## Главная панель (Слияние) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Выбрать эту часть | | -| `` b `` | Выбрать все части | | -| `` `` | Выбрать предыдущую часть | | -| `` `` | Выбрать следующую часть | | -| `` `` | Выбрать предыдущий конфликт | | -| `` `` | Выбрать следующий конфликт | | -| `` z `` | Отменить | Undo last merge conflict resolution. | -| `` e `` | Редактировать файл | Open file in external editor. | -| `` o `` | Открыть файл | Open file in default application. | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` `` | Вернуться к панели файлов | | - -## Главная панель (сборка патчей) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Выбрать предыдущую часть | | -| `` `` | Выбрать следующую часть | | -| `` v `` | Переключить выборку перетаскивания | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | Скопировать выделенный текст в буфер обмена | | -| `` o `` | Открыть файл | Open file in default application. | -| `` e `` | Редактировать файл | Open file in external editor. | -| `` `` | Добавить/удалить строку(и) для патча | | -| `` `` | Выйти из сборщика пользовательских патчей | | -| `` / `` | Найти | | - -## Журнал ссылок (Reflog) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Скопировать hash коммита в буфер обмена | | -| `` `` | Переключить | Checkout the selected commit as a detached HEAD. | -| `` y `` | Скопировать атрибут коммита | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Открыть коммит в браузере | | -| `` n `` | Создать новую ветку с этого коммита | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Сбросить отобранную (скопированную | cherry-picked) выборку коммитов | | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | Просмотреть коммиты | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Коммиты - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Скопировать hash коммита в буфер обмена | | -| `` `` | Сбросить отобранную (скопированную | cherry-picked) выборку коммитов | | -| `` b `` | Просмотреть параметры бинарного поиска | | -| `` s `` | Объединить коммиты (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | -| `` f `` | Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | -| `` r `` | Перефразировать коммит | Reword the selected commit's message. | -| `` R `` | Переписать коммит с помощью редактора | | -| `` d `` | Удалить коммит | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | -| `` e `` | Edit (start interactive rebase) | Изменить коммит | -| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
If you would instead like to start an interactive rebase from the selected commit, press `e`. | -| `` p `` | Pick | Выбрать коммит (в середине перебазирования) | -| `` F `` | Создать fixup коммит | Создать fixup коммит для этого коммита | -| `` S `` | Apply fixup commits | Объединить все 'fixup!' коммиты выше в выбранный коммит (автосохранение) | -| `` `` | Переместить коммит вниз на один | | -| `` `` | Переместить коммит вверх на один | | -| `` V `` | Вставить отобранные коммиты (cherry-pick) | | -| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | -| `` A `` | Amend | Править последний коммит с проиндексированными изменениями | -| `` a `` | Установить/убрать автора коммита | Set/Reset commit author or set co-author. | -| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | -| `` T `` | Пометить коммит тегом | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | -| `` `` | Открыть меню журнала | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | -| `` `` | Переключить | Checkout the selected commit as a detached HEAD. | -| `` y `` | Скопировать атрибут коммита | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Открыть коммит в браузере | | -| `` n `` | Создать новую ветку с этого коммита | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | Просмотреть файлы выбранного элемента | | -| `` w `` | View worktree options | | -| `` / `` | Найти | | - -## Локальные Ветки - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Скопировать название ветки в буфер обмена | | -| `` i `` | Показать параметры git-flow | | -| `` `` | Переключить | Checkout selected item. | -| `` n `` | Новая ветка | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` o `` | Создать запрос на принятие изменений | | -| `` O `` | Создать параметры запроса принятие изменений | | -| `` `` | Скопировать URL запроса на принятие изменений в буфер обмена | | -| `` c `` | Переключить по названию | Checkout by name. In the input box you can enter '-' to switch to the previous branch. | -| `` - `` | Checkout previous branch | | -| `` F `` | Принудительное переключение | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | -| `` d `` | Delete | View delete options for local/remote branch. | -| `` r `` | Перебазировать переключённую ветку на эту ветку | Rebase the checked-out branch onto the selected branch. | -| `` M `` | Слияние с текущей переключённой веткой | View options for merging the selected item into the current branch (regular merge, squash merge) | -| `` f `` | Перемотать эту ветку вперёд из её upstream-ветки | Fast-forward selected branch from its upstream. | -| `` T `` | Создать тег | | -| `` s `` | Порядок сортировки | | -| `` g `` | Просмотреть параметры сброса | | -| `` R `` | Переименовать ветку | | -| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | Просмотреть коммиты | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Меню - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Выполнить | | -| `` `` | Закрыть/отменить | | -| `` / `` | Filter the current view by text | | - -## Панель Подтверждения - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Подтвердить | | -| `` `` | Закрыть/отменить | | -| `` `` | Copy to clipboard | | - -## Подкоммиты - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Скопировать hash коммита в буфер обмена | | -| `` `` | Переключить | Checkout the selected commit as a detached HEAD. | -| `` y `` | Скопировать атрибут коммита | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | Открыть коммит в браузере | | -| `` n `` | Создать новую ветку с этого коммита | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | Сбросить отобранную (скопированную | cherry-picked) выборку коммитов | | -| `` `` | Open external diff tool (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | Просмотреть файлы выбранного элемента | | -| `` w `` | View worktree options | | -| `` / `` | Найти | | - -## Подмодули - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Скопировать название подмодуля в буфер обмена | | -| `` `` | Enter | Ввести подмодуль | -| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | -| `` u `` | Update | Обновить подмодуль | -| `` n `` | Добавить новый подмодуль | | -| `` e `` | Обновить URL подмодуля | | -| `` i `` | Initialize | Инициализировать подмодуль | -| `` b `` | Просмотреть параметры массового подмодуля | | -| `` / `` | Filter the current view by text | | - -## Сводка коммита - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Подтвердить | | -| `` `` | Закрыть | | - -## Сохранить Изменения Файлов - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Скопировать название файла в буфер обмена | | -| `` y `` | Copy to clipboard | | -| `` c `` | Переключить | Переключить файл | -| `` d `` | Remove | Отменить изменения коммита в этом файле | -| `` o `` | Открыть файл | Open file in default application. | -| `` e `` | Edit | Open file in external editor. | -| `` `` | Open external diff tool (git difftool) | | -| `` `` | Переключить файлы включённые в патч | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` a `` | Переключить все файлы, включённые в патч | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` `` | Введите файл, чтобы добавить выбранные строки в патч (или свернуть каталог переключения) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | -| `` ` `` | Переключить вид дерева файлов | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | Найти | | - -## Статус - -| Key | Action | Info | -|-----|--------|-------------| -| `` o `` | Открыть файл конфигурации | Open file in default application. | -| `` e `` | Редактировать файл конфигурации | Open file in external editor. | -| `` u `` | Проверить обновления | | -| `` `` | Переключиться на последний репозиторий | | -| `` a `` | Show/cycle all branch logs | | -| `` 0 `` | Focus main view | | - -## Теги - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy tag to clipboard | | -| `` `` | Переключить | Checkout the selected tag as a detached HEAD. | -| `` n `` | Создать тег | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | -| `` d `` | Delete | View delete options for local/remote tag. | -| `` P `` | Отправить тег | Push the selected tag to a remote. You'll be prompted to select a remote. | -| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | Просмотреть коммиты | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Удалённые ветки - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Скопировать название ветки в буфер обмена | | -| `` `` | Переключить | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | -| `` n `` | Новая ветка | | -| `` M `` | Слияние с текущей переключённой веткой | View options for merging the selected item into the current branch (regular merge, squash merge) | -| `` r `` | Перебазировать переключённую ветку на эту ветку | Rebase the checked-out branch onto the selected branch. | -| `` d `` | Delete | Delete the remote branch from the remote. | -| `` u `` | Set as upstream | Установить как upstream-ветку переключённую ветку | -| `` s `` | Порядок сортировки | | -| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` `` | Open external diff tool (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | Просмотреть коммиты | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | - -## Удалённые репозитории - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | View branches | | -| `` n `` | Добавить новую удалённую ветку | | -| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | -| `` e `` | Edit | Редактировать удалённый репозитории | -| `` f `` | Получить изменения | Получение изменения из удалённого репозитория | -| `` / `` | Filter the current view by text | | - -## Файлы - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Скопировать название файла в буфер обмена | | -| `` `` | Переключить индекс | Toggle staged for selected file. | -| `` `` | Фильтровать файлы (проиндексированные/непроиндексированные) | | -| `` y `` | Copy to clipboard | | -| `` c `` | Сохранить изменения | Commit staged changes. | -| `` w `` | Закоммитить изменения без предварительного хука коммита | | -| `` A `` | Правка последнего коммита | | -| `` C `` | Сохранить изменения с помощью редактора git | | -| `` `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: | -| `` e `` | Edit | Open file in external editor. | -| `` o `` | Открыть файл | Open file in default application. | -| `` i `` | Игнорировать или исключить файл | | -| `` r `` | Обновить файлы | | -| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | -| `` S `` | Просмотреть параметры хранилища | View stash options (e.g. stash all, stash staged, stash unstaged). | -| `` a `` | Все проиндексированные/непроиндексированные | Toggle staged/unstaged for all files in working tree. | -| `` `` | Проиндексировать отдельные части/строки для файла или свернуть/развернуть для каталога | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | -| `` d `` | Просмотреть параметры «отмены изменении» | View options for discarding changes to the selected file. | -| `` g `` | Просмотреть параметры сброса upstream-ветки | | -| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | -| `` ` `` | Переключить вид дерева файлов | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` `` | Open external diff tool (git difftool) | | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` f `` | Получить изменения | Fetch changes from remote. | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | Найти | | - -## Хранилище - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Применить припрятанные изменения | Apply the stash entry to your working directory. | -| `` g `` | Применить припрятанные изменения и тут же удалить их из хранилища | Apply the stash entry to your working directory and remove the stash entry. | -| `` d `` | Удалить припрятанные изменения из хранилища | Remove the stash entry from the stash list. | -| `` n `` | Новая ветка | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | -| `` r `` | Переименовать хранилище | | -| `` 0 `` | Focus main view | | -| `` `` | Просмотреть файлы выбранного элемента | | -| `` w `` | View worktree options | | -| `` / `` | Filter the current view by text | | diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md deleted file mode 100644 index 799d7e511b2..00000000000 --- a/docs/keybindings/Keybindings_zh-CN.md +++ /dev/null @@ -1,413 +0,0 @@ -_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ - -# Lazygit 按键绑定 - -_图例:`` 意味着ctrl+b, `意味着Alt+b, `B` 意味着shift+b_ - -## 全局键绑定 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 切换到最近的仓库 | | -| `` (fn+up/shift+k) `` | 向上滚动主面板 | | -| `` (fn+down/shift+j) `` | 向下滚动主面板 | | -| `` @ `` | 打开命令日志菜单 | 查看命令日志的选项,例如显示/隐藏命令日志以及聚焦命令日志 | -| `` P `` | 推送 | 推送当前分支到它的上游。如果上游未配置,您可以在弹窗中配置上游分支。 | -| `` p `` | 拉取 | 从当前分支的远程分支获取改动。如果上游未配置,您可以在弹窗中配置上游分支。 | -| `` ) `` | 提高重命名相似度阈值 | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` ( `` | 降低重命名相似度阈值 | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` } `` | 扩大差异视图中显示的上下文范围 | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` { `` | 缩小差异视图中显示的上下文范围 | Decrease the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` : `` | 执行 Shell 命令 | 调出可输入shell命令执行的提示符。 | -| `` `` | 查看自定义补丁选项 | | -| `` m `` | 查看合并/变基选项 | 查看当前合并或变基的中止、继续、跳过选项 | -| `` R `` | 刷新 | 刷新git状态(即在后台上运行`git status`,`git branch`等命令以更新面板内容) 不会运行`git fetch` | -| `` + `` | 下一屏模式(正常/半屏/全屏) | | -| `` _ `` | 上一屏模式 | | -| `` | `` | Cycle pagers | Choose the next pager in the list of configured pagers | -| `` `` | 取消 | | -| `` ? `` | 打开菜单 | | -| `` `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 | -| `` W `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。 | -| `` `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。 | -| `` q `` | 退出 | | -| `` `` | Suspend the application | | -| `` `` | 切换是否在差异视图中显示空白字符差异 | Toggle whether or not whitespace changes are shown in the diff view.

The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | -| `` z `` | 撤销 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改,只考虑提交。 | -| `` Z `` | 重做 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改,只考虑提交。 | - -## 列表面板导航 - -| Key | Action | Info | -|-----|--------|-------------| -| `` , `` | 上一页 | | -| `` . `` | 下一页 | | -| `` < () `` | 滚动到顶部 | | -| `` > () `` | 滚动到底部 | | -| `` v `` | 切换拖动选择 | | -| `` `` | 向下扩展选择范围 | | -| `` `` | 向上扩展选择范围 | | -| `` / `` | 开始搜索 | | -| `` H `` | 向左滚动 | | -| `` L `` | 向右滚动 | | -| `` ] `` | 下一个标签 | | -| `` [ `` | 上一个标签 | | - -## Input prompt - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 确认 | | -| `` `` | 关闭 | | - -## Reflog - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 复制提交哈希到剪贴板 | | -| `` `` | 检出 | 检出所选择的提交作为分离HEAD。 | -| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(如hash、URL、diff、消息、作者)。 | -| `` o `` | 在浏览器中打开提交 | | -| `` n `` | 从提交创建新分支 | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | -| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `` 来取消选择。 | -| `` `` | 重置已拣选(复制)的提交 | | -| `` `` | 使用外部差异比较工具(git difftool) | | -| `` * `` | 选择当前分支的提交 | | -| `` 0 `` | Focus main view | | -| `` `` | 查看提交 | | -| `` w `` | 查看工作区选项 | | -| `` / `` | 通过文本过滤当前视图 | | - -## 子提交 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 复制提交哈希到剪贴板 | | -| `` `` | 检出 | 检出所选择的提交作为分离HEAD。 | -| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(如hash、URL、diff、消息、作者)。 | -| `` o `` | 在浏览器中打开提交 | | -| `` n `` | 从提交创建新分支 | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | -| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `` 来取消选择。 | -| `` `` | 重置已拣选(复制)的提交 | | -| `` `` | 使用外部差异比较工具(git difftool) | | -| `` * `` | 选择当前分支的提交 | | -| `` 0 `` | Focus main view | | -| `` `` | 查看提交的文件 | | -| `` w `` | 查看工作区选项 | | -| `` / `` | 开始搜索 | | - -## 子模块 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 复制子模块名称到剪贴板 | | -| `` `` | 进入 | 输入子模块 | -| `` d `` | 删除 | 删除选定的子模块及其相应的目录 | -| `` u `` | 更新 | 更新子模块 | -| `` n `` | 添加新的子模块 | | -| `` e `` | 更新子模块 URL | | -| `` i `` | 初始化 | 初始化子模块 | -| `` b `` | 查看批量子模块选项 | | -| `` / `` | 通过文本过滤当前视图 | | - -## 工作区 - -| Key | Action | Info | -|-----|--------|-------------| -| `` n `` | 新建工作树 | | -| `` `` | 切换 | 切换到选中的工作树 | -| `` o `` | 在编辑器中编写 | | -| `` d `` | 删除 | 删除选定的工作树。这将删除工作树的目录以及 .git 目录中有关工作树的元数据。 | -| `` / `` | 通过文本过滤当前视图 | | - -## 提交 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 复制提交哈希到剪贴板 | | -| `` `` | 重置已拣选(复制)的提交 | | -| `` b `` | 查看二分查找选项 | | -| `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 | -| `` f `` | 修正 (fixup) | 将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。 | -| `` r `` | 改写提交 | 重写所选提交的消息。 | -| `` R `` | 使用编辑器重命名提交 | | -| `` d `` | 删除提交 | 删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。 | -| `` e `` | 编辑(开始交互式变基) | 编辑提交 | -| `` i `` | 开始交互式变基 | 为分支上的提交启动交互式变基。这将包括从 HEAD 提交到第一个合并提交或主分支提交的所有提交。
如果您想从所选提交启动交互式变基,请按 `e`。 | -| `` p `` | 拣选(Pick) | 标记选中的提交为 picked(变基过程中)。这意味该提交将在后续的变基中保留。 | -| `` F `` | 为此提交创建修正 | 创建修正提交 | -| `` S `` | 应用该修复提交 | 压缩所选提交之上或当前分支的所有 “fixup!” 提交(自动压缩)。 | -| `` `` | 下移提交 | | -| `` `` | 上移提交 | | -| `` V `` | 粘贴提交(拣选) | | -| `` B `` | 标记一个主提交用于变基 | 选择下一次变基的主提交。当您变基到一个分支时,只有高于主提交的提交才会被引入。这使用“git rebase --onto”命令。 | -| `` A `` | 修补(Amend) | 用已暂存的变更来修补提交 | -| `` a `` | 修补提交属性 | 设置或重置提交的作者,或添加其他作者。 | -| `` t `` | 撤销(Revert) | 为所选提交创建还原提交,这会反向应用所选提交的更改。 | -| `` T `` | 标签提交 | 创建一个新标签指向所选提交。您可以在弹窗中输入标签名称和描述(可选)。 | -| `` `` | 打开日志菜单 | 查看提交日志的选项,例如更改排序顺序、隐藏 git graph、显示整个 git graph。 | -| `` `` | 检出 | 检出所选择的提交作为分离HEAD。 | -| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(如hash、URL、diff、消息、作者)。 | -| `` o `` | 在浏览器中打开提交 | | -| `` n `` | 从提交创建新分支 | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | -| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `` 来取消选择。 | -| `` `` | 使用外部差异比较工具(git difftool) | | -| `` * `` | 选择当前分支的提交 | | -| `` 0 `` | Focus main view | | -| `` `` | 查看提交的文件 | | -| `` w `` | 查看工作区选项 | | -| `` / `` | 开始搜索 | | - -## 提交信息 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 确认 | | -| `` `` | 关闭 | | - -## 提交文件 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 复制路径到剪贴板 | | -| `` y `` | 复制到剪贴板 | | -| `` c `` | 检出 | 检出文件 | -| `` d `` | 删除 | 放弃对此文件的提交变更 | -| `` o `` | 打开文件 | 使用默认程序打开该文件 | -| `` e `` | 编辑 | 使用外部编辑器打开文件 | -| `` `` | 使用外部差异比较工具(git difftool) | | -| `` `` | 补丁中包含的切换文件 | 切换文件是否包含在自定义补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 | -| `` a `` | 操作所有文件 | 添加或删除所有提交中的文件到自定义的补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 | -| `` `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | 如果已选择一个文件,则Enter进入该文件,以便您可以向自定义补丁添加/删除单独的行。如果选择了目录,则切换目录。 | -| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` - `` | 折叠全部文件 | 折叠文件树中的全部目录 | -| `` = `` | 展开全部文件 | 展开文件树中的全部目录 | -| `` 0 `` | Focus main view | | -| `` / `` | 开始搜索 | | - -## 文件 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 复制路径到剪贴板 | | -| `` `` | 切换暂存状态 | 为选定的文件切换暂存状态 | -| `` `` | 通过状态过滤文件 | | -| `` y `` | 复制到剪贴板 | | -| `` c `` | 提交变更 | 提交暂存文件 | -| `` w `` | 提交变更而无需预先提交钩子 | | -| `` A `` | 修补最后一次提交 | | -| `` C `` | 使用 Git 编辑器提交变更 | | -| `` `` | 找到用于修复的基准提交 | 找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: | -| `` e `` | 编辑 | 使用外部编辑器打开文件 | -| `` o `` | 打开文件 | 使用默认程序打开该文件 | -| `` i `` | 忽略文件 | | -| `` r `` | 刷新文件 | | -| `` s `` | 贮藏 | 贮藏所有变更.若要使用其他贮藏变体,请使用查看贮藏选项快捷键 | -| `` S `` | 查看贮藏选项 | 查看贮藏选项(例如:贮藏所有、贮藏已暂存变更、贮藏未暂存变更) | -| `` a `` | 切换所有文件的暂存状态 | 切换工作区中所有文件的已暂存/未暂存状态 | -| `` `` | 暂存单个 块/行 用于文件, 或 折叠/展开 目录 | 如果选中的是一个文件,则会进入到暂存视图,以便可以暂存单个代码块/行。如果选中的是一个目录,则会折叠/展开这个目录 | -| `` d `` | 查看'放弃变更'选项 | 查看选中文件的放弃变更选项 | -| `` g `` | 查看上游重置选项 | | -| `` D `` | 重置 | 查看工作树的重置选项(例如:清除工作树)。 | -| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` `` | 使用外部差异比较工具(git difftool) | | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` f `` | 抓取 | 从远程获取变更 | -| `` - `` | 折叠全部文件 | 折叠文件树中的全部目录 | -| `` = `` | 展开全部文件 | 展开文件树中的全部目录 | -| `` 0 `` | Focus main view | | -| `` / `` | 开始搜索 | | - -## 本地分支 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 复制分支名称到剪贴板 | | -| `` i `` | 显示 git-flow 选项 | | -| `` `` | 检出 | 检出选中的项目 | -| `` n `` | 新分支 | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` o `` | 创建拉取请求 | | -| `` O `` | 创建拉取请求选项 | | -| `` `` | 复制拉取请求 URL 到剪贴板 | | -| `` c `` | 按名称检出 | 按名称检出。在输入框中,您可以输入'-' 来切换到最后一个分支。 | -| `` - `` | Checkout previous branch | | -| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 | -| `` d `` | 删除 | 查看本地/远程分支的删除选项 | -| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 | -| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. | -| `` f `` | 从上游快进此分支 | 将当前分支直接移动到远程追踪分支的最新提交 | -| `` T `` | 创建标签 | | -| `` s `` | 排序 | | -| `` g `` | 查看重置选项 | | -| `` R `` | 重命名分支 | | -| `` u `` | 查看上游选项 | 查看与分支上游相关的选项,例如设置/取消设置上游和重置为上游。 | -| `` `` | 使用外部差异比较工具(git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | 查看提交 | | -| `` w `` | 查看工作区选项 | | -| `` / `` | 通过文本过滤当前视图 | | - -## 构建补丁中 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 选择上一个区块 | | -| `` `` | 选择下一个区块 | | -| `` v `` | 切换拖动选择 | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | 复制选中文本到剪贴板 | | -| `` o `` | 打开文件 | 使用默认程序打开该文件 | -| `` e `` | 编辑文件 | 使用外部编辑器打开文件 | -| `` `` | 添加/移除 行到补丁 | | -| `` `` | 退出逐行模式 | | -| `` / `` | 开始搜索 | | - -## 标签 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 复制标签到剪贴板 | | -| `` `` | 检出 | 检出选择的标签作为分离的HEAD | -| `` n `` | 创建标签 | 基于当前提交创建一个新标签。您将在弹窗中输入标签名称和描述(可选)。 | -| `` d `` | 删除 | 查看本地/远程标签的删除选项 | -| `` P `` | 推送标签 | 推送选择的标签到远端。您将在弹窗中选择一个远端。 | -| `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | -| `` `` | 使用外部差异比较工具(git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | 查看提交 | | -| `` w `` | 查看工作区选项 | | -| `` / `` | 通过文本过滤当前视图 | | - -## 次要 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) | -| `` `` | Exit back to side panel | | -| `` / `` | 开始搜索 | | - -## 正在合并 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 选中区块 | | -| `` b `` | 选中所有区块 | | -| `` `` | 选择顶部块 | | -| `` `` | 选择底部块 | | -| `` `` | 选择上一个冲突 | | -| `` `` | 选择下一个冲突 | | -| `` z `` | 撤销 | 撤消上次合并冲突解决 | -| `` e `` | 编辑文件 | 使用外部编辑器打开文件 | -| `` o `` | 打开文件 | 使用默认程序打开该文件 | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` `` | 返回文件面板 | | - -## 正在暂存 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 选择上一个区块 | | -| `` `` | 选择下一个区块 | | -| `` v `` | 切换拖动选择 | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | 复制选中文本到剪贴板 | | -| `` `` | 切换暂存状态 | 切换行暂存状态 | -| `` d `` | 取消变更(git reset) | 当选择未暂存的变更时,使用git reset丢弃该变更。当选择已暂存的变更时,取消暂存该变更 | -| `` o `` | 打开文件 | 使用默认程序打开该文件 | -| `` e `` | 编辑文件 | 使用外部编辑器打开文件 | -| `` `` | 返回文件面板 | | -| `` `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) | -| `` E `` | 编辑代码块 | 在外部编辑器中编辑选中的代码块 | -| `` c `` | 提交变更 | 提交暂存文件 | -| `` w `` | 提交变更而无需预先提交钩子 | | -| `` C `` | 使用 Git 编辑器提交变更 | | -| `` `` | 找到用于修复的基准提交 | 找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: | -| `` / `` | 开始搜索 | | - -## 正常 - -| Key | Action | Info | -|-----|--------|-------------| -| `` mouse wheel down (fn+up) `` | 向下滚动 | | -| `` mouse wheel up (fn+down) `` | 向上滚动 | | -| `` `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) | -| `` `` | Exit back to side panel | | -| `` / `` | 开始搜索 | | - -## 状态 - -| Key | Action | Info | -|-----|--------|-------------| -| `` o `` | 打开配置文件 | 使用默认程序打开该文件 | -| `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 | -| `` u `` | 检查更新 | | -| `` `` | 切换到最近的仓库 | | -| `` a `` | 显示/循环所有分支日志 | | -| `` 0 `` | Focus main view | | - -## 确认面板 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 确认 | | -| `` `` | 关闭 | | -| `` `` | 复制到剪贴板 | | - -## 菜单 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 执行 | | -| `` `` | 关闭 | | -| `` / `` | 通过文本过滤当前视图 | | - -## 贮藏 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 应用 | 将贮藏项应用到您的工作目录。 | -| `` g `` | 应用并删除 | 将存储项应用到工作目录并删除存储项。 | -| `` d `` | 删除 | 从贮藏列表中删除该贮藏项 | -| `` n `` | 新分支 | 从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。 | -| `` r `` | 重命名贮藏 | | -| `` 0 `` | Focus main view | | -| `` `` | 查看提交的文件 | | -| `` w `` | 查看工作区选项 | | -| `` / `` | 通过文本过滤当前视图 | | - -## 远程 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 查看分支 | | -| `` n `` | 添加新的远程仓库 | | -| `` d `` | 删除 | 删除选中的远程。从远程跟踪远程分支的任何本地分支都不会受到影响。 | -| `` e `` | 编辑 | 编辑远程仓库 | -| `` f `` | 抓取 | 抓取远程仓库 | -| `` / `` | 通过文本过滤当前视图 | | - -## 远程分支 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 复制分支名称到剪贴板 | | -| `` `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。 | -| `` n `` | 新分支 | | -| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. | -| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 | -| `` d `` | 删除 | 从远程删除远程分支。 | -| `` u `` | 设置为上游 | 设置为检出分支的上游 | -| `` s `` | 排序 | | -| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | -| `` `` | 使用外部差异比较工具(git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | 查看提交 | | -| `` w `` | 查看工作区选项 | | -| `` / `` | 通过文本过滤当前视图 | | diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md deleted file mode 100644 index 9c9f2e913e8..00000000000 --- a/docs/keybindings/Keybindings_zh-TW.md +++ /dev/null @@ -1,413 +0,0 @@ -_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ - -# Lazygit 鍵盤快捷鍵 - -_說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_ - -## 全域快捷鍵 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 切換到最近使用的版本庫 | | -| `` (fn+up/shift+k) `` | 向上捲動主面板 | | -| `` (fn+down/shift+j) `` | 向下捲動主面板 | | -| `` @ `` | 開啟命令記錄選單 | View options for the command log e.g. show/hide the command log and focus the command log. | -| `` P `` | 推送 | 推送到遠端。如果沒有設定遠端,會開啟設定視窗。 | -| `` p `` | 拉取 | 從遠端同步當前分支。如果沒有設定遠端,會開啟設定視窗。 | -| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | -| `` } `` | 增加差異檢視中顯示變更周圍上下文的大小 | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` { `` | 減小差異檢視中顯示變更周圍上下文的大小 | Decrease the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | -| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. | -| `` `` | 檢視自訂補丁選項 | | -| `` m `` | 查看合併/變基選項 | View options to abort/continue/skip the current merge/rebase. | -| `` R `` | 重新整理 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | -| `` + `` | 下一個螢幕模式(常規/半螢幕/全螢幕) | | -| `` _ `` | 上一個螢幕模式 | | -| `` | `` | Cycle pagers | Choose the next pager in the list of configured pagers | -| `` `` | 取消 | | -| `` ? `` | 開啟選單 | | -| `` `` | 檢視篩選路徑選項 | View options for filtering the commit log, so that only commits matching the filter are shown. | -| `` W `` | 開啟差異比較選單 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` `` | 開啟差異比較選單 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | -| `` q `` | 結束 | | -| `` `` | Suspend the application | | -| `` `` | 切換是否在差異檢視中顯示空格變更 | Toggle whether or not whitespace changes are shown in the diff view.

The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | -| `` z `` | 復原 | 將使用 reflog 確任 git 指令以復原。這不包括工作區更改;只考慮提交。 | -| `` Z `` | 取消復原 | 將使用 reflog 確任 git 指令以重作。這不包括工作區更改;只考慮提交。 | - -## 移動 - -| Key | Action | Info | -|-----|--------|-------------| -| `` , `` | 上一頁 | | -| `` . `` | 下一頁 | | -| `` < () `` | 捲動到頂部 | | -| `` > () `` | 捲動到底部 | | -| `` v `` | 切換拖曳選擇 | | -| `` `` | Range select down | | -| `` `` | Range select up | | -| `` / `` | 搜尋 | | -| `` H `` | 向左捲動 | | -| `` L `` | 向右捲動 | | -| `` ] `` | 下一個索引標籤 | | -| `` [ `` | 上一個索引標籤 | | - -## Input prompt - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 確認 | | -| `` `` | 關閉/取消 | | - -## 主面板 (補丁生成) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 選擇上一段 | | -| `` `` | 選擇下一段 | | -| `` v `` | 切換拖曳選擇 | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | 複製所選文本至剪貼簿 | | -| `` o `` | 開啟檔案 | 使用預設軟體開啟 | -| `` e `` | 編輯檔案 | 使用外部編輯器開啟 | -| `` `` | 向 (或從) 補丁中添加/刪除行 | | -| `` `` | 退出自訂補丁建立器 | | -| `` / `` | 搜尋 | | - -## 主面板(一般) - -| Key | Action | Info | -|-----|--------|-------------| -| `` mouse wheel down (fn+up) `` | 向下捲動 | | -| `` mouse wheel up (fn+down) `` | 向上捲動 | | -| `` `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). | -| `` `` | Exit back to side panel | | -| `` / `` | 搜尋 | | - -## 主面板(合併) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 挑選程式碼片段 | | -| `` b `` | 挑選所有程式碼片段 | | -| `` `` | 選擇上一段 | | -| `` `` | 選擇下一段 | | -| `` `` | 選擇上一個衝突 | | -| `` `` | 選擇下一個衝突 | | -| `` z `` | 復原 | Undo last merge conflict resolution. | -| `` e `` | 編輯檔案 | 使用外部編輯器開啟 | -| `` o `` | 開啟檔案 | 使用預設軟體開啟 | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` `` | 返回檔案面板 | | - -## 主面板(預存) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 選擇上一段 | | -| `` `` | 選擇下一段 | | -| `` v `` | 切換拖曳選擇 | | -| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | -| `` `` | 複製所選文本至剪貼簿 | | -| `` `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) | -| `` d `` | 刪除變更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | -| `` o `` | 開啟檔案 | 使用預設軟體開啟 | -| `` e `` | 編輯檔案 | 使用外部編輯器開啟 | -| `` `` | 返回檔案面板 | | -| `` `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). | -| `` E `` | 編輯程式碼塊 | Edit selected hunk in external editor. | -| `` c `` | 提交變更 | 提交暫存區變更 | -| `` w `` | 沒有預提交 hook 就提交更改 | | -| `` C `` | 使用 git 編輯器提交變更 | | -| `` `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: | -| `` / `` | 搜尋 | | - -## 功能表 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 執行 | | -| `` `` | 關閉/取消 | | -| `` / `` | 搜尋 | | - -## 子提交 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 複製提交 hash 到剪貼簿 | | -| `` `` | 檢出 | Checkout the selected commit as a detached HEAD. | -| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | 在瀏覽器中開啟提交 | | -| `` n `` | 從提交建立新分支 | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | 重設選定的揀選 (複製) 提交 | | -| `` `` | 開啟外部差異工具 (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | 檢視所選項目的檔案 | | -| `` w `` | 檢視工作目錄選項 | | -| `` / `` | 搜尋 | | - -## 子模組 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 複製子模組名稱到剪貼簿 | | -| `` `` | Enter | 進入子模組 | -| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | -| `` u `` | Update | 更新子模組 | -| `` n `` | 新增子模組 | | -| `` e `` | 更新子模組 URL | | -| `` i `` | Initialize | 初始化子模組 | -| `` b `` | 查看批量子模組選項 | | -| `` / `` | 搜尋 | | - -## 工作目錄 - -| Key | Action | Info | -|-----|--------|-------------| -| `` n `` | New worktree | | -| `` `` | Switch | Switch to the selected worktree. | -| `` o `` | 在編輯器中開啟 | | -| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. | -| `` / `` | 搜尋 | | - -## 提交 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 複製提交 hash 到剪貼簿 | | -| `` `` | 重設選定的揀選 (複製) 提交 | | -| `` b `` | 查看二分選項 | | -| `` s `` | 壓縮 (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | -| `` f `` | 修復 (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | -| `` r `` | 改寫提交 | 改寫選中的提交訊息 | -| `` R `` | 使用編輯器改寫提交 | | -| `` d `` | 刪除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | -| `` e `` | 編輯(開始互動變基) | 編輯提交 | -| `` i `` | 開始互動變基 | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
If you would instead like to start an interactive rebase from the selected commit, press `e`. | -| `` p `` | 挑選 | 挑選提交 (於變基過程中) | -| `` F `` | 建立修復提交 | 為此提交建立修復提交 | -| `` S `` | 壓縮上方所有「fixup」提交(自動壓縮) | 是否壓縮上方 {{.commit}} 所有「fixup」提交? | -| `` `` | 向下移動提交 | | -| `` `` | 向上移動提交 | | -| `` V `` | 貼上提交 (揀選) | | -| `` B `` | 為了變基已標注提交為基準提交 | 請為了下一次變基選擇一項基準提交;此將執行 `git rebase --onto`。 | -| `` A `` | 修改 | 使用已預存的更改修正提交 | -| `` a `` | 設定/重設提交作者 | Set/Reset commit author or set co-author. | -| `` t `` | 還原 | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | -| `` T `` | 打標籤到提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | -| `` `` | 開啟記錄選單 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | -| `` `` | 檢出 | Checkout the selected commit as a detached HEAD. | -| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | 在瀏覽器中開啟提交 | | -| `` n `` | 從提交建立新分支 | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | 開啟外部差異工具 (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | 檢視所選項目的檔案 | | -| `` w `` | 檢視工作目錄選項 | | -| `` / `` | 搜尋 | | - -## 提交摘要 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 確認 | | -| `` `` | 關閉 | | - -## 提交檔案 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 複製檔案名稱到剪貼簿 | | -| `` y `` | 複製到剪貼簿 | | -| `` c `` | 檢出 | 檢出檔案 | -| `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. | -| `` o `` | 開啟檔案 | 使用預設軟體開啟 | -| `` e `` | 編輯 | 使用外部編輯器開啟 | -| `` `` | 開啟外部差異工具 (git difftool) | | -| `` `` | 切換檔案是否包含在補丁中 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` a `` | 切換所有檔案是否包含在補丁中 | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | -| `` `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | -| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | 搜尋 | | - -## 收藏 (Stash) - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 套用 | Apply the stash entry to your working directory. | -| `` g `` | 還原 | Apply the stash entry to your working directory and remove the stash entry. | -| `` d `` | 捨棄 | Remove the stash entry from the stash list. | -| `` n `` | 新分支 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | -| `` r `` | 重新命名收藏 | | -| `` 0 `` | Focus main view | | -| `` `` | 檢視所選項目的檔案 | | -| `` w `` | 檢視工作目錄選項 | | -| `` / `` | 搜尋 | | - -## 日誌 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 複製提交 hash 到剪貼簿 | | -| `` `` | 檢出 | Checkout the selected commit as a detached HEAD. | -| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | -| `` o `` | 在瀏覽器中開啟提交 | | -| `` n `` | 從提交建立新分支 | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `` to cancel the selection. | -| `` `` | 重設選定的揀選 (複製) 提交 | | -| `` `` | 開啟外部差異工具 (git difftool) | | -| `` * `` | Select commits of current branch | | -| `` 0 `` | Focus main view | | -| `` `` | 檢視提交 | | -| `` w `` | 檢視工作目錄選項 | | -| `` / `` | 搜尋 | | - -## 本地分支 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 複製分支名稱到剪貼簿 | | -| `` i `` | 顯示 git-flow 選項 | | -| `` `` | 檢出 | 檢出選定的項目。 | -| `` n `` | 新分支 | | -| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.

Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | -| `` o `` | 建立拉取請求 | | -| `` O `` | 建立拉取請求選項 | | -| `` `` | 複製拉取請求的 URL 到剪貼板 | | -| `` c `` | 根據名稱檢出 | Checkout by name. In the input box you can enter '-' to switch to the previous branch. | -| `` - `` | Checkout previous branch | | -| `` F `` | 強制檢出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | -| `` d `` | 刪除 | View delete options for local/remote branch. | -| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. | -| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) | -| `` f `` | 從上游快進此分支 | 從遠端快進所選的分支 | -| `` T `` | 建立標籤 | | -| `` s `` | 排序規則 | | -| `` g `` | 檢視重設選項 | | -| `` R `` | 重新命名分支 | | -| `` u `` | 檢視遠端設定 | 檢視有關遠端分支的設定(例如重設至遠端) | -| `` `` | 開啟外部差異工具 (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | 檢視提交 | | -| `` w `` | 檢視工作目錄選項 | | -| `` / `` | 搜尋 | | - -## 標籤 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | Copy tag to clipboard | | -| `` `` | 檢出 | Checkout the selected tag as a detached HEAD. | -| `` n `` | 建立標籤 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | -| `` d `` | 刪除 | View delete options for local/remote tag. | -| `` P `` | 推送標籤 | Push the selected tag to a remote. You'll be prompted to select a remote. | -| `` g `` | 重設 | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` `` | 開啟外部差異工具 (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | 檢視提交 | | -| `` w `` | 檢視工作目錄選項 | | -| `` / `` | 搜尋 | | - -## 檔案 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 複製檔案名稱到剪貼簿 | | -| `` `` | 切換預存 | Toggle staged for selected file. | -| `` `` | 篩選檔案 (預存/未預存) | | -| `` y `` | 複製到剪貼簿 | | -| `` c `` | 提交變更 | 提交暫存區變更 | -| `` w `` | 沒有預提交 hook 就提交更改 | | -| `` A `` | 修改上次提交 | | -| `` C `` | 使用 git 編輯器提交變更 | | -| `` `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: | -| `` e `` | 編輯 | 使用外部編輯器開啟 | -| `` o `` | 開啟檔案 | 使用預設軟體開啟 | -| `` i `` | 忽略或排除檔案 | | -| `` r `` | 重新整理檔案 | | -| `` s `` | 收藏 | Stash all changes. For other variations of stashing, use the view stash options keybinding. | -| `` S `` | 檢視收藏選項 | View stash options (e.g. stash all, stash staged, stash unstaged). | -| `` a `` | 全部預存/取消預存 | Toggle staged/unstaged for all files in working tree. | -| `` `` | 選擇檔案中的單個程式碼塊/行,或展開/折疊目錄 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | -| `` d `` | 捨棄 | 檢視選中變動進行捨棄復原 | -| `` g `` | 檢視遠端重設選項 | | -| `` D `` | 重設 | View reset options for working tree (e.g. nuking the working tree). | -| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.

The default can be changed in the config file with the key 'gui.showFileTree'. | -| `` `` | 開啟外部差異工具 (git difftool) | | -| `` M `` | View merge conflict options | View options for resolving merge conflicts. | -| `` f `` | 擷取 | 同步遠端異動 | -| `` - `` | Collapse all files | Collapse all directories in the files tree | -| `` = `` | Expand all files | Expand all directories in the file tree | -| `` 0 `` | Focus main view | | -| `` / `` | 搜尋 | | - -## 次要 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). | -| `` `` | Exit back to side panel | | -| `` / `` | 搜尋 | | - -## 狀態 - -| Key | Action | Info | -|-----|--------|-------------| -| `` o `` | 開啟設定檔案 | 使用預設軟體開啟 | -| `` e `` | 編輯設定檔案 | 使用外部編輯器開啟 | -| `` u `` | 檢查更新 | | -| `` `` | 切換到最近使用的版本庫 | | -| `` a `` | Show/cycle all branch logs | | -| `` 0 `` | Focus main view | | - -## 確認面板 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 確認 | | -| `` `` | 關閉/取消 | | -| `` `` | 複製到剪貼簿 | | - -## 遠端 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | View branches | | -| `` n `` | 新增遠端 | | -| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | -| `` e `` | 編輯 | 編輯遠端 | -| `` f `` | 擷取 | 擷取遠端 | -| `` / `` | 搜尋 | | - -## 遠端分支 - -| Key | Action | Info | -|-----|--------|-------------| -| `` `` | 複製分支名稱到剪貼簿 | | -| `` `` | 檢出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | -| `` n `` | 新分支 | | -| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) | -| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. | -| `` d `` | 刪除 | Delete the remote branch from the remote. | -| `` u `` | 設置為遠端 | 將此分支設為當前分支之遠端 | -| `` s `` | 排序規則 | | -| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | -| `` `` | 開啟外部差異工具 (git difftool) | | -| `` 0 `` | Focus main view | | -| `` `` | 檢視提交 | | -| `` w `` | 檢視工作目錄選項 | | -| `` / `` | 搜尋 | | diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 55f0c3139bd..00000000000 --- a/flake.lock +++ /dev/null @@ -1,127 +0,0 @@ -{ - "nodes": { - "flake-compat": { - "locked": { - "lastModified": 1733328505, - "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", - "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", - "revCount": 69, - "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" - } - }, - "flake-parts": { - "inputs": { - "nixpkgs-lib": "nixpkgs-lib" - }, - "locked": { - "lastModified": 1759362264, - "narHash": "sha256-wfG0S7pltlYyZTM+qqlhJ7GMw2fTF4mLKCIVhLii/4M=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "758cf7296bee11f1706a574c77d072b8a7baa881", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1759831965, - "narHash": "sha256-vgPm2xjOmKdZ0xKA6yLXPJpjOtQPHfaZDRtH+47XEBo=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "c9b6fb798541223bbb396d287d16f43520250518", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-lib": { - "locked": { - "lastModified": 1754788789, - "narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=", - "owner": "nix-community", - "repo": "nixpkgs.lib", - "rev": "a73b9c743612e4244d865a2fdee11865283c04e6", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "nixpkgs.lib", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1754340878, - "narHash": "sha256-lgmUyVQL9tSnvvIvBp7x1euhkkCho7n3TMzgjdvgPoU=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "cab778239e705082fe97bb4990e0d24c50924c04", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-compat": "flake-compat", - "flake-parts": "flake-parts", - "nixpkgs": "nixpkgs", - "systems": "systems", - "treefmt-nix": "treefmt-nix" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "treefmt-nix": { - "inputs": { - "nixpkgs": "nixpkgs_2" - }, - "locked": { - "lastModified": 1758728421, - "narHash": "sha256-ySNJ008muQAds2JemiyrWYbwbG+V7S5wg3ZVKGHSFu8=", - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "5eda4ee8121f97b218f7cc73f5172098d458f1d1", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "treefmt-nix", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index b8069e9f746..00000000000 --- a/flake.nix +++ /dev/null @@ -1,125 +0,0 @@ -{ - description = "A simple terminal UI for git commands"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - systems.url = "github:nix-systems/default"; - flake-parts.url = "github:hercules-ci/flake-parts"; - flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"; - treefmt-nix.url = "github:numtide/treefmt-nix"; - }; - - outputs = - inputs@{ flake-parts, systems, ... }: - flake-parts.lib.mkFlake { inherit inputs; } { - systems = import systems; - imports = [ - inputs.treefmt-nix.flakeModule - ]; - - perSystem = - { - pkgs, - system, - ... - }: - let - goMod = builtins.readFile ./go.mod; - versionMatch = builtins.match ".*go[[:space:]]([0-9]+\\.[0-9]+)(\\.[0-9]+)?.*" goMod; - - goVersion = - if versionMatch != null then - builtins.head versionMatch - else - throw "Could not extract Go version from go.mod"; - - goOverlay = final: prev: { - go = prev."go_${builtins.replaceStrings [ "." ] [ "_" ] goVersion}"; - }; - - lazygit = pkgs.buildGoModule rec { - pname = "lazygit"; - version = "dev"; - - gitCommit = inputs.self.rev or inputs.self.dirtyRev or "dev"; - - src = ./.; - vendorHash = null; - - # Disable integration tests that require specific environment - doCheck = false; - - nativeBuildInputs = with pkgs; [ - git - makeWrapper - ]; - buildInputs = [ pkgs.git ]; - - ldflags = [ - "-s" - "-w" - "-X main.commit=${gitCommit}" - "-X main.version=${version}" - "-X main.buildSource=nix" - ]; - - postInstall = '' - wrapProgram $out/bin/lazygit \ - --prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.git ]} - ''; - - meta = { - description = "A simple terminal UI for git commands"; - homepage = "https://github.com/jesseduffield/lazygit"; - license = pkgs.lib.licenses.mit; - maintainers = [ "jesseduffield" ]; - platforms = pkgs.lib.platforms.unix; - mainProgram = "lazygit"; - }; - }; - in - { - _module.args.pkgs = import inputs.nixpkgs { - inherit system; - overlays = [ goOverlay ]; - config = { }; - }; - - packages = { - default = lazygit; - inherit lazygit; - }; - - devShells.default = pkgs.mkShell { - name = "lazygit-dev"; - - buildInputs = with pkgs; [ - # Go toolchain - go - gotools - - # Development tools - git - gnumake - ]; - - # Environment variables for development - CGO_ENABLED = "0"; - }; - - treefmt = { - programs.nixfmt.enable = pkgs.lib.meta.availableOn pkgs.stdenv.buildPlatform pkgs.nixfmt-rfc-style.compiler; - programs.nixfmt.package = pkgs.nixfmt-rfc-style; - programs.gofmt.enable = true; - }; - - checks.build = lazygit; - }; - - flake = { - overlays.default = final: prev: { - lazygit = inputs.self.packages.${final.system}.lazygit; - }; - }; - }; -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 81f7d8df325..00000000000 --- a/go.mod +++ /dev/null @@ -1,89 +0,0 @@ -module github.com/jesseduffield/lazygit - -go 1.25.0 - -// This is necessary to ignore test files when executing gofumpt. -ignore ./test - -require ( - dario.cat/mergo v1.0.1 - github.com/adrg/xdg v0.4.0 - github.com/atotto/clipboard v0.1.4 - github.com/aybabtme/humanlog v0.4.1 - github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 - github.com/creack/pty v1.1.11 - github.com/gdamore/tcell/v2 v2.9.0 - github.com/go-errors/errors v1.5.1 - github.com/gookit/color v1.4.2 - github.com/integrii/flaggy v1.4.0 - github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c - github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd - github.com/jesseduffield/gocui v0.3.1-0.20251002151855-67e0e55ff42a - github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 - github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e - github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 - github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3 - github.com/kyokomi/emoji/v2 v2.2.8 - github.com/lucasb-eyer/go-colorful v1.3.0 - github.com/mattn/go-runewidth v0.0.19 - github.com/mgutz/str v1.2.0 - github.com/mitchellh/go-ps v1.0.0 - github.com/sahilm/fuzzy v0.1.0 - github.com/samber/lo v1.31.0 - github.com/sanity-io/litter v1.5.2 - github.com/sasha-s/go-deadlock v0.3.6 - github.com/sirupsen/logrus v1.9.3 - github.com/spf13/afero v1.9.5 - github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad - github.com/stefanhaller/git-todo-parser v0.0.7-0.20250905083220-c50528f08304 - github.com/stretchr/testify v1.10.0 - github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/sync v0.17.0 - golang.org/x/sys v0.36.0 - gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0 - gopkg.in/yaml.v3 v3.0.1 -) - -require ( - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.1.6 // indirect - github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/buger/jsonparser v1.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.2.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emirpasic/gods v1.18.1 // indirect - github.com/fatih/color v1.9.0 // indirect - github.com/gdamore/encoding v1.0.1 // indirect - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-logfmt/logfmt v0.5.0 // indirect - github.com/gobwas/glob v0.2.3 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/hpcloud/tail v1.0.0 // indirect - github.com/invopop/jsonschema v0.10.0 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect - github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.11 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect - github.com/onsi/ginkgo v1.10.3 // indirect - github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect - github.com/pjbgf/sha1cd v0.3.2 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.3.1 // indirect - github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect - golang.org/x/crypto v0.37.0 // indirect - golang.org/x/net v0.39.0 // indirect - golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect - gopkg.in/fsnotify.v1 v1.4.7 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index ec9495501f0..00000000000 --- a/go.sum +++ /dev/null @@ -1,697 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= -github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= -github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= -github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aybabtme/humanlog v0.4.1 h1:D8d9um55rrthJsP8IGSHBcti9lTb/XknmDAX6Zy8tek= -github.com/aybabtme/humanlog v0.4.1/go.mod h1:B0bnQX4FTSU3oftPMTTPvENCy8LqixLDvYJA9TUCAGo= -github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= -github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= -github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= -github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do= -github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= -github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= -github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.1-0.20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= -github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= -github.com/gdamore/tcell/v2 v2.8.0/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw= -github.com/gdamore/tcell/v2 v2.9.0 h1:N6t+eqK7/xwtRPwxzs1PXeRWnm0H9l02CrgJ7DLn1ys= -github.com/gdamore/tcell/v2 v2.9.0/go.mod h1:8/ZoqM9rxzYphT9tH/9LnunhV9oPBqwS8WHGYm5nrmo= -github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= -github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= -github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= -github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk= -github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/integrii/flaggy v1.4.0 h1:A1x7SYx4jqu5NSrY14z8Z+0UyX2S5ygfJJrfolWR3zM= -github.com/integrii/flaggy v1.4.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI= -github.com/invopop/jsonschema v0.10.0 h1:c1ktzNLBun3LyQQhyty5WE3lulbOdIIyOVlkmDLehcE= -github.com/invopop/jsonschema v0.10.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c h1:tC2PaiisXAC5sOjDPfMArSnbswDObtCssx+xn28edX4= -github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c/go.mod h1:F2fEBk0ddf6ixrBrJjY7phfQ3hL9rXG0uSjvwYe50bE= -github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd h1:ViKj6qth8FgcIWizn9KiACWwPemWSymx62OPN0tHT+Q= -github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd/go.mod h1:lRhCiBr6XjQrvcQVa+UYsy/99d3wMXn/a0nSQlhnhlA= -github.com/jesseduffield/gocui v0.3.1-0.20251002151855-67e0e55ff42a h1:z3NQvFXAWGT4B/MwQBZc+1ej8WJ/Nv35xngQRvwzPuI= -github.com/jesseduffield/gocui v0.3.1-0.20251002151855-67e0e55ff42a/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s= -github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= -github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5/go.mod h1:qxN4mHOAyeIDLP7IK7defgPClM/z1Kze8VVQiaEjzsQ= -github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U= -github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e/go.mod h1:u60qdFGXRd36jyEXxetz0vQceQIxzI13lIo3EFUDf4I= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= -github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3 h1:s995u+gNQADMaixtNOs+jilRC/Q78q0UXSI7+4T0cDE= -github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3/go.mod h1:MCbEh21gjOzxc31udr3u4QM9DAdf8TFJCZz3u5hYIxA= -github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= -github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE= -github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= -github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw= -github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= -github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE= -github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= -github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= -github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM= -github.com/samber/lo v1.31.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= -github.com/sanity-io/litter v1.5.2 h1:AnC8s9BMORWH5a4atZ4D6FPVvKGzHcnc5/IVTa87myw= -github.com/sanity-io/litter v1.5.2/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0= -github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= -github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= -github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= -github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= -github.com/stefanhaller/git-todo-parser v0.0.7-0.20250905083220-c50528f08304 h1:bg+K3E0GYuqwTGaEfNrsZ0rH0Bw4p3EmPjk9Zjnua+w= -github.com/stefanhaller/git-todo-parser v0.0.7-0.20250905083220-c50528f08304/go.mod h1:HFt9hGqMzgQ+gVxMKcvTvGaFz4Y0yYycqqAp2V3wcJY= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= -github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= -github.com/urfave/cli v1.20.1-0.20180226030253-8e01ec4cd3e2/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= -github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= -github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= -github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0 h1:KzcWKJ0nMAmGoBhYVMnkWc1rXjB42lKy5aIys4TdLOA= -gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0/go.mod h1:XoytMOotjRRJVkIsQdxsPIioRLYFISEaY9a4tftOXAo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/lazygit-example.gif b/lazygit-example.gif new file mode 100644 index 00000000000..c169f84704a Binary files /dev/null and b/lazygit-example.gif differ diff --git a/main.go b/main.go deleted file mode 100644 index d7ce1db1455..00000000000 --- a/main.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "github.com/jesseduffield/lazygit/pkg/app" -) - -// These values may be set by the build script via the LDFLAGS argument -var ( - commit string - date string - version string - buildSource = "unknown" -) - -func main() { - ldFlagsBuildInfo := &app.BuildInfo{ - Commit: commit, - Date: date, - Version: version, - BuildSource: buildSource, - } - - app.Start(ldFlagsBuildInfo, nil) -} diff --git a/pkg/app/app.go b/pkg/app/app.go deleted file mode 100644 index 09b2236db4a..00000000000 --- a/pkg/app/app.go +++ /dev/null @@ -1,290 +0,0 @@ -package app - -import ( - "bufio" - "fmt" - "io" - "log" - "os" - "path/filepath" - "strings" - - "github.com/go-errors/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/afero" - - appTypes "github.com/jesseduffield/lazygit/pkg/app/types" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/constants" - "github.com/jesseduffield/lazygit/pkg/env" - "github.com/jesseduffield/lazygit/pkg/gui" - "github.com/jesseduffield/lazygit/pkg/i18n" - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" - "github.com/jesseduffield/lazygit/pkg/logs" - "github.com/jesseduffield/lazygit/pkg/updates" -) - -// App is the struct that's instantiated from within main.go and it manages -// bootstrapping and running the application. -type App struct { - *common.Common - closers []io.Closer - Config config.AppConfigurer - OSCommand *oscommands.OSCommand - Gui *gui.Gui -} - -func Run( - config config.AppConfigurer, - common *common.Common, - startArgs appTypes.StartArgs, -) { - app, err := NewApp(config, startArgs.IntegrationTest, common) - - if err == nil { - err = app.Run(startArgs) - } - - if err != nil { - if errorMessage, known := knownError(common.Tr, err); known { - log.Fatal(errorMessage) - } - newErr := errors.Wrap(err, 0) - stackTrace := newErr.ErrorStack() - app.Log.Error(stackTrace) - - log.Fatalf("%s: %s\n\n%s", common.Tr.ErrorOccurred, constants.Links.Issues, stackTrace) - } -} - -func NewCommon(config config.AppConfigurer) (*common.Common, error) { - userConfig := config.GetUserConfig() - appState := config.GetAppState() - log := newLogger(config) - // Initialize with English for the time being; the real translation set for - // the configured language will be read after reading the user config - tr := i18n.EnglishTranslationSet() - - cmn := &common.Common{ - Log: log, - Tr: tr, - AppState: appState, - Debug: config.GetDebug(), - Fs: afero.NewOsFs(), - } - cmn.SetUserConfig(userConfig) - return cmn, nil -} - -func newLogger(cfg config.AppConfigurer) *logrus.Entry { - if cfg.GetDebug() { - logPath, err := config.LogPath() - if err != nil { - log.Fatal(err) - } - return logs.NewDevelopmentLogger(logPath) - } - - return logs.NewProductionLogger() -} - -// NewApp bootstrap a new application -func NewApp(config config.AppConfigurer, test integrationTypes.IntegrationTest, common *common.Common) (*App, error) { - app := &App{ - closers: []io.Closer{}, - Config: config, - Common: common, - } - - app.OSCommand = oscommands.NewOSCommand(common, config, oscommands.GetPlatform(), oscommands.NewNullGuiIO(app.Log)) - - updater, err := updates.NewUpdater(common, config, app.OSCommand) - if err != nil { - return app, err - } - - dirName, err := os.Getwd() - if err != nil { - return app, err - } - - gitVersion, err := app.validateGitVersion() - if err != nil { - return app, err - } - - // If we're not in a repo, GetRepoPaths will return an error. The error is moot for us - // at this stage, since we'll try to init a new repo in setupRepo(), below - repoPaths, err := git_commands.GetRepoPaths(app.OSCommand.Cmd, gitVersion) - if err != nil { - common.Log.Infof("Error getting repo paths: %v", err) - } - - showRecentRepos, err := app.setupRepo(repoPaths) - if err != nil { - return app, err - } - - // used for testing purposes - if os.Getenv("SHOW_RECENT_REPOS") == "true" { - showRecentRepos = true - } - - app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName, test) - if err != nil { - return app, err - } - return app, nil -} - -const minGitVersionStr = "2.32.0" - -func minGitVersionErrorMessage(tr *i18n.TranslationSet) string { - return fmt.Sprintf(tr.MinGitVersionError, minGitVersionStr) -} - -func (app *App) validateGitVersion() (*git_commands.GitVersion, error) { - version, err := git_commands.GetGitVersion(app.OSCommand) - // if we get an error anywhere here we'll show the same status - minVersionError := errors.New(minGitVersionErrorMessage(app.Tr)) - if err != nil { - return nil, minVersionError - } - - minRequiredVersion, _ := git_commands.ParseGitVersion(minGitVersionStr) - if version.IsOlderThanVersion(minRequiredVersion) { - return nil, minVersionError - } - - return version, nil -} - -func isDirectoryAGitRepository(dir string) (bool, error) { - info, err := os.Stat(filepath.Join(dir, ".git")) - return info != nil, err -} - -func openRecentRepo(app *App) bool { - for _, repoDir := range app.Config.GetAppState().RecentRepos { - if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo { - if err := os.Chdir(repoDir); err == nil { - return true - } - } - } - - return false -} - -func (app *App) setupRepo( - repoPaths *git_commands.RepoPaths, -) (bool, error) { - if env.GetGitDirEnv() != "" { - // we've been given the git dir directly. Skip setup - return false, nil - } - - // if we are not in a git repo, we ask if we want to `git init` - if repoPaths == nil { - cwd, err := os.Getwd() - if err != nil { - return false, err - } - - if isRepo, err := isDirectoryAGitRepository(cwd); isRepo { - return false, err - } - - var shouldInitRepo bool - initialBranchArg := "" - switch app.UserConfig().NotARepository { - case "prompt": - // Offer to initialize a new repository in current directory. - fmt.Print(app.Tr.CreateRepo) - response, _ := bufio.NewReader(os.Stdin).ReadString('\n') - shouldInitRepo = (strings.Trim(response, " \r\n") == "y") - if shouldInitRepo { - // Ask for the initial branch name - fmt.Print(app.Tr.InitialBranch) - response, _ := bufio.NewReader(os.Stdin).ReadString('\n') - if trimmedResponse := strings.Trim(response, " \r\n"); len(trimmedResponse) > 0 { - initialBranchArg += "--initial-branch=" + trimmedResponse - } - } - case "create": - shouldInitRepo = true - case "skip": - shouldInitRepo = false - case "quit": - fmt.Fprintln(os.Stderr, app.Tr.NotARepository) - os.Exit(1) - default: - fmt.Fprintln(os.Stderr, app.Tr.IncorrectNotARepository) - os.Exit(1) - } - - if shouldInitRepo { - args := []string{"git", "init"} - if initialBranchArg != "" { - args = append(args, initialBranchArg) - } - if err := app.OSCommand.Cmd.New(args).Run(); err != nil { - return false, err - } - - return false, nil - } - - // check if we have a recent repo we can open - for _, repoDir := range app.Config.GetAppState().RecentRepos { - if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo { - if err := os.Chdir(repoDir); err == nil { - return true, nil - } - } - } - - fmt.Fprintln(os.Stderr, app.Tr.NoRecentRepositories) - os.Exit(1) - } - - // Run this afterward so that the previous repo creation steps can run without this interfering - if repoPaths.IsBareRepo() { - - fmt.Print(app.Tr.BareRepo) - - response, _ := bufio.NewReader(os.Stdin).ReadString('\n') - - if shouldOpenRecent := strings.Trim(response, " \r\n") == "y"; !shouldOpenRecent { - os.Exit(0) - } - - if didOpenRepo := openRecentRepo(app); didOpenRepo { - return true, nil - } - - fmt.Println(app.Tr.NoRecentRepositories) - os.Exit(1) - } - - return false, nil -} - -func (app *App) Run(startArgs appTypes.StartArgs) error { - err := app.Gui.RunAndHandleError(startArgs) - return err -} - -// Close closes any resources -func (app *App) Close() error { - for _, closer := range app.closers { - if err := closer.Close(); err != nil { - return err - } - } - - return nil -} diff --git a/pkg/app/daemon/daemon.go b/pkg/app/daemon/daemon.go deleted file mode 100644 index 6b29fd62d3b..00000000000 --- a/pkg/app/daemon/daemon.go +++ /dev/null @@ -1,366 +0,0 @@ -package daemon - -import ( - "encoding/json" - "fmt" - "log" - "os" - "os/exec" - "strconv" - - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process. -// We do this when git lets us supply a program to run within a git command. -// For example, if we want to ensure that a git command doesn't hang due to -// waiting for an editor to save a commit message, we can tell git to invoke lazygit -// as the editor via 'GIT_EDITOR=lazygit', and use the env var -// 'LAZYGIT_DAEMON_KIND=1' (exit immediately) to specify that we want to run lazygit -// as a daemon which simply exits immediately. -// -// 'Daemon' is not the best name for this, because it's not a persistent background -// process, but it's close enough. - -type DaemonKind int - -const ( - // for when we fail to parse the daemon kind - DaemonKindUnknown DaemonKind = iota - - DaemonKindExitImmediately - DaemonKindRemoveUpdateRefsForCopiedBranch - DaemonKindMoveTodosUp - DaemonKindMoveTodosDown - DaemonKindInsertBreak - DaemonKindChangeTodoActions - DaemonKindDropMergeCommit - DaemonKindMoveFixupCommitDown - DaemonKindWriteRebaseTodo -) - -const ( - DaemonKindEnvKey string = "LAZYGIT_DAEMON_KIND" - - // Contains json-encoded arguments to the daemon - DaemonInstructionEnvKey string = "LAZYGIT_DAEMON_INSTRUCTION" -) - -func getInstruction() Instruction { - jsonData := os.Getenv(DaemonInstructionEnvKey) - - mapping := map[DaemonKind]func(string) Instruction{ - DaemonKindExitImmediately: deserializeInstruction[*ExitImmediatelyInstruction], - DaemonKindRemoveUpdateRefsForCopiedBranch: deserializeInstruction[*RemoveUpdateRefsForCopiedBranchInstruction], - DaemonKindChangeTodoActions: deserializeInstruction[*ChangeTodoActionsInstruction], - DaemonKindDropMergeCommit: deserializeInstruction[*DropMergeCommitInstruction], - DaemonKindMoveFixupCommitDown: deserializeInstruction[*MoveFixupCommitDownInstruction], - DaemonKindMoveTodosUp: deserializeInstruction[*MoveTodosUpInstruction], - DaemonKindMoveTodosDown: deserializeInstruction[*MoveTodosDownInstruction], - DaemonKindInsertBreak: deserializeInstruction[*InsertBreakInstruction], - DaemonKindWriteRebaseTodo: deserializeInstruction[*WriteRebaseTodoInstruction], - } - - return mapping[getDaemonKind()](jsonData) -} - -func Handle(common *common.Common) { - if !InDaemonMode() { - return - } - - instruction := getInstruction() - - if err := instruction.run(common); err != nil { - log.Fatal(err) - } -} - -func InDaemonMode() bool { - return getDaemonKind() != DaemonKindUnknown -} - -func getDaemonKind() DaemonKind { - intValue, err := strconv.Atoi(os.Getenv(DaemonKindEnvKey)) - if err != nil { - return DaemonKindUnknown - } - - return DaemonKind(intValue) -} - -func getCommentChar() byte { - cmd := exec.Command("git", "config", "--get", "--null", "core.commentChar") - if output, err := cmd.Output(); err == nil && len(output) == 2 { - return output[0] - } - - return '#' -} - -// An Instruction is a command to be run by lazygit in daemon mode. -// It is serialized to json and passed to lazygit via environment variables -type Instruction interface { - Kind() DaemonKind - SerializedInstructions() string - - // runs the instruction - run(common *common.Common) error -} - -func serializeInstruction[T any](instruction T) string { - jsonData, err := json.Marshal(instruction) - if err != nil { - // this should never happen - panic(err) - } - - return string(jsonData) -} - -func deserializeInstruction[T Instruction](jsonData string) Instruction { - var instruction T - err := json.Unmarshal([]byte(jsonData), &instruction) - if err != nil { - panic(err) - } - - return instruction -} - -func ToEnvVars(instruction Instruction) []string { - return []string{ - fmt.Sprintf("%s=%d", DaemonKindEnvKey, instruction.Kind()), - fmt.Sprintf("%s=%s", DaemonInstructionEnvKey, instruction.SerializedInstructions()), - } -} - -type ExitImmediatelyInstruction struct{} - -func (self *ExitImmediatelyInstruction) Kind() DaemonKind { - return DaemonKindExitImmediately -} - -func (self *ExitImmediatelyInstruction) SerializedInstructions() string { - return serializeInstruction(self) -} - -func (self *ExitImmediatelyInstruction) run(common *common.Common) error { - return nil -} - -func NewExitImmediatelyInstruction() Instruction { - return &ExitImmediatelyInstruction{} -} - -type RemoveUpdateRefsForCopiedBranchInstruction struct{} - -func (self *RemoveUpdateRefsForCopiedBranchInstruction) Kind() DaemonKind { - return DaemonKindRemoveUpdateRefsForCopiedBranch -} - -func (self *RemoveUpdateRefsForCopiedBranchInstruction) SerializedInstructions() string { - return serializeInstruction(self) -} - -func (self *RemoveUpdateRefsForCopiedBranchInstruction) run(common *common.Common) error { - return handleInteractiveRebase(common, func(path string) error { - return nil - }) -} - -func NewRemoveUpdateRefsForCopiedBranchInstruction() Instruction { - return &RemoveUpdateRefsForCopiedBranchInstruction{} -} - -type ChangeTodoActionsInstruction struct { - Changes []ChangeTodoAction -} - -func NewChangeTodoActionsInstruction(changes []ChangeTodoAction) Instruction { - return &ChangeTodoActionsInstruction{ - Changes: changes, - } -} - -func (self *ChangeTodoActionsInstruction) Kind() DaemonKind { - return DaemonKindChangeTodoActions -} - -func (self *ChangeTodoActionsInstruction) SerializedInstructions() string { - return serializeInstruction(self) -} - -func (self *ChangeTodoActionsInstruction) run(common *common.Common) error { - return handleInteractiveRebase(common, func(path string) error { - changes := lo.Map(self.Changes, func(c ChangeTodoAction, _ int) utils.TodoChange { - return utils.TodoChange{ - Hash: c.Hash, - NewAction: c.NewAction, - } - }) - - return utils.EditRebaseTodo(path, changes, getCommentChar()) - }) -} - -type DropMergeCommitInstruction struct { - Hash string -} - -func NewDropMergeCommitInstruction(hash string) Instruction { - return &DropMergeCommitInstruction{ - Hash: hash, - } -} - -func (self *DropMergeCommitInstruction) Kind() DaemonKind { - return DaemonKindDropMergeCommit -} - -func (self *DropMergeCommitInstruction) SerializedInstructions() string { - return serializeInstruction(self) -} - -func (self *DropMergeCommitInstruction) run(common *common.Common) error { - return handleInteractiveRebase(common, func(path string) error { - return utils.DropMergeCommit(path, self.Hash, getCommentChar()) - }) -} - -// Takes the hash of some commit, and the hash of a fixup commit that was created -// at the end of the branch, then moves the fixup commit down to right after the -// original commit, changing its type to "fixup" (only if ChangeToFixup is true) -type MoveFixupCommitDownInstruction struct { - OriginalHash string - FixupHash string - ChangeToFixup bool -} - -func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string, changeToFixup bool) Instruction { - return &MoveFixupCommitDownInstruction{ - OriginalHash: originalHash, - FixupHash: fixupHash, - ChangeToFixup: changeToFixup, - } -} - -func (self *MoveFixupCommitDownInstruction) Kind() DaemonKind { - return DaemonKindMoveFixupCommitDown -} - -func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string { - return serializeInstruction(self) -} - -func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error { - return handleInteractiveRebase(common, func(path string) error { - return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, self.ChangeToFixup, getCommentChar()) - }) -} - -type MoveTodosUpInstruction struct { - Hashes []string -} - -func NewMoveTodosUpInstruction(hashes []string) Instruction { - return &MoveTodosUpInstruction{ - Hashes: hashes, - } -} - -func (self *MoveTodosUpInstruction) Kind() DaemonKind { - return DaemonKindMoveTodosUp -} - -func (self *MoveTodosUpInstruction) SerializedInstructions() string { - return serializeInstruction(self) -} - -func (self *MoveTodosUpInstruction) run(common *common.Common) error { - todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo { - return utils.Todo{ - Hash: hash, - } - }) - - return handleInteractiveRebase(common, func(path string) error { - return utils.MoveTodosUp(path, todosToMove, false, getCommentChar()) - }) -} - -type MoveTodosDownInstruction struct { - Hashes []string -} - -func NewMoveTodosDownInstruction(hashes []string) Instruction { - return &MoveTodosDownInstruction{ - Hashes: hashes, - } -} - -func (self *MoveTodosDownInstruction) Kind() DaemonKind { - return DaemonKindMoveTodosDown -} - -func (self *MoveTodosDownInstruction) SerializedInstructions() string { - return serializeInstruction(self) -} - -func (self *MoveTodosDownInstruction) run(common *common.Common) error { - todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo { - return utils.Todo{ - Hash: hash, - } - }) - - return handleInteractiveRebase(common, func(path string) error { - return utils.MoveTodosDown(path, todosToMove, false, getCommentChar()) - }) -} - -type InsertBreakInstruction struct{} - -func NewInsertBreakInstruction() Instruction { - return &InsertBreakInstruction{} -} - -func (self *InsertBreakInstruction) Kind() DaemonKind { - return DaemonKindInsertBreak -} - -func (self *InsertBreakInstruction) SerializedInstructions() string { - return serializeInstruction(self) -} - -func (self *InsertBreakInstruction) run(common *common.Common) error { - return handleInteractiveRebase(common, func(path string) error { - return utils.PrependStrToTodoFile(path, []byte("break\n")) - }) -} - -type WriteRebaseTodoInstruction struct { - TodosFileContent []byte -} - -func NewWriteRebaseTodoInstruction(todosFileContent []byte) Instruction { - return &WriteRebaseTodoInstruction{ - TodosFileContent: todosFileContent, - } -} - -func (self *WriteRebaseTodoInstruction) Kind() DaemonKind { - return DaemonKindWriteRebaseTodo -} - -func (self *WriteRebaseTodoInstruction) SerializedInstructions() string { - return serializeInstruction(self) -} - -func (self *WriteRebaseTodoInstruction) run(common *common.Common) error { - return handleInteractiveRebase(common, func(path string) error { - return os.WriteFile(path, self.TodosFileContent, 0o644) - }) -} diff --git a/pkg/app/daemon/rebase.go b/pkg/app/daemon/rebase.go deleted file mode 100644 index e7cf1f57b1d..00000000000 --- a/pkg/app/daemon/rebase.go +++ /dev/null @@ -1,46 +0,0 @@ -package daemon - -import ( - "os" - "path/filepath" - "strings" - - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/env" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/stefanhaller/git-todo-parser/todo" -) - -type ChangeTodoAction struct { - Hash string - NewAction todo.TodoCommand -} - -func handleInteractiveRebase(common *common.Common, f func(path string) error) error { - common.Log.Info("Lazygit invoked as interactive rebase demon") - common.Log.Info("args: ", os.Args) - path := os.Args[1] - - if strings.HasSuffix(path, "git-rebase-todo") { - err := utils.RemoveUpdateRefsForCopiedBranch(path, getCommentChar()) - if err != nil { - return err - } - return f(path) - } else if strings.HasSuffix(path, filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test - // if we are rebasing and squashing, we'll see a COMMIT_EDITMSG - // but in this case we don't need to edit it, so we'll just return - } else { - common.Log.Info("Lazygit demon did not match on any use cases") - } - - return nil -} - -func gitDir() string { - dir := env.GetGitDirEnv() - if dir == "" { - return ".git" - } - return dir -} diff --git a/pkg/app/entry_point.go b/pkg/app/entry_point.go deleted file mode 100644 index 3a692ac53ea..00000000000 --- a/pkg/app/entry_point.go +++ /dev/null @@ -1,334 +0,0 @@ -package app - -import ( - "bytes" - "fmt" - "log" - "net/http" - _ "net/http/pprof" - "os" - "os/exec" - "os/user" - "path/filepath" - "runtime" - "runtime/debug" - "strings" - - "github.com/integrii/flaggy" - "github.com/jesseduffield/lazygit/pkg/app/daemon" - appTypes "github.com/jesseduffield/lazygit/pkg/app/types" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/env" - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" - "github.com/jesseduffield/lazygit/pkg/logs/tail" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "gopkg.in/yaml.v3" -) - -type cliArgs struct { - RepoPath string - FilterPath string - GitArg string - UseConfigDir string - WorkTree string - GitDir string - CustomConfigFile string - ScreenMode string - PrintVersionInfo bool - Debug bool - TailLogs bool - Profile bool - PrintDefaultConfig bool - PrintConfigDir bool -} - -type BuildInfo struct { - Commit string - Date string - Version string - BuildSource string -} - -func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTest) { - cliArgs := parseCliArgsAndEnvVars() - mergeBuildInfo(buildInfo) - - if cliArgs.RepoPath != "" { - if cliArgs.WorkTree != "" || cliArgs.GitDir != "" { - log.Fatal("--path option is incompatible with the --work-tree and --git-dir options") - } - - absRepoPath, err := filepath.Abs(cliArgs.RepoPath) - if err != nil { - log.Fatal(err) - } - - if isRepo, err := isDirectoryAGitRepository(absRepoPath); err != nil || !isRepo { - log.Fatal(absRepoPath + " is not a valid git repository.") - } - - cliArgs.GitDir = filepath.Join(absRepoPath, ".git") - err = os.Chdir(absRepoPath) - if err != nil { - log.Fatalf("Failed to change directory to %s: %v", absRepoPath, err) - } - } else if cliArgs.WorkTree != "" { - env.SetWorkTreeEnv(cliArgs.WorkTree) - - if err := os.Chdir(cliArgs.WorkTree); err != nil { - log.Fatalf("Failed to change directory to %s: %v", cliArgs.WorkTree, err) - } - } - - if cliArgs.CustomConfigFile != "" { - os.Setenv("LG_CONFIG_FILE", cliArgs.CustomConfigFile) - } - - if cliArgs.UseConfigDir != "" { - os.Setenv("CONFIG_DIR", cliArgs.UseConfigDir) - } - - if cliArgs.GitDir != "" { - env.SetGitDirEnv(cliArgs.GitDir) - } - - if cliArgs.PrintVersionInfo { - gitVersion := getGitVersionInfo() - fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s, git version=%s\n", buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, buildInfo.Version, runtime.GOOS, runtime.GOARCH, gitVersion) - os.Exit(0) - } - - if cliArgs.PrintDefaultConfig { - var buf bytes.Buffer - encoder := yaml.NewEncoder(&buf) - err := encoder.Encode(config.GetDefaultConfig()) - if err != nil { - log.Fatal(err.Error()) - } - fmt.Printf("%s\n", buf.String()) - os.Exit(0) - } - - if cliArgs.PrintConfigDir { - fmt.Printf("%s\n", config.ConfigDir()) - os.Exit(0) - } - - if cliArgs.TailLogs { - logPath, err := config.LogPath() - if err != nil { - log.Fatal(err.Error()) - } - - tail.TailLogs(logPath) - os.Exit(0) - } - - tempDirBase := getTempDirBase() - tempDir, err := os.MkdirTemp(tempDirBase, "lazygit-*") - if err != nil { - if os.IsPermission(err) { - log.Fatalf("Your temp directory (%s) is not writeable. Try if rebooting your machine fixes this.", tempDirBase) - } - - log.Fatal(err.Error()) - } - defer os.RemoveAll(tempDir) - - appConfig, err := config.NewAppConfig("lazygit", buildInfo.Version, buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, cliArgs.Debug, tempDir) - if err != nil { - log.Fatal(err.Error()) - } - - if integrationTest != nil { - integrationTest.SetupConfig(appConfig) - // Set this to true so that integration tests don't have to explicitly deal with the hunk - // staging hint: - appConfig.GetAppState().DidShowHunkStagingHint = true - - // Preserve the changes that the test setup just made to the config, so - // they don't get lost when we reload the config while running the test - // (which happens when switching between repos, going in and out of - // submodules, etc). - appConfig.SaveGlobalUserConfig() - } - - common, err := NewCommon(appConfig) - if err != nil { - log.Fatal(err) - } - - if daemon.InDaemonMode() { - daemon.Handle(common) - return - } - - if cliArgs.Profile { - go func() { - if err := http.ListenAndServe("localhost:6060", nil); err != nil { - log.Fatal(err) - } - }() - } - - parsedGitArg := parseGitArg(cliArgs.GitArg) - - Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, cliArgs.ScreenMode, integrationTest)) -} - -func parseCliArgsAndEnvVars() *cliArgs { - flaggy.DefaultParser.ShowVersionWithVersionFlag = false - - repoPath := "" - flaggy.String(&repoPath, "p", "path", "Path of git repo. (equivalent to --work-tree= --git-dir=/.git/)") - - filterPath := "" - flaggy.String(&filterPath, "f", "filter", "Path to filter on in `git log -- `. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted") - - gitArg := "" - flaggy.AddPositionalValue(&gitArg, "git-arg", 1, false, "Panel to focus upon opening lazygit. Accepted values (based on git terminology): status, branch, log, stash. Ignored if --filter arg is passed.") - - printVersionInfo := false - flaggy.Bool(&printVersionInfo, "v", "version", "Print the current version") - - debug := false - flaggy.Bool(&debug, "d", "debug", "Run in debug mode with logging (see --logs flag below). Use the LOG_LEVEL env var to set the log level (debug/info/warn/error)") - - tailLogs := false - flaggy.Bool(&tailLogs, "l", "logs", "Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)") - - profile := false - flaggy.Bool(&profile, "", "profile", "Start the profiler and serve it on http port 6060. See CONTRIBUTING.md for more info.") - - printDefaultConfig := false - flaggy.Bool(&printDefaultConfig, "c", "config", "Print the default config") - - printConfigDir := false - flaggy.Bool(&printConfigDir, "cd", "print-config-dir", "Print the config directory") - - useConfigDir := "" - flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory") - - workTree := os.Getenv("GIT_WORK_TREE") - flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument") - - gitDir := os.Getenv("GIT_DIR") - flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument") - - customConfigFile := "" - flaggy.String(&customConfigFile, "ucf", "use-config-file", "Comma separated list to custom config file(s)") - - screenMode := "" - flaggy.String(&screenMode, "sm", "screen-mode", "The initial screen-mode, which determines the size of the focused panel. Valid options: 'normal' (default), 'half', 'full'") - - flaggy.Parse() - - if os.Getenv("DEBUG") == "TRUE" { - debug = true - } - - return &cliArgs{ - RepoPath: repoPath, - FilterPath: filterPath, - GitArg: gitArg, - PrintVersionInfo: printVersionInfo, - Debug: debug, - TailLogs: tailLogs, - Profile: profile, - PrintDefaultConfig: printDefaultConfig, - PrintConfigDir: printConfigDir, - UseConfigDir: useConfigDir, - WorkTree: workTree, - GitDir: gitDir, - CustomConfigFile: customConfigFile, - ScreenMode: screenMode, - } -} - -func parseGitArg(gitArg string) appTypes.GitArg { - typedArg := appTypes.GitArg(gitArg) - - // using switch so that linter catches when a new git arg value is defined but not handled here - switch typedArg { - case appTypes.GitArgNone, appTypes.GitArgStatus, appTypes.GitArgBranch, appTypes.GitArgLog, appTypes.GitArgStash: - return typedArg - } - - permittedValues := []string{ - string(appTypes.GitArgStatus), - string(appTypes.GitArgBranch), - string(appTypes.GitArgLog), - string(appTypes.GitArgStash), - } - - log.Fatalf("Invalid git arg value: '%s'. Must be one of the following values: %s. e.g. 'lazygit status'. See 'lazygit --help'.", - gitArg, - strings.Join(permittedValues, ", "), - ) - - panic("unreachable") -} - -// the buildInfo struct we get passed in is based on what's baked into the lazygit -// binary via the LDFLAGS argument. Some lazygit distributions will make use of these -// arguments and some will not. Go recently started baking in build info -// into the binary by default e.g. the git commit hash. So in this function -// we merge the two together, giving priority to the stuff set by LDFLAGS. -// Note: this mutates the argument passed in -func mergeBuildInfo(buildInfo *BuildInfo) { - // if the version has already been set by build flags then we'll honour that. - // chances are it's something like v0.31.0 which is more informative than a - // commit hash. - if buildInfo.Version != "" { - return - } - - buildInfo.Version = "unversioned" - - goBuildInfo, ok := debug.ReadBuildInfo() - if !ok { - return - } - - revision, ok := lo.Find(goBuildInfo.Settings, func(setting debug.BuildSetting) bool { - return setting.Key == "vcs.revision" - }) - if ok { - buildInfo.Commit = revision.Value - // if lazygit was built from source we'll show the version as the - // abbreviated commit hash - buildInfo.Version = utils.ShortHash(revision.Value) - } - - // if version hasn't been set we assume that neither has the date - time, ok := lo.Find(goBuildInfo.Settings, func(setting debug.BuildSetting) bool { - return setting.Key == "vcs.time" - }) - if ok { - buildInfo.Date = time.Value - } -} - -func getGitVersionInfo() string { - cmd := exec.Command("git", "--version") - stdout, _ := cmd.Output() - gitVersion := strings.Trim(strings.TrimPrefix(string(stdout), "git version "), " \r\n") - return gitVersion -} - -func getTempDirBase() string { - tempDir := os.TempDir() - - user, err := user.Current() - if err != nil || user.Uid == "" { - return tempDir - } - - tmpDirBase := filepath.Join(tempDir, "lazygit-"+user.Uid) - if err := os.MkdirAll(tmpDirBase, 0o700); err != nil { - return tempDir - } - - return tmpDirBase -} diff --git a/pkg/app/errors.go b/pkg/app/errors.go deleted file mode 100644 index 506fec276e2..00000000000 --- a/pkg/app/errors.go +++ /dev/null @@ -1,43 +0,0 @@ -package app - -import ( - "strings" - - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/samber/lo" -) - -type errorMapping struct { - originalError string - newError string -} - -// knownError takes an error and tells us whether it's an error that we know about where we can print a nicely formatted version of it rather than panicking with a stack trace -func knownError(tr *i18n.TranslationSet, err error) (string, bool) { - errorMessage := err.Error() - - knownErrorMessages := []string{minGitVersionErrorMessage(tr)} - - if lo.Contains(knownErrorMessages, errorMessage) { - return errorMessage, true - } - - mappings := []errorMapping{ - { - originalError: "fatal: not a git repository", - newError: tr.NotARepository, - }, - { - originalError: "getwd: no such file or directory", - newError: tr.WorkingDirectoryDoesNotExist, - }, - } - - if mapping, ok := lo.Find(mappings, func(mapping errorMapping) bool { - return strings.Contains(errorMessage, mapping.originalError) - }); ok { - return mapping.newError, true - } - - return "", false -} diff --git a/pkg/app/types/types.go b/pkg/app/types/types.go deleted file mode 100644 index 660bd29196e..00000000000 --- a/pkg/app/types/types.go +++ /dev/null @@ -1,36 +0,0 @@ -package app - -import ( - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" -) - -// StartArgs is the struct that represents some things we want to do on program start -type StartArgs struct { - // GitArg determines what context we open in - GitArg GitArg - // integration test (only relevant when invoking lazygit in the context of an integration test) - IntegrationTest integrationTypes.IntegrationTest - // FilterPath determines which path we're going to filter on so that we only see commits from that file. - FilterPath string - // ScreenMode determines the initial Screen Mode (normal, half or full) to use - ScreenMode string -} - -type GitArg string - -const ( - GitArgNone GitArg = "" - GitArgStatus GitArg = "status" - GitArgBranch GitArg = "branch" - GitArgLog GitArg = "log" - GitArgStash GitArg = "stash" -) - -func NewStartArgs(filterPath string, gitArg GitArg, screenMode string, test integrationTypes.IntegrationTest) StartArgs { - return StartArgs{ - FilterPath: filterPath, - GitArg: gitArg, - ScreenMode: screenMode, - IntegrationTest: test, - } -} diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go deleted file mode 100644 index 3a52990625c..00000000000 --- a/pkg/cheatsheet/generate.go +++ /dev/null @@ -1,235 +0,0 @@ -//go:generate go run generator.go - -// This "script" generates files called Keybindings_{{.LANG}}.md -// in the docs/keybindings directory. -// -// The content of these generated files is a keybindings cheatsheet. -// -// To generate the cheatsheets, run: -// go generate pkg/cheatsheet/generate.go - -package cheatsheet - -import ( - "cmp" - "fmt" - "log" - "os" - "slices" - "strings" - - "github.com/jesseduffield/generics/maps" - "github.com/jesseduffield/lazycore/pkg/utils" - "github.com/jesseduffield/lazygit/pkg/app" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/samber/lo" -) - -type bindingSection struct { - title string - bindings []*types.Binding -} - -type header struct { - // priority decides the order of the headers in the cheatsheet (lower means higher) - priority int - title string -} - -type headerWithBindings struct { - header header - bindings []*types.Binding -} - -func CommandToRun() string { - return "go generate ./..." -} - -func GetKeybindingsDir() string { - return utils.GetLazyRootDirectory() + "/docs/keybindings" -} - -func generateAtDir(cheatsheetDir string) { - translationSetsByLang, err := i18n.GetTranslationSets() - if err != nil { - log.Fatal(err) - } - mConfig := config.NewDummyAppConfig() - - for lang := range translationSetsByLang { - mConfig.GetUserConfig().Gui.Language = lang - common, err := app.NewCommon(mConfig) - if err != nil { - log.Fatal(err) - } - tr, err := i18n.NewTranslationSetFromConfig(common.Log, lang) - if err != nil { - log.Fatal(err) - } - common.Tr = tr - mApp, _ := app.NewApp(mConfig, nil, common) - path := cheatsheetDir + "/Keybindings_" + lang + ".md" - file, err := os.Create(path) - if err != nil { - panic(err) - } - - bindings := mApp.Gui.GetCheatsheetKeybindings() - bindingSections := getBindingSections(bindings, mApp.Tr) - content := formatSections(mApp.Tr, bindingSections) - content = fmt.Sprintf("_This file is auto-generated. To update, make the changes in the "+ - "pkg/i18n directory and then run `%s` from the project root._\n\n%s", CommandToRun(), content) - writeString(file, content) - } -} - -func Generate() { - generateAtDir(GetKeybindingsDir()) -} - -func writeString(file *os.File, str string) { - _, err := file.WriteString(str) - if err != nil { - log.Fatal(err) - } -} - -func localisedTitle(tr *i18n.TranslationSet, str string) string { - contextTitleMap := map[string]string{ - "global": tr.GlobalTitle, - "navigation": tr.NavigationTitle, - "branches": tr.BranchesTitle, - "localBranches": tr.LocalBranchesTitle, - "files": tr.FilesTitle, - "status": tr.StatusTitle, - "submodules": tr.SubmodulesTitle, - "subCommits": tr.SubCommitsTitle, - "remoteBranches": tr.RemoteBranchesTitle, - "remotes": tr.RemotesTitle, - "reflogCommits": tr.ReflogCommitsTitle, - "tags": tr.TagsTitle, - "commitFiles": tr.CommitFilesTitle, - "commitMessage": tr.CommitSummaryTitle, - "commitDescription": tr.CommitDescriptionTitle, - "commits": tr.CommitsTitle, - "confirmation": tr.ConfirmationTitle, - "prompt": tr.PromptTitle, - "information": tr.InformationTitle, - "main": tr.NormalTitle, - "patchBuilding": tr.PatchBuildingTitle, - "mergeConflicts": tr.MergingTitle, - "staging": tr.StagingTitle, - "menu": tr.MenuTitle, - "search": tr.SearchTitle, - "secondary": tr.SecondaryTitle, - "stash": tr.StashTitle, - "suggestions": tr.SuggestionsCheatsheetTitle, - "extras": tr.ExtrasTitle, - "worktrees": tr.WorktreesTitle, - } - - title, ok := contextTitleMap[str] - if !ok { - panic(fmt.Sprintf("title not found for %s", str)) - } - - return title -} - -func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*bindingSection { - excludedViews := []string{"stagingSecondary", "patchBuildingSecondary"} - bindingsToDisplay := lo.Filter(bindings, func(binding *types.Binding, _ int) bool { - if lo.Contains(excludedViews, binding.ViewName) { - return false - } - - return (binding.Description != "" || binding.Alternative != "") && binding.Key != nil - }) - - bindingsByHeader := lo.GroupBy(bindingsToDisplay, func(binding *types.Binding) header { - return getHeader(binding, tr) - }) - - bindingGroups := maps.MapToSlice( - bindingsByHeader, - func(header header, hBindings []*types.Binding) headerWithBindings { - uniqBindings := lo.UniqBy(hBindings, func(binding *types.Binding) string { - return binding.Description + keybindings.LabelFromKey(binding.Key) - }) - - return headerWithBindings{ - header: header, - bindings: uniqBindings, - } - }, - ) - - slices.SortFunc(bindingGroups, func(a, b headerWithBindings) int { - if a.header.priority != b.header.priority { - return cmp.Compare(b.header.priority, a.header.priority) - } - return strings.Compare(a.header.title, b.header.title) - }) - - return lo.Map(bindingGroups, func(hb headerWithBindings, _ int) *bindingSection { - return &bindingSection{ - title: hb.header.title, - bindings: hb.bindings, - } - }) -} - -func getHeader(binding *types.Binding, tr *i18n.TranslationSet) header { - if binding.Tag == "navigation" { - return header{priority: 2, title: localisedTitle(tr, "navigation")} - } - - if binding.ViewName == "" { - return header{priority: 3, title: localisedTitle(tr, "global")} - } - - return header{priority: 1, title: localisedTitle(tr, binding.ViewName)} -} - -func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string { - content := fmt.Sprintf("# Lazygit %s\n", tr.Keybindings) - - content += fmt.Sprintf("\n%s\n", italicize(tr.KeybindingsLegend)) - - for _, section := range bindingSections { - content += formatTitle(section.title) - content += "| Key | Action | Info |\n" - content += "|-----|--------|-------------|\n" - for _, binding := range section.bindings { - content += formatBinding(binding) - } - } - - return content -} - -func formatTitle(title string) string { - return fmt.Sprintf("\n## %s\n\n", title) -} - -func formatBinding(binding *types.Binding) string { - action := keybindings.LabelFromKey(binding.Key) - description := binding.Description - if binding.Alternative != "" { - action += fmt.Sprintf(" (%s)", binding.Alternative) - } - - // Replace newlines with
tags for proper markdown table formatting - tooltip := strings.ReplaceAll(binding.Tooltip, "\n", "
") - - // Use backticks for keyboard keys. Two backticks are needed with an inner space - // to escape a key that is itself a backtick. - return fmt.Sprintf("| `` %s `` | %s | %s |\n", action, description, tooltip) -} - -func italicize(str string) string { - return fmt.Sprintf("_%s_", str) -} diff --git a/pkg/cheatsheet/generate_test.go b/pkg/cheatsheet/generate_test.go deleted file mode 100644 index 4dbf7e3dd72..00000000000 --- a/pkg/cheatsheet/generate_test.go +++ /dev/null @@ -1,269 +0,0 @@ -package cheatsheet - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/stretchr/testify/assert" -) - -func TestGetBindingSections(t *testing.T) { - tr := i18n.EnglishTranslationSet() - - tests := []struct { - testName string - bindings []*types.Binding - expected []*bindingSection - }{ - { - testName: "no bindings", - bindings: []*types.Binding{}, - expected: []*bindingSection{}, - }, - { - testName: "one binding", - bindings: []*types.Binding{ - { - ViewName: "files", - Description: "stage file", - Key: 'a', - }, - }, - expected: []*bindingSection{ - { - title: "Files", - bindings: []*types.Binding{ - { - ViewName: "files", - Description: "stage file", - Key: 'a', - }, - }, - }, - }, - }, - { - testName: "global binding", - bindings: []*types.Binding{ - { - ViewName: "", - Description: "quit", - Key: 'a', - }, - }, - expected: []*bindingSection{ - { - title: "Global keybindings", - bindings: []*types.Binding{ - { - ViewName: "", - Description: "quit", - Key: 'a', - }, - }, - }, - }, - }, - { - testName: "grouped bindings", - bindings: []*types.Binding{ - { - ViewName: "files", - Description: "stage file", - Key: 'a', - }, - { - ViewName: "files", - Description: "unstage file", - Key: 'a', - }, - { - ViewName: "submodules", - Description: "drop submodule", - Key: 'a', - }, - }, - expected: []*bindingSection{ - { - title: "Files", - bindings: []*types.Binding{ - { - ViewName: "files", - Description: "stage file", - Key: 'a', - }, - { - ViewName: "files", - Description: "unstage file", - Key: 'a', - }, - }, - }, - { - title: "Submodules", - bindings: []*types.Binding{ - { - ViewName: "submodules", - Description: "drop submodule", - Key: 'a', - }, - }, - }, - }, - }, - { - testName: "with navigation bindings", - bindings: []*types.Binding{ - { - ViewName: "files", - Description: "stage file", - Key: 'a', - }, - { - ViewName: "files", - Description: "unstage file", - Key: 'a', - }, - { - ViewName: "files", - Description: "scroll", - Key: 'a', - Tag: "navigation", - }, - { - ViewName: "commits", - Description: "revert commit", - Key: 'a', - }, - }, - expected: []*bindingSection{ - { - title: "List panel navigation", - bindings: []*types.Binding{ - { - ViewName: "files", - Description: "scroll", - Key: 'a', - Tag: "navigation", - }, - }, - }, - { - title: "Commits", - bindings: []*types.Binding{ - { - ViewName: "commits", - Description: "revert commit", - Key: 'a', - }, - }, - }, - { - title: "Files", - bindings: []*types.Binding{ - { - ViewName: "files", - Description: "stage file", - Key: 'a', - }, - { - ViewName: "files", - Description: "unstage file", - Key: 'a', - }, - }, - }, - }, - }, - { - testName: "with duplicate navigation bindings", - bindings: []*types.Binding{ - { - ViewName: "files", - Description: "stage file", - Key: 'a', - }, - { - ViewName: "files", - Description: "unstage file", - Key: 'a', - }, - { - ViewName: "files", - Description: "scroll", - Key: 'a', - Tag: "navigation", - }, - { - ViewName: "commits", - Description: "revert commit", - Key: 'a', - }, - { - ViewName: "commits", - Description: "scroll", - Key: 'a', - Tag: "navigation", - }, - { - ViewName: "commits", - Description: "page up", - Key: 'a', - Tag: "navigation", - }, - }, - expected: []*bindingSection{ - { - title: "List panel navigation", - bindings: []*types.Binding{ - { - ViewName: "files", - Description: "scroll", - Key: 'a', - Tag: "navigation", - }, - { - ViewName: "commits", - Description: "page up", - Key: 'a', - Tag: "navigation", - }, - }, - }, - { - title: "Commits", - bindings: []*types.Binding{ - { - ViewName: "commits", - Description: "revert commit", - Key: 'a', - }, - }, - }, - { - title: "Files", - bindings: []*types.Binding{ - { - ViewName: "files", - Description: "stage file", - Key: 'a', - }, - { - ViewName: "files", - Description: "unstage file", - Key: 'a', - }, - }, - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.testName, func(t *testing.T) { - actual := getBindingSections(test.bindings, tr) - assert.EqualValues(t, test.expected, actual) - }) - } -} diff --git a/pkg/cheatsheet/generator.go b/pkg/cheatsheet/generator.go deleted file mode 100644 index 1708a28e8b0..00000000000 --- a/pkg/cheatsheet/generator.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build ignore - -package main - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/cheatsheet" -) - -func main() { - fmt.Printf("Generating cheatsheets in %s...\n", cheatsheet.GetKeybindingsDir()) - cheatsheet.Generate() -} diff --git a/pkg/commands/git.go b/pkg/commands/git.go deleted file mode 100644 index 90514cbd62a..00000000000 --- a/pkg/commands/git.go +++ /dev/null @@ -1,188 +0,0 @@ -package commands - -import ( - "os" - "strings" - - "github.com/go-errors/errors" - - gogit "github.com/jesseduffield/go-git/v5" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/git_config" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// GitCommand is our main git interface -type GitCommand struct { - Blame *git_commands.BlameCommands - Branch *git_commands.BranchCommands - Commit *git_commands.CommitCommands - Config *git_commands.ConfigCommands - Custom *git_commands.CustomCommands - Diff *git_commands.DiffCommands - File *git_commands.FileCommands - Flow *git_commands.FlowCommands - Patch *git_commands.PatchCommands - Rebase *git_commands.RebaseCommands - Remote *git_commands.RemoteCommands - Stash *git_commands.StashCommands - Status *git_commands.StatusCommands - Submodule *git_commands.SubmoduleCommands - Sync *git_commands.SyncCommands - Tag *git_commands.TagCommands - WorkingTree *git_commands.WorkingTreeCommands - Bisect *git_commands.BisectCommands - Worktree *git_commands.WorktreeCommands - Version *git_commands.GitVersion - RepoPaths *git_commands.RepoPaths - - Loaders Loaders -} - -type Loaders struct { - BranchLoader *git_commands.BranchLoader - CommitFileLoader *git_commands.CommitFileLoader - CommitLoader *git_commands.CommitLoader - FileLoader *git_commands.FileLoader - ReflogCommitLoader *git_commands.ReflogCommitLoader - RemoteLoader *git_commands.RemoteLoader - StashLoader *git_commands.StashLoader - TagLoader *git_commands.TagLoader - Worktrees *git_commands.WorktreeLoader -} - -func NewGitCommand( - cmn *common.Common, - version *git_commands.GitVersion, - osCommand *oscommands.OSCommand, - gitConfig git_config.IGitConfig, - pagerConfig *config.PagerConfig, -) (*GitCommand, error) { - repoPaths, err := git_commands.GetRepoPaths(osCommand.Cmd, version) - if err != nil { - return nil, errors.Errorf("Error getting repo paths: %v", err) - } - - err = os.Chdir(repoPaths.WorktreePath()) - if err != nil { - return nil, utils.WrapError(err) - } - - repository, err := gogit.PlainOpenWithOptions( - repoPaths.WorktreeGitDirPath(), - &gogit.PlainOpenOptions{DetectDotGit: false, EnableDotGitCommonDir: true}, - ) - if err != nil { - if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) { - return nil, errors.New(cmn.Tr.GitconfigParseErr) - } - return nil, err - } - - return NewGitCommandAux( - cmn, - version, - osCommand, - gitConfig, - repoPaths, - repository, - pagerConfig, - ), nil -} - -func NewGitCommandAux( - cmn *common.Common, - version *git_commands.GitVersion, - osCommand *oscommands.OSCommand, - gitConfig git_config.IGitConfig, - repoPaths *git_commands.RepoPaths, - repo *gogit.Repository, - pagerConfig *config.PagerConfig, -) *GitCommand { - cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd) - - // here we're doing a bunch of dependency injection for each of our commands structs. - // This is admittedly messy, but allows us to test each command struct in isolation, - // and allows for better namespacing when compared to having every method living - // on the one struct. - // common ones are: cmn, osCommand, dotGitDir, configCommands - configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo) - - gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands, pagerConfig) - - fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands) - statusCommands := git_commands.NewStatusCommands(gitCommon) - flowCommands := git_commands.NewFlowCommands(gitCommon) - remoteCommands := git_commands.NewRemoteCommands(gitCommon) - branchCommands := git_commands.NewBranchCommands(gitCommon) - syncCommands := git_commands.NewSyncCommands(gitCommon) - tagCommands := git_commands.NewTagCommands(gitCommon) - commitCommands := git_commands.NewCommitCommands(gitCommon) - customCommands := git_commands.NewCustomCommands(gitCommon) - diffCommands := git_commands.NewDiffCommands(gitCommon) - fileCommands := git_commands.NewFileCommands(gitCommon) - submoduleCommands := git_commands.NewSubmoduleCommands(gitCommon) - workingTreeCommands := git_commands.NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader) - rebaseCommands := git_commands.NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands) - stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands) - patchBuilder := patch.NewPatchBuilder(cmn.Log, - func(from string, to string, reverse bool, filename string, plain bool) (string, error) { - return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, plain) - }) - patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder) - bisectCommands := git_commands.NewBisectCommands(gitCommon) - worktreeCommands := git_commands.NewWorktreeCommands(gitCommon) - blameCommands := git_commands.NewBlameCommands(gitCommon) - - branchLoader := git_commands.NewBranchLoader(cmn, gitCommon, cmd, branchCommands.CurrentBranchInfo, configCommands) - commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd) - commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.WorkingTreeState, gitCommon) - reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd) - remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes) - worktreeLoader := git_commands.NewWorktreeLoader(gitCommon) - stashLoader := git_commands.NewStashLoader(cmn, cmd) - tagLoader := git_commands.NewTagLoader(cmn, cmd) - - return &GitCommand{ - Blame: blameCommands, - Branch: branchCommands, - Commit: commitCommands, - Config: configCommands, - Custom: customCommands, - Diff: diffCommands, - File: fileCommands, - Flow: flowCommands, - Patch: patchCommands, - Rebase: rebaseCommands, - Remote: remoteCommands, - Stash: stashCommands, - Status: statusCommands, - Submodule: submoduleCommands, - Sync: syncCommands, - Tag: tagCommands, - Bisect: bisectCommands, - WorkingTree: workingTreeCommands, - Worktree: worktreeCommands, - Version: version, - Loaders: Loaders{ - BranchLoader: branchLoader, - CommitFileLoader: commitFileLoader, - CommitLoader: commitLoader, - FileLoader: fileLoader, - ReflogCommitLoader: reflogCommitLoader, - RemoteLoader: remoteLoader, - Worktrees: worktreeLoader, - StashLoader: stashLoader, - TagLoader: tagLoader, - }, - RepoPaths: repoPaths, - } -} - -func VerifyInGitRepo(osCommand *oscommands.OSCommand) error { - return osCommand.Cmd.New(git_commands.NewGitCmd("rev-parse").Arg("--git-dir").ToArgv()).DontLog().Run() -} diff --git a/pkg/commands/git_cmd_obj_builder.go b/pkg/commands/git_cmd_obj_builder.go deleted file mode 100644 index 753489ef4ec..00000000000 --- a/pkg/commands/git_cmd_obj_builder.go +++ /dev/null @@ -1,43 +0,0 @@ -package commands - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/sirupsen/logrus" -) - -// all we're doing here is wrapping the default command object builder with -// some git-specific stuff: e.g. adding a git-specific env var - -type gitCmdObjBuilder struct { - innerBuilder *oscommands.CmdObjBuilder -} - -var _ oscommands.ICmdObjBuilder = &gitCmdObjBuilder{} - -func NewGitCmdObjBuilder(log *logrus.Entry, innerBuilder *oscommands.CmdObjBuilder) *gitCmdObjBuilder { - // the price of having a convenient interface where we can say .New(...).Run() is that our builder now depends on our runner, so when we want to wrap the default builder/runner in new functionality we need to jump through some hoops. We could avoid the use of a decorator function here by just exporting the runner field on the default builder but that would be misleading because we don't want anybody using that to run commands (i.e. we want there to be a single API used across the codebase) - updatedBuilder := innerBuilder.CloneWithNewRunner(func(runner oscommands.ICmdObjRunner) oscommands.ICmdObjRunner { - return &gitCmdObjRunner{ - log: log, - innerRunner: runner, - } - }) - - return &gitCmdObjBuilder{ - innerBuilder: updatedBuilder, - } -} - -var defaultEnvVar = "GIT_OPTIONAL_LOCKS=0" - -func (self *gitCmdObjBuilder) New(args []string) *oscommands.CmdObj { - return self.innerBuilder.New(args).AddEnvVars(defaultEnvVar) -} - -func (self *gitCmdObjBuilder) NewShell(cmdStr string, shellFunctionsFile string) *oscommands.CmdObj { - return self.innerBuilder.NewShell(cmdStr, shellFunctionsFile).AddEnvVars(defaultEnvVar) -} - -func (self *gitCmdObjBuilder) Quote(str string) string { - return self.innerBuilder.Quote(str) -} diff --git a/pkg/commands/git_cmd_obj_runner.go b/pkg/commands/git_cmd_obj_runner.go deleted file mode 100644 index fd98bc84f7a..00000000000 --- a/pkg/commands/git_cmd_obj_runner.go +++ /dev/null @@ -1,69 +0,0 @@ -package commands - -import ( - "strings" - "time" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/sirupsen/logrus" -) - -// here we're wrapping the default command runner in some git-specific stuff e.g. retry logic if we get an error due to the presence of .git/index.lock - -const ( - WaitTime = 50 * time.Millisecond - RetryCount = 5 -) - -type gitCmdObjRunner struct { - log *logrus.Entry - innerRunner oscommands.ICmdObjRunner -} - -func (self *gitCmdObjRunner) Run(cmdObj *oscommands.CmdObj) error { - _, err := self.RunWithOutput(cmdObj) - return err -} - -func (self *gitCmdObjRunner) RunWithOutput(cmdObj *oscommands.CmdObj) (string, error) { - var output string - var err error - for range RetryCount { - newCmdObj := cmdObj.Clone() - output, err = self.innerRunner.RunWithOutput(newCmdObj) - - if err == nil || !strings.Contains(output, ".git/index.lock") { - return output, err - } - - // if we have an error based on the index lock, we should wait a bit and then retry - self.log.Warn("index.lock prevented command from running. Retrying command after a small wait") - time.Sleep(WaitTime) - } - - return output, err -} - -func (self *gitCmdObjRunner) RunWithOutputs(cmdObj *oscommands.CmdObj) (string, string, error) { - var stdout, stderr string - var err error - for range RetryCount { - newCmdObj := cmdObj.Clone() - stdout, stderr, err = self.innerRunner.RunWithOutputs(newCmdObj) - - if err == nil || !strings.Contains(stdout+stderr, ".git/index.lock") { - return stdout, stderr, err - } - - // if we have an error based on the index lock, we should wait a bit and then retry - self.log.Warn("index.lock prevented command from running. Retrying command after a small wait") - time.Sleep(WaitTime) - } - - return stdout, stderr, err -} - -// Retry logic not implemented here, but these commands typically don't need to obtain a lock. -func (self *gitCmdObjRunner) RunAndProcessLines(cmdObj *oscommands.CmdObj, onLine func(line string) (bool, error)) error { - return self.innerRunner.RunAndProcessLines(cmdObj, onLine) -} diff --git a/pkg/commands/git_commands/bisect.go b/pkg/commands/git_commands/bisect.go deleted file mode 100644 index 300613e1673..00000000000 --- a/pkg/commands/git_commands/bisect.go +++ /dev/null @@ -1,194 +0,0 @@ -package git_commands - -import ( - "os" - "path/filepath" - "strings" -) - -type BisectCommands struct { - *GitCommon -} - -func NewBisectCommands(gitCommon *GitCommon) *BisectCommands { - return &BisectCommands{ - GitCommon: gitCommon, - } -} - -// This command is pretty cheap to run so we're not storing the result anywhere. -// But if it becomes problematic we can chang that. -func (self *BisectCommands) GetInfo() *BisectInfo { - return self.GetInfoForGitDir(self.repoPaths.WorktreeGitDirPath()) -} - -func (self *BisectCommands) GetInfoForGitDir(gitDir string) *BisectInfo { - var err error - info := &BisectInfo{started: false, log: self.Log, newTerm: "bad", oldTerm: "good"} - // we return nil if we're not in a git bisect session. - // we know we're in a session by the presence of a .git/BISECT_START file - - bisectStartPath := filepath.Join(gitDir, "BISECT_START") - exists, err := self.os.FileExists(bisectStartPath) - if err != nil { - self.Log.Infof("error getting git bisect info: %s", err.Error()) - return info - } - - if !exists { - return info - } - - startContent, err := os.ReadFile(bisectStartPath) - if err != nil { - self.Log.Infof("error getting git bisect info: %s", err.Error()) - return info - } - - info.started = true - info.start = strings.TrimSpace(string(startContent)) - - termsContent, err := os.ReadFile(filepath.Join(gitDir, "BISECT_TERMS")) - if err != nil { - // old git versions won't have this file so we default to bad/good - } else { - splitContent := strings.Split(string(termsContent), "\n") - info.newTerm = splitContent[0] - info.oldTerm = splitContent[1] - } - - bisectRefsDir := filepath.Join(gitDir, "refs", "bisect") - files, err := os.ReadDir(bisectRefsDir) - if err != nil { - self.Log.Infof("error getting git bisect info: %s", err.Error()) - return info - } - - info.statusMap = make(map[string]BisectStatus) - for _, file := range files { - status := BisectStatusSkipped - name := file.Name() - path := filepath.Join(bisectRefsDir, name) - - fileContent, err := os.ReadFile(path) - if err != nil { - self.Log.Infof("error getting git bisect info: %s", err.Error()) - return info - } - - hash := strings.TrimSpace(string(fileContent)) - - if name == info.newTerm { - status = BisectStatusNew - } else if strings.HasPrefix(name, info.oldTerm+"-") { - status = BisectStatusOld - } else if strings.HasPrefix(name, "skipped-") { - status = BisectStatusSkipped - } - - info.statusMap[hash] = status - } - - currentContent, err := os.ReadFile(filepath.Join(gitDir, "BISECT_EXPECTED_REV")) - if err != nil { - self.Log.Infof("error getting git bisect info: %s", err.Error()) - return info - } - currentHash := strings.TrimSpace(string(currentContent)) - info.current = currentHash - - return info -} - -func (self *BisectCommands) Reset() error { - cmdArgs := NewGitCmd("bisect").Arg("reset").ToArgv() - - return self.cmd.New(cmdArgs).StreamOutput().Run() -} - -func (self *BisectCommands) Mark(ref string, term string) error { - cmdArgs := NewGitCmd("bisect").Arg(term, ref).ToArgv() - - return self.cmd.New(cmdArgs). - IgnoreEmptyError(). - StreamOutput(). - Run() -} - -func (self *BisectCommands) Skip(ref string) error { - return self.Mark(ref, "skip") -} - -func (self *BisectCommands) Start() error { - cmdArgs := NewGitCmd("bisect").Arg("start").ToArgv() - - return self.cmd.New(cmdArgs).StreamOutput().Run() -} - -func (self *BisectCommands) StartWithTerms(oldTerm string, newTerm string) error { - cmdArgs := NewGitCmd("bisect").Arg("start"). - Arg("--term-old=" + oldTerm). - Arg("--term-new=" + newTerm). - ToArgv() - - return self.cmd.New(cmdArgs).StreamOutput().Run() -} - -// tells us whether we've found our problem commit(s). We return a string slice of -// commit hashes if we're done, and that slice may have more that one item if -// skipped commits are involved. -func (self *BisectCommands) IsDone() (bool, []string, error) { - info := self.GetInfo() - if !info.Bisecting() { - return false, nil, nil - } - - newHash := info.GetNewHash() - if newHash == "" { - return false, nil, nil - } - - // if we start from the new commit and reach the a good commit without - // coming across any unprocessed commits, then we're done - done := false - candidates := []string{} - - cmdArgs := NewGitCmd("rev-list").Arg(newHash).ToArgv() - err := self.cmd.New(cmdArgs).RunAndProcessLines(func(line string) (bool, error) { - hash := strings.TrimSpace(line) - - if status, ok := info.statusMap[hash]; ok { - switch status { - case BisectStatusSkipped, BisectStatusNew: - candidates = append(candidates, hash) - return false, nil - case BisectStatusOld: - done = true - return true, nil - } - } else { - return true, nil - } - - // should never land here - return true, nil - }) - if err != nil { - return false, nil, err - } - - return done, candidates, nil -} - -// tells us whether the 'start' ref that we'll be sent back to after we're done -// bisecting is actually a descendant of our current bisect commit. If it's not, we need to -// render the commits from the bad commit. -func (self *BisectCommands) ReachableFromStart(bisectInfo *BisectInfo) bool { - cmdArgs := NewGitCmd("merge-base"). - Arg("--is-ancestor", bisectInfo.GetNewHash(), bisectInfo.GetStartHash()). - ToArgv() - - err := self.cmd.New(cmdArgs).DontLog().Run() - - return err == nil -} diff --git a/pkg/commands/git_commands/bisect_info.go b/pkg/commands/git_commands/bisect_info.go deleted file mode 100644 index c49d6866f70..00000000000 --- a/pkg/commands/git_commands/bisect_info.go +++ /dev/null @@ -1,101 +0,0 @@ -package git_commands - -import ( - "github.com/jesseduffield/generics/maps" - "github.com/samber/lo" - "github.com/sirupsen/logrus" -) - -// although the typical terms in a git bisect are 'bad' and 'good', they're more -// generally known as 'new' and 'old'. Semi-recently git allowed the user to define -// their own terms e.g. when you want to used 'fixed', 'unfixed' in the event -// that you're looking for a commit that fixed a bug. - -// Git bisect only keeps track of a single 'bad' commit. Once you pick a commit -// that's older than the current bad one, it forgets about the previous one. On -// the other hand, it does keep track of all the good and skipped commits. - -type BisectInfo struct { - log *logrus.Entry - - // tells us whether all our git bisect files are there meaning we're in bisect mode. - // Doesn't necessarily mean that we've actually picked a good/bad commit yet. - started bool - - // this is the ref you started the commit from - start string // this will always be defined - - // these will be defined if we've started - newTerm string // 'bad' by default - oldTerm string // 'good' by default - - // map of commit hashes to their status - statusMap map[string]BisectStatus - - // the hash of the commit that's under test - current string -} - -type BisectStatus int - -const ( - BisectStatusOld BisectStatus = iota - BisectStatusNew - BisectStatusSkipped -) - -// null object pattern -func NewNullBisectInfo() *BisectInfo { - return &BisectInfo{started: false} -} - -func (self *BisectInfo) GetNewHash() string { - for hash, status := range self.statusMap { - if status == BisectStatusNew { - return hash - } - } - - return "" -} - -func (self *BisectInfo) GetCurrentHash() string { - return self.current -} - -func (self *BisectInfo) GetStartHash() string { - return self.start -} - -func (self *BisectInfo) Status(commitHash string) (BisectStatus, bool) { - status, ok := self.statusMap[commitHash] - return status, ok -} - -func (self *BisectInfo) NewTerm() string { - return self.newTerm -} - -func (self *BisectInfo) OldTerm() string { - return self.oldTerm -} - -// this is for when we have called `git bisect start`. It does not -// mean that we have actually started narrowing things down or selecting good/bad commits -func (self *BisectInfo) Started() bool { - return self.started -} - -// this is where we have both a good and bad revision and we're actually -// starting to narrow things down -func (self *BisectInfo) Bisecting() bool { - if !self.Started() { - return false - } - - if self.GetNewHash() == "" { - return false - } - - return lo.Contains(maps.Values(self.statusMap), BisectStatusOld) -} diff --git a/pkg/commands/git_commands/blame.go b/pkg/commands/git_commands/blame.go deleted file mode 100644 index aba1c63fe84..00000000000 --- a/pkg/commands/git_commands/blame.go +++ /dev/null @@ -1,33 +0,0 @@ -package git_commands - -import ( - "fmt" -) - -type BlameCommands struct { - *GitCommon -} - -func NewBlameCommands(gitCommon *GitCommon) *BlameCommands { - return &BlameCommands{ - GitCommon: gitCommon, - } -} - -// Blame a range of lines. For each line, output the hash of the commit where -// the line last changed, then a space, then a description of the commit (author -// and date), another space, and then the line. For example: -// -// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 11) func NewBlameCommands(gitCommon *GitCommon) *BlameCommands { -// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 12) return &BlameCommands{ -// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 13) GitCommon: gitCommon, -func (self *BlameCommands) BlameLineRange(filename string, commit string, firstLine int, numLines int) (string, error) { - cmdArgs := NewGitCmd("blame"). - Arg("-l"). - Arg(fmt.Sprintf("-L%d,+%d", firstLine, numLines)). - Arg(commit). - Arg("--"). - Arg(filename) - - return self.cmd.New(cmdArgs.ToArgv()).RunWithOutput() -} diff --git a/pkg/commands/git_commands/branch.go b/pkg/commands/git_commands/branch.go deleted file mode 100644 index cd78a755b7f..00000000000 --- a/pkg/commands/git_commands/branch.go +++ /dev/null @@ -1,356 +0,0 @@ -package git_commands - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/mgutz/str" - "github.com/samber/lo" -) - -type BranchCommands struct { - *GitCommon - allBranchesLogCmdIndex int // keeps track of current all branches log command -} - -func NewBranchCommands(gitCommon *GitCommon) *BranchCommands { - return &BranchCommands{ - GitCommon: gitCommon, - } -} - -// New creates a new branch -func (self *BranchCommands) New(name string, base string) error { - cmdArgs := NewGitCmd("checkout"). - Arg("-b", name, base). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *BranchCommands) NewWithoutTracking(name string, base string) error { - cmdArgs := NewGitCmd("checkout"). - Arg("-b", name, base). - Arg("--no-track"). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// NewWithoutCheckout creates a new branch without checking it out -func (self *BranchCommands) NewWithoutCheckout(name string, base string) error { - cmdArgs := NewGitCmd("branch"). - Arg(name, base). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// CreateWithUpstream creates a new branch with a given upstream, but without -// checking it out -func (self *BranchCommands) CreateWithUpstream(name string, upstream string) error { - cmdArgs := NewGitCmd("branch"). - Arg("--track"). - Arg(name, upstream). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// CurrentBranchInfo get the current branch information. -func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) { - branchName, err := self.cmd.New( - NewGitCmd("symbolic-ref"). - Arg("--short", "HEAD"). - ToArgv(), - ).DontLog().RunWithOutput() - if err == nil && branchName != "HEAD\n" { - trimmedBranchName := strings.TrimSpace(branchName) - return BranchInfo{ - RefName: trimmedBranchName, - DisplayName: trimmedBranchName, - DetachedHead: false, - }, nil - } - output, err := self.cmd.New( - NewGitCmd("branch"). - Arg("--points-at=HEAD", "--format=%(HEAD)%00%(objectname)%00%(refname)"). - ToArgv(), - ).DontLog().RunWithOutput() - if err != nil { - return BranchInfo{}, err - } - for _, line := range utils.SplitLines(output) { - split := strings.Split(strings.TrimRight(line, "\r\n"), "\x00") - if len(split) == 3 && split[0] == "*" { - hash := split[1] - displayName := split[2] - return BranchInfo{ - RefName: hash, - DisplayName: displayName, - DetachedHead: true, - }, nil - } - } - return BranchInfo{ - RefName: "HEAD", - DisplayName: "HEAD", - DetachedHead: true, - }, nil -} - -// CurrentBranchName get name of current branch. Returns empty string if HEAD is detached. -func (self *BranchCommands) CurrentBranchName() (string, error) { - cmdArgs := NewGitCmd("branch"). - Arg("--show-current"). - ToArgv() - - output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - if err != nil { - return "", err - } - - return strings.TrimSpace(output), nil -} - -// Gets the full ref name of the previously checked out branch. Can return an empty string (but no -// error) e.g. when the previously checked out thing was a detached head. -func (self *BranchCommands) PreviousRef() (string, error) { - cmdArgs := NewGitCmd("rev-parse"). - Arg("--symbolic-full-name"). - Arg("@{-1}"). - ToArgv() - - output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - if err != nil { - return "", err - } - - return strings.TrimSpace(output), nil -} - -// LocalDelete delete branch locally -func (self *BranchCommands) LocalDelete(branches []string, force bool) error { - cmdArgs := NewGitCmd("branch"). - ArgIfElse(force, "-D", "-d"). - Arg(branches...). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// Checkout checks out a branch (or commit), with --force if you set the force arg to true -type CheckoutOptions struct { - Force bool - EnvVars []string -} - -func (self *BranchCommands) Checkout(branch string, options CheckoutOptions) error { - cmdArgs := NewGitCmd("checkout"). - ArgIf(options.Force, "--force"). - Arg(branch). - ToArgv() - - return self.cmd.New(cmdArgs). - // prevents git from prompting us for input which would freeze the program - // TODO: see if this is actually needed here - AddEnvVars("GIT_TERMINAL_PROMPT=0"). - AddEnvVars(options.EnvVars...). - Run() -} - -// GetGraph gets the color-formatted graph of the log for the given branch -// Currently it limits the result to 100 commits, but when we get async stuff -// working we can do lazy loading -func (self *BranchCommands) GetGraph(branchName string) (string, error) { - return self.GetGraphCmdObj(branchName).DontLog().RunWithOutput() -} - -func (self *BranchCommands) GetGraphCmdObj(branchName string) *oscommands.CmdObj { - branchLogCmdTemplate := self.UserConfig().Git.BranchLogCmd - templateValues := map[string]string{ - "branchName": self.cmd.Quote(branchName), - } - - resolvedTemplate := utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues) - - return self.cmd.New(str.ToArgv(resolvedTemplate)).DontLog() -} - -func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error { - cmdArgs := NewGitCmd("branch"). - Arg(fmt.Sprintf("--set-upstream-to=%s/%s", remoteName, remoteBranchName)). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error { - cmdArgs := NewGitCmd("branch"). - Arg(fmt.Sprintf("--set-upstream-to=%s/%s", remoteName, remoteBranchName)). - Arg(branchName). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *BranchCommands) UnsetUpstream(branchName string) error { - cmdArgs := NewGitCmd("branch").Arg("--unset-upstream", branchName). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *BranchCommands) GetCurrentBranchUpstreamDifferenceCount() (string, string) { - return self.GetCommitDifferences("HEAD", "HEAD@{u}") -} - -func (self *BranchCommands) GetUpstreamDifferenceCount(branchName string) (string, string) { - return self.GetCommitDifferences(branchName, branchName+"@{u}") -} - -// GetCommitDifferences checks how many pushables/pullables there are for the -// current branch -func (self *BranchCommands) GetCommitDifferences(from, to string) (string, string) { - pushableCount, err := self.countDifferences(to, from) - if err != nil { - return "?", "?" - } - pullableCount, err := self.countDifferences(from, to) - if err != nil { - return "?", "?" - } - return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount) -} - -func (self *BranchCommands) countDifferences(from, to string) (string, error) { - cmdArgs := NewGitCmd("rev-list"). - Arg(fmt.Sprintf("%s..%s", from, to)). - Arg("--count"). - ToArgv() - - return self.cmd.New(cmdArgs).DontLog().RunWithOutput() -} - -func (self *BranchCommands) IsHeadDetached() bool { - cmdArgs := NewGitCmd("symbolic-ref").Arg("-q", "HEAD").ToArgv() - - err := self.cmd.New(cmdArgs).DontLog().Run() - return err != nil -} - -func (self *BranchCommands) Rename(oldName string, newName string) error { - cmdArgs := NewGitCmd("branch"). - Arg("--move", oldName, newName). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -type MergeVariant int - -const ( - MERGE_VARIANT_REGULAR MergeVariant = iota - MERGE_VARIANT_FAST_FORWARD - MERGE_VARIANT_NON_FAST_FORWARD - MERGE_VARIANT_SQUASH -) - -func (self *BranchCommands) Merge(branchName string, variant MergeVariant) error { - extraArgs := func() []string { - switch variant { - case MERGE_VARIANT_REGULAR: - return []string{} - case MERGE_VARIANT_FAST_FORWARD: - return []string{"--ff"} - case MERGE_VARIANT_NON_FAST_FORWARD: - return []string{"--no-ff"} - case MERGE_VARIANT_SQUASH: - return []string{"--squash", "--ff"} - } - - panic("shouldn't get here") - }() - - cmdArgs := NewGitCmd("merge"). - Arg("--no-edit"). - Arg(strings.Fields(self.UserConfig().Git.Merging.Args)...). - Arg(extraArgs...). - Arg(branchName). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// Returns whether refName can be fast-forward merged into the current branch -func (self *BranchCommands) CanDoFastForwardMerge(refName string) bool { - cmdArgs := NewGitCmd("merge-base"). - Arg("--is-ancestor"). - Arg("HEAD", refName). - ToArgv() - err := self.cmd.New(cmdArgs).DontLog().Run() - return err == nil -} - -// Only choose between non-empty, non-identical commands -func (self *BranchCommands) allBranchesLogCandidates() []string { - return lo.Uniq(lo.WithoutEmpty(self.UserConfig().Git.AllBranchesLogCmds)) -} - -func (self *BranchCommands) AllBranchesLogCmdObj() *oscommands.CmdObj { - candidates := self.allBranchesLogCandidates() - - if self.allBranchesLogCmdIndex >= len(candidates) { - self.allBranchesLogCmdIndex = 0 - } - - i := self.allBranchesLogCmdIndex - return self.cmd.New(str.ToArgv(candidates[i])).DontLog() -} - -func (self *BranchCommands) RotateAllBranchesLogIdx() { - n := len(self.allBranchesLogCandidates()) - i := self.allBranchesLogCmdIndex - self.allBranchesLogCmdIndex = (i + 1) % n -} - -func (self *BranchCommands) GetAllBranchesLogIdxAndCount() (int, int) { - n := len(self.allBranchesLogCandidates()) - i := self.allBranchesLogCmdIndex - return i, n -} - -func (self *BranchCommands) IsBranchMerged(branch *models.Branch, mainBranches *MainBranches) (bool, error) { - branchesToCheckAgainst := []string{"HEAD"} - if branch.RemoteBranchStoredLocally() { - branchesToCheckAgainst = append(branchesToCheckAgainst, fmt.Sprintf("%s@{upstream}", branch.Name)) - } - branchesToCheckAgainst = append(branchesToCheckAgainst, mainBranches.Get()...) - - cmdArgs := NewGitCmd("rev-list"). - Arg("--max-count=1"). - Arg(branch.Name). - Arg(lo.Map(branchesToCheckAgainst, func(branch string, _ int) string { - return fmt.Sprintf("^%s", branch) - })...). - Arg("--"). - ToArgv() - - stdout, _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutputs() - if err != nil { - return false, err - } - - return stdout == "", nil -} - -func (self *BranchCommands) UpdateBranchRefs(updateCommands string) error { - cmdArgs := NewGitCmd("update-ref"). - Arg("--stdin"). - ToArgv() - - return self.cmd.New(cmdArgs).SetStdin(updateCommands).Run() -} diff --git a/pkg/commands/git_commands/branch_loader.go b/pkg/commands/git_commands/branch_loader.go deleted file mode 100644 index a4e41b75604..00000000000 --- a/pkg/commands/git_commands/branch_loader.go +++ /dev/null @@ -1,388 +0,0 @@ -package git_commands - -import ( - "fmt" - "regexp" - "slices" - "strconv" - "strings" - "time" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/go-git/v5/config" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "golang.org/x/sync/errgroup" -) - -// context: -// we want to only show 'safe' branches (ones that haven't e.g. been deleted) -// which `git branch -a` gives us, but we also want the recency data that -// git reflog gives us. -// So we get the HEAD, then append get the reflog branches that intersect with -// our safe branches, then add the remaining safe branches, ensuring uniqueness -// along the way - -// if we find out we need to use one of these functions in the git.go file, we -// can just pull them out of here and put them there and then call them from in here - -type BranchLoaderConfigCommands interface { - Branches() (map[string]*config.Branch, error) -} - -type BranchInfo struct { - RefName string - DisplayName string // e.g. '(HEAD detached at 123asdf)' - DetachedHead bool -} - -// BranchLoader returns a list of Branch objects for the current repo -type BranchLoader struct { - *common.Common - *GitCommon - cmd oscommands.ICmdObjBuilder - getCurrentBranchInfo func() (BranchInfo, error) - config BranchLoaderConfigCommands -} - -func NewBranchLoader( - cmn *common.Common, - gitCommon *GitCommon, - cmd oscommands.ICmdObjBuilder, - getCurrentBranchInfo func() (BranchInfo, error), - config BranchLoaderConfigCommands, -) *BranchLoader { - return &BranchLoader{ - Common: cmn, - GitCommon: gitCommon, - cmd: cmd, - getCurrentBranchInfo: getCurrentBranchInfo, - config: config, - } -} - -// Load the list of branches for the current repo -func (self *BranchLoader) Load(reflogCommits []*models.Commit, - mainBranches *MainBranches, - oldBranches []*models.Branch, - loadBehindCounts bool, - onWorker func(func() error), - renderFunc func(), -) ([]*models.Branch, error) { - branches := self.obtainBranches() - - if self.UserConfig().Git.LocalBranchSortOrder == "recency" { - reflogBranches := self.obtainReflogBranches(reflogCommits) - // loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches - branchesWithRecency := make([]*models.Branch, 0) - outer: - for _, reflogBranch := range reflogBranches { - for j, branch := range branches { - if branch.Head { - continue - } - if strings.EqualFold(reflogBranch.Name, branch.Name) { - branch.Recency = reflogBranch.Recency - branchesWithRecency = append(branchesWithRecency, branch) - branches = utils.Remove(branches, j) - continue outer - } - } - } - - // Sort branches that don't have a recency value alphabetically - // (we're really doing this for the sake of deterministic behaviour across git versions) - slices.SortFunc(branches, func(a *models.Branch, b *models.Branch) int { - return strings.Compare(a.Name, b.Name) - }) - - branches = utils.Prepend(branches, branchesWithRecency...) - } - - foundHead := false - for i, branch := range branches { - if branch.Head { - foundHead = true - branch.Recency = " *" - branches = utils.Move(branches, i, 0) - break - } - } - if !foundHead { - info, err := self.getCurrentBranchInfo() - if err != nil { - return nil, err - } - branches = utils.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"}) - } - - configBranches, err := self.config.Branches() - if err != nil { - return nil, err - } - - for _, branch := range branches { - match := configBranches[branch.Name] - if match != nil { - branch.UpstreamRemote = match.Remote - branch.UpstreamBranch = match.Merge.Short() - } - - // If the branch already existed, take over its BehindBaseBranch value - // to reduce flicker - if oldBranch, found := lo.Find(oldBranches, func(b *models.Branch) bool { - return b.Name == branch.Name - }); found { - branch.BehindBaseBranch.Store(oldBranch.BehindBaseBranch.Load()) - } - } - - if loadBehindCounts && self.UserConfig().Gui.ShowDivergenceFromBaseBranch != "none" { - onWorker(func() error { - return self.GetBehindBaseBranchValuesForAllBranches(branches, mainBranches, renderFunc) - }) - } - - return branches, nil -} - -func (self *BranchLoader) GetBehindBaseBranchValuesForAllBranches( - branches []*models.Branch, - mainBranches *MainBranches, - renderFunc func(), -) error { - mainBranchRefs := mainBranches.Get() - if len(mainBranchRefs) == 0 { - return nil - } - - t := time.Now() - errg := errgroup.Group{} - - for _, branch := range branches { - errg.Go(func() error { - baseBranch, err := self.GetBaseBranch(branch, mainBranches) - if err != nil { - return err - } - behind := 0 // prime it in case something below fails - if baseBranch != "" { - output, err := self.cmd.New( - NewGitCmd("rev-list"). - Arg("--left-right"). - Arg("--count"). - Arg(fmt.Sprintf("%s...%s", branch.FullRefName(), baseBranch)). - ToArgv(), - ).DontLog().RunWithOutput() - if err != nil { - return err - } - // The format of the output is "\t" - aheadBehindStr := strings.Split(strings.TrimSpace(output), "\t") - if len(aheadBehindStr) == 2 { - if value, err := strconv.Atoi(aheadBehindStr[1]); err == nil { - behind = value - } - } - } - branch.BehindBaseBranch.Store(int32(behind)) - return nil - }) - } - - err := errg.Wait() - self.Log.Debugf("time to get behind base branch values for all branches: %s", time.Since(t)) - renderFunc() - return err -} - -// Find the base branch for the given branch (i.e. the main branch that the -// given branch was forked off of) -// -// Note that this function may return an empty string even if the returned error -// is nil, e.g. when none of the configured main branches exist. This is not -// considered an error condition, so callers need to check both the returned -// error and whether the returned base branch is empty (and possibly react -// differently in both cases). -func (self *BranchLoader) GetBaseBranch(branch *models.Branch, mainBranches *MainBranches) (string, error) { - mergeBase := mainBranches.GetMergeBase(branch.FullRefName()) - if mergeBase == "" { - return "", nil - } - - output, err := self.cmd.New( - NewGitCmd("for-each-ref"). - Arg("--contains"). - Arg(mergeBase). - Arg("--format=%(refname)"). - Arg(mainBranches.Get()...). - ToArgv(), - ).DontLog().RunWithOutput() - if err != nil { - return "", err - } - trimmedOutput := strings.TrimSpace(output) - split := strings.Split(trimmedOutput, "\n") - if len(split) == 0 || split[0] == "" { - return "", nil - } - return split[0], nil -} - -func (self *BranchLoader) obtainBranches() []*models.Branch { - output, err := self.getRawBranches() - if err != nil { - panic(err) - } - - trimmedOutput := strings.TrimSpace(output) - outputLines := strings.Split(trimmedOutput, "\n") - - return lo.FilterMap(outputLines, func(line string, _ int) (*models.Branch, bool) { - if line == "" { - return nil, false - } - - split := strings.Split(line, "\x00") - if len(split) != len(branchFields) { - // Ignore line if it isn't separated into the expected number of parts - // This is probably a warning message, for more info see: - // https://github.com/jesseduffield/lazygit/issues/1385#issuecomment-885580439 - return nil, false - } - - storeCommitDateAsRecency := self.UserConfig().Git.LocalBranchSortOrder != "recency" - return obtainBranch(split, storeCommitDateAsRecency), true - }) -} - -func (self *BranchLoader) getRawBranches() (string, error) { - format := strings.Join( - lo.Map(branchFields, func(thing string, _ int) string { - return "%(" + thing + ")" - }), - "%00", - ) - - var sortOrder string - switch strings.ToLower(self.UserConfig().Git.LocalBranchSortOrder) { - case "recency", "date": - sortOrder = "-committerdate" - case "alphabetical": - sortOrder = "refname" - default: - sortOrder = "refname" - } - - cmdArgs := NewGitCmd("for-each-ref"). - Arg(fmt.Sprintf("--sort=%s", sortOrder)). - Arg(fmt.Sprintf("--format=%s", format)). - Arg("refs/heads"). - ToArgv() - - return self.cmd.New(cmdArgs).DontLog().RunWithOutput() -} - -var branchFields = []string{ - "HEAD", - "refname:short", - "upstream:short", - "upstream:track", - "push:track", - "subject", - "objectname", - "committerdate:unix", -} - -// Obtain branch information from parsed line output of getRawBranches() -func obtainBranch(split []string, storeCommitDateAsRecency bool) *models.Branch { - headMarker := split[0] - fullName := split[1] - upstreamName := split[2] - track := split[3] - pushTrack := split[4] - subject := split[5] - commitHash := split[6] - commitDate := split[7] - - name := strings.TrimPrefix(fullName, "heads/") - aheadForPull, behindForPull, gone := parseUpstreamInfo(upstreamName, track) - aheadForPush, behindForPush, _ := parseUpstreamInfo(upstreamName, pushTrack) - - recency := "" - if storeCommitDateAsRecency { - if unixTimestamp, err := strconv.ParseInt(commitDate, 10, 64); err == nil { - recency = utils.UnixToTimeAgo(unixTimestamp) - } - } - - return &models.Branch{ - Name: name, - Recency: recency, - AheadForPull: aheadForPull, - BehindForPull: behindForPull, - AheadForPush: aheadForPush, - BehindForPush: behindForPush, - UpstreamGone: gone, - Head: headMarker == "*", - Subject: subject, - CommitHash: commitHash, - } -} - -func parseUpstreamInfo(upstreamName string, track string) (string, string, bool) { - if upstreamName == "" { - // if we're here then it means we do not have a local version of the remote. - // The branch might still be tracking a remote though, we just don't know - // how many commits ahead/behind it is - return "?", "?", false - } - - if track == "[gone]" { - return "?", "?", true - } - - ahead := parseDifference(track, `ahead (\d+)`) - behind := parseDifference(track, `behind (\d+)`) - - return ahead, behind, false -} - -func parseDifference(track string, regexStr string) string { - re := regexp.MustCompile(regexStr) - match := re.FindStringSubmatch(track) - if len(match) > 1 { - return match[1] - } - return "0" -} - -// TODO: only look at the new reflog commits, and otherwise store the recencies in -// int form against the branch to recalculate the time ago -func (self *BranchLoader) obtainReflogBranches(reflogCommits []*models.Commit) []*models.Branch { - foundBranches := set.New[string]() - re := regexp.MustCompile(`checkout: moving from ([\S]+) to ([\S]+)`) - reflogBranches := make([]*models.Branch, 0, len(reflogCommits)) - - for _, commit := range reflogCommits { - match := re.FindStringSubmatch(commit.Name) - if len(match) != 3 { - continue - } - - recency := utils.UnixToTimeAgo(commit.UnixTimestamp) - for _, branchName := range match[1:] { - if !foundBranches.Includes(branchName) { - foundBranches.Add(branchName) - reflogBranches = append(reflogBranches, &models.Branch{ - Recency: recency, - Name: branchName, - }) - } - } - } - return reflogBranches -} diff --git a/pkg/commands/git_commands/branch_loader_test.go b/pkg/commands/git_commands/branch_loader_test.go deleted file mode 100644 index 27a747879aa..00000000000 --- a/pkg/commands/git_commands/branch_loader_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package git_commands - -// "*|feat/detect-purge|origin/feat/detect-purge|[ahead 1]" -import ( - "strconv" - "testing" - "time" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/stretchr/testify/assert" -) - -func TestObtainBranch(t *testing.T) { - type scenario struct { - testName string - input []string - storeCommitDateAsRecency bool - expectedBranch *models.Branch - } - - // Use a time stamp of 2 1/2 hours ago, resulting in a recency string of "2h" - now := time.Now().Unix() - timeStamp := strconv.Itoa(int(now - 2.5*60*60)) - - scenarios := []scenario{ - { - testName: "TrimHeads", - input: []string{"", "heads/a_branch", "", "", "", "subject", "123", timeStamp}, - storeCommitDateAsRecency: false, - expectedBranch: &models.Branch{ - Name: "a_branch", - AheadForPull: "?", - BehindForPull: "?", - AheadForPush: "?", - BehindForPush: "?", - Head: false, - Subject: "subject", - CommitHash: "123", - }, - }, - { - testName: "NoUpstream", - input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp}, - storeCommitDateAsRecency: false, - expectedBranch: &models.Branch{ - Name: "a_branch", - AheadForPull: "?", - BehindForPull: "?", - AheadForPush: "?", - BehindForPush: "?", - Head: false, - Subject: "subject", - CommitHash: "123", - }, - }, - { - testName: "IsHead", - input: []string{"*", "a_branch", "", "", "", "subject", "123", timeStamp}, - storeCommitDateAsRecency: false, - expectedBranch: &models.Branch{ - Name: "a_branch", - AheadForPull: "?", - BehindForPull: "?", - AheadForPush: "?", - BehindForPush: "?", - Head: true, - Subject: "subject", - CommitHash: "123", - }, - }, - { - testName: "IsBehindAndAhead", - input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "[behind 2, ahead 3]", "subject", "123", timeStamp}, - storeCommitDateAsRecency: false, - expectedBranch: &models.Branch{ - Name: "a_branch", - AheadForPull: "3", - BehindForPull: "2", - AheadForPush: "3", - BehindForPush: "2", - Head: false, - Subject: "subject", - CommitHash: "123", - }, - }, - { - testName: "RemoteBranchIsGone", - input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "[gone]", "subject", "123", timeStamp}, - storeCommitDateAsRecency: false, - expectedBranch: &models.Branch{ - Name: "a_branch", - UpstreamGone: true, - AheadForPull: "?", - BehindForPull: "?", - AheadForPush: "?", - BehindForPush: "?", - Head: false, - Subject: "subject", - CommitHash: "123", - }, - }, - { - testName: "WithCommitDateAsRecency", - input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp}, - storeCommitDateAsRecency: true, - expectedBranch: &models.Branch{ - Name: "a_branch", - Recency: "2h", - AheadForPull: "?", - BehindForPull: "?", - AheadForPush: "?", - BehindForPush: "?", - Head: false, - Subject: "subject", - CommitHash: "123", - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - branch := obtainBranch(s.input, s.storeCommitDateAsRecency) - assert.EqualValues(t, s.expectedBranch, branch) - }) - } -} diff --git a/pkg/commands/git_commands/branch_test.go b/pkg/commands/git_commands/branch_test.go deleted file mode 100644 index a0c0096b945..00000000000 --- a/pkg/commands/git_commands/branch_test.go +++ /dev/null @@ -1,319 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/stretchr/testify/assert" -) - -func TestBranchGetCommitDifferences(t *testing.T) { - type scenario struct { - testName string - runner *oscommands.FakeCmdObjRunner - expectedPushables string - expectedPullables string - } - - scenarios := []scenario{ - { - "Can't retrieve pushable count", - oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rev-list", "@{u}..HEAD", "--count"}, "", errors.New("error")), - "?", "?", - }, - { - "Can't retrieve pullable count", - oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rev-list", "@{u}..HEAD", "--count"}, "1\n", nil). - ExpectGitArgs([]string{"rev-list", "HEAD..@{u}", "--count"}, "", errors.New("error")), - "?", "?", - }, - { - "Retrieve pullable and pushable count", - oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rev-list", "@{u}..HEAD", "--count"}, "1\n", nil). - ExpectGitArgs([]string{"rev-list", "HEAD..@{u}", "--count"}, "2\n", nil), - "1", "2", - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildBranchCommands(commonDeps{runner: s.runner}) - pushables, pullables := instance.GetCommitDifferences("HEAD", "@{u}") - assert.EqualValues(t, s.expectedPushables, pushables) - assert.EqualValues(t, s.expectedPullables, pullables) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestBranchNewBranch(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"checkout", "-b", "test", "refs/heads/master"}, "", nil) - instance := buildBranchCommands(commonDeps{runner: runner}) - - assert.NoError(t, instance.New("test", "refs/heads/master")) - runner.CheckForMissingCalls() -} - -func TestBranchDeleteBranch(t *testing.T) { - type scenario struct { - testName string - branchNames []string - force bool - runner *oscommands.FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - "Delete a branch", - []string{"test"}, - false, - oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-d", "test"}, "", nil), - func(err error) { - assert.NoError(t, err) - }, - }, - { - "Delete multiple branches", - []string{"test1", "test2", "test3"}, - false, - oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-d", "test1", "test2", "test3"}, "", nil), - func(err error) { - assert.NoError(t, err) - }, - }, - { - "Force delete a branch", - []string{"test"}, - true, - oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-D", "test"}, "", nil), - func(err error) { - assert.NoError(t, err) - }, - }, - { - "Force delete multiple branches", - []string{"test1", "test2", "test3"}, - true, - oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-D", "test1", "test2", "test3"}, "", nil), - func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildBranchCommands(commonDeps{runner: s.runner}) - - s.test(instance.LocalDelete(s.branchNames, s.force)) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestBranchMerge(t *testing.T) { - scenarios := []struct { - testName string - userConfig *config.UserConfig - variant MergeVariant - branchName string - expected []string - }{ - { - testName: "basic", - userConfig: &config.UserConfig{}, - variant: MERGE_VARIANT_REGULAR, - branchName: "mybranch", - expected: []string{"merge", "--no-edit", "mybranch"}, - }, - { - testName: "merging args", - userConfig: &config.UserConfig{ - Git: config.GitConfig{ - Merging: config.MergingConfig{ - Args: "--merging-args", // it's up to the user what they put here - }, - }, - }, - variant: MERGE_VARIANT_REGULAR, - branchName: "mybranch", - expected: []string{"merge", "--no-edit", "--merging-args", "mybranch"}, - }, - { - testName: "multiple merging args", - userConfig: &config.UserConfig{ - Git: config.GitConfig{ - Merging: config.MergingConfig{ - Args: "--arg1 --arg2", // it's up to the user what they put here - }, - }, - }, - variant: MERGE_VARIANT_REGULAR, - branchName: "mybranch", - expected: []string{"merge", "--no-edit", "--arg1", "--arg2", "mybranch"}, - }, - { - testName: "fast-forward merge", - userConfig: &config.UserConfig{}, - variant: MERGE_VARIANT_FAST_FORWARD, - branchName: "mybranch", - expected: []string{"merge", "--no-edit", "--ff", "mybranch"}, - }, - { - testName: "non-fast-forward merge", - userConfig: &config.UserConfig{}, - variant: MERGE_VARIANT_NON_FAST_FORWARD, - branchName: "mybranch", - expected: []string{"merge", "--no-edit", "--no-ff", "mybranch"}, - }, - { - testName: "squash merge", - userConfig: &config.UserConfig{}, - variant: MERGE_VARIANT_SQUASH, - branchName: "mybranch", - expected: []string{"merge", "--no-edit", "--squash", "--ff", "mybranch"}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs(s.expected, "", nil) - instance := buildBranchCommands(commonDeps{runner: runner, userConfig: s.userConfig}) - - assert.NoError(t, instance.Merge(s.branchName, s.variant)) - runner.CheckForMissingCalls() - }) - } -} - -func TestBranchCheckout(t *testing.T) { - type scenario struct { - testName string - runner *oscommands.FakeCmdObjRunner - test func(error) - force bool - } - - scenarios := []scenario{ - { - "Checkout", - oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"checkout", "test"}, "", nil), - func(err error) { - assert.NoError(t, err) - }, - false, - }, - { - "Checkout forced", - oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"checkout", "--force", "test"}, "", nil), - func(err error) { - assert.NoError(t, err) - }, - true, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildBranchCommands(commonDeps{runner: s.runner}) - s.test(instance.Checkout("test", CheckoutOptions{Force: s.force})) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestBranchGetBranchGraph(t *testing.T) { - runner := oscommands.NewFakeRunner(t).ExpectGitArgs([]string{ - "log", "--graph", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test", "--", - }, "", nil) - instance := buildBranchCommands(commonDeps{runner: runner}) - _, err := instance.GetGraph("test") - assert.NoError(t, err) -} - -func TestBranchGetAllBranchGraph(t *testing.T) { - runner := oscommands.NewFakeRunner(t).ExpectGitArgs([]string{ - "log", "--graph", "--all", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", - }, "", nil) - instance := buildBranchCommands(commonDeps{runner: runner}) - err := instance.AllBranchesLogCmdObj().Run() - assert.NoError(t, err) -} - -func TestBranchCurrentBranchInfo(t *testing.T) { - type scenario struct { - testName string - runner *oscommands.FakeCmdObjRunner - test func(BranchInfo, error) - } - - scenarios := []scenario{ - { - "says we are on the master branch if we are", - oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"symbolic-ref", "--short", "HEAD"}, "master", nil), - func(info BranchInfo, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "master", info.RefName) - assert.EqualValues(t, "master", info.DisplayName) - assert.False(t, info.DetachedHead) - }, - }, - { - "falls back to git `git branch --points-at=HEAD` if symbolic-ref fails", - oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"symbolic-ref", "--short", "HEAD"}, "", errors.New("error")). - ExpectGitArgs([]string{"branch", "--points-at=HEAD", "--format=%(HEAD)%00%(objectname)%00%(refname)"}, - "*\x006f71c57a8d4bd6c11399c3f55f42c815527a73a4\x00(HEAD detached at 6f71c57a)\n", nil), - func(info BranchInfo, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "6f71c57a8d4bd6c11399c3f55f42c815527a73a4", info.RefName) - assert.EqualValues(t, "(HEAD detached at 6f71c57a)", info.DisplayName) - assert.True(t, info.DetachedHead) - }, - }, - { - "handles a detached head (LANG=zh_CN.UTF-8)", - oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"symbolic-ref", "--short", "HEAD"}, "", errors.New("error")). - ExpectGitArgs( - []string{"branch", "--points-at=HEAD", "--format=%(HEAD)%00%(objectname)%00%(refname)"}, - "*\x00679b0456f3db7c505b398def84e7d023e5b55a8d\x00(头指针在 679b0456 分离)\n"+ - " \x00679b0456f3db7c505b398def84e7d023e5b55a8d\x00refs/heads/master\n", - nil), - func(info BranchInfo, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "679b0456f3db7c505b398def84e7d023e5b55a8d", info.RefName) - assert.EqualValues(t, "(头指针在 679b0456 分离)", info.DisplayName) - assert.True(t, info.DetachedHead) - }, - }, - { - "bubbles up error if there is one", - oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"symbolic-ref", "--short", "HEAD"}, "", errors.New("error")). - ExpectGitArgs([]string{"branch", "--points-at=HEAD", "--format=%(HEAD)%00%(objectname)%00%(refname)"}, "", errors.New("error")), - func(info BranchInfo, err error) { - assert.Error(t, err) - assert.EqualValues(t, "", info.RefName) - assert.EqualValues(t, "", info.DisplayName) - assert.False(t, info.DetachedHead) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildBranchCommands(commonDeps{runner: s.runner}) - s.test(instance.CurrentBranchInfo()) - s.runner.CheckForMissingCalls() - }) - } -} diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go deleted file mode 100644 index 89063be82bf..00000000000 --- a/pkg/commands/git_commands/commit.go +++ /dev/null @@ -1,335 +0,0 @@ -package git_commands - -import ( - "fmt" - "strings" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -var ErrInvalidCommitIndex = errors.New("invalid commit index") - -type CommitCommands struct { - *GitCommon -} - -func NewCommitCommands(gitCommon *GitCommon) *CommitCommands { - return &CommitCommands{ - GitCommon: gitCommon, - } -} - -// ResetAuthor resets the author of the topmost commit -func (self *CommitCommands) ResetAuthor() error { - cmdArgs := NewGitCmd("commit"). - Arg("--allow-empty", "--allow-empty-message", "--only", "--no-edit", "--amend", "--reset-author"). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// Sets the commit's author to the supplied value. Value is expected to be of the form 'Name ' -func (self *CommitCommands) SetAuthor(value string) error { - cmdArgs := NewGitCmd("commit"). - Arg("--allow-empty", "--allow-empty-message", "--only", "--no-edit", "--amend", "--author="+value). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// Add a commit's coauthor using Github/Gitlab Co-authored-by metadata. Value is expected to be of the form 'Name ' -func (self *CommitCommands) AddCoAuthor(hash string, author string) error { - message, err := self.GetCommitMessage(hash) - if err != nil { - return err - } - - message = AddCoAuthorToMessage(message, author) - - cmdArgs := NewGitCmd("commit"). - Arg("--allow-empty", "--amend", "--only", "-m", message). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func AddCoAuthorToMessage(message string, author string) string { - subject, body, _ := strings.Cut(message, "\n") - - return strings.TrimSpace(subject) + "\n\n" + AddCoAuthorToDescription(strings.TrimSpace(body), author) -} - -func AddCoAuthorToDescription(description string, author string) string { - if description != "" { - lines := strings.Split(description, "\n") - if strings.HasPrefix(lines[len(lines)-1], "Co-authored-by:") { - description += "\n" - } else { - description += "\n\n" - } - } - - return description + fmt.Sprintf("Co-authored-by: %s", author) -} - -// ResetToCommit reset to commit -func (self *CommitCommands) ResetToCommit(hash string, strength string, envVars []string) error { - cmdArgs := NewGitCmd("reset").Arg("--"+strength, hash).ToArgv() - - return self.cmd.New(cmdArgs). - // prevents git from prompting us for input which would freeze the program - // TODO: see if this is actually needed here - AddEnvVars("GIT_TERMINAL_PROMPT=0"). - AddEnvVars(envVars...). - Run() -} - -func (self *CommitCommands) CommitCmdObj(summary string, description string, forceSkipHooks bool) *oscommands.CmdObj { - messageArgs := self.commitMessageArgs(summary, description) - skipHookPrefix := self.UserConfig().Git.SkipHookPrefix - cmdArgs := NewGitCmd("commit"). - ArgIf(forceSkipHooks || (skipHookPrefix != "" && strings.HasPrefix(summary, skipHookPrefix)), "--no-verify"). - ArgIf(self.signoffFlag() != "", self.signoffFlag()). - Arg(messageArgs...). - ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *CommitCommands) RewordLastCommitInEditorCmdObj() *oscommands.CmdObj { - return self.cmd.New(NewGitCmd("commit").Arg("--allow-empty", "--amend", "--only").ToArgv()) -} - -func (self *CommitCommands) RewordLastCommitInEditorWithMessageFileCmdObj(tmpMessageFile string) *oscommands.CmdObj { - return self.cmd.New(NewGitCmd("commit"). - Arg("--allow-empty", "--amend", "--only", "--edit", "--file="+tmpMessageFile).ToArgv()) -} - -func (self *CommitCommands) CommitInEditorWithMessageFileCmdObj(tmpMessageFile string, forceSkipHooks bool) *oscommands.CmdObj { - return self.cmd.New(NewGitCmd("commit"). - ArgIf(forceSkipHooks, "--no-verify"). - Arg("--edit"). - Arg("--file="+tmpMessageFile). - ArgIf(self.signoffFlag() != "", self.signoffFlag()). - ToArgv()) -} - -// RewordLastCommit rewords the topmost commit with the given message -func (self *CommitCommands) RewordLastCommit(summary string, description string) *oscommands.CmdObj { - messageArgs := self.commitMessageArgs(summary, description) - - cmdArgs := NewGitCmd("commit"). - Arg("--allow-empty", "--amend", "--only"). - Arg(messageArgs...). - ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *CommitCommands) commitMessageArgs(summary string, description string) []string { - args := []string{"-m", summary} - - if description != "" { - args = append(args, "-m", description) - } - - return args -} - -// runs git commit without the -m argument meaning it will invoke the user's editor -func (self *CommitCommands) CommitEditorCmdObj() *oscommands.CmdObj { - cmdArgs := NewGitCmd("commit"). - ArgIf(self.signoffFlag() != "", self.signoffFlag()). - ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *CommitCommands) signoffFlag() string { - if self.UserConfig().Git.Commit.SignOff { - return "--signoff" - } - return "" -} - -func (self *CommitCommands) GetCommitMessage(commitHash string) (string, error) { - cmdArgs := NewGitCmd("log"). - Arg("--format=%B", "--max-count=1", commitHash). - Config("log.showsignature=false"). - ToArgv() - - message, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - return strings.ReplaceAll(strings.TrimSpace(message), "\r\n", "\n"), err -} - -func (self *CommitCommands) GetCommitSubject(commitHash string) (string, error) { - cmdArgs := NewGitCmd("log"). - Arg("--format=%s", "--max-count=1", commitHash). - Config("log.showsignature=false"). - ToArgv() - - subject, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - return strings.TrimSpace(subject), err -} - -func (self *CommitCommands) GetCommitDiff(commitHash string) (string, error) { - cmdArgs := NewGitCmd("show").Arg("--no-color", commitHash).ToArgv() - - diff, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - return diff, err -} - -type Author struct { - Name string - Email string -} - -func (self *CommitCommands) GetCommitAuthor(commitHash string) (Author, error) { - cmdArgs := NewGitCmd("show"). - Arg("--no-patch", "--pretty=format:%an%x00%ae", commitHash). - ToArgv() - - output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - if err != nil { - return Author{}, err - } - - split := strings.SplitN(strings.TrimSpace(output), "\x00", 2) - if len(split) < 2 { - return Author{}, errors.New("unexpected git output") - } - - author := Author{Name: split[0], Email: split[1]} - return author, err -} - -func (self *CommitCommands) GetCommitMessageFirstLine(hash string) (string, error) { - return self.GetCommitMessagesFirstLine([]string{hash}) -} - -func (self *CommitCommands) GetCommitMessagesFirstLine(hashes []string) (string, error) { - cmdArgs := NewGitCmd("show"). - Arg("--no-patch", "--pretty=format:%s"). - Arg(hashes...). - ToArgv() - - return self.cmd.New(cmdArgs).DontLog().RunWithOutput() -} - -// Example output: -// -// cd50c79ae Preserve the commit message correctly even if the description has blank lines -// 3ebba5f32 Add test demonstrating a bug with preserving the commit message -// 9a423c388 Remove unused function -func (self *CommitCommands) GetHashesAndCommitMessagesFirstLine(hashes []string) (string, error) { - cmdArgs := NewGitCmd("show"). - Arg("--no-patch", "--pretty=format:%h %s"). - Arg(hashes...). - ToArgv() - - return self.cmd.New(cmdArgs).DontLog().RunWithOutput() -} - -func (self *CommitCommands) GetCommitsOneline(hashes []string) (string, error) { - cmdArgs := NewGitCmd("show"). - Arg("--no-patch", "--oneline"). - Arg(hashes...). - ToArgv() - - return self.cmd.New(cmdArgs).DontLog().RunWithOutput() -} - -// AmendHead amends HEAD with whatever is staged in your working tree -func (self *CommitCommands) AmendHead() error { - return self.AmendHeadCmdObj().Run() -} - -func (self *CommitCommands) AmendHeadCmdObj() *oscommands.CmdObj { - cmdArgs := NewGitCmd("commit"). - Arg("--amend", "--no-edit", "--allow-empty", "--allow-empty-message"). - ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *CommitCommands) ShowCmdObj(hash string, filterPaths []string) *oscommands.CmdObj { - contextSize := self.UserConfig().Git.DiffContextSize - - extDiffCmd := self.pagerConfig.GetExternalDiffCommand() - useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig() - cmdArgs := NewGitCmd("show"). - Config("diff.noprefix=false"). - ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd). - ArgIfElse(extDiffCmd != "" || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff"). - Arg("--submodule"). - Arg("--color="+self.pagerConfig.GetColorArg()). - Arg(fmt.Sprintf("--unified=%d", contextSize)). - Arg("--stat"). - Arg("--decorate"). - Arg("-p"). - Arg(hash). - ArgIf(self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space"). - Arg(fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold)). - Arg("--"). - Arg(filterPaths...). - Dir(self.repoPaths.worktreePath). - ToArgv() - - return self.cmd.New(cmdArgs).DontLog() -} - -func (self *CommitCommands) ShowFileContentCmdObj(hash string, filePath string) *oscommands.CmdObj { - cmdArgs := NewGitCmd("show"). - Arg(fmt.Sprintf("%s:%s", hash, filePath)). - ToArgv() - return self.cmd.New(cmdArgs).DontLog() -} - -// Revert reverts the selected commits by hash. If isMerge is true, we'll pass -m 1 -// to say we want to revert the first parent of the merge commit, which is the one -// people want in 99.9% of cases. In current git versions we could unconditionally -// pass -m 1 even for non-merge commits, but older versions of git choke on it. -func (self *CommitCommands) Revert(hashes []string, isMerge bool) error { - cmdArgs := NewGitCmd("revert"). - ArgIf(isMerge, "-m", "1"). - Arg(hashes...). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// CreateFixupCommit creates a commit that fixes up a previous commit -func (self *CommitCommands) CreateFixupCommit(hash string) error { - cmdArgs := NewGitCmd("commit").Arg("--fixup=" + hash).ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// CreateAmendCommit creates a commit that changes the commit message of a previous commit -func (self *CommitCommands) CreateAmendCommit(originalSubject, newSubject, newDescription string, includeFileChanges bool) error { - description := newSubject - if newDescription != "" { - description += "\n\n" + newDescription - } - cmdArgs := NewGitCmd("commit"). - Arg("-m", "amend! "+originalSubject). - Arg("-m", description). - ArgIf(!includeFileChanges, "--only", "--allow-empty"). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// a value of 0 means the head commit, 1 is the parent commit, etc -func (self *CommitCommands) GetCommitMessageFromHistory(value int) (string, error) { - cmdArgs := NewGitCmd("log").Arg("-1", fmt.Sprintf("--skip=%d", value), "--pretty=%H"). - ToArgv() - - hash, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - formattedHash := strings.TrimSpace(hash) - if len(formattedHash) == 0 { - return "", ErrInvalidCommitIndex - } - return self.GetCommitMessage(formattedHash) -} diff --git a/pkg/commands/git_commands/commit_file_loader.go b/pkg/commands/git_commands/commit_file_loader.go deleted file mode 100644 index 33bd40e135f..00000000000 --- a/pkg/commands/git_commands/commit_file_loader.go +++ /dev/null @@ -1,61 +0,0 @@ -package git_commands - -import ( - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/samber/lo" -) - -type CommitFileLoader struct { - *common.Common - cmd oscommands.ICmdObjBuilder -} - -func NewCommitFileLoader(common *common.Common, cmd oscommands.ICmdObjBuilder) *CommitFileLoader { - return &CommitFileLoader{ - Common: common, - cmd: cmd, - } -} - -// GetFilesInDiff get the specified commit files -func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) { - cmdArgs := NewGitCmd("diff"). - Config("diff.noprefix=false"). - Arg("--submodule"). - Arg("--no-ext-diff"). - Arg("--name-status"). - Arg("-z"). - Arg("--no-renames"). - ArgIf(reverse, "-R"). - Arg(from). - Arg(to). - ToArgv() - - filenames, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - if err != nil { - return nil, err - } - - return getCommitFilesFromFilenames(filenames), nil -} - -// filenames string is something like "MM\x00file1\x00MU\x00file2\x00AA\x00file3\x00" -// so we need to split it by the null character and then map each status-name pair to a commit file -func getCommitFilesFromFilenames(filenames string) []*models.CommitFile { - lines := strings.Split(strings.TrimRight(filenames, "\x00"), "\x00") - if len(lines) == 1 { - return []*models.CommitFile{} - } - - // typical result looks like 'A my_file' meaning my_file was added - return lo.Map(lo.Chunk(lines, 2), func(chunk []string, _ int) *models.CommitFile { - return &models.CommitFile{ - ChangeStatus: chunk[0], - Path: chunk[1], - } - }) -} diff --git a/pkg/commands/git_commands/commit_file_loader_test.go b/pkg/commands/git_commands/commit_file_loader_test.go deleted file mode 100644 index ec91ec22ea6..00000000000 --- a/pkg/commands/git_commands/commit_file_loader_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/stretchr/testify/assert" -) - -func TestGetCommitFilesFromFilenames(t *testing.T) { - tests := []struct { - testName string - input string - output []*models.CommitFile - }{ - { - testName: "no files", - input: "", - output: []*models.CommitFile{}, - }, - { - testName: "one file", - input: "MM\x00Myfile\x00", - output: []*models.CommitFile{ - { - Path: "Myfile", - ChangeStatus: "MM", - }, - }, - }, - { - testName: "two files", - input: "MM\x00Myfile\x00M \x00MyOtherFile\x00", - output: []*models.CommitFile{ - { - Path: "Myfile", - ChangeStatus: "MM", - }, - { - Path: "MyOtherFile", - ChangeStatus: "M ", - }, - }, - }, - { - testName: "three files", - input: "MM\x00Myfile\x00M \x00MyOtherFile\x00 M\x00YetAnother\x00", - output: []*models.CommitFile{ - { - Path: "Myfile", - ChangeStatus: "MM", - }, - { - Path: "MyOtherFile", - ChangeStatus: "M ", - }, - { - Path: "YetAnother", - ChangeStatus: " M", - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.testName, func(t *testing.T) { - result := getCommitFilesFromFilenames(test.input) - assert.Equal(t, test.output, result) - }) - } -} diff --git a/pkg/commands/git_commands/commit_loader.go b/pkg/commands/git_commands/commit_loader.go deleted file mode 100644 index 72adf1b699c..00000000000 --- a/pkg/commands/git_commands/commit_loader.go +++ /dev/null @@ -1,606 +0,0 @@ -package git_commands - -import ( - "bytes" - "fmt" - "os" - "path/filepath" - "regexp" - "sort" - "strconv" - "strings" - "sync" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/stefanhaller/git-todo-parser/todo" -) - -// context: -// here we get the commits from git log but format them to show whether they're -// unpushed/pushed/merged into the base branch or not, or if they're yet to -// be processed as part of a rebase (these won't appear in git log but we -// grab them from the rebase-related files in the .git directory to show them - -// CommitLoader returns a list of Commit objects for the current repo -type CommitLoader struct { - *common.Common - cmd oscommands.ICmdObjBuilder - - getWorkingTreeState func() models.WorkingTreeState - readFile func(filename string) ([]byte, error) - walkFiles func(root string, fn filepath.WalkFunc) error - dotGitDir string - *GitCommon -} - -// making our dependencies explicit for the sake of easier testing -func NewCommitLoader( - cmn *common.Common, - cmd oscommands.ICmdObjBuilder, - getWorkingTreeState func() models.WorkingTreeState, - gitCommon *GitCommon, -) *CommitLoader { - return &CommitLoader{ - Common: cmn, - cmd: cmd, - getWorkingTreeState: getWorkingTreeState, - readFile: os.ReadFile, - walkFiles: filepath.Walk, - GitCommon: gitCommon, - } -} - -type GetCommitsOptions struct { - Limit bool - FilterPath string - FilterAuthor string - IncludeRebaseCommits bool - RefName string // e.g. "HEAD" or "my_branch" - RefForPushedStatus models.Ref // the ref to use for determining pushed/unpushed status - // determines if we show the whole git graph i.e. pass the '--all' flag - All bool - // If non-empty, show divergence from this ref (left-right log) - RefToShowDivergenceFrom string - MainBranches *MainBranches - HashPool *utils.StringPool -} - -// GetCommits obtains the commits of the current branch -func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit, error) { - commits := []*models.Commit{} - - if opts.IncludeRebaseCommits && opts.FilterPath == "" { - var err error - commits, err = self.MergeRebasingCommits(opts.HashPool, commits) - if err != nil { - return nil, err - } - } - - wg := sync.WaitGroup{} - - wg.Add(2) - - var logErr error - go utils.Safe(func() { - defer wg.Done() - - var realCommits []*models.Commit - realCommits, logErr = loadCommits(self.getLogCmd(opts), opts.FilterPath, func(line string) (*models.Commit, bool) { - return self.extractCommitFromLine(opts.HashPool, line, opts.RefToShowDivergenceFrom != ""), false - }) - if logErr == nil { - commits = append(commits, realCommits...) - } - }) - - var unmergedCommitHashes *set.Set[string] - var remoteUnmergedCommitHashes *set.Set[string] - mainBranches := opts.MainBranches.Get() - - go utils.Safe(func() { - defer wg.Done() - - if len(mainBranches) > 0 { - unmergedCommitHashes = self.getReachableHashes(opts.RefName, mainBranches) - if opts.RefToShowDivergenceFrom != "" { - remoteUnmergedCommitHashes = self.getReachableHashes(opts.RefToShowDivergenceFrom, mainBranches) - } - } - }) - - var unpushedCommitHashes *set.Set[string] - if opts.RefForPushedStatus != nil { - unpushedCommitHashes = self.getReachableHashes(opts.RefForPushedStatus.FullRefName(), - append([]string{opts.RefForPushedStatus.RefName() + "@{u}"}, mainBranches...)) - } - - wg.Wait() - - if logErr != nil { - return nil, logErr - } - - if len(commits) == 0 { - return commits, nil - } - - if opts.RefToShowDivergenceFrom != "" { - sort.SliceStable(commits, func(i, j int) bool { - // In the divergence view we want incoming commits to come first - return commits[i].Divergence > commits[j].Divergence - }) - - _, localSectionStart, found := lo.FindIndexOf(commits, func(commit *models.Commit) bool { - return commit.Divergence == models.DivergenceLeft - }) - if !found { - localSectionStart = len(commits) - } - - setCommitStatuses(unpushedCommitHashes, remoteUnmergedCommitHashes, commits[:localSectionStart]) - setCommitStatuses(unpushedCommitHashes, unmergedCommitHashes, commits[localSectionStart:]) - } else { - setCommitStatuses(unpushedCommitHashes, unmergedCommitHashes, commits) - } - - return commits, nil -} - -func (self *CommitLoader) MergeRebasingCommits(hashPool *utils.StringPool, commits []*models.Commit) ([]*models.Commit, error) { - // chances are we have as many commits as last time so we'll set the capacity to be the old length - result := make([]*models.Commit, 0, len(commits)) - for i, commit := range commits { - if !commit.IsTODO() { // removing the existing rebase commits so we can add the refreshed ones - result = append(result, commits[i:]...) - break - } - } - - workingTreeState := self.getWorkingTreeState() - addConflictedRebasingCommit := true - if workingTreeState.CherryPicking || workingTreeState.Reverting { - sequencerCommits, err := self.getHydratedSequencerCommits(hashPool, workingTreeState) - if err != nil { - return nil, err - } - result = append(sequencerCommits, result...) - addConflictedRebasingCommit = false - } - - if workingTreeState.Rebasing { - rebasingCommits, err := self.getHydratedRebasingCommits(hashPool, addConflictedRebasingCommit) - if err != nil { - return nil, err - } - if len(rebasingCommits) > 0 { - result = append(rebasingCommits, result...) - } - } - return result, nil -} - -// extractCommitFromLine takes a line from a git log and extracts the hash, message, date, and tag if present -// then puts them into a commit object -// example input: -// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag -func (self *CommitLoader) extractCommitFromLine(hashPool *utils.StringPool, line string, showDivergence bool) *models.Commit { - split := strings.SplitN(line, "\x00", 8) - - // Ensure we have the minimum required fields (at least 7 for basic functionality) - if len(split) < 7 { - self.Log.Warnf("Malformed git log line: expected at least 7 fields, got %d. Line: %s", len(split), line) - return nil - } - - hash := split[0] - unixTimestamp := split[1] - authorName := split[2] - authorEmail := split[3] - parentHashes := split[4] - divergence := models.DivergenceNone - if showDivergence { - divergence = lo.Ternary(split[5] == "<", models.DivergenceLeft, models.DivergenceRight) - } - extraInfo := strings.TrimSpace(split[6]) - - // message (and the \x00 before it) might not be present if extraInfo is extremely long - message := "" - if len(split) > 7 { - message = split[7] - } - - var tags []string - - if extraInfo != "" { - extraInfoFields := strings.Split(extraInfo, ",") - for _, extraInfoField := range extraInfoFields { - extraInfoField = strings.TrimSpace(extraInfoField) - re := regexp.MustCompile(`tag: (.+)`) - tagMatch := re.FindStringSubmatch(extraInfoField) - if len(tagMatch) > 1 { - tags = append(tags, tagMatch[1]) - } - } - - extraInfo = "(" + extraInfo + ")" - } - - unitTimestampInt, _ := strconv.Atoi(unixTimestamp) - - parents := []string{} - if len(parentHashes) > 0 { - parents = strings.Split(parentHashes, " ") - } - - return models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: hash, - Name: message, - Tags: tags, - ExtraInfo: extraInfo, - UnixTimestamp: int64(unitTimestampInt), - AuthorName: authorName, - AuthorEmail: authorEmail, - Parents: parents, - Divergence: divergence, - }) -} - -func (self *CommitLoader) getHydratedRebasingCommits(hashPool *utils.StringPool, addConflictingCommit bool) ([]*models.Commit, error) { - return self.getHydratedTodoCommits(hashPool, self.getRebasingCommits(hashPool, addConflictingCommit), false) -} - -func (self *CommitLoader) getHydratedSequencerCommits(hashPool *utils.StringPool, workingTreeState models.WorkingTreeState) ([]*models.Commit, error) { - commits := self.getSequencerCommits(hashPool) - if len(commits) > 0 { - // If we have any commits in .git/sequencer/todo, then the last one of - // those is the conflicting one. - commits[len(commits)-1].Status = models.StatusConflicted - } else { - // For single-commit cherry-picks and reverts, git apparently doesn't - // use the sequencer; in that case, CHERRY_PICK_HEAD or REVERT_HEAD is - // our conflicting commit, so synthesize it here. - conflicedCommit := self.getConflictedSequencerCommit(hashPool, workingTreeState) - if conflicedCommit != nil { - commits = append(commits, conflicedCommit) - } - } - - return self.getHydratedTodoCommits(hashPool, commits, true) -} - -func (self *CommitLoader) getHydratedTodoCommits(hashPool *utils.StringPool, todoCommits []*models.Commit, todoFileHasShortHashes bool) ([]*models.Commit, error) { - if len(todoCommits) == 0 { - return nil, nil - } - - commitHashes := lo.FilterMap(todoCommits, func(commit *models.Commit, _ int) (string, bool) { - return commit.Hash(), commit.Hash() != "" - }) - - // note that we're not filtering these as we do non-rebasing commits just because - // I suspect that will cause some damage - cmdObj := self.cmd.New( - NewGitCmd("show"). - Config("log.showSignature=false"). - Arg("--no-patch", "--oneline", "--abbrev=20", prettyFormat). - Arg(commitHashes...). - ToArgv(), - ).DontLog() - - fullCommits := map[string]*models.Commit{} - err := cmdObj.RunAndProcessLines(func(line string) (bool, error) { - if line == "" || line[0] != '+' { - return false, nil - } - commit := self.extractCommitFromLine(hashPool, line[1:], false) - fullCommits[commit.Hash()] = commit - return false, nil - }) - if err != nil { - return nil, err - } - - findFullCommit := lo.Ternary(todoFileHasShortHashes, - func(hash string) *models.Commit { - for s, c := range fullCommits { - if strings.HasPrefix(s, hash) { - return c - } - } - return nil - }, - func(hash string) *models.Commit { - return fullCommits[hash] - }) - - hydratedCommits := make([]*models.Commit, 0, len(todoCommits)) - for _, rebasingCommit := range todoCommits { - if rebasingCommit.Hash() == "" { - hydratedCommits = append(hydratedCommits, rebasingCommit) - } else if commit := findFullCommit(rebasingCommit.Hash()); commit != nil { - commit.Action = rebasingCommit.Action - commit.Status = rebasingCommit.Status - hydratedCommits = append(hydratedCommits, commit) - } - } - return hydratedCommits, nil -} - -// getRebasingCommits obtains the commits that we're in the process of rebasing - -// git-rebase-todo example: -// pick ac446ae94ee560bdb8d1d057278657b251aaef17 ac446ae -// pick afb893148791a2fbd8091aeb81deba4930c73031 afb8931 -func (self *CommitLoader) getRebasingCommits(hashPool *utils.StringPool, addConflictingCommit bool) []*models.Commit { - bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")) - if err != nil { - self.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error())) - // we assume an error means the file doesn't exist so we just return - return nil - } - - commits := []*models.Commit{} - - todos, err := todo.Parse(bytes.NewBuffer(bytesContent), self.config.GetCoreCommentChar()) - if err != nil { - self.Log.Error(fmt.Sprintf("error occurred while parsing git-rebase-todo file: %s", err.Error())) - return nil - } - - // See if the current commit couldn't be applied because it conflicted; if - // so, add a fake entry for it - if addConflictingCommit { - if conflictedCommit := self.getConflictedCommit(hashPool, todos); conflictedCommit != nil { - commits = append(commits, conflictedCommit) - } - } - - for _, t := range todos { - if t.Command == todo.UpdateRef { - t.Msg = t.Ref - } else if t.Command == todo.Exec { - t.Msg = t.ExecCommand - } else if t.Commit == "" { - // Command does not have a commit associated, skip - continue - } - commits = utils.Prepend(commits, models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: t.Commit, - Name: t.Msg, - Status: models.StatusRebasing, - Action: t.Command, - })) - } - - return commits -} - -func (self *CommitLoader) getConflictedCommit(hashPool *utils.StringPool, todos []todo.Todo) *models.Commit { - bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/done")) - if err != nil { - self.Log.Error(fmt.Sprintf("error occurred reading rebase-merge/done: %s", err.Error())) - return nil - } - - doneTodos, err := todo.Parse(bytes.NewBuffer(bytesContent), self.config.GetCoreCommentChar()) - if err != nil { - self.Log.Error(fmt.Sprintf("error occurred while parsing rebase-merge/done file: %s", err.Error())) - return nil - } - - amendFileExists, _ := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/amend")) - messageFileExists, _ := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/message")) - - return self.getConflictedCommitImpl(hashPool, todos, doneTodos, amendFileExists, messageFileExists) -} - -func (self *CommitLoader) getConflictedCommitImpl(hashPool *utils.StringPool, todos []todo.Todo, doneTodos []todo.Todo, amendFileExists bool, messageFileExists bool) *models.Commit { - // Should never be possible, but just to be safe: - if len(doneTodos) == 0 { - self.Log.Error("no done entries in rebase-merge/done file") - return nil - } - lastTodo := doneTodos[len(doneTodos)-1] - if lastTodo.Command == todo.Break || lastTodo.Command == todo.Exec || lastTodo.Command == todo.Reword { - return nil - } - - // In certain cases, git reschedules commands that failed. One example is if - // a patch would overwrite an untracked file (another one is an "exec" that - // failed, but we don't care about that here because we dealt with exec - // already above). To detect this, compare the last command of the "done" - // file against the first command of "git-rebase-todo"; if they are the - // same, the command was rescheduled. - if len(doneTodos) > 0 && len(todos) > 0 && doneTodos[len(doneTodos)-1] == todos[0] { - // Command was rescheduled, no need to display it - return nil - } - - // Older versions of git have a bug whereby, if a command is rescheduled, - // the last successful command is appended to the "done" file again. To - // detect this, we need to compare the second-to-last done entry against the - // first todo entry, and also compare the last done entry against the - // last-but-two done entry; this latter check is needed for the following - // case: - // pick A - // exec make test - // pick B - // exec make test - // If pick B fails with conflicts, then the "done" file contains - // pick A - // exec make test - // pick B - // and git-rebase-todo contains - // exec make test - // Without the last condition we would erroneously treat this as the exec - // command being rescheduled, so we wouldn't display our fake entry for - // "pick B". - if len(doneTodos) >= 3 && len(todos) > 0 && doneTodos[len(doneTodos)-2] == todos[0] && - doneTodos[len(doneTodos)-1] == doneTodos[len(doneTodos)-3] { - // Command was rescheduled, no need to display it - return nil - } - - if lastTodo.Command == todo.Edit { - if amendFileExists { - // Special case for "edit": if the "amend" file exists, the "edit" - // command was successful, otherwise it wasn't - return nil - } - - if !messageFileExists { - // As an additional check, see if the "message" file exists; if it - // doesn't, it must be because a multi-commit cherry-pick or revert - // was performed in the meantime, which deleted both the amend file - // and the message file. - return nil - } - } - - // I don't think this is ever possible, but again, just to be safe: - if lastTodo.Commit == "" { - self.Log.Error("last command in rebase-merge/done file doesn't have a commit") - return nil - } - - // Any other todo that has a commit associated with it must have failed with - // a conflict, otherwise we wouldn't have stopped the rebase: - return models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: lastTodo.Commit, - Action: lastTodo.Command, - Status: models.StatusConflicted, - }) -} - -func (self *CommitLoader) getSequencerCommits(hashPool *utils.StringPool) []*models.Commit { - bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "sequencer/todo")) - if err != nil { - self.Log.Error(fmt.Sprintf("error occurred reading sequencer/todo: %s", err.Error())) - // we assume an error means the file doesn't exist so we just return - return nil - } - - commits := []*models.Commit{} - - todos, err := todo.Parse(bytes.NewBuffer(bytesContent), self.config.GetCoreCommentChar()) - if err != nil { - self.Log.Error(fmt.Sprintf("error occurred while parsing sequencer/todo file: %s", err.Error())) - return nil - } - - for _, t := range todos { - if t.Commit == "" { - // Command does not have a commit associated, skip - continue - } - commits = utils.Prepend(commits, models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: t.Commit, - Name: t.Msg, - Status: models.StatusCherryPickingOrReverting, - Action: t.Command, - })) - } - - return commits -} - -func (self *CommitLoader) getConflictedSequencerCommit(hashPool *utils.StringPool, workingTreeState models.WorkingTreeState) *models.Commit { - var shaFile string - var action todo.TodoCommand - if workingTreeState.CherryPicking { - shaFile = "CHERRY_PICK_HEAD" - action = todo.Pick - } else if workingTreeState.Reverting { - shaFile = "REVERT_HEAD" - action = todo.Revert - } else { - return nil - } - bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), shaFile)) - if err != nil { - self.Log.Error(fmt.Sprintf("error occurred reading %s: %s", shaFile, err.Error())) - // we assume an error means the file doesn't exist so we just return - return nil - } - lines := strings.Split(string(bytesContent), "\n") - if len(lines) == 0 { - return nil - } - return models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: lines[0], - Status: models.StatusConflicted, - Action: action, - }) -} - -func setCommitStatuses(unpushedCommitHashes *set.Set[string], unmergedCommitHashes *set.Set[string], commits []*models.Commit) { - for i, commit := range commits { - if commit.IsTODO() { - continue - } - - if unmergedCommitHashes == nil || unmergedCommitHashes.Includes(commit.Hash()) { - if unpushedCommitHashes != nil && unpushedCommitHashes.Includes(commit.Hash()) { - commits[i].Status = models.StatusUnpushed - } else { - commits[i].Status = models.StatusPushed - } - } else { - commits[i].Status = models.StatusMerged - } - } -} - -func (self *CommitLoader) getReachableHashes(refName string, notRefNames []string) *set.Set[string] { - output, _, err := self.cmd.New( - NewGitCmd("rev-list"). - Arg(refName). - Arg(lo.Map(notRefNames, func(name string, _ int) string { - return "^" + name - })...). - ToArgv(), - ). - DontLog(). - RunWithOutputs() - if err != nil { - return set.New[string]() - } - - return set.NewFromSlice(utils.SplitLines(output)) -} - -// getLog gets the git log. -func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) *oscommands.CmdObj { - gitLogOrder := self.UserConfig().Git.Log.Order - - refSpec := opts.RefName - if opts.RefToShowDivergenceFrom != "" { - refSpec += "..." + opts.RefToShowDivergenceFrom - } - - cmdArgs := NewGitCmd("log"). - Arg(refSpec). - ArgIf(gitLogOrder != "default", "--"+gitLogOrder). - ArgIf(opts.All, "--all"). - Arg("--oneline"). - Arg(prettyFormat). - Arg("--abbrev=40"). - ArgIf(opts.FilterAuthor != "", "--author="+opts.FilterAuthor). - ArgIf(opts.Limit, "-300"). - ArgIf(opts.FilterPath != "", "--follow", "--name-status"). - Arg("--no-show-signature"). - ArgIf(opts.RefToShowDivergenceFrom != "", "--left-right"). - Arg("--"). - ArgIf(opts.FilterPath != "", opts.FilterPath). - ToArgv() - - return self.cmd.New(cmdArgs).DontLog() -} - -const prettyFormat = `--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s` diff --git a/pkg/commands/git_commands/commit_loader_test.go b/pkg/commands/git_commands/commit_loader_test.go deleted file mode 100644 index 7f9873b0b0b..00000000000 --- a/pkg/commands/git_commands/commit_loader_test.go +++ /dev/null @@ -1,796 +0,0 @@ -package git_commands - -import ( - "path/filepath" - "strings" - "testing" - - "github.com/go-errors/errors" - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/stefanhaller/git-todo-parser/todo" - "github.com/stretchr/testify/assert" -) - -var commitsOutput = strings.ReplaceAll(`+0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|b21997d6b4cbdf84b149|>|HEAD -> better-tests|better typing for rebase mode -+b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield|jessedduffield@gmail.com|e94e8fc5b6fab4cb755f|>|origin/better-tests|fix logging -+e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield|jessedduffield@gmail.com|d8084cd558925eb7c9c3|>|tag: 123, tag: 456|refactor -+d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffield@gmail.com|65f910ebd85283b5cce9|>||WIP -+65f910ebd85283b5cce9bf67d03d3f1a9ea3813a|1640821275|Jesse Duffield|jessedduffield@gmail.com|26c07b1ab33860a1a759|>||WIP -+26c07b1ab33860a1a7591a0638f9925ccf497ffa|1640750752|Jesse Duffield|jessedduffield@gmail.com|3d4470a6c072208722e5|>||WIP -+3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com|053a66a7be3da43aacdc|>||WIP -+053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com|985fe482e806b172aea4|>||refactoring the config struct`, "|", "\x00") - -var singleCommitOutput = strings.ReplaceAll(`+0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|b21997d6b4cbdf84b149|>|HEAD -> better-tests|better typing for rebase mode`, "|", "\x00") - -func TestGetCommits(t *testing.T) { - type scenario struct { - testName string - runner *oscommands.FakeCmdObjRunner - expectedCommitOpts []models.NewCommitOpts - expectedError error - logOrder string - opts GetCommitsOptions - mainBranches []string - } - - scenarios := []scenario{ - { - testName: "should return no commits if there are none", - logOrder: "topo-order", - opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: &models.Branch{Name: "mybranch"}, IncludeRebaseCommits: false}, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rev-list", "refs/heads/mybranch", "^mybranch@{u}"}, "", nil). - ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil), - - expectedCommitOpts: []models.NewCommitOpts{}, - expectedError: nil, - }, - { - testName: "should use proper upstream name for branch", - logOrder: "topo-order", - opts: GetCommitsOptions{RefName: "refs/heads/mybranch", RefForPushedStatus: &models.Branch{Name: "mybranch"}, IncludeRebaseCommits: false}, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rev-list", "refs/heads/mybranch", "^mybranch@{u}"}, "", nil). - ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil), - - expectedCommitOpts: []models.NewCommitOpts{}, - expectedError: nil, - }, - { - testName: "should return commits if they are present", - logOrder: "topo-order", - opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: &models.Branch{Name: "mybranch"}, IncludeRebaseCommits: false}, - mainBranches: []string{"master", "main", "develop"}, - runner: oscommands.NewFakeRunner(t). - // here it's seeing which commits are yet to be pushed - ExpectGitArgs([]string{"rev-list", "refs/heads/mybranch", "^mybranch@{u}", "^refs/remotes/origin/master", "^refs/remotes/origin/main"}, "0eea75e8c631fba6b58135697835d58ba4c18dbc\n", nil). - // here it's actually getting all the commits in a formatted form, one per line - ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil). - // here it's testing which of the configured main branches have an upstream - ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does - ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead - ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", nil). // yep, origin/main exists - ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead - ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/develop"}, "", errors.New("error")). // doesn't exist there, either, so it checks for a local branch - ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", errors.New("error")). // no local branch either - // here it's seeing which of our commits are not on any of the main branches yet - ExpectGitArgs([]string{"rev-list", "HEAD", "^refs/remotes/origin/master", "^refs/remotes/origin/main"}, - "0eea75e8c631fba6b58135697835d58ba4c18dbc\nb21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164\ne94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c\nd8084cd558925eb7c9c38afeed5725c21653ab90\n65f910ebd85283b5cce9bf67d03d3f1a9ea3813a\n", nil), - - expectedCommitOpts: []models.NewCommitOpts{ - { - Hash: "0eea75e8c631fba6b58135697835d58ba4c18dbc", - Name: "better typing for rebase mode", - Status: models.StatusUnpushed, - Action: models.ActionNone, - Tags: nil, - ExtraInfo: "(HEAD -> better-tests)", - AuthorName: "Jesse Duffield", - AuthorEmail: "jessedduffield@gmail.com", - UnixTimestamp: 1640826609, - Parents: []string{ - "b21997d6b4cbdf84b149", - }, - }, - { - Hash: "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", - Name: "fix logging", - Status: models.StatusPushed, - Action: models.ActionNone, - Tags: nil, - ExtraInfo: "(origin/better-tests)", - AuthorName: "Jesse Duffield", - AuthorEmail: "jessedduffield@gmail.com", - UnixTimestamp: 1640824515, - Parents: []string{ - "e94e8fc5b6fab4cb755f", - }, - }, - { - Hash: "e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c", - Name: "refactor", - Status: models.StatusPushed, - Action: models.ActionNone, - Tags: []string{"123", "456"}, - ExtraInfo: "(tag: 123, tag: 456)", - AuthorName: "Jesse Duffield", - AuthorEmail: "jessedduffield@gmail.com", - UnixTimestamp: 1640823749, - Parents: []string{ - "d8084cd558925eb7c9c3", - }, - }, - { - Hash: "d8084cd558925eb7c9c38afeed5725c21653ab90", - Name: "WIP", - Status: models.StatusPushed, - Action: models.ActionNone, - Tags: nil, - ExtraInfo: "", - AuthorName: "Jesse Duffield", - AuthorEmail: "jessedduffield@gmail.com", - UnixTimestamp: 1640821426, - Parents: []string{ - "65f910ebd85283b5cce9", - }, - }, - { - Hash: "65f910ebd85283b5cce9bf67d03d3f1a9ea3813a", - Name: "WIP", - Status: models.StatusPushed, - Action: models.ActionNone, - Tags: nil, - ExtraInfo: "", - AuthorName: "Jesse Duffield", - AuthorEmail: "jessedduffield@gmail.com", - UnixTimestamp: 1640821275, - Parents: []string{ - "26c07b1ab33860a1a759", - }, - }, - { - Hash: "26c07b1ab33860a1a7591a0638f9925ccf497ffa", - Name: "WIP", - Status: models.StatusMerged, - Action: models.ActionNone, - Tags: nil, - ExtraInfo: "", - AuthorName: "Jesse Duffield", - AuthorEmail: "jessedduffield@gmail.com", - UnixTimestamp: 1640750752, - Parents: []string{ - "3d4470a6c072208722e5", - }, - }, - { - Hash: "3d4470a6c072208722e5ae9a54bcb9634959a1c5", - Name: "WIP", - Status: models.StatusMerged, - Action: models.ActionNone, - Tags: nil, - ExtraInfo: "", - AuthorName: "Jesse Duffield", - AuthorEmail: "jessedduffield@gmail.com", - UnixTimestamp: 1640748818, - Parents: []string{ - "053a66a7be3da43aacdc", - }, - }, - { - Hash: "053a66a7be3da43aacdc7aa78e1fe757b82c4dd2", - Name: "refactoring the config struct", - Status: models.StatusMerged, - Action: models.ActionNone, - Tags: nil, - ExtraInfo: "", - AuthorName: "Jesse Duffield", - AuthorEmail: "jessedduffield@gmail.com", - UnixTimestamp: 1640739815, - Parents: []string{ - "985fe482e806b172aea4", - }, - }, - }, - expectedError: nil, - }, - { - testName: "should not call rev-list for mainBranches if none exist", - logOrder: "topo-order", - opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: &models.Branch{Name: "mybranch"}, IncludeRebaseCommits: false}, - mainBranches: []string{"master", "main"}, - runner: oscommands.NewFakeRunner(t). - // here it's seeing which commits are yet to be pushed - ExpectGitArgs([]string{"rev-list", "refs/heads/mybranch", "^mybranch@{u}"}, "0eea75e8c631fba6b58135697835d58ba4c18dbc\n", nil). - // here it's actually getting all the commits in a formatted form, one per line - ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil). - // here it's testing which of the configured main branches exist; neither does - ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "", errors.New("error")). - ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/master"}, "", errors.New("error")). - ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/master"}, "", errors.New("error")). - ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). - ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")). - ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")), - - expectedCommitOpts: []models.NewCommitOpts{ - { - Hash: "0eea75e8c631fba6b58135697835d58ba4c18dbc", - Name: "better typing for rebase mode", - Status: models.StatusUnpushed, - Action: models.ActionNone, - Tags: nil, - ExtraInfo: "(HEAD -> better-tests)", - AuthorName: "Jesse Duffield", - AuthorEmail: "jessedduffield@gmail.com", - UnixTimestamp: 1640826609, - Parents: []string{ - "b21997d6b4cbdf84b149", - }, - }, - }, - expectedError: nil, - }, - { - testName: "should call rev-list with all main branches that exist", - logOrder: "topo-order", - opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: &models.Branch{Name: "mybranch"}, IncludeRebaseCommits: false}, - mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"}, - runner: oscommands.NewFakeRunner(t). - // here it's seeing which commits are yet to be pushed - ExpectGitArgs([]string{"rev-list", "refs/heads/mybranch", "^mybranch@{u}", "^refs/remotes/origin/master", "^refs/remotes/origin/develop", "^refs/remotes/origin/1.0-hotfixes"}, "0eea75e8c631fba6b58135697835d58ba4c18dbc\n", nil). - // here it's actually getting all the commits in a formatted form, one per line - ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil). - // here it's testing which of the configured main branches exist - ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). - ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). - ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")). - ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")). - ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "refs/remotes/origin/develop", nil). - ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "1.0-hotfixes@{u}"}, "refs/remotes/origin/1.0-hotfixes", nil). - // here it's seeing which of our commits are not on any of the main branches yet - ExpectGitArgs([]string{"rev-list", "HEAD", "^refs/remotes/origin/master", "^refs/remotes/origin/develop", "^refs/remotes/origin/1.0-hotfixes"}, "0eea75e8c631fba6b58135697835d58ba4c18dbc\n", nil), - - expectedCommitOpts: []models.NewCommitOpts{ - { - Hash: "0eea75e8c631fba6b58135697835d58ba4c18dbc", - Name: "better typing for rebase mode", - Status: models.StatusUnpushed, - Action: models.ActionNone, - Tags: nil, - ExtraInfo: "(HEAD -> better-tests)", - AuthorName: "Jesse Duffield", - AuthorEmail: "jessedduffield@gmail.com", - UnixTimestamp: 1640826609, - Parents: []string{ - "b21997d6b4cbdf84b149", - }, - }, - }, - expectedError: nil, - }, - { - testName: "should not specify order if `log.order` is `default`", - logOrder: "default", - opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: &models.Branch{Name: "mybranch"}, IncludeRebaseCommits: false}, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rev-list", "refs/heads/mybranch", "^mybranch@{u}"}, "", nil). - ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil), - - expectedCommitOpts: []models.NewCommitOpts{}, - expectedError: nil, - }, - { - testName: "should set filter path", - logOrder: "default", - opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: &models.Branch{Name: "mybranch"}, FilterPath: "src"}, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rev-list", "refs/heads/mybranch", "^mybranch@{u}"}, "", nil). - ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s", "--abbrev=40", "--follow", "--name-status", "--no-show-signature", "--", "src"}, "", nil), - - expectedCommitOpts: []models.NewCommitOpts{}, - expectedError: nil, - }, - } - - for _, scenario := range scenarios { - t.Run(scenario.testName, func(t *testing.T) { - common := common.NewDummyCommon() - common.UserConfig().Git.Log.Order = scenario.logOrder - cmd := oscommands.NewDummyCmdObjBuilder(scenario.runner) - - builder := &CommitLoader{ - Common: common, - cmd: cmd, - getWorkingTreeState: func() models.WorkingTreeState { return models.WorkingTreeState{} }, - dotGitDir: ".git", - readFile: func(filename string) ([]byte, error) { - return []byte(""), nil - }, - walkFiles: func(root string, fn filepath.WalkFunc) error { - return nil - }, - } - - hashPool := &utils.StringPool{} - - common.UserConfig().Git.MainBranches = scenario.mainBranches - opts := scenario.opts - opts.MainBranches = NewMainBranches(common, cmd) - opts.HashPool = hashPool - commits, err := builder.GetCommits(opts) - - expectedCommits := lo.Map(scenario.expectedCommitOpts, - func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) }) - assert.Equal(t, expectedCommits, commits) - assert.Equal(t, scenario.expectedError, err) - - scenario.runner.CheckForMissingCalls() - }) - } -} - -func TestCommitLoader_getConflictedCommitImpl(t *testing.T) { - hashPool := &utils.StringPool{} - - scenarios := []struct { - testName string - todos []todo.Todo - doneTodos []todo.Todo - amendFileExists bool - messageFileExists bool - expectedResult *models.Commit - }{ - { - testName: "no done todos", - todos: []todo.Todo{}, - doneTodos: []todo.Todo{}, - amendFileExists: false, - expectedResult: nil, - }, - { - testName: "common case (conflict)", - todos: []todo.Todo{}, - doneTodos: []todo.Todo{ - { - Command: todo.Pick, - Commit: "deadbeef", - }, - { - Command: todo.Pick, - Commit: "fa1afe1", - }, - }, - amendFileExists: false, - expectedResult: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "fa1afe1", - Action: todo.Pick, - Status: models.StatusConflicted, - }), - }, - { - testName: "last command was 'break'", - todos: []todo.Todo{}, - doneTodos: []todo.Todo{ - {Command: todo.Break}, - }, - amendFileExists: false, - expectedResult: nil, - }, - { - testName: "last command was 'exec'", - todos: []todo.Todo{}, - doneTodos: []todo.Todo{ - { - Command: todo.Exec, - ExecCommand: "make test", - }, - }, - amendFileExists: false, - expectedResult: nil, - }, - { - testName: "last command was 'reword'", - todos: []todo.Todo{}, - doneTodos: []todo.Todo{ - {Command: todo.Reword}, - }, - amendFileExists: false, - expectedResult: nil, - }, - { - testName: "'pick' was rescheduled", - todos: []todo.Todo{ - { - Command: todo.Pick, - Commit: "fa1afe1", - }, - }, - doneTodos: []todo.Todo{ - { - Command: todo.Pick, - Commit: "fa1afe1", - }, - }, - amendFileExists: false, - expectedResult: nil, - }, - { - testName: "'pick' was rescheduled, buggy git version", - todos: []todo.Todo{ - { - Command: todo.Pick, - Commit: "fa1afe1", - }, - }, - doneTodos: []todo.Todo{ - { - Command: todo.Pick, - Commit: "deadbeaf", - }, - { - Command: todo.Pick, - Commit: "fa1afe1", - }, - { - Command: todo.Pick, - Commit: "deadbeaf", - }, - }, - amendFileExists: false, - expectedResult: nil, - }, - { - testName: "conflicting 'pick' after 'exec'", - todos: []todo.Todo{ - { - Command: todo.Exec, - ExecCommand: "make test", - }, - }, - doneTodos: []todo.Todo{ - { - Command: todo.Pick, - Commit: "deadbeaf", - }, - { - Command: todo.Exec, - ExecCommand: "make test", - }, - { - Command: todo.Pick, - Commit: "fa1afe1", - }, - }, - amendFileExists: false, - expectedResult: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "fa1afe1", - Action: todo.Pick, - Status: models.StatusConflicted, - }), - }, - { - testName: "'edit' with amend file", - todos: []todo.Todo{}, - doneTodos: []todo.Todo{ - { - Command: todo.Edit, - Commit: "fa1afe1", - }, - }, - amendFileExists: true, - expectedResult: nil, - }, - { - testName: "'edit' without amend file but message file", - todos: []todo.Todo{}, - doneTodos: []todo.Todo{ - { - Command: todo.Edit, - Commit: "fa1afe1", - }, - }, - amendFileExists: false, - messageFileExists: true, - expectedResult: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "fa1afe1", - Action: todo.Edit, - Status: models.StatusConflicted, - }), - }, - { - testName: "'edit' without amend and without message file", - todos: []todo.Todo{}, - doneTodos: []todo.Todo{ - { - Command: todo.Edit, - Commit: "fa1afe1", - }, - }, - amendFileExists: false, - messageFileExists: false, - expectedResult: nil, - }, - } - for _, scenario := range scenarios { - t.Run(scenario.testName, func(t *testing.T) { - common := common.NewDummyCommon() - - builder := &CommitLoader{ - Common: common, - cmd: oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)), - getWorkingTreeState: func() models.WorkingTreeState { return models.WorkingTreeState{Rebasing: true} }, - dotGitDir: ".git", - readFile: func(filename string) ([]byte, error) { - return []byte(""), nil - }, - walkFiles: func(root string, fn filepath.WalkFunc) error { - return nil - }, - } - - hash := builder.getConflictedCommitImpl(hashPool, scenario.todos, scenario.doneTodos, scenario.amendFileExists, scenario.messageFileExists) - assert.Equal(t, scenario.expectedResult, hash) - }) - } -} - -func TestCommitLoader_setCommitStatuses(t *testing.T) { - type scenario struct { - testName string - commitOpts []models.NewCommitOpts - unmergedCommits []string - unpushedCommits []string - expectedCommitOpts []models.NewCommitOpts - } - - scenarios := []scenario{ - { - testName: "basic", - commitOpts: []models.NewCommitOpts{ - {Hash: "12345", Name: "1", Action: models.ActionNone}, - {Hash: "67890", Name: "2", Action: models.ActionNone}, - {Hash: "abcde", Name: "3", Action: models.ActionNone}, - }, - unmergedCommits: []string{"12345"}, - expectedCommitOpts: []models.NewCommitOpts{ - {Hash: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusPushed}, - {Hash: "67890", Name: "2", Action: models.ActionNone, Status: models.StatusMerged}, - {Hash: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusMerged}, - }, - }, - { - testName: "with update-ref", - commitOpts: []models.NewCommitOpts{ - {Hash: "12345", Name: "1", Action: models.ActionNone}, - {Hash: "", Name: "", Action: todo.UpdateRef}, - {Hash: "abcde", Name: "3", Action: models.ActionNone}, - }, - unmergedCommits: []string{"12345", "abcde"}, - unpushedCommits: []string{"12345"}, - expectedCommitOpts: []models.NewCommitOpts{ - {Hash: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed}, - {Hash: "", Name: "", Action: todo.UpdateRef, Status: models.StatusNone}, - {Hash: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed}, - }, - }, - } - - for _, scenario := range scenarios { - t.Run(scenario.testName, func(t *testing.T) { - hashPool := &utils.StringPool{} - - commits := lo.Map(scenario.commitOpts, - func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) }) - setCommitStatuses(set.NewFromSlice(scenario.unpushedCommits), set.NewFromSlice(scenario.unmergedCommits), commits) - expectedCommits := lo.Map(scenario.expectedCommitOpts, - func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) }) - assert.Equal(t, expectedCommits, commits) - }) - } -} - -func TestCommitLoader_extractCommitFromLine(t *testing.T) { - common := common.NewDummyCommon() - hashPool := &utils.StringPool{} - - loader := &CommitLoader{ - Common: common, - } - - scenarios := []struct { - testName string - line string - showDivergence bool - expectedCommit *models.Commit - }{ - { - testName: "normal commit line with all fields", - line: "0eea75e8c631fba6b58135697835d58ba4c18dbc\x001640826609\x00Jesse Duffield\x00jessedduffield@gmail.com\x00b21997d6b4cbdf84b149\x00>\x00HEAD -> better-tests\x00better typing for rebase mode", - showDivergence: false, - expectedCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "0eea75e8c631fba6b58135697835d58ba4c18dbc", - Name: "better typing for rebase mode", - Tags: nil, - ExtraInfo: "(HEAD -> better-tests)", - UnixTimestamp: 1640826609, - AuthorName: "Jesse Duffield", - AuthorEmail: "jessedduffield@gmail.com", - Parents: []string{"b21997d6b4cbdf84b149"}, - Divergence: models.DivergenceNone, - }), - }, - { - testName: "normal commit line with left divergence", - line: "hash123\x001234567890\x00John Doe\x00john@example.com\x00parent1 parent2\x00<\x00origin/main\x00commit message", - showDivergence: true, - expectedCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "hash123", - Name: "commit message", - Tags: nil, - ExtraInfo: "(origin/main)", - UnixTimestamp: 1234567890, - AuthorName: "John Doe", - AuthorEmail: "john@example.com", - Parents: []string{"parent1", "parent2"}, - Divergence: models.DivergenceLeft, - }), - }, - { - testName: "commit line with tags in extraInfo", - line: "abc123\x001640000000\x00Jane Smith\x00jane@example.com\x00parenthash\x00>\x00tag: v1.0, tag: release\x00tagged release", - showDivergence: true, - expectedCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "abc123", - Name: "tagged release", - Tags: []string{"v1.0", "release"}, - ExtraInfo: "(tag: v1.0, tag: release)", - UnixTimestamp: 1640000000, - AuthorName: "Jane Smith", - AuthorEmail: "jane@example.com", - Parents: []string{"parenthash"}, - Divergence: models.DivergenceRight, - }), - }, - { - testName: "commit line with empty extraInfo", - line: "def456\x001640000000\x00Bob Wilson\x00bob@example.com\x00parenthash\x00>\x00\x00simple commit", - showDivergence: true, - expectedCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "def456", - Name: "simple commit", - Tags: nil, - ExtraInfo: "", - UnixTimestamp: 1640000000, - AuthorName: "Bob Wilson", - AuthorEmail: "bob@example.com", - Parents: []string{"parenthash"}, - Divergence: models.DivergenceRight, - }), - }, - { - testName: "commit line with no parents (root commit)", - line: "root123\x001640000000\x00Alice Cooper\x00alice@example.com\x00\x00>\x00\x00initial commit", - showDivergence: true, - expectedCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "root123", - Name: "initial commit", - Tags: nil, - ExtraInfo: "", - UnixTimestamp: 1640000000, - AuthorName: "Alice Cooper", - AuthorEmail: "alice@example.com", - Parents: nil, - Divergence: models.DivergenceRight, - }), - }, - { - testName: "malformed line with only 3 fields", - line: "hash\x00timestamp\x00author", - showDivergence: false, - expectedCommit: nil, - }, - { - testName: "malformed line with only 4 fields", - line: "hash\x00timestamp\x00author\x00email", - showDivergence: false, - expectedCommit: nil, - }, - { - testName: "malformed line with only 5 fields", - line: "hash\x00timestamp\x00author\x00email\x00parents", - showDivergence: false, - expectedCommit: nil, - }, - { - testName: "malformed line with only 6 fields", - line: "hash\x00timestamp\x00author\x00email\x00parents\x00<", - showDivergence: true, - expectedCommit: nil, - }, - { - testName: "minimal valid line with 7 fields (no message)", - line: "hash\x00timestamp\x00author\x00email\x00parents\x00>\x00extraInfo", - showDivergence: true, - expectedCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "hash", - Name: "", - Tags: nil, - ExtraInfo: "(extraInfo)", - UnixTimestamp: 0, - AuthorName: "author", - AuthorEmail: "email", - Parents: []string{"parents"}, - Divergence: models.DivergenceRight, - }), - }, - { - testName: "minimal valid line with 7 fields (empty extraInfo)", - line: "hash\x00timestamp\x00author\x00email\x00parents\x00>\x00", - showDivergence: true, - expectedCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "hash", - Name: "", - Tags: nil, - ExtraInfo: "", - UnixTimestamp: 0, - AuthorName: "author", - AuthorEmail: "email", - Parents: []string{"parents"}, - Divergence: models.DivergenceRight, - }), - }, - { - testName: "valid line with 8 fields (complete)", - line: "hash\x00timestamp\x00author\x00email\x00parents\x00<\x00extraInfo\x00message", - showDivergence: true, - expectedCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "hash", - Name: "message", - Tags: nil, - ExtraInfo: "(extraInfo)", - UnixTimestamp: 0, - AuthorName: "author", - AuthorEmail: "email", - Parents: []string{"parents"}, - Divergence: models.DivergenceLeft, - }), - }, - { - testName: "empty line", - line: "", - showDivergence: false, - expectedCommit: nil, - }, - { - testName: "line with special characters in commit message", - line: "special123\x001640000000\x00Dev User\x00dev@example.com\x00parenthash\x00>\x00\x00fix: handle \x00 null bytes and 'quotes'", - showDivergence: true, - expectedCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "special123", - Name: "fix: handle \x00 null bytes and 'quotes'", - Tags: nil, - ExtraInfo: "", - UnixTimestamp: 1640000000, - AuthorName: "Dev User", - AuthorEmail: "dev@example.com", - Parents: []string{"parenthash"}, - Divergence: models.DivergenceRight, - }), - }, - } - - for _, scenario := range scenarios { - t.Run(scenario.testName, func(t *testing.T) { - result := loader.extractCommitFromLine(hashPool, scenario.line, scenario.showDivergence) - if scenario.expectedCommit == nil { - assert.Nil(t, result) - } else { - assert.Equal(t, scenario.expectedCommit, result) - } - }) - } -} diff --git a/pkg/commands/git_commands/commit_loading_shared.go b/pkg/commands/git_commands/commit_loading_shared.go deleted file mode 100644 index f1a14698e27..00000000000 --- a/pkg/commands/git_commands/commit_loading_shared.go +++ /dev/null @@ -1,74 +0,0 @@ -package git_commands - -import ( - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/samber/lo" -) - -func loadCommits( - cmd *oscommands.CmdObj, - filterPath string, - parseLogLine func(string) (*models.Commit, bool), -) ([]*models.Commit, error) { - commits := []*models.Commit{} - - var commit *models.Commit - var filterPaths []string - // A string pool that stores interned strings to reduce memory usage - pool := make(map[string]string) - - finishLastCommit := func() { - if commit != nil { - // Only set the filter paths if we have one that is not contained in the original - // filter path. When filtering on a directory, all file paths will start with that - // directory, so we needn't bother storing the individual paths. Likewise, if we - // filter on a file and the file path hasn't changed, we needn't store it either. - // Only if a file has been moved or renamed do we need to store the paths, but then - // we need them all so that we can properly render a diff for the rename. - if lo.SomeBy(filterPaths, func(path string) bool { - return !strings.HasPrefix(path, filterPath) - }) { - commit.FilterPaths = lo.Map(filterPaths, func(path string, _ int) string { - if v, ok := pool[path]; ok { - return v - } - pool[path] = path - return path - }) - } - commits = append(commits, commit) - commit = nil - filterPaths = nil - } - } - err := cmd.RunAndProcessLines(func(line string) (bool, error) { - if line == "" { - return false, nil - } - - if line[0] == '+' { - finishLastCommit() - var stop bool - commit, stop = parseLogLine(line[1:]) - if stop { - commit = nil - return true, nil - } - } else if commit != nil && filterPath != "" { - // We are filtering by path, and this line is the output of the --name-status flag - fields := strings.Split(line, "\t") - // We don't bother looking at the first field (it will be 'A', 'M', 'R072' or a bunch of others). - // All we care about is the path(s), and there will be one for 'M' and 'A', and two for 'R' or 'C', - // in which case we want them both so that we can show the diff between the two. - if len(fields) > 1 { - filterPaths = append(filterPaths, fields[1:]...) - } - } - return false, nil - }) - finishLastCommit() - return commits, err -} diff --git a/pkg/commands/git_commands/commit_test.go b/pkg/commands/git_commands/commit_test.go deleted file mode 100644 index 6ea914c6402..00000000000 --- a/pkg/commands/git_commands/commit_test.go +++ /dev/null @@ -1,493 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/stretchr/testify/assert" -) - -func TestCommitRewordCommit(t *testing.T) { - type scenario struct { - testName string - runner *oscommands.FakeCmdObjRunner - summary string - description string - } - scenarios := []scenario{ - { - "Single line reword", - oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil), - "test", - "", - }, - { - "Multi line reword", - oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test", "-m", "line 2\nline 3"}, "", nil), - "test", - "line 2\nline 3", - }, - } - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildCommitCommands(commonDeps{runner: s.runner}) - - assert.NoError(t, instance.RewordLastCommit(s.summary, s.description).Run()) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestCommitResetToCommit(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"reset", "--hard", "78976bc"}, "", nil) - - instance := buildCommitCommands(commonDeps{runner: runner}) - - assert.NoError(t, instance.ResetToCommit("78976bc", "hard", []string{})) - runner.CheckForMissingCalls() -} - -func TestCommitCommitCmdObj(t *testing.T) { - type scenario struct { - testName string - summary string - forceSkipHooks bool - description string - configSignoff bool - configSkipHookPrefix string - expectedArgs []string - } - - scenarios := []scenario{ - { - testName: "Commit", - summary: "test", - forceSkipHooks: false, - configSignoff: false, - configSkipHookPrefix: "", - expectedArgs: []string{"commit", "-m", "test"}, - }, - { - testName: "Commit with --no-verify flag < only prefix", - summary: "WIP: test", - forceSkipHooks: false, - configSignoff: false, - configSkipHookPrefix: "WIP", - expectedArgs: []string{"commit", "--no-verify", "-m", "WIP: test"}, - }, - { - testName: "Commit with --no-verify flag < skip flag and prefix", - summary: "WIP: test", - forceSkipHooks: true, - configSignoff: false, - configSkipHookPrefix: "WIP", - expectedArgs: []string{"commit", "--no-verify", "-m", "WIP: test"}, - }, - { - testName: "Commit with --no-verify flag < skip flag no prefix", - summary: "test", - forceSkipHooks: true, - configSignoff: false, - configSkipHookPrefix: "WIP", - expectedArgs: []string{"commit", "--no-verify", "-m", "test"}, - }, - { - testName: "Commit with multiline message", - summary: "line1", - forceSkipHooks: false, - description: "line2", - configSignoff: false, - configSkipHookPrefix: "", - expectedArgs: []string{"commit", "-m", "line1", "-m", "line2"}, - }, - { - testName: "Commit with signoff", - summary: "test", - forceSkipHooks: false, - configSignoff: true, - configSkipHookPrefix: "", - expectedArgs: []string{"commit", "--signoff", "-m", "test"}, - }, - { - testName: "Commit with signoff and no-verify", - summary: "WIP: test", - forceSkipHooks: true, - configSignoff: true, - configSkipHookPrefix: "WIP", - expectedArgs: []string{"commit", "--no-verify", "--signoff", "-m", "WIP: test"}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - userConfig := config.GetDefaultConfig() - userConfig.Git.Commit.SignOff = s.configSignoff - userConfig.Git.SkipHookPrefix = s.configSkipHookPrefix - - runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expectedArgs, "", nil) - instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner}) - - assert.NoError(t, instance.CommitCmdObj(s.summary, s.description, s.forceSkipHooks).Run()) - runner.CheckForMissingCalls() - }) - } -} - -func TestCommitCommitEditorCmdObj(t *testing.T) { - type scenario struct { - testName string - configSignoff bool - expected []string - } - - scenarios := []scenario{ - { - testName: "Commit using editor", - configSignoff: false, - expected: []string{"commit"}, - }, - { - testName: "Commit with --signoff", - configSignoff: true, - expected: []string{"commit", "--signoff"}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - userConfig := config.GetDefaultConfig() - userConfig.Git.Commit.SignOff = s.configSignoff - - runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil) - instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner}) - - assert.NoError(t, instance.CommitEditorCmdObj().Run()) - runner.CheckForMissingCalls() - }) - } -} - -func TestCommitCreateFixupCommit(t *testing.T) { - type scenario struct { - testName string - hash string - runner *oscommands.FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - testName: "valid case", - hash: "12345", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"commit", "--fixup=12345"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildCommitCommands(commonDeps{runner: s.runner}) - s.test(instance.CreateFixupCommit(s.hash)) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestCommitCreateAmendCommit(t *testing.T) { - type scenario struct { - testName string - originalSubject string - newSubject string - newDescription string - includeFileChanges bool - runner *oscommands.FakeCmdObjRunner - } - - scenarios := []scenario{ - { - testName: "subject only", - originalSubject: "original subject", - newSubject: "new subject", - newDescription: "", - includeFileChanges: true, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"commit", "-m", "amend! original subject", "-m", "new subject"}, "", nil), - }, - { - testName: "subject and description", - originalSubject: "original subject", - newSubject: "new subject", - newDescription: "new description", - includeFileChanges: true, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"commit", "-m", "amend! original subject", "-m", "new subject\n\nnew description"}, "", nil), - }, - { - testName: "without file changes", - originalSubject: "original subject", - newSubject: "new subject", - newDescription: "", - includeFileChanges: false, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"commit", "-m", "amend! original subject", "-m", "new subject", "--only", "--allow-empty"}, "", nil), - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildCommitCommands(commonDeps{runner: s.runner}) - err := instance.CreateAmendCommit(s.originalSubject, s.newSubject, s.newDescription, s.includeFileChanges) - assert.NoError(t, err) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestCommitShowCmdObj(t *testing.T) { - type scenario struct { - testName string - filterPaths []string - contextSize uint64 - similarityThreshold int - ignoreWhitespace bool - pagerConfig *config.PagingConfig - expected []string - } - - scenarios := []scenario{ - { - testName: "Default case without filter path", - filterPaths: []string{}, - contextSize: 3, - similarityThreshold: 50, - ignoreWhitespace: false, - pagerConfig: nil, - expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"}, - }, - { - testName: "Default case with filter path", - filterPaths: []string{"file.txt"}, - contextSize: 3, - similarityThreshold: 50, - ignoreWhitespace: false, - pagerConfig: nil, - expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--", "file.txt"}, - }, - { - testName: "Show diff with custom context size", - filterPaths: []string{}, - contextSize: 77, - similarityThreshold: 50, - ignoreWhitespace: false, - pagerConfig: nil, - expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"}, - }, - { - testName: "Show diff with custom similarity threshold", - filterPaths: []string{}, - contextSize: 3, - similarityThreshold: 33, - ignoreWhitespace: false, - pagerConfig: nil, - expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=33%", "--"}, - }, - { - testName: "Show diff, ignoring whitespace", - filterPaths: []string{}, - contextSize: 77, - similarityThreshold: 50, - ignoreWhitespace: true, - pagerConfig: nil, - expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space", "--find-renames=50%", "--"}, - }, - { - testName: "Show diff with external diff command", - filterPaths: []string{}, - contextSize: 3, - similarityThreshold: 50, - ignoreWhitespace: false, - pagerConfig: &config.PagingConfig{ExternalDiffCommand: "difft --color=always"}, - expected: []string{"-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "-c", "diff.noprefix=false", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"}, - }, - { - testName: "Show diff using git's external diff config", - filterPaths: []string{}, - contextSize: 3, - similarityThreshold: 50, - ignoreWhitespace: false, - pagerConfig: &config.PagingConfig{UseExternalDiffGitConfig: true}, - expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - userConfig := config.GetDefaultConfig() - if s.pagerConfig != nil { - userConfig.Git.Pagers = []config.PagingConfig{*s.pagerConfig} - } - userConfig.Git.IgnoreWhitespaceInDiffView = s.ignoreWhitespace - userConfig.Git.DiffContextSize = s.contextSize - userConfig.Git.RenameSimilarityThreshold = s.similarityThreshold - - runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil) - repoPaths := RepoPaths{ - worktreePath: "/path/to/worktree", - } - instance := buildCommitCommands(commonDeps{userConfig: userConfig, appState: &config.AppState{}, runner: runner, repoPaths: &repoPaths}) - - assert.NoError(t, instance.ShowCmdObj("1234567890", s.filterPaths).Run()) - runner.CheckForMissingCalls() - }) - } -} - -func TestGetCommitMsg(t *testing.T) { - type scenario struct { - testName string - input string - expectedOutput string - } - scenarios := []scenario{ - { - "empty", - ``, - ``, - }, - { - "no line breaks (single line)", - `use generics to DRY up context code`, - `use generics to DRY up context code`, - }, - { - "with line breaks", - `Merge pull request #1750 from mark2185/fix-issue-template - -'git-rev parse' should be 'git rev-parse'`, - `Merge pull request #1750 from mark2185/fix-issue-template - -'git-rev parse' should be 'git rev-parse'`, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildCommitCommands(commonDeps{ - runner: oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"-c", "log.showsignature=false", "log", "--format=%B", "--max-count=1", "deadbeef"}, s.input, nil), - }) - - output, err := instance.GetCommitMessage("deadbeef") - - assert.NoError(t, err) - - assert.Equal(t, s.expectedOutput, output) - }) - } -} - -func TestGetCommitMessageFromHistory(t *testing.T) { - type scenario struct { - testName string - runner *oscommands.FakeCmdObjRunner - test func(string, error) - } - scenarios := []scenario{ - { - "Empty message", - oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "", nil).ExpectGitArgs([]string{"-c", "log.showsignature=false", "log", "--format=%B", "--max-count=1"}, "", nil), - func(output string, err error) { - assert.Error(t, err) - }, - }, - { - "Default case to retrieve a commit in history", - oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "hash3 \n", nil).ExpectGitArgs([]string{"-c", "log.showsignature=false", "log", "--format=%B", "--max-count=1", "hash3"}, `use generics to DRY up context code`, nil), - func(output string, err error) { - assert.NoError(t, err) - assert.Equal(t, "use generics to DRY up context code", output) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildCommitCommands(commonDeps{runner: s.runner}) - - output, err := instance.GetCommitMessageFromHistory(2) - - s.test(output, err) - }) - } -} - -func TestAddCoAuthorToMessage(t *testing.T) { - scenarios := []struct { - name string - message string - expectedResult string - }{ - { - // This never happens, I think it isn't possible to create a commit - // with an empty message. Just including it for completeness. - name: "Empty message", - message: "", - expectedResult: "\n\nCo-authored-by: John Doe ", - }, - { - name: "Just a subject, no body", - message: "Subject", - expectedResult: "Subject\n\nCo-authored-by: John Doe ", - }, - { - name: "Subject and body", - message: "Subject\n\nBody", - expectedResult: "Subject\n\nBody\n\nCo-authored-by: John Doe ", - }, - { - name: "Body already ending with a Co-authored-by line", - message: "Subject\n\nBody\n\nCo-authored-by: Jane Smith ", - expectedResult: "Subject\n\nBody\n\nCo-authored-by: Jane Smith \nCo-authored-by: John Doe ", - }, - } - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - result := AddCoAuthorToMessage(s.message, "John Doe ") - assert.Equal(t, s.expectedResult, result) - }) - } -} - -func TestAddCoAuthorToDescription(t *testing.T) { - scenarios := []struct { - name string - description string - expectedResult string - }{ - { - name: "Empty description", - description: "", - expectedResult: "Co-authored-by: John Doe ", - }, - { - name: "Non-empty description", - description: "Body", - expectedResult: "Body\n\nCo-authored-by: John Doe ", - }, - { - name: "Description already ending with a Co-authored-by line", - description: "Body\n\nCo-authored-by: Jane Smith ", - expectedResult: "Body\n\nCo-authored-by: Jane Smith \nCo-authored-by: John Doe ", - }, - } - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - result := AddCoAuthorToDescription(s.description, "John Doe ") - assert.Equal(t, s.expectedResult, result) - }) - } -} diff --git a/pkg/commands/git_commands/common.go b/pkg/commands/git_commands/common.go deleted file mode 100644 index 28dd78e8bb7..00000000000 --- a/pkg/commands/git_commands/common.go +++ /dev/null @@ -1,41 +0,0 @@ -package git_commands - -import ( - gogit "github.com/jesseduffield/go-git/v5" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" -) - -type GitCommon struct { - *common.Common - version *GitVersion - cmd oscommands.ICmdObjBuilder - os *oscommands.OSCommand - repoPaths *RepoPaths - repo *gogit.Repository - config *ConfigCommands - pagerConfig *config.PagerConfig -} - -func NewGitCommon( - cmn *common.Common, - version *GitVersion, - cmd oscommands.ICmdObjBuilder, - osCommand *oscommands.OSCommand, - repoPaths *RepoPaths, - repo *gogit.Repository, - config *ConfigCommands, - pagerConfig *config.PagerConfig, -) *GitCommon { - return &GitCommon{ - Common: cmn, - version: version, - cmd: cmd, - os: osCommand, - repoPaths: repoPaths, - repo: repo, - config: config, - pagerConfig: pagerConfig, - } -} diff --git a/pkg/commands/git_commands/config.go b/pkg/commands/git_commands/config.go deleted file mode 100644 index a9fd4e14772..00000000000 --- a/pkg/commands/git_commands/config.go +++ /dev/null @@ -1,106 +0,0 @@ -package git_commands - -import ( - gogit "github.com/jesseduffield/go-git/v5" - "github.com/jesseduffield/go-git/v5/config" - "github.com/jesseduffield/lazygit/pkg/commands/git_config" - "github.com/jesseduffield/lazygit/pkg/common" -) - -type ConfigCommands struct { - *common.Common - - gitConfig git_config.IGitConfig - repo *gogit.Repository -} - -func NewConfigCommands( - common *common.Common, - gitConfig git_config.IGitConfig, - repo *gogit.Repository, -) *ConfigCommands { - return &ConfigCommands{ - Common: common, - gitConfig: gitConfig, - repo: repo, - } -} - -type GpgConfigKey string - -const ( - CommitGpgSign GpgConfigKey = "commit.gpgSign" - TagGpgSign GpgConfigKey = "tag.gpgSign" -) - -// NeedsGpgSubprocess tells us whether the user has gpg enabled for the specified action type -// and needs a subprocess because they have a process where they manually -// enter their password every time a GPG action is taken -func (self *ConfigCommands) NeedsGpgSubprocess(key GpgConfigKey) bool { - overrideGpg := self.UserConfig().Git.OverrideGpg - if overrideGpg { - return false - } - - return self.gitConfig.GetBool(string(key)) -} - -func (self *ConfigCommands) NeedsGpgSubprocessForCommit() bool { - return self.NeedsGpgSubprocess(CommitGpgSign) -} - -func (self *ConfigCommands) GetGpgTagSign() bool { - return self.gitConfig.GetBool(string(TagGpgSign)) -} - -func (self *ConfigCommands) GetCoreEditor() string { - return self.gitConfig.Get("core.editor") -} - -// GetRemoteURL returns current repo remote url -func (self *ConfigCommands) GetRemoteURL() string { - return self.gitConfig.Get("remote.origin.url") -} - -func (self *ConfigCommands) GetShowUntrackedFiles() string { - return self.gitConfig.Get("status.showUntrackedFiles") -} - -// this determines whether the user has configured to push to the remote branch of the same name as the current or not -func (self *ConfigCommands) GetPushToCurrent() bool { - return self.gitConfig.Get("push.default") == "current" -} - -// returns the repo's branches as specified in the git config -func (self *ConfigCommands) Branches() (map[string]*config.Branch, error) { - conf, err := self.repo.Config() - if err != nil { - return nil, err - } - - return conf.Branches, nil -} - -func (self *ConfigCommands) GetGitFlowPrefixes() string { - return self.gitConfig.GetGeneral("--local --get-regexp gitflow.prefix") -} - -func (self *ConfigCommands) GetCoreCommentChar() byte { - if commentCharStr := self.gitConfig.Get("core.commentChar"); len(commentCharStr) == 1 { - return commentCharStr[0] - } - - return '#' -} - -func (self *ConfigCommands) GetRebaseUpdateRefs() bool { - return self.gitConfig.GetBool("rebase.updateRefs") -} - -func (self *ConfigCommands) GetMergeFF() string { - return self.gitConfig.Get("merge.ff") -} - -func (self *ConfigCommands) DropConfigCache() { - self.gitConfig.DropCache() -} diff --git a/pkg/commands/git_commands/custom.go b/pkg/commands/git_commands/custom.go deleted file mode 100644 index c58a2baa748..00000000000 --- a/pkg/commands/git_commands/custom.go +++ /dev/null @@ -1,40 +0,0 @@ -package git_commands - -import ( - "fmt" - "strings" - - "github.com/mgutz/str" -) - -type CustomCommands struct { - *GitCommon -} - -func NewCustomCommands(gitCommon *GitCommon) *CustomCommands { - return &CustomCommands{ - GitCommon: gitCommon, - } -} - -// Only to be used for the sake of running custom commands specified by the user. -// If you want to run a new command, try finding a place for it in one of the neighbouring -// files, or creating a new BlahCommands struct to hold it. -func (self *CustomCommands) RunWithOutput(cmdStr string) (string, error) { - return self.cmd.New(str.ToArgv(cmdStr)).RunWithOutput() -} - -// A function that can be used as a "runCommand" entry in the template.FuncMap of templates. -func (self *CustomCommands) TemplateFunctionRunCommand(cmdStr string) (string, error) { - output, err := self.RunWithOutput(cmdStr) - if err != nil { - return "", err - } - output = strings.TrimRight(output, "\r\n") - - if strings.Contains(output, "\r\n") { - return "", fmt.Errorf("command output contains newlines: %s", output) - } - - return output, nil -} diff --git a/pkg/commands/git_commands/deps_test.go b/pkg/commands/git_commands/deps_test.go deleted file mode 100644 index 3c4bd5a144d..00000000000 --- a/pkg/commands/git_commands/deps_test.go +++ /dev/null @@ -1,169 +0,0 @@ -package git_commands - -import ( - "os" - - "github.com/go-errors/errors" - gogit "github.com/jesseduffield/go-git/v5" - "github.com/jesseduffield/lazygit/pkg/commands/git_config" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/spf13/afero" -) - -type commonDeps struct { - runner *oscommands.FakeCmdObjRunner - userConfig *config.UserConfig - appState *config.AppState - gitVersion *GitVersion - gitConfig *git_config.FakeGitConfig - getenv func(string) string - removeFile func(string) error - common *common.Common - cmd *oscommands.CmdObjBuilder - fs afero.Fs - repoPaths *RepoPaths -} - -func buildGitCommon(deps commonDeps) *GitCommon { - gitCommon := &GitCommon{} - - gitCommon.Common = deps.common - if gitCommon.Common == nil { - gitCommon.Common = common.NewDummyCommonWithUserConfigAndAppState(deps.userConfig, deps.appState) - } - - if deps.fs != nil { - gitCommon.Fs = deps.fs - } - - if deps.repoPaths != nil { - gitCommon.repoPaths = deps.repoPaths - } else { - gitCommon.repoPaths = MockRepoPaths(".git") - } - - runner := deps.runner - if runner == nil { - runner = oscommands.NewFakeRunner(nil) - } - - cmd := deps.cmd - // gotta check deps.cmd because it's not an interface type and an interface value of nil is not considered to be nil - if cmd == nil { - cmd = oscommands.NewDummyCmdObjBuilder(runner) - } - gitCommon.cmd = cmd - - gitCommon.Common.SetUserConfig(deps.userConfig) - if gitCommon.Common.UserConfig() == nil { - gitCommon.Common.SetUserConfig(config.GetDefaultConfig()) - } - - gitCommon.pagerConfig = config.NewPagerConfig(func() *config.UserConfig { - return gitCommon.Common.UserConfig() - }) - - gitCommon.version = deps.gitVersion - if gitCommon.version == nil { - gitCommon.version = &GitVersion{2, 0, 0, ""} - } - - gitConfig := deps.gitConfig - if gitConfig == nil { - gitConfig = git_config.NewFakeGitConfig(nil) - } - - gitCommon.repo = buildRepo() - gitCommon.config = NewConfigCommands(gitCommon.Common, gitConfig, gitCommon.repo) - - getenv := deps.getenv - if getenv == nil { - getenv = func(string) string { return "" } - } - - removeFile := deps.removeFile - if removeFile == nil { - removeFile = func(string) error { return errors.New("unexpected call to removeFile") } - } - - gitCommon.os = oscommands.NewDummyOSCommandWithDeps(oscommands.OSCommandDeps{ - Common: gitCommon.Common, - GetenvFn: getenv, - Cmd: cmd, - RemoveFileFn: removeFile, - TempDir: os.TempDir(), - }) - - return gitCommon -} - -func buildRepo() *gogit.Repository { - // TODO: think of a way to actually mock this out - var repo *gogit.Repository - return repo -} - -func buildFileLoader(gitCommon *GitCommon) *FileLoader { - return NewFileLoader(gitCommon, gitCommon.cmd, gitCommon.config) -} - -func buildSubmoduleCommands(deps commonDeps) *SubmoduleCommands { - gitCommon := buildGitCommon(deps) - - return NewSubmoduleCommands(gitCommon) -} - -func buildCommitCommands(deps commonDeps) *CommitCommands { - gitCommon := buildGitCommon(deps) - return NewCommitCommands(gitCommon) -} - -func buildWorkingTreeCommands(deps commonDeps) *WorkingTreeCommands { - gitCommon := buildGitCommon(deps) - submoduleCommands := buildSubmoduleCommands(deps) - fileLoader := buildFileLoader(gitCommon) - - return NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader) -} - -func buildStashCommands(deps commonDeps) *StashCommands { - gitCommon := buildGitCommon(deps) - fileLoader := buildFileLoader(gitCommon) - workingTreeCommands := buildWorkingTreeCommands(deps) - - return NewStashCommands(gitCommon, fileLoader, workingTreeCommands) -} - -func buildRebaseCommands(deps commonDeps) *RebaseCommands { - gitCommon := buildGitCommon(deps) - workingTreeCommands := buildWorkingTreeCommands(deps) - commitCommands := buildCommitCommands(deps) - - return NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands) -} - -func buildSyncCommands(deps commonDeps) *SyncCommands { - gitCommon := buildGitCommon(deps) - - return NewSyncCommands(gitCommon) -} - -func buildFileCommands(deps commonDeps) *FileCommands { - gitCommon := buildGitCommon(deps) - - return NewFileCommands(gitCommon) -} - -func buildBranchCommands(deps commonDeps) *BranchCommands { - gitCommon := buildGitCommon(deps) - - return NewBranchCommands(gitCommon) -} - -func buildFlowCommands(deps commonDeps) *FlowCommands { - gitCommon := buildGitCommon(deps) - - return NewFlowCommands(gitCommon) -} diff --git a/pkg/commands/git_commands/diff.go b/pkg/commands/git_commands/diff.go deleted file mode 100644 index 4feeb4d7ff9..00000000000 --- a/pkg/commands/git_commands/diff.go +++ /dev/null @@ -1,106 +0,0 @@ -package git_commands - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -type DiffCommands struct { - *GitCommon -} - -func NewDiffCommands(gitCommon *GitCommon) *DiffCommands { - return &DiffCommands{ - GitCommon: gitCommon, - } -} - -// This is for generating diffs to be shown in the UI (e.g. rendering a range -// diff to the main view). It uses a custom pager if one is configured. -func (self *DiffCommands) DiffCmdObj(diffArgs []string) *oscommands.CmdObj { - extDiffCmd := self.pagerConfig.GetExternalDiffCommand() - useExtDiff := extDiffCmd != "" - useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig() - ignoreWhitespace := self.UserConfig().Git.IgnoreWhitespaceInDiffView - - return self.cmd.New( - NewGitCmd("diff"). - Config("diff.noprefix=false"). - ConfigIf(useExtDiff, "diff.external="+extDiffCmd). - ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff"). - Arg("--submodule"). - Arg(fmt.Sprintf("--color=%s", self.pagerConfig.GetColorArg())). - ArgIf(ignoreWhitespace, "--ignore-all-space"). - Arg(fmt.Sprintf("--unified=%d", self.UserConfig().Git.DiffContextSize)). - Arg(diffArgs...). - Dir(self.repoPaths.worktreePath). - ToArgv(), - ) -} - -// This is a basic generic diff command that can be used for any diff operation -// (e.g. copying a diff to the clipboard). It will not use a custom pager, and -// does not use user configs such as ignore whitespace. -// If you want to diff specific refs (one or two), you need to add them yourself -// in additionalArgs; it is recommended to also pass `--` after that. If you -// want to restrict the diff to specific paths, pass them in additionalArgs -// after the `--`. -func (self *DiffCommands) GetDiff(staged bool, additionalArgs ...string) (string, error) { - return self.cmd.New( - NewGitCmd("diff"). - Config("diff.noprefix=false"). - Arg("--no-ext-diff", "--no-color"). - ArgIf(staged, "--staged"). - Dir(self.repoPaths.worktreePath). - Arg(additionalArgs...). - ToArgv(), - ).RunWithOutput() -} - -type DiffToolCmdOptions struct { - // The path to show a diff for. Pass "." for the entire repo. - Filepath string - - // The commit against which to show the diff. Leave empty to show a diff of - // the working copy. - FromCommit string - - // The commit to diff against FromCommit. Leave empty to diff the working - // copy against FromCommit. Leave both FromCommit and ToCommit empty to show - // the diff of the unstaged working copy changes against the index if Staged - // is false, or the staged changes against HEAD if Staged is true. - ToCommit string - - // Whether to reverse the left and right sides of the diff. - Reverse bool - - // Whether the given Filepath is a directory. We'll pass --dir-diff to - // git-difftool in that case. - IsDirectory bool - - // Whether to show the staged or the unstaged changes. Must be false if both - // FromCommit and ToCommit are non-empty. - Staged bool -} - -func (self *DiffCommands) OpenDiffToolCmdObj(opts DiffToolCmdOptions) *oscommands.CmdObj { - return self.cmd.New(NewGitCmd("difftool"). - Arg("--no-prompt"). - ArgIf(opts.IsDirectory, "--dir-diff"). - ArgIf(opts.Staged, "--cached"). - ArgIf(opts.FromCommit != "", opts.FromCommit). - ArgIf(opts.ToCommit != "", opts.ToCommit). - ArgIf(opts.Reverse, "-R"). - Arg("--", opts.Filepath). - ToArgv()) -} - -func (self *DiffCommands) DiffIndexCmdObj(diffArgs ...string) *oscommands.CmdObj { - return self.cmd.New( - NewGitCmd("diff-index"). - Config("diff.noprefix=false"). - Arg("--submodule", "--no-ext-diff", "--no-color", "--patch"). - Arg(diffArgs...).ToArgv(), - ) -} diff --git a/pkg/commands/git_commands/file.go b/pkg/commands/git_commands/file.go deleted file mode 100644 index 1b5f5b2ddd9..00000000000 --- a/pkg/commands/git_commands/file.go +++ /dev/null @@ -1,100 +0,0 @@ -package git_commands - -import ( - "os" - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type FileCommands struct { - *GitCommon -} - -func NewFileCommands(gitCommon *GitCommon) *FileCommands { - return &FileCommands{ - GitCommon: gitCommon, - } -} - -// Cat obtains the content of a file -func (self *FileCommands) Cat(fileName string) (string, error) { - buf, err := os.ReadFile(fileName) - if err != nil { - return "", nil - } - return string(buf), nil -} - -func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) { - template, suspend := config.GetEditTemplate(self.os.Platform.Shell, &self.UserConfig().OS, self.guessDefaultEditor) - quotedFilenames := lo.Map(filenames, func(filename string, _ int) string { return self.cmd.Quote(filename) }) - - templateValues := map[string]string{ - "filename": strings.Join(quotedFilenames, " "), - } - - cmdStr := utils.ResolvePlaceholderString(template, templateValues) - return cmdStr, suspend -} - -func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (string, bool) { - template, suspend := config.GetEditAtLineTemplate(self.os.Platform.Shell, &self.UserConfig().OS, self.guessDefaultEditor) - - templateValues := map[string]string{ - "filename": self.cmd.Quote(filename), - "line": strconv.Itoa(lineNumber), - } - - cmdStr := utils.ResolvePlaceholderString(template, templateValues) - return cmdStr, suspend -} - -func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber int) string { - template := config.GetEditAtLineAndWaitTemplate(self.os.Platform.Shell, &self.UserConfig().OS, self.guessDefaultEditor) - - templateValues := map[string]string{ - "filename": self.cmd.Quote(filename), - "line": strconv.Itoa(lineNumber), - } - - cmdStr := utils.ResolvePlaceholderString(template, templateValues) - return cmdStr -} - -func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) (string, bool) { - template, suspend := config.GetOpenDirInEditorTemplate(self.os.Platform.Shell, &self.UserConfig().OS, self.guessDefaultEditor) - - templateValues := map[string]string{ - "dir": self.cmd.Quote(path), - } - - cmdStr := utils.ResolvePlaceholderString(template, templateValues) - return cmdStr, suspend -} - -func (self *FileCommands) guessDefaultEditor() string { - // Try to query a few places where editors get configured - editor := self.config.GetCoreEditor() - if editor == "" { - editor = self.os.Getenv("GIT_EDITOR") - } - if editor == "" { - editor = self.os.Getenv("VISUAL") - } - if editor == "" { - editor = self.os.Getenv("EDITOR") - } - - if editor != "" { - // At this point, it might be more than just the name of the editor; - // e.g. it might be "code -w" or "vim -u myvim.rc". So assume that - // everything up to the first space is the editor name. - editor = strings.Split(editor, " ")[0] - } - - return editor -} diff --git a/pkg/commands/git_commands/file_loader.go b/pkg/commands/git_commands/file_loader.go deleted file mode 100644 index 36ab8ef6742..00000000000 --- a/pkg/commands/git_commands/file_loader.go +++ /dev/null @@ -1,215 +0,0 @@ -package git_commands - -import ( - "fmt" - "path/filepath" - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -type FileLoaderConfig interface { - GetShowUntrackedFiles() string -} - -type FileLoader struct { - *GitCommon - cmd oscommands.ICmdObjBuilder - config FileLoaderConfig - getFileType func(string) string -} - -func NewFileLoader(gitCommon *GitCommon, cmd oscommands.ICmdObjBuilder, config FileLoaderConfig) *FileLoader { - return &FileLoader{ - GitCommon: gitCommon, - cmd: cmd, - getFileType: oscommands.FileType, - config: config, - } -} - -type GetStatusFileOptions struct { - NoRenames bool - // If true, we'll show untracked files even if the user has set the config to hide them. - // This is useful for users with bare repos for dotfiles who default to hiding untracked files, - // but want to occasionally see them to `git add` a new file. - ForceShowUntracked bool -} - -func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File { - // check if config wants us ignoring untracked files - untrackedFilesSetting := self.config.GetShowUntrackedFiles() - - if opts.ForceShowUntracked || untrackedFilesSetting == "" { - untrackedFilesSetting = "all" - } - untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting) - - statuses, err := self.gitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg}) - if err != nil { - self.Log.Error(err) - } - files := []*models.File{} - - fileDiffs := map[string]FileDiff{} - if self.GitCommon.Common.UserConfig().Gui.ShowNumstatInFilesView { - fileDiffs, err = self.getFileDiffs() - if err != nil { - self.Log.Error(err) - } - } - - for _, status := range statuses { - if strings.HasPrefix(status.StatusString, "warning") { - self.Log.Warningf("warning when calling git status: %s", status.StatusString) - continue - } - - file := &models.File{ - Path: status.Path, - PreviousPath: status.PreviousPath, - DisplayString: status.StatusString, - } - - if diff, ok := fileDiffs[status.Path]; ok { - file.LinesAdded = diff.LinesAdded - file.LinesDeleted = diff.LinesDeleted - } - - models.SetStatusFields(file, status.Change) - files = append(files, file) - } - - // Go through the files to see if any of these files are actually worktrees - // so that we can render them correctly - worktreePaths := linkedWortkreePaths(self.Fs, self.repoPaths.RepoGitDirPath()) - for _, file := range files { - for _, worktreePath := range worktreePaths { - absFilePath, err := filepath.Abs(file.Path) - if err != nil { - self.Log.Error(err) - continue - } - if absFilePath == worktreePath { - file.IsWorktree = true - // `git status` renders this worktree as a folder with a trailing slash but we'll represent it as a singular worktree - // If we include the slash, it will be rendered as a folder with a null file inside. - file.Path = strings.TrimSuffix(file.Path, "/") - break - } - } - } - - return files -} - -type FileDiff struct { - LinesAdded int - LinesDeleted int -} - -func (self *FileLoader) getFileDiffs() (map[string]FileDiff, error) { - diffs, err := self.gitDiffNumStat() - if err != nil { - return nil, err - } - - splitLines := strings.Split(diffs, "\x00") - - fileDiffs := map[string]FileDiff{} - for _, line := range splitLines { - splitLine := strings.Split(line, "\t") - if len(splitLine) != 3 { - continue - } - - linesAdded, err := strconv.Atoi(splitLine[0]) - if err != nil { - continue - } - linesDeleted, err := strconv.Atoi(splitLine[1]) - if err != nil { - continue - } - - fileName := splitLine[2] - fileDiffs[fileName] = FileDiff{ - LinesAdded: linesAdded, - LinesDeleted: linesDeleted, - } - } - - return fileDiffs, nil -} - -// GitStatus returns the file status of the repo -type GitStatusOptions struct { - NoRenames bool - UntrackedFilesArg string -} - -type FileStatus struct { - StatusString string - Change string // ??, MM, AM, ... - Path string - PreviousPath string -} - -func (self *FileLoader) gitDiffNumStat() (string, error) { - return self.cmd.New( - NewGitCmd("diff"). - Arg("--numstat"). - Arg("-z"). - Arg("HEAD"). - ToArgv(), - ).DontLog().RunWithOutput() -} - -func (self *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) { - cmdArgs := NewGitCmd("status"). - Arg(opts.UntrackedFilesArg). - Arg("--porcelain"). - Arg("-z"). - ArgIfElse( - opts.NoRenames, - "--no-renames", - fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold), - ). - ToArgv() - - statusLines, _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutputs() - if err != nil { - return []FileStatus{}, err - } - - splitLines := strings.Split(statusLines, "\x00") - response := []FileStatus{} - - for i := 0; i < len(splitLines); i++ { - original := splitLines[i] - - if len(original) < 3 { - continue - } - - status := FileStatus{ - StatusString: original, - Change: original[:2], - Path: original[3:], - PreviousPath: "", - } - - if strings.HasPrefix(status.Change, "R") || strings.HasPrefix(status.Change, "C") { - // if a line starts with 'R' (rename) or 'C' (copy) then the next line is the original file. - status.PreviousPath = splitLines[i+1] - status.StatusString = fmt.Sprintf("%s %s -> %s", status.Change, status.PreviousPath, status.Path) - i++ - } - - response = append(response, status) - } - - return response, nil -} diff --git a/pkg/commands/git_commands/file_loader_test.go b/pkg/commands/git_commands/file_loader_test.go deleted file mode 100644 index ec1f502f1a8..00000000000 --- a/pkg/commands/git_commands/file_loader_test.go +++ /dev/null @@ -1,260 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/stretchr/testify/assert" -) - -func TestFileGetStatusFiles(t *testing.T) { - type scenario struct { - testName string - similarityThreshold int - runner oscommands.ICmdObjRunner - showNumstatInFilesView bool - expectedFiles []*models.File - } - - scenarios := []scenario{ - { - testName: "No files found", - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "", nil), - expectedFiles: []*models.File{}, - }, - { - testName: "Several files found", - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, - "MM file1.txt\x00A file3.txt\x00AM file2.txt\x00?? file4.txt\x00UU file5.txt", - nil, - ). - ExpectGitArgs([]string{"diff", "--numstat", "-z", "HEAD"}, - "4\t1\tfile1.txt\x001\t0\tfile2.txt\x002\t2\tfile3.txt\x000\t2\tfile4.txt\x002\t2\tfile5.txt", - nil, - ), - showNumstatInFilesView: true, - expectedFiles: []*models.File{ - { - Path: "file1.txt", - HasStagedChanges: true, - HasUnstagedChanges: true, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "MM file1.txt", - ShortStatus: "MM", - LinesAdded: 4, - LinesDeleted: 1, - }, - { - Path: "file3.txt", - HasStagedChanges: true, - HasUnstagedChanges: false, - Tracked: false, - Added: true, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "A file3.txt", - ShortStatus: "A ", - LinesAdded: 2, - LinesDeleted: 2, - }, - { - Path: "file2.txt", - HasStagedChanges: true, - HasUnstagedChanges: true, - Tracked: false, - Added: true, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "AM file2.txt", - ShortStatus: "AM", - LinesAdded: 1, - LinesDeleted: 0, - }, - { - Path: "file4.txt", - HasStagedChanges: false, - HasUnstagedChanges: true, - Tracked: false, - Added: true, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "?? file4.txt", - ShortStatus: "??", - LinesAdded: 0, - LinesDeleted: 2, - }, - { - Path: "file5.txt", - HasStagedChanges: false, - HasUnstagedChanges: true, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: true, - HasInlineMergeConflicts: true, - DisplayString: "UU file5.txt", - ShortStatus: "UU", - LinesAdded: 2, - LinesDeleted: 2, - }, - }, - }, - { - testName: "File with new line char", - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "MM a\nb.txt", nil), - expectedFiles: []*models.File{ - { - Path: "a\nb.txt", - HasStagedChanges: true, - HasUnstagedChanges: true, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "MM a\nb.txt", - ShortStatus: "MM", - }, - }, - }, - { - testName: "Renamed files", - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, - "R after1.txt\x00before1.txt\x00RM after2.txt\x00before2.txt", - nil, - ), - expectedFiles: []*models.File{ - { - Path: "after1.txt", - PreviousPath: "before1.txt", - HasStagedChanges: true, - HasUnstagedChanges: false, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "R before1.txt -> after1.txt", - ShortStatus: "R ", - }, - { - Path: "after2.txt", - PreviousPath: "before2.txt", - HasStagedChanges: true, - HasUnstagedChanges: true, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "RM before2.txt -> after2.txt", - ShortStatus: "RM", - }, - }, - }, - { - testName: "File with arrow in name", - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, - `?? a -> b.txt`, - nil, - ), - expectedFiles: []*models.File{ - { - Path: "a -> b.txt", - HasStagedChanges: false, - HasUnstagedChanges: true, - Tracked: false, - Added: true, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "?? a -> b.txt", - ShortStatus: "??", - }, - }, - }, - { - testName: "Copied files", - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, - "C copy1.txt\x00original.txt\x00CM copy2.txt\x00original.txt", - nil, - ), - expectedFiles: []*models.File{ - { - Path: "copy1.txt", - PreviousPath: "original.txt", - HasStagedChanges: true, - HasUnstagedChanges: false, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "C original.txt -> copy1.txt", - ShortStatus: "C ", - }, - { - Path: "copy2.txt", - PreviousPath: "original.txt", - HasStagedChanges: true, - HasUnstagedChanges: true, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "CM original.txt -> copy2.txt", - ShortStatus: "CM", - }, - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - cmd := oscommands.NewDummyCmdObjBuilder(s.runner) - - userConfig := &config.UserConfig{} - userConfig.Gui.ShowNumstatInFilesView = s.showNumstatInFilesView - userConfig.Git.RenameSimilarityThreshold = s.similarityThreshold - - loader := &FileLoader{ - GitCommon: buildGitCommon(commonDeps{appState: &config.AppState{}, userConfig: userConfig}), - cmd: cmd, - config: &FakeFileLoaderConfig{showUntrackedFiles: "yes"}, - getFileType: func(string) string { return "file" }, - } - - assert.EqualValues(t, s.expectedFiles, loader.GetStatusFiles(GetStatusFileOptions{})) - }) - } -} - -type FakeFileLoaderConfig struct { - showUntrackedFiles string -} - -func (self *FakeFileLoaderConfig) GetShowUntrackedFiles() string { - return self.showUntrackedFiles -} diff --git a/pkg/commands/git_commands/file_test.go b/pkg/commands/git_commands/file_test.go deleted file mode 100644 index 5dd3d133c4f..00000000000 --- a/pkg/commands/git_commands/file_test.go +++ /dev/null @@ -1,216 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/git_config" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/stretchr/testify/assert" -) - -func TestEditFilesCmd(t *testing.T) { - type scenario struct { - filenames []string - osConfig config.OSConfig - expectedCmdStr string - suspend bool - } - - scenarios := []scenario{ - { - filenames: []string{"test"}, - osConfig: config.OSConfig{}, - expectedCmdStr: `vim -- "test"`, - suspend: true, - }, - { - filenames: []string{"test"}, - osConfig: config.OSConfig{ - Edit: "nano {{filename}}", - }, - expectedCmdStr: `nano "test"`, - suspend: true, - }, - { - filenames: []string{"file/with space"}, - osConfig: config.OSConfig{ - EditPreset: "sublime", - }, - expectedCmdStr: `subl -- "file/with space"`, - suspend: false, - }, - { - filenames: []string{"multiple", "files"}, - osConfig: config.OSConfig{ - EditPreset: "sublime", - }, - expectedCmdStr: `subl -- "multiple" "files"`, - suspend: false, - }, - } - - for _, s := range scenarios { - userConfig := config.GetDefaultConfig() - userConfig.OS = s.osConfig - - instance := buildFileCommands(commonDeps{ - userConfig: userConfig, - }) - - cmdStr, suspend := instance.GetEditCmdStr(s.filenames) - assert.Equal(t, s.expectedCmdStr, cmdStr) - assert.Equal(t, s.suspend, suspend) - } -} - -func TestEditFileAtLineCmd(t *testing.T) { - type scenario struct { - filename string - lineNumber int - osConfig config.OSConfig - expectedCmdStr string - suspend bool - } - - scenarios := []scenario{ - { - filename: "test", - lineNumber: 42, - osConfig: config.OSConfig{}, - expectedCmdStr: `vim +42 -- "test"`, - suspend: true, - }, - { - filename: "test", - lineNumber: 35, - osConfig: config.OSConfig{ - EditAtLine: "nano +{{line}} {{filename}}", - }, - expectedCmdStr: `nano +35 "test"`, - suspend: true, - }, - { - filename: "file/with space", - lineNumber: 12, - osConfig: config.OSConfig{ - EditPreset: "sublime", - }, - expectedCmdStr: `subl -- "file/with space":12`, - suspend: false, - }, - } - - for _, s := range scenarios { - userConfig := config.GetDefaultConfig() - userConfig.OS = s.osConfig - - instance := buildFileCommands(commonDeps{ - userConfig: userConfig, - }) - - cmdStr, suspend := instance.GetEditAtLineCmdStr(s.filename, s.lineNumber) - assert.Equal(t, s.expectedCmdStr, cmdStr) - assert.Equal(t, s.suspend, suspend) - } -} - -func TestEditFileAtLineAndWaitCmd(t *testing.T) { - type scenario struct { - filename string - lineNumber int - osConfig config.OSConfig - expectedCmdStr string - } - - scenarios := []scenario{ - { - filename: "test", - lineNumber: 42, - osConfig: config.OSConfig{}, - expectedCmdStr: `vim +42 -- "test"`, - }, - { - filename: "file/with space", - lineNumber: 12, - osConfig: config.OSConfig{ - EditPreset: "sublime", - }, - expectedCmdStr: `subl --wait -- "file/with space":12`, - }, - } - - for _, s := range scenarios { - userConfig := config.GetDefaultConfig() - userConfig.OS = s.osConfig - - instance := buildFileCommands(commonDeps{ - userConfig: userConfig, - }) - - cmdStr := instance.GetEditAtLineAndWaitCmdStr(s.filename, s.lineNumber) - assert.Equal(t, s.expectedCmdStr, cmdStr) - } -} - -func TestGuessDefaultEditor(t *testing.T) { - type scenario struct { - gitConfigMockResponses map[string]string - getenv func(string) string - expectedResult string - } - - scenarios := []scenario{ - { - gitConfigMockResponses: nil, - getenv: func(env string) string { - return "" - }, - expectedResult: "", - }, - { - gitConfigMockResponses: map[string]string{"core.editor": "nano"}, - getenv: func(env string) string { - return "" - }, - expectedResult: "nano", - }, - { - gitConfigMockResponses: map[string]string{"core.editor": "code -w"}, - getenv: func(env string) string { - return "" - }, - expectedResult: "code", - }, - { - gitConfigMockResponses: nil, - getenv: func(env string) string { - if env == "VISUAL" { - return "emacs" - } - - return "" - }, - expectedResult: "emacs", - }, - { - gitConfigMockResponses: nil, - getenv: func(env string) string { - if env == "EDITOR" { - return "bbedit -w" - } - - return "" - }, - expectedResult: "bbedit", - }, - } - - for _, s := range scenarios { - instance := buildFileCommands(commonDeps{ - gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses), - getenv: s.getenv, - }) - - assert.Equal(t, s.expectedResult, instance.guessDefaultEditor()) - } -} diff --git a/pkg/commands/git_commands/flow.go b/pkg/commands/git_commands/flow.go deleted file mode 100644 index a5aa9d4dc07..00000000000 --- a/pkg/commands/git_commands/flow.go +++ /dev/null @@ -1,61 +0,0 @@ -package git_commands - -import ( - "regexp" - "strings" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -type FlowCommands struct { - *GitCommon -} - -func NewFlowCommands( - gitCommon *GitCommon, -) *FlowCommands { - return &FlowCommands{ - GitCommon: gitCommon, - } -} - -func (self *FlowCommands) GitFlowEnabled() bool { - return self.config.GetGitFlowPrefixes() != "" -} - -func (self *FlowCommands) FinishCmdObj(branchName string) (*oscommands.CmdObj, error) { - prefixes := self.config.GetGitFlowPrefixes() - - // need to find out what kind of branch this is - prefix := strings.SplitAfterN(branchName, "/", 2)[0] - suffix := strings.Replace(branchName, prefix, "", 1) - - branchType := "" - for _, line := range strings.Split(strings.TrimSpace(prefixes), "\n") { - if strings.HasPrefix(line, "gitflow.prefix.") && strings.HasSuffix(line, prefix) { - - regex := regexp.MustCompile("gitflow.prefix.([^ ]*) .*") - matches := regex.FindAllStringSubmatch(line, 1) - - if len(matches) > 0 && len(matches[0]) > 1 { - branchType = matches[0][1] - break - } - } - } - - if branchType == "" { - return nil, errors.New(self.Tr.NotAGitFlowBranch) - } - - cmdArgs := NewGitCmd("flow").Arg(branchType, "finish", suffix).ToArgv() - - return self.cmd.New(cmdArgs), nil -} - -func (self *FlowCommands) StartCmdObj(branchType string, name string) *oscommands.CmdObj { - cmdArgs := NewGitCmd("flow").Arg(branchType, "start", name).ToArgv() - - return self.cmd.New(cmdArgs) -} diff --git a/pkg/commands/git_commands/flow_test.go b/pkg/commands/git_commands/flow_test.go deleted file mode 100644 index 911f50c7eb4..00000000000 --- a/pkg/commands/git_commands/flow_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/git_config" - "github.com/stretchr/testify/assert" -) - -func TestStartCmdObj(t *testing.T) { - scenarios := []struct { - testName string - branchType string - name string - expected []string - }{ - { - testName: "basic", - branchType: "feature", - name: "test", - expected: []string{"git", "flow", "feature", "start", "test"}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildFlowCommands(commonDeps{}) - - assert.Equal(t, - instance.StartCmdObj(s.branchType, s.name).Args(), - s.expected, - ) - }) - } -} - -func TestFinishCmdObj(t *testing.T) { - scenarios := []struct { - testName string - branchName string - expected []string - expectedError string - gitConfigMockResponses map[string]string - }{ - { - testName: "not a git flow branch", - branchName: "mybranch", - expected: nil, - expectedError: "This does not seem to be a git flow branch", - gitConfigMockResponses: nil, - }, - { - testName: "feature branch without config", - branchName: "feature/mybranch", - expected: nil, - expectedError: "This does not seem to be a git flow branch", - gitConfigMockResponses: nil, - }, - { - testName: "feature branch with config", - branchName: "feature/mybranch", - expected: []string{"git", "flow", "feature", "finish", "mybranch"}, - expectedError: "", - gitConfigMockResponses: map[string]string{ - "--local --get-regexp gitflow.prefix": "gitflow.prefix.feature feature/", - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildFlowCommands(commonDeps{ - gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses), - }) - - cmd, err := instance.FinishCmdObj(s.branchName) - - if s.expectedError != "" { - if err == nil { - t.Errorf("Expected error, got nil") - } else { - assert.Equal(t, err.Error(), s.expectedError) - } - } else { - assert.NoError(t, err) - assert.Equal(t, cmd.Args(), s.expected) - } - }) - } -} diff --git a/pkg/commands/git_commands/git_command_builder.go b/pkg/commands/git_commands/git_command_builder.go deleted file mode 100644 index 5e9c3b2589a..00000000000 --- a/pkg/commands/git_commands/git_command_builder.go +++ /dev/null @@ -1,108 +0,0 @@ -package git_commands - -import ( - "strings" -) - -// convenience struct for building git commands. Especially useful when -// including conditional args -type GitCommandBuilder struct { - // command string - args []string -} - -func NewGitCmd(command string) *GitCommandBuilder { - return &GitCommandBuilder{args: []string{command}} -} - -func (self *GitCommandBuilder) Arg(args ...string) *GitCommandBuilder { - self.args = append(self.args, args...) - - return self -} - -func (self *GitCommandBuilder) ArgIf(condition bool, ifTrue ...string) *GitCommandBuilder { - if condition { - self.Arg(ifTrue...) - } - - return self -} - -func (self *GitCommandBuilder) ArgIfElse(condition bool, ifTrue string, ifFalse string) *GitCommandBuilder { - if condition { - return self.Arg(ifTrue) - } - return self.Arg(ifFalse) -} - -func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder { - // config settings come before the command - self.args = append([]string{"-c", value}, self.args...) - - return self -} - -func (self *GitCommandBuilder) ConfigIf(condition bool, ifTrue string) *GitCommandBuilder { - if condition { - self.Config(ifTrue) - } - - return self -} - -// the -C arg will make git do a `cd` to the directory before doing anything else -func (self *GitCommandBuilder) Dir(path string) *GitCommandBuilder { - // repo path comes before the command - self.args = append([]string{"-C", path}, self.args...) - - return self -} - -func (self *GitCommandBuilder) DirIf(condition bool, path string) *GitCommandBuilder { - if condition { - return self.Dir(path) - } - - return self -} - -// Note, you may prefer to use the Dir method instead of this one -func (self *GitCommandBuilder) Worktree(path string) *GitCommandBuilder { - // worktree arg comes before the command - self.args = append([]string{"--work-tree", path}, self.args...) - - return self -} - -func (self *GitCommandBuilder) WorktreePathIf(condition bool, path string) *GitCommandBuilder { - if condition { - return self.Worktree(path) - } - - return self -} - -// Note, you may prefer to use the Dir method instead of this one -func (self *GitCommandBuilder) GitDir(path string) *GitCommandBuilder { - // git dir arg comes before the command - self.args = append([]string{"--git-dir", path}, self.args...) - - return self -} - -func (self *GitCommandBuilder) GitDirIf(condition bool, path string) *GitCommandBuilder { - if condition { - return self.GitDir(path) - } - - return self -} - -func (self *GitCommandBuilder) ToArgv() []string { - return append([]string{"git"}, self.args...) -} - -func (self *GitCommandBuilder) ToString() string { - return strings.Join(self.ToArgv(), " ") -} diff --git a/pkg/commands/git_commands/git_command_builder_test.go b/pkg/commands/git_commands/git_command_builder_test.go deleted file mode 100644 index 69d41854cc8..00000000000 --- a/pkg/commands/git_commands/git_command_builder_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGitCommandBuilder(t *testing.T) { - scenarios := []struct { - input []string - expected []string - }{ - { - input: NewGitCmd("push"). - Arg("--force-with-lease"). - Arg("--set-upstream"). - Arg("origin"). - Arg("master"). - ToArgv(), - expected: []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "master"}, - }, - { - input: NewGitCmd("push").ArgIf(true, "--test").ToArgv(), - expected: []string{"git", "push", "--test"}, - }, - { - input: NewGitCmd("push").ArgIf(false, "--test").ToArgv(), - expected: []string{"git", "push"}, - }, - { - input: NewGitCmd("push").ArgIfElse(true, "-b", "-a").ToArgv(), - expected: []string{"git", "push", "-b"}, - }, - { - input: NewGitCmd("push").ArgIfElse(false, "-a", "-b").ToArgv(), - expected: []string{"git", "push", "-b"}, - }, - { - input: NewGitCmd("push").Arg("-a", "-b").ToArgv(), - expected: []string{"git", "push", "-a", "-b"}, - }, - { - input: NewGitCmd("push").Config("user.name=foo").Config("user.email=bar").ToArgv(), - expected: []string{"git", "-c", "user.email=bar", "-c", "user.name=foo", "push"}, - }, - { - input: NewGitCmd("push").Dir("a/b/c").ToArgv(), - expected: []string{"git", "-C", "a/b/c", "push"}, - }, - } - - for _, s := range scenarios { - assert.Equal(t, s.input, s.expected) - } -} diff --git a/pkg/commands/git_commands/main_branches.go b/pkg/commands/git_commands/main_branches.go deleted file mode 100644 index 109f9cfd847..00000000000 --- a/pkg/commands/git_commands/main_branches.go +++ /dev/null @@ -1,127 +0,0 @@ -package git_commands - -import ( - "slices" - "strings" - "sync" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/sasha-s/go-deadlock" -) - -type MainBranches struct { - c *common.Common - // Which of the configured main branches actually exist in the repository. Full - // ref names, and it could be either "refs/heads/..." or "refs/remotes/origin/..." - // depending on which one exists for a given bare name. - existingMainBranches []string - - previousMainBranches []string - - cmd oscommands.ICmdObjBuilder - mutex deadlock.Mutex -} - -func NewMainBranches( - cmn *common.Common, - cmd oscommands.ICmdObjBuilder, -) *MainBranches { - return &MainBranches{ - c: cmn, - existingMainBranches: nil, - cmd: cmd, - } -} - -// Get the list of main branches that exist in the repository. This is a list of -// full ref names. -func (self *MainBranches) Get() []string { - self.mutex.Lock() - defer self.mutex.Unlock() - - configuredMainBranches := self.c.UserConfig().Git.MainBranches - - if self.existingMainBranches == nil || !slices.Equal(self.previousMainBranches, configuredMainBranches) { - self.existingMainBranches = self.determineMainBranches(configuredMainBranches) - self.previousMainBranches = configuredMainBranches - } - - return self.existingMainBranches -} - -// Return the merge base of the given refName with the closest main branch. -func (self *MainBranches) GetMergeBase(refName string) string { - mainBranches := self.Get() - if len(mainBranches) == 0 { - return "" - } - - // We pass all existing main branches to the merge-base call; git will - // return the base commit for the closest one. - - // We ignore errors from this call, since we can't distinguish whether the - // error is because one of the main branches has been deleted since the last - // call to determineMainBranches, or because the refName has no common - // history with any of the main branches. Since the former should happen - // very rarely, users must quit and restart lazygit to fix it; the latter is - // also not very common, but can totally happen and is not an error. - - output, _, _ := self.cmd.New( - NewGitCmd("merge-base").Arg(refName).Arg(mainBranches...). - ToArgv(), - ).DontLog().RunWithOutputs() - return strings.TrimSpace(output) -} - -func (self *MainBranches) determineMainBranches(configuredMainBranches []string) []string { - var existingBranches []string - var wg sync.WaitGroup - - existingBranches = make([]string, len(configuredMainBranches)) - - for i, branchName := range configuredMainBranches { - wg.Add(1) - go utils.Safe(func() { - defer wg.Done() - - // Try to determine upstream of local main branch - if ref, err := self.cmd.New( - NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(), - ).DontLog().RunWithOutput(); err == nil { - existingBranches[i] = strings.TrimSpace(ref) - return - } - - // If this failed, a local branch for this main branch doesn't exist or it - // has no upstream configured. Try looking for one in the "origin" remote. - ref := "refs/remotes/origin/" + branchName - if err := self.cmd.New( - NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(), - ).DontLog().Run(); err == nil { - existingBranches[i] = ref - return - } - - // If this failed as well, try if we have the main branch as a local - // branch. This covers the case where somebody is using git locally - // for something, but never pushing anywhere. - ref = "refs/heads/" + branchName - if err := self.cmd.New( - NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(), - ).DontLog().Run(); err == nil { - existingBranches[i] = ref - } - }) - } - - wg.Wait() - - existingBranches = lo.Filter(existingBranches, func(branch string, _ int) bool { - return branch != "" - }) - - return existingBranches -} diff --git a/pkg/commands/git_commands/patch.go b/pkg/commands/git_commands/patch.go deleted file mode 100644 index 2a979dd54ef..00000000000 --- a/pkg/commands/git_commands/patch.go +++ /dev/null @@ -1,354 +0,0 @@ -package git_commands - -import ( - "fmt" - "path/filepath" - "time" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/app/daemon" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/stefanhaller/git-todo-parser/todo" -) - -type PatchCommands struct { - *GitCommon - rebase *RebaseCommands - commit *CommitCommands - status *StatusCommands - stash *StashCommands - - PatchBuilder *patch.PatchBuilder -} - -func NewPatchCommands( - gitCommon *GitCommon, - rebase *RebaseCommands, - commit *CommitCommands, - status *StatusCommands, - stash *StashCommands, - patchBuilder *patch.PatchBuilder, -) *PatchCommands { - return &PatchCommands{ - GitCommon: gitCommon, - rebase: rebase, - commit: commit, - status: status, - stash: stash, - PatchBuilder: patchBuilder, - } -} - -type ApplyPatchOpts struct { - ThreeWay bool - Cached bool - Index bool - Reverse bool -} - -func (self *PatchCommands) ApplyCustomPatch(reverse bool, turnAddedFilesIntoDiffAgainstEmptyFile bool) error { - patch := self.PatchBuilder.PatchToApply(reverse, turnAddedFilesIntoDiffAgainstEmptyFile) - - return self.ApplyPatch(patch, ApplyPatchOpts{ - Index: true, - ThreeWay: true, - Reverse: reverse, - }) -} - -func (self *PatchCommands) ApplyPatch(patch string, opts ApplyPatchOpts) error { - filepath, err := self.SaveTemporaryPatch(patch) - if err != nil { - return err - } - - return self.applyPatchFile(filepath, opts) -} - -func (self *PatchCommands) applyPatchFile(filepath string, opts ApplyPatchOpts) error { - cmdArgs := NewGitCmd("apply"). - ArgIf(opts.ThreeWay, "--3way"). - ArgIf(opts.Cached, "--cached"). - ArgIf(opts.Index, "--index"). - ArgIf(opts.Reverse, "--reverse"). - Arg(filepath). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *PatchCommands) SaveTemporaryPatch(patch string) (string, error) { - filepath := filepath.Join(self.os.GetTempDir(), self.repoPaths.RepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch") - self.Log.Infof("saving temporary patch to %s", filepath) - if err := self.os.CreateFileWithContent(filepath, patch); err != nil { - return "", err - } - return filepath, nil -} - -// DeletePatchesFromCommit applies a patch in reverse for a commit -func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, commitIndex int) error { - if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIndex, false); err != nil { - return err - } - - // apply each patch in reverse - if err := self.ApplyCustomPatch(true, true); err != nil { - _ = self.rebase.AbortRebase() - return err - } - - // time to amend the selected commit - if err := self.commit.AmendHead(); err != nil { - return err - } - - self.rebase.onSuccessfulContinue = func() error { - self.PatchBuilder.Reset() - return nil - } - - // continue - return self.rebase.ContinueRebase() -} - -func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, sourceCommitIdx int, destinationCommitIdx int) error { - if sourceCommitIdx < destinationCommitIdx { - // Passing true for keepCommitsThatBecomeEmpty: if the moved-from - // commit becomes empty, we want to keep it, mainly for consistency with - // moving the patch to a *later* commit, which behaves the same. - if err := self.rebase.BeginInteractiveRebaseForCommit(commits, destinationCommitIdx, true); err != nil { - return err - } - - // apply each patch forward - if err := self.ApplyCustomPatch(false, false); err != nil { - // Don't abort the rebase here; this might cause conflicts, so give - // the user a chance to resolve them - return err - } - - // amend the destination commit - if err := self.commit.AmendHead(); err != nil { - return err - } - - self.rebase.onSuccessfulContinue = func() error { - self.PatchBuilder.Reset() - return nil - } - - // continue - return self.rebase.ContinueRebase() - } - - if len(commits)-1 < sourceCommitIdx { - return errors.New("index outside of range of commits") - } - - // we can make this GPG thing possible it just means we need to do this in two parts: - // one where we handle the possibility of a credential request, and the other - // where we continue the rebase - if self.config.NeedsGpgSubprocessForCommit() { - return errors.New(self.Tr.DisabledForGPG) - } - - baseIndex := sourceCommitIdx + 1 - - changes := []daemon.ChangeTodoAction{ - {Hash: commits[sourceCommitIdx].Hash(), NewAction: todo.Edit}, - {Hash: commits[destinationCommitIdx].Hash(), NewAction: todo.Edit}, - } - self.os.LogCommand(logTodoChanges(changes), false) - - err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: getBaseHashOrRoot(commits, baseIndex), - overrideEditor: true, - instruction: daemon.NewChangeTodoActionsInstruction(changes), - }).Run() - if err != nil { - return err - } - - // apply each patch in reverse - if err := self.ApplyCustomPatch(true, true); err != nil { - _ = self.rebase.AbortRebase() - return err - } - - // amend the source commit - if err := self.commit.AmendHead(); err != nil { - return err - } - - patch, err := self.diffHeadAgainstCommit(commits[sourceCommitIdx]) - if err != nil { - _ = self.rebase.AbortRebase() - return err - } - - if self.rebase.onSuccessfulContinue != nil { - return errors.New("You are midway through another rebase operation. Please abort to start again") - } - - self.rebase.onSuccessfulContinue = func() error { - // now we should be up to the destination, so let's apply forward these patches to that. - // ideally we would ensure we're on the right commit but I'm not sure if that check is necessary - if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil { - // Don't abort the rebase here; this might cause conflicts, so give - // the user a chance to resolve them - return err - } - - // amend the destination commit - if err := self.commit.AmendHead(); err != nil { - return err - } - - self.rebase.onSuccessfulContinue = func() error { - self.PatchBuilder.Reset() - return nil - } - - return self.rebase.ContinueRebase() - } - - return self.rebase.ContinueRebase() -} - -func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitIdx int, stash bool) error { - if stash { - if err := self.stash.Push(fmt.Sprintf(self.Tr.AutoStashForMovingPatchToIndex, commits[commitIdx].ShortHash())); err != nil { - return err - } - } - - if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx, false); err != nil { - return err - } - - if err := self.ApplyCustomPatch(true, true); err != nil { - if self.status.WorkingTreeState().Rebasing { - _ = self.rebase.AbortRebase() - } - return err - } - - // amend the commit - if err := self.commit.AmendHead(); err != nil { - return err - } - - patch, err := self.diffHeadAgainstCommit(commits[commitIdx]) - if err != nil { - _ = self.rebase.AbortRebase() - return err - } - - if self.rebase.onSuccessfulContinue != nil { - return errors.New("You are midway through another rebase operation. Please abort to start again") - } - - self.rebase.onSuccessfulContinue = func() error { - // add patches to index - if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil { - if self.status.WorkingTreeState().Rebasing { - _ = self.rebase.AbortRebase() - } - return err - } - - if stash { - if err := self.stash.Pop(0); err != nil { - return err - } - } - - self.PatchBuilder.Reset() - return nil - } - - return self.rebase.ContinueRebase() -} - -func (self *PatchCommands) PullPatchIntoNewCommit( - commits []*models.Commit, - commitIdx int, - commitSummary string, - commitDescription string, -) error { - if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx, false); err != nil { - return err - } - - if err := self.ApplyCustomPatch(true, true); err != nil { - _ = self.rebase.AbortRebase() - return err - } - - // amend the commit - if err := self.commit.AmendHead(); err != nil { - return err - } - - patch, err := self.diffHeadAgainstCommit(commits[commitIdx]) - if err != nil { - _ = self.rebase.AbortRebase() - return err - } - - if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil { - _ = self.rebase.AbortRebase() - return err - } - - if err := self.commit.CommitCmdObj(commitSummary, commitDescription, false).Run(); err != nil { - return err - } - - if self.rebase.onSuccessfulContinue != nil { - return errors.New("You are midway through another rebase operation. Please abort to start again") - } - - self.PatchBuilder.Reset() - return self.rebase.ContinueRebase() -} - -func (self *PatchCommands) PullPatchIntoNewCommitBefore( - commits []*models.Commit, - commitIdx int, - commitSummary string, - commitDescription string, -) error { - if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx+1, true); err != nil { - return err - } - - if err := self.ApplyCustomPatch(false, false); err != nil { - _ = self.rebase.AbortRebase() - return err - } - - if err := self.commit.CommitCmdObj(commitSummary, commitDescription, false).Run(); err != nil { - return err - } - - self.PatchBuilder.Reset() - return self.rebase.ContinueRebase() -} - -// We have just applied a patch in reverse to discard it from a commit; if we -// now try to apply the patch again to move it to a later commit, or to the -// index, then this would conflict "with itself" in case the patch contained -// only some lines of a range of adjacent added lines. To solve this, we -// get the diff of HEAD and the original commit and then apply that. -func (self *PatchCommands) diffHeadAgainstCommit(commit *models.Commit) (string, error) { - cmdArgs := NewGitCmd("diff"). - Config("diff.noprefix=false"). - Arg("--no-ext-diff"). - Arg("HEAD.." + commit.Hash()). - ToArgv() - - return self.cmd.New(cmdArgs).RunWithOutput() -} diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go deleted file mode 100644 index 152c88bbcca..00000000000 --- a/pkg/commands/git_commands/rebase.go +++ /dev/null @@ -1,588 +0,0 @@ -package git_commands - -import ( - "fmt" - "path/filepath" - "strings" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/app/daemon" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/stefanhaller/git-todo-parser/todo" -) - -type RebaseCommands struct { - *GitCommon - commit *CommitCommands - workingTree *WorkingTreeCommands - - onSuccessfulContinue func() error -} - -func NewRebaseCommands( - gitCommon *GitCommon, - commitCommands *CommitCommands, - workingTreeCommands *WorkingTreeCommands, -) *RebaseCommands { - return &RebaseCommands{ - GitCommon: gitCommon, - commit: commitCommands, - workingTree: workingTreeCommands, - } -} - -func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, summary string, description string) error { - // This check is currently unreachable (handled in LocalCommitsController.reword), - // but kept as a safeguard in case this method is used elsewhere. - if self.config.NeedsGpgSubprocessForCommit() { - return errors.New(self.Tr.DisabledForGPG) - } - - err := self.BeginInteractiveRebaseForCommit(commits, index, false) - if err != nil { - return err - } - - // now the selected commit should be our head so we'll amend it with the new message - err = self.commit.RewordLastCommit(summary, description).Run() - if err != nil { - return err - } - - return self.ContinueRebase() -} - -func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (*oscommands.CmdObj, error) { - changes := []daemon.ChangeTodoAction{{ - Hash: commits[index].Hash(), - NewAction: todo.Reword, - }} - self.os.LogCommand(logTodoChanges(changes), false) - - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: getBaseHashOrRoot(commits, index+1), - instruction: daemon.NewChangeTodoActionsInstruction(changes), - }), nil -} - -func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, start, end int) error { - return self.GenericAmend(commits, start, end, func(_ *models.Commit) error { - return self.commit.ResetAuthor() - }) -} - -func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, start, end int, value string) error { - return self.GenericAmend(commits, start, end, func(_ *models.Commit) error { - return self.commit.SetAuthor(value) - }) -} - -func (self *RebaseCommands) AddCommitCoAuthor(commits []*models.Commit, start, end int, value string) error { - return self.GenericAmend(commits, start, end, func(commit *models.Commit) error { - return self.commit.AddCoAuthor(commit.Hash(), value) - }) -} - -func (self *RebaseCommands) GenericAmend(commits []*models.Commit, start, end int, f func(commit *models.Commit) error) error { - if start == end && models.IsHeadCommit(commits, start) { - // we've selected the top commit so no rebase is required - return f(commits[start]) - } - - err := self.BeginInteractiveRebaseForCommitRange(commits, start, end, false) - if err != nil { - return err - } - - for commitIndex := end; commitIndex >= start; commitIndex-- { - err = f(commits[commitIndex]) - if err != nil { - return err - } - - if err := self.ContinueRebase(); err != nil { - return err - } - } - - return nil -} - -func (self *RebaseCommands) MoveCommitsDown(commits []*models.Commit, startIdx int, endIdx int) error { - baseHashOrRoot := getBaseHashOrRoot(commits, endIdx+2) - - hashes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) string { - return commit.Hash() - }) - - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: baseHashOrRoot, - instruction: daemon.NewMoveTodosDownInstruction(hashes), - overrideEditor: true, - }).Run() -} - -func (self *RebaseCommands) MoveCommitsUp(commits []*models.Commit, startIdx int, endIdx int) error { - baseHashOrRoot := getBaseHashOrRoot(commits, endIdx+1) - - hashes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) string { - return commit.Hash() - }) - - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: baseHashOrRoot, - instruction: daemon.NewMoveTodosUpInstruction(hashes), - overrideEditor: true, - }).Run() -} - -func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx int, endIdx int, action todo.TodoCommand) error { - baseIndex := endIdx + 1 - if action == todo.Squash || action == todo.Fixup { - baseIndex++ - } - - baseHashOrRoot := getBaseHashOrRoot(commits, baseIndex) - - changes := lo.FilterMap(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) (daemon.ChangeTodoAction, bool) { - return daemon.ChangeTodoAction{ - Hash: commit.Hash(), - NewAction: action, - }, !commit.IsMerge() - }) - - self.os.LogCommand(logTodoChanges(changes), false) - - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: baseHashOrRoot, - overrideEditor: true, - instruction: daemon.NewChangeTodoActionsInstruction(changes), - }).Run() -} - -func (self *RebaseCommands) EditRebase(branchRef string) error { - msg := utils.ResolvePlaceholderString( - self.Tr.Log.EditRebase, - map[string]string{ - "ref": branchRef, - }, - ) - self.os.LogCommand(msg, false) - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: branchRef, - instruction: daemon.NewInsertBreakInstruction(), - }).Run() -} - -func (self *RebaseCommands) EditRebaseFromBaseCommit(targetBranchName string, baseCommit string) error { - msg := utils.ResolvePlaceholderString( - self.Tr.Log.EditRebaseFromBaseCommit, - map[string]string{ - "baseCommit": baseCommit, - "targetBranchName": targetBranchName, - }, - ) - self.os.LogCommand(msg, false) - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: baseCommit, - onto: targetBranchName, - instruction: daemon.NewInsertBreakInstruction(), - }).Run() -} - -func logTodoChanges(changes []daemon.ChangeTodoAction) string { - changeTodoStr := strings.Join(lo.Map(changes, func(c daemon.ChangeTodoAction, _ int) string { - return fmt.Sprintf("%s:%s", c.Hash, c.NewAction) - }), "\n") - return fmt.Sprintf("Changing TODO actions:\n%s", changeTodoStr) -} - -type PrepareInteractiveRebaseCommandOpts struct { - baseHashOrRoot string - onto string - instruction daemon.Instruction - overrideEditor bool - keepCommitsThatBecomeEmpty bool -} - -// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase -// we tell git to run lazygit to edit the todo list, and we pass the client -// lazygit instructions what to do with the todo file -func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) *oscommands.CmdObj { - ex := oscommands.GetLazygitPath() - - cmdArgs := NewGitCmd("rebase"). - Arg("--interactive"). - Arg("--autostash"). - Arg("--keep-empty"). - ArgIf(opts.keepCommitsThatBecomeEmpty, "--empty=keep"). - Arg("--no-autosquash"). - Arg("--rebase-merges"). - ArgIf(opts.onto != "", "--onto", opts.onto). - Arg(opts.baseHashOrRoot). - ToArgv() - - debug := "FALSE" - if self.Debug { - debug = "TRUE" - } - - self.Log.WithField("command", cmdArgs).Debug("RunCommand") - - cmdObj := self.cmd.New(cmdArgs) - - gitSequenceEditor := ex - - if opts.instruction != nil { - cmdObj.AddEnvVars(daemon.ToEnvVars(opts.instruction)...) - } else { - cmdObj.AddEnvVars(daemon.ToEnvVars(daemon.NewRemoveUpdateRefsForCopiedBranchInstruction())...) - } - - cmdObj.AddEnvVars( - "DEBUG="+debug, - "LANG=C", // Force using English language - "LC_ALL=C", // Force using English language - "LC_MESSAGES=C", // Force using English language - "GIT_SEQUENCE_EDITOR="+gitSequenceEditor, - ) - - if opts.overrideEditor { - cmdObj.AddEnvVars("GIT_EDITOR=" + ex) - } - - return cmdObj -} - -// GitRebaseEditTodo runs "git rebase --edit-todo", saving the given todosFileContent to the file -func (self *RebaseCommands) GitRebaseEditTodo(todosFileContent []byte) error { - ex := oscommands.GetLazygitPath() - - cmdArgs := NewGitCmd("rebase"). - Arg("--edit-todo"). - ToArgv() - - debug := "FALSE" - if self.Debug { - debug = "TRUE" - } - - self.Log.WithField("command", cmdArgs).Debug("RunCommand") - - cmdObj := self.cmd.New(cmdArgs) - - cmdObj.AddEnvVars(daemon.ToEnvVars(daemon.NewWriteRebaseTodoInstruction(todosFileContent))...) - - cmdObj.AddEnvVars( - "DEBUG="+debug, - "LANG=C", // Force using English language - "LC_ALL=C", // Force using English language - "LC_MESSAGES=C", // Force using English language - "GIT_EDITOR="+ex, - "GIT_SEQUENCE_EDITOR="+ex, - ) - - return cmdObj.Run() -} - -func (self *RebaseCommands) getHashOfLastCommitMade() (string, error) { - cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv() - return self.cmd.New(cmdArgs).RunWithOutput() -} - -// AmendTo amends the given commit with whatever files are staged -func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error { - commit := commits[commitIndex] - - if err := self.commit.CreateFixupCommit(commit.Hash()); err != nil { - return err - } - - fixupHash, err := self.getHashOfLastCommitMade() - if err != nil { - return err - } - - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1), - overrideEditor: true, - instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash(), fixupHash, true), - }).Run() -} - -func (self *RebaseCommands) MoveFixupCommitDown(commits []*models.Commit, targetCommitIndex int) error { - fixupHash, err := self.getHashOfLastCommitMade() - if err != nil { - return err - } - - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: getBaseHashOrRoot(commits, targetCommitIndex+1), - overrideEditor: true, - instruction: daemon.NewMoveFixupCommitDownInstruction(commits[targetCommitIndex].Hash(), fixupHash, false), - }).Run() -} - -func todoFromCommit(commit *models.Commit) utils.Todo { - if commit.Action == todo.UpdateRef { - return utils.Todo{Ref: commit.Name} - } - return utils.Todo{Hash: commit.Hash()} -} - -// Sets the action for the given commits in the git-rebase-todo file -func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo.TodoCommand) error { - commitsWithAction := lo.Map(commits, func(commit *models.Commit, _ int) utils.TodoChange { - return utils.TodoChange{ - Hash: commit.Hash(), - NewAction: action, - } - }) - - return utils.EditRebaseTodo( - filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"), - commitsWithAction, - self.config.GetCoreCommentChar(), - ) -} - -func (self *RebaseCommands) DeleteUpdateRefTodos(commits []*models.Commit) error { - todosToDelete := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo { - return todoFromCommit(commit) - }) - - todosFileContent, err := utils.DeleteTodos( - filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"), - todosToDelete, - self.config.GetCoreCommentChar(), - ) - if err != nil { - return err - } - - return self.GitRebaseEditTodo(todosFileContent) -} - -func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error { - fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo") - todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo { - return todoFromCommit(commit) - }) - - return utils.MoveTodosDown(fileName, todosToMove, true, self.config.GetCoreCommentChar()) -} - -func (self *RebaseCommands) MoveTodosUp(commits []*models.Commit) error { - fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo") - todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo { - return todoFromCommit(commit) - }) - - return utils.MoveTodosUp(fileName, todosToMove, true, self.config.GetCoreCommentChar()) -} - -// SquashAllAboveFixupCommits squashes all fixup! commits above the given one -func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) error { - hashOrRoot := commit.Hash() + "^" - if commit.IsFirstCommit() { - hashOrRoot = "--root" - } - - cmdArgs := NewGitCmd("rebase"). - Arg("--interactive", "--rebase-merges", "--autostash", "--autosquash", hashOrRoot). - ToArgv() - - return self.runSkipEditorCommand(self.cmd.New(cmdArgs)) -} - -// BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current -// commit and pick all others. After this you'll want to call `self.ContinueRebase() -func (self *RebaseCommands) BeginInteractiveRebaseForCommit( - commits []*models.Commit, commitIndex int, keepCommitsThatBecomeEmpty bool, -) error { - if commitIndex < len(commits) && commits[commitIndex].IsMerge() { - if self.config.NeedsGpgSubprocessForCommit() { - return errors.New(self.Tr.DisabledForGPG) - } - - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex), - instruction: daemon.NewInsertBreakInstruction(), - keepCommitsThatBecomeEmpty: keepCommitsThatBecomeEmpty, - }).Run() - } - - return self.BeginInteractiveRebaseForCommitRange(commits, commitIndex, commitIndex, keepCommitsThatBecomeEmpty) -} - -func (self *RebaseCommands) BeginInteractiveRebaseForCommitRange( - commits []*models.Commit, start, end int, keepCommitsThatBecomeEmpty bool, -) error { - if len(commits)-1 < end { - return errors.New("index outside of range of commits") - } - - // we can make this GPG thing possible it just means we need to do this in two parts: - // one where we handle the possibility of a credential request, and the other - // where we continue the rebase - if self.config.NeedsGpgSubprocessForCommit() { - return errors.New(self.Tr.DisabledForGPG) - } - - changes := make([]daemon.ChangeTodoAction, 0, end-start) - for commitIndex := end; commitIndex >= start; commitIndex-- { - changes = append(changes, daemon.ChangeTodoAction{ - Hash: commits[commitIndex].Hash(), - NewAction: todo.Edit, - }) - } - self.os.LogCommand(logTodoChanges(changes), false) - - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: getBaseHashOrRoot(commits, end+1), - overrideEditor: true, - keepCommitsThatBecomeEmpty: keepCommitsThatBecomeEmpty, - instruction: daemon.NewChangeTodoActionsInstruction(changes), - }).Run() -} - -// RebaseBranch interactive rebases onto a branch -func (self *RebaseCommands) RebaseBranch(branchName string) error { - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{baseHashOrRoot: branchName}).Run() -} - -func (self *RebaseCommands) RebaseBranchFromBaseCommit(targetBranchName string, baseCommit string) error { - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: baseCommit, - onto: targetBranchName, - }).Run() -} - -func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) *oscommands.CmdObj { - cmdArgs := NewGitCmd(commandType).Arg("--" + command).ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *RebaseCommands) ContinueRebase() error { - return self.GenericMergeOrRebaseAction("rebase", "continue") -} - -func (self *RebaseCommands) AbortRebase() error { - return self.GenericMergeOrRebaseAction("rebase", "abort") -} - -// GenericMerge takes a commandType of "merge" or "rebase" and a command of "abort", "skip" or "continue" -// By default we skip the editor in the case where a commit will be made -func (self *RebaseCommands) GenericMergeOrRebaseAction(commandType string, command string) error { - err := self.runSkipEditorCommand(self.GenericMergeOrRebaseActionCmdObj(commandType, command)) - if err != nil { - if !strings.Contains(err.Error(), "no rebase in progress") { - return err - } - self.Log.Warn(err) - } - - // sometimes we need to do a sequence of things in a rebase but the user needs to - // fix merge conflicts along the way. When this happens we queue up the next step - // so that after the next successful rebase continue we can continue from where we left off - if commandType == "rebase" && command == "continue" && self.onSuccessfulContinue != nil { - f := self.onSuccessfulContinue - self.onSuccessfulContinue = nil - return f() - } - if command == "abort" { - self.onSuccessfulContinue = nil - } - return nil -} - -func (self *RebaseCommands) runSkipEditorCommand(cmdObj *oscommands.CmdObj) error { - instruction := daemon.NewExitImmediatelyInstruction() - lazyGitPath := oscommands.GetLazygitPath() - return cmdObj. - AddEnvVars( - "GIT_EDITOR="+lazyGitPath, - "GIT_SEQUENCE_EDITOR="+lazyGitPath, - "EDITOR="+lazyGitPath, - "VISUAL="+lazyGitPath, - ). - AddEnvVars(daemon.ToEnvVars(instruction)...). - Run() -} - -// DiscardOldFileChanges discards changes to a file from an old commit -func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, commitIndex int, filePaths []string) error { - if err := self.BeginInteractiveRebaseForCommit(commits, commitIndex, false); err != nil { - return err - } - - for _, filePath := range filePaths { - doesFileExistInPreviousCommit := false - if commitIndex < len(commits)-1 { - // check if file exists in previous commit (this command returns an empty string if the file doesn't exist) - cmdArgs := NewGitCmd("ls-tree").Arg("--name-only", "HEAD^", "--", filePath).ToArgv() - output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - if err != nil { - return err - } - doesFileExistInPreviousCommit = strings.TrimRight(output, "\n") == filePath - } - if !doesFileExistInPreviousCommit { - if err := self.os.Remove(filePath); err != nil { - return err - } - if err := self.workingTree.StageFile(filePath); err != nil { - return err - } - } else if err := self.workingTree.CheckoutFile("HEAD^", filePath); err != nil { - return err - } - } - - // amend the commit - err := self.commit.AmendHead() - if err != nil { - return err - } - - // continue - return self.ContinueRebase() -} - -// CherryPickCommits begins an interactive rebase with the given hashes being cherry picked onto HEAD -func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error { - hasMergeCommit := lo.SomeBy(commits, func(c *models.Commit) bool { return c.IsMerge() }) - cmdArgs := NewGitCmd("cherry-pick"). - Arg("--allow-empty"). - ArgIf(self.version.IsAtLeast(2, 45, 0), "--empty=keep", "--keep-redundant-commits"). - ArgIf(hasMergeCommit, "-m1"). - Arg(lo.Reverse(lo.Map(commits, func(c *models.Commit, _ int) string { return c.Hash() }))...). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *RebaseCommands) DropMergeCommit(commits []*models.Commit, commitIndex int) error { - return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1), - instruction: daemon.NewDropMergeCommitInstruction(commits[commitIndex].Hash()), - }).Run() -} - -// we can't start an interactive rebase from the first commit without passing the -// '--root' arg -func getBaseHashOrRoot(commits []*models.Commit, index int) string { - // We assume that the commits slice contains the initial commit of the repo. - // Technically this assumption could prove false, but it's unlikely you'll - // be starting a rebase from 300 commits ago (which is the original commit limit - // at time of writing) - if index < len(commits) { - return commits[index].Hash() - } - - return "--root" -} diff --git a/pkg/commands/git_commands/rebase_test.go b/pkg/commands/git_commands/rebase_test.go deleted file mode 100644 index 46f1fcc1cc1..00000000000 --- a/pkg/commands/git_commands/rebase_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package git_commands - -import ( - "regexp" - "strconv" - "testing" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/app/daemon" - "github.com/jesseduffield/lazygit/pkg/commands/git_config" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/stretchr/testify/assert" -) - -func TestRebaseRebaseBranch(t *testing.T) { - type scenario struct { - testName string - arg string - gitVersion *GitVersion - runner *oscommands.FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - testName: "successful rebase", - arg: "master", - gitVersion: &GitVersion{2, 26, 0, ""}, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "master"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - { - testName: "unsuccessful rebase", - arg: "master", - gitVersion: &GitVersion{2, 26, 0, ""}, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "master"}, "", errors.New("error")), - test: func(err error) { - assert.Error(t, err) - }, - }, - { - testName: "successful rebase (< 2.26.0)", - arg: "master", - gitVersion: &GitVersion{2, 25, 5, ""}, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "master"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildRebaseCommands(commonDeps{runner: s.runner, gitVersion: s.gitVersion}) - s.test(instance.RebaseBranch(s.arg)) - }) - } -} - -// TestRebaseSkipEditorCommand confirms that SkipEditorCommand injects -// environment variables that suppress an interactive editor -func TestRebaseSkipEditorCommand(t *testing.T) { - cmdArgs := []string{"git", "blah"} - runner := oscommands.NewFakeRunner(t).ExpectFunc("matches editor env var", func(cmdObj *oscommands.CmdObj) bool { - assert.EqualValues(t, cmdArgs, cmdObj.Args()) - envVars := cmdObj.GetEnvVars() - for _, regexStr := range []string{ - `^VISUAL=.*$`, - `^EDITOR=.*$`, - `^GIT_EDITOR=.*$`, - `^GIT_SEQUENCE_EDITOR=.*$`, - "^" + daemon.DaemonKindEnvKey + "=" + strconv.Itoa(int(daemon.DaemonKindExitImmediately)) + "$", - } { - foundMatch := lo.ContainsBy(envVars, func(envVar string) bool { - return regexp.MustCompile(regexStr).MatchString(envVar) - }) - if !foundMatch { - return false - } - } - return true - }, "", nil) - instance := buildRebaseCommands(commonDeps{runner: runner}) - err := instance.runSkipEditorCommand(instance.cmd.New(cmdArgs)) - assert.NoError(t, err) - runner.CheckForMissingCalls() -} - -func TestRebaseDiscardOldFileChanges(t *testing.T) { - type scenario struct { - testName string - gitConfigMockResponses map[string]string - commitOpts []models.NewCommitOpts - commitIndex int - fileName []string - runner *oscommands.FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - testName: "returns error when index outside of range of commits", - gitConfigMockResponses: nil, - commitOpts: []models.NewCommitOpts{}, - commitIndex: 0, - fileName: []string{"test999.txt"}, - runner: oscommands.NewFakeRunner(t), - test: func(err error) { - assert.Error(t, err) - }, - }, - { - testName: "returns error when using gpg", - gitConfigMockResponses: map[string]string{"commit.gpgSign": "true"}, - commitOpts: []models.NewCommitOpts{{Name: "commit", Hash: "123456"}}, - commitIndex: 0, - fileName: []string{"test999.txt"}, - runner: oscommands.NewFakeRunner(t), - test: func(err error) { - assert.Error(t, err) - }, - }, - { - testName: "checks out file if it already existed", - gitConfigMockResponses: nil, - commitOpts: []models.NewCommitOpts{ - {Name: "commit", Hash: "123456"}, - {Name: "commit2", Hash: "abcdef"}, - }, - commitIndex: 0, - fileName: []string{"test999.txt"}, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "abcdef"}, "", nil). - ExpectGitArgs([]string{"ls-tree", "--name-only", "HEAD^", "--", "test999.txt"}, "test999.txt\n", nil). - ExpectGitArgs([]string{"checkout", "HEAD^", "--", "test999.txt"}, "", nil). - ExpectGitArgs([]string{"commit", "--amend", "--no-edit", "--allow-empty", "--allow-empty-message"}, "", nil). - ExpectGitArgs([]string{"rebase", "--continue"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - // test for when the file was created within the commit requires a refactor to support proper mocks - // currently we'd need to mock out the os.Remove function and that's gonna introduce tech debt - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildRebaseCommands(commonDeps{ - runner: s.runner, - gitVersion: &GitVersion{2, 26, 0, ""}, - gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses), - }) - - hashPool := &utils.StringPool{} - commits := lo.Map(s.commitOpts, - func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) }) - - s.test(instance.DiscardOldFileChanges(commits, s.commitIndex, s.fileName)) - s.runner.CheckForMissingCalls() - }) - } -} diff --git a/pkg/commands/git_commands/reflog_commit_loader.go b/pkg/commands/git_commands/reflog_commit_loader.go deleted file mode 100644 index 053fc45984b..00000000000 --- a/pkg/commands/git_commands/reflog_commit_loader.go +++ /dev/null @@ -1,90 +0,0 @@ -package git_commands - -import ( - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type ReflogCommitLoader struct { - *common.Common - cmd oscommands.ICmdObjBuilder -} - -func NewReflogCommitLoader(common *common.Common, cmd oscommands.ICmdObjBuilder) *ReflogCommitLoader { - return &ReflogCommitLoader{ - Common: common, - cmd: cmd, - } -} - -// GetReflogCommits only returns the new reflog commits since the given lastReflogCommit -// if none is passed (i.e. it's value is nil) then we get all the reflog commits -func (self *ReflogCommitLoader) GetReflogCommits(hashPool *utils.StringPool, lastReflogCommit *models.Commit, filterPath string, filterAuthor string) ([]*models.Commit, bool, error) { - cmdArgs := NewGitCmd("log"). - Config("log.showSignature=false"). - Arg("-g"). - Arg("--format=+%H%x00%ct%x00%gs%x00%P"). - ArgIf(filterAuthor != "", "--author="+filterAuthor). - ArgIf(filterPath != "", "--follow", "--name-status", "--", filterPath). - ToArgv() - - cmdObj := self.cmd.New(cmdArgs).DontLog() - - onlyObtainedNewReflogCommits := false - - commits, err := loadCommits(cmdObj, filterPath, func(line string) (*models.Commit, bool) { - commit, ok := self.parseLine(hashPool, line) - if !ok { - return nil, false - } - - // note that the unix timestamp here is the timestamp of the COMMIT, not the reflog entry itself, - // so two consecutive reflog entries may have both the same hash and therefore same timestamp. - // We use the reflog message to disambiguate, and fingers crossed that we never see the same of those - // twice in a row. Reason being that it would mean we'd be erroneously exiting early. - if lastReflogCommit != nil && self.sameReflogCommit(commit, lastReflogCommit) { - onlyObtainedNewReflogCommits = true - // after this point we already have these reflogs loaded so we'll simply return the new ones - return nil, true - } - - return commit, false - }) - if err != nil { - return nil, false, err - } - - return commits, onlyObtainedNewReflogCommits, nil -} - -func (self *ReflogCommitLoader) sameReflogCommit(a *models.Commit, b *models.Commit) bool { - return a.Hash() == b.Hash() && a.UnixTimestamp == b.UnixTimestamp && a.Name == b.Name -} - -func (self *ReflogCommitLoader) parseLine(hashPool *utils.StringPool, line string) (*models.Commit, bool) { - fields := strings.SplitN(line, "\x00", 4) - if len(fields) <= 3 { - return nil, false - } - - unixTimestamp, _ := strconv.Atoi(fields[1]) - - parentHashes := fields[3] - parents := []string{} - if len(parentHashes) > 0 { - parents = strings.Split(parentHashes, " ") - } - - return models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: fields[0], - Name: fields[2], - UnixTimestamp: int64(unixTimestamp), - Status: models.StatusReflog, - Parents: parents, - }), true -} diff --git a/pkg/commands/git_commands/reflog_commit_loader_test.go b/pkg/commands/git_commands/reflog_commit_loader_test.go deleted file mode 100644 index 839533f3f0d..00000000000 --- a/pkg/commands/git_commands/reflog_commit_loader_test.go +++ /dev/null @@ -1,203 +0,0 @@ -package git_commands - -import ( - "errors" - "strings" - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/sanity-io/litter" - "github.com/stretchr/testify/assert" -) - -var reflogOutput = strings.ReplaceAll(`+c3c4b66b64c97ffeecde|1643150483|checkout: moving from A to B|51baa8c1 -+c3c4b66b64c97ffeecde|1643150483|checkout: moving from B to A|51baa8c1 -+c3c4b66b64c97ffeecde|1643150483|checkout: moving from A to B|51baa8c1 -+c3c4b66b64c97ffeecde|1643150483|checkout: moving from master to A|51baa8c1 -+f4ddf2f0d4be4ccc7efa|1643149435|checkout: moving from A to master|51baa8c1 -`, "|", "\x00") - -func TestGetReflogCommits(t *testing.T) { - type scenario struct { - testName string - runner *oscommands.FakeCmdObjRunner - lastReflogCommit *models.Commit - filterPath string - filterAuthor string - expectedCommitOpts []models.NewCommitOpts - expectedOnlyObtainedNew bool - expectedError error - } - - hashPool := &utils.StringPool{} - - scenarios := []scenario{ - { - testName: "no reflog entries", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--format=+%H%x00%ct%x00%gs%x00%P"}, "", nil), - - lastReflogCommit: nil, - expectedCommitOpts: []models.NewCommitOpts{}, - expectedOnlyObtainedNew: false, - expectedError: nil, - }, - { - testName: "some reflog entries", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--format=+%H%x00%ct%x00%gs%x00%P"}, reflogOutput, nil), - - lastReflogCommit: nil, - expectedCommitOpts: []models.NewCommitOpts{ - { - Hash: "c3c4b66b64c97ffeecde", - Name: "checkout: moving from A to B", - Status: models.StatusReflog, - UnixTimestamp: 1643150483, - Parents: []string{"51baa8c1"}, - }, - { - Hash: "c3c4b66b64c97ffeecde", - Name: "checkout: moving from B to A", - Status: models.StatusReflog, - UnixTimestamp: 1643150483, - Parents: []string{"51baa8c1"}, - }, - { - Hash: "c3c4b66b64c97ffeecde", - Name: "checkout: moving from A to B", - Status: models.StatusReflog, - UnixTimestamp: 1643150483, - Parents: []string{"51baa8c1"}, - }, - { - Hash: "c3c4b66b64c97ffeecde", - Name: "checkout: moving from master to A", - Status: models.StatusReflog, - UnixTimestamp: 1643150483, - Parents: []string{"51baa8c1"}, - }, - { - Hash: "f4ddf2f0d4be4ccc7efa", - Name: "checkout: moving from A to master", - Status: models.StatusReflog, - UnixTimestamp: 1643149435, - Parents: []string{"51baa8c1"}, - }, - }, - expectedOnlyObtainedNew: false, - expectedError: nil, - }, - { - testName: "some reflog entries where last commit is given", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--format=+%H%x00%ct%x00%gs%x00%P"}, reflogOutput, nil), - - lastReflogCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "c3c4b66b64c97ffeecde", - Name: "checkout: moving from B to A", - Status: models.StatusReflog, - UnixTimestamp: 1643150483, - Parents: []string{"51baa8c1"}, - }), - expectedCommitOpts: []models.NewCommitOpts{ - { - Hash: "c3c4b66b64c97ffeecde", - Name: "checkout: moving from A to B", - Status: models.StatusReflog, - UnixTimestamp: 1643150483, - Parents: []string{"51baa8c1"}, - }, - }, - expectedOnlyObtainedNew: true, - expectedError: nil, - }, - { - testName: "when passing filterPath", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--format=+%H%x00%ct%x00%gs%x00%P", "--follow", "--name-status", "--", "path"}, reflogOutput, nil), - - lastReflogCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "c3c4b66b64c97ffeecde", - Name: "checkout: moving from B to A", - Status: models.StatusReflog, - UnixTimestamp: 1643150483, - Parents: []string{"51baa8c1"}, - }), - filterPath: "path", - expectedCommitOpts: []models.NewCommitOpts{ - { - Hash: "c3c4b66b64c97ffeecde", - Name: "checkout: moving from A to B", - Status: models.StatusReflog, - UnixTimestamp: 1643150483, - Parents: []string{"51baa8c1"}, - }, - }, - expectedOnlyObtainedNew: true, - expectedError: nil, - }, - { - testName: "when passing filterAuthor", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--format=+%H%x00%ct%x00%gs%x00%P", "--author=John Doe "}, reflogOutput, nil), - - lastReflogCommit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "c3c4b66b64c97ffeecde", - Name: "checkout: moving from B to A", - Status: models.StatusReflog, - UnixTimestamp: 1643150483, - Parents: []string{"51baa8c1"}, - }), - filterAuthor: "John Doe ", - expectedCommitOpts: []models.NewCommitOpts{ - { - Hash: "c3c4b66b64c97ffeecde", - Name: "checkout: moving from A to B", - Status: models.StatusReflog, - UnixTimestamp: 1643150483, - Parents: []string{"51baa8c1"}, - }, - }, - expectedOnlyObtainedNew: true, - expectedError: nil, - }, - { - testName: "when command returns error", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--format=+%H%x00%ct%x00%gs%x00%P"}, "", errors.New("haha")), - - lastReflogCommit: nil, - filterPath: "", - expectedCommitOpts: nil, - expectedOnlyObtainedNew: false, - expectedError: errors.New("haha"), - }, - } - - for _, scenario := range scenarios { - t.Run(scenario.testName, func(t *testing.T) { - builder := &ReflogCommitLoader{ - Common: common.NewDummyCommon(), - cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner), - } - - commits, onlyObtainednew, err := builder.GetReflogCommits(hashPool, scenario.lastReflogCommit, scenario.filterPath, scenario.filterAuthor) - assert.Equal(t, scenario.expectedOnlyObtainedNew, onlyObtainednew) - assert.Equal(t, scenario.expectedError, err) - t.Logf("actual commits: \n%s", litter.Sdump(commits)) - var expectedCommits []*models.Commit - if scenario.expectedCommitOpts != nil { - expectedCommits = lo.Map(scenario.expectedCommitOpts, - func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) }) - } - assert.Equal(t, expectedCommits, commits) - - scenario.runner.CheckForMissingCalls() - }) - } -} diff --git a/pkg/commands/git_commands/remote.go b/pkg/commands/git_commands/remote.go deleted file mode 100644 index ca361067976..00000000000 --- a/pkg/commands/git_commands/remote.go +++ /dev/null @@ -1,89 +0,0 @@ -package git_commands - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/gocui" -) - -type RemoteCommands struct { - *GitCommon -} - -func NewRemoteCommands(gitCommon *GitCommon) *RemoteCommands { - return &RemoteCommands{ - GitCommon: gitCommon, - } -} - -func (self *RemoteCommands) AddRemote(name string, url string) error { - cmdArgs := NewGitCmd("remote"). - Arg("add", name, url). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *RemoteCommands) RemoveRemote(name string) error { - cmdArgs := NewGitCmd("remote"). - Arg("remove", name). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *RemoteCommands) RenameRemote(oldRemoteName string, newRemoteName string) error { - cmdArgs := NewGitCmd("remote"). - Arg("rename", oldRemoteName, newRemoteName). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string) error { - cmdArgs := NewGitCmd("remote"). - Arg("set-url", remoteName, updatedUrl). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName string, branchNames []string) error { - cmdArgs := NewGitCmd("push"). - Arg(remoteName, "--delete"). - Arg(branchNames...). - ToArgv() - - return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() -} - -func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string, tagName string) error { - cmdArgs := NewGitCmd("push"). - Arg(remoteName, "--delete", tagName). - ToArgv() - - return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() -} - -// CheckRemoteBranchExists Returns remote branch -func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool { - cmdArgs := NewGitCmd("show-ref"). - Arg("--verify", "--", fmt.Sprintf("refs/remotes/origin/%s", branchName)). - ToArgv() - - _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - - return err == nil -} - -// Resolve what might be a aliased URL into a full URL -// SEE: `man -P 'less +/--get-url +n' git-ls-remote` -func (self *RemoteCommands) GetRemoteURL(remoteName string) (string, error) { - cmdArgs := NewGitCmd("ls-remote"). - Arg("--get-url", remoteName). - ToArgv() - - url, err := self.cmd.New(cmdArgs).RunWithOutput() - return strings.TrimSpace(url), err -} diff --git a/pkg/commands/git_commands/remote_loader.go b/pkg/commands/git_commands/remote_loader.go deleted file mode 100644 index 2e632def430..00000000000 --- a/pkg/commands/git_commands/remote_loader.go +++ /dev/null @@ -1,134 +0,0 @@ -package git_commands - -import ( - "fmt" - "slices" - "strings" - "sync" - - gogit "github.com/jesseduffield/go-git/v5" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type RemoteLoader struct { - *common.Common - cmd oscommands.ICmdObjBuilder - getGoGitRemotes func() ([]*gogit.Remote, error) -} - -func NewRemoteLoader( - common *common.Common, - cmd oscommands.ICmdObjBuilder, - getGoGitRemotes func() ([]*gogit.Remote, error), -) *RemoteLoader { - return &RemoteLoader{ - Common: common, - cmd: cmd, - getGoGitRemotes: getGoGitRemotes, - } -} - -func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) { - wg := sync.WaitGroup{} - wg.Add(1) - - var remoteBranchesByRemoteName map[string][]*models.RemoteBranch - var remoteBranchesErr error - go utils.Safe(func() { - defer wg.Done() - - remoteBranchesByRemoteName, remoteBranchesErr = self.getRemoteBranchesByRemoteName() - }) - - goGitRemotes, err := self.getGoGitRemotes() - if err != nil { - return nil, err - } - - wg.Wait() - - if remoteBranchesErr != nil { - return nil, remoteBranchesErr - } - - remotes := lo.Map(goGitRemotes, func(goGitRemote *gogit.Remote, _ int) *models.Remote { - remoteName := goGitRemote.Config().Name - branches := remoteBranchesByRemoteName[remoteName] - - return &models.Remote{ - Name: goGitRemote.Config().Name, - Urls: goGitRemote.Config().URLs, - Branches: branches, - } - }) - - // now lets sort our remotes by name alphabetically - slices.SortFunc(remotes, func(a, b *models.Remote) int { - // we want origin at the top because we'll be most likely to want it - if a.Name == "origin" { - return -1 - } - if b.Name == "origin" { - return 1 - } - return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)) - }) - - return remotes, nil -} - -func (self *RemoteLoader) getRemoteBranchesByRemoteName() (map[string][]*models.RemoteBranch, error) { - remoteBranchesByRemoteName := make(map[string][]*models.RemoteBranch) - - var sortOrder string - switch strings.ToLower(self.UserConfig().Git.RemoteBranchSortOrder) { - case "alphabetical": - sortOrder = "refname" - case "date": - sortOrder = "-committerdate" - default: - sortOrder = "refname" - } - - cmdArgs := NewGitCmd("for-each-ref"). - Arg(fmt.Sprintf("--sort=%s", sortOrder)). - Arg("--format=%(refname)"). - Arg("refs/remotes"). - ToArgv() - - err := self.cmd.New(cmdArgs).DontLog().RunAndProcessLines(func(line string) (bool, error) { - line = strings.TrimSpace(line) - - split := strings.SplitN(line, "/", 4) - if len(split) != 4 { - return false, nil - } - remoteName := split[2] - name := split[3] - - if name == "HEAD" { - return false, nil - } - - _, ok := remoteBranchesByRemoteName[remoteName] - if !ok { - remoteBranchesByRemoteName[remoteName] = []*models.RemoteBranch{} - } - - remoteBranchesByRemoteName[remoteName] = append(remoteBranchesByRemoteName[remoteName], - &models.RemoteBranch{ - Name: name, - RemoteName: remoteName, - }) - return false, nil - }) - if err != nil { - return nil, err - } - - return remoteBranchesByRemoteName, nil -} diff --git a/pkg/commands/git_commands/repo_paths.go b/pkg/commands/git_commands/repo_paths.go deleted file mode 100644 index c64debfc5aa..00000000000 --- a/pkg/commands/git_commands/repo_paths.go +++ /dev/null @@ -1,177 +0,0 @@ -package git_commands - -import ( - ioFs "io/fs" - "os" - "path/filepath" - "strings" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/spf13/afero" -) - -type RepoPaths struct { - worktreePath string - worktreeGitDirPath string - repoPath string - repoGitDirPath string - repoName string - isBareRepo bool -} - -// Path to the current worktree. If we're in the main worktree, this will -// be the same as RepoPath() -func (self *RepoPaths) WorktreePath() string { - return self.worktreePath -} - -// Path of the worktree's git dir. -// If we're in the main worktree, this will be the .git dir under the RepoPath(). -// If we're in a linked worktree, it will be the directory pointed at by the worktree's .git file -func (self *RepoPaths) WorktreeGitDirPath() string { - return self.worktreeGitDirPath -} - -// Path of the repo. If we're in a the main worktree, this will be the same as WorktreePath() -// If we're in a bare repo, it will be the parent folder of the bare repo -func (self *RepoPaths) RepoPath() string { - return self.repoPath -} - -// path of the git-dir for the repo. -// If this is a bare repo, it will be the location of the bare repo -// If this is a non-bare repo, it will be the location of the .git dir in -// the main worktree. -func (self *RepoPaths) RepoGitDirPath() string { - return self.repoGitDirPath -} - -// Name of the repo. Basename of the folder containing the repo. -func (self *RepoPaths) RepoName() string { - return self.repoName -} - -func (self *RepoPaths) IsBareRepo() bool { - return self.isBareRepo -} - -// Returns the repo paths for a typical repo -func MockRepoPaths(currentPath string) *RepoPaths { - return &RepoPaths{ - worktreePath: currentPath, - worktreeGitDirPath: filepath.Join(currentPath, ".git"), - repoPath: currentPath, - repoGitDirPath: filepath.Join(currentPath, ".git"), - repoName: "lazygit", - isBareRepo: false, - } -} - -func GetRepoPaths( - cmd oscommands.ICmdObjBuilder, - version *GitVersion, -) (*RepoPaths, error) { - cwd, err := os.Getwd() - if err != nil { - return nil, err - } - return GetRepoPathsForDir(cwd, cmd) -} - -func GetRepoPathsForDir( - dir string, - cmd oscommands.ICmdObjBuilder, -) (*RepoPaths, error) { - gitDirOutput, err := callGitRevParseWithDir(cmd, dir, "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree") - if err != nil { - return nil, err - } - - gitDirResults := strings.Split(utils.NormalizeLinefeeds(gitDirOutput), "\n") - worktreePath := gitDirResults[0] - worktreeGitDirPath := gitDirResults[1] - repoGitDirPath := gitDirResults[2] - isBareRepo := gitDirResults[3] == "true" - - // If we're in a submodule, --show-superproject-working-tree will return - // a value, meaning gitDirResults will be length 5. In that case - // return the worktree path as the repoPath. Otherwise we're in a - // normal repo or a worktree so return the parent of the git common - // dir (repoGitDirPath) - isSubmodule := len(gitDirResults) == 5 - - var repoPath string - if isSubmodule { - repoPath = worktreePath - } else { - repoPath = filepath.Dir(repoGitDirPath) - } - repoName := filepath.Base(repoPath) - - return &RepoPaths{ - worktreePath: worktreePath, - worktreeGitDirPath: worktreeGitDirPath, - repoPath: repoPath, - repoGitDirPath: repoGitDirPath, - repoName: repoName, - isBareRepo: isBareRepo, - }, nil -} - -func callGitRevParseWithDir( - cmd oscommands.ICmdObjBuilder, - dir string, - gitRevArgs ...string, -) (string, error) { - gitRevParse := NewGitCmd("rev-parse").Arg("--path-format=absolute").Arg(gitRevArgs...) - if dir != "" { - gitRevParse.Dir(dir) - } - - gitCmd := cmd.New(gitRevParse.ToArgv()).DontLog() - res, err := gitCmd.RunWithOutput() - if err != nil { - return "", errors.Errorf("'%s' failed: %v", gitCmd.ToString(), err) - } - return strings.TrimSpace(res), nil -} - -// Returns the paths of linked worktrees -func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string { - result := []string{} - // For each directory in this path we're going to cat the `gitdir` file and append its contents to our result - // That file points us to the `.git` file in the worktree. - worktreeGitDirsPath := filepath.Join(repoGitDirPath, "worktrees") - - // ensure the directory exists - _, err := fs.Stat(worktreeGitDirsPath) - if err != nil { - return result - } - - _ = afero.Walk(fs, worktreeGitDirsPath, func(currPath string, info ioFs.FileInfo, err error) error { - if err != nil { - return err - } - - if !info.IsDir() { - return nil - } - - gitDirPath := filepath.Join(currPath, "gitdir") - gitDirBytes, err := afero.ReadFile(fs, gitDirPath) - if err != nil { - // ignoring error - return nil - } - trimmedGitDir := strings.TrimSpace(string(gitDirBytes)) - // removing the .git part - worktreeDir := filepath.Dir(trimmedGitDir) - result = append(result, worktreeDir) - return nil - }) - - return result -} diff --git a/pkg/commands/git_commands/repo_paths_test.go b/pkg/commands/git_commands/repo_paths_test.go deleted file mode 100644 index 29c40acee90..00000000000 --- a/pkg/commands/git_commands/repo_paths_test.go +++ /dev/null @@ -1,216 +0,0 @@ -package git_commands - -import ( - "fmt" - "runtime" - "strings" - "testing" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/samber/lo" - "github.com/stretchr/testify/assert" -) - -type ( - argFn func() []string - errFn func(getRevParseArgs argFn) error -) - -type Scenario struct { - Name string - BeforeFunc func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) - Path string - Expected *RepoPaths - Err errFn -} - -func TestGetRepoPaths(t *testing.T) { - scenarios := []Scenario{ - { - Name: "typical case", - BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) { - // setup for main worktree - mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{ - // --show-toplevel - `C:\path\to\repo`, - // --git-dir - `C:\path\to\repo\.git`, - // --git-common-dir - `C:\path\to\repo\.git`, - // --is-bare-repository - "false", - // --show-superproject-working-tree - }, []string{ - // --show-toplevel - "/path/to/repo", - // --git-dir - "/path/to/repo/.git", - // --git-common-dir - "/path/to/repo/.git", - // --is-bare-repository - "false", - // --show-superproject-working-tree - }) - runner.ExpectGitArgs( - append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"), - strings.Join(mockOutput, "\n"), - nil) - }, - Path: "/path/to/repo", - Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{ - worktreePath: `C:\path\to\repo`, - worktreeGitDirPath: `C:\path\to\repo\.git`, - repoPath: `C:\path\to\repo`, - repoGitDirPath: `C:\path\to\repo\.git`, - repoName: `repo`, - isBareRepo: false, - }, &RepoPaths{ - worktreePath: "/path/to/repo", - worktreeGitDirPath: "/path/to/repo/.git", - repoPath: "/path/to/repo", - repoGitDirPath: "/path/to/repo/.git", - repoName: "repo", - isBareRepo: false, - }), - Err: nil, - }, - { - Name: "bare repo", - BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) { - // setup for main worktree - mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{ - // --show-toplevel - `C:\path\to\repo`, - // --git-dir - `C:\path\to\bare_repo\bare.git`, - // --git-common-dir - `C:\path\to\bare_repo\bare.git`, - // --is-bare-repository - `true`, - // --show-superproject-working-tree - }, []string{ - // --show-toplevel - "/path/to/repo", - // --git-dir - "/path/to/bare_repo/bare.git", - // --git-common-dir - "/path/to/bare_repo/bare.git", - // --is-bare-repository - "true", - // --show-superproject-working-tree - }) - runner.ExpectGitArgs( - append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"), - strings.Join(mockOutput, "\n"), - nil) - }, - Path: "/path/to/repo", - Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{ - worktreePath: `C:\path\to\repo`, - worktreeGitDirPath: `C:\path\to\bare_repo\bare.git`, - repoPath: `C:\path\to\bare_repo`, - repoGitDirPath: `C:\path\to\bare_repo\bare.git`, - repoName: `bare_repo`, - isBareRepo: true, - }, &RepoPaths{ - worktreePath: "/path/to/repo", - worktreeGitDirPath: "/path/to/bare_repo/bare.git", - repoPath: "/path/to/bare_repo", - repoGitDirPath: "/path/to/bare_repo/bare.git", - repoName: "bare_repo", - isBareRepo: true, - }), - Err: nil, - }, - { - Name: "submodule", - BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) { - mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{ - // --show-toplevel - `C:\path\to\repo\submodule1`, - // --git-dir - `C:\path\to\repo\.git\modules\submodule1`, - // --git-common-dir - `C:\path\to\repo\.git\modules\submodule1`, - // --is-bare-repository - `false`, - // --show-superproject-working-tree - `C:\path\to\repo`, - }, []string{ - // --show-toplevel - "/path/to/repo/submodule1", - // --git-dir - "/path/to/repo/.git/modules/submodule1", - // --git-common-dir - "/path/to/repo/.git/modules/submodule1", - // --is-bare-repository - "false", - // --show-superproject-working-tree - "/path/to/repo", - }) - runner.ExpectGitArgs( - append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"), - strings.Join(mockOutput, "\n"), - nil) - }, - Path: "/path/to/repo/submodule1", - Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{ - worktreePath: `C:\path\to\repo\submodule1`, - worktreeGitDirPath: `C:\path\to\repo\.git\modules\submodule1`, - repoPath: `C:\path\to\repo\submodule1`, - repoGitDirPath: `C:\path\to\repo\.git\modules\submodule1`, - repoName: `submodule1`, - isBareRepo: false, - }, &RepoPaths{ - worktreePath: "/path/to/repo/submodule1", - worktreeGitDirPath: "/path/to/repo/.git/modules/submodule1", - repoPath: "/path/to/repo/submodule1", - repoGitDirPath: "/path/to/repo/.git/modules/submodule1", - repoName: "submodule1", - isBareRepo: false, - }), - Err: nil, - }, - { - Name: "git rev-parse returns an error", - BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) { - runner.ExpectGitArgs( - append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"), - "", - errors.New("fatal: invalid gitfile format: /path/to/repo/worktree2/.git")) - }, - Path: "/path/to/repo/worktree2", - Expected: nil, - Err: func(getRevParseArgs argFn) error { - args := strings.Join(getRevParseArgs(), " ") - return fmt.Errorf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --is-bare-repository --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.Name, func(t *testing.T) { - runner := oscommands.NewFakeRunner(t) - cmd := oscommands.NewDummyCmdObjBuilder(runner) - - getRevParseArgs := func() []string { - return []string{"rev-parse", "--path-format=absolute"} - } - // prepare the filesystem for the scenario - s.BeforeFunc(runner, getRevParseArgs) - - repoPaths, err := GetRepoPathsForDir("", cmd) - - // check the error and the paths - if s.Err != nil { - scenarioErr := s.Err(getRevParseArgs) - assert.Error(t, err) - assert.EqualError(t, err, scenarioErr.Error()) - } else { - assert.Nil(t, err) - assert.Equal(t, s.Expected, repoPaths) - } - }) - } -} diff --git a/pkg/commands/git_commands/stash.go b/pkg/commands/git_commands/stash.go deleted file mode 100644 index 0e5eb299d50..00000000000 --- a/pkg/commands/git_commands/stash.go +++ /dev/null @@ -1,218 +0,0 @@ -package git_commands - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -type StashCommands struct { - *GitCommon - fileLoader *FileLoader - workingTree *WorkingTreeCommands -} - -func NewStashCommands( - gitCommon *GitCommon, - fileLoader *FileLoader, - workingTree *WorkingTreeCommands, -) *StashCommands { - return &StashCommands{ - GitCommon: gitCommon, - fileLoader: fileLoader, - workingTree: workingTree, - } -} - -func (self *StashCommands) DropNewest() error { - cmdArgs := NewGitCmd("stash").Arg("drop").ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *StashCommands) Drop(index int) error { - cmdArgs := NewGitCmd("stash").Arg("drop", fmt.Sprintf("refs/stash@{%d}", index)). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *StashCommands) Pop(index int) error { - cmdArgs := NewGitCmd("stash").Arg("pop", fmt.Sprintf("refs/stash@{%d}", index)). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *StashCommands) Apply(index int) error { - cmdArgs := NewGitCmd("stash").Arg("apply", fmt.Sprintf("refs/stash@{%d}", index)). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// Push push stash -func (self *StashCommands) Push(message string) error { - cmdArgs := NewGitCmd("stash").Arg("push", "-m", message). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *StashCommands) Store(hash string, message string) error { - trimmedMessage := strings.Trim(message, " \t") - - cmdArgs := NewGitCmd("stash").Arg("store"). - ArgIf(trimmedMessage != "", "-m", trimmedMessage). - Arg(hash). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *StashCommands) Hash(index int) (string, error) { - cmdArgs := NewGitCmd("rev-parse"). - Arg(fmt.Sprintf("refs/stash@{%d}", index)). - ToArgv() - - hash, _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutputs() - return strings.Trim(hash, "\r\n"), err -} - -func (self *StashCommands) ShowStashEntryCmdObj(index int) *oscommands.CmdObj { - extDiffCmd := self.pagerConfig.GetExternalDiffCommand() - useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig() - - // "-u" is the same as "--include-untracked", but the latter fails in older git versions for some reason - cmdArgs := NewGitCmd("stash").Arg("show"). - Arg("-p"). - Arg("--stat"). - Arg("-u"). - ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd). - ArgIfElse(extDiffCmd != "" || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff"). - Arg(fmt.Sprintf("--color=%s", self.pagerConfig.GetColorArg())). - Arg(fmt.Sprintf("--unified=%d", self.UserConfig().Git.DiffContextSize)). - ArgIf(self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space"). - Arg(fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold)). - Arg(fmt.Sprintf("refs/stash@{%d}", index)). - Dir(self.repoPaths.worktreePath). - ToArgv() - - return self.cmd.New(cmdArgs).DontLog() -} - -func (self *StashCommands) StashAndKeepIndex(message string) error { - cmdArgs := NewGitCmd("stash").Arg("push", "--keep-index", "-m", message). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *StashCommands) StashUnstagedChanges(message string) error { - if err := self.cmd.New( - NewGitCmd("commit"). - Arg("--no-verify", "-m", "[lazygit] stashing unstaged changes"). - ToArgv(), - ).Run(); err != nil { - return err - } - if err := self.Push(message); err != nil { - return err - } - - if err := self.cmd.New( - NewGitCmd("reset").Arg("--soft", "HEAD^").ToArgv(), - ).Run(); err != nil { - return err - } - return nil -} - -// SaveStagedChanges stashes only the currently staged changes. -func (self *StashCommands) SaveStagedChanges(message string) error { - if self.version.IsAtLeast(2, 35, 0) { - return self.cmd.New(NewGitCmd("stash").Arg("push").Arg("--staged").Arg("-m", message).ToArgv()).Run() - } - - // Git versions older than 2.35.0 don't support the --staged flag, so we - // need to fall back to a more complex solution. - // Shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible - // - // Note that this method has a few bugs: - // - it fails when there are *only* staged changes - // - it fails when staged and unstaged changes within a single file are too close together - // We don't bother fixing these, because users can simply update git when - // they are affected by these issues. - - // wrap in 'writing', which uses a mutex - if err := self.cmd.New( - NewGitCmd("stash").Arg("--keep-index").ToArgv(), - ).Run(); err != nil { - return err - } - - if err := self.Push(message); err != nil { - return err - } - - if err := self.cmd.New( - NewGitCmd("stash").Arg("apply", "refs/stash@{1}").ToArgv(), - ).Run(); err != nil { - return err - } - - if err := self.os.PipeCommands( - self.cmd.New(NewGitCmd("stash").Arg("show", "-p").ToArgv()), - self.cmd.New(NewGitCmd("apply").Arg("-R").ToArgv()), - ); err != nil { - return err - } - - if err := self.cmd.New( - NewGitCmd("stash").Arg("drop", "refs/stash@{1}").ToArgv(), - ).Run(); err != nil { - return err - } - - // if you had staged an untracked file, that will now appear as 'AD' in git status - // meaning it's deleted in your working tree but added in your index. Given that it's - // now safely stashed, we need to remove it. - files := self.fileLoader. - GetStatusFiles(GetStatusFileOptions{}) - - for _, file := range files { - if file.ShortStatus == "AD" { - if err := self.workingTree.UnStageFile(file.Names(), false); err != nil { - return err - } - } - } - - return nil -} - -func (self *StashCommands) StashIncludeUntrackedChanges(message string) error { - return self.cmd.New( - NewGitCmd("stash").Arg("push", "--include-untracked", "-m", message). - ToArgv(), - ).Run() -} - -func (self *StashCommands) Rename(index int, message string) error { - hash, err := self.Hash(index) - if err != nil { - return err - } - - if err := self.Drop(index); err != nil { - return err - } - - err = self.Store(hash, message) - if err != nil { - return err - } - - return nil -} diff --git a/pkg/commands/git_commands/stash_loader.go b/pkg/commands/git_commands/stash_loader.go deleted file mode 100644 index a1e16ae4725..00000000000 --- a/pkg/commands/git_commands/stash_loader.go +++ /dev/null @@ -1,103 +0,0 @@ -package git_commands - -import ( - "regexp" - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type StashLoader struct { - *common.Common - cmd oscommands.ICmdObjBuilder -} - -func NewStashLoader( - common *common.Common, - cmd oscommands.ICmdObjBuilder, -) *StashLoader { - return &StashLoader{ - Common: common, - cmd: cmd, - } -} - -func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry { - if filterPath == "" { - return self.getUnfilteredStashEntries() - } - - cmdArgs := NewGitCmd("stash").Arg("list", "--name-only", "--pretty=%gd:%H|%ct|%gs").ToArgv() - rawString, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - if err != nil { - return self.getUnfilteredStashEntries() - } - stashEntries := []*models.StashEntry{} - var currentStashEntry *models.StashEntry - lines := utils.SplitLines(rawString) - isAStash := func(line string) bool { return strings.HasPrefix(line, "stash@{") } - re := regexp.MustCompile(`^stash@\{(\d+)\}:(.*)$`) - -outer: - for i := 0; i < len(lines); i++ { - match := re.FindStringSubmatch(lines[i]) - if match == nil { - continue - } - idx, err := strconv.Atoi(match[1]) - if err != nil { - return self.getUnfilteredStashEntries() - } - currentStashEntry = stashEntryFromLine(match[2], idx) - for i+1 < len(lines) && !isAStash(lines[i+1]) { - i++ - if strings.HasPrefix(lines[i], filterPath) { - stashEntries = append(stashEntries, currentStashEntry) - continue outer - } - } - } - return stashEntries -} - -func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry { - cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%H|%ct|%gs").ToArgv() - - rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - return lo.Map(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry { - return stashEntryFromLine(line, index) - }) -} - -func stashEntryFromLine(line string, index int) *models.StashEntry { - model := &models.StashEntry{ - Name: line, - Index: index, - } - - hash, line, ok := strings.Cut(line, "|") - if !ok { - return model - } - model.Hash = hash - - tstr, msg, ok := strings.Cut(line, "|") - if !ok { - return model - } - - t, err := strconv.ParseInt(tstr, 10, 64) - if err != nil { - return model - } - - model.Name = msg - model.Recency = utils.UnixToTimeAgo(t) - - return model -} diff --git a/pkg/commands/git_commands/stash_loader_test.go b/pkg/commands/git_commands/stash_loader_test.go deleted file mode 100644 index 7ed000589d4..00000000000 --- a/pkg/commands/git_commands/stash_loader_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package git_commands - -import ( - "fmt" - "testing" - "time" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/stretchr/testify/assert" -) - -func TestGetStashEntries(t *testing.T) { - type scenario struct { - testName string - filterPath string - runner oscommands.ICmdObjRunner - expectedStashEntries []*models.StashEntry - } - - hoursAgo := time.Now().Unix() - 3*3600 - 1800 - daysAgo := time.Now().Unix() - 3*3600*24 - 3600*12 - - scenarios := []scenario{ - { - "No stash entries found", - "", - oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%H|%ct|%gs"}, "", nil), - []*models.StashEntry{}, - }, - { - "Several stash entries found", - "", - oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%H|%ct|%gs"}, - fmt.Sprintf("fa1afe1|%d|WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00deadbeef|%d|WIP on master: bb86a3f update github template\x00", - hoursAgo, - daysAgo, - ), nil), - []*models.StashEntry{ - { - Index: 0, - Name: "WIP on add-pkg-commands-test: 55c6af2 increase parallel build", - Recency: "3h", - Hash: "fa1afe1", - }, - { - Index: 1, - Name: "WIP on master: bb86a3f update github template", - Recency: "3d", - Hash: "deadbeef", - }, - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - cmd := oscommands.NewDummyCmdObjBuilder(s.runner) - - loader := NewStashLoader(common.NewDummyCommon(), cmd) - - assert.EqualValues(t, s.expectedStashEntries, loader.GetStashEntries(s.filterPath)) - }) - } -} diff --git a/pkg/commands/git_commands/stash_test.go b/pkg/commands/git_commands/stash_test.go deleted file mode 100644 index a942a4e98a9..00000000000 --- a/pkg/commands/git_commands/stash_test.go +++ /dev/null @@ -1,227 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/stretchr/testify/assert" -) - -func TestStashDrop(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"stash", "drop", "refs/stash@{1}"}, "Dropped refs/stash@{1} (98e9cca532c37c766107093010c72e26f2c24c04)\n", nil) - instance := buildStashCommands(commonDeps{runner: runner}) - - assert.NoError(t, instance.Drop(1)) - runner.CheckForMissingCalls() -} - -func TestStashApply(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"stash", "apply", "refs/stash@{1}"}, "", nil) - instance := buildStashCommands(commonDeps{runner: runner}) - - assert.NoError(t, instance.Apply(1)) - runner.CheckForMissingCalls() -} - -func TestStashPop(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"stash", "pop", "refs/stash@{1}"}, "", nil) - instance := buildStashCommands(commonDeps{runner: runner}) - - assert.NoError(t, instance.Pop(1)) - runner.CheckForMissingCalls() -} - -func TestStashSave(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"stash", "push", "-m", "A stash message"}, "", nil) - instance := buildStashCommands(commonDeps{runner: runner}) - - assert.NoError(t, instance.Push("A stash message")) - runner.CheckForMissingCalls() -} - -func TestStashStore(t *testing.T) { - type scenario struct { - testName string - hash string - message string - expected []string - } - - scenarios := []scenario{ - { - testName: "Non-empty message", - hash: "0123456789abcdef", - message: "New stash name", - expected: []string{"stash", "store", "-m", "New stash name", "0123456789abcdef"}, - }, - { - testName: "Empty message", - hash: "0123456789abcdef", - message: "", - expected: []string{"stash", "store", "0123456789abcdef"}, - }, - { - testName: "Space message", - hash: "0123456789abcdef", - message: " ", - expected: []string{"stash", "store", "0123456789abcdef"}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs(s.expected, "", nil) - instance := buildStashCommands(commonDeps{runner: runner}) - - assert.NoError(t, instance.Store(s.hash, s.message)) - runner.CheckForMissingCalls() - }) - } -} - -func TestStashHash(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rev-parse", "refs/stash@{5}"}, "14d94495194651adfd5f070590df566c11d28243\n", nil) - instance := buildStashCommands(commonDeps{runner: runner}) - - hash, err := instance.Hash(5) - assert.NoError(t, err) - assert.Equal(t, "14d94495194651adfd5f070590df566c11d28243", hash) - runner.CheckForMissingCalls() -} - -func TestStashStashEntryCmdObj(t *testing.T) { - type scenario struct { - testName string - index int - contextSize uint64 - similarityThreshold int - ignoreWhitespace bool - pagerConfig *config.PagingConfig - expected []string - } - - scenarios := []scenario{ - { - testName: "Default case", - index: 5, - contextSize: 3, - similarityThreshold: 50, - ignoreWhitespace: false, - expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"}, - }, - { - testName: "Show diff with custom context size", - index: 5, - contextSize: 77, - similarityThreshold: 50, - ignoreWhitespace: false, - expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=77", "--find-renames=50%", "refs/stash@{5}"}, - }, - { - testName: "Show diff with custom similarity threshold", - index: 5, - contextSize: 3, - similarityThreshold: 33, - ignoreWhitespace: false, - expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=3", "--find-renames=33%", "refs/stash@{5}"}, - }, - { - testName: "Show diff with external diff command", - index: 5, - contextSize: 3, - similarityThreshold: 50, - ignoreWhitespace: false, - pagerConfig: &config.PagingConfig{ExternalDiffCommand: "difft --color=always"}, - expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "stash", "show", "-p", "--stat", "-u", "--ext-diff", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"}, - }, - { - testName: "Show diff using git's external diff config", - index: 5, - contextSize: 3, - similarityThreshold: 50, - ignoreWhitespace: false, - pagerConfig: &config.PagingConfig{UseExternalDiffGitConfig: true}, - expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--ext-diff", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"}, - }, - { - testName: "Default case", - index: 5, - contextSize: 3, - similarityThreshold: 50, - ignoreWhitespace: true, - expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=3", "--ignore-all-space", "--find-renames=50%", "refs/stash@{5}"}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - userConfig := config.GetDefaultConfig() - userConfig.Git.IgnoreWhitespaceInDiffView = s.ignoreWhitespace - userConfig.Git.DiffContextSize = s.contextSize - userConfig.Git.RenameSimilarityThreshold = s.similarityThreshold - if s.pagerConfig != nil { - userConfig.Git.Pagers = []config.PagingConfig{*s.pagerConfig} - } - repoPaths := RepoPaths{ - worktreePath: "/path/to/worktree", - } - instance := buildStashCommands(commonDeps{userConfig: userConfig, appState: &config.AppState{}, repoPaths: &repoPaths}) - - cmdStr := instance.ShowStashEntryCmdObj(s.index).Args() - assert.Equal(t, s.expected, cmdStr) - }) - } -} - -func TestStashRename(t *testing.T) { - type scenario struct { - testName string - index int - message string - expectedHashCmd []string - hashResult string - expectedDropCmd []string - expectedStoreCmd []string - } - - scenarios := []scenario{ - { - testName: "Default case", - index: 3, - message: "New message", - expectedHashCmd: []string{"rev-parse", "refs/stash@{3}"}, - hashResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n", - expectedDropCmd: []string{"stash", "drop", "refs/stash@{3}"}, - expectedStoreCmd: []string{"stash", "store", "-m", "New message", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"}, - }, - { - testName: "Empty message", - index: 4, - message: "", - expectedHashCmd: []string{"rev-parse", "refs/stash@{4}"}, - hashResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n", - expectedDropCmd: []string{"stash", "drop", "refs/stash@{4}"}, - expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs(s.expectedHashCmd, s.hashResult, nil). - ExpectGitArgs(s.expectedDropCmd, "", nil). - ExpectGitArgs(s.expectedStoreCmd, "", nil) - instance := buildStashCommands(commonDeps{runner: runner}) - - err := instance.Rename(s.index, s.message) - assert.NoError(t, err) - }) - } -} diff --git a/pkg/commands/git_commands/status.go b/pkg/commands/git_commands/status.go deleted file mode 100644 index ff09e22bc31..00000000000 --- a/pkg/commands/git_commands/status.go +++ /dev/null @@ -1,94 +0,0 @@ -package git_commands - -import ( - "os" - "path/filepath" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" -) - -type StatusCommands struct { - *GitCommon -} - -func NewStatusCommands( - gitCommon *GitCommon, -) *StatusCommands { - return &StatusCommands{ - GitCommon: gitCommon, - } -} - -func (self *StatusCommands) WorkingTreeState() models.WorkingTreeState { - result := models.WorkingTreeState{} - result.Rebasing, _ = self.IsInRebase() - result.Merging, _ = self.IsInMergeState() - result.CherryPicking, _ = self.IsInCherryPick() - result.Reverting, _ = self.IsInRevert() - return result -} - -func (self *StatusCommands) IsBareRepo() bool { - return self.repoPaths.isBareRepo -} - -func (self *StatusCommands) IsInRebase() (bool, error) { - exists, err := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge")) - if err == nil && exists { - return true, nil - } - return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply")) -} - -// IsInMergeState states whether we are still mid-merge -func (self *StatusCommands) IsInMergeState() (bool, error) { - return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "MERGE_HEAD")) -} - -func (self *StatusCommands) IsInCherryPick() (bool, error) { - exists, err := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "CHERRY_PICK_HEAD")) - if err != nil || !exists { - return exists, err - } - // Sometimes, CHERRY_PICK_HEAD is present during rebases even if no - // cherry-pick is in progress. I suppose this is because rebase used to be - // implemented as a series of cherry-picks, so this could be remnants of - // code that is shared between cherry-pick and rebase, or something. The way - // to tell if this is the case is to check for the presence of the - // stopped-sha file, which records the sha of the last pick that was - // executed before the rebase stopped, and seeing if the sha in that file is - // the same as the one in CHERRY_PICK_HEAD. - cherryPickHead, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "CHERRY_PICK_HEAD")) - if err != nil { - return false, err - } - stoppedSha, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge", "stopped-sha")) - if err != nil { - // If we get an error we assume the file doesn't exist - return true, nil - } - cherryPickHeadStr := strings.TrimSpace(string(cherryPickHead)) - stoppedShaStr := strings.TrimSpace(string(stoppedSha)) - // Need to use HasPrefix here because the cherry-pick HEAD is a full sha1, - // but stopped-sha is an abbreviated sha1 - if strings.HasPrefix(cherryPickHeadStr, stoppedShaStr) { - return false, nil - } - return true, nil -} - -func (self *StatusCommands) IsInRevert() (bool, error) { - return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "REVERT_HEAD")) -} - -// Full ref (e.g. "refs/heads/mybranch") of the branch that is currently -// being rebased, or empty string when we're not in a rebase -func (self *StatusCommands) BranchBeingRebased() string { - for _, dir := range []string{"rebase-merge", "rebase-apply"} { - if bytesContent, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), dir, "head-name")); err == nil { - return strings.TrimSpace(string(bytesContent)) - } - } - return "" -} diff --git a/pkg/commands/git_commands/submodule.go b/pkg/commands/git_commands/submodule.go deleted file mode 100644 index f06e1013428..00000000000 --- a/pkg/commands/git_commands/submodule.go +++ /dev/null @@ -1,282 +0,0 @@ -package git_commands - -import ( - "bufio" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -// .gitmodules looks like this: -// [submodule "mysubmodule"] -// path = blah/mysubmodule -// url = git@github.com:subbo.git - -type SubmoduleCommands struct { - *GitCommon -} - -func NewSubmoduleCommands(gitCommon *GitCommon) *SubmoduleCommands { - return &SubmoduleCommands{ - GitCommon: gitCommon, - } -} - -func (self *SubmoduleCommands) GetConfigs(parentModule *models.SubmoduleConfig) ([]*models.SubmoduleConfig, error) { - gitModulesPath := ".gitmodules" - if parentModule != nil { - gitModulesPath = filepath.Join(parentModule.FullPath(), gitModulesPath) - } - file, err := os.Open(gitModulesPath) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - return nil, err - } - defer file.Close() - - scanner := bufio.NewScanner(file) - scanner.Split(bufio.ScanLines) - - firstMatch := func(str string, regex string) (string, bool) { - re := regexp.MustCompile(regex) - matches := re.FindStringSubmatch(str) - - if len(matches) > 0 { - return matches[1], true - } - return "", false - } - - configs := []*models.SubmoduleConfig{} - lastConfigIdx := -1 - for scanner.Scan() { - line := scanner.Text() - - if name, ok := firstMatch(line, `\[submodule "(.*)"\]`); ok { - configs = append(configs, &models.SubmoduleConfig{ - Name: name, ParentModule: parentModule, - }) - lastConfigIdx = len(configs) - 1 - continue - } - - if lastConfigIdx != -1 { - if path, ok := firstMatch(line, `\s*path\s*=\s*(.*)\s*`); ok { - configs[lastConfigIdx].Path = path - nestedConfigs, err := self.GetConfigs(configs[lastConfigIdx]) - if err == nil { - configs = append(configs, nestedConfigs...) - } - } else if url, ok := firstMatch(line, `\s*url\s*=\s*(.*)\s*`); ok { - configs[lastConfigIdx].Url = url - } - } - } - - return configs, nil -} - -func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error { - // if the path does not exist then it hasn't yet been initialized so we'll swallow the error - // because the intention here is to have no dirty worktree state - if _, err := os.Stat(submodule.Path); os.IsNotExist(err) { - self.Log.Infof("submodule path %s does not exist, returning", submodule.FullPath()) - return nil - } - - cmdArgs := NewGitCmd("stash"). - Dir(submodule.FullPath()). - Arg("--include-untracked"). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error { - parentDir := "" - if submodule.ParentModule != nil { - parentDir = submodule.ParentModule.FullPath() - } - cmdArgs := NewGitCmd("submodule"). - Arg("update", "--init", "--force", "--", submodule.Path). - DirIf(parentDir != "", parentDir). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *SubmoduleCommands) UpdateAll() error { - // not doing an --init here because the user probably doesn't want that - cmdArgs := NewGitCmd("submodule").Arg("update", "--force").ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error { - // based on https://gist.github.com/myusuf3/7f645819ded92bda6677 - - if submodule.ParentModule != nil { - wd, err := os.Getwd() - if err != nil { - return err - } - - err = os.Chdir(submodule.ParentModule.FullPath()) - if err != nil { - return err - } - - defer func() { _ = os.Chdir(wd) }() - } - - if err := self.cmd.New( - NewGitCmd("submodule"). - Arg("deinit", "--force", "--", submodule.Path).ToArgv(), - ).Run(); err != nil { - if !strings.Contains(err.Error(), "did not match any file(s) known to git") { - return err - } - - if err := self.cmd.New( - NewGitCmd("config"). - Arg("--file", ".gitmodules", "--remove-section", "submodule."+submodule.Path). - ToArgv(), - ).Run(); err != nil { - return err - } - - if err := self.cmd.New( - NewGitCmd("config"). - Arg("--remove-section", "submodule."+submodule.Path). - ToArgv(), - ).Run(); err != nil { - return err - } - } - - if err := self.cmd.New( - NewGitCmd("rm").Arg("--force", "-r", submodule.Path).ToArgv(), - ).Run(); err != nil { - // if the directory isn't there then that's fine - self.Log.Error(err) - } - - // We may in fact want to use the repo's git dir path but git docs say not to - // mix submodules and worktrees anyway. - return os.RemoveAll(submodule.GitDirPath(self.repoPaths.repoGitDirPath)) -} - -func (self *SubmoduleCommands) Add(name string, path string, url string) error { - cmdArgs := NewGitCmd("submodule"). - Arg("add"). - Arg("--force"). - Arg("--name"). - Arg(name). - Arg("--"). - Arg(url). - Arg(path). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *SubmoduleCommands) UpdateUrl(submodule *models.SubmoduleConfig, newUrl string) error { - if submodule.ParentModule != nil { - wd, err := os.Getwd() - if err != nil { - return err - } - - err = os.Chdir(submodule.ParentModule.FullPath()) - if err != nil { - return err - } - - defer func() { _ = os.Chdir(wd) }() - } - - setUrlCmdStr := NewGitCmd("config"). - Arg( - "--file", ".gitmodules", "submodule."+submodule.Name+".url", newUrl, - ). - ToArgv() - - // the set-url command is only for later git versions so we're doing it manually here - if err := self.cmd.New(setUrlCmdStr).Run(); err != nil { - return err - } - - syncCmdStr := NewGitCmd("submodule").Arg("sync", "--", submodule.Path). - ToArgv() - - if err := self.cmd.New(syncCmdStr).Run(); err != nil { - return err - } - - return nil -} - -func (self *SubmoduleCommands) Init(path string) error { - cmdArgs := NewGitCmd("submodule").Arg("init", "--", path). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *SubmoduleCommands) Update(path string) error { - cmdArgs := NewGitCmd("submodule").Arg("update", "--init", "--", path). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *SubmoduleCommands) BulkInitCmdObj() *oscommands.CmdObj { - cmdArgs := NewGitCmd("submodule").Arg("init"). - ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *SubmoduleCommands) BulkUpdateCmdObj() *oscommands.CmdObj { - cmdArgs := NewGitCmd("submodule").Arg("update"). - ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() *oscommands.CmdObj { - cmdArgs := NewGitCmd("submodule").Arg("update", "--force"). - ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *SubmoduleCommands) BulkUpdateRecursivelyCmdObj() *oscommands.CmdObj { - cmdArgs := NewGitCmd("submodule").Arg("update", "--init", "--recursive"). - ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *SubmoduleCommands) BulkDeinitCmdObj() *oscommands.CmdObj { - cmdArgs := NewGitCmd("submodule").Arg("deinit", "--all", "--force"). - ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *SubmoduleCommands) ResetSubmodules(submodules []*models.SubmoduleConfig) error { - for _, submodule := range submodules { - if err := self.Stash(submodule); err != nil { - return err - } - } - - return self.UpdateAll() -} diff --git a/pkg/commands/git_commands/sync.go b/pkg/commands/git_commands/sync.go deleted file mode 100644 index 0f5559a7b78..00000000000 --- a/pkg/commands/git_commands/sync.go +++ /dev/null @@ -1,132 +0,0 @@ -package git_commands - -import ( - "fmt" - - "github.com/go-errors/errors" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -type SyncCommands struct { - *GitCommon -} - -func NewSyncCommands(gitCommon *GitCommon) *SyncCommands { - return &SyncCommands{ - GitCommon: gitCommon, - } -} - -// Push pushes to a branch -type PushOpts struct { - Force bool - ForceWithLease bool - CurrentBranch string - UpstreamRemote string - UpstreamBranch string - SetUpstream bool -} - -func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (*oscommands.CmdObj, error) { - if opts.UpstreamBranch != "" && opts.UpstreamRemote == "" { - return nil, errors.New(self.Tr.MustSpecifyOriginError) - } - - cmdArgs := NewGitCmd("push"). - ArgIf(opts.Force, "--force"). - ArgIf(opts.ForceWithLease, "--force-with-lease"). - ArgIf(opts.SetUpstream, "--set-upstream"). - ArgIf(opts.UpstreamRemote != "", opts.UpstreamRemote). - ArgIf(opts.UpstreamBranch != "", fmt.Sprintf("refs/heads/%s:%s", opts.CurrentBranch, opts.UpstreamBranch)). - ToArgv() - - cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task) - return cmdObj, nil -} - -func (self *SyncCommands) Push(task gocui.Task, opts PushOpts) error { - cmdObj, err := self.PushCmdObj(task, opts) - if err != nil { - return err - } - - return cmdObj.Run() -} - -func (self *SyncCommands) fetchCommandBuilder(fetchAll bool) *GitCommandBuilder { - return NewGitCmd("fetch"). - ArgIf(fetchAll, "--all"). - // avoid writing to .git/FETCH_HEAD; this allows running a pull - // concurrently without getting errors - Arg("--no-write-fetch-head") -} - -func (self *SyncCommands) FetchCmdObj(task gocui.Task) *oscommands.CmdObj { - cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv() - - cmdObj := self.cmd.New(cmdArgs) - cmdObj.PromptOnCredentialRequest(task) - return cmdObj -} - -func (self *SyncCommands) Fetch(task gocui.Task) error { - return self.FetchCmdObj(task).Run() -} - -func (self *SyncCommands) FetchBackgroundCmdObj() *oscommands.CmdObj { - cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv() - - cmdObj := self.cmd.New(cmdArgs) - cmdObj.DontLog().FailOnCredentialRequest() - return cmdObj -} - -func (self *SyncCommands) FetchBackground() error { - return self.FetchBackgroundCmdObj().Run() -} - -type PullOptions struct { - RemoteName string - BranchName string - FastForwardOnly bool - WorktreeGitDir string - WorktreePath string -} - -func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error { - cmdArgs := NewGitCmd("pull"). - Arg("--no-edit"). - ArgIf(opts.FastForwardOnly, "--ff-only"). - ArgIf(opts.RemoteName != "", opts.RemoteName). - ArgIf(opts.BranchName != "", "refs/heads/"+opts.BranchName). - GitDirIf(opts.WorktreeGitDir != "", opts.WorktreeGitDir). - WorktreePathIf(opts.WorktreePath != "", opts.WorktreePath). - ToArgv() - - // setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user - // has 'pull.rebase = interactive' configured. - return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).Run() -} - -func (self *SyncCommands) FastForward( - task gocui.Task, - branchName string, - remoteName string, - remoteBranchName string, -) error { - cmdArgs := self.fetchCommandBuilder(false). - Arg(remoteName). - Arg("refs/heads/" + remoteBranchName + ":" + branchName). - ToArgv() - - return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() -} - -func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error { - cmdArgs := self.fetchCommandBuilder(false). - Arg(remoteName). - ToArgv() - - return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() -} diff --git a/pkg/commands/git_commands/sync_test.go b/pkg/commands/git_commands/sync_test.go deleted file mode 100644 index 4254788d8ed..00000000000 --- a/pkg/commands/git_commands/sync_test.go +++ /dev/null @@ -1,181 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/stretchr/testify/assert" -) - -func TestSyncPush(t *testing.T) { - type scenario struct { - testName string - opts PushOpts - test func(*oscommands.CmdObj, error) - } - - scenarios := []scenario{ - { - testName: "Push with force disabled", - opts: PushOpts{ForceWithLease: false}, - test: func(cmdObj *oscommands.CmdObj, err error) { - assert.Equal(t, cmdObj.Args(), []string{"git", "push"}) - assert.NoError(t, err) - }, - }, - { - testName: "Push with force-with-lease enabled", - opts: PushOpts{ForceWithLease: true}, - test: func(cmdObj *oscommands.CmdObj, err error) { - assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease"}) - assert.NoError(t, err) - }, - }, - { - testName: "Push with force enabled", - opts: PushOpts{Force: true}, - test: func(cmdObj *oscommands.CmdObj, err error) { - assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force"}) - assert.NoError(t, err) - }, - }, - { - testName: "Push with force disabled, upstream supplied", - opts: PushOpts{ - ForceWithLease: false, - CurrentBranch: "master", - UpstreamRemote: "origin", - UpstreamBranch: "master", - }, - test: func(cmdObj *oscommands.CmdObj, err error) { - assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "refs/heads/master:master"}) - assert.NoError(t, err) - }, - }, - { - testName: "Push with force disabled, setting upstream", - opts: PushOpts{ - ForceWithLease: false, - CurrentBranch: "master-local", - UpstreamRemote: "origin", - UpstreamBranch: "master", - SetUpstream: true, - }, - test: func(cmdObj *oscommands.CmdObj, err error) { - assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "refs/heads/master-local:master"}) - assert.NoError(t, err) - }, - }, - { - testName: "Push with force-with-lease enabled, setting upstream", - opts: PushOpts{ - ForceWithLease: true, - CurrentBranch: "master", - UpstreamRemote: "origin", - UpstreamBranch: "master", - SetUpstream: true, - }, - test: func(cmdObj *oscommands.CmdObj, err error) { - assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "refs/heads/master:master"}) - assert.NoError(t, err) - }, - }, - { - testName: "Push with remote branch but no origin", - opts: PushOpts{ - ForceWithLease: true, - UpstreamRemote: "", - UpstreamBranch: "master", - SetUpstream: true, - }, - test: func(cmdObj *oscommands.CmdObj, err error) { - assert.Error(t, err) - assert.EqualValues(t, "Must specify a remote if specifying a branch", err.Error()) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildSyncCommands(commonDeps{}) - task := gocui.NewFakeTask() - s.test(instance.PushCmdObj(task, s.opts)) - }) - } -} - -func TestSyncFetch(t *testing.T) { - type scenario struct { - testName string - fetchAllConfig bool - test func(*oscommands.CmdObj) - } - - scenarios := []scenario{ - { - testName: "Fetch in foreground (all=false)", - fetchAllConfig: false, - test: func(cmdObj *oscommands.CmdObj) { - assert.True(t, cmdObj.ShouldLog()) - assert.Equal(t, cmdObj.GetCredentialStrategy(), oscommands.PROMPT) - assert.Equal(t, cmdObj.Args(), []string{"git", "fetch", "--no-write-fetch-head"}) - }, - }, - { - testName: "Fetch in foreground (all=true)", - fetchAllConfig: true, - test: func(cmdObj *oscommands.CmdObj) { - assert.True(t, cmdObj.ShouldLog()) - assert.Equal(t, cmdObj.GetCredentialStrategy(), oscommands.PROMPT) - assert.Equal(t, cmdObj.Args(), []string{"git", "fetch", "--all", "--no-write-fetch-head"}) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildSyncCommands(commonDeps{}) - instance.UserConfig().Git.FetchAll = s.fetchAllConfig - task := gocui.NewFakeTask() - s.test(instance.FetchCmdObj(task)) - }) - } -} - -func TestSyncFetchBackground(t *testing.T) { - type scenario struct { - testName string - fetchAllConfig bool - test func(*oscommands.CmdObj) - } - - scenarios := []scenario{ - { - testName: "Fetch in background (all=false)", - fetchAllConfig: false, - test: func(cmdObj *oscommands.CmdObj) { - assert.False(t, cmdObj.ShouldLog()) - assert.Equal(t, cmdObj.GetCredentialStrategy(), oscommands.FAIL) - assert.Equal(t, cmdObj.Args(), []string{"git", "fetch", "--no-write-fetch-head"}) - }, - }, - { - testName: "Fetch in background (all=true)", - fetchAllConfig: true, - test: func(cmdObj *oscommands.CmdObj) { - assert.False(t, cmdObj.ShouldLog()) - assert.Equal(t, cmdObj.GetCredentialStrategy(), oscommands.FAIL) - assert.Equal(t, cmdObj.Args(), []string{"git", "fetch", "--all", "--no-write-fetch-head"}) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildSyncCommands(commonDeps{}) - instance.UserConfig().Git.FetchAll = s.fetchAllConfig - s.test(instance.FetchBackgroundCmdObj()) - }) - } -} diff --git a/pkg/commands/git_commands/tag.go b/pkg/commands/git_commands/tag.go deleted file mode 100644 index 1e9b449b1a1..00000000000 --- a/pkg/commands/git_commands/tag.go +++ /dev/null @@ -1,88 +0,0 @@ -package git_commands - -import ( - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -type TagCommands struct { - *GitCommon -} - -func NewTagCommands(gitCommon *GitCommon) *TagCommands { - return &TagCommands{ - GitCommon: gitCommon, - } -} - -func (self *TagCommands) CreateLightweightObj(tagName string, ref string, force bool) *oscommands.CmdObj { - cmdArgs := NewGitCmd("tag"). - ArgIf(force, "--force"). - Arg("--", tagName). - ArgIf(len(ref) > 0, ref). - ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *TagCommands) CreateAnnotatedObj(tagName, ref, msg string, force bool) *oscommands.CmdObj { - cmdArgs := NewGitCmd("tag").Arg(tagName). - ArgIf(force, "--force"). - ArgIf(len(ref) > 0, ref). - Arg("-m", msg). - ToArgv() - - return self.cmd.New(cmdArgs) -} - -func (self *TagCommands) HasTag(tagName string) bool { - cmdArgs := NewGitCmd("show-ref"). - Arg("--tags", "--quiet", "--verify", "--"). - Arg("refs/tags/" + tagName). - ToArgv() - - return self.cmd.New(cmdArgs).Run() == nil -} - -func (self *TagCommands) LocalDelete(tagName string) error { - cmdArgs := NewGitCmd("tag").Arg("-d", tagName). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string) error { - cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName). - ToArgv() - - return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() -} - -// Return info about an annotated tag in the format: -// -// Tagger: tagger name -// TaggerDate: tagger date -// -// Tag message -// -// Should only be called for annotated tags. -func (self *TagCommands) ShowAnnotationInfo(tagName string) (string, error) { - cmdArgs := NewGitCmd("for-each-ref"). - Arg("--format=Tagger: %(taggername) %(taggeremail)%0aTaggerDate: %(taggerdate)%0a%0a%(contents)"). - Arg("refs/tags/" + tagName). - ToArgv() - - return self.cmd.New(cmdArgs).RunWithOutput() -} - -func (self *TagCommands) IsTagAnnotated(tagName string) (bool, error) { - cmdArgs := NewGitCmd("cat-file"). - Arg("-t"). - Arg("refs/tags/" + tagName). - ToArgv() - - output, err := self.cmd.New(cmdArgs).RunWithOutput() - return strings.TrimSpace(output) == "tag", err -} diff --git a/pkg/commands/git_commands/tag_loader.go b/pkg/commands/git_commands/tag_loader.go deleted file mode 100644 index bd05fe4b45d..00000000000 --- a/pkg/commands/git_commands/tag_loader.go +++ /dev/null @@ -1,56 +0,0 @@ -package git_commands - -import ( - "regexp" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type TagLoader struct { - *common.Common - cmd oscommands.ICmdObjBuilder -} - -func NewTagLoader( - common *common.Common, - cmd oscommands.ICmdObjBuilder, -) *TagLoader { - return &TagLoader{ - Common: common, - cmd: cmd, - } -} - -func (self *TagLoader) GetTags() ([]*models.Tag, error) { - // get remote branches, sorted by creation date (descending) - // see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt - cmdArgs := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToArgv() - tagsOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - if err != nil { - return nil, err - } - - split := utils.SplitLines(tagsOutput) - - lineRegex := regexp.MustCompile(`^([^\s]+)(\s+)?(.*)$`) - - tags := lo.Map(split, func(line string, _ int) *models.Tag { - matches := lineRegex.FindStringSubmatch(line) - tagName := matches[1] - message := "" - if len(matches) > 3 { - message = matches[3] - } - - return &models.Tag{ - Name: tagName, - Message: message, - } - }) - - return tags, nil -} diff --git a/pkg/commands/git_commands/tag_loader_test.go b/pkg/commands/git_commands/tag_loader_test.go deleted file mode 100644 index e8f19fcd476..00000000000 --- a/pkg/commands/git_commands/tag_loader_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/stretchr/testify/assert" -) - -const tagsOutput = `tag1 this is my message -tag2 -tag3 this is my other message -` - -func TestGetTags(t *testing.T) { - type scenario struct { - testName string - runner *oscommands.FakeCmdObjRunner - expectedTags []*models.Tag - expectedError error - } - - scenarios := []scenario{ - { - testName: "should return no tags if there are none", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, "", nil), - expectedTags: []*models.Tag{}, - expectedError: nil, - }, - { - testName: "should return tags if present", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, tagsOutput, nil), - expectedTags: []*models.Tag{ - {Name: "tag1", Message: "this is my message"}, - {Name: "tag2", Message: ""}, - {Name: "tag3", Message: "this is my other message"}, - }, - expectedError: nil, - }, - } - - for _, scenario := range scenarios { - t.Run(scenario.testName, func(t *testing.T) { - loader := &TagLoader{ - Common: common.NewDummyCommon(), - cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner), - } - - tags, err := loader.GetTags() - - assert.Equal(t, scenario.expectedTags, tags) - assert.Equal(t, scenario.expectedError, err) - - scenario.runner.CheckForMissingCalls() - }) - } -} diff --git a/pkg/commands/git_commands/version.go b/pkg/commands/git_commands/version.go deleted file mode 100644 index aab912ba5bf..00000000000 --- a/pkg/commands/git_commands/version.go +++ /dev/null @@ -1,79 +0,0 @@ -package git_commands - -import ( - "errors" - "regexp" - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -type GitVersion struct { - Major, Minor, Patch int - Additional string -} - -func GetGitVersion(osCommand *oscommands.OSCommand) (*GitVersion, error) { - versionStr, _, err := osCommand.Cmd.New(NewGitCmd("--version").ToArgv()).RunWithOutputs() - if err != nil { - return nil, err - } - - version, err := ParseGitVersion(versionStr) - if err != nil { - return nil, err - } - - return version, nil -} - -func ParseGitVersion(versionStr string) (*GitVersion, error) { - // versionStr should be something like: - // git version 2.39.0 - // git version 2.37.1 (Apple Git-137.1) - re := regexp.MustCompile(`[^\d]*(\d+)(\.\d+)?(\.\d+)?(.*)`) - matches := re.FindStringSubmatch(versionStr) - - if len(matches) < 5 { - return nil, errors.New("unexpected git version format: " + versionStr) - } - - v := &GitVersion{} - var err error - - if v.Major, err = strconv.Atoi(matches[1]); err != nil { - return nil, err - } - if len(matches[2]) > 1 { - if v.Minor, err = strconv.Atoi(matches[2][1:]); err != nil { - return nil, err - } - } - if len(matches[3]) > 1 { - if v.Patch, err = strconv.Atoi(matches[3][1:]); err != nil { - return nil, err - } - } - v.Additional = strings.Trim(matches[4], " \r\n") - - return v, nil -} - -func (v *GitVersion) IsOlderThan(major, minor, patch int) bool { - actual := v.Major*1000*1000 + v.Minor*1000 + v.Patch - required := major*1000*1000 + minor*1000 + patch - return actual < required -} - -func (v *GitVersion) IsOlderThanVersion(version *GitVersion) bool { - return v.IsOlderThan(version.Major, version.Minor, version.Patch) -} - -func (v *GitVersion) IsAtLeast(major, minor, patch int) bool { - return !v.IsOlderThan(major, minor, patch) -} - -func (v *GitVersion) IsAtLeastVersion(version *GitVersion) bool { - return v.IsAtLeast(version.Major, version.Minor, version.Patch) -} diff --git a/pkg/commands/git_commands/version_test.go b/pkg/commands/git_commands/version_test.go deleted file mode 100644 index 46b002f606b..00000000000 --- a/pkg/commands/git_commands/version_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParseGitVersion(t *testing.T) { - scenarios := []struct { - input string - expected GitVersion - }{ - { - input: "git version 2.39.0", - expected: GitVersion{Major: 2, Minor: 39, Patch: 0, Additional: ""}, - }, - { - input: "git version 2.37.1 (Apple Git-137.1)", - expected: GitVersion{Major: 2, Minor: 37, Patch: 1, Additional: "(Apple Git-137.1)"}, - }, - { - input: "git version 2.37 (Apple Git-137.1)", - expected: GitVersion{Major: 2, Minor: 37, Patch: 0, Additional: "(Apple Git-137.1)"}, - }, - } - - for _, s := range scenarios { - actual, err := ParseGitVersion(s.input) - - assert.NoError(t, err) - assert.NotNil(t, actual) - assert.Equal(t, s.expected.Major, actual.Major) - assert.Equal(t, s.expected.Minor, actual.Minor) - assert.Equal(t, s.expected.Patch, actual.Patch) - assert.Equal(t, s.expected.Additional, actual.Additional) - } -} - -func TestGitVersionIsOlderThan(t *testing.T) { - assert.False(t, (&GitVersion{2, 0, 0, ""}).IsOlderThan(1, 99, 99)) - assert.False(t, (&GitVersion{2, 0, 0, ""}).IsOlderThan(2, 0, 0)) - assert.False(t, (&GitVersion{2, 1, 0, ""}).IsOlderThan(2, 0, 9)) - - assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(2, 1, 0)) - assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(3, 0, 0)) -} - -func TestGitVersionIsAtLeast(t *testing.T) { - assert.True(t, (&GitVersion{2, 0, 0, ""}).IsAtLeast(1, 99, 99)) - assert.True(t, (&GitVersion{2, 0, 0, ""}).IsAtLeast(2, 0, 0)) - assert.True(t, (&GitVersion{2, 1, 0, ""}).IsAtLeast(2, 0, 9)) - - assert.False(t, (&GitVersion{2, 0, 1, ""}).IsAtLeast(2, 1, 0)) - assert.False(t, (&GitVersion{2, 0, 1, ""}).IsAtLeast(3, 0, 0)) -} diff --git a/pkg/commands/git_commands/working_tree.go b/pkg/commands/git_commands/working_tree.go deleted file mode 100644 index 1cba0d5114e..00000000000 --- a/pkg/commands/git_commands/working_tree.go +++ /dev/null @@ -1,453 +0,0 @@ -package git_commands - -import ( - "fmt" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -type WorkingTreeCommands struct { - *GitCommon - submodule *SubmoduleCommands - fileLoader *FileLoader -} - -func NewWorkingTreeCommands( - gitCommon *GitCommon, - submodule *SubmoduleCommands, - fileLoader *FileLoader, -) *WorkingTreeCommands { - return &WorkingTreeCommands{ - GitCommon: gitCommon, - submodule: submodule, - fileLoader: fileLoader, - } -} - -func (self *WorkingTreeCommands) OpenMergeToolCmdObj() *oscommands.CmdObj { - return self.cmd.New(NewGitCmd("mergetool").ToArgv()) -} - -// StageFile stages a file -func (self *WorkingTreeCommands) StageFile(path string) error { - return self.StageFiles([]string{path}, nil) -} - -func (self *WorkingTreeCommands) StageFiles(paths []string, extraArgs []string) error { - cmdArgs := NewGitCmd("add"). - Arg(extraArgs...). - Arg("--"). - Arg(paths...). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// StageAll stages all files -func (self *WorkingTreeCommands) StageAll(onlyTrackedFiles bool) error { - cmdArgs := NewGitCmd("add"). - ArgIfElse(onlyTrackedFiles, "-u", "-A"). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// UnstageAll unstages all files -func (self *WorkingTreeCommands) UnstageAll() error { - return self.cmd.New(NewGitCmd("reset").ToArgv()).Run() -} - -// UnStageFile unstages a file -// we accept an array of filenames for the cases where a file has been renamed i.e. -// we accept the current name and the previous name -func (self *WorkingTreeCommands) UnStageFile(paths []string, tracked bool) error { - if tracked { - return self.UnstageTrackedFiles(paths) - } - return self.UnstageUntrackedFiles(paths) -} - -func (self *WorkingTreeCommands) UnstageTrackedFiles(paths []string) error { - return self.cmd.New(NewGitCmd("reset").Arg("HEAD", "--").Arg(paths...).ToArgv()).Run() -} - -func (self *WorkingTreeCommands) UnstageUntrackedFiles(paths []string) error { - return self.cmd.New(NewGitCmd("rm").Arg("--cached", "--force", "--").Arg(paths...).ToArgv()).Run() -} - -func (self *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File) (*models.File, *models.File, error) { - if !file.IsRename() { - return nil, nil, errors.New("Expected renamed file") - } - - // we've got a file that represents a rename from one file to another. Here we will refetch - // all files, passing the --no-renames flag and then recursively call the function - // again for the before file and after file. - - filesWithoutRenames := self.fileLoader.GetStatusFiles(GetStatusFileOptions{NoRenames: true}) - - var beforeFile *models.File - var afterFile *models.File - for _, f := range filesWithoutRenames { - if f.Path == file.PreviousPath { - beforeFile = f - } - - if f.Path == file.Path { - afterFile = f - } - } - - if beforeFile == nil || afterFile == nil { - return nil, nil, errors.New("Could not find deleted file or new file for file rename") - } - - if beforeFile.IsRename() || afterFile.IsRename() { - // probably won't happen but we want to ensure we don't get an infinite loop - return nil, nil, errors.New("Nested rename found") - } - - return beforeFile, afterFile, nil -} - -// DiscardAllFileChanges directly -func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error { - if file.IsRename() { - beforeFile, afterFile, err := self.BeforeAndAfterFileForRename(file) - if err != nil { - return err - } - - if err := self.DiscardAllFileChanges(beforeFile); err != nil { - return err - } - - if err := self.DiscardAllFileChanges(afterFile); err != nil { - return err - } - - return nil - } - - if file.ShortStatus == "AA" { - if err := self.cmd.New( - NewGitCmd("checkout").Arg("--ours", "--", file.Path).ToArgv(), - ).Run(); err != nil { - return err - } - - if err := self.cmd.New( - NewGitCmd("add").Arg("--", file.Path).ToArgv(), - ).Run(); err != nil { - return err - } - return nil - } - - if file.ShortStatus == "DU" { - return self.cmd.New( - NewGitCmd("rm").Arg("--", file.Path).ToArgv(), - ).Run() - } - - // if the file isn't tracked, we assume you want to delete it - if file.HasStagedChanges || file.HasMergeConflicts { - if err := self.cmd.New( - NewGitCmd("reset").Arg("--", file.Path).ToArgv(), - ).Run(); err != nil { - return err - } - } - - if file.ShortStatus == "DD" || file.ShortStatus == "AU" { - return nil - } - - if file.Added { - return self.os.RemoveFile(file.Path) - } - - return self.DiscardUnstagedFileChanges(file) -} - -type IFileNode interface { - ForEachFile(cb func(*models.File) error) error - GetFilePathsMatching(test func(*models.File) bool) []string - GetPath() string - // Returns file if the node is not a directory, otherwise returns nil - GetFile() *models.File -} - -func (self *WorkingTreeCommands) DiscardAllDirChanges(node IFileNode) error { - // this could be more efficient but we would need to handle all the edge cases - return node.ForEachFile(self.DiscardAllFileChanges) -} - -func (self *WorkingTreeCommands) DiscardUnstagedDirChanges(node IFileNode) error { - file := node.GetFile() - if file == nil { - if err := self.RemoveUntrackedDirFiles(node); err != nil { - return err - } - - cmdArgs := NewGitCmd("checkout").Arg("--", node.GetPath()).ToArgv() - if err := self.cmd.New(cmdArgs).Run(); err != nil { - return err - } - } else { - if file.Added && !file.HasStagedChanges { - return self.os.RemoveFile(file.Path) - } - - if err := self.DiscardUnstagedFileChanges(file); err != nil { - return err - } - } - - return nil -} - -func (self *WorkingTreeCommands) RemoveUntrackedDirFiles(node IFileNode) error { - untrackedFilePaths := node.GetFilePathsMatching( - func(file *models.File) bool { return !file.GetIsTracked() }, - ) - - for _, path := range untrackedFilePaths { - err := os.Remove(path) - if err != nil { - return err - } - } - - return nil -} - -func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error { - cmdArgs := NewGitCmd("checkout").Arg("--", file.Path).ToArgv() - return self.cmd.New(cmdArgs).Run() -} - -// Escapes special characters in a filename for gitignore and exclude files -func escapeFilename(filename string) string { - re := regexp.MustCompile(`^[!#]|[\[\]*]`) - return re.ReplaceAllString(filename, `\${0}`) -} - -// Ignore adds a file to the gitignore for the repo -func (self *WorkingTreeCommands) Ignore(filename string) error { - return self.os.AppendLineToFile(".gitignore", escapeFilename(filename)) -} - -// Exclude adds a file to the .git/info/exclude for the repo -func (self *WorkingTreeCommands) Exclude(filename string) error { - excludeFile := filepath.Join(self.repoPaths.repoGitDirPath, "info", "exclude") - return self.os.AppendLineToFile(excludeFile, escapeFilename(filename)) -} - -// WorktreeFileDiff returns the diff of a file -func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool) string { - // for now we assume an error means the file was deleted - s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached).RunWithOutput() - return s -} - -func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) *oscommands.CmdObj { - colorArg := self.pagerConfig.GetColorArg() - if plain { - colorArg = "never" - } - - contextSize := self.UserConfig().Git.DiffContextSize - prevPath := node.GetPreviousPath() - noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile() - extDiffCmd := self.pagerConfig.GetExternalDiffCommand() - useExtDiff := extDiffCmd != "" && !plain - useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig() && !plain - - cmdArgs := NewGitCmd("diff"). - ConfigIf(useExtDiff, "diff.external="+extDiffCmd). - ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff"). - Arg("--submodule"). - Arg(fmt.Sprintf("--unified=%d", contextSize)). - Arg(fmt.Sprintf("--color=%s", colorArg)). - ArgIf(!plain && self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space"). - Arg(fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold)). - ArgIf(cached, "--cached"). - ArgIf(noIndex, "--no-index"). - Arg("--"). - ArgIf(noIndex, "/dev/null"). - Arg(node.GetPath()). - ArgIf(prevPath != "", prevPath). - Dir(self.repoPaths.worktreePath). - ToArgv() - - return self.cmd.New(cmdArgs).DontLog() -} - -// ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc -// but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode. -func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) { - return self.ShowFileDiffCmdObj(from, to, reverse, fileName, plain).RunWithOutput() -} - -func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) *oscommands.CmdObj { - contextSize := self.UserConfig().Git.DiffContextSize - - colorArg := self.pagerConfig.GetColorArg() - if plain { - colorArg = "never" - } - - extDiffCmd := self.pagerConfig.GetExternalDiffCommand() - useExtDiff := extDiffCmd != "" && !plain - useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig() && !plain - - cmdArgs := NewGitCmd("diff"). - Config("diff.noprefix=false"). - ConfigIf(useExtDiff, "diff.external="+extDiffCmd). - ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff"). - Arg("--submodule"). - Arg(fmt.Sprintf("--unified=%d", contextSize)). - Arg("--no-renames"). - Arg(fmt.Sprintf("--color=%s", colorArg)). - Arg(from). - Arg(to). - ArgIf(reverse, "-R"). - ArgIf(!plain && self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space"). - Arg("--"). - Arg(fileName). - Dir(self.repoPaths.worktreePath). - ToArgv() - - return self.cmd.New(cmdArgs).DontLog() -} - -// CheckoutFile checks out the file for the given commit -func (self *WorkingTreeCommands) CheckoutFile(commitHash, fileName string) error { - cmdArgs := NewGitCmd("checkout").Arg(commitHash, "--", fileName). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// DiscardAnyUnstagedFileChanges discards any unstaged file changes via `git checkout -- .` -func (self *WorkingTreeCommands) DiscardAnyUnstagedFileChanges() error { - cmdArgs := NewGitCmd("checkout").Arg("--", "."). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked -func (self *WorkingTreeCommands) RemoveTrackedFiles(name string) error { - cmdArgs := NewGitCmd("rm").Arg("-r", "--cached", "--", name). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *WorkingTreeCommands) RemoveConflictedFile(name string) error { - cmdArgs := NewGitCmd("rm").Arg("--", name). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// RemoveUntrackedFiles runs `git clean -fd` -func (self *WorkingTreeCommands) RemoveUntrackedFiles() error { - cmdArgs := NewGitCmd("clean").Arg("-fd").ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// ResetAndClean removes all unstaged changes and removes all untracked files -func (self *WorkingTreeCommands) ResetAndClean() error { - submoduleConfigs, err := self.submodule.GetConfigs(nil) - if err != nil { - return err - } - - if len(submoduleConfigs) > 0 { - if err := self.submodule.ResetSubmodules(submoduleConfigs); err != nil { - return err - } - } - - if err := self.ResetHard("HEAD"); err != nil { - return err - } - - return self.RemoveUntrackedFiles() -} - -// ResetHard runs `git reset --hard` -func (self *WorkingTreeCommands) ResetHard(ref string) error { - cmdArgs := NewGitCmd("reset").Arg("--hard", ref). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -// ResetSoft runs `git reset --soft HEAD` -func (self *WorkingTreeCommands) ResetSoft(ref string) error { - cmdArgs := NewGitCmd("reset").Arg("--soft", ref). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *WorkingTreeCommands) ResetMixed(ref string) error { - cmdArgs := NewGitCmd("reset").Arg("--mixed", ref). - ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *WorkingTreeCommands) ShowFileAtStage(path string, stage int) (string, error) { - cmdArgs := NewGitCmd("show"). - Arg(fmt.Sprintf(":%d:%s", stage, path)). - ToArgv() - - return self.cmd.New(cmdArgs).RunWithOutput() -} - -func (self *WorkingTreeCommands) ObjectIDAtStage(path string, stage int) (string, error) { - cmdArgs := NewGitCmd("rev-parse"). - Arg(fmt.Sprintf(":%d:%s", stage, path)). - ToArgv() - - output, err := self.cmd.New(cmdArgs).RunWithOutput() - if err != nil { - return "", err - } - - return strings.TrimSpace(output), nil -} - -func (self *WorkingTreeCommands) MergeFileForFiles(strategy string, oursFilepath string, baseFilepath string, theirsFilepath string) (string, error) { - cmdArgs := NewGitCmd("merge-file"). - Arg(strategy). - Arg("--stdout"). - Arg(oursFilepath, baseFilepath, theirsFilepath). - ToArgv() - - return self.cmd.New(cmdArgs).RunWithOutput() -} - -// OIDs mode (Git 2.43+) -func (self *WorkingTreeCommands) MergeFileForObjectIDs(strategy string, oursID string, baseID string, theirsID string) (string, error) { - cmdArgs := NewGitCmd("merge-file"). - Arg(strategy). - Arg("--stdout"). - Arg("--object-id"). - Arg(oursID, baseID, theirsID). - ToArgv() - - return self.cmd.New(cmdArgs).RunWithOutput() -} diff --git a/pkg/commands/git_commands/working_tree_test.go b/pkg/commands/git_commands/working_tree_test.go deleted file mode 100644 index 759eb1bbe55..00000000000 --- a/pkg/commands/git_commands/working_tree_test.go +++ /dev/null @@ -1,565 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/stretchr/testify/assert" -) - -func TestWorkingTreeStageFile(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"add", "--", "test.txt"}, "", nil) - - instance := buildWorkingTreeCommands(commonDeps{runner: runner}) - - assert.NoError(t, instance.StageFile("test.txt")) - runner.CheckForMissingCalls() -} - -func TestWorkingTreeStageFiles(t *testing.T) { - runner := oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"add", "--", "test.txt", "test2.txt"}, "", nil) - - instance := buildWorkingTreeCommands(commonDeps{runner: runner}) - - assert.NoError(t, instance.StageFiles([]string{"test.txt", "test2.txt"}, nil)) - runner.CheckForMissingCalls() -} - -func TestWorkingTreeUnstageFile(t *testing.T) { - type scenario struct { - testName string - reset bool - runner *oscommands.FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - testName: "Remove an untracked file from staging", - reset: false, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"rm", "--cached", "--force", "--", "test.txt"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - { - testName: "Remove a tracked file from staging", - reset: true, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"reset", "HEAD", "--", "test.txt"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) - s.test(instance.UnStageFile([]string{"test.txt"}, s.reset)) - }) - } -} - -// these tests don't cover everything, in part because we already have an integration -// test which does cover everything. I don't want to unnecessarily assert on the 'how' -// when the 'what' is what matters -func TestWorkingTreeDiscardAllFileChanges(t *testing.T) { - type scenario struct { - testName string - file *models.File - removeFile func(string) error - runner *oscommands.FakeCmdObjRunner - expectedError string - } - - scenarios := []scenario{ - { - testName: "An error occurred when resetting", - file: &models.File{ - Path: "test", - HasStagedChanges: true, - }, - removeFile: func(string) error { return nil }, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"reset", "--", "test"}, "", errors.New("error")), - expectedError: "error", - }, - { - testName: "An error occurred when removing file", - file: &models.File{ - Path: "test", - Tracked: false, - Added: true, - }, - removeFile: func(string) error { - return errors.New("an error occurred when removing file") - }, - runner: oscommands.NewFakeRunner(t), - expectedError: "an error occurred when removing file", - }, - { - testName: "An error occurred with checkout", - file: &models.File{ - Path: "test", - Tracked: true, - HasStagedChanges: false, - }, - removeFile: func(string) error { return nil }, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"checkout", "--", "test"}, "", errors.New("error")), - expectedError: "error", - }, - { - testName: "Checkout only", - file: &models.File{ - Path: "test", - Tracked: true, - HasStagedChanges: false, - }, - removeFile: func(string) error { return nil }, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil), - expectedError: "", - }, - { - testName: "Reset and checkout staged changes", - file: &models.File{ - Path: "test", - Tracked: true, - HasStagedChanges: true, - }, - removeFile: func(string) error { return nil }, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"reset", "--", "test"}, "", nil). - ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil), - expectedError: "", - }, - { - testName: "Reset and checkout merge conflicts", - file: &models.File{ - Path: "test", - Tracked: true, - HasMergeConflicts: true, - }, - removeFile: func(string) error { return nil }, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"reset", "--", "test"}, "", nil). - ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil), - expectedError: "", - }, - { - testName: "Reset and remove", - file: &models.File{ - Path: "test", - Tracked: false, - Added: true, - HasStagedChanges: true, - }, - removeFile: func(filename string) error { - assert.Equal(t, "test", filename) - return nil - }, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"reset", "--", "test"}, "", nil), - expectedError: "", - }, - { - testName: "Remove only", - file: &models.File{ - Path: "test", - Tracked: false, - Added: true, - HasStagedChanges: false, - }, - removeFile: func(filename string) error { - assert.Equal(t, "test", filename) - return nil - }, - runner: oscommands.NewFakeRunner(t), - expectedError: "", - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, removeFile: s.removeFile}) - err := instance.DiscardAllFileChanges(s.file) - - if s.expectedError == "" { - assert.Nil(t, err) - } else { - assert.Equal(t, s.expectedError, err.Error()) - } - s.runner.CheckForMissingCalls() - }) - } -} - -func TestWorkingTreeDiff(t *testing.T) { - type scenario struct { - testName string - file *models.File - plain bool - cached bool - ignoreWhitespace bool - contextSize uint64 - similarityThreshold int - runner *oscommands.FakeCmdObjRunner - } - - const expectedResult = "pretend this is an actual git diff" - - scenarios := []scenario{ - { - testName: "Default case", - file: &models.File{ - Path: "test.txt", - HasStagedChanges: false, - Tracked: true, - }, - plain: false, - cached: false, - ignoreWhitespace: false, - contextSize: 3, - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil), - }, - { - testName: "cached", - file: &models.File{ - Path: "test.txt", - HasStagedChanges: false, - Tracked: true, - }, - plain: false, - cached: true, - ignoreWhitespace: false, - contextSize: 3, - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=50%", "--cached", "--", "test.txt"}, expectedResult, nil), - }, - { - testName: "plain", - file: &models.File{ - Path: "test.txt", - HasStagedChanges: false, - Tracked: true, - }, - plain: true, - cached: false, - ignoreWhitespace: false, - contextSize: 3, - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=never", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil), - }, - { - testName: "File not tracked and file has no staged changes", - file: &models.File{ - Path: "test.txt", - HasStagedChanges: false, - Tracked: false, - }, - plain: false, - cached: false, - ignoreWhitespace: false, - contextSize: 3, - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=50%", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil), - }, - { - testName: "Default case (ignore whitespace)", - file: &models.File{ - Path: "test.txt", - HasStagedChanges: false, - Tracked: true, - }, - plain: false, - cached: false, - ignoreWhitespace: true, - contextSize: 3, - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--ignore-all-space", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil), - }, - { - testName: "Show diff with custom context size", - file: &models.File{ - Path: "test.txt", - HasStagedChanges: false, - Tracked: true, - }, - plain: false, - cached: false, - ignoreWhitespace: false, - contextSize: 17, - similarityThreshold: 50, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=17", "--color=always", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil), - }, - { - testName: "Show diff with custom similarity threshold", - file: &models.File{ - Path: "test.txt", - HasStagedChanges: false, - Tracked: true, - }, - plain: false, - cached: false, - ignoreWhitespace: false, - contextSize: 3, - similarityThreshold: 33, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=33%", "--", "test.txt"}, expectedResult, nil), - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - userConfig := config.GetDefaultConfig() - userConfig.Git.IgnoreWhitespaceInDiffView = s.ignoreWhitespace - userConfig.Git.DiffContextSize = s.contextSize - userConfig.Git.RenameSimilarityThreshold = s.similarityThreshold - repoPaths := RepoPaths{ - worktreePath: "/path/to/worktree", - } - - instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig, appState: &config.AppState{}, repoPaths: &repoPaths}) - result := instance.WorktreeFileDiff(s.file, s.plain, s.cached) - assert.Equal(t, expectedResult, result) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestWorkingTreeShowFileDiff(t *testing.T) { - type scenario struct { - testName string - from string - to string - reverse bool - plain bool - ignoreWhitespace bool - contextSize uint64 - runner *oscommands.FakeCmdObjRunner - } - - const expectedResult = "pretend this is an actual git diff" - - scenarios := []scenario{ - { - testName: "Default case", - from: "1234567890", - to: "0987654321", - reverse: false, - plain: false, - ignoreWhitespace: false, - contextSize: 3, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil), - }, - { - testName: "Show diff with custom context size", - from: "1234567890", - to: "0987654321", - reverse: false, - plain: false, - ignoreWhitespace: false, - contextSize: 123, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil), - }, - { - testName: "Default case (ignore whitespace)", - from: "1234567890", - to: "0987654321", - reverse: false, - plain: false, - ignoreWhitespace: true, - contextSize: 3, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil), - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - userConfig := config.GetDefaultConfig() - userConfig.Git.IgnoreWhitespaceInDiffView = s.ignoreWhitespace - userConfig.Git.DiffContextSize = s.contextSize - repoPaths := RepoPaths{ - worktreePath: "/path/to/worktree", - } - - instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig, appState: &config.AppState{}, repoPaths: &repoPaths}) - - result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain) - assert.NoError(t, err) - assert.Equal(t, expectedResult, result) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestWorkingTreeCheckoutFile(t *testing.T) { - type scenario struct { - testName string - commitHash string - fileName string - runner *oscommands.FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - testName: "typical case", - commitHash: "11af912", - fileName: "test999.txt", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"checkout", "11af912", "--", "test999.txt"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - { - testName: "returns error if there is one", - commitHash: "11af912", - fileName: "test999.txt", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"checkout", "11af912", "--", "test999.txt"}, "", errors.New("error")), - test: func(err error) { - assert.Error(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) - - s.test(instance.CheckoutFile(s.commitHash, s.fileName)) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestWorkingTreeDiscardUnstagedFileChanges(t *testing.T) { - type scenario struct { - testName string - file *models.File - runner *oscommands.FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - testName: "valid case", - file: &models.File{Path: "test.txt"}, - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"checkout", "--", "test.txt"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) - s.test(instance.DiscardUnstagedFileChanges(s.file)) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestWorkingTreeDiscardAnyUnstagedFileChanges(t *testing.T) { - type scenario struct { - testName string - runner *oscommands.FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - testName: "valid case", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"checkout", "--", "."}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) - s.test(instance.DiscardAnyUnstagedFileChanges()) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestWorkingTreeRemoveUntrackedFiles(t *testing.T) { - type scenario struct { - testName string - runner *oscommands.FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - testName: "valid case", - runner: oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"clean", "-fd"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) - s.test(instance.RemoveUntrackedFiles()) - s.runner.CheckForMissingCalls() - }) - } -} - -func TestWorkingTreeResetHard(t *testing.T) { - type scenario struct { - testName string - ref string - runner *oscommands.FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - "valid case", - "HEAD", - oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"reset", "--hard", "HEAD"}, "", nil), - func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) - s.test(instance.ResetHard(s.ref)) - }) - } -} diff --git a/pkg/commands/git_commands/worktree.go b/pkg/commands/git_commands/worktree.go deleted file mode 100644 index 986bb6d426a..00000000000 --- a/pkg/commands/git_commands/worktree.go +++ /dev/null @@ -1,74 +0,0 @@ -package git_commands - -import ( - "path/filepath" - - "github.com/jesseduffield/lazygit/pkg/commands/models" -) - -type WorktreeCommands struct { - *GitCommon -} - -func NewWorktreeCommands(gitCommon *GitCommon) *WorktreeCommands { - return &WorktreeCommands{ - GitCommon: gitCommon, - } -} - -type NewWorktreeOpts struct { - // required. The path of the new worktree. - Path string - // required. The base branch/ref. - Base string - - // if true, ends up with a detached head - Detach bool - - // optional. if empty, and if detach is false, we will checkout the base - Branch string -} - -func (self *WorktreeCommands) New(opts NewWorktreeOpts) error { - if opts.Detach && opts.Branch != "" { - panic("cannot specify branch when detaching") - } - - cmdArgs := NewGitCmd("worktree").Arg("add"). - ArgIf(opts.Detach, "--detach"). - ArgIf(opts.Branch != "", "-b", opts.Branch). - Arg(opts.Path, opts.Base) - - return self.cmd.New(cmdArgs.ToArgv()).Run() -} - -func (self *WorktreeCommands) Delete(worktreePath string, force bool) error { - cmdArgs := NewGitCmd("worktree").Arg("remove").ArgIf(force, "-f").Arg(worktreePath).ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func (self *WorktreeCommands) Detach(worktreePath string) error { - cmdArgs := NewGitCmd("checkout").Arg("--detach").GitDir(filepath.Join(worktreePath, ".git")).ToArgv() - - return self.cmd.New(cmdArgs).Run() -} - -func WorktreeForBranch(branch *models.Branch, worktrees []*models.Worktree) (*models.Worktree, bool) { - for _, worktree := range worktrees { - if worktree.Branch == branch.Name { - return worktree, true - } - } - - return nil, false -} - -func CheckedOutByOtherWorktree(branch *models.Branch, worktrees []*models.Worktree) bool { - worktree, ok := WorktreeForBranch(branch, worktrees) - if !ok { - return false - } - - return !worktree.IsCurrent -} diff --git a/pkg/commands/git_commands/worktree_loader.go b/pkg/commands/git_commands/worktree_loader.go deleted file mode 100644 index 42881f2fc7a..00000000000 --- a/pkg/commands/git_commands/worktree_loader.go +++ /dev/null @@ -1,273 +0,0 @@ -package git_commands - -import ( - iofs "io/fs" - "path/filepath" - "strings" - "sync" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/spf13/afero" -) - -type WorktreeLoader struct { - *GitCommon -} - -func NewWorktreeLoader(gitCommon *GitCommon) *WorktreeLoader { - return &WorktreeLoader{GitCommon: gitCommon} -} - -func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) { - currentRepoPath := self.repoPaths.RepoPath() - worktreePath := self.repoPaths.WorktreePath() - - cmdArgs := NewGitCmd("worktree").Arg("list", "--porcelain").ToArgv() - worktreesOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - if err != nil { - return nil, err - } - - splitLines := strings.Split( - utils.NormalizeLinefeeds(worktreesOutput), "\n", - ) - - var worktrees []*models.Worktree - var current *models.Worktree - for _, splitLine := range splitLines { - // worktrees are defined over multiple lines and are separated by blank lines - // so if we reach a blank line we're done with the current worktree - if len(splitLine) == 0 && current != nil { - worktrees = append(worktrees, current) - current = nil - continue - } - - // ignore bare repo (not sure why it's even appearing in this list: it's not a worktree) - if splitLine == "bare" { - current = nil - continue - } - - if strings.HasPrefix(splitLine, "worktree ") { - path := strings.SplitN(splitLine, " ", 2)[1] - isMain := path == currentRepoPath - isCurrent := path == worktreePath - isPathMissing := self.pathExists(path) - - current = &models.Worktree{ - IsMain: isMain, - IsCurrent: isCurrent, - IsPathMissing: isPathMissing, - Path: path, - // we defer populating GitDir until a loop below so that - // we can parallelize the calls to git rev-parse - GitDir: "", - } - } else if strings.HasPrefix(splitLine, "branch ") { - branch := strings.SplitN(splitLine, " ", 2)[1] - current.Branch = strings.TrimPrefix(branch, "refs/heads/") - } - } - - wg := sync.WaitGroup{} - wg.Add(len(worktrees)) - for _, worktree := range worktrees { - go utils.Safe(func() { - defer wg.Done() - - if worktree.IsPathMissing { - return - } - gitDir, err := callGitRevParseWithDir(self.cmd, worktree.Path, "--absolute-git-dir") - if err != nil { - self.Log.Warnf("Could not find git dir for worktree %s: %v", worktree.Path, err) - return - } - - worktree.GitDir = gitDir - }) - } - wg.Wait() - - names := getUniqueNamesFromPaths(lo.Map(worktrees, func(worktree *models.Worktree, _ int) string { - return worktree.Path - })) - - for index, worktree := range worktrees { - worktree.Name = names[index] - } - - // move current worktree to the top - for i, worktree := range worktrees { - if worktree.IsCurrent { - worktrees = append(worktrees[:i], worktrees[i+1:]...) - worktrees = append([]*models.Worktree{worktree}, worktrees...) - break - } - } - - // Some worktrees are on a branch but are mid-rebase, and in those cases, - // `git worktree list` will not show the branch name. We can get the branch - // name from the `rebase-merge/head-name` file (if it exists) in the folder - // for the worktree in the parent repo's .git/worktrees folder. - for _, worktree := range worktrees { - // No point checking if we already have a branch name - if worktree.Branch != "" { - continue - } - - // If we couldn't find the git directory, we can't find the branch name - if worktree.GitDir == "" { - continue - } - - rebasedBranch, ok := self.rebasedBranch(worktree) - if ok { - worktree.Branch = rebasedBranch - continue - } - - bisectedBranch, ok := self.bisectedBranch(worktree) - if ok { - worktree.Branch = bisectedBranch - continue - } - } - - return worktrees, nil -} - -func (self *WorktreeLoader) pathExists(path string) bool { - if _, err := self.Fs.Stat(path); err != nil { - if errors.Is(err, iofs.ErrNotExist) { - return true - } - self.Log.Errorf("failed to check if worktree path `%s` exists\n%v", path, err) - return false - } - return false -} - -func (self *WorktreeLoader) rebasedBranch(worktree *models.Worktree) (string, bool) { - for _, dir := range []string{"rebase-merge", "rebase-apply"} { - if bytesContent, err := afero.ReadFile(self.Fs, filepath.Join(worktree.GitDir, dir, "head-name")); err == nil { - headName := strings.TrimSpace(string(bytesContent)) - shortHeadName := strings.TrimPrefix(headName, "refs/heads/") - return shortHeadName, true - } - } - - return "", false -} - -func (self *WorktreeLoader) bisectedBranch(worktree *models.Worktree) (string, bool) { - bisectStartPath := filepath.Join(worktree.GitDir, "BISECT_START") - startContent, err := afero.ReadFile(self.Fs, bisectStartPath) - if err != nil { - return "", false - } - - return strings.TrimSpace(string(startContent)), true -} - -type pathWithIndexT struct { - path string - index int -} - -type nameWithIndexT struct { - name string - index int -} - -func getUniqueNamesFromPaths(paths []string) []string { - pathsWithIndex := lo.Map(paths, func(path string, index int) pathWithIndexT { - return pathWithIndexT{path, index} - }) - - namesWithIndex := getUniqueNamesFromPathsAux(pathsWithIndex, 0) - - // now sort based on index - result := make([]string, len(namesWithIndex)) - for _, nameWithIndex := range namesWithIndex { - result[nameWithIndex.index] = nameWithIndex.name - } - - return result -} - -func getUniqueNamesFromPathsAux(paths []pathWithIndexT, depth int) []nameWithIndexT { - // If we have no paths, return an empty array - if len(paths) == 0 { - return []nameWithIndexT{} - } - - // If we have only one path, return the last segment of the path - if len(paths) == 1 { - path := paths[0] - return []nameWithIndexT{{index: path.index, name: sliceAtDepth(path.path, depth)}} - } - - // group the paths by their value at the specified depth - groups := make(map[string][]pathWithIndexT) - for _, path := range paths { - value := valueAtDepth(path.path, depth) - groups[value] = append(groups[value], path) - } - - result := []nameWithIndexT{} - for _, group := range groups { - if len(group) == 1 { - path := group[0] - result = append(result, nameWithIndexT{index: path.index, name: sliceAtDepth(path.path, depth)}) - } else { - result = append(result, getUniqueNamesFromPathsAux(group, depth+1)...) - } - } - - return result -} - -// if the path is /a/b/c/d, and the depth is 0, the value is 'd'. If the depth is 1, the value is 'c', etc -func valueAtDepth(path string, depth int) string { - path = strings.TrimPrefix(path, "/") - path = strings.TrimSuffix(path, "/") - - // Split the path into segments - segments := strings.Split(path, "/") - - // Get the length of segments - length := len(segments) - - // If the depth is greater than the length of segments, return an empty string - if depth >= length { - return "" - } - - // Return the segment at the specified depth from the end of the path - return segments[length-1-depth] -} - -// if the path is /a/b/c/d, and the depth is 0, the value is 'd'. If the depth is 1, the value is 'b/c', etc -func sliceAtDepth(path string, depth int) string { - path = strings.TrimPrefix(path, "/") - path = strings.TrimSuffix(path, "/") - - // Split the path into segments - segments := strings.Split(path, "/") - - // Get the length of segments - length := len(segments) - - // If the depth is greater than or equal to the length of segments, return an empty string - if depth >= length { - return "" - } - - // Join the segments from the specified depth till end of the path - return strings.Join(segments[length-1-depth:], "/") -} diff --git a/pkg/commands/git_commands/worktree_loader_test.go b/pkg/commands/git_commands/worktree_loader_test.go deleted file mode 100644 index 3d8c3ad393f..00000000000 --- a/pkg/commands/git_commands/worktree_loader_test.go +++ /dev/null @@ -1,256 +0,0 @@ -package git_commands - -import ( - "testing" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" -) - -func TestGetWorktrees(t *testing.T) { - type scenario struct { - testName string - repoPaths *RepoPaths - before func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) - expectedWorktrees []*models.Worktree - expectedErr string - } - - scenarios := []scenario{ - { - testName: "Single worktree (main)", - repoPaths: &RepoPaths{ - repoPath: "/path/to/repo", - worktreePath: "/path/to/repo", - }, - before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) { - runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"}, - `worktree /path/to/repo -HEAD d85cc9d281fa6ae1665c68365fc70e75e82a042d -branch refs/heads/mybranch -`, - nil) - - gitArgsMainWorktree := append(append([]string{"-C", "/path/to/repo"}, getRevParseArgs()...), "--absolute-git-dir") - runner.ExpectGitArgs(gitArgsMainWorktree, "/path/to/repo/.git", nil) - _ = fs.MkdirAll("/path/to/repo/.git", 0o755) - }, - expectedWorktrees: []*models.Worktree{ - { - IsMain: true, - IsCurrent: true, - Path: "/path/to/repo", - IsPathMissing: false, - GitDir: "/path/to/repo/.git", - Branch: "mybranch", - Name: "repo", - }, - }, - expectedErr: "", - }, - { - testName: "Multiple worktrees (main + linked)", - repoPaths: &RepoPaths{ - repoPath: "/path/to/repo", - worktreePath: "/path/to/repo", - }, - before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) { - runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"}, - `worktree /path/to/repo -HEAD d85cc9d281fa6ae1665c68365fc70e75e82a042d -branch refs/heads/mybranch - -worktree /path/to/repo-worktree -HEAD 775955775e79b8f5b4c4b56f82fbf657e2d5e4de -branch refs/heads/mybranch-worktree -`, - nil) - gitArgsMainWorktree := append(append([]string{"-C", "/path/to/repo"}, getRevParseArgs()...), "--absolute-git-dir") - runner.ExpectGitArgs(gitArgsMainWorktree, "/path/to/repo/.git", nil) - gitArgsLinkedWorktree := append(append([]string{"-C", "/path/to/repo-worktree"}, getRevParseArgs()...), "--absolute-git-dir") - runner.ExpectGitArgs(gitArgsLinkedWorktree, "/path/to/repo/.git/worktrees/repo-worktree", nil) - - _ = fs.MkdirAll("/path/to/repo/.git", 0o755) - _ = fs.MkdirAll("/path/to/repo-worktree", 0o755) - _ = fs.MkdirAll("/path/to/repo/.git/worktrees/repo-worktree", 0o755) - _ = afero.WriteFile(fs, "/path/to/repo-worktree/.git", []byte("gitdir: /path/to/repo/.git/worktrees/repo-worktree"), 0o755) - }, - expectedWorktrees: []*models.Worktree{ - { - IsMain: true, - IsCurrent: true, - Path: "/path/to/repo", - IsPathMissing: false, - GitDir: "/path/to/repo/.git", - Branch: "mybranch", - Name: "repo", - }, - { - IsMain: false, - IsCurrent: false, - Path: "/path/to/repo-worktree", - IsPathMissing: false, - GitDir: "/path/to/repo/.git/worktrees/repo-worktree", - Branch: "mybranch-worktree", - Name: "repo-worktree", - }, - }, - expectedErr: "", - }, - { - testName: "Worktree missing path", - repoPaths: &RepoPaths{ - repoPath: "/path/to/repo", - worktreePath: "/path/to/repo", - }, - before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) { - runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"}, - `worktree /path/to/worktree -HEAD 775955775e79b8f5b4c4b56f82fbf657e2d5e4de -branch refs/heads/missingbranch -`, - nil) - - _ = fs.MkdirAll("/path/to/repo/.git", 0o755) - }, - expectedWorktrees: []*models.Worktree{ - { - IsMain: false, - IsCurrent: false, - Path: "/path/to/worktree", - IsPathMissing: true, - GitDir: "", - Branch: "missingbranch", - Name: "worktree", - }, - }, - expectedErr: "", - }, - { - testName: "In linked worktree", - repoPaths: &RepoPaths{ - repoPath: "/path/to/repo", - worktreePath: "/path/to/repo-worktree", - }, - before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) { - runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"}, - `worktree /path/to/repo -HEAD d85cc9d281fa6ae1665c68365fc70e75e82a042d -branch refs/heads/mybranch - -worktree /path/to/repo-worktree -HEAD 775955775e79b8f5b4c4b56f82fbf657e2d5e4de -branch refs/heads/mybranch-worktree -`, - nil) - gitArgsMainWorktree := append(append([]string{"-C", "/path/to/repo"}, getRevParseArgs()...), "--absolute-git-dir") - runner.ExpectGitArgs(gitArgsMainWorktree, "/path/to/repo/.git", nil) - gitArgsLinkedWorktree := append(append([]string{"-C", "/path/to/repo-worktree"}, getRevParseArgs()...), "--absolute-git-dir") - runner.ExpectGitArgs(gitArgsLinkedWorktree, "/path/to/repo/.git/worktrees/repo-worktree", nil) - - _ = fs.MkdirAll("/path/to/repo/.git", 0o755) - _ = fs.MkdirAll("/path/to/repo-worktree", 0o755) - _ = fs.MkdirAll("/path/to/repo/.git/worktrees/repo-worktree", 0o755) - _ = afero.WriteFile(fs, "/path/to/repo-worktree/.git", []byte("gitdir: /path/to/repo/.git/worktrees/repo-worktree"), 0o755) - }, - expectedWorktrees: []*models.Worktree{ - { - IsMain: false, - IsCurrent: true, - Path: "/path/to/repo-worktree", - IsPathMissing: false, - GitDir: "/path/to/repo/.git/worktrees/repo-worktree", - Branch: "mybranch-worktree", - Name: "repo-worktree", - }, - { - IsMain: true, - IsCurrent: false, - Path: "/path/to/repo", - IsPathMissing: false, - GitDir: "/path/to/repo/.git", - Branch: "mybranch", - Name: "repo", - }, - }, - expectedErr: "", - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - runner := oscommands.NewFakeRunner(t) - fs := afero.NewMemMapFs() - version, err := GetGitVersion(oscommands.NewDummyOSCommand()) - if err != nil { - t.Fatal(err) - } - - getRevParseArgs := func() []string { - return []string{"rev-parse", "--path-format=absolute"} - } - - s.before(runner, fs, getRevParseArgs) - - loader := &WorktreeLoader{ - GitCommon: buildGitCommon(commonDeps{runner: runner, fs: fs, repoPaths: s.repoPaths, gitVersion: version}), - } - - worktrees, err := loader.GetWorktrees() - if s.expectedErr != "" { - assert.EqualError(t, errors.New(s.expectedErr), err.Error()) - } else { - assert.NoError(t, err) - assert.EqualValues(t, s.expectedWorktrees, worktrees) - } - }) - } -} - -func TestGetUniqueNamesFromPaths(t *testing.T) { - for _, scenario := range []struct { - input []string - expected []string - }{ - { - input: []string{}, - expected: []string{}, - }, - { - input: []string{ - "/my/path/feature/one", - }, - expected: []string{ - "one", - }, - }, - { - input: []string{ - "/my/path/feature/one/", - }, - expected: []string{ - "one", - }, - }, - { - input: []string{ - "/a/b/c/d", - "/a/b/c/e", - "/a/b/f/d", - "/a/e/c/d", - }, - expected: []string{ - "b/c/d", - "e", - "f/d", - "e/c/d", - }, - }, - } { - actual := getUniqueNamesFromPaths(scenario.input) - assert.EqualValues(t, scenario.expected, actual) - } -} diff --git a/pkg/commands/git_config/cached_git_config.go b/pkg/commands/git_config/cached_git_config.go deleted file mode 100644 index 256cd325b67..00000000000 --- a/pkg/commands/git_config/cached_git_config.go +++ /dev/null @@ -1,104 +0,0 @@ -package git_config - -import ( - "os/exec" - "strings" - "sync" - - "github.com/sirupsen/logrus" -) - -type IGitConfig interface { - // this is for when you want to pass 'mykey' (it calls `git config --get --null mykey` under the hood) - Get(string) string - // this is for when you want to pass '--local --get-regexp mykey' - GetGeneral(string) string - // this is for when you want to pass 'mykey' and check if the result is truthy - GetBool(string) bool - - DropCache() -} - -type CachedGitConfig struct { - cache map[string]string - runGitConfigCmd func(*exec.Cmd) (string, error) - log *logrus.Entry - mutex sync.Mutex -} - -func NewStdCachedGitConfig(log *logrus.Entry) *CachedGitConfig { - return NewCachedGitConfig(runGitConfigCmd, log) -} - -func NewCachedGitConfig(runGitConfigCmd func(*exec.Cmd) (string, error), log *logrus.Entry) *CachedGitConfig { - return &CachedGitConfig{ - cache: make(map[string]string), - runGitConfigCmd: runGitConfigCmd, - log: log, - mutex: sync.Mutex{}, - } -} - -func (self *CachedGitConfig) Get(key string) string { - self.mutex.Lock() - defer self.mutex.Unlock() - - if value, ok := self.cache[key]; ok { - self.log.Debug("using cache for key " + key) - return value - } - - value := self.getAux(key) - self.cache[key] = value - return value -} - -func (self *CachedGitConfig) GetGeneral(args string) string { - self.mutex.Lock() - defer self.mutex.Unlock() - - if value, ok := self.cache[args]; ok { - self.log.Debug("using cache for args " + args) - return value - } - - value := self.getGeneralAux(args) - self.cache[args] = value - return value -} - -func (self *CachedGitConfig) getGeneralAux(args string) string { - cmd := getGitConfigGeneralCmd(args) - value, err := self.runGitConfigCmd(cmd) - if err != nil { - self.log.Debugf("Error getting git config value for args: %s. Error: %v", args, err.Error()) - return "" - } - return strings.TrimSpace(value) -} - -func (self *CachedGitConfig) getAux(key string) string { - cmd := getGitConfigCmd(key) - value, err := self.runGitConfigCmd(cmd) - if err != nil { - self.log.Debugf("Error getting git config value for key: %s. Error: %v", key, err.Error()) - return "" - } - return strings.TrimSpace(value) -} - -func (self *CachedGitConfig) GetBool(key string) bool { - return isTruthy(self.Get(key)) -} - -func isTruthy(value string) bool { - lcValue := strings.ToLower(value) - return lcValue == "true" || lcValue == "1" || lcValue == "yes" || lcValue == "on" -} - -func (self *CachedGitConfig) DropCache() { - self.mutex.Lock() - defer self.mutex.Unlock() - - self.cache = make(map[string]string) -} diff --git a/pkg/commands/git_config/cached_git_config_test.go b/pkg/commands/git_config/cached_git_config_test.go deleted file mode 100644 index fd884df6528..00000000000 --- a/pkg/commands/git_config/cached_git_config_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package git_config - -import ( - "os/exec" - "strings" - "testing" - - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/stretchr/testify/assert" -) - -func TestGetBool(t *testing.T) { - type scenario struct { - testName string - mockResponses map[string]string - expected bool - } - - scenarios := []scenario{ - { - "Option global and local config commit.gpgsign is not set", - map[string]string{}, - false, - }, - { - "Some other random key is set", - map[string]string{"blah": "blah"}, - false, - }, - { - "Option commit.gpgsign is true", - map[string]string{"commit.gpgsign": "True"}, - true, - }, - { - "Option commit.gpgsign is on", - map[string]string{"commit.gpgsign": "ON"}, - true, - }, - { - "Option commit.gpgsign is yes", - map[string]string{"commit.gpgsign": "YeS"}, - true, - }, - { - "Option commit.gpgsign is 1", - map[string]string{"commit.gpgsign": "1"}, - true, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - fake := NewFakeGitConfig(s.mockResponses) - real := NewCachedGitConfig( - func(cmd *exec.Cmd) (string, error) { - assert.Equal(t, "config --get --null commit.gpgsign", strings.Join(cmd.Args[1:], " ")) - return fake.Get("commit.gpgsign"), nil - }, - utils.NewDummyLog(), - ) - result := real.GetBool("commit.gpgsign") - assert.Equal(t, s.expected, result) - }) - } -} - -func TestGet(t *testing.T) { - type scenario struct { - testName string - mockResponses map[string]string - expected string - } - - scenarios := []scenario{ - { - "not set", - map[string]string{}, - "", - }, - { - "is set", - map[string]string{"commit.gpgsign": "blah"}, - "blah", - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - fake := NewFakeGitConfig(s.mockResponses) - real := NewCachedGitConfig( - func(cmd *exec.Cmd) (string, error) { - assert.Equal(t, "config --get --null commit.gpgsign", strings.Join(cmd.Args[1:], " ")) - return fake.Get("commit.gpgsign"), nil - }, - utils.NewDummyLog(), - ) - result := real.Get("commit.gpgsign") - assert.Equal(t, s.expected, result) - }) - } - - // verifying that the cache is used - count := 0 - real := NewCachedGitConfig( - func(cmd *exec.Cmd) (string, error) { - count++ - assert.Equal(t, "config --get --null commit.gpgsign", strings.Join(cmd.Args[1:], " ")) - return "blah", nil - }, - utils.NewDummyLog(), - ) - result := real.Get("commit.gpgsign") - assert.Equal(t, "blah", result) - result = real.Get("commit.gpgsign") - assert.Equal(t, "blah", result) - assert.Equal(t, 1, count) -} diff --git a/pkg/commands/git_config/fake_git_config.go b/pkg/commands/git_config/fake_git_config.go deleted file mode 100644 index e82efcd1b3a..00000000000 --- a/pkg/commands/git_config/fake_git_config.go +++ /dev/null @@ -1,32 +0,0 @@ -package git_config - -type FakeGitConfig struct { - mockResponses map[string]string -} - -func NewFakeGitConfig(mockResponses map[string]string) *FakeGitConfig { - return &FakeGitConfig{ - mockResponses: mockResponses, - } -} - -func (self *FakeGitConfig) Get(key string) string { - if self.mockResponses == nil { - return "" - } - return self.mockResponses[key] -} - -func (self *FakeGitConfig) GetGeneral(args string) string { - if self.mockResponses == nil { - return "" - } - return self.mockResponses[args] -} - -func (self *FakeGitConfig) GetBool(key string) bool { - return isTruthy(self.Get(key)) -} - -func (self *FakeGitConfig) DropCache() { -} diff --git a/pkg/commands/git_config/get_key.go b/pkg/commands/git_config/get_key.go deleted file mode 100644 index 7369fa08e6e..00000000000 --- a/pkg/commands/git_config/get_key.go +++ /dev/null @@ -1,64 +0,0 @@ -package git_config - -import ( - "bytes" - "errors" - "fmt" - "io" - "os/exec" - "strings" - "syscall" -) - -// including license from https://github.com/tcnksm/go-gitconfig because this file is an adaptation of that repo's code -// Copyright (c) 2014 tcnksm - -// MIT License - -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: - -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -func runGitConfigCmd(cmd *exec.Cmd) (string, error) { - var stdout bytes.Buffer - cmd.Stdout = &stdout - cmd.Stderr = io.Discard - - err := cmd.Run() - var exitError *exec.ExitError - if errors.As(err, &exitError) { - if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok { - if waitStatus.ExitStatus() == 1 { - return "", fmt.Errorf("the key is not found for %s", cmd.Args) - } - } - return "", err - } - - return strings.TrimRight(stdout.String(), "\000"), nil -} - -func getGitConfigCmd(key string) *exec.Cmd { - gitArgs := []string{"config", "--get", "--null", key} - return exec.Command("git", gitArgs...) -} - -func getGitConfigGeneralCmd(args string) *exec.Cmd { - gitArgs := append([]string{"config"}, strings.Split(args, " ")...) - return exec.Command("git", gitArgs...) -} diff --git a/pkg/commands/hosting_service/definitions.go b/pkg/commands/hosting_service/definitions.go deleted file mode 100644 index bd82d99a5b7..00000000000 --- a/pkg/commands/hosting_service/definitions.go +++ /dev/null @@ -1,113 +0,0 @@ -package hosting_service - -// if you want to make a custom regex for a given service feel free to test it out -// at regoio.herokuapp.com -var defaultUrlRegexStrings = []string{ - `^(?:https?|ssh)://[^/]+/(?P.*)/(?P.*?)(?:\.git)?$`, - `^.*?@.*:(?P.*)/(?P.*?)(?:\.git)?$`, -} -var defaultRepoURLTemplate = "https://{{.webdomain}}/%7B%7B.owner%7D%7D/%7B%7B.repo%7D%7D" - -// we've got less type safety using go templates but this lends itself better to -// users adding custom service definitions in their config -var githubServiceDef = ServiceDefinition{ - provider: "github", - pullRequestURLIntoDefaultBranch: "/compare/{{.From}}?expand=1", - pullRequestURLIntoTargetBranch: "/compare/{{.To}}...{{.From}}?expand=1", - commitURL: "/commit/{{.CommitHash}}", - regexStrings: defaultUrlRegexStrings, - repoURLTemplate: defaultRepoURLTemplate, -} - -var bitbucketServiceDef = ServiceDefinition{ - provider: "bitbucket", - pullRequestURLIntoDefaultBranch: "/pull-requests/new?source={{.From}}&t=1", - pullRequestURLIntoTargetBranch: "/pull-requests/new?source={{.From}}&dest={{.To}}&t=1", - commitURL: "/commits/{{.CommitHash}}", - regexStrings: []string{ - `^(?:https?|ssh)://.*/(?P.*)/(?P.*?)(?:\.git)?$`, - `^.*@.*:(?P.*)/(?P.*?)(?:\.git)?$`, - }, - repoURLTemplate: defaultRepoURLTemplate, -} - -var gitLabServiceDef = ServiceDefinition{ - provider: "gitlab", - pullRequestURLIntoDefaultBranch: "/-/merge_requests/new?merge_request%5Bsource_branch%5D={{.From}}", - pullRequestURLIntoTargetBranch: "/-/merge_requests/new?merge_request%5Bsource_branch%5D={{.From}}&merge_request%5Btarget_branch%5D={{.To}}", - commitURL: "/-/commit/{{.CommitHash}}", - regexStrings: defaultUrlRegexStrings, - repoURLTemplate: defaultRepoURLTemplate, -} - -var azdoServiceDef = ServiceDefinition{ - provider: "azuredevops", - pullRequestURLIntoDefaultBranch: "/pullrequestcreate?sourceRef={{.From}}", - pullRequestURLIntoTargetBranch: "/pullrequestcreate?sourceRef={{.From}}&targetRef={{.To}}", - commitURL: "/commit/{{.CommitHash}}", - regexStrings: []string{ - `^.+@vs-ssh\.visualstudio\.com[:/](?:v3/)?(?P[^/]+)/(?P[^/]+)/(?P[^/]+?)(?:\.git)?$`, - `^git@ssh.dev.azure.com.*/(?P.*)/(?P.*)/(?P.*?)(?:\.git)?$`, - `^https://.*@dev.azure.com/(?P.*?)/(?P.*?)/_git/(?P.*?)(?:\.git)?$`, - `^https://.*/(?P.*?)/(?P.*?)/_git/(?P.*?)(?:\.git)?$`, - }, - repoURLTemplate: "https://{{.webdomain}}/%7B%7B.org%7D%7D/%7B%7B.project%7D%7D/_git/%7B%7B.repo%7D%7D", -} - -var bitbucketServerServiceDef = ServiceDefinition{ - provider: "bitbucketServer", - pullRequestURLIntoDefaultBranch: "/pull-requests?create&sourceBranch={{.From}}", - pullRequestURLIntoTargetBranch: "/pull-requests?create&targetBranch={{.To}}&sourceBranch={{.From}}", - commitURL: "/commits/{{.CommitHash}}", - regexStrings: []string{ - `^ssh://git@.*/(?P.*)/(?P.*?)(?:\.git)?$`, - `^https://.*/scm/(?P.*)/(?P.*?)(?:\.git)?$`, - }, - repoURLTemplate: "https://{{.webdomain}}/projects/%7B%7B.project%7D%7D/repos/%7B%7B.repo%7D%7D", -} - -var giteaServiceDef = ServiceDefinition{ - provider: "gitea", - pullRequestURLIntoDefaultBranch: "/compare/{{.From}}", - pullRequestURLIntoTargetBranch: "/compare/{{.To}}...{{.From}}", - commitURL: "/commit/{{.CommitHash}}", - regexStrings: defaultUrlRegexStrings, - repoURLTemplate: defaultRepoURLTemplate, -} - -var serviceDefinitions = []ServiceDefinition{ - githubServiceDef, - bitbucketServiceDef, - gitLabServiceDef, - azdoServiceDef, - bitbucketServerServiceDef, - giteaServiceDef, -} - -var defaultServiceDomains = []ServiceDomain{ - { - serviceDefinition: githubServiceDef, - gitDomain: "github.com", - webDomain: "github.com", - }, - { - serviceDefinition: bitbucketServiceDef, - gitDomain: "bitbucket.org", - webDomain: "bitbucket.org", - }, - { - serviceDefinition: gitLabServiceDef, - gitDomain: "gitlab.com", - webDomain: "gitlab.com", - }, - { - serviceDefinition: azdoServiceDef, - gitDomain: "dev.azure.com", - webDomain: "dev.azure.com", - }, - { - serviceDefinition: giteaServiceDef, - gitDomain: "try.gitea.io", - webDomain: "try.gitea.io", - }, -} diff --git a/pkg/commands/hosting_service/hosting_service.go b/pkg/commands/hosting_service/hosting_service.go deleted file mode 100644 index 1c328fd0da2..00000000000 --- a/pkg/commands/hosting_service/hosting_service.go +++ /dev/null @@ -1,182 +0,0 @@ -package hosting_service - -import ( - "net/url" - "regexp" - "strings" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/sirupsen/logrus" - - "golang.org/x/exp/slices" -) - -// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, gitea, etc. -// Different git hosting services have different URL formats for when you want to open a PR or view a commit, -// and this package's responsibility is to determine which service you're using based on the remote URL, -// and then which URL you need for whatever use case you have. - -type HostingServiceMgr struct { - log logrus.FieldLogger - tr *i18n.TranslationSet - remoteURL string // e.g. https://github.com/jesseduffield/lazygit - - // see https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-pull-request-urls - configServiceDomains map[string]string -} - -// NewHostingServiceMgr creates new instance of PullRequest -func NewHostingServiceMgr(log logrus.FieldLogger, tr *i18n.TranslationSet, remoteURL string, configServiceDomains map[string]string) *HostingServiceMgr { - return &HostingServiceMgr{ - log: log, - tr: tr, - remoteURL: remoteURL, - configServiceDomains: configServiceDomains, - } -} - -func (self *HostingServiceMgr) GetPullRequestURL(from string, to string) (string, error) { - gitService, err := self.getService() - if err != nil { - return "", err - } - - if to == "" { - return gitService.getPullRequestURLIntoDefaultBranch(url.QueryEscape(from)), nil - } - return gitService.getPullRequestURLIntoTargetBranch(url.QueryEscape(from), url.QueryEscape(to)), nil -} - -func (self *HostingServiceMgr) GetCommitURL(commitHash string) (string, error) { - gitService, err := self.getService() - if err != nil { - return "", err - } - - pullRequestURL := gitService.getCommitURL(commitHash) - - return pullRequestURL, nil -} - -func (self *HostingServiceMgr) getService() (*Service, error) { - serviceDomain, err := self.getServiceDomain(self.remoteURL) - if err != nil { - return nil, err - } - - repoURL, err := serviceDomain.serviceDefinition.getRepoURLFromRemoteURL(self.remoteURL, serviceDomain.webDomain) - if err != nil { - return nil, err - } - - return &Service{ - repoURL: repoURL, - ServiceDefinition: serviceDomain.serviceDefinition, - }, nil -} - -func (self *HostingServiceMgr) getServiceDomain(repoURL string) (*ServiceDomain, error) { - candidateServiceDomains := self.getCandidateServiceDomains() - - for _, serviceDomain := range candidateServiceDomains { - if strings.Contains(repoURL, serviceDomain.gitDomain) { - return &serviceDomain, nil - } - } - - return nil, errors.New(self.tr.UnsupportedGitService) -} - -func (self *HostingServiceMgr) getCandidateServiceDomains() []ServiceDomain { - serviceDefinitionByProvider := map[string]ServiceDefinition{} - for _, serviceDefinition := range serviceDefinitions { - serviceDefinitionByProvider[serviceDefinition.provider] = serviceDefinition - } - - serviceDomains := slices.Clone(defaultServiceDomains) - - for gitDomain, typeAndDomain := range self.configServiceDomains { - provider, webDomain, success := strings.Cut(typeAndDomain, ":") - - // we allow for one ':' for specifying the TCP port - if !success || strings.Count(webDomain, ":") > 1 { - self.log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain) - continue - } - - serviceDefinition, ok := serviceDefinitionByProvider[provider] - if !ok { - providerNames := lo.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition, _ int) string { - return serviceDefinition.provider - }) - - self.log.Errorf("Unknown git service type: '%s'. Expected one of %s", provider, strings.Join(providerNames, ", ")) - continue - } - - serviceDomains = append(serviceDomains, ServiceDomain{ - gitDomain: gitDomain, - webDomain: webDomain, - serviceDefinition: serviceDefinition, - }) - } - - return serviceDomains -} - -// a service domains pairs a service definition with the actual domain it's being served from. -// Sometimes the git service is hosted in a custom domains so although it'll use say -// the github service definition, it'll actually be served from e.g. my-custom-github.com -type ServiceDomain struct { - gitDomain string // the one that appears in the git remote url - webDomain string // the one that appears in the web url - serviceDefinition ServiceDefinition -} - -type ServiceDefinition struct { - provider string - pullRequestURLIntoDefaultBranch string - pullRequestURLIntoTargetBranch string - commitURL string - regexStrings []string - - // can expect 'webdomain' to be passed in. Otherwise, you get to pick what we match in the regex - repoURLTemplate string -} - -func (self ServiceDefinition) getRepoURLFromRemoteURL(url string, webDomain string) (string, error) { - for _, regexStr := range self.regexStrings { - re := regexp.MustCompile(regexStr) - input := utils.FindNamedMatches(re, url) - if input != nil { - input["webDomain"] = webDomain - return utils.ResolvePlaceholderString(self.repoURLTemplate, input), nil - } - } - - return "", errors.New("Failed to parse repo information from url") -} - -type Service struct { - repoURL string - ServiceDefinition -} - -func (self *Service) getPullRequestURLIntoDefaultBranch(from string) string { - return self.resolveUrl(self.pullRequestURLIntoDefaultBranch, map[string]string{"From": from}) -} - -func (self *Service) getPullRequestURLIntoTargetBranch(from string, to string) string { - return self.resolveUrl(self.pullRequestURLIntoTargetBranch, map[string]string{"From": from, "To": to}) -} - -func (self *Service) getCommitURL(commitHash string) string { - return self.resolveUrl(self.commitURL, map[string]string{"CommitHash": commitHash}) -} - -func (self *Service) resolveUrl(templateString string, args map[string]string) string { - return self.repoURL + utils.ResolvePlaceholderString(templateString, args) -} diff --git a/pkg/commands/hosting_service/hosting_service_test.go b/pkg/commands/hosting_service/hosting_service_test.go deleted file mode 100644 index bcf5e6fb4d2..00000000000 --- a/pkg/commands/hosting_service/hosting_service_test.go +++ /dev/null @@ -1,449 +0,0 @@ -package hosting_service - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/fakes" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/stretchr/testify/assert" -) - -func TestGetPullRequestURL(t *testing.T) { - type scenario struct { - testName string - from string - to string - remoteUrl string - configServiceDomains map[string]string - test func(url string, err error) - expectedLoggedErrors []string - } - - scenarios := []scenario{ - { - testName: "Opens a link to new pull request on bitbucket", - from: "feature/profile-page", - remoteUrl: "git@bitbucket.org:johndoe/social_network.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fprofile-page&t=1", url) - }, - }, - { - testName: "Opens a link to new pull request on bitbucket with http remote url", - from: "feature/events", - remoteUrl: "https://my_username@bitbucket.org/johndoe/social_network.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fevents&t=1", url) - }, - }, - { - testName: "Opens a link to new pull request on github", - from: "feature/sum-operation", - remoteUrl: "git@github.com:peter/calculator.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Fsum-operation?expand=1", url) - }, - }, - { - testName: "Opens a link to new pull request on github with https remote url", - from: "feature/sum-operation", - remoteUrl: "https://github.com/peter/calculator.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Fsum-operation?expand=1", url) - }, - }, - { - testName: "Opens a link to new pull request on bitbucket with specific target branch", - from: "feature/profile-page/avatar", - to: "feature/profile-page", - remoteUrl: "git@bitbucket.org:johndoe/social_network.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fprofile-page%2Favatar&dest=feature%2Fprofile-page&t=1", url) - }, - }, - { - testName: "Opens a link to new pull request on bitbucket with http remote url with specified target branch", - from: "feature/remote-events", - to: "feature/events", - remoteUrl: "https://my_username@bitbucket.org/johndoe/social_network.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fremote-events&dest=feature%2Fevents&t=1", url) - }, - }, - { - testName: "Opens a link to new pull request on github with specific target branch", - from: "feature/sum-operation", - to: "feature/operations", - remoteUrl: "git@github.com:peter/calculator.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Foperations...feature%2Fsum-operation?expand=1", url) - }, - }, - { - testName: "Opens a link to new pull request on github with specific target branch (different git username)", - from: "feature/sum-operation", - to: "feature/operations", - remoteUrl: "ssh://org-12345@github.com:peter/calculator.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Foperations...feature%2Fsum-operation?expand=1", url) - }, - }, - { - testName: "Opens a link to new pull request on github with https remote url with specific target branch", - from: "feature/sum-operation", - to: "feature/operations", - remoteUrl: "https://github.com/peter/calculator.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Foperations...feature%2Fsum-operation?expand=1", url) - }, - }, - { - testName: "Opens a link to new pull request on gitlab", - from: "feature/ui", - remoteUrl: "git@gitlab.com:peter/calculator.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/calculator/-/merge_requests/new?merge_request%5Bsource_branch%5D=feature%2Fui", url) - }, - }, - { - testName: "Opens a link to new pull request on gitlab in nested groups", - from: "feature/ui", - remoteUrl: "git@gitlab.com:peter/public/calculator.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/public/calculator/-/merge_requests/new?merge_request%5Bsource_branch%5D=feature%2Fui", url) - }, - }, - { - testName: "Opens a link to new pull request on gitlab with https remote url in nested groups", - from: "feature/ui", - remoteUrl: "https://gitlab.com/peter/public/calculator.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/public/calculator/-/merge_requests/new?merge_request%5Bsource_branch%5D=feature%2Fui", url) - }, - }, - { - testName: "Opens a link to new pull request on gitlab with specific target branch", - from: "feature/commit-ui", - to: "epic/ui", - remoteUrl: "git@gitlab.com:peter/calculator.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/calculator/-/merge_requests/new?merge_request%5Bsource_branch%5D=feature%2Fcommit-ui&merge_request%5Btarget_branch%5D=epic%2Fui", url) - }, - }, - { - testName: "Opens a link to new pull request on gitlab with specific target branch in nested groups", - from: "feature/commit-ui", - to: "epic/ui", - remoteUrl: "git@gitlab.com:peter/public/calculator.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/public/calculator/-/merge_requests/new?merge_request%5Bsource_branch%5D=feature%2Fcommit-ui&merge_request%5Btarget_branch%5D=epic%2Fui", url) - }, - }, - { - testName: "Opens a link to new pull request on gitlab with https remote url with specific target branch in nested groups", - from: "feature/commit-ui", - to: "epic/ui", - remoteUrl: "https://gitlab.com/peter/public/calculator.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/public/calculator/-/merge_requests/new?merge_request%5Bsource_branch%5D=feature%2Fcommit-ui&merge_request%5Btarget_branch%5D=epic%2Fui", url) - }, - }, - { - testName: "Opens a link to new pull request on bitbucket with a custom SSH username", - from: "feature/profile-page", - remoteUrl: "john@bitbucket.org:johndoe/social_network.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fprofile-page&t=1", url) - }, - }, - { - testName: "Opens a link to new pull request on Azure DevOps (SSH)", - from: "feature/new", - remoteUrl: "git@ssh.dev.azure.com:v3/myorg/myproject/myrepo", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://dev.azure.com/myorg/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew", url) - }, - }, - { - testName: "Opens a link to new pull request on Azure DevOps (SSH) with specific target", - from: "feature/new", - to: "dev", - remoteUrl: "git@ssh.dev.azure.com:v3/myorg/myproject/myrepo", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://dev.azure.com/myorg/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew&targetRef=dev", url) - }, - }, - { - testName: "Opens a link to new pull request on Azure DevOps (HTTP)", - from: "feature/new", - remoteUrl: "https://myorg@dev.azure.com/myorg/myproject/_git/myrepo", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://dev.azure.com/myorg/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew", url) - }, - }, - { - testName: "Opens a link to new pull request on Azure DevOps (HTTP) with specific target", - from: "feature/new", - to: "dev", - remoteUrl: "https://myorg@dev.azure.com/myorg/myproject/_git/myrepo", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://dev.azure.com/myorg/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew&targetRef=dev", url) - }, - }, - { - testName: "Opens a link to new pull request on Azure DevOps Server (HTTP)", - from: "feature/new", - remoteUrl: "https://mycompany.azuredevops.com/collection/myproject/_git/myrepo", - configServiceDomains: map[string]string{ - // valid configuration for a azure devops server URL - "mycompany.azuredevops.com": "azuredevops:mycompany.azuredevops.com", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://mycompany.azuredevops.com/collection/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew", url) - }, - }, - { - testName: "Opens a link to new pull request on Azure DevOps (legacy vs-ssh.visualstudio.com SSH) with mapping to dev.azure.com", - from: "feature/new", - remoteUrl: "git@vs-ssh.visualstudio.com:v3/myorg/myproject/myrepo", - configServiceDomains: map[string]string{ - "vs-ssh.visualstudio.com": "azuredevops:dev.azure.com", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://dev.azure.com/myorg/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew", url) - }, - }, - { - testName: "Opens a link to new pull request on Bitbucket Server (SSH)", - from: "feature/new", - remoteUrl: "ssh://git@mycompany.bitbucket.com/myproject/myrepo.git", - configServiceDomains: map[string]string{ - // valid configuration for a bitbucket server URL - "mycompany.bitbucket.com": "bitbucketServer:mycompany.bitbucket.com", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://mycompany.bitbucket.com/projects/myproject/repos/myrepo/pull-requests?create&sourceBranch=feature%2Fnew", url) - }, - }, - { - testName: "Opens a link to new pull request on Bitbucket Server (SSH) with specific target", - from: "feature/new", - to: "dev", - remoteUrl: "ssh://git@mycompany.bitbucket.com/myproject/myrepo.git", - configServiceDomains: map[string]string{ - // valid configuration for a bitbucket server URL - "mycompany.bitbucket.com": "bitbucketServer:mycompany.bitbucket.com", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://mycompany.bitbucket.com/projects/myproject/repos/myrepo/pull-requests?create&targetBranch=dev&sourceBranch=feature%2Fnew", url) - }, - }, - { - testName: "Opens a link to new pull request on Bitbucket Server (HTTP)", - from: "feature/new", - remoteUrl: "https://mycompany.bitbucket.com/scm/myproject/myrepo.git", - configServiceDomains: map[string]string{ - // valid configuration for a bitbucket server URL - "mycompany.bitbucket.com": "bitbucketServer:mycompany.bitbucket.com", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://mycompany.bitbucket.com/projects/myproject/repos/myrepo/pull-requests?create&sourceBranch=feature%2Fnew", url) - }, - }, - { - testName: "Opens a link to new pull request on Bitbucket Server (HTTP) with specific target", - from: "feature/new", - to: "dev", - remoteUrl: "https://mycompany.bitbucket.com/scm/myproject/myrepo.git", - configServiceDomains: map[string]string{ - // valid configuration for a bitbucket server URL - "mycompany.bitbucket.com": "bitbucketServer:mycompany.bitbucket.com", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://mycompany.bitbucket.com/projects/myproject/repos/myrepo/pull-requests?create&targetBranch=dev&sourceBranch=feature%2Fnew", url) - }, - }, - { - testName: "Opens a link to new pull request on Gitea Server (SSH)", - from: "feature/new", - remoteUrl: "ssh://git@mycompany.gitea.io/myproject/myrepo.git", - configServiceDomains: map[string]string{ - // valid configuration for a gitea server URL - "mycompany.gitea.io": "gitea:mycompany.gitea.io", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/feature%2Fnew", url) - }, - }, - { - testName: "Opens a link to new pull request on Gitea Server (SSH) with specific target", - from: "feature/new", - to: "dev", - remoteUrl: "ssh://git@mycompany.gitea.io/myproject/myrepo.git", - configServiceDomains: map[string]string{ - // valid configuration for a gitea server URL - "mycompany.gitea.io": "gitea:mycompany.gitea.io", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/dev...feature%2Fnew", url) - }, - }, - { - testName: "Opens a link to new pull request on Gitea Server (HTTP)", - from: "feature/new", - remoteUrl: "https://mycompany.gitea.io/myproject/myrepo.git", - configServiceDomains: map[string]string{ - // valid configuration for a gitea server URL - "mycompany.gitea.io": "gitea:mycompany.gitea.io", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/feature%2Fnew", url) - }, - }, - { - testName: "Opens a link to new pull request on Gitea Server (HTTP) with specific target", - from: "feature/new", - to: "dev", - remoteUrl: "https://mycompany.gitea.io/myproject/myrepo.git", - configServiceDomains: map[string]string{ - // valid configuration for a gitea server URL - "mycompany.gitea.io": "gitea:mycompany.gitea.io", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/dev...feature%2Fnew", url) - }, - }, - { - testName: "Throws an error if git service is unsupported", - from: "feature/divide-operation", - remoteUrl: "git@something.com:peter/calculator.git", - test: func(url string, err error) { - assert.EqualError(t, err, "Unsupported git service") - }, - }, - { - testName: "Does not log error when config service domains are valid", - from: "feature/profile-page", - remoteUrl: "git@bitbucket.org:johndoe/social_network.git", - configServiceDomains: map[string]string{ - // valid configuration for a custom service URL - "git.work.com": "gitlab:code.work.com", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fprofile-page&t=1", url) - }, - expectedLoggedErrors: nil, - }, - { - testName: "Does not log error when config service webDomain contains a port", - from: "feature/profile-page", - remoteUrl: "git@my.domain.test:johndoe/social_network.git", - configServiceDomains: map[string]string{ - "my.domain.test": "gitlab:my.domain.test:1111", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://my.domain.test:1111/johndoe/social_network/-/merge_requests/new?merge_request%5Bsource_branch%5D=feature%2Fprofile-page", url) - }, - }, - { - testName: "Logs error when webDomain contains more than one colon", - from: "feature/profile-page", - remoteUrl: "git@my.domain.test:johndoe/social_network.git", - configServiceDomains: map[string]string{ - "my.domain.test": "gitlab:my.domain.test:1111:2222", - }, - test: func(url string, err error) { - assert.Error(t, err) - }, - expectedLoggedErrors: []string{"Unexpected format for git service: 'gitlab:my.domain.test:1111:2222'. Expected something like 'github.com:github.com'"}, - }, - { - testName: "Logs error when config service domain is malformed", - from: "feature/profile-page", - remoteUrl: "git@bitbucket.org:johndoe/social_network.git", - configServiceDomains: map[string]string{ - "noservice.work.com": "noservice.work.com", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fprofile-page&t=1", url) - }, - expectedLoggedErrors: []string{"Unexpected format for git service: 'noservice.work.com'. Expected something like 'github.com:github.com'"}, - }, - { - testName: "Logs error when config service domain uses unknown provider", - from: "feature/profile-page", - remoteUrl: "git@bitbucket.org:johndoe/social_network.git", - configServiceDomains: map[string]string{ - "invalid.work.com": "noservice:invalid.work.com", - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fprofile-page&t=1", url) - }, - expectedLoggedErrors: []string{"Unknown git service type: 'noservice'. Expected one of github, bitbucket, gitlab, azuredevops, bitbucketServer, gitea"}, - }, - { - testName: "Escapes reserved URL characters in from branch name", - from: "feature/someIssue#123", - to: "master", - remoteUrl: "git@gitlab.com:me/public/repo-with-issues.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/me/public/repo-with-issues/-/merge_requests/new?merge_request%5Bsource_branch%5D=feature%2FsomeIssue%23123&merge_request%5Btarget_branch%5D=master", url) - }, - }, - { - testName: "Escapes reserved URL characters in to branch name", - from: "yolo", - to: "archive/never-ending-feature#666", - remoteUrl: "git@gitlab.com:me/public/repo-with-issues.git", - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/me/public/repo-with-issues/-/merge_requests/new?merge_request%5Bsource_branch%5D=yolo&merge_request%5Btarget_branch%5D=archive%2Fnever-ending-feature%23666", url) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - tr := i18n.EnglishTranslationSet() - log := &fakes.FakeFieldLogger{} - hostingServiceMgr := NewHostingServiceMgr(log, tr, s.remoteUrl, s.configServiceDomains) - s.test(hostingServiceMgr.GetPullRequestURL(s.from, s.to)) - log.AssertErrors(t, s.expectedLoggedErrors) - }) - } -} diff --git a/pkg/commands/models/author.go b/pkg/commands/models/author.go deleted file mode 100644 index ece9b7972d0..00000000000 --- a/pkg/commands/models/author.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -import "fmt" - -// A commit author -type Author struct { - Name string - Email string -} - -func (self *Author) Combined() string { - return fmt.Sprintf("%s <%s>", self.Name, self.Email) -} diff --git a/pkg/commands/models/branch.go b/pkg/commands/models/branch.go deleted file mode 100644 index 4dc48a88d8d..00000000000 --- a/pkg/commands/models/branch.go +++ /dev/null @@ -1,125 +0,0 @@ -package models - -import ( - "fmt" - "sync/atomic" -) - -// Branch : A git branch -// duplicating this for now -type Branch struct { - Name string - // the displayname is something like '(HEAD detached at 123asdf)', whereas in that case the name would be '123asdf' - DisplayName string - // indicator of when the branch was last checked out e.g. '2d', '3m' - Recency string - // how many commits ahead we are from the remote branch (how many commits we can push, assuming we push to our tracked remote branch) - AheadForPull string - // how many commits behind we are from the remote branch (how many commits we can pull) - BehindForPull string - // how many commits ahead we are from the branch we're pushing to (which might not be the same as our upstream branch in a triangular workflow) - AheadForPush string - // how many commits behind we are from the branch we're pushing to (which might not be the same as our upstream branch in a triangular workflow) - BehindForPush string - // whether the remote branch is 'gone' i.e. we're tracking a remote branch that has been deleted - UpstreamGone bool - // whether this is the current branch. Exactly one branch should have this be true - Head bool - DetachedHead bool - // if we have a named remote locally this will be the name of that remote e.g. - // 'origin' or 'tiwood'. If we don't have the remote locally it'll look like - // 'git@github.com:tiwood/lazygit.git' - UpstreamRemote string - UpstreamBranch string - // subject line in commit message - Subject string - // commit hash - CommitHash string - - // How far we have fallen behind our base branch. 0 means either not - // determined yet, or up to date with base branch. (We don't need to - // distinguish the two, as we don't draw anything in both cases.) - BehindBaseBranch atomic.Int32 -} - -func (b *Branch) FullRefName() string { - if b.DetachedHead { - return b.Name - } - return "refs/heads/" + b.Name -} - -func (b *Branch) RefName() string { - return b.Name -} - -func (b *Branch) ShortRefName() string { - return b.RefName() -} - -func (b *Branch) ParentRefName() string { - return b.RefName() + "^" -} - -func (b *Branch) FullUpstreamRefName() string { - if b.UpstreamRemote == "" || b.UpstreamBranch == "" { - return "" - } - - return fmt.Sprintf("refs/remotes/%s/%s", b.UpstreamRemote, b.UpstreamBranch) -} - -func (b *Branch) ShortUpstreamRefName() string { - if b.UpstreamRemote == "" || b.UpstreamBranch == "" { - return "" - } - - return fmt.Sprintf("%s/%s", b.UpstreamRemote, b.UpstreamBranch) -} - -func (b *Branch) ID() string { - return b.RefName() -} - -func (b *Branch) URN() string { - return "branch-" + b.ID() -} - -func (b *Branch) Description() string { - return b.RefName() -} - -func (b *Branch) IsTrackingRemote() bool { - return b.UpstreamRemote != "" -} - -// we know that the remote branch is not stored locally based on our pushable/pullable -// count being question marks. -func (b *Branch) RemoteBranchStoredLocally() bool { - return b.IsTrackingRemote() && b.AheadForPull != "?" && b.BehindForPull != "?" -} - -func (b *Branch) RemoteBranchNotStoredLocally() bool { - return b.IsTrackingRemote() && b.AheadForPull == "?" && b.BehindForPull == "?" -} - -func (b *Branch) MatchesUpstream() bool { - return b.RemoteBranchStoredLocally() && b.AheadForPull == "0" && b.BehindForPull == "0" -} - -func (b *Branch) IsAheadForPull() bool { - return b.RemoteBranchStoredLocally() && b.AheadForPull != "0" -} - -func (b *Branch) IsBehindForPull() bool { - return b.RemoteBranchStoredLocally() && b.BehindForPull != "0" -} - -func (b *Branch) IsBehindForPush() bool { - return b.RemoteBranchStoredLocally() && b.BehindForPush != "0" -} - -// for when we're in a detached head state -func (b *Branch) IsRealBranch() bool { - return b.AheadForPull != "" && b.BehindForPull != "" -} diff --git a/pkg/commands/models/commit.go b/pkg/commands/models/commit.go deleted file mode 100644 index 8dfec4d4b75..00000000000 --- a/pkg/commands/models/commit.go +++ /dev/null @@ -1,159 +0,0 @@ -package models - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/stefanhaller/git-todo-parser/todo" -) - -// Special commit hash for empty tree object -const EmptyTreeCommitHash = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" - -type CommitStatus uint8 - -const ( - StatusNone CommitStatus = iota - StatusUnpushed - StatusPushed - StatusMerged - StatusRebasing - StatusCherryPickingOrReverting - StatusConflicted - StatusReflog -) - -const ( - // Conveniently for us, the todo package starts the enum at 1, and given - // that it doesn't have a "none" value, we're setting ours to 0 - ActionNone todo.TodoCommand = 0 -) - -type Divergence uint8 - -// For a divergence log (left/right comparison of two refs) this is set to -// either DivergenceLeft or DivergenceRight for each commit; for normal -// commit views it is always DivergenceNone. -const ( - DivergenceNone Divergence = iota - DivergenceLeft - DivergenceRight -) - -// Commit : A git commit -type Commit struct { - hash *string - Name string - Tags []string - ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2' - AuthorName string // something like 'Jesse Duffield' - AuthorEmail string // something like 'jessedduffield@gmail.com' - UnixTimestamp int64 - - // Hashes of parent commits (will be multiple if it's a merge commit) - parents []*string - - // When filtering by path, this contains the paths that were changed in this - // commit; nil when not filtering by path. - FilterPaths []string - - Status CommitStatus - Action todo.TodoCommand - Divergence Divergence // set to DivergenceNone unless we are showing the divergence view -} - -type NewCommitOpts struct { - Hash string - Name string - Status CommitStatus - Action todo.TodoCommand - Tags []string - ExtraInfo string - AuthorName string - AuthorEmail string - UnixTimestamp int64 - Divergence Divergence - Parents []string -} - -func NewCommit(hashPool *utils.StringPool, opts NewCommitOpts) *Commit { - return &Commit{ - hash: hashPool.Add(opts.Hash), - Name: opts.Name, - Status: opts.Status, - Action: opts.Action, - Tags: opts.Tags, - ExtraInfo: opts.ExtraInfo, - AuthorName: opts.AuthorName, - AuthorEmail: opts.AuthorEmail, - UnixTimestamp: opts.UnixTimestamp, - Divergence: opts.Divergence, - parents: lo.Map(opts.Parents, func(s string, _ int) *string { return hashPool.Add(s) }), - } -} - -func (c *Commit) Hash() string { - return *c.hash -} - -func (c *Commit) HashPtr() *string { - return c.hash -} - -func (c *Commit) ShortHash() string { - return utils.ShortHash(c.Hash()) -} - -func (c *Commit) FullRefName() string { - return c.Hash() -} - -func (c *Commit) RefName() string { - return c.Hash() -} - -func (c *Commit) ShortRefName() string { - return c.Hash()[:7] -} - -func (c *Commit) ParentRefName() string { - if c.IsFirstCommit() { - return EmptyTreeCommitHash - } - return c.RefName() + "^" -} - -func (c *Commit) Parents() []string { - return lo.Map(c.parents, func(s *string, _ int) string { return *s }) -} - -func (c *Commit) ParentPtrs() []*string { - return c.parents -} - -func (c *Commit) IsFirstCommit() bool { - return len(c.parents) == 0 -} - -func (c *Commit) ID() string { - return c.RefName() -} - -func (c *Commit) Description() string { - return fmt.Sprintf("%s %s", c.Hash()[:7], c.Name) -} - -func (c *Commit) IsMerge() bool { - return len(c.parents) > 1 -} - -// returns true if this commit is not actually in the git log but instead -// is from a TODO file for an interactive rebase. -func (c *Commit) IsTODO() bool { - return c.Action != ActionNone -} - -func IsHeadCommit(commits []*Commit, index int) bool { - return !commits[index].IsTODO() && (index == 0 || commits[index-1].IsTODO()) -} diff --git a/pkg/commands/models/commit_file.go b/pkg/commands/models/commit_file.go deleted file mode 100644 index 90ffd6365a1..00000000000 --- a/pkg/commands/models/commit_file.go +++ /dev/null @@ -1,28 +0,0 @@ -package models - -// CommitFile : A git commit file -type CommitFile struct { - Path string - - ChangeStatus string // e.g. 'A' for added or 'M' for modified. This is based on the result from git diff --name-status -} - -func (f *CommitFile) ID() string { - return f.Path -} - -func (f *CommitFile) Description() string { - return f.Path -} - -func (f *CommitFile) Added() bool { - return f.ChangeStatus == "A" -} - -func (f *CommitFile) Deleted() bool { - return f.ChangeStatus == "D" -} - -func (f *CommitFile) GetPath() string { - return f.Path -} diff --git a/pkg/commands/models/file.go b/pkg/commands/models/file.go deleted file mode 100644 index e48696a4f0c..00000000000 --- a/pkg/commands/models/file.go +++ /dev/null @@ -1,164 +0,0 @@ -package models - -import ( - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -// File : A file from git status -// duplicating this for now -type File struct { - Path string - PreviousPath string - HasStagedChanges bool - HasUnstagedChanges bool - Tracked bool - Added bool - Deleted bool - HasMergeConflicts bool - HasInlineMergeConflicts bool - DisplayString string - ShortStatus string // e.g. 'AD', ' A', 'M ', '??' - LinesDeleted int - LinesAdded int - - // If true, this must be a worktree folder - IsWorktree bool -} - -// sometimes we need to deal with either a node (which contains a file) or an actual file -type IFile interface { - GetHasUnstagedChanges() bool - GetHasStagedChanges() bool - GetIsTracked() bool - GetPath() string - GetPreviousPath() string - GetIsFile() bool -} - -func (f *File) IsRename() bool { - return f.PreviousPath != "" -} - -// Names returns an array containing just the filename, or in the case of a rename, the after filename and the before filename -func (f *File) Names() []string { - result := []string{f.Path} - if f.PreviousPath != "" { - result = append(result, f.PreviousPath) - } - return result -} - -// returns true if the file names are the same or if a file rename includes the filename of the other -func (f *File) Matches(f2 *File) bool { - return utils.StringArraysOverlap(f.Names(), f2.Names()) -} - -func (f *File) ID() string { - return f.Path -} - -func (f *File) Description() string { - return f.Path -} - -func (f *File) IsSubmodule(configs []*SubmoduleConfig) bool { - return f.SubmoduleConfig(configs) != nil -} - -func (f *File) SubmoduleConfig(configs []*SubmoduleConfig) *SubmoduleConfig { - for _, config := range configs { - if f.Path == config.Path { - return config - } - } - - return nil -} - -func (f *File) GetHasUnstagedChanges() bool { - return f.HasUnstagedChanges -} - -func (f *File) GetHasStagedChanges() bool { - return f.HasStagedChanges -} - -func (f *File) GetIsTracked() bool { - return f.Tracked -} - -func (f *File) GetPath() string { - // TODO: remove concept of name; just use path - return f.Path -} - -func (f *File) GetPreviousPath() string { - return f.PreviousPath -} - -func (f *File) GetIsFile() bool { - return true -} - -func (f *File) GetMergeStateDescription(tr *i18n.TranslationSet) string { - m := map[string]string{ - "DD": tr.MergeConflictDescription_DD, - "AU": tr.MergeConflictDescription_AU, - "UA": tr.MergeConflictDescription_UA, - "DU": tr.MergeConflictDescription_DU, - "UD": tr.MergeConflictDescription_UD, - } - - if description, ok := m[f.ShortStatus]; ok { - return description - } - - panic("should only be called if there's a merge conflict") -} - -type StatusFields struct { - HasStagedChanges bool - HasUnstagedChanges bool - Tracked bool - Deleted bool - Added bool - HasMergeConflicts bool - HasInlineMergeConflicts bool - ShortStatus string -} - -func SetStatusFields(file *File, shortStatus string) { - derived := deriveStatusFields(shortStatus) - - file.HasStagedChanges = derived.HasStagedChanges - file.HasUnstagedChanges = derived.HasUnstagedChanges - file.Tracked = derived.Tracked - file.Deleted = derived.Deleted - file.Added = derived.Added - file.HasMergeConflicts = derived.HasMergeConflicts - file.HasInlineMergeConflicts = derived.HasInlineMergeConflicts - file.ShortStatus = derived.ShortStatus -} - -// shortStatus is something like '??' or 'A ' -func deriveStatusFields(shortStatus string) StatusFields { - stagedChange := shortStatus[0:1] - unstagedChange := shortStatus[1:2] - tracked := !lo.Contains([]string{"??", "A ", "AM"}, shortStatus) - hasStagedChanges := !lo.Contains([]string{" ", "U", "?"}, stagedChange) - hasInlineMergeConflicts := lo.Contains([]string{"UU", "AA"}, shortStatus) - hasMergeConflicts := hasInlineMergeConflicts || lo.Contains([]string{"DD", "AU", "UA", "UD", "DU"}, shortStatus) - - return StatusFields{ - HasStagedChanges: hasStagedChanges, - HasUnstagedChanges: unstagedChange != " ", - Tracked: tracked, - Deleted: unstagedChange == "D" || stagedChange == "D", - Added: unstagedChange == "A" || !tracked, - HasMergeConflicts: hasMergeConflicts, - HasInlineMergeConflicts: hasInlineMergeConflicts, - ShortStatus: shortStatus, - } -} diff --git a/pkg/commands/models/ref.go b/pkg/commands/models/ref.go deleted file mode 100644 index 6ef94ee6dbc..00000000000 --- a/pkg/commands/models/ref.go +++ /dev/null @@ -1,9 +0,0 @@ -package models - -type Ref interface { - FullRefName() string - RefName() string - ShortRefName() string - ParentRefName() string - Description() string -} diff --git a/pkg/commands/models/remote.go b/pkg/commands/models/remote.go deleted file mode 100644 index 418b458339e..00000000000 --- a/pkg/commands/models/remote.go +++ /dev/null @@ -1,24 +0,0 @@ -package models - -// Remote : A git remote -type Remote struct { - Name string - Urls []string - Branches []*RemoteBranch -} - -func (r *Remote) RefName() string { - return r.Name -} - -func (r *Remote) ID() string { - return r.RefName() -} - -func (r *Remote) URN() string { - return "remote-" + r.ID() -} - -func (r *Remote) Description() string { - return r.RefName() -} diff --git a/pkg/commands/models/remote_branch.go b/pkg/commands/models/remote_branch.go deleted file mode 100644 index 1e89ef582d4..00000000000 --- a/pkg/commands/models/remote_branch.go +++ /dev/null @@ -1,35 +0,0 @@ -package models - -// Remote Branch : A git remote branch -type RemoteBranch struct { - Name string - RemoteName string -} - -func (r *RemoteBranch) FullName() string { - return r.RemoteName + "/" + r.Name -} - -func (r *RemoteBranch) FullRefName() string { - return "refs/remotes/" + r.FullName() -} - -func (r *RemoteBranch) RefName() string { - return r.FullName() -} - -func (r *RemoteBranch) ShortRefName() string { - return r.RefName() -} - -func (r *RemoteBranch) ParentRefName() string { - return r.RefName() + "^" -} - -func (r *RemoteBranch) ID() string { - return r.RefName() -} - -func (r *RemoteBranch) Description() string { - return r.RefName() -} diff --git a/pkg/commands/models/stash_entry.go b/pkg/commands/models/stash_entry.go deleted file mode 100644 index caf20b1d6c8..00000000000 --- a/pkg/commands/models/stash_entry.go +++ /dev/null @@ -1,35 +0,0 @@ -package models - -import "fmt" - -// StashEntry : A git stash entry -type StashEntry struct { - Index int - Recency string - Name string - Hash string -} - -func (s *StashEntry) FullRefName() string { - return "refs/" + s.RefName() -} - -func (s *StashEntry) RefName() string { - return fmt.Sprintf("stash@{%d}", s.Index) -} - -func (s *StashEntry) ShortRefName() string { - return s.RefName() -} - -func (s *StashEntry) ParentRefName() string { - return s.RefName() + "^" -} - -func (s *StashEntry) ID() string { - return s.RefName() -} - -func (s *StashEntry) Description() string { - return s.RefName() + ": " + s.Name -} diff --git a/pkg/commands/models/submodule_config.go b/pkg/commands/models/submodule_config.go deleted file mode 100644 index 7df0d131abf..00000000000 --- a/pkg/commands/models/submodule_config.go +++ /dev/null @@ -1,48 +0,0 @@ -package models - -import "path/filepath" - -type SubmoduleConfig struct { - Name string - Path string - Url string - - ParentModule *SubmoduleConfig // nil if top-level -} - -func (r *SubmoduleConfig) FullName() string { - if r.ParentModule != nil { - return r.ParentModule.FullName() + "/" + r.Name - } - - return r.Name -} - -func (r *SubmoduleConfig) FullPath() string { - if r.ParentModule != nil { - return r.ParentModule.FullPath() + "/" + r.Path - } - - return r.Path -} - -func (r *SubmoduleConfig) RefName() string { - return r.FullName() -} - -func (r *SubmoduleConfig) ID() string { - return r.RefName() -} - -func (r *SubmoduleConfig) Description() string { - return r.RefName() -} - -func (r *SubmoduleConfig) GitDirPath(repoGitDirPath string) string { - parentPath := repoGitDirPath - if r.ParentModule != nil { - parentPath = r.ParentModule.GitDirPath(repoGitDirPath) - } - - return filepath.Join(parentPath, "modules", r.Name) -} diff --git a/pkg/commands/models/tag.go b/pkg/commands/models/tag.go deleted file mode 100644 index 876e2cd77ed..00000000000 --- a/pkg/commands/models/tag.go +++ /dev/null @@ -1,37 +0,0 @@ -package models - -// Tag : A git tag -type Tag struct { - Name string - // this is either the first line of the message of an annotated tag, or the - // first line of a commit message for a lightweight tag - Message string -} - -func (t *Tag) FullRefName() string { - return "refs/tags/" + t.RefName() -} - -func (t *Tag) RefName() string { - return t.Name -} - -func (t *Tag) ShortRefName() string { - return t.RefName() -} - -func (t *Tag) ParentRefName() string { - return t.RefName() + "^" -} - -func (t *Tag) ID() string { - return t.RefName() -} - -func (t *Tag) URN() string { - return "tag-" + t.ID() -} - -func (t *Tag) Description() string { - return t.Message -} diff --git a/pkg/commands/models/working_tree_state.go b/pkg/commands/models/working_tree_state.go deleted file mode 100644 index 3e7bdbf1b40..00000000000 --- a/pkg/commands/models/working_tree_state.go +++ /dev/null @@ -1,112 +0,0 @@ -package models - -import "github.com/jesseduffield/lazygit/pkg/i18n" - -// The state of the working tree. Several of these can be true at once. -// In particular, the concrete multi-state combinations that can occur in -// practice are Rebasing+CherryPicking, and Rebasing+Reverting. Theoretically, I -// guess Rebasing+Merging could also happen, but it probably won't in practice. -type WorkingTreeState struct { - Rebasing bool - Merging bool - CherryPicking bool - Reverting bool -} - -func (self WorkingTreeState) Any() bool { - return self.Rebasing || self.Merging || self.CherryPicking || self.Reverting -} - -func (self WorkingTreeState) None() bool { - return !self.Any() -} - -type EffectiveWorkingTreeState int - -const ( - // this means we're neither rebasing nor merging, cherry-picking, or reverting - WORKING_TREE_STATE_NONE EffectiveWorkingTreeState = iota - WORKING_TREE_STATE_REBASING - WORKING_TREE_STATE_MERGING - WORKING_TREE_STATE_CHERRY_PICKING - WORKING_TREE_STATE_REVERTING -) - -// Effective returns the "current" state; if several states are true at once, -// this is the one that should be displayed in status views, and it's the one -// that the user can continue or abort. -// -// As an example, if you are stopped in an interactive rebase, and then you -// perform a cherry-pick, and the cherry-pick conflicts, then both -// WorkingTreeState.Rebasing and WorkingTreeState.CherryPicking are true. -// The effective state is cherry-picking, because that's the one you can -// continue or abort. It is not possible to continue the rebase without first -// aborting the cherry-pick. -func (self WorkingTreeState) Effective() EffectiveWorkingTreeState { - if self.Reverting { - return WORKING_TREE_STATE_REVERTING - } - if self.CherryPicking { - return WORKING_TREE_STATE_CHERRY_PICKING - } - if self.Merging { - return WORKING_TREE_STATE_MERGING - } - if self.Rebasing { - return WORKING_TREE_STATE_REBASING - } - return WORKING_TREE_STATE_NONE -} - -func (self WorkingTreeState) Title(tr *i18n.TranslationSet) string { - return map[EffectiveWorkingTreeState]string{ - WORKING_TREE_STATE_REBASING: tr.RebasingStatus, - WORKING_TREE_STATE_MERGING: tr.MergingStatus, - WORKING_TREE_STATE_CHERRY_PICKING: tr.CherryPickingStatus, - WORKING_TREE_STATE_REVERTING: tr.RevertingStatus, - }[self.Effective()] -} - -func (self WorkingTreeState) LowerCaseTitle(tr *i18n.TranslationSet) string { - return map[EffectiveWorkingTreeState]string{ - WORKING_TREE_STATE_REBASING: tr.LowercaseRebasingStatus, - WORKING_TREE_STATE_MERGING: tr.LowercaseMergingStatus, - WORKING_TREE_STATE_CHERRY_PICKING: tr.LowercaseCherryPickingStatus, - WORKING_TREE_STATE_REVERTING: tr.LowercaseRevertingStatus, - }[self.Effective()] -} - -func (self WorkingTreeState) OptionsMenuTitle(tr *i18n.TranslationSet) string { - return map[EffectiveWorkingTreeState]string{ - WORKING_TREE_STATE_REBASING: tr.RebaseOptionsTitle, - WORKING_TREE_STATE_MERGING: tr.MergeOptionsTitle, - WORKING_TREE_STATE_CHERRY_PICKING: tr.CherryPickOptionsTitle, - WORKING_TREE_STATE_REVERTING: tr.RevertOptionsTitle, - }[self.Effective()] -} - -func (self WorkingTreeState) OptionsMapTitle(tr *i18n.TranslationSet) string { - return map[EffectiveWorkingTreeState]string{ - WORKING_TREE_STATE_REBASING: tr.ViewRebaseOptions, - WORKING_TREE_STATE_MERGING: tr.ViewMergeOptions, - WORKING_TREE_STATE_CHERRY_PICKING: tr.ViewCherryPickOptions, - WORKING_TREE_STATE_REVERTING: tr.ViewRevertOptions, - }[self.Effective()] -} - -func (self WorkingTreeState) CommandName() string { - return map[EffectiveWorkingTreeState]string{ - WORKING_TREE_STATE_REBASING: "rebase", - WORKING_TREE_STATE_MERGING: "merge", - WORKING_TREE_STATE_CHERRY_PICKING: "cherry-pick", - WORKING_TREE_STATE_REVERTING: "revert", - }[self.Effective()] -} - -func (self WorkingTreeState) CanShowTodos() bool { - return self.Rebasing || self.CherryPicking || self.Reverting -} - -func (self WorkingTreeState) CanSkip() bool { - return self.Rebasing || self.CherryPicking || self.Reverting -} diff --git a/pkg/commands/models/worktree.go b/pkg/commands/models/worktree.go deleted file mode 100644 index 58d4bf7906d..00000000000 --- a/pkg/commands/models/worktree.go +++ /dev/null @@ -1,37 +0,0 @@ -package models - -// A git worktree -type Worktree struct { - // if false, this is a linked worktree - IsMain bool - // if true, this is the worktree that is currently checked out - IsCurrent bool - // path to the directory of the worktree i.e. the directory that contains all the user's files - Path string - // if true, the path is not found - IsPathMissing bool - // path of the git directory for this worktree. The equivalent of the .git directory - // in the main worktree. For linked worktrees this would be /.git/worktrees/ - GitDir string - // If the worktree has a branch checked out, this field will be set to the branch name. - // A branch is considered 'checked out' if: - // * the worktree is directly on the branch - // * the worktree is mid-rebase on the branch - // * the worktree is mid-bisect on the branch - Branch string - // based on the path, but uniquified. Not the same name that git uses in the worktrees/ folder (no good reason for this, - // I just prefer my naming convention better) - Name string -} - -func (w *Worktree) RefName() string { - return w.Name -} - -func (w *Worktree) ID() string { - return w.Path -} - -func (w *Worktree) Description() string { - return w.RefName() -} diff --git a/pkg/commands/oscommands/cmd_obj.go b/pkg/commands/oscommands/cmd_obj.go deleted file mode 100644 index 96392dcb459..00000000000 --- a/pkg/commands/oscommands/cmd_obj.go +++ /dev/null @@ -1,224 +0,0 @@ -package oscommands - -import ( - "os/exec" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/samber/lo" - "github.com/sasha-s/go-deadlock" -) - -// A command object is a general way to represent a command to be run on the -// command line. -type CmdObj struct { - cmd *exec.Cmd - - runner ICmdObjRunner - - // see DontLog() - dontLog bool - - // see StreamOutput() - streamOutput bool - - // see UsePty() - usePty bool - - // see IgnoreEmptyError() - ignoreEmptyError bool - - // if set to true, it means we might be asked to enter a username/password by this command. - credentialStrategy CredentialStrategy - task gocui.Task - - // can be set so that we don't run certain commands simultaneously - mutex *deadlock.Mutex -} - -type CredentialStrategy int - -const ( - // do not expect a credential request. If we end up getting one - // we'll be in trouble because the command will hang indefinitely - NONE CredentialStrategy = iota - // expect a credential request and if we get one, prompt the user to enter their username/password - PROMPT - // in this case we will check for a credential request (i.e. the command pauses to ask for - // username/password) and if we get one, we just submit a newline, forcing the - // command to fail. We use this e.g. for a background `git fetch` to prevent it - // from hanging indefinitely. - FAIL -) - -func (self *CmdObj) GetCmd() *exec.Cmd { - return self.cmd -} - -// outputs string representation of command. Note that if the command was built -// using NewFromArgs, the output won't be quite the same as what you would type -// into a terminal e.g. 'sh -c git commit' as opposed to 'sh -c "git commit"' -func (self *CmdObj) ToString() string { - // if a given arg contains a space, we need to wrap it in quotes - quotedArgs := lo.Map(self.cmd.Args, func(arg string, _ int) string { - if strings.Contains(arg, " ") { - return `"` + arg + `"` - } - return arg - }) - - return strings.Join(quotedArgs, " ") -} - -// outputs args vector e.g. ["git", "commit", "-m", "my message"] -func (self *CmdObj) Args() []string { - return self.cmd.Args -} - -// Set a string to be used as stdin for the command. -func (self *CmdObj) SetStdin(input string) *CmdObj { - self.cmd.Stdin = strings.NewReader(input) - - return self -} - -func (self *CmdObj) AddEnvVars(vars ...string) *CmdObj { - self.cmd.Env = append(self.cmd.Env, vars...) - - return self -} - -func (self *CmdObj) GetEnvVars() []string { - return self.cmd.Env -} - -// sets the working directory -func (self *CmdObj) SetWd(wd string) *CmdObj { - self.cmd.Dir = wd - - return self -} - -// By calling DontLog(), we're saying that once we call Run(), we don't want to -// log the command in the UI (it'll still be logged in the log file). The general rule -// is that if a command doesn't change the git state (e.g. read commands like `git diff`) -// then we don't want to log it. If we are changing something (e.g. `git add .`) then -// we do. The only exception is if we're running a command in the background periodically -// like `git fetch`, which technically does mutate stuff but isn't something we need -// to notify the user about. -func (self *CmdObj) DontLog() *CmdObj { - self.dontLog = true - return self -} - -// This returns false if DontLog() was called -func (self *CmdObj) ShouldLog() bool { - return !self.dontLog -} - -// when you call this, then call Run(), we'll stream the output to the cmdWriter (i.e. the command log panel) -func (self *CmdObj) StreamOutput() *CmdObj { - self.streamOutput = true - - return self -} - -// returns true if StreamOutput() was called -func (self *CmdObj) ShouldStreamOutput() bool { - return self.streamOutput -} - -// when you call this, then call Run(), we'll use a PTY to run the command. Only -// has an effect if StreamOutput() was also called. Ignored on Windows. -func (self *CmdObj) UsePty() *CmdObj { - self.usePty = true - - return self -} - -// returns true if UsePty() was called -func (self *CmdObj) ShouldUsePty() bool { - return self.usePty -} - -// if you call this before ShouldStreamOutput we'll consider an error with no -// stderr content as a non-error. Not yet supported for Run or RunWithOutput ( -// but adding support is trivial) -func (self *CmdObj) IgnoreEmptyError() *CmdObj { - self.ignoreEmptyError = true - - return self -} - -// returns true if IgnoreEmptyError() was called -func (self *CmdObj) ShouldIgnoreEmptyError() bool { - return self.ignoreEmptyError -} - -func (self *CmdObj) Mutex() *deadlock.Mutex { - return self.mutex -} - -func (self *CmdObj) WithMutex(mutex *deadlock.Mutex) *CmdObj { - self.mutex = mutex - - return self -} - -// runs the command and returns an error if any -func (self *CmdObj) Run() error { - return self.runner.Run(self) -} - -// runs the command and returns the output as a string, and an error if any -func (self *CmdObj) RunWithOutput() (string, error) { - return self.runner.RunWithOutput(self) -} - -// runs the command and returns stdout and stderr as a string, and an error if any -func (self *CmdObj) RunWithOutputs() (string, string, error) { - return self.runner.RunWithOutputs(self) -} - -// runs the command and runs a callback function on each line of the output. If the callback -// returns true for the boolean value, we kill the process and return. -func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) error { - return self.runner.RunAndProcessLines(self, onLine) -} - -func (self *CmdObj) PromptOnCredentialRequest(task gocui.Task) *CmdObj { - self.credentialStrategy = PROMPT - self.usePty = true - self.task = task - - return self -} - -func (self *CmdObj) FailOnCredentialRequest() *CmdObj { - self.credentialStrategy = FAIL - self.usePty = true - - return self -} - -func (self *CmdObj) GetCredentialStrategy() CredentialStrategy { - return self.credentialStrategy -} - -func (self *CmdObj) GetTask() gocui.Task { - return self.task -} - -func (self *CmdObj) Clone() *CmdObj { - clone := &CmdObj{} - *clone = *self - clone.cmd = cloneCmd(self.cmd) - return clone -} - -func cloneCmd(cmd *exec.Cmd) *exec.Cmd { - clone := &exec.Cmd{} - *clone = *cmd - - return clone -} diff --git a/pkg/commands/oscommands/cmd_obj_builder.go b/pkg/commands/oscommands/cmd_obj_builder.go deleted file mode 100644 index fde64258210..00000000000 --- a/pkg/commands/oscommands/cmd_obj_builder.go +++ /dev/null @@ -1,100 +0,0 @@ -package oscommands - -import ( - "fmt" - "os" - "os/exec" - "strings" - - "github.com/mgutz/str" -) - -type ICmdObjBuilder interface { - // NewFromArgs takes a slice of strings like []string{"git", "commit"} and returns a new command object. - New(args []string) *CmdObj - // NewShell takes a string like `git commit` and returns an executable shell command for it e.g. `sh -c 'git commit'` - // shellFunctionsFile is an optional file path that will be sourced before executing the command. Callers should pass - // the value of UserConfig.OS.ShellFunctionsFile. - NewShell(commandStr string, shellFunctionsFile string) *CmdObj - // Quote wraps a string in quotes with any necessary escaping applied. The reason for bundling this up with the other methods in this interface is that we basically always need to make use of this when creating new command objects. - Quote(str string) string -} - -type CmdObjBuilder struct { - runner ICmdObjRunner - platform *Platform -} - -// poor man's version of explicitly saying that struct X implements interface Y -var _ ICmdObjBuilder = &CmdObjBuilder{} - -func (self *CmdObjBuilder) New(args []string) *CmdObj { - cmdObj := self.NewWithEnviron(args, os.Environ()) - return cmdObj -} - -// A command with explicit environment from env -func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) *CmdObj { - cmd := exec.Command(args[0], args[1:]...) - cmd.Env = env - - return &CmdObj{ - cmd: cmd, - runner: self.runner, - } -} - -func (self *CmdObjBuilder) NewShell(commandStr string, shellFunctionsFile string) *CmdObj { - if len(shellFunctionsFile) > 0 { - commandStr = fmt.Sprintf("%ssource %s\n%s", self.platform.PrefixForShellFunctionsFile, shellFunctionsFile, commandStr) - } - quotedCommand := self.quotedCommandString(commandStr) - cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand)) - - return self.New(cmdArgs) -} - -func (self *CmdObjBuilder) quotedCommandString(commandStr string) string { - // Windows does not seem to like quotes around the command - if self.platform.OS == "windows" { - return strings.NewReplacer( - "^", "^^", - "&", "^&", - "|", "^|", - "<", "^<", - ">", "^>", - "%", "^%", - ).Replace(commandStr) - } - - return self.Quote(commandStr) -} - -func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdObjRunner) *CmdObjBuilder { - decoratedRunner := decorate(self.runner) - - return &CmdObjBuilder{ - runner: decoratedRunner, - platform: self.platform, - } -} - -func (self *CmdObjBuilder) Quote(message string) string { - var quote string - if self.platform.OS == "windows" { - quote = `\"` - message = strings.NewReplacer( - `"`, `"'"'"`, - `\"`, `\\"`, - ).Replace(message) - } else { - quote = `"` - message = strings.NewReplacer( - `\`, `\\`, - `"`, `\"`, - `$`, `\$`, - "`", "\\`", - ).Replace(message) - } - return quote + message + quote -} diff --git a/pkg/commands/oscommands/cmd_obj_runner.go b/pkg/commands/oscommands/cmd_obj_runner.go deleted file mode 100644 index 953937706b1..00000000000 --- a/pkg/commands/oscommands/cmd_obj_runner.go +++ /dev/null @@ -1,465 +0,0 @@ -package oscommands - -import ( - "bufio" - "bytes" - "io" - "os/exec" - "regexp" - "strings" - "time" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/sasha-s/go-deadlock" - "github.com/sirupsen/logrus" -) - -type ICmdObjRunner interface { - Run(cmdObj *CmdObj) error - RunWithOutput(cmdObj *CmdObj) (string, error) - RunWithOutputs(cmdObj *CmdObj) (string, string, error) - RunAndProcessLines(cmdObj *CmdObj, onLine func(line string) (bool, error)) error -} - -type cmdObjRunner struct { - log *logrus.Entry - guiIO *guiIO -} - -var _ ICmdObjRunner = &cmdObjRunner{} - -func (self *cmdObjRunner) Run(cmdObj *CmdObj) error { - if cmdObj.Mutex() != nil { - cmdObj.Mutex().Lock() - defer cmdObj.Mutex().Unlock() - } - - if cmdObj.GetCredentialStrategy() != NONE { - return self.runWithCredentialHandling(cmdObj) - } - - if cmdObj.ShouldStreamOutput() { - return self.runAndStream(cmdObj) - } - - _, err := self.RunWithOutputAux(cmdObj) - return err -} - -func (self *cmdObjRunner) RunWithOutput(cmdObj *CmdObj) (string, error) { - if cmdObj.Mutex() != nil { - cmdObj.Mutex().Lock() - defer cmdObj.Mutex().Unlock() - } - - if cmdObj.GetCredentialStrategy() != NONE { - err := self.runWithCredentialHandling(cmdObj) - // for now we're not capturing output, just because it would take a little more - // effort and there's currently no use case for it. Some commands call RunWithOutput - // but ignore the output, hence why we've got this check here. - return "", err - } - - if cmdObj.ShouldStreamOutput() { - err := self.runAndStream(cmdObj) - // for now we're not capturing output, just because it would take a little more - // effort and there's currently no use case for it. Some commands call RunWithOutput - // but ignore the output, hence why we've got this check here. - return "", err - } - - return self.RunWithOutputAux(cmdObj) -} - -func (self *cmdObjRunner) RunWithOutputs(cmdObj *CmdObj) (string, string, error) { - if cmdObj.Mutex() != nil { - cmdObj.Mutex().Lock() - defer cmdObj.Mutex().Unlock() - } - - if cmdObj.GetCredentialStrategy() != NONE { - err := self.runWithCredentialHandling(cmdObj) - // for now we're not capturing output, just because it would take a little more - // effort and there's currently no use case for it. Some commands call RunWithOutputs - // but ignore the output, hence why we've got this check here. - return "", "", err - } - - if cmdObj.ShouldStreamOutput() { - err := self.runAndStream(cmdObj) - // for now we're not capturing output, just because it would take a little more - // effort and there's currently no use case for it. Some commands call RunWithOutputs - // but ignore the output, hence why we've got this check here. - return "", "", err - } - - return self.RunWithOutputsAux(cmdObj) -} - -func (self *cmdObjRunner) RunWithOutputAux(cmdObj *CmdObj) (string, error) { - self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand") - - if cmdObj.ShouldLog() { - self.logCmdObj(cmdObj) - } - - t := time.Now() - output, err := sanitisedCommandOutput(cmdObj.GetCmd().CombinedOutput()) - if err != nil { - self.log.WithField("command", cmdObj.ToString()).Error(output) - } - - self.log.Infof("%s (%s)", cmdObj.ToString(), time.Since(t)) - - return output, err -} - -func (self *cmdObjRunner) RunWithOutputsAux(cmdObj *CmdObj) (string, string, error) { - self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand") - - if cmdObj.ShouldLog() { - self.logCmdObj(cmdObj) - } - - t := time.Now() - var outBuffer, errBuffer bytes.Buffer - cmd := cmdObj.GetCmd() - cmd.Stdout = &outBuffer - cmd.Stderr = &errBuffer - err := cmd.Run() - - self.log.Infof("%s (%s)", cmdObj.ToString(), time.Since(t)) - - stdout := outBuffer.String() - stderr, err := sanitisedCommandOutput(errBuffer.Bytes(), err) - if err != nil { - self.log.WithField("command", cmdObj.ToString()).Error(stderr) - } - - return stdout, stderr, err -} - -func (self *cmdObjRunner) RunAndProcessLines(cmdObj *CmdObj, onLine func(line string) (bool, error)) error { - if cmdObj.Mutex() != nil { - cmdObj.Mutex().Lock() - defer cmdObj.Mutex().Unlock() - } - - if cmdObj.GetCredentialStrategy() != NONE { - return errors.New("cannot call RunAndProcessLines with credential strategy. If you're seeing this then a contributor to Lazygit has accidentally called this method! Please raise an issue") - } - - if cmdObj.ShouldLog() { - self.logCmdObj(cmdObj) - } - t := time.Now() - - cmd := cmdObj.GetCmd() - stdoutPipe, err := cmd.StdoutPipe() - if err != nil { - return err - } - - scanner := bufio.NewScanner(stdoutPipe) - scanner.Split(utils.ScanLinesAndTruncateWhenLongerThanBuffer(bufio.MaxScanTokenSize)) - if err := cmd.Start(); err != nil { - return err - } - - for scanner.Scan() { - line := scanner.Text() - stop, err := onLine(line) - if err != nil { - stdoutPipe.Close() - return err - } - if stop { - stdoutPipe.Close() // close the pipe so that the called process terminates - break - } - } - - if scanner.Err() != nil { - stdoutPipe.Close() - return scanner.Err() - } - - _ = cmd.Wait() - - self.log.Infof("%s (%s)", cmdObj.ToString(), time.Since(t)) - - return nil -} - -func (self *cmdObjRunner) logCmdObj(cmdObj *CmdObj) { - self.guiIO.logCommandFn(cmdObj.ToString(), true) -} - -func sanitisedCommandOutput(output []byte, err error) (string, error) { - outputString := string(output) - if err != nil { - // errors like 'exit status 1' are not very useful so we'll create an error - // from the combined output - if outputString == "" { - return "", utils.WrapError(err) - } - return outputString, errors.New(outputString) - } - return outputString, nil -} - -type cmdHandler struct { - stdoutPipe io.Reader - stdinPipe io.Writer - close func() error -} - -func (self *cmdObjRunner) runAndStream(cmdObj *CmdObj) error { - return self.runAndStreamAux(cmdObj, func(handler *cmdHandler, cmdWriter io.Writer) { - go func() { - _, _ = io.Copy(cmdWriter, handler.stdoutPipe) - }() - }) -} - -func (self *cmdObjRunner) runAndStreamAux( - cmdObj *CmdObj, - onRun func(*cmdHandler, io.Writer), -) error { - cmdWriter := self.guiIO.newCmdWriterFn() - - if cmdObj.ShouldLog() { - self.logCmdObj(cmdObj) - } - self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand") - cmd := cmdObj.GetCmd() - - var stderr bytes.Buffer - cmd.Stderr = io.MultiWriter(cmdWriter, &stderr) - - var handler *cmdHandler - var err error - if cmdObj.ShouldUsePty() { - handler, err = self.getCmdHandlerPty(cmd) - } else { - handler, err = self.getCmdHandlerNonPty(cmd) - } - if err != nil { - return err - } - - var stdout bytes.Buffer - handler.stdoutPipe = io.TeeReader(handler.stdoutPipe, &stdout) - - defer func() { - if closeErr := handler.close(); closeErr != nil { - self.log.Error(closeErr) - } - }() - - t := time.Now() - - onRun(handler, cmdWriter) - - err = cmd.Wait() - - self.log.Infof("%s (%s)", cmdObj.ToString(), time.Since(t)) - - if err != nil { - errStr := stderr.String() - if errStr != "" { - return errors.New(errStr) - } - - if cmdObj.ShouldIgnoreEmptyError() { - return nil - } - stdoutStr := stdout.String() - if stdoutStr != "" { - return errors.New(stdoutStr) - } - return errors.New("Command exited with non-zero exit code, but no output") - } - - return nil -} - -type CredentialType int - -const ( - Password CredentialType = iota - Username - Passphrase - PIN - Token -) - -// Whenever we're asked for a password we return a nil channel to tell the -// caller to kill the process. -var failPromptFn = func(CredentialType) <-chan string { - return nil -} - -func (self *cmdObjRunner) runWithCredentialHandling(cmdObj *CmdObj) error { - promptFn, err := self.getCredentialPromptFn(cmdObj) - if err != nil { - return err - } - - return self.runAndDetectCredentialRequest(cmdObj, promptFn) -} - -func (self *cmdObjRunner) getCredentialPromptFn(cmdObj *CmdObj) (func(CredentialType) <-chan string, error) { - switch cmdObj.GetCredentialStrategy() { - case PROMPT: - return self.guiIO.promptForCredentialFn, nil - case FAIL: - return failPromptFn, nil - default: - // we should never land here - return nil, errors.New("runWithCredentialHandling called but cmdObj does not have a credential strategy") - } -} - -// runAndDetectCredentialRequest detect a username / password / passphrase question in a command -// promptUserForCredential is a function that gets executed when this function detect you need to fill in a password or passphrase -// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back -func (self *cmdObjRunner) runAndDetectCredentialRequest( - cmdObj *CmdObj, - promptUserForCredential func(CredentialType) <-chan string, -) error { - // setting the output to english so we can parse it for a username/password request - cmdObj.AddEnvVars("LANG=C", "LC_ALL=C", "LC_MESSAGES=C") - - return self.runAndStreamAux(cmdObj, func(handler *cmdHandler, cmdWriter io.Writer) { - tr := io.TeeReader(handler.stdoutPipe, cmdWriter) - - go utils.Safe(func() { - self.processOutput(tr, handler.stdinPipe, promptUserForCredential, handler.close, cmdObj) - }) - }) -} - -func (self *cmdObjRunner) processOutput( - reader io.Reader, - writer io.Writer, - promptUserForCredential func(CredentialType) <-chan string, - closeFunc func() error, - cmdObj *CmdObj, -) { - checkForCredentialRequest := self.getCheckForCredentialRequestFunc() - task := cmdObj.GetTask() - - scanner := bufio.NewScanner(reader) - scanner.Split(bufio.ScanBytes) - for scanner.Scan() { - newBytes := scanner.Bytes() - askFor, ok := checkForCredentialRequest(newBytes) - if ok { - responseChan := promptUserForCredential(askFor) - if responseChan == nil { - // Returning a nil channel means we should terminate the process. - // We achieve this by closing the pty that it's running in. Note that this won't - // work for the case where we're not running in a pty (i.e. on Windows), but - // in that case we'll never be prompted for credentials, so it's not a concern. - if err := closeFunc(); err != nil { - self.log.Error(err) - } - break - } - - if task != nil { - task.Pause() - } - toInput := <-responseChan - if task != nil { - task.Continue() - } - // If the return data is empty we don't write anything to stdin - if toInput != "" { - _, _ = writer.Write([]byte(toInput)) - } - } - } -} - -// having a function that returns a function because we need to maintain some state inbetween calls hence the closure -func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (CredentialType, bool) { - var ttyText strings.Builder - prompts := map[string]CredentialType{ - `Password:`: Password, - `.+'s password:`: Password, - `Password\s*for\s*'.+':`: Password, - `Username\s*for\s*'.+':`: Username, - `Enter\s*passphrase\s*for\s*key\s*'.+':`: Passphrase, - `Enter\s*PIN\s*for\s*.+\s*key\s*.+:`: PIN, - `Enter\s*PIN\s*for\s*'.+':`: PIN, - `.*2FA Token.*`: Token, - } - - compiledPrompts := map[*regexp.Regexp]CredentialType{} - for pattern, askFor := range prompts { - compiledPattern := regexp.MustCompile(pattern) - compiledPrompts[compiledPattern] = askFor - } - - newlineRegex := regexp.MustCompile("\n") - - // this function takes each word of output from the command and builds up a string to see if we're being asked for a password - return func(newBytes []byte) (CredentialType, bool) { - _, err := ttyText.Write(newBytes) - if err != nil { - self.log.Error(err) - } - - for pattern, askFor := range compiledPrompts { - if match := pattern.Match([]byte(ttyText.String())); match { - ttyText.Reset() - return askFor, true - } - } - - if indices := newlineRegex.FindIndex([]byte(ttyText.String())); indices != nil { - newText := []byte(ttyText.String()[indices[1]:]) - ttyText.Reset() - ttyText.Write(newText) - } - return 0, false - } -} - -type Buffer struct { - b bytes.Buffer - m deadlock.Mutex -} - -func (b *Buffer) Read(p []byte) (n int, err error) { - b.m.Lock() - defer b.m.Unlock() - return b.b.Read(p) -} - -func (b *Buffer) Write(p []byte) (n int, err error) { - b.m.Lock() - defer b.m.Unlock() - return b.b.Write(p) -} - -func (self *cmdObjRunner) getCmdHandlerNonPty(cmd *exec.Cmd) (*cmdHandler, error) { - stdoutReader, stdoutWriter := io.Pipe() - cmd.Stdout = stdoutWriter - - buf := &Buffer{} - cmd.Stdin = buf - - if err := cmd.Start(); err != nil { - return nil, err - } - - return &cmdHandler{ - stdoutPipe: stdoutReader, - stdinPipe: buf, - close: func() error { return nil }, - }, nil -} diff --git a/pkg/commands/oscommands/cmd_obj_runner_default.go b/pkg/commands/oscommands/cmd_obj_runner_default.go deleted file mode 100644 index 72cbc26c66c..00000000000 --- a/pkg/commands/oscommands/cmd_obj_runner_default.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build !windows - -package oscommands - -import ( - "os/exec" - - "github.com/creack/pty" -) - -// we define this separately for windows and non-windows given that windows does -// not have great PTY support and we need a PTY to handle a credential request -func (self *cmdObjRunner) getCmdHandlerPty(cmd *exec.Cmd) (*cmdHandler, error) { - ptmx, err := pty.Start(cmd) - if err != nil { - return nil, err - } - - return &cmdHandler{ - stdoutPipe: ptmx, - stdinPipe: ptmx, - close: ptmx.Close, - }, nil -} diff --git a/pkg/commands/oscommands/cmd_obj_runner_test.go b/pkg/commands/oscommands/cmd_obj_runner_test.go deleted file mode 100644 index 74e3a198590..00000000000 --- a/pkg/commands/oscommands/cmd_obj_runner_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package oscommands - -import ( - "strings" - "testing" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -func getRunner() *cmdObjRunner { - log := utils.NewDummyLog() - return &cmdObjRunner{ - log: log, - guiIO: NewNullGuiIO(log), - } -} - -func toChanFn(f func(ct CredentialType) string) func(CredentialType) <-chan string { - return func(ct CredentialType) <-chan string { - ch := make(chan string) - - go func() { - ch <- f(ct) - }() - - return ch - } -} - -func TestProcessOutput(t *testing.T) { - defaultPromptUserForCredential := func(ct CredentialType) string { - switch ct { - case Password: - return "password" - case Username: - return "username" - case Passphrase: - return "passphrase" - case PIN: - return "pin" - case Token: - return "token" - default: - panic("unexpected credential type") - } - } - - scenarios := []struct { - name string - promptUserForCredential func(CredentialType) string - output string - expectedToWrite string - }{ - { - name: "no output", - promptUserForCredential: defaultPromptUserForCredential, - output: "", - expectedToWrite: "", - }, - { - name: "password prompt", - promptUserForCredential: defaultPromptUserForCredential, - output: "Password:", - expectedToWrite: "password", - }, - { - name: "password prompt 2", - promptUserForCredential: defaultPromptUserForCredential, - output: "Bill's password:", - expectedToWrite: "password", - }, - { - name: "password prompt 3", - promptUserForCredential: defaultPromptUserForCredential, - output: "Password for 'Bill':", - expectedToWrite: "password", - }, - { - name: "username prompt", - promptUserForCredential: defaultPromptUserForCredential, - output: "Username for 'Bill':", - expectedToWrite: "username", - }, - { - name: "passphrase prompt", - promptUserForCredential: defaultPromptUserForCredential, - output: "Enter passphrase for key '123':", - expectedToWrite: "passphrase", - }, - { - name: "security key pin prompt", - promptUserForCredential: defaultPromptUserForCredential, - output: "Enter PIN for key '123':", - expectedToWrite: "pin", - }, - { - name: "pkcs11 key pin prompt", - promptUserForCredential: defaultPromptUserForCredential, - output: "Enter PIN for '123':", - expectedToWrite: "pin", - }, - { - name: "2FA token prompt", - promptUserForCredential: defaultPromptUserForCredential, - output: "testuser 2FA Token (citadel)", - expectedToWrite: "token", - }, - { - name: "username and password prompt", - promptUserForCredential: defaultPromptUserForCredential, - output: "Password:\nUsername for 'Alice':\n", - expectedToWrite: "passwordusername", - }, - { - name: "user submits empty credential", - promptUserForCredential: func(ct CredentialType) string { return "" }, - output: "Password:\n", - expectedToWrite: "", - }, - } - - for _, scenario := range scenarios { - t.Run(scenario.name, func(t *testing.T) { - runner := getRunner() - reader := strings.NewReader(scenario.output) - writer := &strings.Builder{} - - cmdObj := &CmdObj{task: gocui.NewFakeTask()} - runner.processOutput(reader, writer, toChanFn(scenario.promptUserForCredential), func() error { return nil }, cmdObj) - - if writer.String() != scenario.expectedToWrite { - t.Errorf("expected to write '%s' but got '%s'", scenario.expectedToWrite, writer.String()) - } - }) - } -} diff --git a/pkg/commands/oscommands/cmd_obj_runner_windows.go b/pkg/commands/oscommands/cmd_obj_runner_windows.go deleted file mode 100644 index f92e36c69e4..00000000000 --- a/pkg/commands/oscommands/cmd_obj_runner_windows.go +++ /dev/null @@ -1,10 +0,0 @@ -package oscommands - -import ( - "os/exec" -) - -func (self *cmdObjRunner) getCmdHandlerPty(cmd *exec.Cmd) (*cmdHandler, error) { - // We don't have PTY support on Windows yet, so we just return a non-PTY handler. - return self.getCmdHandlerNonPty(cmd) -} diff --git a/pkg/commands/oscommands/cmd_obj_test.go b/pkg/commands/oscommands/cmd_obj_test.go deleted file mode 100644 index b135f1b7433..00000000000 --- a/pkg/commands/oscommands/cmd_obj_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package oscommands - -import ( - "os/exec" - "testing" - - "github.com/jesseduffield/gocui" -) - -func TestCmdObjToString(t *testing.T) { - quote := func(s string) string { - return "\"" + s + "\"" - } - - scenarios := []struct { - cmdArgs []string - expected string - }{ - { - cmdArgs: []string{"git", "push", "myfile.txt"}, - expected: "git push myfile.txt", - }, - { - cmdArgs: []string{"git", "push", "my file.txt"}, - expected: "git push \"my file.txt\"", - }, - } - - for _, scenario := range scenarios { - cmd := exec.Command(scenario.cmdArgs[0], scenario.cmdArgs[1:]...) - cmdObj := &CmdObj{cmd: cmd} - actual := cmdObj.ToString() - if actual != scenario.expected { - t.Errorf("Expected %s, got %s", quote(scenario.expected), quote(actual)) - } - } -} - -func TestClone(t *testing.T) { - task := gocui.NewFakeTask() - cmdObj := &CmdObj{task: task, cmd: &exec.Cmd{}} - clone := cmdObj.Clone() - if clone == cmdObj { - t.Errorf("Clone should not return the same object") - } - - if clone.GetTask() == nil { - t.Errorf("Clone task should not be nil") - } - - if clone.GetTask() != task { - t.Errorf("Clone should have the same task") - } -} diff --git a/pkg/commands/oscommands/copy.go b/pkg/commands/oscommands/copy.go deleted file mode 100644 index 2df96d05729..00000000000 --- a/pkg/commands/oscommands/copy.go +++ /dev/null @@ -1,142 +0,0 @@ -package oscommands - -import ( - "errors" - "io" - "os" - "path/filepath" -) - -/* MIT License - * - * Copyright (c) 2017 Roland Singer [roland.singer@desertbit.com] - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -// CopyFile copies the contents of the file named src to the file named -// by dst. The file will be created if it does not already exist. If the -// destination file exists, all it's contents will be replaced by the contents -// of the source file. The file mode will be copied from the source and -// the copied data is synced/flushed to stable storage. -func CopyFile(src, dst string) error { - in, err := os.Open(src) - if err != nil { - return err - } - defer in.Close() - - out, err := os.Create(dst) - if err != nil { - return err - } - defer func() { - if e := out.Close(); e != nil { - err = e - } - }() - - _, err = io.Copy(out, in) - if err != nil { - return err - } - - err = out.Sync() - if err != nil { - return err - } - - si, err := os.Stat(src) - if err != nil { - return err - } - err = os.Chmod(dst, si.Mode()) - if err != nil { - return err - } - - return err -} - -// CopyDir recursively copies a directory tree, attempting to preserve permissions. -// Source directory must exist. If destination already exists we'll clobber it. -// Symlinks are ignored and skipped. -func CopyDir(src string, dst string) error { - src = filepath.Clean(src) - dst = filepath.Clean(dst) - - si, err := os.Stat(src) - if err != nil { - return err - } - if !si.IsDir() { - return errors.New("source is not a directory") - } - - _, err = os.Stat(dst) - if err != nil && !os.IsNotExist(err) { - return err - } - if err == nil { - // it exists so let's remove it - if err := os.RemoveAll(dst); err != nil { - return err - } - } - - err = os.MkdirAll(dst, si.Mode()) - if err != nil { - return err - } - - entries, err := os.ReadDir(src) - if err != nil { - return err - } - - for _, entry := range entries { - srcPath := filepath.Join(src, entry.Name()) - dstPath := filepath.Join(dst, entry.Name()) - - if entry.IsDir() { - err = CopyDir(srcPath, dstPath) - if err != nil { - return err - } - } else { - var info os.FileInfo - info, err = entry.Info() - if err != nil { - return err - } - - // Skip symlinks. - if info.Mode()&os.ModeSymlink != 0 { - continue - } - - err = CopyFile(srcPath, dstPath) - if err != nil { - return err - } - } - } - - return err -} diff --git a/pkg/commands/oscommands/dummies.go b/pkg/commands/oscommands/dummies.go deleted file mode 100644 index 9490a970d14..00000000000 --- a/pkg/commands/oscommands/dummies.go +++ /dev/null @@ -1,66 +0,0 @@ -package oscommands - -import ( - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// NewDummyOSCommand creates a new dummy OSCommand for testing -func NewDummyOSCommand() *OSCommand { - osCmd := NewOSCommand(common.NewDummyCommon(), config.NewDummyAppConfig(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) - - return osCmd -} - -type OSCommandDeps struct { - Common *common.Common - Platform *Platform - GetenvFn func(string) string - RemoveFileFn func(string) error - Cmd *CmdObjBuilder - TempDir string -} - -func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand { - cmn := deps.Common - if cmn == nil { - cmn = common.NewDummyCommon() - } - - platform := deps.Platform - if platform == nil { - platform = dummyPlatform - } - - return &OSCommand{ - Common: cmn, - Platform: platform, - getenvFn: deps.GetenvFn, - removeFileFn: deps.RemoveFileFn, - guiIO: NewNullGuiIO(utils.NewDummyLog()), - tempDir: deps.TempDir, - } -} - -func NewDummyCmdObjBuilder(runner ICmdObjRunner) *CmdObjBuilder { - return &CmdObjBuilder{ - runner: runner, - platform: dummyPlatform, - } -} - -var dummyPlatform = &Platform{ - OS: "darwin", - Shell: "bash", - ShellArg: "-c", - OpenCommand: "open {{filename}}", - OpenLinkCommand: "open {{link}}", -} - -func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand { - osCommand := NewOSCommand(common.NewDummyCommon(), config.NewDummyAppConfig(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) - osCommand.Cmd = NewDummyCmdObjBuilder(runner) - - return osCommand -} diff --git a/pkg/commands/oscommands/fake_cmd_obj_runner.go b/pkg/commands/oscommands/fake_cmd_obj_runner.go deleted file mode 100644 index 4e80b288829..00000000000 --- a/pkg/commands/oscommands/fake_cmd_obj_runner.go +++ /dev/null @@ -1,157 +0,0 @@ -package oscommands - -import ( - "bufio" - "fmt" - "strings" - "sync" - "testing" - - "github.com/go-errors/errors" - "github.com/samber/lo" - "golang.org/x/exp/slices" -) - -// for use in testing - -type FakeCmdObjRunner struct { - t *testing.T - // commands can be run in any order; mimicking the concurrent behaviour of - // production code. - expectedCmds []CmdObjMatcher - - invokedCmdIndexes []int - - mutex sync.Mutex -} - -type CmdObjMatcher struct { - description string - // returns true if the matcher matches the command object - test func(*CmdObj) bool - - // output of the command - output string - // error of the command - err error -} - -var _ ICmdObjRunner = &FakeCmdObjRunner{} - -func NewFakeRunner(t *testing.T) *FakeCmdObjRunner { //nolint:thelper - return &FakeCmdObjRunner{t: t} -} - -func (self *FakeCmdObjRunner) remainingExpectedCmds() []CmdObjMatcher { - return lo.Filter(self.expectedCmds, func(_ CmdObjMatcher, i int) bool { - return !lo.Contains(self.invokedCmdIndexes, i) - }) -} - -func (self *FakeCmdObjRunner) Run(cmdObj *CmdObj) error { - _, err := self.RunWithOutput(cmdObj) - return err -} - -func (self *FakeCmdObjRunner) RunWithOutput(cmdObj *CmdObj) (string, error) { - self.mutex.Lock() - defer self.mutex.Unlock() - - if len(self.remainingExpectedCmds()) == 0 { - self.t.Errorf("ran too many commands. Unexpected command: `%s`", cmdObj.ToString()) - return "", errors.New("ran too many commands") - } - - for i := range self.expectedCmds { - if lo.Contains(self.invokedCmdIndexes, i) { - continue - } - expectedCmd := self.expectedCmds[i] - matched := expectedCmd.test(cmdObj) - if matched { - self.invokedCmdIndexes = append(self.invokedCmdIndexes, i) - return expectedCmd.output, expectedCmd.err - } - } - - self.t.Errorf("Unexpected command: `%s`", cmdObj.ToString()) - return "", nil -} - -func (self *FakeCmdObjRunner) RunWithOutputs(cmdObj *CmdObj) (string, string, error) { - output, err := self.RunWithOutput(cmdObj) - return output, "", err -} - -func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj *CmdObj, onLine func(line string) (bool, error)) error { - output, err := self.RunWithOutput(cmdObj) - if err != nil { - return err - } - - scanner := bufio.NewScanner(strings.NewReader(output)) - scanner.Split(bufio.ScanLines) - for scanner.Scan() { - line := scanner.Text() - stop, err := onLine(line) - if err != nil { - return err - } - if stop { - break - } - } - - return nil -} - -func (self *FakeCmdObjRunner) ExpectFunc(description string, fn func(cmdObj *CmdObj) bool, output string, err error) *FakeCmdObjRunner { - self.mutex.Lock() - defer self.mutex.Unlock() - - self.expectedCmds = append(self.expectedCmds, CmdObjMatcher{ - test: fn, - output: output, - err: err, - description: description, - }) - - return self -} - -func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner { - description := fmt.Sprintf("matches args %s", strings.Join(expectedArgs, " ")) - self.ExpectFunc(description, func(cmdObj *CmdObj) bool { - return slices.Equal(expectedArgs, cmdObj.GetCmd().Args) - }, output, err) - - return self -} - -func (self *FakeCmdObjRunner) ExpectGitArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner { - description := fmt.Sprintf("matches git args %s", strings.Join(expectedArgs, " ")) - self.ExpectFunc(description, func(cmdObj *CmdObj) bool { - return slices.Equal(expectedArgs, cmdObj.GetCmd().Args[1:]) - }, output, err) - - return self -} - -func (self *FakeCmdObjRunner) CheckForMissingCalls() { - self.mutex.Lock() - defer self.mutex.Unlock() - - remaining := self.remainingExpectedCmds() - if len(remaining) > 0 { - self.t.Errorf( - "expected %d more command(s) to be run. Remaining commands:\n%s", - len(remaining), - strings.Join( - lo.Map(remaining, func(cmdObj CmdObjMatcher, _ int) string { - return cmdObj.description - }), - "\n", - ), - ) - } -} diff --git a/pkg/commands/oscommands/gui_io.go b/pkg/commands/oscommands/gui_io.go deleted file mode 100644 index 6a61983109d..00000000000 --- a/pkg/commands/oscommands/gui_io.go +++ /dev/null @@ -1,55 +0,0 @@ -package oscommands - -import ( - "io" - - "github.com/sirupsen/logrus" -) - -// this struct captures some IO stuff -type guiIO struct { - // this is for logging anything we want. It'll be written to a log file for the sake - // of debugging. - log *logrus.Entry - - // this is for us to log the command we're about to run e.g. 'git push'. The GUI - // will write this to a log panel so that the user can see which commands are being - // run. - // The isCommandLineCommand arg is there so that we can style the log differently - // depending on whether we're directly outputting a command we're about to run that - // will be run on the command line, or if we're using something from Go's standard lib. - logCommandFn func(str string, isCommandLineCommand bool) - // this is for us to directly write the output of a command. We will do this for - // certain commands like 'git push'. The GUI will write this to a command output panel. - // We need a new cmd writer per command, hence it being a function. - newCmdWriterFn func() io.Writer - // this allows us to request info from the user like username/password, in the event - // that a command requests it. - // the 'credential' arg is something like 'username' or 'password' - promptForCredentialFn func(credential CredentialType) <-chan string -} - -func NewGuiIO( - log *logrus.Entry, - logCommandFn func(string, bool), - newCmdWriterFn func() io.Writer, - promptForCredentialFn func(CredentialType) <-chan string, -) *guiIO { - return &guiIO{ - log: log, - logCommandFn: logCommandFn, - newCmdWriterFn: newCmdWriterFn, - promptForCredentialFn: promptForCredentialFn, - } -} - -// we use this function when we want to access the functionality of our OS struct but we -// don't have anywhere to log things, or request input from the user. -func NewNullGuiIO(log *logrus.Entry) *guiIO { - return &guiIO{ - log: log, - logCommandFn: func(string, bool) {}, - newCmdWriterFn: func() io.Writer { return io.Discard }, - promptForCredentialFn: failPromptFn, - } -} diff --git a/pkg/commands/oscommands/os.go b/pkg/commands/oscommands/os.go deleted file mode 100644 index f070e485627..00000000000 --- a/pkg/commands/oscommands/os.go +++ /dev/null @@ -1,330 +0,0 @@ -package oscommands - -import ( - "io" - "os" - "os/exec" - "path/filepath" - "strings" - "sync" - - "github.com/go-errors/errors" - "github.com/samber/lo" - - "github.com/atotto/clipboard" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// OSCommand holds all the os commands -type OSCommand struct { - *common.Common - Platform *Platform - getenvFn func(string) string - guiIO *guiIO - - removeFileFn func(string) error - - Cmd *CmdObjBuilder - - tempDir string -} - -// Platform stores the os state -type Platform struct { - OS string - Shell string - ShellArg string - PrefixForShellFunctionsFile string - OpenCommand string - OpenLinkCommand string -} - -// NewOSCommand os command runner -func NewOSCommand(common *common.Common, config config.AppConfigurer, platform *Platform, guiIO *guiIO) *OSCommand { - c := &OSCommand{ - Common: common, - Platform: platform, - getenvFn: os.Getenv, - removeFileFn: os.RemoveAll, - guiIO: guiIO, - tempDir: config.GetTempDir(), - } - - runner := &cmdObjRunner{log: common.Log, guiIO: guiIO} - c.Cmd = &CmdObjBuilder{runner: runner, platform: platform} - - return c -} - -func (c *OSCommand) LogCommand(cmdStr string, commandLine bool) { - c.Log.WithField("command", cmdStr).Info("RunCommand") - - c.guiIO.logCommandFn(cmdStr, commandLine) -} - -// FileType tells us if the file is a file, directory or other -func FileType(path string) string { - fileInfo, err := os.Stat(path) - if err != nil { - return "other" - } - if fileInfo.IsDir() { - return "directory" - } - return "file" -} - -func (c *OSCommand) OpenFile(filename string) error { - commandTemplate := c.UserConfig().OS.Open - if commandTemplate == "" { - commandTemplate = config.GetPlatformDefaultConfig().Open - } - templateValues := map[string]string{ - "filename": c.Quote(filename), - } - command := utils.ResolvePlaceholderString(commandTemplate, templateValues) - return c.Cmd.NewShell(command, c.UserConfig().OS.ShellFunctionsFile).Run() -} - -func (c *OSCommand) OpenLink(link string) error { - commandTemplate := c.UserConfig().OS.OpenLink - if commandTemplate == "" { - commandTemplate = config.GetPlatformDefaultConfig().OpenLink - } - templateValues := map[string]string{ - "link": c.Quote(link), - } - - command := utils.ResolvePlaceholderString(commandTemplate, templateValues) - return c.Cmd.NewShell(command, c.UserConfig().OS.ShellFunctionsFile).Run() -} - -// Quote wraps a message in platform-specific quotation marks -func (c *OSCommand) Quote(message string) string { - return c.Cmd.Quote(message) -} - -// AppendLineToFile adds a new line in file -func (c *OSCommand) AppendLineToFile(filename, line string) error { - msg := utils.ResolvePlaceholderString( - c.Tr.Log.AppendingLineToFile, - map[string]string{ - "line": line, - "filename": filename, - }, - ) - c.LogCommand(msg, false) - - f, err := os.OpenFile(filename, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0o600) - if err != nil { - return utils.WrapError(err) - } - defer f.Close() - - info, err := os.Stat(filename) - if err != nil { - return utils.WrapError(err) - } - - if info.Size() > 0 { - // read last char - buf := make([]byte, 1) - if _, err := f.ReadAt(buf, info.Size()-1); err != nil { - return utils.WrapError(err) - } - - // if the last byte of the file is not a newline, add it - if []byte("\n")[0] != buf[0] { - _, err = f.WriteString("\n") - } - } - - if err == nil { - _, err = f.WriteString(line + "\n") - } - - if err != nil { - return utils.WrapError(err) - } - return nil -} - -// CreateFileWithContent creates a file with the given content -func (c *OSCommand) CreateFileWithContent(path string, content string) error { - msg := utils.ResolvePlaceholderString( - c.Tr.Log.CreateFileWithContent, - map[string]string{ - "path": path, - }, - ) - c.LogCommand(msg, false) - if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { - c.Log.Error(err) - return err - } - - if err := os.WriteFile(path, []byte(content), 0o644); err != nil { - c.Log.Error(err) - return utils.WrapError(err) - } - - return nil -} - -// Remove removes a file or directory at the specified path -func (c *OSCommand) Remove(filename string) error { - msg := utils.ResolvePlaceholderString( - c.Tr.Log.Remove, - map[string]string{ - "filename": filename, - }, - ) - c.LogCommand(msg, false) - err := os.RemoveAll(filename) - return utils.WrapError(err) -} - -// FileExists checks whether a file exists at the specified path -func (c *OSCommand) FileExists(path string) (bool, error) { - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - return false, nil - } - return false, err - } - return true, nil -} - -// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C -func (c *OSCommand) PipeCommands(cmdObjs ...*CmdObj) error { - cmds := lo.Map(cmdObjs, func(cmdObj *CmdObj, _ int) *exec.Cmd { - return cmdObj.GetCmd() - }) - - logCmdStr := strings.Join( - lo.Map(cmdObjs, func(cmdObj *CmdObj, _ int) string { - return cmdObj.ToString() - }), - " | ", - ) - - c.LogCommand(logCmdStr, true) - - for i := range len(cmds) - 1 { - stdout, err := cmds[i].StdoutPipe() - if err != nil { - return err - } - - cmds[i+1].Stdin = stdout - } - - // keeping this here in case I adapt this code for some other purpose in the future - // cmds[len(cmds)-1].Stdout = os.Stdout - - finalErrors := []string{} - - wg := sync.WaitGroup{} - wg.Add(len(cmds)) - - for _, cmd := range cmds { - go utils.Safe(func() { - stderr, err := cmd.StderrPipe() - if err != nil { - c.Log.Error(err) - } - - if err := cmd.Start(); err != nil { - c.Log.Error(err) - } - - if b, err := io.ReadAll(stderr); err == nil { - if len(b) > 0 { - finalErrors = append(finalErrors, string(b)) - } - } - - if err := cmd.Wait(); err != nil { - c.Log.Error(err) - } - - wg.Done() - }) - } - - wg.Wait() - - if len(finalErrors) > 0 { - return errors.New(strings.Join(finalErrors, "\n")) - } - return nil -} - -func (c *OSCommand) CopyToClipboard(str string) error { - escaped := strings.ReplaceAll(str, "\n", "\\n") - truncated := utils.TruncateWithEllipsis(escaped, 40) - - msg := utils.ResolvePlaceholderString( - c.Tr.Log.CopyToClipboard, - map[string]string{ - "str": truncated, - }, - ) - c.LogCommand(msg, false) - if c.UserConfig().OS.CopyToClipboardCmd != "" { - cmdStr := utils.ResolvePlaceholderString(c.UserConfig().OS.CopyToClipboardCmd, map[string]string{ - "text": c.Cmd.Quote(str), - }) - return c.Cmd.NewShell(cmdStr, c.UserConfig().OS.ShellFunctionsFile).Run() - } - - return clipboard.WriteAll(str) -} - -func (c *OSCommand) PasteFromClipboard() (string, error) { - var s string - var err error - if c.UserConfig().OS.CopyToClipboardCmd != "" { - cmdStr := c.UserConfig().OS.ReadFromClipboardCmd - s, err = c.Cmd.NewShell(cmdStr, c.UserConfig().OS.ShellFunctionsFile).RunWithOutput() - } else { - s, err = clipboard.ReadAll() - } - - if err != nil { - return "", err - } - - return strings.ReplaceAll(s, "\r\n", "\n"), nil -} - -func (c *OSCommand) RemoveFile(path string) error { - msg := utils.ResolvePlaceholderString( - c.Tr.Log.RemoveFile, - map[string]string{ - "path": path, - }, - ) - c.LogCommand(msg, false) - - return c.removeFileFn(path) -} - -func (c *OSCommand) Getenv(key string) string { - return c.getenvFn(key) -} - -func (c *OSCommand) GetTempDir() string { - return c.tempDir -} - -// GetLazygitPath returns the path of the currently executed file -func GetLazygitPath() string { - ex, err := os.Executable() // get the executable path for git to use - if err != nil { - ex = os.Args[0] // fallback to the first call argument if needed - } - return `"` + filepath.ToSlash(ex) + `"` -} diff --git a/pkg/commands/oscommands/os_default_platform.go b/pkg/commands/oscommands/os_default_platform.go deleted file mode 100644 index 06684434e7e..00000000000 --- a/pkg/commands/oscommands/os_default_platform.go +++ /dev/null @@ -1,49 +0,0 @@ -//go:build !windows - -package oscommands - -import ( - "os" - "os/exec" - "runtime" - "strings" - "syscall" -) - -func GetPlatform() *Platform { - shell := getUserShell() - - prefixForShellFunctionsFile := "" - if strings.HasSuffix(shell, "bash") { - prefixForShellFunctionsFile = "shopt -s expand_aliases\n" - } - - return &Platform{ - OS: runtime.GOOS, - Shell: shell, - ShellArg: "-c", - PrefixForShellFunctionsFile: prefixForShellFunctionsFile, - OpenCommand: "open {{filename}}", - OpenLinkCommand: "open {{link}}", - } -} - -func getUserShell() string { - if shell := os.Getenv("SHELL"); shell != "" { - return shell - } - - return "bash" -} - -func (c *OSCommand) UpdateWindowTitle() error { - return nil -} - -func TerminateProcessGracefully(cmd *exec.Cmd) error { - if cmd.Process == nil { - return nil - } - - return cmd.Process.Signal(syscall.SIGTERM) -} diff --git a/pkg/commands/oscommands/os_default_test.go b/pkg/commands/oscommands/os_default_test.go deleted file mode 100644 index ff8c59cd2ac..00000000000 --- a/pkg/commands/oscommands/os_default_test.go +++ /dev/null @@ -1,141 +0,0 @@ -//go:build !windows - -package oscommands - -import ( - "testing" - - "github.com/go-errors/errors" - "github.com/stretchr/testify/assert" -) - -func TestOSCommandRunWithOutput(t *testing.T) { - type scenario struct { - args []string - test func(string, error) - } - - scenarios := []scenario{ - { - []string{"echo", "-n", "123"}, - func(output string, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "123", output) - }, - }, - { - []string{"rmdir", "unexisting-folder"}, - func(output string, err error) { - assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error()) - }, - }, - } - - for _, s := range scenarios { - c := NewDummyOSCommand() - s.test(c.Cmd.New(s.args).RunWithOutput()) - } -} - -func TestOSCommandOpenFileDarwin(t *testing.T) { - type scenario struct { - filename string - runner *FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - filename: "test", - runner: NewFakeRunner(t). - ExpectArgs([]string{"bash", "-c", `open "test"`}, "", errors.New("error")), - test: func(err error) { - assert.Error(t, err) - }, - }, - { - filename: "test", - runner: NewFakeRunner(t). - ExpectArgs([]string{"bash", "-c", `open "test"`}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - { - filename: "filename with spaces", - runner: NewFakeRunner(t). - ExpectArgs([]string{"bash", "-c", `open "filename with spaces"`}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - oSCmd := NewDummyOSCommandWithRunner(s.runner) - oSCmd.Platform.OS = "darwin" - oSCmd.UserConfig().OS.Open = "open {{filename}}" - - s.test(oSCmd.OpenFile(s.filename)) - } -} - -// TestOSCommandOpenFileLinux tests the OpenFile command on Linux -func TestOSCommandOpenFileLinux(t *testing.T) { - type scenario struct { - filename string - runner *FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - filename: "test", - runner: NewFakeRunner(t). - ExpectArgs([]string{"bash", "-c", `xdg-open "test" > /dev/null`}, "", errors.New("error")), - test: func(err error) { - assert.Error(t, err) - }, - }, - { - filename: "test", - runner: NewFakeRunner(t). - ExpectArgs([]string{"bash", "-c", `xdg-open "test" > /dev/null`}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - { - filename: "filename with spaces", - runner: NewFakeRunner(t). - ExpectArgs([]string{"bash", "-c", `xdg-open "filename with spaces" > /dev/null`}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - { - filename: "let's_test_with_single_quote", - runner: NewFakeRunner(t). - ExpectArgs([]string{"bash", "-c", `xdg-open "let's_test_with_single_quote" > /dev/null`}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - { - filename: "$USER.txt", - runner: NewFakeRunner(t). - ExpectArgs([]string{"bash", "-c", `xdg-open "\$USER.txt" > /dev/null`}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - oSCmd := NewDummyOSCommandWithRunner(s.runner) - oSCmd.Platform.OS = "linux" - oSCmd.UserConfig().OS.Open = `xdg-open {{filename}} > /dev/null` - - s.test(oSCmd.OpenFile(s.filename)) - } -} diff --git a/pkg/commands/oscommands/os_test.go b/pkg/commands/oscommands/os_test.go deleted file mode 100644 index ecae92b18a2..00000000000 --- a/pkg/commands/oscommands/os_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package oscommands - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestOSCommandRun(t *testing.T) { - type scenario struct { - args []string - test func(error) - } - - scenarios := []scenario{ - { - []string{"rmdir", "unexisting-folder"}, - func(err error) { - assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error()) - }, - }, - } - - for _, s := range scenarios { - c := NewDummyOSCommand() - s.test(c.Cmd.New(s.args).Run()) - } -} - -func TestOSCommandQuote(t *testing.T) { - osCommand := NewDummyOSCommand() - - osCommand.Platform.OS = "linux" - - actual := osCommand.Quote("hello `test`") - - expected := "\"hello \\`test\\`\"" - - assert.EqualValues(t, expected, actual) -} - -// TestOSCommandQuoteSingleQuote tests the quote function with ' quotes explicitly for Linux -func TestOSCommandQuoteSingleQuote(t *testing.T) { - osCommand := NewDummyOSCommand() - - osCommand.Platform.OS = "linux" - - actual := osCommand.Quote("hello 'test'") - - expected := `"hello 'test'"` - - assert.EqualValues(t, expected, actual) -} - -// TestOSCommandQuoteDoubleQuote tests the quote function with " quotes explicitly for Linux -func TestOSCommandQuoteDoubleQuote(t *testing.T) { - osCommand := NewDummyOSCommand() - - osCommand.Platform.OS = "linux" - - actual := osCommand.Quote(`hello "test"`) - - expected := `"hello \"test\""` - - assert.EqualValues(t, expected, actual) -} - -// TestOSCommandQuoteWindows tests the quote function for Windows -func TestOSCommandQuoteWindows(t *testing.T) { - osCommand := NewDummyOSCommand() - - osCommand.Platform.OS = "windows" - - actual := osCommand.Quote(`hello "test" 'test2'`) - - expected := `\"hello "'"'"test"'"'" 'test2'\"` - - assert.EqualValues(t, expected, actual) -} - -func TestOSCommandFileType(t *testing.T) { - type scenario struct { - path string - setup func() - test func(string) - } - - scenarios := []scenario{ - { - "testFile", - func() { - if _, err := os.Create("testFile"); err != nil { - panic(err) - } - }, - func(output string) { - assert.EqualValues(t, "file", output) - }, - }, - { - "file with spaces", - func() { - if _, err := os.Create("file with spaces"); err != nil { - panic(err) - } - }, - func(output string) { - assert.EqualValues(t, "file", output) - }, - }, - { - "testDirectory", - func() { - if err := os.Mkdir("testDirectory", 0o644); err != nil { - panic(err) - } - }, - func(output string) { - assert.EqualValues(t, "directory", output) - }, - }, - { - "nonExistent", - func() {}, - func(output string) { - assert.EqualValues(t, "other", output) - }, - }, - } - - for _, s := range scenarios { - s.setup() - s.test(FileType(s.path)) - _ = os.RemoveAll(s.path) - } -} - -func TestOSCommandAppendLineToFile(t *testing.T) { - type scenario struct { - path string - setup func(string) - test func(string) - } - - scenarios := []scenario{ - { - filepath.Join(os.TempDir(), "testFile"), - func(path string) { - if err := os.WriteFile(path, []byte("hello"), 0o600); err != nil { - panic(err) - } - }, - func(output string) { - assert.EqualValues(t, "hello\nworld\n", output) - }, - }, - { - filepath.Join(os.TempDir(), "emptyTestFile"), - func(path string) { - if err := os.WriteFile(path, []byte(""), 0o600); err != nil { - panic(err) - } - }, - func(output string) { - assert.EqualValues(t, "world\n", output) - }, - }, - { - filepath.Join(os.TempDir(), "testFileWithNewline"), - func(path string) { - if err := os.WriteFile(path, []byte("hello\n"), 0o600); err != nil { - panic(err) - } - }, - func(output string) { - assert.EqualValues(t, "hello\nworld\n", output) - }, - }, - } - - for _, s := range scenarios { - s.setup(s.path) - osCommand := NewDummyOSCommand() - if err := osCommand.AppendLineToFile(s.path, "world"); err != nil { - panic(err) - } - f, err := os.ReadFile(s.path) - if err != nil { - panic(err) - } - s.test(string(f)) - _ = os.RemoveAll(s.path) - } -} diff --git a/pkg/commands/oscommands/os_windows.go b/pkg/commands/oscommands/os_windows.go deleted file mode 100644 index 605ed768275..00000000000 --- a/pkg/commands/oscommands/os_windows.go +++ /dev/null @@ -1,30 +0,0 @@ -package oscommands - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" -) - -func GetPlatform() *Platform { - return &Platform{ - OS: "windows", - Shell: "cmd", - ShellArg: "/c", - } -} - -func (c *OSCommand) UpdateWindowTitle() error { - path, getWdErr := os.Getwd() - if getWdErr != nil { - return getWdErr - } - argString := fmt.Sprint("title ", filepath.Base(path), " - Lazygit") - return c.Cmd.NewShell(argString, c.UserConfig().OS.ShellFunctionsFile).Run() -} - -func TerminateProcessGracefully(cmd *exec.Cmd) error { - // Signals other than SIGKILL are not supported on Windows - return nil -} diff --git a/pkg/commands/oscommands/os_windows_test.go b/pkg/commands/oscommands/os_windows_test.go deleted file mode 100644 index 60ba495bf4e..00000000000 --- a/pkg/commands/oscommands/os_windows_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package oscommands - -import ( - "testing" - - "github.com/go-errors/errors" - "github.com/stretchr/testify/assert" -) - -// handling this in a separate file because str.ToArgv has different behaviour if we're on windows - -func TestOSCommandOpenFileWindows(t *testing.T) { - type scenario struct { - filename string - runner *FakeCmdObjRunner - test func(error) - } - - scenarios := []scenario{ - { - filename: "test", - runner: NewFakeRunner(t). - ExpectArgs([]string{"cmd", "/c", "start", "", "test"}, "", errors.New("error")), - test: func(err error) { - assert.Error(t, err) - }, - }, - { - filename: "test", - runner: NewFakeRunner(t). - ExpectArgs([]string{"cmd", "/c", "start", "", "test"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - { - filename: "filename with spaces", - runner: NewFakeRunner(t). - ExpectArgs([]string{"cmd", "/c", "start", "", "filename with spaces"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - { - filename: "let's_test_with_single_quote", - runner: NewFakeRunner(t). - ExpectArgs([]string{"cmd", "/c", "start", "", "let's_test_with_single_quote"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - { - filename: "$USER.txt", - runner: NewFakeRunner(t). - ExpectArgs([]string{"cmd", "/c", "start", "", "$USER.txt"}, "", nil), - test: func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - oSCmd := NewDummyOSCommandWithRunner(s.runner) - platform := &Platform{ - OS: "windows", - Shell: "cmd", - ShellArg: "/c", - } - oSCmd.Platform = platform - oSCmd.Cmd.platform = platform - oSCmd.UserConfig().OS.Open = `start "" {{filename}}` - - s.test(oSCmd.OpenFile(s.filename)) - } -} diff --git a/pkg/commands/patch/format.go b/pkg/commands/patch/format.go deleted file mode 100644 index afd09df4b6a..00000000000 --- a/pkg/commands/patch/format.go +++ /dev/null @@ -1,146 +0,0 @@ -package patch - -import ( - "strings" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/samber/lo" -) - -type patchPresenter struct { - patch *Patch - // if true, all following fields are ignored - plain bool - - // line indices for tagged lines (e.g. lines added to a custom patch) - incLineIndices *set.Set[int] -} - -// formats the patch as a plain string -func formatPlain(patch *Patch) string { - presenter := &patchPresenter{ - patch: patch, - plain: true, - incLineIndices: set.New[int](), - } - return presenter.format() -} - -func formatRangePlain(patch *Patch, startIdx int, endIdx int) string { - lines := patch.Lines()[startIdx : endIdx+1] - return strings.Join( - lo.Map(lines, func(line *PatchLine, _ int) string { - return line.Content + "\n" - }), - "", - ) -} - -type FormatViewOpts struct { - // line indices for tagged lines (e.g. lines added to a custom patch) - IncLineIndices *set.Set[int] -} - -// formats the patch for rendering within a view, meaning it's coloured and -// highlights selected items -func formatView(patch *Patch, opts FormatViewOpts) string { - includedLineIndices := opts.IncLineIndices - if includedLineIndices == nil { - includedLineIndices = set.New[int]() - } - presenter := &patchPresenter{ - patch: patch, - plain: false, - incLineIndices: includedLineIndices, - } - return presenter.format() -} - -func (self *patchPresenter) format() string { - // if we have no changes in our patch (i.e. no additions or deletions) then - // the patch is effectively empty and we can return an empty string - if !self.patch.ContainsChanges() { - return "" - } - - stringBuilder := &strings.Builder{} - lineIdx := 0 - appendLine := func(line string) { - _, _ = stringBuilder.WriteString(line + "\n") - - lineIdx++ - } - - for _, line := range self.patch.header { - // always passing false for 'included' here because header lines are not part of the patch - appendLine(self.formatLineAux(line, theme.DefaultTextColor.SetBold(), false)) - } - - for _, hunk := range self.patch.hunks { - appendLine( - self.formatLineAux( - hunk.formatHeaderStart(), - style.FgCyan, - false, - ) + - // we're splitting the line into two parts: the diff header and the context - // We explicitly pass 'included' as false for both because these are not part - // of the actual patch - self.formatLineAux( - hunk.headerContext, - theme.DefaultTextColor, - false, - ), - ) - - for _, line := range hunk.bodyLines { - style := self.patchLineStyle(line) - if line.IsChange() { - appendLine(self.formatLine(line.Content, style, lineIdx)) - } else { - appendLine(self.formatLineAux(line.Content, style, false)) - } - } - } - - return stringBuilder.String() -} - -func (self *patchPresenter) patchLineStyle(patchLine *PatchLine) style.TextStyle { - switch patchLine.Kind { - case ADDITION: - return style.FgGreen - case DELETION: - return style.FgRed - default: - return theme.DefaultTextColor - } -} - -func (self *patchPresenter) formatLine(str string, textStyle style.TextStyle, index int) string { - included := self.incLineIndices.Includes(index) - - return self.formatLineAux(str, textStyle, included) -} - -// 'selected' means you've got it highlighted with your cursor -// 'included' means the line has been included in the patch (only applicable when -// building a patch) -func (self *patchPresenter) formatLineAux(str string, textStyle style.TextStyle, included bool) string { - if self.plain { - return str - } - - firstCharStyle := textStyle - if included { - firstCharStyle = firstCharStyle.MergeStyle(style.BgGreen) - } - - if len(str) < 2 { - return firstCharStyle.Sprint(str) - } - - return firstCharStyle.Sprint(str[:1]) + textStyle.Sprint(str[1:]) -} diff --git a/pkg/commands/patch/hunk.go b/pkg/commands/patch/hunk.go deleted file mode 100644 index 6d0177d05a3..00000000000 --- a/pkg/commands/patch/hunk.go +++ /dev/null @@ -1,67 +0,0 @@ -package patch - -import "fmt" - -// Example hunk: -// @@ -16,2 +14,3 @@ func (f *CommitFile) Description() string { -// return f.Name -// -} -// + -// +// test - -type Hunk struct { - // the line number of the first line in the old file ('16' in the above example) - oldStart int - // the line number of the first line in the new file ('14' in the above example) - newStart int - // the context at the end of the header line (' func (f *CommitFile) Description() string {' in the above example) - headerContext string - // the body of the hunk, excluding the header line - bodyLines []*PatchLine -} - -// Returns the number of lines in the hunk in the original file ('2' in the above example) -func (self *Hunk) oldLength() int { - return nLinesWithKind(self.bodyLines, []PatchLineKind{CONTEXT, DELETION}) -} - -// Returns the number of lines in the hunk in the new file ('3' in the above example) -func (self *Hunk) newLength() int { - return nLinesWithKind(self.bodyLines, []PatchLineKind{CONTEXT, ADDITION}) -} - -// Returns true if the hunk contains any changes (i.e. if it's not just a context hunk). -// We'll end up with a context hunk if we're transforming a patch and one of the hunks -// has no selected lines. -func (self *Hunk) containsChanges() bool { - return nLinesWithKind(self.bodyLines, []PatchLineKind{ADDITION, DELETION}) > 0 -} - -// Returns the number of lines in the hunk, including the header line -func (self *Hunk) lineCount() int { - return len(self.bodyLines) + 1 -} - -// Returns all lines in the hunk, including the header line -func (self *Hunk) allLines() []*PatchLine { - lines := []*PatchLine{{Content: self.formatHeaderLine(), Kind: HUNK_HEADER}} - lines = append(lines, self.bodyLines...) - return lines -} - -// Returns the header line, including the unified diff header and the context -func (self *Hunk) formatHeaderLine() string { - return fmt.Sprintf("%s%s", self.formatHeaderStart(), self.headerContext) -} - -// Returns the first part of the header line i.e. the unified diff part (excluding any context) -func (self *Hunk) formatHeaderStart() string { - newLengthDisplay := "" - newLength := self.newLength() - // if the new length is 1, it's omitted - if newLength != 1 { - newLengthDisplay = fmt.Sprintf(",%d", newLength) - } - - return fmt.Sprintf("@@ -%d,%d +%d%s @@", self.oldStart, self.oldLength(), self.newStart, newLengthDisplay) -} diff --git a/pkg/commands/patch/parse.go b/pkg/commands/patch/parse.go deleted file mode 100644 index fee7d291814..00000000000 --- a/pkg/commands/patch/parse.go +++ /dev/null @@ -1,85 +0,0 @@ -package patch - -import ( - "regexp" - "strings" - - "github.com/jesseduffield/lazygit/pkg/utils" -) - -var hunkHeaderRegexp = regexp.MustCompile(`(?m)^@@ -(\d+)[^\+]+\+(\d+)[^@]+@@(.*)$`) - -func Parse(patchStr string) *Patch { - // ignore trailing newline. - lines := strings.Split(strings.TrimSuffix(patchStr, "\n"), "\n") - - hunks := []*Hunk{} - patchHeader := []string{} - - var currentHunk *Hunk - for _, line := range lines { - if strings.HasPrefix(line, "@@") { - oldStart, newStart, headerContext := headerInfo(line) - - currentHunk = &Hunk{ - oldStart: oldStart, - newStart: newStart, - headerContext: headerContext, - bodyLines: []*PatchLine{}, - } - hunks = append(hunks, currentHunk) - } else if currentHunk != nil { - currentHunk.bodyLines = append(currentHunk.bodyLines, newHunkLine(line)) - } else { - patchHeader = append(patchHeader, line) - } - } - - return &Patch{ - hunks: hunks, - header: patchHeader, - } -} - -func headerInfo(header string) (int, int, string) { - match := hunkHeaderRegexp.FindStringSubmatch(header) - - oldStart := utils.MustConvertToInt(match[1]) - newStart := utils.MustConvertToInt(match[2]) - headerContext := match[3] - - return oldStart, newStart, headerContext -} - -func newHunkLine(line string) *PatchLine { - if line == "" { - return &PatchLine{ - Kind: CONTEXT, - Content: "", - } - } - - firstChar := line[:1] - - kind := parseFirstChar(firstChar) - - return &PatchLine{ - Kind: kind, - Content: line, - } -} - -func parseFirstChar(firstChar string) PatchLineKind { - switch firstChar { - case " ": - return CONTEXT - case "+": - return ADDITION - case "-": - return DELETION - case "\\": - return NEWLINE_MESSAGE - } - - return CONTEXT -} diff --git a/pkg/commands/patch/patch.go b/pkg/commands/patch/patch.go deleted file mode 100644 index 38d432ae6bc..00000000000 --- a/pkg/commands/patch/patch.go +++ /dev/null @@ -1,202 +0,0 @@ -package patch - -import ( - "github.com/samber/lo" -) - -type Patch struct { - // header of the patch (split on newlines) e.g. - // diff --git a/filename b/filename - // index dcd3485..1ba5540 100644 - // --- a/filename - // +++ b/filename - header []string - // hunks of the patch - hunks []*Hunk -} - -// Returns a new patch with the specified transformation applied (e.g. -// only selecting a subset of changes). -// Leaves the original patch unchanged. -func (self *Patch) Transform(opts TransformOpts) *Patch { - return transform(self, opts) -} - -// Returns the patch as a plain string -func (self *Patch) FormatPlain() string { - return formatPlain(self) -} - -// Returns a range of lines from the patch as a plain string (range is inclusive) -func (self *Patch) FormatRangePlain(startIdx int, endIdx int) string { - return formatRangePlain(self, startIdx, endIdx) -} - -// Returns the patch as a string with ANSI color codes for displaying in a view -func (self *Patch) FormatView(opts FormatViewOpts) string { - return formatView(self, opts) -} - -// Returns the lines of the patch -func (self *Patch) Lines() []*PatchLine { - lines := []*PatchLine{} - for _, line := range self.header { - lines = append(lines, &PatchLine{Content: line, Kind: PATCH_HEADER}) - } - - for _, hunk := range self.hunks { - lines = append(lines, hunk.allLines()...) - } - - return lines -} - -// Returns the patch line index of the first line in the given hunk -func (self *Patch) HunkStartIdx(hunkIndex int) int { - hunkIndex = lo.Clamp(hunkIndex, 0, len(self.hunks)-1) - - result := len(self.header) - for i := range hunkIndex { - result += self.hunks[i].lineCount() - } - return result -} - -// Returns the patch line index of the last line in the given hunk -func (self *Patch) HunkEndIdx(hunkIndex int) int { - hunkIndex = lo.Clamp(hunkIndex, 0, len(self.hunks)-1) - - return self.HunkStartIdx(hunkIndex) + self.hunks[hunkIndex].lineCount() - 1 -} - -func (self *Patch) ContainsChanges() bool { - return lo.SomeBy(self.hunks, func(hunk *Hunk) bool { - return hunk.containsChanges() - }) -} - -// Takes a line index in the patch and returns the line number in the new file. -// If the line is a header line, returns 1. -// If the line is a hunk header line, returns the first file line number in that hunk. -// If the line is out of range below, returns the last file line number in the last hunk. -func (self *Patch) LineNumberOfLine(idx int) int { - if idx < len(self.header) || len(self.hunks) == 0 { - return 1 - } - - hunkIdx := self.HunkContainingLine(idx) - // cursor out of range, return last file line number - if hunkIdx == -1 { - lastHunk := self.hunks[len(self.hunks)-1] - return lastHunk.newStart + lastHunk.newLength() - 1 - } - - hunk := self.hunks[hunkIdx] - hunkStartIdx := self.HunkStartIdx(hunkIdx) - idxInHunk := idx - hunkStartIdx - - if idxInHunk == 0 { - return hunk.newStart - } - - lines := hunk.bodyLines[:idxInHunk-1] - offset := nLinesWithKind(lines, []PatchLineKind{ADDITION, CONTEXT}) - return hunk.newStart + offset -} - -// Returns hunk index containing the line at the given patch line index -func (self *Patch) HunkContainingLine(idx int) int { - for hunkIdx, hunk := range self.hunks { - hunkStartIdx := self.HunkStartIdx(hunkIdx) - if idx >= hunkStartIdx && idx < hunkStartIdx+hunk.lineCount() { - return hunkIdx - } - } - return -1 -} - -// Returns the patch line index of the next change (i.e. addition or deletion) -// that matches the same "included" state, given the includedLines. If you don't -// care about included states, pass nil for includedLines and false for included. -func (self *Patch) GetNextChangeIdxOfSameIncludedState(idx int, includedLines []int, included bool) (int, bool) { - idx = lo.Clamp(idx, 0, self.LineCount()-1) - - lines := self.Lines() - - isMatch := func(i int, line *PatchLine) bool { - sameIncludedState := lo.Contains(includedLines, i) == included - return line.IsChange() && sameIncludedState - } - - for i, line := range lines[idx:] { - if isMatch(i+idx, line) { - return i + idx, true - } - } - - // there are no changes from the cursor onwards so we'll instead - // return the index of the last change - for i := len(lines) - 1; i >= 0; i-- { - line := lines[i] - if isMatch(i, line) { - return i, true - } - } - - return 0, false -} - -// Returns the patch line index of the next change (i.e. addition or deletion). -func (self *Patch) GetNextChangeIdx(idx int) int { - result, _ := self.GetNextChangeIdxOfSameIncludedState(idx, nil, false) - return result -} - -// Returns the length of the patch in lines -func (self *Patch) LineCount() int { - count := len(self.header) - for _, hunk := range self.hunks { - count += hunk.lineCount() - } - return count -} - -// Returns the number of hunks of the patch -func (self *Patch) HunkCount() int { - return len(self.hunks) -} - -// Adjust the given line number (one-based) according to the current patch. The -// patch is supposed to be a diff of an old file state against the working -// directory; the line number is a line number in that old file, and the -// function returns the corresponding line number in the working directory file. -func (self *Patch) AdjustLineNumber(lineNumber int) int { - adjustedLineNumber := lineNumber - for _, hunk := range self.hunks { - if hunk.oldStart >= lineNumber { - break - } - - if hunk.oldStart+hunk.oldLength() > lineNumber { - return hunk.newStart - } - - adjustedLineNumber += hunk.newLength() - hunk.oldLength() - } - - return adjustedLineNumber -} - -func (self *Patch) IsSingleHunkForWholeFile() bool { - if len(self.hunks) != 1 { - return false - } - - // We consider a patch to be a single hunk for the whole file if it has only additions or - // deletions but not both, and no context lines. This not quite correct, because it will also - // return true for a block of added or deleted lines if the diff context size is 0, but in this - // case you wouldn't be able to stage things anyway, so it doesn't matter. - bodyLines := self.hunks[0].bodyLines - return nLinesWithKind(bodyLines, []PatchLineKind{DELETION, CONTEXT}) == 0 || - nLinesWithKind(bodyLines, []PatchLineKind{ADDITION, CONTEXT}) == 0 -} diff --git a/pkg/commands/patch/patch_builder.go b/pkg/commands/patch/patch_builder.go deleted file mode 100644 index 466b3da097f..00000000000 --- a/pkg/commands/patch/patch_builder.go +++ /dev/null @@ -1,282 +0,0 @@ -package patch - -import ( - "sort" - "strings" - - "github.com/jesseduffield/generics/maps" - "github.com/samber/lo" - "github.com/sirupsen/logrus" -) - -type PatchStatus int - -const ( - // UNSELECTED is for when the commit file has not been added to the patch in any way - UNSELECTED PatchStatus = iota - // WHOLE is for when you want to add the whole diff of a file to the patch, - // including e.g. if it was deleted - WHOLE - // PART is for when you're only talking about specific lines that have been modified - PART -) - -type fileInfo struct { - mode PatchStatus - includedLineIndices []int - diff string -} - -type ( - loadFileDiffFunc func(from string, to string, reverse bool, filename string, plain bool) (string, error) -) - -// PatchBuilder manages the building of a patch for a commit to be applied to another commit (or the working tree, or removed from the current commit). We also support building patches from things like stashes, for which there is less flexibility -type PatchBuilder struct { - // To is the commit hash if we're dealing with files of a commit, or a stash ref for a stash - To string - From string - reverse bool - - // CanRebase tells us whether we're allowed to modify our commits. CanRebase should be true for commits of the currently checked out branch and false for everything else - // TODO: move this out into a proper mode struct in the gui package: it doesn't really belong here - CanRebase bool - - // fileInfoMap starts empty but you add files to it as you go along - fileInfoMap map[string]*fileInfo - Log *logrus.Entry - - // loadFileDiff loads the diff of a file, for a given to (typically a commit hash) - loadFileDiff loadFileDiffFunc -} - -func NewPatchBuilder(log *logrus.Entry, loadFileDiff loadFileDiffFunc) *PatchBuilder { - return &PatchBuilder{ - Log: log, - loadFileDiff: loadFileDiff, - } -} - -func (p *PatchBuilder) Start(from, to string, reverse bool, canRebase bool) { - p.To = to - p.From = from - p.reverse = reverse - p.CanRebase = canRebase - p.fileInfoMap = map[string]*fileInfo{} -} - -func (p *PatchBuilder) PatchToApply(reverse bool, turnAddedFilesIntoDiffAgainstEmptyFile bool) string { - patch := "" - - for filename, info := range p.fileInfoMap { - if info.mode == UNSELECTED { - continue - } - - patch += p.RenderPatchForFile(RenderPatchForFileOpts{ - Filename: filename, - Plain: true, - Reverse: reverse, - TurnAddedFilesIntoDiffAgainstEmptyFile: turnAddedFilesIntoDiffAgainstEmptyFile, - }) - } - - return patch -} - -func (p *PatchBuilder) addFileWhole(info *fileInfo) { - if info.mode != WHOLE { - info.mode = WHOLE - lineCount := len(strings.Split(info.diff, "\n")) - // add every line index - // TODO: add tests and then use lo.Range to simplify - info.includedLineIndices = make([]int, lineCount) - for i := range lineCount { - info.includedLineIndices[i] = i - } - } -} - -func (p *PatchBuilder) removeFile(info *fileInfo) { - info.mode = UNSELECTED - info.includedLineIndices = nil -} - -func (p *PatchBuilder) AddFileWhole(filename string) error { - info, err := p.getFileInfo(filename) - if err != nil { - return err - } - - p.addFileWhole(info) - - return nil -} - -func (p *PatchBuilder) RemoveFile(filename string) error { - info, err := p.getFileInfo(filename) - if err != nil { - return err - } - - p.removeFile(info) - - return nil -} - -func (p *PatchBuilder) getFileInfo(filename string) (*fileInfo, error) { - info, ok := p.fileInfoMap[filename] - if ok { - return info, nil - } - - diff, err := p.loadFileDiff(p.From, p.To, p.reverse, filename, true) - if err != nil { - return nil, err - } - info = &fileInfo{ - mode: UNSELECTED, - diff: diff, - } - - p.fileInfoMap[filename] = info - - return info, nil -} - -func (p *PatchBuilder) AddFileLineRange(filename string, lineIndices []int) error { - info, err := p.getFileInfo(filename) - if err != nil { - return err - } - info.mode = PART - info.includedLineIndices = lo.Union(info.includedLineIndices, lineIndices) - - return nil -} - -func (p *PatchBuilder) RemoveFileLineRange(filename string, lineIndices []int) error { - info, err := p.getFileInfo(filename) - if err != nil { - return err - } - info.mode = PART - info.includedLineIndices, _ = lo.Difference(info.includedLineIndices, lineIndices) - if len(info.includedLineIndices) == 0 { - p.removeFile(info) - } - - return nil -} - -type RenderPatchForFileOpts struct { - Filename string - Plain bool - Reverse bool - TurnAddedFilesIntoDiffAgainstEmptyFile bool -} - -func (p *PatchBuilder) RenderPatchForFile(opts RenderPatchForFileOpts) string { - info, err := p.getFileInfo(opts.Filename) - if err != nil { - p.Log.Error(err) - return "" - } - - if info.mode == UNSELECTED { - return "" - } - - if info.mode == WHOLE && opts.Plain { - // Use the whole diff (spares us parsing it and then formatting it). - // TODO: see if this is actually noticeably faster. - // The reverse flag is only for part patches so we're ignoring it here. - return info.diff - } - - patch := Parse(info.diff). - Transform(TransformOpts{ - Reverse: opts.Reverse, - TurnAddedFilesIntoDiffAgainstEmptyFile: opts.TurnAddedFilesIntoDiffAgainstEmptyFile, - IncludedLineIndices: info.includedLineIndices, - }) - - if opts.Plain { - return patch.FormatPlain() - } - return patch.FormatView(FormatViewOpts{}) -} - -func (p *PatchBuilder) renderEachFilePatch(plain bool) []string { - // sort files by name then iterate through and render each patch - filenames := maps.Keys(p.fileInfoMap) - - sort.Strings(filenames) - patches := lo.Map(filenames, func(filename string, _ int) string { - return p.RenderPatchForFile(RenderPatchForFileOpts{ - Filename: filename, - Plain: plain, - Reverse: false, - TurnAddedFilesIntoDiffAgainstEmptyFile: true, - }) - }) - output := lo.Filter(patches, func(patch string, _ int) bool { - return patch != "" - }) - - return output -} - -func (p *PatchBuilder) RenderAggregatedPatch(plain bool) string { - return strings.Join(p.renderEachFilePatch(plain), "") -} - -func (p *PatchBuilder) GetFileStatus(filename string, parent string) PatchStatus { - if parent != p.To { - return UNSELECTED - } - - info, ok := p.fileInfoMap[filename] - if !ok { - return UNSELECTED - } - - return info.mode -} - -func (p *PatchBuilder) GetFileIncLineIndices(filename string) ([]int, error) { - info, err := p.getFileInfo(filename) - if err != nil { - return nil, err - } - return info.includedLineIndices, nil -} - -// clears the patch -func (p *PatchBuilder) Reset() { - p.To = "" - p.fileInfoMap = map[string]*fileInfo{} -} - -func (p *PatchBuilder) Active() bool { - return p.To != "" -} - -func (p *PatchBuilder) IsEmpty() bool { - for _, fileInfo := range p.fileInfoMap { - if fileInfo.mode == WHOLE || (fileInfo.mode == PART && len(fileInfo.includedLineIndices) > 0) { - return false - } - } - - return true -} - -// if any of these things change we'll need to reset and start a new patch -func (p *PatchBuilder) NewPatchRequired(from string, to string, reverse bool) bool { - return from != p.From || to != p.To || reverse != p.reverse -} - -func (p *PatchBuilder) AllFilesInPatch() []string { - return lo.Keys(p.fileInfoMap) -} diff --git a/pkg/commands/patch/patch_line.go b/pkg/commands/patch/patch_line.go deleted file mode 100644 index 78eef3a19d0..00000000000 --- a/pkg/commands/patch/patch_line.go +++ /dev/null @@ -1,30 +0,0 @@ -package patch - -import "github.com/samber/lo" - -type PatchLineKind int - -const ( - PATCH_HEADER PatchLineKind = iota - HUNK_HEADER - ADDITION - DELETION - CONTEXT - NEWLINE_MESSAGE -) - -type PatchLine struct { - Kind PatchLineKind - Content string // something like '+ hello' (note the first character is not removed) -} - -func (self *PatchLine) IsChange() bool { - return self.Kind == ADDITION || self.Kind == DELETION -} - -// Returns the number of lines in the given slice that have one of the given kinds -func nLinesWithKind(lines []*PatchLine, kinds []PatchLineKind) int { - return lo.CountBy(lines, func(line *PatchLine) bool { - return lo.Contains(kinds, line.Kind) - }) -} diff --git a/pkg/commands/patch/patch_test.go b/pkg/commands/patch/patch_test.go deleted file mode 100644 index f91fc406eeb..00000000000 --- a/pkg/commands/patch/patch_test.go +++ /dev/null @@ -1,773 +0,0 @@ -package patch - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -const simpleDiff = `diff --git a/filename b/filename -index dcd3485..1ba5540 100644 ---- a/filename -+++ b/filename -@@ -1,5 +1,5 @@ - apple --orange -+grape - ... - ... - ... -` - -const addNewlineToEndOfFile = `diff --git a/filename b/filename -index 80a73f1..e48a11c 100644 ---- a/filename -+++ b/filename -@@ -60,4 +60,4 @@ grape - ... - ... - ... --last line -\ No newline at end of file -+last line -` - -const removeNewlinefromEndOfFile = `diff --git a/filename b/filename -index e48a11c..80a73f1 100644 ---- a/filename -+++ b/filename -@@ -60,4 +60,4 @@ grape - ... - ... - ... --last line -+last line -\ No newline at end of file -` - -const twoHunks = `diff --git a/filename b/filename -index e48a11c..b2ab81b 100644 ---- a/filename -+++ b/filename -@@ -1,5 +1,5 @@ - apple --grape -+orange - ... - ... - ... -@@ -8,6 +8,8 @@ grape - ... - ... - ... -+pear -+lemon - ... - ... - ... -` - -const twoHunksWithMoreAdditionsThanRemovals = `diff --git a/filename b/filename -index bac359d75..6e5b89f36 100644 ---- a/filename -+++ b/filename -@@ -1,5 +1,6 @@ - apple --grape -+orange -+kiwi - ... - ... - ... -@@ -8,6 +9,8 @@ grape - ... - ... - ... -+pear -+lemon - ... - ... - ... -` - -const twoChangesInOneHunk = `diff --git a/filename b/filename -index 9320895..6d79956 100644 ---- a/filename -+++ b/filename -@@ -1,5 +1,5 @@ - apple --grape -+kiwi - orange --pear -+banana - lemon -` - -const newFile = `diff --git a/newfile b/newfile -new file mode 100644 -index 0000000..4e680cc ---- /dev/null -+++ b/newfile -@@ -0,0 +1,3 @@ -+apple -+orange -+grape -` - -const deletedFile = `diff --git a/newfile b/newfile -deleted file mode 100644 -index 4e680cc1f..000000000 ---- a/newfile -+++ /dev/null -@@ -1,3 +0,0 @@ --apple --orange --grape -` - -const addNewlineToPreviouslyEmptyFile = `diff --git a/newfile b/newfile -index e69de29..c6568ea 100644 ---- a/newfile -+++ b/newfile -@@ -0,0 +1 @@ -+new line -\ No newline at end of file -` - -const exampleHunk = `@@ -1,5 +1,5 @@ - apple --grape -+orange -... -... -... -` - -func TestTransform(t *testing.T) { - type scenario struct { - testName string - filename string - diffText string - firstLineIndex int - lastLineIndex int - reverse bool - expected string - } - - scenarios := []scenario{ - { - testName: "nothing selected", - filename: "filename", - firstLineIndex: -1, - lastLineIndex: -1, - diffText: simpleDiff, - expected: "", - }, - { - testName: "only context selected", - filename: "filename", - firstLineIndex: 5, - lastLineIndex: 5, - diffText: simpleDiff, - expected: "", - }, - { - testName: "whole range selected", - filename: "filename", - firstLineIndex: 0, - lastLineIndex: 11, - diffText: simpleDiff, - expected: `--- a/filename -+++ b/filename -@@ -1,5 +1,5 @@ - apple --orange -+grape - ... - ... - ... -`, - }, - { - testName: "only removal selected", - filename: "filename", - firstLineIndex: 6, - lastLineIndex: 6, - diffText: simpleDiff, - expected: `--- a/filename -+++ b/filename -@@ -1,5 +1,4 @@ - apple --orange - ... - ... - ... -`, - }, - { - testName: "only addition selected", - filename: "filename", - firstLineIndex: 7, - lastLineIndex: 7, - diffText: simpleDiff, - expected: `--- a/filename -+++ b/filename -@@ -1,5 +1,6 @@ - apple - orange -+grape - ... - ... - ... -`, - }, - { - testName: "range that extends beyond diff bounds", - filename: "filename", - firstLineIndex: -100, - lastLineIndex: 100, - diffText: simpleDiff, - expected: `--- a/filename -+++ b/filename -@@ -1,5 +1,5 @@ - apple --orange -+grape - ... - ... - ... -`, - }, - { - testName: "add newline to end of file", - filename: "filename", - firstLineIndex: -100, - lastLineIndex: 100, - diffText: addNewlineToEndOfFile, - expected: `--- a/filename -+++ b/filename -@@ -60,4 +60,4 @@ grape - ... - ... - ... --last line -\ No newline at end of file -+last line -`, - }, - { - testName: "add newline to end of file, reversed", - filename: "filename", - firstLineIndex: -100, - lastLineIndex: 100, - reverse: true, - diffText: addNewlineToEndOfFile, - expected: `--- a/filename -+++ b/filename -@@ -60,4 +60,4 @@ grape - ... - ... - ... --last line -\ No newline at end of file -+last line -`, - }, - { - testName: "remove newline from end of file", - filename: "filename", - firstLineIndex: -100, - lastLineIndex: 100, - diffText: removeNewlinefromEndOfFile, - expected: `--- a/filename -+++ b/filename -@@ -60,4 +60,4 @@ grape - ... - ... - ... --last line -+last line -\ No newline at end of file -`, - }, - { - testName: "remove newline from end of file, reversed", - filename: "filename", - firstLineIndex: -100, - lastLineIndex: 100, - reverse: true, - diffText: removeNewlinefromEndOfFile, - expected: `--- a/filename -+++ b/filename -@@ -60,4 +60,4 @@ grape - ... - ... - ... --last line -+last line -\ No newline at end of file -`, - }, - { - testName: "remove newline from end of file, removal only", - filename: "filename", - firstLineIndex: 8, - lastLineIndex: 8, - diffText: removeNewlinefromEndOfFile, - expected: `--- a/filename -+++ b/filename -@@ -60,4 +60,3 @@ grape - ... - ... - ... --last line -`, - }, - { - testName: "remove newline from end of file, removal only, reversed", - filename: "filename", - firstLineIndex: 8, - lastLineIndex: 8, - reverse: true, - diffText: removeNewlinefromEndOfFile, - expected: `--- a/filename -+++ b/filename -@@ -60,5 +60,4 @@ grape - ... - ... - ... --last line - last line -\ No newline at end of file -`, - }, - { - testName: "remove newline from end of file, addition only", - filename: "filename", - firstLineIndex: 9, - lastLineIndex: 9, - diffText: removeNewlinefromEndOfFile, - expected: `--- a/filename -+++ b/filename -@@ -60,4 +60,5 @@ grape - ... - ... - ... - last line -+last line -\ No newline at end of file -`, - }, - { - testName: "remove newline from end of file, addition only, reversed", - filename: "filename", - firstLineIndex: 9, - lastLineIndex: 9, - reverse: true, - diffText: removeNewlinefromEndOfFile, - expected: `--- a/filename -+++ b/filename -@@ -60,3 +60,4 @@ grape - ... - ... - ... -+last line -\ No newline at end of file -`, - }, - { - testName: "staging two whole hunks", - filename: "filename", - firstLineIndex: -100, - lastLineIndex: 100, - diffText: twoHunks, - expected: `--- a/filename -+++ b/filename -@@ -1,5 +1,5 @@ - apple --grape -+orange - ... - ... - ... -@@ -8,6 +8,8 @@ grape - ... - ... - ... -+pear -+lemon - ... - ... - ... -`, - }, - { - testName: "staging part of both hunks", - filename: "filename", - firstLineIndex: 7, - lastLineIndex: 15, - diffText: twoHunks, - expected: `--- a/filename -+++ b/filename -@@ -1,5 +1,6 @@ - apple - grape -+orange - ... - ... - ... -@@ -8,6 +9,7 @@ grape - ... - ... - ... -+pear - ... - ... - ... -`, - }, - { - testName: "adding a new file", - filename: "newfile", - firstLineIndex: -100, - lastLineIndex: 100, - diffText: newFile, - expected: `--- a/newfile -+++ b/newfile -@@ -0,0 +1,3 @@ -+apple -+orange -+grape -`, - }, - { - testName: "adding part of a new file", - filename: "newfile", - firstLineIndex: 6, - lastLineIndex: 7, - diffText: newFile, - expected: `--- a/newfile -+++ b/newfile -@@ -0,0 +1,2 @@ -+apple -+orange -`, - }, - { - testName: "adding a new line to a previously empty file", - filename: "newfile", - firstLineIndex: -100, - lastLineIndex: 100, - diffText: addNewlineToPreviouslyEmptyFile, - expected: `--- a/newfile -+++ b/newfile -@@ -0,0 +1 @@ -+new line -\ No newline at end of file -`, - }, - { - testName: "adding a new line to a previously empty file, reversed", - filename: "newfile", - firstLineIndex: -100, - lastLineIndex: 100, - diffText: addNewlineToPreviouslyEmptyFile, - reverse: true, - expected: `--- a/newfile -+++ b/newfile -@@ -0,0 +1 @@ -+new line -\ No newline at end of file -`, - }, - { - testName: "adding part of a hunk", - filename: "filename", - firstLineIndex: 6, - lastLineIndex: 7, - reverse: false, - diffText: twoChangesInOneHunk, - expected: `--- a/filename -+++ b/filename -@@ -1,5 +1,5 @@ - apple --grape -+kiwi - orange - pear - lemon -`, - }, - { - testName: "adding part of a hunk, reverse", - filename: "filename", - firstLineIndex: 6, - lastLineIndex: 7, - reverse: true, - diffText: twoChangesInOneHunk, - expected: `--- a/filename -+++ b/filename -@@ -1,5 +1,5 @@ - apple --grape -+kiwi - orange - banana - lemon -`, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - lineIndices := ExpandRange(s.firstLineIndex, s.lastLineIndex) - - result := Parse(s.diffText). - Transform(TransformOpts{ - Reverse: s.reverse, - FileNameOverride: s.filename, - IncludedLineIndices: lineIndices, - }). - FormatPlain() - - assert.Equal(t, s.expected, result) - }) - } -} - -func TestParseAndFormatPlain(t *testing.T) { - scenarios := []struct { - testName string - patchStr string - }{ - { - testName: "simpleDiff", - patchStr: simpleDiff, - }, - { - testName: "addNewlineToEndOfFile", - patchStr: addNewlineToEndOfFile, - }, - { - testName: "removeNewlinefromEndOfFile", - patchStr: removeNewlinefromEndOfFile, - }, - { - testName: "twoHunks", - patchStr: twoHunks, - }, - { - testName: "twoChangesInOneHunk", - patchStr: twoChangesInOneHunk, - }, - { - testName: "newFile", - patchStr: newFile, - }, - { - testName: "deletedFile", - patchStr: deletedFile, - }, - { - testName: "addNewlineToPreviouslyEmptyFile", - patchStr: addNewlineToPreviouslyEmptyFile, - }, - { - testName: "exampleHunk", - patchStr: exampleHunk, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - // here we parse the patch, then format it, and ensure the result - // matches the original patch. Note that unified diffs allow omitting - // the new length in a hunk header if the value is 1, and currently we always - // omit the new length in such cases. - patch := Parse(s.patchStr) - result := formatPlain(patch) - assert.Equal(t, s.patchStr, result) - }) - } -} - -func TestLineNumberOfLine(t *testing.T) { - type scenario struct { - testName string - patchStr string - indexes []int - expecteds []int - } - - scenarios := []scenario{ - { - testName: "twoHunks", - patchStr: twoHunks, - // this is really more of a characteristic test than anything. - indexes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 1000}, - expecteds: []int{1, 1, 1, 1, 1, 1, 2, 2, 3, 4, 5, 8, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15}, - }, - { - testName: "twoHunksWithMoreAdditionsThanRemovals", - patchStr: twoHunksWithMoreAdditionsThanRemovals, - indexes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 1000}, - expecteds: []int{1, 1, 1, 1, 1, 1, 2, 2, 3, 4, 5, 6, 9, 9, 10, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - for i, idx := range s.indexes { - patch := Parse(s.patchStr) - result := patch.LineNumberOfLine(idx) - assert.Equal(t, s.expecteds[i], result) - } - }) - } -} - -func TestGetNextStageableLineIndex(t *testing.T) { - type scenario struct { - testName string - patchStr string - indexes []int - expecteds []int - } - - scenarios := []scenario{ - { - testName: "twoHunks", - patchStr: twoHunks, - indexes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 1000}, - expecteds: []int{6, 6, 6, 6, 6, 6, 6, 7, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - for i, idx := range s.indexes { - patch := Parse(s.patchStr) - result := patch.GetNextChangeIdx(idx) - assert.Equal(t, s.expecteds[i], result) - } - }) - } -} - -func TestAdjustLineNumber(t *testing.T) { - type scenario struct { - oldLineNumbers []int - expectedResults []int - } - scenarios := []scenario{ - { - oldLineNumbers: []int{1, 2, 3, 4, 5, 6, 7}, - expectedResults: []int{1, 2, 2, 3, 4, 7, 8}, - }, - } - - // The following diff was generated from old.txt: - // 1 - // 2a - // 2b - // 3 - // 4 - // 7 - // 8 - // against new.txt: - // 1 - // 2 - // 3 - // 4 - // 5 - // 6 - // 7 - // 8 - - // This test setup makes the test easy to understand, because the resulting - // adjusted line numbers are the same as the content of the lines in new.txt. - - diff := `--- old.txt 2024-12-16 18:04:29 -+++ new.txt 2024-12-16 18:04:27 -@@ -2,2 +2 @@ --2a --2b -+2 -@@ -5,0 +5,2 @@ -+5 -+6 -` - - patch := Parse(diff) - - for _, s := range scenarios { - t.Run("TestAdjustLineNumber", func(t *testing.T) { - for idx, oldLineNumber := range s.oldLineNumbers { - result := patch.AdjustLineNumber(oldLineNumber) - assert.Equal(t, s.expectedResults[idx], result) - } - }) - } -} - -func TestIsSingleHunkForWholeFile(t *testing.T) { - scenarios := []struct { - testName string - patchStr string - expectedResult bool - }{ - { - testName: "simpleDiff", - patchStr: simpleDiff, - expectedResult: false, - }, - { - testName: "addNewlineToEndOfFile", - patchStr: addNewlineToEndOfFile, - expectedResult: false, - }, - { - testName: "removeNewlinefromEndOfFile", - patchStr: removeNewlinefromEndOfFile, - expectedResult: false, - }, - { - testName: "twoHunks", - patchStr: twoHunks, - expectedResult: false, - }, - { - testName: "twoChangesInOneHunk", - patchStr: twoChangesInOneHunk, - expectedResult: false, - }, - { - testName: "newFile", - patchStr: newFile, - expectedResult: true, - }, - { - testName: "deletedFile", - patchStr: deletedFile, - expectedResult: true, - }, - { - testName: "addNewlineToPreviouslyEmptyFile", - patchStr: addNewlineToPreviouslyEmptyFile, - expectedResult: true, - }, - { - testName: "exampleHunk", - patchStr: exampleHunk, - expectedResult: false, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - patch := Parse(s.patchStr) - assert.Equal(t, s.expectedResult, patch.IsSingleHunkForWholeFile()) - }) - } -} diff --git a/pkg/commands/patch/transform.go b/pkg/commands/patch/transform.go deleted file mode 100644 index 4cd4c0207f3..00000000000 --- a/pkg/commands/patch/transform.go +++ /dev/null @@ -1,179 +0,0 @@ -package patch - -import ( - "strings" - - "github.com/samber/lo" -) - -type patchTransformer struct { - patch *Patch - opts TransformOpts -} - -type TransformOpts struct { - // Create a patch that will applied in reverse with `git apply --reverse`. - // This affects how unselected lines are treated when only parts of a hunk - // are selected: usually, for unselected lines we change '-' lines to - // context lines and remove '+' lines, but when Reverse is true we need to - // turn '+' lines into context lines and remove '-' lines. - Reverse bool - - // If set, we will replace the original header with one referring to this file name. - // For staging/unstaging lines we don't want the original header because - // it makes git confused e.g. when dealing with deleted/added files - // but with building and applying patches the original header gives git - // information it needs to cleanly apply patches - FileNameOverride string - - // Custom patches tend to work better when treating new files as diffs - // against an empty file. The only case where we need this to be false is - // when moving a custom patch to an earlier commit; in that case the patch - // command would fail with the error "file does not exist in index" if we - // treat it as a diff against an empty file. - TurnAddedFilesIntoDiffAgainstEmptyFile bool - - // The indices of lines that should be included in the patch. - IncludedLineIndices []int -} - -func transform(patch *Patch, opts TransformOpts) *Patch { - transformer := &patchTransformer{ - patch: patch, - opts: opts, - } - - return transformer.transform() -} - -// helper function that takes a start and end index and returns a slice of all -// indexes inbetween (inclusive) -func ExpandRange(start int, end int) []int { - expanded := []int{} - for i := start; i <= end; i++ { - expanded = append(expanded, i) - } - return expanded -} - -func (self *patchTransformer) transform() *Patch { - header := self.transformHeader() - hunks := self.transformHunks() - - return &Patch{ - header: header, - hunks: hunks, - } -} - -func (self *patchTransformer) transformHeader() []string { - if self.opts.FileNameOverride != "" { - return []string{ - "--- a/" + self.opts.FileNameOverride, - "+++ b/" + self.opts.FileNameOverride, - } - } else if self.opts.TurnAddedFilesIntoDiffAgainstEmptyFile { - result := make([]string, 0, len(self.patch.header)) - for idx, line := range self.patch.header { - if strings.HasPrefix(line, "new file mode") { - continue - } - if line == "--- /dev/null" && strings.HasPrefix(self.patch.header[idx+1], "+++ b/") { - line = "--- a/" + self.patch.header[idx+1][6:] - } - result = append(result, line) - } - return result - } - - return self.patch.header -} - -func (self *patchTransformer) transformHunks() []*Hunk { - newHunks := make([]*Hunk, 0, len(self.patch.hunks)) - - startOffset := 0 - var formattedHunk *Hunk - for i, hunk := range self.patch.hunks { - startOffset, formattedHunk = self.transformHunk( - hunk, - startOffset, - self.patch.HunkStartIdx(i), - ) - if formattedHunk.containsChanges() { - newHunks = append(newHunks, formattedHunk) - } - } - - return newHunks -} - -func (self *patchTransformer) transformHunk(hunk *Hunk, startOffset int, firstLineIdx int) (int, *Hunk) { - newLines := self.transformHunkLines(hunk, firstLineIdx) - newNewStart, newStartOffset := self.transformHunkHeader(newLines, hunk.oldStart, startOffset) - - newHunk := &Hunk{ - bodyLines: newLines, - oldStart: hunk.oldStart, - newStart: newNewStart, - headerContext: hunk.headerContext, - } - - return newStartOffset, newHunk -} - -func (self *patchTransformer) transformHunkLines(hunk *Hunk, firstLineIdx int) []*PatchLine { - skippedNewlineMessageIndex := -1 - newLines := []*PatchLine{} - - for i, line := range hunk.bodyLines { - lineIdx := i + firstLineIdx + 1 // plus one for header line - if line.Content == "" { - break - } - isLineSelected := lo.Contains(self.opts.IncludedLineIndices, lineIdx) - - if isLineSelected || (line.Kind == NEWLINE_MESSAGE && skippedNewlineMessageIndex != lineIdx) || line.Kind == CONTEXT { - newLines = append(newLines, line) - continue - } - - if (line.Kind == DELETION && !self.opts.Reverse) || (line.Kind == ADDITION && self.opts.Reverse) { - content := " " + line.Content[1:] - newLines = append(newLines, &PatchLine{ - Kind: CONTEXT, - Content: content, - }) - continue - } - - if line.Kind == ADDITION { - // we don't want to include the 'newline at end of file' line if it involves an addition we're not including - skippedNewlineMessageIndex = lineIdx + 1 - } - } - - return newLines -} - -func (self *patchTransformer) transformHunkHeader(newBodyLines []*PatchLine, oldStart int, startOffset int) (int, int) { - oldLength := nLinesWithKind(newBodyLines, []PatchLineKind{CONTEXT, DELETION}) - newLength := nLinesWithKind(newBodyLines, []PatchLineKind{CONTEXT, ADDITION}) - - var newStartOffset int - // if the hunk went from zero to positive length, we need to increment the starting point by one - // if the hunk went from positive to zero length, we need to decrement the starting point by one - if oldLength == 0 { - newStartOffset = 1 - } else if newLength == 0 { - newStartOffset = -1 - } else { - newStartOffset = 0 - } - - newStart := oldStart + startOffset + newStartOffset - - newStartOffset = startOffset + newLength - oldLength - - return newStart, newStartOffset -} diff --git a/pkg/commands/testdata/a_dir/file b/pkg/commands/testdata/a_dir/file deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/pkg/commands/testdata/a_file b/pkg/commands/testdata/a_file deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/pkg/common/common.go b/pkg/common/common.go deleted file mode 100644 index 837af7bea13..00000000000 --- a/pkg/common/common.go +++ /dev/null @@ -1,30 +0,0 @@ -package common - -import ( - "sync/atomic" - - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/sirupsen/logrus" - "github.com/spf13/afero" -) - -// Commonly used things wrapped into one struct for convenience when passing it around -type Common struct { - Log *logrus.Entry - Tr *i18n.TranslationSet - userConfig atomic.Pointer[config.UserConfig] - AppState *config.AppState - Debug bool - // for interacting with the filesystem. We use afero rather than the default - // `os` package for the sake of mocking the filesystem in tests - Fs afero.Fs -} - -func (c *Common) UserConfig() *config.UserConfig { - return c.userConfig.Load() -} - -func (c *Common) SetUserConfig(userConfig *config.UserConfig) { - c.userConfig.Store(userConfig) -} diff --git a/pkg/common/dummies.go b/pkg/common/dummies.go deleted file mode 100644 index c689bca09b0..00000000000 --- a/pkg/common/dummies.go +++ /dev/null @@ -1,33 +0,0 @@ -package common - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/spf13/afero" -) - -func NewDummyCommon() *Common { - tr := i18n.EnglishTranslationSet() - cmn := &Common{ - Log: utils.NewDummyLog(), - Tr: tr, - Fs: afero.NewOsFs(), - } - cmn.SetUserConfig(config.GetDefaultConfig()) - return cmn -} - -func NewDummyCommonWithUserConfigAndAppState(userConfig *config.UserConfig, appState *config.AppState) *Common { - tr := i18n.EnglishTranslationSet() - cmn := &Common{ - Log: utils.NewDummyLog(), - Tr: tr, - AppState: appState, - // TODO: remove dependency on actual filesystem in tests and switch to using - // in-memory for everything - Fs: afero.NewOsFs(), - } - cmn.SetUserConfig(userConfig) - return cmn -} diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go deleted file mode 100644 index 8205f7ef6cf..00000000000 --- a/pkg/config/app_config.go +++ /dev/null @@ -1,719 +0,0 @@ -package config - -import ( - "errors" - "fmt" - "log" - "os" - "path/filepath" - "reflect" - "strings" - "time" - - "github.com/adrg/xdg" - "github.com/jesseduffield/generics/orderedset" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/jesseduffield/lazygit/pkg/utils/yaml_utils" - "github.com/samber/lo" - "gopkg.in/yaml.v3" -) - -// AppConfig contains the base configuration fields required for lazygit. -type AppConfig struct { - debug bool `long:"debug" env:"DEBUG" default:"false"` - version string `long:"version" env:"VERSION" default:"unversioned"` - buildDate string `long:"build-date" env:"BUILD_DATE"` - name string `long:"name" env:"NAME" default:"lazygit"` - buildSource string `long:"build-source" env:"BUILD_SOURCE" default:""` - userConfig *UserConfig - globalUserConfigFiles []*ConfigFile - userConfigFiles []*ConfigFile - userConfigDir string - tempDir string - appState *AppState -} - -type AppConfigurer interface { - GetDebug() bool - - // build info - GetVersion() string - GetName() string - GetBuildSource() string - - GetUserConfig() *UserConfig - GetUserConfigPaths() []string - GetUserConfigDir() string - ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error - ReloadChangedUserConfigFiles() (error, bool) - GetTempDir() string - - GetAppState() *AppState - SaveAppState() error -} - -type ConfigFilePolicy int - -const ( - ConfigFilePolicyCreateIfMissing ConfigFilePolicy = iota - ConfigFilePolicyErrorIfMissing - ConfigFilePolicySkipIfMissing -) - -type ConfigFile struct { - Path string - Policy ConfigFilePolicy - modDate time.Time - exists bool -} - -// NewAppConfig makes a new app config -func NewAppConfig( - name string, - version, - commit, - date string, - buildSource string, - debuggingFlag bool, - tempDir string, -) (*AppConfig, error) { - configDir, err := findOrCreateConfigDir() - if err != nil && !os.IsPermission(err) { - return nil, err - } - - var configFiles []*ConfigFile - customConfigFiles := os.Getenv("LG_CONFIG_FILE") - if customConfigFiles != "" { - // Load user defined config files - userConfigPaths := strings.Split(customConfigFiles, ",") - configFiles = lo.Map(userConfigPaths, func(path string, _ int) *ConfigFile { - return &ConfigFile{Path: path, Policy: ConfigFilePolicyErrorIfMissing} - }) - } else { - // Load default config files - path := filepath.Join(configDir, ConfigFilename) - configFile := &ConfigFile{Path: path, Policy: ConfigFilePolicyCreateIfMissing} - configFiles = []*ConfigFile{configFile} - } - - userConfig, err := loadUserConfigWithDefaults(configFiles, false) - if err != nil { - return nil, err - } - - appState, err := loadAppState() - if err != nil { - return nil, err - } - - appConfig := &AppConfig{ - name: name, - version: version, - buildDate: date, - debug: debuggingFlag, - buildSource: buildSource, - userConfig: userConfig, - globalUserConfigFiles: configFiles, - userConfigFiles: configFiles, - userConfigDir: configDir, - tempDir: tempDir, - appState: appState, - } - - return appConfig, nil -} - -func ConfigDir() string { - _, filePath := findConfigFile(ConfigFilename) - - return filepath.Dir(filePath) -} - -func findOrCreateConfigDir() (string, error) { - folder := ConfigDir() - return folder, os.MkdirAll(folder, 0o755) -} - -func loadUserConfigWithDefaults(configFiles []*ConfigFile, isGuiInitialized bool) (*UserConfig, error) { - return loadUserConfig(configFiles, GetDefaultConfig(), isGuiInitialized) -} - -func loadUserConfig(configFiles []*ConfigFile, base *UserConfig, isGuiInitialized bool) (*UserConfig, error) { - for _, configFile := range configFiles { - path := configFile.Path - statInfo, err := os.Stat(path) - if err == nil { - configFile.exists = true - configFile.modDate = statInfo.ModTime() - } else { - if !os.IsNotExist(err) { - return nil, err - } - - switch configFile.Policy { - case ConfigFilePolicyErrorIfMissing: - return nil, err - - case ConfigFilePolicySkipIfMissing: - configFile.exists = false - continue - - case ConfigFilePolicyCreateIfMissing: - file, err := os.Create(path) - if err != nil { - if os.IsPermission(err) { - // apparently when people have read-only permissions they prefer us to fail silently - continue - } - return nil, err - } - file.Close() - - configFile.exists = true - statInfo, err := os.Stat(configFile.Path) - if err != nil { - return nil, err - } - configFile.modDate = statInfo.ModTime() - } - } - - content, err := os.ReadFile(path) - if err != nil { - return nil, err - } - - content, err = migrateUserConfig(path, content, isGuiInitialized) - if err != nil { - return nil, err - } - - existingCustomCommands := base.CustomCommands - - if err := yaml.Unmarshal(content, base); err != nil { - return nil, fmt.Errorf("The config at `%s` couldn't be parsed, please inspect it before opening up an issue.\n%w", path, err) - } - - base.CustomCommands = append(base.CustomCommands, existingCustomCommands...) - - if err := base.Validate(); err != nil { - return nil, fmt.Errorf("The config at `%s` has a validation error.\n%w", path, err) - } - } - - return base, nil -} - -type ChangesSet = orderedset.OrderedSet[string] - -func NewChangesSet() *ChangesSet { - return orderedset.New[string]() -} - -// Do any backward-compatibility migrations of things that have changed in the -// config over time; examples are renaming a key to a better name, moving a key -// from one container to another, or changing the type of a key (e.g. from bool -// to an enum). -func migrateUserConfig(path string, content []byte, isGuiInitialized bool) ([]byte, error) { - changes := NewChangesSet() - - changedContent, didChange, err := computeMigratedConfig(path, content, changes) - if err != nil { - return nil, err - } - - // Nothing to do if config didn't change - if !didChange { - return content, nil - } - - changesText := "The following changes were made:\n\n" - changesText += strings.Join(lo.Map(changes.ToSliceFromOldest(), func(change string, _ int) string { - return fmt.Sprintf("- %s\n", change) - }), "") - - // Write config back - if !isGuiInitialized { - fmt.Printf("The user config file %s must be migrated. Attempting to do this automatically.\n", path) - fmt.Println(changesText) - } - if err := os.WriteFile(path, changedContent, 0o644); err != nil { - errorMsg := fmt.Sprintf("While attempting to write back migrated user config to %s, an error occurred: %s", path, err) - if isGuiInitialized { - errorMsg += "\n\n" + changesText - } - return nil, errors.New(errorMsg) - } - if !isGuiInitialized { - fmt.Printf("Config file saved successfully to %s\n", path) - } - return changedContent, nil -} - -// A pure function helper for testing purposes -func computeMigratedConfig(path string, content []byte, changes *ChangesSet) ([]byte, bool, error) { - var err error - var rootNode yaml.Node - err = yaml.Unmarshal(content, &rootNode) - if err != nil { - return nil, false, fmt.Errorf("failed to parse YAML: %w", err) - } - var originalCopy yaml.Node - err = yaml.Unmarshal(content, &originalCopy) - if err != nil { - return nil, false, fmt.Errorf("failed to parse YAML, but only the second time!?!? How did that happen: %w", err) - } - - pathsToReplace := []struct { - oldPath []string - newName string - }{ - {[]string{"gui", "skipUnstageLineWarning"}, "skipDiscardChangeWarning"}, - {[]string{"keybinding", "universal", "executeCustomCommand"}, "executeShellCommand"}, - {[]string{"gui", "windowSize"}, "screenMode"}, - {[]string{"keybinding", "files", "openMergeTool"}, "openMergeOptions"}, - } - - for _, pathToReplace := range pathsToReplace { - err, didReplace := yaml_utils.RenameYamlKey(&rootNode, pathToReplace.oldPath, pathToReplace.newName) - if err != nil { - return nil, false, fmt.Errorf("Couldn't migrate config file at `%s` for key %s: %w", path, strings.Join(pathToReplace.oldPath, "."), err) - } - if didReplace { - changes.Add(fmt.Sprintf("Renamed '%s' to '%s'", strings.Join(pathToReplace.oldPath, "."), pathToReplace.newName)) - } - } - - err = changeNullKeybindingsToDisabled(&rootNode, changes) - if err != nil { - return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err) - } - - err = changeElementToSequence(&rootNode, []string{"git", "commitPrefix"}, changes) - if err != nil { - return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err) - } - - err = changeCommitPrefixesMap(&rootNode, changes) - if err != nil { - return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err) - } - - err = changeCustomCommandStreamAndOutputToOutputEnum(&rootNode, changes) - if err != nil { - return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err) - } - - err = migrateAllBranchesLogCmd(&rootNode, changes) - if err != nil { - return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err) - } - - err = migratePagers(&rootNode, changes) - if err != nil { - return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err) - } - - // Add more migrations here... - - if reflect.DeepEqual(rootNode, originalCopy) { - return nil, false, nil - } - - newContent, err := yaml_utils.YamlMarshal(&rootNode) - if err != nil { - return nil, false, fmt.Errorf("Failed to remarsal!\n %w", err) - } - return newContent, true, nil -} - -func changeNullKeybindingsToDisabled(rootNode *yaml.Node, changes *ChangesSet) error { - return yaml_utils.Walk(rootNode, func(node *yaml.Node, path string) { - if strings.HasPrefix(path, "keybinding.") && node.Kind == yaml.ScalarNode && node.Tag == "!!null" { - node.Value = "" - node.Tag = "!!str" - changes.Add(fmt.Sprintf("Changed 'null' to '' for keybinding '%s'", path)) - } - }) -} - -func changeElementToSequence(rootNode *yaml.Node, path []string, changes *ChangesSet) error { - return yaml_utils.TransformNode(rootNode, path, func(node *yaml.Node) error { - if node.Kind == yaml.MappingNode { - nodeContentCopy := node.Content - node.Kind = yaml.SequenceNode - node.Value = "" - node.Tag = "!!seq" - node.Content = []*yaml.Node{{ - Kind: yaml.MappingNode, - Content: nodeContentCopy, - }} - - changes.Add(fmt.Sprintf("Changed '%s' to an array of strings", strings.Join(path, "."))) - - return nil - } - return nil - }) -} - -func changeCommitPrefixesMap(rootNode *yaml.Node, changes *ChangesSet) error { - return yaml_utils.TransformNode(rootNode, []string{"git", "commitPrefixes"}, func(prefixesNode *yaml.Node) error { - if prefixesNode.Kind == yaml.MappingNode { - for _, contentNode := range prefixesNode.Content { - if contentNode.Kind == yaml.MappingNode { - nodeContentCopy := contentNode.Content - contentNode.Kind = yaml.SequenceNode - contentNode.Value = "" - contentNode.Tag = "!!seq" - contentNode.Content = []*yaml.Node{{ - Kind: yaml.MappingNode, - Content: nodeContentCopy, - }} - changes.Add("Changed 'git.commitPrefixes' elements to arrays of strings") - } - } - } - return nil - }) -} - -func changeCustomCommandStreamAndOutputToOutputEnum(rootNode *yaml.Node, changes *ChangesSet) error { - return yaml_utils.Walk(rootNode, func(node *yaml.Node, path string) { - // We are being lazy here and rely on the fact that the only mapping - // nodes in the tree under customCommands are actual custom commands. If - // this ever changes (e.g. because we add a struct field to - // customCommand), then we need to change this to iterate properly. - if strings.HasPrefix(path, "customCommands[") && node.Kind == yaml.MappingNode { - output := "" - if streamKey, streamValue := yaml_utils.RemoveKey(node, "subprocess"); streamKey != nil { - if streamValue.Kind == yaml.ScalarNode && streamValue.Value == "true" { - output = "terminal" - changes.Add("Changed 'subprocess: true' to 'output: terminal' in custom command") - } else { - changes.Add("Deleted redundant 'subprocess: false' in custom command") - } - } - if streamKey, streamValue := yaml_utils.RemoveKey(node, "stream"); streamKey != nil { - if streamValue.Kind == yaml.ScalarNode && streamValue.Value == "true" && output == "" { - output = "log" - changes.Add("Changed 'stream: true' to 'output: log' in custom command") - } else { - changes.Add(fmt.Sprintf("Deleted redundant 'stream: %v' property in custom command", streamValue.Value)) - } - } - if streamKey, streamValue := yaml_utils.RemoveKey(node, "showOutput"); streamKey != nil { - if streamValue.Kind == yaml.ScalarNode && streamValue.Value == "true" && output == "" { - changes.Add("Changed 'showOutput: true' to 'output: popup' in custom command") - output = "popup" - } else { - changes.Add(fmt.Sprintf("Deleted redundant 'showOutput: %v' property in custom command", streamValue.Value)) - } - } - if output != "" { - outputKeyNode := &yaml.Node{ - Kind: yaml.ScalarNode, - Value: "output", - Tag: "!!str", - } - outputValueNode := &yaml.Node{ - Kind: yaml.ScalarNode, - Value: output, - Tag: "!!str", - } - node.Content = append(node.Content, outputKeyNode, outputValueNode) - } - } - }) -} - -// This migration is special because users have already defined -// a single element at `allBranchesLogCmd` and the sequence at `allBranchesLogCmds`. -// Some users have explicitly set `allBranchesLogCmd` to be an empty string in order -// to remove it, so in that case we just delete the element, and add nothing to the list -func migrateAllBranchesLogCmd(rootNode *yaml.Node, changes *ChangesSet) error { - return yaml_utils.TransformNode(rootNode, []string{"git"}, func(gitNode *yaml.Node) error { - cmdKeyNode, cmdValueNode := yaml_utils.LookupKey(gitNode, "allBranchesLogCmd") - // Nothing to do if they do not have the deprecated item - if cmdKeyNode == nil { - return nil - } - - cmdsKeyNode, cmdsValueNode := yaml_utils.LookupKey(gitNode, "allBranchesLogCmds") - var change string - if cmdsKeyNode == nil { - // Create empty sequence node and attach it onto the root git node - // We will later populate it with the individual allBranchesLogCmd record - cmdsKeyNode = &yaml.Node{Kind: yaml.ScalarNode, Value: "allBranchesLogCmds"} - cmdsValueNode = &yaml.Node{Kind: yaml.SequenceNode, Content: []*yaml.Node{}} - gitNode.Content = append(gitNode.Content, - cmdsKeyNode, - cmdsValueNode, - ) - change = "Created git.allBranchesLogCmds array containing value of git.allBranchesLogCmd" - } else { - if cmdsValueNode.Kind != yaml.SequenceNode { - return errors.New("You should have an allBranchesLogCmds defined as a sequence!") - } - - change = "Prepended git.allBranchesLogCmd value to git.allBranchesLogCmds array" - } - - if cmdValueNode.Value != "" { - // Prepending the individual element to make it show up first in the list, which was prior behavior - cmdsValueNode.Content = utils.Prepend(cmdsValueNode.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: cmdValueNode.Value}) - changes.Add(change) - } - - // Clear out the existing allBranchesLogCmd, now that we have migrated it into the list - _, _ = yaml_utils.RemoveKey(gitNode, "allBranchesLogCmd") - changes.Add("Removed obsolete git.allBranchesLogCmd") - - return nil - }) -} - -func migratePagers(rootNode *yaml.Node, changes *ChangesSet) error { - return yaml_utils.TransformNode(rootNode, []string{"git"}, func(gitNode *yaml.Node) error { - pagingKeyNode, pagingValueNode := yaml_utils.LookupKey(gitNode, "paging") - if pagingKeyNode == nil || pagingValueNode.Kind != yaml.MappingNode { - // If there's no "paging" section (or it's not an object), there's nothing to do - return nil - } - - pagersKeyNode, _ := yaml_utils.LookupKey(gitNode, "pagers") - if pagersKeyNode != nil { - // Conversely, if there *is* already a "pagers" array, we also have nothing to do. - // This covers the case where the user keeps both the "paging" section and the "pagers" - // array for the sake of easier testing of old versions. - return nil - } - - pagingKeyNode.Value = "pagers" - pagingContentCopy := pagingValueNode.Content - pagingValueNode.Kind = yaml.SequenceNode - pagingValueNode.Tag = "!!seq" - pagingValueNode.Content = []*yaml.Node{{ - Kind: yaml.MappingNode, - Content: pagingContentCopy, - }} - - changes.Add("Moved git.paging object to git.pagers array") - - return nil - }) -} - -func (c *AppConfig) GetDebug() bool { - return c.debug -} - -func (c *AppConfig) GetVersion() string { - return c.version -} - -func (c *AppConfig) GetName() string { - return c.name -} - -// GetBuildSource returns the source of the build. For builds from goreleaser -// this will be binaryBuild -func (c *AppConfig) GetBuildSource() string { - return c.buildSource -} - -// GetUserConfig returns the user config -func (c *AppConfig) GetUserConfig() *UserConfig { - return c.userConfig -} - -// GetAppState returns the app state -func (c *AppConfig) GetAppState() *AppState { - return c.appState -} - -func (c *AppConfig) GetUserConfigPaths() []string { - return lo.FilterMap(c.userConfigFiles, func(f *ConfigFile, _ int) (string, bool) { - return f.Path, f.exists - }) -} - -func (c *AppConfig) GetUserConfigDir() string { - return c.userConfigDir -} - -func (c *AppConfig) ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error { - configFiles := append(c.globalUserConfigFiles, repoConfigFiles...) - userConfig, err := loadUserConfigWithDefaults(configFiles, true) - if err != nil { - return err - } - - c.userConfig = userConfig - c.userConfigFiles = configFiles - return nil -} - -func (c *AppConfig) ReloadChangedUserConfigFiles() (error, bool) { - fileHasChanged := func(f *ConfigFile) bool { - info, err := os.Stat(f.Path) - if err != nil && !os.IsNotExist(err) { - // If we can't stat the file, assume it hasn't changed - return false - } - exists := err == nil - return exists != f.exists || (exists && info.ModTime() != f.modDate) - } - - if lo.NoneBy(c.userConfigFiles, fileHasChanged) { - return nil, false - } - - userConfig, err := loadUserConfigWithDefaults(c.userConfigFiles, true) - if err != nil { - return err, false - } - - c.userConfig = userConfig - return nil, true -} - -func (c *AppConfig) GetTempDir() string { - return c.tempDir -} - -// findConfigFile looks for a possibly existing config file. -// This function does NOT create any folders or files. -func findConfigFile(filename string) (exists bool, path string) { - if envConfigDir := os.Getenv("CONFIG_DIR"); envConfigDir != "" { - return true, filepath.Join(envConfigDir, filename) - } - - // look for jesseduffield/lazygit/filename in XDG_CONFIG_HOME and XDG_CONFIG_DIRS - legacyConfigPath, err := xdg.SearchConfigFile(filepath.Join("jesseduffield", "lazygit", filename)) - if err == nil { - return true, legacyConfigPath - } - - // look for lazygit/filename in XDG_CONFIG_HOME and XDG_CONFIG_DIRS - configFilepath, err := xdg.SearchConfigFile(filepath.Join("lazygit", filename)) - if err == nil { - return true, configFilepath - } - - return false, filepath.Join(xdg.ConfigHome, "lazygit", filename) -} - -var ConfigFilename = "config.yml" - -// stateFilePath looks for a possibly existing state file. -// if none exist, the default path is returned and all parent directories are created. -func stateFilePath(filename string) (string, error) { - exists, legacyStateFile := findConfigFile(filename) - if exists { - return legacyStateFile, nil - } - - // looks for XDG_STATE_HOME/lazygit/filename - return xdg.StateFile(filepath.Join("lazygit", filename)) -} - -// SaveAppState marshalls the AppState struct and writes it to the disk -func (c *AppConfig) SaveAppState() error { - marshalledAppState, err := yaml.Marshal(c.appState) - if err != nil { - return err - } - - filepath, err := stateFilePath(stateFileName) - if err != nil { - return err - } - - err = os.WriteFile(filepath, marshalledAppState, 0o644) - if err != nil && os.IsPermission(err) { - // apparently when people have read-only permissions they prefer us to fail silently - return nil - } - - return err -} - -var stateFileName = "state.yml" - -// loadAppState loads recorded AppState from file -func loadAppState() (*AppState, error) { - appState := getDefaultAppState() - - filepath, err := stateFilePath(stateFileName) - if err != nil { - if os.IsPermission(err) { - // apparently when people have read-only permissions they prefer us to fail silently - return appState, nil - } - return nil, err - } - - appStateBytes, err := os.ReadFile(filepath) - if err != nil && !os.IsNotExist(err) { - return nil, err - } - - if len(appStateBytes) == 0 { - return appState, nil - } - - err = yaml.Unmarshal(appStateBytes, appState) - if err != nil { - return nil, err - } - - return appState, nil -} - -// SaveGlobalUserConfig saves the UserConfig back to disk. This is only used in -// integration tests, so we are a bit sloppy with error handling. -func (c *AppConfig) SaveGlobalUserConfig() { - if len(c.globalUserConfigFiles) != 1 { - panic("expected exactly one global user config file") - } - - yamlContent, err := yaml.Marshal(c.userConfig) - if err != nil { - log.Fatalf("error marshalling user config: %v", err) - } - - err = os.WriteFile(c.globalUserConfigFiles[0].Path, yamlContent, 0o644) - if err != nil { - log.Fatalf("error saving user config: %v", err) - } -} - -// AppState stores data between runs of the app like when the last update check -// was performed and which other repos have been checked out -type AppState struct { - LastUpdateCheck int64 - RecentRepos []string - StartupPopupVersion int - DidShowHunkStagingHint bool - LastVersion string // this is the last version the user was using, for the purpose of showing release notes - - // these are for shell commands typed in directly, not for custom commands in the lazygit config. - // For backwards compatibility we keep the old name in yaml files. - ShellCommandsHistory []string `yaml:"customcommandshistory"` - - HideCommandLog bool -} - -func getDefaultAppState() *AppState { - return &AppState{} -} - -func LogPath() (string, error) { - if os.Getenv("LAZYGIT_LOG_PATH") != "" { - return os.Getenv("LAZYGIT_LOG_PATH"), nil - } - - return stateFilePath("development.log") -} diff --git a/pkg/config/app_config_test.go b/pkg/config/app_config_test.go deleted file mode 100644 index 1109256a9d3..00000000000 --- a/pkg/config/app_config_test.go +++ /dev/null @@ -1,1186 +0,0 @@ -package config - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestMigrationOfRenamedKeys(t *testing.T) { - scenarios := []struct { - name string - input string - expected string - expectedDidChange bool - expectedChanges []string - }{ - { - name: "Empty String", - input: "", - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "No rename needed", - input: `foo: - bar: 5 -`, - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "Rename one", - input: `gui: - skipUnstageLineWarning: true -`, - expected: `gui: - skipDiscardChangeWarning: true -`, - expectedDidChange: true, - expectedChanges: []string{"Renamed 'gui.skipUnstageLineWarning' to 'skipDiscardChangeWarning'"}, - }, - { - name: "Rename several", - input: `gui: - windowSize: half - skipUnstageLineWarning: true -keybinding: - universal: - executeCustomCommand: a -`, - expected: `gui: - screenMode: half - skipDiscardChangeWarning: true -keybinding: - universal: - executeShellCommand: a -`, - expectedDidChange: true, - expectedChanges: []string{ - "Renamed 'gui.skipUnstageLineWarning' to 'skipDiscardChangeWarning'", - "Renamed 'keybinding.universal.executeCustomCommand' to 'executeShellCommand'", - "Renamed 'gui.windowSize' to 'screenMode'", - }, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - changes := NewChangesSet() - actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes) - assert.NoError(t, err) - assert.Equal(t, s.expectedDidChange, didChange) - if didChange { - assert.Equal(t, s.expected, string(actual)) - } - assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest()) - }) - } -} - -func TestMigrateNullKeybindingsToDisabled(t *testing.T) { - scenarios := []struct { - name string - input string - expected string - expectedDidChange bool - expectedChanges []string - }{ - { - name: "Empty String", - input: "", - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "No change needed", - input: `keybinding: - universal: - quit: q -`, - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "Change one", - input: `keybinding: - universal: - quit: null -`, - expected: `keybinding: - universal: - quit: -`, - expectedDidChange: true, - expectedChanges: []string{"Changed 'null' to '' for keybinding 'keybinding.universal.quit'"}, - }, - { - name: "Change several", - input: `keybinding: - universal: - quit: null - return: - new: null -`, - expected: `keybinding: - universal: - quit: - return: - new: -`, - expectedDidChange: true, - expectedChanges: []string{ - "Changed 'null' to '' for keybinding 'keybinding.universal.quit'", - "Changed 'null' to '' for keybinding 'keybinding.universal.new'", - }, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - changes := NewChangesSet() - actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes) - assert.NoError(t, err) - assert.Equal(t, s.expectedDidChange, didChange) - if didChange { - assert.Equal(t, s.expected, string(actual)) - } - assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest()) - }) - } -} - -func TestCommitPrefixMigrations(t *testing.T) { - scenarios := []struct { - name string - input string - expected string - expectedDidChange bool - expectedChanges []string - }{ - { - name: "Empty String", - input: "", - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "Single CommitPrefix Rename", - input: `git: - commitPrefix: - pattern: "^\\w+-\\w+.*" - replace: '[JIRA $0] ' -`, - expected: `git: - commitPrefix: - - pattern: "^\\w+-\\w+.*" - replace: '[JIRA $0] ' -`, - expectedDidChange: true, - expectedChanges: []string{"Changed 'git.commitPrefix' to an array of strings"}, - }, - { - name: "Complicated CommitPrefixes Rename", - input: `git: - commitPrefixes: - foo: - pattern: "^\\w+-\\w+.*" - replace: '[OTHER $0] ' - CrazyName!@#$^*&)_-)[[}{f{[]: - pattern: "^foo.bar*" - replace: '[FUN $0] ' -`, - expected: `git: - commitPrefixes: - foo: - - pattern: "^\\w+-\\w+.*" - replace: '[OTHER $0] ' - CrazyName!@#$^*&)_-)[[}{f{[]: - - pattern: "^foo.bar*" - replace: '[FUN $0] ' -`, - expectedDidChange: true, - expectedChanges: []string{"Changed 'git.commitPrefixes' elements to arrays of strings"}, - }, - { - name: "Incomplete Configuration", - input: "git:", - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "No changes made when already migrated", - input: ` -git: - commitPrefix: - - pattern: "Hello World" - replace: "Goodbye" - commitPrefixes: - foo: - - pattern: "^\\w+-\\w+.*" - replace: '[JIRA $0] '`, - expectedDidChange: false, - expectedChanges: []string{}, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - changes := NewChangesSet() - actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes) - assert.NoError(t, err) - assert.Equal(t, s.expectedDidChange, didChange) - if didChange { - assert.Equal(t, s.expected, string(actual)) - } - assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest()) - }) - } -} - -func TestCustomCommandsOutputMigration(t *testing.T) { - scenarios := []struct { - name string - input string - expected string - expectedDidChange bool - expectedChanges []string - }{ - { - name: "Empty String", - input: "", - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "Convert subprocess to output=terminal", - input: `customCommands: - - command: echo 'hello' - subprocess: true - `, - expected: `customCommands: - - command: echo 'hello' - output: terminal -`, - expectedDidChange: true, - expectedChanges: []string{"Changed 'subprocess: true' to 'output: terminal' in custom command"}, - }, - { - name: "Convert stream to output=log", - input: `customCommands: - - command: echo 'hello' - stream: true - `, - expected: `customCommands: - - command: echo 'hello' - output: log -`, - expectedDidChange: true, - expectedChanges: []string{"Changed 'stream: true' to 'output: log' in custom command"}, - }, - { - name: "Convert showOutput to output=popup", - input: `customCommands: - - command: echo 'hello' - showOutput: true - `, - expected: `customCommands: - - command: echo 'hello' - output: popup -`, - expectedDidChange: true, - expectedChanges: []string{"Changed 'showOutput: true' to 'output: popup' in custom command"}, - }, - { - name: "Subprocess wins over the other two", - input: `customCommands: - - command: echo 'hello' - subprocess: true - stream: true - showOutput: true - `, - expected: `customCommands: - - command: echo 'hello' - output: terminal -`, - expectedDidChange: true, - expectedChanges: []string{ - "Changed 'subprocess: true' to 'output: terminal' in custom command", - "Deleted redundant 'stream: true' property in custom command", - "Deleted redundant 'showOutput: true' property in custom command", - }, - }, - { - name: "Stream wins over showOutput", - input: `customCommands: - - command: echo 'hello' - stream: true - showOutput: true - `, - expected: `customCommands: - - command: echo 'hello' - output: log -`, - expectedDidChange: true, - expectedChanges: []string{ - "Changed 'stream: true' to 'output: log' in custom command", - "Deleted redundant 'showOutput: true' property in custom command", - }, - }, - { - name: "Explicitly setting to false doesn't create an output=none key", - input: `customCommands: - - command: echo 'hello' - subprocess: false - stream: false - showOutput: false - `, - expected: `customCommands: - - command: echo 'hello' -`, - expectedDidChange: true, - expectedChanges: []string{ - "Deleted redundant 'subprocess: false' in custom command", - "Deleted redundant 'stream: false' property in custom command", - "Deleted redundant 'showOutput: false' property in custom command", - }, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - changes := NewChangesSet() - actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes) - assert.NoError(t, err) - assert.Equal(t, s.expectedDidChange, didChange) - if didChange { - assert.Equal(t, s.expected, string(actual)) - } - assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest()) - }) - } -} - -var largeConfiguration = []byte(` -# Config relating to the Lazygit UI -gui: - # The number of lines you scroll by when scrolling the main window - scrollHeight: 2 - - # If true, allow scrolling past the bottom of the content in the main window - scrollPastBottom: true - - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#scroll-off-margin - scrollOffMargin: 2 - - # One of: 'margin' (default) | 'jump' - scrollOffBehavior: margin - - # The number of spaces per tab; used for everything that's shown in the main view, but probably mostly relevant for diffs. - # Note that when using a pager, the pager has its own tab width setting, so you need to pass it separately in the pager command. - tabWidth: 4 - - # If true, capture mouse events. - # When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS. - mouseEvents: true - - # If true, do not show a warning when amending a commit. - skipAmendWarning: false - - # If true, do not show a warning when discarding changes in the staging view. - skipDiscardChangeWarning: false - - # If true, do not show warning when applying/popping the stash - skipStashWarning: false - - # If true, do not show a warning when attempting to commit without any staged files; instead stage all unstaged files. - skipNoStagedFilesWarning: false - - # If true, do not show a warning when rewording a commit via an external editor - skipRewordInEditorWarning: false - - # Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section. - # Number from 0 to 1.0. - sidePanelWidth: 0.3333 - - # If true, increase the height of the focused side window; creating an accordion effect. - expandFocusedSidePanel: false - - # The weight of the expanded side panel, relative to the other panels. 2 means - # twice as tall as the other panels. Only relevant if expandFocusedSidePanel is true. - expandedSidePanelWeight: 2 - - # Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split. - # Options are: - # - 'horizontal': split the window horizontally - # - 'vertical': split the window vertically - # - 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically - mainPanelSplitMode: flexible - - # How the window is split when in half screen mode (i.e. after hitting '+' once). - # Possible values: - # - 'left': split the window horizontally (side panel on the left, main view on the right) - # - 'top': split the window vertically (side panel on top, main view below) - enlargedSideViewLocation: left - - # If true, wrap lines in the staging view to the width of the view. This - # makes it much easier to work with diffs that have long lines, e.g. - # paragraphs of markdown text. - wrapLinesInStagingView: true - - # One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru' - language: auto - - # Format used when displaying time e.g. commit time. - # Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format - timeFormat: 02 Jan 06 - - # Format used when displaying time if the time is less than 24 hours ago. - # Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format - shortTimeFormat: 3:04PM - - # Config relating to colors and styles. - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes - theme: - # Border color of focused window - activeBorderColor: - - green - - bold - - # Border color of non-focused windows - inactiveBorderColor: - - default - - # Border color of focused window when searching in that window - searchingActiveBorderColor: - - cyan - - bold - - # Color of keybindings help text in the bottom line - optionsTextColor: - - blue - - # Background color of selected line. - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line - selectedLineBgColor: - - blue - - # Background color of selected line when view doesn't have focus. - inactiveViewSelectedLineBgColor: - - bold - - # Foreground color of copied commit - cherryPickedCommitFgColor: - - blue - - # Background color of copied commit - cherryPickedCommitBgColor: - - cyan - - # Foreground color of marked base commit (for rebase) - markedBaseCommitFgColor: - - blue - - # Background color of marked base commit (for rebase) - markedBaseCommitBgColor: - - yellow - - # Color for file with unstaged changes - unstagedChangesColor: - - red - - # Default text color - defaultFgColor: - - default - - # Config relating to the commit length indicator - commitLength: - # If true, show an indicator of commit message length - show: true - - # If true, show the '5 of 20' footer at the bottom of list views - showListFooter: true - - # If true, display the files in the file views as a tree. If false, display the files as a flat list. - # This can be toggled from within Lazygit with the '' key, but that will not change the default. - showFileTree: true - - # If true, show the number of lines changed per file in the Files view - showNumstatInFilesView: false - - # If true, show a random tip in the command log when Lazygit starts - showRandomTip: true - - # If true, show the command log - showCommandLog: true - - # If true, show the bottom line that contains keybinding info and useful buttons. If false, this line will be hidden except to display a loader for an in-progress action. - showBottomLine: true - - # If true, show jump-to-window keybindings in window titles. - showPanelJumps: true - - # Deprecated: use nerdFontsVersion instead - showIcons: false - - # Nerd fonts version to use. - # One of: '2' | '3' | empty string (default) - # If empty, do not show icons. - nerdFontsVersion: "" - - # If true (default), file icons are shown in the file views. Only relevant if NerdFontsVersion is not empty. - showFileIcons: true - - # Length of author name in (non-expanded) commits view. 2 means show initials only. - commitAuthorShortLength: 2 - - # Length of author name in expanded commits view. 2 means show initials only. - commitAuthorLongLength: 17 - - # Length of commit hash in commits view. 0 shows '*' if NF icons aren't on. - commitHashLength: 8 - - # If true, show commit hashes alongside branch names in the branches view. - showBranchCommitHash: false - - # Whether to show the divergence from the base branch in the branches view. - # One of: 'none' | 'onlyArrow' | 'arrowAndNumber' - showDivergenceFromBaseBranch: none - - # Height of the command log view - commandLogSize: 8 - - # Whether to split the main window when viewing file changes. - # One of: 'auto' | 'always' - # If 'auto', only split the main window when a file has both staged and unstaged changes - splitDiff: auto - - # Default size for focused window. Can be changed from within Lazygit with '+' and '_' (but this won't change the default). - # One of: 'normal' (default) | 'half' | 'full' - screenMode: normal - - # Window border style. - # One of 'rounded' (default) | 'single' | 'double' | 'hidden' | 'bold' - border: rounded - - # If true, show a seriously epic explosion animation when nuking the working tree. - animateExplosion: true - - # Whether to stack UI components on top of each other. - # One of 'auto' (default) | 'always' | 'never' - portraitMode: auto - - # How things are filtered when typing '/'. - # One of 'substring' (default) | 'fuzzy' - filterMode: substring - - # Config relating to the spinner. - spinner: - # The frames of the spinner animation. - frames: - - '|' - - / - - '-' - - \ - - # The "speed" of the spinner in milliseconds. - rate: 50 - - # Status panel view. - # One of 'dashboard' (default) | 'allBranchesLog' - statusPanelView: dashboard - - # If true, jump to the Files panel after popping a stash - switchToFilesAfterStashPop: true - - # If true, jump to the Files panel after applying a stash - switchToFilesAfterStashApply: true - - # If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead - switchTabsWithPanelJumpKeys: false - -# Config relating to git -git: - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md - paging: - # Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never' - colorArg: always - - # e.g. - # diff-so-fancy - # delta --dark --paging=never - # ydiff -p cat -s --wrap --width={{columnWidth}} - pager: "" - - useConfig: false - - # e.g. 'difft --color=always' - externalDiffCommand: "" - - # Config relating to committing - commit: - # If true, pass '--signoff' flag when committing - signOff: false - - # Automatic WYSIWYG wrapping of the commit message as you type - autoWrapCommitMessage: true - - # If autoWrapCommitMessage is true, the width to wrap to - autoWrapWidth: 72 - - # Config relating to merging - merging: - # If true, run merges in a subprocess so that if a commit message is required, Lazygit will not hang - # Only applicable to unix users. - manualCommit: false - - # Extra args passed to , e.g. --no-ff - args: "" - - # The commit message to use for a squash merge commit. Can contain "{{selectedRef}}" and "{{currentBranch}}" placeholders. - squashMergeMessage: Squash merge {{selectedRef}} into {{currentBranch}} - - # list of branches that are considered 'main' branches, used when displaying commits - mainBranches: - - master - - main - - # Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP' - skipHookPrefix: WIP - - # If true, periodically fetch from remote - autoFetch: true - - # If true, periodically refresh files and submodules - autoRefresh: true - - # If true, pass the --all arg to git fetch - fetchAll: true - - # If true, lazygit will automatically stage files that used to have merge - # conflicts but no longer do; and it will also ask you if you want to - # continue a merge or rebase if you've resolved all conflicts. If false, it - # won't do either of these things. - autoStageResolvedConflicts: true - - # Command used when displaying the current branch git log in the main window - branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} -- - - # Command used to display git log of all branches in the main window. - # Deprecated: Use allBranchesLogCmds instead. - allBranchesLogCmd: git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium - - # If true, do not spawn a separate process when using GPG - overrideGpg: false - - # If true, do not allow force pushes - disableForcePushing: false - - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix - branchPrefix: "" - - # If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀 - # (This should really be under 'gui', not 'git') - parseEmoji: false - - # Config for showing the log in the commits view - log: - # One of: 'date-order' | 'author-date-order' | 'topo-order' | 'default' - # 'topo-order' makes it easier to read the git log graph, but commits may not - # appear chronologically. See https://git-scm.com/docs/ - # - # Deprecated: Configure this with Log menu -> Commit sort order ( in the commits window by default). - order: topo-order - - # This determines whether the git graph is rendered in the commits panel - # One of 'always' | 'never' | 'when-maximised' - # - # Deprecated: Configure this with Log menu -> Show git graph ( in the commits window by default). - showGraph: always - - # displays the whole git graph by default in the commits view (equivalent to passing the --all argument to git log) - showWholeGraph: false - - # When copying commit hashes to the clipboard, truncate them to this - # length. Set to 40 to disable truncation. - truncateCopiedCommitHashesTo: 12 - -# Periodic update checks -update: - # One of: 'prompt' (default) | 'background' | 'never' - method: prompt - - # Period in days between update checks - days: 14 - -# Background refreshes -refresher: - # File/submodule refresh interval in seconds. - # Auto-refresh can be disabled via option 'git.autoRefresh'. - refreshInterval: 10 - - # Re-fetch interval in seconds. - # Auto-fetch can be disabled via option 'git.autoFetch'. - fetchInterval: 60 - -# If true, show a confirmation popup before quitting Lazygit -confirmOnQuit: false - -# If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close -quitOnTopLevelReturn: false - -# Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc -os: - # Command for editing a file. Should contain "{{filename}}". - edit: "" - - # Command for editing a file at a given line number. Should contain - # "{{filename}}", and may optionally contain "{{line}}". - editAtLine: "" - - # Same as EditAtLine, except that the command needs to wait until the - # window is closed. - editAtLineAndWait: "" - - # Whether lazygit suspends until an edit process returns - editInTerminal: false - - # For opening a directory in an editor - openDirInEditor: "" - - # A built-in preset that sets all of the above settings. Supported presets - # are defined in the getPreset function in editor_presets.go. - editPreset: "" - - # Command for opening a file, as if the file is double-clicked. Should - # contain "{{filename}}", but doesn't support "{{line}}". - open: "" - - # Command for opening a link. Should contain "{{link}}". - openLink: "" - - # EditCommand is the command for editing a file. - # Deprecated: use Edit instead. Note that semantics are different: - # EditCommand is just the command itself, whereas Edit contains a - # "{{filename}}" variable. - editCommand: "" - - # EditCommandTemplate is the command template for editing a file - # Deprecated: use EditAtLine instead. - editCommandTemplate: "" - - # OpenCommand is the command for opening a file - # Deprecated: use Open instead. - openCommand: "" - - # OpenLinkCommand is the command for opening a link - # Deprecated: use OpenLink instead. - openLinkCommand: "" - - # CopyToClipboardCmd is the command for copying to clipboard. - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard - copyToClipboardCmd: "" - - # ReadFromClipboardCmd is the command for reading the clipboard. - # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard - readFromClipboardCmd: "" - -# If true, don't display introductory popups upon opening Lazygit. -disableStartupPopups: false - -# What to do when opening Lazygit outside of a git repo. -# - 'prompt': (default) ask whether to initialize a new repo or open in the most recent repo -# - 'create': initialize a new repo -# - 'skip': open most recent repo -# - 'quit': exit Lazygit -notARepository: prompt - -# If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit. -promptToReturnFromSubprocess: true - -# Keybindings -keybinding: - universal: - quit: q - quit-alt1: - return: - quitWithoutChangingDirectory: Q - togglePanel: - prevItem: - nextItem: - prevItem-alt: k - nextItem-alt: j - prevPage: ',' - nextPage: . - scrollLeft: H - scrollRight: L - gotoTop: < - gotoBottom: '>' - toggleRangeSelect: v - rangeSelectDown: - rangeSelectUp: - prevBlock: - nextBlock: - prevBlock-alt: h - nextBlock-alt: l - nextBlock-alt2: - prevBlock-alt2: - jumpToBlock: - - "1" - - "2" - - "3" - - "4" - - "5" - nextMatch: "n" - prevMatch: "N" - startSearch: / - optionMenu: - optionMenu-alt1: '?' - select: - goInto: - confirm: - confirmInEditor: - remove: d - new: "n" - edit: e - openFile: o - scrollUpMain: - scrollDownMain: - scrollUpMain-alt1: K - scrollDownMain-alt1: J - scrollUpMain-alt2: - scrollDownMain-alt2: - executeShellCommand: ':' - createRebaseOptionsMenu: m - - # 'Files' appended for legacy reasons - pushFiles: P - - # 'Files' appended for legacy reasons - pullFiles: p - refresh: R - createPatchOptionsMenu: - nextTab: ']' - prevTab: '[' - nextScreenMode: + - prevScreenMode: _ - undo: z - redo: Z - filteringMenu: - diffingMenu: W - diffingMenu-alt: - copyToClipboard: - openRecentRepos: - submitEditorText: - extrasMenu: '@' - toggleWhitespaceInDiffView: - increaseContextInDiffView: '}' - decreaseContextInDiffView: '{' - increaseRenameSimilarityThreshold: ) - decreaseRenameSimilarityThreshold: ( - openDiffTool: - status: - checkForUpdate: u - recentRepos: - allBranchesLogGraph: a - files: - commitChanges: c - commitChangesWithoutHook: w - amendLastCommit: A - commitChangesWithEditor: C - findBaseCommitForFixup: - confirmDiscard: x - ignoreFile: i - refreshFiles: r - stashAllChanges: s - viewStashOptions: S - toggleStagedAll: a - viewResetOptions: D - fetch: f - openMergeOptions: M - openStatusFilter: - copyFileInfoToClipboard: "y" - collapseAll: '-' - expandAll: = - branches: - createPullRequest: o - viewPullRequestOptions: O - copyPullRequestURL: - checkoutBranchByName: c - forceCheckoutBranch: F - rebaseBranch: r - renameBranch: R - mergeIntoCurrentBranch: M - viewGitFlowOptions: i - fastForward: f - createTag: T - pushTag: P - setUpstream: u - fetchRemote: f - sortOrder: s - worktrees: - viewWorktreeOptions: w - commits: - squashDown: s - renameCommit: r - renameCommitWithEditor: R - viewResetOptions: g - markCommitAsFixup: f - createFixupCommit: F - squashAboveCommits: S - moveDownCommit: - moveUpCommit: - amendToCommit: A - resetCommitAuthor: a - pickCommit: p - revertCommit: t - cherryPickCopy: C - pasteCommits: V - markCommitAsBaseForRebase: B - tagCommit: T - checkoutCommit: - resetCherryPick: - copyCommitAttributeToClipboard: "y" - openLogMenu: - openInBrowser: o - viewBisectOptions: b - startInteractiveRebase: i - amendAttribute: - resetAuthor: a - setAuthor: A - addCoAuthor: c - stash: - popStash: g - renameStash: r - commitFiles: - checkoutCommitFile: c - main: - toggleSelectHunk: a - pickBothHunks: b - editSelectHunk: E - submodules: - init: i - update: u - bulkMenu: b - commitMessage: - commitMenu: -`) - -func BenchmarkMigrationOnLargeConfiguration(b *testing.B) { - for b.Loop() { - changes := NewChangesSet() - _, _, _ = computeMigratedConfig("path doesn't matter", largeConfiguration, changes) - } -} - -func TestAllBranchesLogCmdMigrations(t *testing.T) { - scenarios := []struct { - name string - input string - expected string - expectedDidChange bool - expectedChanges []string - }{ - { - name: "Incomplete Configuration Passes uneventfully", - input: "git:", - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "Single Cmd with no Cmds", - input: `git: - allBranchesLogCmd: git log --graph --oneline -`, - expected: `git: - allBranchesLogCmds: - - git log --graph --oneline -`, - expectedDidChange: true, - expectedChanges: []string{ - "Created git.allBranchesLogCmds array containing value of git.allBranchesLogCmd", - "Removed obsolete git.allBranchesLogCmd", - }, - }, - { - name: "Cmd with one existing Cmds", - input: `git: - allBranchesLogCmd: git log --graph --oneline - allBranchesLogCmds: - - git log --graph --oneline --pretty -`, - expected: `git: - allBranchesLogCmds: - - git log --graph --oneline - - git log --graph --oneline --pretty -`, - expectedDidChange: true, - expectedChanges: []string{ - "Prepended git.allBranchesLogCmd value to git.allBranchesLogCmds array", - "Removed obsolete git.allBranchesLogCmd", - }, - }, - { - name: "Only Cmds set have no changes", - input: `git: - allBranchesLogCmds: - - git log -`, - expected: "", - expectedChanges: []string{}, - }, - { - name: "Removes Empty Cmd When at end of yaml", - input: `git: - allBranchesLogCmds: - - git log --graph --oneline - allBranchesLogCmd: -`, - expected: `git: - allBranchesLogCmds: - - git log --graph --oneline -`, - expectedDidChange: true, - expectedChanges: []string{"Removed obsolete git.allBranchesLogCmd"}, - }, - { - name: "Migrates when sequence defined inline", - input: `git: - allBranchesLogCmds: [foo, bar] - allBranchesLogCmd: baz -`, - expected: `git: - allBranchesLogCmds: [baz, foo, bar] -`, - expectedDidChange: true, - expectedChanges: []string{ - "Prepended git.allBranchesLogCmd value to git.allBranchesLogCmds array", - "Removed obsolete git.allBranchesLogCmd", - }, - }, - { - name: "Removes Empty Cmd With Keys Afterwards", - input: `git: - allBranchesLogCmds: - - git log --graph --oneline - allBranchesLogCmd: - foo: bar -`, - expected: `git: - allBranchesLogCmds: - - git log --graph --oneline - foo: bar -`, - expectedDidChange: true, - expectedChanges: []string{"Removed obsolete git.allBranchesLogCmd"}, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - changes := NewChangesSet() - actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes) - assert.NoError(t, err) - assert.Equal(t, s.expectedDidChange, didChange) - if didChange { - assert.Equal(t, s.expected, string(actual)) - } - assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest()) - }) - } -} - -func TestPagerMigration(t *testing.T) { - scenarios := []struct { - name string - input string - expected string - expectedDidChange bool - expectedChanges []string - }{ - { - name: "Incomplete Configuration Passes uneventfully", - input: "git:", - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "No paging section", - input: `git: - autoFetch: true -`, - expected: `git: - autoFetch: true -`, - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "Both paging and pagers exist", - input: `git: - paging: - pager: delta --dark --paging=never - pagers: - - diff: diff-so-fancy -`, - expected: `git: - paging: - pager: delta --dark --paging=never - pagers: - - diff: diff-so-fancy -`, - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "paging is not an object", - input: `git: - paging: 5 -`, - expected: `git: - paging: 5 -`, - expectedDidChange: false, - expectedChanges: []string{}, - }, - { - name: "paging is moved to pagers array (keeping the order)", - input: `git: - paging: - pager: delta --dark --paging=never - autoFetch: true -`, - expected: `git: - pagers: - - pager: delta --dark --paging=never - autoFetch: true -`, - expectedDidChange: true, - expectedChanges: []string{"Moved git.paging object to git.pagers array"}, - }, - { - name: "paging is moved to pagers array even if empty", - input: `git: - paging: {} -`, - expected: `git: - pagers: [{}] -`, - expectedDidChange: true, - expectedChanges: []string{"Moved git.paging object to git.pagers array"}, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - changes := NewChangesSet() - actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes) - assert.NoError(t, err) - assert.Equal(t, s.expectedDidChange, didChange) - if didChange { - assert.Equal(t, s.expected, string(actual)) - } - assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest()) - }) - } -} diff --git a/pkg/config/config_default_platform.go b/pkg/config/config_default_platform.go deleted file mode 100644 index 9375e18512d..00000000000 --- a/pkg/config/config_default_platform.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !windows && !linux - -package config - -// GetPlatformDefaultConfig gets the defaults for the platform -func GetPlatformDefaultConfig() OSConfig { - return OSConfig{ - Open: "open -- {{filename}}", - OpenLink: "open {{link}}", - } -} diff --git a/pkg/config/config_linux.go b/pkg/config/config_linux.go deleted file mode 100644 index aa3793901fc..00000000000 --- a/pkg/config/config_linux.go +++ /dev/null @@ -1,33 +0,0 @@ -package config - -import ( - "os" - "strings" -) - -func isWSL() bool { - data, err := os.ReadFile("/proc/sys/kernel/osrelease") - return err == nil && strings.Contains(string(data), "microsoft") -} - -func isContainer() bool { - data, err := os.ReadFile("/proc/1/cgroup") - return err == nil && (strings.Contains(string(data), "docker") || - strings.Contains(string(data), "/lxc/") || - os.Getenv("CONTAINER") != "") -} - -// GetPlatformDefaultConfig gets the defaults for the platform -func GetPlatformDefaultConfig() OSConfig { - if isWSL() && !isContainer() { - return OSConfig{ - Open: `powershell.exe start explorer.exe "$(wslpath -w {{filename}})" >/dev/null`, - OpenLink: `powershell.exe start '{{link}}' >/dev/null`, - } - } - - return OSConfig{ - Open: `xdg-open {{filename}} >/dev/null`, - OpenLink: `xdg-open {{link}} >/dev/null`, - } -} diff --git a/pkg/config/config_windows.go b/pkg/config/config_windows.go deleted file mode 100644 index 96a810b501b..00000000000 --- a/pkg/config/config_windows.go +++ /dev/null @@ -1,9 +0,0 @@ -package config - -// GetPlatformDefaultConfig gets the defaults for the platform -func GetPlatformDefaultConfig() OSConfig { - return OSConfig{ - Open: `start "" {{filename}}`, - OpenLink: `start "" {{link}}`, - } -} diff --git a/pkg/config/dummies.go b/pkg/config/dummies.go deleted file mode 100644 index 06c8755a61e..00000000000 --- a/pkg/config/dummies.go +++ /dev/null @@ -1,18 +0,0 @@ -package config - -import ( - "gopkg.in/yaml.v3" -) - -// NewDummyAppConfig creates a new dummy AppConfig for testing -func NewDummyAppConfig() *AppConfig { - appConfig := &AppConfig{ - name: "lazygit", - version: "unversioned", - debug: false, - userConfig: GetDefaultConfig(), - appState: &AppState{}, - } - _ = yaml.Unmarshal([]byte{}, appConfig.appState) - return appConfig -} diff --git a/pkg/config/editor_presets.go b/pkg/config/editor_presets.go deleted file mode 100644 index 42d9a4c0af3..00000000000 --- a/pkg/config/editor_presets.go +++ /dev/null @@ -1,194 +0,0 @@ -package config - -import ( - "os" - "strings" -) - -func GetEditTemplate(shell string, osConfig *OSConfig, guessDefaultEditor func() string) (string, bool) { - preset := getPreset(shell, osConfig, guessDefaultEditor) - template := osConfig.Edit - if template == "" { - template = preset.editTemplate - } - - return template, getEditInTerminal(osConfig, preset) -} - -func GetEditAtLineTemplate(shell string, osConfig *OSConfig, guessDefaultEditor func() string) (string, bool) { - preset := getPreset(shell, osConfig, guessDefaultEditor) - template := osConfig.EditAtLine - if template == "" { - template = preset.editAtLineTemplate - } - return template, getEditInTerminal(osConfig, preset) -} - -func GetEditAtLineAndWaitTemplate(shell string, osConfig *OSConfig, guessDefaultEditor func() string) string { - preset := getPreset(shell, osConfig, guessDefaultEditor) - template := osConfig.EditAtLineAndWait - if template == "" { - template = preset.editAtLineAndWaitTemplate - } - return template -} - -func GetOpenDirInEditorTemplate(shell string, osConfig *OSConfig, guessDefaultEditor func() string) (string, bool) { - preset := getPreset(shell, osConfig, guessDefaultEditor) - template := osConfig.OpenDirInEditor - if template == "" { - template = preset.openDirInEditorTemplate - } - return template, getEditInTerminal(osConfig, preset) -} - -type editPreset struct { - editTemplate string - editAtLineTemplate string - editAtLineAndWaitTemplate string - openDirInEditorTemplate string - suspend func() bool -} - -func returnBool(a bool) func() bool { return (func() bool { return a }) } - -// IF YOU ADD A PRESET TO THIS FUNCTION YOU MUST UPDATE THE `Supported presets` SECTION OF docs/Config.md -func getPreset(shell string, osConfig *OSConfig, guessDefaultEditor func() string) *editPreset { - var nvimRemoteEditTemplate, nvimRemoteEditAtLineTemplate, nvimRemoteOpenDirInEditorTemplate string - // By default fish doesn't have SHELL variable set, but it does have FISH_VERSION since Nov 2012. - if (strings.HasSuffix(shell, "fish")) || (os.Getenv("FISH_VERSION") != "") { - nvimRemoteEditTemplate = `begin; if test -z "$NVIM"; nvim -- {{filename}}; else; nvim --server "$NVIM" --remote-send "q"; nvim --server "$NVIM" --remote-tab {{filename}}; end; end` - nvimRemoteEditAtLineTemplate = `begin; if test -z "$NVIM"; nvim +{{line}} -- {{filename}}; else; nvim --server "$NVIM" --remote-send "q"; nvim --server "$NVIM" --remote-tab {{filename}}; nvim --server "$NVIM" --remote-send ":{{line}}"; end; end` - nvimRemoteOpenDirInEditorTemplate = `begin; if test -z "$NVIM"; nvim -- {{dir}}; else; nvim --server "$NVIM" --remote-send "q"; nvim --server "$NVIM" --remote-tab {{dir}}; end; end` - } else { - nvimRemoteEditTemplate = `[ -z "$NVIM" ] && (nvim -- {{filename}}) || (nvim --server "$NVIM" --remote-send "q" && nvim --server "$NVIM" --remote-tab {{filename}})` - nvimRemoteEditAtLineTemplate = `[ -z "$NVIM" ] && (nvim +{{line}} -- {{filename}}) || (nvim --server "$NVIM" --remote-send "q" && nvim --server "$NVIM" --remote-tab {{filename}} && nvim --server "$NVIM" --remote-send ":{{line}}")` - nvimRemoteOpenDirInEditorTemplate = `[ -z "$NVIM" ] && (nvim -- {{dir}}) || (nvim --server "$NVIM" --remote-send "q" && nvim --server "$NVIM" --remote-tab {{dir}})` - } - presets := map[string]*editPreset{ - "vi": standardTerminalEditorPreset("vi"), - "vim": standardTerminalEditorPreset("vim"), - "nvim": standardTerminalEditorPreset("nvim"), - "nvim-remote": { - editTemplate: nvimRemoteEditTemplate, - editAtLineTemplate: nvimRemoteEditAtLineTemplate, - // No remote-wait support yet. See https://github.com/neovim/neovim/pull/17856 - editAtLineAndWaitTemplate: `nvim +{{line}} {{filename}}`, - openDirInEditorTemplate: nvimRemoteOpenDirInEditorTemplate, - suspend: func() bool { - _, ok := os.LookupEnv("NVIM") - return !ok - }, - }, - "lvim": standardTerminalEditorPreset("lvim"), - "emacs": standardTerminalEditorPreset("emacs"), - "micro": { - editTemplate: "micro {{filename}}", - editAtLineTemplate: "micro +{{line}} {{filename}}", - editAtLineAndWaitTemplate: "micro +{{line}} {{filename}}", - openDirInEditorTemplate: "micro {{dir}}", - suspend: returnBool(true), - }, - "nano": standardTerminalEditorPreset("nano"), - "kakoune": standardTerminalEditorPreset("kak"), - "helix": { - editTemplate: "helix -- {{filename}}", - editAtLineTemplate: "helix -- {{filename}}:{{line}}", - editAtLineAndWaitTemplate: "helix -- {{filename}}:{{line}}", - openDirInEditorTemplate: "helix -- {{dir}}", - suspend: returnBool(true), - }, - "helix (hx)": { - editTemplate: "hx -- {{filename}}", - editAtLineTemplate: "hx -- {{filename}}:{{line}}", - editAtLineAndWaitTemplate: "hx -- {{filename}}:{{line}}", - openDirInEditorTemplate: "hx -- {{dir}}", - suspend: returnBool(true), - }, - "vscode": { - editTemplate: "code --reuse-window -- {{filename}}", - editAtLineTemplate: "code --reuse-window --goto -- {{filename}}:{{line}}", - editAtLineAndWaitTemplate: "code --reuse-window --goto --wait -- {{filename}}:{{line}}", - openDirInEditorTemplate: "code -- {{dir}}", - suspend: returnBool(false), - }, - "sublime": { - editTemplate: "subl -- {{filename}}", - editAtLineTemplate: "subl -- {{filename}}:{{line}}", - editAtLineAndWaitTemplate: "subl --wait -- {{filename}}:{{line}}", - openDirInEditorTemplate: "subl -- {{dir}}", - suspend: returnBool(false), - }, - "bbedit": { - editTemplate: "bbedit -- {{filename}}", - editAtLineTemplate: "bbedit +{{line}} -- {{filename}}", - editAtLineAndWaitTemplate: "bbedit +{{line}} --wait -- {{filename}}", - openDirInEditorTemplate: "bbedit -- {{dir}}", - suspend: returnBool(false), - }, - "xcode": { - editTemplate: "xed -- {{filename}}", - editAtLineTemplate: "xed --line {{line}} -- {{filename}}", - editAtLineAndWaitTemplate: "xed --line {{line}} --wait -- {{filename}}", - openDirInEditorTemplate: "xed -- {{dir}}", - suspend: returnBool(false), - }, - "zed": { - editTemplate: "zed -- {{filename}}", - editAtLineTemplate: "zed -- {{filename}}:{{line}}", - editAtLineAndWaitTemplate: "zed --wait -- {{filename}}:{{line}}", - openDirInEditorTemplate: "zed -- {{dir}}", - suspend: returnBool(false), - }, - "acme": { - editTemplate: "B {{filename}}", - editAtLineTemplate: "B {{filename}}:{{line}}", - editAtLineAndWaitTemplate: "E {{filename}}:{{line}}", - openDirInEditorTemplate: "B {{dir}}", - suspend: returnBool(false), - }, - } - - // Some of our presets have a different name than the editor they are using. - editorToPreset := map[string]string{ - "kak": "kakoune", - "helix": "helix", - "hx": "helix (hx)", - "code": "vscode", - "subl": "sublime", - "xed": "xcode", - } - - presetName := osConfig.EditPreset - if presetName == "" { - defaultEditor := guessDefaultEditor() - if presets[defaultEditor] != nil { - presetName = defaultEditor - } else if p := editorToPreset[defaultEditor]; p != "" { - presetName = p - } - } - - if presetName == "" || presets[presetName] == nil { - presetName = "vim" - } - - return presets[presetName] -} - -func standardTerminalEditorPreset(editor string) *editPreset { - return &editPreset{ - editTemplate: editor + " -- {{filename}}", - editAtLineTemplate: editor + " +{{line}} -- {{filename}}", - editAtLineAndWaitTemplate: editor + " +{{line}} -- {{filename}}", - openDirInEditorTemplate: editor + " -- {{dir}}", - suspend: returnBool(true), - } -} - -func getEditInTerminal(osConfig *OSConfig, preset *editPreset) bool { - if osConfig.SuspendOnEdit != nil { - return *osConfig.SuspendOnEdit - } - return preset.suspend() -} diff --git a/pkg/config/editor_presets_test.go b/pkg/config/editor_presets_test.go deleted file mode 100644 index d7c56965a46..00000000000 --- a/pkg/config/editor_presets_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package config - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetEditTemplate(t *testing.T) { - trueVal := true - - scenarios := []struct { - name string - osConfig *OSConfig - guessDefaultEditor func() string - expectedEditTemplate string - expectedEditAtLineTemplate string - expectedEditAtLineAndWaitTemplate string - expectedSuspend bool - }{ - { - "Default template is vim", - &OSConfig{}, - func() string { return "" }, - "vim -- {{filename}}", - "vim +{{line}} -- {{filename}}", - "vim +{{line}} -- {{filename}}", - true, - }, - { - "Setting a preset", - &OSConfig{ - EditPreset: "vscode", - }, - func() string { return "" }, - "code --reuse-window -- {{filename}}", - "code --reuse-window --goto -- {{filename}}:{{line}}", - "code --reuse-window --goto --wait -- {{filename}}:{{line}}", - false, - }, - { - "Setting a preset wins over guessed editor", - &OSConfig{ - EditPreset: "vscode", - }, - func() string { return "nano" }, - "code --reuse-window -- {{filename}}", - "code --reuse-window --goto -- {{filename}}:{{line}}", - "code --reuse-window --goto --wait -- {{filename}}:{{line}}", - false, - }, - { - "Overriding a preset with explicit config (edit)", - &OSConfig{ - EditPreset: "vscode", - Edit: "myeditor {{filename}}", - SuspendOnEdit: &trueVal, - }, - func() string { return "" }, - "myeditor {{filename}}", - "code --reuse-window --goto -- {{filename}}:{{line}}", - "code --reuse-window --goto --wait -- {{filename}}:{{line}}", - true, - }, - { - "Overriding a preset with explicit config (edit at line)", - &OSConfig{ - EditPreset: "vscode", - EditAtLine: "myeditor --line={{line}} {{filename}}", - SuspendOnEdit: &trueVal, - }, - func() string { return "" }, - "code --reuse-window -- {{filename}}", - "myeditor --line={{line}} {{filename}}", - "code --reuse-window --goto --wait -- {{filename}}:{{line}}", - true, - }, - { - "Overriding a preset with explicit config (edit at line and wait)", - &OSConfig{ - EditPreset: "vscode", - EditAtLineAndWait: "myeditor --line={{line}} -w {{filename}}", - SuspendOnEdit: &trueVal, - }, - func() string { return "" }, - "code --reuse-window -- {{filename}}", - "code --reuse-window --goto -- {{filename}}:{{line}}", - "myeditor --line={{line}} -w {{filename}}", - true, - }, - { - "Unknown preset name", - &OSConfig{ - EditPreset: "thisPresetDoesNotExist", - }, - func() string { return "" }, - "vim -- {{filename}}", - "vim +{{line}} -- {{filename}}", - "vim +{{line}} -- {{filename}}", - true, - }, - { - "Guessing a preset from guessed editor", - &OSConfig{}, - func() string { return "emacs" }, - "emacs -- {{filename}}", - "emacs +{{line}} -- {{filename}}", - "emacs +{{line}} -- {{filename}}", - true, - }, - } - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - template, suspend := GetEditTemplate("bash", s.osConfig, s.guessDefaultEditor) - assert.Equal(t, s.expectedEditTemplate, template) - assert.Equal(t, s.expectedSuspend, suspend) - - template, suspend = GetEditAtLineTemplate("bash", s.osConfig, s.guessDefaultEditor) - assert.Equal(t, s.expectedEditAtLineTemplate, template) - assert.Equal(t, s.expectedSuspend, suspend) - - template = GetEditAtLineAndWaitTemplate("bash", s.osConfig, s.guessDefaultEditor) - assert.Equal(t, s.expectedEditAtLineAndWaitTemplate, template) - }) - } -} diff --git a/pkg/config/keynames.go b/pkg/config/keynames.go deleted file mode 100644 index bb9756b4387..00000000000 --- a/pkg/config/keynames.go +++ /dev/null @@ -1,93 +0,0 @@ -package config - -import ( - "strings" - "unicode/utf8" - - "github.com/jesseduffield/gocui" - "github.com/samber/lo" -) - -// NOTE: if you make changes to this table, be sure to update -// docs/keybindings/Custom_Keybindings.md as well - -var LabelByKey = map[gocui.Key]string{ - gocui.KeyF1: "", - gocui.KeyF2: "", - gocui.KeyF3: "", - gocui.KeyF4: "", - gocui.KeyF5: "", - gocui.KeyF6: "", - gocui.KeyF7: "", - gocui.KeyF8: "", - gocui.KeyF9: "", - gocui.KeyF10: "", - gocui.KeyF11: "", - gocui.KeyF12: "", - gocui.KeyInsert: "", - gocui.KeyDelete: "", - gocui.KeyHome: "", - gocui.KeyEnd: "", - gocui.KeyPgup: "", - gocui.KeyPgdn: "", - gocui.KeyArrowUp: "", - gocui.KeyShiftArrowUp: "", - gocui.KeyArrowDown: "", - gocui.KeyShiftArrowDown: "", - gocui.KeyArrowLeft: "", - gocui.KeyArrowRight: "", - gocui.KeyTab: "", // - gocui.KeyBacktab: "", - gocui.KeyEnter: "", // - gocui.KeyAltEnter: "", - gocui.KeyEsc: "", // , - gocui.KeyBackspace: "", // - gocui.KeyCtrlSpace: "", // , - gocui.KeyCtrlSlash: "", // - gocui.KeySpace: "", - gocui.KeyCtrlA: "", - gocui.KeyCtrlB: "", - gocui.KeyCtrlC: "", - gocui.KeyCtrlD: "", - gocui.KeyCtrlE: "", - gocui.KeyCtrlF: "", - gocui.KeyCtrlG: "", - gocui.KeyCtrlJ: "", - gocui.KeyCtrlK: "", - gocui.KeyCtrlL: "", - gocui.KeyCtrlN: "", - gocui.KeyCtrlO: "", - gocui.KeyCtrlP: "", - gocui.KeyCtrlQ: "", - gocui.KeyCtrlR: "", - gocui.KeyCtrlS: "", - gocui.KeyCtrlT: "", - gocui.KeyCtrlU: "", - gocui.KeyCtrlV: "", - gocui.KeyCtrlW: "", - gocui.KeyCtrlX: "", - gocui.KeyCtrlY: "", - gocui.KeyCtrlZ: "", - gocui.KeyCtrl4: "", // - gocui.KeyCtrl5: "", // - gocui.KeyCtrl6: "", - gocui.KeyCtrl8: "", - gocui.MouseWheelUp: "mouse wheel up", - gocui.MouseWheelDown: "mouse wheel down", -} - -var KeyByLabel = lo.Invert(LabelByKey) - -func isValidKeybindingKey(key string) bool { - runeCount := utf8.RuneCountInString(key) - if key == "" { - return true - } - - if runeCount > 1 { - _, ok := KeyByLabel[strings.ToLower(key)] - return ok - } - - return true -} diff --git a/pkg/config/pager_config.go b/pkg/config/pager_config.go deleted file mode 100644 index e721da0e8f9..00000000000 --- a/pkg/config/pager_config.go +++ /dev/null @@ -1,82 +0,0 @@ -package config - -import ( - "strconv" - - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type PagerConfig struct { - getUserConfig func() *UserConfig - pagerIndex int -} - -func NewPagerConfig(getUserConfig func() *UserConfig) *PagerConfig { - return &PagerConfig{getUserConfig: getUserConfig} -} - -func (self *PagerConfig) currentPagerConfig() *PagingConfig { - pagers := self.getUserConfig().Git.Pagers - if len(pagers) == 0 { - return nil - } - - // Guard against the pager index being out of range, which can happen if the user - // has removed pagers from their config file while lazygit is running. - if self.pagerIndex >= len(pagers) { - self.pagerIndex = 0 - } - - return &pagers[self.pagerIndex] -} - -func (self *PagerConfig) GetPagerCommand(width int) string { - currentPagerConfig := self.currentPagerConfig() - if currentPagerConfig == nil { - return "" - } - - templateValues := map[string]string{ - "columnWidth": strconv.Itoa(width/2 - 6), - } - - pagerTemplate := string(currentPagerConfig.Pager) - return utils.ResolvePlaceholderString(pagerTemplate, templateValues) -} - -func (self *PagerConfig) GetColorArg() string { - currentPagerConfig := self.currentPagerConfig() - if currentPagerConfig == nil { - return "always" - } - - colorArg := currentPagerConfig.ColorArg - if colorArg == "" { - return "always" - } - return colorArg -} - -func (self *PagerConfig) GetExternalDiffCommand() string { - currentPagerConfig := self.currentPagerConfig() - if currentPagerConfig == nil { - return "" - } - return currentPagerConfig.ExternalDiffCommand -} - -func (self *PagerConfig) GetUseExternalDiffGitConfig() bool { - currentPagerConfig := self.currentPagerConfig() - if currentPagerConfig == nil { - return false - } - return currentPagerConfig.UseExternalDiffGitConfig -} - -func (self *PagerConfig) CyclePagers() { - self.pagerIndex = (self.pagerIndex + 1) % len(self.getUserConfig().Git.Pagers) -} - -func (self *PagerConfig) CurrentPagerIndex() (int, int) { - return self.pagerIndex, len(self.getUserConfig().Git.Pagers) -} diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go deleted file mode 100644 index f0b41603ce2..00000000000 --- a/pkg/config/user_config.go +++ /dev/null @@ -1,1047 +0,0 @@ -package config - -import ( - "time" - - "github.com/karimkhaleel/jsonschema" -) - -type UserConfig struct { - // Config relating to the Lazygit UI - Gui GuiConfig `yaml:"gui"` - // Config relating to git - Git GitConfig `yaml:"git"` - // Periodic update checks - Update UpdateConfig `yaml:"update"` - // Background refreshes - Refresher RefresherConfig `yaml:"refresher"` - // If true, show a confirmation popup before quitting Lazygit - ConfirmOnQuit bool `yaml:"confirmOnQuit"` - // If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close - QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"` - // Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc - OS OSConfig `yaml:"os,omitempty"` - // If true, don't display introductory popups upon opening Lazygit. - DisableStartupPopups bool `yaml:"disableStartupPopups"` - // User-configured commands that can be invoked from within Lazygit - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Command_Keybindings.md - CustomCommands []CustomCommand `yaml:"customCommands" jsonschema:"uniqueItems=true"` - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-pull-request-urls - Services map[string]string `yaml:"services"` - // What to do when opening Lazygit outside of a git repo. - // - 'prompt': (default) ask whether to initialize a new repo or open in the most recent repo - // - 'create': initialize a new repo - // - 'skip': open most recent repo - // - 'quit': exit Lazygit - NotARepository string `yaml:"notARepository" jsonschema:"enum=prompt,enum=create,enum=skip,enum=quit"` - // If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit. - PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"` - // Keybindings - Keybinding KeybindingConfig `yaml:"keybinding"` -} - -type RefresherConfig struct { - // File/submodule refresh interval in seconds. - // Auto-refresh can be disabled via option 'git.autoRefresh'. - RefreshInterval int `yaml:"refreshInterval" jsonschema:"minimum=0"` - // Re-fetch interval in seconds. - // Auto-fetch can be disabled via option 'git.autoFetch'. - FetchInterval int `yaml:"fetchInterval" jsonschema:"minimum=0"` -} - -type GuiConfig struct { - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color - AuthorColors map[string]string `yaml:"authorColors"` - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color - // Deprecated: use branchColorPatterns instead - BranchColors map[string]string `yaml:"branchColors" jsonschema:"deprecated"` - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color - BranchColorPatterns map[string]string `yaml:"branchColorPatterns"` - // Custom icons for filenames and file extensions - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-files-icon--color - CustomIcons CustomIconsConfig `yaml:"customIcons"` - // The number of lines you scroll by when scrolling the main window - ScrollHeight int `yaml:"scrollHeight" jsonschema:"minimum=1"` - // If true, allow scrolling past the bottom of the content in the main window - ScrollPastBottom bool `yaml:"scrollPastBottom"` - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#scroll-off-margin - ScrollOffMargin int `yaml:"scrollOffMargin"` - // One of: 'margin' (default) | 'jump' - ScrollOffBehavior string `yaml:"scrollOffBehavior"` - // The number of spaces per tab; used for everything that's shown in the main view, but probably mostly relevant for diffs. - // Note that when using a pager, the pager has its own tab width setting, so you need to pass it separately in the pager command. - TabWidth int `yaml:"tabWidth" jsonschema:"minimum=1"` - // If true, capture mouse events. - // When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS. - MouseEvents bool `yaml:"mouseEvents"` - // If true, do not show a warning when amending a commit. - SkipAmendWarning bool `yaml:"skipAmendWarning"` - // If true, do not show a warning when discarding changes in the staging view. - SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"` - // If true, do not show warning when applying/popping the stash - SkipStashWarning bool `yaml:"skipStashWarning"` - // If true, do not show a warning when attempting to commit without any staged files; instead stage all unstaged files. - SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"` - // If true, do not show a warning when rewording a commit via an external editor - SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"` - // If true, switch to a different worktree without confirmation when checking out a branch that is checked out in that worktree - SkipSwitchWorktreeOnCheckoutWarning bool `yaml:"skipSwitchWorktreeOnCheckoutWarning"` - // Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section. - // Number from 0 to 1.0. - SidePanelWidth float64 `yaml:"sidePanelWidth" jsonschema:"maximum=1,minimum=0"` - // If true, increase the height of the focused side window; creating an accordion effect. - ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"` - // The weight of the expanded side panel, relative to the other panels. 2 means twice as tall as the other panels. Only relevant if `expandFocusedSidePanel` is true. - ExpandedSidePanelWeight int `yaml:"expandedSidePanelWeight"` - // Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split. - // Options are: - // - 'horizontal': split the window horizontally - // - 'vertical': split the window vertically - // - 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically - MainPanelSplitMode string `yaml:"mainPanelSplitMode" jsonschema:"enum=horizontal,enum=flexible,enum=vertical"` - // How the window is split when in half screen mode (i.e. after hitting '+' once). - // Possible values: - // - 'left': split the window horizontally (side panel on the left, main view on the right) - // - 'top': split the window vertically (side panel on top, main view below) - EnlargedSideViewLocation string `yaml:"enlargedSideViewLocation"` - // If true, wrap lines in the staging view to the width of the view. This makes it much easier to work with diffs that have long lines, e.g. paragraphs of markdown text. - WrapLinesInStagingView bool `yaml:"wrapLinesInStagingView"` - // If true, hunk selection mode will be enabled by default when entering the staging view. - UseHunkModeInStagingView bool `yaml:"useHunkModeInStagingView"` - // One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru' | 'pt' - Language string `yaml:"language" jsonschema:"enum=auto,enum=en,enum=zh-TW,enum=zh-CN,enum=pl,enum=nl,enum=ja,enum=ko,enum=ru"` - // Format used when displaying time e.g. commit time. - // Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format - TimeFormat string `yaml:"timeFormat"` - // Format used when displaying time if the time is less than 24 hours ago. - // Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format - ShortTimeFormat string `yaml:"shortTimeFormat"` - // Config relating to colors and styles. - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes - Theme ThemeConfig `yaml:"theme"` - // Config relating to the commit length indicator - CommitLength CommitLengthConfig `yaml:"commitLength"` - // If true, show the '5 of 20' footer at the bottom of list views - ShowListFooter bool `yaml:"showListFooter"` - // If true, display the files in the file views as a tree. If false, display the files as a flat list. - // This can be toggled from within Lazygit with the '`' key, but that will not change the default. - ShowFileTree bool `yaml:"showFileTree"` - // If true, add a "/" root item in the file tree representing the root of the repository. It is only added when necessary, i.e. when there is more than one item at top level. - ShowRootItemInFileTree bool `yaml:"showRootItemInFileTree"` - // If true, show the number of lines changed per file in the Files view - ShowNumstatInFilesView bool `yaml:"showNumstatInFilesView"` - // If true, show a random tip in the command log when Lazygit starts - ShowRandomTip bool `yaml:"showRandomTip"` - // If true, show the command log - ShowCommandLog bool `yaml:"showCommandLog"` - // If true, show the bottom line that contains keybinding info and useful buttons. If false, this line will be hidden except to display a loader for an in-progress action. - ShowBottomLine bool `yaml:"showBottomLine"` - // If true, show jump-to-window keybindings in window titles. - ShowPanelJumps bool `yaml:"showPanelJumps"` - // Deprecated: use nerdFontsVersion instead - ShowIcons bool `yaml:"showIcons" jsonschema:"deprecated"` - // Nerd fonts version to use. - // One of: '2' | '3' | empty string (default) - // If empty, do not show icons. - NerdFontsVersion string `yaml:"nerdFontsVersion" jsonschema:"enum=2,enum=3,enum="` - // If true (default), file icons are shown in the file views. Only relevant if NerdFontsVersion is not empty. - ShowFileIcons bool `yaml:"showFileIcons"` - // Length of author name in (non-expanded) commits view. 2 means show initials only. - CommitAuthorShortLength int `yaml:"commitAuthorShortLength"` - // Length of author name in expanded commits view. 2 means show initials only. - CommitAuthorLongLength int `yaml:"commitAuthorLongLength"` - // Length of commit hash in commits view. 0 shows '*' if NF icons aren't on. - CommitHashLength int `yaml:"commitHashLength" jsonschema:"minimum=0"` - // If true, show commit hashes alongside branch names in the branches view. - ShowBranchCommitHash bool `yaml:"showBranchCommitHash"` - // Whether to show the divergence from the base branch in the branches view. - // One of: 'none' | 'onlyArrow' | 'arrowAndNumber' - ShowDivergenceFromBaseBranch string `yaml:"showDivergenceFromBaseBranch" jsonschema:"enum=none,enum=onlyArrow,enum=arrowAndNumber"` - // Height of the command log view - CommandLogSize int `yaml:"commandLogSize" jsonschema:"minimum=0"` - // Whether to split the main window when viewing file changes. - // One of: 'auto' | 'always' - // If 'auto', only split the main window when a file has both staged and unstaged changes - SplitDiff string `yaml:"splitDiff" jsonschema:"enum=auto,enum=always"` - // Default size for focused window. Can be changed from within Lazygit with '+' and '_' (but this won't change the default). - // One of: 'normal' (default) | 'half' | 'full' - ScreenMode string `yaml:"screenMode" jsonschema:"enum=normal,enum=half,enum=full"` - // Window border style. - // One of 'rounded' (default) | 'single' | 'double' | 'hidden' | 'bold' - Border string `yaml:"border" jsonschema:"enum=single,enum=double,enum=rounded,enum=hidden,enum=bold"` - // If true, show a seriously epic explosion animation when nuking the working tree. - AnimateExplosion bool `yaml:"animateExplosion"` - // Whether to stack UI components on top of each other. - // One of 'auto' (default) | 'always' | 'never' - PortraitMode string `yaml:"portraitMode"` - // How things are filtered when typing '/'. - // One of 'substring' (default) | 'fuzzy' - FilterMode string `yaml:"filterMode" jsonschema:"enum=substring,enum=fuzzy"` - // Config relating to the spinner. - Spinner SpinnerConfig `yaml:"spinner"` - // Status panel view. - // One of 'dashboard' (default) | 'allBranchesLog' - StatusPanelView string `yaml:"statusPanelView" jsonschema:"enum=dashboard,enum=allBranchesLog"` - // If true, jump to the Files panel after popping a stash - SwitchToFilesAfterStashPop bool `yaml:"switchToFilesAfterStashPop"` - // If true, jump to the Files panel after applying a stash - SwitchToFilesAfterStashApply bool `yaml:"switchToFilesAfterStashApply"` - // If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead - SwitchTabsWithPanelJumpKeys bool `yaml:"switchTabsWithPanelJumpKeys"` -} - -func (c *GuiConfig) UseFuzzySearch() bool { - return c.FilterMode == "fuzzy" -} - -type ThemeConfig struct { - // Border color of focused window - ActiveBorderColor []string `yaml:"activeBorderColor" jsonschema:"minItems=1,uniqueItems=true"` - // Border color of non-focused windows - InactiveBorderColor []string `yaml:"inactiveBorderColor" jsonschema:"minItems=1,uniqueItems=true"` - // Border color of focused window when searching in that window - SearchingActiveBorderColor []string `yaml:"searchingActiveBorderColor" jsonschema:"minItems=1,uniqueItems=true"` - // Color of keybindings help text in the bottom line - OptionsTextColor []string `yaml:"optionsTextColor" jsonschema:"minItems=1,uniqueItems=true"` - // Background color of selected line. - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line - SelectedLineBgColor []string `yaml:"selectedLineBgColor" jsonschema:"minItems=1,uniqueItems=true"` - // Background color of selected line when view doesn't have focus. - InactiveViewSelectedLineBgColor []string `yaml:"inactiveViewSelectedLineBgColor" jsonschema:"minItems=1,uniqueItems=true"` - // Foreground color of copied commit - CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor" jsonschema:"minItems=1,uniqueItems=true"` - // Background color of copied commit - CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor" jsonschema:"minItems=1,uniqueItems=true"` - // Foreground color of marked base commit (for rebase) - MarkedBaseCommitFgColor []string `yaml:"markedBaseCommitFgColor"` - // Background color of marked base commit (for rebase) - MarkedBaseCommitBgColor []string `yaml:"markedBaseCommitBgColor"` - // Color for file with unstaged changes - UnstagedChangesColor []string `yaml:"unstagedChangesColor" jsonschema:"minItems=1,uniqueItems=true"` - // Default text color - DefaultFgColor []string `yaml:"defaultFgColor" jsonschema:"minItems=1,uniqueItems=true"` -} - -type CommitLengthConfig struct { - // If true, show an indicator of commit message length - Show bool `yaml:"show"` -} - -type SpinnerConfig struct { - // The frames of the spinner animation. - Frames []string `yaml:"frames"` - // The "speed" of the spinner in milliseconds. - Rate int `yaml:"rate" jsonschema:"minimum=1"` -} - -type GitConfig struct { - // Array of pagers. Each entry has the following format: - // [dev] The following documentation is duplicated from the PagingConfig struct below. - // - // # Value of the --color arg in the git diff command. Some pagers want - // # this to be set to 'always' and some want it set to 'never' - // colorArg: "always" - // - // # e.g. - // # diff-so-fancy - // # delta --dark --paging=never - // # ydiff -p cat -s --wrap --width={{columnWidth}} - // pager: "" - // - // # e.g. 'difft --color=always' - // externalDiffCommand: "" - // - // # If true, Lazygit will use git's `diff.external` config for paging. - // # The advantage over `externalDiffCommand` is that this can be - // # configured per file type in .gitattributes; see - // # https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver. - // useExternalDiffGitConfig: false - // - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md for more information. - Pagers []PagingConfig `yaml:"pagers"` - // Config relating to committing - Commit CommitConfig `yaml:"commit"` - // Config relating to merging - Merging MergingConfig `yaml:"merging"` - // list of branches that are considered 'main' branches, used when displaying commits - MainBranches []string `yaml:"mainBranches" jsonschema:"uniqueItems=true"` - // Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP' - SkipHookPrefix string `yaml:"skipHookPrefix"` - // If true, periodically fetch from remote - AutoFetch bool `yaml:"autoFetch"` - // If true, periodically refresh files and submodules - AutoRefresh bool `yaml:"autoRefresh"` - // If not "none", lazygit will automatically fast-forward local branches to match their upstream after fetching. Applies to branches that are not the currently checked out branch, and only to those that are strictly behind their upstream (as opposed to diverged). - // Possible values: 'none' | 'onlyMainBranches' | 'allBranches' - AutoForwardBranches string `yaml:"autoForwardBranches" jsonschema:"enum=none,enum=onlyMainBranches,enum=allBranches"` - // If true, pass the --all arg to git fetch - FetchAll bool `yaml:"fetchAll"` - // If true, lazygit will automatically stage files that used to have merge conflicts but no longer do; and it will also ask you if you want to continue a merge or rebase if you've resolved all conflicts. If false, it won't do either of these things. - AutoStageResolvedConflicts bool `yaml:"autoStageResolvedConflicts"` - // Command used when displaying the current branch git log in the main window - BranchLogCmd string `yaml:"branchLogCmd"` - // Commands used to display git log of all branches in the main window, they will be cycled in order of appearance (array of strings) - AllBranchesLogCmds []string `yaml:"allBranchesLogCmds"` - // If true, git diffs are rendered with the `--ignore-all-space` flag, which ignores whitespace changes. Can be toggled from within Lazygit with ``. - IgnoreWhitespaceInDiffView bool `yaml:"ignoreWhitespaceInDiffView"` - // The number of lines of context to show around each diff hunk. Can be changed from within Lazygit with the `{` and `}` keys. - DiffContextSize uint64 `yaml:"diffContextSize"` - // The threshold for considering a file to be renamed, in percent. Can be changed from within Lazygit with the `(` and `)` keys. - RenameSimilarityThreshold int `yaml:"renameSimilarityThreshold" jsonschema:"minimum=0,maximum=100"` - // If true, do not spawn a separate process when using GPG - OverrideGpg bool `yaml:"overrideGpg"` - // If true, do not allow force pushes - DisableForcePushing bool `yaml:"disableForcePushing"` - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix - CommitPrefix []CommitPrefixConfig `yaml:"commitPrefix"` - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix - CommitPrefixes map[string][]CommitPrefixConfig `yaml:"commitPrefixes"` - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix - BranchPrefix string `yaml:"branchPrefix"` - // If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀 - // (This should really be under 'gui', not 'git') - ParseEmoji bool `yaml:"parseEmoji"` - // Config for showing the log in the commits view - Log LogConfig `yaml:"log"` - // How branches are sorted in the local branches view. - // One of: 'date' (default) | 'recency' | 'alphabetical' - // Can be changed from within Lazygit with the Sort Order menu (`s`) in the branches panel. - LocalBranchSortOrder string `yaml:"localBranchSortOrder" jsonschema:"enum=date,enum=recency,enum=alphabetical"` - // How branches are sorted in the remote branches view. - // One of: 'date' (default) | 'alphabetical' - // Can be changed from within Lazygit with the Sort Order menu (`s`) in the remote branches panel. - RemoteBranchSortOrder string `yaml:"remoteBranchSortOrder" jsonschema:"enum=date,enum=alphabetical"` - // When copying commit hashes to the clipboard, truncate them to this length. Set to 40 to disable truncation. - TruncateCopiedCommitHashesTo int `yaml:"truncateCopiedCommitHashesTo"` -} - -type PagerType string - -func (PagerType) JSONSchemaExtend(schema *jsonschema.Schema) { - schema.Examples = []any{ - "delta --dark --paging=never", - "diff-so-fancy", - "ydiff -p cat -s --wrap --width={{columnWidth}}", - } -} - -// [dev] This documentation is duplicated in the GitConfig struct. If you make changes here, make them there too. -type PagingConfig struct { - // Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never' - ColorArg string `yaml:"colorArg" jsonschema:"enum=always,enum=never"` - // e.g. - // diff-so-fancy - // delta --dark --paging=never - // ydiff -p cat -s --wrap --width={{columnWidth}} - Pager PagerType `yaml:"pager"` - // e.g. 'difft --color=always' - ExternalDiffCommand string `yaml:"externalDiffCommand"` - // If true, Lazygit will use git's `diff.external` config for paging. The advantage over `externalDiffCommand` is that this can be configured per file type in .gitattributes; see https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver. - UseExternalDiffGitConfig bool `yaml:"useExternalDiffGitConfig"` -} - -type CommitConfig struct { - // If true, pass '--signoff' flag when committing - SignOff bool `yaml:"signOff"` - // Automatic WYSIWYG wrapping of the commit message as you type - AutoWrapCommitMessage bool `yaml:"autoWrapCommitMessage"` - // If autoWrapCommitMessage is true, the width to wrap to - AutoWrapWidth int `yaml:"autoWrapWidth"` -} - -type MergingConfig struct { - // If true, run merges in a subprocess so that if a commit message is required, Lazygit will not hang - // Only applicable to unix users. - ManualCommit bool `yaml:"manualCommit"` - // Extra args passed to `git merge`, e.g. --no-ff - Args string `yaml:"args" jsonschema:"example=--no-ff"` - // The commit message to use for a squash merge commit. Can contain "{{selectedRef}}" and "{{currentBranch}}" placeholders. - SquashMergeMessage string `yaml:"squashMergeMessage"` -} - -type LogConfig struct { - // One of: 'date-order' | 'author-date-order' | 'topo-order' | 'default' - // 'topo-order' makes it easier to read the git log graph, but commits may not appear chronologically. See https://git-scm.com/docs/ - // - // Can be changed from within Lazygit with `Log menu -> Commit sort order` (`` in the commits window by default). - Order string `yaml:"order" jsonschema:"enum=date-order,enum=author-date-order,enum=topo-order,enum=default"` - // This determines whether the git graph is rendered in the commits panel - // One of 'always' | 'never' | 'when-maximised' - // - // Can be toggled from within lazygit with `Log menu -> Show git graph` (`` in the commits window by default). - ShowGraph string `yaml:"showGraph" jsonschema:"enum=always,enum=never,enum=when-maximised"` - // displays the whole git graph by default in the commits view (equivalent to passing the `--all` argument to `git log`) - ShowWholeGraph bool `yaml:"showWholeGraph"` -} - -type CommitPrefixConfig struct { - // pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*" - Pattern string `yaml:"pattern" jsonschema:"example=^\\w+\\/(\\w+-\\w+).*"` - // Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] " - Replace string `yaml:"replace" jsonschema:"example=[$1]"` -} - -type UpdateConfig struct { - // One of: 'prompt' (default) | 'background' | 'never' - Method string `yaml:"method" jsonschema:"enum=prompt,enum=background,enum=never"` - // Period in days between update checks - Days int64 `yaml:"days" jsonschema:"minimum=0"` -} - -type KeybindingConfig struct { - Universal KeybindingUniversalConfig `yaml:"universal"` - Status KeybindingStatusConfig `yaml:"status"` - Files KeybindingFilesConfig `yaml:"files"` - Branches KeybindingBranchesConfig `yaml:"branches"` - Worktrees KeybindingWorktreesConfig `yaml:"worktrees"` - Commits KeybindingCommitsConfig `yaml:"commits"` - AmendAttribute KeybindingAmendAttributeConfig `yaml:"amendAttribute"` - Stash KeybindingStashConfig `yaml:"stash"` - CommitFiles KeybindingCommitFilesConfig `yaml:"commitFiles"` - Main KeybindingMainConfig `yaml:"main"` - Submodules KeybindingSubmodulesConfig `yaml:"submodules"` - CommitMessage KeybindingCommitMessageConfig `yaml:"commitMessage"` -} - -// damn looks like we have some inconsistencies here with -alt and -alt1 -type KeybindingUniversalConfig struct { - Quit string `yaml:"quit"` - QuitAlt1 string `yaml:"quit-alt1"` - SuspendApp string `yaml:"suspendApp"` - Return string `yaml:"return"` - QuitWithoutChangingDirectory string `yaml:"quitWithoutChangingDirectory"` - TogglePanel string `yaml:"togglePanel"` - PrevItem string `yaml:"prevItem"` - NextItem string `yaml:"nextItem"` - PrevItemAlt string `yaml:"prevItem-alt"` - NextItemAlt string `yaml:"nextItem-alt"` - PrevPage string `yaml:"prevPage"` - NextPage string `yaml:"nextPage"` - ScrollLeft string `yaml:"scrollLeft"` - ScrollRight string `yaml:"scrollRight"` - GotoTop string `yaml:"gotoTop"` - GotoBottom string `yaml:"gotoBottom"` - GotoTopAlt string `yaml:"gotoTop-alt"` - GotoBottomAlt string `yaml:"gotoBottom-alt"` - ToggleRangeSelect string `yaml:"toggleRangeSelect"` - RangeSelectDown string `yaml:"rangeSelectDown"` - RangeSelectUp string `yaml:"rangeSelectUp"` - PrevBlock string `yaml:"prevBlock"` - NextBlock string `yaml:"nextBlock"` - PrevBlockAlt string `yaml:"prevBlock-alt"` - NextBlockAlt string `yaml:"nextBlock-alt"` - NextBlockAlt2 string `yaml:"nextBlock-alt2"` - PrevBlockAlt2 string `yaml:"prevBlock-alt2"` - JumpToBlock []string `yaml:"jumpToBlock"` - FocusMainView string `yaml:"focusMainView"` - NextMatch string `yaml:"nextMatch"` - PrevMatch string `yaml:"prevMatch"` - StartSearch string `yaml:"startSearch"` - OptionMenu string `yaml:"optionMenu"` - OptionMenuAlt1 string `yaml:"optionMenu-alt1"` - Select string `yaml:"select"` - GoInto string `yaml:"goInto"` - Confirm string `yaml:"confirm"` - ConfirmMenu string `yaml:"confirmMenu"` - ConfirmSuggestion string `yaml:"confirmSuggestion"` - ConfirmInEditor string `yaml:"confirmInEditor"` - ConfirmInEditorAlt string `yaml:"confirmInEditor-alt"` - Remove string `yaml:"remove"` - New string `yaml:"new"` - Edit string `yaml:"edit"` - OpenFile string `yaml:"openFile"` - ScrollUpMain string `yaml:"scrollUpMain"` - ScrollDownMain string `yaml:"scrollDownMain"` - ScrollUpMainAlt1 string `yaml:"scrollUpMain-alt1"` - ScrollDownMainAlt1 string `yaml:"scrollDownMain-alt1"` - ScrollUpMainAlt2 string `yaml:"scrollUpMain-alt2"` - ScrollDownMainAlt2 string `yaml:"scrollDownMain-alt2"` - ExecuteShellCommand string `yaml:"executeShellCommand"` - CreateRebaseOptionsMenu string `yaml:"createRebaseOptionsMenu"` - Push string `yaml:"pushFiles"` // 'Files' appended for legacy reasons - Pull string `yaml:"pullFiles"` // 'Files' appended for legacy reasons - Refresh string `yaml:"refresh"` - CreatePatchOptionsMenu string `yaml:"createPatchOptionsMenu"` - NextTab string `yaml:"nextTab"` - PrevTab string `yaml:"prevTab"` - NextScreenMode string `yaml:"nextScreenMode"` - PrevScreenMode string `yaml:"prevScreenMode"` - CyclePagers string `yaml:"cyclePagers"` - Undo string `yaml:"undo"` - Redo string `yaml:"redo"` - FilteringMenu string `yaml:"filteringMenu"` - DiffingMenu string `yaml:"diffingMenu"` - DiffingMenuAlt string `yaml:"diffingMenu-alt"` - CopyToClipboard string `yaml:"copyToClipboard"` - OpenRecentRepos string `yaml:"openRecentRepos"` - SubmitEditorText string `yaml:"submitEditorText"` - ExtrasMenu string `yaml:"extrasMenu"` - ToggleWhitespaceInDiffView string `yaml:"toggleWhitespaceInDiffView"` - IncreaseContextInDiffView string `yaml:"increaseContextInDiffView"` - DecreaseContextInDiffView string `yaml:"decreaseContextInDiffView"` - IncreaseRenameSimilarityThreshold string `yaml:"increaseRenameSimilarityThreshold"` - DecreaseRenameSimilarityThreshold string `yaml:"decreaseRenameSimilarityThreshold"` - OpenDiffTool string `yaml:"openDiffTool"` -} - -type KeybindingStatusConfig struct { - CheckForUpdate string `yaml:"checkForUpdate"` - RecentRepos string `yaml:"recentRepos"` - AllBranchesLogGraph string `yaml:"allBranchesLogGraph"` -} - -type KeybindingFilesConfig struct { - CommitChanges string `yaml:"commitChanges"` - CommitChangesWithoutHook string `yaml:"commitChangesWithoutHook"` - AmendLastCommit string `yaml:"amendLastCommit"` - CommitChangesWithEditor string `yaml:"commitChangesWithEditor"` - FindBaseCommitForFixup string `yaml:"findBaseCommitForFixup"` - ConfirmDiscard string `yaml:"confirmDiscard"` - IgnoreFile string `yaml:"ignoreFile"` - RefreshFiles string `yaml:"refreshFiles"` - StashAllChanges string `yaml:"stashAllChanges"` - ViewStashOptions string `yaml:"viewStashOptions"` - ToggleStagedAll string `yaml:"toggleStagedAll"` - ViewResetOptions string `yaml:"viewResetOptions"` - Fetch string `yaml:"fetch"` - ToggleTreeView string `yaml:"toggleTreeView"` - OpenMergeOptions string `yaml:"openMergeOptions"` - OpenStatusFilter string `yaml:"openStatusFilter"` - CopyFileInfoToClipboard string `yaml:"copyFileInfoToClipboard"` - CollapseAll string `yaml:"collapseAll"` - ExpandAll string `yaml:"expandAll"` -} - -type KeybindingBranchesConfig struct { - CreatePullRequest string `yaml:"createPullRequest"` - ViewPullRequestOptions string `yaml:"viewPullRequestOptions"` - CopyPullRequestURL string `yaml:"copyPullRequestURL"` - CheckoutBranchByName string `yaml:"checkoutBranchByName"` - ForceCheckoutBranch string `yaml:"forceCheckoutBranch"` - CheckoutPreviousBranch string `yaml:"checkoutPreviousBranch"` - RebaseBranch string `yaml:"rebaseBranch"` - RenameBranch string `yaml:"renameBranch"` - MergeIntoCurrentBranch string `yaml:"mergeIntoCurrentBranch"` - MoveCommitsToNewBranch string `yaml:"moveCommitsToNewBranch"` - ViewGitFlowOptions string `yaml:"viewGitFlowOptions"` - FastForward string `yaml:"fastForward"` - CreateTag string `yaml:"createTag"` - PushTag string `yaml:"pushTag"` - SetUpstream string `yaml:"setUpstream"` - FetchRemote string `yaml:"fetchRemote"` - SortOrder string `yaml:"sortOrder"` -} - -type KeybindingWorktreesConfig struct { - ViewWorktreeOptions string `yaml:"viewWorktreeOptions"` -} - -type KeybindingCommitsConfig struct { - SquashDown string `yaml:"squashDown"` - RenameCommit string `yaml:"renameCommit"` - RenameCommitWithEditor string `yaml:"renameCommitWithEditor"` - ViewResetOptions string `yaml:"viewResetOptions"` - MarkCommitAsFixup string `yaml:"markCommitAsFixup"` - CreateFixupCommit string `yaml:"createFixupCommit"` - SquashAboveCommits string `yaml:"squashAboveCommits"` - MoveDownCommit string `yaml:"moveDownCommit"` - MoveUpCommit string `yaml:"moveUpCommit"` - AmendToCommit string `yaml:"amendToCommit"` - ResetCommitAuthor string `yaml:"resetCommitAuthor"` - PickCommit string `yaml:"pickCommit"` - RevertCommit string `yaml:"revertCommit"` - CherryPickCopy string `yaml:"cherryPickCopy"` - PasteCommits string `yaml:"pasteCommits"` - MarkCommitAsBaseForRebase string `yaml:"markCommitAsBaseForRebase"` - CreateTag string `yaml:"tagCommit"` - CheckoutCommit string `yaml:"checkoutCommit"` - ResetCherryPick string `yaml:"resetCherryPick"` - CopyCommitAttributeToClipboard string `yaml:"copyCommitAttributeToClipboard"` - OpenLogMenu string `yaml:"openLogMenu"` - OpenInBrowser string `yaml:"openInBrowser"` - ViewBisectOptions string `yaml:"viewBisectOptions"` - StartInteractiveRebase string `yaml:"startInteractiveRebase"` - SelectCommitsOfCurrentBranch string `yaml:"selectCommitsOfCurrentBranch"` -} - -type KeybindingAmendAttributeConfig struct { - ResetAuthor string `yaml:"resetAuthor"` - SetAuthor string `yaml:"setAuthor"` - AddCoAuthor string `yaml:"addCoAuthor"` -} - -type KeybindingStashConfig struct { - PopStash string `yaml:"popStash"` - RenameStash string `yaml:"renameStash"` -} - -type KeybindingCommitFilesConfig struct { - CheckoutCommitFile string `yaml:"checkoutCommitFile"` -} - -type KeybindingMainConfig struct { - ToggleSelectHunk string `yaml:"toggleSelectHunk"` - PickBothHunks string `yaml:"pickBothHunks"` - EditSelectHunk string `yaml:"editSelectHunk"` -} - -type KeybindingSubmodulesConfig struct { - Init string `yaml:"init"` - Update string `yaml:"update"` - BulkMenu string `yaml:"bulkMenu"` -} - -type KeybindingCommitMessageConfig struct { - CommitMenu string `yaml:"commitMenu"` -} - -// OSConfig contains config on the level of the os -type OSConfig struct { - // Command for editing a file. Should contain "{{filename}}". - Edit string `yaml:"edit,omitempty"` - - // Command for editing a file at a given line number. Should contain "{{filename}}", and may optionally contain "{{line}}". - EditAtLine string `yaml:"editAtLine,omitempty"` - - // Same as EditAtLine, except that the command needs to wait until the window is closed. - EditAtLineAndWait string `yaml:"editAtLineAndWait,omitempty"` - - // Whether lazygit suspends until an edit process returns - // [dev] Pointer to bool so that we can distinguish unset (nil) from false. - // [dev] We're naming this `editInTerminal` for backwards compatibility - SuspendOnEdit *bool `yaml:"editInTerminal,omitempty"` - - // For opening a directory in an editor - OpenDirInEditor string `yaml:"openDirInEditor,omitempty"` - - // A built-in preset that sets all of the above settings. Supported presets are defined in the getPreset function in editor_presets.go. - EditPreset string `yaml:"editPreset,omitempty" jsonschema:"example=vim,example=nvim,example=emacs,example=nano,example=vscode,example=sublime,example=kakoune,example=helix,example=xcode,example=zed,example=acme"` - - // Command for opening a file, as if the file is double-clicked. Should contain "{{filename}}", but doesn't support "{{line}}". - Open string `yaml:"open,omitempty"` - - // Command for opening a link. Should contain "{{link}}". - OpenLink string `yaml:"openLink,omitempty"` - - // CopyToClipboardCmd is the command for copying to clipboard. - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard - CopyToClipboardCmd string `yaml:"copyToClipboardCmd,omitempty"` - - // ReadFromClipboardCmd is the command for reading the clipboard. - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard - ReadFromClipboardCmd string `yaml:"readFromClipboardCmd,omitempty"` - - // A shell startup file containing shell aliases or shell functions. This will be sourced before running any shell commands, so that shell functions are available in the `:` command prompt or even in custom commands. - // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#using-aliases-or-functions-in-shell-commands - ShellFunctionsFile string `yaml:"shellFunctionsFile"` -} - -type CustomCommandAfterHook struct { - CheckForConflicts bool `yaml:"checkForConflicts"` -} - -type CustomCommand struct { - // The key to trigger the command. Use a single letter or one of the values from https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md - Key string `yaml:"key"` - // Instead of defining a single custom command, create a menu of custom commands. Useful for grouping related commands together under a single keybinding, and for keeping them out of the global keybindings menu. - // When using this, all other fields except Key and Description are ignored and must be empty. - CommandMenu []CustomCommand `yaml:"commandMenu"` - // The context in which to listen for the key. Valid values are: status, files, worktrees, localBranches, remotes, remoteBranches, tags, commits, reflogCommits, subCommits, commitFiles, stash, and global. Multiple contexts separated by comma are allowed; most useful for "commits, subCommits" or "files, commitFiles". - Context string `yaml:"context" jsonschema:"example=status,example=files,example=worktrees,example=localBranches,example=remotes,example=remoteBranches,example=tags,example=commits,example=reflogCommits,example=subCommits,example=commitFiles,example=stash,example=global"` - // The command to run (using Go template syntax for placeholder values) - Command string `yaml:"command" jsonschema:"example=git fetch {{.Form.Remote}} {{.Form.Branch}} && git checkout FETCH_HEAD"` - // A list of prompts that will request user input before running the final command - Prompts []CustomCommandPrompt `yaml:"prompts"` - // Text to display while waiting for command to finish - LoadingText string `yaml:"loadingText" jsonschema:"example=Loading..."` - // Label for the custom command when displayed in the keybindings menu - Description string `yaml:"description"` - // Where the output of the command should go. 'none' discards it, 'terminal' suspends lazygit and runs the command in the terminal (useful for commands that require user input), 'log' streams it to the command log, 'logWithPty' is like 'log' but runs the command in a pseudo terminal (can be useful for commands that produce colored output when the output is a terminal), and 'popup' shows it in a popup. - Output string `yaml:"output" jsonschema:"enum=none,enum=terminal,enum=log,enum=logWithPty,enum=popup"` - // The title to display in the popup panel if output is set to 'popup'. If left unset, the command will be used as the title. - OutputTitle string `yaml:"outputTitle"` - // Actions to take after the command has completed - // [dev] Pointer so that we can tell whether it appears in the config file - After *CustomCommandAfterHook `yaml:"after"` -} - -func (c *CustomCommand) GetDescription() string { - if c.Description != "" { - return c.Description - } - - return c.Command -} - -type CustomCommandPrompt struct { - // One of: 'input' | 'menu' | 'confirm' | 'menuFromCommand' - Type string `yaml:"type"` - // Used to reference the entered value from within the custom command. E.g. a prompt with `key: 'Branch'` can be referred to as `{{.Form.Branch}}` in the command - Key string `yaml:"key"` - // The title to display in the popup panel - Title string `yaml:"title"` - - // The initial value to appear in the text box. - // Only for input prompts. - InitialValue string `yaml:"initialValue"` - // Shows suggestions as the input is entered - // Only for input prompts. - Suggestions CustomCommandSuggestions `yaml:"suggestions"` - - // The message of the confirmation prompt. - // Only for confirm prompts. - Body string `yaml:"body" jsonschema:"example=Are you sure you want to push to the remote?"` - - // Menu options. - // Only for menu prompts. - Options []CustomCommandMenuOption `yaml:"options"` - - // The command to run to generate menu options - // Only for menuFromCommand prompts. - Command string `yaml:"command" jsonschema:"example=git fetch {{.Form.Remote}} {{.Form.Branch}} && git checkout FETCH_HEAD"` - // The regexp to run specifying groups which are going to be kept from the command's output. - // Only for menuFromCommand prompts. - Filter string `yaml:"filter" jsonschema:"example=.*{{.SelectedRemote.Name }}/(?P.*)"` - // How to format matched groups from the filter to construct a menu item's value. - // Only for menuFromCommand prompts. - ValueFormat string `yaml:"valueFormat" jsonschema:"example={{ .branch }}"` - // Like valueFormat but for the labels. If `labelFormat` is not specified, `valueFormat` is shown instead. - // Only for menuFromCommand prompts. - LabelFormat string `yaml:"labelFormat" jsonschema:"example={{ .branch | green }}"` -} - -type CustomCommandSuggestions struct { - // Uses built-in logic to obtain the suggestions. One of 'authors' | 'branches' | 'files' | 'refs' | 'remotes' | 'remoteBranches' | 'tags' - Preset string `yaml:"preset" jsonschema:"enum=authors,enum=branches,enum=files,enum=refs,enum=remotes,enum=remoteBranches,enum=tags"` - // Command to run such that each line in the output becomes a suggestion. Mutually exclusive with 'preset' field. - Command string `yaml:"command" jsonschema:"example=git fetch {{.Form.Remote}} {{.Form.Branch}} && git checkout FETCH_HEAD"` -} - -type CustomCommandMenuOption struct { - // The first part of the label - Name string `yaml:"name"` - // The second part of the label - Description string `yaml:"description"` - // The value that will be used in the command - Value string `yaml:"value" jsonschema:"example=feature,minLength=1"` -} - -type CustomIconsConfig struct { - // Map of filenames to icon properties (icon and color) - Filenames map[string]IconProperties `yaml:"filenames"` - // Map of file extensions (including the dot) to icon properties (icon and color) - Extensions map[string]IconProperties `yaml:"extensions"` -} - -type IconProperties struct { - Icon string `yaml:"icon"` - Color string `yaml:"color"` -} - -func GetDefaultConfig() *UserConfig { - return &UserConfig{ - Gui: GuiConfig{ - ScrollHeight: 2, - ScrollPastBottom: true, - ScrollOffMargin: 2, - ScrollOffBehavior: "margin", - TabWidth: 4, - MouseEvents: true, - SkipAmendWarning: false, - SkipDiscardChangeWarning: false, - SkipStashWarning: false, - SidePanelWidth: 0.3333, - ExpandFocusedSidePanel: false, - ExpandedSidePanelWeight: 2, - MainPanelSplitMode: "flexible", - EnlargedSideViewLocation: "left", - WrapLinesInStagingView: true, - UseHunkModeInStagingView: true, - Language: "auto", - TimeFormat: "02 Jan 06", - ShortTimeFormat: time.Kitchen, - Theme: ThemeConfig{ - ActiveBorderColor: []string{"green", "bold"}, - SearchingActiveBorderColor: []string{"cyan", "bold"}, - InactiveBorderColor: []string{"default"}, - OptionsTextColor: []string{"blue"}, - SelectedLineBgColor: []string{"blue"}, - InactiveViewSelectedLineBgColor: []string{"bold"}, - CherryPickedCommitBgColor: []string{"cyan"}, - CherryPickedCommitFgColor: []string{"blue"}, - MarkedBaseCommitBgColor: []string{"yellow"}, - MarkedBaseCommitFgColor: []string{"blue"}, - UnstagedChangesColor: []string{"red"}, - DefaultFgColor: []string{"default"}, - }, - CommitLength: CommitLengthConfig{Show: true}, - SkipNoStagedFilesWarning: false, - ShowListFooter: true, - ShowCommandLog: true, - ShowBottomLine: true, - ShowPanelJumps: true, - ShowFileTree: true, - ShowRootItemInFileTree: true, - ShowNumstatInFilesView: false, - ShowRandomTip: true, - ShowIcons: false, - NerdFontsVersion: "", - ShowFileIcons: true, - CommitAuthorShortLength: 2, - CommitAuthorLongLength: 17, - CommitHashLength: 8, - ShowBranchCommitHash: false, - ShowDivergenceFromBaseBranch: "none", - CommandLogSize: 8, - SplitDiff: "auto", - SkipRewordInEditorWarning: false, - SkipSwitchWorktreeOnCheckoutWarning: false, - ScreenMode: "normal", - Border: "rounded", - AnimateExplosion: true, - PortraitMode: "auto", - FilterMode: "substring", - Spinner: SpinnerConfig{ - Frames: []string{"|", "/", "-", "\\"}, - Rate: 50, - }, - StatusPanelView: "dashboard", - SwitchToFilesAfterStashPop: true, - SwitchToFilesAfterStashApply: true, - SwitchTabsWithPanelJumpKeys: false, - }, - Git: GitConfig{ - Commit: CommitConfig{ - SignOff: false, - AutoWrapCommitMessage: true, - AutoWrapWidth: 72, - }, - Merging: MergingConfig{ - ManualCommit: false, - Args: "", - SquashMergeMessage: "Squash merge {{selectedRef}} into {{currentBranch}}", - }, - Log: LogConfig{ - Order: "topo-order", - ShowGraph: "always", - ShowWholeGraph: false, - }, - LocalBranchSortOrder: "date", - RemoteBranchSortOrder: "date", - SkipHookPrefix: "WIP", - MainBranches: []string{"master", "main"}, - AutoFetch: true, - AutoRefresh: true, - AutoForwardBranches: "onlyMainBranches", - FetchAll: true, - AutoStageResolvedConflicts: true, - BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --", - AllBranchesLogCmds: []string{"git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium"}, - IgnoreWhitespaceInDiffView: false, - DiffContextSize: 3, - RenameSimilarityThreshold: 50, - DisableForcePushing: false, - CommitPrefixes: map[string][]CommitPrefixConfig(nil), - BranchPrefix: "", - ParseEmoji: false, - TruncateCopiedCommitHashesTo: 12, - }, - Refresher: RefresherConfig{ - RefreshInterval: 10, - FetchInterval: 60, - }, - Update: UpdateConfig{ - Method: "prompt", - Days: 14, - }, - ConfirmOnQuit: false, - QuitOnTopLevelReturn: false, - OS: OSConfig{}, - DisableStartupPopups: false, - CustomCommands: []CustomCommand(nil), - Services: map[string]string(nil), - NotARepository: "prompt", - PromptToReturnFromSubprocess: true, - Keybinding: KeybindingConfig{ - Universal: KeybindingUniversalConfig{ - Quit: "q", - QuitAlt1: "", - SuspendApp: "", - Return: "", - QuitWithoutChangingDirectory: "Q", - TogglePanel: "", - PrevItem: "", - NextItem: "", - PrevItemAlt: "k", - NextItemAlt: "j", - PrevPage: ",", - NextPage: ".", - ScrollLeft: "H", - ScrollRight: "L", - GotoTop: "<", - GotoBottom: ">", - GotoTopAlt: "", - GotoBottomAlt: "", - ToggleRangeSelect: "v", - RangeSelectDown: "", - RangeSelectUp: "", - PrevBlock: "", - NextBlock: "", - PrevBlockAlt: "h", - NextBlockAlt: "l", - PrevBlockAlt2: "", - NextBlockAlt2: "", - JumpToBlock: []string{"1", "2", "3", "4", "5"}, - FocusMainView: "0", - NextMatch: "n", - PrevMatch: "N", - StartSearch: "/", - OptionMenu: "", - OptionMenuAlt1: "?", - Select: "", - GoInto: "", - Confirm: "", - ConfirmMenu: "", - ConfirmSuggestion: "", - ConfirmInEditor: "", - ConfirmInEditorAlt: "", - Remove: "d", - New: "n", - Edit: "e", - OpenFile: "o", - OpenRecentRepos: "", - ScrollUpMain: "", - ScrollDownMain: "", - ScrollUpMainAlt1: "K", - ScrollDownMainAlt1: "J", - ScrollUpMainAlt2: "", - ScrollDownMainAlt2: "", - ExecuteShellCommand: ":", - CreateRebaseOptionsMenu: "m", - Push: "P", - Pull: "p", - Refresh: "R", - CreatePatchOptionsMenu: "", - NextTab: "]", - PrevTab: "[", - NextScreenMode: "+", - PrevScreenMode: "_", - CyclePagers: "|", - Undo: "z", - Redo: "Z", - FilteringMenu: "", - DiffingMenu: "W", - DiffingMenuAlt: "", - CopyToClipboard: "", - SubmitEditorText: "", - ExtrasMenu: "@", - ToggleWhitespaceInDiffView: "", - IncreaseContextInDiffView: "}", - DecreaseContextInDiffView: "{", - IncreaseRenameSimilarityThreshold: ")", - DecreaseRenameSimilarityThreshold: "(", - OpenDiffTool: "", - }, - Status: KeybindingStatusConfig{ - CheckForUpdate: "u", - RecentRepos: "", - AllBranchesLogGraph: "a", - }, - Files: KeybindingFilesConfig{ - CommitChanges: "c", - CommitChangesWithoutHook: "w", - AmendLastCommit: "A", - CommitChangesWithEditor: "C", - FindBaseCommitForFixup: "", - IgnoreFile: "i", - RefreshFiles: "r", - StashAllChanges: "s", - ViewStashOptions: "S", - ToggleStagedAll: "a", - ViewResetOptions: "D", - Fetch: "f", - ToggleTreeView: "`", - OpenMergeOptions: "M", - OpenStatusFilter: "", - ConfirmDiscard: "x", - CopyFileInfoToClipboard: "y", - CollapseAll: "-", - ExpandAll: "=", - }, - Branches: KeybindingBranchesConfig{ - CopyPullRequestURL: "", - CreatePullRequest: "o", - ViewPullRequestOptions: "O", - CheckoutBranchByName: "c", - ForceCheckoutBranch: "F", - CheckoutPreviousBranch: "-", - RebaseBranch: "r", - RenameBranch: "R", - MergeIntoCurrentBranch: "M", - MoveCommitsToNewBranch: "N", - ViewGitFlowOptions: "i", - FastForward: "f", - CreateTag: "T", - PushTag: "P", - SetUpstream: "u", - FetchRemote: "f", - SortOrder: "s", - }, - Worktrees: KeybindingWorktreesConfig{ - ViewWorktreeOptions: "w", - }, - Commits: KeybindingCommitsConfig{ - SquashDown: "s", - RenameCommit: "r", - RenameCommitWithEditor: "R", - ViewResetOptions: "g", - MarkCommitAsFixup: "f", - CreateFixupCommit: "F", - SquashAboveCommits: "S", - MoveDownCommit: "", - MoveUpCommit: "", - AmendToCommit: "A", - ResetCommitAuthor: "a", - PickCommit: "p", - RevertCommit: "t", - CherryPickCopy: "C", - PasteCommits: "V", - MarkCommitAsBaseForRebase: "B", - CreateTag: "T", - CheckoutCommit: "", - ResetCherryPick: "", - CopyCommitAttributeToClipboard: "y", - OpenLogMenu: "", - OpenInBrowser: "o", - ViewBisectOptions: "b", - StartInteractiveRebase: "i", - SelectCommitsOfCurrentBranch: "*", - }, - AmendAttribute: KeybindingAmendAttributeConfig{ - ResetAuthor: "a", - SetAuthor: "A", - AddCoAuthor: "c", - }, - Stash: KeybindingStashConfig{ - PopStash: "g", - RenameStash: "r", - }, - CommitFiles: KeybindingCommitFilesConfig{ - CheckoutCommitFile: "c", - }, - Main: KeybindingMainConfig{ - ToggleSelectHunk: "a", - PickBothHunks: "b", - EditSelectHunk: "E", - }, - Submodules: KeybindingSubmodulesConfig{ - Init: "i", - Update: "u", - BulkMenu: "b", - }, - CommitMessage: KeybindingCommitMessageConfig{ - CommitMenu: "", - }, - }, - } -} diff --git a/pkg/config/user_config_validation.go b/pkg/config/user_config_validation.go deleted file mode 100644 index fc504fe32c4..00000000000 --- a/pkg/config/user_config_validation.go +++ /dev/null @@ -1,146 +0,0 @@ -package config - -import ( - "fmt" - "log" - "reflect" - "slices" - "strings" - - "github.com/jesseduffield/lazygit/pkg/constants" -) - -func (config *UserConfig) Validate() error { - if err := validateEnum("gui.statusPanelView", config.Gui.StatusPanelView, - []string{"dashboard", "allBranchesLog"}); err != nil { - return err - } - if err := validateEnum("gui.showDivergenceFromBaseBranch", config.Gui.ShowDivergenceFromBaseBranch, - []string{"none", "onlyArrow", "arrowAndNumber"}); err != nil { - return err - } - if err := validateEnum("git.autoForwardBranches", config.Git.AutoForwardBranches, - []string{"none", "onlyMainBranches", "allBranches"}); err != nil { - return err - } - if err := validateEnum("git.localBranchSortOrder", config.Git.LocalBranchSortOrder, - []string{"date", "recency", "alphabetical"}); err != nil { - return err - } - if err := validateEnum("git.remoteBranchSortOrder", config.Git.RemoteBranchSortOrder, - []string{"date", "alphabetical"}); err != nil { - return err - } - if err := validateEnum("git.log.order", config.Git.Log.Order, - []string{"date-order", "author-date-order", "topo-order", "default"}); err != nil { - return err - } - if err := validateEnum("git.log.showGraph", config.Git.Log.ShowGraph, - []string{"always", "never", "when-maximised"}); err != nil { - return err - } - if err := validateKeybindings(config.Keybinding); err != nil { - return err - } - if err := validateCustomCommands(config.CustomCommands); err != nil { - return err - } - return nil -} - -func validateEnum(name string, value string, allowedValues []string) error { - if slices.Contains(allowedValues, value) { - return nil - } - allowedValuesStr := strings.Join(allowedValues, ", ") - return fmt.Errorf("Unexpected value '%s' for '%s'. Allowed values: %s", value, name, allowedValuesStr) -} - -func validateKeybindingsRecurse(path string, node any) error { - value := reflect.ValueOf(node) - if value.Kind() == reflect.Struct { - for _, field := range reflect.VisibleFields(reflect.TypeOf(node)) { - var newPath string - if len(path) == 0 { - newPath = field.Name - } else { - newPath = fmt.Sprintf("%s.%s", path, field.Name) - } - if err := validateKeybindingsRecurse(newPath, - value.FieldByName(field.Name).Interface()); err != nil { - return err - } - } - } else if value.Kind() == reflect.Slice { - for i := range value.Len() { - if err := validateKeybindingsRecurse( - fmt.Sprintf("%s[%d]", path, i), value.Index(i).Interface()); err != nil { - return err - } - } - } else if value.Kind() == reflect.String { - key := node.(string) - if !isValidKeybindingKey(key) { - return fmt.Errorf("Unrecognized key '%s' for keybinding '%s'. For permitted values see %s", - key, path, constants.Links.Docs.CustomKeybindings) - } - } else { - log.Fatalf("Unexpected type for property '%s': %s", path, value.Kind()) - } - return nil -} - -func validateKeybindings(keybindingConfig KeybindingConfig) error { - if err := validateKeybindingsRecurse("", keybindingConfig); err != nil { - return err - } - - if len(keybindingConfig.Universal.JumpToBlock) != 5 { - return fmt.Errorf("keybinding.universal.jumpToBlock must have 5 elements; found %d.", - len(keybindingConfig.Universal.JumpToBlock)) - } - - return nil -} - -func validateCustomCommandKey(key string) error { - if !isValidKeybindingKey(key) { - return fmt.Errorf("Unrecognized key '%s' for custom command. For permitted values see %s", - key, constants.Links.Docs.CustomKeybindings) - } - return nil -} - -func validateCustomCommands(customCommands []CustomCommand) error { - for _, customCommand := range customCommands { - if err := validateCustomCommandKey(customCommand.Key); err != nil { - return err - } - - if len(customCommand.CommandMenu) > 0 { - if len(customCommand.Context) > 0 || - len(customCommand.Command) > 0 || - len(customCommand.Prompts) > 0 || - len(customCommand.LoadingText) > 0 || - len(customCommand.Output) > 0 || - len(customCommand.OutputTitle) > 0 || - customCommand.After != nil { - commandRef := "" - if len(customCommand.Key) > 0 { - commandRef = fmt.Sprintf(" with key '%s'", customCommand.Key) - } - return fmt.Errorf("Error with custom command%s: it is not allowed to use both commandMenu and any of the other fields except key and description.", commandRef) - } - - if err := validateCustomCommands(customCommand.CommandMenu); err != nil { - return err - } - } else { - if err := validateEnum("customCommand.output", customCommand.Output, - []string{"", "none", "terminal", "log", "logWithPty", "popup"}); err != nil { - return err - } - } - } - return nil -} diff --git a/pkg/config/user_config_validation_test.go b/pkg/config/user_config_validation_test.go deleted file mode 100644 index fcbfe1139db..00000000000 --- a/pkg/config/user_config_validation_test.go +++ /dev/null @@ -1,266 +0,0 @@ -package config - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestUserConfigValidate_enums(t *testing.T) { - type testCase struct { - value string - valid bool - } - - scenarios := []struct { - name string - setup func(config *UserConfig, value string) - testCases []testCase - }{ - { - name: "Gui.StatusPanelView", - setup: func(config *UserConfig, value string) { - config.Gui.StatusPanelView = value - }, - testCases: []testCase{ - {value: "dashboard", valid: true}, - {value: "allBranchesLog", valid: true}, - {value: "", valid: false}, - {value: "invalid_value", valid: false}, - }, - }, - { - name: "Gui.ShowDivergenceFromBaseBranch", - setup: func(config *UserConfig, value string) { - config.Gui.ShowDivergenceFromBaseBranch = value - }, - testCases: []testCase{ - {value: "none", valid: true}, - {value: "onlyArrow", valid: true}, - {value: "arrowAndNumber", valid: true}, - {value: "", valid: false}, - {value: "invalid_value", valid: false}, - }, - }, - { - name: "Git.AutoForwardBranches", - setup: func(config *UserConfig, value string) { - config.Git.AutoForwardBranches = value - }, - testCases: []testCase{ - {value: "none", valid: true}, - {value: "onlyMainBranches", valid: true}, - {value: "allBranches", valid: true}, - {value: "", valid: false}, - {value: "invalid_value", valid: false}, - }, - }, - { - name: "Git.LocalBranchSortOrder", - setup: func(config *UserConfig, value string) { - config.Git.LocalBranchSortOrder = value - }, - testCases: []testCase{ - {value: "date", valid: true}, - {value: "recency", valid: true}, - {value: "alphabetical", valid: true}, - {value: "", valid: false}, - {value: "invalid_value", valid: false}, - }, - }, - { - name: "Git.RemoteBranchSortOrder", - setup: func(config *UserConfig, value string) { - config.Git.RemoteBranchSortOrder = value - }, - testCases: []testCase{ - {value: "date", valid: true}, - {value: "recency", valid: false}, - {value: "alphabetical", valid: true}, - {value: "", valid: false}, - {value: "invalid_value", valid: false}, - }, - }, - { - name: "Git.Log.Order", - setup: func(config *UserConfig, value string) { - config.Git.Log.Order = value - }, - testCases: []testCase{ - {value: "date-order", valid: true}, - {value: "author-date-order", valid: true}, - {value: "topo-order", valid: true}, - {value: "default", valid: true}, - - {value: "", valid: false}, - {value: "invalid_value", valid: false}, - }, - }, - { - name: "Git.Log.ShowGraph", - setup: func(config *UserConfig, value string) { - config.Git.Log.ShowGraph = value - }, - testCases: []testCase{ - {value: "always", valid: true}, - {value: "never", valid: true}, - {value: "when-maximised", valid: true}, - - {value: "", valid: false}, - {value: "invalid_value", valid: false}, - }, - }, - { - name: "Keybindings", - setup: func(config *UserConfig, value string) { - config.Keybinding.Universal.Quit = value - }, - testCases: []testCase{ - {value: "", valid: true}, - {value: "", valid: true}, - {value: "q", valid: true}, - {value: "", valid: true}, - {value: "invalid_value", valid: false}, - }, - }, - { - name: "JumpToBlock keybinding", - setup: func(config *UserConfig, value string) { - config.Keybinding.Universal.JumpToBlock = strings.Split(value, ",") - }, - testCases: []testCase{ - {value: "", valid: false}, - {value: "1,2,3", valid: false}, - {value: "1,2,3,4,5", valid: true}, - {value: "1,2,3,4,invalid", valid: false}, - {value: "1,2,3,4,5,6", valid: false}, - }, - }, - { - name: "Custom command keybinding", - setup: func(config *UserConfig, value string) { - config.CustomCommands = []CustomCommand{ - { - Key: value, - Command: "echo 'hello'", - }, - } - }, - testCases: []testCase{ - {value: "", valid: true}, - {value: "", valid: true}, - {value: "q", valid: true}, - {value: "", valid: true}, - {value: "invalid_value", valid: false}, - }, - }, - { - name: "Custom command keybinding in sub menu", - setup: func(config *UserConfig, value string) { - config.CustomCommands = []CustomCommand{ - { - Key: "X", - Description: "My Custom Commands", - CommandMenu: []CustomCommand{ - {Key: value, Command: "echo 'hello'", Context: "global"}, - }, - }, - } - }, - testCases: []testCase{ - {value: "", valid: true}, - {value: "", valid: true}, - {value: "q", valid: true}, - {value: "", valid: true}, - {value: "invalid_value", valid: false}, - }, - }, - { - name: "Custom command output", - setup: func(config *UserConfig, value string) { - config.CustomCommands = []CustomCommand{ - { - Output: value, - }, - } - }, - testCases: []testCase{ - {value: "", valid: true}, - {value: "none", valid: true}, - {value: "terminal", valid: true}, - {value: "log", valid: true}, - {value: "logWithPty", valid: true}, - {value: "popup", valid: true}, - {value: "invalid_value", valid: false}, - }, - }, - { - name: "Custom command sub menu", - setup: func(config *UserConfig, _ string) { - config.CustomCommands = []CustomCommand{ - { - Key: "X", - Description: "My Custom Commands", - CommandMenu: []CustomCommand{ - {Key: "1", Command: "echo 'hello'", Context: "global"}, - }, - }, - } - }, - testCases: []testCase{ - {value: "", valid: true}, - }, - }, - { - name: "Custom command sub menu", - setup: func(config *UserConfig, _ string) { - config.CustomCommands = []CustomCommand{ - { - Key: "X", - Context: "global", // context is not allowed for submenus - CommandMenu: []CustomCommand{ - {Key: "1", Command: "echo 'hello'", Context: "global"}, - }, - }, - } - }, - testCases: []testCase{ - {value: "", valid: false}, - }, - }, - { - name: "Custom command sub menu", - setup: func(config *UserConfig, _ string) { - config.CustomCommands = []CustomCommand{ - { - Key: "X", - LoadingText: "loading", // other properties are not allowed for submenus (using loadingText as an example) - CommandMenu: []CustomCommand{ - {Key: "1", Command: "echo 'hello'", Context: "global"}, - }, - }, - } - }, - testCases: []testCase{ - {value: "", valid: false}, - }, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - for _, testCase := range s.testCases { - config := GetDefaultConfig() - s.setup(config, testCase.value) - err := config.Validate() - - if testCase.valid { - assert.NoError(t, err) - } else { - assert.Error(t, err) - } - } - }) - } -} diff --git a/pkg/constants/links.go b/pkg/constants/links.go deleted file mode 100644 index e9b06cba312..00000000000 --- a/pkg/constants/links.go +++ /dev/null @@ -1,37 +0,0 @@ -package constants - -type Docs struct { - CustomPagers string - CustomCommands string - CustomKeybindings string - Keybindings string - Undoing string - Config string - Tutorial string - CustomPatchDemo string -} - -var Links = struct { - Docs Docs - Issues string - Donate string - Discussions string - RepoUrl string - Releases string -}{ - RepoUrl: "https://github.com/jesseduffield/lazygit", - Issues: "https://github.com/jesseduffield/lazygit/issues", - Donate: "https://github.com/sponsors/jesseduffield", - Discussions: "https://github.com/jesseduffield/lazygit/discussions", - Releases: "https://github.com/jesseduffield/lazygit/releases", - Docs: Docs{ - CustomPagers: "https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md", - CustomKeybindings: "https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md", - CustomCommands: "https://github.com/jesseduffield/lazygit/wiki/Custom-Commands-Compendium", - Keybindings: "https://github.com/jesseduffield/lazygit/blob/%s/docs/keybindings", - Undoing: "https://github.com/jesseduffield/lazygit/blob/master/docs/Undoing.md", - Config: "https://github.com/jesseduffield/lazygit/blob/%s/docs/Config.md", - Tutorial: "https://youtu.be/VDXvbHZYeKY", - CustomPatchDemo: "https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches", - }, -} diff --git a/pkg/env/env.go b/pkg/env/env.go deleted file mode 100644 index 1ade5b8c614..00000000000 --- a/pkg/env/env.go +++ /dev/null @@ -1,28 +0,0 @@ -package env - -import ( - "os" -) - -// This package encapsulates accessing/mutating the ENV of the program. - -func GetGitDirEnv() string { - return os.Getenv("GIT_DIR") -} - -func SetGitDirEnv(value string) { - os.Setenv("GIT_DIR", value) -} - -func GetWorkTreeEnv() string { - return os.Getenv("GIT_WORK_TREE") -} - -func SetWorkTreeEnv(value string) { - os.Setenv("GIT_WORK_TREE", value) -} - -func UnsetGitLocationEnvVars() { - _ = os.Unsetenv("GIT_DIR") - _ = os.Unsetenv("GIT_WORK_TREE") -} diff --git a/pkg/fakes/log.go b/pkg/fakes/log.go deleted file mode 100644 index fc68f23c15d..00000000000 --- a/pkg/fakes/log.go +++ /dev/null @@ -1,40 +0,0 @@ -package fakes - -import ( - "fmt" - "testing" - - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" -) - -var _ logrus.FieldLogger = &FakeFieldLogger{} - -// for now we're just tracking calls to the Error and Errorf methods -type FakeFieldLogger struct { - loggedErrors []string - *logrus.Entry -} - -func (self *FakeFieldLogger) Error(args ...interface{}) { - if len(args) != 1 { - panic("Expected exactly one argument to FakeFieldLogger.Error") - } - - switch arg := args[0].(type) { - case error: - self.loggedErrors = append(self.loggedErrors, arg.Error()) - case string: - self.loggedErrors = append(self.loggedErrors, arg) - } -} - -func (self *FakeFieldLogger) Errorf(format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - self.loggedErrors = append(self.loggedErrors, msg) -} - -func (self *FakeFieldLogger) AssertErrors(t *testing.T, expectedErrors []string) { - t.Helper() - assert.EqualValues(t, expectedErrors, self.loggedErrors) -} diff --git a/pkg/gui/background.go b/pkg/gui/background.go deleted file mode 100644 index 6eaf041d42b..00000000000 --- a/pkg/gui/background.go +++ /dev/null @@ -1,136 +0,0 @@ -package gui - -import ( - "fmt" - "runtime" - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type BackgroundRoutineMgr struct { - gui *Gui - - // if we've suspended the gui (e.g. because we've switched to a subprocess) - // we typically want to pause some things that are running like background - // file refreshes - pauseBackgroundRefreshes bool -} - -func (self *BackgroundRoutineMgr) PauseBackgroundRefreshes(pause bool) { - self.pauseBackgroundRefreshes = pause -} - -func (self *BackgroundRoutineMgr) startBackgroundRoutines() { - userConfig := self.gui.UserConfig() - - if userConfig.Git.AutoFetch { - fetchInterval := userConfig.Refresher.FetchInterval - if fetchInterval > 0 { - go utils.Safe(self.startBackgroundFetch) - } else { - self.gui.c.Log.Errorf( - "Value of config option 'refresher.fetchInterval' (%d) is invalid, disabling auto-fetch", - fetchInterval) - } - } - - if userConfig.Git.AutoRefresh { - refreshInterval := userConfig.Refresher.RefreshInterval - if refreshInterval > 0 { - go utils.Safe(func() { self.startBackgroundFilesRefresh(refreshInterval) }) - } else { - self.gui.c.Log.Errorf( - "Value of config option 'refresher.refreshInterval' (%d) is invalid, disabling auto-refresh", - refreshInterval) - } - } - - if self.gui.Config.GetDebug() { - self.goEvery(time.Second*time.Duration(10), self.gui.stopChan, func() error { - formatBytes := func(b uint64) string { - const unit = 1000 - if b < unit { - return fmt.Sprintf("%d B", b) - } - div, exp := uint64(unit), 0 - for n := b / unit; n >= unit; n /= unit { - div *= unit - exp++ - } - return fmt.Sprintf("%.1f %cB", - float64(b)/float64(div), "kMGTPE"[exp]) - } - - m := runtime.MemStats{} - runtime.ReadMemStats(&m) - self.gui.c.Log.Infof("Heap memory in use: %s", formatBytes(m.HeapAlloc)) - return nil - }) - } -} - -func (self *BackgroundRoutineMgr) startBackgroundFetch() { - self.gui.waitForIntro.Wait() - - fetch := func() error { - return self.gui.helpers.AppStatus.WithWaitingStatusImpl(self.gui.Tr.FetchingStatus, func(gocui.Task) error { - return self.backgroundFetch() - }, nil) - } - - // We want an immediate fetch at startup, and since goEvery starts by - // waiting for the interval, we need to trigger one manually first - _ = fetch() - - userConfig := self.gui.UserConfig() - self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, fetch) -} - -func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh(refreshInterval int) { - self.gui.waitForIntro.Wait() - - self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error { - self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}) - return nil - }) -} - -func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func() error) { - done := make(chan struct{}) - go utils.Safe(func() { - ticker := time.NewTicker(interval) - defer ticker.Stop() - for { - select { - case <-ticker.C: - if self.pauseBackgroundRefreshes { - continue - } - self.gui.c.OnWorker(func(gocui.Task) error { - _ = function() - done <- struct{}{} - return nil - }) - // waiting so that we don't bunch up refreshes if the refresh takes longer than the interval - <-done - case <-stop: - return - } - } - }) -} - -func (self *BackgroundRoutineMgr) backgroundFetch() (err error) { - err = self.gui.git.Sync.FetchBackground() - - self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC}) - - if err == nil { - err = self.gui.helpers.BranchesHelper.AutoForwardBranches() - } - - return err -} diff --git a/pkg/gui/command_log_panel.go b/pkg/gui/command_log_panel.go deleted file mode 100644 index d4b847c946d..00000000000 --- a/pkg/gui/command_log_panel.go +++ /dev/null @@ -1,194 +0,0 @@ -package gui - -import ( - "fmt" - "math/rand" - "strings" - "time" - - "github.com/jesseduffield/lazygit/pkg/constants" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/theme" -) - -// our UI command log looks like this: -// Stage File: -// git add -- 'filename' -// Unstage File: -// git reset HEAD 'filename' -// -// The 'Stage File' and 'Unstage File' lines are actions i.e they group up a set -// of command logs (typically there's only one command under an action but there may be more). -// So we call logAction to log the 'Stage File' part and then we call logCommand to log the command itself. -// We pass logCommand to our OSCommand struct so that it can handle logging commands -// for us. -func (gui *Gui) LogAction(action string) { - if gui.Views.Extras == nil { - return - } - - gui.Views.Extras.Autoscroll = true - - gui.GuiLog = append(gui.GuiLog, action) - fmt.Fprint(gui.Views.Extras, "\n"+style.FgYellow.Sprint(action)) -} - -func (gui *Gui) LogCommand(cmdStr string, commandLine bool) { - if gui.Views.Extras == nil { - return - } - - gui.Views.Extras.Autoscroll = true - - textStyle := theme.DefaultTextColor - if !commandLine { - // if we're not dealing with a direct command that could be run on the command line, - // we style it differently to communicate that - textStyle = style.FgMagenta - } - gui.GuiLog = append(gui.GuiLog, cmdStr) - indentedCmdStr := " " + strings.ReplaceAll(cmdStr, "\n", "\n ") - fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr)) -} - -func (gui *Gui) printCommandLogHeader() { - introStr := fmt.Sprintf( - gui.c.Tr.CommandLogHeader, - keybindings.Label(gui.c.UserConfig().Keybinding.Universal.ExtrasMenu), - ) - fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr)) - - if gui.c.UserConfig().Gui.ShowRandomTip { - fmt.Fprintf( - gui.Views.Extras, - "%s: %s", - style.FgYellow.Sprint(gui.c.Tr.RandomTip), - style.FgGreen.Sprint(gui.getRandomTip()), - ) - } -} - -func (gui *Gui) getRandomTip() string { - config := gui.c.UserConfig().Keybinding - - formattedKey := func(key string) string { - return keybindings.Label(key) - } - - tips := []string{ - // keybindings and lazygit-specific advice - fmt.Sprintf( - "To force push, press '%s' and then if the push is rejected you will be asked if you want to force push", - formattedKey(config.Universal.Push), - ), - fmt.Sprintf( - "To filter commits by path, press '%s'", - formattedKey(config.Universal.FilteringMenu), - ), - fmt.Sprintf( - "To start an interactive rebase, press '%s' on a commit. You can always abort the rebase by pressing '%s' and selecting 'abort'", - formattedKey(config.Universal.Edit), - formattedKey(config.Universal.CreateRebaseOptionsMenu), - ), - fmt.Sprintf( - "In flat file view, merge conflicts are sorted to the top. To switch to flat file view press '%s'", - formattedKey(config.Files.ToggleTreeView), - ), - "If you want to learn Go and can think of ways to improve lazygit, join the team! Click 'Ask Question' and express your interest", - fmt.Sprintf( - "If you press '%s'/'%s' you can undo/redo your changes. Be wary though, this only applies to branches/commits, so only do this if your worktree is clear.\nDocs: %s", - formattedKey(config.Universal.Undo), - formattedKey(config.Universal.Redo), - constants.Links.Docs.Undoing, - ), - fmt.Sprintf( - "to hard reset onto your current upstream branch, press '%s' in the files panel", - formattedKey(config.Commits.ViewResetOptions), - ), - fmt.Sprintf( - "To push a tag, navigate to the tag in the tags tab and press '%s'", - formattedKey(config.Branches.PushTag), - ), - fmt.Sprintf( - "You can view the individual files of a stash entry by pressing '%s'", - formattedKey(config.Universal.GoInto), - ), - fmt.Sprintf( - "You can diff two commits by pressing '%s' on one commit and then navigating to the other. You can then press '%s' to view the files of the diff", - formattedKey(config.Universal.DiffingMenu), - formattedKey(config.Universal.GoInto), - ), - fmt.Sprintf( - "press '%s' on a commit to drop it (delete it)", - formattedKey(config.Universal.Remove), - ), - fmt.Sprintf( - "If you need to pull out the big guns to resolve merge conflicts, you can press '%s' in the files panel to open merge options", - formattedKey(config.Files.OpenMergeOptions), - ), - fmt.Sprintf( - "To revert a commit, press '%s' on that commit", - formattedKey(config.Commits.RevertCommit), - ), - fmt.Sprintf( - "To escape a mode, for example cherry-picking, patch-building, diffing, or filtering mode, you can just spam the '%s' button. Unless of course you have `quitOnTopLevelReturn` enabled in your config", - formattedKey(config.Universal.Return), - ), - fmt.Sprintf( - "You can page through the items of a panel using '%s' and '%s'", - formattedKey(config.Universal.PrevPage), - formattedKey(config.Universal.NextPage), - ), - fmt.Sprintf( - "You can jump to the top/bottom of a panel using '%s (or %s)' and '%s (or %s)'", - formattedKey(config.Universal.GotoTop), formattedKey(config.Universal.GotoTopAlt), - formattedKey(config.Universal.GotoBottom), formattedKey(config.Universal.GotoBottomAlt), - ), - fmt.Sprintf( - "To collapse/expand a directory, press '%s'", - formattedKey(config.Universal.GoInto), - ), - fmt.Sprintf( - "You can append your staged changes to an older commit by pressing '%s' on that commit", - formattedKey(config.Commits.AmendToCommit), - ), - fmt.Sprintf( - "You can amend the last commit with your new file changes by pressing '%s' in the files panel", - formattedKey(config.Files.AmendLastCommit), - ), - fmt.Sprintf( - "You can now navigate the side panels with '%s' and '%s'", - formattedKey(config.Universal.NextBlockAlt2), - formattedKey(config.Universal.PrevBlockAlt2), - ), - - "You can use lazygit with a bare repo by passing the --git-dir and --work-tree arguments as you would for the git CLI", - - // general advice - "`git commit` is really just the programmer equivalent of saving your game. Always do it before embarking on an ambitious change!", - "Try to separate commits that refactor code from commits that add new functionality: if they're squashed into one commit, it can be hard to spot what's new.", - "If you ever want to experiment, it's easy to create a new branch off your current one and go nuts, then delete it afterwards", - "Always read through the diff of your changes before assigning somebody to review your code. Better for you to catch any silly mistakes than your colleagues!", - "If something goes wrong, you can always checkout a commit from your reflog to return to an earlier state", - "The stash is a good place to save snippets of code that you always find yourself adding when debugging.", - - // links - fmt.Sprintf( - "If you want a git diff with syntax colouring, check out lazygit's integration with delta:\n%s", - constants.Links.Docs.CustomPagers, - ), - fmt.Sprintf( - "You can build your own custom menus and commands to run from within lazygit. For examples see:\n%s", - constants.Links.Docs.CustomCommands, - ), - fmt.Sprintf( - "If you ever find a bug, do not hesitate to raise an issue on the repo:\n%s", - constants.Links.Issues, - ), - } - - rnd := rand.New(rand.NewSource(time.Now().UnixNano())) - randomIndex := rnd.Intn(len(tips)) - return tips[randomIndex] -} diff --git a/pkg/gui/context.go b/pkg/gui/context.go deleted file mode 100644 index 98aea7e8f79..00000000000 --- a/pkg/gui/context.go +++ /dev/null @@ -1,375 +0,0 @@ -package gui - -import ( - "sync" - - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -// This file is for the management of contexts. There is a context stack such that -// for example you might start off in the commits context and then open a menu, putting -// you in the menu context. When contexts are activated/deactivated certain things need -// to happen like showing/hiding views and rendering content. - -type ContextMgr struct { - ContextStack []types.Context - sync.RWMutex - gui *Gui - - allContexts *context.ContextTree -} - -func NewContextMgr( - gui *Gui, - allContexts *context.ContextTree, -) *ContextMgr { - return &ContextMgr{ - ContextStack: []types.Context{}, - RWMutex: sync.RWMutex{}, - gui: gui, - allContexts: allContexts, - } -} - -// use when you don't want to return to the original context upon -// hitting escape: you want to go that context's parent instead. -func (self *ContextMgr) Replace(c types.Context) { - if !c.IsFocusable() { - return - } - - self.Lock() - - if len(self.ContextStack) == 0 { - self.ContextStack = []types.Context{c} - } else { - // replace the last item with the given item - self.ContextStack = append(self.ContextStack[0:len(self.ContextStack)-1], c) - } - - self.Unlock() - - self.Activate(c, types.OnFocusOpts{}) -} - -func (self *ContextMgr) Push(c types.Context, opts types.OnFocusOpts) { - if !c.IsFocusable() { - return - } - - contextsToDeactivate, contextToActivate := self.pushToContextStack(c) - - for _, contextToDeactivate := range contextsToDeactivate { - self.deactivate(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}) - } - - if contextToActivate != nil { - self.Activate(contextToActivate, opts) - } -} - -// Adjusts the context stack based on the context that's being pushed and -// returns (contexts to deactivate, context to activate) -func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, types.Context) { - contextsToDeactivate := []types.Context{} - - self.Lock() - defer self.Unlock() - - if len(self.ContextStack) > 0 && - c.GetKey() == self.ContextStack[len(self.ContextStack)-1].GetKey() { - // Context being pushed is already on top of the stack: nothing to - // deactivate or activate - return contextsToDeactivate, nil - } - - if len(self.ContextStack) == 0 { - self.ContextStack = append(self.ContextStack, c) - } else if c.GetKind() == types.SIDE_CONTEXT { - // if we are switching to a side context, remove all other contexts in the stack - contextsToDeactivate = lo.Filter(self.ContextStack, func(context types.Context, _ int) bool { - return context.GetKey() != c.GetKey() - }) - self.ContextStack = []types.Context{c} - } else if c.GetKind() == types.MAIN_CONTEXT { - // if we're switching to a main context, remove all other main contexts in the stack - contextsToKeep := []types.Context{} - for _, stackContext := range self.ContextStack { - if stackContext.GetKind() == types.MAIN_CONTEXT { - contextsToDeactivate = append(contextsToDeactivate, stackContext) - } else { - contextsToKeep = append(contextsToKeep, stackContext) - } - } - self.ContextStack = append(contextsToKeep, c) - } else { - topContext := self.currentContextWithoutLock() - - // if we're pushing the same context on, we do nothing. - if topContext.GetKey() != c.GetKey() { - // if top one is a temporary popup, we remove it. Ideally you'd be able to - // escape back to previous temporary popups, but because we're currently reusing - // views for this, you might not be able to get back to where you previously were. - // The exception is when going to the search context e.g. for searching a menu. - if (topContext.GetKind() == types.TEMPORARY_POPUP && c.GetKey() != context.SEARCH_CONTEXT_KEY) || - // we only ever want one main context on the stack at a time. - (topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) { - - contextsToDeactivate = append(contextsToDeactivate, topContext) - _, self.ContextStack = utils.Pop(self.ContextStack) - } - - self.ContextStack = append(self.ContextStack, c) - } - } - - return contextsToDeactivate, c -} - -func (self *ContextMgr) Pop() { - self.Lock() - - if len(self.ContextStack) == 1 { - // cannot escape from bottommost context - self.Unlock() - return - } - - var currentContext types.Context - currentContext, self.ContextStack = utils.Pop(self.ContextStack) - - newContext := self.ContextStack[len(self.ContextStack)-1] - - self.Unlock() - - self.deactivate(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}) - - self.Activate(newContext, types.OnFocusOpts{}) -} - -func (self *ContextMgr) deactivate(c types.Context, opts types.OnFocusLostOpts) { - view, _ := self.gui.c.GocuiGui().View(c.GetViewName()) - - if opts.NewContextKey != context.SEARCH_CONTEXT_KEY { - if c.GetKind() == types.MAIN_CONTEXT || c.GetKind() == types.TEMPORARY_POPUP { - self.gui.helpers.Search.CancelSearchIfSearching(c) - } - } - - // if we are the kind of context that is sent to back upon deactivation, we should do that - if view != nil && - (c.GetKind() == types.TEMPORARY_POPUP || - c.GetKind() == types.PERSISTENT_POPUP) { - view.Visible = false - } - - c.HandleFocusLost(opts) -} - -func (self *ContextMgr) Activate(c types.Context, opts types.OnFocusOpts) { - viewName := c.GetViewName() - v, err := self.gui.c.GocuiGui().View(viewName) - if err != nil { - panic(err) - } - - self.gui.helpers.Window.SetWindowContext(c) - - self.gui.helpers.Window.MoveToTopOfWindow(c) - oldView := self.gui.c.GocuiGui().CurrentView() - if oldView != nil && oldView.Name() != viewName { - oldView.HighlightInactive = true - } - if _, err := self.gui.c.GocuiGui().SetCurrentView(viewName); err != nil { - panic(err) - } - - self.gui.helpers.Search.RenderSearchStatus(c) - - desiredTitle := c.Title() - if desiredTitle != "" { - v.Title = desiredTitle - } - - v.Visible = true - - self.gui.c.GocuiGui().Cursor = v.Editable && v.Mask == 0 - - c.HandleFocus(opts) -} - -func (self *ContextMgr) Current() types.Context { - self.RLock() - defer self.RUnlock() - - return self.currentContextWithoutLock() -} - -func (self *ContextMgr) currentContextWithoutLock() types.Context { - if len(self.ContextStack) == 0 { - return self.gui.defaultSideContext() - } - - return self.ContextStack[len(self.ContextStack)-1] -} - -// Note that this could return the 'status' context which is not itself a list context. -func (self *ContextMgr) CurrentSide() types.Context { - self.RLock() - defer self.RUnlock() - - stack := self.ContextStack - - // find the first context in the stack with the type of types.SIDE_CONTEXT - for i := range stack { - context := stack[len(stack)-1-i] - - if context.GetKind() == types.SIDE_CONTEXT { - return context - } - } - - return self.gui.defaultSideContext() -} - -// static as opposed to popup -func (self *ContextMgr) CurrentStatic() types.Context { - self.RLock() - defer self.RUnlock() - - return self.currentStaticContextWithoutLock() -} - -func (self *ContextMgr) currentStaticContextWithoutLock() types.Context { - stack := self.ContextStack - - if len(stack) == 0 { - return self.gui.defaultSideContext() - } - - // find the first context in the stack without a popup type - for i := range stack { - context := stack[len(stack)-1-i] - - if context.GetKind() != types.TEMPORARY_POPUP && context.GetKind() != types.PERSISTENT_POPUP { - return context - } - } - - return self.gui.defaultSideContext() -} - -func (self *ContextMgr) ForEach(f func(types.Context)) { - self.RLock() - defer self.RUnlock() - - for _, context := range self.gui.State.ContextMgr.ContextStack { - f(context) - } -} - -func (self *ContextMgr) IsCurrent(c types.Context) bool { - return self.Current().GetKey() == c.GetKey() -} - -func (self *ContextMgr) IsCurrentOrParent(c types.Context) bool { - current := self.Current() - for current != nil { - if current.GetKey() == c.GetKey() { - return true - } - current = current.GetParentContext() - } - - return false -} - -func (self *ContextMgr) AllFilterable() []types.IFilterableContext { - var result []types.IFilterableContext - - for _, context := range self.allContexts.Flatten() { - if ctx, ok := context.(types.IFilterableContext); ok { - result = append(result, ctx) - } - } - - return result -} - -func (self *ContextMgr) AllSearchable() []types.ISearchableContext { - var result []types.ISearchableContext - - for _, context := range self.allContexts.Flatten() { - if ctx, ok := context.(types.ISearchableContext); ok { - result = append(result, ctx) - } - } - - return result -} - -// all list contexts -func (self *ContextMgr) AllList() []types.IListContext { - var listContexts []types.IListContext - - for _, context := range self.allContexts.Flatten() { - if listContext, ok := context.(types.IListContext); ok { - listContexts = append(listContexts, listContext) - } - } - - return listContexts -} - -func (self *ContextMgr) AllPatchExplorer() []types.IPatchExplorerContext { - var listContexts []types.IPatchExplorerContext - - for _, context := range self.allContexts.Flatten() { - if listContext, ok := context.(types.IPatchExplorerContext); ok { - listContexts = append(listContexts, listContext) - } - } - - return listContexts -} - -func (self *ContextMgr) ContextForKey(key types.ContextKey) types.Context { - self.RLock() - defer self.RUnlock() - - for _, context := range self.allContexts.Flatten() { - if context.GetKey() == key { - return context - } - } - - return nil -} - -func (self *ContextMgr) CurrentPopup() []types.Context { - self.RLock() - defer self.RUnlock() - - return lo.Filter(self.ContextStack, func(context types.Context, _ int) bool { - return context.GetKind() == types.TEMPORARY_POPUP || context.GetKind() == types.PERSISTENT_POPUP - }) -} - -func (self *ContextMgr) NextInStack(c types.Context) types.Context { - self.RLock() - defer self.RUnlock() - - for i := range self.ContextStack { - if self.ContextStack[i].GetKey() == c.GetKey() { - if i == 0 { - return nil - } - return self.ContextStack[i-1] - } - } - - panic("context not in stack") -} diff --git a/pkg/gui/context/base_context.go b/pkg/gui/context/base_context.go deleted file mode 100644 index 7c6e9b617ab..00000000000 --- a/pkg/gui/context/base_context.go +++ /dev/null @@ -1,232 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type BaseContext struct { - kind types.ContextKind - key types.ContextKey - view *gocui.View - viewTrait types.IViewTrait - windowName string - onGetOptionsMap func() map[string]string - - keybindingsFns []types.KeybindingsFn - mouseKeybindingsFns []types.MouseKeybindingsFn - onClickFn func() error - onClickFocusedMainViewFn onClickFocusedMainViewFn - onRenderToMainFn func() - onFocusFns []onFocusFn - onFocusLostFns []onFocusLostFn - - focusable bool - transient bool - hasControlledBounds bool - needsRerenderOnWidthChange types.NeedsRerenderOnWidthChangeLevel - needsRerenderOnHeightChange bool - highlightOnFocus bool - - *ParentContextMgr -} - -type ( - onFocusFn = func(types.OnFocusOpts) - onFocusLostFn = func(types.OnFocusLostOpts) - onClickFocusedMainViewFn = func(mainViewName string, clickedLineIdx int) error -) - -var _ types.IBaseContext = &BaseContext{} - -type NewBaseContextOpts struct { - Kind types.ContextKind - Key types.ContextKey - View *gocui.View - WindowName string - Focusable bool - Transient bool - HasUncontrolledBounds bool // negating for the sake of making false the default - HighlightOnFocus bool - NeedsRerenderOnWidthChange types.NeedsRerenderOnWidthChangeLevel - NeedsRerenderOnHeightChange bool - - OnGetOptionsMap func() map[string]string -} - -func NewBaseContext(opts NewBaseContextOpts) *BaseContext { - viewTrait := NewViewTrait(opts.View) - - hasControlledBounds := !opts.HasUncontrolledBounds - - return &BaseContext{ - kind: opts.Kind, - key: opts.Key, - view: opts.View, - windowName: opts.WindowName, - onGetOptionsMap: opts.OnGetOptionsMap, - focusable: opts.Focusable, - transient: opts.Transient, - hasControlledBounds: hasControlledBounds, - highlightOnFocus: opts.HighlightOnFocus, - needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange, - needsRerenderOnHeightChange: opts.NeedsRerenderOnHeightChange, - ParentContextMgr: &ParentContextMgr{}, - viewTrait: viewTrait, - } -} - -func (self *BaseContext) GetOptionsMap() map[string]string { - if self.onGetOptionsMap != nil { - return self.onGetOptionsMap() - } - return nil -} - -func (self *BaseContext) SetWindowName(windowName string) { - self.windowName = windowName -} - -func (self *BaseContext) GetWindowName() string { - return self.windowName -} - -func (self *BaseContext) GetViewName() string { - // for the sake of the global context which has no view - if self.view == nil { - return "" - } - - return self.view.Name() -} - -func (self *BaseContext) GetView() *gocui.View { - return self.view -} - -func (self *BaseContext) GetViewTrait() types.IViewTrait { - return self.viewTrait -} - -func (self *BaseContext) GetKind() types.ContextKind { - return self.kind -} - -func (self *BaseContext) GetKey() types.ContextKey { - return self.key -} - -func (self *BaseContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{} - for i := range self.keybindingsFns { - // the first binding in the bindings array takes precedence but we want the - // last keybindingsFn to take precedence to we add them in reverse - bindings = append(bindings, self.keybindingsFns[len(self.keybindingsFns)-1-i](opts)...) - } - - return bindings -} - -func (self *BaseContext) AddKeybindingsFn(fn types.KeybindingsFn) { - self.keybindingsFns = append(self.keybindingsFns, fn) -} - -func (self *BaseContext) AddMouseKeybindingsFn(fn types.MouseKeybindingsFn) { - self.mouseKeybindingsFns = append(self.mouseKeybindingsFns, fn) -} - -func (self *BaseContext) ClearAllAttachedControllerFunctions() { - self.keybindingsFns = nil - self.mouseKeybindingsFns = nil - self.onFocusFns = nil - self.onFocusLostFns = nil - self.onClickFn = nil - self.onClickFocusedMainViewFn = nil - self.onRenderToMainFn = nil -} - -func (self *BaseContext) AddOnClickFn(fn func() error) { - if fn != nil { - if self.onClickFn != nil { - panic("only one controller is allowed to set an onClickFn") - } - self.onClickFn = fn - } -} - -func (self *BaseContext) AddOnClickFocusedMainViewFn(fn onClickFocusedMainViewFn) { - if fn != nil { - if self.onClickFocusedMainViewFn != nil { - panic("only one controller is allowed to set an onClickFocusedMainViewFn") - } - self.onClickFocusedMainViewFn = fn - } -} - -func (self *BaseContext) GetOnClick() func() error { - return self.onClickFn -} - -func (self *BaseContext) GetOnClickFocusedMainView() onClickFocusedMainViewFn { - return self.onClickFocusedMainViewFn -} - -func (self *BaseContext) AddOnRenderToMainFn(fn func()) { - if fn != nil { - if self.onRenderToMainFn != nil { - panic("only one controller is allowed to set an onRenderToMainFn") - } - self.onRenderToMainFn = fn - } -} - -func (self *BaseContext) AddOnFocusFn(fn onFocusFn) { - if fn != nil { - self.onFocusFns = append(self.onFocusFns, fn) - } -} - -func (self *BaseContext) AddOnFocusLostFn(fn onFocusLostFn) { - if fn != nil { - self.onFocusLostFns = append(self.onFocusLostFns, fn) - } -} - -func (self *BaseContext) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - bindings := []*gocui.ViewMouseBinding{} - for i := range self.mouseKeybindingsFns { - // the first binding in the bindings array takes precedence but we want the - // last keybindingsFn to take precedence to we add them in reverse - bindings = append(bindings, self.mouseKeybindingsFns[len(self.mouseKeybindingsFns)-1-i](opts)...) - } - - return bindings -} - -func (self *BaseContext) IsFocusable() bool { - return self.focusable -} - -func (self *BaseContext) IsTransient() bool { - return self.transient -} - -func (self *BaseContext) HasControlledBounds() bool { - return self.hasControlledBounds -} - -func (self *BaseContext) NeedsRerenderOnWidthChange() types.NeedsRerenderOnWidthChangeLevel { - return self.needsRerenderOnWidthChange -} - -func (self *BaseContext) NeedsRerenderOnHeightChange() bool { - return self.needsRerenderOnHeightChange -} - -func (self *BaseContext) Title() string { - return "" -} - -func (self *BaseContext) TotalContentHeight() int { - return self.view.ViewLinesHeight() -} diff --git a/pkg/gui/context/branches_context.go b/pkg/gui/context/branches_context.go deleted file mode 100644 index d7396127529..00000000000 --- a/pkg/gui/context/branches_context.go +++ /dev/null @@ -1,93 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type BranchesContext struct { - *FilteredListViewModel[*models.Branch] - *ListContextTrait -} - -var ( - _ types.IListContext = (*BranchesContext)(nil) - _ types.DiffableContext = (*BranchesContext)(nil) -) - -func NewBranchesContext(c *ContextCommon) *BranchesContext { - viewModel := NewFilteredListViewModel( - func() []*models.Branch { return c.Model().Branches }, - func(branch *models.Branch) []string { - return []string{branch.Name} - }, - ) - - getDisplayStrings := func(_ int, _ int) [][]string { - return presentation.GetBranchListDisplayStrings( - viewModel.GetItems(), - c.State().GetItemOperation, - c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL, - c.Modes().Diffing.Ref, - c.Views().Branches.InnerWidth()+c.Views().Branches.OriginX(), - c.Tr, - c.UserConfig(), - c.Model().Worktrees, - ) - } - - self := &BranchesContext{ - FilteredListViewModel: viewModel, - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Branches, - WindowName: "branches", - Key: LOCAL_BRANCHES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - }, - c: c, - }, - } - - return self -} - -func (self *BranchesContext) GetSelectedRef() models.Ref { - branch := self.GetSelected() - if branch == nil { - return nil - } - return branch -} - -func (self *BranchesContext) GetDiffTerminals() []string { - // for our local branches we want to include both the branch and its upstream - branch := self.GetSelected() - if branch != nil { - names := []string{branch.ID()} - if branch.IsTrackingRemote() { - names = append(names, branch.ID()+"@{u}") - } - return names - } - return nil -} - -func (self *BranchesContext) RefForAdjustingLineNumberInDiff() string { - branch := self.GetSelected() - if branch != nil { - return branch.ID() - } - return "" -} - -func (self *BranchesContext) ShowBranchHeadsInSubCommits() bool { - return true -} diff --git a/pkg/gui/context/commit_files_context.go b/pkg/gui/context/commit_files_context.go deleted file mode 100644 index 52807faa9e4..00000000000 --- a/pkg/gui/context/commit_files_context.go +++ /dev/null @@ -1,108 +0,0 @@ -package context - -import ( - "fmt" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/filetree" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type CommitFilesContext struct { - *filetree.CommitFileTreeViewModel - *ListContextTrait - *DynamicTitleBuilder - *SearchTrait -} - -var ( - _ types.IListContext = (*CommitFilesContext)(nil) - _ types.DiffableContext = (*CommitFilesContext)(nil) - _ types.ISearchableContext = (*CommitFilesContext)(nil) -) - -func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext { - viewModel := filetree.NewCommitFileTreeViewModel( - func() []*models.CommitFile { return c.Model().CommitFiles }, - c.Common, - c.UserConfig().Gui.ShowFileTree, - ) - - getDisplayStrings := func(_ int, _ int) [][]string { - if viewModel.Len() == 0 { - return [][]string{{style.FgRed.Sprint("(none)")}} - } - - showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons - lines := presentation.RenderCommitFileTree(viewModel, c.Git().Patch.PatchBuilder, showFileIcons, &c.UserConfig().Gui.CustomIcons) - return lo.Map(lines, func(line string, _ int) []string { - return []string{line} - }) - } - - ctx := &CommitFilesContext{ - CommitFileTreeViewModel: viewModel, - DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.CommitFilesDynamicTitle), - SearchTrait: NewSearchTrait(c), - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - View: c.Views().CommitFiles, - WindowName: "commits", - Key: COMMIT_FILES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - Transient: true, - }), - ), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - }, - c: c, - }, - } - - ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect)) - - return ctx -} - -func (self *CommitFilesContext) GetDiffTerminals() []string { - return []string{self.GetRef().RefName()} -} - -func (self *CommitFilesContext) RefForAdjustingLineNumberInDiff() string { - if refs := self.GetRefRange(); refs != nil { - return refs.To.RefName() - } - return self.GetRef().RefName() -} - -func (self *CommitFilesContext) GetFromAndToForDiff() (string, string) { - if refs := self.GetRefRange(); refs != nil { - return refs.From.ParentRefName(), refs.To.RefName() - } - ref := self.GetRef() - return ref.ParentRefName(), ref.RefName() -} - -func (self *CommitFilesContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition { - return nil -} - -func (self *CommitFilesContext) ReInit(ref models.Ref, refRange *types.RefRange) { - self.SetRef(ref) - self.SetRefRange(refRange) - if refRange != nil { - self.SetTitleRef(fmt.Sprintf("%s-%s", refRange.From.ShortRefName(), refRange.To.ShortRefName())) - } else { - self.SetTitleRef(ref.Description()) - } - self.GetView().Title = self.Title() -} diff --git a/pkg/gui/context/commit_message_context.go b/pkg/gui/context/commit_message_context.go deleted file mode 100644 index d533e1dea40..00000000000 --- a/pkg/gui/context/commit_message_context.go +++ /dev/null @@ -1,203 +0,0 @@ -package context - -import ( - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/spf13/afero" -) - -const PreservedCommitMessageFileName = "LAZYGIT_PENDING_COMMIT" - -type CommitMessageContext struct { - c *ContextCommon - types.Context - viewModel *CommitMessageViewModel -} - -var _ types.Context = (*CommitMessageContext)(nil) - -// when selectedIndex (see below) is set to this value, it means that we're not -// currently viewing a commit message of an existing commit: instead we're making our own -// new commit message -const NoCommitIndex = -1 - -type CommitMessageViewModel struct { - // index of the commit message, where -1 is 'no commit', 0 is the HEAD commit, 1 - // is the prior commit, and so on - selectedindex int - // if true, then upon escaping from the commit message panel, we will preserve - // the message so that it's still shown next time we open the panel - preserveMessage bool - // we remember the initial message so that we can tell whether we should preserve - // the message; if it's still identical to the initial message, we don't - initialMessage string - // invoked when pressing enter in the commit message panel - onConfirm func(string, string) error - // invoked when pressing the switch-to-editor key binding - onSwitchToEditor func(string) error - - // the following two fields are used for the display of the "hooks disabled" subtitle - forceSkipHooks bool - skipHooksPrefix string - - // The message typed in before cycling through history - // We store this separately to 'preservedMessage' because 'preservedMessage' - // is specifically for committing staged files and we don't want this affected - // by cycling through history in the context of rewording an old commit. - historyMessage string -} - -func NewCommitMessageContext( - c *ContextCommon, -) *CommitMessageContext { - viewModel := &CommitMessageViewModel{} - return &CommitMessageContext{ - c: c, - viewModel: viewModel, - Context: NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - Kind: types.PERSISTENT_POPUP, - View: c.Views().CommitMessage, - WindowName: "commitMessage", - Key: COMMIT_MESSAGE_CONTEXT_KEY, - Focusable: true, - HasUncontrolledBounds: true, - }), - ), - } -} - -func (self *CommitMessageContext) SetSelectedIndex(value int) { - self.viewModel.selectedindex = value -} - -func (self *CommitMessageContext) GetSelectedIndex() int { - return self.viewModel.selectedindex -} - -func (self *CommitMessageContext) GetPreservedMessagePath() string { - return filepath.Join(self.c.Git().RepoPaths.WorktreeGitDirPath(), PreservedCommitMessageFileName) -} - -func (self *CommitMessageContext) GetPreserveMessage() bool { - return self.viewModel.preserveMessage -} - -func (self *CommitMessageContext) getPreservedMessage() (string, error) { - buf, err := afero.ReadFile(self.c.Fs, self.GetPreservedMessagePath()) - if os.IsNotExist(err) { - return "", nil - } - if err != nil { - return "", err - } - return string(buf), nil -} - -func (self *CommitMessageContext) GetPreservedMessageAndLogError() string { - msg, err := self.getPreservedMessage() - if err != nil { - self.c.Log.Errorf("error when retrieving persisted commit message: %v", err) - } - return msg -} - -func (self *CommitMessageContext) setPreservedMessage(message string) error { - preservedFilePath := self.GetPreservedMessagePath() - - if len(message) == 0 { - err := self.c.Fs.Remove(preservedFilePath) - if os.IsNotExist(err) { - return nil - } - return err - } - - return afero.WriteFile(self.c.Fs, preservedFilePath, []byte(message), 0o644) -} - -func (self *CommitMessageContext) SetPreservedMessageAndLogError(message string) { - if err := self.setPreservedMessage(message); err != nil { - self.c.Log.Errorf("error when persisting commit message: %v", err) - } -} - -func (self *CommitMessageContext) GetInitialMessage() string { - return strings.TrimSpace(self.viewModel.initialMessage) -} - -func (self *CommitMessageContext) GetHistoryMessage() string { - return self.viewModel.historyMessage -} - -func (self *CommitMessageContext) SetHistoryMessage(message string) { - self.viewModel.historyMessage = message -} - -func (self *CommitMessageContext) OnConfirm(summary string, description string) error { - return self.viewModel.onConfirm(summary, description) -} - -func (self *CommitMessageContext) SetPanelState( - index int, - summaryTitle string, - descriptionTitle string, - preserveMessage bool, - initialMessage string, - onConfirm func(string, string) error, - onSwitchToEditor func(string) error, - forceSkipHooks bool, - skipHooksPrefix string, -) { - self.viewModel.selectedindex = index - self.viewModel.preserveMessage = preserveMessage - self.viewModel.initialMessage = initialMessage - self.viewModel.onConfirm = onConfirm - self.viewModel.onSwitchToEditor = onSwitchToEditor - self.viewModel.forceSkipHooks = forceSkipHooks - self.viewModel.skipHooksPrefix = skipHooksPrefix - self.GetView().Title = summaryTitle - self.c.Views().CommitDescription.Title = descriptionTitle - - self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionSubTitle, - map[string]string{ - "togglePanelKeyBinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.TogglePanel), - "commitMenuKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.CommitMessage.CommitMenu), - }) - - self.c.Views().CommitDescription.Visible = true -} - -func (self *CommitMessageContext) RenderSubtitle() { - skipHookPrefix := self.viewModel.skipHooksPrefix - subject := self.c.Views().CommitMessage.TextArea.GetContent() - var subtitle string - if self.viewModel.forceSkipHooks || (skipHookPrefix != "" && strings.HasPrefix(subject, skipHookPrefix)) { - subtitle = self.c.Tr.CommitHooksDisabledSubTitle - } - if self.c.UserConfig().Gui.CommitLength.Show { - if subtitle != "" { - subtitle += "─" - } - subtitle += getBufferLength(subject) - } - self.c.Views().CommitMessage.Subtitle = subtitle -} - -func getBufferLength(subject string) string { - return " " + strconv.Itoa(strings.Count(subject, "")-1) + " " -} - -func (self *CommitMessageContext) SwitchToEditor(message string) error { - return self.viewModel.onSwitchToEditor(message) -} - -func (self *CommitMessageContext) CanSwitchToEditor() bool { - return self.viewModel.onSwitchToEditor != nil -} diff --git a/pkg/gui/context/confirmation_context.go b/pkg/gui/context/confirmation_context.go deleted file mode 100644 index 69c498e49b4..00000000000 --- a/pkg/gui/context/confirmation_context.go +++ /dev/null @@ -1,35 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type ConfirmationContext struct { - *SimpleContext - c *ContextCommon - - State ConfirmationContextState -} - -type ConfirmationContextState struct { - OnConfirm func() error - OnClose func() error -} - -var _ types.Context = (*ConfirmationContext)(nil) - -func NewConfirmationContext( - c *ContextCommon, -) *ConfirmationContext { - return &ConfirmationContext{ - c: c, - SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Confirmation, - WindowName: "confirmation", - Key: CONFIRMATION_CONTEXT_KEY, - Kind: types.TEMPORARY_POPUP, - Focusable: true, - HasUncontrolledBounds: true, - })), - } -} diff --git a/pkg/gui/context/context.go b/pkg/gui/context/context.go deleted file mode 100644 index 8af05e36fe3..00000000000 --- a/pkg/gui/context/context.go +++ /dev/null @@ -1,175 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -const ( - // used as a nil value when passing a context key as an arg - NO_CONTEXT types.ContextKey = "none" - - GLOBAL_CONTEXT_KEY types.ContextKey = "global" - STATUS_CONTEXT_KEY types.ContextKey = "status" - SNAKE_CONTEXT_KEY types.ContextKey = "snake" - FILES_CONTEXT_KEY types.ContextKey = "files" - LOCAL_BRANCHES_CONTEXT_KEY types.ContextKey = "localBranches" - REMOTES_CONTEXT_KEY types.ContextKey = "remotes" - WORKTREES_CONTEXT_KEY types.ContextKey = "worktrees" - REMOTE_BRANCHES_CONTEXT_KEY types.ContextKey = "remoteBranches" - TAGS_CONTEXT_KEY types.ContextKey = "tags" - LOCAL_COMMITS_CONTEXT_KEY types.ContextKey = "commits" - REFLOG_COMMITS_CONTEXT_KEY types.ContextKey = "reflogCommits" - SUB_COMMITS_CONTEXT_KEY types.ContextKey = "subCommits" - COMMIT_FILES_CONTEXT_KEY types.ContextKey = "commitFiles" - STASH_CONTEXT_KEY types.ContextKey = "stash" - NORMAL_MAIN_CONTEXT_KEY types.ContextKey = "normal" - NORMAL_SECONDARY_CONTEXT_KEY types.ContextKey = "normalSecondary" - STAGING_MAIN_CONTEXT_KEY types.ContextKey = "staging" - STAGING_SECONDARY_CONTEXT_KEY types.ContextKey = "stagingSecondary" - PATCH_BUILDING_MAIN_CONTEXT_KEY types.ContextKey = "patchBuilding" - PATCH_BUILDING_SECONDARY_CONTEXT_KEY types.ContextKey = "patchBuildingSecondary" - MERGE_CONFLICTS_CONTEXT_KEY types.ContextKey = "mergeConflicts" - - // these shouldn't really be needed for anything but I'm giving them unique keys nonetheless - OPTIONS_CONTEXT_KEY types.ContextKey = "options" - APP_STATUS_CONTEXT_KEY types.ContextKey = "appStatus" - SEARCH_PREFIX_CONTEXT_KEY types.ContextKey = "searchPrefix" - INFORMATION_CONTEXT_KEY types.ContextKey = "information" - LIMIT_CONTEXT_KEY types.ContextKey = "limit" - STATUS_SPACER1_CONTEXT_KEY types.ContextKey = "statusSpacer1" - STATUS_SPACER2_CONTEXT_KEY types.ContextKey = "statusSpacer2" - - MENU_CONTEXT_KEY types.ContextKey = "menu" - CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation" - PROMPT_CONTEXT_KEY types.ContextKey = "prompt" - SEARCH_CONTEXT_KEY types.ContextKey = "search" - COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage" - COMMIT_DESCRIPTION_CONTEXT_KEY types.ContextKey = "commitDescription" - SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules" - SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions" - COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog" -) - -var AllContextKeys = []types.ContextKey{ - GLOBAL_CONTEXT_KEY, - STATUS_CONTEXT_KEY, - FILES_CONTEXT_KEY, - LOCAL_BRANCHES_CONTEXT_KEY, - REMOTES_CONTEXT_KEY, - WORKTREES_CONTEXT_KEY, - REMOTE_BRANCHES_CONTEXT_KEY, - TAGS_CONTEXT_KEY, - LOCAL_COMMITS_CONTEXT_KEY, - REFLOG_COMMITS_CONTEXT_KEY, - SUB_COMMITS_CONTEXT_KEY, - COMMIT_FILES_CONTEXT_KEY, - STASH_CONTEXT_KEY, - NORMAL_MAIN_CONTEXT_KEY, - NORMAL_SECONDARY_CONTEXT_KEY, - STAGING_MAIN_CONTEXT_KEY, - STAGING_SECONDARY_CONTEXT_KEY, - PATCH_BUILDING_MAIN_CONTEXT_KEY, - PATCH_BUILDING_SECONDARY_CONTEXT_KEY, - MERGE_CONFLICTS_CONTEXT_KEY, - - MENU_CONTEXT_KEY, - CONFIRMATION_CONTEXT_KEY, - PROMPT_CONTEXT_KEY, - SEARCH_CONTEXT_KEY, - COMMIT_MESSAGE_CONTEXT_KEY, - SUBMODULES_CONTEXT_KEY, - SUGGESTIONS_CONTEXT_KEY, - COMMAND_LOG_CONTEXT_KEY, -} - -type ContextTree struct { - Global types.Context - Status types.Context - Snake types.Context - Files *WorkingTreeContext - Menu *MenuContext - Branches *BranchesContext - Tags *TagsContext - LocalCommits *LocalCommitsContext - CommitFiles *CommitFilesContext - Remotes *RemotesContext - Worktrees *WorktreesContext - Submodules *SubmodulesContext - RemoteBranches *RemoteBranchesContext - ReflogCommits *ReflogCommitsContext - SubCommits *SubCommitsContext - Stash *StashContext - Suggestions *SuggestionsContext - Normal *MainContext - NormalSecondary *MainContext - Staging *PatchExplorerContext - StagingSecondary *PatchExplorerContext - CustomPatchBuilder *PatchExplorerContext - CustomPatchBuilderSecondary types.Context - MergeConflicts *MergeConflictsContext - Confirmation *ConfirmationContext - Prompt *PromptContext - CommitMessage *CommitMessageContext - CommitDescription types.Context - CommandLog types.Context - - // display contexts - AppStatus types.Context - Options types.Context - SearchPrefix types.Context - Search types.Context - Information types.Context - Limit types.Context - StatusSpacer1 types.Context - StatusSpacer2 types.Context -} - -// the order of this decides which context is initially at the top of its window -func (self *ContextTree) Flatten() []types.Context { - return []types.Context{ - self.Global, - self.Status, - self.Snake, - self.Submodules, - self.Worktrees, - self.Files, - self.SubCommits, - self.Remotes, - self.RemoteBranches, - self.Tags, - self.Branches, - self.CommitFiles, - self.ReflogCommits, - self.LocalCommits, - self.Stash, - self.Menu, - self.Confirmation, - self.Prompt, - self.CommitMessage, - self.CommitDescription, - - self.MergeConflicts, - self.StagingSecondary, - self.Staging, - self.CustomPatchBuilderSecondary, - self.CustomPatchBuilder, - self.NormalSecondary, - self.Normal, - - self.Suggestions, - self.CommandLog, - self.AppStatus, - self.Options, - self.SearchPrefix, - self.Search, - self.Information, - self.Limit, - self.StatusSpacer1, - self.StatusSpacer2, - } -} - -type TabView struct { - Tab string - ViewName string -} diff --git a/pkg/gui/context/context_common.go b/pkg/gui/context/context_common.go deleted file mode 100644 index 9425280a493..00000000000 --- a/pkg/gui/context/context_common.go +++ /dev/null @@ -1,11 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type ContextCommon struct { - *common.Common - types.IGuiCommon -} diff --git a/pkg/gui/context/dynamic_title_builder.go b/pkg/gui/context/dynamic_title_builder.go deleted file mode 100644 index ee4facad2b8..00000000000 --- a/pkg/gui/context/dynamic_title_builder.go +++ /dev/null @@ -1,23 +0,0 @@ -package context - -import "fmt" - -type DynamicTitleBuilder struct { - formatStr string // e.g. 'remote branches for %s' - - titleRef string // e.g. 'origin' -} - -func NewDynamicTitleBuilder(formatStr string) *DynamicTitleBuilder { - return &DynamicTitleBuilder{ - formatStr: formatStr, - } -} - -func (self *DynamicTitleBuilder) SetTitleRef(titleRef string) { - self.titleRef = titleRef -} - -func (self *DynamicTitleBuilder) Title() string { - return fmt.Sprintf(self.formatStr, self.titleRef) -} diff --git a/pkg/gui/context/filtered_list.go b/pkg/gui/context/filtered_list.go deleted file mode 100644 index e8112f95b04..00000000000 --- a/pkg/gui/context/filtered_list.go +++ /dev/null @@ -1,121 +0,0 @@ -package context - -import ( - "strings" - - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/sahilm/fuzzy" - "github.com/samber/lo" - "github.com/sasha-s/go-deadlock" -) - -type FilteredList[T any] struct { - filteredIndices []int // if nil, we are not filtering - - getList func() []T - getFilterFields func(T) []string - preprocessFilter func(string) string - filter string - - mutex deadlock.Mutex -} - -func NewFilteredList[T any](getList func() []T, getFilterFields func(T) []string) *FilteredList[T] { - return &FilteredList[T]{ - getList: getList, - getFilterFields: getFilterFields, - } -} - -func (self *FilteredList[T]) SetPreprocessFilterFunc(preprocessFilter func(string) string) { - self.preprocessFilter = preprocessFilter -} - -func (self *FilteredList[T]) GetFilter() string { - return self.filter -} - -func (self *FilteredList[T]) SetFilter(filter string, useFuzzySearch bool) { - self.filter = filter - - self.applyFilter(useFuzzySearch) -} - -func (self *FilteredList[T]) ClearFilter() { - self.SetFilter("", false) -} - -func (self *FilteredList[T]) ReApplyFilter(useFuzzySearch bool) { - self.applyFilter(useFuzzySearch) -} - -func (self *FilteredList[T]) IsFiltering() bool { - return self.filter != "" -} - -func (self *FilteredList[T]) GetFilteredList() []T { - if self.filteredIndices == nil { - return self.getList() - } - return utils.ValuesAtIndices(self.getList(), self.filteredIndices) -} - -// TODO: update to just 'Len' -func (self *FilteredList[T]) UnfilteredLen() int { - return len(self.getList()) -} - -type fuzzySource[T any] struct { - list []T - getFilterFields func(T) []string -} - -var _ fuzzy.Source = &fuzzySource[string]{} - -func (self *fuzzySource[T]) String(i int) string { - return strings.Join(self.getFilterFields(self.list[i]), " ") -} - -func (self *fuzzySource[T]) Len() int { - return len(self.list) -} - -func (self *FilteredList[T]) applyFilter(useFuzzySearch bool) { - self.mutex.Lock() - defer self.mutex.Unlock() - - filter := self.filter - if self.preprocessFilter != nil { - filter = self.preprocessFilter(filter) - } - - if filter == "" { - self.filteredIndices = nil - } else { - source := &fuzzySource[T]{ - list: self.getList(), - getFilterFields: self.getFilterFields, - } - - matches := utils.FindFrom(filter, source, useFuzzySearch) - self.filteredIndices = lo.Map(matches, func(match fuzzy.Match, _ int) int { - return match.Index - }) - } -} - -func (self *FilteredList[T]) UnfilteredIndex(index int) int { - self.mutex.Lock() - defer self.mutex.Unlock() - - if self.filteredIndices == nil { - return index - } - - // we use -1 when there are no items - if index == -1 { - return -1 - } - - return self.filteredIndices[index] -} diff --git a/pkg/gui/context/filtered_list_view_model.go b/pkg/gui/context/filtered_list_view_model.go deleted file mode 100644 index ce2f8ac3640..00000000000 --- a/pkg/gui/context/filtered_list_view_model.go +++ /dev/null @@ -1,42 +0,0 @@ -package context - -import "github.com/jesseduffield/lazygit/pkg/i18n" - -type FilteredListViewModel[T HasID] struct { - *FilteredList[T] - *ListViewModel[T] - *SearchHistory -} - -func NewFilteredListViewModel[T HasID](getList func() []T, getFilterFields func(T) []string) *FilteredListViewModel[T] { - filteredList := NewFilteredList(getList, getFilterFields) - - self := &FilteredListViewModel[T]{ - FilteredList: filteredList, - SearchHistory: NewSearchHistory(), - } - - listViewModel := NewListViewModel(filteredList.GetFilteredList) - - self.ListViewModel = listViewModel - - return self -} - -// used for type switch -func (self *FilteredListViewModel[T]) IsFilterableContext() {} - -func (self *FilteredListViewModel[T]) ClearFilter() { - // Set the selected line index to the unfiltered index of the currently selected line, - // so that the current item is still selected after the filter is cleared. - unfilteredIndex := self.FilteredList.UnfilteredIndex(self.GetSelectedLineIdx()) - - self.FilteredList.ClearFilter() - - self.SetSelection(unfilteredIndex) -} - -// Default implementation of most filterable contexts. Can be overridden if needed. -func (self *FilteredListViewModel[T]) FilterPrefix(tr *i18n.TranslationSet) string { - return tr.FilterPrefix -} diff --git a/pkg/gui/context/history_trait.go b/pkg/gui/context/history_trait.go deleted file mode 100644 index f850a3b774c..00000000000 --- a/pkg/gui/context/history_trait.go +++ /dev/null @@ -1,20 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// Maintains a list of strings that have previously been searched/filtered for -type SearchHistory struct { - history *utils.HistoryBuffer[string] -} - -func NewSearchHistory() *SearchHistory { - return &SearchHistory{ - history: utils.NewHistoryBuffer[string](1000), - } -} - -func (self *SearchHistory) GetSearchHistory() *utils.HistoryBuffer[string] { - return self.history -} diff --git a/pkg/gui/context/list_context_trait.go b/pkg/gui/context/list_context_trait.go deleted file mode 100644 index cc1d402c067..00000000000 --- a/pkg/gui/context/list_context_trait.go +++ /dev/null @@ -1,155 +0,0 @@ -package context - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type ListContextTrait struct { - types.Context - ListRenderer - - c *ContextCommon - // Some contexts, like the commit context, will highlight the path from the selected commit - // to its parents, because it's ambiguous otherwise. For these, we need to refresh the viewport - // so that we show the highlighted path. - // TODO: now that we allow scrolling, we should be smarter about what gets refreshed: - // we should find out exactly which lines are now part of the path and refresh those. - // We should also keep track of the previous path and refresh those lines too. - refreshViewportOnChange bool - // If this is true, we only render the visible lines of the list. Useful for lists that can - // get very long, because it can save a lot of memory - renderOnlyVisibleLines bool -} - -func (self *ListContextTrait) IsListContext() {} - -func (self *ListContextTrait) FocusLine() { - self.Context.FocusLine() - - // Doing this at the end of the layout function because we need the view to be - // resized before we focus the line, otherwise if we're in accordion mode - // the view could be squashed and won't know how to adjust the cursor/origin. - // Also, refreshing the viewport needs to happen after the view has been resized. - self.c.AfterLayout(func() error { - oldOrigin, _ := self.GetViewTrait().ViewPortYBounds() - - self.GetViewTrait().FocusPoint( - self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx())) - - selectRangeIndex, isSelectingRange := self.list.GetRangeStartIdx() - if isSelectingRange { - selectRangeIndex = self.ModelIndexToViewIndex(selectRangeIndex) - self.GetViewTrait().SetRangeSelectStart(selectRangeIndex) - } else { - self.GetViewTrait().CancelRangeSelect() - } - - if self.refreshViewportOnChange { - self.refreshViewport() - } else if self.renderOnlyVisibleLines { - newOrigin, _ := self.GetViewTrait().ViewPortYBounds() - if oldOrigin != newOrigin { - self.HandleRender() - } - } - return nil - }) - - self.setFooter() -} - -func (self *ListContextTrait) refreshViewport() { - startIdx, length := self.GetViewTrait().ViewPortYBounds() - content := self.renderLines(startIdx, startIdx+length) - self.GetViewTrait().SetViewPortContent(content) -} - -func (self *ListContextTrait) setFooter() { - self.GetViewTrait().SetFooter(formatListFooter(self.list.GetSelectedLineIdx(), self.list.Len())) -} - -func formatListFooter(selectedLineIdx int, length int) string { - return fmt.Sprintf("%d of %d", selectedLineIdx+1, length) -} - -func (self *ListContextTrait) HandleFocus(opts types.OnFocusOpts) { - self.FocusLine() - - self.GetViewTrait().SetHighlight(self.list.Len() > 0) - - self.Context.HandleFocus(opts) -} - -func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) { - self.GetViewTrait().SetOriginX(0) - - if self.refreshViewportOnChange { - self.refreshViewport() - } - - self.Context.HandleFocusLost(opts) -} - -// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view -func (self *ListContextTrait) HandleRender() { - self.list.ClampSelection() - if self.renderOnlyVisibleLines { - // Rendering only the visible area can save a lot of cell memory for - // those views that support it. - totalLength := self.list.Len() - if self.getNonModelItems != nil { - totalLength += len(self.getNonModelItems()) - } - self.GetViewTrait().SetContentLineCount(totalLength) - startIdx, length := self.GetViewTrait().ViewPortYBounds() - content := self.renderLines(startIdx, startIdx+length) - self.GetViewTrait().SetViewPortContentAndClearEverythingElse(content) - } else { - content := self.renderLines(-1, -1) - self.GetViewTrait().SetContent(content) - } - self.c.Render() - self.setFooter() -} - -func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error { - self.GetList().SetSelection(self.ViewIndexToModelIndex(selectedLineIdx)) - self.HandleFocus(types.OnFocusOpts{}) - return nil -} - -func (self *ListContextTrait) IsItemVisible(item types.HasUrn) bool { - startIdx, length := self.GetViewTrait().ViewPortYBounds() - selectionStart := self.ViewIndexToModelIndex(startIdx) - selectionEnd := self.ViewIndexToModelIndex(startIdx + length) - for i := selectionStart; i < selectionEnd; i++ { - iterItem := self.GetList().GetItem(i) - if iterItem != nil && iterItem.URN() == item.URN() { - return true - } - } - return false -} - -// By default, list contexts supports range select -func (self *ListContextTrait) RangeSelectEnabled() bool { - return true -} - -func (self *ListContextTrait) RenderOnlyVisibleLines() bool { - return self.renderOnlyVisibleLines -} - -func (self *ListContextTrait) TotalContentHeight() int { - result := self.list.Len() - if self.getNonModelItems != nil { - result += len(self.getNonModelItems()) - } - return result -} - -func (self *ListContextTrait) IndexForGotoBottom() int { - return self.list.Len() - 1 -} diff --git a/pkg/gui/context/list_renderer.go b/pkg/gui/context/list_renderer.go deleted file mode 100644 index e863045e0b4..00000000000 --- a/pkg/gui/context/list_renderer.go +++ /dev/null @@ -1,135 +0,0 @@ -package context - -import ( - "strings" - - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "golang.org/x/exp/slices" -) - -type NonModelItem struct { - // Where in the model this should be inserted - Index int - // Content to render - Content string - // The column from which to render the item - Column int -} - -type ListRenderer struct { - list types.IList - // Function to get the display strings for each model item in the given - // range. startIdx and endIdx are model indices. For each model item, return - // an array of strings, one for each column; the list renderer will take - // care of aligning the columns appropriately. - getDisplayStrings func(startIdx int, endIdx int) [][]string - // Alignment for each column. If nil, the default is left alignment - getColumnAlignments func() []utils.Alignment - // Function to insert non-model items (e.g. section headers). If nil, no - // such items are inserted - getNonModelItems func() []*NonModelItem - - // The remaining fields are private and shouldn't be initialized by clients - numNonModelItems int - viewIndicesByModelIndex []int - modelIndicesByViewIndex []int - columnPositions []int -} - -func (self *ListRenderer) GetList() types.IList { - return self.list -} - -func (self *ListRenderer) ModelIndexToViewIndex(modelIndex int) int { - modelIndex = lo.Clamp(modelIndex, 0, self.list.Len()) - if self.viewIndicesByModelIndex != nil { - return self.viewIndicesByModelIndex[modelIndex] - } - - return modelIndex -} - -func (self *ListRenderer) ViewIndexToModelIndex(viewIndex int) int { - viewIndex = lo.Clamp(viewIndex, 0, self.list.Len()+self.numNonModelItems) - if self.modelIndicesByViewIndex != nil { - return self.modelIndicesByViewIndex[viewIndex] - } - - return viewIndex -} - -func (self *ListRenderer) ColumnPositions() []int { - return self.columnPositions -} - -// startIdx and endIdx are view indices, not model indices. If you want to -// render the whole list, pass -1 for both. -func (self *ListRenderer) renderLines(startIdx int, endIdx int) string { - var columnAlignments []utils.Alignment - if self.getColumnAlignments != nil { - columnAlignments = self.getColumnAlignments() - } - nonModelItems := []*NonModelItem{} - self.numNonModelItems = 0 - if self.getNonModelItems != nil { - nonModelItems = self.getNonModelItems() - self.prepareConversionArrays(nonModelItems) - } - startModelIdx := 0 - if startIdx == -1 { - startIdx = 0 - } else { - startModelIdx = self.ViewIndexToModelIndex(startIdx) - } - endModelIdx := self.list.Len() - if endIdx == -1 { - endIdx = endModelIdx + len(nonModelItems) - } else { - endModelIdx = self.ViewIndexToModelIndex(endIdx) - } - lines, columnPositions := utils.RenderDisplayStrings( - self.getDisplayStrings(startModelIdx, endModelIdx), - columnAlignments) - self.columnPositions = columnPositions - lines = self.insertNonModelItems(nonModelItems, endIdx, startIdx, lines, columnPositions) - return strings.Join(lines, "\n") -} - -func (self *ListRenderer) prepareConversionArrays(nonModelItems []*NonModelItem) { - self.numNonModelItems = len(nonModelItems) - viewIndicesByModelIndex := lo.Range(self.list.Len() + 1) - modelIndicesByViewIndex := lo.Range(self.list.Len() + 1) - offset := 0 - for _, item := range nonModelItems { - for i := item.Index; i <= self.list.Len(); i++ { - viewIndicesByModelIndex[i]++ - } - modelIndicesByViewIndex = slices.Insert( - modelIndicesByViewIndex, item.Index+offset, modelIndicesByViewIndex[item.Index+offset]) - offset++ - } - self.viewIndicesByModelIndex = viewIndicesByModelIndex - self.modelIndicesByViewIndex = modelIndicesByViewIndex -} - -func (self *ListRenderer) insertNonModelItems( - nonModelItems []*NonModelItem, endIdx int, startIdx int, lines []string, columnPositions []int, -) []string { - offset := 0 - for _, item := range nonModelItems { - if item.Index+offset >= endIdx { - break - } - if item.Index+offset >= startIdx { - padding := "" - if columnPositions != nil { - padding = strings.Repeat(" ", columnPositions[item.Column]) - } - lines = slices.Insert(lines, item.Index+offset-startIdx, padding+item.Content) - } - offset++ - } - return lines -} diff --git a/pkg/gui/context/list_renderer_test.go b/pkg/gui/context/list_renderer_test.go deleted file mode 100644 index 08af680ffd3..00000000000 --- a/pkg/gui/context/list_renderer_test.go +++ /dev/null @@ -1,269 +0,0 @@ -package context - -import ( - "fmt" - "strings" - "testing" - - "github.com/samber/lo" - "github.com/stretchr/testify/assert" -) - -// wrapping string in my own type to give it an ID method which is required for list items -type mystring string - -func (self mystring) ID() string { - return string(self) -} - -func TestListRenderer_renderLines(t *testing.T) { - scenarios := []struct { - name string - modelStrings []mystring - nonModelIndices []int - startIdx int - endIdx int - expectedOutput string - }{ - { - name: "Render whole list", - modelStrings: []mystring{"a", "b", "c"}, - startIdx: 0, - endIdx: 3, - expectedOutput: ` - a - b - c`, - }, - { - name: "Partial list, beginning", - modelStrings: []mystring{"a", "b", "c"}, - startIdx: 0, - endIdx: 2, - expectedOutput: ` - a - b`, - }, - { - name: "Partial list, end", - modelStrings: []mystring{"a", "b", "c"}, - startIdx: 1, - endIdx: 3, - expectedOutput: ` - b - c`, - }, - { - name: "Pass an endIdx greater than the model length", - modelStrings: []mystring{"a", "b", "c"}, - startIdx: 2, - endIdx: 5, - expectedOutput: ` - c`, - }, - { - name: "Whole list with section headers", - modelStrings: []mystring{"a", "b", "c"}, - nonModelIndices: []int{1, 3}, - startIdx: 0, - endIdx: 5, - expectedOutput: ` - a - --- 1 (0) --- - b - c - --- 3 (1) ---`, - }, - { - name: "Multiple consecutive headers", - modelStrings: []mystring{"a", "b", "c"}, - nonModelIndices: []int{0, 0, 2, 2, 2}, - startIdx: 0, - endIdx: 8, - expectedOutput: ` - --- 0 (0) --- - --- 0 (1) --- - a - b - --- 2 (2) --- - --- 2 (3) --- - --- 2 (4) --- - c`, - }, - { - name: "Partial list with headers, beginning", - modelStrings: []mystring{"a", "b", "c"}, - nonModelIndices: []int{1, 3}, - startIdx: 0, - endIdx: 3, - expectedOutput: ` - a - --- 1 (0) --- - b`, - }, - { - name: "Partial list with headers, end (beyond end index)", - modelStrings: []mystring{"a", "b", "c"}, - nonModelIndices: []int{1, 3}, - startIdx: 2, - endIdx: 7, - expectedOutput: ` - b - c - --- 3 (1) ---`, - }, - } - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - viewModel := NewListViewModel(func() []mystring { return s.modelStrings }) - var getNonModelItems func() []*NonModelItem - if s.nonModelIndices != nil { - getNonModelItems = func() []*NonModelItem { - return lo.Map(s.nonModelIndices, func(modelIndex int, nonModelIndex int) *NonModelItem { - return &NonModelItem{ - Index: modelIndex, - Content: fmt.Sprintf("--- %d (%d) ---", modelIndex, nonModelIndex), - } - }) - } - } - self := &ListRenderer{ - list: viewModel, - getDisplayStrings: func(startIdx int, endIdx int) [][]string { - return lo.Map(s.modelStrings[startIdx:endIdx], - func(s mystring, _ int) []string { return []string{string(s)} }) - }, - getNonModelItems: getNonModelItems, - } - - expectedOutput := strings.Join(lo.Map( - strings.Split(strings.TrimPrefix(s.expectedOutput, "\n"), "\n"), - func(line string, _ int) string { return strings.TrimSpace(line) }), "\n") - - assert.Equal(t, expectedOutput, self.renderLines(s.startIdx, s.endIdx)) - }) - } -} - -type myint int - -func (self myint) ID() string { - return fmt.Sprint(int(self)) -} - -func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) { - scenarios := []struct { - name string - numModelItems int - nonModelIndices []int - - modelIndices []int - expectedViewIndices []int - - viewIndices []int - expectedModelIndices []int - }{ - { - name: "no headers (no getNonModelItems provided)", - numModelItems: 3, - nonModelIndices: nil, // no get - - modelIndices: []int{-1, 0, 1, 2, 3, 4}, - expectedViewIndices: []int{0, 0, 1, 2, 3, 3}, - - viewIndices: []int{-1, 0, 1, 2, 3, 4}, - expectedModelIndices: []int{0, 0, 1, 2, 3, 3}, - }, - { - name: "no headers (getNonModelItems returns zero items)", - numModelItems: 3, - nonModelIndices: []int{}, - - modelIndices: []int{-1, 0, 1, 2, 3, 4}, - expectedViewIndices: []int{0, 0, 1, 2, 3, 3}, - - viewIndices: []int{-1, 0, 1, 2, 3, 4}, - expectedModelIndices: []int{0, 0, 1, 2, 3, 3}, - }, - { - name: "basic", - numModelItems: 3, - nonModelIndices: []int{1, 2}, - - /* - 0: model 0 - 1: --- header 0 --- - 2: model 1 - 3: --- header 1 --- - 4: model 2 - */ - - modelIndices: []int{-1, 0, 1, 2, 3, 4}, - expectedViewIndices: []int{0, 0, 2, 4, 5, 5}, - - viewIndices: []int{-1, 0, 1, 2, 3, 4, 5, 6}, - expectedModelIndices: []int{0, 0, 1, 1, 2, 2, 3, 3}, - }, - { - name: "consecutive section headers", - numModelItems: 3, - nonModelIndices: []int{0, 0, 2, 2, 2, 3, 3}, - - /* - 0: --- header 0 --- - 1: --- header 1 --- - 2: model 0 - 3: model 1 - 4: --- header 2 --- - 5: --- header 3 --- - 6: --- header 4 --- - 7: model 2 - 8: --- header 5 --- - 9: --- header 6 --- - */ - modelIndices: []int{-1, 0, 1, 2, 3, 4}, - expectedViewIndices: []int{2, 2, 3, 7, 10, 10}, - - viewIndices: []int{-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, - expectedModelIndices: []int{0, 0, 0, 0, 1, 2, 2, 2, 2, 3, 3, 3, 3}, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - // Expect lists of equal length for each test: - assert.Equal(t, len(s.modelIndices), len(s.expectedViewIndices)) - assert.Equal(t, len(s.viewIndices), len(s.expectedModelIndices)) - - modelInts := lo.Map(lo.Range(s.numModelItems), func(i int, _ int) myint { return myint(i) }) - viewModel := NewListViewModel(func() []myint { return modelInts }) - var getNonModelItems func() []*NonModelItem - if s.nonModelIndices != nil { - getNonModelItems = func() []*NonModelItem { - return lo.Map(s.nonModelIndices, func(modelIndex int, _ int) *NonModelItem { - return &NonModelItem{Index: modelIndex, Content: ""} - }) - } - } - self := &ListRenderer{ - list: viewModel, - getDisplayStrings: func(startIdx int, endIdx int) [][]string { - return lo.Map(modelInts[startIdx:endIdx], - func(i myint, _ int) []string { return []string{fmt.Sprint(i)} }) - }, - getNonModelItems: getNonModelItems, - } - - // Need to render first so that it knows the non-model items - self.renderLines(-1, -1) - - for i := range len(s.modelIndices) { - assert.Equal(t, s.expectedViewIndices[i], self.ModelIndexToViewIndex(s.modelIndices[i])) - } - - for i := range len(s.viewIndices) { - assert.Equal(t, s.expectedModelIndices[i], self.ViewIndexToModelIndex(s.viewIndices[i])) - } - }) - } -} diff --git a/pkg/gui/context/list_view_model.go b/pkg/gui/context/list_view_model.go deleted file mode 100644 index 1cb2ee6486d..00000000000 --- a/pkg/gui/context/list_view_model.go +++ /dev/null @@ -1,75 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/context/traits" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type HasID interface { - ID() string -} - -type ListViewModel[T HasID] struct { - *traits.ListCursor - getModel func() []T -} - -func NewListViewModel[T HasID](getModel func() []T) *ListViewModel[T] { - self := &ListViewModel[T]{ - getModel: getModel, - } - - self.ListCursor = traits.NewListCursor(func() int { return len(getModel()) }) - - return self -} - -func (self *ListViewModel[T]) GetSelected() T { - if self.Len() == 0 { - return Zero[T]() - } - - return self.getModel()[self.GetSelectedLineIdx()] -} - -func (self *ListViewModel[T]) GetSelectedItemId() string { - if self.Len() == 0 { - return "" - } - - return self.GetSelected().ID() -} - -func (self *ListViewModel[T]) GetSelectedItems() ([]T, int, int) { - if self.Len() == 0 { - return nil, -1, -1 - } - - startIdx, endIdx := self.GetSelectionRange() - - return self.getModel()[startIdx : endIdx+1], startIdx, endIdx -} - -func (self *ListViewModel[T]) GetSelectedItemIds() ([]string, int, int) { - selectedItems, startIdx, endIdx := self.GetSelectedItems() - - ids := lo.Map(selectedItems, func(item T, _ int) string { - return item.ID() - }) - - return ids, startIdx, endIdx -} - -func (self *ListViewModel[T]) GetItems() []T { - return self.getModel() -} - -func Zero[T any]() T { - return *new(T) -} - -func (self *ListViewModel[T]) GetItem(index int) types.HasUrn { - item := self.getModel()[index] - return any(item).(types.HasUrn) -} diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go deleted file mode 100644 index e873663c30d..00000000000 --- a/pkg/gui/context/local_commits_context.go +++ /dev/null @@ -1,310 +0,0 @@ -package context - -import ( - "fmt" - "log" - "strings" - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type LocalCommitsContext struct { - *LocalCommitsViewModel - *ListContextTrait - *SearchTrait -} - -var ( - _ types.IListContext = (*LocalCommitsContext)(nil) - _ types.DiffableContext = (*LocalCommitsContext)(nil) - _ types.ISearchableContext = (*LocalCommitsContext)(nil) -) - -func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext { - viewModel := NewLocalCommitsViewModel( - func() []*models.Commit { return c.Model().Commits }, - c, - ) - - getDisplayStrings := func(startIdx int, endIdx int) [][]string { - var selectedCommitHashPtr *string - - if c.Context().Current().GetKey() == LOCAL_COMMITS_CONTEXT_KEY { - selectedCommit := viewModel.GetSelected() - if selectedCommit != nil { - selectedCommitHashPtr = selectedCommit.HashPtr() - } - } - - hasRebaseUpdateRefsConfig := c.Git().Config.GetRebaseUpdateRefs() - - return presentation.GetCommitListDisplayStrings( - c.Common, - c.Model().Commits, - c.Model().Branches, - c.Model().CheckedOutBranch, - hasRebaseUpdateRefsConfig, - c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL, - c.Modes().CherryPicking.SelectedHashSet(), - c.Modes().Diffing.Ref, - c.Modes().MarkedBaseCommit.GetHash(), - c.UserConfig().Gui.TimeFormat, - c.UserConfig().Gui.ShortTimeFormat, - time.Now(), - c.UserConfig().Git.ParseEmoji, - selectedCommitHashPtr, - startIdx, - endIdx, - shouldShowGraph(c), - c.Model().BisectInfo, - ) - } - - getNonModelItems := func() []*NonModelItem { - result := []*NonModelItem{} - if c.Model().WorkingTreeStateAtLastCommitRefresh.CanShowTodos() { - if c.Model().WorkingTreeStateAtLastCommitRefresh.Rebasing { - result = append(result, &NonModelItem{ - Index: 0, - Content: fmt.Sprintf("--- %s ---", c.Tr.PendingRebaseTodosSectionHeader), - }) - } - - if c.Model().WorkingTreeStateAtLastCommitRefresh.CherryPicking || - c.Model().WorkingTreeStateAtLastCommitRefresh.Reverting { - _, firstCherryPickOrRevertTodo, found := lo.FindIndexOf( - c.Model().Commits, func(c *models.Commit) bool { - return c.Status == models.StatusCherryPickingOrReverting || - c.Status == models.StatusConflicted - }) - if !found { - firstCherryPickOrRevertTodo = 0 - } - label := lo.Ternary(c.Model().WorkingTreeStateAtLastCommitRefresh.CherryPicking, - c.Tr.PendingCherryPicksSectionHeader, - c.Tr.PendingRevertsSectionHeader) - result = append(result, &NonModelItem{ - Index: firstCherryPickOrRevertTodo, - Content: fmt.Sprintf("--- %s ---", label), - }) - } - - _, firstRealCommit, found := lo.FindIndexOf( - c.Model().Commits, func(c *models.Commit) bool { - return !c.IsTODO() - }) - if !found { - firstRealCommit = 0 - } - result = append(result, &NonModelItem{ - Index: firstRealCommit, - Content: fmt.Sprintf("--- %s ---", c.Tr.CommitsSectionHeader), - }) - } - - return result - } - - ctx := &LocalCommitsContext{ - LocalCommitsViewModel: viewModel, - SearchTrait: NewSearchTrait(c), - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Commits, - WindowName: "commits", - Key: LOCAL_COMMITS_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES, - NeedsRerenderOnHeightChange: true, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - getNonModelItems: getNonModelItems, - }, - c: c, - refreshViewportOnChange: true, - renderOnlyVisibleLines: true, - }, - } - - ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect)) - - return ctx -} - -type LocalCommitsViewModel struct { - *ListViewModel[*models.Commit] - - // If this is true we limit the amount of commits we load, for the sake of keeping things fast. - // If the user attempts to scroll past the end of the list, we will load more commits. - limitCommits bool - - // If this is true we'll use git log --all when fetching the commits. - showWholeGitGraph bool -} - -func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon) *LocalCommitsViewModel { - self := &LocalCommitsViewModel{ - ListViewModel: NewListViewModel(getModel), - limitCommits: true, - showWholeGitGraph: c.UserConfig().Git.Log.ShowWholeGraph, - } - - return self -} - -func (self *LocalCommitsContext) CanRebase() bool { - return true -} - -func (self *LocalCommitsContext) GetSelectedRef() models.Ref { - commit := self.GetSelected() - if commit == nil { - return nil - } - return commit -} - -func (self *LocalCommitsContext) GetSelectedRefRangeForDiffFiles() *types.RefRange { - commits, startIdx, endIdx := self.GetSelectedItems() - if commits == nil || startIdx == endIdx { - return nil - } - from := commits[len(commits)-1] - to := commits[0] - if from.IsTODO() || to.IsTODO() { - return nil - } - return &types.RefRange{From: from, To: to} -} - -// Returns the commit hash of the selected commit, or an empty string if no -// commit is selected -func (self *LocalCommitsContext) GetSelectedCommitHash() string { - commit := self.GetSelected() - if commit == nil { - return "" - } - return commit.Hash() -} - -func (self *LocalCommitsContext) SelectCommitByHash(hash string) bool { - if hash == "" { - return false - } - - if _, idx, found := lo.FindIndexOf(self.GetItems(), func(c *models.Commit) bool { return c.Hash() == hash }); found { - self.SetSelection(idx) - return true - } - - return false -} - -func (self *LocalCommitsContext) GetDiffTerminals() []string { - itemId := self.GetSelectedItemId() - - return []string{itemId} -} - -func (self *LocalCommitsContext) RefForAdjustingLineNumberInDiff() string { - commits, _, _ := self.GetSelectedItems() - if commits == nil { - return "" - } - return commits[0].Hash() -} - -func (self *LocalCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition { - return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), self.ModelIndexToViewIndex, searchStr) -} - -func (self *LocalCommitsViewModel) SetLimitCommits(value bool) { - self.limitCommits = value -} - -func (self *LocalCommitsViewModel) GetLimitCommits() bool { - return self.limitCommits -} - -func (self *LocalCommitsViewModel) SetShowWholeGitGraph(value bool) { - self.showWholeGitGraph = value -} - -func (self *LocalCommitsViewModel) GetShowWholeGitGraph() bool { - return self.showWholeGitGraph -} - -func (self *LocalCommitsViewModel) GetCommits() []*models.Commit { - return self.getModel() -} - -func shouldShowGraph(c *ContextCommon) bool { - if c.Modes().Filtering.Active() { - return false - } - - value := c.UserConfig().Git.Log.ShowGraph - - switch value { - case "always": - return true - case "never": - return false - case "when-maximised": - return c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL - } - - log.Fatalf("Unknown value for git.log.showGraph: %s. Expected one of: 'always', 'never', 'when-maximised'", value) - return false -} - -func searchModelCommits(caseSensitive bool, commits []*models.Commit, columnPositions []int, - modelToViewIndex func(int) int, searchStr string, -) []gocui.SearchPosition { - if columnPositions == nil { - // This should never happen. We are being called at a time where our - // entire view content is scrolled out of view, so that we didn't draw - // anything the last time we rendered. If we run into a scenario where - // this happens, we should fix it, but until we found them all, at least - // make sure we don't crash. - return []gocui.SearchPosition{} - } - - normalize := lo.Ternary(caseSensitive, func(s string) string { return s }, strings.ToLower) - return lo.FilterMap(commits, func(commit *models.Commit, idx int) (gocui.SearchPosition, bool) { - // The XStart and XEnd values are only used if the search string can't - // be found in the view. This can really only happen if the user is - // searching for a commit hash that is longer than the truncated hash - // that we render. So we just set the XStart and XEnd values to the - // start and end of the commit hash column, which is the second one. - result := gocui.SearchPosition{XStart: columnPositions[1], XEnd: columnPositions[2] - 1, Y: modelToViewIndex(idx)} - return result, strings.Contains(normalize(commit.Hash()), searchStr) || - strings.Contains(normalize(commit.Name), searchStr) || - strings.Contains(normalize(commit.ExtraInfo), searchStr) // allow searching for tags - }) -} - -func (self *LocalCommitsContext) IndexForGotoBottom() int { - commits := self.GetCommits() - selectedIdx := self.GetSelectedLineIdx() - if selectedIdx >= 0 && selectedIdx < len(commits)-1 { - if commits[selectedIdx+1].Status != models.StatusMerged { - _, idx, found := lo.FindIndexOf(commits, func(c *models.Commit) bool { - return c.Status == models.StatusMerged - }) - if found { - return idx - 1 - } - } - } - - return self.list.Len() - 1 -} diff --git a/pkg/gui/context/main_context.go b/pkg/gui/context/main_context.go deleted file mode 100644 index 66babac0362..00000000000 --- a/pkg/gui/context/main_context.go +++ /dev/null @@ -1,41 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type MainContext struct { - *SimpleContext - *SearchTrait -} - -var _ types.ISearchableContext = (*MainContext)(nil) - -func NewMainContext( - view *gocui.View, - windowName string, - key types.ContextKey, - c *ContextCommon, -) *MainContext { - ctx := &MainContext{ - SimpleContext: NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - Kind: types.MAIN_CONTEXT, - View: view, - WindowName: windowName, - Key: key, - Focusable: true, - HighlightOnFocus: false, - })), - SearchTrait: NewSearchTrait(c), - } - - ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(int) error { return nil })) - - return ctx -} - -func (self *MainContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition { - return nil -} diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go deleted file mode 100644 index 0d59f1fa9a4..00000000000 --- a/pkg/gui/context/menu_context.go +++ /dev/null @@ -1,249 +0,0 @@ -package context - -import ( - "errors" - "strings" - - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type MenuContext struct { - c *ContextCommon - - *MenuViewModel - *ListContextTrait -} - -var _ types.IListContext = (*MenuContext)(nil) - -func NewMenuContext( - c *ContextCommon, -) *MenuContext { - viewModel := NewMenuViewModel(c) - - return &MenuContext{ - c: c, - MenuViewModel: viewModel, - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Menu, - WindowName: "menu", - Key: "menu", - Kind: types.TEMPORARY_POPUP, - Focusable: true, - HasUncontrolledBounds: true, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: viewModel.GetDisplayStrings, - getColumnAlignments: func() []utils.Alignment { return viewModel.columnAlignment }, - getNonModelItems: viewModel.GetNonModelItems, - }, - c: c, - }, - } -} - -type MenuViewModel struct { - c *ContextCommon - menuItems []*types.MenuItem - prompt string - promptLines []string - columnAlignment []utils.Alignment - allowFilteringKeybindings bool - *FilteredListViewModel[*types.MenuItem] -} - -func NewMenuViewModel(c *ContextCommon) *MenuViewModel { - self := &MenuViewModel{ - menuItems: nil, - c: c, - } - - filterKeybindings := false - - self.FilteredListViewModel = NewFilteredListViewModel( - func() []*types.MenuItem { return self.menuItems }, - func(item *types.MenuItem) []string { - if filterKeybindings { - return []string{keybindings.LabelFromKey(item.Key)} - } - - return item.LabelColumns - }, - ) - - self.FilteredListViewModel.SetPreprocessFilterFunc(func(filter string) string { - if self.allowFilteringKeybindings && strings.HasPrefix(filter, "@") { - filterKeybindings = true - return filter[1:] - } - - filterKeybindings = false - return filter - }) - - return self -} - -func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem, columnAlignment []utils.Alignment) { - self.menuItems = items - self.columnAlignment = columnAlignment -} - -func (self *MenuViewModel) GetPrompt() string { - return self.prompt -} - -func (self *MenuViewModel) SetPrompt(prompt string) { - self.prompt = prompt - self.promptLines = nil -} - -func (self *MenuViewModel) GetPromptLines() []string { - return self.promptLines -} - -func (self *MenuViewModel) SetPromptLines(promptLines []string) { - self.promptLines = promptLines -} - -func (self *MenuViewModel) SetAllowFilteringKeybindings(allow bool) { - self.allowFilteringKeybindings = allow -} - -// TODO: move into presentation package -func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string { - menuItems := self.FilteredListViewModel.GetItems() - - return lo.Map(menuItems, func(item *types.MenuItem, _ int) []string { - displayStrings := item.LabelColumns - if item.DisabledReason != nil { - displayStrings[0] = style.FgDefault.SetStrikethrough().Sprint(displayStrings[0]) - } - - keyLabel := "" - if item.Key != nil { - keyLabel = style.FgCyan.Sprint(keybindings.LabelFromKey(item.Key)) - } - - checkMark := "" - switch item.Widget { - case types.MenuWidgetNone: - // do nothing - case types.MenuWidgetRadioButtonSelected: - checkMark = "(•)" - case types.MenuWidgetRadioButtonUnselected: - checkMark = "( )" - case types.MenuWidgetCheckboxSelected: - checkMark = "[✓]" - case types.MenuWidgetCheckboxUnselected: - checkMark = "[ ]" - } - - displayStrings = utils.Prepend(displayStrings, keyLabel, checkMark) - return displayStrings - }) -} - -func (self *MenuViewModel) GetNonModelItems() []*NonModelItem { - result := []*NonModelItem{} - result = append(result, lo.Map(self.promptLines, func(line string, _ int) *NonModelItem { - return &NonModelItem{ - Index: 0, - Column: 0, - Content: line, - } - })...) - - // Don't display section headers when we are filtering, and the filter mode - // is fuzzy. The reason is that filtering changes the order of the items - // (they are sorted by best match), so all the sections would be messed up. - if self.FilteredListViewModel.IsFiltering() && self.c.UserConfig().Gui.UseFuzzySearch() { - return result - } - - menuItems := self.FilteredListViewModel.GetItems() - var prevSection *types.MenuSection - for i, menuItem := range menuItems { - if menuItem.Section != nil && menuItem.Section != prevSection { - if prevSection != nil { - result = append(result, &NonModelItem{ - Index: i, - Column: 1, - Content: "", - }) - } - - result = append(result, &NonModelItem{ - Index: i, - Column: 1, - Content: style.FgGreen.SetBold().Sprintf("--- %s ---", menuItem.Section.Title), - }) - prevSection = menuItem.Section - } - } - - return result -} - -func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - basicBindings := self.ListContextTrait.GetKeybindings(opts) - menuItemsWithKeys := lo.Filter(self.menuItems, func(item *types.MenuItem, _ int) bool { - return item.Key != nil - }) - - menuItemBindings := lo.Map(menuItemsWithKeys, func(item *types.MenuItem, _ int) *types.Binding { - return &types.Binding{ - Key: item.Key, - Handler: func() error { return self.OnMenuPress(item) }, - } - }) - - // appending because that means the menu item bindings have lower precedence. - // So if a basic binding is to escape from the menu, we want that to still be - // what happens when you press escape. This matters when we're showing the menu - // for all keybindings of say the files context. - return append(basicBindings, menuItemBindings...) -} - -func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error { - if selectedItem != nil && selectedItem.DisabledReason != nil { - if selectedItem.DisabledReason.ShowErrorInPanel { - return errors.New(selectedItem.DisabledReason.Text) - } - - self.c.ErrorToast(self.c.Tr.DisabledMenuItemPrefix + selectedItem.DisabledReason.Text) - return nil - } - - self.c.Context().Pop() - - if selectedItem == nil { - return nil - } - - if err := selectedItem.OnPress(); err != nil { - return err - } - - return nil -} - -// There is currently no need to use range-select in a menu so we're disabling it. -func (self *MenuContext) RangeSelectEnabled() bool { - return false -} - -func (self *MenuContext) FilterPrefix(tr *i18n.TranslationSet) string { - if self.allowFilteringKeybindings { - return tr.FilterPrefixMenu - } - - return self.FilteredListViewModel.FilterPrefix(tr) -} diff --git a/pkg/gui/context/merge_conflicts_context.go b/pkg/gui/context/merge_conflicts_context.go deleted file mode 100644 index 2ab446c066c..00000000000 --- a/pkg/gui/context/merge_conflicts_context.go +++ /dev/null @@ -1,118 +0,0 @@ -package context - -import ( - "math" - - "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/sasha-s/go-deadlock" -) - -type MergeConflictsContext struct { - types.Context - viewModel *ConflictsViewModel - c *ContextCommon - mutex deadlock.Mutex -} - -type ConflictsViewModel struct { - state *mergeconflicts.State - - // userVerticalScrolling tells us if the user has started scrolling through the file themselves - // in which case we won't auto-scroll to a conflict. - userVerticalScrolling bool -} - -func NewMergeConflictsContext( - c *ContextCommon, -) *MergeConflictsContext { - viewModel := &ConflictsViewModel{ - state: mergeconflicts.NewState(), - userVerticalScrolling: false, - } - - return &MergeConflictsContext{ - viewModel: viewModel, - Context: NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - Kind: types.MAIN_CONTEXT, - View: c.Views().MergeConflicts, - WindowName: "main", - Key: MERGE_CONFLICTS_CONTEXT_KEY, - Focusable: true, - HighlightOnFocus: true, - }), - ), - c: c, - } -} - -func (self *MergeConflictsContext) GetState() *mergeconflicts.State { - return self.viewModel.state -} - -func (self *MergeConflictsContext) SetState(state *mergeconflicts.State) { - self.viewModel.state = state -} - -func (self *MergeConflictsContext) GetMutex() *deadlock.Mutex { - return &self.mutex -} - -func (self *MergeConflictsContext) SetUserScrolling(isScrolling bool) { - self.viewModel.userVerticalScrolling = isScrolling -} - -func (self *MergeConflictsContext) IsUserScrolling() bool { - return self.viewModel.userVerticalScrolling -} - -func (self *MergeConflictsContext) RenderAndFocus() { - self.setContent() - self.FocusSelection() - - self.c.Render() -} - -func (self *MergeConflictsContext) Render() error { - self.setContent() - - self.c.Render() - - return nil -} - -func (self *MergeConflictsContext) GetContentToRender() string { - if self.GetState() == nil { - return "" - } - - return mergeconflicts.ColoredConflictFile(self.GetState()) -} - -func (self *MergeConflictsContext) setContent() { - self.GetView().SetContent(self.GetContentToRender()) -} - -func (self *MergeConflictsContext) FocusSelection() { - if !self.IsUserScrolling() { - self.GetView().SetOriginY(self.GetOriginY()) - } - - self.SetSelectedLineRange() -} - -func (self *MergeConflictsContext) SetSelectedLineRange() { - startIdx, endIdx := self.GetState().GetSelectedRange() - view := self.GetView() - originY := view.OriginY() - // As far as the view is concerned, we are always selecting a range - view.SetRangeSelectStart(startIdx) - view.SetCursorY(endIdx - originY) -} - -func (self *MergeConflictsContext) GetOriginY() int { - view := self.GetView() - conflictMiddle := self.GetState().GetConflictMiddle() - return int(math.Max(0, float64(conflictMiddle-(view.InnerHeight()/2)))) -} diff --git a/pkg/gui/context/parent_context_mgr.go b/pkg/gui/context/parent_context_mgr.go deleted file mode 100644 index 4f704e35e23..00000000000 --- a/pkg/gui/context/parent_context_mgr.go +++ /dev/null @@ -1,17 +0,0 @@ -package context - -import "github.com/jesseduffield/lazygit/pkg/gui/types" - -type ParentContextMgr struct { - ParentContext types.Context -} - -var _ types.ParentContexter = (*ParentContextMgr)(nil) - -func (self *ParentContextMgr) SetParentContext(context types.Context) { - self.ParentContext = context -} - -func (self *ParentContextMgr) GetParentContext() types.Context { - return self.ParentContext -} diff --git a/pkg/gui/context/patch_explorer_context.go b/pkg/gui/context/patch_explorer_context.go deleted file mode 100644 index 79d585a1226..00000000000 --- a/pkg/gui/context/patch_explorer_context.go +++ /dev/null @@ -1,147 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/patch_exploring" - "github.com/jesseduffield/lazygit/pkg/gui/types" - deadlock "github.com/sasha-s/go-deadlock" -) - -type PatchExplorerContext struct { - *SimpleContext - *SearchTrait - - state *patch_exploring.State - viewTrait *ViewTrait - getIncludedLineIndices func() []int - c *ContextCommon - mutex deadlock.Mutex -} - -var ( - _ types.IPatchExplorerContext = (*PatchExplorerContext)(nil) - _ types.ISearchableContext = (*PatchExplorerContext)(nil) -) - -func NewPatchExplorerContext( - view *gocui.View, - windowName string, - key types.ContextKey, - - getIncludedLineIndices func() []int, - - c *ContextCommon, -) *PatchExplorerContext { - ctx := &PatchExplorerContext{ - state: nil, - viewTrait: NewViewTrait(view), - c: c, - getIncludedLineIndices: getIncludedLineIndices, - SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: view, - WindowName: windowName, - Key: key, - Kind: types.MAIN_CONTEXT, - Focusable: true, - HighlightOnFocus: true, - NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES, - })), - SearchTrait: NewSearchTrait(c), - } - - ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper( - func(selectedLineIdx int) error { - ctx.GetMutex().Lock() - defer ctx.GetMutex().Unlock() - ctx.NavigateTo(selectedLineIdx) - return nil - }), - ) - - ctx.SetHandleRenderFunc(ctx.OnViewWidthChanged) - - return ctx -} - -func (self *PatchExplorerContext) IsPatchExplorerContext() {} - -func (self *PatchExplorerContext) GetState() *patch_exploring.State { - return self.state -} - -func (self *PatchExplorerContext) SetState(state *patch_exploring.State) { - self.state = state -} - -func (self *PatchExplorerContext) GetViewTrait() types.IViewTrait { - return self.viewTrait -} - -func (self *PatchExplorerContext) GetIncludedLineIndices() []int { - return self.getIncludedLineIndices() -} - -func (self *PatchExplorerContext) RenderAndFocus() { - self.setContent() - - self.FocusSelection() - self.c.Render() -} - -func (self *PatchExplorerContext) Render() { - self.setContent() - - self.c.Render() -} - -func (self *PatchExplorerContext) setContent() { - self.GetView().SetContent(self.GetContentToRender()) -} - -func (self *PatchExplorerContext) FocusSelection() { - view := self.GetView() - state := self.GetState() - bufferHeight := view.InnerHeight() - _, origin := view.Origin() - numLines := view.ViewLinesHeight() - - newOriginY := state.CalculateOrigin(origin, bufferHeight, numLines) - - view.SetOriginY(newOriginY) - - startIdx, endIdx := state.SelectedViewRange() - // As far as the view is concerned, we are always selecting a range - view.SetRangeSelectStart(startIdx) - view.SetCursorY(endIdx - newOriginY) -} - -func (self *PatchExplorerContext) GetContentToRender() string { - if self.GetState() == nil { - return "" - } - - return self.GetState().RenderForLineIndices(self.GetIncludedLineIndices()) -} - -func (self *PatchExplorerContext) NavigateTo(selectedLineIdx int) { - self.GetState().SetLineSelectMode() - self.GetState().SelectLine(selectedLineIdx) - - self.RenderAndFocus() -} - -func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex { - return &self.mutex -} - -func (self *PatchExplorerContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition { - return nil -} - -func (self *PatchExplorerContext) OnViewWidthChanged() { - if state := self.GetState(); state != nil { - state.OnViewWidthChanged(self.GetView()) - self.setContent() - self.RenderAndFocus() - } -} diff --git a/pkg/gui/context/prompt_context.go b/pkg/gui/context/prompt_context.go deleted file mode 100644 index c1def571cc9..00000000000 --- a/pkg/gui/context/prompt_context.go +++ /dev/null @@ -1,30 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type PromptContext struct { - *SimpleContext - c *ContextCommon - - State ConfirmationContextState -} - -var _ types.Context = (*PromptContext)(nil) - -func NewPromptContext( - c *ContextCommon, -) *PromptContext { - return &PromptContext{ - c: c, - SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Prompt, - WindowName: "prompt", - Key: PROMPT_CONTEXT_KEY, - Kind: types.TEMPORARY_POPUP, - Focusable: true, - HasUncontrolledBounds: true, - })), - } -} diff --git a/pkg/gui/context/reflog_commits_context.go b/pkg/gui/context/reflog_commits_context.go deleted file mode 100644 index 1a882bf120a..00000000000 --- a/pkg/gui/context/reflog_commits_context.go +++ /dev/null @@ -1,95 +0,0 @@ -package context - -import ( - "time" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type ReflogCommitsContext struct { - *FilteredListViewModel[*models.Commit] - *ListContextTrait -} - -var ( - _ types.IListContext = (*ReflogCommitsContext)(nil) - _ types.DiffableContext = (*ReflogCommitsContext)(nil) -) - -func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext { - viewModel := NewFilteredListViewModel( - func() []*models.Commit { return c.Model().FilteredReflogCommits }, - func(commit *models.Commit) []string { - return []string{commit.ShortHash(), commit.Name} - }, - ) - - getDisplayStrings := func(_ int, _ int) [][]string { - return presentation.GetReflogCommitListDisplayStrings( - viewModel.GetItems(), - c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL, - c.Modes().CherryPicking.SelectedHashSet(), - c.Modes().Diffing.Ref, - time.Now(), - c.UserConfig().Gui.TimeFormat, - c.UserConfig().Gui.ShortTimeFormat, - c.UserConfig().Git.ParseEmoji, - ) - } - - return &ReflogCommitsContext{ - FilteredListViewModel: viewModel, - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().ReflogCommits, - WindowName: "commits", - Key: REFLOG_COMMITS_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - }, - c: c, - }, - } -} - -func (self *ReflogCommitsContext) CanRebase() bool { - return false -} - -func (self *ReflogCommitsContext) GetSelectedRef() models.Ref { - commit := self.GetSelected() - if commit == nil { - return nil - } - return commit -} - -func (self *ReflogCommitsContext) GetSelectedRefRangeForDiffFiles() *types.RefRange { - // It doesn't make much sense to show a range diff between two reflog entries. - return nil -} - -func (self *ReflogCommitsContext) GetCommits() []*models.Commit { - return self.getModel() -} - -func (self *ReflogCommitsContext) GetDiffTerminals() []string { - itemId := self.GetSelectedItemId() - - return []string{itemId} -} - -func (self *ReflogCommitsContext) RefForAdjustingLineNumberInDiff() string { - return self.GetSelectedItemId() -} - -func (self *ReflogCommitsContext) ShowBranchHeadsInSubCommits() bool { - return false -} diff --git a/pkg/gui/context/remote_branches_context.go b/pkg/gui/context/remote_branches_context.go deleted file mode 100644 index 65f156f25d4..00000000000 --- a/pkg/gui/context/remote_branches_context.go +++ /dev/null @@ -1,87 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type RemoteBranchesContext struct { - *FilteredListViewModel[*models.RemoteBranch] - *ListContextTrait - *DynamicTitleBuilder -} - -var ( - _ types.IListContext = (*RemoteBranchesContext)(nil) - _ types.DiffableContext = (*RemoteBranchesContext)(nil) -) - -func NewRemoteBranchesContext( - c *ContextCommon, -) *RemoteBranchesContext { - viewModel := NewFilteredListViewModel( - func() []*models.RemoteBranch { return c.Model().RemoteBranches }, - func(remoteBranch *models.RemoteBranch) []string { - return []string{remoteBranch.Name} - }, - ) - - getDisplayStrings := func(_ int, _ int) [][]string { - return presentation.GetRemoteBranchListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref) - } - - return &RemoteBranchesContext{ - FilteredListViewModel: viewModel, - DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle), - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().RemoteBranches, - WindowName: "branches", - Key: REMOTE_BRANCHES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - Transient: true, - NeedsRerenderOnHeightChange: true, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - }, - c: c, - }, - } -} - -func (self *RemoteBranchesContext) GetSelectedRef() models.Ref { - remoteBranch := self.GetSelected() - if remoteBranch == nil { - return nil - } - return remoteBranch -} - -func (self *RemoteBranchesContext) GetSelectedRefs() ([]models.Ref, int, int) { - items, startIdx, endIdx := self.GetSelectedItems() - - refs := lo.Map(items, func(item *models.RemoteBranch, _ int) models.Ref { - return item - }) - - return refs, startIdx, endIdx -} - -func (self *RemoteBranchesContext) GetDiffTerminals() []string { - itemId := self.GetSelectedItemId() - - return []string{itemId} -} - -func (self *RemoteBranchesContext) RefForAdjustingLineNumberInDiff() string { - return self.GetSelectedItemId() -} - -func (self *RemoteBranchesContext) ShowBranchHeadsInSubCommits() bool { - return true -} diff --git a/pkg/gui/context/remotes_context.go b/pkg/gui/context/remotes_context.go deleted file mode 100644 index 4a96bbc1881..00000000000 --- a/pkg/gui/context/remotes_context.go +++ /dev/null @@ -1,59 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type RemotesContext struct { - *FilteredListViewModel[*models.Remote] - *ListContextTrait -} - -var ( - _ types.IListContext = (*RemotesContext)(nil) - _ types.DiffableContext = (*RemotesContext)(nil) -) - -func NewRemotesContext(c *ContextCommon) *RemotesContext { - viewModel := NewFilteredListViewModel( - func() []*models.Remote { return c.Model().Remotes }, - func(remote *models.Remote) []string { - return []string{remote.Name} - }, - ) - - getDisplayStrings := func(_ int, _ int) [][]string { - return presentation.GetRemoteListDisplayStrings( - viewModel.GetItems(), c.Modes().Diffing.Ref, c.State().GetItemOperation, c.Tr, c.UserConfig()) - } - - return &RemotesContext{ - FilteredListViewModel: viewModel, - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Remotes, - WindowName: "branches", - Key: REMOTES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - }, - c: c, - }, - } -} - -func (self *RemotesContext) GetDiffTerminals() []string { - itemId := self.GetSelectedItemId() - - return []string{itemId} -} - -func (self *RemotesContext) RefForAdjustingLineNumberInDiff() string { - return "" -} diff --git a/pkg/gui/context/search_trait.go b/pkg/gui/context/search_trait.go deleted file mode 100644 index 0b01ee8d635..00000000000 --- a/pkg/gui/context/search_trait.go +++ /dev/null @@ -1,86 +0,0 @@ -package context - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/theme" -) - -type SearchTrait struct { - c *ContextCommon - *SearchHistory - - searchString string -} - -func NewSearchTrait(c *ContextCommon) *SearchTrait { - return &SearchTrait{ - c: c, - SearchHistory: NewSearchHistory(), - } -} - -func (self *SearchTrait) GetSearchString() string { - return self.searchString -} - -func (self *SearchTrait) SetSearchString(searchString string) { - self.searchString = searchString -} - -func (self *SearchTrait) ClearSearchString() { - self.SetSearchString("") -} - -// used for type switch -func (self *SearchTrait) IsSearchableContext() {} - -func (self *SearchTrait) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) error { - return func(selectedLineIdx int, index int, total int) error { - self.RenderSearchStatus(index, total) - - if total != 0 { - if err := innerFunc(selectedLineIdx); err != nil { - return err - } - } - - return nil - } -} - -func (self *SearchTrait) RenderSearchStatus(index int, total int) { - keybindingConfig := self.c.UserConfig().Keybinding - - if total == 0 { - self.c.SetViewContent( - self.c.Views().Search, - fmt.Sprintf( - self.c.Tr.NoMatchesFor, - self.searchString, - theme.OptionsFgColor.Sprintf(self.c.Tr.ExitSearchMode, keybindings.Label(keybindingConfig.Universal.Return)), - ), - ) - } else { - self.c.SetViewContent( - self.c.Views().Search, - fmt.Sprintf( - self.c.Tr.MatchesFor, - self.searchString, - index+1, - total, - theme.OptionsFgColor.Sprintf( - self.c.Tr.SearchKeybindings, - keybindings.Label(keybindingConfig.Universal.NextMatch), - keybindings.Label(keybindingConfig.Universal.PrevMatch), - keybindings.Label(keybindingConfig.Universal.Return), - ), - ), - ) - } -} - -func (self *SearchTrait) IsSearching() bool { - return self.searchString != "" -} diff --git a/pkg/gui/context/setup.go b/pkg/gui/context/setup.go deleted file mode 100644 index 8f498e6a985..00000000000 --- a/pkg/gui/context/setup.go +++ /dev/null @@ -1,134 +0,0 @@ -package context - -import "github.com/jesseduffield/lazygit/pkg/gui/types" - -func NewContextTree(c *ContextCommon) *ContextTree { - commitFilesContext := NewCommitFilesContext(c) - - return &ContextTree{ - Global: NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - Kind: types.GLOBAL_CONTEXT, - View: nil, // TODO: see if this breaks anything - WindowName: "", - Key: GLOBAL_CONTEXT_KEY, - Focusable: false, - HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view - }), - ), - Status: NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - Kind: types.SIDE_CONTEXT, - View: c.Views().Status, - WindowName: "status", - Key: STATUS_CONTEXT_KEY, - Focusable: true, - }), - ), - Files: NewWorkingTreeContext(c), - Submodules: NewSubmodulesContext(c), - Menu: NewMenuContext(c), - Remotes: NewRemotesContext(c), - Worktrees: NewWorktreesContext(c), - RemoteBranches: NewRemoteBranchesContext(c), - LocalCommits: NewLocalCommitsContext(c), - CommitFiles: commitFilesContext, - ReflogCommits: NewReflogCommitsContext(c), - SubCommits: NewSubCommitsContext(c), - Branches: NewBranchesContext(c), - Tags: NewTagsContext(c), - Stash: NewStashContext(c), - Suggestions: NewSuggestionsContext(c), - Normal: NewMainContext(c.Views().Main, "main", NORMAL_MAIN_CONTEXT_KEY, c), - NormalSecondary: NewMainContext(c.Views().Secondary, "secondary", NORMAL_SECONDARY_CONTEXT_KEY, c), - Staging: NewPatchExplorerContext( - c.Views().Staging, - "main", - STAGING_MAIN_CONTEXT_KEY, - func() []int { return nil }, - c, - ), - StagingSecondary: NewPatchExplorerContext( - c.Views().StagingSecondary, - "secondary", - STAGING_SECONDARY_CONTEXT_KEY, - func() []int { return nil }, - c, - ), - CustomPatchBuilder: NewPatchExplorerContext( - c.Views().PatchBuilding, - "main", - PATCH_BUILDING_MAIN_CONTEXT_KEY, - func() []int { - filename := commitFilesContext.GetSelectedPath() - includedLineIndices, err := c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename) - if err != nil { - c.Log.Error(err) - return nil - } - - return includedLineIndices - }, - c, - ), - CustomPatchBuilderSecondary: NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - Kind: types.MAIN_CONTEXT, - View: c.Views().PatchBuildingSecondary, - WindowName: "secondary", - Key: PATCH_BUILDING_SECONDARY_CONTEXT_KEY, - Focusable: false, - }), - ), - MergeConflicts: NewMergeConflictsContext( - c, - ), - Confirmation: NewConfirmationContext(c), - Prompt: NewPromptContext(c), - CommitMessage: NewCommitMessageContext(c), - CommitDescription: NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - Kind: types.PERSISTENT_POPUP, - View: c.Views().CommitDescription, - WindowName: "commitDescription", - Key: COMMIT_DESCRIPTION_CONTEXT_KEY, - Focusable: true, - HasUncontrolledBounds: true, - }), - ), - Search: NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - Kind: types.PERSISTENT_POPUP, - View: c.Views().Search, - WindowName: "search", - Key: SEARCH_CONTEXT_KEY, - Focusable: true, - }), - ), - CommandLog: NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - Kind: types.EXTRAS_CONTEXT, - View: c.Views().Extras, - WindowName: "extras", - Key: COMMAND_LOG_CONTEXT_KEY, - Focusable: true, - }), - ), - Snake: NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - Kind: types.SIDE_CONTEXT, - View: c.Views().Snake, - WindowName: "files", - Key: SNAKE_CONTEXT_KEY, - Focusable: true, - }), - ), - Options: NewDisplayContext(OPTIONS_CONTEXT_KEY, c.Views().Options, "options"), - AppStatus: NewDisplayContext(APP_STATUS_CONTEXT_KEY, c.Views().AppStatus, "appStatus"), - SearchPrefix: NewDisplayContext(SEARCH_PREFIX_CONTEXT_KEY, c.Views().SearchPrefix, "searchPrefix"), - Information: NewDisplayContext(INFORMATION_CONTEXT_KEY, c.Views().Information, "information"), - Limit: NewDisplayContext(LIMIT_CONTEXT_KEY, c.Views().Limit, "limit"), - StatusSpacer1: NewDisplayContext(STATUS_SPACER1_CONTEXT_KEY, c.Views().StatusSpacer1, "statusSpacer1"), - StatusSpacer2: NewDisplayContext(STATUS_SPACER2_CONTEXT_KEY, c.Views().StatusSpacer2, "statusSpacer2"), - } -} diff --git a/pkg/gui/context/simple_context.go b/pkg/gui/context/simple_context.go deleted file mode 100644 index a5b051b9349..00000000000 --- a/pkg/gui/context/simple_context.go +++ /dev/null @@ -1,74 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type SimpleContext struct { - *BaseContext - handleRenderFunc func() -} - -func NewSimpleContext(baseContext *BaseContext) *SimpleContext { - return &SimpleContext{ - BaseContext: baseContext, - } -} - -var _ types.Context = &SimpleContext{} - -// A Display context only renders a view. It has no keybindings and is not focusable. -func NewDisplayContext(key types.ContextKey, view *gocui.View, windowName string) types.Context { - return NewSimpleContext( - NewBaseContext(NewBaseContextOpts{ - Kind: types.DISPLAY_CONTEXT, - Key: key, - View: view, - WindowName: windowName, - Focusable: false, - Transient: false, - }), - ) -} - -func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) { - if self.highlightOnFocus { - self.GetViewTrait().SetHighlight(true) - } - - for _, fn := range self.onFocusFns { - fn(opts) - } - - if self.onRenderToMainFn != nil { - self.onRenderToMainFn() - } -} - -func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) { - self.GetViewTrait().SetHighlight(false) - self.view.SetOriginX(0) - for _, fn := range self.onFocusLostFns { - fn(opts) - } -} - -func (self *SimpleContext) FocusLine() { -} - -func (self *SimpleContext) HandleRender() { - if self.handleRenderFunc != nil { - self.handleRenderFunc() - } -} - -func (self *SimpleContext) SetHandleRenderFunc(f func()) { - self.handleRenderFunc = f -} - -func (self *SimpleContext) HandleRenderToMain() { - if self.onRenderToMainFn != nil { - self.onRenderToMainFn() - } -} diff --git a/pkg/gui/context/stash_context.go b/pkg/gui/context/stash_context.go deleted file mode 100644 index 2014de9f309..00000000000 --- a/pkg/gui/context/stash_context.go +++ /dev/null @@ -1,77 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type StashContext struct { - *FilteredListViewModel[*models.StashEntry] - *ListContextTrait -} - -var ( - _ types.IListContext = (*StashContext)(nil) - _ types.DiffableContext = (*StashContext)(nil) -) - -func NewStashContext( - c *ContextCommon, -) *StashContext { - viewModel := NewFilteredListViewModel( - func() []*models.StashEntry { return c.Model().StashEntries }, - func(stashEntry *models.StashEntry) []string { - return []string{stashEntry.Name} - }, - ) - - getDisplayStrings := func(_ int, _ int) [][]string { - return presentation.GetStashEntryListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref) - } - - return &StashContext{ - FilteredListViewModel: viewModel, - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Stash, - WindowName: "stash", - Key: STASH_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - }, - c: c, - }, - } -} - -func (self *StashContext) CanRebase() bool { - return false -} - -func (self *StashContext) GetSelectedRef() models.Ref { - stash := self.GetSelected() - if stash == nil { - return nil - } - return stash -} - -func (self *StashContext) GetSelectedRefRangeForDiffFiles() *types.RefRange { - // It doesn't make much sense to show a range diff between two stash entries. - return nil -} - -func (self *StashContext) GetDiffTerminals() []string { - itemId := self.GetSelectedItemId() - - return []string{itemId} -} - -func (self *StashContext) RefForAdjustingLineNumberInDiff() string { - return self.GetSelectedItemId() -} diff --git a/pkg/gui/context/sub_commits_context.go b/pkg/gui/context/sub_commits_context.go deleted file mode 100644 index 1e084077bcf..00000000000 --- a/pkg/gui/context/sub_commits_context.go +++ /dev/null @@ -1,246 +0,0 @@ -package context - -import ( - "fmt" - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type SubCommitsContext struct { - c *ContextCommon - - *SubCommitsViewModel - *ListContextTrait - *DynamicTitleBuilder - *SearchTrait -} - -var ( - _ types.IListContext = (*SubCommitsContext)(nil) - _ types.DiffableContext = (*SubCommitsContext)(nil) - _ types.ISearchableContext = (*SubCommitsContext)(nil) -) - -func NewSubCommitsContext( - c *ContextCommon, -) *SubCommitsContext { - viewModel := &SubCommitsViewModel{ - ListViewModel: NewListViewModel( - func() []*models.Commit { return c.Model().SubCommits }, - ), - ref: nil, - limitCommits: true, - } - - getDisplayStrings := func(startIdx int, endIdx int) [][]string { - // This can happen if a sub-commits view is asked to be rerendered while - // it is invisible; for example when switching screen modes, which - // rerenders all views. - if viewModel.GetRef() == nil { - return [][]string{} - } - - var selectedCommitHashPtr *string - if c.Context().Current().GetKey() == SUB_COMMITS_CONTEXT_KEY { - selectedCommit := viewModel.GetSelected() - if selectedCommit != nil { - selectedCommitHashPtr = selectedCommit.HashPtr() - } - } - branches := []*models.Branch{} - if viewModel.GetShowBranchHeads() { - branches = c.Model().Branches - } - hasRebaseUpdateRefsConfig := c.Git().Config.GetRebaseUpdateRefs() - return presentation.GetCommitListDisplayStrings( - c.Common, - c.Model().SubCommits, - branches, - viewModel.GetRef().RefName(), - hasRebaseUpdateRefsConfig, - c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL, - c.Modes().CherryPicking.SelectedHashSet(), - c.Modes().Diffing.Ref, - "", - c.UserConfig().Gui.TimeFormat, - c.UserConfig().Gui.ShortTimeFormat, - time.Now(), - c.UserConfig().Git.ParseEmoji, - selectedCommitHashPtr, - startIdx, - endIdx, - shouldShowGraph(c), - git_commands.NewNullBisectInfo(), - ) - } - - getNonModelItems := func() []*NonModelItem { - result := []*NonModelItem{} - if viewModel.GetRefToShowDivergenceFrom() != "" { - _, upstreamIdx, found := lo.FindIndexOf( - c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceRight }) - if !found { - upstreamIdx = 0 - } - result = append(result, &NonModelItem{ - Index: upstreamIdx, - Content: fmt.Sprintf("--- %s ---", c.Tr.DivergenceSectionHeaderRemote), - }) - - _, localIdx, found := lo.FindIndexOf( - c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceLeft }) - if !found { - localIdx = len(c.Model().SubCommits) - } - result = append(result, &NonModelItem{ - Index: localIdx, - Content: fmt.Sprintf("--- %s ---", c.Tr.DivergenceSectionHeaderLocal), - }) - } - - return result - } - - ctx := &SubCommitsContext{ - c: c, - SubCommitsViewModel: viewModel, - SearchTrait: NewSearchTrait(c), - DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.SubCommitsDynamicTitle), - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().SubCommits, - WindowName: "branches", - Key: SUB_COMMITS_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - Transient: true, - NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES, - NeedsRerenderOnHeightChange: true, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - getNonModelItems: getNonModelItems, - }, - c: c, - refreshViewportOnChange: true, - renderOnlyVisibleLines: true, - }, - } - - ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect)) - - return ctx -} - -type SubCommitsViewModel struct { - // name of the ref that the sub-commits are shown for - ref models.Ref - refToShowDivergenceFrom string - *ListViewModel[*models.Commit] - - limitCommits bool - showBranchHeads bool -} - -func (self *SubCommitsViewModel) SetRef(ref models.Ref) { - self.ref = ref -} - -func (self *SubCommitsViewModel) GetRef() models.Ref { - return self.ref -} - -func (self *SubCommitsViewModel) SetRefToShowDivergenceFrom(ref string) { - self.refToShowDivergenceFrom = ref -} - -func (self *SubCommitsViewModel) GetRefToShowDivergenceFrom() string { - return self.refToShowDivergenceFrom -} - -func (self *SubCommitsViewModel) SetShowBranchHeads(value bool) { - self.showBranchHeads = value -} - -func (self *SubCommitsViewModel) GetShowBranchHeads() bool { - return self.showBranchHeads -} - -func (self *SubCommitsContext) CanRebase() bool { - return false -} - -func (self *SubCommitsContext) GetSelectedRef() models.Ref { - commit := self.GetSelected() - if commit == nil { - return nil - } - return commit -} - -func (self *SubCommitsContext) GetSelectedRefRangeForDiffFiles() *types.RefRange { - commits, startIdx, endIdx := self.GetSelectedItems() - if commits == nil || startIdx == endIdx { - return nil - } - from := commits[len(commits)-1] - to := commits[0] - if from.Divergence != to.Divergence { - return nil - } - return &types.RefRange{From: from, To: to} -} - -func (self *SubCommitsContext) GetCommits() []*models.Commit { - return self.getModel() -} - -func (self *SubCommitsContext) SetLimitCommits(value bool) { - self.limitCommits = value -} - -func (self *SubCommitsContext) GetLimitCommits() bool { - return self.limitCommits -} - -func (self *SubCommitsContext) GetDiffTerminals() []string { - itemId := self.GetSelectedItemId() - - return []string{itemId} -} - -func (self *SubCommitsContext) RefForAdjustingLineNumberInDiff() string { - commits, _, _ := self.GetSelectedItems() - if commits == nil { - return "" - } - return commits[0].Hash() -} - -func (self *SubCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition { - return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), self.ModelIndexToViewIndex, searchStr) -} - -func (self *SubCommitsContext) IndexForGotoBottom() int { - commits := self.GetCommits() - selectedIdx := self.GetSelectedLineIdx() - if selectedIdx >= 0 && selectedIdx < len(commits)-1 { - if commits[selectedIdx+1].Status != models.StatusMerged { - _, idx, found := lo.FindIndexOf(commits, func(c *models.Commit) bool { - return c.Status == models.StatusMerged - }) - if found { - return idx - 1 - } - } - } - - return self.list.Len() - 1 -} diff --git a/pkg/gui/context/submodules_context.go b/pkg/gui/context/submodules_context.go deleted file mode 100644 index 5428da04491..00000000000 --- a/pkg/gui/context/submodules_context.go +++ /dev/null @@ -1,45 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type SubmodulesContext struct { - *FilteredListViewModel[*models.SubmoduleConfig] - *ListContextTrait -} - -var _ types.IListContext = (*SubmodulesContext)(nil) - -func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext { - viewModel := NewFilteredListViewModel( - func() []*models.SubmoduleConfig { return c.Model().Submodules }, - func(submodule *models.SubmoduleConfig) []string { - return []string{submodule.FullName()} - }, - ) - - getDisplayStrings := func(_ int, _ int) [][]string { - return presentation.GetSubmoduleListDisplayStrings(viewModel.GetItems()) - } - - return &SubmodulesContext{ - FilteredListViewModel: viewModel, - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Submodules, - WindowName: "files", - Key: SUBMODULES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - }, - c: c, - }, - } -} diff --git a/pkg/gui/context/suggestions_context.go b/pkg/gui/context/suggestions_context.go deleted file mode 100644 index 97d28ffc7db..00000000000 --- a/pkg/gui/context/suggestions_context.go +++ /dev/null @@ -1,94 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/tasks" -) - -type SuggestionsContext struct { - *ListViewModel[*types.Suggestion] - *ListContextTrait - - State *SuggestionsContextState -} - -type SuggestionsContextState struct { - Suggestions []*types.Suggestion - OnConfirm func() error - OnClose func() error - OnDeleteSuggestion func() error - AsyncHandler *tasks.AsyncHandler - - AllowEditSuggestion bool - - // FindSuggestions will take a string that the user has typed into a prompt - // and return a slice of suggestions which match that string. - FindSuggestions func(string) []*types.Suggestion -} - -var _ types.IListContext = (*SuggestionsContext)(nil) - -func NewSuggestionsContext( - c *ContextCommon, -) *SuggestionsContext { - state := &SuggestionsContextState{ - AsyncHandler: tasks.NewAsyncHandler(c.OnWorker), - } - getModel := func() []*types.Suggestion { - return state.Suggestions - } - - getDisplayStrings := func(_ int, _ int) [][]string { - return presentation.GetSuggestionListDisplayStrings(state.Suggestions) - } - - viewModel := NewListViewModel(getModel) - - return &SuggestionsContext{ - State: state, - ListViewModel: viewModel, - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Suggestions, - WindowName: "suggestions", - Key: SUGGESTIONS_CONTEXT_KEY, - Kind: types.PERSISTENT_POPUP, - Focusable: true, - HasUncontrolledBounds: true, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - }, - c: c, - }, - } -} - -func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion) { - self.State.Suggestions = suggestions - self.SetSelection(0) - self.c.ResetViewOrigin(self.GetView()) - self.HandleRender() -} - -func (self *SuggestionsContext) RefreshSuggestions() { - self.State.AsyncHandler.Do(func() func() { - findSuggestionsFn := self.State.FindSuggestions - if findSuggestionsFn != nil { - suggestions := findSuggestionsFn(self.c.GetPromptInput()) - return func() { self.SetSuggestions(suggestions) } - } - return func() {} - }) -} - -// There is currently no need to use range-select in the suggestions view so we're disabling it. -func (self *SuggestionsContext) RangeSelectEnabled() bool { - return false -} - -func (self *SuggestionsContext) GetOnClick() func() error { - return self.State.OnConfirm -} diff --git a/pkg/gui/context/tags_context.go b/pkg/gui/context/tags_context.go deleted file mode 100644 index 0b98b146a49..00000000000 --- a/pkg/gui/context/tags_context.go +++ /dev/null @@ -1,75 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type TagsContext struct { - *FilteredListViewModel[*models.Tag] - *ListContextTrait -} - -var ( - _ types.IListContext = (*TagsContext)(nil) - _ types.DiffableContext = (*TagsContext)(nil) -) - -func NewTagsContext( - c *ContextCommon, -) *TagsContext { - viewModel := NewFilteredListViewModel( - func() []*models.Tag { return c.Model().Tags }, - func(tag *models.Tag) []string { - return []string{tag.Name, tag.Message} - }, - ) - - getDisplayStrings := func(_ int, _ int) [][]string { - return presentation.GetTagListDisplayStrings( - viewModel.GetItems(), - c.State().GetItemOperation, - c.Modes().Diffing.Ref, c.Tr, c.UserConfig()) - } - - return &TagsContext{ - FilteredListViewModel: viewModel, - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Tags, - WindowName: "branches", - Key: TAGS_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - }, - c: c, - }, - } -} - -func (self *TagsContext) GetSelectedRef() models.Ref { - tag := self.GetSelected() - if tag == nil { - return nil - } - return tag -} - -func (self *TagsContext) GetDiffTerminals() []string { - itemId := self.GetSelectedItemId() - - return []string{itemId} -} - -func (self *TagsContext) RefForAdjustingLineNumberInDiff() string { - return self.GetSelectedItemId() -} - -func (self *TagsContext) ShowBranchHeadsInSubCommits() bool { - return true -} diff --git a/pkg/gui/context/traits/list_cursor.go b/pkg/gui/context/traits/list_cursor.go deleted file mode 100644 index 3b3e5863983..00000000000 --- a/pkg/gui/context/traits/list_cursor.go +++ /dev/null @@ -1,186 +0,0 @@ -package traits - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type RangeSelectMode int - -const ( - // None means we are not selecting a range - RangeSelectModeNone RangeSelectMode = iota - // Sticky range select is started by pressing 'v', then the range is expanded - // when you move up or down. It is cancelled by pressing 'v' again. - RangeSelectModeSticky - // Nonsticky range select is started by pressing shift+arrow and cancelled - // when pressing up/down without shift, or by pressing 'v' - RangeSelectModeNonSticky -) - -type ListCursor struct { - selectedIdx int - rangeSelectMode RangeSelectMode - // value is ignored when rangeSelectMode is RangeSelectModeNone - rangeStartIdx int - // Get the length of the list. We use this to clamp the selection so that - // the selected index is always valid - getLength func() int -} - -func NewListCursor(getLength func() int) *ListCursor { - return &ListCursor{ - selectedIdx: 0, - rangeStartIdx: 0, - rangeSelectMode: RangeSelectModeNone, - getLength: getLength, - } -} - -var _ types.IListCursor = (*ListCursor)(nil) - -func (self *ListCursor) GetSelectedLineIdx() int { - return self.selectedIdx -} - -// Sets the selected line index. Note, you probably don't want to use this directly, -// because it doesn't affect the range select mode or range start index. You should only -// use this for navigation situations where e.g. the user wants to jump to the top of -// a list while in range select mode so that the selection ends up being between -// the top of the list and the previous selection -func (self *ListCursor) SetSelectedLineIdx(value int) { - self.selectedIdx = self.clampValue(value) -} - -// Sets the selected index and cancels the range. You almost always want to use -// this instead of SetSelectedLineIdx. For example, if you want to jump the cursor -// to the top of a list after checking out a branch, you should use this method, -// or you may end up with a large range selection from the previous cursor position -// to the top of the list. -func (self *ListCursor) SetSelection(value int) { - self.selectedIdx = self.clampValue(value) - self.CancelRangeSelect() -} - -func (self *ListCursor) SetSelectionRangeAndMode(selectedIdx, rangeStartIdx int, mode RangeSelectMode) { - self.selectedIdx = self.clampValue(selectedIdx) - self.rangeStartIdx = self.clampValue(rangeStartIdx) - if mode == RangeSelectModeNonSticky && selectedIdx == rangeStartIdx { - self.rangeSelectMode = RangeSelectModeNone - } else { - self.rangeSelectMode = mode - } -} - -// Returns the selectedIdx, the rangeStartIdx, and the mode of the current selection. -func (self *ListCursor) GetSelectionRangeAndMode() (int, int, RangeSelectMode) { - if self.IsSelectingRange() { - return self.selectedIdx, self.rangeStartIdx, self.rangeSelectMode - } - return self.selectedIdx, self.selectedIdx, self.rangeSelectMode -} - -func (self *ListCursor) clampValue(value int) int { - clampedValue := -1 - length := self.getLength() - if length > 0 { - clampedValue = lo.Clamp(value, 0, length-1) - } - - return clampedValue -} - -// Moves the cursor up or down by the given amount. -// If we are in non-sticky range select mode, this will cancel the range select -func (self *ListCursor) MoveSelectedLine(change int) { - if self.rangeSelectMode == RangeSelectModeNonSticky { - self.CancelRangeSelect() - } - - self.SetSelectedLineIdx(self.selectedIdx + change) -} - -// Moves the cursor up or down by the given amount, and also moves the range start -// index by the same amount -func (self *ListCursor) MoveSelection(delta int) { - self.selectedIdx = self.clampValue(self.selectedIdx + delta) - if self.IsSelectingRange() { - self.rangeStartIdx = self.clampValue(self.rangeStartIdx + delta) - } -} - -// To be called when the model might have shrunk so that our selection is not out of bounds -func (self *ListCursor) ClampSelection() { - self.selectedIdx = self.clampValue(self.selectedIdx) - self.rangeStartIdx = self.clampValue(self.rangeStartIdx) -} - -func (self *ListCursor) Len() int { - // The length of the model slice can change at any time, so the selection may - // become out of bounds. To reduce the likelihood of this, we clamp the selection - // whenever we obtain the length of the model. - self.ClampSelection() - - return self.getLength() -} - -func (self *ListCursor) GetRangeStartIdx() (int, bool) { - if self.IsSelectingRange() { - return self.rangeStartIdx, true - } - - return 0, false -} - -// Cancel range select mode, but keep the "moving end" of the range selected. -// Used when pressing 'v' or escape to toggle range select mode, for example. -func (self *ListCursor) CancelRangeSelect() { - self.rangeSelectMode = RangeSelectModeNone -} - -// Cancel range select mode, but keep the top of the range selected. Note that -// this is different from CancelRangeSelect. Useful after deleting a range of items. -func (self *ListCursor) CollapseRangeSelectionToTop() { - start, _ := self.GetSelectionRange() - self.SetSelection(start) -} - -// Returns true if we are in range select mode. Note that we may be in range select -// mode and still only selecting a single item. See AreMultipleItemsSelected below. -func (self *ListCursor) IsSelectingRange() bool { - return self.rangeSelectMode != RangeSelectModeNone -} - -// Returns true if we are in range select mode and selecting multiple items -func (self *ListCursor) AreMultipleItemsSelected() bool { - startIdx, endIdx := self.GetSelectionRange() - return startIdx != endIdx -} - -func (self *ListCursor) GetSelectionRange() (int, int) { - if self.IsSelectingRange() { - return utils.SortRange(self.selectedIdx, self.rangeStartIdx) - } - - return self.selectedIdx, self.selectedIdx -} - -func (self *ListCursor) ToggleStickyRange() { - if self.IsSelectingRange() { - self.CancelRangeSelect() - } else { - self.rangeStartIdx = self.selectedIdx - self.rangeSelectMode = RangeSelectModeSticky - } -} - -func (self *ListCursor) ExpandNonStickyRange(change int) { - if !self.IsSelectingRange() { - self.rangeStartIdx = self.selectedIdx - } - - self.rangeSelectMode = RangeSelectModeNonSticky - - self.SetSelectedLineIdx(self.selectedIdx + change) -} diff --git a/pkg/gui/context/view_trait.go b/pkg/gui/context/view_trait.go deleted file mode 100644 index ccf7d3e9623..00000000000 --- a/pkg/gui/context/view_trait.go +++ /dev/null @@ -1,104 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -const HORIZONTAL_SCROLL_FACTOR = 3 - -type ViewTrait struct { - view *gocui.View -} - -var _ types.IViewTrait = &ViewTrait{} - -func NewViewTrait(view *gocui.View) *ViewTrait { - return &ViewTrait{view: view} -} - -func (self *ViewTrait) FocusPoint(yIdx int) { - self.view.FocusPoint(self.view.OriginX(), yIdx) -} - -func (self *ViewTrait) SetRangeSelectStart(yIdx int) { - self.view.SetRangeSelectStart(yIdx) -} - -func (self *ViewTrait) CancelRangeSelect() { - self.view.CancelRangeSelect() -} - -func (self *ViewTrait) SetViewPortContent(content string) { - _, y := self.view.Origin() - self.view.OverwriteLines(y, content) -} - -func (self *ViewTrait) SetViewPortContentAndClearEverythingElse(content string) { - _, y := self.view.Origin() - self.view.OverwriteLinesAndClearEverythingElse(y, content) -} - -func (self *ViewTrait) SetContentLineCount(lineCount int) { - self.view.SetContentLineCount(lineCount) -} - -func (self *ViewTrait) SetContent(content string) { - self.view.SetContent(content) -} - -func (self *ViewTrait) SetHighlight(highlight bool) { - self.view.Highlight = highlight - self.view.HighlightInactive = false -} - -func (self *ViewTrait) SetFooter(value string) { - self.view.Footer = value -} - -func (self *ViewTrait) SetOriginX(value int) { - self.view.SetOriginX(value) -} - -// tells us the start of line indexes shown in the view currently as well as the capacity of lines shown in the viewport. -func (self *ViewTrait) ViewPortYBounds() (int, int) { - _, start := self.view.Origin() - length := self.view.InnerHeight() - return start, length -} - -func (self *ViewTrait) ScrollLeft() { - self.view.ScrollLeft(self.horizontalScrollAmount()) -} - -func (self *ViewTrait) ScrollRight() { - self.view.ScrollRight(self.horizontalScrollAmount()) -} - -func (self *ViewTrait) horizontalScrollAmount() int { - return self.view.InnerWidth() / HORIZONTAL_SCROLL_FACTOR -} - -func (self *ViewTrait) ScrollUp(value int) { - self.view.ScrollUp(value) -} - -func (self *ViewTrait) ScrollDown(value int) { - self.view.ScrollDown(value) -} - -// this returns the amount we'll scroll if we want to scroll by a page. -func (self *ViewTrait) PageDelta() int { - height := self.view.InnerHeight() - - delta := height - 1 - if delta == 0 { - return 1 - } - - return delta -} - -func (self *ViewTrait) SelectedLineIdx() int { - return self.view.SelectedLineIdx() -} diff --git a/pkg/gui/context/working_tree_context.go b/pkg/gui/context/working_tree_context.go deleted file mode 100644 index f7df1b84a60..00000000000 --- a/pkg/gui/context/working_tree_context.go +++ /dev/null @@ -1,66 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/filetree" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type WorkingTreeContext struct { - *filetree.FileTreeViewModel - *ListContextTrait - *SearchTrait -} - -var ( - _ types.IListContext = (*WorkingTreeContext)(nil) - _ types.ISearchableContext = (*WorkingTreeContext)(nil) -) - -func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext { - viewModel := filetree.NewFileTreeViewModel( - func() []*models.File { return c.Model().Files }, - c.Common, - c.UserConfig().Gui.ShowFileTree, - ) - - getDisplayStrings := func(_ int, _ int) [][]string { - showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons - showNumstat := c.UserConfig().Gui.ShowNumstatInFilesView - lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons, showNumstat, &c.UserConfig().Gui.CustomIcons, c.UserConfig().Gui.ShowRootItemInFileTree) - return lo.Map(lines, func(line string, _ int) []string { - return []string{line} - }) - } - - ctx := &WorkingTreeContext{ - SearchTrait: NewSearchTrait(c), - FileTreeViewModel: viewModel, - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Files, - WindowName: "files", - Key: FILES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - }, - c: c, - }, - } - - ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect)) - - return ctx -} - -func (self *WorkingTreeContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition { - return nil -} diff --git a/pkg/gui/context/worktrees_context.go b/pkg/gui/context/worktrees_context.go deleted file mode 100644 index 3e45f2d4581..00000000000 --- a/pkg/gui/context/worktrees_context.go +++ /dev/null @@ -1,48 +0,0 @@ -package context - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type WorktreesContext struct { - *FilteredListViewModel[*models.Worktree] - *ListContextTrait -} - -var _ types.IListContext = (*WorktreesContext)(nil) - -func NewWorktreesContext(c *ContextCommon) *WorktreesContext { - viewModel := NewFilteredListViewModel( - func() []*models.Worktree { return c.Model().Worktrees }, - func(Worktree *models.Worktree) []string { - return []string{Worktree.Name} - }, - ) - - getDisplayStrings := func(_ int, _ int) [][]string { - return presentation.GetWorktreeDisplayStrings( - c.Tr, - viewModel.GetFilteredList(), - ) - } - - return &WorktreesContext{ - FilteredListViewModel: viewModel, - ListContextTrait: &ListContextTrait{ - Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Worktrees, - WindowName: "files", - Key: WORKTREES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - })), - ListRenderer: ListRenderer{ - list: viewModel, - getDisplayStrings: getDisplayStrings, - }, - c: c, - }, - } -} diff --git a/pkg/gui/context_config.go b/pkg/gui/context_config.go deleted file mode 100644 index 841ca4e60b7..00000000000 --- a/pkg/gui/context_config.go +++ /dev/null @@ -1,22 +0,0 @@ -package gui - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -func (gui *Gui) contextTree() *context.ContextTree { - contextCommon := &context.ContextCommon{ - IGuiCommon: gui.c.IGuiCommon, - Common: gui.c.Common, - } - return context.NewContextTree(contextCommon) -} - -func (gui *Gui) defaultSideContext() types.Context { - if gui.State.Modes.Filtering.Active() { - return gui.State.Contexts.LocalCommits - } - - return gui.State.Contexts.Files -} diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go deleted file mode 100644 index 2f729a7b50b..00000000000 --- a/pkg/gui/controllers.go +++ /dev/null @@ -1,443 +0,0 @@ -package gui - -import ( - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/controllers" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands" - "github.com/jesseduffield/lazygit/pkg/gui/status" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -func (gui *Gui) Helpers() *helpers.Helpers { - return gui.helpers -} - -// Note, the order of controllers determines the order in which keybindings appear -// in the keybinding menu: the earlier that the controller is attached to a context, -// the lower in the list the keybindings will appear. -func (gui *Gui) resetHelpersAndControllers() { - for _, context := range gui.Contexts().Flatten() { - context.ClearAllAttachedControllerFunctions() - } - - helperCommon := gui.c - recordDirectoryHelper := helpers.NewRecordDirectoryHelper(helperCommon) - reposHelper := helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onNewRepo) - rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon) - refsHelper := helpers.NewRefsHelper(helperCommon, rebaseHelper) - suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon) - worktreeHelper := helpers.NewWorktreeHelper(helperCommon, reposHelper, refsHelper, suggestionsHelper) - - setCommitSummary := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage }) - setCommitDescription := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitDescription }) - getCommitSummary := func() string { - return strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent()) - } - - getCommitDescription := func() string { - return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetContent()) - } - getUnwrappedCommitDescription := func() string { - return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetUnwrappedContent()) - } - commitsHelper := helpers.NewCommitsHelper(helperCommon, - getCommitSummary, - setCommitSummary, - getCommitDescription, - getUnwrappedCommitDescription, - setCommitDescription, - ) - - gpgHelper := helpers.NewGpgHelper(helperCommon) - viewHelper := helpers.NewViewHelper(helperCommon, gui.State.Contexts) - patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon) - stagingHelper := helpers.NewStagingHelper(helperCommon) - mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon) - searchHelper := helpers.NewSearchHelper(helperCommon) - - refreshHelper := helpers.NewRefreshHelper( - helperCommon, - refsHelper, - rebaseHelper, - patchBuildingHelper, - stagingHelper, - mergeConflictsHelper, - worktreeHelper, - searchHelper, - ) - diffHelper := helpers.NewDiffHelper(helperCommon) - cherryPickHelper := helpers.NewCherryPickHelper( - helperCommon, - rebaseHelper, - ) - bisectHelper := helpers.NewBisectHelper(helperCommon) - windowHelper := helpers.NewWindowHelper(helperCommon, viewHelper) - modeHelper := helpers.NewModeHelper( - helperCommon, - diffHelper, - patchBuildingHelper, - cherryPickHelper, - rebaseHelper, - bisectHelper, - ) - appStatusHelper := helpers.NewAppStatusHelper( - helperCommon, - func() *status.StatusManager { return gui.statusManager }, - modeHelper, - ) - - gui.helpers = &helpers.Helpers{ - Refs: refsHelper, - Host: helpers.NewHostHelper(helperCommon), - PatchBuilding: patchBuildingHelper, - Staging: stagingHelper, - Bisect: bisectHelper, - Suggestions: suggestionsHelper, - Files: helpers.NewFilesHelper(helperCommon), - WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper, rebaseHelper), - Tags: helpers.NewTagsHelper(helperCommon, commitsHelper, gpgHelper), - BranchesHelper: helpers.NewBranchesHelper(helperCommon, worktreeHelper), - GPG: helpers.NewGpgHelper(helperCommon), - MergeAndRebase: rebaseHelper, - MergeConflicts: mergeConflictsHelper, - CherryPick: cherryPickHelper, - Upstream: helpers.NewUpstreamHelper(helperCommon, suggestionsHelper.GetRemoteBranchesSuggestionsFunc), - AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper), - FixupHelper: helpers.NewFixupHelper(helperCommon), - Commits: commitsHelper, - SuspendResume: helpers.NewSuspendResumeHelper(helperCommon), - Snake: helpers.NewSnakeHelper(helperCommon), - Diff: diffHelper, - Repos: reposHelper, - RecordDirectory: recordDirectoryHelper, - Update: helpers.NewUpdateHelper(helperCommon, gui.Updater), - Window: windowHelper, - View: viewHelper, - Refresh: refreshHelper, - Confirmation: helpers.NewConfirmationHelper(helperCommon), - Mode: modeHelper, - AppStatus: appStatusHelper, - InlineStatus: helpers.NewInlineStatusHelper(helperCommon, windowHelper), - WindowArrangement: helpers.NewWindowArrangementHelper( - gui.c, - windowHelper, - modeHelper, - appStatusHelper, - ), - Search: searchHelper, - Worktree: worktreeHelper, - SubCommits: helpers.NewSubCommitsHelper(helperCommon, refreshHelper), - } - - gui.CustomCommandsClient = custom_commands.NewClient( - helperCommon, - gui.helpers, - ) - - common := controllers.NewControllerCommon(helperCommon, gui) - - syncController := controllers.NewSyncController( - common, - ) - - submodulesController := controllers.NewSubmodulesController(common) - - bisectController := controllers.NewBisectController(common) - - commitMessageController := controllers.NewCommitMessageController( - common, - ) - - commitDescriptionController := controllers.NewCommitDescriptionController( - common, - ) - - remoteBranchesController := controllers.NewRemoteBranchesController(common) - - menuController := controllers.NewMenuController(common) - localCommitsController := controllers.NewLocalCommitsController(common, syncController.HandlePull) - tagsController := controllers.NewTagsController(common) - filesController := controllers.NewFilesController( - common, - ) - mergeConflictsController := controllers.NewMergeConflictsController(common) - remotesController := controllers.NewRemotesController( - common, - func(branches []*models.RemoteBranch) { gui.State.Model.RemoteBranches = branches }, - ) - worktreesController := controllers.NewWorktreesController(common) - undoController := controllers.NewUndoController(common) - globalController := controllers.NewGlobalController(common) - contextLinesController := controllers.NewContextLinesController(common) - renameSimilarityThresholdController := controllers.NewRenameSimilarityThresholdController(common) - verticalScrollControllerFactory := controllers.NewVerticalScrollControllerFactory(common) - viewSelectionControllerFactory := controllers.NewViewSelectionControllerFactory(common) - - branchesController := controllers.NewBranchesController(common) - gitFlowController := controllers.NewGitFlowController(common) - stashController := controllers.NewStashController(common) - commitFilesController := controllers.NewCommitFilesController(common) - patchExplorerControllerFactory := controllers.NewPatchExplorerControllerFactory(common) - stagingController := controllers.NewStagingController(common, gui.State.Contexts.Staging, gui.State.Contexts.StagingSecondary, false) - stagingSecondaryController := controllers.NewStagingController(common, gui.State.Contexts.StagingSecondary, gui.State.Contexts.Staging, true) - mainViewController := controllers.NewMainViewController(common, gui.State.Contexts.Normal, gui.State.Contexts.NormalSecondary) - secondaryViewController := controllers.NewMainViewController(common, gui.State.Contexts.NormalSecondary, gui.State.Contexts.Normal) - patchBuildingController := controllers.NewPatchBuildingController(common) - snakeController := controllers.NewSnakeController(common) - reflogCommitsController := controllers.NewReflogCommitsController(common) - subCommitsController := controllers.NewSubCommitsController(common) - statusController := controllers.NewStatusController(common) - commandLogController := controllers.NewCommandLogController(common) - confirmationController := controllers.NewConfirmationController(common) - promptController := controllers.NewPromptController(common) - suggestionsController := controllers.NewSuggestionsController(common) - jumpToSideWindowController := controllers.NewJumpToSideWindowController(common, gui.handleNextTab) - - sideWindowControllerFactory := controllers.NewSideWindowControllerFactory(common) - - filterControllerFactory := controllers.NewFilterControllerFactory(common) - for _, context := range gui.c.Context().AllFilterable() { - controllers.AttachControllers(context, filterControllerFactory.Create(context)) - } - - searchControllerFactory := controllers.NewSearchControllerFactory(common) - for _, context := range gui.c.Context().AllSearchable() { - controllers.AttachControllers(context, searchControllerFactory.Create(context)) - } - - for _, context := range []controllers.CanViewWorktreeOptions{ - gui.State.Contexts.LocalCommits, - gui.State.Contexts.ReflogCommits, - gui.State.Contexts.SubCommits, - gui.State.Contexts.Stash, - gui.State.Contexts.Branches, - gui.State.Contexts.RemoteBranches, - gui.State.Contexts.Tags, - } { - controllers.AttachControllers(context, controllers.NewWorktreeOptionsController(common, context)) - } - - // allow for navigating between side window contexts - for _, context := range []types.Context{ - gui.State.Contexts.Status, - gui.State.Contexts.Remotes, - gui.State.Contexts.Worktrees, - gui.State.Contexts.Tags, - gui.State.Contexts.Branches, - gui.State.Contexts.RemoteBranches, - gui.State.Contexts.Files, - gui.State.Contexts.Submodules, - gui.State.Contexts.ReflogCommits, - gui.State.Contexts.LocalCommits, - gui.State.Contexts.CommitFiles, - gui.State.Contexts.SubCommits, - gui.State.Contexts.Stash, - } { - controllers.AttachControllers(context, sideWindowControllerFactory.Create(context)) - } - - for _, context := range []controllers.CanSwitchToSubCommits{ - gui.State.Contexts.Branches, - gui.State.Contexts.RemoteBranches, - gui.State.Contexts.Tags, - gui.State.Contexts.ReflogCommits, - } { - controllers.AttachControllers(context, controllers.NewSwitchToSubCommitsController( - common, context, - )) - } - - for _, context := range []controllers.CanSwitchToDiffFiles{ - gui.State.Contexts.LocalCommits, - gui.State.Contexts.SubCommits, - gui.State.Contexts.Stash, - } { - controllers.AttachControllers(context, controllers.NewSwitchToDiffFilesController( - common, context, - )) - } - - for _, context := range []types.Context{ - gui.State.Contexts.Status, - gui.State.Contexts.Files, - gui.State.Contexts.Branches, - gui.State.Contexts.RemoteBranches, - gui.State.Contexts.Tags, - gui.State.Contexts.LocalCommits, - gui.State.Contexts.ReflogCommits, - gui.State.Contexts.SubCommits, - gui.State.Contexts.CommitFiles, - gui.State.Contexts.Stash, - } { - controllers.AttachControllers(context, controllers.NewSwitchToFocusedMainViewController( - common, context, - )) - } - - for _, context := range []controllers.ContainsCommits{ - gui.State.Contexts.LocalCommits, - gui.State.Contexts.ReflogCommits, - gui.State.Contexts.SubCommits, - } { - controllers.AttachControllers(context, controllers.NewBasicCommitsController(common, context)) - } - - controllers.AttachControllers(gui.State.Contexts.ReflogCommits, - reflogCommitsController, - ) - - controllers.AttachControllers(gui.State.Contexts.SubCommits, - subCommitsController, - ) - - // TODO: add scroll controllers for main panels (need to bring some more functionality across for that e.g. reading more from the currently displayed git command) - controllers.AttachControllers(gui.State.Contexts.Staging, - stagingController, - patchExplorerControllerFactory.Create(gui.State.Contexts.Staging), - verticalScrollControllerFactory.Create(gui.State.Contexts.Staging), - ) - - controllers.AttachControllers(gui.State.Contexts.StagingSecondary, - stagingSecondaryController, - patchExplorerControllerFactory.Create(gui.State.Contexts.StagingSecondary), - verticalScrollControllerFactory.Create(gui.State.Contexts.StagingSecondary), - ) - - controllers.AttachControllers(gui.State.Contexts.CustomPatchBuilder, - patchBuildingController, - patchExplorerControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder), - verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder), - ) - - controllers.AttachControllers(gui.State.Contexts.CustomPatchBuilderSecondary, - verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilderSecondary), - ) - - controllers.AttachControllers(gui.State.Contexts.MergeConflicts, - mergeConflictsController, - ) - - controllers.AttachControllers(gui.State.Contexts.Normal, - mainViewController, - verticalScrollControllerFactory.Create(gui.State.Contexts.Normal), - viewSelectionControllerFactory.Create(gui.State.Contexts.Normal), - ) - - controllers.AttachControllers(gui.State.Contexts.NormalSecondary, - secondaryViewController, - verticalScrollControllerFactory.Create(gui.State.Contexts.NormalSecondary), - viewSelectionControllerFactory.Create(gui.State.Contexts.NormalSecondary), - ) - - controllers.AttachControllers(gui.State.Contexts.Files, - filesController, - ) - - controllers.AttachControllers(gui.State.Contexts.Tags, - tagsController, - ) - - controllers.AttachControllers(gui.State.Contexts.Submodules, - submodulesController, - ) - - controllers.AttachControllers(gui.State.Contexts.Branches, - branchesController, - gitFlowController, - ) - - controllers.AttachControllers(gui.State.Contexts.LocalCommits, - localCommitsController, - bisectController, - ) - - controllers.AttachControllers(gui.State.Contexts.CommitFiles, - commitFilesController, - ) - - controllers.AttachControllers(gui.State.Contexts.Remotes, - remotesController, - ) - - controllers.AttachControllers(gui.State.Contexts.Worktrees, - worktreesController, - ) - - controllers.AttachControllers(gui.State.Contexts.Stash, - stashController, - ) - - controllers.AttachControllers(gui.State.Contexts.Menu, - menuController, - ) - - controllers.AttachControllers(gui.State.Contexts.CommitMessage, - commitMessageController, - ) - - controllers.AttachControllers(gui.State.Contexts.CommitDescription, - commitDescriptionController, - verticalScrollControllerFactory.Create(gui.State.Contexts.CommitDescription), - ) - - controllers.AttachControllers(gui.State.Contexts.RemoteBranches, - remoteBranchesController, - ) - - controllers.AttachControllers(gui.State.Contexts.Status, - statusController, - ) - - controllers.AttachControllers(gui.State.Contexts.CommandLog, - commandLogController, - ) - - controllers.AttachControllers(gui.State.Contexts.Confirmation, - confirmationController, - ) - - controllers.AttachControllers(gui.State.Contexts.Prompt, - promptController, - ) - - controllers.AttachControllers(gui.State.Contexts.Suggestions, - suggestionsController, - ) - - controllers.AttachControllers(gui.State.Contexts.Search, - controllers.NewSearchPromptController(common), - ) - - controllers.AttachControllers(gui.State.Contexts.Global, - undoController, - globalController, - contextLinesController, - renameSimilarityThresholdController, - jumpToSideWindowController, - syncController, - ) - - controllers.AttachControllers(gui.State.Contexts.Snake, - snakeController, - ) - - // this must come last so that we've got our click handlers defined against the context - listControllerFactory := controllers.NewListControllerFactory(common) - for _, context := range gui.c.Context().AllList() { - controllers.AttachControllers(context, listControllerFactory.Create(context)) - } -} - -func (gui *Gui) getCommitMessageSetTextareaTextFn(getView func() *gocui.View) func(string) { - return func(text string) { - // using a getView function so that we don't need to worry about when the view is created - view := getView() - view.ClearTextArea() - view.TextArea.TypeString(text) - view.RenderTextArea() - } -} diff --git a/pkg/gui/controllers/attach.go b/pkg/gui/controllers/attach.go deleted file mode 100644 index 8c1dbb91e75..00000000000 --- a/pkg/gui/controllers/attach.go +++ /dev/null @@ -1,15 +0,0 @@ -package controllers - -import "github.com/jesseduffield/lazygit/pkg/gui/types" - -func AttachControllers(context types.Context, controllers ...types.IController) { - for _, controller := range controllers { - context.AddKeybindingsFn(controller.GetKeybindings) - context.AddMouseKeybindingsFn(controller.GetMouseKeybindings) - context.AddOnClickFn(controller.GetOnClick()) - context.AddOnClickFocusedMainViewFn(controller.GetOnClickFocusedMainView()) - context.AddOnRenderToMainFn(controller.GetOnRenderToMain()) - context.AddOnFocusFn(controller.GetOnFocus()) - context.AddOnFocusLostFn(controller.GetOnFocusLost()) - } -} diff --git a/pkg/gui/controllers/base_controller.go b/pkg/gui/controllers/base_controller.go deleted file mode 100644 index 0e122922289..00000000000 --- a/pkg/gui/controllers/base_controller.go +++ /dev/null @@ -1,36 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type baseController struct{} - -func (self *baseController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return nil -} - -func (self *baseController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return nil -} - -func (self *baseController) GetOnClick() func() error { - return nil -} - -func (self *baseController) GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error { - return nil -} - -func (self *baseController) GetOnRenderToMain() func() { - return nil -} - -func (self *baseController) GetOnFocus() func(types.OnFocusOpts) { - return nil -} - -func (self *baseController) GetOnFocusLost() func(types.OnFocusLostOpts) { - return nil -} diff --git a/pkg/gui/controllers/basic_commits_controller.go b/pkg/gui/controllers/basic_commits_controller.go deleted file mode 100644 index b3a5bf38c64..00000000000 --- a/pkg/gui/controllers/basic_commits_controller.go +++ /dev/null @@ -1,446 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context/traits" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -// This controller is for all contexts that contain a list of commits. - -var _ types.IController = &BasicCommitsController{} - -type ContainsCommits interface { - types.Context - types.IListContext - GetSelected() *models.Commit - GetSelectedItems() ([]*models.Commit, int, int) - GetCommits() []*models.Commit - GetSelectedLineIdx() int - GetSelectionRangeAndMode() (int, int, traits.RangeSelectMode) - SetSelectionRangeAndMode(int, int, traits.RangeSelectMode) -} - -type BasicCommitsController struct { - baseController - *ListControllerTrait[*models.Commit] - c *ControllerCommon - context ContainsCommits -} - -func NewBasicCommitsController(c *ControllerCommon, context ContainsCommits) *BasicCommitsController { - return &BasicCommitsController{ - baseController: baseController{}, - c: c, - context: context, - ListControllerTrait: NewListControllerTrait( - c, - context, - context.GetSelected, - context.GetSelectedItems, - ), - } -} - -func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Commits.CheckoutCommit), - Handler: self.withItem(self.checkout), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Checkout, - Tooltip: self.c.Tr.CheckoutCommitTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.CopyCommitAttributeToClipboard), - Handler: self.withItem(self.copyCommitAttribute), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.CopyCommitAttributeToClipboard, - Tooltip: self.c.Tr.CopyCommitAttributeToClipboardTooltip, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.OpenInBrowser), - Handler: self.withItem(self.openInBrowser), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.OpenCommitInBrowser, - }, - { - Key: opts.GetKey(opts.Config.Universal.New), - Handler: self.withItem(self.newBranch), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.CreateNewBranchFromCommit, - }, - { - // Putting this in BasicCommitsController even though we really only want it in the commits - // panel. But I find it important that this ends up next to "New Branch", and I couldn't - // find another way to achieve this. It's not such a big deal to have it in subcommits and - // reflog too, I'd say. - Key: opts.GetKey(opts.Config.Branches.MoveCommitsToNewBranch), - Handler: self.c.Helpers().Refs.MoveCommitsToNewBranch, - GetDisabledReason: self.c.Helpers().Refs.CanMoveCommitsToNewBranch, - Description: self.c.Tr.MoveCommitsToNewBranch, - Tooltip: self.c.Tr.MoveCommitsToNewBranchTooltip, - }, - { - Key: opts.GetKey(opts.Config.Commits.ViewResetOptions), - Handler: self.withItem(self.createResetMenu), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.ViewResetOptions, - Tooltip: self.c.Tr.ResetTooltip, - OpensMenu: true, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.CherryPickCopy), - Handler: self.withItem(self.copyRange), - GetDisabledReason: self.require(self.itemRangeSelected(self.canCopyCommits)), - Description: self.c.Tr.CherryPickCopy, - Tooltip: utils.ResolvePlaceholderString(self.c.Tr.CherryPickCopyTooltip, - map[string]string{ - "paste": keybindings.Label(opts.Config.Commits.PasteCommits), - "escape": keybindings.Label(opts.Config.Universal.Return), - }, - ), - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.ResetCherryPick), - Handler: self.c.Helpers().CherryPick.Reset, - Description: self.c.Tr.ResetCherryPick, - }, - { - Key: opts.GetKey(opts.Config.Universal.OpenDiffTool), - Handler: self.withItem(self.openDiffTool), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.OpenDiffTool, - }, - { - Key: opts.GetKey(opts.Config.Commits.SelectCommitsOfCurrentBranch), - Handler: self.selectCommitsOfCurrentBranch, - GetDisabledReason: self.require(self.canSelectCommitsOfCurrentBranch), - Description: self.c.Tr.SelectCommitsOfCurrentBranch, - }, - // Putting this at the bottom of the list so that it has the lowest priority, - // meaning that if the user has configured another keybinding to the same key - // then that will take precedence. - { - // Hardcoding this key because it's not configurable - Key: opts.GetKey("c"), - Handler: self.handleOldCherryPickKey, - }, - } - - return bindings -} - -func (self *BasicCommitsController) getCommitMessageBody(hash string) string { - commitMessageBody, err := self.c.Git().Commit.GetCommitMessage(hash) - if err != nil { - return "" - } - _, body := self.c.Helpers().Commits.SplitCommitMessageAndDescription(commitMessageBody) - return body -} - -func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) error { - commitMessageBody := self.getCommitMessageBody(commit.Hash()) - var commitMessageBodyDisabled *types.DisabledReason - if commitMessageBody == "" { - commitMessageBodyDisabled = &types.DisabledReason{ - Text: self.c.Tr.CommitHasNoMessageBody, - } - } - - items := []*types.MenuItem{ - { - Label: self.c.Tr.CommitHash, - OnPress: func() error { - return self.copyCommitHashToClipboard(commit) - }, - }, - { - Label: self.c.Tr.CommitSubject, - OnPress: func() error { - return self.copyCommitSubjectToClipboard(commit) - }, - Key: 's', - }, - { - Label: self.c.Tr.CommitMessage, - OnPress: func() error { - return self.copyCommitMessageToClipboard(commit) - }, - Key: 'm', - }, - { - Label: self.c.Tr.CommitMessageBody, - DisabledReason: commitMessageBodyDisabled, - OnPress: func() error { - return self.copyCommitMessageBodyToClipboard(commitMessageBody) - }, - Key: 'b', - }, - { - Label: self.c.Tr.CommitURL, - OnPress: func() error { - return self.copyCommitURLToClipboard(commit) - }, - Key: 'u', - }, - { - Label: self.c.Tr.CommitDiff, - OnPress: func() error { - return self.copyCommitDiffToClipboard(commit) - }, - Key: 'd', - }, - { - Label: self.c.Tr.CommitAuthor, - OnPress: func() error { - return self.copyAuthorToClipboard(commit) - }, - Key: 'a', - }, - } - - commitTagsItem := types.MenuItem{ - Label: self.c.Tr.CommitTags, - OnPress: func() error { - return self.copyCommitTagsToClipboard(commit) - }, - Key: 't', - } - - if len(commit.Tags) == 0 { - commitTagsItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CommitHasNoTags} - } - - items = append(items, &commitTagsItem) - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.Actions.CopyCommitAttributeToClipboard, - Items: items, - }) -} - -func (self *BasicCommitsController) copyCommitHashToClipboard(commit *models.Commit) error { - self.c.LogAction(self.c.Tr.Actions.CopyCommitHashToClipboard) - if err := self.c.OS().CopyToClipboard(commit.Hash()); err != nil { - return err - } - - self.c.Toast(fmt.Sprintf("'%s' %s", commit.Hash(), self.c.Tr.CopiedToClipboard)) - return nil -} - -func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Commit) error { - url, err := self.c.Helpers().Host.GetCommitURL(commit.Hash()) - if err != nil { - return err - } - - self.c.LogAction(self.c.Tr.Actions.CopyCommitURLToClipboard) - if err := self.c.OS().CopyToClipboard(url); err != nil { - return err - } - - self.c.Toast(self.c.Tr.CommitURLCopiedToClipboard) - return nil -} - -func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Commit) error { - diff, err := self.c.Git().Commit.GetCommitDiff(commit.Hash()) - if err != nil { - return err - } - - self.c.LogAction(self.c.Tr.Actions.CopyCommitDiffToClipboard) - if err := self.c.OS().CopyToClipboard(diff); err != nil { - return err - } - - self.c.Toast(self.c.Tr.CommitDiffCopiedToClipboard) - return nil -} - -func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit) error { - author, err := self.c.Git().Commit.GetCommitAuthor(commit.Hash()) - if err != nil { - return err - } - - formattedAuthor := fmt.Sprintf("%s <%s>", author.Name, author.Email) - - self.c.LogAction(self.c.Tr.Actions.CopyCommitAuthorToClipboard) - if err := self.c.OS().CopyToClipboard(formattedAuthor); err != nil { - return err - } - - self.c.Toast(self.c.Tr.CommitAuthorCopiedToClipboard) - return nil -} - -func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.Commit) error { - message, err := self.c.Git().Commit.GetCommitMessage(commit.Hash()) - if err != nil { - return err - } - - self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard) - if err := self.c.OS().CopyToClipboard(message); err != nil { - return err - } - - self.c.Toast(self.c.Tr.CommitMessageCopiedToClipboard) - return nil -} - -func (self *BasicCommitsController) copyCommitMessageBodyToClipboard(commitMessageBody string) error { - self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageBodyToClipboard) - if err := self.c.OS().CopyToClipboard(commitMessageBody); err != nil { - return err - } - - self.c.Toast(self.c.Tr.CommitMessageBodyCopiedToClipboard) - return nil -} - -func (self *BasicCommitsController) copyCommitSubjectToClipboard(commit *models.Commit) error { - message, err := self.c.Git().Commit.GetCommitSubject(commit.Hash()) - if err != nil { - return err - } - - self.c.LogAction(self.c.Tr.Actions.CopyCommitSubjectToClipboard) - if err := self.c.OS().CopyToClipboard(message); err != nil { - return err - } - - self.c.Toast(self.c.Tr.CommitSubjectCopiedToClipboard) - return nil -} - -func (self *BasicCommitsController) copyCommitTagsToClipboard(commit *models.Commit) error { - message := strings.Join(commit.Tags, "\n") - - self.c.LogAction(self.c.Tr.Actions.CopyCommitTagsToClipboard) - if err := self.c.OS().CopyToClipboard(message); err != nil { - return err - } - - self.c.Toast(self.c.Tr.CommitTagsCopiedToClipboard) - return nil -} - -func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error { - url, err := self.c.Helpers().Host.GetCommitURL(commit.Hash()) - if err != nil { - return err - } - - self.c.LogAction(self.c.Tr.Actions.OpenCommitInBrowser) - if err := self.c.OS().OpenLink(url); err != nil { - return err - } - - return nil -} - -func (self *BasicCommitsController) newBranch(commit *models.Commit) error { - return self.c.Helpers().Refs.NewBranch(commit.RefName(), commit.Description(), "") -} - -func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error { - return self.c.Helpers().Refs.CreateGitResetMenu(commit.Hash(), commit.Hash()) -} - -func (self *BasicCommitsController) checkout(commit *models.Commit) error { - return self.c.Helpers().Refs.CreateCheckoutMenu(commit) -} - -func (self *BasicCommitsController) copyRange(*models.Commit) error { - return self.c.Helpers().CherryPick.CopyRange(self.context.GetCommits(), self.context) -} - -func (self *BasicCommitsController) canCopyCommits(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason { - for _, commit := range selectedCommits { - if commit.Hash() == "" { - return &types.DisabledReason{Text: self.c.Tr.CannotCherryPickNonCommit, ShowErrorInPanel: true} - } - } - - return nil -} - -func (self *BasicCommitsController) handleOldCherryPickKey() error { - msg := utils.ResolvePlaceholderString(self.c.Tr.OldCherryPickKeyWarning, - map[string]string{ - "copy": keybindings.Label(self.c.UserConfig().Keybinding.Commits.CherryPickCopy), - "paste": keybindings.Label(self.c.UserConfig().Keybinding.Commits.PasteCommits), - }) - - return errors.New(msg) -} - -func (self *BasicCommitsController) openDiffTool(commit *models.Commit) error { - to := commit.RefName() - from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(commit.ParentRefName()) - _, err := self.c.RunSubprocess(self.c.Git().Diff.OpenDiffToolCmdObj( - git_commands.DiffToolCmdOptions{ - Filepath: ".", - FromCommit: from, - ToCommit: to, - Reverse: reverse, - IsDirectory: true, - Staged: false, - })) - return err -} - -func (self *BasicCommitsController) canSelectCommitsOfCurrentBranch() *types.DisabledReason { - if index := self.findFirstCommitAfterCurrentBranch(); index <= 0 { - return &types.DisabledReason{Text: self.c.Tr.NoCommitsThisBranch} - } - - return nil -} - -func (self *BasicCommitsController) findFirstCommitAfterCurrentBranch() int { - _, index, ok := lo.FindIndexOf(self.context.GetCommits(), func(c *models.Commit) bool { - return c.IsMerge() || c.Status == models.StatusMerged - }) - - if !ok { - return 0 - } - - return index -} - -func (self *BasicCommitsController) selectCommitsOfCurrentBranch() error { - index := self.findFirstCommitAfterCurrentBranch() - if index <= 0 { - return nil - } - - _, _, mode := self.context.GetSelectionRangeAndMode() - if mode != traits.RangeSelectModeSticky { - // If we are in sticky range mode already, keep that; otherwise, open a non-sticky range - mode = traits.RangeSelectModeNonSticky - } - // Create the range from bottom to top, so that when you cancel the range, - // the head commit is selected - self.context.SetSelectionRangeAndMode(0, index-1, mode) - self.context.HandleFocus(types.OnFocusOpts{}) - return nil -} diff --git a/pkg/gui/controllers/bisect_controller.go b/pkg/gui/controllers/bisect_controller.go deleted file mode 100644 index 9ae3eac09a8..00000000000 --- a/pkg/gui/controllers/bisect_controller.go +++ /dev/null @@ -1,310 +0,0 @@ -package controllers - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type BisectController struct { - baseController - *ListControllerTrait[*models.Commit] - c *ControllerCommon -} - -var _ types.IController = &BisectController{} - -func NewBisectController( - c *ControllerCommon, -) *BisectController { - return &BisectController{ - baseController: baseController{}, - c: c, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().LocalCommits, - c.Contexts().LocalCommits.GetSelected, - c.Contexts().LocalCommits.GetSelectedItems, - ), - } -} - -func (self *BisectController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Commits.ViewBisectOptions), - Handler: opts.Guards.OutsideFilterMode(self.withItem(self.openMenu)), - Description: self.c.Tr.ViewBisectOptions, - OpensMenu: true, - }, - } - - return bindings -} - -func (self *BisectController) openMenu(commit *models.Commit) error { - // no shame in getting this directly rather than using the cached value - // given how cheap it is to obtain - info := self.c.Git().Bisect.GetInfo() - if info.Started() { - return self.openMidBisectMenu(info, commit) - } - return self.openStartBisectMenu(info, commit) -} - -func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error { - // if there is not yet a 'current' bisect commit, or if we have - // selected the current commit, we need to jump to the next 'current' commit - // after we perform a bisect action. The reason we don't unconditionally jump - // is that sometimes the user will want to go and mark a few commits as skipped - // in a row and they wouldn't want to be jumped back to the current bisect - // commit each time. - // Originally we were allowing the user to, from the bisect menu, select whether - // they were talking about the selected commit or the current bisect commit, - // and that was a bit confusing (and required extra keypresses). - selectCurrentAfter := info.GetCurrentHash() == "" || info.GetCurrentHash() == commit.Hash() - // we need to wait to reselect if our bisect commits aren't ancestors of our 'start' - // ref, because we'll be reloading our commits in that case. - waitToReselect := selectCurrentAfter && !self.c.Git().Bisect.ReachableFromStart(info) - - // If we have a current hash already, then we always want to use that one. If - // not, we're still picking the initial commits before we really start, so - // use the selected commit in that case. - - bisecting := info.GetCurrentHash() != "" - hashToMark := lo.Ternary(bisecting, info.GetCurrentHash(), commit.Hash()) - shortHashToMark := utils.ShortHash(hashToMark) - - // For marking a commit as bad, when we're not already bisecting, we require - // a single item selected, but once we are bisecting, it doesn't matter because - // the action applies to the HEAD commit rather than the selected commit. - var singleItemIfNotBisecting *types.DisabledReason - if !bisecting { - singleItemIfNotBisecting = self.require(self.singleItemSelected())() - } - - menuItems := []*types.MenuItem{ - { - Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortHashToMark, info.NewTerm()), - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.BisectMark) - if err := self.c.Git().Bisect.Mark(hashToMark, info.NewTerm()); err != nil { - return err - } - - return self.afterMark(selectCurrentAfter, waitToReselect) - }, - DisabledReason: singleItemIfNotBisecting, - Key: 'b', - }, - { - Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortHashToMark, info.OldTerm()), - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.BisectMark) - if err := self.c.Git().Bisect.Mark(hashToMark, info.OldTerm()); err != nil { - return err - } - - return self.afterMark(selectCurrentAfter, waitToReselect) - }, - DisabledReason: singleItemIfNotBisecting, - Key: 'g', - }, - { - Label: fmt.Sprintf(self.c.Tr.Bisect.SkipCurrent, shortHashToMark), - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.BisectSkip) - if err := self.c.Git().Bisect.Skip(hashToMark); err != nil { - return err - } - - return self.afterMark(selectCurrentAfter, waitToReselect) - }, - DisabledReason: singleItemIfNotBisecting, - Key: 's', - }, - } - if info.GetCurrentHash() != "" && info.GetCurrentHash() != commit.Hash() { - menuItems = append(menuItems, lo.ToPtr(types.MenuItem{ - Label: fmt.Sprintf(self.c.Tr.Bisect.SkipSelected, commit.ShortHash()), - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.BisectSkip) - if err := self.c.Git().Bisect.Skip(commit.Hash()); err != nil { - return err - } - - return self.afterMark(selectCurrentAfter, waitToReselect) - }, - DisabledReason: self.require(self.singleItemSelected())(), - Key: 'S', - })) - } - menuItems = append(menuItems, lo.ToPtr(types.MenuItem{ - Label: self.c.Tr.Bisect.ResetOption, - OnPress: func() error { - return self.c.Helpers().Bisect.Reset() - }, - Key: 'r', - })) - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.Bisect.BisectMenuTitle, - Items: menuItems, - }) -} - -func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error { - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.Bisect.BisectMenuTitle, - Items: []*types.MenuItem{ - { - Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortHash(), info.NewTerm()), - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.StartBisect) - if err := self.c.Git().Bisect.Start(); err != nil { - return err - } - - if err := self.c.Git().Bisect.Mark(commit.Hash(), info.NewTerm()); err != nil { - return err - } - - self.c.Helpers().Bisect.PostBisectCommandRefresh() - return nil - }, - DisabledReason: self.require(self.singleItemSelected())(), - Key: 'b', - }, - { - Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortHash(), info.OldTerm()), - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.StartBisect) - if err := self.c.Git().Bisect.Start(); err != nil { - return err - } - - if err := self.c.Git().Bisect.Mark(commit.Hash(), info.OldTerm()); err != nil { - return err - } - - self.c.Helpers().Bisect.PostBisectCommandRefresh() - return nil - }, - DisabledReason: self.require(self.singleItemSelected())(), - Key: 'g', - }, - { - Label: self.c.Tr.Bisect.ChooseTerms, - OnPress: func() error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.Bisect.OldTermPrompt, - HandleConfirm: func(oldTerm string) error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.Bisect.NewTermPrompt, - HandleConfirm: func(newTerm string) error { - self.c.LogAction(self.c.Tr.Actions.StartBisect) - if err := self.c.Git().Bisect.StartWithTerms(oldTerm, newTerm); err != nil { - return err - } - - self.c.Helpers().Bisect.PostBisectCommandRefresh() - return nil - }, - }) - return nil - }, - }) - return nil - }, - Key: 't', - }, - }, - }) -} - -func (self *BisectController) showBisectCompleteMessage(candidateHashes []string) error { - prompt := self.c.Tr.Bisect.CompletePrompt - if len(candidateHashes) > 1 { - prompt = self.c.Tr.Bisect.CompletePromptIndeterminate - } - - formattedCommits, err := self.c.Git().Commit.GetCommitsOneline(candidateHashes) - if err != nil { - return err - } - - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Bisect.CompleteTitle, - Prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)), - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.ResetBisect) - if err := self.c.Git().Bisect.Reset(); err != nil { - return err - } - - self.c.Helpers().Bisect.PostBisectCommandRefresh() - return nil - }, - }) - - return nil -} - -func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool) error { - done, candidateHashes, err := self.c.Git().Bisect.IsDone() - if err != nil { - return err - } - - if err := self.afterBisectMarkRefresh(selectCurrent, waitToReselect); err != nil { - return err - } - - if done { - return self.showBisectCompleteMessage(candidateHashes) - } - - return nil -} - -func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error { - selectFn := func() { - if selectCurrent { - self.selectCurrentBisectCommit() - } - } - - if waitToReselect { - self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{}, Then: selectFn}) - return nil - } - - selectFn() - - self.c.Helpers().Bisect.PostBisectCommandRefresh() - return nil -} - -func (self *BisectController) selectCurrentBisectCommit() { - info := self.c.Git().Bisect.GetInfo() - if info.GetCurrentHash() != "" { - // find index of commit with that hash, move cursor to that. - for i, commit := range self.c.Model().Commits { - if commit.Hash() == info.GetCurrentHash() { - self.context().SetSelection(i) - self.context().HandleFocus(types.OnFocusOpts{}) - break - } - } - } -} - -func (self *BisectController) context() *context.LocalCommitsContext { - return self.c.Contexts().LocalCommits -} diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go deleted file mode 100644 index f430f7b0ef8..00000000000 --- a/pkg/gui/controllers/branches_controller.go +++ /dev/null @@ -1,875 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type BranchesController struct { - baseController - *ListControllerTrait[*models.Branch] - c *ControllerCommon -} - -var _ types.IController = &BranchesController{} - -func NewBranchesController( - c *ControllerCommon, -) *BranchesController { - return &BranchesController{ - baseController: baseController{}, - c: c, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().Branches, - c.Contexts().Branches.GetSelected, - c.Contexts().Branches.GetSelectedItems, - ), - } -} - -func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.withItem(self.press), - GetDisabledReason: self.require( - self.singleItemSelected(), - self.notPulling, - ), - Description: self.c.Tr.Checkout, - Tooltip: self.c.Tr.CheckoutTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.New), - Handler: self.withItem(self.newBranch), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.NewBranch, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Branches.MoveCommitsToNewBranch), - Handler: self.c.Helpers().Refs.MoveCommitsToNewBranch, - GetDisabledReason: self.c.Helpers().Refs.CanMoveCommitsToNewBranch, - Description: self.c.Tr.MoveCommitsToNewBranch, - Tooltip: self.c.Tr.MoveCommitsToNewBranchTooltip, - }, - { - Key: opts.GetKey(opts.Config.Branches.CreatePullRequest), - Handler: self.withItem(self.handleCreatePullRequest), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.CreatePullRequest, - }, - { - Key: opts.GetKey(opts.Config.Branches.ViewPullRequestOptions), - Handler: self.withItem(self.handleCreatePullRequestMenu), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.CreatePullRequestOptions, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Branches.CopyPullRequestURL), - Handler: self.copyPullRequestURL, - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.CopyPullRequestURL, - }, - { - Key: opts.GetKey(opts.Config.Branches.CheckoutBranchByName), - Handler: self.checkoutByName, - Description: self.c.Tr.CheckoutByName, - Tooltip: self.c.Tr.CheckoutByNameTooltip, - }, - { - Key: opts.GetKey(opts.Config.Branches.CheckoutPreviousBranch), - Handler: self.checkoutPreviousBranch, - Description: self.c.Tr.CheckoutPreviousBranch, - }, - { - Key: opts.GetKey(opts.Config.Branches.ForceCheckoutBranch), - Handler: self.forceCheckout, - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.ForceCheckout, - Tooltip: self.c.Tr.ForceCheckoutTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: self.withItems(self.delete), - GetDisabledReason: self.require(self.itemRangeSelected(self.branchesAreReal)), - Description: self.c.Tr.Delete, - Tooltip: self.c.Tr.BranchDeleteTooltip, - OpensMenu: true, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Branches.RebaseBranch), - Handler: opts.Guards.OutsideFilterMode(self.withItem(self.rebase)), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.RebaseBranch, - Tooltip: self.c.Tr.RebaseBranchTooltip, - OpensMenu: true, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch), - Handler: opts.Guards.OutsideFilterMode(self.merge), - GetDisabledReason: self.require(self.singleItemSelected(self.notMergingIntoYourself)), - Description: self.c.Tr.Merge, - Tooltip: self.c.Tr.MergeBranchTooltip, - DisplayOnScreen: true, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Branches.FastForward), - Handler: self.withItem(self.fastForward), - GetDisabledReason: self.require(self.singleItemSelected(self.branchIsReal)), - Description: self.c.Tr.FastForward, - Tooltip: self.c.Tr.FastForwardTooltip, - }, - { - Key: opts.GetKey(opts.Config.Branches.CreateTag), - Handler: self.withItem(self.createTag), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.NewTag, - }, - { - Key: opts.GetKey(opts.Config.Branches.SortOrder), - Handler: self.createSortMenu, - Description: self.c.Tr.SortOrder, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.ViewResetOptions), - Handler: self.withItem(self.createResetMenu), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.ViewResetOptions, - OpensMenu: true, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Branches.RenameBranch), - Handler: self.withItem(self.rename), - GetDisabledReason: self.require(self.singleItemSelected(self.branchIsReal)), - Description: self.c.Tr.RenameBranch, - }, - { - Key: opts.GetKey(opts.Config.Branches.SetUpstream), - Handler: self.withItem(self.viewUpstreamOptions), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.ViewBranchUpstreamOptions, - Tooltip: self.c.Tr.ViewBranchUpstreamOptionsTooltip, - ShortDescription: self.c.Tr.Upstream, - OpensMenu: true, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.OpenDiffTool), - Handler: self.withItem(func(selectedBranch *models.Branch) error { - return self.c.Helpers().Diff.OpenDiffToolForRef(selectedBranch) - }), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.OpenDiffTool, - }, - } -} - -func (self *BranchesController) GetOnRenderToMain() func() { - return func() { - self.c.Helpers().Diff.WithDiffModeCheck(func() { - var task types.UpdateTask - branch := self.context().GetSelected() - if branch == nil { - task = types.NewRenderStringTask(self.c.Tr.NoBranchesThisRepo) - } else { - cmdObj := self.c.Git().Branch.GetGraphCmdObj(branch.FullRefName()) - - task = types.NewRunPtyTask(cmdObj.GetCmd()) - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: self.c.Tr.LogTitle, - Task: task, - }, - }) - }) - } -} - -func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branch) error { - upstream := lo.Ternary(selectedBranch.RemoteBranchStoredLocally(), - selectedBranch.ShortUpstreamRefName(), - self.c.Tr.UpstreamGenericName) - - viewDivergenceItem := &types.MenuItem{ - LabelColumns: []string{self.c.Tr.ViewDivergenceFromUpstream}, - OnPress: func() error { - branch := self.context().GetSelected() - if branch == nil { - return nil - } - - return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{ - Ref: branch, - TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), upstream), - RefToShowDivergenceFrom: branch.FullUpstreamRefName(), - Context: self.context(), - ShowBranchHeads: false, - }) - }, - } - - var disabledReason *types.DisabledReason - baseBranch, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(selectedBranch, self.c.Model().MainBranches) - if err != nil { - return err - } - if baseBranch == "" { - baseBranch = self.c.Tr.CouldNotDetermineBaseBranch - disabledReason = &types.DisabledReason{Text: self.c.Tr.CouldNotDetermineBaseBranch} - } - shortBaseBranchName := helpers.ShortBranchName(baseBranch) - label := utils.ResolvePlaceholderString( - self.c.Tr.ViewDivergenceFromBaseBranch, - map[string]string{"baseBranch": shortBaseBranchName}, - ) - viewDivergenceFromBaseBranchItem := &types.MenuItem{ - LabelColumns: []string{label}, - Key: 'b', - OnPress: func() error { - branch := self.context().GetSelected() - if branch == nil { - return nil - } - - return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{ - Ref: branch, - TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), shortBaseBranchName), - RefToShowDivergenceFrom: baseBranch, - Context: self.context(), - ShowBranchHeads: false, - }) - }, - DisabledReason: disabledReason, - } - - unsetUpstreamItem := &types.MenuItem{ - LabelColumns: []string{self.c.Tr.UnsetUpstream}, - OnPress: func() error { - if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{ - Mode: types.SYNC, - Scope: []types.RefreshableView{ - types.BRANCHES, - types.COMMITS, - }, - }) - return nil - }, - Key: 'u', - } - - setUpstreamItem := &types.MenuItem{ - LabelColumns: []string{self.c.Tr.SetUpstream}, - OnPress: func() error { - return self.c.Helpers().Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error { - upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream) - if err != nil { - return err - } - - if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{ - Mode: types.SYNC, - Scope: []types.RefreshableView{ - types.BRANCHES, - types.COMMITS, - }, - }) - return nil - }) - }, - Key: 's', - } - - upstreamResetOptions := utils.ResolvePlaceholderString( - self.c.Tr.ViewUpstreamResetOptions, - map[string]string{"upstream": upstream}, - ) - upstreamResetTooltip := utils.ResolvePlaceholderString( - self.c.Tr.ViewUpstreamResetOptionsTooltip, - map[string]string{"upstream": upstream}, - ) - - upstreamRebaseOptions := utils.ResolvePlaceholderString( - self.c.Tr.ViewUpstreamRebaseOptions, - map[string]string{"upstream": upstream}, - ) - upstreamRebaseTooltip := utils.ResolvePlaceholderString( - self.c.Tr.ViewUpstreamRebaseOptionsTooltip, - map[string]string{"upstream": upstream}, - ) - - upstreamResetItem := &types.MenuItem{ - LabelColumns: []string{upstreamResetOptions}, - OpensMenu: true, - OnPress: func() error { - // We only can invoke this when the remote branch is stored locally, so using the selectedBranch here is fine. - err := self.c.Helpers().Refs.CreateGitResetMenu(upstream, selectedBranch.FullUpstreamRefName()) - if err != nil { - return err - } - return nil - }, - Tooltip: upstreamResetTooltip, - Key: 'g', - } - - upstreamRebaseItem := &types.MenuItem{ - LabelColumns: []string{upstreamRebaseOptions}, - OpensMenu: true, - OnPress: func() error { - if err := self.c.Helpers().MergeAndRebase.RebaseOntoRef(upstream); err != nil { - return err - } - return nil - }, - Tooltip: upstreamRebaseTooltip, - Key: 'r', - } - - if !selectedBranch.IsTrackingRemote() { - unsetUpstreamItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError} - } - - if !selectedBranch.RemoteBranchStoredLocally() { - viewDivergenceItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError} - upstreamResetItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError} - upstreamRebaseItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError} - } - - options := []*types.MenuItem{ - viewDivergenceItem, - viewDivergenceFromBaseBranchItem, - unsetUpstreamItem, - setUpstreamItem, - upstreamResetItem, - upstreamRebaseItem, - } - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.BranchUpstreamOptionsTitle, - Items: options, - }) -} - -func (self *BranchesController) Context() types.Context { - return self.context() -} - -func (self *BranchesController) context() *context.BranchesContext { - return self.c.Contexts().Branches -} - -func (self *BranchesController) press(selectedBranch *models.Branch) error { - if selectedBranch == self.c.Helpers().Refs.GetCheckedOutRef() { - return errors.New(self.c.Tr.AlreadyCheckedOutBranch) - } - - worktreeForRef, ok := self.worktreeForBranch(selectedBranch) - if ok && !worktreeForRef.IsCurrent { - return self.promptToCheckoutWorktree(worktreeForRef) - } - - self.c.LogAction(self.c.Tr.Actions.CheckoutBranch) - return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{}) -} - -func (self *BranchesController) notPulling() *types.DisabledReason { - currentBranch := self.c.Helpers().Refs.GetCheckedOutRef() - if currentBranch != nil { - op := self.c.State().GetItemOperation(currentBranch) - if op == types.ItemOperationFastForwarding || op == types.ItemOperationPulling { - return &types.DisabledReason{Text: self.c.Tr.CantCheckoutBranchWhilePulling} - } - } - - return nil -} - -func (self *BranchesController) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) { - return git_commands.WorktreeForBranch(branch, self.c.Model().Worktrees) -} - -func (self *BranchesController) promptToCheckoutWorktree(worktree *models.Worktree) error { - prompt := utils.ResolvePlaceholderString(self.c.Tr.AlreadyCheckedOutByWorktree, map[string]string{ - "worktreeName": worktree.Name, - }) - - return self.c.ConfirmIf(!self.c.UserConfig().Gui.SkipSwitchWorktreeOnCheckoutWarning, types.ConfirmOpts{ - Title: self.c.Tr.SwitchToWorktree, - Prompt: prompt, - HandleConfirm: func() error { - return self.c.Helpers().Worktree.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY) - }, - }) -} - -func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.Branch) error { - if !selectedBranch.IsTrackingRemote() { - return errors.New(self.c.Tr.PullRequestNoUpstream) - } - return self.createPullRequest(selectedBranch.UpstreamBranch, "") -} - -func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *models.Branch) error { - checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef() - - return self.createPullRequestMenu(selectedBranch, checkedOutBranch) -} - -func (self *BranchesController) copyPullRequestURL() error { - branch := self.context().GetSelected() - - branchExistsOnRemote := self.c.Git().Remote.CheckRemoteBranchExists(branch.Name) - - if !branchExistsOnRemote { - return errors.New(self.c.Tr.NoBranchOnRemote) - } - - url, err := self.c.Helpers().Host.GetPullRequestURL(branch.Name, "") - if err != nil { - return err - } - self.c.LogAction(self.c.Tr.Actions.CopyPullRequestURL) - if err := self.c.OS().CopyToClipboard(url); err != nil { - return err - } - - self.c.Toast(self.c.Tr.PullRequestURLCopiedToClipboard) - - return nil -} - -func (self *BranchesController) forceCheckout() error { - branch := self.context().GetSelected() - message := self.c.Tr.SureForceCheckout - title := self.c.Tr.ForceCheckoutBranch - - self.c.Confirm(types.ConfirmOpts{ - Title: title, - Prompt: message, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.ForceCheckoutBranch) - if err := self.c.Git().Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }, - }) - - return nil -} - -func (self *BranchesController) checkoutPreviousBranch() error { - self.c.LogAction(self.c.Tr.Actions.CheckoutBranch) - return self.c.Helpers().Refs.CheckoutPreviousRef() -} - -func (self *BranchesController) checkoutByName() error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.BranchName + ":", - FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(), - HandleConfirm: func(response string) error { - self.c.LogAction("Checkout branch") - _, branchName, found := self.c.Helpers().Refs.ParseRemoteBranchName(response) - if found { - return self.c.Helpers().Refs.CheckoutRemoteBranch(response, branchName) - } - return self.c.Helpers().Refs.CheckoutRef(response, types.CheckoutRefOptions{ - OnRefNotFound: func(ref string) error { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.BranchNotFoundTitle, - Prompt: fmt.Sprintf("%s %s%s", self.c.Tr.BranchNotFoundPrompt, ref, "?"), - HandleConfirm: func() error { - return self.createNewBranchWithName(ref) - }, - }) - - return nil - }, - }) - }, - }, - ) - - return nil -} - -func (self *BranchesController) createNewBranchWithName(newBranchName string) error { - branch := self.context().GetSelected() - if branch == nil { - return nil - } - - if err := self.c.Git().Branch.New(newBranchName, branch.FullRefName()); err != nil { - return err - } - - self.context().SetSelection(0) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true}) - return nil -} - -func (self *BranchesController) localDelete(branches []*models.Branch) error { - return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branches) -} - -func (self *BranchesController) remoteDelete(branches []*models.Branch) error { - remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch { - return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote} - }) - return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(remoteBranches, false) -} - -func (self *BranchesController) localAndRemoteDelete(branches []*models.Branch) error { - return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branches) -} - -func (self *BranchesController) delete(branches []*models.Branch) error { - checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef() - isBranchCheckedOut := lo.SomeBy(branches, func(branch *models.Branch) bool { - return checkedOutBranch.Name == branch.Name - }) - hasUpstream := lo.EveryBy(branches, func(branch *models.Branch) bool { - return branch.IsTrackingRemote() && !branch.UpstreamGone - }) - - localDeleteItem := &types.MenuItem{ - Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalBranches, self.c.Tr.DeleteLocalBranch), - Key: 'c', - OnPress: func() error { - return self.localDelete(branches) - }, - } - if isBranchCheckedOut { - localDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch} - } - - remoteDeleteItem := &types.MenuItem{ - Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteRemoteBranches, self.c.Tr.DeleteRemoteBranch), - Key: 'r', - OnPress: func() error { - return self.remoteDelete(branches) - }, - } - if !hasUpstream { - remoteDeleteItem.DisabledReason = &types.DisabledReason{ - Text: lo.Ternary(len(branches) > 1, self.c.Tr.UpstreamsNotSetError, self.c.Tr.UpstreamNotSetError), - } - } - - deleteBothItem := &types.MenuItem{ - Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalAndRemoteBranches, self.c.Tr.DeleteLocalAndRemoteBranch), - Key: 'b', - OnPress: func() error { - return self.localAndRemoteDelete(branches) - }, - } - if isBranchCheckedOut { - deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch} - } else if !hasUpstream { - deleteBothItem.DisabledReason = &types.DisabledReason{ - Text: lo.Ternary(len(branches) > 1, self.c.Tr.UpstreamsNotSetError, self.c.Tr.UpstreamNotSetError), - } - } - - var menuTitle string - if len(branches) == 1 { - menuTitle = utils.ResolvePlaceholderString( - self.c.Tr.DeleteBranchTitle, - map[string]string{ - "selectedBranchName": branches[0].Name, - }, - ) - } else { - menuTitle = self.c.Tr.DeleteBranchesTitle - } - - return self.c.Menu(types.CreateMenuOptions{ - Title: menuTitle, - Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem, deleteBothItem}, - }) -} - -func (self *BranchesController) merge() error { - selectedBranchName := self.context().GetSelected().Name - return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName) -} - -func (self *BranchesController) rebase(branch *models.Branch) error { - return self.c.Helpers().MergeAndRebase.RebaseOntoRef(branch.Name) -} - -func (self *BranchesController) fastForward(branch *models.Branch) error { - if !branch.IsTrackingRemote() { - return errors.New(self.c.Tr.FwdNoUpstream) - } - if !branch.RemoteBranchStoredLocally() { - return errors.New(self.c.Tr.FwdNoLocalUpstream) - } - if branch.IsAheadForPull() { - return errors.New(self.c.Tr.FwdCommitsToPush) - } - - action := self.c.Tr.Actions.FastForwardBranch - - return self.c.WithInlineStatus(branch, types.ItemOperationFastForwarding, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error { - worktree, ok := self.worktreeForBranch(branch) - if ok { - self.c.LogAction(action) - - worktreeGitDir := "" - worktreePath := "" - // if it is the current worktree path, no need to specify the path - if !worktree.IsCurrent { - worktreeGitDir = worktree.GitDir - worktreePath = worktree.Path - } - - err := self.c.Git().Sync.Pull( - task, - git_commands.PullOptions{ - RemoteName: branch.UpstreamRemote, - BranchName: branch.UpstreamBranch, - FastForwardOnly: true, - WorktreeGitDir: worktreeGitDir, - WorktreePath: worktreePath, - }, - ) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return err - } - - self.c.LogAction(action) - - err := self.c.Git().Sync.FastForward( - task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch, - ) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}}) - return err - }) -} - -func (self *BranchesController) createTag(branch *models.Branch) error { - return self.c.Helpers().Tags.OpenCreateTagPrompt(branch.FullRefName(), func() {}) -} - -func (self *BranchesController) createSortMenu() error { - return self.c.Helpers().Refs.CreateSortOrderMenu( - []string{"recency", "alphabetical", "date"}, - self.c.Tr.SortOrderPromptLocalBranches, - func(sortOrder string) error { - if self.c.UserConfig().Git.LocalBranchSortOrder != sortOrder { - self.c.UserConfig().Git.LocalBranchSortOrder = sortOrder - self.c.Contexts().Branches.SetSelection(0) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}}) - return nil - } - return nil - }, - self.c.UserConfig().Git.LocalBranchSortOrder) -} - -func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error { - return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name, selectedBranch.FullRefName()) -} - -func (self *BranchesController) rename(branch *models.Branch) error { - promptForNewName := func() error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.NewBranchNamePrompt + " " + branch.Name + ":", - InitialContent: branch.Name, - HandleConfirm: func(newBranchName string) error { - self.c.LogAction(self.c.Tr.Actions.RenameBranch) - if err := self.c.Git().Branch.Rename(branch.Name, helpers.SanitizedBranchName(newBranchName)); err != nil { - return err - } - - // need to find where the branch is now so that we can re-select it. That means we need to refetch the branches synchronously and then find our branch - self.c.Refresh(types.RefreshOptions{ - Mode: types.SYNC, - Scope: []types.RefreshableView{types.BRANCHES, types.WORKTREES}, - }) - - // now that we've got our stuff again we need to find that branch and reselect it. - for i, newBranch := range self.c.Model().Branches { - if newBranch.Name == newBranchName { - self.context().SetSelection(i) - self.context().HandleRender() - } - } - - return nil - }, - }) - - return nil - } - - // I could do an explicit check here for whether the branch is tracking a remote branch - // but if we've selected it we'll already know that via Pullables and Pullables. - // Bit of a hack but I'm lazy. - return self.c.ConfirmIf(branch.IsTrackingRemote(), types.ConfirmOpts{ - Title: self.c.Tr.RenameBranch, - Prompt: self.c.Tr.RenameBranchWarning, - HandleConfirm: promptForNewName, - }) -} - -func (self *BranchesController) newBranch(selectedBranch *models.Branch) error { - return self.c.Helpers().Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "") -} - -func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error { - menuItems := make([]*types.MenuItem, 0, 4) - - fromToLabelColumns := func(from string, to string) []string { - return []string{fmt.Sprintf("%s → %s", from, to)} - } - - menuItemsForBranch := func(branch *models.Branch) []*types.MenuItem { - return []*types.MenuItem{ - { - LabelColumns: fromToLabelColumns(branch.Name, self.c.Tr.DefaultBranch), - OnPress: func() error { - return self.handleCreatePullRequest(branch) - }, - }, - { - LabelColumns: fromToLabelColumns(branch.Name, self.c.Tr.SelectBranch), - OnPress: func() error { - if !branch.IsTrackingRemote() { - return errors.New(self.c.Tr.PullRequestNoUpstream) - } - - if len(self.c.Model().Remotes) == 1 { - toRemote := self.c.Model().Remotes[0].Name - self.c.Log.Debugf("PR will target the only existing remote '%s'", toRemote) - return self.promptForTargetBranchNameAndCreatePullRequest(branch, toRemote) - } - - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.SelectTargetRemote, - FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(), - HandleConfirm: func(toRemote string) error { - self.c.Log.Debugf("PR will target remote '%s'", toRemote) - - return self.promptForTargetBranchNameAndCreatePullRequest(branch, toRemote) - }, - }) - - return nil - }, - }, - } - } - - if selectedBranch != checkedOutBranch { - menuItems = append(menuItems, - &types.MenuItem{ - LabelColumns: fromToLabelColumns(checkedOutBranch.Name, selectedBranch.Name), - OnPress: func() error { - if !checkedOutBranch.IsTrackingRemote() || !selectedBranch.IsTrackingRemote() { - return errors.New(self.c.Tr.PullRequestNoUpstream) - } - return self.createPullRequest(checkedOutBranch.UpstreamBranch, selectedBranch.UpstreamBranch) - }, - }, - ) - menuItems = append(menuItems, menuItemsForBranch(checkedOutBranch)...) - } - - menuItems = append(menuItems, menuItemsForBranch(selectedBranch)...) - - return self.c.Menu(types.CreateMenuOptions{Title: fmt.Sprint(self.c.Tr.CreatePullRequestOptions), Items: menuItems}) -} - -func (self *BranchesController) promptForTargetBranchNameAndCreatePullRequest(fromBranch *models.Branch, toRemote string) error { - remoteDoesNotExist := lo.NoneBy(self.c.Model().Remotes, func(remote *models.Remote) bool { - return remote.Name == toRemote - }) - if remoteDoesNotExist { - return fmt.Errorf(self.c.Tr.NoValidRemoteName, toRemote) - } - - self.c.Prompt(types.PromptOpts{ - Title: fmt.Sprintf("%s → %s/", fromBranch.UpstreamBranch, toRemote), - FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteBranchesForRemoteSuggestionsFunc(toRemote), - HandleConfirm: func(toBranch string) error { - self.c.Log.Debugf("PR will target branch '%s' on remote '%s'", toBranch, toRemote) - return self.createPullRequest(fromBranch.UpstreamBranch, toBranch) - }, - }) - - return nil -} - -func (self *BranchesController) createPullRequest(from string, to string) error { - url, err := self.c.Helpers().Host.GetPullRequestURL(from, to) - if err != nil { - return err - } - - self.c.LogAction(self.c.Tr.Actions.OpenPullRequest) - - if err := self.c.OS().OpenLink(url); err != nil { - return err - } - - return nil -} - -func (self *BranchesController) branchIsReal(branch *models.Branch) *types.DisabledReason { - if !branch.IsRealBranch() { - return &types.DisabledReason{Text: self.c.Tr.SelectedItemIsNotABranch} - } - - return nil -} - -func (self *BranchesController) branchesAreReal(selectedBranches []*models.Branch, startIdx int, endIdx int) *types.DisabledReason { - if !lo.EveryBy(selectedBranches, func(branch *models.Branch) bool { - return branch.IsRealBranch() - }) { - return &types.DisabledReason{Text: self.c.Tr.SelectedItemIsNotABranch} - } - - return nil -} - -func (self *BranchesController) notMergingIntoYourself(branch *models.Branch) *types.DisabledReason { - selectedBranchName := branch.Name - checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name - - if checkedOutBranch == selectedBranchName { - return &types.DisabledReason{Text: self.c.Tr.CantMergeBranchIntoItself} - } - - return nil -} diff --git a/pkg/gui/controllers/command_log_controller.go b/pkg/gui/controllers/command_log_controller.go deleted file mode 100644 index 3056a9d96c2..00000000000 --- a/pkg/gui/controllers/command_log_controller.go +++ /dev/null @@ -1,41 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type CommandLogController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &CommandLogController{} - -func NewCommandLogController( - c *ControllerCommon, -) *CommandLogController { - return &CommandLogController{ - baseController: baseController{}, - c: c, - } -} - -func (self *CommandLogController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{} - - return bindings -} - -func (self *CommandLogController) GetOnFocusLost() func(types.OnFocusLostOpts) { - return func(types.OnFocusLostOpts) { - self.c.Views().Extras.Autoscroll = true - } -} - -func (self *CommandLogController) Context() types.Context { - return self.context() -} - -func (self *CommandLogController) context() types.Context { - return self.c.Contexts().CommandLog -} diff --git a/pkg/gui/controllers/commit_description_controller.go b/pkg/gui/controllers/commit_description_controller.go deleted file mode 100644 index 24653c7674b..00000000000 --- a/pkg/gui/controllers/commit_description_controller.go +++ /dev/null @@ -1,142 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type CommitDescriptionController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &CommitMessageController{} - -func NewCommitDescriptionController( - c *ControllerCommon, -) *CommitDescriptionController { - return &CommitDescriptionController{ - baseController: baseController{}, - c: c, - } -} - -func (self *CommitDescriptionController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.TogglePanel), - Handler: self.handleTogglePanel, - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Handler: self.close, - }, - { - Key: opts.GetKey(opts.Config.Universal.ConfirmInEditor), - Handler: self.confirm, - }, - { - Key: opts.GetKey(opts.Config.Universal.ConfirmInEditorAlt), - Handler: self.confirm, - }, - { - Key: opts.GetKey(opts.Config.CommitMessage.CommitMenu), - Handler: self.openCommitMenu, - }, - } - - return bindings -} - -func (self *CommitDescriptionController) Context() types.Context { - return self.c.Contexts().CommitDescription -} - -func (self *CommitDescriptionController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: self.Context().GetViewName(), - FocusedView: self.c.Contexts().CommitMessage.GetViewName(), - Key: gocui.MouseLeft, - Handler: self.onClick, - }, - } -} - -func (self *CommitDescriptionController) GetOnFocus() func(types.OnFocusOpts) { - return func(types.OnFocusOpts) { - footer := "" - if self.c.UserConfig().Keybinding.Universal.ConfirmInEditor != "" || self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt != "" { - if self.c.UserConfig().Keybinding.Universal.ConfirmInEditor == "" { - footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooter, - map[string]string{ - "confirmInEditorKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt), - }) - } else if self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt == "" { - footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooter, - map[string]string{ - "confirmInEditorKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditor), - }) - } else { - footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooterTwoBindings, - map[string]string{ - "confirmInEditorKeybinding1": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditor), - "confirmInEditorKeybinding2": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt), - }) - } - } - self.c.Views().CommitDescription.Footer = footer - } -} - -func (self *CommitDescriptionController) switchToCommitMessage() error { - self.c.Context().Replace(self.c.Contexts().CommitMessage) - return nil -} - -func (self *CommitDescriptionController) handleTogglePanel() error { - // The default keybinding for this action is "", which means that we - // also get here when pasting multi-line text that contains tabs. In that - // case we don't want to toggle the panel, but insert the tab as a character - // (somehow, see below). - // - // Only do this if the TogglePanel command is actually mapped to "" - // (the default). If it's not, we can only hope that it's mapped to some - // ctrl key or fn key, which is unlikely to occur in pasted text. And if - // they mapped some *other* command to "", then we're totally out of - // luck. - if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.TogglePanel == "" { - // Handling tabs in pasted commit messages is not optimal, but hopefully - // good enough for now. We simply insert 4 spaces without worrying about - // column alignment. This works well enough for leading indentation, - // which is common in pasted code snippets. - view := self.Context().GetView() - for range 4 { - view.Editor.Edit(view, gocui.KeySpace, ' ', 0) - } - return nil - } - - return self.switchToCommitMessage() -} - -func (self *CommitDescriptionController) close() error { - self.c.Helpers().Commits.CloseCommitMessagePanel() - return nil -} - -func (self *CommitDescriptionController) confirm() error { - return self.c.Helpers().Commits.HandleCommitConfirm() -} - -func (self *CommitDescriptionController) openCommitMenu() error { - authorSuggestion := self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc() - return self.c.Helpers().Commits.OpenCommitMenu(authorSuggestion) -} - -func (self *CommitDescriptionController) onClick(opts gocui.ViewMouseBindingOpts) error { - self.c.Context().Replace(self.c.Contexts().CommitDescription) - return nil -} diff --git a/pkg/gui/controllers/commit_message_controller.go b/pkg/gui/controllers/commit_message_controller.go deleted file mode 100644 index e9ff1bc67a3..00000000000 --- a/pkg/gui/controllers/commit_message_controller.go +++ /dev/null @@ -1,200 +0,0 @@ -package controllers - -import ( - "errors" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type CommitMessageController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &CommitMessageController{} - -func NewCommitMessageController( - c *ControllerCommon, -) *CommitMessageController { - return &CommitMessageController{ - baseController: baseController{}, - c: c, - } -} - -func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.SubmitEditorText), - Handler: self.confirm, - Description: self.c.Tr.Confirm, - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Handler: self.close, - Description: self.c.Tr.Close, - }, - { - Key: opts.GetKey(opts.Config.Universal.PrevItem), - Handler: self.handlePreviousCommit, - }, - { - Key: opts.GetKey(opts.Config.Universal.NextItem), - Handler: self.handleNextCommit, - }, - { - Key: opts.GetKey(opts.Config.Universal.TogglePanel), - Handler: self.handleTogglePanel, - }, - { - Key: opts.GetKey(opts.Config.CommitMessage.CommitMenu), - Handler: self.openCommitMenu, - }, - } - - return bindings -} - -func (self *CommitMessageController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: self.Context().GetViewName(), - FocusedView: self.c.Contexts().CommitDescription.GetViewName(), - Key: gocui.MouseLeft, - Handler: self.onClick, - }, - } -} - -func (self *CommitMessageController) GetOnFocus() func(types.OnFocusOpts) { - return func(types.OnFocusOpts) { - self.c.Views().CommitDescription.Footer = "" - } -} - -func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) { - return func(types.OnFocusLostOpts) { - self.context().RenderSubtitle() - } -} - -func (self *CommitMessageController) Context() types.Context { - return self.context() -} - -func (self *CommitMessageController) context() *context.CommitMessageContext { - return self.c.Contexts().CommitMessage -} - -func (self *CommitMessageController) handlePreviousCommit() error { - return self.handleCommitIndexChange(1) -} - -func (self *CommitMessageController) handleNextCommit() error { - if self.context().GetSelectedIndex() == context.NoCommitIndex { - return nil - } - return self.handleCommitIndexChange(-1) -} - -func (self *CommitMessageController) switchToCommitDescription() error { - self.c.Context().Replace(self.c.Contexts().CommitDescription) - return nil -} - -func (self *CommitMessageController) handleTogglePanel() error { - // The default keybinding for this action is "", which means that we - // also get here when pasting multi-line text that contains tabs. In that - // case we don't want to toggle the panel, but insert the tab as a character - // (somehow, see below). - // - // Only do this if the TogglePanel command is actually mapped to "" - // (the default). If it's not, we can only hope that it's mapped to some - // ctrl key or fn key, which is unlikely to occur in pasted text. And if - // they mapped some *other* command to "", then we're totally out of - // luck. - if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.TogglePanel == "" { - // It is unlikely that a pasted commit message contains a tab in the - // subject line, so it shouldn't matter too much how we handle it. - // Simply insert 4 spaces instead; all that matters is that we don't - // switch to the description panel. - view := self.context().GetView() - for range 4 { - view.Editor.Edit(view, gocui.KeySpace, ' ', 0) - } - return nil - } - - return self.switchToCommitDescription() -} - -func (self *CommitMessageController) handleCommitIndexChange(value int) error { - currentIndex := self.context().GetSelectedIndex() - newIndex := currentIndex + value - if newIndex == context.NoCommitIndex { - self.context().SetSelectedIndex(newIndex) - self.c.Helpers().Commits.SetMessageAndDescriptionInView(self.context().GetHistoryMessage()) - return nil - } else if currentIndex == context.NoCommitIndex { - self.context().SetHistoryMessage(self.c.Helpers().Commits.JoinCommitMessageAndUnwrappedDescription()) - } - - validCommit, err := self.setCommitMessageAtIndex(newIndex) - if validCommit { - self.context().SetSelectedIndex(newIndex) - } - return err -} - -// returns true if the given index is for a valid commit -func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, error) { - commitMessage, err := self.c.Git().Commit.GetCommitMessageFromHistory(index) - if err != nil { - if errors.Is(err, git_commands.ErrInvalidCommitIndex) { - return false, nil - } - return false, errors.New(self.c.Tr.CommitWithoutMessageErr) - } - if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage { - commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig().Git.Commit.AutoWrapWidth) - } - self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage) - return true, nil -} - -func (self *CommitMessageController) confirm() error { - // The default keybinding for this action is "", which means that we - // also get here when pasting multi-line text that contains newlines. In - // that case we don't want to confirm the commit, but switch to the - // description panel instead so that the rest of the pasted text goes there. - // - // Only do this if the SubmitEditorText command is actually mapped to - // "" (the default). If it's not, we can only hope that it's mapped - // to some ctrl key or fn key, which is unlikely to occur in pasted text. - // And if they mapped some *other* command to "", then we're totally - // out of luck. - if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.SubmitEditorText == "" { - return self.switchToCommitDescription() - } - - return self.c.Helpers().Commits.HandleCommitConfirm() -} - -func (self *CommitMessageController) close() error { - self.c.Helpers().Commits.CloseCommitMessagePanel() - return nil -} - -func (self *CommitMessageController) openCommitMenu() error { - authorSuggestion := self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc() - return self.c.Helpers().Commits.OpenCommitMenu(authorSuggestion) -} - -func (self *CommitMessageController) onClick(opts gocui.ViewMouseBindingOpts) error { - self.c.Context().Replace(self.c.Contexts().CommitMessage) - return nil -} diff --git a/pkg/gui/controllers/commits_files_controller.go b/pkg/gui/controllers/commits_files_controller.go deleted file mode 100644 index 45bfa5f0b9c..00000000000 --- a/pkg/gui/controllers/commits_files_controller.go +++ /dev/null @@ -1,572 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - "path/filepath" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/jesseduffield/lazygit/pkg/constants" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/filetree" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type CommitFilesController struct { - baseController - *ListControllerTrait[*filetree.CommitFileNode] - c *ControllerCommon -} - -var _ types.IController = &CommitFilesController{} - -func NewCommitFilesController( - c *ControllerCommon, -) *CommitFilesController { - return &CommitFilesController{ - baseController: baseController{}, - c: c, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().CommitFiles, - c.Contexts().CommitFiles.GetSelected, - c.Contexts().CommitFiles.GetSelectedItems, - ), - } -} - -func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Files.CopyFileInfoToClipboard), - Handler: self.openCopyMenu, - Description: self.c.Tr.CopyToClipboardMenu, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.CommitFiles.CheckoutCommitFile), - Handler: self.withItem(self.checkout), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Checkout, - Tooltip: self.c.Tr.CheckoutCommitFileTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: self.withItems(self.discard), - GetDisabledReason: self.require(self.itemsSelected()), - Description: self.c.Tr.Remove, - Tooltip: self.c.Tr.DiscardOldFileChangeTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.OpenFile), - Handler: self.withItem(self.open), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.OpenFile, - Tooltip: self.c.Tr.OpenFileTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Edit), - Handler: self.withItems(self.edit), - GetDisabledReason: self.require(self.itemsSelected(self.canEditFiles)), - Description: self.c.Tr.Edit, - Tooltip: self.c.Tr.EditFileTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.OpenDiffTool), - Handler: self.withItem(self.openDiffTool), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.OpenDiffTool, - }, - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.withItems(self.toggleForPatch), - GetDisabledReason: self.require(self.itemsSelected()), - Description: self.c.Tr.ToggleAddToPatch, - Tooltip: utils.ResolvePlaceholderString(self.c.Tr.ToggleAddToPatchTooltip, - map[string]string{"doc": constants.Links.Docs.CustomPatchDemo}, - ), - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Files.ToggleStagedAll), - Handler: self.withItem(self.toggleAllForPatch), - Description: self.c.Tr.ToggleAllInPatch, - Tooltip: utils.ResolvePlaceholderString(self.c.Tr.ToggleAllInPatchTooltip, - map[string]string{"doc": constants.Links.Docs.CustomPatchDemo}, - ), - }, - { - Key: opts.GetKey(opts.Config.Universal.GoInto), - Handler: self.withItem(self.enter), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.EnterCommitFile, - Tooltip: self.c.Tr.EnterCommitFileTooltip, - }, - { - Key: opts.GetKey(opts.Config.Files.ToggleTreeView), - Handler: self.toggleTreeView, - Description: self.c.Tr.ToggleTreeView, - Tooltip: self.c.Tr.ToggleTreeViewTooltip, - }, - { - Key: opts.GetKey(opts.Config.Files.CollapseAll), - Handler: self.collapseAll, - Description: self.c.Tr.CollapseAll, - Tooltip: self.c.Tr.CollapseAllTooltip, - GetDisabledReason: self.require(self.isInTreeMode), - }, - { - Key: opts.GetKey(opts.Config.Files.ExpandAll), - Handler: self.expandAll, - Description: self.c.Tr.ExpandAll, - Tooltip: self.c.Tr.ExpandAllTooltip, - GetDisabledReason: self.require(self.isInTreeMode), - }, - } - - return bindings -} - -func (self *CommitFilesController) context() *context.CommitFilesContext { - return self.c.Contexts().CommitFiles -} - -func (self *CommitFilesController) GetOnRenderToMain() func() { - return func() { - node := self.context().GetSelected() - if node == nil { - return - } - - from, to := self.context().GetFromAndToForDiff() - from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from) - - cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false) - task := types.NewRunPtyTask(cmdObj.GetCmd()) - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: self.c.Tr.Patch, - SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(), - Task: task, - }, - Secondary: secondaryPatchPanelUpdateOpts(self.c), - }) - } -} - -func (self *CommitFilesController) copyDiffToClipboard(path string, toastMessage string) error { - from, to := self.context().GetFromAndToForDiff() - from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from) - - cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, path, true) - diff, err := cmdObj.RunWithOutput() - if err != nil { - return err - } - if err := self.c.OS().CopyToClipboard(diff); err != nil { - return err - } - self.c.Toast(toastMessage) - return nil -} - -func (self *CommitFilesController) copyFileContentToClipboard(path string) error { - _, to := self.context().GetFromAndToForDiff() - cmdObj := self.c.Git().Commit.ShowFileContentCmdObj(to, path) - diff, err := cmdObj.RunWithOutput() - if err != nil { - return err - } - return self.c.OS().CopyToClipboard(diff) -} - -func (self *CommitFilesController) openCopyMenu() error { - node := self.context().GetSelected() - - copyNameItem := &types.MenuItem{ - Label: self.c.Tr.CopyFileName, - OnPress: func() error { - if err := self.c.OS().CopyToClipboard(node.Name()); err != nil { - return err - } - self.c.Toast(self.c.Tr.FileNameCopiedToast) - return nil - }, - DisabledReason: self.require(self.singleItemSelected())(), - Key: 'n', - } - copyRelativePathItem := &types.MenuItem{ - Label: self.c.Tr.CopyRelativeFilePath, - OnPress: func() error { - if err := self.c.OS().CopyToClipboard(node.GetPath()); err != nil { - return err - } - self.c.Toast(self.c.Tr.FilePathCopiedToast) - return nil - }, - DisabledReason: self.require(self.singleItemSelected())(), - Key: 'p', - } - copyAbsolutePathItem := &types.MenuItem{ - Label: self.c.Tr.CopyAbsoluteFilePath, - OnPress: func() error { - if err := self.c.OS().CopyToClipboard(filepath.Join(self.c.Git().RepoPaths.RepoPath(), node.GetPath())); err != nil { - return err - } - self.c.Toast(self.c.Tr.FilePathCopiedToast) - return nil - }, - DisabledReason: self.require(self.singleItemSelected())(), - Key: 'P', - } - copyFileDiffItem := &types.MenuItem{ - Label: self.c.Tr.CopySelectedDiff, - OnPress: func() error { - return self.copyDiffToClipboard(node.GetPath(), self.c.Tr.FileDiffCopiedToast) - }, - DisabledReason: self.require(self.singleItemSelected())(), - Key: 's', - } - copyAllDiff := &types.MenuItem{ - Label: self.c.Tr.CopyAllFilesDiff, - OnPress: func() error { - return self.copyDiffToClipboard(".", self.c.Tr.AllFilesDiffCopiedToast) - }, - DisabledReason: self.require(self.itemsSelected())(), - Key: 'a', - } - copyFileContentItem := &types.MenuItem{ - Label: self.c.Tr.CopyFileContent, - OnPress: func() error { - if err := self.copyFileContentToClipboard(node.GetPath()); err != nil { - return err - } - self.c.Toast(self.c.Tr.FileContentCopiedToast) - return nil - }, - DisabledReason: self.require(self.singleItemSelected( - func(node *filetree.CommitFileNode) *types.DisabledReason { - if !node.IsFile() { - return &types.DisabledReason{ - Text: self.c.Tr.ErrCannotCopyContentOfDirectory, - ShowErrorInPanel: true, - } - } - return nil - }))(), - Key: 'c', - } - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.CopyToClipboardMenu, - Items: []*types.MenuItem{ - copyNameItem, - copyRelativePathItem, - copyAbsolutePathItem, - copyFileDiffItem, - copyAllDiff, - copyFileContentItem, - }, - }) -} - -func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error { - self.c.LogAction(self.c.Tr.Actions.CheckoutFile) - _, to := self.context().GetFromAndToForDiff() - if err := self.c.Git().WorkingTree.CheckoutFile(to, node.GetPath()); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil -} - -func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileNode) error { - parentContext := self.c.Context().Current().GetParentContext() - if parentContext == nil || parentContext.GetKey() != context.LOCAL_COMMITS_CONTEXT_KEY { - return errors.New(self.c.Tr.CanOnlyDiscardFromLocalCommits) - } - - if ok, err := self.c.Helpers().PatchBuilding.ValidateNormalWorkingTreeState(); !ok { - return err - } - - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.DiscardFileChangesTitle, - Prompt: self.c.Tr.DiscardFileChangesPrompt, - HandleConfirm: func() error { - return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { - var filePaths []string - selectedNodes = normalisedSelectedCommitFileNodes(selectedNodes) - - // Reset the current patch if there is one. - if self.c.Git().Patch.PatchBuilder.Active() { - self.c.Git().Patch.PatchBuilder.Reset() - } - - for _, node := range selectedNodes { - _ = node.ForEachFile(func(file *models.CommitFile) error { - filePaths = append(filePaths, file.GetPath()) - return nil - }) - } - - err := self.c.Git().Rebase.DiscardOldFileChanges(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), filePaths) - if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil { - return err - } - - if self.context().RangeSelectEnabled() { - self.context().GetList().CancelRangeSelect() - } - - return nil - }) - }, - }) - - return nil -} - -func (self *CommitFilesController) open(node *filetree.CommitFileNode) error { - return self.c.Helpers().Files.OpenFile(node.GetPath()) -} - -func (self *CommitFilesController) edit(nodes []*filetree.CommitFileNode) error { - return self.c.Helpers().Files.EditFiles(lo.FilterMap(nodes, - func(node *filetree.CommitFileNode, _ int) (string, bool) { - return node.GetPath(), node.IsFile() - })) -} - -func (self *CommitFilesController) canEditFiles(nodes []*filetree.CommitFileNode) *types.DisabledReason { - if lo.NoneBy(nodes, func(node *filetree.CommitFileNode) bool { return node.IsFile() }) { - return &types.DisabledReason{ - Text: self.c.Tr.ErrCannotEditDirectory, - ShowErrorInPanel: true, - } - } - - return nil -} - -func (self *CommitFilesController) openDiffTool(node *filetree.CommitFileNode) error { - from, to := self.context().GetFromAndToForDiff() - from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from) - _, err := self.c.RunSubprocess(self.c.Git().Diff.OpenDiffToolCmdObj( - git_commands.DiffToolCmdOptions{ - Filepath: node.GetPath(), - FromCommit: from, - ToCommit: to, - Reverse: reverse, - IsDirectory: !node.IsFile(), - Staged: false, - })) - return err -} - -func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.CommitFileNode) error { - if self.c.UserConfig().Git.DiffContextSize == 0 { - return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextForCustomPatch, - keybindings.Label(self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)) - } - - toggle := func() error { - return self.c.WithWaitingStatus(self.c.Tr.UpdatingPatch, func(gocui.Task) error { - if !self.c.Git().Patch.PatchBuilder.Active() { - if err := self.startPatchBuilder(); err != nil { - return err - } - } - - selectedNodes = normalisedSelectedCommitFileNodes(selectedNodes) - - // Find if any file in the selection is unselected or partially added - adding := lo.SomeBy(selectedNodes, func(node *filetree.CommitFileNode) bool { - return node.SomeFile(func(file *models.CommitFile) bool { - fileStatus := self.c.Git().Patch.PatchBuilder.GetFileStatus(file.Path, self.context().GetRef().RefName()) - return fileStatus == patch.PART || fileStatus == patch.UNSELECTED - }) - }) - - patchOperationFunction := self.c.Git().Patch.PatchBuilder.RemoveFile - - if adding { - patchOperationFunction = self.c.Git().Patch.PatchBuilder.AddFileWhole - } - - for _, node := range selectedNodes { - err := node.ForEachFile(func(file *models.CommitFile) error { - return patchOperationFunction(file.Path) - }) - if err != nil { - return err - } - } - - if self.c.Git().Patch.PatchBuilder.IsEmpty() { - self.c.Git().Patch.PatchBuilder.Reset() - } - - self.c.PostRefreshUpdate(self.context()) - return nil - }) - } - - from, to, reverse := self.currentFromToReverseForPatchBuilding() - mustDiscardPatch := self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.NewPatchRequired(from, to, reverse) - return self.c.ConfirmIf(mustDiscardPatch, types.ConfirmOpts{ - Title: self.c.Tr.DiscardPatch, - Prompt: self.c.Tr.DiscardPatchConfirm, - HandleConfirm: func() error { - if mustDiscardPatch { - self.c.Git().Patch.PatchBuilder.Reset() - } - - return toggle() - }, - }) -} - -func (self *CommitFilesController) toggleAllForPatch(_ *filetree.CommitFileNode) error { - root := self.context().CommitFileTreeViewModel.GetRoot() - return self.toggleForPatch([]*filetree.CommitFileNode{root}) -} - -func (self *CommitFilesController) startPatchBuilder() error { - commitFilesContext := self.context() - - canRebase := commitFilesContext.GetCanRebase() - from, to, reverse := self.currentFromToReverseForPatchBuilding() - - self.c.Git().Patch.PatchBuilder.Start(from, to, reverse, canRebase) - return nil -} - -func (self *CommitFilesController) currentFromToReverseForPatchBuilding() (string, string, bool) { - commitFilesContext := self.context() - - from, to := commitFilesContext.GetFromAndToForDiff() - from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from) - return from, to, reverse -} - -func (self *CommitFilesController) enter(node *filetree.CommitFileNode) error { - return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1}) -} - -func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode, opts types.OnFocusOpts) error { - if node.File == nil { - return self.handleToggleCommitFileDirCollapsed(node) - } - - if self.c.UserConfig().Git.DiffContextSize == 0 { - return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextForCustomPatch, - keybindings.Label(self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)) - } - - from, to, reverse := self.currentFromToReverseForPatchBuilding() - mustDiscardPatch := self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.NewPatchRequired(from, to, reverse) - return self.c.ConfirmIf(mustDiscardPatch, types.ConfirmOpts{ - Title: self.c.Tr.DiscardPatch, - Prompt: self.c.Tr.DiscardPatchConfirm, - HandleConfirm: func() error { - if mustDiscardPatch { - self.c.Git().Patch.PatchBuilder.Reset() - } - - if !self.c.Git().Patch.PatchBuilder.Active() { - if err := self.startPatchBuilder(); err != nil { - return err - } - } - - self.c.Context().Push(self.c.Contexts().CustomPatchBuilder, opts) - self.c.Helpers().PatchBuilding.ShowHunkStagingHint() - - return nil - }, - }) -} - -func (self *CommitFilesController) handleToggleCommitFileDirCollapsed(node *filetree.CommitFileNode) error { - self.context().CommitFileTreeViewModel.ToggleCollapsed(node.GetInternalPath()) - - self.c.PostRefreshUpdate(self.context()) - - return nil -} - -// NOTE: this is very similar to handleToggleFileTreeView, could be DRY'd with generics -func (self *CommitFilesController) toggleTreeView() error { - self.context().CommitFileTreeViewModel.ToggleShowTree() - - self.c.PostRefreshUpdate(self.context()) - return nil -} - -func (self *CommitFilesController) collapseAll() error { - self.context().CommitFileTreeViewModel.CollapseAll() - - self.c.PostRefreshUpdate(self.context()) - - return nil -} - -func (self *CommitFilesController) expandAll() error { - self.context().CommitFileTreeViewModel.ExpandAll() - - self.c.PostRefreshUpdate(self.context()) - - return nil -} - -func (self *CommitFilesController) GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error { - return func(mainViewName string, clickedLineIdx int) error { - node := self.getSelectedItem() - if node != nil && node.File != nil { - return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: mainViewName, ClickedViewLineIdx: clickedLineIdx}) - } - return nil - } -} - -// NOTE: these functions are identical to those in files_controller.go (except for types) and -// could also be cleaned up with some generics -func normalisedSelectedCommitFileNodes(selectedNodes []*filetree.CommitFileNode) []*filetree.CommitFileNode { - return lo.Filter(selectedNodes, func(node *filetree.CommitFileNode, _ int) bool { - return !isDescendentOfSelectedCommitFileNodes(node, selectedNodes) - }) -} - -func isDescendentOfSelectedCommitFileNodes(node *filetree.CommitFileNode, selectedNodes []*filetree.CommitFileNode) bool { - for _, selectedNode := range selectedNodes { - selectedNodePath := selectedNode.GetPath() - nodePath := node.GetPath() - - if strings.HasPrefix(nodePath, selectedNodePath) && nodePath != selectedNodePath { - return true - } - } - return false -} - -func (self *CommitFilesController) isInTreeMode() *types.DisabledReason { - if !self.context().CommitFileTreeViewModel.InTreeMode() { - return &types.DisabledReason{Text: self.c.Tr.DisabledInFlatView} - } - - return nil -} diff --git a/pkg/gui/controllers/common.go b/pkg/gui/controllers/common.go deleted file mode 100644 index 3498ad59d62..00000000000 --- a/pkg/gui/controllers/common.go +++ /dev/null @@ -1,24 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" -) - -type ControllerCommon struct { - *helpers.HelperCommon - IGetHelpers -} - -type IGetHelpers interface { - Helpers() *helpers.Helpers -} - -func NewControllerCommon( - c *helpers.HelperCommon, - IGetHelpers IGetHelpers, -) *ControllerCommon { - return &ControllerCommon{ - HelperCommon: c, - IGetHelpers: IGetHelpers, - } -} diff --git a/pkg/gui/controllers/confirmation_controller.go b/pkg/gui/controllers/confirmation_controller.go deleted file mode 100644 index 206818f406f..00000000000 --- a/pkg/gui/controllers/confirmation_controller.go +++ /dev/null @@ -1,72 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type ConfirmationController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &ConfirmationController{} - -func NewConfirmationController( - c *ControllerCommon, -) *ConfirmationController { - return &ConfirmationController{ - baseController: baseController{}, - c: c, - } -} - -func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.Confirm), - Handler: func() error { return self.context().State.OnConfirm() }, - Description: self.c.Tr.Confirm, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Handler: func() error { return self.context().State.OnClose() }, - Description: self.c.Tr.CloseCancel, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: self.handleCopyToClipboard, - Description: self.c.Tr.CopyToClipboardMenu, - DisplayOnScreen: true, - }, - } - - return bindings -} - -func (self *ConfirmationController) GetOnFocusLost() func(types.OnFocusLostOpts) { - return func(types.OnFocusLostOpts) { - self.c.Helpers().Confirmation.DeactivateConfirmation() - } -} - -func (self *ConfirmationController) Context() types.Context { - return self.context() -} - -func (self *ConfirmationController) context() *context.ConfirmationContext { - return self.c.Contexts().Confirmation -} - -func (self *ConfirmationController) handleCopyToClipboard() error { - confirmationView := self.c.Views().Confirmation - text := confirmationView.Buffer() - if err := self.c.OS().CopyToClipboard(text); err != nil { - return err - } - - self.c.Toast(self.c.Tr.MessageCopiedToClipboard) - return nil -} diff --git a/pkg/gui/controllers/context_lines_controller.go b/pkg/gui/controllers/context_lines_controller.go deleted file mode 100644 index 5e0f9d60660..00000000000 --- a/pkg/gui/controllers/context_lines_controller.go +++ /dev/null @@ -1,139 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - "math" - - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -// This controller lets you change the context size for diffs. The 'context' in 'context size' refers to the conventional meaning of the word 'context' in a diff, as opposed to lazygit's own idea of a 'context'. - -var CONTEXT_KEYS_SHOWING_DIFFS = []types.ContextKey{ - context.FILES_CONTEXT_KEY, - context.COMMIT_FILES_CONTEXT_KEY, - context.STASH_CONTEXT_KEY, - context.LOCAL_COMMITS_CONTEXT_KEY, - context.SUB_COMMITS_CONTEXT_KEY, - context.STAGING_MAIN_CONTEXT_KEY, - context.STAGING_SECONDARY_CONTEXT_KEY, - context.PATCH_BUILDING_MAIN_CONTEXT_KEY, - context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY, - context.NORMAL_MAIN_CONTEXT_KEY, - context.NORMAL_SECONDARY_CONTEXT_KEY, -} - -type ContextLinesController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &ContextLinesController{} - -func NewContextLinesController( - c *ControllerCommon, -) *ContextLinesController { - return &ContextLinesController{ - baseController: baseController{}, - c: c, - } -} - -func (self *ContextLinesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.IncreaseContextInDiffView), - Handler: self.Increase, - Description: self.c.Tr.IncreaseContextInDiffView, - Tooltip: self.c.Tr.IncreaseContextInDiffViewTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.DecreaseContextInDiffView), - Handler: self.Decrease, - Description: self.c.Tr.DecreaseContextInDiffView, - Tooltip: self.c.Tr.DecreaseContextInDiffViewTooltip, - }, - } - - return bindings -} - -func (self *ContextLinesController) Context() types.Context { - return nil -} - -func (self *ContextLinesController) Increase() error { - if self.isShowingDiff() { - if err := self.checkCanChangeContext(); err != nil { - return err - } - - if self.c.UserConfig().Git.DiffContextSize < math.MaxUint64 { - self.c.UserConfig().Git.DiffContextSize++ - } - return self.applyChange() - } - - return nil -} - -func (self *ContextLinesController) Decrease() error { - if self.isShowingDiff() { - if err := self.checkCanChangeContext(); err != nil { - return err - } - - if self.c.UserConfig().Git.DiffContextSize > 0 { - self.c.UserConfig().Git.DiffContextSize-- - } - return self.applyChange() - } - - return nil -} - -func (self *ContextLinesController) applyChange() error { - self.c.Toast(fmt.Sprintf(self.c.Tr.DiffContextSizeChanged, self.c.UserConfig().Git.DiffContextSize)) - - currentContext := self.currentSidePanel() - switch currentContext.GetKey() { - // we make an exception for our staging and patch building contexts because they actually need to refresh their state afterwards. - case context.PATCH_BUILDING_MAIN_CONTEXT_KEY: - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.PATCH_BUILDING}}) - case context.STAGING_MAIN_CONTEXT_KEY, context.STAGING_SECONDARY_CONTEXT_KEY: - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STAGING}}) - default: - currentContext.HandleRenderToMain() - } - return nil -} - -func (self *ContextLinesController) checkCanChangeContext() error { - if self.c.Git().Patch.PatchBuilder.Active() { - return errors.New(self.c.Tr.CantChangeContextSizeError) - } - - return nil -} - -func (self *ContextLinesController) isShowingDiff() bool { - return lo.Contains( - CONTEXT_KEYS_SHOWING_DIFFS, - self.currentSidePanel().GetKey(), - ) -} - -func (self *ContextLinesController) currentSidePanel() types.Context { - currentContext := self.c.Context().CurrentStatic() - if currentContext.GetKey() == context.NORMAL_MAIN_CONTEXT_KEY || - currentContext.GetKey() == context.NORMAL_SECONDARY_CONTEXT_KEY { - if sidePanelContext := self.c.Context().NextInStack(currentContext); sidePanelContext != nil { - return sidePanelContext - } - } - - return currentContext -} diff --git a/pkg/gui/controllers/custom_patch_options_menu_action.go b/pkg/gui/controllers/custom_patch_options_menu_action.go deleted file mode 100644 index 7930484291e..00000000000 --- a/pkg/gui/controllers/custom_patch_options_menu_action.go +++ /dev/null @@ -1,318 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type CustomPatchOptionsMenuAction struct { - c *ControllerCommon -} - -func (self *CustomPatchOptionsMenuAction) Call() error { - if !self.c.Git().Patch.PatchBuilder.Active() { - return errors.New(self.c.Tr.NoPatchError) - } - - if self.c.Git().Patch.PatchBuilder.IsEmpty() { - return errors.New(self.c.Tr.EmptyPatchError) - } - - menuItems := []*types.MenuItem{ - { - Label: self.c.Tr.ResetPatch, - Tooltip: self.c.Tr.ResetPatchTooltip, - OnPress: self.c.Helpers().PatchBuilding.Reset, - Key: 'c', - }, - { - Label: self.c.Tr.ApplyPatch, - Tooltip: self.c.Tr.ApplyPatchTooltip, - OnPress: func() error { return self.handleApplyPatch(false) }, - Key: 'a', - }, - { - Label: self.c.Tr.ApplyPatchInReverse, - Tooltip: self.c.Tr.ApplyPatchInReverseTooltip, - OnPress: func() error { return self.handleApplyPatch(true) }, - Key: 'r', - }, - } - - if self.c.Git().Patch.PatchBuilder.CanRebase && self.c.Git().Status.WorkingTreeState().None() { - menuItems = append(menuItems, []*types.MenuItem{ - { - Label: fmt.Sprintf(self.c.Tr.RemovePatchFromOriginalCommit, utils.ShortHash(self.c.Git().Patch.PatchBuilder.To)), - Tooltip: self.c.Tr.RemovePatchFromOriginalCommitTooltip, - OnPress: self.handleDeletePatchFromCommit, - Key: 'd', - }, - { - Label: self.c.Tr.MovePatchOutIntoIndex, - Tooltip: self.c.Tr.MovePatchOutIntoIndexTooltip, - OnPress: self.handleMovePatchIntoWorkingTree, - Key: 'i', - }, - { - Label: self.c.Tr.MovePatchIntoNewCommit, - Tooltip: self.c.Tr.MovePatchIntoNewCommitTooltip, - OnPress: self.handlePullPatchIntoNewCommit, - Key: 'n', - }, - { - Label: self.c.Tr.MovePatchIntoNewCommitBefore, - Tooltip: self.c.Tr.MovePatchIntoNewCommitBeforeTooltip, - OnPress: self.handlePullPatchIntoNewCommitBefore, - Key: 'N', - }, - }...) - - if self.c.Context().Current().GetKey() == self.c.Contexts().LocalCommits.GetKey() { - selectedCommit := self.c.Contexts().LocalCommits.GetSelected() - if selectedCommit != nil && self.c.Git().Patch.PatchBuilder.To != selectedCommit.Hash() { - - var disabledReason *types.DisabledReason - if self.c.Contexts().LocalCommits.AreMultipleItemsSelected() { - disabledReason = &types.DisabledReason{Text: self.c.Tr.RangeSelectNotSupported} - } - - // adding this option to index 1 - menuItems = append( - menuItems[:1], - append( - []*types.MenuItem{ - { - Label: fmt.Sprintf(self.c.Tr.MovePatchToSelectedCommit, selectedCommit.Hash()), - Tooltip: self.c.Tr.MovePatchToSelectedCommitTooltip, - OnPress: self.handleMovePatchToSelectedCommit, - Key: 'm', - DisabledReason: disabledReason, - }, - }, menuItems[1:]..., - )..., - ) - } - } - } - - menuItems = append(menuItems, []*types.MenuItem{ - { - Label: self.c.Tr.CopyPatchToClipboard, - OnPress: func() error { return self.copyPatchToClipboard() }, - Key: 'y', - }, - }...) - - return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.PatchOptionsTitle, Items: menuItems}) -} - -func (self *CustomPatchOptionsMenuAction) getPatchCommitIndex() int { - for index, commit := range self.c.Model().Commits { - if commit.Hash() == self.c.Git().Patch.PatchBuilder.To { - return index - } - } - return -1 -} - -func (self *CustomPatchOptionsMenuAction) validateNormalWorkingTreeState() (bool, error) { - if self.c.Git().Status.WorkingTreeState().Any() { - return false, errors.New(self.c.Tr.CantPatchWhileRebasingError) - } - return true, nil -} - -func (self *CustomPatchOptionsMenuAction) returnFocusFromPatchExplorerIfNecessary() { - if self.c.Context().Current().GetKey() == self.c.Contexts().CustomPatchBuilder.GetKey() { - self.c.Helpers().PatchBuilding.Escape() - } -} - -func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error { - if ok, err := self.validateNormalWorkingTreeState(); !ok { - return err - } - - self.returnFocusFromPatchExplorerIfNecessary() - - return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { - commitIndex := self.getPatchCommitIndex() - self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit) - err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex) - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) - }) -} - -func (self *CustomPatchOptionsMenuAction) handleMovePatchToSelectedCommit() error { - if ok, err := self.validateNormalWorkingTreeState(); !ok { - return err - } - - self.returnFocusFromPatchExplorerIfNecessary() - - return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { - commitIndex := self.getPatchCommitIndex() - self.c.LogAction(self.c.Tr.Actions.MovePatchToSelectedCommit) - err := self.c.Git().Patch.MovePatchToSelectedCommit(self.c.Model().Commits, commitIndex, self.c.Contexts().LocalCommits.GetSelectedLineIdx()) - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) - }) -} - -func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error { - if ok, err := self.validateNormalWorkingTreeState(); !ok { - return err - } - - self.returnFocusFromPatchExplorerIfNecessary() - - mustStash := self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules() - return self.c.ConfirmIf(mustStash, types.ConfirmOpts{ - Title: self.c.Tr.MustStashTitle, - Prompt: self.c.Tr.MustStashWarning, - HandleConfirm: func() error { - return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { - commitIndex := self.getPatchCommitIndex() - self.c.LogAction(self.c.Tr.Actions.MovePatchIntoIndex) - err := self.c.Git().Patch.MovePatchIntoIndex(self.c.Model().Commits, commitIndex, mustStash) - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) - }) - }, - }) -} - -func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error { - if ok, err := self.validateNormalWorkingTreeState(); !ok { - return err - } - - self.returnFocusFromPatchExplorerIfNecessary() - - commitIndex := self.getPatchCommitIndex() - self.c.Helpers().Commits.OpenCommitMessagePanel( - &helpers.OpenCommitMessagePanelOpts{ - // Pass a commit index of one less than the moved-from commit, so that - // you can press up arrow once to recall the original commit message: - CommitIndex: commitIndex - 1, - InitialMessage: "", - SummaryTitle: self.c.Tr.CommitSummaryTitle, - DescriptionTitle: self.c.Tr.CommitDescriptionTitle, - PreserveMessage: false, - OnConfirm: func(summary string, description string) error { - return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { - self.c.Helpers().Commits.CloseCommitMessagePanel() - self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit) - err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex, summary, description) - if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil { - return err - } - self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{}) - return nil - }) - }, - }, - ) - - return nil -} - -func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommitBefore() error { - if ok, err := self.validateNormalWorkingTreeState(); !ok { - return err - } - - self.returnFocusFromPatchExplorerIfNecessary() - - commitIndex := self.getPatchCommitIndex() - self.c.Helpers().Commits.OpenCommitMessagePanel( - &helpers.OpenCommitMessagePanelOpts{ - // Pass a commit index of one less than the moved-from commit, so that - // you can press up arrow once to recall the original commit message: - CommitIndex: commitIndex - 1, - InitialMessage: "", - SummaryTitle: self.c.Tr.CommitSummaryTitle, - DescriptionTitle: self.c.Tr.CommitDescriptionTitle, - PreserveMessage: false, - OnConfirm: func(summary string, description string) error { - return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { - self.c.Helpers().Commits.CloseCommitMessagePanel() - self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit) - err := self.c.Git().Patch.PullPatchIntoNewCommitBefore(self.c.Model().Commits, commitIndex, summary, description) - if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil { - return err - } - self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{}) - return nil - }) - }, - }, - ) - - return nil -} - -func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error { - self.returnFocusFromPatchExplorerIfNecessary() - - affectedUnstagedFiles := self.getAffectedUnstagedFiles() - - mustStageFiles := len(affectedUnstagedFiles) > 0 - return self.c.ConfirmIf(mustStageFiles, types.ConfirmOpts{ - Title: self.c.Tr.MustStageFilesAffectedByPatchTitle, - Prompt: self.c.Tr.MustStageFilesAffectedByPatchWarning, - HandleConfirm: func() error { - action := self.c.Tr.Actions.ApplyPatch - if reverse { - action = "Apply patch in reverse" - } - self.c.LogAction(action) - - if mustStageFiles { - if err := self.c.Git().WorkingTree.StageFiles(affectedUnstagedFiles, nil); err != nil { - return err - } - } - - if err := self.c.Git().Patch.ApplyCustomPatch(reverse, true); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }, - }) -} - -func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error { - patch := self.c.Git().Patch.PatchBuilder.RenderAggregatedPatch(true) - - self.c.LogAction(self.c.Tr.Actions.CopyPatchToClipboard) - if err := self.c.OS().CopyToClipboard(patch); err != nil { - return err - } - - self.c.Toast(self.c.Tr.PatchCopiedToClipboard) - - return nil -} - -// Returns a list of files that have unstaged changes and are contained in the patch. -func (self *CustomPatchOptionsMenuAction) getAffectedUnstagedFiles() []string { - unstagedFiles := set.NewFromSlice(lo.FilterMap(self.c.Model().Files, func(f *models.File, _ int) (string, bool) { - if f.GetHasUnstagedChanges() { - return f.GetPath(), true - } - return "", false - })) - - return lo.Filter(self.c.Git().Patch.PatchBuilder.AllFilesInPatch(), func(patchFile string, _ int) bool { - return unstagedFiles.Includes(patchFile) - }) -} diff --git a/pkg/gui/controllers/diffing_menu_action.go b/pkg/gui/controllers/diffing_menu_action.go deleted file mode 100644 index a53ad65e5d1..00000000000 --- a/pkg/gui/controllers/diffing_menu_action.go +++ /dev/null @@ -1,74 +0,0 @@ -package controllers - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/gui/modes/diffing" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type DiffingMenuAction struct { - c *ControllerCommon -} - -func (self *DiffingMenuAction) Call() error { - names := self.c.Helpers().Diff.CurrentDiffTerminals() - - menuItems := []*types.MenuItem{} - for _, name := range names { - menuItems = append(menuItems, []*types.MenuItem{ - { - Label: fmt.Sprintf("%s %s", self.c.Tr.Diff, name), - OnPress: func() error { - self.c.Modes().Diffing.Ref = name - // can scope this down based on current view but too lazy right now - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }, - }, - }...) - } - - menuItems = append(menuItems, []*types.MenuItem{ - { - Label: self.c.Tr.EnterRefToDiff, - OnPress: func() error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.EnterRefName, - FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(), - HandleConfirm: func(response string) error { - self.c.Modes().Diffing.Ref = strings.TrimSpace(response) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }, - }) - - return nil - }, - }, - }...) - - if self.c.Modes().Diffing.Active() { - menuItems = append(menuItems, []*types.MenuItem{ - { - Label: self.c.Tr.SwapDiff, - OnPress: func() error { - self.c.Modes().Diffing.Reverse = !self.c.Modes().Diffing.Reverse - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }, - }, - { - Label: self.c.Tr.ExitDiffMode, - OnPress: func() error { - self.c.Modes().Diffing = diffing.New() - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }, - }, - }...) - } - - return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.DiffingMenuTitle, Items: menuItems}) -} diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go deleted file mode 100644 index 0289936de16..00000000000 --- a/pkg/gui/controllers/files_controller.go +++ /dev/null @@ -1,1476 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - "path/filepath" - "strings" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/filetree" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type FilesController struct { - baseController - *ListControllerTrait[*filetree.FileNode] - c *ControllerCommon -} - -var _ types.IController = &FilesController{} - -func NewFilesController( - c *ControllerCommon, -) *FilesController { - return &FilesController{ - c: c, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().Files, - c.Contexts().Files.GetSelected, - c.Contexts().Files.GetSelectedItems, - ), - } -} - -func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.withItems(self.press), - GetDisabledReason: self.require(self.itemsSelected()), - Description: self.c.Tr.Stage, - Tooltip: self.c.Tr.StageTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Files.OpenStatusFilter), - Handler: self.handleStatusFilterPressed, - Description: self.c.Tr.FileFilter, - }, - { - Key: opts.GetKey(opts.Config.Files.CopyFileInfoToClipboard), - Handler: self.openCopyMenu, - Description: self.c.Tr.CopyToClipboardMenu, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Files.CommitChanges), - Handler: self.c.Helpers().WorkingTree.HandleCommitPress, - Description: self.c.Tr.Commit, - Tooltip: self.c.Tr.CommitTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Files.CommitChangesWithoutHook), - Handler: self.c.Helpers().WorkingTree.HandleWIPCommitPress, - Description: self.c.Tr.CommitChangesWithoutHook, - }, - { - Key: opts.GetKey(opts.Config.Files.AmendLastCommit), - Handler: self.handleAmendCommitPress, - Description: self.c.Tr.AmendLastCommit, - }, - { - Key: opts.GetKey(opts.Config.Files.CommitChangesWithEditor), - Handler: self.c.Helpers().WorkingTree.HandleCommitEditorPress, - Description: self.c.Tr.CommitChangesWithEditor, - }, - { - Key: opts.GetKey(opts.Config.Files.FindBaseCommitForFixup), - Handler: self.c.Helpers().FixupHelper.HandleFindBaseCommitForFixupPress, - Description: self.c.Tr.FindBaseCommitForFixup, - Tooltip: self.c.Tr.FindBaseCommitForFixupTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Edit), - Handler: self.withItems(self.edit), - GetDisabledReason: self.require(self.itemsSelected(self.canEditFiles)), - Description: self.c.Tr.Edit, - Tooltip: self.c.Tr.EditFileTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.OpenFile), - Handler: self.Open, - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.OpenFile, - Tooltip: self.c.Tr.OpenFileTooltip, - }, - { - Key: opts.GetKey(opts.Config.Files.IgnoreFile), - Handler: self.withItem(self.ignoreOrExcludeMenu), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Actions.IgnoreExcludeFile, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Files.RefreshFiles), - Handler: self.refresh, - Description: self.c.Tr.RefreshFiles, - }, - { - Key: opts.GetKey(opts.Config.Files.StashAllChanges), - Handler: self.stash, - Description: self.c.Tr.Stash, - Tooltip: self.c.Tr.StashTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Files.ViewStashOptions), - Handler: self.createStashMenu, - Description: self.c.Tr.ViewStashOptions, - Tooltip: self.c.Tr.ViewStashOptionsTooltip, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Files.ToggleStagedAll), - Handler: self.toggleStagedAll, - Description: self.c.Tr.ToggleStagedAll, - Tooltip: self.c.Tr.ToggleStagedAllTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.GoInto), - Handler: self.enter, - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.FileEnter, - Tooltip: self.c.Tr.FileEnterTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: self.withItems(self.remove), - GetDisabledReason: self.require(self.itemsSelected(self.canRemove)), - Description: self.c.Tr.Discard, - Tooltip: self.c.Tr.DiscardFileChangesTooltip, - OpensMenu: true, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.ViewResetOptions), - Handler: self.createResetToUpstreamMenu, - Description: self.c.Tr.ViewResetToUpstreamOptions, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Files.ViewResetOptions), - Handler: self.createResetMenu, - Description: self.c.Tr.Reset, - Tooltip: self.c.Tr.FileResetOptionsTooltip, - OpensMenu: true, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Files.ToggleTreeView), - Handler: self.toggleTreeView, - Description: self.c.Tr.ToggleTreeView, - Tooltip: self.c.Tr.ToggleTreeViewTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.OpenDiffTool), - Handler: self.withItem(self.openDiffTool), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.OpenDiffTool, - }, - { - Key: opts.GetKey(opts.Config.Files.OpenMergeOptions), - Handler: self.withItems(self.openMergeConflictMenu), - Description: self.c.Tr.ViewMergeConflictOptions, - Tooltip: self.c.Tr.ViewMergeConflictOptionsTooltip, - GetDisabledReason: self.require(self.itemsSelected(self.canOpenMergeConflictMenu)), - OpensMenu: true, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Files.Fetch), - Handler: self.fetch, - Description: self.c.Tr.Fetch, - Tooltip: self.c.Tr.FetchTooltip, - }, - { - Key: opts.GetKey(opts.Config.Files.CollapseAll), - Handler: self.collapseAll, - Description: self.c.Tr.CollapseAll, - Tooltip: self.c.Tr.CollapseAllTooltip, - GetDisabledReason: self.require(self.isInTreeMode), - }, - { - Key: opts.GetKey(opts.Config.Files.ExpandAll), - Handler: self.expandAll, - Description: self.c.Tr.ExpandAll, - Tooltip: self.c.Tr.ExpandAllTooltip, - GetDisabledReason: self.require(self.isInTreeMode), - }, - } -} - -func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: "mergeConflicts", - Key: gocui.MouseLeft, - Handler: self.onClickMain, - FocusedView: self.context().GetViewName(), - }, - } -} - -func (self *FilesController) GetOnRenderToMain() func() { - return func() { - self.c.Helpers().Diff.WithDiffModeCheck(func() { - node := self.context().GetSelected() - - if node == nil { - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: self.c.Tr.DiffTitle, - SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(), - Task: types.NewRenderStringTask(self.c.Tr.NoChangedFiles), - }, - }) - return - } - - if node.File != nil && node.File.HasInlineMergeConflicts { - hasConflicts, err := self.c.Helpers().MergeConflicts.SetMergeState(node.GetPath()) - if err != nil { - return - } - - if hasConflicts { - self.c.Helpers().MergeConflicts.Render() - return - } - } else if node.File != nil && node.File.HasMergeConflicts { - opts := types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: self.c.Tr.DiffTitle, - SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(), - }, - } - message := node.File.GetMergeStateDescription(self.c.Tr) - message += "\n\n" + fmt.Sprintf(self.c.Tr.MergeConflictPressEnterToResolve, - self.c.UserConfig().Keybinding.Universal.GoInto) - if self.c.Views().Main.InnerWidth() > 70 { - // If the main view is very wide, wrap the message to increase readability - lines, _, _ := utils.WrapViewLinesToWidth(true, false, message, 70, 4) - message = strings.Join(lines, "\n") - } - if node.File.ShortStatus == "DU" || node.File.ShortStatus == "UD" { - cmdObj := self.c.Git().Diff.DiffCmdObj([]string{"--base", "--", node.GetPath()}) - prefix := message + "\n\n" - if node.File.ShortStatus == "DU" { - prefix += self.c.Tr.MergeConflictIncomingDiff - } else { - prefix += self.c.Tr.MergeConflictCurrentDiff - } - prefix += "\n\n" - opts.Main.Task = types.NewRunPtyTaskWithPrefix(cmdObj.GetCmd(), prefix) - } else { - opts.Main.Task = types.NewRenderStringTask(message) - } - self.c.RenderToMainViews(opts) - return - } - - self.c.Helpers().MergeConflicts.ResetMergeState() - - split := self.c.UserConfig().Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges()) - mainShowsStaged := !split && node.GetHasStagedChanges() - - cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged) - title := self.c.Tr.UnstagedChanges - if mainShowsStaged { - title = self.c.Tr.StagedChanges - } - refreshOpts := types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Task: types.NewRunPtyTask(cmdObj.GetCmd()), - SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(), - Title: title, - }, - } - - if split { - cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true) - - title := self.c.Tr.StagedChanges - if mainShowsStaged { - title = self.c.Tr.UnstagedChanges - } - - refreshOpts.Secondary = &types.ViewUpdateOpts{ - Title: title, - SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(), - Task: types.NewRunPtyTask(cmdObj.GetCmd()), - } - } - - self.c.RenderToMainViews(refreshOpts) - }) - } -} - -func (self *FilesController) GetOnClick() func() error { - return self.withItemGraceful(func(node *filetree.FileNode) error { - return self.press([]*filetree.FileNode{node}) - }) -} - -func (self *FilesController) GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error { - return func(mainViewName string, clickedLineIdx int) error { - node := self.getSelectedItem() - if node != nil && node.File != nil { - return self.EnterFile(types.OnFocusOpts{ClickedWindowName: mainViewName, ClickedViewLineIdx: clickedLineIdx}) - } - return nil - } -} - -// if we are dealing with a status for which there is no key in this map, -// then we won't optimistically render: we'll just let `git status` tell -// us what the new status is. -// There are no doubt more entries that could be added to these two maps. -var stageStatusMap = map[string]string{ - "??": "A ", - " M": "M ", - "MM": "M ", - " D": "D ", - " A": "A ", - "AM": "A ", - "MD": "D ", -} - -var unstageStatusMap = map[string]string{ - "A ": "??", - "M ": " M", - "D ": " D", -} - -func (self *FilesController) optimisticStage(file *models.File) bool { - newShortStatus, ok := stageStatusMap[file.ShortStatus] - if !ok { - return false - } - - models.SetStatusFields(file, newShortStatus) - return true -} - -func (self *FilesController) optimisticUnstage(file *models.File) bool { - newShortStatus, ok := unstageStatusMap[file.ShortStatus] - if !ok { - return false - } - - models.SetStatusFields(file, newShortStatus) - return true -} - -// Running a git add command followed by a git status command can take some time (e.g. 200ms). -// Given how often users stage/unstage files in Lazygit, we're adding some -// optimistic rendering to make things feel faster. When we go to stage -// a file, we'll first update that file's status in-memory, then re-render -// the files panel. Then we'll immediately do a proper git status call -// so that if the optimistic rendering got something wrong, it's quickly -// corrected. -func (self *FilesController) optimisticChange(nodes []*filetree.FileNode, optimisticChangeFn func(*models.File) bool) error { - rerender := false - - for _, node := range nodes { - err := node.ForEachFile(func(f *models.File) error { - // can't act on the file itself: we need to update the original model file - for _, modelFile := range self.c.Model().Files { - if modelFile.Path == f.Path { - if optimisticChangeFn(modelFile) { - rerender = true - } - break - } - } - - return nil - }) - if err != nil { - return err - } - } - - if rerender { - self.c.PostRefreshUpdate(self.c.Contexts().Files) - } - - return nil -} - -func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) error { - // Obtaining this lock because optimistic rendering requires us to mutate - // the files in our model. - self.c.Mutexes().RefreshingFilesMutex.Lock() - defer self.c.Mutexes().RefreshingFilesMutex.Unlock() - - for _, node := range selectedNodes { - // if any files within have inline merge conflicts we can't stage or unstage, - // or it'll end up with those >>>>>> lines actually staged - if node.GetHasInlineMergeConflicts() { - return errors.New(self.c.Tr.ErrStageDirWithInlineMergeConflicts) - } - } - - toPaths := func(nodes []*filetree.FileNode) []string { - return lo.Map(nodes, func(node *filetree.FileNode, _ int) string { - return node.GetPath() - }) - } - - selectedNodes = normalisedSelectedNodes(selectedNodes) - - // If any node has unstaged changes, we'll stage all the selected unstaged nodes (staging already staged deleted files/folders would fail). - // Otherwise, we unstage all the selected nodes. - unstagedSelectedNodes := filterNodesHaveUnstagedChanges(selectedNodes) - - if len(unstagedSelectedNodes) > 0 { - var extraArgs []string - - if self.context().GetFilter() == filetree.DisplayTracked { - extraArgs = []string{"-u"} - } - - self.c.LogAction(self.c.Tr.Actions.StageFile) - - if err := self.optimisticChange(unstagedSelectedNodes, self.optimisticStage); err != nil { - return err - } - - if err := self.c.Git().WorkingTree.StageFiles(toPaths(unstagedSelectedNodes), extraArgs); err != nil { - return err - } - } else { - self.c.LogAction(self.c.Tr.Actions.UnstageFile) - - if err := self.optimisticChange(selectedNodes, self.optimisticUnstage); err != nil { - return err - } - - // need to partition the paths into tracked and untracked (where we assume directories are tracked). Then we'll run the commands separately. - trackedNodes, untrackedNodes := utils.Partition(selectedNodes, func(node *filetree.FileNode) bool { - // We treat all directories as tracked. I'm not actually sure why we do this but - // it's been the existing behaviour for a while and nobody has complained - return !node.IsFile() || node.GetIsTracked() - }) - - if len(untrackedNodes) > 0 { - if err := self.c.Git().WorkingTree.UnstageUntrackedFiles(toPaths(untrackedNodes)); err != nil { - return err - } - } - - if len(trackedNodes) > 0 { - if err := self.c.Git().WorkingTree.UnstageTrackedFiles(toPaths(trackedNodes)); err != nil { - return err - } - } - } - - return nil -} - -func (self *FilesController) press(nodes []*filetree.FileNode) error { - if err := self.pressWithLock(nodes); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC}) - - self.context().HandleFocus(types.OnFocusOpts{}) - return nil -} - -func (self *FilesController) Context() types.Context { - return self.context() -} - -func (self *FilesController) context() *context.WorkingTreeContext { - return self.c.Contexts().Files -} - -func (self *FilesController) getSelectedFile() *models.File { - node := self.context().GetSelected() - if node == nil { - return nil - } - return node.File -} - -func (self *FilesController) enter() error { - return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1}) -} - -func (self *FilesController) collapseAll() error { - self.context().FileTreeViewModel.CollapseAll() - - self.c.PostRefreshUpdate(self.context()) - - return nil -} - -func (self *FilesController) expandAll() error { - self.context().FileTreeViewModel.ExpandAll() - - self.c.PostRefreshUpdate(self.context()) - - return nil -} - -func (self *FilesController) EnterFile(opts types.OnFocusOpts) error { - node := self.context().GetSelected() - if node == nil { - return nil - } - - if node.File == nil { - return self.handleToggleDirCollapsed() - } - - file := node.File - - submoduleConfigs := self.c.Model().Submodules - if file.IsSubmodule(submoduleConfigs) { - submoduleConfig := file.SubmoduleConfig(submoduleConfigs) - return self.c.Helpers().Repos.EnterSubmodule(submoduleConfig) - } - - if file.HasInlineMergeConflicts { - return self.switchToMerge() - } - if file.HasMergeConflicts { - return self.handleNonInlineConflict(file) - } - - context := lo.Ternary(opts.ClickedWindowName == "secondary", self.c.Contexts().StagingSecondary, self.c.Contexts().Staging) - self.c.Context().Push(context, opts) - self.c.Helpers().PatchBuilding.ShowHunkStagingHint() - - return nil -} - -func (self *FilesController) handleNonInlineConflict(file *models.File) error { - handle := func(command func(command string) error, logText string) error { - self.c.LogAction(logText) - if err := command(file.GetPath()); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}) - return nil - } - keepItem := &types.MenuItem{ - Label: self.c.Tr.MergeConflictKeepFile, - OnPress: func() error { - return handle(self.c.Git().WorkingTree.StageFile, self.c.Tr.Actions.ResolveConflictByKeepingFile) - }, - Key: 'p', - } - deleteItem := &types.MenuItem{ - Label: self.c.Tr.MergeConflictDeleteFile, - OnPress: func() error { - return handle(self.c.Git().WorkingTree.RemoveConflictedFile, self.c.Tr.Actions.ResolveConflictByDeletingFile) - }, - Key: 'd', - } - items := []*types.MenuItem{} - switch file.ShortStatus { - case "DD": - // For "both deleted" conflicts, deleting the file is the only reasonable thing you can do. - // Restoring to the state before deletion is not the responsibility of a conflict resolution tool. - items = append(items, deleteItem) - case "DU", "UD": - // For these, we put the delete option first because it's the most common one, - // even if it's more destructive. - items = append(items, deleteItem, keepItem) - case "AU", "UA": - // For these, we put the keep option first because it's less destructive, - // and the chances between keep and delete are 50/50. - items = append(items, keepItem, deleteItem) - default: - panic("should only be called if there's a merge conflict") - } - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.MergeConflictsTitle, - Prompt: file.GetMergeStateDescription(self.c.Tr), - Items: items, - }) -} - -func (self *FilesController) toggleStagedAll() error { - if err := self.toggleStagedAllWithLock(); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC}) - - self.context().HandleFocus(types.OnFocusOpts{}) - return nil -} - -func (self *FilesController) toggleStagedAllWithLock() error { - self.c.Mutexes().RefreshingFilesMutex.Lock() - defer self.c.Mutexes().RefreshingFilesMutex.Unlock() - - root := self.context().FileTreeViewModel.GetRoot() - - // if any files within have inline merge conflicts we can't stage or unstage, - // or it'll end up with those >>>>>> lines actually staged - if root.GetHasInlineMergeConflicts() { - return errors.New(self.c.Tr.ErrStageDirWithInlineMergeConflicts) - } - - if root.GetHasUnstagedChanges() { - self.c.LogAction(self.c.Tr.Actions.StageAllFiles) - - if err := self.optimisticChange([]*filetree.FileNode{root}, self.optimisticStage); err != nil { - return err - } - - onlyTrackedFiles := self.context().GetFilter() == filetree.DisplayTracked - if err := self.c.Git().WorkingTree.StageAll(onlyTrackedFiles); err != nil { - return err - } - } else { - self.c.LogAction(self.c.Tr.Actions.UnstageAllFiles) - - if err := self.optimisticChange([]*filetree.FileNode{root}, self.optimisticUnstage); err != nil { - return err - } - - if err := self.c.Git().WorkingTree.UnstageAll(); err != nil { - return err - } - } - - return nil -} - -func (self *FilesController) unstageFiles(node *filetree.FileNode) error { - return node.ForEachFile(func(file *models.File) error { - if file.HasStagedChanges { - if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil { - return err - } - } - - return nil - }) -} - -func (self *FilesController) ignoreOrExcludeTracked(node *filetree.FileNode, trAction string, f func(string) error) error { - self.c.LogAction(trAction) - // not 100% sure if this is necessary but I'll assume it is - if err := self.unstageFiles(node); err != nil { - return err - } - - if err := self.c.Git().WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil { - return err - } - - if err := f(node.GetPath()); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}) - return nil -} - -func (self *FilesController) ignoreOrExcludeUntracked(node *filetree.FileNode, trAction string, f func(string) error) error { - self.c.LogAction(trAction) - - if err := f(node.GetPath()); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}) - return nil -} - -func (self *FilesController) ignoreOrExcludeFile(node *filetree.FileNode, trText string, trPrompt string, trAction string, f func(string) error) error { - if node.GetIsTracked() { - self.c.Confirm(types.ConfirmOpts{ - Title: trText, - Prompt: trPrompt, - HandleConfirm: func() error { - return self.ignoreOrExcludeTracked(node, trAction, f) - }, - }) - - return nil - } - return self.ignoreOrExcludeUntracked(node, trAction, f) -} - -func (self *FilesController) ignore(node *filetree.FileNode) error { - if node.GetPath() == ".gitignore" { - return errors.New(self.c.Tr.Actions.IgnoreFileErr) - } - return self.ignoreOrExcludeFile(node, self.c.Tr.IgnoreTracked, self.c.Tr.IgnoreTrackedPrompt, self.c.Tr.Actions.IgnoreExcludeFile, self.c.Git().WorkingTree.Ignore) -} - -func (self *FilesController) exclude(node *filetree.FileNode) error { - if node.GetPath() == ".gitignore" { - return errors.New(self.c.Tr.Actions.ExcludeGitIgnoreErr) - } - - return self.ignoreOrExcludeFile(node, self.c.Tr.ExcludeTracked, self.c.Tr.ExcludeTrackedPrompt, self.c.Tr.Actions.ExcludeFile, self.c.Git().WorkingTree.Exclude) -} - -func (self *FilesController) ignoreOrExcludeMenu(node *filetree.FileNode) error { - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.Actions.IgnoreExcludeFile, - Items: []*types.MenuItem{ - { - LabelColumns: []string{self.c.Tr.IgnoreFile}, - OnPress: func() error { - if err := self.ignore(node); err != nil { - return err - } - return nil - }, - Key: 'i', - }, - { - LabelColumns: []string{self.c.Tr.ExcludeFile}, - OnPress: func() error { - if err := self.exclude(node); err != nil { - return err - } - return nil - }, - Key: 'e', - }, - }, - }) -} - -func (self *FilesController) refresh() error { - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}) - return nil -} - -func (self *FilesController) handleAmendCommitPress() error { - doAmend := func() error { - return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error { - if len(self.c.Model().Commits) == 0 { - return errors.New(self.c.Tr.NoCommitToAmend) - } - - return self.c.Helpers().AmendHelper.AmendHead() - }) - } - - if self.isResolvingConflicts() { - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.AmendCommitTitle, - Prompt: self.c.Tr.AmendCommitWithConflictsMenuPrompt, - HideCancel: true, // We want the cancel item first, so we add one manually - Items: []*types.MenuItem{ - { - Label: self.c.Tr.Cancel, - OnPress: func() error { - return nil - }, - }, - { - Label: self.c.Tr.AmendCommitWithConflictsContinue, - OnPress: func() error { - return self.c.Helpers().MergeAndRebase.ContinueRebase() - }, - }, - { - Label: self.c.Tr.AmendCommitWithConflictsAmend, - OnPress: func() error { - return doAmend() - }, - }, - }, - }) - } - - return self.c.ConfirmIf(!self.c.UserConfig().Gui.SkipAmendWarning, - types.ConfirmOpts{ - Title: self.c.Tr.AmendLastCommitTitle, - Prompt: self.c.Tr.SureToAmend, - HandleConfirm: func() error { - return doAmend() - }, - }, - ) -} - -func (self *FilesController) isResolvingConflicts() bool { - commits := self.c.Model().Commits - for _, c := range commits { - if c.Status == models.StatusConflicted { - return true - } - if !c.IsTODO() { - break - } - } - return false -} - -func (self *FilesController) handleStatusFilterPressed() error { - currentFilter := self.context().GetFilter() - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.FilteringMenuTitle, - Items: []*types.MenuItem{ - { - Label: self.c.Tr.FilterStagedFiles, - OnPress: func() error { - return self.setStatusFiltering(filetree.DisplayStaged) - }, - Key: 's', - Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayStaged), - }, - { - Label: self.c.Tr.FilterUnstagedFiles, - OnPress: func() error { - return self.setStatusFiltering(filetree.DisplayUnstaged) - }, - Key: 'u', - Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayUnstaged), - }, - { - Label: self.c.Tr.FilterTrackedFiles, - OnPress: func() error { - return self.setStatusFiltering(filetree.DisplayTracked) - }, - Key: 't', - Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayTracked), - }, - { - Label: self.c.Tr.FilterUntrackedFiles, - OnPress: func() error { - return self.setStatusFiltering(filetree.DisplayUntracked) - }, - Key: 'T', - Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayUntracked), - }, - { - Label: self.c.Tr.NoFilter, - OnPress: func() error { - return self.setStatusFiltering(filetree.DisplayAll) - }, - Key: 'r', - Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayAll), - }, - }, - }) -} - -func (self *FilesController) filteringLabel(filter filetree.FileTreeDisplayFilter) string { - switch filter { - case filetree.DisplayAll: - return "" - case filetree.DisplayStaged: - return self.c.Tr.FilterLabelStagedFiles - case filetree.DisplayUnstaged: - return self.c.Tr.FilterLabelUnstagedFiles - case filetree.DisplayTracked: - return self.c.Tr.FilterLabelTrackedFiles - case filetree.DisplayUntracked: - return self.c.Tr.FilterLabelUntrackedFiles - case filetree.DisplayConflicted: - return self.c.Tr.FilterLabelConflictingFiles - } - - panic(fmt.Sprintf("Unexpected files display filter: %d", filter)) -} - -func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error { - previousFilter := self.context().GetFilter() - - self.context().FileTreeViewModel.SetStatusFilter(filter) - self.c.Contexts().Files.GetView().Subtitle = self.filteringLabel(filter) - - // Whenever we switch between untracked and other filters, we need to refresh the files view - // because the untracked files filter applies when running `git status`. - if previousFilter != filter && (previousFilter == filetree.DisplayUntracked || filter == filetree.DisplayUntracked) { - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC}) - } else { - self.c.PostRefreshUpdate(self.context()) - } - return nil -} - -func (self *FilesController) edit(nodes []*filetree.FileNode) error { - return self.c.Helpers().Files.EditFiles(lo.FilterMap(nodes, - func(node *filetree.FileNode, _ int) (string, bool) { - return node.GetPath(), node.IsFile() - })) -} - -func (self *FilesController) canEditFiles(nodes []*filetree.FileNode) *types.DisabledReason { - if lo.NoneBy(nodes, func(node *filetree.FileNode) bool { return node.IsFile() }) { - return &types.DisabledReason{ - Text: self.c.Tr.ErrCannotEditDirectory, - ShowErrorInPanel: true, - } - } - - return nil -} - -func (self *FilesController) Open() error { - node := self.context().GetSelected() - if node == nil { - return nil - } - - return self.c.Helpers().Files.OpenFile(node.GetPath()) -} - -func (self *FilesController) openDiffTool(node *filetree.FileNode) error { - fromCommit := "" - reverse := false - if self.c.Modes().Diffing.Active() { - fromCommit = self.c.Modes().Diffing.Ref - reverse = self.c.Modes().Diffing.Reverse - } - return self.c.RunSubprocessAndRefresh( - self.c.Git().Diff.OpenDiffToolCmdObj( - git_commands.DiffToolCmdOptions{ - Filepath: node.GetPath(), - FromCommit: fromCommit, - ToCommit: "", - Reverse: reverse, - IsDirectory: !node.IsFile(), - Staged: !node.GetHasUnstagedChanges(), - }), - ) -} - -func (self *FilesController) switchToMerge() error { - file := self.getSelectedFile() - if file == nil { - return nil - } - - return self.c.Helpers().MergeConflicts.SwitchToMerge(file.Path) -} - -func (self *FilesController) createStashMenu() error { - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.StashOptions, - Items: []*types.MenuItem{ - { - Label: self.c.Tr.StashAllChanges, - OnPress: func() error { - if !self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules() { - return errors.New(self.c.Tr.NoFilesToStash) - } - return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashAllChanges) - }, - Key: 'a', - }, - { - Label: self.c.Tr.StashAllChangesKeepIndex, - OnPress: func() error { - if !self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules() { - return errors.New(self.c.Tr.NoFilesToStash) - } - // if there are no staged files it behaves the same as Stash.Save - return self.handleStashSave(self.c.Git().Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex) - }, - Key: 'i', - }, - { - Label: self.c.Tr.StashIncludeUntrackedChanges, - OnPress: func() error { - return self.handleStashSave(self.c.Git().Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges) - }, - Key: 'U', - }, - { - Label: self.c.Tr.StashStagedChanges, - OnPress: func() error { - // there must be something in staging otherwise the current implementation mucks the stash up - if !self.c.Helpers().WorkingTree.AnyStagedFilesExceptSubmodules() { - return errors.New(self.c.Tr.NoTrackedStagedFilesStash) - } - return self.handleStashSave(self.c.Git().Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges) - }, - Key: 's', - }, - { - Label: self.c.Tr.StashUnstagedChanges, - OnPress: func() error { - if !self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules() { - return errors.New(self.c.Tr.NoFilesToStash) - } - if self.c.Helpers().WorkingTree.AnyStagedFilesExceptSubmodules() { - return self.handleStashSave(self.c.Git().Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges) - } - // ordinary stash - return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashUnstagedChanges) - }, - Key: 'u', - }, - }, - }) -} - -func (self *FilesController) openMergeConflictMenu(nodes []*filetree.FileNode) error { - normalizedNodes := flattenSelectedNodesToFiles(nodes) - - fileNodesWithConflicts := lo.Filter(normalizedNodes, func(node *filetree.FileNode, _ int) bool { - return node.File != nil && node.File.HasInlineMergeConflicts - }) - - filepaths := lo.Map(fileNodesWithConflicts, func(node *filetree.FileNode, _ int) string { - return node.GetPath() - }) - - return self.c.Helpers().WorkingTree.CreateMergeConflictMenu(filepaths) -} - -func (self *FilesController) canOpenMergeConflictMenu(nodes []*filetree.FileNode) *types.DisabledReason { - normalizedNodes := flattenSelectedNodesToFiles(nodes) - - hasFileNodesWithConflicts := lo.SomeBy(normalizedNodes, func(node *filetree.FileNode) bool { - return node.File != nil && node.File.HasInlineMergeConflicts - }) - - if !hasFileNodesWithConflicts { - return &types.DisabledReason{Text: self.c.Tr.NoFilesWithMergeConflicts} - } - - return nil -} - -func (self *FilesController) openCopyMenu() error { - node := self.context().GetSelected() - - copyNameItem := &types.MenuItem{ - Label: self.c.Tr.CopyFileName, - OnPress: func() error { - if err := self.c.OS().CopyToClipboard(node.Name()); err != nil { - return err - } - self.c.Toast(self.c.Tr.FileNameCopiedToast) - return nil - }, - DisabledReason: self.require(self.singleItemSelected())(), - Key: 'n', - } - copyRelativePathItem := &types.MenuItem{ - Label: self.c.Tr.CopyRelativeFilePath, - OnPress: func() error { - if err := self.c.OS().CopyToClipboard(node.GetPath()); err != nil { - return err - } - self.c.Toast(self.c.Tr.FilePathCopiedToast) - return nil - }, - DisabledReason: self.require(self.singleItemSelected())(), - Key: 'p', - } - copyAbsolutePathItem := &types.MenuItem{ - Label: self.c.Tr.CopyAbsoluteFilePath, - OnPress: func() error { - if err := self.c.OS().CopyToClipboard(filepath.Join(self.c.Git().RepoPaths.RepoPath(), node.GetPath())); err != nil { - return err - } - self.c.Toast(self.c.Tr.FilePathCopiedToast) - return nil - }, - DisabledReason: self.require(self.singleItemSelected())(), - Key: 'P', - } - copyFileDiffItem := &types.MenuItem{ - Label: self.c.Tr.CopySelectedDiff, - Tooltip: self.c.Tr.CopyFileDiffTooltip, - OnPress: func() error { - path := self.context().GetSelectedPath() - hasStaged := self.hasPathStagedChanges(node) - diff, err := self.c.Git().Diff.GetDiff(hasStaged, "--", path) - if err != nil { - return err - } - if err := self.c.OS().CopyToClipboard(diff); err != nil { - return err - } - self.c.Toast(self.c.Tr.FileDiffCopiedToast) - return nil - }, - DisabledReason: self.require(self.singleItemSelected( - func(file *filetree.FileNode) *types.DisabledReason { - if !node.GetHasStagedOrTrackedChanges() { - return &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError} - } - return nil - }, - ))(), - Key: 's', - } - copyAllDiff := &types.MenuItem{ - Label: self.c.Tr.CopyAllFilesDiff, - Tooltip: self.c.Tr.CopyFileDiffTooltip, - OnPress: func() error { - hasStaged := self.c.Helpers().WorkingTree.AnyStagedFiles() - diff, err := self.c.Git().Diff.GetDiff(hasStaged, "--") - if err != nil { - return err - } - if err := self.c.OS().CopyToClipboard(diff); err != nil { - return err - } - self.c.Toast(self.c.Tr.AllFilesDiffCopiedToast) - return nil - }, - DisabledReason: self.require( - func() *types.DisabledReason { - if !self.anyStagedOrTrackedFile() { - return &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError} - } - return nil - }, - )(), - Key: 'a', - } - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.CopyToClipboardMenu, - Items: []*types.MenuItem{ - copyNameItem, - copyRelativePathItem, - copyAbsolutePathItem, - copyFileDiffItem, - copyAllDiff, - }, - }) -} - -func (self *FilesController) anyStagedOrTrackedFile() bool { - if !self.c.Helpers().WorkingTree.AnyStagedFiles() { - return self.c.Helpers().WorkingTree.AnyTrackedFiles() - } - return true -} - -func (self *FilesController) hasPathStagedChanges(node *filetree.FileNode) bool { - return node.SomeFile(func(t *models.File) bool { - return t.HasStagedChanges - }) -} - -func (self *FilesController) stash() error { - return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashAllChanges) -} - -func (self *FilesController) createResetToUpstreamMenu() error { - return self.c.Helpers().Refs.CreateGitResetMenu("@{upstream}", "@{upstream}") -} - -func (self *FilesController) handleToggleDirCollapsed() error { - node := self.context().GetSelected() - if node == nil { - return nil - } - - self.context().FileTreeViewModel.ToggleCollapsed(node.GetInternalPath()) - - self.c.PostRefreshUpdate(self.c.Contexts().Files) - - return nil -} - -func (self *FilesController) toggleTreeView() error { - self.context().FileTreeViewModel.ToggleShowTree() - - self.c.PostRefreshUpdate(self.context()) - return nil -} - -func (self *FilesController) handleStashSave(stashFunc func(message string) error, action string) error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.StashChanges, - HandleConfirm: func(stashComment string) error { - self.c.LogAction(action) - - if err := stashFunc(stashComment); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}}) - return nil - }, - }) - - return nil -} - -func (self *FilesController) onClickMain(opts gocui.ViewMouseBindingOpts) error { - return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: opts.Y}) -} - -func (self *FilesController) fetch() error { - return self.c.WithWaitingStatus(self.c.Tr.FetchingStatus, func(task gocui.Task) error { - self.c.LogAction("Fetch") - err := self.c.Git().Sync.Fetch(task) - - if err != nil && strings.Contains(err.Error(), "exit status 128") { - return errors.New(self.c.Tr.PassUnameWrong) - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC}) - - if err == nil { - err = self.c.Helpers().BranchesHelper.AutoForwardBranches() - } - - return err - }) -} - -// Couldn't think of a better term than 'normalised'. Alas. -// The idea is that when you select a range of nodes, you will often have both -// a node and its parent node selected. If we are trying to discard changes to the -// selected nodes, we'll get an error if we try to discard the child after the parent. -// So we just need to filter out any nodes from the selection that are descendants -// of other nodes -func normalisedSelectedNodes(selectedNodes []*filetree.FileNode) []*filetree.FileNode { - return lo.Filter(selectedNodes, func(node *filetree.FileNode, _ int) bool { - return !isDescendentOfSelectedNodes(node, selectedNodes) - }) -} - -func isDescendentOfSelectedNodes(node *filetree.FileNode, selectedNodes []*filetree.FileNode) bool { - nodePath := node.GetInternalPath() - - for _, selectedNode := range selectedNodes { - if selectedNode.IsFile() { - continue - } - - selectedNodePath := selectedNode.GetInternalPath() - - if strings.HasPrefix(nodePath, selectedNodePath+"/") { - return true - } - } - return false -} - -// BFS algorithm for expanding directories into their children, -// and for collecting the unique file nodes -func flattenSelectedNodesToFiles(selectedNodes []*filetree.FileNode) []*filetree.FileNode { - queue := append(make([]*filetree.FileNode, 0, len(selectedNodes)), selectedNodes...) - visited := set.New[string]() - var files []*filetree.FileNode - - for len(queue) > 0 { - // pop node from queue - node := queue[0] - queue = queue[1:] - - nodeID := node.ID() - if visited.Includes(nodeID) { - continue - } - visited.Add(nodeID) - - if node.File != nil { - // unique file node -> collect it - files = append(files, node) - continue - } - - // directory node -> enqueue children - for _, ch := range node.Children { - queue = append(queue, &filetree.FileNode{Node: ch}) - } - } - return files -} - -func someNodesHaveUnstagedChanges(nodes []*filetree.FileNode) bool { - return lo.SomeBy(nodes, (*filetree.FileNode).GetHasUnstagedChanges) -} - -func someNodesHaveStagedChanges(nodes []*filetree.FileNode) bool { - return lo.SomeBy(nodes, (*filetree.FileNode).GetHasStagedChanges) -} - -func filterNodesHaveUnstagedChanges(nodes []*filetree.FileNode) []*filetree.FileNode { - return lo.Filter(nodes, func(node *filetree.FileNode, _ int) bool { - return node.GetHasUnstagedChanges() - }) -} - -func findSubmoduleNode(nodes []*filetree.FileNode, submodules []*models.SubmoduleConfig) *models.File { - for _, node := range nodes { - submoduleNode := node.FindFirstFileBy(func(f *models.File) bool { - return f.IsSubmodule(submodules) - }) - if submoduleNode != nil { - return submoduleNode - } - } - return nil -} - -func (self *FilesController) canRemove(selectedNodes []*filetree.FileNode) *types.DisabledReason { - // Return disabled if the selection contains multiple changed items and includes a submodule change. - submodules := self.c.Model().Submodules - hasFiles := false - uniqueSelectedSubmodules := set.New[*models.SubmoduleConfig]() - - for _, node := range selectedNodes { - _ = node.ForEachFile(func(f *models.File) error { - if submodule := f.SubmoduleConfig(submodules); submodule != nil { - uniqueSelectedSubmodules.Add(submodule) - } else { - hasFiles = true - } - return nil - }) - if uniqueSelectedSubmodules.Len() > 0 && (hasFiles || uniqueSelectedSubmodules.Len() > 1) { - return &types.DisabledReason{Text: self.c.Tr.MultiSelectNotSupportedForSubmodules} - } - } - - return nil -} - -func (self *FilesController) remove(selectedNodes []*filetree.FileNode) error { - submodules := self.c.Model().Submodules - - selectedNodes = normalisedSelectedNodes(selectedNodes) - - // If we have one submodule then we must only have one submodule or `canRemove` would have - // returned an error - submoduleNode := findSubmoduleNode(selectedNodes, submodules) - if submoduleNode != nil { - submodule := submoduleNode.SubmoduleConfig(submodules) - - menuItems := []*types.MenuItem{ - { - Label: self.c.Tr.SubmoduleStashAndReset, - OnPress: func() error { - return self.ResetSubmodule(submodule) - }, - }, - } - - return self.c.Menu(types.CreateMenuOptions{Title: submoduleNode.GetPath(), Items: menuItems}) - } - - discardAllChangesItem := types.MenuItem{ - Label: self.c.Tr.DiscardAllChanges, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.DiscardAllChangesInFile) - - if self.context().IsSelectingRange() { - defer self.context().CancelRangeSelect() - } - - for _, node := range selectedNodes { - if err := self.c.Git().WorkingTree.DiscardAllDirChanges(node); err != nil { - return err - } - } - - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}}) - return nil - }, - Key: self.c.KeybindingsOpts().GetKey(self.c.UserConfig().Keybinding.Files.ConfirmDiscard), - Tooltip: utils.ResolvePlaceholderString( - self.c.Tr.DiscardAllTooltip, - map[string]string{ - "path": self.formattedPaths(selectedNodes), - }, - ), - } - - discardUnstagedChangesItem := types.MenuItem{ - Label: self.c.Tr.DiscardUnstagedChanges, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.DiscardAllUnstagedChangesInFile) - - if self.context().IsSelectingRange() { - defer self.context().CancelRangeSelect() - } - - for _, node := range selectedNodes { - if err := self.c.Git().WorkingTree.DiscardUnstagedDirChanges(node); err != nil { - return err - } - } - - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}}) - return nil - }, - Key: 'u', - Tooltip: utils.ResolvePlaceholderString( - self.c.Tr.DiscardUnstagedTooltip, - map[string]string{ - "path": self.formattedPaths(selectedNodes), - }, - ), - } - - if !someNodesHaveStagedChanges(selectedNodes) || !someNodesHaveUnstagedChanges(selectedNodes) { - discardUnstagedChangesItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.DiscardUnstagedDisabled} - } - - menuItems := []*types.MenuItem{ - &discardAllChangesItem, - &discardUnstagedChangesItem, - } - - return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.DiscardChangesTitle, Items: menuItems}) -} - -func (self *FilesController) ResetSubmodule(submodule *models.SubmoduleConfig) error { - return self.c.WithWaitingStatus(self.c.Tr.ResettingSubmoduleStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.ResetSubmodule) - - file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule) - if file != nil { - if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil { - return err - } - } - - if err := self.c.Git().Submodule.Stash(submodule); err != nil { - return err - } - if err := self.c.Git().Submodule.Reset(submodule); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}}) - return nil - }) -} - -func (self *FilesController) formattedPaths(nodes []*filetree.FileNode) string { - return utils.FormatPaths(lo.Map(nodes, func(node *filetree.FileNode, _ int) string { - return node.GetPath() - })) -} - -func (self *FilesController) isInTreeMode() *types.DisabledReason { - if !self.context().FileTreeViewModel.InTreeMode() { - return &types.DisabledReason{Text: self.c.Tr.DisabledInFlatView} - } - - return nil -} diff --git a/pkg/gui/controllers/filter_controller.go b/pkg/gui/controllers/filter_controller.go deleted file mode 100644 index 8b049b26cea..00000000000 --- a/pkg/gui/controllers/filter_controller.go +++ /dev/null @@ -1,48 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type FilterControllerFactory struct { - c *ControllerCommon -} - -func NewFilterControllerFactory(c *ControllerCommon) *FilterControllerFactory { - return &FilterControllerFactory{ - c: c, - } -} - -func (self *FilterControllerFactory) Create(context types.IFilterableContext) *FilterController { - return &FilterController{ - baseController: baseController{}, - c: self.c, - context: context, - } -} - -type FilterController struct { - baseController - c *ControllerCommon - - context types.IFilterableContext -} - -func (self *FilterController) Context() types.Context { - return self.context -} - -func (self *FilterController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.StartSearch), - Handler: self.OpenFilterPrompt, - Description: self.c.Tr.StartFilter, - }, - } -} - -func (self *FilterController) OpenFilterPrompt() error { - return self.c.Helpers().Search.OpenFilterPrompt(self.context) -} diff --git a/pkg/gui/controllers/filtering_menu_action.go b/pkg/gui/controllers/filtering_menu_action.go deleted file mode 100644 index 2ed072676f5..00000000000 --- a/pkg/gui/controllers/filtering_menu_action.go +++ /dev/null @@ -1,132 +0,0 @@ -package controllers - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type FilteringMenuAction struct { - c *ControllerCommon -} - -func (self *FilteringMenuAction) Call() error { - fileName := "" - author := "" - switch self.c.Context().CurrentSide() { - case self.c.Contexts().Files: - node := self.c.Contexts().Files.GetSelected() - if node != nil { - fileName = node.GetPath() - } - case self.c.Contexts().CommitFiles: - node := self.c.Contexts().CommitFiles.GetSelected() - if node != nil { - fileName = node.GetPath() - } - case self.c.Contexts().LocalCommits: - commit := self.c.Contexts().LocalCommits.GetSelected() - if commit != nil { - author = fmt.Sprintf("%s <%s>", commit.AuthorName, commit.AuthorEmail) - } - } - - menuItems := []*types.MenuItem{} - tooltip := "" - if self.c.Modes().Filtering.Active() { - tooltip = self.c.Tr.WillCancelExistingFilterTooltip - } - - if fileName != "" { - menuItems = append(menuItems, &types.MenuItem{ - Label: fmt.Sprintf("%s '%s'", self.c.Tr.FilterBy, fileName), - OnPress: func() error { - return self.setFilteringPath(fileName) - }, - Tooltip: tooltip, - }) - } - - if author != "" { - menuItems = append(menuItems, &types.MenuItem{ - Label: fmt.Sprintf("%s '%s'", self.c.Tr.FilterBy, author), - OnPress: func() error { - return self.setFilteringAuthor(author) - }, - Tooltip: tooltip, - }) - } - - menuItems = append(menuItems, &types.MenuItem{ - Label: self.c.Tr.FilterPathOption, - OnPress: func() error { - self.c.Prompt(types.PromptOpts{ - FindSuggestionsFunc: self.c.Helpers().Suggestions.GetFilePathSuggestionsFunc(), - Title: self.c.Tr.EnterFileName, - HandleConfirm: func(response string) error { - return self.setFilteringPath(strings.TrimSpace(response)) - }, - }) - - return nil - }, - Tooltip: tooltip, - }) - - menuItems = append(menuItems, &types.MenuItem{ - Label: self.c.Tr.FilterAuthorOption, - OnPress: func() error { - self.c.Prompt(types.PromptOpts{ - FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(), - Title: self.c.Tr.EnterAuthor, - HandleConfirm: func(response string) error { - return self.setFilteringAuthor(strings.TrimSpace(response)) - }, - }) - - return nil - }, - Tooltip: tooltip, - }) - - if self.c.Modes().Filtering.Active() { - menuItems = append(menuItems, &types.MenuItem{ - Label: self.c.Tr.ExitFilterMode, - OnPress: self.c.Helpers().Mode.ClearFiltering, - }) - } - - return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.FilteringMenuTitle, Items: menuItems}) -} - -func (self *FilteringMenuAction) setFilteringPath(path string) error { - self.c.Modes().Filtering.Reset() - self.c.Modes().Filtering.SetPath(path) - return self.setFiltering() -} - -func (self *FilteringMenuAction) setFilteringAuthor(author string) error { - self.c.Modes().Filtering.Reset() - self.c.Modes().Filtering.SetAuthor(author) - return self.setFiltering() -} - -func (self *FilteringMenuAction) setFiltering() error { - self.c.Modes().Filtering.SetSelectedCommitHash(self.c.Contexts().LocalCommits.GetSelectedCommitHash()) - - repoState := self.c.State().GetRepoState() - if repoState.GetScreenMode() == types.SCREEN_NORMAL { - repoState.SetScreenMode(types.SCREEN_HALF) - } - - self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{}) - - self.c.Refresh(types.RefreshOptions{Scope: helpers.ScopesToRefreshWhenFilteringModeChanges(), Then: func() { - self.c.Contexts().LocalCommits.SetSelection(0) - self.c.Contexts().LocalCommits.HandleFocus(types.OnFocusOpts{}) - }}) - - return nil -} diff --git a/pkg/gui/controllers/git_flow_controller.go b/pkg/gui/controllers/git_flow_controller.go deleted file mode 100644 index cf996e5d9ed..00000000000 --- a/pkg/gui/controllers/git_flow_controller.go +++ /dev/null @@ -1,114 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type GitFlowController struct { - baseController - *ListControllerTrait[*models.Branch] - c *ControllerCommon -} - -var _ types.IController = &GitFlowController{} - -func NewGitFlowController( - c *ControllerCommon, -) *GitFlowController { - return &GitFlowController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().Branches, - c.Contexts().Branches.GetSelected, - c.Contexts().Branches.GetSelectedItems, - ), - c: c, - } -} - -func (self *GitFlowController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Branches.ViewGitFlowOptions), - Handler: self.withItem(self.handleCreateGitFlowMenu), - Description: self.c.Tr.GitFlowOptions, - OpensMenu: true, - }, - } - - return bindings -} - -func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) error { - if !self.c.Git().Flow.GitFlowEnabled() { - return errors.New("You need to install git-flow and enable it in this repo to use git-flow features") - } - - startHandler := func(branchType string) func() error { - return func() error { - title := utils.ResolvePlaceholderString(self.c.Tr.NewGitFlowBranchPrompt, map[string]string{"branchType": branchType}) - - self.c.Prompt(types.PromptOpts{ - Title: title, - HandleConfirm: func(name string) error { - self.c.LogAction(self.c.Tr.Actions.GitFlowStart) - return self.c.RunSubprocessAndRefresh( - self.c.Git().Flow.StartCmdObj(branchType, name), - ) - }, - }) - - return nil - } - } - - return self.c.Menu(types.CreateMenuOptions{ - Title: "git flow", - Items: []*types.MenuItem{ - { - // not localising here because it's one to one with the actual git flow commands - Label: fmt.Sprintf("finish branch '%s'", branch.Name), - OnPress: func() error { - return self.gitFlowFinishBranch(branch.Name) - }, - DisabledReason: self.require(self.singleItemSelected())(), - }, - { - Label: "start feature", - OnPress: startHandler("feature"), - Key: 'f', - }, - { - Label: "start hotfix", - OnPress: startHandler("hotfix"), - Key: 'h', - }, - { - Label: "start bugfix", - OnPress: startHandler("bugfix"), - Key: 'b', - }, - { - Label: "start release", - OnPress: startHandler("release"), - Key: 'r', - }, - }, - }) -} - -func (self *GitFlowController) gitFlowFinishBranch(branchName string) error { - cmdObj, err := self.c.Git().Flow.FinishCmdObj(branchName) - if err != nil { - return err - } - - self.c.LogAction(self.c.Tr.Actions.GitFlowFinish) - return self.c.RunSubprocessAndRefresh(cmdObj) -} diff --git a/pkg/gui/controllers/global_controller.go b/pkg/gui/controllers/global_controller.go deleted file mode 100644 index 1ae56068561..00000000000 --- a/pkg/gui/controllers/global_controller.go +++ /dev/null @@ -1,264 +0,0 @@ -package controllers - -import ( - "fmt" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type GlobalController struct { - baseController - c *ControllerCommon -} - -func NewGlobalController( - c *ControllerCommon, -) *GlobalController { - return &GlobalController{ - baseController: baseController{}, - c: c, - } -} - -func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.ExecuteShellCommand), - Handler: self.shellCommand, - Description: self.c.Tr.ExecuteShellCommand, - Tooltip: self.c.Tr.ExecuteShellCommandTooltip, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.CreatePatchOptionsMenu), - Handler: self.createCustomPatchOptionsMenu, - Description: self.c.Tr.ViewPatchOptions, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.CreateRebaseOptionsMenu), - Handler: opts.Guards.NoPopupPanel(self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu), - Description: self.c.Tr.ViewMergeRebaseOptions, - Tooltip: self.c.Tr.ViewMergeRebaseOptionsTooltip, - OpensMenu: true, - GetDisabledReason: self.canShowRebaseOptions, - }, - { - Key: opts.GetKey(opts.Config.Universal.Refresh), - Handler: opts.Guards.NoPopupPanel(self.refresh), - Description: self.c.Tr.Refresh, - Tooltip: self.c.Tr.RefreshTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.NextScreenMode), - Handler: opts.Guards.NoPopupPanel(self.nextScreenMode), - Description: self.c.Tr.NextScreenMode, - }, - { - Key: opts.GetKey(opts.Config.Universal.PrevScreenMode), - Handler: opts.Guards.NoPopupPanel(self.prevScreenMode), - Description: self.c.Tr.PrevScreenMode, - }, - { - Key: opts.GetKey(opts.Config.Universal.CyclePagers), - Handler: opts.Guards.NoPopupPanel(self.cyclePagers), - GetDisabledReason: self.canCyclePagers, - Description: self.c.Tr.CyclePagers, - Tooltip: self.c.Tr.CyclePagersTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Modifier: gocui.ModNone, - Handler: self.escape, - Description: self.c.Tr.Cancel, - DescriptionFunc: self.escapeDescription, - GetDisabledReason: self.escapeEnabled, - DisplayOnScreen: true, - }, - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.OptionMenu), - Handler: self.createOptionsMenu, - OpensMenu: true, - }, - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.OptionMenuAlt1), - Modifier: gocui.ModNone, - // we have the description on the alt key and not the main key for legacy reasons - // (the original main key was 'x' but we've reassigned that to other purposes) - Description: self.c.Tr.OpenKeybindingsMenu, - Handler: self.createOptionsMenu, - ShortDescription: self.c.Tr.Keybindings, - DisplayOnScreen: true, - GetDisabledReason: self.optionsMenuDisabledReason, - }, - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.FilteringMenu), - Handler: opts.Guards.NoPopupPanel(self.createFilteringMenu), - Description: self.c.Tr.OpenFilteringMenu, - Tooltip: self.c.Tr.OpenFilteringMenuTooltip, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.DiffingMenu), - Handler: opts.Guards.NoPopupPanel(self.createDiffingMenu), - Description: self.c.Tr.ViewDiffingOptions, - Tooltip: self.c.Tr.ViewDiffingOptionsTooltip, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.DiffingMenuAlt), - Handler: opts.Guards.NoPopupPanel(self.createDiffingMenu), - Description: self.c.Tr.ViewDiffingOptions, - Tooltip: self.c.Tr.ViewDiffingOptionsTooltip, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Quit), - Modifier: gocui.ModNone, - Description: self.c.Tr.Quit, - Handler: self.quit, - }, - { - Key: opts.GetKey(opts.Config.Universal.QuitAlt1), - Modifier: gocui.ModNone, - Handler: self.quit, - }, - { - Key: opts.GetKey(opts.Config.Universal.QuitWithoutChangingDirectory), - Modifier: gocui.ModNone, - Handler: self.quitWithoutChangingDirectory, - }, - { - Key: opts.GetKey(opts.Config.Universal.SuspendApp), - Modifier: gocui.ModNone, - Handler: self.c.Helpers().SuspendResume.SuspendApp, - Description: self.c.Tr.SuspendApp, - GetDisabledReason: func() *types.DisabledReason { - if !self.c.Helpers().SuspendResume.CanSuspendApp() { - return &types.DisabledReason{ - Text: self.c.Tr.CannotSuspendApp, - } - } - return nil - }, - }, - { - Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView), - Handler: self.toggleWhitespace, - Description: self.c.Tr.ToggleWhitespaceInDiffView, - Tooltip: self.c.Tr.ToggleWhitespaceInDiffViewTooltip, - }, - } -} - -func (self *GlobalController) Context() types.Context { - return nil -} - -func (self *GlobalController) shellCommand() error { - return (&ShellCommandAction{c: self.c}).Call() -} - -func (self *GlobalController) createCustomPatchOptionsMenu() error { - return (&CustomPatchOptionsMenuAction{c: self.c}).Call() -} - -func (self *GlobalController) refresh() error { - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil -} - -func (self *GlobalController) nextScreenMode() error { - return (&ScreenModeActions{c: self.c}).Next() -} - -func (self *GlobalController) prevScreenMode() error { - return (&ScreenModeActions{c: self.c}).Prev() -} - -func (self *GlobalController) cyclePagers() error { - self.c.State().GetPagerConfig().CyclePagers() - if self.c.Context().CurrentSide().GetKey() == self.c.Context().Current().GetKey() { - self.c.Context().CurrentSide().HandleFocus(types.OnFocusOpts{}) - } - - current, total := self.c.State().GetPagerConfig().CurrentPagerIndex() - self.c.Toast(fmt.Sprintf("Selected pager %d of %d", current+1, total)) - return nil -} - -func (self *GlobalController) canCyclePagers() *types.DisabledReason { - _, total := self.c.State().GetPagerConfig().CurrentPagerIndex() - if total <= 1 { - return &types.DisabledReason{ - Text: self.c.Tr.CyclePagersDisabledReason, - } - } - return nil -} - -func (self *GlobalController) createOptionsMenu() error { - return (&OptionsMenuAction{c: self.c}).Call() -} - -func (self *GlobalController) optionsMenuDisabledReason() *types.DisabledReason { - ctx := self.c.Context().Current() - // Don't show options menu while displaying popup. - if ctx.GetKind() == types.PERSISTENT_POPUP || ctx.GetKind() == types.TEMPORARY_POPUP { - // The empty error text is intentional. We don't want to show an error - // toast for this, but only hide it from the options map. - return &types.DisabledReason{Text: ""} - } - return nil -} - -func (self *GlobalController) createFilteringMenu() error { - return (&FilteringMenuAction{c: self.c}).Call() -} - -func (self *GlobalController) createDiffingMenu() error { - return (&DiffingMenuAction{c: self.c}).Call() -} - -func (self *GlobalController) quit() error { - return (&QuitActions{c: self.c}).Quit() -} - -func (self *GlobalController) quitWithoutChangingDirectory() error { - return (&QuitActions{c: self.c}).QuitWithoutChangingDirectory() -} - -func (self *GlobalController) escape() error { - return (&QuitActions{c: self.c}).Escape() -} - -func (self *GlobalController) escapeDescription() string { - return (&QuitActions{c: self.c}).EscapeDescription() -} - -func (self *GlobalController) escapeEnabled() *types.DisabledReason { - if (&QuitActions{c: self.c}).EscapeEnabled() { - return nil - } - - // The empty error text is intentional. We don't want to show an error - // toast for this, but only hide it from the options map. - return &types.DisabledReason{Text: ""} -} - -func (self *GlobalController) toggleWhitespace() error { - return (&ToggleWhitespaceAction{c: self.c}).Call() -} - -func (self *GlobalController) canShowRebaseOptions() *types.DisabledReason { - if self.c.Model().WorkingTreeStateAtLastCommitRefresh.None() { - return &types.DisabledReason{ - Text: self.c.Tr.NotMergingOrRebasing, - } - } - return nil -} diff --git a/pkg/gui/controllers/helpers/amend_helper.go b/pkg/gui/controllers/helpers/amend_helper.go deleted file mode 100644 index 03c6bb48564..00000000000 --- a/pkg/gui/controllers/helpers/amend_helper.go +++ /dev/null @@ -1,24 +0,0 @@ -package helpers - -import "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - -type AmendHelper struct { - c *HelperCommon - gpg *GpgHelper -} - -func NewAmendHelper( - c *HelperCommon, - gpg *GpgHelper, -) *AmendHelper { - return &AmendHelper{ - c: c, - gpg: gpg, - } -} - -func (self *AmendHelper) AmendHead() error { - cmdObj := self.c.Git().Commit.AmendHeadCmdObj() - self.c.LogAction(self.c.Tr.Actions.AmendCommit) - return self.gpg.WithGpgHandling(cmdObj, git_commands.CommitGpgSign, self.c.Tr.AmendingStatus, nil, nil) -} diff --git a/pkg/gui/controllers/helpers/app_status_helper.go b/pkg/gui/controllers/helpers/app_status_helper.go deleted file mode 100644 index 9375186326a..00000000000 --- a/pkg/gui/controllers/helpers/app_status_helper.go +++ /dev/null @@ -1,145 +0,0 @@ -package helpers - -import ( - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/status" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type AppStatusHelper struct { - c *HelperCommon - - statusMgr func() *status.StatusManager - modeHelper *ModeHelper -} - -func NewAppStatusHelper(c *HelperCommon, statusMgr func() *status.StatusManager, modeHelper *ModeHelper) *AppStatusHelper { - return &AppStatusHelper{ - c: c, - statusMgr: statusMgr, - modeHelper: modeHelper, - } -} - -func (self *AppStatusHelper) Toast(message string, kind types.ToastKind) { - if self.c.RunningIntegrationTest() { - // Don't bother showing toasts in integration tests. You can't check for - // them anyway, and they would only slow down the test unnecessarily by - // two seconds. - return - } - - self.statusMgr().AddToastStatus(message, kind) - - self.renderAppStatus() -} - -// A custom task for WithWaitingStatus calls; it wraps the original one and -// hides the status whenever the task is paused, and shows it again when -// continued. -type appStatusHelperTask struct { - gocui.Task - waitingStatusHandle *status.WaitingStatusHandle -} - -// poor man's version of explicitly saying that struct X implements interface Y -var _ gocui.Task = appStatusHelperTask{} - -func (self appStatusHelperTask) Pause() { - self.waitingStatusHandle.Hide() - self.Task.Pause() -} - -func (self appStatusHelperTask) Continue() { - self.Task.Continue() - self.waitingStatusHandle.Show() -} - -// withWaitingStatus wraps a function and shows a waiting status while the function is still executing -func (self *AppStatusHelper) WithWaitingStatus(message string, f func(gocui.Task) error) { - self.c.OnWorker(func(task gocui.Task) error { - return self.WithWaitingStatusImpl(message, f, task) - }) -} - -func (self *AppStatusHelper) WithWaitingStatusImpl(message string, f func(gocui.Task) error, task gocui.Task) error { - return self.statusMgr().WithWaitingStatus(message, self.renderAppStatus, func(waitingStatusHandle *status.WaitingStatusHandle) error { - return f(appStatusHelperTask{task, waitingStatusHandle}) - }) -} - -func (self *AppStatusHelper) WithWaitingStatusSync(message string, f func() error) error { - return self.statusMgr().WithWaitingStatus(message, func() {}, func(*status.WaitingStatusHandle) error { - stop := make(chan struct{}) - defer func() { close(stop) }() - self.renderAppStatusSync(stop) - - return f() - }) -} - -func (self *AppStatusHelper) HasStatus() bool { - return self.statusMgr().HasStatus() -} - -func (self *AppStatusHelper) GetStatusString() string { - appStatus, _ := self.statusMgr().GetStatusString(self.c.UserConfig()) - return appStatus -} - -func (self *AppStatusHelper) renderAppStatus() { - self.c.OnWorker(func(_ gocui.Task) error { - ticker := time.NewTicker(time.Millisecond * time.Duration(self.c.UserConfig().Gui.Spinner.Rate)) - defer ticker.Stop() - for range ticker.C { - appStatus, color := self.statusMgr().GetStatusString(self.c.UserConfig()) - self.c.Views().AppStatus.FgColor = color - self.c.OnUIThread(func() error { - self.c.SetViewContent(self.c.Views().AppStatus, appStatus) - return nil - }) - - if appStatus == "" { - break - } - } - return nil - }) -} - -func (self *AppStatusHelper) renderAppStatusSync(stop chan struct{}) { - go func() { - ticker := time.NewTicker(time.Millisecond * 50) - defer ticker.Stop() - - // Forcing a re-layout and redraw after we added the waiting status; - // this is needed in case the gui.showBottomLine config is set to false, - // to make sure the bottom line appears. It's also useful for redrawing - // once after each of several consecutive keypresses, e.g. pressing - // ctrl-j to move a commit down several steps. - _ = self.c.GocuiGui().ForceLayoutAndRedraw() - - self.modeHelper.SetSuppressRebasingMode(true) - defer func() { self.modeHelper.SetSuppressRebasingMode(false) }() - - outer: - for { - select { - case <-ticker.C: - appStatus, color := self.statusMgr().GetStatusString(self.c.UserConfig()) - self.c.Views().AppStatus.FgColor = color - self.c.SetViewContent(self.c.Views().AppStatus, appStatus) - // Redraw all views of the bottom line: - bottomLineViews := []*gocui.View{ - self.c.Views().AppStatus, self.c.Views().Options, self.c.Views().Information, - self.c.Views().StatusSpacer1, self.c.Views().StatusSpacer2, - } - _ = self.c.GocuiGui().ForceRedrawViews(bottomLineViews...) - case <-stop: - break outer - } - } - }() -} diff --git a/pkg/gui/controllers/helpers/bisect_helper.go b/pkg/gui/controllers/helpers/bisect_helper.go deleted file mode 100644 index 6ce517dac65..00000000000 --- a/pkg/gui/controllers/helpers/bisect_helper.go +++ /dev/null @@ -1,35 +0,0 @@ -package helpers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type BisectHelper struct { - c *HelperCommon -} - -func NewBisectHelper(c *HelperCommon) *BisectHelper { - return &BisectHelper{c: c} -} - -func (self *BisectHelper) Reset() error { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Bisect.ResetTitle, - Prompt: self.c.Tr.Bisect.ResetPrompt, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.ResetBisect) - if err := self.c.Git().Bisect.Reset(); err != nil { - return err - } - - self.PostBisectCommandRefresh() - return nil - }, - }) - - return nil -} - -func (self *BisectHelper) PostBisectCommandRefresh() { - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{}}) -} diff --git a/pkg/gui/controllers/helpers/branches_helper.go b/pkg/gui/controllers/helpers/branches_helper.go deleted file mode 100644 index 5566dc40c41..00000000000 --- a/pkg/gui/controllers/helpers/branches_helper.go +++ /dev/null @@ -1,307 +0,0 @@ -package helpers - -import ( - "errors" - "fmt" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type BranchesHelper struct { - c *HelperCommon - worktreeHelper *WorktreeHelper -} - -func NewBranchesHelper(c *HelperCommon, worktreeHelper *WorktreeHelper) *BranchesHelper { - return &BranchesHelper{ - c: c, - worktreeHelper: worktreeHelper, - } -} - -func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error { - if len(branches) > 1 { - if lo.SomeBy(branches, func(branch *models.Branch) bool { return self.checkedOutByOtherWorktree(branch) }) { - return errors.New(self.c.Tr.SomeBranchesCheckedOutByWorktreeError) - } - } else if self.checkedOutByOtherWorktree(branches[0]) { - return self.promptWorktreeBranchDelete(branches[0]) - } - - allBranchesMerged, err := self.allBranchesMerged(branches) - if err != nil { - return err - } - - doDelete := func() error { - return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch) - branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string { return branch.Name }) - if err := self.c.Git().Branch.LocalDelete(branchNames, true); err != nil { - return err - } - - self.c.Contexts().Branches.CollapseRangeSelectionToTop() - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}}) - return nil - }) - } - - if allBranchesMerged { - return doDelete() - } - - title := self.c.Tr.ForceDeleteBranchTitle - var message string - if len(branches) == 1 { - message = utils.ResolvePlaceholderString( - self.c.Tr.ForceDeleteBranchMessage, - map[string]string{ - "selectedBranchName": branches[0].Name, - }, - ) - } else { - message = self.c.Tr.ForceDeleteBranchesMessage - } - - self.c.Confirm(types.ConfirmOpts{ - Title: title, - Prompt: message, - HandleConfirm: func() error { - return doDelete() - }, - }) - - return nil -} - -func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteBranch, resetRemoteBranchesSelection bool) error { - var title string - if len(remoteBranches) == 1 { - title = utils.ResolvePlaceholderString( - self.c.Tr.DeleteBranchTitle, - map[string]string{ - "selectedBranchName": remoteBranches[0].Name, - }, - ) - } else { - title = self.c.Tr.DeleteBranchesTitle - } - var prompt string - if len(remoteBranches) == 1 { - prompt = utils.ResolvePlaceholderString( - self.c.Tr.DeleteRemoteBranchPrompt, - map[string]string{ - "selectedBranchName": remoteBranches[0].Name, - "upstream": remoteBranches[0].RemoteName, - }, - ) - } else { - prompt = self.c.Tr.DeleteRemoteBranchesPrompt - } - self.c.Confirm(types.ConfirmOpts{ - Title: title, - Prompt: prompt, - HandleConfirm: func() error { - return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error { - if err := self.deleteRemoteBranches(remoteBranches, task); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}}) - if resetRemoteBranchesSelection { - self.c.Contexts().RemoteBranches.CollapseRangeSelectionToTop() - } - return nil - }) - }, - }) - - return nil -} - -func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branches []*models.Branch) error { - if lo.SomeBy(branches, func(branch *models.Branch) bool { return self.checkedOutByOtherWorktree(branch) }) { - return errors.New(self.c.Tr.SomeBranchesCheckedOutByWorktreeError) - } - - allBranchesMerged, err := self.allBranchesMerged(branches) - if err != nil { - return err - } - - var prompt string - if len(branches) == 1 { - prompt = utils.ResolvePlaceholderString( - self.c.Tr.DeleteLocalAndRemoteBranchPrompt, - map[string]string{ - "localBranchName": branches[0].Name, - "remoteBranchName": branches[0].UpstreamBranch, - "remoteName": branches[0].UpstreamRemote, - }, - ) - } else { - prompt = self.c.Tr.DeleteLocalAndRemoteBranchesPrompt - } - - if !allBranchesMerged { - if len(branches) == 1 { - prompt += "\n\n" + utils.ResolvePlaceholderString( - self.c.Tr.ForceDeleteBranchMessage, - map[string]string{ - "selectedBranchName": branches[0].Name, - }, - ) - } else { - prompt += "\n\n" + self.c.Tr.ForceDeleteBranchesMessage - } - } - - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.DeleteLocalAndRemoteBranch, - Prompt: prompt, - HandleConfirm: func() error { - return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error { - // Delete the remote branches first so that we keep the local ones - // in case of failure - remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch { - return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote} - }) - if err := self.deleteRemoteBranches(remoteBranches, task); err != nil { - return err - } - - self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch) - branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string { return branch.Name }) - if err := self.c.Git().Branch.LocalDelete(branchNames, true); err != nil { - return err - } - - self.c.Contexts().Branches.CollapseRangeSelectionToTop() - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}}) - return nil - }) - }, - }) - - return nil -} - -func ShortBranchName(fullBranchName string) string { - return strings.TrimPrefix(strings.TrimPrefix(fullBranchName, "refs/heads/"), "refs/remotes/") -} - -func (self *BranchesHelper) checkedOutByOtherWorktree(branch *models.Branch) bool { - return git_commands.CheckedOutByOtherWorktree(branch, self.c.Model().Worktrees) -} - -func (self *BranchesHelper) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) { - return git_commands.WorktreeForBranch(branch, self.c.Model().Worktrees) -} - -func (self *BranchesHelper) promptWorktreeBranchDelete(selectedBranch *models.Branch) error { - worktree, ok := self.worktreeForBranch(selectedBranch) - if !ok { - self.c.Log.Error("promptWorktreeBranchDelete out of sync with list of worktrees") - return nil - } - - title := utils.ResolvePlaceholderString(self.c.Tr.BranchCheckedOutByWorktree, map[string]string{ - "worktreeName": worktree.Name, - "branchName": selectedBranch.Name, - }) - return self.c.Menu(types.CreateMenuOptions{ - Title: title, - Items: []*types.MenuItem{ - { - Label: self.c.Tr.SwitchToWorktree, - OnPress: func() error { - return self.worktreeHelper.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY) - }, - }, - { - Label: self.c.Tr.DetachWorktree, - Tooltip: self.c.Tr.DetachWorktreeTooltip, - OnPress: func() error { - return self.worktreeHelper.Detach(worktree) - }, - }, - { - Label: self.c.Tr.RemoveWorktree, - OnPress: func() error { - return self.worktreeHelper.Remove(worktree, false) - }, - }, - }, - }) -} - -func (self *BranchesHelper) allBranchesMerged(branches []*models.Branch) (bool, error) { - allBranchesMerged := true - for _, branch := range branches { - isMerged, err := self.c.Git().Branch.IsBranchMerged(branch, self.c.Model().MainBranches) - if err != nil { - return false, err - } - if !isMerged { - allBranchesMerged = false - break - } - } - return allBranchesMerged, nil -} - -func (self *BranchesHelper) deleteRemoteBranches(remoteBranches []*models.RemoteBranch, task gocui.Task) error { - remotes := lo.GroupBy(remoteBranches, func(branch *models.RemoteBranch) string { return branch.RemoteName }) - for remote, branches := range remotes { - self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch) - branchNames := lo.Map(branches, func(branch *models.RemoteBranch, _ int) string { return branch.Name }) - if err := self.c.Git().Remote.DeleteRemoteBranch(task, remote, branchNames); err != nil { - return err - } - } - return nil -} - -func (self *BranchesHelper) AutoForwardBranches() error { - if self.c.UserConfig().Git.AutoForwardBranches == "none" { - return nil - } - - branches := self.c.Model().Branches - if len(branches) == 0 { - return nil - } - - allBranches := self.c.UserConfig().Git.AutoForwardBranches == "allBranches" - updateCommands := "" - // The first branch is the currently checked out branch; skip it - for _, branch := range branches[1:] { - if branch.RemoteBranchStoredLocally() && - !self.checkedOutByOtherWorktree(branch) && - (allBranches || lo.Contains(self.c.UserConfig().Git.MainBranches, branch.Name)) { - isStrictlyBehind := branch.IsBehindForPull() && !branch.IsAheadForPull() - if isStrictlyBehind { - updateCommands += fmt.Sprintf("update %s %s %s\n", branch.FullRefName(), branch.FullUpstreamRefName(), branch.CommitHash) - } - } - } - - if updateCommands == "" { - return nil - } - - self.c.LogAction(self.c.Tr.Actions.AutoForwardBranches) - self.c.LogCommand(strings.TrimRight(updateCommands, "\n"), false) - err := self.c.Git().Branch.UpdateBranchRefs(updateCommands) - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES}, Mode: types.SYNC}) - - return err -} diff --git a/pkg/gui/controllers/helpers/cherry_pick_helper.go b/pkg/gui/controllers/helpers/cherry_pick_helper.go deleted file mode 100644 index c4be07ceb28..00000000000 --- a/pkg/gui/controllers/helpers/cherry_pick_helper.go +++ /dev/null @@ -1,170 +0,0 @@ -package helpers - -import ( - "strconv" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type CherryPickHelper struct { - c *HelperCommon - - rebaseHelper *MergeAndRebaseHelper -} - -// I'm using the analogy of copy+paste in the terminology here because it's intuitively what's going on, -// even if in truth we're running git cherry-pick - -func NewCherryPickHelper( - c *HelperCommon, - rebaseHelper *MergeAndRebaseHelper, -) *CherryPickHelper { - return &CherryPickHelper{ - c: c, - rebaseHelper: rebaseHelper, - } -} - -func (self *CherryPickHelper) getData() *cherrypicking.CherryPicking { - return self.c.Modes().CherryPicking -} - -func (self *CherryPickHelper) CopyRange(commitsList []*models.Commit, context types.IListContext) error { - startIdx, endIdx := context.GetList().GetSelectionRange() - - if err := self.resetIfNecessary(context); err != nil { - return err - } - - commitSet := self.getData().SelectedHashSet() - - allCommitsCopied := lo.EveryBy(commitsList[startIdx:endIdx+1], func(commit *models.Commit) bool { - return commitSet.Includes(commit.Hash()) - }) - - // if all selected commits are already copied, we'll uncopy them - if allCommitsCopied { - for index := startIdx; index <= endIdx; index++ { - commit := commitsList[index] - self.getData().Remove(commit, commitsList) - } - } else { - for index := startIdx; index <= endIdx; index++ { - commit := commitsList[index] - self.getData().Add(commit, commitsList) - } - } - - self.getData().DidPaste = false - - self.rerender() - return nil -} - -// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied. -// Only to be called from the branch commits controller -func (self *CherryPickHelper) Paste() error { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.CherryPick, - Prompt: utils.ResolvePlaceholderString( - self.c.Tr.SureCherryPick, - map[string]string{ - "numCommits": strconv.Itoa(len(self.getData().CherryPickedCommits)), - }), - HandleConfirm: func() error { - return self.c.WithWaitingStatusSync(self.c.Tr.CherryPickingStatus, func() error { - mustStash := IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules) - - self.c.LogAction(self.c.Tr.Actions.CherryPick) - - if mustStash { - if err := self.c.Git().Stash.Push(self.c.Tr.AutoStashForCherryPicking); err != nil { - return err - } - } - - cherryPickedCommits := self.getData().CherryPickedCommits - result := self.c.Git().Rebase.CherryPickCommits(cherryPickedCommits) - err := self.rebaseHelper.CheckMergeOrRebaseWithRefreshOptions(result, types.RefreshOptions{Mode: types.SYNC}) - if err != nil { - return result - } - - // Move the selection down by the number of commits we just - // cherry-picked, to keep the same commit selected as before. - // Don't do this if a rebase todo is selected, because in this - // case we are in a rebase and the cherry-picked commits end up - // below the selection. - if commit := self.c.Contexts().LocalCommits.GetSelected(); commit != nil && !commit.IsTODO() { - self.c.Contexts().LocalCommits.MoveSelection(len(cherryPickedCommits)) - self.c.Contexts().LocalCommits.FocusLine() - } - - // If we're in the cherry-picking state at this point, it must - // be because there were conflicts. Don't clear the copied - // commits in this case, since we might want to abort and try - // pasting them again. - isInCherryPick, result := self.c.Git().Status.IsInCherryPick() - if result != nil { - return result - } - if !isInCherryPick { - self.getData().DidPaste = true - self.rerender() - - if mustStash { - if err := self.c.Git().Stash.Pop(0); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{ - Scope: []types.RefreshableView{types.STASH, types.FILES}, - }) - } - } - - return nil - }) - }, - }) - - return nil -} - -func (self *CherryPickHelper) CanPaste() bool { - return self.getData().CanPaste() -} - -func (self *CherryPickHelper) Reset() error { - self.getData().ContextKey = "" - self.getData().CherryPickedCommits = nil - - self.rerender() - return nil -} - -// you can only copy from one context at a time, because the order and position of commits matter -func (self *CherryPickHelper) resetIfNecessary(context types.Context) error { - oldContextKey := types.ContextKey(self.getData().ContextKey) - - if oldContextKey != context.GetKey() { - // need to reset the cherry picking mode - self.getData().ContextKey = string(context.GetKey()) - self.getData().CherryPickedCommits = make([]*models.Commit, 0) - } - - return nil -} - -func (self *CherryPickHelper) rerender() { - for _, context := range []types.Context{ - self.c.Contexts().LocalCommits, - self.c.Contexts().ReflogCommits, - self.c.Contexts().SubCommits, - } { - self.c.PostRefreshUpdate(context) - } -} diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go deleted file mode 100644 index 306c689a1e4..00000000000 --- a/pkg/gui/controllers/helpers/commits_helper.go +++ /dev/null @@ -1,268 +0,0 @@ -package helpers - -import ( - "errors" - "path/filepath" - "strings" - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type CommitsHelper struct { - c *HelperCommon - - getCommitSummary func() string - setCommitSummary func(string) - getCommitDescription func() string - getUnwrappedCommitDescription func() string - setCommitDescription func(string) -} - -func NewCommitsHelper( - c *HelperCommon, - getCommitSummary func() string, - setCommitSummary func(string), - getCommitDescription func() string, - getUnwrappedCommitDescription func() string, - setCommitDescription func(string), -) *CommitsHelper { - return &CommitsHelper{ - c: c, - getCommitSummary: getCommitSummary, - setCommitSummary: setCommitSummary, - getCommitDescription: getCommitDescription, - getUnwrappedCommitDescription: getUnwrappedCommitDescription, - setCommitDescription: setCommitDescription, - } -} - -func (self *CommitsHelper) SplitCommitMessageAndDescription(message string) (string, string) { - msg, description, _ := strings.Cut(message, "\n") - return msg, strings.TrimSpace(description) -} - -func (self *CommitsHelper) SetMessageAndDescriptionInView(message string) { - summary, description := self.SplitCommitMessageAndDescription(message) - - self.setCommitSummary(summary) - self.setCommitDescription(description) - self.c.Contexts().CommitMessage.RenderSubtitle() -} - -func (self *CommitsHelper) JoinCommitMessageAndUnwrappedDescription() string { - if len(self.getUnwrappedCommitDescription()) == 0 { - return self.getCommitSummary() - } - return self.getCommitSummary() + "\n" + self.getUnwrappedCommitDescription() -} - -func TryRemoveHardLineBreaks(message string, autoWrapWidth int) string { - messageRunes := []rune(message) - lastHardLineStart := 0 - for i, r := range messageRunes { - if r == '\n' { - // Try to make this a soft linebreak by turning it into a space, and - // checking whether it still wraps to the same result then. - messageRunes[i] = ' ' - - _, cursorMapping := gocui.AutoWrapContent(messageRunes[lastHardLineStart:], autoWrapWidth) - - // Look at the cursorMapping to check whether auto-wrapping inserted - // a line break. If it did, there will be a cursorMapping entry with - // Orig pointing to the position after the inserted line break. - if len(cursorMapping) == 0 || cursorMapping[0].Orig != i-lastHardLineStart+1 { - // It didn't, so change it back to a newline - messageRunes[i] = '\n' - } - lastHardLineStart = i + 1 - } - } - - return string(messageRunes) -} - -func (self *CommitsHelper) SwitchToEditor() error { - message := lo.Ternary(len(self.getCommitDescription()) == 0, - self.getCommitSummary(), - self.getCommitSummary()+"\n\n"+self.getCommitDescription()) - filepath := filepath.Join(self.c.OS().GetTempDir(), self.c.Git().RepoPaths.RepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".msg") - err := self.c.OS().CreateFileWithContent(filepath, message) - if err != nil { - return err - } - - self.CloseCommitMessagePanel() - - return self.c.Contexts().CommitMessage.SwitchToEditor(filepath) -} - -func (self *CommitsHelper) UpdateCommitPanelView(message string) { - if message != "" { - self.SetMessageAndDescriptionInView(message) - return - } - - if self.c.Contexts().CommitMessage.GetPreserveMessage() { - preservedMessage := self.c.Contexts().CommitMessage.GetPreservedMessageAndLogError() - self.SetMessageAndDescriptionInView(preservedMessage) - return - } - - self.SetMessageAndDescriptionInView("") -} - -type OpenCommitMessagePanelOpts struct { - CommitIndex int - SummaryTitle string - DescriptionTitle string - PreserveMessage bool - OnConfirm func(summary string, description string) error - OnSwitchToEditor func(string) error - InitialMessage string - - // The following two fields are only for the display of the "(hooks - // disabled)" display in the commit message panel. They have no effect on - // the actual behavior; make sure what you are passing in matches that. - // Leave unassigned if the concept of skipping hooks doesn't make sense for - // what you are doing, e.g. when creating a tag. - ForceSkipHooks bool - SkipHooksPrefix string -} - -func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOpts) { - onConfirm := func(summary string, description string) error { - self.CloseCommitMessagePanel() - - return opts.OnConfirm(summary, description) - } - - self.c.Contexts().CommitMessage.SetPanelState( - opts.CommitIndex, - opts.SummaryTitle, - opts.DescriptionTitle, - opts.PreserveMessage, - opts.InitialMessage, - onConfirm, - opts.OnSwitchToEditor, - opts.ForceSkipHooks, - opts.SkipHooksPrefix, - ) - - self.UpdateCommitPanelView(opts.InitialMessage) - - self.c.Context().Push(self.c.Contexts().CommitMessage, types.OnFocusOpts{}) -} - -func (self *CommitsHelper) ClearPreservedCommitMessage() { - self.c.Contexts().CommitMessage.SetPreservedMessageAndLogError("") -} - -func (self *CommitsHelper) HandleCommitConfirm() error { - summary, description := self.getCommitSummary(), self.getCommitDescription() - - if summary == "" { - return errors.New(self.c.Tr.CommitWithoutMessageErr) - } - - err := self.c.Contexts().CommitMessage.OnConfirm(summary, description) - if err != nil { - return err - } - - return nil -} - -func (self *CommitsHelper) CloseCommitMessagePanel() { - if self.c.Contexts().CommitMessage.GetPreserveMessage() { - message := self.JoinCommitMessageAndUnwrappedDescription() - if message != self.c.Contexts().CommitMessage.GetInitialMessage() { - self.c.Contexts().CommitMessage.SetPreservedMessageAndLogError(message) - } - } else { - self.SetMessageAndDescriptionInView("") - } - - self.c.Contexts().CommitMessage.SetHistoryMessage("") - - self.c.Views().CommitMessage.Visible = false - self.c.Views().CommitDescription.Visible = false - - self.c.Context().Pop() -} - -func (self *CommitsHelper) OpenCommitMenu(suggestionFunc func(string) []*types.Suggestion) error { - var disabledReasonForOpenInEditor *types.DisabledReason - if !self.c.Contexts().CommitMessage.CanSwitchToEditor() { - disabledReasonForOpenInEditor = &types.DisabledReason{ - Text: self.c.Tr.CommandDoesNotSupportOpeningInEditor, - } - } - - menuItems := []*types.MenuItem{ - { - Label: self.c.Tr.OpenInEditor, - OnPress: func() error { - return self.SwitchToEditor() - }, - Key: 'e', - DisabledReason: disabledReasonForOpenInEditor, - }, - { - Label: self.c.Tr.AddCoAuthor, - OnPress: func() error { - return self.addCoAuthor(suggestionFunc) - }, - Key: 'c', - }, - { - Label: self.c.Tr.PasteCommitMessageFromClipboard, - OnPress: func() error { - return self.pasteCommitMessageFromClipboard() - }, - Key: 'p', - }, - } - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.CommitMenuTitle, - Items: menuItems, - }) -} - -func (self *CommitsHelper) addCoAuthor(suggestionFunc func(string) []*types.Suggestion) error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.AddCoAuthorPromptTitle, - FindSuggestionsFunc: suggestionFunc, - HandleConfirm: func(value string) error { - commitDescription := self.getCommitDescription() - commitDescription = git_commands.AddCoAuthorToDescription(commitDescription, value) - self.setCommitDescription(commitDescription) - return nil - }, - }) - - return nil -} - -func (self *CommitsHelper) pasteCommitMessageFromClipboard() error { - message, err := self.c.OS().PasteFromClipboard() - if err != nil { - return err - } - if message == "" { - return nil - } - - currentMessage := self.JoinCommitMessageAndUnwrappedDescription() - return self.c.ConfirmIf(currentMessage != "", types.ConfirmOpts{ - Title: self.c.Tr.PasteCommitMessageFromClipboard, - Prompt: self.c.Tr.SurePasteCommitMessage, - HandleConfirm: func() error { - self.SetMessageAndDescriptionInView(message) - return nil - }, - }) -} diff --git a/pkg/gui/controllers/helpers/commits_helper_test.go b/pkg/gui/controllers/helpers/commits_helper_test.go deleted file mode 100644 index 6197c3916ce..00000000000 --- a/pkg/gui/controllers/helpers/commits_helper_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package helpers - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTryRemoveHardLineBreaks(t *testing.T) { - scenarios := []struct { - name string - message string - autoWrapWidth int - expectedResult string - }{ - { - name: "empty", - message: "", - autoWrapWidth: 7, - expectedResult: "", - }, - { - name: "all line breaks are needed", - message: "abc\ndef\n\nxyz", - autoWrapWidth: 7, - expectedResult: "abc\ndef\n\nxyz", - }, - { - name: "some can be unwrapped", - message: "123\nabc def\nghi jkl\nmno\n456\n", - autoWrapWidth: 7, - expectedResult: "123\nabc def ghi jkl mno\n456\n", - }, - } - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - actualResult := TryRemoveHardLineBreaks(s.message, s.autoWrapWidth) - assert.Equal(t, s.expectedResult, actualResult) - }) - } -} diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go deleted file mode 100644 index 4a746819bbc..00000000000 --- a/pkg/gui/controllers/helpers/confirmation_helper.go +++ /dev/null @@ -1,416 +0,0 @@ -package helpers - -import ( - goContext "context" - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type ConfirmationHelper struct { - c *HelperCommon -} - -func NewConfirmationHelper(c *HelperCommon) *ConfirmationHelper { - return &ConfirmationHelper{ - c: c, - } -} - -// This file is for the rendering of confirmation panels along with setting and handling associated -// keybindings. - -func (self *ConfirmationHelper) wrappedConfirmationFunction(cancel goContext.CancelFunc, function func() error) func() error { - return func() error { - if self.c.GocuiGui().IsPasting { - // The user is pasting multi-line text into a prompt; we don't want to handle the - // line feeds as "confirm" keybindings. Simply ignoring them is the best we can do; this - // will cause the entire pasted text to appear as a single line in the prompt. Hopefully - // the user knows that ctrl-u allows them to delete it again... - return nil - } - - cancel() - - self.c.Context().Pop() - - if function != nil { - if err := function(); err != nil { - return err - } - } - - return nil - } -} - -func (self *ConfirmationHelper) wrappedPromptConfirmationFunction(cancel goContext.CancelFunc, function func(string) error, getResponse func() string) func() error { - return self.wrappedConfirmationFunction(cancel, func() error { - return function(getResponse()) - }) -} - -func (self *ConfirmationHelper) DeactivateConfirmation() { - self.c.Mutexes().PopupMutex.Lock() - self.c.State().GetRepoState().SetCurrentPopupOpts(nil) - self.c.Mutexes().PopupMutex.Unlock() - - self.c.Views().Confirmation.Visible = false - - self.clearConfirmationViewKeyBindings() -} - -func (self *ConfirmationHelper) DeactivatePrompt() { - self.c.Mutexes().PopupMutex.Lock() - self.c.State().GetRepoState().SetCurrentPopupOpts(nil) - self.c.Mutexes().PopupMutex.Unlock() - - self.c.Views().Prompt.Visible = false - self.c.Views().Suggestions.Visible = false - - self.clearPromptViewKeyBindings() -} - -func getMessageHeight(wrap bool, editable bool, message string, width int, tabWidth int) int { - wrappedLines, _, _ := utils.WrapViewLinesToWidth(wrap, editable, message, width, tabWidth) - return len(wrappedLines) -} - -func (self *ConfirmationHelper) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int, parentPopupContext types.Context) (int, int, int, int) { - return self.getPopupPanelDimensionsAux(panelWidth, contentHeight, parentPopupContext) -} - -func (self *ConfirmationHelper) getPopupPanelDimensionsAux(panelWidth int, panelHeight int, parentPopupContext types.Context) (int, int, int, int) { - width, height := self.c.GocuiGui().Size() - if panelHeight > height*3/4 { - panelHeight = height * 3 / 4 - } - if parentPopupContext != nil { - // If there's already a popup on the screen, offset the new one from its - // parent so that it's clearly distinguished from the parent - x0, y0, _, _ := parentPopupContext.GetView().Dimensions() - x0 += 2 - y0 += 1 - return x0, y0, x0 + panelWidth, y0 + panelHeight + 1 - } - return width/2 - panelWidth/2, - height/2 - panelHeight/2 - panelHeight%2 - 1, - width/2 + panelWidth/2, - height/2 + panelHeight/2 -} - -func (self *ConfirmationHelper) getPopupPanelWidth() int { - width, _ := self.c.GocuiGui().Size() - // we want a minimum width up to a point, then we do it based on ratio. - panelWidth := 4 * width / 7 - minWidth := 80 - if panelWidth < minWidth { - if width-2 < minWidth { - panelWidth = width - 2 - } else { - panelWidth = minWidth - } - } - - return panelWidth -} - -func (self *ConfirmationHelper) prepareConfirmationPanel( - opts types.ConfirmOpts, -) { - self.c.Views().Confirmation.Title = opts.Title - self.c.Views().Confirmation.FgColor = theme.GocuiDefaultTextColor - - self.c.ResetViewOrigin(self.c.Views().Confirmation) - self.c.SetViewContent(self.c.Views().Confirmation, style.AttrBold.Sprint(strings.TrimSpace(opts.Prompt))) -} - -func (self *ConfirmationHelper) preparePromptPanel( - opts types.ConfirmOpts, -) { - self.c.Views().Prompt.Title = opts.Title - self.c.Views().Prompt.FgColor = theme.GocuiDefaultTextColor - self.c.Views().Prompt.Mask = runeForMask(opts.Mask) - self.c.Views().Prompt.SetOrigin(0, 0) - - textArea := self.c.Views().Prompt.TextArea - textArea.Clear() - textArea.TypeString(opts.Prompt) - self.c.Views().Prompt.RenderTextArea() - - if opts.FindSuggestionsFunc != nil { - suggestionsContext := self.c.Contexts().Suggestions - suggestionsContext.State.FindSuggestions = opts.FindSuggestionsFunc - suggestionsView := self.c.Views().Suggestions - suggestionsView.Wrap = false - suggestionsView.FgColor = theme.GocuiDefaultTextColor - suggestionsContext.SetSuggestions(opts.FindSuggestionsFunc("")) - suggestionsView.Visible = true - suggestionsView.Title = fmt.Sprintf(self.c.Tr.SuggestionsTitle, self.c.UserConfig().Keybinding.Universal.TogglePanel) - suggestionsView.Subtitle = "" - } -} - -func runeForMask(mask bool) rune { - if mask { - return '*' - } - return 0 -} - -func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts types.CreatePopupPanelOpts) { - self.c.Mutexes().PopupMutex.Lock() - defer self.c.Mutexes().PopupMutex.Unlock() - - _, cancel := goContext.WithCancel(ctx) - - // we don't allow interruptions of non-loader popups in case we get stuck somehow - // e.g. a credentials popup never gets its required user input so a process hangs - // forever. - // The proper solution is to have a queue of popup options - currentPopupOpts := self.c.State().GetRepoState().GetCurrentPopupOpts() - if currentPopupOpts != nil && !currentPopupOpts.HasLoader { - self.c.Log.Error("ignoring create popup panel because a popup panel is already open") - cancel() - return - } - - // remove any previous keybindings - self.clearConfirmationViewKeyBindings() - self.clearPromptViewKeyBindings() - - var context types.Context - if opts.Editable { - self.c.Contexts().Suggestions.State.FindSuggestions = opts.FindSuggestionsFunc - - self.preparePromptPanel( - types.ConfirmOpts{ - Title: opts.Title, - Prompt: opts.Prompt, - FindSuggestionsFunc: opts.FindSuggestionsFunc, - Mask: opts.Mask, - }) - - context = self.c.Contexts().Prompt - - self.setPromptKeyBindings(cancel, opts) - } else { - if opts.FindSuggestionsFunc != nil { - panic("non-editable confirmation views do not support suggestions") - } - - self.c.Contexts().Suggestions.State.FindSuggestions = nil - - self.prepareConfirmationPanel( - types.ConfirmOpts{ - Title: opts.Title, - Prompt: opts.Prompt, - }) - - context = self.c.Contexts().Confirmation - - self.setConfirmationKeyBindings(cancel, opts) - } - - self.c.Contexts().Suggestions.State.AllowEditSuggestion = opts.AllowEditSuggestion - - self.c.State().GetRepoState().SetCurrentPopupOpts(&opts) - - self.c.Context().Push(context, types.OnFocusOpts{}) -} - -func (self *ConfirmationHelper) setConfirmationKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) { - onConfirm := self.wrappedConfirmationFunction(cancel, opts.HandleConfirm) - onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose) - - self.c.Contexts().Confirmation.State.OnConfirm = onConfirm - self.c.Contexts().Confirmation.State.OnClose = onClose -} - -func (self *ConfirmationHelper) setPromptKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) { - onConfirm := self.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, - func() string { return self.c.Views().Prompt.TextArea.GetContent() }) - - onSuggestionConfirm := self.wrappedPromptConfirmationFunction( - cancel, - opts.HandleConfirmPrompt, - self.getSelectedSuggestionValue, - ) - - onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose) - - onDeleteSuggestion := func() error { - if opts.HandleDeleteSuggestion == nil { - return nil - } - - idx := self.c.Contexts().Suggestions.GetSelectedLineIdx() - return opts.HandleDeleteSuggestion(idx) - } - - self.c.Contexts().Prompt.State.OnConfirm = onConfirm - self.c.Contexts().Prompt.State.OnClose = onClose - self.c.Contexts().Suggestions.State.OnConfirm = onSuggestionConfirm - self.c.Contexts().Suggestions.State.OnClose = onClose - self.c.Contexts().Suggestions.State.OnDeleteSuggestion = onDeleteSuggestion -} - -func (self *ConfirmationHelper) clearConfirmationViewKeyBindings() { - noop := func() error { return nil } - self.c.Contexts().Confirmation.State.OnConfirm = noop - self.c.Contexts().Confirmation.State.OnClose = noop -} - -func (self *ConfirmationHelper) clearPromptViewKeyBindings() { - noop := func() error { return nil } - self.c.Contexts().Prompt.State.OnConfirm = noop - self.c.Contexts().Prompt.State.OnClose = noop - self.c.Contexts().Suggestions.State.OnConfirm = noop - self.c.Contexts().Suggestions.State.OnClose = noop - self.c.Contexts().Suggestions.State.OnDeleteSuggestion = noop -} - -func (self *ConfirmationHelper) getSelectedSuggestionValue() string { - selectedSuggestion := self.c.Contexts().Suggestions.GetSelected() - - if selectedSuggestion != nil { - return selectedSuggestion.Value - } - - return "" -} - -func (self *ConfirmationHelper) ResizeCurrentPopupPanels() { - var parentPopupContext types.Context - for _, c := range self.c.Context().CurrentPopup() { - switch c { - case self.c.Contexts().Menu: - self.resizeMenu(parentPopupContext) - case self.c.Contexts().Confirmation: - self.resizeConfirmationPanel(parentPopupContext) - case self.c.Contexts().Prompt, self.c.Contexts().Suggestions: - self.resizePromptPanel(parentPopupContext) - case self.c.Contexts().CommitMessage, self.c.Contexts().CommitDescription: - self.ResizeCommitMessagePanels(parentPopupContext) - } - - parentPopupContext = c - } -} - -func (self *ConfirmationHelper) resizeMenu(parentPopupContext types.Context) { - // we want the unfiltered length here so that if we're filtering we don't - // resize the window - itemCount := self.c.Contexts().Menu.UnfilteredLen() - offset := 3 - panelWidth := self.getPopupPanelWidth() - contentWidth := panelWidth - 2 // minus 2 for the frame - promptLinesCount := self.layoutMenuPrompt(contentWidth) - x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset+promptLinesCount, parentPopupContext) - menuBottom := y1 - offset - _, _ = self.c.GocuiGui().SetView(self.c.Views().Menu.Name(), x0, y0, x1, menuBottom, 0) - - tooltipTop := menuBottom + 1 - tooltip := "" - selectedItem := self.c.Contexts().Menu.GetSelected() - if selectedItem != nil { - tooltip = self.TooltipForMenuItem(selectedItem) - } - tooltipHeight := getMessageHeight(true, false, tooltip, contentWidth, self.c.Views().Menu.TabWidth) + 2 // plus 2 for the frame - _, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0) -} - -// Wraps the lines of the menu prompt to the available width and rerenders the -// menu if needed. Returns the number of lines the prompt takes up. -func (self *ConfirmationHelper) layoutMenuPrompt(contentWidth int) int { - oldPromptLines := self.c.Contexts().Menu.GetPromptLines() - var promptLines []string - prompt := self.c.Contexts().Menu.GetPrompt() - if len(prompt) > 0 { - promptLines, _, _ = utils.WrapViewLinesToWidth(true, false, prompt, contentWidth, self.c.Views().Menu.TabWidth) - promptLines = append(promptLines, "") - } - self.c.Contexts().Menu.SetPromptLines(promptLines) - if len(oldPromptLines) != len(promptLines) { - // The number of lines in the prompt has changed; this happens either - // because we're now showing a menu that has a prompt, and the previous - // menu didn't (or vice versa), or because the user is resizing the - // terminal window while a menu with a prompt is open. - - // We need to rerender to give the menu context a chance to update its - // non-model items, and reinitialize the data it uses for converting - // between view index and model index. - self.c.Contexts().Menu.HandleRender() - - // Then we need to refocus to ensure the cursor is in the right place in - // the view. - self.c.Contexts().Menu.HandleFocus(types.OnFocusOpts{}) - } - return len(promptLines) -} - -func (self *ConfirmationHelper) resizeConfirmationPanel(parentPopupContext types.Context) { - panelWidth := self.getPopupPanelWidth() - contentWidth := panelWidth - 2 // minus 2 for the frame - confirmationView := self.c.Views().Confirmation - prompt := confirmationView.Buffer() - panelHeight := getMessageHeight(true, false, prompt, contentWidth, confirmationView.TabWidth) - x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext) - _, _ = self.c.GocuiGui().SetView(confirmationView.Name(), x0, y0, x1, y1, 0) -} - -func (self *ConfirmationHelper) resizePromptPanel(parentPopupContext types.Context) { - suggestionsViewHeight := 0 - if self.c.Views().Suggestions.Visible { - suggestionsViewHeight = 11 - } - panelWidth := self.getPopupPanelWidth() - contentWidth := panelWidth - 2 // minus 2 for the frame - promptView := self.c.Views().Prompt - prompt := promptView.TextArea.GetContent() - panelHeight := getMessageHeight(false, true, prompt, contentWidth, promptView.TabWidth) + suggestionsViewHeight - x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext) - promptViewBottom := y1 - suggestionsViewHeight - _, _ = self.c.GocuiGui().SetView(promptView.Name(), x0, y0, x1, promptViewBottom, 0) - - suggestionsViewTop := promptViewBottom + 1 - _, _ = self.c.GocuiGui().SetView(self.c.Views().Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0) -} - -func (self *ConfirmationHelper) ResizeCommitMessagePanels(parentPopupContext types.Context) { - panelWidth := self.getPopupPanelWidth() - content := self.c.Views().CommitDescription.TextArea.GetContent() - summaryViewHeight := 3 - panelHeight := getMessageHeight(false, true, content, panelWidth, self.c.Views().CommitDescription.TabWidth) - minHeight := 7 - if panelHeight < minHeight { - panelHeight = minHeight - } - x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext) - - _, _ = self.c.GocuiGui().SetView(self.c.Views().CommitMessage.Name(), x0, y0, x1, y0+summaryViewHeight-1, 0) - _, _ = self.c.GocuiGui().SetView(self.c.Views().CommitDescription.Name(), x0, y0+summaryViewHeight, x1, y1+summaryViewHeight, 0) -} - -func (self *ConfirmationHelper) IsPopupPanel(context types.Context) bool { - return context.GetKind() == types.PERSISTENT_POPUP || context.GetKind() == types.TEMPORARY_POPUP -} - -func (self *ConfirmationHelper) IsPopupPanelFocused() bool { - return self.IsPopupPanel(self.c.Context().Current()) -} - -func (self *ConfirmationHelper) TooltipForMenuItem(menuItem *types.MenuItem) string { - tooltip := menuItem.Tooltip - if menuItem.DisabledReason != nil && menuItem.DisabledReason.Text != "" { - if tooltip != "" { - tooltip += "\n\n" - } - tooltip += style.FgRed.Sprintf(self.c.Tr.DisabledMenuItemPrefix) + menuItem.DisabledReason.Text - } - return tooltip -} diff --git a/pkg/gui/controllers/helpers/credentials_helper.go b/pkg/gui/controllers/helpers/credentials_helper.go deleted file mode 100644 index 0783e65f2ce..00000000000 --- a/pkg/gui/controllers/helpers/credentials_helper.go +++ /dev/null @@ -1,68 +0,0 @@ -package helpers - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type CredentialsHelper struct { - c *HelperCommon -} - -func NewCredentialsHelper( - c *HelperCommon, -) *CredentialsHelper { - return &CredentialsHelper{ - c: c, - } -} - -// promptUserForCredential wait for a username, password or passphrase input from the credentials popup -// We return a channel rather than returning the string directly so that the calling function knows -// when the prompt has been created (before the user has entered anything) so that it can -// note that we're now waiting on user input and lazygit isn't processing anything. -func (self *CredentialsHelper) PromptUserForCredential(passOrUname oscommands.CredentialType) <-chan string { - ch := make(chan string) - - self.c.OnUIThread(func() error { - title, mask := self.getTitleAndMask(passOrUname) - - self.c.Prompt(types.PromptOpts{ - Title: title, - Mask: mask, - HandleConfirm: func(input string) error { - ch <- input + "\n" - - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }, - HandleClose: func() error { - ch <- "\n" - - return nil - }, - }) - - return nil - }) - - return ch -} - -func (self *CredentialsHelper) getTitleAndMask(passOrUname oscommands.CredentialType) (string, bool) { - switch passOrUname { - case oscommands.Username: - return self.c.Tr.CredentialsUsername, false - case oscommands.Password: - return self.c.Tr.CredentialsPassword, true - case oscommands.Passphrase: - return self.c.Tr.CredentialsPassphrase, true - case oscommands.PIN: - return self.c.Tr.CredentialsPIN, true - case oscommands.Token: - return self.c.Tr.CredentialsToken, true - } - - // should never land here - panic("unexpected credential request") -} diff --git a/pkg/gui/controllers/helpers/diff_helper.go b/pkg/gui/controllers/helpers/diff_helper.go deleted file mode 100644 index 668ee916adb..00000000000 --- a/pkg/gui/controllers/helpers/diff_helper.go +++ /dev/null @@ -1,232 +0,0 @@ -package helpers - -import ( - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/modes/diffing" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type DiffHelper struct { - c *HelperCommon -} - -func NewDiffHelper(c *HelperCommon) *DiffHelper { - return &DiffHelper{ - c: c, - } -} - -func (self *DiffHelper) DiffArgs() []string { - output := []string{"--stat", "-p", self.c.Modes().Diffing.Ref} - - right := self.currentDiffTerminal() - if right != "" { - output = append(output, right) - } - - if self.c.Modes().Diffing.Reverse { - output = append(output, "-R") - } - - output = append(output, "--") - - file := self.currentlySelectedFilename() - if file != "" { - output = append(output, file) - } else if self.c.Modes().Filtering.Active() { - output = append(output, self.c.Modes().Filtering.GetPath()) - } - - return output -} - -// Returns an update task that can be passed to RenderToMainViews to render a -// diff for the selected commit(s). We need to pass both the selected commit -// and the refRange for a range selection. If the refRange is nil (meaning that -// either there's no range, or it can't be diffed for some reason), then we want -// to fall back to rendering the diff for the single commit. -func (self *DiffHelper) GetUpdateTaskForRenderingCommitsDiff(commit *models.Commit, refRange *types.RefRange) types.UpdateTask { - if refRange != nil { - from, to := refRange.From, refRange.To - args := []string{from.ParentRefName(), to.RefName(), "--stat", "-p"} - args = append(args, "--") - if filterPath := self.c.Modes().Filtering.GetPath(); filterPath != "" { - // If both refs are commits, filter by the union of their paths. This is useful for - // example when diffing a range of commits in filter-by-path mode across a rename. - fromCommit, ok1 := from.(*models.Commit) - toCommit, ok2 := to.(*models.Commit) - if ok1 && ok2 { - paths := append(self.FilterPathsForCommit(fromCommit), self.FilterPathsForCommit(toCommit)...) - args = append(args, lo.Uniq(paths)...) - } else { - // If either ref is not a commit (which is possible in sticky diff mode, when - // diffing against a branch or tag), we just filter by the filter path; that's the - // best we can do in this case. - args = append(args, filterPath) - } - } - cmdObj := self.c.Git().Diff.DiffCmdObj(args) - prefix := style.FgYellow.Sprintf("%s %s-%s\n\n", self.c.Tr.ShowingDiffForRange, from.ShortRefName(), to.ShortRefName()) - return types.NewRunPtyTaskWithPrefix(cmdObj.GetCmd(), prefix) - } - - cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Hash(), self.FilterPathsForCommit(commit)) - return types.NewRunPtyTask(cmdObj.GetCmd()) -} - -func (self *DiffHelper) FilterPathsForCommit(commit *models.Commit) []string { - filterPath := self.c.Modes().Filtering.GetPath() - if filterPath != "" { - if len(commit.FilterPaths) > 0 { - return commit.FilterPaths - } - return []string{filterPath} - } - return nil -} - -func (self *DiffHelper) ExitDiffMode() error { - self.c.Modes().Diffing = diffing.New() - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil -} - -func (self *DiffHelper) RenderDiff() { - args := self.DiffArgs() - cmdObj := self.c.Git().Diff.DiffCmdObj(args) - prefix := style.FgMagenta.Sprintf( - "%s %s\n\n", - self.c.Tr.ShowingGitDiff, - "git diff "+strings.Join(args, " "), - ) - task := types.NewRunPtyTaskWithPrefix(cmdObj.GetCmd(), prefix) - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: "Diff", - SubTitle: self.IgnoringWhitespaceSubTitle(), - Task: task, - }, - }) -} - -// CurrentDiffTerminals returns the current diff terminals of the currently selected item. -// in the case of a branch it returns both the branch and it's upstream name, -// which becomes an option when you bring up the diff menu, but when you're just -// flicking through branches it will be using the local branch name. -func (self *DiffHelper) CurrentDiffTerminals() []string { - c := self.c.Context().CurrentSide() - - if c.GetKey() == "" { - return nil - } - - switch v := c.(type) { - case types.DiffableContext: - return v.GetDiffTerminals() - } - - return nil -} - -func (self *DiffHelper) currentDiffTerminal() string { - names := self.CurrentDiffTerminals() - if len(names) == 0 { - return "" - } - return names[0] -} - -func (self *DiffHelper) currentlySelectedFilename() string { - currentContext := self.c.Context().Current() - - switch currentContext := currentContext.(type) { - case types.IListContext: - if lo.Contains([]types.ContextKey{context.FILES_CONTEXT_KEY, context.COMMIT_FILES_CONTEXT_KEY}, currentContext.GetKey()) { - return currentContext.GetSelectedItemId() - } - } - - return "" -} - -func (self *DiffHelper) WithDiffModeCheck(f func()) { - if self.c.Modes().Diffing.Active() { - self.RenderDiff() - } else { - f() - } -} - -func (self *DiffHelper) IgnoringWhitespaceSubTitle() string { - if self.c.UserConfig().Git.IgnoreWhitespaceInDiffView { - return self.c.Tr.IgnoreWhitespaceDiffViewSubTitle - } - - return "" -} - -func (self *DiffHelper) OpenDiffToolForRef(selectedRef models.Ref) error { - to := selectedRef.RefName() - from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff("") - _, err := self.c.RunSubprocess(self.c.Git().Diff.OpenDiffToolCmdObj( - git_commands.DiffToolCmdOptions{ - Filepath: ".", - FromCommit: from, - ToCommit: to, - Reverse: reverse, - IsDirectory: true, - Staged: false, - })) - return err -} - -// AdjustLineNumber is used to adjust a line number in the diff that's currently -// being viewed, so that it corresponds to the line number in the actual working -// copy state of the file. It is used when clicking on a delta hyperlink in a -// diff, or when pressing `e` in the staging or patch building panels. It works -// by getting a diff of what's being viewed in the main view against the working -// copy, and then using that diff to adjust the line number. -// path is the file path of the file being viewed -// linenumber is the line number to adjust (one-based) -// viewname is the name of the view that shows the diff. We need to pass it -// because the diff adjustment is slightly different depending on which view is -// showing the diff. -func (self *DiffHelper) AdjustLineNumber(path string, linenumber int, viewname string) int { - switch viewname { - - case "main", "patchBuilding": - if diffableContext, ok := self.c.Context().CurrentSide().(types.DiffableContext); ok { - ref := diffableContext.RefForAdjustingLineNumberInDiff() - if len(ref) != 0 { - return self.adjustLineNumber(linenumber, ref, "--", path) - } - } - // if the type cast to DiffableContext returns false, we are in the - // unstaged changes view of the Files panel; no need to adjust line - // numbers in this case - - case "secondary", "stagingSecondary": - return self.adjustLineNumber(linenumber, "--", path) - } - - return linenumber -} - -func (self *DiffHelper) adjustLineNumber(linenumber int, diffArgs ...string) int { - args := append([]string{"--unified=0"}, diffArgs...) - diff, err := self.c.Git().Diff.GetDiff(false, args...) - if err != nil { - return linenumber - } - patch := patch.Parse(diff) - return patch.AdjustLineNumber(linenumber) -} diff --git a/pkg/gui/controllers/helpers/files_helper.go b/pkg/gui/controllers/helpers/files_helper.go deleted file mode 100644 index 81c9b272e18..00000000000 --- a/pkg/gui/controllers/helpers/files_helper.go +++ /dev/null @@ -1,83 +0,0 @@ -package helpers - -import ( - "path/filepath" - - "github.com/samber/lo" -) - -type FilesHelper struct { - c *HelperCommon -} - -func NewFilesHelper(c *HelperCommon) *FilesHelper { - return &FilesHelper{ - c: c, - } -} - -func (self *FilesHelper) EditFiles(filenames []string) error { - absPaths := lo.Map(filenames, func(filename string, _ int) string { - absPath, err := filepath.Abs(filename) - if err != nil { - return filename - } - return absPath - }) - cmdStr, suspend := self.c.Git().File.GetEditCmdStr(absPaths) - return self.callEditor(cmdStr, suspend) -} - -func (self *FilesHelper) EditFileAtLine(filename string, lineNumber int) error { - absPath, err := filepath.Abs(filename) - if err != nil { - return err - } - cmdStr, suspend := self.c.Git().File.GetEditAtLineCmdStr(absPath, lineNumber) - return self.callEditor(cmdStr, suspend) -} - -func (self *FilesHelper) EditFileAtLineAndWait(filename string, lineNumber int) error { - absPath, err := filepath.Abs(filename) - if err != nil { - return err - } - cmdStr := self.c.Git().File.GetEditAtLineAndWaitCmdStr(absPath, lineNumber) - - // Always suspend, regardless of the value of the suspend config, - // since we want to prevent interacting with the UI until the editor - // returns, even if the editor doesn't use the terminal - return self.callEditor(cmdStr, true) -} - -func (self *FilesHelper) OpenDirInEditor(path string) error { - absPath, err := filepath.Abs(path) - if err != nil { - return err - } - cmdStr, suspend := self.c.Git().File.GetOpenDirInEditorCmdStr(absPath) - - return self.callEditor(cmdStr, suspend) -} - -func (self *FilesHelper) callEditor(cmdStr string, suspend bool) error { - if suspend { - return self.c.RunSubprocessAndRefresh( - self.c.OS().Cmd.NewShell(cmdStr, self.c.UserConfig().OS.ShellFunctionsFile), - ) - } - - return self.c.OS().Cmd.NewShell(cmdStr, self.c.UserConfig().OS.ShellFunctionsFile).Run() -} - -func (self *FilesHelper) OpenFile(filename string) error { - absPath, err := filepath.Abs(filename) - if err != nil { - return err - } - self.c.LogAction(self.c.Tr.Actions.OpenFile) - if err := self.c.OS().OpenFile(absPath); err != nil { - return err - } - return nil -} diff --git a/pkg/gui/controllers/helpers/fixup_helper.go b/pkg/gui/controllers/helpers/fixup_helper.go deleted file mode 100644 index f44c23da8b4..00000000000 --- a/pkg/gui/controllers/helpers/fixup_helper.go +++ /dev/null @@ -1,337 +0,0 @@ -package helpers - -import ( - "errors" - "fmt" - "regexp" - "strings" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "golang.org/x/sync/errgroup" -) - -type FixupHelper struct { - c *HelperCommon -} - -func NewFixupHelper( - c *HelperCommon, -) *FixupHelper { - return &FixupHelper{ - c: c, - } -} - -// hunk describes the lines in a diff hunk. Used for two distinct cases: -// -// - when the hunk contains some deleted lines. Because we're diffing with a -// context of 0, all deleted lines always come first, and then the added lines -// (if any). In this case, numLines is only the number of deleted lines, we -// ignore whether there are also some added lines in the hunk, as this is not -// relevant for our algorithm. -// -// - when the hunk contains only added lines, in which case (obviously) numLines -// is the number of added lines. -type hunk struct { - filename string - startLineIdx int - numLines int -} - -func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error { - diff, hasStagedChanges, err := self.getDiff() - if err != nil { - return err - } - - deletedLineHunks, addedLineHunks := parseDiff(diff) - - commits := self.c.Model().Commits - - var hashes []string - warnAboutAddedLines := false - - if len(deletedLineHunks) > 0 { - hashes, err = self.blameDeletedLines(deletedLineHunks) - warnAboutAddedLines = len(addedLineHunks) > 0 - } else if len(addedLineHunks) > 0 { - hashes, err = self.blameAddedLines(commits, addedLineHunks) - } else { - return errors.New(self.c.Tr.NoChangedFiles) - } - - if err != nil { - return err - } - - if len(hashes) == 0 { - // This should never happen - return errors.New(self.c.Tr.NoBaseCommitsFound) - } - - // If a commit can't be found, and the last known commit is already merged, - // we know that the commit we're looking for is also merged. Otherwise we - // can't tell. - notFoundMeansMerged := len(commits) > 0 && commits[len(commits)-1].Status == models.StatusMerged - - const ( - MERGED int = iota - NOT_MERGED - CANNOT_TELL - ) - - // Group the hashes into buckets by merged status - hashGroups := lo.GroupBy(hashes, func(hash string) int { - commit, _, ok := self.findCommit(commits, hash) - if ok { - return lo.Ternary(commit.Status == models.StatusMerged, MERGED, NOT_MERGED) - } - return lo.Ternary(notFoundMeansMerged, MERGED, CANNOT_TELL) - }) - - if len(hashGroups[CANNOT_TELL]) > 0 { - // If we have any commits that we can't tell if they're merged, just - // show the generic "not in current view" error. This can only happen if - // a feature branch has more than 300 commits, or there is no main - // branch. Both are so unlikely that we don't bother returning a more - // detailed error message (e.g. we could say something about the commits - // that *are* in the current branch, but it's not worth it). - return errors.New(self.c.Tr.BaseCommitIsNotInCurrentView) - } - - if len(hashGroups[NOT_MERGED]) == 0 { - // If all the commits are merged, show the "already on main branch" - // error. It isn't worth doing a detailed report of which commits we - // found. - return errors.New(self.c.Tr.BaseCommitIsAlreadyOnMainBranch) - } - - if len(hashGroups[NOT_MERGED]) > 1 { - // If there are multiple commits that could be the base commit, list - // them in the error message. But only the candidates from the current - // branch, not including any that are already merged. - subjects, err := self.c.Git().Commit.GetHashesAndCommitMessagesFirstLine(hashGroups[NOT_MERGED]) - if err != nil { - return err - } - message := lo.Ternary(hasStagedChanges, - self.c.Tr.MultipleBaseCommitsFoundStaged, - self.c.Tr.MultipleBaseCommitsFoundUnstaged) - return fmt.Errorf("%s\n\n%s", message, subjects) - } - - // At this point we know that the NOT_MERGED bucket has exactly one commit, - // and that's the one we want to select. - _, index, _ := self.findCommit(commits, hashGroups[NOT_MERGED][0]) - - return self.c.ConfirmIf(warnAboutAddedLines, types.ConfirmOpts{ - Title: self.c.Tr.FindBaseCommitForFixup, - Prompt: self.c.Tr.HunksWithOnlyAddedLinesWarning, - HandleConfirm: func() error { - if !hasStagedChanges { - if err := self.c.Git().WorkingTree.StageAll(true); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}}) - } - - self.c.Contexts().LocalCommits.SetSelection(index) - self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{}) - return nil - }, - }) -} - -func (self *FixupHelper) getDiff() (string, bool, error) { - args := []string{"-U0", "--ignore-submodules=all", "HEAD", "--"} - - // Try staged changes first - hasStagedChanges := true - diff, err := self.c.Git().Diff.DiffIndexCmdObj(append([]string{"--cached"}, args...)...).RunWithOutput() - - if err == nil && diff == "" { - hasStagedChanges = false - // If there are no staged changes, try unstaged changes - diff, err = self.c.Git().Diff.DiffIndexCmdObj(args...).RunWithOutput() - } - - return diff, hasStagedChanges, err -} - -// Parse the diff output into hunks, and return two lists of hunks: the first -// are ones that contain deleted lines, the second are ones that contain only -// added lines. -func parseDiff(diff string) ([]*hunk, []*hunk) { - lines := strings.Split(strings.TrimSuffix(diff, "\n"), "\n") - - deletedLineHunks := []*hunk{} - addedLineHunks := []*hunk{} - - hunkHeaderRegexp := regexp.MustCompile(`@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@`) - - var filename string - var currentHunk *hunk - numDeletedLines := 0 - numAddedLines := 0 - finishHunk := func() { - if currentHunk != nil { - if numDeletedLines > 0 { - currentHunk.numLines = numDeletedLines - deletedLineHunks = append(deletedLineHunks, currentHunk) - } else if numAddedLines > 0 { - currentHunk.numLines = numAddedLines - addedLineHunks = append(addedLineHunks, currentHunk) - } - } - numDeletedLines = 0 - numAddedLines = 0 - } - for _, line := range lines { - if strings.HasPrefix(line, "diff --git") { - finishHunk() - currentHunk = nil - } else if strings.HasPrefix(line, "--- ") { - // For some reason, the line ends with a tab character if the file - // name contains spaces - filename = strings.TrimRight(line[6:], "\t") - } else if strings.HasPrefix(line, "@@ ") { - finishHunk() - match := hunkHeaderRegexp.FindStringSubmatch(line) - startIdx := utils.MustConvertToInt(match[1]) - currentHunk = &hunk{filename, startIdx, 0} - } else if currentHunk != nil && line[0] == '-' { - numDeletedLines++ - } else if currentHunk != nil && line[0] == '+' { - numAddedLines++ - } - } - finishHunk() - - return deletedLineHunks, addedLineHunks -} - -// returns the list of commit hashes that introduced the lines which have now been deleted -func (self *FixupHelper) blameDeletedLines(deletedLineHunks []*hunk) ([]string, error) { - errg := errgroup.Group{} - hashChan := make(chan string) - - for _, h := range deletedLineHunks { - errg.Go(func() error { - blameOutput, err := self.c.Git().Blame.BlameLineRange(h.filename, "HEAD", h.startLineIdx, h.numLines) - if err != nil { - return err - } - blameLines := strings.Split(strings.TrimSuffix(blameOutput, "\n"), "\n") - for _, line := range blameLines { - hashChan <- strings.Split(line, " ")[0] - } - return nil - }) - } - - go func() { - // We don't care about the error here, we'll check it later (in the - // return statement below). Here we only wait for all the goroutines to - // finish so that we can close the channel. - _ = errg.Wait() - close(hashChan) - }() - - result := set.New[string]() - for hash := range hashChan { - result.Add(hash) - } - - return result.ToSlice(), errg.Wait() -} - -func (self *FixupHelper) blameAddedLines(commits []*models.Commit, addedLineHunks []*hunk) ([]string, error) { - errg := errgroup.Group{} - hashesChan := make(chan []string) - - for _, h := range addedLineHunks { - errg.Go(func() error { - result := make([]string, 0, 2) - - appendBlamedLine := func(blameOutput string) { - blameLines := strings.Split(strings.TrimSuffix(blameOutput, "\n"), "\n") - if len(blameLines) == 1 { - result = append(result, strings.Split(blameLines[0], " ")[0]) - } - } - - // Blame the line before this hunk, if there is one - if h.startLineIdx > 0 { - blameOutput, err := self.c.Git().Blame.BlameLineRange(h.filename, "HEAD", h.startLineIdx, 1) - if err != nil { - return err - } - appendBlamedLine(blameOutput) - } - - // Blame the line after this hunk. We don't know how many lines the - // file has, so we can't check if there is a line after the hunk; - // let the error tell us. - blameOutput, err := self.c.Git().Blame.BlameLineRange(h.filename, "HEAD", h.startLineIdx+1, 1) - if err != nil { - // If this fails, we're probably at the end of the file (we - // could have checked this beforehand, but it's expensive). If - // there was a line before this hunk, this is fine, we'll just - // return that one; if not, the hunk encompasses the entire - // file, and we can't blame the lines before and after the hunk. - // This is an error. - if h.startLineIdx == 0 { - return errors.New("Entire file") // TODO i18n - } - } else { - appendBlamedLine(blameOutput) - } - - hashesChan <- result - return nil - }) - } - - go func() { - // We don't care about the error here, we'll check it later (in the - // return statement below). Here we only wait for all the goroutines to - // finish so that we can close the channel. - _ = errg.Wait() - close(hashesChan) - }() - - result := set.New[string]() - for hashes := range hashesChan { - if len(hashes) == 1 { - result.Add(hashes[0]) - } else if len(hashes) > 1 { - if hashes[0] == hashes[1] { - result.Add(hashes[0]) - } else { - _, index1, ok1 := self.findCommit(commits, hashes[0]) - _, index2, ok2 := self.findCommit(commits, hashes[1]) - if ok1 && ok2 { - result.Add(lo.Ternary(index1 < index2, hashes[0], hashes[1])) - } else if ok1 { - result.Add(hashes[0]) - } else if ok2 { - result.Add(hashes[1]) - } else { - return nil, errors.New(self.c.Tr.NoBaseCommitsFound) - } - } - } - } - - return result.ToSlice(), errg.Wait() -} - -func (self *FixupHelper) findCommit(commits []*models.Commit, hash string) (*models.Commit, int, bool) { - return lo.FindIndexOf(commits, func(commit *models.Commit) bool { - return commit.Hash() == hash - }) -} diff --git a/pkg/gui/controllers/helpers/fixup_helper_test.go b/pkg/gui/controllers/helpers/fixup_helper_test.go deleted file mode 100644 index 954161cd16a..00000000000 --- a/pkg/gui/controllers/helpers/fixup_helper_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package helpers - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestFixupHelper_parseDiff(t *testing.T) { - scenarios := []struct { - name string - diff string - expectedDeletedLineHunks []*hunk - expectedAddedLineHunks []*hunk - }{ - { - name: "no diff", - diff: "", - expectedDeletedLineHunks: []*hunk{}, - expectedAddedLineHunks: []*hunk{}, - }, - { - name: "hunk with only deleted lines", - diff: ` -diff --git a/file1.txt b/file1.txt -index 9ce8efb33..aaf2a4666 100644 ---- a/file1.txt -+++ b/file1.txt -@@ -3 +2,0 @@ bbb --xxx -`, - expectedDeletedLineHunks: []*hunk{ - { - filename: "file1.txt", - startLineIdx: 3, - numLines: 1, - }, - }, - expectedAddedLineHunks: []*hunk{}, - }, - { - name: "hunk with deleted and added lines", - diff: ` -diff --git a/file1.txt b/file1.txt -index 9ce8efb33..eb246cf98 100644 ---- a/file1.txt -+++ b/file1.txt -@@ -3 +3 @@ bbb --xxx -+yyy -`, - expectedDeletedLineHunks: []*hunk{ - { - filename: "file1.txt", - startLineIdx: 3, - numLines: 1, - }, - }, - expectedAddedLineHunks: []*hunk{}, - }, - { - name: "hunk with only added lines", - diff: ` -diff --git a/file1.txt b/file1.txt -index 9ce8efb33..fb5e469e7 100644 ---- a/file1.txt -+++ b/file1.txt -@@ -4,0 +5,2 @@ ddd -+xxx -+yyy -`, - expectedDeletedLineHunks: []*hunk{}, - expectedAddedLineHunks: []*hunk{ - { - filename: "file1.txt", - startLineIdx: 4, - numLines: 2, - }, - }, - }, - { - name: "several hunks in different files", - diff: ` -diff --git a/file1.txt b/file1.txt -index 9ce8efb33..0632e41b0 100644 ---- a/file1.txt -+++ b/file1.txt -@@ -2 +1,0 @@ aaa --bbb -@@ -4 +3 @@ ccc --ddd -+xxx -@@ -6,0 +6 @@ fff -+zzz -diff --git a/file2.txt b/file2.txt -index 9ce8efb33..0632e41b0 100644 ---- a/file2.txt -+++ b/file2.txt -@@ -0,3 +1,0 @@ aaa --aaa --bbb --ccc -`, - expectedDeletedLineHunks: []*hunk{ - { - filename: "file1.txt", - startLineIdx: 2, - numLines: 1, - }, - { - filename: "file1.txt", - startLineIdx: 4, - numLines: 1, - }, - { - filename: "file2.txt", - startLineIdx: 0, - numLines: 3, - }, - }, - expectedAddedLineHunks: []*hunk{ - { - filename: "file1.txt", - startLineIdx: 6, - numLines: 1, - }, - }, - }, - } - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - deletedLineHunks, addedLineHunks := parseDiff(s.diff) - assert.Equal(t, s.expectedDeletedLineHunks, deletedLineHunks) - assert.Equal(t, s.expectedAddedLineHunks, addedLineHunks) - }) - } -} diff --git a/pkg/gui/controllers/helpers/gpg_helper.go b/pkg/gui/controllers/helpers/gpg_helper.go deleted file mode 100644 index afac52f13a1..00000000000 --- a/pkg/gui/controllers/helpers/gpg_helper.go +++ /dev/null @@ -1,61 +0,0 @@ -package helpers - -import ( - "fmt" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type GpgHelper struct { - c *HelperCommon -} - -func NewGpgHelper(c *HelperCommon) *GpgHelper { - return &GpgHelper{ - c: c, - } -} - -// Currently there is a bug where if we switch to a subprocess from within -// WithWaitingStatus we get stuck there and can't return to lazygit. We could -// fix this bug, or just stop running subprocesses from within there, given that -// we don't need to see a loading status if we're in a subprocess. -func (self *GpgHelper) WithGpgHandling(cmdObj *oscommands.CmdObj, configKey git_commands.GpgConfigKey, waitingStatus string, onSuccess func() error, refreshScope []types.RefreshableView) error { - useSubprocess := self.c.Git().Config.NeedsGpgSubprocess(configKey) - if useSubprocess { - success, err := self.c.RunSubprocess(cmdObj) - if success && onSuccess != nil { - if err := onSuccess(); err != nil { - return err - } - } - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope}) - - return err - } - - return self.runAndStream(cmdObj, waitingStatus, onSuccess, refreshScope) -} - -func (self *GpgHelper) runAndStream(cmdObj *oscommands.CmdObj, waitingStatus string, onSuccess func() error, refreshScope []types.RefreshableView) error { - return self.c.WithWaitingStatus(waitingStatus, func(gocui.Task) error { - if err := cmdObj.StreamOutput().Run(); err != nil { - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope}) - return fmt.Errorf( - self.c.Tr.GitCommandFailed, self.c.UserConfig().Keybinding.Universal.ExtrasMenu, - ) - } - - if onSuccess != nil { - if err := onSuccess(); err != nil { - return err - } - } - - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope}) - return nil - }) -} diff --git a/pkg/gui/controllers/helpers/helpers.go b/pkg/gui/controllers/helpers/helpers.go deleted file mode 100644 index 4c9c79f3d81..00000000000 --- a/pkg/gui/controllers/helpers/helpers.go +++ /dev/null @@ -1,94 +0,0 @@ -package helpers - -import ( - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type HelperCommon struct { - *common.Common - types.IGuiCommon - IGetContexts -} - -type IGetContexts interface { - Contexts() *context.ContextTree -} - -type Helpers struct { - Refs *RefsHelper - Bisect *BisectHelper - Suggestions *SuggestionsHelper - Files *FilesHelper - WorkingTree *WorkingTreeHelper - BranchesHelper *BranchesHelper - Tags *TagsHelper - MergeAndRebase *MergeAndRebaseHelper - MergeConflicts *MergeConflictsHelper - CherryPick *CherryPickHelper - Host *HostHelper - PatchBuilding *PatchBuildingHelper - Staging *StagingHelper - GPG *GpgHelper - Upstream *UpstreamHelper - AmendHelper *AmendHelper - FixupHelper *FixupHelper - Commits *CommitsHelper - SuspendResume *SuspendResumeHelper - Snake *SnakeHelper - // lives in context package because our contexts need it to render to main - Diff *DiffHelper - Repos *ReposHelper - RecordDirectory *RecordDirectoryHelper - Update *UpdateHelper - Window *WindowHelper - View *ViewHelper - Refresh *RefreshHelper - Confirmation *ConfirmationHelper - Mode *ModeHelper - AppStatus *AppStatusHelper - InlineStatus *InlineStatusHelper - WindowArrangement *WindowArrangementHelper - Search *SearchHelper - Worktree *WorktreeHelper - SubCommits *SubCommitsHelper -} - -func NewStubHelpers() *Helpers { - return &Helpers{ - Refs: &RefsHelper{}, - Bisect: &BisectHelper{}, - Suggestions: &SuggestionsHelper{}, - Files: &FilesHelper{}, - WorkingTree: &WorkingTreeHelper{}, - Tags: &TagsHelper{}, - MergeAndRebase: &MergeAndRebaseHelper{}, - MergeConflicts: &MergeConflictsHelper{}, - CherryPick: &CherryPickHelper{}, - Host: &HostHelper{}, - PatchBuilding: &PatchBuildingHelper{}, - Staging: &StagingHelper{}, - GPG: &GpgHelper{}, - Upstream: &UpstreamHelper{}, - AmendHelper: &AmendHelper{}, - FixupHelper: &FixupHelper{}, - Commits: &CommitsHelper{}, - Snake: &SnakeHelper{}, - Diff: &DiffHelper{}, - Repos: &ReposHelper{}, - RecordDirectory: &RecordDirectoryHelper{}, - Update: &UpdateHelper{}, - Window: &WindowHelper{}, - View: &ViewHelper{}, - Refresh: &RefreshHelper{}, - Confirmation: &ConfirmationHelper{}, - Mode: &ModeHelper{}, - AppStatus: &AppStatusHelper{}, - InlineStatus: &InlineStatusHelper{}, - WindowArrangement: &WindowArrangementHelper{}, - Search: &SearchHelper{}, - Worktree: &WorktreeHelper{}, - SubCommits: &SubCommitsHelper{}, - } -} diff --git a/pkg/gui/controllers/helpers/host_helper.go b/pkg/gui/controllers/helpers/host_helper.go deleted file mode 100644 index 42115e86f82..00000000000 --- a/pkg/gui/controllers/helpers/host_helper.go +++ /dev/null @@ -1,46 +0,0 @@ -package helpers - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/hosting_service" -) - -// this helper just wraps our hosting_service package - -type HostHelper struct { - c *HelperCommon -} - -func NewHostHelper( - c *HelperCommon, -) *HostHelper { - return &HostHelper{ - c: c, - } -} - -func (self *HostHelper) GetPullRequestURL(from string, to string) (string, error) { - mgr, err := self.getHostingServiceMgr() - if err != nil { - return "", err - } - return mgr.GetPullRequestURL(from, to) -} - -func (self *HostHelper) GetCommitURL(commitHash string) (string, error) { - mgr, err := self.getHostingServiceMgr() - if err != nil { - return "", err - } - return mgr.GetCommitURL(commitHash) -} - -// getting this on every request rather than storing it in state in case our remoteURL changes -// from one invocation to the next. -func (self *HostHelper) getHostingServiceMgr() (*hosting_service.HostingServiceMgr, error) { - remoteUrl, err := self.c.Git().Remote.GetRemoteURL("origin") - if err != nil { - return nil, err - } - configServices := self.c.UserConfig().Services - return hosting_service.NewHostingServiceMgr(self.c.Log, self.c.Tr, remoteUrl, configServices), nil -} diff --git a/pkg/gui/controllers/helpers/inline_status_helper.go b/pkg/gui/controllers/helpers/inline_status_helper.go deleted file mode 100644 index 38a4e2cf7f2..00000000000 --- a/pkg/gui/controllers/helpers/inline_status_helper.go +++ /dev/null @@ -1,156 +0,0 @@ -package helpers - -import ( - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/sasha-s/go-deadlock" -) - -type InlineStatusHelper struct { - c *HelperCommon - - windowHelper *WindowHelper - contextsWithInlineStatus map[types.ContextKey]*inlineStatusInfo - mutex deadlock.Mutex -} - -func NewInlineStatusHelper(c *HelperCommon, windowHelper *WindowHelper) *InlineStatusHelper { - return &InlineStatusHelper{ - c: c, - windowHelper: windowHelper, - contextsWithInlineStatus: make(map[types.ContextKey]*inlineStatusInfo), - } -} - -type InlineStatusOpts struct { - Item types.HasUrn - Operation types.ItemOperation - ContextKey types.ContextKey -} - -type inlineStatusInfo struct { - refCount int - stop chan struct{} -} - -// A custom task for WithInlineStatus calls; it wraps the original one and -// hides the status whenever the task is paused, and shows it again when -// continued. -type inlineStatusHelperTask struct { - gocui.Task - - inlineStatusHelper *InlineStatusHelper - opts InlineStatusOpts -} - -// poor man's version of explicitly saying that struct X implements interface Y -var _ gocui.Task = inlineStatusHelperTask{} - -func (self inlineStatusHelperTask) Pause() { - self.inlineStatusHelper.stop(self.opts) - self.Task.Pause() - - self.inlineStatusHelper.renderContext(self.opts.ContextKey) -} - -func (self inlineStatusHelperTask) Continue() { - self.Task.Continue() - self.inlineStatusHelper.start(self.opts) -} - -func (self *InlineStatusHelper) WithInlineStatus(opts InlineStatusOpts, f func(gocui.Task) error) { - context := self.c.ContextForKey(opts.ContextKey).(types.IListContext) - view := context.GetView() - visible := view.Visible && self.windowHelper.TopViewInWindow(context.GetWindowName(), false) == view - if visible && context.IsItemVisible(opts.Item) { - self.c.OnWorker(func(task gocui.Task) error { - self.start(opts) - defer self.stop(opts) - - return f(inlineStatusHelperTask{task, self, opts}) - }) - } else { - message := presentation.ItemOperationToString(opts.Operation, self.c.Tr) - _ = self.c.WithWaitingStatus(message, func(t gocui.Task) error { - // We still need to set the item operation, because it might be used - // for other (non-presentation) purposes - self.c.State().SetItemOperation(opts.Item, opts.Operation) - defer self.c.State().ClearItemOperation(opts.Item) - - return f(t) - }) - } -} - -func (self *InlineStatusHelper) start(opts InlineStatusOpts) { - self.c.State().SetItemOperation(opts.Item, opts.Operation) - - self.mutex.Lock() - defer self.mutex.Unlock() - - info := self.contextsWithInlineStatus[opts.ContextKey] - if info == nil { - info = &inlineStatusInfo{refCount: 0, stop: make(chan struct{})} - self.contextsWithInlineStatus[opts.ContextKey] = info - - go utils.Safe(func() { - ticker := time.NewTicker(time.Millisecond * time.Duration(self.c.UserConfig().Gui.Spinner.Rate)) - defer ticker.Stop() - outer: - for { - select { - case <-ticker.C: - self.renderContext(opts.ContextKey) - case <-info.stop: - break outer - } - } - }) - } - - info.refCount++ -} - -func (self *InlineStatusHelper) stop(opts InlineStatusOpts) { - self.mutex.Lock() - - if info := self.contextsWithInlineStatus[opts.ContextKey]; info != nil { - info.refCount-- - if info.refCount <= 0 { - info.stop <- struct{}{} - delete(self.contextsWithInlineStatus, opts.ContextKey) - } - } - - self.mutex.Unlock() - - self.c.State().ClearItemOperation(opts.Item) - - // When recording a demo we need to re-render the context again here to - // remove the inline status. In normal usage we don't want to do this - // because in the case of pushing a branch this would first reveal the ↑3↓7 - // status from before the push for a brief moment, to be replaced by a green - // checkmark a moment later when the async refresh is done. This looks - // jarring, so normally we rely on the async refresh to redraw with the - // status removed. (In some rare cases, where there's no refresh at all, we - // need to redraw manually in the controller; see TagsController.push() for - // an example.) - // - // In demos, however, we turn all async refreshes into sync ones, because - // this looks better in demos. In this case the refresh happens while the - // status is still set, so we need to render again after removing it. - if self.c.InDemo() { - self.renderContext(opts.ContextKey) - } -} - -func (self *InlineStatusHelper) renderContext(contextKey types.ContextKey) { - self.c.OnUIThread(func() error { - self.c.ContextForKey(contextKey).HandleRender() - return nil - }) -} diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go deleted file mode 100644 index 7d4f0a96a7d..00000000000 --- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go +++ /dev/null @@ -1,556 +0,0 @@ -package helpers - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/stefanhaller/git-todo-parser/todo" -) - -type MergeAndRebaseHelper struct { - c *HelperCommon -} - -func NewMergeAndRebaseHelper( - c *HelperCommon, -) *MergeAndRebaseHelper { - return &MergeAndRebaseHelper{ - c: c, - } -} - -type RebaseOption string - -const ( - REBASE_OPTION_CONTINUE string = "continue" - REBASE_OPTION_ABORT string = "abort" - REBASE_OPTION_SKIP string = "skip" -) - -func (self *MergeAndRebaseHelper) CreateRebaseOptionsMenu() error { - type optionAndKey struct { - option string - key types.Key - } - - options := []optionAndKey{ - {option: REBASE_OPTION_CONTINUE, key: 'c'}, - {option: REBASE_OPTION_ABORT, key: 'a'}, - } - - if self.c.Git().Status.WorkingTreeState().CanSkip() { - options = append(options, optionAndKey{ - option: REBASE_OPTION_SKIP, key: 's', - }) - } - - menuItems := lo.Map(options, func(row optionAndKey, _ int) *types.MenuItem { - return &types.MenuItem{ - Label: row.option, - OnPress: func() error { - return self.genericMergeCommand(row.option) - }, - Key: row.key, - } - }) - - title := self.c.Git().Status.WorkingTreeState().OptionsMenuTitle(self.c.Tr) - return self.c.Menu(types.CreateMenuOptions{Title: title, Items: menuItems}) -} - -func (self *MergeAndRebaseHelper) ContinueRebase() error { - return self.genericMergeCommand(REBASE_OPTION_CONTINUE) -} - -func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error { - status := self.c.Git().Status.WorkingTreeState() - - if status.None() { - return errors.New(self.c.Tr.NotMergingOrRebasing) - } - - self.c.LogAction(fmt.Sprintf("Merge/Rebase: %s", command)) - effectiveStatus := status.Effective() - if effectiveStatus == models.WORKING_TREE_STATE_REBASING { - todoFile, err := os.ReadFile( - filepath.Join(self.c.Git().RepoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"), - ) - - if err != nil { - if !os.IsNotExist(err) { - return err - } - } else { - self.c.LogCommand(string(todoFile), false) - } - } - - commandType := status.CommandName() - - // we should end up with a command like 'git merge --continue' - - // it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge - needsSubprocess := (effectiveStatus == models.WORKING_TREE_STATE_MERGING && command != REBASE_OPTION_ABORT && self.c.UserConfig().Git.Merging.ManualCommit) || - // but we'll also use a subprocess if we have exec todos; those are likely to be lengthy build - // tasks whose output the user will want to see in the terminal - (effectiveStatus == models.WORKING_TREE_STATE_REBASING && command != REBASE_OPTION_ABORT && self.hasExecTodos()) - - if needsSubprocess { - // TODO: see if we should be calling more of the code from self.Git.Rebase.GenericMergeOrRebaseAction - return self.c.RunSubprocessAndRefresh( - self.c.Git().Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command), - ) - } - result := self.c.Git().Rebase.GenericMergeOrRebaseAction(commandType, command) - if err := self.CheckMergeOrRebase(result); err != nil { - return err - } - return nil -} - -func (self *MergeAndRebaseHelper) hasExecTodos() bool { - for _, commit := range self.c.Model().Commits { - if !commit.IsTODO() { - break - } - if commit.Action == todo.Exec { - return true - } - } - return false -} - -var conflictStrings = []string{ - "Failed to merge in the changes", - "When you have resolved this problem", - "fix conflicts", - "Resolve all conflicts manually", - "Merge conflict in file", - "hint: after resolving the conflicts", - "CONFLICT (content):", -} - -func isMergeConflictErr(errStr string) bool { - for _, str := range conflictStrings { - if strings.Contains(errStr, str) { - return true - } - } - - return false -} - -func (self *MergeAndRebaseHelper) CheckMergeOrRebaseWithRefreshOptions(result error, refreshOptions types.RefreshOptions) error { - self.c.Refresh(refreshOptions) - - if result == nil { - return nil - } else if strings.Contains(result.Error(), "No changes - did you forget to use") { - return self.genericMergeCommand(REBASE_OPTION_SKIP) - } else if strings.Contains(result.Error(), "The previous cherry-pick is now empty") { - return self.genericMergeCommand(REBASE_OPTION_SKIP) - } else if strings.Contains(result.Error(), "No rebase in progress?") { - // assume in this case that we're already done - return nil - } - return self.CheckForConflicts(result) -} - -func (self *MergeAndRebaseHelper) CheckMergeOrRebase(result error) error { - return self.CheckMergeOrRebaseWithRefreshOptions(result, types.RefreshOptions{Mode: types.ASYNC}) -} - -func (self *MergeAndRebaseHelper) CheckForConflicts(result error) error { - if result == nil { - return nil - } - - if isMergeConflictErr(result.Error()) { - return self.PromptForConflictHandling() - } - - return result -} - -func (self *MergeAndRebaseHelper) PromptForConflictHandling() error { - mode := self.c.Git().Status.WorkingTreeState().CommandName() - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.FoundConflictsTitle, - Items: []*types.MenuItem{ - { - Label: self.c.Tr.ViewConflictsMenuItem, - OnPress: func() error { - self.c.Context().Push(self.c.Contexts().Files, types.OnFocusOpts{}) - return nil - }, - }, - { - Label: fmt.Sprintf(self.c.Tr.AbortMenuItem, mode), - OnPress: func() error { - return self.genericMergeCommand(REBASE_OPTION_ABORT) - }, - Key: 'a', - }, - }, - HideCancel: true, - }) -} - -func (self *MergeAndRebaseHelper) AbortMergeOrRebaseWithConfirm() error { - // prompt user to confirm that they want to abort, then do it - mode := self.c.Git().Status.WorkingTreeState().CommandName() - self.c.Confirm(types.ConfirmOpts{ - Title: fmt.Sprintf(self.c.Tr.AbortTitle, mode), - Prompt: fmt.Sprintf(self.c.Tr.AbortPrompt, mode), - HandleConfirm: func() error { - return self.genericMergeCommand(REBASE_OPTION_ABORT) - }, - }) - - return nil -} - -// PromptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress -func (self *MergeAndRebaseHelper) PromptToContinueRebase() error { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Continue, - Prompt: fmt.Sprintf(self.c.Tr.ConflictsResolved, self.c.Git().Status.WorkingTreeState().CommandName()), - HandleConfirm: func() error { - // By the time we get here, we might have unstaged changes again, - // e.g. if the user had to fix build errors after resolving the - // conflicts, but after lazygit opened the prompt already. Ask again - // to auto-stage these. - - // Need to refresh the files to be really sure if this is the case. - // We would otherwise be relying on lazygit's auto-refresh on focus, - // but this is not supported by all terminals or on all platforms. - self.c.Refresh(types.RefreshOptions{ - Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}, - }) - - root := self.c.Contexts().Files.FileTreeViewModel.GetRoot() - if root.GetHasUnstagedChanges() { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Continue, - Prompt: self.c.Tr.UnstagedFilesAfterConflictsResolved, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.StageAllFiles) - if err := self.c.Git().WorkingTree.StageAll(true); err != nil { - return err - } - - return self.genericMergeCommand(REBASE_OPTION_CONTINUE) - }, - }) - - return nil - } - - return self.genericMergeCommand(REBASE_OPTION_CONTINUE) - }, - }) - - return nil -} - -func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error { - checkedOutBranch := self.c.Model().Branches[0] - checkedOutBranchName := checkedOutBranch.Name - var disabledReason, baseBranchDisabledReason *types.DisabledReason - if checkedOutBranchName == ref { - disabledReason = &types.DisabledReason{Text: self.c.Tr.CantRebaseOntoSelf} - } - - baseBranch, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(checkedOutBranch, self.c.Model().MainBranches) - if err != nil { - return err - } - if baseBranch == "" { - baseBranch = self.c.Tr.CouldNotDetermineBaseBranch - baseBranchDisabledReason = &types.DisabledReason{Text: self.c.Tr.CouldNotDetermineBaseBranch} - } - - menuItems := []*types.MenuItem{ - { - Label: utils.ResolvePlaceholderString(self.c.Tr.SimpleRebase, - map[string]string{"ref": ref}, - ), - Key: 's', - DisabledReason: disabledReason, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.RebaseBranch) - return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error { - baseCommit := self.c.Modes().MarkedBaseCommit.GetHash() - var err error - if baseCommit != "" { - err = self.c.Git().Rebase.RebaseBranchFromBaseCommit(ref, baseCommit) - } else { - err = self.c.Git().Rebase.RebaseBranch(ref) - } - err = self.CheckMergeOrRebase(err) - if err == nil { - return self.ResetMarkedBaseCommit() - } - return err - }) - }, - }, - { - Label: utils.ResolvePlaceholderString(self.c.Tr.InteractiveRebase, - map[string]string{"ref": ref}, - ), - Key: 'i', - DisabledReason: disabledReason, - Tooltip: self.c.Tr.InteractiveRebaseTooltip, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.RebaseBranch) - baseCommit := self.c.Modes().MarkedBaseCommit.GetHash() - var err error - if baseCommit != "" { - err = self.c.Git().Rebase.EditRebaseFromBaseCommit(ref, baseCommit) - } else { - err = self.c.Git().Rebase.EditRebase(ref) - } - if err = self.CheckMergeOrRebase(err); err != nil { - return err - } - if err = self.ResetMarkedBaseCommit(); err != nil { - return err - } - self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{}) - return nil - }, - }, - { - Label: utils.ResolvePlaceholderString(self.c.Tr.RebaseOntoBaseBranch, - map[string]string{"baseBranch": ShortBranchName(baseBranch)}, - ), - Key: 'b', - DisabledReason: baseBranchDisabledReason, - Tooltip: self.c.Tr.RebaseOntoBaseBranchTooltip, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.RebaseBranch) - return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error { - baseCommit := self.c.Modes().MarkedBaseCommit.GetHash() - var err error - if baseCommit != "" { - err = self.c.Git().Rebase.RebaseBranchFromBaseCommit(baseBranch, baseCommit) - } else { - err = self.c.Git().Rebase.RebaseBranch(baseBranch) - } - err = self.CheckMergeOrRebase(err) - if err == nil { - return self.ResetMarkedBaseCommit() - } - return err - }) - }, - }, - } - - title := utils.ResolvePlaceholderString( - lo.Ternary(self.c.Modes().MarkedBaseCommit.GetHash() != "", - self.c.Tr.RebasingFromBaseCommitTitle, - self.c.Tr.RebasingTitle), - map[string]string{ - "checkedOutBranch": checkedOutBranchName, - }, - ) - - return self.c.Menu(types.CreateMenuOptions{ - Title: title, - Items: menuItems, - }) -} - -func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) error { - if self.c.Git().Branch.IsHeadDetached() { - return errors.New("Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on") - } - checkedOutBranchName := self.c.Model().Branches[0].Name - if checkedOutBranchName == refName { - return errors.New(self.c.Tr.CantMergeBranchIntoItself) - } - - wantFastForward, wantNonFastForward := self.fastForwardMergeUserPreference() - canFastForward := self.c.Git().Branch.CanDoFastForwardMerge(refName) - - var firstRegularMergeItem *types.MenuItem - var secondRegularMergeItem *types.MenuItem - var fastForwardMergeItem *types.MenuItem - - if !wantNonFastForward && (wantFastForward || canFastForward) { - firstRegularMergeItem = &types.MenuItem{ - Label: self.c.Tr.RegularMergeFastForward, - OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_REGULAR), - Key: 'm', - Tooltip: utils.ResolvePlaceholderString( - self.c.Tr.RegularMergeFastForwardTooltip, - map[string]string{ - "checkedOutBranch": checkedOutBranchName, - "selectedBranch": refName, - }, - ), - } - fastForwardMergeItem = firstRegularMergeItem - - secondRegularMergeItem = &types.MenuItem{ - Label: self.c.Tr.RegularMergeNonFastForward, - OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_NON_FAST_FORWARD), - Key: 'n', - Tooltip: utils.ResolvePlaceholderString( - self.c.Tr.RegularMergeNonFastForwardTooltip, - map[string]string{ - "checkedOutBranch": checkedOutBranchName, - "selectedBranch": refName, - }, - ), - } - } else { - firstRegularMergeItem = &types.MenuItem{ - Label: self.c.Tr.RegularMergeNonFastForward, - OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_REGULAR), - Key: 'm', - Tooltip: utils.ResolvePlaceholderString( - self.c.Tr.RegularMergeNonFastForwardTooltip, - map[string]string{ - "checkedOutBranch": checkedOutBranchName, - "selectedBranch": refName, - }, - ), - } - - secondRegularMergeItem = &types.MenuItem{ - Label: self.c.Tr.RegularMergeFastForward, - OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_FAST_FORWARD), - Key: 'f', - Tooltip: utils.ResolvePlaceholderString( - self.c.Tr.RegularMergeFastForwardTooltip, - map[string]string{ - "checkedOutBranch": checkedOutBranchName, - "selectedBranch": refName, - }, - ), - } - fastForwardMergeItem = secondRegularMergeItem - } - - if !canFastForward { - fastForwardMergeItem.DisabledReason = &types.DisabledReason{ - Text: utils.ResolvePlaceholderString( - self.c.Tr.CannotFastForwardMerge, - map[string]string{ - "checkedOutBranch": checkedOutBranchName, - "selectedBranch": refName, - }, - ), - } - } - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.Merge, - Items: []*types.MenuItem{ - firstRegularMergeItem, - secondRegularMergeItem, - { - Label: self.c.Tr.SquashMergeUncommitted, - OnPress: self.SquashMergeUncommitted(refName), - Key: 's', - Tooltip: utils.ResolvePlaceholderString( - self.c.Tr.SquashMergeUncommittedTooltip, - map[string]string{ - "selectedBranch": refName, - }, - ), - }, - { - Label: self.c.Tr.SquashMergeCommitted, - OnPress: self.SquashMergeCommitted(refName, checkedOutBranchName), - Key: 'S', - Tooltip: utils.ResolvePlaceholderString( - self.c.Tr.SquashMergeCommittedTooltip, - map[string]string{ - "checkedOutBranch": checkedOutBranchName, - "selectedBranch": refName, - }, - ), - }, - }, - }) -} - -func (self *MergeAndRebaseHelper) RegularMerge(refName string, variant git_commands.MergeVariant) func() error { - return func() error { - self.c.LogAction(self.c.Tr.Actions.Merge) - err := self.c.Git().Branch.Merge(refName, variant) - return self.CheckMergeOrRebase(err) - } -} - -func (self *MergeAndRebaseHelper) SquashMergeUncommitted(refName string) func() error { - return func() error { - self.c.LogAction(self.c.Tr.Actions.SquashMerge) - err := self.c.Git().Branch.Merge(refName, git_commands.MERGE_VARIANT_SQUASH) - return self.CheckMergeOrRebase(err) - } -} - -func (self *MergeAndRebaseHelper) SquashMergeCommitted(refName, checkedOutBranchName string) func() error { - return func() error { - self.c.LogAction(self.c.Tr.Actions.SquashMerge) - err := self.c.Git().Branch.Merge(refName, git_commands.MERGE_VARIANT_SQUASH) - if err = self.CheckMergeOrRebase(err); err != nil { - return err - } - message := utils.ResolvePlaceholderString(self.c.UserConfig().Git.Merging.SquashMergeMessage, map[string]string{ - "selectedRef": refName, - "currentBranch": checkedOutBranchName, - }) - err = self.c.Git().Commit.CommitCmdObj(message, "", false).Run() - if err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - } -} - -// Returns wantsFastForward, wantsNonFastForward. These will never both be true, but they can both be false. -func (self *MergeAndRebaseHelper) fastForwardMergeUserPreference() (bool, bool) { - // Check user config first, because it takes precedence over git config - mergingArgs := self.c.UserConfig().Git.Merging.Args - if strings.Contains(mergingArgs, "--ff") { // also covers "--ff-only" - return true, false - } - - if strings.Contains(mergingArgs, "--no-ff") { - return false, true - } - - // Then check git config - mergeFfConfig := self.c.Git().Config.GetMergeFF() - if mergeFfConfig == "true" || mergeFfConfig == "only" { - return true, false - } - - if mergeFfConfig == "false" { - return false, true - } - - return false, false -} - -func (self *MergeAndRebaseHelper) ResetMarkedBaseCommit() error { - self.c.Modes().MarkedBaseCommit.Reset() - self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits) - return nil -} diff --git a/pkg/gui/controllers/helpers/merge_conflicts_helper.go b/pkg/gui/controllers/helpers/merge_conflicts_helper.go deleted file mode 100644 index 6e6a01531ec..00000000000 --- a/pkg/gui/controllers/helpers/merge_conflicts_helper.go +++ /dev/null @@ -1,141 +0,0 @@ -package helpers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type MergeConflictsHelper struct { - c *HelperCommon -} - -func NewMergeConflictsHelper( - c *HelperCommon, -) *MergeConflictsHelper { - return &MergeConflictsHelper{ - c: c, - } -} - -func (self *MergeConflictsHelper) SetMergeState(path string) (bool, error) { - self.context().GetMutex().Lock() - defer self.context().GetMutex().Unlock() - - return self.setMergeStateWithoutLock(path) -} - -func (self *MergeConflictsHelper) setMergeStateWithoutLock(path string) (bool, error) { - content, err := self.c.Git().File.Cat(path) - if err != nil { - return false, err - } - - if path != self.context().GetState().GetPath() { - self.context().SetUserScrolling(false) - } - - self.context().GetState().SetContent(content, path) - - return !self.context().GetState().NoConflicts(), nil -} - -func (self *MergeConflictsHelper) ResetMergeState() { - self.context().GetMutex().Lock() - defer self.context().GetMutex().Unlock() - - self.resetMergeState() -} - -func (self *MergeConflictsHelper) resetMergeState() { - self.context().SetUserScrolling(false) - self.context().GetState().Reset() -} - -func (self *MergeConflictsHelper) EscapeMerge() error { - self.resetMergeState() - - // doing this in separate UI thread so that we're not still holding the lock by the time refresh the file - self.c.OnUIThread(func() error { - // There is a race condition here: refreshing the files scope can trigger the - // confirmation context to be pushed if all conflicts are resolved (prompting - // to continue the merge/rebase. In that case, we don't want to then push the - // files context over it. - // So long as both places call OnUIThread, we're fine. - if self.c.Context().IsCurrent(self.c.Contexts().MergeConflicts) { - self.c.Context().Push(self.c.Contexts().Files, types.OnFocusOpts{}) - } - return nil - }) - return nil -} - -func (self *MergeConflictsHelper) SetConflictsAndRender(path string) (bool, error) { - hasConflicts, err := self.setMergeStateWithoutLock(path) - if err != nil { - return false, err - } - - if hasConflicts { - return true, self.context().Render() - } - - return false, nil -} - -func (self *MergeConflictsHelper) SwitchToMerge(path string) error { - if self.context().GetState().GetPath() != path { - hasConflicts, err := self.SetMergeState(path) - if err != nil { - return err - } - if !hasConflicts { - return nil - } - } - - self.c.Context().Push(self.c.Contexts().MergeConflicts, types.OnFocusOpts{}) - return nil -} - -func (self *MergeConflictsHelper) context() *context.MergeConflictsContext { - return self.c.Contexts().MergeConflicts -} - -func (self *MergeConflictsHelper) Render() { - content := self.context().GetContentToRender() - - var task types.UpdateTask - if self.context().IsUserScrolling() { - task = types.NewRenderStringWithoutScrollTask(content) - } else { - originY := self.context().GetOriginY() - task = types.NewRenderStringWithScrollTask(content, 0, originY) - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().MergeConflicts, - Main: &types.ViewUpdateOpts{ - Task: task, - }, - }) -} - -func (self *MergeConflictsHelper) RefreshMergeState() error { - self.c.Contexts().MergeConflicts.GetMutex().Lock() - defer self.c.Contexts().MergeConflicts.GetMutex().Unlock() - - if self.c.Context().Current().GetKey() != context.MERGE_CONFLICTS_CONTEXT_KEY { - return nil - } - - hasConflicts, err := self.SetConflictsAndRender(self.c.Contexts().MergeConflicts.GetState().GetPath()) - if err != nil { - return err - } - - if !hasConflicts { - return self.EscapeMerge() - } - - return nil -} diff --git a/pkg/gui/controllers/helpers/mode_helper.go b/pkg/gui/controllers/helpers/mode_helper.go deleted file mode 100644 index e44d7b01bcd..00000000000 --- a/pkg/gui/controllers/helpers/mode_helper.go +++ /dev/null @@ -1,223 +0,0 @@ -package helpers - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type ModeHelper struct { - c *HelperCommon - - diffHelper *DiffHelper - patchBuildingHelper *PatchBuildingHelper - cherryPickHelper *CherryPickHelper - mergeAndRebaseHelper *MergeAndRebaseHelper - bisectHelper *BisectHelper - suppressRebasingMode bool -} - -func NewModeHelper( - c *HelperCommon, - diffHelper *DiffHelper, - patchBuildingHelper *PatchBuildingHelper, - cherryPickHelper *CherryPickHelper, - mergeAndRebaseHelper *MergeAndRebaseHelper, - bisectHelper *BisectHelper, -) *ModeHelper { - return &ModeHelper{ - c: c, - diffHelper: diffHelper, - patchBuildingHelper: patchBuildingHelper, - cherryPickHelper: cherryPickHelper, - mergeAndRebaseHelper: mergeAndRebaseHelper, - bisectHelper: bisectHelper, - } -} - -type ModeStatus struct { - IsActive func() bool - InfoLabel func() string - CancelLabel func() string - Reset func() error -} - -func (self *ModeHelper) Statuses() []ModeStatus { - return []ModeStatus{ - { - IsActive: self.c.Modes().Diffing.Active, - InfoLabel: func() string { - return self.withResetButton( - fmt.Sprintf( - "%s %s", - self.c.Tr.ShowingGitDiff, - "git diff "+strings.Join(self.diffHelper.DiffArgs(), " "), - ), - style.FgMagenta, - ) - }, - CancelLabel: func() string { - return self.c.Tr.CancelDiffingMode - }, - Reset: self.diffHelper.ExitDiffMode, - }, - { - IsActive: self.c.Git().Patch.PatchBuilder.Active, - InfoLabel: func() string { - return self.withResetButton(self.c.Tr.BuildingPatch, style.FgYellow.SetBold()) - }, - CancelLabel: func() string { - return self.c.Tr.ExitCustomPatchBuilder - }, - Reset: self.patchBuildingHelper.Reset, - }, - { - IsActive: self.c.Modes().Filtering.Active, - InfoLabel: func() string { - filterContent := lo.Ternary(self.c.Modes().Filtering.GetPath() != "", self.c.Modes().Filtering.GetPath(), self.c.Modes().Filtering.GetAuthor()) - return self.withResetButton( - fmt.Sprintf( - "%s '%s'", - self.c.Tr.FilteringBy, - filterContent, - ), - style.FgRed, - ) - }, - CancelLabel: func() string { - return self.c.Tr.ExitFilterMode - }, - Reset: self.ExitFilterMode, - }, - { - IsActive: self.c.Modes().MarkedBaseCommit.Active, - InfoLabel: func() string { - return self.withResetButton( - self.c.Tr.MarkedBaseCommitStatus, - style.FgCyan, - ) - }, - CancelLabel: func() string { - return self.c.Tr.CancelMarkedBaseCommit - }, - Reset: self.mergeAndRebaseHelper.ResetMarkedBaseCommit, - }, - { - IsActive: self.c.Modes().CherryPicking.Active, - InfoLabel: func() string { - copiedCount := len(self.c.Modes().CherryPicking.CherryPickedCommits) - text := self.c.Tr.CommitsCopied - if copiedCount == 1 { - text = self.c.Tr.CommitCopied - } - - return self.withResetButton( - fmt.Sprintf( - "%d %s", - copiedCount, - text, - ), - style.FgCyan, - ) - }, - CancelLabel: func() string { - return self.c.Tr.ResetCherryPickShort - }, - Reset: self.cherryPickHelper.Reset, - }, - { - IsActive: func() bool { - return !self.suppressRebasingMode && self.c.Git().Status.WorkingTreeState().Any() - }, - InfoLabel: func() string { - workingTreeState := self.c.Git().Status.WorkingTreeState() - return self.withResetButton( - workingTreeState.Title(self.c.Tr), style.FgYellow, - ) - }, - CancelLabel: func() string { - return fmt.Sprintf(self.c.Tr.AbortTitle, self.c.Git().Status.WorkingTreeState().CommandName()) - }, - Reset: self.mergeAndRebaseHelper.AbortMergeOrRebaseWithConfirm, - }, - { - IsActive: func() bool { - return self.c.Model().BisectInfo.Started() - }, - InfoLabel: func() string { - return self.withResetButton(self.c.Tr.Bisect.Bisecting, style.FgGreen) - }, - CancelLabel: func() string { - return self.c.Tr.Actions.ResetBisect - }, - Reset: self.bisectHelper.Reset, - }, - } -} - -func (self *ModeHelper) withResetButton(content string, textStyle style.TextStyle) string { - return textStyle.Sprintf( - "%s %s", - content, - style.AttrUnderline.Sprint(self.c.Tr.ResetInParentheses), - ) -} - -func (self *ModeHelper) GetActiveMode() (ModeStatus, bool) { - return lo.Find(self.Statuses(), func(mode ModeStatus) bool { - return mode.IsActive() - }) -} - -func (self *ModeHelper) IsAnyModeActive() bool { - return lo.SomeBy(self.Statuses(), func(mode ModeStatus) bool { - return mode.IsActive() - }) -} - -func (self *ModeHelper) ExitFilterMode() error { - return self.ClearFiltering() -} - -func (self *ModeHelper) ClearFiltering() error { - selectedCommitHash := self.c.Contexts().LocalCommits.GetSelectedCommitHash() - self.c.Modes().Filtering.Reset() - if self.c.State().GetRepoState().GetScreenMode() == types.SCREEN_HALF { - self.c.State().GetRepoState().SetScreenMode(types.SCREEN_NORMAL) - } - - self.c.Refresh(types.RefreshOptions{ - Scope: ScopesToRefreshWhenFilteringModeChanges(), - Then: func() { - // Find the commit that was last selected in filtering mode, and select it again after refreshing - if !self.c.Contexts().LocalCommits.SelectCommitByHash(selectedCommitHash) { - // If we couldn't find it (either because no commit was selected - // in filtering mode, or because the commit is outside the - // initial 300 range), go back to the commit that was selected - // before we entered filtering - self.c.Contexts().LocalCommits.SelectCommitByHash(self.c.Modes().Filtering.GetSelectedCommitHash()) - } - - self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits) - }, - }) - return nil -} - -// Stashes really only need to be refreshed when filtering by path, not by author, but it's too much -// work to distinguish this, and refreshing stashes is fast, so we don't bother -func ScopesToRefreshWhenFilteringModeChanges() []types.RefreshableView { - return []types.RefreshableView{ - types.COMMITS, - types.SUB_COMMITS, - types.REFLOG, - types.STASH, - } -} - -func (self *ModeHelper) SetSuppressRebasingMode(value bool) { - self.suppressRebasingMode = value -} diff --git a/pkg/gui/controllers/helpers/patch_building_helper.go b/pkg/gui/controllers/helpers/patch_building_helper.go deleted file mode 100644 index 72ece0a7135..00000000000 --- a/pkg/gui/controllers/helpers/patch_building_helper.go +++ /dev/null @@ -1,124 +0,0 @@ -package helpers - -import ( - "errors" - "fmt" - - "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/patch_exploring" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type PatchBuildingHelper struct { - c *HelperCommon -} - -func NewPatchBuildingHelper( - c *HelperCommon, -) *PatchBuildingHelper { - return &PatchBuildingHelper{ - c: c, - } -} - -func (self *PatchBuildingHelper) ValidateNormalWorkingTreeState() (bool, error) { - if self.c.Git().Status.WorkingTreeState().Any() { - return false, errors.New(self.c.Tr.CantPatchWhileRebasingError) - } - return true, nil -} - -func (self *PatchBuildingHelper) ShowHunkStagingHint() { - if !self.c.AppState.DidShowHunkStagingHint && self.c.UserConfig().Gui.UseHunkModeInStagingView { - self.c.AppState.DidShowHunkStagingHint = true - self.c.SaveAppStateAndLogError() - - message := fmt.Sprintf(self.c.Tr.HunkStagingHint, - keybindings.Label(self.c.UserConfig().Keybinding.Main.ToggleSelectHunk)) - self.c.Confirm(types.ConfirmOpts{ - Prompt: message, - }) - } -} - -// takes us from the patch building panel back to the commit files panel -func (self *PatchBuildingHelper) Escape() { - self.c.Context().Pop() -} - -// kills the custom patch and returns us back to the commit files panel if needed -func (self *PatchBuildingHelper) Reset() error { - self.c.Git().Patch.PatchBuilder.Reset() - - if self.c.Context().CurrentStatic().GetKind() != types.SIDE_CONTEXT { - self.Escape() - } - - self.c.Refresh(types.RefreshOptions{ - Scope: []types.RefreshableView{types.COMMIT_FILES}, - }) - - // refreshing the current context so that the secondary panel is hidden if necessary. - self.c.PostRefreshUpdate(self.c.Context().Current()) - return nil -} - -func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpts) { - selectedLineIdx := -1 - if opts.ClickedWindowName == "main" { - selectedLineIdx = opts.ClickedViewLineIdx - } - - if !self.c.Git().Patch.PatchBuilder.Active() { - self.Escape() - return - } - - // get diff from commit file that's currently selected - path := self.c.Contexts().CommitFiles.GetSelectedPath() - if path == "" { - return - } - - from, to := self.c.Contexts().CommitFiles.GetFromAndToForDiff() - from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from) - diff, err := self.c.Git().WorkingTree.ShowFileDiff(from, to, reverse, path, true) - if err != nil { - return - } - - secondaryDiff := self.c.Git().Patch.PatchBuilder.RenderPatchForFile(patch.RenderPatchForFileOpts{ - Filename: path, - Plain: false, - Reverse: false, - TurnAddedFilesIntoDiffAgainstEmptyFile: true, - }) - - context := self.c.Contexts().CustomPatchBuilder - - oldState := context.GetState() - - state := patch_exploring.NewState(diff, selectedLineIdx, context.GetView(), oldState, self.c.UserConfig().Gui.UseHunkModeInStagingView) - context.SetState(state) - if state == nil { - self.Escape() - return - } - - mainContent := context.GetContentToRender() - - self.c.Contexts().CustomPatchBuilder.FocusSelection() - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().PatchBuilding, - Main: &types.ViewUpdateOpts{ - Task: types.NewRenderStringWithoutScrollTask(mainContent), - Title: self.c.Tr.Patch, - }, - Secondary: &types.ViewUpdateOpts{ - Task: types.NewRenderStringWithoutScrollTask(secondaryDiff), - Title: self.c.Tr.CustomPatch, - }, - }) -} diff --git a/pkg/gui/controllers/helpers/record_directory_helper.go b/pkg/gui/controllers/helpers/record_directory_helper.go deleted file mode 100644 index 377e3b94a93..00000000000 --- a/pkg/gui/controllers/helpers/record_directory_helper.go +++ /dev/null @@ -1,36 +0,0 @@ -package helpers - -import ( - "os" -) - -type RecordDirectoryHelper struct { - c *HelperCommon -} - -func NewRecordDirectoryHelper(c *HelperCommon) *RecordDirectoryHelper { - return &RecordDirectoryHelper{ - c: c, - } -} - -// when a user runs lazygit with the LAZYGIT_NEW_DIR_FILE env variable defined -// we will write the current directory to that file on exit so that their -// shell can then change to that directory. That means you don't get kicked -// back to the directory that you started with. -func (self *RecordDirectoryHelper) RecordCurrentDirectory() error { - // determine current directory, set it in LAZYGIT_NEW_DIR_FILE - dirName, err := os.Getwd() - if err != nil { - return err - } - return self.RecordDirectory(dirName) -} - -func (self *RecordDirectoryHelper) RecordDirectory(dirName string) error { - newDirFilePath := os.Getenv("LAZYGIT_NEW_DIR_FILE") - if newDirFilePath == "" { - return nil - } - return self.c.OS().CreateFileWithContent(newDirFilePath, dirName) -} diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go deleted file mode 100644 index 8ebc76d161d..00000000000 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ /dev/null @@ -1,759 +0,0 @@ -package helpers - -import ( - "strings" - "sync" - "time" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/filetree" - "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type RefreshHelper struct { - c *HelperCommon - refsHelper *RefsHelper - mergeAndRebaseHelper *MergeAndRebaseHelper - patchBuildingHelper *PatchBuildingHelper - stagingHelper *StagingHelper - mergeConflictsHelper *MergeConflictsHelper - worktreeHelper *WorktreeHelper - searchHelper *SearchHelper -} - -func NewRefreshHelper( - c *HelperCommon, - refsHelper *RefsHelper, - mergeAndRebaseHelper *MergeAndRebaseHelper, - patchBuildingHelper *PatchBuildingHelper, - stagingHelper *StagingHelper, - mergeConflictsHelper *MergeConflictsHelper, - worktreeHelper *WorktreeHelper, - searchHelper *SearchHelper, -) *RefreshHelper { - return &RefreshHelper{ - c: c, - refsHelper: refsHelper, - mergeAndRebaseHelper: mergeAndRebaseHelper, - patchBuildingHelper: patchBuildingHelper, - stagingHelper: stagingHelper, - mergeConflictsHelper: mergeConflictsHelper, - worktreeHelper: worktreeHelper, - searchHelper: searchHelper, - } -} - -func (self *RefreshHelper) Refresh(options types.RefreshOptions) { - if options.Mode == types.ASYNC && options.Then != nil { - panic("RefreshOptions.Then doesn't work with mode ASYNC") - } - - t := time.Now() - defer func() { - self.c.Log.Infof("Refresh took %s", time.Since(t)) - }() - - if options.Scope == nil { - self.c.Log.Infof( - "refreshing all scopes in %s mode", - getModeName(options.Mode), - ) - } else { - self.c.Log.Infof( - "refreshing the following scopes in %s mode: %s", - getModeName(options.Mode), - strings.Join(getScopeNames(options.Scope), ","), - ) - } - - f := func() { - var scopeSet *set.Set[types.RefreshableView] - if len(options.Scope) == 0 { - // not refreshing staging/patch-building unless explicitly requested because we only need - // to refresh those while focused. - scopeSet = set.NewFromSlice([]types.RefreshableView{ - types.COMMITS, - types.BRANCHES, - types.FILES, - types.STASH, - types.REFLOG, - types.TAGS, - types.REMOTES, - types.WORKTREES, - types.STATUS, - types.BISECT_INFO, - types.STAGING, - }) - } else { - scopeSet = set.NewFromSlice(options.Scope) - } - - wg := sync.WaitGroup{} - refresh := func(name string, f func()) { - // if we're in a demo we don't want any async refreshes because - // everything happens fast and it's better to have everything update - // in the one frame - if !self.c.InDemo() && options.Mode == types.ASYNC { - self.c.OnWorker(func(t gocui.Task) error { - f() - return nil - }) - } else { - wg.Add(1) - go utils.Safe(func() { - t := time.Now() - defer wg.Done() - f() - self.c.Log.Infof("refreshed %s in %s", name, time.Since(t)) - }) - } - } - - includeWorktreesWithBranches := false - if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) || scopeSet.Includes(types.REFLOG) || scopeSet.Includes(types.BISECT_INFO) { - // whenever we change commits, we should update branches because the upstream/downstream - // counts can change. Whenever we change branches we should also change commits - // e.g. in the case of switching branches. - refresh("commits and commit files", self.refreshCommitsAndCommitFiles) - - includeWorktreesWithBranches = scopeSet.Includes(types.WORKTREES) - if self.c.UserConfig().Git.LocalBranchSortOrder == "recency" { - refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex) }) - } else { - refresh("branches", func() { self.refreshBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex, true) }) - refresh("reflog", func() { _ = self.refreshReflogCommits() }) - } - } else if scopeSet.Includes(types.REBASE_COMMITS) { - // the above block handles rebase commits so we only need to call this one - // if we've asked specifically for rebase commits and not those other things - refresh("rebase commits", func() { _ = self.refreshRebaseCommits() }) - } - - if scopeSet.Includes(types.SUB_COMMITS) { - refresh("sub commits", func() { _ = self.refreshSubCommitsWithLimit() }) - } - - // reason we're not doing this if the COMMITS type is included is that if the COMMITS type _is_ included we will refresh the commit files context anyway - if scopeSet.Includes(types.COMMIT_FILES) && !scopeSet.Includes(types.COMMITS) { - refresh("commit files", func() { _ = self.refreshCommitFilesContext() }) - } - - fileWg := sync.WaitGroup{} - if scopeSet.Includes(types.FILES) || scopeSet.Includes(types.SUBMODULES) { - fileWg.Add(1) - refresh("files", func() { - _ = self.refreshFilesAndSubmodules() - fileWg.Done() - }) - } - - if scopeSet.Includes(types.STASH) { - refresh("stash", func() { self.refreshStashEntries() }) - } - - if scopeSet.Includes(types.TAGS) { - refresh("tags", func() { _ = self.refreshTags() }) - } - - if scopeSet.Includes(types.REMOTES) { - refresh("remotes", func() { _ = self.refreshRemotes() }) - } - - if scopeSet.Includes(types.WORKTREES) && !includeWorktreesWithBranches { - refresh("worktrees", func() { self.refreshWorktrees() }) - } - - if scopeSet.Includes(types.STAGING) { - refresh("staging", func() { - fileWg.Wait() - self.stagingHelper.RefreshStagingPanel(types.OnFocusOpts{}) - }) - } - - if scopeSet.Includes(types.PATCH_BUILDING) { - refresh("patch building", func() { self.patchBuildingHelper.RefreshPatchBuildingPanel(types.OnFocusOpts{}) }) - } - - if scopeSet.Includes(types.MERGE_CONFLICTS) || scopeSet.Includes(types.FILES) { - refresh("merge conflicts", func() { _ = self.mergeConflictsHelper.RefreshMergeState() }) - } - - self.refreshStatus() - - wg.Wait() - - if options.Then != nil { - options.Then() - } - } - - if options.Mode == types.BLOCK_UI { - self.c.OnUIThread(func() error { - f() - return nil - }) - return - } - - f() -} - -func getScopeNames(scopes []types.RefreshableView) []string { - scopeNameMap := map[types.RefreshableView]string{ - types.COMMITS: "commits", - types.BRANCHES: "branches", - types.FILES: "files", - types.SUBMODULES: "submodules", - types.SUB_COMMITS: "subCommits", - types.STASH: "stash", - types.REFLOG: "reflog", - types.TAGS: "tags", - types.REMOTES: "remotes", - types.WORKTREES: "worktrees", - types.STATUS: "status", - types.BISECT_INFO: "bisect", - types.STAGING: "staging", - types.MERGE_CONFLICTS: "mergeConflicts", - } - - return lo.Map(scopes, func(scope types.RefreshableView, _ int) string { - return scopeNameMap[scope] - }) -} - -func getModeName(mode types.RefreshMode) string { - switch mode { - case types.SYNC: - return "sync" - case types.ASYNC: - return "async" - case types.BLOCK_UI: - return "block-ui" - default: - return "unknown mode" - } -} - -// during startup, the bottleneck is fetching the reflog entries. We need these -// on startup to sort the branches by recency. So we have two phases: INITIAL, and COMPLETE. -// In the initial phase we don't get any reflog commits, but we asynchronously get them -// and refresh the branches after that -func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() { - switch self.c.State().GetRepoState().GetStartupStage() { - case types.INITIAL: - self.c.OnWorker(func(_ gocui.Task) error { - _ = self.refreshReflogCommits() - self.refreshBranches(false, true, true) - self.c.State().GetRepoState().SetStartupStage(types.COMPLETE) - return nil - }) - - case types.COMPLETE: - _ = self.refreshReflogCommits() - } -} - -func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool, keepBranchSelectionIndex bool) { - loadBehindCounts := self.c.State().GetRepoState().GetStartupStage() == types.COMPLETE - - self.refreshReflogCommitsConsideringStartup() - - self.refreshBranches(refreshWorktrees, keepBranchSelectionIndex, loadBehindCounts) -} - -func (self *RefreshHelper) refreshCommitsAndCommitFiles() { - _ = self.refreshCommitsWithLimit() - ctx := self.c.Contexts().CommitFiles.GetParentContext() - if ctx != nil && ctx.GetKey() == context.LOCAL_COMMITS_CONTEXT_KEY { - // This makes sense when we've e.g. just amended a commit, meaning we get a new commit hash at the same position. - // However if we've just added a brand new commit, it pushes the list down by one and so we would end up - // showing the contents of a different commit than the one we initially entered. - // Ideally we would know when to refresh the commit files context and when not to, - // or perhaps we could just pop that context off the stack whenever cycling windows. - // For now the awkwardness remains. - commit := self.c.Contexts().LocalCommits.GetSelected() - if commit != nil && commit.RefName() != "" { - refRange := self.c.Contexts().LocalCommits.GetSelectedRefRangeForDiffFiles() - self.c.Contexts().CommitFiles.ReInit(commit, refRange) - _ = self.refreshCommitFilesContext() - } - } -} - -func (self *RefreshHelper) determineCheckedOutRef() models.Ref { - if rebasedBranch := self.c.Git().Status.BranchBeingRebased(); rebasedBranch != "" { - // During a rebase we're on a detached head, so cannot determine the - // branch name in the usual way. We need to read it from the - // ".git/rebase-merge/head-name" file instead. - return &models.Branch{Name: strings.TrimPrefix(rebasedBranch, "refs/heads/")} - } - - if bisectInfo := self.c.Git().Bisect.GetInfo(); bisectInfo.Bisecting() && bisectInfo.GetStartHash() != "" { - // Likewise, when we're bisecting we're on a detached head as well. In - // this case we read the branch name from the ".git/BISECT_START" file. - return &models.Branch{Name: bisectInfo.GetStartHash()} - } - - // In all other cases, get the branch name by asking git what branch is - // checked out. Note that if we're on a detached head (for reasons other - // than rebasing or bisecting, i.e. it was explicitly checked out), then - // this will return an empty string. - if branchName, err := self.c.Git().Branch.CurrentBranchName(); err == nil && branchName != "" { - return &models.Branch{Name: branchName} - } - - // Should never get here unless the working copy is corrupt - return nil -} - -func (self *RefreshHelper) refreshCommitsWithLimit() error { - self.c.Mutexes().LocalCommitsMutex.Lock() - defer self.c.Mutexes().LocalCommitsMutex.Unlock() - - checkedOutRef := self.determineCheckedOutRef() - commits, err := self.c.Git().Loaders.CommitLoader.GetCommits( - git_commands.GetCommitsOptions{ - Limit: self.c.Contexts().LocalCommits.GetLimitCommits(), - FilterPath: self.c.Modes().Filtering.GetPath(), - FilterAuthor: self.c.Modes().Filtering.GetAuthor(), - IncludeRebaseCommits: true, - RefName: self.refForLog(), - RefForPushedStatus: checkedOutRef, - All: self.c.Contexts().LocalCommits.GetShowWholeGitGraph(), - MainBranches: self.c.Model().MainBranches, - HashPool: self.c.Model().HashPool, - }, - ) - if err != nil { - return err - } - self.c.Model().Commits = commits - self.RefreshAuthors(commits) - self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState() - if checkedOutRef != nil { - self.c.Model().CheckedOutBranch = checkedOutRef.RefName() - } else { - self.c.Model().CheckedOutBranch = "" - } - - self.refreshView(self.c.Contexts().LocalCommits) - return nil -} - -func (self *RefreshHelper) refreshSubCommitsWithLimit() error { - if self.c.Contexts().SubCommits.GetRef() == nil { - return nil - } - - self.c.Mutexes().SubCommitsMutex.Lock() - defer self.c.Mutexes().SubCommitsMutex.Unlock() - - commits, err := self.c.Git().Loaders.CommitLoader.GetCommits( - git_commands.GetCommitsOptions{ - Limit: self.c.Contexts().SubCommits.GetLimitCommits(), - FilterPath: self.c.Modes().Filtering.GetPath(), - FilterAuthor: self.c.Modes().Filtering.GetAuthor(), - IncludeRebaseCommits: false, - RefName: self.c.Contexts().SubCommits.GetRef().FullRefName(), - RefToShowDivergenceFrom: self.c.Contexts().SubCommits.GetRefToShowDivergenceFrom(), - RefForPushedStatus: self.c.Contexts().SubCommits.GetRef(), - MainBranches: self.c.Model().MainBranches, - HashPool: self.c.Model().HashPool, - }, - ) - if err != nil { - return err - } - self.c.Model().SubCommits = commits - self.RefreshAuthors(commits) - - self.refreshView(self.c.Contexts().SubCommits) - return nil -} - -func (self *RefreshHelper) RefreshAuthors(commits []*models.Commit) { - self.c.Mutexes().AuthorsMutex.Lock() - defer self.c.Mutexes().AuthorsMutex.Unlock() - - authors := self.c.Model().Authors - for _, commit := range commits { - if _, ok := authors[commit.AuthorEmail]; !ok { - authors[commit.AuthorEmail] = &models.Author{ - Email: commit.AuthorEmail, - Name: commit.AuthorName, - } - } - } -} - -func (self *RefreshHelper) refreshCommitFilesContext() error { - from, to := self.c.Contexts().CommitFiles.GetFromAndToForDiff() - from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from) - - files, err := self.c.Git().Loaders.CommitFileLoader.GetFilesInDiff(from, to, reverse) - if err != nil { - return err - } - self.c.Model().CommitFiles = files - self.c.Contexts().CommitFiles.CommitFileTreeViewModel.SetTree() - - self.refreshView(self.c.Contexts().CommitFiles) - return nil -} - -func (self *RefreshHelper) refreshRebaseCommits() error { - self.c.Mutexes().LocalCommitsMutex.Lock() - defer self.c.Mutexes().LocalCommitsMutex.Unlock() - - updatedCommits, err := self.c.Git().Loaders.CommitLoader.MergeRebasingCommits(self.c.Model().HashPool, self.c.Model().Commits) - if err != nil { - return err - } - self.c.Model().Commits = updatedCommits - self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState() - - self.refreshView(self.c.Contexts().LocalCommits) - return nil -} - -func (self *RefreshHelper) refreshTags() error { - tags, err := self.c.Git().Loaders.TagLoader.GetTags() - if err != nil { - return err - } - - self.c.Model().Tags = tags - - self.refreshView(self.c.Contexts().Tags) - return nil -} - -func (self *RefreshHelper) refreshStateSubmoduleConfigs() error { - configs, err := self.c.Git().Submodule.GetConfigs(nil) - if err != nil { - return err - } - - self.c.Model().Submodules = configs - - return nil -} - -// self.refreshStatus is called at the end of this because that's when we can -// be sure there is a State.Model.Branches array to pick the current branch from -func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSelectionIndex bool, loadBehindCounts bool) { - self.c.Mutexes().RefreshingBranchesMutex.Lock() - defer self.c.Mutexes().RefreshingBranchesMutex.Unlock() - - branches, err := self.c.Git().Loaders.BranchLoader.Load( - self.c.Model().ReflogCommits, - self.c.Model().MainBranches, - self.c.Model().Branches, - loadBehindCounts, - func(f func() error) { - self.c.OnWorker(func(_ gocui.Task) error { - return f() - }) - }, - func() { - self.c.OnUIThread(func() error { - self.c.Contexts().Branches.HandleRender() - self.refreshStatus() - return nil - }) - }) - if err != nil { - self.c.Log.Error(err) - } - - prevSelectedBranch := self.c.Contexts().Branches.GetSelected() - - self.c.Model().Branches = branches - - if refreshWorktrees { - self.loadWorktrees() - self.refreshView(self.c.Contexts().Worktrees) - } - - if !keepBranchSelectionIndex && prevSelectedBranch != nil { - self.searchHelper.ReApplyFilter(self.c.Contexts().Branches) - - _, idx, found := lo.FindIndexOf(self.c.Contexts().Branches.GetItems(), - func(b *models.Branch) bool { return b.Name == prevSelectedBranch.Name }) - if found { - self.c.Contexts().Branches.SetSelectedLineIdx(idx) - } - } - - self.refreshView(self.c.Contexts().Branches) - - // Need to re-render the commits view because the visualization of local - // branch heads might have changed - self.c.Mutexes().LocalCommitsMutex.Lock() - self.c.Contexts().LocalCommits.HandleRender() - self.c.Mutexes().LocalCommitsMutex.Unlock() - - self.refreshStatus() -} - -func (self *RefreshHelper) refreshFilesAndSubmodules() error { - self.c.Mutexes().RefreshingFilesMutex.Lock() - self.c.State().SetIsRefreshingFiles(true) - defer func() { - self.c.State().SetIsRefreshingFiles(false) - self.c.Mutexes().RefreshingFilesMutex.Unlock() - }() - - if err := self.refreshStateSubmoduleConfigs(); err != nil { - return err - } - - if err := self.refreshStateFiles(); err != nil { - return err - } - - self.c.OnUIThread(func() error { - self.refreshView(self.c.Contexts().Submodules) - self.refreshView(self.c.Contexts().Files) - return nil - }) - - return nil -} - -func (self *RefreshHelper) refreshStateFiles() error { - fileTreeViewModel := self.c.Contexts().Files.FileTreeViewModel - - prevConflictFileCount := 0 - if self.c.UserConfig().Git.AutoStageResolvedConflicts { - // If git thinks any of our files have inline merge conflicts, but they actually don't, - // we stage them. - // Note that if files with merge conflicts have both arisen and have been resolved - // between refreshes, we won't stage them here. This is super unlikely though, - // and this approach spares us from having to call `git status` twice in a row. - // Although this also means that at startup we won't be staging anything until - // we call git status again. - pathsToStage := []string{} - for _, file := range self.c.Model().Files { - if file.HasMergeConflicts { - prevConflictFileCount++ - } - if file.HasInlineMergeConflicts { - hasConflicts, err := mergeconflicts.FileHasConflictMarkers(file.Path) - if err != nil { - self.c.Log.Error(err) - } else if !hasConflicts { - pathsToStage = append(pathsToStage, file.Path) - } - } - } - - if len(pathsToStage) > 0 { - self.c.LogAction(self.c.Tr.Actions.StageResolvedFiles) - if err := self.c.Git().WorkingTree.StageFiles(pathsToStage, nil); err != nil { - return err - } - } - } - - files := self.c.Git().Loaders.FileLoader. - GetStatusFiles(git_commands.GetStatusFileOptions{ - ForceShowUntracked: self.c.Contexts().Files.ForceShowUntracked(), - }) - - conflictFileCount := 0 - for _, file := range files { - if file.HasMergeConflicts { - conflictFileCount++ - } - } - - if self.c.Git().Status.WorkingTreeState().Any() && conflictFileCount == 0 && prevConflictFileCount > 0 { - self.c.OnUIThread(func() error { return self.mergeAndRebaseHelper.PromptToContinueRebase() }) - } - - fileTreeViewModel.RWMutex.Lock() - - // only taking over the filter if it hasn't already been set by the user. - if conflictFileCount > 0 && prevConflictFileCount == 0 { - if fileTreeViewModel.GetFilter() == filetree.DisplayAll { - fileTreeViewModel.SetStatusFilter(filetree.DisplayConflicted) - self.c.Contexts().Files.GetView().Subtitle = self.c.Tr.FilterLabelConflictingFiles - } - } else if conflictFileCount == 0 && fileTreeViewModel.GetFilter() == filetree.DisplayConflicted { - fileTreeViewModel.SetStatusFilter(filetree.DisplayAll) - self.c.Contexts().Files.GetView().Subtitle = "" - } - - self.c.Model().Files = files - fileTreeViewModel.SetTree() - fileTreeViewModel.RWMutex.Unlock() - - return nil -} - -// the reflogs panel is the only panel where we cache data, in that we only -// load entries that have been created since we last ran the call. This means -// we need to be more careful with how we use this, and to ensure we're emptying -// the reflogs array when changing contexts. -// This method also manages two things: ReflogCommits and FilteredReflogCommits. -// FilteredReflogCommits are rendered in the reflogs panel, and ReflogCommits -// are used by the branches panel to obtain recency values for sorting. -func (self *RefreshHelper) refreshReflogCommits() error { - // pulling state into its own variable in case it gets swapped out for another state - // and we get an out of bounds exception - model := self.c.Model() - - refresh := func(stateCommits *[]*models.Commit, filterPath string, filterAuthor string) error { - var lastReflogCommit *models.Commit - if filterPath == "" && filterAuthor == "" && len(*stateCommits) > 0 { - lastReflogCommit = (*stateCommits)[0] - } - - commits, onlyObtainedNewReflogCommits, err := self.c.Git().Loaders.ReflogCommitLoader. - GetReflogCommits(self.c.Model().HashPool, lastReflogCommit, filterPath, filterAuthor) - if err != nil { - return err - } - - if onlyObtainedNewReflogCommits { - *stateCommits = append(commits, *stateCommits...) - } else { - *stateCommits = commits - } - return nil - } - - if err := refresh(&model.ReflogCommits, "", ""); err != nil { - return err - } - - if self.c.Modes().Filtering.Active() { - if err := refresh(&model.FilteredReflogCommits, self.c.Modes().Filtering.GetPath(), self.c.Modes().Filtering.GetAuthor()); err != nil { - return err - } - } else { - model.FilteredReflogCommits = model.ReflogCommits - } - - self.refreshView(self.c.Contexts().ReflogCommits) - return nil -} - -func (self *RefreshHelper) refreshRemotes() error { - prevSelectedRemote := self.c.Contexts().Remotes.GetSelected() - - remotes, err := self.c.Git().Loaders.RemoteLoader.GetRemotes() - if err != nil { - return err - } - - self.c.Model().Remotes = remotes - - // we need to ensure our selected remote branches aren't now outdated - if prevSelectedRemote != nil && self.c.Model().RemoteBranches != nil { - // find remote now - for _, remote := range remotes { - if remote.Name == prevSelectedRemote.Name { - self.c.Model().RemoteBranches = remote.Branches - break - } - } - } - - self.refreshView(self.c.Contexts().Remotes) - self.refreshView(self.c.Contexts().RemoteBranches) - return nil -} - -func (self *RefreshHelper) loadWorktrees() { - worktrees, err := self.c.Git().Loaders.Worktrees.GetWorktrees() - if err != nil { - self.c.Log.Error(err) - self.c.Model().Worktrees = []*models.Worktree{} - } - - self.c.Model().Worktrees = worktrees -} - -func (self *RefreshHelper) refreshWorktrees() { - self.loadWorktrees() - - // need to refresh branches because the branches view shows worktrees against - // branches - self.refreshView(self.c.Contexts().Branches) - self.refreshView(self.c.Contexts().Worktrees) -} - -func (self *RefreshHelper) refreshStashEntries() { - self.c.Model().StashEntries = self.c.Git().Loaders.StashLoader. - GetStashEntries(self.c.Modes().Filtering.GetPath()) - - self.refreshView(self.c.Contexts().Stash) -} - -// never call this on its own, it should only be called from within refreshCommits() -func (self *RefreshHelper) refreshStatus() { - self.c.Mutexes().RefreshingStatusMutex.Lock() - defer self.c.Mutexes().RefreshingStatusMutex.Unlock() - - currentBranch := self.refsHelper.GetCheckedOutRef() - if currentBranch == nil { - // need to wait for branches to refresh - return - } - - workingTreeState := self.c.Git().Status.WorkingTreeState() - linkedWorktreeName := self.worktreeHelper.GetLinkedWorktreeName() - - repoName := self.c.Git().RepoPaths.RepoName() - - status := presentation.FormatStatus(repoName, currentBranch, types.ItemOperationNone, linkedWorktreeName, workingTreeState, self.c.Tr, self.c.UserConfig()) - - self.c.SetViewContent(self.c.Views().Status, status) -} - -func (self *RefreshHelper) refForLog() string { - bisectInfo := self.c.Git().Bisect.GetInfo() - self.c.Model().BisectInfo = bisectInfo - - if !bisectInfo.Started() { - return "HEAD" - } - - // need to see if our bisect's current commit is reachable from our 'new' ref. - if bisectInfo.Bisecting() && !self.c.Git().Bisect.ReachableFromStart(bisectInfo) { - return bisectInfo.GetNewHash() - } - - return bisectInfo.GetStartHash() -} - -func (self *RefreshHelper) refreshView(context types.Context) { - // Re-applying the filter must be done before re-rendering the view, so that - // the filtered list model is up to date for rendering. - self.searchHelper.ReApplyFilter(context) - - self.c.PostRefreshUpdate(context) - - self.c.AfterLayout(func() error { - // Re-applying the search must be done after re-rendering the view though, - // so that the "x of y" status is shown correctly. - // - // Also, it must be done after layout, because otherwise FocusPoint - // hasn't been called yet (see ListContextTrait.FocusLine), which means - // that the scroll position might be such that the entire visible - // content is outside the viewport. And this would cause problems in - // searchModelCommits. - self.searchHelper.ReApplySearch(context) - return nil - }) -} diff --git a/pkg/gui/controllers/helpers/refs_helper.go b/pkg/gui/controllers/helpers/refs_helper.go deleted file mode 100644 index 39cda5bd493..00000000000 --- a/pkg/gui/controllers/helpers/refs_helper.go +++ /dev/null @@ -1,612 +0,0 @@ -package helpers - -import ( - "fmt" - "strings" - "text/template" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type RefsHelper struct { - c *HelperCommon - - rebaseHelper *MergeAndRebaseHelper -} - -func NewRefsHelper( - c *HelperCommon, - rebaseHelper *MergeAndRebaseHelper, -) *RefsHelper { - return &RefsHelper{ - c: c, - rebaseHelper: rebaseHelper, - } -} - -func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions) error { - waitingStatus := options.WaitingStatus - if waitingStatus == "" { - waitingStatus = self.c.Tr.CheckingOutStatus - } - - cmdOptions := git_commands.CheckoutOptions{Force: false, EnvVars: options.EnvVars} - - refresh := func() { - self.c.Contexts().Branches.SetSelection(0) - self.c.Contexts().ReflogCommits.SetSelection(0) - self.c.Contexts().LocalCommits.SetSelection(0) - // loading a heap of commits is slow so we limit them whenever doing a reset - self.c.Contexts().LocalCommits.SetLimitCommits(true) - - self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true}) - } - - localBranch, found := lo.Find(self.c.Model().Branches, func(branch *models.Branch) bool { - return branch.Name == ref - }) - - withCheckoutStatus := func(f func(gocui.Task) error) error { - if found { - return self.c.WithInlineStatus(localBranch, types.ItemOperationCheckingOut, context.LOCAL_BRANCHES_CONTEXT_KEY, f) - } - - return self.c.WithWaitingStatus(waitingStatus, f) - } - - return withCheckoutStatus(func(gocui.Task) error { - if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil { - // note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option - - if options.OnRefNotFound != nil && strings.Contains(err.Error(), "did not match any file(s) known to git") { - return options.OnRefNotFound(ref) - } - - if IsSwitchBranchUncommittedChangesError(err) { - // offer to autostash changes - self.c.OnUIThread(func() error { - // (Before showing the prompt, render again to remove the inline status) - self.c.Contexts().Branches.HandleRender() - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.AutoStashTitle, - Prompt: self.c.Tr.AutoStashPrompt, - HandleConfirm: func() error { - return withCheckoutStatus(func(gocui.Task) error { - if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForCheckout, ref)); err != nil { - return err - } - if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil { - return err - } - err := self.c.Git().Stash.Pop(0) - // Branch switch successful so re-render the UI even if the pop operation failed (e.g. conflict). - refresh() - return err - }) - }, - }) - - return nil - }) - return nil - } - - return err - } - - refresh() - return nil - }) -} - -// Shows a prompt to choose between creating a new branch or checking out a detached head -func (self *RefsHelper) CheckoutRemoteBranch(fullBranchName string, localBranchName string) error { - checkout := func(branchName string) error { - // Switch to the branches context _before_ starting to check out the - // branch, so that we see the inline status - if self.c.Context().Current() != self.c.Contexts().Branches { - self.c.Context().Push(self.c.Contexts().Branches, types.OnFocusOpts{}) - } - return self.CheckoutRef(branchName, types.CheckoutRefOptions{}) - } - - // If a branch with this name already exists locally, just check it out. We - // don't bother checking whether it actually tracks this remote branch, since - // it's very unlikely that it doesn't. - if lo.ContainsBy(self.c.Model().Branches, func(branch *models.Branch) bool { - return branch.Name == localBranchName - }) { - return checkout(localBranchName) - } - - return self.c.Menu(types.CreateMenuOptions{ - Title: utils.ResolvePlaceholderString(self.c.Tr.RemoteBranchCheckoutTitle, map[string]string{ - "branchName": fullBranchName, - }), - Prompt: self.c.Tr.RemoteBranchCheckoutPrompt, - Items: []*types.MenuItem{ - { - Label: self.c.Tr.CheckoutTypeNewBranch, - Tooltip: self.c.Tr.CheckoutTypeNewBranchTooltip, - OnPress: func() error { - // First create the local branch with the upstream set, and - // then check it out. We could do that in one step using - // "git checkout -b", but we want to benefit from all the - // nice features of the CheckoutRef function. - if err := self.c.Git().Branch.CreateWithUpstream(localBranchName, fullBranchName); err != nil { - return err - } - // Do a sync refresh to make sure the new branch is visible, - // so that we see an inline status when checking it out - self.c.Refresh(types.RefreshOptions{ - Mode: types.SYNC, - Scope: []types.RefreshableView{types.BRANCHES}, - }) - return checkout(localBranchName) - }, - }, - { - Label: self.c.Tr.CheckoutTypeDetachedHead, - Tooltip: self.c.Tr.CheckoutTypeDetachedHeadTooltip, - OnPress: func() error { - return checkout(fullBranchName) - }, - }, - }, - }) -} - -func (self *RefsHelper) CheckoutPreviousRef() error { - previousRef, err := self.c.Git().Branch.PreviousRef() - if err == nil && strings.HasPrefix(previousRef, "refs/heads/") { - return self.CheckoutRef(strings.TrimPrefix(previousRef, "refs/heads/"), types.CheckoutRefOptions{}) - } - - return self.CheckoutRef("-", types.CheckoutRefOptions{}) -} - -func (self *RefsHelper) GetCheckedOutRef() *models.Branch { - if len(self.c.Model().Branches) == 0 { - return nil - } - - return self.c.Model().Branches[0] -} - -func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string) error { - if err := self.c.Git().Commit.ResetToCommit(ref, strength, envVars); err != nil { - return err - } - - self.c.Contexts().LocalCommits.SetSelection(0) - self.c.Contexts().ReflogCommits.SetSelection(0) - // loading a heap of commits is slow so we limit them whenever doing a reset - self.c.Contexts().LocalCommits.SetLimitCommits(true) - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS}}) - - return nil -} - -func (self *RefsHelper) CreateSortOrderMenu(sortOptionsOrder []string, menuPrompt string, onSelected func(sortOrder string) error, currentValue string) error { - type sortMenuOption struct { - key types.Key - label string - description string - sortOrder string - } - availableSortOptions := map[string]sortMenuOption{ - "recency": {label: self.c.Tr.SortByRecency, description: self.c.Tr.SortBasedOnReflog, key: 'r'}, - "alphabetical": {label: self.c.Tr.SortAlphabetical, description: "--sort=refname", key: 'a'}, - "date": {label: self.c.Tr.SortByDate, description: "--sort=-committerdate", key: 'd'}, - } - sortOptions := make([]sortMenuOption, 0, len(sortOptionsOrder)) - for _, key := range sortOptionsOrder { - sortOption, ok := availableSortOptions[key] - if !ok { - panic(fmt.Sprintf("unexpected sort order: %s", key)) - } - sortOption.sortOrder = key - sortOptions = append(sortOptions, sortOption) - } - - menuItems := lo.Map(sortOptions, func(opt sortMenuOption, _ int) *types.MenuItem { - return &types.MenuItem{ - LabelColumns: []string{ - opt.label, - style.FgYellow.Sprint(opt.description), - }, - OnPress: func() error { - return onSelected(opt.sortOrder) - }, - Key: opt.key, - Widget: types.MakeMenuRadioButton(opt.sortOrder == currentValue), - } - }) - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.SortOrder, - Items: menuItems, - Prompt: menuPrompt, - }) -} - -func (self *RefsHelper) CreateGitResetMenu(name string, ref string) error { - type strengthWithKey struct { - strength string - label string - key types.Key - tooltip string - } - strengths := []strengthWithKey{ - // not i18'ing because it's git terminology - {strength: "mixed", label: "Mixed reset", key: 'm', tooltip: self.c.Tr.ResetMixedTooltip}, - {strength: "soft", label: "Soft reset", key: 's', tooltip: self.c.Tr.ResetSoftTooltip}, - {strength: "hard", label: "Hard reset", key: 'h', tooltip: self.c.Tr.ResetHardTooltip}, - } - - menuItems := lo.Map(strengths, func(row strengthWithKey, _ int) *types.MenuItem { - return &types.MenuItem{ - LabelColumns: []string{ - row.label, - style.FgRed.Sprintf("reset --%s %s", row.strength, name), - }, - OnPress: func() error { - return self.c.ConfirmIf(row.strength == "hard" && IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules), - types.ConfirmOpts{ - Title: self.c.Tr.Actions.HardReset, - Prompt: self.c.Tr.ResetHardConfirmation, - HandleConfirm: func() error { - self.c.LogAction("Reset") - return self.ResetToRef(ref, row.strength, []string{}) - }, - }) - }, - Key: row.key, - Tooltip: row.tooltip, - } - }) - - return self.c.Menu(types.CreateMenuOptions{ - Title: fmt.Sprintf("%s %s", self.c.Tr.ResetTo, name), - Items: menuItems, - }) -} - -func (self *RefsHelper) CreateCheckoutMenu(commit *models.Commit) error { - branches := lo.Filter(self.c.Model().Branches, func(branch *models.Branch, _ int) bool { - return commit.Hash() == branch.CommitHash && branch.Name != self.c.Model().CheckedOutBranch - }) - - hash := commit.Hash() - - menuItems := []*types.MenuItem{ - { - LabelColumns: []string{fmt.Sprintf(self.c.Tr.Actions.CheckoutCommitAsDetachedHead, utils.ShortHash(hash))}, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.CheckoutCommit) - return self.CheckoutRef(hash, types.CheckoutRefOptions{}) - }, - Key: 'd', - }, - } - - if len(branches) > 0 { - menuItems = append(menuItems, lo.Map(branches, func(branch *models.Branch, index int) *types.MenuItem { - var key types.Key - if index < 9 { - key = rune(index + 1 + '0') // Convert 1-based index to key - } - return &types.MenuItem{ - LabelColumns: []string{fmt.Sprintf(self.c.Tr.Actions.CheckoutBranchAtCommit, branch.Name)}, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.CheckoutBranch) - return self.CheckoutRef(branch.RefName(), types.CheckoutRefOptions{}) - }, - Key: key, - } - })...) - } else { - menuItems = append(menuItems, &types.MenuItem{ - LabelColumns: []string{self.c.Tr.Actions.CheckoutBranch}, - OnPress: func() error { return nil }, - DisabledReason: &types.DisabledReason{Text: self.c.Tr.NoBranchesFoundAtCommitTooltip}, - Key: '1', - }) - } - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.Actions.CheckoutBranchOrCommit, - Items: menuItems, - }) -} - -func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggestedBranchName string) error { - message := utils.ResolvePlaceholderString( - self.c.Tr.NewBranchNameBranchOff, - map[string]string{ - "branchName": fromFormattedName, - }, - ) - - if suggestedBranchName == "" { - var err error - - suggestedBranchName, err = self.getSuggestedBranchName() - if err != nil { - return err - } - } - - refresh := func() { - if self.c.Context().Current() != self.c.Contexts().Branches { - self.c.Context().Push(self.c.Contexts().Branches, types.OnFocusOpts{}) - } - - self.c.Contexts().LocalCommits.SetSelection(0) - self.c.Contexts().Branches.SetSelection(0) - - self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true}) - } - - self.c.Prompt(types.PromptOpts{ - Title: message, - InitialContent: suggestedBranchName, - HandleConfirm: func(response string) error { - self.c.LogAction(self.c.Tr.Actions.CreateBranch) - newBranchName := SanitizedBranchName(response) - newBranchFunc := self.c.Git().Branch.New - if newBranchName != suggestedBranchName { - newBranchFunc = self.c.Git().Branch.NewWithoutTracking - } - if err := newBranchFunc(newBranchName, from); err != nil { - if IsSwitchBranchUncommittedChangesError(err) { - // offer to autostash changes - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.AutoStashTitle, - Prompt: self.c.Tr.AutoStashPrompt, - HandleConfirm: func() error { - if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForNewBranch, newBranchName)); err != nil { - return err - } - if err := newBranchFunc(newBranchName, from); err != nil { - return err - } - err := self.c.Git().Stash.Pop(0) - // Branch switch successful so re-render the UI even if the pop operation failed (e.g. conflict). - refresh() - return err - }, - }) - - return nil - } - - return err - } - - refresh() - return nil - }, - }) - - return nil -} - -func (self *RefsHelper) MoveCommitsToNewBranch() error { - currentBranch := self.c.Model().Branches[0] - baseBranchRef, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(currentBranch, self.c.Model().MainBranches) - if err != nil { - return err - } - - withNewBranchNamePrompt := func(baseBranchName string, f func(string) error) error { - prompt := utils.ResolvePlaceholderString( - self.c.Tr.NewBranchNameBranchOff, - map[string]string{ - "branchName": baseBranchName, - }, - ) - suggestedBranchName, err := self.getSuggestedBranchName() - if err != nil { - return err - } - - self.c.Prompt(types.PromptOpts{ - Title: prompt, - InitialContent: suggestedBranchName, - HandleConfirm: func(response string) error { - self.c.LogAction(self.c.Tr.MoveCommitsToNewBranch) - newBranchName := SanitizedBranchName(response) - return self.c.WithWaitingStatus(self.c.Tr.MovingCommitsToNewBranchStatus, func(gocui.Task) error { - return f(newBranchName) - }) - }, - }) - return nil - } - - isMainBranch := lo.Contains(self.c.UserConfig().Git.MainBranches, currentBranch.Name) - if isMainBranch { - prompt := utils.ResolvePlaceholderString( - self.c.Tr.MoveCommitsToNewBranchFromMainPrompt, - map[string]string{ - "baseBranchName": currentBranch.Name, - }, - ) - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.MoveCommitsToNewBranch, - Prompt: prompt, - HandleConfirm: func() error { - return withNewBranchNamePrompt(currentBranch.Name, self.moveCommitsToNewBranchStackedOnCurrentBranch) - }, - }) - return nil - } - - shortBaseBranchName := ShortBranchName(baseBranchRef) - prompt := utils.ResolvePlaceholderString( - self.c.Tr.MoveCommitsToNewBranchMenuPrompt, - map[string]string{ - "baseBranchName": shortBaseBranchName, - }, - ) - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.MoveCommitsToNewBranch, - Prompt: prompt, - Items: []*types.MenuItem{ - { - Label: fmt.Sprintf(self.c.Tr.MoveCommitsToNewBranchFromBaseItem, shortBaseBranchName), - OnPress: func() error { - return withNewBranchNamePrompt(shortBaseBranchName, func(newBranchName string) error { - return self.moveCommitsToNewBranchOffOfMainBranch(newBranchName, baseBranchRef) - }) - }, - }, - { - Label: fmt.Sprintf(self.c.Tr.MoveCommitsToNewBranchStackedItem, currentBranch.Name), - OnPress: func() error { - return withNewBranchNamePrompt(currentBranch.Name, self.moveCommitsToNewBranchStackedOnCurrentBranch) - }, - }, - }, - }) -} - -func (self *RefsHelper) moveCommitsToNewBranchStackedOnCurrentBranch(newBranchName string) error { - if err := self.c.Git().Branch.NewWithoutCheckout(newBranchName, "HEAD"); err != nil { - return err - } - - mustStash := IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules) - if mustStash { - if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForNewBranch, newBranchName)); err != nil { - return err - } - } - - if err := self.c.Git().Commit.ResetToCommit("@{u}", "hard", []string{}); err != nil { - return err - } - - if err := self.c.Git().Branch.Checkout(newBranchName, git_commands.CheckoutOptions{}); err != nil { - return err - } - - if mustStash { - if err := self.c.Git().Stash.Pop(0); err != nil { - return err - } - } - - self.c.Contexts().LocalCommits.SetSelection(0) - self.c.Contexts().Branches.SetSelection(0) - - self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true}) - return nil -} - -func (self *RefsHelper) moveCommitsToNewBranchOffOfMainBranch(newBranchName string, baseBranchRef string) error { - commitsToCherryPick := lo.Filter(self.c.Model().Commits, func(commit *models.Commit, _ int) bool { - return commit.Status == models.StatusUnpushed - }) - - mustStash := IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules) - if mustStash { - if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForNewBranch, newBranchName)); err != nil { - return err - } - } - - if err := self.c.Git().Commit.ResetToCommit("@{u}", "hard", []string{}); err != nil { - return err - } - - if err := self.c.Git().Branch.NewWithoutTracking(newBranchName, baseBranchRef); err != nil { - return err - } - - err := self.c.Git().Rebase.CherryPickCommits(commitsToCherryPick) - err = self.rebaseHelper.CheckMergeOrRebaseWithRefreshOptions(err, types.RefreshOptions{Mode: types.SYNC}) - if err != nil { - return err - } - - if mustStash { - if err := self.c.Git().Stash.Pop(0); err != nil { - return err - } - } - - self.c.Contexts().LocalCommits.SetSelection(0) - self.c.Contexts().Branches.SetSelection(0) - - self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true}) - return nil -} - -func (self *RefsHelper) CanMoveCommitsToNewBranch() *types.DisabledReason { - if len(self.c.Model().Branches) == 0 { - return &types.DisabledReason{Text: self.c.Tr.NoBranchesThisRepo} - } - currentBranch := self.GetCheckedOutRef() - if currentBranch.DetachedHead { - return &types.DisabledReason{Text: self.c.Tr.CannotMoveCommitsFromDetachedHead, ShowErrorInPanel: true} - } - if !currentBranch.RemoteBranchStoredLocally() { - return &types.DisabledReason{Text: self.c.Tr.CannotMoveCommitsNoUpstream, ShowErrorInPanel: true} - } - if currentBranch.IsBehindForPull() { - return &types.DisabledReason{Text: self.c.Tr.CannotMoveCommitsBehindUpstream, ShowErrorInPanel: true} - } - if !currentBranch.IsAheadForPull() { - return &types.DisabledReason{Text: self.c.Tr.CannotMoveCommitsNoUnpushedCommits, ShowErrorInPanel: true} - } - - return nil -} - -// SanitizedBranchName will remove all spaces in favor of a dash "-" to meet -// git's branch naming requirement. -func SanitizedBranchName(input string) string { - return strings.ReplaceAll(input, " ", "-") -} - -// Checks if the given branch name is a remote branch, and returns the name of -// the remote and the bare branch name if it is. -func (self *RefsHelper) ParseRemoteBranchName(fullBranchName string) (string, string, bool) { - remoteName, branchName, found := strings.Cut(fullBranchName, "/") - if !found { - return "", "", false - } - - // See if the part before the first slash is actually one of our remotes - if !lo.ContainsBy(self.c.Model().Remotes, func(remote *models.Remote) bool { - return remote.Name == remoteName - }) { - return "", "", false - } - - return remoteName, branchName, true -} - -func IsSwitchBranchUncommittedChangesError(err error) bool { - return strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch") -} - -func (self *RefsHelper) getSuggestedBranchName() (string, error) { - suggestedBranchName, err := utils.ResolveTemplate(self.c.UserConfig().Git.BranchPrefix, nil, template.FuncMap{ - "runCommand": self.c.Git().Custom.TemplateFunctionRunCommand, - }) - if err != nil { - return suggestedBranchName, err - } - suggestedBranchName = strings.ReplaceAll(suggestedBranchName, "\t", " ") - return suggestedBranchName, nil -} diff --git a/pkg/gui/controllers/helpers/repos_helper.go b/pkg/gui/controllers/helpers/repos_helper.go deleted file mode 100644 index decebba3699..00000000000 --- a/pkg/gui/controllers/helpers/repos_helper.go +++ /dev/null @@ -1,182 +0,0 @@ -package helpers - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "strings" - "sync" - - "github.com/jesseduffield/gocui" - appTypes "github.com/jesseduffield/lazygit/pkg/app/types" - "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/env" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type onNewRepoFn func(startArgs appTypes.StartArgs, contextKey types.ContextKey) error - -// helps switch back and forth between repos -type ReposHelper struct { - c *HelperCommon - recordDirectoryHelper *RecordDirectoryHelper - onNewRepo onNewRepoFn -} - -func NewRecentReposHelper( - c *HelperCommon, - recordDirectoryHelper *RecordDirectoryHelper, - onNewRepo onNewRepoFn, -) *ReposHelper { - return &ReposHelper{ - c: c, - recordDirectoryHelper: recordDirectoryHelper, - onNewRepo: onNewRepo, - } -} - -func (self *ReposHelper) EnterSubmodule(submodule *models.SubmoduleConfig) error { - wd, err := os.Getwd() - if err != nil { - return err - } - self.c.State().GetRepoPathStack().Push(wd) - - return self.DispatchSwitchToRepo(submodule.FullPath(), context.NO_CONTEXT) -} - -func (self *ReposHelper) getCurrentBranch(path string) string { - readHeadFile := func(path string) (string, error) { - headFile, err := os.ReadFile(filepath.Join(path, "HEAD")) - if err == nil { - content := strings.TrimSpace(string(headFile)) - refsPrefix := "ref: refs/heads/" - var branchDisplay string - if strings.HasPrefix(content, refsPrefix) { - // is a branch - branchDisplay = strings.TrimPrefix(content, refsPrefix) - } else { - // detached HEAD state, displaying short hash - branchDisplay = utils.ShortHash(content) - } - return branchDisplay, nil - } - return "", err - } - - gitDirPath := filepath.Join(path, ".git") - - if gitDir, err := os.Stat(gitDirPath); err == nil { - if gitDir.IsDir() { - // ordinary repo - if branch, err := readHeadFile(gitDirPath); err == nil { - return branch - } - } else { - // worktree - if worktreeGitDir, err := os.ReadFile(gitDirPath); err == nil { - content := strings.TrimSpace(string(worktreeGitDir)) - worktreePath := strings.TrimPrefix(content, "gitdir: ") - if branch, err := readHeadFile(worktreePath); err == nil { - return branch - } - } - } - } - - return self.c.Tr.BranchUnknown -} - -func (self *ReposHelper) CreateRecentReposMenu() error { - // we'll show an empty panel if there are no recent repos - recentRepoPaths := []string{} - if len(self.c.GetAppState().RecentRepos) > 0 { - // we skip the first one because we're currently in it - recentRepoPaths = self.c.GetAppState().RecentRepos[1:] - } - - currentBranches := sync.Map{} - - wg := sync.WaitGroup{} - wg.Add(len(recentRepoPaths)) - - for _, path := range recentRepoPaths { - go func(path string) { - defer wg.Done() - currentBranches.Store(path, self.getCurrentBranch(path)) - }(path) - } - - wg.Wait() - - menuItems := lo.Map(recentRepoPaths, func(path string, _ int) *types.MenuItem { - branchName, _ := currentBranches.Load(path) - if icons.IsIconEnabled() { - branchName = icons.BRANCH_ICON + " " + fmt.Sprintf("%v", branchName) - } - - return &types.MenuItem{ - LabelColumns: []string{ - filepath.Base(path), - style.FgCyan.Sprint(branchName), - style.FgMagenta.Sprint(path), - }, - OnPress: func() error { - // if we were in a submodule, we want to forget about that stack of repos - // so that hitting escape in the new repo does nothing - self.c.State().GetRepoPathStack().Clear() - return self.DispatchSwitchToRepo(path, context.NO_CONTEXT) - }, - } - }) - - return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.RecentRepos, Items: menuItems}) -} - -func (self *ReposHelper) DispatchSwitchToRepo(path string, contextKey types.ContextKey) error { - return self.DispatchSwitchTo(path, self.c.Tr.ErrRepositoryMovedOrDeleted, contextKey) -} - -func (self *ReposHelper) DispatchSwitchTo(path string, errMsg string, contextKey types.ContextKey) error { - return self.c.WithWaitingStatus(self.c.Tr.Switching, func(gocui.Task) error { - env.UnsetGitLocationEnvVars() - originalPath, err := os.Getwd() - if err != nil { - return nil - } - - msg := utils.ResolvePlaceholderString(self.c.Tr.ChangingDirectoryTo, map[string]string{"path": path}) - self.c.LogCommand(msg, false) - - if err := os.Chdir(path); err != nil { - if os.IsNotExist(err) { - return errors.New(errMsg) - } - return err - } - - if err := commands.VerifyInGitRepo(self.c.OS()); err != nil { - if err := os.Chdir(originalPath); err != nil { - return err - } - - return err - } - - if err := self.recordDirectoryHelper.RecordCurrentDirectory(); err != nil { - return err - } - - self.c.Mutexes().RefreshingFilesMutex.Lock() - defer self.c.Mutexes().RefreshingFilesMutex.Unlock() - - return self.onNewRepo(appTypes.StartArgs{}, contextKey) - }) -} diff --git a/pkg/gui/controllers/helpers/search_helper.go b/pkg/gui/controllers/helpers/search_helper.go deleted file mode 100644 index 67af0695bab..00000000000 --- a/pkg/gui/controllers/helpers/search_helper.go +++ /dev/null @@ -1,331 +0,0 @@ -package helpers - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// NOTE: this helper supports both filtering and searching. Filtering is when -// the contents of the list are filtered, whereas searching does not actually -// change the contents of the list but instead just highlights the search. -// The general term we use to capture both searching and filtering is... -// 'searching', which is unfortunate but I can't think of a better name. - -type SearchHelper struct { - c *HelperCommon -} - -func NewSearchHelper( - c *HelperCommon, -) *SearchHelper { - return &SearchHelper{ - c: c, - } -} - -func (self *SearchHelper) OpenFilterPrompt(context types.IFilterableContext) error { - state := self.searchState() - - state.Context = context - - self.searchPrefixView().SetContent(context.FilterPrefix(self.c.Tr)) - promptView := self.promptView() - promptView.ClearTextArea() - self.OnPromptContentChanged("") - promptView.RenderTextArea() - - self.c.Context().Push(self.c.Contexts().Search, types.OnFocusOpts{}) - - return self.c.ResetKeybindings() -} - -func (self *SearchHelper) OpenSearchPrompt(context types.ISearchableContext) error { - state := self.searchState() - - state.PrevSearchIndex = -1 - - state.Context = context - - self.searchPrefixView().SetContent(self.c.Tr.SearchPrefix) - promptView := self.promptView() - promptView.ClearTextArea() - promptView.RenderTextArea() - - self.c.Context().Push(self.c.Contexts().Search, types.OnFocusOpts{}) - - return self.c.ResetKeybindings() -} - -func (self *SearchHelper) DisplayFilterStatus(context types.IFilterableContext) { - state := self.searchState() - - state.Context = context - searchString := context.GetFilter() - - self.searchPrefixView().SetContent(context.FilterPrefix(self.c.Tr)) - - promptView := self.promptView() - keybindingConfig := self.c.UserConfig().Keybinding - promptView.SetContent(fmt.Sprintf("matches for '%s' ", searchString) + theme.OptionsFgColor.Sprintf(self.c.Tr.ExitTextFilterMode, keybindings.Label(keybindingConfig.Universal.Return))) -} - -func (self *SearchHelper) DisplaySearchStatus(context types.ISearchableContext) { - state := self.searchState() - - state.Context = context - - self.searchPrefixView().SetContent(self.c.Tr.SearchPrefix) - index, totalCount := context.GetView().GetSearchStatus() - context.RenderSearchStatus(index, totalCount) -} - -func (self *SearchHelper) searchState() *types.SearchState { - return self.c.State().GetRepoState().GetSearchState() -} - -func (self *SearchHelper) searchPrefixView() *gocui.View { - return self.c.Views().SearchPrefix -} - -func (self *SearchHelper) promptView() *gocui.View { - return self.c.Contexts().Search.GetView() -} - -func (self *SearchHelper) promptContent() string { - return self.c.Contexts().Search.GetView().TextArea.GetContent() -} - -func (self *SearchHelper) Confirm() error { - state := self.searchState() - if self.promptContent() == "" { - return self.CancelPrompt() - } - - var err error - switch state.SearchType() { - case types.SearchTypeFilter: - self.ConfirmFilter() - case types.SearchTypeSearch: - err = self.ConfirmSearch() - case types.SearchTypeNone: - self.c.Context().Pop() - } - - if err != nil { - return err - } - - return self.c.ResetKeybindings() -} - -func (self *SearchHelper) ConfirmFilter() { - // We also do this on each keypress but we do it here again just in case - state := self.searchState() - - context, ok := state.Context.(types.IFilterableContext) - if !ok { - self.c.Log.Warnf("Context %s is not filterable", state.Context.GetKey()) - return - } - - self.OnPromptContentChanged(self.promptContent()) - filterString := self.promptContent() - if filterString != "" { - context.GetSearchHistory().Push(filterString) - } - - self.c.Context().Pop() -} - -func (self *SearchHelper) ConfirmSearch() error { - state := self.searchState() - - context, ok := state.Context.(types.ISearchableContext) - if !ok { - self.c.Log.Warnf("Context %s is searchable", state.Context.GetKey()) - return nil - } - - searchString := self.promptContent() - context.SetSearchString(searchString) - if searchString != "" { - context.GetSearchHistory().Push(searchString) - } - - self.c.Context().Pop() - - return context.GetView().Search(searchString, modelSearchResults(context)) -} - -func modelSearchResults(context types.ISearchableContext) []gocui.SearchPosition { - searchString := context.GetSearchString() - - var normalizedSearchStr string - // if we have any uppercase characters we'll do a case-sensitive search - caseSensitive := utils.ContainsUppercase(searchString) - if caseSensitive { - normalizedSearchStr = searchString - } else { - normalizedSearchStr = strings.ToLower(searchString) - } - - return context.ModelSearchResults(normalizedSearchStr, caseSensitive) -} - -func (self *SearchHelper) CancelPrompt() error { - self.Cancel() - - self.c.Context().Pop() - - return self.c.ResetKeybindings() -} - -func (self *SearchHelper) ScrollHistory(scrollIncrement int) { - state := self.searchState() - - context, ok := state.Context.(types.ISearchHistoryContext) - if !ok { - return - } - - states := context.GetSearchHistory() - - if val, err := states.PeekAt(state.PrevSearchIndex + scrollIncrement); err == nil { - state.PrevSearchIndex += scrollIncrement - promptView := self.promptView() - promptView.ClearTextArea() - promptView.TextArea.TypeString(val) - promptView.RenderTextArea() - self.OnPromptContentChanged(val) - } -} - -func (self *SearchHelper) Cancel() { - state := self.searchState() - - switch context := state.Context.(type) { - case types.IFilterableContext: - context.ClearFilter() - self.c.PostRefreshUpdate(context) - case types.ISearchableContext: - context.ClearSearchString() - context.GetView().ClearSearch() - default: - // do nothing - } - - self.HidePrompt() -} - -func (self *SearchHelper) OnPromptContentChanged(searchString string) { - state := self.searchState() - switch context := state.Context.(type) { - case types.IFilterableContext: - context.SetSelection(0) - context.GetView().SetOriginY(0) - context.SetFilter(searchString, self.c.UserConfig().Gui.UseFuzzySearch()) - self.c.PostRefreshUpdate(context) - case types.ISearchableContext: - // do nothing - default: - // do nothing (shouldn't land here) - } -} - -func (self *SearchHelper) ReApplyFilter(context types.Context) { - filterableContext, ok := context.(types.IFilterableContext) - if ok { - state := self.searchState() - if context == state.Context { - filterableContext.SetSelection(0) - filterableContext.GetView().SetOriginY(0) - } - filterableContext.ReApplyFilter(self.c.UserConfig().Gui.UseFuzzySearch()) - } -} - -func (self *SearchHelper) ReApplySearch(ctx types.Context) { - // Reapply the search if the model has changed. This is needed for contexts - // that use the model for searching, to pass the new model search positions - // to the view. - searchableContext, ok := ctx.(types.ISearchableContext) - if ok { - ctx.GetView().UpdateSearchResults(searchableContext.GetSearchString(), modelSearchResults(searchableContext)) - - state := self.searchState() - if ctx == state.Context { - // Re-render the "x of y" search status, unless the search prompt is - // open for typing. - if self.c.Context().Current().GetKey() != context.SEARCH_CONTEXT_KEY { - self.RenderSearchStatus(searchableContext) - } - } - } -} - -func (self *SearchHelper) RenderSearchStatus(c types.Context) { - if c.GetKey() == context.SEARCH_CONTEXT_KEY { - return - } - - if searchableContext, ok := c.(types.ISearchableContext); ok { - if searchableContext.IsSearching() { - self.setSearchingFrameColor() - self.DisplaySearchStatus(searchableContext) - return - } - } - if filterableContext, ok := c.(types.IFilterableContext); ok { - if filterableContext.IsFiltering() { - self.setSearchingFrameColor() - self.DisplayFilterStatus(filterableContext) - return - } - } - - self.HidePrompt() -} - -func (self *SearchHelper) CancelSearchIfSearching(c types.Context) { - if searchableContext, ok := c.(types.ISearchableContext); ok { - view := searchableContext.GetView() - if view != nil && view.IsSearching() { - view.ClearSearch() - searchableContext.ClearSearchString() - self.Cancel() - } - return - } - - if filterableContext, ok := c.(types.IFilterableContext); ok { - if filterableContext.IsFiltering() { - filterableContext.ClearFilter() - self.Cancel() - } - return - } -} - -func (self *SearchHelper) HidePrompt() { - self.setNonSearchingFrameColor() - - state := self.searchState() - state.Context = nil -} - -func (self *SearchHelper) setSearchingFrameColor() { - self.c.GocuiGui().SelFgColor = theme.SearchingActiveBorderColor - self.c.GocuiGui().SelFrameColor = theme.SearchingActiveBorderColor -} - -func (self *SearchHelper) setNonSearchingFrameColor() { - self.c.GocuiGui().SelFgColor = theme.ActiveBorderColor - self.c.GocuiGui().SelFrameColor = theme.ActiveBorderColor -} diff --git a/pkg/gui/controllers/helpers/signal_handling.go b/pkg/gui/controllers/helpers/signal_handling.go deleted file mode 100644 index 40d04f6895d..00000000000 --- a/pkg/gui/controllers/helpers/signal_handling.go +++ /dev/null @@ -1,59 +0,0 @@ -//go:build !windows - -package helpers - -import ( - "os" - "os/signal" - "syscall" - - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -func canSuspendApp() bool { - return true -} - -func sendStopSignal() error { - return syscall.Kill(0, syscall.SIGSTOP) -} - -// setForegroundPgrp sets the current process group as the foreground process group -// for the terminal, allowing the program to read input after resuming from suspension. -func setForegroundPgrp() error { - fd, err := unix.Open("/dev/tty", unix.O_RDWR, 0) - if err != nil { - return err - } - defer unix.Close(fd) - - pgid := syscall.Getpgrp() - - return unix.IoctlSetPointerInt(fd, unix.TIOCSPGRP, pgid) -} - -func handleResumeSignal(log *logrus.Entry, onResume func() error) { - if err := setForegroundPgrp(); err != nil { - log.Warning(err) - return - } - - if err := onResume(); err != nil { - log.Warning(err) - } -} - -func installResumeSignalHandler(log *logrus.Entry, onResume func() error) { - go func() { - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGCONT) - - for sig := range sigs { - switch sig { - case syscall.SIGCONT: - handleResumeSignal(log, onResume) - } - } - }() -} diff --git a/pkg/gui/controllers/helpers/signal_handling_windows.go b/pkg/gui/controllers/helpers/signal_handling_windows.go deleted file mode 100644 index 8517e775d61..00000000000 --- a/pkg/gui/controllers/helpers/signal_handling_windows.go +++ /dev/null @@ -1,16 +0,0 @@ -package helpers - -import ( - "github.com/sirupsen/logrus" -) - -func canSuspendApp() bool { - return false -} - -func sendStopSignal() error { - return nil -} - -func installResumeSignalHandler(log *logrus.Entry, onResume func() error) { -} diff --git a/pkg/gui/controllers/helpers/snake_helper.go b/pkg/gui/controllers/helpers/snake_helper.go deleted file mode 100644 index 8477933736c..00000000000 --- a/pkg/gui/controllers/helpers/snake_helper.go +++ /dev/null @@ -1,76 +0,0 @@ -package helpers - -import ( - "errors" - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/snake" -) - -type SnakeHelper struct { - c *HelperCommon - game *snake.Game -} - -func NewSnakeHelper(c *HelperCommon) *SnakeHelper { - return &SnakeHelper{ - c: c, - } -} - -func (self *SnakeHelper) StartGame() { - view := self.c.Views().Snake - - game := snake.NewGame(view.InnerWidth(), view.InnerHeight(), self.renderSnakeGame, self.c.LogAction) - self.game = game - game.Start() -} - -func (self *SnakeHelper) ExitGame() { - self.game.Exit() -} - -func (self *SnakeHelper) SetDirection(direction snake.Direction) { - self.game.SetDirection(direction) -} - -func (self *SnakeHelper) renderSnakeGame(cells [][]snake.CellType, alive bool) { - view := self.c.Views().Snake - - if !alive { - self.c.OnUIThread(func() error { return errors.New(self.c.Tr.YouDied) }) - return - } - - output := self.drawSnakeGame(cells) - - view.Clear() - fmt.Fprint(view, output) - self.c.Render() -} - -func (self *SnakeHelper) drawSnakeGame(cells [][]snake.CellType) string { - writer := &strings.Builder{} - - for i, row := range cells { - for _, cell := range row { - switch cell { - case snake.None: - writer.WriteString(" ") - case snake.Snake: - writer.WriteString("█") - case snake.Food: - writer.WriteString(style.FgMagenta.Sprint("█")) - } - } - - if i < len(cells) { - writer.WriteString("\n") - } - } - - output := writer.String() - return output -} diff --git a/pkg/gui/controllers/helpers/staging_helper.go b/pkg/gui/controllers/helpers/staging_helper.go deleted file mode 100644 index 55b9c133bd0..00000000000 --- a/pkg/gui/controllers/helpers/staging_helper.go +++ /dev/null @@ -1,127 +0,0 @@ -package helpers - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/patch_exploring" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type StagingHelper struct { - c *HelperCommon -} - -func NewStagingHelper( - c *HelperCommon, -) *StagingHelper { - return &StagingHelper{ - c: c, - } -} - -// NOTE: used from outside this file -func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) { - secondaryFocused := self.secondaryStagingFocused() - mainFocused := self.mainStagingFocused() - - // this method could be called when the staging panel is not being used, - // in which case we don't want to do anything. - if !mainFocused && !secondaryFocused { - return - } - - mainSelectedLineIdx := -1 - secondarySelectedLineIdx := -1 - if focusOpts.ClickedViewLineIdx > 0 { - if secondaryFocused { - secondarySelectedLineIdx = focusOpts.ClickedViewLineIdx - } else { - mainSelectedLineIdx = focusOpts.ClickedViewLineIdx - } - } - - mainContext := self.c.Contexts().Staging - secondaryContext := self.c.Contexts().StagingSecondary - - var file *models.File - node := self.c.Contexts().Files.GetSelected() - if node != nil { - file = node.File - } - - if file == nil || (!file.HasUnstagedChanges && !file.HasStagedChanges) { - self.handleStagingEscape() - return - } - - mainDiff := self.c.Git().WorkingTree.WorktreeFileDiff(file, true, false) - secondaryDiff := self.c.Git().WorkingTree.WorktreeFileDiff(file, true, true) - - // grabbing locks here and releasing before we finish the function - // because pushing say the secondary context could mean entering this function - // again, and we don't want to have a deadlock - mainContext.GetMutex().Lock() - secondaryContext.GetMutex().Lock() - - hunkMode := self.c.UserConfig().Gui.UseHunkModeInStagingView - mainContext.SetState( - patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetView(), mainContext.GetState(), hunkMode), - ) - - secondaryContext.SetState( - patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetView(), secondaryContext.GetState(), hunkMode), - ) - - mainState := mainContext.GetState() - secondaryState := secondaryContext.GetState() - - mainContent := mainContext.GetContentToRender() - secondaryContent := secondaryContext.GetContentToRender() - - mainContext.GetMutex().Unlock() - secondaryContext.GetMutex().Unlock() - - if mainState == nil && secondaryState == nil { - self.handleStagingEscape() - return - } - - if mainState == nil && !secondaryFocused { - self.c.Context().Push(secondaryContext, focusOpts) - return - } - - if secondaryState == nil && secondaryFocused { - self.c.Context().Push(mainContext, focusOpts) - return - } - - if secondaryFocused { - self.c.Contexts().StagingSecondary.FocusSelection() - } else { - self.c.Contexts().Staging.FocusSelection() - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Staging, - Main: &types.ViewUpdateOpts{ - Task: types.NewRenderStringWithoutScrollTask(mainContent), - Title: self.c.Tr.UnstagedChanges, - }, - Secondary: &types.ViewUpdateOpts{ - Task: types.NewRenderStringWithoutScrollTask(secondaryContent), - Title: self.c.Tr.StagedChanges, - }, - }) -} - -func (self *StagingHelper) handleStagingEscape() { - self.c.Context().Push(self.c.Contexts().Files, types.OnFocusOpts{}) -} - -func (self *StagingHelper) secondaryStagingFocused() bool { - return self.c.Context().CurrentStatic().GetKey() == self.c.Contexts().StagingSecondary.GetKey() -} - -func (self *StagingHelper) mainStagingFocused() bool { - return self.c.Context().CurrentStatic().GetKey() == self.c.Contexts().Staging.GetKey() -} diff --git a/pkg/gui/controllers/helpers/sub_commits_helper.go b/pkg/gui/controllers/helpers/sub_commits_helper.go deleted file mode 100644 index 080e1b45611..00000000000 --- a/pkg/gui/controllers/helpers/sub_commits_helper.go +++ /dev/null @@ -1,79 +0,0 @@ -package helpers - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type SubCommitsHelper struct { - c *HelperCommon - - refreshHelper *RefreshHelper -} - -func NewSubCommitsHelper( - c *HelperCommon, - refreshHelper *RefreshHelper, -) *SubCommitsHelper { - return &SubCommitsHelper{ - c: c, - refreshHelper: refreshHelper, - } -} - -type ViewSubCommitsOpts struct { - Ref models.Ref - RefToShowDivergenceFrom string - TitleRef string - Context types.Context - ShowBranchHeads bool -} - -func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error { - commits, err := self.c.Git().Loaders.CommitLoader.GetCommits( - git_commands.GetCommitsOptions{ - Limit: true, - FilterPath: self.c.Modes().Filtering.GetPath(), - FilterAuthor: self.c.Modes().Filtering.GetAuthor(), - IncludeRebaseCommits: false, - RefName: opts.Ref.FullRefName(), - RefForPushedStatus: opts.Ref, - RefToShowDivergenceFrom: opts.RefToShowDivergenceFrom, - MainBranches: self.c.Model().MainBranches, - HashPool: self.c.Model().HashPool, - }, - ) - if err != nil { - return err - } - - self.setSubCommits(commits) - self.refreshHelper.RefreshAuthors(commits) - - subCommitsContext := self.c.Contexts().SubCommits - subCommitsContext.SetSelection(0) - subCommitsContext.SetParentContext(opts.Context) - subCommitsContext.SetWindowName(opts.Context.GetWindowName()) - subCommitsContext.SetTitleRef(utils.TruncateWithEllipsis(opts.TitleRef, 50)) - subCommitsContext.SetRef(opts.Ref) - subCommitsContext.SetRefToShowDivergenceFrom(opts.RefToShowDivergenceFrom) - subCommitsContext.SetLimitCommits(true) - subCommitsContext.SetShowBranchHeads(opts.ShowBranchHeads) - subCommitsContext.ClearSearchString() - subCommitsContext.GetView().ClearSearch() - subCommitsContext.GetView().TitlePrefix = opts.Context.GetView().TitlePrefix - - self.c.PostRefreshUpdate(self.c.Contexts().SubCommits) - - self.c.Context().Push(self.c.Contexts().SubCommits, types.OnFocusOpts{}) - return nil -} - -func (self *SubCommitsHelper) setSubCommits(commits []*models.Commit) { - self.c.Mutexes().SubCommitsMutex.Lock() - defer self.c.Mutexes().SubCommitsMutex.Unlock() - - self.c.Model().SubCommits = commits -} diff --git a/pkg/gui/controllers/helpers/suggestions_helper.go b/pkg/gui/controllers/helpers/suggestions_helper.go deleted file mode 100644 index 38ef4543077..00000000000 --- a/pkg/gui/controllers/helpers/suggestions_helper.go +++ /dev/null @@ -1,219 +0,0 @@ -package helpers - -import ( - "fmt" - "os" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/jesseduffield/minimal/gitignore" - "github.com/samber/lo" - "golang.org/x/exp/slices" - "gopkg.in/ozeidan/fuzzy-patricia.v3/patricia" -) - -// Thinking out loud: I'm typically a staunch advocate of organising code by feature rather than type, -// because colocating code that relates to the same feature means far less effort -// to get all the context you need to work on any particular feature. But the one -// major benefit of grouping by type is that it makes it makes it less likely that -// somebody will re-implement the same logic twice, because they can quickly see -// if a certain method has been used for some use case, given that as a starting point -// they know about the type. In that vein, I'm including all our functions for -// finding suggestions in this file, so that it's easy to see if a function already -// exists for fetching a particular model. - -type SuggestionsHelper struct { - c *HelperCommon -} - -func NewSuggestionsHelper( - c *HelperCommon, -) *SuggestionsHelper { - return &SuggestionsHelper{ - c: c, - } -} - -func (self *SuggestionsHelper) getRemoteNames() []string { - return lo.Map(self.c.Model().Remotes, func(remote *models.Remote, _ int) string { - return remote.Name - }) -} - -func matchesToSuggestions(matches []string) []*types.Suggestion { - return lo.Map(matches, func(match string, _ int) *types.Suggestion { - return &types.Suggestion{ - Value: match, - Label: match, - } - }) -} - -func (self *SuggestionsHelper) GetRemoteSuggestionsFunc() func(string) []*types.Suggestion { - remoteNames := self.getRemoteNames() - - return FilterFunc(remoteNames, self.c.UserConfig().Gui.UseFuzzySearch()) -} - -func (self *SuggestionsHelper) getBranchNames() []string { - return lo.Map(self.c.Model().Branches, func(branch *models.Branch, _ int) string { - return branch.Name - }) -} - -func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*types.Suggestion { - branchNames := self.getBranchNames() - - return func(input string) []*types.Suggestion { - var matchingBranchNames []string - if input == "" { - matchingBranchNames = branchNames - } else { - matchingBranchNames = utils.FilterStrings(input, branchNames, self.c.UserConfig().Gui.UseFuzzySearch()) - } - - return lo.Map(matchingBranchNames, func(branchName string, _ int) *types.Suggestion { - return &types.Suggestion{ - Value: branchName, - Label: presentation.GetBranchTextStyle(branchName).Sprint(branchName), - } - }) - } -} - -// here we asynchronously fetch the latest set of paths in the repo and store in -// self.c.Model().FilesTrie. On the main thread we'll be doing a fuzzy search via -// self.c.Model().FilesTrie. So if we've looked for a file previously, we'll start with -// the old trie and eventually it'll be swapped out for the new one. -func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*types.Suggestion { - _ = self.c.WithWaitingStatus(self.c.Tr.LoadingFileSuggestions, func(gocui.Task) error { - trie := patricia.NewTrie() - // load every non-gitignored file in the repo - ignore, err := gitignore.FromGit() - if err != nil { - return err - } - - err = ignore.Walk(".", - func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if path != "." { - trie.Insert(patricia.Prefix(path), path) - } - return nil - }) - - // cache the trie for future use - self.c.Model().FilesTrie = trie - - self.c.Contexts().Suggestions.RefreshSuggestions() - - return err - }) - - return func(input string) []*types.Suggestion { - matchingNames := []string{} - if self.c.UserConfig().Gui.UseFuzzySearch() { - _ = self.c.Model().FilesTrie.VisitFuzzy(patricia.Prefix(input), true, func(prefix patricia.Prefix, item patricia.Item, skipped int) error { - matchingNames = append(matchingNames, item.(string)) - return nil - }) - - // doing another fuzzy search for good measure - matchingNames = utils.FilterStrings(input, matchingNames, true) - } else { - substrings := strings.Fields(input) - _ = self.c.Model().FilesTrie.Visit(func(prefix patricia.Prefix, item patricia.Item) error { - for _, sub := range substrings { - if !utils.CaseAwareContains(item.(string), sub) { - return nil - } - } - matchingNames = append(matchingNames, item.(string)) - return nil - }) - } - - return matchesToSuggestions(matchingNames) - } -} - -func (self *SuggestionsHelper) getRemoteBranchNames(separator string) []string { - return lo.FlatMap(self.c.Model().Remotes, func(remote *models.Remote, _ int) []string { - return lo.Map(remote.Branches, func(branch *models.RemoteBranch, _ int) string { - return fmt.Sprintf("%s%s%s", remote.Name, separator, branch.Name) - }) - }) -} - -func (self *SuggestionsHelper) getRemoteBranchNamesForRemote(remoteName string) []string { - remote, ok := lo.Find(self.c.Model().Remotes, func(remote *models.Remote) bool { - return remote.Name == remoteName - }) - if ok { - return lo.Map(remote.Branches, func(branch *models.RemoteBranch, _ int) string { - return branch.Name - }) - } - return nil -} - -func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion { - return FilterFunc(self.getRemoteBranchNames(separator), self.c.UserConfig().Gui.UseFuzzySearch()) -} - -func (self *SuggestionsHelper) GetRemoteBranchesForRemoteSuggestionsFunc(remoteName string) func(string) []*types.Suggestion { - return FilterFunc(self.getRemoteBranchNamesForRemote(remoteName), self.c.UserConfig().Gui.UseFuzzySearch()) -} - -func (self *SuggestionsHelper) getTagNames() []string { - return lo.Map(self.c.Model().Tags, func(tag *models.Tag, _ int) string { - return tag.Name - }) -} - -func (self *SuggestionsHelper) GetTagsSuggestionsFunc() func(string) []*types.Suggestion { - tagNames := self.getTagNames() - - return FilterFunc(tagNames, self.c.UserConfig().Gui.UseFuzzySearch()) -} - -func (self *SuggestionsHelper) GetRefsSuggestionsFunc() func(string) []*types.Suggestion { - remoteBranchNames := self.getRemoteBranchNames("/") - localBranchNames := self.getBranchNames() - tagNames := self.getTagNames() - additionalRefNames := []string{"HEAD", "FETCH_HEAD", "MERGE_HEAD", "ORIG_HEAD"} - - refNames := append(append(append(remoteBranchNames, localBranchNames...), tagNames...), additionalRefNames...) - - return FilterFunc(refNames, self.c.UserConfig().Gui.UseFuzzySearch()) -} - -func (self *SuggestionsHelper) GetAuthorsSuggestionsFunc() func(string) []*types.Suggestion { - authors := lo.Map(lo.Values(self.c.Model().Authors), func(author *models.Author, _ int) string { - return author.Combined() - }) - - slices.Sort(authors) - - return FilterFunc(authors, self.c.UserConfig().Gui.UseFuzzySearch()) -} - -func FilterFunc(options []string, useFuzzySearch bool) func(string) []*types.Suggestion { - return func(input string) []*types.Suggestion { - var matches []string - if input == "" { - matches = options - } else { - matches = utils.FilterStrings(input, options, useFuzzySearch) - } - - return matchesToSuggestions(matches) - } -} diff --git a/pkg/gui/controllers/helpers/suspend_resume_helper.go b/pkg/gui/controllers/helpers/suspend_resume_helper.go deleted file mode 100644 index cc63af495f5..00000000000 --- a/pkg/gui/controllers/helpers/suspend_resume_helper.go +++ /dev/null @@ -1,31 +0,0 @@ -package helpers - -type SuspendResumeHelper struct { - c *HelperCommon -} - -func NewSuspendResumeHelper(c *HelperCommon) *SuspendResumeHelper { - return &SuspendResumeHelper{ - c: c, - } -} - -func (s *SuspendResumeHelper) CanSuspendApp() bool { - return canSuspendApp() -} - -func (s *SuspendResumeHelper) SuspendApp() error { - if !canSuspendApp() { - return nil - } - - if err := s.c.Suspend(); err != nil { - return err - } - - return sendStopSignal() -} - -func (s *SuspendResumeHelper) InstallResumeSignalHandler() { - installResumeSignalHandler(s.c.Log, s.c.Resume) -} diff --git a/pkg/gui/controllers/helpers/tags_helper.go b/pkg/gui/controllers/helpers/tags_helper.go deleted file mode 100644 index 6a7e4721948..00000000000 --- a/pkg/gui/controllers/helpers/tags_helper.go +++ /dev/null @@ -1,68 +0,0 @@ -package helpers - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type TagsHelper struct { - c *HelperCommon - commitsHelper *CommitsHelper - gpg *GpgHelper -} - -func NewTagsHelper(c *HelperCommon, commitsHelper *CommitsHelper, gpg *GpgHelper) *TagsHelper { - return &TagsHelper{ - c: c, - commitsHelper: commitsHelper, - gpg: gpg, - } -} - -func (self *TagsHelper) OpenCreateTagPrompt(ref string, onCreate func()) error { - onConfirm := func(tagName string, description string) error { - prompt := utils.ResolvePlaceholderString( - self.c.Tr.ForceTagPrompt, - map[string]string{ - "tagName": tagName, - "cancelKey": self.c.UserConfig().Keybinding.Universal.Return, - "confirmKey": self.c.UserConfig().Keybinding.Universal.Confirm, - }, - ) - force := self.c.Git().Tag.HasTag(tagName) - return self.c.ConfirmIf(force, types.ConfirmOpts{ - Title: self.c.Tr.ForceTag, - Prompt: prompt, - HandleConfirm: func() error { - var command *oscommands.CmdObj - if description != "" || self.c.Git().Config.GetGpgTagSign() { - self.c.LogAction(self.c.Tr.Actions.CreateAnnotatedTag) - command = self.c.Git().Tag.CreateAnnotatedObj(tagName, ref, description, force) - } else { - self.c.LogAction(self.c.Tr.Actions.CreateLightweightTag) - command = self.c.Git().Tag.CreateLightweightObj(tagName, ref, force) - } - - return self.gpg.WithGpgHandling(command, git_commands.TagGpgSign, self.c.Tr.CreatingTag, func() error { - return nil - }, []types.RefreshableView{types.COMMITS, types.TAGS}) - }, - }) - } - - self.commitsHelper.OpenCommitMessagePanel( - &OpenCommitMessagePanelOpts{ - CommitIndex: context.NoCommitIndex, - InitialMessage: "", - SummaryTitle: self.c.Tr.TagNameTitle, - DescriptionTitle: self.c.Tr.TagMessageTitle, - PreserveMessage: false, - OnConfirm: onConfirm, - }, - ) - - return nil -} diff --git a/pkg/gui/controllers/helpers/update_helper.go b/pkg/gui/controllers/helpers/update_helper.go deleted file mode 100644 index 4491b433058..00000000000 --- a/pkg/gui/controllers/helpers/update_helper.go +++ /dev/null @@ -1,102 +0,0 @@ -package helpers - -import ( - "errors" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/updates" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type UpdateHelper struct { - c *HelperCommon - updater *updates.Updater -} - -func NewUpdateHelper(c *HelperCommon, updater *updates.Updater) *UpdateHelper { - return &UpdateHelper{ - c: c, - updater: updater, - } -} - -func (self *UpdateHelper) CheckForUpdateInBackground() { - self.updater.CheckForNewUpdate(func(newVersion string, err error) error { - if err != nil { - // ignoring the error for now so that I'm not annoying users - self.c.Log.Error(err.Error()) - return nil - } - if newVersion == "" { - return nil - } - if self.c.UserConfig().Update.Method == "background" { - self.startUpdating(newVersion) - return nil - } - return self.showUpdatePrompt(newVersion) - }, false) -} - -func (self *UpdateHelper) CheckForUpdateInForeground() error { - return self.c.WithWaitingStatus(self.c.Tr.CheckingForUpdates, func(gocui.Task) error { - self.updater.CheckForNewUpdate(func(newVersion string, err error) error { - if err != nil { - return err - } - if newVersion == "" { - return errors.New(self.c.Tr.FailedToRetrieveLatestVersionErr) - } - return self.showUpdatePrompt(newVersion) - }, true) - - return nil - }) -} - -func (self *UpdateHelper) startUpdating(newVersion string) { - _ = self.c.WithWaitingStatus(self.c.Tr.UpdateInProgressWaitingStatus, func(gocui.Task) error { - self.c.State().SetUpdating(true) - err := self.updater.Update(newVersion) - return self.onUpdateFinish(err) - }) -} - -func (self *UpdateHelper) onUpdateFinish(err error) error { - self.c.State().SetUpdating(false) - self.c.OnUIThread(func() error { - self.c.SetViewContent(self.c.Views().AppStatus, "") - if err != nil { - errMessage := utils.ResolvePlaceholderString( - self.c.Tr.UpdateFailedErr, map[string]string{ - "errMessage": err.Error(), - }, - ) - return errors.New(errMessage) - } - self.c.Alert(self.c.Tr.UpdateCompletedTitle, self.c.Tr.UpdateCompleted) - return nil - }) - - return nil -} - -func (self *UpdateHelper) showUpdatePrompt(newVersion string) error { - message := utils.ResolvePlaceholderString( - self.c.Tr.UpdateAvailable, map[string]string{ - "newVersion": newVersion, - }, - ) - - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.UpdateAvailableTitle, - Prompt: message, - HandleConfirm: func() error { - self.startUpdating(newVersion) - return nil - }, - }) - - return nil -} diff --git a/pkg/gui/controllers/helpers/upstream_helper.go b/pkg/gui/controllers/helpers/upstream_helper.go deleted file mode 100644 index 092ca6bb1e8..00000000000 --- a/pkg/gui/controllers/helpers/upstream_helper.go +++ /dev/null @@ -1,78 +0,0 @@ -package helpers - -import ( - "errors" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type UpstreamHelper struct { - c *HelperCommon - - getRemoteBranchesSuggestionsFunc func(string) func(string) []*types.Suggestion -} - -func NewUpstreamHelper( - c *HelperCommon, - getRemoteBranchesSuggestionsFunc func(string) func(string) []*types.Suggestion, -) *UpstreamHelper { - return &UpstreamHelper{ - c: c, - getRemoteBranchesSuggestionsFunc: getRemoteBranchesSuggestionsFunc, - } -} - -func (self *UpstreamHelper) ParseUpstream(upstream string) (string, string, error) { - var upstreamBranch, upstreamRemote string - split := strings.Split(upstream, " ") - if len(split) != 2 { - return "", "", errors.New(self.c.Tr.InvalidUpstream) - } - - upstreamRemote = split[0] - upstreamBranch = split[1] - - return upstreamRemote, upstreamBranch, nil -} - -func (self *UpstreamHelper) promptForUpstream(initialContent string, onConfirm func(string) error) error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.EnterUpstream, - InitialContent: initialContent, - FindSuggestionsFunc: self.getRemoteBranchesSuggestionsFunc(" "), - HandleConfirm: onConfirm, - }) - - return nil -} - -func (self *UpstreamHelper) PromptForUpstreamWithInitialContent(currentBranch *models.Branch, onConfirm func(string) error) error { - suggestedRemote := self.GetSuggestedRemote() - initialContent := suggestedRemote + " " + currentBranch.Name - - return self.promptForUpstream(initialContent, onConfirm) -} - -func (self *UpstreamHelper) PromptForUpstreamWithoutInitialContent(_ *models.Branch, onConfirm func(string) error) error { - return self.promptForUpstream("", onConfirm) -} - -func (self *UpstreamHelper) GetSuggestedRemote() string { - return getSuggestedRemote(self.c.Model().Remotes) -} - -func getSuggestedRemote(remotes []*models.Remote) string { - if len(remotes) == 0 { - return "origin" - } - - for _, remote := range remotes { - if remote.Name == "origin" { - return remote.Name - } - } - - return remotes[0].Name -} diff --git a/pkg/gui/controllers/helpers/upstream_helper_test.go b/pkg/gui/controllers/helpers/upstream_helper_test.go deleted file mode 100644 index 9742ef944eb..00000000000 --- a/pkg/gui/controllers/helpers/upstream_helper_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package helpers - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/samber/lo" - "github.com/stretchr/testify/assert" -) - -func TestGetSuggestedRemote(t *testing.T) { - cases := []struct { - remotes []*models.Remote - expected string - }{ - {mkRemoteList(), "origin"}, - {mkRemoteList("upstream", "origin", "foo"), "origin"}, - {mkRemoteList("upstream", "foo", "bar"), "upstream"}, - } - - for _, c := range cases { - result := getSuggestedRemote(c.remotes) - assert.EqualValues(t, c.expected, result) - } -} - -func mkRemoteList(names ...string) []*models.Remote { - return lo.Map(names, func(name string, _ int) *models.Remote { - return &models.Remote{Name: name} - }) -} diff --git a/pkg/gui/controllers/helpers/view_helper.go b/pkg/gui/controllers/helpers/view_helper.go deleted file mode 100644 index c8d9ad94e2b..00000000000 --- a/pkg/gui/controllers/helpers/view_helper.go +++ /dev/null @@ -1,31 +0,0 @@ -package helpers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type ViewHelper struct { - c *HelperCommon -} - -func NewViewHelper(c *HelperCommon, contexts *context.ContextTree) *ViewHelper { - return &ViewHelper{ - c: c, - } -} - -func (self *ViewHelper) ContextForView(viewName string) (types.Context, bool) { - view, err := self.c.GocuiGui().View(viewName) - if err != nil { - return nil, false - } - - for _, context := range self.c.Contexts().Flatten() { - if context.GetViewName() == view.Name() { - return context, true - } - } - - return nil, false -} diff --git a/pkg/gui/controllers/helpers/window_arrangement_helper.go b/pkg/gui/controllers/helpers/window_arrangement_helper.go deleted file mode 100644 index f2d3ff01252..00000000000 --- a/pkg/gui/controllers/helpers/window_arrangement_helper.go +++ /dev/null @@ -1,498 +0,0 @@ -package helpers - -import ( - "fmt" - "math" - "strings" - - "github.com/jesseduffield/lazycore/pkg/boxlayout" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "golang.org/x/exp/slices" -) - -// In this file we use the boxlayout package, along with knowledge about the app's state, -// to arrange the windows (i.e. panels) on the screen. - -type WindowArrangementHelper struct { - c *HelperCommon - windowHelper *WindowHelper - modeHelper *ModeHelper - appStatusHelper *AppStatusHelper -} - -func NewWindowArrangementHelper( - c *HelperCommon, - windowHelper *WindowHelper, - modeHelper *ModeHelper, - appStatusHelper *AppStatusHelper, -) *WindowArrangementHelper { - return &WindowArrangementHelper{ - c: c, - windowHelper: windowHelper, - modeHelper: modeHelper, - appStatusHelper: appStatusHelper, - } -} - -type WindowArrangementArgs struct { - // Width of the screen (in characters) - Width int - // Height of the screen (in characters) - Height int - // User config - UserConfig *config.UserConfig - // Name of the currently focused window. (It's actually the current static window, meaning - // popups are ignored) - CurrentWindow string - // Name of the current side window (i.e. the current window in the left - // section of the UI) - CurrentSideWindow string - // Whether the main panel is split (as is the case e.g. when a file has both - // staged and unstaged changes) - SplitMainPanel bool - // The current screen mode (normal, half, full) - ScreenMode types.ScreenMode - // The content shown on the bottom left of the screen when showing a loader - // or toast e.g. 'Rebasing /' - AppStatus string - // The content shown on the bottom right of the screen (e.g. the 'donate', - // 'ask question' links or a message about the current mode e.g. rebase mode) - InformationStr string - // Whether to show the extras window which contains the command log context - ShowExtrasWindow bool - // Whether we are in a demo (which is used for generating demo gifs for the - // repo's readme) - InDemo bool - // Whether any mode is active (e.g. rebasing, cherry picking, etc) - IsAnyModeActive bool - // Whether the search prompt is shown in the bottom left - InSearchPrompt bool - // One of '' (not searching), 'Search: ', and 'Filter: ' - SearchPrefix string -} - -func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions { - width, height := self.c.GocuiGui().Size() - repoState := self.c.State().GetRepoState() - - var searchPrefix string - if filterableContext, ok := repoState.GetSearchState().Context.(types.IFilterableContext); ok { - searchPrefix = filterableContext.FilterPrefix(self.c.Tr) - } else { - searchPrefix = self.c.Tr.SearchPrefix - } - - args := WindowArrangementArgs{ - Width: width, - Height: height, - UserConfig: self.c.UserConfig(), - CurrentWindow: self.c.Context().CurrentStatic().GetWindowName(), - CurrentSideWindow: self.c.Context().CurrentSide().GetWindowName(), - SplitMainPanel: repoState.GetSplitMainPanel(), - ScreenMode: repoState.GetScreenMode(), - AppStatus: appStatus, - InformationStr: informationStr, - ShowExtrasWindow: self.c.State().GetShowExtrasWindow(), - InDemo: self.c.InDemo(), - IsAnyModeActive: self.modeHelper.IsAnyModeActive(), - InSearchPrompt: repoState.InSearchPrompt(), - SearchPrefix: searchPrefix, - } - - return GetWindowDimensions(args) -} - -func shouldUsePortraitMode(args WindowArrangementArgs) bool { - if args.ScreenMode == types.SCREEN_HALF { - return args.UserConfig.Gui.EnlargedSideViewLocation == "top" - } - - switch args.UserConfig.Gui.PortraitMode { - case "never": - return false - case "always": - return true - default: // "auto" or any garbage values in PortraitMode value - return args.Width <= 84 && args.Height > 45 - } -} - -func GetWindowDimensions(args WindowArrangementArgs) map[string]boxlayout.Dimensions { - sideSectionWeight, mainSectionWeight := getMidSectionWeights(args) - - sidePanelsDirection := boxlayout.COLUMN - if shouldUsePortraitMode(args) { - sidePanelsDirection = boxlayout.ROW - } - - showInfoSection := args.UserConfig.Gui.ShowBottomLine || - args.InSearchPrompt || - args.IsAnyModeActive || - args.AppStatus != "" - infoSectionSize := 0 - if showInfoSection { - infoSectionSize = 1 - } - - root := &boxlayout.Box{ - Direction: boxlayout.ROW, - Children: []*boxlayout.Box{ - { - Direction: sidePanelsDirection, - Weight: 1, - Children: []*boxlayout.Box{ - { - Direction: boxlayout.ROW, - Weight: sideSectionWeight, - ConditionalChildren: sidePanelChildren(args), - }, - { - Direction: boxlayout.ROW, - Weight: mainSectionWeight, - Children: mainPanelChildren(args), - }, - }, - }, - { - Direction: boxlayout.COLUMN, - Size: infoSectionSize, - Children: infoSectionChildren(args), - }, - }, - } - - layerOneWindows := boxlayout.ArrangeWindows(root, 0, 0, args.Width, args.Height) - limitWindows := boxlayout.ArrangeWindows(&boxlayout.Box{Window: "limit"}, 0, 0, args.Width, args.Height) - - return MergeMaps(layerOneWindows, limitWindows) -} - -func mainPanelChildren(args WindowArrangementArgs) []*boxlayout.Box { - mainPanelsDirection := boxlayout.ROW - if splitMainPanelSideBySide(args) { - mainPanelsDirection = boxlayout.COLUMN - } - - result := []*boxlayout.Box{ - { - Direction: mainPanelsDirection, - Children: mainSectionChildren(args), - Weight: 1, - }, - } - if args.ShowExtrasWindow { - result = append(result, &boxlayout.Box{ - Window: "extras", - Size: getExtrasWindowSize(args), - }) - } - return result -} - -func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V { - result := map[K]V{} - for _, currMap := range maps { - for key, value := range currMap { - result[key] = value - } - } - - return result -} - -func mainSectionChildren(args WindowArrangementArgs) []*boxlayout.Box { - // if we're not in split mode we can just show the one main panel. Likewise if - // the main panel is focused and we're in full-screen mode - if !args.SplitMainPanel || (args.ScreenMode == types.SCREEN_FULL && args.CurrentWindow == "main") { - return []*boxlayout.Box{ - { - Window: "main", - Weight: 1, - }, - } - } - - if args.CurrentWindow == "secondary" && args.ScreenMode == types.SCREEN_FULL { - return []*boxlayout.Box{ - { - Window: "secondary", - Weight: 1, - }, - } - } - - return []*boxlayout.Box{ - { - Window: "main", - Weight: 1, - }, - { - Window: "secondary", - Weight: 1, - }, - } -} - -func getMidSectionWeights(args WindowArrangementArgs) (int, int) { - sidePanelWidthRatio := args.UserConfig.Gui.SidePanelWidth - // Using 120 so that the default of 0.3333 will remain consistent with previous behavior - const maxColumnCount = 120 - mainSectionWeight := int(math.Round(maxColumnCount * (1 - sidePanelWidthRatio))) - sideSectionWeight := int(math.Round(maxColumnCount * sidePanelWidthRatio)) - - if splitMainPanelSideBySide(args) { - mainSectionWeight = sideSectionWeight * 5 // need to shrink side panel to make way for main panels if side-by-side - } - - if args.CurrentWindow == "main" || args.CurrentWindow == "secondary" { - if args.ScreenMode == types.SCREEN_HALF || args.ScreenMode == types.SCREEN_FULL { - sideSectionWeight = 0 - } - } else { - if args.ScreenMode == types.SCREEN_HALF { - if args.UserConfig.Gui.EnlargedSideViewLocation == "top" { - mainSectionWeight = sideSectionWeight * 2 - } else { - mainSectionWeight = sideSectionWeight - } - } else if args.ScreenMode == types.SCREEN_FULL { - mainSectionWeight = 0 - } - } - - return sideSectionWeight, mainSectionWeight -} - -func infoSectionChildren(args WindowArrangementArgs) []*boxlayout.Box { - if args.InSearchPrompt { - return []*boxlayout.Box{ - { - Window: "searchPrefix", - Size: utils.StringWidth(args.SearchPrefix), - }, - { - Window: "search", - Weight: 1, - }, - } - } - - statusSpacerPrefix := "statusSpacer" - spacerBoxIndex := 0 - maxSpacerBoxIndex := 2 // See pkg/gui/types/views.go - // Returns a box with size 1 to be used as padding between views - spacerBox := func() *boxlayout.Box { - spacerBoxIndex++ - - if spacerBoxIndex > maxSpacerBoxIndex { - panic("Too many spacer boxes") - } - - return &boxlayout.Box{Window: fmt.Sprintf("%s%d", statusSpacerPrefix, spacerBoxIndex), Size: 1} - } - - // Returns a box with weight 1 to be used as flexible padding between views - flexibleSpacerBox := func() *boxlayout.Box { - spacerBoxIndex++ - - if spacerBoxIndex > maxSpacerBoxIndex { - panic("Too many spacer boxes") - } - - return &boxlayout.Box{Window: fmt.Sprintf("%s%d", statusSpacerPrefix, spacerBoxIndex), Weight: 1} - } - - // Adds spacer boxes inbetween given boxes - insertSpacerBoxes := func(boxes []*boxlayout.Box) []*boxlayout.Box { - for i := len(boxes) - 1; i >= 1; i-- { - // ignore existing spacer boxes - if !strings.HasPrefix(boxes[i].Window, statusSpacerPrefix) { - boxes = slices.Insert(boxes, i, spacerBox()) - } - } - return boxes - } - - // First collect the real views that we want to show, we'll add spacers in - // between at the end - var result []*boxlayout.Box - - if !args.InDemo { - // app status appears very briefly in demos and dislodges the caption, - // so better not to show it at all - if args.AppStatus != "" { - result = append(result, &boxlayout.Box{Window: "appStatus", Size: utils.StringWidth(args.AppStatus)}) - } - } - - if args.UserConfig.Gui.ShowBottomLine { - result = append(result, &boxlayout.Box{Window: "options", Weight: 1}) - } - - if (!args.InDemo && args.UserConfig.Gui.ShowBottomLine) || args.IsAnyModeActive { - result = append(result, - &boxlayout.Box{ - Window: "information", - // unlike appStatus, informationStr has various colors so we need to decolorise before taking the length - Size: utils.StringWidth(utils.Decolorise(args.InformationStr)), - }) - } - - if len(result) == 2 && result[0].Window == "appStatus" { - // Only status and information are showing; need to insert a flexible - // spacer between the two, so that information is right-aligned. Note - // that the call to insertSpacerBoxes below will still insert a 1-char - // spacer in addition (right after the flexible one); this is needed for - // the case that there's not enough room, to ensure there's always at - // least one space. - result = slices.Insert(result, 1, flexibleSpacerBox()) - } else if len(result) == 1 { - if result[0].Window == "information" { - // Only information is showing; need to add a flexible spacer so - // that information is right-aligned - result = slices.Insert(result, 0, flexibleSpacerBox()) - } else { - // Only status is showing; need to make it flexible so that it - // extends over the whole width - result[0].Size = 0 - result[0].Weight = 1 - } - } - - if len(result) > 0 { - // If we have at least one view, insert 1-char wide spacer boxes between them. - result = insertSpacerBoxes(result) - } - - return result -} - -func splitMainPanelSideBySide(args WindowArrangementArgs) bool { - if !args.SplitMainPanel { - return false - } - - mainPanelSplitMode := args.UserConfig.Gui.MainPanelSplitMode - switch mainPanelSplitMode { - case "vertical": - return false - case "horizontal": - return true - default: - if args.Width < 200 && args.Height > 30 { // 2 80 character width panels + 40 width for side panel - return false - } - return true - } -} - -func getExtrasWindowSize(args WindowArrangementArgs) int { - var baseSize int - // The 'extras' window contains the command log context - if args.CurrentWindow == "extras" { - baseSize = 1000 // my way of saying 'fill the available space' - } else if args.Height < 40 { - baseSize = 1 - } else { - baseSize = args.UserConfig.Gui.CommandLogSize - } - - frameSize := 2 - return baseSize + frameSize -} - -// The stash window by default only contains one line so that it's not hogging -// too much space, but if you access it it should take up some space. This is -// the default behaviour when accordion mode is NOT in effect. If it is in effect -// then when it's accessed it will have weight 2, not 1. -func getDefaultStashWindowBox(args WindowArrangementArgs) *boxlayout.Box { - box := &boxlayout.Box{Window: "stash"} - // if the stash window is anywhere in our stack we should enlargen it - if args.CurrentSideWindow == "stash" { - box.Weight = 1 - } else { - box.Size = 3 - } - - return box -} - -func sidePanelChildren(args WindowArrangementArgs) func(width int, height int) []*boxlayout.Box { - return func(width int, height int) []*boxlayout.Box { - if args.ScreenMode == types.SCREEN_FULL || args.ScreenMode == types.SCREEN_HALF { - fullHeightBox := func(window string) *boxlayout.Box { - if window == args.CurrentSideWindow { - return &boxlayout.Box{ - Window: window, - Weight: 1, - } - } - - return &boxlayout.Box{ - Window: window, - Size: 0, - } - } - - return []*boxlayout.Box{ - fullHeightBox("status"), - fullHeightBox("files"), - fullHeightBox("branches"), - fullHeightBox("commits"), - fullHeightBox("stash"), - } - } else if height >= 28 { - accordionMode := args.UserConfig.Gui.ExpandFocusedSidePanel - accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box { - if accordionMode && defaultBox.Window == args.CurrentSideWindow { - return &boxlayout.Box{ - Window: defaultBox.Window, - Weight: args.UserConfig.Gui.ExpandedSidePanelWeight, - } - } - - return defaultBox - } - - return []*boxlayout.Box{ - { - Window: "status", - Size: 3, - }, - accordionBox(&boxlayout.Box{Window: "files", Weight: 1}), - accordionBox(&boxlayout.Box{Window: "branches", Weight: 1}), - accordionBox(&boxlayout.Box{Window: "commits", Weight: 1}), - accordionBox(getDefaultStashWindowBox(args)), - } - } - - squashedHeight := 1 - if height >= 21 { - squashedHeight = 3 - } - - squashedSidePanelBox := func(window string) *boxlayout.Box { - if window == args.CurrentSideWindow { - return &boxlayout.Box{ - Window: window, - Weight: 1, - } - } - - return &boxlayout.Box{ - Window: window, - Size: squashedHeight, - } - } - - return []*boxlayout.Box{ - squashedSidePanelBox("status"), - squashedSidePanelBox("files"), - squashedSidePanelBox("branches"), - squashedSidePanelBox("commits"), - squashedSidePanelBox("stash"), - } - } -} diff --git a/pkg/gui/controllers/helpers/window_arrangement_helper_test.go b/pkg/gui/controllers/helpers/window_arrangement_helper_test.go deleted file mode 100644 index bb36ea03d0e..00000000000 --- a/pkg/gui/controllers/helpers/window_arrangement_helper_test.go +++ /dev/null @@ -1,630 +0,0 @@ -package helpers - -import ( - "cmp" - "fmt" - "slices" - "strings" - "testing" - - "github.com/jesseduffield/lazycore/pkg/boxlayout" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -// The best way to add test cases here is to set your args and then get the -// test to fail and copy+paste the output into the test case's expected string. -// TODO: add more test cases -func TestGetWindowDimensions(t *testing.T) { - getDefaultArgs := func() WindowArrangementArgs { - return WindowArrangementArgs{ - Width: 75, - Height: 30, - UserConfig: config.GetDefaultConfig(), - CurrentWindow: "files", - CurrentSideWindow: "files", - SplitMainPanel: false, - ScreenMode: types.SCREEN_NORMAL, - AppStatus: "", - InformationStr: "information", - ShowExtrasWindow: false, - InDemo: false, - IsAnyModeActive: false, - InSearchPrompt: false, - SearchPrefix: "", - } - } - - type Test struct { - name string - mutateArgs func(*WindowArrangementArgs) - expected string - } - - tests := []Test{ - { - name: "default", - mutateArgs: func(args *WindowArrangementArgs) {}, - expected: ` - ╭status─────────────────╮╭main────────────────────────────────────────────╮ - │ ││ │ - ╰───────────────────────╯│ │ - ╭files──────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭branches───────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭commits────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭stash──────────────────╮│ │ - │ ││ │ - ╰───────────────────────╯╰────────────────────────────────────────────────╯ - A - A: statusSpacer1 - B: information - `, - }, - { - name: "stash focused", - mutateArgs: func(args *WindowArrangementArgs) { - args.CurrentSideWindow = "stash" - }, - expected: ` - ╭status─────────────────╮╭main────────────────────────────────────────────╮ - │ ││ │ - ╰───────────────────────╯│ │ - ╭files──────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭branches───────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭commits────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭stash──────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯╰────────────────────────────────────────────────╯ - A - A: statusSpacer1 - B: information - `, - }, - { - name: "expandFocusedSidePanel", - mutateArgs: func(args *WindowArrangementArgs) { - args.UserConfig.Gui.ExpandFocusedSidePanel = true - }, - expected: ` - ╭status─────────────────╮╭main────────────────────────────────────────────╮ - │ ││ │ - ╰───────────────────────╯│ │ - ╭files──────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭branches───────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭commits────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭stash──────────────────╮│ │ - │ ││ │ - ╰───────────────────────╯╰────────────────────────────────────────────────╯ - A - A: statusSpacer1 - B: information - `, - }, - { - name: "expandSidePanelWeight", - mutateArgs: func(args *WindowArrangementArgs) { - args.UserConfig.Gui.ExpandFocusedSidePanel = true - args.UserConfig.Gui.ExpandedSidePanelWeight = 4 - }, - expected: ` - ╭status─────────────────╮╭main────────────────────────────────────────────╮ - │ ││ │ - ╰───────────────────────╯│ │ - ╭files──────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭branches───────────────╮│ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭commits────────────────╮│ │ - │ ││ │ - │ ││ │ - ╰───────────────────────╯│ │ - ╭stash──────────────────╮│ │ - │ ││ │ - ╰───────────────────────╯╰────────────────────────────────────────────────╯ - A - A: statusSpacer1 - B: information - `, - }, - { - name: "0.5 SidePanelWidth", - mutateArgs: func(args *WindowArrangementArgs) { - args.UserConfig.Gui.SidePanelWidth = 0.5 - }, - expected: ` - ╭status──────────────────────────────╮╭main───────────────────────────────╮ - │ ││ │ - ╰────────────────────────────────────╯│ │ - ╭files───────────────────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰────────────────────────────────────╯│ │ - ╭branches────────────────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰────────────────────────────────────╯│ │ - ╭commits─────────────────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰────────────────────────────────────╯│ │ - ╭stash───────────────────────────────╮│ │ - │ ││ │ - ╰────────────────────────────────────╯╰───────────────────────────────────╯ - A - A: statusSpacer1 - B: information - `, - }, - { - name: "0.8 SidePanelWidth", - mutateArgs: func(args *WindowArrangementArgs) { - args.UserConfig.Gui.SidePanelWidth = 0.8 - }, - expected: ` - ╭status────────────────────────────────────────────────────╮╭main─────────╮ - │ ││ │ - ╰──────────────────────────────────────────────────────────╯│ │ - ╭files─────────────────────────────────────────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰──────────────────────────────────────────────────────────╯│ │ - ╭branches──────────────────────────────────────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰──────────────────────────────────────────────────────────╯│ │ - ╭commits───────────────────────────────────────────────────╮│ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰──────────────────────────────────────────────────────────╯│ │ - ╭stash─────────────────────────────────────────────────────╮│ │ - │ ││ │ - ╰──────────────────────────────────────────────────────────╯╰─────────────╯ - A - A: statusSpacer1 - B: information - `, - }, - { - name: "half screen mode, enlargedSideViewLocation left", - mutateArgs: func(args *WindowArrangementArgs) { - args.Height = 20 // smaller height because we don't more here - args.ScreenMode = types.SCREEN_HALF - args.UserConfig.Gui.EnlargedSideViewLocation = "left" - }, - expected: ` - ╭status──────────────────────────────╮╭main───────────────────────────────╮ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - │ ││ │ - ╰────────────────────────────────────╯╰───────────────────────────────────╯ - A - A: statusSpacer1 - B: information - `, - }, - { - name: "half screen mode, enlargedSideViewLocation top", - mutateArgs: func(args *WindowArrangementArgs) { - args.Height = 20 // smaller height because we don't more here - args.ScreenMode = types.SCREEN_HALF - args.UserConfig.Gui.EnlargedSideViewLocation = "top" - }, - expected: ` - ╭status───────────────────────────────────────────────────────────────────╮ - │ │ - │ │ - │ │ - │ │ - │ │ - ╰─────────────────────────────────────────────────────────────────────────╯ - ╭main─────────────────────────────────────────────────────────────────────╮ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - ╰─────────────────────────────────────────────────────────────────────────╯ - A - A: statusSpacer1 - B: information - `, - }, - { - name: "search mode", - mutateArgs: func(args *WindowArrangementArgs) { - args.InSearchPrompt = true - args.SearchPrefix = "Search: " - args.Height = 6 // small height cos we only care about the bottom line - }, - expected: ` - ╭main────────────────────────────────────────────╮ - │ │ - │ │ - │ │ - ╰────────────────────────────────────────────────╯ - - A: searchPrefix - `, - }, - { - name: "app status present", - mutateArgs: func(args *WindowArrangementArgs) { - args.AppStatus = "Rebasing /" - args.Height = 6 // small height cos we only care about the bottom line - }, - // We expect single-character spacers between the windows of the bottom line - expected: ` - ╭main────────────────────────────────────────────╮ - │ │ - │ │ - │ │ - ╰────────────────────────────────────────────────╯ - BC - A: appStatus - B: statusSpacer2 - C: statusSpacer1 - D: information - `, - }, - { - name: "information present without options", - mutateArgs: func(args *WindowArrangementArgs) { - args.Height = 6 // small height cos we only care about the bottom line - args.UserConfig.Gui.ShowBottomLine = false // this hides the options window - args.IsAnyModeActive = true // this means we show the bottom line despite the user config - }, - // We expect a spacer on the left of the bottom line so that the information - // window is right-aligned - expected: ` - ╭main────────────────────────────────────────────╮ - │ │ - │ │ - │ │ - ╰────────────────────────────────────────────────╯ - A - A: statusSpacer2 - B: information - `, - }, - { - name: "app status present without information or options", - mutateArgs: func(args *WindowArrangementArgs) { - args.Height = 6 // small height cos we only care about the bottom line - args.UserConfig.Gui.ShowBottomLine = false // this hides the options window - args.IsAnyModeActive = false - args.AppStatus = "Rebasing /" - }, - // We expect the app status window to take up all the available space - expected: ` - ╭main────────────────────────────────────────────╮ - │ │ - │ │ - │ │ - ╰────────────────────────────────────────────────╯ - - `, - }, - { - name: "app status present with information but without options", - mutateArgs: func(args *WindowArrangementArgs) { - args.Height = 6 // small height cos we only care about the bottom line - args.UserConfig.Gui.ShowBottomLine = false // this hides the options window - args.IsAnyModeActive = true - args.AppStatus = "Rebasing /" - }, - expected: ` - ╭main────────────────────────────────────────────╮ - │ │ - │ │ - │ │ - ╰────────────────────────────────────────────────╯ - B - A: appStatus - B: statusSpacer2 - C: information - `, - }, - { - name: "app status present with very long information but without options", - mutateArgs: func(args *WindowArrangementArgs) { - args.Height = 6 // small height cos we only care about the bottom line - args.Width = 55 // smaller width so that not all bottom line views fit - args.UserConfig.Gui.ShowBottomLine = false // this hides the options window - args.IsAnyModeActive = true - args.AppStatus = "Rebasing /" - args.InformationStr = "Showing output for: git diff deadbeef fa1afe1 -- (Reset)" - }, - expected: ` - ╭main──────────────────────────────╮ - │ │ - │ │ - │ │ - ╰──────────────────────────────────╯ - B - A: appStatus - B: statusSpacer2 - `, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - args := getDefaultArgs() - test.mutateArgs(&args) - windows := GetWindowDimensions(args) - output := renderLayout(windows) - // removing tabs so that it's easier to paste the expected output - expected := strings.ReplaceAll(test.expected, "\t", "") - expected = strings.TrimSpace(expected) - if output != expected { - fmt.Println(output) - t.Errorf("Expected:\n%s\n\nGot:\n%s", expected, output) - } - }) - } -} - -func renderLayout(windows map[string]boxlayout.Dimensions) string { - // Each window will be represented by a letter. - windowMarkers := map[string]string{} - shortLabels := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"} - currentShortLabelIdx := 0 - windowNames := lo.Keys(windows) - // Sort first by name, then by position. This means our short labels will - // increment in the order that the windows appear on the screen. - slices.Sort(windowNames) - slices.SortStableFunc(windowNames, func(a, b string) int { - dimensionsA := windows[a] - dimensionsB := windows[b] - if dimensionsA.Y0 != dimensionsB.Y0 { - return cmp.Compare(dimensionsA.Y0, dimensionsB.Y0) - } - return cmp.Compare(dimensionsA.X0, dimensionsB.X0) - }) - - // Uniquify windows by dimensions (so perfectly overlapping windows are de-duped). This prevents getting 'fileshes' as a label where the files and branches windows overlap. - // branches windows overlap. - windowNames = lo.UniqBy(windowNames, func(windowName string) boxlayout.Dimensions { - return windows[windowName] - }) - - // excluding the limit window because it overlaps with everything. In future - // we should have a concept of layers and then our test can assert against - // each layer. - windowNames = lo.Without(windowNames, "limit") - - // get width/height by getting the max values of the dimensions - width := 0 - height := 0 - for _, dimensions := range windows { - if dimensions.X1+1 > width { - width = dimensions.X1 + 1 - } - if dimensions.Y1+1 > height { - height = dimensions.Y1 + 1 - } - } - - screen := make([][]string, height) - for i := range screen { - screen[i] = make([]string, width) - } - - // Draw each window - for _, windowName := range windowNames { - dimensions := windows[windowName] - - zeroWidth := dimensions.X0 == dimensions.X1+1 - if zeroWidth { - continue - } - - singleRow := dimensions.Y0 == dimensions.Y1 - oneOrTwoColumns := dimensions.X0 == dimensions.X1 || dimensions.X0+1 == dimensions.X1 - - assignShortLabel := func(windowName string) string { - windowMarkers[windowName] = shortLabels[currentShortLabelIdx] - currentShortLabelIdx++ - return windowMarkers[windowName] - } - - if singleRow { - y := dimensions.Y0 - // If our window only occupies one (or two) columns we'll just use the short - // label once (or twice) i.e. 'A' or 'AA'. - if oneOrTwoColumns { - shortLabel := assignShortLabel(windowName) - - for x := dimensions.X0; x <= dimensions.X1; x++ { - screen[y][x] = shortLabel - } - } else { - screen[y][dimensions.X0] = "<" - screen[y][dimensions.X1] = ">" - for x := dimensions.X0 + 1; x < dimensions.X1; x++ { - screen[y][x] = "─" - } - - // Now add the label - label := windowName - // If we can't fit the label we'll use a one-character short label - if len(label) > dimensions.X1-dimensions.X0-1 { - label = assignShortLabel(windowName) - } - for i, char := range label { - screen[y][dimensions.X0+1+i] = string(char) - } - } - } else { - // Draw box border - for y := dimensions.Y0; y <= dimensions.Y1; y++ { - for x := dimensions.X0; x <= dimensions.X1; x++ { - if x == dimensions.X0 && y == dimensions.Y0 { - screen[y][x] = "╭" - } else if x == dimensions.X1 && y == dimensions.Y0 { - screen[y][x] = "╮" - } else if x == dimensions.X0 && y == dimensions.Y1 { - screen[y][x] = "╰" - } else if x == dimensions.X1 && y == dimensions.Y1 { - screen[y][x] = "╯" - } else if y == dimensions.Y0 || y == dimensions.Y1 { - screen[y][x] = "─" - } else if x == dimensions.X0 || x == dimensions.X1 { - screen[y][x] = "│" - } else { - screen[y][x] = " " - } - } - } - - // Add the label - label := windowName - // If we can't fit the label we'll use a one-character short label - if len(label) > dimensions.X1-dimensions.X0-1 { - label = assignShortLabel(windowName) - } - for i, char := range label { - screen[dimensions.Y0][dimensions.X0+1+i] = string(char) - } - } - } - - // Draw the screen - output := "" - for _, row := range screen { - for _, marker := range row { - output += marker - } - output += "\n" - } - - // Add a legend - for _, windowName := range windowNames { - if !lo.Contains(lo.Keys(windowMarkers), windowName) { - continue - } - marker := windowMarkers[windowName] - output += fmt.Sprintf("%s: %s\n", marker, windowName) - } - - output = strings.TrimSpace(output) - - return output -} diff --git a/pkg/gui/controllers/helpers/window_helper.go b/pkg/gui/controllers/helpers/window_helper.go deleted file mode 100644 index e2b0e38f086..00000000000 --- a/pkg/gui/controllers/helpers/window_helper.go +++ /dev/null @@ -1,139 +0,0 @@ -package helpers - -import ( - "fmt" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type WindowHelper struct { - c *HelperCommon - viewHelper *ViewHelper -} - -func NewWindowHelper(c *HelperCommon, viewHelper *ViewHelper) *WindowHelper { - return &WindowHelper{ - c: c, - viewHelper: viewHelper, - } -} - -// A window refers to a place on the screen which can hold one or more views. -// A view is a box that renders content, and within a window only one view will -// appear at a time. When a view appears within a window, it occupies the whole -// space. Right now most windows are 1:1 with views, except for commitFiles which -// is a view that moves between windows - -func (self *WindowHelper) GetViewNameForWindow(window string) string { - viewName, ok := self.windowViewNameMap().Get(window) - if !ok { - panic(fmt.Sprintf("Viewname not found for window: %s", window)) - } - - return viewName -} - -func (self *WindowHelper) GetContextForWindow(window string) types.Context { - viewName := self.GetViewNameForWindow(window) - - context, ok := self.viewHelper.ContextForView(viewName) - if !ok { - panic("TODO: fix this") - } - - return context -} - -// for now all we actually care about is the context's view so we're storing that -func (self *WindowHelper) SetWindowContext(c types.Context) { - if c.IsTransient() { - self.resetWindowContext(c) - } - - self.windowViewNameMap().Set(c.GetWindowName(), c.GetViewName()) -} - -func (self *WindowHelper) windowViewNameMap() *utils.ThreadSafeMap[string, string] { - return self.c.State().GetRepoState().GetWindowViewNameMap() -} - -func (self *WindowHelper) CurrentWindow() string { - return self.c.Context().Current().GetWindowName() -} - -// assumes the context's windowName has been set to the new window if necessary -func (self *WindowHelper) resetWindowContext(c types.Context) { - for _, windowName := range self.windowViewNameMap().Keys() { - viewName, ok := self.windowViewNameMap().Get(windowName) - if !ok { - continue - } - if viewName == c.GetViewName() && windowName != c.GetWindowName() { - for _, context := range self.c.Contexts().Flatten() { - if context.GetKey() != c.GetKey() && context.GetWindowName() == windowName { - self.windowViewNameMap().Set(windowName, context.GetViewName()) - } - } - } - } -} - -// moves given context's view to the top of the window -func (self *WindowHelper) MoveToTopOfWindow(context types.Context) { - view := context.GetView() - if view == nil { - return - } - - window := context.GetWindowName() - - topView := self.TopViewInWindow(window, true) - - if topView != nil && view.Name() != topView.Name() { - if err := self.c.GocuiGui().SetViewOnTopOf(view.Name(), topView.Name()); err != nil { - self.c.Log.Error(err) - } - } -} - -func (self *WindowHelper) TopViewInWindow(windowName string, includeInvisibleViews bool) *gocui.View { - // now I need to find all views in that same window, via contexts. And I guess then I need to find the index of the highest view in that list. - viewNamesInWindow := self.viewNamesInWindow(windowName) - - // The views list is ordered highest-last, so we're grabbing the last view of the window - var topView *gocui.View - for _, currentView := range self.c.GocuiGui().Views() { - if lo.Contains(viewNamesInWindow, currentView.Name()) && (currentView.Visible || includeInvisibleViews) { - topView = currentView - } - } - - return topView -} - -func (self *WindowHelper) viewNamesInWindow(windowName string) []string { - result := []string{} - for _, context := range self.c.Contexts().Flatten() { - if context.GetWindowName() == windowName { - result = append(result, context.GetViewName()) - } - } - - return result -} - -func (self *WindowHelper) WindowForView(viewName string) string { - context, ok := self.viewHelper.ContextForView(viewName) - if !ok { - panic("todo: deal with this") - } - - return context.GetWindowName() -} - -func (self *WindowHelper) SideWindows() []string { - return []string{"status", "files", "branches", "commits", "stash"} -} diff --git a/pkg/gui/controllers/helpers/working_tree_helper.go b/pkg/gui/controllers/helpers/working_tree_helper.go deleted file mode 100644 index 6437afa3507..00000000000 --- a/pkg/gui/controllers/helpers/working_tree_helper.go +++ /dev/null @@ -1,402 +0,0 @@ -package helpers - -import ( - "errors" - "fmt" - "os" - "regexp" - - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type WorkingTreeHelper struct { - c *HelperCommon - refHelper *RefsHelper - commitsHelper *CommitsHelper - gpgHelper *GpgHelper - mergeAndRebaseHelper *MergeAndRebaseHelper -} - -func NewWorkingTreeHelper( - c *HelperCommon, - refHelper *RefsHelper, - commitsHelper *CommitsHelper, - gpgHelper *GpgHelper, - mergeAndRebaseHelper *MergeAndRebaseHelper, -) *WorkingTreeHelper { - return &WorkingTreeHelper{ - c: c, - refHelper: refHelper, - commitsHelper: commitsHelper, - gpgHelper: gpgHelper, - mergeAndRebaseHelper: mergeAndRebaseHelper, - } -} - -func (self *WorkingTreeHelper) AnyStagedFiles() bool { - return AnyStagedFiles(self.c.Model().Files) -} - -func AnyStagedFiles(files []*models.File) bool { - return lo.SomeBy(files, func(f *models.File) bool { return f.HasStagedChanges }) -} - -func (self *WorkingTreeHelper) AnyStagedFilesExceptSubmodules() bool { - return AnyStagedFilesExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules) -} - -func AnyStagedFilesExceptSubmodules(files []*models.File, submoduleConfigs []*models.SubmoduleConfig) bool { - return lo.SomeBy(files, func(f *models.File) bool { return f.HasStagedChanges && !f.IsSubmodule(submoduleConfigs) }) -} - -func (self *WorkingTreeHelper) AnyTrackedFiles() bool { - return AnyTrackedFiles(self.c.Model().Files) -} - -func AnyTrackedFiles(files []*models.File) bool { - return lo.SomeBy(files, func(f *models.File) bool { return f.Tracked }) -} - -func (self *WorkingTreeHelper) AnyTrackedFilesExceptSubmodules() bool { - return AnyTrackedFilesExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules) -} - -func AnyTrackedFilesExceptSubmodules(files []*models.File, submoduleConfigs []*models.SubmoduleConfig) bool { - return lo.SomeBy(files, func(f *models.File) bool { return f.Tracked && !f.IsSubmodule(submoduleConfigs) }) -} - -func (self *WorkingTreeHelper) IsWorkingTreeDirtyExceptSubmodules() bool { - return IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules) -} - -func IsWorkingTreeDirtyExceptSubmodules(files []*models.File, submoduleConfigs []*models.SubmoduleConfig) bool { - return AnyStagedFilesExceptSubmodules(files, submoduleConfigs) || AnyTrackedFilesExceptSubmodules(files, submoduleConfigs) -} - -func (self *WorkingTreeHelper) FileForSubmodule(submodule *models.SubmoduleConfig) *models.File { - for _, file := range self.c.Model().Files { - if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) { - return file - } - } - - return nil -} - -func (self *WorkingTreeHelper) OpenMergeTool() error { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.MergeToolTitle, - Prompt: self.c.Tr.MergeToolPrompt, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.OpenMergeTool) - return self.c.RunSubprocessAndRefresh( - self.c.Git().WorkingTree.OpenMergeToolCmdObj(), - ) - }, - }) - - return nil -} - -func (self *WorkingTreeHelper) HandleCommitPressWithMessage(initialMessage string, forceSkipHooks bool) error { - return self.WithEnsureCommittableFiles(func() error { - self.commitsHelper.OpenCommitMessagePanel( - &OpenCommitMessagePanelOpts{ - CommitIndex: context.NoCommitIndex, - InitialMessage: initialMessage, - SummaryTitle: self.c.Tr.CommitSummaryTitle, - DescriptionTitle: self.c.Tr.CommitDescriptionTitle, - PreserveMessage: true, - OnConfirm: func(summary string, description string) error { - return self.handleCommit(summary, description, forceSkipHooks) - }, - OnSwitchToEditor: func(filepath string) error { - return self.switchFromCommitMessagePanelToEditor(filepath, forceSkipHooks) - }, - ForceSkipHooks: forceSkipHooks, - SkipHooksPrefix: self.c.UserConfig().Git.SkipHookPrefix, - }, - ) - - return nil - }) -} - -func (self *WorkingTreeHelper) handleCommit(summary string, description string, forceSkipHooks bool) error { - cmdObj := self.c.Git().Commit.CommitCmdObj(summary, description, forceSkipHooks) - self.c.LogAction(self.c.Tr.Actions.Commit) - return self.gpgHelper.WithGpgHandling(cmdObj, git_commands.CommitGpgSign, self.c.Tr.CommittingStatus, - func() error { - self.commitsHelper.ClearPreservedCommitMessage() - return nil - }, nil) -} - -func (self *WorkingTreeHelper) switchFromCommitMessagePanelToEditor(filepath string, forceSkipHooks bool) error { - // We won't be able to tell whether the commit was successful, because - // RunSubprocessAndRefresh doesn't return the error (it opens an error alert - // itself and returns nil on error). But even if we could, we wouldn't have - // access to the last message that the user typed, and it might be very - // different from what was last in the commit panel. So the best we can do - // here is to always clear the remembered commit message. - self.commitsHelper.ClearPreservedCommitMessage() - - self.c.LogAction(self.c.Tr.Actions.Commit) - return self.c.RunSubprocessAndRefresh( - self.c.Git().Commit.CommitInEditorWithMessageFileCmdObj(filepath, forceSkipHooks), - ) -} - -// HandleCommitEditorPress - handle when the user wants to commit changes via -// their editor rather than via the popup panel -func (self *WorkingTreeHelper) HandleCommitEditorPress() error { - return self.WithEnsureCommittableFiles(func() error { - // See reasoning in switchFromCommitMessagePanelToEditor for why it makes sense - // to clear this message before calling into the editor - self.commitsHelper.ClearPreservedCommitMessage() - - self.c.LogAction(self.c.Tr.Actions.Commit) - return self.c.RunSubprocessAndRefresh( - self.c.Git().Commit.CommitEditorCmdObj(), - ) - }) -} - -func (self *WorkingTreeHelper) HandleWIPCommitPress() error { - var initialMessage string - preservedMessage := self.c.Contexts().CommitMessage.GetPreservedMessageAndLogError() - if preservedMessage == "" { - // Use the skipHook prefix only if we don't have a preserved message - initialMessage = self.c.UserConfig().Git.SkipHookPrefix - } - return self.HandleCommitPressWithMessage(initialMessage, true) -} - -func (self *WorkingTreeHelper) HandleCommitPress() error { - message := self.c.Contexts().CommitMessage.GetPreservedMessageAndLogError() - - if message == "" { - commitPrefixConfigs := self.commitPrefixConfigsForRepo() - for _, commitPrefixConfig := range commitPrefixConfigs { - prefixPattern := commitPrefixConfig.Pattern - if prefixPattern == "" { - continue - } - prefixReplace := commitPrefixConfig.Replace - branchName := self.refHelper.GetCheckedOutRef().Name - rgx, err := regexp.Compile(prefixPattern) - if err != nil { - return fmt.Errorf("%s: %s", self.c.Tr.CommitPrefixPatternError, err.Error()) - } - - if rgx.MatchString(branchName) { - prefix := rgx.ReplaceAllString(branchName, prefixReplace) - message = prefix - break - } - } - } - - return self.HandleCommitPressWithMessage(message, false) -} - -func (self *WorkingTreeHelper) WithEnsureCommittableFiles(handler func() error) error { - if err := self.prepareFilesForCommit(); err != nil { - return err - } - - if len(self.c.Model().Files) == 0 { - return errors.New(self.c.Tr.NoFilesStagedTitle) - } - - if !self.AnyStagedFiles() { - return self.promptToStageAllAndRetry(handler) - } - - return handler() -} - -func (self *WorkingTreeHelper) promptToStageAllAndRetry(retry func() error) error { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.NoFilesStagedTitle, - Prompt: self.c.Tr.NoFilesStagedPrompt, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.StageAllFiles) - if err := self.c.Git().WorkingTree.StageAll(false); err != nil { - return err - } - self.syncRefresh() - - return retry() - }, - }) - - return nil -} - -// for when you need to refetch files before continuing an action. Runs synchronously. -func (self *WorkingTreeHelper) syncRefresh() { - self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}}) -} - -func (self *WorkingTreeHelper) prepareFilesForCommit() error { - noStagedFiles := !self.AnyStagedFiles() - if noStagedFiles && self.c.UserConfig().Gui.SkipNoStagedFilesWarning { - self.c.LogAction(self.c.Tr.Actions.StageAllFiles) - err := self.c.Git().WorkingTree.StageAll(false) - if err != nil { - return err - } - - self.syncRefresh() - } - - return nil -} - -func (self *WorkingTreeHelper) commitPrefixConfigsForRepo() []config.CommitPrefixConfig { - cfg, ok := self.c.UserConfig().Git.CommitPrefixes[self.c.Git().RepoPaths.RepoName()] - if ok { - return append(cfg, self.c.UserConfig().Git.CommitPrefix...) - } - - return self.c.UserConfig().Git.CommitPrefix -} - -func (self *WorkingTreeHelper) mergeFile(filepath string, strategy string) (string, error) { - if self.c.Git().Version.IsOlderThan(2, 43, 0) { - return self.mergeFileWithTempFiles(filepath, strategy) - } - - return self.mergeFileWithObjectIDs(filepath, strategy) -} - -func (self *WorkingTreeHelper) mergeFileWithTempFiles(filepath string, strategy string) (string, error) { - showToTempFile := func(stage int, label string) (string, error) { - output, err := self.c.Git().WorkingTree.ShowFileAtStage(filepath, stage) - if err != nil { - return "", err - } - - f, err := os.CreateTemp(self.c.GetConfig().GetTempDir(), "mergefile-"+label+"-*") - if err != nil { - return "", err - } - defer f.Close() - - if _, err := f.Write([]byte(output)); err != nil { - return "", err - } - - return f.Name(), nil - } - - baseFilepath, err := showToTempFile(1, "base") - if err != nil { - return "", err - } - defer os.Remove(baseFilepath) - - oursFilepath, err := showToTempFile(2, "ours") - if err != nil { - return "", err - } - defer os.Remove(oursFilepath) - - theirsFilepath, err := showToTempFile(3, "theirs") - if err != nil { - return "", err - } - defer os.Remove(theirsFilepath) - - return self.c.Git().WorkingTree.MergeFileForFiles(strategy, oursFilepath, baseFilepath, theirsFilepath) -} - -func (self *WorkingTreeHelper) mergeFileWithObjectIDs(filepath, strategy string) (string, error) { - baseID, err := self.c.Git().WorkingTree.ObjectIDAtStage(filepath, 1) - if err != nil { - return "", err - } - - oursID, err := self.c.Git().WorkingTree.ObjectIDAtStage(filepath, 2) - if err != nil { - return "", err - } - - theirsID, err := self.c.Git().WorkingTree.ObjectIDAtStage(filepath, 3) - if err != nil { - return "", err - } - - return self.c.Git().WorkingTree.MergeFileForObjectIDs(strategy, oursID, baseID, theirsID) -} - -func (self *WorkingTreeHelper) CreateMergeConflictMenu(selectedFilepaths []string) error { - onMergeStrategySelected := func(strategy string) error { - for _, filepath := range selectedFilepaths { - output, err := self.mergeFile(filepath, strategy) - if err != nil { - return err - } - - if err = os.WriteFile(filepath, []byte(output), 0o644); err != nil { - return err - } - } - - err := self.c.Git().WorkingTree.StageFiles(selectedFilepaths, nil) - self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}}) - return err - } - - cmdColor := style.FgBlue - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.MergeConflictOptionsTitle, - Items: []*types.MenuItem{ - { - LabelColumns: []string{ - self.c.Tr.UseCurrentChanges, - cmdColor.Sprint("git merge-file --ours"), - }, - OnPress: func() error { - return onMergeStrategySelected("--ours") - }, - Key: 'c', - }, - { - LabelColumns: []string{ - self.c.Tr.UseIncomingChanges, - cmdColor.Sprint("git merge-file --theirs"), - }, - OnPress: func() error { - return onMergeStrategySelected("--theirs") - }, - Key: 'i', - }, - { - LabelColumns: []string{ - self.c.Tr.UseBothChanges, - cmdColor.Sprint("git merge-file --union"), - }, - OnPress: func() error { - return onMergeStrategySelected("--union") - }, - Key: 'b', - }, - { - LabelColumns: []string{ - self.c.Tr.OpenMergeTool, - cmdColor.Sprint("git mergetool"), - }, - OnPress: self.OpenMergeTool, - Key: 'm', - }, - }, - }) -} diff --git a/pkg/gui/controllers/helpers/worktree_helper.go b/pkg/gui/controllers/helpers/worktree_helper.go deleted file mode 100644 index ea889781e88..00000000000 --- a/pkg/gui/controllers/helpers/worktree_helper.go +++ /dev/null @@ -1,251 +0,0 @@ -package helpers - -import ( - "errors" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type WorktreeHelper struct { - c *HelperCommon - reposHelper *ReposHelper - refsHelper *RefsHelper - suggestionsHelper *SuggestionsHelper -} - -func NewWorktreeHelper(c *HelperCommon, reposHelper *ReposHelper, refsHelper *RefsHelper, suggestionsHelper *SuggestionsHelper) *WorktreeHelper { - return &WorktreeHelper{ - c: c, - reposHelper: reposHelper, - refsHelper: refsHelper, - suggestionsHelper: suggestionsHelper, - } -} - -func (self *WorktreeHelper) GetMainWorktreeName() string { - for _, worktree := range self.c.Model().Worktrees { - if worktree.IsMain { - return worktree.Name - } - } - - return "" -} - -// If we're on the main worktree, we return an empty string -func (self *WorktreeHelper) GetLinkedWorktreeName() string { - worktrees := self.c.Model().Worktrees - if len(worktrees) == 0 { - return "" - } - - // worktrees always have the current worktree on top - currentWorktree := worktrees[0] - if currentWorktree.IsMain { - return "" - } - - return currentWorktree.Name -} - -func (self *WorktreeHelper) NewWorktree() error { - branch := self.refsHelper.GetCheckedOutRef() - currentBranchName := branch.RefName() - - f := func(detached bool) { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.NewWorktreeBase, - InitialContent: currentBranchName, - FindSuggestionsFunc: self.suggestionsHelper.GetRefsSuggestionsFunc(), - HandleConfirm: func(base string) error { - // we assume that the base can be checked out - canCheckoutBase := true - return self.NewWorktreeCheckout(base, canCheckoutBase, detached, context.WORKTREES_CONTEXT_KEY) - }, - }) - } - - placeholders := map[string]string{"ref": "ref"} - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.WorktreeTitle, - Items: []*types.MenuItem{ - { - LabelColumns: []string{utils.ResolvePlaceholderString(self.c.Tr.CreateWorktreeFrom, placeholders)}, - OnPress: func() error { - f(false) - return nil - }, - }, - { - LabelColumns: []string{utils.ResolvePlaceholderString(self.c.Tr.CreateWorktreeFromDetached, placeholders)}, - OnPress: func() error { - f(true) - return nil - }, - }, - }, - }) -} - -func (self *WorktreeHelper) NewWorktreeCheckout(base string, canCheckoutBase bool, detached bool, contextKey types.ContextKey) error { - opts := git_commands.NewWorktreeOpts{ - Base: base, - Detach: detached, - } - - f := func() error { - return self.c.WithWaitingStatus(self.c.Tr.AddingWorktree, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.AddWorktree) - if err := self.c.Git().Worktree.New(opts); err != nil { - return err - } - - return self.reposHelper.DispatchSwitchTo(opts.Path, self.c.Tr.ErrWorktreeMovedOrRemoved, contextKey) - }) - } - - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.NewWorktreePath, - HandleConfirm: func(path string) error { - opts.Path = path - - if detached { - return f() - } - - if canCheckoutBase { - title := utils.ResolvePlaceholderString(self.c.Tr.NewBranchNameLeaveBlank, map[string]string{"default": base}) - // prompt for the new branch name where a blank means we just check out the branch - self.c.Prompt(types.PromptOpts{ - Title: title, - HandleConfirm: func(branchName string) error { - opts.Branch = branchName - - return f() - }, - }) - - return nil - } - - // prompt for the new branch name where a blank means we just check out the branch - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.NewBranchName, - HandleConfirm: func(branchName string) error { - if branchName == "" { - return errors.New(self.c.Tr.BranchNameCannotBeBlank) - } - - opts.Branch = branchName - - return f() - }, - }) - - return nil - }, - }) - - return nil -} - -func (self *WorktreeHelper) Switch(worktree *models.Worktree, contextKey types.ContextKey) error { - if worktree.IsCurrent { - return errors.New(self.c.Tr.AlreadyInWorktree) - } - - self.c.LogAction(self.c.Tr.SwitchToWorktree) - - return self.reposHelper.DispatchSwitchTo(worktree.Path, self.c.Tr.ErrWorktreeMovedOrRemoved, contextKey) -} - -func (self *WorktreeHelper) Remove(worktree *models.Worktree, force bool) error { - title := self.c.Tr.RemoveWorktreeTitle - var templateStr string - if force { - templateStr = self.c.Tr.ForceRemoveWorktreePrompt - } else { - templateStr = self.c.Tr.RemoveWorktreePrompt - } - message := utils.ResolvePlaceholderString( - templateStr, - map[string]string{ - "worktreeName": worktree.Name, - }, - ) - - self.c.Confirm(types.ConfirmOpts{ - Title: title, - Prompt: message, - HandleConfirm: func() error { - return self.c.WithWaitingStatus(self.c.Tr.RemovingWorktree, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.RemoveWorktree) - if err := self.c.Git().Worktree.Delete(worktree.Path, force); err != nil { - errMessage := err.Error() - if !strings.Contains(errMessage, "--force") && - !strings.Contains(errMessage, "fatal: working trees containing submodules cannot be moved or removed") { - return err - } - - if !force { - return self.Remove(worktree, true) - } - return err - } - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}}) - return nil - }) - }, - }) - - return nil -} - -func (self *WorktreeHelper) Detach(worktree *models.Worktree) error { - return self.c.WithWaitingStatus(self.c.Tr.DetachingWorktree, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.RemovingWorktree) - - err := self.c.Git().Worktree.Detach(worktree.Path) - if err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}}) - return nil - }) -} - -func (self *WorktreeHelper) ViewWorktreeOptions(context types.IListContext, ref string) error { - currentBranch := self.refsHelper.GetCheckedOutRef() - canCheckoutBase := context == self.c.Contexts().Branches && ref != currentBranch.RefName() - - return self.ViewBranchWorktreeOptions(ref, canCheckoutBase) -} - -func (self *WorktreeHelper) ViewBranchWorktreeOptions(branchName string, canCheckoutBase bool) error { - placeholders := map[string]string{"ref": branchName} - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.WorktreeTitle, - Items: []*types.MenuItem{ - { - LabelColumns: []string{utils.ResolvePlaceholderString(self.c.Tr.CreateWorktreeFrom, placeholders)}, - OnPress: func() error { - return self.NewWorktreeCheckout(branchName, canCheckoutBase, false, context.LOCAL_BRANCHES_CONTEXT_KEY) - }, - }, - { - LabelColumns: []string{utils.ResolvePlaceholderString(self.c.Tr.CreateWorktreeFromDetached, placeholders)}, - OnPress: func() error { - return self.NewWorktreeCheckout(branchName, canCheckoutBase, true, context.LOCAL_BRANCHES_CONTEXT_KEY) - }, - }, - }, - }) -} diff --git a/pkg/gui/controllers/jump_to_side_window_controller.go b/pkg/gui/controllers/jump_to_side_window_controller.go deleted file mode 100644 index c0ef2faece3..00000000000 --- a/pkg/gui/controllers/jump_to_side_window_controller.go +++ /dev/null @@ -1,62 +0,0 @@ -package controllers - -import ( - "log" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type JumpToSideWindowController struct { - baseController - c *ControllerCommon - nextTabFunc func() error -} - -func NewJumpToSideWindowController( - c *ControllerCommon, - nextTabFunc func() error, -) *JumpToSideWindowController { - return &JumpToSideWindowController{ - baseController: baseController{}, - c: c, - nextTabFunc: nextTabFunc, - } -} - -func (self *JumpToSideWindowController) Context() types.Context { - return nil -} - -func (self *JumpToSideWindowController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - windows := self.c.Helpers().Window.SideWindows() - - if len(opts.Config.Universal.JumpToBlock) != len(windows) { - log.Fatal("Jump to block keybindings cannot be set. Exactly 5 keybindings must be supplied.") - } - - return lo.Map(windows, func(window string, index int) *types.Binding { - return &types.Binding{ - ViewName: "", - // by default the keys are 1, 2, 3, etc - Key: opts.GetKey(opts.Config.Universal.JumpToBlock[index]), - Modifier: gocui.ModNone, - Handler: opts.Guards.NoPopupPanel(self.goToSideWindow(window)), - } - }) -} - -func (self *JumpToSideWindowController) goToSideWindow(window string) func() error { - return func() error { - sideWindowAlreadyActive := self.c.Helpers().Window.CurrentWindow() == window - if sideWindowAlreadyActive && self.c.UserConfig().Gui.SwitchTabsWithPanelJumpKeys { - return self.nextTabFunc() - } - - context := self.c.Helpers().Window.GetContextForWindow(window) - - self.c.Context().Push(context, types.OnFocusOpts{}) - return nil - } -} diff --git a/pkg/gui/controllers/list_controller.go b/pkg/gui/controllers/list_controller.go deleted file mode 100644 index f5f7c9289d5..00000000000 --- a/pkg/gui/controllers/list_controller.go +++ /dev/null @@ -1,244 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type ListControllerFactory struct { - c *ControllerCommon -} - -func NewListControllerFactory(c *ControllerCommon) *ListControllerFactory { - return &ListControllerFactory{ - c: c, - } -} - -func (self *ListControllerFactory) Create(context types.IListContext) *ListController { - return &ListController{ - baseController: baseController{}, - c: self.c, - context: context, - } -} - -type ListController struct { - baseController - c *ControllerCommon - - context types.IListContext -} - -func (self *ListController) Context() types.Context { - return self.context -} - -func (self *ListController) HandlePrevLine() error { - return self.handleLineChange(-1) -} - -func (self *ListController) HandleNextLine() error { - return self.handleLineChange(1) -} - -func (self *ListController) HandleScrollLeft() error { - return self.scrollHorizontal(self.context.GetViewTrait().ScrollLeft) -} - -func (self *ListController) HandleScrollRight() error { - return self.scrollHorizontal(self.context.GetViewTrait().ScrollRight) -} - -func (self *ListController) HandleScrollUp() error { - scrollHeight := self.c.UserConfig().Gui.ScrollHeight - self.context.GetViewTrait().ScrollUp(scrollHeight) - if self.context.RenderOnlyVisibleLines() { - self.context.HandleRender() - } - - return nil -} - -func (self *ListController) HandleScrollDown() error { - scrollHeight := self.c.UserConfig().Gui.ScrollHeight - self.context.GetViewTrait().ScrollDown(scrollHeight) - if self.context.RenderOnlyVisibleLines() { - self.context.HandleRender() - } - - return nil -} - -func (self *ListController) scrollHorizontal(scrollFunc func()) error { - scrollFunc() - - self.context.HandleFocus(types.OnFocusOpts{}) - if self.context.NeedsRerenderOnWidthChange() == types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES { - self.context.HandleRender() - } - return nil -} - -func (self *ListController) handleLineChange(change int) error { - return self.handleLineChangeAux( - self.context.GetList().MoveSelectedLine, change, - ) -} - -func (self *ListController) HandleRangeSelectChange(change int) error { - return self.handleLineChangeAux( - self.context.GetList().ExpandNonStickyRange, change, - ) -} - -func (self *ListController) handleLineChangeAux(f func(int), change int) error { - list := self.context.GetList() - - rangeBefore := list.IsSelectingRange() - before := list.GetSelectedLineIdx() - f(change) - rangeAfter := list.IsSelectingRange() - after := list.GetSelectedLineIdx() - - if err := self.pushContextIfNotFocused(); err != nil { - return err - } - - // doing this check so that if we're holding the up key at the start of the list - // we're not constantly re-rendering the main view. - cursorMoved := before != after - if cursorMoved { - switch change { - case -1: - checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig(), - self.context.ModelIndexToViewIndex(before), self.context.ModelIndexToViewIndex(after)) - case 1: - checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig(), - self.context.ModelIndexToViewIndex(before), self.context.ModelIndexToViewIndex(after)) - } - } - - if cursorMoved || rangeBefore != rangeAfter { - self.context.HandleFocus(types.OnFocusOpts{}) - } - - return nil -} - -func (self *ListController) HandlePrevPage() error { - return self.handleLineChange(-self.context.GetViewTrait().PageDelta()) -} - -func (self *ListController) HandleNextPage() error { - return self.handleLineChange(self.context.GetViewTrait().PageDelta()) -} - -func (self *ListController) HandleGotoTop() error { - return self.handleLineChange(-self.context.GetList().Len()) -} - -func (self *ListController) HandleGotoBottom() error { - bottomIdx := self.context.IndexForGotoBottom() - change := bottomIdx - self.context.GetList().GetSelectedLineIdx() - return self.handleLineChange(change) -} - -func (self *ListController) HandleToggleRangeSelect() error { - list := self.context.GetList() - - list.ToggleStickyRange() - - self.context.HandleFocus(types.OnFocusOpts{}) - return nil -} - -func (self *ListController) HandleRangeSelectDown() error { - return self.HandleRangeSelectChange(1) -} - -func (self *ListController) HandleRangeSelectUp() error { - return self.HandleRangeSelectChange(-1) -} - -func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error { - newSelectedLineIdx := self.context.ViewIndexToModelIndex(opts.Y) - alreadyFocused := self.isFocused() - - if err := self.pushContextIfNotFocused(); err != nil { - return err - } - - if newSelectedLineIdx > self.context.GetList().Len()-1 { - return nil - } - - self.context.GetList().SetSelection(newSelectedLineIdx) - - if opts.IsDoubleClick && alreadyFocused && self.context.GetOnClick() != nil { - return self.context.GetOnClick()() - } - self.context.HandleFocus(types.OnFocusOpts{}) - return nil -} - -func (self *ListController) pushContextIfNotFocused() error { - if !self.isFocused() { - self.c.Context().Push(self.context, types.OnFocusOpts{}) - } - - return nil -} - -func (self *ListController) isFocused() bool { - return self.c.Context().Current().GetKey() == self.context.GetKey() -} - -func (self *ListController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), Handler: self.HandlePrevLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItem), Handler: self.HandlePrevLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItemAlt), Handler: self.HandleNextLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItem), Handler: self.HandleNextLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevPage), Handler: self.HandlePrevPage, Description: self.c.Tr.PrevPage}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextPage), Handler: self.HandleNextPage, Description: self.c.Tr.NextPage}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoTop), Handler: self.HandleGotoTop, Description: self.c.Tr.GotoTop, Alternative: ""}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoBottom), Handler: self.HandleGotoBottom, Description: self.c.Tr.GotoBottom, Alternative: ""}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoTopAlt), Handler: self.HandleGotoTop}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoBottomAlt), Handler: self.HandleGotoBottom}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollLeft), Handler: self.HandleScrollLeft}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollRight), Handler: self.HandleScrollRight}, - } - - if self.context.RangeSelectEnabled() { - bindings = append(bindings, - []*types.Binding{ - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ToggleRangeSelect), Handler: self.HandleToggleRangeSelect, Description: self.c.Tr.ToggleRangeSelect}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.RangeSelectDown), Handler: self.HandleRangeSelectDown, Description: self.c.Tr.RangeSelectDown}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.RangeSelectUp), Handler: self.HandleRangeSelectUp, Description: self.c.Tr.RangeSelectUp}, - }..., - ) - } - - return bindings -} - -func (self *ListController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: self.context.GetViewName(), - Key: gocui.MouseWheelUp, - Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollUp() }, - }, - { - ViewName: self.context.GetViewName(), - Key: gocui.MouseLeft, - Handler: func(opts gocui.ViewMouseBindingOpts) error { return self.HandleClick(opts) }, - }, - { - ViewName: self.context.GetViewName(), - Key: gocui.MouseWheelDown, - Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollDown() }, - }, - } -} diff --git a/pkg/gui/controllers/list_controller_trait.go b/pkg/gui/controllers/list_controller_trait.go deleted file mode 100644 index 4f05a3d2abc..00000000000 --- a/pkg/gui/controllers/list_controller_trait.go +++ /dev/null @@ -1,160 +0,0 @@ -package controllers - -import ( - "errors" - - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -// Embed this into your list controller to get some convenience methods for -// ensuring a single item is selected, etc. - -type ListControllerTrait[T comparable] struct { - c *ControllerCommon - context types.IListContext - getSelectedItem func() T - getSelectedItems func() ([]T, int, int) -} - -func NewListControllerTrait[T comparable]( - c *ControllerCommon, - context types.IListContext, - getSelected func() T, - getSelectedItems func() ([]T, int, int), -) *ListControllerTrait[T] { - return &ListControllerTrait[T]{ - c: c, - context: context, - getSelectedItem: getSelected, - getSelectedItems: getSelectedItems, - } -} - -// Convenience function for combining multiple disabledReason callbacks. -// The first callback to return a disabled reason will be the one returned. -func (self *ListControllerTrait[T]) require(callbacks ...func() *types.DisabledReason) func() *types.DisabledReason { - return func() *types.DisabledReason { - for _, callback := range callbacks { - if disabledReason := callback(); disabledReason != nil { - return disabledReason - } - } - - return nil - } -} - -// Convenience function for enforcing that a single item is selected. -// Also takes callbacks for additional disabled reasons, and passes the selected -// item into each one. -func (self *ListControllerTrait[T]) singleItemSelected(callbacks ...func(T) *types.DisabledReason) func() *types.DisabledReason { - return func() *types.DisabledReason { - if self.context.GetList().AreMultipleItemsSelected() { - return &types.DisabledReason{Text: self.c.Tr.RangeSelectNotSupported} - } - - var zeroValue T - item := self.getSelectedItem() - if item == zeroValue { - return &types.DisabledReason{Text: self.c.Tr.NoItemSelected} - } - - for _, callback := range callbacks { - if reason := callback(item); reason != nil { - return reason - } - } - - return nil - } -} - -// Ensures that at least one item is selected. -func (self *ListControllerTrait[T]) itemRangeSelected(callbacks ...func([]T, int, int) *types.DisabledReason) func() *types.DisabledReason { - return func() *types.DisabledReason { - items, startIdx, endIdx := self.getSelectedItems() - if len(items) == 0 { - return &types.DisabledReason{Text: self.c.Tr.NoItemSelected} - } - - for _, callback := range callbacks { - if reason := callback(items, startIdx, endIdx); reason != nil { - return reason - } - } - - return nil - } -} - -func (self *ListControllerTrait[T]) itemsSelected(callbacks ...func([]T) *types.DisabledReason) func() *types.DisabledReason { - return func() *types.DisabledReason { - items, _, _ := self.getSelectedItems() - if len(items) == 0 { - return &types.DisabledReason{Text: self.c.Tr.NoItemSelected} - } - - for _, callback := range callbacks { - if reason := callback(items); reason != nil { - return reason - } - } - - return nil - } -} - -// Passes the selected item to the callback. Used for handler functions. -func (self *ListControllerTrait[T]) withItem(callback func(T) error) func() error { - return func() error { - var zeroValue T - commit := self.getSelectedItem() - if commit == zeroValue { - return errors.New(self.c.Tr.NoItemSelected) - } - - return callback(commit) - } -} - -func (self *ListControllerTrait[T]) withItems(callback func([]T) error) func() error { - return func() error { - items, _, _ := self.getSelectedItems() - if len(items) == 0 { - return errors.New(self.c.Tr.NoItemSelected) - } - - return callback(items) - } -} - -// like withItems but also passes the start and end index of the selection -func (self *ListControllerTrait[T]) withItemsRange(callback func([]T, int, int) error) func() error { - return func() error { - items, startIdx, endIdx := self.getSelectedItems() - if len(items) == 0 { - return errors.New(self.c.Tr.NoItemSelected) - } - - return callback(items, startIdx, endIdx) - } -} - -// Like withItem, but doesn't show an error message if no item is selected. -// Use this for click actions (it's a no-op to click empty space) -func (self *ListControllerTrait[T]) withItemGraceful(callback func(T) error) func() error { - return func() error { - var zeroValue T - commit := self.getSelectedItem() - if commit == zeroValue { - return nil - } - - return callback(commit) - } -} - -// All controllers must implement this method so we're defining it here for convenience -func (self *ListControllerTrait[T]) Context() types.Context { - return self.context -} diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go deleted file mode 100644 index 9badaf1edcb..00000000000 --- a/pkg/gui/controllers/local_commits_controller.go +++ /dev/null @@ -1,1487 +0,0 @@ -package controllers - -import ( - "strings" - - "github.com/go-errors/errors" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/context/traits" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/stefanhaller/git-todo-parser/todo" -) - -// after selecting the 200th commit, we'll load in all the rest -const COMMIT_THRESHOLD = 200 - -type ( - PullFilesFn func() error -) - -type LocalCommitsController struct { - baseController - *ListControllerTrait[*models.Commit] - c *ControllerCommon - - pullFiles PullFilesFn -} - -var _ types.IController = &LocalCommitsController{} - -func NewLocalCommitsController( - c *ControllerCommon, - pullFiles PullFilesFn, -) *LocalCommitsController { - return &LocalCommitsController{ - baseController: baseController{}, - c: c, - pullFiles: pullFiles, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().LocalCommits, - c.Contexts().LocalCommits.GetSelected, - c.Contexts().LocalCommits.GetSelectedItems, - ), - } -} - -func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - editCommitKey := opts.Config.Universal.Edit - - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Commits.SquashDown), - Handler: opts.Guards.OutsideFilterMode(self.withItemsRange(self.squashDown)), - GetDisabledReason: self.require( - self.itemRangeSelected( - self.midRebaseCommandEnabled, - self.canSquashOrFixup, - ), - ), - Description: self.c.Tr.Squash, - Tooltip: self.c.Tr.SquashTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.MarkCommitAsFixup), - Handler: opts.Guards.OutsideFilterMode(self.withItemsRange(self.fixup)), - GetDisabledReason: self.require( - self.itemRangeSelected( - self.midRebaseCommandEnabled, - self.canSquashOrFixup, - ), - ), - Description: self.c.Tr.Fixup, - Tooltip: self.c.Tr.FixupTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.RenameCommit), - Handler: self.withItem(self.reword), - GetDisabledReason: self.require( - self.singleItemSelected(self.rewordEnabled), - ), - Description: self.c.Tr.Reword, - Tooltip: self.c.Tr.CommitRewordTooltip, - DisplayOnScreen: true, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.RenameCommitWithEditor), - Handler: self.withItem(self.rewordEditor), - GetDisabledReason: self.require( - self.singleItemSelected(self.rewordEnabled), - ), - Description: self.c.Tr.RewordCommitEditor, - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: self.withItemsRange(self.drop), - GetDisabledReason: self.require( - self.itemRangeSelected( - self.canDropCommits, - ), - ), - Description: self.c.Tr.DropCommit, - Tooltip: self.c.Tr.DropCommitTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(editCommitKey), - Handler: opts.Guards.OutsideFilterMode(self.withItemsRange(self.edit)), - GetDisabledReason: self.require( - self.itemRangeSelected(self.midRebaseCommandEnabled), - ), - Description: self.c.Tr.EditCommit, - ShortDescription: self.c.Tr.Edit, - Tooltip: self.c.Tr.EditCommitTooltip, - DisplayOnScreen: true, - }, - { - // The user-facing description here is 'Start interactive rebase' but internally - // we're calling it 'quick-start interactive rebase' to differentiate it from - // when you manually select the base commit. - Key: opts.GetKey(opts.Config.Commits.StartInteractiveRebase), - Handler: opts.Guards.OutsideFilterMode(self.quickStartInteractiveRebase), - GetDisabledReason: self.require(self.notMidRebase(self.c.Tr.AlreadyRebasing), self.canFindCommitForQuickStart), - Description: self.c.Tr.QuickStartInteractiveRebase, - Tooltip: utils.ResolvePlaceholderString(self.c.Tr.QuickStartInteractiveRebaseTooltip, map[string]string{ - "editKey": keybindings.Label(editCommitKey), - }), - }, - { - Key: opts.GetKey(opts.Config.Commits.PickCommit), - Handler: opts.Guards.OutsideFilterMode(self.withItems(self.pick)), - GetDisabledReason: self.require( - self.itemRangeSelected(self.pickEnabled), - ), - Description: self.c.Tr.Pick, - Tooltip: self.c.Tr.PickCommitTooltip, - }, - { - Key: opts.GetKey(opts.Config.Commits.CreateFixupCommit), - Handler: opts.Guards.OutsideFilterMode(self.withItem(self.createFixupCommit)), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.CreateFixupCommit, - Tooltip: utils.ResolvePlaceholderString( - self.c.Tr.CreateFixupCommitTooltip, - map[string]string{ - "squashAbove": keybindings.Label(opts.Config.Commits.SquashAboveCommits), - }, - ), - }, - { - Key: opts.GetKey(opts.Config.Commits.SquashAboveCommits), - Handler: opts.Guards.OutsideFilterMode(self.squashFixupCommits), - GetDisabledReason: self.require( - self.notMidRebase(self.c.Tr.AlreadyRebasing), - ), - Description: self.c.Tr.SquashAboveCommits, - Tooltip: self.c.Tr.SquashAboveCommitsTooltip, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.MoveDownCommit), - Handler: opts.Guards.OutsideFilterMode(self.withItemsRange(self.moveDown)), - GetDisabledReason: self.require(self.itemRangeSelected( - self.midRebaseMoveCommandEnabled, - self.canMoveDown, - )), - Description: self.c.Tr.MoveDownCommit, - }, - { - Key: opts.GetKey(opts.Config.Commits.MoveUpCommit), - Handler: opts.Guards.OutsideFilterMode(self.withItemsRange(self.moveUp)), - GetDisabledReason: self.require(self.itemRangeSelected( - self.midRebaseMoveCommandEnabled, - self.canMoveUp, - )), - Description: self.c.Tr.MoveUpCommit, - }, - { - Key: opts.GetKey(opts.Config.Commits.PasteCommits), - Handler: opts.Guards.OutsideFilterMode(self.paste), - GetDisabledReason: self.require(self.canPaste), - Description: self.c.Tr.PasteCommits, - DisplayStyle: &style.FgCyan, - }, - { - Key: opts.GetKey(opts.Config.Commits.MarkCommitAsBaseForRebase), - Handler: opts.Guards.OutsideFilterMode(self.withItem(self.markAsBaseCommit)), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.MarkAsBaseCommit, - Tooltip: self.c.Tr.MarkAsBaseCommitTooltip, - }, - // overriding this navigation keybinding because we might need to load - // more commits on demand - { - Key: opts.GetKey(opts.Config.Universal.StartSearch), - Handler: self.openSearch, - Description: self.c.Tr.StartSearch, - Tag: "navigation", - }, - { - Key: opts.GetKey(opts.Config.Commits.AmendToCommit), - Handler: self.withItem(self.amendTo), - GetDisabledReason: self.require(self.singleItemSelected(self.canAmend)), - Description: self.c.Tr.Amend, - Tooltip: self.c.Tr.AmendCommitTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.ResetCommitAuthor), - Handler: self.withItemsRange(self.amendAttribute), - GetDisabledReason: self.require(self.itemRangeSelected(self.canAmendRange)), - Description: self.c.Tr.AmendCommitAttribute, - Tooltip: self.c.Tr.AmendCommitAttributeTooltip, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.RevertCommit), - Handler: self.withItemsRange(self.revert), - GetDisabledReason: self.require(self.itemRangeSelected()), - Description: self.c.Tr.Revert, - Tooltip: self.c.Tr.RevertCommitTooltip, - }, - { - Key: opts.GetKey(opts.Config.Commits.CreateTag), - Handler: self.withItem(self.createTag), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.TagCommit, - Tooltip: self.c.Tr.TagCommitTooltip, - }, - { - Key: opts.GetKey(opts.Config.Commits.OpenLogMenu), - Handler: self.handleOpenLogMenu, - Description: self.c.Tr.OpenLogMenu, - Tooltip: self.c.Tr.OpenLogMenuTooltip, - OpensMenu: true, - }, - } - - return bindings -} - -func (self *LocalCommitsController) GetOnRenderToMain() func() { - return func() { - self.c.Helpers().Diff.WithDiffModeCheck(func() { - var task types.UpdateTask - commit := self.context().GetSelected() - if commit == nil { - task = types.NewRenderStringTask(self.c.Tr.NoCommitsThisBranch) - } else if commit.Action == todo.UpdateRef { - task = types.NewRenderStringTask( - utils.ResolvePlaceholderString( - self.c.Tr.UpdateRefHere, - map[string]string{ - "ref": strings.TrimPrefix(commit.Name, "refs/heads/"), - })) - } else if commit.Action == todo.Exec { - task = types.NewRenderStringTask( - self.c.Tr.ExecCommandHere + "\n\n" + commit.Name) - } else { - refRange := self.context().GetSelectedRefRangeForDiffFiles() - task = self.c.Helpers().Diff.GetUpdateTaskForRenderingCommitsDiff(commit, refRange) - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: "Patch", - SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(), - Task: task, - }, - Secondary: secondaryPatchPanelUpdateOpts(self.c), - }) - }) - } -} - -func secondaryPatchPanelUpdateOpts(c *ControllerCommon) *types.ViewUpdateOpts { - if c.Git().Patch.PatchBuilder.Active() { - patch := c.Git().Patch.PatchBuilder.RenderAggregatedPatch(false) - - return &types.ViewUpdateOpts{ - Task: types.NewRenderStringWithoutScrollTask(patch), - Title: c.Tr.CustomPatch, - } - } - - return nil -} - -func (self *LocalCommitsController) squashDown(selectedCommits []*models.Commit, startIdx int, endIdx int) error { - if self.isRebasing() { - return self.updateTodos(todo.Squash, selectedCommits) - } - - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Squash, - Prompt: self.c.Tr.SureSquashThisCommit, - HandleConfirm: func() error { - return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.SquashCommitDown) - return self.interactiveRebase(todo.Squash, startIdx, endIdx) - }) - }, - }) - - return nil -} - -func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, startIdx int, endIdx int) error { - if self.isRebasing() { - return self.updateTodos(todo.Fixup, selectedCommits) - } - - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Fixup, - Prompt: self.c.Tr.SureFixupThisCommit, - HandleConfirm: func() error { - return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.FixupCommit) - return self.interactiveRebase(todo.Fixup, startIdx, endIdx) - }) - }, - }) - - return nil -} - -func (self *LocalCommitsController) reword(commit *models.Commit) error { - commitIdx := self.context().GetSelectedLineIdx() - if self.c.Git().Config.NeedsGpgSubprocessForCommit() && !self.isHeadCommit(commitIdx) { - return errors.New(self.c.Tr.DisabledForGPG) - } - commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Hash()) - if err != nil { - return err - } - if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage { - commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig().Git.Commit.AutoWrapWidth) - } - self.c.Helpers().Commits.OpenCommitMessagePanel( - &helpers.OpenCommitMessagePanelOpts{ - CommitIndex: commitIdx, - InitialMessage: commitMessage, - SummaryTitle: self.c.Tr.Actions.RewordCommit, - DescriptionTitle: self.c.Tr.CommitDescriptionTitle, - PreserveMessage: false, - OnConfirm: self.handleReword, - OnSwitchToEditor: self.switchFromCommitMessagePanelToEditor, - }, - ) - - return nil -} - -func (self *LocalCommitsController) switchFromCommitMessagePanelToEditor(filepath string) error { - if self.isSelectedHeadCommit() { - return self.c.RunSubprocessAndRefresh( - self.c.Git().Commit.RewordLastCommitInEditorWithMessageFileCmdObj(filepath)) - } - - err := self.c.Git().Rebase.BeginInteractiveRebaseForCommit(self.c.Model().Commits, self.context().GetSelectedLineIdx(), false) - if err != nil { - return err - } - - // now the selected commit should be our head so we'll amend it with the new message - err = self.c.RunSubprocessAndRefresh( - self.c.Git().Commit.RewordLastCommitInEditorWithMessageFileCmdObj(filepath)) - if err != nil { - return err - } - - err = self.c.Git().Rebase.ContinueRebase() - if err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil -} - -func (self *LocalCommitsController) handleReword(summary string, description string) error { - if models.IsHeadCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx()) { - // we've selected the top commit so no rebase is required - return self.c.Helpers().GPG.WithGpgHandling(self.c.Git().Commit.RewordLastCommit(summary, description), - git_commands.CommitGpgSign, - self.c.Tr.RewordingStatus, nil, nil) - } - - return self.c.WithWaitingStatus(self.c.Tr.RewordingStatus, func(gocui.Task) error { - err := self.c.Git().Rebase.RewordCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), summary, description) - if err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }) -} - -func (self *LocalCommitsController) doRewordEditor() error { - self.c.LogAction(self.c.Tr.Actions.RewordCommit) - - if self.isSelectedHeadCommit() { - return self.c.RunSubprocessAndRefresh(self.c.Git().Commit.RewordLastCommitInEditorCmdObj()) - } - - subProcess, err := self.c.Git().Rebase.RewordCommitInEditor( - self.c.Model().Commits, self.context().GetSelectedLineIdx(), - ) - if err != nil { - return err - } - if subProcess != nil { - return self.c.RunSubprocessAndRefresh(subProcess) - } - - return nil -} - -func (self *LocalCommitsController) rewordEditor(commit *models.Commit) error { - return self.c.ConfirmIf(!self.c.UserConfig().Gui.SkipRewordInEditorWarning, - types.ConfirmOpts{ - Title: self.c.Tr.RewordInEditorTitle, - Prompt: self.c.Tr.RewordInEditorPrompt, - HandleConfirm: self.doRewordEditor, - }) -} - -func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, startIdx int, endIdx int) error { - if self.isRebasing() { - groupedTodos := lo.GroupBy(selectedCommits, func(c *models.Commit) bool { - return c.Action == todo.UpdateRef - }) - updateRefTodos := groupedTodos[true] - nonUpdateRefTodos := groupedTodos[false] - - if len(updateRefTodos) > 0 { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.DropCommitTitle, - Prompt: self.c.Tr.DropUpdateRefPrompt, - HandleConfirm: func() error { - selectedIdx, rangeStartIdx, rangeSelectMode := self.context().GetSelectionRangeAndMode() - - if err := self.c.Git().Rebase.DeleteUpdateRefTodos(updateRefTodos); err != nil { - return err - } - - if selectedIdx > rangeStartIdx { - selectedIdx = max(selectedIdx-len(updateRefTodos), rangeStartIdx) - } else { - rangeStartIdx = max(rangeStartIdx-len(updateRefTodos), selectedIdx) - } - - self.context().SetSelectionRangeAndMode(selectedIdx, rangeStartIdx, rangeSelectMode) - - return self.updateTodos(todo.Drop, nonUpdateRefTodos) - }, - }) - - return nil - } - - return self.updateTodos(todo.Drop, selectedCommits) - } - - isMerge := selectedCommits[0].IsMerge() - - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.DropCommitTitle, - Prompt: lo.Ternary(isMerge, self.c.Tr.DropMergeCommitPrompt, self.c.Tr.DropCommitPrompt), - HandleConfirm: func() error { - return self.c.WithWaitingStatus(self.c.Tr.DroppingStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.DropCommit) - if isMerge { - return self.dropMergeCommit(startIdx) - } - return self.interactiveRebase(todo.Drop, startIdx, endIdx) - }) - }, - }) - - return nil -} - -func (self *LocalCommitsController) dropMergeCommit(commitIdx int) error { - err := self.c.Git().Rebase.DropMergeCommit(self.c.Model().Commits, commitIdx) - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) -} - -func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, startIdx int, endIdx int) error { - if self.isRebasing() { - return self.updateTodos(todo.Edit, selectedCommits) - } - - commits := self.c.Model().Commits - if !commits[endIdx].IsMerge() { - selectionRangeAndMode := self.getSelectionRangeAndMode() - err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit) - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions( - err, - types.RefreshOptions{ - Mode: types.BLOCK_UI, Then: func() { - self.restoreSelectionRangeAndMode(selectionRangeAndMode) - }, - }) - } - - return self.startInteractiveRebaseWithEdit(selectedCommits) -} - -func (self *LocalCommitsController) quickStartInteractiveRebase() error { - commitToEdit, err := self.findCommitForQuickStartInteractiveRebase() - if err != nil { - return err - } - - return self.startInteractiveRebaseWithEdit([]*models.Commit{commitToEdit}) -} - -func (self *LocalCommitsController) startInteractiveRebaseWithEdit( - commitsToEdit []*models.Commit, -) error { - return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.EditCommit) - selectionRangeAndMode := self.getSelectionRangeAndMode() - err := self.c.Git().Rebase.EditRebase(commitsToEdit[len(commitsToEdit)-1].Hash()) - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions( - err, - types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() { - todos := make([]*models.Commit, 0, len(commitsToEdit)-1) - for _, c := range commitsToEdit[:len(commitsToEdit)-1] { - // Merge commits can't be set to "edit", so just skip them - if !c.IsMerge() { - todos = append(todos, models.NewCommit(self.c.Model().HashPool, models.NewCommitOpts{Hash: c.Hash(), Action: todo.Pick})) - } - } - if len(todos) > 0 { - err := self.updateTodos(todo.Edit, todos) - if err != nil { - self.c.Log.Errorf("error when updating todos: %v", err) - } - } - - self.restoreSelectionRangeAndMode(selectionRangeAndMode) - }}) - }) -} - -type SelectionRangeAndMode struct { - selectedHash string - rangeStartHash string - mode traits.RangeSelectMode -} - -func (self *LocalCommitsController) getSelectionRangeAndMode() SelectionRangeAndMode { - selectedIdx, rangeStartIdx, rangeSelectMode := self.context().GetSelectionRangeAndMode() - commits := self.c.Model().Commits - selectedHash := commits[selectedIdx].Hash() - rangeStartHash := commits[rangeStartIdx].Hash() - return SelectionRangeAndMode{selectedHash, rangeStartHash, rangeSelectMode} -} - -func (self *LocalCommitsController) restoreSelectionRangeAndMode(selectionRangeAndMode SelectionRangeAndMode) { - // We need to select the same commit range again because after starting a rebase, - // new lines can be added for update-ref commands in the TODO file, due to - // stacked branches. So the selected commits may be in different positions in the list. - _, newSelectedIdx, ok1 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool { - return c.Hash() == selectionRangeAndMode.selectedHash - }) - _, newRangeStartIdx, ok2 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool { - return c.Hash() == selectionRangeAndMode.rangeStartHash - }) - if ok1 && ok2 { - self.context().SetSelectionRangeAndMode(newSelectedIdx, newRangeStartIdx, selectionRangeAndMode.mode) - self.context().HandleFocus(types.OnFocusOpts{}) - } -} - -func (self *LocalCommitsController) findCommitForQuickStartInteractiveRebase() (*models.Commit, error) { - commit, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool { - return c.IsMerge() || c.Status == models.StatusMerged - }) - - if !ok || index == 0 { - errorMsg := utils.ResolvePlaceholderString(self.c.Tr.CannotQuickStartInteractiveRebase, map[string]string{ - "editKey": keybindings.Label(self.c.UserConfig().Keybinding.Universal.Edit), - }) - - return nil, errors.New(errorMsg) - } - - return commit, nil -} - -func (self *LocalCommitsController) pick(selectedCommits []*models.Commit) error { - if self.isRebasing() { - return self.updateTodos(todo.Pick, selectedCommits) - } - - panic("should be disabled when not rebasing") -} - -func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand, startIdx int, endIdx int) error { - // When performing an action that will remove the selected commits, we need to select the - // next commit down (which will end up at the start index after the action is performed) - if action == todo.Drop || action == todo.Fixup || action == todo.Squash { - self.context().SetSelection(startIdx) - } - - err := self.c.Git().Rebase.InteractiveRebase(self.c.Model().Commits, startIdx, endIdx, action) - - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) -} - -// updateTodos sees if the selected commit is in fact a rebasing -// commit meaning you are trying to edit the todo file rather than actually -// begin a rebase. It then updates the todo file with that action -func (self *LocalCommitsController) updateTodos(action todo.TodoCommand, selectedCommits []*models.Commit) error { - if err := self.c.Git().Rebase.EditRebaseTodo(selectedCommits, action); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{ - Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS}, - }) - - return nil -} - -func (self *LocalCommitsController) rewordEnabled(commit *models.Commit) *types.DisabledReason { - // for now we do not support setting 'reword' on TODO commits because it requires an editor - // and that means we either unconditionally wait around for the subprocess to ask for - // our input or we set a lazygit client as the EDITOR env variable and have it - // request us to edit the commit message when prompted. - if commit.IsTODO() { - return &types.DisabledReason{Text: self.c.Tr.RewordNotSupported} - } - - // If we are in a rebase, the only action that is allowed for - // non-todo commits is rewording the current head commit - if self.isRebasing() && !self.isSelectedHeadCommit() { - return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing} - } - - return nil -} - -func (self *LocalCommitsController) isRebasing() bool { - return self.c.Model().WorkingTreeStateAtLastCommitRefresh.Any() -} - -func (self *LocalCommitsController) isCherryPickingOrReverting() bool { - return self.c.Model().WorkingTreeStateAtLastCommitRefresh.CherryPicking || - self.c.Model().WorkingTreeStateAtLastCommitRefresh.Reverting -} - -func (self *LocalCommitsController) moveDown(selectedCommits []*models.Commit, startIdx int, endIdx int) error { - if self.isRebasing() { - if err := self.c.Git().Rebase.MoveTodosDown(selectedCommits); err != nil { - return err - } - self.context().MoveSelection(1) - - self.c.Refresh(types.RefreshOptions{ - Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS}, - }) - return nil - } - - return self.c.WithWaitingStatusSync(self.c.Tr.MovingStatus, func() error { - self.c.LogAction(self.c.Tr.Actions.MoveCommitDown) - err := self.c.Git().Rebase.MoveCommitsDown(self.c.Model().Commits, startIdx, endIdx) - if err == nil { - self.context().MoveSelection(1) - } - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions( - err, types.RefreshOptions{Mode: types.SYNC}) - }) -} - -func (self *LocalCommitsController) moveUp(selectedCommits []*models.Commit, startIdx int, endIdx int) error { - if self.isRebasing() { - if err := self.c.Git().Rebase.MoveTodosUp(selectedCommits); err != nil { - return err - } - self.context().MoveSelection(-1) - - self.c.Refresh(types.RefreshOptions{ - Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS}, - }) - return nil - } - - return self.c.WithWaitingStatusSync(self.c.Tr.MovingStatus, func() error { - self.c.LogAction(self.c.Tr.Actions.MoveCommitUp) - err := self.c.Git().Rebase.MoveCommitsUp(self.c.Model().Commits, startIdx, endIdx) - if err == nil { - self.context().MoveSelection(-1) - } - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions( - err, types.RefreshOptions{Mode: types.SYNC}) - }) -} - -func (self *LocalCommitsController) amendTo(commit *models.Commit) error { - var handleCommit func() error - - if self.isSelectedHeadCommit() { - handleCommit = func() error { - return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error { - if err := self.c.Helpers().AmendHelper.AmendHead(); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }) - } - } else { - handleCommit = func() error { - return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error { - return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.AmendCommit) - err := self.c.Git().Rebase.AmendTo(self.c.Model().Commits, self.context().GetView().SelectedLineIdx()) - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) - }) - }) - } - } - - return self.c.ConfirmIf(!self.c.UserConfig().Gui.SkipAmendWarning, - types.ConfirmOpts{ - Title: self.c.Tr.AmendCommitTitle, - Prompt: self.c.Tr.AmendCommitPrompt, - HandleConfirm: handleCommit, - }) -} - -func (self *LocalCommitsController) canAmendRange(commits []*models.Commit, start, end int) *types.DisabledReason { - if (start != end || !self.isHeadCommit(start)) && self.isRebasing() { - return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing} - } - - return nil -} - -func (self *LocalCommitsController) canAmend(_ *models.Commit) *types.DisabledReason { - idx := self.context().GetSelectedLineIdx() - return self.canAmendRange(self.c.Model().Commits, idx, idx) -} - -func (self *LocalCommitsController) amendAttribute(commits []*models.Commit, start, end int) error { - opts := self.c.KeybindingsOpts() - return self.c.Menu(types.CreateMenuOptions{ - Title: "Amend commit attribute", - Items: []*types.MenuItem{ - { - Label: self.c.Tr.ResetAuthor, - OnPress: func() error { return self.resetAuthor(start, end) }, - Key: opts.GetKey(opts.Config.AmendAttribute.ResetAuthor), - Tooltip: self.c.Tr.ResetAuthorTooltip, - }, - { - Label: self.c.Tr.SetAuthor, - OnPress: func() error { return self.setAuthor(start, end) }, - Key: opts.GetKey(opts.Config.AmendAttribute.SetAuthor), - Tooltip: self.c.Tr.SetAuthorTooltip, - }, - { - Label: self.c.Tr.AddCoAuthor, - OnPress: func() error { return self.addCoAuthor(start, end) }, - Key: opts.GetKey(opts.Config.AmendAttribute.AddCoAuthor), - Tooltip: self.c.Tr.AddCoAuthorTooltip, - }, - }, - }) -} - -func (self *LocalCommitsController) resetAuthor(start, end int) error { - return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.ResetCommitAuthor) - if err := self.c.Git().Rebase.ResetCommitAuthor(self.c.Model().Commits, start, end); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }) -} - -func (self *LocalCommitsController) setAuthor(start, end int) error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.SetAuthorPromptTitle, - FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(), - HandleConfirm: func(value string) error { - return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.SetCommitAuthor) - if err := self.c.Git().Rebase.SetCommitAuthor(self.c.Model().Commits, start, end, value); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }) - }, - }) - - return nil -} - -func (self *LocalCommitsController) addCoAuthor(start, end int) error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.AddCoAuthorPromptTitle, - FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(), - HandleConfirm: func(value string) error { - return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.AddCommitCoAuthor) - if err := self.c.Git().Rebase.AddCommitCoAuthor(self.c.Model().Commits, start, end, value); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }) - }, - }) - - return nil -} - -func (self *LocalCommitsController) revert(commits []*models.Commit, start, end int) error { - var promptText string - if len(commits) == 1 { - promptText = utils.ResolvePlaceholderString( - self.c.Tr.ConfirmRevertCommit, - map[string]string{ - "selectedCommit": commits[0].ShortHash(), - }) - } else { - promptText = self.c.Tr.ConfirmRevertCommitRange - } - hashes := lo.Map(commits, func(c *models.Commit, _ int) string { return c.Hash() }) - isMerge := lo.SomeBy(commits, func(c *models.Commit) bool { return c.IsMerge() }) - - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Actions.RevertCommit, - Prompt: promptText, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.RevertCommit) - return self.c.WithWaitingStatusSync(self.c.Tr.RevertingStatus, func() error { - mustStash := helpers.IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules) - - if mustStash { - if err := self.c.Git().Stash.Push(self.c.Tr.AutoStashForReverting); err != nil { - return err - } - } - - result := self.c.Git().Commit.Revert(hashes, isMerge) - if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(result, types.RefreshOptions{Mode: types.SYNC}); err != nil { - return err - } - self.context().MoveSelection(len(commits)) - self.context().FocusLine() - - if mustStash { - if err := self.c.Git().Stash.Pop(0); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{ - Scope: []types.RefreshableView{types.STASH, types.FILES}, - }) - } - - return nil - }) - }, - }) - - return nil -} - -func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) error { - var disabledReasonWhenFilesAreNeeded *types.DisabledReason - if len(self.c.Model().Files) == 0 { - disabledReasonWhenFilesAreNeeded = &types.DisabledReason{ - Text: self.c.Tr.NoFilesStagedTitle, - ShowErrorInPanel: true, - } - } - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.CreateFixupCommit, - Items: []*types.MenuItem{ - { - Label: self.c.Tr.FixupMenu_Fixup, - Key: 'f', - OnPress: func() error { - return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error { - self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit) - return self.c.WithWaitingStatusSync(self.c.Tr.CreatingFixupCommitStatus, func() error { - if err := self.c.Git().Commit.CreateFixupCommit(commit.Hash()); err != nil { - return err - } - - if err := self.moveFixupCommitToOwnerStackedBranch(commit); err != nil { - return err - } - - self.context().MoveSelectedLine(1) - self.c.Refresh(types.RefreshOptions{Mode: types.SYNC}) - return nil - }) - }) - }, - DisabledReason: disabledReasonWhenFilesAreNeeded, - Tooltip: self.c.Tr.FixupMenu_FixupTooltip, - }, - { - Label: self.c.Tr.FixupMenu_AmendWithChanges, - Key: 'a', - OnPress: func() error { - return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error { - return self.createAmendCommit(commit, true) - }) - }, - DisabledReason: disabledReasonWhenFilesAreNeeded, - Tooltip: self.c.Tr.FixupMenu_AmendWithChangesTooltip, - }, - { - Label: self.c.Tr.FixupMenu_AmendWithoutChanges, - Key: 'r', - OnPress: func() error { return self.createAmendCommit(commit, false) }, - Tooltip: self.c.Tr.FixupMenu_AmendWithoutChangesTooltip, - }, - }, - }) -} - -func (self *LocalCommitsController) moveFixupCommitToOwnerStackedBranch(targetCommit *models.Commit) error { - if self.c.Git().Version.IsOlderThan(2, 38, 0) { - // Git 2.38.0 introduced the `rebase.updateRefs` config option. Don't - // move the commit down with older versions, as it would break the stack. - return nil - } - - if self.c.Git().Status.WorkingTreeState().Any() { - // Can't move commits while rebasing - return nil - } - - if targetCommit.Status == models.StatusMerged { - // Target commit is already on main. It's a bit questionable that we - // allow creating a fixup commit for it in the first place, but we - // always did, so why restrict that now; however, it doesn't make sense - // to move the created fixup commit down in that case. - return nil - } - - if !self.c.Git().Config.GetRebaseUpdateRefs() { - // If the user has disabled rebase.updateRefs, we don't move the fixup - // because this would break the stack of branches (presumably they like - // to manage it themselves manually, or something). - return nil - } - - headOfOwnerBranchIdx := -1 - for i := self.context().GetSelectedLineIdx(); i > 0; i-- { - if lo.SomeBy(self.c.Model().Branches, func(b *models.Branch) bool { - return b.CommitHash == self.c.Model().Commits[i].Hash() - }) { - headOfOwnerBranchIdx = i - break - } - } - - if headOfOwnerBranchIdx == -1 { - return nil - } - - return self.c.Git().Rebase.MoveFixupCommitDown(self.c.Model().Commits, headOfOwnerBranchIdx) -} - -func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, includeFileChanges bool) error { - commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Hash()) - if err != nil { - return err - } - if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage { - commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig().Git.Commit.AutoWrapWidth) - } - originalSubject, _, _ := strings.Cut(commitMessage, "\n") - self.c.Helpers().Commits.OpenCommitMessagePanel( - &helpers.OpenCommitMessagePanelOpts{ - CommitIndex: self.context().GetSelectedLineIdx(), - InitialMessage: commitMessage, - SummaryTitle: self.c.Tr.CreateAmendCommit, - DescriptionTitle: self.c.Tr.CommitDescriptionTitle, - PreserveMessage: false, - OnConfirm: func(summary string, description string) error { - self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit) - return self.c.WithWaitingStatusSync(self.c.Tr.CreatingFixupCommitStatus, func() error { - if err := self.c.Git().Commit.CreateAmendCommit(originalSubject, summary, description, includeFileChanges); err != nil { - return err - } - - if err := self.moveFixupCommitToOwnerStackedBranch(commit); err != nil { - return err - } - - self.context().MoveSelectedLine(1) - self.c.Refresh(types.RefreshOptions{Mode: types.SYNC}) - return nil - }) - }, - OnSwitchToEditor: nil, - }, - ) - - return nil -} - -func (self *LocalCommitsController) squashFixupCommits() error { - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.SquashAboveCommits, - Items: []*types.MenuItem{ - { - Label: self.c.Tr.SquashCommitsInCurrentBranch, - OnPress: self.squashAllFixupsInCurrentBranch, - DisabledReason: self.canFindCommitForSquashFixupsInCurrentBranch(), - Key: 'b', - Tooltip: self.c.Tr.SquashCommitsInCurrentBranchTooltip, - }, - { - Label: self.c.Tr.SquashCommitsAboveSelectedCommit, - OnPress: self.withItem(self.squashAllFixupsAboveSelectedCommit), - DisabledReason: self.singleItemSelected()(), - Key: 'a', - Tooltip: self.c.Tr.SquashCommitsAboveSelectedTooltip, - }, - }, - }) -} - -func (self *LocalCommitsController) squashAllFixupsAboveSelectedCommit(commit *models.Commit) error { - return self.squashFixupsImpl(commit, self.context().GetSelectedLineIdx()) -} - -func (self *LocalCommitsController) squashAllFixupsInCurrentBranch() error { - commit, rebaseStartIdx, err := self.findCommitForSquashFixupsInCurrentBranch() - if err != nil { - return err - } - - return self.squashFixupsImpl(commit, rebaseStartIdx) -} - -func (self *LocalCommitsController) squashFixupsImpl(commit *models.Commit, rebaseStartIdx int) error { - selectionOffset := countSquashableCommitsAbove(self.c.Model().Commits, self.context().GetSelectedLineIdx(), rebaseStartIdx) - return self.c.WithWaitingStatusSync(self.c.Tr.SquashingStatus, func() error { - self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits) - err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit) - self.context().MoveSelectedLine(-selectionOffset) - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions( - err, types.RefreshOptions{Mode: types.SYNC}) - }) -} - -func (self *LocalCommitsController) findCommitForSquashFixupsInCurrentBranch() (*models.Commit, int, error) { - commits := self.c.Model().Commits - _, index, ok := lo.FindIndexOf(commits, func(c *models.Commit) bool { - return c.IsMerge() || c.Status == models.StatusMerged - }) - - if !ok || index == 0 { - return nil, -1, errors.New(self.c.Tr.CannotSquashCommitsInCurrentBranch) - } - - return commits[index-1], index - 1, nil -} - -// Anticipate how many commits above the selectedIdx are going to get squashed -// by the SquashAllAboveFixupCommits call, so that we can adjust the selection -// afterwards. Let's hope we're matching git's behavior correctly here. -func countSquashableCommitsAbove(commits []*models.Commit, selectedIdx int, rebaseStartIdx int) int { - result := 0 - - // For each commit _above_ the selection, ... - for i, commit := range commits[0:selectedIdx] { - // ... see if it is a fixup commit, and get the base subject it applies to - if baseSubject, isFixup := isFixupCommit(commit.Name); isFixup { - // Then, for each commit after the fixup, up to and including the - // rebase start commit, see if we find the base commit - for _, baseCommit := range commits[i+1 : rebaseStartIdx+1] { - if strings.HasPrefix(baseCommit.Name, baseSubject) { - result++ - } - } - } - } - return result -} - -// Check whether the given subject line is the subject of a fixup commit, and -// returns (trimmedSubject, true) if so (where trimmedSubject is the subject -// with all fixup prefixes removed), or (subject, false) if not. -func isFixupCommit(subject string) (string, bool) { - prefixes := []string{"fixup! ", "squash! ", "amend! "} - trimPrefix := func(s string) (string, bool) { - for _, prefix := range prefixes { - if strings.HasPrefix(s, prefix) { - return strings.TrimPrefix(s, prefix), true - } - } - return s, false - } - - if subject, wasTrimmed := trimPrefix(subject); wasTrimmed { - for { - // handle repeated prefixes like "fixup! amend! fixup! Subject" - if subject, wasTrimmed = trimPrefix(subject); !wasTrimmed { - break - } - } - return subject, true - } - - return subject, false -} - -func (self *LocalCommitsController) createTag(commit *models.Commit) error { - return self.c.Helpers().Tags.OpenCreateTagPrompt(commit.Hash(), func() {}) -} - -func (self *LocalCommitsController) openSearch() error { - // we usually lazyload these commits but now that we're searching we need to load them now - if self.context().GetLimitCommits() { - self.context().SetLimitCommits(false) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS}}) - } - - return self.c.Helpers().Search.OpenSearchPrompt(self.context()) -} - -func (self *LocalCommitsController) handleOpenLogMenu() error { - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.LogMenuTitle, - Items: []*types.MenuItem{ - { - Label: self.c.Tr.ToggleShowGitGraphAll, - OnPress: func() error { - self.context().SetShowWholeGitGraph(!self.context().GetShowWholeGitGraph()) - - if self.context().GetShowWholeGitGraph() { - self.context().SetLimitCommits(false) - } - - return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(gocui.Task) error { - self.c.Refresh( - types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}}, - ) - return nil - }) - }, - }, - { - Label: self.c.Tr.ShowGitGraph, - Tooltip: self.c.Tr.ShowGitGraphTooltip, - OpensMenu: true, - OnPress: func() error { - currentValue := self.c.UserConfig().Git.Log.ShowGraph - onPress := func(value string) func() error { - return func() error { - self.c.UserConfig().Git.Log.ShowGraph = value - self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits) - self.c.PostRefreshUpdate(self.c.Contexts().SubCommits) - return nil - } - } - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.LogMenuTitle, - Items: []*types.MenuItem{ - { - Label: "always", - OnPress: onPress("always"), - Widget: types.MakeMenuRadioButton(currentValue == "always"), - }, - { - Label: "never", - OnPress: onPress("never"), - Widget: types.MakeMenuRadioButton(currentValue == "never"), - }, - { - Label: "when maximised", - OnPress: onPress("when-maximised"), - Widget: types.MakeMenuRadioButton(currentValue == "when-maximised"), - }, - }, - }) - }, - }, - { - Label: self.c.Tr.SortCommits, - Tooltip: self.c.Tr.SortCommitsTooltip, - OpensMenu: true, - OnPress: func() error { - currentValue := self.c.UserConfig().Git.Log.Order - onPress := func(value string) func() error { - return func() error { - self.c.UserConfig().Git.Log.Order = value - return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(gocui.Task) error { - self.c.Refresh( - types.RefreshOptions{ - Mode: types.SYNC, - Scope: []types.RefreshableView{types.COMMITS}, - }, - ) - return nil - }) - } - } - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.LogMenuTitle, - Items: []*types.MenuItem{ - { - Label: "topological (topo-order)", - OnPress: onPress("topo-order"), - Widget: types.MakeMenuRadioButton(currentValue == "topo-order"), - }, - { - Label: "date-order", - OnPress: onPress("date-order"), - Widget: types.MakeMenuRadioButton(currentValue == "date-order"), - }, - { - Label: "author-date-order", - OnPress: onPress("author-date-order"), - Widget: types.MakeMenuRadioButton(currentValue == "author-date-order"), - }, - { - Label: "default", - OnPress: onPress("default"), - Widget: types.MakeMenuRadioButton(currentValue == "default"), - }, - }, - }) - }, - }, - }, - }) -} - -func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) { - return func(types.OnFocusOpts) { - context := self.context() - if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() { - context.SetLimitCommits(false) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS}}) - } - } -} - -func (self *LocalCommitsController) context() *context.LocalCommitsContext { - return self.c.Contexts().LocalCommits -} - -func (self *LocalCommitsController) paste() error { - return self.c.Helpers().CherryPick.Paste() -} - -func (self *LocalCommitsController) canPaste() *types.DisabledReason { - if !self.c.Helpers().CherryPick.CanPaste() { - return &types.DisabledReason{Text: self.c.Tr.NoCopiedCommits} - } - - return nil -} - -func (self *LocalCommitsController) markAsBaseCommit(commit *models.Commit) error { - if commit.Hash() == self.c.Modes().MarkedBaseCommit.GetHash() { - // Reset when invoking it again on the marked commit - self.c.Modes().MarkedBaseCommit.SetHash("") - } else { - self.c.Modes().MarkedBaseCommit.SetHash(commit.Hash()) - } - self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits) - return nil -} - -func (self *LocalCommitsController) isHeadCommit(idx int) bool { - return models.IsHeadCommit(self.c.Model().Commits, idx) -} - -func (self *LocalCommitsController) isSelectedHeadCommit() bool { - return self.isHeadCommit(self.context().GetSelectedLineIdx()) -} - -func (self *LocalCommitsController) notMidRebase(message string) func() *types.DisabledReason { - return func() *types.DisabledReason { - if self.isRebasing() { - return &types.DisabledReason{Text: message} - } - - return nil - } -} - -func (self *LocalCommitsController) canFindCommitForQuickStart() *types.DisabledReason { - if _, err := self.findCommitForQuickStartInteractiveRebase(); err != nil { - return &types.DisabledReason{Text: err.Error(), ShowErrorInPanel: true} - } - - return nil -} - -func (self *LocalCommitsController) canFindCommitForSquashFixupsInCurrentBranch() *types.DisabledReason { - if _, _, err := self.findCommitForSquashFixupsInCurrentBranch(); err != nil { - return &types.DisabledReason{Text: err.Error()} - } - - return nil -} - -func (self *LocalCommitsController) canSquashOrFixup(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason { - if endIdx >= len(self.c.Model().Commits)-1 { - return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit} - } - - if lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) { - return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupMergeCommit} - } - - return nil -} - -func (self *LocalCommitsController) canMoveDown(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason { - if endIdx >= len(self.c.Model().Commits)-1 { - return &types.DisabledReason{Text: self.c.Tr.CannotMoveAnyFurther} - } - - if self.isRebasing() { - commits := self.c.Model().Commits - - if !commits[endIdx+1].IsTODO() || commits[endIdx+1].Status == models.StatusConflicted { - return &types.DisabledReason{Text: self.c.Tr.CannotMoveAnyFurther} - } - } - - return nil -} - -func (self *LocalCommitsController) canMoveUp(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason { - if startIdx == 0 { - return &types.DisabledReason{Text: self.c.Tr.CannotMoveAnyFurther} - } - - if self.isRebasing() { - commits := self.c.Model().Commits - - if !commits[startIdx-1].IsTODO() || commits[startIdx-1].Status == models.StatusConflicted { - return &types.DisabledReason{Text: self.c.Tr.CannotMoveAnyFurther} - } - } - - return nil -} - -// Ensures that if we are mid-rebase, we're only selecting valid commits (non-conflict TODO commits) -func (self *LocalCommitsController) midRebaseCommandEnabled(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason { - if self.isCherryPickingOrReverting() { - return &types.DisabledReason{Text: self.c.Tr.NotAllowedMidCherryPickOrRevert} - } - - if !self.isRebasing() { - return nil - } - - for _, commit := range selectedCommits { - if !commit.IsTODO() { - return &types.DisabledReason{Text: self.c.Tr.MustSelectTodoCommits} - } - - if !isChangeOfRebaseTodoAllowed(commit.Action) { - return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed} - } - } - - return nil -} - -// Ensures that if we are mid-rebase, we're only selecting commits that can be moved -func (self *LocalCommitsController) midRebaseMoveCommandEnabled(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason { - if self.isCherryPickingOrReverting() { - return &types.DisabledReason{Text: self.c.Tr.NotAllowedMidCherryPickOrRevert} - } - - if !self.isRebasing() { - if lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) { - return &types.DisabledReason{Text: self.c.Tr.CannotMoveMergeCommit} - } - - return nil - } - - for _, commit := range selectedCommits { - if !commit.IsTODO() { - return &types.DisabledReason{Text: self.c.Tr.MustSelectTodoCommits} - } - - // All todo types that can be edited are allowed to be moved, plus - // update-ref todos - if !isChangeOfRebaseTodoAllowed(commit.Action) && commit.Action != todo.UpdateRef { - return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed} - } - } - - return nil -} - -func (self *LocalCommitsController) canDropCommits(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason { - if self.isCherryPickingOrReverting() { - return &types.DisabledReason{Text: self.c.Tr.NotAllowedMidCherryPickOrRevert} - } - - if !self.isRebasing() { - if len(selectedCommits) > 1 && lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) { - return &types.DisabledReason{Text: self.c.Tr.DroppingMergeRequiresSingleSelection} - } - - return nil - } - - nonUpdateRefTodos := lo.Filter(selectedCommits, func(c *models.Commit, _ int) bool { - return c.Action != todo.UpdateRef - }) - - for _, commit := range nonUpdateRefTodos { - if !commit.IsTODO() { - return &types.DisabledReason{Text: self.c.Tr.MustSelectTodoCommits} - } - - if !isChangeOfRebaseTodoAllowed(commit.Action) { - return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed} - } - } - - return nil -} - -// These actions represent standard things you might want to do with a commit, -// as opposed to TODO actions like 'merge', 'update-ref', etc. -var standardActions = []todo.TodoCommand{ - todo.Pick, - todo.Drop, - todo.Edit, - todo.Fixup, - todo.Squash, - todo.Reword, -} - -func isChangeOfRebaseTodoAllowed(oldAction todo.TodoCommand) bool { - // Only allow updating a standard action, meaning we disallow - // updating a merge commit or update ref commit (until we decide what would be sensible - // to do in those cases) - return lo.Contains(standardActions, oldAction) -} - -func (self *LocalCommitsController) pickEnabled(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason { - if self.isCherryPickingOrReverting() { - return &types.DisabledReason{Text: self.c.Tr.NotAllowedMidCherryPickOrRevert} - } - - if !self.isRebasing() { - return &types.DisabledReason{Text: self.c.Tr.PickIsOnlyAllowedDuringRebase, AllowFurtherDispatching: true} - } - - return self.midRebaseCommandEnabled(selectedCommits, startIdx, endIdx) -} diff --git a/pkg/gui/controllers/local_commits_controller_test.go b/pkg/gui/controllers/local_commits_controller_test.go deleted file mode 100644 index d425821d777..00000000000 --- a/pkg/gui/controllers/local_commits_controller_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package controllers - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/stretchr/testify/assert" -) - -func Test_countSquashableCommitsAbove(t *testing.T) { - scenarios := []struct { - name string - commits []*models.Commit - selectedIdx int - rebaseStartIdx int - expectedResult int - }{ - { - name: "no squashable commits", - commits: []*models.Commit{ - {Name: "abc"}, - {Name: "def"}, - {Name: "ghi"}, - }, - selectedIdx: 2, - rebaseStartIdx: 2, - expectedResult: 0, - }, - { - name: "some squashable commits, including for the selected commit", - commits: []*models.Commit{ - {Name: "fixup! def"}, - {Name: "fixup! ghi"}, - {Name: "abc"}, - {Name: "def"}, - {Name: "ghi"}, - }, - selectedIdx: 4, - rebaseStartIdx: 4, - expectedResult: 2, - }, - { - name: "base commit is below rebase start", - commits: []*models.Commit{ - {Name: "fixup! def"}, - {Name: "abc"}, - {Name: "def"}, - }, - selectedIdx: 1, - rebaseStartIdx: 1, - expectedResult: 0, - }, - { - name: "base commit does not exist at all", - commits: []*models.Commit{ - {Name: "fixup! xyz"}, - {Name: "abc"}, - {Name: "def"}, - }, - selectedIdx: 2, - rebaseStartIdx: 2, - expectedResult: 0, - }, - { - name: "selected commit is in the middle of fixups", - commits: []*models.Commit{ - {Name: "fixup! def"}, - {Name: "abc"}, - {Name: "fixup! ghi"}, - {Name: "def"}, - {Name: "ghi"}, - }, - selectedIdx: 1, - rebaseStartIdx: 4, - expectedResult: 1, - }, - { - name: "selected commit is after rebase start", - commits: []*models.Commit{ - {Name: "fixup! def"}, - {Name: "abc"}, - {Name: "def"}, - {Name: "ghi"}, - }, - selectedIdx: 3, - rebaseStartIdx: 2, - expectedResult: 1, - }, - } - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - assert.Equal(t, s.expectedResult, countSquashableCommitsAbove(s.commits, s.selectedIdx, s.rebaseStartIdx)) - }) - } -} - -func Test_isFixupCommit(t *testing.T) { - scenarios := []struct { - subject string - expectedTrimmedSubject string - expectedIsFixup bool - }{ - { - subject: "Bla", - expectedTrimmedSubject: "Bla", - expectedIsFixup: false, - }, - { - subject: "fixup Bla", - expectedTrimmedSubject: "fixup Bla", - expectedIsFixup: false, - }, - { - subject: "fixup! Bla", - expectedTrimmedSubject: "Bla", - expectedIsFixup: true, - }, - { - subject: "fixup! fixup! Bla", - expectedTrimmedSubject: "Bla", - expectedIsFixup: true, - }, - { - subject: "amend! squash! Bla", - expectedTrimmedSubject: "Bla", - expectedIsFixup: true, - }, - { - subject: "fixup!", - expectedTrimmedSubject: "fixup!", - expectedIsFixup: false, - }, - } - for _, s := range scenarios { - t.Run(s.subject, func(t *testing.T) { - trimmedSubject, isFixupCommit := isFixupCommit(s.subject) - assert.Equal(t, s.expectedTrimmedSubject, trimmedSubject) - assert.Equal(t, s.expectedIsFixup, isFixupCommit) - }) - } -} diff --git a/pkg/gui/controllers/main_view_controller.go b/pkg/gui/controllers/main_view_controller.go deleted file mode 100644 index fa7e6438a2e..00000000000 --- a/pkg/gui/controllers/main_view_controller.go +++ /dev/null @@ -1,118 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type MainViewController struct { - baseController - c *ControllerCommon - - context *context.MainContext - otherContext *context.MainContext -} - -var _ types.IController = &MainViewController{} - -func NewMainViewController( - c *ControllerCommon, - context *context.MainContext, - otherContext *context.MainContext, -) *MainViewController { - return &MainViewController{ - baseController: baseController{}, - c: c, - context: context, - otherContext: otherContext, - } -} - -func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.TogglePanel), - Handler: self.togglePanel, - Description: self.c.Tr.ToggleStagingView, - Tooltip: self.c.Tr.ToggleStagingViewTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Handler: self.escape, - Description: self.c.Tr.ExitFocusedMainView, - DisplayOnScreen: true, - }, - { - // overriding this because we want to read all of the task's output before we start searching - Key: opts.GetKey(opts.Config.Universal.StartSearch), - Handler: self.openSearch, - Description: self.c.Tr.StartSearch, - Tag: "navigation", - }, - } -} - -func (self *MainViewController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: self.context.GetViewName(), - Key: gocui.MouseLeft, - Handler: self.onClickInAlreadyFocusedView, - FocusedView: self.context.GetViewName(), - }, - { - ViewName: self.context.GetViewName(), - Key: gocui.MouseLeft, - Handler: self.onClickInOtherViewOfMainViewPair, - FocusedView: self.otherContext.GetViewName(), - }, - } -} - -func (self *MainViewController) Context() types.Context { - return self.context -} - -func (self *MainViewController) togglePanel() error { - if self.otherContext.GetView().Visible { - self.c.Context().Push(self.otherContext, types.OnFocusOpts{}) - } - - return nil -} - -func (self *MainViewController) escape() error { - self.c.Context().Pop() - return nil -} - -func (self *MainViewController) onClickInAlreadyFocusedView(opts gocui.ViewMouseBindingOpts) error { - sidePanelContext := self.c.Context().NextInStack(self.context) - if sidePanelContext != nil && sidePanelContext.GetOnClickFocusedMainView() != nil { - return sidePanelContext.GetOnClickFocusedMainView()(self.context.GetViewName(), opts.Y) - } - return nil -} - -func (self *MainViewController) onClickInOtherViewOfMainViewPair(opts gocui.ViewMouseBindingOpts) error { - self.c.Context().Push(self.context, types.OnFocusOpts{ - ClickedWindowName: self.context.GetWindowName(), - ClickedViewLineIdx: opts.Y, - }) - - return nil -} - -func (self *MainViewController) openSearch() error { - if manager := self.c.GetViewBufferManagerForView(self.context.GetView()); manager != nil { - manager.ReadToEnd(func() { - self.c.OnUIThread(func() error { - return self.c.Helpers().Search.OpenSearchPrompt(self.context) - }) - }) - } - - return nil -} diff --git a/pkg/gui/controllers/menu_controller.go b/pkg/gui/controllers/menu_controller.go deleted file mode 100644 index 0465308df9a..00000000000 --- a/pkg/gui/controllers/menu_controller.go +++ /dev/null @@ -1,87 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type MenuController struct { - baseController - *ListControllerTrait[*types.MenuItem] - c *ControllerCommon -} - -var _ types.IController = &MenuController{} - -func NewMenuController( - c *ControllerCommon, -) *MenuController { - return &MenuController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().Menu, - c.Contexts().Menu.GetSelected, - c.Contexts().Menu.GetSelectedItems, - ), - c: c, - } -} - -// NOTE: if you add a new keybinding here, you'll also need to add it to -// `reservedKeys` in `pkg/gui/context/menu_context.go` -func (self *MenuController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.withItem(self.press), - GetDisabledReason: self.require(self.singleItemSelected()), - }, - { - Key: opts.GetKey(opts.Config.Universal.ConfirmMenu), - Handler: self.withItem(self.press), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Execute, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Handler: self.close, - Description: self.c.Tr.CloseCancel, - DisplayOnScreen: true, - }, - } - - return bindings -} - -func (self *MenuController) GetOnClick() func() error { - return self.withItemGraceful(self.press) -} - -func (self *MenuController) GetOnFocus() func(types.OnFocusOpts) { - return func(types.OnFocusOpts) { - selectedMenuItem := self.context().GetSelected() - if selectedMenuItem != nil { - self.c.Views().Tooltip.SetContent(self.c.Helpers().Confirmation.TooltipForMenuItem(selectedMenuItem)) - } - } -} - -func (self *MenuController) press(selectedItem *types.MenuItem) error { - return self.context().OnMenuPress(selectedItem) -} - -func (self *MenuController) close() error { - if self.context().IsFiltering() { - self.c.Helpers().Search.Cancel() - return nil - } - - self.c.Context().Pop() - return nil -} - -func (self *MenuController) context() *context.MenuContext { - return self.c.Contexts().Menu -} diff --git a/pkg/gui/controllers/merge_conflicts_controller.go b/pkg/gui/controllers/merge_conflicts_controller.go deleted file mode 100644 index dc358bb0318..00000000000 --- a/pkg/gui/controllers/merge_conflicts_controller.go +++ /dev/null @@ -1,351 +0,0 @@ -package controllers - -import ( - "os" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type MergeConflictsController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &MergeConflictsController{} - -func NewMergeConflictsController( - c *ControllerCommon, -) *MergeConflictsController { - return &MergeConflictsController{ - baseController: baseController{}, - c: c, - } -} - -func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.withRenderAndFocus(self.HandlePickHunk), - Description: self.c.Tr.PickHunk, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Main.PickBothHunks), - Handler: self.withRenderAndFocus(self.HandlePickAllHunks), - Description: self.c.Tr.PickAllHunks, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.PrevItem), - Handler: self.withRenderAndFocus(self.PrevConflictHunk), - Description: self.c.Tr.SelectPrevHunk, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.NextItem), - Handler: self.withRenderAndFocus(self.NextConflictHunk), - Description: self.c.Tr.SelectNextHunk, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.PrevBlock), - Handler: self.withRenderAndFocus(self.PrevConflict), - Description: self.c.Tr.PrevConflict, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.NextBlock), - Handler: self.withRenderAndFocus(self.NextConflict), - Description: self.c.Tr.NextConflict, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Undo), - Handler: self.withRenderAndFocus(self.HandleUndo), - Description: self.c.Tr.Undo, - Tooltip: self.c.Tr.UndoMergeResolveTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Edit), - Handler: self.HandleEditFile, - Description: self.c.Tr.EditFile, - Tooltip: self.c.Tr.EditFileTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.OpenFile), - Handler: self.HandleOpenFile, - Description: self.c.Tr.OpenFile, - Tooltip: self.c.Tr.OpenFileTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt), - Handler: self.withRenderAndFocus(self.PrevConflict), - }, - { - Key: opts.GetKey(opts.Config.Universal.NextBlockAlt), - Handler: self.withRenderAndFocus(self.NextConflict), - }, - { - Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), - Handler: self.withRenderAndFocus(self.PrevConflictHunk), - }, - { - Key: opts.GetKey(opts.Config.Universal.NextItemAlt), - Handler: self.withRenderAndFocus(self.NextConflictHunk), - }, - { - Key: opts.GetKey(opts.Config.Universal.ScrollLeft), - Handler: self.withRenderAndFocus(self.HandleScrollLeft), - Description: self.c.Tr.ScrollLeft, - Tag: "navigation", - }, - { - Key: opts.GetKey(opts.Config.Universal.ScrollRight), - Handler: self.withRenderAndFocus(self.HandleScrollRight), - Description: self.c.Tr.ScrollRight, - Tag: "navigation", - }, - { - Key: opts.GetKey(opts.Config.Files.OpenMergeOptions), - Handler: self.openMergeConflictMenu, - Description: self.c.Tr.ViewMergeConflictOptions, - Tooltip: self.c.Tr.ViewMergeConflictOptionsTooltip, - OpensMenu: true, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Handler: self.Escape, - Description: self.c.Tr.ReturnToFilesPanel, - }, - } - - return bindings -} - -func (self *MergeConflictsController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: self.context().GetViewName(), - Key: gocui.MouseWheelUp, - Handler: func(gocui.ViewMouseBindingOpts) error { - return self.HandleScrollUp() - }, - }, - { - ViewName: self.context().GetViewName(), - Key: gocui.MouseWheelDown, - Handler: func(gocui.ViewMouseBindingOpts) error { - return self.HandleScrollDown() - }, - }, - } -} - -func (self *MergeConflictsController) GetOnFocus() func(types.OnFocusOpts) { - return func(types.OnFocusOpts) { - self.c.Views().MergeConflicts.Wrap = false - - self.c.Helpers().MergeConflicts.Render() - - self.context().SetSelectedLineRange() - } -} - -func (self *MergeConflictsController) GetOnFocusLost() func(types.OnFocusLostOpts) { - return func(types.OnFocusLostOpts) { - self.context().SetUserScrolling(false) - self.context().GetState().ResetConflictSelection() - self.c.Views().MergeConflicts.Wrap = true - } -} - -func (self *MergeConflictsController) HandleScrollUp() error { - self.context().SetUserScrolling(true) - self.context().GetViewTrait().ScrollUp(self.c.UserConfig().Gui.ScrollHeight) - - return nil -} - -func (self *MergeConflictsController) HandleScrollDown() error { - self.context().SetUserScrolling(true) - self.context().GetViewTrait().ScrollDown(self.c.UserConfig().Gui.ScrollHeight) - - return nil -} - -func (self *MergeConflictsController) Context() types.Context { - return self.context() -} - -func (self *MergeConflictsController) context() *context.MergeConflictsContext { - return self.c.Contexts().MergeConflicts -} - -func (self *MergeConflictsController) Escape() error { - self.c.Context().Pop() - return nil -} - -func (self *MergeConflictsController) HandleEditFile() error { - lineNumber := self.context().GetState().GetSelectedLine() - return self.c.Helpers().Files.EditFileAtLine(self.context().GetState().GetPath(), lineNumber) -} - -func (self *MergeConflictsController) HandleOpenFile() error { - return self.c.Helpers().Files.OpenFile(self.context().GetState().GetPath()) -} - -func (self *MergeConflictsController) HandleScrollLeft() error { - self.context().GetViewTrait().ScrollLeft() - - return nil -} - -func (self *MergeConflictsController) HandleScrollRight() error { - self.context().GetViewTrait().ScrollRight() - - return nil -} - -func (self *MergeConflictsController) HandleUndo() error { - state := self.context().GetState() - - ok := state.Undo() - if !ok { - return nil - } - - self.c.LogAction("Restoring file to previous state") - self.c.LogCommand(self.c.Tr.Log.HandleUndo, false) - if err := os.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil { - return err - } - - return nil -} - -func (self *MergeConflictsController) PrevConflictHunk() error { - self.context().SetUserScrolling(false) - self.context().GetState().SelectPrevConflictHunk() - - return nil -} - -func (self *MergeConflictsController) NextConflictHunk() error { - self.context().SetUserScrolling(false) - self.context().GetState().SelectNextConflictHunk() - - return nil -} - -func (self *MergeConflictsController) NextConflict() error { - self.context().SetUserScrolling(false) - self.context().GetState().SelectNextConflict() - - return nil -} - -func (self *MergeConflictsController) PrevConflict() error { - self.context().SetUserScrolling(false) - self.context().GetState().SelectPrevConflict() - - return nil -} - -func (self *MergeConflictsController) HandlePickHunk() error { - return self.pickSelection(self.context().GetState().Selection()) -} - -func (self *MergeConflictsController) HandlePickAllHunks() error { - return self.pickSelection(mergeconflicts.ALL) -} - -func (self *MergeConflictsController) pickSelection(selection mergeconflicts.Selection) error { - ok, err := self.resolveConflict(selection) - if err != nil { - return err - } - - if !ok { - return nil - } - - if self.context().GetState().AllConflictsResolved() { - self.onLastConflictResolved() - } - - return nil -} - -func (self *MergeConflictsController) resolveConflict(selection mergeconflicts.Selection) (bool, error) { - self.context().SetUserScrolling(false) - - state := self.context().GetState() - - ok, content, err := state.ContentAfterConflictResolve(selection) - if err != nil { - return false, err - } - - if !ok { - return false, nil - } - - var logStr string - switch selection { - case mergeconflicts.TOP: - logStr = "Picking top hunk" - case mergeconflicts.MIDDLE: - logStr = "Picking middle hunk" - case mergeconflicts.BOTTOM: - logStr = "Picking bottom hunk" - case mergeconflicts.ALL: - logStr = "Picking all hunks" - } - self.c.LogAction("Resolve merge conflict") - self.c.LogCommand(logStr, false) - state.PushContent(content) - return true, os.WriteFile(state.GetPath(), []byte(content), 0o644) -} - -func (self *MergeConflictsController) onLastConflictResolved() { - // as part of refreshing files, we handle the situation where a file has had - // its merge conflicts resolved. - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}}) -} - -func (self *MergeConflictsController) openMergeConflictMenu() error { - filepath := self.context().GetState().GetPath() - return self.c.Helpers().WorkingTree.CreateMergeConflictMenu([]string{filepath}) -} - -func (self *MergeConflictsController) withRenderAndFocus(f func() error) func() error { - return self.withLock(func() error { - if err := f(); err != nil { - return err - } - - self.context().RenderAndFocus() - return nil - }) -} - -func (self *MergeConflictsController) withLock(f func() error) func() error { - return func() error { - self.context().GetMutex().Lock() - defer self.context().GetMutex().Unlock() - - if self.context().GetState() == nil { - return nil - } - - return f() - } -} diff --git a/pkg/gui/controllers/options_menu_action.go b/pkg/gui/controllers/options_menu_action.go deleted file mode 100644 index 28819caba4e..00000000000 --- a/pkg/gui/controllers/options_menu_action.go +++ /dev/null @@ -1,87 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type OptionsMenuAction struct { - c *ControllerCommon -} - -func (self *OptionsMenuAction) Call() error { - ctx := self.c.Context().Current() - local, global, navigation := self.getBindings(ctx) - - menuItems := []*types.MenuItem{} - - appendBindings := func(bindings []*types.Binding, section *types.MenuSection) { - menuItems = append(menuItems, - lo.Map(bindings, func(binding *types.Binding, _ int) *types.MenuItem { - var disabledReason *types.DisabledReason - if binding.GetDisabledReason != nil { - disabledReason = binding.GetDisabledReason() - } - return &types.MenuItem{ - OpensMenu: binding.OpensMenu, - Label: binding.GetDescription(), - OnPress: func() error { - if binding.Handler == nil { - return nil - } - - return self.c.IGuiCommon.CallKeybindingHandler(binding) - }, - Key: binding.Key, - Tooltip: binding.Tooltip, - DisabledReason: disabledReason, - Section: section, - } - })...) - } - - appendBindings(local, &types.MenuSection{Title: self.c.Tr.KeybindingsMenuSectionLocal, Column: 1}) - appendBindings(global, &types.MenuSection{Title: self.c.Tr.KeybindingsMenuSectionGlobal, Column: 1}) - appendBindings(navigation, &types.MenuSection{Title: self.c.Tr.KeybindingsMenuSectionNavigation, Column: 1}) - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.Keybindings, - Items: menuItems, - HideCancel: true, - ColumnAlignment: []utils.Alignment{utils.AlignRight, utils.AlignLeft}, - AllowFilteringKeybindings: true, - KeepConfirmKeybindings: true, - }) -} - -// Returns three slices of bindings: local, global, and navigation -func (self *OptionsMenuAction) getBindings(context types.Context) ([]*types.Binding, []*types.Binding, []*types.Binding) { - var bindingsGlobal, bindingsPanel, bindingsNavigation []*types.Binding - - bindings, _ := self.c.GetInitialKeybindingsWithCustomCommands() - - for _, binding := range bindings { - if binding.GetDescription() != "" { - if binding.ViewName == "" || binding.Tag == "global" { - bindingsGlobal = append(bindingsGlobal, binding) - } else if binding.ViewName == context.GetViewName() { - if binding.Tag == "navigation" { - bindingsNavigation = append(bindingsNavigation, binding) - } else { - bindingsPanel = append(bindingsPanel, binding) - } - } - } - } - - return uniqueBindings(bindingsPanel), uniqueBindings(bindingsGlobal), uniqueBindings(bindingsNavigation) -} - -// We shouldn't really need to do this. We should define alternative keys for the same -// handler in the keybinding struct. -func uniqueBindings(bindings []*types.Binding) []*types.Binding { - return lo.UniqBy(bindings, func(binding *types.Binding) string { - return binding.GetDescription() - }) -} diff --git a/pkg/gui/controllers/patch_building_controller.go b/pkg/gui/controllers/patch_building_controller.go deleted file mode 100644 index c4f8390f6e8..00000000000 --- a/pkg/gui/controllers/patch_building_controller.go +++ /dev/null @@ -1,198 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type PatchBuildingController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &PatchBuildingController{} - -func NewPatchBuildingController( - c *ControllerCommon, -) *PatchBuildingController { - return &PatchBuildingController{ - baseController: baseController{}, - c: c, - } -} - -func (self *PatchBuildingController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.OpenFile), - Handler: self.OpenFile, - Description: self.c.Tr.OpenFile, - Tooltip: self.c.Tr.OpenFileTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Edit), - Handler: self.EditFile, - Description: self.c.Tr.EditFile, - Tooltip: self.c.Tr.EditFileTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.ToggleSelectionAndRefresh, - Description: self.c.Tr.ToggleSelectionForPatch, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Handler: self.Escape, - Description: self.c.Tr.ExitCustomPatchBuilder, - DescriptionFunc: self.EscapeDescription, - DisplayOnScreen: true, - }, - } -} - -func (self *PatchBuildingController) Context() types.Context { - return self.c.Contexts().CustomPatchBuilder -} - -func (self *PatchBuildingController) context() types.IPatchExplorerContext { - return self.c.Contexts().CustomPatchBuilder -} - -func (self *PatchBuildingController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{} -} - -func (self *PatchBuildingController) GetOnFocus() func(types.OnFocusOpts) { - return func(opts types.OnFocusOpts) { - // no need to change wrap on the secondary view because it can't be interacted with - self.c.Views().PatchBuilding.Wrap = self.c.UserConfig().Gui.WrapLinesInStagingView - - self.c.Helpers().PatchBuilding.RefreshPatchBuildingPanel(opts) - } -} - -func (self *PatchBuildingController) GetOnFocusLost() func(types.OnFocusLostOpts) { - return func(opts types.OnFocusLostOpts) { - self.context().SetState(nil) - - self.c.Views().PatchBuilding.Wrap = true - - if self.c.Git().Patch.PatchBuilder.IsEmpty() { - self.c.Git().Patch.PatchBuilder.Reset() - } - } -} - -func (self *PatchBuildingController) OpenFile() error { - self.context().GetMutex().Lock() - defer self.context().GetMutex().Unlock() - - path := self.c.Contexts().CommitFiles.GetSelectedPath() - - if path == "" { - return nil - } - - return self.c.Helpers().Files.OpenFile(path) -} - -func (self *PatchBuildingController) EditFile() error { - self.context().GetMutex().Lock() - defer self.context().GetMutex().Unlock() - - path := self.c.Contexts().CommitFiles.GetSelectedPath() - - if path == "" { - return nil - } - - lineNumber := self.context().GetState().CurrentLineNumber() - lineNumber = self.c.Helpers().Diff.AdjustLineNumber(path, lineNumber, self.context().GetViewName()) - return self.c.Helpers().Files.EditFileAtLine(path, lineNumber) -} - -func (self *PatchBuildingController) ToggleSelectionAndRefresh() error { - if err := self.toggleSelection(); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{ - Scope: []types.RefreshableView{types.PATCH_BUILDING, types.COMMIT_FILES}, - }) - return nil -} - -func (self *PatchBuildingController) toggleSelection() error { - self.context().GetMutex().Lock() - defer self.context().GetMutex().Unlock() - - filename := self.c.Contexts().CommitFiles.GetSelectedPath() - if filename == "" { - return nil - } - - state := self.context().GetState() - - // Get added/deleted lines in the selected patch range - lineIndicesToToggle := state.LineIndicesOfAddedOrDeletedLinesInSelectedPatchRange() - if len(lineIndicesToToggle) == 0 { - // Only context lines or header lines selected, so nothing to do - return nil - } - - includedLineIndices, err := self.c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename) - if err != nil { - return err - } - - toggleFunc := self.c.Git().Patch.PatchBuilder.AddFileLineRange - firstSelectedChangeLineIsStaged := lo.Contains(includedLineIndices, lineIndicesToToggle[0]) - if firstSelectedChangeLineIsStaged { - toggleFunc = self.c.Git().Patch.PatchBuilder.RemoveFileLineRange - } - - // add range of lines to those set for the file - if err := toggleFunc(filename, lineIndicesToToggle); err != nil { - // might actually want to return an error here - self.c.Log.Error(err) - } - - if state.SelectingRange() { - state.SetLineSelectMode() - } - - state.SelectNextStageableLineOfSameIncludedState(self.context().GetIncludedLineIndices(), firstSelectedChangeLineIsStaged) - - return nil -} - -func (self *PatchBuildingController) Escape() error { - context := self.c.Contexts().CustomPatchBuilder - state := context.GetState() - - if state.SelectingRange() || state.SelectingHunkEnabledByUser() { - state.SetLineSelectMode() - self.c.PostRefreshUpdate(context) - return nil - } - - self.c.Helpers().PatchBuilding.Escape() - return nil -} - -func (self *PatchBuildingController) EscapeDescription() string { - context := self.c.Contexts().CustomPatchBuilder - if state := context.GetState(); state != nil { - if state.SelectingRange() { - return self.c.Tr.DismissRangeSelect - } - - if state.SelectingHunkEnabledByUser() { - return self.c.Tr.SelectLineByLine - } - } - - return self.c.Tr.ExitCustomPatchBuilder -} diff --git a/pkg/gui/controllers/patch_explorer_controller.go b/pkg/gui/controllers/patch_explorer_controller.go deleted file mode 100644 index fdaafec5d6e..00000000000 --- a/pkg/gui/controllers/patch_explorer_controller.go +++ /dev/null @@ -1,376 +0,0 @@ -package controllers - -import ( - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type PatchExplorerControllerFactory struct { - c *ControllerCommon -} - -func NewPatchExplorerControllerFactory(c *ControllerCommon) *PatchExplorerControllerFactory { - return &PatchExplorerControllerFactory{ - c: c, - } -} - -func (self *PatchExplorerControllerFactory) Create(context types.IPatchExplorerContext) *PatchExplorerController { - return &PatchExplorerController{ - baseController: baseController{}, - c: self.c, - context: context, - } -} - -type PatchExplorerController struct { - baseController - c *ControllerCommon - - context types.IPatchExplorerContext -} - -func (self *PatchExplorerController) Context() types.Context { - return self.context -} - -func (self *PatchExplorerController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), - Handler: self.withRenderAndFocus(self.HandlePrevLine), - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.PrevItem), - Handler: self.withRenderAndFocus(self.HandlePrevLine), - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.NextItemAlt), - Handler: self.withRenderAndFocus(self.HandleNextLine), - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.NextItem), - Handler: self.withRenderAndFocus(self.HandleNextLine), - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.RangeSelectUp), - Handler: self.withRenderAndFocus(self.HandlePrevLineRange), - Description: self.c.Tr.RangeSelectUp, - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.RangeSelectDown), - Handler: self.withRenderAndFocus(self.HandleNextLineRange), - Description: self.c.Tr.RangeSelectDown, - }, - { - Key: opts.GetKey(opts.Config.Universal.PrevBlock), - Handler: self.withRenderAndFocus(self.HandlePrevHunk), - Description: self.c.Tr.PrevHunk, - }, - { - Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt), - Handler: self.withRenderAndFocus(self.HandlePrevHunk), - }, - { - Key: opts.GetKey(opts.Config.Universal.NextBlock), - Handler: self.withRenderAndFocus(self.HandleNextHunk), - Description: self.c.Tr.NextHunk, - }, - { - Key: opts.GetKey(opts.Config.Universal.NextBlockAlt), - Handler: self.withRenderAndFocus(self.HandleNextHunk), - }, - { - Key: opts.GetKey(opts.Config.Universal.ToggleRangeSelect), - Handler: self.withRenderAndFocus(self.HandleToggleSelectRange), - Description: self.c.Tr.ToggleRangeSelect, - }, - { - Key: opts.GetKey(opts.Config.Main.ToggleSelectHunk), - Handler: self.withRenderAndFocus(self.HandleToggleSelectHunk), - Description: self.c.Tr.ToggleSelectHunk, - DescriptionFunc: func() string { - if state := self.context.GetState(); state != nil && state.SelectingHunk() { - return self.c.Tr.SelectLineByLine - } - return self.c.Tr.SelectHunk - }, - Tooltip: self.c.Tr.ToggleSelectHunkTooltip, - DisplayOnScreen: true, - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.PrevPage), - Handler: self.withRenderAndFocus(self.HandlePrevPage), - Description: self.c.Tr.PrevPage, - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.NextPage), - Handler: self.withRenderAndFocus(self.HandleNextPage), - Description: self.c.Tr.NextPage, - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.GotoTop), - Handler: self.withRenderAndFocus(self.HandleGotoTop), - Description: self.c.Tr.GotoTop, - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.GotoBottom), - Description: self.c.Tr.GotoBottom, - Handler: self.withRenderAndFocus(self.HandleGotoBottom), - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.GotoTopAlt), - Handler: self.withRenderAndFocus(self.HandleGotoTop), - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.GotoBottomAlt), - Handler: self.withRenderAndFocus(self.HandleGotoBottom), - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.ScrollLeft), - Handler: self.withRenderAndFocus(self.HandleScrollLeft), - }, - { - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.ScrollRight), - Handler: self.withRenderAndFocus(self.HandleScrollRight), - }, - { - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: self.withLock(self.CopySelectedToClipboard), - Description: self.c.Tr.CopySelectedTextToClipboard, - }, - } -} - -func (self *PatchExplorerController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: self.context.GetViewName(), - Key: gocui.MouseLeft, - Handler: func(opts gocui.ViewMouseBindingOpts) error { - if self.isFocused() { - return self.withRenderAndFocus(self.HandleMouseDown)() - } - - self.c.Context().Push(self.context, types.OnFocusOpts{ - ClickedWindowName: self.context.GetWindowName(), - ClickedViewLineIdx: opts.Y, - }) - - return nil - }, - }, - { - ViewName: self.context.GetViewName(), - Key: gocui.MouseLeft, - Modifier: gocui.ModMotion, - Handler: func(gocui.ViewMouseBindingOpts) error { - return self.withRenderAndFocus(self.HandleMouseDrag)() - }, - }, - } -} - -func (self *PatchExplorerController) HandlePrevLine() error { - before := self.context.GetState().GetSelectedViewLineIdx() - self.context.GetState().CycleSelection(false) - after := self.context.GetState().GetSelectedViewLineIdx() - - if self.context.GetState().SelectingLine() { - checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig(), before, after) - } - - return nil -} - -func (self *PatchExplorerController) HandleNextLine() error { - before := self.context.GetState().GetSelectedViewLineIdx() - self.context.GetState().CycleSelection(true) - after := self.context.GetState().GetSelectedViewLineIdx() - - if self.context.GetState().SelectingLine() { - checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig(), before, after) - } - - return nil -} - -func (self *PatchExplorerController) HandlePrevLineRange() error { - s := self.context.GetState() - - s.CycleRange(false) - - return nil -} - -func (self *PatchExplorerController) HandleNextLineRange() error { - s := self.context.GetState() - - s.CycleRange(true) - - return nil -} - -func (self *PatchExplorerController) HandlePrevHunk() error { - self.context.GetState().SelectPreviousHunk() - - return nil -} - -func (self *PatchExplorerController) HandleNextHunk() error { - self.context.GetState().SelectNextHunk() - - return nil -} - -func (self *PatchExplorerController) HandleToggleSelectRange() error { - self.context.GetState().ToggleStickySelectRange() - - return nil -} - -func (self *PatchExplorerController) HandleToggleSelectHunk() error { - self.context.GetState().ToggleSelectHunk() - - return nil -} - -func (self *PatchExplorerController) HandleScrollLeft() error { - self.context.GetViewTrait().ScrollLeft() - - return nil -} - -func (self *PatchExplorerController) HandleScrollRight() error { - self.context.GetViewTrait().ScrollRight() - - return nil -} - -func (self *PatchExplorerController) HandlePrevPage() error { - self.context.GetState().AdjustSelectedLineIdx(-self.context.GetViewTrait().PageDelta()) - - return nil -} - -func (self *PatchExplorerController) HandleNextPage() error { - self.context.GetState().AdjustSelectedLineIdx(self.context.GetViewTrait().PageDelta()) - - return nil -} - -func (self *PatchExplorerController) HandleGotoTop() error { - self.context.GetState().SelectTop() - - return nil -} - -func (self *PatchExplorerController) HandleGotoBottom() error { - self.context.GetState().SelectBottom() - - return nil -} - -func (self *PatchExplorerController) HandleMouseDown() error { - self.context.GetState().SelectNewLineForRange(self.context.GetViewTrait().SelectedLineIdx()) - - return nil -} - -func (self *PatchExplorerController) HandleMouseDrag() error { - self.context.GetState().DragSelectLine(self.context.GetViewTrait().SelectedLineIdx()) - - return nil -} - -func (self *PatchExplorerController) CopySelectedToClipboard() error { - selected := self.context.GetState().PlainRenderSelected() - - self.c.LogAction(self.c.Tr.Actions.CopySelectedTextToClipboard) - if err := self.c.OS().CopyToClipboard(dropDiffPrefix(selected)); err != nil { - return err - } - - return nil -} - -// Removes '+' or '-' from the beginning of each line in the diff string, except -// when both '+' and '-' lines are present, or diff header lines, in which case -// the diff is returned unchanged. This is useful for copying parts of diffs to -// the clipboard in order to paste them into code. -func dropDiffPrefix(diff string) string { - lines := strings.Split(strings.TrimRight(diff, "\n"), "\n") - - const ( - PLUS int = iota - MINUS - CONTEXT - OTHER - ) - - linesByType := lo.GroupBy(lines, func(line string) int { - switch { - case strings.HasPrefix(line, "+"): - return PLUS - case strings.HasPrefix(line, "-"): - return MINUS - case strings.HasPrefix(line, " "): - return CONTEXT - } - return OTHER - }) - - hasLinesOfType := func(lineType int) bool { return len(linesByType[lineType]) > 0 } - - keepPrefix := hasLinesOfType(OTHER) || (hasLinesOfType(PLUS) && hasLinesOfType(MINUS)) - if keepPrefix { - return diff - } - - return strings.Join(lo.Map(lines, func(line string, _ int) string { return line[1:] + "\n" }), "") -} - -func (self *PatchExplorerController) isFocused() bool { - return self.c.Context().Current().GetKey() == self.context.GetKey() -} - -func (self *PatchExplorerController) withRenderAndFocus(f func() error) func() error { - return self.withLock(func() error { - if err := f(); err != nil { - return err - } - - self.context.RenderAndFocus() - return nil - }) -} - -func (self *PatchExplorerController) withLock(f func() error) func() error { - return func() error { - self.context.GetMutex().Lock() - defer self.context.GetMutex().Unlock() - - if self.context.GetState() == nil { - return nil - } - - return f() - } -} diff --git a/pkg/gui/controllers/patch_explorer_controller_test.go b/pkg/gui/controllers/patch_explorer_controller_test.go deleted file mode 100644 index 4f815d1d390..00000000000 --- a/pkg/gui/controllers/patch_explorer_controller_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package controllers - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_dropDiffPrefix(t *testing.T) { - scenarios := []struct { - name string - diff string - expectedResult string - }{ - { - name: "empty string", - diff: "", - expectedResult: "", - }, - { - name: "only added lines", - diff: `+line1 -+line2 -`, - expectedResult: `line1 -line2 -`, - }, - { - name: "added lines with context", - diff: ` line1 -+line2 -`, - expectedResult: `line1 -line2 -`, - }, - { - name: "only deleted lines", - diff: `-line1 --line2 -`, - expectedResult: `line1 -line2 -`, - }, - { - name: "deleted lines with context", - diff: `-line1 - line2 -`, - expectedResult: `line1 -line2 -`, - }, - { - name: "only context", - diff: ` line1 - line2 -`, - expectedResult: `line1 -line2 -`, - }, - { - name: "added and deleted lines", - diff: `+line1 --line2 -`, - expectedResult: `+line1 --line2 -`, - }, - { - name: "hunk header lines", - diff: `@@ -1,8 +1,11 @@ - line1 -`, - expectedResult: `@@ -1,8 +1,11 @@ - line1 -`, - }, - } - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - assert.Equal(t, s.expectedResult, dropDiffPrefix(s.diff)) - }) - } -} diff --git a/pkg/gui/controllers/prompt_controller.go b/pkg/gui/controllers/prompt_controller.go deleted file mode 100644 index 1f6953951ab..00000000000 --- a/pkg/gui/controllers/prompt_controller.go +++ /dev/null @@ -1,95 +0,0 @@ -package controllers - -import ( - "fmt" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type PromptController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &PromptController{} - -func NewPromptController( - c *ControllerCommon, -) *PromptController { - return &PromptController{ - baseController: baseController{}, - c: c, - } -} - -func (self *PromptController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: gocui.KeyEnter, - Handler: func() error { return self.context().State.OnConfirm() }, - Description: self.c.Tr.Confirm, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Handler: func() error { return self.context().State.OnClose() }, - Description: self.c.Tr.CloseCancel, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.TogglePanel), - Handler: func() error { - if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 { - self.switchToSuggestions() - } - return nil - }, - }, - } - - return bindings -} - -func (self *PromptController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: self.c.Contexts().Suggestions.GetViewName(), - FocusedView: self.c.Contexts().Prompt.GetViewName(), - Key: gocui.MouseLeft, - Handler: func(gocui.ViewMouseBindingOpts) error { - self.switchToSuggestions() - // Let it fall through to the ListController's click handler so that - // the clicked line gets selected: - return gocui.ErrKeybindingNotHandled - }, - }, - } -} - -func (self *PromptController) GetOnFocusLost() func(types.OnFocusLostOpts) { - return func(types.OnFocusLostOpts) { - self.c.Helpers().Confirmation.DeactivatePrompt() - } -} - -func (self *PromptController) Context() types.Context { - return self.context() -} - -func (self *PromptController) context() *context.PromptContext { - return self.c.Contexts().Prompt -} - -func (self *PromptController) switchToSuggestions() { - subtitle := "" - if self.c.State().GetRepoState().GetCurrentPopupOpts().HandleDeleteSuggestion != nil { - // We assume that whenever things are deletable, they - // are also editable, so we show both keybindings - subtitle = fmt.Sprintf(self.c.Tr.SuggestionsSubtitle, - self.c.UserConfig().Keybinding.Universal.Remove, self.c.UserConfig().Keybinding.Universal.Edit) - } - self.c.Views().Suggestions.Subtitle = subtitle - self.c.Context().Replace(self.c.Contexts().Suggestions) -} diff --git a/pkg/gui/controllers/quit_actions.go b/pkg/gui/controllers/quit_actions.go deleted file mode 100644 index 4713a6e23b6..00000000000 --- a/pkg/gui/controllers/quit_actions.go +++ /dev/null @@ -1,170 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type QuitActions struct { - c *ControllerCommon -} - -func (self *QuitActions) Quit() error { - self.c.State().SetRetainOriginalDir(false) - return self.quitAux() -} - -func (self *QuitActions) QuitWithoutChangingDirectory() error { - self.c.State().SetRetainOriginalDir(true) - return self.quitAux() -} - -func (self *QuitActions) quitAux() error { - if self.c.State().GetUpdating() { - return self.confirmQuitDuringUpdate() - } - - return self.c.ConfirmIf(self.c.UserConfig().ConfirmOnQuit, - types.ConfirmOpts{ - Title: "", - Prompt: self.c.Tr.ConfirmQuit, - HandleConfirm: func() error { - return gocui.ErrQuit - }, - }) -} - -func (self *QuitActions) confirmQuitDuringUpdate() error { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.ConfirmQuitDuringUpdateTitle, - Prompt: self.c.Tr.ConfirmQuitDuringUpdate, - HandleConfirm: func() error { - return gocui.ErrQuit - }, - }) - - return nil -} - -func (self *QuitActions) Escape() error { - // If you make changes to this function, be sure to update EscapeEnabled and EscapeDescription accordingly. - - currentContext := self.c.Context().Current() - - if listContext, ok := currentContext.(types.IListContext); ok { - if listContext.GetList().IsSelectingRange() { - listContext.GetList().CancelRangeSelect() - self.c.PostRefreshUpdate(listContext) - return nil - } - } - - // Cancelling searching (as opposed to filtering) is handled by gocui - if ctx, ok := currentContext.(types.IFilterableContext); ok { - if ctx.IsFiltering() { - self.c.Helpers().Search.Cancel() - return nil - } - } - - parentContext := currentContext.GetParentContext() - if parentContext != nil { - // TODO: think about whether this should be marked as a return rather than adding to the stack - self.c.Context().Push(parentContext, types.OnFocusOpts{}) - return nil - } - - for _, mode := range self.c.Helpers().Mode.Statuses() { - if mode.IsActive() { - return mode.Reset() - } - } - - repoPathStack := self.c.State().GetRepoPathStack() - if !repoPathStack.IsEmpty() { - return self.c.Helpers().Repos.DispatchSwitchToRepo(repoPathStack.Pop(), context.NO_CONTEXT) - } - - if self.c.UserConfig().QuitOnTopLevelReturn { - return self.Quit() - } - - return nil -} - -func (self *QuitActions) EscapeEnabled() bool { - currentContext := self.c.Context().Current() - - if listContext, ok := currentContext.(types.IListContext); ok { - if listContext.GetList().IsSelectingRange() { - return true - } - } - - if ctx, ok := currentContext.(types.IFilterableContext); ok { - if ctx.IsFiltering() { - return true - } - } - - parentContext := currentContext.GetParentContext() - if parentContext != nil { - return true - } - - for _, mode := range self.c.Helpers().Mode.Statuses() { - if mode.IsActive() { - return true - } - } - - repoPathStack := self.c.State().GetRepoPathStack() - if !repoPathStack.IsEmpty() { - return true - } - - if self.c.UserConfig().QuitOnTopLevelReturn { - return true - } - - return false -} - -func (self *QuitActions) EscapeDescription() string { - currentContext := self.c.Context().Current() - - if listContext, ok := currentContext.(types.IListContext); ok { - if listContext.GetList().IsSelectingRange() { - return self.c.Tr.DismissRangeSelect - } - } - - if ctx, ok := currentContext.(types.IFilterableContext); ok { - if ctx.IsFiltering() { - return self.c.Tr.ExitFilterMode - } - } - - parentContext := currentContext.GetParentContext() - if parentContext != nil { - return self.c.Tr.ExitSubview - } - - for _, mode := range self.c.Helpers().Mode.Statuses() { - if mode.IsActive() { - return mode.CancelLabel() - } - } - - repoPathStack := self.c.State().GetRepoPathStack() - if !repoPathStack.IsEmpty() { - return self.c.Tr.BackToParentRepo - } - - if self.c.UserConfig().QuitOnTopLevelReturn { - return self.c.Tr.Quit - } - - return self.c.Tr.Cancel -} diff --git a/pkg/gui/controllers/reflog_commits_controller.go b/pkg/gui/controllers/reflog_commits_controller.go deleted file mode 100644 index 2d0751a0bb8..00000000000 --- a/pkg/gui/controllers/reflog_commits_controller.go +++ /dev/null @@ -1,62 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type ReflogCommitsController struct { - baseController - *ListControllerTrait[*models.Commit] - c *ControllerCommon -} - -var _ types.IController = &ReflogCommitsController{} - -func NewReflogCommitsController( - c *ControllerCommon, -) *ReflogCommitsController { - return &ReflogCommitsController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().ReflogCommits, - c.Contexts().ReflogCommits.GetSelected, - c.Contexts().ReflogCommits.GetSelectedItems, - ), - c: c, - } -} - -func (self *ReflogCommitsController) Context() types.Context { - return self.context() -} - -func (self *ReflogCommitsController) context() *context.ReflogCommitsContext { - return self.c.Contexts().ReflogCommits -} - -func (self *ReflogCommitsController) GetOnRenderToMain() func() { - return func() { - self.c.Helpers().Diff.WithDiffModeCheck(func() { - commit := self.context().GetSelected() - var task types.UpdateTask - if commit == nil { - task = types.NewRenderStringTask("No reflog history") - } else { - cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Hash(), self.c.Helpers().Diff.FilterPathsForCommit(commit)) - - task = types.NewRunPtyTask(cmdObj.GetCmd()) - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: "Reflog Entry", - Task: task, - }, - }) - }) - } -} diff --git a/pkg/gui/controllers/remote_branches_controller.go b/pkg/gui/controllers/remote_branches_controller.go deleted file mode 100644 index 3a0350477f1..00000000000 --- a/pkg/gui/controllers/remote_branches_controller.go +++ /dev/null @@ -1,203 +0,0 @@ -package controllers - -import ( - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type RemoteBranchesController struct { - baseController - *ListControllerTrait[*models.RemoteBranch] - c *ControllerCommon -} - -var _ types.IController = &RemoteBranchesController{} - -func NewRemoteBranchesController( - c *ControllerCommon, -) *RemoteBranchesController { - return &RemoteBranchesController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().RemoteBranches, - c.Contexts().RemoteBranches.GetSelected, - c.Contexts().RemoteBranches.GetSelectedItems, - ), - c: c, - } -} - -func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.withItem(self.checkoutBranch), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Checkout, - Tooltip: self.c.Tr.RemoteBranchCheckoutTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.New), - Handler: self.withItem(self.newLocalBranch), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.NewBranch, - }, - { - Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch), - Handler: opts.Guards.OutsideFilterMode(self.withItem(self.merge)), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Merge, - Tooltip: self.c.Tr.MergeBranchTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Branches.RebaseBranch), - Handler: opts.Guards.OutsideFilterMode(self.withItem(self.rebase)), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.RebaseBranch, - Tooltip: self.c.Tr.RebaseBranchTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: self.withItems(self.delete), - GetDisabledReason: self.require(self.itemRangeSelected()), - Description: self.c.Tr.Delete, - Tooltip: self.c.Tr.DeleteRemoteBranchTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Branches.SetUpstream), - Handler: self.withItem(self.setAsUpstream), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.SetAsUpstream, - Tooltip: self.c.Tr.SetAsUpstreamTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Branches.SortOrder), - Handler: self.createSortMenu, - Description: self.c.Tr.SortOrder, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.ViewResetOptions), - Handler: self.withItem(self.createResetMenu), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.ViewResetOptions, - Tooltip: self.c.Tr.ResetTooltip, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.OpenDiffTool), - Handler: self.withItem(func(selectedBranch *models.RemoteBranch) error { - return self.c.Helpers().Diff.OpenDiffToolForRef(selectedBranch) - }), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.OpenDiffTool, - }, - } -} - -func (self *RemoteBranchesController) GetOnRenderToMain() func() { - return func() { - self.c.Helpers().Diff.WithDiffModeCheck(func() { - var task types.UpdateTask - remoteBranch := self.context().GetSelected() - if remoteBranch == nil { - task = types.NewRenderStringTask("No branches for this remote") - } else { - cmdObj := self.c.Git().Branch.GetGraphCmdObj(remoteBranch.FullRefName()) - task = types.NewRunCommandTask(cmdObj.GetCmd()) - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: "Remote Branch", - Task: task, - }, - }) - }) - } -} - -func (self *RemoteBranchesController) context() *context.RemoteBranchesContext { - return self.c.Contexts().RemoteBranches -} - -func (self *RemoteBranchesController) delete(selectedBranches []*models.RemoteBranch) error { - return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(selectedBranches, true) -} - -func (self *RemoteBranchesController) merge(selectedBranch *models.RemoteBranch) error { - return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranch.FullName()) -} - -func (self *RemoteBranchesController) rebase(selectedBranch *models.RemoteBranch) error { - return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranch.FullName()) -} - -func (self *RemoteBranchesController) createSortMenu() error { - return self.c.Helpers().Refs.CreateSortOrderMenu( - []string{"alphabetical", "date"}, - self.c.Tr.SortOrderPromptRemoteBranches, - func(sortOrder string) error { - if self.c.UserConfig().Git.RemoteBranchSortOrder != sortOrder { - self.c.UserConfig().Git.RemoteBranchSortOrder = sortOrder - self.c.Contexts().RemoteBranches.SetSelection(0) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.REMOTES}}) - } - return nil - }, - self.c.UserConfig().Git.RemoteBranchSortOrder) -} - -func (self *RemoteBranchesController) createResetMenu(selectedBranch *models.RemoteBranch) error { - return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.FullName(), selectedBranch.FullRefName()) -} - -func (self *RemoteBranchesController) setAsUpstream(selectedBranch *models.RemoteBranch) error { - checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef() - - message := utils.ResolvePlaceholderString( - self.c.Tr.SetUpstreamMessage, - map[string]string{ - "checkedOut": checkedOutBranch.Name, - "selected": selectedBranch.FullName(), - }, - ) - - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.SetUpstreamTitle, - Prompt: message, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.SetBranchUpstream) - if err := self.c.Git().Branch.SetUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}}) - return nil - }, - }) - - return nil -} - -func (self *RemoteBranchesController) newLocalBranch(selectedBranch *models.RemoteBranch) error { - // will set to the remote's branch name without the remote name - nameSuggestion := strings.SplitAfterN(selectedBranch.RefName(), "/", 2)[1] - - return self.c.Helpers().Refs.NewBranch(selectedBranch.RefName(), selectedBranch.RefName(), nameSuggestion) -} - -func (self *RemoteBranchesController) checkoutBranch(selectedBranch *models.RemoteBranch) error { - return self.c.Helpers().Refs.CheckoutRemoteBranch(selectedBranch.FullName(), selectedBranch.Name) -} diff --git a/pkg/gui/controllers/remotes_controller.go b/pkg/gui/controllers/remotes_controller.go deleted file mode 100644 index 10f15d457a1..00000000000 --- a/pkg/gui/controllers/remotes_controller.go +++ /dev/null @@ -1,259 +0,0 @@ -package controllers - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type RemotesController struct { - baseController - *ListControllerTrait[*models.Remote] - c *ControllerCommon - - setRemoteBranches func([]*models.RemoteBranch) -} - -var _ types.IController = &RemotesController{} - -func NewRemotesController( - c *ControllerCommon, - setRemoteBranches func([]*models.RemoteBranch), -) *RemotesController { - return &RemotesController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().Remotes, - c.Contexts().Remotes.GetSelected, - c.Contexts().Remotes.GetSelectedItems, - ), - c: c, - setRemoteBranches: setRemoteBranches, - } -} - -func (self *RemotesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.GoInto), - Handler: self.withItem(self.enter), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.ViewBranches, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.New), - Handler: self.add, - Description: self.c.Tr.NewRemote, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: self.withItem(self.remove), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Remove, - Tooltip: self.c.Tr.RemoveRemoteTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Edit), - Handler: self.withItem(self.edit), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Edit, - Tooltip: self.c.Tr.EditRemoteTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Branches.FetchRemote), - Handler: self.withItem(self.fetch), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Fetch, - Tooltip: self.c.Tr.FetchRemoteTooltip, - DisplayOnScreen: true, - }, - } - - return bindings -} - -func (self *RemotesController) context() *context.RemotesContext { - return self.c.Contexts().Remotes -} - -func (self *RemotesController) GetOnRenderToMain() func() { - return func() { - self.c.Helpers().Diff.WithDiffModeCheck(func() { - var task types.UpdateTask - remote := self.context().GetSelected() - if remote == nil { - task = types.NewRenderStringTask("No remotes") - } else { - task = types.NewRenderStringTask(fmt.Sprintf("%s\nUrls:\n%s", style.FgGreen.Sprint(remote.Name), strings.Join(remote.Urls, "\n"))) - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: "Remote", - Task: task, - }, - }) - }) - } -} - -func (self *RemotesController) GetOnClick() func() error { - return self.withItemGraceful(self.enter) -} - -func (self *RemotesController) enter(remote *models.Remote) error { - // naive implementation: get the branches from the remote and render them to the list, change the context - self.setRemoteBranches(remote.Branches) - - newSelectedLine := 0 - if len(remote.Branches) == 0 { - newSelectedLine = -1 - } - remoteBranchesContext := self.c.Contexts().RemoteBranches - remoteBranchesContext.SetSelection(newSelectedLine) - remoteBranchesContext.SetTitleRef(remote.Name) - remoteBranchesContext.SetParentContext(self.Context()) - remoteBranchesContext.GetView().TitlePrefix = self.Context().GetView().TitlePrefix - - self.c.PostRefreshUpdate(remoteBranchesContext) - - self.c.Context().Push(remoteBranchesContext, types.OnFocusOpts{}) - return nil -} - -func (self *RemotesController) add() error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.NewRemoteName, - HandleConfirm: func(remoteName string) error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.NewRemoteUrl, - HandleConfirm: func(remoteUrl string) error { - self.c.LogAction(self.c.Tr.Actions.AddRemote) - if err := self.c.Git().Remote.AddRemote(remoteName, remoteUrl); err != nil { - return err - } - - // Do a sync refresh of the remotes so that we can select - // the new one. Loading remotes is not expensive, so we can - // afford it. - self.c.Refresh(types.RefreshOptions{ - Scope: []types.RefreshableView{types.REMOTES}, - Mode: types.SYNC, - }) - - // Select the new remote - for idx, remote := range self.c.Model().Remotes { - if remote.Name == remoteName { - self.c.Contexts().Remotes.SetSelection(idx) - break - } - } - - // Fetch the new remote - return self.fetch(self.c.Contexts().Remotes.GetSelected()) - }, - }) - - return nil - }, - }) - - return nil -} - -func (self *RemotesController) remove(remote *models.Remote) error { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.RemoveRemote, - Prompt: self.c.Tr.RemoveRemotePrompt + " '" + remote.Name + "'?", - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.RemoveRemote) - if err := self.c.Git().Remote.RemoveRemote(remote.Name); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}}) - return nil - }, - }) - - return nil -} - -func (self *RemotesController) edit(remote *models.Remote) error { - editNameMessage := utils.ResolvePlaceholderString( - self.c.Tr.EditRemoteName, - map[string]string{ - "remoteName": remote.Name, - }, - ) - - self.c.Prompt(types.PromptOpts{ - Title: editNameMessage, - InitialContent: remote.Name, - HandleConfirm: func(updatedRemoteName string) error { - if updatedRemoteName != remote.Name { - self.c.LogAction(self.c.Tr.Actions.UpdateRemote) - if err := self.c.Git().Remote.RenameRemote(remote.Name, updatedRemoteName); err != nil { - return err - } - } - - editUrlMessage := utils.ResolvePlaceholderString( - self.c.Tr.EditRemoteUrl, - map[string]string{ - "remoteName": updatedRemoteName, - }, - ) - - urls := remote.Urls - url := "" - if len(urls) > 0 { - url = urls[0] - } - - self.c.Prompt(types.PromptOpts{ - Title: editUrlMessage, - InitialContent: url, - HandleConfirm: func(updatedRemoteUrl string) error { - self.c.LogAction(self.c.Tr.Actions.UpdateRemote) - if err := self.c.Git().Remote.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}}) - return nil - }, - }) - - return nil - }, - }) - - return nil -} - -func (self *RemotesController) fetch(remote *models.Remote) error { - return self.c.WithInlineStatus(remote, types.ItemOperationFetching, context.REMOTES_CONTEXT_KEY, func(task gocui.Task) error { - err := self.c.Git().Sync.FetchRemote(task, remote.Name) - if err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{ - Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}, - Mode: types.ASYNC, - }) - return nil - }) -} diff --git a/pkg/gui/controllers/rename_similarity_threshold_controller.go b/pkg/gui/controllers/rename_similarity_threshold_controller.go deleted file mode 100644 index 98df26127e6..00000000000 --- a/pkg/gui/controllers/rename_similarity_threshold_controller.go +++ /dev/null @@ -1,114 +0,0 @@ -package controllers - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -// This controller lets you change the similarity threshold for detecting renames. - -var CONTEXT_KEYS_SHOWING_RENAMES = []types.ContextKey{ - context.FILES_CONTEXT_KEY, - context.SUB_COMMITS_CONTEXT_KEY, - context.LOCAL_COMMITS_CONTEXT_KEY, - context.STASH_CONTEXT_KEY, - context.NORMAL_MAIN_CONTEXT_KEY, - context.NORMAL_SECONDARY_CONTEXT_KEY, -} - -type RenameSimilarityThresholdController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &RenameSimilarityThresholdController{} - -func NewRenameSimilarityThresholdController( - common *ControllerCommon, -) *RenameSimilarityThresholdController { - return &RenameSimilarityThresholdController{ - baseController: baseController{}, - c: common, - } -} - -func (self *RenameSimilarityThresholdController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.IncreaseRenameSimilarityThreshold), - Handler: self.Increase, - Description: self.c.Tr.IncreaseRenameSimilarityThreshold, - Tooltip: self.c.Tr.IncreaseRenameSimilarityThresholdTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.DecreaseRenameSimilarityThreshold), - Handler: self.Decrease, - Description: self.c.Tr.DecreaseRenameSimilarityThreshold, - Tooltip: self.c.Tr.DecreaseRenameSimilarityThresholdTooltip, - }, - } - - return bindings -} - -func (self *RenameSimilarityThresholdController) Context() types.Context { - return nil -} - -func (self *RenameSimilarityThresholdController) Increase() error { - old_size := self.c.UserConfig().Git.RenameSimilarityThreshold - - if self.isShowingRenames() && old_size < 100 { - self.c.UserConfig().Git.RenameSimilarityThreshold = min(100, old_size+5) - return self.applyChange() - } - - return nil -} - -func (self *RenameSimilarityThresholdController) Decrease() error { - old_size := self.c.UserConfig().Git.RenameSimilarityThreshold - - if self.isShowingRenames() && old_size > 5 { - self.c.UserConfig().Git.RenameSimilarityThreshold = max(5, old_size-5) - return self.applyChange() - } - - return nil -} - -func (self *RenameSimilarityThresholdController) applyChange() error { - self.c.Toast(fmt.Sprintf(self.c.Tr.RenameSimilarityThresholdChanged, self.c.UserConfig().Git.RenameSimilarityThreshold)) - - currentContext := self.currentSidePanel() - switch currentContext.GetKey() { - // we make an exception for our files context, because it actually need to refresh its state afterwards. - case context.FILES_CONTEXT_KEY: - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}) - default: - currentContext.HandleRenderToMain() - } - return nil -} - -func (self *RenameSimilarityThresholdController) isShowingRenames() bool { - return lo.Contains( - CONTEXT_KEYS_SHOWING_RENAMES, - self.currentSidePanel().GetKey(), - ) -} - -func (self *RenameSimilarityThresholdController) currentSidePanel() types.Context { - currentContext := self.c.Context().CurrentStatic() - if currentContext.GetKey() == context.NORMAL_MAIN_CONTEXT_KEY || - currentContext.GetKey() == context.NORMAL_SECONDARY_CONTEXT_KEY { - if sidePanelContext := self.c.Context().NextInStack(currentContext); sidePanelContext != nil { - return sidePanelContext - } - } - - return currentContext -} diff --git a/pkg/gui/controllers/screen_mode_actions.go b/pkg/gui/controllers/screen_mode_actions.go deleted file mode 100644 index 190aad60427..00000000000 --- a/pkg/gui/controllers/screen_mode_actions.go +++ /dev/null @@ -1,78 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type ScreenModeActions struct { - c *ControllerCommon -} - -func (self *ScreenModeActions) Next() error { - self.c.State().GetRepoState().SetScreenMode( - nextIntInCycle( - []types.ScreenMode{types.SCREEN_NORMAL, types.SCREEN_HALF, types.SCREEN_FULL}, - self.c.State().GetRepoState().GetScreenMode(), - ), - ) - - self.rerenderViewsWithScreenModeDependentContent() - return nil -} - -func (self *ScreenModeActions) Prev() error { - self.c.State().GetRepoState().SetScreenMode( - prevIntInCycle( - []types.ScreenMode{types.SCREEN_NORMAL, types.SCREEN_HALF, types.SCREEN_FULL}, - self.c.State().GetRepoState().GetScreenMode(), - ), - ) - - self.rerenderViewsWithScreenModeDependentContent() - return nil -} - -// these views need to be re-rendered when the screen mode changes. The commits view, -// for example, will show authorship information in half and full screen mode. -func (self *ScreenModeActions) rerenderViewsWithScreenModeDependentContent() { - for _, context := range self.c.Context().AllList() { - if context.NeedsRerenderOnWidthChange() == types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES { - self.rerenderView(context.GetView()) - } - } -} - -func (self *ScreenModeActions) rerenderView(view *gocui.View) { - context, ok := self.c.Helpers().View.ContextForView(view.Name()) - if !ok { - self.c.Log.Errorf("no context found for view %s", view.Name()) - return - } - - context.HandleRender() -} - -func nextIntInCycle(sl []types.ScreenMode, current types.ScreenMode) types.ScreenMode { - for i, val := range sl { - if val == current { - if i == len(sl)-1 { - return sl[0] - } - return sl[i+1] - } - } - return sl[0] -} - -func prevIntInCycle(sl []types.ScreenMode, current types.ScreenMode) types.ScreenMode { - for i, val := range sl { - if val == current { - if i > 0 { - return sl[i-1] - } - return sl[len(sl)-1] - } - } - return sl[len(sl)-1] -} diff --git a/pkg/gui/controllers/scroll_off_margin.go b/pkg/gui/controllers/scroll_off_margin.go deleted file mode 100644 index ec155158231..00000000000 --- a/pkg/gui/controllers/scroll_off_margin.go +++ /dev/null @@ -1,74 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -// To be called after pressing up-arrow; checks whether the cursor entered the -// top scroll-off margin, and so the view needs to be scrolled up one line -func checkScrollUp(view types.IViewTrait, userConfig *config.UserConfig, lineIdxBefore int, lineIdxAfter int) { - if userConfig.Gui.ScrollOffBehavior != "jump" { - viewPortStart, viewPortHeight := view.ViewPortYBounds() - - linesToScroll := calculateLinesToScrollUp( - viewPortStart, viewPortHeight, userConfig.Gui.ScrollOffMargin, lineIdxBefore, lineIdxAfter) - if linesToScroll != 0 { - view.ScrollUp(linesToScroll) - } - } -} - -// To be called after pressing down-arrow; checks whether the cursor entered the -// bottom scroll-off margin, and so the view needs to be scrolled down one line -func checkScrollDown(view types.IViewTrait, userConfig *config.UserConfig, lineIdxBefore int, lineIdxAfter int) { - if userConfig.Gui.ScrollOffBehavior != "jump" { - viewPortStart, viewPortHeight := view.ViewPortYBounds() - - linesToScroll := calculateLinesToScrollDown( - viewPortStart, viewPortHeight, userConfig.Gui.ScrollOffMargin, lineIdxBefore, lineIdxAfter) - if linesToScroll != 0 { - view.ScrollDown(linesToScroll) - } - } -} - -func calculateLinesToScrollUp(viewPortStart int, viewPortHeight int, scrollOffMargin int, lineIdxBefore int, lineIdxAfter int) int { - // Cap the margin to half the view height. This allows setting the config to - // a very large value to keep the cursor always in the middle of the screen. - // Use +.5 so that if the height is even, the top margin is one line higher - // than the bottom margin. - scrollOffMargin = min(scrollOffMargin, int((float64(viewPortHeight)+.5)/2)) - - // Scroll only if the "before" position was visible (this could be false if - // the scroll wheel was used to scroll the selected line out of view) ... - if lineIdxBefore >= viewPortStart && lineIdxBefore < viewPortStart+viewPortHeight { - marginEnd := viewPortStart + scrollOffMargin - // ... and the "after" position is within the top margin (or before it) - if lineIdxAfter < marginEnd { - return marginEnd - lineIdxAfter - } - } - - return 0 -} - -func calculateLinesToScrollDown(viewPortStart int, viewPortHeight int, scrollOffMargin int, lineIdxBefore int, lineIdxAfter int) int { - // Cap the margin to half the view height. This allows setting the config to - // a very large value to keep the cursor always in the middle of the screen. - // Use -.5 so that if the height is even, the bottom margin is one line lower - // than the top margin. - scrollOffMargin = min(scrollOffMargin, int((float64(viewPortHeight)-.5)/2)) - - // Scroll only if the "before" position was visible (this could be false if - // the scroll wheel was used to scroll the selected line out of view) ... - if lineIdxBefore >= viewPortStart && lineIdxBefore < viewPortStart+viewPortHeight { - marginStart := viewPortStart + viewPortHeight - scrollOffMargin - 1 - // ... and the "after" position is within the bottom margin (or after it) - if lineIdxAfter > marginStart { - return lineIdxAfter - marginStart - } - } - - return 0 -} diff --git a/pkg/gui/controllers/scroll_off_margin_test.go b/pkg/gui/controllers/scroll_off_margin_test.go deleted file mode 100644 index 4059aaa4848..00000000000 --- a/pkg/gui/controllers/scroll_off_margin_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package controllers - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_calculateLinesToScrollUp(t *testing.T) { - scenarios := []struct { - name string - viewPortStart int - viewPortHeight int - scrollOffMargin int - lineIdxBefore int - lineIdxAfter int - expectedLinesToScroll int - }{ - { - name: "before position is above viewport - don't scroll", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 3, - lineIdxBefore: 9, - lineIdxAfter: 8, - expectedLinesToScroll: 0, - }, - { - name: "before position is below viewport - don't scroll", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 3, - lineIdxBefore: 20, - lineIdxAfter: 19, - expectedLinesToScroll: 0, - }, - { - name: "before and after positions are outside scroll-off margin - don't scroll", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 3, - lineIdxBefore: 14, - lineIdxAfter: 13, - expectedLinesToScroll: 0, - }, - { - name: "before outside, after inside scroll-off margin - scroll by 1", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 3, - lineIdxBefore: 13, - lineIdxAfter: 12, - expectedLinesToScroll: 1, - }, - { - name: "scroll-off margin is zero - scroll by 1 at end of view", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 0, - lineIdxBefore: 10, - lineIdxAfter: 9, - expectedLinesToScroll: 1, - }, - { - name: "before inside scroll-off margin - scroll by more than 1", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 3, - lineIdxBefore: 11, - lineIdxAfter: 10, - expectedLinesToScroll: 3, - }, - { - name: "very large scroll-off margin - keep view centered (even viewport height)", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 999, - lineIdxBefore: 15, - lineIdxAfter: 14, - expectedLinesToScroll: 1, - }, - { - name: "very large scroll-off margin - keep view centered (odd viewport height)", - viewPortStart: 10, - viewPortHeight: 9, - scrollOffMargin: 999, - lineIdxBefore: 14, - lineIdxAfter: 13, - expectedLinesToScroll: 1, - }, - } - for _, scenario := range scenarios { - t.Run(scenario.name, func(t *testing.T) { - linesToScroll := calculateLinesToScrollUp(scenario.viewPortStart, scenario.viewPortHeight, scenario.scrollOffMargin, scenario.lineIdxBefore, scenario.lineIdxAfter) - assert.Equal(t, scenario.expectedLinesToScroll, linesToScroll) - }) - } -} - -func Test_calculateLinesToScrollDown(t *testing.T) { - scenarios := []struct { - name string - viewPortStart int - viewPortHeight int - scrollOffMargin int - lineIdxBefore int - lineIdxAfter int - expectedLinesToScroll int - }{ - { - name: "before position is above viewport - don't scroll", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 3, - lineIdxBefore: 9, - lineIdxAfter: 10, - expectedLinesToScroll: 0, - }, - { - name: "before position is below viewport - don't scroll", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 3, - lineIdxBefore: 20, - lineIdxAfter: 21, - expectedLinesToScroll: 0, - }, - { - name: "before and after positions are outside scroll-off margin - don't scroll", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 3, - lineIdxBefore: 15, - lineIdxAfter: 16, - expectedLinesToScroll: 0, - }, - { - name: "before outside, after inside scroll-off margin - scroll by 1", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 3, - lineIdxBefore: 16, - lineIdxAfter: 17, - expectedLinesToScroll: 1, - }, - { - name: "scroll-off margin is zero - scroll by 1 at end of view", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 0, - lineIdxBefore: 19, - lineIdxAfter: 20, - expectedLinesToScroll: 1, - }, - { - name: "before inside scroll-off margin - scroll by more than 1", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 3, - lineIdxBefore: 18, - lineIdxAfter: 19, - expectedLinesToScroll: 3, - }, - { - name: "very large scroll-off margin - keep view centered (even viewport height)", - viewPortStart: 10, - viewPortHeight: 10, - scrollOffMargin: 999, - lineIdxBefore: 15, - lineIdxAfter: 16, - expectedLinesToScroll: 1, - }, - { - name: "very large scroll-off margin - keep view centered (odd viewport height)", - viewPortStart: 10, - viewPortHeight: 9, - scrollOffMargin: 999, - lineIdxBefore: 14, - lineIdxAfter: 15, - expectedLinesToScroll: 1, - }, - } - for _, scenario := range scenarios { - t.Run(scenario.name, func(t *testing.T) { - linesToScroll := calculateLinesToScrollDown(scenario.viewPortStart, scenario.viewPortHeight, scenario.scrollOffMargin, scenario.lineIdxBefore, scenario.lineIdxAfter) - assert.Equal(t, scenario.expectedLinesToScroll, linesToScroll) - }) - } -} diff --git a/pkg/gui/controllers/search_controller.go b/pkg/gui/controllers/search_controller.go deleted file mode 100644 index 395784d105c..00000000000 --- a/pkg/gui/controllers/search_controller.go +++ /dev/null @@ -1,48 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type SearchControllerFactory struct { - c *ControllerCommon -} - -func NewSearchControllerFactory(c *ControllerCommon) *SearchControllerFactory { - return &SearchControllerFactory{ - c: c, - } -} - -func (self *SearchControllerFactory) Create(context types.ISearchableContext) *SearchController { - return &SearchController{ - baseController: baseController{}, - c: self.c, - context: context, - } -} - -type SearchController struct { - baseController - c *ControllerCommon - - context types.ISearchableContext -} - -func (self *SearchController) Context() types.Context { - return self.context -} - -func (self *SearchController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.StartSearch), - Handler: self.OpenSearchPrompt, - Description: self.c.Tr.StartSearch, - }, - } -} - -func (self *SearchController) OpenSearchPrompt() error { - return self.c.Helpers().Search.OpenSearchPrompt(self.context) -} diff --git a/pkg/gui/controllers/search_prompt_controller.go b/pkg/gui/controllers/search_prompt_controller.go deleted file mode 100644 index 9eca74c90a9..00000000000 --- a/pkg/gui/controllers/search_prompt_controller.go +++ /dev/null @@ -1,73 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type SearchPromptController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &SearchPromptController{} - -func NewSearchPromptController( - c *ControllerCommon, -) *SearchPromptController { - return &SearchPromptController{ - baseController: baseController{}, - c: c, - } -} - -func (self *SearchPromptController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Key: gocui.KeyEnter, - Modifier: gocui.ModNone, - Handler: self.confirm, - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Modifier: gocui.ModNone, - Handler: self.cancel, - }, - { - Key: opts.GetKey(opts.Config.Universal.PrevItem), - Modifier: gocui.ModNone, - Handler: self.prevHistory, - }, - { - Key: opts.GetKey(opts.Config.Universal.NextItem), - Modifier: gocui.ModNone, - Handler: self.nextHistory, - }, - } -} - -func (self *SearchPromptController) Context() types.Context { - return self.context() -} - -func (self *SearchPromptController) context() types.Context { - return self.c.Contexts().Search -} - -func (self *SearchPromptController) confirm() error { - return self.c.Helpers().Search.Confirm() -} - -func (self *SearchPromptController) cancel() error { - return self.c.Helpers().Search.CancelPrompt() -} - -func (self *SearchPromptController) prevHistory() error { - self.c.Helpers().Search.ScrollHistory(1) - return nil -} - -func (self *SearchPromptController) nextHistory() error { - self.c.Helpers().Search.ScrollHistory(-1) - return nil -} diff --git a/pkg/gui/controllers/shell_command_action.go b/pkg/gui/controllers/shell_command_action.go deleted file mode 100644 index 185f74893ea..00000000000 --- a/pkg/gui/controllers/shell_command_action.go +++ /dev/null @@ -1,73 +0,0 @@ -package controllers - -import ( - "slices" - "strings" - - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type ShellCommandAction struct { - c *ControllerCommon -} - -func (self *ShellCommandAction) Call() error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.ShellCommand, - FindSuggestionsFunc: self.GetShellCommandsHistorySuggestionsFunc(), - AllowEditSuggestion: true, - HandleConfirm: func(command string) error { - if self.shouldSaveCommand(command) { - self.c.GetAppState().ShellCommandsHistory = utils.Limit( - lo.Uniq(append([]string{command}, self.c.GetAppState().ShellCommandsHistory...)), - 1000, - ) - } - - self.c.SaveAppStateAndLogError() - - self.c.LogAction(self.c.Tr.Actions.CustomCommand) - return self.c.RunSubprocessAndRefresh( - self.c.OS().Cmd.NewShell(command, self.c.UserConfig().OS.ShellFunctionsFile), - ) - }, - HandleDeleteSuggestion: func(index int) error { - // index is the index in the _filtered_ list of suggestions, so we - // need to map it back to the full list. There's no really good way - // to do this, but fortunately we keep the items in the - // ShellCommandsHistory unique, which allows us to simply search - // for it by string. - item := self.c.Contexts().Suggestions.GetItems()[index].Value - fullIndex := lo.IndexOf(self.c.GetAppState().ShellCommandsHistory, item) - if fullIndex == -1 { - // Should never happen, but better be safe - return nil - } - - self.c.GetAppState().ShellCommandsHistory = slices.Delete( - self.c.GetAppState().ShellCommandsHistory, fullIndex, fullIndex+1) - self.c.SaveAppStateAndLogError() - self.c.Contexts().Suggestions.RefreshSuggestions() - return nil - }, - }) - - return nil -} - -func (self *ShellCommandAction) GetShellCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion { - return func(input string) []*types.Suggestion { - history := self.c.GetAppState().ShellCommandsHistory - - return helpers.FilterFunc(history, self.c.UserConfig().Gui.UseFuzzySearch())(input) - } -} - -// this mimics the shell functionality `ignorespace` -// which doesn't save a command to history if it starts with a space -func (self *ShellCommandAction) shouldSaveCommand(command string) bool { - return !strings.HasPrefix(command, " ") -} diff --git a/pkg/gui/controllers/side_window_controller.go b/pkg/gui/controllers/side_window_controller.go deleted file mode 100644 index 2cd421e0ecf..00000000000 --- a/pkg/gui/controllers/side_window_controller.go +++ /dev/null @@ -1,98 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type SideWindowControllerFactory struct { - c *ControllerCommon -} - -func NewSideWindowControllerFactory(c *ControllerCommon) *SideWindowControllerFactory { - return &SideWindowControllerFactory{c: c} -} - -func (self *SideWindowControllerFactory) Create(context types.Context) types.IController { - return NewSideWindowController(self.c, context) -} - -type SideWindowController struct { - baseController - c *ControllerCommon - context types.Context -} - -func NewSideWindowController( - c *ControllerCommon, - context types.Context, -) *SideWindowController { - return &SideWindowController{ - baseController: baseController{}, - c: c, - context: context, - } -} - -func (self *SideWindowController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - {Key: opts.GetKey(opts.Config.Universal.PrevBlock), Modifier: gocui.ModNone, Handler: self.previousSideWindow}, - {Key: opts.GetKey(opts.Config.Universal.NextBlock), Modifier: gocui.ModNone, Handler: self.nextSideWindow}, - {Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt), Modifier: gocui.ModNone, Handler: self.previousSideWindow}, - {Key: opts.GetKey(opts.Config.Universal.NextBlockAlt), Modifier: gocui.ModNone, Handler: self.nextSideWindow}, - {Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt2), Modifier: gocui.ModNone, Handler: self.previousSideWindow}, - {Key: opts.GetKey(opts.Config.Universal.NextBlockAlt2), Modifier: gocui.ModNone, Handler: self.nextSideWindow}, - } -} - -func (self *SideWindowController) Context() types.Context { - return nil -} - -func (self *SideWindowController) previousSideWindow() error { - windows := self.c.Helpers().Window.SideWindows() - currentWindow := self.c.Helpers().Window.CurrentWindow() - var newWindow string - if currentWindow == "" || currentWindow == windows[0] { - newWindow = windows[len(windows)-1] - } else { - for i := range windows { - if currentWindow == windows[i] { - newWindow = windows[i-1] - break - } - if i == len(windows)-1 { - return nil - } - } - } - - context := self.c.Helpers().Window.GetContextForWindow(newWindow) - - self.c.Context().Push(context, types.OnFocusOpts{}) - return nil -} - -func (self *SideWindowController) nextSideWindow() error { - windows := self.c.Helpers().Window.SideWindows() - currentWindow := self.c.Helpers().Window.CurrentWindow() - var newWindow string - if currentWindow == "" || currentWindow == windows[len(windows)-1] { - newWindow = windows[0] - } else { - for i := range windows { - if currentWindow == windows[i] { - newWindow = windows[i+1] - break - } - if i == len(windows)-1 { - return nil - } - } - } - - context := self.c.Helpers().Window.GetContextForWindow(newWindow) - - self.c.Context().Push(context, types.OnFocusOpts{}) - return nil -} diff --git a/pkg/gui/controllers/snake_controller.go b/pkg/gui/controllers/snake_controller.go deleted file mode 100644 index a2a2030b7c6..00000000000 --- a/pkg/gui/controllers/snake_controller.go +++ /dev/null @@ -1,78 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/snake" -) - -type SnakeController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &SnakeController{} - -func NewSnakeController( - c *ControllerCommon, -) *SnakeController { - return &SnakeController{ - baseController: baseController{}, - c: c, - } -} - -func (self *SnakeController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.NextItem), - Handler: self.SetDirection(snake.Down), - }, - { - Key: opts.GetKey(opts.Config.Universal.PrevItem), - Handler: self.SetDirection(snake.Up), - }, - { - Key: opts.GetKey(opts.Config.Universal.PrevBlock), - Handler: self.SetDirection(snake.Left), - }, - { - Key: opts.GetKey(opts.Config.Universal.NextBlock), - Handler: self.SetDirection(snake.Right), - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Handler: self.Escape, - }, - } - - return bindings -} - -func (self *SnakeController) Context() types.Context { - return self.c.Contexts().Snake -} - -func (self *SnakeController) GetOnFocus() func(types.OnFocusOpts) { - return func(types.OnFocusOpts) { - self.c.Helpers().Snake.StartGame() - } -} - -func (self *SnakeController) GetOnFocusLost() func(types.OnFocusLostOpts) { - return func(types.OnFocusLostOpts) { - self.c.Helpers().Snake.ExitGame() - self.c.Helpers().Window.MoveToTopOfWindow(self.c.Contexts().Submodules) - } -} - -func (self *SnakeController) SetDirection(direction snake.Direction) func() error { - return func() error { - self.c.Helpers().Snake.SetDirection(direction) - return nil - } -} - -func (self *SnakeController) Escape() error { - self.c.Context().Push(self.c.Contexts().Submodules, types.OnFocusOpts{}) - return nil -} diff --git a/pkg/gui/controllers/staging_controller.go b/pkg/gui/controllers/staging_controller.go deleted file mode 100644 index f667dd212c3..00000000000 --- a/pkg/gui/controllers/staging_controller.go +++ /dev/null @@ -1,354 +0,0 @@ -package controllers - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type StagingController struct { - baseController - c *ControllerCommon - - context types.IPatchExplorerContext - otherContext types.IPatchExplorerContext - - // if true, we're dealing with the secondary context i.e. dealing with staged file changes - staged bool -} - -var _ types.IController = &StagingController{} - -func NewStagingController( - c *ControllerCommon, - context types.IPatchExplorerContext, - otherContext types.IPatchExplorerContext, - staged bool, -) *StagingController { - return &StagingController{ - baseController: baseController{}, - c: c, - context: context, - otherContext: otherContext, - staged: staged, - } -} - -func (self *StagingController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.ToggleStaged, - Description: self.c.Tr.Stage, - Tooltip: self.c.Tr.StageSelectionTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: self.DiscardSelection, - Description: self.c.Tr.DiscardSelection, - Tooltip: self.c.Tr.DiscardSelectionTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.OpenFile), - Handler: self.OpenFile, - Description: self.c.Tr.OpenFile, - Tooltip: self.c.Tr.OpenFileTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Edit), - Handler: self.EditFile, - Description: self.c.Tr.EditFile, - Tooltip: self.c.Tr.EditFileTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Handler: self.Escape, - Description: self.c.Tr.ReturnToFilesPanel, - DescriptionFunc: self.EscapeDescription, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.TogglePanel), - Handler: self.TogglePanel, - Description: self.c.Tr.ToggleStagingView, - Tooltip: self.c.Tr.ToggleStagingViewTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Main.EditSelectHunk), - Handler: self.EditHunkAndRefresh, - Description: self.c.Tr.EditHunk, - Tooltip: self.c.Tr.EditHunkTooltip, - }, - { - Key: opts.GetKey(opts.Config.Files.CommitChanges), - Handler: self.c.Helpers().WorkingTree.HandleCommitPress, - Description: self.c.Tr.Commit, - Tooltip: self.c.Tr.CommitTooltip, - }, - { - Key: opts.GetKey(opts.Config.Files.CommitChangesWithoutHook), - Handler: self.c.Helpers().WorkingTree.HandleWIPCommitPress, - Description: self.c.Tr.CommitChangesWithoutHook, - }, - { - Key: opts.GetKey(opts.Config.Files.CommitChangesWithEditor), - Handler: self.c.Helpers().WorkingTree.HandleCommitEditorPress, - Description: self.c.Tr.CommitChangesWithEditor, - }, - { - Key: opts.GetKey(opts.Config.Files.FindBaseCommitForFixup), - Handler: self.c.Helpers().FixupHelper.HandleFindBaseCommitForFixupPress, - Description: self.c.Tr.FindBaseCommitForFixup, - Tooltip: self.c.Tr.FindBaseCommitForFixupTooltip, - }, - } -} - -func (self *StagingController) Context() types.Context { - return self.context -} - -func (self *StagingController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{} -} - -func (self *StagingController) GetOnFocus() func(types.OnFocusOpts) { - return func(opts types.OnFocusOpts) { - wrap := self.c.UserConfig().Gui.WrapLinesInStagingView - self.c.Views().Staging.Wrap = wrap - self.c.Views().StagingSecondary.Wrap = wrap - - self.c.Helpers().Staging.RefreshStagingPanel(opts) - } -} - -func (self *StagingController) GetOnFocusLost() func(types.OnFocusLostOpts) { - return func(opts types.OnFocusLostOpts) { - self.context.SetState(nil) - - if opts.NewContextKey != self.otherContext.GetKey() { - self.c.Views().Staging.Wrap = true - self.c.Views().StagingSecondary.Wrap = true - } - } -} - -func (self *StagingController) OpenFile() error { - self.context.GetMutex().Lock() - defer self.context.GetMutex().Unlock() - - path := self.FilePath() - - if path == "" { - return nil - } - - return self.c.Helpers().Files.OpenFile(path) -} - -func (self *StagingController) EditFile() error { - self.context.GetMutex().Lock() - defer self.context.GetMutex().Unlock() - - path := self.FilePath() - - if path == "" { - return nil - } - - lineNumber := self.context.GetState().CurrentLineNumber() - lineNumber = self.c.Helpers().Diff.AdjustLineNumber(path, lineNumber, self.context.GetViewName()) - return self.c.Helpers().Files.EditFileAtLine(path, lineNumber) -} - -func (self *StagingController) Escape() error { - if self.context.GetState().SelectingRange() || self.context.GetState().SelectingHunkEnabledByUser() { - self.context.GetState().SetLineSelectMode() - self.c.PostRefreshUpdate(self.context) - return nil - } - - self.c.Context().Pop() - return nil -} - -func (self *StagingController) EscapeDescription() string { - if state := self.context.GetState(); state != nil { - if state.SelectingRange() { - return self.c.Tr.DismissRangeSelect - } - - if state.SelectingHunkEnabledByUser() { - return self.c.Tr.SelectLineByLine - } - } - - return self.c.Tr.ReturnToFilesPanel -} - -func (self *StagingController) TogglePanel() error { - if self.otherContext.GetState() != nil { - self.c.Context().Push(self.otherContext, types.OnFocusOpts{}) - } - - return nil -} - -func (self *StagingController) ToggleStaged() error { - if self.c.UserConfig().Git.DiffContextSize == 0 { - return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextToStage, - keybindings.Label(self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)) - } - - return self.applySelectionAndRefresh(self.staged) -} - -func (self *StagingController) DiscardSelection() error { - if self.c.UserConfig().Git.DiffContextSize == 0 { - return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextToDiscard, - keybindings.Label(self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)) - } - - return self.c.ConfirmIf(!self.staged && !self.c.UserConfig().Gui.SkipDiscardChangeWarning, - types.ConfirmOpts{ - Title: self.c.Tr.DiscardChangeTitle, - Prompt: self.c.Tr.DiscardChangePrompt, - HandleConfirm: func() error { return self.applySelectionAndRefresh(true) }, - }) -} - -func (self *StagingController) applySelectionAndRefresh(reverse bool) error { - if err := self.applySelection(reverse); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}}) - return nil -} - -func (self *StagingController) applySelection(reverse bool) error { - self.context.GetMutex().Lock() - defer self.context.GetMutex().Unlock() - - state := self.context.GetState() - path := self.FilePath() - if path == "" { - return nil - } - - firstLineIdx, lastLineIdx := state.SelectedPatchRange() - patchToApply := patch. - Parse(state.GetDiff()). - Transform(patch.TransformOpts{ - Reverse: reverse, - IncludedLineIndices: patch.ExpandRange(firstLineIdx, lastLineIdx), - FileNameOverride: path, - }). - FormatPlain() - - if patchToApply == "" { - return nil - } - - // apply the patch then refresh this panel - // create a new temp file with the patch, then call git apply with that patch - self.c.LogAction(self.c.Tr.Actions.ApplyPatch) - err := self.c.Git().Patch.ApplyPatch( - patchToApply, - git_commands.ApplyPatchOpts{ - Reverse: reverse, - Cached: !reverse || self.staged, - }, - ) - if err != nil { - return err - } - - if state.SelectingRange() { - firstLine, _ := state.SelectedViewRange() - state.SelectLine(firstLine) - } - - return nil -} - -func (self *StagingController) EditHunkAndRefresh() error { - if err := self.editHunk(); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}}) - return nil -} - -func (self *StagingController) editHunk() error { - self.context.GetMutex().Lock() - defer self.context.GetMutex().Unlock() - - state := self.context.GetState() - path := self.FilePath() - if path == "" { - return nil - } - - hunkStartIdx, hunkEndIdx := state.CurrentHunkBounds() - patchText := patch. - Parse(state.GetDiff()). - Transform(patch.TransformOpts{ - Reverse: self.staged, - IncludedLineIndices: patch.ExpandRange(hunkStartIdx, hunkEndIdx), - FileNameOverride: path, - }). - FormatPlain() - - patchFilepath, err := self.c.Git().Patch.SaveTemporaryPatch(patchText) - if err != nil { - return err - } - - lineOffset := 3 - lineIdxInHunk := state.GetSelectedPatchLineIdx() - hunkStartIdx - if err := self.c.Helpers().Files.EditFileAtLineAndWait(patchFilepath, lineIdxInHunk+lineOffset); err != nil { - return err - } - - editedPatchText, err := self.c.Git().File.Cat(patchFilepath) - if err != nil { - return err - } - - self.c.LogAction(self.c.Tr.Actions.ApplyPatch) - - lineCount := strings.Count(editedPatchText, "\n") + 1 - newPatchText := patch. - Parse(editedPatchText). - Transform(patch.TransformOpts{ - IncludedLineIndices: patch.ExpandRange(0, lineCount), - FileNameOverride: path, - }). - FormatPlain() - - if err := self.c.Git().Patch.ApplyPatch( - newPatchText, - git_commands.ApplyPatchOpts{ - Reverse: self.staged, - Cached: true, - }, - ); err != nil { - return err - } - - return nil -} - -func (self *StagingController) FilePath() string { - return self.c.Contexts().Files.GetSelectedPath() -} diff --git a/pkg/gui/controllers/stash_controller.go b/pkg/gui/controllers/stash_controller.go deleted file mode 100644 index 889bc087293..00000000000 --- a/pkg/gui/controllers/stash_controller.go +++ /dev/null @@ -1,215 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type StashController struct { - baseController - *ListControllerTrait[*models.StashEntry] - c *ControllerCommon -} - -var _ types.IController = &StashController{} - -func NewStashController( - c *ControllerCommon, -) *StashController { - return &StashController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().Stash, - c.Contexts().Stash.GetSelected, - c.Contexts().Stash.GetSelectedItems, - ), - c: c, - } -} - -func (self *StashController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.withItem(self.handleStashApply), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Apply, - Tooltip: self.c.Tr.StashApplyTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Stash.PopStash), - Handler: self.withItem(self.handleStashPop), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Pop, - Tooltip: self.c.Tr.StashPopTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: self.withItems(self.handleStashDrop), - GetDisabledReason: self.require(self.itemRangeSelected()), - Description: self.c.Tr.Drop, - Tooltip: self.c.Tr.StashDropTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.New), - Handler: self.withItem(self.handleNewBranchOffStashEntry), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.NewBranch, - Tooltip: self.c.Tr.NewBranchFromStashTooltip, - }, - { - Key: opts.GetKey(opts.Config.Stash.RenameStash), - Handler: self.withItem(self.handleRenameStashEntry), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.RenameStash, - }, - } - - return bindings -} - -func (self *StashController) GetOnRenderToMain() func() { - return func() { - self.c.Helpers().Diff.WithDiffModeCheck(func() { - var task types.UpdateTask - stashEntry := self.context().GetSelected() - if stashEntry == nil { - task = types.NewRenderStringTask(self.c.Tr.NoStashEntries) - } else { - prefix := style.FgYellow.Sprintf("%s\n\n", stashEntry.Description()) - task = types.NewRunPtyTaskWithPrefix( - self.c.Git().Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd(), - prefix, - ) - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: "Stash", - SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(), - Task: task, - }, - }) - }) - } -} - -func (self *StashController) context() *context.StashContext { - return self.c.Contexts().Stash -} - -func (self *StashController) handleStashApply(stashEntry *models.StashEntry) error { - return self.c.ConfirmIf(!self.c.UserConfig().Gui.SkipStashWarning, - types.ConfirmOpts{ - Title: self.c.Tr.StashApply, - Prompt: self.c.Tr.SureApplyStashEntry, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.ApplyStash) - err := self.c.Git().Stash.Apply(stashEntry.Index) - self.postStashRefresh() - if err != nil { - return err - } - if self.c.UserConfig().Gui.SwitchToFilesAfterStashApply { - self.c.Context().Push(self.c.Contexts().Files, types.OnFocusOpts{}) - } - return nil - }, - }) -} - -func (self *StashController) handleStashPop(stashEntry *models.StashEntry) error { - pop := func() error { - self.c.LogAction(self.c.Tr.Actions.PopStash) - self.c.LogCommand("Popping stash "+stashEntry.Hash, false) - err := self.c.Git().Stash.Pop(stashEntry.Index) - self.postStashRefresh() - if err != nil { - return err - } - if self.c.UserConfig().Gui.SwitchToFilesAfterStashPop { - self.c.Context().Push(self.c.Contexts().Files, types.OnFocusOpts{}) - } - return nil - } - - if self.c.UserConfig().Gui.SkipStashWarning { - return pop() - } - - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.StashPop, - Prompt: self.c.Tr.SurePopStashEntry, - HandleConfirm: func() error { - return pop() - }, - }) - - return nil -} - -func (self *StashController) handleStashDrop(stashEntries []*models.StashEntry) error { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.StashDrop, - Prompt: self.c.Tr.SureDropStashEntry, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.DropStash) - for i := len(stashEntries) - 1; i >= 0; i-- { - self.c.LogCommand("Dropping stash "+stashEntries[i].Hash, false) - err := self.c.Git().Stash.Drop(stashEntries[i].Index) - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}}) - if err != nil { - return err - } - } - self.context().CollapseRangeSelectionToTop() - return nil - }, - }) - - return nil -} - -func (self *StashController) postStashRefresh() { - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}}) -} - -func (self *StashController) handleNewBranchOffStashEntry(stashEntry *models.StashEntry) error { - return self.c.Helpers().Refs.NewBranch(stashEntry.FullRefName(), stashEntry.Description(), "") -} - -func (self *StashController) handleRenameStashEntry(stashEntry *models.StashEntry) error { - message := utils.ResolvePlaceholderString( - self.c.Tr.RenameStashPrompt, - map[string]string{ - "stashName": stashEntry.RefName(), - }, - ) - - self.c.Prompt(types.PromptOpts{ - Title: message, - InitialContent: stashEntry.Name, - HandleConfirm: func(response string) error { - self.c.LogAction(self.c.Tr.Actions.RenameStash) - err := self.c.Git().Stash.Rename(stashEntry.Index, response) - if err != nil { - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}}) - return err - } - self.context().SetSelection(0) // Select the renamed stash - self.context().FocusLine() - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}}) - return nil - }, - }) - - return nil -} diff --git a/pkg/gui/controllers/status_controller.go b/pkg/gui/controllers/status_controller.go deleted file mode 100644 index d2c65809524..00000000000 --- a/pkg/gui/controllers/status_controller.go +++ /dev/null @@ -1,242 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - "strings" - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/constants" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type StatusController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &StatusController{} - -func NewStatusController( - c *ControllerCommon, -) *StatusController { - return &StatusController{ - baseController: baseController{}, - c: c, - } -} - -func (self *StatusController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.OpenFile), - Handler: self.openConfig, - Description: self.c.Tr.OpenConfig, - Tooltip: self.c.Tr.OpenFileTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Edit), - Handler: self.editConfig, - Description: self.c.Tr.EditConfig, - Tooltip: self.c.Tr.EditFileTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Status.CheckForUpdate), - Handler: self.handleCheckForUpdate, - Description: self.c.Tr.CheckForUpdate, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Status.RecentRepos), - Handler: self.c.Helpers().Repos.CreateRecentReposMenu, - Description: self.c.Tr.SwitchRepo, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Status.AllBranchesLogGraph), - Handler: func() error { self.switchToOrRotateAllBranchesLogs(); return nil }, - Description: self.c.Tr.AllBranchesLogGraph, - }, - } - - return bindings -} - -func (self *StatusController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: self.Context().GetViewName(), - Key: gocui.MouseLeft, - Handler: self.onClick, - }, - } -} - -func (self *StatusController) GetOnRenderToMain() func() { - return func() { - switch self.c.UserConfig().Gui.StatusPanelView { - case "dashboard": - self.showDashboard() - case "allBranchesLog": - self.showAllBranchLogs() - default: - self.showDashboard() - } - } -} - -func (self *StatusController) Context() types.Context { - return self.c.Contexts().Status -} - -func (self *StatusController) onClick(opts gocui.ViewMouseBindingOpts) error { - // TODO: move into some abstraction (status is currently not a listViewContext where a lot of this code lives) - currentBranch := self.c.Helpers().Refs.GetCheckedOutRef() - if currentBranch == nil { - // need to wait for branches to refresh - return nil - } - - self.c.Context().Push(self.Context(), types.OnFocusOpts{}) - - upstreamStatus := utils.Decolorise(presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now(), self.c.UserConfig())) - repoName := self.c.Git().RepoPaths.RepoName() - workingTreeState := self.c.Git().Status.WorkingTreeState() - if workingTreeState.Any() { - workingTreeStatus := fmt.Sprintf("(%s)", workingTreeState.LowerCaseTitle(self.c.Tr)) - if cursorInSubstring(opts.X, upstreamStatus+" ", workingTreeStatus) { - return self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu() - } - if cursorInSubstring(opts.X, upstreamStatus+" "+workingTreeStatus+" ", repoName) { - return self.c.Helpers().Repos.CreateRecentReposMenu() - } - } else if cursorInSubstring(opts.X, upstreamStatus+" ", repoName) { - return self.c.Helpers().Repos.CreateRecentReposMenu() - } - - return nil -} - -func runeCount(str string) int { - return len([]rune(str)) -} - -func cursorInSubstring(cx int, prefix string, substring string) bool { - return cx >= runeCount(prefix) && cx < runeCount(prefix+substring) -} - -func lazygitTitle() string { - return ` - _ _ _ - | | (_) | - | | __ _ _____ _ __ _ _| |_ - | |/ _` + "`" + ` |_ / | | |/ _` + "`" + ` | | __| - | | (_| |/ /| |_| | (_| | | |_ - |_|\__,_/___|\__, |\__, |_|\__| - __/ | __/ | - |___/ |___/ ` -} - -func (self *StatusController) askForConfigFile(action func(file string) error) error { - confPaths := self.c.GetConfig().GetUserConfigPaths() - switch len(confPaths) { - case 0: - return errors.New(self.c.Tr.NoConfigFileFoundErr) - case 1: - return action(confPaths[0]) - default: - menuItems := lo.Map(confPaths, func(path string, _ int) *types.MenuItem { - return &types.MenuItem{ - Label: path, - OnPress: func() error { - return action(path) - }, - } - }) - - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.SelectConfigFile, - Items: menuItems, - }) - } -} - -func (self *StatusController) openConfig() error { - return self.askForConfigFile(self.c.Helpers().Files.OpenFile) -} - -func (self *StatusController) editConfig() error { - return self.askForConfigFile(func(file string) error { - return self.c.Helpers().Files.EditFiles([]string{file}) - }) -} - -func (self *StatusController) showAllBranchLogs() { - cmdObj := self.c.Git().Branch.AllBranchesLogCmdObj() - task := types.NewRunPtyTask(cmdObj.GetCmd()) - - title := self.c.Tr.LogTitle - if i, n := self.c.Git().Branch.GetAllBranchesLogIdxAndCount(); n > 1 { - title = fmt.Sprintf(self.c.Tr.LogXOfYTitle, i+1, n) - } - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: title, - Task: task, - }, - }) -} - -// Switches to the all branches view, or, if already on that view, -// rotates to the next command in the list, and then renders it. -func (self *StatusController) switchToOrRotateAllBranchesLogs() { - // A bit of a hack to ensure we only rotate to the next branch log command - // if we currently are looking at a branch log. Otherwise, we should just show - // the current index (if we are coming from the dashboard). - if self.c.Views().Main.Title != self.c.Tr.StatusTitle { - self.c.Git().Branch.RotateAllBranchesLogIdx() - } - self.showAllBranchLogs() -} - -func (self *StatusController) showDashboard() { - versionStr := "master" - version, err := types.ParseVersionNumber(self.c.GetConfig().GetVersion()) - if err == nil { - // Don't just take the version string as is, but format it again. This - // way it will be correct even if a distribution omits the "v", or the - // ".0" at the end. - versionStr = fmt.Sprintf("v%d.%d.%d", version.Major, version.Minor, version.Patch) - } - - dashboardString := strings.Join( - []string{ - lazygitTitle(), - fmt.Sprintf("Copyright %d Jesse Duffield", time.Now().Year()), - fmt.Sprintf("Keybindings: %s", fmt.Sprintf(constants.Links.Docs.Keybindings, versionStr)), - fmt.Sprintf("Config Options: %s", fmt.Sprintf(constants.Links.Docs.Config, versionStr)), - fmt.Sprintf("Tutorial: %s", constants.Links.Docs.Tutorial), - fmt.Sprintf("Raise an Issue: %s", constants.Links.Issues), - fmt.Sprintf("Release Notes: %s", constants.Links.Releases), - style.FgMagenta.Sprintf("Become a sponsor: %s", constants.Links.Donate), // caffeine ain't free - }, "\n\n") + "\n" - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: self.c.Tr.StatusTitle, - Task: types.NewRenderStringTask(dashboardString), - }, - }) -} - -func (self *StatusController) handleCheckForUpdate() error { - return self.c.Helpers().Update.CheckForUpdateInForeground() -} diff --git a/pkg/gui/controllers/sub_commits_controller.go b/pkg/gui/controllers/sub_commits_controller.go deleted file mode 100644 index 8799cd3c60b..00000000000 --- a/pkg/gui/controllers/sub_commits_controller.go +++ /dev/null @@ -1,72 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type SubCommitsController struct { - baseController - *ListControllerTrait[*models.Commit] - c *ControllerCommon -} - -var _ types.IController = &SubCommitsController{} - -func NewSubCommitsController( - c *ControllerCommon, -) *SubCommitsController { - return &SubCommitsController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().SubCommits, - c.Contexts().SubCommits.GetSelected, - c.Contexts().SubCommits.GetSelectedItems, - ), - c: c, - } -} - -func (self *SubCommitsController) Context() types.Context { - return self.context() -} - -func (self *SubCommitsController) context() *context.SubCommitsContext { - return self.c.Contexts().SubCommits -} - -func (self *SubCommitsController) GetOnRenderToMain() func() { - return func() { - self.c.Helpers().Diff.WithDiffModeCheck(func() { - commit := self.context().GetSelected() - var task types.UpdateTask - if commit == nil { - task = types.NewRenderStringTask("No commits") - } else { - refRange := self.context().GetSelectedRefRangeForDiffFiles() - task = self.c.Helpers().Diff.GetUpdateTaskForRenderingCommitsDiff(commit, refRange) - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: "Commit", - SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(), - Task: task, - }, - }) - }) - } -} - -func (self *SubCommitsController) GetOnFocus() func(types.OnFocusOpts) { - return func(types.OnFocusOpts) { - context := self.context() - if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() { - context.SetLimitCommits(false) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.SUB_COMMITS}}) - } - } -} diff --git a/pkg/gui/controllers/submodules_controller.go b/pkg/gui/controllers/submodules_controller.go deleted file mode 100644 index a4a1cb7961f..00000000000 --- a/pkg/gui/controllers/submodules_controller.go +++ /dev/null @@ -1,327 +0,0 @@ -package controllers - -import ( - "fmt" - "path/filepath" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type SubmodulesController struct { - baseController - *ListControllerTrait[*models.SubmoduleConfig] - c *ControllerCommon -} - -var _ types.IController = &SubmodulesController{} - -func NewSubmodulesController( - c *ControllerCommon, -) *SubmodulesController { - return &SubmodulesController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().Submodules, - c.Contexts().Submodules.GetSelected, - c.Contexts().Submodules.GetSelectedItems, - ), - c: c, - } -} - -func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.GoInto), - Handler: self.withItem(self.enter), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Enter, - Tooltip: utils.ResolvePlaceholderString(self.c.Tr.EnterSubmoduleTooltip, - map[string]string{"escape": keybindings.Label(opts.Config.Universal.Return)}), - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.withItem(self.enter), - GetDisabledReason: self.require(self.singleItemSelected()), - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: self.withItem(self.remove), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Remove, - Tooltip: self.c.Tr.RemoveSubmoduleTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Submodules.Update), - Handler: self.withItem(self.update), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Update, - Tooltip: self.c.Tr.SubmoduleUpdateTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.New), - Handler: self.add, - Description: self.c.Tr.NewSubmodule, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Edit), - Handler: self.withItem(self.editURL), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.EditSubmoduleUrl, - }, - { - Key: opts.GetKey(opts.Config.Submodules.Init), - Handler: self.withItem(self.init), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Initialize, - Tooltip: self.c.Tr.InitSubmoduleTooltip, - }, - { - Key: opts.GetKey(opts.Config.Submodules.BulkMenu), - Handler: self.openBulkActionsMenu, - Description: self.c.Tr.ViewBulkSubmoduleOptions, - OpensMenu: true, - }, - { - Key: nil, - Handler: self.easterEgg, - Description: self.c.Tr.EasterEgg, - }, - } -} - -func (self *SubmodulesController) GetOnClick() func() error { - return self.withItemGraceful(self.enter) -} - -func (self *SubmodulesController) GetOnRenderToMain() func() { - return func() { - self.c.Helpers().Diff.WithDiffModeCheck(func() { - var task types.UpdateTask - submodule := self.context().GetSelected() - if submodule == nil { - task = types.NewRenderStringTask("No submodules") - } else { - prefix := fmt.Sprintf( - "Name: %s\nPath: %s\nUrl: %s\n\n", - style.FgGreen.Sprint(submodule.FullName()), - style.FgYellow.Sprint(submodule.FullPath()), - style.FgCyan.Sprint(submodule.Url), - ) - - file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule) - if file == nil { - task = types.NewRenderStringTask(prefix) - } else { - cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges) - task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix) - } - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: "Submodule", - Task: task, - }, - }) - }) - } -} - -func (self *SubmodulesController) enter(submodule *models.SubmoduleConfig) error { - return self.c.Helpers().Repos.EnterSubmodule(submodule) -} - -func (self *SubmodulesController) add() error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.NewSubmoduleUrl, - HandleConfirm: func(submoduleUrl string) error { - nameSuggestion := filepath.Base(strings.TrimSuffix(submoduleUrl, filepath.Ext(submoduleUrl))) - - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.NewSubmoduleName, - InitialContent: nameSuggestion, - HandleConfirm: func(submoduleName string) error { - self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.NewSubmodulePath, - InitialContent: submoduleName, - HandleConfirm: func(submodulePath string) error { - return self.c.WithWaitingStatus(self.c.Tr.AddingSubmoduleStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.AddSubmodule) - err := self.c.Git().Submodule.Add(submoduleName, submodulePath, submoduleUrl) - if err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}}) - return nil - }) - }, - }) - - return nil - }, - }) - - return nil - }, - }) - - return nil -} - -func (self *SubmodulesController) editURL(submodule *models.SubmoduleConfig) error { - self.c.Prompt(types.PromptOpts{ - Title: fmt.Sprintf(self.c.Tr.UpdateSubmoduleUrl, submodule.FullName()), - InitialContent: submodule.Url, - HandleConfirm: func(newUrl string) error { - return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleUrlStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.UpdateSubmoduleUrl) - err := self.c.Git().Submodule.UpdateUrl(submodule, newUrl) - if err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}}) - return nil - }) - }, - }) - - return nil -} - -func (self *SubmodulesController) init(submodule *models.SubmoduleConfig) error { - return self.c.WithWaitingStatus(self.c.Tr.InitializingSubmoduleStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.InitialiseSubmodule) - err := self.c.Git().Submodule.Init(submodule.Path) - if err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}}) - return nil - }) -} - -func (self *SubmodulesController) openBulkActionsMenu() error { - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.BulkSubmoduleOptions, - Items: []*types.MenuItem{ - { - LabelColumns: []string{self.c.Tr.BulkInitSubmodules, style.FgGreen.Sprint(self.c.Git().Submodule.BulkInitCmdObj().ToString())}, - OnPress: func() error { - return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.BulkInitialiseSubmodules) - err := self.c.Git().Submodule.BulkInitCmdObj().Run() - if err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}}) - return nil - }) - }, - Key: 'i', - }, - { - LabelColumns: []string{self.c.Tr.BulkUpdateSubmodules, style.FgYellow.Sprint(self.c.Git().Submodule.BulkUpdateCmdObj().ToString())}, - OnPress: func() error { - return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.BulkUpdateSubmodules) - if err := self.c.Git().Submodule.BulkUpdateCmdObj().Run(); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}}) - return nil - }) - }, - Key: 'u', - }, - { - LabelColumns: []string{self.c.Tr.BulkUpdateRecursiveSubmodules, style.FgYellow.Sprint(self.c.Git().Submodule.BulkUpdateRecursivelyCmdObj().ToString())}, - OnPress: func() error { - return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.BulkUpdateRecursiveSubmodules) - if err := self.c.Git().Submodule.BulkUpdateRecursivelyCmdObj().Run(); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}}) - return nil - }) - }, - Key: 'r', - }, - { - LabelColumns: []string{self.c.Tr.BulkDeinitSubmodules, style.FgRed.Sprint(self.c.Git().Submodule.BulkDeinitCmdObj().ToString())}, - OnPress: func() error { - return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.BulkDeinitialiseSubmodules) - if err := self.c.Git().Submodule.BulkDeinitCmdObj().Run(); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}}) - return nil - }) - }, - Key: 'd', - }, - }, - }) -} - -func (self *SubmodulesController) update(submodule *models.SubmoduleConfig) error { - return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.UpdateSubmodule) - err := self.c.Git().Submodule.Update(submodule.Path) - if err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}}) - return nil - }) -} - -func (self *SubmodulesController) remove(submodule *models.SubmoduleConfig) error { - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.RemoveSubmodule, - Prompt: fmt.Sprintf(self.c.Tr.RemoveSubmodulePrompt, submodule.FullName()), - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.RemoveSubmodule) - if err := self.c.Git().Submodule.Delete(submodule); err != nil { - return err - } - - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES, types.FILES}}) - return nil - }, - }) - - return nil -} - -func (self *SubmodulesController) easterEgg() error { - self.c.Context().Push(self.c.Contexts().Snake, types.OnFocusOpts{}) - return nil -} - -func (self *SubmodulesController) context() *context.SubmodulesContext { - return self.c.Contexts().Submodules -} diff --git a/pkg/gui/controllers/suggestions_controller.go b/pkg/gui/controllers/suggestions_controller.go deleted file mode 100644 index 715ee12e953..00000000000 --- a/pkg/gui/controllers/suggestions_controller.go +++ /dev/null @@ -1,101 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type SuggestionsController struct { - baseController - *ListControllerTrait[*types.Suggestion] - c *ControllerCommon -} - -var _ types.IController = &SuggestionsController{} - -func NewSuggestionsController( - c *ControllerCommon, -) *SuggestionsController { - return &SuggestionsController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().Suggestions, - c.Contexts().Suggestions.GetSelected, - c.Contexts().Suggestions.GetSelectedItems, - ), - c: c, - } -} - -func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.ConfirmSuggestion), - Handler: func() error { return self.context().State.OnConfirm() }, - GetDisabledReason: self.require(self.singleItemSelected()), - }, - { - Key: opts.GetKey(opts.Config.Universal.Return), - Handler: func() error { return self.context().State.OnClose() }, - }, - { - Key: opts.GetKey(opts.Config.Universal.TogglePanel), - Handler: self.switchToPrompt, - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: func() error { - return self.context().State.OnDeleteSuggestion() - }, - }, - { - Key: opts.GetKey(opts.Config.Universal.Edit), - Handler: func() error { - if self.context().State.AllowEditSuggestion { - if selectedItem := self.c.Contexts().Suggestions.GetSelected(); selectedItem != nil { - self.c.Contexts().Prompt.GetView().TextArea.Clear() - self.c.Contexts().Prompt.GetView().TextArea.TypeString(selectedItem.Value) - self.c.Contexts().Prompt.GetView().RenderTextArea() - self.c.Contexts().Suggestions.RefreshSuggestions() - return self.switchToPrompt() - } - } - return nil - }, - }, - } - - return bindings -} - -func (self *SuggestionsController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: self.c.Contexts().Prompt.GetViewName(), - FocusedView: self.c.Contexts().Suggestions.GetViewName(), - Key: gocui.MouseLeft, - Handler: func(gocui.ViewMouseBindingOpts) error { - return self.switchToPrompt() - }, - }, - } -} - -func (self *SuggestionsController) switchToPrompt() error { - self.c.Views().Suggestions.Subtitle = "" - self.c.Views().Suggestions.Highlight = false - self.c.Context().Replace(self.c.Contexts().Prompt) - return nil -} - -func (self *SuggestionsController) GetOnFocusLost() func(types.OnFocusLostOpts) { - return func(types.OnFocusLostOpts) { - self.c.Helpers().Confirmation.DeactivatePrompt() - } -} - -func (self *SuggestionsController) context() *context.SuggestionsContext { - return self.c.Contexts().Suggestions -} diff --git a/pkg/gui/controllers/switch_to_diff_files_controller.go b/pkg/gui/controllers/switch_to_diff_files_controller.go deleted file mode 100644 index 86e9275588a..00000000000 --- a/pkg/gui/controllers/switch_to_diff_files_controller.go +++ /dev/null @@ -1,122 +0,0 @@ -package controllers - -import ( - "path/filepath" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -// This controller is for all contexts that contain commit files. - -var _ types.IController = &SwitchToDiffFilesController{} - -type CanSwitchToDiffFiles interface { - types.IListContext - CanRebase() bool - GetSelectedRef() models.Ref - GetSelectedRefRangeForDiffFiles() *types.RefRange -} - -// Not using our ListControllerTrait because we have our own way of working with -// range selections that's different from ListControllerTrait's -type SwitchToDiffFilesController struct { - baseController - c *ControllerCommon - context CanSwitchToDiffFiles -} - -func NewSwitchToDiffFilesController( - c *ControllerCommon, - context CanSwitchToDiffFiles, -) *SwitchToDiffFilesController { - return &SwitchToDiffFilesController{ - baseController: baseController{}, - c: c, - context: context, - } -} - -func (self *SwitchToDiffFilesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.GoInto), - Handler: self.enter, - GetDisabledReason: self.canEnter, - Description: self.c.Tr.ViewItemFiles, - }, - } - - return bindings -} - -func (self *SwitchToDiffFilesController) Context() types.Context { - return self.context -} - -func (self *SwitchToDiffFilesController) GetOnClick() func() error { - return func() error { - if self.canEnter() == nil { - return self.enter() - } - - return nil - } -} - -func (self *SwitchToDiffFilesController) enter() error { - ref := self.context.GetSelectedRef() - refsRange := self.context.GetSelectedRefRangeForDiffFiles() - commitFilesContext := self.c.Contexts().CommitFiles - - canRebase := self.context.CanRebase() - if canRebase { - if self.c.Modes().Diffing.Active() { - if self.c.Modes().Diffing.Ref != ref.RefName() { - canRebase = false - } - } else if refsRange != nil { - canRebase = false - } - } - - commitFilesContext.ReInit(ref, refsRange) - commitFilesContext.SetSelection(0) - commitFilesContext.SetCanRebase(canRebase) - commitFilesContext.SetParentContext(self.context) - commitFilesContext.SetWindowName(self.context.GetWindowName()) - commitFilesContext.ClearSearchString() - commitFilesContext.GetView().TitlePrefix = self.context.GetView().TitlePrefix - - self.c.Refresh(types.RefreshOptions{ - Scope: []types.RefreshableView{types.COMMIT_FILES}, - }) - - if filterPath := self.c.Modes().Filtering.GetPath(); filterPath != "" { - path, err := filepath.Rel(self.c.Git().RepoPaths.RepoPath(), filterPath) - if err != nil { - path = filterPath - } - commitFilesContext.CommitFileTreeViewModel.SelectPath( - filepath.ToSlash(path), self.c.UserConfig().Gui.ShowRootItemInFileTree) - } - - self.c.Context().Push(commitFilesContext, types.OnFocusOpts{}) - return nil -} - -func (self *SwitchToDiffFilesController) canEnter() *types.DisabledReason { - refRange := self.context.GetSelectedRefRangeForDiffFiles() - if refRange != nil { - return nil - } - ref := self.context.GetSelectedRef() - if ref == nil { - return &types.DisabledReason{Text: self.c.Tr.NoItemSelected} - } - if ref.RefName() == "" { - return &types.DisabledReason{Text: self.c.Tr.SelectedItemDoesNotHaveFiles} - } - - return nil -} diff --git a/pkg/gui/controllers/switch_to_focused_main_view_controller.go b/pkg/gui/controllers/switch_to_focused_main_view_controller.go deleted file mode 100644 index 132ec96dbae..00000000000 --- a/pkg/gui/controllers/switch_to_focused_main_view_controller.go +++ /dev/null @@ -1,81 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -// This controller is for all contexts that can focus their main view. - -var _ types.IController = &SwitchToFocusedMainViewController{} - -type SwitchToFocusedMainViewController struct { - baseController - c *ControllerCommon - context types.Context -} - -func NewSwitchToFocusedMainViewController( - c *ControllerCommon, - context types.Context, -) *SwitchToFocusedMainViewController { - return &SwitchToFocusedMainViewController{ - baseController: baseController{}, - c: c, - context: context, - } -} - -func (self *SwitchToFocusedMainViewController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.FocusMainView), - Handler: self.handleFocusMainView, - Description: self.c.Tr.FocusMainView, - Tag: "global", - }, - } - - return bindings -} - -func (self *SwitchToFocusedMainViewController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: "main", - Key: gocui.MouseLeft, - Handler: self.onClickMain, - FocusedView: self.context.GetViewName(), - }, - { - ViewName: "secondary", - Key: gocui.MouseLeft, - Handler: self.onClickSecondary, - FocusedView: self.context.GetViewName(), - }, - } -} - -func (self *SwitchToFocusedMainViewController) Context() types.Context { - return self.context -} - -func (self *SwitchToFocusedMainViewController) onClickMain(opts gocui.ViewMouseBindingOpts) error { - return self.focusMainView(self.c.Contexts().Normal) -} - -func (self *SwitchToFocusedMainViewController) onClickSecondary(opts gocui.ViewMouseBindingOpts) error { - return self.focusMainView(self.c.Contexts().NormalSecondary) -} - -func (self *SwitchToFocusedMainViewController) handleFocusMainView() error { - return self.focusMainView(self.c.Contexts().Normal) -} - -func (self *SwitchToFocusedMainViewController) focusMainView(mainViewContext types.Context) error { - if context, ok := mainViewContext.(types.ISearchableContext); ok { - context.ClearSearchString() - } - self.c.Context().Push(mainViewContext, types.OnFocusOpts{}) - return nil -} diff --git a/pkg/gui/controllers/switch_to_sub_commits_controller.go b/pkg/gui/controllers/switch_to_sub_commits_controller.go deleted file mode 100644 index 9257e33d3cd..00000000000 --- a/pkg/gui/controllers/switch_to_sub_commits_controller.go +++ /dev/null @@ -1,74 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -var _ types.IController = &SwitchToSubCommitsController{} - -type CanSwitchToSubCommits interface { - types.IListContext - GetSelectedRef() models.Ref - ShowBranchHeadsInSubCommits() bool -} - -// Not using our ListControllerTrait because our 'selected' item is not a list item -// but an attribute on it i.e. the ref of an item. -type SwitchToSubCommitsController struct { - baseController - *ListControllerTrait[models.Ref] - c *ControllerCommon - context CanSwitchToSubCommits -} - -func NewSwitchToSubCommitsController( - c *ControllerCommon, - context CanSwitchToSubCommits, -) *SwitchToSubCommitsController { - return &SwitchToSubCommitsController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - context, - context.GetSelectedRef, - func() ([]models.Ref, int, int) { - panic("Not implemented") - }, - ), - c: c, - context: context, - } -} - -func (self *SwitchToSubCommitsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Handler: self.viewCommits, - GetDisabledReason: self.require(self.singleItemSelected()), - Key: opts.GetKey(opts.Config.Universal.GoInto), - Description: self.c.Tr.ViewCommits, - }, - } - - return bindings -} - -func (self *SwitchToSubCommitsController) GetOnClick() func() error { - return self.viewCommits -} - -func (self *SwitchToSubCommitsController) viewCommits() error { - ref := self.context.GetSelectedRef() - if ref == nil { - return nil - } - - return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{ - Ref: ref, - TitleRef: ref.RefName(), - Context: self.context, - ShowBranchHeads: self.context.ShowBranchHeadsInSubCommits(), - }) -} diff --git a/pkg/gui/controllers/sync_controller.go b/pkg/gui/controllers/sync_controller.go deleted file mode 100644 index 023ac0d2589..00000000000 --- a/pkg/gui/controllers/sync_controller.go +++ /dev/null @@ -1,263 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type SyncController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &SyncController{} - -func NewSyncController( - common *ControllerCommon, -) *SyncController { - return &SyncController{ - baseController: baseController{}, - c: common, - } -} - -func (self *SyncController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.Push), - Handler: opts.Guards.NoPopupPanel(self.HandlePush), - GetDisabledReason: self.getDisabledReasonForPushOrPull, - Description: self.c.Tr.Push, - Tooltip: self.c.Tr.PushTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Pull), - Handler: opts.Guards.NoPopupPanel(self.HandlePull), - GetDisabledReason: self.getDisabledReasonForPushOrPull, - Description: self.c.Tr.Pull, - Tooltip: self.c.Tr.PullTooltip, - }, - } - - return bindings -} - -func (self *SyncController) Context() types.Context { - return nil -} - -func (self *SyncController) HandlePush() error { - return self.branchCheckedOut(self.push)() -} - -func (self *SyncController) HandlePull() error { - return self.branchCheckedOut(self.pull)() -} - -func (self *SyncController) getDisabledReasonForPushOrPull() *types.DisabledReason { - currentBranch := self.c.Helpers().Refs.GetCheckedOutRef() - if currentBranch != nil { - op := self.c.State().GetItemOperation(currentBranch) - if op != types.ItemOperationNone { - return &types.DisabledReason{Text: self.c.Tr.CantPullOrPushSameBranchTwice} - } - } - - return nil -} - -func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func() error { - return func() error { - currentBranch := self.c.Helpers().Refs.GetCheckedOutRef() - if currentBranch == nil { - // need to wait for branches to refresh - return nil - } - - return f(currentBranch) - } -} - -func (self *SyncController) push(currentBranch *models.Branch) error { - // if we are behind our upstream branch we'll ask if the user wants to force push - if currentBranch.IsTrackingRemote() { - opts := pushOpts{remoteBranchStoredLocally: currentBranch.RemoteBranchStoredLocally()} - if currentBranch.IsBehindForPush() { - return self.requestToForcePush(currentBranch, opts) - } - - return self.pushAux(currentBranch, opts) - } - - if self.c.Git().Config.GetPushToCurrent() { - return self.pushAux(currentBranch, pushOpts{setUpstream: true}) - } - - return self.c.Helpers().Upstream.PromptForUpstreamWithInitialContent(currentBranch, func(upstream string) error { - upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream) - if err != nil { - return err - } - - return self.pushAux(currentBranch, pushOpts{ - setUpstream: true, - upstreamRemote: upstreamRemote, - upstreamBranch: upstreamBranch, - }) - }) -} - -func (self *SyncController) pull(currentBranch *models.Branch) error { - action := self.c.Tr.Actions.Pull - - // if we have no upstream branch we need to set that first - if !currentBranch.IsTrackingRemote() { - return self.c.Helpers().Upstream.PromptForUpstreamWithInitialContent(currentBranch, func(upstream string) error { - if err := self.setCurrentBranchUpstream(upstream); err != nil { - return err - } - - return self.PullAux(currentBranch, PullFilesOptions{Action: action}) - }) - } - - return self.PullAux(currentBranch, PullFilesOptions{Action: action}) -} - -func (self *SyncController) setCurrentBranchUpstream(upstream string) error { - upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream) - if err != nil { - return err - } - - if err := self.c.Git().Branch.SetCurrentBranchUpstream(upstreamRemote, upstreamBranch); err != nil { - if strings.Contains(err.Error(), "does not exist") { - return fmt.Errorf( - "upstream branch %s/%s not found.\nIf you expect it to exist, you should fetch (with 'f').\nOtherwise, you should push (with 'shift+P')", - upstreamRemote, upstreamBranch, - ) - } - return err - } - return nil -} - -type PullFilesOptions struct { - UpstreamRemote string - UpstreamBranch string - FastForwardOnly bool - Action string -} - -func (self *SyncController) PullAux(currentBranch *models.Branch, opts PullFilesOptions) error { - return self.c.WithInlineStatus(currentBranch, types.ItemOperationPulling, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error { - return self.pullWithLock(task, opts) - }) -} - -func (self *SyncController) pullWithLock(task gocui.Task, opts PullFilesOptions) error { - self.c.LogAction(opts.Action) - - err := self.c.Git().Sync.Pull( - task, - git_commands.PullOptions{ - RemoteName: opts.UpstreamRemote, - BranchName: opts.UpstreamBranch, - FastForwardOnly: opts.FastForwardOnly, - }, - ) - - return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) -} - -type pushOpts struct { - force bool - forceWithLease bool - upstreamRemote string - upstreamBranch string - setUpstream bool - - // If this is false, we can't tell ahead of time whether a force-push will - // be necessary, so we start with a normal push and offer to force-push if - // the server rejected. If this is true, we don't offer to force-push if the - // server rejected, but rather ask the user to fetch. - remoteBranchStoredLocally bool -} - -func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts) error { - return self.c.WithInlineStatus(currentBranch, types.ItemOperationPushing, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.Push) - err := self.c.Git().Sync.Push( - task, - git_commands.PushOpts{ - Force: opts.force, - ForceWithLease: opts.forceWithLease, - CurrentBranch: currentBranch.Name, - UpstreamRemote: opts.upstreamRemote, - UpstreamBranch: opts.upstreamBranch, - SetUpstream: opts.setUpstream, - }) - if err != nil { - if !opts.force && !opts.forceWithLease && strings.Contains(err.Error(), "Updates were rejected") { - if opts.remoteBranchStoredLocally { - return errors.New(self.c.Tr.UpdatesRejected) - } - - forcePushDisabled := self.c.UserConfig().Git.DisableForcePushing - if forcePushDisabled { - return errors.New(self.c.Tr.UpdatesRejectedAndForcePushDisabled) - } - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.ForcePush, - Prompt: self.forcePushPrompt(), - HandleConfirm: func() error { - newOpts := opts - newOpts.force = true - - return self.pushAux(currentBranch, newOpts) - }, - }) - return nil - } - return err - } - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return nil - }) -} - -func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opts pushOpts) error { - forcePushDisabled := self.c.UserConfig().Git.DisableForcePushing - if forcePushDisabled { - return errors.New(self.c.Tr.ForcePushDisabled) - } - - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.ForcePush, - Prompt: self.forcePushPrompt(), - HandleConfirm: func() error { - opts.forceWithLease = true - return self.pushAux(currentBranch, opts) - }, - }) - - return nil -} - -func (self *SyncController) forcePushPrompt() string { - return utils.ResolvePlaceholderString( - self.c.Tr.ForcePushPrompt, - map[string]string{ - "cancelKey": self.c.UserConfig().Keybinding.Universal.Return, - "confirmKey": self.c.UserConfig().Keybinding.Universal.Confirm, - }, - ) -} diff --git a/pkg/gui/controllers/tags_controller.go b/pkg/gui/controllers/tags_controller.go deleted file mode 100644 index c6fa60bd2cb..00000000000 --- a/pkg/gui/controllers/tags_controller.go +++ /dev/null @@ -1,359 +0,0 @@ -package controllers - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type TagsController struct { - baseController - *ListControllerTrait[*models.Tag] - c *ControllerCommon -} - -var _ types.IController = &TagsController{} - -func NewTagsController( - c *ControllerCommon, -) *TagsController { - return &TagsController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().Tags, - c.Contexts().Tags.GetSelected, - c.Contexts().Tags.GetSelectedItems, - ), - c: c, - } -} - -func (self *TagsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.withItem(self.checkout), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Checkout, - Tooltip: self.c.Tr.TagCheckoutTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.New), - Handler: self.create, - Description: self.c.Tr.NewTag, - Tooltip: self.c.Tr.NewTagTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: self.withItem(self.delete), - Description: self.c.Tr.Delete, - GetDisabledReason: self.require(self.singleItemSelected()), - Tooltip: self.c.Tr.TagDeleteTooltip, - OpensMenu: true, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Branches.PushTag), - Handler: self.withItem(self.push), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.PushTag, - Tooltip: self.c.Tr.PushTagTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Commits.ViewResetOptions), - Handler: self.withItem(self.createResetMenu), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Reset, - Tooltip: self.c.Tr.ResetTooltip, - DisplayOnScreen: true, - OpensMenu: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.OpenDiffTool), - Handler: self.withItem(func(selectedTag *models.Tag) error { - return self.c.Helpers().Diff.OpenDiffToolForRef(selectedTag) - }), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.OpenDiffTool, - }, - } - - return bindings -} - -func (self *TagsController) GetOnRenderToMain() func() { - return func() { - self.c.Helpers().Diff.WithDiffModeCheck(func() { - var task types.UpdateTask - tag := self.context().GetSelected() - if tag == nil { - task = types.NewRenderStringTask("No tags") - } else { - cmdObj := self.c.Git().Branch.GetGraphCmdObj(tag.FullRefName()) - prefix := self.getTagInfo(tag) + "\n\n---\n\n" - task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix) - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: "Tag", - Task: task, - }, - }) - }) - } -} - -func (self *TagsController) getTagInfo(tag *models.Tag) string { - tagIsAnnotated, err := self.c.Git().Tag.IsTagAnnotated(tag.Name) - if err != nil { - self.c.Log.Warnf("Error checking if tag is annotated: %v", err) - } - - if tagIsAnnotated { - info := fmt.Sprintf("%s: %s", self.c.Tr.AnnotatedTag, style.AttrBold.Sprint(style.FgYellow.Sprint(tag.Name))) - output, err := self.c.Git().Tag.ShowAnnotationInfo(tag.Name) - if err == nil { - info += "\n\n" + strings.TrimRight(filterOutPgpSignature(output), "\n") - } - return info - } - - return fmt.Sprintf("%s: %s", self.c.Tr.LightweightTag, style.AttrBold.Sprint(style.FgYellow.Sprint(tag.Name))) -} - -func filterOutPgpSignature(output string) string { - lines := strings.Split(output, "\n") - inPgpSignature := false - filteredLines := lo.Filter(lines, func(line string, _ int) bool { - if line == "-----END PGP SIGNATURE-----" { - inPgpSignature = false - return false - } - if line == "-----BEGIN PGP SIGNATURE-----" { - inPgpSignature = true - } - return !inPgpSignature - }) - return strings.Join(filteredLines, "\n") -} - -func (self *TagsController) checkout(tag *models.Tag) error { - self.c.LogAction(self.c.Tr.Actions.CheckoutTag) - if err := self.c.Helpers().Refs.CheckoutRef(tag.FullRefName(), types.CheckoutRefOptions{}); err != nil { - return err - } - self.c.Context().Push(self.c.Contexts().Branches, types.OnFocusOpts{}) - return nil -} - -func (self *TagsController) localDelete(tag *models.Tag) error { - return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.DeleteLocalTag) - err := self.c.Git().Tag.LocalDelete(tag.Name) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}}) - return err - }) -} - -func (self *TagsController) remoteDelete(tag *models.Tag) error { - title := utils.ResolvePlaceholderString( - self.c.Tr.SelectRemoteTagUpstream, - map[string]string{ - "tagName": tag.Name, - }, - ) - - self.c.Prompt(types.PromptOpts{ - Title: title, - InitialContent: "origin", - FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(), - HandleConfirm: func(upstream string) error { - confirmTitle := utils.ResolvePlaceholderString( - self.c.Tr.DeleteTagTitle, - map[string]string{ - "tagName": tag.Name, - }, - ) - confirmPrompt := utils.ResolvePlaceholderString( - self.c.Tr.DeleteRemoteTagPrompt, - map[string]string{ - "tagName": tag.Name, - "upstream": upstream, - }, - ) - - self.c.Confirm(types.ConfirmOpts{ - Title: confirmTitle, - Prompt: confirmPrompt, - HandleConfirm: func() error { - return self.c.WithInlineStatus(tag, types.ItemOperationDeleting, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.DeleteRemoteTag) - if err := self.c.Git().Remote.DeleteRemoteTag(task, upstream, tag.Name); err != nil { - return err - } - self.c.Toast(self.c.Tr.RemoteTagDeletedMessage) - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}}) - return nil - }) - }, - }) - - return nil - }, - }) - - return nil -} - -func (self *TagsController) localAndRemoteDelete(tag *models.Tag) error { - title := utils.ResolvePlaceholderString( - self.c.Tr.SelectRemoteTagUpstream, - map[string]string{ - "tagName": tag.Name, - }, - ) - - self.c.Prompt(types.PromptOpts{ - Title: title, - InitialContent: "origin", - FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(), - HandleConfirm: func(upstream string) error { - confirmTitle := utils.ResolvePlaceholderString( - self.c.Tr.DeleteTagTitle, - map[string]string{ - "tagName": tag.Name, - }, - ) - confirmPrompt := utils.ResolvePlaceholderString( - self.c.Tr.DeleteLocalAndRemoteTagPrompt, - map[string]string{ - "tagName": tag.Name, - "upstream": upstream, - }, - ) - - self.c.Confirm(types.ConfirmOpts{ - Title: confirmTitle, - Prompt: confirmPrompt, - HandleConfirm: func() error { - return self.c.WithInlineStatus(tag, types.ItemOperationDeleting, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.DeleteRemoteTag) - if err := self.c.Git().Remote.DeleteRemoteTag(task, upstream, tag.Name); err != nil { - return err - } - - self.c.LogAction(self.c.Tr.Actions.DeleteLocalTag) - if err := self.c.Git().Tag.LocalDelete(tag.Name); err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}}) - return nil - }) - }, - }) - - return nil - }, - }) - - return nil -} - -func (self *TagsController) delete(tag *models.Tag) error { - menuTitle := utils.ResolvePlaceholderString( - self.c.Tr.DeleteTagTitle, - map[string]string{ - "tagName": tag.Name, - }, - ) - - menuItems := []*types.MenuItem{ - { - Label: self.c.Tr.DeleteLocalTag, - Key: 'c', - OnPress: func() error { - return self.localDelete(tag) - }, - }, - { - Label: self.c.Tr.DeleteRemoteTag, - Key: 'r', - OpensMenu: true, - OnPress: func() error { - return self.remoteDelete(tag) - }, - }, - { - Label: self.c.Tr.DeleteLocalAndRemoteTag, - Key: 'b', - OpensMenu: true, - OnPress: func() error { - return self.localAndRemoteDelete(tag) - }, - }, - } - - return self.c.Menu(types.CreateMenuOptions{ - Title: menuTitle, - Items: menuItems, - }) -} - -func (self *TagsController) push(tag *models.Tag) error { - title := utils.ResolvePlaceholderString( - self.c.Tr.PushTagTitle, - map[string]string{ - "tagName": tag.Name, - }, - ) - - self.c.Prompt(types.PromptOpts{ - Title: title, - InitialContent: "origin", - FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(), - HandleConfirm: func(response string) error { - return self.c.WithInlineStatus(tag, types.ItemOperationPushing, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.PushTag) - err := self.c.Git().Tag.Push(task, response, tag.Name) - - // Render again to remove the inline status: - self.c.OnUIThread(func() error { - self.c.Contexts().Tags.HandleRender() - return nil - }) - - return err - }) - }, - }) - - return nil -} - -func (self *TagsController) createResetMenu(tag *models.Tag) error { - return self.c.Helpers().Refs.CreateGitResetMenu(tag.Name, tag.FullRefName()) -} - -func (self *TagsController) create() error { - // leaving commit hash blank so that we're just creating the tag for the current commit - return self.c.Helpers().Tags.OpenCreateTagPrompt("", func() { - self.context().SetSelection(0) - }) -} - -func (self *TagsController) context() *context.TagsContext { - return self.c.Contexts().Tags -} diff --git a/pkg/gui/controllers/toggle_whitespace_action.go b/pkg/gui/controllers/toggle_whitespace_action.go deleted file mode 100644 index a1ac0c8da6b..00000000000 --- a/pkg/gui/controllers/toggle_whitespace_action.go +++ /dev/null @@ -1,32 +0,0 @@ -package controllers - -import ( - "errors" - - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type ToggleWhitespaceAction struct { - c *ControllerCommon -} - -func (self *ToggleWhitespaceAction) Call() error { - contextsThatDontSupportIgnoringWhitespace := []types.ContextKey{ - context.STAGING_MAIN_CONTEXT_KEY, - context.STAGING_SECONDARY_CONTEXT_KEY, - context.PATCH_BUILDING_MAIN_CONTEXT_KEY, - } - - if lo.Contains(contextsThatDontSupportIgnoringWhitespace, self.c.Context().Current().GetKey()) { - // Ignoring whitespace is not supported in these views. Let the user - // know that it's not going to work in case they try to turn it on. - return errors.New(self.c.Tr.IgnoreWhitespaceNotSupportedHere) - } - - self.c.UserConfig().Git.IgnoreWhitespaceInDiffView = !self.c.UserConfig().Git.IgnoreWhitespaceInDiffView - - self.c.Context().CurrentSide().HandleFocus(types.OnFocusOpts{}) - return nil -} diff --git a/pkg/gui/controllers/undo_controller.go b/pkg/gui/controllers/undo_controller.go deleted file mode 100644 index cdc8a128025..00000000000 --- a/pkg/gui/controllers/undo_controller.go +++ /dev/null @@ -1,282 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// Quick summary of how this all works: -// when you want to undo or redo, we start from the top of the reflog and work -// down until we've reached the last user-initiated reflog entry that hasn't already been undone -// we then do the reverse of what that reflog describes. -// When we do this, we create a new reflog entry, and tag it as either an undo or redo -// Then, next time we want to undo, we'll use those entries to know which user-initiated -// actions we can skip. E.g. if I do three things, A, B, and C, and hit undo twice, -// the reflog will read UUCBA, and when I read the first two undos, I know to skip the following -// two user actions, meaning we end up undoing reflog entry C. Redoing works in a similar way. - -type UndoController struct { - baseController - c *ControllerCommon -} - -var _ types.IController = &UndoController{} - -func NewUndoController( - c *ControllerCommon, -) *UndoController { - return &UndoController{ - baseController: baseController{}, - c: c, - } -} - -type ReflogActionKind int - -const ( - CHECKOUT ReflogActionKind = iota - COMMIT - REBASE - CURRENT_REBASE -) - -type reflogAction struct { - kind ReflogActionKind - from string - to string -} - -func (self *UndoController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.Undo), - Handler: self.reflogUndo, - Description: self.c.Tr.UndoReflog, - Tooltip: self.c.Tr.UndoTooltip, - }, - { - Key: opts.GetKey(opts.Config.Universal.Redo), - Handler: self.reflogRedo, - Description: self.c.Tr.RedoReflog, - Tooltip: self.c.Tr.RedoTooltip, - }, - } - - return bindings -} - -func (self *UndoController) Context() types.Context { - return nil -} - -func (self *UndoController) reflogUndo() error { - undoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit undo]"} - undoingStatus := self.c.Tr.UndoingStatus - - if self.c.Git().Status.WorkingTreeState().Any() { - return errors.New(self.c.Tr.CantUndoWhileRebasing) - } - - return self.parseReflogForActions(func(counter int, action reflogAction) (bool, error) { - if counter != 0 { - return false, nil - } - - switch action.kind { - case COMMIT: - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Actions.Undo, - Prompt: fmt.Sprintf(self.c.Tr.SoftResetPrompt, utils.ShortHash(action.from)), - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.Undo) - return self.c.WithWaitingStatus(undoingStatus, func(gocui.Task) error { - return self.c.Helpers().Refs.ResetToRef(action.from, "soft", undoEnvVars) - }) - }, - }) - return true, nil - - case REBASE: - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Actions.Undo, - Prompt: fmt.Sprintf(self.c.Tr.HardResetAutostashPrompt, utils.ShortHash(action.from)), - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.Undo) - return self.hardResetWithAutoStash(action.from, hardResetOptions{ - EnvVars: undoEnvVars, - WaitingStatus: undoingStatus, - }) - }, - }) - return true, nil - - case CHECKOUT: - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Actions.Undo, - Prompt: fmt.Sprintf(self.c.Tr.CheckoutAutostashPrompt, action.from), - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.Undo) - return self.c.Helpers().Refs.CheckoutRef(action.from, types.CheckoutRefOptions{ - EnvVars: undoEnvVars, - WaitingStatus: undoingStatus, - }) - }, - }) - return true, nil - - case CURRENT_REBASE: - // do nothing - } - - self.c.Log.Error("didn't match on the user action when trying to undo") - return true, nil - }) -} - -func (self *UndoController) reflogRedo() error { - redoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit redo]"} - redoingStatus := self.c.Tr.RedoingStatus - - if self.c.Git().Status.WorkingTreeState().Any() { - return errors.New(self.c.Tr.CantRedoWhileRebasing) - } - - return self.parseReflogForActions(func(counter int, action reflogAction) (bool, error) { - // if we're redoing and the counter is zero, we just return - if counter == 0 { - return true, nil - } else if counter > 1 { - return false, nil - } - - switch action.kind { - case COMMIT, REBASE: - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Actions.Redo, - Prompt: fmt.Sprintf(self.c.Tr.HardResetAutostashPrompt, utils.ShortHash(action.to)), - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.Redo) - return self.hardResetWithAutoStash(action.to, hardResetOptions{ - EnvVars: redoEnvVars, - WaitingStatus: redoingStatus, - }) - }, - }) - return true, nil - - case CHECKOUT: - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Actions.Redo, - Prompt: fmt.Sprintf(self.c.Tr.CheckoutAutostashPrompt, action.to), - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.Redo) - return self.c.Helpers().Refs.CheckoutRef(action.to, types.CheckoutRefOptions{ - EnvVars: redoEnvVars, - WaitingStatus: redoingStatus, - }) - }, - }) - return true, nil - - case CURRENT_REBASE: - // do nothing - } - - self.c.Log.Error("didn't match on the user action when trying to redo") - return true, nil - }) -} - -// Here we're going through the reflog and maintaining a counter that represents how many -// undos/redos/user actions we've seen. when we hit a user action we call the callback specifying -// what the counter is up to and the nature of the action. -// If we find ourselves mid-rebase, we just return because undo/redo mid rebase -// requires knowledge of previous TODO file states, which you can't just get from the reflog. -// Though we might support this later, hence the use of the CURRENT_REBASE action kind. -func (self *UndoController) parseReflogForActions(onUserAction func(counter int, action reflogAction) (bool, error)) error { - counter := 0 - reflogCommits := self.c.Model().ReflogCommits - rebaseFinishCommitHash := "" - var action *reflogAction - for reflogCommitIdx, reflogCommit := range reflogCommits { - action = nil - - prevCommitHash := "" - if len(reflogCommits)-1 >= reflogCommitIdx+1 { - prevCommitHash = reflogCommits[reflogCommitIdx+1].Hash() - } - - if rebaseFinishCommitHash == "" { - if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^\[lazygit undo\]`); ok { - counter++ - } else if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^\[lazygit redo\]`); ok { - counter-- - } else if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^rebase (-i )?\(abort\)|^rebase (-i )?\(finish\)`); ok { - rebaseFinishCommitHash = reflogCommit.Hash() - } else if ok, match := utils.FindStringSubmatch(reflogCommit.Name, `^checkout: moving from ([\S]+) to ([\S]+)`); ok { - action = &reflogAction{kind: CHECKOUT, from: match[1], to: match[2]} - } else if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^commit|^reset: moving to|^pull`); ok { - action = &reflogAction{kind: COMMIT, from: prevCommitHash, to: reflogCommit.Hash()} - } else if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^rebase (-i )?\(start\)`); ok { - // if we're here then we must be currently inside an interactive rebase - action = &reflogAction{kind: CURRENT_REBASE, from: prevCommitHash} - } - } else if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^rebase (-i )?\(start\)`); ok { - action = &reflogAction{kind: REBASE, from: prevCommitHash, to: rebaseFinishCommitHash} - rebaseFinishCommitHash = "" - } - - if action != nil { - if action.kind != CURRENT_REBASE && action.from == action.to { - // if we're going from one place to the same place we'll ignore the action. - continue - } - ok, err := onUserAction(counter, *action) - if ok { - return err - } - counter-- - } - } - return nil -} - -type hardResetOptions struct { - WaitingStatus string - EnvVars []string -} - -// only to be used in the undo flow for now (does an autostash) -func (self *UndoController) hardResetWithAutoStash(commitHash string, options hardResetOptions) error { - reset := func() error { - return self.c.Helpers().Refs.ResetToRef(commitHash, "hard", options.EnvVars) - } - - // if we have any modified tracked files we need to auto-stash - dirtyWorkingTree := self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules() - if dirtyWorkingTree { - return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error { - if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForUndo, utils.ShortHash(commitHash))); err != nil { - return err - } - if err := reset(); err != nil { - return err - } - - err := self.c.Git().Stash.Pop(0) - if err != nil { - return err - } - self.c.Refresh(types.RefreshOptions{}) - return nil - }) - } - - return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error { - return reset() - }) -} diff --git a/pkg/gui/controllers/vertical_scroll_controller.go b/pkg/gui/controllers/vertical_scroll_controller.go deleted file mode 100644 index b88574451cb..00000000000 --- a/pkg/gui/controllers/vertical_scroll_controller.go +++ /dev/null @@ -1,77 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -// given we have no fields here, arguably we shouldn't even need this factory -// struct, but we're maintaining consistency with the other files. -type VerticalScrollControllerFactory struct { - c *ControllerCommon -} - -func NewVerticalScrollControllerFactory(c *ControllerCommon) *VerticalScrollControllerFactory { - return &VerticalScrollControllerFactory{ - c: c, - } -} - -func (self *VerticalScrollControllerFactory) Create(context types.Context) types.IController { - return &VerticalScrollController{ - baseController: baseController{}, - c: self.c, - context: context, - } -} - -type VerticalScrollController struct { - baseController - c *ControllerCommon - - context types.Context -} - -func (self *VerticalScrollController) Context() types.Context { - return self.context -} - -func (self *VerticalScrollController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{} -} - -func (self *VerticalScrollController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: self.context.GetViewName(), - Key: gocui.MouseWheelUp, - Handler: func(gocui.ViewMouseBindingOpts) error { - return self.HandleScrollUp() - }, - }, - { - ViewName: self.context.GetViewName(), - Key: gocui.MouseWheelDown, - Handler: func(gocui.ViewMouseBindingOpts) error { - return self.HandleScrollDown() - }, - }, - } -} - -func (self *VerticalScrollController) HandleScrollUp() error { - self.context.GetViewTrait().ScrollUp(self.c.UserConfig().Gui.ScrollHeight) - - return nil -} - -func (self *VerticalScrollController) HandleScrollDown() error { - scrollHeight := self.c.UserConfig().Gui.ScrollHeight - self.context.GetViewTrait().ScrollDown(scrollHeight) - - if manager := self.c.GetViewBufferManagerForView(self.context.GetView()); manager != nil { - manager.ReadLines(scrollHeight) - } - - return nil -} diff --git a/pkg/gui/controllers/view_selection_controller.go b/pkg/gui/controllers/view_selection_controller.go deleted file mode 100644 index 638c46ba608..00000000000 --- a/pkg/gui/controllers/view_selection_controller.go +++ /dev/null @@ -1,109 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type ViewSelectionControllerFactory struct { - c *ControllerCommon -} - -func NewViewSelectionControllerFactory(c *ControllerCommon) *ViewSelectionControllerFactory { - return &ViewSelectionControllerFactory{ - c: c, - } -} - -func (self *ViewSelectionControllerFactory) Create(context types.Context) types.IController { - return &ViewSelectionController{ - baseController: baseController{}, - c: self.c, - context: context, - } -} - -type ViewSelectionController struct { - baseController - c *ControllerCommon - - context types.Context -} - -func (self *ViewSelectionController) Context() types.Context { - return self.context -} - -func (self *ViewSelectionController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItem), Handler: self.handlePrevLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), Handler: self.handlePrevLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItem), Handler: self.handleNextLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItemAlt), Handler: self.handleNextLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevPage), Handler: self.handlePrevPage, Description: self.c.Tr.PrevPage}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextPage), Handler: self.handleNextPage, Description: self.c.Tr.NextPage}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoTop), Handler: self.handleGotoTop, Description: self.c.Tr.GotoTop, Alternative: ""}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoBottom), Handler: self.handleGotoBottom, Description: self.c.Tr.GotoBottom, Alternative: ""}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoTopAlt), Handler: self.handleGotoTop}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoBottomAlt), Handler: self.handleGotoBottom}, - } -} - -func (self *ViewSelectionController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{} -} - -func (self *ViewSelectionController) handleLineChange(delta int) { - if delta > 0 { - if manager := self.c.GetViewBufferManagerForView(self.context.GetView()); manager != nil { - manager.ReadLines(delta) - } - } - - v := self.Context().GetView() - if delta < 0 { - v.ScrollUp(-delta) - } else { - v.ScrollDown(delta) - } -} - -func (self *ViewSelectionController) handlePrevLine() error { - self.handleLineChange(-1) - return nil -} - -func (self *ViewSelectionController) handleNextLine() error { - self.handleLineChange(1) - return nil -} - -func (self *ViewSelectionController) handlePrevPage() error { - self.handleLineChange(-self.context.GetViewTrait().PageDelta()) - return nil -} - -func (self *ViewSelectionController) handleNextPage() error { - self.handleLineChange(self.context.GetViewTrait().PageDelta()) - return nil -} - -func (self *ViewSelectionController) handleGotoTop() error { - v := self.Context().GetView() - self.handleLineChange(-v.ViewLinesHeight()) - return nil -} - -func (self *ViewSelectionController) handleGotoBottom() error { - if manager := self.c.GetViewBufferManagerForView(self.context.GetView()); manager != nil { - manager.ReadToEnd(func() { - self.c.OnUIThread(func() error { - v := self.Context().GetView() - self.handleLineChange(v.ViewLinesHeight()) - return nil - }) - }) - } - - return nil -} diff --git a/pkg/gui/controllers/workspace_reset_controller.go b/pkg/gui/controllers/workspace_reset_controller.go deleted file mode 100644 index 82357922f4a..00000000000 --- a/pkg/gui/controllers/workspace_reset_controller.go +++ /dev/null @@ -1,282 +0,0 @@ -package controllers - -import ( - "bytes" - "errors" - "fmt" - "math" - "math/rand" - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -// this is in its own file given that the workspace controller file is already quite long - -func (self *FilesController) createResetMenu() error { - red := style.FgRed - - nukeStr := "git reset --hard HEAD && git clean -fd" - if len(self.c.Model().Submodules) > 0 { - nukeStr = fmt.Sprintf("%s (%s)", nukeStr, self.c.Tr.AndResetSubmodules) - } - - menuItems := []*types.MenuItem{ - { - LabelColumns: []string{ - self.c.Tr.DiscardAllChangesToAllFiles, - red.Sprint(nukeStr), - }, - OnPress: func() error { - self.c.Confirm( - types.ConfirmOpts{ - Title: self.c.Tr.Actions.NukeWorkingTree, - Prompt: self.c.Tr.NukeTreeConfirmation, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.NukeWorkingTree) - if err := self.c.Git().WorkingTree.ResetAndClean(); err != nil { - return err - } - - if self.c.UserConfig().Gui.AnimateExplosion { - self.animateExplosion() - } - - self.c.Refresh( - types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}}, - ) - return nil - }, - }) - return nil - }, - Key: 'x', - Tooltip: self.c.Tr.NukeDescription, - }, - { - LabelColumns: []string{ - self.c.Tr.DiscardAnyUnstagedChanges, - red.Sprint("git checkout -- ."), - }, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.DiscardUnstagedFileChanges) - if err := self.c.Git().WorkingTree.DiscardAnyUnstagedFileChanges(); err != nil { - return err - } - - self.c.Refresh( - types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}}, - ) - return nil - }, - Key: 'u', - }, - { - LabelColumns: []string{ - self.c.Tr.DiscardUntrackedFiles, - red.Sprint("git clean -fd"), - }, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.RemoveUntrackedFiles) - if err := self.c.Git().WorkingTree.RemoveUntrackedFiles(); err != nil { - return err - } - - self.c.Refresh( - types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}}, - ) - return nil - }, - Key: 'c', - }, - { - LabelColumns: []string{ - self.c.Tr.DiscardStagedChanges, - red.Sprint("stash staged and drop stash"), - }, - Tooltip: self.c.Tr.DiscardStagedChangesDescription, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.RemoveStagedFiles) - if !self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules() { - return errors.New(self.c.Tr.NoTrackedStagedFilesStash) - } - if err := self.c.Git().Stash.SaveStagedChanges("[lazygit] tmp stash"); err != nil { - return err - } - if err := self.c.Git().Stash.DropNewest(); err != nil { - return err - } - - self.c.Refresh( - types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}}, - ) - return nil - }, - Key: 'S', - }, - { - LabelColumns: []string{ - self.c.Tr.SoftReset, - red.Sprint("git reset --soft HEAD"), - }, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.SoftReset) - if err := self.c.Git().WorkingTree.ResetSoft("HEAD"); err != nil { - return err - } - - self.c.Refresh( - types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}}, - ) - return nil - }, - Key: 's', - }, - { - LabelColumns: []string{ - "mixed reset", - red.Sprint("git reset --mixed HEAD"), - }, - OnPress: func() error { - self.c.LogAction(self.c.Tr.Actions.MixedReset) - if err := self.c.Git().WorkingTree.ResetMixed("HEAD"); err != nil { - return err - } - - self.c.Refresh( - types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}}, - ) - return nil - }, - Key: 'm', - }, - { - LabelColumns: []string{ - self.c.Tr.HardReset, - red.Sprint("git reset --hard HEAD"), - }, - OnPress: func() error { - return self.c.ConfirmIf(helpers.IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules), - types.ConfirmOpts{ - Title: self.c.Tr.Actions.HardReset, - Prompt: self.c.Tr.ResetHardConfirmation, - HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.HardReset) - if err := self.c.Git().WorkingTree.ResetHard("HEAD"); err != nil { - return err - } - - self.c.Refresh( - types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}}, - ) - return nil - }, - }) - }, - Key: 'h', - }, - } - - return self.c.Menu(types.CreateMenuOptions{Title: "", Items: menuItems}) -} - -func (self *FilesController) animateExplosion() { - self.Explode(self.c.Views().Files, func() { - self.c.PostRefreshUpdate(self.c.Contexts().Files) - }) -} - -// Animates an explosion within the view by drawing a bunch of flamey characters -func (self *FilesController) Explode(v *gocui.View, onDone func()) { - width := v.InnerWidth() - height := v.InnerHeight() - styles := []style.TextStyle{ - style.FgLightWhite.SetBold(), - style.FgYellow.SetBold(), - style.FgRed.SetBold(), - style.FgBlue.SetBold(), - style.FgBlack.SetBold(), - } - - self.c.OnWorker(func(_ gocui.Task) error { - max := 25 - for i := range max { - image := getExplodeImage(width, height, i, max) - style := styles[(i*len(styles)/max)%len(styles)] - coloredImage := style.Sprint(image) - self.c.OnUIThread(func() error { - v.SetOrigin(0, 0) - v.SetContent(coloredImage) - return nil - }) - time.Sleep(time.Millisecond * 20) - } - self.c.OnUIThread(func() error { - v.Clear() - onDone() - return nil - }) - return nil - }) -} - -// Render an explosion in the given bounds. -func getExplodeImage(width int, height int, frame int, max int) string { - // Predefine the explosion symbols - explosionChars := []rune{'*', '.', '@', '#', '&', '+', '%'} - - // Initialize a buffer to build our string - var buf bytes.Buffer - - // Initialize RNG seed - random := rand.New(rand.NewSource(time.Now().UnixNano())) - - // calculate the center of explosion - centerX, centerY := width/2, height/2 - - // calculate the max radius (hypotenuse of the view) - maxRadius := math.Hypot(float64(centerX), float64(centerY)) - - // calculate frame as a proportion of max, apply square root to create the non-linear effect - progress := math.Sqrt(float64(frame) / float64(max)) - - // calculate radius of explosion according to frame and max - radius := progress * maxRadius * 2 - - // introduce a new radius for the inner boundary of the explosion (the shockwave effect) - var innerRadius float64 - if progress > 0.5 { - innerRadius = (progress - 0.5) * 2 * maxRadius - } - - for y := range height { - for x := range width { - // calculate distance from center, scale x by 2 to compensate for character aspect ratio - distance := math.Hypot(float64(x-centerX), float64(y-centerY)*2) - - // if distance is less than radius and greater than innerRadius, draw explosion char - if distance <= radius && distance >= innerRadius { - // Make placement random and less likely as explosion progresses - if random.Float64() > progress { - // Pick a random explosion char - char := explosionChars[random.Intn(len(explosionChars))] - buf.WriteRune(char) - } else { - buf.WriteRune(' ') - } - } else { - // If not explosion, then it's empty space - buf.WriteRune(' ') - } - } - // End of line - if y < height-1 { - buf.WriteRune('\n') - } - } - - return buf.String() -} diff --git a/pkg/gui/controllers/worktree_options_controller.go b/pkg/gui/controllers/worktree_options_controller.go deleted file mode 100644 index 0cdf4d0081b..00000000000 --- a/pkg/gui/controllers/worktree_options_controller.go +++ /dev/null @@ -1,51 +0,0 @@ -package controllers - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -// This controller is for all contexts that have items you can create a worktree from - -var _ types.IController = &WorktreeOptionsController{} - -type CanViewWorktreeOptions interface { - types.IListContext -} - -type WorktreeOptionsController struct { - baseController - *ListControllerTrait[string] - c *ControllerCommon - context CanViewWorktreeOptions -} - -func NewWorktreeOptionsController(c *ControllerCommon, context CanViewWorktreeOptions) *WorktreeOptionsController { - return &WorktreeOptionsController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - context, - context.GetSelectedItemId, - context.GetSelectedItemIds, - ), - c: c, - context: context, - } -} - -func (self *WorktreeOptionsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Worktrees.ViewWorktreeOptions), - Handler: self.withItem(self.viewWorktreeOptions), - Description: self.c.Tr.ViewWorktreeOptions, - OpensMenu: true, - }, - } - - return bindings -} - -func (self *WorktreeOptionsController) viewWorktreeOptions(ref string) error { - return self.c.Helpers().Worktree.ViewWorktreeOptions(self.context, ref) -} diff --git a/pkg/gui/controllers/worktrees_controller.go b/pkg/gui/controllers/worktrees_controller.go deleted file mode 100644 index d3f44e4ada7..00000000000 --- a/pkg/gui/controllers/worktrees_controller.go +++ /dev/null @@ -1,145 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - "strings" - "text/tabwriter" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type WorktreesController struct { - baseController - *ListControllerTrait[*models.Worktree] - c *ControllerCommon -} - -var _ types.IController = &WorktreesController{} - -func NewWorktreesController( - c *ControllerCommon, -) *WorktreesController { - return &WorktreesController{ - baseController: baseController{}, - ListControllerTrait: NewListControllerTrait( - c, - c.Contexts().Worktrees, - c.Contexts().Worktrees.GetSelected, - c.Contexts().Worktrees.GetSelectedItems, - ), - c: c, - } -} - -func (self *WorktreesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { - bindings := []*types.Binding{ - { - Key: opts.GetKey(opts.Config.Universal.New), - Handler: self.add, - Description: self.c.Tr.NewWorktree, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.Select), - Handler: self.withItem(self.enter), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Switch, - Tooltip: self.c.Tr.SwitchToWorktreeTooltip, - DisplayOnScreen: true, - }, - { - Key: opts.GetKey(opts.Config.Universal.GoInto), - Handler: self.withItem(self.enter), - GetDisabledReason: self.require(self.singleItemSelected()), - }, - { - Key: opts.GetKey(opts.Config.Universal.OpenFile), - Handler: self.withItem(self.open), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.OpenInEditor, - }, - { - Key: opts.GetKey(opts.Config.Universal.Remove), - Handler: self.withItem(self.remove), - GetDisabledReason: self.require(self.singleItemSelected()), - Description: self.c.Tr.Remove, - Tooltip: self.c.Tr.RemoveWorktreeTooltip, - DisplayOnScreen: true, - }, - } - - return bindings -} - -func (self *WorktreesController) GetOnRenderToMain() func() { - return func() { - var task types.UpdateTask - worktree := self.context().GetSelected() - if worktree == nil { - task = types.NewRenderStringTask(self.c.Tr.NoWorktreesThisRepo) - } else { - main := "" - if worktree.IsMain { - main = style.FgDefault.Sprintf(" %s", self.c.Tr.MainWorktree) - } - - missing := "" - if worktree.IsPathMissing { - missing = style.FgRed.Sprintf(" %s", self.c.Tr.MissingWorktree) - } - - var builder strings.Builder - w := tabwriter.NewWriter(&builder, 0, 0, 2, ' ', 0) - _, _ = fmt.Fprintf(w, "%s:\t%s%s\n", self.c.Tr.Name, style.FgGreen.Sprint(worktree.Name), main) - _, _ = fmt.Fprintf(w, "%s:\t%s\n", self.c.Tr.Branch, style.FgYellow.Sprint(worktree.Branch)) - _, _ = fmt.Fprintf(w, "%s:\t%s%s\n", self.c.Tr.Path, style.FgCyan.Sprint(worktree.Path), missing) - _ = w.Flush() - - task = types.NewRenderStringTask(builder.String()) - } - - self.c.RenderToMainViews(types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ - Title: self.c.Tr.WorktreeTitle, - Task: task, - }, - }) - } -} - -func (self *WorktreesController) add() error { - return self.c.Helpers().Worktree.NewWorktree() -} - -func (self *WorktreesController) remove(worktree *models.Worktree) error { - if worktree.IsMain { - return errors.New(self.c.Tr.CantDeleteMainWorktree) - } - - if worktree.IsCurrent { - return errors.New(self.c.Tr.CantDeleteCurrentWorktree) - } - - return self.c.Helpers().Worktree.Remove(worktree, false) -} - -func (self *WorktreesController) GetOnClick() func() error { - return self.withItemGraceful(self.enter) -} - -func (self *WorktreesController) enter(worktree *models.Worktree) error { - return self.c.Helpers().Worktree.Switch(worktree, context.WORKTREES_CONTEXT_KEY) -} - -func (self *WorktreesController) open(worktree *models.Worktree) error { - return self.c.Helpers().Files.OpenDirInEditor(worktree.Path) -} - -func (self *WorktreesController) context() *context.WorktreesContext { - return self.c.Contexts().Worktrees -} diff --git a/pkg/gui/dummies.go b/pkg/gui/dummies.go deleted file mode 100644 index 979f818e1a8..00000000000 --- a/pkg/gui/dummies.go +++ /dev/null @@ -1,22 +0,0 @@ -package gui - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/updates" -) - -func NewDummyUpdater() *updates.Updater { - newAppConfig := config.NewDummyAppConfig() - dummyUpdater, _ := updates.NewUpdater(common.NewDummyCommon(), newAppConfig, oscommands.NewDummyOSCommand()) - return dummyUpdater -} - -// NewDummyGui creates a new dummy GUI for testing -func NewDummyGui() *Gui { - newAppConfig := config.NewDummyAppConfig() - dummyGui, _ := NewGui(common.NewDummyCommon(), newAppConfig, &git_commands.GitVersion{Major: 2, Minor: 0, Patch: 0}, NewDummyUpdater(), false, "", nil) - return dummyGui -} diff --git a/pkg/gui/editors.go b/pkg/gui/editors.go deleted file mode 100644 index 6345c93cf15..00000000000 --- a/pkg/gui/editors.go +++ /dev/null @@ -1,101 +0,0 @@ -package gui - -import ( - "unicode" - - "github.com/jesseduffield/gocui" -) - -func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch rune, mod gocui.Modifier, allowMultiline bool) bool { - switch { - case (key == gocui.KeyBackspace || key == gocui.KeyBackspace2) && (mod&gocui.ModAlt) != 0, - key == gocui.KeyCtrlW: - textArea.BackSpaceWord() - case key == gocui.KeyBackspace || key == gocui.KeyBackspace2: - textArea.BackSpaceChar() - case key == gocui.KeyCtrlD || key == gocui.KeyDelete: - textArea.DeleteChar() - case key == gocui.KeyArrowDown: - textArea.MoveCursorDown() - case key == gocui.KeyArrowUp: - textArea.MoveCursorUp() - case (key == gocui.KeyArrowLeft || ch == 'b') && (mod&gocui.ModAlt) != 0: - textArea.MoveLeftWord() - case key == gocui.KeyArrowLeft || key == gocui.KeyCtrlB: - textArea.MoveCursorLeft() - case (key == gocui.KeyArrowRight || ch == 'f') && (mod&gocui.ModAlt) != 0: - textArea.MoveRightWord() - case key == gocui.KeyArrowRight || key == gocui.KeyCtrlF: - textArea.MoveCursorRight() - case key == gocui.KeyEnter: - if allowMultiline { - textArea.TypeRune('\n') - } else { - return false - } - case key == gocui.KeySpace: - textArea.TypeRune(' ') - case key == gocui.KeyInsert: - textArea.ToggleOverwrite() - case key == gocui.KeyCtrlU: - textArea.DeleteToStartOfLine() - case key == gocui.KeyCtrlK: - textArea.DeleteToEndOfLine() - case key == gocui.KeyCtrlA || key == gocui.KeyHome: - textArea.GoToStartOfLine() - case key == gocui.KeyCtrlE || key == gocui.KeyEnd: - textArea.GoToEndOfLine() - case key == gocui.KeyCtrlY: - textArea.Yank() - - case unicode.IsPrint(ch): - textArea.TypeRune(ch) - default: - return false - } - - return true -} - -// we've just copy+pasted the editor from gocui to here so that we can also re- -// render the commit message length on each keypress -func (gui *Gui) commitMessageEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool { - matched := gui.handleEditorKeypress(v.TextArea, key, ch, mod, false) - v.RenderTextArea() - gui.c.Contexts().CommitMessage.RenderSubtitle() - return matched -} - -func (gui *Gui) commitDescriptionEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool { - matched := gui.handleEditorKeypress(v.TextArea, key, ch, mod, true) - v.RenderTextArea() - return matched -} - -func (gui *Gui) promptEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool { - matched := gui.handleEditorKeypress(v.TextArea, key, ch, mod, false) - - v.RenderTextArea() - - suggestionsContext := gui.State.Contexts.Suggestions - if suggestionsContext.State.FindSuggestions != nil { - input := v.TextArea.GetContent() - suggestionsContext.State.AsyncHandler.Do(func() func() { - suggestions := suggestionsContext.State.FindSuggestions(input) - return func() { suggestionsContext.SetSuggestions(suggestions) } - }) - } - - return matched -} - -func (gui *Gui) searchEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool { - matched := gui.handleEditorKeypress(v.TextArea, key, ch, mod, false) - v.RenderTextArea() - - searchString := v.TextArea.GetContent() - - gui.helpers.Search.OnPromptContentChanged(searchString) - - return matched -} diff --git a/pkg/gui/extras_panel.go b/pkg/gui/extras_panel.go deleted file mode 100644 index 0c3f20cd5c1..00000000000 --- a/pkg/gui/extras_panel.go +++ /dev/null @@ -1,116 +0,0 @@ -package gui - -import ( - "io" - - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -func (gui *Gui) handleCreateExtrasMenuPanel() error { - return gui.c.Menu(types.CreateMenuOptions{ - Title: gui.c.Tr.CommandLog, - Items: []*types.MenuItem{ - { - Label: gui.c.Tr.ToggleShowCommandLog, - OnPress: func() error { - currentContext := gui.c.Context().CurrentStatic() - if gui.c.State().GetShowExtrasWindow() && currentContext.GetKey() == context.COMMAND_LOG_CONTEXT_KEY { - gui.c.Context().Pop() - } - show := !gui.c.State().GetShowExtrasWindow() - gui.c.State().SetShowExtrasWindow(show) - gui.c.GetAppState().HideCommandLog = !show - gui.c.SaveAppStateAndLogError() - return nil - }, - }, - { - Label: gui.c.Tr.FocusCommandLog, - OnPress: gui.handleFocusCommandLog, - }, - }, - }) -} - -func (gui *Gui) handleFocusCommandLog() error { - gui.c.State().SetShowExtrasWindow(true) - // TODO: is this necessary? Can't I just call 'return from context'? - gui.State.Contexts.CommandLog.SetParentContext(gui.c.Context().CurrentSide()) - gui.c.Context().Push(gui.State.Contexts.CommandLog, types.OnFocusOpts{}) - return nil -} - -func (gui *Gui) scrollUpExtra() error { - gui.Views.Extras.Autoscroll = false - - gui.scrollUpView(gui.Views.Extras) - - return nil -} - -func (gui *Gui) scrollDownExtra() error { - gui.Views.Extras.Autoscroll = false - - gui.scrollDownView(gui.Views.Extras) - - return nil -} - -func (gui *Gui) pageUpExtrasPanel() error { - gui.Views.Extras.Autoscroll = false - - gui.Views.Extras.ScrollUp(gui.Contexts().CommandLog.GetViewTrait().PageDelta()) - - return nil -} - -func (gui *Gui) pageDownExtrasPanel() error { - gui.Views.Extras.Autoscroll = false - - gui.Views.Extras.ScrollDown(gui.Contexts().CommandLog.GetViewTrait().PageDelta()) - - return nil -} - -func (gui *Gui) goToExtrasPanelTop() error { - gui.Views.Extras.Autoscroll = false - - gui.Views.Extras.ScrollUp(gui.Views.Extras.ViewLinesHeight()) - - return nil -} - -func (gui *Gui) goToExtrasPanelBottom() error { - gui.Views.Extras.Autoscroll = true - - gui.Views.Extras.ScrollDown(gui.Views.Extras.ViewLinesHeight()) - - return nil -} - -func (gui *Gui) getCmdWriter() io.Writer { - return &prefixWriter{writer: gui.Views.Extras, prefix: style.FgMagenta.Sprintf("\n\n%s\n", gui.c.Tr.GitOutput)} -} - -// Ensures that the first write is preceded by writing a prefix. -// This allows us to say 'Git output:' before writing the actual git output. -// We could just write directly to the view in this package before running the command but we already have code in the commands package that writes to the same view beforehand (with the command it's about to run) so things would be out of order. -type prefixWriter struct { - prefix string - prefixWritten bool - writer io.Writer -} - -func (self *prefixWriter) Write(p []byte) (int, error) { - if !self.prefixWritten { - self.prefixWritten = true - // assuming we can write this prefix in one go - n, err := self.writer.Write([]byte(self.prefix)) - if err != nil { - return n, err - } - } - return self.writer.Write(p) -} diff --git a/pkg/gui/filetree/README.md b/pkg/gui/filetree/README.md deleted file mode 100644 index d2d16ace66d..00000000000 --- a/pkg/gui/filetree/README.md +++ /dev/null @@ -1,22 +0,0 @@ -## FileTree Package - -This package handles the representation of file trees. There are two ways to render files: one is to render them flat, so something like this: - -``` -dir1/file1 -dir1/file2 -file3 -``` - -And the other is to render them as a tree - -``` -dir1/ - file1 - file2 -file3 -``` - -Internally we represent each of the above as a tree, but with the flat approach there's just a single root node and every path is a direct child of that root. Viewing in 'tree' mode (as opposed to 'flat' mode) allows for collapsing and expanding directories, and lets you perform actions on directories e.g. staging a whole directory. But it takes up more vertical space and sometimes you just want to have a flat view where you can go flick through your files one by one to see the diff. - -This package is not concerned about rendering the tree: only representing its internal state. diff --git a/pkg/gui/filetree/build_tree.go b/pkg/gui/filetree/build_tree.go deleted file mode 100644 index 91e6d198655..00000000000 --- a/pkg/gui/filetree/build_tree.go +++ /dev/null @@ -1,174 +0,0 @@ -package filetree - -import ( - "sort" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" -) - -func BuildTreeFromFiles(files []*models.File, showRootItem bool) *Node[models.File] { - root := &Node[models.File]{} - - childrenMapsByNode := make(map[*Node[models.File]]map[string]*Node[models.File]) - - var curr *Node[models.File] - for _, file := range files { - splitPath := SplitFileTreePath(file.Path, showRootItem) - curr = root - outer: - for i := range splitPath { - var setFile *models.File - isFile := i == len(splitPath)-1 - if isFile { - setFile = file - } - - path := join(splitPath[:i+1]) - - var currNodeChildrenMap map[string]*Node[models.File] - var isCurrNodeMapped bool - - if currNodeChildrenMap, isCurrNodeMapped = childrenMapsByNode[curr]; !isCurrNodeMapped { - currNodeChildrenMap = make(map[string]*Node[models.File]) - childrenMapsByNode[curr] = currNodeChildrenMap - } - - child, doesCurrNodeHaveChildAlready := currNodeChildrenMap[path] - if doesCurrNodeHaveChildAlready { - curr = child - continue outer - } - - if i == 0 && len(files) == 1 && len(splitPath) == 2 { - // skip the root item when there's only one file at top level; we don't need it in that case - continue outer - } - - newChild := &Node[models.File]{ - path: path, - File: setFile, - } - curr.Children = append(curr.Children, newChild) - - currNodeChildrenMap[path] = newChild - - curr = newChild - } - } - - root.Sort() - root.Compress() - - return root -} - -func BuildFlatTreeFromCommitFiles(files []*models.CommitFile, showRootItem bool) *Node[models.CommitFile] { - rootAux := BuildTreeFromCommitFiles(files, showRootItem) - sortedFiles := rootAux.GetLeaves() - - return &Node[models.CommitFile]{Children: sortedFiles} -} - -func BuildTreeFromCommitFiles(files []*models.CommitFile, showRootItem bool) *Node[models.CommitFile] { - root := &Node[models.CommitFile]{} - - var curr *Node[models.CommitFile] - for _, file := range files { - splitPath := SplitFileTreePath(file.Path, showRootItem) - curr = root - outer: - for i := range splitPath { - var setFile *models.CommitFile - isFile := i == len(splitPath)-1 - if isFile { - setFile = file - } - - path := join(splitPath[:i+1]) - - for _, existingChild := range curr.Children { - if existingChild.path == path { - curr = existingChild - continue outer - } - } - - if i == 0 && len(files) == 1 && len(splitPath) == 2 { - // skip the root item when there's only one file at top level; we don't need it in that case - continue outer - } - - newChild := &Node[models.CommitFile]{ - path: path, - File: setFile, - } - curr.Children = append(curr.Children, newChild) - - curr = newChild - } - } - - root.Sort() - root.Compress() - - return root -} - -func BuildFlatTreeFromFiles(files []*models.File, showRootItem bool) *Node[models.File] { - rootAux := BuildTreeFromFiles(files, showRootItem) - sortedFiles := rootAux.GetLeaves() - - // from top down we have merge conflict files, then tracked file, then untracked - // files. This is the one way in which sorting differs between flat mode and - // tree mode - sort.SliceStable(sortedFiles, func(i, j int) bool { - iFile := sortedFiles[i].File - jFile := sortedFiles[j].File - - // never going to happen but just to be safe - if iFile == nil || jFile == nil { - return false - } - - if iFile.HasMergeConflicts && !jFile.HasMergeConflicts { - return true - } - - if jFile.HasMergeConflicts && !iFile.HasMergeConflicts { - return false - } - - if iFile.Tracked && !jFile.Tracked { - return true - } - - if jFile.Tracked && !iFile.Tracked { - return false - } - - return false - }) - - return &Node[models.File]{Children: sortedFiles} -} - -func split(str string) []string { - return strings.Split(str, "/") -} - -func join(strs []string) string { - return strings.Join(strs, "/") -} - -func SplitFileTreePath(path string, showRootItem bool) []string { - return split(InternalTreePathForFilePath(path, showRootItem)) -} - -func InternalTreePathForFilePath(path string, showRootItem bool) string { - if showRootItem { - return "./" + path - } - - return path -} diff --git a/pkg/gui/filetree/build_tree_test.go b/pkg/gui/filetree/build_tree_test.go deleted file mode 100644 index c3077783bff..00000000000 --- a/pkg/gui/filetree/build_tree_test.go +++ /dev/null @@ -1,788 +0,0 @@ -package filetree - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/stretchr/testify/assert" -) - -func TestBuildTreeFromFiles(t *testing.T) { - scenarios := []struct { - name string - files []*models.File - showRootItem bool - expected *Node[models.File] - }{ - { - name: "no files", - files: []*models.File{}, - expected: &Node[models.File]{ - path: "", - Children: nil, - }, - }, - { - name: "files in same directory", - files: []*models.File{ - { - Path: "dir1/a", - }, - { - Path: "dir1/b", - }, - }, - showRootItem: true, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - path: "./dir1", - CompressionLevel: 1, - Children: []*Node[models.File]{ - { - File: &models.File{Path: "dir1/a"}, - path: "./dir1/a", - }, - { - File: &models.File{Path: "dir1/b"}, - path: "./dir1/b", - }, - }, - }, - }, - }, - }, - { - name: "files in same directory, not root item", - files: []*models.File{ - { - Path: "dir1/a", - }, - { - Path: "dir1/b", - }, - }, - showRootItem: false, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - path: "dir1", - CompressionLevel: 0, - Children: []*Node[models.File]{ - { - File: &models.File{Path: "dir1/a"}, - path: "dir1/a", - }, - { - File: &models.File{Path: "dir1/b"}, - path: "dir1/b", - }, - }, - }, - }, - }, - }, - { - name: "paths that can be compressed", - files: []*models.File{ - { - Path: "dir1/dir3/a", - }, - { - Path: "dir2/dir4/b", - }, - }, - showRootItem: true, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - path: ".", - Children: []*Node[models.File]{ - { - path: "./dir1/dir3", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "dir1/dir3/a"}, - path: "./dir1/dir3/a", - }, - }, - CompressionLevel: 1, - }, - { - path: "./dir2/dir4", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "dir2/dir4/b"}, - path: "./dir2/dir4/b", - }, - }, - CompressionLevel: 1, - }, - }, - }, - }, - }, - }, - { - name: "paths that can be compressed, no root item", - files: []*models.File{ - { - Path: "dir1/dir3/a", - }, - { - Path: "dir2/dir4/b", - }, - }, - showRootItem: false, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - path: "dir1/dir3", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "dir1/dir3/a"}, - path: "dir1/dir3/a", - }, - }, - CompressionLevel: 1, - }, - { - path: "dir2/dir4", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "dir2/dir4/b"}, - path: "dir2/dir4/b", - }, - }, - CompressionLevel: 1, - }, - }, - }, - }, - { - name: "paths that can be sorted", - files: []*models.File{ - { - Path: "b", - }, - { - Path: "a", - }, - }, - showRootItem: true, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - path: ".", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "a"}, - path: "./a", - }, - { - File: &models.File{Path: "b"}, - path: "./b", - }, - }, - }, - }, - }, - }, - { - name: "paths that can be sorted including a merge conflict file", - files: []*models.File{ - { - Path: "b", - }, - { - Path: "z", - HasMergeConflicts: true, - }, - { - Path: "a", - }, - }, - showRootItem: true, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - path: ".", - // it is a little strange that we're not bubbling up our merge conflict - // here but we are technically still in tree mode and that's the rule - Children: []*Node[models.File]{ - { - File: &models.File{Path: "a"}, - path: "./a", - }, - { - File: &models.File{Path: "b"}, - path: "./b", - }, - { - File: &models.File{Path: "z", HasMergeConflicts: true}, - path: "./z", - }, - }, - }, - }, - }, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - result := BuildTreeFromFiles(s.files, s.showRootItem) - assert.EqualValues(t, s.expected, result) - }) - } -} - -func TestBuildFlatTreeFromFiles(t *testing.T) { - scenarios := []struct { - name string - files []*models.File - showRootItem bool - expected *Node[models.File] - }{ - { - name: "no files", - files: []*models.File{}, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{}, - }, - }, - { - name: "files in same directory", - files: []*models.File{ - { - Path: "dir1/a", - }, - { - Path: "dir1/b", - }, - }, - showRootItem: true, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "dir1/a"}, - path: "./dir1/a", - CompressionLevel: 0, - }, - { - File: &models.File{Path: "dir1/b"}, - path: "./dir1/b", - CompressionLevel: 0, - }, - }, - }, - }, - { - name: "files in same directory, not root item", - files: []*models.File{ - { - Path: "dir1/a", - }, - { - Path: "dir1/b", - }, - }, - showRootItem: false, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "dir1/a"}, - path: "dir1/a", - CompressionLevel: 0, - }, - { - File: &models.File{Path: "dir1/b"}, - path: "dir1/b", - CompressionLevel: 0, - }, - }, - }, - }, - { - name: "paths that can be compressed", - files: []*models.File{ - { - Path: "dir1/a", - }, - { - Path: "dir2/b", - }, - }, - showRootItem: true, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "dir1/a"}, - path: "./dir1/a", - CompressionLevel: 0, - }, - { - File: &models.File{Path: "dir2/b"}, - path: "./dir2/b", - CompressionLevel: 0, - }, - }, - }, - }, - { - name: "paths that can be compressed, no root item", - files: []*models.File{ - { - Path: "dir1/a", - }, - { - Path: "dir2/b", - }, - }, - showRootItem: false, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "dir1/a"}, - path: "dir1/a", - CompressionLevel: 0, - }, - { - File: &models.File{Path: "dir2/b"}, - path: "dir2/b", - CompressionLevel: 0, - }, - }, - }, - }, - { - name: "paths that can be sorted", - files: []*models.File{ - { - Path: "b", - }, - { - Path: "a", - }, - }, - showRootItem: true, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "a"}, - path: "./a", - }, - { - File: &models.File{Path: "b"}, - path: "./b", - }, - }, - }, - }, - { - name: "tracked, untracked, and conflicted files", - files: []*models.File{ - { - Path: "a2", - Tracked: false, - }, - { - Path: "a1", - Tracked: false, - }, - { - Path: "c2", - HasMergeConflicts: true, - }, - { - Path: "c1", - HasMergeConflicts: true, - }, - { - Path: "b2", - Tracked: true, - }, - { - Path: "b1", - Tracked: true, - }, - }, - showRootItem: true, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "c1", HasMergeConflicts: true}, - path: "./c1", - }, - { - File: &models.File{Path: "c2", HasMergeConflicts: true}, - path: "./c2", - }, - { - File: &models.File{Path: "b1", Tracked: true}, - path: "./b1", - }, - { - File: &models.File{Path: "b2", Tracked: true}, - path: "./b2", - }, - { - File: &models.File{Path: "a1", Tracked: false}, - path: "./a1", - }, - { - File: &models.File{Path: "a2", Tracked: false}, - path: "./a2", - }, - }, - }, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - result := BuildFlatTreeFromFiles(s.files, s.showRootItem) - assert.EqualValues(t, s.expected, result) - }) - } -} - -func TestBuildTreeFromCommitFiles(t *testing.T) { - scenarios := []struct { - name string - files []*models.CommitFile - showRootItem bool - expected *Node[models.CommitFile] - }{ - { - name: "no files", - files: []*models.CommitFile{}, - expected: &Node[models.CommitFile]{ - path: "", - Children: nil, - }, - }, - { - name: "files in same directory", - files: []*models.CommitFile{ - { - Path: "dir1/a", - }, - { - Path: "dir1/b", - }, - }, - showRootItem: true, - expected: &Node[models.CommitFile]{ - path: "", - Children: []*Node[models.CommitFile]{ - { - path: "./dir1", - CompressionLevel: 1, - Children: []*Node[models.CommitFile]{ - { - File: &models.CommitFile{Path: "dir1/a"}, - path: "./dir1/a", - }, - { - File: &models.CommitFile{Path: "dir1/b"}, - path: "./dir1/b", - }, - }, - }, - }, - }, - }, - { - name: "files in same directory, not root item", - files: []*models.CommitFile{ - { - Path: "dir1/a", - }, - { - Path: "dir1/b", - }, - }, - showRootItem: false, - expected: &Node[models.CommitFile]{ - path: "", - Children: []*Node[models.CommitFile]{ - { - path: "dir1", - CompressionLevel: 0, - Children: []*Node[models.CommitFile]{ - { - File: &models.CommitFile{Path: "dir1/a"}, - path: "dir1/a", - }, - { - File: &models.CommitFile{Path: "dir1/b"}, - path: "dir1/b", - }, - }, - }, - }, - }, - }, - { - name: "paths that can be compressed", - files: []*models.CommitFile{ - { - Path: "dir1/dir3/a", - }, - { - Path: "dir2/dir4/b", - }, - }, - showRootItem: true, - expected: &Node[models.CommitFile]{ - path: "", - Children: []*Node[models.CommitFile]{ - { - path: ".", - Children: []*Node[models.CommitFile]{ - { - path: "./dir1/dir3", - Children: []*Node[models.CommitFile]{ - { - File: &models.CommitFile{Path: "dir1/dir3/a"}, - path: "./dir1/dir3/a", - }, - }, - CompressionLevel: 1, - }, - { - path: "./dir2/dir4", - Children: []*Node[models.CommitFile]{ - { - File: &models.CommitFile{Path: "dir2/dir4/b"}, - path: "./dir2/dir4/b", - }, - }, - CompressionLevel: 1, - }, - }, - }, - }, - }, - }, - { - name: "paths that can be compressed, no root item", - files: []*models.CommitFile{ - { - Path: "dir1/dir3/a", - }, - { - Path: "dir2/dir4/b", - }, - }, - showRootItem: false, - expected: &Node[models.CommitFile]{ - path: "", - Children: []*Node[models.CommitFile]{ - { - path: "dir1/dir3", - Children: []*Node[models.CommitFile]{ - { - File: &models.CommitFile{Path: "dir1/dir3/a"}, - path: "dir1/dir3/a", - }, - }, - CompressionLevel: 1, - }, - { - path: "dir2/dir4", - Children: []*Node[models.CommitFile]{ - { - File: &models.CommitFile{Path: "dir2/dir4/b"}, - path: "dir2/dir4/b", - }, - }, - CompressionLevel: 1, - }, - }, - }, - }, - { - name: "paths that can be sorted", - files: []*models.CommitFile{ - { - Path: "b", - }, - { - Path: "a", - }, - }, - showRootItem: true, - expected: &Node[models.CommitFile]{ - path: "", - Children: []*Node[models.CommitFile]{ - { - path: ".", - Children: []*Node[models.CommitFile]{ - { - File: &models.CommitFile{Path: "a"}, - path: "./a", - }, - { - File: &models.CommitFile{Path: "b"}, - path: "./b", - }, - }, - }, - }, - }, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - result := BuildTreeFromCommitFiles(s.files, s.showRootItem) - assert.EqualValues(t, s.expected, result) - }) - } -} - -func TestBuildFlatTreeFromCommitFiles(t *testing.T) { - scenarios := []struct { - name string - files []*models.CommitFile - showRootItem bool - expected *Node[models.CommitFile] - }{ - { - name: "no files", - files: []*models.CommitFile{}, - expected: &Node[models.CommitFile]{ - path: "", - Children: []*Node[models.CommitFile]{}, - }, - }, - { - name: "files in same directory", - files: []*models.CommitFile{ - { - Path: "dir1/a", - }, - { - Path: "dir1/b", - }, - }, - showRootItem: true, - expected: &Node[models.CommitFile]{ - path: "", - Children: []*Node[models.CommitFile]{ - { - File: &models.CommitFile{Path: "dir1/a"}, - path: "./dir1/a", - CompressionLevel: 0, - }, - { - File: &models.CommitFile{Path: "dir1/b"}, - path: "./dir1/b", - CompressionLevel: 0, - }, - }, - }, - }, - { - name: "files in same directory, not root item", - files: []*models.CommitFile{ - { - Path: "dir1/a", - }, - { - Path: "dir1/b", - }, - }, - showRootItem: false, - expected: &Node[models.CommitFile]{ - path: "", - Children: []*Node[models.CommitFile]{ - { - File: &models.CommitFile{Path: "dir1/a"}, - path: "dir1/a", - CompressionLevel: 0, - }, - { - File: &models.CommitFile{Path: "dir1/b"}, - path: "dir1/b", - CompressionLevel: 0, - }, - }, - }, - }, - { - name: "paths that can be compressed", - files: []*models.CommitFile{ - { - Path: "dir1/a", - }, - { - Path: "dir2/b", - }, - }, - showRootItem: true, - expected: &Node[models.CommitFile]{ - path: "", - Children: []*Node[models.CommitFile]{ - { - File: &models.CommitFile{Path: "dir1/a"}, - path: "./dir1/a", - CompressionLevel: 0, - }, - { - File: &models.CommitFile{Path: "dir2/b"}, - path: "./dir2/b", - CompressionLevel: 0, - }, - }, - }, - }, - { - name: "paths that can be sorted", - files: []*models.CommitFile{ - { - Path: "b", - }, - { - Path: "a", - }, - }, - showRootItem: true, - expected: &Node[models.CommitFile]{ - path: "", - Children: []*Node[models.CommitFile]{ - { - File: &models.CommitFile{Path: "a"}, - path: "./a", - }, - { - File: &models.CommitFile{Path: "b"}, - path: "./b", - }, - }, - }, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - result := BuildFlatTreeFromCommitFiles(s.files, s.showRootItem) - assert.EqualValues(t, s.expected, result) - }) - } -} diff --git a/pkg/gui/filetree/collapsed_paths.go b/pkg/gui/filetree/collapsed_paths.go deleted file mode 100644 index e22435b7ffa..00000000000 --- a/pkg/gui/filetree/collapsed_paths.go +++ /dev/null @@ -1,43 +0,0 @@ -package filetree - -import "github.com/jesseduffield/generics/set" - -type CollapsedPaths struct { - collapsedPaths *set.Set[string] -} - -func NewCollapsedPaths() *CollapsedPaths { - return &CollapsedPaths{ - collapsedPaths: set.New[string](), - } -} - -func (self *CollapsedPaths) ExpandToPath(path string) { - // need every directory along the way - splitPath := split(path) - for i := range splitPath { - dir := join(splitPath[0 : i+1]) - self.collapsedPaths.Remove(dir) - } -} - -func (self *CollapsedPaths) IsCollapsed(path string) bool { - return self.collapsedPaths.Includes(path) -} - -func (self *CollapsedPaths) Collapse(path string) { - self.collapsedPaths.Add(path) -} - -func (self *CollapsedPaths) ToggleCollapsed(path string) { - if self.collapsedPaths.Includes(path) { - self.collapsedPaths.Remove(path) - } else { - self.collapsedPaths.Add(path) - } -} - -func (self *CollapsedPaths) ExpandAll() { - // Could be cleaner if Set had a Clear() method... - self.collapsedPaths.RemoveSlice(self.collapsedPaths.ToSlice()) -} diff --git a/pkg/gui/filetree/commit_file_node.go b/pkg/gui/filetree/commit_file_node.go deleted file mode 100644 index be9868daa31..00000000000 --- a/pkg/gui/filetree/commit_file_node.go +++ /dev/null @@ -1,25 +0,0 @@ -package filetree - -import "github.com/jesseduffield/lazygit/pkg/commands/models" - -// CommitFileNode wraps a node and provides some commit-file-specific methods for it. -type CommitFileNode struct { - *Node[models.CommitFile] -} - -func NewCommitFileNode(node *Node[models.CommitFile]) *CommitFileNode { - if node == nil { - return nil - } - - return &CommitFileNode{Node: node} -} - -// returns the underlying node, without any commit-file-specific methods attached -func (self *CommitFileNode) Raw() *Node[models.CommitFile] { - if self == nil { - return nil - } - - return self.Node -} diff --git a/pkg/gui/filetree/commit_file_tree.go b/pkg/gui/filetree/commit_file_tree.go deleted file mode 100644 index 7af83176f60..00000000000 --- a/pkg/gui/filetree/commit_file_tree.go +++ /dev/null @@ -1,133 +0,0 @@ -package filetree - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type ICommitFileTree interface { - ITree[models.CommitFile] - - Get(index int) *CommitFileNode - GetFile(path string) *models.CommitFile - GetAllItems() []*CommitFileNode - GetAllFiles() []*models.CommitFile - GetRoot() *CommitFileNode -} - -type CommitFileTree struct { - getFiles func() []*models.CommitFile - tree *Node[models.CommitFile] - showTree bool - common *common.Common - collapsedPaths *CollapsedPaths -} - -func (self *CommitFileTree) CollapseAll() { - dirPaths := lo.FilterMap(self.GetAllItems(), func(file *CommitFileNode, index int) (string, bool) { - return file.path, !file.IsFile() - }) - - for _, path := range dirPaths { - self.collapsedPaths.Collapse(path) - } -} - -func (self *CommitFileTree) ExpandAll() { - self.collapsedPaths.ExpandAll() -} - -var _ ICommitFileTree = &CommitFileTree{} - -func NewCommitFileTree(getFiles func() []*models.CommitFile, common *common.Common, showTree bool) *CommitFileTree { - return &CommitFileTree{ - getFiles: getFiles, - common: common, - showTree: showTree, - collapsedPaths: NewCollapsedPaths(), - } -} - -func (self *CommitFileTree) ExpandToPath(path string) { - self.collapsedPaths.ExpandToPath(path) -} - -func (self *CommitFileTree) ToggleShowTree() { - self.showTree = !self.showTree - self.SetTree() -} - -func (self *CommitFileTree) Get(index int) *CommitFileNode { - // need to traverse the three depth first until we get to the index. - return NewCommitFileNode(self.tree.GetNodeAtIndex(index+1, self.collapsedPaths)) // ignoring root -} - -func (self *CommitFileTree) GetIndexForPath(path string) (int, bool) { - index, found := self.tree.GetIndexForPath(path, self.collapsedPaths) - return index - 1, found -} - -func (self *CommitFileTree) GetAllItems() []*CommitFileNode { - if self.tree == nil { - return nil - } - - // ignoring root - return lo.Map(self.tree.Flatten(self.collapsedPaths)[1:], func(node *Node[models.CommitFile], _ int) *CommitFileNode { - return NewCommitFileNode(node) - }) -} - -func (self *CommitFileTree) Len() int { - return self.tree.Size(self.collapsedPaths) - 1 // ignoring root -} - -func (self *CommitFileTree) GetItem(index int) types.HasUrn { - // Unimplemented because we don't yet need to show inlines statuses in commit file views - return nil -} - -func (self *CommitFileTree) GetAllFiles() []*models.CommitFile { - return self.getFiles() -} - -func (self *CommitFileTree) SetTree() { - showRootItem := self.common.UserConfig().Gui.ShowRootItemInFileTree - if self.showTree { - self.tree = BuildTreeFromCommitFiles(self.getFiles(), showRootItem) - } else { - self.tree = BuildFlatTreeFromCommitFiles(self.getFiles(), showRootItem) - } -} - -func (self *CommitFileTree) IsCollapsed(path string) bool { - return self.collapsedPaths.IsCollapsed(path) -} - -func (self *CommitFileTree) ToggleCollapsed(path string) { - self.collapsedPaths.ToggleCollapsed(path) -} - -func (self *CommitFileTree) GetRoot() *CommitFileNode { - return NewCommitFileNode(self.tree) -} - -func (self *CommitFileTree) CollapsedPaths() *CollapsedPaths { - return self.collapsedPaths -} - -func (self *CommitFileTree) GetFile(path string) *models.CommitFile { - for _, file := range self.getFiles() { - if file.Path == path { - return file - } - } - - return nil -} - -func (self *CommitFileTree) InTreeMode() bool { - return self.showTree -} diff --git a/pkg/gui/filetree/commit_file_tree_view_model.go b/pkg/gui/filetree/commit_file_tree_view_model.go deleted file mode 100644 index 02b0fff9a86..00000000000 --- a/pkg/gui/filetree/commit_file_tree_view_model.go +++ /dev/null @@ -1,205 +0,0 @@ -package filetree - -import ( - "strings" - "sync" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/gui/context/traits" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type ICommitFileTreeViewModel interface { - ICommitFileTree - types.IListCursor - - GetRef() models.Ref - SetRef(models.Ref) - GetRefRange() *types.RefRange // can be nil, in which case GetRef should be used - SetRefRange(*types.RefRange) // should be set to nil when selection is not a range - GetCanRebase() bool - SetCanRebase(bool) -} - -type CommitFileTreeViewModel struct { - sync.RWMutex - types.IListCursor - ICommitFileTree - - // this is e.g. the commit for which we're viewing the files, if there is no - // range selection, or if the range selection can't be used for some reason - ref models.Ref - - // this is a commit range for which we're viewing the files. Can be nil, in - // which case ref is used. - refRange *types.RefRange - - // we set this to true when you're viewing the files within the checked-out branch's commits. - // If you're viewing the files of some random other branch we can't do any rebase stuff. - canRebase bool -} - -var _ ICommitFileTreeViewModel = &CommitFileTreeViewModel{} - -func NewCommitFileTreeViewModel(getFiles func() []*models.CommitFile, common *common.Common, showTree bool) *CommitFileTreeViewModel { - fileTree := NewCommitFileTree(getFiles, common, showTree) - listCursor := traits.NewListCursor(fileTree.Len) - return &CommitFileTreeViewModel{ - ICommitFileTree: fileTree, - IListCursor: listCursor, - ref: nil, - refRange: nil, - canRebase: false, - } -} - -func (self *CommitFileTreeViewModel) GetRef() models.Ref { - return self.ref -} - -func (self *CommitFileTreeViewModel) SetRef(ref models.Ref) { - self.ref = ref -} - -func (self *CommitFileTreeViewModel) GetRefRange() *types.RefRange { - return self.refRange -} - -func (self *CommitFileTreeViewModel) SetRefRange(refsForRange *types.RefRange) { - self.refRange = refsForRange -} - -func (self *CommitFileTreeViewModel) GetCanRebase() bool { - return self.canRebase -} - -func (self *CommitFileTreeViewModel) SetCanRebase(canRebase bool) { - self.canRebase = canRebase -} - -func (self *CommitFileTreeViewModel) GetSelected() *CommitFileNode { - if self.Len() == 0 { - return nil - } - - return self.Get(self.GetSelectedLineIdx()) -} - -func (self *CommitFileTreeViewModel) GetSelectedItemId() string { - item := self.GetSelected() - if item == nil { - return "" - } - - return item.ID() -} - -func (self *CommitFileTreeViewModel) GetSelectedItems() ([]*CommitFileNode, int, int) { - if self.Len() == 0 { - return nil, 0, 0 - } - - startIdx, endIdx := self.GetSelectionRange() - - nodes := []*CommitFileNode{} - for i := startIdx; i <= endIdx; i++ { - nodes = append(nodes, self.Get(i)) - } - - return nodes, startIdx, endIdx -} - -func (self *CommitFileTreeViewModel) GetSelectedItemIds() ([]string, int, int) { - selectedItems, startIdx, endIdx := self.GetSelectedItems() - - ids := lo.Map(selectedItems, func(item *CommitFileNode, _ int) string { - return item.ID() - }) - - return ids, startIdx, endIdx -} - -func (self *CommitFileTreeViewModel) GetSelectedFile() *models.CommitFile { - node := self.GetSelected() - if node == nil { - return nil - } - - return node.File -} - -func (self *CommitFileTreeViewModel) GetSelectedPath() string { - node := self.GetSelected() - if node == nil { - return "" - } - - return node.GetPath() -} - -// duplicated from file_tree_view_model.go. Generics will help here -func (self *CommitFileTreeViewModel) ToggleShowTree() { - selectedNode := self.GetSelected() - - self.ICommitFileTree.ToggleShowTree() - - if selectedNode == nil { - return - } - path := selectedNode.path - - if self.InTreeMode() { - self.ExpandToPath(path) - } else if len(selectedNode.Children) > 0 { - path = selectedNode.GetLeaves()[0].path - } - - index, found := self.GetIndexForPath(path) - if found { - self.SetSelection(index) - } -} - -func (self *CommitFileTreeViewModel) CollapseAll() { - selectedNode := self.GetSelected() - - self.ICommitFileTree.CollapseAll() - if selectedNode == nil { - return - } - - topLevelPath := strings.Split(selectedNode.path, "/")[0] - index, found := self.GetIndexForPath(topLevelPath) - if found { - self.SetSelectedLineIdx(index) - } -} - -func (self *CommitFileTreeViewModel) ExpandAll() { - selectedNode := self.GetSelected() - - self.ICommitFileTree.ExpandAll() - - if selectedNode == nil { - return - } - - index, found := self.GetIndexForPath(selectedNode.path) - if found { - self.SetSelectedLineIdx(index) - } -} - -// Try to select the given path if present. If it doesn't exist, or one of the parent directories is -// collapsed, do nothing. -// Note that filepath is an actual file path, not an internal tree path as with e.g. -// ToggleCollapsed. It must be a relative path (relative to the repo root), and it must contain -// forward slashes rather than backslashes even on Windows. -func (self *CommitFileTreeViewModel) SelectPath(filepath string, showRootItem bool) { - index, found := self.GetIndexForPath(InternalTreePathForFilePath(filepath, showRootItem)) - if found { - self.SetSelection(index) - } -} diff --git a/pkg/gui/filetree/file_node.go b/pkg/gui/filetree/file_node.go deleted file mode 100644 index 0836eaf02d9..00000000000 --- a/pkg/gui/filetree/file_node.go +++ /dev/null @@ -1,73 +0,0 @@ -package filetree - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" -) - -// FileNode wraps a node and provides some file-specific methods for it. -type FileNode struct { - *Node[models.File] -} - -var _ models.IFile = &FileNode{} - -func NewFileNode(node *Node[models.File]) *FileNode { - if node == nil { - return nil - } - - return &FileNode{Node: node} -} - -// returns the underlying node, without any file-specific methods attached -func (self *FileNode) Raw() *Node[models.File] { - if self == nil { - return nil - } - - return self.Node -} - -func (self *FileNode) GetHasUnstagedChanges() bool { - return self.SomeFile(func(file *models.File) bool { return file.HasUnstagedChanges }) -} - -func (self *FileNode) GetHasStagedOrTrackedChanges() bool { - if !self.GetHasStagedChanges() { - return self.SomeFile(func(t *models.File) bool { - return t.Tracked - }) - } - return true -} - -func (self *FileNode) GetHasStagedChanges() bool { - return self.SomeFile(func(file *models.File) bool { return file.HasStagedChanges }) -} - -func (self *FileNode) GetHasInlineMergeConflicts() bool { - return self.SomeFile(func(file *models.File) bool { - if !file.HasInlineMergeConflicts { - return false - } - hasConflicts, _ := mergeconflicts.FileHasConflictMarkers(file.Path) - return hasConflicts - }) -} - -func (self *FileNode) GetIsTracked() bool { - return self.SomeFile(func(file *models.File) bool { return file.Tracked }) -} - -func (self *FileNode) GetIsFile() bool { - return self.IsFile() -} - -func (self *FileNode) GetPreviousPath() string { - if self.File == nil { - return "" - } - - return self.File.PreviousPath -} diff --git a/pkg/gui/filetree/file_node_test.go b/pkg/gui/filetree/file_node_test.go deleted file mode 100644 index 87d62bf8617..00000000000 --- a/pkg/gui/filetree/file_node_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package filetree - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/stretchr/testify/assert" -) - -func TestCompress(t *testing.T) { - scenarios := []struct { - name string - root *Node[models.File] - expected *Node[models.File] - }{ - { - name: "nil node", - root: nil, - expected: nil, - }, - { - name: "leaf node", - root: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - {File: &models.File{Path: "test", ShortStatus: " M", HasStagedChanges: true}, path: "test"}, - }, - }, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - {File: &models.File{Path: "test", ShortStatus: " M", HasStagedChanges: true}, path: "test"}, - }, - }, - }, - { - name: "big example", - root: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - path: "dir1", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "file2", ShortStatus: "M ", HasUnstagedChanges: true}, - path: "dir1/file2", - }, - }, - }, - { - path: "dir2", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "file3", ShortStatus: " M", HasStagedChanges: true}, - path: "dir2/file3", - }, - { - File: &models.File{Path: "file4", ShortStatus: "M ", HasUnstagedChanges: true}, - path: "dir2/file4", - }, - }, - }, - { - path: "dir3", - Children: []*Node[models.File]{ - { - path: "dir3/dir3-1", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "file5", ShortStatus: "M ", HasUnstagedChanges: true}, - path: "dir3/dir3-1/file5", - }, - }, - }, - }, - }, - { - File: &models.File{Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - path: "file1", - }, - }, - }, - expected: &Node[models.File]{ - path: "", - Children: []*Node[models.File]{ - { - path: "dir1", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "file2", ShortStatus: "M ", HasUnstagedChanges: true}, - path: "dir1/file2", - }, - }, - }, - { - path: "dir2", - Children: []*Node[models.File]{ - { - File: &models.File{Path: "file3", ShortStatus: " M", HasStagedChanges: true}, - path: "dir2/file3", - }, - { - File: &models.File{Path: "file4", ShortStatus: "M ", HasUnstagedChanges: true}, - path: "dir2/file4", - }, - }, - }, - { - path: "dir3/dir3-1", - CompressionLevel: 1, - Children: []*Node[models.File]{ - { - File: &models.File{Path: "file5", ShortStatus: "M ", HasUnstagedChanges: true}, - path: "dir3/dir3-1/file5", - }, - }, - }, - { - File: &models.File{Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - path: "file1", - }, - }, - }, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - s.root.Compress() - assert.EqualValues(t, s.expected, s.root) - }) - } -} - -func TestGetFile(t *testing.T) { - scenarios := []struct { - name string - viewModel *FileTree - path string - expected *models.File - }{ - { - name: "valid case", - viewModel: NewFileTree(func() []*models.File { return []*models.File{{Path: "blah/one"}, {Path: "blah/two"}} }, nil, false), - path: "blah/two", - expected: &models.File{Path: "blah/two"}, - }, - { - name: "not found", - viewModel: NewFileTree(func() []*models.File { return []*models.File{{Path: "blah/one"}, {Path: "blah/two"}} }, nil, false), - path: "blah/three", - expected: nil, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - assert.EqualValues(t, s.expected, s.viewModel.GetFile(s.path)) - }) - } -} diff --git a/pkg/gui/filetree/file_tree.go b/pkg/gui/filetree/file_tree.go deleted file mode 100644 index d0056a0a86e..00000000000 --- a/pkg/gui/filetree/file_tree.go +++ /dev/null @@ -1,215 +0,0 @@ -package filetree - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -type FileTreeDisplayFilter int - -const ( - DisplayAll FileTreeDisplayFilter = iota - DisplayStaged - DisplayUnstaged - DisplayTracked - DisplayUntracked - // this shows files with merge conflicts - DisplayConflicted -) - -type ITree[T any] interface { - InTreeMode() bool - ExpandToPath(path string) - ToggleShowTree() - GetIndexForPath(path string) (int, bool) - Len() int - GetItem(index int) types.HasUrn - SetTree() - IsCollapsed(path string) bool - ToggleCollapsed(path string) - CollapsedPaths() *CollapsedPaths - CollapseAll() - ExpandAll() -} - -type IFileTree interface { - ITree[models.File] - - FilterFiles(test func(*models.File) bool) []*models.File - SetStatusFilter(filter FileTreeDisplayFilter) - ForceShowUntracked() bool - Get(index int) *FileNode - GetFile(path string) *models.File - GetAllItems() []*FileNode - GetAllFiles() []*models.File - GetFilter() FileTreeDisplayFilter - GetRoot() *FileNode -} - -type FileTree struct { - getFiles func() []*models.File - tree *Node[models.File] - showTree bool - common *common.Common - filter FileTreeDisplayFilter - collapsedPaths *CollapsedPaths -} - -var _ IFileTree = &FileTree{} - -func NewFileTree(getFiles func() []*models.File, common *common.Common, showTree bool) *FileTree { - return &FileTree{ - getFiles: getFiles, - common: common, - showTree: showTree, - filter: DisplayAll, - collapsedPaths: NewCollapsedPaths(), - } -} - -func (self *FileTree) InTreeMode() bool { - return self.showTree -} - -func (self *FileTree) ExpandToPath(path string) { - self.collapsedPaths.ExpandToPath(path) -} - -func (self *FileTree) getFilesForDisplay() []*models.File { - switch self.filter { - case DisplayAll: - return self.getFiles() - case DisplayStaged: - return self.FilterFiles(func(file *models.File) bool { return file.HasStagedChanges }) - case DisplayUnstaged: - return self.FilterFiles(func(file *models.File) bool { return file.HasUnstagedChanges }) - case DisplayTracked: - // untracked but staged files are technically not tracked by git - // but including such files in the filtered mode helps see what files are getting committed - return self.FilterFiles(func(file *models.File) bool { return file.Tracked || file.HasStagedChanges }) - case DisplayUntracked: - return self.FilterFiles(func(file *models.File) bool { return !(file.Tracked || file.HasStagedChanges) }) - case DisplayConflicted: - return self.FilterFiles(func(file *models.File) bool { return file.HasMergeConflicts }) - default: - panic(fmt.Sprintf("Unexpected files display filter: %d", self.filter)) - } -} - -func (self *FileTree) ForceShowUntracked() bool { - return self.filter == DisplayUntracked -} - -func (self *FileTree) FilterFiles(test func(*models.File) bool) []*models.File { - return lo.Filter(self.getFiles(), func(file *models.File, _ int) bool { return test(file) }) -} - -func (self *FileTree) SetStatusFilter(filter FileTreeDisplayFilter) { - self.filter = filter - self.SetTree() -} - -func (self *FileTree) ToggleShowTree() { - self.showTree = !self.showTree - self.SetTree() -} - -func (self *FileTree) Get(index int) *FileNode { - // need to traverse the tree depth first until we get to the index. - return NewFileNode(self.tree.GetNodeAtIndex(index+1, self.collapsedPaths)) // ignoring root -} - -func (self *FileTree) GetFile(path string) *models.File { - for _, file := range self.getFiles() { - if file.Path == path { - return file - } - } - - return nil -} - -func (self *FileTree) GetIndexForPath(path string) (int, bool) { - index, found := self.tree.GetIndexForPath(path, self.collapsedPaths) - return index - 1, found -} - -// note: this gets all items when the filter is taken into consideration. There may -// be hidden files that aren't included here. Files off the screen however will -// be included -func (self *FileTree) GetAllItems() []*FileNode { - if self.tree == nil { - return nil - } - - // ignoring root - return lo.Map(self.tree.Flatten(self.collapsedPaths)[1:], func(node *Node[models.File], _ int) *FileNode { - return NewFileNode(node) - }) -} - -func (self *FileTree) Len() int { - // -1 because we're ignoring the root - return max(self.tree.Size(self.collapsedPaths)-1, 0) -} - -func (self *FileTree) GetItem(index int) types.HasUrn { - // Unimplemented because we don't yet need to show inlines statuses in commit file views - return nil -} - -func (self *FileTree) GetAllFiles() []*models.File { - return self.getFiles() -} - -func (self *FileTree) SetTree() { - filesForDisplay := self.getFilesForDisplay() - showRootItem := self.common.UserConfig().Gui.ShowRootItemInFileTree - if self.showTree { - self.tree = BuildTreeFromFiles(filesForDisplay, showRootItem) - } else { - self.tree = BuildFlatTreeFromFiles(filesForDisplay, showRootItem) - } -} - -func (self *FileTree) IsCollapsed(path string) bool { - return self.collapsedPaths.IsCollapsed(path) -} - -func (self *FileTree) ToggleCollapsed(path string) { - self.collapsedPaths.ToggleCollapsed(path) -} - -func (self *FileTree) CollapseAll() { - dirPaths := lo.FilterMap(self.GetAllItems(), func(file *FileNode, index int) (string, bool) { - return file.path, !file.IsFile() - }) - - for _, path := range dirPaths { - self.collapsedPaths.Collapse(path) - } -} - -func (self *FileTree) ExpandAll() { - self.collapsedPaths.ExpandAll() -} - -func (self *FileTree) Tree() *FileNode { - return NewFileNode(self.tree) -} - -func (self *FileTree) GetRoot() *FileNode { - return NewFileNode(self.tree) -} - -func (self *FileTree) CollapsedPaths() *CollapsedPaths { - return self.collapsedPaths -} - -func (self *FileTree) GetFilter() FileTreeDisplayFilter { - return self.filter -} diff --git a/pkg/gui/filetree/file_tree_test.go b/pkg/gui/filetree/file_tree_test.go deleted file mode 100644 index 4a593711ce6..00000000000 --- a/pkg/gui/filetree/file_tree_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package filetree - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/stretchr/testify/assert" -) - -func TestFilterAction(t *testing.T) { - scenarios := []struct { - name string - filter FileTreeDisplayFilter - files []*models.File - expected []*models.File - }{ - { - name: "filter files with unstaged changes", - filter: DisplayUnstaged, - files: []*models.File{ - {Path: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "dir2/file5", ShortStatus: "M ", HasStagedChanges: true}, - {Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - }, - expected: []*models.File{ - {Path: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - }, - }, - { - name: "filter files with staged changes", - filter: DisplayStaged, - files: []*models.File{ - {Path: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true}, - {Path: "dir2/file5", ShortStatus: "M ", HasStagedChanges: false}, - {Path: "file1", ShortStatus: "M ", HasStagedChanges: true}, - }, - expected: []*models.File{ - {Path: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true}, - {Path: "file1", ShortStatus: "M ", HasStagedChanges: true}, - }, - }, - { - name: "filter files that are tracked", - filter: DisplayTracked, - files: []*models.File{ - {Path: "dir2/dir2/file4", ShortStatus: "M ", Tracked: true}, - {Path: "dir2/file5", ShortStatus: "M ", Tracked: false}, - {Path: "file1", ShortStatus: "M ", Tracked: true}, - }, - expected: []*models.File{ - {Path: "dir2/dir2/file4", ShortStatus: "M ", Tracked: true}, - {Path: "file1", ShortStatus: "M ", Tracked: true}, - }, - }, - { - name: "filter all files", - filter: DisplayAll, - files: []*models.File{ - {Path: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - }, - expected: []*models.File{ - {Path: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - }, - }, - { - name: "filter conflicted files", - filter: DisplayConflicted, - files: []*models.File{ - {Path: "dir2/dir2/file4", ShortStatus: "DU", HasMergeConflicts: true}, - {Path: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "dir2/file6", ShortStatus: " M", HasStagedChanges: true}, - {Path: "file1", ShortStatus: "UU", HasMergeConflicts: true, HasInlineMergeConflicts: true}, - }, - expected: []*models.File{ - {Path: "dir2/dir2/file4", ShortStatus: "DU", HasMergeConflicts: true}, - {Path: "file1", ShortStatus: "UU", HasMergeConflicts: true, HasInlineMergeConflicts: true}, - }, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - mngr := &FileTree{getFiles: func() []*models.File { return s.files }, filter: s.filter} - result := mngr.getFilesForDisplay() - assert.EqualValues(t, s.expected, result) - }) - } -} diff --git a/pkg/gui/filetree/file_tree_view_model.go b/pkg/gui/filetree/file_tree_view_model.go deleted file mode 100644 index 3db39d0a197..00000000000 --- a/pkg/gui/filetree/file_tree_view_model.go +++ /dev/null @@ -1,222 +0,0 @@ -package filetree - -import ( - "strings" - "sync" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/gui/context/traits" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type IFileTreeViewModel interface { - IFileTree - types.IListCursor -} - -// This combines our FileTree struct with a cursor that retains information about -// which item is selected. It also contains logic for repositioning that cursor -// after the files are refreshed -type FileTreeViewModel struct { - sync.RWMutex - types.IListCursor - IFileTree -} - -var _ IFileTreeViewModel = &FileTreeViewModel{} - -func NewFileTreeViewModel(getFiles func() []*models.File, common *common.Common, showTree bool) *FileTreeViewModel { - fileTree := NewFileTree(getFiles, common, showTree) - listCursor := traits.NewListCursor(fileTree.Len) - return &FileTreeViewModel{ - IFileTree: fileTree, - IListCursor: listCursor, - } -} - -func (self *FileTreeViewModel) GetSelected() *FileNode { - if self.Len() == 0 { - return nil - } - - return self.Get(self.GetSelectedLineIdx()) -} - -func (self *FileTreeViewModel) GetSelectedItemId() string { - item := self.GetSelected() - if item == nil { - return "" - } - - return item.ID() -} - -func (self *FileTreeViewModel) GetSelectedItems() ([]*FileNode, int, int) { - if self.Len() == 0 { - return nil, 0, 0 - } - - startIdx, endIdx := self.GetSelectionRange() - - nodes := []*FileNode{} - for i := startIdx; i <= endIdx; i++ { - nodes = append(nodes, self.Get(i)) - } - - return nodes, startIdx, endIdx -} - -func (self *FileTreeViewModel) GetSelectedItemIds() ([]string, int, int) { - selectedItems, startIdx, endIdx := self.GetSelectedItems() - - ids := lo.Map(selectedItems, func(item *FileNode, _ int) string { - return item.ID() - }) - - return ids, startIdx, endIdx -} - -func (self *FileTreeViewModel) GetSelectedFile() *models.File { - node := self.GetSelected() - if node == nil { - return nil - } - - return node.File -} - -func (self *FileTreeViewModel) GetSelectedPath() string { - node := self.GetSelected() - if node == nil { - return "" - } - - return node.GetPath() -} - -func (self *FileTreeViewModel) SetTree() { - newFiles := self.GetAllFiles() - selectedNode := self.GetSelected() - - // for when you stage the old file of a rename and the new file is in a collapsed dir - for _, file := range newFiles { - if selectedNode != nil && selectedNode.path != "" && file.PreviousPath == selectedNode.path { - self.ExpandToPath(file.Path) - } - } - - prevNodes := self.GetAllItems() - prevSelectedLineIdx := self.GetSelectedLineIdx() - - self.IFileTree.SetTree() - - if selectedNode != nil { - newNodes := self.GetAllItems() - newIdx := self.findNewSelectedIdx(prevNodes[prevSelectedLineIdx:], newNodes) - if newIdx != -1 && newIdx != prevSelectedLineIdx { - self.SetSelection(newIdx) - } - } - - self.ClampSelection() -} - -// Let's try to find our file again and move the cursor to that. -// If we can't find our file, it was probably just removed by the user. In that -// case, we go looking for where the next file has been moved to. Given that the -// user could have removed a whole directory, we continue iterating through the old -// nodes until we find one that exists in the new set of nodes, then move the cursor -// to that. -// prevNodes starts from our previously selected node because we don't need to consider anything above that -func (self *FileTreeViewModel) findNewSelectedIdx(prevNodes []*FileNode, currNodes []*FileNode) int { - getPaths := func(node *FileNode) []string { - if node == nil { - return nil - } - if node.File != nil && node.File.IsRename() { - return node.File.Names() - } - return []string{node.path} - } - - for _, prevNode := range prevNodes { - selectedPaths := getPaths(prevNode) - - for idx, node := range currNodes { - paths := getPaths(node) - - // If you started off with a rename selected, and now it's broken in two, we want you to jump to the new file, not the old file. - // This is because the new should be in the same position as the rename was meaning less cursor jumping - foundOldFileInRename := prevNode.File != nil && prevNode.File.IsRename() && node.path == prevNode.File.PreviousPath - foundNode := utils.StringArraysOverlap(paths, selectedPaths) && !foundOldFileInRename - if foundNode { - return idx - } - } - } - - return -1 -} - -func (self *FileTreeViewModel) SetStatusFilter(filter FileTreeDisplayFilter) { - self.IFileTree.SetStatusFilter(filter) - self.IListCursor.SetSelection(0) -} - -// If we're going from flat to tree we want to select the same file. -// If we're going from tree to flat and we have a file selected we want to select that. -// If instead we've selected a directory we need to select the first file in that directory. -func (self *FileTreeViewModel) ToggleShowTree() { - selectedNode := self.GetSelected() - - self.IFileTree.ToggleShowTree() - - if selectedNode == nil { - return - } - path := selectedNode.path - - if self.InTreeMode() { - self.ExpandToPath(path) - } else if len(selectedNode.Children) > 0 { - path = selectedNode.GetLeaves()[0].path - } - - index, found := self.GetIndexForPath(path) - if found { - self.SetSelectedLineIdx(index) - } -} - -func (self *FileTreeViewModel) CollapseAll() { - selectedNode := self.GetSelected() - - self.IFileTree.CollapseAll() - if selectedNode == nil { - return - } - - topLevelPath := strings.Split(selectedNode.path, "/")[0] - index, found := self.GetIndexForPath(topLevelPath) - if found { - self.SetSelectedLineIdx(index) - } -} - -func (self *FileTreeViewModel) ExpandAll() { - selectedNode := self.GetSelected() - - self.IFileTree.ExpandAll() - - if selectedNode == nil { - return - } - - index, found := self.GetIndexForPath(selectedNode.path) - if found { - self.SetSelectedLineIdx(index) - } -} diff --git a/pkg/gui/filetree/node.go b/pkg/gui/filetree/node.go deleted file mode 100644 index 97d5232b5c9..00000000000 --- a/pkg/gui/filetree/node.go +++ /dev/null @@ -1,342 +0,0 @@ -package filetree - -import ( - "path" - "slices" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -// Represents a file or directory in a file tree. -type Node[T any] struct { - // File will be nil if the node is a directory. - File *T - - // If the node is a directory, Children contains the contents of the directory, - // otherwise it's nil. - Children []*Node[T] - - // path of the file/directory - // private; use either GetPath() or GetInternalPath() to access - path string - - // rather than render a tree as: - // a/ - // b/ - // file.blah - // - // we instead render it as: - // a/b/ - // file.blah - // This saves vertical space. The CompressionLevel of a node is equal to the - // number of times a 'compression' like the above has happened, where two - // nodes are squished into one. - CompressionLevel int -} - -var _ types.ListItem = &Node[models.File]{} - -func (self *Node[T]) IsFile() bool { - return self.File != nil -} - -func (self *Node[T]) GetFile() *T { - return self.File -} - -// This returns the logical path from the user's point of view. It is the -// relative path from the root of the repository. -// Use this for display, or when you want to perform some action on the path -// (e.g. a git command). -func (self *Node[T]) GetPath() string { - return strings.TrimPrefix(self.path, "./") -} - -// This returns the internal path from the tree's point of view. It's the same -// as GetPath(), but prefixed with "./" for the root item. -// Use this when interacting with the tree itself, e.g. when calling -// ToggleCollapsed. -func (self *Node[T]) GetInternalPath() string { - return self.path -} - -func (self *Node[T]) Sort() { - self.SortChildren() - - for _, child := range self.Children { - child.Sort() - } -} - -func (self *Node[T]) ForEachFile(cb func(*T) error) error { - if self.IsFile() { - if err := cb(self.File); err != nil { - return err - } - } - - for _, child := range self.Children { - if err := child.ForEachFile(cb); err != nil { - return err - } - } - - return nil -} - -func (self *Node[T]) SortChildren() { - if self.IsFile() { - return - } - - children := slices.Clone(self.Children) - - slices.SortFunc(children, func(a, b *Node[T]) int { - if !a.IsFile() && b.IsFile() { - return -1 - } - if a.IsFile() && !b.IsFile() { - return 1 - } - - return strings.Compare(a.path, b.path) - }) - - // TODO: think about making this in-place - self.Children = children -} - -func (self *Node[T]) Some(predicate func(*Node[T]) bool) bool { - if predicate(self) { - return true - } - - for _, child := range self.Children { - if child.Some(predicate) { - return true - } - } - - return false -} - -func (self *Node[T]) SomeFile(predicate func(*T) bool) bool { - if self.IsFile() { - if predicate(self.File) { - return true - } - } else { - for _, child := range self.Children { - if child.SomeFile(predicate) { - return true - } - } - } - - return false -} - -func (self *Node[T]) Every(predicate func(*Node[T]) bool) bool { - if !predicate(self) { - return false - } - - for _, child := range self.Children { - if !child.Every(predicate) { - return false - } - } - - return true -} - -func (self *Node[T]) EveryFile(predicate func(*T) bool) bool { - if self.IsFile() { - if !predicate(self.File) { - return false - } - } else { - for _, child := range self.Children { - if !child.EveryFile(predicate) { - return false - } - } - } - - return true -} - -func (self *Node[T]) FindFirstFileBy(predicate func(*T) bool) *T { - if self.IsFile() { - if predicate(self.File) { - return self.File - } - } else { - for _, child := range self.Children { - if file := child.FindFirstFileBy(predicate); file != nil { - return file - } - } - } - - return nil -} - -func (self *Node[T]) Flatten(collapsedPaths *CollapsedPaths) []*Node[T] { - result := []*Node[T]{self} - - if len(self.Children) > 0 && !collapsedPaths.IsCollapsed(self.path) { - result = append(result, lo.FlatMap(self.Children, func(child *Node[T], _ int) []*Node[T] { - return child.Flatten(collapsedPaths) - })...) - } - - return result -} - -func (self *Node[T]) GetNodeAtIndex(index int, collapsedPaths *CollapsedPaths) *Node[T] { - if self == nil { - return nil - } - - node, _ := self.getNodeAtIndexAux(index, collapsedPaths) - - return node -} - -func (self *Node[T]) getNodeAtIndexAux(index int, collapsedPaths *CollapsedPaths) (*Node[T], int) { - offset := 1 - - if index == 0 { - return self, offset - } - - if !collapsedPaths.IsCollapsed(self.path) { - for _, child := range self.Children { - foundNode, offsetChange := child.getNodeAtIndexAux(index-offset, collapsedPaths) - offset += offsetChange - if foundNode != nil { - return foundNode, offset - } - } - } - - return nil, offset -} - -func (self *Node[T]) GetIndexForPath(path string, collapsedPaths *CollapsedPaths) (int, bool) { - offset := 0 - - if self.path == path { - return offset, true - } - - if !collapsedPaths.IsCollapsed(self.path) { - for _, child := range self.Children { - offsetChange, found := child.GetIndexForPath(path, collapsedPaths) - offset += offsetChange + 1 - if found { - return offset, true - } - } - } - - return offset, false -} - -func (self *Node[T]) Size(collapsedPaths *CollapsedPaths) int { - if self == nil { - return 0 - } - - output := 1 - - if !collapsedPaths.IsCollapsed(self.path) { - for _, child := range self.Children { - output += child.Size(collapsedPaths) - } - } - - return output -} - -func (self *Node[T]) Compress() { - if self == nil { - return - } - - self.compressAux() -} - -func (self *Node[T]) compressAux() *Node[T] { - if self.IsFile() { - return self - } - - children := self.Children - for i := range children { - grandchildren := children[i].Children - for len(grandchildren) == 1 && !grandchildren[0].IsFile() { - grandchildren[0].CompressionLevel = children[i].CompressionLevel + 1 - children[i] = grandchildren[0] - grandchildren = children[i].Children - } - } - - for i := range children { - children[i] = children[i].compressAux() - } - - self.Children = children - - return self -} - -func (self *Node[T]) GetPathsMatching(predicate func(*Node[T]) bool) []string { - paths := []string{} - - if predicate(self) { - paths = append(paths, self.GetPath()) - } - - for _, child := range self.Children { - paths = append(paths, child.GetPathsMatching(predicate)...) - } - - return paths -} - -func (self *Node[T]) GetFilePathsMatching(predicate func(*T) bool) []string { - matchingFileNodes := lo.Filter(self.GetLeaves(), func(node *Node[T], _ int) bool { - return predicate(node.File) - }) - - return lo.Map(matchingFileNodes, func(node *Node[T], _ int) string { - return node.GetPath() - }) -} - -func (self *Node[T]) GetLeaves() []*Node[T] { - if self.IsFile() { - return []*Node[T]{self} - } - - return lo.FlatMap(self.Children, func(child *Node[T], _ int) []*Node[T] { - return child.GetLeaves() - }) -} - -func (self *Node[T]) ID() string { - return self.GetPath() -} - -func (self *Node[T]) Description() string { - return self.GetPath() -} - -func (self *Node[T]) Name() string { - return path.Base(self.path) -} diff --git a/pkg/gui/global_handlers.go b/pkg/gui/global_handlers.go deleted file mode 100644 index 9b6551d33cf..00000000000 --- a/pkg/gui/global_handlers.go +++ /dev/null @@ -1,210 +0,0 @@ -package gui - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -const HORIZONTAL_SCROLL_FACTOR = 3 - -func (gui *Gui) scrollUpView(view *gocui.View) { - view.ScrollUp(gui.c.UserConfig().Gui.ScrollHeight) -} - -func (gui *Gui) scrollDownView(view *gocui.View) { - scrollHeight := gui.c.UserConfig().Gui.ScrollHeight - view.ScrollDown(scrollHeight) - - if manager := gui.getViewBufferManagerForView(view); manager != nil { - manager.ReadLines(scrollHeight) - } -} - -func (gui *Gui) scrollUpMain() error { - var view *gocui.View - if gui.c.Context().Current().GetWindowName() == "secondary" { - view = gui.secondaryView() - } else { - view = gui.mainView() - } - - if view.Name() == "mergeConflicts" { - // although we have this same logic in the controller, this method can be invoked - // via the global scroll up/down keybindings, as opposed to just the mouse wheel keybinding. - // It would be nice to have a concept of a global keybinding that runs on the top context in a - // window but that might be overkill for this one use case. - gui.State.Contexts.MergeConflicts.SetUserScrolling(true) - } - - gui.scrollUpView(view) - - return nil -} - -func (gui *Gui) scrollDownMain() error { - var view *gocui.View - if gui.c.Context().Current().GetWindowName() == "secondary" { - view = gui.secondaryView() - } else { - view = gui.mainView() - } - - if view.Name() == "mergeConflicts" { - gui.State.Contexts.MergeConflicts.SetUserScrolling(true) - } - - gui.scrollDownView(view) - - return nil -} - -func (gui *Gui) mainView() *gocui.View { - viewName := gui.helpers.Window.GetViewNameForWindow("main") - view, _ := gui.g.View(viewName) - return view -} - -func (gui *Gui) secondaryView() *gocui.View { - viewName := gui.helpers.Window.GetViewNameForWindow("secondary") - view, _ := gui.g.View(viewName) - return view -} - -func (gui *Gui) scrollUpSecondary() error { - gui.scrollUpView(gui.secondaryView()) - - return nil -} - -func (gui *Gui) scrollDownSecondary() error { - secondaryView := gui.secondaryView() - - gui.scrollDownView(secondaryView) - - return nil -} - -func (gui *Gui) scrollUpConfirmationPanel() error { - gui.scrollUpView(gui.Views.Confirmation) - - return nil -} - -func (gui *Gui) scrollDownConfirmationPanel() error { - gui.scrollDownView(gui.Views.Confirmation) - - return nil -} - -func (gui *Gui) pageUpConfirmationPanel() error { - gui.Views.Confirmation.ScrollUp(gui.Contexts().Confirmation.GetViewTrait().PageDelta()) - - return nil -} - -func (gui *Gui) pageDownConfirmationPanel() error { - gui.Views.Confirmation.ScrollDown(gui.Contexts().Confirmation.GetViewTrait().PageDelta()) - - return nil -} - -func (gui *Gui) goToConfirmationPanelTop() error { - gui.Views.Confirmation.ScrollUp(gui.Views.Confirmation.ViewLinesHeight()) - - return nil -} - -func (gui *Gui) goToConfirmationPanelBottom() error { - gui.Views.Confirmation.ScrollDown(gui.Views.Confirmation.ViewLinesHeight()) - - return nil -} - -func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error { - return gui.handleCopySelectedSideContextItemToClipboardWithTruncation(-1) -} - -func (gui *Gui) handleCopySelectedSideContextItemCommitHashToClipboard() error { - return gui.handleCopySelectedSideContextItemToClipboardWithTruncation( - gui.UserConfig().Git.TruncateCopiedCommitHashesTo) -} - -func (gui *Gui) handleCopySelectedSideContextItemToClipboardWithTruncation(maxWidth int) error { - // important to note that this assumes we've selected an item in a side context - currentSideContext := gui.c.Context().CurrentSide() - if currentSideContext == nil { - return nil - } - - listContext, ok := currentSideContext.(types.IListContext) - if !ok { - return nil - } - - itemId := listContext.GetSelectedItemId() - - if itemId == "" { - return nil - } - - if maxWidth > 0 { - itemId = itemId[:min(len(itemId), maxWidth)] - } - - gui.c.LogAction(gui.c.Tr.Actions.CopyToClipboard) - if err := gui.os.CopyToClipboard(itemId); err != nil { - return err - } - - truncatedItemId := utils.TruncateWithEllipsis(strings.ReplaceAll(itemId, "\n", " "), 50) - - gui.c.Toast(fmt.Sprintf("'%s' %s", truncatedItemId, gui.c.Tr.CopiedToClipboard)) - - return nil -} - -func (gui *Gui) getCopySelectedSideContextItemToClipboardDisabledReason() *types.DisabledReason { - // important to note that this assumes we've selected an item in a side context - currentSideContext := gui.c.Context().CurrentSide() - if currentSideContext == nil { - // This should never happen but if it does we'll just ignore the keypress - return nil - } - - listContext, ok := currentSideContext.(types.IListContext) - if !ok { - // This should never happen but if it does we'll just ignore the keypress - return nil - } - - startIdx, endIdx := listContext.GetList().GetSelectionRange() - if startIdx != endIdx { - return &types.DisabledReason{Text: gui.Tr.RangeSelectNotSupported} - } - - return nil -} - -func (gui *Gui) setCaption(caption string) { - gui.Views.Options.FgColor = gocui.ColorWhite - gui.Views.Options.FgColor |= gocui.AttrBold - gui.Views.Options.SetContent(captionPrefix + " " + style.FgCyan.SetBold().Sprint(caption)) - gui.c.Render() -} - -var captionPrefix = "" - -func (gui *Gui) setCaptionPrefix(prefix string) { - gui.Views.Options.FgColor = gocui.ColorWhite - gui.Views.Options.FgColor |= gocui.AttrBold - - captionPrefix = prefix - - gui.Views.Options.SetContent(prefix) - gui.c.Render() -} diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go deleted file mode 100644 index 2727df3c45a..00000000000 --- a/pkg/gui/gui.go +++ /dev/null @@ -1,1122 +0,0 @@ -package gui - -import ( - goContext "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "reflect" - "regexp" - "sort" - "strings" - "sync" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazycore/pkg/boxlayout" - appTypes "github.com/jesseduffield/lazygit/pkg/app/types" - "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/git_config" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking" - "github.com/jesseduffield/lazygit/pkg/gui/modes/diffing" - "github.com/jesseduffield/lazygit/pkg/gui/modes/filtering" - "github.com/jesseduffield/lazygit/pkg/gui/modes/marked_base_commit" - "github.com/jesseduffield/lazygit/pkg/gui/popup" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/authors" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/graph" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands" - "github.com/jesseduffield/lazygit/pkg/gui/status" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/integration/components" - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" - "github.com/jesseduffield/lazygit/pkg/tasks" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/updates" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/sasha-s/go-deadlock" - "gopkg.in/ozeidan/fuzzy-patricia.v3/patricia" -) - -const StartupPopupVersion = 5 - -// OverlappingEdges determines if panel edges overlap -var OverlappingEdges = false - -type Repo string - -// Gui wraps the gocui Gui object which handles rendering and events -type Gui struct { - *common.Common - g *gocui.Gui - gitVersion *git_commands.GitVersion - git *commands.GitCommand - os *oscommands.OSCommand - - // this is the state of the GUI for the current repo - State *GuiRepoState - - pagerConfig *config.PagerConfig - - CustomCommandsClient *custom_commands.Client - - // this is a mapping of repos to gui states, so that we can restore the original - // gui state when returning from a subrepo. - // In repos with multiple worktrees, we store a separate repo state per worktree. - RepoStateMap map[Repo]*GuiRepoState - Config config.AppConfigurer - Updater *updates.Updater - statusManager *status.StatusManager - waitForIntro sync.WaitGroup - viewBufferManagerMap map[string]*tasks.ViewBufferManager - // holds a mapping of view names to ptmx's. This is for rendering command outputs - // from within a pty. The point of keeping track of them is so that if we re-size - // the window, we can tell the pty it needs to resize accordingly. - viewPtmxMap map[string]*os.File - stopChan chan struct{} - - // when lazygit is opened outside a git directory we want to open to the most - // recent repo with the recent repos popup showing - showRecentRepos bool - - Mutexes types.Mutexes - - // when you enter into a submodule we'll append the superproject's path to this array - // so that you can return to the superproject - RepoPathStack *utils.StringStack - - // this tells us whether our views have been initially set up - ViewsSetup bool - - Views types.Views - - // Log of the commands/actions logged in the Command Log panel. - GuiLog []string - - // the extras window contains things like the command log - ShowExtrasWindow bool - - PopupHandler types.IPopupHandler - - IsRefreshingFiles bool - - // we use this to decide whether we'll return to the original directory that - // lazygit was opened in, or if we'll retain the one we're currently in. - RetainOriginalDir bool - - // stores long-running operations associated with items (e.g. when a branch - // is being pushed). At the moment the rule is to use an item operation when - // we need to talk to the remote. - itemOperations map[string]types.ItemOperation - itemOperationsMutex deadlock.Mutex - - PrevLayout PrevLayout - - // this is the initial dir we are in upon opening lazygit. We hold onto this - // in case we want to restore it before quitting for users who have set up - // the feature for changing directory upon quit. - // The reason we don't just wait until quit time to handle changing directories - // is because some users want to keep track of the current lazygit directory in an outside - // process - InitialDir string - - BackgroundRoutineMgr *BackgroundRoutineMgr - // for accessing the gui's state from outside this package - stateAccessor *StateAccessor - - Updating bool - - c *helpers.HelperCommon - helpers *helpers.Helpers - - previousLanguageConfig string - - integrationTest integrationTypes.IntegrationTest - - afterLayoutFuncs chan func() error -} - -type StateAccessor struct { - gui *Gui -} - -var _ types.IStateAccessor = new(StateAccessor) - -func (self *StateAccessor) GetRepoPathStack() *utils.StringStack { - return self.gui.RepoPathStack -} - -func (self *StateAccessor) GetUpdating() bool { - return self.gui.Updating -} - -func (self *StateAccessor) SetUpdating(value bool) { - self.gui.Updating = value -} - -func (self *StateAccessor) GetRepoState() types.IRepoStateAccessor { - return self.gui.State -} - -func (self *StateAccessor) GetPagerConfig() *config.PagerConfig { - return self.gui.pagerConfig -} - -func (self *StateAccessor) GetIsRefreshingFiles() bool { - return self.gui.IsRefreshingFiles -} - -func (self *StateAccessor) SetIsRefreshingFiles(value bool) { - self.gui.IsRefreshingFiles = value -} - -func (self *StateAccessor) GetShowExtrasWindow() bool { - return self.gui.ShowExtrasWindow -} - -func (self *StateAccessor) SetShowExtrasWindow(value bool) { - self.gui.ShowExtrasWindow = value -} - -func (self *StateAccessor) GetRetainOriginalDir() bool { - return self.gui.RetainOriginalDir -} - -func (self *StateAccessor) SetRetainOriginalDir(value bool) { - self.gui.RetainOriginalDir = value -} - -func (self *StateAccessor) GetItemOperation(item types.HasUrn) types.ItemOperation { - self.gui.itemOperationsMutex.Lock() - defer self.gui.itemOperationsMutex.Unlock() - - return self.gui.itemOperations[item.URN()] -} - -func (self *StateAccessor) SetItemOperation(item types.HasUrn, operation types.ItemOperation) { - self.gui.itemOperationsMutex.Lock() - defer self.gui.itemOperationsMutex.Unlock() - - self.gui.itemOperations[item.URN()] = operation -} - -func (self *StateAccessor) ClearItemOperation(item types.HasUrn) { - self.gui.itemOperationsMutex.Lock() - defer self.gui.itemOperationsMutex.Unlock() - - delete(self.gui.itemOperations, item.URN()) -} - -// we keep track of some stuff from one render to the next to see if certain -// things have changed -type PrevLayout struct { - Information string - MainWidth int - MainHeight int -} - -type GuiRepoState struct { - Model *types.Model - Modes *types.Modes - - SplitMainPanel bool - LimitCommits bool - - SearchState *types.SearchState - StartupStage types.StartupStage // Allows us to not load everything at once - - ContextMgr *ContextMgr - Contexts *context.ContextTree - - // WindowViewNameMap is a mapping of windows to the current view of that window. - // Some views move between windows for example the commitFiles view and when cycling through - // side windows we need to know which view to give focus to for a given window - WindowViewNameMap *utils.ThreadSafeMap[string, string] - - // tells us whether we've set up our views for the current repo. We'll need to - // do this whenever we switch back and forth between repos to get the views - // back in sync with the repo state - ViewsSetup bool - - ScreenMode types.ScreenMode - - CurrentPopupOpts *types.CreatePopupPanelOpts -} - -var _ types.IRepoStateAccessor = new(GuiRepoState) - -func (self *GuiRepoState) GetViewsSetup() bool { - return self.ViewsSetup -} - -func (self *GuiRepoState) GetWindowViewNameMap() *utils.ThreadSafeMap[string, string] { - return self.WindowViewNameMap -} - -func (self *GuiRepoState) GetStartupStage() types.StartupStage { - return self.StartupStage -} - -func (self *GuiRepoState) SetStartupStage(value types.StartupStage) { - self.StartupStage = value -} - -func (self *GuiRepoState) GetCurrentPopupOpts() *types.CreatePopupPanelOpts { - return self.CurrentPopupOpts -} - -func (self *GuiRepoState) SetCurrentPopupOpts(value *types.CreatePopupPanelOpts) { - self.CurrentPopupOpts = value -} - -func (self *GuiRepoState) GetScreenMode() types.ScreenMode { - return self.ScreenMode -} - -func (self *GuiRepoState) SetScreenMode(value types.ScreenMode) { - self.ScreenMode = value -} - -func (self *GuiRepoState) InSearchPrompt() bool { - return self.SearchState.SearchType() != types.SearchTypeNone -} - -func (self *GuiRepoState) GetSearchState() *types.SearchState { - return self.SearchState -} - -func (self *GuiRepoState) SetSplitMainPanel(value bool) { - self.SplitMainPanel = value -} - -func (self *GuiRepoState) GetSplitMainPanel() bool { - return self.SplitMainPanel -} - -func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.ContextKey) error { - var err error - gui.git, err = commands.NewGitCommand( - gui.Common, - gui.gitVersion, - gui.os, - git_config.NewStdCachedGitConfig(gui.Log), - gui.pagerConfig, - ) - if err != nil { - return err - } - - err = gui.Config.ReloadUserConfigForRepo(gui.getPerRepoConfigFiles()) - if err != nil { - return err - } - - err = gui.onUserConfigLoaded() - if err != nil { - return err - } - - contextToPush := gui.resetState(startArgs) - - gui.resetHelpersAndControllers() - - if err := gui.resetKeybindings(); err != nil { - return err - } - - gui.g.SetFocusHandler(func(Focused bool) error { - if Focused { - gui.git.Config.DropConfigCache() - - oldConfig := gui.Config.GetUserConfig() - reloadErr, didChange := gui.Config.ReloadChangedUserConfigFiles() - if didChange && reloadErr == nil { - gui.c.Log.Info("User config changed - reloading") - reloadErr = gui.onUserConfigLoaded() - if err := gui.resetKeybindings(); err != nil { - return err - } - - if err := gui.checkForChangedConfigsThatDontAutoReload(oldConfig, gui.Config.GetUserConfig()); err != nil { - return err - } - } - - gui.c.Log.Info("Receiving focus - refreshing") - gui.helpers.Refresh.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return reloadErr - } - - return nil - }) - - gui.g.SetOpenHyperlinkFunc(func(url string, viewname string) error { - if strings.HasPrefix(url, "lazygit-edit:") { - re := regexp.MustCompile(`^lazygit-edit://(.+?)(?::(\d+))?$`) - matches := re.FindStringSubmatch(url) - if matches == nil { - return fmt.Errorf(gui.Tr.InvalidLazygitEditURL, url) - } - filepath := matches[1] - if matches[2] != "" { - lineNumber := utils.MustConvertToInt(matches[2]) - lineNumber = gui.helpers.Diff.AdjustLineNumber(filepath, lineNumber, viewname) - return gui.helpers.Files.EditFileAtLine(filepath, lineNumber) - } - return gui.helpers.Files.EditFiles([]string{filepath}) - } - - if err := gui.os.OpenLink(url); err != nil { - return fmt.Errorf(gui.Tr.FailedToOpenURL, url, err) - } - - return nil - }) - - // if a context key has been given, push that instead, and set its index to 0 - if contextKey != context.NO_CONTEXT { - contextToPush = gui.c.ContextForKey(contextKey) - // when we pass a list context, the expectation is that our cursor goes to the top, - // because e.g. with worktrees, we'll show the current worktree at the top of the list. - listContext, ok := contextToPush.(types.IListContext) - if ok { - listContext.GetList().SetSelection(0) - } - } - - gui.c.Context().Push(contextToPush, types.OnFocusOpts{}) - - return nil -} - -func (gui *Gui) getPerRepoConfigFiles() []*config.ConfigFile { - repoConfigFiles := []*config.ConfigFile{ - // TODO: add filepath.Join(gui.git.RepoPaths.RepoPath(), ".lazygit.yml"), - // with trust prompt - { - Path: filepath.Join(gui.git.RepoPaths.RepoGitDirPath(), "lazygit.yml"), - Policy: config.ConfigFilePolicySkipIfMissing, - }, - } - - prevDir := gui.c.Git().RepoPaths.RepoPath() - dir := filepath.Dir(prevDir) - for dir != prevDir { - repoConfigFiles = utils.Prepend(repoConfigFiles, &config.ConfigFile{ - Path: filepath.Join(dir, ".lazygit.yml"), - Policy: config.ConfigFilePolicySkipIfMissing, - }) - prevDir = dir - dir = filepath.Dir(dir) - } - return repoConfigFiles -} - -func (gui *Gui) onUserConfigLoaded() error { - userConfig := gui.Config.GetUserConfig() - gui.Common.SetUserConfig(userConfig) - - if gui.previousLanguageConfig != userConfig.Gui.Language { - tr, err := i18n.NewTranslationSetFromConfig(gui.Log, userConfig.Gui.Language) - if err != nil { - return err - } - gui.c.Tr = tr - gui.previousLanguageConfig = userConfig.Gui.Language - } - - gui.setColorScheme() - gui.configureViewProperties() - - gui.g.SearchEscapeKey = keybindings.GetKey(userConfig.Keybinding.Universal.Return) - gui.g.NextSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.NextMatch) - gui.g.PrevSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.PrevMatch) - - gui.g.ShowListFooter = userConfig.Gui.ShowListFooter - - gui.g.Mouse = userConfig.Gui.MouseEvents - - // originally we could only hide the command log permanently via the config - // but now we do it via state. So we need to still support the config for the - // sake of backwards compatibility. We're making use of short circuiting here - gui.ShowExtrasWindow = userConfig.Gui.ShowCommandLog && !gui.c.GetAppState().HideCommandLog - - authors.SetCustomAuthors(userConfig.Gui.AuthorColors) - if userConfig.Gui.NerdFontsVersion != "" { - icons.SetNerdFontsVersion(userConfig.Gui.NerdFontsVersion) - } else if userConfig.Gui.ShowIcons { - icons.SetNerdFontsVersion("2") - } - - if len(userConfig.Gui.BranchColorPatterns) > 0 { - presentation.SetCustomBranches(userConfig.Gui.BranchColorPatterns, true) - } else { - // Fall back to the deprecated branchColors config - presentation.SetCustomBranches(userConfig.Gui.BranchColors, false) - } - - return nil -} - -func (gui *Gui) checkForChangedConfigsThatDontAutoReload(oldConfig *config.UserConfig, newConfig *config.UserConfig) error { - configsThatDontAutoReload := []string{ - "Git.AutoFetch", - "Git.AutoRefresh", - "Refresher.RefreshInterval", - "Refresher.FetchInterval", - "Update.Method", - "Update.Days", - } - - changedConfigs := []string{} - for _, config := range configsThatDontAutoReload { - old := reflect.ValueOf(oldConfig).Elem() - new := reflect.ValueOf(newConfig).Elem() - fieldNames := strings.Split(config, ".") - userFacingPath := make([]string, 0, len(fieldNames)) - // navigate to the leaves in old and new config - for _, fieldName := range fieldNames { - f, _ := old.Type().FieldByName(fieldName) - userFacingName := f.Tag.Get("yaml") - if userFacingName == "" { - userFacingName = fieldName - } - userFacingPath = append(userFacingPath, userFacingName) - old = old.FieldByName(fieldName) - new = new.FieldByName(fieldName) - } - // if the value has changed, ... - if !old.Equal(new) { - // ... append it to the list of changed configs - changedConfigs = append(changedConfigs, strings.Join(userFacingPath, ".")) - } - } - - if len(changedConfigs) == 0 { - return nil - } - - message := utils.ResolvePlaceholderString( - gui.c.Tr.NonReloadableConfigWarning, - map[string]string{ - "configs": strings.Join(changedConfigs, "\n"), - }, - ) - gui.c.Confirm(types.ConfirmOpts{ - Title: gui.c.Tr.NonReloadableConfigWarningTitle, - Prompt: message, - }) - - return nil -} - -// resetState reuses the repo state from our repo state map, if the repo was -// open before; otherwise it creates a new one. -func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context { - // Un-highlight the current view if there is one. The reason we do this is - // that the repo we are switching to might have a different view focused, - // and would then show an inactive highlight for the previous view. - if oldCurrentView := gui.g.CurrentView(); oldCurrentView != nil { - oldCurrentView.Highlight = false - } - - worktreePath := gui.git.RepoPaths.WorktreePath() - - if state := gui.RepoStateMap[Repo(worktreePath)]; state != nil { - gui.State = state - gui.State.ViewsSetup = false - - contextTree := gui.State.Contexts - gui.State.WindowViewNameMap = initialWindowViewNameMap(contextTree) - - // setting this to nil so we don't get stuck based on a popup that was - // previously opened - gui.Mutexes.PopupMutex.Lock() - gui.State.CurrentPopupOpts = nil - gui.Mutexes.PopupMutex.Unlock() - - return gui.c.Context().Current() - } - - contextTree := gui.contextTree() - - initialScreenMode := initialScreenMode(startArgs, gui.Config) - - gui.State = &GuiRepoState{ - ViewsSetup: false, - Model: &types.Model{ - CommitFiles: nil, - Files: make([]*models.File, 0), - Commits: make([]*models.Commit, 0), - StashEntries: make([]*models.StashEntry, 0), - FilteredReflogCommits: make([]*models.Commit, 0), - ReflogCommits: make([]*models.Commit, 0), - BisectInfo: git_commands.NewNullBisectInfo(), - FilesTrie: patricia.NewTrie(), - Authors: map[string]*models.Author{}, - MainBranches: git_commands.NewMainBranches(gui.c.Common, gui.os.Cmd), - HashPool: &utils.StringPool{}, - }, - Modes: &types.Modes{ - Filtering: filtering.New(startArgs.FilterPath, ""), - CherryPicking: cherrypicking.New(), - Diffing: diffing.New(), - MarkedBaseCommit: marked_base_commit.New(), - }, - ScreenMode: initialScreenMode, - // TODO: only use contexts from context manager - ContextMgr: NewContextMgr(gui, contextTree), - Contexts: contextTree, - WindowViewNameMap: initialWindowViewNameMap(contextTree), - SearchState: types.NewSearchState(), - } - - gui.RepoStateMap[Repo(worktreePath)] = gui.State - - return initialContext(contextTree, startArgs) -} - -func (gui *Gui) getViewBufferManagerForView(view *gocui.View) *tasks.ViewBufferManager { - manager, ok := gui.viewBufferManagerMap[view.Name()] - if !ok { - return nil - } - - return manager -} - -func initialWindowViewNameMap(contextTree *context.ContextTree) *utils.ThreadSafeMap[string, string] { - result := utils.NewThreadSafeMap[string, string]() - - for _, context := range contextTree.Flatten() { - result.Set(context.GetWindowName(), context.GetViewName()) - } - - return result -} - -func initialScreenMode(startArgs appTypes.StartArgs, config config.AppConfigurer) types.ScreenMode { - if startArgs.ScreenMode != "" { - return parseScreenModeArg(startArgs.ScreenMode) - } else if startArgs.FilterPath != "" || startArgs.GitArg != appTypes.GitArgNone { - return types.SCREEN_HALF - } - - return parseScreenModeArg(config.GetUserConfig().Gui.ScreenMode) -} - -func parseScreenModeArg(screenModeArg string) types.ScreenMode { - switch screenModeArg { - case "half": - return types.SCREEN_HALF - case "full": - return types.SCREEN_FULL - default: - return types.SCREEN_NORMAL - } -} - -func initialContext(contextTree *context.ContextTree, startArgs appTypes.StartArgs) types.IListContext { - var initialContext types.IListContext = contextTree.Files - - if startArgs.FilterPath != "" { - initialContext = contextTree.LocalCommits - } else if startArgs.GitArg != appTypes.GitArgNone { - switch startArgs.GitArg { - case appTypes.GitArgStatus: - initialContext = contextTree.Files - case appTypes.GitArgBranch: - initialContext = contextTree.Branches - case appTypes.GitArgLog: - initialContext = contextTree.LocalCommits - case appTypes.GitArgStash: - initialContext = contextTree.Stash - default: - panic("unhandled git arg") - } - } - - return initialContext -} - -func (gui *Gui) Contexts() *context.ContextTree { - return gui.State.Contexts -} - -// for now the split view will always be on -// NewGui builds a new gui handler -func NewGui( - cmn *common.Common, - configurer config.AppConfigurer, - gitVersion *git_commands.GitVersion, - updater *updates.Updater, - showRecentRepos bool, - initialDir string, - test integrationTypes.IntegrationTest, -) (*Gui, error) { - gui := &Gui{ - Common: cmn, - gitVersion: gitVersion, - Config: configurer, - Updater: updater, - statusManager: status.NewStatusManager(), - viewBufferManagerMap: map[string]*tasks.ViewBufferManager{}, - viewPtmxMap: map[string]*os.File{}, - showRecentRepos: showRecentRepos, - RepoPathStack: &utils.StringStack{}, - RepoStateMap: map[Repo]*GuiRepoState{}, - GuiLog: []string{}, - - // initializing this to true for the time being; it will be reset to the - // real value after loading the user config: - ShowExtrasWindow: true, - - InitialDir: initialDir, - afterLayoutFuncs: make(chan func() error, 1000), - - itemOperations: make(map[string]types.ItemOperation), - } - - gui.PopupHandler = popup.NewPopupHandler( - cmn, - func(ctx goContext.Context, opts types.CreatePopupPanelOpts) { - gui.helpers.Confirmation.CreatePopupPanel(ctx, opts) - }, - func() error { gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); return nil }, - func() { gui.State.ContextMgr.Pop() }, - func() types.Context { return gui.State.ContextMgr.Current() }, - gui.createMenu, - func(message string, f func(gocui.Task) error) { gui.helpers.AppStatus.WithWaitingStatus(message, f) }, - func(message string, f func() error) error { - return gui.helpers.AppStatus.WithWaitingStatusSync(message, f) - }, - func(message string, kind types.ToastKind) { gui.helpers.AppStatus.Toast(message, kind) }, - func() string { return gui.Views.Prompt.TextArea.GetContent() }, - func() bool { return gui.c.InDemo() }, - ) - - guiCommon := &guiCommon{gui: gui, IPopupHandler: gui.PopupHandler} - helperCommon := &helpers.HelperCommon{IGuiCommon: guiCommon, Common: cmn, IGetContexts: gui} - - credentialsHelper := helpers.NewCredentialsHelper(helperCommon) - - guiIO := oscommands.NewGuiIO( - cmn.Log, - gui.LogCommand, - gui.getCmdWriter, - credentialsHelper.PromptUserForCredential, - ) - - osCommand := oscommands.NewOSCommand(cmn, configurer, oscommands.GetPlatform(), guiIO) - - gui.os = osCommand - - // storing this stuff on the gui for now to ease refactoring - // TODO: reset these controllers upon changing repos due to state changing - gui.c = helperCommon - - gui.BackgroundRoutineMgr = &BackgroundRoutineMgr{gui: gui} - gui.stateAccessor = &StateAccessor{gui: gui} - - gui.pagerConfig = config.NewPagerConfig(func() *config.UserConfig { return gui.UserConfig() }) - - return gui, nil -} - -var RuneReplacements = map[rune]string{ - // for the commit graph - graph.MergeSymbol: "M", - graph.CommitSymbol: "o", -} - -func (gui *Gui) initGocui(headless bool, test integrationTypes.IntegrationTest) (*gocui.Gui, error) { - runInSandbox := os.Getenv(components.SANDBOX_ENV_VAR) == "true" - playRecording := test != nil && !runInSandbox - - width, height := 0, 0 - if test != nil { - if test.RequiresHeadless() { - if runInSandbox { - panic("Test requires headless, can't run in sandbox") - } - headless = true - } - width, height = test.HeadlessDimensions() - } - - g, err := gocui.NewGui(gocui.NewGuiOpts{ - OutputMode: gocui.OutputTrue, - SupportOverlaps: OverlappingEdges, - PlayRecording: playRecording, - Headless: headless, - RuneReplacements: RuneReplacements, - Width: width, - Height: height, - }) - if err != nil { - return nil, err - } - - return g, nil -} - -func (gui *Gui) viewTabMap() map[string][]context.TabView { - result := map[string][]context.TabView{ - "branches": { - { - Tab: gui.c.Tr.LocalBranchesTitle, - ViewName: "localBranches", - }, - { - Tab: gui.c.Tr.RemotesTitle, - ViewName: "remotes", - }, - { - Tab: gui.c.Tr.TagsTitle, - ViewName: "tags", - }, - }, - "commits": { - { - Tab: gui.c.Tr.CommitsTitle, - ViewName: "commits", - }, - { - Tab: gui.c.Tr.ReflogCommitsTitle, - ViewName: "reflogCommits", - }, - }, - "files": { - { - Tab: gui.c.Tr.FilesTitle, - ViewName: "files", - }, - context.TabView{ - Tab: gui.c.Tr.WorktreesTitle, - ViewName: "worktrees", - }, - { - Tab: gui.c.Tr.SubmodulesTitle, - ViewName: "submodules", - }, - }, - } - - return result -} - -// Run: setup the gui with keybindings and start the mainloop -func (gui *Gui) Run(startArgs appTypes.StartArgs) error { - g, err := gui.initGocui(Headless(), startArgs.IntegrationTest) - if err != nil { - return err - } - - gui.g = g - defer gui.g.Close() - - g.ErrorHandler = gui.PopupHandler.ErrorHandler - - // if the deadlock package wants to report a deadlock, we first need to - // close the gui so that we can actually read what it prints. - deadlock.Opts.LogBuf = utils.NewOnceWriter(os.Stderr, func() { - gui.g.Close() - }) - // disable deadlock reporting if we're not running in debug mode, or if - // we're debugging an integration test. In this latter case, stopping at - // breakpoints and stepping through code can easily take more than 30s. - deadlock.Opts.Disable = !gui.Debug || os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) != "" - - gui.g.OnSearchEscape = func() error { gui.helpers.Search.Cancel(); return nil } - - gui.g.SetManager(gocui.ManagerFunc(gui.layout)) - - if err := gui.createAllViews(); err != nil { - return err - } - - // onNewRepo must be called after g.SetManager because SetManager deletes keybindings - if err := gui.onNewRepo(startArgs, context.NO_CONTEXT); err != nil { - return err - } - - gui.waitForIntro.Add(1) - - gui.BackgroundRoutineMgr.startBackgroundRoutines() - - gui.Helpers().SuspendResume.InstallResumeSignalHandler() - - gui.c.Log.Info("starting main loop") - - // setting here so we can use it in layout.go - gui.integrationTest = startArgs.IntegrationTest - - return gui.g.MainLoop() -} - -func (gui *Gui) RunAndHandleError(startArgs appTypes.StartArgs) error { - gui.stopChan = make(chan struct{}) - return utils.SafeWithError(func() error { - if err := gui.Run(startArgs); err != nil { - for _, manager := range gui.viewBufferManagerMap { - manager.Close() - } - - close(gui.stopChan) - - if errors.Is(err, gocui.ErrQuit) { - if gui.c.State().GetRetainOriginalDir() { - if err := gui.helpers.RecordDirectory.RecordDirectory(gui.InitialDir); err != nil { - return err - } - } else { - if err := gui.helpers.RecordDirectory.RecordCurrentDirectory(); err != nil { - return err - } - } - - return nil - } - - return err - } - - return nil - }) -} - -// returns whether command exited without error or not -func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess *oscommands.CmdObj) error { - _, err := gui.runSubprocessWithSuspense(subprocess) - if err != nil { - return err - } - - gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - - return nil -} - -func (gui *Gui) suspend() error { - if err := gui.g.Suspend(); err != nil { - return err - } - - gui.BackgroundRoutineMgr.PauseBackgroundRefreshes(true) - return nil -} - -func (gui *Gui) resume() error { - if err := gui.g.Resume(); err != nil { - return err - } - - gui.BackgroundRoutineMgr.PauseBackgroundRefreshes(false) - return nil -} - -// returns whether command exited without error or not -func (gui *Gui) runSubprocessWithSuspense(subprocess *oscommands.CmdObj) (bool, error) { - gui.Mutexes.SubprocessMutex.Lock() - defer gui.Mutexes.SubprocessMutex.Unlock() - - if err := gui.suspend(); err != nil { - return false, err - } - - cmdErr := gui.runSubprocess(subprocess) - - if err := gui.resume(); err != nil { - return false, err - } - - if cmdErr != nil { - return false, cmdErr - } - - return true, nil -} - -func (gui *Gui) runSubprocess(cmdObj *oscommands.CmdObj) error { - gui.LogCommand(cmdObj.ToString(), true) - - subprocess := cmdObj.GetCmd() - subprocess.Stdout = os.Stdout - subprocess.Stderr = os.Stderr - subprocess.Stdin = os.Stdin - - fmt.Fprintf(os.Stdout, "\n%s\n\n", style.FgBlue.Sprint("+ "+strings.Join(subprocess.Args, " "))) - - err := subprocess.Run() - - subprocess.Stdout = io.Discard - subprocess.Stderr = io.Discard - subprocess.Stdin = nil - - if gui.integrationTest == nil && (gui.Config.GetUserConfig().PromptToReturnFromSubprocess || err != nil) { - fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint(gui.Tr.PressEnterToReturn)) - - // scan to buffer to prevent run unintentional operations when TUI resumes. - var buffer string - _, _ = fmt.Scanln(&buffer) // wait for enter press - } - - return err -} - -func (gui *Gui) loadNewRepo() error { - if err := gui.updateRecentRepoList(); err != nil { - return err - } - - gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - - if err := gui.os.UpdateWindowTitle(); err != nil { - return err - } - - return nil -} - -func (gui *Gui) showIntroPopupMessage() { - gui.waitForIntro.Add(1) - - gui.c.OnUIThread(func() error { - onConfirm := func() error { - gui.c.GetAppState().StartupPopupVersion = StartupPopupVersion - err := gui.c.SaveAppState() - gui.waitForIntro.Done() - return err - } - - introMessage := utils.ResolvePlaceholderString( - gui.c.Tr.IntroPopupMessage, - map[string]string{ - "confirmationKey": gui.c.UserConfig().Keybinding.Universal.Confirm, - }, - ) - - gui.c.Confirm(types.ConfirmOpts{ - Title: "", - Prompt: introMessage, - HandleConfirm: onConfirm, - HandleClose: onConfirm, - }) - - return nil - }) -} - -func (gui *Gui) showBreakingChangesMessage() { - _, err := types.ParseVersionNumber(gui.Config.GetVersion()) - if err != nil { - // We don't have a parseable version, so we'll assume it's a developer - // build, or a build from HEAD with a version such as 0.40.0-g1234567; - // in these cases we don't show release notes. - return - } - - last := &types.VersionNumber{} - lastVersionStr := gui.c.GetAppState().LastVersion - // If there's no saved last version, we show all release notes. This is for - // people upgrading from a version before we started to save lastVersion. - // First time new users won't see the release notes because we show them the - // intro popup instead. - if lastVersionStr != "" { - last, err = types.ParseVersionNumber(lastVersionStr) - if err != nil { - // The last version was a developer build, so don't show release - // notes in this case either. - return - } - } - - // Now collect all release notes texts for versions newer than lastVersion. - // We don't need to bother checking the current version here, because we - // can't possibly have texts for versions newer than current. - type versionAndText struct { - version *types.VersionNumber - text string - } - texts := []versionAndText{} - for versionStr, text := range gui.Tr.BreakingChangesByVersion { - v, err := types.ParseVersionNumber(versionStr) - if err != nil { - // Ignore bogus entries in the BreakingChanges map - continue - } - if last.IsOlderThan(v) { - texts = append(texts, versionAndText{version: v, text: text}) - } - } - - if len(texts) > 0 { - sort.Slice(texts, func(i, j int) bool { - return texts[i].version.IsOlderThan(texts[j].version) - }) - message := strings.Join(lo.Map(texts, func(t versionAndText, _ int) string { return t.text }), "\n") - - gui.waitForIntro.Add(1) - gui.c.OnUIThread(func() error { - onConfirm := func() error { - gui.waitForIntro.Done() - return nil - } - - gui.c.Confirm(types.ConfirmOpts{ - Title: gui.Tr.BreakingChangesTitle, - Prompt: gui.Tr.BreakingChangesMessage + "\n\n" + message, - HandleConfirm: onConfirm, - HandleClose: onConfirm, - }) - return nil - }) - } -} - -// setColorScheme sets the color scheme for the app based on the user config -func (gui *Gui) setColorScheme() { - userConfig := gui.UserConfig() - theme.UpdateTheme(userConfig.Gui.Theme) - - gui.g.FgColor = theme.InactiveBorderColor - gui.g.SelFgColor = theme.ActiveBorderColor - gui.g.FrameColor = theme.InactiveBorderColor - gui.g.SelFrameColor = theme.ActiveBorderColor -} - -func (gui *Gui) onUIThread(f func() error) { - gui.g.Update(func(*gocui.Gui) error { - return f() - }) -} - -func (gui *Gui) onWorker(f func(gocui.Task) error) { - gui.g.OnWorker(f) -} - -func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions { - return gui.helpers.WindowArrangement.GetWindowDimensions(informationStr, appStatus) -} - -func (gui *Gui) afterLayout(f func() error) { - select { - case gui.afterLayoutFuncs <- f: - default: - // hopefully this never happens - gui.c.Log.Error("afterLayoutFuncs channel is full, skipping function") - } -} diff --git a/pkg/gui/gui_common.go b/pkg/gui/gui_common.go deleted file mode 100644 index d946659d166..00000000000 --- a/pkg/gui/gui_common.go +++ /dev/null @@ -1,183 +0,0 @@ -package gui - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/tasks" -) - -// hacking this by including the gui struct for now until we split more things out -type guiCommon struct { - gui *Gui - types.IPopupHandler -} - -var _ types.IGuiCommon = &guiCommon{} - -func (self *guiCommon) LogAction(msg string) { - self.gui.LogAction(msg) -} - -func (self *guiCommon) LogCommand(cmdStr string, isCommandLine bool) { - self.gui.LogCommand(cmdStr, isCommandLine) -} - -func (self *guiCommon) Refresh(opts types.RefreshOptions) { - self.gui.helpers.Refresh.Refresh(opts) -} - -func (self *guiCommon) PostRefreshUpdate(context types.Context) { - self.gui.postRefreshUpdate(context) -} - -func (self *guiCommon) RunSubprocessAndRefresh(cmdObj *oscommands.CmdObj) error { - return self.gui.runSubprocessWithSuspenseAndRefresh(cmdObj) -} - -func (self *guiCommon) RunSubprocess(cmdObj *oscommands.CmdObj) (bool, error) { - return self.gui.runSubprocessWithSuspense(cmdObj) -} - -func (self *guiCommon) Suspend() error { - return self.gui.suspend() -} - -func (self *guiCommon) Resume() error { - return self.gui.resume() -} - -func (self *guiCommon) Context() types.IContextMgr { - return self.gui.State.ContextMgr -} - -func (self *guiCommon) ContextForKey(key types.ContextKey) types.Context { - return self.gui.State.ContextMgr.ContextForKey(key) -} - -func (self *guiCommon) GetAppState() *config.AppState { - return self.gui.Config.GetAppState() -} - -func (self *guiCommon) SaveAppState() error { - return self.gui.Config.SaveAppState() -} - -func (self *guiCommon) SaveAppStateAndLogError() { - if err := self.gui.Config.SaveAppState(); err != nil { - self.gui.Log.Errorf("error when saving app state: %v", err) - } -} - -func (self *guiCommon) GetConfig() config.AppConfigurer { - return self.gui.Config -} - -func (self *guiCommon) ResetViewOrigin(view *gocui.View) { - self.gui.resetViewOrigin(view) -} - -func (self *guiCommon) SetViewContent(view *gocui.View, content string) { - self.gui.setViewContent(view, content) -} - -func (self *guiCommon) Render() { - self.gui.render() -} - -func (self *guiCommon) Views() types.Views { - return self.gui.Views -} - -func (self *guiCommon) Git() *commands.GitCommand { - return self.gui.git -} - -func (self *guiCommon) OS() *oscommands.OSCommand { - return self.gui.os -} - -func (self *guiCommon) Modes() *types.Modes { - return self.gui.State.Modes -} - -func (self *guiCommon) Model() *types.Model { - return self.gui.State.Model -} - -func (self *guiCommon) Mutexes() *types.Mutexes { - return &self.gui.Mutexes -} - -func (self *guiCommon) GocuiGui() *gocui.Gui { - return self.gui.g -} - -func (self *guiCommon) OnUIThread(f func() error) { - self.gui.onUIThread(f) -} - -func (self *guiCommon) OnWorker(f func(gocui.Task) error) { - self.gui.onWorker(f) -} - -func (self *guiCommon) RenderToMainViews(opts types.RefreshMainOpts) { - self.gui.refreshMainViews(opts) -} - -func (self *guiCommon) MainViewPairs() types.MainViewPairs { - return types.MainViewPairs{ - Normal: self.gui.normalMainContextPair(), - Staging: self.gui.stagingMainContextPair(), - PatchBuilding: self.gui.patchBuildingMainContextPair(), - MergeConflicts: self.gui.mergingMainContextPair(), - } -} - -func (self *guiCommon) GetViewBufferManagerForView(view *gocui.View) *tasks.ViewBufferManager { - return self.gui.getViewBufferManagerForView(view) -} - -func (self *guiCommon) State() types.IStateAccessor { - return self.gui.stateAccessor -} - -func (self *guiCommon) KeybindingsOpts() types.KeybindingsOpts { - return self.gui.keybindingOpts() -} - -func (self *guiCommon) CallKeybindingHandler(binding *types.Binding) error { - return self.gui.callKeybindingHandler(binding) -} - -func (self *guiCommon) ResetKeybindings() error { - return self.gui.resetKeybindings() -} - -func (self *guiCommon) IsAnyModeActive() bool { - return self.gui.helpers.Mode.IsAnyModeActive() -} - -func (self *guiCommon) GetInitialKeybindingsWithCustomCommands() ([]*types.Binding, []*gocui.ViewMouseBinding) { - return self.gui.GetInitialKeybindingsWithCustomCommands() -} - -func (self *guiCommon) AfterLayout(f func() error) { - self.gui.afterLayout(f) -} - -func (self *guiCommon) RunningIntegrationTest() bool { - return self.gui.integrationTest != nil -} - -func (self *guiCommon) InDemo() bool { - return self.gui.integrationTest != nil && self.gui.integrationTest.IsDemo() -} - -func (self *guiCommon) WithInlineStatus(item types.HasUrn, operation types.ItemOperation, contextKey types.ContextKey, f func(gocui.Task) error) error { - self.gui.helpers.InlineStatus.WithInlineStatus(helpers.InlineStatusOpts{Item: item, Operation: operation, ContextKey: contextKey}, f) - return nil -} diff --git a/pkg/gui/gui_driver.go b/pkg/gui/gui_driver.go deleted file mode 100644 index 35c201a1463..00000000000 --- a/pkg/gui/gui_driver.go +++ /dev/null @@ -1,173 +0,0 @@ -package gui - -import ( - "fmt" - "os" - "strings" - "time" - - "github.com/gdamore/tcell/v2" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" -) - -// this gives our integration test a way of interacting with the gui for sending keypresses -// and reading state. -type GuiDriver struct { - gui *Gui - isIdleChan chan struct{} - toastChan chan string - headless bool -} - -var _ integrationTypes.GuiDriver = &GuiDriver{} - -func (self *GuiDriver) PressKey(keyStr string) { - self.CheckAllToastsAcknowledged() - - key := keybindings.GetKey(keyStr) - - var r rune - var tcellKey tcell.Key - switch v := key.(type) { - case rune: - r = v - tcellKey = tcell.KeyRune - case gocui.Key: - tcellKey = tcell.Key(v) - } - - self.gui.g.ReplayedEvents.Keys <- gocui.NewTcellKeyEventWrapper( - tcell.NewEventKey(tcellKey, r, tcell.ModNone), - 0, - ) - - self.waitTillIdle() -} - -func (self *GuiDriver) Click(x, y int) { - self.CheckAllToastsAcknowledged() - - self.gui.g.ReplayedEvents.MouseEvents <- gocui.NewTcellMouseEventWrapper( - tcell.NewEventMouse(x, y, tcell.ButtonPrimary, 0), - 0, - ) - self.waitTillIdle() - self.gui.g.ReplayedEvents.MouseEvents <- gocui.NewTcellMouseEventWrapper( - tcell.NewEventMouse(x, y, tcell.ButtonNone, 0), - 0, - ) - self.waitTillIdle() -} - -// wait until lazygit is idle (i.e. all processing is done) before continuing -func (self *GuiDriver) waitTillIdle() { - <-self.isIdleChan -} - -func (self *GuiDriver) CheckAllToastsAcknowledged() { - if t := self.NextToast(); t != nil { - self.Fail("Toast not acknowledged: " + *t) - } -} - -func (self *GuiDriver) Keys() config.KeybindingConfig { - return self.gui.Config.GetUserConfig().Keybinding -} - -func (self *GuiDriver) CurrentContext() types.Context { - return self.gui.c.Context().Current() -} - -func (self *GuiDriver) ContextForView(viewName string) types.Context { - context, ok := self.gui.helpers.View.ContextForView(viewName) - if !ok { - return nil - } - - return context -} - -func (self *GuiDriver) Fail(message string) { - currentView := self.gui.g.CurrentView() - - // Check for unacknowledged toast: it may give us a hint as to why the test failed - toastMessage := "" - if t := self.NextToast(); t != nil { - toastMessage = fmt.Sprintf("Unacknowledged toast message: %s\n", *t) - } - - fullMessage := fmt.Sprintf( - "%s\nFinal Lazygit state:\n%s\nUpon failure, focused view was '%s'.\n%sLog:\n%s", message, - self.gui.g.Snapshot(), - currentView.Name(), - toastMessage, - strings.Join(self.gui.GuiLog, "\n"), - ) - - self.gui.g.Close() - // need to give the gui time to close - time.Sleep(time.Millisecond * 100) - _, err := fmt.Fprintln(os.Stderr, fullMessage) - if err != nil { - panic("Test failed. Failed writing to stderr") - } - panic("Test failed") -} - -// logs to the normal place that you log to i.e. viewable with `lazygit --logs` -func (self *GuiDriver) Log(message string) { - self.gui.c.Log.Warn(message) -} - -// logs in the actual UI (in the commands panel) -func (self *GuiDriver) LogUI(message string) { - self.gui.c.LogAction(message) -} - -func (self *GuiDriver) CheckedOutRef() *models.Branch { - return self.gui.helpers.Refs.GetCheckedOutRef() -} - -func (self *GuiDriver) MainView() *gocui.View { - return self.gui.mainView() -} - -func (self *GuiDriver) SecondaryView() *gocui.View { - return self.gui.secondaryView() -} - -func (self *GuiDriver) View(viewName string) *gocui.View { - view, err := self.gui.g.View(viewName) - if err != nil { - panic(err) - } - return view -} - -func (self *GuiDriver) SetCaption(caption string) { - self.gui.setCaption(caption) - self.waitTillIdle() -} - -func (self *GuiDriver) SetCaptionPrefix(prefix string) { - self.gui.setCaptionPrefix(prefix) - self.waitTillIdle() -} - -func (self *GuiDriver) NextToast() *string { - select { - case t := <-self.toastChan: - return &t - default: - return nil - } -} - -func (self *GuiDriver) Headless() bool { - return self.headless -} diff --git a/pkg/gui/information_panel.go b/pkg/gui/information_panel.go deleted file mode 100644 index 76001ecea06..00000000000 --- a/pkg/gui/information_panel.go +++ /dev/null @@ -1,43 +0,0 @@ -package gui - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/constants" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -func (gui *Gui) informationStr() string { - if activeMode, ok := gui.helpers.Mode.GetActiveMode(); ok { - return activeMode.InfoLabel() - } - - if gui.g.Mouse { - donate := style.FgMagenta.Sprint(style.PrintHyperlink(gui.c.Tr.Donate, constants.Links.Donate)) - askQuestion := style.FgYellow.Sprint(style.PrintHyperlink(gui.c.Tr.AskQuestion, constants.Links.Discussions)) - return fmt.Sprintf("%s %s %s", donate, askQuestion, gui.Config.GetVersion()) - } - - return gui.Config.GetVersion() -} - -func (gui *Gui) handleInfoClick() error { - if !gui.g.Mouse { - return nil - } - - view := gui.Views.Information - - cx, _ := view.Cursor() - width := view.Width() - - if activeMode, ok := gui.helpers.Mode.GetActiveMode(); ok { - if width-cx > utils.StringWidth(gui.c.Tr.ResetInParentheses) { - return nil - } - return activeMode.Reset() - } - - return nil -} diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go deleted file mode 100644 index 084a2515929..00000000000 --- a/pkg/gui/keybindings.go +++ /dev/null @@ -1,547 +0,0 @@ -package gui - -import ( - "errors" - "log" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -func (gui *Gui) noPopupPanel(f func() error) func() error { - return func() error { - if gui.helpers.Confirmation.IsPopupPanelFocused() { - return nil - } - - return f() - } -} - -func (gui *Gui) outsideFilterMode(f func() error) func() error { - return func() error { - if !gui.validateNotInFilterMode() { - return nil - } - - return f() - } -} - -func (gui *Gui) validateNotInFilterMode() bool { - if gui.State.Modes.Filtering.Active() { - gui.c.Confirm(types.ConfirmOpts{ - Title: gui.c.Tr.MustExitFilterModeTitle, - Prompt: gui.c.Tr.MustExitFilterModePrompt, - HandleConfirm: gui.helpers.Mode.ExitFilterMode, - }) - - return false - } - return true -} - -// only to be called from the cheatsheet generate script. This mutates the Gui struct. -func (gui *Gui) GetCheatsheetKeybindings() []*types.Binding { - gui.g = &gocui.Gui{} - if err := gui.createAllViews(); err != nil { - panic(err) - } - // need to instantiate views - gui.helpers = helpers.NewStubHelpers() - gui.State = &GuiRepoState{} - gui.State.Contexts = gui.contextTree() - gui.State.ContextMgr = NewContextMgr(gui, gui.State.Contexts) - gui.resetHelpersAndControllers() - bindings, _ := gui.GetInitialKeybindings() - return bindings -} - -func (gui *Gui) keybindingOpts() types.KeybindingsOpts { - config := gui.c.UserConfig().Keybinding - - guards := types.KeybindingGuards{ - OutsideFilterMode: gui.outsideFilterMode, - NoPopupPanel: gui.noPopupPanel, - } - - return types.KeybindingsOpts{ - GetKey: keybindings.GetKey, - Config: config, - Guards: guards, - } -} - -func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBinding) { - opts := gui.c.KeybindingsOpts() - - bindings := []*types.Binding{ - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.OpenRecentRepos), - Handler: opts.Guards.NoPopupPanel(gui.helpers.Repos.CreateRecentReposMenu), - Description: gui.c.Tr.SwitchRepo, - }, - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.ScrollUpMain), - Handler: gui.scrollUpMain, - Alternative: "fn+up/shift+k", - Description: gui.c.Tr.ScrollUpMainWindow, - }, - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.ScrollDownMain), - Handler: gui.scrollDownMain, - Alternative: "fn+down/shift+j", - Description: gui.c.Tr.ScrollDownMainWindow, - }, - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.ScrollUpMainAlt1), - Modifier: gocui.ModNone, - Handler: gui.scrollUpMain, - }, - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.ScrollDownMainAlt1), - Modifier: gocui.ModNone, - Handler: gui.scrollDownMain, - }, - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.ScrollUpMainAlt2), - Modifier: gocui.ModNone, - Handler: gui.scrollUpMain, - }, - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.ScrollDownMainAlt2), - Modifier: gocui.ModNone, - Handler: gui.scrollDownMain, - }, - { - ViewName: "files", - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: gui.handleCopySelectedSideContextItemToClipboard, - GetDisabledReason: gui.getCopySelectedSideContextItemToClipboardDisabledReason, - Description: gui.c.Tr.CopyPathToClipboard, - }, - { - ViewName: "localBranches", - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: gui.handleCopySelectedSideContextItemToClipboard, - GetDisabledReason: gui.getCopySelectedSideContextItemToClipboardDisabledReason, - Description: gui.c.Tr.CopyBranchNameToClipboard, - }, - { - ViewName: "remoteBranches", - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: gui.handleCopySelectedSideContextItemToClipboard, - GetDisabledReason: gui.getCopySelectedSideContextItemToClipboardDisabledReason, - Description: gui.c.Tr.CopyBranchNameToClipboard, - }, - { - ViewName: "tags", - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: gui.handleCopySelectedSideContextItemToClipboard, - GetDisabledReason: gui.getCopySelectedSideContextItemToClipboardDisabledReason, - Description: gui.c.Tr.CopyTagToClipboard, - }, - { - ViewName: "commits", - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: gui.handleCopySelectedSideContextItemCommitHashToClipboard, - GetDisabledReason: gui.getCopySelectedSideContextItemToClipboardDisabledReason, - Description: gui.c.Tr.CopyCommitHashToClipboard, - }, - { - ViewName: "commits", - Key: opts.GetKey(opts.Config.Commits.ResetCherryPick), - Handler: gui.helpers.CherryPick.Reset, - Description: gui.c.Tr.ResetCherryPick, - }, - { - ViewName: "reflogCommits", - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: gui.handleCopySelectedSideContextItemToClipboard, - GetDisabledReason: gui.getCopySelectedSideContextItemToClipboardDisabledReason, - Description: gui.c.Tr.CopyCommitHashToClipboard, - }, - { - ViewName: "subCommits", - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: gui.handleCopySelectedSideContextItemCommitHashToClipboard, - GetDisabledReason: gui.getCopySelectedSideContextItemToClipboardDisabledReason, - Description: gui.c.Tr.CopyCommitHashToClipboard, - }, - { - ViewName: "information", - Key: gocui.MouseLeft, - Modifier: gocui.ModNone, - Handler: gui.handleInfoClick, - }, - { - ViewName: "commitFiles", - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: gui.handleCopySelectedSideContextItemToClipboard, - GetDisabledReason: gui.getCopySelectedSideContextItemToClipboardDisabledReason, - Description: gui.c.Tr.CopyPathToClipboard, - }, - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.ExtrasMenu), - Handler: opts.Guards.NoPopupPanel(gui.handleCreateExtrasMenuPanel), - Description: gui.c.Tr.OpenCommandLogMenu, - Tooltip: gui.c.Tr.OpenCommandLogMenuTooltip, - OpensMenu: true, - }, - { - ViewName: "main", - Key: gocui.MouseWheelDown, - Handler: gui.scrollDownMain, - Description: gui.c.Tr.ScrollDown, - Alternative: "fn+up", - }, - { - ViewName: "main", - Key: gocui.MouseWheelUp, - Handler: gui.scrollUpMain, - Description: gui.c.Tr.ScrollUp, - Alternative: "fn+down", - }, - { - ViewName: "secondary", - Key: gocui.MouseWheelDown, - Modifier: gocui.ModNone, - Handler: gui.scrollDownSecondary, - }, - { - ViewName: "secondary", - Key: gocui.MouseWheelUp, - Modifier: gocui.ModNone, - Handler: gui.scrollUpSecondary, - }, - { - ViewName: "confirmation", - Key: opts.GetKey(opts.Config.Universal.PrevItem), - Modifier: gocui.ModNone, - Handler: gui.scrollUpConfirmationPanel, - }, - { - ViewName: "confirmation", - Key: opts.GetKey(opts.Config.Universal.NextItem), - Modifier: gocui.ModNone, - Handler: gui.scrollDownConfirmationPanel, - }, - { - ViewName: "confirmation", - Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), - Modifier: gocui.ModNone, - Handler: gui.scrollUpConfirmationPanel, - }, - { - ViewName: "confirmation", - Key: opts.GetKey(opts.Config.Universal.NextItemAlt), - Modifier: gocui.ModNone, - Handler: gui.scrollDownConfirmationPanel, - }, - { - ViewName: "confirmation", - Key: gocui.MouseWheelUp, - Handler: gui.scrollUpConfirmationPanel, - }, - { - ViewName: "confirmation", - Key: gocui.MouseWheelDown, - Handler: gui.scrollDownConfirmationPanel, - }, - { - ViewName: "confirmation", - Key: opts.GetKey(opts.Config.Universal.NextPage), - Modifier: gocui.ModNone, - Handler: gui.pageDownConfirmationPanel, - }, - { - ViewName: "confirmation", - Key: opts.GetKey(opts.Config.Universal.PrevPage), - Modifier: gocui.ModNone, - Handler: gui.pageUpConfirmationPanel, - }, - { - ViewName: "confirmation", - Key: opts.GetKey(opts.Config.Universal.GotoTop), - Modifier: gocui.ModNone, - Handler: gui.goToConfirmationPanelTop, - }, - { - ViewName: "confirmation", - Key: opts.GetKey(opts.Config.Universal.GotoTopAlt), - Modifier: gocui.ModNone, - Handler: gui.goToConfirmationPanelTop, - }, - { - ViewName: "confirmation", - Key: opts.GetKey(opts.Config.Universal.GotoBottom), - Modifier: gocui.ModNone, - Handler: gui.goToConfirmationPanelBottom, - }, - { - ViewName: "confirmation", - Key: opts.GetKey(opts.Config.Universal.GotoBottomAlt), - Modifier: gocui.ModNone, - Handler: gui.goToConfirmationPanelBottom, - }, - { - ViewName: "submodules", - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: gui.handleCopySelectedSideContextItemToClipboard, - GetDisabledReason: gui.getCopySelectedSideContextItemToClipboardDisabledReason, - Description: gui.c.Tr.CopySubmoduleNameToClipboard, - }, - { - ViewName: "extras", - Key: gocui.MouseWheelUp, - Handler: gui.scrollUpExtra, - }, - { - ViewName: "extras", - Key: gocui.MouseWheelDown, - Handler: gui.scrollDownExtra, - }, - { - ViewName: "extras", - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), - Modifier: gocui.ModNone, - Handler: gui.scrollUpExtra, - }, - { - ViewName: "extras", - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.PrevItem), - Modifier: gocui.ModNone, - Handler: gui.scrollUpExtra, - }, - { - ViewName: "extras", - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.NextItem), - Modifier: gocui.ModNone, - Handler: gui.scrollDownExtra, - }, - { - ViewName: "extras", - Tag: "navigation", - Key: opts.GetKey(opts.Config.Universal.NextItemAlt), - Modifier: gocui.ModNone, - Handler: gui.scrollDownExtra, - }, - { - ViewName: "extras", - Key: opts.GetKey(opts.Config.Universal.NextPage), - Modifier: gocui.ModNone, - Handler: gui.pageDownExtrasPanel, - }, - { - ViewName: "extras", - Key: opts.GetKey(opts.Config.Universal.PrevPage), - Modifier: gocui.ModNone, - Handler: gui.pageUpExtrasPanel, - }, - { - ViewName: "extras", - Key: opts.GetKey(opts.Config.Universal.GotoTop), - Modifier: gocui.ModNone, - Handler: gui.goToExtrasPanelTop, - }, - { - ViewName: "extras", - Key: opts.GetKey(opts.Config.Universal.GotoTopAlt), - Modifier: gocui.ModNone, - Handler: gui.goToExtrasPanelTop, - }, - { - ViewName: "extras", - Key: opts.GetKey(opts.Config.Universal.GotoBottom), - Modifier: gocui.ModNone, - Handler: gui.goToExtrasPanelBottom, - }, - { - ViewName: "extras", - Key: opts.GetKey(opts.Config.Universal.GotoBottomAlt), - Modifier: gocui.ModNone, - Handler: gui.goToExtrasPanelBottom, - }, - { - ViewName: "extras", - Tag: "navigation", - Key: gocui.MouseLeft, - Modifier: gocui.ModNone, - Handler: gui.handleFocusCommandLog, - }, - } - - mouseKeybindings := []*gocui.ViewMouseBinding{} - for _, c := range gui.State.Contexts.Flatten() { - viewName := c.GetViewName() - for _, binding := range c.GetKeybindings(opts) { - // TODO: move all mouse keybindings into the mouse keybindings approach below - binding.ViewName = viewName - bindings = append(bindings, binding) - } - - mouseKeybindings = append(mouseKeybindings, c.GetMouseKeybindings(opts)...) - } - - bindings = append(bindings, []*types.Binding{ - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.NextTab), - Handler: opts.Guards.NoPopupPanel(gui.handleNextTab), - Description: gui.c.Tr.NextTab, - Tag: "navigation", - }, - { - ViewName: "", - Key: opts.GetKey(opts.Config.Universal.PrevTab), - Handler: opts.Guards.NoPopupPanel(gui.handlePrevTab), - Description: gui.c.Tr.PrevTab, - Tag: "navigation", - }, - }...) - - return bindings, mouseKeybindings -} - -func (gui *Gui) GetInitialKeybindingsWithCustomCommands() ([]*types.Binding, []*gocui.ViewMouseBinding) { - // if the search or filter prompt is open, we only want the keybindings for - // that context. It shouldn't be possible, for example, to open a menu while - // the prompt is showing; you first need to confirm or cancel the search/filter. - if currentContext := gui.State.ContextMgr.Current(); currentContext.GetKey() == context.SEARCH_CONTEXT_KEY { - bindings := currentContext.GetKeybindings(gui.c.KeybindingsOpts()) - viewName := currentContext.GetViewName() - for _, binding := range bindings { - binding.ViewName = viewName - } - return bindings, nil - } - - bindings, mouseBindings := gui.GetInitialKeybindings() - customBindings, err := gui.CustomCommandsClient.GetCustomCommandKeybindings() - if err != nil { - log.Fatal(err) - } - // prepending because we want to give our custom keybindings precedence over default keybindings - bindings = append(customBindings, bindings...) - return bindings, mouseBindings -} - -func (gui *Gui) resetKeybindings() error { - gui.g.DeleteAllKeybindings() - - bindings, mouseBindings := gui.GetInitialKeybindingsWithCustomCommands() - - for _, binding := range bindings { - if err := gui.SetKeybinding(binding); err != nil { - return err - } - } - - for _, binding := range mouseBindings { - if err := gui.SetMouseKeybinding(binding); err != nil { - return err - } - } - - for _, values := range gui.viewTabMap() { - for _, value := range values { - viewName := value.ViewName - tabClickCallback := func(tabIndex int) error { - return gui.onViewTabClick(gui.helpers.Window.WindowForView(viewName), tabIndex) - } - - if err := gui.g.SetTabClickBinding(viewName, tabClickCallback); err != nil { - return err - } - } - } - - return nil -} - -func (gui *Gui) wrappedHandler(f func() error) func(g *gocui.Gui, v *gocui.View) error { - return func(g *gocui.Gui, v *gocui.View) error { - return f() - } -} - -func (gui *Gui) SetKeybinding(binding *types.Binding) error { - handler := func() error { - return gui.callKeybindingHandler(binding) - } - - // TODO: move all mouse-ey stuff into new mouse approach - if gocui.IsMouseKey(binding.Key) { - handler = func() error { - // we ignore click events on views that aren't popup panels, when a popup panel is focused - if gui.helpers.Confirmation.IsPopupPanelFocused() && gui.currentViewName() != binding.ViewName { - return nil - } - - return binding.Handler() - } - } - - return gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, gui.wrappedHandler(handler)) -} - -// warning: mutates the binding -func (gui *Gui) SetMouseKeybinding(binding *gocui.ViewMouseBinding) error { - baseHandler := binding.Handler - newHandler := func(opts gocui.ViewMouseBindingOpts) error { - if gui.helpers.Confirmation.IsPopupPanelFocused() && gui.currentViewName() != binding.ViewName && - !gocui.IsMouseScrollKey(opts.Key) { - // we ignore click events on views that aren't popup panels, when a popup panel is focused. - // Unless both the current view and the clicked-on view are either commit message or commit - // description, or a prompt and the suggestions view, because we want to allow switching - // between those two views by clicking. - isCommitMessageOrSuggestionsView := func(viewName string) bool { - return viewName == "commitMessage" || viewName == "commitDescription" || - viewName == "prompt" || viewName == "suggestions" - } - if !isCommitMessageOrSuggestionsView(gui.currentViewName()) || !isCommitMessageOrSuggestionsView(binding.ViewName) { - return nil - } - } - - return baseHandler(opts) - } - binding.Handler = newHandler - - return gui.g.SetViewClickBinding(binding) -} - -func (gui *Gui) callKeybindingHandler(binding *types.Binding) error { - if binding.GetDisabledReason != nil { - if disabledReason := binding.GetDisabledReason(); disabledReason != nil { - if disabledReason.AllowFurtherDispatching { - return &types.ErrKeybindingNotHandled{DisabledReason: disabledReason} - } - - if disabledReason.ShowErrorInPanel { - return errors.New(disabledReason.Text) - } - - if len(disabledReason.Text) > 0 { - gui.c.ErrorToast(gui.Tr.DisabledMenuItemPrefix + disabledReason.Text) - } - return nil - } - } - - return binding.Handler() -} diff --git a/pkg/gui/keybindings/keybindings.go b/pkg/gui/keybindings/keybindings.go deleted file mode 100644 index 76b757b72e2..00000000000 --- a/pkg/gui/keybindings/keybindings.go +++ /dev/null @@ -1,54 +0,0 @@ -package keybindings - -import ( - "fmt" - "log" - "strings" - "unicode/utf8" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/constants" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -func Label(name string) string { - return LabelFromKey(GetKey(name)) -} - -func LabelFromKey(key types.Key) string { - if key == nil { - return "" - } - - keyInt := 0 - - switch key := key.(type) { - case rune: - keyInt = int(key) - case gocui.Key: - value, ok := config.LabelByKey[key] - if ok { - return value - } - keyInt = int(key) - } - - return fmt.Sprintf("%c", keyInt) -} - -func GetKey(key string) types.Key { - runeCount := utf8.RuneCountInString(key) - if key == "" { - return nil - } else if runeCount > 1 { - binding, ok := config.KeyByLabel[strings.ToLower(key)] - if !ok { - log.Fatalf("Unrecognized key %s for keybinding. For permitted values see %s", strings.ToLower(key), constants.Links.Docs.CustomKeybindings) - } - return binding - } else if runeCount == 1 { - return []rune(key)[0] - } - return nil -} diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go deleted file mode 100644 index 7ab7a88a09d..00000000000 --- a/pkg/gui/layout.go +++ /dev/null @@ -1,281 +0,0 @@ -package gui - -import ( - "errors" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -// layout is called for every screen re-render e.g. when the screen is resized -func (gui *Gui) layout(g *gocui.Gui) error { - if !gui.ViewsSetup { - gui.printCommandLogHeader() - - if _, err := gui.g.SetCurrentView(gui.defaultSideContext().GetViewName()); err != nil { - return err - } - } - - g.Highlight = true - width, height := g.Size() - - informationStr := gui.informationStr() - - appStatus := gui.helpers.AppStatus.GetStatusString() - - viewDimensions := gui.getWindowDimensions(informationStr, appStatus) - - // reading more lines into main view buffers upon resize - prevMainView := gui.Views.Main - if prevMainView != nil { - prevMainHeight := prevMainView.Height() - newMainHeight := viewDimensions["main"].Y1 - viewDimensions["main"].Y0 + 1 - heightDiff := newMainHeight - prevMainHeight - if heightDiff > 0 { - if manager := gui.getViewBufferManagerForView(gui.Views.Main); manager != nil { - manager.ReadLines(heightDiff) - } - if manager := gui.getViewBufferManagerForView(gui.Views.Secondary); manager != nil { - manager.ReadLines(heightDiff) - } - } - } - - contextsToRerender := []types.Context{} - - // we assume that the view has already been created. - setViewFromDimensions := func(context types.Context) (*gocui.View, error) { - viewName := context.GetViewName() - windowName := context.GetWindowName() - - dimensionsObj, ok := viewDimensions[windowName] - - view, err := g.View(viewName) - if err != nil { - return nil, err - } - - if !ok { - // view not specified in dimensions object: so create the view and hide it - // making the view take up the whole space in the background in case it needs - // to render content as soon as it appears, because lazyloaded content (via a pty task) - // cares about the size of the view. - _, err := g.SetView(viewName, 0, 0, width, height, 0) - view.Visible = false - return view, err - } - - frameOffset := 1 - if view.Frame { - frameOffset = 0 - } - - mustRerender := false - newHeight := dimensionsObj.Y1 - dimensionsObj.Y0 + 2*frameOffset - maxOriginY := context.TotalContentHeight() - if !view.CanScrollPastBottom { - maxOriginY -= newHeight - 1 - } - if oldOriginY := view.OriginY(); oldOriginY > maxOriginY { - view.ScrollUp(oldOriginY - maxOriginY) - // the view might not have scrolled actually (if it was at the limit - // already), so we need to check if it did - if oldOriginY != view.OriginY() && context.NeedsRerenderOnHeightChange() { - mustRerender = true - } - } - if context.NeedsRerenderOnWidthChange() == types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES { - oldWidth := view.Width() - newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 1 - if oldWidth != newWidth { - mustRerender = true - } - } - if context.NeedsRerenderOnHeightChange() { - oldHeight := view.Height() - newHeight := dimensionsObj.Y1 - dimensionsObj.Y0 + 1 - if oldHeight != newHeight { - mustRerender = true - } - } - if mustRerender { - contextsToRerender = append(contextsToRerender, context) - } - - _, err = g.SetView( - viewName, - dimensionsObj.X0-frameOffset, - dimensionsObj.Y0-frameOffset, - dimensionsObj.X1+frameOffset, - dimensionsObj.Y1+frameOffset, - 0, - ) - view.Visible = true - - return view, err - } - - for _, context := range gui.State.Contexts.Flatten() { - if !context.HasControlledBounds() { - continue - } - - _, err := setViewFromDimensions(context) - if err != nil && !errors.Is(err, gocui.ErrUnknownView) { - return err - } - } - - minimumHeight := 9 - minimumWidth := 10 - gui.Views.Limit.Visible = height < minimumHeight || width < minimumWidth - - gui.Views.Tooltip.Visible = gui.Views.Menu.Visible && gui.Views.Tooltip.Buffer() != "" - - for _, context := range gui.transientContexts() { - view, err := gui.g.View(context.GetViewName()) - if err != nil && !errors.Is(err, gocui.ErrUnknownView) { - return err - } - view.Visible = gui.helpers.Window.GetViewNameForWindow(context.GetWindowName()) == context.GetViewName() - } - - if gui.PrevLayout.Information != informationStr { - gui.c.SetViewContent(gui.Views.Information, informationStr) - gui.PrevLayout.Information = informationStr - } - - if !gui.ViewsSetup { - if err := gui.onInitialViewsCreation(); err != nil { - return err - } - - gui.handleTestMode() - - gui.ViewsSetup = true - } - - if !gui.State.ViewsSetup { - if err := gui.onInitialViewsCreationForRepo(); err != nil { - return err - } - - gui.State.ViewsSetup = true - } - - mainViewWidth, mainViewHeight := gui.Views.Main.Size() - if mainViewWidth != gui.PrevLayout.MainWidth || mainViewHeight != gui.PrevLayout.MainHeight { - gui.PrevLayout.MainWidth = mainViewWidth - gui.PrevLayout.MainHeight = mainViewHeight - if err := gui.onResize(); err != nil { - return err - } - } - - for _, context := range contextsToRerender { - context.HandleRender() - } - - // here is a good place log some stuff - // if you run `lazygit --logs` - // this will let you see these branches as prettified json - // gui.c.Log.Info(utils.AsJson(gui.State.Model.Branches[0:4])) - gui.helpers.Confirmation.ResizeCurrentPopupPanels() - - gui.renderContextOptionsMap() - -outer: - for { - select { - case f := <-gui.afterLayoutFuncs: - if err := f(); err != nil { - return err - } - default: - break outer - } - } - - return nil -} - -func (gui *Gui) prepareView(viewName string) (*gocui.View, error) { - // arbitrarily giving the view enough size so that we don't get an error, but - // it's expected that the view will be given the correct size before being shown - return gui.g.SetView(viewName, 0, 0, 10, 10, 0) -} - -func (gui *Gui) onInitialViewsCreationForRepo() error { - if err := gui.onRepoViewReset(); err != nil { - return err - } - - // hide any popup views. This only applies when we've just switched repos - for _, viewName := range gui.popupViewNames() { - view, err := gui.g.View(viewName) - if err == nil { - view.Visible = false - } - } - - initialContext := gui.c.Context().Current() - gui.c.Context().Activate(initialContext, types.OnFocusOpts{}) - - return gui.loadNewRepo() -} - -func (gui *Gui) popupViewNames() []string { - popups := lo.Filter(gui.State.Contexts.Flatten(), func(c types.Context, _ int) bool { - return c.GetKind() == types.PERSISTENT_POPUP || c.GetKind() == types.TEMPORARY_POPUP - }) - - return lo.Map(popups, func(c types.Context, _ int) string { - return c.GetViewName() - }) -} - -func (gui *Gui) onRepoViewReset() error { - // now we order the views (in order of bottom first) - for _, view := range gui.orderedViews() { - if _, err := gui.g.SetViewOnTop(view.Name()); err != nil { - return err - } - } - - return nil -} - -func (gui *Gui) onInitialViewsCreation() error { - if !gui.c.UserConfig().DisableStartupPopups { - storedPopupVersion := gui.c.GetAppState().StartupPopupVersion - if storedPopupVersion < StartupPopupVersion { - gui.showIntroPopupMessage() - } else { - gui.showBreakingChangesMessage() - } - } - - gui.c.GetAppState().LastVersion = gui.Config.GetVersion() - gui.c.SaveAppStateAndLogError() - - if gui.showRecentRepos { - if err := gui.helpers.Repos.CreateRecentReposMenu(); err != nil { - return err - } - gui.showRecentRepos = false - } - - gui.helpers.Update.CheckForUpdateInBackground() - - gui.waitForIntro.Done() - - return nil -} - -func (gui *Gui) transientContexts() []types.Context { - return lo.Filter(gui.State.Contexts.Flatten(), func(context types.Context, _ int) bool { - return context.IsTransient() - }) -} diff --git a/pkg/gui/main_panels.go b/pkg/gui/main_panels.go deleted file mode 100644 index 30055e80585..00000000000 --- a/pkg/gui/main_panels.go +++ /dev/null @@ -1,137 +0,0 @@ -package gui - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -func (gui *Gui) runTaskForView(view *gocui.View, task types.UpdateTask) error { - switch v := task.(type) { - case *types.RenderStringTask: - return gui.newStringTask(view, v.Str) - - case *types.RenderStringWithoutScrollTask: - return gui.newStringTaskWithoutScroll(view, v.Str) - - case *types.RenderStringWithScrollTask: - return gui.newStringTaskWithScroll(view, v.Str, v.OriginX, v.OriginY) - - case *types.RunCommandTask: - return gui.newCmdTask(view, v.Cmd, v.Prefix) - - case *types.RunPtyTask: - return gui.newPtyTask(view, v.Cmd, v.Prefix) - } - - return nil -} - -func (gui *Gui) moveMainContextPairToTop(pair types.MainContextPair) { - gui.moveMainContextToTop(pair.Main) - if pair.Secondary != nil { - gui.moveMainContextToTop(pair.Secondary) - } -} - -func (gui *Gui) moveMainContextToTop(context types.Context) { - gui.helpers.Window.SetWindowContext(context) - - view := context.GetView() - - topView := gui.helpers.Window.TopViewInWindow(context.GetWindowName(), true) - - if topView != nil && topView != view { - // We need to copy the content to avoid a flicker effect: If we're flicking - // through files in the files panel, we use a different view to render the - // files vs the directories, and if you select dir A, then file B, then dir - // C, you'll briefly see dir A's contents again before the view is updated. - // So here we're copying the content from the top window to avoid that - // flicker effect. - gui.g.CopyContent(topView, view) - - if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil { - gui.Log.Error(err) - } - } -} - -func (gui *Gui) RefreshMainView(opts *types.ViewUpdateOpts, context types.Context) { - view := context.GetView() - - if opts.Title != "" { - view.Title = opts.Title - } - - view.Subtitle = opts.SubTitle - - if err := gui.runTaskForView(view, opts.Task); err != nil { - gui.c.Log.Error(err) - } -} - -func (gui *Gui) normalMainContextPair() types.MainContextPair { - return types.NewMainContextPair( - gui.State.Contexts.Normal, - gui.State.Contexts.NormalSecondary, - ) -} - -func (gui *Gui) stagingMainContextPair() types.MainContextPair { - return types.NewMainContextPair( - gui.State.Contexts.Staging, - gui.State.Contexts.StagingSecondary, - ) -} - -func (gui *Gui) patchBuildingMainContextPair() types.MainContextPair { - return types.NewMainContextPair( - gui.State.Contexts.CustomPatchBuilder, - gui.State.Contexts.CustomPatchBuilderSecondary, - ) -} - -func (gui *Gui) mergingMainContextPair() types.MainContextPair { - return types.NewMainContextPair( - gui.State.Contexts.MergeConflicts, - nil, - ) -} - -func (gui *Gui) allMainContextPairs() []types.MainContextPair { - return []types.MainContextPair{ - gui.normalMainContextPair(), - gui.stagingMainContextPair(), - gui.patchBuildingMainContextPair(), - gui.mergingMainContextPair(), - } -} - -func (gui *Gui) refreshMainViews(opts types.RefreshMainOpts) { - // need to reset scroll positions of all other main views - for _, pair := range gui.allMainContextPairs() { - if pair.Main != opts.Pair.Main { - pair.Main.GetView().SetOrigin(0, 0) - } - if pair.Secondary != nil && pair.Secondary != opts.Pair.Secondary { - pair.Secondary.GetView().SetOrigin(0, 0) - } - } - - if opts.Main != nil { - gui.RefreshMainView(opts.Main, opts.Pair.Main) - } - - if opts.Secondary != nil { - gui.RefreshMainView(opts.Secondary, opts.Pair.Secondary) - } else if opts.Pair.Secondary != nil { - opts.Pair.Secondary.GetView().Clear() - } - - gui.moveMainContextPairToTop(opts.Pair) - - gui.splitMainPanel(opts.Secondary != nil) -} - -func (gui *Gui) splitMainPanel(splitMainPanel bool) { - gui.State.SplitMainPanel = splitMainPanel -} diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go deleted file mode 100644 index 324c171c0e4..00000000000 --- a/pkg/gui/menu_panel.go +++ /dev/null @@ -1,73 +0,0 @@ -package gui - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/theme" -) - -// note: items option is mutated by this function -func (gui *Gui) createMenu(opts types.CreateMenuOptions) error { - if !opts.HideCancel { - // this is mutative but I'm okay with that for now - opts.Items = append(opts.Items, &types.MenuItem{ - LabelColumns: []string{gui.c.Tr.Cancel}, - OnPress: func() error { - return nil - }, - }) - } - - maxColumnSize := 1 - confirmKey := keybindings.GetKey(gui.c.UserConfig().Keybinding.Universal.ConfirmMenu) - - for _, item := range opts.Items { - if item.LabelColumns == nil { - item.LabelColumns = []string{item.Label} - } - - if item.OpensMenu { - item.LabelColumns[0] = fmt.Sprintf("%s...", item.LabelColumns[0]) - } - - maxColumnSize = max(maxColumnSize, len(item.LabelColumns)) - - // Remove all item keybindings that are the same as the confirm binding - if item.Key == confirmKey && !opts.KeepConfirmKeybindings { - item.Key = nil - } - } - - for _, item := range opts.Items { - if len(item.LabelColumns) < maxColumnSize { - // we require that each item has the same number of columns so we're padding out with blank strings - // if this item has too few - item.LabelColumns = append(item.LabelColumns, make([]string, maxColumnSize-len(item.LabelColumns))...) - } - } - - gui.State.Contexts.Menu.SetMenuItems(opts.Items, opts.ColumnAlignment) - gui.State.Contexts.Menu.SetPrompt(opts.Prompt) - gui.State.Contexts.Menu.SetAllowFilteringKeybindings(opts.AllowFilteringKeybindings) - gui.State.Contexts.Menu.SetSelection(0) - - gui.Views.Menu.Title = opts.Title - gui.Views.Menu.FgColor = theme.GocuiDefaultTextColor - - gui.Views.Tooltip.Wrap = true - gui.Views.Tooltip.FgColor = theme.GocuiDefaultTextColor - gui.Views.Tooltip.Visible = true - - // resetting keybindings so that the menu-specific keybindings are registered - if err := gui.resetKeybindings(); err != nil { - return err - } - - gui.c.PostRefreshUpdate(gui.State.Contexts.Menu) - - // TODO: ensure that if we're opened a menu from within a menu that it renders correctly - gui.c.Context().Push(gui.State.Contexts.Menu, types.OnFocusOpts{}) - return nil -} diff --git a/pkg/gui/mergeconflicts/find_conflicts.go b/pkg/gui/mergeconflicts/find_conflicts.go deleted file mode 100644 index c4d3a51a8b0..00000000000 --- a/pkg/gui/mergeconflicts/find_conflicts.go +++ /dev/null @@ -1,117 +0,0 @@ -package mergeconflicts - -import ( - "bufio" - "bytes" - "io" - "os" - "strings" - - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// LineType tells us whether a given line is a start/middle/end marker of a conflict, -// or if it's not a marker at all -type LineType int - -const ( - START LineType = iota - ANCESTOR - TARGET - END - NOT_A_MARKER -) - -func findConflicts(content string) []*mergeConflict { - conflicts := make([]*mergeConflict, 0) - - if content == "" { - return conflicts - } - - var newConflict *mergeConflict - for i, line := range utils.SplitLines(content) { - switch determineLineType(line) { - case START: - newConflict = &mergeConflict{start: i, ancestor: -1} - case ANCESTOR: - if newConflict != nil { - newConflict.ancestor = i - } - case TARGET: - if newConflict != nil { - newConflict.target = i - } - case END: - if newConflict != nil { - newConflict.end = i - conflicts = append(conflicts, newConflict) - } - // reset value to avoid any possible silent mutations in further iterations - newConflict = nil - default: - // line isn't a merge conflict marker so we just continue - } - } - - return conflicts -} - -var ( - CONFLICT_START = "<<<<<<< " - CONFLICT_END = ">>>>>>> " - CONFLICT_START_BYTES = []byte(CONFLICT_START) - CONFLICT_END_BYTES = []byte(CONFLICT_END) -) - -func determineLineType(line string) LineType { - // TODO: find out whether we ever actually get this prefix - trimmedLine := strings.TrimPrefix(line, "++") - - switch { - case strings.HasPrefix(trimmedLine, CONFLICT_START): - return START - case strings.HasPrefix(trimmedLine, "||||||| "): - return ANCESTOR - case trimmedLine == "=======": - return TARGET - case strings.HasPrefix(trimmedLine, CONFLICT_END): - return END - default: - return NOT_A_MARKER - } -} - -// tells us whether a file actually has inline merge conflicts. We need to run this -// because git will continue showing a status of 'UU' even after the conflicts have -// been resolved in the user's editor -func FileHasConflictMarkers(path string) (bool, error) { - file, err := os.Open(path) - if err != nil { - return false, err - } - - defer file.Close() - - return fileHasConflictMarkersAux(file), nil -} - -// Efficiently scans through a file looking for merge conflict markers. Returns true if it does -func fileHasConflictMarkersAux(file io.Reader) bool { - scanner := bufio.NewScanner(file) - scanner.Split(utils.ScanLinesAndTruncateWhenLongerThanBuffer(bufio.MaxScanTokenSize)) - for scanner.Scan() { - line := scanner.Bytes() - - // only searching for start/end markers because the others are more ambiguous - if bytes.HasPrefix(line, CONFLICT_START_BYTES) { - return true - } - - if bytes.HasPrefix(line, CONFLICT_END_BYTES) { - return true - } - } - - return false -} diff --git a/pkg/gui/mergeconflicts/find_conflicts_test.go b/pkg/gui/mergeconflicts/find_conflicts_test.go deleted file mode 100644 index f4ab4d30ccd..00000000000 --- a/pkg/gui/mergeconflicts/find_conflicts_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package mergeconflicts - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestDetermineLineType(t *testing.T) { - type scenario struct { - line string - expected LineType - } - - scenarios := []scenario{ - { - line: "", - expected: NOT_A_MARKER, - }, - { - line: "blah", - expected: NOT_A_MARKER, - }, - { - line: "<<<<<<< HEAD", - expected: START, - }, - { - line: "<<<<<<< HEAD:my_branch", - expected: START, - }, - { - line: "<<<<<<< MERGE_HEAD:my_branch", - expected: START, - }, - { - line: "<<<<<<< Updated upstream:my_branch", - expected: START, - }, - { - line: "<<<<<<< ours:my_branch", - expected: START, - }, - { - line: "=======", - expected: TARGET, - }, - { - line: ">>>>>>> blah", - expected: END, - }, - { - line: "||||||| adf33b9", - expected: ANCESTOR, - }, - } - - for _, s := range scenarios { - assert.EqualValues(t, s.expected, determineLineType(s.line)) - } -} - -func TestFindConflictsAux(t *testing.T) { - type scenario struct { - content string - expected bool - } - - scenarios := []scenario{ - { - content: "", - expected: false, - }, - { - content: "blah", - expected: false, - }, - { - content: ">>>>>>> ", - expected: true, - }, - { - content: "<<<<<<< ", - expected: true, - }, - { - content: " <<<<<<< ", - expected: false, - }, - { - content: "a\nb\nc\n<<<<<<< ", - expected: true, - }, - } - - for _, s := range scenarios { - reader := strings.NewReader(s.content) - assert.EqualValues(t, s.expected, fileHasConflictMarkersAux(reader)) - } -} diff --git a/pkg/gui/mergeconflicts/merge_conflict.go b/pkg/gui/mergeconflicts/merge_conflict.go deleted file mode 100644 index 9b9b72f552f..00000000000 --- a/pkg/gui/mergeconflicts/merge_conflict.go +++ /dev/null @@ -1,76 +0,0 @@ -package mergeconflicts - -// mergeConflict : A git conflict with a start, ancestor (if exists), target, and end corresponding to line -// numbers in the file where the conflict markers appear. -// If no ancestor is present (i.e. we're not using the diff3 algorithm), then -// the `ancestor` field's value will be -1 -type mergeConflict struct { - start int - ancestor int - target int - end int -} - -func (c *mergeConflict) hasAncestor() bool { - return c.ancestor >= 0 -} - -func (c *mergeConflict) isMarkerLine(i int) bool { - return i == c.start || - i == c.ancestor || - i == c.target || - i == c.end -} - -type Selection int - -const ( - TOP Selection = iota - MIDDLE - BOTTOM - ALL -) - -func (s Selection) isIndexToKeep(conflict *mergeConflict, i int) bool { - // we're only handling one conflict at a time so any lines outside this - // conflict we'll keep - if i < conflict.start || conflict.end < i { - return true - } - - if conflict.isMarkerLine(i) { - return false - } - - return s.selected(conflict, i) -} - -func (s Selection) bounds(c *mergeConflict) (int, int) { - switch s { - case TOP: - if c.hasAncestor() { - return c.start, c.ancestor - } - return c.start, c.target - case MIDDLE: - return c.ancestor, c.target - case BOTTOM: - return c.target, c.end - case ALL: - return c.start, c.end - } - - panic("unexpected selection for merge conflict") -} - -func (s Selection) selected(c *mergeConflict, idx int) bool { - start, end := s.bounds(c) - return start < idx && idx < end -} - -func availableSelections(c *mergeConflict) []Selection { - if c.hasAncestor() { - return []Selection{TOP, MIDDLE, BOTTOM} - } - return []Selection{TOP, BOTTOM} -} diff --git a/pkg/gui/mergeconflicts/rendering.go b/pkg/gui/mergeconflicts/rendering.go deleted file mode 100644 index e57754e4bd6..00000000000 --- a/pkg/gui/mergeconflicts/rendering.go +++ /dev/null @@ -1,34 +0,0 @@ -package mergeconflicts - -import ( - "bytes" - - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -func ColoredConflictFile(state *State) string { - content := state.GetContent() - if len(state.conflicts) == 0 { - return content - } - conflict, remainingConflicts := shiftConflict(state.conflicts) - var outputBuffer bytes.Buffer - for i, line := range utils.SplitLines(content) { - textStyle := theme.DefaultTextColor - if conflict.isMarkerLine(i) { - textStyle = style.FgRed - } - - if i == conflict.end && len(remainingConflicts) > 0 { - conflict, remainingConflicts = shiftConflict(remainingConflicts) - } - outputBuffer.WriteString(textStyle.Sprint(line) + "\n") - } - return outputBuffer.String() -} - -func shiftConflict(conflicts []*mergeConflict) (*mergeConflict, []*mergeConflict) { - return conflicts[0], conflicts[1:] -} diff --git a/pkg/gui/mergeconflicts/state.go b/pkg/gui/mergeconflicts/state.go deleted file mode 100644 index 047241353e4..00000000000 --- a/pkg/gui/mergeconflicts/state.go +++ /dev/null @@ -1,220 +0,0 @@ -package mergeconflicts - -import ( - "strings" - - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -// State represents the selection state of the merge conflict context. -type State struct { - // path of the file with the conflicts - path string - - // This is a stack of the file content. It is used to undo changes. - // The last item is the current file content. - contents []string - - conflicts []*mergeConflict - // this is the index of the above `conflicts` field which is currently selected - conflictIndex int - - // this is the index of the selected conflict's available selections slice e.g. [TOP, MIDDLE, BOTTOM] - // We use this to know which hunk of the conflict is selected. - selectionIndex int -} - -func NewState() *State { - return &State{ - conflictIndex: 0, - selectionIndex: 0, - conflicts: []*mergeConflict{}, - contents: []string{}, - } -} - -func (s *State) setConflictIndex(index int) { - if len(s.conflicts) == 0 { - s.conflictIndex = 0 - } else { - s.conflictIndex = lo.Clamp(index, 0, len(s.conflicts)-1) - } - s.setSelectionIndex(s.selectionIndex) -} - -func (s *State) setSelectionIndex(index int) { - if selections := s.availableSelections(); len(selections) != 0 { - s.selectionIndex = lo.Clamp(index, 0, len(selections)-1) - } -} - -func (s *State) SelectNextConflictHunk() { - s.setSelectionIndex(s.selectionIndex + 1) -} - -func (s *State) SelectPrevConflictHunk() { - s.setSelectionIndex(s.selectionIndex - 1) -} - -func (s *State) SelectNextConflict() { - s.setConflictIndex(s.conflictIndex + 1) -} - -func (s *State) SelectPrevConflict() { - s.setConflictIndex(s.conflictIndex - 1) -} - -func (s *State) currentConflict() *mergeConflict { - if len(s.conflicts) == 0 { - return nil - } - - return s.conflicts[s.conflictIndex] -} - -// this is for starting a new merge conflict session -func (s *State) SetContent(content string, path string) { - if content == s.GetContent() && path == s.path { - return - } - - s.path = path - s.contents = []string{} - s.PushContent(content) -} - -// this is for when you've resolved a conflict. This allows you to undo to a previous -// state -func (s *State) PushContent(content string) { - s.contents = append(s.contents, content) - s.setConflicts(findConflicts(content)) -} - -func (s *State) GetContent() string { - if len(s.contents) == 0 { - return "" - } - - return s.contents[len(s.contents)-1] -} - -func (s *State) GetPath() string { - return s.path -} - -func (s *State) Undo() bool { - if len(s.contents) <= 1 { - return false - } - - s.contents = s.contents[:len(s.contents)-1] - - newContent := s.GetContent() - // We could be storing the old conflicts and selected index on a stack too. - s.setConflicts(findConflicts(newContent)) - - return true -} - -func (s *State) setConflicts(conflicts []*mergeConflict) { - s.conflicts = conflicts - s.setConflictIndex(s.conflictIndex) -} - -func (s *State) NoConflicts() bool { - return len(s.conflicts) == 0 -} - -func (s *State) Selection() Selection { - if selections := s.availableSelections(); len(selections) > 0 { - return selections[s.selectionIndex] - } - return TOP -} - -func (s *State) availableSelections() []Selection { - if conflict := s.currentConflict(); conflict != nil { - return availableSelections(conflict) - } - return nil -} - -func (s *State) AllConflictsResolved() bool { - return len(s.conflicts) == 0 -} - -func (s *State) Reset() { - s.contents = []string{} - s.path = "" -} - -// we're not resetting selectedIndex here because the user typically would want -// to pick either all top hunks or all bottom hunks so we retain that selection -func (s *State) ResetConflictSelection() { - s.conflictIndex = 0 -} - -func (s *State) Active() bool { - return s.path != "" -} - -func (s *State) GetConflictMiddle() int { - currentConflict := s.currentConflict() - - if currentConflict == nil { - return 0 - } - - return currentConflict.target -} - -func (s *State) ContentAfterConflictResolve(selection Selection) (bool, string, error) { - conflict := s.currentConflict() - if conflict == nil { - return false, "", nil - } - - content := "" - err := utils.ForEachLineInFile(s.path, func(line string, i int) { - if selection.isIndexToKeep(conflict, i) { - content += line - } - }) - if err != nil { - return false, "", err - } - - return true, content, nil -} - -func (s *State) GetSelectedLine() int { - conflict := s.currentConflict() - if conflict == nil { - // TODO: see why this is 1 and not 0 - return 1 - } - selection := s.Selection() - startIndex, _ := selection.bounds(conflict) - return startIndex + 1 -} - -func (s *State) GetSelectedRange() (int, int) { - conflict := s.currentConflict() - if conflict == nil { - return 0, 0 - } - selection := s.Selection() - startIndex, endIndex := selection.bounds(conflict) - return startIndex, endIndex -} - -func (s *State) PlainRenderSelected() string { - startIndex, endIndex := s.GetSelectedRange() - - content := s.GetContent() - - contentLines := utils.SplitLines(content) - - return strings.Join(contentLines[startIndex:endIndex+1], "\n") -} diff --git a/pkg/gui/mergeconflicts/state_test.go b/pkg/gui/mergeconflicts/state_test.go deleted file mode 100644 index 7a9ee8c2691..00000000000 --- a/pkg/gui/mergeconflicts/state_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package mergeconflicts - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestFindConflicts(t *testing.T) { - type scenario struct { - name string - content string - expected []*mergeConflict - } - - scenarios := []scenario{ - { - name: "empty", - content: "", - expected: []*mergeConflict{}, - }, - { - name: "various conflicts", - content: `++<<<<<<< HEAD -foo -++======= -bar -++>>>>>>> branch - -<<<<<<< HEAD: foo/bar/baz.go -foo -bar -======= -baz ->>>>>>> branch - -++<<<<<<< MERGE_HEAD -foo -++======= -bar -++>>>>>>> branch - -++<<<<<<< Updated upstream -foo -++======= -bar -++>>>>>>> branch - -++<<<<<<< ours -foo -++======= -bar -++>>>>>>> branch - -<<<<<<< Updated upstream: foo/bar/baz.go -foo -bar -======= -baz ->>>>>>> branch - -<<<<<<< HEAD -foo -||||||| fffffff -bar -======= -baz ->>>>>>> branch -`, - expected: []*mergeConflict{ - { - start: 0, - ancestor: -1, - target: 2, - end: 4, - }, - { - start: 6, - ancestor: -1, - target: 9, - end: 11, - }, - { - start: 13, - ancestor: -1, - target: 15, - end: 17, - }, - { - start: 19, - ancestor: -1, - target: 21, - end: 23, - }, - { - start: 25, - ancestor: -1, - target: 27, - end: 29, - }, - { - start: 31, - ancestor: -1, - target: 34, - end: 36, - }, - { - start: 38, - ancestor: 40, - target: 42, - end: 44, - }, - }, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - assert.EqualValues(t, s.expected, findConflicts(s.content)) - }) - } -} diff --git a/pkg/gui/modes/cherrypicking/cherry_picking.go b/pkg/gui/modes/cherrypicking/cherry_picking.go deleted file mode 100644 index 2ad3e61aaf8..00000000000 --- a/pkg/gui/modes/cherrypicking/cherry_picking.go +++ /dev/null @@ -1,65 +0,0 @@ -package cherrypicking - -import ( - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/samber/lo" -) - -type CherryPicking struct { - CherryPickedCommits []*models.Commit - - // we only allow cherry picking from one context at a time, so you can't copy a commit from - // the local commits context and then also copy a commit in the reflog context - ContextKey string - - // keep track of whether the currently copied commits have been pasted already. If so, we hide - // the mode and the blue display of the commits, but we still allow pasting them again. - DidPaste bool -} - -func New() *CherryPicking { - return &CherryPicking{ - CherryPickedCommits: make([]*models.Commit, 0), - ContextKey: "", - } -} - -func (self *CherryPicking) Active() bool { - return self.CanPaste() && !self.DidPaste -} - -func (self *CherryPicking) CanPaste() bool { - return len(self.CherryPickedCommits) > 0 -} - -func (self *CherryPicking) SelectedHashSet() *set.Set[string] { - if self.DidPaste { - return set.New[string]() - } - - hashes := lo.Map(self.CherryPickedCommits, func(commit *models.Commit, _ int) string { - return commit.Hash() - }) - return set.NewFromSlice(hashes) -} - -func (self *CherryPicking) Add(selectedCommit *models.Commit, commitsList []*models.Commit) { - commitSet := self.SelectedHashSet() - commitSet.Add(selectedCommit.Hash()) - - self.update(commitSet, commitsList) -} - -func (self *CherryPicking) Remove(selectedCommit *models.Commit, commitsList []*models.Commit) { - commitSet := self.SelectedHashSet() - commitSet.Remove(selectedCommit.Hash()) - - self.update(commitSet, commitsList) -} - -func (self *CherryPicking) update(selectedHashSet *set.Set[string], commitsList []*models.Commit) { - self.CherryPickedCommits = lo.Filter(commitsList, func(commit *models.Commit, _ int) bool { - return selectedHashSet.Includes(commit.Hash()) - }) -} diff --git a/pkg/gui/modes/diffing/diffing.go b/pkg/gui/modes/diffing/diffing.go deleted file mode 100644 index aa13bd1c1fb..00000000000 --- a/pkg/gui/modes/diffing/diffing.go +++ /dev/null @@ -1,28 +0,0 @@ -package diffing - -// if ref is blank we're not diffing anything -type Diffing struct { - Ref string - Reverse bool -} - -func New() Diffing { - return Diffing{} -} - -func (self *Diffing) Active() bool { - return self.Ref != "" -} - -// GetFromAndReverseArgsForDiff tells us the from and reverse args to be used in a diff command. -// If we're not in diff mode we'll end up with the equivalent of a `git show` i.e `git diff blah^..blah`. -func (self *Diffing) GetFromAndReverseArgsForDiff(from string) (string, bool) { - reverse := false - - if self.Active() { - reverse = self.Reverse - from = self.Ref - } - - return from, reverse -} diff --git a/pkg/gui/modes/filtering/filtering.go b/pkg/gui/modes/filtering/filtering.go deleted file mode 100644 index acdb94e5320..00000000000 --- a/pkg/gui/modes/filtering/filtering.go +++ /dev/null @@ -1,44 +0,0 @@ -package filtering - -type Filtering struct { - path string // the filename that gets passed to git log - author string // the author that gets passed to git log - selectedCommitHash string // the commit that was selected before we entered filtering mode -} - -func New(path string, author string) Filtering { - return Filtering{path: path, author: author} -} - -func (m *Filtering) Active() bool { - return m.path != "" || m.author != "" -} - -func (m *Filtering) Reset() { - m.path = "" - m.author = "" -} - -func (m *Filtering) SetPath(path string) { - m.path = path -} - -func (m *Filtering) GetPath() string { - return m.path -} - -func (m *Filtering) SetAuthor(author string) { - m.author = author -} - -func (m *Filtering) GetAuthor() string { - return m.author -} - -func (m *Filtering) SetSelectedCommitHash(hash string) { - m.selectedCommitHash = hash -} - -func (m *Filtering) GetSelectedCommitHash() string { - return m.selectedCommitHash -} diff --git a/pkg/gui/modes/marked_base_commit/marked_base_commit.go b/pkg/gui/modes/marked_base_commit/marked_base_commit.go deleted file mode 100644 index ace1e35d0fb..00000000000 --- a/pkg/gui/modes/marked_base_commit/marked_base_commit.go +++ /dev/null @@ -1,25 +0,0 @@ -package marked_base_commit - -type MarkedBaseCommit struct { - hash string // the hash of the commit used as a rebase base commit; empty string when unset -} - -func New() MarkedBaseCommit { - return MarkedBaseCommit{} -} - -func (m *MarkedBaseCommit) Active() bool { - return m.hash != "" -} - -func (m *MarkedBaseCommit) Reset() { - m.hash = "" -} - -func (m *MarkedBaseCommit) SetHash(hash string) { - m.hash = hash -} - -func (m *MarkedBaseCommit) GetHash() string { - return m.hash -} diff --git a/pkg/gui/options_map.go b/pkg/gui/options_map.go deleted file mode 100644 index 45521546883..00000000000 --- a/pkg/gui/options_map.go +++ /dev/null @@ -1,146 +0,0 @@ -package gui - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type OptionsMapMgr struct { - c *helpers.HelperCommon -} - -func (gui *Gui) renderContextOptionsMap() { - // In demos, we render our own content to this view - if gui.integrationTest != nil && gui.integrationTest.IsDemo() { - return - } - mgr := OptionsMapMgr{c: gui.c} - mgr.renderContextOptionsMap() -} - -// Render the options available for the current context at the bottom of the screen -// STYLE GUIDE: we use the default options fg color for most keybindings. We can -// only use a different color if we're in a specific mode where the user is likely -// to want to press that key. For example, when in cherry-picking mode, we -// want to prominently show the keybinding for pasting commits. -func (self *OptionsMapMgr) renderContextOptionsMap() { - currentContext := self.c.Context().Current() - - currentContextBindings := currentContext.GetKeybindings(self.c.KeybindingsOpts()) - globalBindings := self.c.Contexts().Global.GetKeybindings(self.c.KeybindingsOpts()) - - currentContextKeys := set.NewFromSlice( - lo.Map(currentContextBindings, func(binding *types.Binding, _ int) types.Key { - return binding.Key - })) - - allBindings := append(currentContextBindings, lo.Filter(globalBindings, func(b *types.Binding, _ int) bool { - return !currentContextKeys.Includes(b.Key) - })...) - - bindingsToDisplay := lo.Filter(allBindings, func(binding *types.Binding, _ int) bool { - return binding.DisplayOnScreen && !binding.IsDisabled() - }) - - optionsMap := lo.Map(bindingsToDisplay, func(binding *types.Binding, _ int) bindingInfo { - displayStyle := theme.OptionsFgColor - if binding.DisplayStyle != nil { - displayStyle = *binding.DisplayStyle - } - - return bindingInfo{ - key: keybindings.LabelFromKey(binding.Key), - description: binding.GetShortDescription(), - style: displayStyle, - } - }) - - // Mode-specific local keybindings - if currentContext.GetKey() == context.LOCAL_COMMITS_CONTEXT_KEY { - if self.c.Modes().CherryPicking.Active() { - optionsMap = utils.Prepend(optionsMap, bindingInfo{ - key: keybindings.Label(self.c.KeybindingsOpts().Config.Commits.PasteCommits), - description: self.c.Tr.PasteCommits, - style: style.FgCyan, - }) - } - - if self.c.Model().BisectInfo.Started() { - optionsMap = utils.Prepend(optionsMap, bindingInfo{ - key: keybindings.Label(self.c.KeybindingsOpts().Config.Commits.ViewBisectOptions), - description: self.c.Tr.ViewBisectOptions, - style: style.FgGreen, - }) - } - } - - // Mode-specific global keybindings - if state := self.c.Model().WorkingTreeStateAtLastCommitRefresh; state.Any() { - optionsMap = utils.Prepend(optionsMap, bindingInfo{ - key: keybindings.Label(self.c.KeybindingsOpts().Config.Universal.CreateRebaseOptionsMenu), - description: state.OptionsMapTitle(self.c.Tr), - style: style.FgYellow, - }) - } - - if self.c.Git().Patch.PatchBuilder.Active() { - optionsMap = utils.Prepend(optionsMap, bindingInfo{ - key: keybindings.Label(self.c.KeybindingsOpts().Config.Universal.CreatePatchOptionsMenu), - description: self.c.Tr.ViewPatchOptions, - style: style.FgYellow, - }) - } - - self.renderOptions(self.formatBindingInfos(optionsMap)) -} - -func (self *OptionsMapMgr) formatBindingInfos(bindingInfos []bindingInfo) string { - width := self.c.Views().Options.InnerWidth() - 2 // -2 for some padding - var builder strings.Builder - ellipsis := "…" - separator := " | " - - length := 0 - - for i, info := range bindingInfos { - plainText := fmt.Sprintf("%s: %s", info.description, info.key) - - // Check if adding the next formatted string exceeds the available width - textLen := utils.StringWidth(plainText) - if i > 0 && length+len(separator)+textLen > width { - builder.WriteString(theme.OptionsFgColor.Sprint(separator + ellipsis)) - break - } - - formatted := info.style.Sprintf(plainText) - - if i > 0 { - builder.WriteString(theme.OptionsFgColor.Sprint(separator)) - length += len(separator) - } - builder.WriteString(formatted) - length += textLen - } - - return builder.String() -} - -func (self *OptionsMapMgr) renderOptions(options string) { - self.c.SetViewContent(self.c.Views().Options, options) -} - -type bindingInfo struct { - key string - description string - style style.TextStyle -} diff --git a/pkg/gui/patch_exploring/focus.go b/pkg/gui/patch_exploring/focus.go deleted file mode 100644 index cf917cd4d45..00000000000 --- a/pkg/gui/patch_exploring/focus.go +++ /dev/null @@ -1,47 +0,0 @@ -package patch_exploring - -func calculateOrigin(currentOrigin int, bufferHeight int, numLines int, firstLineIdx int, lastLineIdx int, selectedLineIdx int, mode selectMode) int { - needToSeeIdx, wantToSeeIdx := getNeedAndWantLineIdx(firstLineIdx, lastLineIdx, selectedLineIdx, mode) - - return calculateNewOriginWithNeededAndWantedIdx(currentOrigin, bufferHeight, numLines, needToSeeIdx, wantToSeeIdx) -} - -// we want to scroll our origin so that the index we need to see is in view -// and the other index we want to see (e.g. the other side of a line range) -// is as close to being in view as possible. -func calculateNewOriginWithNeededAndWantedIdx(currentOrigin int, bufferHeight int, numLines int, needToSeeIdx int, wantToSeeIdx int) int { - origin := currentOrigin - if needToSeeIdx < currentOrigin || needToSeeIdx >= currentOrigin+bufferHeight { - origin = max(min(needToSeeIdx-bufferHeight/2, numLines-bufferHeight), 0) - } - - bottom := origin + bufferHeight - - if wantToSeeIdx < origin { - requiredChange := origin - wantToSeeIdx - allowedChange := bottom - needToSeeIdx - return origin - min(requiredChange, allowedChange) - } else if wantToSeeIdx >= bottom { - requiredChange := wantToSeeIdx + 1 - bottom - allowedChange := needToSeeIdx - origin - return origin + min(requiredChange, allowedChange) - } - return origin -} - -func getNeedAndWantLineIdx(firstLineIdx int, lastLineIdx int, selectedLineIdx int, mode selectMode) (int, int) { - switch mode { - case LINE: - return selectedLineIdx, selectedLineIdx - case RANGE: - if selectedLineIdx == firstLineIdx { - return firstLineIdx, lastLineIdx - } - return lastLineIdx, firstLineIdx - case HUNK: - return firstLineIdx, lastLineIdx - default: - // we should never land here - panic("unknown mode") - } -} diff --git a/pkg/gui/patch_exploring/focus_test.go b/pkg/gui/patch_exploring/focus_test.go deleted file mode 100644 index 290f1356c73..00000000000 --- a/pkg/gui/patch_exploring/focus_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package patch_exploring - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNewOrigin(t *testing.T) { - type scenario struct { - name string - origin int - bufferHeight int - numLines int - firstLineIdx int - lastLineIdx int - selectedLineIdx int - selectMode selectMode - expected int - } - - scenarios := []scenario{ - { - name: "selection above scroll window, enough room to put it in the middle", - origin: 250, - bufferHeight: 100, - numLines: 500, - firstLineIdx: 210, - lastLineIdx: 210, - selectedLineIdx: 210, - selectMode: LINE, - expected: 160, - }, - { - name: "selection above scroll window, not enough room to put it in the middle", - origin: 50, - bufferHeight: 100, - numLines: 500, - firstLineIdx: 10, - lastLineIdx: 10, - selectedLineIdx: 10, - selectMode: LINE, - expected: 0, - }, - { - name: "selection below scroll window, enough room to put it in the middle", - origin: 0, - bufferHeight: 100, - numLines: 500, - firstLineIdx: 150, - lastLineIdx: 150, - selectedLineIdx: 150, - selectMode: LINE, - expected: 100, - }, - { - name: "selection below scroll window, not enough room to put it in the middle", - origin: 0, - bufferHeight: 100, - numLines: 200, - firstLineIdx: 199, - lastLineIdx: 199, - selectedLineIdx: 199, - selectMode: LINE, - expected: 100, - }, - { - name: "selection within scroll window", - origin: 0, - bufferHeight: 100, - numLines: 500, - firstLineIdx: 50, - lastLineIdx: 50, - selectedLineIdx: 50, - selectMode: LINE, - expected: 0, - }, - { - name: "range ending below scroll window with selection at end of range", - origin: 0, - bufferHeight: 100, - numLines: 500, - firstLineIdx: 40, - lastLineIdx: 150, - selectedLineIdx: 150, - selectMode: RANGE, - expected: 50, - }, - { - name: "range ending below scroll window with selection at beginning of range", - origin: 0, - bufferHeight: 100, - numLines: 500, - firstLineIdx: 40, - lastLineIdx: 150, - selectedLineIdx: 40, - selectMode: RANGE, - expected: 40, - }, - { - name: "range starting above scroll window with selection at beginning of range", - origin: 50, - bufferHeight: 100, - numLines: 500, - firstLineIdx: 40, - lastLineIdx: 150, - selectedLineIdx: 40, - selectMode: RANGE, - expected: 40, - }, - { - name: "hunk extending beyond both bounds of scroll window", - origin: 50, - bufferHeight: 100, - numLines: 500, - firstLineIdx: 40, - lastLineIdx: 200, - selectedLineIdx: 70, - selectMode: HUNK, - expected: 40, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - assert.EqualValues(t, s.expected, calculateOrigin(s.origin, s.bufferHeight, s.numLines, s.firstLineIdx, s.lastLineIdx, s.selectedLineIdx, s.selectMode)) - }) - } -} diff --git a/pkg/gui/patch_exploring/state.go b/pkg/gui/patch_exploring/state.go deleted file mode 100644 index 3852dc09656..00000000000 --- a/pkg/gui/patch_exploring/state.go +++ /dev/null @@ -1,421 +0,0 @@ -package patch_exploring - -import ( - "strings" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -// State represents the current state of the patch explorer context i.e. when -// you're staging a file or you're building a patch from an existing commit -// this struct holds the info about the diff you're interacting with and what's currently selected. -type State struct { - // These are in terms of view lines (wrapped), not patch lines - selectedLineIdx int - rangeStartLineIdx int - // If a range is sticky, it means we expand the range when we move up or down. - // Otherwise, we cancel the range when we move up or down. - rangeIsSticky bool - diff string - patch *patch.Patch - selectMode selectMode - - // Array of indices of the wrapped lines indexed by a patch line index - viewLineIndices []int - // Array of indices of the original patch lines indexed by a wrapped view line index - patchLineIndices []int - - // whether the user has switched to hunk mode manually; if hunk mode is on - // but this is false, then hunk mode was enabled because the config makes it - // on by default. - // this makes a difference for whether we want to escape out of hunk mode - userEnabledHunkMode bool -} - -// these represent what select mode we're in -type selectMode int - -const ( - LINE selectMode = iota - RANGE - HUNK -) - -func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *State, useHunkModeByDefault bool) *State { - if oldState != nil && diff == oldState.diff && selectedLineIdx == -1 { - // if we're here then we can return the old state. If selectedLineIdx was not -1 - // then that would mean we were trying to click and potentially drag a range, which - // is why in that case we continue below - return oldState - } - - patch := patch.Parse(diff) - - if !patch.ContainsChanges() { - return nil - } - - viewLineIndices, patchLineIndices := wrapPatchLines(diff, view) - - rangeStartLineIdx := 0 - if oldState != nil { - rangeStartLineIdx = oldState.rangeStartLineIdx - } - - selectMode := LINE - if useHunkModeByDefault && !patch.IsSingleHunkForWholeFile() { - selectMode = HUNK - } - - userEnabledHunkMode := false - if oldState != nil { - userEnabledHunkMode = oldState.userEnabledHunkMode - } - - // if we have clicked from the outside to focus the main view we'll pass in a non-negative line index so that we can instantly select that line - if selectedLineIdx >= 0 { - // Clamp to the number of wrapped view lines; index might be out of - // bounds if a custom pager is being used which produces more lines - selectedLineIdx = min(selectedLineIdx, len(viewLineIndices)-1) - - selectMode = RANGE - rangeStartLineIdx = selectedLineIdx - } else if oldState != nil { - // if we previously had a selectMode of RANGE, we want that to now be line again (or hunk, if that's the default) - if oldState.selectMode != RANGE { - selectMode = oldState.selectMode - } - selectedLineIdx = viewLineIndices[patch.GetNextChangeIdx(oldState.patchLineIndices[oldState.selectedLineIdx])] - } else { - selectedLineIdx = viewLineIndices[patch.GetNextChangeIdx(0)] - } - - return &State{ - patch: patch, - selectedLineIdx: selectedLineIdx, - selectMode: selectMode, - rangeStartLineIdx: rangeStartLineIdx, - rangeIsSticky: false, - diff: diff, - viewLineIndices: viewLineIndices, - patchLineIndices: patchLineIndices, - userEnabledHunkMode: userEnabledHunkMode, - } -} - -func (s *State) OnViewWidthChanged(view *gocui.View) { - if !view.Wrap { - return - } - - selectedPatchLineIdx := s.patchLineIndices[s.selectedLineIdx] - var rangeStartPatchLineIdx int - if s.selectMode == RANGE { - rangeStartPatchLineIdx = s.patchLineIndices[s.rangeStartLineIdx] - } - s.viewLineIndices, s.patchLineIndices = wrapPatchLines(s.diff, view) - s.selectedLineIdx = s.viewLineIndices[selectedPatchLineIdx] - if s.selectMode == RANGE { - s.rangeStartLineIdx = s.viewLineIndices[rangeStartPatchLineIdx] - } -} - -func (s *State) GetSelectedPatchLineIdx() int { - return s.patchLineIndices[s.selectedLineIdx] -} - -func (s *State) GetSelectedViewLineIdx() int { - return s.selectedLineIdx -} - -func (s *State) GetDiff() string { - return s.diff -} - -func (s *State) ToggleSelectHunk() { - if s.selectMode == HUNK { - s.selectMode = LINE - } else { - s.selectMode = HUNK - s.userEnabledHunkMode = true - - // If we are not currently on a change line, select the next one (or the - // previous one if there is no next one): - s.selectedLineIdx = s.viewLineIndices[s.patch.GetNextChangeIdx( - s.patchLineIndices[s.selectedLineIdx])] - } -} - -func (s *State) ToggleStickySelectRange() { - s.ToggleSelectRange(true) -} - -func (s *State) ToggleSelectRange(sticky bool) { - if s.SelectingRange() { - s.selectMode = LINE - } else { - s.selectMode = RANGE - s.rangeStartLineIdx = s.selectedLineIdx - s.rangeIsSticky = sticky - } -} - -func (s *State) SetRangeIsSticky(value bool) { - s.rangeIsSticky = value -} - -func (s *State) SelectingHunk() bool { - return s.selectMode == HUNK -} - -func (s *State) SelectingHunkEnabledByUser() bool { - return s.selectMode == HUNK && s.userEnabledHunkMode -} - -func (s *State) SelectingRange() bool { - return s.selectMode == RANGE && (s.rangeIsSticky || s.rangeStartLineIdx != s.selectedLineIdx) -} - -func (s *State) SelectingLine() bool { - return s.selectMode == LINE -} - -func (s *State) SetLineSelectMode() { - s.selectMode = LINE -} - -func (s *State) DismissHunkSelectMode() { - if s.SelectingHunk() { - s.selectMode = LINE - } -} - -// For when you move the cursor without holding shift (meaning if we're in -// a non-sticky range select, we'll cancel it) -func (s *State) SelectLine(newSelectedLineIdx int) { - if s.selectMode == RANGE && !s.rangeIsSticky { - s.selectMode = LINE - } - - s.selectLineWithoutRangeCheck(newSelectedLineIdx) -} - -func (s *State) clampLineIdx(lineIdx int) int { - return lo.Clamp(lineIdx, 0, len(s.patchLineIndices)-1) -} - -// This just moves the cursor without caring about range select -func (s *State) selectLineWithoutRangeCheck(newSelectedLineIdx int) { - s.selectedLineIdx = s.clampLineIdx(newSelectedLineIdx) -} - -func (s *State) SelectNewLineForRange(newSelectedLineIdx int) { - s.rangeStartLineIdx = s.clampLineIdx(newSelectedLineIdx) - - s.selectMode = RANGE - - s.selectLineWithoutRangeCheck(newSelectedLineIdx) -} - -func (s *State) DragSelectLine(newSelectedLineIdx int) { - s.selectMode = RANGE - - s.selectLineWithoutRangeCheck(newSelectedLineIdx) -} - -func (s *State) CycleSelection(forward bool) { - if s.SelectingHunk() { - if forward { - s.SelectNextHunk() - } else { - s.SelectPreviousHunk() - } - } else { - s.CycleLine(forward) - } -} - -func (s *State) SelectPreviousHunk() { - patchLines := s.patch.Lines() - patchLineIdx := s.patchLineIndices[s.selectedLineIdx] - nextNonChangeLine := patchLineIdx - for nextNonChangeLine >= 0 && patchLines[nextNonChangeLine].IsChange() { - nextNonChangeLine-- - } - nextChangeLine := nextNonChangeLine - for nextChangeLine >= 0 && !patchLines[nextChangeLine].IsChange() { - nextChangeLine-- - } - if nextChangeLine >= 0 { - // Now we found a previous hunk, but we're on its last line. Skip to the beginning. - for nextChangeLine > 0 && patchLines[nextChangeLine-1].IsChange() { - nextChangeLine-- - } - s.selectedLineIdx = s.viewLineIndices[nextChangeLine] - } -} - -func (s *State) SelectNextHunk() { - patchLines := s.patch.Lines() - patchLineIdx := s.patchLineIndices[s.selectedLineIdx] - nextNonChangeLine := patchLineIdx - for nextNonChangeLine < len(patchLines) && patchLines[nextNonChangeLine].IsChange() { - nextNonChangeLine++ - } - nextChangeLine := nextNonChangeLine - for nextChangeLine < len(patchLines) && !patchLines[nextChangeLine].IsChange() { - nextChangeLine++ - } - if nextChangeLine < len(patchLines) { - s.selectedLineIdx = s.viewLineIndices[nextChangeLine] - } -} - -func (s *State) CycleLine(forward bool) { - change := 1 - if !forward { - change = -1 - } - - s.SelectLine(s.selectedLineIdx + change) -} - -// This is called when we use shift+arrow to expand the range (i.e. a non-sticky -// range) -func (s *State) CycleRange(forward bool) { - if !s.SelectingRange() { - s.ToggleSelectRange(false) - } - - s.SetRangeIsSticky(false) - - change := 1 - if !forward { - change = -1 - } - - s.selectLineWithoutRangeCheck(s.selectedLineIdx + change) -} - -// returns first and last patch line index of current hunk -func (s *State) CurrentHunkBounds() (int, int) { - hunkIdx := s.patch.HunkContainingLine(s.patchLineIndices[s.selectedLineIdx]) - start := s.patch.HunkStartIdx(hunkIdx) - end := s.patch.HunkEndIdx(hunkIdx) - return start, end -} - -func (s *State) selectionRangeForCurrentBlockOfChanges() (int, int) { - patchLines := s.patch.Lines() - patchLineIdx := s.patchLineIndices[s.selectedLineIdx] - - patchStart := patchLineIdx - for patchStart > 0 && patchLines[patchStart-1].IsChange() { - patchStart-- - } - - patchEnd := patchLineIdx - for patchEnd < len(patchLines)-1 && patchLines[patchEnd+1].IsChange() { - patchEnd++ - } - - viewStart, viewEnd := s.viewLineIndices[patchStart], s.viewLineIndices[patchEnd] - - // Increase viewEnd in case the last patch line is wrapped to more than one view line. - for viewEnd < len(s.patchLineIndices)-1 && s.patchLineIndices[viewEnd] == s.patchLineIndices[viewEnd+1] { - viewEnd++ - } - - return viewStart, viewEnd -} - -func (s *State) SelectedViewRange() (int, int) { - switch s.selectMode { - case HUNK: - return s.selectionRangeForCurrentBlockOfChanges() - case RANGE: - if s.rangeStartLineIdx > s.selectedLineIdx { - return s.selectedLineIdx, s.rangeStartLineIdx - } - return s.rangeStartLineIdx, s.selectedLineIdx - case LINE: - return s.selectedLineIdx, s.selectedLineIdx - default: - // should never happen - return 0, 0 - } -} - -func (s *State) SelectedPatchRange() (int, int) { - start, end := s.SelectedViewRange() - return s.patchLineIndices[start], s.patchLineIndices[end] -} - -// Returns the line indices of the selected patch range that are changes (i.e. additions or deletions) -func (s *State) LineIndicesOfAddedOrDeletedLinesInSelectedPatchRange() []int { - viewStart, viewEnd := s.SelectedViewRange() - patchStart, patchEnd := s.patchLineIndices[viewStart], s.patchLineIndices[viewEnd] - lines := s.patch.Lines() - indices := []int{} - for i := patchStart; i <= patchEnd; i++ { - if lines[i].IsChange() { - indices = append(indices, i) - } - } - return indices -} - -func (s *State) CurrentLineNumber() int { - return s.patch.LineNumberOfLine(s.patchLineIndices[s.selectedLineIdx]) -} - -func (s *State) AdjustSelectedLineIdx(change int) { - s.DismissHunkSelectMode() - s.SelectLine(s.selectedLineIdx + change) -} - -func (s *State) RenderForLineIndices(includedLineIndices []int) string { - includedLineIndicesSet := set.NewFromSlice(includedLineIndices) - return s.patch.FormatView(patch.FormatViewOpts{ - IncLineIndices: includedLineIndicesSet, - }) -} - -func (s *State) PlainRenderSelected() string { - firstLineIdx, lastLineIdx := s.SelectedPatchRange() - return s.patch.FormatRangePlain(firstLineIdx, lastLineIdx) -} - -func (s *State) SelectBottom() { - s.DismissHunkSelectMode() - s.SelectLine(len(s.patchLineIndices) - 1) -} - -func (s *State) SelectTop() { - s.DismissHunkSelectMode() - s.SelectLine(0) -} - -func (s *State) CalculateOrigin(currentOrigin int, bufferHeight int, numLines int) int { - firstLineIdx, lastLineIdx := s.SelectedViewRange() - - return calculateOrigin(currentOrigin, bufferHeight, numLines, firstLineIdx, lastLineIdx, s.GetSelectedViewLineIdx(), s.selectMode) -} - -func wrapPatchLines(diff string, view *gocui.View) ([]int, []int) { - _, viewLineIndices, patchLineIndices := utils.WrapViewLinesToWidth( - view.Wrap, view.Editable, strings.TrimSuffix(diff, "\n"), view.InnerWidth(), view.TabWidth) - return viewLineIndices, patchLineIndices -} - -func (s *State) SelectNextStageableLineOfSameIncludedState(includedLines []int, included bool) { - _, lastLineIdx := s.SelectedPatchRange() - patchLineIdx, found := s.patch.GetNextChangeIdxOfSameIncludedState(lastLineIdx+1, includedLines, included) - if found { - s.SelectLine(s.viewLineIndices[patchLineIdx]) - } -} diff --git a/pkg/gui/popup/popup_handler.go b/pkg/gui/popup/popup_handler.go deleted file mode 100644 index d8ce654b4da..00000000000 --- a/pkg/gui/popup/popup_handler.go +++ /dev/null @@ -1,150 +0,0 @@ -package popup - -import ( - "context" - "errors" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type PopupHandler struct { - *common.Common - createPopupPanelFn func(context.Context, types.CreatePopupPanelOpts) - onErrorFn func() error - popContextFn func() - currentContextFn func() types.Context - createMenuFn func(types.CreateMenuOptions) error - withWaitingStatusFn func(message string, f func(gocui.Task) error) - withWaitingStatusSyncFn func(message string, f func() error) error - toastFn func(message string, kind types.ToastKind) - getPromptInputFn func() string - inDemo func() bool -} - -var _ types.IPopupHandler = &PopupHandler{} - -func NewPopupHandler( - common *common.Common, - createPopupPanelFn func(context.Context, types.CreatePopupPanelOpts), - onErrorFn func() error, - popContextFn func(), - currentContextFn func() types.Context, - createMenuFn func(types.CreateMenuOptions) error, - withWaitingStatusFn func(message string, f func(gocui.Task) error), - withWaitingStatusSyncFn func(message string, f func() error) error, - toastFn func(message string, kind types.ToastKind), - getPromptInputFn func() string, - inDemo func() bool, -) *PopupHandler { - return &PopupHandler{ - Common: common, - createPopupPanelFn: createPopupPanelFn, - onErrorFn: onErrorFn, - popContextFn: popContextFn, - currentContextFn: currentContextFn, - createMenuFn: createMenuFn, - withWaitingStatusFn: withWaitingStatusFn, - withWaitingStatusSyncFn: withWaitingStatusSyncFn, - toastFn: toastFn, - getPromptInputFn: getPromptInputFn, - inDemo: inDemo, - } -} - -func (self *PopupHandler) Menu(opts types.CreateMenuOptions) error { - return self.createMenuFn(opts) -} - -func (self *PopupHandler) Toast(message string) { - self.toastFn(message, types.ToastKindStatus) -} - -func (self *PopupHandler) ErrorToast(message string) { - self.toastFn(message, types.ToastKindError) -} - -func (self *PopupHandler) SetToastFunc(f func(string, types.ToastKind)) { - self.toastFn = f -} - -func (self *PopupHandler) WithWaitingStatus(message string, f func(gocui.Task) error) error { - self.withWaitingStatusFn(message, f) - return nil -} - -func (self *PopupHandler) WithWaitingStatusSync(message string, f func() error) error { - return self.withWaitingStatusSyncFn(message, f) -} - -func (self *PopupHandler) ErrorHandler(err error) error { - var notHandledError *types.ErrKeybindingNotHandled - if errors.As(err, ¬HandledError) { - if !notHandledError.DisabledReason.ShowErrorInPanel { - if msg := notHandledError.DisabledReason.Text; len(msg) > 0 { - self.ErrorToast(self.Tr.DisabledMenuItemPrefix + msg) - } - return nil - } - } - - // Need to set bold here explicitly; otherwise it gets cancelled by the red colouring. - coloredMessage := style.FgRed.SetBold().Sprint(strings.TrimSpace(err.Error())) - if err := self.onErrorFn(); err != nil { - return err - } - - self.Alert(self.Tr.Error, coloredMessage) - - return nil -} - -func (self *PopupHandler) Alert(title string, message string) { - self.Confirm(types.ConfirmOpts{Title: title, Prompt: message}) -} - -func (self *PopupHandler) Confirm(opts types.ConfirmOpts) { - self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{ - Title: opts.Title, - Prompt: opts.Prompt, - HandleConfirm: opts.HandleConfirm, - HandleClose: opts.HandleClose, - }) -} - -func (self *PopupHandler) ConfirmIf(condition bool, opts types.ConfirmOpts) error { - if condition { - self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{ - Title: opts.Title, - Prompt: opts.Prompt, - HandleConfirm: opts.HandleConfirm, - HandleClose: opts.HandleClose, - }) - return nil - } - - return opts.HandleConfirm() -} - -func (self *PopupHandler) Prompt(opts types.PromptOpts) { - self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{ - Title: opts.Title, - Prompt: opts.InitialContent, - Editable: true, - HandleConfirmPrompt: opts.HandleConfirm, - HandleClose: opts.HandleClose, - HandleDeleteSuggestion: opts.HandleDeleteSuggestion, - FindSuggestionsFunc: opts.FindSuggestionsFunc, - AllowEditSuggestion: opts.AllowEditSuggestion, - Mask: opts.Mask, - }) -} - -// returns the content that has currently been typed into the prompt. Useful for -// asynchronously updating the suggestions list under the prompt. -func (self *PopupHandler) GetPromptInput() string { - return self.getPromptInputFn() -} diff --git a/pkg/gui/presentation/authors/authors.go b/pkg/gui/presentation/authors/authors.go deleted file mode 100644 index 5b939d76dec..00000000000 --- a/pkg/gui/presentation/authors/authors.go +++ /dev/null @@ -1,141 +0,0 @@ -package authors - -import ( - "crypto/md5" - "strings" - - "github.com/gookit/color" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/lucasb-eyer/go-colorful" - "github.com/mattn/go-runewidth" -) - -type authorNameCacheKey struct { - authorName string - truncateTo int -} - -// if these being global variables causes trouble we can wrap them in a struct -// attached to the gui state. -var ( - authorInitialCache = make(map[string]string) - authorNameCache = make(map[authorNameCacheKey]string) - authorStyleCache = make(map[string]*style.TextStyle) -) - -const authorNameWildcard = "*" - -func ShortAuthor(authorName string) string { - if value, ok := authorInitialCache[authorName]; ok { - return value - } - - initials := getInitials(authorName) - if initials == "" { - return "" - } - - value := AuthorStyle(authorName).Sprint(initials) - authorInitialCache[authorName] = value - - return value -} - -func LongAuthor(authorName string, length int) string { - cacheKey := authorNameCacheKey{authorName: authorName, truncateTo: length} - if value, ok := authorNameCache[cacheKey]; ok { - return value - } - - paddedAuthorName := utils.WithPadding(authorName, length, utils.AlignLeft) - truncatedName := utils.TruncateWithEllipsis(paddedAuthorName, length) - value := AuthorStyle(authorName).Sprint(truncatedName) - authorNameCache[cacheKey] = value - - return value -} - -// AuthorWithLength returns a representation of the author that fits into a -// given maximum length: -// - if the length is less than 2, it returns an empty string -// - if the length is 2, it returns the initials -// - otherwise, it returns the author name truncated to the maximum length -func AuthorWithLength(authorName string, length int) string { - if length < 2 { - return "" - } - - if length == 2 { - return ShortAuthor(authorName) - } - - return LongAuthor(authorName, length) -} - -func AuthorStyle(authorName string) *style.TextStyle { - if value, ok := authorStyleCache[authorName]; ok { - return value - } - - // use the unified style whatever the author name is - if value, ok := authorStyleCache[authorNameWildcard]; ok { - return value - } - - value := trueColorStyle(authorName) - - authorStyleCache[authorName] = &value - - return &value -} - -func trueColorStyle(str string) style.TextStyle { - hash := md5.Sum([]byte(str)) - c := colorful.Hsl(randFloat(hash[0:4])*360.0, 0.6+0.4*randFloat(hash[4:8]), 0.4+randFloat(hash[8:12])*0.2) - - return style.New().SetFg(style.NewRGBColor(color.RGB(uint8(c.R*255), uint8(c.G*255), uint8(c.B*255)))) -} - -func randFloat(hash []byte) float64 { - return float64(randInt(hash, 100)) / 100 -} - -func randInt(hash []byte, max int) int { - sum := 0 - for _, b := range hash { - sum = (sum + int(b)) % max - } - return sum -} - -func getInitials(authorName string) string { - if authorName == "" { - return authorName - } - - firstRune := getFirstRune(authorName) - if runewidth.RuneWidth(firstRune) > 1 { - return string(firstRune) - } - - split := strings.Split(authorName, " ") - if len(split) == 1 { - return utils.LimitStr(authorName, 2) - } - - return utils.LimitStr(split[0], 1) + utils.LimitStr(split[1], 1) -} - -func getFirstRune(str string) rune { - // just using the loop for the sake of getting the first rune - for _, r := range str { - return r - } - // should never land here - return 0 -} - -func SetCustomAuthors(customAuthorColors map[string]string) { - authorStyleCache = utils.SetCustomColors(customAuthorColors) -} diff --git a/pkg/gui/presentation/authors/authors_test.go b/pkg/gui/presentation/authors/authors_test.go deleted file mode 100644 index d7c651031e1..00000000000 --- a/pkg/gui/presentation/authors/authors_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package authors - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/stretchr/testify/assert" -) - -func TestGetInitials(t *testing.T) { - for input, expectedOutput := range map[string]string{ - "Jesse Duffield": "JD", - "Jesse Duffield Man": "JD", - "JesseDuffield": "Je", - "J": "J", - "六书六書": "六", - "書": "書", - "": "", - } { - output := getInitials(input) - if output != expectedOutput { - t.Errorf("Expected %s to be %s", output, expectedOutput) - } - } -} - -func TestAuthorWithLength(t *testing.T) { - scenarios := []struct { - authorName string - length int - expectedOutput string - }{ - {"Jesse Duffield", 0, ""}, - {"Jesse Duffield", 1, ""}, - {"Jesse Duffield", 2, "JD"}, - {"Jesse Duffield", 3, "Je…"}, - {"Jesse Duffield", 10, "Jesse Duf…"}, - {"Jesse Duffield", 14, "Jesse Duffield"}, - } - for _, s := range scenarios { - assert.Equal(t, s.expectedOutput, utils.Decolorise(AuthorWithLength(s.authorName, s.length))) - } -} diff --git a/pkg/gui/presentation/branches.go b/pkg/gui/presentation/branches.go deleted file mode 100644 index 012059f8ba8..00000000000 --- a/pkg/gui/presentation/branches.go +++ /dev/null @@ -1,231 +0,0 @@ -package presentation - -import ( - "fmt" - "regexp" - "strings" - "time" - - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/mattn/go-runewidth" - "github.com/samber/lo" -) - -type colorMatcher struct { - patterns map[string]*style.TextStyle - isRegex bool // NOTE: this value is needed only until the deprecated branchColors config is removed and only regex color patterns are used -} - -var colorPatterns *colorMatcher - -func GetBranchListDisplayStrings( - branches []*models.Branch, - getItemOperation func(item types.HasUrn) types.ItemOperation, - fullDescription bool, - diffName string, - viewWidth int, - tr *i18n.TranslationSet, - userConfig *config.UserConfig, - worktrees []*models.Worktree, -) [][]string { - return lo.Map(branches, func(branch *models.Branch, _ int) []string { - diffed := branch.Name == diffName - return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, viewWidth, tr, userConfig, worktrees, time.Now()) - }) -} - -// getBranchDisplayStrings returns the display string of branch -func getBranchDisplayStrings( - b *models.Branch, - itemOperation types.ItemOperation, - fullDescription bool, - diffed bool, - viewWidth int, - tr *i18n.TranslationSet, - userConfig *config.UserConfig, - worktrees []*models.Worktree, - now time.Time, -) []string { - checkedOutByWorkTree := git_commands.CheckedOutByOtherWorktree(b, worktrees) - showCommitHash := fullDescription || userConfig.Gui.ShowBranchCommitHash - branchStatus := BranchStatus(b, itemOperation, tr, now, userConfig) - divergence := divergenceStr(b, itemOperation, tr, userConfig) - worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree)) - - // Recency is always three characters, plus one for the space - availableWidth := viewWidth - 4 - if len(divergence) > 0 { - availableWidth -= utils.StringWidth(divergence) + 1 - } - if icons.IsIconEnabled() { - availableWidth -= 2 // one for the icon, one for the space - } - if showCommitHash { - availableWidth -= utils.COMMIT_HASH_SHORT_SIZE + 1 - } - paddingNeededForDivergence := availableWidth - - if checkedOutByWorkTree { - availableWidth -= utils.StringWidth(worktreeIcon) + 1 - } - - if len(branchStatus) > 0 { - availableWidth -= utils.StringWidth(utils.Decolorise(branchStatus)) + 1 - } - - displayName := b.Name - if b.DisplayName != "" { - displayName = b.DisplayName - } - - nameTextStyle := GetBranchTextStyle(b.Name) - if diffed { - nameTextStyle = theme.DiffTerminalColor - } - - // Don't bother shortening branch names that are already 3 characters or less - if utils.StringWidth(displayName) > max(availableWidth, 3) { - // Never shorten the branch name to less then 3 characters - len := max(availableWidth, 4) - displayName = runewidth.Truncate(displayName, len, "…") - } - coloredName := nameTextStyle.Sprint(displayName) - if checkedOutByWorkTree { - coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon)) - } - if len(branchStatus) > 0 { - coloredName = fmt.Sprintf("%s %s", coloredName, branchStatus) - } - - recencyColor := style.FgCyan - if b.Recency == " *" { - recencyColor = style.FgGreen - } - - res := make([]string, 0, 6) - res = append(res, recencyColor.Sprint(b.Recency)) - - if icons.IsIconEnabled() { - res = append(res, nameTextStyle.Sprint(icons.IconForBranch(b))) - } - - if showCommitHash { - res = append(res, utils.ShortHash(b.CommitHash)) - } - - if divergence != "" { - paddingNeededForDivergence -= utils.StringWidth(utils.Decolorise(coloredName)) - 1 - if paddingNeededForDivergence > 0 { - coloredName += strings.Repeat(" ", paddingNeededForDivergence) - coloredName += style.FgCyan.Sprint(divergence) - } - } - res = append(res, coloredName) - - if fullDescription { - res = append( - res, - fmt.Sprintf("%s %s", - style.FgYellow.Sprint(b.UpstreamRemote), - style.FgYellow.Sprint(b.UpstreamBranch), - ), - utils.TruncateWithEllipsis(b.Subject, 60), - ) - } - return res -} - -// GetBranchTextStyle branch color -func GetBranchTextStyle(name string) style.TextStyle { - if style, ok := colorPatterns.match(name); ok { - return *style - } - - return theme.DefaultTextColor -} - -func (m *colorMatcher) match(name string) (*style.TextStyle, bool) { - if m.isRegex { - for pattern, style := range m.patterns { - if matched, _ := regexp.MatchString(pattern, name); matched { - return style, true - } - } - } else { - // old behavior using the deprecated branchColors behavior matching on branch type - branchType := strings.Split(name, "/")[0] - if value, ok := m.patterns[branchType]; ok { - return value, true - } - } - - return nil, false -} - -func BranchStatus( - branch *models.Branch, - itemOperation types.ItemOperation, - tr *i18n.TranslationSet, - now time.Time, - userConfig *config.UserConfig, -) string { - itemOperationStr := ItemOperationToString(itemOperation, tr) - if itemOperationStr != "" { - return style.FgCyan.Sprintf("%s %s", itemOperationStr, Loader(now, userConfig.Gui.Spinner)) - } - - result := "" - if branch.IsTrackingRemote() { - if branch.UpstreamGone { - result = style.FgRed.Sprint(tr.UpstreamGone) - } else if branch.MatchesUpstream() { - result = style.FgGreen.Sprint("✓") - } else if branch.RemoteBranchNotStoredLocally() { - result = style.FgMagenta.Sprint("?") - } else if branch.IsBehindForPull() && branch.IsAheadForPull() { - result = style.FgYellow.Sprintf("↓%s↑%s", branch.BehindForPull, branch.AheadForPull) - } else if branch.IsBehindForPull() { - result = style.FgYellow.Sprintf("↓%s", branch.BehindForPull) - } else if branch.IsAheadForPull() { - result = style.FgYellow.Sprintf("↑%s", branch.AheadForPull) - } - } - - return result -} - -func divergenceStr( - branch *models.Branch, - itemOperation types.ItemOperation, - tr *i18n.TranslationSet, - userConfig *config.UserConfig, -) string { - result := "" - if ItemOperationToString(itemOperation, tr) == "" && userConfig.Gui.ShowDivergenceFromBaseBranch != "none" { - behind := branch.BehindBaseBranch.Load() - if behind != 0 { - if userConfig.Gui.ShowDivergenceFromBaseBranch == "arrowAndNumber" { - result += fmt.Sprintf("↓%d", behind) - } else { - result += "↓" - } - } - } - - return result -} - -func SetCustomBranches(customBranchColors map[string]string, isRegex bool) { - colorPatterns = &colorMatcher{ - patterns: utils.SetCustomColors(customBranchColors), - isRegex: isRegex, - } -} diff --git a/pkg/gui/presentation/branches_test.go b/pkg/gui/presentation/branches_test.go deleted file mode 100644 index a73c7aa56dc..00000000000 --- a/pkg/gui/presentation/branches_test.go +++ /dev/null @@ -1,358 +0,0 @@ -package presentation - -import ( - "fmt" - "sync/atomic" - "testing" - "time" - - "github.com/gookit/color" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" - "github.com/stretchr/testify/assert" - "github.com/xo/terminfo" -) - -func makeAtomic(v int32) *atomic.Int32 { - var result atomic.Int32 - result.Store(v) - return &result -} - -func Test_getBranchDisplayStrings(t *testing.T) { - scenarios := []struct { - branch *models.Branch - itemOperation types.ItemOperation - fullDescription bool - viewWidth int - useIcons bool - checkedOutByWorktree bool - showDivergenceCfg string - expected []string - }{ - // First some tests for when the view is wide enough so that everything fits: - { - branch: &models.Branch{Name: "branch_name", Recency: "1m"}, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 100, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "branch_name"}, - }, - { - branch: &models.Branch{Name: "🍉_special_char", Recency: "1m"}, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 19, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "🍉_special_char"}, - }, - { - branch: &models.Branch{Name: "branch_name", Recency: "1m"}, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 100, - useIcons: false, - checkedOutByWorktree: true, - showDivergenceCfg: "none", - expected: []string{"1m", "branch_name (worktree)"}, - }, - { - branch: &models.Branch{Name: "branch_name", Recency: "1m"}, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 100, - useIcons: true, - checkedOutByWorktree: true, - showDivergenceCfg: "none", - expected: []string{"1m", "󰘬", "branch_name 󰌹"}, - }, - { - branch: &models.Branch{ - Name: "branch_name", - Recency: "1m", - UpstreamRemote: "origin", - AheadForPull: "0", - BehindForPull: "0", - }, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 100, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "branch_name ✓"}, - }, - { - branch: &models.Branch{ - Name: "branch_name", - Recency: "1m", - UpstreamRemote: "origin", - AheadForPull: "3", - BehindForPull: "5", - }, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 100, - useIcons: false, - checkedOutByWorktree: true, - showDivergenceCfg: "none", - expected: []string{"1m", "branch_name (worktree) ↓5↑3"}, - }, - { - branch: &models.Branch{ - Name: "branch_name", - Recency: "1m", - BehindBaseBranch: *makeAtomic(2), - }, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 20, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "onlyArrow", - expected: []string{"1m", "branch_name ↓"}, - }, - { - branch: &models.Branch{ - Name: "branch_name", - Recency: "1m", - UpstreamRemote: "origin", - AheadForPull: "0", - BehindForPull: "0", - BehindBaseBranch: *makeAtomic(2), - }, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 22, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "arrowAndNumber", - expected: []string{"1m", "branch_name ✓ ↓2"}, - }, - { - branch: &models.Branch{ - Name: "branch_name", - Recency: "1m", - UpstreamRemote: "origin", - AheadForPull: "3", - BehindForPull: "5", - BehindBaseBranch: *makeAtomic(2), - }, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 26, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "arrowAndNumber", - expected: []string{"1m", "branch_name ↓5↑3 ↓2"}, - }, - { - branch: &models.Branch{Name: "branch_name", Recency: "1m"}, - itemOperation: types.ItemOperationPushing, - fullDescription: false, - viewWidth: 100, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "branch_name Pushing |"}, - }, - { - branch: &models.Branch{ - Name: "branch_name", - Recency: "1m", - CommitHash: "1234567890", - UpstreamRemote: "origin", - UpstreamBranch: "branch_name", - AheadForPull: "0", - BehindForPull: "0", - Subject: "commit title", - }, - itemOperation: types.ItemOperationNone, - fullDescription: true, - viewWidth: 100, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "12345678", "branch_name ✓", "origin branch_name", "commit title"}, - }, - - // Now tests for how we truncate the branch name when there's not enough room: - { - branch: &models.Branch{Name: "branch_name", Recency: "1m"}, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 14, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "branch_na…"}, - }, - { - branch: &models.Branch{Name: "🍉_special_char", Recency: "1m"}, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 18, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "🍉_special_ch…"}, - }, - { - branch: &models.Branch{Name: "branch_name", Recency: "1m"}, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 14, - useIcons: false, - checkedOutByWorktree: true, - showDivergenceCfg: "none", - expected: []string{"1m", "bra… (worktree)"}, - }, - { - branch: &models.Branch{Name: "branch_name", Recency: "1m"}, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 14, - useIcons: true, - checkedOutByWorktree: true, - showDivergenceCfg: "none", - expected: []string{"1m", "󰘬", "branc… 󰌹"}, - }, - { - branch: &models.Branch{ - Name: "branch_name", - Recency: "1m", - UpstreamRemote: "origin", - AheadForPull: "0", - BehindForPull: "0", - }, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 14, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "branch_… ✓"}, - }, - { - branch: &models.Branch{ - Name: "branch_name", - Recency: "1m", - UpstreamRemote: "origin", - AheadForPull: "3", - BehindForPull: "5", - BehindBaseBranch: *makeAtomic(4), - }, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 21, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "arrowAndNumber", - expected: []string{"1m", "branch_n… ↓5↑3 ↓4"}, - }, - { - branch: &models.Branch{ - Name: "branch_name", - Recency: "1m", - UpstreamRemote: "origin", - AheadForPull: "3", - BehindForPull: "5", - }, - itemOperation: types.ItemOperationNone, - fullDescription: false, - viewWidth: 30, - useIcons: false, - checkedOutByWorktree: true, - showDivergenceCfg: "none", - expected: []string{"1m", "branch_na… (worktree) ↓5↑3"}, - }, - { - branch: &models.Branch{Name: "branch_name", Recency: "1m"}, - itemOperation: types.ItemOperationPushing, - fullDescription: false, - viewWidth: 20, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "branc… Pushing |"}, - }, - { - branch: &models.Branch{Name: "abc", Recency: "1m"}, - itemOperation: types.ItemOperationPushing, - fullDescription: false, - viewWidth: -1, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "abc Pushing |"}, - }, - { - branch: &models.Branch{Name: "ab", Recency: "1m"}, - itemOperation: types.ItemOperationPushing, - fullDescription: false, - viewWidth: -1, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "ab Pushing |"}, - }, - { - branch: &models.Branch{Name: "a", Recency: "1m"}, - itemOperation: types.ItemOperationPushing, - fullDescription: false, - viewWidth: -1, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "a Pushing |"}, - }, - { - branch: &models.Branch{ - Name: "branch_name", - Recency: "1m", - CommitHash: "1234567890", - UpstreamRemote: "origin", - UpstreamBranch: "branch_name", - AheadForPull: "0", - BehindForPull: "0", - Subject: "commit title", - }, - itemOperation: types.ItemOperationNone, - fullDescription: true, - viewWidth: 20, - useIcons: false, - checkedOutByWorktree: false, - showDivergenceCfg: "none", - expected: []string{"1m", "12345678", "bran… ✓", "origin branch_name", "commit title"}, - }, - } - - oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelNone) - defer color.ForceSetColorLevel(oldColorLevel) - - c := common.NewDummyCommon() - SetCustomBranches(c.UserConfig().Gui.BranchColorPatterns, true) - - for i, s := range scenarios { - icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", "")) - c.UserConfig().Gui.ShowDivergenceFromBaseBranch = s.showDivergenceCfg - - worktrees := []*models.Worktree{} - if s.checkedOutByWorktree { - worktrees = append(worktrees, &models.Worktree{Branch: s.branch.Name}) - } - - t.Run(fmt.Sprintf("getBranchDisplayStrings_%d", i), func(t *testing.T) { - strings := getBranchDisplayStrings(s.branch, s.itemOperation, s.fullDescription, false, s.viewWidth, c.Tr, c.UserConfig(), worktrees, time.Time{}) - assert.Equal(t, s.expected, strings) - }) - } -} diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go deleted file mode 100644 index a5799bcb333..00000000000 --- a/pkg/gui/presentation/commits.go +++ /dev/null @@ -1,527 +0,0 @@ -package presentation - -import ( - "fmt" - "strings" - "time" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/authors" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/graph" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/kyokomi/emoji/v2" - "github.com/samber/lo" - "github.com/sasha-s/go-deadlock" - "github.com/stefanhaller/git-todo-parser/todo" -) - -type pipeSetCacheKey struct { - commitHash string - commitCount int - divergence models.Divergence -} - -var ( - pipeSetCache = make(map[pipeSetCacheKey][][]graph.Pipe) - mutex deadlock.Mutex -) - -type bisectBounds struct { - newIndex int - oldIndex int -} - -func GetCommitListDisplayStrings( - common *common.Common, - commits []*models.Commit, - branches []*models.Branch, - currentBranchName string, - hasRebaseUpdateRefsConfig bool, - fullDescription bool, - cherryPickedCommitHashSet *set.Set[string], - diffName string, - markedBaseCommit string, - timeFormat string, - shortTimeFormat string, - now time.Time, - parseEmoji bool, - selectedCommitHashPtr *string, - startIdx int, - endIdx int, - showGraph bool, - bisectInfo *git_commands.BisectInfo, -) [][]string { - mutex.Lock() - defer mutex.Unlock() - - if len(commits) == 0 { - return nil - } - - if startIdx >= len(commits) { - return nil - } - - // this is where my non-TODO commits begin - rebaseOffset := min(indexOfFirstNonTODOCommit(commits), endIdx) - - filteredCommits := commits[startIdx:endIdx] - - bisectBounds := getbisectBounds(commits, bisectInfo) - - // function expects to be passed the index of the commit in terms of the `commits` slice - var getGraphLine func(int) string - if showGraph { - if len(commits) > 0 && commits[0].Divergence != models.DivergenceNone { - // Showing a divergence log; we know we don't have any rebasing - // commits in this case. But we need to render separate graphs for - // the Local and Remote sections. - allGraphLines := []string{} - - _, localSectionStart, found := lo.FindIndexOf( - commits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceLeft }) - if !found { - localSectionStart = len(commits) - } - - if localSectionStart > 0 { - // we have some remote commits - pipeSets := loadPipesets(commits[:localSectionStart]) - if startIdx < localSectionStart { - // some of the remote commits are visible - start := startIdx - end := min(endIdx, localSectionStart) - graphPipeSets := pipeSets[start:end] - graphCommits := commits[start:end] - graphLines := graph.RenderAux( - graphPipeSets, - graphCommits, - selectedCommitHashPtr, - ) - allGraphLines = append(allGraphLines, graphLines...) - } - } - if localSectionStart < len(commits) { - // we have some local commits - pipeSets := loadPipesets(commits[localSectionStart:]) - if localSectionStart < endIdx { - // some of the local commits are visible - graphOffset := max(startIdx, localSectionStart) - pipeSetOffset := max(startIdx-localSectionStart, 0) - graphPipeSets := pipeSets[pipeSetOffset : endIdx-localSectionStart] - graphCommits := commits[graphOffset:endIdx] - graphLines := graph.RenderAux( - graphPipeSets, - graphCommits, - selectedCommitHashPtr, - ) - allGraphLines = append(allGraphLines, graphLines...) - } - } - - getGraphLine = func(idx int) string { - return allGraphLines[idx-startIdx] - } - } else { - // this is where the graph begins (may be beyond the TODO commits depending on startIdx, - // but we'll never include TODO commits as part of the graph because it'll be messy) - graphOffset := max(startIdx, rebaseOffset) - - pipeSets := loadPipesets(commits[rebaseOffset:]) - pipeSetOffset := max(startIdx-rebaseOffset, 0) - graphPipeSets := pipeSets[pipeSetOffset:max(endIdx-rebaseOffset, 0)] - graphCommits := commits[graphOffset:endIdx] - graphLines := graph.RenderAux( - graphPipeSets, - graphCommits, - selectedCommitHashPtr, - ) - getGraphLine = func(idx int) string { - if idx >= graphOffset { - return graphLines[idx-graphOffset] - } - return "" - } - } - } else { - getGraphLine = func(int) string { return "" } - } - - // Determine the hashes of the local branches for which we want to show a - // branch marker in the commits list. We only want to do this for branches - // that are not the current branch, and not any of the main branches. The - // goal is to visualize stacks of local branches, so anything that doesn't - // contribute to a branch stack shouldn't show a marker. - // - // If there are other branches pointing to the current head commit, we only - // want to show the marker if the rebase.updateRefs config is on. - branchHeadsToVisualize := set.NewFromSlice(lo.FilterMap(branches, - func(b *models.Branch, index int) (string, bool) { - return b.CommitHash, - // Don't consider branches that don't have a commit hash. As far - // as I can see, this happens for a detached head, so filter - // these out - b.CommitHash != "" && - // Don't show a marker for the current branch - b.Name != currentBranchName && - // Don't show a marker for main branches - !lo.Contains(common.UserConfig().Git.MainBranches, b.Name) && - // Don't show a marker for the head commit unless the - // rebase.updateRefs config is on - (hasRebaseUpdateRefsConfig || b.CommitHash != commits[0].Hash()) - })) - - lines := make([][]string, 0, len(filteredCommits)) - var bisectStatus BisectStatus - willBeRebased := markedBaseCommit == "" - for i, commit := range filteredCommits { - unfilteredIdx := i + startIdx - bisectStatus = getBisectStatus(unfilteredIdx, commit.Hash(), bisectInfo, bisectBounds) - isMarkedBaseCommit := commit.Hash() != "" && commit.Hash() == markedBaseCommit - if isMarkedBaseCommit { - willBeRebased = true - } - lines = append(lines, displayCommit( - common, - commit, - branchHeadsToVisualize, - hasRebaseUpdateRefsConfig, - cherryPickedCommitHashSet, - isMarkedBaseCommit, - willBeRebased, - diffName, - timeFormat, - shortTimeFormat, - now, - parseEmoji, - getGraphLine(unfilteredIdx), - fullDescription, - bisectStatus, - bisectInfo, - )) - } - return lines -} - -func getbisectBounds(commits []*models.Commit, bisectInfo *git_commands.BisectInfo) *bisectBounds { - if !bisectInfo.Bisecting() { - return nil - } - - bisectBounds := &bisectBounds{} - - for i, commit := range commits { - if commit.Hash() == bisectInfo.GetNewHash() { - bisectBounds.newIndex = i - } - - status, ok := bisectInfo.Status(commit.Hash()) - if ok && status == git_commands.BisectStatusOld { - bisectBounds.oldIndex = i - return bisectBounds - } - } - - // shouldn't land here - return nil -} - -// precondition: slice is not empty -func indexOfFirstNonTODOCommit(commits []*models.Commit) int { - for i, commit := range commits { - if !commit.IsTODO() { - return i - } - } - - // shouldn't land here - return 0 -} - -func loadPipesets(commits []*models.Commit) [][]graph.Pipe { - // given that our cache key is a commit hash and a commit count, it's very important that we don't actually try to render pipes - // when dealing with things like filtered commits. - cacheKey := pipeSetCacheKey{ - commitHash: commits[0].Hash(), - commitCount: len(commits), - divergence: commits[0].Divergence, - } - - pipeSets, ok := pipeSetCache[cacheKey] - if !ok { - // pipe sets are unique to a commit head. and a commit count. Sometimes we haven't loaded everything for that. - // so let's just cache it based on that. - getStyle := func(commit *models.Commit) *style.TextStyle { - return authors.AuthorStyle(commit.AuthorName) - } - pipeSets = graph.GetPipeSets(commits, getStyle) - pipeSetCache[cacheKey] = pipeSets - } - - return pipeSets -} - -// similar to the git_commands.BisectStatus but more gui-focused -type BisectStatus int - -const ( - BisectStatusNone BisectStatus = iota - BisectStatusOld - BisectStatusNew - BisectStatusSkipped - // adding candidate here which isn't present in the commands package because - // we need to actually go through the commits to get this info - BisectStatusCandidate - // also adding this - BisectStatusCurrent -) - -func getBisectStatus(index int, commitHash string, bisectInfo *git_commands.BisectInfo, bisectBounds *bisectBounds) BisectStatus { - if !bisectInfo.Started() { - return BisectStatusNone - } - - if bisectInfo.GetCurrentHash() == commitHash { - return BisectStatusCurrent - } - - status, ok := bisectInfo.Status(commitHash) - if ok { - switch status { - case git_commands.BisectStatusNew: - return BisectStatusNew - case git_commands.BisectStatusOld: - return BisectStatusOld - case git_commands.BisectStatusSkipped: - return BisectStatusSkipped - } - } else { - if bisectBounds != nil && index >= bisectBounds.newIndex && index <= bisectBounds.oldIndex { - return BisectStatusCandidate - } - return BisectStatusNone - } - - // should never land here - return BisectStatusNone -} - -func getBisectStatusText(bisectStatus BisectStatus, bisectInfo *git_commands.BisectInfo) string { - if bisectStatus == BisectStatusNone { - return "" - } - - style := getBisectStatusColor(bisectStatus) - - switch bisectStatus { - case BisectStatusNew: - return style.Sprintf("<-- " + bisectInfo.NewTerm()) - case BisectStatusOld: - return style.Sprintf("<-- " + bisectInfo.OldTerm()) - case BisectStatusCurrent: - // TODO: i18n - return style.Sprintf("<-- current") - case BisectStatusSkipped: - return style.Sprintf("<-- skipped") - case BisectStatusCandidate: - return style.Sprintf("?") - case BisectStatusNone: - return "" - } - - return "" -} - -func displayCommit( - common *common.Common, - commit *models.Commit, - branchHeadsToVisualize *set.Set[string], - hasRebaseUpdateRefsConfig bool, - cherryPickedCommitHashSet *set.Set[string], - isMarkedBaseCommit bool, - willBeRebased bool, - diffName string, - timeFormat string, - shortTimeFormat string, - now time.Time, - parseEmoji bool, - graphLine string, - fullDescription bool, - bisectStatus BisectStatus, - bisectInfo *git_commands.BisectInfo, -) []string { - bisectString := getBisectStatusText(bisectStatus, bisectInfo) - - hashString := "" - hashColor := getHashColor(commit, diffName, cherryPickedCommitHashSet, bisectStatus, bisectInfo) - hashLength := common.UserConfig().Gui.CommitHashLength - if hashLength >= len(commit.Hash()) { - hashString = hashColor.Sprint(commit.Hash()) - } else if hashLength > 0 { - hashString = hashColor.Sprint(commit.Hash()[:hashLength]) - } else if !icons.IsIconEnabled() { // hashLength <= 0 - hashString = hashColor.Sprint("*") - } - - divergenceString := "" - if commit.Divergence != models.DivergenceNone { - divergenceString = hashColor.Sprint(lo.Ternary(commit.Divergence == models.DivergenceLeft, "↑", "↓")) - } else if icons.IsIconEnabled() { - divergenceString = hashColor.Sprint(icons.IconForCommit(commit)) - } - - descriptionString := "" - if fullDescription { - descriptionString = style.FgBlue.Sprint( - utils.UnixToDateSmart(now, commit.UnixTimestamp, timeFormat, shortTimeFormat), - ) - } - - actionString := "" - if commit.Action != models.ActionNone { - actionString = actionColorMap(commit.Action, commit.Status).Sprint(commit.Action.String()) - } - - tagString := "" - if fullDescription { - if commit.ExtraInfo != "" { - tagString = style.FgMagenta.SetBold().Sprint(commit.ExtraInfo) + " " - } - } else { - if len(commit.Tags) > 0 { - tagString = theme.DiffTerminalColor.SetBold().Sprint(strings.Join(commit.Tags, " ")) + " " - } - - if branchHeadsToVisualize.Includes(commit.Hash()) && - // Don't show branch head on commits that are already merged to a main branch - commit.Status != models.StatusMerged && - // Don't show branch head on a "pick" todo if the rebase.updateRefs config is on - !(commit.IsTODO() && hasRebaseUpdateRefsConfig) { - tagString = style.FgCyan.SetBold().Sprint( - lo.Ternary(icons.IsIconEnabled(), icons.BRANCH_ICON, "*") + " " + tagString) - } - } - - name := commit.Name - if commit.Action == todo.UpdateRef { - name = strings.TrimPrefix(name, "refs/heads/") - } - if parseEmoji { - name = emoji.Sprint(name) - } - - mark := "" - if commit.Status == models.StatusConflicted { - youAreHere := style.FgRed.Sprintf("<-- %s ---", common.Tr.ConflictLabel) - mark = fmt.Sprintf("%s ", youAreHere) - } else if isMarkedBaseCommit { - rebaseFromHere := style.FgYellow.Sprint(common.Tr.MarkedCommitMarker) - mark = fmt.Sprintf("%s ", rebaseFromHere) - } else if !willBeRebased { - willBeRebased := style.FgYellow.Sprint("✓") - mark = fmt.Sprintf("%s ", willBeRebased) - } - - authorLength := common.UserConfig().Gui.CommitAuthorShortLength - if fullDescription { - authorLength = common.UserConfig().Gui.CommitAuthorLongLength - } - author := authors.AuthorWithLength(commit.AuthorName, authorLength) - - cols := make([]string, 0, 7) - cols = append( - cols, - divergenceString, - hashString, - bisectString, - descriptionString, - actionString, - author, - graphLine+mark+tagString+theme.DefaultTextColor.Sprint(name), - ) - - return cols -} - -func getBisectStatusColor(status BisectStatus) style.TextStyle { - switch status { - case BisectStatusNone: - return style.FgBlack - case BisectStatusNew: - return style.FgRed - case BisectStatusOld: - return style.FgGreen - case BisectStatusSkipped: - return style.FgYellow - case BisectStatusCurrent: - return style.FgMagenta - case BisectStatusCandidate: - return style.FgBlue - } - - // shouldn't land here - return style.FgWhite -} - -func getHashColor( - commit *models.Commit, - diffName string, - cherryPickedCommitHashSet *set.Set[string], - bisectStatus BisectStatus, - bisectInfo *git_commands.BisectInfo, -) style.TextStyle { - if bisectInfo.Started() { - return getBisectStatusColor(bisectStatus) - } - - diffed := commit.Hash() != "" && commit.Hash() == diffName - hashColor := theme.DefaultTextColor - switch commit.Status { - case models.StatusUnpushed: - hashColor = style.FgRed - case models.StatusPushed: - hashColor = style.FgYellow - case models.StatusMerged: - hashColor = style.FgGreen - case models.StatusRebasing, models.StatusCherryPickingOrReverting, models.StatusConflicted: - hashColor = style.FgBlue - case models.StatusReflog: - hashColor = style.FgBlue - default: - } - - if diffed { - hashColor = theme.DiffTerminalColor - } else if cherryPickedCommitHashSet.Includes(commit.Hash()) { - hashColor = theme.CherryPickedCommitTextStyle - } else if commit.Divergence == models.DivergenceRight && commit.Status != models.StatusMerged { - hashColor = style.FgBlue - } - - return hashColor -} - -func actionColorMap(action todo.TodoCommand, status models.CommitStatus) style.TextStyle { - if status == models.StatusConflicted { - return style.FgRed - } - - switch action { - case todo.Pick: - return style.FgCyan - case todo.Drop: - return style.FgRed - case todo.Edit: - return style.FgGreen - case todo.Fixup: - return style.FgMagenta - default: - return style.FgYellow - } -} diff --git a/pkg/gui/presentation/commits_test.go b/pkg/gui/presentation/commits_test.go deleted file mode 100644 index 1536d420a8c..00000000000 --- a/pkg/gui/presentation/commits_test.go +++ /dev/null @@ -1,582 +0,0 @@ -package presentation - -import ( - "os" - "strings" - "testing" - "time" - - "github.com/gookit/color" - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/stefanhaller/git-todo-parser/todo" - "github.com/stretchr/testify/assert" - "github.com/xo/terminfo" -) - -func formatExpected(expected string) string { - return strings.TrimSpace(strings.ReplaceAll(expected, "\t", "")) -} - -func TestGetCommitListDisplayStrings(t *testing.T) { - scenarios := []struct { - testName string - commitOpts []models.NewCommitOpts - branches []*models.Branch - currentBranchName string - hasUpdateRefConfig bool - fullDescription bool - cherryPickedCommitHashSet *set.Set[string] - markedBaseCommit string - diffName string - timeFormat string - shortTimeFormat string - now time.Time - parseEmoji bool - selectedCommitHashPtr *string - startIdx int - endIdx int - showGraph bool - bisectInfo *git_commands.BisectInfo - expected string - focus bool - }{ - { - testName: "no commits", - commitOpts: []models.NewCommitOpts{}, - startIdx: 0, - endIdx: 1, - showGraph: false, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: "", - }, - { - testName: "some commits", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1"}, - {Name: "commit2", Hash: "hash2"}, - }, - startIdx: 0, - endIdx: 2, - showGraph: false, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash1 commit1 - hash2 commit2 - `), - }, - { - testName: "commit with tags", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1", Tags: []string{"tag1", "tag2"}}, - {Name: "commit2", Hash: "hash2"}, - }, - startIdx: 0, - endIdx: 2, - showGraph: false, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash1 tag1 tag2 commit1 - hash2 commit2 - `), - }, - { - testName: "show local branch head, except the current branch, main branches, or merged branches", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1"}, - {Name: "commit2", Hash: "hash2"}, - {Name: "commit3", Hash: "hash3"}, - {Name: "commit4", Hash: "hash4", Status: models.StatusMerged}, - }, - branches: []*models.Branch{ - {Name: "current-branch", CommitHash: "hash1", Head: true}, - {Name: "other-branch", CommitHash: "hash2", Head: false}, - {Name: "master", CommitHash: "hash3", Head: false}, - {Name: "old-branch", CommitHash: "hash4", Head: false}, - }, - currentBranchName: "current-branch", - hasUpdateRefConfig: true, - startIdx: 0, - endIdx: 4, - showGraph: false, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash1 commit1 - hash2 * commit2 - hash3 commit3 - hash4 commit4 - `), - }, - { - testName: "show local branch head for head commit if updateRefs is on", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1"}, - {Name: "commit2", Hash: "hash2"}, - }, - branches: []*models.Branch{ - {Name: "current-branch", CommitHash: "hash1", Head: true}, - {Name: "other-branch", CommitHash: "hash1", Head: false}, - }, - currentBranchName: "current-branch", - hasUpdateRefConfig: true, - startIdx: 0, - endIdx: 2, - showGraph: false, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash1 * commit1 - hash2 commit2 - `), - }, - { - testName: "don't show local branch head for head commit if updateRefs is off", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1"}, - {Name: "commit2", Hash: "hash2"}, - }, - branches: []*models.Branch{ - {Name: "current-branch", CommitHash: "hash1", Head: true}, - {Name: "other-branch", CommitHash: "hash1", Head: false}, - }, - currentBranchName: "current-branch", - hasUpdateRefConfig: false, - startIdx: 0, - endIdx: 2, - showGraph: false, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash1 commit1 - hash2 commit2 - `), - }, - { - testName: "show local branch head and tag if both exist", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1"}, - {Name: "commit2", Hash: "hash2", Tags: []string{"some-tag"}}, - {Name: "commit3", Hash: "hash3"}, - }, - branches: []*models.Branch{ - {Name: "some-branch", CommitHash: "hash2"}, - }, - startIdx: 0, - endIdx: 3, - showGraph: false, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash1 commit1 - hash2 * some-tag commit2 - hash3 commit3 - `), - }, - { - testName: "showing graph", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1", Parents: []string{"hash2", "hash3"}}, - {Name: "commit2", Hash: "hash2", Parents: []string{"hash3"}}, - {Name: "commit3", Hash: "hash3", Parents: []string{"hash4"}}, - {Name: "commit4", Hash: "hash4", Parents: []string{"hash5"}}, - {Name: "commit5", Hash: "hash5", Parents: []string{"hash7"}}, - }, - startIdx: 0, - endIdx: 5, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash1 ⏣─╮ commit1 - hash2 ◯ │ commit2 - hash3 ◯─╯ commit3 - hash4 ◯ commit4 - hash5 ◯ commit5 - `), - }, - { - testName: "showing graph, including rebase commits", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1", Parents: []string{"hash2", "hash3"}, Action: todo.Pick}, - {Name: "commit2", Hash: "hash2", Parents: []string{"hash3"}, Action: todo.Pick}, - {Name: "commit3", Hash: "hash3", Parents: []string{"hash4"}}, - {Name: "commit4", Hash: "hash4", Parents: []string{"hash5"}}, - {Name: "commit5", Hash: "hash5", Parents: []string{"hash7"}}, - }, - startIdx: 0, - endIdx: 5, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash1 pick commit1 - hash2 pick commit2 - hash3 ◯ commit3 - hash4 ◯ commit4 - hash5 ◯ commit5 - `), - }, - { - testName: "showing graph, including rebase commits, with offset", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1", Parents: []string{"hash2", "hash3"}, Action: todo.Pick}, - {Name: "commit2", Hash: "hash2", Parents: []string{"hash3"}, Action: todo.Pick}, - {Name: "commit3", Hash: "hash3", Parents: []string{"hash4"}}, - {Name: "commit4", Hash: "hash4", Parents: []string{"hash5"}}, - {Name: "commit5", Hash: "hash5", Parents: []string{"hash7"}}, - }, - startIdx: 1, - endIdx: 5, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash2 pick commit2 - hash3 ◯ commit3 - hash4 ◯ commit4 - hash5 ◯ commit5 - `), - }, - { - testName: "startIdx is past TODO commits", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1", Parents: []string{"hash2", "hash3"}, Action: todo.Pick}, - {Name: "commit2", Hash: "hash2", Parents: []string{"hash3"}, Action: todo.Pick}, - {Name: "commit3", Hash: "hash3", Parents: []string{"hash4"}}, - {Name: "commit4", Hash: "hash4", Parents: []string{"hash5"}}, - {Name: "commit5", Hash: "hash5", Parents: []string{"hash7"}}, - }, - startIdx: 3, - endIdx: 5, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash4 ◯ commit4 - hash5 ◯ commit5 - `), - }, - { - testName: "only showing TODO commits", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1", Parents: []string{"hash2", "hash3"}, Action: todo.Pick}, - {Name: "commit2", Hash: "hash2", Parents: []string{"hash3"}, Action: todo.Pick}, - {Name: "commit3", Hash: "hash3", Parents: []string{"hash4"}}, - {Name: "commit4", Hash: "hash4", Parents: []string{"hash5"}}, - {Name: "commit5", Hash: "hash5", Parents: []string{"hash7"}}, - }, - startIdx: 0, - endIdx: 2, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash1 pick commit1 - hash2 pick commit2 - `), - }, - { - testName: "no TODO commits, towards bottom", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1", Parents: []string{"hash2", "hash3"}}, - {Name: "commit2", Hash: "hash2", Parents: []string{"hash3"}}, - {Name: "commit3", Hash: "hash3", Parents: []string{"hash4"}}, - {Name: "commit4", Hash: "hash4", Parents: []string{"hash5"}}, - {Name: "commit5", Hash: "hash5", Parents: []string{"hash7"}}, - }, - startIdx: 4, - endIdx: 5, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash5 ◯ commit5 - `), - }, - { - testName: "only TODO commits except last", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1", Parents: []string{"hash2", "hash3"}, Action: todo.Pick}, - {Name: "commit2", Hash: "hash2", Parents: []string{"hash3"}, Action: todo.Pick}, - {Name: "commit3", Hash: "hash3", Parents: []string{"hash4"}, Action: todo.Pick}, - {Name: "commit4", Hash: "hash4", Parents: []string{"hash5"}, Action: todo.Pick}, - {Name: "commit5", Hash: "hash5", Parents: []string{"hash7"}}, - }, - startIdx: 0, - endIdx: 2, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - hash1 pick commit1 - hash2 pick commit2 - `), - }, - { - testName: "graph in divergence view - all commits visible", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight}, - {Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight}, - {Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight}, - {Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft}, - {Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft}, - {Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft}, - }, - startIdx: 0, - endIdx: 8, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - ↓ hash1r ◯ commit1 - ↓ hash2r ⏣─╮ commit2 - ↓ hash3r ◯ │ commit3 - ↑ hash1l ◯ commit1 - ↑ hash2l ⏣─╮ commit2 - ↑ hash3l ◯ │ commit3 - ↑ hash4l ◯─╯ commit4 - ↑ hash5l ◯ commit5 - `), - }, - { - testName: "graph in divergence view - not all remote commits visible", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight}, - {Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight}, - {Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight}, - {Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft}, - {Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft}, - {Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft}, - }, - startIdx: 2, - endIdx: 8, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - ↓ hash3r ◯ │ commit3 - ↑ hash1l ◯ commit1 - ↑ hash2l ⏣─╮ commit2 - ↑ hash3l ◯ │ commit3 - ↑ hash4l ◯─╯ commit4 - ↑ hash5l ◯ commit5 - `), - }, - { - testName: "graph in divergence view - not all local commits", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight}, - {Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight}, - {Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight}, - {Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft}, - {Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft}, - {Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft}, - }, - startIdx: 0, - endIdx: 5, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - ↓ hash1r ◯ commit1 - ↓ hash2r ⏣─╮ commit2 - ↓ hash3r ◯ │ commit3 - ↑ hash1l ◯ commit1 - ↑ hash2l ⏣─╮ commit2 - `), - }, - { - testName: "graph in divergence view - no remote commits visible", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight}, - {Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight}, - {Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight}, - {Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft}, - {Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft}, - {Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft}, - }, - startIdx: 4, - endIdx: 8, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - ↑ hash2l ⏣─╮ commit2 - ↑ hash3l ◯ │ commit3 - ↑ hash4l ◯─╯ commit4 - ↑ hash5l ◯ commit5 - `), - }, - { - testName: "graph in divergence view - no local commits visible", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight}, - {Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight}, - {Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight}, - {Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft}, - {Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft}, - {Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft}, - }, - startIdx: 0, - endIdx: 2, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - ↓ hash1r ◯ commit1 - ↓ hash2r ⏣─╮ commit2 - `), - }, - { - testName: "graph in divergence view - no remote commits present", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft}, - {Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft}, - {Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft}, - {Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft}, - }, - startIdx: 0, - endIdx: 5, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - ↑ hash1l ◯ commit1 - ↑ hash2l ⏣─╮ commit2 - ↑ hash3l ◯ │ commit3 - ↑ hash4l ◯─╯ commit4 - ↑ hash5l ◯ commit5 - `), - }, - { - testName: "graph in divergence view - no local commits present", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight}, - {Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight}, - {Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight}, - }, - startIdx: 0, - endIdx: 3, - showGraph: true, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - expected: formatExpected(` - ↓ hash1r ◯ commit1 - ↓ hash2r ⏣─╮ commit2 - ↓ hash3r ◯ │ commit3 - `), - }, - { - testName: "custom time format", - commitOpts: []models.NewCommitOpts{ - {Name: "commit1", Hash: "hash1", UnixTimestamp: 1577844184, AuthorName: "Jesse Duffield"}, - {Name: "commit2", Hash: "hash2", UnixTimestamp: 1576844184, AuthorName: "Jesse Duffield"}, - }, - fullDescription: true, - timeFormat: "2006-01-02", - shortTimeFormat: "3:04PM", - startIdx: 0, - endIdx: 2, - showGraph: false, - bisectInfo: git_commands.NewNullBisectInfo(), - cherryPickedCommitHashSet: set.New[string](), - now: time.Date(2020, 1, 1, 5, 3, 4, 0, time.UTC), - expected: formatExpected(` - hash1 2:03AM Jesse Duffield commit1 - hash2 2019-12-20 Jesse Duffield commit2 - `), - }, - } - - oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelNone) - defer color.ForceSetColorLevel(oldColorLevel) - - os.Setenv("TZ", "UTC") - - focusing := false - for _, scenario := range scenarios { - if scenario.focus { - focusing = true - } - } - - common := common.NewDummyCommon() - - for _, s := range scenarios { - if !focusing || s.focus { - t.Run(s.testName, func(t *testing.T) { - hashPool := &utils.StringPool{} - - commits := lo.Map(s.commitOpts, - func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) }) - - result := GetCommitListDisplayStrings( - common, - commits, - s.branches, - s.currentBranchName, - s.hasUpdateRefConfig, - s.fullDescription, - s.cherryPickedCommitHashSet, - s.diffName, - s.markedBaseCommit, - s.timeFormat, - s.shortTimeFormat, - s.now, - s.parseEmoji, - s.selectedCommitHashPtr, - s.startIdx, - s.endIdx, - s.showGraph, - s.bisectInfo, - ) - - renderedLines, _ := utils.RenderDisplayStrings(result, nil) - renderedResult := strings.Join(renderedLines, "\n") - t.Logf("\n%s", renderedResult) - - assert.EqualValues(t, s.expected, renderedResult) - }) - } - } -} diff --git a/pkg/gui/presentation/files.go b/pkg/gui/presentation/files.go deleted file mode 100644 index cc0a9388972..00000000000 --- a/pkg/gui/presentation/files.go +++ /dev/null @@ -1,348 +0,0 @@ -package presentation - -import ( - "strings" - - "github.com/gookit/color" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/filetree" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -const ( - EXPANDED_ARROW = "▼" - COLLAPSED_ARROW = "▶" -) - -func RenderFileTree( - tree filetree.IFileTree, - submoduleConfigs []*models.SubmoduleConfig, - showFileIcons bool, - showNumstat bool, - customIconsConfig *config.CustomIconsConfig, - showRootItem bool, -) []string { - collapsedPaths := tree.CollapsedPaths() - return renderAux(tree.GetRoot().Raw(), collapsedPaths, -1, -1, func(node *filetree.Node[models.File], treeDepth int, visualDepth int, isCollapsed bool) string { - fileNode := filetree.NewFileNode(node) - - return getFileLine(isCollapsed, fileNode.GetHasUnstagedChanges(), fileNode.GetHasStagedChanges(), treeDepth, visualDepth, showNumstat, showFileIcons, submoduleConfigs, node, customIconsConfig, showRootItem) - }) -} - -func RenderCommitFileTree( - tree *filetree.CommitFileTreeViewModel, - patchBuilder *patch.PatchBuilder, - showFileIcons bool, - customIconsConfig *config.CustomIconsConfig, -) []string { - collapsedPaths := tree.CollapsedPaths() - return renderAux(tree.GetRoot().Raw(), collapsedPaths, -1, -1, func(node *filetree.Node[models.CommitFile], treeDepth int, visualDepth int, isCollapsed bool) string { - status := commitFilePatchStatus(node, tree, patchBuilder) - - return getCommitFileLine(isCollapsed, treeDepth, visualDepth, node, status, showFileIcons, customIconsConfig) - }) -} - -// Returns the status of a commit file in terms of its inclusion in the custom patch -func commitFilePatchStatus(node *filetree.Node[models.CommitFile], tree *filetree.CommitFileTreeViewModel, patchBuilder *patch.PatchBuilder) patch.PatchStatus { - // This is a little convoluted because we're dealing with either a leaf or a non-leaf. - // But this code actually applies to both. If it's a leaf, the status will just - // be whatever status it is, but if it's a non-leaf it will determine its status - // based on the leaves of that subtree - if node.EveryFile(func(file *models.CommitFile) bool { - return patchBuilder.GetFileStatus(file.Path, tree.GetRef().RefName()) == patch.WHOLE - }) { - return patch.WHOLE - } else if node.EveryFile(func(file *models.CommitFile) bool { - return patchBuilder.GetFileStatus(file.Path, tree.GetRef().RefName()) == patch.UNSELECTED - }) { - return patch.UNSELECTED - } - return patch.PART -} - -func renderAux[T any]( - node *filetree.Node[T], - collapsedPaths *filetree.CollapsedPaths, - // treeDepth is the depth of the node in the actual file tree. This is different to - // visualDepth because some directory nodes are compressed e.g. 'pkg/gui/blah' takes - // up two tree depths, but one visual depth. We need to track these separately, - // because indentation relies on visual depth, whereas file path truncation - // relies on tree depth. - treeDepth int, - visualDepth int, - renderLine func(*filetree.Node[T], int, int, bool) string, -) []string { - if node == nil { - return []string{} - } - - isRoot := treeDepth == -1 - - if node.IsFile() { - if isRoot { - return []string{} - } - return []string{renderLine(node, treeDepth, visualDepth, false)} - } - - arr := []string{} - if !isRoot { - isCollapsed := collapsedPaths.IsCollapsed(node.GetInternalPath()) - arr = append(arr, renderLine(node, treeDepth, visualDepth, isCollapsed)) - } - - if collapsedPaths.IsCollapsed(node.GetInternalPath()) { - return arr - } - - for _, child := range node.Children { - arr = append(arr, renderAux(child, collapsedPaths, treeDepth+1+node.CompressionLevel, visualDepth+1, renderLine)...) - } - - return arr -} - -func getFileLine( - isCollapsed bool, - hasUnstagedChanges bool, - hasStagedChanges bool, - treeDepth int, - visualDepth int, - showNumstat, - showFileIcons bool, - submoduleConfigs []*models.SubmoduleConfig, - node *filetree.Node[models.File], - customIconsConfig *config.CustomIconsConfig, - showRootItem bool, -) string { - name := fileNameAtDepth(node, treeDepth, showRootItem) - output := "" - - var nameColor style.TextStyle - - file := node.File - - indentation := strings.Repeat(" ", visualDepth) - - if hasStagedChanges && !hasUnstagedChanges { - nameColor = style.FgGreen - } else if hasStagedChanges { - nameColor = style.FgYellow - } else { - nameColor = theme.DefaultTextColor - } - - if file == nil { - output += indentation + "" - arrow := EXPANDED_ARROW - if isCollapsed { - arrow = COLLAPSED_ARROW - } - - arrowStyle := nameColor - - output += arrowStyle.Sprint(arrow) + " " - } else { - // Sprinting the space at the end in the specific style is for the sake of - // when a reverse style is used in the theme, which looks ugly if you just - // use the default style - output += indentation + formatFileStatus(file, nameColor) + nameColor.Sprint(" ") - } - - isSubmodule := file != nil && file.IsSubmodule(submoduleConfigs) - isLinkedWorktree := file != nil && file.IsWorktree - isDirectory := file == nil - - if showFileIcons { - icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory, customIconsConfig) - paint := color.HEX(icon.Color, false) - output += paint.Sprint(icon.Icon) + nameColor.Sprint(" ") - } - - output += nameColor.Sprint(utils.EscapeSpecialChars(name)) - - if isSubmodule { - output += theme.DefaultTextColor.Sprint(" (submodule)") - } - - if file != nil && showNumstat { - if lineChanges := formatLineChanges(file.LinesAdded, file.LinesDeleted); lineChanges != "" { - output += " " + lineChanges - } - } - - return output -} - -func formatFileStatus(file *models.File, restColor style.TextStyle) string { - firstChar := file.ShortStatus[0:1] - firstCharCl := style.FgGreen - switch firstChar { - case "?": - firstCharCl = theme.UnstagedChangesColor - case " ": - firstCharCl = restColor - } - - secondChar := file.ShortStatus[1:2] - secondCharCl := theme.UnstagedChangesColor - if secondChar == " " { - secondCharCl = restColor - } - - return firstCharCl.Sprint(firstChar) + secondCharCl.Sprint(secondChar) -} - -func formatLineChanges(linesAdded, linesDeleted int) string { - output := "" - - if linesAdded != 0 { - output += style.FgGreen.Sprintf("+%d", linesAdded) - } - - if linesDeleted != 0 { - if output != "" { - output += " " - } - output += style.FgRed.Sprintf("-%d", linesDeleted) - } - - return output -} - -func getCommitFileLine( - isCollapsed bool, - treeDepth int, - visualDepth int, - node *filetree.Node[models.CommitFile], - status patch.PatchStatus, - showFileIcons bool, - customIconsConfig *config.CustomIconsConfig, -) string { - indentation := strings.Repeat(" ", visualDepth) - name := commitFileNameAtDepth(node, treeDepth) - commitFile := node.File - output := indentation - - isDirectory := commitFile == nil - - nameColor := theme.DefaultTextColor - - switch status { - case patch.WHOLE: - nameColor = style.FgGreen - case patch.PART: - nameColor = style.FgYellow - case patch.UNSELECTED: - nameColor = theme.DefaultTextColor - } - - if isDirectory { - arrow := EXPANDED_ARROW - if isCollapsed { - arrow = COLLAPSED_ARROW - } - - output += nameColor.Sprint(arrow) + " " - } else { - var symbol string - symbolStyle := nameColor - - switch status { - case patch.WHOLE: - symbol = "●" - case patch.PART: - symbol = "◐" - case patch.UNSELECTED: - symbol = commitFile.ChangeStatus - symbolStyle = getColorForChangeStatus(symbol) - } - - output += symbolStyle.Sprint(symbol) + " " - } - - name = utils.EscapeSpecialChars(name) - isSubmodule := false - isLinkedWorktree := false - - if showFileIcons { - icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory, customIconsConfig) - paint := color.HEX(icon.Color, false) - output += paint.Sprint(icon.Icon) + " " - } - - output += nameColor.Sprint(name) - return output -} - -func getColorForChangeStatus(changeStatus string) style.TextStyle { - switch changeStatus { - case "A": - return style.FgGreen - case "M", "R": - return style.FgYellow - case "D": - return theme.UnstagedChangesColor - case "C": - return style.FgCyan - case "T": - return style.FgMagenta - default: - return theme.DefaultTextColor - } -} - -func fileNameAtDepth(node *filetree.Node[models.File], depth int, showRootItem bool) string { - splitName := split(node.GetInternalPath()) - if depth == 0 && splitName[0] == "." { - if len(splitName) == 1 { - return "/" - } - depth = 1 - } - name := join(splitName[depth:]) - - if node.File != nil && node.File.IsRename() { - splitPrevName := filetree.SplitFileTreePath(node.File.PreviousPath, showRootItem) - - prevName := node.File.PreviousPath - // if the file has just been renamed inside the same directory, we can shave off - // the prefix for the previous path too. Otherwise we'll keep it unchanged - sameParentDir := len(splitName) == len(splitPrevName) && join(splitName[0:depth]) == join(splitPrevName[0:depth]) - if sameParentDir { - prevName = join(splitPrevName[depth:]) - } - - return prevName + " → " + name - } - - return name -} - -func commitFileNameAtDepth(node *filetree.Node[models.CommitFile], depth int) string { - splitName := split(node.GetInternalPath()) - if depth == 0 && splitName[0] == "." { - if len(splitName) == 1 { - return "/" - } - depth = 1 - } - name := join(splitName[depth:]) - - return name -} - -func split(str string) []string { - return strings.Split(str, "/") -} - -func join(strs []string) string { - return strings.Join(strs, "/") -} diff --git a/pkg/gui/presentation/files_test.go b/pkg/gui/presentation/files_test.go deleted file mode 100644 index a5b01c1561e..00000000000 --- a/pkg/gui/presentation/files_test.go +++ /dev/null @@ -1,231 +0,0 @@ -package presentation - -import ( - "strings" - "testing" - - "github.com/gookit/color" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/filetree" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/stretchr/testify/assert" - "github.com/xo/terminfo" -) - -func toStringSlice(str string) []string { - return strings.Split(strings.TrimSpace(str), "\n") -} - -func TestRenderFileTree(t *testing.T) { - scenarios := []struct { - name string - root *filetree.FileNode - files []*models.File - collapsedPaths []string - showLineChanges bool - showRootItem bool - expected []string - }{ - { - name: "nil node", - files: nil, - expected: []string{}, - }, - { - name: "leaf node", - files: []*models.File{ - {Path: "test", ShortStatus: " M", HasStagedChanges: true}, - }, - showRootItem: true, - expected: []string{" M test"}, - }, - { - name: "numstat", - files: []*models.File{ - {Path: "test", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 1, LinesDeleted: 1}, - {Path: "test2", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 1}, - {Path: "test3", ShortStatus: " M", HasStagedChanges: true, LinesDeleted: 1}, - {Path: "test4", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 0, LinesDeleted: 0}, - }, - showLineChanges: true, - showRootItem: true, - expected: []string{ - "▼ /", - " M test +1 -1", - " M test2 +1", - " M test3 -1", - " M test4", - }, - }, - { - name: "big example", - files: []*models.File{ - {Path: "dir1/file2", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "dir1/file3", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "dir2/dir2/file3", ShortStatus: " M", HasStagedChanges: true}, - {Path: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - }, - showRootItem: true, - expected: toStringSlice( - ` -▼ / - ▶ dir1 - ▼ dir2 - ▼ dir2 - M file3 - M file4 - M file5 - M file1 -`, - ), - collapsedPaths: []string{"./dir1"}, - }, - { - name: "big example without root item", - files: []*models.File{ - {Path: "dir1/file2", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "dir1/file3", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "dir2/dir2/file3", ShortStatus: " M", HasStagedChanges: true}, - {Path: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, - {Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - }, - showRootItem: false, - expected: toStringSlice( - ` -▶ dir1 -▼ dir2 - ▼ dir2 - M file3 - M file4 - M file5 -M file1 -`, - ), - collapsedPaths: []string{"dir1"}, - }, - } - - oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelNone) - defer color.ForceSetColorLevel(oldColorLevel) - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - common := common.NewDummyCommon() - common.UserConfig().Gui.ShowRootItemInFileTree = s.showRootItem - viewModel := filetree.NewFileTree(func() []*models.File { return s.files }, common, true) - viewModel.SetTree() - for _, path := range s.collapsedPaths { - viewModel.ToggleCollapsed(path) - } - result := RenderFileTree(viewModel, nil, false, s.showLineChanges, &config.CustomIconsConfig{}, s.showRootItem) - assert.EqualValues(t, s.expected, result) - }) - } -} - -func TestRenderCommitFileTree(t *testing.T) { - scenarios := []struct { - name string - root *filetree.FileNode - files []*models.CommitFile - collapsedPaths []string - showRootItem bool - expected []string - }{ - { - name: "nil node", - files: nil, - expected: []string{}, - }, - { - name: "leaf node", - files: []*models.CommitFile{ - {Path: "test", ChangeStatus: "A"}, - }, - showRootItem: true, - expected: []string{"A test"}, - }, - { - name: "big example", - files: []*models.CommitFile{ - {Path: "dir1/file2", ChangeStatus: "M"}, - {Path: "dir1/file3", ChangeStatus: "A"}, - {Path: "dir2/dir2/file3", ChangeStatus: "D"}, - {Path: "dir2/dir2/file4", ChangeStatus: "M"}, - {Path: "dir2/file5", ChangeStatus: "M"}, - {Path: "file1", ChangeStatus: "M"}, - }, - showRootItem: true, - expected: toStringSlice( - ` -▼ / - ▶ dir1 - ▼ dir2 - ▼ dir2 - D file3 - M file4 - M file5 - M file1 -`, - ), - collapsedPaths: []string{"./dir1"}, - }, - { - name: "big example without root item", - files: []*models.CommitFile{ - {Path: "dir1/file2", ChangeStatus: "M"}, - {Path: "dir1/file3", ChangeStatus: "A"}, - {Path: "dir2/dir2/file3", ChangeStatus: "D"}, - {Path: "dir2/dir2/file4", ChangeStatus: "M"}, - {Path: "dir2/file5", ChangeStatus: "M"}, - {Path: "file1", ChangeStatus: "M"}, - }, - showRootItem: false, - expected: toStringSlice( - ` -▶ dir1 -▼ dir2 - ▼ dir2 - D file3 - M file4 - M file5 -M file1 -`, - ), - collapsedPaths: []string{"dir1"}, - }, - } - - oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelNone) - defer color.ForceSetColorLevel(oldColorLevel) - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - hashPool := &utils.StringPool{} - - common := common.NewDummyCommon() - common.UserConfig().Gui.ShowRootItemInFileTree = s.showRootItem - viewModel := filetree.NewCommitFileTreeViewModel(func() []*models.CommitFile { return s.files }, common, true) - viewModel.SetRef(models.NewCommit(hashPool, models.NewCommitOpts{Hash: "1234"})) - viewModel.SetTree() - for _, path := range s.collapsedPaths { - viewModel.ToggleCollapsed(path) - } - patchBuilder := patch.NewPatchBuilder( - utils.NewDummyLog(), - func(from string, to string, reverse bool, filename string, plain bool) (string, error) { - return "", nil - }, - ) - patchBuilder.Start("from", "to", false, false) - result := RenderCommitFileTree(viewModel, patchBuilder, false, &config.CustomIconsConfig{}) - assert.EqualValues(t, s.expected, result) - }) - } -} diff --git a/pkg/gui/presentation/graph/cell.go b/pkg/gui/presentation/graph/cell.go deleted file mode 100644 index be039a01848..00000000000 --- a/pkg/gui/presentation/graph/cell.go +++ /dev/null @@ -1,183 +0,0 @@ -package graph - -import ( - "io" - "sync" - - "github.com/gookit/color" - "github.com/jesseduffield/lazygit/pkg/gui/style" -) - -const ( - MergeSymbol = '⏣' - CommitSymbol = '◯' -) - -type cellType int - -const ( - CONNECTION cellType = iota - COMMIT - MERGE -) - -type Cell struct { - up, down, left, right bool - cellType cellType - rightStyle *style.TextStyle - style *style.TextStyle -} - -func (cell *Cell) render(writer io.StringWriter) { - up, down, left, right := cell.up, cell.down, cell.left, cell.right - - first, second := getBoxDrawingChars(up, down, left, right) - var adjustedFirst string - switch cell.cellType { - case CONNECTION: - adjustedFirst = first - case COMMIT: - adjustedFirst = string(CommitSymbol) - case MERGE: - adjustedFirst = string(MergeSymbol) - } - - var rightStyle *style.TextStyle - if cell.rightStyle == nil { - rightStyle = cell.style - } else { - rightStyle = cell.rightStyle - } - - // just doing this for the sake of easy testing, so that we don't need to - // assert on the style of a space given a space has no styling (assuming we - // stick to only using foreground styles) - var styledSecondChar string - if second == " " { - styledSecondChar = " " - } else { - styledSecondChar = cachedSprint(*rightStyle, second) - } - - _, _ = writer.WriteString(cachedSprint(*cell.style, adjustedFirst)) - _, _ = writer.WriteString(styledSecondChar) -} - -type rgbCacheKey struct { - *color.RGBStyle - str string -} - -var ( - rgbCache = make(map[rgbCacheKey]string) - rgbCacheMutex sync.RWMutex -) - -func cachedSprint(style style.TextStyle, str string) string { - switch v := style.Style.(type) { - case *color.RGBStyle: - rgbCacheMutex.RLock() - key := rgbCacheKey{v, str} - value, ok := rgbCache[key] - rgbCacheMutex.RUnlock() - if ok { - return value - } - value = style.Sprint(str) - rgbCacheMutex.Lock() - rgbCache[key] = value - rgbCacheMutex.Unlock() - return value - case color.Basic: - return style.Sprint(str) - case color.Style: - value := style.Sprint(str) - return value - } - return style.Sprint(str) -} - -func (cell *Cell) reset() { - cell.up = false - cell.down = false - cell.left = false - cell.right = false -} - -func (cell *Cell) setUp(style *style.TextStyle) *Cell { - cell.up = true - cell.style = style - return cell -} - -func (cell *Cell) setDown(style *style.TextStyle) *Cell { - cell.down = true - cell.style = style - return cell -} - -func (cell *Cell) setLeft(style *style.TextStyle) *Cell { - cell.left = true - if !cell.up && !cell.down { - // vertical trumps left - cell.style = style - } - return cell -} - -//nolint:unparam -func (cell *Cell) setRight(style *style.TextStyle, override bool) *Cell { - cell.right = true - if cell.rightStyle == nil || override { - cell.rightStyle = style - } - return cell -} - -func (cell *Cell) setStyle(style *style.TextStyle) *Cell { - cell.style = style - return cell -} - -func (cell *Cell) setType(cellType cellType) *Cell { - cell.cellType = cellType - return cell -} - -func getBoxDrawingChars(up, down, left, right bool) (string, string) { - if up && down && left && right { - return "│", "─" - } else if up && down && left && !right { - return "│", " " - } else if up && down && !left && right { - return "│", "─" - } else if up && down && !left && !right { - return "│", " " - } else if up && !down && left && right { - return "┴", "─" - } else if up && !down && left && !right { - return "╯", " " - } else if up && !down && !left && right { - return "╰", "─" - } else if up && !down && !left && !right { - return "╵", " " - } else if !up && down && left && right { - return "┬", "─" - } else if !up && down && left && !right { - return "╮", " " - } else if !up && down && !left && right { - return "╭", "─" - } else if !up && down && !left && !right { - return "╷", " " - } else if !up && !down && left && right { - return "─", "─" - } else if !up && !down && left && !right { - return "─", " " - } else if !up && !down && !left && right { - return "╶", "─" - } else if !up && !down && !left && !right { - return " ", " " - } - - panic("should not be possible") -} diff --git a/pkg/gui/presentation/graph/graph.go b/pkg/gui/presentation/graph/graph.go deleted file mode 100644 index 1639a62e62e..00000000000 --- a/pkg/gui/presentation/graph/graph.go +++ /dev/null @@ -1,387 +0,0 @@ -package graph - -import ( - "cmp" - "runtime" - "slices" - "strings" - "sync" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type PipeKind uint8 - -const ( - TERMINATES PipeKind = iota - STARTS - CONTINUES -) - -type Pipe struct { - fromHash *string - toHash *string - style *style.TextStyle - fromPos int16 - toPos int16 - kind PipeKind -} - -var ( - highlightStyle = style.FgLightWhite.SetBold() - EmptyTreeCommitHash = models.EmptyTreeCommitHash - StartCommitHash = "START" -) - -func (self Pipe) left() int16 { - return min(self.fromPos, self.toPos) -} - -func (self Pipe) right() int16 { - return max(self.fromPos, self.toPos) -} - -func RenderCommitGraph(commits []*models.Commit, selectedCommitHashPtr *string, getStyle func(c *models.Commit) *style.TextStyle) []string { - pipeSets := GetPipeSets(commits, getStyle) - if len(pipeSets) == 0 { - return nil - } - - lines := RenderAux(pipeSets, commits, selectedCommitHashPtr) - - return lines -} - -func GetPipeSets(commits []*models.Commit, getStyle func(c *models.Commit) *style.TextStyle) [][]Pipe { - if len(commits) == 0 { - return nil - } - - pipes := []Pipe{{fromPos: 0, toPos: 0, fromHash: &StartCommitHash, toHash: commits[0].HashPtr(), kind: STARTS, style: &style.FgDefault}} - - return lo.Map(commits, func(commit *models.Commit, _ int) []Pipe { - pipes = getNextPipes(pipes, commit, getStyle) - return pipes - }) -} - -func RenderAux(pipeSets [][]Pipe, commits []*models.Commit, selectedCommitHashPtr *string) []string { - maxProcs := runtime.GOMAXPROCS(0) - - // splitting up the rendering of the graph into multiple goroutines allows us to render the graph in parallel - chunks := make([][]string, maxProcs) - perProc := len(pipeSets) / maxProcs - - wg := sync.WaitGroup{} - wg.Add(maxProcs) - - for i := range maxProcs { - go func() { - from := i * perProc - to := (i + 1) * perProc - if i == maxProcs-1 { - to = len(pipeSets) - } - innerLines := make([]string, 0, to-from) - for j, pipeSet := range pipeSets[from:to] { - k := from + j - var prevCommit *models.Commit - if k > 0 { - prevCommit = commits[k-1] - } - line := renderPipeSet(pipeSet, selectedCommitHashPtr, prevCommit) - innerLines = append(innerLines, line) - } - chunks[i] = innerLines - wg.Done() - }() - } - - wg.Wait() - - return lo.Flatten(chunks) -} - -func getNextPipes(prevPipes []Pipe, commit *models.Commit, getStyle func(c *models.Commit) *style.TextStyle) []Pipe { - maxPos := int16(0) - for _, pipe := range prevPipes { - if pipe.toPos > maxPos { - maxPos = pipe.toPos - } - } - - // a pipe that terminated in the previous line has no bearing on the current line - // so we'll filter those out - currentPipes := lo.Filter(prevPipes, func(pipe Pipe, _ int) bool { - return pipe.kind != TERMINATES - }) - - newPipes := make([]Pipe, 0, len(currentPipes)+len(commit.ParentPtrs())) - // start by assuming that we've got a brand new commit not related to any preceding commit. - // (this only happens when we're doing `git log --all`). These will be tacked onto the far end. - pos := maxPos + 1 - for _, pipe := range currentPipes { - if equalHashes(pipe.toHash, commit.HashPtr()) { - // turns out this commit does have a descendant so we'll place it right under the first instance - pos = pipe.toPos - break - } - } - - // a taken spot is one where a current pipe is ending on - // Note: this set and similar ones below use int instead of int16 because - // that's much more efficient. We cast the int16 values we store in these - // sets to int on every access. - takenSpots := set.New[int]() - // a traversed spot is one where a current pipe is starting on, ending on, or passing through - traversedSpots := set.New[int]() - - var toHash *string - if commit.IsFirstCommit() { - toHash = &EmptyTreeCommitHash - } else { - toHash = commit.ParentPtrs()[0] - } - newPipes = append(newPipes, Pipe{ - fromPos: pos, - toPos: pos, - fromHash: commit.HashPtr(), - toHash: toHash, - kind: STARTS, - style: getStyle(commit), - }) - - traversedSpotsForContinuingPipes := set.New[int]() - for _, pipe := range currentPipes { - if !equalHashes(pipe.toHash, commit.HashPtr()) { - traversedSpotsForContinuingPipes.Add(int(pipe.toPos)) - } - } - - getNextAvailablePosForContinuingPipe := func() int16 { - i := int16(0) - for { - if !traversedSpots.Includes(int(i)) { - return i - } - i++ - } - } - - getNextAvailablePosForNewPipe := func() int16 { - i := int16(0) - for { - // a newly created pipe is not allowed to end on a spot that's already taken, - // nor on a spot that's been traversed by a continuing pipe. - if !takenSpots.Includes(int(i)) && !traversedSpotsForContinuingPipes.Includes(int(i)) { - return i - } - i++ - } - } - - traverse := func(from, to int16) { - left, right := from, to - if left > right { - left, right = right, left - } - for i := left; i <= right; i++ { - traversedSpots.Add(int(i)) - } - takenSpots.Add(int(to)) - } - - for _, pipe := range currentPipes { - if equalHashes(pipe.toHash, commit.HashPtr()) { - // terminating here - newPipes = append(newPipes, Pipe{ - fromPos: pipe.toPos, - toPos: pos, - fromHash: pipe.fromHash, - toHash: pipe.toHash, - kind: TERMINATES, - style: pipe.style, - }) - traverse(pipe.toPos, pos) - } else if pipe.toPos < pos { - // continuing here - availablePos := getNextAvailablePosForContinuingPipe() - newPipes = append(newPipes, Pipe{ - fromPos: pipe.toPos, - toPos: availablePos, - fromHash: pipe.fromHash, - toHash: pipe.toHash, - kind: CONTINUES, - style: pipe.style, - }) - traverse(pipe.toPos, availablePos) - } - } - - if commit.IsMerge() { - for _, parent := range commit.ParentPtrs()[1:] { - availablePos := getNextAvailablePosForNewPipe() - // need to act as if continuing pipes are going to continue on the same line. - newPipes = append(newPipes, Pipe{ - fromPos: pos, - toPos: availablePos, - fromHash: commit.HashPtr(), - toHash: parent, - kind: STARTS, - style: getStyle(commit), - }) - - takenSpots.Add(int(availablePos)) - } - } - - for _, pipe := range currentPipes { - if !equalHashes(pipe.toHash, commit.HashPtr()) && pipe.toPos > pos { - // continuing on, potentially moving left to fill in a blank spot - last := pipe.toPos - for i := pipe.toPos; i > pos; i-- { - if takenSpots.Includes(int(i)) || traversedSpots.Includes(int(i)) { - break - } - last = i - } - newPipes = append(newPipes, Pipe{ - fromPos: pipe.toPos, - toPos: last, - fromHash: pipe.fromHash, - toHash: pipe.toHash, - kind: CONTINUES, - style: pipe.style, - }) - traverse(pipe.toPos, last) - } - } - - // not efficient but doing it for now: sorting my pipes by toPos, then by kind - slices.SortFunc(newPipes, func(a, b Pipe) int { - if a.toPos == b.toPos { - return cmp.Compare(a.kind, b.kind) - } - return cmp.Compare(a.toPos, b.toPos) - }) - - return newPipes -} - -func renderPipeSet( - pipes []Pipe, - selectedCommitHashPtr *string, - prevCommit *models.Commit, -) string { - maxPos := int16(0) - commitPos := int16(0) - startCount := 0 - for _, pipe := range pipes { - if pipe.kind == STARTS { - startCount++ - commitPos = pipe.fromPos - } else if pipe.kind == TERMINATES { - commitPos = pipe.toPos - } - - if pipe.right() > maxPos { - maxPos = pipe.right() - } - } - isMerge := startCount > 1 - - cells := lo.Map(lo.Range(int(maxPos)+1), func(i int, _ int) *Cell { - return &Cell{cellType: CONNECTION, style: &style.FgDefault} - }) - - renderPipe := func(pipe *Pipe, style *style.TextStyle, overrideRightStyle bool) { - left := pipe.left() - right := pipe.right() - - if left != right { - for i := left + 1; i < right; i++ { - cells[i].setLeft(style).setRight(style, overrideRightStyle) - } - cells[left].setRight(style, overrideRightStyle) - cells[right].setLeft(style) - } - - if pipe.kind == STARTS || pipe.kind == CONTINUES { - cells[pipe.toPos].setDown(style) - } - if pipe.kind == TERMINATES || pipe.kind == CONTINUES { - cells[pipe.fromPos].setUp(style) - } - } - - // we don't want to highlight two commits if they're contiguous. We only want - // to highlight multiple things if there's an actual visible pipe involved. - highlight := true - if prevCommit != nil && equalHashes(prevCommit.HashPtr(), selectedCommitHashPtr) { - highlight = false - for _, pipe := range pipes { - if equalHashes(pipe.fromHash, selectedCommitHashPtr) && (pipe.kind != TERMINATES || pipe.fromPos != pipe.toPos) { - highlight = true - } - } - } - - // so we have our commit pos again, now it's time to build the cells. - // we'll handle the one that's sourced from our selected commit last so that it can override the other cells. - selectedPipes, nonSelectedPipes := utils.Partition(pipes, func(pipe Pipe) bool { - return highlight && equalHashes(pipe.fromHash, selectedCommitHashPtr) - }) - - for _, pipe := range nonSelectedPipes { - if pipe.kind == STARTS { - renderPipe(&pipe, pipe.style, true) - } - } - - for _, pipe := range nonSelectedPipes { - if pipe.kind != STARTS && !(pipe.kind == TERMINATES && pipe.fromPos == commitPos && pipe.toPos == commitPos) { - renderPipe(&pipe, pipe.style, false) - } - } - - for _, pipe := range selectedPipes { - for i := pipe.left(); i <= pipe.right(); i++ { - cells[i].reset() - } - } - for _, pipe := range selectedPipes { - renderPipe(&pipe, &highlightStyle, true) - if pipe.toPos == commitPos { - cells[pipe.toPos].setStyle(&highlightStyle) - } - } - - cType := COMMIT - if isMerge { - cType = MERGE - } - - cells[commitPos].setType(cType) - - // using a string builder here for the sake of performance - writer := &strings.Builder{} - writer.Grow(len(cells) * 2) - for _, cell := range cells { - cell.render(writer) - } - return writer.String() -} - -func equalHashes(a, b *string) bool { - // if our selectedCommitHashPtr is nil, there is no selected commit - if a == nil || b == nil { - return false - } - - // We know that all hashes are stored in the pool, so we can compare their addresses - return a == b -} diff --git a/pkg/gui/presentation/graph/graph_test.go b/pkg/gui/presentation/graph/graph_test.go deleted file mode 100644 index e567ec674a6..00000000000 --- a/pkg/gui/presentation/graph/graph_test.go +++ /dev/null @@ -1,606 +0,0 @@ -package graph - -import ( - "fmt" - "math/rand" - "strings" - "testing" - - "github.com/gookit/color" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/authors" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" - "github.com/stretchr/testify/assert" - "github.com/xo/terminfo" -) - -func TestRenderCommitGraph(t *testing.T) { - tests := []struct { - name string - commitOpts []models.NewCommitOpts - expectedOutput string - }{ - { - name: "with some merges", - commitOpts: []models.NewCommitOpts{ - {Hash: "1", Parents: []string{"2"}}, - {Hash: "2", Parents: []string{"3"}}, - {Hash: "3", Parents: []string{"4"}}, - {Hash: "4", Parents: []string{"5", "7"}}, - {Hash: "7", Parents: []string{"5"}}, - {Hash: "5", Parents: []string{"8"}}, - {Hash: "8", Parents: []string{"9"}}, - {Hash: "9", Parents: []string{"A", "B"}}, - {Hash: "B", Parents: []string{"D"}}, - {Hash: "D", Parents: []string{"D"}}, - {Hash: "A", Parents: []string{"E"}}, - {Hash: "E", Parents: []string{"F"}}, - {Hash: "F", Parents: []string{"D"}}, - {Hash: "D", Parents: []string{"G"}}, - }, - expectedOutput: ` - 1 ◯ - 2 ◯ - 3 ◯ - 4 ⏣─╮ - 7 │ ◯ - 5 ◯─╯ - 8 ◯ - 9 ⏣─╮ - B │ ◯ - D │ ◯ - A ◯ │ - E ◯ │ - F ◯ │ - D ◯─╯`, - }, - { - name: "with a path that has room to move to the left", - commitOpts: []models.NewCommitOpts{ - {Hash: "1", Parents: []string{"2"}}, - {Hash: "2", Parents: []string{"3", "4"}}, - {Hash: "4", Parents: []string{"3", "5"}}, - {Hash: "3", Parents: []string{"5"}}, - {Hash: "5", Parents: []string{"6"}}, - {Hash: "6", Parents: []string{"7"}}, - }, - expectedOutput: ` - 1 ◯ - 2 ⏣─╮ - 4 │ ⏣─╮ - 3 ◯─╯ │ - 5 ◯───╯ - 6 ◯`, - }, - { - name: "with a new commit", - commitOpts: []models.NewCommitOpts{ - {Hash: "1", Parents: []string{"2"}}, - {Hash: "2", Parents: []string{"3", "4"}}, - {Hash: "4", Parents: []string{"3", "5"}}, - {Hash: "Z", Parents: []string{"Z"}}, - {Hash: "3", Parents: []string{"5"}}, - {Hash: "5", Parents: []string{"6"}}, - {Hash: "6", Parents: []string{"7"}}, - }, - expectedOutput: ` - 1 ◯ - 2 ⏣─╮ - 4 │ ⏣─╮ - Z │ │ │ ◯ - 3 ◯─╯ │ │ - 5 ◯───╯ │ - 6 ◯ ╭───╯`, - }, - { - name: "with a path that has room to move to the left and continues", - commitOpts: []models.NewCommitOpts{ - {Hash: "1", Parents: []string{"2"}}, - {Hash: "2", Parents: []string{"3", "4"}}, - {Hash: "3", Parents: []string{"5", "4"}}, - {Hash: "5", Parents: []string{"7", "8"}}, - {Hash: "4", Parents: []string{"7"}}, - {Hash: "7", Parents: []string{"11"}}, - }, - expectedOutput: ` - 1 ◯ - 2 ⏣─╮ - 3 ⏣─│─╮ - 5 ⏣─│─│─╮ - 4 │ ◯─╯ │ - 7 ◯─╯ ╭─╯`, - }, - { - name: "with a path that has room to move to the left and continues", - commitOpts: []models.NewCommitOpts{ - {Hash: "1", Parents: []string{"2"}}, - {Hash: "2", Parents: []string{"3", "4"}}, - {Hash: "3", Parents: []string{"5", "4"}}, - {Hash: "5", Parents: []string{"7", "8"}}, - {Hash: "7", Parents: []string{"4", "A"}}, - {Hash: "4", Parents: []string{"B"}}, - {Hash: "B", Parents: []string{"C"}}, - }, - expectedOutput: ` - 1 ◯ - 2 ⏣─╮ - 3 ⏣─│─╮ - 5 ⏣─│─│─╮ - 7 ⏣─│─│─│─╮ - 4 ◯─┴─╯ │ │ - B ◯ ╭───╯ │`, - }, - { - name: "with a path that has room to move to the left and continues", - commitOpts: []models.NewCommitOpts{ - {Hash: "1", Parents: []string{"2", "3"}}, - {Hash: "3", Parents: []string{"2"}}, - {Hash: "2", Parents: []string{"4", "5"}}, - {Hash: "4", Parents: []string{"6", "7"}}, - {Hash: "6", Parents: []string{"8"}}, - }, - expectedOutput: ` - 1 ⏣─╮ - 3 │ ◯ - 2 ⏣─│ - 4 ⏣─│─╮ - 6 ◯ │ │`, - }, - { - name: "new merge path fills gap before continuing path on right", - commitOpts: []models.NewCommitOpts{ - {Hash: "1", Parents: []string{"2", "3", "4", "5"}}, - {Hash: "4", Parents: []string{"2"}}, - {Hash: "2", Parents: []string{"A"}}, - {Hash: "A", Parents: []string{"6", "B"}}, - {Hash: "B", Parents: []string{"C"}}, - }, - expectedOutput: ` - 1 ⏣─┬─┬─╮ - 4 │ │ ◯ │ - 2 ◯─│─╯ │ - A ⏣─│─╮ │ - B │ │ ◯ │`, - }, - { - name: "with a path that has room to move to the left and continues", - commitOpts: []models.NewCommitOpts{ - {Hash: "1", Parents: []string{"2"}}, - {Hash: "2", Parents: []string{"3", "4"}}, - {Hash: "3", Parents: []string{"5", "4"}}, - {Hash: "5", Parents: []string{"7", "8"}}, - {Hash: "7", Parents: []string{"4", "A"}}, - {Hash: "4", Parents: []string{"B"}}, - {Hash: "B", Parents: []string{"C"}}, - {Hash: "C", Parents: []string{"D"}}, - }, - expectedOutput: ` - 1 ◯ - 2 ⏣─╮ - 3 ⏣─│─╮ - 5 ⏣─│─│─╮ - 7 ⏣─│─│─│─╮ - 4 ◯─┴─╯ │ │ - B ◯ ╭───╯ │ - C ◯ │ ╭───╯`, - }, - { - name: "with a path that has room to move to the left and continues", - commitOpts: []models.NewCommitOpts{ - {Hash: "1", Parents: []string{"2"}}, - {Hash: "2", Parents: []string{"3", "4"}}, - {Hash: "3", Parents: []string{"5", "4"}}, - {Hash: "5", Parents: []string{"7", "G"}}, - {Hash: "7", Parents: []string{"8", "A"}}, - {Hash: "8", Parents: []string{"4", "E"}}, - {Hash: "4", Parents: []string{"B"}}, - {Hash: "B", Parents: []string{"C"}}, - {Hash: "C", Parents: []string{"D"}}, - {Hash: "D", Parents: []string{"F"}}, - }, - expectedOutput: ` - 1 ◯ - 2 ⏣─╮ - 3 ⏣─│─╮ - 5 ⏣─│─│─╮ - 7 ⏣─│─│─│─╮ - 8 ⏣─│─│─│─│─╮ - 4 ◯─┴─╯ │ │ │ - B ◯ ╭───╯ │ │ - C ◯ │ ╭───╯ │ - D ◯ │ │ ╭───╯`, - }, - } - - oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions) - defer color.ForceSetColorLevel(oldColorLevel) - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - hashPool := &utils.StringPool{} - - getStyle := func(c *models.Commit) *style.TextStyle { return &style.FgDefault } - commits := lo.Map(test.commitOpts, - func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) }) - lines := RenderCommitGraph(commits, hashPool.Add("blah"), getStyle) - - trimmedExpectedOutput := "" - for _, line := range strings.Split(strings.TrimPrefix(test.expectedOutput, "\n"), "\n") { - trimmedExpectedOutput += strings.TrimSpace(line) + "\n" - } - - t.Log("\nexpected: \n" + trimmedExpectedOutput) - - output := "" - for i, line := range lines { - description := test.commitOpts[i].Hash - output += strings.TrimSpace(description+" "+utils.Decolorise(line)) + "\n" - } - t.Log("\nactual: \n" + output) - - assert.Equal(t, - trimmedExpectedOutput, - output) - }) - } -} - -func TestRenderPipeSet(t *testing.T) { - cyan := style.FgCyan - red := style.FgRed - green := style.FgGreen - // blue := style.FgBlue - yellow := style.FgYellow - magenta := style.FgMagenta - nothing := style.Nothing - - hashPool := &utils.StringPool{} - pool := func(s string) *string { return hashPool.Add(s) } - - tests := []struct { - name string - pipes []Pipe - commit *models.Commit - prevCommit *models.Commit - expectedStr string - expectedStyles []style.TextStyle - }{ - { - name: "single cell", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: &cyan}, - {fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: STARTS, style: &green}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}), - expectedStr: "◯", - expectedStyles: []style.TextStyle{green}, - }, - { - name: "single cell, selected", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("selected"), kind: TERMINATES, style: &cyan}, - {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("c"), kind: STARTS, style: &green}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}), - expectedStr: "◯", - expectedStyles: []style.TextStyle{highlightStyle}, - }, - { - name: "terminating hook and starting hook, selected", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("selected"), kind: TERMINATES, style: &cyan}, - {fromPos: 1, toPos: 0, fromHash: pool("c"), toHash: pool("selected"), kind: TERMINATES, style: &yellow}, - {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("d"), kind: STARTS, style: &green}, - {fromPos: 0, toPos: 1, fromHash: pool("selected"), toHash: pool("e"), kind: STARTS, style: &green}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}), - expectedStr: "⏣─╮", - expectedStyles: []style.TextStyle{ - highlightStyle, highlightStyle, highlightStyle, - }, - }, - { - name: "terminating hook and starting hook, prioritise the terminating one", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: &red}, - {fromPos: 1, toPos: 0, fromHash: pool("c"), toHash: pool("b"), kind: TERMINATES, style: &magenta}, - {fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("d"), kind: STARTS, style: &green}, - {fromPos: 0, toPos: 1, fromHash: pool("b"), toHash: pool("e"), kind: STARTS, style: &green}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}), - expectedStr: "⏣─│", - expectedStyles: []style.TextStyle{ - green, green, magenta, - }, - }, - { - name: "starting and terminating pipe sharing some space", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: &red}, - {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: &yellow}, - {fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("b2"), kind: CONTINUES, style: &magenta}, - {fromPos: 3, toPos: 0, fromHash: pool("e1"), toHash: pool("a2"), kind: TERMINATES, style: &green}, - {fromPos: 0, toPos: 2, fromHash: pool("a2"), toHash: pool("c3"), kind: STARTS, style: &yellow}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), - expectedStr: "⏣─│─┬─╯", - expectedStyles: []style.TextStyle{ - yellow, yellow, magenta, yellow, yellow, green, green, - }, - }, - { - name: "starting and terminating pipe sharing some space, with selection", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("selected"), kind: TERMINATES, style: &red}, - {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a3"), kind: STARTS, style: &yellow}, - {fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("b2"), kind: CONTINUES, style: &magenta}, - {fromPos: 3, toPos: 0, fromHash: pool("e1"), toHash: pool("selected"), kind: TERMINATES, style: &green}, - {fromPos: 0, toPos: 2, fromHash: pool("selected"), toHash: pool("c3"), kind: STARTS, style: &yellow}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), - expectedStr: "⏣───╮ ╯", - expectedStyles: []style.TextStyle{ - highlightStyle, highlightStyle, highlightStyle, highlightStyle, highlightStyle, nothing, green, - }, - }, - { - name: "many terminating pipes", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: &red}, - {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: &yellow}, - {fromPos: 1, toPos: 0, fromHash: pool("b1"), toHash: pool("a2"), kind: TERMINATES, style: &magenta}, - {fromPos: 2, toPos: 0, fromHash: pool("c1"), toHash: pool("a2"), kind: TERMINATES, style: &green}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), - expectedStr: "◯─┴─╯", - expectedStyles: []style.TextStyle{ - yellow, magenta, magenta, green, green, - }, - }, - { - name: "starting pipe passing through", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: &red}, - {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: &yellow}, - {fromPos: 0, toPos: 3, fromHash: pool("a2"), toHash: pool("d3"), kind: STARTS, style: &yellow}, - {fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("b3"), kind: CONTINUES, style: &magenta}, - {fromPos: 2, toPos: 2, fromHash: pool("c1"), toHash: pool("c3"), kind: CONTINUES, style: &green}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), - expectedStr: "⏣─│─│─╮", - expectedStyles: []style.TextStyle{ - yellow, yellow, magenta, yellow, green, yellow, yellow, - }, - }, - { - name: "starting and terminating path crossing continuing path", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: &red}, - {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: &yellow}, - {fromPos: 0, toPos: 1, fromHash: pool("a2"), toHash: pool("b3"), kind: STARTS, style: &yellow}, - {fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("a2"), kind: CONTINUES, style: &green}, - {fromPos: 2, toPos: 0, fromHash: pool("c1"), toHash: pool("a2"), kind: TERMINATES, style: &magenta}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), - expectedStr: "⏣─│─╯", - expectedStyles: []style.TextStyle{ - yellow, yellow, green, magenta, magenta, - }, - }, - { - name: "another clash of starting and terminating paths", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: &red}, - {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: &yellow}, - {fromPos: 0, toPos: 1, fromHash: pool("a2"), toHash: pool("b3"), kind: STARTS, style: &yellow}, - {fromPos: 2, toPos: 2, fromHash: pool("c1"), toHash: pool("c3"), kind: CONTINUES, style: &green}, - {fromPos: 3, toPos: 0, fromHash: pool("d1"), toHash: pool("a2"), kind: TERMINATES, style: &magenta}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), - expectedStr: "⏣─┬─│─╯", - expectedStyles: []style.TextStyle{ - yellow, yellow, yellow, magenta, green, magenta, magenta, - }, - }, - { - name: "commit whose previous commit is selected", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: &red}, - {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: &yellow}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}), - expectedStr: "◯", - expectedStyles: []style.TextStyle{ - yellow, - }, - }, - { - name: "commit whose previous commit is selected and is a merge commit", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: &red}, - {fromPos: 1, toPos: 1, fromHash: pool("selected"), toHash: pool("b3"), kind: CONTINUES, style: &red}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}), - expectedStr: "◯ │", - expectedStyles: []style.TextStyle{ - highlightStyle, nothing, highlightStyle, - }, - }, - { - name: "commit whose previous commit is selected and is a merge commit, with continuing pipe inbetween", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: &red}, - {fromPos: 1, toPos: 1, fromHash: pool("z1"), toHash: pool("z3"), kind: CONTINUES, style: &green}, - {fromPos: 2, toPos: 2, fromHash: pool("selected"), toHash: pool("b3"), kind: CONTINUES, style: &red}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}), - expectedStr: "◯ │ │", - expectedStyles: []style.TextStyle{ - highlightStyle, nothing, green, nothing, highlightStyle, - }, - }, - { - name: "when previous commit is selected, not a merge commit, and spawns a continuing pipe", - pipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: &red}, - {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: &green}, - {fromPos: 0, toPos: 1, fromHash: pool("a2"), toHash: pool("b3"), kind: STARTS, style: &green}, - {fromPos: 1, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: &yellow}, - }, - prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}), - expectedStr: "⏣─╯", - expectedStyles: []style.TextStyle{ - highlightStyle, highlightStyle, highlightStyle, - }, - }, - } - - oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions) - defer color.ForceSetColorLevel(oldColorLevel) - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actualStr := renderPipeSet(test.pipes, pool("selected"), test.prevCommit) - t.Log("actual cells:") - t.Log(actualStr) - expectedStr := "" - if len([]rune(test.expectedStr)) != len(test.expectedStyles) { - t.Fatalf("Error in test setup: you have %d characters in the expected output (%s) but have specified %d styles", len([]rune(test.expectedStr)), test.expectedStr, len(test.expectedStyles)) - } - for i, char := range []rune(test.expectedStr) { - expectedStr += test.expectedStyles[i].Sprint(string(char)) - } - expectedStr += " " - t.Log("expected cells:") - t.Log(expectedStr) - - assert.Equal(t, expectedStr, actualStr) - }) - } -} - -func TestGetNextPipes(t *testing.T) { - hashPool := &utils.StringPool{} - pool := func(s string) *string { return hashPool.Add(s) } - - tests := []struct { - prevPipes []Pipe - commit *models.Commit - expected []Pipe - }{ - { - prevPipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: STARTS, style: &style.FgDefault}, - }, - commit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "b", - Parents: []string{"c"}, - }), - expected: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: &style.FgDefault}, - {fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: STARTS, style: &style.FgDefault}, - }, - }, - { - prevPipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: &style.FgDefault}, - {fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: STARTS, style: &style.FgDefault}, - {fromPos: 0, toPos: 1, fromHash: pool("b"), toHash: pool("d"), kind: STARTS, style: &style.FgDefault}, - }, - commit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "d", - Parents: []string{"e"}, - }), - expected: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: CONTINUES, style: &style.FgDefault}, - {fromPos: 1, toPos: 1, fromHash: pool("b"), toHash: pool("d"), kind: TERMINATES, style: &style.FgDefault}, - {fromPos: 1, toPos: 1, fromHash: pool("d"), toHash: pool("e"), kind: STARTS, style: &style.FgDefault}, - }, - }, - { - prevPipes: []Pipe{ - {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("root"), kind: TERMINATES, style: &style.FgDefault}, - }, - commit: models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: "root", - Parents: []string{}, - }), - expected: []Pipe{ - {fromPos: 1, toPos: 1, fromHash: pool("root"), toHash: pool(models.EmptyTreeCommitHash), kind: STARTS, style: &style.FgDefault}, - }, - }, - } - - oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions) - defer color.ForceSetColorLevel(oldColorLevel) - - for _, test := range tests { - getStyle := func(c *models.Commit) *style.TextStyle { return &style.FgDefault } - pipes := getNextPipes(test.prevPipes, test.commit, getStyle) - // rendering cells so that it's easier to see what went wrong - actualStr := renderPipeSet(pipes, pool("selected"), nil) - expectedStr := renderPipeSet(test.expected, pool("selected"), nil) - t.Log("expected cells:") - t.Log(expectedStr) - t.Log("actual cells:") - t.Log(actualStr) - assert.EqualValues(t, test.expected, pipes) - } -} - -func BenchmarkRenderCommitGraph(b *testing.B) { - oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions) - defer color.ForceSetColorLevel(oldColorLevel) - - hashPool := &utils.StringPool{} - - commits := generateCommits(hashPool, 50) - getStyle := func(commit *models.Commit) *style.TextStyle { - return authors.AuthorStyle(commit.AuthorName) - } - b.ResetTimer() - for b.Loop() { - RenderCommitGraph(commits, hashPool.Add("selected"), getStyle) - } -} - -func generateCommits(hashPool *utils.StringPool, count int) []*models.Commit { - rnd := rand.New(rand.NewSource(1234)) - pool := []*models.Commit{models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a", AuthorName: "A"})} - commits := make([]*models.Commit, 0, count) - authorPool := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"} - for len(commits) < count { - currentCommitIdx := rnd.Intn(len(pool)) - currentCommit := pool[currentCommitIdx] - pool = append(pool[0:currentCommitIdx], pool[currentCommitIdx+1:]...) - // I need to pick a random number of parents to add - parentCount := rnd.Intn(2) + 1 - - parentHashes := currentCommit.Parents() - for j := range parentCount { - reuseParent := rnd.Intn(6) != 1 && j <= len(pool)-1 && j != 0 - var newParent *models.Commit - if reuseParent { - newParent = pool[j] - } else { - newParent = models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: fmt.Sprintf("%s%d", currentCommit.Hash(), j), - AuthorName: authorPool[rnd.Intn(len(authorPool))], - }) - pool = append(pool, newParent) - } - parentHashes = append(parentHashes, newParent.Hash()) - } - - changedCommit := models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: currentCommit.Hash(), - AuthorName: currentCommit.AuthorName, - Parents: parentHashes, - }) - commits = append(commits, changedCommit) - } - - return commits -} diff --git a/pkg/gui/presentation/icons/file_icons.go b/pkg/gui/presentation/icons/file_icons.go deleted file mode 100644 index 8df887bee6a..00000000000 --- a/pkg/gui/presentation/icons/file_icons.go +++ /dev/null @@ -1,794 +0,0 @@ -package icons - -import ( - "path/filepath" - "strings" - - "github.com/jesseduffield/lazygit/pkg/config" -) - -// NOTE: Visit next links for inspiration: -// https://github.com/eza-community/eza/blob/main/src/output/icons.rs -// https://github.com/nvim-tree/nvim-web-devicons/tree/master/lua/nvim-web-devicons/default - -var ( - DEFAULT_FILE_ICON = IconProperties{Icon: "\uf15b", Color: "#878787"} //  - DEFAULT_SUBMODULE_ICON = IconProperties{Icon: "\U000f02a2", Color: "#FF4F00"} // 󰊢 - DEFAULT_DIRECTORY_ICON = IconProperties{Icon: "\uf07b", Color: "#878787"} //  -) - -// NOTE: The filename map is case sensitive. -var nameIconMap = map[string]IconProperties{ - ".atom": {Icon: "\ue764", Color: "#EED9B7"}, //  - ".babelrc": {Icon: "\ue639", Color: "#FED836"}, //  - ".bash_profile": {Icon: "\ue615", Color: "#89E051"}, //  - ".bashprofile": {Icon: "\ue615", Color: "#89E051"}, //  - ".bashrc": {Icon: "\ue795", Color: "#89E051"}, //  - ".clang-format": {Icon: "\ue615", Color: "#86806D"}, //  - ".clang-tidy": {Icon: "\ue615", Color: "#86806D"}, //  - ".codespellrc": {Icon: "\U000f04c6", Color: "#35DA60"}, // 󰓆 - ".condarc": {Icon: "\ue715", Color: "#43B02A"}, //  - ".dockerignore": {Icon: "\U000f0868", Color: "#458EE6"}, // 󰡨 - ".ds_store": {Icon: "\uf302", Color: "#78919C"}, //  - ".editorconfig": {Icon: "\ue652", Color: "#FFFFFF"}, //  - ".env": {Icon: "\U000f066a", Color: "#FBC02D"}, // 󰙪 - ".eslintignore": {Icon: "\U000f0c7a", Color: "#3F52B5"}, // 󰱺 - ".eslintrc": {Icon: "\U000f0c7a", Color: "#3F52B5"}, // 󰱺 - ".git": {Icon: "\U000f02a2", Color: "#E64A19"}, // 󰊢 - ".git-blame-ignore-revs": {Icon: "\U000f02a2", Color: "#E64A19"}, // 󰊢 - ".gitattributes": {Icon: "\U000f02a2", Color: "#E64A19"}, // 󰊢 - ".gitconfig": {Icon: "\U000f02a2", Color: "#E64A19"}, // 󰊢 - ".github": {Icon: "\uf408", Color: "#333333"}, //  - ".gitignore": {Icon: "\U000f02a2", Color: "#E64A19"}, // 󰊢 - ".gitlab-ci.yml": {Icon: "\uf296", Color: "#F54D27"}, //  - ".gitmodules": {Icon: "\U000f02a2", Color: "#E64A19"}, // 󰊢 - ".gtkrc-2.0": {Icon: "\uf362", Color: "#FFFFFF"}, //  - ".gvimrc": {Icon: "\ue62b", Color: "#019833"}, //  - ".idea": {Icon: "\ue7b5", Color: "#626262"}, //  - ".justfile": {Icon: "\uf0ad", Color: "#6D8086"}, //  - ".luacheckrc": {Icon: "\ue615", Color: "#868F9D"}, //  - ".luaurc": {Icon: "\ue615", Color: "#00A2FF"}, //  - ".mailmap": {Icon: "\U000f01ee", Color: "#42A5F5"}, // 󰇮 - ".nanorc": {Icon: "\ue838", Color: "#440077"}, //  - ".npmignore": {Icon: "\ued0e", Color: "#CC3837"}, //  - ".npmrc": {Icon: "\ued0e", Color: "#CC3837"}, //  - ".nuxtrc": {Icon: "\U000f1106", Color: "#00C58E"}, // 󱄆 - ".nvmrc": {Icon: "\ued0d", Color: "#4CAF51"}, //  - ".pre-commit-config.yaml": {Icon: "\U000f06e2", Color: "#F8B424"}, // 󰛢 - ".prettierignore": {Icon: "\ue6b4", Color: "#4285F4"}, //  - ".prettierrc": {Icon: "\ue6b4", Color: "#4285F4"}, //  - ".prettierrc.json": {Icon: "\ue6b4", Color: "#4285F4"}, //  - ".prettierrc.json5": {Icon: "\ue6b4", Color: "#4285F4"}, //  - ".prettierrc.toml": {Icon: "\ue6b4", Color: "#4285F4"}, //  - ".prettierrc.yaml": {Icon: "\ue6b4", Color: "#4285F4"}, //  - ".prettierrc.yml": {Icon: "\ue6b4", Color: "#4285F4"}, //  - ".pylintrc": {Icon: "\ue615", Color: "#968F6D"}, //  - ".rvm": {Icon: "\ue21e", Color: "#D70000"}, //  - ".settings.json": {Icon: "\ue70c", Color: "#854CC7"}, //  - ".SRCINFO": {Icon: "\uf129", Color: "#0F94D2"}, //  - ".tmux.conf": {Icon: "\uebc8", Color: "#14BA19"}, //  - ".tmux.conf.local": {Icon: "\uebc8", Color: "#14BA19"}, //  - ".Trash": {Icon: "\uf1f8", Color: "#ACBCEF"}, //  - ".vimrc": {Icon: "\ue62b", Color: "#019833"}, //  - ".vscode": {Icon: "\ue70c", Color: "#007ACC"}, //  - ".Xauthority": {Icon: "\uf369", Color: "#E54D18"}, //  - ".Xresources": {Icon: "\uf369", Color: "#E54D18"}, //  - ".xinitrc": {Icon: "\uf369", Color: "#E54D18"}, //  - ".xsession": {Icon: "\uf369", Color: "#E54D18"}, //  - ".zprofile": {Icon: "\ue615", Color: "#89E051"}, //  - ".zshenv": {Icon: "\ue615", Color: "#89E051"}, //  - ".zshrc": {Icon: "\ue795", Color: "#89E051"}, //  - "_gvimrc": {Icon: "\ue62b", Color: "#019833"}, //  - "_vimrc": {Icon: "\ue62b", Color: "#019833"}, //  - "AUTHORS": {Icon: "\uedca", Color: "#A172FF"}, //  - "AUTHORS.txt": {Icon: "\uedca", Color: "#A172FF"}, //  - "bin": {Icon: "\U000f12a7", Color: "#25A79A"}, // 󱊧 - "brewfile": {Icon: "\ue791", Color: "#701516"}, //  - "bspwmrc": {Icon: "\uf355", Color: "#2F2F2F"}, //  - "BUILD": {Icon: "\ue63a", Color: "#89E051"}, //  - "build.gradle": {Icon: "\ue660", Color: "#005F87"}, //  - "build.zig.zon": {Icon: "\ue6a9", Color: "#F69A1B"}, //  - "bun.lockb": {Icon: "\ue76f", Color: "#EADCD1"}, //  - "cantorrc": {Icon: "\uf373", Color: "#1C99F3"}, //  - "Cargo.lock": {Icon: "\ue7a8", Color: "#DEA584"}, //  - "Cargo.toml": {Icon: "\ue7a8", Color: "#DEA584"}, //  - "checkhealth": {Icon: "\U000f04d9", Color: "#75B4FB"}, // 󰓙 - "CMakeLists.txt": {Icon: "\ue794", Color: "#DCE3EB"}, //  - "CODE_OF_CONDUCT": {Icon: "\uf4ae", Color: "#E41662"}, //  - "CODE_OF_CONDUCT.md": {Icon: "\uf4ae", Color: "#E41662"}, //  - "CODE-OF-CONDUCT.md": {Icon: "\uf4ae", Color: "#E41662"}, //  - "commit_editmsg": {Icon: "\ue702", Color: "#F54D27"}, //  - "COMMIT_EDITMSG": {Icon: "\ue702", Color: "#E54D18"}, //  - "commitlint.config.js": {Icon: "\U000f0718", Color: "#039688"}, //  - "commitlint.config.ts": {Icon: "\U000f0718", Color: "#039688"}, //  - "compose.yaml": {Icon: "\uf21f", Color: "#0088C9"}, //  - "compose.yml": {Icon: "\uf21f", Color: "#0088C9"}, //  - "config": {Icon: "\uf013", Color: "#696969"}, //  - "containerfile": {Icon: "\uf21f", Color: "#0088C9"}, //  - "copying": {Icon: "\U000f0124", Color: "#FF5821"}, // 󰄤 - "copying.lesser": {Icon: "\ue60a", Color: "#CBCB41"}, //  - "docker-compose.yaml": {Icon: "\uf21f", Color: "#0088C9"}, //  - "docker-compose.yml": {Icon: "\uf21f", Color: "#0088C9"}, //  - "dockerfile": {Icon: "\uf21f", Color: "#0088C9"}, //  - "Dockerfile": {Icon: "\uf308", Color: "#458EE6"}, //  - "ds_store": {Icon: "\uf179", Color: "#DDDDDD"}, //  - "eslint.config.cjs": {Icon: "\U000f0c7a", Color: "#3F52B5"}, // 󰱺 - "eslint.config.js": {Icon: "\U000f0c7a", Color: "#3F52B5"}, // 󰱺 - "eslint.config.mjs": {Icon: "\U000f0c7a", Color: "#3F52B5"}, // 󰱺 - "eslint.config.ts": {Icon: "\U000f0c7a", Color: "#3F52B5"}, // 󰱺 - "ext_typoscript_setup.txt": {Icon: "\ue772", Color: "#FF8700"}, //  - "favicon.ico": {Icon: "\ue623", Color: "#CBCB41"}, //  - "fp-info-cache": {Icon: "\uf34c", Color: "#FFFFFF"}, //  - "fp-lib-table": {Icon: "\uf34c", Color: "#FFFFFF"}, //  - "FreeCAD.conf": {Icon: "\uf336", Color: "#CB333B"}, //  - "gemfile$": {Icon: "\ue791", Color: "#701516"}, //  - "gitignore_global": {Icon: "\U000f02a2", Color: "#E64A19"}, // 󰊢 - "gnumakefile": {Icon: "\ueba2", Color: "#EF5351"}, //  - "GNUmakefile": {Icon: "\ue779", Color: "#6D8086"}, //  - "go.mod": {Icon: "\ue627", Color: "#02ACC1"}, //  - "go.sum": {Icon: "\ue627", Color: "#02ACC1"}, //  - "go.work": {Icon: "\ue627", Color: "#02ACC1"}, //  - "gradle": {Icon: "\ue660", Color: "#005F87"}, //  - "gradle-wrapper.properties": {Icon: "\ue660", Color: "#005F87"}, //  - "gradle.properties": {Icon: "\ue660", Color: "#005F87"}, //  - "gradlew": {Icon: "\ue660", Color: "#005F87"}, //  - "gruntfile.babel.js": {Icon: "\ue611", Color: "#E37933"}, //  - "gruntfile.coffee": {Icon: "\ue611", Color: "#E37933"}, //  - "gruntfile.js": {Icon: "\ue611", Color: "#E37933"}, //  - "gruntfile.ls": {Icon: "\ue611", Color: "#E37933"}, //  - "gruntfile.ts": {Icon: "\ue611", Color: "#E37933"}, //  - "gtkrc": {Icon: "\uf362", Color: "#FFFFFF"}, //  - "gulpfile.babel.js": {Icon: "\ue610", Color: "#CC3E44"}, //  - "gulpfile.coffee": {Icon: "\ue610", Color: "#CC3E44"}, //  - "gulpfile.js": {Icon: "\ue610", Color: "#CC3E44"}, //  - "gulpfile.ls": {Icon: "\ue610", Color: "#CC3E44"}, //  - "gulpfile.ts": {Icon: "\ue610", Color: "#CC3E44"}, //  - "hidden": {Icon: "\uf023", Color: "#555555"}, //  - "hypridle.conf": {Icon: "\uf359", Color: "#00AAAE"}, //  - "hyprland.conf": {Icon: "\uf359", Color: "#00AAAE"}, //  - "hyprlock.conf": {Icon: "\uf359", Color: "#00AAAE"}, //  - "hyprpaper.conf": {Icon: "\uf359", Color: "#00AAAE"}, //  - "i3blocks.conf": {Icon: "\uf35a", Color: "#E8EBEE"}, //  - "i3status.conf": {Icon: "\uf35a", Color: "#E8EBEE"}, //  - "include": {Icon: "\ue5fc", Color: "#EEEEEE"}, //  - "index.theme": {Icon: "\uee72", Color: "#2DB96F"}, //  - "ionic.config.json": {Icon: "\ue66b", Color: "#508FF7"}, //  - "justfile": {Icon: "\uf0ad", Color: "#6D8086"}, //  - "kalgebrarc": {Icon: "\uf373", Color: "#1C99F3"}, //  - "kdeglobals": {Icon: "\uf373", Color: "#1C99F3"}, //  - "kdenlive-layoutsrc": {Icon: "\uf33c", Color: "#83B8F2"}, //  - "kdenliverc": {Icon: "\uf33c", Color: "#83B8F2"}, //  - "kritadisplayrc": {Icon: "\uf33d", Color: "#F245FB"}, //  - "kritarc": {Icon: "\uf33d", Color: "#F245FB"}, //  - "lib": {Icon: "\U000f1517", Color: "#8BC34A"}, // 󱔗 - "LICENSE": {Icon: "\uf02d", Color: "#EDEDED"}, //  - "LICENSE.md": {Icon: "\uf02d", Color: "#EDEDED"}, //  - "localized": {Icon: "\uf179", Color: "#DDDDDD"}, //  - "lxde-rc.xml": {Icon: "\uf363", Color: "#909090"}, //  - "lxqt.conf": {Icon: "\uf364", Color: "#0192D3"}, //  - "Makefile": {Icon: "\ue673", Color: "#FEFEFE"}, //  - "mix.lock": {Icon: "\ue62d", Color: "#A074C4"}, //  - "mpv.conf": {Icon: "\uf36e", Color: "#3B1342"}, //  - "node_modules": {Icon: "\ue718", Color: "#E8274B"}, //  - "npmignore": {Icon: "\ue71e", Color: "#E8274B"}, //  - "nuxt.config.cjs": {Icon: "\U000f1106", Color: "#00C58E"}, // 󱄆 - "nuxt.config.js": {Icon: "\U000f1106", Color: "#00C58E"}, // 󱄆 - "nuxt.config.mjs": {Icon: "\U000f1106", Color: "#00C58E"}, // 󱄆 - "nuxt.config.ts": {Icon: "\U000f1106", Color: "#00C58E"}, // 󱄆 - "package-lock.json": {Icon: "\ued0d", Color: "#F54436"}, //  - "package.json": {Icon: "\ued0d", Color: "#4CAF51"}, //  - "PKGBUILD": {Icon: "\uf303", Color: "#0F94D2"}, //  - "platformio.ini": {Icon: "\ue682", Color: "#F6822B"}, //  - "pom.xml": {Icon: "\U000f06d3", Color: "#FF7043"}, // 󰛓 - "prettier.config.cjs": {Icon: "\ue6b4", Color: "#4285F4"}, //  - "prettier.config.js": {Icon: "\ue6b4", Color: "#4285F4"}, //  - "prettier.config.mjs": {Icon: "\ue6b4", Color: "#4285F4"}, //  - "prettier.config.ts": {Icon: "\ue6b4", Color: "#4285F4"}, //  - "PrusaSlicer.ini": {Icon: "\uf351", Color: "#EC6B23"}, //  - "PrusaSlicerGcodeViewer.ini": {Icon: "\uf351", Color: "#EC6B23"}, //  - "py.typed": {Icon: "\ue606", Color: "#ffbc03"}, //  - "QtProject.conf": {Icon: "\uf375", Color: "#40CD52"}, //  - "R": {Icon: "\U000f07d4", Color: "#2266BA"}, // 󰟔 - "README": {Icon: "\U000f00ba", Color: "#EDEDED"}, // 󰂺 - "README.md": {Icon: "\U000f00ba", Color: "#EDEDED"}, // 󰂺 - "robots.txt": {Icon: "\U000f06a9", Color: "#5D7096"}, // 󰚩 - "rubydoc": {Icon: "\ue73b", Color: "#F32C24"}, //  - "SECURITY": {Icon: "\U000f0483", Color: "#BEC4C9"}, // 󰒃 - "SECURITY.md": {Icon: "\U000f0483", Color: "#BEC4C9"}, // 󰒃 - "settings.gradle": {Icon: "\ue660", Color: "#005F87"}, //  - "svelte.config.js": {Icon: "\ue697", Color: "#FF5821"}, //  - "sxhkdrc": {Icon: "\uf355", Color: "#2F2F2F"}, //  - "sym-lib-table": {Icon: "\uf34c", Color: "#FFFFFF"}, //  - "tailwind.config.js": {Icon: "\U000f13ff", Color: "#4DB6AC"}, // 󱏿 - "tailwind.config.mjs": {Icon: "\U000f13ff", Color: "#4DB6AC"}, // 󱏿 - "tailwind.config.ts": {Icon: "\U000f13ff", Color: "#4DB6AC"}, // 󱏿 - "tmux.conf": {Icon: "\uebc8", Color: "#14BA19"}, //  - "tmux.conf.local": {Icon: "\uebc8", Color: "#14BA19"}, //  - "tsconfig.json": {Icon: "\ue628", Color: "#0188D1"}, //  - "unlicense": {Icon: "\ue60a", Color: "#D0BF41"}, //  - "vagrantfile$": {Icon: "\uf2b8", Color: "#1868F2"}, //  - "vlcrc": {Icon: "\U000f057c", Color: "#E85E00"}, // 󰕼 - "webpack": {Icon: "\U000f072b", Color: "#519ABA"}, // 󰜫 - "weston.ini": {Icon: "\uf367", Color: "#FFBB01"}, //  - "WORKSPACE": {Icon: "\ue63a", Color: "#89E051"}, //  - "WORKSPACE.bzlmod": {Icon: "\ue63a", Color: "#89E051"}, //  - "xmobarrc": {Icon: "\uf35e", Color: "#FD4D5D"}, //  - "xmobarrc.hs": {Icon: "\uf35e", Color: "#FD4D5D"}, //  - "xmonad.hs": {Icon: "\uf35e", Color: "#FD4D5D"}, //  - "xorg.conf": {Icon: "\uf369", Color: "#E54D18"}, //  - "xsettingsd.conf": {Icon: "\uf369", Color: "#E54D18"}, //  - "yarn.lock": {Icon: "\ue6a7", Color: "#0188D1"}, //  -} - -var extIconMap = map[string]IconProperties{ - ".3gp": {Icon: "\uf03d", Color: "#F6822B"}, //  - ".3mf": {Icon: "\U000f01a7", Color: "#888888"}, // 󰆧 - ".7z": {Icon: "\uf410", Color: "#ECA517"}, //  - ".DS_store": {Icon: "\uf179", Color: "#A2AAAD"}, //  - ".a": {Icon: "\U000f1517", Color: "#8BC34A"}, // 󱔗 - ".aac": {Icon: "\uf001", Color: "#20C2E3"}, //  - ".adb": {Icon: "\ue6b5", Color: "#22FFFF"}, //  - ".ads": {Icon: "\ue6b5", Color: "#22FFFF"}, //  - ".ai": {Icon: "\ue7b4", Color: "#D0BF41"}, //  - ".aif": {Icon: "\uf001", Color: "#00AFFF"}, //  - ".aiff": {Icon: "\U000f0386", Color: "#EE534F"}, // 󰎆 - ".android": {Icon: "\ue70e", Color: "#66AF3D"}, //  - ".ape": {Icon: "\uf001", Color: "#00AFFF"}, //  - ".apk": {Icon: "\ue70e", Color: "#8BC34A"}, //  - ".app": {Icon: "\ueae8", Color: "#9F0500"}, //  - ".apple": {Icon: "\ue635", Color: "#A2AAAD"}, //  - ".applescript": {Icon: "\uf302", Color: "#78919C"}, //  - ".asc": {Icon: "\U000f0306", Color: "#25A79A"}, // 󰌆 - ".asm": {Icon: "\ue637", Color: "#0091BD"}, //  - ".ass": {Icon: "\U000f0a16", Color: "#FFB713"}, // 󰨖 - ".astro": {Icon: "\ue6b3", Color: "#FF6D00"}, //  - ".avi": {Icon: "\U000f0381", Color: "#FF9800"}, // 󰎁 - ".avif": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".avro": {Icon: "\ue60b", Color: "#965824"}, //  - ".awk": {Icon: "\U000f018d", Color: "#FF7043"}, // 󰆍 - ".azcli": {Icon: "\uebd8", Color: "#2088E5"}, //  - ".bak": {Icon: "\U000f006f", Color: "#6D8086"}, // 󰁯 - ".bash": {Icon: "\uebca", Color: "#FF7043"}, //  - ".bash_history": {Icon: "\ue795", Color: "#8DC149"}, //  - ".bash_profile": {Icon: "\ue795", Color: "#8DC149"}, //  - ".bashrc": {Icon: "\ue795", Color: "#8DC149"}, //  - ".bat": {Icon: "\U000f018d", Color: "#FF7043"}, // 󰆍 - ".bats": {Icon: "\U000f0b5f", Color: "#D2D2D2"}, // 󰭟 - ".bazel": {Icon: "\ue63a", Color: "#44A047"}, //  - ".bib": {Icon: "\U000f1517", Color: "#8BC34A"}, // 󱔗 - ".bicep": {Icon: "\U000f0fd7", Color: "#FBC02D"}, // 󰿗 - ".bicepparam": {Icon: "\ue63b", Color: "#797DAC"}, //  - ".blade.php": {Icon: "\uf2f7", Color: "#FF5252"}, //  - ".blend": {Icon: "\U000f00ab", Color: "#ED8F30"}, // 󰂫 - ".blp": {Icon: "\U000f0ebe", Color: "#458EE6"}, // 󰺾 - ".bmp": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".brep": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".bz": {Icon: "\uf410", Color: "#ECA517"}, //  - ".bz2": {Icon: "\uf410", Color: "#ECA517"}, //  - ".bz3": {Icon: "\uf410", Color: "#ECA517"}, //  - ".bzl": {Icon: "\ue63a", Color: "#44A047"}, //  - ".c": {Icon: "\ue61e", Color: "#0188D1"}, //  - ".c++": {Icon: "\ue61d", Color: "#0188D1"}, //  - ".cab": {Icon: "\ue70f", Color: "#626262"}, //  - ".cache": {Icon: "\uf49b", Color: "#FFFFFF"}, //  - ".cast": {Icon: "\uf03d", Color: "#EA8220"}, //  - ".cbl": {Icon: "\u2699", Color: "#005CA5"}, // ⚙ - ".cc": {Icon: "\ue61d", Color: "#0188D1"}, //  - ".ccm": {Icon: "\ue61d", Color: "#F34B7D"}, //  - ".cfg": {Icon: "\uf013", Color: "#42A5F5"}, //  - ".cjs": {Icon: "\ue60c", Color: "#CBCB41"}, //  - ".class": {Icon: "\uf0f4", Color: "#2088E5"}, //  - ".clj": {Icon: "\ue642", Color: "#2AB6F6"}, //  - ".cljc": {Icon: "\ue642", Color: "#2AB6F6"}, //  - ".cljd": {Icon: "\ue76a", Color: "#519ABA"}, //  - ".cljs": {Icon: "\ue642", Color: "#2AB6F6"}, //  - ".cls": {Icon: "\ue69b", Color: "#4B5163"}, //  - ".cmake": {Icon: "\ue794", Color: "#DCE3EB"}, //  - ".cmd": {Icon: "\uebc4", Color: "#FF7043"}, //  - ".cob": {Icon: "\u2699", Color: "#005CA5"}, // ⚙ - ".cobol": {Icon: "\u2699", Color: "#005CA5"}, // ⚙ - ".coffee": {Icon: "\ue61b", Color: "#6F4E38"}, //  - ".conda": {Icon: "\ue715", Color: "#43B02A"}, //  - ".conf": {Icon: "\uf013", Color: "#696969"}, //  - ".config.ru": {Icon: "\ue791", Color: "#701516"}, //  - ".cp": {Icon: "\ue646", Color: "#0188D1"}, //  - ".cpio": {Icon: "\uf410", Color: "#ECA517"}, //  - ".cpp": {Icon: "\ue61d", Color: "#0188D1"}, //  - ".cppm": {Icon: "\ue61d", Color: "#519ABA"}, //  - ".cpy": {Icon: "\u2699", Color: "#005CA5"}, // ⚙ - ".cr": {Icon: "\ue62f", Color: "#CFD8DD"}, //  - ".crdownload": {Icon: "\uf019", Color: "#44CDA8"}, //  - ".cs": {Icon: "\U000f031b", Color: "#0188D1"}, // 󰌛 - ".csh": {Icon: "\U000f018d", Color: "#FF7043"}, // 󰆍 - ".cshtml": {Icon: "\uf486", Color: "#42A5F5"}, //  - ".cson": {Icon: "\ue61b", Color: "#6F4E38"}, //  - ".csproj": {Icon: "\U000f0610", Color: "#AB48BC"}, // 󰘐 - ".css": {Icon: "\ue749", Color: "#42A5F5"}, //  - ".csv": {Icon: "\U000f021b", Color: "#8BC34A"}, // 󰈛 - ".csx": {Icon: "\U000f031b", Color: "#0188D1"}, // 󰌛 - ".cts": {Icon: "\ue628", Color: "#519ABA"}, //  - ".cu": {Icon: "\ue64b", Color: "#89E051"}, //  - ".cue": {Icon: "\U000f0cb9", Color: "#ED95AE"}, // 󰲹 - ".cuh": {Icon: "\ue64b", Color: "#A074C4"}, //  - ".cxx": {Icon: "\ue646", Color: "#0188D1"}, //  - ".cxxm": {Icon: "\ue61d", Color: "#519ABA"}, //  - ".d": {Icon: "\ue7af", Color: "#B03931"}, //  - ".d.ts": {Icon: "\ue628", Color: "#0188D1"}, //  - ".dart": {Icon: "\ue64c", Color: "#59B6F0"}, //  - ".db": {Icon: "\uf1c0", Color: "#FFCA29"}, //  - ".dconf": {Icon: "\ue706", Color: "#DAD8D8"}, //  - ".deb": {Icon: "\uebc5", Color: "#D80651"}, //  - ".desktop": {Icon: "\uf108", Color: "#56347C"}, //  - ".diff": {Icon: "\uf4d2", Color: "#4262A2"}, //  - ".djvu": {Icon: "\uf02d", Color: "#624262"}, //  - ".dll": {Icon: "\U000f107c", Color: "#42A5F5"}, // 󱁼 - ".doc": {Icon: "\U000f022c", Color: "#0188D1"}, // 󰈬 - ".docx": {Icon: "\U000f022c", Color: "#0188D1"}, // 󰈬 - ".dot": {Icon: "\U000f1049", Color: "#005F87"}, // 󱁉 - ".download": {Icon: "\uf019", Color: "#44CDA8"}, //  - ".drl": {Icon: "\ue28c", Color: "#FFAFAF"}, //  - ".dropbox": {Icon: "\ue707", Color: "#2E63FF"}, //  - ".ds_store": {Icon: "\uf179", Color: "#A2AAAD"}, //  - ".dump": {Icon: "\uf1c0", Color: "#DAD8D8"}, //  - ".dwg": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".dxf": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".ebook": {Icon: "\ue28b", Color: "#EAB16D"}, //  - ".ebuild": {Icon: "\uf30d", Color: "#4C416E"}, //  - ".editorconfig": {Icon: "\ue615", Color: "#626262"}, //  - ".edn": {Icon: "\ue76a", Color: "#519ABA"}, //  - ".eex": {Icon: "\ue62d", Color: "#9575CE"}, //  - ".ejs": {Icon: "\ue618", Color: "#CBCB41"}, //  - ".el": {Icon: "\ue632", Color: "#805EB7"}, //  - ".elc": {Icon: "\ue632", Color: "#805EB7"}, //  - ".elf": {Icon: "\ueae8", Color: "#9F0500"}, //  - ".elm": {Icon: "\ue62c", Color: "#60B6CC"}, //  - ".eln": {Icon: "\ue632", Color: "#8172BE"}, //  - ".env": {Icon: "\uf462", Color: "#FAF743"}, //  - ".eot": {Icon: "\ue659", Color: "#F54436"}, //  - ".epp": {Icon: "\ue631", Color: "#FFA61A"}, //  - ".epub": {Icon: "\ue28b", Color: "#EAB16D"}, //  - ".erb": {Icon: "\U000f0d2d", Color: "#F54436"}, // 󰴭 - ".erl": {Icon: "\uf23f", Color: "#F54436"}, //  - ".ex": {Icon: "\ue62d", Color: "#9575CE"}, //  - ".exe": {Icon: "\uf2d0", Color: "#E64A19"}, //  - ".exs": {Icon: "\ue62d", Color: "#9575CE"}, //  - ".f#": {Icon: "\ue7a7", Color: "#519ABA"}, //  - ".f3d": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".f90": {Icon: "\U000f121a", Color: "#FF7043"}, // 󱈚 - ".fbx": {Icon: "\uea8c", Color: "#2AB6F6"}, //  - ".fcbak": {Icon: "\uf336", Color: "#6D8086"}, //  - ".fcmacro": {Icon: "\uf336", Color: "#CB333B"}, //  - ".fcmat": {Icon: "\uf336", Color: "#CB333B"}, //  - ".fcparam": {Icon: "\uf336", Color: "#CB333B"}, //  - ".fcscript": {Icon: "\uf336", Color: "#CB333B"}, //  - ".fcstd": {Icon: "\uf336", Color: "#CB333B"}, //  - ".fcstd1": {Icon: "\uf336", Color: "#CB333B"}, //  - ".fctb": {Icon: "\uf336", Color: "#CB333B"}, //  - ".fctl": {Icon: "\uf336", Color: "#CB333B"}, //  - ".fdmdownload": {Icon: "\uf019", Color: "#44CDA8"}, //  - ".fish": {Icon: "\U000f023a", Color: "#FF7043"}, // 󰈺 - ".flac": {Icon: "\U000f0386", Color: "#EE534F"}, // 󰎆 - ".flc": {Icon: "\uf031", Color: "#ECECEC"}, //  - ".flf": {Icon: "\uf031", Color: "#ECECEC"}, //  - ".flv": {Icon: "\U000f0381", Color: "#FF9800"}, // 󰎁 - ".fnl": {Icon: "\ue6af", Color: "#FFF3D7"}, //  - ".fodg": {Icon: "\uf379", Color: "#FFFB57"}, //  - ".fodp": {Icon: "\uf37a", Color: "#FE9C45"}, //  - ".fods": {Icon: "\uf378", Color: "#78FC4E"}, //  - ".fodt": {Icon: "\uf37c", Color: "#2DCBFD"}, //  - ".font": {Icon: "\ue659", Color: "#F54436"}, //  - ".fs": {Icon: "\ue7a7", Color: "#31B9DB"}, //  - ".fsi": {Icon: "\ue7a7", Color: "#31B9DB"}, //  - ".fsscript": {Icon: "\ue7a7", Color: "#519ABA"}, //  - ".fsx": {Icon: "\ue7a7", Color: "#31B9DB"}, //  - ".gcode": {Icon: "\U000f0af4", Color: "#505075"}, // 󰫴 - ".gd": {Icon: "\ue65f", Color: "#42A5F5"}, //  - ".gdoc": {Icon: "\uf1c2", Color: "#01D000"}, //  - ".gem": {Icon: "\ue21e", Color: "#C90F02"}, //  - ".gemfile": {Icon: "\ueb48", Color: "#E63936"}, //  - ".gemspec": {Icon: "\ue21e", Color: "#C90F02"}, //  - ".gform": {Icon: "\uf298", Color: "#01D000"}, //  - ".gif": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".git": {Icon: "\U000f02a2", Color: "#EC6B23"}, // 󰊢 - ".glb": {Icon: "\uf1b2", Color: "#FFA61A"}, //  - ".gnumakefile": {Icon: "\ueba2", Color: "#EF5351"}, //  - ".go": {Icon: "\ue627", Color: "#02ACC1"}, //  - ".godot": {Icon: "\ue65f", Color: "#42A5F5"}, //  - ".gpr": {Icon: "\ue6b5", Color: "#22FFFF"}, //  - ".gql": {Icon: "\U000f0877", Color: "#EC417A"}, // 󰡷 - ".gradle": {Icon: "\ue660", Color: "#0397A7"}, //  - ".graphql": {Icon: "\U000f0877", Color: "#EC417A"}, // 󰡷 - ".gresource": {Icon: "\uf362", Color: "#FFFFFF"}, //  - ".groovy": {Icon: "\ue775", Color: "#005F87"}, //  - ".gsheet": {Icon: "\uf1c3", Color: "#97BA6A"}, //  - ".gslides": {Icon: "\uf1c4", Color: "#FFFF00"}, //  - ".guardfile": {Icon: "\ue21e", Color: "#626262"}, //  - ".gv": {Icon: "\U000f1049", Color: "#005F87"}, // 󱁉 - ".gz": {Icon: "\uf410", Color: "#ECA517"}, //  - ".h": {Icon: "\uf0fd", Color: "#A074C4"}, //  - ".haml": {Icon: "\ue664", Color: "#F4521E"}, //  - ".hbs": {Icon: "\U000f15de", Color: "#FF7043"}, // 󱗞 - ".hc": {Icon: "\U000f00a2", Color: "#FAF743"}, // 󰂢 - ".heex": {Icon: "\ue62d", Color: "#9575CE"}, //  - ".hex": {Icon: "\U000f12a7", Color: "#25A79A"}, // 󱊧 - ".hh": {Icon: "\uf0fd", Color: "#A074C4"}, //  - ".hpp": {Icon: "\uf0fd", Color: "#A074C4"}, //  - ".hrl": {Icon: "\ue7b1", Color: "#B83998"}, //  - ".hs": {Icon: "\ue61f", Color: "#FFA726"}, //  - ".htm": {Icon: "\uf13b", Color: "#E44E27"}, //  - ".html": {Icon: "\uf13b", Color: "#E44E27"}, //  - ".huff": {Icon: "\U000f0858", Color: "#CFD8DD"}, // 󰡘 - ".hurl": {Icon: "\uf0ec", Color: "#FF0288"}, //  - ".hx": {Icon: "\ue666", Color: "#F68713"}, //  - ".hxx": {Icon: "\uf0fd", Color: "#A074C4"}, //  - ".ical": {Icon: "\uf073", Color: "#2B9EF3"}, //  - ".icalendar": {Icon: "\uf073", Color: "#2B9EF3"}, //  - ".ico": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".ics": {Icon: "\U000f01ee", Color: "#42A5F5"}, // 󰇮 - ".ifb": {Icon: "\uf073", Color: "#2B9EF3"}, //  - ".ifc": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".ige": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".iges": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".igs": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".image": {Icon: "\uf1c5", Color: "#CBCB41"}, //  - ".img": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".iml": {Icon: "\U000f022e", Color: "#8BC34A"}, // 󰈮 - ".import": {Icon: "\uf0c6", Color: "#ECECEC"}, //  - ".info": {Icon: "\uf129", Color: "#FFF3D7"}, //  - ".ini": {Icon: "\uf013", Color: "#42A5F5"}, //  - ".ino": {Icon: "\uf34b", Color: "#01979D"}, //  - ".ipynb": {Icon: "\ue80f", Color: "#F57D01"}, //  - ".iso": {Icon: "\uede9", Color: "#B1BEC5"}, //  - ".ixx": {Icon: "\ue61d", Color: "#519ABA"}, //  - ".j2c": {Icon: "\uf1c5", Color: "#4B5163"}, //  - ".j2k": {Icon: "\uf1c5", Color: "#4B5163"}, //  - ".jad": {Icon: "\ue256", Color: "#F19210"}, //  - ".jar": {Icon: "\U000f06ca", Color: "#F19210"}, // 󰛊 - ".java": {Icon: "\uf0f4", Color: "#F19210"}, //  - ".jfi": {Icon: "\uf1c5", Color: "#626262"}, //  - ".jfif": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".jif": {Icon: "\uf1c5", Color: "#626262"}, //  - ".jl": {Icon: "\ue624", Color: "#338A23"}, //  - ".jmd": {Icon: "\uf48a", Color: "#519ABA"}, //  - ".jp2": {Icon: "\uf1c5", Color: "#626262"}, //  - ".jpe": {Icon: "\uf1c5", Color: "#626262"}, //  - ".jpeg": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".jpg": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".jpx": {Icon: "\uf1c5", Color: "#626262"}, //  - ".js": {Icon: "\U000f031e", Color: "#FFCA29"}, // 󰌞 - ".json": {Icon: "\ue60b", Color: "#FAA825"}, //  - ".json5": {Icon: "\ue60b", Color: "#FAA825"}, //  - ".jsonc": {Icon: "\ue60b", Color: "#FAA825"}, //  - ".jsx": {Icon: "\ued46", Color: "#FFCA29"}, //  - ".jwmrc": {Icon: "\uf35b", Color: "#007AC2"}, //  - ".jxl": {Icon: "\uf1c5", Color: "#727252"}, //  - ".kbx": {Icon: "\U000f0bc4", Color: "#537662"}, // 󰯄 - ".kdb": {Icon: "\uf23e", Color: "#529B34"}, //  - ".kdbx": {Icon: "\uf23e", Color: "#529B34"}, //  - ".kdenlive": {Icon: "\uf33c", Color: "#83B8F2"}, //  - ".kdenlivetitle": {Icon: "\uf33c", Color: "#83B8F2"}, //  - ".kicad_dru": {Icon: "\uf34c", Color: "#FFFFFF"}, //  - ".kicad_mod": {Icon: "\uf34c", Color: "#FFFFFF"}, //  - ".kicad_pcb": {Icon: "\uf34c", Color: "#FFFFFF"}, //  - ".kicad_prl": {Icon: "\uf34c", Color: "#FFFFFF"}, //  - ".kicad_pro": {Icon: "\uf34c", Color: "#FFFFFF"}, //  - ".kicad_sch": {Icon: "\uf34c", Color: "#FFFFFF"}, //  - ".kicad_sym": {Icon: "\uf34c", Color: "#FFFFFF"}, //  - ".kicad_wks": {Icon: "\uf34c", Color: "#FFFFFF"}, //  - ".ko": {Icon: "\uf17c", Color: "#DDDDDD"}, //  - ".kpp": {Icon: "\uf33d", Color: "#F245FB"}, //  - ".kra": {Icon: "\uf33d", Color: "#F245FB"}, //  - ".krz": {Icon: "\uf33d", Color: "#F245FB"}, //  - ".ksh": {Icon: "\U000f018d", Color: "#FF7043"}, // 󰆍 - ".kt": {Icon: "\ue634", Color: "#1A95D9"}, //  - ".kts": {Icon: "\ue634", Color: "#1A95D9"}, //  - ".latex": {Icon: "\ue69b", Color: "#626262"}, //  - ".lck": {Icon: "\ue672", Color: "#BBBBBB"}, //  - ".leex": {Icon: "\ue62d", Color: "#9575CE"}, //  - ".less": {Icon: "\ued48", Color: "#0277BD"}, //  - ".lff": {Icon: "\uf031", Color: "#ECECEC"}, //  - ".lhs": {Icon: "\ue777", Color: "#A074C4"}, //  - ".license": {Icon: "\U000f0124", Color: "#FFCA29"}, // 󰄤 - ".liquid": {Icon: "\uf043", Color: "#2AB6F6"}, //  - ".localized": {Icon: "\uf179", Color: "#A2AAAD"}, //  - ".lock": {Icon: "\uf023", Color: "#FFD550"}, //  - ".log": {Icon: "\uf0f6", Color: "#ECA517"}, //  - ".lrc": {Icon: "\U000f0a16", Color: "#FFA61A"}, // 󰨖 - ".lua": {Icon: "\ue620", Color: "#42A5F5"}, //  - ".luac": {Icon: "\ue620", Color: "#519ABA"}, //  - ".luau": {Icon: "\ue620", Color: "#519ABA"}, //  - ".lz": {Icon: "\uf410", Color: "#ECA517"}, //  - ".lz4": {Icon: "\uf410", Color: "#ECA517"}, //  - ".lzh": {Icon: "\uf410", Color: "#ECA517"}, //  - ".lzma": {Icon: "\uf410", Color: "#ECA517"}, //  - ".lzo": {Icon: "\uf410", Color: "#ECA517"}, //  - ".m": {Icon: "\ue61e", Color: "#599EFF"}, //  - ".m3u": {Icon: "\U000f0cb9", Color: "#ED95AE"}, // 󰲹 - ".m3u8": {Icon: "\U000f0cb9", Color: "#ED95AE"}, // 󰲹 - ".m4a": {Icon: "\U000f0386", Color: "#EE534F"}, // 󰎆 - ".m4v": {Icon: "\U000f0381", Color: "#FF9800"}, // 󰎁 - ".magnet": {Icon: "\uf076", Color: "#9F0500"}, //  - ".makefile": {Icon: "\ue673", Color: "#FEFEFE"}, //  - ".markdown": {Icon: "\ueb1d", Color: "#42A5F5"}, //  - ".material": {Icon: "\U000f0509", Color: "#B83998"}, // 󰔉 - ".md": {Icon: "\ueb1d", Color: "#42A5F5"}, //  - ".md5": {Icon: "\U000f0565", Color: "#8C86AF"}, // 󰕥 - ".mdx": {Icon: "\ueb1d", Color: "#FFCA29"}, //  - ".mint": {Icon: "\ue7a4", Color: "#44A047"}, //  - ".mjs": {Icon: "\U000f031e", Color: "#FFCA29"}, // 󰌞 - ".mk": {Icon: "\ue795", Color: "#626262"}, //  - ".mkd": {Icon: "\uf48a", Color: "#519ABA"}, //  - ".mkv": {Icon: "\U000f0381", Color: "#FF9800"}, // 󰎁 - ".ml": {Icon: "\ue67a", Color: "#FF9800"}, //  - ".mli": {Icon: "\ue67a", Color: "#FF9800"}, //  - ".mm": {Icon: "\ue61d", Color: "#599EFF"}, //  - ".mo": {Icon: "\U000f05ca", Color: "#7986CB"}, // 󰗊 - ".mobi": {Icon: "\ue28b", Color: "#EAB16D"}, //  - ".mojo": {Icon: "\ue780", Color: "#FF7043"}, //  - ".mov": {Icon: "\U000f0381", Color: "#FF9800"}, // 󰎁 - ".mp3": {Icon: "\U000f0386", Color: "#EE534F"}, // 󰎆 - ".mp4": {Icon: "\U000f0381", Color: "#FF9800"}, // 󰎁 - ".mpp": {Icon: "\ue61d", Color: "#519ABA"}, //  - ".msf": {Icon: "\uf370", Color: "#137BE1"}, //  - ".msi": {Icon: "\uf2d0", Color: "#E64A19"}, //  - ".mts": {Icon: "\ue628", Color: "#519ABA"}, //  - ".mustache": {Icon: "\U000f15de", Color: "#FF7043"}, // 󱗞 - ".nfo": {Icon: "\uf129", Color: "#FFF3D7"}, //  - ".nim": {Icon: "\ue677", Color: "#FFCA29"}, //  - ".nix": {Icon: "\uf313", Color: "#5175C2"}, //  - ".node": {Icon: "\U000f0399", Color: "#E8274B"}, // 󰎙 - ".npmignore": {Icon: "\ue71e", Color: "#E8274B"}, //  - ".nswag": {Icon: "\ue60b", Color: "#85EA2D"}, //  - ".nu": {Icon: "\U000f018d", Color: "#FF7043"}, // 󰆍 - ".o": {Icon: "\uea8c", Color: "#2AB6F6"}, //  - ".obj": {Icon: "\uea8c", Color: "#2AB6F6"}, //  - ".odin": {Icon: "\U000f07e2", Color: "#3882D2"}, // 󰟢 - ".odf": {Icon: "\uf37b", Color: "#FF5A96"}, //  - ".odg": {Icon: "\uf379", Color: "#FFFB57"}, //  - ".odp": {Icon: "\uf37a", Color: "#FE9C45"}, //  - ".ods": {Icon: "\uf378", Color: "#78FC4E"}, //  - ".odt": {Icon: "\uf37c", Color: "#2DCBFD"}, //  - ".ogg": {Icon: "\U000f0381", Color: "#FF9800"}, // 󰎁 - ".ogv": {Icon: "\U000f0381", Color: "#FF9800"}, // 󰎁 - ".opus": {Icon: "\U000f0223", Color: "#EA8220"}, // 󰈣 - ".org": {Icon: "\ue633", Color: "#56B6C2"}, //  - ".otf": {Icon: "\ue659", Color: "#F54436"}, //  - ".out": {Icon: "\ueae8", Color: "#9F0500"}, //  - ".part": {Icon: "\uf43a", Color: "#628262"}, //  - ".patch": {Icon: "\uf440", Color: "#4262A2"}, //  - ".pck": {Icon: "\uf487", Color: "#5D8096"}, //  - ".pdf": {Icon: "\uf1c1", Color: "#EF5351"}, //  - ".php": {Icon: "\U000f031f", Color: "#2088E5"}, // 󰌟 - ".pl": {Icon: "\U000f03d2", Color: "#EF5351"}, // 󰏒 - ".pls": {Icon: "\U000f0cb9", Color: "#ED95AE"}, // 󰲹 - ".ply": {Icon: "\U000f01a7", Color: "#888888"}, // 󰆧 - ".pm": {Icon: "\ue769", Color: "#9575CE"}, //  - ".png": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".po": {Icon: "\U000f05ca", Color: "#7986CB"}, // 󰗊 - ".pot": {Icon: "\U000f05ca", Color: "#7986CB"}, // 󰗊 - ".pp": {Icon: "\ue631", Color: "#FFA61A"}, //  - ".ppt": {Icon: "\U000f0227", Color: "#D14525"}, // 󰈧 - ".pptx": {Icon: "\U000f0227", Color: "#D14525"}, // 󰈧 - ".prisma": {Icon: "\ue684", Color: "#00BFA5"}, //  - ".pro": {Icon: "\U000f03d2", Color: "#EF5351"}, // 󰏒 - ".procfile": {Icon: "\ue607", Color: "#6964BA"}, //  - ".properties": {Icon: "\uf013", Color: "#42A5F5"}, //  - ".ps1": {Icon: "\U000f0a0a", Color: "#04A9F4"}, // 󰨊 - ".psb": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".psd": {Icon: "\ue7b8", Color: "#25A6A0"}, //  - ".psd1": {Icon: "\U000f0a0a", Color: "#04A9F4"}, // 󰨊 - ".psm1": {Icon: "\U000f0a0a", Color: "#04A9F4"}, // 󰨊 - ".pub": {Icon: "\U000f0306", Color: "#25A79A"}, // 󰌆 - ".pxd": {Icon: "\ue606", Color: "#00AFFF"}, //  - ".pxi": {Icon: "\ue606", Color: "#00AFFF"}, //  - ".pxm": {Icon: "\uf1c5", Color: "#626262"}, //  - ".py": {Icon: "\ued1b", Color: "#FED836"}, //  - ".pyc": {Icon: "\ue606", Color: "#FFA61A"}, //  - ".pyd": {Icon: "\ue606", Color: "#E3C58E"}, //  - ".pyi": {Icon: "\ue606", Color: "#FFA61A"}, //  - ".pyo": {Icon: "\ue606", Color: "#E3C58E"}, //  - ".pyw": {Icon: "\ue606", Color: "#00AFFF"}, //  - ".pyx": {Icon: "\ue606", Color: "#00AFFF"}, //  - ".qm": {Icon: "\U000f05ca", Color: "#2596BE"}, // 󰗊 - ".qml": {Icon: "\uf375", Color: "#42CD52"}, //  - ".qrc": {Icon: "\uf375", Color: "#40CD52"}, //  - ".qss": {Icon: "\uf375", Color: "#40CD52"}, //  - ".query": {Icon: "\ue21c", Color: "#90A850"}, //  - ".r": {Icon: "\ue68a", Color: "#1976D3"}, //  - ".rake": {Icon: "\ue791", Color: "#701516"}, //  - ".rakefile": {Icon: "\ue21e", Color: "#C90F02"}, //  - ".rar": {Icon: "\uf410", Color: "#ECA517"}, //  - ".razor": {Icon: "\uf1fa", Color: "#207245"}, //  - ".rb": {Icon: "\U000f0d2d", Color: "#F54436"}, // 󰴭 - ".rdata": {Icon: "\uf25d", Color: "#458EE6"}, //  - ".rdb": {Icon: "\ue76d", Color: "#C90F02"}, //  - ".rdoc": {Icon: "\uf48a", Color: "#519ABA"}, //  - ".rds": {Icon: "\uf25d", Color: "#458EE6"}, //  - ".readme": {Icon: "\uf05a", Color: "#42A5F5"}, //  - ".res": {Icon: "\ue688", Color: "#EF5351"}, //  - ".resi": {Icon: "\ue688", Color: "#FFB300"}, //  - ".rlib": {Icon: "\ue7a8", Color: "#DEA584"}, //  - ".rmd": {Icon: "\ue68a", Color: "#1976D3"}, //  - ".rpm": {Icon: "\ue7bb", Color: "#EE0000"}, //  - ".rproj": {Icon: "\U000f05c6", Color: "#358A5B"}, // 󰗆 - ".rs": {Icon: "\ue68b", Color: "#FF7043"}, //  - ".rspec": {Icon: "\ue21e", Color: "#C90F02"}, //  - ".rspec_parallel": {Icon: "\ue21e", Color: "#C90F02"}, //  - ".rspec_status": {Icon: "\ue21e", Color: "#C90F02"}, //  - ".rss": {Icon: "\uf09e", Color: "#965824"}, //  - ".rtf": {Icon: "\U000f022c", Color: "#0188D1"}, // 󰈬 - ".ru": {Icon: "\ue21e", Color: "#C90F02"}, //  - ".rubydoc": {Icon: "\ue73b", Color: "#C90F02"}, //  - ".s": {Icon: "\ue637", Color: "#0091BD"}, //  - ".sass": {Icon: "\ue603", Color: "#EC417A"}, //  - ".sbt": {Icon: "\ue68d", Color: "#0277BD"}, //  - ".sc": {Icon: "\ue68e", Color: "#F54436"}, //  - ".scad": {Icon: "\uf34e", Color: "#F9D72C"}, //  - ".scala": {Icon: "\ue68e", Color: "#F54436"}, //  - ".scm": {Icon: "\U000f0627", Color: "#F54436"}, // 󰘧 - ".scss": {Icon: "\ue603", Color: "#EC417A"}, //  - ".sh": {Icon: "\U000f018d", Color: "#FF7043"}, // 󰆍 - ".sha1": {Icon: "\U000f0565", Color: "#8C86AF"}, // 󰕥 - ".sha224": {Icon: "\U000f0565", Color: "#8C86AF"}, // 󰕥 - ".sha256": {Icon: "\U000f0565", Color: "#8C86AF"}, // 󰕥 - ".sha384": {Icon: "\U000f0565", Color: "#8C86AF"}, // 󰕥 - ".sha512": {Icon: "\U000f0565", Color: "#8C86AF"}, // 󰕥 - ".shell": {Icon: "\ue795", Color: "#89E051"}, //  - ".sig": {Icon: "\u03bb", Color: "#DC682E"}, // Λ - ".signature": {Icon: "\u03bb", Color: "#DC682E"}, // Λ - ".skp": {Icon: "\uea8c", Color: "#2AB6F6"}, //  - ".sldasm": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".sldprt": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".slim": {Icon: "\ue692", Color: "#F57F19"}, //  - ".sln": {Icon: "\U000f0610", Color: "#AB48BC"}, // 󰘐 - ".slvs": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".sml": {Icon: "\u03bb", Color: "#DC682E"}, // Λ - ".so": {Icon: "\U000f107c", Color: "#42A5F5"}, // 󱁼 - ".sol": {Icon: "\ue656", Color: "#0188D1"}, //  - ".spec.js": {Icon: "\uf499", Color: "#FFCA29"}, //  - ".spec.jsx": {Icon: "\uf499", Color: "#FFCA29"}, //  - ".spec.ts": {Icon: "\uf499", Color: "#519ABA"}, //  - ".spec.tsx": {Icon: "\uf499", Color: "#0188D1"}, //  - ".sql": {Icon: "\uf1c0", Color: "#CFCA99"}, //  - ".sqlite": {Icon: "\uf1c0", Color: "#CFCA99"}, //  - ".sqlite3": {Icon: "\uf1c0", Color: "#CFCA99"}, //  - ".srt": {Icon: "\U000f0a16", Color: "#FFA61A"}, // 󰨖 - ".ssa": {Icon: "\U000f0a16", Color: "#FFA61A"}, // 󰨖 - ".ste": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".step": {Icon: "\U000f0eeb", Color: "#839463"}, // 󰻫 - ".stl": {Icon: "\uea8c", Color: "#2AB6F6"}, //  - ".stp": {Icon: "\uea8c", Color: "#2AB6F6"}, //  - ".strings": {Icon: "\U000f05ca", Color: "#2596BE"}, // 󰗊 - ".sty": {Icon: "\ue69b", Color: "#42A5F5"}, //  - ".styl": {Icon: "\ue759", Color: "#C0CA33"}, //  - ".stylus": {Icon: "\ue600", Color: "#83C837"}, //  - ".sub": {Icon: "\U000f0a16", Color: "#FFA61A"}, // 󰨖 - ".sublime": {Icon: "\ue7aa", Color: "#DC682E"}, //  - ".suo": {Icon: "\U000f0610", Color: "#AB48BC"}, // 󰘐 - ".sv": {Icon: "\U000f035b", Color: "#FF7043"}, // 󰍛 - ".svelte": {Icon: "\ue697", Color: "#FF5821"}, //  - ".svg": {Icon: "\U000f0721", Color: "#FFB300"}, // 󰜡 - ".svh": {Icon: "\U000f035b", Color: "#FF7043"}, // 󰍛 - ".swift": {Icon: "\U000f06e5", Color: "#FE5E2F"}, // 󰛥 - ".t": {Icon: "\ue769", Color: "#519ABA"}, //  - ".tar": {Icon: "\uf410", Color: "#ECA517"}, //  - ".taz": {Icon: "\uf410", Color: "#ECA517"}, //  - ".tbc": {Icon: "\U000f06d3", Color: "#005CA5"}, // 󰛓 - ".tbz": {Icon: "\uf410", Color: "#ECA517"}, //  - ".tbz2": {Icon: "\uf410", Color: "#ECA517"}, //  - ".tcl": {Icon: "\U000f06d3", Color: "#EF5351"}, // 󰛓 - ".templ": {Icon: "\U000f05c0", Color: "#FFD550"}, // 󰗀 - ".terminal": {Icon: "\uf489", Color: "#14BA19"}, //  - ".test.js": {Icon: "\uf499", Color: "#FFCA29"}, //  - ".test.jsx": {Icon: "\uf499", Color: "#FFCA29"}, //  - ".test.ts": {Icon: "\uf499", Color: "#519ABA"}, //  - ".test.tsx": {Icon: "\uf499", Color: "#0188D1"}, //  - ".tex": {Icon: "\ue69b", Color: "#42A5F5"}, //  - ".tf": {Icon: "\ue69a", Color: "#5D6BC0"}, //  - ".tfvars": {Icon: "\ue69a", Color: "#5D6BC0"}, //  - ".tgz": {Icon: "\uf410", Color: "#ECA517"}, //  - ".tiff": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".tlz": {Icon: "\uf410", Color: "#ECA517"}, //  - ".tmux": {Icon: "\uebc8", Color: "#14BA19"}, //  - ".toml": {Icon: "\ue6b2", Color: "#9C4221"}, //  - ".torrent": {Icon: "\ue275", Color: "#4C90E8"}, //  - ".tres": {Icon: "\ue65f", Color: "#42A5F5"}, //  - ".ts": {Icon: "\U000f06e6", Color: "#0188D1"}, // 󰛦 - ".tscn": {Icon: "\ue65f", Color: "#42A5F5"}, //  - ".tsconfig": {Icon: "\ue772", Color: "#EA8220"}, //  - ".tsv": {Icon: "\U000f021b", Color: "#8BC34A"}, // 󰈛 - ".tsx": {Icon: "\ued46", Color: "#04BCD4"}, //  - ".ttf": {Icon: "\ue659", Color: "#F54436"}, //  - ".twig": {Icon: "\ue61c", Color: "#9BB92F"}, //  - ".txt": {Icon: "\U000f0219", Color: "#42A5F5"}, // 󰈙 - ".txz": {Icon: "\uf410", Color: "#ECA517"}, //  - ".typ": {Icon: "\uf37f", Color: "#0DBCC0"}, //  - ".typoscript": {Icon: "\ue772", Color: "#EA8220"}, //  - ".tz": {Icon: "\uf410", Color: "#ECA517"}, //  - ".tzo": {Icon: "\uf410", Color: "#ECA517"}, //  - ".ui": {Icon: "\uf2d0", Color: "#015BF0"}, //  - ".v": {Icon: "\ue6ac", Color: "#009CE5"}, //  - ".vala": {Icon: "\ue8d1", Color: "#7B3DB9"}, //  - ".vh": {Icon: "\U000f035b", Color: "#009900"}, // 󰍛 - ".vhd": {Icon: "\U000f035b", Color: "#FF7043"}, // 󰍛 - ".vhdl": {Icon: "\U000f035b", Color: "#009900"}, // 󰍛 - ".video": {Icon: "\uf03d", Color: "#626262"}, //  - ".vi": {Icon: "\ue81e", Color: "#FEC60A"}, //  - ".vim": {Icon: "\ue62b", Color: "#44A047"}, //  - ".vsh": {Icon: "\ue6ac", Color: "#5D87BF"}, //  - ".vsix": {Icon: "\U000f0a1e", Color: "#2296F3"}, // 󰨞 - ".vue": {Icon: "\ue6a0", Color: "#40B883"}, //  - ".war": {Icon: "\ue256", Color: "#F54436"}, //  - ".wasm": {Icon: "\ue6a1", Color: "#7D4DFF"}, //  - ".wav": {Icon: "\U000f0386", Color: "#76B900"}, // 󰎆 - ".webm": {Icon: "\U000f0381", Color: "#FF9800"}, // 󰎁 - ".webmanifest": {Icon: "\ue60b", Color: "#CBCB41"}, //  - ".webp": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 - ".webpack": {Icon: "\U000f072b", Color: "#519ABA"}, // 󰜫 - ".windows": {Icon: "\uf17a", Color: "#00A4EF"}, //  - ".wma": {Icon: "\U000f0386", Color: "#EE534F"}, // 󰎆 - ".woff": {Icon: "\ue659", Color: "#F54436"}, //  - ".woff2": {Icon: "\ue659", Color: "#F54436"}, //  - ".wrl": {Icon: "\U000f01a7", Color: "#778899"}, // 󰆧 - ".wrz": {Icon: "\U000f01a7", Color: "#778899"}, // 󰆧 - ".wv": {Icon: "\uf001", Color: "#00AFFF"}, //  - ".wvc": {Icon: "\uf001", Color: "#00AFFF"}, //  - ".x": {Icon: "\ue691", Color: "#599EFF"}, //  - ".xaml": {Icon: "\U000f0673", Color: "#42A5F5"}, // 󰙳 - ".xcf": {Icon: "\uf338", Color: "#635b46"}, //  - ".xcplayground": {Icon: "\ue755", Color: "#DC682E"}, //  - ".xcstrings": {Icon: "\U000f05ca", Color: "#2596BE"}, // 󰗊 - ".xhtml": {Icon: "\uf13b", Color: "#E44E27"}, //  - ".xls": {Icon: "\U000f021b", Color: "#8BC34A"}, // 󰈛 - ".xlsx": {Icon: "\U000f021b", Color: "#8BC34A"}, // 󰈛 - ".xm": {Icon: "\ue691", Color: "#519ABA"}, //  - ".xml": {Icon: "\U000f022e", Color: "#8BC34A"}, // 󰈮 - ".xpi": {Icon: "\ueae6", Color: "#375A8E"}, //  - ".xul": {Icon: "\uf121", Color: "#DC682E"}, //  - ".xz": {Icon: "\uf410", Color: "#ECA517"}, //  - ".yaml": {Icon: "\ue6a8", Color: "#a074b3"}, //  - ".yml": {Icon: "\ue6a8", Color: "#a074b3"}, //  - ".zig": {Icon: "\ue6a9", Color: "#FAA825"}, //  - ".zip": {Icon: "\uf410", Color: "#ECA517"}, //  - ".zsh": {Icon: "\U000f018d", Color: "#FF7043"}, // 󰆍 - ".zsh-theme": {Icon: "\ue795", Color: "#89E051"}, //  - ".zshrc": {Icon: "\ue795", Color: "#89E051"}, //  - ".zst": {Icon: "\uf410", Color: "#ECA517"}, //  -} - -func patchFileIconsForNerdFontsV2() { - extIconMap[".cs"] = IconProperties{Icon: "\uf81a", Color: "#FEDECA"} //  - extIconMap[".csproj"] = IconProperties{Icon: "\uf81a", Color: "#AB48BC"} //  - extIconMap[".csx"] = IconProperties{Icon: "\uf81a", Color: "#0188D1"} //  - extIconMap[".license"] = IconProperties{Icon: "\uf718", Color: "#626262"} //  - extIconMap[".node"] = IconProperties{Icon: "\uf898", Color: "#E8274B"} //  - extIconMap[".rtf"] = IconProperties{Icon: "\uf718", Color: "#626262"} //  - extIconMap[".vue"] = IconProperties{Icon: "\ufd42", Color: "#89e051"} // ﵂ -} - -func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool, customIconsConfig *config.CustomIconsConfig) IconProperties { - base := filepath.Base(name) - if icon, ok := customIconsConfig.Filenames[base]; ok { - return IconProperties{Color: icon.Color, Icon: icon.Icon} - } - if icon, ok := nameIconMap[base]; ok { - return icon - } - - ext := strings.ToLower(filepath.Ext(name)) - if icon, ok := customIconsConfig.Extensions[ext]; ok { - return IconProperties{Color: icon.Color, Icon: icon.Icon} - } - if icon, ok := extIconMap[ext]; ok { - return icon - } - - if isSubmodule { - return DEFAULT_SUBMODULE_ICON - } else if isLinkedWorktree { - return IconProperties{LINKED_WORKTREE_ICON, "#4E4E4E"} - } else if isDirectory { - return DEFAULT_DIRECTORY_ICON - } - return DEFAULT_FILE_ICON -} diff --git a/pkg/gui/presentation/icons/file_icons_test.go b/pkg/gui/presentation/icons/file_icons_test.go deleted file mode 100644 index 030dfb020c9..00000000000 --- a/pkg/gui/presentation/icons/file_icons_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package icons - -import ( - "testing" -) - -func TestFileIcons(t *testing.T) { - t.Run("TestFileIcons", func(t *testing.T) { - for name, icon := range nameIconMap { - if len([]rune(icon.Icon)) != 1 { - t.Errorf("nameIconMap[\"%s\"] is not a single rune", name) - } - } - - for ext, icon := range extIconMap { - if len([]rune(icon.Icon)) != 1 { - t.Errorf("extIconMap[\"%s\"] is not a single rune", ext) - } - } - }) -} diff --git a/pkg/gui/presentation/icons/git_icons.go b/pkg/gui/presentation/icons/git_icons.go deleted file mode 100644 index 23b7d878715..00000000000 --- a/pkg/gui/presentation/icons/git_icons.go +++ /dev/null @@ -1,91 +0,0 @@ -package icons - -import ( - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" -) - -var ( - BRANCH_ICON = "\U000f062c" // 󰘬 - DETACHED_HEAD_ICON = "\ue729" //  - TAG_ICON = "\uf02b" //  - COMMIT_ICON = "\U000f0718" // 󰜘 - MERGE_COMMIT_ICON = "\U000f062d" // 󰘭 - DEFAULT_REMOTE_ICON = "\U000f02a2" // 󰊢 - STASH_ICON = "\uf01c" //  - LINKED_WORKTREE_ICON = "\U000f0339" // 󰌹 - MISSING_LINKED_WORKTREE_ICON = "\U000f033a" // 󰌺 -) - -var remoteIcons = map[string]string{ - "github.com": "\ue709", //  - "bitbucket.org": "\ue703", //  - "gitlab.com": "\uf296", //  - "dev.azure.com": "\U000f0805", // 󰠅 - "codeberg.org": "\uf330", //  - "git.FreeBSD.org": "\uf30c", //  - "gitlab.archlinux.org": "\uf303", //  - "gitlab.freedesktop.org": "\uf360", //  - "gitlab.gnome.org": "\uf361", //  - "gnu.org": "\ue779", //  - "invent.kde.org": "\uf373", //  - "kernel.org": "\uf31a", //  - "salsa.debian.org": "\uf306", //  - "sr.ht": "\uf1db", //  -} - -func patchGitIconsForNerdFontsV2() { - BRANCH_ICON = "\ufb2b" // שׂ - COMMIT_ICON = "\ufc16" // ﰖ - MERGE_COMMIT_ICON = "\ufb2c" // שּׁ - DEFAULT_REMOTE_ICON = "\uf7a1" //  - LINKED_WORKTREE_ICON = "\uf838" //  - MISSING_LINKED_WORKTREE_ICON = "\uf839" //  - - remoteIcons["dev.azure.com"] = "\ufd03" // ﴃ -} - -func IconForBranch(branch *models.Branch) string { - if branch.DetachedHead { - return DETACHED_HEAD_ICON - } - return BRANCH_ICON -} - -func IconForRemoteBranch(branch *models.RemoteBranch) string { - return BRANCH_ICON -} - -func IconForTag(tag *models.Tag) string { - return TAG_ICON -} - -func IconForCommit(commit *models.Commit) string { - if commit.IsMerge() { - return MERGE_COMMIT_ICON - } - return COMMIT_ICON -} - -func IconForRemote(remote *models.Remote) string { - for domain, icon := range remoteIcons { - for _, url := range remote.Urls { - if strings.Contains(url, domain) { - return icon - } - } - } - return DEFAULT_REMOTE_ICON -} - -func IconForStash(stash *models.StashEntry) string { - return STASH_ICON -} - -func IconForWorktree(missing bool) string { - if missing { - return MISSING_LINKED_WORKTREE_ICON - } - return LINKED_WORKTREE_ICON -} diff --git a/pkg/gui/presentation/icons/icons.go b/pkg/gui/presentation/icons/icons.go deleted file mode 100644 index 6175fbd3b9f..00000000000 --- a/pkg/gui/presentation/icons/icons.go +++ /dev/null @@ -1,35 +0,0 @@ -package icons - -import ( - "log" - - "github.com/samber/lo" -) - -type IconProperties struct { - Icon string - Color string -} - -var isIconEnabled = false - -func IsIconEnabled() bool { - return isIconEnabled -} - -func SetNerdFontsVersion(version string) { - if version == "" { - isIconEnabled = false - } else { - if !lo.Contains([]string{"2", "3"}, version) { - log.Fatalf("Unsupported nerdFontVersion %s", version) - } - - if version == "2" { - patchGitIconsForNerdFontsV2() - patchFileIconsForNerdFontsV2() - } - - isIconEnabled = true - } -} diff --git a/pkg/gui/presentation/item_operations.go b/pkg/gui/presentation/item_operations.go deleted file mode 100644 index b3ebaf8df5f..00000000000 --- a/pkg/gui/presentation/item_operations.go +++ /dev/null @@ -1,27 +0,0 @@ -package presentation - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/i18n" -) - -func ItemOperationToString(itemOperation types.ItemOperation, tr *i18n.TranslationSet) string { - switch itemOperation { - case types.ItemOperationNone: - return "" - case types.ItemOperationPushing: - return tr.PushingStatus - case types.ItemOperationPulling: - return tr.PullingStatus - case types.ItemOperationFastForwarding: - return tr.FastForwarding - case types.ItemOperationDeleting: - return tr.DeletingStatus - case types.ItemOperationFetching: - return tr.FetchingStatus - case types.ItemOperationCheckingOut: - return tr.CheckingOutStatus - } - - return "" -} diff --git a/pkg/gui/presentation/loader.go b/pkg/gui/presentation/loader.go deleted file mode 100644 index 23ab3433ee5..00000000000 --- a/pkg/gui/presentation/loader.go +++ /dev/null @@ -1,14 +0,0 @@ -package presentation - -import ( - "time" - - "github.com/jesseduffield/lazygit/pkg/config" -) - -// Loader dumps a string to be displayed as a loader -func Loader(now time.Time, config config.SpinnerConfig) string { - milliseconds := now.UnixMilli() - index := milliseconds / int64(config.Rate) % int64(len(config.Frames)) - return config.Frames[index] -} diff --git a/pkg/gui/presentation/reflog_commits.go b/pkg/gui/presentation/reflog_commits.go deleted file mode 100644 index b84a10e594c..00000000000 --- a/pkg/gui/presentation/reflog_commits.go +++ /dev/null @@ -1,83 +0,0 @@ -package presentation - -import ( - "time" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/kyokomi/emoji/v2" - "github.com/samber/lo" -) - -func GetReflogCommitListDisplayStrings(commits []*models.Commit, fullDescription bool, cherryPickedCommitHashSet *set.Set[string], diffName string, now time.Time, timeFormat string, shortTimeFormat string, parseEmoji bool) [][]string { - var displayFunc func(*models.Commit, reflogCommitDisplayAttributes) []string - if fullDescription { - displayFunc = getFullDescriptionDisplayStringsForReflogCommit - } else { - displayFunc = getDisplayStringsForReflogCommit - } - - return lo.Map(commits, func(commit *models.Commit, _ int) []string { - diffed := commit.Hash() == diffName - cherryPicked := cherryPickedCommitHashSet.Includes(commit.Hash()) - return displayFunc(commit, - reflogCommitDisplayAttributes{ - cherryPicked: cherryPicked, - diffed: diffed, - parseEmoji: parseEmoji, - timeFormat: timeFormat, - shortTimeFormat: shortTimeFormat, - now: now, - }) - }) -} - -func reflogHashColor(cherryPicked, diffed bool) style.TextStyle { - if diffed { - return theme.DiffTerminalColor - } - - hashColor := style.FgBlue - if cherryPicked { - hashColor = theme.CherryPickedCommitTextStyle - } - - return hashColor -} - -type reflogCommitDisplayAttributes struct { - cherryPicked bool - diffed bool - parseEmoji bool - timeFormat string - shortTimeFormat string - now time.Time -} - -func getFullDescriptionDisplayStringsForReflogCommit(c *models.Commit, attrs reflogCommitDisplayAttributes) []string { - name := c.Name - if attrs.parseEmoji { - name = emoji.Sprint(name) - } - - return []string{ - reflogHashColor(attrs.cherryPicked, attrs.diffed).Sprint(c.ShortHash()), - style.FgMagenta.Sprint(utils.UnixToDateSmart(attrs.now, c.UnixTimestamp, attrs.timeFormat, attrs.shortTimeFormat)), - theme.DefaultTextColor.Sprint(name), - } -} - -func getDisplayStringsForReflogCommit(c *models.Commit, attrs reflogCommitDisplayAttributes) []string { - name := c.Name - if attrs.parseEmoji { - name = emoji.Sprint(name) - } - - return []string{ - reflogHashColor(attrs.cherryPicked, attrs.diffed).Sprint(c.ShortHash()), - theme.DefaultTextColor.Sprint(name), - } -} diff --git a/pkg/gui/presentation/remote_branches.go b/pkg/gui/presentation/remote_branches.go deleted file mode 100644 index 55e4bc1137e..00000000000 --- a/pkg/gui/presentation/remote_branches.go +++ /dev/null @@ -1,30 +0,0 @@ -package presentation - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/samber/lo" -) - -func GetRemoteBranchListDisplayStrings(branches []*models.RemoteBranch, diffName string) [][]string { - return lo.Map(branches, func(branch *models.RemoteBranch, _ int) []string { - diffed := branch.FullName() == diffName - return getRemoteBranchDisplayStrings(branch, diffed) - }) -} - -// getRemoteBranchDisplayStrings returns the display string of branch -func getRemoteBranchDisplayStrings(b *models.RemoteBranch, diffed bool) []string { - textStyle := GetBranchTextStyle(b.Name) - if diffed { - textStyle = theme.DiffTerminalColor - } - - res := make([]string, 0, 2) - if icons.IsIconEnabled() { - res = append(res, textStyle.Sprint(icons.IconForRemoteBranch(b))) - } - res = append(res, textStyle.Sprint(b.Name)) - return res -} diff --git a/pkg/gui/presentation/remotes.go b/pkg/gui/presentation/remotes.go deleted file mode 100644 index 4cb9e00abac..00000000000 --- a/pkg/gui/presentation/remotes.go +++ /dev/null @@ -1,55 +0,0 @@ -package presentation - -import ( - "time" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/samber/lo" -) - -func GetRemoteListDisplayStrings( - remotes []*models.Remote, - diffName string, - getItemOperation func(item types.HasUrn) types.ItemOperation, - tr *i18n.TranslationSet, - userConfig *config.UserConfig, -) [][]string { - return lo.Map(remotes, func(remote *models.Remote, _ int) []string { - diffed := remote.Name == diffName - return getRemoteDisplayStrings(remote, diffed, getItemOperation(remote), tr, userConfig) - }) -} - -// getRemoteDisplayStrings returns the display string of branch -func getRemoteDisplayStrings( - r *models.Remote, - diffed bool, - itemOperation types.ItemOperation, - tr *i18n.TranslationSet, - userConfig *config.UserConfig, -) []string { - branchCount := len(r.Branches) - - textStyle := theme.DefaultTextColor - if diffed { - textStyle = theme.DiffTerminalColor - } - - res := make([]string, 0, 3) - if icons.IsIconEnabled() { - res = append(res, textStyle.Sprint(icons.IconForRemote(r))) - } - descriptionStr := style.FgBlue.Sprintf("%d branches", branchCount) - itemOperationStr := ItemOperationToString(itemOperation, tr) - if itemOperationStr != "" { - descriptionStr += " " + style.FgCyan.Sprint(itemOperationStr+" "+Loader(time.Now(), userConfig.Gui.Spinner)) - } - res = append(res, textStyle.Sprint(r.Name), descriptionStr) - return res -} diff --git a/pkg/gui/presentation/stash_entries.go b/pkg/gui/presentation/stash_entries.go deleted file mode 100644 index c4a1a4de167..00000000000 --- a/pkg/gui/presentation/stash_entries.go +++ /dev/null @@ -1,34 +0,0 @@ -package presentation - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/samber/lo" -) - -func GetStashEntryListDisplayStrings(stashEntries []*models.StashEntry, diffName string) [][]string { - return lo.Map(stashEntries, func(stashEntry *models.StashEntry, _ int) []string { - diffed := stashEntry.RefName() == diffName - return getStashEntryDisplayStrings(stashEntry, diffed) - }) -} - -// getStashEntryDisplayStrings returns the display string of branch -func getStashEntryDisplayStrings(s *models.StashEntry, diffed bool) []string { - textStyle := theme.DefaultTextColor - if diffed { - textStyle = theme.DiffTerminalColor - } - - res := make([]string, 0, 3) - res = append(res, style.FgCyan.Sprint(s.Recency)) - - if icons.IsIconEnabled() { - res = append(res, textStyle.Sprint(icons.IconForStash(s))) - } - - res = append(res, textStyle.Sprint(s.Name)) - return res -} diff --git a/pkg/gui/presentation/status.go b/pkg/gui/presentation/status.go deleted file mode 100644 index b636df6471d..00000000000 --- a/pkg/gui/presentation/status.go +++ /dev/null @@ -1,49 +0,0 @@ -package presentation - -import ( - "fmt" - "time" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/i18n" -) - -func FormatStatus( - repoName string, - currentBranch *models.Branch, - itemOperation types.ItemOperation, - linkedWorktreeName string, - workingTreeState models.WorkingTreeState, - tr *i18n.TranslationSet, - userConfig *config.UserConfig, -) string { - status := "" - - if currentBranch.IsRealBranch() { - status += BranchStatus(currentBranch, itemOperation, tr, time.Now(), userConfig) - if status != "" { - status += " " - } - } - - if workingTreeState.Any() { - status += style.FgYellow.Sprintf("(%s) ", workingTreeState.LowerCaseTitle(tr)) - } - - name := GetBranchTextStyle(currentBranch.Name).Sprint(currentBranch.Name) - // If the user is in a linked worktree (i.e. not the main worktree) we'll display that - if linkedWorktreeName != "" { - icon := "" - if icons.IsIconEnabled() { - icon = icons.LINKED_WORKTREE_ICON + " " - } - repoName = fmt.Sprintf("%s(%s%s)", repoName, icon, style.FgCyan.Sprint(linkedWorktreeName)) - } - status += fmt.Sprintf("%s → %s", repoName, name) - - return status -} diff --git a/pkg/gui/presentation/submodules.go b/pkg/gui/presentation/submodules.go deleted file mode 100644 index 72c6bfc081a..00000000000 --- a/pkg/gui/presentation/submodules.go +++ /dev/null @@ -1,27 +0,0 @@ -package presentation - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/samber/lo" -) - -func GetSubmoduleListDisplayStrings(submodules []*models.SubmoduleConfig) [][]string { - return lo.Map(submodules, func(submodule *models.SubmoduleConfig, _ int) []string { - return getSubmoduleDisplayStrings(submodule) - }) -} - -func getSubmoduleDisplayStrings(s *models.SubmoduleConfig) []string { - name := s.Name - if s.ParentModule != nil { - indentation := "" - for p := s.ParentModule; p != nil; p = p.ParentModule { - indentation += " " - } - - name = indentation + "- " + s.Name - } - - return []string{theme.DefaultTextColor.Sprint(name)} -} diff --git a/pkg/gui/presentation/suggestions.go b/pkg/gui/presentation/suggestions.go deleted file mode 100644 index 429ce6b5b00..00000000000 --- a/pkg/gui/presentation/suggestions.go +++ /dev/null @@ -1,16 +0,0 @@ -package presentation - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -func GetSuggestionListDisplayStrings(suggestions []*types.Suggestion) [][]string { - return lo.Map(suggestions, func(suggestion *types.Suggestion, _ int) []string { - return getSuggestionDisplayStrings(suggestion) - }) -} - -func getSuggestionDisplayStrings(suggestion *types.Suggestion) []string { - return []string{suggestion.Label} -} diff --git a/pkg/gui/presentation/tags.go b/pkg/gui/presentation/tags.go deleted file mode 100644 index e626c731522..00000000000 --- a/pkg/gui/presentation/tags.go +++ /dev/null @@ -1,53 +0,0 @@ -package presentation - -import ( - "time" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/samber/lo" -) - -func GetTagListDisplayStrings( - tags []*models.Tag, - getItemOperation func(item types.HasUrn) types.ItemOperation, - diffName string, - tr *i18n.TranslationSet, - userConfig *config.UserConfig, -) [][]string { - return lo.Map(tags, func(tag *models.Tag, _ int) []string { - diffed := tag.Name == diffName - return getTagDisplayStrings(tag, getItemOperation(tag), diffed, tr, userConfig) - }) -} - -// getTagDisplayStrings returns the display string of branch -func getTagDisplayStrings( - t *models.Tag, - itemOperation types.ItemOperation, - diffed bool, - tr *i18n.TranslationSet, - userConfig *config.UserConfig, -) []string { - textStyle := theme.DefaultTextColor - if diffed { - textStyle = theme.DiffTerminalColor - } - res := make([]string, 0, 2) - if icons.IsIconEnabled() { - res = append(res, textStyle.Sprint(icons.IconForTag(t))) - } - descriptionColor := style.FgYellow - descriptionStr := descriptionColor.Sprint(t.Description()) - itemOperationStr := ItemOperationToString(itemOperation, tr) - if itemOperationStr != "" { - descriptionStr = style.FgCyan.Sprint(itemOperationStr+" "+Loader(time.Now(), userConfig.Gui.Spinner)) + " " + descriptionStr - } - res = append(res, textStyle.Sprint(t.Name), descriptionStr) - return res -} diff --git a/pkg/gui/presentation/worktrees.go b/pkg/gui/presentation/worktrees.go deleted file mode 100644 index d2796af8380..00000000000 --- a/pkg/gui/presentation/worktrees.go +++ /dev/null @@ -1,51 +0,0 @@ -package presentation - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/samber/lo" -) - -func GetWorktreeDisplayStrings(tr *i18n.TranslationSet, worktrees []*models.Worktree) [][]string { - return lo.Map(worktrees, func(worktree *models.Worktree, _ int) []string { - return GetWorktreeDisplayString( - tr, - worktree) - }) -} - -func GetWorktreeDisplayString(tr *i18n.TranslationSet, worktree *models.Worktree) []string { - textStyle := theme.DefaultTextColor - - current := "" - currentColor := style.FgCyan - if worktree.IsCurrent { - current = " *" - currentColor = style.FgGreen - } - - icon := icons.IconForWorktree(false) - if worktree.IsPathMissing { - textStyle = style.FgRed - icon = icons.IconForWorktree(true) - } - - res := []string{} - res = append(res, currentColor.Sprint(current)) - if icons.IsIconEnabled() { - res = append(res, textStyle.Sprint(icon)) - } - - name := worktree.Name - if worktree.IsMain { - name += " " + tr.MainWorktree - } - if worktree.IsPathMissing && !icons.IsIconEnabled() { - name += " " + tr.MissingWorktree - } - res = append(res, textStyle.Sprint(name)) - return res -} diff --git a/pkg/gui/pty.go b/pkg/gui/pty.go deleted file mode 100644 index 688240dff32..00000000000 --- a/pkg/gui/pty.go +++ /dev/null @@ -1,120 +0,0 @@ -//go:build !windows - -package gui - -import ( - "io" - "os" - "os/exec" - "strings" - - "github.com/creack/pty" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -func (gui *Gui) desiredPtySize(view *gocui.View) *pty.Winsize { - width, height := view.InnerSize() - - return &pty.Winsize{Cols: uint16(width), Rows: uint16(height)} -} - -func (gui *Gui) onResize() error { - gui.Mutexes.PtyMutex.Lock() - defer gui.Mutexes.PtyMutex.Unlock() - - for viewName, ptmx := range gui.viewPtmxMap { - // TODO: handle resizing properly: we need to actually clear the main view - // and re-read the output from our pty. Or we could just re-run the original - // command from scratch - view, _ := gui.g.View(viewName) - if err := pty.Setsize(ptmx, gui.desiredPtySize(view)); err != nil { - return utils.WrapError(err) - } - } - - return nil -} - -// Some commands need to output for a terminal to active certain behaviour. -// For example, git won't invoke the GIT_PAGER env var unless it thinks it's -// talking to a terminal. We typically write cmd outputs straight to a view, -// which is just an io.Reader. the pty package lets us wrap a command in a -// pseudo-terminal meaning we'll get the behaviour we want from the underlying -// command. -func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error { - width := view.InnerWidth() - pager := gui.stateAccessor.GetPagerConfig().GetPagerCommand(width) - externalDiffCommand := gui.stateAccessor.GetPagerConfig().GetExternalDiffCommand() - - if pager == "" && externalDiffCommand == "" { - // if we're not using a custom pager we don't need to use a pty - return gui.newCmdTask(view, cmd, prefix) - } - - // Run the pty after layout so that it gets the correct size - gui.afterLayout(func() error { - // Need to get the width and the pager again because the layout might have - // changed the size of the view - width = view.InnerWidth() - pager := gui.stateAccessor.GetPagerConfig().GetPagerCommand(width) - - cmdStr := strings.Join(cmd.Args, " ") - - // This communicates to pagers that we're in a very simple - // terminal that they should not expect to have much capabilities. - // Moving the cursor, clearing the screen, or querying for colors are among such "advanced" capabilities. - // Context: https://github.com/jesseduffield/lazygit/issues/3419 - cmd.Env = removeExistingTermEnvVars(cmd.Env) - cmd.Env = append(cmd.Env, "TERM=dumb") - - cmd.Env = append(cmd.Env, "GIT_PAGER="+pager) - - manager := gui.getManager(view) - - var ptmx *os.File - start := func() (*exec.Cmd, io.Reader) { - var err error - ptmx, err = pty.StartWithSize(cmd, gui.desiredPtySize(view)) - if err != nil { - gui.c.Log.Error(err) - } - - gui.Mutexes.PtyMutex.Lock() - gui.viewPtmxMap[view.Name()] = ptmx - gui.Mutexes.PtyMutex.Unlock() - - return cmd, ptmx - } - - onClose := func() { - gui.Mutexes.PtyMutex.Lock() - ptmx.Close() - delete(gui.viewPtmxMap, view.Name()) - gui.Mutexes.PtyMutex.Unlock() - } - - linesToRead := gui.linesToReadFromCmdTask(view) - return manager.NewTask(manager.NewCmdTask(start, prefix, linesToRead, onClose), cmdStr) - }) - - return nil -} - -func removeExistingTermEnvVars(env []string) []string { - return lo.Filter(env, func(envVar string, _ int) bool { - return !isTermEnvVar(envVar) - }) -} - -// Terminals set a variety of different environment variables -// to identify themselves to processes. This list should catch the most common among them. -func isTermEnvVar(envVar string) bool { - return strings.HasPrefix(envVar, "TERM=") || - strings.HasPrefix(envVar, "TERM_PROGRAM=") || - strings.HasPrefix(envVar, "TERM_PROGRAM_VERSION=") || - strings.HasPrefix(envVar, "TERMINAL_EMULATOR=") || - strings.HasPrefix(envVar, "TERMINAL_NAME=") || - strings.HasPrefix(envVar, "TERMINAL_VERSION_") -} diff --git a/pkg/gui/pty_windows.go b/pkg/gui/pty_windows.go deleted file mode 100644 index 39577a19931..00000000000 --- a/pkg/gui/pty_windows.go +++ /dev/null @@ -1,17 +0,0 @@ -package gui - -import ( - "fmt" - "os/exec" - - "github.com/jesseduffield/gocui" -) - -func (gui *Gui) onResize() error { - return nil -} - -func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error { - cmd.Env = append(cmd.Env, fmt.Sprintf("LAZYGIT_COLUMNS=%d", view.InnerWidth())) - return gui.newCmdTask(view, cmd, prefix) -} diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go deleted file mode 100644 index ba6bc8ce133..00000000000 --- a/pkg/gui/recent_repos_panel.go +++ /dev/null @@ -1,43 +0,0 @@ -package gui - -import ( - "os" - "path/filepath" -) - -// updateRecentRepoList registers the fact that we opened lazygit in this repo, -// so that we can open the same repo via the 'recent repos' menu -func (gui *Gui) updateRecentRepoList() error { - if gui.git.Status.IsBareRepo() { - // we could totally do this but it would require storing both the git-dir and the - // worktree in our recent repos list, which is a change that would need to be - // backwards compatible - gui.c.Log.Info("Not appending bare repo to recent repo list") - return nil - } - - recentRepos := gui.c.GetAppState().RecentRepos - currentRepo, err := os.Getwd() - if err != nil { - return err - } - recentRepos = newRecentReposList(recentRepos, currentRepo) - // TODO: migrate this file to use forward slashes on all OSes for consistency - // (windows uses backslashes at the moment) - gui.c.GetAppState().RecentRepos = recentRepos - return gui.c.SaveAppState() -} - -// newRecentReposList returns a new repo list with a new entry but only when it doesn't exist yet -func newRecentReposList(recentRepos []string, currentRepo string) []string { - newRepos := []string{currentRepo} - for _, repo := range recentRepos { - if repo != currentRepo { - if _, err := os.Stat(filepath.Join(repo, ".git")); err != nil { - continue - } - newRepos = append(newRepos, repo) - } - } - return newRepos -} diff --git a/pkg/gui/services/custom_commands/client.go b/pkg/gui/services/custom_commands/client.go deleted file mode 100644 index 12d8c78622f..00000000000 --- a/pkg/gui/services/custom_commands/client.go +++ /dev/null @@ -1,121 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/samber/lo" -) - -// Client is the entry point to this package. It returns a list of keybindings based on the config's user-defined custom commands. -// See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Command_Keybindings.md for more info. -type Client struct { - c *helpers.HelperCommon - handlerCreator *HandlerCreator - keybindingCreator *KeybindingCreator -} - -func NewClient( - c *helpers.HelperCommon, - helpers *helpers.Helpers, -) *Client { - sessionStateLoader := NewSessionStateLoader(c, helpers.Refs) - handlerCreator := NewHandlerCreator( - c, - sessionStateLoader, - helpers.Suggestions, - helpers.MergeAndRebase, - ) - keybindingCreator := NewKeybindingCreator(c) - - return &Client{ - c: c, - keybindingCreator: keybindingCreator, - handlerCreator: handlerCreator, - } -} - -func (self *Client) GetCustomCommandKeybindings() ([]*types.Binding, error) { - bindings := []*types.Binding{} - for _, customCommand := range self.c.UserConfig().CustomCommands { - if len(customCommand.CommandMenu) > 0 { - handler := func() error { - return self.showCustomCommandsMenu(customCommand) - } - bindings = append(bindings, &types.Binding{ - ViewName: "", // custom commands menus are global; we filter the commands inside by context - Key: keybindings.GetKey(customCommand.Key), - Modifier: gocui.ModNone, - Handler: handler, - Description: getCustomCommandsMenuDescription(customCommand, self.c.Tr), - OpensMenu: true, - }) - } else { - handler := self.handlerCreator.call(customCommand) - compoundBindings, err := self.keybindingCreator.call(customCommand, handler) - if err != nil { - return nil, err - } - bindings = append(bindings, compoundBindings...) - } - } - - return bindings, nil -} - -func (self *Client) showCustomCommandsMenu(customCommand config.CustomCommand) error { - menuItems := make([]*types.MenuItem, 0, len(customCommand.CommandMenu)) - for _, subCommand := range customCommand.CommandMenu { - if len(subCommand.CommandMenu) > 0 { - handler := func() error { - return self.showCustomCommandsMenu(subCommand) - } - menuItems = append(menuItems, &types.MenuItem{ - Label: subCommand.GetDescription(), - Key: keybindings.GetKey(subCommand.Key), - OnPress: handler, - OpensMenu: true, - }) - } else { - if subCommand.Context != "" && subCommand.Context != "global" { - viewNames, err := self.keybindingCreator.getViewNamesAndContexts(subCommand) - if err != nil { - return err - } - - currentView := self.c.GocuiGui().CurrentView() - enabled := currentView != nil && lo.Contains(viewNames, currentView.Name()) - if !enabled { - continue - } - } - - menuItems = append(menuItems, &types.MenuItem{ - Label: subCommand.GetDescription(), - Key: keybindings.GetKey(subCommand.Key), - OnPress: self.handlerCreator.call(subCommand), - }) - } - } - - if len(menuItems) == 0 { - menuItems = append(menuItems, &types.MenuItem{ - Label: self.c.Tr.NoApplicableCommandsInThisContext, - OnPress: func() error { return nil }, - }) - } - - title := getCustomCommandsMenuDescription(customCommand, self.c.Tr) - return self.c.Menu(types.CreateMenuOptions{Title: title, Items: menuItems, HideCancel: true}) -} - -func getCustomCommandsMenuDescription(customCommand config.CustomCommand, tr *i18n.TranslationSet) string { - if customCommand.Description != "" { - return customCommand.Description - } - - return tr.CustomCommands -} diff --git a/pkg/gui/services/custom_commands/handler_creator.go b/pkg/gui/services/custom_commands/handler_creator.go deleted file mode 100644 index 73e68c3b3c0..00000000000 --- a/pkg/gui/services/custom_commands/handler_creator.go +++ /dev/null @@ -1,312 +0,0 @@ -package custom_commands - -import ( - "errors" - "fmt" - "strings" - "text/template" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -// takes a custom command and returns a function that will be called when the corresponding user-defined keybinding is pressed -type HandlerCreator struct { - c *helpers.HelperCommon - sessionStateLoader *SessionStateLoader - resolver *Resolver - menuGenerator *MenuGenerator - suggestionsHelper *helpers.SuggestionsHelper - mergeAndRebaseHelper *helpers.MergeAndRebaseHelper -} - -func NewHandlerCreator( - c *helpers.HelperCommon, - sessionStateLoader *SessionStateLoader, - suggestionsHelper *helpers.SuggestionsHelper, - mergeAndRebaseHelper *helpers.MergeAndRebaseHelper, -) *HandlerCreator { - resolver := NewResolver(c.Common) - menuGenerator := NewMenuGenerator(c.Common) - - return &HandlerCreator{ - c: c, - sessionStateLoader: sessionStateLoader, - resolver: resolver, - menuGenerator: menuGenerator, - suggestionsHelper: suggestionsHelper, - mergeAndRebaseHelper: mergeAndRebaseHelper, - } -} - -func (self *HandlerCreator) call(customCommand config.CustomCommand) func() error { - return func() error { - sessionState := self.sessionStateLoader.call() - promptResponses := make([]string, len(customCommand.Prompts)) - form := make(map[string]string) - - f := func() error { return self.finalHandler(customCommand, sessionState, promptResponses, form) } - - // if we have prompts we'll recursively wrap our confirm handlers with more prompts - // until we reach the actual command - for reverseIdx := range customCommand.Prompts { - // reassigning so that we don't end up with an infinite recursion - g := f - idx := len(customCommand.Prompts) - 1 - reverseIdx - - // going backwards so the outermost prompt is the first one - prompt := customCommand.Prompts[idx] - - wrappedF := func(response string) error { - promptResponses[idx] = response - form[prompt.Key] = response - return g() - } - - resolveTemplate := self.getResolveTemplateFn(form, promptResponses, sessionState) - - switch prompt.Type { - case "input": - f = func() error { - resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate) - if err != nil { - return err - } - return self.inputPrompt(resolvedPrompt, wrappedF) - } - case "menu": - f = func() error { - resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate) - if err != nil { - return err - } - return self.menuPrompt(resolvedPrompt, wrappedF) - } - case "menuFromCommand": - f = func() error { - resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate) - if err != nil { - return err - } - return self.menuPromptFromCommand(resolvedPrompt, wrappedF) - } - case "confirm": - f = func() error { - resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate) - if err != nil { - return err - } - return self.confirmPrompt(resolvedPrompt, g) - } - default: - return errors.New("custom command prompt must have a type of 'input', 'menu', 'menuFromCommand', or 'confirm'") - } - } - - return f() - } -} - -func (self *HandlerCreator) inputPrompt(prompt *config.CustomCommandPrompt, wrappedF func(string) error) error { - findSuggestionsFn, err := self.generateFindSuggestionsFunc(prompt) - if err != nil { - return err - } - - self.c.Prompt(types.PromptOpts{ - Title: prompt.Title, - InitialContent: prompt.InitialValue, - FindSuggestionsFunc: findSuggestionsFn, - HandleConfirm: func(str string) error { - return wrappedF(str) - }, - }) - - return nil -} - -func (self *HandlerCreator) generateFindSuggestionsFunc(prompt *config.CustomCommandPrompt) (func(string) []*types.Suggestion, error) { - if prompt.Suggestions.Preset != "" && prompt.Suggestions.Command != "" { - return nil, fmt.Errorf( - "Custom command prompt cannot have both a preset and a command for suggestions. Preset: '%s', Command: '%s'", - prompt.Suggestions.Preset, - prompt.Suggestions.Command, - ) - } else if prompt.Suggestions.Preset != "" { - return self.getPresetSuggestionsFn(prompt.Suggestions.Preset) - } else if prompt.Suggestions.Command != "" { - return self.getCommandSuggestionsFn(prompt.Suggestions.Command) - } - - return nil, nil -} - -func (self *HandlerCreator) getCommandSuggestionsFn(command string) (func(string) []*types.Suggestion, error) { - lines := []*types.Suggestion{} - err := self.c.OS().Cmd.NewShell(command, self.c.UserConfig().OS.ShellFunctionsFile).RunAndProcessLines(func(line string) (bool, error) { - lines = append(lines, &types.Suggestion{Value: line, Label: line}) - return false, nil - }) - if err != nil { - return nil, err - } - - return func(currentWord string) []*types.Suggestion { - return lo.Filter(lines, func(suggestion *types.Suggestion, _ int) bool { - return strings.Contains(strings.ToLower(suggestion.Value), strings.ToLower(currentWord)) - }) - }, nil -} - -func (self *HandlerCreator) getPresetSuggestionsFn(preset string) (func(string) []*types.Suggestion, error) { - switch preset { - case "authors": - return self.suggestionsHelper.GetAuthorsSuggestionsFunc(), nil - case "branches": - return self.suggestionsHelper.GetBranchNameSuggestionsFunc(), nil - case "files": - return self.suggestionsHelper.GetFilePathSuggestionsFunc(), nil - case "refs": - return self.suggestionsHelper.GetRefsSuggestionsFunc(), nil - case "remotes": - return self.suggestionsHelper.GetRemoteSuggestionsFunc(), nil - case "remoteBranches": - return self.suggestionsHelper.GetRemoteBranchesSuggestionsFunc("/"), nil - case "tags": - return self.suggestionsHelper.GetTagsSuggestionsFunc(), nil - default: - return nil, fmt.Errorf("Unknown value for suggestionsPreset in custom command: %s. Valid values: files, branches, remotes, remoteBranches, refs", preset) - } -} - -func (self *HandlerCreator) confirmPrompt(prompt *config.CustomCommandPrompt, handleConfirm func() error) error { - self.c.Confirm(types.ConfirmOpts{ - Title: prompt.Title, - Prompt: prompt.Body, - HandleConfirm: handleConfirm, - }) - - return nil -} - -func (self *HandlerCreator) menuPrompt(prompt *config.CustomCommandPrompt, wrappedF func(string) error) error { - menuItems := lo.Map(prompt.Options, func(option config.CustomCommandMenuOption, _ int) *types.MenuItem { - return &types.MenuItem{ - LabelColumns: []string{option.Name, style.FgYellow.Sprint(option.Description)}, - OnPress: func() error { - return wrappedF(option.Value) - }, - } - }) - - return self.c.Menu(types.CreateMenuOptions{Title: prompt.Title, Items: menuItems}) -} - -func (self *HandlerCreator) menuPromptFromCommand(prompt *config.CustomCommandPrompt, wrappedF func(string) error) error { - // Run and save output - message, err := self.c.Git().Custom.RunWithOutput(prompt.Command) - if err != nil { - return err - } - - // Need to make a menu out of what the cmd has displayed - candidates, err := self.menuGenerator.call(message, prompt.Filter, prompt.ValueFormat, prompt.LabelFormat) - if err != nil { - return err - } - - menuItems := lo.Map(candidates, func(candidate *commandMenuItem, _ int) *types.MenuItem { - return &types.MenuItem{ - LabelColumns: []string{candidate.label}, - OnPress: func() error { - return wrappedF(candidate.value) - }, - } - }) - - return self.c.Menu(types.CreateMenuOptions{Title: prompt.Title, Items: menuItems}) -} - -type CustomCommandObjects struct { - *SessionState - PromptResponses []string - Form map[string]string -} - -func (self *HandlerCreator) getResolveTemplateFn(form map[string]string, promptResponses []string, sessionState *SessionState) func(string) (string, error) { - objects := CustomCommandObjects{ - SessionState: sessionState, - PromptResponses: promptResponses, - Form: form, - } - - funcs := template.FuncMap{ - "quote": self.c.OS().Quote, - "runCommand": self.c.Git().Custom.TemplateFunctionRunCommand, - } - - return func(templateStr string) (string, error) { return utils.ResolveTemplate(templateStr, objects, funcs) } -} - -func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, sessionState *SessionState, promptResponses []string, form map[string]string) error { - resolveTemplate := self.getResolveTemplateFn(form, promptResponses, sessionState) - cmdStr, err := resolveTemplate(customCommand.Command) - if err != nil { - return err - } - - cmdObj := self.c.OS().Cmd.NewShell(cmdStr, self.c.UserConfig().OS.ShellFunctionsFile) - - if customCommand.Output == "terminal" { - return self.c.RunSubprocessAndRefresh(cmdObj) - } - - loadingText := customCommand.LoadingText - if loadingText == "" { - loadingText = self.c.Tr.RunningCustomCommandStatus - } - - return self.c.WithWaitingStatus(loadingText, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.CustomCommand) - - if customCommand.Output == "log" || customCommand.Output == "logWithPty" { - cmdObj.StreamOutput() - } - if customCommand.Output == "logWithPty" { - cmdObj.UsePty() - } - output, err := cmdObj.RunWithOutput() - - self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - - if err != nil { - if customCommand.After != nil && customCommand.After.CheckForConflicts { - return self.mergeAndRebaseHelper.CheckForConflicts(err) - } - - return err - } - - if customCommand.Output == "popup" { - if strings.TrimSpace(output) == "" { - output = self.c.Tr.EmptyOutput - } - - title := cmdStr - if customCommand.OutputTitle != "" { - title, err = resolveTemplate(customCommand.OutputTitle) - if err != nil { - return err - } - } - self.c.Alert(title, output) - } - - return nil - }) -} diff --git a/pkg/gui/services/custom_commands/keybinding_creator.go b/pkg/gui/services/custom_commands/keybinding_creator.go deleted file mode 100644 index 259954c17bb..00000000000 --- a/pkg/gui/services/custom_commands/keybinding_creator.go +++ /dev/null @@ -1,91 +0,0 @@ -package custom_commands - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" -) - -// KeybindingCreator takes a custom command along with its handler and returns a corresponding keybinding -type KeybindingCreator struct { - c *helpers.HelperCommon -} - -func NewKeybindingCreator(c *helpers.HelperCommon) *KeybindingCreator { - return &KeybindingCreator{ - c: c, - } -} - -func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler func() error) ([]*types.Binding, error) { - if customCommand.Context == "" { - return nil, formatContextNotProvidedError(customCommand) - } - - viewNames, err := self.getViewNamesAndContexts(customCommand) - if err != nil { - return nil, err - } - - return lo.Map(viewNames, func(viewName string, _ int) *types.Binding { - return &types.Binding{ - ViewName: viewName, - Key: keybindings.GetKey(customCommand.Key), - Modifier: gocui.ModNone, - Handler: handler, - Description: customCommand.GetDescription(), - } - }), nil -} - -func (self *KeybindingCreator) getViewNamesAndContexts(customCommand config.CustomCommand) ([]string, error) { - if customCommand.Context == "global" { - return []string{""}, nil - } - - contexts := strings.Split(customCommand.Context, ",") - contexts = lo.Map(contexts, func(context string, _ int) string { - return strings.TrimSpace(context) - }) - - viewNames := []string{} - for _, context := range contexts { - ctx, ok := self.contextForContextKey(types.ContextKey(context)) - if !ok { - return []string{}, formatUnknownContextError(customCommand) - } - - viewNames = append(viewNames, ctx.GetViewName()) - } - - return viewNames, nil -} - -func (self *KeybindingCreator) contextForContextKey(contextKey types.ContextKey) (types.Context, bool) { - for _, context := range self.c.Contexts().Flatten() { - if context.GetKey() == contextKey { - return context, true - } - } - - return nil, false -} - -func formatUnknownContextError(customCommand config.CustomCommand) error { - allContextKeyStrings := lo.Map(context.AllContextKeys, func(key types.ContextKey, _ int) string { - return string(key) - }) - - return fmt.Errorf("Error when setting custom command keybindings: unknown context: %s. Key: %s, Command: %s.\nPermitted contexts: %s", customCommand.Context, customCommand.Key, customCommand.Command, strings.Join(allContextKeyStrings, ", ")) -} - -func formatContextNotProvidedError(customCommand config.CustomCommand) error { - return fmt.Errorf("Error parsing custom command keybindings: context not provided (use context: 'global' for the global context). Key: %s, Command: %s", customCommand.Key, customCommand.Command) -} diff --git a/pkg/gui/services/custom_commands/menu_generator.go b/pkg/gui/services/custom_commands/menu_generator.go deleted file mode 100644 index 4a73fe433cf..00000000000 --- a/pkg/gui/services/custom_commands/menu_generator.go +++ /dev/null @@ -1,155 +0,0 @@ -package custom_commands - -import ( - "bytes" - "errors" - "regexp" - "strconv" - "strings" - "text/template" - - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/gui/style" -) - -type MenuGenerator struct { - c *common.Common -} - -// takes the output of a command and returns a list of menu entries based on a filter -// and value/label format templates provided by the user -func NewMenuGenerator(c *common.Common) *MenuGenerator { - return &MenuGenerator{c: c} -} - -type commandMenuItem struct { - label string - value string -} - -func (self *MenuGenerator) call(commandOutput, filter, valueFormat, labelFormat string) ([]*commandMenuItem, error) { - menuItemFromLine, err := self.getMenuItemFromLinefn(filter, valueFormat, labelFormat) - if err != nil { - return nil, err - } - - menuItems := []*commandMenuItem{} - for _, line := range strings.Split(commandOutput, "\n") { - if line == "" { - continue - } - - menuItem, err := menuItemFromLine(line) - if err != nil { - return nil, err - } - menuItems = append(menuItems, menuItem) - } - - return menuItems, nil -} - -func (self *MenuGenerator) getMenuItemFromLinefn(filter string, valueFormat string, labelFormat string) (func(line string) (*commandMenuItem, error), error) { - if filter == "" && valueFormat == "" && labelFormat == "" { - // showing command output lines as-is in suggestions panel - return func(line string) (*commandMenuItem, error) { - return &commandMenuItem{label: line, value: line}, nil - }, nil - } - - regex, err := regexp.Compile(filter) - if err != nil { - return nil, errors.New("unable to parse filter regex, error: " + err.Error()) - } - - valueTemplateAux, err := template.New("format").Parse(valueFormat) - if err != nil { - return nil, errors.New("unable to parse value format, error: " + err.Error()) - } - valueTemplate := NewTrimmerTemplate(valueTemplateAux) - - var labelTemplate *TrimmerTemplate - if labelFormat != "" { - colorFuncMap := style.TemplateFuncMapAddColors(template.FuncMap{}) - labelTemplateAux, err := template.New("format").Funcs(colorFuncMap).Parse(labelFormat) - if err != nil { - return nil, errors.New("unable to parse label format, error: " + err.Error()) - } - labelTemplate = NewTrimmerTemplate(labelTemplateAux) - } else { - labelTemplate = valueTemplate - } - - return func(line string) (*commandMenuItem, error) { - return self.generateMenuItem( - line, - regex, - valueTemplate, - labelTemplate, - ) - }, nil -} - -func (self *MenuGenerator) generateMenuItem( - line string, - regex *regexp.Regexp, - valueTemplate *TrimmerTemplate, - labelTemplate *TrimmerTemplate, -) (*commandMenuItem, error) { - tmplData := self.parseLine(line, regex) - - entry := &commandMenuItem{} - - var err error - entry.value, err = valueTemplate.execute(tmplData) - if err != nil { - return nil, err - } - - entry.label, err = labelTemplate.execute(tmplData) - if err != nil { - return nil, err - } - - return entry, nil -} - -func (self *MenuGenerator) parseLine(line string, regex *regexp.Regexp) map[string]string { - tmplData := map[string]string{} - out := regex.FindAllStringSubmatch(line, -1) - if len(out) > 0 { - for groupIdx, group := range regex.SubexpNames() { - // Record matched group with group ids - matchName := "group_" + strconv.Itoa(groupIdx) - tmplData[matchName] = out[0][groupIdx] - // Record last named group non-empty matches as group matches - if group != "" { - tmplData[group] = out[0][groupIdx] - } - } - } - - return tmplData -} - -// wrapper around a template which trims the output -type TrimmerTemplate struct { - template *template.Template - buffer *bytes.Buffer -} - -func NewTrimmerTemplate(template *template.Template) *TrimmerTemplate { - return &TrimmerTemplate{ - template: template, - buffer: bytes.NewBuffer(nil), - } -} - -func (self *TrimmerTemplate) execute(tmplData map[string]string) (string, error) { - self.buffer.Reset() - err := self.template.Execute(self.buffer, tmplData) - if err != nil { - return "", err - } - return strings.TrimSpace(self.buffer.String()), nil -} diff --git a/pkg/gui/services/custom_commands/menu_generator_test.go b/pkg/gui/services/custom_commands/menu_generator_test.go deleted file mode 100644 index d5f15028ec5..00000000000 --- a/pkg/gui/services/custom_commands/menu_generator_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package custom_commands - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/stretchr/testify/assert" -) - -func TestMenuGenerator(t *testing.T) { - type scenario struct { - testName string - cmdOut string - filter string - valueFormat string - labelFormat string - test func([]*commandMenuItem, error) - } - - scenarios := []scenario{ - { - "Extract remote branch name", - "upstream/pr-1", - "(?P[a-z_]+)/(?P.*)", - "{{ .branch }}", - "Remote: {{ .remote }}", - func(actualEntry []*commandMenuItem, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "pr-1", actualEntry[0].value) - assert.EqualValues(t, "Remote: upstream", actualEntry[0].label) - }, - }, - { - "Multiple named groups with empty labelFormat", - "upstream/pr-1", - "(?P[a-z]*)/(?P.*)", - "{{ .branch }}|{{ .remote }}", - "", - func(actualEntry []*commandMenuItem, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "pr-1|upstream", actualEntry[0].value) - assert.EqualValues(t, "pr-1|upstream", actualEntry[0].label) - }, - }, - { - "Multiple named groups with group ids", - "upstream/pr-1", - "(?P[a-z]*)/(?P.*)", - "{{ .group_2 }}|{{ .group_1 }}", - "Remote: {{ .group_1 }}", - func(actualEntry []*commandMenuItem, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "pr-1|upstream", actualEntry[0].value) - assert.EqualValues(t, "Remote: upstream", actualEntry[0].label) - }, - }, - { - "No named groups", - "upstream/pr-1", - "([a-z]*)/(.*)", - "{{ .group_2 }}|{{ .group_1 }}", - "Remote: {{ .group_1 }}", - func(actualEntry []*commandMenuItem, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "pr-1|upstream", actualEntry[0].value) - assert.EqualValues(t, "Remote: upstream", actualEntry[0].label) - }, - }, - { - "No filter", - "upstream/pr-1", - "", - "", - "", - func(actualEntry []*commandMenuItem, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "upstream/pr-1", actualEntry[0].value) - assert.EqualValues(t, "upstream/pr-1", actualEntry[0].label) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - s.test(NewMenuGenerator(common.NewDummyCommon()).call(s.cmdOut, s.filter, s.valueFormat, s.labelFormat)) - }) - } -} diff --git a/pkg/gui/services/custom_commands/models.go b/pkg/gui/services/custom_commands/models.go deleted file mode 100644 index eee52ff5d0b..00000000000 --- a/pkg/gui/services/custom_commands/models.go +++ /dev/null @@ -1,100 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/stefanhaller/git-todo-parser/todo" -) - -// We create shims for all the model classes in order to get a more stable API -// for custom commands. At the moment these are almost identical to the model -// classes, but this allows us to add "private" fields to the model classes that -// we don't want to expose to custom commands, or rename a model field to a -// better name without breaking people's custom commands. In such a case we add -// the new, better name to the shim but keep the old one for backwards -// compatibility. We already did this for Commit.Sha, which was renamed to Hash. - -type Commit struct { - Hash string - Sha string // deprecated: use Hash - Name string - Status models.CommitStatus - Action todo.TodoCommand - Tags []string - ExtraInfo string - AuthorName string - AuthorEmail string - UnixTimestamp int64 - Divergence models.Divergence - Parents []string -} - -type File struct { - Name string - PreviousName string - HasStagedChanges bool - HasUnstagedChanges bool - Tracked bool - Added bool - Deleted bool - HasMergeConflicts bool - HasInlineMergeConflicts bool - DisplayString string - ShortStatus string - IsWorktree bool -} - -type Branch struct { - Name string - DisplayName string - Recency string - Pushables string // deprecated: use AheadForPull - Pullables string // deprecated: use BehindForPull - AheadForPull string - BehindForPull string - AheadForPush string - BehindForPush string - UpstreamGone bool - Head bool - DetachedHead bool - UpstreamRemote string - UpstreamBranch string - Subject string - CommitHash string -} - -type RemoteBranch struct { - Name string - RemoteName string -} - -type Remote struct { - Name string - Urls []string - Branches []*RemoteBranch -} - -type Tag struct { - Name string - Message string -} - -type StashEntry struct { - Index int - Recency string - Name string -} - -type CommitFile struct { - Name string - ChangeStatus string -} - -type Worktree struct { - IsMain bool - IsCurrent bool - Path string - IsPathMissing bool - GitDir string - Branch string - Name string -} diff --git a/pkg/gui/services/custom_commands/resolver.go b/pkg/gui/services/custom_commands/resolver.go deleted file mode 100644 index bf269735562..00000000000 --- a/pkg/gui/services/custom_commands/resolver.go +++ /dev/null @@ -1,118 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" -) - -// takes a prompt that is defined in terms of template strings and resolves the templates to contain actual values -type Resolver struct { - c *common.Common -} - -func NewResolver(c *common.Common) *Resolver { - return &Resolver{c: c} -} - -func (self *Resolver) resolvePrompt( - prompt *config.CustomCommandPrompt, - resolveTemplate func(string) (string, error), -) (*config.CustomCommandPrompt, error) { - var err error - result := &config.CustomCommandPrompt{ - ValueFormat: prompt.ValueFormat, - LabelFormat: prompt.LabelFormat, - } - - result.Title, err = resolveTemplate(prompt.Title) - if err != nil { - return nil, err - } - - result.InitialValue, err = resolveTemplate(prompt.InitialValue) - if err != nil { - return nil, err - } - - result.Suggestions.Preset, err = resolveTemplate(prompt.Suggestions.Preset) - if err != nil { - return nil, err - } - - result.Suggestions.Command, err = resolveTemplate(prompt.Suggestions.Command) - if err != nil { - return nil, err - } - - result.Body, err = resolveTemplate(prompt.Body) - if err != nil { - return nil, err - } - - result.Command, err = resolveTemplate(prompt.Command) - if err != nil { - return nil, err - } - - result.Filter, err = resolveTemplate(prompt.Filter) - if err != nil { - return nil, err - } - - if prompt.Type == "menu" { - result.Options, err = self.resolveMenuOptions(prompt, resolveTemplate) - if err != nil { - return nil, err - } - } - - return result, nil -} - -func (self *Resolver) resolveMenuOptions(prompt *config.CustomCommandPrompt, resolveTemplate func(string) (string, error)) ([]config.CustomCommandMenuOption, error) { - newOptions := make([]config.CustomCommandMenuOption, 0, len(prompt.Options)) - for _, option := range prompt.Options { - newOption, err := self.resolveMenuOption(&option, resolveTemplate) - if err != nil { - return nil, err - } - newOptions = append(newOptions, *newOption) - } - - return newOptions, nil -} - -func (self *Resolver) resolveMenuOption(option *config.CustomCommandMenuOption, resolveTemplate func(string) (string, error)) (*config.CustomCommandMenuOption, error) { - nameTemplate := option.Name - if nameTemplate == "" { - // this allows you to only pass values rather than bother with names/descriptions - nameTemplate = option.Value - } - - name, err := resolveTemplate(nameTemplate) - if err != nil { - return nil, err - } - - description, err := resolveTemplate(option.Description) - if err != nil { - return nil, err - } - - value, err := resolveTemplate(option.Value) - if err != nil { - return nil, err - } - - return &config.CustomCommandMenuOption{ - Name: name, - Description: description, - Value: value, - }, nil -} - -type CustomCommandObject struct { - // deprecated. Use Responses instead - PromptResponses []string - Form map[string]string -} diff --git a/pkg/gui/services/custom_commands/session_state_loader.go b/pkg/gui/services/custom_commands/session_state_loader.go deleted file mode 100644 index 87465ec1a14..00000000000 --- a/pkg/gui/services/custom_commands/session_state_loader.go +++ /dev/null @@ -1,244 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/samber/lo" -) - -// loads the session state at the time that a custom command is invoked, for use -// in the custom command's template strings -type SessionStateLoader struct { - c *helpers.HelperCommon - refsHelper *helpers.RefsHelper -} - -func NewSessionStateLoader(c *helpers.HelperCommon, refsHelper *helpers.RefsHelper) *SessionStateLoader { - return &SessionStateLoader{ - c: c, - refsHelper: refsHelper, - } -} - -func commitShimFromModelCommit(commit *models.Commit) *Commit { - if commit == nil { - return nil - } - - return &Commit{ - Hash: commit.Hash(), - Sha: commit.Hash(), - Name: commit.Name, - Status: commit.Status, - Action: commit.Action, - Tags: commit.Tags, - ExtraInfo: commit.ExtraInfo, - AuthorName: commit.AuthorName, - AuthorEmail: commit.AuthorEmail, - UnixTimestamp: commit.UnixTimestamp, - Divergence: commit.Divergence, - Parents: commit.Parents(), - } -} - -func fileShimFromModelFile(file *models.File) *File { - if file == nil { - return nil - } - - return &File{ - Name: file.Path, - PreviousName: file.PreviousPath, - HasStagedChanges: file.HasStagedChanges, - HasUnstagedChanges: file.HasUnstagedChanges, - Tracked: file.Tracked, - Added: file.Added, - Deleted: file.Deleted, - HasMergeConflicts: file.HasMergeConflicts, - HasInlineMergeConflicts: file.HasInlineMergeConflicts, - DisplayString: file.DisplayString, - ShortStatus: file.ShortStatus, - IsWorktree: file.IsWorktree, - } -} - -func branchShimFromModelBranch(branch *models.Branch) *Branch { - if branch == nil { - return nil - } - - return &Branch{ - Name: branch.Name, - DisplayName: branch.DisplayName, - Recency: branch.Recency, - Pushables: branch.AheadForPull, - Pullables: branch.BehindForPull, - AheadForPull: branch.AheadForPull, - BehindForPull: branch.BehindForPull, - AheadForPush: branch.AheadForPush, - BehindForPush: branch.BehindForPush, - UpstreamGone: branch.UpstreamGone, - Head: branch.Head, - DetachedHead: branch.DetachedHead, - UpstreamRemote: branch.UpstreamRemote, - UpstreamBranch: branch.UpstreamBranch, - Subject: branch.Subject, - CommitHash: branch.CommitHash, - } -} - -func remoteBranchShimFromModelRemoteBranch(remoteBranch *models.RemoteBranch) *RemoteBranch { - if remoteBranch == nil { - return nil - } - - return &RemoteBranch{ - Name: remoteBranch.Name, - RemoteName: remoteBranch.RemoteName, - } -} - -func remoteShimFromModelRemote(remote *models.Remote) *Remote { - if remote == nil { - return nil - } - - return &Remote{ - Name: remote.Name, - Urls: remote.Urls, - Branches: lo.Map(remote.Branches, func(branch *models.RemoteBranch, _ int) *RemoteBranch { - return remoteBranchShimFromModelRemoteBranch(branch) - }), - } -} - -func tagShimFromModelRemote(tag *models.Tag) *Tag { - if tag == nil { - return nil - } - - return &Tag{ - Name: tag.Name, - Message: tag.Message, - } -} - -func stashEntryShimFromModelRemote(stashEntry *models.StashEntry) *StashEntry { - if stashEntry == nil { - return nil - } - - return &StashEntry{ - Index: stashEntry.Index, - Recency: stashEntry.Recency, - Name: stashEntry.Name, - } -} - -func commitFileShimFromModelRemote(commitFile *models.CommitFile) *CommitFile { - if commitFile == nil { - return nil - } - - return &CommitFile{ - Name: commitFile.Path, - ChangeStatus: commitFile.ChangeStatus, - } -} - -func worktreeShimFromModelRemote(worktree *models.Worktree) *Worktree { - if worktree == nil { - return nil - } - - return &Worktree{ - IsMain: worktree.IsMain, - IsCurrent: worktree.IsCurrent, - Path: worktree.Path, - IsPathMissing: worktree.IsPathMissing, - GitDir: worktree.GitDir, - Branch: worktree.Branch, - Name: worktree.Name, - } -} - -type CommitRange struct { - From string - To string -} - -func makeCommitRange(commits []*models.Commit, _ int, _ int) *CommitRange { - if len(commits) == 0 { - return nil - } - - return &CommitRange{ - From: commits[len(commits)-1].Hash(), - To: commits[0].Hash(), - } -} - -// SessionState captures the current state of the application for use in custom commands -type SessionState struct { - SelectedLocalCommit *Commit // deprecated, use SelectedCommit - SelectedReflogCommit *Commit // deprecated, use SelectedCommit - SelectedSubCommit *Commit // deprecated, use SelectedCommit - SelectedCommit *Commit - SelectedCommitRange *CommitRange - SelectedFile *File - SelectedPath string - SelectedLocalBranch *Branch - SelectedRemoteBranch *RemoteBranch - SelectedRemote *Remote - SelectedTag *Tag - SelectedStashEntry *StashEntry - SelectedCommitFile *CommitFile - SelectedCommitFilePath string - SelectedWorktree *Worktree - CheckedOutBranch *Branch -} - -func (self *SessionStateLoader) call() *SessionState { - selectedLocalCommit := commitShimFromModelCommit(self.c.Contexts().LocalCommits.GetSelected()) - selectedLocalCommitRange := makeCommitRange(self.c.Contexts().LocalCommits.GetSelectedItems()) - selectedReflogCommit := commitShimFromModelCommit(self.c.Contexts().ReflogCommits.GetSelected()) - selectedReflogCommitRange := makeCommitRange(self.c.Contexts().ReflogCommits.GetSelectedItems()) - selectedSubCommit := commitShimFromModelCommit(self.c.Contexts().SubCommits.GetSelected()) - selectedSubCommitRange := makeCommitRange(self.c.Contexts().SubCommits.GetSelectedItems()) - - selectedCommit := selectedLocalCommit - selectedCommitRange := selectedLocalCommitRange - if self.c.Context().IsCurrentOrParent(self.c.Contexts().ReflogCommits) { - selectedCommit = selectedReflogCommit - selectedCommitRange = selectedReflogCommitRange - } else if self.c.Context().IsCurrentOrParent(self.c.Contexts().SubCommits) { - selectedCommit = selectedSubCommit - selectedCommitRange = selectedSubCommitRange - } - - selectedPath := self.c.Contexts().Files.GetSelectedPath() - selectedCommitFilePath := self.c.Contexts().CommitFiles.GetSelectedPath() - - if self.c.Context().IsCurrent(self.c.Contexts().CommitFiles) { - selectedPath = selectedCommitFilePath - } - - return &SessionState{ - SelectedFile: fileShimFromModelFile(self.c.Contexts().Files.GetSelectedFile()), - SelectedPath: selectedPath, - SelectedLocalCommit: selectedLocalCommit, - SelectedReflogCommit: selectedReflogCommit, - SelectedSubCommit: selectedSubCommit, - SelectedCommit: selectedCommit, - SelectedCommitRange: selectedCommitRange, - SelectedLocalBranch: branchShimFromModelBranch(self.c.Contexts().Branches.GetSelected()), - SelectedRemoteBranch: remoteBranchShimFromModelRemoteBranch(self.c.Contexts().RemoteBranches.GetSelected()), - SelectedRemote: remoteShimFromModelRemote(self.c.Contexts().Remotes.GetSelected()), - SelectedTag: tagShimFromModelRemote(self.c.Contexts().Tags.GetSelected()), - SelectedStashEntry: stashEntryShimFromModelRemote(self.c.Contexts().Stash.GetSelected()), - SelectedCommitFile: commitFileShimFromModelRemote(self.c.Contexts().CommitFiles.GetSelectedFile()), - SelectedCommitFilePath: selectedCommitFilePath, - SelectedWorktree: worktreeShimFromModelRemote(self.c.Contexts().Worktrees.GetSelected()), - CheckedOutBranch: branchShimFromModelBranch(self.refsHelper.GetCheckedOutRef()), - } -} diff --git a/pkg/gui/status/status_manager.go b/pkg/gui/status/status_manager.go deleted file mode 100644 index 40c68fe2dca..00000000000 --- a/pkg/gui/status/status_manager.go +++ /dev/null @@ -1,117 +0,0 @@ -package status - -import ( - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" - "github.com/sasha-s/go-deadlock" -) - -// StatusManager's job is to handle queuing of loading states and toast notifications -// that you see at the bottom left of the screen. -type StatusManager struct { - statuses []appStatus - nextId int - mutex deadlock.Mutex -} - -// Can be used to manipulate a waiting status while it is running (e.g. pause -// and resume it) -type WaitingStatusHandle struct { - statusManager *StatusManager - message string - renderFunc func() - id int -} - -func (self *WaitingStatusHandle) Show() { - self.id = self.statusManager.addStatus(self.message, "waiting", types.ToastKindStatus) - self.renderFunc() -} - -func (self *WaitingStatusHandle) Hide() { - self.statusManager.removeStatus(self.id) -} - -type appStatus struct { - message string - statusType string - color gocui.Attribute - id int -} - -func NewStatusManager() *StatusManager { - return &StatusManager{} -} - -func (self *StatusManager) WithWaitingStatus(message string, renderFunc func(), f func(*WaitingStatusHandle) error) error { - handle := &WaitingStatusHandle{statusManager: self, message: message, renderFunc: renderFunc, id: -1} - handle.Show() - defer handle.Hide() - - return f(handle) -} - -func (self *StatusManager) AddToastStatus(message string, kind types.ToastKind) int { - id := self.addStatus(message, "toast", kind) - - go func() { - delay := lo.Ternary(kind == types.ToastKindError, time.Second*4, time.Second*2) - time.Sleep(delay) - - self.removeStatus(id) - }() - - return id -} - -func (self *StatusManager) GetStatusString(userConfig *config.UserConfig) (string, gocui.Attribute) { - if len(self.statuses) == 0 { - return "", gocui.ColorDefault - } - topStatus := self.statuses[0] - if topStatus.statusType == "waiting" { - return topStatus.message + " " + presentation.Loader(time.Now(), userConfig.Gui.Spinner), topStatus.color - } - return topStatus.message, topStatus.color -} - -func (self *StatusManager) HasStatus() bool { - return len(self.statuses) > 0 -} - -func (self *StatusManager) addStatus(message string, statusType string, kind types.ToastKind) int { - self.mutex.Lock() - defer self.mutex.Unlock() - - self.nextId++ - id := self.nextId - - color := gocui.ColorCyan - if kind == types.ToastKindError { - color = gocui.ColorRed - } - - newStatus := appStatus{ - message: message, - statusType: statusType, - color: color, - id: id, - } - self.statuses = append([]appStatus{newStatus}, self.statuses...) - - return id -} - -func (self *StatusManager) removeStatus(id int) { - self.mutex.Lock() - defer self.mutex.Unlock() - - self.statuses = lo.Filter(self.statuses, func(status appStatus, _ int) bool { - return status.id != id - }) -} diff --git a/pkg/gui/style/basic_styles.go b/pkg/gui/style/basic_styles.go deleted file mode 100644 index 59bd907ff1e..00000000000 --- a/pkg/gui/style/basic_styles.go +++ /dev/null @@ -1,69 +0,0 @@ -package style - -import ( - "text/template" - - "github.com/gookit/color" -) - -var ( - FgWhite = FromBasicFg(color.FgWhite) - FgLightWhite = FromBasicFg(color.FgLightWhite) - FgBlack = FromBasicFg(color.FgBlack) - FgBlackLighter = FromBasicFg(color.FgBlack.Light()) - FgCyan = FromBasicFg(color.FgCyan) - FgRed = FromBasicFg(color.FgRed) - FgGreen = FromBasicFg(color.FgGreen) - FgBlue = FromBasicFg(color.FgBlue) - FgYellow = FromBasicFg(color.FgYellow) - FgMagenta = FromBasicFg(color.FgMagenta) - FgDefault = FromBasicFg(color.FgDefault) - - BgWhite = FromBasicBg(color.BgWhite) - BgBlack = FromBasicBg(color.BgBlack) - BgRed = FromBasicBg(color.BgRed) - BgGreen = FromBasicBg(color.BgGreen) - BgYellow = FromBasicBg(color.BgYellow) - BgBlue = FromBasicBg(color.BgBlue) - BgMagenta = FromBasicBg(color.BgMagenta) - BgCyan = FromBasicBg(color.BgCyan) - BgDefault = FromBasicBg(color.BgDefault) - - // will not print any colour escape codes, including the reset escape code - Nothing = New() - - AttrUnderline = New().SetUnderline() - AttrBold = New().SetBold() - - ColorMap = map[string]struct { - Foreground TextStyle - Background TextStyle - }{ - "default": {FgDefault, BgDefault}, - "black": {FgBlack, BgBlack}, - "red": {FgRed, BgRed}, - "green": {FgGreen, BgGreen}, - "yellow": {FgYellow, BgYellow}, - "blue": {FgBlue, BgBlue}, - "magenta": {FgMagenta, BgMagenta}, - "cyan": {FgCyan, BgCyan}, - "white": {FgWhite, BgWhite}, - } -) - -func FromBasicFg(fg color.Color) TextStyle { - return New().SetFg(NewBasicColor(fg)) -} - -func FromBasicBg(bg color.Color) TextStyle { - return New().SetBg(NewBasicColor(bg)) -} - -func TemplateFuncMapAddColors(m template.FuncMap) template.FuncMap { - for k, v := range ColorMap { - m[k] = v.Foreground.Sprint - } - m["underline"] = color.OpUnderscore.Sprint - m["bold"] = color.OpBold.Sprint - return m -} diff --git a/pkg/gui/style/color.go b/pkg/gui/style/color.go deleted file mode 100644 index 2b13c9236f5..00000000000 --- a/pkg/gui/style/color.go +++ /dev/null @@ -1,39 +0,0 @@ -package style - -import "github.com/gookit/color" - -type Color struct { - rgb *color.RGBColor - basic *color.Color -} - -func NewRGBColor(cl color.RGBColor) Color { - c := Color{} - c.rgb = &cl - return c -} - -func NewBasicColor(cl color.Color) Color { - c := Color{} - c.basic = &cl - return c -} - -func (c Color) IsRGB() bool { - return c.rgb != nil -} - -func (c Color) ToRGB(isBg bool) Color { - if c.IsRGB() { - return c - } - - if isBg { - // We need to convert bg color to fg color - // This is a gookit/color bug, - // https://github.com/gookit/color/issues/39 - return NewRGBColor((*c.basic - 10).RGB()) - } - - return NewRGBColor(c.basic.RGB()) -} diff --git a/pkg/gui/style/decoration.go b/pkg/gui/style/decoration.go deleted file mode 100644 index b5d95b645d6..00000000000 --- a/pkg/gui/style/decoration.go +++ /dev/null @@ -1,68 +0,0 @@ -package style - -import "github.com/gookit/color" - -type Decoration struct { - bold bool - underline bool - reverse bool - strikethrough bool -} - -func (d *Decoration) SetBold() { - d.bold = true -} - -func (d *Decoration) SetUnderline() { - d.underline = true -} - -func (d *Decoration) SetReverse() { - d.reverse = true -} - -func (d *Decoration) SetStrikethrough() { - d.strikethrough = true -} - -func (d Decoration) ToOpts() color.Opts { - opts := make([]color.Color, 0, 3) - - if d.bold { - opts = append(opts, color.OpBold) - } - - if d.underline { - opts = append(opts, color.OpUnderscore) - } - - if d.reverse { - opts = append(opts, color.OpReverse) - } - - if d.strikethrough { - opts = append(opts, color.OpStrikethrough) - } - - return opts -} - -func (d Decoration) Merge(other Decoration) Decoration { - if other.bold { - d.bold = true - } - - if other.underline { - d.underline = true - } - - if other.reverse { - d.reverse = true - } - - if other.strikethrough { - d.strikethrough = true - } - - return d -} diff --git a/pkg/gui/style/hyperlink.go b/pkg/gui/style/hyperlink.go deleted file mode 100644 index 0585e89a957..00000000000 --- a/pkg/gui/style/hyperlink.go +++ /dev/null @@ -1,13 +0,0 @@ -package style - -import "fmt" - -// Render the given text as an OSC 8 hyperlink -func PrintHyperlink(text string, link string) string { - return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", link, text) -} - -// Render a link where the text is the same as a link -func PrintSimpleHyperlink(link string) string { - return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", link, link) -} diff --git a/pkg/gui/style/style_test.go b/pkg/gui/style/style_test.go deleted file mode 100644 index 10e88883b71..00000000000 --- a/pkg/gui/style/style_test.go +++ /dev/null @@ -1,225 +0,0 @@ -package style - -import ( - "bytes" - "testing" - "text/template" - - "github.com/gookit/color" - "github.com/stretchr/testify/assert" - "github.com/xo/terminfo" -) - -func TestMerge(t *testing.T) { - type scenario struct { - name string - toMerge []TextStyle - expectedStyle TextStyle - expectedStr string - } - - fgRed := color.FgRed - bgRed := color.BgRed - fgBlue := color.FgBlue - - rgbPinkLib := color.Rgb(0xFF, 0x00, 0xFF) - rgbPink := NewRGBColor(rgbPinkLib) - - rgbYellowLib := color.Rgb(0xFF, 0xFF, 0x00) - rgbYellow := NewRGBColor(rgbYellowLib) - - strToPrint := "foo" - - scenarios := []scenario{ - { - "no color", - nil, - TextStyle{Style: color.Style{}}, - "foo", - }, - { - "only fg color", - []TextStyle{FgRed}, - TextStyle{fg: &Color{basic: &fgRed}, Style: color.Style{fgRed}}, - "\x1b[31mfoo\x1b[0m", - }, - { - "only bg color", - []TextStyle{BgRed}, - TextStyle{bg: &Color{basic: &bgRed}, Style: color.Style{bgRed}}, - "\x1b[41mfoo\x1b[0m", - }, - { - "fg and bg color", - []TextStyle{FgBlue, BgRed}, - TextStyle{ - fg: &Color{basic: &fgBlue}, - bg: &Color{basic: &bgRed}, - Style: color.Style{fgBlue, bgRed}, - }, - "\x1b[34;41mfoo\x1b[0m", - }, - { - "single attribute", - []TextStyle{AttrBold}, - TextStyle{ - decoration: Decoration{bold: true}, - Style: color.Style{color.OpBold}, - }, - "\x1b[1mfoo\x1b[0m", - }, - { - "multiple attributes", - []TextStyle{AttrBold, AttrUnderline}, - TextStyle{ - decoration: Decoration{ - bold: true, - underline: true, - }, - Style: color.Style{color.OpBold, color.OpUnderscore}, - }, - "\x1b[1;4mfoo\x1b[0m", - }, - { - "multiple attributes and colors", - []TextStyle{AttrBold, FgBlue, AttrUnderline, BgRed}, - TextStyle{ - fg: &Color{basic: &fgBlue}, - bg: &Color{basic: &bgRed}, - decoration: Decoration{ - bold: true, - underline: true, - }, - Style: color.Style{fgBlue, bgRed, color.OpBold, color.OpUnderscore}, - }, - "\x1b[34;41;1;4mfoo\x1b[0m", - }, - { - "rgb fg color", - []TextStyle{New().SetFg(rgbPink)}, - TextStyle{ - fg: &rgbPink, - Style: color.NewRGBStyle(rgbPinkLib).SetOpts(color.Opts{}), - }, - // '38;2' qualifies an RGB foreground color - "\x1b[38;2;255;0;255mfoo\x1b[0m", - }, - { - "rgb fg and bg color", - []TextStyle{New().SetFg(rgbPink).SetBg(rgbYellow)}, - TextStyle{ - fg: &rgbPink, - bg: &rgbYellow, - Style: color.NewRGBStyle(rgbPinkLib, rgbYellowLib).SetOpts(color.Opts{}), - }, - // '48;2' qualifies an RGB background color - "\x1b[38;2;255;0;255;48;2;255;255;0mfoo\x1b[0m", - }, - { - "rgb fg and bg color with opts", - []TextStyle{AttrBold, New().SetFg(rgbPink).SetBg(rgbYellow), AttrUnderline}, - TextStyle{ - fg: &rgbPink, - bg: &rgbYellow, - decoration: Decoration{ - bold: true, - underline: true, - }, - Style: color.NewRGBStyle(rgbPinkLib, rgbYellowLib).SetOpts(color.Opts{color.OpBold, color.OpUnderscore}), - }, - "\x1b[38;2;255;0;255;48;2;255;255;0;1;4mfoo\x1b[0m", - }, - { - "mix color-16 (background) with rgb (foreground)", - []TextStyle{New().SetFg(rgbYellow), BgRed}, - TextStyle{ - fg: &rgbYellow, - bg: &Color{basic: &bgRed}, - Style: color.NewRGBStyle( - rgbYellowLib, - fgRed.RGB(), // We need to use FG here, https://github.com/gookit/color/issues/39 - ).SetOpts(color.Opts{}), - }, - "\x1b[38;2;255;255;0;48;2;197;30;20mfoo\x1b[0m", - }, - { - "mix color-16 (foreground) with rgb (background)", - []TextStyle{FgRed, New().SetBg(rgbYellow)}, - TextStyle{ - fg: &Color{basic: &fgRed}, - bg: &rgbYellow, - Style: color.NewRGBStyle( - fgRed.RGB(), - rgbYellowLib, - ).SetOpts(color.Opts{}), - }, - "\x1b[38;2;197;30;20;48;2;255;255;0mfoo\x1b[0m", - }, - } - - oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions) - defer color.ForceSetColorLevel(oldColorLevel) - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - style := New() - for _, other := range s.toMerge { - style = style.MergeStyle(other) - } - assert.Equal(t, s.expectedStyle, style) - assert.Equal(t, s.expectedStr, style.Sprint(strToPrint)) - }) - } -} - -func TestTemplateFuncMapAddColors(t *testing.T) { - type scenario struct { - name string - tmpl string - expect string - } - - scenarios := []scenario{ - { - "normal template", - "{{ .Foo }}", - "bar", - }, - { - "colored string", - "{{ .Foo | red }}", - "\x1b[31mbar\x1b[0m", - }, - { - "string with decorator", - "{{ .Foo | bold }}", - "\x1b[1mbar\x1b[0m", - }, - { - "string with color and decorator", - "{{ .Foo | bold | red }}", - "\x1b[31m\x1b[1mbar\x1b[0m\x1b[0m", - }, - { - "multiple string with different colors", - "{{ .Foo | red }} - {{ .Foo | blue }}", - "\x1b[31mbar\x1b[0m - \x1b[34mbar\x1b[0m", - }, - } - - oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions) - defer color.ForceSetColorLevel(oldColorLevel) - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - tmpl, err := template.New("test template").Funcs(TemplateFuncMapAddColors(template.FuncMap{})).Parse(s.tmpl) - assert.NoError(t, err) - - buff := bytes.NewBuffer(nil) - err = tmpl.Execute(buff, struct{ Foo string }{"bar"}) - assert.NoError(t, err) - - assert.Equal(t, s.expect, buff.String()) - }) - } -} diff --git a/pkg/gui/style/text_style.go b/pkg/gui/style/text_style.go deleted file mode 100644 index f0a1da0e663..00000000000 --- a/pkg/gui/style/text_style.go +++ /dev/null @@ -1,157 +0,0 @@ -package style - -import ( - "github.com/gookit/color" -) - -// A TextStyle contains a foreground color, background color, and -// decorations (bold/underline/reverse). -// -// Colors may each be either 16-bit or 24-bit RGB colors. When -// we need to produce a string with a TextStyle, if either foreground or -// background color is RGB, we'll promote the other color component to RGB as well. -// We could simplify this code by forcing everything to be RGB, but we're not -// sure how compatible or efficient that would be with various terminals. -// Lazygit will typically stick to 16-bit colors, but users may configure RGB colors. -// -// TextStyles are value objects, not entities, so for example if you want to -// add the bold decoration to a TextStyle, we'll create a new TextStyle with -// that decoration applied. -// -// Decorations are additive, so when we merge two TextStyles, if either is bold -// then the resulting style will also be bold. -// -// So that we aren't rederiving the underlying style each time we want to print -// a string, we derive it when a new TextStyle is created and store it in the -// `style` field. - -type TextStyle struct { - fg *Color - bg *Color - decoration Decoration - - // making this public so that we can use a type switch to get to the underlying - // value so we can cache styles. This is very much a hack. - Style Sprinter -} - -type Sprinter interface { - Sprint(a ...interface{}) string - Sprintf(format string, a ...interface{}) string -} - -func New() TextStyle { - s := TextStyle{} - s.Style = s.deriveStyle() - return s -} - -func (b TextStyle) Sprint(a ...interface{}) string { - return b.Style.Sprint(a...) -} - -func (b TextStyle) Sprintf(format string, a ...interface{}) string { - return b.Style.Sprintf(format, a...) -} - -// note that our receiver here is not a pointer which means we're receiving a -// copy of the original TextStyle. This allows us to mutate and return that -// TextStyle receiver without actually modifying the original. -func (b TextStyle) SetBold() TextStyle { - b.decoration.SetBold() - b.Style = b.deriveStyle() - return b -} - -func (b TextStyle) SetUnderline() TextStyle { - b.decoration.SetUnderline() - b.Style = b.deriveStyle() - return b -} - -func (b TextStyle) SetReverse() TextStyle { - b.decoration.SetReverse() - b.Style = b.deriveStyle() - return b -} - -func (b TextStyle) SetStrikethrough() TextStyle { - b.decoration.SetStrikethrough() - b.Style = b.deriveStyle() - return b -} - -func (b TextStyle) SetBg(color Color) TextStyle { - b.bg = &color - b.Style = b.deriveStyle() - return b -} - -func (b TextStyle) SetFg(color Color) TextStyle { - b.fg = &color - b.Style = b.deriveStyle() - return b -} - -func (b TextStyle) MergeStyle(other TextStyle) TextStyle { - b.decoration = b.decoration.Merge(other.decoration) - - if other.fg != nil { - b.fg = other.fg - } - - if other.bg != nil { - b.bg = other.bg - } - - b.Style = b.deriveStyle() - - return b -} - -func (b TextStyle) deriveStyle() Sprinter { - if b.fg == nil && b.bg == nil { - return color.Style(b.decoration.ToOpts()) - } - - isRgb := (b.fg != nil && b.fg.IsRGB()) || (b.bg != nil && b.bg.IsRGB()) - if isRgb { - return b.deriveRGBStyle() - } - - return b.deriveBasicStyle() -} - -func (b TextStyle) deriveBasicStyle() color.Style { - style := make([]color.Color, 0, 5) - - if b.fg != nil { - style = append(style, *b.fg.basic) - } - - if b.bg != nil { - style = append(style, *b.bg.basic) - } - - style = append(style, b.decoration.ToOpts()...) - - return color.Style(style) -} - -func (b TextStyle) deriveRGBStyle() *color.RGBStyle { - style := &color.RGBStyle{} - - if b.fg != nil { - style.SetFg(*b.fg.ToRGB(false).rgb) - } - - if b.bg != nil { - // We need to convert the bg firstly to a foreground color, - // For more info see - style.SetBg(*b.bg.ToRGB(true).rgb) - } - - style.SetOpts(b.decoration.ToOpts()) - - return style -} diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go deleted file mode 100644 index 151d1566baf..00000000000 --- a/pkg/gui/tasks_adapter.go +++ /dev/null @@ -1,146 +0,0 @@ -package gui - -import ( - "io" - "os/exec" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/tasks" -) - -func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error { - cmdStr := strings.Join(cmd.Args, " ") - gui.c.Log.WithField( - "command", - cmdStr, - ).Debug("RunCommand") - - manager := gui.getManager(view) - - var r io.ReadCloser - start := func() (*exec.Cmd, io.Reader) { - var err error - r, err = cmd.StdoutPipe() - if err != nil { - gui.c.Log.Error(err) - r = nil - } - cmd.Stderr = cmd.Stdout - - if err := cmd.Start(); err != nil { - gui.c.Log.Error(err) - } - - return cmd, r - } - - onClose := func() { - if r != nil { - r.Close() - r = nil - } - } - - linesToRead := gui.linesToReadFromCmdTask(view) - if err := manager.NewTask(manager.NewCmdTask(start, prefix, linesToRead, onClose), cmdStr); err != nil { - gui.c.Log.Error(err) - } - - return nil -} - -func (gui *Gui) newStringTask(view *gocui.View, str string) error { - // using str so that if rendering the exact same thing we don't reset the origin - return gui.newStringTaskWithKey(view, str, str) -} - -func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error { - manager := gui.getManager(view) - - f := func(tasks.TaskOpts) error { - gui.c.SetViewContent(view, str) - return nil - } - - if err := manager.NewTask(f, manager.GetTaskKey()); err != nil { - return err - } - - return nil -} - -func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX int, originY int) error { - manager := gui.getManager(view) - - f := func(tasks.TaskOpts) error { - gui.c.SetViewContent(view, str) - view.SetOrigin(originX, originY) - return nil - } - - if err := manager.NewTask(f, manager.GetTaskKey()); err != nil { - return err - } - - return nil -} - -func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) error { - manager := gui.getManager(view) - - f := func(tasks.TaskOpts) error { - gui.c.ResetViewOrigin(view) - gui.c.SetViewContent(view, str) - return nil - } - - if err := manager.NewTask(f, key); err != nil { - return err - } - - return nil -} - -func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager { - manager, ok := gui.viewBufferManagerMap[view.Name()] - if !ok { - manager = tasks.NewViewBufferManager( - gui.Log, - view, - func() { - // we could clear here, but that actually has the effect of causing a flicker - // where the view may contain no content momentarily as the gui refreshes. - // Instead, we're rewinding the write pointer so that we will just start - // overwriting the existing content from the top down. Once we've reached - // the end of the content do display, we call view.FlushStaleCells() to - // clear out the remaining content from the previous render. - view.Reset() - }, - func() { - gui.render() - }, - func() { - // Need to check if the content of the view is well past the origin. - linesHeight := view.ViewLinesHeight() - _, originY := view.Origin() - if linesHeight < originY { - newOriginY := linesHeight - - view.SetOrigin(0, newOriginY) - } - - view.FlushStaleCells() - }, - func() { - view.SetOrigin(0, 0) - }, - func() gocui.Task { - return gui.c.GocuiGui().NewTask() - }, - ) - gui.viewBufferManagerMap[view.Name()] = manager - } - - return manager -} diff --git a/pkg/gui/test_mode.go b/pkg/gui/test_mode.go deleted file mode 100644 index c5014ad729e..00000000000 --- a/pkg/gui/test_mode.go +++ /dev/null @@ -1,65 +0,0 @@ -package gui - -import ( - "log" - "os" - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/popup" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type IntegrationTest interface { - Run(*GuiDriver) -} - -func (gui *Gui) handleTestMode() { - test := gui.integrationTest - if os.Getenv(components.SANDBOX_ENV_VAR) == "true" { - return - } - - if test != nil { - isIdleChan := make(chan struct{}) - - gui.c.GocuiGui().AddIdleListener(isIdleChan) - - waitUntilIdle := func() { - <-isIdleChan - } - - go func() { - waitUntilIdle() - - toastChan := make(chan string, 100) - gui.PopupHandler.(*popup.PopupHandler).SetToastFunc( - func(message string, kind types.ToastKind) { toastChan <- message }) - - test.Run(&GuiDriver{gui: gui, isIdleChan: isIdleChan, toastChan: toastChan, headless: Headless()}) - - gui.g.Update(func(*gocui.Gui) error { - return gocui.ErrQuit - }) - - waitUntilIdle() - - time.Sleep(time.Second * 1) - - log.Fatal("gocui should have already exited") - }() - - if os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) == "" { - go utils.Safe(func() { - time.Sleep(time.Second * 40) - log.Fatal("40 seconds is up, lazygit recording took too long to complete") - }) - } - } -} - -func Headless() bool { - return os.Getenv("HEADLESS") != "" -} diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go deleted file mode 100644 index 905a9b65d50..00000000000 --- a/pkg/gui/types/common.go +++ /dev/null @@ -1,407 +0,0 @@ -package types - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/tasks" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/sasha-s/go-deadlock" - "gopkg.in/ozeidan/fuzzy-patricia.v3/patricia" -) - -type HelperCommon struct { - *ContextCommon -} - -type ContextCommon struct { - *common.Common - IGuiCommon -} - -type IGuiCommon interface { - IPopupHandler - - LogAction(action string) - LogCommand(cmdStr string, isCommandLine bool) - // we call this when we want to refetch some models and render the result. Internally calls PostRefreshUpdate - Refresh(RefreshOptions) - // we call this when we've changed something in the view model but not the actual model, - // e.g. expanding or collapsing a folder in a file view. Calling 'Refresh' in this - // case would be overkill, although refresh will internally call 'PostRefreshUpdate' - PostRefreshUpdate(Context) - - // renders string to a view without resetting its origin - SetViewContent(view *gocui.View, content string) - // resets cursor and origin of view. Often used before calling SetViewContent - ResetViewOrigin(view *gocui.View) - - // this just re-renders the screen - Render() - // allows rendering to main views (i.e. the ones to the right of the side panel) - // in such a way that avoids concurrency issues when there are slow commands - // to display the output of - RenderToMainViews(opts RefreshMainOpts) - // used purely for the sake of RenderToMainViews to provide the pair of main views we want to render to - MainViewPairs() MainViewPairs - - // return the view buffer manager for the given view, or nil if it doesn't have one - GetViewBufferManagerForView(view *gocui.View) *tasks.ViewBufferManager - - // returns true if command completed successfully - RunSubprocess(cmdObj *oscommands.CmdObj) (bool, error) - RunSubprocessAndRefresh(*oscommands.CmdObj) error - - Suspend() error - Resume() error - - Context() IContextMgr - ContextForKey(key ContextKey) Context - - GetConfig() config.AppConfigurer - GetAppState() *config.AppState - SaveAppState() error - SaveAppStateAndLogError() - - // Runs the given function on the UI thread (this is for things like showing a popup asking a user for input). - // Only necessary to call if you're not already on the UI thread i.e. you're inside a goroutine. - // All controller handlers are executed on the UI thread. - OnUIThread(f func() error) - // Runs a function in a goroutine. Use this whenever you want to run a goroutine and keep track of the fact - // that lazygit is still busy. See docs/dev/Busy.md - OnWorker(f func(gocui.Task) error) - // Function to call at the end of our 'layout' function which renders views - // For example, you may want a view's line to be focused only after that view is - // resized, if in accordion mode. - AfterLayout(f func() error) - - // Wraps a function, attaching the given operation to the given item while - // the function is executing, and also causes the given context to be - // redrawn periodically. This allows the operation to be visualized with a - // spinning loader animation (e.g. when a branch is being pushed). - WithInlineStatus(item HasUrn, operation ItemOperation, contextKey ContextKey, f func(gocui.Task) error) error - - // returns the gocui Gui struct. There is a good chance you don't actually want to use - // this struct and instead want to use another method above - GocuiGui() *gocui.Gui - - Views() Views - - Git() *commands.GitCommand - OS() *oscommands.OSCommand - Model() *Model - - Modes() *Modes - - Mutexes() *Mutexes - - State() IStateAccessor - - KeybindingsOpts() KeybindingsOpts - CallKeybindingHandler(binding *Binding) error - - ResetKeybindings() error - - // hopefully we can remove this once we've moved all our keybinding stuff out of the gui god struct. - GetInitialKeybindingsWithCustomCommands() ([]*Binding, []*gocui.ViewMouseBinding) - - // Returns true if we're running an integration test - RunningIntegrationTest() bool - - // Returns true if we're in a demo recording/playback - InDemo() bool -} - -type IModeMgr interface { - IsAnyModeActive() bool -} - -type IPopupHandler interface { - // The global error handler for gocui. Not to be used by application code. - ErrorHandler(err error) error - // Shows a notification popup with the given title and message to the user. - // - // This is a convenience wrapper around Confirm(), thus the popup can be closed using both 'Enter' and 'ESC'. - Alert(title string, message string) - // Shows a popup asking the user for confirmation. - Confirm(opts ConfirmOpts) - // Shows a popup asking the user for confirmation if condition is true; otherwise, the HandleConfirm function is called directly. - ConfirmIf(condition bool, opts ConfirmOpts) error - // Shows a popup prompting the user for input. - Prompt(opts PromptOpts) - WithWaitingStatus(message string, f func(gocui.Task) error) error - WithWaitingStatusSync(message string, f func() error) error - Menu(opts CreateMenuOptions) error - Toast(message string) - ErrorToast(message string) - SetToastFunc(func(string, ToastKind)) - GetPromptInput() string -} - -type ToastKind int - -const ( - ToastKindStatus ToastKind = iota - ToastKindError -) - -type CreateMenuOptions struct { - Title string - Prompt string // a message that will be displayed above the menu options - Items []*MenuItem - HideCancel bool - ColumnAlignment []utils.Alignment - AllowFilteringKeybindings bool - KeepConfirmKeybindings bool // if true, the keybindings that match the confirm binding will not be removed from menu items -} - -type CreatePopupPanelOpts struct { - HasLoader bool - Editable bool - Title string - Prompt string - HandleConfirm func() error - HandleConfirmPrompt func(string) error - HandleClose func() error - HandleDeleteSuggestion func(int) error - - FindSuggestionsFunc func(string) []*Suggestion - Mask bool - AllowEditSuggestion bool -} - -type ConfirmOpts struct { - Title string - Prompt string - HandleConfirm func() error - HandleClose func() error - FindSuggestionsFunc func(string) []*Suggestion - Editable bool - Mask bool -} - -type PromptOpts struct { - Title string - InitialContent string - FindSuggestionsFunc func(string) []*Suggestion - HandleConfirm func(string) error - AllowEditSuggestion bool - // CAPTURE THIS - HandleClose func() error - HandleDeleteSuggestion func(int) error - Mask bool -} - -type MenuSection struct { - Title string - Column int // The column that this section title should be aligned with -} - -type DisabledReason struct { - Text string - - // When trying to invoke a disabled key binding or menu item, we normally - // show the disabled reason as a toast; setting this to true shows it as an - // error panel instead. This is useful if the text is very long, or if it is - // important enough to show it more prominently, or both. - ShowErrorInPanel bool - - // If true, the keybinding dispatch mechanism will continue to look for - // other handlers for the keypress. - AllowFurtherDispatching bool -} - -type MenuWidget int - -const ( - MenuWidgetNone MenuWidget = iota - MenuWidgetRadioButtonSelected - MenuWidgetRadioButtonUnselected - MenuWidgetCheckboxSelected - MenuWidgetCheckboxUnselected -) - -func MakeMenuRadioButton(value bool) MenuWidget { - if value { - return MenuWidgetRadioButtonSelected - } - return MenuWidgetRadioButtonUnselected -} - -func MakeMenuCheckBox(value bool) MenuWidget { - if value { - return MenuWidgetCheckboxSelected - } - return MenuWidgetCheckboxUnselected -} - -type MenuItem struct { - Label string - - // alternative to Label. Allows specifying columns which will be auto-aligned - LabelColumns []string - - OnPress func() error - - // Only applies when Label is used - OpensMenu bool - - // If Key is defined it allows the user to press the key to invoke the menu - // item, as opposed to having to navigate to it - Key Key - - // A widget to show in front of the menu item. Supported widget types are - // checkboxes and radio buttons, - // This only handles the rendering of the widget; the behavior needs to be - // provided by the client. - Widget MenuWidget - - // The tooltip will be displayed upon highlighting the menu item - Tooltip string - - // If non-nil, show this in a tooltip, style the menu item as disabled, - // and refuse to invoke the command - DisabledReason *DisabledReason - - // Can be used to group menu items into sections with headers. MenuItems - // with the same Section should be contiguous, and will automatically get a - // section header. If nil, the item is not part of a section. - // Note that pointer comparison is used to determine whether two menu items - // belong to the same section, so make sure all your items in a given - // section point to the same MenuSection instance. - Section *MenuSection -} - -// Defining this for the sake of conforming to the HasID interface, which is used -// in list contexts. -func (self *MenuItem) ID() string { - return self.Label -} - -type Model struct { - CommitFiles []*models.CommitFile - Files []*models.File - Submodules []*models.SubmoduleConfig - Branches []*models.Branch - Commits []*models.Commit - StashEntries []*models.StashEntry - SubCommits []*models.Commit - Remotes []*models.Remote - Worktrees []*models.Worktree - - // FilteredReflogCommits are the ones that appear in the reflog panel. - // When in filtering mode we only include the ones that match the given path - FilteredReflogCommits []*models.Commit - // ReflogCommits are the ones used by the branches panel to obtain recency values, - // and for the undo functionality. - // If we're not in filtering mode, CommitFiles and FilteredReflogCommits will be - // one and the same - ReflogCommits []*models.Commit - - BisectInfo *git_commands.BisectInfo - WorkingTreeStateAtLastCommitRefresh models.WorkingTreeState - RemoteBranches []*models.RemoteBranch - Tags []*models.Tag - - // Name of the currently checked out branch. This will be set even when - // we're on a detached head because we're rebasing or bisecting. - CheckedOutBranch string - - MainBranches *git_commands.MainBranches - - // for displaying suggestions while typing in a file name - FilesTrie *patricia.Trie - - Authors map[string]*models.Author - - HashPool *utils.StringPool -} - -type Mutexes struct { - RefreshingFilesMutex deadlock.Mutex - RefreshingBranchesMutex deadlock.Mutex - RefreshingStatusMutex deadlock.Mutex - LocalCommitsMutex deadlock.Mutex - SubCommitsMutex deadlock.Mutex - AuthorsMutex deadlock.Mutex - SubprocessMutex deadlock.Mutex - PopupMutex deadlock.Mutex - PtyMutex deadlock.Mutex -} - -// A long-running operation associated with an item. For example, we'll show -// that a branch is being pushed from so that there's visual feedback about -// what's happening and so that you can see multiple branches' concurrent -// operations -type ItemOperation int - -const ( - ItemOperationNone ItemOperation = iota - ItemOperationPushing - ItemOperationPulling - ItemOperationFastForwarding - ItemOperationDeleting - ItemOperationFetching - ItemOperationCheckingOut -) - -type HasUrn interface { - URN() string -} - -type IStateAccessor interface { - GetRepoPathStack() *utils.StringStack - GetRepoState() IRepoStateAccessor - GetPagerConfig() *config.PagerConfig - // tells us whether we're currently updating lazygit - GetUpdating() bool - SetUpdating(bool) - SetIsRefreshingFiles(bool) - GetIsRefreshingFiles() bool - GetShowExtrasWindow() bool - SetShowExtrasWindow(bool) - GetRetainOriginalDir() bool - SetRetainOriginalDir(bool) - GetItemOperation(item HasUrn) ItemOperation - SetItemOperation(item HasUrn, operation ItemOperation) - ClearItemOperation(item HasUrn) -} - -type IRepoStateAccessor interface { - GetViewsSetup() bool - GetWindowViewNameMap() *utils.ThreadSafeMap[string, string] - GetStartupStage() StartupStage - SetStartupStage(stage StartupStage) - GetCurrentPopupOpts() *CreatePopupPanelOpts - SetCurrentPopupOpts(*CreatePopupPanelOpts) - GetScreenMode() ScreenMode - SetScreenMode(ScreenMode) - InSearchPrompt() bool - GetSearchState() *SearchState - SetSplitMainPanel(bool) - GetSplitMainPanel() bool -} - -// startup stages so we don't need to load everything at once -type StartupStage int - -const ( - INITIAL StartupStage = iota - COMPLETE -) - -// screen sizing determines how much space your selected window takes up (window -// as in panel, not your terminal's window). Sometimes you want a bit more space -// to see the contents of a panel, and this keeps track of how much maximisation -// you've set -type ScreenMode int - -const ( - SCREEN_NORMAL ScreenMode = iota - SCREEN_HALF - SCREEN_FULL -) diff --git a/pkg/gui/types/common_commands.go b/pkg/gui/types/common_commands.go deleted file mode 100644 index 74bfd603b64..00000000000 --- a/pkg/gui/types/common_commands.go +++ /dev/null @@ -1,7 +0,0 @@ -package types - -type CheckoutRefOptions struct { - WaitingStatus string - EnvVars []string - OnRefNotFound func(ref string) error -} diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go deleted file mode 100644 index 917342776e3..00000000000 --- a/pkg/gui/types/context.go +++ /dev/null @@ -1,313 +0,0 @@ -package types - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/patch_exploring" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/sasha-s/go-deadlock" -) - -type ContextKind int - -const ( - // this is your files, branches, commits, contexts etc. They're all on the left hand side - // and you can cycle through them. - SIDE_CONTEXT ContextKind = iota - // This is either the left or right 'main' contexts that appear to the right of the side contexts - MAIN_CONTEXT - // A persistent popup is one that has its own identity e.g. the commit message context. - // When you open a popup over it, we'll let you return to it upon pressing escape - PERSISTENT_POPUP - // A temporary popup is one that could be used for various things (e.g. a generic menu or confirmation popup). - // Because we reuse these contexts, they're temporary in that you can't return to them after you've switched from them - // to some other context, because the context you switched to might actually be the same context but rendering different content. - // We should really be able to spawn new contexts for menus/prompts so that we can actually return to old ones. - TEMPORARY_POPUP - // This contains the command log, underneath the main contexts. - EXTRAS_CONTEXT - // only used by the one global context, purely for the sake of defining keybindings globally - GLOBAL_CONTEXT - // a display context only renders a view. It has no keybindings associated and - // it cannot receive focus. - DISPLAY_CONTEXT -) - -type ParentContexter interface { - SetParentContext(Context) - GetParentContext() Context -} - -type NeedsRerenderOnWidthChangeLevel int - -const ( - // view doesn't render differently when its width changes - NEEDS_RERENDER_ON_WIDTH_CHANGE_NONE NeedsRerenderOnWidthChangeLevel = iota - // view renders differently when its width changes. An example is a view - // that truncates long lines to the view width, e.g. the branches view - NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES - // view renders differently only when the screen mode changes - NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES -) - -type IBaseContext interface { - HasKeybindings - ParentContexter - - GetKind() ContextKind - GetViewName() string - GetView() *gocui.View - GetViewTrait() IViewTrait - GetWindowName() string - SetWindowName(string) - GetKey() ContextKey - IsFocusable() bool - // if a context is transient, then it only appears via some keybinding on another - // context. Until we add support for having multiple of the same context, no two - // of the same transient context can appear at once meaning one might be 'stolen' - // from another window. - IsTransient() bool - // this tells us if the view's bounds are determined by its window or if they're - // determined independently. - HasControlledBounds() bool - - // the total height of the content that the view is currently showing - TotalContentHeight() int - - // to what extent the view needs to be rerendered when its width changes - NeedsRerenderOnWidthChange() NeedsRerenderOnWidthChangeLevel - - // true if the view needs to be rerendered when its height changes - NeedsRerenderOnHeightChange() bool - - // returns the desired title for the view upon activation. If there is no desired title (returns empty string), then - // no title will be set - Title() string - - GetOptionsMap() map[string]string - - AddKeybindingsFn(KeybindingsFn) - AddMouseKeybindingsFn(MouseKeybindingsFn) - ClearAllAttachedControllerFunctions() - - // This is a bit of a hack at the moment: we currently only set an onclick function so that - // our list controller can come along and wrap it in a list-specific click handler. - // We'll need to think of a better way to do this. - AddOnClickFn(func() error) - // Likewise for the focused main view: we need this to communicate between a - // side panel controller and the focused main view controller. - AddOnClickFocusedMainViewFn(func(mainViewName string, clickedLineIdx int) error) - - AddOnRenderToMainFn(func()) - AddOnFocusFn(func(OnFocusOpts)) - AddOnFocusLostFn(func(OnFocusLostOpts)) -} - -type Context interface { - IBaseContext - - HandleFocus(opts OnFocusOpts) - HandleFocusLost(opts OnFocusLostOpts) - FocusLine() - HandleRender() - HandleRenderToMain() -} - -type ISearchHistoryContext interface { - Context - - GetSearchHistory() *utils.HistoryBuffer[string] -} - -type IFilterableContext interface { - Context - IListPanelState - ISearchHistoryContext - - SetFilter(string, bool) - GetFilter() string - ClearFilter() - ReApplyFilter(bool) - IsFiltering() bool - IsFilterableContext() - FilterPrefix(tr *i18n.TranslationSet) string -} - -type ISearchableContext interface { - Context - ISearchHistoryContext - - // These are all implemented by SearchTrait - SetSearchString(string) - GetSearchString() string - ClearSearchString() - IsSearching() bool - IsSearchableContext() - RenderSearchStatus(int, int) - - // This must be implemented by each concrete context. Return nil if not searching the model. - ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition -} - -type DiffableContext interface { - Context - - // Returns the current diff terminals of the currently selected item. - // in the case of a branch it returns both the branch and it's upstream name, - // which becomes an option when you bring up the diff menu, but when you're just - // flicking through branches it will be using the local branch name. - GetDiffTerminals() []string - - // Returns the ref that should be used for creating a diff of what's - // currently shown in the main view against the working directory, in order - // to adjust line numbers in the diff to match the current state of the - // shown file. For example, if the main view shows a range diff of commits, - // we need to pass the first commit of the range. This is used by - // DiffHelper.AdjustLineNumber. - RefForAdjustingLineNumberInDiff() string -} - -type IListContext interface { - Context - - GetSelectedItemId() string - GetSelectedItemIds() ([]string, int, int) - IsItemVisible(item HasUrn) bool - - GetList() IList - ViewIndexToModelIndex(int) int - ModelIndexToViewIndex(int) int - - IsListContext() // used for type switch - RangeSelectEnabled() bool - RenderOnlyVisibleLines() bool - - IndexForGotoBottom() int -} - -type IPatchExplorerContext interface { - Context - - GetState() *patch_exploring.State - SetState(*patch_exploring.State) - GetIncludedLineIndices() []int - RenderAndFocus() - Render() - GetContentToRender() string - NavigateTo(selectedLineIdx int) - GetMutex() *deadlock.Mutex - IsPatchExplorerContext() // used for type switch -} - -type IViewTrait interface { - FocusPoint(yIdx int) - SetRangeSelectStart(yIdx int) - CancelRangeSelect() - SetViewPortContent(content string) - SetViewPortContentAndClearEverythingElse(content string) - SetContentLineCount(lineCount int) - SetContent(content string) - SetFooter(value string) - SetOriginX(value int) - ViewPortYBounds() (int, int) - ScrollLeft() - ScrollRight() - ScrollUp(value int) - ScrollDown(value int) - PageDelta() int - SelectedLineIdx() int - SetHighlight(bool) -} - -type OnFocusOpts struct { - ClickedWindowName string - ClickedViewLineIdx int -} - -type OnFocusLostOpts struct { - NewContextKey ContextKey -} - -type ContextKey string - -type KeybindingsOpts struct { - GetKey func(key string) Key - Config config.KeybindingConfig - Guards KeybindingGuards -} - -type ( - KeybindingsFn func(opts KeybindingsOpts) []*Binding - MouseKeybindingsFn func(opts KeybindingsOpts) []*gocui.ViewMouseBinding -) - -type HasKeybindings interface { - GetKeybindings(opts KeybindingsOpts) []*Binding - GetMouseKeybindings(opts KeybindingsOpts) []*gocui.ViewMouseBinding - GetOnClick() func() error - GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error -} - -type IController interface { - HasKeybindings - Context() Context - - GetOnRenderToMain() func() - GetOnFocus() func(OnFocusOpts) - GetOnFocusLost() func(OnFocusLostOpts) -} - -type IList interface { - IListCursor - Len() int - GetItem(index int) HasUrn -} - -type IListCursor interface { - GetSelectedLineIdx() int - SetSelectedLineIdx(value int) - SetSelection(value int) - MoveSelectedLine(delta int) - ClampSelection() - CancelRangeSelect() - GetRangeStartIdx() (int, bool) - GetSelectionRange() (int, int) - IsSelectingRange() bool - AreMultipleItemsSelected() bool - ToggleStickyRange() - ExpandNonStickyRange(int) -} - -type IListPanelState interface { - SetSelectedLineIdx(int) - SetSelection(int) - GetSelectedLineIdx() int -} - -type ListItem interface { - // ID is a hash when the item is a commit, a filename when the item is a file, 'stash@{4}' when it's a stash entry, 'my_branch' when it's a branch - ID() string - - // Description is something we would show in a message e.g. '123as14: push blah' for a commit - Description() string -} - -type IContextMgr interface { - Push(context Context, opts OnFocusOpts) - Pop() - Replace(context Context) - Activate(context Context, opts OnFocusOpts) - Current() Context - CurrentStatic() Context - CurrentSide() Context - CurrentPopup() []Context - NextInStack(context Context) Context - IsCurrent(c Context) bool - IsCurrentOrParent(c Context) bool - ForEach(func(Context)) - AllList() []IListContext - AllFilterable() []IFilterableContext - AllSearchable() []ISearchableContext - AllPatchExplorer() []IPatchExplorerContext -} diff --git a/pkg/gui/types/keybindings.go b/pkg/gui/types/keybindings.go deleted file mode 100644 index ac3ce049dcc..00000000000 --- a/pkg/gui/types/keybindings.go +++ /dev/null @@ -1,94 +0,0 @@ -package types - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/style" -) - -type Key interface{} // FIXME: find out how to get `gocui.Key | rune` - -// Binding - a keybinding mapping a key and modifier to a handler. The keypress -// is only handled if the given view has focus, or handled globally if the view -// is "" -type Binding struct { - ViewName string - Handler func() error - Key Key - Modifier gocui.Modifier - Description string - // DescriptionFunc is used instead of Description if non-nil, and is useful for dynamic - // descriptions that change depending on context. Important: this must not be an expensive call. - // Note that you should still provide a generic, non-dynamic description in the Description field, - // as this is used in the cheatsheet. - DescriptionFunc func() string - // If defined, this is used in place of Description when showing the keybinding - // in the options view at the bottom left of the screen. - ShortDescription string - // ShortDescriptionFunc is used instead of ShortDescription if non-nil, and is useful for dynamic - // descriptions that change depending on context. Important: this must not be an expensive call. - ShortDescriptionFunc func() string - Alternative string - Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet - OpensMenu bool - - // If true, the keybinding will appear at the bottom of the screen. - // Even if set to true, the keybinding will not be displayed if it is currently - // disabled. We could instead display it with a strikethrough, but there's - // limited realestate to show all the keybindings we want, so we're hiding it instead. - DisplayOnScreen bool - // if unset, the binding will be displayed in the default color. Only applies to the keybinding - // on-screen, not in the keybindings menu. - DisplayStyle *style.TextStyle - - // to be displayed if the keybinding is highlighted from within a menu - Tooltip string - - // Function to decide whether the command is enabled, and why. If this - // returns an empty string, it is; if it returns a non-empty string, it is - // disabled and we show the given text in an error message when trying to - // invoke it. When left nil, the command is always enabled. Note that this - // function must not do expensive calls. - GetDisabledReason func() *DisabledReason -} - -func (b *Binding) IsDisabled() bool { - return b.GetDisabledReason != nil && b.GetDisabledReason() != nil -} - -func (b *Binding) GetDescription() string { - if b.DescriptionFunc != nil { - return b.DescriptionFunc() - } - return b.Description -} - -func (b *Binding) GetShortDescription() string { - if b.ShortDescriptionFunc != nil { - return b.ShortDescriptionFunc() - } - if b.ShortDescription != "" { - return b.ShortDescription - } - return b.GetDescription() -} - -// A guard is a decorator which checks something before executing a handler -// and potentially early-exits if some precondition hasn't been met. -type Guard func(func() error) func() error - -type KeybindingGuards struct { - OutsideFilterMode Guard - NoPopupPanel Guard -} - -type ErrKeybindingNotHandled struct { - DisabledReason *DisabledReason -} - -func (e ErrKeybindingNotHandled) Error() string { - return e.DisabledReason.Text -} - -func (e ErrKeybindingNotHandled) Unwrap() error { - return gocui.ErrKeybindingNotHandled -} diff --git a/pkg/gui/types/modes.go b/pkg/gui/types/modes.go deleted file mode 100644 index a11ed0081f6..00000000000 --- a/pkg/gui/types/modes.go +++ /dev/null @@ -1,15 +0,0 @@ -package types - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking" - "github.com/jesseduffield/lazygit/pkg/gui/modes/diffing" - "github.com/jesseduffield/lazygit/pkg/gui/modes/filtering" - "github.com/jesseduffield/lazygit/pkg/gui/modes/marked_base_commit" -) - -type Modes struct { - Filtering filtering.Filtering - CherryPicking *cherrypicking.CherryPicking - Diffing diffing.Diffing - MarkedBaseCommit marked_base_commit.MarkedBaseCommit -} diff --git a/pkg/gui/types/ref_range.go b/pkg/gui/types/ref_range.go deleted file mode 100644 index 437b1aafd9c..00000000000 --- a/pkg/gui/types/ref_range.go +++ /dev/null @@ -1,8 +0,0 @@ -package types - -import "github.com/jesseduffield/lazygit/pkg/commands/models" - -type RefRange struct { - From models.Ref - To models.Ref -} diff --git a/pkg/gui/types/refresh.go b/pkg/gui/types/refresh.go deleted file mode 100644 index c20a5f54ae5..00000000000 --- a/pkg/gui/types/refresh.go +++ /dev/null @@ -1,46 +0,0 @@ -package types - -// models/views that we can refresh -type RefreshableView int - -const ( - COMMITS RefreshableView = iota - REBASE_COMMITS - SUB_COMMITS - BRANCHES - FILES - STASH - REFLOG - TAGS - REMOTES - WORKTREES - STATUS - SUBMODULES - STAGING - PATCH_BUILDING - MERGE_CONFLICTS - COMMIT_FILES - // not actually a view. Will refactor this later - BISECT_INFO -) - -type RefreshMode int - -const ( - SYNC RefreshMode = iota // wait until everything is done before returning - ASYNC // return immediately, allowing each independent thing to update itself - BLOCK_UI // wrap code in an update call to ensure UI updates all at once and keybindings aren't executed till complete -) - -type RefreshOptions struct { - Then func() - Scope []RefreshableView // e.g. []RefreshableView{COMMITS, BRANCHES}. Leave empty to refresh everything - Mode RefreshMode // one of SYNC (default), ASYNC, and BLOCK_UI - - // Normally a refresh of the branches tries to keep the same branch selected - // (by name); this is usually important in case the order of branches - // changes. Passing true for KeepBranchSelectionIndex suppresses this and - // keeps the selection index the same. Useful after checking out a detached - // head, and selecting index 0. - KeepBranchSelectionIndex bool -} diff --git a/pkg/gui/types/rendering.go b/pkg/gui/types/rendering.go deleted file mode 100644 index 70e47e0330d..00000000000 --- a/pkg/gui/types/rendering.go +++ /dev/null @@ -1,100 +0,0 @@ -package types - -import ( - "os/exec" -) - -type MainContextPair struct { - Main Context - Secondary Context -} - -func NewMainContextPair(main Context, secondary Context) MainContextPair { - return MainContextPair{Main: main, Secondary: secondary} -} - -type MainViewPairs struct { - Normal MainContextPair - MergeConflicts MainContextPair - Staging MainContextPair - PatchBuilding MainContextPair -} - -type ViewUpdateOpts struct { - Title string - SubTitle string - - Task UpdateTask -} - -type RefreshMainOpts struct { - Pair MainContextPair - Main *ViewUpdateOpts - Secondary *ViewUpdateOpts -} - -type UpdateTask interface { - IsUpdateTask() -} - -type RenderStringTask struct { - Str string -} - -func (t *RenderStringTask) IsUpdateTask() {} - -func NewRenderStringTask(str string) *RenderStringTask { - return &RenderStringTask{Str: str} -} - -type RenderStringWithoutScrollTask struct { - Str string -} - -func (t *RenderStringWithoutScrollTask) IsUpdateTask() {} - -func NewRenderStringWithoutScrollTask(str string) *RenderStringWithoutScrollTask { - return &RenderStringWithoutScrollTask{Str: str} -} - -type RenderStringWithScrollTask struct { - Str string - OriginX int - OriginY int -} - -func (t *RenderStringWithScrollTask) IsUpdateTask() {} - -func NewRenderStringWithScrollTask(str string, originX int, originY int) *RenderStringWithScrollTask { - return &RenderStringWithScrollTask{Str: str, OriginX: originX, OriginY: originY} -} - -type RunCommandTask struct { - Cmd *exec.Cmd - Prefix string -} - -func (t *RunCommandTask) IsUpdateTask() {} - -func NewRunCommandTask(cmd *exec.Cmd) *RunCommandTask { - return &RunCommandTask{Cmd: cmd} -} - -func NewRunCommandTaskWithPrefix(cmd *exec.Cmd, prefix string) *RunCommandTask { - return &RunCommandTask{Cmd: cmd, Prefix: prefix} -} - -type RunPtyTask struct { - Cmd *exec.Cmd - Prefix string -} - -func (t *RunPtyTask) IsUpdateTask() {} - -func NewRunPtyTask(cmd *exec.Cmd) *RunPtyTask { - return &RunPtyTask{Cmd: cmd} -} - -func NewRunPtyTaskWithPrefix(cmd *exec.Cmd, prefix string) *RunPtyTask { - return &RunPtyTask{Cmd: cmd, Prefix: prefix} -} diff --git a/pkg/gui/types/search_state.go b/pkg/gui/types/search_state.go deleted file mode 100644 index af806f2c36d..00000000000 --- a/pkg/gui/types/search_state.go +++ /dev/null @@ -1,32 +0,0 @@ -package types - -type SearchType int - -const ( - SearchTypeNone SearchType = iota - // searching is where matches are highlighted but the content is not filtered down - SearchTypeSearch - // filter is where the list is filtered down to only matches - SearchTypeFilter -) - -// TODO: could we remove this entirely? -type SearchState struct { - Context Context - PrevSearchIndex int -} - -func NewSearchState() *SearchState { - return &SearchState{PrevSearchIndex: -1} -} - -func (self *SearchState) SearchType() SearchType { - switch self.Context.(type) { - case IFilterableContext: - return SearchTypeFilter - case ISearchableContext: - return SearchTypeSearch - default: - return SearchTypeNone - } -} diff --git a/pkg/gui/types/suggestion.go b/pkg/gui/types/suggestion.go deleted file mode 100644 index 1d451693263..00000000000 --- a/pkg/gui/types/suggestion.go +++ /dev/null @@ -1,13 +0,0 @@ -package types - -type Suggestion struct { - // value is the thing that we're matching on and the thing that will be submitted if you select the suggestion - Value string - // label is what is actually displayed so it can e.g. contain color - Label string -} - -// Conforming to the HasID interface, which is needed for list contexts -func (self *Suggestion) ID() string { - return self.Value -} diff --git a/pkg/gui/types/version_number.go b/pkg/gui/types/version_number.go deleted file mode 100644 index ae51a27222c..00000000000 --- a/pkg/gui/types/version_number.go +++ /dev/null @@ -1,41 +0,0 @@ -package types - -import ( - "errors" - "regexp" - "strconv" -) - -type VersionNumber struct { - Major, Minor, Patch int -} - -func (v *VersionNumber) IsOlderThan(otherVersion *VersionNumber) bool { - this := v.Major*1000*1000 + v.Minor*1000 + v.Patch - other := otherVersion.Major*1000*1000 + otherVersion.Minor*1000 + otherVersion.Patch - return this < other -} - -func ParseVersionNumber(versionStr string) (*VersionNumber, error) { - re := regexp.MustCompile(`^v?(\d+)\.(\d+)(?:\.(\d+))?$`) - matches := re.FindStringSubmatch(versionStr) - if matches == nil { - return nil, errors.New("unexpected version format: " + versionStr) - } - - v := &VersionNumber{} - var err error - - if v.Major, err = strconv.Atoi(matches[1]); err != nil { - return nil, err - } - if v.Minor, err = strconv.Atoi(matches[2]); err != nil { - return nil, err - } - if len(matches[3]) > 0 { - if v.Patch, err = strconv.Atoi(matches[3]); err != nil { - return nil, err - } - } - return v, nil -} diff --git a/pkg/gui/types/version_number_test.go b/pkg/gui/types/version_number_test.go deleted file mode 100644 index 407a01139db..00000000000 --- a/pkg/gui/types/version_number_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package types - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParseVersionNumber(t *testing.T) { - tests := []struct { - versionStr string - expected *VersionNumber - err error - }{ - { - versionStr: "1.2.3", - expected: &VersionNumber{ - Major: 1, - Minor: 2, - Patch: 3, - }, - err: nil, - }, - { - versionStr: "v1.2.3", - expected: &VersionNumber{ - Major: 1, - Minor: 2, - Patch: 3, - }, - err: nil, - }, - { - versionStr: "12.34.56", - expected: &VersionNumber{ - Major: 12, - Minor: 34, - Patch: 56, - }, - err: nil, - }, - { - versionStr: "1.2", - expected: &VersionNumber{ - Major: 1, - Minor: 2, - Patch: 0, - }, - err: nil, - }, - { - versionStr: "1", - expected: nil, - err: errors.New("unexpected version format: 1"), - }, - { - versionStr: "invalid", - expected: nil, - err: errors.New("unexpected version format: invalid"), - }, - { - versionStr: "junk_before 1.2.3", - expected: nil, - err: errors.New("unexpected version format: junk_before 1.2.3"), - }, - { - versionStr: "1.2.3 junk_after", - expected: nil, - err: errors.New("unexpected version format: 1.2.3 junk_after"), - }, - } - - for _, test := range tests { - t.Run(test.versionStr, func(t *testing.T) { - actual, err := ParseVersionNumber(test.versionStr) - assert.Equal(t, test.expected, actual) - assert.Equal(t, test.err, err) - }) - } -} diff --git a/pkg/gui/types/views.go b/pkg/gui/types/views.go deleted file mode 100644 index 46a67d23a59..00000000000 --- a/pkg/gui/types/views.go +++ /dev/null @@ -1,47 +0,0 @@ -package types - -import "github.com/jesseduffield/gocui" - -type Views struct { - Status *gocui.View - Submodules *gocui.View - Files *gocui.View - Branches *gocui.View - Remotes *gocui.View - Worktrees *gocui.View - Tags *gocui.View - RemoteBranches *gocui.View - ReflogCommits *gocui.View - Commits *gocui.View - Stash *gocui.View - - Main *gocui.View - Secondary *gocui.View - Staging *gocui.View - StagingSecondary *gocui.View - PatchBuilding *gocui.View - PatchBuildingSecondary *gocui.View - MergeConflicts *gocui.View - - Options *gocui.View - Confirmation *gocui.View - Prompt *gocui.View - Menu *gocui.View - CommitMessage *gocui.View - CommitDescription *gocui.View - CommitFiles *gocui.View - SubCommits *gocui.View - Information *gocui.View - AppStatus *gocui.View - Search *gocui.View - SearchPrefix *gocui.View - StatusSpacer1 *gocui.View - StatusSpacer2 *gocui.View - Limit *gocui.View - Suggestions *gocui.View - Tooltip *gocui.View - Extras *gocui.View - - // for playing the easter egg snake game - Snake *gocui.View -} diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go deleted file mode 100644 index 10cb8d48b16..00000000000 --- a/pkg/gui/view_helpers.go +++ /dev/null @@ -1,167 +0,0 @@ -package gui - -import ( - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/tasks" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/spkg/bom" -) - -func (gui *Gui) resetViewOrigin(v *gocui.View) { - v.SetCursor(0, 0) - v.SetOrigin(0, 0) -} - -// Returns the number of lines that we should read initially from a cmd task so -// that the scrollbar has the correct size, along with the number of lines after -// which the view is filled and we can do a first refresh. -func (gui *Gui) linesToReadFromCmdTask(v *gocui.View) tasks.LinesToRead { - height := v.InnerHeight() - oy := v.OriginY() - - linesForFirstRefresh := height + oy + 10 - - // We want to read as many lines initially as necessary to let the - // scrollbar go to its minimum height, so that the scrollbar thumb doesn't - // change size as you scroll down. - minScrollbarHeight := 1 - linesToReadForAccurateScrollbar := height*(height-1)/minScrollbarHeight + oy - - // However, cap it at some arbitrary max limit, so that we don't get - // performance problems for huge monitors or tiny font sizes - if linesToReadForAccurateScrollbar > 5000 { - linesToReadForAccurateScrollbar = 5000 - } - - return tasks.LinesToRead{ - Total: linesToReadForAccurateScrollbar, - InitialRefreshAfter: linesForFirstRefresh, - } -} - -func (gui *Gui) cleanString(s string) string { - output := string(bom.Clean([]byte(s))) - return utils.NormalizeLinefeeds(output) -} - -func (gui *Gui) setViewContent(v *gocui.View, s string) { - v.SetContent(gui.cleanString(s)) -} - -func (gui *Gui) currentViewName() string { - currentView := gui.g.CurrentView() - if currentView == nil { - return "" - } - return currentView.Name() -} - -func (gui *Gui) onViewTabClick(windowName string, tabIndex int) error { - tabs := gui.viewTabMap()[windowName] - if len(tabs) == 0 { - return nil - } - - viewName := tabs[tabIndex].ViewName - - context, ok := gui.helpers.View.ContextForView(viewName) - if !ok { - return nil - } - - gui.c.Context().Push(context, types.OnFocusOpts{}) - return nil -} - -func (gui *Gui) handleNextTab() error { - view := getTabbedView(gui) - if view == nil { - return nil - } - - for _, context := range gui.State.Contexts.Flatten() { - if context.GetViewName() == view.Name() { - return gui.onViewTabClick( - context.GetWindowName(), - utils.ModuloWithWrap(view.TabIndex+1, len(view.Tabs)), - ) - } - } - - return nil -} - -func (gui *Gui) handlePrevTab() error { - view := getTabbedView(gui) - if view == nil { - return nil - } - - for _, context := range gui.State.Contexts.Flatten() { - if context.GetViewName() == view.Name() { - return gui.onViewTabClick( - context.GetWindowName(), - utils.ModuloWithWrap(view.TabIndex-1, len(view.Tabs)), - ) - } - } - - return nil -} - -func getTabbedView(gui *Gui) *gocui.View { - // It safe assumption that only static contexts have tabs - context := gui.c.Context().CurrentStatic() - view, _ := gui.g.View(context.GetViewName()) - return view -} - -func (gui *Gui) render() { - gui.c.OnUIThread(func() error { return nil }) -} - -// postRefreshUpdate is to be called on a context after the state that it depends on has been refreshed -// if the context's view is set to another context we do nothing. -// if the context's view is the current view we trigger a focus; re-selecting the current item. -func (gui *Gui) postRefreshUpdate(c types.Context) { - t := time.Now() - defer func() { - gui.Log.Infof("postRefreshUpdate for %s took %s", c.GetKey(), time.Since(t)) - }() - - c.HandleRender() - - if gui.currentViewName() == c.GetViewName() { - c.HandleFocus(types.OnFocusOpts{}) - } else { - // The FocusLine call is included in the HandleFocus method which we - // call for focused views above; but we need to call it here for - // non-focused views to ensure that an inactive selection is painted - // correctly, and that integration tests see the up to date selection - // state. - c.FocusLine() - - currentCtx := gui.State.ContextMgr.Current() - if currentCtx.GetKey() == context.NORMAL_MAIN_CONTEXT_KEY || currentCtx.GetKey() == context.NORMAL_SECONDARY_CONTEXT_KEY { - // Searching can't cope well with the view being updated while it is being searched. - // We might be able to fix the problems with this, but it doesn't seem easy, so for now - // just don't rerender the view while searching, on the assumption that users will probably - // either search or change their data, but not both at the same time. - if !currentCtx.GetView().IsSearching() { - sidePanelContext := gui.State.ContextMgr.NextInStack(currentCtx) - if sidePanelContext != nil && sidePanelContext.GetKey() == c.GetKey() { - sidePanelContext.HandleRenderToMain() - } - } - } else if c.GetKey() == gui.State.ContextMgr.CurrentStatic().GetKey() { - // If our view is not the current one, but it is the current static context, then this - // can only mean that a popup is showing. In that case we want to refresh the main view - // behind the popup. - c.HandleRenderToMain() - } - } -} diff --git a/pkg/gui/views.go b/pkg/gui/views.go deleted file mode 100644 index 629f5e3961b..00000000000 --- a/pkg/gui/views.go +++ /dev/null @@ -1,275 +0,0 @@ -package gui - -import ( - "errors" - "fmt" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/samber/lo" - "golang.org/x/exp/slices" -) - -type viewNameMapping struct { - viewPtr **gocui.View - name string -} - -func (gui *Gui) orderedViews() []*gocui.View { - return lo.Map(gui.orderedViewNameMappings(), func(v viewNameMapping, _ int) *gocui.View { - return *v.viewPtr - }) -} - -func (gui *Gui) orderedViewNameMappings() []viewNameMapping { - return []viewNameMapping{ - // first layer. Ordering within this layer does not matter because there are - // no overlapping views - {viewPtr: &gui.Views.Status, name: "status"}, - {viewPtr: &gui.Views.Snake, name: "snake"}, - {viewPtr: &gui.Views.Submodules, name: "submodules"}, - {viewPtr: &gui.Views.Worktrees, name: "worktrees"}, - {viewPtr: &gui.Views.Files, name: "files"}, - {viewPtr: &gui.Views.Tags, name: "tags"}, - {viewPtr: &gui.Views.Remotes, name: "remotes"}, - {viewPtr: &gui.Views.Branches, name: "localBranches"}, - {viewPtr: &gui.Views.RemoteBranches, name: "remoteBranches"}, - {viewPtr: &gui.Views.ReflogCommits, name: "reflogCommits"}, - {viewPtr: &gui.Views.Commits, name: "commits"}, - {viewPtr: &gui.Views.Stash, name: "stash"}, - {viewPtr: &gui.Views.SubCommits, name: "subCommits"}, - {viewPtr: &gui.Views.CommitFiles, name: "commitFiles"}, - - {viewPtr: &gui.Views.Staging, name: "staging"}, - {viewPtr: &gui.Views.StagingSecondary, name: "stagingSecondary"}, - {viewPtr: &gui.Views.PatchBuilding, name: "patchBuilding"}, - {viewPtr: &gui.Views.PatchBuildingSecondary, name: "patchBuildingSecondary"}, - {viewPtr: &gui.Views.MergeConflicts, name: "mergeConflicts"}, - {viewPtr: &gui.Views.Secondary, name: "secondary"}, - {viewPtr: &gui.Views.Main, name: "main"}, - - {viewPtr: &gui.Views.Extras, name: "extras"}, - - // bottom line - {viewPtr: &gui.Views.Options, name: "options"}, - {viewPtr: &gui.Views.AppStatus, name: "appStatus"}, - {viewPtr: &gui.Views.Information, name: "information"}, - {viewPtr: &gui.Views.Search, name: "search"}, - // this view shows either the "Search:" prompt when searching, or the "Filter:" prompt when filtering - {viewPtr: &gui.Views.SearchPrefix, name: "searchPrefix"}, - // these views contain one space, and are used as spacers between the various views in the bottom line - {viewPtr: &gui.Views.StatusSpacer1, name: "statusSpacer1"}, - {viewPtr: &gui.Views.StatusSpacer2, name: "statusSpacer2"}, - - // popups. - {viewPtr: &gui.Views.CommitMessage, name: "commitMessage"}, - {viewPtr: &gui.Views.CommitDescription, name: "commitDescription"}, - {viewPtr: &gui.Views.Menu, name: "menu"}, - {viewPtr: &gui.Views.Suggestions, name: "suggestions"}, - {viewPtr: &gui.Views.Confirmation, name: "confirmation"}, - {viewPtr: &gui.Views.Prompt, name: "prompt"}, - {viewPtr: &gui.Views.Tooltip, name: "tooltip"}, - - // this guy will cover everything else when it appears - {viewPtr: &gui.Views.Limit, name: "limit"}, - } -} - -func (gui *Gui) createAllViews() error { - var err error - for _, mapping := range gui.orderedViewNameMappings() { - *mapping.viewPtr, err = gui.prepareView(mapping.name) - if err != nil && !errors.Is(err, gocui.ErrUnknownView) { - return err - } - } - - gui.Views.Options.Frame = false - - gui.Views.SearchPrefix.BgColor = gocui.ColorDefault - gui.Views.SearchPrefix.FgColor = gocui.ColorCyan - gui.Views.SearchPrefix.Frame = false - - gui.Views.StatusSpacer1.Frame = false - gui.Views.StatusSpacer2.Frame = false - - gui.Views.Search.BgColor = gocui.ColorDefault - gui.Views.Search.FgColor = gocui.ColorCyan - gui.Views.Search.Editable = true - gui.Views.Search.Frame = false - gui.Views.Search.Editor = gocui.EditorFunc(gui.searchEditor) - - for _, view := range []*gocui.View{gui.Views.Main, gui.Views.Secondary, gui.Views.Staging, gui.Views.StagingSecondary, gui.Views.PatchBuilding, gui.Views.PatchBuildingSecondary, gui.Views.MergeConflicts} { - view.Wrap = true - view.IgnoreCarriageReturns = true - view.UnderlineHyperLinksOnlyOnHover = true - view.AutoRenderHyperLinks = true - } - - gui.Views.Staging.Wrap = true - gui.Views.StagingSecondary.Wrap = true - gui.Views.PatchBuilding.Wrap = true - gui.Views.PatchBuildingSecondary.Wrap = true - gui.Views.MergeConflicts.Wrap = false - gui.Views.Limit.Wrap = true - - gui.Views.AppStatus.BgColor = gocui.ColorDefault - gui.Views.AppStatus.FgColor = gocui.ColorCyan - gui.Views.AppStatus.Visible = false - gui.Views.AppStatus.Frame = false - - gui.Views.CommitMessage.Visible = false - gui.Views.CommitMessage.Editable = true - gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor) - - gui.Views.CommitDescription.Visible = false - gui.Views.CommitDescription.Editable = true - gui.Views.CommitDescription.Editor = gocui.EditorFunc(gui.commitDescriptionEditor) - - gui.Views.Confirmation.Visible = false - gui.Views.Confirmation.Wrap = true - gui.Views.Confirmation.AutoRenderHyperLinks = true - - gui.Views.Prompt.Visible = false - gui.Views.Prompt.Wrap = false // We don't want wrapping in one-line prompts - gui.Views.Prompt.Editable = true - gui.Views.Prompt.Editor = gocui.EditorFunc(gui.promptEditor) - - gui.Views.Suggestions.Visible = false - - gui.Views.Menu.Visible = false - - gui.Views.Tooltip.Visible = false - gui.Views.Tooltip.AutoRenderHyperLinks = true - - gui.Views.Information.BgColor = gocui.ColorDefault - gui.Views.Information.FgColor = gocui.ColorGreen - gui.Views.Information.Frame = false - - gui.Views.Extras.Autoscroll = true - gui.Views.Extras.Wrap = true - gui.Views.Extras.AutoRenderHyperLinks = true - - gui.Views.Snake.FgColor = gocui.ColorGreen - - return nil -} - -func (gui *Gui) configureViewProperties() { - frameRunes := []rune{'─', '│', '┌', '┐', '└', '┘'} - switch gui.c.UserConfig().Gui.Border { - case "double": - frameRunes = []rune{'═', '║', '╔', '╗', '╚', '╝'} - case "rounded": - frameRunes = []rune{'─', '│', '╭', '╮', '╰', '╯'} - case "hidden": - frameRunes = []rune{' ', ' ', ' ', ' ', ' ', ' '} - case "bold": - frameRunes = []rune{'━', '┃', '┏', '┓', '┗', '┛'} - } - - for _, mapping := range gui.orderedViewNameMappings() { - (*mapping.viewPtr).FrameRunes = frameRunes - (*mapping.viewPtr).BgColor = gui.g.BgColor - (*mapping.viewPtr).FgColor = theme.GocuiDefaultTextColor - (*mapping.viewPtr).SelBgColor = theme.GocuiSelectedLineBgColor - (*mapping.viewPtr).SelFgColor = gui.g.SelFgColor - (*mapping.viewPtr).InactiveViewSelBgColor = theme.GocuiInactiveViewSelectedLineBgColor - } - - gui.c.SetViewContent(gui.Views.SearchPrefix, gui.c.Tr.SearchPrefix) - - gui.Views.Stash.Title = gui.c.Tr.StashTitle - gui.Views.Commits.Title = gui.c.Tr.CommitsTitle - gui.Views.CommitFiles.Title = gui.c.Tr.CommitFiles - gui.Views.Branches.Title = gui.c.Tr.BranchesTitle - gui.Views.Remotes.Title = gui.c.Tr.RemotesTitle - gui.Views.Worktrees.Title = gui.c.Tr.WorktreesTitle - gui.Views.Tags.Title = gui.c.Tr.TagsTitle - gui.Views.Files.Title = gui.c.Tr.FilesTitle - gui.Views.PatchBuilding.Title = gui.c.Tr.Patch - gui.Views.PatchBuildingSecondary.Title = gui.c.Tr.CustomPatch - gui.Views.MergeConflicts.Title = gui.c.Tr.MergeConflictsTitle - gui.Views.Limit.Title = gui.c.Tr.NotEnoughSpace - gui.Views.Status.Title = gui.c.Tr.StatusTitle - gui.Views.Staging.Title = gui.c.Tr.UnstagedChanges - gui.Views.StagingSecondary.Title = gui.c.Tr.StagedChanges - gui.Views.CommitMessage.Title = gui.c.Tr.CommitSummary - gui.Views.CommitDescription.Title = gui.c.Tr.CommitDescriptionTitle - gui.Views.Extras.Title = gui.c.Tr.CommandLog - gui.Views.Snake.Title = gui.c.Tr.SnakeTitle - - for _, view := range []*gocui.View{gui.Views.Main, gui.Views.Secondary, gui.Views.Staging, gui.Views.StagingSecondary, gui.Views.PatchBuilding, gui.Views.PatchBuildingSecondary, gui.Views.MergeConflicts} { - view.Title = gui.c.Tr.DiffTitle - view.CanScrollPastBottom = gui.c.UserConfig().Gui.ScrollPastBottom - view.TabWidth = gui.c.UserConfig().Gui.TabWidth - } - - gui.Views.CommitDescription.FgColor = theme.GocuiDefaultTextColor - gui.Views.CommitDescription.TextArea.AutoWrap = gui.c.UserConfig().Git.Commit.AutoWrapCommitMessage - gui.Views.CommitDescription.TextArea.AutoWrapWidth = gui.c.UserConfig().Git.Commit.AutoWrapWidth - - if gui.c.UserConfig().Gui.ShowPanelJumps { - keyToTitlePrefix := func(key string) string { - if key == "" { - return "" - } - return fmt.Sprintf("[%s]", key) - } - jumpBindings := gui.c.UserConfig().Keybinding.Universal.JumpToBlock - jumpLabels := lo.Map(jumpBindings, func(binding string, _ int) string { - return keyToTitlePrefix(binding) - }) - - gui.Views.Status.TitlePrefix = jumpLabels[0] - - gui.Views.Files.TitlePrefix = jumpLabels[1] - gui.Views.Worktrees.TitlePrefix = jumpLabels[1] - gui.Views.Submodules.TitlePrefix = jumpLabels[1] - - gui.Views.Branches.TitlePrefix = jumpLabels[2] - gui.Views.Remotes.TitlePrefix = jumpLabels[2] - gui.Views.Tags.TitlePrefix = jumpLabels[2] - - gui.Views.Commits.TitlePrefix = jumpLabels[3] - gui.Views.ReflogCommits.TitlePrefix = jumpLabels[3] - - gui.Views.Stash.TitlePrefix = jumpLabels[4] - - gui.Views.Main.TitlePrefix = keyToTitlePrefix(gui.c.UserConfig().Keybinding.Universal.FocusMainView) - } else { - gui.Views.Status.TitlePrefix = "" - - gui.Views.Files.TitlePrefix = "" - gui.Views.Worktrees.TitlePrefix = "" - gui.Views.Submodules.TitlePrefix = "" - - gui.Views.Branches.TitlePrefix = "" - gui.Views.Remotes.TitlePrefix = "" - gui.Views.Tags.TitlePrefix = "" - - gui.Views.Commits.TitlePrefix = "" - gui.Views.ReflogCommits.TitlePrefix = "" - - gui.Views.Stash.TitlePrefix = "" - - gui.Views.Main.TitlePrefix = "" - } - - for _, view := range gui.g.Views() { - // if the view is in our mapping, we'll set the tabs and the tab index - for _, values := range gui.viewTabMap() { - index := slices.IndexFunc(values, func(tabContext context.TabView) bool { - return tabContext.ViewName == view.Name() - }) - - if index != -1 { - view.Tabs = lo.Map(values, func(tabContext context.TabView, _ int) string { - return tabContext.Tab - }) - view.TabIndex = index - } - } - } -} diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go deleted file mode 100644 index cb070b89207..00000000000 --- a/pkg/i18n/english.go +++ /dev/null @@ -1,2211 +0,0 @@ -/* - -Todo list when making a new translation -- Copy this file and rename it to the language you want to translate to like someLanguage.go -- Change the EnglishTranslationSet() name to the language you want to translate to like SomeLanguageTranslationSet() -- Add an entry of someLanguage in GetTranslationSets() -- Remove this todo and the about section - -*/ - -package i18n - -type TranslationSet struct { - NotEnoughSpace string - DiffTitle string - FilesTitle string - BranchesTitle string - CommitsTitle string - StashTitle string - SnakeTitle string - EasterEgg string - UnstagedChanges string - StagedChanges string - StagingTitle string - MergingTitle string - NormalTitle string - LogTitle string - LogXOfYTitle string - CommitSummary string - CredentialsUsername string - CredentialsPassword string - CredentialsPassphrase string - CredentialsPIN string - CredentialsToken string - PassUnameWrong string - Commit string - CommitTooltip string - AmendLastCommit string - AmendLastCommitTitle string - SureToAmend string - NoCommitToAmend string - CommitChangesWithEditor string - FindBaseCommitForFixup string - FindBaseCommitForFixupTooltip string - NoBaseCommitsFound string - MultipleBaseCommitsFoundStaged string - MultipleBaseCommitsFoundUnstaged string - BaseCommitIsAlreadyOnMainBranch string - BaseCommitIsNotInCurrentView string - HunksWithOnlyAddedLinesWarning string - StatusTitle string - GlobalTitle string - Execute string - Stage string - StageTooltip string - ToggleStagedAll string - ToggleStagedAllTooltip string - ToggleTreeView string - ToggleTreeViewTooltip string - OpenDiffTool string - OpenMergeTool string - Refresh string - RefreshTooltip string - Push string - Pull string - PushTooltip string - PullTooltip string - FileFilter string - CopyToClipboardMenu string - CopyFileName string - CopyRelativeFilePath string - CopyAbsoluteFilePath string - CopyFileDiffTooltip string - CopySelectedDiff string - CopyAllFilesDiff string - CopyFileContent string - NoContentToCopyError string - FileNameCopiedToast string - FilePathCopiedToast string - FileDiffCopiedToast string - AllFilesDiffCopiedToast string - FileContentCopiedToast string - FilterStagedFiles string - FilterUnstagedFiles string - FilterTrackedFiles string - FilterUntrackedFiles string - NoFilter string - FilterLabelStagedFiles string - FilterLabelUnstagedFiles string - FilterLabelTrackedFiles string - FilterLabelUntrackedFiles string - FilterLabelConflictingFiles string - MergeConflictsTitle string - MergeConflictDescription_DD string - MergeConflictDescription_AU string - MergeConflictDescription_UA string - MergeConflictDescription_DU string - MergeConflictDescription_UD string - MergeConflictIncomingDiff string - MergeConflictCurrentDiff string - MergeConflictPressEnterToResolve string - MergeConflictKeepFile string - MergeConflictDeleteFile string - Checkout string - CheckoutTooltip string - CantCheckoutBranchWhilePulling string - TagCheckoutTooltip string - RemoteBranchCheckoutTooltip string - CantPullOrPushSameBranchTwice string - NoChangedFiles string - SoftReset string - AlreadyCheckedOutBranch string - SureForceCheckout string - ForceCheckoutBranch string - BranchName string - NewBranchNameBranchOff string - CantDeleteCheckOutBranch string - DeleteBranchTitle string - DeleteBranchesTitle string - DeleteLocalBranch string - DeleteLocalBranches string - DeleteRemoteBranchPrompt string - DeleteRemoteBranchesPrompt string - DeleteLocalAndRemoteBranchPrompt string - DeleteLocalAndRemoteBranchesPrompt string - ForceDeleteBranchTitle string - ForceDeleteBranchMessage string - ForceDeleteBranchesMessage string - RebaseBranch string - RebaseBranchTooltip string - CantRebaseOntoSelf string - CantMergeBranchIntoItself string - ForceCheckout string - ForceCheckoutTooltip string - CheckoutByName string - CheckoutByNameTooltip string - CheckoutPreviousBranch string - RemoteBranchCheckoutTitle string - RemoteBranchCheckoutPrompt string - CheckoutTypeNewBranch string - CheckoutTypeNewBranchTooltip string - CheckoutTypeDetachedHead string - CheckoutTypeDetachedHeadTooltip string - NewBranch string - NewBranchFromStashTooltip string - MoveCommitsToNewBranch string - MoveCommitsToNewBranchTooltip string - MoveCommitsToNewBranchFromMainPrompt string - MoveCommitsToNewBranchMenuPrompt string - MoveCommitsToNewBranchFromBaseItem string - MoveCommitsToNewBranchStackedItem string - CannotMoveCommitsFromDetachedHead string - CannotMoveCommitsNoUpstream string - CannotMoveCommitsBehindUpstream string - CannotMoveCommitsNoUnpushedCommits string - NoBranchesThisRepo string - CommitWithoutMessageErr string - Close string - CloseCancel string - Confirm string - Quit string - SquashTooltip string - CannotSquashOrFixupFirstCommit string - CannotSquashOrFixupMergeCommit string - Fixup string - FixupTooltip string - SureFixupThisCommit string - SureSquashThisCommit string - Squash string - PickCommitTooltip string - Pick string - Edit string - Revert string - RevertCommitTooltip string - Reword string - CommitRewordTooltip string - DropCommit string - DropCommitTooltip string - MoveDownCommit string - MoveUpCommit string - CannotMoveAnyFurther string - CannotMoveMergeCommit string - EditCommit string - EditCommitTooltip string - AmendCommitTooltip string - Amend string - ResetAuthor string - ResetAuthorTooltip string - SetAuthor string - SetAuthorTooltip string - AddCoAuthor string - AmendCommitAttribute string - AmendCommitAttributeTooltip string - SetAuthorPromptTitle string - AddCoAuthorPromptTitle string - AddCoAuthorTooltip string - RewordCommitEditor string - NoCommitsThisBranch string - UpdateRefHere string - ExecCommandHere string - Error string - Undo string - UndoReflog string - RedoReflog string - UndoTooltip string - RedoTooltip string - UndoMergeResolveTooltip string - DiscardAllTooltip string - DiscardUnstagedTooltip string - DiscardUnstagedDisabled string - Pop string - StashPopTooltip string - Drop string - StashDropTooltip string - Apply string - StashApplyTooltip string - NoStashEntries string - StashDrop string - SureDropStashEntry string - StashPop string - SurePopStashEntry string - StashApply string - SureApplyStashEntry string - NoTrackedStagedFilesStash string - NoFilesToStash string - StashChanges string - RenameStash string - RenameStashPrompt string - OpenConfig string - EditConfig string - ForcePush string - ForcePushPrompt string - ForcePushDisabled string - UpdatesRejected string - UpdatesRejectedAndForcePushDisabled string - CheckForUpdate string - CheckingForUpdates string - UpdateAvailableTitle string - UpdateAvailable string - UpdateInProgressWaitingStatus string - UpdateCompletedTitle string - UpdateCompleted string - FailedToRetrieveLatestVersionErr string - OnLatestVersionErr string - MajorVersionErr string - CouldNotFindBinaryErr string - UpdateFailedErr string - ConfirmQuitDuringUpdateTitle string - ConfirmQuitDuringUpdate string - MergeToolTitle string - MergeToolPrompt string - IntroPopupMessage string - NonReloadableConfigWarningTitle string - NonReloadableConfigWarning string - GitconfigParseErr string - EditFile string - EditFileTooltip string - OpenFile string - OpenFileTooltip string - OpenInEditor string - IgnoreFile string - ExcludeFile string - RefreshFiles string - FocusMainView string - Merge string - MergeBranchTooltip string - RegularMergeFastForward string - RegularMergeFastForwardTooltip string - CannotFastForwardMerge string - RegularMergeNonFastForward string - RegularMergeNonFastForwardTooltip string - SquashMergeUncommitted string - SquashMergeUncommittedTooltip string - SquashMergeCommitted string - SquashMergeCommittedTooltip string - ConfirmQuit string - SwitchRepo string - AllBranchesLogGraph string - UnsupportedGitService string - CopyPullRequestURL string - NoBranchOnRemote string - Fetch string - FetchTooltip string - CollapseAll string - CollapseAllTooltip string - ExpandAll string - ExpandAllTooltip string - DisabledInFlatView string - FileEnter string - FileEnterTooltip string - StageSelectionTooltip string - DiscardSelection string - DiscardSelectionTooltip string - ToggleSelectHunk string - SelectHunk string - SelectLineByLine string - ToggleSelectHunkTooltip string - HunkStagingHint string - ToggleSelectionForPatch string - EditHunk string - EditHunkTooltip string - ToggleStagingView string - ToggleStagingViewTooltip string - ReturnToFilesPanel string - FastForward string - FastForwardTooltip string - FastForwarding string - FoundConflictsTitle string - ViewConflictsMenuItem string - AbortMenuItem string - PickHunk string - PickAllHunks string - ViewMergeRebaseOptions string - ViewMergeRebaseOptionsTooltip string - ViewMergeOptions string - ViewRebaseOptions string - ViewCherryPickOptions string - ViewRevertOptions string - NotMergingOrRebasing string - AlreadyRebasing string - RecentRepos string - MergeOptionsTitle string - RebaseOptionsTitle string - CherryPickOptionsTitle string - RevertOptionsTitle string - CommitSummaryTitle string - CommitDescriptionTitle string - CommitDescriptionSubTitle string - CommitDescriptionFooter string - CommitDescriptionFooterTwoBindings string - CommitHooksDisabledSubTitle string - LocalBranchesTitle string - SearchTitle string - TagsTitle string - MenuTitle string - CommitMenuTitle string - RemotesTitle string - RemoteBranchesTitle string - PatchBuildingTitle string - InformationTitle string - SecondaryTitle string - ReflogCommitsTitle string - ConflictsResolved string - Continue string - UnstagedFilesAfterConflictsResolved string - RebasingTitle string - RebasingFromBaseCommitTitle string - SimpleRebase string - InteractiveRebase string - RebaseOntoBaseBranch string - InteractiveRebaseTooltip string - RebaseOntoBaseBranchTooltip string - MustSelectTodoCommits string - FwdNoUpstream string - FwdNoLocalUpstream string - FwdCommitsToPush string - PullRequestNoUpstream string - ErrorOccurred string - ConflictLabel string - PendingRebaseTodosSectionHeader string - PendingCherryPicksSectionHeader string - PendingRevertsSectionHeader string - CommitsSectionHeader string - YouDied string - RewordNotSupported string - ChangingThisActionIsNotAllowed string - NotAllowedMidCherryPickOrRevert string - PickIsOnlyAllowedDuringRebase string - DroppingMergeRequiresSingleSelection string - CherryPickCopy string - CherryPickCopyTooltip string - PasteCommits string - SureCherryPick string - CherryPick string - CannotCherryPickNonCommit string - Donate string - AskQuestion string - PrevHunk string - NextHunk string - PrevConflict string - NextConflict string - SelectPrevHunk string - SelectNextHunk string - ScrollDown string - ScrollUp string - ScrollUpMainWindow string - ScrollDownMainWindow string - SuspendApp string - CannotSuspendApp string - AmendCommitTitle string - AmendCommitPrompt string - AmendCommitWithConflictsMenuPrompt string - AmendCommitWithConflictsContinue string - AmendCommitWithConflictsAmend string - DropCommitTitle string - DropCommitPrompt string - DropUpdateRefPrompt string - DropMergeCommitPrompt string - PullingStatus string - PushingStatus string - FetchingStatus string - SquashingStatus string - FixingStatus string - DeletingStatus string - DroppingStatus string - MovingStatus string - RebasingStatus string - MergingStatus string - LowercaseRebasingStatus string - LowercaseMergingStatus string - LowercaseCherryPickingStatus string - LowercaseRevertingStatus string - AmendingStatus string - CherryPickingStatus string - UndoingStatus string - RedoingStatus string - CheckingOutStatus string - CommittingStatus string - RewordingStatus string - RevertingStatus string - CreatingFixupCommitStatus string - MovingCommitsToNewBranchStatus string - CommitFiles string - SubCommitsDynamicTitle string - CommitFilesDynamicTitle string - RemoteBranchesDynamicTitle string - ViewItemFiles string - CommitFilesTitle string - CheckoutCommitFileTooltip string - CanOnlyDiscardFromLocalCommits string - Remove string - DiscardOldFileChangeTooltip string - DiscardFileChangesTitle string - DiscardFileChangesPrompt string - DisabledForGPG string - CreateRepo string - BareRepo string - InitialBranch string - NoRecentRepositories string - IncorrectNotARepository string - AutoStashTitle string - AutoStashPrompt string - AutoStashForUndo string - AutoStashForCheckout string - AutoStashForNewBranch string - AutoStashForMovingPatchToIndex string - AutoStashForCherryPicking string - AutoStashForReverting string - Discard string - DiscardChangesTitle string - DiscardFileChangesTooltip string - Cancel string - DiscardAllChanges string - DiscardUnstagedChanges string - DiscardAllChangesToAllFiles string - DiscardAnyUnstagedChanges string - DiscardUntrackedFiles string - DiscardStagedChanges string - HardReset string - BranchDeleteTooltip string - TagDeleteTooltip string - Delete string - Reset string - ResetTooltip string - ViewResetOptions string - FileResetOptionsTooltip string - CreateFixupCommit string - CreateFixupCommitTooltip string - CreateAmendCommit string - FixupMenu_Fixup string - FixupMenu_FixupTooltip string - FixupMenu_AmendWithChanges string - FixupMenu_AmendWithChangesTooltip string - FixupMenu_AmendWithoutChanges string - FixupMenu_AmendWithoutChangesTooltip string - SquashAboveCommitsTooltip string - SquashCommitsAboveSelectedTooltip string - SquashCommitsInCurrentBranchTooltip string - SquashAboveCommits string - SquashCommitsInCurrentBranch string - SquashCommitsAboveSelectedCommit string - CannotSquashCommitsInCurrentBranch string - ExecuteShellCommand string - ExecuteShellCommandTooltip string - ShellCommand string - CommitChangesWithoutHook string - ResetTo string - ResetSoftTooltip string - ResetMixedTooltip string - ResetHardTooltip string - ResetHardConfirmation string - PressEnterToReturn string - ViewStashOptions string - ViewStashOptionsTooltip string - Stash string - StashTooltip string - StashAllChanges string - StashStagedChanges string - StashAllChangesKeepIndex string - StashUnstagedChanges string - StashIncludeUntrackedChanges string - StashOptions string - NotARepository string - WorkingDirectoryDoesNotExist string - ScrollLeft string - ScrollRight string - DiscardPatch string - DiscardPatchConfirm string - CantPatchWhileRebasingError string - ToggleAddToPatch string - ToggleAddToPatchTooltip string - ToggleAllInPatch string - ToggleAllInPatchTooltip string - UpdatingPatch string - ViewPatchOptions string - PatchOptionsTitle string - NoPatchError string - EmptyPatchError string - EnterCommitFile string - EnterCommitFileTooltip string - ExitCustomPatchBuilder string - ExitFocusedMainView string - EnterUpstream string - InvalidUpstream string - NewRemote string - NewRemoteName string - NewRemoteUrl string - ViewBranches string - EditRemoteName string - EditRemoteUrl string - RemoveRemote string - RemoveRemoteTooltip string - RemoveRemotePrompt string - DeleteRemoteBranch string - DeleteRemoteBranches string - DeleteRemoteBranchTooltip string - DeleteLocalAndRemoteBranch string - DeleteLocalAndRemoteBranches string - SetAsUpstream string - SetAsUpstreamTooltip string - SetUpstream string - UnsetUpstream string - ViewDivergenceFromUpstream string - ViewDivergenceFromBaseBranch string - CouldNotDetermineBaseBranch string - DivergenceSectionHeaderLocal string - DivergenceSectionHeaderRemote string - ViewUpstreamResetOptions string - ViewUpstreamResetOptionsTooltip string - ViewUpstreamRebaseOptions string - ViewUpstreamRebaseOptionsTooltip string - UpstreamGenericName string - SetUpstreamTitle string - SetUpstreamMessage string - EditRemoteTooltip string - TagCommit string - TagCommitTooltip string - TagNameTitle string - TagMessageTitle string - LightweightTag string - AnnotatedTag string - DeleteTagTitle string - DeleteLocalTag string - DeleteRemoteTag string - DeleteLocalAndRemoteTag string - SelectRemoteTagUpstream string - DeleteRemoteTagPrompt string - DeleteLocalAndRemoteTagPrompt string - RemoteTagDeletedMessage string - PushTagTitle string - PushTag string - PushTagTooltip string - NewTag string - NewTagTooltip string - CreatingTag string - ForceTag string - ForceTagPrompt string - FetchRemoteTooltip string - CheckoutCommitTooltip string - NoBranchesFoundAtCommitTooltip string - GitFlowOptions string - NotAGitFlowBranch string - NewBranchNamePrompt string - IgnoreTracked string - ExcludeTracked string - IgnoreTrackedPrompt string - ExcludeTrackedPrompt string - ViewResetToUpstreamOptions string - NextScreenMode string - PrevScreenMode string - CyclePagers string - CyclePagersTooltip string - CyclePagersDisabledReason string - StartSearch string - StartFilter string - Keybindings string - KeybindingsLegend string - KeybindingsMenuSectionLocal string - KeybindingsMenuSectionGlobal string - KeybindingsMenuSectionNavigation string - RenameBranch string - Upstream string - BranchUpstreamOptionsTitle string - ViewBranchUpstreamOptions string - ViewBranchUpstreamOptionsTooltip string - UpstreamNotSetError string - UpstreamsNotSetError string - NewGitFlowBranchPrompt string - RenameBranchWarning string - OpenKeybindingsMenu string - ResetCherryPick string - ResetCherryPickShort string - NextTab string - PrevTab string - CantUndoWhileRebasing string - CantRedoWhileRebasing string - MustStashWarning string - MustStashTitle string - ConfirmationTitle string - PromptTitle string - PrevPage string - NextPage string - GotoTop string - GotoBottom string - FilteringBy string - ResetInParentheses string - OpenFilteringMenu string - OpenFilteringMenuTooltip string - FilterBy string - ExitFilterMode string - FilterPathOption string - FilterAuthorOption string - EnterFileName string - EnterAuthor string - FilteringMenuTitle string - WillCancelExistingFilterTooltip string - MustExitFilterModeTitle string - MustExitFilterModePrompt string - Diff string - EnterRefToDiff string - EnterRefName string - ExitDiffMode string - DiffingMenuTitle string - SwapDiff string - ViewDiffingOptions string - ViewDiffingOptionsTooltip string - CancelDiffingMode string - OpenCommandLogMenu string - OpenCommandLogMenuTooltip string - ShowingGitDiff string - ShowingDiffForRange string - CommitDiff string - CopyCommitHashToClipboard string - CommitHash string - CommitURL string - PasteCommitMessageFromClipboard string - SurePasteCommitMessage string - CommitMessage string - CommitMessageBody string - CommitSubject string - CommitAuthor string - CommitTags string - CopyCommitAttributeToClipboard string - CopyCommitAttributeToClipboardTooltip string - CopyBranchNameToClipboard string - CopyTagToClipboard string - CopyPathToClipboard string - CommitPrefixPatternError string - CopySelectedTextToClipboard string - NoFilesStagedTitle string - NoFilesStagedPrompt string - BranchNotFoundTitle string - BranchNotFoundPrompt string - BranchUnknown string - DiscardChangeTitle string - DiscardChangePrompt string - CreateNewBranchFromCommit string - BuildingPatch string - ViewCommits string - MinGitVersionError string - RunningCustomCommandStatus string - SubmoduleStashAndReset string - AndResetSubmodules string - EnterSubmoduleTooltip string - BackToParentRepo string - Enter string - CopySubmoduleNameToClipboard string - RemoveSubmodule string - RemoveSubmoduleTooltip string - RemoveSubmodulePrompt string - ResettingSubmoduleStatus string - NewSubmoduleName string - NewSubmoduleUrl string - NewSubmodulePath string - NewSubmodule string - AddingSubmoduleStatus string - UpdateSubmoduleUrl string - UpdatingSubmoduleUrlStatus string - EditSubmoduleUrl string - InitializingSubmoduleStatus string - InitSubmoduleTooltip string - Update string - Initialize string - SubmoduleUpdateTooltip string - UpdatingSubmoduleStatus string - BulkInitSubmodules string - BulkUpdateSubmodules string - BulkDeinitSubmodules string - BulkUpdateRecursiveSubmodules string - ViewBulkSubmoduleOptions string - BulkSubmoduleOptions string - RunningCommand string - SubCommitsTitle string - ExitSubview string - SubmodulesTitle string - NavigationTitle string - SuggestionsCheatsheetTitle string - // Unlike the cheatsheet title above, the real suggestions title has a little message saying press tab to focus - SuggestionsTitle string - SuggestionsSubtitle string - ExtrasTitle string - PullRequestURLCopiedToClipboard string - CommitDiffCopiedToClipboard string - CommitURLCopiedToClipboard string - CommitMessageCopiedToClipboard string - CommitMessageBodyCopiedToClipboard string - CommitSubjectCopiedToClipboard string - CommitAuthorCopiedToClipboard string - CommitTagsCopiedToClipboard string - CommitHasNoTags string - CommitHasNoMessageBody string - PatchCopiedToClipboard string - MessageCopiedToClipboard string - CopiedToClipboard string - ErrCannotEditDirectory string - ErrCannotCopyContentOfDirectory string - ErrStageDirWithInlineMergeConflicts string - ErrRepositoryMovedOrDeleted string - ErrWorktreeMovedOrRemoved string - CommandLog string - ToggleShowCommandLog string - FocusCommandLog string - CommandLogHeader string - RandomTip string - ToggleWhitespaceInDiffView string - ToggleWhitespaceInDiffViewTooltip string - IgnoreWhitespaceDiffViewSubTitle string - IgnoreWhitespaceNotSupportedHere string - IncreaseContextInDiffView string - IncreaseContextInDiffViewTooltip string - DecreaseContextInDiffView string - DecreaseContextInDiffViewTooltip string - DiffContextSizeChanged string - IncreaseRenameSimilarityThreshold string - IncreaseRenameSimilarityThresholdTooltip string - DecreaseRenameSimilarityThreshold string - DecreaseRenameSimilarityThresholdTooltip string - RenameSimilarityThresholdChanged string - CreatePullRequestOptions string - DefaultBranch string - SelectBranch string - SelectTargetRemote string - NoValidRemoteName string - CreatePullRequest string - SelectConfigFile string - NoConfigFileFoundErr string - LoadingFileSuggestions string - LoadingCommits string - MustSpecifyOriginError string - GitOutput string - GitCommandFailed string - AbortTitle string - AbortPrompt string - OpenLogMenu string - OpenLogMenuTooltip string - LogMenuTitle string - ToggleShowGitGraphAll string - ShowGitGraph string - ShowGitGraphTooltip string - SortOrder string - SortOrderPromptLocalBranches string - SortOrderPromptRemoteBranches string - SortAlphabetical string - SortByDate string - SortByRecency string - SortBasedOnReflog string - SortOrderPrompt string - SortCommits string - SortCommitsTooltip string - CantChangeContextSizeError string - OpenCommitInBrowser string - ViewBisectOptions string - ConfirmRevertCommit string - ConfirmRevertCommitRange string - RewordInEditorTitle string - RewordInEditorPrompt string - CheckoutAutostashPrompt string - HardResetAutostashPrompt string - SoftResetPrompt string - UpstreamGone string - NukeDescription string - NukeTreeConfirmation string - DiscardStagedChangesDescription string - EmptyOutput string - Patch string - CustomPatch string - CommitsCopied string - CommitCopied string - ResetPatch string - ResetPatchTooltip string - ApplyPatch string - ApplyPatchTooltip string - ApplyPatchInReverse string - ApplyPatchInReverseTooltip string - RemovePatchFromOriginalCommit string - RemovePatchFromOriginalCommitTooltip string - MovePatchOutIntoIndex string - MovePatchOutIntoIndexTooltip string - MovePatchIntoNewCommit string - MovePatchIntoNewCommitTooltip string - MovePatchIntoNewCommitBefore string - MovePatchIntoNewCommitBeforeTooltip string - MovePatchToSelectedCommit string - MovePatchToSelectedCommitTooltip string - CopyPatchToClipboard string - MustStageFilesAffectedByPatchTitle string - MustStageFilesAffectedByPatchWarning string - NoMatchesFor string - MatchesFor string - SearchKeybindings string - SearchPrefix string - FilterPrefix string - FilterPrefixMenu string - ExitSearchMode string - ExitTextFilterMode string - Switch string - SwitchToWorktree string - SwitchToWorktreeTooltip string - AlreadyCheckedOutByWorktree string - BranchCheckedOutByWorktree string - SomeBranchesCheckedOutByWorktreeError string - DetachWorktreeTooltip string - Switching string - RemoveWorktree string - RemoveWorktreeTitle string - DetachWorktree string - DetachingWorktree string - WorktreesTitle string - WorktreeTitle string - RemoveWorktreePrompt string - ForceRemoveWorktreePrompt string - RemovingWorktree string - AddingWorktree string - CantDeleteCurrentWorktree string - AlreadyInWorktree string - CantDeleteMainWorktree string - NoWorktreesThisRepo string - MissingWorktree string - MainWorktree string - NewWorktree string - NewWorktreePath string - NewWorktreeBase string - RemoveWorktreeTooltip string - BranchNameCannotBeBlank string - NewBranchName string - NewBranchNameLeaveBlank string - ViewWorktreeOptions string - CreateWorktreeFrom string - CreateWorktreeFromDetached string - LcWorktree string - ChangingDirectoryTo string - Name string - Branch string - Path string - MarkedBaseCommitStatus string - MarkAsBaseCommit string - MarkAsBaseCommitTooltip string - CancelMarkedBaseCommit string - MarkedCommitMarker string - FailedToOpenURL string - InvalidLazygitEditURL string - NoCopiedCommits string - DisabledMenuItemPrefix string - QuickStartInteractiveRebase string - QuickStartInteractiveRebaseTooltip string - CannotQuickStartInteractiveRebase string - ToggleRangeSelect string - DismissRangeSelect string - RangeSelectUp string - RangeSelectDown string - RangeSelectNotSupported string - NoItemSelected string - SelectedItemIsNotABranch string - SelectedItemDoesNotHaveFiles string - MultiSelectNotSupportedForSubmodules string - OldCherryPickKeyWarning string - CommandDoesNotSupportOpeningInEditor string - CustomCommands string - NoApplicableCommandsInThisContext string - SelectCommitsOfCurrentBranch string - Actions Actions - Bisect Bisect - Log Log - BreakingChangesTitle string - BreakingChangesMessage string - BreakingChangesByVersion map[string]string - ViewMergeConflictOptions string - ViewMergeConflictOptionsTooltip string - NoFilesWithMergeConflicts string - MergeConflictOptionsTitle string - UseCurrentChanges string - UseIncomingChanges string - UseBothChanges string -} - -type Bisect struct { - MarkStart string - ResetTitle string - ResetPrompt string - ResetOption string - ChooseTerms string - OldTermPrompt string - NewTermPrompt string - BisectMenuTitle string - Mark string - SkipCurrent string - SkipSelected string - CompleteTitle string - CompletePrompt string - CompletePromptIndeterminate string - Bisecting string -} - -type Log struct { - EditRebase string - HandleUndo string - RemoveFile string - CopyToClipboard string - Remove string - CreateFileWithContent string - AppendingLineToFile string - EditRebaseFromBaseCommit string -} - -type Actions struct { - CheckoutCommit string - CheckoutBranchAtCommit string - CheckoutCommitAsDetachedHead string - CheckoutTag string - CheckoutBranch string - CheckoutBranchOrCommit string - ForceCheckoutBranch string - DeleteLocalBranch string - Merge string - SquashMerge string - RebaseBranch string - RenameBranch string - CreateBranch string - FastForwardBranch string - AutoForwardBranches string - CherryPick string - CheckoutFile string - SquashCommitDown string - FixupCommit string - RewordCommit string - DropCommit string - EditCommit string - AmendCommit string - ResetCommitAuthor string - SetCommitAuthor string - AddCommitCoAuthor string - RevertCommit string - CreateFixupCommit string - SquashAllAboveFixupCommits string - MoveCommitUp string - MoveCommitDown string - CopyCommitMessageToClipboard string - CopyCommitMessageBodyToClipboard string - CopyCommitSubjectToClipboard string - CopyCommitDiffToClipboard string - CopyCommitHashToClipboard string - CopyCommitURLToClipboard string - CopyCommitAuthorToClipboard string - CopyCommitAttributeToClipboard string - CopyCommitTagsToClipboard string - CopyPatchToClipboard string - CustomCommand string - DiscardAllChangesInFile string - DiscardAllUnstagedChangesInFile string - StageFile string - StageResolvedFiles string - UnstageFile string - UnstageAllFiles string - StageAllFiles string - ResolveConflictByKeepingFile string - ResolveConflictByDeletingFile string - NotEnoughContextToStage string - NotEnoughContextToDiscard string - NotEnoughContextForCustomPatch string - IgnoreExcludeFile string - IgnoreFileErr string - ExcludeFile string - ExcludeGitIgnoreErr string - Commit string - Push string - Pull string - OpenFile string - StashAllChanges string - StashAllChangesKeepIndex string - StashStagedChanges string - StashUnstagedChanges string - StashIncludeUntrackedChanges string - GitFlowFinish string - GitFlowStart string - CopyToClipboard string - CopySelectedTextToClipboard string - RemovePatchFromCommit string - MovePatchToSelectedCommit string - MovePatchIntoIndex string - MovePatchIntoNewCommit string - DeleteRemoteBranch string - SetBranchUpstream string - AddRemote string - RemoveRemote string - UpdateRemote string - ApplyPatch string - Stash string - PopStash string - ApplyStash string - DropStash string - RenameStash string - RemoveSubmodule string - ResetSubmodule string - AddSubmodule string - UpdateSubmoduleUrl string - InitialiseSubmodule string - BulkInitialiseSubmodules string - BulkUpdateSubmodules string - BulkDeinitialiseSubmodules string - BulkUpdateRecursiveSubmodules string - UpdateSubmodule string - CreateLightweightTag string - CreateAnnotatedTag string - DeleteLocalTag string - DeleteRemoteTag string - PushTag string - NukeWorkingTree string - DiscardUnstagedFileChanges string - RemoveUntrackedFiles string - RemoveStagedFiles string - SoftReset string - MixedReset string - HardReset string - Undo string - Redo string - CopyPullRequestURL string - OpenMergeTool string - OpenCommitInBrowser string - OpenPullRequest string - StartBisect string - ResetBisect string - BisectSkip string - BisectMark string - AddWorktree string -} - -const englishIntroPopupMessage = ` -Thanks for using lazygit! Seriously you rock. Three things to share with you: - - 1) If you want to learn about lazygit's features, watch this vid: - https://youtu.be/CPLdltN7wgE - - 2) Be sure to read the latest release notes at: - https://github.com/jesseduffield/lazygit/releases - - 3) If you're using git, that makes you a programmer! With your help we can make - lazygit better, so consider becoming a contributor and joining the fun at - https://github.com/jesseduffield/lazygit - Or even just star the repo to share the love! - - 4) If lazygit has made your life easier, you can say thanks by clicking the - donate button at the bottom right. Donation does not grant priority support, - but it is much appreciated. - -Press {{confirmationKey}} to get started. -` - -const englishNonReloadableConfigWarning = `The following config settings were changed, but the change doesn't take effect immediately. Please quit and restart lazygit for changes to take effect: - -{{configs}}` - -const englishHunkStagingHint = `Hunk selection mode is now the default for staging. If you want to stage individual lines, press '%s' to switch to line-by-line mode. - -If you prefer to use line-by-line mode by default (like in earlier lazygit versions), add - -gui: - useHunkModeInStagingView: false - -to your lazygit config.` - -// exporting this so we can use it in tests -func EnglishTranslationSet() *TranslationSet { - return &TranslationSet{ - NotEnoughSpace: "Not enough space to render panels", - DiffTitle: "Diff", - FilesTitle: "Files", - BranchesTitle: "Branches", - CommitsTitle: "Commits", - StashTitle: "Stash", - SnakeTitle: "Snake", - EasterEgg: "Easter egg", - UnstagedChanges: "Unstaged changes", - StagedChanges: "Staged changes", - StagingTitle: "Main panel (staging)", - MergingTitle: "Main panel (merging)", - NormalTitle: "Main panel (normal)", - LogTitle: "Log", - LogXOfYTitle: "Log (%d of %d)", - CommitSummary: "Commit summary", - CredentialsUsername: "Username", - CredentialsPassword: "Password", - CredentialsPassphrase: "Enter passphrase for SSH key", - CredentialsPIN: "Enter PIN for SSH key", - CredentialsToken: "Enter Token for SSH key", - PassUnameWrong: "Password, passphrase and/or username wrong", - Commit: "Commit", - CommitTooltip: "Commit staged changes.", - AmendLastCommit: "Amend last commit", - AmendLastCommitTitle: "Amend last commit", - SureToAmend: "Are you sure you want to amend last commit? Afterwards, you can change the commit message from the commits panel.", - NoCommitToAmend: "There's no commit to amend.", - CommitChangesWithEditor: "Commit changes using git editor", - FindBaseCommitForFixup: "Find base commit for fixup", - FindBaseCommitForFixupTooltip: "Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: ", - NoBaseCommitsFound: "No base commits found", - MultipleBaseCommitsFoundStaged: "Multiple base commits found. (Try staging fewer changes at once)", - MultipleBaseCommitsFoundUnstaged: "Multiple base commits found. (Try staging some of the changes)", - BaseCommitIsAlreadyOnMainBranch: "The base commit for this change is already on the main branch", - BaseCommitIsNotInCurrentView: "Base commit is not in current view", - HunksWithOnlyAddedLinesWarning: "There are ranges of only added lines in the diff; be careful to check that these belong in the found base commit.\n\nProceed?", - StatusTitle: "Status", - Execute: "Execute", - Stage: "Stage", - StageTooltip: "Toggle staged for selected file.", - ToggleStagedAll: "Stage all", - ToggleStagedAllTooltip: "Toggle staged/unstaged for all files in working tree.", - ToggleTreeView: "Toggle file tree view", - ToggleTreeViewTooltip: "Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.\n\nThe default can be changed in the config file with the key 'gui.showFileTree'.", - OpenDiffTool: "Open external diff tool (git difftool)", - OpenMergeTool: "Open external merge tool", - Refresh: "Refresh", - RefreshTooltip: "Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`.", - Push: "Push", - PushTooltip: "Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch.", - Pull: "Pull", - PullTooltip: "Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch.", - MergeConflictsTitle: "Merge conflicts", - MergeConflictDescription_DD: "Conflict: this file was moved or renamed both in the current and the incoming changes, but to different destinations. I don't know which ones, but they should both show up as conflicts too (marked 'AU' and 'UA', respectively). The most likely resolution is to delete this file, and pick one of the destinations and delete the other.", - MergeConflictDescription_AU: "Conflict: this file is the destination of a move or rename in the current changes, but was moved or renamed to a different destination in the incoming changes. That other destination should also show up as a conflict (marked 'UA'), as well as the file that both were renamed from (marked 'DD').", - MergeConflictDescription_UA: "Conflict: this file is the destination of a move or rename in the incoming changes, but was moved or renamed to a different destination in the current changes. That other destination should also show up as a conflict (marked 'AU'), as well as the file that both were renamed from (marked 'DD').", - MergeConflictDescription_DU: "Conflict: this file was deleted in the current changes and modified in the incoming changes.\n\nThe most likely resolution is to delete the file after applying the incoming modifications manually to some other place in the code.", - MergeConflictDescription_UD: "Conflict: this file was modified in the current changes and deleted in incoming changes.\n\nThe most likely resolution is to delete the file after applying the current modifications manually to some other place in the code.", - MergeConflictIncomingDiff: "Incoming changes:", - MergeConflictCurrentDiff: "Current changes:", - MergeConflictPressEnterToResolve: "Press %s to resolve.", - MergeConflictKeepFile: "Keep file", - MergeConflictDeleteFile: "Delete file", - Checkout: "Checkout", - CheckoutTooltip: "Checkout selected item.", - CantCheckoutBranchWhilePulling: "You cannot checkout another branch while pulling the current branch", - TagCheckoutTooltip: "Checkout the selected tag as a detached HEAD.", - RemoteBranchCheckoutTooltip: "Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head.", - CantPullOrPushSameBranchTwice: "You cannot push or pull a branch while it is already being pushed or pulled", - FileFilter: "Filter files by status", - CopyToClipboardMenu: "Copy to clipboard", - CopyFileName: "File name", - CopyRelativeFilePath: "Relative path", - CopyAbsoluteFilePath: "Absolute path", - CopyFileDiffTooltip: "If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.", - CopySelectedDiff: "Diff of selected file", - CopyAllFilesDiff: "Diff of all files", - CopyFileContent: "Content of selected file", - NoContentToCopyError: "Nothing to copy", - FileNameCopiedToast: "File name copied to clipboard", - FilePathCopiedToast: "File path copied to clipboard", - FileDiffCopiedToast: "File diff copied to clipboard", - AllFilesDiffCopiedToast: "All files diff copied to clipboard", - FileContentCopiedToast: "File content copied to clipboard", - FilterStagedFiles: "Show only staged files", - FilterUnstagedFiles: "Show only unstaged files", - FilterTrackedFiles: "Show only tracked files", - FilterUntrackedFiles: "Show only untracked files", - NoFilter: "No filter", - FilterLabelStagedFiles: "(only staged)", - FilterLabelUnstagedFiles: "(only unstaged)", - FilterLabelTrackedFiles: "(only tracked)", - FilterLabelUntrackedFiles: "(only untracked)", - FilterLabelConflictingFiles: "(only conflicting)", - NoChangedFiles: "No changed files", - SoftReset: "Soft reset", - AlreadyCheckedOutBranch: "You have already checked out this branch", - SureForceCheckout: "Are you sure you want force checkout? You will lose all local changes", - ForceCheckoutBranch: "Force checkout branch", - BranchName: "Branch name", - NewBranchNameBranchOff: "New branch name (branch is off of '{{.branchName}}')", - CantDeleteCheckOutBranch: "You cannot delete the checked out branch!", - DeleteBranchTitle: "Delete branch '{{.selectedBranchName}}'?", - DeleteBranchesTitle: "Delete selected branches?", - DeleteLocalBranch: "Delete local branch", - DeleteLocalBranches: "Delete local branches", - DeleteRemoteBranchPrompt: "Are you sure you want to delete the remote branch '{{.selectedBranchName}}' from '{{.upstream}}'?", - DeleteRemoteBranchesPrompt: "Are you sure you want to delete the remote branches of the selected branches from their respective remotes?", - DeleteLocalAndRemoteBranchPrompt: "Are you sure you want to delete both '{{.localBranchName}}' from your machine, and '{{.remoteBranchName}}' from '{{.remoteName}}'?", - DeleteLocalAndRemoteBranchesPrompt: "Are you sure you want to delete both the selected branches from your machine, and their remote branches from their respective remotes?", - ForceDeleteBranchTitle: "Force delete branch", - ForceDeleteBranchMessage: "'{{.selectedBranchName}}' is not fully merged. Are you sure you want to delete it?", - ForceDeleteBranchesMessage: "Some of the selected branches are not fully merged. Are you sure you want to delete them?", - RebaseBranch: "Rebase", - RebaseBranchTooltip: "Rebase the checked-out branch onto the selected branch.", - CantRebaseOntoSelf: "You cannot rebase a branch onto itself", - CantMergeBranchIntoItself: "You cannot merge a branch into itself", - ForceCheckout: "Force checkout", - ForceCheckoutTooltip: "Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch.", - CheckoutByName: "Checkout by name", - CheckoutByNameTooltip: "Checkout by name. In the input box you can enter '-' to switch to the previous branch.", - CheckoutPreviousBranch: "Checkout previous branch", - RemoteBranchCheckoutTitle: "Checkout {{.branchName}}", - RemoteBranchCheckoutPrompt: "How would you like to check out this branch?", - CheckoutTypeNewBranch: "New local branch", - CheckoutTypeNewBranchTooltip: "Checkout the remote branch as a local branch, tracking the remote branch.", - CheckoutTypeDetachedHead: "Detached head", - CheckoutTypeDetachedHeadTooltip: "Checkout the remote branch as a detached head, which can be useful if you just want to test the branch but not work on it yourself. You can still create a local branch from it later.", - NewBranch: "New branch", - NewBranchFromStashTooltip: "Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit.", - MoveCommitsToNewBranch: "Move commits to new branch", - MoveCommitsToNewBranchTooltip: "Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.\n\nNote that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which).", - MoveCommitsToNewBranchFromMainPrompt: "This will take all unpushed commits and move them to a new branch (off of {{.baseBranchName}}). It will then hard-reset the current branch to its upstream branch. Do you want to continue?", - MoveCommitsToNewBranchMenuPrompt: "This will take all unpushed commits and move them to a new branch. This new branch can either be created from the main branch ({{.baseBranchName}}) or stacked on top of the current branch. Which of these would you like to do?", - MoveCommitsToNewBranchFromBaseItem: "New branch from base branch (%s)", - MoveCommitsToNewBranchStackedItem: "New branch stacked on current branch (%s)", - CannotMoveCommitsFromDetachedHead: "Cannot move commits from a detached head", - CannotMoveCommitsNoUpstream: "Cannot move commits from a branch that has no upstream branch", - CannotMoveCommitsBehindUpstream: "Cannot move commits from a branch that is behind its upstream branch", - CannotMoveCommitsNoUnpushedCommits: "There are no unpushed commits to move to a new branch", - NoBranchesThisRepo: "No branches for this repo", - CommitWithoutMessageErr: "You cannot commit without a commit message", - Close: "Close", - CloseCancel: "Close/Cancel", - Confirm: "Confirm", - Quit: "Quit", - SquashTooltip: "Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it.", - NoCommitsThisBranch: "No commits for this branch", - UpdateRefHere: "Update branch '{{.ref}}' here", - ExecCommandHere: "Execute the following command here:", - CannotSquashOrFixupFirstCommit: "There's no commit below to squash into", - CannotSquashOrFixupMergeCommit: "Cannot squash or fixup a merge commit", - Fixup: "Fixup", - SureFixupThisCommit: "Are you sure you want to 'fixup' the selected commit(s) into the commit below?", - SureSquashThisCommit: "Are you sure you want to squash the selected commit(s) into the commit below?", - Squash: "Squash", - PickCommitTooltip: "Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase.", - Pick: "Pick", - Edit: "Edit", - Revert: "Revert", - RevertCommitTooltip: "Create a revert commit for the selected commit, which applies the selected commit's changes in reverse.", - Reword: "Reword", - CommitRewordTooltip: "Reword the selected commit's message.", - DropCommit: "Drop", - DropCommitTooltip: "Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts.", - MoveDownCommit: "Move commit down one", - MoveUpCommit: "Move commit up one", - CannotMoveAnyFurther: "Cannot move any further", - CannotMoveMergeCommit: "Cannot move a merge commit", - EditCommit: "Edit (start interactive rebase)", - EditCommitTooltip: "Edit the selected commit. Use this to start an interactive rebase from the selected commit. When already mid-rebase, this will mark the selected commit for editing, which means that upon continuing the rebase, the rebase will pause at the selected commit to allow you to make changes.", - AmendCommitTooltip: "Amend commit with staged changes. If the selected commit is the HEAD commit, this will perform `git commit --amend`. Otherwise the commit will be amended via a rebase.", - Amend: "Amend", - ResetAuthor: "Reset author", - ResetAuthorTooltip: "Reset the commit's author to the currently configured user. This will also renew the author timestamp", - SetAuthor: "Set author", - SetAuthorTooltip: "Set the author based on a prompt", - AddCoAuthor: "Add co-author", - AmendCommitAttribute: "Amend commit attribute", - AmendCommitAttributeTooltip: "Set/Reset commit author or set co-author.", - SetAuthorPromptTitle: "Set author (must look like 'Name ')", - AddCoAuthorPromptTitle: "Add co-author (must look like 'Name ')", - AddCoAuthorTooltip: "Add co-author using the Github/Gitlab metadata Co-authored-by.", - RewordCommitEditor: "Reword with editor", - Error: "Error", - PickHunk: "Pick hunk", - PickAllHunks: "Pick all hunks", - Undo: "Undo", - UndoReflog: "Undo", - RedoReflog: "Redo", - UndoTooltip: "The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration.", - RedoTooltip: "The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration.", - UndoMergeResolveTooltip: "Undo last merge conflict resolution.", - DiscardAllTooltip: "Discard both staged and unstaged changes in '{{.path}}'.", - DiscardUnstagedTooltip: "Discard unstaged changes in '{{.path}}'.", - DiscardUnstagedDisabled: "The selected items don't have both staged and unstaged changes.", - Pop: "Pop", - StashPopTooltip: "Apply the stash entry to your working directory and remove the stash entry.", - Drop: "Drop", - StashDropTooltip: "Remove the stash entry from the stash list.", - Apply: "Apply", - StashApplyTooltip: "Apply the stash entry to your working directory.", - NoStashEntries: "No stash entries", - StashDrop: "Stash drop", - SureDropStashEntry: "Are you sure you want to drop the selected stash entry(ies)?", - StashPop: "Stash pop", - SurePopStashEntry: "Are you sure you want to pop this stash entry?", - StashApply: "Stash apply", - SureApplyStashEntry: "Are you sure you want to apply this stash entry?", - NoTrackedStagedFilesStash: "You have no tracked/staged files to stash", - NoFilesToStash: "You have no files to stash", - StashChanges: "Stash changes", - RenameStash: "Rename stash", - RenameStashPrompt: "Rename stash: {{.stashName}}", - OpenConfig: "Open config file", - EditConfig: "Edit config file", - ForcePush: "Force push", - ForcePushPrompt: "Your branch has diverged from the remote branch. Press {{.cancelKey}} to cancel, or {{.confirmKey}} to force push.", - ForcePushDisabled: "Your branch has diverged from the remote branch and you've disabled force pushing", - UpdatesRejected: "Updates were rejected. Please fetch and examine the remote changes before pushing again.", - UpdatesRejectedAndForcePushDisabled: "Updates were rejected and you have disabled force pushing", - CheckForUpdate: "Check for update", - CheckingForUpdates: "Checking for updates...", - UpdateAvailableTitle: "Update available!", - UpdateAvailable: "Download and install version {{.newVersion}}?", - UpdateInProgressWaitingStatus: "Updating", - UpdateCompletedTitle: "Update completed!", - UpdateCompleted: "Update has been installed successfully. Restart lazygit for it to take effect.", - FailedToRetrieveLatestVersionErr: "Failed to retrieve version information", - OnLatestVersionErr: "You already have the latest version", - MajorVersionErr: "New version ({{.newVersion}}) has non-backwards compatible changes compared to the current version ({{.currentVersion}})", - CouldNotFindBinaryErr: "Could not find any binary at {{.url}}", - UpdateFailedErr: "Update failed: {{.errMessage}}", - ConfirmQuitDuringUpdateTitle: "Currently updating", - ConfirmQuitDuringUpdate: "An update is in progress. Are you sure you want to quit?", - MergeToolTitle: "Merge tool", - MergeToolPrompt: "Are you sure you want to open `git mergetool`?", - IntroPopupMessage: englishIntroPopupMessage, - NonReloadableConfigWarningTitle: "Config changed", - NonReloadableConfigWarning: englishNonReloadableConfigWarning, - GitconfigParseErr: `Gogit failed to parse your gitconfig file due to the presence of unquoted '\' characters. Removing these should fix the issue.`, - EditFile: `Edit file`, - EditFileTooltip: "Open file in external editor.", - OpenFile: `Open file`, - OpenFileTooltip: "Open file in default application.", - OpenInEditor: "Open in editor", - IgnoreFile: `Add to .gitignore`, - ExcludeFile: `Add to .git/info/exclude`, - RefreshFiles: `Refresh files`, - FocusMainView: "Focus main view", - Merge: `Merge`, - MergeBranchTooltip: "View options for merging the selected item into the current branch (regular merge, squash merge)", - RegularMergeFastForward: "Regular merge (fast-forward)", - RegularMergeFastForwardTooltip: "Fast-forward '{{.checkedOutBranch}}' to '{{.selectedBranch}}' without creating a merge commit.", - CannotFastForwardMerge: "Cannot fast-forward '{{.checkedOutBranch}}' to '{{.selectedBranch}}'", - RegularMergeNonFastForward: "Regular merge (with merge commit)", - RegularMergeNonFastForwardTooltip: "Merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}', creating a merge commit.", - SquashMergeUncommitted: "Squash merge and leave uncommitted", - SquashMergeUncommittedTooltip: "Squash merge '{{.selectedBranch}}' into the working tree.", - SquashMergeCommitted: "Squash merge and commit", - SquashMergeCommittedTooltip: "Squash merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}' as a single commit.", - ConfirmQuit: `Are you sure you want to quit?`, - SwitchRepo: `Switch to a recent repo`, - AllBranchesLogGraph: `Show/cycle all branch logs`, - UnsupportedGitService: `Unsupported git service`, - CreatePullRequest: `Create pull request`, - CopyPullRequestURL: `Copy pull request URL to clipboard`, - NoBranchOnRemote: `This branch doesn't exist on remote. You need to push it to remote first.`, - Fetch: `Fetch`, - FetchTooltip: "Fetch changes from remote.", - CollapseAll: "Collapse all files", - CollapseAllTooltip: "Collapse all directories in the files tree", - ExpandAll: "Expand all files", - ExpandAllTooltip: "Expand all directories in the file tree", - DisabledInFlatView: "Not available in flat view", - FileEnter: `Stage lines / Collapse directory`, - FileEnterTooltip: "If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it.", - StageSelectionTooltip: `Toggle selection staged / unstaged.`, - DiscardSelection: `Discard`, - DiscardSelectionTooltip: "When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change.", - ToggleRangeSelect: "Toggle range select", - DismissRangeSelect: "Dismiss range select", - ToggleSelectHunk: "Toggle hunk selection", - SelectHunk: "Select hunks", - SelectLineByLine: "Select line-by-line", - ToggleSelectHunkTooltip: "Toggle line-by-line vs. hunk selection mode.", - HunkStagingHint: englishHunkStagingHint, - ToggleSelectionForPatch: `Toggle lines in patch`, - EditHunk: `Edit hunk`, - EditHunkTooltip: "Edit selected hunk in external editor.", - ToggleStagingView: "Switch view", - ToggleStagingViewTooltip: "Switch to other view (staged/unstaged changes).", - ReturnToFilesPanel: `Return to files panel`, - FastForward: `Fast-forward`, - FastForwardTooltip: "Fast-forward selected branch from its upstream.", - FastForwarding: "Fast-forwarding", - FoundConflictsTitle: "Conflicts!", - ViewConflictsMenuItem: "View conflicts", - AbortMenuItem: "Abort the %s", - ViewMergeRebaseOptions: "View merge/rebase options", - ViewMergeRebaseOptionsTooltip: "View options to abort/continue/skip the current merge/rebase.", - ViewMergeOptions: "View merge options", - ViewRebaseOptions: "View rebase options", - ViewCherryPickOptions: "View cherry-pick options", - ViewRevertOptions: "View revert options", - NotMergingOrRebasing: "You are currently neither rebasing nor merging", - AlreadyRebasing: "Can't perform this action during a rebase", - RecentRepos: "Recent repositories", - MergeOptionsTitle: "Merge options", - RebaseOptionsTitle: "Rebase options", - CherryPickOptionsTitle: "Cherry-pick options", - RevertOptionsTitle: "Revert options", - CommitSummaryTitle: "Commit summary", - CommitDescriptionTitle: "Commit description", - CommitDescriptionSubTitle: "Press {{.togglePanelKeyBinding}} to toggle focus, {{.commitMenuKeybinding}} to open menu", - CommitDescriptionFooter: "Press {{.confirmInEditorKeybinding}} to submit", - CommitDescriptionFooterTwoBindings: "Press {{.confirmInEditorKeybinding1}} or {{.confirmInEditorKeybinding2}} to submit", - CommitHooksDisabledSubTitle: "(hooks disabled)", - LocalBranchesTitle: "Local branches", - SearchTitle: "Search", - TagsTitle: "Tags", - MenuTitle: "Menu", - CommitMenuTitle: "Commit Menu", - RemotesTitle: "Remotes", - RemoteBranchesTitle: "Remote branches", - PatchBuildingTitle: "Main panel (patch building)", - InformationTitle: "Information", - SecondaryTitle: "Secondary", - ReflogCommitsTitle: "Reflog", - GlobalTitle: "Global keybindings", - ConflictsResolved: "All merge conflicts resolved. Continue the %s?", - Continue: "Continue", - UnstagedFilesAfterConflictsResolved: "Files have been modified since conflicts were resolved. Auto-stage them and continue?", - Keybindings: "Keybindings", - KeybindingsMenuSectionLocal: "Local", - KeybindingsMenuSectionGlobal: "Global", - KeybindingsMenuSectionNavigation: "Navigation", - RebasingTitle: "Rebase '{{.checkedOutBranch}}'", - RebasingFromBaseCommitTitle: "Rebase '{{.checkedOutBranch}}' from marked base", - SimpleRebase: "Simple rebase onto '{{.ref}}'", - InteractiveRebase: "Interactive rebase onto '{{.ref}}'", - RebaseOntoBaseBranch: "Rebase onto base branch ({{.baseBranch}})", - InteractiveRebaseTooltip: "Begin an interactive rebase with a break at the start, so you can update the TODO commits before continuing.", - RebaseOntoBaseBranchTooltip: "Rebase the checked out branch onto its base branch (i.e. the closest main branch).", - MustSelectTodoCommits: "When rebasing, this action only works on a selection of TODO commits.", - FwdNoUpstream: "Cannot fast-forward a branch with no upstream", - FwdNoLocalUpstream: "Cannot fast-forward a branch whose remote is not registered locally", - FwdCommitsToPush: "Cannot fast-forward a branch with commits to push", - PullRequestNoUpstream: "Cannot open a pull request for a branch with no upstream", - ErrorOccurred: "An error occurred! Please create an issue at", - ConflictLabel: "CONFLICT", - PendingRebaseTodosSectionHeader: "Pending rebase todos", - PendingCherryPicksSectionHeader: "Pending cherry-picks", - PendingRevertsSectionHeader: "Pending reverts", - CommitsSectionHeader: "Commits", - YouDied: "YOU DIED!", - RewordNotSupported: "Rewording commits while interactively rebasing is not currently supported", - ChangingThisActionIsNotAllowed: "Changing this kind of rebase todo entry is not allowed", - NotAllowedMidCherryPickOrRevert: "This action is not allowed while cherry-picking or reverting", - PickIsOnlyAllowedDuringRebase: "This action is only allowed while rebasing", - DroppingMergeRequiresSingleSelection: "Dropping a merge commit requires a single selected item", - CherryPickCopy: "Copy (cherry-pick)", - CherryPickCopyTooltip: "Mark commit as copied. Then, within the local commits view, you can press `{{.paste}}` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `{{.escape}}` to cancel the selection.", - PasteCommits: "Paste (cherry-pick)", - SureCherryPick: "Are you sure you want to cherry-pick the {{.numCommits}} copied commit(s) onto this branch?", - CherryPick: "Cherry-pick", - CannotCherryPickNonCommit: "Cannot cherry-pick this kind of todo item", - Donate: "Donate", - AskQuestion: "Ask Question", - PrevHunk: "Go to previous hunk", - NextHunk: "Go to next hunk", - PrevConflict: "Previous conflict", - NextConflict: "Next conflict", - SelectPrevHunk: "Previous hunk", - SelectNextHunk: "Next hunk", - ScrollDown: "Scroll down", - ScrollUp: "Scroll up", - ScrollUpMainWindow: "Scroll up main window", - ScrollDownMainWindow: "Scroll down main window", - SuspendApp: "Suspend the application", - CannotSuspendApp: "Suspending the application is not supported on Windows", - AmendCommitTitle: "Amend commit", - AmendCommitPrompt: "Are you sure you want to amend this commit with your staged files?", - AmendCommitWithConflictsMenuPrompt: "WARNING: you are about to amend the last finished commit with your resolved conflicts. This is very unlikely to be what you want at this point. More likely, you simply want to continue the rebase instead.\n\nDo you still want to amend the previous commit?", - AmendCommitWithConflictsContinue: "No, continue rebase", - AmendCommitWithConflictsAmend: "Yes, amend previous commit", - DropCommitTitle: "Drop commit", - DropCommitPrompt: "Are you sure you want to drop the selected commit(s)?", - DropMergeCommitPrompt: "Are you sure you want to drop the selected merge commit? Note that it will also drop all the commits that were merged in by it.", - DropUpdateRefPrompt: "Are you sure you want to delete the selected update-ref todo(s)? This is irreversible except by aborting the rebase.", - PullingStatus: "Pulling", - PushingStatus: "Pushing", - FetchingStatus: "Fetching", - SquashingStatus: "Squashing", - FixingStatus: "Fixing up", - DeletingStatus: "Deleting", - DroppingStatus: "Dropping", - MovingStatus: "Moving", - RebasingStatus: "Rebasing", - MergingStatus: "Merging", - LowercaseRebasingStatus: "rebasing", // lowercase because it shows up in parentheses - LowercaseMergingStatus: "merging", // lowercase because it shows up in parentheses - LowercaseCherryPickingStatus: "cherry-picking", // lowercase because it shows up in parentheses - LowercaseRevertingStatus: "reverting", // lowercase because it shows up in parentheses - AmendingStatus: "Amending", - CherryPickingStatus: "Cherry-picking", - UndoingStatus: "Undoing", - RedoingStatus: "Redoing", - CheckingOutStatus: "Checking out", - CommittingStatus: "Committing", - RewordingStatus: "Rewording", - RevertingStatus: "Reverting", - CreatingFixupCommitStatus: "Creating fixup commit", - MovingCommitsToNewBranchStatus: "Moving commits to new branch", - CommitFiles: "Commit files", - SubCommitsDynamicTitle: "Commits (%s)", - CommitFilesDynamicTitle: "Diff files (%s)", - RemoteBranchesDynamicTitle: "Remote branches (%s)", - ViewItemFiles: "View files", - CommitFilesTitle: "Commit files", - CheckoutCommitFileTooltip: "Checkout file. This replaces the file in your working tree with the version from the selected commit.", - CanOnlyDiscardFromLocalCommits: "Changes can only be discarded from local commits", - Remove: "Remove", - DiscardOldFileChangeTooltip: "Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file.", - DiscardFileChangesTitle: "Discard file changes", - DiscardFileChangesPrompt: "Are you sure you want to remove changes to the selected file(s) from this commit?\n\nThis action will start a rebase, reverting these file changes. Be aware that if subsequent commits depend on these changes, you may need to resolve conflicts.\nNote: This will also reset any active custom patches.", - DisabledForGPG: "Feature not available for users using GPG.\n\nIf you are using a passphrase agent (e.g. gpg-agent) so that you don't have to type your passphrase when signing, you can enable this feature by adding\n\ngit:\n overrideGpg: true\n\nto your lazygit config file.", - CreateRepo: "Not in a git repository. Create a new git repository? (y/N): ", - BareRepo: "You've attempted to open Lazygit in a bare repo but Lazygit does not yet support bare repos. Open most recent repo? (y/n) ", - InitialBranch: "Branch name? (leave empty for git's default): ", - NoRecentRepositories: "Must open lazygit in a git repository. No valid recent repositories. Exiting.", - IncorrectNotARepository: "The value of 'notARepository' is incorrect. It should be one of 'prompt', 'create', 'skip', or 'quit'.", - AutoStashTitle: "Autostash?", - AutoStashPrompt: "You must stash and pop your changes to bring them across. Do this automatically? (enter/esc)", - AutoStashForUndo: "Auto-stashing changes for undoing to %s", - AutoStashForCheckout: "Auto-stashing changes for checking out %s", - AutoStashForNewBranch: "Auto-stashing changes for creating new branch %s", - AutoStashForMovingPatchToIndex: "Auto-stashing changes for moving custom patch to index from %s", - AutoStashForCherryPicking: "Auto-stashing changes for cherry-picking commits", - AutoStashForReverting: "Auto-stashing changes for reverting commits", - Discard: "Discard", - DiscardFileChangesTooltip: "View options for discarding changes to the selected file.", - DiscardChangesTitle: "Discard changes", - Cancel: "Cancel", - DiscardAllChanges: "Discard all changes", - DiscardUnstagedChanges: "Discard unstaged changes", - DiscardAllChangesToAllFiles: "Nuke working tree", - DiscardAnyUnstagedChanges: "Discard unstaged changes", - DiscardUntrackedFiles: "Discard untracked files", - DiscardStagedChanges: "Discard staged changes", - HardReset: "Hard reset", - BranchDeleteTooltip: "View delete options for local/remote branch.", - TagDeleteTooltip: "View delete options for local/remote tag.", - Delete: "Delete", - Reset: "Reset", - ResetTooltip: "View reset options (soft/mixed/hard) for resetting onto selected item.", - ResetSoftTooltip: "Reset HEAD to the chosen commit, and keep the changes between the current and chosen commit as staged changes.", - ResetMixedTooltip: "Reset HEAD to the chosen commit, and keep the changes between the current and chosen commit as unstaged changes.", - ResetHardTooltip: "Reset HEAD to the chosen commit, and discard all changes between the current and chosen commit, as well as all current modifications in the working tree.", - ResetHardConfirmation: "Are you sure you want to do a hard reset? This will discard all uncommitted changes (both staged and unstaged), which is not undoable.", - ViewResetOptions: `Reset`, - FileResetOptionsTooltip: "View reset options for working tree (e.g. nuking the working tree).", - FixupTooltip: "Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded.", - CreateFixupCommit: "Create fixup commit", - CreateFixupCommitTooltip: "Create 'fixup!' commit for the selected commit. Later on, you can press `{{.squashAbove}}` on this same commit to apply all above fixup commits.", - CreateAmendCommit: `Create "amend!" commit`, - FixupMenu_Fixup: "fixup! commit", - FixupMenu_FixupTooltip: "Lets you fixup another commit and keep the original commit's message.", - FixupMenu_AmendWithChanges: "amend! commit with changes", - FixupMenu_AmendWithChangesTooltip: "Lets you fixup another commit and also change its commit message.", - FixupMenu_AmendWithoutChanges: "amend! commit without changes (pure reword)", - FixupMenu_AmendWithoutChangesTooltip: "Lets you change the commit message of another commit without changing its content.", - SquashAboveCommits: "Apply fixup commits", - SquashAboveCommitsTooltip: `Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash).`, - SquashCommitsAboveSelectedTooltip: `Squash all 'fixup!' commits above the selected commit (autosquash).`, - SquashCommitsInCurrentBranchTooltip: `Squash all 'fixup!' commits in the current branch (autosquash).`, - SquashCommitsInCurrentBranch: "In current branch", - SquashCommitsAboveSelectedCommit: "Above the selected commit", - CannotSquashCommitsInCurrentBranch: "Cannot squash commits in current branch: the HEAD commit is a merge commit or is present on the main branch.", - ExecuteShellCommand: "Execute shell command", - ExecuteShellCommandTooltip: "Bring up a prompt where you can enter a shell command to execute.", - ShellCommand: "Shell command:", - CommitChangesWithoutHook: "Commit changes without pre-commit hook", - ResetTo: `Reset to`, - PressEnterToReturn: "Press enter to return to lazygit", - ViewStashOptions: "View stash options", - ViewStashOptionsTooltip: "View stash options (e.g. stash all, stash staged, stash unstaged).", - Stash: "Stash", - StashTooltip: "Stash all changes. For other variations of stashing, use the view stash options keybinding.", - StashAllChanges: "Stash all changes", - StashStagedChanges: "Stash staged changes", - StashAllChangesKeepIndex: "Stash all changes and keep index", - StashUnstagedChanges: "Stash unstaged changes", - StashIncludeUntrackedChanges: "Stash all changes including untracked files", - StashOptions: "Stash options", - NotARepository: "Error: must be run inside a git repository", - WorkingDirectoryDoesNotExist: "Error: the current working directory does not exist", - ScrollLeft: "Scroll left", - ScrollRight: "Scroll right", - DiscardPatch: "Discard patch", - DiscardPatchConfirm: "You can only build a patch from one commit/stash-entry at a time. Discard current patch?", - CantPatchWhileRebasingError: "You cannot build a patch or run patch commands while in a merging or rebasing state", - ToggleAddToPatch: "Toggle file included in patch", - ToggleAddToPatchTooltip: "Toggle whether the file is included in the custom patch. See {{.doc}}.", - ToggleAllInPatch: "Toggle all files", - ToggleAllInPatchTooltip: "Add/remove all commit's files to custom patch. See {{.doc}}.", - UpdatingPatch: "Updating patch", - ViewPatchOptions: "View custom patch options", - PatchOptionsTitle: "Patch options", - NoPatchError: "No patch created yet. To start building a patch, use 'space' on a commit file or enter to add specific lines", - EmptyPatchError: "Patch is still empty. Add some files or lines to your patch first.", - EnterCommitFile: "Enter file / Toggle directory collapsed", - EnterCommitFileTooltip: "If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory.", - ExitCustomPatchBuilder: `Exit custom patch builder`, - ExitFocusedMainView: "Exit back to side panel", - EnterUpstream: `Enter upstream as ' '`, - InvalidUpstream: "Invalid upstream. Must be in the format ' '", - NewRemote: `New remote`, - NewRemoteName: `New remote name:`, - NewRemoteUrl: `New remote url:`, - ViewBranches: "View branches", - EditRemoteName: `Enter updated remote name for {{.remoteName}}:`, - EditRemoteUrl: `Enter updated remote url for {{.remoteName}}:`, - RemoveRemote: `Remove remote`, - RemoveRemoteTooltip: `Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected.`, - RemoveRemotePrompt: "Are you sure you want to remove remote?", - DeleteRemoteBranch: "Delete remote branch", - DeleteRemoteBranches: "Delete remote branches", - DeleteRemoteBranchTooltip: "Delete the remote branch from the remote.", - DeleteLocalAndRemoteBranch: "Delete local and remote branch", - DeleteLocalAndRemoteBranches: "Delete local and remote branches", - SetAsUpstream: "Set as upstream", - SetAsUpstreamTooltip: "Set the selected remote branch as the upstream of the checked-out branch.", - SetUpstream: "Set upstream of selected branch", - UnsetUpstream: "Unset upstream of selected branch", - ViewDivergenceFromUpstream: "View divergence from upstream", - ViewDivergenceFromBaseBranch: "View divergence from base branch ({{.baseBranch}})", - CouldNotDetermineBaseBranch: "Couldn't determine base branch", - DivergenceSectionHeaderLocal: "Local", - DivergenceSectionHeaderRemote: "Remote", - ViewUpstreamResetOptions: "Reset checked-out branch onto {{.upstream}}", - ViewUpstreamResetOptionsTooltip: "View options for resetting the checked-out branch onto {{upstream}}. Note: this will not reset the selected branch onto the upstream, it will reset the checked-out branch onto the upstream.", - ViewUpstreamRebaseOptions: "Rebase checked-out branch onto {{.upstream}}", - ViewUpstreamRebaseOptionsTooltip: "View options for rebasing the checked-out branch onto {{upstream}}. Note: this will not rebase the selected branch onto the upstream, it will rebase the checked-out branch onto the upstream.", - UpstreamGenericName: "upstream of selected branch", - SetUpstreamTitle: "Set upstream branch", - SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'?", - EditRemoteTooltip: "Edit the selected remote's name or URL.", - TagCommit: "Tag commit", - TagCommitTooltip: "Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description.", - TagNameTitle: "Tag name", - TagMessageTitle: "Tag description", - AnnotatedTag: "Annotated tag", - LightweightTag: "Lightweight tag", - DeleteTagTitle: "Delete tag '{{.tagName}}'?", - DeleteLocalTag: "Delete local tag", - DeleteRemoteTag: "Delete remote tag", - DeleteLocalAndRemoteTag: "Delete local and remote tag", - RemoteTagDeletedMessage: "Remote tag deleted", - SelectRemoteTagUpstream: "Remote from which to remove tag '{{.tagName}}':", - DeleteRemoteTagPrompt: "Are you sure you want to delete the remote tag '{{.tagName}}' from '{{.upstream}}'?", - DeleteLocalAndRemoteTagPrompt: "Are you sure you want to delete '{{.tagName}}' from both your machine and from '{{.upstream}}'?", - PushTagTitle: "Remote to push tag '{{.tagName}}' to:", - // Using 'push tag' rather than just 'push' to disambiguate from a global push - PushTag: "Push tag", - PushTagTooltip: "Push the selected tag to a remote. You'll be prompted to select a remote.", - NewTag: "New tag", - NewTagTooltip: "Create new tag from current commit. You'll be prompted to enter a tag name and optional description.", - CreatingTag: "Creating tag", - ForceTag: "Force Tag", - ForceTagPrompt: "The tag '{{.tagName}}' exists already. Press {{.cancelKey}} to cancel, or {{.confirmKey}} to overwrite.", - FetchRemoteTooltip: "Fetch updates from the remote repository. This retrieves new commits and branches without merging them into your local branches.", - CheckoutCommitTooltip: "Checkout the selected commit as a detached HEAD.", - NoBranchesFoundAtCommitTooltip: "No branches found at selected commit.", - GitFlowOptions: "Show git-flow options", - NotAGitFlowBranch: "This does not seem to be a git flow branch", - NewGitFlowBranchPrompt: "New {{.branchType}} name:", - - IgnoreTracked: "Ignore tracked file", - IgnoreTrackedPrompt: "Are you sure you want to ignore a tracked file?", - ExcludeTracked: "Exclude tracked file", - ExcludeTrackedPrompt: "Are you sure you want to exclude a tracked file?", - ViewResetToUpstreamOptions: "View upstream reset options", - NextScreenMode: "Next screen mode (normal/half/fullscreen)", - PrevScreenMode: "Prev screen mode", - CyclePagers: "Cycle pagers", - CyclePagersTooltip: "Choose the next pager in the list of configured pagers", - CyclePagersDisabledReason: "No other pagers configured", - StartSearch: "Search the current view by text", - StartFilter: "Filter the current view by text", - KeybindingsLegend: "Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b", - RenameBranch: "Rename branch", - BranchUpstreamOptionsTitle: "Upstream options", - ViewBranchUpstreamOptions: "View upstream options", - ViewBranchUpstreamOptionsTooltip: "View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream.", - UpstreamNotSetError: "The selected branch has no upstream (or the upstream is not stored locally)", - UpstreamsNotSetError: "Some of the selected branches have no upstream (or the upstream is not stored locally)", - Upstream: "Upstream", - NewBranchNamePrompt: "Enter new branch name for branch", - RenameBranchWarning: "This branch is tracking a remote. This action will only rename the local branch name, not the name of the remote branch. Continue?", - OpenKeybindingsMenu: "Open keybindings menu", - ResetCherryPick: "Reset copied (cherry-picked) commits selection", - ResetCherryPickShort: "Reset copied commits", - NextTab: "Next tab", - PrevTab: "Previous tab", - CantUndoWhileRebasing: "Can't undo while rebasing", - CantRedoWhileRebasing: "Can't redo while rebasing", - MustStashWarning: "Pulling a patch out into the index requires stashing and unstashing your changes. If something goes wrong, you'll be able to access your files from the stash. Continue?", - MustStashTitle: "Must stash", - ConfirmationTitle: "Confirmation panel", - PromptTitle: "Input prompt", - PrevPage: "Previous page", - NextPage: "Next page", - GotoTop: "Scroll to top", - GotoBottom: "Scroll to bottom", - FilteringBy: "Filtering by", - ResetInParentheses: "(Reset)", - OpenFilteringMenu: "View filter options", - OpenFilteringMenuTooltip: "View options for filtering the commit log, so that only commits matching the filter are shown.", - FilterBy: "Filter by", - ExitFilterMode: "Stop filtering", - FilterPathOption: "Enter path to filter by", - FilterAuthorOption: "Enter author to filter by", - EnterFileName: "Enter path:", - EnterAuthor: "Enter author:", - FilteringMenuTitle: "Filtering", - WillCancelExistingFilterTooltip: "Note: this will cancel the existing filter", - MustExitFilterModeTitle: "Command not available", - MustExitFilterModePrompt: "Command not available in filter-by-path mode. Exit filter-by-path mode?", - Diff: "Diff", - EnterRefToDiff: "Enter ref to diff", - EnterRefName: "Enter ref:", - ExitDiffMode: "Exit diff mode", - DiffingMenuTitle: "Diffing", - SwapDiff: "Reverse diff direction", - ViewDiffingOptions: "View diffing options", - ViewDiffingOptionsTooltip: "View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction.", - CancelDiffingMode: "Cancel diffing mode", - // the actual view is the extras view which I intend to give more tabs in future but for now we'll only mention the command log part - OpenCommandLogMenu: "View command log options", - OpenCommandLogMenuTooltip: "View options for the command log e.g. show/hide the command log and focus the command log.", - ShowingGitDiff: "Showing output for:", - ShowingDiffForRange: "Showing diff for range", - CommitDiff: "Commit diff", - CopyCommitHashToClipboard: "Copy commit hash to clipboard", - CommitHash: "Commit hash", - CommitURL: "Commit URL", - PasteCommitMessageFromClipboard: "Paste commit message from clipboard", - SurePasteCommitMessage: "Pasting will overwrite the current commit message, continue?", - CommitMessage: "Commit message (subject and body)", - CommitMessageBody: "Commit message body", - CommitSubject: "Commit subject", - CommitAuthor: "Commit author", - CommitTags: "Commit tags", - CopyCommitAttributeToClipboard: "Copy commit attribute to clipboard", - CopyCommitAttributeToClipboardTooltip: "Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author).", - CopyBranchNameToClipboard: "Copy branch name to clipboard", - CopyTagToClipboard: "Copy tag to clipboard", - CopyPathToClipboard: "Copy path to clipboard", - CopySelectedTextToClipboard: "Copy selected text to clipboard", - CommitPrefixPatternError: "Error in commitPrefix pattern", - NoFilesStagedTitle: "No files staged", - NoFilesStagedPrompt: "You have not staged any files. Commit all files?", - BranchNotFoundTitle: "Branch not found", - BranchNotFoundPrompt: "Branch not found. Create a new branch named", - BranchUnknown: "Branch unknown", - DiscardChangeTitle: "Discard change", - DiscardChangePrompt: "Are you sure you want to discard this change (git reset)? It is irreversible.\nTo disable this dialogue set the config key of 'gui.skipDiscardChangeWarning' to true", - CreateNewBranchFromCommit: "Create new branch off of commit", - BuildingPatch: "Building patch", - ViewCommits: "View commits", - MinGitVersionError: "Git version must be at least %s. Please upgrade your git version.", - RunningCustomCommandStatus: "Running custom command", - SubmoduleStashAndReset: "Stash uncommitted submodule changes and update", - AndResetSubmodules: "And reset submodules", - Enter: "Enter", - EnterSubmoduleTooltip: "Enter submodule. After entering the submodule, you can press `{{.escape}}` to escape back to the parent repo.", - BackToParentRepo: "Back to parent repo", - CopySubmoduleNameToClipboard: "Copy submodule name to clipboard", - RemoveSubmodule: "Remove submodule", - RemoveSubmodulePrompt: "Are you sure you want to remove submodule '%s' and its corresponding directory? This is irreversible.", - RemoveSubmoduleTooltip: "Remove the selected submodule and its corresponding directory.", - ResettingSubmoduleStatus: "Resetting submodule", - NewSubmoduleName: "New submodule name:", - NewSubmoduleUrl: "New submodule URL:", - NewSubmodulePath: "New submodule path:", - NewSubmodule: "New submodule", - AddingSubmoduleStatus: "Adding submodule", - UpdateSubmoduleUrl: "Update URL for submodule '%s'", - UpdatingSubmoduleUrlStatus: "Updating URL", - EditSubmoduleUrl: "Update submodule URL", - InitializingSubmoduleStatus: "Initializing submodule", - InitSubmoduleTooltip: "Initialize the selected submodule to prepare for fetching. You probably want to follow this up by invoking the 'update' action to fetch the submodule.", - Update: "Update", - Initialize: "Initialize", - SubmoduleUpdateTooltip: "Update selected submodule.", - UpdatingSubmoduleStatus: "Updating submodule", - BulkInitSubmodules: "Bulk init submodules", - BulkUpdateSubmodules: "Bulk update submodules", - BulkDeinitSubmodules: "Bulk deinit submodules", - BulkUpdateRecursiveSubmodules: "Bulk init and update submodules recursively", - ViewBulkSubmoduleOptions: "View bulk submodule options", - BulkSubmoduleOptions: "Bulk submodule options", - RunningCommand: "Running command", - SubCommitsTitle: "Sub-commits", - ExitSubview: "Exit subview", - SubmodulesTitle: "Submodules", - NavigationTitle: "List panel navigation", - SuggestionsCheatsheetTitle: "Suggestions", - SuggestionsTitle: "Suggestions (press %s to focus)", - SuggestionsSubtitle: "(press %s to delete, %s to edit)", - ExtrasTitle: "Command log", - PullRequestURLCopiedToClipboard: "Pull request URL copied to clipboard", - CommitDiffCopiedToClipboard: "Commit diff copied to clipboard", - CommitURLCopiedToClipboard: "Commit URL copied to clipboard", - CommitMessageCopiedToClipboard: "Commit message copied to clipboard", - CommitMessageBodyCopiedToClipboard: "Commit message body copied to clipboard", - CommitSubjectCopiedToClipboard: "Commit subject copied to clipboard", - CommitAuthorCopiedToClipboard: "Commit author copied to clipboard", - CommitTagsCopiedToClipboard: "Commit tags copied to clipboard", - CommitHasNoTags: "Commit has no tags", - CommitHasNoMessageBody: "Commit has no message body", - PatchCopiedToClipboard: "Patch copied to clipboard", - MessageCopiedToClipboard: "Message copied to clipboard", - CopiedToClipboard: "copied to clipboard", - ErrCannotEditDirectory: "Cannot edit directories: you can only edit individual files", - ErrCannotCopyContentOfDirectory: "Cannot copy content of directories: you can only copy content of individual files", - ErrStageDirWithInlineMergeConflicts: "Cannot stage/unstage directory containing files with inline merge conflicts. Please fix up the merge conflicts first", - ErrRepositoryMovedOrDeleted: "Cannot find repo. It might have been moved or deleted ¯\\_(ツ)_/¯", - CommandLog: "Command log", - ErrWorktreeMovedOrRemoved: "Cannot find worktree. It might have been moved or removed ¯\\_(ツ)_/¯", - ToggleShowCommandLog: "Toggle show/hide command log", - FocusCommandLog: "Focus command log", - CommandLogHeader: "You can hide/focus this panel by pressing '%s'\n", - RandomTip: "Random tip", - ToggleWhitespaceInDiffView: "Toggle whitespace", - ToggleWhitespaceInDiffViewTooltip: "Toggle whether or not whitespace changes are shown in the diff view.\n\nThe default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'.", - IgnoreWhitespaceDiffViewSubTitle: "(ignoring whitespace)", - IgnoreWhitespaceNotSupportedHere: "Ignoring whitespace is not supported in this view", - IncreaseContextInDiffView: "Increase diff context size", - IncreaseContextInDiffViewTooltip: "Increase the amount of the context shown around changes in the diff view.\n\nThe default can be changed in the config file with the key 'git.diffContextSize'.", - DecreaseContextInDiffView: "Decrease diff context size", - DecreaseContextInDiffViewTooltip: "Decrease the amount of the context shown around changes in the diff view.\n\nThe default can be changed in the config file with the key 'git.diffContextSize'.", - DiffContextSizeChanged: "Changed diff context size to %d", - IncreaseRenameSimilarityThresholdTooltip: "Increase the similarity threshold for a deletion and addition pair to be treated as a rename.\n\nThe default can be changed in the config file with the key 'git.renameSimilarityThreshold'.", - IncreaseRenameSimilarityThreshold: "Increase rename similarity threshold", - DecreaseRenameSimilarityThresholdTooltip: "Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.\n\nThe default can be changed in the config file with the key 'git.renameSimilarityThreshold'.", - DecreaseRenameSimilarityThreshold: "Decrease rename similarity threshold", - RenameSimilarityThresholdChanged: "Changed rename similarity threshold to %d%%", - CreatePullRequestOptions: "View create pull request options", - DefaultBranch: "Default branch", - SelectBranch: "Select branch", - SelectTargetRemote: "Select target remote", - NoValidRemoteName: "A remote named '%s' does not exist", - SelectConfigFile: "Select config file", - NoConfigFileFoundErr: "No config file found", - LoadingFileSuggestions: "Loading file suggestions", - LoadingCommits: "Loading commits", - MustSpecifyOriginError: "Must specify a remote if specifying a branch", - GitOutput: "Git output:", - GitCommandFailed: "Git command failed. Check command log for details (open with %s)", - AbortTitle: "Abort %s", - AbortPrompt: "Are you sure you want to abort the current %s?", - OpenLogMenu: "View log options", - OpenLogMenuTooltip: "View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph.", - LogMenuTitle: "Commit Log Options", - ToggleShowGitGraphAll: "Toggle show whole git graph (pass the `--all` flag to `git log`)", - ShowGitGraph: "Show git graph", - ShowGitGraphTooltip: "Show or hide the git graph in the commit log.\n\nThe default can be changed in the config file with the key 'git.log.showGraph'.", - SortOrder: "Sort order", - SortOrderPromptLocalBranches: "The default sort order for local branches can be set in the config file with the key 'git.localBranchSortOrder'.", - SortOrderPromptRemoteBranches: "The default sort order for remote branches can be set in the config file with the key 'git.remoteBranchSortOrder'.", - SortAlphabetical: "Alphabetical", - SortByDate: "Date", - SortByRecency: "Recency", - SortBasedOnReflog: "(based on reflog)", - SortCommits: "Commit sort order", - SortCommitsTooltip: "Change the sort order of the commits in the commit log.\n\nThe default can be changed in the config file with the key 'git.log.sortOrder'.", - CantChangeContextSizeError: "Cannot change context while in patch building mode because we were too lazy to support it when releasing the feature. If you really want it, please let us know!", - OpenCommitInBrowser: "Open commit in browser", - ViewBisectOptions: "View bisect options", - ConfirmRevertCommit: "Are you sure you want to revert {{.selectedCommit}}?", - ConfirmRevertCommitRange: "Are you sure you want to revert the selected commits?", - RewordInEditorTitle: "Reword in editor", - RewordInEditorPrompt: "Are you sure you want to reword this commit in your editor?", - HardResetAutostashPrompt: "Are you sure you want to hard reset to '%s'? An auto-stash will be performed if necessary.", - SoftResetPrompt: "Are you sure you want to soft reset to '%s'?", - CheckoutAutostashPrompt: "Are you sure you want to checkout '%s'? An auto-stash will be performed if necessary.", - UpstreamGone: "(upstream gone)", - NukeDescription: "If you want to make all the changes in the worktree go away, this is the way to do it. If there are dirty submodule changes this will stash those changes in the submodule(s).", - NukeTreeConfirmation: "Are you sure you want to nuke the working tree? This will discard all changes in the worktree (staged, unstaged and untracked), which is not undoable.", - DiscardStagedChangesDescription: "This will create a new stash entry containing only staged files and then drop it, so that the working tree is left with only unstaged changes", - EmptyOutput: "", - Patch: "Patch", - CustomPatch: "Custom patch", - CommitsCopied: "commits copied", // lowercase because it's used in a sentence - CommitCopied: "commit copied", // lowercase because it's used in a sentence - ResetPatch: "Reset patch", - ResetPatchTooltip: "Clear the current patch.", - ApplyPatch: "Apply patch", - ApplyPatchTooltip: "Apply the current patch to the working tree.", - ApplyPatchInReverse: "Apply patch in reverse", - ApplyPatchInReverseTooltip: "Apply the current patch in reverse to the working tree.", - RemovePatchFromOriginalCommit: "Remove patch from original commit (%s)", - RemovePatchFromOriginalCommitTooltip: "Remove the current patch from its commit. This is achieved by starting an interactive rebase at the commit, applying the patch in reverse, and then continuing the rebase. If later commits depend on the patch, you may need to resolve conflicts.", - MovePatchOutIntoIndex: "Move patch out into index", - MovePatchOutIntoIndexTooltip: "Move the patch out of its commit and into the index. This is achieved by starting an interactive rebase at the commit, applying the patch in reverse, continuing the rebase to completion, and then applying the patch to the index. If later commits depend on the patch, you may need to resolve conflicts.", - MovePatchIntoNewCommit: "Move patch into new commit after the original commit", - MovePatchIntoNewCommitTooltip: "Move the patch out of its commit and into a new commit sitting on top of the original commit. This is achieved by starting an interactive rebase at the original commit, applying the patch in reverse, then applying the patch to the index and committing it as a new commit, before continuing the rebase to completion. If later commits depend on the patch, you may need to resolve conflicts.", - MovePatchIntoNewCommitBefore: "Move patch into new commit before the original commit", - MovePatchIntoNewCommitBeforeTooltip: "Move the patch out of its commit and into a new commit before the original commit. This works best when the custom patch contains only entire hunks or even entire files; if it contains partial hunks, you are likely to get conflicts.", - MovePatchToSelectedCommit: "Move patch to selected commit (%s)", - MovePatchToSelectedCommitTooltip: "Move the patch out of its original commit and into the selected commit. This is achieved by starting an interactive rebase at the original commit, applying the patch in reverse, then continuing the rebase up to the selected commit, before applying the patch forward and amending the selected commit. The rebase is then continued to completion. If commits between the source and destination commit depend on the patch, you may need to resolve conflicts.", - CopyPatchToClipboard: "Copy patch to clipboard", - MustStageFilesAffectedByPatchTitle: "Must stage files", - MustStageFilesAffectedByPatchWarning: "Applying a patch to the index requires staging the unstaged files that are affected by the patch. Note that you might get conflicts when applying the patch. Continue?", - NoMatchesFor: "No matches for '%s' %s", - ExitSearchMode: "%s: Exit search mode", - ExitTextFilterMode: "%s: Exit filter mode", - MatchesFor: "matches for '%s' (%d of %d) %s", // lowercase because it's after other text - SearchKeybindings: "%s: Next match, %s: Previous match, %s: Exit search mode", - SearchPrefix: "Search: ", - FilterPrefix: "Filter: ", - FilterPrefixMenu: "Filter (prepend '@' to filter keybindings): ", - WorktreesTitle: "Worktrees", - WorktreeTitle: "Worktree", - Switch: "Switch", - SwitchToWorktree: "Switch to worktree", - SwitchToWorktreeTooltip: "Switch to the selected worktree.", - AlreadyCheckedOutByWorktree: "This branch is checked out by worktree {{.worktreeName}}. Do you want to switch to that worktree?", - BranchCheckedOutByWorktree: "Branch {{.branchName}} is checked out by worktree {{.worktreeName}}", - SomeBranchesCheckedOutByWorktreeError: "Some of the selected branches are checked out by other worktrees. Select them one by one to delete them.", - DetachWorktreeTooltip: "This will run `git checkout --detach` on the worktree so that it stops hogging the branch, but the worktree's working tree will be left alone.", - Switching: "Switching", - RemoveWorktree: "Remove worktree", - RemoveWorktreeTitle: "Remove worktree", - RemoveWorktreePrompt: "Are you sure you want to remove worktree '{{.worktreeName}}'?", - ForceRemoveWorktreePrompt: "'{{.worktreeName}}' contains modified or untracked files, or submodules (or all of these). Are you sure you want to remove it?", - RemovingWorktree: "Deleting worktree", - DetachWorktree: "Detach worktree", - DetachingWorktree: "Detaching worktree", - AddingWorktree: "Adding worktree", - CantDeleteCurrentWorktree: "You cannot remove the current worktree!", - AlreadyInWorktree: "You are already in the selected worktree", - CantDeleteMainWorktree: "You cannot remove the main worktree!", - NoWorktreesThisRepo: "No worktrees", - MissingWorktree: "(missing)", - MainWorktree: "(main)", - NewWorktree: "New worktree", - NewWorktreePath: "New worktree path", - NewWorktreeBase: "New worktree base ref", - RemoveWorktreeTooltip: "Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory.", - BranchNameCannotBeBlank: "Branch name cannot be blank", - NewBranchName: "New branch name", - NewBranchNameLeaveBlank: "New branch name (leave blank to checkout {{.default}})", - ViewWorktreeOptions: "View worktree options", - CreateWorktreeFrom: "Create worktree from {{.ref}}", - CreateWorktreeFromDetached: "Create worktree from {{.ref}} (detached)", - LcWorktree: "worktree", - ChangingDirectoryTo: "Changing directory to {{.path}}", - Name: "Name", - Branch: "Branch", - Path: "Path", - MarkedBaseCommitStatus: "Marked a base commit for rebase", - MarkAsBaseCommit: "Mark as base commit for rebase", - MarkAsBaseCommitTooltip: "Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command.", - CancelMarkedBaseCommit: "Cancel marked base commit", - MarkedCommitMarker: "↑↑↑ Will rebase from here ↑↑↑", - FailedToOpenURL: "Failed to open URL %s\n\nError: %v", - InvalidLazygitEditURL: "Invalid lazygit-edit URL format: %s", - DisabledMenuItemPrefix: "Disabled: ", - NoCopiedCommits: "No copied commits", - QuickStartInteractiveRebase: "Start interactive rebase", - QuickStartInteractiveRebaseTooltip: "Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.\nIf you would instead like to start an interactive rebase from the selected commit, press `{{.editKey}}`.", - CannotQuickStartInteractiveRebase: "Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `{{.editKey}}`.", - RangeSelectUp: "Range select up", - RangeSelectDown: "Range select down", - RangeSelectNotSupported: "Action does not support range selection, please select a single item", - NoItemSelected: "No item selected", - SelectedItemIsNotABranch: "Selected item is not a branch", - SelectedItemDoesNotHaveFiles: "Selected item does not have files to view", - MultiSelectNotSupportedForSubmodules: "Multiselection not supported for submodules", - OldCherryPickKeyWarning: "The 'c' key is no longer the default key for copying commits to cherry pick. Please use `{{.copy}}` instead (and `{{.paste}}` to paste). The reason for this change is that the 'v' key for selecting a range of lines when staging is now also used for selecting a range of lines in any list view, meaning that we needed to find a new key for pasting commits, and if we're going to now use `{{.paste}}` for pasting commits, we may as well use `{{.copy}}` for copying them. If you want to configure the keybindings to get the old behaviour, set the following in your config:\n\nkeybinding:\n universal:\n toggleRangeSelect: \n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'", - CommandDoesNotSupportOpeningInEditor: "This command doesn't support switching to the editor", - CustomCommands: "Custom commands", - NoApplicableCommandsInThisContext: "(No applicable commands in this context)", - SelectCommitsOfCurrentBranch: "Select commits of current branch", - ViewMergeConflictOptions: "View merge conflict options", - ViewMergeConflictOptionsTooltip: "View options for resolving merge conflicts.", - NoFilesWithMergeConflicts: "There are no files with merge conflicts.", - MergeConflictOptionsTitle: "Resolve merge conflicts", - UseCurrentChanges: "Use current changes", - UseIncomingChanges: "Use incoming changes", - UseBothChanges: "Use both", - - Actions: Actions{ - // TODO: combine this with the original keybinding descriptions (those are all in lowercase atm) - CheckoutCommit: "Checkout commit", - CheckoutBranchAtCommit: "Checkout branch '%s'", - CheckoutCommitAsDetachedHead: "Checkout commit %s as detached head", - CheckoutTag: "Checkout tag", - CheckoutBranch: "Checkout branch", - ForceCheckoutBranch: "Force checkout branch", - CheckoutBranchOrCommit: "Checkout branch or commit", - DeleteLocalBranch: "Delete local branch", - Merge: "Merge", - SquashMerge: "Squash merge", - RebaseBranch: "Rebase branch", - RenameBranch: "Rename branch", - CreateBranch: "Create branch", - CherryPick: "(Cherry-pick) paste commits", - CheckoutFile: "Checkout file", - SquashCommitDown: "Squash commit down", - FixupCommit: "Fixup commit", - RewordCommit: "Reword commit", - DropCommit: "Drop commit", - EditCommit: "Edit commit", - AmendCommit: "Amend commit", - ResetCommitAuthor: "Reset commit author", - SetCommitAuthor: "Set commit author", - AddCommitCoAuthor: "Add commit co-author", - RevertCommit: "Revert commit", - CreateFixupCommit: "Create fixup commit", - SquashAllAboveFixupCommits: "Squash all above fixup commits", - CreateLightweightTag: "Create lightweight tag", - CreateAnnotatedTag: "Create annotated tag", - CopyCommitMessageToClipboard: "Copy commit message to clipboard", - CopyCommitMessageBodyToClipboard: "Copy commit message body to clipboard", - CopyCommitSubjectToClipboard: "Copy commit subject to clipboard", - CopyCommitTagsToClipboard: "Copy commit tags to clipboard", - CopyCommitDiffToClipboard: "Copy commit diff to clipboard", - CopyCommitHashToClipboard: "Copy full commit hash to clipboard", - CopyCommitURLToClipboard: "Copy commit URL to clipboard", - CopyCommitAuthorToClipboard: "Copy commit author to clipboard", - CopyCommitAttributeToClipboard: "Copy to clipboard", - CopyPatchToClipboard: "Copy patch to clipboard", - MoveCommitUp: "Move commit up", - MoveCommitDown: "Move commit down", - CustomCommand: "Custom command", - DiscardAllChangesInFile: "Discard all changes in selected file(s)", - DiscardAllUnstagedChangesInFile: "Discard all unstaged changes selected file(s)", - StageFile: "Stage file", - StageResolvedFiles: "Stage files whose merge conflicts were resolved", - UnstageFile: "Unstage file", - UnstageAllFiles: "Unstage all files", - StageAllFiles: "Stage all files", - ResolveConflictByKeepingFile: "Resolve by keeping file", - ResolveConflictByDeletingFile: "Resolve by deleting file", - NotEnoughContextToStage: "Staging or unstaging changes is not possible with a diff context size of 0. Increase the context using '%s'.", - NotEnoughContextToDiscard: "Discarding changes is not possible with a diff context size of 0. Increase the context using '%s'.", - NotEnoughContextForCustomPatch: "Creating custom patches is not possible with a diff context size of 0. Increase the context using '%s'.", - IgnoreExcludeFile: "Ignore or exclude file", - IgnoreFileErr: "Cannot ignore .gitignore", - ExcludeFile: "Exclude file", - ExcludeGitIgnoreErr: "Cannot exclude .gitignore", - Commit: "Commit", - Push: "Push", - Pull: "Pull", - OpenFile: "Open file", - StashAllChanges: "Stash all changes", - StashAllChangesKeepIndex: "Stash all changes and keep index", - StashStagedChanges: "Stash staged changes", - StashUnstagedChanges: "Stash unstaged changes", - StashIncludeUntrackedChanges: "Stash all changes including untracked files", - GitFlowFinish: "git flow finish", - GitFlowStart: "git flow start", - CopyToClipboard: "Copy to clipboard", - CopySelectedTextToClipboard: "Copy selected text to clipboard", - RemovePatchFromCommit: "Remove patch from commit", - MovePatchToSelectedCommit: "Move patch to selected commit", - MovePatchIntoIndex: "Move patch into index", - MovePatchIntoNewCommit: "Move patch into new commit", - DeleteRemoteBranch: "Delete remote branch", - SetBranchUpstream: "Set branch upstream", - AddRemote: "Add remote", - RemoveRemote: "Remove remote", - UpdateRemote: "Update remote", - ApplyPatch: "Apply patch", - Stash: "Stash", - PopStash: "Pop stash", - ApplyStash: "Apply stash", - DropStash: "Drop stash", - RenameStash: "Rename stash", - RemoveSubmodule: "Remove submodule", - ResetSubmodule: "Reset submodule", - AddSubmodule: "Add submodule", - UpdateSubmoduleUrl: "Update submodule URL", - InitialiseSubmodule: "Initialise submodule", - BulkInitialiseSubmodules: "Bulk initialise submodules", - BulkUpdateSubmodules: "Bulk update submodules", - BulkDeinitialiseSubmodules: "Bulk deinitialise submodules", - BulkUpdateRecursiveSubmodules: "Bulk initialise and update submodules recursively", - UpdateSubmodule: "Update submodule", - DeleteLocalTag: "Delete local tag", - DeleteRemoteTag: "Delete remote tag", - PushTag: "Push tag", - NukeWorkingTree: "Nuke working tree", - DiscardUnstagedFileChanges: "Discard unstaged file changes", - RemoveUntrackedFiles: "Remove untracked files", - RemoveStagedFiles: "Remove staged files", - SoftReset: "Soft reset", - MixedReset: "Mixed reset", - HardReset: "Hard reset", - FastForwardBranch: "Fast forward branch", - AutoForwardBranches: "Auto-forward branches", - Undo: "Undo", - Redo: "Redo", - CopyPullRequestURL: "Copy pull request URL", - OpenMergeTool: "Open merge tool", - OpenCommitInBrowser: "Open commit in browser", - OpenPullRequest: "Open pull request in browser", - StartBisect: "Start bisect", - ResetBisect: "Reset bisect", - BisectSkip: "Bisect skip", - BisectMark: "Bisect mark", - AddWorktree: "Add worktree", - }, - Bisect: Bisect{ - Mark: "Mark current commit (%s) as %s", - MarkStart: "Mark %s as %s (start bisect)", - SkipCurrent: "Skip current commit (%s)", - SkipSelected: "Skip selected commit (%s)", - ResetTitle: "Reset 'git bisect'", - ResetPrompt: "Are you sure you want to reset 'git bisect'?", - ResetOption: "Reset bisect", - ChooseTerms: "Choose bisect terms", - OldTermPrompt: "Term for old/good commit:", - NewTermPrompt: "Term for new/bad commit:", - BisectMenuTitle: "Bisect", - CompleteTitle: "Bisect complete", - CompletePrompt: "Bisect complete! The following commit introduced the change:\n\n%s\n\nDo you want to reset 'git bisect' now?", - CompletePromptIndeterminate: "Bisect complete! Some commits were skipped, so any of the following commits may have introduced the change:\n\n%s\n\nDo you want to reset 'git bisect' now?", - Bisecting: "Bisecting", - }, - Log: Log{ - EditRebase: "Beginning interactive rebase at '{{.ref}}'", - HandleUndo: "Undoing last conflict resolution", - RemoveFile: "Deleting path '{{.path}}'", - CopyToClipboard: "Copying '{{.str}}' to clipboard", - Remove: "Removing '{{.filename}}'", - CreateFileWithContent: "Creating file '{{.path}}'", - AppendingLineToFile: "Appending '{{.line}}' to file '{{.filename}}'", - EditRebaseFromBaseCommit: "Beginning interactive rebase from '{{.baseCommit}}' onto '{{.targetBranchName}}", - }, - BreakingChangesTitle: "Breaking Changes", - BreakingChangesMessage: `You are updating to a new version of lazygit which contains breaking changes. Please review the notes below and update your configuration if necessary. -For more information, see the full release notes at .`, - BreakingChangesByVersion: map[string]string{ - "0.41.0": `- When you press 'g' to bring up the git reset menu, the 'mixed' option is now the first and default, rather than 'soft'. This is because 'mixed' is the most commonly used option. -- The commit message panel now automatically hard-wraps by default (i.e. it adds newline characters when you reach the margin). You can adjust the config like so: - -git: - commit: - autoWrapCommitMessage: true - autoWrapWidth: 72 - -- The 'v' key was already being used in the staging view to start a range select, but now you can use it to start a range select in any view. Unfortunately this clashes with the 'v' keybinding for pasting commits (cherry-pick), so now pasting commits is done via 'shift+V' and for the sake of consistency, copying commits is now done via 'shift+C' instead of just 'c'. Note that the 'v' keybinding is only one way to start a range-select: you can use shift+up/down arrow instead. So, if you want to configure the cherry-pick keybindings to get the old behaviour, set the following in your config: - -keybinding: - universal: - toggleRangeSelect: - commits: - cherryPickCopy: 'c' - pasteCommits: 'v' - -- Squashing fixups using 'shift-S' now brings up a menu, with the default option being to squash all fixup commits in the branch. The original behaviour of only squashing fixup commits above the selected commit is still available as the second option in that menu. -- Push/pull/fetch loading statuses are now shown against the branch rather than in a popup. This allows you to e.g. fetch multiple branches in parallel and see the status for each branch. -- The git log graph in the commits view is now always shown by default (previously it was only shown when the view was maximised). If you find this too noisy, you can change it back via ctrl+L -> 'Show git graph' -> 'when maximised' -- Pressing space on a remote branch used to show a prompt for entering a name for a new local branch to check out from the remote branch. Now it just checks out the remote branch directly, letting you choose between a new local branch with the same name, or a detached head. The old behavior is still available via the 'n' keybinding. -- Filtering (e.g. when pressing '/') is less fuzzy by default; it only matches substrings now. Multiple substrings can be matched by separating them with spaces. If you want to revert to the old behavior, set the following in your config: - -gui: - filterMode: 'fuzzy' -`, - "0.44.0": `- The gui.branchColors config option is deprecated; it will be removed in a future version. Please use gui.branchColorPatterns instead. -- The automatic coloring of branches starting with "feature/", "bugfix/", or "hotfix/" has been removed; if you want this, it's easy to set up using the new gui.branchColorPatterns option.`, - "0.49.0": `- Executing shell commands (with the ':' prompt) no longer uses an interactive shell, which means that if you want to use your shell aliases in this prompt, you need to do a little bit of setup work. See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#using-aliases-or-functions-in-shell-commands for details.`, - "0.50.0": `- After fetching, main branches now get auto-forwarded to their upstream if they fall behind. This is useful for keeping your main or master branch up to date automatically. If you don't want this, you can disable it by setting the following in your config: - -git: - autoForwardBranches: none - -If, on the other hand, you want this even for feature branches, you can set it to 'allBranches' instead.`, - "0.51.0": `- The 'subprocess', 'stream', and 'showOutput' fields of custom commands have been replaced by a single 'output' field. This should be transparent, if you used these in your config file it should have been automatically updated for you. There's one notable change though: the 'stream' field used to mean both that the command's output would be streamed to the command log, and that the command would be run in a pseudo terminal (pty). We converted this to 'output: log', which means that the command's output will be streamed to the command log, but not use a pty, on the assumption that this is what most people wanted. If you do actually want to run a command in a pty, you can change this to 'output: logWithPty' instead.`, - "0.54.0": `- The default sort order for local and remote branches has changed: it used to be 'recency' (based on reflog) for local branches, and 'alphabetical' for remote branches. Both of these have been changed to 'date' (which means committerdate). If you do liked the old defaults better, you can revert to them with the following config: - -git: - localBranchSortOrder: recency - remoteBranchSortOrder: alphabetical - -- The default selection mode in the staging and custom patch building views has been changed to hunk mode. This is the more useful mode in most cases, as it usually saves a lot of keystrokes. If you want to switch back to the old line mode default, you can do so by adding the following to your config: - -gui: - useHunkModeInStagingView: false -`, - "0.55.0": `- The 'redo' command, which used to be bound to ctrl-z, is now bound to shift-Z instead. This is because ctrl-z is now used for suspending the application; it is a commonly known keybinding for that in the Linux world. If you want to revert this change, you can do so by adding the following to your config: - -keybinding: - universal: - suspendApp: - redo: - -- The 'git.paging.useConfig' option has been removed. If you were relying on it to configure your pager, you'll have to explicitly set the pager again using the git.paging.pager' option. -`, - }, - } -} diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go deleted file mode 100644 index 528f3815a02..00000000000 --- a/pkg/i18n/i18n.go +++ /dev/null @@ -1,127 +0,0 @@ -package i18n - -import ( - "embed" - "encoding/json" - "fmt" - "io/fs" - "strings" - - "dario.cat/mergo" - "github.com/cloudfoundry/jibber_jabber" - "github.com/go-errors/errors" - "github.com/samber/lo" - "github.com/sirupsen/logrus" -) - -func NewTranslationSetFromConfig(log *logrus.Entry, configLanguage string) (*TranslationSet, error) { - languageCodes, err := getSupportedLanguageCodes() - if err != nil { - return nil, err - } - - if configLanguage == "auto" { - language := detectLanguage(jibber_jabber.DetectIETF) - for _, languageCode := range languageCodes { - if strings.HasPrefix(language, languageCode) { - return newTranslationSet(log, languageCode) - } - } - - // Detecting a language that we don't have a translation for is not an - // error, we'll just use English. - return EnglishTranslationSet(), nil - } - - if configLanguage == "en" { - return EnglishTranslationSet(), nil - } - - for _, key := range languageCodes { - if key == configLanguage { - return newTranslationSet(log, configLanguage) - } - } - - // Configuring a language that we don't have a translation for *is* an - // error, though. - return nil, errors.New("Language not found: " + configLanguage) -} - -func newTranslationSet(log *logrus.Entry, language string) (*TranslationSet, error) { - log.Info("language: " + language) - - baseSet := EnglishTranslationSet() - - if language != "en" { - translationSet, err := readLanguageFile(language) - if err != nil { - return nil, err - } - err = mergo.Merge(baseSet, *translationSet, mergo.WithOverride) - if err != nil { - return nil, err - } - } - - return baseSet, nil -} - -//go:embed translations/*.json -var embedFS embed.FS - -// getSupportedLanguageCodes gets all the supported language codes. -// Note: this doesn't include "en" -func getSupportedLanguageCodes() ([]string, error) { - dirEntries, err := embedFS.ReadDir("translations") - if err != nil { - return nil, err - } - return lo.Map(dirEntries, func(entry fs.DirEntry, _ int) string { - return strings.TrimSuffix(entry.Name(), ".json") - }), nil -} - -func readLanguageFile(languageCode string) (*TranslationSet, error) { - jsonData, err := embedFS.ReadFile(fmt.Sprintf("translations/%s.json", languageCode)) - if err != nil { - return nil, err - } - var translationSet TranslationSet - err = json.Unmarshal(jsonData, &translationSet) - if err != nil { - return nil, err - } - return &translationSet, nil -} - -// GetTranslationSets gets all the translation sets, keyed by language code -// This includes "en". -func GetTranslationSets() (map[string]*TranslationSet, error) { - languageCodes, err := getSupportedLanguageCodes() - if err != nil { - return nil, err - } - - result := make(map[string]*TranslationSet) - result["en"] = EnglishTranslationSet() - - for _, languageCode := range languageCodes { - translationSet, err := readLanguageFile(languageCode) - if err != nil { - return nil, err - } - result[languageCode] = translationSet - } - - return result, nil -} - -// detectLanguage extracts user language from environment -func detectLanguage(langDetector func() (string, error)) string { - if userLang, err := langDetector(); err == nil { - return userLang - } - - return "C" -} diff --git a/pkg/i18n/i18n_test.go b/pkg/i18n/i18n_test.go deleted file mode 100644 index 8c5787fcb32..00000000000 --- a/pkg/i18n/i18n_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package i18n - -import ( - "fmt" - "io" - "runtime" - "testing" - - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" -) - -// TestDetectLanguage is a function. -func TestDetectLanguage(t *testing.T) { - type scenario struct { - langDetector func() (string, error) - expected string - } - - scenarios := []scenario{ - { - func() (string, error) { - return "", fmt.Errorf("An error occurred") - }, - "C", - }, - { - func() (string, error) { - return "en", nil - }, - "en", - }, - } - - for _, s := range scenarios { - assert.EqualValues(t, s.expected, detectLanguage(s.langDetector)) - } -} - -// Can't use utils.NewDummyLog() because of a cyclic dependency -func newDummyLog() *logrus.Entry { - log := logrus.New() - log.Out = io.Discard - return log.WithField("test", "test") -} - -func TestNewTranslationSetFromConfig(t *testing.T) { - if runtime.GOOS == "windows" { - // These tests are based on setting the LANG environment variable, which - // isn't respected on Windows. - t.Skip("Skipping test on Windows") - } - - scenarios := []struct { - name string - configLanguage string - envLanguage string - expected string - expectedErr bool - }{ - { - name: "configLanguage is nl", - configLanguage: "nl", - envLanguage: "en_US", - expected: "nl", - expectedErr: false, - }, - { - name: "configLanguage is an unsupported language", - configLanguage: "xy", - envLanguage: "en_US", - expectedErr: true, - }, - { - name: "auto-detection without LANG set", - configLanguage: "auto", - envLanguage: "", - expected: "en", - expectedErr: false, - }, - { - name: "auto-detection with LANG set to nl_NL", - configLanguage: "auto", - envLanguage: "nl_NL", - expected: "nl", - expectedErr: false, - }, - { - name: "auto-detection with LANG set to zh-CN", - configLanguage: "auto", - envLanguage: "zh-CN", - expected: "zh-CN", - expectedErr: false, - }, - { - name: "auto-detection with LANG set to an unsupported language", - configLanguage: "auto", - envLanguage: "xy_XY", - expected: "en", - expectedErr: false, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - log := newDummyLog() - t.Setenv("LANG", s.envLanguage) - actualTranslationSet, err := NewTranslationSetFromConfig(log, s.configLanguage) - if s.expectedErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - - expectedTranslationSet, _ := newTranslationSet(log, s.expected) - assert.Equal(t, expectedTranslationSet, actualTranslationSet) - } - }) - } -} diff --git a/pkg/i18n/translations/README.md b/pkg/i18n/translations/README.md deleted file mode 100644 index 57feea077e6..00000000000 --- a/pkg/i18n/translations/README.md +++ /dev/null @@ -1,19 +0,0 @@ -The JSON files in this directory are machine-generated; please do not edit. - -Translating lazygit happens at https://crowdin.com/project/lazygit/. - -# Updating translations from Crowdin - -We regularly need to pull changes from Crowdin and integrate them here. This is -done by downloading a zip file of the translations from Crowdin, unzipping it, -and calling `scripts/update_language_files.sh` with the unzipped directory as an -argument. - -# Uploading the English file to Crowdin - -The English version of all the texts is still maintained in -`pkg/i18n/english.go`; it needs to be uploaded to Crowdin regularly. To do this, -call `go run cmd/i18n/main.go`; this will create an unversioned file `en.json` -in the root of the repository. Upload this to -`https://crowdin.com/project/lazygit/sources/files` and delete it from the -working copy again. diff --git a/pkg/i18n/translations/ja.json b/pkg/i18n/translations/ja.json deleted file mode 100644 index 4f2d014d094..00000000000 --- a/pkg/i18n/translations/ja.json +++ /dev/null @@ -1,982 +0,0 @@ -{ - "NotEnoughSpace": "パネルを表示するスペースが足りません", - "DiffTitle": "差分", - "FilesTitle": "ファイル", - "BranchesTitle": "ブランチ", - "CommitsTitle": "コミット", - "StashTitle": "スタッシュ", - "SnakeTitle": "スネーク", - "EasterEgg": "イースターエッグ", - "UnstagedChanges": "ステージされていない変更", - "StagedChanges": "ステージされた変更", - "StagingTitle": "メインパネル(ステージング)", - "MergingTitle": "メインパネル(マージ中)", - "SquashMergeUncommittedTitle": "スカッシュマージして未コミットのままにする", - "SquashMergeCommittedTitle": "スカッシュマージしてコミットする", - "SquashMergeUncommitted": "'{{.selectedBranch}}'をワーキングツリーにスカッシュマージします。", - "SquashMergeCommitted": "'{{.selectedBranch}}'を'{{.checkedOutBranch}}'に単一のコミットとしてスカッシュマージします。", - "RegularMergeTooltip": "'{{.selectedBranch}}'を'{{.checkedOutBranch}}'にマージします。", - "NormalTitle": "メインパネル(通常)", - "LogTitle": "ログ", - "CommitSummary": "コミット概要", - "CredentialsUsername": "ユーザー名", - "CredentialsPassword": "パスワード", - "CredentialsPassphrase": "SSHキーのパスフレーズを入力", - "CredentialsPIN": "SSHキーのPINを入力", - "CredentialsToken": "SSHキーのトークンを入力", - "PassUnameWrong": "パスワード、パスフレーズ、またはユーザー名が間違っています", - "Commit": "コミット", - "CommitTooltip": "ステージされた変更をコミットします。", - "AmendLastCommit": "直前のコミットを修正", - "AmendLastCommitTitle": "直前のコミットを修正", - "SureToAmend": "直前のコミットを修正してもよろしいですか?その後、コミットパネルからコミットメッセージを変更できます。", - "NoCommitToAmend": "修正するコミットがありません。", - "CommitChangesWithEditor": "Gitエディタを使用して変更をコミット", - "FindBaseCommitForFixup": "フィックスアップのベースコミットを検索", - "FindBaseCommitForFixupTooltip": "現在の変更が基づいているコミットを見つけて、コミットの修正/フィックスアップを行います。これにより、ブランチのコミットを一つずつ確認して、どのコミットを修正/フィックスアップすべきかを調べる手間が省けます。詳細はドキュメントを参照: ", - "NoBaseCommitsFound": "ベースコミットが見つかりません", - "MultipleBaseCommitsFoundStaged": "複数のベースコミットが見つかりました。(一度にステージする変更を減らしてみてください)", - "MultipleBaseCommitsFoundUnstaged": "複数のベースコミットが見つかりました。(変更の一部をステージしてみてください)", - "BaseCommitIsAlreadyOnMainBranch": "この変更のベースコミットはすでにメインブランチ上にあります", - "BaseCommitIsNotInCurrentView": "ベースコミットが現在のビューにありません", - "HunksWithOnlyAddedLinesWarning": "差分には追加行のみの範囲があります。これらが見つかったベースコミットに属していることを確認してください。\n\n続行してよろしいですか?", - "StatusTitle": "ステータス", - "GlobalTitle": "グローバルキーバインド", - "Execute": "実行", - "Stage": "ステージ", - "StageTooltip": "選択したファイルのステージ状態を切り替えます。", - "ToggleStagedAll": "すべてステージ", - "ToggleStagedAllTooltip": "ワーキングツリー内のすべてのファイルのステージ/アンステージを切り替えます。", - "ToggleTreeView": "ファイルツリービューを切り替え", - "OpenDiffTool": "外部差分ツールを開く(git difftool)", - "OpenMergeTool": "外部マージツールを開く", - "OpenMergeToolTooltip": "`git mergetool`を実行します。", - "Refresh": "更新", - "RefreshTooltip": "Gitの状態を更新します(`git status`、`git branch`などをバックグラウンドで実行してパネルの内容を更新します)。これは`git fetch`を実行しません。", - "Push": "プッシュ", - "Pull": "プル", - "PushTooltip": "現在のブランチを対応するアップストリームブランチにプッシュします。アップストリームが設定されていない場合、アップストリームブランチの設定を求められます。", - "PullTooltip": "現在のブランチのリモートから変更をプルします。アップストリームが設定されていない場合、アップストリームブランチの設定を求められます。", - "FileFilter": "ステータスでファイルをフィルタリング", - "CopyToClipboardMenu": "クリップボードにコピー", - "CopyFileName": "ファイル名", - "CopyRelativeFilePath": "相対パス", - "CopyAbsoluteFilePath": "絶対パス", - "CopyFileDiffTooltip": "ステージされた項目がある場合、このコマンドはそれらのみを考慮します。そうでない場合は、ステージされていないすべての項目を考慮します。", - "CopySelectedDiff": "選択したファイルの差分", - "CopyAllFilesDiff": "すべてのファイルの差分", - "CopyFileContent": "選択したファイルの内容", - "NoContentToCopyError": "コピーするものがありません", - "FileNameCopiedToast": "ファイル名をクリップボードにコピーしました", - "FilePathCopiedToast": "ファイルパスをクリップボードにコピーしました", - "FileDiffCopiedToast": "ファイルの差分をクリップボードにコピーしました", - "AllFilesDiffCopiedToast": "すべてのファイルの差分をクリップボードにコピーしました", - "FileContentCopiedToast": "ファイルの内容をクリップボードにコピーしました", - "FilterStagedFiles": "ステージされたファイルのみ表示", - "FilterUnstagedFiles": "ステージされていないファイルのみ表示", - "FilterTrackedFiles": "追跡されているファイルのみ表示", - "FilterUntrackedFiles": "追跡されていないファイルのみ表示", - "NoFilter": "フィルタなし", - "FilterLabelStagedFiles": "(ステージされたもののみ)", - "FilterLabelUnstagedFiles": "(ステージされていないもののみ)", - "FilterLabelTrackedFiles": "(追跡されているもののみ)", - "FilterLabelUntrackedFiles": "(追跡されていないもののみ)", - "FilterLabelConflictingFiles": "(コンフリクトしているもののみ)", - "MergeConflictsTitle": "競合をマージ", - "MergeConflictDescription_DD": "コンフリクト:このファイルは現在の変更と取り込まれる変更の両方で移動またはリネームされましたが、異なる宛先に移動されています。どれかはわかりませんが、両方がコンフリクトとして表示されるはずです(それぞれ「AU」と「UA」とマークされています)。最も可能性の高い解決策は、このファイルを削除し、宛先の1つを選んで他を削除することです。", - "MergeConflictDescription_AU": "コンフリクト:このファイルは現在の変更で移動またはリネームの宛先ですが、取り込まれる変更では別の宛先に移動またはリネームされました。もう一方の宛先もコンフリクトとして表示されるはずです(「UA」とマークされています)。また、両方がリネームされた元のファイル(「DD」とマークされています)も表示されるはずです。", - "MergeConflictDescription_UA": "コンフリクト:このファイルは取り込まれる変更で移動またはリネームの宛先ですが、現在の変更では別の宛先に移動またはリネームされました。もう一方の宛先もコンフリクトとして表示されるはずです(「AU」とマークされています)。また、両方がリネームされた元のファイル(「DD」とマークされています)も表示されるはずです。", - "MergeConflictDescription_DU": "コンフリクト:このファイルは現在の変更で削除され、取り込まれる変更で修正されました。\n\n最も可能性の高い解決策は、取り込まれる変更を手動でコード内の別の場所に適用した後、ファイルを削除することです。", - "MergeConflictDescription_UD": "コンフリクト:このファイルは現在の変更で修正され、取り込まれる変更で削除されました。\n\n最も可能性の高い解決策は、現在の変更を手動でコード内の別の場所に適用した後、ファイルを削除することです。", - "MergeConflictIncomingDiff": "取り込まれる変更:", - "MergeConflictCurrentDiff": "現在の変更:", - "MergeConflictPressEnterToResolve": "%sを押して解決します。", - "MergeConflictKeepFile": "ファイルを保持", - "MergeConflictDeleteFile": "ファイルを削除", - "Checkout": "チェックアウト(ブランチの切り替え)", - "CheckoutTooltip": "選択した項目をチェックアウトします。", - "CantCheckoutBranchWhilePulling": "現在のブランチをプル中に別のブランチをチェックアウトすることはできません", - "TagCheckoutTooltip": "選択したタグをデタッチドHEADとしてチェックアウトします。", - "RemoteBranchCheckoutTooltip": "選択したリモートブランチに基づいて新しいローカルブランチをチェックアウトするか、リモートブランチをデタッチドヘッドとしてチェックアウトします。", - "CantPullOrPushSameBranchTwice": "既にプッシュまたはプル中のブランチをプッシュまたはプルすることはできません", - "NoChangedFiles": "変更されたファイルはありません", - "SoftReset": "ソフトリセット(変更を保持してステージ)", - "AlreadyCheckedOutBranch": "このブランチは既にチェックアウトされています", - "SureForceCheckout": "強制的にチェックアウトしてよろしいですか?すべてのローカル変更が失われます", - "ForceCheckoutBranch": "ブランチを強制チェックアウト", - "BranchName": "ブランチ名", - "NewBranchNameBranchOff": "新しいブランチ名('{{.branchName}}'から分岐)", - "CantDeleteCheckOutBranch": "チェックアウト中のブランチは削除できません!", - "DeleteBranchTitle": "ブランチ'{{.selectedBranchName}}'を削除してよろしいですか?", - "DeleteBranchesTitle": "選択したブランチを削除してよろしいですか?", - "DeleteLocalBranch": "ローカルブランチを削除", - "DeleteLocalBranches": "ローカルブランチを削除", - "DeleteRemoteBranchPrompt": "'{{.upstream}}'から'{{.selectedBranchName}}'リモートブランチを削除してもよろしいですか?", - "DeleteRemoteBranchesPrompt": "選択したブランチのリモートブランチをそれぞれのリモートから削除してもよろしいですか?", - "DeleteLocalAndRemoteBranchPrompt": "ブランチ'{{.localBranchName}}'をローカルと'{{.remoteName}}'のリモートの両方から削除してもよろしいですか?", - "DeleteLocalAndRemoteBranchesPrompt": "選択したブランチをローカルとリモートの両方から削除してもよろしいですか?", - "ForceDeleteBranchTitle": "ブランチを強制削除", - "ForceDeleteBranchMessage": "'{{.selectedBranchName}}'は完全にマージされていません。削除してもよろしいですか?", - "ForceDeleteBranchesMessage": "選択したブランチの一部は完全にマージされていません。削除してもよろしいですか?", - "RebaseBranch": "リベース", - "RebaseBranchTooltip": "チェックアウトしたブランチを選択したブランチ上にリベースします。", - "CantRebaseOntoSelf": "ブランチを自身にリベースすることはできません", - "CantMergeBranchIntoItself": "ブランチを自身にマージすることはできません", - "ForceCheckout": "強制チェックアウト", - "ForceCheckoutTooltip": "選択したブランチを強制的にチェックアウトします。これにより、選択したブランチをチェックアウトする前にワーキングディレクトリ内のすべてのローカル変更が破棄されます。", - "CheckoutByName": "名前でチェックアウト", - "CheckoutByNameTooltip": "名前でチェックアウトします。入力ボックスに「-」を入力すると、最後のブランチをチェックアウトすることができます。", - "CheckoutPreviousBranch": "直前のブランチにチェックアウト", - "RemoteBranchCheckoutTitle": "{{.branchName}}をチェックアウト", - "RemoteBranchCheckoutPrompt": "このブランチをどのようにチェックアウトしますか?", - "CheckoutTypeNewBranch": "新しいローカルブランチ", - "CheckoutTypeNewBranchTooltip": "リモートブランチをローカルブランチとしてチェックアウトし、リモートブランチを追跡します。", - "CheckoutTypeDetachedHead": "デタッチドヘッド(特定のブランチに属さない状態)", - "CheckoutTypeDetachedHeadTooltip": "リモートブランチをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。これは、一時的にブランチをテストするだけで継続的な作業をする予定がない場合に便利です。後でそこからローカルブランチを作成することもできます。", - "NewBranch": "新しいブランチ", - "NewBranchFromStashTooltip": "選択したスタッシュエントリから新しいブランチを作成します。これは、スタッシュエントリが作成されたコミットをgitがチェックアウトし、そのコミットから新しいブランチを作成した後、スタッシュエントリを追加のコミットとして新しいブランチに適用することで機能します。", - "MoveCommitsToNewBranch": "コミットを新しいブランチに移動", - "MoveCommitsToNewBranchFromBaseItem": "ベースブランチから新規ブランチ (%s)", - "CannotMoveCommitsNoUnpushedCommits": "新しいブランチに移動するプッシュされていないコミットはありません", - "NoBranchesThisRepo": "このリポジトリにはブランチがありません", - "CommitWithoutMessageErr": "コミットメッセージなしでコミットすることはできません", - "Close": "閉じる", - "CloseCancel": "閉じる/キャンセル", - "Confirm": "確認", - "Quit": "終了", - "SquashTooltip": "選択したコミットをその下のコミットにスカッシュします。スカッシュとは複数のコミットを1つにまとめる操作です。選択したコミットのメッセージが下のコミットに追加されます。", - "CannotSquashOrFixupFirstCommit": "スカッシュする下のコミットがありません", - "CannotSquashOrFixupMergeCommit": "マージコミットをスカッシュまたはフィックスアップすることはできません", - "Fixup": "フィックスアップ", - "FixupTooltip": "選択したコミットをその下のコミットにマージします。フィックスアップはスカッシュと似ていますが、選択したコミットのメッセージは破棄され、下のコミットのメッセージのみが保持されます。", - "SureFixupThisCommit": "選択したコミットを下のコミットに'フィックスアップ'してもよろしいですか?", - "SureSquashThisCommit": "選択したコミットを下のコミットにスカッシュしてもよろしいですか?", - "Squash": "スカッシュ", - "PickCommitTooltip": "選択したコミットをピックするようにマークします(リベース中)。これは、リベースを続行すると、コミットが保持されることを意味します。", - "Pick": "ピック", - "Edit": "編集", - "Revert": "リバート", - "RevertCommitTooltip": "選択したコミットの変更を逆に適用する、リバートコミットを作成します。", - "Reword": "メッセージ変更", - "CommitRewordTooltip": "選択したコミットのメッセージを変更します。", - "DropCommit": "削除", - "DropCommitTooltip": "選択したコミットを削除します。これはリベースを通じてブランチからコミットを削除します。コミットが後続のコミットが依存する変更を行っている場合、マージコンフリクトを解決する必要があるかもしれません。", - "MoveDownCommit": "コミットを1つ下に移動", - "MoveUpCommit": "コミットを1つ上に移動", - "CannotMoveAnyFurther": "これ以上移動できません", - "CannotMoveMergeCommit": "マージコミットを移動できません", - "EditCommit": "編集(対話型リベースを開始)", - "EditCommitTooltip": "選択したコミットを編集します。これを使用して、選択したコミットから対話型リベースを開始します。すでにリベース中の場合、これは選択したコミットを編集用にマークし、リベースを続行すると、リベースは選択したコミットで一時停止して変更を行えるようにします。", - "AmendCommitTooltip": "ステージされた変更でコミットを修正します。選択したコミットがHEADコミットの場合、これは `git commit --amend` を実行します。それ以外の場合、コミットはリベースを通じて修正されます。", - "Amend": "修正", - "ResetAuthor": "作者をリセット", - "ResetAuthorTooltip": "コミットの作者を現在設定されているユーザーにリセットします。これにより、作者のタイムスタンプも更新されます", - "SetAuthor": "作者を設定", - "SetAuthorTooltip": "プロンプトに基づいて作者を設定します", - "AddCoAuthor": "共同作者を追加", - "AmendCommitAttribute": "コミット属性を修正", - "AmendCommitAttributeTooltip": "コミット作者の設定/リセットまたは共同作者の設定を行います。", - "SetAuthorPromptTitle": "作者を設定('名前 <メール>'の形式で)", - "AddCoAuthorPromptTitle": "共同作者を追加('名前 <メール>'の形式で)", - "AddCoAuthorTooltip": "Github/Gitlabのメタデータ「Co-authored-by:」を使用して共同作者を追加します。", - "RewordCommitEditor": "エディタでメッセージ変更", - "NoCommitsThisBranch": "このブランチにはコミットがありません", - "UpdateRefHere": "ブランチ'{{.ref}}'をここに更新", - "ExecCommandHere": "ここで次のコマンドを実行:", - "Error": "エラー", - "Undo": "元に戻す", - "UndoReflog": "元に戻す", - "RedoReflog": "やり直す", - "UndoTooltip": "最後のgitコマンドを元に戻すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。", - "RedoTooltip": "最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。", - "UndoMergeResolveTooltip": "最後のマージコンフリクト解決を元に戻します。", - "DiscardAllTooltip": "'{{.path}}'のステージされた変更とステージされていない変更の両方を破棄します。", - "DiscardUnstagedTooltip": "'{{.path}}'のステージされていない変更を破棄します。", - "DiscardUnstagedDisabled": "選択された項目には、ステージされた変更とステージされていない変更の両方がありません。", - "Pop": "ポップ", - "StashPopTooltip": "スタッシュエントリをワーキングディレクトリに適用し、スタッシュエントリを削除します。", - "Drop": "削除", - "StashDropTooltip": "スタッシュリストからスタッシュエントリを削除します。", - "Apply": "適用", - "StashApplyTooltip": "スタッシュエントリをワーキングディレクトリに適用します。", - "NoStashEntries": "スタッシュエントリがありません", - "StashDrop": "スタッシュ削除", - "SureDropStashEntry": "選択したスタッシュエントリを削除してもよろしいですか?", - "StashPop": "スタッシュポップ", - "SurePopStashEntry": "このスタッシュエントリをポップしてもよろしいですか?", - "StashApply": "スタッシュ適用", - "SureApplyStashEntry": "このスタッシュエントリを適用してもよろしいですか?", - "NoTrackedStagedFilesStash": "スタッシュする追跡対象/ステージされたファイルがありません", - "NoFilesToStash": "スタッシュするファイルがありません", - "StashChanges": "変更をスタッシュ(一時保存)", - "RenameStash": "スタッシュの名前を変更", - "RenameStashPrompt": "スタッシュの名前を変更: {{.stashName}}", - "OpenConfig": "設定ファイルを開く", - "EditConfig": "設定ファイルを編集", - "ForcePush": "強制プッシュ", - "ForcePushPrompt": "ローカルブランチはリモートブランチから分岐しています。キャンセルするには{{.cancelKey}}を、強制プッシュするには{{.confirmKey}}を押してください。", - "ForcePushDisabled": "ローカルブランチはリモートブランチから分岐しており、強制プッシュが無効になっています", - "UpdatesRejected": "更新が拒否されました。再度プッシュする前にフェッチしてリモートの変更を確認してください。", - "UpdatesRejectedAndForcePushDisabled": "更新が拒否され、強制プッシュが無効になっています", - "CheckForUpdate": "更新を確認", - "CheckingForUpdates": "更新を確認中...", - "UpdateAvailableTitle": "更新が利用可能です!", - "UpdateAvailable": "バージョン{{.newVersion}}をダウンロードしてインストールしてよろしいですか?", - "UpdateInProgressWaitingStatus": "更新中", - "UpdateCompletedTitle": "更新が完了しました!", - "UpdateCompleted": "更新が正常にインストールされました。反映するにはlazygitを再起動してください。", - "FailedToRetrieveLatestVersionErr": "バージョン情報の取得に失敗しました", - "OnLatestVersionErr": "すでに最新バージョンを使用しています", - "MajorVersionErr": "新しいバージョン({{.newVersion}})には、現在のバージョン({{.currentVersion}})との互換性のない変更が含まれています", - "CouldNotFindBinaryErr": "{{.url}}にバイナリが見つかりませんでした", - "UpdateFailedErr": "更新に失敗しました: {{.errMessage}}", - "ConfirmQuitDuringUpdateTitle": "現在更新中です", - "ConfirmQuitDuringUpdate": "更新が進行中です。本当に終了してよろしいですか?", - "MergeToolTitle": "マージツール", - "MergeToolPrompt": "`git mergetool`を開いてもよろしいですか?", - "NonReloadableConfigWarningTitle": "設定が変更されました", - "NonReloadableConfigWarning": "以下の設定が変更されましたが、変更はすぐには反映されません。変更を反映するには、lazygitを終了して再起動してください:\n\n{{configs}}", - "GitconfigParseErr": "引用符で囲まれていない'\\'文字が存在するため、Gogitがgitconfigファイルの解析に失敗しました。これらを削除することで問題が解決するはずです。", - "EditFile": "ファイルを編集", - "EditFileTooltip": "外部エディタでファイルを開きます。", - "OpenFile": "ファイルを開く", - "OpenFileTooltip": "デフォルトのアプリケーションでファイルを開きます。", - "OpenInEditor": "エディタで開く", - "IgnoreFile": ".gitignoreに追加", - "ExcludeFile": ".git/info/excludeに追加", - "RefreshFiles": "ファイルを更新", - "FocusMainView": "メインビューにフォーカス", - "Merge": "マージ", - "RegularMerge": "通常のマージ", - "MergeBranchTooltip": "選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ)", - "ConfirmQuit": "本当に終了してよろしいですか?", - "SwitchRepo": "最近のリポジトリをチェックアウト", - "AllBranchesLogGraph": "ブランチログの表示モードを順に切り替え", - "UnsupportedGitService": "サポートされていないGitサービス", - "CopyPullRequestURL": "プルリクエストURLをクリップボードにコピー", - "NoBranchOnRemote": "このブランチはリモートに存在しません。まずリモートにプッシュする必要があります。", - "Fetch": "フェッチ", - "FetchTooltip": "リモートから変更をフェッチします。", - "CollapseAll": "すべてのファイルを折りたたむ", - "CollapseAllTooltip": "ファイルツリー内のすべてのディレクトリを折りたたみます", - "ExpandAll": "すべてのファイルを展開", - "ExpandAllTooltip": "ファイルツリー内のすべてのディレクトリを展開します", - "DisabledInFlatView": "フラットビューでは利用できません", - "FileEnter": "行をステージ / ディレクトリを折りたたむ", - "FileEnterTooltip": "選択された項目がファイルの場合、個々のハンク/行をステージできるようにステージングビューにフォーカスします。選択された項目がディレクトリの場合、ディレクトリを折りたたむ/展開します。", - "StageSelectionTooltip": "選択された部分のステージ / アンステージを切り替えます。", - "DiscardSelection": "破棄", - "DiscardSelectionTooltip": "ステージされていない変更が選択されている場合、`git reset`を使用して変更を破棄します。ステージされた変更が選択されている場合、変更をアンステージします。", - "ToggleSelectHunk": "ハンクの選択を切り替える", - "SelectHunk": "ハンクを選択", - "SelectLineByLine": "1行ずつ選択", - "ToggleSelectionForPatch": "パッチ内の行を切り替え", - "EditHunk": "ハンクを編集", - "EditHunkTooltip": "選択したハンクを外部エディタで編集します。", - "ToggleStagingView": "ビューを切り替え", - "ToggleStagingViewTooltip": "他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。", - "ReturnToFilesPanel": "ファイルパネルに戻る", - "FastForward": "ブランチを最新化(fast-forward)", - "FastForwardTooltip": "選択したブランチを対応するアップストリームの最新状態に追いつかせます(fast-forward)。", - "FastForwarding": "ブランチを最新化中", - "FoundConflictsTitle": "コンフリクト発生!", - "ViewConflictsMenuItem": "コンフリクトを表示", - "AbortMenuItem": "%s を中止", - "PickHunk": "ハンクを選択", - "PickAllHunks": "すべてのハンクを選択", - "ViewMergeRebaseOptions": "マージ/リベースオプションを表示", - "ViewMergeRebaseOptionsTooltip": "現在のマージ/リベースを中止/継続/スキップするオプションを表示します。", - "ViewMergeOptions": "マージオプションを表示", - "ViewRebaseOptions": "リベースオプションを表示", - "ViewRevertOptions": "リバートオプションを表示", - "NotMergingOrRebasing": "現在リベースもマージも行っていません", - "AlreadyRebasing": "リベース中はこのアクションを実行できません", - "RecentRepos": "最近のリポジトリ", - "MergeOptionsTitle": "マージオプション", - "RebaseOptionsTitle": "リベースオプション", - "RevertOptionsTitle": "リバートオプション", - "CommitSummaryTitle": "コミット概要", - "CommitDescriptionTitle": "コミット詳細", - "CommitDescriptionSubTitle": "{{.togglePanelKeyBinding}} でフォーカスを切り替え、{{.commitMenuKeybinding}} でメニューを開く", - "CommitHooksDisabledSubTitle": "(フック無効)", - "LocalBranchesTitle": "ローカルブランチ", - "SearchTitle": "検索", - "TagsTitle": "タグ", - "MenuTitle": "メニュー", - "CommitMenuTitle": "コミットメニュー", - "RemotesTitle": "リモート", - "RemoteBranchesTitle": "リモートブランチ", - "PatchBuildingTitle": "メインパネル(パッチ作成)", - "InformationTitle": "情報", - "SecondaryTitle": "セカンダリ", - "ReflogCommitsTitle": "リフログ", - "Continue": "続行", - "UnstagedFilesAfterConflictsResolved": "コンフリクト解決後にファイルが変更されています。自動的にステージして続行してよろしいですか?", - "RebasingTitle": "'{{.checkedOutBranch}}'をリベース中", - "RebasingFromBaseCommitTitle": "マークされたベースから'{{.checkedOutBranch}}'をリベース中", - "SimpleRebase": "'{{.ref}}'に対する通常のリベース(履歴の書き換え)", - "InteractiveRebase": "'{{.ref}}'に対する対話的リベース(編集可能な履歴の書き換え)", - "RebaseOntoBaseBranch": "ベースブランチ({{.baseBranch}})へのリベース", - "InteractiveRebaseTooltip": "開始時に一時停止する対話的リベースを開始します。これにより、続行する前にTODOコミットを更新できます。", - "RebaseOntoBaseBranchTooltip": "チェックアウトしたブランチをそのベースブランチ(つまり、最も近いメインブランチ)にリベースします。", - "MustSelectTodoCommits": "リベース中は、このアクションはTODOコミットの選択に対してのみ機能します。", - "FwdNoUpstream": "アップストリームのないブランチは最新化(fast-forward)できません", - "FwdNoLocalUpstream": "ローカルに登録されていないリモートを持つブランチは最新化(fast-forward)できません", - "FwdCommitsToPush": "プッシュするコミットがあるブランチは最新化(fast-forward)できません", - "PullRequestNoUpstream": "アップストリームのないブランチに対してプルリクエストを開くことはできません", - "ErrorOccurred": "エラーが発生しました!以下でイシューを作成してください:", - "ConflictLabel": "コンフリクト", - "PendingCherryPicksSectionHeader": "保留中のチェリーピック", - "PendingRevertsSectionHeader": "保留中のリバート", - "CommitsSectionHeader": "コミット", - "YouDied": "やられた!", - "RewordNotSupported": "リベースの対話モード中のコミットの変更(reword)は現在サポートされていません", - "ChangingThisActionIsNotAllowed": "この種類のリベース TODO エントリの変更は許可されていません", - "NotAllowedMidCherryPickOrRevert": "チェリーピックまたはリバートの際にはこのアクションは許可されていません", - "PickIsOnlyAllowedDuringRebase": "このアクションはリベース中にのみ許可されています", - "DroppingMergeRequiresSingleSelection": "マージコミットを削除するには単一の項目を選択する必要があります", - "CherryPickCopy": "コピー(チェリーピック)", - "CherryPickCopyTooltip": "コミットをコピーとしてマークします。ローカルコミットビューで `{{.paste}}` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `{{.escape}}` を押して選択をキャンセルできます。", - "PasteCommits": "ペースト(チェリーピック)", - "SureCherryPick": "コピーした {{.numCommits}} 個のコミットをこのブランチにチェリーピックしてよろしいですか?", - "CherryPick": "チェリーピック", - "CannotCherryPickNonCommit": "この種類の TODO 項目はチェリーピックできません", - "Donate": "寄付", - "AskQuestion": "質問する", - "PrevHunk": "前のハンクに移動", - "NextHunk": "次のハンクに移動", - "PrevConflict": "前のコンフリクト", - "NextConflict": "次のコンフリクト", - "SelectPrevHunk": "前のハンク", - "SelectNextHunk": "次のハンク", - "ScrollDown": "下にスクロール", - "ScrollUp": "上にスクロール", - "ScrollUpMainWindow": "メインウィンドウを上にスクロール", - "ScrollDownMainWindow": "メインウィンドウを下にスクロール", - "AmendCommitTitle": "コミットを修正", - "AmendCommitPrompt": "ステージされたファイルでこのコミットを修正してよろしいですか?", - "AmendCommitWithConflictsMenuPrompt": "警告:解決済みのコンフリクトで最後に完了したコミットを修正しようとしています。これはこの時点で望んでいることではない可能性が高いです。おそらく、代わりにリベースを続行することを望んでいるでしょう。\n\nそれでも前のコミットを修正してよろしいですか?", - "AmendCommitWithConflictsContinue": "いいえ、リベースを続行", - "AmendCommitWithConflictsAmend": "はい、前のコミットを修正", - "DropCommitTitle": "コミットを削除", - "DropCommitPrompt": "選択したコミットを削除してよろしいですか?", - "DropUpdateRefPrompt": "選択した update-ref TODO を削除してよろしいですか?リベースを中止しない限り、これは元に戻せません。", - "DropMergeCommitPrompt": "選択したマージコミットを削除してよろしいですか?これにより、そのコミットによってマージされたすべてのコミットも削除されることに注意してください。", - "PullingStatus": "プル中", - "PushingStatus": "プッシュ中", - "FetchingStatus": "フェッチ中", - "SquashingStatus": "スカッシュ中", - "FixingStatus": "修正中", - "DeletingStatus": "削除中", - "DroppingStatus": "削除中", - "MovingStatus": "移動中", - "RebasingStatus": "リベース中", - "MergingStatus": "マージ中", - "LowercaseRebasingStatus": "リベース中", - "LowercaseMergingStatus": "マージ中", - "LowercaseCherryPickingStatus": "チェリーピック中", - "LowercaseRevertingStatus": "リバート中", - "AmendingStatus": "修正中", - "CherryPickingStatus": "チェリーピック中", - "UndoingStatus": "取り消し中", - "RedoingStatus": "やり直し中", - "CheckingOutStatus": "チェックアウト中", - "CommittingStatus": "コミット中", - "RewordingStatus": "修正中", - "RevertingStatus": "リバート中", - "CreatingFixupCommitStatus": "fixupコミット作成中", - "MovingCommitsToNewBranchStatus": "コミットを新しいブランチに移動", - "CommitFiles": "コミットファイル", - "SubCommitsDynamicTitle": "コミット (%s)", - "CommitFilesDynamicTitle": "差分ファイル (%s)", - "RemoteBranchesDynamicTitle": "リモートブランチ (%s)", - "ViewItemFiles": "ファイルを表示", - "CommitFilesTitle": "コミットファイル", - "CheckoutCommitFileTooltip": "ファイルをチェックアウトします。これにより、作業ツリー内のファイルが選択したコミットのバージョンに置き換えられます。", - "CanOnlyDiscardFromLocalCommits": "ローカルコミットからのみ変更を破棄できます", - "Remove": "削除", - "DiscardOldFileChangeTooltip": "このコミットのこのファイルへの変更を破棄します。これはバックグラウンドで対話的なリベースを実行するため、後のコミットでもこのファイルが変更されている場合、マージコンフリクトが発生する可能性があります。", - "DiscardFileChangesTitle": "ファイルの変更を破棄", - "DiscardFileChangesPrompt": "このコミットから選択したファイルの変更を削除してよろしいですか?\n\nこのアクションはリベースを開始し、これらのファイル変更を元に戻します。後続のコミットがこれらの変更に依存している場合、コンフリクトを解決する必要があるかもしれないことに注意してください。\n注意:これにより、アクティブなカスタムパッチもリセットされます。", - "DisabledForGPG": "GPGを使用しているユーザーには利用できない機能です。\n\nパスフレーズエージェント(gpg-agentなど)を使用して署名時にパスフレーズを入力しなくても済むようにしている場合は、lazygitの設定ファイルに\n\ngit:\n overrideGpg: true\n\nを追加することでこの機能を有効にできます。", - "CreateRepo": "Gitリポジトリがありません。新しいgitリポジトリを作成しますか? (y/N): ", - "BareRepo": "ベアリポジトリでLazygitを開こうとしましたが、Lazygitはまだベアリポジトリをサポートしていません。最近のリポジトリを開いてよろしいですか? (y/n) ", - "InitialBranch": "ブランチ名(gitのデフォルトの場合は空のままにしてください): ", - "NoRecentRepositories": "Lazygitはgitリポジトリで開く必要があります。有効な最近のリポジトリはありません。終了します。", - "IncorrectNotARepository": "'notARepository'の値が正しくありません。'prompt'、'create'、'skip'、または'quit'のいずれかである必要があります。", - "AutoStashTitle": "自動スタッシュ?", - "AutoStashPrompt": "変更を持ち越すにはスタッシュとポップを行う必要があります。自動的に行ってよろしいですか? (enter/esc)", - "AutoStashForReverting": "リバートのために自動的にスタッシュします", - "Discard": "破棄", - "DiscardChangesTitle": "変更を破棄", - "DiscardFileChangesTooltip": "選択したファイルの変更を破棄するオプションを表示します。", - "Cancel": "キャンセル", - "DiscardAllChanges": "すべての変更を破棄", - "DiscardUnstagedChanges": "ステージされていない変更を破棄", - "DiscardAllChangesToAllFiles": "作業ツリーを完全に破棄", - "DiscardAnyUnstagedChanges": "ステージされていない変更を破棄", - "DiscardUntrackedFiles": "追跡されていないファイルを破棄", - "DiscardStagedChanges": "ステージされた変更を破棄", - "HardReset": "ハードリセット(すべての変更を破棄)", - "BranchDeleteTooltip": "ローカル/リモートブランチの削除オプションを表示します。", - "TagDeleteTooltip": "ローカル/リモートタグの削除オプションを表示します。", - "Delete": "削除", - "Reset": "リセット", - "ResetTooltip": "選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:\n- ソフトリセット:変更を保持し、ステージされた状態にします\n- ミックスリセット:変更を保持し、ステージされていない状態にします\n- ハードリセット:すべての変更を破棄します", - "ViewResetOptions": "リセット", - "FileResetOptionsTooltip": "作業ツリーのリセットオプション(例:作業ツリーの完全破棄)を表示します。", - "CreateFixupCommit": "fixupコミットを作成", - "CreateFixupCommitTooltip": "選択したコミットに対する「fixup!」コミットを作成します。fixupコミットは、選択したコミットの修正用コミットです。後で、同じコミットで `{{.squashAbove}}` を押すと、上記のすべてのfixupコミットが適用されます。", - "CreateAmendCommit": "「amend!」コミットを作成", - "FixupMenu_Fixup": "fixup! コミット", - "FixupMenu_FixupTooltip": "別のコミットをfixupし、元のコミットメッセージを保持することができます。", - "FixupMenu_AmendWithChanges": "変更を含む amend! コミット", - "FixupMenu_AmendWithChangesTooltip": "別のコミットをfixupし、そのコミットメッセージも変更することができます。", - "FixupMenu_AmendWithoutChanges": "変更なしの amend! コミット(純粋なreword)", - "FixupMenu_AmendWithoutChangesTooltip": "別のコミットの内容を変更せずにコミットメッセージを変更することができます。", - "SquashAboveCommitsTooltip": "すべての「fixup!」コミットを、選択したコミットの上部または現在のブランチ内のすべてをスカッシュします(autosquash)。", - "SquashCommitsAboveSelectedTooltip": "選択したコミットの上部にあるすべての「fixup!」コミットをスカッシュします(autosquash)。", - "SquashCommitsInCurrentBranchTooltip": "現在のブランチ内のすべての「fixup!」コミットをスカッシュします(autosquash)。", - "SquashAboveCommits": "fixupコミットを適用", - "SquashCommitsInCurrentBranch": "現在のブランチ内", - "SquashCommitsAboveSelectedCommit": "選択したコミットの上部", - "CannotSquashCommitsInCurrentBranch": "現在のブランチのコミットをスカッシュできません:HEADコミットはマージコミットであるか、メインブランチに存在しています。", - "ExecuteShellCommand": "シェルコマンドを実行", - "ExecuteShellCommandTooltip": "実行するシェルコマンドを入力するプロンプトを表示します。", - "ShellCommand": "シェルコマンド:", - "CommitChangesWithoutHook": "pre-commitフックなしで変更をコミット", - "ResetTo": "次にリセット", - "ResetSoftTooltip": "HEADを選択したコミットにリセットし、現在のコミットと選択したコミットとの間の変更をステージされた変更として保持します。作業内容を保持したまま、コミット履歴を変更したい場合に便利です。", - "ResetMixedTooltip": "HEADを選択したコミットにリセットし、現在のコミットと選択したコミットとの間の変更をステージされていない変更として保持します。変更内容を再ステージしたい場合に便利です。", - "ResetHardTooltip": "HEADを選択したコミットにリセットし、現在のコミットと選択したコミットとの間のすべての変更、および作業ツリー内の現在のすべての変更を破棄します。注意:このオプションでは変更が完全に削除されます。", - "PressEnterToReturn": "Enterキーを押してlazygitに戻る", - "ViewStashOptions": "スタッシュオプションを表示", - "ViewStashOptionsTooltip": "スタッシュオプション(すべてをスタッシュ、ステージされた変更をスタッシュ、ステージされていない変更をスタッシュなど)を表示します。", - "Stash": "スタッシュ", - "StashTooltip": "すべての変更をスタッシュします。スタッシュの他のバリエーションについては、スタッシュオプションを表示するキーバインディングを使用してください。", - "StashAllChanges": "すべての変更をスタッシュ", - "StashStagedChanges": "ステージされた変更をスタッシュ", - "StashAllChangesKeepIndex": "すべての変更をスタッシュしてインデックスを保持", - "StashUnstagedChanges": "ステージされていない変更をスタッシュ", - "StashIncludeUntrackedChanges": "追跡されていないファイルを含むすべての変更をスタッシュ", - "StashOptions": "スタッシュオプション", - "NotARepository": "エラー:gitリポジトリ内で実行する必要があります", - "WorkingDirectoryDoesNotExist": "エラー:現在の作業ディレクトリが存在しません", - "ScrollLeft": "左にスクロール", - "ScrollRight": "右にスクロール", - "DiscardPatch": "パッチを破棄", - "DiscardPatchConfirm": "一度に1つのコミット/スタッシュエントリからのみパッチを作成できます。現在のパッチを破棄してよろしいですか?", - "CantPatchWhileRebasingError": "マージまたはリベース状態中にパッチを作成したりパッチコマンドを実行したりすることはできません", - "ToggleAddToPatch": "パッチに含めるファイルを切り替え", - "ToggleAddToPatchTooltip": "ファイルがカスタムパッチに含まれるかどうかを切り替えます。{{.doc}}を参照してください。", - "ToggleAllInPatch": "すべてのファイルを切り替え", - "ToggleAllInPatchTooltip": "コミットのすべてのファイルをカスタムパッチに追加/削除します。{{.doc}}を参照してください。", - "UpdatingPatch": "パッチを更新中", - "ViewPatchOptions": "カスタムパッチオプションを表示", - "PatchOptionsTitle": "パッチオプション", - "NoPatchError": "まだパッチが作成されていません。パッチの作成を開始するには、コミットファイルで「スペース」を使用するか、特定の行を追加するためにEnterキーを押してください", - "EmptyPatchError": "パッチはまだ空です。まずいくつかのファイルまたは行をパッチに追加してください。", - "EnterCommitFile": "ファイルに入る / ディレクトリの折りたたみを切り替える", - "EnterCommitFileTooltip": "ファイルが選択されている場合、そのファイルに入ってカスタムパッチに個々の行を追加/削除できます。ディレクトリが選択されている場合、ディレクトリを切り替えます。", - "ExitCustomPatchBuilder": "カスタムパッチビルダーを終了", - "ExitFocusedMainView": "サイドパネルに戻る", - "EnterUpstream": "アップストリームを '<リモート> <ブランチ名>' 形式で入力", - "InvalidUpstream": "無効なアップストリームです。'<リモート> <ブランチ名>' 形式である必要があります", - "NewRemote": "新しいリモート", - "NewRemoteName": "新しいリモート名:", - "NewRemoteUrl": "新しいリモートURL:", - "ViewBranches": "ブランチを表示", - "EditRemoteName": "{{.remoteName}} の更新されたリモート名を入力:", - "EditRemoteUrl": "{{.remoteName}} の更新されたリモートURLを入力:", - "RemoveRemote": "リモートを削除", - "RemoveRemoteTooltip": "選択したリモートを削除します。そのリモートからのリモートブランチを追跡しているローカルブランチは影響を受けません。", - "RemoveRemotePrompt": "リモートを削除してよろしいですか?", - "DeleteRemoteBranch": "リモートブランチを削除", - "DeleteRemoteBranches": "リモートブランチを削除", - "DeleteRemoteBranchTooltip": "リモートからリモートブランチを削除します。", - "DeleteLocalAndRemoteBranch": "ローカルとリモートのブランチを削除", - "DeleteLocalAndRemoteBranches": "ローカルとリモートのブランチを削除", - "SetAsUpstream": "アップストリームとして設定", - "SetAsUpstreamTooltip": "選択したリモートブランチをチェックアウトされたブランチのアップストリームとして設定します。", - "SetUpstream": "選択したブランチのアップストリームを設定", - "UnsetUpstream": "選択したブランチのアップストリームを解除", - "ViewDivergenceFromUpstream": "アップストリームからの乖離を表示", - "ViewDivergenceFromBaseBranch": "ベースブランチ({{.baseBranch}})からの乖離を表示", - "CouldNotDetermineBaseBranch": "ベースブランチを決定できませんでした", - "DivergenceSectionHeaderLocal": "ローカル", - "DivergenceSectionHeaderRemote": "リモート", - "ViewUpstreamResetOptions": "チェックアウトされたブランチを {{.upstream}} にリセット", - "ViewUpstreamResetOptionsTooltip": "チェックアウトされたブランチを {{upstream}} にリセットするオプションを表示します。注意:これは選択したブランチをアップストリームにリセットするのではなく、チェックアウトされたブランチをアップストリームにリセットします。", - "ViewUpstreamRebaseOptions": "チェックアウトされたブランチを {{.upstream}} にリベース", - "ViewUpstreamRebaseOptionsTooltip": "チェックアウトされたブランチを {{upstream}} にリベースするオプションを表示します。注意:これは選択したブランチをアップストリームにリベースするのではなく、チェックアウトされたブランチをアップストリームにリベースします。", - "UpstreamGenericName": "選択したブランチのアップストリーム", - "SetUpstreamTitle": "アップストリームブランチを設定", - "SetUpstreamMessage": "'{{.checkedOut}}' のアップストリームブランチを '{{.selected}}' に設定してよろしいですか?", - "EditRemoteTooltip": "選択したリモートの名前またはURLを編集します。", - "TagCommit": "コミットにタグを付ける", - "TagCommitTooltip": "選択したコミットを指すタグを新規作成します。タグ名とオプションの説明を入力するよう促されます。", - "TagNameTitle": "タグ名", - "TagMessageTitle": "タグの説明", - "LightweightTag": "軽量タグ", - "AnnotatedTag": "注釈付きタグ", - "DeleteTagTitle": "タグ '{{.tagName}}' を削除してよろしいですか?", - "DeleteLocalTag": "ローカルタグを削除", - "DeleteRemoteTag": "リモートタグを削除", - "DeleteLocalAndRemoteTag": "ローカルとリモートのタグを削除", - "SelectRemoteTagUpstream": "タグ '{{.tagName}}' を削除するリモート:", - "DeleteRemoteTagPrompt": "リモートタグ '{{.tagName}}' を '{{.upstream}}' から削除してよろしいですか?", - "DeleteLocalAndRemoteTagPrompt": "タグ '{{.tagName}}' をローカルと '{{.upstream}}' の両方から削除してよろしいですか?", - "RemoteTagDeletedMessage": "リモートタグが削除されました", - "PushTagTitle": "タグ '{{.tagName}}' をプッシュするリモート:", - "PushTag": "タグをプッシュ", - "PushTagTooltip": "選択したタグをリモートにプッシュします。リモートを選択するよう促されます。", - "NewTag": "新しいタグを作成", - "NewTagTooltip": "現在のコミットから新しいタグを作成します。タグ名とオプションの説明を入力するよう促されます。", - "CreatingTag": "タグを作成中", - "ForceTag": "タグを強制", - "ForceTagPrompt": "タグ '{{.tagName}}' はすでに存在します。キャンセルするには {{.cancelKey}} を、上書きするには {{.confirmKey}} を押してください。", - "FetchRemoteTooltip": "リモートリポジトリから更新をフェッチします。これにより、ローカルブランチにマージせずに新しいコミットとブランチを取得します。", - "CheckoutCommitTooltip": "選択したコミットをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。", - "NoBranchesFoundAtCommitTooltip": "選択したコミットにブランチが見つかりません。", - "GitFlowOptions": "git-flowオプションを表示", - "NotAGitFlowBranch": "これはgit flowブランチではないようです", - "NewBranchNamePrompt": "ブランチの新しい名前を入力してください", - "IgnoreTracked": "追跡されているファイルを無視", - "ExcludeTracked": "追跡されているファイルを除外", - "IgnoreTrackedPrompt": "追跡されているファイルを無視してよろしいですか?", - "ExcludeTrackedPrompt": "追跡されているファイルを除外してよろしいですか?", - "ViewResetToUpstreamOptions": "アップストリームへのリセットオプションを表示", - "NextScreenMode": "次の画面モード(通常/半分/全画面)", - "PrevScreenMode": "前の画面モード", - "StartSearch": "現在のビューをテキストで検索", - "StartFilter": "現在のビューをテキストでフィルタリング", - "Keybindings": "キーバインディング", - "KeybindingsLegend": "凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味します", - "KeybindingsMenuSectionLocal": "ローカル", - "KeybindingsMenuSectionGlobal": "グローバル", - "KeybindingsMenuSectionNavigation": "ナビゲーション", - "RenameBranch": "ブランチ名を変更", - "Upstream": "アップストリーム", - "BranchUpstreamOptionsTitle": "アップストリームオプション", - "ViewBranchUpstreamOptions": "アップストリームオプションを表示", - "ViewBranchUpstreamOptionsTooltip": "ブランチのアップストリームに関連するオプションを表示します(例:アップストリームの設定/解除やアップストリームへのリセット)。", - "UpstreamNotSetError": "選択したブランチにはアップストリームがありません(またはアップストリームがローカルに保存されていません)", - "UpstreamsNotSetError": "選択したブランチの一部にはアップストリームがありません(またはアップストリームがローカルに保存されていません)", - "NewGitFlowBranchPrompt": "新しい {{.branchType}} 名:", - "RenameBranchWarning": "このブランチはリモートを追跡しています。このアクションはローカルブランチ名のみを変更し、リモートブランチ名は変更されません。続行してよろしいですか?", - "OpenKeybindingsMenu": "キーバインディングメニューを開く", - "ResetCherryPick": "コピーされた(チェリーピックされた)コミットの選択をリセット", - "NextTab": "次のタブ", - "PrevTab": "前のタブ", - "CantUndoWhileRebasing": "リベース中は元に戻せません", - "CantRedoWhileRebasing": "リベース中はやり直せません", - "MustStashWarning": "パッチをインデックスに引き出すには、変更をスタッシュし、アンスタッシュする必要があります。何か問題が発生した場合、スタッシュからファイルにアクセスできます。続行してよろしいですか?", - "MustStashTitle": "スタッシュが必要", - "ConfirmationTitle": "確認パネル", - "PrevPage": "前のページ", - "NextPage": "次のページ", - "GotoTop": "先頭にスクロール", - "GotoBottom": "末尾にスクロール", - "FilteringBy": "フィルタリング条件", - "ResetInParentheses": "(リセット)", - "OpenFilteringMenu": "フィルターオプションを表示", - "OpenFilteringMenuTooltip": "コミットログのフィルタリングオプションを表示し、フィルタに一致するコミットのみを表示します。", - "FilterBy": "フィルター条件", - "ExitFilterMode": "フィルタリングを停止", - "FilterPathOption": "フィルタリングするパスを入力", - "FilterAuthorOption": "フィルタリングする作者を入力", - "EnterFileName": "パスを入力:", - "EnterAuthor": "作者を入力:", - "FilteringMenuTitle": "フィルタリング", - "WillCancelExistingFilterTooltip": "注意:これにより既存のフィルターがキャンセルされます", - "MustExitFilterModeTitle": "コマンドが利用できません", - "MustExitFilterModePrompt": "パスによるフィルターモードではコマンドを利用できません。パスによるフィルターモードを終了してよろしいですか?", - "Diff": "差分", - "EnterRefToDiff": "差分を表示するrefを入力", - "EnterRefName": "refを入力:", - "ExitDiffMode": "差分モードを終了", - "DiffingMenuTitle": "差分表示", - "SwapDiff": "差分の方向を反転", - "ViewDiffingOptions": "差分オプションを表示", - "ViewDiffingOptionsTooltip": "2つのrefの差分に関連するオプションを表示します(例:選択したrefとの差分表示、差分を取るrefの入力、差分方向の反転など)。", - "OpenCommandLogMenu": "コマンドログオプションを表示", - "OpenCommandLogMenuTooltip": "コマンドログのオプションを表示します(例:コマンドログの表示/非表示、コマンドログへのフォーカスなど)。", - "ShowingGitDiff": "出力を表示:", - "ShowingDiffForRange": "範囲の差分を表示", - "CommitDiff": "コミットの差分", - "CopyCommitHashToClipboard": "コミットハッシュをクリップボードにコピー", - "CommitHash": "コミットハッシュ", - "CommitURL": "コミットURL", - "PasteCommitMessageFromClipboard": "クリップボードからコミットメッセージを貼り付け", - "SurePasteCommitMessage": "貼り付けると現在のコミットメッセージが上書きされます、続行してよろしいですか?", - "CommitMessage": "コミットメッセージ(件名と本文)", - "CommitMessageBody": "コミットメッセージ本文", - "CommitSubject": "コミットの件名", - "CommitAuthor": "コミット作者", - "CommitTags": "コミットタグ", - "CopyCommitAttributeToClipboard": "コミット属性をクリップボードにコピー", - "CopyCommitAttributeToClipboardTooltip": "コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。", - "CopyBranchNameToClipboard": "ブランチ名をクリップボードにコピー", - "CopyTagToClipboard": "タグをクリップボードにコピー", - "CopyPathToClipboard": "パスをクリップボードにコピー", - "CommitPrefixPatternError": "commitPrefixパターンのエラー", - "CopySelectedTextToClipboard": "選択したテキストをクリップボードにコピー", - "NoFilesStagedTitle": "ステージされたファイルがありません", - "NoFilesStagedPrompt": "ステージされたファイルがありません。すべてのファイルをコミットしてよろしいですか?", - "BranchNotFoundTitle": "ブランチが見つかりません", - "BranchNotFoundPrompt": "ブランチが見つかりません。次の名前の新しいブランチを作成します:", - "BranchUnknown": "ブランチ不明", - "DiscardChangeTitle": "変更を破棄", - "DiscardChangePrompt": "この変更を破棄しますか(git reset)?これは元に戻せません。\nこのダイアログを無効にするには、'gui.skipDiscardChangeWarning'の設定キーをtrueに設定してください", - "CreateNewBranchFromCommit": "コミットから新しいブランチを作成", - "BuildingPatch": "パッチを作成中", - "ViewCommits": "コミットを表示", - "MinGitVersionError": "Git バージョンは %s以上である必要があります。git バージョンをアップグレードしてください。", - "RunningCustomCommandStatus": "カスタムコマンドを実行中", - "SubmoduleStashAndReset": "未コミットのサブモジュールの変更をスタッシュして更新", - "AndResetSubmodules": "サブモジュールをリセット", - "EnterSubmoduleTooltip": "サブモジュールに入ります。サブモジュールに入った後、`{{.escape}}`を押して親リポジトリに戻ることができます。", - "Enter": "入る", - "CopySubmoduleNameToClipboard": "サブモジュール名をクリップボードにコピー", - "RemoveSubmodule": "サブモジュールを削除", - "RemoveSubmoduleTooltip": "選択したサブモジュールとそれに対応するディレクトリを削除します。", - "RemoveSubmodulePrompt": "サブモジュール '%s' とそれに対応するディレクトリを削除してよろしいですか?これは元に戻せません。", - "ResettingSubmoduleStatus": "サブモジュールをリセット中", - "NewSubmoduleName": "新しいサブモジュール名:", - "NewSubmoduleUrl": "新しいサブモジュールURL:", - "NewSubmodulePath": "新しいサブモジュールのパス:", - "NewSubmodule": "新しいサブモジュール", - "AddingSubmoduleStatus": "サブモジュールを追加中", - "UpdateSubmoduleUrl": "サブモジュール '%s' のURLを更新", - "UpdatingSubmoduleUrlStatus": "URLを更新中", - "EditSubmoduleUrl": "サブモジュールURLを更新", - "InitializingSubmoduleStatus": "サブモジュールを初期化中", - "InitSubmoduleTooltip": "選択したサブモジュールを初期化してフェッチの準備をします。おそらく、続いて「更新」アクションを呼び出してサブモジュールをフェッチしたいでしょう。", - "Update": "更新", - "Initialize": "初期化", - "SubmoduleUpdateTooltip": "選択したサブモジュールを更新します。", - "UpdatingSubmoduleStatus": "サブモジュールを更新中", - "BulkInitSubmodules": "すべてのサブモジュールを一括初期化", - "BulkUpdateSubmodules": "すべてのサブモジュールを一括更新", - "BulkDeinitSubmodules": "すべてのサブモジュールを一括解除", - "BulkUpdateRecursiveSubmodules": "すべてのサブモジュールを再帰的に一括初期化および更新", - "ViewBulkSubmoduleOptions": "一括サブモジュールオプションを表示", - "BulkSubmoduleOptions": "一括サブモジュールオプション", - "RunningCommand": "コマンド実行中", - "SubCommitsTitle": "サブコミット", - "SubmodulesTitle": "サブモジュール", - "NavigationTitle": "リストパネルのナビゲーション", - "SuggestionsCheatsheetTitle": "候補", - "SuggestionsTitle": "候補(%s を押してフォーカス)", - "SuggestionsSubtitle": "(%s を押して削除、%s を押して編集)", - "ExtrasTitle": "コマンドログ", - "PullRequestURLCopiedToClipboard": "プルリクエストURLがクリップボードにコピーされました", - "CommitDiffCopiedToClipboard": "コミットの差分がクリップボードにコピーされました", - "CommitURLCopiedToClipboard": "コミットURLがクリップボードにコピーされました", - "CommitMessageCopiedToClipboard": "コミットメッセージがクリップボードにコピーされました", - "CommitMessageBodyCopiedToClipboard": "コミットメッセージ本文がクリップボードにコピーされました", - "CommitSubjectCopiedToClipboard": "コミット件名がクリップボードにコピーされました", - "CommitAuthorCopiedToClipboard": "コミット作者がクリップボードにコピーされました", - "CommitTagsCopiedToClipboard": "コミットタグがクリップボードにコピーされました", - "CommitHasNoTags": "コミットにタグがありません", - "CommitHasNoMessageBody": "コミットにメッセージ本文がありません", - "PatchCopiedToClipboard": "パッチがクリップボードにコピーされました", - "CopiedToClipboard": "クリップボードにコピーされました", - "ErrCannotEditDirectory": "ディレクトリは編集できません:個々のファイルのみ編集できます", - "ErrCannotCopyContentOfDirectory": "ディレクトリの内容はコピーできません:個々のファイルの内容のみコピーできます", - "ErrStageDirWithInlineMergeConflicts": "インラインマージコンフリクトを含むファイルがあるディレクトリはステージ/アンステージできません。まずマージコンフリクトを解決してください", - "ErrRepositoryMovedOrDeleted": "リポジトリが見つかりません。移動または削除された可能性があります ¯\\_(ツ)_/¯", - "ErrWorktreeMovedOrRemoved": "ワークツリーが見つかりません。移動または削除された可能性があります ¯\\_(ツ)_/¯", - "CommandLog": "コマンドログ", - "ToggleShowCommandLog": "コマンドログの表示/非表示を切り替え", - "FocusCommandLog": "コマンドログにフォーカス", - "CommandLogHeader": "'%s'を押してこのパネルを非表示/フォーカスできます\n", - "RandomTip": "ランダムなヒント", - "ToggleWhitespaceInDiffView": "空白表示の切り替え", - "IgnoreWhitespaceDiffViewSubTitle": "(空白を無視)", - "IgnoreWhitespaceNotSupportedHere": "このビューでは空白の無視はサポートされていません", - "IncreaseContextInDiffView": "差分コンテキストサイズを増やす", - "DecreaseContextInDiffView": "差分コンテキストサイズを減らす", - "DiffContextSizeChanged": "差分コンテキストサイズを %d に変更しました", - "IncreaseRenameSimilarityThreshold": "リネーム検出の類似度しきい値を上げる", - "DecreaseRenameSimilarityThreshold": "リネーム検出の類似度しきい値を下げる", - "RenameSimilarityThresholdChanged": "リネーム検出の類似度しきい値を %d%% に変更しました", - "CreatePullRequestOptions": "プルリクエスト作成オプションを表示", - "DefaultBranch": "デフォルトブランチ", - "SelectBranch": "ブランチを選択", - "SelectTargetRemote": "対象リモートを選択", - "NoValidRemoteName": "'%s' という名前のリモートは存在しません", - "CreatePullRequest": "プルリクエストを作成", - "SelectConfigFile": "設定ファイルを選択", - "NoConfigFileFoundErr": "設定ファイルが見つかりません", - "LoadingFileSuggestions": "ファイル候補を読み込み中", - "LoadingCommits": "コミットを読み込み中", - "MustSpecifyOriginError": "ブランチを指定する場合はリモートを指定する必要があります", - "GitOutput": "Git出力:", - "GitCommandFailed": "Gitコマンドが失敗しました。詳細はコマンドログで確認してください(%s で開く)", - "AbortTitle": "%s を中止", - "AbortPrompt": "現在の %s を中止してよろしいですか?", - "OpenLogMenu": "ログオプションを表示", - "OpenLogMenuTooltip": "コミットログのオプションを表示します(例:並び順の変更、Gitグラフの非表示、Gitグラフ全体の表示)。", - "LogMenuTitle": "コミットログオプション", - "ToggleShowGitGraphAll": "Gitグラフ全体の表示切り替え(`git log`に `--all` フラグを渡す)", - "ShowGitGraph": "Gitグラフを表示", - "SortOrder": "並び順", - "SortAlphabetical": "アルファベット順", - "SortByDate": "日付順", - "SortByRecency": "最新順", - "SortBasedOnReflog": "(reflogに基づく)", - "SortCommits": "コミットの並び順", - "CantChangeContextSizeError": "パッチ作成モード中はコンテキストを変更できません。この機能をリリースするときに対応するのが面倒だったためです。本当に必要な場合は、お知らせください!", - "OpenCommitInBrowser": "ブラウザでコミットを開く", - "ViewBisectOptions": "bisectオプションを表示", - "ConfirmRevertCommit": "{{.selectedCommit}} をリバートしてよろしいですか?", - "ConfirmRevertCommitRange": "選択したコミットをリバートしてよろしいですか?", - "RewordInEditorTitle": "エディタで修正", - "RewordInEditorPrompt": "このコミットをエディタで修正してよろしいですか?", - "CheckoutAutostashPrompt": "'%s' をチェックアウトしてよろしいですか?必要に応じて自動スタッシュが実行されます。", - "HardResetAutostashPrompt": "'%s' にハードリセットしてよろしいですか?必要に応じて自動スタッシュが実行されます。", - "SoftResetPrompt": "'%s' にソフトリセットしてよろしいですか?", - "UpstreamGone": "(アップストリームが消失)", - "NukeDescription": "作業ツリー内のすべての変更を消したい場合は、これが方法です。サブモジュールに変更がある場合、それらの変更はサブモジュール内にスタッシュされます。", - "DiscardStagedChangesDescription": "これはステージされたファイルのみを含む新しいスタッシュエントリを作成し、その後それを削除するため、作業ツリーにはステージされていない変更のみが残ります", - "EmptyOutput": "<空の出力>", - "Patch": "パッチ", - "CustomPatch": "カスタムパッチ", - "CommitsCopied": "コミットをコピーしました", - "CommitCopied": "コミットをコピーしました", - "ResetPatch": "パッチをリセット", - "ResetPatchTooltip": "現在のパッチをクリアします。", - "ApplyPatch": "パッチを適用", - "ApplyPatchTooltip": "現在のパッチを作業ツリーに適用します。", - "ApplyPatchInReverse": "パッチを逆に適用", - "ApplyPatchInReverseTooltip": "現在のパッチを作業ツリーに逆方向で適用します。", - "RemovePatchFromOriginalCommit": "元のコミット(%s)からパッチを削除", - "RemovePatchFromOriginalCommitTooltip": "現在のパッチをそのコミットから削除します。これはコミットで対話的リベースを開始し、パッチを逆方向に適用し、リベースを続行することで実現されます。後続のコミットがパッチに依存している場合、コンフリクトを解決する必要があるかもしれません。", - "MovePatchOutIntoIndex": "パッチをインデックスに移動", - "MovePatchOutIntoIndexTooltip": "パッチをそのコミットから取り出し、インデックスに移動します。\n\n動作の仕組み:\n1. コミットで対話的リベースを開始\n2. パッチを逆方向に適用\n3. リベースを完了まで続行\n4. パッチをインデックスに適用\n\n注意:後続のコミットがパッチに依存している場合、コンフリクトを解決する必要があるかもしれません。", - "MovePatchIntoNewCommitTooltip": "パッチをそのコミットから取り出し、元のコミットの上に新しいコミットとして移動します。\n\n動作の仕組み:\n1. 元のコミットで対話的リベースを開始\n2. パッチを逆方向に適用\n3. パッチをインデックスに適用して新しいコミットを作成\n4. リベースを完了まで続行\n\n注意:後続のコミットがパッチに依存している場合、コンフリクトを解決する必要があるかもしれません。", - "MovePatchToSelectedCommit": "パッチを選択したコミット(%s)に移動", - "MovePatchToSelectedCommitTooltip": "パッチを元のコミットから取り出し、選択したコミットに移動します。\n\n動作の仕組み:\n1. 元のコミットで対話的リベースを開始\n2. パッチを逆方向に適用\n3. 選択したコミットまでリベースを続行\n4. パッチを順方向に適用して選択したコミットを修正\n5. リベースを完了まで続行\n\n注意:元のコミットと移動先コミットの間のコミットがパッチに依存している場合、コンフリクトを解決する必要があるかもしれません。", - "CopyPatchToClipboard": "パッチをクリップボードにコピー", - "NoMatchesFor": "'%s' に一致するものはありません %s", - "MatchesFor": "'%s' に一致するもの(%d / %d)%s", - "SearchKeybindings": "%s: 次の一致, %s: 前の一致, %s: 検索モード終了", - "SearchPrefix": "検索: ", - "FilterPrefix": "フィルター: ", - "ExitSearchMode": "%s: 検索モード終了", - "ExitTextFilterMode": "%s: フィルターモード終了", - "Switch": "チェックアウト(切り替え)", - "SwitchToWorktree": "ワークツリーをチェックアウト(切り替え)", - "SwitchToWorktreeTooltip": "選択したワークツリーをチェックアウト(切り替え)します。", - "AlreadyCheckedOutByWorktree": "このブランチはワークツリー {{.worktreeName}} によってチェックアウトされています。そのワークツリーをチェックアウトしてよろしいですか?", - "BranchCheckedOutByWorktree": "ブランチ {{.branchName}} はワークツリー {{.worktreeName}} によってチェックアウトされています", - "SomeBranchesCheckedOutByWorktreeError": "選択したブランチのいくつかは他のワークツリーによってチェックアウトされています。それらを削除するには、一つずつ選択してください。", - "DetachWorktreeTooltip": "ワークツリーで `git checkout --detach` を実行して、ブランチを占有しないようにします。ただし、ワークツリーの作業ツリーはそのままです。", - "Switching": "チェックアウト中", - "RemoveWorktree": "ワークツリーを削除", - "RemoveWorktreeTitle": "ワークツリーを削除", - "DetachWorktree": "ワークツリーをデタッチ", - "DetachingWorktree": "ワークツリーをデタッチ中", - "WorktreesTitle": "ワークツリー", - "WorktreeTitle": "ワークツリー", - "RemoveWorktreePrompt": "ワークツリー '{{.worktreeName}}' を削除してよろしいですか?", - "ForceRemoveWorktreePrompt": "'{{.worktreeName}}' には変更されたファイルまたは追跡されていないファイルが含まれています(正直なところ、両方含まれている可能性があります)。削除してよろしいですか?", - "RemovingWorktree": "ワークツリーを削除中", - "AddingWorktree": "ワークツリーを追加中", - "CantDeleteCurrentWorktree": "現在のワークツリーは削除できません!", - "AlreadyInWorktree": "すでに選択したワークツリーにいます", - "CantDeleteMainWorktree": "メインのワークツリーは削除できません!", - "NoWorktreesThisRepo": "ワークツリーがありません", - "MissingWorktree": "(見つかりません)", - "MainWorktree": "(メイン)", - "NewWorktree": "新しいワークツリー", - "NewWorktreePath": "新しいワークツリーのパス", - "NewWorktreeBase": "新しいワークツリーのベース参照", - "RemoveWorktreeTooltip": "選択したワークツリーを削除します。これはワークツリーのディレクトリとワークツリーに関するメタデータの両方を.gitディレクトリから削除します。", - "BranchNameCannotBeBlank": "ブランチ名は空白にできません", - "NewBranchName": "新しいブランチ名", - "NewBranchNameLeaveBlank": "新しいブランチ名({{.default}} をチェックアウトするには空白のままにしてください)", - "ViewWorktreeOptions": "ワークツリーオプションを表示", - "CreateWorktreeFrom": "{{.ref}} からワークツリーを作成", - "CreateWorktreeFromDetached": "{{.ref}} からワークツリーを作成(デタッチド)", - "LcWorktree": "ワークツリー", - "ChangingDirectoryTo": "ディレクトリを {{.path}} に変更中", - "Name": "名前", - "Branch": "ブランチ", - "Path": "パス", - "MarkedBaseCommitStatus": "リベース用のベースコミットをマークしました", - "MarkAsBaseCommit": "リベース用のベースコミットとしてマーク", - "MarkAsBaseCommitTooltip": "次のリベース用のベースコミットを選択します。ブランチにリベースするとき、ベースコミットより上のコミットのみが持ち込まれます。これは `git rebase --onto` コマンドを使用します。", - "MarkedCommitMarker": "↑↑↑ ここからリベースします ↑↑↑", - "FailedToOpenURL": "URL %s を開けませんでした\n\nエラー: %v", - "InvalidLazygitEditURL": "無効な lazygit-edit URL 形式: %s", - "NoCopiedCommits": "コピーされたコミットがありません", - "DisabledMenuItemPrefix": "無効: ", - "QuickStartInteractiveRebase": "対話的リベースを開始", - "QuickStartInteractiveRebaseTooltip": "ブランチ上のコミットの対話的リベースを開始します。これには、HEADコミットから最初のマージコミットまたはメインブランチのコミットまでのすべてのコミットが含まれます。\n選択したコミットから対話的リベースを開始したい場合は、代わりに `{{.editKey}}` を押してください。", - "CannotQuickStartInteractiveRebase": "対話的リベースを開始できません:HEADコミットはマージコミットであるか、メインブランチに存在しているため、リベースを開始するための適切なベースコミットがありません。特定のコミットから対話的リベースを開始するには、コミットを選択して `{{.editKey}}` を押してください。", - "ToggleRangeSelect": "範囲選択を切り替え", - "RangeSelectUp": "範囲選択を上に", - "RangeSelectDown": "範囲選択を下に", - "RangeSelectNotSupported": "このアクションは範囲選択をサポートしていません。単一の項目を選択してください", - "NoItemSelected": "項目が選択されていません", - "SelectedItemIsNotABranch": "選択された項目はブランチではありません", - "SelectedItemDoesNotHaveFiles": "選択された項目には表示するファイルがありません", - "MultiSelectNotSupportedForSubmodules": "サブモジュールでは複数選択はサポートされていません", - "OldCherryPickKeyWarning": "'c'キーは現在、コミットのコピー(チェリーピック)のデフォルトキーではなくなりました。代わりに`{{.copy}}`キーを使用してコピーし、`{{.paste}}`キーでペーストしてください。\n\nこの変更理由は以下の通りです:\n- 以前は'v'キーがステージング時の行選択にのみ使用されていましたが、現在はすべてのリストビューで範囲選択に使われています\n- そのため、コミットペースト用の新しいキーが必要になり、`{{.paste}}`をペーストに使うなら、`{{.copy}}`をコピーに使う方が直感的です\n\n以前の動作に戻したい場合は、設定ファイルに以下を追加してください:\n\nkeybinding:\n universal:\n toggleRangeSelect: \n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'", - "CommandDoesNotSupportOpeningInEditor": "このコマンドはエディタへの切り替えをサポートしていません", - "CustomCommands": "カスタムコマンド", - "NoApplicableCommandsInThisContext": "(このコンテキストでは適用可能なコマンドはありません)", - "SelectCommitsOfCurrentBranch": "現在のブランチのコミットを選択", - "Actions": { - "CheckoutCommit": "コミットをチェックアウト", - "CheckoutBranchAtCommit": "ブランチ '%s' をチェックアウト", - "CheckoutCommitAsDetachedHead": "コミット %s をデタッチドヘッドとしてチェックアウト", - "CheckoutTag": "タグをチェックアウト(切り替え)", - "CheckoutBranch": "ブランチをチェックアウト", - "CheckoutBranchOrCommit": "ブランチまたはコミットをチェックアウト", - "ForceCheckoutBranch": "ブランチを強制チェックアウト", - "DeleteLocalBranch": "ローカルブランチを削除", - "Merge": "マージ", - "SquashMerge": "スカッシュマージ", - "RebaseBranch": "ブランチをリベース", - "RenameBranch": "ブランチ名を変更", - "CreateBranch": "ブランチを作成", - "FastForwardBranch": "ブランチを最新化(fast-forward)", - "CherryPick": "(チェリーピック)コミットをペースト", - "CheckoutFile": "ファイルをチェックアウト", - "SquashCommitDown": "コミットを下にスカッシュ", - "FixupCommit": "fixupコミット", - "RewordCommit": "コミットの修正", - "DropCommit": "コミットを削除", - "EditCommit": "コミットを編集", - "AmendCommit": "コミットを修正", - "ResetCommitAuthor": "コミット作者をリセット", - "SetCommitAuthor": "コミット作者を設定", - "AddCommitCoAuthor": "コミット共同作者を追加", - "RevertCommit": "コミットをリバート", - "CreateFixupCommit": "fixupコミットを作成", - "SquashAllAboveFixupCommits": "上記のすべてのfixupコミットをスカッシュ", - "MoveCommitUp": "コミットを上に移動", - "MoveCommitDown": "コミットを下に移動", - "CopyCommitMessageToClipboard": "コミットメッセージをクリップボードにコピー", - "CopyCommitMessageBodyToClipboard": "コミットメッセージ本文をクリップボードにコピー", - "CopyCommitSubjectToClipboard": "コミット件名をクリップボードにコピー", - "CopyCommitDiffToClipboard": "コミット差分をクリップボードにコピー", - "CopyCommitHashToClipboard": "完全なコミットハッシュをクリップボードにコピー", - "CopyCommitURLToClipboard": "コミットURLをクリップボードにコピー", - "CopyCommitAuthorToClipboard": "コミット作者をクリップボードにコピー", - "CopyCommitAttributeToClipboard": "クリップボードにコピー", - "CopyCommitTagsToClipboard": "コミットタグをクリップボードにコピー", - "CopyPatchToClipboard": "パッチをクリップボードにコピー", - "CustomCommand": "カスタムコマンド", - "DiscardAllChangesInFile": "選択したファイルのすべての変更を破棄", - "DiscardAllUnstagedChangesInFile": "選択したファイルのステージされていないすべての変更を破棄", - "StageFile": "ファイルをステージ", - "StageResolvedFiles": "マージコンフリクトが解決されたファイルをステージ", - "UnstageFile": "ファイルをアンステージ", - "UnstageAllFiles": "すべてのファイルをアンステージ", - "StageAllFiles": "すべてのファイルをステージ", - "ResolveConflictByKeepingFile": "ファイルを保持してコンフリクトを解決", - "ResolveConflictByDeletingFile": "ファイルを削除してコンフリクトを解決", - "NotEnoughContextToStage": "差分コンテキストサイズが0の場合、変更のステージまたはアンステージはできません。'%s'を使用してコンテキストを増やしてください。", - "NotEnoughContextToDiscard": "差分コンテキストサイズが0の場合、変更の破棄はできません。'%s'を使用してコンテキストを増やしてください。", - "IgnoreExcludeFile": "ファイルを無視または除外", - "IgnoreFileErr": ".gitignoreを無視できません", - "ExcludeFile": "ファイルを除外", - "ExcludeGitIgnoreErr": ".gitignoreを除外できません", - "Commit": "コミット", - "Push": "プッシュ", - "Pull": "プル", - "OpenFile": "ファイルを開く", - "StashAllChanges": "すべての変更をスタッシュ", - "StashAllChangesKeepIndex": "すべての変更をスタッシュしてインデックスを保持", - "StashStagedChanges": "ステージされた変更をスタッシュ", - "StashUnstagedChanges": "ステージされていない変更をスタッシュ", - "StashIncludeUntrackedChanges": "追跡されていないファイルを含むすべての変更をスタッシュ", - "GitFlowFinish": "git flow 完了", - "GitFlowStart": "git flow 開始", - "CopyToClipboard": "クリップボードにコピー", - "CopySelectedTextToClipboard": "選択したテキストをクリップボードにコピー", - "RemovePatchFromCommit": "パッチをコミットから削除", - "MovePatchToSelectedCommit": "パッチを選択したコミットに移動", - "MovePatchIntoIndex": "パッチをインデックスに移動", - "MovePatchIntoNewCommit": "パッチを新しいコミットに移動", - "DeleteRemoteBranch": "リモートブランチを削除", - "SetBranchUpstream": "ブランチのアップストリームを設定", - "AddRemote": "リモートを追加", - "RemoveRemote": "リモートを削除", - "UpdateRemote": "リモートを更新", - "ApplyPatch": "パッチを適用", - "Stash": "スタッシュ", - "RenameStash": "スタッシュ名を変更", - "RemoveSubmodule": "サブモジュールを削除", - "ResetSubmodule": "サブモジュールをリセット", - "AddSubmodule": "サブモジュールを追加", - "UpdateSubmoduleUrl": "サブモジュールのURLを更新", - "InitialiseSubmodule": "サブモジュールを初期化", - "BulkInitialiseSubmodules": "サブモジュールを一括初期化", - "BulkUpdateSubmodules": "サブモジュールを一括更新", - "BulkDeinitialiseSubmodules": "サブモジュールを一括解除", - "BulkUpdateRecursiveSubmodules": "サブモジュールを再帰的に一括初期化および更新", - "UpdateSubmodule": "サブモジュールを更新", - "CreateLightweightTag": "軽量タグを作成", - "CreateAnnotatedTag": "注釈付きタグを作成", - "DeleteLocalTag": "ローカルタグを削除", - "DeleteRemoteTag": "リモートタグを削除", - "PushTag": "タグをプッシュ", - "NukeWorkingTree": "作業ツリーを完全に破棄", - "DiscardUnstagedFileChanges": "ステージされていないファイルの変更を破棄", - "RemoveUntrackedFiles": "追跡されていないファイルを削除", - "RemoveStagedFiles": "ステージされたファイルを削除", - "SoftReset": "ソフトリセット(変更を保持してステージ)", - "MixedReset": "ミックスリセット", - "HardReset": "ハードリセット", - "Undo": "元に戻す", - "Redo": "やり直す", - "CopyPullRequestURL": "プルリクエストURLをコピー", - "OpenMergeTool": "マージツールを開く", - "OpenCommitInBrowser": "ブラウザでコミットを開く", - "OpenPullRequest": "ブラウザでプルリクエストを開く", - "StartBisect": "bisectを開始", - "ResetBisect": "bisectをリセット", - "BisectSkip": "bisect スキップ", - "BisectMark": "bisect マーク", - "AddWorktree": "ワークツリーを追加" - }, - "Bisect": { - "MarkStart": "%s を %s としてマーク(bisect開始)", - "ResetTitle": "'git bisect' をリセット", - "ResetPrompt": "'git bisect' をリセットしてもよろしいですか?", - "ResetOption": "bisectをリセット", - "ChooseTerms": "bisect用語を選択", - "OldTermPrompt": "古い/正常なコミットの用語:", - "NewTermPrompt": "新しい/不具合のあるコミットの用語:", - "BisectMenuTitle": "bisect", - "Mark": "現在のコミット(%s)を %s としてマーク", - "SkipCurrent": "現在のコミット(%s)をスキップ", - "SkipSelected": "選択したコミット(%s)をスキップ", - "CompleteTitle": "Bisect完了", - "CompletePrompt": "Bisect完了!以下のコミットが変更を導入しました:\n\n%s\n\n今すぐ 'git bisect' をリセットしてよろしいですか?", - "CompletePromptIndeterminate": "Bisect完了!いくつかのコミットがスキップされたため、以下のコミットのいずれかが変更を導入した可能性があります:\n\n%s\n\n今すぐ 'git bisect' をリセットしてよろしいですか?", - "Bisecting": "Bisect実行中" - }, - "Log": { - "EditRebase": "'{{.ref}}' での対話的リベースを開始", - "HandleUndo": "最後のコンフリクト解決を元に戻し中", - "RemoveFile": "パス '{{.path}}' を削除中", - "CopyToClipboard": "'{{.str}}' をクリップボードにコピー中", - "Remove": "'{{.filename}}' を削除中", - "CreateFileWithContent": "ファイル '{{.path}}' を作成中", - "AppendingLineToFile": "'{{.line}}' をファイル '{{.filename}}' に追加中", - "EditRebaseFromBaseCommit": "'{{.baseCommit}}' から '{{.targetBranchName}}' への対話的リベースを開始" - }, - "BreakingChangesTitle": "破壊的変更", - "BreakingChangesMessage": "lazygitの新しいバージョンに更新すると、破壊的変更が含まれています。以下の注意事項を確認し、必要に応じて設定を更新してください。\n詳細については、の完全なリリースノートを参照してください。", - "BreakingChangesByVersion": { - "0.41.0": "- 'g'キーを押してgitリセットメニューを表示すると、'soft'ではなく'mixed'オプションが最初のデフォルトになりました。これは'mixed'が最も一般的に使用されるオプションだからです。\n- コミットメッセージパネルは、デフォルトで自動的にハードラップされるようになりました(つまり、余白に達すると改行文字が追加されます)。設定は次のように調整できます:\n\ngit:\n commit:\n autoWrapCommitMessage: true\n autoWrapWidth: 72\n\n- 'v'キーはすでにステージングビューで範囲選択を開始するために使用されていましたが、現在ではどのビューでも範囲選択を開始するために使用できます。残念ながら、これはコミットの貼り付け(チェリーピック)の'v'キーバインディングと競合するため、現在はコミットの貼り付けは'shift+V'で行われ、一貫性のために、コミットのコピーは単に'c'ではなく'shift+C'で行われるようになりました。'v'キーバインディングは範囲選択を開始する方法の1つに過ぎないことに注意してください:代わりにshift+上/下矢印を使用できます。したがって、チェリーピックキーバインディングを古い動作に設定したい場合は、設定に以下を設定してください:\n\nkeybinding:\n universal:\n toggleRangeSelect: \n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'\n\n- 'shift-S'を使用したフィックスアップのスカッシュは、現在メニューを表示し、デフォルトオプションはブランチ内のすべてのfixupコミットをスカッシュすることです。選択したコミットの上にあるfixupコミットのみをスカッシュするという元の動作は、そのメニューの2番目のオプションとして引き続き利用できます。\n- プッシュ/プル/フェッチの読み込みステータスは、ポップアップではなくブランチに対して表示されるようになりました。これにより、例えば複数のブランチを並行してフェッチし、各ブランチのステータスを確認できます。\n- コミットビューのgitロググラフは、現在デフォルトで常に表示されるようになりました(以前はビューが最大化されている場合にのみ表示されていました)。これがうるさいと感じる場合は、ctrl+L -> 'Gitグラフを表示' -> '最大化時のみ'で元に戻すことができます。\n- リモートブランチでスペースを押すと、以前はリモートブランチからチェックアウトする新しいローカルブランチの名前を入力するプロンプトが表示されていました。現在は、リモートブランチを直接チェックアウトし、同じ名前の新しいローカルブランチまたはデタッチドヘッドのいずれかを選択できます。古い動作は引き続き'n'キーバインディングで利用できます。\n- フィルタリング(例えば'/'を押したとき)は、デフォルトでは以前ほどあいまいではありません;現在は部分文字列のみに一致します。複数の部分文字列はスペースで区切ることで一致させることができます。古い動作に戻したい場合は、設定に以下を設定してください:\n\ngui:\n filterMode: 'fuzzy'\n\t ", - "0.44.0": "- gui.branchColors設定オプションは非推奨です;将来のバージョンで削除される予定です。代わりにgui.branchColorPatternsを使用してください。\n- 「feature/」、「bugfix/」、または「hotfix/」で始まるブランチの自動カラーリングが削除されました;これが必要な場合は、新しいgui.branchColorPatternsオプションを使用して簡単に設定できます。", - "0.49.0": "- シェルコマンドの実行(':'プロンプトを使用)は、対話型シェルを使用しなくなりました。つまり、このプロンプトでシェルエイリアスを使用したい場合は、少し設定作業が必要です。詳細については、https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#using-aliases-or-functions-in-shell-commands を参照してください。" - } -} diff --git a/pkg/i18n/translations/ko.json b/pkg/i18n/translations/ko.json deleted file mode 100644 index 0027658937a..00000000000 --- a/pkg/i18n/translations/ko.json +++ /dev/null @@ -1,378 +0,0 @@ -{ - "NotEnoughSpace": "패널을 렌더링 할 공간이 부족합니다.", - "DiffTitle": "변경점", - "FilesTitle": "파일", - "BranchesTitle": "브랜치", - "CommitsTitle": "커밋", - "EasterEgg": "이스터 에그", - "UnstagedChanges": "Staged되지 않은 변경 내용", - "StagedChanges": "Staged된 변경 내용", - "StagingTitle": "메인 패널 (Staging)", - "MergingTitle": "메인 패널 (Merging)", - "SquashMergeCommittedTitle": "스쿼시 병합 및 커밋", - "NormalTitle": "메인 패널 (Normal)", - "LogTitle": "로그", - "CommitSummary": "커밋 메시지", - "CredentialsUsername": "사용자 이름", - "CredentialsPassword": "패스워드", - "CredentialsPassphrase": "SSH키의 passphrase 입력", - "CredentialsPIN": "SSH키\u001d의 PIN\u001d을 입력", - "PassUnameWrong": "패스워드, passphrase 또는 사용자 이름이 잘못되었습니다.", - "Commit": "커밋 변경내용", - "AmendLastCommit": "마지맛 커밋 수정", - "AmendLastCommitTitle": "마지막 커밋 수정", - "SureToAmend": "마지막 커밋을 수정하시겠습니까? 그런 다음 커밋 패널에서 커밋 메시지를 변경할 수 있습니다.", - "NoCommitToAmend": "Amend 가능한 커밋이 없습니다.", - "CommitChangesWithEditor": "Git 편집기를 사용하여 변경 내용을 커밋합니다.", - "StatusTitle": "상태", - "GlobalTitle": "글로벌 키 바인딩", - "Execute": "실행", - "Stage": "Staged 전환", - "ToggleStagedAll": "모든 변경을 Staged/unstaged으로 전환", - "ToggleTreeView": "파일 트리뷰로 전환", - "OpenMergeTool": "Git mergetool를 열기", - "Refresh": "새로고침", - "Push": "푸시", - "Pull": "업데이트", - "FileFilter": "파일을 필터하기 (Staged/unstaged)", - "CopyToClipboardMenu": "클립보드에 복사", - "CopyFileName": "파일명", - "CopySelectedDiff": "선택한 파일의 변경점", - "CopyAllFilesDiff": "모든 파일의 변경점", - "NoContentToCopyError": "복사 대상이 없습니다", - "FileNameCopiedToast": "파일명을 클립보드에 복사했습니다.", - "FilePathCopiedToast": "파일경로를 클립보드에 복사했습니다.", - "FileDiffCopiedToast": "파일의 변경점을 클립보드에 복사했습니다.", - "AllFilesDiffCopiedToast": "모든 파일의 변경점을 클립보드에 복사했습니다.", - "FilterStagedFiles": "Staged된 파일만 표시", - "FilterUnstagedFiles": "Stage되지 않은 파일만 표시", - "MergeConflictsTitle": "병합 충돌 내용", - "Checkout": "체크아웃", - "NoChangedFiles": "변경된 파일이 없습니다.", - "SoftReset": "소프트 리셋", - "AlreadyCheckedOutBranch": "브랜치가 이미 체크아웃 되었습니다", - "SureForceCheckout": "강제로 체크아웃하시겠습니까? 모든 로컬 변경 사항을 잃게 됩니다.", - "ForceCheckoutBranch": "브랜치 강제 체크아웃", - "BranchName": "브랜치 이름", - "NewBranchNameBranchOff": "새 브랜치 이름 (branch is off of '{{.branchName}}')", - "CantDeleteCheckOutBranch": "체크아웃하는 브랜치는 삭제할 수 없습니다!", - "DeleteBranchTitle": "'{{.selectedBranchName}}' 브랜치를 삭제하시겠습니까?", - "DeleteLocalBranch": "로컬 브랜치를 삭제", - "ForceDeleteBranchTitle": "브랜치를 강제 삭제", - "ForceDeleteBranchMessage": "'{{.selectedBranchName}}'는 완전히 병합되지 않았습니다. 정말 삭제하시겠습니까?", - "RebaseBranch": "체크아웃된 브랜치를 이 브랜치에 리베이스", - "CantRebaseOntoSelf": "브랜치를 자기 자신에게 리베이스할 수는 없습니다.", - "CantMergeBranchIntoItself": "브랜치를 자기 자신에게 병합할 수는 없습니다.", - "ForceCheckout": "강제 체크아웃", - "CheckoutByName": "이름으로 체크아웃", - "NewBranch": "새 브랜치 생성", - "NoBranchesThisRepo": "저장소에 브랜치가 존재하지 않습니다.", - "CommitWithoutMessageErr": "커밋 메시지를 입력하세요.", - "Close": "닫기", - "CloseCancel": "닫기/취소", - "Confirm": "확인", - "Quit": "종료", - "SureFixupThisCommit": "Are you sure you want to 'fixup' this commit? It will be merged into the commit below", - "SureSquashThisCommit": "Are you sure you want to squash this commit into the commit below?", - "Squash": "스쿼시", - "PickCommitTooltip": "Pick commit (when mid-rebase)", - "Reword": "커밋메시지 변경", - "DropCommit": "커밋 삭제", - "MoveDownCommit": "커밋을 1개 아래로 이동", - "MoveUpCommit": "커밋을 1개 위로 이동", - "EditCommitTooltip": "커밋을 편집", - "AmendCommitTooltip": "Amend commit with staged changes", - "ResetAuthor": "Reset commit author", - "RewordCommitEditor": "에디터에서 커밋메시지 수정", - "NoCommitsThisBranch": "이 브랜치에 커밋이 없습니다.", - "Error": "오류", - "Undo": "되돌리기", - "UndoReflog": "되돌리기 (reflog) (실험적)", - "RedoReflog": "다시 실행 (reflog) (실험적)", - "Apply": "적용", - "NoStashEntries": "Stash가 존재하지 않습니다.", - "StashDrop": "Stash를 삭제", - "StashPop": "Stash를 pop", - "SurePopStashEntry": "정말로 Stash를 pop하시겠습니까?", - "StashApply": "Stash 적용", - "SureApplyStashEntry": "정말로 Stash를 적용하시겠습니까?", - "StashChanges": "변경을 Stash", - "OpenConfig": "설정 파일 열기", - "EditConfig": "설정 파일 수정", - "ForcePush": "강제 푸시", - "ForcePushPrompt": "브랜치가 원격 브랜치에서 분기하고 있습니다. 'esc'를 눌러 취소하거나, 'enter'를 눌러 강제로 푸시하세요.", - "ForcePushDisabled": "브랜치가 원격 브랜치에서 분기하고 있습니다. force push가 비활성화 되었습니다.", - "UpdatesRejectedAndForcePushDisabled": "업데이트가 거부되었으며 강제 푸시를 비활성화했습니다.", - "CheckForUpdate": "업데이트 확인", - "CheckingForUpdates": "업데이트 확인 중...", - "UpdateAvailableTitle": "새로운 업데이트 사용가능!", - "UpdateAvailable": "버전 {{.newVersion}} 을(를) 설치하시겠습니까?", - "UpdateInProgressWaitingStatus": "업데이트 중", - "UpdateCompletedTitle": "업데이트 완료!", - "UpdateCompleted": "업데이트 설치에 성공했습니다. lazygit를 재시작해주세요.", - "FailedToRetrieveLatestVersionErr": "버전 정보를 받아오는데 실패했습니다.", - "OnLatestVersionErr": "이미 최신 버전을 사용하고 있습니다.", - "MajorVersionErr": "새 버전 ({{.newVersion}}) 에 현재 버전({{.currentVersion}}) 과 비교할 때 호환되지 않는 변경 사항이 있습니다.", - "CouldNotFindBinaryErr": "{{.url}} 에서 바이너리를 찾을 수 없습니다.", - "UpdateFailedErr": "업데이트 실패: {{.errMessage}}", - "ConfirmQuitDuringUpdateTitle": "현재 업데이트 중입니다.", - "ConfirmQuitDuringUpdate": "현재 업데이트를 진행 중입니다.종료하시겠습니까?", - "MergeToolTitle": "병합 도구", - "MergeToolPrompt": "정말로 `git mergetool`을 여시겠습니까?", - "GitconfigParseErr": "따옴표로 묶이지 않은 '\\' 문자가 있어서 Gogit이 gitconfig 파일을 분석하지 못했습니다. 이를 제거하면 문제가 해결됩니다.", - "EditFile": "파일 편집", - "OpenFile": "파일 닫기", - "IgnoreFile": ".gitignore에 추가", - "RefreshFiles": "파일 새로고침", - "Merge": "현재 브랜치에 병합", - "ConfirmQuit": "정말로 종료하시겠습니까?", - "SwitchRepo": "최근에 사용한 저장소로 전환", - "UnsupportedGitService": "지원되지 않는 Git 서비스입니다.", - "CopyPullRequestURL": "풀 리퀘스트 URL을 클립보드에 복사", - "NoBranchOnRemote": "브랜치가 원격에 없습니다. 원격에 먼저 푸시해야합니다.", - "FileEnter": "Stage individual hunks/lines for file, or collapse/expand for directory", - "StageSelectionTooltip": "선택한 행을 staged / unstaged", - "DiscardSelection": "변경을 삭제 (git reset)", - "ToggleSelectionForPatch": "Line(s)을 패치에 추가/삭제", - "ToggleStagingView": "패널 전환", - "ReturnToFilesPanel": "파일 목록으로 돌아가기", - "FastForward": "Fast-forward this branch from its upstream", - "FoundConflictsTitle": "Auto-merge failed", - "RecentRepos": "최근에 사용한 저장소", - "CommitSummaryTitle": "커밋메시지", - "LocalBranchesTitle": "브랜치", - "SearchTitle": "검색", - "TagsTitle": "태그", - "MenuTitle": "메뉴", - "RemotesTitle": "원격", - "RemoteBranchesTitle": "원격 브랜치", - "PatchBuildingTitle": "메인 패널 (Patch Building)", - "InformationTitle": "정보", - "ErrorOccurred": "오류가 발생했습니다! issue를 작성해 주세요: ", - "CherryPickCopy": "커밋을 복사 (cherry-pick)", - "PasteCommits": "커밋을 붙여넣기 (cherry-pick)", - "CherryPick": "체리픽", - "Donate": "후원", - "AskQuestion": "질문하기", - "PrevHunk": "이전 hunk를 선택", - "NextHunk": "다음 hunk를 선택", - "PrevConflict": "이전 충돌을 선택", - "NextConflict": "다음 충돌을 선택", - "SelectPrevHunk": "이전 hunk를 선택", - "SelectNextHunk": "다음 hunk를 선택", - "ScrollDown": "아래로 스크롤", - "ScrollUp": "위로 스크롤", - "ScrollUpMainWindow": "메인 패널을 위로 스크롤", - "ScrollDownMainWindow": "메인 패널을 아래로로 스크롤", - "DropCommitTitle": "커밋 삭제", - "DropCommitPrompt": "정말로 선택한 커밋을 삭제하시겠습니까?", - "PullingStatus": "업데이트 중", - "PushingStatus": "푸시 중", - "FetchingStatus": "패치 중", - "SubCommitsDynamicTitle": "커밋 (%s)", - "RemoteBranchesDynamicTitle": "원격브랜치 (%s)", - "ViewItemFiles": "View selected item's files", - "CommitFilesTitle": "커밋 파일", - "CheckoutCommitFileTooltip": "Checkout file", - "DiscardOldFileChangeTooltip": "Discard this commit's changes to this file", - "DiscardFileChangesTitle": "파일 변경 사항 버리기", - "DiscardFileChangesPrompt": "Are you sure you want to discard this commit's changes to this file? If this file was created in this commit, it will be deleted", - "Discard": "View 'discard changes' options", - "Cancel": "취소", - "DiscardAllChanges": "모든 변경사항 버리기", - "Delete": "삭제", - "Reset": "초기화", - "ViewResetOptions": "View reset options", - "CreateFixupCommitTooltip": "Create fixup commit for this commit", - "SquashAboveCommitsTooltip": "Squash all 'fixup!' commits above selected commit (autosquash)", - "PressEnterToReturn": "엔터를 눌러 lazygit으로 돌아갑니다.", - "ViewStashOptions": "Stash 옵션 보기", - "StashAllChanges": "변경사항을 Stash", - "StashOptions": "Stash 옵션", - "ScrollLeft": "우 스크롤", - "ScrollRight": "좌 스크롤", - "DiscardPatch": "Patch 버리기", - "ToggleAllInPatch": "Toggle all files included in patch", - "ViewPatchOptions": "커스텀 Patch 옵션 보기", - "PatchOptionsTitle": "Patch 옵션", - "EnterCommitFile": "Enter file to add selected lines to the patch (or toggle directory collapsed)", - "EnterUpstream": "' '와 같은 형식으로 입력하세요.", - "InvalidUpstream": "Upstream의 형식이 잘못되었습니다.' ' 와 같은 형식으로 입력하세요.", - "NewRemote": "새로운 Remote 추가", - "NewRemoteName": "새로운 Remote 이름:", - "NewRemoteUrl": "새로운 Remote URL:", - "EditRemoteName": "{{.remoteName}} 의 새로운 Remote 이름 입력:", - "EditRemoteUrl": "{{.remoteName}} 의 새로운 Remote URL 입력:", - "RemoveRemote": "Remote를 삭제", - "DeleteRemoteBranch": "원격 브랜치를 삭제", - "SetUpstream": "Set as upstream of checked-out branch", - "EditRemoteTooltip": "Remote를 수정", - "TagNameTitle": "태그 이름", - "TagMessageTitle": "태그 메시지", - "PushTagTitle": "원격에 태그 '{{.tagName}}' 를 푸시", - "PushTag": "태그를 push", - "NewTag": "태그를 생성", - "FetchRemoteTooltip": "원격을 업데이트", - "GitFlowOptions": "Git-flow 옵션 보기", - "NewBranchNamePrompt": "새로운 브랜치 이름 입력", - "NextScreenMode": "다음 스크린 모드 (normal/half/fullscreen)", - "PrevScreenMode": "이전 스크린 모드", - "StartSearch": "검색 시작", - "Keybindings": "키 바인딩", - "RenameBranch": "브랜치 이름 변경", - "OpenKeybindingsMenu": "매뉴 열기", - "ResetCherryPick": "Reset cherry-picked (copied) commits selection", - "NextTab": "이전 탭", - "PrevTab": "다음 탭", - "CantUndoWhileRebasing": "리베이스중에는 되돌릴 수 없습니다.", - "CantRedoWhileRebasing": "리베이스중에는 다시 실행할 수 없습니다.", - "ConfirmationTitle": "확인 패널", - "PrevPage": "이전 페이지", - "NextPage": "다음 페이지", - "GotoTop": "맨 위로 스크롤 ", - "GotoBottom": "맨 아래로 스크롤 ", - "ResetInParentheses": "(reset)", - "OpenFilteringMenu": "View filter-by-path options", - "ExitFilterMode": "Stop filtering by path", - "MustExitFilterModePrompt": "Command not available in filtered mode. Exit filtered mode?", - "EnterRefName": "Ref 입력:", - "ExitDiffMode": "Diff 모드 종료", - "DiffingMenuTitle": "Diff", - "ViewDiffingOptions": "Diff 메뉴 열기", - "OpenCommandLogMenu": "명령어 로그 메뉴 열기", - "CommitDiff": "커밋의 iff", - "CopyCommitHashToClipboard": "커밋 해시를 클립보드에 복사", - "CommitHash": "커밋 해시", - "CommitURL": "커밋 URL", - "CommitMessage": "커밋 메시지", - "CommitAuthor": "커밋 작성자", - "CopyCommitAttributeToClipboard": "커밋 attribute 복사", - "CopyBranchNameToClipboard": "브랜치명을 클립보드에 복사", - "CopyPathToClipboard": "파일명을 클립보드에 복사", - "CopySelectedTextToClipboard": "선택한 텍스트를 클립보드에 복사", - "NoFilesStagedTitle": "파일이 Staged 되지 않았습니다.", - "NoFilesStagedPrompt": "파일이 Staged 되지 않았습니다. 모든 파일을 커밋하시겠습니까?", - "BranchNotFoundTitle": "브랜치를 찾을 수 없습니다.", - "BranchNotFoundPrompt": "브랜치를 찾을 수 없습니다. 새로운 브랜치를 생성합니다.", - "DiscardChangeTitle": "선택한 라인을 unstaged", - "DiscardChangePrompt": "정말로 선택한 라인을 삭제 (git reset) 하시겠습니까? 이 조작은 취소할 수 없습니다.\n이 경고를 비활성화 하려면 설정 파일의 'gui.skipDiscardChangeWarning' 를 true로 설정하세요.", - "CreateNewBranchFromCommit": "커밋에서 새 브랜치를 만듭니다.", - "ViewCommits": "커밋 보기", - "RunningCustomCommandStatus": "커스텀 명령어 실행", - "EnterSubmoduleTooltip": "서브모듈 열기", - "CopySubmoduleNameToClipboard": "서브모듈 이름을 클립보드에 복사", - "RemoveSubmodule": "서브모듈 삭제", - "RemoveSubmodulePrompt": "정말로 서브모듈 '%s'및 해당 디렉토리를 제거하시겠습니까? 이것은 되돌릴 수 없습니다.", - "ResettingSubmoduleStatus": "서브모듈를 리셋", - "NewSubmoduleName": "새로운 서브모듈이름 :", - "NewSubmoduleUrl": "새로운 서브모듈의 URL:", - "NewSubmodulePath": "새로운 서브모듈의 경로", - "NewSubmodule": "새로운 서브모듈 추가", - "AddingSubmoduleStatus": "새로운 서브모듈 추가", - "UpdateSubmoduleUrl": "서브모듈 '%s' 의 URL을 업데이트", - "EditSubmoduleUrl": "서브모듈의 URL을 수정", - "InitializingSubmoduleStatus": "서브모듈 초기화", - "InitSubmoduleTooltip": "서브모듈 초기화", - "SubmoduleUpdateTooltip": "서브모듈 업데이트", - "UpdatingSubmoduleStatus": "서브모듈 업데이트", - "BulkInitSubmodules": "서브모듈 일괄 초기화", - "BulkUpdateSubmodules": "서브모듈 일괄 업데이트", - "SubmodulesTitle": "서브모듈", - "SuggestionsCheatsheetTitle": "추천", - "SuggestionsTitle": "추천 (press %s to focus)", - "ExtrasTitle": "명령어 로그", - "PullRequestURLCopiedToClipboard": "풀 리퀘스트의 URL을 클립보드에 복사했습니다.", - "CommitDiffCopiedToClipboard": "커밋의 Diff를 클립보드에 복사했습니다.", - "CommitURLCopiedToClipboard": "커밋의 URL를 클립보드에 복사했습니다.", - "CommitMessageCopiedToClipboard": "커밋 메시지를 클립보드에 복사했습니다.", - "CommitAuthorCopiedToClipboard": "커밋 작성자를 클립보드에 복사했습니다.", - "CopiedToClipboard": "클립보드에 복사했습니다.", - "ErrCannotEditDirectory": "디렉토리는 편집할 수 없습니다.", - "ErrStageDirWithInlineMergeConflicts": "병합 충돌이 발생한 파일을 포함하는 디렉토리는 Staged/untaged할 수 없습니다. 병합 충돌을 먼저 해결하세요.", - "ErrRepositoryMovedOrDeleted": "저장소를 찾을 수 없습니다. 이미 삭제되었거나 이동되었을 가능성이 있습니다. ¯\\_(ツ)_/¯", - "CommandLog": "명령어 로그", - "ToggleShowCommandLog": "명령어 로그 표시 여부 전환", - "FocusCommandLog": "명령어 로그에 포커스", - "CommandLogHeader": "명령어 로그표시 여부는 '%s' 으로 전환할 수 있습니다.\n", - "RandomTip": "랜덤 Tip", - "ToggleWhitespaceInDiffView": "공백문자를 Diff 뷰에서 표시 여부 전환", - "IncreaseContextInDiffView": "Diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기", - "DecreaseContextInDiffView": "Diff 보기의 변경 사항 주위에 표시되는 컨텍스트 크기 줄이기", - "CreatePullRequestOptions": "풀 리퀘스트 생성 옵션", - "DefaultBranch": "기본 브랜치", - "SelectBranch": "브랜치를 선택", - "CreatePullRequest": "풀 리퀘스트 생성", - "SelectConfigFile": "설정파일 선택", - "NoConfigFileFoundErr": "설정 파일을 찾지 못했습니다.", - "LoadingFileSuggestions": "파일 제안 로딩 중", - "LoadingCommits": "커밋 로딩", - "AbortTitle": "%s 중지", - "AbortPrompt": "정말로 실행중인 %s 를 중지할까요?", - "OpenLogMenu": "로그 메뉴 열기", - "LogMenuTitle": "커밋 로그 옵션", - "ShowGitGraph": "커밋 그래프 표시", - "SortCommits": "커밋 정렬", - "OpenCommitInBrowser": "브라우저에서 커밋 열기", - "ViewBisectOptions": "Bisect 옵션 보기", - "RewordInEditorTitle": "커밋 메시지를 에디터에서 수정", - "ToggleRangeSelect": "드래그 선택 전환", - "Actions": { - "CheckoutCommit": "커밋 체크아웃", - "CheckoutTag": "태그 체크아웃", - "CheckoutBranch": "브랜치 체크아웃", - "ForceCheckoutBranch": "브랜치 Force 체크아웃", - "Merge": "병합", - "RebaseBranch": "브랜치 리베이스", - "RenameBranch": "브랜치 이름 변경", - "CreateBranch": "브랜치 생성", - "CherryPick": "(Cherry-pick) 커밋 붙여넣기", - "CheckoutFile": "체크아웃 파일", - "FixupCommit": "커밋 Fixup", - "RewordCommit": "커밋 Reword", - "DropCommit": "커밋 Drop", - "EditCommit": "커밋 수정", - "AmendCommit": "커밋 Amend", - "ResetCommitAuthor": "커밋 작성자 Reset", - "RevertCommit": "커밋 Revert", - "CreateFixupCommit": "Fixup 커밋 생성", - "CopyCommitMessageToClipboard": "커밋 메시지를 클립보드에 복사", - "CopyCommitDiffToClipboard": "커밋 diff를 클립보드에 복사", - "CopyCommitHashToClipboard": "커밋 해시를 클립보드에 복사", - "CopyCommitURLToClipboard": "커밋 URL를 클립보드에 복사", - "CopyCommitAuthorToClipboard": "커밋 작성자를 클립보드에 복사", - "CopyCommitAttributeToClipboard": "클립보드에 복사", - "DiscardAllChangesInFile": "Discard all changes in file", - "DiscardAllUnstagedChangesInFile": "Discard all unstaged changes in file", - "IgnoreExcludeFile": "Ignore file", - "Commit": "커밋", - "Push": "푸시", - "Pull": "업데이트(Pull)", - "OpenFile": "파일 열기", - "RemoveSubmodule": "서브모듈 삭제", - "ResetSubmodule": "서브모듈 Reset", - "AddSubmodule": "서브모듈 추가", - "UpdateSubmoduleUrl": "서브모듈 URL 업데이트", - "InitialiseSubmodule": "서브모듈 초기화", - "UpdateSubmodule": "서브모듈 업데이트", - "PushTag": "태그 푸시g", - "DiscardUnstagedFileChanges": "Unstaged 파일 변경사항 버리기", - "RemoveUntrackedFiles": "Untracked 파일 삭제", - "RemoveStagedFiles": "Staged 파일 삭제", - "Undo": "되돌리기", - "Redo": "다시 실행", - "CopyPullRequestURL": "풀 리퀘스트 URL 복사", - "OpenMergeTool": "병합 도구 열기", - "OpenCommitInBrowser": "브라우저에서 커밋 열기", - "OpenPullRequest": "브라우저에서 풀 리퀘스트 열기" - }, - "Bisect": { - "ResetTitle": "'git bisect' 를 리셋", - "ResetPrompt": "정말로 'git bisect' 를 리셋하시겠습니까?", - "ResetOption": "Bisect를 리셋", - "Mark": "Mark %s as %s", - "SkipCurrent": "%s 를 스킵", - "CompleteTitle": "Bisect 완료" - }, - "Log": {}, - "BreakingChangesByVersion": {} -} diff --git a/pkg/i18n/translations/nl.json b/pkg/i18n/translations/nl.json deleted file mode 100644 index 24b63f540a3..00000000000 --- a/pkg/i18n/translations/nl.json +++ /dev/null @@ -1,272 +0,0 @@ -{ - "NotEnoughSpace": "Niet genoeg ruimte om de panelen te renderen", - "DiffTitle": "Diff", - "FilesTitle": "Bestanden", - "BranchesTitle": "Branches", - "CommitsTitle": "Commits", - "StashTitle": "Stash", - "UnstagedChanges": "Unstaged wijzigingen", - "StagedChanges": "Staged wijzigingen", - "StagingTitle": "Staging", - "MergingTitle": "Mergen", - "NormalTitle": "Normaal", - "CommitSummary": "Commitbericht", - "CredentialsUsername": "Gebruikersnaam", - "CredentialsPassword": "Wachtwoord", - "CredentialsPassphrase": "Voer een wachtwoordzin in voor de SSH-sleutel", - "PassUnameWrong": "Wachtwoord en/of gebruikersnaam verkeerd", - "Commit": "Commit veranderingen", - "AmendLastCommit": "Wijzig laatste commit", - "AmendLastCommitTitle": "Wijzig laatste commit", - "SureToAmend": "Weet je zeker dat je de laatste commit wilt wijzigen? U kunt het commit-bericht wijzigen vanuit het commits-paneel.", - "NoCommitToAmend": "Er is geen commits om te wijzigen.", - "CommitChangesWithEditor": "Commit veranderingen met de git editor", - "GlobalTitle": "Globale sneltoetsen", - "Execute": "Uitvoeren", - "Stage": "Toggle staged", - "ToggleStagedAll": "Toggle staged alle", - "ToggleTreeView": "Toggle bestandsboom weergave", - "Refresh": "Verversen", - "MergeConflictsTitle": "Merge conflicten", - "Checkout": "Uitchecken", - "NoChangedFiles": "Geen veranderde bestanden", - "SoftReset": "Zacht reset", - "AlreadyCheckedOutBranch": "Je hebt deze branch al uitgecheckt", - "SureForceCheckout": "Weet je zeker dat je het uitchecken wil forceren? Al je lokale verandering zullen worden verwijdert", - "ForceCheckoutBranch": "Forceer uitchecken op deze branch", - "BranchName": "Branch naam", - "NewBranchNameBranchOff": "Nieuw branch naam (Branch is afgeleid van '{{.branchName}}')", - "CantDeleteCheckOutBranch": "Je kan een uitgecheckte branch niet verwijderen!", - "ForceDeleteBranchMessage": "Weet je zeker dat je branch '{{.selectedBranchName}}' geforceerd wil verwijderen?", - "RebaseBranch": "Rebase branch", - "CantRebaseOntoSelf": "Je kan niet een branch rebasen op zichzelf", - "CantMergeBranchIntoItself": "Je kan niet een branch in zichzelf mergen", - "ForceCheckout": "Forceer checkout", - "CheckoutByName": "Uitchecken bij naam", - "NewBranch": "Nieuwe branch", - "NoBranchesThisRepo": "Geen branches voor deze repo", - "CommitWithoutMessageErr": "Je kan geen commit maken zonder commit bericht", - "Close": "Sluiten", - "CloseCancel": "Sluiten", - "Confirm": "Bevestig", - "SureFixupThisCommit": "Weet je zeker dat je fixup wil uitvoeren op deze commit? De commit hieronder zol worden squashed in deze", - "SureSquashThisCommit": "Weet je zeker dat je deze commit wil samenvoegen met de commit hieronder?", - "PickCommitTooltip": "Kies commit (wanneer midden in rebase)", - "Reword": "Hernoem commit", - "DropCommit": "Verwijder commit", - "MoveDownCommit": "Verplaats commit 1 naar beneden", - "MoveUpCommit": "Verplaats commit 1 naar boven", - "EditCommitTooltip": "Wijzig commit", - "AmendCommitTooltip": "Wijzig commit met staged veranderingen", - "RewordCommitEditor": "Hernoem commit met editor", - "NoCommitsThisBranch": "Geen commits in deze branch", - "Error": "Foutmelding", - "Undo": "Ongedaan maken", - "UndoReflog": "Ongedaan maken (via reflog) (experimenteel)", - "RedoReflog": "Redo (via reflog) (experimenteel)", - "Drop": "Laten vallen", - "Apply": "Toepassen", - "NoStashEntries": "Geen stash items", - "StashDrop": "Stash laten vallen", - "SurePopStashEntry": "Weet je zeker dat je deze stash entry wil poppen?", - "StashApply": "Stash toepassen", - "SureApplyStashEntry": "Weet je zeker dat je deze stash entry wil toepassen?", - "NoTrackedStagedFilesStash": "Je hebt geen tracked/staged bestanden om te laten stashen", - "StashChanges": "Stash veranderingen", - "OpenConfig": "Open config bestand", - "EditConfig": "Verander config bestand", - "ForcePush": "Forceer push", - "ForcePushPrompt": "Je branch is afgeweken van de remote branch. Druk {{.cancelKey}} om te annuleren, of {{.confirmKey}} om geforceerd te pushen.", - "CheckForUpdate": "Check voor updates", - "CheckingForUpdates": "Zoeken naar updates...", - "OnLatestVersionErr": "Je hebt al de laatste versie", - "MajorVersionErr": "Nieuwe versie ({{.newVersion}}) is niet backwards compatibele vergeleken met de huidige versie ({{.currentVersion}})", - "CouldNotFindBinaryErr": "Kon geen binary vinden op {{.url}}", - "GitconfigParseErr": "Gogit kon je gitconfig bestand niet goed parsen door de aanwezigheid van losstaande '\\' tekens. Het weghalen van deze tekens zou het probleem moeten oplossen. ", - "EditFile": "Verander bestand", - "OpenFile": "Open bestand", - "IgnoreFile": "Voeg toe aan .gitignore", - "RefreshFiles": "Refresh bestanden", - "Merge": "Merge in met huidige checked out branch", - "ConfirmQuit": "Weet je zeker dat je dit programma wil sluiten?", - "SwitchRepo": "Wissel naar een recente repo", - "UnsupportedGitService": "Niet-ondersteunde git-service", - "CopyPullRequestURL": "Kopieer de URL van het pull-verzoek naar het klembord", - "NoBranchOnRemote": "Deze branch bestaat niet op de remote. U moet het eerst naar de remote pushen.", - "FileEnter": "Stage individuele hunks/lijnen", - "StageSelectionTooltip": "Toggle lijnen staged / unstaged", - "DiscardSelection": "Verwijdert change (git reset)", - "ToggleSelectionForPatch": "Voeg toe/verwijder lijn(en) in patch", - "ToggleStagingView": "Ga naar een ander paneel", - "ReturnToFilesPanel": "Ga terug naar het bestanden paneel", - "FastForward": "Fast-forward deze branch vanaf zijn upstream", - "FoundConflictsTitle": "Conflicten!", - "PickHunk": "Kies stuk", - "PickAllHunks": "Kies beide stukken", - "ViewMergeRebaseOptions": "Bekijk merge/rebase opties", - "NotMergingOrRebasing": "Je bent momenteel niet aan het rebasen of mergen", - "RecentRepos": "Recente repositories", - "MergeOptionsTitle": "Merge opties", - "RebaseOptionsTitle": "Rebase opties", - "CommitSummaryTitle": "Commit bericht", - "LocalBranchesTitle": "Branches", - "SearchTitle": "Zoek", - "PatchBuildingTitle": "Patch bouwen", - "InformationTitle": "Informatie", - "FwdNoUpstream": "Kan niet de branch vooruitspoelen zonder upstream", - "FwdCommitsToPush": "Je kan niet vooruitspoelen als de branch geen nieuwe commits heeft", - "ErrorOccurred": "Er is iets fout gegaan! Zou je hier een issue aan willen maken", - "RewordNotSupported": "Herformatteren van commits in interactief rebasen is nog niet ondersteund", - "CherryPickCopy": "Kopieer commit (cherry-pick)", - "PasteCommits": "Plak commits (cherry-pick)", - "CherryPick": "Cherry-Pick", - "Donate": "Doneer", - "PrevHunk": "Selecteer de vorige hunk", - "NextHunk": "Selecteer de volgende hunk", - "PrevConflict": "Selecteer voorgaand conflict", - "NextConflict": "Selecteer volgende conflict", - "SelectPrevHunk": "Selecteer bovenste hunk", - "SelectNextHunk": "Selecteer onderste hunk", - "ScrollDown": "Scroll omlaag", - "ScrollUp": "Scroll omhoog", - "ScrollUpMainWindow": "Scroll naar beneden vanaf hoofdpaneel", - "ScrollDownMainWindow": "Scroll naar beneden vanaf hoofdpaneel", - "AmendCommitTitle": "Commit wijzigen", - "AmendCommitPrompt": "Weet je zeker dat je deze commit wil wijzigen met de vorige staged bestanden?", - "DropCommitTitle": "Verwijder commit", - "DropCommitPrompt": "Weet je zeker dat je deze commit wil verwijderen?", - "PullingStatus": "Pullen", - "PushingStatus": "Pushen", - "FetchingStatus": "Fetchen", - "SquashingStatus": "Squashen", - "DeletingStatus": "Verwijderen", - "MovingStatus": "Verplaatsen", - "RebasingStatus": "Rebasen", - "AmendingStatus": "Wijzigen", - "CherryPickingStatus": "Cherry-picken", - "UndoingStatus": "Ongedaan maken", - "CheckingOutStatus": "Uitchecken", - "CommitFiles": "Commit bestanden", - "ViewItemFiles": "Bekijk gecommite bestanden", - "CommitFilesTitle": "Commit bestanden", - "CheckoutCommitFileTooltip": "Bestand uitchecken", - "DiscardOldFileChangeTooltip": "Uitsluit deze commit zijn veranderingen aan dit bestand", - "DiscardFileChangesTitle": "Uitsluit bestand zijn veranderingen", - "DiscardFileChangesPrompt": "Weet je zeker dat je de wijzigingen van deze commit in dit bestand wilt weggooien? Als dit bestand is gecreëerd in deze commit dan zal dit bestand worden verwijdert", - "AutoStashPrompt": "Je moet je veranderingen stashen en poppen om ze over te brengen. Dit automatisch doen? (enter/esc)", - "Discard": "Bekijk 'veranderingen ongedaan maken' opties", - "Cancel": "Annuleren", - "DiscardAllChanges": "Negeer alle wijzigingen", - "DiscardUnstagedChanges": "Negeer unstaged wijzigingen", - "DiscardAllChangesToAllFiles": "Verwijder werkende tree", - "DiscardAnyUnstagedChanges": "Gooi unstaged wijzigingen weg", - "DiscardUntrackedFiles": "Negeer niet-gevonden bestanden", - "HardReset": "Harde reset", - "ViewResetOptions": "Bekijk reset opties", - "CreateFixupCommit": "Creëer fixup commit", - "CreateFixupCommitTooltip": "Creëer fixup commit", - "SquashAboveCommitsTooltip": "Squash bovenstaande commits", - "CommitChangesWithoutHook": "Commit veranderingen zonder pre-commit hook", - "ResetTo": "Reset naar", - "PressEnterToReturn": "Press om terug te gaan naar lazygit", - "ViewStashOptions": "Bekijk stash opties", - "StashAllChanges": "Stash-bestanden", - "StashAllChangesKeepIndex": "Stash staged wijzigingen", - "StashOptions": "Stash opties", - "NotARepository": "Fout: moet in een git repository uitgevoerd worden", - "DiscardPatch": "Patch weg gooien", - "DiscardPatchConfirm": "Je kan alleen maar een patch bouwen van 1 commit. Huidige patch weggooien?", - "CantPatchWhileRebasingError": "Je kan geen patch bouwen of patch commando uitvoeren wanneer je in een merging of rebasing state zit", - "ToggleAddToPatch": "Toggle bestand inbegrepen in patch", - "ViewPatchOptions": "Bekijk aangepaste patch opties", - "PatchOptionsTitle": "Patch opties", - "NoPatchError": "Nog geen patch gecreëerd. Om een patch te bouwen gebruik 'space' op een commit bestand of 'enter' om een spesiefieke lijnen toe te voegen", - "EnterCommitFile": "Enter bestand om geselecteerde regels toe te voegen aan de patch", - "ExitCustomPatchBuilder": "Sluit lijn-bij-lijn modus", - "EnterUpstream": "Enter upstream als ' '", - "NewRemote": "Voeg een nieuwe remote toe", - "NewRemoteName": "Nieuwe remote name:", - "NewRemoteUrl": "Nieuwe remote url:", - "EditRemoteName": "Enter updated remote naam voor {{.remoteName}}:", - "EditRemoteUrl": "Enter updated remote url voor {{.remoteName}}:", - "RemoveRemote": "Verwijder remote", - "DeleteRemoteBranch": "Verwijder remote branch", - "SetAsUpstreamTooltip": "Stel in als upstream van uitgecheckte branch", - "SetUpstream": "Stel in als upstream van uitgecheckte branch", - "SetUpstreamTitle": "Stel in als upstream branch", - "EditRemoteTooltip": "Wijzig remote", - "TagNameTitle": "Tag naam:", - "PushTagTitle": "Remote om tag '{{.tagName}}' te pushen naar:", - "NewTag": "Creëer tag", - "FetchRemoteTooltip": "Fetch remote", - "GitFlowOptions": "Laat git-flow opties zien", - "NotAGitFlowBranch": "Dit lijkt geen git flow branch te zijn", - "NewBranchNamePrompt": "Noem een nieuwe branch naam", - "IgnoreTracked": "Negeer tracked bestand", - "IgnoreTrackedPrompt": "Weet je zeker dat je een getracked bestand wil negeren?", - "ViewResetToUpstreamOptions": "Bekijk upstream reset opties", - "NextScreenMode": "Volgende scherm modus (normaal/half/groot)", - "PrevScreenMode": "Vorige scherm modus", - "StartSearch": "Start met zoeken", - "Keybindings": "Sneltoetsen", - "RenameBranch": "Hernoem branch", - "NewGitFlowBranchPrompt": "Nieuwe '{{.branchType}}' naam:", - "RenameBranchWarning": "Deze branch volgt een remote. Deze actie zal alleen de locale branch name wijzigen niet de naam van de remote branch. Verder gaan?", - "OpenKeybindingsMenu": "Open menu", - "ResetCherryPick": "Reset cherry-picked (gekopieerde) commits selectie", - "NextTab": "Volgende tabblad", - "PrevTab": "Vorige tabblad", - "CantUndoWhileRebasing": "Kan niet ongedaan maken terwijl je aan het rebasen bent", - "CantRedoWhileRebasing": "Kan niet opnieuw doen (redo) terwijl je aan het rebasen bent", - "MustStashWarning": "Een patch in de index stoppen vereist stashen en onstashen van je wijzigingen. Als er iets verkeert gaat kan je je bestanden terug vinden in de stash. Verder gaan?", - "MustStashTitle": "Moet stashen", - "ConfirmationTitle": "Bevestigingspaneel", - "PrevPage": "Vorige pagina", - "NextPage": "Volgende pagina", - "GotoTop": "Scroll naar boven", - "GotoBottom": "Scroll naar beneden", - "FilteringBy": "Filteren bij", - "ResetInParentheses": "(reset)", - "OpenFilteringMenu": "Bekijk scoping opties", - "FilterBy": "Filter bij", - "ExitFilterMode": "Stop met filteren bij pad", - "FilterPathOption": "Vulin pad om op te filteren", - "EnterFileName": "Vulin path:", - "FilteringMenuTitle": "Filteren", - "MustExitFilterModeTitle": "Command niet beschikbaar", - "MustExitFilterModePrompt": "Command niet beschikbaar in filter modus. Sluit filter modus?", - "EnterRefToDiff": "Vul in ref naar diff", - "EnterRefName": "Vul in ref:", - "ExitDiffMode": "Sluit diff mode", - "DiffingMenuTitle": "Diffen", - "SwapDiff": "Keer diff richting om", - "ViewDiffingOptions": "Open diff menu", - "ShowingGitDiff": "Laat output zien voor:", - "CopyCommitHashToClipboard": "Kopieer commit hash naar klembord", - "CopyBranchNameToClipboard": "Kopieer branch name naar klembord", - "CopyPathToClipboard": "Kopieer de bestandsnaam naar het klembord", - "CommitPrefixPatternError": "Fout in commitPrefix patroon", - "NoFilesStagedTitle": "Geen bestanden gestaged", - "NoFilesStagedPrompt": "Je hebt geen bestanden gestaged. Commit alle bestanden?", - "BranchNotFoundTitle": "Branch niet gevonden", - "BranchNotFoundPrompt": "Branch niet gevonden. Creëer een nieuwe branch genaamd", - "CreateNewBranchFromCommit": "Creëer nieuwe branch van commit", - "ViewCommits": "Bekijk commits", - "EnterSubmoduleTooltip": "Enter submodule", - "CopySubmoduleNameToClipboard": "Kopieer submodule naam naar klembord", - "NewSubmodule": "Voeg nieuwe submodule toe", - "InitSubmoduleTooltip": "Initialiseer submodule", - "ViewBulkSubmoduleOptions": "Bekijk bulk submodule opties", - "NavigationTitle": "Lijstpaneel navigatie", - "PullRequestURLCopiedToClipboard": "Pull-aanvraag-URL gekopieerd naar klembord", - "CommitMessageCopiedToClipboard": "Commit message gekopieerd naar klembord", - "CopiedToClipboard": "gekopieerd naar klembord", - "CreatePullRequestOptions": "Bekijk opties voor pull-aanvraag", - "CreatePullRequest": "Maak een pull-request", - "ConfirmRevertCommit": "Weet u zeker dat u {{.selectedCommit}} ongedaan wilt maken?", - "ToggleRangeSelect": "Toggle drag selecteer", - "Actions": {}, - "Bisect": {}, - "Log": {}, - "BreakingChangesByVersion": {} -} diff --git a/pkg/i18n/translations/pl.json b/pkg/i18n/translations/pl.json deleted file mode 100644 index 976eb5a7678..00000000000 --- a/pkg/i18n/translations/pl.json +++ /dev/null @@ -1,812 +0,0 @@ -{ - "NotEnoughSpace": "Za mało miejsca na wyświetlenie paneli", - "DiffTitle": "Różnice", - "FilesTitle": "Pliki", - "BranchesTitle": "Gałęzie", - "CommitsTitle": "Commity", - "StashTitle": "Schowek", - "EasterEgg": "Jajko wielkanocne", - "UnstagedChanges": "Zmiany niezatwierdzone", - "StagedChanges": "Zmiany zatwierdzone", - "StagingTitle": "Panel główny (zatwierdzanie)", - "MergingTitle": "Panel główny (scalanie)", - "NormalTitle": "Panel główny (normalny)", - "LogTitle": "Dziennik", - "CommitSummary": "Podsumowanie commita", - "CredentialsUsername": "Nazwa użytkownika", - "CredentialsPassword": "Hasło", - "CredentialsPassphrase": "Wprowadź hasło do klucza SSH", - "CredentialsPIN": "Wprowadź PIN do klucza SSH", - "PassUnameWrong": "Niewłaściwe hasło, fraza lub nazwa użytkownika", - "CommitTooltip": "Zatwierdź zmiany zatwierdzone.", - "AmendLastCommit": "Popraw ostatni commit", - "AmendLastCommitTitle": "Popraw ostatni commit", - "SureToAmend": "Czy na pewno chcesz poprawić ostatni commit? Następnie możesz zmienić wiadomość commita z panelu commitów.", - "NoCommitToAmend": "Brak commita do poprawienia.", - "CommitChangesWithEditor": "Zatwierdź zmiany używając edytora git", - "FindBaseCommitForFixup": "Znajdź bazowy commit do poprawki", - "FindBaseCommitForFixupTooltip": "Znajdź commit, na którym opierają się Twoje obecne zmiany, w celu poprawienia/zmiany commita. To pozwala Ci uniknąć przeglądania commitów w Twojej gałęzi jeden po drugim, aby zobaczyć, który commit powinien być poprawiony/zmieniony. Zobacz dokumentację: ", - "NoBaseCommitsFound": "Nie znaleziono bazowych commitów", - "MultipleBaseCommitsFoundStaged": "Znaleziono wiele bazowych commitów. (Spróbuj zatwierdzić mniej zmian naraz)", - "MultipleBaseCommitsFoundUnstaged": "Znaleziono wiele bazowych commitów. (Spróbuj zatwierdzić część zmian)", - "BaseCommitIsAlreadyOnMainBranch": "Bazowy commit dla tej zmiany jest już na gałęzi głównej", - "BaseCommitIsNotInCurrentView": "Bazowy commit nie jest w bieżącym widoku", - "HunksWithOnlyAddedLinesWarning": "Istnieją zakresy tylko z dodanymi liniami w różnicach; uważaj, aby sprawdzić, czy te należą do znalezionego bazowego commita.\n\nKontynuować?", - "GlobalTitle": "Globalne skróty klawiszowe", - "Execute": "Wykonaj", - "Stage": "Zatwierdź", - "StageTooltip": "Przełącz zatwierdzenie dla wybranego pliku.", - "ToggleStagedAll": "Zatwierdź wszystko", - "ToggleStagedAllTooltip": "Przełącz zatwierdzenie/odznaczenie dla wszystkich plików w drzewie roboczym.", - "ToggleTreeView": "Przełącz widok drzewa plików", - "OpenDiffTool": "Otwórz zewnętrzne narzędzie różnic (git difftool)", - "OpenMergeTool": "Otwórz zewnętrzne narzędzie scalania", - "OpenMergeToolTooltip": "Uruchom `git mergetool`.", - "Refresh": "Odśwież", - "RefreshTooltip": "Odśwież stan git (tj. uruchom `git status`, `git branch`, itp. w tle, aby zaktualizować zawartość paneli). To nie uruchamia `git fetch`.", - "Push": "Wypchnij", - "Pull": "Pociągnij", - "PushTooltip": "Wypchnij bieżącą gałąź do jej gałęzi nadrzędnej. Jeśli nie skonfigurowano gałęzi nadrzędnej, zostaniesz poproszony o skonfigurowanie gałęzi nadrzędnej.", - "PullTooltip": "Pociągnij zmiany z zdalnego dla bieżącej gałęzi. Jeśli nie skonfigurowano gałęzi nadrzędnej, zostaniesz poproszony o skonfigurowanie gałęzi nadrzędnej.", - "FileFilter": "Filtruj pliki według statusu", - "CopyToClipboardMenu": "Kopiuj do schowka", - "CopyFileName": "Nazwa pliku", - "CopyFileDiffTooltip": "Jeśli istnieją zatwierdzone elementy, ta komenda bierze pod uwagę tylko je. W przeciwnym razie bierze pod uwagę wszystkie niezatwierdzone.", - "CopySelectedDiff": "Różnice wybranego pliku", - "CopyAllFilesDiff": "Różnice wszystkich plików", - "NoContentToCopyError": "Nic do skopiowania", - "FileNameCopiedToast": "Nazwa pliku skopiowana do schowka", - "FilePathCopiedToast": "Ścieżka pliku skopiowana do schowka", - "FileDiffCopiedToast": "Różnice pliku skopiowane do schowka", - "AllFilesDiffCopiedToast": "Różnice wszystkich plików skopiowane do schowka", - "FilterStagedFiles": "Pokaż tylko zatwierdzone pliki", - "FilterUnstagedFiles": "Pokaż tylko niezatwierdzone pliki", - "MergeConflictsTitle": "Konflikty scalania", - "Checkout": "Przełącz", - "CheckoutTooltip": "Przełącz wybrany element.", - "CantCheckoutBranchWhilePulling": "Nie możesz przełączyć na inną gałąź podczas pobierania bieżącej gałęzi", - "TagCheckoutTooltip": "Przełącz wybrany tag jako odłączoną głowę (detached HEAD).", - "RemoteBranchCheckoutTooltip": "Przełącz na nową lokalną gałąź na podstawie wybranej gałęzi zdalnej. Nowa gałąź będzie śledzić gałąź zdalną.", - "CantPullOrPushSameBranchTwice": "Nie możesz wypchnąć lub pociągnąć gałęzi, podczas gdy jest już wypychana lub pociągana", - "NoChangedFiles": "Brak zmienionych plików", - "SoftReset": "Miękki reset", - "AlreadyCheckedOutBranch": "Już przełączono na tę gałąź", - "SureForceCheckout": "Czy na pewno chcesz wymusić przełączenie? Stracisz wszystkie lokalne zmiany", - "ForceCheckoutBranch": "Wymuś przełączenie gałęzi", - "BranchName": "Nazwa gałęzi", - "NewBranchNameBranchOff": "Nowa nazwa gałęzi (gałąź oparta na '{{.branchName}}')", - "CantDeleteCheckOutBranch": "Nie możesz usunąć przełączonej gałęzi!", - "DeleteBranchTitle": "Usuń gałąź '{{.selectedBranchName}}'?", - "DeleteLocalBranch": "Usuń lokalną gałąź", - "DeleteRemoteBranchPrompt": "Czy na pewno chcesz usunąć gałąź zdalną '{{.selectedBranchName}}' z '{{.upstream}}'?", - "ForceDeleteBranchTitle": "Wymuś usunięcie gałęzi", - "ForceDeleteBranchMessage": "'{{.selectedBranchName}}' nie jest w pełni scalona. Czy na pewno chcesz ją usunąć?", - "RebaseBranch": "Przebazuj", - "RebaseBranchTooltip": "Przebazuj przełączoną gałąź na wybraną gałąź.", - "CantRebaseOntoSelf": "Nie możesz przebazować gałęzi na siebie", - "CantMergeBranchIntoItself": "Nie możesz scalić gałęzi do siebie", - "ForceCheckout": "Wymuś przełączenie", - "ForceCheckoutTooltip": "Wymuś przełączenie wybranej gałęzi. To spowoduje odrzucenie wszystkich lokalnych zmian w drzewie roboczym przed przełączeniem na wybraną gałąź.", - "CheckoutByName": "Przełącz według nazwy", - "CheckoutByNameTooltip": "Przełącz według nazwy. W polu wprowadzania możesz wpisać '-' aby przełączyć się na ostatnią gałąź.", - "NewBranch": "Nowa gałąź", - "NewBranchFromStashTooltip": "Utwórz nową gałąź z wybranego wpisu schowka. Działa poprzez przełączenie git na commit, na którym wpis schowka został utworzony, tworzenie nowej gałęzi z tego commita, a następnie zastosowanie wpisu schowka do nowej gałęzi jako dodatkowego commita.", - "NoBranchesThisRepo": "Brak gałęzi dla tego repozytorium", - "CommitWithoutMessageErr": "Nie możesz commitować bez wiadomości commita", - "Close": "Zamknij", - "CloseCancel": "Zamknij/Anuluj", - "Confirm": "Potwierdź", - "Quit": "Wyjdź", - "SquashTooltip": "Scal wybrany commit z commitami poniżej. Wiadomość wybranego commita zostanie dołączona do commita poniżej.", - "CannotSquashOrFixupFirstCommit": "Nie ma commita poniżej do scalenia", - "Fixup": "Poprawka", - "FixupTooltip": "Włącz wybrany commit do commita poniżej. Podobnie do fixup, ale wiadomość wybranego commita zostanie odrzucona.", - "SureFixupThisCommit": "Czy na pewno chcesz 'poprawić' wybrane commit(y) do commita poniżej?", - "SureSquashThisCommit": "Czy na pewno chcesz scalić wybrane commit(y) do commita poniżej?", - "Squash": "Scal", - "PickCommitTooltip": "Oznacz wybrany commit do wybrania (podczas rebazowania). Oznacza to, że commit zostanie zachowany po kontynuacji rebazowania.", - "Pick": "Wybierz", - "Edit": "Edytuj", - "Revert": "Cofnij", - "RevertCommitTooltip": "Utwórz commit cofający dla wybranego commita, który stosuje zmiany wybranego commita w odwrotnej kolejności.", - "Reword": "Przeformułuj", - "CommitRewordTooltip": "Przeformułuj wiadomość wybranego commita.", - "DropCommit": "Usuń", - "DropCommitTooltip": "Usuń wybrany commit. To usunie commit z gałęzi za pomocą rebazowania. Jeśli commit wprowadza zmiany, od których zależą późniejsze commity, być może będziesz musiał rozwiązać konflikty scalania.", - "MoveDownCommit": "Przesuń commit w dół", - "MoveUpCommit": "Przesuń commit w górę", - "CannotMoveAnyFurther": "Nie można przesunąć dalej", - "EditCommit": "Edytuj (rozpocznij interaktywne rebazowanie)", - "EditCommitTooltip": "Edytuj wybrany commit. Użyj tego, aby rozpocząć interaktywne rebazowanie od wybranego commita. Podczas trwania rebazowania, to oznaczy wybrany commit do edycji, co oznacza, że po kontynuacji rebazowania, rebazowanie zostanie wstrzymane na wybranym commicie, aby umożliwić wprowadzenie zmian.", - "AmendCommitTooltip": "Popraw commit ze zmianami zatwierdzonymi. Jeśli wybrany commit jest commit HEAD, to wykona `git commit --amend`. W przeciwnym razie commit zostanie poprawiony za pomocą rebazowania.", - "Amend": "Popraw", - "ResetAuthor": "Resetuj autora", - "ResetAuthorTooltip": "Resetuj autora commita do aktualnie skonfigurowanego użytkownika. To również odświeży znacznik czasu autora", - "SetAuthor": "Ustaw autora", - "SetAuthorTooltip": "Ustaw autora na podstawie monitu", - "AddCoAuthor": "Dodaj współautora", - "AmendCommitAttribute": "Popraw atrybut commita", - "AmendCommitAttributeTooltip": "Ustaw/Resetuj autora commita lub ustaw współautora.", - "SetAuthorPromptTitle": "Ustaw autora (musi wyglądać jak 'Imię ')", - "AddCoAuthorPromptTitle": "Dodaj współautora (musi wyglądać jak 'Imię ')", - "AddCoAuthorTooltip": "Dodaj współautora używając metadanych Github/Gitlab Co-authored-by.", - "RewordCommitEditor": "Przeformułuj za pomocą edytora", - "NoCommitsThisBranch": "Brak commitów dla tej gałęzi", - "UpdateRefHere": "Zaktualizuj gałąź '{{.ref}}' tutaj", - "Error": "Błąd", - "Undo": "Cofnij", - "UndoReflog": "Cofnij", - "RedoReflog": "Ponów", - "UndoTooltip": "Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby cofnąć ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity.", - "RedoTooltip": "Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby ponowić ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity.", - "UndoMergeResolveTooltip": "Cofnij ostatnie rozwiązanie konfliktu scalania.", - "DiscardAllTooltip": "Odrzuć wszystkie zmiany (zarówno zatwierdzone jak i niezatwierdzone) w '{{.path}}'.", - "DiscardUnstagedTooltip": "Odrzuć niezatwierdzone zmiany w '{{.path}}'.", - "Pop": "Wyciągnij", - "StashPopTooltip": "Zastosuj wpis schowka do katalogu roboczego i usuń wpis schowka.", - "Drop": "Usuń", - "StashDropTooltip": "Usuń wpis schowka z listy schowka.", - "Apply": "Zastosuj", - "StashApplyTooltip": "Zastosuj wpis schowka do katalogu roboczego.", - "NoStashEntries": "Brak wpisów schowka", - "StashDrop": "Usuń schowek", - "StashPop": "Wyciągnij schowek", - "SurePopStashEntry": "Czy na pewno chcesz wyciągnąć ten wpis schowka?", - "StashApply": "Zastosuj schowek", - "SureApplyStashEntry": "Czy na pewno chcesz zastosować ten wpis schowka?", - "NoTrackedStagedFilesStash": "Nie masz śledzonych/zatwierdzonych plików do schowania", - "NoFilesToStash": "Nie masz plików do schowania", - "StashChanges": "Schowaj zmiany", - "RenameStash": "Zmień nazwę schowka", - "RenameStashPrompt": "Zmień nazwę schowka: {{.stashName}}", - "OpenConfig": "Otwórz plik konfiguracyjny", - "EditConfig": "Edytuj plik konfiguracyjny", - "ForcePush": "Wymuś wysłanie", - "ForcePushPrompt": "Twoja gałąź rozbiegła się z gałęzią zdalną. Naciśnij {{.cancelKey}}, aby anulować, lub {{.confirmKey}}, aby wymusić wysłanie.", - "ForcePushDisabled": "Twoja gałąź rozbiegła się z gałęzią zdalną i masz wyłączone wymuszanie wysyłania", - "UpdatesRejectedAndForcePushDisabled": "Aktualizacje zostały odrzucone i wyłączyłeś wymuszenie wysłania", - "CheckForUpdate": "Sprawdź aktualizacje", - "CheckingForUpdates": "Sprawdzanie aktualizacji...", - "UpdateAvailableTitle": "Dostępna aktualizacja!", - "UpdateAvailable": "Pobrać i zainstalować wersję {{.newVersion}}?", - "UpdateInProgressWaitingStatus": "Aktualizacja", - "UpdateCompletedTitle": "Aktualizacja zakończona!", - "UpdateCompleted": "Aktualizacja została pomyślnie zainstalowana. Uruchom ponownie lazygit, aby zaczęła działać.", - "FailedToRetrieveLatestVersionErr": "Nie udało się pobrać informacji o wersji", - "OnLatestVersionErr": "Masz już najnowszą wersję", - "MajorVersionErr": "Nowa wersja ({{.newVersion}}) zawiera zmiany niekompatybilne wstecznie w porównaniu z bieżącą wersją ({{.currentVersion}})", - "CouldNotFindBinaryErr": "Nie można znaleźć żadnego pliku binarnego pod adresem {{.url}}", - "UpdateFailedErr": "Aktualizacja nie powiodła się: {{.errMessage}}", - "ConfirmQuitDuringUpdateTitle": "Aktualizacja w toku", - "ConfirmQuitDuringUpdate": "Aktualizacja jest w toku. Czy na pewno chcesz wyjść?", - "MergeToolTitle": "Narzędzie scalania", - "MergeToolPrompt": "Czy na pewno chcesz otworzyć `git mergetool`?", - "GitconfigParseErr": "Gogit nie mógł przetworzyć pliku gitconfig z powodu obecności niezacytowanych znaków '\\'. Usunięcie ich powinno rozwiązać problem.", - "EditFile": "Edytuj plik", - "EditFileTooltip": "Otwórz plik w zewnętrznym edytorze.", - "OpenFile": "Otwórz plik", - "OpenFileTooltip": "Otwórz plik w domyślnej aplikacji.", - "OpenInEditor": "Otwórz w edytorze", - "IgnoreFile": "Dodaj do .gitignore", - "ExcludeFile": "Dodaj do .git/info/exclude", - "RefreshFiles": "Odśwież pliki", - "Merge": "Scal", - "MergeBranchTooltip": "Scal wybraną gałąź z aktualnie sprawdzoną gałęzią.", - "ConfirmQuit": "Czy na pewno chcesz wyjść?", - "SwitchRepo": "Przełącz na ostatnie repozytorium", - "UnsupportedGitService": "Nieobsługiwana usługa git", - "CopyPullRequestURL": "Kopiuj adres URL żądania ściągnięcia do schowka", - "NoBranchOnRemote": "Ta gałąź nie istnieje na zdalnym serwerze. Musisz ją najpierw wysłać na zdalny serwer.", - "Fetch": "Pobierz", - "FetchTooltip": "Pobierz zmiany ze zdalnego serwera.", - "FileEnter": "Zatwierdź linie / Zwiń katalog", - "FileEnterTooltip": "Jeśli wybrany element jest plikiem, skup się na widoku zatwierdzania, aby móc zatwierdzać poszczególne fragmenty/linie. Jeśli wybrany element jest katalogiem, zwiń/rozwiń go.", - "StageSelectionTooltip": "Przełącz zaznaczenie zatwierdzone/niezatwierdzone.", - "DiscardSelection": "Odrzuć", - "DiscardSelectionTooltip": "Gdy zaznaczona jest niezatwierdzona zmiana, odrzuć ją używając `git reset`. Gdy zaznaczona jest zatwierdzona zmiana, cofnij zatwierdzenie.", - "ToggleSelectionForPatch": "Przełącz linie w łatce", - "EditHunk": "Edytuj fragment", - "EditHunkTooltip": "Edytuj wybrany fragment w zewnętrznym edytorze.", - "ToggleStagingView": "Przełącz widok", - "ToggleStagingViewTooltip": "Przełącz na inny widok (zatwierdzone/niezatwierdzone zmiany).", - "ReturnToFilesPanel": "Wróć do panelu plików", - "FastForward": "Szybkie przewijanie", - "FastForwardTooltip": "Szybkie przewijanie wybranej gałęzi z jej źródła.", - "FastForwarding": "Szybkie przewijanie", - "FoundConflictsTitle": "Konflikty!", - "ViewConflictsMenuItem": "Pokaż konflikty", - "AbortMenuItem": "Przerwij %s", - "PickHunk": "Wybierz fragment", - "PickAllHunks": "Wybierz wszystkie fragmenty", - "ViewMergeRebaseOptions": "Pokaż opcje scalania/rebase", - "ViewMergeRebaseOptionsTooltip": "Pokaż opcje do przerwania/kontynuowania/pominięcia bieżącego scalania/rebase.", - "ViewMergeOptions": "Pokaż opcje scalania", - "ViewRebaseOptions": "Pokaż opcje rebase", - "NotMergingOrRebasing": "Aktualnie nie wykonujesz ani scalania, ani rebase", - "AlreadyRebasing": "Nie można wykonać tej akcji podczas rebase", - "RecentRepos": "Ostatnie repozytoria", - "MergeOptionsTitle": "Opcje scalania", - "RebaseOptionsTitle": "Opcje rebase", - "CommitSummaryTitle": "Podsumowanie commita", - "CommitDescriptionTitle": "Opis commita", - "CommitDescriptionSubTitle": "Naciśnij {{.togglePanelKeyBinding}}, aby przełączyć fokus, {{.commitMenuKeybinding}}, aby otworzyć menu", - "LocalBranchesTitle": "Lokalne gałęzie", - "SearchTitle": "Szukaj", - "TagsTitle": "Tagi", - "CommitMenuTitle": "Menu commita", - "RemotesTitle": "Zdalne", - "RemoteBranchesTitle": "Zdalne gałęzie", - "PatchBuildingTitle": "Główny panel (budowanie łatki)", - "InformationTitle": "Informacje", - "SecondaryTitle": "Dodatkowy", - "Continue": "Kontynuuj", - "RebasingFromBaseCommitTitle": "Rebase '{{.checkedOutBranch}}' od oznaczonego commita bazowego", - "SimpleRebase": "Prosty rebase na '{{.ref}}'", - "InteractiveRebase": "Interaktywny rebase na '{{.ref}}'", - "InteractiveRebaseTooltip": "Rozpocznij interaktywny rebase z przerwaniem na początku, abyś mógł zaktualizować commity TODO przed kontynuacją.", - "MustSelectTodoCommits": "Podczas rebase ta akcja działa tylko na zaznaczonych commitach TODO.", - "FwdNoUpstream": "Nie można szybko przewinąć gałęzi bez źródła", - "FwdNoLocalUpstream": "Nie można szybko przewinąć gałęzi, której zdalne źródło nie jest zarejestrowane lokalnie", - "FwdCommitsToPush": "Nie można szybko przewinąć gałęzi z commitami do wysłania", - "PullRequestNoUpstream": "Nie można otworzyć żądania ściągnięcia dla gałęzi bez źródła", - "ErrorOccurred": "Wystąpił błąd! Proszę utworzyć zgłoszenie na", - "YouDied": "ZGINĄŁEŚ!", - "RewordNotSupported": "Zmiana słów commitów podczas interaktywnego rebase nie jest obecnie obsługiwana", - "ChangingThisActionIsNotAllowed": "Zmiana tego rodzaju wpisu rebase TODO nie jest dozwolona", - "CherryPickCopy": "Kopiuj (cherry-pick)", - "CherryPickCopyTooltip": "Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `{{.paste}}`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `{{.escape}}`, aby anulować zaznaczenie.", - "PasteCommits": "Wklej (cherry-pick)", - "CannotCherryPickNonCommit": "Nie można cherry-pick tego rodzaju wpisu TODO", - "Donate": "Wesprzyj", - "AskQuestion": "Zadaj pytanie", - "PrevHunk": "Idź do poprzedniego fragmentu", - "NextHunk": "Idź do następnego fragmentu", - "PrevConflict": "Poprzedni konflikt", - "NextConflict": "Następny konflikt", - "SelectPrevHunk": "Poprzedni fragment", - "SelectNextHunk": "Następny fragment", - "ScrollDown": "Przewiń w dół", - "ScrollUp": "Przewiń w górę", - "ScrollUpMainWindow": "Przewiń główne okno w górę", - "ScrollDownMainWindow": "Przewiń główne okno w dół", - "AmendCommitTitle": "Popraw commit", - "AmendCommitPrompt": "Czy na pewno chcesz poprawić ten commit swoimi zatwierdzonymi plikami?", - "DropCommitTitle": "Usuń commit", - "DropCommitPrompt": "Czy na pewno chcesz usunąć wybrane commity?", - "PullingStatus": "Ściąganie", - "PushingStatus": "Wysyłanie", - "FetchingStatus": "Pobieranie", - "SquashingStatus": "Sciskanie", - "FixingStatus": "Naprawianie", - "DeletingStatus": "Usuwanie", - "DroppingStatus": "Upuszczanie", - "MovingStatus": "Przesuwanie", - "RebasingStatus": "Rebase", - "MergingStatus": "Scalanie", - "LowercaseRebasingStatus": "rebase", - "LowercaseMergingStatus": "scalanie", - "AmendingStatus": "Poprawianie", - "UndoingStatus": "Cofanie", - "RedoingStatus": "Ponawianie", - "CheckingOutStatus": "Sprawdzanie", - "CommittingStatus": "Commitowanie", - "RevertingStatus": "Przywracanie", - "CreatingFixupCommitStatus": "Tworzenie commita poprawiającego", - "CommitFiles": "Zatwierdź pliki", - "SubCommitsDynamicTitle": "Commity (%s)", - "CommitFilesDynamicTitle": "Pliki różnic (%s)", - "RemoteBranchesDynamicTitle": "Zdalne gałęzie (%s)", - "ViewItemFiles": "Wyświetl pliki", - "CommitFilesTitle": "Pliki commita", - "CheckoutCommitFileTooltip": "Przełącz plik. Zastępuje plik w twoim drzewie roboczym wersją z wybranego commita.", - "CanOnlyDiscardFromLocalCommits": "Można odrzucić tylko zmiany z lokalnych commitów", - "Remove": "Usuń", - "DiscardOldFileChangeTooltip": "Odrzuć zmiany w tym pliku z tego commita. Uruchamia interaktywny rebase w tle, więc możesz otrzymać konflikt scalania, jeśli późniejszy commit również zmienia ten plik.", - "DiscardFileChangesTitle": "Odrzuć zmiany w pliku", - "DiscardFileChangesPrompt": "Czy na pewno chcesz usunąć zmiany w wybranym pliku/ach z tego commita?\n\nTa akcja uruchomi rebase, cofając te zmiany w pliku. Pamiętaj, że jeśli późniejsze commity zależą od tych zmian, możesz potrzebować rozwiązać konflikty.\nUwaga: Spowoduje to również zresetowanie wszelkich aktywnych niestandardowych łatek.", - "BareRepo": "Próbujesz otworzyć Lazygit w gołym repozytorium, ale Lazygit jeszcze nie obsługuje gołych repozytoriów. Otworzyć najnowsze repozytorium? (t/n) ", - "InitialBranch": "Nazwa gałęzi? (pozostaw puste dla domyślnej gita): ", - "NoRecentRepositories": "Musisz otworzyć lazygit w repozytorium git. Brak ważnych ostatnich repozytoriów. Wyjście.", - "IncorrectNotARepository": "Wartość 'notARepository' jest nieprawidłowa. Powinna być jedną z 'prompt', 'create', 'skip', lub 'quit'.", - "AutoStashPrompt": "Musisz schować i wyciągnąć swoje zmiany, aby je przenieść. Zrobić to automatycznie? (enter/esc)", - "Discard": "Odrzuć", - "DiscardChangesTitle": "Odrzuć zmiany", - "DiscardFileChangesTooltip": "Wyświetl opcje odrzucania zmian w wybranym pliku.", - "Cancel": "Anuluj", - "DiscardAllChanges": "Odrzuć wszystkie zmiany", - "DiscardUnstagedChanges": "Odrzuć niezatwierdzone zmiany", - "DiscardAllChangesToAllFiles": "Zniszcz drzewo robocze", - "DiscardAnyUnstagedChanges": "Odrzuć niezatwierdzone zmiany", - "DiscardUntrackedFiles": "Odrzuć nieśledzone pliki", - "DiscardStagedChanges": "Odrzuć zatwierdzone zmiany", - "HardReset": "Twardy reset", - "BranchDeleteTooltip": "Wyświetl opcje usuwania lokalnej/odległej gałęzi.", - "TagDeleteTooltip": "Wyświetl opcje usuwania lokalnego/odległego tagu.", - "Delete": "Usuń", - "ResetTooltip": "Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu.", - "FileResetOptionsTooltip": "Wyświetl opcje resetu dla drzewa roboczego (np. zniszczenie drzewa roboczego).", - "CreateFixupCommit": "Utwórz commit fixup", - "CreateFixupCommitTooltip": "Utwórz commit 'fixup!' dla wybranego commita. Później możesz nacisnąć `{{.squashAbove}}` na tym samym commicie, aby zastosować wszystkie powyższe commity fixup.", - "SquashAboveCommitsTooltip": "Scal wszystkie commity 'fixup!', albo powyżej wybranego commita, albo wszystkie w bieżącej gałęzi (autosquash).", - "SquashCommitsAboveSelectedTooltip": "Scal wszystkie commity 'fixup!' powyżej wybranego commita (autosquash).", - "SquashCommitsInCurrentBranchTooltip": "Scal wszystkie commity 'fixup!' w bieżącej gałęzi (autosquash).", - "SquashAboveCommits": "Zastosuj commity fixup", - "SquashCommitsInCurrentBranch": "W bieżącej gałęzi", - "SquashCommitsAboveSelectedCommit": "Powyżej wybranego commita", - "CannotSquashCommitsInCurrentBranch": "Nie można scalić commitów w bieżącej gałęzi: commit HEAD jest commit merge lub jest obecny na głównej gałęzi.", - "CommitChangesWithoutHook": "Zatwierdź zmiany bez hooka pre-commit", - "ResetTo": "Resetuj do", - "ResetSoftTooltip": "Resetuj HEAD do wybranego commita, zachowując zmiany między bieżącym a wybranym commit jako zmiany zatwierdzone.", - "ResetMixedTooltip": "Resetuj HEAD do wybranego commita, zachowując zmiany między bieżącym a wybranym commit jako zmiany niezatwierdzone.", - "ResetHardTooltip": "Resetuj HEAD do wybranego commita, odrzucając wszystkie zmiany między bieżącym a wybranym commit, jak również wszystkie bieżące modyfikacje w drzewie roboczym.", - "PressEnterToReturn": "Naciśnij enter, aby wrócić do lazygit", - "ViewStashOptions": "Wyświetl opcje schowka", - "ViewStashOptionsTooltip": "Wyświetl opcje schowka (np. schowaj wszystko, schowaj zatwierdzone, schowaj niezatwierdzone).", - "Stash": "Schowaj", - "StashTooltip": "Schowaj wszystkie zmiany. Dla innych wariantów schowania, użyj klawisza wyświetlania opcji schowka.", - "StashAllChanges": "Schowaj wszystkie zmiany", - "StashStagedChanges": "Schowaj zatwierdzone zmiany", - "StashAllChangesKeepIndex": "Schowaj wszystkie zmiany i zachowaj indeks", - "StashUnstagedChanges": "Schowaj niezatwierdzone zmiany", - "StashIncludeUntrackedChanges": "Schowaj wszystkie zmiany włącznie z nieśledzonymi plikami", - "StashOptions": "Opcje schowka", - "NotARepository": "Błąd: musi być uruchomione wewnątrz repozytorium git", - "WorkingDirectoryDoesNotExist": "Błąd: bieżący katalog roboczy nie istnieje", - "ScrollLeft": "Przewiń w lewo", - "ScrollRight": "Przewiń w prawo", - "DiscardPatch": "Odrzuć łatkę", - "DiscardPatchConfirm": "Możesz zbudować łatkę tylko z jednego commita/stanu schowka na raz. Odrzucić bieżącą łatkę?", - "CantPatchWhileRebasingError": "Nie można budować łatki ani uruchamiać poleceń łatki podczas scalania lub rebasowania", - "ToggleAddToPatch": "Przełącz plik włączony w łatkę", - "ToggleAddToPatchTooltip": "Przełącz, czy plik jest włączony w niestandardową łatkę. Zobacz {{.doc}}.", - "ToggleAllInPatch": "Przełącz wszystkie pliki", - "ToggleAllInPatchTooltip": "Dodaj/usuń wszystkie pliki commita do niestandardowej łatki. Zobacz {{.doc}}.", - "UpdatingPatch": "Aktualizowanie łatki", - "ViewPatchOptions": "Wyświetl opcje niestandardowej łatki", - "PatchOptionsTitle": "Opcje łatki", - "NoPatchError": "Brak utworzonej łatki. Aby zacząć budować łatkę, użyj 'spacji' na pliku commita lub enter, aby dodać określone linie", - "EmptyPatchError": "Łatka jest nadal pusta. Najpierw dodaj kilka plików lub linii do łatki.", - "EnterCommitFile": "Wejdź do pliku / Przełącz zwiń katalog", - "EnterCommitFileTooltip": "Jeśli plik jest wybrany, wejdź do pliku, aby móc dodawać/usuwać poszczególne linie do niestandardowej łatki. Jeśli wybrany jest katalog, przełącz katalog.", - "ExitCustomPatchBuilder": "Wyjdź z budowniczego niestandardowej łatki", - "EnterUpstream": "Wprowadź upstream jako ' '", - "InvalidUpstream": "Nieprawidłowy upstream. Musi być w formacie ' '", - "NewRemote": "Nowy zdalny", - "NewRemoteName": "Nowa nazwa zdalnego:", - "NewRemoteUrl": "Nowy URL zdalnego:", - "ViewBranches": "Wyświetl gałęzie", - "EditRemoteName": "Wprowadź zaktualizowaną nazwę zdalnego dla {{.remoteName}}:", - "EditRemoteUrl": "Wprowadź zaktualizowany URL zdalnego dla {{.remoteName}}:", - "RemoveRemote": "Usuń zdalny", - "RemoveRemoteTooltip": "Usuń wybrany zdalny. Wszelkie lokalne gałęzie śledzące gałąź zdalną z tego zdalnego nie zostaną dotknięte.", - "DeleteRemoteBranch": "Usuń gałąź zdalną", - "DeleteRemoteBranchTooltip": "Usuń gałąź zdalną ze zdalnego.", - "SetAsUpstream": "Ustaw jako upstream", - "SetAsUpstreamTooltip": "Ustaw wybraną gałąź zdalną jako upstream sprawdzonej gałęzi.", - "SetUpstream": "Ustaw upstream wybranej gałęzi", - "UnsetUpstream": "Usuń upstream wybranej gałęzi", - "ViewDivergenceFromUpstream": "Wyświetl rozbieżność od upstream", - "DivergenceSectionHeaderLocal": "Lokalne", - "DivergenceSectionHeaderRemote": "Zdalne", - "ViewUpstreamResetOptions": "Resetuj sprawdzoną gałąź na {{.upstream}}", - "ViewUpstreamResetOptionsTooltip": "Wyświetl opcje resetowania sprawdzonej gałęzi na {{upstream}}. Uwaga: to nie zresetuje wybranej gałęzi na upstream, zresetuje sprawdzoną gałąź na upstream.", - "ViewUpstreamRebaseOptions": "Rebase sprawdzonej gałęzi na {{.upstream}}", - "ViewUpstreamRebaseOptionsTooltip": "Wyświetl opcje rebasowania sprawdzonej gałęzi na {{upstream}}. Uwaga: to nie zrebase'uje wybranej gałęzi na upstream, zrebase'uje sprawdzoną gałąź na upstream.", - "UpstreamGenericName": "upstream wybranej gałęzi", - "SetUpstreamTitle": "Ustaw gałąź upstream", - "EditRemoteTooltip": "Edytuj nazwę lub URL wybranego zdalnego.", - "TagCommit": "Otaguj commit", - "TagCommitTooltip": "Utwórz nowy tag wskazujący na wybrany commit. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu.", - "TagNameTitle": "Nazwa tagu", - "TagMessageTitle": "Opis tagu", - "LightweightTag": "Lekki tag", - "AnnotatedTag": "Tag z adnotacją", - "DeleteTagTitle": "Usuń tag '{{.tagName}}'?", - "DeleteLocalTag": "Usuń lokalny tag", - "DeleteRemoteTag": "Usuń zdalny tag", - "SelectRemoteTagUpstream": "Zdalny, z którego usunąć tag '{{.tagName}}':", - "DeleteRemoteTagPrompt": "Czy na pewno chcesz usunąć zdalny tag '{{.tagName}}' z '{{.upstream}}'?", - "RemoteTagDeletedMessage": "Zdalny tag usunięty", - "PushTagTitle": "Zdalny, do którego wysłać tag '{{.tagName}}':", - "PushTag": "Wyślij tag", - "PushTagTooltip": "Wyślij wybrany tag do zdalnego. Zostaniesz poproszony o wybranie zdalnego.", - "NewTag": "Nowy tag", - "NewTagTooltip": "Utwórz nowy tag z bieżącego commita. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu.", - "CreatingTag": "Tworzenie tagu", - "ForceTag": "Wymuś Tag", - "ForceTagPrompt": "Tag '{{.tagName}}' już istnieje. Naciśnij {{.cancelKey}}, aby anulować, lub {{.confirmKey}}, aby nadpisać.", - "FetchRemoteTooltip": "Pobierz aktualizacje z zdalnego repozytorium. Pobiera nowe commity i gałęzie bez scalania ich z lokalnymi gałęziami.", - "CheckoutCommitTooltip": "Przełącz wybrany commit jako odłączoną HEAD.", - "GitFlowOptions": "Pokaż opcje git-flow", - "NotAGitFlowBranch": "To nie wygląda na gałąź git flow", - "NewBranchNamePrompt": "Wprowadź nową nazwę gałęzi dla gałęzi", - "IgnoreTracked": "Ignoruj śledzony plik", - "ExcludeTracked": "Wyklucz śledzony plik", - "IgnoreTrackedPrompt": "Czy na pewno chcesz zignorować śledzony plik?", - "ExcludeTrackedPrompt": "Czy na pewno chcesz wykluczyć śledzony plik?", - "ViewResetToUpstreamOptions": "Pokaż opcje resetowania do upstream", - "NextScreenMode": "Następny tryb ekranu (normalny/półpełny/pełnoekranowy)", - "PrevScreenMode": "Poprzedni tryb ekranu", - "StartSearch": "Szukaj w bieżącym widoku po tekście", - "StartFilter": "Filtruj bieżący widok po tekście", - "Keybindings": "Skróty klawiszowe", - "KeybindingsLegend": "Legenda: `` oznacza ctrl+b, `` oznacza alt+b, `B` oznacza shift+b", - "KeybindingsMenuSectionLocal": "Lokalne", - "KeybindingsMenuSectionGlobal": "Globalne", - "KeybindingsMenuSectionNavigation": "Nawigacja", - "RenameBranch": "Zmień nazwę gałęzi", - "BranchUpstreamOptionsTitle": "Opcje upstream", - "ViewBranchUpstreamOptions": "Pokaż opcje upstream", - "ViewBranchUpstreamOptionsTooltip": "Pokaż opcje dotyczące upstream gałęzi, np. ustawianie/usuwanie upstream i resetowanie do upstream.", - "UpstreamNotSetError": "Wybrana gałąź nie ma upstream (lub upstream nie jest przechowywany lokalnie)", - "NewGitFlowBranchPrompt": "Nowa nazwa {{.branchType}}:", - "RenameBranchWarning": "Ta gałąź śledzi zdalną. Ta akcja zmieni tylko lokalną nazwę gałęzi, nie nazwę zdalnej gałęzi. Kontynuować?", - "OpenKeybindingsMenu": "Otwórz menu przypisań klawiszy", - "ResetCherryPick": "Resetuj wybrane (cherry-picked) commity", - "NextTab": "Następna zakładka", - "PrevTab": "Poprzednia zakładka", - "CantUndoWhileRebasing": "Nie można cofnąć podczas rebasingu", - "CantRedoWhileRebasing": "Nie można ponowić podczas rebasingu", - "MustStashWarning": "Wyjęcie łatki do indeksu wymaga schowania i odschowania zmian. Jeśli coś pójdzie nie tak, będziesz mógł uzyskać dostęp do plików ze schowka. Kontynuować?", - "MustStashTitle": "Musisz schować", - "ConfirmationTitle": "Panel potwierdzenia", - "PrevPage": "Poprzednia strona", - "NextPage": "Następna strona", - "GotoTop": "Przewiń do góry", - "GotoBottom": "Przewiń do dołu", - "FilteringBy": "Filtrowanie przez", - "ResetInParentheses": "(Resetuj)", - "OpenFilteringMenu": "Pokaż opcje filtrowania", - "OpenFilteringMenuTooltip": "Pokaż opcje filtrowania dziennika commitów, tak aby pokazywane były tylko commity pasujące do filtra.", - "FilterBy": "Filtruj przez", - "ExitFilterMode": "Zatrzymaj filtrowanie", - "FilterPathOption": "Wprowadź ścieżkę do filtrowania", - "FilterAuthorOption": "Wprowadź autora do filtrowania", - "EnterFileName": "Wprowadź ścieżkę:", - "EnterAuthor": "Wprowadź autora:", - "FilteringMenuTitle": "Filtrowanie", - "WillCancelExistingFilterTooltip": "Uwaga: to anuluje istniejący filtr", - "MustExitFilterModeTitle": "Polecenie niedostępne", - "MustExitFilterModePrompt": "Polecenie niedostępne w trybie filtrowania po ścieżce. Wyjść z trybu filtrowania po ścieżce?", - "Diff": "Różnice", - "EnterRefToDiff": "Wprowadź ref do różnic", - "EnterRefName": "Wprowadź ref:", - "ExitDiffMode": "Wyjdź z trybu różnic", - "DiffingMenuTitle": "Różnicowanie", - "SwapDiff": "Odwróć kierunek różnic", - "ViewDiffingOptions": "Pokaż opcje różnicowania", - "ViewDiffingOptionsTooltip": "Pokaż opcje dotyczące różnicowania dwóch refów, np. różnicowanie względem wybranego refa, wprowadzanie refa do różnicowania i odwracanie kierunku różnic.", - "OpenCommandLogMenu": "Pokaż opcje dziennika poleceń", - "OpenCommandLogMenuTooltip": "Pokaż opcje dla dziennika poleceń, np. pokazywanie/ukrywanie dziennika poleceń i skupienie na dzienniku poleceń.", - "ShowingGitDiff": "Pokazuje wynik dla:", - "CommitDiff": "Różnice commita", - "CopyCommitHashToClipboard": "Kopiuj hash commita do schowka", - "CommitHash": "hash commita", - "CommitURL": "URL commita", - "CommitMessage": "Wiadomość commita", - "CommitSubject": "Temat commita", - "CommitAuthor": "Autor commita", - "CopyCommitAttributeToClipboard": "Kopiuj atrybut commita do schowka", - "CopyCommitAttributeToClipboardTooltip": "Kopiuj atrybut commita do schowka (np. hash, URL, różnice, wiadomość, autor).", - "CopyBranchNameToClipboard": "Kopiuj nazwę gałęzi do schowka", - "CopyPathToClipboard": "Kopiuj ścieżkę do schowka", - "CommitPrefixPatternError": "Błąd w wzorcu commitPrefix", - "CopySelectedTextToClipboard": "Kopiuj zaznaczony tekst do schowka", - "NoFilesStagedTitle": "Brak plików przygotowanych", - "NoFilesStagedPrompt": "Nie przygotowałeś żadnych plików. Zatwierdzić wszystkie pliki?", - "BranchNotFoundTitle": "Gałąź nie znaleziona", - "BranchNotFoundPrompt": "Gałąź nie znaleziona. Utwórz nową gałąź o nazwie", - "BranchUnknown": "Gałąź nieznana", - "DiscardChangeTitle": "Odrzuć zmianę", - "DiscardChangePrompt": "Czy na pewno chcesz odrzucić tę zmianę (git reset)? Jest to nieodwracalne.\nAby wyłączyć to okno dialogowe, ustaw klucz konfiguracyjny 'gui.skipDiscardChangeWarning' na true", - "CreateNewBranchFromCommit": "Utwórz nową gałąź z commita", - "BuildingPatch": "Tworzenie łatki", - "ViewCommits": "Pokaż commity", - "RunningCustomCommandStatus": "Uruchamianie niestandardowego polecenia", - "SubmoduleStashAndReset": "Schowaj niezatwierdzone zmiany submodułu i zaktualizuj", - "AndResetSubmodules": "I zresetuj submoduły", - "EnterSubmoduleTooltip": "Wejdź do submodułu. Po wejściu do submodułu możesz nacisnąć `{{.escape}}`, aby wrócić do repozytorium nadrzędnego.", - "Enter": "Wejdź", - "CopySubmoduleNameToClipboard": "Kopiuj nazwę submodułu do schowka", - "RemoveSubmodule": "Usuń submoduł", - "RemoveSubmoduleTooltip": "Usuń wybrany submoduł i odpowiadający mu katalog.", - "RemoveSubmodulePrompt": "Czy na pewno chcesz usunąć submoduł '%s' i odpowiadający mu katalog? Jest to nieodwracalne.", - "ResettingSubmoduleStatus": "Resetowanie submodułu", - "NewSubmoduleName": "Nowa nazwa submodułu:", - "NewSubmoduleUrl": "Nowy URL submodułu:", - "NewSubmodulePath": "Nowa ścieżka submodułu:", - "NewSubmodule": "Nowy submoduł", - "AddingSubmoduleStatus": "Dodawanie submodułu", - "UpdateSubmoduleUrl": "Zaktualizuj URL dla submodułu '%s'", - "UpdatingSubmoduleUrlStatus": "Aktualizowanie URL", - "EditSubmoduleUrl": "Zaktualizuj URL submodułu", - "InitializingSubmoduleStatus": "Inicjalizowanie submodułu", - "InitSubmoduleTooltip": "Zainicjuj wybrany submoduł, aby przygotować do pobrania. Prawdopodobnie chcesz to kontynuować, wywołując akcję 'update', aby pobrać submoduł.", - "Update": "Aktualizuj", - "Initialize": "Zainicjuj", - "SubmoduleUpdateTooltip": "Aktualizuj wybrany submoduł.", - "UpdatingSubmoduleStatus": "Aktualizowanie submodułu", - "BulkInitSubmodules": "Masowe inicjowanie submodułów", - "BulkUpdateSubmodules": "Masowa aktualizacja submodułów", - "BulkDeinitSubmodules": "Masowe wyłączanie submodułów", - "ViewBulkSubmoduleOptions": "Pokaż opcje masowych operacji na submodułach", - "BulkSubmoduleOptions": "Opcje masowych operacji na submodułach", - "RunningCommand": "Uruchamianie polecenia", - "SubCommitsTitle": "Sub-commity", - "SubmodulesTitle": "Submoduły", - "NavigationTitle": "Nawigacja panelu listy", - "SuggestionsCheatsheetTitle": "Podpowiedzi", - "SuggestionsTitle": "Podpowiedzi (naciśnij %s, aby skupić)", - "ExtrasTitle": "Dziennik poleceń", - "PullRequestURLCopiedToClipboard": "URL żądania ściągnięcia skopiowany do schowka", - "CommitDiffCopiedToClipboard": "Różnice commita skopiowane do schowka", - "CommitURLCopiedToClipboard": "URL commita skopiowany do schowka", - "CommitMessageCopiedToClipboard": "Wiadomość commita skopiowana do schowka", - "CommitSubjectCopiedToClipboard": "Temat commita skopiowany do schowka", - "CommitAuthorCopiedToClipboard": "Autor commita skopiowany do schowka", - "PatchCopiedToClipboard": "Łatka skopiowana do schowka", - "CopiedToClipboard": "skopiowane do schowka", - "ErrCannotEditDirectory": "Nie można edytować katalogu: można edytować tylko pojedyncze pliki", - "ErrStageDirWithInlineMergeConflicts": "Nie można przygotować/odprzygotować katalogu zawierającego pliki z konfliktami scalania w linii. Proszę najpierw rozwiązać konflikty scalania", - "ErrRepositoryMovedOrDeleted": "Nie można znaleźć repozytorium. Mogło zostać przeniesione lub usunięte ¯\\_(ツ)_/¯", - "ErrWorktreeMovedOrRemoved": "Nie można znaleźć drzewa roboczego. Mogło zostać przeniesione lub usunięte ¯\\_(ツ)_/¯", - "CommandLog": "Dziennik poleceń", - "ToggleShowCommandLog": "Przełącz pokazywanie/ukrywanie dziennika poleceń", - "FocusCommandLog": "Skup na dzienniku poleceń", - "CommandLogHeader": "Możesz ukryć/skupić się na tym panelu naciskając '%s'\n", - "RandomTip": "Losowa porada", - "ToggleWhitespaceInDiffView": "Przełącz białe znaki", - "IgnoreWhitespaceDiffViewSubTitle": "(ignorując białe znaki)", - "IgnoreWhitespaceNotSupportedHere": "Ignorowanie białych znaków nie jest wspierane w tym widoku", - "IncreaseContextInDiffView": "Zwiększ rozmiar kontekstu w widoku różnic", - "DecreaseContextInDiffView": "Zmniejsz rozmiar kontekstu w widoku różnic", - "DiffContextSizeChanged": "Zmieniono rozmiar kontekstu różnic na %d", - "CreatePullRequestOptions": "Zobacz opcje tworzenia pull requesta", - "DefaultBranch": "Domyślny branch", - "SelectBranch": "Wybierz branch", - "CreatePullRequest": "Utwórz żądanie ściągnięcia", - "SelectConfigFile": "Wybierz plik konfiguracyjny", - "NoConfigFileFoundErr": "Nie znaleziono pliku konfiguracyjnego", - "LoadingFileSuggestions": "Ładowanie sugestii plików", - "LoadingCommits": "Ładowanie commitów", - "MustSpecifyOriginError": "Musisz określić zdalne repozytorium jeśli określasz branch", - "GitOutput": "Wyjście Gita:", - "GitCommandFailed": "Polecenie Gita nie powiodło się. Sprawdź logi poleceń po szczegóły (otwórz za pomocą %s)", - "AbortTitle": "Przerwij %s", - "AbortPrompt": "Czy na pewno chcesz przerwać bieżące %s?", - "OpenLogMenu": "Zobacz opcje logów", - "OpenLogMenuTooltip": "Zobacz opcje dla logów commitów, np. zmiana kolejności sortowania, ukrywanie grafu gita, pokazywanie całego grafu gita.", - "LogMenuTitle": "Opcje logów commitów", - "ToggleShowGitGraphAll": "Przełącz pokazanie całego grafu gita (dodaj flagę `--all` do `git log`)", - "ShowGitGraph": "Pokaż graf gita", - "SortOrder": "Kolejność sortowania", - "SortAlphabetical": "Alfabetycznie", - "SortByDate": "Data", - "SortByRecency": "Najnowsze", - "SortBasedOnReflog": "(na podstawie reflog)", - "SortCommits": "Kolejność sortowania commitów", - "CantChangeContextSizeError": "Nie można zmienić rozmiaru kontekstu będąc w trybie budowania patcha, ponieważ byliśmy zbyt leniwi, aby to wspierać przy wydaniu funkcji. Jeśli naprawdę tego chcesz, daj nam znać!", - "OpenCommitInBrowser": "Otwórz commit w przeglądarce", - "ViewBisectOptions": "Zobacz opcje bisect", - "ConfirmRevertCommit": "Czy na pewno chcesz cofnąć {{.selectedCommit}}?", - "RewordInEditorTitle": "Przeformułuj w edytorze", - "RewordInEditorPrompt": "Czy na pewno chcesz przeformułować ten commit w swoim edytorze?", - "HardResetAutostashPrompt": "Czy na pewno chcesz zrobić twardy reset do '%s'? Auto-stash zostanie wykonany jeśli będzie potrzebny.", - "UpstreamGone": "(upstream zniknął)", - "NukeDescription": "Jeśli chcesz, aby wszystkie zmiany w drzewie pracy zniknęły, to jest sposób na to. Jeśli są brudne zmiany w submodule, to zostaną one zapisane w submodule(s).", - "DiscardStagedChangesDescription": "To stworzy nowy wpis stash zawierający tylko pliki w stanie staged, a następnie go usunie, tak że drzewo pracy zostanie tylko ze zmianami niezatwierdzonymi", - "EmptyOutput": "", - "CustomPatch": "Niestandardowy patch", - "CommitsCopied": "commitów skopiowanych", - "CommitCopied": "commit skopiowany", - "ResetPatch": "Resetuj patch", - "ResetPatchTooltip": "Wyczyść bieżący patch.", - "ApplyPatch": "Zastosuj patch", - "ApplyPatchTooltip": "Zastosuj bieżący patch do drzewa pracy.", - "ApplyPatchInReverse": "Zastosuj patch w odwrotności", - "ApplyPatchInReverseTooltip": "Zastosuj bieżący patch w odwrotności do drzewa pracy.", - "RemovePatchFromOriginalCommit": "Usuń patch z oryginalnego commita (%s)", - "RemovePatchFromOriginalCommitTooltip": "Usuń bieżący patch z jego commita. Jest to osiągane przez rozpoczęcie interaktywnego rebase na commicie, zastosowanie patcha w odwrotności, a następnie kontynuowanie rebase. Jeśli późniejsze commity zależą od patcha, możesz musieć rozwiązać konflikty.", - "MovePatchOutIntoIndex": "Przenieś patch do indeksu", - "MovePatchOutIntoIndexTooltip": "Przenieś patch z jego commita do indeksu. Jest to osiągane przez rozpoczęcie interaktywnego rebase na commicie, zastosowanie patcha w odwrotności, kontynuowanie rebase do zakończenia, a następnie zastosowanie patcha do indeksu. Jeśli późniejsze commity zależą od patcha, możesz musieć rozwiązać konflikty.", - "MovePatchIntoNewCommitTooltip": "Przenieś patch z jego commita do nowego commita na górze oryginalnego commita. Jest to osiągane przez rozpoczęcie interaktywnego rebase na oryginalnym commicie, zastosowanie patcha w odwrotności, następnie zastosowanie patcha do indeksu i zatwierdzenie go jako nowy commit, przed kontynuowaniem rebase do zakończenia. Jeśli późniejsze commity zależą od patcha, możesz musieć rozwiązać konflikty.", - "MovePatchToSelectedCommit": "Przenieś patch do wybranego commita (%s)", - "MovePatchToSelectedCommitTooltip": "Przenieś patch z jego oryginalnego commita do wybranego commita. Jest to osiągane przez rozpoczęcie interaktywnego rebase na oryginalnym commicie, zastosowanie patcha w odwrotności, następnie kontynuowanie rebase do wybranego commita, przed zastosowaniem patcha do przodu i zmodyfikowaniem wybranego commita. Rebase jest następnie kontynuowany do zakończenia. Jeśli commity między źródłem a miejscem docelowym zależą od patcha, możesz musieć rozwiązać konflikty.", - "CopyPatchToClipboard": "Kopiuj patch do schowka", - "NoMatchesFor": "Brak dopasowań dla '%s' %s", - "MatchesFor": "dopasowania dla '%s' (%d z %d) %s", - "SearchKeybindings": "%s: Następne dopasowanie, %s: Poprzednie dopasowanie, %s: Wyjdź z trybu wyszukiwania", - "SearchPrefix": "Szukaj: ", - "FilterPrefix": "Filtruj: ", - "ExitSearchMode": "%s: Wyjdź z trybu wyszukiwania", - "ExitTextFilterMode": "%s: Wyjdź z trybu filtrowania", - "Switch": "Przełącz", - "SwitchToWorktree": "Przełącz do drzewa pracy", - "SwitchToWorktreeTooltip": "Przełącz do wybranego drzewa pracy.", - "AlreadyCheckedOutByWorktree": "Ten branch jest już używany przez drzewo pracy {{.worktreeName}}. Czy chcesz przełączyć się do tego drzewa pracy?", - "BranchCheckedOutByWorktree": "Branch {{.branchName}} jest używany przez drzewo pracy {{.worktreeName}}", - "DetachWorktreeTooltip": "To uruchomi `git checkout --detach` na drzewie pracy, tak że przestanie ono używać brancha, ale drzewo pracy drzewa pracy zostanie nietknięte.", - "Switching": "Przełączanie", - "RemoveWorktree": "Usuń drzewo pracy", - "RemoveWorktreeTitle": "Usuń drzewo pracy", - "DetachWorktree": "Odłącz drzewo pracy", - "DetachingWorktree": "Odłączanie drzewa pracy", - "WorktreesTitle": "Drzewa pracy", - "WorktreeTitle": "Drzewo pracy", - "RemoveWorktreePrompt": "Czy na pewno chcesz usunąć drzewo pracy '{{.worktreeName}}'?", - "ForceRemoveWorktreePrompt": "'{{.worktreeName}}' zawiera zmodyfikowane lub nieśledzone pliki (szczerze mówiąc, może zawierać oba). Czy na pewno chcesz to usunąć?", - "RemovingWorktree": "Usuwanie drzewa pracy", - "AddingWorktree": "Dodawanie drzewa pracy", - "CantDeleteCurrentWorktree": "Nie możesz usunąć bieżącego drzewa pracy!", - "AlreadyInWorktree": "Jesteś już w wybranym drzewie pracy", - "CantDeleteMainWorktree": "Nie możesz usunąć głównego drzewa pracy!", - "NoWorktreesThisRepo": "Brak drzew pracy", - "MissingWorktree": "(brakujące)", - "MainWorktree": "(główne)", - "NewWorktree": "Nowe drzewo pracy", - "NewWorktreePath": "Nowa ścieżka drzewa pracy", - "NewWorktreeBase": "Nowa bazowa ref drzewa pracy", - "RemoveWorktreeTooltip": "Usuń wybrane drzewo pracy. To usunie zarówno katalog drzewa pracy, jak i metadane o drzewie pracy w katalogu .git.", - "BranchNameCannotBeBlank": "Nazwa brancha nie może być pusta", - "NewBranchName": "Nowa nazwa brancha", - "NewBranchNameLeaveBlank": "Nowa nazwa brancha (pozostaw puste, aby przełączyć {{.default}})", - "ViewWorktreeOptions": "Zobacz opcje drzewa pracy", - "CreateWorktreeFrom": "Utwórz drzewo pracy z {{.ref}}", - "CreateWorktreeFromDetached": "Utwórz drzewo pracy z {{.ref}} (odłączone)", - "LcWorktree": "drzewo pracy", - "ChangingDirectoryTo": "Zmiana katalogu na {{.path}}", - "Name": "Nazwa", - "Path": "Ścieżka", - "MarkedBaseCommitStatus": "Oznaczono bazowy commit dla rebase", - "MarkAsBaseCommit": "Oznacz jako bazowy commit dla rebase", - "MarkAsBaseCommitTooltip": "Wybierz bazowy commit dla następnego rebase. Kiedy robisz rebase na branch, tylko commity powyżej bazowego commita zostaną przeniesione. Używa to polecenia `git rebase --onto`.", - "MarkedCommitMarker": "↑↑↑ Rebase rozpocznie się stąd ↑↑↑", - "NoCopiedCommits": "Brak skopiowanych commitów", - "DisabledMenuItemPrefix": "Wyłączone: ", - "QuickStartInteractiveRebase": "Rozpocznij interaktywny rebase", - "QuickStartInteractiveRebaseTooltip": "Rozpocznij interaktywny rebase dla commitów na twoim branchu. To będzie zawierać wszystkie commity od HEAD do pierwszego commita scalenia lub commita głównego brancha.\nJeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita, naciśnij `{{.editKey}}`.", - "CannotQuickStartInteractiveRebase": "Nie można rozpocząć interaktywnego rebase: commit HEAD jest commit'em scalenia lub jest obecny na głównym branchu, więc nie ma odpowiedniego bazowego commita, od którego można by zacząć rebase. Możesz rozpocząć interaktywny rebase z konkretnego commita, wybierając commit i naciskając `{{.editKey}}`.", - "ToggleRangeSelect": "Przełącz zaznaczenie zakresu", - "RangeSelectUp": "Zaznacz zakres w górę", - "RangeSelectDown": "Zaznacz zakres w dół", - "RangeSelectNotSupported": "Akcja nie wspiera zaznaczania zakresu, proszę wybrać pojedynczy element", - "NoItemSelected": "Nie wybrano elementu", - "SelectedItemIsNotABranch": "Wybrany element nie jest branch'em", - "SelectedItemDoesNotHaveFiles": "Wybrany element nie ma plików do wyświetlenia", - "OldCherryPickKeyWarning": "Klawisz 'c' nie jest już domyślnym klawiszem do kopiowania commitów do cherry pick. Proszę użyj `{{.copy}}` zamiast tego (i `{{.paste}}` aby wkleić). Powodem tej zmiany jest to, że klawisz 'v' do wybierania zakresu linii podczas stagingu jest teraz używany również do wybierania zakresu linii w każdym widoku listy, co oznacza, że musieliśmy znaleźć nowy klawisz do wklejania commitów, i jeśli zamierzamy teraz używać `{{.paste}}` do wklejania commitów, możemy równie dobrze użyć `{{.copy}}` do ich kopiowania. Jeśli chcesz skonfigurować klawisze, aby uzyskać stare zachowanie, ustaw następujące w swojej konfiguracji:\n\nkeybinding:\n universal:\n toggleRangeSelect: \n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'", - "Actions": { - "CheckoutCommit": "Przełącz commit", - "CheckoutTag": "Przełącz tag", - "CheckoutBranch": "Przełącz gałąź", - "ForceCheckoutBranch": "Wymuś przełączenie gałęzi", - "DeleteLocalBranch": "Usuń lokalną gałąź", - "Merge": "Scal", - "RebaseBranch": "Rebazuj gałąź", - "RenameBranch": "Zmień nazwę gałęzi", - "CreateBranch": "Utwórz gałąź", - "FastForwardBranch": "Szybkie przewijanie gałęzi", - "CherryPick": "(Cherry-pick) wklej commity", - "CheckoutFile": "Przełącz plik", - "SquashCommitDown": "Scal commit w dół", - "FixupCommit": "Popraw commit", - "RewordCommit": "Zmień treść commita", - "DropCommit": "Odrzuć commit", - "EditCommit": "Edytuj commit", - "AmendCommit": "Popraw commit", - "ResetCommitAuthor": "Zresetuj autora commita", - "SetCommitAuthor": "Ustaw autora commita", - "RevertCommit": "Cofnij commit", - "CreateFixupCommit": "Utwórz commit poprawkowy", - "SquashAllAboveFixupCommits": "Scal wszystkie powyższe commity poprawkowe", - "MoveCommitUp": "Przenieś commit w górę", - "MoveCommitDown": "Przenieś commit w dół", - "CopyCommitMessageToClipboard": "Kopiuj wiadomość commita do schowka", - "CopyCommitSubjectToClipboard": "Kopiuj temat commita do schowka", - "CopyCommitDiffToClipboard": "Kopiuj różnice commita do schowka", - "CopyCommitHashToClipboard": "Kopiuj hash commita do schowka", - "CopyCommitURLToClipboard": "Kopiuj URL commita do schowka", - "CopyCommitAuthorToClipboard": "Kopiuj autora commita do schowka", - "CopyCommitAttributeToClipboard": "Kopiuj do schowka", - "CopyPatchToClipboard": "Kopiuj łatkę do schowka", - "CustomCommand": "Polecenie niestandardowe", - "DiscardAllChangesInFile": "Odrzuć wszystkie zmiany w wybranych plikach", - "DiscardAllUnstagedChangesInFile": "Odrzuć wszystkie niezatwierdzone zmiany w wybranych plikach", - "StageFile": "Dodaj plik do indeksu", - "StageResolvedFiles": "Dodaj pliki, których konflikty scalania zostały rozwiązane", - "UnstageFile": "Usuń plik z indeksu", - "UnstageAllFiles": "Usuń wszystkie pliki z indeksu", - "StageAllFiles": "Dodaj wszystkie pliki do indeksu", - "IgnoreExcludeFile": "Ignoruj lub wyklucz plik", - "IgnoreFileErr": "Nie można zignorować .gitignore", - "ExcludeFile": "Wyklucz plik", - "ExcludeGitIgnoreErr": "Nie można wykluczyć .gitignore", - "Commit": "Commituj", - "Push": "Wypchnij", - "Pull": "Pociągnij", - "OpenFile": "Otwórz plik", - "StashAllChanges": "Schowaj wszystkie zmiany", - "StashAllChangesKeepIndex": "Schowaj wszystkie zmiany i zachowaj indeks", - "StashStagedChanges": "Schowaj zatwierdzone zmiany", - "StashUnstagedChanges": "Schowaj niezatwierdzone zmiany", - "StashIncludeUntrackedChanges": "Schowaj wszystkie zmiany włącznie z nieśledzonymi plikami", - "GitFlowFinish": "git flow zakończ", - "GitFlowStart": "git flow rozpocznij", - "CopyToClipboard": "Kopiuj do schowka", - "CopySelectedTextToClipboard": "Kopiuj zaznaczony tekst do schowka", - "RemovePatchFromCommit": "Usuń łatkę z commita", - "MovePatchToSelectedCommit": "Przenieś łatkę do wybranego commita", - "MovePatchIntoIndex": "Przenieś łatkę do indeksu", - "MovePatchIntoNewCommit": "Przenieś łatkę do nowego commita", - "DeleteRemoteBranch": "Usuń zdalną gałąź", - "SetBranchUpstream": "Ustaw gałąź nadrzędną", - "AddRemote": "Dodaj zdalne", - "RemoveRemote": "Usuń zdalne", - "UpdateRemote": "Aktualizuj zdalne", - "ApplyPatch": "Zastosuj łatkę", - "Stash": "Schowaj", - "RenameStash": "Zmień nazwę schowka", - "RemoveSubmodule": "Usuń podmoduł", - "ResetSubmodule": "Resetuj podmoduł", - "AddSubmodule": "Dodaj podmoduł", - "UpdateSubmoduleUrl": "Aktualizuj URL podmodułu", - "InitialiseSubmodule": "Zainicjuj podmoduł", - "BulkInitialiseSubmodules": "Masowo zainicjuj podmoduły", - "BulkUpdateSubmodules": "Masowo aktualizuj podmoduły", - "BulkDeinitialiseSubmodules": "Masowo deinicjuj podmoduły", - "UpdateSubmodule": "Aktualizuj podmoduł", - "CreateLightweightTag": "Utwórz lekki tag", - "CreateAnnotatedTag": "Utwórz opisowy tag", - "DeleteLocalTag": "Usuń lokalny tag", - "DeleteRemoteTag": "Usuń zdalny tag", - "PushTag": "Wypchnij tag", - "NukeWorkingTree": "Zniszcz drzewo robocze", - "DiscardUnstagedFileChanges": "Odrzuć niezatwierdzone zmiany w pliku", - "RemoveUntrackedFiles": "Usuń nieśledzone pliki", - "RemoveStagedFiles": "Usuń zatwierdzone pliki", - "SoftReset": "Miękki reset", - "MixedReset": "Mieszany reset", - "HardReset": "Twardy reset", - "Undo": "Cofnij", - "Redo": "Ponów", - "CopyPullRequestURL": "Kopiuj URL żądania ściągnięcia", - "OpenMergeTool": "Otwórz narzędzie scalania", - "OpenCommitInBrowser": "Otwórz commit w przeglądarce", - "OpenPullRequest": "Otwórz żądanie ściągnięcia w przeglądarce", - "StartBisect": "Rozpocznij bisect", - "ResetBisect": "Resetuj bisect", - "BisectSkip": "Pomiń bisect", - "BisectMark": "Oznacz bisect", - "AddWorktree": "Dodaj drzewo robocze" - }, - "Bisect": { - "MarkStart": "Oznacz %s jako %s (rozpocznij bisect)", - "ResetTitle": "Resetuj 'git bisect'", - "ResetPrompt": "Czy na pewno chcesz zresetować 'git bisect'?", - "ResetOption": "Resetuj bisect", - "ChooseTerms": "Wybierz terminy bisect", - "OldTermPrompt": "Termin dla starego/dobrego commita:", - "NewTermPrompt": "Termin dla nowego/złego commita:", - "Mark": "Oznacz bieżący commit (%s) jako %s", - "SkipCurrent": "Pomiń bieżący commit (%s)", - "SkipSelected": "Pomiń wybrany commit (%s)", - "CompleteTitle": "Bisect zakończony", - "CompletePrompt": "Bisect zakończony! Następujący commit wprowadził zmianę:\n\n%s\n\nCzy chcesz teraz zresetować 'git bisect'?", - "CompletePromptIndeterminate": "Bisect zakończony! Niektóre commity zostały pominięte, więc którykolwiek z następujących commitów mógł wprowadzić zmianę:\n\n%s\n\nCzy chcesz teraz zresetować 'git bisect'?", - "Bisecting": "Bisectowanie" - }, - "Log": { - "EditRebase": "Rozpoczynanie interaktywnego rebazowania od '{{.ref}}'", - "HandleUndo": "Cofanie ostatniego rozwiązania konfliktu", - "RemoveFile": "Usuwanie ścieżki '{{.path}}'", - "CopyToClipboard": "Kopiowanie '{{.str}}' do schowka", - "Remove": "Usuwanie '{{.filename}}'", - "CreateFileWithContent": "Tworzenie pliku '{{.path}}'", - "AppendingLineToFile": "Dodawanie '{{.line}}' do pliku '{{.filename}}'", - "EditRebaseFromBaseCommit": "Rozpoczynanie interaktywnego rebazowania od '{{.baseCommit}}' na '{{.targetBranchName}}" - }, - "BreakingChangesTitle": "Zmiany przełomowe", - "BreakingChangesMessage": "Aktualizujesz do nowej wersji lazygit, która zawiera zmiany przełomowe. Proszę przejrzeć poniższe notatki i zaktualizować swoją konfigurację, jeśli jest to konieczne.\nAby uzyskać więcej informacji, zobacz pełne notatki do wydania na .", - "BreakingChangesByVersion": { - "0.41.0": "- Gdy naciśniesz 'g', aby wywołać menu resetu git, opcja 'mixed' jest teraz pierwsza i domyślna, a nie 'soft'. Jest to dlatego, że 'mixed' jest najczęściej używaną opcją.\n- Panel wiadomości commita teraz domyślnie zawija tekst (tj. dodaje znaki nowej linii, gdy osiągniesz margines). Możesz dostosować konfigurację w następujący sposób:\n\ngit:\n commit:\n autoWrapCommitMessage: true\n autoWrapWidth: 72\n\n- Klawisz 'v' był już używany w widoku staging do rozpoczęcia zaznaczania zakresu, ale teraz możesz go użyć do rozpoczęcia zaznaczania zakresu w dowolnym widoku. Niestety koliduje to z klawiszem 'v' dla wklejania commitów (cherry-pick), więc teraz wklejanie commitów odbywa się za pomocą 'shift+V', a dla spójności kopiowanie commitów odbywa się teraz za pomocą 'shift+C' zamiast 'c'. Zauważ, że klawisz 'v' to tylko jeden ze sposobów na rozpoczęcie zaznaczania zakresu: możesz zamiast tego użyć shift+góra/dół. Więc jeśli chcesz skonfigurować klawisze cherry-pick, aby uzyskać stare zachowanie, ustaw następujące w swojej konfiguracji:\n\nkeybinding:\n universal:\n toggleRangeSelect: \n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'\n\n- Sciskanie fixupów za pomocą 'shift-S' teraz wywołuje menu, z domyślną opcją sciskania wszystkich commitów fixup w gałęzi. Oryginalne zachowanie sciskania tylko commitów fixup powyżej wybranego commita jest nadal dostępne jako druga opcja w tym menu.\n- Statusy ładowania push/pull/fetch są teraz wyświetlane przy gałęzi, a nie w popupie. Pozwala to np. na równoczesne fetchowanie wielu gałęzi i widzenie statusu dla każdej gałęzi.\n- Graf logu git w widoku commitów jest teraz zawsze wyświetlany domyślnie (wcześniej był wyświetlany tylko, gdy widok był maksymalizowany). Jeśli uznasz to za zbyt hałaśliwe, możesz to zmienić za pomocą ctrl+L -> 'Pokaż graf git' -> 'gdy maksymalizowany'\n\t " - } -} diff --git a/pkg/i18n/translations/pt.json b/pkg/i18n/translations/pt.json deleted file mode 100644 index 413299d3b18..00000000000 --- a/pkg/i18n/translations/pt.json +++ /dev/null @@ -1,495 +0,0 @@ -{ - "NotEnoughSpace": "Espaço insuficiente para renderizar painéis", - "DiffTitle": "Diff", - "FilesTitle": "Arquivos", - "BranchesTitle": "Branches", - "CommitsTitle": "Commits", - "StashTitle": "Stash", - "SnakeTitle": "Snake", - "EasterEgg": "Easter Egg", - "UnstagedChanges": "Alterações não preparadas", - "StagedChanges": "Alterações preparadas", - "StagingTitle": "Painel Principal (preparação)", - "MergingTitle": "Painel principal (mesclagem)", - "SquashMergeUncommittedTitle": "Mesclar Squash e sair sem commit", - "SquashMergeCommittedTitle": "Mesclar Squash e commit", - "SquashMergeUncommitted": "Mesclar Squash '{{.selectedBranch}}' na árvore de trabalho", - "SquashMergeCommitted": "Mesclar Squash '{{.selectedBranch}}' em '{{.checkedOutBranch}}' como um único commit.", - "RegularMergeTooltip": "Mesclar '{{.selectedBranch}}' em '{{.checkedOutBranch}}'.", - "NormalTitle": "Painel Principal (Normal)", - "LogTitle": "Log", - "CommitSummary": "Sumário do commit", - "CredentialsUsername": "Nome de usuário", - "CredentialsPassword": "Senha", - "CredentialsPassphrase": "Digite a senha para a chave SSH", - "CredentialsPIN": "Digite o PIN para a chave SSH", - "CredentialsToken": "Digite o Token para chave SSH", - "PassUnameWrong": "Senha, palavra-chave e/ou nome de usuário incorreto", - "Commit": "Commit", - "CommitTooltip": "Submeter mudanças em staging", - "AmendLastCommit": "Alterar último commit", - "AmendLastCommitTitle": "Alterar último commit", - "SureToAmend": "Está certo de querer alterar o último commit? Posteriormente, pode alterar a mensagem do commit do painel de commits", - "NoCommitToAmend": "Não há commit para alterar.", - "CommitChangesWithEditor": "Enviar alteração usando um editor Git", - "FindBaseCommitForFixup": "Encontrar commit da base para consertar", - "FindBaseCommitForFixupTooltip": "Encontre o commit em que as suas mudanças atuais estão se baseando, para alterar/consertar o commit. Isso poupa-te você de ter que olhar pelos commits da sua branch um por um para ver qual commit deve ser alterado/consertado\nVeja a documentação:\n", - "NoBaseCommitsFound": "Nenhum commit base encontrado", - "MultipleBaseCommitsFoundStaged": "Múltiplos commits da base encontrados.", - "MultipleBaseCommitsFoundUnstaged": "Múltiplos commits da base encontrados. (Tente preparar alguma dessas mudanças)", - "BaseCommitIsAlreadyOnMainBranch": "O commit da base para está mudança já está na branch main", - "BaseCommitIsNotInCurrentView": "O commit da base não está na visão atual", - "HunksWithOnlyAddedLinesWarning": "Existem intervalos apenas de linhas adicionadas no diff; tenha cuidado para verificar se elas pertencem ao commit base encontrado.\n\nProceder?", - "StatusTitle": "Status", - "GlobalTitle": "Combinações globais de teclas", - "Execute": "Executar", - "Stage": "Etapa", - "StageTooltip": "Alternar para staging para o arquivo selecionado.", - "ToggleStagedAll": "Stage completo", - "ToggleStagedAllTooltip": "Alternar para todos os arquivos na árvore de trabalho", - "ToggleTreeView": "Alternar exibição de árvore de arquivo", - "OpenDiffTool": "Abrir ferramenta de diff externa (git difftool)", - "OpenMergeTool": "Abrir ferramenta de merge externa", - "OpenMergeToolTooltip": "Execute `git mergetool`.", - "Refresh": "Atualizar", - "RefreshTooltip": "Atualize o estado do git (ou seja, execute `git status`, `git branch`, etc em segundo plano para atualizar o conteúdo de painéis). Isso não executa `git fetch`.", - "Push": "Empurre (Push)", - "Pull": "Puxar (Pull)", - "PushTooltip": "Faça push do branch atual para o seu branch upstream. Se nenhum upstream estiver configurado, você será solicitado a configurar um branch a montante.", - "PullTooltip": "Puxe alterações do controle remoto para o ramo atual. Se nenhum upstream estiver configurado, será solicitado configurar um ramo a montante.", - "FileFilter": "Filtrar arquivos por status", - "CopyFileName": "Nome do arquivo", - "CopyRelativeFilePath": "Caminho relativo", - "CopyAbsoluteFilePath": "Caminho absoluto", - "CopyFileDiffTooltip": "Se existirem itens preparados, este comando considera apenas eles", - "CopySelectedDiff": "Diferença do arquivo selecionado", - "CopyAllFilesDiff": "Diferença de todos os arquivos", - "CopyFileContent": "Conteúdo do arquivo selecionado", - "NoContentToCopyError": "Nada para copiar", - "FileNameCopiedToast": "No do arquivo copiado para a área de transferência", - "FilePathCopiedToast": "Caminho do arquivo copiado para a área de transferência", - "FileDiffCopiedToast": "Diferença do arquivo copiado para a área de transferência ", - "AllFilesDiffCopiedToast": "Todos os arquivos diferentes foram copiados para a área de transferência", - "FileContentCopiedToast": "Conteúdo do arquivo copiado para a área de transferência", - "FilterStagedFiles": "Mostrar somente os arquivos em staging", - "FilterUnstagedFiles": "Mostrar somente arquivos que não estão em staging", - "FilterTrackedFiles": "Mostrar apenas arquivos rastreados", - "FilterUntrackedFiles": "Mostrar apenas arquivos não rastreados", - "NoFilter": "Sem filtros", - "FilterLabelStagedFiles": "(somente preparado)", - "FilterLabelUnstagedFiles": "(apenas unstaged)", - "FilterLabelTrackedFiles": "(somente rastreado)", - "FilterLabelUntrackedFiles": "(somente não acompanhado)", - "FilterLabelConflictingFiles": "(apenas conflitando)", - "MergeConflictsTitle": "Mesclar conflitos", - "MergeConflictIncomingDiff": "Alterações recebidas:", - "MergeConflictCurrentDiff": "Alterações atuais:", - "MergeConflictPressEnterToResolve": "Pressione %s para resolver.", - "MergeConflictKeepFile": "Manter arquivo", - "MergeConflictDeleteFile": "Excluir arquivo", - "Checkout": "Verificar", - "CheckoutTooltip": "Checar item selecionado", - "CantCheckoutBranchWhilePulling": "Você não pode hecar outra branch enquanto puxa a branch atual", - "TagCheckoutTooltip": "Checar a tag selecionada como um HEAD, desanexado", - "RemoteBranchCheckoutTooltip": "Checar a nova branch baseada na brach remota selecionada, ou a branch remota como HEAD, desanexado", - "CantPullOrPushSameBranchTwice": "Você não pode empurar ou puxar uma branch enquanto ele já está a ser puxado ou empurrado", - "NoChangedFiles": "Arquivos não alterados", - "SoftReset": "Reiniciar suave", - "AlreadyCheckedOutBranch": "Você já tem uma checagem dessa branch", - "SureForceCheckout": "Você está certo de que quer forçar uma checagem? Você perdera todos as mudanças locais", - "ForceCheckoutBranch": "Forçar checagem de branch", - "BranchName": "Nome da Branch", - "NewBranchNameBranchOff": "Novo nome da branch (branch está fora de '{{.branchName}}')", - "CantDeleteCheckOutBranch": "Você não pode excluir a branch checada", - "DeleteBranchTitle": "Deletar branch '{{.selectedBranchName}}'?", - "DeleteBranchesTitle": "Excluir as branches selecionadas?", - "DeleteLocalBranch": "Deletar branch local", - "DeleteLocalBranches": "Deletar branches locais", - "DeleteRemoteBranchPrompt": "Você está certo de que quer deletar a branch remota '{{.selectedBranchName}}' de '{{.upstream}}'?", - "DeleteRemoteBranchesPrompt": "Tem certeza de que deseja excluir as ramificações remotas selecionadas dos seus respectivos remotos?", - "DeleteLocalAndRemoteBranchPrompt": "Tem certeza que quer excluir ambos '{{.localBranchName}}' da sua máquina, e '{{.remoteBranchName}}' de '{{.remoteName}}'?", - "DeleteLocalAndRemoteBranchesPrompt": "Tem certeza de que deseja excluir as ramificações selecionadas da sua máquina, e suas ramificações remotas de seus respectivos remotos?", - "ForceDeleteBranchTitle": "Forçar deleção de branch", - "ForceDeleteBranchMessage": "{{.selectedBranchName}} não está completamente mesclada. Você está certo que quer deletar ela?", - "ForceDeleteBranchesMessage": "Algumas das filiais selecionadas não são totalmente mescladas. Tem certeza que deseja excluí-las?", - "RebaseBranch": "Refazer", - "RebaseBranchTooltip": "Refazer a branch checada na branch selecionada", - "CantRebaseOntoSelf": "Você não pode refazer a branch nela mesma", - "CantMergeBranchIntoItself": "Você não pode mesclar a branch em si mesmo", - "ForceCheckout": "Forçar checagem", - "ForceCheckoutTooltip": "Forçar checagem da branch selecionada. Isso irá descartar todas as mudanças no seu diretório de trabalho antes cheque a branch selecionada ", - "CheckoutByName": "Checar por nome", - "CheckoutByNameTooltip": "Checar por nome. Na caixa de entrada você pode inserir '-' para trocar para a última branch ", - "CheckoutPreviousBranch": "Checkout da branch anterior", - "RemoteBranchCheckoutTitle": "Checar {{.branchName}}", - "RemoteBranchCheckoutPrompt": "Como você gostaria de checar essa branch?", - "CheckoutTypeNewBranch": "Nova branch local", - "CheckoutTypeNewBranchTooltip": "Checar a branch remota como a branch local, rastreando a branch remota", - "CheckoutTypeDetachedHead": "HEAD desanexado", - "CheckoutTypeDetachedHeadTooltip": "Checar a branch remota como um HEAD desanexado, que pode ser útil se você apenas quer para testar a branch, mas não trabalha nela você mesmo. Você ainda pode criar um branch remoto a partir dela depois", - "NewBranch": "Nova branch", - "NewBranchFromStashTooltip": "Criar um novo ramo a partir da entrada de lixo selecionada. Isso funciona verificando o commit do qual a entrada de lixo foi criada, criar um novo branch a partir desse commit e, em seguida, aplicar a entrada de lixo ao novo branch como um commit adicional.", - "MoveCommitsToNewBranch": "Mover commits para uma nova branch", - "MoveCommitsToNewBranchFromBaseItem": "Nova branch da branch base (%s)", - "MoveCommitsToNewBranchStackedItem": "Novo branch empilhado na branch atual (%s)", - "NoBranchesThisRepo": "Nenhuma branch para esse repositório", - "CommitWithoutMessageErr": "Você não pode dar commit sem uma mensagem de commit", - "Close": "Fechar", - "CloseCancel": "Fechar/Cancelar", - "Confirm": "Confirmar", - "Quit": "Sair", - "SquashTooltip": "Squash o commit selecionado no commit abaixo dele. A mensagem do commit selecionado será anexada ao commit abaixo dele.", - "CannotSquashOrFixupFirstCommit": "Não há commit abaixo para squash em", - "CannotSquashOrFixupMergeCommit": "Não é possível squash ou corrigir um commit de merge", - "Fixup": "Fixup", - "FixupTooltip": "Faça o commit selecionado no commit abaixo dele. Semelhante para o squash, mas a mensagem do commit selecionado será descartada.", - "SureFixupThisCommit": "Tem certeza que deseja 'corrigir' o(s) commit(s) selecionado(s) no commit abaixo?", - "SureSquashThisCommit": "Tem certeza que deseja esmagar o(s) commit(s) selecionado(s) no commit abaixo?", - "Squash": "Squash", - "PickCommitTooltip": "Marque o commit selecionado para ser escolhido (quando meados da base). Isso significa que o commit será mantido ao continuar o rebase.", - "Pick": "Escolher", - "Edit": "Editar", - "Revert": "Reverter", - "RevertCommitTooltip": "Crie um commit reverter para o commit selecionado, que aplica as alterações do commit selecionado em reverso.", - "Reword": "Reword", - "CommitRewordTooltip": "Repetir a mensagem de submissão selecionada.", - "DropCommit": "Descartar", - "DropCommitTooltip": "Solte o commit selecionado. Isso irá remover o commit do branch através de uma rebase. Se o commit faz com que as alterações em commits posteriores dependem, você pode precisar resolver conflitos de merge.", - "MoveDownCommit": "Mover commit um para baixo", - "MoveUpCommit": "Mover o commit um para cima", - "CannotMoveAnyFurther": "Não é possível mover mais", - "CannotMoveMergeCommit": "Não é possível mover commit de um merge", - "EditCommit": "Editar (iniciar rebase interativa)", - "EditCommitTooltip": "Editar o commit selecionado. Use isto para iniciar uma rebase interativa a partir do commit selecionado. Quando já estiver no meio da reconstrução, isto irá marcar o commit selecionado para edição, o que significa que ao continuar com a reformulação. a rebase irá pausar no commit selecionado para permitir que você faça alterações.", - "AmendCommitTooltip": "Alterar o commit com mudanças em sted. Se o commit selecionado for o commit HEAD, ele executará o `git commit --amend`. Caso contrário, o compromisso será alterado por meio de uma base de apoio.", - "Amend": "Modificar", - "ResetAuthor": "Redefinir autor", - "ResetAuthorTooltip": "Redefinir o autor do commit para o usuário atualmente configurado. Isto também irá renovar o timestamp do autor", - "SetAuthor": "Definir autor", - "SetAuthorTooltip": "Definir o autor baseado em um prompt", - "AddCoAuthor": "Adicionar co-autor", - "AmendCommitAttribute": "Alterar atributo de commit", - "AmendCommitAttributeTooltip": "Definir/Redefinir autor de submissão ou co-autor definido.", - "SetAuthorPromptTitle": "Configura autor (deve se parecer com 'Nome ')", - "AddCoAuthorPromptTitle": "Adicionar co-autor (deve se parecer com 'Nome ')", - "AddCoAuthorTooltip": "Adicione um coautor usando o Github/Gitlab metadata co-produzido por.", - "RewordCommitEditor": "Republicar com o editor", - "NoCommitsThisBranch": "Não há commits para este branch", - "UpdateRefHere": "Atualizar branch '{{.ref}}' aqui", - "ExecCommandHere": "Execute o seguinte comando aqui:", - "Error": "Erro", - "Undo": "Desfazer", - "UndoReflog": "Desfazer", - "RedoReflog": "Refazer", - "UndoTooltip": "O reflog será usado para determinar qual comando git para executar para desfazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração.", - "RedoTooltip": "O reflog será usado para determinar qual comando git para executar para refazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração.", - "UndoMergeResolveTooltip": "Desfazer resolução de conflitos de última mesclagem.", - "DiscardAllTooltip": "Descartar mudanças agendadas e não preparadas em '{{.path}}'.", - "DiscardUnstagedTooltip": "Descartar mudanças não preparadas em '{{.path}}'.", - "DiscardUnstagedDisabled": "Os itens selecionados não possuem mudanças staging e não preparados.", - "Pop": "Pop", - "StashPopTooltip": "Aplique a entrada de stash no seu diretório de trabalho e remova a entrada de stash.", - "Drop": "Descartar", - "StashDropTooltip": "Remova a entrada do stash da lista de armazenamento.", - "Apply": "Aplicar", - "StashApplyTooltip": "Aplique o stash no seu diretório de trabalho.", - "NoStashEntries": "Sem itens no stash", - "StashDrop": "Remover stash", - "StashPop": "Remover Stash ", - "SurePopStashEntry": "Tem certeza de que deseja exibir esta entrada de stash?", - "StashApply": "Aplica o Stash", - "SureApplyStashEntry": "Tem certeza que deseja aplicar esta entrada de stash?", - "NoTrackedStagedFilesStash": "Você não tem arquivos rastreados/staging para armazenar", - "NoFilesToStash": "Você não tem arquivos para armazenar", - "StashChanges": "Alterações preparadas", - "RenameStash": "Renomear o stasj", - "RenameStashPrompt": "Renomear o estoque: {{.stashName}}", - "OpenConfig": "Abrir o ficheiro de config", - "EditConfig": "Editar arquivo de configuração", - "ForcePush": "Forçar push", - "ForcePushPrompt": "Seu branch divergiu do branch remoto. Pressione {{.cancelKey}} para cancelar, ou {{.confirmKey}} para forçar a push.", - "ForcePushDisabled": "Seu branch divergiu do branch remoto e você desabilitou o push forçado forçado", - "UpdatesRejected": "Atualizações foram rejeitadas. Por favor, busque e examine as alterações remotas antes de enviar novamente.", - "UpdatesRejectedAndForcePushDisabled": "Atualizações foram rejeitadas e você desativou o push de força", - "CheckForUpdate": "Verificar atualização", - "CheckingForUpdates": "A verificar por actualização…", - "UpdateAvailableTitle": "Atualização disponível!", - "UpdateAvailable": "Baixar e instalar a versão {{.newVersion}}?", - "UpdateInProgressWaitingStatus": "Atualizando", - "UpdateCompletedTitle": "Atualização concluída!", - "UpdateCompleted": "A atualização foi instalada com sucesso. Reinicie o lazygit para que tenha efeito.", - "FailedToRetrieveLatestVersionErr": "Falha ao recuperar informações da versão", - "OnLatestVersionErr": "Você já tem a versão mais recente!", - "MajorVersionErr": "Nova versão ({{.newVersion}}) tem mudanças não compatíveis com as versões anteriores comparadas com a versão atual ({{.currentVersion}})", - "CouldNotFindBinaryErr": "Não foi possível encontrar nenhum binário em {{.url}}", - "UpdateFailedErr": "Falha na atualização: {{.errMessage}}", - "ConfirmQuitDuringUpdateTitle": "Atualmente atualizando", - "ConfirmQuitDuringUpdate": "Uma atualização está em andamento. Tem certeza que deseja sair?", - "MergeToolTitle": "Ferramenta de mesclagem", - "MergeToolPrompt": "Tem certeza de que deseja abrir o `git mergetool`?", - "NonReloadableConfigWarningTitle": "Configuração alterada", - "NonReloadableConfigWarning": "As seguintes configurações foram alteradas, mas a mudança não tem efeito imediatamente. Encerre e reinicie o lazygit para que as mudanças tenham efeito:\n\n{{configs}}", - "GitconfigParseErr": "Gogit falhou ao analisar seu arquivo gitconfig devido à presença de caracteres '\\' não citados. Removendo-os deve corrigir o problema.", - "EditFile": "Editar arquivo", - "EditFileTooltip": "Abrir arquivo no editor externo.", - "OpenFile": "Abrir arquivo", - "OpenFileTooltip": "Abrir arquivo no aplicativo padrão.", - "OpenInEditor": "Abrir no editor", - "IgnoreFile": "Adicionar ao .gitignore", - "ExcludeFile": "Adicionar ao .git/info/exclui", - "RefreshFiles": "Atualizar arquivos", - "Merge": "Mesclar", - "RegularMerge": "Mesclagem regular", - "MergeBranchTooltip": "Ver opções para mesclar o item selecionado no branch atual (mesclar regularmente, mesclar squash)", - "ConfirmQuit": "Tem a certeza de que pretende sair?", - "SwitchRepo": "Mudar para um repositório recente", - "AllBranchesLogGraph": "Mostrar/ciclo todos os logs de filiais", - "UnsupportedGitService": "Serviço git não suportado", - "CopyPullRequestURL": "Copiar URL do pull request para área de transferência", - "NoBranchOnRemote": "Este branch não existe no remoto. Primeiro, você precisa fazer push para o remoto.", - "Fetch": "Buscar", - "FetchTooltip": "Buscar alterações do controle remoto.", - "CollapseAll": "Recolher todos os arquivos", - "CollapseAllTooltip": "Recolher todos os diretórios na árvore de arquivos", - "ExpandAll": "Expandir todos os arquivos", - "ExpandAllTooltip": "Expandir todos os diretórios na árvore do arquivo", - "DisabledInFlatView": "Não disponível na vista plana", - "FileEnter": "Stage lines / Colapso diretório", - "FileEnterTooltip": "Se o item selecionado for um arquivo, o foco na exibição de preparo para o estágio de cenas/linhas individuais. Se o item selecionado for um diretório, recolher/expandi-lo.", - "StageSelectionTooltip": "Ativar/desativar seleção em staged/unstaged", - "DiscardSelection": "Descartar", - "DiscardSelectionTooltip": "Quando a mudança não desejada for selecionada, descarte a mudança usando `git reset`. Quando a mudança em fase é selecionada, despare a mudança.", - "ToggleSelectionForPatch": "Alternar linhas no caminho", - "EditHunk": "Editar hunk", - "EditHunkTooltip": "Editar o local selecionado no editor externo.", - "ToggleStagingView": "Mudar de visão", - "ToggleStagingViewTooltip": "Alternar para outra visão (staged/não processadas alterações).", - "ReturnToFilesPanel": "Retornar ao painel de arquivos", - "FastForward": "Avanço rápido", - "FastForwardTooltip": "Encaminhamento rápido de branch selecionada a partir do upstream.", - "FastForwarding": "Encaminhamento rápido", - "FoundConflictsTitle": "Conflitos!", - "ViewConflictsMenuItem": "Visualizar conflitos", - "AbortMenuItem": "Abortar %s", - "PickHunk": "Escolha o local", - "PickAllHunks": "Pegar todos os pedaços", - "ViewMergeRebaseOptions": "Ver opções de mesclar/rebase", - "ViewMergeRebaseOptionsTooltip": "Ver opções para abortar/continuar/pular o merge/rebase atual.", - "ViewMergeOptions": "Visualizar opções de merge", - "ViewRebaseOptions": "Ver opções de rebase", - "NotMergingOrRebasing": "Você não está atualmente nem rebasing nem mesclando", - "AlreadyRebasing": "Não é possível executar esta ação durante uma rebase", - "RecentRepos": "Repositórios recentes", - "MergeOptionsTitle": "Opções de mesclagem", - "RebaseOptionsTitle": "Opções de rebase", - "CommitSummaryTitle": "Sumário do commit", - "CommitDescriptionTitle": "Descrição de Commit", - "CommitDescriptionSubTitle": "Pressione {{.togglePanelKeyBinding}} para alternar o foco, {{.commitMenuKeybinding}} para abrir o menu", - "LocalBranchesTitle": "Branches locais", - "SearchTitle": "Procurar", - "TagsTitle": "Etiquetas", - "MenuTitle": "Menu", - "CommitMenuTitle": "Menu de Commit", - "RemotesTitle": "Remotes", - "RemoteBranchesTitle": "Branches remotos", - "PatchBuildingTitle": "Painel principal (patch build)", - "InformationTitle": "Informações", - "SecondaryTitle": "Secundário", - "ReflogCommitsTitle": "Reflog", - "Continue": "Continuar", - "UnstagedFilesAfterConflictsResolved": "Os arquivos foram modificados desde que os conflitos foram resolvidos. Auto-encapsulá-los e continuar?", - "RebasingTitle": "Rebase '{{.checkedOutBranch}}'", - "RebasingFromBaseCommitTitle": "Rebase '{{.checkedOutBranch}}' de uma base marcada", - "SimpleRebase": "Rebase simples para '{{.ref}}'", - "InteractiveRebase": "Rebase interativa em '{{.ref}}'", - "RebaseOntoBaseBranch": "Rebase no ramo base ({{.baseBranch}})", - "InteractiveRebaseTooltip": "Comece uma rebase interativa com uma pausa no início, então você pode atualizar os commits TODO antes de continuar.", - "RebaseOntoBaseBranchTooltip": "Rebase o branch check-out em seu ramo base (ou seja, o ramo principal mais próximo).", - "MustSelectTodoCommits": "Ao rebaste, esta ação só funciona numa seleção de commits do TODO.", - "FwdNoUpstream": "Não é possível encaminhar rapidamente um branch sem upstream", - "FwdNoLocalUpstream": "Não é possível encaminhar rapidamente um branch cujo controle remoto não está registrado localmente", - "FwdCommitsToPush": "Não é possível encaminhar um branch com commits para fazer push", - "PullRequestNoUpstream": "Não é possível abrir uma pull request para um branch sem upstream", - "ErrorOccurred": "Ocorreu um erro! Por favor, crie um problema em", - "ConflictLabel": "CONFLITO", - "CommitsSectionHeader": "Commits", - "YouDied": "VOCÊ MORREU!", - "RewordNotSupported": "Reredacção de commits enquanto rebasing interativamente não é suportado atualmente", - "ChangingThisActionIsNotAllowed": "Não é permitido alterar este tipo de rebase de tarefas", - "DroppingMergeRequiresSingleSelection": "Soltar um commit de merge requer um único item selecionado", - "CherryPickCopy": "Copiar (cherry-pick)", - "CherryPickCopyTooltip": "Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `{{.paste}}` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `{{.escape}}` para cancelar a seleção.", - "PasteCommits": "Colar (cherry-pick)", - "SureCherryPick": "Tem certeza que deseja cherry-pick o(s) commit(s) {{.numCommits}} copiado(s) para este branch?", - "CherryPick": "cherry-pick", - "CannotCherryPickNonCommit": "Não é possível escolher este tipo de item de tarefa", - "Donate": "Doar", - "AskQuestion": "Faça perguntas", - "PrevHunk": "Ir para o local anterior", - "NextHunk": "Ir para o próximo trecho", - "PrevConflict": "Conflito anterior", - "NextConflict": "Próximo conflito", - "SelectPrevHunk": "Trecho anterior", - "SelectNextHunk": "Próximo trecho", - "ScrollDown": "Rolar para baixo", - "ScrollUp": "Rolar para cima", - "ScrollUpMainWindow": "Rolar janela principal para cima", - "ScrollDownMainWindow": "Rolar a janela principal para baixo", - "AmendCommitTitle": "Corrigir commit", - "AmendCommitPrompt": "Tem certeza que deseja corrigir esse commit com seus arquivos encapsulados?", - "AmendCommitWithConflictsMenuPrompt": "AVISO: está prestes a reemendar o seu último commit finalizado com conflitos resolvidos. Isso é muito improvável ser o que quer nesse ponto. Mais improvável você simplesmente querer continuar o rebase ao invés disso", - "AmendCommitWithConflictsContinue": "Não, continuar a reconstrução", - "AmendCommitWithConflictsAmend": "Sim, alterar o commit anterior", - "DropCommitTitle": "Excluir commit", - "DropCommitPrompt": "Tem certeza que deseja remover o(s) commit(s) selecionado(s)?", - "DropUpdateRefPrompt": "Tem certeza que deseja excluir a(s) atualização(ões) selecionada(s)? Isso é irreversível, exceto abortando a rebase.", - "DropMergeCommitPrompt": "Tem certeza que deseja remover o commit de mesclagem selecionado? Note que ele também vai apagar todos os commits que foram mesclados por ele.", - "PullingStatus": "Puxando", - "PushingStatus": "Enviando", - "FetchingStatus": "Buscando", - "SquashingStatus": "Esmagando", - "FixingStatus": "Consertando", - "DeletingStatus": "Deletando", - "DroppingStatus": "Descartando", - "MovingStatus": "Movendo", - "RebasingStatus": "Rebase", - "MergingStatus": "Mesclando", - "LowercaseRebasingStatus": "recriando", - "LowercaseMergingStatus": "mesclando", - "AmendingStatus": "Modificação", - "CherryPickingStatus": "Cherry-picking", - "UndoingStatus": "Desfazendo", - "RedoingStatus": "Refazer", - "CheckingOutStatus": "Confira", - "CommittingStatus": "Commitando", - "RevertingStatus": "revertendo", - "CreatingFixupCommitStatus": "Criando commit de correção", - "MovingCommitsToNewBranchStatus": "Mover commits para uma nova branch", - "CommitFiles": "Commit arquivos", - "SubCommitsDynamicTitle": "Commits (%s)", - "CommitFilesDynamicTitle": "Arquivos diff (%s)", - "RemoteBranchesDynamicTitle": "Branches remotas (%s)", - "ViewItemFiles": "Ver arquivos", - "CommitFilesTitle": "Commit arquivos", - "CheckoutCommitFileTooltip": "Arquivo de check-out. Isso substitui o arquivo em sua árvore de trabalho com a versão do commit selecionado.", - "CanOnlyDiscardFromLocalCommits": "As alterações só podem ser descartadas de commits locais", - "Remove": "Remover", - "DiscardOldFileChangeTooltip": "Descartar as alterações desse commit para este arquivo. Isso executa uma rebase interativa em segundo plano, então você pode ter um conflito de merge se um commit posterior também alterar este arquivo.", - "DiscardFileChangesTitle": "Descartar alterações de arquivo", - "DiscardFileChangesPrompt": "Você tem certeza de que deseja remover as alterações do(s) arquivo(s) selecionado(s) deste commit?", - "BareRepo": "Você tentou abrir Lazygit em um repositório puro, mas Lazygit ainda não suporta repositórios vazios. Abrir os repositórios mais recentes? (y/n) ", - "InitialBranch": "Nome da branch? (deixe vazio para o padrão do git): ", - "NoRecentRepositories": "É necessário abrir lazygit em um repositório git. Nenhum repositório recente válido. Saindo do sistema.", - "IncorrectNotARepository": "O valor de 'notARepository' está incorreto. Deve ser um dos 'prompt', 'create', 'sk', ou 'quit'.", - "AutoStashTitle": "Autoarmazenar?", - "AutoStashPrompt": "Você deve esconder e mostrar suas alterações para que elas passem. Quer fazer isso automaticamente? (enter/esc)", - "Discard": "Descartar", - "DiscardChangesTitle": "Descartar alterações", - "DiscardFileChangesTooltip": "Exibir opções para descartar alterações para o arquivo selecionado.", - "Cancel": "Cancelar", - "DiscardAllChanges": "Descartar todas as Alterações", - "DiscardUnstagedChanges": "Descartar mudanças não preparadas", - "DiscardAllChangesToAllFiles": "Apargar árvore ativa", - "DiscardAnyUnstagedChanges": "Descartar mudanças não preparadas", - "DiscardUntrackedFiles": "Descartar arquivos não monitorizados", - "DiscardStagedChanges": "Descartar mudanças em Staging", - "HardReset": "reinicialização total", - "BranchDeleteTooltip": "Ver opções de exclusão para a branch local/remoto.", - "TagDeleteTooltip": "Ver opções de exclusão para tag local/remoto.", - "Delete": "Apagar", - "Reset": "Restaurar", - "ResetTooltip": "Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado.", - "ViewResetOptions": "Restaurar", - "FileResetOptionsTooltip": "Opções de redefinição de exibição para árvore de trabalho (por exemplo, nukando a árvore de trabalho).", - "CreateFixupCommit": "Criar commit de correção", - "CreateFixupCommitTooltip": "Crie o commit 'correção!' para o commit selecionado. Mais tarde, você pode pressionar `{{.squashAbove}}` neste mesmo commit para aplicar todas os commits de correção acima.", - "CreateAmendCommit": "Criar \"alterar!\" commit", - "FixupMenu_Fixup": "corrigir! commit", - "FixupMenu_FixupTooltip": "Permite que você arrume outro commit e mantenha a mensagem do commit original.", - "FixupMenu_AmendWithChanges": "alterar! commit com mudanças", - "FixupMenu_AmendWithChangesTooltip": "Permite que você corrija outro commit e também altere sua mensagem de commit.", - "FixupMenu_AmendWithoutChanges": "alterar! commit sem alterações (pura reformulação)", - "FixupMenu_AmendWithoutChangesTooltip": "Permite alterar a mensagem de commit de outro commit sem alterar seu conteúdo.", - "SquashAboveCommitsTooltip": "Aplicar Squash all 'correção!', seja acima do commit selecionado, ou tudo no branch atual (autosquash).", - "SquashCommitsAboveSelectedTooltip": "Squash todos 'fixup!' commits acima do commit selecionado (autosquash).", - "SquashCommitsInCurrentBranchTooltip": "Squash all 'fixup!' commits no branch atual (autosquash).", - "SquashAboveCommits": "Aplicar commits de correções", - "SquashCommitsInCurrentBranch": "No branch atual", - "SquashCommitsAboveSelectedCommit": "Acima do commit escolhido", - "CannotSquashCommitsInCurrentBranch": "Não é possível realizar o squash commits no branch atual: o commit do HEAD é um commit de merge ou está presente no branch principal.", - "ExecuteShellCommand": "Executar comando da shell", - "ExecuteShellCommandTooltip": "Traga um prompt onde você pode digitar um comando shell para executar.", - "ShellCommand": "Comando Shell:", - "CommitChangesWithoutHook": "Fazer commit de alterações sem pré-commit", - "ResetTo": "Redefinir para", - "ResetSoftTooltip": "Redefinir o HEAD para o commit escolhido, e manter as alterações entre o commit atual e o commit escolhido à medida que as mudanças forem processadas.", - "ResetMixedTooltip": "Redefinir o HEAD para o commit escolhido e manter as alterações entre o commit atual e escolhido conforme mudanças não preparadas.", - "ResetHardTooltip": "Redefinir HEAD para o commit escolhido e descartar todas as alterações entre o atual e o commit escolhido, bem como todas as modificações actuais na árvore de trabalho.", - "PressEnterToReturn": "Pressione Enter para retornar ao lazygit", - "ViewStashOptions": "Ver opções de stash", - "ViewStashOptionsTooltip": "Ver opções de stash (por exemplo, trash all, stash staged, stash unsttued).", - "Stash": "Stash", - "StashTooltip": "Stash todas as alterações. Para outras variações de armazenamento, use a fixação de teclas de armazenamento.", - "StashAllChanges": "Alterações preparadas", - "StashStagedChanges": "Alterações não preparadas", - "StashAllChangesKeepIndex": "Guardar todas as alterações e manter índice", - "StashUnstagedChanges": "Stash todas as mudanças não preparadas", - "StashIncludeUntrackedChanges": "Stash todas as alterações, incluindo arquivos não rastreados", - "StashOptions": "Opções de Stash", - "NotARepository": "Erro: deve ser executado dentro de um repositório git", - "WorkingDirectoryDoesNotExist": "Erro: o diretório de trabalho atual não existe", - "ScrollLeft": "Rolar à esquerda", - "ScrollRight": "Scroll para a direita", - "DiscardPatch": "Descartar patch", - "DiscardPatchConfirm": "Você só pode construir um patch de um commit/stash-entry por vez. Descartar o patch atual?", - "CantPatchWhileRebasingError": "Você não pode construir um patch ou executar comandos de patch enquanto estiver em estado de fusão ou de recriação", - "ToggleAddToPatch": "Alternar entre o arquivo incluído no patch", - "ToggleAddToPatchTooltip": "Alternar se o arquivo está incluído no patch personalizado. Veja {{.doc}}.", - "ToggleAllInPatch": "Alternar todos os arquivos", - "ToggleAllInPatchTooltip": "Adicionar/remover todos os arquivos de commit para atualização personalizada. Consulte {{.doc}}.", - "UpdatingPatch": "Atualizando patch", - "ViewPatchOptions": "Ver opções de patch personalizadas", - "PatchOptionsTitle": "Opções de modificação", - "NoPatchError": "Nenhum patch criado ainda. Para começar a construir um patch, use 'space' em um arquivo de commit ou insira para adicionar linhas específicas", - "EmptyPatchError": "O patch ainda está vazio. Adicione alguns arquivos ou linhas ao seu patch primeiro.", - "EnterCommitFile": "Insira o arquivo / Alternar diretório recolhido", - "EnterCommitFileTooltip": "Se um arquivo estiver selecionado, insira o arquivo para que você possa adicionar/remover linhas individuais no patch personalizado. Se um diretório for selecionado, ative o diretório.", - "ExitCustomPatchBuilder": "Sair do construtor de patch personalizado", - "EnterUpstream": "Insira o upstream como ' '", - "InvalidUpstream": "Upstream inválido. Deve estar no formato ' '", - "NewRemote": "Novo controle", - "NewRemoteName": "Novo nome do controle remoto:", - "NewRemoteUrl": "Nova URL remota:", - "ViewBranches": "Ver branches", - "EditRemoteName": "Digite o nome remoto atualizado para {{.remoteName}}:", - "EditRemoteUrl": "Digite a URL remota atualizada para {{.remoteName}}:", - "RemoveRemote": "Remover remoto", - "RemoveRemoteTooltip": "Remover o controle remoto. Quaisquer ramificações locais de rastreamento de um ramo remoto do controle não serão afetadas.", - "RemoveRemotePrompt": "Tem certeza que deseja remover o controle remoto?", - "DeleteRemoteBranch": "Deletar branch remota", - "DeleteRemoteBranches": "Deletar branch remota", - "DeleteRemoteBranchTooltip": "Excluir o branch remoto do controle remoto.", - "DeleteLocalAndRemoteBranch": "Excluir ramo local e remoto", - "DeleteLocalAndRemoteBranches": "Excluir branches locais e remotos", - "SetAsUpstream": "Definir como upstream", - "SetAsUpstreamTooltip": "Definir o ramo remoto selecionado como fluxo do branch check-out.", - "SetUpstream": "Configurar o upstream da filial selecionada", - "UnsetUpstream": "Desconfigurar upstream do branch selecionado", - "ViewDivergenceFromUpstream": "Ver divergência do upstream", - "ViewDivergenceFromBaseBranch": "Ver diferenças em relação ao ramo base ({{.baseBranch}})", - "CouldNotDetermineBaseBranch": "Não foi possível determinar o branch base", - "DivergenceSectionHeaderLocal": "Local", - "DivergenceSectionHeaderRemote": "Remoto", - "ViewUpstreamResetOptions": "Redefinir ramo check-out para {{.upstream}}", - "ViewUpstreamResetOptionsTooltip": "Exibir opções para redefinir o branch check-out no {{upstream}}. Nota: isso não irá redefinir o ramo selecionado para a montante, ele irá redefinir o ramo de verificação para a parte a montante.", - "ViewUpstreamRebaseOptions": "Rebase ramo check-out na {{.upstream}}", - "ViewUpstreamRebaseOptionsTooltip": "Ver opções para rectificar o branch check-out no {{upstream}}. Nota: isso não irá rebasear o branch selecionado no plano a montante, ele irá rebasear o branch de check-out no fluxo a montante.", - "UpstreamGenericName": "upstream da branch selecionada", - "CreatingTag": "Criando etiqueta", - "ForceTag": "Forçar Etiqueta", - "GitFlowOptions": "Exibir opções do git-flow", - "Actions": {}, - "Bisect": {}, - "Log": {}, - "BreakingChangesByVersion": {} -} diff --git a/pkg/i18n/translations/ru.json b/pkg/i18n/translations/ru.json deleted file mode 100644 index ef93bf49816..00000000000 --- a/pkg/i18n/translations/ru.json +++ /dev/null @@ -1,585 +0,0 @@ -{ - "NotEnoughSpace": "Недостаточно места для отрисовки панелей", - "DiffTitle": "Сравнения", - "FilesTitle": "Файлы", - "BranchesTitle": "Ветки", - "CommitsTitle": "Коммиты", - "StashTitle": "Хранилище", - "SnakeTitle": "Змейка", - "EasterEgg": "Пасхалка", - "UnstagedChanges": "Непроиндексированные Изменения", - "StagedChanges": "Проиндексированные Изменения", - "StagingTitle": "Главная панель (Индексирование)", - "MergingTitle": "Главная панель (Слияние)", - "NormalTitle": "Главная панель (Обычный)", - "LogTitle": "Журнал", - "CommitSummary": "Сводка коммита", - "CredentialsUsername": "Имя пользователя", - "CredentialsPassword": "Пароль", - "CredentialsPassphrase": "Введите пароль для SSH ключа", - "CredentialsPIN": "Введите PIN-код для SSH ключа", - "PassUnameWrong": "Неверный пароль, кодовая фраза и/или имя пользователя", - "Commit": "Сохранить изменения", - "AmendLastCommit": "Правка последнего коммита", - "AmendLastCommitTitle": "Правка Последнего Коммита", - "SureToAmend": "Вы уверены, что хотите править последний коммит? Впоследствии можно изменить сообщение коммита на панели коммитов.", - "NoCommitToAmend": "Не найден коммит для внесения поправок.", - "CommitChangesWithEditor": "Сохранить изменения с помощью редактора git", - "StatusTitle": "Статус", - "GlobalTitle": "Глобальные сочетания клавиш", - "Execute": "Выполнить", - "Stage": "Переключить индекс", - "ToggleStagedAll": "Все проиндексированные/непроиндексированные", - "ToggleTreeView": "Переключить вид дерева файлов", - "OpenMergeTool": "Открыть внешний инструмент слияния (git mergetool)", - "Refresh": "Обновить", - "Push": "Отправить изменения", - "Pull": "Получить и слить изменения", - "FileFilter": "Фильтровать файлы (проиндексированные/непроиндексированные)", - "FilterStagedFiles": "Показывать только проиндексированные файлы", - "FilterUnstagedFiles": "Показывать только непроиндексированные файлы", - "MergeConflictsTitle": "Конфликты Слияния", - "Checkout": "Переключить", - "NoChangedFiles": "Нет изменённых файлов", - "SoftReset": "Мягкий сброс", - "AlreadyCheckedOutBranch": "Вы уже переключились в эту ветку", - "SureForceCheckout": "Вы уверены, что хотите принудительная переключить? Вы потеряете все локальные изменения", - "ForceCheckoutBranch": "Принудительное Переключение Ветки", - "BranchName": "Название ветки", - "NewBranchNameBranchOff": "Название новой ветки (Ветка с '{{.branchName}}')", - "CantDeleteCheckOutBranch": "Невозможно удалить переключённую ветку!", - "ForceDeleteBranchMessage": "'{{.selectedBranchName}}' не полностью слилась. Вы уверены, что хотите удалить его?", - "RebaseBranch": "Перебазировать переключённую ветку на эту ветку", - "CantRebaseOntoSelf": "Невозможно перебазировать ветку на себя", - "CantMergeBranchIntoItself": "Невозможно объединить ветку в себя", - "ForceCheckout": "Принудительное переключение", - "CheckoutByName": "Переключить по названию", - "NewBranch": "Новая ветка", - "NoBranchesThisRepo": "Нет веток для этого репозитория", - "CommitWithoutMessageErr": "Вы не можете сохранить изменения без сообщения коммита", - "Close": "Закрыть", - "CloseCancel": "Закрыть/отменить", - "Confirm": "Подтвердить", - "Quit": "Выйти", - "CannotSquashOrFixupFirstCommit": "Ниже нет коммита, который можно было бы объединить", - "Fixup": "Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) ", - "SureFixupThisCommit": "Вы уверены, что хотите объединить несколько коммитов, отбросив сообщение коммита? Он будет объединён с коммитом ниже", - "SureSquashThisCommit": "Вы уверены, что хотите объединить несколько коммитов в нижний коммит?", - "Squash": "Объединить коммиты (Squash)", - "PickCommitTooltip": "Выбрать коммит (в середине перебазирования)", - "Reword": "Перефразировать коммит", - "DropCommit": "Удалить коммит", - "MoveDownCommit": "Переместить коммит вниз на один", - "MoveUpCommit": "Переместить коммит вверх на один", - "EditCommitTooltip": "Изменить коммит", - "AmendCommitTooltip": "Править последний коммит с проиндексированными изменениями", - "ResetAuthor": "Сброс автора коммита", - "SetAuthor": "Установить автора", - "AmendCommitAttribute": "Установить/убрать автора коммита", - "SetAuthorPromptTitle": "Установить автора (должно выглядеть как «Имя »)", - "RewordCommitEditor": "Переписать коммит с помощью редактора", - "NoCommitsThisBranch": "Нет коммитов для этой ветки", - "UpdateRefHere": "Обновить ветку '{{.ref}}' здесь", - "Error": "Ошибка", - "Undo": "Отменить", - "UndoReflog": "Отменить (через reflog) (экспериментальный)", - "RedoReflog": "Повторить (через reflog) (экспериментальный)", - "UndoTooltip": "Журнал ссылок (reflog) будет использоваться для определения того, какую команду git запустить, чтобы отменить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты.", - "RedoTooltip": "Журнал ссылок (reflog) будет использоваться для определения того, какую команду git нужно запустить, чтобы повторить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты.", - "DiscardAllTooltip": "Отменить проиндексированные и непроиндексированные изменения в '{{.path}}'.", - "DiscardUnstagedTooltip": "Отменить непроиндексированные изменения в '{{.path}}'.", - "Pop": "Применить припрятанные изменения и тут же удалить их из хранилища", - "Drop": "Удалить припрятанные изменения из хранилища", - "Apply": "Применить припрятанные изменения", - "NoStashEntries": "Нет записей в хранилище", - "StashDrop": "Сбросить хранилище", - "StashPop": "Применить припрятанные изменения и тут же удалить их из хранилища", - "SurePopStashEntry": "Вы уверены, что хотите применить эти припрятанные изменения и тут же удалить их из хранилища?", - "StashApply": "Применить припрятанные изменения", - "SureApplyStashEntry": "Вы уверены, что хотите применить эти припрятанные изменения?", - "NoTrackedStagedFilesStash": "У вас нет отслеженных/проиндексированных файлов для хранения", - "NoFilesToStash": "У вас нет файлов для хранения", - "StashChanges": "Припрятать изменения", - "RenameStash": "Переименовать хранилище", - "RenameStashPrompt": "Переименовать хранилище: {{.stashName}}", - "OpenConfig": "Открыть файл конфигурации", - "EditConfig": "Редактировать файл конфигурации", - "ForcePush": "Принудительная отправка изменении", - "ForcePushPrompt": "Ветка отклонилась от удалённой ветки. Нажмите «esc», чтобы отменить, или «enter», чтобы начать принудительную отправку изменении.", - "ForcePushDisabled": "Ветка отклонилась от удалённой ветки. Принудительная отправка изменении была отключена", - "UpdatesRejectedAndForcePushDisabled": "Обновления были отклонены. Принудительная отправка изменении была отключена", - "CheckForUpdate": "Проверить обновления", - "CheckingForUpdates": "Проверка обновлений...", - "UpdateAvailableTitle": "Доступно обновление!", - "UpdateAvailable": "Скачать и установить версию {{.newVersion}}?", - "UpdateInProgressWaitingStatus": "Обновление", - "UpdateCompletedTitle": "Обновление завершено!", - "UpdateCompleted": "Обновление успешно установлено. Перезапустите lazygit, чтобы обновление вступило в силу.", - "FailedToRetrieveLatestVersionErr": "Не удалось получить информацию о версии", - "OnLatestVersionErr": "Установлена последняя версия", - "MajorVersionErr": "Новая версия ({{.newVersion}}) содержит несовместимые с предыдущими версии изменения по сравнению с текущей версией ({{.currentVersion}})", - "CouldNotFindBinaryErr": "Не удалось найти бинарный файл на {{.url}}", - "UpdateFailedErr": "Не удалось обновить: {{.errMessage}}", - "ConfirmQuitDuringUpdateTitle": "Идёт Обновление", - "ConfirmQuitDuringUpdate": "Выполняется обновление. Вы уверены, что хотите выйти?", - "MergeToolTitle": "Инструмент слияния", - "MergeToolPrompt": "Вы уверены, что хотите открыть `git mergetool`?", - "GitconfigParseErr": "Gogit не удалось проанализировать ваш файл gitconfig из-за наличия символов «\\» без кавычек. Их удаление должно решить проблему.", - "EditFile": "Редактировать файл", - "OpenFile": "Открыть файл", - "IgnoreFile": "Добавить в .gitignore", - "ExcludeFile": "Добавить в .git/info/exclude", - "RefreshFiles": "Обновить файлы", - "Merge": "Слияние с текущей переключённой веткой", - "ConfirmQuit": "Вы уверены, что хотите выйти?", - "SwitchRepo": "Переключиться на последний репозиторий", - "UnsupportedGitService": "Неподдерживаемая служба git", - "CopyPullRequestURL": "Скопировать URL запроса на принятие изменений в буфер обмена", - "NoBranchOnRemote": "Этой ветки не существует в удалённом репозитории. Сначала вам нужно его отправить в удалённый репозитории.", - "Fetch": "Получить изменения", - "FileEnter": "Проиндексировать отдельные части/строки для файла или свернуть/развернуть для каталога", - "StageSelectionTooltip": "Переключить строку в проиндексированные / непроиндексированные", - "DiscardSelection": "Отменить изменение (git reset)", - "ToggleSelectionForPatch": "Добавить/удалить строку(и) для патча", - "EditHunk": "Изменить эту часть", - "ToggleStagingView": "Переключиться на другую панель (проиндексированные/непроиндексированные изменения)", - "ReturnToFilesPanel": "Вернуться к панели файлов", - "FastForward": "Перемотать эту ветку вперёд из её upstream-ветки", - "FastForwarding": "Получить изменения и перемотать вперёд", - "FoundConflictsTitle": "Конфликты!", - "ViewConflictsMenuItem": "Просмотр конфликтов", - "AbortMenuItem": "Прервать %s", - "PickHunk": "Выбрать эту часть", - "PickAllHunks": "Выбрать все части", - "ViewMergeRebaseOptions": "Просмотреть параметры слияния/перебазирования", - "NotMergingOrRebasing": "В данный момент вы не выполняете ни перебазирования, ни слияние", - "AlreadyRebasing": "Невозможно выполнить это действие во время перебазирования", - "RecentRepos": "Последние репозитории", - "MergeOptionsTitle": "Параметры слияния", - "RebaseOptionsTitle": "Параметры перебазирования", - "CommitSummaryTitle": "Сводка коммита", - "CommitDescriptionTitle": "Описание коммита", - "CommitDescriptionSubTitle": "Нажмите вкладку, чтобы переключить фокус", - "LocalBranchesTitle": "Локальные Ветки", - "SearchTitle": "Поиск", - "TagsTitle": "Теги", - "MenuTitle": "Меню", - "RemotesTitle": "Удалённые репозитории", - "RemoteBranchesTitle": "Удалённые ветки", - "PatchBuildingTitle": "Главная панель (сборка патчей)", - "InformationTitle": "Информация", - "SecondaryTitle": "Вторичный", - "ReflogCommitsTitle": "Журнал ссылок (Reflog)", - "Continue": "Продолжить", - "RebasingTitle": "Перебазировать '{{.checkedOutBranch}}'", - "SimpleRebase": "Простая перебазировка на '{{.ref}}'", - "InteractiveRebase": "Интерактивная перебазировка на '{{.ref}}'", - "InteractiveRebaseTooltip": "Начать интерактивную перебазировку с перерыва в начале, чтобы можно было обновить TODO коммиты, прежде чем продолжить.", - "FwdNoUpstream": "Невозможно перемотать ветку без upstream-ветки", - "FwdNoLocalUpstream": "Невозможно перемотать ветку. Удалённый репозитории не зарегистрирован локально", - "FwdCommitsToPush": "Невозможно перемотать ветку с коммитами для отправки", - "ErrorOccurred": "Произошла ошибка! Пожалуйста, заявите о проблеме на", - "YouDied": "ТЫ УМЕР!", - "RewordNotSupported": "Переформулировка коммитов при интерактивном перебазировании в настоящее время не поддерживается", - "ChangingThisActionIsNotAllowed": "Изменение этого типа записи todo перебазирования не допускается", - "CherryPickCopy": "Скопировать отобранные коммит (cherry-pick)", - "PasteCommits": "Вставить отобранные коммиты (cherry-pick)", - "CherryPick": "Выборочная отборка (Cherry-Pick)", - "Donate": "Пожертвовать", - "AskQuestion": "Задать вопрос", - "PrevHunk": "Выбрать предыдущую часть", - "NextHunk": "Выбрать следующую часть", - "PrevConflict": "Выбрать предыдущий конфликт", - "NextConflict": "Выбрать следующий конфликт", - "SelectPrevHunk": "Выбрать предыдущую часть", - "SelectNextHunk": "Выбрать следующую часть", - "ScrollDown": "Прокрутить вниз", - "ScrollUp": "Прокрутить вверх", - "ScrollUpMainWindow": "Прокрутить вверх главную панель", - "ScrollDownMainWindow": "Прокрутить вниз главную панель", - "AmendCommitTitle": "Править коммит (amend)", - "AmendCommitPrompt": "Вы уверены, что хотите править этот коммит проиндексированными файлами?", - "DropCommitTitle": "Удалить коммит", - "DropCommitPrompt": "Вы уверены, что хотите удалить этот коммит?", - "PullingStatus": "Получение и слияние изменении", - "PushingStatus": "Отправка изменении", - "FetchingStatus": "Получение изменении", - "SquashingStatus": "Объединение коммитов", - "FixingStatus": "Объединение коммитов, отбросив сообщение коммита", - "DeletingStatus": "Удаление", - "MovingStatus": "Перемещение", - "RebasingStatus": "Перебазирование", - "MergingStatus": "Слияние", - "LowercaseRebasingStatus": "перебазировка", - "LowercaseMergingStatus": "слияние", - "AmendingStatus": "Правка коммита", - "CherryPickingStatus": "Выборочная отборка (cherry-picking)", - "UndoingStatus": "Отмена последней команды", - "RedoingStatus": "Выполнение последней команды", - "CheckingOutStatus": "Переключение", - "CommittingStatus": "Сохранение изменении", - "CommitFiles": "Сохранить изменения файлов", - "SubCommitsDynamicTitle": "Коммиты (%s)", - "CommitFilesDynamicTitle": "Различия файлов (%s)", - "RemoteBranchesDynamicTitle": "Удалённые ветки (%s)", - "ViewItemFiles": "Просмотреть файлы выбранного элемента", - "CommitFilesTitle": "Сохранить Изменения Файлов", - "CheckoutCommitFileTooltip": "Переключить файл", - "CanOnlyDiscardFromLocalCommits": "Изменения можно отменить только из локальных коммитов.", - "DiscardOldFileChangeTooltip": "Отменить изменения коммита в этом файле", - "DiscardFileChangesTitle": "Отменить изменения файла", - "DiscardFileChangesPrompt": "Вы уверены, что хотите удалить изменения в выбранных файлах из этого коммита?\n\nЭто действие запустит перебазирование и отменит изменения в этих файлах. Обратите внимание, что если последующие коммиты зависят от этих изменений, вам, возможно, придется разрешить конфликты.\nПримечание: это также сбросит все активные пользовательские патчи.", - "BareRepo": "Вы пытались открыть Lazygit в пустом репозитории, но Lazygit ещё не поддерживает пустые репозитории. Открыть последний репозиторий? (y/n)", - "InitialBranch": "Название ветки? (оставьте пустым для git по умолчанию):", - "NoRecentRepositories": "Необходимо открыть lazygit в git репозитории. Нет валидных последних репозиториев. Выход.", - "IncorrectNotARepository": "Неверное значение 'notARepository'. Это должно быть одним из 'prompt', 'create', 'skip', или 'quit'.", - "AutoStashTitle": "Автосохранить изменения?", - "AutoStashPrompt": "Чтобы перенести изменения, их нужно сохранить и вынуть. Сделать это автоматически? (enter/esc)", - "Discard": "Просмотреть параметры «отмены изменении»", - "Cancel": "Отменить", - "DiscardAllChanges": "Отменить все изменения", - "DiscardUnstagedChanges": "Отменить непроиндексированные изменения", - "DiscardAllChangesToAllFiles": "Разбомбить рабочее дерево?", - "DiscardAnyUnstagedChanges": "Отменить непроиндексированные изменения", - "DiscardUntrackedFiles": "Удалить неотслеживаемые файлы", - "DiscardStagedChanges": "Отменить проиндексированные изменения", - "HardReset": "Жёсткий сброс", - "ViewResetOptions": "Просмотреть параметры сброса", - "CreateFixupCommit": "Создать fixup коммит", - "CreateFixupCommitTooltip": "Создать fixup коммит для этого коммита", - "SquashAboveCommitsTooltip": "Объединить все 'fixup!' коммиты выше в выбранный коммит (автосохранение)", - "CommitChangesWithoutHook": "Закоммитить изменения без предварительного хука коммита", - "ResetTo": "Сбросить на", - "PressEnterToReturn": "Нажмите Enter, чтобы вернуться в lazygit", - "ViewStashOptions": "Просмотреть параметры хранилища", - "StashAllChanges": "Припрятать все изменения", - "StashStagedChanges": "Припрятать проиндексированные изменения", - "StashAllChangesKeepIndex": "Припрятать все изменения и сохранить индекс", - "StashUnstagedChanges": "Припрятать непроиндексированные изменения", - "StashIncludeUntrackedChanges": "Припрятать все изменения, включая неотслеживаемые файлы", - "StashOptions": "Параметры хранилища", - "NotARepository": "Ошибка: необходимо запустить внутри git репозитория", - "ScrollLeft": "Прокрутить влево", - "ScrollRight": "Прокрутить вправо", - "DiscardPatch": "Отменить патч", - "DiscardPatchConfirm": "Вы можете собрать патч только из одной записи коммита/хранилища за раз. Отменить текущий патч?", - "CantPatchWhileRebasingError": "Вы не можете создавать патчи или запускать команды патча, находясь в состоянии слияния или перемещения.", - "ToggleAddToPatch": "Переключить файлы включённые в патч", - "ToggleAllInPatch": "Переключить все файлы, включённые в патч", - "UpdatingPatch": "Обновление патча", - "ViewPatchOptions": "Просмотреть пользовательские параметры патча", - "PatchOptionsTitle": "Параметры патча", - "NoPatchError": "Патч ещё не создан. Чтобы начать сборку патча, используйте «пробел» в файле коммита или введите, чтобы добавить определённые строки.", - "EnterCommitFile": "Введите файл, чтобы добавить выбранные строки в патч (или свернуть каталог переключения)", - "ExitCustomPatchBuilder": "Выйти из сборщика пользовательских патчей", - "EnterUpstream": "Введите upstream как ' '", - "InvalidUpstream": "Недействительный upstream. Должен быть в формате ' '", - "NewRemote": "Добавить новую удалённую ветку", - "NewRemoteName": "Название новой удалённой ветки", - "NewRemoteUrl": "Ссылка новой удалённой ветки", - "EditRemoteName": "Введите новое название для удалённое ветки {{.remoteName}}:", - "EditRemoteUrl": "Введите новую ссылку для удалённое ветки {{.remoteName}}:", - "RemoveRemote": "Удалить удалённую ветку", - "DeleteRemoteBranch": "Удалить Удалённую Ветку", - "SetAsUpstreamTooltip": "Установить как upstream-ветку переключённую ветку", - "SetUpstream": "Установить upstream-ветку из выбранной ветки", - "UnsetUpstream": "Убрать upstream-ветку из выбранной ветки", - "SetUpstreamTitle": "Установить upstream-ветку", - "EditRemoteTooltip": "Редактировать удалённый репозитории", - "TagCommit": "Пометить коммит тегом", - "TagNameTitle": "Название тега", - "TagMessageTitle": "Сообщения тега", - "LightweightTag": "Легковесный тег", - "AnnotatedTag": "Аннотированный тег", - "DeleteTagTitle": "Удалить тег", - "PushTagTitle": "Удалённый репозитории для отправки тега '{{.tagName}}' в:", - "PushTag": "Отправить тег", - "NewTag": "Создать тег", - "FetchRemoteTooltip": "Получение изменения из удалённого репозитория", - "GitFlowOptions": "Показать параметры git-flow", - "NotAGitFlowBranch": "Это не похоже на ветку git-flow", - "NewBranchNamePrompt": "Введите новое название ветки", - "IgnoreTracked": "Игнорировать отслеживаемый файл", - "ExcludeTracked": "Исключить отслеживаемый файл", - "IgnoreTrackedPrompt": "Вы уверены, что хотите игнорировать отслеживаемый файл?", - "ExcludeTrackedPrompt": "Вы уверены, что хотите исключить отслеживаемый файл?", - "ViewResetToUpstreamOptions": "Просмотреть параметры сброса upstream-ветки", - "NextScreenMode": "Следующий режим экрана (нормальный/полуэкранный/полноэкранный)", - "PrevScreenMode": "Предыдущий режим экрана", - "StartSearch": "Найти", - "Keybindings": "Связки клавиш", - "KeybindingsLegend": "Связки клавиш", - "RenameBranch": "Переименовать ветку", - "NewGitFlowBranchPrompt": "Новое {{.branchType}} название:", - "RenameBranchWarning": "Эта ветвь отслеживает удалённый репозитории. Это действие переименует только имя локальной ветки, а не имя удалённой ветки. Продолжать?", - "OpenKeybindingsMenu": "Открыть меню", - "ResetCherryPick": "Сбросить отобранную (скопированную | cherry-picked) выборку коммитов", - "NextTab": "Следующая вкладка", - "PrevTab": "Предыдущая вкладка", - "CantUndoWhileRebasing": "Невозможно отменить во время перебазирования", - "CantRedoWhileRebasing": "Невозможно повторить при перебазировании", - "MustStashWarning": "Вытаскивание исправления в индекс требует сохранения и распаковки ваших изменений. Если что-то пойдёт не так, можно получить доступ к файлам из хранилища. Продолжить?", - "MustStashTitle": "Необходимо припрятать", - "ConfirmationTitle": "Панель Подтверждения", - "PrevPage": "Предыдущая страница", - "NextPage": "Следующая страница", - "GotoTop": "Пролистать наверх", - "GotoBottom": "Прокрутить вниз", - "FilteringBy": "Фильтрация по", - "ResetInParentheses": "(сбросить)", - "OpenFilteringMenu": "Просмотреть параметры фильтрации по пути", - "FilterBy": "Фильтровать по", - "ExitFilterMode": "Прекратить фильтрацию по пути", - "FilterPathOption": "Введите путь для фильтрации", - "EnterFileName": "Введите путь:", - "FilteringMenuTitle": "Фильтрация", - "MustExitFilterModeTitle": "Команда недоступна", - "MustExitFilterModePrompt": "Команда недоступна в режиме фильтрации. Выйти из режима фильтрации?", - "Diff": "Разница", - "EnterRefToDiff": "Введите ссылку для сравнения", - "EnterRefName": "Введите ссылку:", - "ExitDiffMode": "Выйти из режима сравнения", - "DiffingMenuTitle": "Сравнение", - "SwapDiff": "Обратное направление сравнении", - "ViewDiffingOptions": "Открыть меню сравнении", - "OpenCommandLogMenu": "Открыть меню журнала команд", - "ShowingGitDiff": "Показывает вывод для:", - "CommitDiff": "Разница коммита", - "CopyCommitHashToClipboard": "Скопировать hash коммита в буфер обмена", - "CommitHash": "hash коммита", - "CommitURL": "URL коммита", - "CommitMessage": "Полное сообщение коммита", - "CommitSubject": "Тема коммита", - "CommitAuthor": "Автор коммита", - "CopyCommitAttributeToClipboard": "Скопировать атрибут коммита", - "CopyBranchNameToClipboard": "Скопировать название ветки в буфер обмена", - "CopyPathToClipboard": "Скопировать название файла в буфер обмена", - "CommitPrefixPatternError": "Ошибка в шаблоне commitPrefix", - "CopySelectedTextToClipboard": "Скопировать выделенный текст в буфер обмена", - "NoFilesStagedTitle": "Нет проиндексированных файлов", - "NoFilesStagedPrompt": "Нет проиндексированых файлов. Закоммитить все файлы?", - "BranchNotFoundTitle": "Ветка не найдена", - "BranchNotFoundPrompt": "Ветка не найден. Создайте новую ветку с названием", - "BranchUnknown": "Ветка неизвестна", - "DiscardChangeTitle": "Отменить изменение", - "DiscardChangePrompt": "Вы уверены, что хотите отменить это изменение (git reset)? Это необратимо.\nЧтобы отключить этот диалог, установите для конфигурационного ключа 'gui.skipDiscardChangeWarning' значение true.", - "CreateNewBranchFromCommit": "Создать новую ветку с этого коммита", - "BuildingPatch": "Сборка патча", - "ViewCommits": "Просмотреть коммиты", - "RunningCustomCommandStatus": "Запуск пользовательской команды", - "SubmoduleStashAndReset": "Спрятать непроиндексированные изменения подмодуля и обновить", - "AndResetSubmodules": "И сбросить подмодули", - "EnterSubmoduleTooltip": "Ввести подмодуль", - "CopySubmoduleNameToClipboard": "Скопировать название подмодуля в буфер обмена", - "RemoveSubmodule": "Удалить подмодуль", - "RemoveSubmodulePrompt": "Вы уверены, что хотите удалить подмодуль '%s' и соответствующий ему каталог? Это необратимо.", - "ResettingSubmoduleStatus": "Сброс подмодуля", - "NewSubmoduleName": "Названия нового подмодуля:", - "NewSubmoduleUrl": "URL нового подмодуля:", - "NewSubmodulePath": "Путь нового подмодуля:", - "NewSubmodule": "Добавить новый подмодуль", - "AddingSubmoduleStatus": "Добавление подмодуля", - "UpdateSubmoduleUrl": "Обновить URL подмодуля '%s'", - "UpdatingSubmoduleUrlStatus": "Обновление URL", - "EditSubmoduleUrl": "Обновить URL подмодуля", - "InitializingSubmoduleStatus": "Инициализация подмодуля", - "InitSubmoduleTooltip": "Инициализировать подмодуль", - "SubmoduleUpdateTooltip": "Обновить подмодуль", - "UpdatingSubmoduleStatus": "Обновление подмодуля", - "BulkInitSubmodules": "Массовая инициализация подмодулей", - "BulkUpdateSubmodules": "Массовое обновление подмодулей", - "BulkDeinitSubmodules": "Массовая деинициализация подмодулей", - "ViewBulkSubmoduleOptions": "Просмотреть параметры массового подмодуля", - "BulkSubmoduleOptions": "Параметры массового подмодуля", - "RunningCommand": "Выполнение команды", - "SubCommitsTitle": "Подкоммиты", - "SubmodulesTitle": "Подмодули", - "NavigationTitle": "Навигация по панели списка", - "SuggestionsCheatsheetTitle": "Подсказки", - "SuggestionsTitle": "Подсказки (нажмите %s, чтобы сфокусироваться)", - "ExtrasTitle": "Журнал команд", - "PullRequestURLCopiedToClipboard": "URL запроса на принятие изменений скопирован в буфер обмена", - "CommitDiffCopiedToClipboard": "Сравнения коммита скопированы в буфер обмена", - "CommitURLCopiedToClipboard": "URL коммита скопирован в буфер обмена", - "CommitMessageCopiedToClipboard": "Сообщение коммита скопировано в буфер обмена", - "CommitSubjectCopiedToClipboard": "Тема коммита скопирована в буфер обмена", - "CommitAuthorCopiedToClipboard": "Автор коммита скопирован в буфер обмена", - "PatchCopiedToClipboard": "Патч скопирован в буфер обмена", - "CopiedToClipboard": "Скопировано в буфер обмена", - "ErrCannotEditDirectory": "Невозможно редактировать каталог: вы можете редактировать только отдельные файлы", - "ErrStageDirWithInlineMergeConflicts": "Невозможно подготовить/удалить каталог, содержащий файлы со встроенными конфликтами слияния. Сначала устраните конфликты слияния", - "ErrRepositoryMovedOrDeleted": "Не могу найти репозиторий. Возможно, он был перемещён или удалён ¯\\_(ツ)_/¯", - "CommandLog": "Журнал команд", - "ToggleShowCommandLog": "Показать/скрыть журнал команд", - "FocusCommandLog": "Сфокусировать журнал команд", - "CommandLogHeader": "Вы можете скрыть/сфокусировать эту панель, нажав '%s'\n", - "RandomTip": "Случайный совет", - "ToggleWhitespaceInDiffView": "Переключить отображение изменении пробелов в просмотрщике сравнении", - "IgnoreWhitespaceDiffViewSubTitle": "(игнорирование пробелов)", - "IgnoreWhitespaceNotSupportedHere": "Игнорирование пробелов не поддерживается в этом представлении", - "IncreaseContextInDiffView": "Увеличить размер контекста, отображаемого вокруг изменений в просмотрщике сравнении", - "DecreaseContextInDiffView": "Уменьшите размер контекста, отображаемого вокруг изменений в просмотрщике сравнении", - "CreatePullRequestOptions": "Создать параметры запроса принятие изменений", - "DefaultBranch": "Ветка по-умолчанию", - "SelectBranch": "Выбрать ветку", - "CreatePullRequest": "Создать запрос на принятие изменений", - "SelectConfigFile": "Выбрать файл конфигурации", - "NoConfigFileFoundErr": "Файл конфигурации не найден", - "LoadingFileSuggestions": "Загрузка подсказок по файлам", - "LoadingCommits": "Загрузка коммитов", - "MustSpecifyOriginError": "Необходимо указать удалённый репозитории, если указываете ветку", - "GitOutput": "Вывод git:", - "GitCommandFailed": "Ошибка команды Git. Подробности смотрите в журнале команд (открыть с помощью %s)", - "AbortTitle": "Прервать %s", - "AbortPrompt": "Вы уверены, что хотите прервать текущий %s?", - "OpenLogMenu": "Открыть меню журнала", - "LogMenuTitle": "Параметры журнала коммитов", - "ToggleShowGitGraphAll": "Переключить отображение всего git графа (передать флаг --all в git log )", - "ShowGitGraph": "Показать git граф", - "SortOrder": "Порядок сортировки", - "SortAlphabetical": "По алфавиту", - "SortByDate": "По дате", - "SortCommits": "Упорядочить коммиты", - "CantChangeContextSizeError": "Невозможно изменить контекст в режиме создания патча, потому что мы были слишком ленивы, чтобы поддерживать его при выпуске функции. Если вы действительно этого хотите, пожалуйста, дайте нам знать!", - "OpenCommitInBrowser": "Открыть коммит в браузере", - "ViewBisectOptions": "Просмотреть параметры бинарного поиска", - "ConfirmRevertCommit": "Вы уверены, что хотите отменить {{.selectedCommit}}?", - "RewordInEditorTitle": "Перефразировать в редакторе", - "RewordInEditorPrompt": "Вы уверены, что хотите перефразировать этот коммит вашем редакторе?", - "HardResetAutostashPrompt": "Вы уверены, что хотите сделать жёсткий сброс на '%s'? При необходимости будет выполнен автосохранение в хранилище.", - "NukeDescription": "Если вы хотите, чтобы все изменения в рабочем дереве исчезли, это способ сделать это. Если есть какие-либо изменения подмодуля, эти изменения будут припрятаны в подмодуле(-ях).", - "DiscardStagedChangesDescription": "Это создаст новую запись в хранилище, содержащую только проиндексированные файлы, а затем удалит её, так что в рабочем дереве останутся только непроиндексированные изменения.", - "EmptyOutput": "<Пустой вывод>", - "Patch": "Патч", - "CustomPatch": "Пользовательский патч", - "CommitsCopied": "коммиты скопированы", - "CommitCopied": "коммит скопирован", - "ResetPatch": "Сбросить патч", - "ApplyPatch": "Применить патч", - "ApplyPatchInReverse": "Применить патч в обратном порядке", - "RemovePatchFromOriginalCommit": "Удалить патч из исходного коммита (%s)", - "MovePatchOutIntoIndex": "Переместить патч в индекс", - "MovePatchToSelectedCommit": "Переместить патч в выбранный коммит (%s)", - "CopyPatchToClipboard": "Скопировать патч в буфер обмена", - "NoMatchesFor": "Нет совпадений для '%s' %s", - "MatchesFor": "совпадений для '%s' (%d из %d) %s", - "SearchKeybindings": "%s: Следующее совпадение, %s: Предыдущее совпадение, %s: Выйти из режима поиска", - "SearchPrefix": "Поиск: ", - "ExitSearchMode": "%s: Выйти из режима поиска", - "ToggleRangeSelect": "Переключить выборку перетаскивания", - "Actions": { - "CheckoutCommit": "Переключить коммит", - "CheckoutTag": "Переключить тег", - "CheckoutBranch": "Переключить ветку", - "ForceCheckoutBranch": "Принудительное переключение ветки", - "Merge": "Слить", - "RebaseBranch": "Перебазировать ветку", - "RenameBranch": "Переименовать ветку", - "CreateBranch": "Создать ветку", - "FastForwardBranch": "Ветка перемотки вперёд", - "CherryPick": "(Cherry-pick) Вставить коммиты", - "CheckoutFile": "Переключить файл", - "SquashCommitDown": "Объединить несколько коммитов в один нижний", - "FixupCommit": "Объединить несколько коммитов в один, отбросив сообщение коммита", - "RewordCommit": "Перефразировать коммит", - "DropCommit": "Сбросить коммит", - "EditCommit": "Изменить коммит", - "AmendCommit": "Править коммит (amend)", - "ResetCommitAuthor": "Сброс автора коммита", - "SetCommitAuthor": "Установить автора коммита", - "RevertCommit": "Отменить коммит", - "CreateFixupCommit": "Создать fixup коммит", - "SquashAllAboveFixupCommits": "Объединить все выше fixup коммиты", - "MoveCommitUp": "Переместить коммит вверх", - "MoveCommitDown": "Переместить коммит вниз", - "CopyCommitMessageToClipboard": "Скопировать сообщение коммита в буфер обмена", - "CopyCommitSubjectToClipboard": "Скопировать тему коммита в буфер обмена", - "CopyCommitDiffToClipboard": "Скопировать сравнения коммита в буфер обмена", - "CopyCommitHashToClipboard": "Скопировать hash коммита в буфер обмена", - "CopyCommitURLToClipboard": "Скопировать URL коммита в буфер обмена", - "CopyCommitAuthorToClipboard": "Скопировать автора коммита в буфер обмена", - "CopyCommitAttributeToClipboard": "Скопировать в буфер обмена", - "CopyPatchToClipboard": "Скопировать патч в буфер обмена", - "CustomCommand": "Пользовательская команда", - "DiscardAllChangesInFile": "Отменить все изменения в файле", - "DiscardAllUnstagedChangesInFile": "Отменить все непроиндексированные изменения в файле", - "StageFile": "Проиндексировать файл", - "StageResolvedFiles": "Проиндексированные файлы, конфликты слияния которых были устранены", - "UnstageFile": "Непроиндексированные файл", - "UnstageAllFiles": "Удалить все файлы из индекса", - "StageAllFiles": "Проиндексировать все файлы", - "IgnoreExcludeFile": "Игнорировать или исключить файл", - "IgnoreFileErr": "Невозможно игнорировать .gitignore", - "ExcludeFile": "Исключить файл", - "ExcludeGitIgnoreErr": "Невозможно исключить .gitignore", - "Commit": "Коммит", - "Push": "Отправить изменения", - "Pull": "Получить и слить изменения", - "OpenFile": "Открыть файл", - "StashAllChanges": "Припрятать все изменения", - "StashAllChangesKeepIndex": "Припрятать все изменения и сохранить индекс", - "StashStagedChanges": "Припрятать проиндексированные изменения", - "StashUnstagedChanges": "Припрятать непроиндексированные изменения", - "StashIncludeUntrackedChanges": "Припрятать все изменения, включая неотслеживаемые файлы", - "GitFlowFinish": "Завершение Git-потока", - "GitFlowStart": "Запуск Git-потока", - "CopyToClipboard": "Скопировать в буфер обмена", - "CopySelectedTextToClipboard": "Скопировать выделенный текст в буфер обмена", - "RemovePatchFromCommit": "Удалить патч из коммита", - "MovePatchToSelectedCommit": "Переместить патч в выбранный коммит", - "MovePatchIntoIndex": "Переместите патч в индекс", - "MovePatchIntoNewCommit": "Переместить патч в новый коммит", - "DeleteRemoteBranch": "Удалить удалённую ветку", - "SetBranchUpstream": "Установить ветку как upstream", - "AddRemote": "Добавить удалённую ветку", - "RemoveRemote": "Удалить удалённую ветку", - "UpdateRemote": "Обновить удалённую ветку", - "ApplyPatch": "Применить патч", - "Stash": "Хранилище", - "RenameStash": "Переименовать хранилище", - "RemoveSubmodule": "Удалить подмодуль", - "ResetSubmodule": "Сброс подмодуля", - "AddSubmodule": "Добавить подмодуль", - "UpdateSubmoduleUrl": "Обновить URL подмодуля", - "InitialiseSubmodule": "Инициализация подмодуля", - "BulkInitialiseSubmodules": "Массовая инициализация подмодулей", - "BulkUpdateSubmodules": "Массовое обновление подмодулей", - "BulkDeinitialiseSubmodules": "Массовая деинициализация подмодулей", - "UpdateSubmodule": "Обновить подмодуль", - "CreateLightweightTag": "Создать легковесный тег", - "CreateAnnotatedTag": "Создать аннотированный тег", - "PushTag": "Отправить тег", - "NukeWorkingTree": "Уничтожить рабочее дерево", - "DiscardUnstagedFileChanges": "Отменить непроиндексированные изменения файла", - "RemoveUntrackedFiles": "Удалить неотслеживаемые файлы", - "RemoveStagedFiles": "Удалить проиндексированные файлы", - "SoftReset": "Мягкий сброс", - "MixedReset": "Смешанный сброс", - "HardReset": "Жёсткий сброс", - "Undo": "Отменить", - "Redo": "Повторить", - "CopyPullRequestURL": "Скопировать запрос на принятие изменений URL", - "OpenMergeTool": "Открыть инструмент слияния", - "OpenCommitInBrowser": "Открыть коммит в браузере", - "OpenPullRequest": "Открыть запрос на принятие изменений в браузера", - "StartBisect": "Начать бинарный поиск", - "ResetBisect": "Сбросить бинарный поиск", - "BisectSkip": "Пропустить бинарный поиск", - "BisectMark": "Отметить бинарный поиск" - }, - "Bisect": { - "MarkStart": "Отметить %s как %s (начать бинарный поиск)", - "ResetTitle": "Сбросить 'git bisect'", - "ResetPrompt": "Вы уверены, что хотите сбросить 'git bisect'?", - "ResetOption": "Сбросить бинарный поиск", - "BisectMenuTitle": "Бинарный поиск", - "Mark": "Отметить %s как %s", - "SkipCurrent": "Пропустить %s", - "CompleteTitle": "Бинарный поиск завершён", - "CompletePrompt": "Бинарный поиск завершён! Изменения внесённые следующим коммитом:\n\n%s\n\nСбросить 'git bisect' сейчас?", - "CompletePromptIndeterminate": "Бинарный поиск завершён! Некоторые коммиты были пропущены, поэтому любое из следующих коммитов могло внести изменения::\n\n%s\n\nСбросить 'git bisect' сейчас?", - "Bisecting": "Бинарный поиск" - }, - "Log": {}, - "BreakingChangesByVersion": {} -} diff --git a/pkg/i18n/translations/zh-CN.json b/pkg/i18n/translations/zh-CN.json deleted file mode 100644 index ef566c61068..00000000000 --- a/pkg/i18n/translations/zh-CN.json +++ /dev/null @@ -1,958 +0,0 @@ -{ - "NotEnoughSpace": "没有足够的空间来渲染面板", - "DiffTitle": "差异", - "FilesTitle": "文件", - "BranchesTitle": "分支", - "CommitsTitle": "提交", - "StashTitle": "贮藏", - "SnakeTitle": "贪吃蛇", - "EasterEgg": "彩蛋", - "UnstagedChanges": "未暂存变更", - "StagedChanges": "已暂存变更", - "StagingTitle": "正在暂存", - "MergingTitle": "正在合并", - "SquashMergeUncommittedTitle": "压缩合并并保持未提交状态", - "SquashMergeCommittedTitle": "压缩合并,然后提交", - "SquashMergeUncommitted": "将分支 ‘{{.selectedBranch}}’ 压缩合并到工作树中", - "SquashMergeCommitted": "将分支 '{{.selectedBranch}}' 压缩合并为单个提交,到 '{{.checkedOutBranch}}' 分支中。", - "RegularMergeTooltip": "将分支 '{{.selectedBranch}}' 合并到 '{{.checkedOutBranch}}'", - "NormalTitle": "正常", - "LogTitle": "日志", - "CommitSummary": "提交信息", - "CredentialsUsername": "用户名", - "CredentialsPassword": "密码", - "CredentialsPassphrase": "输入 SSH 密钥的密码", - "CredentialsPIN": "输入 SSH 密钥的 PIN", - "CredentialsToken": "输入 SSH 密钥令牌", - "PassUnameWrong": "密码 和/或 用户名错误", - "Commit": "提交变更", - "CommitTooltip": "提交暂存文件", - "AmendLastCommit": "修补最后一次提交", - "AmendLastCommitTitle": "修补最后一次提交", - "SureToAmend": "您确定要修补上一次提交吗?之后您可以从提交面板更改提交消息", - "NoCommitToAmend": "没有需要提交的修补", - "CommitChangesWithEditor": "使用 Git 编辑器提交变更", - "FindBaseCommitForFixup": "找到用于修复的基准提交", - "FindBaseCommitForFixupTooltip": "找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: ", - "NoBaseCommitsFound": "没有找到基础提交", - "MultipleBaseCommitsFoundStaged": "找到了多个基础提交 (尝试一次暂存更少的变更)", - "MultipleBaseCommitsFoundUnstaged": "找到了多个基础提交 (尝试暂存部分变更)", - "BaseCommitIsAlreadyOnMainBranch": "此变更内容所在的提交已经存在于主分支上了", - "BaseCommitIsNotInCurrentView": "基础提交不在当前视图中", - "HunksWithOnlyAddedLinesWarning": "差异中仅包含添加行,小心检查这些是否属于已找到的主提交。\n\n是否继续?", - "StatusTitle": "状态", - "GlobalTitle": "全局键绑定", - "Execute": "执行", - "Stage": "切换暂存状态", - "StageTooltip": "为选定的文件切换暂存状态", - "ToggleStagedAll": "切换所有文件的暂存状态", - "ToggleStagedAllTooltip": "切换工作区中所有文件的已暂存/未暂存状态", - "ToggleTreeView": "切换文件树视图", - "OpenDiffTool": "使用外部差异比较工具(git difftool)", - "OpenMergeTool": "打开外部合并工具(git mergetool)", - "OpenMergeToolTooltip": "执行 `git mergetool`.", - "Refresh": "刷新", - "RefreshTooltip": "刷新git状态(即在后台上运行`git status`,`git branch`等命令以更新面板内容) 不会运行`git fetch`", - "Push": "推送", - "Pull": "拉取", - "PushTooltip": "推送当前分支到它的上游。如果上游未配置,您可以在弹窗中配置上游分支。", - "PullTooltip": "从当前分支的远程分支获取改动。如果上游未配置,您可以在弹窗中配置上游分支。", - "FileFilter": "通过状态过滤文件", - "CopyToClipboardMenu": "复制到剪贴板", - "CopyFileName": "文件名", - "CopyRelativeFilePath": "相对路径", - "CopyAbsoluteFilePath": "绝对路径", - "CopyFileDiffTooltip": "如果存在已暂存更改,该命令将仅作用于它们。否则将作用于所有未暂存更改。", - "CopySelectedDiff": "比较选中的文件", - "CopyAllFilesDiff": "对比全部文件", - "CopyFileContent": "所选文件内容", - "NoContentToCopyError": "无可复制内容", - "FileNameCopiedToast": "文件名已复制至剪贴板", - "FilePathCopiedToast": "文件路径已复制至剪贴板", - "FileDiffCopiedToast": "文件差异已复制至剪贴板", - "AllFilesDiffCopiedToast": "全部文件差异已复制至剪贴板", - "FileContentCopiedToast": "文件内容已复制到剪贴板", - "FilterStagedFiles": "仅显示已暂存文件", - "FilterUnstagedFiles": "仅显示未暂存文件", - "FilterTrackedFiles": "仅显示已跟踪的文件", - "FilterUntrackedFiles": "仅显示未跟踪的文件", - "NoFilter": "无过滤", - "FilterLabelStagedFiles": "(仅暂存)", - "FilterLabelUnstagedFiles": "(仅未暂存)", - "FilterLabelTrackedFiles": "(仅跟踪)", - "FilterLabelUntrackedFiles": "(仅未跟踪)", - "FilterLabelConflictingFiles": "(仅冲突)", - "MergeConflictsTitle": "合并冲突", - "MergeConflictDescription_DD": "冲突:当前变更和传入变更都移动或重命名了此文件,但目标位置不同。虽然不知道具体位置,但这两个目标文件应该都会显示为冲突(分别标记为'AU'和'UA')。最可能的解决方法是删除此文件,并选择其中一个目标位置,删除另一个。", - "MergeConflictDescription_AU": "冲突:此文件是当前变更中移动或重命名的目标位置,但在传入变更中被移动或重命名到其他位置。另一个目标位置也应显示为冲突(标记为'UA'),同时两个重命名前的原文件也会显示为冲突(标记为'DD')。", - "MergeConflictDescription_UA": "冲突:此文件是传入变更中移动或重命名的目标位置,但在当前变更中被移动或重命名到其他位置。另一个目标位置也应显示为冲突(标记为'AU'),同时两个重命名前的原文件也会显示为冲突(标记为'DD')。", - "MergeConflictDescription_DU": "冲突:当前变更删除了此文件,而传入变更修改了此文件。\n\n最可能的解决方法是手动将传入的修改应用到代码其他位置后再删除该文件。", - "MergeConflictDescription_UD": "冲突:当前变更修改了此文件,而传入变更删除了此文件。\n\n最可能的解决方法是手动将当前修改应用到代码其他位置后再删除该文件。", - "MergeConflictIncomingDiff": "传入变更:", - "MergeConflictCurrentDiff": "当前变更:", - "MergeConflictPressEnterToResolve": "按%s键解决。", - "MergeConflictKeepFile": "保留文件", - "MergeConflictDeleteFile": "删除文件", - "Checkout": "检出", - "CheckoutTooltip": "检出选中的项目", - "CantCheckoutBranchWhilePulling": "当前分支在拉取远端时,无法检出到其他分支。", - "TagCheckoutTooltip": "检出选择的标签作为分离的HEAD", - "RemoteBranchCheckoutTooltip": "基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。", - "CantPullOrPushSameBranchTwice": "在推送或拉取分支的过程中,您不能再次推送或拉取同一个分支", - "NoChangedFiles": "没有变更的文件", - "SoftReset": "软重置", - "AlreadyCheckedOutBranch": "您已经检出至此分支", - "SureForceCheckout": "您确定要强制检出吗?您将丢失所有本地变更", - "ForceCheckoutBranch": "强制检出分支", - "BranchName": "分支名称", - "NewBranchNameBranchOff": "新分支名称(基于 {{.branchName}})", - "CantDeleteCheckOutBranch": "您不能删除已检出的分支!", - "DeleteBranchTitle": "删除分支'{{.selectedBranchName}}'?", - "DeleteBranchesTitle": "删除选定的分支?", - "DeleteLocalBranch": "删除本地分支", - "DeleteLocalBranches": "删除本地分支", - "DeleteRemoteBranchPrompt": "您确定要从'{{.upstream}}'中删除远程分支'{{.selectedBranchName}}'?", - "DeleteRemoteBranchesPrompt": "确定要从各自远程仓库删除所选分支的远程分支吗?", - "DeleteLocalAndRemoteBranchPrompt": "确定要同时删除本地的'{{.localBranchName}}'分支和远程'{{.remoteName}}'上的'{{.remoteBranchName}}'分支吗?", - "DeleteLocalAndRemoteBranchesPrompt": "确定要同时从本地删除所选分支,并从各自远程仓库删除对应的远程分支吗?", - "ForceDeleteBranchTitle": "强制删除分支", - "ForceDeleteBranchMessage": "{{.selectedBranchName}} 还没有被完全合并。您确定要删除它吗?", - "ForceDeleteBranchesMessage": "部分所选分支尚未完全合并。确定要删除它们吗?", - "RebaseBranch": "变基", - "RebaseBranchTooltip": "将检出的分支变基到所选的分支上。", - "CantRebaseOntoSelf": "您不能将分支变基到其自身", - "CantMergeBranchIntoItself": "您不能将分支合并到其自身", - "ForceCheckout": "强制检出", - "ForceCheckoutTooltip": "强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。", - "CheckoutByName": "按名称检出", - "CheckoutByNameTooltip": "按名称检出。在输入框中,您可以输入'-' 来切换到最后一个分支。", - "RemoteBranchCheckoutTitle": "检出 {{.branchName}}", - "RemoteBranchCheckoutPrompt": "您希望已什么方式检出到该分支?", - "CheckoutTypeNewBranch": "新建本地分支", - "CheckoutTypeNewBranchTooltip": "检出远程分支到本地,并跟踪它。", - "CheckoutTypeDetachedHead": "分离HEAD", - "CheckoutTypeDetachedHeadTooltip": "将远程分支检出作为分离HEAD,如果您只想用来测试而不是正式使用,这会很有用。您之后仍可以根据它创建本地分支。", - "NewBranch": "新分支", - "NewBranchFromStashTooltip": "从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。", - "NoBranchesThisRepo": "此仓库中没有分支", - "CommitWithoutMessageErr": "您必须编写提交消息才能进行提交", - "Close": "关闭", - "CloseCancel": "关闭", - "Confirm": "确认", - "Quit": "退出", - "SquashTooltip": "将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。", - "CannotSquashOrFixupFirstCommit": "下面没有可以压缩的提交", - "CannotSquashOrFixupMergeCommit": "无法对合并提交进行压缩或修正", - "Fixup": "修正 (fixup)", - "FixupTooltip": "将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。", - "SureFixupThisCommit": "您确定要“修正”此提交吗?它将合并到下面的提交中", - "SureSquashThisCommit": "您确定要将这个提交压缩到下面的提交中吗?", - "Squash": "压缩(Squash)", - "PickCommitTooltip": "标记选中的提交为 picked(变基过程中)。这意味该提交将在后续的变基中保留。", - "Pick": "拣选(Pick)", - "Edit": "编辑", - "Revert": "撤销(Revert)", - "RevertCommitTooltip": "为所选提交创建还原提交,这会反向应用所选提交的更改。", - "Reword": "改写提交", - "CommitRewordTooltip": "重写所选提交的消息。", - "DropCommit": "删除提交", - "DropCommitTooltip": "删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。", - "MoveDownCommit": "下移提交", - "MoveUpCommit": "上移提交", - "CannotMoveAnyFurther": "无法进一步移动", - "CannotMoveMergeCommit": "无法移动合并提交", - "EditCommit": "编辑(开始交互式变基)", - "EditCommitTooltip": "编辑提交", - "AmendCommitTooltip": "用已暂存的变更来修补提交", - "Amend": "修补(Amend)", - "ResetAuthor": "重置作者", - "ResetAuthorTooltip": "将提交作者重置为当前配置的用户。这也将更新作者的时间戳", - "SetAuthor": "设置作者", - "SetAuthorTooltip": "基于提示设置作者", - "AddCoAuthor": "添加共同作者", - "AmendCommitAttribute": "修补提交属性", - "AmendCommitAttributeTooltip": "设置或重置提交的作者,或添加其他作者。", - "SetAuthorPromptTitle": "设置作者(格式为 'Name ')", - "AddCoAuthorPromptTitle": "添加共同作者(格式为 'Name ')", - "AddCoAuthorTooltip": "添加共同作者 使用GitHub/GitLab元数据共同作者(Co-authored-by)", - "RewordCommitEditor": "使用编辑器重命名提交", - "NoCommitsThisBranch": "该分支没有提交", - "UpdateRefHere": "更新分支到 '{{.ref}}'", - "ExecCommandHere": "在这里执行以下命令:", - "Error": "错误", - "Undo": "撤销", - "UndoReflog": "撤销", - "RedoReflog": "重做", - "UndoTooltip": "Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改,只考虑提交。", - "RedoTooltip": "Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改,只考虑提交。", - "UndoMergeResolveTooltip": "撤消上次合并冲突解决", - "DiscardAllTooltip": "丢弃'{{.path}}'中已暂存和未暂存的变更", - "DiscardUnstagedTooltip": "丢弃'{{.path}}'中未暂存的变更", - "DiscardUnstagedDisabled": "选中的项目既没有已暂存的变更也没有未暂存的变更", - "Pop": "应用并删除", - "StashPopTooltip": "将存储项应用到工作目录并删除存储项。", - "Drop": "删除", - "StashDropTooltip": "从贮藏列表中删除该贮藏项", - "Apply": "应用", - "StashApplyTooltip": "将贮藏项应用到您的工作目录。", - "NoStashEntries": "没有贮藏条目", - "StashDrop": "删除贮藏", - "SureDropStashEntry": "确定要删除选中的储藏条目吗?", - "StashPop": "应用并删除贮藏", - "SurePopStashEntry": "您确定要应用并删除此贮藏条目吗?", - "StashApply": "应用贮藏", - "SureApplyStashEntry": "您确定要应用此贮藏条目?", - "NoTrackedStagedFilesStash": "没有可以贮藏的已跟踪/暂存文件", - "NoFilesToStash": "没有需要贮藏的文件", - "StashChanges": "贮藏变更", - "RenameStash": "重命名贮藏", - "RenameStashPrompt": "重命名贮藏: {{.stashName}}", - "OpenConfig": "打开配置文件", - "EditConfig": "编辑配置文件", - "ForcePush": "强制推送", - "ForcePushPrompt": "您的分支已与远程分支不同。按‘esc’取消,或‘enter’强制推送.", - "ForcePushDisabled": "您的分支已与远程分支不同, 并且您已经禁用了强行推送", - "UpdatesRejected": "更新被拒绝。在下次推送前,请先抓取并检查远程分支。", - "UpdatesRejectedAndForcePushDisabled": "更新被拒绝,您已禁用强制推送", - "CheckForUpdate": "检查更新", - "CheckingForUpdates": "正在检查更新…", - "UpdateAvailableTitle": "有可用更新!", - "UpdateAvailable": "下载并安装版本 {{.newVersion}}?", - "UpdateInProgressWaitingStatus": "更新中", - "UpdateCompletedTitle": "更新完成!", - "UpdateCompleted": "已成功安装更新,重新启动 lazygit 使其生效。", - "FailedToRetrieveLatestVersionErr": "检索版本信息失败", - "OnLatestVersionErr": "已是最新版本", - "MajorVersionErr": "新版本({{.newVersion}})与当前版本({{.currentVersion}})相比,具有非向后兼容的更改", - "CouldNotFindBinaryErr": "在 {{.url}} 处找不到任何二进制文件", - "UpdateFailedErr": "更新失败: {{.errMessage}}", - "ConfirmQuitDuringUpdateTitle": "当前正在更新中...", - "ConfirmQuitDuringUpdate": "当前正在更新中,您确定要退出吗?", - "MergeToolTitle": "合并工具", - "MergeToolPrompt": "确定要打开 `git mergetool` 吗?", - "NonReloadableConfigWarningTitle": "配置已更改", - "NonReloadableConfigWarning": "以下配置设置已更改,但更改不会立即生效。请退出并重新启动lazygit以使更改生效:\n\n{{configs}}", - "GitconfigParseErr": "由于存在未加引号的'\\'字符,因此 Gogit 无法解析您的 gitconfig 文件。删除它们应该可以解决问题。", - "EditFile": "编辑文件", - "EditFileTooltip": "使用外部编辑器打开文件", - "OpenFile": "打开文件", - "OpenFileTooltip": "使用默认程序打开该文件", - "OpenInEditor": "在编辑器中编写", - "IgnoreFile": "添加到 .gitignore", - "ExcludeFile": "添加到 .git/info/exclude", - "RefreshFiles": "刷新文件", - "Merge": "合并到当前检出的分支", - "RegularMerge": "常规合并", - "MergeBranchTooltip": "Merge selected branch into currently checked out branch.", - "ConfirmQuit": "您确定要退出吗?", - "SwitchRepo": "切换到最近的仓库", - "AllBranchesLogGraph": "显示/循环所有分支日志", - "UnsupportedGitService": "不支持的 git 服务", - "CopyPullRequestURL": "复制拉取请求 URL 到剪贴板", - "NoBranchOnRemote": "该分支在远程上不存在. 您需要先将其推送到远程.", - "Fetch": "抓取", - "FetchTooltip": "从远程获取变更", - "CollapseAll": "折叠全部文件", - "CollapseAllTooltip": "折叠文件树中的全部目录", - "ExpandAll": "展开全部文件", - "ExpandAllTooltip": "展开文件树中的全部目录", - "DisabledInFlatView": "平面视图中不可用", - "FileEnter": "暂存单个 块/行 用于文件, 或 折叠/展开 目录", - "FileEnterTooltip": "如果选中的是一个文件,则会进入到暂存视图,以便可以暂存单个代码块/行。如果选中的是一个目录,则会折叠/展开这个目录", - "StageSelectionTooltip": "切换行暂存状态", - "DiscardSelection": "取消变更(git reset)", - "DiscardSelectionTooltip": "当选择未暂存的变更时,使用git reset丢弃该变更。当选择已暂存的变更时,取消暂存该变更", - "ToggleSelectionForPatch": "添加/移除 行到补丁", - "EditHunk": "编辑代码块", - "EditHunkTooltip": "在外部编辑器中编辑选中的代码块", - "ToggleStagingView": "切换到其他面板", - "ToggleStagingViewTooltip": "切换到其他视图(已暂存/未暂存的变更)", - "ReturnToFilesPanel": "返回文件面板", - "FastForward": "从上游快进此分支", - "FastForwardTooltip": "将当前分支直接移动到远程追踪分支的最新提交", - "FastForwarding": "抓取并快进", - "FoundConflictsTitle": "自动合并失败", - "ViewConflictsMenuItem": "查看冲突", - "AbortMenuItem": "中止 %s", - "PickHunk": "选中区块", - "PickAllHunks": "选中所有区块", - "ViewMergeRebaseOptions": "查看合并/变基选项", - "ViewMergeRebaseOptionsTooltip": "查看当前合并或变基的中止、继续、跳过选项", - "ViewMergeOptions": "查看合并选项", - "ViewRebaseOptions": "查看变基选项", - "NotMergingOrRebasing": "您目前既不进行变基也不进行合并", - "AlreadyRebasing": "在变基时无法执行此操作", - "RecentRepos": "最近的仓库", - "MergeOptionsTitle": "合并选项", - "RebaseOptionsTitle": "变基选项", - "CommitSummaryTitle": "提交信息", - "CommitDescriptionTitle": "提交信息说明", - "CommitDescriptionSubTitle": "按 {{.togglePanelKeyBinding}} 键切换焦点, {{.commitMenuKeybinding}} 打开菜单", - "CommitHooksDisabledSubTitle": "(钩子已禁用)", - "LocalBranchesTitle": "本地分支", - "SearchTitle": "搜索", - "TagsTitle": "标签", - "MenuTitle": "菜单", - "CommitMenuTitle": "提交 菜单", - "RemotesTitle": "远程", - "RemoteBranchesTitle": "远程分支", - "PatchBuildingTitle": "构建补丁中", - "InformationTitle": "信息", - "SecondaryTitle": "次要", - "ReflogCommitsTitle": "Reflog", - "Continue": "继续", - "UnstagedFilesAfterConflictsResolved": "冲突解决后文件已被修改。是否自动暂存并继续?", - "RebasingTitle": "变基 '{{.checkedOutBranch}}'", - "RebasingFromBaseCommitTitle": "从标记的几点变基'{{.checkedOutBranch}}'", - "SimpleRebase": "简单变基到 '{{.ref}}'", - "InteractiveRebase": "交互式变基到 '{{.ref}}'", - "RebaseOntoBaseBranch": "变基到主分支 ({{.baseBranch}})", - "InteractiveRebaseTooltip": "由于交互式变基被中断,所以您可以在继续变基之前更新TODO提交。", - "RebaseOntoBaseBranchTooltip": "将已检出的分支变基到主分支上(例如最近的主分支)。", - "MustSelectTodoCommits": "在变基过程中, 该操作仅在选中TODO提交时有效。", - "FwdNoUpstream": "此分支没有上游,无法快进", - "FwdNoLocalUpstream": "此分支的远程未在本地注册,无法快进", - "FwdCommitsToPush": "此分支带有尚未推送的提交,无法快进", - "PullRequestNoUpstream": "没有设置上游的分支无法执行拉取请求", - "ErrorOccurred": "发生错误!请在以下位置创建 issue", - "YouDied": "您死了!", - "RewordNotSupported": "当前不支持交互式重新基准化时的重新措词提交", - "ChangingThisActionIsNotAllowed": "不允许更改这类变基待办项目", - "DroppingMergeRequiresSingleSelection": "删除合并提交需要单个选中项", - "CherryPickCopy": "复制提交(拣选)", - "CherryPickCopyTooltip": "标记提交为已复制。然后,在本地提交视图中,您可以按 `{{.paste}}` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `{{.escape}}` 来取消选择。", - "PasteCommits": "粘贴提交(拣选)", - "SureCherryPick": "确定要将复制的{{.numCommits}}个提交拣选到该分支上吗?", - "CherryPick": "拣选(Cherry-Pick)", - "CannotCherryPickNonCommit": "无法拣选TODO类型的提交", - "Donate": "捐助", - "AskQuestion": "提问咨询", - "PrevHunk": "选择上一个区块", - "NextHunk": "选择下一个区块", - "PrevConflict": "选择上一个冲突", - "NextConflict": "选择下一个冲突", - "SelectPrevHunk": "选择顶部块", - "SelectNextHunk": "选择底部块", - "ScrollDown": "向下滚动", - "ScrollUp": "向上滚动", - "ScrollUpMainWindow": "向上滚动主面板", - "ScrollDownMainWindow": "向下滚动主面板", - "AmendCommitTitle": "修补(amend)提交", - "AmendCommitPrompt": "您确定要使用暂存文件来修补此提交吗?", - "AmendCommitWithConflictsMenuPrompt": "警告:您即将使用已解决的冲突来修正上一个已完成的提交。此时这样做很可能并非您所期望的。更可能的情况是,您只是想继续执行变基操作。\n\n您仍然想要修正前一个提交吗?", - "AmendCommitWithConflictsContinue": "否,继续变基", - "AmendCommitWithConflictsAmend": "是,修正上一个提交", - "DropCommitTitle": "删除提交", - "DropCommitPrompt": "您确定要删除此提交吗?", - "DropUpdateRefPrompt": "您确定要删除选定的 update-ref 待办事项吗?除非中止变基,否则这是不可逆转的。", - "DropMergeCommitPrompt": "确定要删除选中的合并提交吗?注意:这将同时删除通过该合并提交引入的所有提交。", - "PullingStatus": "正在拉取", - "PushingStatus": "正在推送", - "FetchingStatus": "正在抓取", - "SquashingStatus": "正在压缩", - "FixingStatus": "正在修正", - "DeletingStatus": "正在删除", - "DroppingStatus": "删除中...", - "MovingStatus": "正在移动", - "RebasingStatus": "正在变基", - "MergingStatus": "合并中...", - "LowercaseRebasingStatus": "变基中...", - "LowercaseMergingStatus": "合并中...", - "AmendingStatus": "正在修补", - "CherryPickingStatus": "正在拣选", - "UndoingStatus": "正在撤销", - "RedoingStatus": "正在重做", - "CheckingOutStatus": "正在检出", - "CommittingStatus": "正在提交", - "RewordingStatus": "修改提交信息", - "RevertingStatus": "还原中...", - "CreatingFixupCommitStatus": "正在创建一个修复提交", - "CommitFiles": "提交文件", - "SubCommitsDynamicTitle": "提交 (%s)", - "CommitFilesDynamicTitle": "比较文件差异 (%s)", - "RemoteBranchesDynamicTitle": "远程分支 (%s)", - "ViewItemFiles": "查看提交的文件", - "CommitFilesTitle": "提交文件", - "CheckoutCommitFileTooltip": "检出文件", - "CanOnlyDiscardFromLocalCommits": "只能从本地提交中丢弃更改", - "Remove": "删除", - "DiscardOldFileChangeTooltip": "放弃对此文件的提交变更", - "DiscardFileChangesTitle": "放弃文件变更", - "DiscardFileChangesPrompt": "您确定要舍弃此提交对该文件的变更吗?如果此文件是在此提交中创建的,它将被删除", - "DisabledForGPG": "使用GPG的用户无法使用此功能。\n\n如果您正在使用密码代理(如gpg-agent)以避免每次签名时输入密码,可以通过在lazygit配置文件中添加\n\ngit:\n overrideGpg: true\n\n来启用此功能。", - "BareRepo": "您已经尝试在空仓库中打开Lazygit,但是Lazygit还不支持空仓库。打开最近的仓库吗?(y / n) ", - "InitialBranch": "分支名称? (git的默认值为空): ", - "NoRecentRepositories": "必须在git存储库中打开lazygit。没有有效的最近存储库。即将退出...", - "IncorrectNotARepository": "'notARepository'的值不正确。它应该是“prompt”,“create”,“skip”或“quit”中的一个。", - "AutoStashTitle": "自动存储?", - "AutoStashPrompt": "您必须隐藏并弹出变更以使变更生效。自动执行?(enter/esc)", - "Discard": "查看'放弃变更'选项", - "DiscardChangesTitle": "放弃变更", - "DiscardFileChangesTooltip": "查看选中文件的放弃变更选项", - "Cancel": "取消", - "DiscardAllChanges": "放弃所有变更", - "DiscardUnstagedChanges": "放弃未暂存的变更", - "DiscardAllChangesToAllFiles": "清空工作区", - "DiscardAnyUnstagedChanges": "丢弃未暂存的变更", - "DiscardUntrackedFiles": "丢弃未跟踪的文件", - "DiscardStagedChanges": "丢弃已暂存的变更", - "HardReset": "硬重置", - "BranchDeleteTooltip": "查看本地/远程分支的删除选项", - "TagDeleteTooltip": "查看本地/远程标签的删除选项", - "Delete": "删除", - "Reset": "重置", - "ResetTooltip": "查看重置选项 (soft/mixed/hard) 用于重置到选择项", - "ViewResetOptions": "查看重置选项", - "FileResetOptionsTooltip": "查看工作树的重置选项(例如:清除工作树)。", - "CreateFixupCommit": "为此提交创建修正", - "CreateFixupCommitTooltip": "创建修正提交", - "CreateAmendCommit": "创建 'amend!' 提交", - "FixupMenu_Fixup": "修复提交", - "FixupMenu_FixupTooltip": "允许您修复另一个提交并保持原本的提交信息", - "FixupMenu_AmendWithChanges": "基于当前变动内容修改提交", - "FixupMenu_AmendWithChangesTooltip": "允许您修复另一个提交并修改提交信息", - "FixupMenu_AmendWithoutChanges": "修改提交(不含变动内容,类似reword)", - "FixupMenu_AmendWithoutChangesTooltip": "允许您修改另一个提交的提交消息而不更改其内容", - "SquashAboveCommitsTooltip": "压缩所选提交之上或当前分支的所有 “fixup!” 提交(自动压缩)。", - "SquashCommitsAboveSelectedTooltip": "压缩当前选中提交下的所有修复提交(自动压缩)", - "SquashCommitsInCurrentBranchTooltip": "压缩当前分支中的所有修复提交(自动压缩)", - "SquashAboveCommits": "应用该修复提交", - "SquashCommitsInCurrentBranch": "在当前分支", - "SquashCommitsAboveSelectedCommit": "在选定提交之上", - "CannotSquashCommitsInCurrentBranch": "在当前分支中无法压缩提交:因为分离HEAD提交是一个合并提交或者已经存在于主分支中", - "ExecuteShellCommand": "执行 Shell 命令", - "ExecuteShellCommandTooltip": "调出可输入shell命令执行的提示符。", - "ShellCommand": "Shell 命令:", - "CommitChangesWithoutHook": "提交变更而无需预先提交钩子", - "ResetTo": "重置为", - "ResetSoftTooltip": "将 HEAD 重置为所选提交,并将当前提交和所选提交之间的更改保留为已暂存更改。", - "ResetMixedTooltip": "将 HEAD 重置为所选提交,并将当前提交和所选提交之间的更改保留为未暂存的更改。", - "ResetHardTooltip": "将 HEAD 重置为所选提交,并丢弃当前提交和所选提交之间的所有更改,以及工作树中的所有当前修改。", - "PressEnterToReturn": "按下 Enter 键返回 lazygit", - "ViewStashOptions": "查看贮藏选项", - "ViewStashOptionsTooltip": "查看贮藏选项(例如:贮藏所有、贮藏已暂存变更、贮藏未暂存变更)", - "Stash": "贮藏", - "StashTooltip": "贮藏所有变更.若要使用其他贮藏变体,请使用查看贮藏选项快捷键", - "StashAllChanges": "将所有变更加入贮藏", - "StashStagedChanges": "贮藏已暂存变更", - "StashAllChangesKeepIndex": "将已暂存的变更加入贮藏", - "StashUnstagedChanges": "贮藏未暂存变更", - "StashIncludeUntrackedChanges": "贮藏所有变更,包括未跟踪的文件", - "StashOptions": "贮藏选项", - "NotARepository": "错误:必须在 git 仓库中运行", - "WorkingDirectoryDoesNotExist": "错误:当前工作目录不存在", - "ScrollLeft": "向左滚动", - "ScrollRight": "向右滚动", - "DiscardPatch": "丢弃补丁", - "DiscardPatchConfirm": "您一次只能通过一个提交或贮藏条目构建补丁。需要放弃当前补丁吗?", - "CantPatchWhileRebasingError": "处于合并或变基状态时,您无法构建修补程序或运行修补程序命令", - "ToggleAddToPatch": "补丁中包含的切换文件", - "ToggleAddToPatchTooltip": "切换文件是否包含在自定义补丁中。请参阅 {{.doc}}。", - "ToggleAllInPatch": "操作所有文件", - "ToggleAllInPatchTooltip": "添加或删除所有提交中的文件到自定义的补丁中。请参阅 {{.doc}}。", - "UpdatingPatch": "正在更新补丁", - "ViewPatchOptions": "查看自定义补丁选项", - "PatchOptionsTitle": "补丁选项", - "NoPatchError": "尚未创建补丁。您可以在提交中的文件上按下“空格”或使用“回车”添加其中的特定行以开始构建补丁", - "EmptyPatchError": "补丁还是空的。首先将一些文件或行添加到您的补丁中。", - "EnterCommitFile": "输入文件以将所选行添加到补丁中(或切换目录折叠)", - "EnterCommitFileTooltip": "如果已选择一个文件,则Enter进入该文件,以便您可以向自定义补丁添加/删除单独的行。如果选择了目录,则切换目录。", - "ExitCustomPatchBuilder": "退出逐行模式", - "EnterUpstream": "以这种格式输入上游:'<远程仓库> <分支名称>'", - "InvalidUpstream": "上游格式无效,格式应当为:' '", - "NewRemote": "添加新的远程仓库", - "NewRemoteName": "新远程仓库名称:", - "NewRemoteUrl": "新远程仓库 URL:", - "ViewBranches": "查看分支", - "EditRemoteName": "输入远程仓库 {{.remoteName}} 的新名称:", - "EditRemoteUrl": "输入远程仓库 {{.remoteName}} 的新 URL:", - "RemoveRemote": "删除远程", - "RemoveRemoteTooltip": "删除选中的远程。从远程跟踪远程分支的任何本地分支都不会受到影响。", - "RemoveRemotePrompt": "确定要删除远程仓库吗?", - "DeleteRemoteBranch": "删除远程分支", - "DeleteRemoteBranches": "删除原创分支", - "DeleteRemoteBranchTooltip": "从远程删除远程分支。", - "DeleteLocalAndRemoteBranch": "删除本地和远程分支", - "DeleteLocalAndRemoteBranches": "删除本地和远程分支", - "SetAsUpstream": "设置为上游", - "SetAsUpstreamTooltip": "设置为检出分支的上游", - "SetUpstream": "设置为检出分支的上游", - "UnsetUpstream": "取消已选分支的上游", - "ViewDivergenceFromUpstream": "查看与上游的差异", - "ViewDivergenceFromBaseBranch": "查看主分支({{.baseBranch}})与上游的差异", - "CouldNotDetermineBaseBranch": "无法确定主分支", - "DivergenceSectionHeaderLocal": "本地", - "DivergenceSectionHeaderRemote": "远端", - "ViewUpstreamResetOptions": "重置已检出的分支到上游{{.upstream}}", - "ViewUpstreamResetOptionsTooltip": "查看用于将检出分支重置到 {{upstream}} 的选项。注意:这不会将选定的分支重置到上游,而是将检出的分支重置到上游。", - "ViewUpstreamRebaseOptions": "将检出分支的上游重新设置为 {{.upstream}}", - "ViewUpstreamRebaseOptionsTooltip": "查看将检出分支变基到 {{upstream}} 的选项。注意:这不会将所选分支重新设置为上游,而是将检出分支重新设置为上游。", - "UpstreamGenericName": "所选分支的上游", - "SetUpstreamTitle": "设置上游分支", - "SetUpstreamMessage": "确定要将'{{.checkedOut}}'的上游分支设置为'{{.selected}}'吗?", - "EditRemoteTooltip": "编辑远程仓库", - "TagCommit": "标签提交", - "TagCommitTooltip": "创建一个新标签指向所选提交。您可以在弹窗中输入标签名称和描述(可选)。", - "TagNameTitle": "标签名称", - "TagMessageTitle": "标签消息", - "LightweightTag": "轻量标签", - "AnnotatedTag": "附注标签", - "DeleteTagTitle": "要删除 '{{.tagName}}' 标签?", - "DeleteLocalTag": "删除本地标签", - "DeleteRemoteTag": "删除远程标签", - "DeleteLocalAndRemoteTag": "删除本地和远程标签", - "SelectRemoteTagUpstream": "被删除标签'{{.tagName}}'的远端", - "DeleteRemoteTagPrompt": "您确定要从'{{.upstream}}'中删除远程标签'{{.tagName}}'吗?", - "DeleteLocalAndRemoteTagPrompt": "确定要从本地和'{{.upstream}}'远程删除标签'{{.tagName}}'吗?", - "RemoteTagDeletedMessage": "远程标签已删除", - "PushTagTitle": "将 {{.tagName}} 推送到远程仓库:", - "PushTag": "推送标签", - "PushTagTooltip": "推送选择的标签到远端。您将在弹窗中选择一个远端。", - "NewTag": "创建标签", - "NewTagTooltip": "基于当前提交创建一个新标签。您将在弹窗中输入标签名称和描述(可选)。", - "CreatingTag": "创建标签", - "ForceTag": "强制标记标签", - "ForceTagPrompt": "该标签‘{{.tagName}}’已存在。请按{{.cancelKey}}取消,或者按{{.confirmKey}}覆盖它。", - "FetchRemoteTooltip": "抓取远程仓库", - "CheckoutCommitTooltip": "检出所选择的提交作为分离HEAD。", - "NoBranchesFoundAtCommitTooltip": "在选定的提交处未找到分支。", - "GitFlowOptions": "显示 git-flow 选项", - "NotAGitFlowBranch": "这似乎不是 git flow 分支", - "NewBranchNamePrompt": "输入分支的新名称", - "IgnoreTracked": "忽略跟踪文件", - "ExcludeTracked": "排除跟踪文件", - "IgnoreTrackedPrompt": "您确定要忽略已跟踪的文件吗?", - "ExcludeTrackedPrompt": "您确定要排除已跟踪的文件吗?", - "ViewResetToUpstreamOptions": "查看上游重置选项", - "NextScreenMode": "下一屏模式(正常/半屏/全屏)", - "PrevScreenMode": "上一屏模式", - "StartSearch": "开始搜索", - "StartFilter": "通过文本过滤当前视图", - "Keybindings": "按键绑定", - "KeybindingsLegend": "图例:`` 意味着ctrl+b, `意味着Alt+b, `B` 意味着shift+b", - "KeybindingsMenuSectionLocal": "本地", - "KeybindingsMenuSectionGlobal": "全局", - "KeybindingsMenuSectionNavigation": "导航", - "RenameBranch": "重命名分支", - "Upstream": "上游", - "BranchUpstreamOptionsTitle": "上游选项", - "ViewBranchUpstreamOptions": "查看上游选项", - "ViewBranchUpstreamOptionsTooltip": "查看与分支上游相关的选项,例如设置/取消设置上游和重置为上游。", - "UpstreamNotSetError": "所选分支没有上游(或者上游没有存储在本地)", - "UpstreamsNotSetError": "部分选中分支没有上游分支(或上游分支未在本地存储)", - "NewGitFlowBranchPrompt": "新的 {{.branchType}} 名称:", - "RenameBranchWarning": "该分支正在跟踪远程仓库。此操作将仅会重命名本地分支名称,而不会重命名远程分支的名称。确定继续?", - "OpenKeybindingsMenu": "打开菜单", - "ResetCherryPick": "重置已拣选(复制)的提交", - "NextTab": "下一个标签", - "PrevTab": "上一个标签", - "CantUndoWhileRebasing": "进行基础调整时无法撤消", - "CantRedoWhileRebasing": "变基时无法重做", - "MustStashWarning": "将补丁拉出到索引中需要存储和取消存储所做的变更。如果出现问题,您将可以从存储中访问文件。继续?", - "MustStashTitle": "必须保存进度", - "ConfirmationTitle": "确认面板", - "PrevPage": "上一页", - "NextPage": "下一页", - "GotoTop": "滚动到顶部", - "GotoBottom": "滚动到底部", - "FilteringBy": "过滤依据", - "ResetInParentheses": "(重置)", - "OpenFilteringMenu": "查看按路径过滤选项", - "OpenFilteringMenuTooltip": "查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。", - "FilterBy": "过滤", - "ExitFilterMode": "停止按路径过滤", - "FilterPathOption": "输入要过滤的路径", - "FilterAuthorOption": "输入作者进行过滤", - "EnterFileName": "输入路径:", - "EnterAuthor": "输入作者:", - "FilteringMenuTitle": "正在过滤", - "WillCancelExistingFilterTooltip": "注意:这将取消现有的过滤器", - "MustExitFilterModeTitle": "命令不可用", - "MustExitFilterModePrompt": "命令在过滤模式下不可用。退出过滤模式?", - "Diff": "差异", - "EnterRefToDiff": "输入 ref 以 diff", - "EnterRefName": "输入 ref:", - "ExitDiffMode": "退出差异模式", - "DiffingMenuTitle": "正在 diff", - "SwapDiff": "反向 diff", - "ViewDiffingOptions": "打开 diff 菜单", - "ViewDiffingOptionsTooltip": "查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。", - "OpenCommandLogMenu": "打开命令日志菜单", - "OpenCommandLogMenuTooltip": "查看命令日志的选项,例如显示/隐藏命令日志以及聚焦命令日志", - "ShowingGitDiff": "显示输出:", - "ShowingDiffForRange": "显示范围差异", - "CommitDiff": "比较提交差异", - "CopyCommitHashToClipboard": "复制提交哈希到剪贴板", - "CommitHash": "提交的 hash", - "CommitURL": "提交URL", - "PasteCommitMessageFromClipboard": "粘贴提交信息自剪贴板", - "SurePasteCommitMessage": "粘贴将覆盖当前提交消息,继续吗?", - "CommitMessage": "提交信息", - "CommitMessageBody": "提交信息正文", - "CommitSubject": "提交主题", - "CommitAuthor": "作者", - "CommitTags": "提交标签", - "CopyCommitAttributeToClipboard": "复制提交属性到剪贴板", - "CopyCommitAttributeToClipboardTooltip": "复制提交属性到剪贴板(如hash、URL、diff、消息、作者)。", - "CopyBranchNameToClipboard": "复制分支名称到剪贴板", - "CopyTagToClipboard": "复制标签到剪贴板", - "CopyPathToClipboard": "复制路径到剪贴板", - "CommitPrefixPatternError": "提交前缀模式错误", - "CopySelectedTextToClipboard": "复制选中文本到剪贴板", - "NoFilesStagedTitle": "没有暂存文件", - "NoFilesStagedPrompt": "您尚未暂存任何文件。提交所有文件?", - "BranchNotFoundTitle": "找不到分支", - "BranchNotFoundPrompt": "找不到分支。创建一个新分支命名为:", - "BranchUnknown": "未知的分支", - "DiscardChangeTitle": "取消暂存选中的行", - "DiscardChangePrompt": "您确定要删除所选的行(git reset)吗?这是不可逆的。\n要禁用此对话框,请将 'gui.skipDiscardChangeWarning' 的配置键设置为 true", - "CreateNewBranchFromCommit": "从提交创建新分支", - "BuildingPatch": "正在构建补丁", - "ViewCommits": "查看提交", - "RunningCustomCommandStatus": "正在运行自定义命令", - "SubmoduleStashAndReset": "存放未提交的子模块变更和更新", - "AndResetSubmodules": "和重置子模块", - "EnterSubmoduleTooltip": "输入子模块", - "Enter": "进入", - "CopySubmoduleNameToClipboard": "复制子模块名称到剪贴板", - "RemoveSubmodule": "删除子模块", - "RemoveSubmoduleTooltip": "删除选定的子模块及其相应的目录", - "RemoveSubmodulePrompt": "您确定要删除子模块 '%s' 及其对应的目录吗?这是不可逆的。", - "ResettingSubmoduleStatus": "正在重置子模块", - "NewSubmoduleName": "新的子模块名称:", - "NewSubmoduleUrl": "新的子模块 URL:", - "NewSubmodulePath": "新的子模块路径:", - "NewSubmodule": "添加新的子模块", - "AddingSubmoduleStatus": "添加子模块", - "UpdateSubmoduleUrl": "更新子模块 '%s' 的 URL", - "UpdatingSubmoduleUrlStatus": "更新 URL 中", - "EditSubmoduleUrl": "更新子模块 URL", - "InitializingSubmoduleStatus": "正在初始化子模块", - "InitSubmoduleTooltip": "初始化子模块", - "Update": "更新", - "Initialize": "初始化", - "SubmoduleUpdateTooltip": "更新子模块", - "UpdatingSubmoduleStatus": "正在更新子模块", - "BulkInitSubmodules": "批量初始化子模块", - "BulkUpdateSubmodules": "批量更新子模块", - "BulkDeinitSubmodules": "批量反初始化子模块", - "BulkUpdateRecursiveSubmodules": "批量递归初始化和更新子模块", - "ViewBulkSubmoduleOptions": "查看批量子模块选项", - "BulkSubmoduleOptions": "批量子模块选项", - "RunningCommand": "运行命令", - "SubCommitsTitle": "子提交", - "SubmodulesTitle": "子模块", - "NavigationTitle": "列表面板导航", - "SuggestionsCheatsheetTitle": "意见建议", - "SuggestionsTitle": "意见建议(按 %s 键以切换焦点)", - "SuggestionsSubtitle": "(按 %s 键进行删除, %s 键进行编辑)", - "ExtrasTitle": "附加", - "PullRequestURLCopiedToClipboard": "拉取请求URL已复制到剪贴板", - "CommitDiffCopiedToClipboard": "提交差异已复制到剪贴板", - "CommitURLCopiedToClipboard": "提交URL已复制到剪贴板", - "CommitMessageCopiedToClipboard": "提交消息已复制到剪贴板", - "CommitMessageBodyCopiedToClipboard": "提交信息正文已复制到剪贴板", - "CommitSubjectCopiedToClipboard": "提交主题已复制到剪贴板", - "CommitAuthorCopiedToClipboard": "提交作者已复制到剪贴板", - "CommitTagsCopiedToClipboard": "提交标签已复制到剪贴板", - "CommitHasNoTags": "提交没有标签", - "CommitHasNoMessageBody": "提交没有信息正文", - "PatchCopiedToClipboard": "补丁已复制到剪贴板", - "CopiedToClipboard": "已复制到剪贴板", - "ErrCannotEditDirectory": "无法编辑目录:您只能编辑单个文件", - "ErrCannotCopyContentOfDirectory": "无法复制目录内容:只能复制单个文件的内容", - "ErrStageDirWithInlineMergeConflicts": "无法 暂存/取消暂存 包含具有内联合并冲突的文件的目录。请先解决合并冲突", - "ErrRepositoryMovedOrDeleted": "找不到仓库。它可能已被移动或删除 ¯\\_(ツ)_/¯", - "ErrWorktreeMovedOrRemoved": "找不到工作树,它可能被删除或者移走了。 ¯\\\\_(ツ)_/¯", - "CommandLog": "命令日志", - "ToggleShowCommandLog": "切换 显示/隐藏 命令日志", - "FocusCommandLog": "焦点命令日志", - "CommandLogHeader": "您可以通过按 '%s' 隐藏或集中显示该面板,或使用 `gui.showCommandLog: false`\n将其永久隐藏在您的配置中", - "RandomTip": "随机小提示", - "ToggleWhitespaceInDiffView": "切换是否在差异视图中显示空白字符差异", - "IgnoreWhitespaceDiffViewSubTitle": "(忽略空白)", - "IgnoreWhitespaceNotSupportedHere": "该视图不支持忽略空白", - "IncreaseContextInDiffView": "扩大差异视图中显示的上下文范围", - "DecreaseContextInDiffView": "缩小差异视图中显示的上下文范围", - "DiffContextSizeChanged": "将diff上下文大小更改为%d", - "IncreaseRenameSimilarityThreshold": "提高重命名相似度阈值", - "DecreaseRenameSimilarityThreshold": "降低重命名相似度阈值", - "RenameSimilarityThresholdChanged": "已重命名相似度阈值更改为 %d%%", - "CreatePullRequestOptions": "创建拉取请求选项", - "DefaultBranch": "默认分支", - "SelectBranch": "选择分支", - "SelectTargetRemote": "选择目标远程仓库", - "NoValidRemoteName": "名为 '%s' 的远程名称不存在", - "CreatePullRequest": "创建拉取请求", - "SelectConfigFile": "选择配置文件", - "NoConfigFileFoundErr": "找不到配置文件", - "LoadingFileSuggestions": "正在加载文件建议", - "LoadingCommits": "正在加载提交", - "MustSpecifyOriginError": "指定分支时,必须同时指定远程", - "GitOutput": "Git 输出:", - "GitCommandFailed": "Git 命令执行失败。查看命令日志了解详情(使用 %s 打开)", - "AbortTitle": "放弃 %s", - "AbortPrompt": "您确定要放弃当前 %s 吗?", - "OpenLogMenu": "打开日志菜单", - "OpenLogMenuTooltip": "查看提交日志的选项,例如更改排序顺序、隐藏 git graph、显示整个 git graph。", - "LogMenuTitle": "提交日志选项", - "ToggleShowGitGraphAll": "切换显示完整 git 分支图(向 `git log` 命令传入 `--all` 选项)", - "ShowGitGraph": "显示 git 分支图", - "SortOrder": "排序", - "SortAlphabetical": "字母顺序", - "SortByDate": "日期大小", - "SortByRecency": "新旧程度", - "SortBasedOnReflog": "(基于reflog)", - "SortCommits": "提交排序", - "CantChangeContextSizeError": "无法在补丁构建模式下变更上下文,因为我们在发布该功能时懒得支持它。 如果您真的想要这么做,请告诉我们!", - "OpenCommitInBrowser": "在浏览器中打开提交", - "ViewBisectOptions": "查看二分查找选项", - "ConfirmRevertCommit": "您确定要恢复{{.selectedCommit}}?", - "RewordInEditorTitle": "在编辑器中重写", - "RewordInEditorPrompt": "您确定要在编辑器中重写此提交吗?", - "CheckoutAutostashPrompt": "确定要检出 '%s' 吗?必要时将自动储藏更改。", - "HardResetAutostashPrompt": "您确定要硬重置到 '%s' 吗?如有必要,将执行自动贮藏。", - "SoftResetPrompt": "您确定要软重置到 '%s' 吗?", - "UpstreamGone": "(上游不存在)", - "NukeDescription": "如果您想让工作树中的所有更改消失,可以这样做。如果存在受污染的子模块更改,这会将这些更改贮藏在子模块中。", - "DiscardStagedChangesDescription": "这将创建一个仅包含已暂存更改的新贮藏条目,然后这些更改删除,以便工作树仅保留未暂存的更改。", - "EmptyOutput": "", - "Patch": "补丁", - "CustomPatch": "自定义补丁", - "CommitsCopied": "已复制这些提交", - "CommitCopied": "已复制提交", - "ResetPatch": "重置补丁", - "ResetPatchTooltip": "清理当前补丁", - "ApplyPatch": "应用补丁", - "ApplyPatchTooltip": "应用当前补丁到工作树中", - "ApplyPatchInReverse": "反向应用补丁", - "ApplyPatchInReverseTooltip": "反向应用当前补丁到工作树中", - "RemovePatchFromOriginalCommit": "从原来的提交 (%s) 中删除补丁", - "RemovePatchFromOriginalCommitTooltip": "从这些提交中删除该补丁。这是通过在提交时启动交互式变基,反向应用补丁,然后继续变基来实现的。如果之后的提交依赖于补丁,您可能需要解决冲突。", - "MovePatchOutIntoIndex": "将补丁移出到索引中", - "MovePatchOutIntoIndexTooltip": "将补丁从提交中移出并移入到索引中。这是通过在提交时启动交互式变基、反向应用补丁、继续变基直至完成,然后将补丁应用到索引来实现的。如果之后的提交依赖于补丁,您可能需要解决冲突。", - "MovePatchIntoNewCommitTooltip": "将补丁从提交中移出并移至位于原始提交之上的新提交中。这是通过在原始提交处启动交互式变基,反向应用补丁,然后将补丁应用到索引并将其作为新提交提交,然后继续变基直至完成来实现的。如果以后的提交依赖于补丁,您可能需要解决冲突。", - "MovePatchToSelectedCommit": "移动补丁到所选提交 (%s)", - "MovePatchToSelectedCommitTooltip": "将补丁从其原始提交修改到选定的提交中。 实现这一点的方法是在原始提交时启动交互式重置,反向应用补丁, 然后在应用补丁和修改选定的提交之前,继续将其重新建立到选定的提交上。 重置将继续完成。如果源代码和目标代码提交之间的提交取决于补丁,您可能需要解决冲突。", - "CopyPatchToClipboard": "复制补丁到剪贴板", - "NoMatchesFor": "%s %s 没有匹配项", - "MatchesFor": "正在匹配'%s' (%d of %d) %s", - "SearchKeybindings": "%s: 下一个匹配项, %s: 上一个匹配项, %s: 退出搜索模式", - "SearchPrefix": "搜索: ", - "FilterPrefix": "过滤: ", - "ExitSearchMode": "%s:退出搜索模式", - "ExitTextFilterMode": "%s:退出过滤模式", - "Switch": "切换", - "SwitchToWorktree": "切换至工作树", - "SwitchToWorktreeTooltip": "切换到选中的工作树", - "AlreadyCheckedOutByWorktree": "该分支由工作树 {{.worktreeName}} 检出,您想切换到该工作树吗?", - "BranchCheckedOutByWorktree": "分支 {{.branchName}} 由工作树 {{.worktreeName}} 检出", - "SomeBranchesCheckedOutByWorktreeError": "部分选中分支被其他工作树检出。请逐个选择删除。", - "DetachWorktreeTooltip": "这将在工作树上运行“git checkout --detach”,以便它停止占用分支,但工作树的工作树将保持不变。", - "Switching": "正在切换", - "RemoveWorktree": "移除工作区", - "RemoveWorktreeTitle": "移除工作区", - "DetachWorktree": "分离工作树", - "DetachingWorktree": "正在分离工作树", - "WorktreesTitle": "工作区", - "WorktreeTitle": "工作区", - "RemoveWorktreePrompt": "您确定要删除工作树 {{.worktreeName}}' ?", - "ForceRemoveWorktreePrompt": "'{{.worktreeName}}' 包含已修改或未跟踪的文件(说实话,它可能包含两者)。您确定要删除它吗?", - "RemovingWorktree": "正在删除工作树", - "AddingWorktree": "添加工作区", - "CantDeleteCurrentWorktree": "您不能删除当前的工作树!", - "AlreadyInWorktree": "您已经在选中的工作树", - "CantDeleteMainWorktree": "您不能移除主工作树!", - "NoWorktreesThisRepo": "没有工作区", - "MissingWorktree": "(缺失)", - "MainWorktree": "(主要)", - "NewWorktree": "新建工作树", - "NewWorktreePath": "新建工作树路径", - "NewWorktreeBase": "新建工作树基于ref", - "RemoveWorktreeTooltip": "删除选定的工作树。这将删除工作树的目录以及 .git 目录中有关工作树的元数据。", - "BranchNameCannotBeBlank": "分支名称不能为空", - "NewBranchName": "新分支名称", - "NewBranchNameLeaveBlank": "新分支名称(为空则默认检出为 {{.default}})", - "ViewWorktreeOptions": "查看工作区选项", - "CreateWorktreeFrom": "从 {{.ref}} 创建工作树", - "CreateWorktreeFromDetached": "从 {{.ref}} 创建工作树(分离)", - "LcWorktree": "工作区", - "ChangingDirectoryTo": "将目录更改为 {{.path}}", - "Name": "名称", - "Branch": "分支", - "Path": "路径", - "MarkedBaseCommitStatus": "已标记一个主提交用于变基", - "MarkAsBaseCommit": "标记一个主提交用于变基", - "MarkAsBaseCommitTooltip": "选择下一次变基的主提交。当您变基到一个分支时,只有高于主提交的提交才会被引入。这使用“git rebase --onto”命令。", - "MarkedCommitMarker": "↑↑↑ 将从这里开始变基 ↑↑↑", - "FailedToOpenURL": "打开URL %s 失败。\n\n错误:%v", - "InvalidLazygitEditURL": "无效的lazygit-edit URL格式:%s", - "NoCopiedCommits": "未复制提交", - "DisabledMenuItemPrefix": "不可用: ", - "QuickStartInteractiveRebase": "开始交互式变基", - "QuickStartInteractiveRebaseTooltip": "为分支上的提交启动交互式变基。这将包括从 HEAD 提交到第一个合并提交或主分支提交的所有提交。\n如果您想从所选提交启动交互式变基,请按 `{{.editKey}}`。", - "CannotQuickStartInteractiveRebase": "无法启动交互式变基:HEAD 提交是合并提交或存在于主分支上,因此没有适当的主提交来启动变基。您可以通过选择提交并按 `{{.editKey}}` 从特定提交启动交互式变基。", - "ToggleRangeSelect": "切换拖动选择", - "RangeSelectUp": "向上扩展选择范围", - "RangeSelectDown": "向下扩展选择范围", - "RangeSelectNotSupported": "该操作不支持范围选择,请选择单个项目", - "NoItemSelected": "没有条目被选中", - "SelectedItemIsNotABranch": "选中的条目不是一个分支", - "SelectedItemDoesNotHaveFiles": "选中的条目中没有", - "MultiSelectNotSupportedForSubmodules": "子模块不支持多选操作", - "OldCherryPickKeyWarning": "The 'c' key is no longer the default key for copying commits to cherry pick. Please use `{{.copy}}` instead (and `{{.paste}}` to paste). The reason for this change is that the 'v' key for selecting a range of lines when staging is now also used for selecting a range of lines in any list view, meaning that we needed to find a new key for pasting commits, and if we're going to now use `{{.paste}}` for pasting commits, we may as well use `{{.copy}}` for copying them. If you want to configure the keybindings to get the old behaviour, set the following in your config:\n\nkeybinding:\n universal:\n toggleRangeSelect: \n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'", - "CommandDoesNotSupportOpeningInEditor": "该命令不支持切换到编辑器", - "CustomCommands": "自定义命令", - "NoApplicableCommandsInThisContext": "(当前上下文无可用命令)", - "SelectCommitsOfCurrentBranch": "选择当前分支的提交", - "Actions": { - "CheckoutCommit": "检出提交", - "CheckoutBranchAtCommit": "检出分支 '%s'", - "CheckoutCommitAsDetachedHead": "以分离头指针方式检出提交 %s", - "CheckoutTag": "检出标签", - "CheckoutBranch": "检出分支", - "CheckoutBranchOrCommit": "检出分支或提交", - "ForceCheckoutBranch": "强制检出分支", - "DeleteLocalBranch": "删除本地分支", - "Merge": "合并", - "SquashMerge": "压缩合并", - "RebaseBranch": "变基分支", - "RenameBranch": "重命名分支", - "CreateBranch": "建立分支", - "FastForwardBranch": "快进分支", - "CherryPick": "(拣选) 粘贴提交", - "CheckoutFile": "检出文件", - "SquashCommitDown": "向下压缩提交", - "FixupCommit": "修正提交", - "RewordCommit": "改写提交", - "DropCommit": "删除提交", - "EditCommit": "编辑提交", - "AmendCommit": "提交修补", - "ResetCommitAuthor": "重设作者", - "SetCommitAuthor": "设置作者", - "AddCommitCoAuthor": "添加其他的提交作者", - "RevertCommit": "还原提交", - "CreateFixupCommit": "创建修正提交", - "SquashAllAboveFixupCommits": "压缩以上所有的修正提交", - "MoveCommitUp": "上移提交", - "MoveCommitDown": "下移提交", - "CopyCommitMessageToClipboard": "复制提交消息到剪贴板", - "CopyCommitMessageBodyToClipboard": "复制提交信息正文到剪贴板", - "CopyCommitSubjectToClipboard": "复制提交主题到剪贴板", - "CopyCommitDiffToClipboard": "复制提交差异到剪贴板", - "CopyCommitHashToClipboard": "复制完整的提交哈希到剪贴板", - "CopyCommitURLToClipboard": "复制提交URL到剪贴板", - "CopyCommitAuthorToClipboard": "复制提交作者到剪贴板", - "CopyCommitAttributeToClipboard": "复制到剪贴板", - "CopyCommitTagsToClipboard": "复制提交标签到剪贴板", - "CopyPatchToClipboard": "复制补丁到剪贴板", - "CustomCommand": "自定义命令", - "DiscardAllChangesInFile": "丢弃文件中的所有变更", - "DiscardAllUnstagedChangesInFile": "丢弃文件中所有未暂存的变更", - "StageFile": "暂存文件", - "StageResolvedFiles": "合并冲突已解决的已暂存文件", - "UnstageFile": "取消暂存文件", - "UnstageAllFiles": "取消暂存所有文件", - "StageAllFiles": "暂存所有文件", - "ResolveConflictByKeepingFile": "通过保留文件解决冲突", - "ResolveConflictByDeletingFile": "通过删除文件解决冲突", - "NotEnoughContextToStage": "差异上下文大小为0时无法暂存或取消暂存更改。请使用'%s'增大上下文。", - "NotEnoughContextToDiscard": "差异上下文大小为0时无法丢弃更改。请使用'%s'增大上下文。", - "IgnoreExcludeFile": "忽略文件", - "IgnoreFileErr": "无法忽略 .gitignore", - "ExcludeFile": "排除文件", - "ExcludeGitIgnoreErr": "无法排除.gitignore", - "Commit": "提交(Commit)", - "Push": "推送(Push)", - "Pull": "拉取(Pull)", - "OpenFile": "打开文件", - "StashAllChanges": "贮藏所有变更", - "StashAllChangesKeepIndex": "贮藏所有更改并保持索引", - "StashStagedChanges": "贮藏暂存的变更", - "StashUnstagedChanges": "贮藏所有未暂存更改", - "StashIncludeUntrackedChanges": "贮藏所有更改包括未跟踪的文件", - "GitFlowFinish": "git flow 结果", - "GitFlowStart": "git flow 开始", - "CopyToClipboard": "复制到剪贴板", - "CopySelectedTextToClipboard": "复制选中文本到剪贴板", - "RemovePatchFromCommit": "从提交中删除补丁", - "MovePatchToSelectedCommit": "将补丁移动到选定的提交", - "MovePatchIntoIndex": "将补丁移到索引", - "MovePatchIntoNewCommit": "将补丁移到新提交中", - "DeleteRemoteBranch": "删除远程分支", - "SetBranchUpstream": "设置分支上游", - "AddRemote": "添加远程", - "RemoveRemote": "移除远程", - "UpdateRemote": "更新远程", - "ApplyPatch": "应用补丁", - "Stash": "贮藏(Stash)", - "RenameStash": "重命名贮藏", - "RemoveSubmodule": "删除子模块", - "ResetSubmodule": "重置子模块", - "AddSubmodule": "添加子模块", - "UpdateSubmoduleUrl": "更新子模块 URL", - "InitialiseSubmodule": "初始化子模块", - "BulkInitialiseSubmodules": "批量初始化子模块", - "BulkUpdateSubmodules": "批量更新子模块", - "BulkDeinitialiseSubmodules": "批量取消初始化子模块", - "BulkUpdateRecursiveSubmodules": "批量递归初始化和更新子模块", - "UpdateSubmodule": "更新子模块", - "CreateLightweightTag": "创建轻量标签", - "CreateAnnotatedTag": "创建附注标签", - "DeleteLocalTag": "删除本地标签", - "DeleteRemoteTag": "删除远程标签", - "PushTag": "推送标签", - "NukeWorkingTree": "Nuke 工作树", - "DiscardUnstagedFileChanges": "放弃未暂存的文件变更", - "RemoveUntrackedFiles": "删除未跟踪的文件", - "RemoveStagedFiles": "移除贮藏文件", - "SoftReset": "软重置", - "MixedReset": "混合重置", - "HardReset": "硬重置", - "Undo": "撤销", - "Redo": "重做", - "CopyPullRequestURL": "复制拉取请求 URL", - "OpenMergeTool": "打开合并工具", - "OpenCommitInBrowser": "在浏览器中打开提交", - "OpenPullRequest": "在浏览器中打开拉取请求", - "StartBisect": "开始二分查找(Bisect)", - "ResetBisect": "重置二分查找", - "BisectSkip": "二分查找跳过", - "BisectMark": "二分查找标记", - "AddWorktree": "添加工作区" - }, - "Bisect": { - "MarkStart": "将 %s 标记为 %s (start bisect)", - "ResetTitle": "重置 'git bisect'", - "ResetPrompt": "您确定要重置 'git bisect' 吗?", - "ResetOption": "重置二分查找", - "ChooseTerms": "二选一", - "OldTermPrompt": "旧/好的提交:", - "NewTermPrompt": "新/坏的提交:", - "BisectMenuTitle": "二分查找", - "Mark": "将 %s 标记为 %s", - "SkipCurrent": "跳过 %s", - "SkipSelected": "跳过所选提交(%s)", - "CompleteTitle": "二分查找完成", - "CompletePrompt": "二分查找完成!以下提交引入了此变更:\n\n%s\n\n您现在要重置 'git bisect' 吗?", - "CompletePromptIndeterminate": "二分查找完成!一些提交被跳过了,所以下列提交中的任何一个都可能引入了此变更:\n\n%s\n\n您现在要重置 'git bisect' 吗?", - "Bisecting": "二分查找中" - }, - "Log": { - "EditRebase": "开始从 '{{.ref}}' 进行交互式变基", - "HandleUndo": "撤销最后一次的冲突解决方案", - "RemoveFile": "正在删除路径 '{{.path}}'", - "CopyToClipboard": "正在复制 '{{.str}}' 到剪贴板", - "Remove": "删除 '{{.filename}}'", - "CreateFileWithContent": "正在创建文件 '{{.path}}'", - "AppendingLineToFile": "将 '{{.line}}' 附加到文件 '{{.filename}}'", - "EditRebaseFromBaseCommit": "开始从'{{.baseCommit}}'进行交互式变基到'{{.targetBranchName}}‘" - }, - "BreakingChangesTitle": "重大变化", - "BreakingChangesMessage": "您正在更新到 lazygit 的新版本,其中含有中断的更改。请阅读下面的说明,并在必要时更新您的配置。\n欲了解更多信息,请参阅 的完整版本说明。", - "BreakingChangesByVersion": { - "0.41.0": "- 当您按“g”调出 git 重置菜单时,“mixed”选项现在是第一个也是默认选项,而不是“soft”。这是因为“mixed”是最常用的选项。\n- 提交消息面板现在默认自动硬换行(即,当您到达页边距时,它会添加换行符)。您可以像这样调整配置:\n\ngit:\n commit:\n autoWrapCommitMessage: true\n autoWrapWidth: 72\n\n- “v”键已在暂存视图中用于启动范围选择,但现在您可以使用它在任何视图中启动范围选择。不幸的是,这与粘贴提交(cherry-pick)的“v”键绑定冲突,因此现在粘贴提交是通过“shift+V”完成的,为了一致性,复制提交现在是通过“shift+C”而不是“c”。请注意,“v”键绑定只是启动范围选择的一种方法:您可以使用 shift+向上/向下箭头。因此,如果您想配置cherry-pick键绑定以获得旧行为,请在配置中设置以下内容:\n\nkeybinding:\n universal:\n toggleRangeSelect: \n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'\n\n- 使用“shift-S”压缩修复现在会弹出一个菜单,默认选项是压缩分支中的所有修复提交。仅压缩所选提交之上的修复提交的原始行为仍然可以作为该菜单中的第二个选项使用。\n- Push/pull/fetch 加载状态现在显示在分支上,而不是在弹出窗口中。这允许您例如并行获取多个分支并查看每个分支的状态。\n- 提交视图中的 git 日志图现在始终默认显示(以前仅在视图最大化时显示)。如果您发现这太冗余了,您可以通过 ctrl+L -> 'Show git graph' -> 'when maximized' 将其改回来\n- 在远程分支上按空格用于显示输入新本地分支名称的提示,以从远程分支检出。现在它只是直接检查远程分支,让您在同名的新本地分支或分离的头之间进行选择。旧的行为仍然可以通过“n”键绑定使用。\n- 默认情况下,过滤(例如按“/”时)不太模糊;它现在只匹配子字符串。多个子字符串可以通过用空格分隔来匹配。如果您想恢复到旧的行为,请在配置中设置以下内容:\n\ngui:\n filterMode: 'fuzzy'\n\t ", - "0.44.0": "- gui.branchColors 配置选项已弃用;其将在未来的版本中被移除。请使用 gui.branchColorPatterns 代替。\n- 以 \"feature/\",\"bugfix/\" 和 \"hotfix/\" 开头的分支自动上色已移除;如果您想要保留该功能,可以通过设置新的 gui.branchColorPatterns 选项来启用。", - "0.49.0": "- 执行shell命令(带':'提示符)不再使用交互式shell。如需在提示符中使用shell别名,需进行额外配置。详见:https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#using-aliases-or-functions-in-shell-commands" - } -} diff --git a/pkg/i18n/translations/zh-TW.json b/pkg/i18n/translations/zh-TW.json deleted file mode 100644 index e01778eda6f..00000000000 --- a/pkg/i18n/translations/zh-TW.json +++ /dev/null @@ -1,690 +0,0 @@ -{ - "NotEnoughSpace": "無足夠空間顯示面板", - "DiffTitle": "差異", - "FilesTitle": "檔案", - "BranchesTitle": "分支", - "CommitsTitle": "提交", - "StashTitle": "收藏 (Stash)", - "SnakeTitle": "貪食蛇", - "EasterEgg": "彩蛋", - "UnstagedChanges": "未預存變更", - "StagedChanges": "已預存變更", - "StagingTitle": "主面板(預存)", - "MergingTitle": "主面板(合併)", - "SquashMergeUncommittedTitle": "壓縮合併但不提交", - "SquashMergeCommittedTitle": "壓縮合併並提交", - "NormalTitle": "主面板(一般)", - "LogTitle": "版本記錄", - "CommitSummary": "提交摘要", - "CredentialsUsername": "使用者名稱", - "CredentialsPassword": "密碼", - "CredentialsPassphrase": "SSH 金鑰密語", - "CredentialsPIN": "SSH 金鑰 PIN 碼", - "PassUnameWrong": "密碼、密語或使用者名稱錯誤", - "Commit": "提交變更", - "CommitTooltip": "提交暫存區變更", - "AmendLastCommit": "修改上次提交", - "AmendLastCommitTitle": "修改上次提交", - "SureToAmend": "是否確定要修改上次提交?之後你可以從提交面板中再次更改此次提交的訊息。", - "NoCommitToAmend": "沒有可以修改的提交。", - "CommitChangesWithEditor": "使用 git 編輯器提交變更", - "StatusTitle": "狀態", - "GlobalTitle": "全域快捷鍵", - "Execute": "執行", - "Stage": "切換預存", - "ToggleStagedAll": "全部預存/取消預存", - "ToggleTreeView": "顯示檔案樹狀視圖", - "OpenDiffTool": "開啟外部差異工具 (git difftool)", - "OpenMergeTool": "開啟外部合併工具", - "OpenMergeToolTooltip": "執行 `git mergetool`。", - "Refresh": "重新整理", - "Push": "推送", - "Pull": "拉取", - "PushTooltip": "推送到遠端。如果沒有設定遠端,會開啟設定視窗。", - "PullTooltip": "從遠端同步當前分支。如果沒有設定遠端,會開啟設定視窗。", - "FileFilter": "篩選檔案 (預存/未預存)", - "CopyToClipboardMenu": "複製到剪貼簿", - "CopyFileName": "檔案名稱", - "CopyFileDiffTooltip": "如果有已預存的項目,此指令只考慮它們。否則,它將考慮所有未暫存的項目。", - "CopySelectedDiff": "所選檔案的差異", - "CopyAllFilesDiff": "所有檔案的差異", - "FileNameCopiedToast": "檔案名稱已複製", - "FilePathCopiedToast": "檔案路徑已複製", - "FileDiffCopiedToast": "已複製檔案差異", - "AllFilesDiffCopiedToast": "已複製所有檔案差異", - "FilterStagedFiles": "僅顯示預存的檔案", - "FilterUnstagedFiles": "僅顯示未預存的檔案", - "MergeConflictsTitle": "合併衝突", - "Checkout": "檢出", - "CheckoutTooltip": "檢出選定的項目。", - "NoChangedFiles": "沒有變更的檔案", - "SoftReset": "軟重設", - "AlreadyCheckedOutBranch": "你已經檢出這個分支了", - "SureForceCheckout": "是否強制檢出?這將會使你失去本地的所有更改", - "ForceCheckoutBranch": "強制檢出分支", - "BranchName": "分支名稱", - "NewBranchNameBranchOff": "新的分支名稱 (根據 '{{.branchName}}' 分支創建)", - "CantDeleteCheckOutBranch": "無法刪除已檢出的分支!", - "DeleteRemoteBranchPrompt": "確定要刪除遠端 {{.upstream}} 的標籤 '{{.selectedBranchName}}'?", - "ForceDeleteBranchMessage": "'{{.selectedBranchName}}' 分支尚未完全合併。是否刪除?", - "RebaseBranch": "將已檢出的分支變基至此分支", - "CantRebaseOntoSelf": "無法將分支變基至自己", - "CantMergeBranchIntoItself": "無法將一個分支合併至自己", - "ForceCheckout": "強制檢出", - "CheckoutByName": "根據名稱檢出", - "RemoteBranchCheckoutTitle": "檢出 {{.branchName}}", - "CheckoutTypeNewBranch": "新本地分支", - "CheckoutTypeNewBranchTooltip": "將遠端分支檢出為追蹤它的本地分支。", - "CheckoutTypeDetachedHead": "分離 HEAD", - "CheckoutTypeDetachedHeadTooltip": "將遠端分支檢出為分離的 HEAD,在只想測試但不動工時很實用。您稍後仍能根據它建立一個本地分支。", - "NewBranch": "新分支", - "NoBranchesThisRepo": "這個版本庫中沒有分支", - "CommitWithoutMessageErr": "沒有提交訊息,無法提交", - "Close": "關閉", - "CloseCancel": "關閉/取消", - "Confirm": "確認", - "Quit": "結束", - "CannotSquashOrFixupFirstCommit": "沒有可以壓縮的提交", - "Fixup": "修復 (Fixup)", - "SureFixupThisCommit": "是否對此提交進行 '修復' ? 其將被合併於以下之提交中", - "SureSquashThisCommit": "是否要把這個提交壓縮到下面的提交中?", - "Squash": "壓縮 (Squash)", - "PickCommitTooltip": "挑選提交 (於變基過程中)", - "Pick": "挑選", - "Edit": "編輯", - "Revert": "還原", - "Reword": "改寫提交", - "CommitRewordTooltip": "改寫選中的提交訊息", - "DropCommit": "刪除提交", - "MoveDownCommit": "向下移動提交", - "MoveUpCommit": "向上移動提交", - "EditCommit": "編輯(開始互動變基)", - "EditCommitTooltip": "編輯提交", - "AmendCommitTooltip": "使用已預存的更改修正提交", - "Amend": "修改", - "ResetAuthor": "重設作者", - "SetAuthor": "設定作者", - "AddCoAuthor": "添加合作者", - "AmendCommitAttribute": "設定/重設提交作者", - "SetAuthorPromptTitle": "設定作者(格式:「姓名 <電子郵件>」)", - "RewordCommitEditor": "使用編輯器改寫提交", - "NoCommitsThisBranch": "這個分支沒有提交", - "UpdateRefHere": "在這裡更新 '{{.ref}}' 分支", - "Error": "錯誤", - "Undo": "復原", - "UndoReflog": "復原", - "RedoReflog": "取消復原", - "UndoTooltip": "將使用 reflog 確任 git 指令以復原。這不包括工作區更改;只考慮提交。", - "RedoTooltip": "將使用 reflog 確任 git 指令以重作。這不包括工作區更改;只考慮提交。", - "DiscardAllTooltip": "捨棄 '{{.path}}' 預存/未預存更改。", - "DiscardUnstagedTooltip": "捨棄 '{{.path}}' 未預存更改。", - "Pop": "還原", - "Drop": "捨棄", - "Apply": "套用", - "NoStashEntries": "沒有收藏記錄", - "StashDrop": "放棄收藏記錄", - "StashPop": "還原收藏記錄", - "SurePopStashEntry": "是否從收藏中還原這個記錄?", - "StashApply": "套用收藏記錄", - "SureApplyStashEntry": "是否套用這個收藏記錄?", - "NoTrackedStagedFilesStash": "你沒有被追蹤的、預存的檔案可進行收藏", - "NoFilesToStash": "沒有檔案可以進行收藏", - "StashChanges": "安置現有變更到收藏中", - "RenameStash": "重新命名收藏", - "RenameStashPrompt": "重新命名收藏:{{.stashName}}", - "OpenConfig": "開啟設定檔案", - "EditConfig": "編輯設定檔案", - "ForcePush": "強制推送", - "ForcePushPrompt": "你的分支與遠端分支分岔。按 'ESC' 取消,或按 'Enter' 強制推送。", - "ForcePushDisabled": "你的分支與遠端分支分岔,你已禁用強制推送", - "UpdatesRejectedAndForcePushDisabled": "更新被拒絕,你已禁用強制推送", - "CheckForUpdate": "檢查更新", - "CheckingForUpdates": "正在檢查更新...", - "UpdateAvailableTitle": "有可用的更新!", - "UpdateAvailable": "下載並安裝版本 {{.newVersion}}?", - "UpdateInProgressWaitingStatus": "更新中", - "UpdateCompletedTitle": "更新已完成!", - "UpdateCompleted": "更新已成功安裝。為了使其生效,請重新啟動 lazygit。", - "FailedToRetrieveLatestVersionErr": "無法取得版本資訊", - "OnLatestVersionErr": "已更新至最新版本", - "MajorVersionErr": "新版本({{.newVersion}})不支援當前版本({{.currentVersion}})更改", - "CouldNotFindBinaryErr": "找不到 {{.url}} 執行檔", - "UpdateFailedErr": "更新失敗:{{.errMessage}}", - "ConfirmQuitDuringUpdateTitle": "正在更新中", - "ConfirmQuitDuringUpdate": "正在進行更新,是否結束?", - "MergeToolTitle": "合併工具", - "MergeToolPrompt": "是否開啟 'git mergetool'?", - "GitconfigParseErr": "Gogit 無法解析你的 gitconfig 檔案,因為存在未引用的 '\\' 字符,刪除它們應該可以解決這個問題。", - "EditFile": "編輯檔案", - "EditFileTooltip": "使用外部編輯器開啟", - "OpenFile": "開啟檔案", - "OpenFileTooltip": "使用預設軟體開啟", - "OpenInEditor": "在編輯器中開啟", - "IgnoreFile": "添加到 .gitignore", - "ExcludeFile": "添加到 .git/info/exclude", - "RefreshFiles": "重新整理檔案", - "Merge": "合併到當前檢出的分支", - "RegularMerge": "一般合併", - "ConfirmQuit": "是否結束?", - "SwitchRepo": "切換到最近使用的版本庫", - "UnsupportedGitService": "不支援的 git 服務", - "CopyPullRequestURL": "複製拉取請求的 URL 到剪貼板", - "NoBranchOnRemote": "這個分支在遠端不存在。需要先將其推送至遠端。", - "Fetch": "擷取", - "FetchTooltip": "同步遠端異動", - "FileEnter": "選擇檔案中的單個程式碼塊/行,或展開/折疊目錄", - "StageSelectionTooltip": "切換現有行的狀態 (已預存/未預存)", - "DiscardSelection": "刪除變更 (git reset)", - "ToggleSelectionForPatch": "向 (或從) 補丁中添加/刪除行", - "EditHunk": "編輯程式碼塊", - "ToggleStagingView": "切換至另一個面板 (已預存/未預存更改)", - "ReturnToFilesPanel": "返回檔案面板", - "FastForward": "從上游快進此分支", - "FastForwardTooltip": "從遠端快進所選的分支", - "FastForwarding": "的擷取和快進中", - "FoundConflictsTitle": "自動合併失敗", - "ViewConflictsMenuItem": "檢視衝突", - "AbortMenuItem": "中止%s", - "PickHunk": "挑選程式碼片段", - "PickAllHunks": "挑選所有程式碼片段", - "ViewMergeRebaseOptions": "查看合併/變基選項", - "ViewRebaseOptions": "查看合併/變基選項", - "NotMergingOrRebasing": "你當前既不在變基也不在合併中", - "AlreadyRebasing": "無法在變基期間執行此操作", - "RecentRepos": "最近的版本庫", - "MergeOptionsTitle": "合併選項", - "RebaseOptionsTitle": "變基選項", - "CommitSummaryTitle": "提交摘要", - "CommitDescriptionTitle": "提交描述", - "CommitDescriptionSubTitle": "按 tab 鍵聚焦", - "LocalBranchesTitle": "本地分支", - "SearchTitle": "搜尋", - "TagsTitle": "標籤", - "MenuTitle": "功能表", - "RemotesTitle": "遠端", - "RemoteBranchesTitle": "遠端分支", - "PatchBuildingTitle": "主面板 (補丁生成)", - "InformationTitle": "資訊", - "SecondaryTitle": "次要", - "ReflogCommitsTitle": "日誌", - "Continue": "確認", - "RebasingTitle": "將 '{{.checkedOutBranch}}'", - "SimpleRebase": "簡單變基 變基至 '{{.ref}}'", - "InteractiveRebase": "互動變基 變基至 '{{.ref}}'", - "InteractiveRebaseTooltip": "開始一個互動變基,以中斷開始,這樣你可以在繼續之前更新TODO提交", - "FwdNoUpstream": "無法快進無遠端的分支 ", - "FwdNoLocalUpstream": "無法快進尚未在本地註冊的遠端分支", - "FwdCommitsToPush": "無法快進帶有尚未推送的提交的分支", - "PullRequestNoUpstream": "無法對沒有遠端的分支拉取", - "ErrorOccurred": "發生錯誤!請在此詢問錯誤:", - "YouDied": "你死了!", - "RewordNotSupported": "在互動變基期間改寫提交目前不支援", - "ChangingThisActionIsNotAllowed": "不允許更改此類變基待辦事項", - "CherryPickCopy": "複製提交 (揀選)", - "PasteCommits": "貼上提交 (揀選)", - "CherryPick": "揀選 (Cherry-pick)", - "Donate": "贊助", - "AskQuestion": "諮詢", - "PrevHunk": "選擇上一段", - "NextHunk": "選擇下一段", - "PrevConflict": "選擇上一個衝突", - "NextConflict": "選擇下一個衝突", - "SelectPrevHunk": "選擇上一段", - "SelectNextHunk": "選擇下一段", - "ScrollDown": "向下捲動", - "ScrollUp": "向上捲動", - "ScrollUpMainWindow": "向上捲動主面板", - "ScrollDownMainWindow": "向下捲動主面板", - "AmendCommitTitle": "修改提交", - "AmendCommitPrompt": "是否使用預存檔案修改提交?", - "DropCommitTitle": "刪除提交", - "DropCommitPrompt": "是否刪除此提交?", - "PullingStatus": "拉取", - "PushingStatus": "推送", - "FetchingStatus": "擷取", - "SquashingStatus": "壓縮中", - "FixingStatus": "修復中", - "DeletingStatus": "刪除中", - "MovingStatus": "移動中", - "RebasingStatus": "變基中", - "MergingStatus": "合併中", - "LowercaseRebasingStatus": "變基", - "LowercaseMergingStatus": "合併", - "AmendingStatus": "修改中", - "CherryPickingStatus": "揀選中", - "UndoingStatus": "復原中", - "RedoingStatus": "重做中", - "CheckingOutStatus": "檢出中", - "CommittingStatus": "提交中", - "RevertingStatus": "還原中", - "CommitFiles": "提交檔案", - "SubCommitsDynamicTitle": "提交(%s)", - "CommitFilesDynamicTitle": "差異檔案(%s)", - "RemoteBranchesDynamicTitle": "遠端分支(%s)", - "ViewItemFiles": "檢視所選項目的檔案", - "CommitFilesTitle": "提交檔案", - "CheckoutCommitFileTooltip": "檢出檔案", - "DiscardFileChangesTitle": "捨棄檔案更改", - "DiscardFileChangesPrompt": "是否捨棄此提交?如果這個檔案是在此提交中創建的,它將被刪除", - "BareRepo": "你嘗試在裸版本庫中開啟 Lazygit,但 Lazygit 尚未支援裸版本庫。是否開啟最新版本庫? (y/n) ", - "InitialBranch": "分支名稱?(留空使用 git 的預設值):", - "NoRecentRepositories": "必須在 git 版本庫中開啟 lazygit。沒有有效的最近版本庫。退出。", - "IncorrectNotARepository": "無效 `notARepository` 輸入。輸入應為「prompt」、「create」、「skip」、或「quit」。", - "AutoStashTitle": "是否自動收藏?", - "AutoStashPrompt": "必須收藏並拾起變更才得以繼續操作。是否自動執行?(Enter/Esc)", - "Discard": "捨棄", - "DiscardChangesTitle": "捨棄變更", - "DiscardFileChangesTooltip": "檢視選中變動進行捨棄復原", - "Cancel": "取消", - "DiscardAllChanges": "刪除所有變更", - "DiscardUnstagedChanges": "刪除未預存變更", - "DiscardAllChangesToAllFiles": "刪除工作目錄", - "DiscardAnyUnstagedChanges": "刪除未預存變更", - "DiscardUntrackedFiles": "刪除未追蹤檔案", - "DiscardStagedChanges": "刪除已預存變更", - "HardReset": "強制重設", - "Delete": "刪除", - "Reset": "重設", - "ViewResetOptions": "檢視重設選項", - "CreateFixupCommit": "建立修復提交", - "CreateFixupCommitTooltip": "為此提交建立修復提交", - "SquashAboveCommitsTooltip": "是否壓縮上方 {{.commit}} 所有「fixup」提交?", - "SquashAboveCommits": "壓縮上方所有「fixup」提交(自動壓縮)", - "CommitChangesWithoutHook": "沒有預提交 hook 就提交更改", - "ResetTo": "重設至", - "PressEnterToReturn": "按 Enter 返回到 lazygit", - "ViewStashOptions": "檢視收藏選項", - "Stash": "收藏", - "StashAllChanges": "收藏所有變更", - "StashStagedChanges": "收藏已預存變更", - "StashAllChangesKeepIndex": "收藏所有變更並保留預存區", - "StashUnstagedChanges": "收藏未預存變更", - "StashIncludeUntrackedChanges": "收藏所有變更,包括未追蹤檔案", - "StashOptions": "收藏選項", - "NotARepository": "錯誤:必須在 git 版本庫中執行", - "ScrollLeft": "向左捲動", - "ScrollRight": "向右捲動", - "DiscardPatch": "捨棄補丁", - "DiscardPatchConfirm": "你只能從單一提交或收藏項目建立一個補丁。是否捨棄當前補丁?", - "CantPatchWhileRebasingError": "在合併或變基狀態下,你不能建立或運行補丁命令", - "ToggleAddToPatch": "切換檔案是否包含在補丁中", - "ToggleAllInPatch": "切換所有檔案是否包含在補丁中", - "UpdatingPatch": "正在更新補丁", - "ViewPatchOptions": "檢視自訂補丁選項", - "PatchOptionsTitle": "補丁選項", - "NoPatchError": "尚未建立補丁。要開始建立補丁,請在提交檔案上使用空格或輸入以添加特定行", - "EnterCommitFile": "輸入檔案以將選定的行添加至補丁(或切換目錄折疊)", - "ExitCustomPatchBuilder": "退出自訂補丁建立器", - "EnterUpstream": "輸入遠端為 ' '", - "InvalidUpstream": "無效的遠端分支名稱。必須符合 ' ' 的格式", - "NewRemote": "新增遠端", - "NewRemoteName": "新遠端名稱:", - "NewRemoteUrl": "新遠端 URL:", - "EditRemoteName": "輸入更新 {{.remoteName}} 遠端名稱:", - "EditRemoteUrl": "輸入更新 {{.remoteName}} 遠端 URL:", - "RemoveRemote": "移除遠端", - "DeleteRemoteBranch": "刪除遠端分支", - "SetAsUpstream": "設置為遠端", - "SetAsUpstreamTooltip": "將此分支設為當前分支之遠端", - "SetUpstream": "設定選定分支的遠端分支", - "UnsetUpstream": "重置選定分支的遠端", - "ViewDivergenceFromUpstream": "檢視與遠端的差異", - "DivergenceSectionHeaderLocal": "本地", - "ViewUpstreamResetOptions": "重設當前分支進 {{.upstream}}", - "ViewUpstreamResetOptionsTooltip": "查看重設當前分支進 {{upstream}} 的選項。注意:此動作不會重置所選的遠端,而是將當前分支重置到遠端", - "ViewUpstreamRebaseOptions": "將當前分支變基到 {{.upstream}}", - "ViewUpstreamRebaseOptionsTooltip": "查看變基當前分支到 {{upstream}} 的選項。注意:此動作不會變基所選的遠端,而是將當前分支變基到遠端", - "UpstreamGenericName": "選定分支的選端", - "SetUpstreamTitle": "設定遠端分支", - "EditRemoteTooltip": "編輯遠端", - "TagCommit": "打標籤到提交", - "TagNameTitle": "標籤名稱", - "TagMessageTitle": "標籤訊息", - "LightweightTag": "輕量標籤", - "AnnotatedTag": "附註標籤", - "DeleteLocalTag": "刪除本地標籤", - "DeleteRemoteTagPrompt": "確定要刪除遠端 {{.upstream}} 的標籤 '{{.tagName}}'?", - "PushTagTitle": "推送標籤 '{{.tagName}}' 至遠端:", - "PushTag": "推送標籤", - "NewTag": "建立標籤", - "FetchRemoteTooltip": "擷取遠端", - "GitFlowOptions": "顯示 git-flow 選項", - "NotAGitFlowBranch": "這似乎不是一個 git flow 分支", - "NewBranchNamePrompt": "為分支輸入新名稱", - "IgnoreTracked": "忽略已追蹤檔案", - "ExcludeTracked": "排除已追蹤檔案", - "IgnoreTrackedPrompt": "你確定要忽略一個已追蹤的檔案?", - "ViewResetToUpstreamOptions": "檢視遠端重設選項", - "NextScreenMode": "下一個螢幕模式(常規/半螢幕/全螢幕)", - "PrevScreenMode": "上一個螢幕模式", - "StartSearch": "搜尋", - "StartFilter": "搜尋", - "Keybindings": "鍵盤快捷鍵", - "KeybindingsLegend": "說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B", - "KeybindingsMenuSectionLocal": "本地", - "KeybindingsMenuSectionGlobal": "全域", - "RenameBranch": "重新命名分支", - "Upstream": "遠端", - "BranchUpstreamOptionsTitle": "上游遠端設定", - "ViewBranchUpstreamOptions": "檢視遠端設定", - "ViewBranchUpstreamOptionsTooltip": "檢視有關遠端分支的設定(例如重設至遠端)", - "UpstreamNotSetError": "目標分支沒有遠端對應分支(或其遠端分支未儲存於本地)", - "NewGitFlowBranchPrompt": "{{.branchType}} 名稱:", - "RenameBranchWarning": "此分支正在追蹤遠端分支。此操作僅會重新命名本地分支名稱,而不是遠端分支的名稱。是否繼續?", - "OpenKeybindingsMenu": "開啟選單", - "ResetCherryPick": "重設選定的揀選 (複製) 提交", - "NextTab": "下一個索引標籤", - "PrevTab": "上一個索引標籤", - "CantUndoWhileRebasing": "在變基時無法復原", - "CantRedoWhileRebasing": "在變基時無法取消復原", - "MustStashWarning": "將補丁提取到索引中需要收藏並取消收藏你的變更。如果出現問題,你可以從收藏中訪問你的檔案。是否繼續?", - "MustStashTitle": "必須收藏", - "ConfirmationTitle": "確認面板", - "PrevPage": "上一頁", - "NextPage": "下一頁", - "GotoTop": "捲動到頂部", - "GotoBottom": "捲動到底部", - "FilteringBy": "篩選方式", - "ResetInParentheses": "(已重設)", - "OpenFilteringMenu": "檢視篩選路徑選項", - "FilterBy": "篩選路徑", - "ExitFilterMode": "停止按路徑篩選", - "FilterPathOption": "輸入要依路徑篩選的路徑", - "EnterFileName": "輸入路徑:", - "FilteringMenuTitle": "篩選", - "MustExitFilterModeTitle": "命令不可用", - "MustExitFilterModePrompt": "在按路徑篩選的模式下,該命令不可用。是否退出按路徑篩選的模式?", - "Diff": "差異", - "EnterRefToDiff": "輸入欲比較之 Ref", - "EnterRefName": "輸入 Ref:", - "ExitDiffMode": "退出差異模式", - "DiffingMenuTitle": "差異比較", - "SwapDiff": "反轉差異方向", - "ViewDiffingOptions": "開啟差異比較選單", - "OpenCommandLogMenu": "開啟命令記錄選單", - "ShowingGitDiff": "顯示輸出:", - "CommitDiff": "提交差異", - "CopyCommitHashToClipboard": "複製提交 hash 到剪貼簿", - "CommitHash": "提交 hash", - "CommitURL": "提交 URL", - "CommitMessage": "提交訊息", - "CommitAuthor": "提交者", - "CopyCommitAttributeToClipboard": "複製提交屬性", - "CopyBranchNameToClipboard": "複製分支名稱到剪貼簿", - "CopyPathToClipboard": "複製檔案名稱到剪貼簿", - "CommitPrefixPatternError": "commitPrefix 模式錯誤", - "CopySelectedTextToClipboard": "複製所選文本至剪貼簿", - "NoFilesStagedTitle": "沒有檔案預存", - "NoFilesStagedPrompt": "你沒有預存任何檔案。提交所有檔案?", - "BranchNotFoundTitle": "找不到分支", - "BranchNotFoundPrompt": "找不到分支。新分支名稱", - "BranchUnknown": "分支未知", - "DiscardChangeTitle": "取消預存行", - "DiscardChangePrompt": "是否刪除所選行(git reset)?此操作不可逆。\n將「gui.skipDiscardChangeWarning」設為 true 可禁用此警告。", - "CreateNewBranchFromCommit": "從提交建立新分支", - "BuildingPatch": "正在建立補丁", - "ViewCommits": "檢視提交", - "RunningCustomCommandStatus": "正在執行自訂命令", - "SubmoduleStashAndReset": "收藏未提交的子模組變更並更新", - "AndResetSubmodules": "以及重設子模組", - "EnterSubmoduleTooltip": "進入子模組", - "CopySubmoduleNameToClipboard": "複製子模組名稱到剪貼簿", - "RemoveSubmodule": "移除子模組", - "RemoveSubmodulePrompt": "是否確定要刪除子模組 '%s' 以及它相應的目錄?此操作是不可逆的。", - "ResettingSubmoduleStatus": "重設子模型中", - "NewSubmoduleName": "子模組名稱:", - "NewSubmoduleUrl": "新子模組 URL:", - "NewSubmodulePath": "新子模組路徑:", - "NewSubmodule": "新增子模組", - "AddingSubmoduleStatus": "正在新增子模組", - "UpdateSubmoduleUrl": "更新子模組 '%s' 的 URL", - "UpdatingSubmoduleUrlStatus": "正在更新 URL", - "EditSubmoduleUrl": "更新子模組 URL", - "InitializingSubmoduleStatus": "正在初始化子模組", - "InitSubmoduleTooltip": "初始化子模組", - "SubmoduleUpdateTooltip": "更新子模組", - "UpdatingSubmoduleStatus": "正在更新子模組", - "BulkInitSubmodules": "批量初始化子模組", - "BulkUpdateSubmodules": "批量更新子模組", - "BulkDeinitSubmodules": "批量解除子模組初始化", - "ViewBulkSubmoduleOptions": "查看批量子模組選項", - "BulkSubmoduleOptions": "批量子模組選項", - "RunningCommand": "正在執行命令", - "SubCommitsTitle": "子提交", - "SubmodulesTitle": "子模組", - "NavigationTitle": "移動", - "SuggestionsCheatsheetTitle": "提示", - "SuggestionsTitle": "提示(按 %s 進入焦點)", - "ExtrasTitle": "命令記錄", - "PullRequestURLCopiedToClipboard": "複製拉取請求 URL 至剪貼簿", - "CommitDiffCopiedToClipboard": "已複製提交差異至剪貼簿", - "CommitURLCopiedToClipboard": "已複製提交 URL 至剪貼簿", - "CommitMessageCopiedToClipboard": "已複製提交訊息至剪貼簿", - "CommitAuthorCopiedToClipboard": "已複製提交者至剪貼簿", - "PatchCopiedToClipboard": "已複製補丁至剪貼簿", - "CopiedToClipboard": "已複製至剪貼簿", - "ErrCannotEditDirectory": "無法編輯目錄:你只能編輯單獨的檔案", - "ErrStageDirWithInlineMergeConflicts": "不能預存/取消預存包含具備內嵌合併衝突的檔案的目錄。請先解決合併衝突", - "ErrRepositoryMovedOrDeleted": "找不到版本庫。可能已被移動或刪除", - "CommandLog": "命令記錄", - "ToggleShowCommandLog": "切換顯示/隱藏命令記錄", - "FocusCommandLog": "聚焦命令記錄", - "CommandLogHeader": " '%s' 隱藏/聚焦此面板\n", - "RandomTip": "隨機提示", - "ToggleWhitespaceInDiffView": "切換是否在差異檢視中顯示空格變更", - "IgnoreWhitespaceDiffViewSubTitle": "(忽略空格)", - "IgnoreWhitespaceNotSupportedHere": "在此檢視中不支援忽略空格", - "IncreaseContextInDiffView": "增加差異檢視中顯示變更周圍上下文的大小", - "DecreaseContextInDiffView": "減小差異檢視中顯示變更周圍上下文的大小", - "CreatePullRequestOptions": "建立拉取請求選項", - "DefaultBranch": "預設分支", - "SelectBranch": "選擇分支", - "CreatePullRequest": "建立拉取請求", - "SelectConfigFile": "選擇設定檔", - "NoConfigFileFoundErr": "找不到設定檔", - "LoadingFileSuggestions": "正在加載檔案建議", - "LoadingCommits": "正在加載提交", - "MustSpecifyOriginError": "如果指定分支,必須指定遠端", - "GitOutput": "git 輸出:", - "GitCommandFailed": "git 命令失敗。請查看命令記錄以獲取詳細資訊(按 %s 開啟)", - "AbortTitle": "中止%s", - "AbortPrompt": "是否確定要中止當前的%s?", - "OpenLogMenu": "開啟記錄選單", - "LogMenuTitle": "提交記錄選項", - "ToggleShowGitGraphAll": "切換顯示整個 git 圖表(將 `--all` 標誌傳遞給 `git log`)", - "ShowGitGraph": "顯示 git 圖表", - "SortOrder": "排序規則", - "SortAlphabetical": "依字母", - "SortByDate": "依時間", - "SortByRecency": "依最近使用", - "SortBasedOnReflog": "(依據歷史記錄)", - "SortCommits": "提交排序順序", - "CantChangeContextSizeError": "在製作補丁期間無法更改上下文大小,因為當發布功能時我們太懒了以至於沒有支援它。如果你真的需要它,請告訴我們!", - "OpenCommitInBrowser": "在瀏覽器中開啟提交", - "ViewBisectOptions": "查看二分選項", - "ConfirmRevertCommit": "是否還原 {{.selectedCommit}} ?", - "RewordInEditorTitle": "在編輯器中改寫", - "RewordInEditorPrompt": "是否在編輯器中改寫此提交?", - "HardResetAutostashPrompt": "是否強制重設為 '%s' ?如果需要會進行自動存儲。", - "UpstreamGone": "(遠端已經不存在)", - "NukeDescription": "如果你想讓所有工作樹上的變更消失,這就是正確的選項。如果有未提交的子模組變更,它們將被收藏在子模組中。", - "DiscardStagedChangesDescription": "這將創建一個新的存儲條目,其中只包含預存檔案,然後如果存儲條目不需要,將其刪除,因此工作樹僅保留未預存的變更。", - "EmptyOutput": "<空輸出>", - "Patch": "補丁", - "CustomPatch": "自定義補丁", - "CommitsCopied": "提交已複製", - "CommitCopied": "提交已複製", - "ResetPatch": "重設補丁", - "ApplyPatch": "套用補丁", - "ApplyPatchInReverse": "反向套用補丁", - "RemovePatchFromOriginalCommit": "從原始提交中刪除補丁(%s)", - "MovePatchOutIntoIndex": "將補丁移到預存區", - "MovePatchToSelectedCommit": "將補丁移到選定的提交(%s)", - "CopyPatchToClipboard": "將補丁複製到剪貼簿", - "NoMatchesFor": "沒有找到符合 '%s' %s 的結果", - "MatchesFor": "符合 '%s' 的結果(%d/%d)%s", - "SearchKeybindings": "%s:下一個結果,%s:上一個結果,%s:退出搜尋模式", - "SearchPrefix": "搜尋:", - "FilterPrefix": "篩選:", - "ExitSearchMode": "%s:退出搜尋模式", - "SwitchToWorktree": "切換至工作目錄面板", - "AlreadyCheckedOutByWorktree": "此分支已被檢出到 {{.worktreeName}} 是否切換到此工作目錄?", - "BranchCheckedOutByWorktree": "分支 {{.branchName}} 已被 {{.worktreeName}} 檢出", - "DetachWorktreeTooltip": "此將在工作目錄中執行 `git checkout --detach` 以解開分支與它的連結,但工作目錄本身將不被更動", - "Switching": "切換中", - "RemoveWorktree": "刪除工作目錄", - "RemoveWorktreeTitle": "刪除工作目錄", - "DetachWorktree": "解開工作目錄連結", - "DetachingWorktree": "正在解除工作目錄連結", - "WorktreesTitle": "工作目錄", - "WorktreeTitle": "工作目錄", - "RemoveWorktreePrompt": "是否刪除 {{.worktreeName}} 工作目錄?", - "ForceRemoveWorktreePrompt": "'{{.worktreeName}}' 包括已更動或未追蹤的檔案。是否繼續刪除工作目錄?", - "RemovingWorktree": "正在刪除工作目錄", - "AddingWorktree": "正在建立工作目錄", - "CantDeleteCurrentWorktree": "無法刪除當前工作目錄!", - "AlreadyInWorktree": "已經在目標工作目錄內", - "CantDeleteMainWorktree": "無法刪除主要工作目錄!", - "NoWorktreesThisRepo": "無工作目錄", - "MissingWorktree": "(失蹤)", - "MainWorktree": "(主要)", - "NewWorktreePath": "工作目錄路徑", - "NewWorktreeBase": "工作目錄來源", - "BranchNameCannotBeBlank": "分支名稱不能為空", - "NewBranchName": "分支名稱", - "NewBranchNameLeaveBlank": "分支名稱(留空將檢出 {{.default}})", - "ViewWorktreeOptions": "檢視工作目錄選項", - "CreateWorktreeFrom": "從 {{.ref}} 建立工作目錄", - "CreateWorktreeFromDetached": "從 {{.ref}} 建立工作目錄(未連結)", - "LcWorktree": "工作目錄", - "ChangingDirectoryTo": "切換至 {{.path}}", - "Name": "名稱", - "Branch": "分支", - "Path": "路徑", - "MarkedBaseCommitStatus": "為了變基已標注基準提交", - "MarkAsBaseCommit": "為了變基已標注提交為基準提交", - "MarkAsBaseCommitTooltip": "請為了下一次變基選擇一項基準提交;此將執行 `git rebase --onto`。", - "MarkedCommitMarker": "↑↑↑ 將由此變基 ↑↑↑", - "NoCopiedCommits": "未複製提交", - "DisabledMenuItemPrefix": "已停用:", - "QuickStartInteractiveRebase": "開始互動變基", - "ToggleRangeSelect": "切換拖曳選擇", - "Actions": { - "CheckoutCommit": "檢出提交", - "CheckoutTag": "檢出標籤", - "CheckoutBranch": "檢出分支", - "ForceCheckoutBranch": "強制檢出分支", - "DeleteLocalBranch": "刪除本地分支", - "Merge": "合併", - "RebaseBranch": "變基分支", - "RenameBranch": "重新命名分支", - "CreateBranch": "建立分支", - "FastForwardBranch": "快進分支", - "CherryPick": "(Cherry-pick)複製提交", - "CheckoutFile": "檢出檔案", - "SquashCommitDown": "下列次方執行 Squash", - "FixupCommit": "修復提交", - "RewordCommit": "改寫提交", - "DropCommit": "捨棄提交", - "EditCommit": "編輯提交", - "AmendCommit": "修改提交", - "ResetCommitAuthor": "重設提交作者", - "SetCommitAuthor": "設置提交作者", - "RevertCommit": "還原提交", - "CreateFixupCommit": "建立修改提交", - "SquashAllAboveFixupCommits": "Squash 所有上面的修改提交", - "MoveCommitUp": "上移提交", - "MoveCommitDown": "下移提交", - "CopyCommitMessageToClipboard": "將提交訊息複製到剪貼簿", - "CopyCommitDiffToClipboard": "將提交差異複製到剪貼簿", - "CopyCommitHashToClipboard": "將提交 hash 複製到剪貼簿", - "CopyCommitURLToClipboard": "將提交 URL 複製到剪貼簿", - "CopyCommitAuthorToClipboard": "將提交作者複製到剪貼簿", - "CopyCommitAttributeToClipboard": "複製到剪貼簿", - "CopyPatchToClipboard": "將補丁複製到剪貼簿", - "CustomCommand": "自定義命令", - "DiscardAllChangesInFile": "捨棄檔案中的所有更改", - "DiscardAllUnstagedChangesInFile": "捨棄檔案中未預存的所有更改", - "StageFile": "預存檔案", - "StageResolvedFiles": "預存已解決合併衝突的檔案", - "UnstageFile": "取消預存檔案", - "UnstageAllFiles": "取消預存所有檔案", - "StageAllFiles": "預存所有檔案", - "IgnoreExcludeFile": "忽略或排除檔案", - "IgnoreFileErr": "無法忽略 .gitignore 檔案", - "ExcludeFile": "排除檔案", - "ExcludeGitIgnoreErr": "無法排除 .gitignore 檔案", - "Commit": "提交", - "Push": "推送", - "Pull": "拉取", - "OpenFile": "開啟檔案", - "StashAllChanges": "收藏所有更改", - "StashAllChangesKeepIndex": "收藏所有更改並保留索引", - "StashStagedChanges": "收藏已預存的更改", - "StashUnstagedChanges": "收藏未預存的更改", - "StashIncludeUntrackedChanges": "收藏所有更改,包括未追蹤的檔案", - "GitFlowFinish": "`git flow` 完成", - "GitFlowStart": "`git flow` 開始", - "CopyToClipboard": "複製到剪貼簿", - "CopySelectedTextToClipboard": "複製所選文本到剪貼簿", - "RemovePatchFromCommit": "從提交中刪除補丁", - "MovePatchToSelectedCommit": "將補丁移動到所選提交", - "MovePatchIntoIndex": "將補丁移動到索引中", - "MovePatchIntoNewCommit": "將補丁移動到新提交中", - "DeleteRemoteBranch": "刪除遠端分支", - "SetBranchUpstream": "設置遠端分支", - "AddRemote": "添加遠端", - "RemoveRemote": "移除遠端", - "UpdateRemote": "更新遠端", - "ApplyPatch": "套用補丁", - "Stash": "收藏 (Stash)", - "RenameStash": "重命名暫存", - "RemoveSubmodule": "移除子模塊", - "ResetSubmodule": "重設子模塊", - "AddSubmodule": "添加子模塊", - "UpdateSubmoduleUrl": "更新子模塊 URL", - "InitialiseSubmodule": "初始化子模塊", - "BulkInitialiseSubmodules": "批量初始化子模塊", - "BulkUpdateSubmodules": "批量更新子模塊", - "BulkDeinitialiseSubmodules": "批量取消初始化子模塊", - "UpdateSubmodule": "更新子模塊", - "CreateLightweightTag": "建立輕量標籤", - "CreateAnnotatedTag": "建立附註標籤", - "DeleteLocalTag": "刪除本地標籤", - "PushTag": "推送標籤", - "NukeWorkingTree": "清空工作樹", - "DiscardUnstagedFileChanges": "放棄未預存的檔案更改", - "RemoveUntrackedFiles": "移除未追蹤的檔案", - "RemoveStagedFiles": "移除已預存的檔案", - "SoftReset": "軟重設(保留變動)", - "MixedReset": "混合重設", - "HardReset": "強制重設", - "Undo": "復原", - "Redo": "重做", - "CopyPullRequestURL": "複製拉取請求的 URL", - "OpenMergeTool": "開啟合併工具", - "OpenCommitInBrowser": "在瀏覽器中開啟提交", - "OpenPullRequest": "在瀏覽器中開啟拉取請求", - "StartBisect": "開始二分查找", - "ResetBisect": "重設二分查找", - "BisectSkip": "二分查找跳過", - "BisectMark": "二分查找標記" - }, - "Bisect": { - "MarkStart": "將 %s 標記為 %s(開始二分查找)", - "ResetTitle": "重設 `git bisect`", - "ResetPrompt": "是否重設 `git bisect`?", - "ResetOption": "重設二分查找", - "BisectMenuTitle": "二分查找", - "Mark": "將 %s 標記為 %s", - "SkipCurrent": "跳過 %s", - "CompleteTitle": "二分查找完成", - "CompletePrompt": "二分查找完成!以下提交引入了更改:\n\n%s\n\n是否重設 `git bisect` ?", - "CompletePromptIndeterminate": "二分查找完成!有一些提交被跳過,因此以下任何提交皆可能引進更改:\n\n%s\n\n是否重設 `git bisect`?", - "Bisecting": "二分查找中" - }, - "Log": { - "CopyToClipboard": "{{.str}} 已複製" - }, - "BreakingChangesByVersion": {} -} diff --git a/pkg/integration/README.md b/pkg/integration/README.md deleted file mode 100644 index 0c50d8f4e05..00000000000 --- a/pkg/integration/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# Integration Tests - -The pkg/integration package is for integration testing: that is, actually running a real lazygit session and having a robot pretend to be a human user and then making assertions that everything works as expected. - -TL;DR: integration tests live in pkg/integration/tests. Run integration tests with: - -```sh -go run cmd/integration_test/main.go tui -``` - -or - -```sh -go run cmd/integration_test/main.go cli [--slow or --sandbox] [testname or testpath...] -``` - -## Writing tests - -The tests live in pkg/integration/tests. Each test is registered in `pkg/integration/tests/test_list.go` which is an auto-generated file. You can re-generate that file by running `go generate ./...` at the root of the Lazygit repo. - -Each test has two important steps: the setup step and the run step. - -### Setup step - -In the setup step, we prepare a repo with shell commands, for example, creating a merge conflict that will need to be resolved upon opening lazygit. This is all done via the `shell` argument. - -When the test runs, lazygit will open in the same working directory that the shell ends up in (so if you want to start lazygit somewhere other than the default location, you can use `shell.Chdir()` at the end of the setup step to set that working directory. - -### Run step - -The run step has two arguments passed in: - -1. `t` (the test driver) -2. `keys` - -`t` is for driving the gui by pressing certain keys, selecting list items, etc. -`keys` is for use when getting the test to press a particular key e.g. `t.Views().Commits().Focus().PressKey(keys.Universal.Confirm)` - -## Running tests - -There are three ways to invoke a test: - -1. go run cmd/integration_test/main.go cli [--slow or --sandbox] [testname or testpath...] -2. go run cmd/integration_test/main.go tui -3. go test pkg/integration/clients/*.go - -The first, the test runner, is for directly running a test from the command line. If you pass no arguments, it runs all tests. -The second, the TUI, is for running tests from a terminal UI where it's easier to find a test and run it without having to copy it's name and paste it into the terminal. This is the easiest approach by far. -The third, the go-test command, intended only for use in CI, to be run along with the other `go test` tests. This runs the tests in headless mode so there's no visual output. - -The name of a test is based on its path, so the name of the test at `pkg/integration/tests/commit/new_branch.go` is commit/new_branch. So to run it with our test runner you would run `go run cmd/integration_test/main.go cli commit/new_branch`. - -You can pass the INPUT_DELAY env var to the test runner in order to set a delay in milliseconds between keypresses or mouse clicks, which helps for watching a test at a realistic speed to understand what it's doing. Or you can pass the '--slow' flag which sets a pre-set 'slow' key delay. In the tui you can press 't' to run the test in slow mode. - -The resultant repo will be stored in `test/_results`, so if you're not sure what went wrong you can go there and inspect the repo. - -### Running tests in VSCode - -If you've opened an integration test file in your editor you can run that file by bringing up the command panel with `cmd+shift+p` and typing 'run task', then selecting the test task you want to run - -![image](https://user-images.githubusercontent.com/8456633/201500427-b86e129f-5f35-4d55-b7bd-fff5d8e4a04e.png) -![image](https://user-images.githubusercontent.com/8456633/201500431-903deb8c-c210-4054-8514-ab7088c7a839.png) -The test will run in a VSCode terminal: -![image](https://user-images.githubusercontent.com/8456633/201500446-b87abf11-9653-438f-8a9a-e0bf8abdb7ee.png) - -### Debugging tests - -Debugging an integration test is possible in two ways: - -1. Use the -debug option of the integration test runner's "cli" command, e.g. `go run cmd/integration_test/main.go cli -debug tag/reset.go` -2. Select a test in the "tui" runner and hit "d" to debug it. - -In both cases the test runner will print to the console that it is waiting for a debugger to attach, so now you need to tell your debugger to attach to a running process with the name "test_lazygit". If you are using Visual Studio Code, an easy way to do that is to use the "Attach to integration test runner" debug configuration. The test runner will resume automatically when it detects that a debugger was attached. Don't forget to set a breakpoint in the code that you want to step through, otherwise the test will just finish (i.e. it doesn't stop in the debugger automatically). - -### Sandbox mode - -Say you want to do a manual test of how lazygit handles merge-conflicts, but you can't be bothered actually finding a way to create merge conflicts in a repo. To make your life easier, you can simply run a merge-conflicts test in sandbox mode, meaning the setup step is run for you, and then instead of the test driving the lazygit session, you're allowed to drive it yourself. - -To run a test in sandbox mode you can press 's' on a test in the test TUI or in the test runner pass the --sandbox argument. - -## Tips for writing tests - -### Handle most setup in the `shell` part of the test - -Try to do as much setup work as possible in your setup step. For example, if all you're testing is that the user is able to resolve merge conflicts, create the merge conflicts in the setup step. On the other hand, if you're testing to see that lazygit can warn the user about merge conflicts after an attempted merge, it's fine to wait until the run step to actually create the conflicts. If the run step is focused on the thing you're trying to test, the test will run faster and its intent will be clearer. - -### Create helper functions for (very) frequently used test logic - -If within a test directory you find several tests need to share some logic, you can create a file called `shared.go` in that directory to hold shared helper functions (see `pkg/integration/tests/filter_by_path/shared.go` for an example). - -If you need to share test logic across test directories you can put helper functions in the `tests/shared` package. If you find yourself frequently doing the same thing from within a test across test directories, for example, responding a particular popup, consider adding a helper method to `pkg/integration/components/common.go`. If you look around the code in the `components` directory you may find another place that's sensible to put your helper function. - -### Don't do too much in one test - -If you're testing different pieces of functionality, it's better to test them in isolation using multiple short tests, compared to one larger longer test. Sometimes it's appropriate to have a longer test which tests how various different pieces interact, but err on the side of keeping things short. - -## Testing against old git versions - -Our CI tests against multiple git versions. If your test fails on an old version, then to troubleshoot you'll need to install the failing git version. One option is to use [rtx](https://github.com/jdxcode/rtx) (see installation steps in the readme) with the git plugin like so: -```sh -rtx plugin add git -rtx install git 2.20.0 -rtx local git 2.20.0 -``` diff --git a/pkg/integration/clients/cli.go b/pkg/integration/clients/cli.go deleted file mode 100644 index 34a5f85bdba..00000000000 --- a/pkg/integration/clients/cli.go +++ /dev/null @@ -1,111 +0,0 @@ -package clients - -import ( - "log" - "os" - "os/exec" - "regexp" - "strconv" - "strings" - - "github.com/jesseduffield/lazycore/pkg/utils" - "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests" - "github.com/samber/lo" -) - -// see pkg/integration/README.md - -// The purpose of this program is to run integration tests. It does this by -// building our injector program (in the sibling injector directory) and then for -// each test we're running, invoke the injector program with the test's name as -// an environment variable. Then the injector finds the test and passes it to -// the lazygit startup code. - -// If invoked directly, you can specify tests to run by passing their names as positional arguments - -func RunCLI(testNames []string, slow bool, sandbox bool, waitForDebugger bool, raceDetector bool) { - inputDelay := tryConvert(os.Getenv("INPUT_DELAY"), 0) - if slow { - inputDelay = SLOW_INPUT_DELAY - } - - err := components.RunTests(components.RunTestArgs{ - Tests: getTestsToRun(testNames), - Logf: log.Printf, - RunCmd: runCmdInTerminal, - TestWrapper: runAndPrintFatalError, - Sandbox: sandbox, - WaitForDebugger: waitForDebugger, - RaceDetector: raceDetector, - CodeCoverageDir: "", - InputDelay: inputDelay, - MaxAttempts: 1, - }) - if err != nil { - log.Print(err.Error()) - } -} - -func runAndPrintFatalError(test *components.IntegrationTest, f func() error) { - if err := f(); err != nil { - log.Fatal(err.Error()) - } -} - -func getTestsToRun(testNames []string) []*components.IntegrationTest { - allIntegrationTests := tests.GetTests(utils.GetLazyRootDirectory()) - var testsToRun []*components.IntegrationTest - - if len(testNames) == 0 { - return allIntegrationTests - } - - testNames = lo.Map(testNames, func(name string, _ int) string { - // allowing full test paths to be passed for convenience - return strings.TrimSuffix( - regexp.MustCompile(`.*pkg/integration/tests/`).ReplaceAllString(name, ""), - ".go", - ) - }) - - if lo.SomeBy(testNames, func(name string) bool { - return strings.HasSuffix(name, "/shared") - }) { - log.Fatalf("'shared' is a reserved name for tests that are shared between multiple test files. Please rename your test.") - } - -outer: - for _, testName := range testNames { - // check if our given test name actually exists - for _, test := range allIntegrationTests { - if test.Name() == testName { - testsToRun = append(testsToRun, test) - continue outer - } - } - log.Fatalf("test %s not found. Perhaps you forgot to add it to `pkg/integration/integration_tests/test_list.go`? This can be done by running `go generate ./...` from the Lazygit root. You'll need to ensure that your test name and the file name match (where the test name is in PascalCase and the file name is in snake_case).", testName) - } - - return testsToRun -} - -func runCmdInTerminal(cmd *exec.Cmd) (int, error) { - cmd.Stdout = os.Stdout - cmd.Stdin = os.Stdin - cmd.Stderr = os.Stderr - - if err := cmd.Start(); err != nil { - return -1, err - } - return cmd.Process.Pid, cmd.Wait() -} - -func tryConvert(numStr string, defaultVal int) int { - num, err := strconv.Atoi(numStr) - if err != nil { - return defaultVal - } - - return num -} diff --git a/pkg/integration/clients/go_test.go b/pkg/integration/clients/go_test.go deleted file mode 100644 index 8984c759a5e..00000000000 --- a/pkg/integration/clients/go_test.go +++ /dev/null @@ -1,95 +0,0 @@ -//go:build !windows - -package clients - -// This file allows you to use `go test` to run integration tests. -// See pkg/integration/README.md for more info. - -import ( - "bytes" - "errors" - "io" - "os" - "os/exec" - "testing" - - "github.com/creack/pty" - "github.com/jesseduffield/lazycore/pkg/utils" - "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests" - "github.com/stretchr/testify/assert" -) - -func TestIntegration(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration tests in short mode") - } - - parallelTotal := tryConvert(os.Getenv("PARALLEL_TOTAL"), 1) - parallelIndex := tryConvert(os.Getenv("PARALLEL_INDEX"), 0) - raceDetector := os.Getenv("LAZYGIT_RACE_DETECTOR") != "" - // LAZYGIT_GOCOVERDIR is the directory where we write coverage files to. If this directory - // is defined, go binaries built with the -cover flag will write coverage files to - // to it. - codeCoverageDir := os.Getenv("LAZYGIT_GOCOVERDIR") - testNumber := 0 - - err := components.RunTests(components.RunTestArgs{ - Tests: tests.GetTests(utils.GetLazyRootDirectory()), - Logf: t.Logf, - RunCmd: runCmdHeadless, - TestWrapper: func(test *components.IntegrationTest, f func() error) { - defer func() { testNumber += 1 }() - if testNumber%parallelTotal != parallelIndex { - return - } - - t.Run(test.Name(), func(t *testing.T) { - t.Parallel() - err := f() - assert.NoError(t, err) - }) - }, - Sandbox: false, - WaitForDebugger: false, - RaceDetector: raceDetector, - CodeCoverageDir: codeCoverageDir, - InputDelay: 0, - // Allow two attempts at each test to get around flakiness - MaxAttempts: 2, - }) - - assert.NoError(t, err) -} - -func runCmdHeadless(cmd *exec.Cmd) (int, error) { - cmd.Env = append( - cmd.Env, - "HEADLESS=true", - "TERM=xterm", - ) - - // not writing stderr to the pty because we want to capture a panic if - // there is one. But some commands will not be in tty mode if stderr is - // not a terminal. We'll need to keep an eye out for that. - stderr := new(bytes.Buffer) - cmd.Stderr = stderr - - // these rows and columns are ignored because internally we use tcell's - // simulation screen. However we still need the pty for the sake of - // running other commands in a pty. - f, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: 300, Cols: 300}) - if err != nil { - return -1, err - } - - _, _ = io.Copy(io.Discard, f) - - if cmd.Wait() != nil { - _ = f.Close() - // return an error with the stderr output - return cmd.Process.Pid, errors.New(stderr.String()) - } - - return cmd.Process.Pid, f.Close() -} diff --git a/pkg/integration/clients/injector/main.go b/pkg/integration/clients/injector/main.go deleted file mode 100644 index 96a4fb1f084..00000000000 --- a/pkg/integration/clients/injector/main.go +++ /dev/null @@ -1,88 +0,0 @@ -package main - -import ( - "fmt" - "os" - "time" - - "github.com/jesseduffield/lazygit/pkg/app" - "github.com/jesseduffield/lazygit/pkg/app/daemon" - "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests" - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" - "github.com/mitchellh/go-ps" -) - -// The purpose of this program is to run lazygit with an integration test passed in. -// We could have done the check on TEST_NAME in the root main.go but -// that would mean lazygit would be depending on integration test code which -// would bloat the binary. - -// You should not invoke this program directly. Instead you should go through -// go run cmd/integration_test/main.go - -func main() { - dummyBuildInfo := &app.BuildInfo{ - Commit: "", - Date: "", - Version: "", - BuildSource: "integration test", - } - - integrationTest := getIntegrationTest() - - if os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) != "" && !daemon.InDaemonMode() { - println("Waiting for debugger to attach...") - for !isDebuggerAttached() { - time.Sleep(time.Millisecond * 100) - } - - println("Debugger attached, continuing") - } - - app.Start(dummyBuildInfo, integrationTest) -} - -func getIntegrationTest() integrationTypes.IntegrationTest { - if daemon.InDaemonMode() { - // if we've invoked lazygit as a daemon from within lazygit, - // we don't want to pass a test to the rest of the code. - return nil - } - - integrationTestName := os.Getenv(components.TEST_NAME_ENV_VAR) - if integrationTestName == "" { - panic(fmt.Sprintf( - "expected %s environment variable to be set, given that we're running an integration test", - components.TEST_NAME_ENV_VAR, - )) - } - - lazygitRootDir := os.Getenv(components.LAZYGIT_ROOT_DIR) - allTests := tests.GetTests(lazygitRootDir) - for _, candidateTest := range allTests { - if candidateTest.Name() == integrationTestName { - return candidateTest - } - } - - panic("Could not find integration test with name: " + integrationTestName) -} - -// Returns whether we are running under a debugger. It uses a heuristic to find -// out: when using dlv, it starts a debugserver executable (which is part of -// lldb), and the debuggee becomes a child process of that. So if the name of -// our parent process is "debugserver", we run under a debugger. This works even -// if the parent process used to be the shell and you then attach to the running -// executable. -// -// On Mac this works with VS Code, with the Jetbrains Goland IDE, and when using -// dlv attach in a terminal. I have not been able to verify that it works on -// other platforms, it may have to be adapted there. -func isDebuggerAttached() bool { - process, err := ps.FindProcess(os.Getppid()) - if err != nil { - return false - } - return process.Executable() == "debugserver" -} diff --git a/pkg/integration/clients/tui.go b/pkg/integration/clients/tui.go deleted file mode 100644 index 992f107e877..00000000000 --- a/pkg/integration/clients/tui.go +++ /dev/null @@ -1,408 +0,0 @@ -package clients - -import ( - "errors" - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazycore/pkg/utils" - "github.com/jesseduffield/lazygit/pkg/gui" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests" - "github.com/samber/lo" -) - -// This program lets you run integration tests from a TUI. See pkg/integration/README.md for more info. - -var SLOW_INPUT_DELAY = 600 - -func RunTUI(raceDetector bool) { - rootDir := utils.GetLazyRootDirectory() - testDir := filepath.Join(rootDir, "test", "integration") - - app := newApp(testDir) - app.loadTests() - - g, err := gocui.NewGui(gocui.NewGuiOpts{ - OutputMode: gocui.OutputTrue, - RuneReplacements: gui.RuneReplacements, - }) - if err != nil { - log.Panicln(err) - } - - g.Cursor = false - - app.g = g - - g.SetManagerFunc(app.layout) - - if err := g.SetKeybinding("list", gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { - if app.itemIdx > 0 { - app.itemIdx-- - } - listView, err := g.View("list") - if err != nil { - return err - } - listView.FocusPoint(0, app.itemIdx) - return nil - }); err != nil { - log.Panicln(err) - } - - if err := g.SetKeybinding("list", gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { - if app.itemIdx < len(app.filteredTests)-1 { - app.itemIdx++ - } - - listView, err := g.View("list") - if err != nil { - return err - } - listView.FocusPoint(0, app.itemIdx) - return nil - }); err != nil { - log.Panicln(err) - } - - if err := g.SetKeybinding("list", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { - log.Panicln(err) - } - - if err := g.SetKeybinding("list", 'q', gocui.ModNone, quit); err != nil { - log.Panicln(err) - } - - if err := g.SetKeybinding("list", 's', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { - currentTest := app.getCurrentTest() - if currentTest == nil { - return nil - } - - suspendAndRunTest(currentTest, true, false, raceDetector, 0) - - return nil - }); err != nil { - log.Panicln(err) - } - - if err := g.SetKeybinding("list", gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { - currentTest := app.getCurrentTest() - if currentTest == nil { - return nil - } - - suspendAndRunTest(currentTest, false, false, raceDetector, 0) - - return nil - }); err != nil { - log.Panicln(err) - } - - if err := g.SetKeybinding("list", 't', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { - currentTest := app.getCurrentTest() - if currentTest == nil { - return nil - } - - suspendAndRunTest(currentTest, false, false, raceDetector, SLOW_INPUT_DELAY) - - return nil - }); err != nil { - log.Panicln(err) - } - - if err := g.SetKeybinding("list", 'd', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { - currentTest := app.getCurrentTest() - if currentTest == nil { - return nil - } - - suspendAndRunTest(currentTest, false, true, raceDetector, 0) - - return nil - }); err != nil { - log.Panicln(err) - } - - if err := g.SetKeybinding("list", 'o', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { - currentTest := app.getCurrentTest() - if currentTest == nil { - return nil - } - - cmd := exec.Command("sh", "-c", fmt.Sprintf("code -r pkg/integration/tests/%s.go", currentTest.Name())) - if err := cmd.Run(); err != nil { - return err - } - - return nil - }); err != nil { - log.Panicln(err) - } - - if err := g.SetKeybinding("list", 'O', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { - currentTest := app.getCurrentTest() - if currentTest == nil { - return nil - } - - cmd := exec.Command("sh", "-c", fmt.Sprintf("code test/_results/%s", currentTest.Name())) - if err := cmd.Run(); err != nil { - return err - } - - return nil - }); err != nil { - log.Panicln(err) - } - - if err := g.SetKeybinding("list", '/', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { - app.filtering = true - if _, err := g.SetCurrentView("editor"); err != nil { - return err - } - editorView, err := g.View("editor") - if err != nil { - return err - } - editorView.Clear() - - return nil - }); err != nil { - log.Panicln(err) - } - - // not using the editor yet, but will use it to help filter the list - if err := g.SetKeybinding("editor", gocui.KeyEsc, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { - app.filtering = false - if _, err := g.SetCurrentView("list"); err != nil { - return err - } - - app.filteredTests = app.allTests - app.renderTests() - app.editorView.TextArea.Clear() - app.editorView.Clear() - app.editorView.Reset() - - return nil - }); err != nil { - log.Panicln(err) - } - - if err := g.SetKeybinding("editor", gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { - app.filtering = false - - if _, err := g.SetCurrentView("list"); err != nil { - return err - } - - app.renderTests() - - return nil - }); err != nil { - log.Panicln(err) - } - - err = g.MainLoop() - g.Close() - if errors.Is(err, gocui.ErrQuit) { - return - } - log.Panicln(err) -} - -type app struct { - allTests []*components.IntegrationTest - filteredTests []*components.IntegrationTest - itemIdx int - testDir string - filtering bool - g *gocui.Gui - listView *gocui.View - editorView *gocui.View -} - -func newApp(testDir string) *app { - return &app{testDir: testDir, allTests: tests.GetTests(utils.GetLazyRootDirectory())} -} - -func (self *app) getCurrentTest() *components.IntegrationTest { - self.adjustCursor() - if len(self.filteredTests) > 0 { - return self.filteredTests[self.itemIdx] - } - return nil -} - -func (self *app) loadTests() { - self.filteredTests = self.allTests - - self.adjustCursor() -} - -func (self *app) adjustCursor() { - self.itemIdx = utils.Clamp(self.itemIdx, 0, len(self.filteredTests)-1) -} - -func (self *app) filterWithString(needle string) { - if needle == "" { - self.filteredTests = self.allTests - } else { - self.filteredTests = lo.Filter(self.allTests, func(test *components.IntegrationTest, _ int) bool { - return strings.Contains(test.Name(), needle) - }) - } - - self.renderTests() - self.g.Update(func(g *gocui.Gui) error { return nil }) -} - -func (self *app) renderTests() { - self.listView.Clear() - for _, test := range self.filteredTests { - fmt.Fprintln(self.listView, test.Name()) - } -} - -func (self *app) wrapEditor(f func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool) func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool { - return func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool { - matched := f(v, key, ch, mod) - if matched { - self.filterWithString(v.TextArea.GetContent()) - } - return matched - } -} - -func suspendAndRunTest(test *components.IntegrationTest, sandbox bool, waitForDebugger bool, raceDetector bool, inputDelay int) { - if err := gocui.Screen.Suspend(); err != nil { - panic(err) - } - - runTuiTest(test, sandbox, waitForDebugger, raceDetector, inputDelay) - - fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint("press enter to return")) - _, _ = fmt.Scanln() // wait for enter press - - if err := gocui.Screen.Resume(); err != nil { - panic(err) - } -} - -func (self *app) layout(g *gocui.Gui) error { - maxX, maxY := g.Size() - descriptionViewHeight := 7 - keybindingsViewHeight := 3 - editorViewHeight := 3 - if !self.filtering { - editorViewHeight = 0 - } else { - descriptionViewHeight = 0 - keybindingsViewHeight = 0 - } - g.Cursor = self.filtering - g.FgColor = gocui.ColorGreen - listView, err := g.SetView("list", 0, 0, maxX-1, maxY-descriptionViewHeight-keybindingsViewHeight-editorViewHeight-1, 0) - if err != nil { - if !errors.Is(err, gocui.ErrUnknownView) { - return err - } - - if self.listView == nil { - self.listView = listView - } - - listView.Highlight = true - listView.SelBgColor = gocui.ColorBlue - self.renderTests() - listView.Title = "Tests" - listView.FgColor = gocui.ColorDefault - if _, err := g.SetCurrentView("list"); err != nil { - return err - } - } - - descriptionView, err := g.SetViewBeneath("description", "list", descriptionViewHeight) - if err != nil { - if !errors.Is(err, gocui.ErrUnknownView) { - return err - } - descriptionView.Title = "Test description" - descriptionView.Wrap = true - descriptionView.FgColor = gocui.ColorDefault - } - - keybindingsView, err := g.SetViewBeneath("keybindings", "description", keybindingsViewHeight) - if err != nil { - if !errors.Is(err, gocui.ErrUnknownView) { - return err - } - keybindingsView.Title = "Keybindings" - keybindingsView.Wrap = true - keybindingsView.FgColor = gocui.ColorDefault - fmt.Fprintln(keybindingsView, "up/down: navigate, enter: run test, t: run test slow, s: sandbox, d: debug test, o: open test file, shift+o: open test snapshot directory, forward-slash: filter") - } - - editorView, err := g.SetViewBeneath("editor", "keybindings", editorViewHeight) - if err != nil { - if !errors.Is(err, gocui.ErrUnknownView) { - return err - } - - if self.editorView == nil { - self.editorView = editorView - } - - editorView.Title = "Filter" - editorView.FgColor = gocui.ColorDefault - editorView.Editable = true - editorView.Editor = gocui.EditorFunc(self.wrapEditor(gocui.SimpleEditor)) - } - - currentTest := self.getCurrentTest() - if currentTest == nil { - return nil - } - - descriptionView.Clear() - fmt.Fprint(descriptionView, currentTest.Description()) - - return nil -} - -func quit(g *gocui.Gui, v *gocui.View) error { - return gocui.ErrQuit -} - -func runTuiTest(test *components.IntegrationTest, sandbox bool, waitForDebugger bool, raceDetector bool, inputDelay int) { - err := components.RunTests(components.RunTestArgs{ - Tests: []*components.IntegrationTest{test}, - Logf: log.Printf, - RunCmd: runCmdInTerminal, - TestWrapper: runAndPrintError, - Sandbox: sandbox, - WaitForDebugger: waitForDebugger, - RaceDetector: raceDetector, - CodeCoverageDir: "", - InputDelay: inputDelay, - MaxAttempts: 1, - }) - if err != nil { - log.Println(err.Error()) - } -} - -func runAndPrintError(test *components.IntegrationTest, f func() error) { - if err := f(); err != nil { - log.Println(err.Error()) - } -} diff --git a/pkg/integration/components/alert_driver.go b/pkg/integration/components/alert_driver.go deleted file mode 100644 index f0cfaa4eb07..00000000000 --- a/pkg/integration/components/alert_driver.go +++ /dev/null @@ -1,47 +0,0 @@ -package components - -type AlertDriver struct { - t *TestDriver - hasCheckedTitle bool - hasCheckedContent bool -} - -func (self *AlertDriver) getViewDriver() *ViewDriver { - return self.t.Views().Confirmation() -} - -// asserts that the alert view has the expected title -func (self *AlertDriver) Title(expected *TextMatcher) *AlertDriver { - self.getViewDriver().Title(expected) - - self.hasCheckedTitle = true - - return self -} - -// asserts that the alert view has the expected content -func (self *AlertDriver) Content(expected *TextMatcher) *AlertDriver { - self.getViewDriver().Content(expected) - - self.hasCheckedContent = true - - return self -} - -func (self *AlertDriver) Confirm() { - self.checkNecessaryChecksCompleted() - - self.getViewDriver().PressEnter() -} - -func (self *AlertDriver) Cancel() { - self.checkNecessaryChecksCompleted() - - self.getViewDriver().PressEscape() -} - -func (self *AlertDriver) 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/components/assertion_helper.go b/pkg/integration/components/assertion_helper.go deleted file mode 100644 index 0529e8bec7a..00000000000 --- a/pkg/integration/components/assertion_helper.go +++ /dev/null @@ -1,29 +0,0 @@ -package components - -import ( - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" -) - -type assertionHelper struct { - gui integrationTypes.GuiDriver -} - -func (self *assertionHelper) matchString(matcher *TextMatcher, context string, getValue func() string) { - self.assertWithRetries(func() (bool, string) { - value := getValue() - return matcher.context(context).test(value) - }) -} - -// We no longer assert with retries now that lazygit tells us when it's no longer -// busy. But I'm keeping the function in case we want to re-introduce it later. -func (self *assertionHelper) assertWithRetries(test func() (bool, string)) { - ok, message := test() - if !ok { - self.fail(message) - } -} - -func (self *assertionHelper) fail(message string) { - self.gui.Fail(message) -} diff --git a/pkg/integration/components/commit_description_panel_driver.go b/pkg/integration/components/commit_description_panel_driver.go deleted file mode 100644 index 905080380e8..00000000000 --- a/pkg/integration/components/commit_description_panel_driver.go +++ /dev/null @@ -1,68 +0,0 @@ -package components - -type CommitDescriptionPanelDriver struct { - t *TestDriver -} - -func (self *CommitDescriptionPanelDriver) getViewDriver() *ViewDriver { - return self.t.Views().CommitDescription() -} - -// asserts on the current context of the description -func (self *CommitDescriptionPanelDriver) Content(expected *TextMatcher) *CommitDescriptionPanelDriver { - self.getViewDriver().Content(expected) - - return self -} - -func (self *CommitDescriptionPanelDriver) Type(value string) *CommitDescriptionPanelDriver { - self.t.typeContent(value) - - return self -} - -func (self *CommitDescriptionPanelDriver) SwitchToSummary() *CommitMessagePanelDriver { - self.getViewDriver().PressTab() - return &CommitMessagePanelDriver{t: self.t} -} - -func (self *CommitDescriptionPanelDriver) AddNewline() *CommitDescriptionPanelDriver { - self.t.pressFast("") - return self -} - -func (self *CommitDescriptionPanelDriver) GoToBeginning() *CommitDescriptionPanelDriver { - numLines := len(self.getViewDriver().getView().BufferLines()) - for range numLines { - self.t.pressFast("") - } - - self.t.pressFast("") - return self -} - -func (self *CommitDescriptionPanelDriver) AddCoAuthor(author string) *CommitDescriptionPanelDriver { - self.t.press(self.t.keys.CommitMessage.CommitMenu) - self.t.ExpectPopup().Menu().Title(Equals("Commit Menu")). - Select(Contains("Add co-author")). - Confirm() - self.t.ExpectPopup().Prompt().Title(Contains("Add co-author")). - Type(author). - Confirm() - return self -} - -func (self *CommitDescriptionPanelDriver) Clear() *CommitDescriptionPanelDriver { - self.getViewDriver().Clear() - return self -} - -func (self *CommitDescriptionPanelDriver) Title(expected *TextMatcher) *CommitDescriptionPanelDriver { - self.getViewDriver().Title(expected) - - return self -} - -func (self *CommitDescriptionPanelDriver) Cancel() { - self.getViewDriver().PressEscape() -} diff --git a/pkg/integration/components/commit_message_panel_driver.go b/pkg/integration/components/commit_message_panel_driver.go deleted file mode 100644 index 047cc59b17d..00000000000 --- a/pkg/integration/components/commit_message_panel_driver.go +++ /dev/null @@ -1,78 +0,0 @@ -package components - -type CommitMessagePanelDriver struct { - t *TestDriver -} - -func (self *CommitMessagePanelDriver) getViewDriver() *ViewDriver { - return self.t.Views().CommitMessage() -} - -// asserts on the text initially present in the prompt -func (self *CommitMessagePanelDriver) InitialText(expected *TextMatcher) *CommitMessagePanelDriver { - return self.Content(expected) -} - -// asserts on the current context in the prompt -func (self *CommitMessagePanelDriver) Content(expected *TextMatcher) *CommitMessagePanelDriver { - self.getViewDriver().Content(expected) - - return self -} - -// asserts that the confirmation view has the expected title -func (self *CommitMessagePanelDriver) Title(expected *TextMatcher) *CommitMessagePanelDriver { - self.getViewDriver().Title(expected) - - return self -} - -func (self *CommitMessagePanelDriver) Type(value string) *CommitMessagePanelDriver { - self.t.typeContent(value) - - return self -} - -func (self *CommitMessagePanelDriver) SwitchToDescription() *CommitDescriptionPanelDriver { - self.getViewDriver().PressTab() - return &CommitDescriptionPanelDriver{t: self.t} -} - -func (self *CommitMessagePanelDriver) Clear() *CommitMessagePanelDriver { - self.getViewDriver().Clear() - return self -} - -func (self *CommitMessagePanelDriver) Confirm() { - self.getViewDriver().PressEnter() -} - -func (self *CommitMessagePanelDriver) Close() { - self.getViewDriver().PressEscape() -} - -func (self *CommitMessagePanelDriver) Cancel() { - self.getViewDriver().PressEscape() -} - -func (self *CommitMessagePanelDriver) SwitchToEditor() { - self.OpenCommitMenu() - self.t.ExpectPopup().Menu().Title(Equals("Commit Menu")). - Select(Contains("Open in editor")). - Confirm() -} - -func (self *CommitMessagePanelDriver) SelectPreviousMessage() *CommitMessagePanelDriver { - self.getViewDriver().SelectPreviousItem() - return self -} - -func (self *CommitMessagePanelDriver) SelectNextMessage() *CommitMessagePanelDriver { - self.getViewDriver().SelectNextItem() - return self -} - -func (self *CommitMessagePanelDriver) OpenCommitMenu() *CommitMessagePanelDriver { - self.t.press(self.t.keys.CommitMessage.CommitMenu) - return self -} diff --git a/pkg/integration/components/common.go b/pkg/integration/components/common.go deleted file mode 100644 index 2d62e9ea3c9..00000000000 --- a/pkg/integration/components/common.go +++ /dev/null @@ -1,92 +0,0 @@ -package components - -import "fmt" - -// for running common actions -type Common struct { - t *TestDriver -} - -func (self *Common) ContinueMerge() { - self.t.GlobalPress(self.t.keys.Universal.CreateRebaseOptionsMenu) - - self.t.ExpectPopup().Menu(). - Title(Equals("Rebase options")). - Select(Contains("continue")). - Confirm() -} - -func (self *Common) ContinueRebase() { - self.ContinueMerge() -} - -func (self *Common) AbortRebase() { - self.t.GlobalPress(self.t.keys.Universal.CreateRebaseOptionsMenu) - - self.t.ExpectPopup().Menu(). - Title(Equals("Rebase options")). - Select(Contains("abort")). - Confirm() -} - -func (self *Common) AbortMerge() { - self.t.GlobalPress(self.t.keys.Universal.CreateRebaseOptionsMenu) - - self.t.ExpectPopup().Menu(). - Title(Equals("Merge options")). - Select(Contains("abort")). - Confirm() -} - -func (self *Common) AcknowledgeConflicts() { - self.t.ExpectPopup().Menu(). - Title(Equals("Conflicts!")). - Select(Contains("View conflicts")). - Confirm() -} - -func (self *Common) ContinueOnConflictsResolved(command string) { - self.t.ExpectPopup().Confirmation(). - Title(Equals("Continue")). - Content(Contains(fmt.Sprintf("All merge conflicts resolved. Continue the %s?", command))). - Confirm() -} - -func (self *Common) ConfirmDiscardLines() { - self.t.ExpectPopup().Confirmation(). - Title(Equals("Discard change")). - Content(Contains("Are you sure you want to discard this change")). - Confirm() -} - -func (self *Common) SelectPatchOption(matcher *TextMatcher) { - self.t.GlobalPress(self.t.keys.Universal.CreatePatchOptionsMenu) - - self.t.ExpectPopup().Menu().Title(Equals("Patch options")).Select(matcher).Confirm() -} - -func (self *Common) ResetBisect() { - self.t.Views().Commits(). - Focus(). - Press(self.t.keys.Commits.ViewBisectOptions). - Tap(func() { - self.t.ExpectPopup().Menu(). - Title(Equals("Bisect")). - Select(Contains("Reset bisect")). - Confirm() - - self.t.ExpectPopup().Confirmation(). - Title(Equals("Reset 'git bisect'")). - Content(Contains("Are you sure you want to reset 'git bisect'?")). - Confirm() - }) -} - -func (self *Common) ResetCustomPatch() { - self.t.GlobalPress(self.t.keys.Universal.CreatePatchOptionsMenu) - - self.t.ExpectPopup().Menu(). - Title(Equals("Patch options")). - Select(Contains("Reset patch")). - Confirm() -} diff --git a/pkg/integration/components/confirmation_driver.go b/pkg/integration/components/confirmation_driver.go deleted file mode 100644 index 7934b351c83..00000000000 --- a/pkg/integration/components/confirmation_driver.go +++ /dev/null @@ -1,53 +0,0 @@ -package components - -type ConfirmationDriver struct { - t *TestDriver - hasCheckedTitle bool - hasCheckedContent bool -} - -func (self *ConfirmationDriver) getViewDriver() *ViewDriver { - return self.t.Views().Confirmation() -} - -// asserts that the confirmation view has the expected title -func (self *ConfirmationDriver) Title(expected *TextMatcher) *ConfirmationDriver { - self.getViewDriver().Title(expected) - - self.hasCheckedTitle = true - - return self -} - -// asserts that the confirmation view has the expected content -func (self *ConfirmationDriver) Content(expected *TextMatcher) *ConfirmationDriver { - self.getViewDriver().Content(expected) - - self.hasCheckedContent = true - - return self -} - -func (self *ConfirmationDriver) Confirm() { - self.checkNecessaryChecksCompleted() - - self.getViewDriver().PressEnter() -} - -func (self *ConfirmationDriver) Cancel() { - self.checkNecessaryChecksCompleted() - - 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/components/env.go b/pkg/integration/components/env.go deleted file mode 100644 index e7a8a694196..00000000000 --- a/pkg/integration/components/env.go +++ /dev/null @@ -1,65 +0,0 @@ -package components - -import ( - "fmt" - "os" -) - -const ( - // These values will be passed to lazygit - LAZYGIT_ROOT_DIR = "LAZYGIT_ROOT_DIR" - SANDBOX_ENV_VAR = "SANDBOX" - TEST_NAME_ENV_VAR = "TEST_NAME" - WAIT_FOR_DEBUGGER_ENV_VAR = "WAIT_FOR_DEBUGGER" - - // These values will be passed to both lazygit and shell commands - GIT_CONFIG_GLOBAL_ENV_VAR = "GIT_CONFIG_GLOBAL" - // We pass PWD because if it's defined, Go will use it as the working directory - // rather than make a syscall to the OS, and that means symlinks won't be resolved, - // which is good to test for. - PWD = "PWD" - - // We set $HOME and $GIT_CONFIG_NOGLOBAL during integration tests so - // that older versions of git that don't respect $GIT_CONFIG_GLOBAL - // will find the correct global config file for testing - HOME = "HOME" - GIT_CONFIG_NOGLOBAL = "GIT_CONFIG_NOGLOBAL" - - // These values will be passed through to lazygit and shell commands, with their - // values inherited from the host environment - PATH = "PATH" - TERM = "TERM" -) - -// Tests will inherit these environment variables from the host environment, rather -// than the test runner deciding the values itself. -// All other environment variables present in the host environment will be ignored. -// Having such a minimal list ensures that lazygit behaves the same across different test environments. -var hostEnvironmentAllowlist = [...]string{ - PATH, - TERM, -} - -// Returns a copy of the environment filtered by -// hostEnvironmentAllowlist -func allowedHostEnvironment() []string { - env := []string{} - for _, envVar := range hostEnvironmentAllowlist { - env = append(env, fmt.Sprintf("%s=%s", envVar, os.Getenv(envVar))) - } - return env -} - -func NewTestEnvironment(rootDir string) []string { - env := allowedHostEnvironment() - - // Set $HOME to control the global git config location for git - // versions <= 2.31.8 - env = append(env, fmt.Sprintf("%s=%s", HOME, testPath(rootDir))) - - // $GIT_CONFIG_GLOBAL controls global git config location for git - // versions >= 2.32.0 - env = append(env, fmt.Sprintf("%s=%s", GIT_CONFIG_GLOBAL_ENV_VAR, globalGitConfigPath(rootDir))) - - return env -} diff --git a/pkg/integration/components/file_system.go b/pkg/integration/components/file_system.go deleted file mode 100644 index 194125ec6d6..00000000000 --- a/pkg/integration/components/file_system.go +++ /dev/null @@ -1,52 +0,0 @@ -package components - -import ( - "fmt" - "os" -) - -type FileSystem struct { - *assertionHelper -} - -// This does _not_ check the files panel, it actually checks the filesystem -func (self *FileSystem) PathPresent(path string) *FileSystem { - self.assertWithRetries(func() (bool, string) { - _, err := os.Stat(path) - return err == nil, fmt.Sprintf("Expected path '%s' to exist, but it does not", path) - }) - return self -} - -// This does _not_ check the files panel, it actually checks the filesystem -func (self *FileSystem) PathNotPresent(path string) *FileSystem { - self.assertWithRetries(func() (bool, string) { - _, err := os.Stat(path) - return os.IsNotExist(err), fmt.Sprintf("Expected path '%s' to not exist, but it does", path) - }) - return self -} - -// Asserts that the file at the given path has the given content -func (self *FileSystem) FileContent(path string, matcher *TextMatcher) *FileSystem { - self.assertWithRetries(func() (bool, string) { - _, err := os.Stat(path) - if os.IsNotExist(err) { - return false, fmt.Sprintf("Expected path '%s' to exist, but it does not", path) - } - - output, err := os.ReadFile(path) - if err != nil { - return false, fmt.Sprintf("Expected error when reading file content at path '%s': %s", path, err.Error()) - } - - strOutput := string(output) - - if ok, errMsg := matcher.context("").test(strOutput); !ok { - return false, fmt.Sprintf("Unexpected content in file %s: %s", path, errMsg) - } - - return true, "" - }) - return self -} diff --git a/pkg/integration/components/git.go b/pkg/integration/components/git.go deleted file mode 100644 index 1b07e5cf802..00000000000 --- a/pkg/integration/components/git.go +++ /dev/null @@ -1,65 +0,0 @@ -package components - -import ( - "fmt" - "log" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" -) - -type Git struct { - *assertionHelper - shell *Shell -} - -func (self *Git) CurrentBranchName(expectedName string) *Git { - return self.assert([]string{"git", "rev-parse", "--abbrev-ref", "HEAD"}, expectedName) -} - -func (self *Git) TagNamesAt(ref string, expectedNames []string) *Git { - return self.assert([]string{"git", "tag", "--sort=v:refname", "--points-at", ref}, strings.Join(expectedNames, "\n")) -} - -func (self *Git) RemoteTagDeleted(ref string, tagName string) *Git { - return self.expect([]string{"git", "ls-remote", ref, fmt.Sprintf("refs/tags/%s", tagName)}, func(s string) (bool, string) { - return len(s) == 0, fmt.Sprintf("Expected tag %s to have been removed from %s", tagName, ref) - }) -} - -func (self *Git) assert(cmdArgs []string, expected string) *Git { - self.expect(cmdArgs, func(output string) (bool, string) { - return output == expected, fmt.Sprintf("Expected current branch name to be '%s', but got '%s'", expected, output) - }) - - return self -} - -func (self *Git) expect(cmdArgs []string, condition func(string) (bool, string)) *Git { - self.assertWithRetries(func() (bool, string) { - output, err := self.shell.runCommandWithOutput(cmdArgs) - if err != nil { - return false, fmt.Sprintf("Unexpected error running command: `%v`. Error: %s", cmdArgs, err.Error()) - } - actual := strings.TrimSpace(output) - return condition(actual) - }) - - return self -} - -func (self *Git) Version() *git_commands.GitVersion { - version, err := getGitVersion() - if err != nil { - log.Fatalf("Could not get git version: %v", err) - } - return version -} - -func (self *Git) GetCommitHash(ref string) string { - output, err := self.shell.runCommandWithOutput([]string{"git", "rev-parse", ref}) - if err != nil { - log.Fatalf("Could not get commit hash: %v", err) - } - return strings.TrimSpace(output) -} diff --git a/pkg/integration/components/int_matcher.go b/pkg/integration/components/int_matcher.go deleted file mode 100644 index 4cfd0f958fb..00000000000 --- a/pkg/integration/components/int_matcher.go +++ /dev/null @@ -1,58 +0,0 @@ -package components - -import ( - "fmt" -) - -type IntMatcher struct { - *Matcher[int] -} - -func (self *IntMatcher) EqualsInt(target int) *IntMatcher { - self.appendRule(matcherRule[int]{ - name: fmt.Sprintf("equals %d", target), - testFn: func(value int) (bool, string) { - return value == target, fmt.Sprintf("Expected %d to equal %d", value, target) - }, - }) - - return self -} - -func (self *IntMatcher) GreaterThan(target int) *IntMatcher { - self.appendRule(matcherRule[int]{ - name: fmt.Sprintf("greater than %d", target), - testFn: func(value int) (bool, string) { - return value > target, fmt.Sprintf("Expected %d to greater than %d", value, target) - }, - }) - - return self -} - -func (self *IntMatcher) LessThan(target int) *IntMatcher { - self.appendRule(matcherRule[int]{ - name: fmt.Sprintf("less than %d", target), - testFn: func(value int) (bool, string) { - return value < target, fmt.Sprintf("Expected %d to less than %d", value, target) - }, - }) - - return self -} - -func AnyInt() *IntMatcher { - return &IntMatcher{Matcher: &Matcher[int]{}} -} - -func EqualsInt(target int) *IntMatcher { - return AnyInt().EqualsInt(target) -} - -func GreaterThan(target int) *IntMatcher { - return AnyInt().GreaterThan(target) -} - -func LessThan(target int) *IntMatcher { - return AnyInt().LessThan(target) -} diff --git a/pkg/integration/components/matcher.go b/pkg/integration/components/matcher.go deleted file mode 100644 index d01aa92ef4b..00000000000 --- a/pkg/integration/components/matcher.go +++ /dev/null @@ -1,65 +0,0 @@ -package components - -import ( - "strings" - - "github.com/samber/lo" -) - -// for making assertions on string values -type Matcher[T any] struct { - rules []matcherRule[T] - - // this is printed when there's an error so that it's clear what the context of the assertion is - prefix string -} - -type matcherRule[T any] struct { - // e.g. "contains 'foo'" - name string - // returns a bool that says whether the test passed and if it returns false, it - // also returns a string of the error message - testFn func(T) (bool, string) -} - -func (self *Matcher[T]) name() string { - if len(self.rules) == 0 { - return "anything" - } - - return strings.Join( - lo.Map(self.rules, func(rule matcherRule[T], _ int) string { return rule.name }), - ", ", - ) -} - -func (self *Matcher[T]) test(value T) (bool, string) { - for _, rule := range self.rules { - ok, message := rule.testFn(value) - if ok { - continue - } - - if self.prefix != "" { - return false, self.prefix + " " + message - } - - return false, message - } - - return true, "" -} - -func (self *Matcher[T]) appendRule(rule matcherRule[T]) *Matcher[T] { - self.rules = append(self.rules, rule) - - return self -} - -// adds context so that if the matcher test(s) fails, we understand what we were trying to test. -// E.g. prefix: "Unexpected content in view 'files'." -func (self *Matcher[T]) context(prefix string) *Matcher[T] { - self.prefix = prefix - - return self -} diff --git a/pkg/integration/components/menu_driver.go b/pkg/integration/components/menu_driver.go deleted file mode 100644 index 95f29dcd380..00000000000 --- a/pkg/integration/components/menu_driver.go +++ /dev/null @@ -1,92 +0,0 @@ -package components - -type MenuDriver struct { - t *TestDriver - hasCheckedTitle bool -} - -func (self *MenuDriver) getViewDriver() *ViewDriver { - return self.t.Views().Menu() -} - -// asserts that the popup has the expected title -func (self *MenuDriver) Title(expected *TextMatcher) *MenuDriver { - self.getViewDriver().Title(expected) - - self.hasCheckedTitle = true - - return self -} - -func (self *MenuDriver) Confirm() *MenuDriver { - self.checkNecessaryChecksCompleted() - - self.getViewDriver().Press(self.t.keys.Universal.ConfirmMenu) - - return self -} - -func (self *MenuDriver) Cancel() { - self.checkNecessaryChecksCompleted() - - self.getViewDriver().PressEscape() -} - -func (self *MenuDriver) Select(option *TextMatcher) *MenuDriver { - self.getViewDriver().NavigateToLine(option) - - return self -} - -func (self *MenuDriver) Lines(matchers ...*TextMatcher) *MenuDriver { - self.getViewDriver().Lines(matchers...) - - return self -} - -func (self *MenuDriver) TopLines(matchers ...*TextMatcher) *MenuDriver { - self.getViewDriver().TopLines(matchers...) - - return self -} - -func (self *MenuDriver) ContainsLines(matchers ...*TextMatcher) *MenuDriver { - self.getViewDriver().ContainsLines(matchers...) - - return self -} - -func (self *MenuDriver) Filter(text string) *MenuDriver { - self.getViewDriver().FilterOrSearch(text) - - return self -} - -func (self *MenuDriver) LineCount(matcher *IntMatcher) *MenuDriver { - self.getViewDriver().LineCount(matcher) - - return self -} - -func (self *MenuDriver) Wait(milliseconds int) *MenuDriver { - self.getViewDriver().Wait(milliseconds) - - return self -} - -func (self *MenuDriver) Tooltip(option *TextMatcher) *MenuDriver { - self.t.Views().Tooltip().Content(option) - - return self -} - -func (self *MenuDriver) Tap(f func()) *MenuDriver { - self.getViewDriver().Tap(f) - return self -} - -func (self *MenuDriver) checkNecessaryChecksCompleted() { - if !self.hasCheckedTitle { - self.t.Fail("You must check the title of a menu popup by calling Title() before calling Confirm()/Cancel().") - } -} diff --git a/pkg/integration/components/paths.go b/pkg/integration/components/paths.go deleted file mode 100644 index b8b129d2c4f..00000000000 --- a/pkg/integration/components/paths.go +++ /dev/null @@ -1,36 +0,0 @@ -package components - -import "path/filepath" - -// convenience struct for easily getting directories within our test directory. -// We have one test directory for each test, found in test/_results. -type Paths struct { - // e.g. test/_results/test_name - root string -} - -func NewPaths(root string) Paths { - return Paths{root: root} -} - -// when a test first runs, it's situated in a repo called 'repo' within this -// directory. In its setup step, the test is allowed to create other repos -// alongside the 'repo' repo in this directory, for example, creating remotes -// or repos to add as submodules. -func (self Paths) Actual() string { - return filepath.Join(self.root, "actual") -} - -// this is the 'repo' directory within the 'actual' directory, -// where a lazygit test will start within. -func (self Paths) ActualRepo() string { - return filepath.Join(self.Actual(), "repo") -} - -func (self Paths) Config() string { - return filepath.Join(self.root, "used_config") -} - -func (self Paths) Root() string { - return self.root -} diff --git a/pkg/integration/components/popup.go b/pkg/integration/components/popup.go deleted file mode 100644 index 3cdc4f1f21c..00000000000 --- a/pkg/integration/components/popup.go +++ /dev/null @@ -1,88 +0,0 @@ -package components - -type Popup struct { - t *TestDriver -} - -func (self *Popup) Confirmation() *ConfirmationDriver { - self.inConfirm() - - return &ConfirmationDriver{t: self.t} -} - -func (self *Popup) inConfirm() { - self.t.assertWithRetries(func() (bool, string) { - currentView := self.t.gui.CurrentContext().GetView() - return currentView.Name() == "confirmation", "Expected confirmation popup to be focused" - }) -} - -func (self *Popup) Prompt() *PromptDriver { - self.inPrompt() - - return &PromptDriver{t: self.t} -} - -func (self *Popup) inPrompt() { - self.t.assertWithRetries(func() (bool, string) { - currentView := self.t.gui.CurrentContext().GetView() - return currentView.Name() == "prompt", "Expected prompt popup to be focused" - }) -} - -func (self *Popup) Alert() *AlertDriver { - self.inAlert() - - return &AlertDriver{t: self.t} -} - -func (self *AlertDriver) Tap(f func()) *AlertDriver { - self.getViewDriver().Tap(f) - return self -} - -func (self *Popup) inAlert() { - // basically the same thing as a confirmation popup with the current implementation - self.t.assertWithRetries(func() (bool, string) { - currentView := self.t.gui.CurrentContext().GetView() - return currentView.Name() == "confirmation", "Expected alert popup to be focused" - }) -} - -func (self *Popup) Menu() *MenuDriver { - self.inMenu() - - return &MenuDriver{t: self.t} -} - -func (self *Popup) inMenu() { - self.t.assertWithRetries(func() (bool, string) { - return self.t.gui.CurrentContext().GetView().Name() == "menu", "Expected popup menu to be focused" - }) -} - -func (self *Popup) CommitMessagePanel() *CommitMessagePanelDriver { - self.inCommitMessagePanel() - - return &CommitMessagePanelDriver{t: self.t} -} - -func (self *Popup) CommitDescriptionPanel() *CommitMessagePanelDriver { - self.inCommitDescriptionPanel() - - return &CommitMessagePanelDriver{t: self.t} -} - -func (self *Popup) inCommitMessagePanel() { - self.t.assertWithRetries(func() (bool, string) { - currentView := self.t.gui.CurrentContext().GetView() - return currentView.Name() == "commitMessage", "Expected commit message panel to be focused" - }) -} - -func (self *Popup) inCommitDescriptionPanel() { - self.t.assertWithRetries(func() (bool, string) { - currentView := self.t.gui.CurrentContext().GetView() - return currentView.Name() == "commitDescription", "Expected commit description panel to be focused" - }) -} diff --git a/pkg/integration/components/prompt_driver.go b/pkg/integration/components/prompt_driver.go deleted file mode 100644 index 2c29dd7c41d..00000000000 --- a/pkg/integration/components/prompt_driver.go +++ /dev/null @@ -1,102 +0,0 @@ -package components - -type PromptDriver struct { - t *TestDriver - hasCheckedTitle bool -} - -func (self *PromptDriver) getViewDriver() *ViewDriver { - return self.t.Views().Prompt() -} - -// asserts that the popup has the expected title -func (self *PromptDriver) Title(expected *TextMatcher) *PromptDriver { - self.getViewDriver().Title(expected) - - self.hasCheckedTitle = true - - return self -} - -// asserts on the text initially present in the prompt -func (self *PromptDriver) InitialText(expected *TextMatcher) *PromptDriver { - self.getViewDriver().Content(expected) - - return self -} - -func (self *PromptDriver) Type(value string) *PromptDriver { - self.t.typeContent(value) - - return self -} - -func (self *PromptDriver) Clear() *PromptDriver { - self.t.press(ClearKey) - - return self -} - -func (self *PromptDriver) Confirm() { - self.checkNecessaryChecksCompleted() - - self.getViewDriver().PressEnter() -} - -func (self *PromptDriver) Cancel() { - self.checkNecessaryChecksCompleted() - - self.getViewDriver().PressEscape() -} - -func (self *PromptDriver) checkNecessaryChecksCompleted() { - if !self.hasCheckedTitle { - self.t.Fail("You must check the title of a prompt popup by calling Title() before calling Confirm()/Cancel().") - } -} - -func (self *PromptDriver) SuggestionLines(matchers ...*TextMatcher) *PromptDriver { - self.t.Views().Suggestions().Lines(matchers...) - - return self -} - -func (self *PromptDriver) SuggestionTopLines(matchers ...*TextMatcher) *PromptDriver { - self.t.Views().Suggestions().TopLines(matchers...) - - return self -} - -func (self *PromptDriver) ConfirmFirstSuggestion() { - self.t.press(self.t.keys.Universal.TogglePanel) - self.t.Views().Suggestions(). - IsFocused(). - SelectedLineIdx(0). - Press(self.t.keys.Universal.ConfirmSuggestion) -} - -func (self *PromptDriver) ConfirmSuggestion(matcher *TextMatcher) { - self.t.press(self.t.keys.Universal.TogglePanel) - self.t.Views().Suggestions(). - IsFocused(). - NavigateToLine(matcher). - Press(self.t.keys.Universal.ConfirmSuggestion) -} - -func (self *PromptDriver) DeleteSuggestion(matcher *TextMatcher) *PromptDriver { - self.t.press(self.t.keys.Universal.TogglePanel) - self.t.Views().Suggestions(). - IsFocused(). - NavigateToLine(matcher) - self.t.press(self.t.keys.Universal.Remove) - return self -} - -func (self *PromptDriver) EditSuggestion(matcher *TextMatcher) *PromptDriver { - self.t.press(self.t.keys.Universal.TogglePanel) - self.t.Views().Suggestions(). - IsFocused(). - NavigateToLine(matcher) - self.t.press(self.t.keys.Universal.Edit) - return self -} diff --git a/pkg/integration/components/random.go b/pkg/integration/components/random.go deleted file mode 100644 index 33dcf3ce844..00000000000 --- a/pkg/integration/components/random.go +++ /dev/null @@ -1,497 +0,0 @@ -package components - -var RandomCommitMessages = []string{ - `Refactor HTTP client for better error handling`, - `Integrate pagination in user listings`, - `Fix incorrect type in updateUser function`, - `Create initial setup for postgres database`, - `Add unit tests for authentication service`, - `Improve efficiency of sorting algorithm in util package`, - `Resolve intermittent test failure in CartTest`, - `Introduce cache layer for product images`, - `Revamp User Interface of the settings page`, - `Remove deprecated uses of api endpoints`, - `Ensure proper escaping of SQL queries`, - `Implement feature flag for dark mode`, - `Add functionality for users to reset password`, - `Optimize performance of image loading on home screen`, - `Correct argument type in the sendEmail function`, - `Merge feature branch 'add-payment-gateway'`, - `Add validation to signup form fields`, - `Refactor User model to include middle name`, - `Update README with new setup instructions`, - `Extend session expiry time to 24 hours`, - `Implement rate limiting on login attempts`, - `Add sorting feature to product listing page`, - `Refactor logic in Lazygit Diff view`, - `Optimize Lazygit startup time`, - `Fix typos in documentation`, - `Move global variables to environment config`, - `Upgrade Rails version to 6.1.4`, - `Refactor user notifications system`, - `Implement user blocking functionality`, - `Improve Dockerfile for more efficient builds`, - `Introduce Redis for session management`, - `Ensure CSRF protection for all forms`, - `Implement bulk delete feature in admin panel`, - `Harden security of user password storage`, - `Resolve race condition in transaction handling`, - `Migrate legacy codebase to Typescript`, - `Update UX of password reset feature`, - `Add internationalization support for German`, - `Enhance logging in production environment`, - `Remove hardcoded values from payment module`, - `Introduce retry mechanism in network calls`, - `Handle edge case for zero quantity in cart`, - `Revamp error handling in user registration`, - `Replace deprecated lifecycle methods in React components`, - `Update styles according to new design guidelines`, - `Handle database connection failures gracefully`, - `Ensure atomicity of transactions in payment system`, - `Refactor session management using JWT`, - `Enhance user search with fuzzy matching`, - `Move constants to a separate config file`, - `Add TypeScript types to User module`, - `Implement automated backups for database`, - `Fix broken links on the help page`, - `Add end-to-end tests for checkout flow`, - `Add loading indicators to improve UX`, - `Improve accessibility of site navigation`, - `Refactor error messages for better clarity`, - `Enable gzip compression for faster page loads`, - `Set up CI/CD pipeline using GitHub actions`, - `Add a user-friendly 404 page`, - `Implement OAuth login with Google`, - `Resolve dependency conflicts in package.json`, - `Add proper alt text to all images for SEO`, - `Implement comment moderation feature`, - `Fix double encoding issue in URL parameters`, - `Resolve flickering issue in animation`, - `Update dependencies to latest stable versions`, - `Set proper cache headers for static assets`, - `Add structured data for better SEO`, - `Refactor to remove circular dependencies`, - `Add feature to report inappropriate content`, - `Implement mobile-friendly navigation menu`, - `Update privacy policy to comply with GDPR`, - `Fix memory leak issue in event listeners`, - `Improve form validation feedback for user`, - `Implement API versioning`, - `Improve resilience of system by adding circuit breaker`, - `Add sitemap.xml for better search engine indexing`, - `Set up performance monitoring with New Relic`, - `Introduce service worker for offline support`, - `Enhance email notifications with HTML templates`, - `Ensure all pages are responsive across devices`, - `Create helper functions to reduce code duplication`, - `Add 'remember me' feature to login`, - `Increase test coverage for User model`, - `Refactor error messages into a separate module`, - `Optimize images for faster loading`, - `Ensure correct HTTP status codes for all responses`, - `Implement auto-save feature in post editor`, - `Update user guide with new screenshots`, - `Implement load testing using Gatling`, - `Add keyboard shortcuts for commonly used actions`, - `Set up staging environment similar to production`, - `Ensure all forms use POST method for data submission`, - `Implement soft delete for user accounts`, - `Add Webpack for asset bundling`, - `Handle session timeout gracefully`, - `Remove unused code and libraries`, - `Integrate support for markdown in user posts`, - `Fix bug in timezone conversion.`, -} - -type RandomFile struct { - Name string - Content string -} - -var RandomFiles = []RandomFile{ - {Name: `http_client.go`, Content: `package httpclient`}, - {Name: `user_listings.go`, Content: `package listings`}, - {Name: `user_service.go`, Content: `package service`}, - {Name: `database_setup.sql`, Content: `CREATE TABLE`}, - {Name: `authentication_test.go`, Content: `package auth_test`}, - {Name: `utils/sorting.go`, Content: `package utils`}, - {Name: `tests/cart_test.go`, Content: `package tests`}, - {Name: `cache/product_images.go`, Content: `package cache`}, - {Name: `ui/settings_page.jsx`, Content: `import React`}, - {Name: `api/deprecated_endpoints.go`, Content: `package api`}, - {Name: `db/sql_queries.go`, Content: `package db`}, - {Name: `features/dark_mode.go`, Content: `package features`}, - {Name: `user/password_reset.go`, Content: `package user`}, - {Name: `performance/image_loading.go`, Content: `package performance`}, - {Name: `email/send_email.go`, Content: `package email`}, - {Name: `merge/payment_gateway.go`, Content: `package merge`}, - {Name: `forms/signup_validation.go`, Content: `package forms`}, - {Name: `models/user.go`, Content: `package models`}, - {Name: `README.md`, Content: `# Project`}, - {Name: `config/session.go`, Content: `package config`}, - {Name: `security/rate_limit.go`, Content: `package security`}, - {Name: `product/sort_list.go`, Content: `package product`}, - {Name: `lazygit/diff_view.go`, Content: `package lazygit`}, - {Name: `performance/lazygit.go`, Content: `package performance`}, - {Name: `docs/documentation.go`, Content: `package docs`}, - {Name: `config/global_variables.go`, Content: `package config`}, - {Name: `Gemfile`, Content: `source 'https://rubygems.org/'`}, - {Name: `notification/user_notification.go`, Content: `package notification`}, - {Name: `user/blocking.go`, Content: `package user`}, - {Name: `Dockerfile`, Content: `FROM ubuntu:18.04`}, - {Name: `redis/session_manager.go`, Content: `package redis`}, - {Name: `security/csrf_protection.go`, Content: `package security`}, - {Name: `admin/bulk_delete.go`, Content: `package admin`}, - {Name: `security/password_storage.go`, Content: `package security`}, - {Name: `transactions/transaction_handling.go`, Content: `package transactions`}, - {Name: `migrations/typescript_migration.go`, Content: `package migrations`}, - {Name: `ui/password_reset.jsx`, Content: `import React`}, - {Name: `i18n/german.go`, Content: `package i18n`}, - {Name: `logging/production_logging.go`, Content: `package logging`}, - {Name: `payment/hardcoded_values.go`, Content: `package payment`}, - {Name: `network/retry.go`, Content: `package network`}, - {Name: `cart/zero_quantity.go`, Content: `package cart`}, - {Name: `registration/error_handling.go`, Content: `package registration`}, - {Name: `components/deprecated_methods.jsx`, Content: `import React`}, - {Name: `styles/new_guidelines.css`, Content: `.class {}`}, - {Name: `db/connection_failure.go`, Content: `package db`}, - {Name: `payment/transaction_atomicity.go`, Content: `package payment`}, - {Name: `session/jwt_management.go`, Content: `package session`}, - {Name: `search/fuzzy_matching.go`, Content: `package search`}, - {Name: `config/constants.go`, Content: `package config`}, - {Name: `models/user_types.go`, Content: `package models`}, - {Name: `backup/database_backup.go`, Content: `package backup`}, - {Name: `help_page/links.go`, Content: `package help_page`}, - {Name: `tests/checkout_test.sql`, Content: `DELETE ALL TABLES;`}, - {Name: `ui/loading_indicator.jsx`, Content: `import React`}, - {Name: `navigation/site_navigation.go`, Content: `package navigation`}, - {Name: `error/error_messages.go`, Content: `package error`}, - {Name: `performance/gzip_compression.go`, Content: `package performance`}, - {Name: `.github/workflows/ci.yml`, Content: `name: CI`}, - {Name: `pages/404.html`, Content: ``}, - {Name: `oauth/google_login.go`, Content: `package oauth`}, - {Name: `package.json`, Content: `{}`}, - {Name: `seo/alt_text.go`, Content: `package seo`}, - {Name: `moderation/comment_moderation.go`, Content: `package moderation`}, - {Name: `url/double_encoding.go`, Content: `package url`}, - {Name: `animation/flickering.go`, Content: `package animation`}, - {Name: `upgrade_dependencies.sh`, Content: `#!/bin/sh`}, - {Name: `security/csrf_protection2.go`, Content: `package security`}, - {Name: `admin/bulk_delete2.go`, Content: `package admin`}, - {Name: `security/password_storage2.go`, Content: `package security`}, - {Name: `transactions/transaction_handling2.go`, Content: `package transactions`}, - {Name: `migrations/typescript_migration2.go`, Content: `package migrations`}, - {Name: `ui/password_reset2.jsx`, Content: `import React`}, - {Name: `i18n/german2.go`, Content: `package i18n`}, - {Name: `logging/production_logging2.go`, Content: `package logging`}, - {Name: `payment/hardcoded_values2.go`, Content: `package payment`}, - {Name: `network/retry2.go`, Content: `package network`}, - {Name: `cart/zero_quantity2.go`, Content: `package cart`}, - {Name: `registration/error_handling2.go`, Content: `package registration`}, - {Name: `components/deprecated_methods2.jsx`, Content: `import React`}, - {Name: `styles/new_guidelines2.css`, Content: `.class {}`}, - {Name: `db/connection_failure2.go`, Content: `package db`}, - {Name: `payment/transaction_atomicity2.go`, Content: `package payment`}, - {Name: `session/jwt_management2.go`, Content: `package session`}, - {Name: `search/fuzzy_matching2.go`, Content: `package search`}, - {Name: `config/constants2.go`, Content: `package config`}, - {Name: `models/user_types2.go`, Content: `package models`}, - {Name: `backup/database_backup2.go`, Content: `package backup`}, - {Name: `help_page/links2.go`, Content: `package help_page`}, - {Name: `tests/checkout_test2.go`, Content: `package tests`}, - {Name: `ui/loading_indicator2.jsx`, Content: `import React`}, - {Name: `navigation/site_navigation2.go`, Content: `package navigation`}, - {Name: `error/error_messages2.go`, Content: `package error`}, - {Name: `performance/gzip_compression2.go`, Content: `package performance`}, - {Name: `.github/workflows/ci2.yml`, Content: `name: CI`}, - {Name: `pages/4042.html`, Content: ``}, - {Name: `oauth/google_login2.go`, Content: `package oauth`}, - {Name: `package2.json`, Content: `{}`}, - {Name: `seo/alt_text2.go`, Content: `package seo`}, - {Name: `moderation/comment_moderation2.go`, Content: `package moderation`}, -} - -var RandomFileContents = []string{ - `package main - -import ( - "bytes" - "fmt" - "go/format" - "io/fs" - "os" - "strings" - - "github.com/samber/lo" -) - -func main() { - code := generateCode() - - formattedCode, err := format.Source(code) - if err != nil { - panic(err) - } - if err := os.WriteFile("test_list.go", formattedCode, 0o644); err != nil { - panic(err) - } -} -`, - ` -package tests - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazycore/pkg/utils" - "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/samber/lo" -) - -func GetTests() []*components.IntegrationTest { - // first we ensure that each test in this directory has actually been added to the above list. - testCount := 0 - - testNamesSet := set.NewFromSlice(lo.Map( - tests, - func(test *components.IntegrationTest, _ int) string { - return test.Name() - }, - )) -} -`, - ` -package components - -import ( - "os" - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/config" - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -// IntegrationTest describes an integration test that will be run against the lazygit gui. - -// our unit tests will use this description to avoid a panic caused by attempting -// to get the test's name via it's file's path. -const unitTestDescription = "test test" - -const ( - defaultWidth = 100 - defaultHeight = 100 -) -`, - `package components - -import ( - "fmt" - "time" - - "github.com/atotto/clipboard" - "github.com/jesseduffield/lazygit/pkg/config" - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" -) - -type TestDriver struct { - gui integrationTypes.GuiDriver - keys config.KeybindingConfig - inputDelay int - *assertionHelper - shell *Shell -} - -func NewTestDriver(gui integrationTypes.GuiDriver, shell *Shell, keys config.KeybindingConfig, inputDelay int) *TestDriver { - return &TestDriver{ - gui: gui, - keys: keys, - inputDelay: inputDelay, - assertionHelper: &assertionHelper{gui: gui}, - shell: shell, - } -} - -// key is something like 'w' or ''. It's best not to pass a direct value, -// but instead to go through the default user config to get a more meaningful key name -func (self *TestDriver) press(keyStr string) { - self.SetCaption(fmt.Sprintf("Pressing %s", keyStr)) - self.gui.PressKey(keyStr) - self.Wait(self.inputDelay) -} -`, - `package updates - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "runtime" - "strings" - "time" - - "github.com/go-errors/errors" - - "github.com/kardianos/osext" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/constants" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// Updater checks for updates and does updates -type Updater struct { - *common.Common - Config config.AppConfigurer - OSCommand *oscommands.OSCommand -} - -// Updaterer implements the check and update methods -type Updaterer interface { - CheckForNewUpdate() - Update() -} -`, - ` -package utils - -import ( - "fmt" - "regexp" - "strings" -) - -// IsValidEmail checks if an email address is valid -func IsValidEmail(email string) bool { - // Using a regex pattern to validate email addresses - // This is a simple example and might not cover all edge cases - emailPattern := ` + "`" + `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` + "`" + ` - match, _ := regexp.MatchString(emailPattern, email) - return match -} -`, - ` -package main - -import ( - "fmt" - "net/http" - "time" - - "github.com/jesseduffield/lazygit/pkg/utils" -) - -func main() { - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello, the current time is: %s", time.Now().Format(time.RFC3339)) - }) - - port := 8080 - utils.PrintMessage(fmt.Sprintf("Server is listening on port %d", port)) - http.ListenAndServe(fmt.Sprintf(":%d", port), nil) -} -`, - ` -package logging - -import ( - "fmt" - "os" - "time" -) - -// LogMessage represents a log message with its timestamp -type LogMessage struct { - Timestamp time.Time - Message string -} - -// Log writes a message to the log file along with a timestamp -func Log(message string) { - logFile, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - fmt.Println("Error opening log file:", err) - return - } - defer logFile.Close() - - logEntry := LogMessage{ - Timestamp: time.Now(), - Message: message, - } - - logLine := fmt.Sprintf("[%s] %s\n", logEntry.Timestamp.Format("2006-01-02 15:04:05"), logEntry.Message) - _, err = logFile.WriteString(logLine) - if err != nil { - fmt.Println("Error writing to log file:", err) - } -} -`, - ` -package encryption - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "errors" - "io" -) - -// Encrypt encrypts a plaintext using AES-GCM encryption -func Encrypt(key []byte, plaintext []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - aesGCM, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - nonce := make([]byte, aesGCM.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return nil, err - } - - ciphertext := aesGCM.Seal(nil, nonce, plaintext, nil) - return append(nonce, ciphertext...), nil -} -`, -} - -var RandomBranchNames = []string{ - "hotfix/fix-bug", - "r-u-fkn-srs", - "iserlohn-build", - "hotfix/fezzan-corridor", - "terra-investigation", - "quash-rebellion", - "feature/attack-on-odin", - "feature/peace-time", - "feature/repair-brunhild", - "feature/iserlohn-backdoor", - "bugfix/resolve-crash", - "enhancement/improve-performance", - "experimental/new-feature", - "release/v1.0.0", - "release/v2.0.0", - "chore/update-dependencies", - "docs/add-readme", - "refactor/cleanup-code", - "style/update-css", - "test/add-unit-tests", -} diff --git a/pkg/integration/components/runner.go b/pkg/integration/components/runner.go deleted file mode 100644 index f32fde61873..00000000000 --- a/pkg/integration/components/runner.go +++ /dev/null @@ -1,300 +0,0 @@ -package components - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - - lazycoreUtils "github.com/jesseduffield/lazycore/pkg/utils" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -type RunTestArgs struct { - Tests []*IntegrationTest - Logf func(format string, formatArgs ...interface{}) - RunCmd func(cmd *exec.Cmd) (int, error) - TestWrapper func(test *IntegrationTest, f func() error) - Sandbox bool - WaitForDebugger bool - RaceDetector bool - CodeCoverageDir string - InputDelay int - MaxAttempts int -} - -// This function lets you run tests either from within `go test` or from a regular binary. -// The reason for having two separate ways of testing is that `go test` isn't great at -// showing what's actually happening during the test, but it's still good at running -// tests in telling you about their results. -func RunTests(args RunTestArgs) error { - projectRootDir := lazycoreUtils.GetLazyRootDirectory() - err := os.Chdir(projectRootDir) - if err != nil { - return err - } - - testDir := filepath.Join(projectRootDir, "test", "_results") - if err := buildLazygit(args); err != nil { - return err - } - - gitVersion, err := getGitVersion() - if err != nil { - return err - } - - for _, test := range args.Tests { - args.TestWrapper(test, func() error { - paths := NewPaths( - filepath.Join(testDir, test.Name()), - ) - - for i := range args.MaxAttempts { - err := runTest(test, args, paths, projectRootDir, gitVersion) - if err != nil { - if i == args.MaxAttempts-1 { - return err - } - args.Logf("retrying test %s", test.Name()) - } else { - break - } - } - - return nil - }) - } - - return nil -} - -func runTest( - test *IntegrationTest, - args RunTestArgs, - paths Paths, - projectRootDir string, - gitVersion *git_commands.GitVersion, -) error { - if test.Skip() { - args.Logf("Skipping test %s", test.Name()) - return nil - } - - if !test.ShouldRunForGitVersion(gitVersion) { - args.Logf("Skipping test %s for git version %d.%d.%d", test.Name(), gitVersion.Major, gitVersion.Minor, gitVersion.Patch) - return nil - } - - workingDir, err := prepareTestDir(test, paths, projectRootDir) - if err != nil { - return err - } - - cmd, err := getLazygitCommand(test, args, paths, projectRootDir, workingDir) - if err != nil { - return err - } - - pid, err := args.RunCmd(cmd) - - // Print race detector log regardless of the command's exit status - if args.RaceDetector { - logPath := fmt.Sprintf("%s.%d", raceDetectorLogsPath(), pid) - if bytes, err := os.ReadFile(logPath); err == nil { - args.Logf("Race detector log:\n" + string(bytes)) - } - } - - return err -} - -func prepareTestDir( - test *IntegrationTest, - paths Paths, - rootDir string, -) (string, error) { - findOrCreateDir(paths.Root()) - deleteAndRecreateEmptyDir(paths.Actual()) - - err := os.Mkdir(paths.ActualRepo(), 0o777) - if err != nil { - return "", err - } - - workingDir := createFixture(test, paths, rootDir) - - return workingDir, nil -} - -func buildLazygit(testArgs RunTestArgs) error { - args := []string{"go", "build"} - if testArgs.WaitForDebugger { - // Disable compiler optimizations (-N) and inlining (-l) because this - // makes debugging work better - args = append(args, "-gcflags=all=-N -l") - } - if testArgs.RaceDetector { - args = append(args, "-race") - } - if testArgs.CodeCoverageDir != "" { - args = append(args, "-cover") - } - args = append(args, "-o", tempLazygitPath(), filepath.FromSlash("pkg/integration/clients/injector/main.go")) - osCommand := oscommands.NewDummyOSCommand() - return osCommand.Cmd.New(args).Run() -} - -// Sets up the fixture for test and returns the working directory to invoke -// lazygit in. -func createFixture(test *IntegrationTest, paths Paths, rootDir string) string { - env := NewTestEnvironment(rootDir) - - env = append(env, fmt.Sprintf("%s=%s", PWD, paths.ActualRepo())) - shell := NewShell( - paths.ActualRepo(), - env, - func(errorMsg string) { panic(errorMsg) }, - ) - shell.Init() - - test.SetupRepo(shell) - - return shell.dir -} - -func testPath(rootdir string) string { - return filepath.Join(rootdir, "test") -} - -func globalGitConfigPath(rootDir string) string { - return filepath.Join(testPath(rootDir), "global_git_config") -} - -func getGitVersion() (*git_commands.GitVersion, error) { - osCommand := oscommands.NewDummyOSCommand() - cmdObj := osCommand.Cmd.New([]string{"git", "--version"}) - versionStr, err := cmdObj.RunWithOutput() - if err != nil { - return nil, err - } - return git_commands.ParseGitVersion(versionStr) -} - -func getLazygitCommand( - test *IntegrationTest, - args RunTestArgs, - paths Paths, - rootDir string, - workingDir string, -) (*exec.Cmd, error) { - osCommand := oscommands.NewDummyOSCommand() - - err := os.RemoveAll(paths.Config()) - if err != nil { - return nil, err - } - - templateConfigDir := filepath.Join(rootDir, "test", "default_test_config") - err = oscommands.CopyDir(templateConfigDir, paths.Config()) - if err != nil { - return nil, err - } - - cmdArgs := []string{tempLazygitPath(), "-debug", "--use-config-dir=" + paths.Config()} - - resolvedExtraArgs := lo.Map(test.ExtraCmdArgs(), func(arg string, _ int) string { - return utils.ResolvePlaceholderString(arg, map[string]string{ - "actualPath": paths.Actual(), - "actualRepoPath": paths.ActualRepo(), - }) - }) - cmdArgs = append(cmdArgs, resolvedExtraArgs...) - - // Use a limited environment for test isolation, including pass through - // of just allowed host environment variables - cmdObj := osCommand.Cmd.NewWithEnviron(cmdArgs, NewTestEnvironment(rootDir)) - - // Integration tests related to symlink behavior need a PWD that - // preserves symlinks. By default, SetWd will set a symlink-resolved - // value for PWD. Here, we override that with the path (that may) - // contain a symlink to simulate behavior in a user's shell correctly. - cmdObj.SetWd(workingDir) - cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", PWD, workingDir)) - - cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", LAZYGIT_ROOT_DIR, rootDir)) - - if args.CodeCoverageDir != "" { - // We set this explicitly here rather than inherit it from the test runner's - // environment because the test runner has its own coverage directory that - // it writes to and so if we pass GOCOVERDIR to that, it will be overwritten. - cmdObj.AddEnvVars("GOCOVERDIR=" + args.CodeCoverageDir) - } - - cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", TEST_NAME_ENV_VAR, test.Name())) - if args.Sandbox { - cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", SANDBOX_ENV_VAR, "true")) - } - if args.WaitForDebugger { - cmdObj.AddEnvVars(fmt.Sprintf("%s=true", WAIT_FOR_DEBUGGER_ENV_VAR)) - } - // Set a race detector log path only to avoid spamming the terminal with the - // logs. We are not showing this anywhere yet. - cmdObj.AddEnvVars(fmt.Sprintf("GORACE=log_path=%s", raceDetectorLogsPath())) - if test.ExtraEnvVars() != nil { - for key, value := range test.ExtraEnvVars() { - cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", key, value)) - } - } - - if args.InputDelay > 0 { - cmdObj.AddEnvVars(fmt.Sprintf("INPUT_DELAY=%d", args.InputDelay)) - } - - cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", GIT_CONFIG_GLOBAL_ENV_VAR, globalGitConfigPath(rootDir))) - - return cmdObj.GetCmd(), nil -} - -func tempLazygitPath() string { - return filepath.Join("/tmp", "lazygit", "test_lazygit") -} - -func raceDetectorLogsPath() string { - return filepath.Join("/tmp", "lazygit", "race_log") -} - -func findOrCreateDir(path string) { - _, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - err = os.MkdirAll(path, 0o777) - if err != nil { - panic(err) - } - } else { - panic(err) - } - } -} - -func deleteAndRecreateEmptyDir(path string) { - // remove contents of integration test directory - dir, err := os.ReadDir(path) - if err != nil { - if os.IsNotExist(err) { - err = os.Mkdir(path, 0o777) - if err != nil { - panic(err) - } - } else { - panic(err) - } - } - for _, d := range dir { - os.RemoveAll(filepath.Join(path, d.Name())) - } -} diff --git a/pkg/integration/components/search_driver.go b/pkg/integration/components/search_driver.go deleted file mode 100644 index 498047cce9d..00000000000 --- a/pkg/integration/components/search_driver.go +++ /dev/null @@ -1,39 +0,0 @@ -package components - -// TODO: soft-code this -const ClearKey = "" - -type SearchDriver struct { - t *TestDriver -} - -func (self *SearchDriver) getViewDriver() *ViewDriver { - return self.t.Views().Search() -} - -// asserts on the text initially present in the prompt -func (self *SearchDriver) InitialText(expected *TextMatcher) *SearchDriver { - self.getViewDriver().Content(expected) - - return self -} - -func (self *SearchDriver) Type(value string) *SearchDriver { - self.t.typeContent(value) - - return self -} - -func (self *SearchDriver) Clear() *SearchDriver { - self.t.press(ClearKey) - - return self -} - -func (self *SearchDriver) Confirm() { - self.getViewDriver().PressEnter() -} - -func (self *SearchDriver) Cancel() { - self.getViewDriver().PressEscape() -} diff --git a/pkg/integration/components/shell.go b/pkg/integration/components/shell.go deleted file mode 100644 index b89e08d2bf3..00000000000 --- a/pkg/integration/components/shell.go +++ /dev/null @@ -1,519 +0,0 @@ -package components - -import ( - "fmt" - "io" - "math/rand" - "os" - "os/exec" - "path/filepath" - "runtime" - "time" -) - -// this is for running shell commands, mostly for the sake of setting up the repo -// but you can also run the commands from within lazygit to emulate things happening -// in the background. -type Shell struct { - // working directory the shell is invoked in - dir string - // passed into each command - env []string - - // when running the shell outside the gui we can directly panic on failure, - // but inside the gui we need to close the gui before panicking - fail func(string) - - randomFileContentIndex int -} - -func NewShell(dir string, env []string, fail func(string)) *Shell { - return &Shell{dir: dir, env: env, fail: fail} -} - -func (self *Shell) RunCommand(args []string) *Shell { - return self.RunCommandWithEnv(args, []string{}) -} - -// Run a command with additional environment variables set -func (self *Shell) RunCommandWithEnv(args []string, env []string) *Shell { - output, err := self.runCommandWithOutputAndEnv(args, env) - if err != nil { - self.fail(fmt.Sprintf("error running command: %v\n%s", args, output)) - } - - return self -} - -func (self *Shell) RunCommandExpectError(args []string) *Shell { - output, err := self.runCommandWithOutput(args) - if err == nil { - self.fail(fmt.Sprintf("Expected error running shell command: %v\n%s", args, output)) - } - - return self -} - -func (self *Shell) runCommandWithOutput(args []string) (string, error) { - return self.runCommandWithOutputAndEnv(args, []string{}) -} - -func (self *Shell) runCommandWithOutputAndEnv(args []string, env []string) (string, error) { - cmd := exec.Command(args[0], args[1:]...) - cmd.Env = append(self.env, env...) - cmd.Dir = self.dir - - output, err := cmd.CombinedOutput() - - return string(output), err -} - -func (self *Shell) RunShellCommand(cmdStr string) *Shell { - shell := "sh" - shellArg := "-c" - if runtime.GOOS == "windows" { - shell = "cmd" - shellArg = "/C" - } - - cmd := exec.Command(shell, shellArg, cmdStr) - cmd.Env = os.Environ() - cmd.Dir = self.dir - - output, err := cmd.CombinedOutput() - if err != nil { - self.fail(fmt.Sprintf("error running shell command: %s\n%s", cmdStr, string(output))) - } - - return self -} - -func (self *Shell) CreateFile(path string, content string) *Shell { - fullPath := filepath.Join(self.dir, path) - - // create any required directories - dir := filepath.Dir(fullPath) - if err := os.MkdirAll(dir, 0o755); err != nil { - self.fail(fmt.Sprintf("error creating directory: %s\n%s", dir, err)) - } - - err := os.WriteFile(fullPath, []byte(content), 0o644) - if err != nil { - self.fail(fmt.Sprintf("error creating file: %s\n%s", fullPath, err)) - } - - return self -} - -func (self *Shell) DeleteFile(path string) *Shell { - fullPath := filepath.Join(self.dir, path) - err := os.RemoveAll(fullPath) - if err != nil { - self.fail(fmt.Sprintf("error deleting file: %s\n%s", fullPath, err)) - } - - return self -} - -func (self *Shell) CreateDir(path string) *Shell { - fullPath := filepath.Join(self.dir, path) - if err := os.MkdirAll(fullPath, 0o755); err != nil { - self.fail(fmt.Sprintf("error creating directory: %s\n%s", fullPath, err)) - } - - return self -} - -func (self *Shell) UpdateFile(path string, content string) *Shell { - fullPath := filepath.Join(self.dir, path) - err := os.WriteFile(fullPath, []byte(content), 0o644) - if err != nil { - self.fail(fmt.Sprintf("error updating file: %s\n%s", fullPath, err)) - } - - return self -} - -func (self *Shell) NewBranch(name string) *Shell { - return self.RunCommand([]string{"git", "checkout", "-b", name}) -} - -func (self *Shell) NewBranchFrom(name string, from string) *Shell { - return self.RunCommand([]string{"git", "checkout", "-b", name, from}) -} - -func (self *Shell) RenameCurrentBranch(newName string) *Shell { - return self.RunCommand([]string{"git", "branch", "-m", newName}) -} - -func (self *Shell) Checkout(name string) *Shell { - return self.RunCommand([]string{"git", "checkout", name}) -} - -func (self *Shell) Merge(name string) *Shell { - return self.RunCommand([]string{"git", "merge", "--commit", "--no-ff", name}) -} - -func (self *Shell) ContinueMerge() *Shell { - return self.RunCommand([]string{"git", "-c", "core.editor=true", "merge", "--continue"}) -} - -func (self *Shell) GitAdd(path string) *Shell { - return self.RunCommand([]string{"git", "add", path}) -} - -func (self *Shell) GitAddAll() *Shell { - return self.RunCommand([]string{"git", "add", "-A"}) -} - -func (self *Shell) Commit(message string) *Shell { - return self.RunCommand([]string{"git", "commit", "-m", message}) -} - -func (self *Shell) CommitInWorktreeOrSubmodule(worktreePath string, message string) *Shell { - return self.RunCommand([]string{"git", "-C", worktreePath, "commit", "-m", message}) -} - -func (self *Shell) EmptyCommit(message string) *Shell { - return self.RunCommand([]string{"git", "commit", "--allow-empty", "-m", message}) -} - -func (self *Shell) EmptyCommitWithBody(subject string, body string) *Shell { - return self.RunCommand([]string{"git", "commit", "--allow-empty", "-m", subject, "-m", body}) -} - -func (self *Shell) EmptyCommitDaysAgo(message string, daysAgo int) *Shell { - return self.RunCommand([]string{"git", "commit", "--allow-empty", "--date", fmt.Sprintf("%d days ago", daysAgo), "-m", message}) -} - -func (self *Shell) EmptyCommitWithDate(message string, date string) *Shell { - env := []string{ - "GIT_AUTHOR_DATE=" + date, - "GIT_COMMITTER_DATE=" + date, - } - return self.RunCommandWithEnv([]string{"git", "commit", "--allow-empty", "-m", message}, env) -} - -func (self *Shell) Revert(ref string) *Shell { - return self.RunCommand([]string{"git", "revert", ref}) -} - -func (self *Shell) AssertRemoteTagNotFound(upstream, name string) *Shell { - return self.RunCommandExpectError([]string{"git", "ls-remote", "--exit-code", upstream, fmt.Sprintf("refs/tags/%s", name)}) -} - -func (self *Shell) CreateLightweightTag(name string, ref string) *Shell { - return self.RunCommand([]string{"git", "tag", name, ref}) -} - -func (self *Shell) CreateAnnotatedTag(name string, message string, ref string) *Shell { - return self.RunCommand([]string{"git", "tag", "-a", name, "-m", message, ref}) -} - -func (self *Shell) PushBranch(upstream, branch string) *Shell { - return self.RunCommand([]string{"git", "push", upstream, branch}) -} - -func (self *Shell) PushBranchAndSetUpstream(upstream, branch string) *Shell { - return self.RunCommand([]string{"git", "push", "--set-upstream", upstream, branch}) -} - -// convenience method for creating a file and adding it -func (self *Shell) CreateFileAndAdd(fileName string, fileContents string) *Shell { - return self. - CreateFile(fileName, fileContents). - GitAdd(fileName) -} - -// convenience method for updating a file and adding it -func (self *Shell) UpdateFileAndAdd(fileName string, fileContents string) *Shell { - return self. - UpdateFile(fileName, fileContents). - GitAdd(fileName) -} - -// convenience method for deleting a file and adding it -func (self *Shell) DeleteFileAndAdd(fileName string) *Shell { - return self. - DeleteFile(fileName). - GitAdd(fileName) -} - -func (self *Shell) RenameFileInGit(oldName string, newName string) *Shell { - return self.RunCommand([]string{"git", "mv", oldName, newName}) -} - -// creates commits 01, 02, 03, ..., n with a new file in each -// The reason for padding with zeroes is so that it's easier to do string -// matches on the commit messages when there are many of them -func (self *Shell) CreateNCommits(n int) *Shell { - return self.CreateNCommitsStartingAt(n, 1) -} - -func (self *Shell) CreateNCommitsStartingAt(n, startIndex int) *Shell { - for i := startIndex; i < startIndex+n; i++ { - self.CreateFileAndAdd( - fmt.Sprintf("file%02d.txt", i), - fmt.Sprintf("file%02d content", i), - ). - Commit(fmt.Sprintf("commit %02d", i)) - } - - return self -} - -// Only to be used in demos, because the list might change and we don't want -// tests to break when it does. -func (self *Shell) CreateNCommitsWithRandomMessages(n int) *Shell { - for i := range n { - file := RandomFiles[i] - self.CreateFileAndAdd( - file.Name, - file.Content, - ). - Commit(RandomCommitMessages[i]) - } - - return self -} - -// This creates a repo history of commits -// It uses a branching strategy where each feature branch is directly branched off -// of the master branch -// Only to be used in demos -func (self *Shell) CreateRepoHistory() *Shell { - authors := []string{"Yang Wen-li", "Siegfried Kircheis", "Paul Oberstein", "Oscar Reuenthal", "Fredrica Greenhill"} - - numAuthors := 5 - numBranches := 10 - numInitialCommits := 20 - maxCommitsPerBranch := 5 - // Each commit will happen on a separate day - repoStartDaysAgo := 100 - - totalCommits := 0 - - // Generate commits - for i := range numInitialCommits { - author := authors[i%numAuthors] - commitMessage := RandomCommitMessages[totalCommits%len(RandomCommitMessages)] - - self.SetAuthor(author, "") - self.EmptyCommitDaysAgo(commitMessage, repoStartDaysAgo-totalCommits) - totalCommits++ - } - - // Generate branches and merges - for i := range numBranches { - // We'll have one author creating all the commits in the branch - author := authors[i%numAuthors] - branchName := RandomBranchNames[i%len(RandomBranchNames)] - - // Choose a random commit within the last 20 commits on the master branch - lastMasterCommit := totalCommits - 1 - commitOffset := rand.Intn(min(lastMasterCommit, 5)) + 1 - - // Create the feature branch and checkout the chosen commit - self.NewBranchFrom(branchName, fmt.Sprintf("master~%d", commitOffset)) - - numCommitsInBranch := rand.Intn(maxCommitsPerBranch) + 1 - for range numCommitsInBranch { - commitMessage := RandomCommitMessages[totalCommits%len(RandomCommitMessages)] - - self.SetAuthor(author, "") - self.EmptyCommitDaysAgo(commitMessage, repoStartDaysAgo-totalCommits) - totalCommits++ - } - - self.Checkout("master") - - prevCommitterDate := os.Getenv("GIT_COMMITTER_DATE") - prevAuthorDate := os.Getenv("GIT_AUTHOR_DATE") - - commitDate := time.Now().Add(time.Duration(totalCommits-repoStartDaysAgo) * time.Hour * 24) - os.Setenv("GIT_COMMITTER_DATE", commitDate.Format(time.RFC3339)) - os.Setenv("GIT_AUTHOR_DATE", commitDate.Format(time.RFC3339)) - - // Merge branch into master - self.RunCommand([]string{"git", "merge", "--no-ff", branchName, "-m", fmt.Sprintf("Merge %s into master", branchName)}) - - os.Setenv("GIT_COMMITTER_DATE", prevCommitterDate) - os.Setenv("GIT_AUTHOR_DATE", prevAuthorDate) - } - - return self -} - -// Creates a commit with a random file -// Only to be used in demos -func (self *Shell) RandomChangeCommit(message string) *Shell { - index := self.randomFileContentIndex - self.randomFileContentIndex++ - randomFileName := fmt.Sprintf("random-%d.go", index) - self.CreateFileAndAdd(randomFileName, RandomFileContents[index%len(RandomFileContents)]) - return self.Commit(message) -} - -func (self *Shell) SetConfig(key string, value string) *Shell { - self.RunCommand([]string{"git", "config", "--local", key, value}) - return self -} - -func (self *Shell) CloneIntoRemote(name string) *Shell { - self.Clone(name) - self.RunCommand([]string{"git", "remote", "add", name, "../" + name}) - self.RunCommand([]string{"git", "fetch", name}) - - return self -} - -func (self *Shell) CloneIntoSubmodule(submoduleName string, submodulePath string) *Shell { - self.Clone(submoduleName) - self.RunCommand([]string{"git", "submodule", "add", "--name", submoduleName, "../" + submoduleName, submodulePath}) - - return self -} - -func (self *Shell) Clone(repoName string) *Shell { - self.RunCommand([]string{"git", "clone", "--bare", ".", "../" + repoName}) - - return self -} - -func (self *Shell) CloneNonBare(repoName string) *Shell { - self.RunCommand([]string{"git", "clone", ".", "../" + repoName}) - - return self -} - -func (self *Shell) SetBranchUpstream(branch string, upstream string) *Shell { - self.RunCommand([]string{"git", "branch", "--set-upstream-to=" + upstream, branch}) - - return self -} - -func (self *Shell) RemoveRemoteBranch(remoteName string, branch string) *Shell { - self.RunCommand([]string{"git", "-C", "../" + remoteName, "branch", "-d", branch}) - - return self -} - -func (self *Shell) HardReset(ref string) *Shell { - self.RunCommand([]string{"git", "reset", "--hard", ref}) - return self -} - -func (self *Shell) Stash(message string) *Shell { - self.RunCommand([]string{"git", "stash", "push", "-m", message}) - return self -} - -func (self *Shell) StartBisect(good string, bad string) *Shell { - self.RunCommand([]string{"git", "bisect", "start", good, bad}) - return self -} - -func (self *Shell) Init() *Shell { - self.RunCommand([]string{"git", "-c", "init.defaultBranch=master", "init"}) - return self -} - -func (self *Shell) AddWorktree(base string, path string, newBranchName string) *Shell { - return self.RunCommand([]string{ - "git", "worktree", "add", "-b", - newBranchName, path, base, - }) -} - -// add worktree and have it checkout the base branch -func (self *Shell) AddWorktreeCheckout(base string, path string) *Shell { - return self.RunCommand([]string{ - "git", "worktree", "add", path, base, - }) -} - -func (self *Shell) AddFileInWorktreeOrSubmodule(worktreePath string, filePath string, content string) *Shell { - self.CreateFile(filepath.Join(worktreePath, filePath), content) - - self.RunCommand([]string{ - "git", "-C", worktreePath, "add", filePath, - }) - - return self -} - -func (self *Shell) UpdateFileInWorktreeOrSubmodule(worktreePath string, filePath string, content string) *Shell { - self.UpdateFile(filepath.Join(worktreePath, filePath), content) - - self.RunCommand([]string{ - "git", "-C", worktreePath, "add", filePath, - }) - - return self -} - -func (self *Shell) MakeExecutable(path string) *Shell { - // 0755 sets the executable permission for owner, and read/execute permissions for group and others - err := os.Chmod(filepath.Join(self.dir, path), 0o755) - if err != nil { - panic(err) - } - - return self -} - -// Help files are located at test/files from the root the lazygit repo. -// E.g. You may want to create a pre-commit hook file there, then call this -// function to copy it into your test repo. -func (self *Shell) CopyHelpFile(source string, destination string) *Shell { - return self.CopyFile(fmt.Sprintf("../../../../../files/%s", source), destination) -} - -func (self *Shell) CopyFile(source string, destination string) *Shell { - absSourcePath := filepath.Join(self.dir, source) - absDestPath := filepath.Join(self.dir, destination) - sourceFile, err := os.Open(absSourcePath) - if err != nil { - self.fail(err.Error()) - } - defer sourceFile.Close() - - destinationFile, err := os.Create(absDestPath) - if err != nil { - self.fail(err.Error()) - } - defer destinationFile.Close() - - _, err = io.Copy(destinationFile, sourceFile) - if err != nil { - self.fail(err.Error()) - } - - // copy permissions to destination file too - sourceFileInfo, err := os.Stat(absSourcePath) - if err != nil { - self.fail(err.Error()) - } - - err = os.Chmod(absDestPath, sourceFileInfo.Mode()) - if err != nil { - self.fail(err.Error()) - } - - return self -} - -// The final value passed to Chdir() during setup -// will be the directory the test is run from. -func (self *Shell) Chdir(path string) *Shell { - self.dir = filepath.Join(self.dir, path) - - return self -} - -func (self *Shell) SetAuthor(authorName string, authorEmail string) *Shell { - self.RunCommand([]string{"git", "config", "--local", "user.name", authorName}) - self.RunCommand([]string{"git", "config", "--local", "user.email", authorEmail}) - - return self -} diff --git a/pkg/integration/components/test.go b/pkg/integration/components/test.go deleted file mode 100644 index 6eab23ae04a..00000000000 --- a/pkg/integration/components/test.go +++ /dev/null @@ -1,250 +0,0 @@ -package components - -import ( - "os" - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/config" - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/samber/lo" -) - -// IntegrationTest describes an integration test that will be run against the lazygit gui. - -// our unit tests will use this description to avoid a panic caused by attempting -// to get the test's name via it's file's path. -const unitTestDescription = "test test" - -const ( - defaultWidth = 150 - defaultHeight = 100 -) - -type IntegrationTest struct { - name string - description string - extraCmdArgs []string - extraEnvVars map[string]string - skip bool - setupRepo func(shell *Shell) - setupConfig func(config *config.AppConfig) - run func( - testDriver *TestDriver, - keys config.KeybindingConfig, - ) - gitVersion GitVersionRestriction - width int - height int - isDemo bool -} - -var _ integrationTypes.IntegrationTest = &IntegrationTest{} - -type NewIntegrationTestArgs struct { - // Briefly describes what happens in the test and what it's testing for - Description string - // prepares a repo for testing - SetupRepo func(shell *Shell) - // takes a config and mutates. The mutated context will end up being passed to the gui - SetupConfig func(config *config.AppConfig) - // runs the test - Run func(t *TestDriver, keys config.KeybindingConfig) - // additional args passed to lazygit - ExtraCmdArgs []string - ExtraEnvVars map[string]string - // for when a test is flakey - Skip bool - // to run a test only on certain git versions - GitVersion GitVersionRestriction - // width and height when running in headless mode, for testing - // the UI in different sizes. - // If these are set, the test must be run in headless mode - Width int - Height int - // If true, this is not a test but a demo to be added to our docs - IsDemo bool -} - -type GitVersionRestriction struct { - // Only one of these fields can be non-empty; use functions below to construct - from string - before string - includes []string -} - -// Verifies the version is at least the given version (inclusive) -func AtLeast(version string) GitVersionRestriction { - return GitVersionRestriction{from: version} -} - -// Verifies the version is before the given version (exclusive) -func Before(version string) GitVersionRestriction { - return GitVersionRestriction{before: version} -} - -func Includes(versions ...string) GitVersionRestriction { - return GitVersionRestriction{includes: versions} -} - -func (self GitVersionRestriction) shouldRunOnVersion(version *git_commands.GitVersion) bool { - if self.from != "" { - from, err := git_commands.ParseGitVersion(self.from) - if err != nil { - panic("Invalid git version string: " + self.from) - } - return version.IsAtLeastVersion(from) - } - if self.before != "" { - before, err := git_commands.ParseGitVersion(self.before) - if err != nil { - panic("Invalid git version string: " + self.before) - } - return version.IsOlderThanVersion(before) - } - if len(self.includes) != 0 { - return lo.SomeBy(self.includes, func(str string) bool { - v, err := git_commands.ParseGitVersion(str) - if err != nil { - panic("Invalid git version string: " + str) - } - return version.Major == v.Major && version.Minor == v.Minor && version.Patch == v.Patch - }) - } - return true -} - -func NewIntegrationTest(args NewIntegrationTestArgs) *IntegrationTest { - name := "" - if args.Description != unitTestDescription { - // this panics if we're in a unit test for our integration tests, - // so we're using "test test" as a sentinel value - name = testNameFromCurrentFilePath() - } - - return &IntegrationTest{ - name: name, - description: args.Description, - extraCmdArgs: args.ExtraCmdArgs, - extraEnvVars: args.ExtraEnvVars, - skip: args.Skip, - setupRepo: args.SetupRepo, - setupConfig: args.SetupConfig, - run: args.Run, - gitVersion: args.GitVersion, - width: args.Width, - height: args.Height, - isDemo: args.IsDemo, - } -} - -func (self *IntegrationTest) Name() string { - return self.name -} - -func (self *IntegrationTest) Description() string { - return self.description -} - -func (self *IntegrationTest) ExtraCmdArgs() []string { - return self.extraCmdArgs -} - -func (self *IntegrationTest) ExtraEnvVars() map[string]string { - return self.extraEnvVars -} - -func (self *IntegrationTest) Skip() bool { - return self.skip -} - -func (self *IntegrationTest) IsDemo() bool { - return self.isDemo -} - -func (self *IntegrationTest) ShouldRunForGitVersion(version *git_commands.GitVersion) bool { - return self.gitVersion.shouldRunOnVersion(version) -} - -func (self *IntegrationTest) SetupConfig(config *config.AppConfig) { - self.setupConfig(config) -} - -func (self *IntegrationTest) SetupRepo(shell *Shell) { - self.setupRepo(shell) -} - -func (self *IntegrationTest) Run(gui integrationTypes.GuiDriver) { - pwd, err := os.Getwd() - if err != nil { - panic(err) - } - - shell := NewShell( - pwd, - // passing the full environment because it's already been filtered down - // in the parent process. - os.Environ(), - func(errorMsg string) { gui.Fail(errorMsg) }, - ) - keys := gui.Keys() - testDriver := NewTestDriver(gui, shell, keys, InputDelay()) - - if InputDelay() > 0 { - // Setting caption to clear the options menu from whatever it starts with - testDriver.SetCaption("") - testDriver.SetCaptionPrefix("") - } - - self.run(testDriver, keys) - - gui.CheckAllToastsAcknowledged() - - if InputDelay() > 0 { - // Clear whatever caption there was so it doesn't linger - testDriver.SetCaption("") - testDriver.SetCaptionPrefix("") - // the dev would want to see the final state if they're running in slow mode - testDriver.Wait(2000) - } -} - -func (self *IntegrationTest) HeadlessDimensions() (int, int) { - if self.width == 0 && self.height == 0 { - return defaultWidth, defaultHeight - } - - return self.width, self.height -} - -func (self *IntegrationTest) RequiresHeadless() bool { - return self.width != 0 && self.height != 0 -} - -func testNameFromCurrentFilePath() string { - path := utils.FilePath(3) - return TestNameFromFilePath(path) -} - -func TestNameFromFilePath(path string) string { - name := strings.Split(path, "integration/tests/")[1] - - return name[:len(name)-len(".go")] -} - -// this is the delay in milliseconds between keypresses or mouse clicks -// defaults to zero -func InputDelay() int { - delayStr := os.Getenv("INPUT_DELAY") - if delayStr == "" { - return 0 - } - - delay, err := strconv.Atoi(delayStr) - if err != nil { - panic(err) - } - return delay -} diff --git a/pkg/integration/components/test_driver.go b/pkg/integration/components/test_driver.go deleted file mode 100644 index a1775239c7e..00000000000 --- a/pkg/integration/components/test_driver.go +++ /dev/null @@ -1,158 +0,0 @@ -package components - -import ( - "fmt" - "time" - - "github.com/atotto/clipboard" - "github.com/jesseduffield/lazygit/pkg/config" - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" -) - -type TestDriver struct { - gui integrationTypes.GuiDriver - keys config.KeybindingConfig - inputDelay int - *assertionHelper - shell *Shell -} - -func NewTestDriver(gui integrationTypes.GuiDriver, shell *Shell, keys config.KeybindingConfig, inputDelay int) *TestDriver { - return &TestDriver{ - gui: gui, - keys: keys, - inputDelay: inputDelay, - assertionHelper: &assertionHelper{gui: gui}, - shell: shell, - } -} - -// key is something like 'w' or ''. It's best not to pass a direct value, -// but instead to go through the default user config to get a more meaningful key name -func (self *TestDriver) press(keyStr string) { - self.SetCaption(fmt.Sprintf("Pressing %s", keyStr)) - self.gui.PressKey(keyStr) - self.Wait(self.inputDelay) -} - -// for use when typing or navigating, because in demos we want that to happen -// faster -func (self *TestDriver) pressFast(keyStr string) { - self.SetCaption("") - self.gui.PressKey(keyStr) - self.Wait(self.inputDelay / 5) -} - -func (self *TestDriver) click(x, y int) { - self.SetCaption(fmt.Sprintf("Clicking %d, %d", x, y)) - self.gui.Click(x, y) - self.Wait(self.inputDelay) -} - -// Should only be used in specific cases where you're doing something weird! -// E.g. invoking a global keybinding from within a popup. -// You probably shouldn't use this function, and should instead go through a view like t.Views().Commit().Focus().Press(...) -func (self *TestDriver) GlobalPress(keyStr string) { - self.press(keyStr) -} - -func (self *TestDriver) typeContent(content string) { - for _, char := range content { - self.pressFast(string(char)) - } -} - -func (self *TestDriver) Common() *Common { - return &Common{t: self} -} - -// for when you want to allow lazygit to process something before continuing -func (self *TestDriver) Wait(milliseconds int) { - time.Sleep(time.Duration(milliseconds) * time.Millisecond) -} - -func (self *TestDriver) SetCaption(caption string) { - self.gui.SetCaption(caption) -} - -func (self *TestDriver) SetCaptionPrefix(prefix string) { - self.gui.SetCaptionPrefix(prefix) -} - -func (self *TestDriver) LogUI(message string) { - self.gui.LogUI(message) -} - -func (self *TestDriver) Log(message string) { - self.gui.LogUI(message) -} - -// allows the user to run shell commands during the test to emulate background activity -func (self *TestDriver) Shell() *Shell { - return self.shell -} - -// for making assertions on lazygit views -func (self *TestDriver) Views() *Views { - return &Views{t: self} -} - -// for interacting with popups -func (self *TestDriver) ExpectPopup() *Popup { - return &Popup{t: self} -} - -func (self *TestDriver) ExpectToast(matcher *TextMatcher) *TestDriver { - t := self.gui.NextToast() - if t == nil { - self.gui.Fail("Expected toast, but didn't get one") - } else { - self.matchString(matcher, "Unexpected toast message", - func() string { - return *t - }, - ) - } - - return self -} - -func (self *TestDriver) ExpectClipboard(matcher *TextMatcher) { - self.assertWithRetries(func() (bool, string) { - text, err := clipboard.ReadAll() - if err != nil { - return false, "Error occurred when reading from clipboard: " + err.Error() - } - ok, _ := matcher.test(text) - return ok, fmt.Sprintf("Expected clipboard to match %s, but got %s", matcher.name(), text) - }) -} - -func (self *TestDriver) ExpectSearch() *SearchDriver { - self.inSearch() - - return &SearchDriver{t: self} -} - -func (self *TestDriver) inSearch() { - self.assertWithRetries(func() (bool, string) { - currentView := self.gui.CurrentContext().GetView() - return currentView.Name() == "search", "Expected search prompt to be focused" - }) -} - -// for making assertions through git itself -func (self *TestDriver) Git() *Git { - return &Git{assertionHelper: self.assertionHelper, shell: self.shell} -} - -// for making assertions on the file system -func (self *TestDriver) FileSystem() *FileSystem { - return &FileSystem{assertionHelper: self.assertionHelper} -} - -// for when you just want to fail the test yourself. -// This runs callbacks to ensure we render the error after closing the gui. -func (self *TestDriver) Fail(message string) { - self.assertionHelper.fail(message) -} diff --git a/pkg/integration/components/test_test.go b/pkg/integration/components/test_test.go deleted file mode 100644 index ea1c791242f..00000000000 --- a/pkg/integration/components/test_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package components - -import ( - "testing" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/types" - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" - "github.com/stretchr/testify/assert" -) - -// this file is for testing our test code (meta, I know) - -type coordinate struct { - x, y int -} - -type fakeGuiDriver struct { - failureMessage string - pressedKeys []string - clickedCoordinates []coordinate -} - -var _ integrationTypes.GuiDriver = &fakeGuiDriver{} - -func (self *fakeGuiDriver) PressKey(key string) { - self.pressedKeys = append(self.pressedKeys, key) -} - -func (self *fakeGuiDriver) Click(x, y int) { - self.clickedCoordinates = append(self.clickedCoordinates, coordinate{x: x, y: y}) -} - -func (self *fakeGuiDriver) Keys() config.KeybindingConfig { - return config.KeybindingConfig{} -} - -func (self *fakeGuiDriver) CurrentContext() types.Context { - return nil -} - -func (self *fakeGuiDriver) ContextForView(viewName string) types.Context { - return nil -} - -func (self *fakeGuiDriver) Fail(message string) { - self.failureMessage = message -} - -func (self *fakeGuiDriver) Log(message string) { -} - -func (self *fakeGuiDriver) LogUI(message string) { -} - -func (self *fakeGuiDriver) CheckedOutRef() *models.Branch { - return nil -} - -func (self *fakeGuiDriver) MainView() *gocui.View { - return nil -} - -func (self *fakeGuiDriver) SecondaryView() *gocui.View { - return nil -} - -func (self *fakeGuiDriver) View(viewName string) *gocui.View { - return nil -} - -func (self *fakeGuiDriver) SetCaption(string) { -} - -func (self *fakeGuiDriver) SetCaptionPrefix(string) { -} - -func (self *fakeGuiDriver) NextToast() *string { - return nil -} - -func (self *fakeGuiDriver) CheckAllToastsAcknowledged() {} - -func (self *fakeGuiDriver) Headless() bool { return false } - -func TestManualFailure(t *testing.T) { - test := NewIntegrationTest(NewIntegrationTestArgs{ - Description: unitTestDescription, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Fail("blah") - }, - }) - driver := &fakeGuiDriver{} - test.Run(driver) - assert.Equal(t, "blah", driver.failureMessage) -} - -func TestSuccess(t *testing.T) { - test := NewIntegrationTest(NewIntegrationTestArgs{ - Description: unitTestDescription, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.press("a") - t.press("b") - t.click(0, 1) - t.click(2, 3) - }, - }) - driver := &fakeGuiDriver{} - test.Run(driver) - assert.EqualValues(t, []string{"a", "b"}, driver.pressedKeys) - assert.EqualValues(t, []coordinate{{0, 1}, {2, 3}}, driver.clickedCoordinates) - assert.Equal(t, "", driver.failureMessage) -} - -func TestGitVersionRestriction(t *testing.T) { - scenarios := []struct { - testName string - gitVersion GitVersionRestriction - expectedShouldRun bool - }{ - { - testName: "AtLeast, current is newer", - gitVersion: AtLeast("2.24.9"), - expectedShouldRun: true, - }, - { - testName: "AtLeast, current is same", - gitVersion: AtLeast("2.25.0"), - expectedShouldRun: true, - }, - { - testName: "AtLeast, current is older", - gitVersion: AtLeast("2.26.0"), - expectedShouldRun: false, - }, - { - testName: "Before, current is older", - gitVersion: Before("2.24.9"), - expectedShouldRun: false, - }, - { - testName: "Before, current is same", - gitVersion: Before("2.25.0"), - expectedShouldRun: false, - }, - { - testName: "Before, current is newer", - gitVersion: Before("2.26.0"), - expectedShouldRun: true, - }, - { - testName: "Includes, current is included", - gitVersion: Includes("2.23.0", "2.25.0"), - expectedShouldRun: true, - }, - { - testName: "Includes, current is not included", - gitVersion: Includes("2.23.0", "2.27.0"), - expectedShouldRun: false, - }, - } - - currentGitVersion := git_commands.GitVersion{Major: 2, Minor: 25, Patch: 0} - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - test := NewIntegrationTest(NewIntegrationTestArgs{ - Description: unitTestDescription, - GitVersion: s.gitVersion, - }) - shouldRun := test.ShouldRunForGitVersion(¤tGitVersion) - assert.Equal(t, shouldRun, s.expectedShouldRun) - }) - } -} diff --git a/pkg/integration/components/text_matcher.go b/pkg/integration/components/text_matcher.go deleted file mode 100644 index 3970dc77879..00000000000 --- a/pkg/integration/components/text_matcher.go +++ /dev/null @@ -1,134 +0,0 @@ -package components - -import ( - "fmt" - "regexp" - "strings" - - "github.com/samber/lo" -) - -type TextMatcher struct { - // If you add or change a field here, be sure to update the copy - // code in checkIsSelected() - *Matcher[string] -} - -func (self *TextMatcher) Contains(target string) *TextMatcher { - self.appendRule(matcherRule[string]{ - name: fmt.Sprintf("contains '%s'", target), - testFn: func(value string) (bool, string) { - // everything contains the empty string so we unconditionally return true here - if target == "" { - return true, "" - } - - return strings.Contains(value, target), fmt.Sprintf("Expected '%s' to be found in '%s'", target, value) - }, - }) - - return self -} - -func (self *TextMatcher) DoesNotContain(target string) *TextMatcher { - self.appendRule(matcherRule[string]{ - name: fmt.Sprintf("does not contain '%s'", target), - testFn: func(value string) (bool, string) { - return !strings.Contains(value, target), fmt.Sprintf("Expected '%s' to NOT be found in '%s'", target, value) - }, - }) - - return self -} - -func (self *TextMatcher) DoesNotContainAnyOf(targets []string) *TextMatcher { - self.appendRule(matcherRule[string]{ - name: fmt.Sprintf("does not contain any of '%s'", targets), - testFn: func(value string) (bool, string) { - return lo.NoneBy(targets, func(target string) bool { return strings.Contains(value, target) }), - fmt.Sprintf("Expected none of '%s' to be found in '%s'", targets, value) - }, - }) - - return self -} - -func (self *TextMatcher) MatchesRegexp(target string) *TextMatcher { - self.appendRule(matcherRule[string]{ - name: fmt.Sprintf("matches regular expression '%s'", target), - testFn: func(value string) (bool, string) { - matched, err := regexp.MatchString(target, value) - if err != nil { - return false, fmt.Sprintf("Unexpected error parsing regular expression '%s': %s", target, err.Error()) - } - return matched, fmt.Sprintf("Expected '%s' to match regular expression /%s/", value, target) - }, - }) - - return self -} - -func (self *TextMatcher) Equals(target string) *TextMatcher { - self.appendRule(matcherRule[string]{ - name: fmt.Sprintf("equals '%s'", target), - testFn: func(value string) (bool, string) { - return target == value, fmt.Sprintf("Expected '%s' to equal '%s'", value, target) - }, - }) - - return self -} - -const IS_SELECTED_RULE_NAME = "is selected" - -// special rule that is only to be used in the TopLines and Lines methods, as a way of -// asserting that a given line is selected. -func (self *TextMatcher) IsSelected() *TextMatcher { - self.appendRule(matcherRule[string]{ - name: IS_SELECTED_RULE_NAME, - testFn: func(value string) (bool, string) { - panic("Special IsSelected matcher is not supposed to have its testFn method called. This rule should only be used within the .Lines() and .TopLines() method on a ViewAsserter.") - }, - }) - - return self -} - -// if the matcher has an `IsSelected` rule, it returns true, along with the matcher after that rule has been removed -func (self *TextMatcher) checkIsSelected() (bool, *TextMatcher) { - // copying into a new matcher in case we want to reuse the original later - newMatcher := &TextMatcher{Matcher: &Matcher[string]{}} - *newMatcher.Matcher = *self.Matcher - - check := lo.ContainsBy(newMatcher.rules, func(rule matcherRule[string]) bool { return rule.name == IS_SELECTED_RULE_NAME }) - - newMatcher.rules = lo.Filter(newMatcher.rules, func(rule matcherRule[string], _ int) bool { return rule.name != IS_SELECTED_RULE_NAME }) - - return check, newMatcher -} - -// this matcher has no rules meaning it always passes the test. Use this -// when you don't care what value you're dealing with. -func AnyString() *TextMatcher { - return &TextMatcher{Matcher: &Matcher[string]{}} -} - -func Contains(target string) *TextMatcher { - return AnyString().Contains(target) -} - -func DoesNotContain(target string) *TextMatcher { - return AnyString().DoesNotContain(target) -} - -func DoesNotContainAnyOf(targets ...string) *TextMatcher { - return AnyString().DoesNotContainAnyOf(targets) -} - -func MatchesRegexp(target string) *TextMatcher { - return AnyString().MatchesRegexp(target) -} - -func Equals(target string) *TextMatcher { - return AnyString().Equals(target) -} diff --git a/pkg/integration/components/view_driver.go b/pkg/integration/components/view_driver.go deleted file mode 100644 index ea005d3710d..00000000000 --- a/pkg/integration/components/view_driver.go +++ /dev/null @@ -1,653 +0,0 @@ -package components - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/samber/lo" -) - -type ViewDriver struct { - // context is prepended to any error messages e.g. 'context: "current view"' - context string - getView func() *gocui.View - t *TestDriver -} - -func (self *ViewDriver) getSelectedLines() []string { - view := self.t.gui.View(self.getView().Name()) - return view.SelectedLines() -} - -func (self *ViewDriver) getSelectedRange() (int, int) { - view := self.t.gui.View(self.getView().Name()) - return view.SelectedLineRange() -} - -func (self *ViewDriver) getSelectedLineIdx() int { - view := self.t.gui.View(self.getView().Name()) - return view.SelectedLineIdx() -} - -// asserts that the view has the expected title -func (self *ViewDriver) Title(expected *TextMatcher) *ViewDriver { - self.t.assertWithRetries(func() (bool, string) { - actual := self.getView().Title - return expected.context(fmt.Sprintf("%s title", self.context)).test(actual) - }) - - return self -} - -func (self *ViewDriver) Clear() *ViewDriver { - // clearing multiple times in case there's multiple lines - // (the clear button only clears a single line at a time) - maxAttempts := 100 - for i := range maxAttempts + 1 { - if self.getView().Buffer() == "" { - break - } - - self.t.press(ClearKey) - if i == maxAttempts { - panic("failed to clear view buffer") - } - } - - return self -} - -// asserts that the view has lines matching the given matchers. One matcher must be passed for each line. -// If you only care about the top n lines, use the TopLines method instead. -// If you only care about a subset of lines, use the ContainsLines method instead. -func (self *ViewDriver) Lines(matchers ...*TextMatcher) *ViewDriver { - self.validateMatchersPassed(matchers) - self.LineCount(EqualsInt(len(matchers))) - - return self.assertLines(0, matchers...) -} - -// asserts that the view has lines matching the given matchers. So if three matchers -// are passed, we only check the first three lines of the view. -// This method is convenient when you have a list of commits but you only want to -// assert on the first couple of commits. -func (self *ViewDriver) TopLines(matchers ...*TextMatcher) *ViewDriver { - self.validateMatchersPassed(matchers) - self.validateEnoughLines(matchers) - - return self.assertLines(0, matchers...) -} - -// Asserts on the visible lines of the view. -// Note, this assumes that the view's viewport is filled with lines -func (self *ViewDriver) VisibleLines(matchers ...*TextMatcher) *ViewDriver { - self.validateMatchersPassed(matchers) - self.validateVisibleLineCount(matchers) - - // Get the origin of the view and offset that. - // Note that we don't do any retrying here so if we want to bring back retry logic - // we'll need to update this. - originY := self.getView().OriginY() - - return self.assertLines(originY, matchers...) -} - -// asserts that somewhere in the view there are consecutive lines matching the given matchers. -func (self *ViewDriver) ContainsLines(matchers ...*TextMatcher) *ViewDriver { - self.validateMatchersPassed(matchers) - self.validateEnoughLines(matchers) - - self.t.assertWithRetries(func() (bool, string) { - content := self.getView().Buffer() - lines := strings.Split(content, "\n") - - startIdx, endIdx := self.getSelectedRange() - - for i := range len(lines) - len(matchers) + 1 { - matches := true - for j, matcher := range matchers { - checkIsSelected, matcher := matcher.checkIsSelected() // strip the IsSelected matcher out - lineIdx := i + j - ok, _ := matcher.test(lines[lineIdx]) - if !ok { - matches = false - break - } - if checkIsSelected { - if lineIdx < startIdx || lineIdx > endIdx { - matches = false - break - } - } - } - if matches { - return true, "" - } - } - - expectedContent := expectedContentFromMatchers(matchers) - - return false, fmt.Sprintf( - "Expected the following to be contained in the staging panel:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----\nSelected range: %d-%d", - expectedContent, - content, - startIdx, - endIdx, - ) - }) - - return self -} - -func (self *ViewDriver) ContainsColoredText(fgColorStr string, text string) *ViewDriver { - self.t.assertWithRetries(func() (bool, string) { - view := self.getView() - ok := self.getView().ContainsColoredText(fgColorStr, text) - if !ok { - return false, fmt.Sprintf("expected view '%s' to contain colored text '%s' but it didn't", view.Name(), text) - } - - return true, "" - }) - - return self -} - -func (self *ViewDriver) DoesNotContainColoredText(fgColorStr string, text string) *ViewDriver { - self.t.assertWithRetries(func() (bool, string) { - view := self.getView() - ok := !self.getView().ContainsColoredText(fgColorStr, text) - if !ok { - return false, fmt.Sprintf("expected view '%s' to NOT contain colored text '%s' but it didn't", view.Name(), text) - } - - return true, "" - }) - - return self -} - -// asserts on the lines that are selected in the view. Don't use the `IsSelected` matcher with this because it's redundant. -func (self *ViewDriver) SelectedLines(matchers ...*TextMatcher) *ViewDriver { - self.validateMatchersPassed(matchers) - self.validateEnoughLines(matchers) - - self.t.assertWithRetries(func() (bool, string) { - selectedLines := self.getSelectedLines() - - selectedContent := strings.Join(selectedLines, "\n") - expectedContent := expectedContentFromMatchers(matchers) - - if len(selectedLines) != len(matchers) { - return false, fmt.Sprintf("Expected the following to be selected:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----", expectedContent, selectedContent) - } - - for i, line := range selectedLines { - checkIsSelected, matcher := matchers[i].checkIsSelected() - if checkIsSelected { - self.t.fail("You cannot use the IsSelected matcher with the SelectedLines method") - } - - ok, message := matcher.test(line) - if !ok { - return false, fmt.Sprintf("Error: %s. Expected the following to be selected:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----", message, expectedContent, selectedContent) - } - } - - return true, "" - }) - - return self -} - -func (self *ViewDriver) validateMatchersPassed(matchers []*TextMatcher) { - if len(matchers) < 1 { - self.t.fail("'Lines' methods require at least one matcher to be passed as an argument. If you are trying to assert that there are no lines, use .IsEmpty()") - } -} - -func (self *ViewDriver) validateEnoughLines(matchers []*TextMatcher) { - view := self.getView() - - self.t.assertWithRetries(func() (bool, string) { - lines := view.BufferLines() - return len(lines) >= len(matchers), fmt.Sprintf("unexpected number of lines in view '%s'. Expected at least %d, got %d", view.Name(), len(matchers), len(lines)) - }) -} - -// assumes the view's viewport is filled with lines -func (self *ViewDriver) validateVisibleLineCount(matchers []*TextMatcher) { - view := self.getView() - - self.t.assertWithRetries(func() (bool, string) { - count := view.InnerHeight() - return count == len(matchers), fmt.Sprintf("unexpected number of visible lines in view '%s'. Expected exactly %d, got %d", view.Name(), len(matchers), count) - }) -} - -func (self *ViewDriver) assertLines(offset int, matchers ...*TextMatcher) *ViewDriver { - view := self.getView() - - var expectedStartIdx, expectedEndIdx int - foundSelectionStart := false - foundSelectionEnd := false - expectedSelectedLines := []string{} - - for matcherIndex, matcher := range matchers { - lineIdx := matcherIndex + offset - - checkIsSelected, matcher := matcher.checkIsSelected() - - if checkIsSelected { - if foundSelectionEnd { - self.t.fail("The IsSelected matcher can only be used on a contiguous range of lines.") - } - if !foundSelectionStart { - expectedStartIdx = lineIdx - foundSelectionStart = true - } - expectedSelectedLines = append(expectedSelectedLines, matcher.name()) - expectedEndIdx = lineIdx - } else if foundSelectionStart { - foundSelectionEnd = true - } - } - - for matcherIndex, matcher := range matchers { - lineIdx := matcherIndex + offset - expectSelected, matcher := matcher.checkIsSelected() - - self.t.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()), - func() string { - return view.BufferLines()[lineIdx] - }, - ) - - // If any of the matchers care about the selection, we need to - // assert on the selection for each matcher. - if foundSelectionStart { - self.t.assertWithRetries(func() (bool, string) { - startIdx, endIdx := self.getSelectedRange() - - selected := lineIdx >= startIdx && lineIdx <= endIdx - - if (selected && expectSelected) || (!selected && !expectSelected) { - return true, "" - } - - lines := self.getSelectedLines() - - return false, fmt.Sprintf( - "Unexpected selection in view '%s'. Expected %s to be selected but got %s.\nExpected selected lines:\n---\n%s\n---\n\nActual selected lines:\n---\n%s\n---\n", - view.Name(), - formatLineRange(expectedStartIdx, expectedEndIdx), - formatLineRange(startIdx, endIdx), - strings.Join(expectedSelectedLines, "\n"), - strings.Join(lines, "\n"), - ) - }) - } - } - - return self -} - -func formatLineRange(from int, to int) string { - if from == to { - return "line " + fmt.Sprintf("%d", from) - } - - return "lines " + fmt.Sprintf("%d-%d", from, to) -} - -// asserts on the content of the view i.e. the stuff within the view's frame. -func (self *ViewDriver) Content(matcher *TextMatcher) *ViewDriver { - self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected content.", self.context), - func() string { - return self.getView().Buffer() - }, - ) - - return self -} - -// asserts on the selected line of the view. If you are selecting a range, -// you should use the SelectedLines method instead. -func (self *ViewDriver) SelectedLine(matcher *TextMatcher) *ViewDriver { - self.t.assertWithRetries(func() (bool, string) { - selectedLineIdx := self.getSelectedLineIdx() - - viewLines := self.getView().BufferLines() - - if selectedLineIdx >= len(viewLines) { - return false, fmt.Sprintf("%s: Expected view to have at least %d lines, but it only has %d", self.context, selectedLineIdx+1, len(viewLines)) - } - - value := viewLines[selectedLineIdx] - - return matcher.context(fmt.Sprintf("%s: Unexpected selected line.", self.context)).test(value) - }) - - return self -} - -// asserts on the index of the selected line. 0 is the first index, representing the line at the top of the view. -func (self *ViewDriver) SelectedLineIdx(expected int) *ViewDriver { - self.t.assertWithRetries(func() (bool, string) { - actual := self.getView().SelectedLineIdx() - return expected == actual, fmt.Sprintf("%s: Expected selected line index to be %d, got %d", self.context, expected, actual) - }) - - return self -} - -// focus the view (assumes the view is a side-view) -func (self *ViewDriver) Focus() *ViewDriver { - viewName := self.getView().Name() - - type window struct { - name string - viewNames []string - } - windows := []window{ - {name: "status", viewNames: []string{"status"}}, - {name: "files", viewNames: []string{"files", "worktrees", "submodules"}}, - {name: "branches", viewNames: []string{"localBranches", "remotes", "tags"}}, - {name: "commits", viewNames: []string{"commits", "reflogCommits"}}, - {name: "stash", viewNames: []string{"stash"}}, - } - - for windowIndex, window := range windows { - if lo.Contains(window.viewNames, viewName) { - tabIndex := lo.IndexOf(window.viewNames, viewName) - // jump to the desired window - self.t.press(self.t.keys.Universal.JumpToBlock[windowIndex]) - - // assert we're in the window before continuing - self.t.assertWithRetries(func() (bool, string) { - currentWindowName := self.t.gui.CurrentContext().GetWindowName() - // by convention the window is named after the first view in the window - return currentWindowName == window.name, fmt.Sprintf("Expected to be in window '%s', but was in '%s'", window.name, currentWindowName) - }) - - // switch to the desired tab - currentViewName := self.t.gui.CurrentContext().GetViewName() - currentViewTabIndex := lo.IndexOf(window.viewNames, currentViewName) - if tabIndex > currentViewTabIndex { - for range tabIndex - currentViewTabIndex { - self.t.press(self.t.keys.Universal.NextTab) - } - } else if tabIndex < currentViewTabIndex { - for range currentViewTabIndex - tabIndex { - self.t.press(self.t.keys.Universal.PrevTab) - } - } - - // assert that we're now in the expected view - self.IsFocused() - - return self - } - } - - self.t.fail(fmt.Sprintf("Cannot focus view %s: Focus() method not implemented", viewName)) - - return self -} - -// asserts that the view is focused -func (self *ViewDriver) IsFocused() *ViewDriver { - self.t.assertWithRetries(func() (bool, string) { - expected := self.getView().Name() - actual := self.t.gui.CurrentContext().GetView().Name() - return actual == expected, fmt.Sprintf("%s: Unexpected view focused. Expected %s, got %s", self.context, expected, actual) - }) - - return self -} - -func (self *ViewDriver) Press(keyStr string) *ViewDriver { - self.IsFocused() - - self.t.press(keyStr) - - return self -} - -func (self *ViewDriver) Delay() *ViewDriver { - self.t.Wait(self.t.inputDelay) - - return self -} - -// for use when typing or navigating, because in demos we want that to happen -// faster -func (self *ViewDriver) PressFast(keyStr string) *ViewDriver { - self.IsFocused() - - self.t.pressFast(keyStr) - - return self -} - -func (self *ViewDriver) Click(x, y int) *ViewDriver { - offsetX, offsetY, _, _ := self.getView().Dimensions() - - self.t.click(offsetX+1+x, offsetY+1+y) - - return self -} - -// i.e. pressing down arrow -func (self *ViewDriver) SelectNextItem() *ViewDriver { - return self.PressFast(self.t.keys.Universal.NextItem) -} - -// i.e. pressing up arrow -func (self *ViewDriver) SelectPreviousItem() *ViewDriver { - return self.PressFast(self.t.keys.Universal.PrevItem) -} - -// i.e. pressing '<' -func (self *ViewDriver) GotoTop() *ViewDriver { - return self.PressFast(self.t.keys.Universal.GotoTop) -} - -// i.e. pressing space -func (self *ViewDriver) PressPrimaryAction() *ViewDriver { - return self.Press(self.t.keys.Universal.Select) -} - -// i.e. pressing space -func (self *ViewDriver) PressEnter() *ViewDriver { - return self.Press(self.t.keys.Universal.Confirm) -} - -// i.e. pressing tab -func (self *ViewDriver) PressTab() *ViewDriver { - return self.Press(self.t.keys.Universal.TogglePanel) -} - -// i.e. pressing escape -func (self *ViewDriver) PressEscape() *ViewDriver { - return self.Press(self.t.keys.Universal.Return) -} - -// this will look for a list item in the current panel and if it finds it, it will -// enter the keypresses required to navigate to it. -// The test will fail if: -// - the user is not in a list item -// - no list item is found containing the given text -// - multiple list items are found containing the given text in the initial page of items -func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver { - self.IsFocused() - - view := self.getView() - lines := view.BufferLines() - - matchIndex := -1 - - self.t.assertWithRetries(func() (bool, string) { - var matches []string - // first we look for a duplicate on the current screen. We won't bother looking beyond that though. - for i, line := range lines { - ok, _ := matcher.test(line) - if ok { - matches = append(matches, line) - matchIndex = i - } - } - if len(matches) > 1 { - return false, fmt.Sprintf("Found %d matches for `%s`, expected only a single match. Matching lines:\n%s", len(matches), matcher.name(), strings.Join(matches, "\n")) - } - return true, "" - }) - - // If no match was found, it could be that this is a view that renders only - // the visible lines. In that case, we jump to the top and then press - // down-arrow until we found the match. We simply return the first match we - // find, so we have no way to assert that there are no duplicates. - if matchIndex == -1 { - self.GotoTop() - matchIndex = len(lines) - } - - selectedLineIdx := self.getSelectedLineIdx() - if selectedLineIdx == matchIndex { - return self.SelectedLine(matcher) - } - - // At this point we can't just take the difference of selected and matched - // index and press up or down arrow this many times. The reason is that - // there might be section headers between those lines, and these will be - // skipped when pressing up or down arrow. So we must keep pressing the - // arrow key in a loop, and check after each one whether we now reached the - // target line. - var maxNumKeyPresses int - var keyPress func() - if selectedLineIdx < matchIndex { - maxNumKeyPresses = matchIndex - selectedLineIdx - keyPress = func() { self.SelectNextItem() } - } else { - maxNumKeyPresses = selectedLineIdx - matchIndex - keyPress = func() { self.SelectPreviousItem() } - } - - for range maxNumKeyPresses { - keyPress() - idx := self.getSelectedLineIdx() - // It is important to use view.BufferLines() here and not lines, because it - // could change with every keypress. - if ok, _ := matcher.test(view.BufferLines()[idx]); ok { - return self - } - } - - self.t.fail(fmt.Sprintf("Could not navigate to item matching: %s. Lines:\n%s", matcher.name(), strings.Join(view.BufferLines(), "\n"))) - return self -} - -// returns true if the view is a list view and it contains no items -func (self *ViewDriver) IsEmpty() *ViewDriver { - self.t.assertWithRetries(func() (bool, string) { - actual := strings.TrimSpace(self.getView().Buffer()) - return actual == "", fmt.Sprintf("%s: Unexpected content in view: expected no content. Content: %s", self.context, actual) - }) - - return self -} - -func (self *ViewDriver) LineCount(matcher *IntMatcher) *ViewDriver { - view := self.getView() - - self.t.assertWithRetries(func() (bool, string) { - lineCount := self.getLineCount() - ok, _ := matcher.test(lineCount) - return ok, fmt.Sprintf("unexpected number of lines in view '%s'. Expected %s, got %d", view.Name(), matcher.name(), lineCount) - }) - - return self -} - -func (self *ViewDriver) getLineCount() int { - // can't rely entirely on view.BufferLines because it returns 1 even if there's nothing in the view - if strings.TrimSpace(self.getView().Buffer()) == "" { - return 0 - } - - view := self.getView() - return len(view.BufferLines()) -} - -func (self *ViewDriver) IsVisible() *ViewDriver { - self.t.assertWithRetries(func() (bool, string) { - return self.getView().Visible, fmt.Sprintf("%s: Expected view to be visible, but it was not", self.context) - }) - - return self -} - -func (self *ViewDriver) IsInvisible() *ViewDriver { - self.t.assertWithRetries(func() (bool, string) { - return !self.getView().Visible, fmt.Sprintf("%s: Expected view to be invisible, but it was not", self.context) - }) - - return self -} - -// will filter or search depending on whether the view supports filtering/searching -func (self *ViewDriver) FilterOrSearch(text string) *ViewDriver { - self.IsFocused() - - self.Press(self.t.keys.Universal.StartSearch). - Tap(func() { - self.t.ExpectSearch(). - Clear(). - Type(text). - Confirm() - - self.t.Views().Search().IsVisible().Content(Contains(fmt.Sprintf("matches for '%s'", text))) - }) - - return self -} - -func (self *ViewDriver) SetCaption(caption string) *ViewDriver { - self.t.gui.SetCaption(caption) - - return self -} - -func (self *ViewDriver) SetCaptionPrefix(prefix string) *ViewDriver { - self.t.gui.SetCaptionPrefix(prefix) - - return self -} - -func (self *ViewDriver) Wait(milliseconds int) *ViewDriver { - if !self.t.gui.Headless() { - self.t.Wait(milliseconds) - } - - return self -} - -// for when you want to make some assertion unrelated to the current view -// without breaking the method chain -func (self *ViewDriver) Tap(f func()) *ViewDriver { - f() - - return self -} - -// This purely exists as a convenience method for those who hate the trailing periods in multi-line method chains -func (self *ViewDriver) Self() *ViewDriver { - return self -} - -func expectedContentFromMatchers(matchers []*TextMatcher) string { - return strings.Join(lo.Map(matchers, func(matcher *TextMatcher, _ int) string { - return matcher.name() - }), "\n") -} diff --git a/pkg/integration/components/views.go b/pkg/integration/components/views.go deleted file mode 100644 index 1d32f482875..00000000000 --- a/pkg/integration/components/views.go +++ /dev/null @@ -1,157 +0,0 @@ -package components - -import ( - "fmt" - - "github.com/jesseduffield/gocui" -) - -type Views struct { - t *TestDriver -} - -func (self *Views) Main() *ViewDriver { - return &ViewDriver{ - context: "main view", - getView: func() *gocui.View { return self.t.gui.MainView() }, - t: self.t, - } -} - -func (self *Views) Secondary() *ViewDriver { - return &ViewDriver{ - context: "secondary view", - getView: func() *gocui.View { return self.t.gui.SecondaryView() }, - t: self.t, - } -} - -func (self *Views) regularView(viewName string) *ViewDriver { - return &ViewDriver{ - context: fmt.Sprintf("%s view", viewName), - getView: func() *gocui.View { return self.t.gui.View(viewName) }, - t: self.t, - } -} - -func (self *Views) patchExplorerViewByName(viewName string) *ViewDriver { - return self.regularView(viewName) -} - -func (self *Views) MergeConflicts() *ViewDriver { - return self.regularView("mergeConflicts") -} - -func (self *Views) Commits() *ViewDriver { - return self.regularView("commits") -} - -func (self *Views) Files() *ViewDriver { - return self.regularView("files") -} - -func (self *Views) Worktrees() *ViewDriver { - return self.regularView("worktrees") -} - -func (self *Views) Status() *ViewDriver { - return self.regularView("status") -} - -func (self *Views) Submodules() *ViewDriver { - return self.regularView("submodules") -} - -func (self *Views) Information() *ViewDriver { - return self.regularView("information") -} - -func (self *Views) AppStatus() *ViewDriver { - return self.regularView("appStatus") -} - -func (self *Views) Branches() *ViewDriver { - return self.regularView("localBranches") -} - -func (self *Views) Remotes() *ViewDriver { - return self.regularView("remotes") -} - -func (self *Views) RemoteBranches() *ViewDriver { - return self.regularView("remoteBranches") -} - -func (self *Views) Tags() *ViewDriver { - return self.regularView("tags") -} - -func (self *Views) ReflogCommits() *ViewDriver { - return self.regularView("reflogCommits") -} - -func (self *Views) SubCommits() *ViewDriver { - return self.regularView("subCommits") -} - -func (self *Views) CommitFiles() *ViewDriver { - return self.regularView("commitFiles") -} - -func (self *Views) Stash() *ViewDriver { - return self.regularView("stash") -} - -func (self *Views) Staging() *ViewDriver { - return self.patchExplorerViewByName("staging") -} - -func (self *Views) StagingSecondary() *ViewDriver { - return self.patchExplorerViewByName("stagingSecondary") -} - -func (self *Views) PatchBuilding() *ViewDriver { - return self.patchExplorerViewByName("patchBuilding") -} - -func (self *Views) PatchBuildingSecondary() *ViewDriver { - // this is not a patch explorer view because you can't actually focus it: it - // just renders content - return self.regularView("patchBuildingSecondary") -} - -func (self *Views) Menu() *ViewDriver { - return self.regularView("menu") -} - -func (self *Views) Confirmation() *ViewDriver { - return self.regularView("confirmation") -} - -func (self *Views) Prompt() *ViewDriver { - return self.regularView("prompt") -} - -func (self *Views) CommitMessage() *ViewDriver { - return self.regularView("commitMessage") -} - -func (self *Views) CommitDescription() *ViewDriver { - return self.regularView("commitDescription") -} - -func (self *Views) Suggestions() *ViewDriver { - return self.regularView("suggestions") -} - -func (self *Views) Search() *ViewDriver { - return self.regularView("search") -} - -func (self *Views) Tooltip() *ViewDriver { - return self.regularView("tooltip") -} - -func (self *Views) Options() *ViewDriver { - return self.regularView("options") -} diff --git a/pkg/integration/tests/bisect/basic.go b/pkg/integration/tests/bisect/basic.go deleted file mode 100644 index dbce5096903..00000000000 --- a/pkg/integration/tests/bisect/basic.go +++ /dev/null @@ -1,63 +0,0 @@ -package bisect - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Basic = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Start a git bisect to find a bad commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell. - NewBranch("mybranch"). - CreateNCommits(10) - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().Git.Log.ShowGraph = "never" - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - markCommitAsBad := func() { - t.Views().Commits(). - Press(keys.Commits.ViewBisectOptions) - - t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as bad`)).Confirm() - } - - markCommitAsGood := func() { - t.Views().Commits(). - Press(keys.Commits.ViewBisectOptions) - - t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as good`)).Confirm() - } - - t.Views().Commits(). - Focus(). - SelectedLine(Contains("CI commit 10")). - NavigateToLine(Contains("CI commit 09")). - Tap(func() { - markCommitAsBad() - - t.Views().Information().Content(Contains("Bisecting")) - }). - SelectedLine(Contains("<-- bad")). - NavigateToLine(Contains("CI commit 02")). - Tap(markCommitAsGood). - TopLines(Contains("CI commit 10")). - // lazygit will land us in the commit between our good and bad commits. - SelectedLine(Contains("CI commit 05").Contains("<-- current")). - Tap(markCommitAsBad). - SelectedLine(Contains("CI commit 04").Contains("<-- current")). - Tap(func() { - markCommitAsGood() - - // commit 5 is the culprit because we marked 4 as good and 5 as bad. - t.ExpectPopup().Alert().Title(Equals("Bisect complete")).Content(MatchesRegexp("(?s)commit 05.*Do you want to reset")).Confirm() - }). - IsFocused(). - Content(Contains("CI commit 04")) - - t.Views().Information().Content(DoesNotContain("Bisecting")) - }, -}) diff --git a/pkg/integration/tests/bisect/choose_terms.go b/pkg/integration/tests/bisect/choose_terms.go deleted file mode 100644 index 51c9246ba05..00000000000 --- a/pkg/integration/tests/bisect/choose_terms.go +++ /dev/null @@ -1,74 +0,0 @@ -package bisect - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ChooseTerms = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Start a git bisect by choosing 'broken/fixed' as bisect terms", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell. - NewBranch("mybranch"). - CreateNCommits(10) - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().Git.Log.ShowGraph = "never" - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - markCommitAsFixed := func() { - t.Views().Commits(). - Press(keys.Commits.ViewBisectOptions) - - t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as fixed`)).Confirm() - } - - markCommitAsBroken := func() { - t.Views().Commits(). - Press(keys.Commits.ViewBisectOptions) - - t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as broken`)).Confirm() - } - - t.Views().Commits(). - Focus(). - SelectedLine(Contains("CI commit 10")). - Press(keys.Commits.ViewBisectOptions). - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(Contains("Choose bisect terms")).Confirm() - t.ExpectPopup().Prompt().Title(Equals("Term for old/good commit:")).Type("broken").Confirm() - t.ExpectPopup().Prompt().Title(Equals("Term for new/bad commit:")).Type("fixed").Confirm() - }). - NavigateToLine(Contains("CI commit 09")). - Tap(markCommitAsFixed). - SelectedLine(Contains("<-- fixed")). - NavigateToLine(Contains("CI commit 02")). - Tap(markCommitAsBroken). - Lines( - Contains("CI commit 10").DoesNotContain("<--"), - Contains("CI commit 09").Contains("<-- fixed"), - Contains("CI commit 08").DoesNotContain("<--"), - Contains("CI commit 07").DoesNotContain("<--"), - Contains("CI commit 06").DoesNotContain("<--"), - Contains("CI commit 05").Contains("<-- current").IsSelected(), - Contains("CI commit 04").DoesNotContain("<--"), - Contains("CI commit 03").DoesNotContain("<--"), - Contains("CI commit 02").Contains("<-- broken"), - Contains("CI commit 01").DoesNotContain("<--"), - ). - Tap(markCommitAsFixed). - SelectedLine(Contains("CI commit 04").Contains("<-- current")). - Tap(func() { - markCommitAsBroken() - - // commit 5 is the culprit because we marked 4 as broken and 5 as fixed. - t.ExpectPopup().Alert().Title(Equals("Bisect complete")).Content(MatchesRegexp("(?s)commit 05.*Do you want to reset")).Confirm() - }). - IsFocused(). - Content(Contains("CI commit 04")) - - t.Views().Information().Content(DoesNotContain("Bisecting")) - }, -}) diff --git a/pkg/integration/tests/bisect/from_other_branch.go b/pkg/integration/tests/bisect/from_other_branch.go deleted file mode 100644 index 24e49104b5c..00000000000 --- a/pkg/integration/tests/bisect/from_other_branch.go +++ /dev/null @@ -1,46 +0,0 @@ -package bisect - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FromOtherBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Opening lazygit when bisect has been started from another branch. There's an issue where we don't reselect the current branch if we mark the current branch as bad so this test side-steps that problem", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("only commit on master"). // this'll ensure we have a master branch - NewBranch("other"). - CreateNCommits(10). - Checkout("master"). - StartBisect("other~2", "other~5") - }, - SetupConfig: func(cfg *config.AppConfig) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Information().Content(Contains("Bisecting")) - - t.Views().Commits(). - Focus(). - TopLines( - MatchesRegexp(`<-- bad.*commit 08`), - MatchesRegexp(`<-- current.*commit 07`), - MatchesRegexp(`\?.*commit 06`), - MatchesRegexp(`<-- good.*commit 05`), - ). - SelectNextItem(). - Press(keys.Commits.ViewBisectOptions). - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as good`)).Confirm() - - t.ExpectPopup().Alert().Title(Equals("Bisect complete")).Content(MatchesRegexp("(?s)commit 08.*Do you want to reset")).Confirm() - - t.Views().Information().Content(DoesNotContain("Bisecting")) - }). - // back in master branch which just had the one commit - Lines( - Contains("only commit on master"), - ) - }, -}) diff --git a/pkg/integration/tests/bisect/skip.go b/pkg/integration/tests/bisect/skip.go deleted file mode 100644 index c879cc408b8..00000000000 --- a/pkg/integration/tests/bisect/skip.go +++ /dev/null @@ -1,90 +0,0 @@ -package bisect - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Skip = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Start a git bisect and skip a few commits (selected or current)", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(10) - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().Git.Log.ShowGraph = "never" - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - SelectedLine(Contains("commit 10")). - Press(keys.Commits.ViewBisectOptions). - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as bad`)).Confirm() - }). - NavigateToLine(Contains("commit 01")). - Press(keys.Commits.ViewBisectOptions). - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as good`)).Confirm() - t.Views().Information().Content(Contains("Bisecting")) - }). - Lines( - Contains("CI commit 10").Contains("<-- bad"), - Contains("CI commit 09").DoesNotContain("<--"), - Contains("CI commit 08").DoesNotContain("<--"), - Contains("CI commit 07").DoesNotContain("<--"), - Contains("CI commit 06").DoesNotContain("<--"), - Contains("CI commit 05").Contains("<-- current").IsSelected(), - Contains("CI commit 04").DoesNotContain("<--"), - Contains("CI commit 03").DoesNotContain("<--"), - Contains("CI commit 02").DoesNotContain("<--"), - Contains("CI commit 01").Contains("<-- good"), - ). - Press(keys.Commits.ViewBisectOptions). - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Bisect")). - // Does not show a "Skip selected commit" entry: - Lines( - Contains("b Mark current commit").Contains("as bad"), - Contains("g Mark current commit").Contains("as good"), - Contains("s Skip current commit"), - Contains("r Reset bisect"), - Contains("Cancel"), - ). - Select(Contains("Skip current commit")).Confirm() - }). - // Skipping the current commit selects the new current commit: - Lines( - Contains("CI commit 10").Contains("<-- bad"), - Contains("CI commit 09").DoesNotContain("<--"), - Contains("CI commit 08").DoesNotContain("<--"), - Contains("CI commit 07").DoesNotContain("<--"), - Contains("CI commit 06").Contains("<-- current").IsSelected(), - Contains("CI commit 05").Contains("<-- skipped"), - Contains("CI commit 04").DoesNotContain("<--"), - Contains("CI commit 03").DoesNotContain("<--"), - Contains("CI commit 02").DoesNotContain("<--"), - Contains("CI commit 01").Contains("<-- good"), - ). - NavigateToLine(Contains("commit 07")). - Press(keys.Commits.ViewBisectOptions). - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Bisect")). - // Does show a "Skip selected commit" entry: - Lines( - Contains("b Mark current commit").Contains("as bad"), - Contains("g Mark current commit").Contains("as good"), - Contains("s Skip current commit"), - Contains("S Skip selected commit"), - Contains("r Reset bisect"), - Contains("Cancel"), - ). - Select(Contains("Skip selected commit")).Confirm() - }). - // Skipping a selected, non-current commit keeps the selection - // there: - SelectedLine(Contains("CI commit 07").Contains("<-- skipped")) - }, -}) diff --git a/pkg/integration/tests/branch/checkout_autostash.go b/pkg/integration/tests/branch/checkout_autostash.go deleted file mode 100644 index 1256b3332e1..00000000000 --- a/pkg/integration/tests/branch/checkout_autostash.go +++ /dev/null @@ -1,54 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CheckoutAutostash = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Check out a branch that requires performing autostash", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file", "a\n\nb") - shell.Commit("add file") - shell.UpdateFileAndAdd("file", "a\n\nc") - shell.Commit("edit last line") - - shell.Checkout("HEAD^") - shell.UpdateFile("file", "b\n\nb") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Lines( - Contains("file"), - ) - - t.Views().Branches(). - Focus(). - Lines( - MatchesRegexp(`\*.*HEAD`).IsSelected(), - Contains("master"), - ). - NavigateToLine(Contains("master")). - PressPrimaryAction() - - t.ExpectPopup().Confirmation(). - Title(Contains("Autostash?")). - Content(Contains("You must stash and pop your changes to bring them across. Do this automatically? (enter/esc)")). - Confirm() - - t.Views().Branches(). - Lines( - Contains("master").IsSelected(), - ) - - t.Git().CurrentBranchName("master") - - t.Views().Files(). - Lines( - Contains("file"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/checkout_by_name.go b/pkg/integration/tests/branch/checkout_by_name.go deleted file mode 100644 index 949730c3023..00000000000 --- a/pkg/integration/tests/branch/checkout_by_name.go +++ /dev/null @@ -1,42 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CheckoutByName = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Try to checkout branch by name. Verify that it also works on the branch with the special name @.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(3). - NewBranch("@"). - Checkout("master"). - EmptyCommit("blah") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("master").IsSelected(), - Contains("@"), - ). - SelectNextItem(). - Press(keys.Branches.CheckoutBranchByName). - Tap(func() { - t.ExpectPopup().Prompt().Title(Equals("Branch name:")).Type("new-branch").Confirm() - - t.ExpectPopup().Alert().Title(Equals("Branch not found")).Content(Equals("Branch not found. Create a new branch named new-branch?")).Confirm() - }). - Lines( - MatchesRegexp(`\*.*new-branch`).IsSelected(), - Contains("@"), - Contains("master"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/checkout_previous_branch.go b/pkg/integration/tests/branch/checkout_previous_branch.go deleted file mode 100644 index 8a8698d85c7..00000000000 --- a/pkg/integration/tests/branch/checkout_previous_branch.go +++ /dev/null @@ -1,51 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CheckoutPreviousBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Checkout to the previous branch using the checkout previous branch functionality", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(3). - NewBranch("previous-branch"). - EmptyCommit("previous commit"). - Checkout("master"). - EmptyCommit("master commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("master").IsSelected(), - Contains("previous-branch"), - ) - - // Press the checkout previous branch key (should checkout previous-branch) - t.Views().Branches(). - Press(keys.Branches.CheckoutPreviousBranch). - Lines( - Contains("previous-branch").IsSelected(), - Contains("master"), - ) - - // Verify we're on previous-branch - t.Git().CurrentBranchName("previous-branch") - - // Press again to go back to master - t.Views().Branches(). - Press(keys.Branches.CheckoutPreviousBranch). - Lines( - Contains("master").IsSelected(), - Contains("previous-branch"), - ) - - // Verify we're back on master - t.Git().CurrentBranchName("master") - }, -}) diff --git a/pkg/integration/tests/branch/create_tag.go b/pkg/integration/tests/branch/create_tag.go deleted file mode 100644 index 9ea100f4f06..00000000000 --- a/pkg/integration/tests/branch/create_tag.go +++ /dev/null @@ -1,43 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CreateTag = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create a new tag on branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(10). - NewBranch("new-branch"). - EmptyCommit("new commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - MatchesRegexp(`\*\s*new-branch`).IsSelected(), - MatchesRegexp(`master`), - ). - SelectNextItem(). - Press(keys.Branches.CreateTag) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Tag name")). - Type("new-tag"). - Confirm() - - t.Views().Tags().Focus(). - Lines( - MatchesRegexp(`new-tag`).IsSelected(), - ) - - t.Git(). - TagNamesAt("HEAD", []string{}). - TagNamesAt("master", []string{"new-tag"}) - }, -}) diff --git a/pkg/integration/tests/branch/delete.go b/pkg/integration/tests/branch/delete.go deleted file mode 100644 index 2e6a19c2eae..00000000000 --- a/pkg/integration/tests/branch/delete.go +++ /dev/null @@ -1,231 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Delete = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Try all combination of local and remote branch deletions", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - config.GetUserConfig().Git.RemoteBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - shell. - CloneIntoRemote("origin"). - EmptyCommit("blah"). - NewBranch("branch-one"). - EmptyCommit("on branch-one 01"). - PushBranchAndSetUpstream("origin", "branch-one"). - EmptyCommit("on branch-one 02"). - Checkout("master"). - Merge("branch-one"). // branch-one is contained in master, so no delete confirmation - NewBranch("branch-two"). - EmptyCommit("on branch-two 01"). - PushBranchAndSetUpstream("origin", "branch-two"). // branch-two is contained in its own upstream, so no delete confirmation either - NewBranchFrom("branch-three", "master"). - EmptyCommit("on branch-three 01"). - NewBranch("current-head"). // branch-three is contained in the current head, so no delete confirmation - EmptyCommit("on current-head"). - NewBranchFrom("branch-four", "master"). - EmptyCommit("on branch-four 01"). - PushBranchAndSetUpstream("origin", "branch-four"). - EmptyCommit("on branch-four 02"). // branch-four is not contained in any of these, so we get a delete confirmation - NewBranchFrom("branch-five", "master"). - EmptyCommit("on branch-five 01"). - PushBranchAndSetUpstream("origin", "branch-five"). // branch-five is contained in its own upstream - NewBranchFrom("branch-six", "master"). - EmptyCommit("on branch-six 01"). - PushBranchAndSetUpstream("origin", "branch-six"). - EmptyCommit("on branch-six 02"). // branch-six is not contained in any of these, so we get a delete confirmation - Checkout("current-head") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("current-head").IsSelected(), - Contains("branch-six ↑1"), - Contains("branch-five ✓"), - Contains("branch-four ↑1"), - Contains("branch-three"), - Contains("branch-two ✓"), - Contains("master"), - Contains("branch-one ↑1"), - ). - - // Deleting the current branch is not possible - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Tooltip(Contains("You cannot delete the checked out branch!")). - Title(Equals("Delete branch 'current-head'?")). - Select(Contains("Delete local branch")). - Confirm(). - Tap(func() { - t.ExpectToast(Contains("You cannot delete the checked out branch!")) - }). - Cancel() - }). - - // Delete branch-four. This is the only branch that is not fully merged, so we get - // a confirmation popup. - NavigateToLine(Contains("branch-four")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'branch-four'?")). - Select(Contains("Delete local branch")). - Confirm() - t.ExpectPopup(). - Confirmation(). - Title(Equals("Force delete branch")). - Content(Equals("'branch-four' is not fully merged. Are you sure you want to delete it?")). - Confirm() - }). - Lines( - Contains("current-head"), - Contains("branch-six ↑1"), - Contains("branch-five ✓"), - Contains("branch-three").IsSelected(), - Contains("branch-two ✓"), - Contains("master"), - Contains("branch-one ↑1"), - ). - - // Delete branch-three. This branch is contained in the current head, so this just works - // without any confirmation. - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'branch-three'?")). - Select(Contains("Delete local branch")). - Confirm() - }). - Lines( - Contains("current-head"), - Contains("branch-six ↑1"), - Contains("branch-five ✓"), - Contains("branch-two ✓").IsSelected(), - Contains("master"), - Contains("branch-one ↑1"), - ). - - // Delete branch-two. This branch is contained in its own upstream, so this just works - // without any confirmation. - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'branch-two'?")). - Select(Contains("Delete local branch")). - Confirm() - }). - Lines( - Contains("current-head"), - Contains("branch-six ↑1"), - Contains("branch-five ✓"), - Contains("master").IsSelected(), - Contains("branch-one ↑1"), - ). - - // Delete remote branch of branch-one. We only get the normal remote branch confirmation for this one. - SelectNextItem(). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'branch-one'?")). - Select(Contains("Delete remote branch")). - Confirm() - }). - Tap(func() { - t.ExpectPopup(). - Confirmation(). - Title(Equals("Delete branch 'branch-one'?")). - Content(Equals("Are you sure you want to delete the remote branch 'branch-one' from 'origin'?")). - Confirm() - }). - Tap(func() { - checkRemoteBranches(t, keys, "origin", []string{ - "branch-five", - "branch-four", - "branch-six", - "branch-two", - }) - }). - Lines( - Contains("current-head"), - Contains("branch-six ↑1"), - Contains("branch-five ✓"), - Contains("master"), - Contains("branch-one (upstream gone)").IsSelected(), - ). - - // Delete local branch of branch-one. Even though its upstream is gone, we don't get a confirmation - // because it is contained in master. - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'branch-one'?")). - Select(Contains("Delete local branch")). - Confirm() - }). - Lines( - Contains("current-head"), - Contains("branch-six ↑1"), - Contains("branch-five ✓"), - Contains("master").IsSelected(), - ). - - // Delete both local and remote branch of branch-six. We get the force-delete warning because it is not fully merged. - NavigateToLine(Contains("branch-six")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'branch-six'?")). - Select(Contains("Delete local and remote branch")). - Confirm() - t.ExpectPopup(). - Confirmation(). - Title(Equals("Delete local and remote branch")). - Content(Contains("Are you sure you want to delete both 'branch-six' from your machine, and 'branch-six' from 'origin'?"). - Contains("'branch-six' is not fully merged. Are you sure you want to delete it?")). - Confirm() - }). - Lines( - Contains("current-head"), - Contains("branch-five ✓").IsSelected(), - Contains("master"), - ). - - // Delete both local and remote branch of branch-five. We get the same popups, but the confirmation - // doesn't contain the force-delete warning. - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'branch-five'?")). - Select(Contains("Delete local and remote branch")). - Confirm() - t.ExpectPopup(). - Confirmation(). - Title(Equals("Delete local and remote branch")). - Content(Equals("Are you sure you want to delete both 'branch-five' from your machine, and 'branch-five' from 'origin'?"). - DoesNotContain("not fully merged")). - Confirm() - }). - Lines( - Contains("current-head"), - Contains("master").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/branch/delete_multiple.go b/pkg/integration/tests/branch/delete_multiple.go deleted file mode 100644 index 0bba33b9d8e..00000000000 --- a/pkg/integration/tests/branch/delete_multiple.go +++ /dev/null @@ -1,187 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DeleteMultiple = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Try some combinations of local and remote branch deletions with a range selection of branches", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - config.GetUserConfig().Git.RemoteBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - shell. - CloneIntoRemote("origin"). - CloneIntoRemote("other-remote"). - EmptyCommit("blah"). - NewBranch("branch-01"). - EmptyCommit("on branch-01 01"). - PushBranchAndSetUpstream("origin", "branch-01"). - EmptyCommit("on branch-01 02"). - NewBranch("branch-02"). - EmptyCommit("on branch-02 01"). - PushBranchAndSetUpstream("origin", "branch-02"). - NewBranchFrom("branch-03", "master"). - EmptyCommit("on branch-03 01"). - NewBranch("current-head"). - EmptyCommit("on current-head"). - NewBranchFrom("branch-04", "master"). - EmptyCommit("on branch-04 01"). - PushBranchAndSetUpstream("other-remote", "branch-04"). - EmptyCommit("on branch-04 02"). - NewBranchFrom("branch-05", "master"). - EmptyCommit("on branch-05 01"). - PushBranchAndSetUpstream("origin", "branch-05"). - NewBranchFrom("branch-06", "master"). - EmptyCommit("on branch-06 01"). - PushBranch("origin", "branch-06"). - PushBranchAndSetUpstream("other-remote", "branch-06"). - EmptyCommit("on branch-06 02"). - Checkout("current-head") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("current-head").IsSelected(), - Contains("branch-01 ↑1"), - Contains("branch-02 ✓"), - Contains("branch-03"), - Contains("branch-04 ↑1"), - Contains("branch-05 ✓"), - Contains("branch-06 ↑1"), - Contains("master"), - ). - Press(keys.Universal.RangeSelectDown). - - // Deleting a range that includes the current branch is not possible - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Tooltip(Contains("You cannot delete the checked out branch!")). - Title(Equals("Delete selected branches?")). - Select(Contains("Delete local branches")). - Confirm(). - Tap(func() { - t.ExpectToast(Contains("You cannot delete the checked out branch!")) - }). - Cancel() - }). - - // Delete branch-03 and branch-04. 04 is not fully merged, so we get - // a confirmation popup. - NavigateToLine(Contains("branch-03")). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete selected branches?")). - Select(Contains("Delete local branches")). - Confirm() - t.ExpectPopup(). - Confirmation(). - Title(Equals("Force delete branch")). - Content(Equals("Some of the selected branches are not fully merged. Are you sure you want to delete them?")). - Confirm() - }). - Lines( - Contains("current-head"), - Contains("branch-01 ↑1"), - Contains("branch-02 ✓"), - Contains("branch-05 ✓").IsSelected(), - Contains("branch-06 ↑1"), - Contains("master"), - ). - - // Delete remote branches of branch-05 and branch-06. They are on different remotes. - NavigateToLine(Contains("branch-05")). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete selected branches?")). - Select(Contains("Delete remote branches")). - Confirm() - }). - Tap(func() { - t.ExpectPopup(). - Confirmation(). - Title(Equals("Delete selected branches?")). - Content(Equals("Are you sure you want to delete the remote branches of the selected branches from their respective remotes?")). - Confirm() - }). - Tap(func() { - checkRemoteBranches(t, keys, "origin", []string{ - "branch-01", - "branch-02", - "branch-06", - }) - checkRemoteBranches(t, keys, "other-remote", []string{ - "branch-04", - }) - }). - Lines( - Contains("current-head"), - Contains("branch-01 ↑1"), - Contains("branch-02 ✓"), - Contains("branch-05 (upstream gone)").IsSelected(), - Contains("branch-06 (upstream gone)").IsSelected(), - Contains("master"), - ). - - // Try to delete both local and remote branches of branch-02 and - // branch-05; not possible because branch-05's upstream is gone - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete selected branches?")). - Select(Contains("Delete local and remote branches")). - Confirm(). - Tap(func() { - t.ExpectToast(Contains("Some of the selected branches have no upstream (or the upstream is not stored locally)")) - }). - Cancel() - }). - - // Delete both local and remote branches of branch-01 and branch-02. We get - // the force-delete warning because branch-01 it is not fully merged. - NavigateToLine(Contains("branch-01")). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete selected branches?")). - Select(Contains("Delete local and remote branches")). - Confirm() - t.ExpectPopup(). - Confirmation(). - Title(Equals("Delete local and remote branch")). - Content(Contains("Are you sure you want to delete both the selected branches from your machine, and their remote branches from their respective remotes?"). - Contains("Some of the selected branches are not fully merged. Are you sure you want to delete them?")). - Confirm() - }). - Lines( - Contains("current-head"), - Contains("branch-05 (upstream gone)").IsSelected(), - Contains("branch-06 (upstream gone)"), - Contains("master"), - ). - Tap(func() { - checkRemoteBranches(t, keys, "origin", []string{ - "branch-06", - }) - checkRemoteBranches(t, keys, "other-remote", []string{ - "branch-04", - }) - }) - }, -}) diff --git a/pkg/integration/tests/branch/delete_remote_branch_with_credential_prompt.go b/pkg/integration/tests/branch/delete_remote_branch_with_credential_prompt.go deleted file mode 100644 index 6e3f5202855..00000000000 --- a/pkg/integration/tests/branch/delete_remote_branch_with_credential_prompt.go +++ /dev/null @@ -1,87 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DeleteRemoteBranchWithCredentialPrompt = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Delete a remote branch where credentials are required", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - - shell.CloneIntoRemote("origin") - - shell.NewBranch("mybranch") - - shell.PushBranchAndSetUpstream("origin", "mybranch") - - // actually getting a password prompt is tricky: it requires SSH'ing into localhost under a newly created, restricted, user. - // This is not easy to do in a cross-platform way, nor is it easy to do in a docker container. - // If you can think of a way to do it, please let me know! - shell.CopyHelpFile("pre-push", ".git/hooks/pre-push") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - deleteBranch := func() { - t.Views().Branches(). - Focus(). - Press(keys.Universal.Remove) - - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'mybranch'?")). - Select(Contains("Delete remote branch")). - Confirm() - - t.ExpectPopup(). - Confirmation(). - Title(Equals("Delete branch 'mybranch'?")). - Content(Equals("Are you sure you want to delete the remote branch 'mybranch' from 'origin'?")). - Confirm() - } - - t.Views().Status().Content(Equals("✓ repo → mybranch")) - - deleteBranch() - - // correct credentials are: username=username, password=password - - t.ExpectPopup().Prompt(). - Title(Equals("Username")). - Type("username"). - Confirm() - - // enter incorrect password - t.ExpectPopup().Prompt(). - Title(Equals("Password")). - Type("incorrect password"). - Confirm() - - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Contains("incorrect username/password")). - Confirm() - - t.Views().Status().Content(Equals("✓ repo → mybranch")) - - // try again with correct password - deleteBranch() - - t.ExpectPopup().Prompt(). - Title(Equals("Username")). - Type("username"). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("Password")). - Type("password"). - Confirm() - - t.Views().Status().Content(Equals("(upstream gone) repo → mybranch")) - t.Views().Branches().TopLines(Contains("mybranch (upstream gone)")) - }, -}) diff --git a/pkg/integration/tests/branch/delete_remote_branch_with_different_name.go b/pkg/integration/tests/branch/delete_remote_branch_with_different_name.go deleted file mode 100644 index 91f4ede2716..00000000000 --- a/pkg/integration/tests/branch/delete_remote_branch_with_different_name.go +++ /dev/null @@ -1,49 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DeleteRemoteBranchWithDifferentName = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Delete a remote branch that has a different name than the local branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.CloneIntoRemote("origin") - shell.NewBranch("mybranch-local") - shell.PushBranchAndSetUpstream("origin", "mybranch-local:mybranch-remote") - shell.Checkout("master") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("master").IsSelected(), - Contains("mybranch-local ✓"), - ). - SelectNextItem(). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'mybranch-local'?")). - Select(Contains("Delete remote branch")). - Confirm() - }). - Tap(func() { - t.ExpectPopup(). - Confirmation(). - Title(Equals("Delete branch 'mybranch-remote'?")). - Content(Equals("Are you sure you want to delete the remote branch 'mybranch-remote' from 'origin'?")). - Confirm() - }). - Lines( - Contains("master"), - Contains("mybranch-local (upstream gone)").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/branch/delete_while_filtering.go b/pkg/integration/tests/branch/delete_while_filtering.go deleted file mode 100644 index 2b2039d67c5..00000000000 --- a/pkg/integration/tests/branch/delete_while_filtering.go +++ /dev/null @@ -1,49 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// Regression test for deleting the last branch in the unfiltered list while -// filtering is on. This used to cause a segfault. -var DeleteWhileFiltering = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Delete a local branch while there's a filter in the branches panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.NewBranch("branch1") - shell.NewBranch("branch2") - shell.Checkout("master") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("master").IsSelected(), - Contains("branch1"), - Contains("branch2"), - ). - FilterOrSearch("branch"). - Lines( - Contains("branch1").IsSelected(), - Contains("branch2"), - ). - SelectNextItem(). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'branch2'?")). - Select(Contains("Delete local branch")). - Confirm() - }). - Lines( - Contains("branch1").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/branch/detached_head.go b/pkg/integration/tests/branch/detached_head.go deleted file mode 100644 index 9a419540b78..00000000000 --- a/pkg/integration/tests/branch/detached_head.go +++ /dev/null @@ -1,40 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DetachedHead = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create a new branch on detached head", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(10). - Checkout("HEAD^") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - MatchesRegexp(`\*.*HEAD`).IsSelected(), - MatchesRegexp(`master`), - ). - Press(keys.Universal.New) - - t.ExpectPopup().Prompt(). - Title(MatchesRegexp(`^New branch name \(branch is off of '[0-9a-f]+'\)$`)). - Type("new-branch"). - Confirm() - - t.Views().Branches(). - Lines( - MatchesRegexp(`\* new-branch`).IsSelected(), - MatchesRegexp(`master`), - ) - - t.Git().CurrentBranchName("new-branch") - }, -}) diff --git a/pkg/integration/tests/branch/merge_fast_forward.go b/pkg/integration/tests/branch/merge_fast_forward.go deleted file mode 100644 index 707af1b3e8a..00000000000 --- a/pkg/integration/tests/branch/merge_fast_forward.go +++ /dev/null @@ -1,68 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MergeFastForward = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Merge a branch into another using fast-forward merge", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - shell.NewBranch("original-branch"). - EmptyCommit("one"). - NewBranch("branch1"). - EmptyCommit("branch1"). - Checkout("original-branch"). - NewBranchFrom("branch2", "original-branch"). - EmptyCommit("branch2"). - Checkout("original-branch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("original-branch").IsSelected(), - Contains("branch1"), - Contains("branch2"), - ). - SelectNextItem(). - Press(keys.Branches.MergeIntoCurrentBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Merge")). - TopLines( - Contains("Regular merge (fast-forward)"), - Contains("Regular merge (with merge commit)"), - ). - Select(Contains("Regular merge (fast-forward)")). - Confirm() - - t.Views().Commits(). - Lines( - Contains("branch1").IsSelected(), - Contains("one"), - ) - - // Check that branch2 can't be merged using fast-forward - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("branch2")). - Press(keys.Branches.MergeIntoCurrentBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Merge")). - TopLines( - Contains("Regular merge (with merge commit)"), - Contains("Regular merge (fast-forward)"), - ). - Select(Contains("Regular merge (fast-forward)")). - Confirm() - - t.ExpectToast(Contains("Cannot fast-forward 'original-branch' to 'branch2'")) - }, -}) diff --git a/pkg/integration/tests/branch/merge_non_fast_forward.go b/pkg/integration/tests/branch/merge_non_fast_forward.go deleted file mode 100644 index a1e90d0497a..00000000000 --- a/pkg/integration/tests/branch/merge_non_fast_forward.go +++ /dev/null @@ -1,76 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MergeNonFastForward = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Merge a branch into another using non-fast-forward merge", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - shell.NewBranch("original-branch"). - EmptyCommit("one"). - NewBranch("branch1"). - EmptyCommit("branch1"). - Checkout("original-branch"). - NewBranchFrom("branch2", "original-branch"). - EmptyCommit("branch2"). - Checkout("original-branch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("original-branch").IsSelected(), - Contains("branch1"), - Contains("branch2"), - ). - SelectNextItem(). - Press(keys.Branches.MergeIntoCurrentBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Merge")). - TopLines( - Contains("Regular merge (fast-forward)"), - Contains("Regular merge (with merge commit)"), - ). - Select(Contains("Regular merge (with merge commit)")). - Confirm() - - t.Views().Commits(). - Lines( - Contains("⏣─╮ Merge branch 'branch1' into original-branch").IsSelected(), - Contains("│ ◯ * branch1"), - Contains("◯─╯ one"), - ) - - // Check that branch2 shows the non-fast-forward option first - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("branch2")). - Press(keys.Branches.MergeIntoCurrentBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Merge")). - TopLines( - Contains("Regular merge (with merge commit)"), - Contains("Regular merge (fast-forward)"), - ). - Select(Contains("Regular merge (with merge commit)")). - Confirm() - - t.Views().Commits(). - Lines( - Contains("⏣─╮ Merge branch 'branch2' into original-branch").IsSelected(), - Contains("│ ◯ * branch2"), - Contains("⏣─│─╮ Merge branch 'branch1' into original-branch"), - Contains("│ │ ◯ * branch1"), - Contains("◯─┴─╯ one"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/move_commits_to_new_branch_from_base_branch.go b/pkg/integration/tests/branch/move_commits_to_new_branch_from_base_branch.go deleted file mode 100644 index 0b6bd71aa64..00000000000 --- a/pkg/integration/tests/branch/move_commits_to_new_branch_from_base_branch.go +++ /dev/null @@ -1,66 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveCommitsToNewBranchFromBaseBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create a new branch from the commits that you accidentally made on the wrong branch; choosing base branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CloneIntoRemote("origin") - shell.PushBranchAndSetUpstream("origin", "master") - shell.NewBranch("feature") - shell.EmptyCommit("feature branch commit") - shell.PushBranchAndSetUpstream("origin", "feature") - shell.CreateFileAndAdd("file1", "file1 content") - shell.Commit("new commit 1") - shell.EmptyCommit("new commit 2") - shell.UpdateFile("file1", "file1 changed") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Lines( - Contains("M file1"), - ) - t.Views().Branches(). - Focus(). - Lines( - Contains("feature ↑2").IsSelected(), - Contains("master ✓"), - ). - Press(keys.Branches.MoveCommitsToNewBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Move commits to new branch")). - Select(Contains("New branch from base branch (origin/master)")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New branch name (branch is off of 'origin/master')")). - Type("new branch"). - Confirm() - - t.Views().Branches(). - Lines( - Contains("new-branch").DoesNotContain("↑").IsSelected(), - Contains("feature ✓"), - Contains("master ✓"), - ) - - t.Views().Commits(). - Lines( - Contains("new commit 2").IsSelected(), - Contains("new commit 1"), - Contains("initial commit"), - ) - t.Views().Files(). - Lines( - Contains("M file1"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/move_commits_to_new_branch_from_main_branch.go b/pkg/integration/tests/branch/move_commits_to_new_branch_from_main_branch.go deleted file mode 100644 index 3373064eb90..00000000000 --- a/pkg/integration/tests/branch/move_commits_to_new_branch_from_main_branch.go +++ /dev/null @@ -1,61 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveCommitsToNewBranchFromMainBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create a new branch from the commits that you accidentally made on master", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CloneIntoRemote("origin") - shell.PushBranchAndSetUpstream("origin", "master") - shell.CreateFileAndAdd("file1", "file1 content") - shell.Commit("new commit 1") - shell.EmptyCommit("new commit 2") - shell.UpdateFile("file1", "file1 changed") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Lines( - Contains("M file1"), - ) - t.Views().Branches(). - Focus(). - Lines( - Contains("master ↑2").IsSelected(), - ). - Press(keys.Branches.MoveCommitsToNewBranch) - - t.ExpectPopup().Confirmation(). - Title(Equals("Move commits to new branch")). - Content(Contains("This will take all unpushed commits and move them to a new branch (off of master).")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New branch name (branch is off of 'master')")). - Type("new branch"). - Confirm() - - t.Views().Branches(). - Lines( - Contains("new-branch").DoesNotContain("↑").IsSelected(), - Contains("master ✓"), - ) - - t.Views().Commits(). - Lines( - Contains("new commit 2").IsSelected(), - Contains("new commit 1"), - Contains("initial commit"), - ) - t.Views().Files(). - Lines( - Contains("M file1"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/move_commits_to_new_branch_keep_stacked.go b/pkg/integration/tests/branch/move_commits_to_new_branch_keep_stacked.go deleted file mode 100644 index 143adad0bf4..00000000000 --- a/pkg/integration/tests/branch/move_commits_to_new_branch_keep_stacked.go +++ /dev/null @@ -1,70 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveCommitsToNewBranchKeepStacked = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create a new branch from the commits that you accidentally made on the wrong branch; choosing stacked on current branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.BranchPrefix = "myprefix/" - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CloneIntoRemote("origin") - shell.PushBranchAndSetUpstream("origin", "master") - shell.NewBranch("feature") - shell.EmptyCommit("feature branch commit") - shell.PushBranchAndSetUpstream("origin", "feature") - shell.CreateFileAndAdd("file1", "file1 content") - shell.Commit("new commit 1") - shell.EmptyCommit("new commit 2") - shell.UpdateFile("file1", "file1 changed") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Lines( - Contains("M file1"), - ) - t.Views().Branches(). - Focus(). - Lines( - Contains("feature ↑2").IsSelected(), - Contains("master ✓"), - ). - Press(keys.Branches.MoveCommitsToNewBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Move commits to new branch")). - Select(Contains("New branch stacked on current branch (feature)")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New branch name (branch is off of 'feature')")). - InitialText(Equals("myprefix/")). - Type("new branch"). - Confirm() - - t.Views().Branches(). - Lines( - Contains("myprefix/new-branch").DoesNotContain("↑").IsSelected(), - Contains("feature ✓"), - Contains("master ✓"), - ) - - t.Views().Commits(). - Lines( - Contains("new commit 2").IsSelected(), - Contains("new commit 1"), - Contains("* feature branch commit"), - Contains("initial commit"), - ) - t.Views().Files(). - Lines( - Contains("M file1"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/new_branch_autostash.go b/pkg/integration/tests/branch/new_branch_autostash.go deleted file mode 100644 index c20088a041c..00000000000 --- a/pkg/integration/tests/branch/new_branch_autostash.go +++ /dev/null @@ -1,60 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var NewBranchAutostash = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create a new branch that requires performing autostash", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file", "a\n\nb") - shell.Commit("add file") - shell.UpdateFileAndAdd("file", "a\n\nc") - shell.Commit("edit last line") - - shell.Checkout("HEAD^") - shell.UpdateFile("file", "b\n\nb") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Lines( - Contains("file"), - ) - - t.Views().Branches(). - Focus(). - Lines( - MatchesRegexp(`\*.*HEAD`).IsSelected(), - Contains("master"), - ). - NavigateToLine(Contains("master")). - Press(keys.Universal.New) - - t.ExpectPopup().Prompt(). - Title(Contains("New branch name (branch is off of 'master')")). - Type("new-branch"). - Confirm() - - t.ExpectPopup().Confirmation(). - Title(Contains("Autostash?")). - Content(Contains("You must stash and pop your changes to bring them across. Do this automatically? (enter/esc)")). - Confirm() - - t.Views().Branches(). - Lines( - Contains("new-branch").IsSelected(), - Contains("master"), - ) - - t.Git().CurrentBranchName("new-branch") - - t.Views().Files(). - Lines( - Contains("file"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/new_branch_from_remote_tracking_different_name.go b/pkg/integration/tests/branch/new_branch_from_remote_tracking_different_name.go deleted file mode 100644 index 75cc2828096..00000000000 --- a/pkg/integration/tests/branch/new_branch_from_remote_tracking_different_name.go +++ /dev/null @@ -1,50 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var NewBranchFromRemoteTrackingDifferentName = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Set tracking information when creating a new branch from a remote branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("commit") - shell.NewBranch("other_branch") - shell.CloneIntoRemote("origin") - shell.Checkout("master") - shell.RunCommand([]string{"git", "branch", "-D", "other_branch"}) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Remotes(). - Focus(). - Lines( - Contains("origin").IsSelected(), - ). - PressEnter() - - t.Views().RemoteBranches(). - IsFocused(). - Lines( - Contains("master").IsSelected(), - Contains("other_branch"), - ). - SelectNextItem(). - Press(keys.Universal.New) - - t.ExpectPopup().Prompt(). - Title(Equals("New branch name (branch is off of 'origin/other_branch')")). - Clear(). - Type("different_name"). - Confirm() - - t.Views().Branches(). - Focus(). - Lines( - Contains("different_name").DoesNotContain("✓"), - Contains("master"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/new_branch_from_remote_tracking_same_name.go b/pkg/integration/tests/branch/new_branch_from_remote_tracking_same_name.go deleted file mode 100644 index 753fd32fb9d..00000000000 --- a/pkg/integration/tests/branch/new_branch_from_remote_tracking_same_name.go +++ /dev/null @@ -1,48 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var NewBranchFromRemoteTrackingSameName = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Set tracking information when creating a new branch from a remote branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("commit") - shell.NewBranch("other_branch") - shell.CloneIntoRemote("origin") - shell.Checkout("master") - shell.RunCommand([]string{"git", "branch", "-D", "other_branch"}) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Remotes(). - Focus(). - Lines( - Contains("origin").IsSelected(), - ). - PressEnter() - - t.Views().RemoteBranches(). - IsFocused(). - Lines( - Contains("master").IsSelected(), - Contains("other_branch"), - ). - SelectNextItem(). - Press(keys.Universal.New) - - t.ExpectPopup().Prompt(). - Title(Equals("New branch name (branch is off of 'origin/other_branch')")). - Confirm() - - t.Views().Branches(). - Focus(). - Lines( - Contains("other_branch").Contains("✓"), - Contains("master"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/new_branch_with_prefix.go b/pkg/integration/tests/branch/new_branch_with_prefix.go deleted file mode 100644 index 61da06f94b1..00000000000 --- a/pkg/integration/tests/branch/new_branch_with_prefix.go +++ /dev/null @@ -1,33 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var NewBranchWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Creating a new branch from a commit with a default name", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().Git.BranchPrefix = "myprefix/" - }, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("commit 1") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 1").IsSelected(), - ). - SelectNextItem(). - Press(keys.Universal.New). - Tap(func() { - branchName := "my-branch-name" - t.ExpectPopup().Prompt().Title(Contains("New branch name")).Type(branchName).Confirm() - t.Git().CurrentBranchName("myprefix/" + branchName) - }) - }, -}) diff --git a/pkg/integration/tests/branch/new_branch_with_prefix_using_run_command.go b/pkg/integration/tests/branch/new_branch_with_prefix_using_run_command.go deleted file mode 100644 index 1d41eda63b4..00000000000 --- a/pkg/integration/tests/branch/new_branch_with_prefix_using_run_command.go +++ /dev/null @@ -1,36 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var NewBranchWithPrefixUsingRunCommand = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Creating a new branch with a branch prefix using a runCommand", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().Git.BranchPrefix = "myprefix/{{ runCommand \"echo dynamic\" }}/" - }, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("commit 1") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 1").IsSelected(), - ). - SelectNextItem(). - Press(keys.Universal.New). - Tap(func() { - t.ExpectPopup().Prompt(). - Title(Contains("New branch name")). - InitialText(Equals("myprefix/dynamic/")). - Type("my-branch"). - Confirm() - t.Git().CurrentBranchName("myprefix/dynamic/my-branch") - }) - }, -}) diff --git a/pkg/integration/tests/branch/open_pull_request_invalid_target_remote_name.go b/pkg/integration/tests/branch/open_pull_request_invalid_target_remote_name.go deleted file mode 100644 index ab5e36d048a..00000000000 --- a/pkg/integration/tests/branch/open_pull_request_invalid_target_remote_name.go +++ /dev/null @@ -1,54 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var OpenPullRequestInvalidTargetRemoteName = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Open up a pull request, specifying a non-existing target remote", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - // Create an initial commit ('git branch set-upstream-to' bails out otherwise) - shell.CreateFileAndAdd("file", "content1") - shell.Commit("one") - - // Create a new branch - shell.NewBranch("branch-1") - - // Create a couple of remotes - shell.CloneIntoRemote("upstream") - shell.CloneIntoRemote("origin") - - // To allow a pull request to be created from a branch, it must have an upstream set. - shell.SetBranchUpstream("branch-1", "origin/branch-1") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // Open a PR for the current branch (i.e. 'branch-1') - t.Views(). - Branches(). - Focus(). - Press(keys.Branches.ViewPullRequestOptions) - - t.ExpectPopup(). - Menu(). - Title(Equals("View create pull request options")). - Select(Contains("Select branch")). - Confirm() - - // Verify that we're prompted to enter the remote and enter the name of a non-existing one. - t.ExpectPopup(). - Prompt(). - Title(Equals("Select target remote")). - Type("non-existing-remote"). - Confirm() - - // Verify that this leads to an error being shown (instead of progressing to branch selection). - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Contains("A remote named 'non-existing-remote' does not exist")). - Confirm() - }, -}) diff --git a/pkg/integration/tests/branch/open_pull_request_no_upstream.go b/pkg/integration/tests/branch/open_pull_request_no_upstream.go deleted file mode 100644 index 877f0bddedb..00000000000 --- a/pkg/integration/tests/branch/open_pull_request_no_upstream.go +++ /dev/null @@ -1,25 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var OpenPullRequestNoUpstream = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Open up a pull request with a missing upstream branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views(). - Branches(). - Focus(). - Press(keys.Branches.CreatePullRequest) - - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Contains("Cannot open a pull request for a branch with no upstream")). - Confirm() - }, -}) diff --git a/pkg/integration/tests/branch/open_pull_request_select_remote_and_target_branch.go b/pkg/integration/tests/branch/open_pull_request_select_remote_and_target_branch.go deleted file mode 100644 index ac744210f80..00000000000 --- a/pkg/integration/tests/branch/open_pull_request_select_remote_and_target_branch.go +++ /dev/null @@ -1,74 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var OpenPullRequestSelectRemoteAndTargetBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Open up a pull request, specifying a remote and target branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.OpenLink = "echo {{link}} > /tmp/openlink" - }, - SetupRepo: func(shell *Shell) { - // Create an initial commit ('git branch set-upstream-to' bails out otherwise) - shell.CreateFileAndAdd("file", "content1") - shell.Commit("one") - - // Create a new branch and a remote that has that branch - shell.NewBranch("branch-1") - shell.CloneIntoRemote("upstream") - - // Create another branch and a second remote. The first remote doesn't have this branch. - shell.NewBranch("branch-2") - shell.CloneIntoRemote("origin") - - // To allow a pull request to be created from a branch, it must have an upstream set. - shell.SetBranchUpstream("branch-2", "origin/branch-2") - - shell.RunCommand([]string{"git", "remote", "set-url", "origin", "https://github.com/my-personal-fork/lazygit"}) - shell.RunCommand([]string{"git", "remote", "set-url", "upstream", "https://github.com/jesseduffield/lazygit"}) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // Open a PR for the current branch (i.e. 'branch-2') - t.Views(). - Branches(). - Focus(). - Press(keys.Branches.ViewPullRequestOptions) - - t.ExpectPopup(). - Menu(). - Title(Equals("View create pull request options")). - Select(Contains("Select branch")). - Confirm() - - // Verify that we're prompted to enter the remote - t.ExpectPopup(). - Prompt(). - Title(Equals("Select target remote")). - SuggestionLines( - Equals("origin"), - Equals("upstream")). - ConfirmSuggestion(Equals("upstream")) - - // Verify that we're prompted to enter the target branch and that only those branches - // present in the selected remote are listed as suggestions (i.e. 'branch-2' is not there). - t.ExpectPopup(). - Prompt(). - Title(Equals("branch-2 → upstream/")). - SuggestionLines( - Equals("branch-1"), - Equals("master")). - ConfirmSuggestion(Equals("master")) - - // Verify that the expected URL is used (by checking the openlink file) - // - // Please note that when targeting a different remote - like it's done here in this test - - // the link is not yet correct. Thus, this test is expected to fail once this is fixed. - t.FileSystem().FileContent( - "/tmp/openlink", - Equals("https://github.com/my-personal-fork/lazygit/compare/master...branch-2?expand=1\n")) - }, -}) diff --git a/pkg/integration/tests/branch/open_with_cli_arg.go b/pkg/integration/tests/branch/open_with_cli_arg.go deleted file mode 100644 index 8e08125fb70..00000000000 --- a/pkg/integration/tests/branch/open_with_cli_arg.go +++ /dev/null @@ -1,18 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var OpenWithCliArg = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Open straight to branches panel using a CLI arg", - ExtraCmdArgs: []string{"branch"}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches().IsFocused() - }, -}) diff --git a/pkg/integration/tests/branch/rebase.go b/pkg/integration/tests/branch/rebase.go deleted file mode 100644 index 3c86ddffca9..00000000000 --- a/pkg/integration/tests/branch/rebase.go +++ /dev/null @@ -1,63 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var Rebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rebase onto another branch, deal with the conflicts.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shared.MergeConflictsSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().TopLines( - Contains("first change"), - Contains("original"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("first-change-branch"), - Contains("second-change-branch"), - Contains("original-branch"), - ). - SelectNextItem(). - Press(keys.Branches.RebaseBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'first-change-branch'")). - Select(Contains("Simple rebase")). - Confirm() - - t.Common().AcknowledgeConflicts() - - t.Views().Files(). - IsFocused(). - SelectedLine(Contains("file")). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Rebasing")) - - t.Common().ContinueOnConflictsResolved("rebase") - - t.Views().Information().Content(DoesNotContain("Rebasing")) - - t.Views().Commits().TopLines( - Contains("second-change-branch unrelated change"), - Contains("second change"), - Contains("original"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/rebase_abort_on_conflict.go b/pkg/integration/tests/branch/rebase_abort_on_conflict.go deleted file mode 100644 index 79ceba5105c..00000000000 --- a/pkg/integration/tests/branch/rebase_abort_on_conflict.go +++ /dev/null @@ -1,51 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var RebaseAbortOnConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rebase onto another branch, abort when there are conflicts.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shared.MergeConflictsSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().TopLines( - Contains("first change"), - Contains("original"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("first-change-branch"), - Contains("second-change-branch"), - Contains("original-branch"), - ). - SelectNextItem(). - Press(keys.Branches.RebaseBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'first-change-branch'")). - Select(Contains("Simple rebase")). - Confirm() - - t.ExpectPopup().Menu(). - Title(Equals("Conflicts!")). - Select(Contains("Abort the rebase")). - Confirm() - - t.Views().Branches(). - IsFocused() - - t.Views().Files(). - IsEmpty() - }, -}) diff --git a/pkg/integration/tests/branch/rebase_and_drop.go b/pkg/integration/tests/branch/rebase_and_drop.go deleted file mode 100644 index ff17d6417b1..00000000000 --- a/pkg/integration/tests/branch/rebase_and_drop.go +++ /dev/null @@ -1,97 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rebase onto another branch, deal with the conflicts. Also mark a commit to be dropped before continuing.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shared.MergeConflictsSetup(shell) - // adding a couple additional commits so that we can drop one - shell.EmptyCommit("to remove") - shell.EmptyCommit("to keep") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - TopLines( - Contains("to keep"), - Contains("to remove"), - Contains("first change"), - Contains("original"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("first-change-branch").IsSelected(), - Contains("second-change-branch"), - Contains("original-branch"), - ). - SelectNextItem(). - Press(keys.Branches.RebaseBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'first-change-branch'")). - Select(Contains("Simple rebase")). - Confirm() - - t.Views().Information().Content(Contains("Rebasing")) - - t.Common().AcknowledgeConflicts() - - t.Views().Files().IsFocused(). - SelectedLine(MatchesRegexp("UU.*file")) - - t.Views().Commits(). - Focus(). - TopLines( - Contains("--- Pending rebase todos ---"), - MatchesRegexp(`pick.*to keep`).IsSelected(), - MatchesRegexp(`pick.*to remove`), - MatchesRegexp(`pick.*CONFLICT.*first change`), - Contains("--- Commits ---"), - MatchesRegexp("second-change-branch unrelated change"), - MatchesRegexp("second change"), - MatchesRegexp("original"), - ). - SelectNextItem(). - Press(keys.Universal.Remove). - TopLines( - Contains("--- Pending rebase todos ---"), - MatchesRegexp(`pick.*to keep`), - MatchesRegexp(`drop.*to remove`).IsSelected(), - MatchesRegexp(`pick.*CONFLICT.*first change`), - Contains("--- Commits ---"), - MatchesRegexp("second-change-branch unrelated change"), - MatchesRegexp("second change"), - MatchesRegexp("original"), - ) - - t.Views().Files(). - Focus(). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - PressPrimaryAction() - - t.Common().ContinueOnConflictsResolved("rebase") - - t.Views().Information().Content(DoesNotContain("Rebasing")) - - t.Views().Commits().TopLines( - Contains("to keep"), - Contains("second-change-branch unrelated change").IsSelected(), - Contains("second change"), - Contains("original"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/rebase_cancel_on_conflict.go b/pkg/integration/tests/branch/rebase_cancel_on_conflict.go deleted file mode 100644 index be69f975366..00000000000 --- a/pkg/integration/tests/branch/rebase_cancel_on_conflict.go +++ /dev/null @@ -1,53 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var RebaseCancelOnConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rebase onto another branch, cancel when there are conflicts.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shared.MergeConflictsSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().TopLines( - Contains("first change"), - Contains("original"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("first-change-branch"), - Contains("second-change-branch"), - Contains("original-branch"), - ). - SelectNextItem(). - Press(keys.Branches.RebaseBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'first-change-branch'")). - Select(Contains("Simple rebase")). - Confirm() - - t.ExpectPopup().Menu(). - Title(Equals("Conflicts!")). - Select(Contains("Abort the rebase")). - Cancel() - - t.Views().Branches(). - IsFocused() - - t.Views().Files(). - Lines( - Contains("UU file"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/rebase_conflicts_fix_build_errors.go b/pkg/integration/tests/branch/rebase_conflicts_fix_build_errors.go deleted file mode 100644 index 3690f8f2e7a..00000000000 --- a/pkg/integration/tests/branch/rebase_conflicts_fix_build_errors.go +++ /dev/null @@ -1,78 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var RebaseConflictsFixBuildErrors = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rebase onto another branch, deal with the conflicts. While continue prompt is showing, fix build errors; get another prompt when continuing.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shared.MergeConflictsSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().TopLines( - Contains("first change"), - Contains("original"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("first-change-branch"), - Contains("second-change-branch"), - Contains("original-branch"), - ). - SelectNextItem(). - Press(keys.Branches.RebaseBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'first-change-branch'")). - Select(Contains("Simple rebase")). - Confirm() - - t.Common().AcknowledgeConflicts() - - t.Views().Files(). - IsFocused(). - SelectedLine(Contains("file")). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Rebasing")) - - popup := t.ExpectPopup().Confirmation(). - Title(Equals("Continue")). - Content(Contains("All merge conflicts resolved. Continue the rebase?")) - - // While the popup is showing, fix some build errors - t.Shell().UpdateFile("file", "make it compile again") - - // Continue - popup.Confirm() - - t.ExpectPopup().Confirmation(). - Title(Equals("Continue")). - Content(Contains("Files have been modified since conflicts were resolved. Auto-stage them and continue?")). - Confirm() - - t.Views().Information().Content(DoesNotContain("Rebasing")) - - t.Views().Commits().TopLines( - Contains("first change"), - Contains("second-change-branch unrelated change"), - Contains("second change"), - Contains("original"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/rebase_copied_branch.go b/pkg/integration/tests/branch/rebase_copied_branch.go deleted file mode 100644 index 0247e65d87e..00000000000 --- a/pkg/integration/tests/branch/rebase_copied_branch.go +++ /dev/null @@ -1,68 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RebaseCopiedBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Make a copy of a branch, rebase it, check that the original branch is unaffected", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("master 1"). - EmptyCommit("master 2"). - NewBranchFrom("branch1", "master^"). - EmptyCommit("branch 1"). - EmptyCommit("branch 2"). - NewBranch("branch2") - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Lines( - Contains("CI * branch 2"), - Contains("CI branch 1"), - Contains("CI master 1"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("branch2").IsSelected(), - Contains("branch1"), - Contains("master"), - ). - NavigateToLine(Contains("master")). - Press(keys.Branches.RebaseBranch). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'branch2'")). - Select(Contains("Simple rebase")). - Confirm() - }) - - t.Views().Commits().Lines( - Contains("CI branch 2"), - Contains("CI branch 1"), - Contains("CI master 2"), - Contains("CI master 1"), - ) - - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("branch1")). - PressPrimaryAction() - - t.Views().Commits().Lines( - Contains("CI branch 2"), - Contains("CI branch 1"), - Contains("CI master 1"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/rebase_does_not_autosquash.go b/pkg/integration/tests/branch/rebase_does_not_autosquash.go deleted file mode 100644 index 523682410de..00000000000 --- a/pkg/integration/tests/branch/rebase_does_not_autosquash.go +++ /dev/null @@ -1,54 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RebaseDoesNotAutosquash = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rebase a branch that has fixups onto another branch, and verify that the fixups are not squashed even if rebase.autoSquash is enabled globally.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.SetConfig("rebase.autoSquash", "true") - - shell. - EmptyCommit("base"). - NewBranch("my-branch"). - Checkout("master"). - EmptyCommit("master commit"). - Checkout("my-branch"). - EmptyCommit("branch commit"). - EmptyCommit("fixup! branch commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("fixup! branch commit"), - Contains("branch commit"), - Contains("base"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("my-branch").IsSelected(), - Contains("master"), - ). - SelectNextItem(). - Press(keys.Branches.RebaseBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'my-branch'")). - Select(Contains("Simple rebase")). - Confirm() - - t.Views().Commits().Lines( - Contains("fixup! branch commit"), - Contains("branch commit"), - Contains("master commit"), - Contains("base"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/rebase_from_marked_base.go b/pkg/integration/tests/branch/rebase_from_marked_base.go deleted file mode 100644 index 3dd60184b21..00000000000 --- a/pkg/integration/tests/branch/rebase_from_marked_base.go +++ /dev/null @@ -1,80 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RebaseFromMarkedBase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rebase onto another branch from a marked base commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shell. - NewBranch("base-branch"). - EmptyCommit("one"). - EmptyCommit("two"). - EmptyCommit("three"). - NewBranch("active-branch"). - EmptyCommit("active one"). - EmptyCommit("active two"). - EmptyCommit("active three"). - Checkout("base-branch"). - NewBranch("target-branch"). - EmptyCommit("target one"). - EmptyCommit("target two"). - Checkout("active-branch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("active three"), - Contains("active two"), - Contains("active one"), - Contains("three"), - Contains("two"), - Contains("one"), - ). - NavigateToLine(Contains("active one")). - Press(keys.Commits.MarkCommitAsBaseForRebase). - Lines( - Contains("active three").Contains("✓"), - Contains("active two").Contains("✓"), - Contains("↑↑↑ Will rebase from here ↑↑↑ active one"), - Contains("three").DoesNotContain("✓"), - Contains("two").DoesNotContain("✓"), - Contains("one").DoesNotContain("✓"), - ) - - t.Views().Information().Content(Contains("Marked a base commit for rebase")) - - t.Views().Branches(). - Focus(). - Lines( - Contains("active-branch"), - Contains("target-branch"), - Contains("base-branch"), - ). - SelectNextItem(). - Press(keys.Branches.RebaseBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'active-branch' from marked base")). - Select(Contains("Simple rebase")). - Confirm() - - t.Views().Commits().Lines( - Contains("active three").DoesNotContain("✓"), - Contains("active two").DoesNotContain("✓"), - Contains("target two").DoesNotContain("✓"), - Contains("target one").DoesNotContain("✓"), - Contains("three").DoesNotContain("✓"), - Contains("two").DoesNotContain("✓"), - Contains("one").DoesNotContain("✓"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/rebase_onto_base_branch.go b/pkg/integration/tests/branch/rebase_onto_base_branch.go deleted file mode 100644 index 8514e4f0949..00000000000 --- a/pkg/integration/tests/branch/rebase_onto_base_branch.go +++ /dev/null @@ -1,53 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RebaseOntoBaseBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rebase the current branch onto its base branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.ShowDivergenceFromBaseBranch = "arrowAndNumber" - }, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("master 1"). - EmptyCommit("master 2"). - EmptyCommit("master 3"). - NewBranchFrom("feature", "master^"). - EmptyCommit("feature 1"). - EmptyCommit("feature 2") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Lines( - Contains("feature 2"), - Contains("feature 1"), - Contains("master 2"), - Contains("master 1"), - ) - - t.Views().Branches(). - Focus(). - Lines( - MatchesRegexp(`feature\s+↓1`).IsSelected(), - Contains("master"), - ). - Press(keys.Branches.RebaseBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'feature'")). - Select(Contains("Rebase onto base branch (master)")). - Confirm() - - t.Views().Commits().Lines( - Contains("feature 2"), - Contains("feature 1"), - Contains("master 3"), - Contains("master 2"), - Contains("master 1"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/rebase_to_upstream.go b/pkg/integration/tests/branch/rebase_to_upstream.go deleted file mode 100644 index 397a79d1e43..00000000000 --- a/pkg/integration/tests/branch/rebase_to_upstream.go +++ /dev/null @@ -1,83 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RebaseToUpstream = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rebase the current branch to the selected branch upstream", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CloneIntoRemote("origin"). - EmptyCommit("ensure-master"). - EmptyCommit("to-be-added"). // <- this will only exist remotely - PushBranchAndSetUpstream("origin", "master"). - RenameCurrentBranch("master-local"). - HardReset("HEAD~1"). - NewBranchFrom("base-branch", "master-local"). - EmptyCommit("base-branch-commit"). - NewBranch("target"). - EmptyCommit("target-commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Lines( - Contains("target-commit"), - Contains("base-branch-commit"), - Contains("ensure-master"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("target").IsSelected(), - Contains("base-branch"), - Contains("master-local"), - ). - SelectNextItem(). - Lines( - Contains("target"), - Contains("base-branch").IsSelected(), - Contains("master-local"), - ). - Press(keys.Branches.SetUpstream). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Upstream options")). - Select(Contains("Rebase checked-out branch onto upstream of selected branch")). - Tooltip(Contains("Disabled: The selected branch has no upstream (or the upstream is not stored locally)")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("Disabled: The selected branch has no upstream (or the upstream is not stored locally)")) - }). - Cancel() - }). - SelectNextItem(). - Lines( - Contains("target"), - Contains("base-branch"), - Contains("master-local").IsSelected(), - ). - Press(keys.Branches.SetUpstream). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Upstream options")). - Select(Contains("Rebase checked-out branch onto origin/master...")). - Confirm() - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'target'")). - Select(Contains("Simple rebase")). - Confirm() - }) - - t.Views().Commits().Lines( - Contains("target-commit"), - Contains("base-branch-commit"), - Contains("to-be-added"), - Contains("ensure-master"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/rename.go b/pkg/integration/tests/branch/rename.go deleted file mode 100644 index ee711658f40..00000000000 --- a/pkg/integration/tests/branch/rename.go +++ /dev/null @@ -1,35 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Rename = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rename a branch, replacing spaces in the name with dashes", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("master"), - ). - Press(keys.Branches.RenameBranch). - Tap(func() { - t.ExpectPopup().Prompt(). - Title(Contains("Enter new branch name")). - InitialText(Equals("master")). - Clear(). - Type("new branch name"). - Confirm() - }). - Lines( - Contains("new-branch-name"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/reset.go b/pkg/integration/tests/branch/reset.go deleted file mode 100644 index bafc712d112..00000000000 --- a/pkg/integration/tests/branch/reset.go +++ /dev/null @@ -1,50 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Reset = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Hard reset to another branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("current-branch") - shell.EmptyCommit("root commit") - - shell.NewBranch("other-branch") - shell.EmptyCommit("other-branch commit") - - shell.Checkout("current-branch") - shell.EmptyCommit("current-branch commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Lines( - Contains("current-branch commit"), - Contains("root commit"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("current-branch").IsSelected(), - Contains("other-branch"), - ). - SelectNextItem(). - Press(keys.Commits.ViewResetOptions) - - t.ExpectPopup().Menu(). - Title(Contains("Reset to other-branch")). - Select(Contains("Hard reset")). - Confirm() - - // assert that we now have the expected commits in the commit panel - t.Views().Commits(). - Lines( - Contains("other-branch commit"), - Contains("root commit"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/reset_to_duplicate_named_tag.go b/pkg/integration/tests/branch/reset_to_duplicate_named_tag.go deleted file mode 100644 index d940d9fe112..00000000000 --- a/pkg/integration/tests/branch/reset_to_duplicate_named_tag.go +++ /dev/null @@ -1,52 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ResetToDuplicateNamedTag = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Hard reset to a branch when a tag shares the same name", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("current-branch") - - shell.EmptyCommit("other-branch-tag commit") - shell.CreateLightweightTag("other-branch", "HEAD") - - shell.EmptyCommit("other-branch commit") - shell.NewBranch("other-branch") - - shell.Checkout("current-branch") - shell.EmptyCommit("current-branch commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Lines( - Contains("current-branch commit"), - Contains("other-branch commit"), - Contains("other-branch-tag commit"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("current-branch").IsSelected(), - Contains("other-branch"), - ). - SelectNextItem(). - Press(keys.Commits.ViewResetOptions) - - t.ExpectPopup().Menu(). - Title(Contains("Reset to other-branch")). - Select(Contains("Hard reset")). - Confirm() - - t.Views().Commits(). - Lines( - Contains("other-branch commit"), - Contains("other-branch-tag commit"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/reset_to_duplicate_named_upstream.go b/pkg/integration/tests/branch/reset_to_duplicate_named_upstream.go deleted file mode 100644 index a375e7e754a..00000000000 --- a/pkg/integration/tests/branch/reset_to_duplicate_named_upstream.go +++ /dev/null @@ -1,57 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ResetToDuplicateNamedUpstream = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Hard reset the current branch to an upstream branch when there is a competing tag name", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CloneIntoRemote("origin"). - NewBranch("foo"). - EmptyCommit("commit 1"). - PushBranchAndSetUpstream("origin", "foo"). - EmptyCommit("commit 2"). - CreateLightweightTag("origin/foo", "HEAD") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Lines( - Contains("commit 2"), - Contains("commit 1"), - ) - t.Views().Tags().Focus().Lines(Contains("origin/foo")) - - t.Views().Remotes().Focus(). - Lines(Contains("origin")). - PressEnter() - t.Views().RemoteBranches().IsFocused(). - Lines(Contains("foo")). - Press(keys.Commits.ViewResetOptions) - t.ExpectPopup().Menu(). - Title(Contains("Reset to origin/foo")). - Select(Contains("Hard reset")). - Confirm() - - t.Views().Commits().Lines( - Contains("commit 1"), - ) - - t.Views().Tags().Focus(). - Lines(Contains("origin/foo")). - Press(keys.Commits.ViewResetOptions) - t.ExpectPopup().Menu(). - Title(Contains("Reset to origin/foo")). - Select(Contains("Hard reset")). - Confirm() - - t.Views().Commits().Lines( - Contains("commit 2"), - Contains("commit 1"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/reset_to_upstream.go b/pkg/integration/tests/branch/reset_to_upstream.go deleted file mode 100644 index 9a5ea498eb7..00000000000 --- a/pkg/integration/tests/branch/reset_to_upstream.go +++ /dev/null @@ -1,111 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ResetToUpstream = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Hard reset the current branch to the selected branch upstream", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shell. - CloneIntoRemote("origin"). - NewBranch("hard-branch"). - EmptyCommit("hard commit"). - PushBranchAndSetUpstream("origin", "hard-branch"). - NewBranch("soft-branch"). - EmptyCommit("soft commit"). - PushBranchAndSetUpstream("origin", "soft-branch"). - RenameCurrentBranch("soft-branch-local"). - NewBranch("base"). - EmptyCommit("base-branch commit"). - CreateFile("file-1", "content"). - GitAdd("file-1"). - Commit("commit with file"). - CreateFile("file-2", "content"). - GitAdd("file-2") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // soft reset - t.Views().Branches(). - Focus(). - Lines( - Contains("base").IsSelected(), - Contains("soft-branch-local"), - Contains("hard-branch"), - ). - Press(keys.Branches.SetUpstream). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Upstream options")). - Select(Contains("Reset checked-out branch onto upstream of selected branch")). - Tooltip(Contains("Disabled: The selected branch has no upstream (or the upstream is not stored locally)")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("Disabled: The selected branch has no upstream (or the upstream is not stored locally)")) - }). - Cancel() - }). - SelectNextItem(). - Lines( - Contains("base"), - Contains("soft-branch-local").IsSelected(), - Contains("hard-branch"), - ). - Press(keys.Branches.SetUpstream). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Upstream options")). - Select(Contains("Reset checked-out branch onto origin/soft-branch...")). - Confirm() - - t.ExpectPopup().Menu(). - Title(Equals("Reset to origin/soft-branch")). - Select(Contains("Soft reset")). - Confirm() - }) - t.Views().Commits().Lines( - Contains("soft commit"), - Contains("hard commit"), - ) - t.Views().Files().Lines( - Equals("▼ /"), - Equals(" A file-1"), - Equals(" A file-2"), - ) - - // hard reset - t.Views().Branches(). - Focus(). - Lines( - Contains("base"), - Contains("soft-branch-local").IsSelected(), - Contains("hard-branch"), - ). - NavigateToLine(Contains("hard-branch")). - Press(keys.Branches.SetUpstream). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Upstream options")). - Select(Contains("Reset checked-out branch onto origin/hard-branch...")). - Confirm() - - t.ExpectPopup().Menu(). - Title(Equals("Reset to origin/hard-branch")). - Select(Contains("Hard reset")). - Confirm() - - t.ExpectPopup().Confirmation(). - Title(Equals("Hard reset")). - Content(Contains("Are you sure you want to do a hard reset?")). - Confirm() - }) - t.Views().Commits().Lines(Contains("hard commit")) - t.Views().Files().IsEmpty() - }, -}) diff --git a/pkg/integration/tests/branch/select_commits_of_current_branch.go b/pkg/integration/tests/branch/select_commits_of_current_branch.go deleted file mode 100644 index 7b57455c359..00000000000 --- a/pkg/integration/tests/branch/select_commits_of_current_branch.go +++ /dev/null @@ -1,74 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SelectCommitsOfCurrentBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Select all commits of the current branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("master 01") - shell.EmptyCommit("master 02") - shell.NewBranch("branch1") - shell.CreateNCommits(2) - shell.NewBranchFrom("branch2", "master") - shell.CreateNCommits(3) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 03").IsSelected(), - Contains("commit 02"), - Contains("commit 01"), - Contains("master 02"), - Contains("master 01"), - ). - Press(keys.Commits.SelectCommitsOfCurrentBranch). - Lines( - Contains("commit 03").IsSelected(), - Contains("commit 02").IsSelected(), - Contains("commit 01").IsSelected(), - Contains("master 02"), - Contains("master 01"), - ). - PressEscape(). - Lines( - Contains("commit 03").IsSelected(), - Contains("commit 02"), - Contains("commit 01"), - Contains("master 02"), - Contains("master 01"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("branch2").IsSelected(), - Contains("branch1"), - Contains("master"), - ). - SelectNextItem(). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("commit 02").IsSelected(), - Contains("commit 01"), - Contains("master 02"), - Contains("master 01"), - ). - Press(keys.Commits.SelectCommitsOfCurrentBranch). - Lines( - Contains("commit 02").IsSelected(), - Contains("commit 01").IsSelected(), - Contains("master 02"), - Contains("master 01"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/set_upstream.go b/pkg/integration/tests/branch/set_upstream.go deleted file mode 100644 index 16faeb7e34a..00000000000 --- a/pkg/integration/tests/branch/set_upstream.go +++ /dev/null @@ -1,40 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SetUpstream = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Set the upstream of a branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.CloneIntoRemote("origin") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Press(keys.Universal.NextScreenMode). // we need to enlargen the window to see the upstream - Lines( - Contains("master").DoesNotContain("origin master").IsSelected(), - ). - Press(keys.Branches.SetUpstream). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Upstream options")). - Select(Contains(" Set upstream of selected branch")). // using leading space to disambiguate from the 'reset' option - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("Enter upstream as ' '")). - SuggestionLines(Equals("origin master")). - ConfirmFirstSuggestion() - }). - Lines( - Contains("master").Contains("origin master").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/branch/shared.go b/pkg/integration/tests/branch/shared.go deleted file mode 100644 index 215a3c3af36..00000000000 --- a/pkg/integration/tests/branch/shared.go +++ /dev/null @@ -1,25 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/samber/lo" -) - -func checkRemoteBranches(t *TestDriver, keys config.KeybindingConfig, remoteName string, expectedBranches []string) { - t.Views().Remotes(). - Focus(). - NavigateToLine(Contains(remoteName)). - PressEnter() - - t.Views(). - RemoteBranches(). - Lines( - lo.Map(expectedBranches, func(branch string, _ int) *TextMatcher { return Equals(branch) })..., - ). - Press(keys.Universal.Return) - - t.Views(). - Branches(). - Focus() -} diff --git a/pkg/integration/tests/branch/show_divergence_from_base_branch.go b/pkg/integration/tests/branch/show_divergence_from_base_branch.go deleted file mode 100644 index 2903b78379c..00000000000 --- a/pkg/integration/tests/branch/show_divergence_from_base_branch.go +++ /dev/null @@ -1,47 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ShowDivergenceFromBaseBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Show divergence from base branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.ShowDivergenceFromBaseBranch = "arrowAndNumber" - }, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("master 1"). - EmptyCommit("master 2"). - EmptyCommit("master 3"). - NewBranchFrom("feature", "master^"). - EmptyCommit("feature 1"). - EmptyCommit("feature 2") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - MatchesRegexp(`feature\s+↓1`).IsSelected(), - Contains("master"), - ). - Press(keys.Branches.SetUpstream) - - t.ExpectPopup().Menu().Title(Contains("Upstream")). - Select(Contains("View divergence from base branch (master)")).Confirm() - - t.Views().SubCommits(). - IsFocused(). - Title(Contains("Commits (feature <-> master)")). - Lines( - DoesNotContainAnyOf("↓", "↑").Contains("--- Remote ---"), - Contains("↓").Contains("master 3"), - DoesNotContainAnyOf("↓", "↑").Contains("--- Local ---"), - Contains("↑").Contains("feature 2"), - Contains("↑").Contains("feature 1"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/show_divergence_from_upstream.go b/pkg/integration/tests/branch/show_divergence_from_upstream.go deleted file mode 100644 index 8aff21ca9ae..00000000000 --- a/pkg/integration/tests/branch/show_divergence_from_upstream.go +++ /dev/null @@ -1,54 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ShowDivergenceFromUpstream = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Show divergence from upstream", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file", "content1") - shell.Commit("one") - shell.UpdateFileAndAdd("file", "content2") - shell.Commit("two") - shell.CreateFileAndAdd("file3", "content3") - shell.Commit("three") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - - shell.HardReset("HEAD^^") - shell.CreateFileAndAdd("file4", "content4") - shell.Commit("four") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("four"), - Contains("one"), - ) - - t.Views().Branches(). - Focus(). - Lines(Contains("master")). - Press(keys.Branches.SetUpstream) - - t.ExpectPopup().Menu().Title(Contains("Upstream")).Select(Contains("View divergence from upstream")).Confirm() - - t.Views().SubCommits(). - IsFocused(). - Title(Contains("Commits (master <-> origin/master)")). - Lines( - DoesNotContainAnyOf("↓", "↑").Contains("--- Remote ---"), - Contains("↓").Contains("three"), - Contains("↓").Contains("two"), - DoesNotContainAnyOf("↓", "↑").Contains("--- Local ---"), - Contains("↑").Contains("four"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/show_divergence_from_upstream_no_divergence.go b/pkg/integration/tests/branch/show_divergence_from_upstream_no_divergence.go deleted file mode 100644 index 5b446fcb83b..00000000000 --- a/pkg/integration/tests/branch/show_divergence_from_upstream_no_divergence.go +++ /dev/null @@ -1,34 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ShowDivergenceFromUpstreamNoDivergence = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Show divergence from upstream when the divergence view is empty", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("commit1") - shell.CloneIntoRemote("origin") - shell.SetBranchUpstream("master", "origin/master") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines(Contains("master")). - Press(keys.Branches.SetUpstream) - - t.ExpectPopup().Menu().Title(Contains("Upstream")).Select(Contains("View divergence from upstream")).Confirm() - - t.Views().SubCommits(). - IsFocused(). - Title(Contains("Commits (master <-> origin/master)")). - Lines( - Contains("--- Remote ---"), - Contains("--- Local ---"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/sort_local_branches.go b/pkg/integration/tests/branch/sort_local_branches.go deleted file mode 100644 index fbc5776ba8b..00000000000 --- a/pkg/integration/tests/branch/sort_local_branches.go +++ /dev/null @@ -1,80 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SortLocalBranches = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Sort local branches by recency, date or alphabetically", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("commit"). - NewBranch("first"). - EmptyCommitWithDate("commit", "2023-04-07 10:00:00"). - NewBranch("second"). - EmptyCommitWithDate("commit", "2023-04-07 12:00:00"). - NewBranch("third"). - EmptyCommitWithDate("commit", "2023-04-07 11:00:00"). - Checkout("master") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // sorted by date by default - t.Views().Branches(). - Focus(). - Lines( - Contains("master").IsSelected(), - Contains("second"), - Contains("third"), - Contains("first"), - ). - SelectNextItem() // to test that the selection jumps back to the top when sorting - - t.Views().Branches(). - Press(keys.Branches.SortOrder) - - t.ExpectPopup().Menu().Title(Equals("Sort order")). - ContainsLines( - Contains("r ( ) Recency").IsSelected(), - Contains("a ( ) Alphabetical"), - Contains("d (•) Date"), - Contains(" Cancel"), - ). - Select(Contains("Recency")). - Confirm() - - t.Views().Branches(). - IsFocused(). - Lines( - Contains("master").IsSelected(), - Contains("third"), - Contains("second"), - Contains("first"), - ) - - t.Views().Branches(). - Press(keys.Branches.SortOrder) - - t.ExpectPopup().Menu().Title(Equals("Sort order")). - ContainsLines( - Contains("r (•) Recency").IsSelected(), - Contains("a ( ) Alphabetical"), - Contains("d ( ) Date"), - Contains(" Cancel"), - ). - Select(Contains("refname")). - Confirm() - - t.Views().Branches(). - IsFocused(). - Lines( - Contains("master").IsSelected(), - Contains("first"), - Contains("second"), - Contains("third"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/sort_remote_branches.go b/pkg/integration/tests/branch/sort_remote_branches.go deleted file mode 100644 index 7cf78cf15d9..00000000000 --- a/pkg/integration/tests/branch/sort_remote_branches.go +++ /dev/null @@ -1,60 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SortRemoteBranches = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Sort remote branches alphabetically or by date", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("first") - shell.EmptyCommitWithDate("commit", "2023-04-07 10:00:00") - shell.NewBranch("second") - shell.EmptyCommitWithDate("commit", "2023-04-07 12:00:00") - shell.NewBranch("third") - shell.EmptyCommitWithDate("commit", "2023-04-07 11:00:00") - shell.CloneIntoRemote("origin") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Remotes(). - Focus(). - Lines( - Contains("origin").IsSelected(), - ). - PressEnter() - - // sorted by date by default - t.Views().RemoteBranches(). - IsFocused(). - Lines( - Contains("second").IsSelected(), - Contains("third"), - Contains("first"), - ). - SelectNextItem() // to test that the selection jumps back to the first when sorting - - t.Views().RemoteBranches(). - Press(keys.Branches.SortOrder) - - t.ExpectPopup().Menu().Title(Equals("Sort order")). - ContainsLines( - Contains("a ( ) Alphabetical").IsSelected(), - Contains("d (•) Date"), - Contains(" Cancel"), - ). - Select(Contains("Alphabetical")). - Confirm() - - t.Views().RemoteBranches(). - IsFocused(). - Lines( - Contains("first").IsSelected(), - Contains("second"), - Contains("third"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/squash_merge.go b/pkg/integration/tests/branch/squash_merge.go deleted file mode 100644 index 509073cbe73..00000000000 --- a/pkg/integration/tests/branch/squash_merge.go +++ /dev/null @@ -1,64 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SquashMerge = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Squash merge a branch both with and without committing", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("original-branch"). - EmptyCommit("one"). - NewBranch("change-worktree-branch"). - CreateFileAndAdd("work", "content"). - Commit("work"). - Checkout("original-branch"). - NewBranch("change-commit-branch"). - CreateFileAndAdd("file", "content"). - Commit("file"). - Checkout("original-branch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().TopLines( - Contains("one"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("original-branch").IsSelected(), - Contains("change-commit-branch"), - Contains("change-worktree-branch"), - ). - SelectNextItem(). - Press(keys.Branches.MergeIntoCurrentBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Merge")). - Select(Contains("Squash merge and commit")). - Confirm() - - t.Views().Commits().TopLines( - Contains("Squash merge change-commit-branch into original-branch"), - Contains("one"), - ) - - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("change-worktree-branch")). - Press(keys.Branches.MergeIntoCurrentBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Merge")). - Select(Contains("Squash merge and leave uncommitted")). - Confirm() - - t.Views().Files().Focus().Lines( - Contains("work"), - ) - }, -}) diff --git a/pkg/integration/tests/branch/suggestions.go b/pkg/integration/tests/branch/suggestions.go deleted file mode 100644 index e6215d13f44..00000000000 --- a/pkg/integration/tests/branch/suggestions.go +++ /dev/null @@ -1,38 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Suggestions = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Checking out a branch with name suggestions", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("my commit message"). - NewBranch("new-branch"). - NewBranch("new-branch-2"). - NewBranch("new-branch-3"). - NewBranch("branch-to-checkout"). - NewBranch("other-new-branch-2"). - NewBranch("other-new-branch-3") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Press(keys.Branches.CheckoutBranchByName) - - // we expect the first suggestion to be the branch we want because it most - // closely matches what we typed in - t.ExpectPopup().Prompt(). - Title(Equals("Branch name:")). - Type("branch-to"). - SuggestionTopLines(Contains("branch-to-checkout")). - ConfirmFirstSuggestion() - - t.Git().CurrentBranchName("branch-to-checkout") - }, -}) diff --git a/pkg/integration/tests/branch/unset_upstream.go b/pkg/integration/tests/branch/unset_upstream.go deleted file mode 100644 index 39454d2acc9..00000000000 --- a/pkg/integration/tests/branch/unset_upstream.go +++ /dev/null @@ -1,59 +0,0 @@ -package branch - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var UnsetUpstream = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Unset upstream of selected branch, both when it exists and when it doesn't", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("one"). - NewBranch("branch_to_remove"). - Checkout("master"). - CloneIntoRemote("origin"). - SetBranchUpstream("master", "origin/master"). - SetBranchUpstream("branch_to_remove", "origin/branch_to_remove"). - // to get the "(upstream gone)" branch status - RunCommand([]string{"git", "push", "origin", "--delete", "branch_to_remove"}) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Press(keys.Universal.NextScreenMode). // we need to enlargen the window to see the upstream - SelectedLines( - Contains("master").Contains("origin master"), - ). - Press(keys.Branches.SetUpstream). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Upstream options")). - Select(Contains("Unset upstream of selected branch")). - Confirm() - }). - SelectedLines( - Contains("master").DoesNotContain("origin master"), - ) - - t.Views().Branches(). - Focus(). - SelectNextItem(). - SelectedLines( - Contains("branch_to_remove").Contains("origin branch_to_remove").Contains("upstream gone"), - ). - Press(keys.Branches.SetUpstream). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Upstream options")). - Select(Contains("Unset upstream of selected branch")). - Confirm() - }). - SelectedLines( - Contains("branch_to_remove").DoesNotContain("origin branch_to_remove").DoesNotContain("upstream gone"), - ) - }, -}) diff --git a/pkg/integration/tests/cherry_pick/cherry_pick.go b/pkg/integration/tests/cherry_pick/cherry_pick.go deleted file mode 100644 index a278a5639bd..00000000000 --- a/pkg/integration/tests/cherry_pick/cherry_pick.go +++ /dev/null @@ -1,112 +0,0 @@ -package cherry_pick - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cherry pick commits from the subcommits view, without conflicts", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("base"). - NewBranch("first-branch"). - NewBranch("second-branch"). - Checkout("first-branch"). - EmptyCommit("one"). - EmptyCommit("two"). - Checkout("second-branch"). - EmptyCommit("three"). - EmptyCommit("four"). - Checkout("first-branch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("first-branch"), - Contains("second-branch"), - Contains("master"), - ). - SelectNextItem(). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("base"), - ). - // copy commits 'four' and 'three' - Press(keys.Commits.CherryPickCopy). - Tap(func() { - t.Views().Information().Content(Contains("1 commit copied")) - }). - SelectNextItem(). - Press(keys.Commits.CherryPickCopy) - - t.Views().Information().Content(Contains("2 commits copied")) - - t.Views().Commits(). - Focus(). - Lines( - Contains("two").IsSelected(), - Contains("one"), - Contains("base"), - ). - Press(keys.Commits.PasteCommits). - Tap(func() { - // cherry-picked commits will be deleted after confirmation - t.Views().Information().Content(Contains("2 commits copied")) - }). - Tap(func() { - t.ExpectPopup().Alert(). - Title(Equals("Cherry-pick")). - Content(Contains("Are you sure you want to cherry-pick the 2 copied commit(s) onto this branch?")). - Confirm() - }). - Tap(func() { - t.Views().Information().Content(DoesNotContain("commits copied")) - }). - Lines( - Contains("four"), - Contains("three"), - Contains("two").IsSelected(), - Contains("one"), - Contains("base"), - ) - - // Even though the cherry-picking mode has been reset, it's still possible to paste the copied commits again: - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("master")). - PressPrimaryAction() - - t.Views().Commits(). - Focus(). - Lines( - Contains("base").IsSelected(), - ). - Press(keys.Commits.PasteCommits). - Tap(func() { - t.ExpectPopup().Alert(). - Title(Equals("Cherry-pick")). - Content(Contains("Are you sure you want to cherry-pick the 2 copied commit(s) onto this branch?")). - Confirm() - }). - Tap(func() { - t.Views().Information().Content(DoesNotContain("commits copied")) - }). - Lines( - Contains("four"), - Contains("three"), - Contains("base").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/cherry_pick/cherry_pick_commit_that_becomes_empty.go b/pkg/integration/tests/cherry_pick/cherry_pick_commit_that_becomes_empty.go deleted file mode 100644 index fbd8ee9a659..00000000000 --- a/pkg/integration/tests/cherry_pick/cherry_pick_commit_that_becomes_empty.go +++ /dev/null @@ -1,99 +0,0 @@ -package cherry_pick - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CherryPickCommitThatBecomesEmpty = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cherry-pick a commit that becomes empty at the destination", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("base"). - CreateFileAndAdd("file1", "change 1\n"). - CreateFileAndAdd("file2", "change 2\n"). - Commit("two changes in one commit"). - NewBranchFrom("branch", "HEAD^"). - CreateFileAndAdd("file1", "change 1\n"). - Commit("single change"). - CreateFileAndAdd("file3", "change 3\n"). - Commit("unrelated change"). - Checkout("master") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("master").IsSelected(), - Contains("branch"), - ). - SelectNextItem(). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("unrelated change").IsSelected(), - Contains("single change"), - Contains("base"), - ). - Press(keys.Universal.RangeSelectDown). - Press(keys.Commits.CherryPickCopy). - Tap(func() { - t.Views().Information().Content(Contains("2 commits copied")) - }) - - t.Views().Commits(). - Focus(). - Lines( - Contains("two changes in one commit").IsSelected(), - Contains("base"), - ). - Press(keys.Commits.PasteCommits). - Tap(func() { - t.ExpectPopup().Alert(). - Title(Equals("Cherry-pick")). - Content(Contains("Are you sure you want to cherry-pick the 2 copied commit(s) onto this branch?")). - Confirm() - }) - - if t.Git().Version().IsAtLeast(2, 45, 0) { - t.Views().Commits(). - Lines( - Contains("unrelated change"), - Contains("single change"), - Contains("two changes in one commit").IsSelected(), - Contains("base"), - ). - SelectPreviousItem() - - // Cherry-picked commit is empty - t.Views().Main().Content(DoesNotContain("diff --git")) - } else { - t.Views().Commits(). - // We have a bug with how the selection is updated in this case; normally you would - // expect the "two changes in one commit" commit to be selected because it was - // selected before pasting, and we try to maintain that selection. This is broken - // for two reasons: - // 1. We increment the selected line index after pasting by the number of pasted - // commits; this is wrong because we skipped the commit that became empty. So - // according to this bug, the "base" commit should be selected. - // 2. We only update the selected line index after pasting if the currently selected - // commit is not a rebase TODO commit, on the assumption that if it is, we are in a - // rebase and the cherry-picked commits end up below the selection. In this case, - // however, we still think we are cherry-picking because the final refresh after the - // CheckMergeOrRebase in CherryPickHelper.Paste is async and hasn't completed yet; - // so the "unrelated change" still has a "pick" action. - // - // Since this only happens for older git versions, we don't bother fixing it. - Lines( - Contains("unrelated change").IsSelected(), - Contains("two changes in one commit"), - Contains("base"), - ) - } - }, -}) diff --git a/pkg/integration/tests/cherry_pick/cherry_pick_conflicts.go b/pkg/integration/tests/cherry_pick/cherry_pick_conflicts.go deleted file mode 100644 index b135bfd7ffa..00000000000 --- a/pkg/integration/tests/cherry_pick/cherry_pick_conflicts.go +++ /dev/null @@ -1,101 +0,0 @@ -package cherry_pick - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var CherryPickConflicts = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cherry pick commits from the subcommits view, with conflicts", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shared.MergeConflictsSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("first-change-branch"), - Contains("second-change-branch"), - Contains("original-branch"), - ). - SelectNextItem(). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - TopLines( - Contains("second-change-branch unrelated change"), - Contains("second change"), - ). - Press(keys.Commits.CherryPickCopy). - Tap(func() { - t.Views().Information().Content(Contains("1 commit copied")) - }). - SelectNextItem(). - Press(keys.Commits.CherryPickCopy) - - t.Views().Information().Content(Contains("2 commits copied")) - - t.Views().Commits(). - Focus(). - TopLines( - Contains("first change").IsSelected(), - ). - Press(keys.Commits.PasteCommits) - - t.ExpectPopup().Alert(). - Title(Equals("Cherry-pick")). - Content(Contains("Are you sure you want to cherry-pick the 2 copied commit(s) onto this branch?")). - Confirm() - - t.Common().AcknowledgeConflicts() - - // cherry pick selection is not cleared when there are conflicts, so that the user - // is able to abort and try again without having to re-copy the commits - t.Views().Information().Content(Contains("2 commits copied")) - - t.Views().Files(). - IsFocused(). - SelectedLine(Contains("file")). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - // picking 'Second change' - SelectNextItem(). - PressPrimaryAction() - - t.Common().ContinueOnConflictsResolved("cherry-pick") - - t.Views().Files().IsEmpty() - - t.Views().Commits(). - Focus(). - TopLines( - Contains("second-change-branch unrelated change").IsSelected(), - Contains("second change"), - Contains("first change"), - ). - SelectNextItem(). - Tap(func() { - // because we picked 'Second change' when resolving the conflict, - // we now see this commit as having replaced First Change with Second Change, - // as opposed to replacing 'Original' with 'Second change' - t.Views().Main(). - Content(Contains("-First Change")). - Content(Contains("+Second Change")) - - t.Views().Information().Content(Contains("2 commits copied")) - }). - PressEscape(). - Tap(func() { - t.Views().Information().Content(DoesNotContain("commits copied")) - }) - }, -}) diff --git a/pkg/integration/tests/cherry_pick/cherry_pick_conflicts_empty_commit_after_resolving.go b/pkg/integration/tests/cherry_pick/cherry_pick_conflicts_empty_commit_after_resolving.go deleted file mode 100644 index ff9efda3cd4..00000000000 --- a/pkg/integration/tests/cherry_pick/cherry_pick_conflicts_empty_commit_after_resolving.go +++ /dev/null @@ -1,92 +0,0 @@ -package cherry_pick - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var CherryPickConflictsEmptyCommitAfterResolving = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cherry pick commits with conflicts, resolve them so that the commit becomes empty", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shared.MergeConflictsSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("first-change-branch"), - Contains("second-change-branch"), - Contains("original-branch"), - ). - SelectNextItem(). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - TopLines( - Contains("second-change-branch unrelated change"), - Contains("second change"), - ). - Press(keys.Universal.RangeSelectDown). - Press(keys.Commits.CherryPickCopy) - - t.Views().Information().Content(Contains("2 commits copied")) - - t.Views().Commits(). - Focus(). - TopLines( - Contains("first change").IsSelected(), - ). - Press(keys.Commits.PasteCommits) - - t.ExpectPopup().Alert(). - Title(Equals("Cherry-pick")). - Content(Contains("Are you sure you want to cherry-pick the 2 copied commit(s) onto this branch?")). - Confirm() - - t.Common().AcknowledgeConflicts() - - t.Views().Files(). - IsFocused(). - SelectedLine(Contains("file")). - Press(keys.Universal.Remove) - - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - - t.Common().ContinueOnConflictsResolved("cherry-pick") - - t.Views().Files().IsEmpty() - - t.Views().Commits(). - Focus(). - TopLines( - // We have a bug with how the selection is updated in this case; normally you would - // expect the "first change" commit to be selected because it was selected before - // pasting, and we try to maintain that selection. This is broken for two reasons: - // 1. We increment the selected line index after pasting by the number of pasted - // commits; this is wrong because we skipped the commit that became empty. So - // according to this bug, the "original" commit should be selected. - // 2. We only update the selected line index after pasting if the currently selected - // commit is not a rebase TODO commit, on the assumption that if it is, we are in a - // rebase and the cherry-picked commits end up below the selection. In this case, - // however, we still think we are cherry-picking because the final refresh after the - // CheckMergeOrRebase in CherryPickHelper.Paste is async and hasn't completed yet; - // so the "second-change-branch unrelated change" still has a "pick" action. - // - // We don't bother fixing it for now because it's a pretty niche case, and the - // nature of the problem is only cosmetic. - Contains("second-change-branch unrelated change").IsSelected(), - Contains("first change"), - Contains("original"), - ) - }, -}) diff --git a/pkg/integration/tests/cherry_pick/cherry_pick_during_rebase.go b/pkg/integration/tests/cherry_pick/cherry_pick_during_rebase.go deleted file mode 100644 index a14dbe7c9b5..00000000000 --- a/pkg/integration/tests/cherry_pick/cherry_pick_during_rebase.go +++ /dev/null @@ -1,97 +0,0 @@ -package cherry_pick - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CherryPickDuringRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cherry pick commits from the subcommits view during a rebase", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.Log.ShowGraph = "never" - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("base"). - NewBranch("first-branch"). - NewBranch("second-branch"). - Checkout("first-branch"). - EmptyCommit("one"). - EmptyCommit("two"). - Checkout("second-branch"). - EmptyCommit("three"). - EmptyCommit("four"). - Checkout("first-branch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("first-branch"), - Contains("second-branch"), - Contains("master"), - ). - SelectNextItem(). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("base"), - ). - // copy commit 'three' - SelectNextItem(). - Press(keys.Commits.CherryPickCopy) - - t.Views().Information().Content(Contains("1 commit copied")) - - t.Views().Commits(). - Focus(). - Lines( - Contains("CI two").IsSelected(), - Contains("CI one"), - Contains("CI base"), - ). - SelectNextItem(). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick CI two"), - Contains("--- Commits ---"), - Contains(" CI one").IsSelected(), - Contains(" CI base"), - ). - Press(keys.Commits.PasteCommits). - Tap(func() { - t.ExpectPopup().Alert(). - Title(Equals("Cherry-pick")). - Content(Contains("Are you sure you want to cherry-pick the 1 copied commit(s) onto this branch?")). - Confirm() - }). - Tap(func() { - t.Views().Information().Content(DoesNotContain("commit copied")) - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick CI two"), - Contains("--- Commits ---"), - Contains(" CI three"), - Contains(" CI one").IsSelected(), - Contains(" CI base"), - ). - Tap(func() { - t.Common().ContinueRebase() - }). - Lines( - Contains("CI two"), - Contains("CI three"), - Contains("CI one").IsSelected(), - Contains("CI base"), - ) - }, -}) diff --git a/pkg/integration/tests/cherry_pick/cherry_pick_merge.go b/pkg/integration/tests/cherry_pick/cherry_pick_merge.go deleted file mode 100644 index 6c883cfa4da..00000000000 --- a/pkg/integration/tests/cherry_pick/cherry_pick_merge.go +++ /dev/null @@ -1,81 +0,0 @@ -package cherry_pick - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CherryPickMerge = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cherry pick a merge commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("base"). - NewBranch("first-branch"). - NewBranch("second-branch"). - CreateFileAndAdd("file1.txt", "content"). - Commit("one"). - CreateFileAndAdd("file2.txt", "content"). - Commit("two"). - Checkout("master"). - Merge("second-branch"). - Checkout("first-branch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("first-branch"), - Contains("master"), - Contains("second-branch"), - ). - SelectNextItem(). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("⏣─╮ Merge branch 'second-branch'").IsSelected(), - Contains("│ ◯ two"), - Contains("│ ◯ one"), - Contains("◯ ╯ base"), - ). - // copy the merge commit - Press(keys.Commits.CherryPickCopy) - - t.Views().Information().Content(Contains("1 commit copied")) - - t.Views().Commits(). - Focus(). - Lines( - Contains("base").IsSelected(), - ). - Press(keys.Commits.PasteCommits). - Tap(func() { - t.ExpectPopup().Alert(). - Title(Equals("Cherry-pick")). - Content(Contains("Are you sure you want to cherry-pick the 1 copied commit(s) onto this branch?")). - Confirm() - }). - Tap(func() { - t.Views().Information().Content(DoesNotContain("commit copied")) - }). - Lines( - Contains("Merge branch 'second-branch'"), - Contains("base").IsSelected(), - ). - SelectPreviousItem() - - t.Views().Main().ContainsLines( - Contains("Merge branch 'second-branch'"), - Contains("---"), - Contains("file1.txt | 1 +"), - Contains("file2.txt | 1 +"), - Contains("2 files changed, 2 insertions(+)"), - ) - }, -}) diff --git a/pkg/integration/tests/cherry_pick/cherry_pick_range.go b/pkg/integration/tests/cherry_pick/cherry_pick_range.go deleted file mode 100644 index dfebeae2ed8..00000000000 --- a/pkg/integration/tests/cherry_pick/cherry_pick_range.go +++ /dev/null @@ -1,82 +0,0 @@ -package cherry_pick - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CherryPickRange = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cherry pick range of commits from the subcommits view, without conflicts", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("base"). - NewBranch("first-branch"). - NewBranch("second-branch"). - Checkout("first-branch"). - EmptyCommit("one"). - EmptyCommit("two"). - Checkout("second-branch"). - EmptyCommit("three"). - EmptyCommit("four"). - Checkout("first-branch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("first-branch"), - Contains("second-branch"), - Contains("master"), - ). - SelectNextItem(). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("base"), - ). - // copy commits 'four' and 'three' - Press(keys.Universal.RangeSelectDown). - Lines( - Contains("four").IsSelected(), - Contains("three").IsSelected(), - Contains("base"), - ). - Press(keys.Commits.CherryPickCopy) - - t.Views().Information().Content(Contains("2 commits copied")) - - t.Views().Commits(). - Focus(). - Lines( - Contains("two").IsSelected(), - Contains("one"), - Contains("base"), - ). - Press(keys.Commits.PasteCommits). - Tap(func() { - t.ExpectPopup().Alert(). - Title(Equals("Cherry-pick")). - Content(Contains("Are you sure you want to cherry-pick the 2 copied commit(s) onto this branch?")). - Confirm() - }). - Tap(func() { - t.Views().Information().Content(DoesNotContain("commits copied")) - }). - Lines( - Contains("four"), - Contains("three"), - Contains("two").IsSelected(), - Contains("one"), - Contains("base"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/add_co_author.go b/pkg/integration/tests/commit/add_co_author.go deleted file mode 100644 index f4c8d2c52eb..00000000000 --- a/pkg/integration/tests/commit/add_co_author.go +++ /dev/null @@ -1,41 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AddCoAuthor = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Add co-author on a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("initial commit").IsSelected(), - ). - Press(keys.Commits.ResetCommitAuthor). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Amend commit attribute")). - Select(Contains("Add co-author")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Contains("Add co-author")). - Type("John Smith "). - Confirm() - }) - - t.Views().Main().ContainsLines( - Equals(" initial commit"), - Equals(" "), - Equals(" Co-authored-by: John Smith "), - ) - }, -}) diff --git a/pkg/integration/tests/commit/add_co_author_range.go b/pkg/integration/tests/commit/add_co_author_range.go deleted file mode 100644 index 9452c1ded9e..00000000000 --- a/pkg/integration/tests/commit/add_co_author_range.go +++ /dev/null @@ -1,105 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AddCoAuthorRange = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Add co-author on a range of commits", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("fourth commit") - shell.EmptyCommit("third commit") - shell.EmptyCommit("second commit") - shell.EmptyCommit("first commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("first commit").IsSelected(), - Contains("second commit"), - Contains("third commit"), - Contains("fourth commit"), - ). - SelectNextItem(). - Press(keys.Universal.ToggleRangeSelect). - SelectNextItem(). - Lines( - Contains("first commit"), - Contains("second commit").IsSelected(), - Contains("third commit").IsSelected(), - Contains("fourth commit"), - ). - Press(keys.Commits.ResetCommitAuthor). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Amend commit attribute")). - Select(Contains("Add co-author")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Contains("Add co-author")). - Type("John Smith "). - Confirm() - }). - // exit range selection mode - PressEscape(). - SelectNextItem() - - t.Views().Main().Content( - Contains("fourth commit"). - DoesNotContain("Co-authored-by: John Smith "), - ) - - t.Views().Commits(). - IsFocused(). - SelectPreviousItem(). - Lines( - Contains("first commit"), - Contains("second commit"), - Contains("third commit").IsSelected(), - Contains("fourth commit"), - ) - - t.Views().Main().ContainsLines( - Equals(" third commit"), - Equals(" "), - Equals(" Co-authored-by: John Smith "), - ) - - t.Views().Commits(). - IsFocused(). - SelectPreviousItem(). - Lines( - Contains("first commit"), - Contains("second commit").IsSelected(), - Contains("third commit"), - Contains("fourth commit"), - ) - - t.Views().Main().ContainsLines( - Equals(" second commit"), - Equals(" "), - Equals(" Co-authored-by: John Smith "), - ) - - t.Views().Commits(). - IsFocused(). - SelectPreviousItem(). - Lines( - Contains("first commit").IsSelected(), - Contains("second commit"), - Contains("third commit"), - Contains("fourth commit"), - ) - - t.Views().Main().Content( - Contains("first commit"). - DoesNotContain("Co-authored-by: John Smith "), - ) - }, -}) diff --git a/pkg/integration/tests/commit/add_co_author_while_committing.go b/pkg/integration/tests/commit/add_co_author_while_committing.go deleted file mode 100644 index d817f00f079..00000000000 --- a/pkg/integration/tests/commit/add_co_author_while_committing.go +++ /dev/null @@ -1,51 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AddCoAuthorWhileCommitting = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Add co-author while typing the commit message", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFile("file", "file content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). // stage file - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Type("Subject"). - SwitchToDescription(). - Type("Here's my message."). - AddCoAuthor("John Doe "). - Content(Equals("Here's my message.\n\nCo-authored-by: John Doe ")). - AddCoAuthor("Jane Smith "). - // Second co-author doesn't add a blank line: - Content(Equals("Here's my message.\n\nCo-authored-by: John Doe \nCo-authored-by: Jane Smith ")). - SwitchToSummary(). - Confirm() - - t.Views().Commits(). - Lines( - Contains("Subject"), - ). - Focus(). - Tap(func() { - t.Views().Main().ContainsLines( - Equals(" Subject"), - Equals(" "), - Equals(" Here's my message."), - Equals(" "), - Equals(" Co-authored-by: John Doe "), - Equals(" Co-authored-by: Jane Smith "), - ) - }) - }, -}) diff --git a/pkg/integration/tests/commit/amend.go b/pkg/integration/tests/commit/amend.go deleted file mode 100644 index a67705fd440..00000000000 --- a/pkg/integration/tests/commit/amend.go +++ /dev/null @@ -1,41 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Amend = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Amends the last commit from the files panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", "myfile content\n") - shell.Commit("first commit") - shell.UpdateFileAndAdd("myfile", "myfile content\nmore content\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("first commit"), - ) - - t.Views().Files(). - Focus(). - Press(keys.Commits.AmendToCommit) - - t.ExpectPopup().Confirmation().Title( - Equals("Amend last commit")). - Content(Contains("Are you sure you want to amend last commit?")). - Confirm() - - t.Views().Commits(). - Focus(). - Lines( - Contains("first commit"), - ) - - t.Views().Main().Content(Contains("+myfile content").Contains("+more content")) - }, -}) diff --git a/pkg/integration/tests/commit/amend_when_there_are_conflicts_and_amend.go b/pkg/integration/tests/commit/amend_when_there_are_conflicts_and_amend.go deleted file mode 100644 index acc2f389c14..00000000000 --- a/pkg/integration/tests/commit/amend_when_there_are_conflicts_and_amend.go +++ /dev/null @@ -1,43 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AmendWhenThereAreConflictsAndAmend = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Amends the last commit from the files panel while a rebase is stopped due to conflicts, and amends the commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - setupForAmendTests(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - doTheRebaseForAmendTests(t, keys) - - t.Views().Files(). - Press(keys.Commits.AmendToCommit) - - t.ExpectPopup().Menu(). - Title(Equals("Amend commit")). - Select(Equals("Yes, amend previous commit")). - Confirm() - - t.Views().Files().IsEmpty() - - t.Views().Commits(). - Focus(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit three"), - Contains("pick").Contains("<-- CONFLICT --- file1 changed in branch"), - Contains("--- Commits ---"), - Contains("commit two"), - Contains("file1 changed in master"), - Contains("base commit"), - ) - - checkCommitContainsChange(t, "commit two", "+branch") - }, -}) diff --git a/pkg/integration/tests/commit/amend_when_there_are_conflicts_and_cancel.go b/pkg/integration/tests/commit/amend_when_there_are_conflicts_and_cancel.go deleted file mode 100644 index f7f5ec2e1a7..00000000000 --- a/pkg/integration/tests/commit/amend_when_there_are_conflicts_and_cancel.go +++ /dev/null @@ -1,45 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AmendWhenThereAreConflictsAndCancel = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Amends the last commit from the files panel while a rebase is stopped due to conflicts, and cancels the confirmation", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - setupForAmendTests(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - doTheRebaseForAmendTests(t, keys) - - t.Views().Files(). - Press(keys.Commits.AmendToCommit) - - t.ExpectPopup().Menu(). - Title(Equals("Amend commit")). - Select(Equals("Cancel")). - Confirm() - - // Check that nothing happened: - t.Views().Files(). - Lines( - Contains("M file1"), - ) - - t.Views().Commits(). - Focus(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit three"), - Contains("pick").Contains("<-- CONFLICT --- file1 changed in branch"), - Contains("--- Commits ---"), - Contains("commit two"), - Contains("file1 changed in master"), - Contains("base commit"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/amend_when_there_are_conflicts_and_continue.go b/pkg/integration/tests/commit/amend_when_there_are_conflicts_and_continue.go deleted file mode 100644 index 8f679ba6d91..00000000000 --- a/pkg/integration/tests/commit/amend_when_there_are_conflicts_and_continue.go +++ /dev/null @@ -1,41 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AmendWhenThereAreConflictsAndContinue = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Amends the last commit from the files panel while a rebase is stopped due to conflicts, and continues the rebase", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - setupForAmendTests(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - doTheRebaseForAmendTests(t, keys) - - t.Views().Files(). - Press(keys.Commits.AmendToCommit) - - t.ExpectPopup().Menu(). - Title(Equals("Amend commit")). - Select(Equals("No, continue rebase")). - Confirm() - - t.Views().Files().IsEmpty() - - t.Views().Commits(). - Focus(). - Lines( - Contains("commit three"), - Contains("file1 changed in branch"), - Contains("commit two"), - Contains("file1 changed in master"), - Contains("base commit"), - ) - - checkCommitContainsChange(t, "file1 changed in branch", "+branch") - }, -}) diff --git a/pkg/integration/tests/commit/auto_wrap_message.go b/pkg/integration/tests/commit/auto_wrap_message.go deleted file mode 100644 index f65c182266a..00000000000 --- a/pkg/integration/tests/commit/auto_wrap_message.go +++ /dev/null @@ -1,59 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AutoWrapMessage = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Commit, and test how the commit message body is auto-wrapped", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - // Use a ridiculously small width so that we don't have to use so much test data - config.GetUserConfig().Git.Commit.AutoWrapWidth = 20 - }, - SetupRepo: func(shell *Shell) { - shell.CreateFile("file", "file content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). // stage file - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Type("subject"). - SwitchToDescription(). - Type("Lorem ipsum dolor sit amet, consectetur adipiscing elit."). - // See how it automatically inserted line feeds to wrap the text: - Content(Equals("Lorem ipsum dolor \nsit amet, \nconsectetur \nadipiscing elit.")). - SwitchToSummary(). - Confirm() - - t.Views().Commits(). - Lines( - Contains("subject"), - ). - Focus(). - Tap(func() { - t.Views().Main().Content(Contains( - "subject\n \n Lorem ipsum dolor\n sit amet,\n consectetur\n adipiscing elit.")) - }). - Press(keys.Commits.RenameCommit) - - // Test that when rewording, the hard line breaks are turned back into - // soft ones, so that we can insert text at the beginning and have the - // paragraph reflow nicely. - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("subject")). - SwitchToDescription(). - Content(Equals("Lorem ipsum dolor \nsit amet, \nconsectetur \nadipiscing elit.")). - GoToBeginning(). - Type("More text. "). - Content(Equals("More text. Lorem \nipsum dolor sit \namet, consectetur \nadipiscing elit.")) - }, -}) diff --git a/pkg/integration/tests/commit/checkout.go b/pkg/integration/tests/commit/checkout.go deleted file mode 100644 index ddd20c4ec98..00000000000 --- a/pkg/integration/tests/commit/checkout.go +++ /dev/null @@ -1,72 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Checkout = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Checkout a commit as a detached head, or checkout an existing branch at a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.NewBranch("branch1") - shell.NewBranch("branch2") - shell.EmptyCommit("three") - shell.EmptyCommit("four") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - PressPrimaryAction() - - t.ExpectPopup().Menu(). - Title(Contains("Checkout branch or commit")). - Lines( - MatchesRegexp("Checkout commit [a-f0-9]+ as detached head").IsSelected(), - Contains("Checkout branch"), - Contains("Cancel"), - ). - Select(Contains("Checkout branch")). - Tooltip(Contains("Disabled: No branches found at selected commit.")). - Select(MatchesRegexp("Checkout commit [a-f0-9]+ as detached head")). - Confirm() - t.Views().Branches().Lines( - Contains("* (HEAD detached at"), - Contains("branch1"), - Contains("branch2"), - Contains("master"), - ) - - t.Views().Commits(). - NavigateToLine(Contains("two")). - PressPrimaryAction() - - t.ExpectPopup().Menu(). - Title(Contains("Checkout branch or commit")). - Lines( - MatchesRegexp("Checkout commit [a-f0-9]+ as detached head").IsSelected(), - Contains("Checkout branch 'branch1'"), - Contains("Checkout branch 'master'"), - Contains("Cancel"), - ). - Select(Contains("Checkout branch 'master'")). - Confirm() - t.Views().Branches().Lines( - Contains("master"), - Contains("branch1"), - Contains("branch2"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/checkout_file_from_commit.go b/pkg/integration/tests/commit/checkout_file_from_commit.go deleted file mode 100644 index 40a8ee1464a..00000000000 --- a/pkg/integration/tests/commit/checkout_file_from_commit.go +++ /dev/null @@ -1,55 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CheckoutFileFromCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Checkout a file from a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file.txt", "one\n") - shell.Commit("one") - shell.CreateFileAndAdd("file.txt", "two\n") - shell.Commit("two") - shell.CreateFileAndAdd("file.txt", "three\n") - shell.Commit("three") - shell.CreateFileAndAdd("file.txt", "four\n") - shell.Commit("four") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - NavigateToLine(Contains("three")). - Tap(func() { - t.Views().Main().ContainsLines( - Contains("-two"), - Contains("+three"), - ) - }). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("M file.txt"), - ). - Press(keys.CommitFiles.CheckoutCommitFile) - - t.Views().Files(). - Lines( - Equals("M file.txt"), - ) - - t.FileSystem().FileContent("file.txt", Equals("three\n")) - }, -}) diff --git a/pkg/integration/tests/commit/checkout_file_from_range_selection_of_commits.go b/pkg/integration/tests/commit/checkout_file_from_range_selection_of_commits.go deleted file mode 100644 index 28b8f966c9d..00000000000 --- a/pkg/integration/tests/commit/checkout_file_from_range_selection_of_commits.go +++ /dev/null @@ -1,56 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CheckoutFileFromRangeSelectionOfCommits = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Checkout a file from a range selection of commits", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file.txt", "one\n") - shell.Commit("one") - shell.CreateFileAndAdd("file.txt", "two\n") - shell.Commit("two") - shell.CreateFileAndAdd("file.txt", "three\n") - shell.Commit("three") - shell.CreateFileAndAdd("file.txt", "four\n") - shell.Commit("four") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - NavigateToLine(Contains("three")). - Press(keys.Universal.RangeSelectDown). - Tap(func() { - t.Views().Main().ContainsLines( - Contains("-one"), - Contains("+three"), - ) - }). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("M file.txt"), - ). - Press(keys.CommitFiles.CheckoutCommitFile) - - t.Views().Files(). - Lines( - Equals("M file.txt"), - ) - - t.FileSystem().FileContent("file.txt", Equals("three\n")) - }, -}) diff --git a/pkg/integration/tests/commit/commit.go b/pkg/integration/tests/commit/commit.go deleted file mode 100644 index c9599f4e091..00000000000 --- a/pkg/integration/tests/commit/commit.go +++ /dev/null @@ -1,66 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Commit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Staging a couple files and committing", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFile("myfile", "myfile content") - shell.CreateFile("myfile2", "myfile2 content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ?? myfile"), - Equals(" ?? myfile2"), - ). - SelectNextItem(). - PressPrimaryAction(). // stage file - Lines( - Equals("▼ /"), - Equals(" A myfile").IsSelected(), - Equals(" ?? myfile2"), - ). - SelectNextItem(). - PressPrimaryAction(). // stage other file - Lines( - Equals("▼ /"), - Equals(" A myfile"), - Equals(" A myfile2").IsSelected(), - ). - Press(keys.Files.CommitChanges) - - commitMessage := "my commit message" - - t.ExpectPopup().CommitMessagePanel().Type(commitMessage).Confirm() - - t.Views().Files(). - IsEmpty() - - t.Views().Commits(). - Focus(). - Lines( - Contains(commitMessage).IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /"), - Equals(" A myfile"), - Equals(" A myfile2"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/commit_multiline.go b/pkg/integration/tests/commit/commit_multiline.go deleted file mode 100644 index 576ec3eadac..00000000000 --- a/pkg/integration/tests/commit/commit_multiline.go +++ /dev/null @@ -1,41 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CommitMultiline = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Commit with a multi-line commit message", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFile("myfile", "myfile content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Type("first line"). - SwitchToDescription(). - AddNewline(). - AddNewline(). - Type("fourth line"). - SwitchToSummary(). - Confirm() - t.Views().Commits(). - Lines( - Contains("first line"), - ) - - t.Views().Commits().Focus() - t.Views().Main().Content(MatchesRegexp("first line\n\\s*\n\\s*fourth line")) - }, -}) diff --git a/pkg/integration/tests/commit/commit_skip_hooks.go b/pkg/integration/tests/commit/commit_skip_hooks.go deleted file mode 100644 index d2e9431fb54..00000000000 --- a/pkg/integration/tests/commit/commit_skip_hooks.go +++ /dev/null @@ -1,44 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var blockingHook = `#!/bin/bash - -# For this test all we need is a hook that always fails -exit 1 -` - -var CommitSkipHooks = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Commit with skip hook using CommitChangesWithoutHook", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFile(".git/hooks/pre-commit", blockingHook) - shell.MakeExecutable(".git/hooks/pre-commit") - - shell.CreateFile("file.txt", "content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - checkBlockingHook(t, keys) - - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). - Lines( - Equals("A file.txt"), - ). - Press(keys.Files.CommitChangesWithoutHook) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - Type("foo bar"). - Confirm() - - t.Views().Commits().Focus() - t.Views().Main().Content(Contains("foo bar")) - }, -}) diff --git a/pkg/integration/tests/commit/commit_switch_to_editor.go b/pkg/integration/tests/commit/commit_switch_to_editor.go deleted file mode 100644 index b3c82aadc71..00000000000 --- a/pkg/integration/tests/commit/commit_switch_to_editor.go +++ /dev/null @@ -1,63 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CommitSwitchToEditor = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Commit, then switch from built-in commit message panel to editor", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFile("file1", "file1 content") - shell.CreateFile("file2", "file2 content") - - // Set an editor that appends a line to the existing message. Since - // git adds all this "# Please enter the commit message for your changes" - // stuff, this will result in an extra blank line before the added line. - shell.SetConfig("core.editor", "sh -c 'echo third line >>.git/COMMIT_EDITMSG'") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ?? file1"), - Equals(" ?? file2"), - ). - SelectNextItem(). - PressPrimaryAction(). // stage one of the files - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Type("first line"). - SwitchToDescription(). - Type("second line"). - SwitchToSummary(). - SwitchToEditor() - t.Views().Commits(). - Lines( - Contains("first line"), - ) - - t.Views().Commits().Focus() - t.Views().Main().Content(MatchesRegexp(`first line\n\s*\n\s*second line\n\s*\n\s*third line`)) - - // Now check that the preserved commit message was cleared: - t.Views().Files(). - Focus(). - Lines( - Equals("?? file2"), - ). - PressPrimaryAction(). // stage the other file - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("")) - }, -}) diff --git a/pkg/integration/tests/commit/commit_switch_to_editor_skip_hooks.go b/pkg/integration/tests/commit/commit_switch_to_editor_skip_hooks.go deleted file mode 100644 index 008a217b487..00000000000 --- a/pkg/integration/tests/commit/commit_switch_to_editor_skip_hooks.go +++ /dev/null @@ -1,64 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CommitSwitchToEditorSkipHooks = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Commit, then switch from built-in commit message panel to editor", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFile(".git/hooks/pre-commit", blockingHook) - shell.MakeExecutable(".git/hooks/pre-commit") - shell.CreateFile("file1", "file1 content") - shell.CreateFile("file2", "file2 content") - - // Set an editor that appends a line to the existing message. Since - // git adds all this "# Please enter the commit message for your changes" - // stuff, this will result in an extra blank line before the added line. - shell.SetConfig("core.editor", "sh -c 'echo third line >>.git/COMMIT_EDITMSG'") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - checkBlockingHook(t, keys) - - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ?? file1"), - Equals(" ?? file2"), - ). - SelectNextItem(). - PressPrimaryAction(). // stage one of the files - Press(keys.Files.CommitChangesWithoutHook) - - t.ExpectPopup().CommitMessagePanel(). - Type("first line"). - SwitchToDescription(). - Type("second line"). - SwitchToSummary(). - SwitchToEditor() - t.Views().Commits(). - Lines( - Contains("first line"), - ) - - t.Views().Commits().Focus() - t.Views().Main().Content(MatchesRegexp(`first line\n\s*\n\s*second line\n\s*\n\s*third line`)) - - // Now check that the preserved commit message was cleared: - t.Views().Files(). - Focus(). - PressPrimaryAction(). // stage the other file - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("")) - }, -}) diff --git a/pkg/integration/tests/commit/commit_wip_with_prefix.go b/pkg/integration/tests/commit/commit_wip_with_prefix.go deleted file mode 100644 index 52ca52c2346..00000000000 --- a/pkg/integration/tests/commit/commit_wip_with_prefix.go +++ /dev/null @@ -1,62 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CommitWipWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Commit with skip hook and config commitPrefix is defined. Prefix is ignored when creating WIP commits.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().Git.CommitPrefixes = map[string][]config.CommitPrefixConfig{"repo": {{Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}}} - }, - SetupRepo: func(shell *Shell) { - shell.CreateFile(".git/hooks/pre-commit", blockingHook) - shell.MakeExecutable(".git/hooks/pre-commit") - - shell.NewBranch("feature/TEST-002") - shell.CreateFile("test-wip-commit-prefix", "This is foo bar") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - checkBlockingHook(t, keys) - - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). - Press(keys.Files.CommitChangesWithoutHook) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - InitialText(Equals("WIP")). - Type(" foo"). - Cancel() - - t.Views().Files(). - IsFocused(). - Press(keys.Files.CommitChangesWithoutHook) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - InitialText(Equals("WIP foo")). - Type(" bar"). - Cancel() - - t.Views().Files(). - IsFocused(). - Press(keys.Files.CommitChangesWithoutHook) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - InitialText(Equals("WIP foo bar")). - Type(". Added something else"). - Confirm() - - t.Views().Commits().Focus() - t.Views().Main().Content(Contains("WIP foo bar. Added something else")) - }, -}) diff --git a/pkg/integration/tests/commit/commit_with_fallthrough_prefix.go b/pkg/integration/tests/commit/commit_with_fallthrough_prefix.go deleted file mode 100644 index 801443c59a8..00000000000 --- a/pkg/integration/tests/commit/commit_with_fallthrough_prefix.go +++ /dev/null @@ -1,53 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CommitWithFallthroughPrefix = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Commit with multiple CommitPrefixConfig", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().Git.CommitPrefix = []config.CommitPrefixConfig{ - {Pattern: "^doesntmatch-(\\w+).*", Replace: "[BAD $1]: "}, - {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[GOOD $1]: "}, - } - cfg.GetUserConfig().Git.CommitPrefixes = map[string][]config.CommitPrefixConfig{ - "DifferentProject": {{Pattern: "^otherthatdoesn'tmatch-(\\w+).*", Replace: "[BAD $1]: "}}, - } - }, - SetupRepo: func(shell *Shell) { - shell.NewBranch("feature/TEST-001") - shell.CreateFile("test-commit-prefix", "This is foo bar") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - InitialText(Equals("[GOOD TEST-001]: ")). - Type("my commit message"). - Cancel() - - t.Views().Files(). - IsFocused(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - InitialText(Equals("[GOOD TEST-001]: my commit message")). - Type(". Added something else"). - Confirm() - - t.Views().Commits().Focus() - t.Views().Main().Content(Contains("[GOOD TEST-001]: my commit message. Added something else")) - }, -}) diff --git a/pkg/integration/tests/commit/commit_with_global_prefix.go b/pkg/integration/tests/commit/commit_with_global_prefix.go deleted file mode 100644 index ceb2314c1e0..00000000000 --- a/pkg/integration/tests/commit/commit_with_global_prefix.go +++ /dev/null @@ -1,47 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CommitWithGlobalPrefix = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Commit with defined config commitPrefix", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().Git.CommitPrefix = []config.CommitPrefixConfig{{Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}} - }, - SetupRepo: func(shell *Shell) { - shell.NewBranch("feature/TEST-001") - shell.CreateFile("test-commit-prefix", "This is foo bar") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - InitialText(Equals("[TEST-001]: ")). - Type("my commit message"). - Cancel() - - t.Views().Files(). - IsFocused(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - InitialText(Equals("[TEST-001]: my commit message")). - Type(". Added something else"). - Confirm() - - t.Views().Commits().Focus() - t.Views().Main().Content(Contains("[TEST-001]: my commit message. Added something else")) - }, -}) diff --git a/pkg/integration/tests/commit/commit_with_non_matching_branch_name.go b/pkg/integration/tests/commit/commit_with_non_matching_branch_name.go deleted file mode 100644 index d08264d210f..00000000000 --- a/pkg/integration/tests/commit/commit_with_non_matching_branch_name.go +++ /dev/null @@ -1,35 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CommitWithNonMatchingBranchName = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Commit with defined config commitPrefixes", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().Git.CommitPrefix = []config.CommitPrefixConfig{{ - Pattern: "^\\w+\\/(\\w+-\\w+).*", - Replace: "[$1]: ", - }} - }, - SetupRepo: func(shell *Shell) { - shell.NewBranch("branchnomatch") - shell.CreateFile("test-commit-prefix", "This is foo bar") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - InitialText(Equals("")) - }, -}) diff --git a/pkg/integration/tests/commit/commit_with_prefix.go b/pkg/integration/tests/commit/commit_with_prefix.go deleted file mode 100644 index 09bbf63b442..00000000000 --- a/pkg/integration/tests/commit/commit_with_prefix.go +++ /dev/null @@ -1,61 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CommitWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Commit with defined config commitPrefixes", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().Git.CommitPrefixes = map[string][]config.CommitPrefixConfig{ - "repo": {{ - Pattern: `^\w+/(\w+-\w+).*`, - Replace: "[$1]: ", - }}, - } - }, - SetupRepo: func(shell *Shell) { - shell.NewBranch("feature/TEST-001") - shell.CreateFile("test-commit-prefix", "This is foo bar") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - InitialText(Equals("[TEST-001]: ")). - Cancel() - - t.Views().Files(). - IsFocused(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - InitialText(Equals("[TEST-001]: ")). - Type("my commit message"). - Cancel() - - t.Views().Files(). - IsFocused(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - InitialText(Equals("[TEST-001]: my commit message")). - Type(". Added something else"). - Confirm() - - t.Views().Commits().Focus() - t.Views().Main().Content(Contains("[TEST-001]: my commit message. Added something else")) - }, -}) diff --git a/pkg/integration/tests/commit/copy_author_to_clipboard.go b/pkg/integration/tests/commit/copy_author_to_clipboard.go deleted file mode 100644 index 22a7311093f..00000000000 --- a/pkg/integration/tests/commit/copy_author_to_clipboard.go +++ /dev/null @@ -1,40 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// We're emulating the clipboard by writing to a file called clipboard - -var CopyAuthorToClipboard = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Copy a commit author name to the clipboard", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > clipboard" - }, - - SetupRepo: func(shell *Shell) { - shell.SetAuthor("John Doe", "john@doe.com") - shell.EmptyCommit("commit") - }, - - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit").IsSelected(), - ). - Press(keys.Commits.CopyCommitAttributeToClipboard) - - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Commit author")). - Confirm() - - t.ExpectToast(Equals("Commit author copied to clipboard")) - - t.FileSystem().FileContent("clipboard", Equals("John Doe ")) - }, -}) diff --git a/pkg/integration/tests/commit/copy_message_body_to_clipboard.go b/pkg/integration/tests/commit/copy_message_body_to_clipboard.go deleted file mode 100644 index b0bb72488b9..00000000000 --- a/pkg/integration/tests/commit/copy_message_body_to_clipboard.go +++ /dev/null @@ -1,39 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// We're emulating the clipboard by writing to a file called clipboard - -var CopyMessageBodyToClipboard = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Copy a commit message body to the clipboard", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > clipboard" - }, - - SetupRepo: func(shell *Shell) { - shell.EmptyCommitWithBody("My Subject", "My awesome commit message body") - }, - - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("My Subject").IsSelected(), - ). - Press(keys.Commits.CopyCommitAttributeToClipboard) - - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Commit message body")). - Confirm() - - t.ExpectToast(Equals("Commit message body copied to clipboard")) - - t.FileSystem().FileContent("clipboard", Equals("My awesome commit message body")) - }, -}) diff --git a/pkg/integration/tests/commit/copy_tag_to_clipboard.go b/pkg/integration/tests/commit/copy_tag_to_clipboard.go deleted file mode 100644 index 6bcd0348356..00000000000 --- a/pkg/integration/tests/commit/copy_tag_to_clipboard.go +++ /dev/null @@ -1,42 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// We're emulating the clipboard by writing to a file called clipboard - -var CopyTagToClipboard = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Copy a commit tag to the clipboard", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > clipboard" - }, - - SetupRepo: func(shell *Shell) { - shell.SetAuthor("John Doe", "john@doe.com") - shell.EmptyCommit("commit") - shell.CreateLightweightTag("tag1", "HEAD") - shell.CreateLightweightTag("tag2", "HEAD") - }, - - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit").IsSelected(), - ). - Press(keys.Commits.CopyCommitAttributeToClipboard) - - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Commit tags")). - Confirm() - - t.ExpectToast(Equals("Commit tags copied to clipboard")) - - t.FileSystem().FileContent("clipboard", Equals("tag2\ntag1")) - }, -}) diff --git a/pkg/integration/tests/commit/create_amend_commit.go b/pkg/integration/tests/commit/create_amend_commit.go deleted file mode 100644 index 474e24099f0..00000000000 --- a/pkg/integration/tests/commit/create_amend_commit.go +++ /dev/null @@ -1,58 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CreateAmendCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create an amend commit for an existing commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(3). - CreateFileAndAdd("fixup-file", "fixup content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Commits.CreateFixupCommit). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Create fixup commit")). - Select(Contains("amend! commit with changes")). - Confirm() - t.ExpectPopup().CommitMessagePanel(). - Content(Equals("commit 02")). - Type(" amended").Confirm() - }). - Lines( - Contains("amend! commit 02"), - Contains("commit 03"), - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ) - - t.Views().Commits(). - Press(keys.Commits.SquashAboveCommits). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Apply fixup commits")). - Select(Contains("Above the selected commit")). - Confirm() - }). - Lines( - Contains("commit 03"), - Contains("commit 02 amended").IsSelected(), - Contains("commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/create_fixup_commit_in_branch_stack.go b/pkg/integration/tests/commit/create_fixup_commit_in_branch_stack.go deleted file mode 100644 index 1c593cf29ad..00000000000 --- a/pkg/integration/tests/commit/create_fixup_commit_in_branch_stack.go +++ /dev/null @@ -1,53 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CreateFixupCommitInBranchStack = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create a fixup commit in a stack of branches, verify that it is created at the end of the branch it belongs to", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("branch1") - shell.EmptyCommit("branch1 commit 1") - shell.EmptyCommit("branch1 commit 2") - shell.EmptyCommit("branch1 commit 3") - shell.NewBranch("branch2") - shell.EmptyCommit("branch2 commit 1") - shell.EmptyCommit("branch2 commit 2") - shell.CreateFileAndAdd("fixup-file", "fixup content") - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI ◯ branch2 commit 2"), - Contains("CI ◯ branch2 commit 1"), - Contains("CI ◯ * branch1 commit 3"), - Contains("CI ◯ branch1 commit 2"), - Contains("CI ◯ branch1 commit 1"), - ). - NavigateToLine(Contains("branch1 commit 2")). - Press(keys.Commits.CreateFixupCommit). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Create fixup commit")). - Select(Contains("fixup! commit")). - Confirm() - }). - Lines( - Contains("CI ◯ branch2 commit 2"), - Contains("CI ◯ branch2 commit 1"), - Contains("CI ◯ * fixup! branch1 commit 2"), - Contains("CI ◯ branch1 commit 3"), - Contains("CI ◯ branch1 commit 2"), - Contains("CI ◯ branch1 commit 1"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/create_tag.go b/pkg/integration/tests/commit/create_tag.go deleted file mode 100644 index f068f271bbe..00000000000 --- a/pkg/integration/tests/commit/create_tag.go +++ /dev/null @@ -1,46 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CreateTag = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create a new tag on a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("two").IsSelected(), - Contains("one"), - ). - Press(keys.Commits.CreateTag) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Tag name")). - Type("new-tag"). - Confirm() - - t.Views().Commits(). - Lines( - MatchesRegexp(`new-tag.*two`).IsSelected(), - MatchesRegexp(`one`), - ) - - t.Views().Tags(). - Focus(). - Lines( - MatchesRegexp(`new-tag.*two`).IsSelected(), - ) - - t.Git(). - TagNamesAt("HEAD", []string{"new-tag"}) - }, -}) diff --git a/pkg/integration/tests/commit/disable_copy_commit_message_body.go b/pkg/integration/tests/commit/disable_copy_commit_message_body.go deleted file mode 100644 index d6c03c851ab..00000000000 --- a/pkg/integration/tests/commit/disable_copy_commit_message_body.go +++ /dev/null @@ -1,33 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DisableCopyCommitMessageBody = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Disables copy commit message body when there is no body", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("commit") - }, - - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit").IsSelected(), - ). - Press(keys.Commits.CopyCommitAttributeToClipboard) - - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Commit message body")). - Confirm() - - t.ExpectToast(Equals("Disabled: Commit has no message body")) - }, -}) diff --git a/pkg/integration/tests/commit/discard_old_file_changes.go b/pkg/integration/tests/commit/discard_old_file_changes.go deleted file mode 100644 index 3e4057af520..00000000000 --- a/pkg/integration/tests/commit/discard_old_file_changes.go +++ /dev/null @@ -1,172 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiscardOldFileChanges = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Discarding a range of files from an old commit.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("dir1/d1_file0", "file0\n") - shell.CreateFileAndAdd("dir1/subd1/subfile0", "file1\n") - shell.CreateFileAndAdd("dir2/d2_file1", "d2f1 content\n") - shell.CreateFileAndAdd("dir2/d2_file2", "d2f4 content\n") - shell.Commit("remove one file from this commit") - - shell.UpdateFileAndAdd("dir2/d2_file1", "d2f1 content\nsecond line\n") - shell.DeleteFileAndAdd("dir2/d2_file2") - shell.CreateFileAndAdd("dir2/d2_file3", "d2f3 content\n") - shell.CreateFileAndAdd("dir2/d2_file4", "d2f2 content\n") - shell.Commit("remove four files from this commit") - - shell.CreateFileAndAdd("dir1/fileToRemove", "file to remove content\n") - shell.CreateFileAndAdd("dir1/multiLineFile", "this file has\ncontent on\nthree lines\n") - shell.CreateFileAndAdd("dir1/subd1/file2ToRemove", "file2 to remove content\n") - shell.Commit("remove changes in multiple dirs from this commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("remove changes in multiple dirs from this commit").IsSelected(), - Contains("remove four files from this commit"), - Contains("remove one file from this commit"), - ). - NavigateToLine(Contains("remove one file from this commit")). - PressEnter() - - // Check removing a single file from an old commit - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ dir1"), - Equals(" ▼ subd1"), - Equals(" A subfile0"), - Equals(" A d1_file0"), - Equals(" ▼ dir2"), - Equals(" A d2_file1"), - Equals(" A d2_file2"), - ). - NavigateToLine(Contains("d1_file0")). - Press(keys.Universal.Remove) - - t.ExpectPopup().Confirmation(). - Title(Equals("Discard file changes")). - Content(Equals("Are you sure you want to remove changes to the selected file(s) from this commit?\n\nThis action will start a rebase, reverting these file changes. Be aware that if subsequent commits depend on these changes, you may need to resolve conflicts.\nNote: This will also reset any active custom patches.")). - Confirm() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1/subd1"), - Equals(" A subfile0"), - Equals(" ▼ dir2"), - Equals(" A d2_file1").IsSelected(), - Equals(" A d2_file2"), - ). - PressEscape() - - // Check removing 4 files in the same directory - t.Views().Commits(). - Focus(). - Lines( - Contains("remove changes in multiple dirs from this commit"), - Contains("remove four files from this commit"), - Contains("remove one file from this commit").IsSelected(), - ). - NavigateToLine(Contains("remove four files from this commit")). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ dir2").IsSelected(), - Equals(" M d2_file1"), - Equals(" D d2_file2"), - Equals(" A d2_file3"), - Equals(" A d2_file4"), - ). - NavigateToLine(Contains("d2_file1")). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("d2_file4")). - Press(keys.Universal.Remove) - - t.ExpectPopup().Confirmation(). - Title(Equals("Discard file changes")). - Content(Equals("Are you sure you want to remove changes to the selected file(s) from this commit?\n\nThis action will start a rebase, reverting these file changes. Be aware that if subsequent commits depend on these changes, you may need to resolve conflicts.\nNote: This will also reset any active custom patches.")). - Confirm() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("(none)"), - ). - PressEscape() - - // Check removing multiple files from 2 directories w/ a custom patch. - // This checks node selection logic & if the custom patch is getting reset. - t.Views().Commits(). - IsFocused(). - Lines( - Contains("remove changes in multiple dirs from this commit"), - Contains("remove four files from this commit").IsSelected(), - Contains("remove one file from this commit"), - ). - NavigateToLine(Contains("remove changes in multiple dirs from this commit")). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ dir1").IsSelected(), - Equals(" ▼ subd1"), - Equals(" A file2ToRemove"), - Equals(" A fileToRemove"), - Equals(" A multiLineFile"), - ). - NavigateToLine(Contains("multiLineFile")). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - SelectedLine( - Contains("+this file has"), - ). - PressPrimaryAction(). - PressEscape() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ dir1"), - Equals(" ▼ subd1"), - Equals(" A file2ToRemove"), - Equals(" A fileToRemove"), - Equals(" ◐ multiLineFile").IsSelected(), - ). - NavigateToLine(Contains("dir1")). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("subd1")). - Press(keys.Universal.Remove) - - t.ExpectPopup().Confirmation(). - Title(Equals("Discard file changes")). - Content(Equals("Are you sure you want to remove changes to the selected file(s) from this commit?\n\nThis action will start a rebase, reverting these file changes. Be aware that if subsequent commits depend on these changes, you may need to resolve conflicts.\nNote: This will also reset any active custom patches.")). - Confirm() - - // "Building patch" will still be in this view if the patch isn't reset properly - t.Views().Information().Content(DoesNotContain("Building patch")) - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("(none)"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/discard_submodule_changes.go b/pkg/integration/tests/commit/discard_submodule_changes.go deleted file mode 100644 index b457a3a41b3..00000000000 --- a/pkg/integration/tests/commit/discard_submodule_changes.go +++ /dev/null @@ -1,54 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiscardSubmoduleChanges = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Discarding changes to a submodule from an old commit.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("Initial commit") - shell.CloneIntoSubmodule("submodule", "submodule") - shell.Commit("Add submodule") - - shell.AddFileInWorktreeOrSubmodule("submodule", "file", "content") - shell.CommitInWorktreeOrSubmodule("submodule", "add file in submodule") - shell.GitAdd("submodule") - shell.Commit("Update submodule") - - shell.UpdateFileInWorktreeOrSubmodule("submodule", "file", "changed content") - shell.CommitInWorktreeOrSubmodule("submodule", "change file in submodule") - shell.GitAdd("submodule") - shell.Commit("Update submodule again") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("Update submodule again").IsSelected(), - Contains("Update submodule"), - Contains("Add submodule"), - Contains("Initial commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("M submodule").IsSelected(), - ). - Press(keys.Universal.Remove) - - t.ExpectPopup().Confirmation(). - Title(Equals("Discard file changes")). - Content(Contains("Are you sure you want to remove changes to the selected file(s) from this commit?")). - Confirm() - - t.Shell().RunCommand([]string{"git", "submodule", "update"}) - t.FileSystem().FileContent("submodule/file", Equals("content")) - }, -}) diff --git a/pkg/integration/tests/commit/do_not_show_branch_marker_for_head_commit.go b/pkg/integration/tests/commit/do_not_show_branch_marker_for_head_commit.go deleted file mode 100644 index 51f56cef4a8..00000000000 --- a/pkg/integration/tests/commit/do_not_show_branch_marker_for_head_commit.go +++ /dev/null @@ -1,34 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DoNotShowBranchMarkerForHeadCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that no branch heads are shown for the branch head if there is a tag with the same name as the branch", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.NewBranch("branch1") - shell.EmptyCommit("two") - shell.EmptyCommit("three") - shell.CreateLightweightTag("branch1", "master") - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // Check that the local commits view does show a branch marker for the head commit - t.Views().Commits(). - Lines( - Contains("CI three"), - Contains("CI two"), - Contains("CI branch1 one"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/fail_hooks_then_commit_no_hooks.go b/pkg/integration/tests/commit/fail_hooks_then_commit_no_hooks.go deleted file mode 100644 index 6b328d234b7..00000000000 --- a/pkg/integration/tests/commit/fail_hooks_then_commit_no_hooks.go +++ /dev/null @@ -1,46 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FailHooksThenCommitNoHooks = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that commit message can be reused in commit without hook after failing commit with hooks", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFile(".git/hooks/pre-commit", blockingHook) - shell.MakeExecutable(".git/hooks/pre-commit") - - shell.CreateFileAndAdd("one", "one") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Contains("one"), - ). - Press(keys.Files.CommitChanges). - Tap(func() { - t.ExpectPopup().CommitMessagePanel().Type("my message").Confirm() - - t.ExpectPopup().Alert().Title(Equals("Error")).Content(Contains("Git command failed")).Confirm() - }). - Press(keys.Files.CommitChangesWithoutHook). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("my message")). // it remembered the commit message - Confirm() - - t.Views().Commits(). - Lines( - Contains("my message"), - ) - }) - t.Views().Commits().Focus() - t.Views().Main().Content(Contains("my message")) - }, -}) diff --git a/pkg/integration/tests/commit/find_base_commit_for_fixup.go b/pkg/integration/tests/commit/find_base_commit_for_fixup.go deleted file mode 100644 index 4440932e909..00000000000 --- a/pkg/integration/tests/commit/find_base_commit_for_fixup.go +++ /dev/null @@ -1,79 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FindBaseCommitForFixup = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Finds the base commit to create a fixup for", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch"). - EmptyCommit("1st commit"). - CreateFileAndAdd("file1", "file1 content\n"). - Commit("2nd commit"). - CreateFileAndAdd("file2", "file2 content\n"). - Commit("3rd commit"). - UpdateFile("file1", "file1 changed content"). - UpdateFile("file2", "file2 changed content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("3rd commit"), - Contains("2nd commit"), - Contains("1st commit"), - ) - - // Two changes from different commits: this fails - t.Views().Files(). - Focus(). - Press(keys.Files.FindBaseCommitForFixup) - - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content( - Contains("Multiple base commits found"). - Contains("2nd commit"). - Contains("3rd commit"), - ). - Confirm() - - // Stage only one of the files: this succeeds - t.Views().Files(). - IsFocused(). - NavigateToLine(Contains("file1")). - PressPrimaryAction(). - Press(keys.Files.FindBaseCommitForFixup) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("3rd commit"), - Contains("2nd commit").IsSelected(), - Contains("1st commit"), - ). - Press(keys.Commits.AmendToCommit) - - t.ExpectPopup().Confirmation(). - Title(Equals("Amend commit")). - Content(Contains("Are you sure you want to amend this commit with your staged files?")). - Confirm() - - // Now only the other file is modified (and unstaged); this works now - t.Views().Files(). - Focus(). - Press(keys.Files.FindBaseCommitForFixup) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("3rd commit").IsSelected(), - Contains("2nd commit"), - Contains("1st commit"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/find_base_commit_for_fixup_disregard_main_branch.go b/pkg/integration/tests/commit/find_base_commit_for_fixup_disregard_main_branch.go deleted file mode 100644 index aea9074e6b2..00000000000 --- a/pkg/integration/tests/commit/find_base_commit_for_fixup_disregard_main_branch.go +++ /dev/null @@ -1,47 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FindBaseCommitForFixupDisregardMainBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Finds the base commit to create a fixup for, disregarding changes to a commit that is already on master", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("1st commit"). - CreateFileAndAdd("file1", "file1 content\n"). - Commit("2nd commit"). - NewBranch("mybranch"). - CreateFileAndAdd("file2", "file2 content\n"). - Commit("3rd commit"). - EmptyCommit("4th commit"). - UpdateFile("file1", "file1 changed content"). - UpdateFile("file2", "file2 changed content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("4th commit").IsSelected(), - Contains("3rd commit"), - Contains("2nd commit"), - Contains("1st commit"), - ) - - t.Views().Files(). - Focus(). - Press(keys.Files.FindBaseCommitForFixup) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("4th commit"), - Contains("3rd commit").IsSelected(), - Contains("2nd commit"), - Contains("1st commit"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/find_base_commit_for_fixup_only_added_lines.go b/pkg/integration/tests/commit/find_base_commit_for_fixup_only_added_lines.go deleted file mode 100644 index 281fe72f98d..00000000000 --- a/pkg/integration/tests/commit/find_base_commit_for_fixup_only_added_lines.go +++ /dev/null @@ -1,84 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FindBaseCommitForFixupOnlyAddedLines = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Finds the base commit to create a fixup for, when all staged hunks have only added lines", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch"). - EmptyCommit("1st commit"). - CreateFileAndAdd("file1", "line A\nline B\nline C\n"). - Commit("2nd commit"). - UpdateFileAndAdd("file1", "line A\nline B changed\nline C\n"). - Commit("3rd commit"). - CreateFileAndAdd("file2", "line X\nline Y\nline Z\n"). - Commit("4th commit"). - UpdateFile("file1", "line A\nline B changed\nline B'\nline C\n"). - UpdateFile("file2", "line W\nline X\nline Y\nline Z\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("4th commit"), - Contains("3rd commit"), - Contains("2nd commit"), - Contains("1st commit"), - ) - - // Two changes from different commits: this fails - t.Views().Files(). - Focus(). - Press(keys.Files.FindBaseCommitForFixup) - - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content( - Contains("Multiple base commits found"). - Contains("3rd commit"). - Contains("4th commit"), - ). - Confirm() - - // Stage only one of the files: this succeeds - t.Views().Files(). - IsFocused(). - NavigateToLine(Contains("file1")). - PressPrimaryAction(). - Press(keys.Files.FindBaseCommitForFixup) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("4th commit"), - Contains("3rd commit").IsSelected(), - Contains("2nd commit"), - Contains("1st commit"), - ). - Press(keys.Commits.AmendToCommit) - - t.ExpectPopup().Confirmation(). - Title(Equals("Amend commit")). - Content(Contains("Are you sure you want to amend this commit with your staged files?")). - Confirm() - - // Now only the other file is modified (and unstaged); this works now - t.Views().Files(). - Focus(). - Press(keys.Files.FindBaseCommitForFixup) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("4th commit").IsSelected(), - Contains("3rd commit"), - Contains("2nd commit"), - Contains("1st commit"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/find_base_commit_for_fixup_warning_for_added_lines.go b/pkg/integration/tests/commit/find_base_commit_for_fixup_warning_for_added_lines.go deleted file mode 100644 index 315b757dbdf..00000000000 --- a/pkg/integration/tests/commit/find_base_commit_for_fixup_warning_for_added_lines.go +++ /dev/null @@ -1,48 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FindBaseCommitForFixupWarningForAddedLines = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Finds the base commit to create a fixup for, and warns that there are hunks with only added lines", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch"). - EmptyCommit("1st commit"). - CreateFileAndAdd("file1", "file1 content\n"). - Commit("2nd commit"). - CreateFileAndAdd("file2", "file2 content\n"). - Commit("3rd commit"). - UpdateFile("file1", "file1 changed content"). - UpdateFile("file2", "file2 content\nadded content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("3rd commit").IsSelected(), - Contains("2nd commit"), - Contains("1st commit"), - ) - - t.Views().Files(). - Focus(). - Press(keys.Files.FindBaseCommitForFixup) - - t.ExpectPopup().Confirmation(). - Title(Equals("Find base commit for fixup")). - Content(Contains("There are ranges of only added lines in the diff; be careful to check that these belong in the found base commit.")). - Confirm() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("3rd commit"), - Contains("2nd commit").IsSelected(), - Contains("1st commit"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/highlight.go b/pkg/integration/tests/commit/highlight.go deleted file mode 100644 index eaa77ccf16b..00000000000 --- a/pkg/integration/tests/commit/highlight.go +++ /dev/null @@ -1,37 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Highlight = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that the commit view highlights the correct lines", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.Log.ShowGraph = "always" - config.GetUserConfig().Gui.AuthorColors = map[string]string{ - "CI": "red", - } - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.EmptyCommit("three") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - highlightedColor := "#ffffff" - - t.Views().Commits(). - DoesNotContainColoredText(highlightedColor, "◯"). - Focus(). - ContainsColoredText(highlightedColor, "◯") - - t.Views().Files(). - Focus() - - t.Views().Commits(). - DoesNotContainColoredText(highlightedColor, "◯") - }, -}) diff --git a/pkg/integration/tests/commit/history.go b/pkg/integration/tests/commit/history.go deleted file mode 100644 index 323dcb7f1bb..00000000000 --- a/pkg/integration/tests/commit/history.go +++ /dev/null @@ -1,53 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var History = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cycling through commit message history in the commit message panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.EmptyCommit("commit 2") - shell.EmptyCommit("commit 3") - - shell.CreateFile("myfile", "myfile content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). // stage file - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("")). - Type("my commit message"). - SelectPreviousMessage(). - Content(Equals("commit 3")). - SelectPreviousMessage(). - Content(Equals("commit 2")). - SelectPreviousMessage(). - Content(Equals("initial commit")). - SelectPreviousMessage(). - Content(Equals("initial commit")). // we hit the end - SelectNextMessage(). - Content(Equals("commit 2")). - SelectNextMessage(). - Content(Equals("commit 3")). - SelectNextMessage(). - Content(Equals("my commit message")). - SelectNextMessage(). - Content(Equals("my commit message")). // we hit the beginning - Type(" with extra added"). - Confirm() - - t.Views().Commits(). - TopLines( - Contains("my commit message with extra added").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/commit/history_complex.go b/pkg/integration/tests/commit/history_complex.go deleted file mode 100644 index 69308209813..00000000000 --- a/pkg/integration/tests/commit/history_complex.go +++ /dev/null @@ -1,59 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var HistoryComplex = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "More complex flow for cycling commit message history", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.EmptyCommit("commit 2") - shell.EmptyCommit("commit 3") - - shell.CreateFileAndAdd("myfile", "myfile content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // We're going to start a new commit message, - // then leave and try to reword a commit, then - // come back to original message and confirm we haven't lost our message. - // This shows that we're storing the preserved message for a new commit separately - // to the message when cycling history. - - t.Views().Files(). - IsFocused(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("")). - Type("my commit message"). - Cancel() - - t.Views().Commits(). - Focus(). - SelectedLine(Contains("commit 3")). - Press(keys.Commits.RenameCommit) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("commit 3")). - SelectNextMessage(). - Content(Equals("")). - Type("reworded message"). - SelectPreviousMessage(). - Content(Equals("commit 3")). - SelectNextMessage(). - Content(Equals("reworded message")). - Cancel() - - t.Views().Files(). - Focus(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("my commit message")) - }, -}) diff --git a/pkg/integration/tests/commit/new_branch.go b/pkg/integration/tests/commit/new_branch.go deleted file mode 100644 index 91e8fa4f2d8..00000000000 --- a/pkg/integration/tests/commit/new_branch.go +++ /dev/null @@ -1,40 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var NewBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Creating a new branch from a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("commit 1"). - EmptyCommit("commit 2"). - EmptyCommit("commit 3") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 3").IsSelected(), - Contains("commit 2"), - Contains("commit 1"), - ). - SelectNextItem(). - Press(keys.Universal.New). - Tap(func() { - branchName := "my-branch-name" - t.ExpectPopup().Prompt().Title(Contains("New branch name")).Type(branchName).Confirm() - - t.Git().CurrentBranchName(branchName) - }). - Lines( - Contains("commit 2"), - Contains("commit 1"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/paste_commit_message.go b/pkg/integration/tests/commit/paste_commit_message.go deleted file mode 100644 index ab9939e5b9f..00000000000 --- a/pkg/integration/tests/commit/paste_commit_message.go +++ /dev/null @@ -1,49 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PasteCommitMessage = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Paste a commit message into the commit message panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > ../clipboard" - config.GetUserConfig().OS.ReadFromClipboardCmd = "cat ../clipboard" - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("subject\n\nbody 1st line\nbody 2nd line") - shell.CreateFileAndAdd("file", "file content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - ContainsLines( - Contains("subject").IsSelected(), - ). - Press(keys.Commits.CopyCommitAttributeToClipboard) - - t.ExpectPopup().Menu().Title(Equals("Copy to clipboard")). - Select(Contains("Commit message (subject and body)")).Confirm() - - t.ExpectToast(Equals("Commit message copied to clipboard")) - - t.Views().Files(). - Focus(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - OpenCommitMenu() - - t.ExpectPopup().Menu().Title(Equals("Commit Menu")). - Select(Contains("Paste commit message from clipboard")). - Confirm() - - t.ExpectPopup().CommitMessagePanel(). - Content(Equals("subject")). - SwitchToDescription(). - Content(Equals("body 1st line\nbody 2nd line")) - }, -}) diff --git a/pkg/integration/tests/commit/paste_commit_message_over_existing.go b/pkg/integration/tests/commit/paste_commit_message_over_existing.go deleted file mode 100644 index 9f0ab259cc4..00000000000 --- a/pkg/integration/tests/commit/paste_commit_message_over_existing.go +++ /dev/null @@ -1,54 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PasteCommitMessageOverExisting = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Paste a commit message into the commit message panel when there is already text in the panel, causing a confirmation", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > ../clipboard" - config.GetUserConfig().OS.ReadFromClipboardCmd = "cat ../clipboard" - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("subject\n\nbody 1st line\nbody 2nd line") - shell.CreateFileAndAdd("file", "file content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - ContainsLines( - Contains("subject").IsSelected(), - ). - Press(keys.Commits.CopyCommitAttributeToClipboard) - - t.ExpectPopup().Menu().Title(Equals("Copy to clipboard")). - Select(Contains("Commit message (subject and body)")).Confirm() - - t.ExpectToast(Equals("Commit message copied to clipboard")) - - t.Views().Files(). - Focus(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Type("existing message"). - OpenCommitMenu() - - t.ExpectPopup().Menu().Title(Equals("Commit Menu")). - Select(Contains("Paste commit message from clipboard")). - Confirm() - - t.ExpectPopup().Alert().Title(Equals("Paste commit message from clipboard")). - Content(Equals("Pasting will overwrite the current commit message, continue?")). - Confirm() - - t.ExpectPopup().CommitMessagePanel(). - Content(Equals("subject")). - SwitchToDescription(). - Content(Equals("body 1st line\nbody 2nd line")) - }, -}) diff --git a/pkg/integration/tests/commit/preserve_commit_message.go b/pkg/integration/tests/commit/preserve_commit_message.go deleted file mode 100644 index ab136090428..00000000000 --- a/pkg/integration/tests/commit/preserve_commit_message.go +++ /dev/null @@ -1,58 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PreserveCommitMessage = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Test that the commit message is preserved correctly when canceling the commit message panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", "myfile content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("")). - Type("my commit message"). - SwitchToDescription(). - Type("first paragraph"). - AddNewline(). - AddNewline(). - Type("second paragraph"). - Cancel() - - t.FileSystem().PathPresent(".git/LAZYGIT_PENDING_COMMIT") - - t.Views().Files(). - IsFocused(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Content(Equals("my commit message")). - SwitchToDescription(). - Content(Equals("first paragraph\n\nsecond paragraph")). - Clear(). - SwitchToSummary(). - Clear(). - Cancel() - - t.FileSystem().PathNotPresent(".git/LAZYGIT_PENDING_COMMIT") - - t.Views().Files(). - IsFocused(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Type("my new commit message"). - Confirm() - - t.FileSystem().PathNotPresent(".git/LAZYGIT_PENDING_COMMIT") - }, -}) diff --git a/pkg/integration/tests/commit/reset_author.go b/pkg/integration/tests/commit/reset_author.go deleted file mode 100644 index 506beb9f8a8..00000000000 --- a/pkg/integration/tests/commit/reset_author.go +++ /dev/null @@ -1,39 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ResetAuthor = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Reset author on a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.SetConfig("user.email", "Bill@example.com") - shell.SetConfig("user.name", "Bill Smith") - - shell.EmptyCommit("one") - - shell.SetConfig("user.email", "John@example.com") - shell.SetConfig("user.name", "John Smith") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("BS").Contains("one").IsSelected(), - ). - Press(keys.Commits.ResetCommitAuthor). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Amend commit attribute")). - Select(Contains("Reset author")). - Confirm() - }). - Lines( - Contains("JS").Contains("one").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/commit/reset_author_range.go b/pkg/integration/tests/commit/reset_author_range.go deleted file mode 100644 index 9d7c764cf59..00000000000 --- a/pkg/integration/tests/commit/reset_author_range.go +++ /dev/null @@ -1,52 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ResetAuthorRange = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Reset author on a range of commits", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.SetConfig("user.email", "Bill@example.com") - shell.SetConfig("user.name", "Bill Smith") - - shell.EmptyCommit("fourth") - shell.EmptyCommit("third") - shell.EmptyCommit("second") - shell.EmptyCommit("first") - - shell.SetConfig("user.email", "John@example.com") - shell.SetConfig("user.name", "John Smith") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("BS").Contains("first").IsSelected(), - Contains("BS").Contains("second"), - Contains("BS").Contains("third"), - Contains("BS").Contains("fourth"), - ). - SelectNextItem(). - Press(keys.Universal.ToggleRangeSelect). - SelectNextItem(). - Press(keys.Commits.ResetCommitAuthor). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Amend commit attribute")). - Select(Contains("Reset author")). - Confirm() - }). - PressEscape(). - Lines( - Contains("BS").Contains("first"), - Contains("JS").Contains("second"), - Contains("JS").Contains("third").IsSelected(), - Contains("BS").Contains("fourth"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/revert.go b/pkg/integration/tests/commit/revert.go deleted file mode 100644 index b295abf924d..00000000000 --- a/pkg/integration/tests/commit/revert.go +++ /dev/null @@ -1,40 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Revert = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Reverts a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFile("myfile", "myfile content") - shell.GitAddAll() - shell.Commit("first commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("first commit"), - ). - Press(keys.Commits.RevertCommit). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Revert commit")). - Content(MatchesRegexp(`Are you sure you want to revert \w+?`)). - Confirm() - }). - Lines( - Contains("Revert \"first commit\""), - Contains("first commit").IsSelected(), - ). - SelectPreviousItem() - - t.Views().Main().Content(Contains("-myfile content")) - t.FileSystem().PathNotPresent("myfile") - }, -}) diff --git a/pkg/integration/tests/commit/revert_merge.go b/pkg/integration/tests/commit/revert_merge.go deleted file mode 100644 index 1d45180869b..00000000000 --- a/pkg/integration/tests/commit/revert_merge.go +++ /dev/null @@ -1,38 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var RevertMerge = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Reverts a merge commit and chooses to revert to the parent commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shared.CreateMergeCommit(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Focus(). - TopLines( - Contains("Merge branch 'second-change-branch' into first-change-branch").IsSelected(), - ). - Press(keys.Commits.RevertCommit) - - t.ExpectPopup().Confirmation(). - Title(Equals("Revert commit")). - Content(MatchesRegexp(`Are you sure you want to revert \w+?`)). - Confirm() - - t.Views().Commits().IsFocused(). - TopLines( - Contains("Revert \"Merge branch 'second-change-branch' into first-change-branch\""), - Contains("Merge branch 'second-change-branch' into first-change-branch").IsSelected(), - ). - SelectPreviousItem() - - t.Views().Main().Content(Contains("-Second Change").Contains("+First Change")) - }, -}) diff --git a/pkg/integration/tests/commit/revert_with_conflict_multiple_commits.go b/pkg/integration/tests/commit/revert_with_conflict_multiple_commits.go deleted file mode 100644 index e702c79b3c4..00000000000 --- a/pkg/integration/tests/commit/revert_with_conflict_multiple_commits.go +++ /dev/null @@ -1,85 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RevertWithConflictMultipleCommits = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Reverts a range of commits, the first of which conflicts", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", "") - shell.Commit("add empty file") - shell.CreateFileAndAdd("otherfile", "") - shell.Commit("unrelated change") - shell.CreateFileAndAdd("myfile", "first line\n") - shell.Commit("add first line") - shell.UpdateFileAndAdd("myfile", "first line\nsecond line\n") - shell.Commit("add second line") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI ◯ add second line").IsSelected(), - Contains("CI ◯ add first line"), - Contains("CI ◯ unrelated change"), - Contains("CI ◯ add empty file"), - ). - SelectNextItem(). - Press(keys.Universal.RangeSelectDown). - Press(keys.Commits.RevertCommit). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Revert commit")). - Content(Equals("Are you sure you want to revert the selected commits?")). - Confirm() - - t.ExpectPopup().Menu(). - Title(Equals("Conflicts!")). - Select(Contains("View conflicts")). - Confirm() - }). - Lines( - Contains("--- Pending reverts ---"), - Contains("revert").Contains("CI unrelated change"), - Contains("revert").Contains("CI <-- CONFLICT --- add first line"), - Contains("--- Commits ---"), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line"), - Contains("CI ◯ unrelated change"), - Contains("CI ◯ add empty file"), - ) - - t.Views().Options().Content(Contains("View revert options: m")) - t.Views().Information().Content(Contains("Reverting (Reset)")) - - t.Views().Files().IsFocused(). - Lines( - Contains("UU myfile").IsSelected(), - ). - PressEnter() - - t.Views().MergeConflicts().IsFocused(). - SelectNextItem(). - PressPrimaryAction() - - t.ExpectPopup().Alert(). - Title(Equals("Continue")). - Content(Contains("All merge conflicts resolved. Continue the revert?")). - Confirm() - - t.Views().Commits(). - Lines( - Contains(`CI ◯ Revert "unrelated change"`), - Contains(`CI ◯ Revert "add first line"`), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line"), - Contains("CI ◯ unrelated change"), - Contains("CI ◯ add empty file"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/revert_with_conflict_single_commit.go b/pkg/integration/tests/commit/revert_with_conflict_single_commit.go deleted file mode 100644 index 4d98fdfe5ca..00000000000 --- a/pkg/integration/tests/commit/revert_with_conflict_single_commit.go +++ /dev/null @@ -1,76 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RevertWithConflictSingleCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Reverts a commit that conflicts", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", "") - shell.Commit("add empty file") - shell.CreateFileAndAdd("myfile", "first line\n") - shell.Commit("add first line") - shell.UpdateFileAndAdd("myfile", "first line\nsecond line\n") - shell.Commit("add second line") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI ◯ add second line").IsSelected(), - Contains("CI ◯ add first line"), - Contains("CI ◯ add empty file"), - ). - SelectNextItem(). - Press(keys.Commits.RevertCommit). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Revert commit")). - Content(MatchesRegexp(`Are you sure you want to revert \w+?`)). - Confirm() - t.ExpectPopup().Menu(). - Title(Equals("Conflicts!")). - Select(Contains("View conflicts")). - Confirm() - }). - Lines( - Contains("--- Pending reverts ---"), - Contains("revert").Contains("CI <-- CONFLICT --- add first line"), - Contains("--- Commits ---"), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line"), - Contains("CI ◯ add empty file"), - ) - - t.Views().Options().Content(Contains("View revert options: m")) - t.Views().Information().Content(Contains("Reverting (Reset)")) - - t.Views().Files().IsFocused(). - Lines( - Contains("UU myfile").IsSelected(), - ). - PressEnter() - - t.Views().MergeConflicts().IsFocused(). - SelectNextItem(). - PressPrimaryAction() - - t.ExpectPopup().Alert(). - Title(Equals("Continue")). - Content(Contains("All merge conflicts resolved. Continue the revert?")). - Confirm() - - t.Views().Commits(). - Lines( - Contains(`CI ◯ Revert "add first line"`), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line"), - Contains("CI ◯ add empty file"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/reword.go b/pkg/integration/tests/commit/reword.go deleted file mode 100644 index c349a0e2890..00000000000 --- a/pkg/integration/tests/commit/reword.go +++ /dev/null @@ -1,73 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Reword = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Staging a couple files and committing", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFile("myfile", "myfile content") - shell.CreateFile("myfile2", "myfile2 content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Contains("myfile"), - Contains("myfile2"), - ). - SelectNextItem(). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - commitMessage := "my commit message" - - t.ExpectPopup().CommitMessagePanel().Type(commitMessage).Confirm() - t.Views().Commits(). - Lines( - Contains(commitMessage), - ) - - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - wipCommitMessage := "my commit message wip" - - t.ExpectPopup().CommitMessagePanel().Type(wipCommitMessage).Close() - - t.Views().Commits().Focus(). - Lines( - Contains(commitMessage), - ).Press(keys.Commits.RenameCommit) - - t.ExpectPopup().CommitMessagePanel(). - SwitchToDescription(). - Type("some description"). - SwitchToSummary(). - Confirm() - - t.Views().Main().Content(MatchesRegexp("my commit message\n\\s*some description")) - - t.Views().Files(). - Focus(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel().Confirm() - t.Views().Commits(). - Lines( - Contains(wipCommitMessage), - Contains(commitMessage), - ) - }, -}) diff --git a/pkg/integration/tests/commit/search.go b/pkg/integration/tests/commit/search.go deleted file mode 100644 index 24754b517a6..00000000000 --- a/pkg/integration/tests/commit/search.go +++ /dev/null @@ -1,108 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Search = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Search for a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.EmptyCommit("three") - shell.EmptyCommit("four") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.StartSearch). - Tap(func() { - t.ExpectSearch(). - Type("two"). - Confirm() - - t.Views().Search().IsVisible().Content(Contains("matches for 'two' (1 of 1)")) - }). - Lines( - Contains("four"), - Contains("three"), - Contains("two").IsSelected(), - Contains("one"), - ). - Press(keys.Universal.StartSearch). - Tap(func() { - t.ExpectSearch(). - Clear(). - Type("o"). - Confirm() - - t.Views().Search().IsVisible().Content(Contains("matches for 'o' (2 of 3)")) - }). - Lines( - Contains("four"), - Contains("three"), - Contains("two").IsSelected(), - Contains("one"), - ). - Press("n"). - Tap(func() { - t.Views().Search().IsVisible().Content(Contains("matches for 'o' (3 of 3)")) - }). - Lines( - Contains("four"), - Contains("three"), - Contains("two"), - Contains("one").IsSelected(), - ). - Press("n"). - Tap(func() { - t.Views().Search().IsVisible().Content(Contains("matches for 'o' (1 of 3)")) - }). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - Press("n"). - Tap(func() { - t.Views().Search().IsVisible().Content(Contains("matches for 'o' (2 of 3)")) - }). - Lines( - Contains("four"), - Contains("three"), - Contains("two").IsSelected(), - Contains("one"), - ). - Press("N"). - Tap(func() { - t.Views().Search().IsVisible().Content(Contains("matches for 'o' (1 of 3)")) - }). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - Press("N"). - Tap(func() { - t.Views().Search().IsVisible().Content(Contains("matches for 'o' (3 of 3)")) - }). - Lines( - Contains("four"), - Contains("three"), - Contains("two"), - Contains("one").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/commit/set_author.go b/pkg/integration/tests/commit/set_author.go deleted file mode 100644 index e0cd722daea..00000000000 --- a/pkg/integration/tests/commit/set_author.go +++ /dev/null @@ -1,79 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// Originally we only suggested authors present in the current branch, but now -// we include authors from other branches whose commits you've looked at in the -// lazygit session. - -var SetAuthor = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Set author on a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("original") - - shell.SetConfig("user.email", "Bill@example.com") - shell.SetConfig("user.name", "Bill Smith") - - shell.EmptyCommit("one") - - shell.NewBranch("other") - - shell.SetConfig("user.email", "John@example.com") - shell.SetConfig("user.name", "John Smith") - - shell.EmptyCommit("two") - - shell.Checkout("original") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("BS").Contains("one").IsSelected(), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("original").IsSelected(), - Contains("other"), - ). - NavigateToLine(Contains("other")). - PressEnter() - - // ensuring we get these commit authors as suggestions - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("JS").Contains("two").IsSelected(), - Contains("BS").Contains("one"), - ) - - t.Views().Commits(). - Focus(). - Press(keys.Commits.ResetCommitAuthor). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Amend commit attribute")). - Select(Contains(" Set author")). // adding space at start to distinguish from 'reset author' - Confirm() - - t.ExpectPopup().Prompt(). - Title(Contains("Set author")). - SuggestionLines( - Contains("Bill Smith"), - Contains("John Smith"), - ). - ConfirmSuggestion(Contains("John Smith")) - }). - Lines( - Contains("JS").Contains("one").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/commit/set_author_range.go b/pkg/integration/tests/commit/set_author_range.go deleted file mode 100644 index e366ba05ed7..00000000000 --- a/pkg/integration/tests/commit/set_author_range.go +++ /dev/null @@ -1,57 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SetAuthorRange = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Set author on a range of commits", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.SetConfig("user.email", "Bill@example.com") - shell.SetConfig("user.name", "Bill Smith") - - shell.EmptyCommit("fourth") - shell.EmptyCommit("third") - shell.EmptyCommit("second") - shell.EmptyCommit("first") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("BS").Contains("first").IsSelected(), - Contains("BS").Contains("second"), - Contains("BS").Contains("third"), - Contains("BS").Contains("fourth"), - ) - - t.Views().Commits(). - Focus(). - SelectNextItem(). - Press(keys.Universal.ToggleRangeSelect). - SelectNextItem(). - Press(keys.Commits.ResetCommitAuthor). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Amend commit attribute")). - Select(Contains(" Set author")). // adding space at start to distinguish from 'reset author' - Confirm() - - t.ExpectPopup().Prompt(). - Title(Contains("Set author")). - Type("John Smith "). - Confirm() - }). - PressEscape(). - Lines( - Contains("BS").Contains("first"), - Contains("JS").Contains("second"), - Contains("JS").Contains("third").IsSelected(), - Contains("BS").Contains("fourth"), - ) - }, -}) diff --git a/pkg/integration/tests/commit/shared.go b/pkg/integration/tests/commit/shared.go deleted file mode 100644 index 918197aaf3c..00000000000 --- a/pkg/integration/tests/commit/shared.go +++ /dev/null @@ -1,111 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -func setupForAmendTests(shell *Shell) { - shell.EmptyCommit("base commit") - shell.NewBranch("branch") - shell.Checkout("master") - shell.CreateFileAndAdd("file1", "master") - shell.Commit("file1 changed in master") - shell.Checkout("branch") - shell.UpdateFileAndAdd("file2", "two") - shell.Commit("commit two") - shell.CreateFileAndAdd("file1", "branch") - shell.Commit("file1 changed in branch") - shell.UpdateFileAndAdd("file3", "three") - shell.Commit("commit three") -} - -func doTheRebaseForAmendTests(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit three").IsSelected(), - Contains("file1 changed in branch"), - Contains("commit two"), - Contains("base commit"), - ) - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("master")). - Press(keys.Branches.RebaseBranch). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'branch'")). - Select(Contains("Simple rebase")). - Confirm() - t.Common().AcknowledgeConflicts() - }) - - t.Views().Commits(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit three"), - Contains("pick").Contains("<-- CONFLICT --- file1 changed in branch"), - Contains("--- Commits ---"), - Contains("commit two"), - Contains("file1 changed in master"), - Contains("base commit"), - ) - - t.Views().Files(). - Focus(). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - SelectNextItem(). // choose "incoming" - PressPrimaryAction() - - t.ExpectPopup().Confirmation(). - Title(Equals("Continue")). - Content(Contains("All merge conflicts resolved. Continue the rebase?")). - Cancel() -} - -func checkCommitContainsChange(t *TestDriver, commitSubject string, change string) { - t.Views().Commits(). - Focus(). - NavigateToLine(Contains(commitSubject)) - t.Views().Main(). - Content(Contains(change)) -} - -func checkBlockingHook(t *TestDriver, keys config.KeybindingConfig) { - // Shared function for tests using the blockingHook pre-commit hook for testing hook skipping - // Stage first file - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - // Try to commit with hook - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - Type("Commit should fail"). - Confirm() - - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Contains("Git command failed.")). - Confirm() - - // Clear the message - t.Views().Files(). - IsFocused(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - Clear(). - Cancel() - - // Unstage the file - t.Views().Files(). - IsFocused(). - PressPrimaryAction() -} diff --git a/pkg/integration/tests/commit/stage_range_of_lines.go b/pkg/integration/tests/commit/stage_range_of_lines.go deleted file mode 100644 index 5cac85d49b3..00000000000 --- a/pkg/integration/tests/commit/stage_range_of_lines.go +++ /dev/null @@ -1,41 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StageRangeOfLines = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Staging a range of lines", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.UseHunkModeInStagingView = false - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", "1st\n2nd\n3rd\n4th\n5th\n6th\n") - shell.Commit("Add file") - shell.UpdateFile("myfile", "1st changed\n2nd changed\n3rd\n4th\n5th changed\n6th\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - PressEnter() - - t.Views().Staging(). - Content( - Contains("-1st\n-2nd\n+1st changed\n+2nd changed\n 3rd\n 4th\n-5th\n+5th changed\n 6th"), - ). - SelectedLine(Equals("-1st")). - Press(keys.Universal.ToggleRangeSelect). - SelectNextItem(). - SelectNextItem(). - SelectNextItem(). - SelectNextItem(). - PressPrimaryAction(). - Content( - Contains(" 3rd\n 4th\n-5th\n+5th changed\n 6th"), - ). - SelectedLine(Equals("-5th")) - }, -}) diff --git a/pkg/integration/tests/commit/staged.go b/pkg/integration/tests/commit/staged.go deleted file mode 100644 index aac40313b1f..00000000000 --- a/pkg/integration/tests/commit/staged.go +++ /dev/null @@ -1,69 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Staged = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Staging a couple files, going in the staged files menu, unstaging a line then committing", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateFile("myfile", "myfile content\nwith a second line"). - CreateFile("myfile2", "myfile2 content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Contains("myfile"), - Contains("myfile2"), - ). - SelectNextItem(). - PressPrimaryAction(). // stage the file - PressEnter() - - t.Views().StagingSecondary(). - IsFocused(). - Tap(func() { - // we start with both lines having been staged - t.Views().StagingSecondary().Content(Contains("+myfile content")) - t.Views().StagingSecondary().Content(Contains("+with a second line")) - t.Views().Staging().Content(DoesNotContain("+myfile content")) - t.Views().Staging().Content(DoesNotContain("+with a second line")) - }). - // unstage the selected line - PressPrimaryAction(). - Tap(func() { - // the line should have been moved to the main view - t.Views().StagingSecondary().Content(DoesNotContain("+myfile content")) - t.Views().StagingSecondary().Content(Contains("+with a second line")) - t.Views().Staging().Content(Contains("+myfile content")) - t.Views().Staging().Content(DoesNotContain("+with a second line")) - }). - Press(keys.Files.CommitChanges) - - commitMessage := "my commit message" - t.ExpectPopup().CommitMessagePanel().Type(commitMessage).Confirm() - - t.Views().Commits(). - Lines( - Contains(commitMessage), - ) - - t.Views().StagingSecondary(). - IsEmpty() - - t.Views().Staging(). - IsFocused(). - Content(Contains("+myfile content")). - Content(DoesNotContain("+with a second line")) - }, -}) diff --git a/pkg/integration/tests/commit/staged_without_hooks.go b/pkg/integration/tests/commit/staged_without_hooks.go deleted file mode 100644 index b4e16c8eaba..00000000000 --- a/pkg/integration/tests/commit/staged_without_hooks.go +++ /dev/null @@ -1,74 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StagedWithoutHooks = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Staging a couple files, going in the staged files menu, unstaging a line then committing without pre-commit hooks", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFile(".git/hooks/pre-commit", blockingHook) - shell.MakeExecutable(".git/hooks/pre-commit") - - shell. - CreateFile("myfile", "myfile content\nwith a second line"). - CreateFile("myfile2", "myfile2 content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - checkBlockingHook(t, keys) - - // stage the file - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Contains("myfile"), - Contains("myfile2"), - ). - SelectNextItem(). - PressPrimaryAction(). - PressEnter() - - // we start with both lines having been staged - t.Views().StagingSecondary().Content( - Contains("+myfile content").Contains("+with a second line"), - ) - t.Views().Staging().Content( - DoesNotContain("+myfile content").DoesNotContain("+with a second line"), - ) - - // unstage the selected line - t.Views().StagingSecondary(). - IsFocused(). - PressPrimaryAction(). - Tap(func() { - // the line should have been moved to the main view - t.Views().Staging().Content(Contains("+myfile content").DoesNotContain("+with a second line")) - }). - Content(DoesNotContain("+myfile content").Contains("+with a second line")). - Press(keys.Files.CommitChangesWithoutHook) - - commitMessage := "my commit message" - t.ExpectPopup().CommitMessagePanel().Type(commitMessage).Confirm() - - t.Views().Commits(). - Lines( - Contains(commitMessage), - ) - - t.Views().StagingSecondary(). - IsEmpty() - - t.Views().Staging(). - IsFocused(). - Content(Contains("+myfile content")). - Content(DoesNotContain("+with a second line")) - }, -}) diff --git a/pkg/integration/tests/commit/unstaged.go b/pkg/integration/tests/commit/unstaged.go deleted file mode 100644 index 043e6ea9cb6..00000000000 --- a/pkg/integration/tests/commit/unstaged.go +++ /dev/null @@ -1,59 +0,0 @@ -package commit - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Unstaged = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Staging a couple files, going in the unstaged files menu, staging a line and committing", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateFile("myfile", "myfile content\nwith a second line"). - CreateFile("myfile2", "myfile2 content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - IsEmpty() - - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Contains("myfile"), - Contains("myfile2"), - ). - SelectNextItem(). - PressEnter() - - t.Views().Staging(). - IsFocused(). - Tap(func() { - t.Views().StagingSecondary().Content(DoesNotContain("+myfile content")) - t.Views().Staging().SelectedLine(Equals("+myfile content")) - }). - // stage the first line - PressPrimaryAction(). - Tap(func() { - t.Views().Staging().Content(DoesNotContain("+myfile content")). - SelectedLine(Equals("+with a second line")) - t.Views().StagingSecondary().Content(Contains("+myfile content")) - }). - Press(keys.Files.CommitChanges) - - commitMessage := "my commit message" - t.ExpectPopup().CommitMessagePanel().Type(commitMessage).Confirm() - - t.Views().Commits(). - Lines( - Contains(commitMessage), - ) - - t.Views().Staging().IsFocused() - - // TODO: assert that the staging panel has been refreshed (it currently does not get correctly refreshed) - }, -}) diff --git a/pkg/integration/tests/config/custom_commands_in_per_repo_config.go b/pkg/integration/tests/config/custom_commands_in_per_repo_config.go deleted file mode 100644 index 81f8724aa72..00000000000 --- a/pkg/integration/tests/config/custom_commands_in_per_repo_config.go +++ /dev/null @@ -1,60 +0,0 @@ -package config - -import ( - "path/filepath" - - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CustomCommandsInPerRepoConfig = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Custom commands in per-repo config add to the global ones instead of replacing them", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - otherRepo, _ := filepath.Abs("../other") - cfg.GetAppState().RecentRepos = []string{otherRepo} - - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "X", - Context: "global", - Command: "printf 'global X' > file.txt", - }, - { - Key: "Y", - Context: "global", - Command: "printf 'global Y' > file.txt", - }, - } - }, - SetupRepo: func(shell *Shell) { - shell.CloneNonBare("other") - shell.CreateFile("../other/.git/lazygit.yml", ` -customCommands: - - key: Y - context: global - command: printf 'local Y' > file.txt - - key: Z - context: global - command: printf 'local Z' > file.txt`) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.GlobalPress(keys.Universal.OpenRecentRepos) - t.ExpectPopup().Menu().Title(Equals("Recent repositories")). - Lines( - Contains("other").IsSelected(), - Contains("Cancel"), - ).Confirm() - t.Views().Status().Content(Contains("other → master")) - - t.GlobalPress("X") - t.FileSystem().FileContent("../other/file.txt", Equals("global X")) - - t.GlobalPress("Y") - t.FileSystem().FileContent("../other/file.txt", Equals("local Y")) - - t.GlobalPress("Z") - t.FileSystem().FileContent("../other/file.txt", Equals("local Z")) - }, -}) diff --git a/pkg/integration/tests/config/negative_refspec.go b/pkg/integration/tests/config/negative_refspec.go deleted file mode 100644 index 46148a713fe..00000000000 --- a/pkg/integration/tests/config/negative_refspec.go +++ /dev/null @@ -1,24 +0,0 @@ -package config - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var NegativeRefspec = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Having a config with a negative refspec", - ExtraCmdArgs: []string{}, - SetupRepo: func(shell *Shell) { - shell. - SetConfig("remote.origin.fetch", "^refs/heads/test"). - CreateNCommits(2) - }, - SetupConfig: func(cfg *config.AppConfig) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // the failure case with an unpatched go-git is that no branches display - t.Views().Branches(). - Lines( - Contains("master"), - ) - }, -}) diff --git a/pkg/integration/tests/config/remote_named_star.go b/pkg/integration/tests/config/remote_named_star.go deleted file mode 100644 index 15bb45f2ed4..00000000000 --- a/pkg/integration/tests/config/remote_named_star.go +++ /dev/null @@ -1,26 +0,0 @@ -package config - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RemoteNamedStar = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Having a config remote.*", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell. - SetConfig("remote.*.prune", "true"). - CreateNCommits(2) - }, - SetupConfig: func(cfg *config.AppConfig) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // here we're just asserting that we haven't panicked upon starting lazygit - t.Views().Commits(). - Lines( - AnyString(), - AnyString(), - ) - }, -}) diff --git a/pkg/integration/tests/conflicts/filter.go b/pkg/integration/tests/conflicts/filter.go deleted file mode 100644 index 7b26df6b8ef..00000000000 --- a/pkg/integration/tests/conflicts/filter.go +++ /dev/null @@ -1,40 +0,0 @@ -package conflicts - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var Filter = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Ensures that when there are merge conflicts, the files panel only shows conflicted files", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shared.CreateMergeConflictFiles(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" UU file1"), - Equals(" UU file2"), - ). - Press(keys.Files.OpenStatusFilter). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("No filter")). - Confirm() - }). - Lines( - Equals("▼ /").IsSelected(), - Equals(" UU file1"), - Equals(" UU file2"), - // now we see the non-merge conflict file - Equals(" A file3"), - ) - }, -}) diff --git a/pkg/integration/tests/conflicts/merge_file_both.go b/pkg/integration/tests/conflicts/merge_file_both.go deleted file mode 100644 index 08357212530..00000000000 --- a/pkg/integration/tests/conflicts/merge_file_both.go +++ /dev/null @@ -1,77 +0,0 @@ -package conflicts - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -func testDataBoth() (original, current, incoming, final string) { - original = ` -1 -2 -3 -4 -5 -6 -` - current = ` -1a -2 -3 -4 -5a -6 -` - incoming = ` -1 -2 -3b -4 -5b -6 -` - final = ` -1a -2 -3b -4 -5a -5b -6 -` - return original, current, incoming, final -} - -var MergeFileBoth = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Conflicting file can be resolved to 'union' (both changes) version via merge-file", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - original, current, incoming, _ := testDataBoth() - shared.CreateMergeConflictFileForMergeFileTests(shell, original, current, incoming) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - _, _, _, expected := testDataBoth() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("file").IsSelected(), - ) - - t.GlobalPress(keys.Files.OpenMergeOptions) - - t.ExpectPopup().Menu(). - Title(Equals("Resolve merge conflicts")). - Select(Contains("Use both")). // merge-file --union - Confirm() - - t.Common().ContinueOnConflictsResolved("merge") - - t.Views().Files().IsEmpty() - - t.FileSystem().FileContent("file", Equals(expected)) - }, -}) diff --git a/pkg/integration/tests/conflicts/merge_file_current.go b/pkg/integration/tests/conflicts/merge_file_current.go deleted file mode 100644 index b809174465f..00000000000 --- a/pkg/integration/tests/conflicts/merge_file_current.go +++ /dev/null @@ -1,76 +0,0 @@ -package conflicts - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -func testDataCurrent() (original, current, incoming, final string) { - original = ` -1 -2 -3 -4 -5 -6 -` - current = ` -1 -2 -3 -4 -5a -6 -` - incoming = ` -1b -2 -3 -4 -5b -6 -` - final = ` -1b -2 -3 -4 -5a -6 -` - return original, current, incoming, final -} - -var MergeFileCurrent = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Conflicting file can be resolved to 'ours' (current changes) version via merge-file", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - original, current, incoming, _ := testDataCurrent() - shared.CreateMergeConflictFileForMergeFileTests(shell, original, current, incoming) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - _, _, _, expected := testDataCurrent() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("file").IsSelected(), - ) - - t.GlobalPress(keys.Files.OpenMergeOptions) - - t.ExpectPopup().Menu(). - Title(Equals("Resolve merge conflicts")). - Select(Contains("Use current changes")). // merge-file --ours - Confirm() - - t.Common().ContinueOnConflictsResolved("merge") - - t.Views().Files().IsEmpty() - - t.FileSystem().FileContent("file", Equals(expected)) - }, -}) diff --git a/pkg/integration/tests/conflicts/merge_file_incoming.go b/pkg/integration/tests/conflicts/merge_file_incoming.go deleted file mode 100644 index 8216b4a4d09..00000000000 --- a/pkg/integration/tests/conflicts/merge_file_incoming.go +++ /dev/null @@ -1,76 +0,0 @@ -package conflicts - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -func testDataIncoming() (original, current, incoming, final string) { - original = ` -1 -2 -3 -4 -5 -6 -` - current = ` -1a -2 -3 -4 -5a -6 -` - incoming = ` -1 -2 -3 -4 -5b -6 -` - final = ` -1a -2 -3 -4 -5b -6 -` - return original, current, incoming, final -} - -var MergeFileIncoming = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Conflicting file can be resolved to 'theirs' (incoming changes) version via merge-file", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - original, current, incoming, _ := testDataIncoming() - shared.CreateMergeConflictFileForMergeFileTests(shell, original, current, incoming) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - _, _, _, expected := testDataIncoming() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("file").IsSelected(), - ) - - t.GlobalPress(keys.Files.OpenMergeOptions) - - t.ExpectPopup().Menu(). - Title(Equals("Resolve merge conflicts")). - Select(Contains("Use incoming changes")). // merge-file --theirs - Confirm() - - t.Common().ContinueOnConflictsResolved("merge") - - t.Views().Files().IsEmpty() - - t.FileSystem().FileContent("file", Equals(expected)) - }, -}) diff --git a/pkg/integration/tests/conflicts/resolve_externally.go b/pkg/integration/tests/conflicts/resolve_externally.go deleted file mode 100644 index ab045f23397..00000000000 --- a/pkg/integration/tests/conflicts/resolve_externally.go +++ /dev/null @@ -1,33 +0,0 @@ -package conflicts - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var ResolveExternally = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Ensures that when merge conflicts are resolved outside of lazygit, lazygit prompts you to continue", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shared.CreateMergeConflictFile(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU file").IsSelected(), - ). - Tap(func() { - t.Shell().UpdateFile("file", "resolved content") - }). - Press(keys.Universal.Refresh) - - t.Common().ContinueOnConflictsResolved("merge") - - t.Views().Files(). - IsEmpty() - }, -}) diff --git a/pkg/integration/tests/conflicts/resolve_multiple_files.go b/pkg/integration/tests/conflicts/resolve_multiple_files.go deleted file mode 100644 index 7dd88e02c50..00000000000 --- a/pkg/integration/tests/conflicts/resolve_multiple_files.go +++ /dev/null @@ -1,56 +0,0 @@ -package conflicts - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var ResolveMultipleFiles = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Ensures that upon resolving conflicts for one file, the next file is selected", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shared.CreateMergeConflictFiles(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" UU file1"), - Equals(" UU file2"), - ). - SelectNextItem(). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - SelectedLines( - Contains("<<<<<<< HEAD"), - Contains("First Change"), - Contains("======="), - ). - PressPrimaryAction() - - t.Views().Files(). - IsFocused(). - Lines( - Equals("UU file2").IsSelected(), - ). - PressEnter() - - // coincidentally these files have the same conflict - t.Views().MergeConflicts(). - IsFocused(). - SelectedLines( - Contains("<<<<<<< HEAD"), - Contains("First Change"), - Contains("======="), - ). - PressPrimaryAction() - - t.Common().ContinueOnConflictsResolved("merge") - }, -}) diff --git a/pkg/integration/tests/conflicts/resolve_no_auto_stage.go b/pkg/integration/tests/conflicts/resolve_no_auto_stage.go deleted file mode 100644 index 129ae3fe460..00000000000 --- a/pkg/integration/tests/conflicts/resolve_no_auto_stage.go +++ /dev/null @@ -1,84 +0,0 @@ -package conflicts - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var ResolveNoAutoStage = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Resolving conflicts without auto-staging", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.AutoStageResolvedConflicts = false - }, - SetupRepo: func(shell *Shell) { - shared.CreateMergeConflictFiles(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" UU file1"), - Equals(" UU file2"), - ). - SelectNextItem(). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - SelectedLines( - Contains("<<<<<<< HEAD"), - Contains("First Change"), - Contains("======="), - ). - PressPrimaryAction() - - t.Views().Files(). - IsFocused(). - // Resolving the conflict didn't auto-stage it - Lines( - Equals("▼ /"), - Equals(" UU file1").IsSelected(), - Equals(" UU file2"), - ). - // So do that manually - PressPrimaryAction(). - Lines( - Equals("UU file2").IsSelected(), - ). - // Trying to stage a file that still has conflicts is not allowed: - PressPrimaryAction(). - Tap(func() { - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Contains("Cannot stage/unstage directory containing files with inline merge conflicts.")). - Confirm() - }). - PressEnter() - - // coincidentally these files have the same conflict - t.Views().MergeConflicts(). - IsFocused(). - SelectedLines( - Contains("<<<<<<< HEAD"), - Contains("First Change"), - Contains("======="), - ). - PressPrimaryAction() - - t.Views().Files(). - IsFocused(). - // Again, resolving the conflict didn't auto-stage it - Lines( - Equals("UU file2").IsSelected(), - ). - // Doing that manually now works: - PressPrimaryAction(). - Lines( - Equals("A file3").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/conflicts/resolve_non_textual_conflicts.go b/pkg/integration/tests/conflicts/resolve_non_textual_conflicts.go deleted file mode 100644 index 8601d068091..00000000000 --- a/pkg/integration/tests/conflicts/resolve_non_textual_conflicts.go +++ /dev/null @@ -1,105 +0,0 @@ -package conflicts - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ResolveNonTextualConflicts = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Resolve non-textual merge conflicts (e.g. one side modified, the other side deleted)", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.RunShellCommand(`echo test1 > both-deleted1.txt`) - shell.RunShellCommand(`echo test2 > both-deleted2.txt`) - shell.RunShellCommand(`git checkout -b conflict && git add both-deleted1.txt both-deleted2.txt`) - shell.RunShellCommand(`echo haha1 > deleted-them1.txt && git add deleted-them1.txt`) - shell.RunShellCommand(`echo haha2 > deleted-them2.txt && git add deleted-them2.txt`) - shell.RunShellCommand(`echo haha1 > deleted-us1.txt && git add deleted-us1.txt`) - shell.RunShellCommand(`echo haha2 > deleted-us2.txt && git add deleted-us2.txt`) - shell.RunShellCommand(`git commit -m one`) - - // stuff on other branch - shell.RunShellCommand(`git branch conflict_second`) - shell.RunShellCommand(`git mv both-deleted1.txt added-them-changed-us1.txt`) - shell.RunShellCommand(`git mv both-deleted2.txt added-them-changed-us2.txt`) - shell.RunShellCommand(`git rm deleted-them1.txt deleted-them2.txt`) - shell.RunShellCommand(`echo modded1 > deleted-us1.txt && git add deleted-us1.txt`) - shell.RunShellCommand(`echo modded2 > deleted-us2.txt && git add deleted-us2.txt`) - shell.RunShellCommand(`git commit -m "two"`) - - // stuff on our branch - shell.RunShellCommand(`git checkout conflict_second`) - shell.RunShellCommand(`git mv both-deleted1.txt changed-them-added-us1.txt`) - shell.RunShellCommand(`git mv both-deleted2.txt changed-them-added-us2.txt`) - shell.RunShellCommand(`echo modded1 > deleted-them1.txt && git add deleted-them1.txt`) - shell.RunShellCommand(`echo modded2 > deleted-them2.txt && git add deleted-them2.txt`) - shell.RunShellCommand(`git rm deleted-us1.txt deleted-us2.txt`) - shell.RunShellCommand(`git commit -m "three"`) - shell.RunShellCommand(`git reset --hard conflict_second`) - shell.RunCommandExpectError([]string{"git", "merge", "conflict"}) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - resolve := func(filename string, menuChoice string) { - t.Views().Files(). - NavigateToLine(Contains(filename)). - Tap(func() { - t.Views().Main().Content(Contains("Conflict:")) - }). - Press(keys.Universal.GoInto). - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Merge conflicts")). - Select(Contains(menuChoice)). - Confirm() - }) - } - - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" UA added-them-changed-us1.txt"), - Equals(" UA added-them-changed-us2.txt"), - Equals(" DD both-deleted1.txt"), - Equals(" DD both-deleted2.txt"), - Equals(" AU changed-them-added-us1.txt"), - Equals(" AU changed-them-added-us2.txt"), - Equals(" UD deleted-them1.txt"), - Equals(" UD deleted-them2.txt"), - Equals(" DU deleted-us1.txt"), - Equals(" DU deleted-us2.txt"), - ). - Tap(func() { - resolve("added-them-changed-us1.txt", "Delete file") - resolve("added-them-changed-us2.txt", "Keep file") - resolve("both-deleted1.txt", "Delete file") - resolve("both-deleted2.txt", "Delete file") - resolve("changed-them-added-us1.txt", "Delete file") - resolve("changed-them-added-us2.txt", "Keep file") - resolve("deleted-them1.txt", "Delete file") - resolve("deleted-them2.txt", "Keep file") - resolve("deleted-us1.txt", "Delete file") - resolve("deleted-us2.txt", "Keep file") - }). - Lines( - Equals("▼ /"), - Equals(" A added-them-changed-us2.txt"), - Equals(" D changed-them-added-us1.txt"), - Equals(" D deleted-them1.txt"), - Equals(" A deleted-us2.txt"), - ) - - t.FileSystem(). - PathNotPresent("added-them-changed-us1.txt"). - FileContent("added-them-changed-us2.txt", Equals("test2\n")). - PathNotPresent("both-deleted1.txt"). - PathNotPresent("both-deleted2.txt"). - PathNotPresent("changed-them-added-us1.txt"). - FileContent("changed-them-added-us2.txt", Equals("test2\n")). - PathNotPresent("deleted-them1.txt"). - FileContent("deleted-them2.txt", Equals("modded2\n")). - PathNotPresent("deleted-us1.txt"). - FileContent("deleted-us2.txt", Equals("modded2\n")) - }, -}) diff --git a/pkg/integration/tests/conflicts/resolve_without_trailing_lf.go b/pkg/integration/tests/conflicts/resolve_without_trailing_lf.go deleted file mode 100644 index 3deafb28852..00000000000 --- a/pkg/integration/tests/conflicts/resolve_without_trailing_lf.go +++ /dev/null @@ -1,57 +0,0 @@ -package conflicts - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ResolveWithoutTrailingLf = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Regression test for resolving a merge conflict when the file doesn't have a trailing newline", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - NewBranch("branch1"). - CreateFileAndAdd("file", "a\n\nno eol"). - Commit("initial commit"). - UpdateFileAndAdd("file", "a1\n\nno eol"). - Commit("commit on branch1"). - NewBranchFrom("branch2", "HEAD^"). - UpdateFileAndAdd("file", "a2\n\nno eol"). - Commit("commit on branch2"). - Checkout("branch1"). - RunCommandExpectError([]string{"git", "merge", "--no-edit", "branch2"}) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU file").IsSelected(), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - SelectedLines( - Contains("<<<<<<< HEAD"), - Contains("a1"), - Contains("======="), - ). - SelectNextItem(). - PressPrimaryAction() - - t.ExpectPopup().Alert(). - Title(Equals("Continue")). - Content(Contains("All merge conflicts resolved. Continue the merge?")). - Cancel() - - t.Views().Files(). - Focus(). - Lines( - Contains("M file").IsSelected(), - ) - - t.Views().Main().Content(Contains("-a1\n+a2\n").DoesNotContain("-no eol")) - }, -}) diff --git a/pkg/integration/tests/conflicts/undo_choose_hunk.go b/pkg/integration/tests/conflicts/undo_choose_hunk.go deleted file mode 100644 index c03f79cf68a..00000000000 --- a/pkg/integration/tests/conflicts/undo_choose_hunk.go +++ /dev/null @@ -1,41 +0,0 @@ -package conflicts - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var UndoChooseHunk = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Chooses a hunk when resolving a merge conflict and then undoes the choice", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shared.CreateMergeConflictFileMultiple(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU file").IsSelected(), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - Content(Contains("<<<<<<< HEAD\nFirst Change")). - // explicitly asserting on the selection because sometimes the content renders - // before the selection is ready for user input - SelectedLines( - Contains("<<<<<<< HEAD"), - Contains("First Change"), - Contains("======="), - ). - PressPrimaryAction(). - // choosing the first hunk - Content(DoesNotContain("<<<<<<< HEAD\nFirst Change")). - Press(keys.Universal.Undo). - Content(Contains("<<<<<<< HEAD\nFirst Change")) - }, -}) diff --git a/pkg/integration/tests/custom_commands/access_commit_properties.go b/pkg/integration/tests/custom_commands/access_commit_properties.go deleted file mode 100644 index 22d1d0631d5..00000000000 --- a/pkg/integration/tests/custom_commands/access_commit_properties.go +++ /dev/null @@ -1,37 +0,0 @@ -package custom_commands - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AccessCommitProperties = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Run a command that accesses properties of a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("my change") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "X", - Context: "commits", - Command: "printf '%s\n%s\n%s' '{{ .SelectedLocalCommit.Name }}' '{{ .SelectedLocalCommit.Hash }}' '{{ .SelectedLocalCommit.Sha }}' > file.txt", - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("my change").IsSelected(), - ). - Press("X") - - hash := t.Git().GetCommitHash("HEAD") - t.FileSystem().FileContent("file.txt", Equals(fmt.Sprintf("my change\n%s\n%s", hash, hash))) - }, -}) diff --git a/pkg/integration/tests/custom_commands/basic_command.go b/pkg/integration/tests/custom_commands/basic_command.go deleted file mode 100644 index 10a9058b7e7..00000000000 --- a/pkg/integration/tests/custom_commands/basic_command.go +++ /dev/null @@ -1,33 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var BasicCommand = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Using a custom command to create a new file", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("blah") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "a", - Context: "files", - Command: "touch myfile", - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsEmpty(). - IsFocused(). - Press("a"). - Lines( - Contains("myfile"), - ) - }, -}) diff --git a/pkg/integration/tests/custom_commands/check_for_conflicts.go b/pkg/integration/tests/custom_commands/check_for_conflicts.go deleted file mode 100644 index d3376b45642..00000000000 --- a/pkg/integration/tests/custom_commands/check_for_conflicts.go +++ /dev/null @@ -1,42 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var CheckForConflicts = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Run a command and check for conflicts after", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shared.MergeConflictsSetup(shell) - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "m", - Context: "localBranches", - Command: "git merge {{ .SelectedLocalBranch.Name | quote }}", - After: &config.CustomCommandAfterHook{ - CheckForConflicts: true, - }, - }, - } - - cfg.GetUserConfig().Git.LocalBranchSortOrder = "recency" - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - TopLines( - Contains("first-change-branch"), - Contains("second-change-branch"), - ). - NavigateToLine(Contains("second-change-branch")). - Press("m") - - t.Common().AcknowledgeConflicts() - }, -}) diff --git a/pkg/integration/tests/custom_commands/custom_commands_submenu.go b/pkg/integration/tests/custom_commands/custom_commands_submenu.go deleted file mode 100644 index a8d13cf7336..00000000000 --- a/pkg/integration/tests/custom_commands/custom_commands_submenu.go +++ /dev/null @@ -1,76 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CustomCommandsSubmenu = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Using custom commands from a custom commands menu", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) {}, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "x", - Description: "My Custom Commands", - CommandMenu: []config.CustomCommand{ - { - Key: "1", - Context: "global", - Command: "touch myfile-global", - }, - { - Key: "2", - Context: "files", - Command: "touch myfile-files", - }, - { - Key: "3", - Context: "commits", - Command: "touch myfile-commits", - }, - }, - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Focus(). - IsEmpty(). - Press("x"). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("My Custom Commands")). - Lines( - Contains("1 touch myfile-global"), - Contains("2 touch myfile-files"), - ). - Select(Contains("touch myfile-files")).Confirm() - }). - Lines( - Contains("myfile-files"), - ) - - t.Views().Commits(). - Focus(). - Press("x"). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("My Custom Commands")). - Lines( - Contains("1 touch myfile-global"), - Contains("3 touch myfile-commits"), - ) - t.GlobalPress("3") - }) - - t.Views().Files(). - Lines( - Equals("▼ /"), - Contains("myfile-commits"), - Contains("myfile-files"), - ) - }, -}) diff --git a/pkg/integration/tests/custom_commands/form_prompts.go b/pkg/integration/tests/custom_commands/form_prompts.go deleted file mode 100644 index ccb2339dee9..00000000000 --- a/pkg/integration/tests/custom_commands/form_prompts.go +++ /dev/null @@ -1,80 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FormPrompts = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Using a custom command referring prompt responses by name", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("blah") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "a", - Context: "files", - Command: `echo {{.Form.FileContent | quote}} > {{.Form.FileName | quote}}`, - Prompts: []config.CustomCommandPrompt{ - { - Key: "FileName", - Type: "input", - Title: "Enter a file name", - }, - { - Key: "FileContent", - Type: "menu", - Title: "Choose file content", - Options: []config.CustomCommandMenuOption{ - { - Name: "foo", - Description: "Foo", - Value: "FOO", - }, - { - Name: "bar", - Description: "Bar", - Value: `"BAR"`, - }, - { - Name: "baz", - Description: "Baz", - Value: "BAZ", - }, - }, - }, - { - Type: "confirm", - Title: "Are you sure?", - Body: "Are you REALLY sure you want to make this file? Up to you buddy.", - }, - }, - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsEmpty(). - IsFocused(). - Press("a") - - t.ExpectPopup().Prompt().Title(Equals("Enter a file name")).Type("my file").Confirm() - - t.ExpectPopup().Menu().Title(Equals("Choose file content")).Select(Contains("bar")).Confirm() - - t.ExpectPopup().Confirmation(). - Title(Equals("Are you sure?")). - Content(Equals("Are you REALLY sure you want to make this file? Up to you buddy.")). - Confirm() - - t.Views().Files(). - Lines( - Contains("my file").IsSelected(), - ) - - t.Views().Main().Content(Contains(`"BAR"`)) - }, -}) diff --git a/pkg/integration/tests/custom_commands/global_context.go b/pkg/integration/tests/custom_commands/global_context.go deleted file mode 100644 index 82ef5301011..00000000000 --- a/pkg/integration/tests/custom_commands/global_context.go +++ /dev/null @@ -1,60 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var GlobalContext = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Ensure global context works", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("my change") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "X", - Context: "global", - Command: "touch myfile", - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // commits - t.Views().Commits(). - Focus(). - Press("X") - - t.Views().Files(). - Focus(). - Lines(Contains("myfile")) - - t.Shell().DeleteFile("myfile") - t.GlobalPress(keys.Files.RefreshFiles) - - // branches - t.Views().Branches(). - Focus(). - Press("X") - - t.Views().Files(). - Focus(). - Lines(Contains("myfile")) - - t.Shell().DeleteFile("myfile") - t.GlobalPress(keys.Files.RefreshFiles) - - // files - t.Views().Files(). - Focus(). - Press("X") - - t.Views().Files(). - Focus(). - Lines(Contains("myfile")) - - t.Shell().DeleteFile("myfile") - }, -}) diff --git a/pkg/integration/tests/custom_commands/menu_from_command.go b/pkg/integration/tests/custom_commands/menu_from_command.go deleted file mode 100644 index 10b8192ba8d..00000000000 --- a/pkg/integration/tests/custom_commands/menu_from_command.go +++ /dev/null @@ -1,65 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// NOTE: we're getting a weird offset in the popup prompt for some reason. Not sure what's behind that. - -var MenuFromCommand = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Using menuFromCommand prompt type", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("foo"). - EmptyCommit("bar"). - EmptyCommit("baz"). - NewBranch("feature/foo") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "a", - Context: "localBranches", - Command: `echo "{{index .PromptResponses 0}} {{index .PromptResponses 1}} {{ .SelectedLocalBranch.Name }}" > output.txt`, - Prompts: []config.CustomCommandPrompt{ - { - Type: "menuFromCommand", - Title: "Choose commit message", - Command: `git log --oneline --pretty=%B`, - Filter: `(?P.*)`, - ValueFormat: `{{ .commit_message }}`, - LabelFormat: `{{ .commit_message | yellow }}`, - }, - { - Type: "input", - Title: "Description", - InitialValue: `{{ if .SelectedLocalBranch.Name }}Branch: #{{ .SelectedLocalBranch.Name }}{{end}}`, - }, - }, - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsEmpty() - - t.Views().Branches(). - Focus(). - Press("a") - - t.ExpectPopup().Menu().Title(Equals("Choose commit message")).Select(Contains("bar")).Confirm() - - t.ExpectPopup().Prompt().Title(Equals("Description")).Type(" my branch").Confirm() - - t.Views().Files(). - Focus(). - Lines( - Contains("output.txt").IsSelected(), - ) - - t.Views().Main().Content(Contains("bar Branch: #feature/foo my branch feature/foo")) - }, -}) diff --git a/pkg/integration/tests/custom_commands/menu_from_commands_output.go b/pkg/integration/tests/custom_commands/menu_from_commands_output.go deleted file mode 100644 index 591daa5af12..00000000000 --- a/pkg/integration/tests/custom_commands/menu_from_commands_output.go +++ /dev/null @@ -1,60 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MenuFromCommandsOutput = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Using prompt response in menuFromCommand entries", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("foo"). - NewBranch("feature/foo"). - EmptyCommit("bar"). - NewBranch("feature/bar"). - EmptyCommit("baz") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "a", - Context: "localBranches", - Command: "git checkout {{ index .PromptResponses 1 }}", - Prompts: []config.CustomCommandPrompt{ - { - Type: "input", - Title: "Which git command do you want to run?", - InitialValue: "branch", - }, - { - Type: "menuFromCommand", - Title: "Branch:", - Command: `git {{ index .PromptResponses 0 }} --format='%(refname:short)'`, - Filter: "(?P.*)", - ValueFormat: `{{ .branch }}`, - LabelFormat: `{{ .branch | green }}`, - }, - }, - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Git().CurrentBranchName("feature/bar") - - t.Views().Branches(). - Focus(). - Press("a") - - t.ExpectPopup().Prompt(). - Title(Equals("Which git command do you want to run?")). - InitialText(Equals("branch")). - Confirm() - - t.ExpectPopup().Menu().Title(Equals("Branch:")).Select(Equals("master")).Confirm() - - t.Git().CurrentBranchName("master") - }, -}) diff --git a/pkg/integration/tests/custom_commands/multiple_contexts.go b/pkg/integration/tests/custom_commands/multiple_contexts.go deleted file mode 100644 index 61d46775b00..00000000000 --- a/pkg/integration/tests/custom_commands/multiple_contexts.go +++ /dev/null @@ -1,57 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MultipleContexts = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Test that multiple contexts works", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("my change") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "X", - Context: "commits, reflogCommits", - Command: "touch myfile", - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // commits - t.Views().Commits(). - Focus(). - Press("X") - - t.Views().Files(). - Focus(). - Lines(Contains("myfile")) - - t.Shell().DeleteFile("myfile") - t.GlobalPress(keys.Files.RefreshFiles) - - // branches - t.Views().Branches(). - Focus(). - Press("X") - - t.Views().Files(). - Focus(). - IsEmpty() - - // files - t.Views().ReflogCommits(). - Focus(). - Press("X") - - t.Views().Files(). - Focus(). - Lines(Contains("myfile")) - - t.Shell().DeleteFile("myfile") - }, -}) diff --git a/pkg/integration/tests/custom_commands/multiple_prompts.go b/pkg/integration/tests/custom_commands/multiple_prompts.go deleted file mode 100644 index b40aa77f2ca..00000000000 --- a/pkg/integration/tests/custom_commands/multiple_prompts.go +++ /dev/null @@ -1,79 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MultiplePrompts = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Using a custom command with multiple prompts", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("blah") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "a", - Context: "files", - Command: `echo "{{index .PromptResponses 1}}" > {{index .PromptResponses 0}}`, - Prompts: []config.CustomCommandPrompt{ - { - Type: "input", - Title: "Enter a file name", - }, - { - Type: "menu", - Title: "Choose file content", - Options: []config.CustomCommandMenuOption{ - { - Name: "foo", - Description: "Foo", - Value: "FOO", - }, - { - Name: "bar", - Description: "Bar", - Value: "BAR", - }, - { - Name: "baz", - Description: "Baz", - Value: "BAZ", - }, - }, - }, - { - Type: "confirm", - Title: "Are you sure?", - Body: "Are you REALLY sure you want to make this file? Up to you buddy.", - }, - }, - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsEmpty(). - IsFocused(). - Press("a") - - t.ExpectPopup().Prompt().Title(Equals("Enter a file name")).Type("myfile").Confirm() - - t.ExpectPopup().Menu().Title(Equals("Choose file content")).Select(Contains("bar")).Confirm() - - t.ExpectPopup().Confirmation(). - Title(Equals("Are you sure?")). - Content(Equals("Are you REALLY sure you want to make this file? Up to you buddy.")). - Confirm() - - t.Views().Files(). - Focus(). - Lines( - Contains("myfile").IsSelected(), - ) - - t.Views().Main().Content(Contains("BAR")) - }, -}) diff --git a/pkg/integration/tests/custom_commands/run_command.go b/pkg/integration/tests/custom_commands/run_command.go deleted file mode 100644 index 107b9e28503..00000000000 --- a/pkg/integration/tests/custom_commands/run_command.go +++ /dev/null @@ -1,42 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RunCommand = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Using a custom command that uses runCommand template function in a prompt step", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("blah") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "a", - Context: "localBranches", - Command: `git checkout {{.Form.Branch}}`, - Prompts: []config.CustomCommandPrompt{ - { - Key: "Branch", - Type: "input", - Title: "Enter a branch name", - InitialValue: "myprefix/{{ runCommand \"echo dynamic\" }}/", - }, - }, - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Press("a") - - t.ExpectPopup().Prompt(). - Title(Equals("Enter a branch name")). - InitialText(Contains("myprefix/dynamic/")). - Confirm() - }, -}) diff --git a/pkg/integration/tests/custom_commands/selected_commit.go b/pkg/integration/tests/custom_commands/selected_commit.go deleted file mode 100644 index 6288634f1f9..00000000000 --- a/pkg/integration/tests/custom_commands/selected_commit.go +++ /dev/null @@ -1,67 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SelectedCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Use the {{ .SelectedCommit }} template variable in different contexts", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(3) - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "X", - Context: "global", - Command: "printf '%s' '{{ .SelectedCommit.Name }}' > file.txt", - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // Select different commits in each of the commit views - t.Views().Commits().Focus(). - NavigateToLine(Contains("commit 01")) - t.Views().ReflogCommits().Focus(). - NavigateToLine(Contains("commit 02")) - t.Views().Branches().Focus(). - Lines(Contains("master").IsSelected()). - PressEnter() - t.Views().SubCommits().IsFocused(). - NavigateToLine(Contains("commit 03")) - - // SubCommits - t.GlobalPress("X") - t.FileSystem().FileContent("file.txt", Equals("commit 03")) - - t.Views().SubCommits().PressEnter() - t.GlobalPress("X") - t.FileSystem().FileContent("file.txt", Equals("commit 03")) - - // ReflogCommits - t.Views().ReflogCommits().Focus() - t.GlobalPress("X") - t.FileSystem().FileContent("file.txt", Equals("commit: commit 02")) - - t.Views().ReflogCommits().PressEnter() - t.GlobalPress("X") - t.FileSystem().FileContent("file.txt", Equals("commit: commit 02")) - - // LocalCommits - t.Views().Commits().Focus() - t.GlobalPress("X") - t.FileSystem().FileContent("file.txt", Equals("commit 01")) - - t.Views().Commits().PressEnter() - t.GlobalPress("X") - t.FileSystem().FileContent("file.txt", Equals("commit 01")) - - // None of these - t.Views().Files().Focus() - t.GlobalPress("X") - t.FileSystem().FileContent("file.txt", Equals("commit 01")) - }, -}) diff --git a/pkg/integration/tests/custom_commands/selected_commit_range.go b/pkg/integration/tests/custom_commands/selected_commit_range.go deleted file mode 100644 index 1a4b3087c1e..00000000000 --- a/pkg/integration/tests/custom_commands/selected_commit_range.go +++ /dev/null @@ -1,41 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SelectedCommitRange = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Use the {{ .SelectedCommitRange }} template variable", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(3) - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "X", - Context: "global", - Command: `git log --format="%s" {{.SelectedCommitRange.From}}^..{{.SelectedCommitRange.To}} > file.txt`, - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Focus(). - Lines( - Contains("commit 03").IsSelected(), - Contains("commit 02"), - Contains("commit 01"), - ) - - t.GlobalPress("X") - t.FileSystem().FileContent("file.txt", Equals("commit 03\n")) - - t.Views().Commits().Focus(). - Press(keys.Universal.RangeSelectDown) - - t.GlobalPress("X") - t.FileSystem().FileContent("file.txt", Equals("commit 03\ncommit 02\n")) - }, -}) diff --git a/pkg/integration/tests/custom_commands/selected_path.go b/pkg/integration/tests/custom_commands/selected_path.go deleted file mode 100644 index 9dc63ed4333..00000000000 --- a/pkg/integration/tests/custom_commands/selected_path.go +++ /dev/null @@ -1,44 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SelectedPath = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Use the {{ .SelectedPath }} template variable in different contexts", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.CreateDir("folder1") - shell.CreateFileAndAdd("folder1/file1", "") - shell.Commit("commit") - shell.CreateDir("folder2") - shell.CreateFile("folder2/file2", "") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "X", - Context: "global", - Command: "printf '%s' '{{ .SelectedPath }}' > file.txt", - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Focus(). - NavigateToLine(Contains("file2")) - t.GlobalPress("X") - t.FileSystem().FileContent("file.txt", Equals("folder2/file2")) - - t.Views().Commits(). - Focus(). - PressEnter() - t.Views().CommitFiles(). - IsFocused(). - NavigateToLine(Contains("file1")) - t.GlobalPress("X") - t.FileSystem().FileContent("file.txt", Equals("folder1/file1")) - }, -}) diff --git a/pkg/integration/tests/custom_commands/show_output_in_panel.go b/pkg/integration/tests/custom_commands/show_output_in_panel.go deleted file mode 100644 index 9fcab1be309..00000000000 --- a/pkg/integration/tests/custom_commands/show_output_in_panel.go +++ /dev/null @@ -1,57 +0,0 @@ -package custom_commands - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ShowOutputInPanel = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Run a command and show the output in a panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("my change") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "X", - Context: "commits", - Command: "printf '%s' '{{ .SelectedLocalCommit.Name }}'", - Output: "popup", - }, - { - Key: "Y", - Context: "commits", - Command: "printf '%s' '{{ .SelectedLocalCommit.Name }}'", - Output: "popup", - OutputTitle: "Subject of commit {{ .SelectedLocalCommit.Hash }}", - }, - } - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("my change").IsSelected(), - ). - Press("X") - - t.ExpectPopup().Alert(). - // Uses cmd string as title if no outputTitle is provided - Title(Equals("printf '%s' 'my change'")). - Content(Equals("my change")). - Confirm() - - t.Views().Commits(). - Press("Y") - - hash := t.Git().GetCommitHash("HEAD") - t.ExpectPopup().Alert(). - // Uses provided outputTitle with template fields resolved - Title(Equals(fmt.Sprintf("Subject of commit %s", hash))). - Content(Equals("my change")) - }, -}) diff --git a/pkg/integration/tests/custom_commands/suggestions_command.go b/pkg/integration/tests/custom_commands/suggestions_command.go deleted file mode 100644 index 51bbc2c6522..00000000000 --- a/pkg/integration/tests/custom_commands/suggestions_command.go +++ /dev/null @@ -1,68 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SuggestionsCommand = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Using a custom command that uses a suggestions command in a prompt step", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.NewBranch("branch-one") - shell.EmptyCommit("blah") - shell.NewBranch("branch-two") - shell.EmptyCommit("blah") - shell.NewBranch("branch-three") - shell.EmptyCommit("blah") - shell.NewBranch("branch-four") - shell.EmptyCommit("blah") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "a", - Context: "localBranches", - Command: `git checkout {{.Form.Branch}}`, - Prompts: []config.CustomCommandPrompt{ - { - Key: "Branch", - Type: "input", - Title: "Enter a branch name", - Suggestions: config.CustomCommandSuggestions{ - Command: "git branch --format='%(refname:short)'", - }, - }, - }, - }, - } - - cfg.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("branch-four").IsSelected(), - Contains("branch-one"), - Contains("branch-three"), - Contains("branch-two"), - ). - Press("a") - - t.ExpectPopup().Prompt(). - Title(Equals("Enter a branch name")). - Type("three"). - SuggestionLines(Contains("branch-three")). - ConfirmFirstSuggestion() - - t.Views().Branches(). - Lines( - Contains("branch-three"), - Contains("branch-four").IsSelected(), - Contains("branch-one"), - Contains("branch-two"), - ) - }, -}) diff --git a/pkg/integration/tests/custom_commands/suggestions_preset.go b/pkg/integration/tests/custom_commands/suggestions_preset.go deleted file mode 100644 index 891ebf725ce..00000000000 --- a/pkg/integration/tests/custom_commands/suggestions_preset.go +++ /dev/null @@ -1,68 +0,0 @@ -package custom_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SuggestionsPreset = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Using a custom command that uses a suggestions preset in a prompt step", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.NewBranch("branch-one") - shell.EmptyCommit("blah") - shell.NewBranch("branch-two") - shell.EmptyCommit("blah") - shell.NewBranch("branch-three") - shell.EmptyCommit("blah") - shell.NewBranch("branch-four") - shell.EmptyCommit("blah") - }, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "a", - Context: "localBranches", - Command: `git checkout {{.Form.Branch}}`, - Prompts: []config.CustomCommandPrompt{ - { - Key: "Branch", - Type: "input", - Title: "Enter a branch name", - Suggestions: config.CustomCommandSuggestions{ - Preset: "branches", - }, - }, - }, - }, - } - - cfg.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("branch-four").IsSelected(), - Contains("branch-one"), - Contains("branch-three"), - Contains("branch-two"), - ). - Press("a") - - t.ExpectPopup().Prompt(). - Title(Equals("Enter a branch name")). - Type("three"). - SuggestionLines(Contains("branch-three")). - ConfirmFirstSuggestion() - - t.Views().Branches(). - Lines( - Contains("branch-three"), - Contains("branch-four").IsSelected(), - Contains("branch-one"), - Contains("branch-two"), - ) - }, -}) diff --git a/pkg/integration/tests/demo/amend_old_commit.go b/pkg/integration/tests/demo/amend_old_commit.go deleted file mode 100644 index de76aee211d..00000000000 --- a/pkg/integration/tests/demo/amend_old_commit.go +++ /dev/null @@ -1,60 +0,0 @@ -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) { - setDefaultDemoConfig(config) - config.GetUserConfig().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/bisect.go b/pkg/integration/tests/demo/bisect.go deleted file mode 100644 index 370464af391..00000000000 --- a/pkg/integration/tests/demo/bisect.go +++ /dev/null @@ -1,76 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Bisect = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Interactive rebase", - ExtraCmdArgs: []string{"log", "--screen-mode=full"}, - Skip: false, - IsDemo: true, - SetupConfig: func(config *config.AppConfig) { - setDefaultDemoConfig(config) - }, - SetupRepo: func(shell *Shell) { - shell.CreateFile("my-file.txt", "myfile content") - shell.CreateFile("my-other-file.rb", "my-other-file content") - - shell.CreateNCommitsWithRandomMessages(60) - shell.NewBranch("feature/demo") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("feature/demo", "origin/feature/demo") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("Git bisect") - t.Wait(1000) - - markCommitAsBad := func() { - t.Views().Commits(). - Press(keys.Commits.ViewBisectOptions) - - t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as bad`)).Confirm() - } - - markCommitAsGood := func() { - t.Views().Commits(). - Press(keys.Commits.ViewBisectOptions) - - t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as good`)).Confirm() - } - - t.Views().Commits(). - IsFocused(). - Tap(func() { - markCommitAsBad() - - t.Views().Information().Content(Contains("Bisecting")) - }). - SelectedLine(Contains("<-- bad")). - NavigateToLine(Contains("Add TypeScript types to User module")). - Tap(markCommitAsGood). - SelectedLine(Contains("Add loading indicators to improve UX").Contains("<-- current")). - Tap(markCommitAsBad). - SelectedLine(Contains("Fix broken links on the help page").Contains("<-- current")). - Tap(markCommitAsGood). - SelectedLine(Contains("Add end-to-end tests for checkout flow").Contains("<-- current")). - Tap(markCommitAsBad). - Tap(func() { - t.Wait(2000) - - t.ExpectPopup().Alert().Title(Equals("Bisect complete")).Content(MatchesRegexp("(?s).*Do you want to reset")).Confirm() - }). - SetCaptionPrefix("Inspect problematic commit"). - Wait(500). - Press(keys.Universal.PrevScreenMode). - IsFocused(). - Content(Contains("Add end-to-end tests for checkout flow")). - Wait(500). - PressEnter() - - t.Views().Information().Content(DoesNotContain("Bisecting")) - }, -}) diff --git a/pkg/integration/tests/demo/cherry_pick.go b/pkg/integration/tests/demo/cherry_pick.go deleted file mode 100644 index a29f34bb954..00000000000 --- a/pkg/integration/tests/demo/cherry_pick.go +++ /dev/null @@ -1,88 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cherry pick", - ExtraCmdArgs: []string{}, - Skip: false, - IsDemo: true, - SetupConfig: func(config *config.AppConfig) { - setDefaultDemoConfig(config) - }, - SetupRepo: func(shell *Shell) { - shell.CreateNCommitsWithRandomMessages(50) - - shell. - EmptyCommit("Fix bug in timezone conversion."). - NewBranch("hotfix/fix-bug"). - NewBranch("feature/user-module"). - Checkout("hotfix/fix-bug"). - EmptyCommit("Integrate support for markdown in user posts"). - EmptyCommit("Remove unused code and libraries"). - Checkout("feature/user-module"). - EmptyCommit("Handle session timeout gracefully"). - EmptyCommit("Add Webpack for asset bundling"). - Checkout("hotfix/fix-bug") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("Cherry pick commits from another branch") - t.Wait(1000) - - t.Views().Branches(). - Focus(). - Lines( - Contains("hotfix/fix-bug"), - Contains("feature/user-module"), - Contains("master"), - ). - SelectNextItem(). - Wait(300). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - TopLines( - Contains("Add Webpack for asset bundling").IsSelected(), - Contains("Handle session timeout gracefully"), - Contains("Fix bug in timezone conversion."), - ). - Press(keys.Commits.CherryPickCopy). - Tap(func() { - t.Views().Information().Content(Contains("1 commit copied")) - }). - SelectNextItem(). - Press(keys.Commits.CherryPickCopy) - - t.Views().Information().Content(Contains("2 commits copied")) - - t.Views().Commits(). - Focus(). - TopLines( - Contains("Remove unused code and libraries").IsSelected(), - Contains("Integrate support for markdown in user posts"), - Contains("Fix bug in timezone conversion."), - ). - Press(keys.Commits.PasteCommits). - Tap(func() { - t.Wait(1000) - t.ExpectPopup().Alert(). - Title(Equals("Cherry-pick")). - Content(Contains("Are you sure you want to cherry-pick the 2 copied commit(s) onto this branch?")). - Confirm() - }). - TopLines( - Contains("Add Webpack for asset bundling"), - Contains("Handle session timeout gracefully"), - Contains("Remove unused code and libraries"), - Contains("Integrate support for markdown in user posts"), - Contains("Fix bug in timezone conversion."), - ). - Tap(func() { - t.Views().Information().Content(DoesNotContain("commits copied")) - }) - }, -}) diff --git a/pkg/integration/tests/demo/commit_and_push.go b/pkg/integration/tests/demo/commit_and_push.go deleted file mode 100644 index 360d685ce39..00000000000 --- a/pkg/integration/tests/demo/commit_and_push.go +++ /dev/null @@ -1,55 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CommitAndPush = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Make a commit and push", - ExtraCmdArgs: []string{}, - Skip: false, - IsDemo: true, - SetupConfig: func(config *config.AppConfig) { - setDefaultDemoConfig(config) - }, - SetupRepo: func(shell *Shell) { - shell.CreateFile("my-file.txt", "myfile content") - shell.CreateFile("my-other-file.rb", "my-other-file content") - - shell.CreateNCommitsWithRandomMessages(30) - shell.NewBranch("feature/demo") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("feature/demo", "origin/feature/demo") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("Stage a file") - t.Wait(1000) - - t.Views().Files(). - IsFocused(). - PressPrimaryAction(). - SetCaptionPrefix("Commit our changes"). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Type("my commit summary"). - SwitchToDescription(). - Type("my commit description"). - SwitchToSummary(). - Confirm() - - t.Views().Commits(). - TopLines( - Contains("my commit summary"), - ) - - t.SetCaptionPrefix("Push to the remote") - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Push) - }, -}) diff --git a/pkg/integration/tests/demo/commit_graph.go b/pkg/integration/tests/demo/commit_graph.go deleted file mode 100644 index 8cb2847a3f0..00000000000 --- a/pkg/integration/tests/demo/commit_graph.go +++ /dev/null @@ -1,73 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CommitGraph = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Show commit graph", - ExtraCmdArgs: []string{"log", "--screen-mode=full"}, - Skip: false, - IsDemo: true, - SetupConfig: func(config *config.AppConfig) { - setDefaultDemoConfig(config) - setGeneratedAuthorColours(config) - }, - SetupRepo: func(shell *Shell) { - shell.CreateRepoHistory() - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("View commit log") - t.Wait(1000) - - t.Views().Commits(). - IsFocused(). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100). - SelectNextItem(). - Wait(100) - }, -}) diff --git a/pkg/integration/tests/demo/custom_command.go b/pkg/integration/tests/demo/custom_command.go deleted file mode 100644 index a65c2c07352..00000000000 --- a/pkg/integration/tests/demo/custom_command.go +++ /dev/null @@ -1,76 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var customCommandContent = ` -customCommands: - - key: 'a' - command: 'git checkout {{.Form.Branch}}' - context: 'localBranches' - prompts: - - type: 'input' - title: 'Enter a branch name to checkout:' - key: 'Branch' - suggestions: - preset: 'branches' -` - -var CustomCommand = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Invoke a custom command", - ExtraCmdArgs: []string{}, - Skip: false, - IsDemo: true, - SetupConfig: func(cfg *config.AppConfig) { - setDefaultDemoConfig(cfg) - - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "a", - Context: "localBranches", - Command: `git checkout {{.Form.Branch}}`, - Prompts: []config.CustomCommandPrompt{ - { - Key: "Branch", - Type: "input", - Title: "Enter a branch name to checkout", - Suggestions: config.CustomCommandSuggestions{ - Preset: "branches", - }, - }, - }, - }, - } - }, - 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.EmptyCommit("Make mobile response") - shell.NewBranch("bugfix/fix-login-issue") - shell.HardReset("HEAD~1") - shell.NewBranch("bugfix/fix-crash-bug") - shell.CreateFile("custom_commands_example.yml", customCommandContent) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("Invoke a custom command") - t.Wait(1500) - - t.Views().Branches(). - Focus(). - Wait(500). - Press("a"). - Tap(func() { - t.Wait(500) - - t.ExpectPopup().Prompt(). - Title(Equals("Enter a branch name to checkout")). - Type("mobile"). - ConfirmFirstSuggestion() - }) - }, -}) diff --git a/pkg/integration/tests/demo/custom_patch.go b/pkg/integration/tests/demo/custom_patch.go deleted file mode 100644 index 24e05c63327..00000000000 --- a/pkg/integration/tests/demo/custom_patch.go +++ /dev/null @@ -1,75 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var usersFileContent = `package main - -import "fmt" - -func main() { - // TODO: verify that this actually works - fmt.Println("hello world") -} -` - -var CustomPatch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Remove a line from an old commit", - ExtraCmdArgs: []string{}, - Skip: false, - IsDemo: true, - SetupConfig: func(cfg *config.AppConfig) { - setDefaultDemoConfig(cfg) - cfg.GetUserConfig().Gui.UseHunkModeInStagingView = false - }, - SetupRepo: func(shell *Shell) { - shell.CreateNCommitsWithRandomMessages(30) - shell.NewBranch("feature/user-authentication") - shell.EmptyCommit("Add user authentication feature") - shell.CreateFileAndAdd("src/users.go", "package main\n") - shell.Commit("Fix local session storage") - shell.CreateFile("src/authentication.go", "package main") - shell.CreateFile("src/session.go", "package main") - shell.UpdateFileAndAdd("src/users.go", usersFileContent) - shell.EmptyCommit("Stop using shims") - shell.UpdateFileAndAdd("src/authentication.go", "package authentication") - shell.UpdateFileAndAdd("src/session.go", "package session") - shell.Commit("Enhance user authentication feature") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("Remove a line from an old commit") - t.Wait(1000) - - t.Views().Commits(). - Focus(). - NavigateToLine(Contains("Stop using shims")). - Wait(1000). - PressEnter(). - Tap(func() { - t.Views().CommitFiles(). - IsFocused(). - NavigateToLine(Contains("users.go")). - Wait(1000). - PressEnter(). - Tap(func() { - t.Views().PatchBuilding(). - IsFocused(). - NavigateToLine(Contains("TODO")). - Wait(500). - PressPrimaryAction(). - PressEscape() - }). - Press(keys.Universal.CreatePatchOptionsMenu). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Patch options")). - Select(Contains("Remove patch from original commit")). - Wait(500). - Confirm() - }). - PressEscape() - }) - }, -}) diff --git a/pkg/integration/tests/demo/diff_commits.go b/pkg/integration/tests/demo/diff_commits.go deleted file mode 100644 index ddc7b7ba7a0..00000000000 --- a/pkg/integration/tests/demo/diff_commits.go +++ /dev/null @@ -1,44 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiffCommits = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Diff two commits", - ExtraCmdArgs: []string{}, - Skip: false, - IsDemo: true, - SetupConfig: func(config *config.AppConfig) { - setDefaultDemoConfig(config) - - config.GetUserConfig().Gui.ShowFileTree = false - config.GetUserConfig().Gui.ShowCommandLog = false - }, - SetupRepo: func(shell *Shell) { - shell.CreateNCommitsWithRandomMessages(50) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("Compare two commits") - t.Wait(1000) - - t.Views().Commits(). - Focus(). - NavigateToLine(Contains("Replace deprecated lifecycle methods in React components")). - Wait(1000). - Press(keys.Universal.DiffingMenu). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Diffing")). - TopLines( - MatchesRegexp(`Diff .*`), - ). - Wait(500). - Confirm() - }). - NavigateToLine(Contains("Move constants to a separate config file")). - Wait(1000). - PressEnter() - }, -}) diff --git a/pkg/integration/tests/demo/filter.go b/pkg/integration/tests/demo/filter.go deleted file mode 100644 index 84fba65f9dc..00000000000 --- a/pkg/integration/tests/demo/filter.go +++ /dev/null @@ -1,91 +0,0 @@ -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) { - setDefaultDemoConfig(config) - }, - 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/interactive_rebase.go b/pkg/integration/tests/demo/interactive_rebase.go deleted file mode 100644 index a9e97ee0d77..00000000000 --- a/pkg/integration/tests/demo/interactive_rebase.go +++ /dev/null @@ -1,62 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var InteractiveRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Interactive rebase", - ExtraCmdArgs: []string{"log", "--screen-mode=full"}, - Skip: false, - IsDemo: true, - SetupConfig: func(config *config.AppConfig) { - setDefaultDemoConfig(config) - }, - SetupRepo: func(shell *Shell) { - shell.CreateRepoHistory() - - shell.NewBranch("feature/demo") - - shell.CreateNCommitsWithRandomMessages(10) - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("feature/demo", "origin/feature/demo") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("Interactive rebase") - t.Wait(1000) - - t.Views().Commits(). - IsFocused(). - Press(keys.Commits.StartInteractiveRebase). - PressFast(keys.Universal.RangeSelectDown). - PressFast(keys.Universal.RangeSelectDown). - Press(keys.Commits.MarkCommitAsFixup). - PressFast(keys.Commits.MoveDownCommit). - PressFast(keys.Commits.MoveDownCommit). - Delay(). - SelectNextItem(). - SelectNextItem(). - Press(keys.Universal.Remove). - SelectNextItem(). - Press(keys.Commits.SquashDown). - Press(keys.Universal.CreateRebaseOptionsMenu). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Contains("Rebase options")). - Select(Contains("continue")). - Confirm() - }). - SetCaptionPrefix("Push to remote"). - Press(keys.Universal.NextScreenMode). - Press(keys.Universal.Push). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Contains("Force push")). - Content(AnyString()). - Confirm() - }) - }, -}) diff --git a/pkg/integration/tests/demo/nuke_working_tree.go b/pkg/integration/tests/demo/nuke_working_tree.go deleted file mode 100644 index 5ff2a5dd623..00000000000 --- a/pkg/integration/tests/demo/nuke_working_tree.go +++ /dev/null @@ -1,44 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var NukeWorkingTree = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Nuke the working tree", - ExtraCmdArgs: []string{"status", "--screen-mode=full"}, - Skip: false, - IsDemo: true, - SetupConfig: func(config *config.AppConfig) { - setDefaultDemoConfig(config) - config.GetUserConfig().Gui.AnimateExplosion = true - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("blah") - shell.CreateFile("controllers/red_controller.rb", "") - shell.CreateFile("controllers/green_controller.rb", "") - shell.CreateFileAndAdd("controllers/blue_controller.rb", "") - shell.CreateFile("controllers/README.md", "") - shell.CreateFileAndAdd("views/helpers/list.rb", "") - shell.CreateFile("views/helpers/sort.rb", "") - shell.CreateFileAndAdd("views/users_view.rb", "") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("Nuke the working tree") - t.Wait(1000) - - t.Views().Files(). - IsFocused(). - Wait(1000). - Press(keys.Files.ViewResetOptions). - Tap(func() { - t.Wait(1000) - - t.ExpectPopup().Menu(). - Title(Equals("")). - Select(Contains("Nuke working tree")). - Confirm() - }) - }, -}) diff --git a/pkg/integration/tests/demo/rebase_onto.go b/pkg/integration/tests/demo/rebase_onto.go deleted file mode 100644 index 8b634011cd7..00000000000 --- a/pkg/integration/tests/demo/rebase_onto.go +++ /dev/null @@ -1,81 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RebaseOnto = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rebase with '--onto' flag. We start with a feature branch on the develop branch that we want to rebase onto the master branch", - ExtraCmdArgs: []string{}, - Skip: false, - IsDemo: true, - SetupConfig: func(config *config.AppConfig) { - setDefaultDemoConfig(config) - }, - SetupRepo: func(shell *Shell) { - shell.CreateNCommitsWithRandomMessages(60) - shell.NewBranch("develop") - - shell.SetAuthor("Joe Blow", "joeblow@gmail.com") - - shell.RandomChangeCommit("Develop commit 1") - shell.RandomChangeCommit("Develop commit 2") - shell.RandomChangeCommit("Develop commit 3") - - shell.SetAuthor("Jesse Duffield", "jesseduffield@gmail.com") - - shell.NewBranch("feature/demo") - - shell.RandomChangeCommit("Feature commit 1") - shell.RandomChangeCommit("Feature commit 2") - shell.RandomChangeCommit("Feature commit 3") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("feature/demo", "origin/feature/demo") - shell.SetBranchUpstream("develop", "origin/develop") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("Rebase from marked base commit") - t.Wait(1000) - - // first we focus the commits view, then expand to show the branches against each commit - // Then we go back to normal value, mark the last develop branch commit as the marked commit - // Then go to the branches view and press 'r' on the master branch to rebase onto it - // then we force push our changes. - - t.Views().Commits(). - Focus(). - Press(keys.Universal.PrevScreenMode). - Wait(500). - NavigateToLine(Contains("Develop commit 3")). - Wait(500). - Press(keys.Commits.MarkCommitAsBaseForRebase). - Wait(1000). - Press(keys.Universal.NextScreenMode). - Wait(500) - - t.Views().Branches(). - Focus(). - Wait(500). - NavigateToLine(Contains("master")). - Wait(500). - Press(keys.Branches.RebaseBranch). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Contains("Rebase 'feature/demo' from marked base")). - Select(Contains("Simple rebase")). - Confirm() - }). - Wait(1000). - Press(keys.Universal.Push). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Contains("Force push")). - Content(AnyString()). - Wait(500). - Confirm() - }) - }, -}) diff --git a/pkg/integration/tests/demo/shared.go b/pkg/integration/tests/demo/shared.go deleted file mode 100644 index f7253128946..00000000000 --- a/pkg/integration/tests/demo/shared.go +++ /dev/null @@ -1,19 +0,0 @@ -package demo - -import "github.com/jesseduffield/lazygit/pkg/config" - -// Gives us nicer colours when we generate a git repo history with `shell.CreateRepoHistory()` -func setGeneratedAuthorColours(config *config.AppConfig) { - config.GetUserConfig().Gui.AuthorColors = map[string]string{ - "Fredrica Greenhill": "#fb5aa3", - "Oscar Reuenthal": "#86c82f", - "Paul Oberstein": "#ffd500", - "Siegfried Kircheis": "#fe7e11", - "Yang Wen-li": "#8e3ccb", - } -} - -func setDefaultDemoConfig(config *config.AppConfig) { - // demos look much nicer with icons shown - config.GetUserConfig().Gui.NerdFontsVersion = "3" -} diff --git a/pkg/integration/tests/demo/stage_lines.go b/pkg/integration/tests/demo/stage_lines.go deleted file mode 100644 index 1304d39e5db..00000000000 --- a/pkg/integration/tests/demo/stage_lines.go +++ /dev/null @@ -1,83 +0,0 @@ -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) { - setDefaultDemoConfig(config) - config.GetUserConfig().Gui.ShowFileTree = false - config.GetUserConfig().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.Universal.ToggleRangeSelect). - 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/demo/undo.go b/pkg/integration/tests/demo/undo.go deleted file mode 100644 index 830b1849fee..00000000000 --- a/pkg/integration/tests/demo/undo.go +++ /dev/null @@ -1,53 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Undo = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Undo", - ExtraCmdArgs: []string{}, - Skip: false, - IsDemo: true, - SetupConfig: func(config *config.AppConfig) { - setDefaultDemoConfig(config) - }, - SetupRepo: func(shell *Shell) { - shell.CreateNCommitsWithRandomMessages(30) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("Undo commands") - t.Wait(1000) - - confirmCommitDrop := func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Drop commit")). - Content(Equals("Are you sure you want to drop the selected commit(s)?")). - Wait(500). - Confirm() - } - - confirmUndo := func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Undo")). - Content(MatchesRegexp(`Are you sure you want to hard reset to '.*'\? An auto-stash will be performed if necessary\.`)). - Wait(500). - Confirm() - } - - t.Views().Commits().Focus(). - SetCaptionPrefix("Drop two commits"). - Wait(1000). - Press(keys.Universal.Remove). - Tap(confirmCommitDrop). - Press(keys.Universal.Remove). - Tap(confirmCommitDrop). - SetCaptionPrefix("Undo the drops"). - Wait(1000). - Press(keys.Universal.Undo). - Tap(confirmUndo). - Press(keys.Universal.Undo). - Tap(confirmUndo) - }, -}) diff --git a/pkg/integration/tests/demo/worktree_create_from_branches.go b/pkg/integration/tests/demo/worktree_create_from_branches.go deleted file mode 100644 index 39d2cb73e23..00000000000 --- a/pkg/integration/tests/demo/worktree_create_from_branches.go +++ /dev/null @@ -1,57 +0,0 @@ -package demo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var WorktreeCreateFromBranches = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create a worktree from the branches view", - ExtraCmdArgs: []string{}, - Skip: false, - IsDemo: true, - SetupConfig: func(cfg *config.AppConfig) { - setDefaultDemoConfig(cfg) - }, - SetupRepo: func(shell *Shell) { - shell.CreateNCommitsWithRandomMessages(30) - shell.NewBranch("feature/user-authentication") - shell.EmptyCommit("Add user authentication feature") - shell.EmptyCommit("Fix local session storage") - shell.CreateFile("src/authentication.go", "package main") - shell.CreateFile("src/shims.go", "package main") - shell.CreateFile("src/session.go", "package main") - shell.EmptyCommit("Stop using shims") - shell.UpdateFile("src/authentication.go", "package authentication") - shell.UpdateFileAndAdd("src/shims.go", "// removing for now") - shell.UpdateFile("src/session.go", "package session") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.SetCaptionPrefix("Create a worktree from a branch") - t.Wait(1000) - - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("master")). - Wait(500). - Press(keys.Worktrees.ViewWorktreeOptions). - Tap(func() { - t.Wait(500) - - t.ExpectPopup().Menu(). - Title(Equals("Worktree")). - Select(Contains("Create worktree from master").DoesNotContain("detached")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New worktree path")). - Type("../hotfix"). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Contains("New branch name")). - Type("hotfix/db-on-fire"). - Confirm() - }) - }, -}) diff --git a/pkg/integration/tests/diff/copy_to_clipboard.go b/pkg/integration/tests/diff/copy_to_clipboard.go deleted file mode 100644 index 34f2bb95c9a..00000000000 --- a/pkg/integration/tests/diff/copy_to_clipboard.go +++ /dev/null @@ -1,164 +0,0 @@ -package diff - -import ( - "os" - - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// note: this is required to simulate the clipboard during CI -func expectClipboard(t *TestDriver, matcher *TextMatcher) { - defer t.Shell().DeleteFile("clipboard") - - t.FileSystem().FileContent("clipboard", matcher) -} - -var CopyToClipboard = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "The copy menu allows to copy name and diff of selected/all files", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > clipboard" - }, - SetupRepo: func(shell *Shell) { - shell.CreateDir("dir") - shell.CreateFileAndAdd("dir/file1", "1st line\n") - shell.Commit("1") - shell.UpdateFileAndAdd("dir/file1", "1st line\n2nd line\n") - shell.CreateFileAndAdd("dir/file2", "file2\n") - shell.Commit("2") - shell.UpdateFileAndAdd("dir/file1", "1st line\n2nd line\n3rd line\n") - shell.Commit("3") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("3").IsSelected(), - Contains("2"), - Contains("1"), - ). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("dir").IsSelected(), - Contains("file1"), - Contains("file2"), - ). - NavigateToLine(Contains("file1")). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("File name")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("File name copied to clipboard")) - expectClipboard(t, Equals("file1")) - }) - }). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Relative path")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("File path copied to clipboard")) - expectClipboard(t, Equals("dir/file1")) - }) - }). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Absolute path")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("File path copied to clipboard")) - repoDir, _ := os.Getwd() - // On windows the following path would have backslashes, but we don't run integration tests on windows yet. - expectClipboard(t, Equals(repoDir+"/dir/file1")) - }) - }). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Diff of selected file")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("File diff copied to clipboard")) - expectClipboard(t, - Contains("diff --git a/dir/file1 b/dir/file1").Contains("+2nd line").DoesNotContain("+1st line"). - DoesNotContain("diff --git a/dir/file2 b/dir/file2").DoesNotContain("+file2")) - }) - }). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Diff of all files")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("All files diff copied to clipboard")) - expectClipboard(t, - Contains("diff --git a/dir/file1 b/dir/file1").Contains("+2nd line").DoesNotContain("+1st line"). - Contains("diff --git a/dir/file2 b/dir/file2").Contains("+file2")) - }) - }). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Content of selected file")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("File content copied to clipboard")) - expectClipboard(t, Equals("1st line\n2nd line\n")) - }) - }) - - t.Views().Commits(). - Focus(). - // Select commits 1 and 2 - Press(keys.Universal.RangeSelectDown). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("dir").IsSelected(), - Contains("file1"), - Contains("file2"), - ). - NavigateToLine(Contains("file1")). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Diff of selected file")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("File diff copied to clipboard")) - expectClipboard(t, - Contains("diff --git a/dir/file1 b/dir/file1").Contains("+1st line").Contains("+2nd line")) - }) - }). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Content of selected file")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("File content copied to clipboard")) - expectClipboard(t, Equals("1st line\n2nd line\n")) - }) - }) - }, -}) diff --git a/pkg/integration/tests/diff/diff.go b/pkg/integration/tests/diff/diff.go deleted file mode 100644 index 06e32041884..00000000000 --- a/pkg/integration/tests/diff/diff.go +++ /dev/null @@ -1,73 +0,0 @@ -package diff - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Diff = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "View the diff of two branches, then view the reverse diff", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("branch-a") - shell.CreateFileAndAdd("file1", "first line") - shell.Commit("first commit") - - shell.NewBranch("branch-b") - shell.UpdateFileAndAdd("file1", "first line\nsecond line") - shell.Commit("update") - - shell.Checkout("branch-a") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - TopLines( - Contains("branch-a"), - Contains("branch-b"), - ). - Press(keys.Universal.DiffingMenu) - - t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Contains(`Diff branch-a`)).Confirm() - - t.Views().Branches(). - IsFocused(). - Tap(func() { - t.Views().Information().Content(Contains("Showing output for: git diff --stat -p branch-a branch-a")) - }). - SelectNextItem(). - Tap(func() { - t.Views().Information().Content(Contains("Showing output for: git diff --stat -p branch-a branch-b")) - t.Views().Main().Content(Contains("+second line")) - }). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - SelectedLine(Contains("update")). - Tap(func() { - t.Views().Main().Content(Contains("+second line")) - }). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - SelectedLine(Contains("file1")). - Tap(func() { - t.Views().Main().Content(Contains("+second line")) - }). - PressEscape() - - t.Views().SubCommits().PressEscape() - - t.Views().Branches(). - IsFocused(). - Press(keys.Universal.DiffingMenu) - - t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Contains("Reverse diff direction")).Confirm() - t.Views().Information().Content(Contains("Showing output for: git diff --stat -p branch-a branch-b -R")) - t.Views().Main().Content(Contains("-second line")) - }, -}) diff --git a/pkg/integration/tests/diff/diff_and_apply_patch.go b/pkg/integration/tests/diff/diff_and_apply_patch.go deleted file mode 100644 index 6801e7bea47..00000000000 --- a/pkg/integration/tests/diff/diff_and_apply_patch.go +++ /dev/null @@ -1,78 +0,0 @@ -package diff - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiffAndApplyPatch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create a patch from the diff between two branches and apply the patch.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("branch-a") - shell.CreateFileAndAdd("file1", "first line\n") - shell.Commit("first commit") - - shell.NewBranch("branch-b") - shell.UpdateFileAndAdd("file1", "first line\nsecond line\n") - shell.Commit("update") - - shell.Checkout("branch-a") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("branch-a"), - Contains("branch-b"), - ). - Press(keys.Universal.DiffingMenu) - - t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Equals("Diff branch-a")).Confirm() - - t.Views().Information().Content(Contains("Showing output for: git diff --stat -p branch-a branch-a")) - - t.Views().Branches(). - IsFocused(). - SelectNextItem(). - Tap(func() { - t.Views().Information().Content(Contains("Showing output for: git diff --stat -p branch-a branch-b")) - t.Views().Main().Content(Contains("+second line")) - }). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - SelectedLine(Contains("update")). - Tap(func() { - t.Views().Main().Content(Contains("+second line")) - }). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - SelectedLine(Contains("file1")). - Tap(func() { - t.Views().Main().Content(Contains("+second line")) - }). - PressPrimaryAction(). // add the file to the patch - Press(keys.Universal.DiffingMenu). - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Contains("Exit diff mode")).Confirm() - - t.Views().Information().Content(Contains("Building patch")) - }). - Press(keys.Universal.CreatePatchOptionsMenu) - - // adding the regex '$' here to distinguish the menu item from the 'Apply patch in reverse' item - t.ExpectPopup().Menu().Title(Equals("Patch options")).Select(MatchesRegexp("Apply patch$")).Confirm() - - t.Views().Files(). - Focus(). - SelectedLine(Contains("file1")) - - t.Views().Main().Content(Contains("+second line")) - }, -}) diff --git a/pkg/integration/tests/diff/diff_commits.go b/pkg/integration/tests/diff/diff_commits.go deleted file mode 100644 index 769773b2942..00000000000 --- a/pkg/integration/tests/diff/diff_commits.go +++ /dev/null @@ -1,55 +0,0 @@ -package diff - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiffCommits = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "View the diff between two commits", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "first line\n") - shell.Commit("first commit") - shell.UpdateFileAndAdd("file1", "first line\nsecond line\n") - shell.Commit("second commit") - shell.UpdateFileAndAdd("file1", "first line\nsecond line\nthird line\n") - shell.Commit("third commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("third commit").IsSelected(), - Contains("second commit"), - Contains("first commit"), - ). - Press(keys.Universal.DiffingMenu). - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(MatchesRegexp(`Diff \w+`)).Confirm() - - t.Views().Information().Content(Contains("Showing output for: git diff")) - }). - SelectNextItem(). - SelectNextItem(). - SelectedLine(Contains("first commit")). - Tap(func() { - t.Views().Main().Content(Contains("-second line\n-third line")) - }). - Press(keys.Universal.DiffingMenu). - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Contains("Reverse diff direction")).Confirm() - - t.Views().Main().Content(Contains("+second line\n+third line")) - }). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - SelectedLine(Contains("file1")) - - t.Views().Main().Content(Contains("+second line\n+third line")) - }, -}) diff --git a/pkg/integration/tests/diff/diff_non_sticky_range.go b/pkg/integration/tests/diff/diff_non_sticky_range.go deleted file mode 100644 index 672f744a9c3..00000000000 --- a/pkg/integration/tests/diff/diff_non_sticky_range.go +++ /dev/null @@ -1,45 +0,0 @@ -package diff - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiffNonStickyRange = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "View the combined diff of multiple commits using a range selection", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CreateFileAndAdd("file1", "first line\n") - shell.Commit("first commit") - shell.UpdateFileAndAdd("file1", "first line\nsecond line\n") - shell.Commit("second commit") - shell.UpdateFileAndAdd("file1", "first line\nsecond line\nthird line\n") - shell.Commit("third commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("third commit").IsSelected(), - Contains("second commit"), - Contains("first commit"), - Contains("initial commit"), - ). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.RangeSelectDown). - Tap(func() { - t.Views().Main().Content(Contains("Showing diff for range "). - Contains("+first line\n+second line\n+third line")) - }). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - SelectedLine(Contains("file1")) - - t.Views().Main().Content(Contains("+first line\n+second line\n+third line")) - }, -}) diff --git a/pkg/integration/tests/diff/ignore_whitespace.go b/pkg/integration/tests/diff/ignore_whitespace.go deleted file mode 100644 index 44e87713983..00000000000 --- a/pkg/integration/tests/diff/ignore_whitespace.go +++ /dev/null @@ -1,60 +0,0 @@ -package diff - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ( - initialFileContent = "first-line\nold-second-line\nthird-line\n" - // We're indenting each line and modifying the second line - updatedFileContent = " first-line\n new-second-line\n third-line\n" -) - -var IgnoreWhitespace = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Toggle whitespace in the diff", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", initialFileContent) - shell.Commit("initial commit") - shell.UpdateFile("myfile", updatedFileContent) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Main().ContainsLines( - Contains(`-first-line`), - Contains(`-old-second-line`), - Contains(`-third-line`), - Contains(`+ first-line`), - Contains(`+ new-second-line`), - Contains(`+ third-line`), - ) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.ToggleWhitespaceInDiffView) - - // lines with only whitespace changes are ignored (first and third lines) - t.Views().Main().ContainsLines( - Contains(` first-line`), - Contains(`-old-second-line`), - Contains(`+ new-second-line`), - Contains(` third-line`), - ) - - // when toggling again it goes back to showing whitespace - t.Views().Files(). - IsFocused(). - Press(keys.Universal.ToggleWhitespaceInDiffView) - - t.Views().Main().ContainsLines( - Contains(`-first-line`), - Contains(`-old-second-line`), - Contains(`-third-line`), - Contains(`+ first-line`), - Contains(`+ new-second-line`), - Contains(`+ third-line`), - ) - }, -}) diff --git a/pkg/integration/tests/diff/rename_similarity_threshold_change.go b/pkg/integration/tests/diff/rename_similarity_threshold_change.go deleted file mode 100644 index 397e4e2221f..00000000000 --- a/pkg/integration/tests/diff/rename_similarity_threshold_change.go +++ /dev/null @@ -1,53 +0,0 @@ -package diff - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RenameSimilarityThresholdChange = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Change the rename similarity threshold while in the commits panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("original", "one\ntwo\nthree\nfour\nfive\n") - shell.Commit("add original") - - shell.DeleteFileAndAdd("original") - shell.CreateFileAndAdd("renamed", "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\n") - shell.Commit("change name and contents") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Focus() - - t.Views().Main(). - ContainsLines( - Contains("2 files changed, 10 insertions(+), 5 deletions(-)"), - ) - - t.Views().Commits(). - Press(keys.Universal.DecreaseRenameSimilarityThreshold). - Tap(func() { - t.ExpectToast(Equals("Changed rename similarity threshold to 45%")) - }) - - t.Views().Main(). - ContainsLines( - Contains("original => renamed"), - Contains("1 file changed, 5 insertions(+)"), - ) - - t.Views().Commits(). - Press(keys.Universal.FocusMainView) - - t.Views().Main(). - Press(keys.Universal.IncreaseRenameSimilarityThreshold). - Tap(func() { - t.ExpectToast(Equals("Changed rename similarity threshold to 50%")) - }). - ContainsLines( - Contains("2 files changed, 10 insertions(+), 5 deletions(-)"), - ) - }, -}) diff --git a/pkg/integration/tests/file/collapse_expand.go b/pkg/integration/tests/file/collapse_expand.go deleted file mode 100644 index 7ec25b02a98..00000000000 --- a/pkg/integration/tests/file/collapse_expand.go +++ /dev/null @@ -1,47 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CollapseExpand = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Collapsing and expanding all files in the file tree", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateDir("dir") - shell.CreateFile("dir/file-one", "original content\n") - shell.CreateDir("dir2") - shell.CreateFile("dir2/file-two", "original content\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ dir"), - Equals(" ?? file-one"), - Equals(" ▼ dir2"), - Equals(" ?? file-two"), - ) - - t.Views().Files(). - Press(keys.Files.CollapseAll). - Lines( - Equals("▶ /"), - ) - - t.Views().Files(). - Press(keys.Files.ExpandAll). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ dir"), - Equals(" ?? file-one"), - Equals(" ▼ dir2"), - Equals(" ?? file-two"), - ) - }, -}) diff --git a/pkg/integration/tests/file/copy_menu.go b/pkg/integration/tests/file/copy_menu.go deleted file mode 100644 index 8151cfa101f..00000000000 --- a/pkg/integration/tests/file/copy_menu.go +++ /dev/null @@ -1,212 +0,0 @@ -package file - -import ( - "os" - - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// note: this is required to simulate the clipboard during CI -func expectClipboard(t *TestDriver, matcher *TextMatcher) { - defer t.Shell().DeleteFile("clipboard") - - t.FileSystem().FileContent("clipboard", matcher) -} - -var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "The copy menu allows to copy name and diff of selected/all files", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > clipboard" - }, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // Disabled item - t.Views().Files(). - IsEmpty(). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("File name")). - Tooltip(Equals("Disabled: No item selected")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("Disabled: No item selected")) - }). - Cancel() - }) - - t.Shell(). - CreateDir("dir"). - CreateFile("dir/1-unstaged_file", "unstaged content") - - // Empty content (new file) - t.Views().Files(). - Press(keys.Universal.Refresh). - Lines( - Contains("dir").IsSelected(), - Contains("unstaged_file"), - ). - SelectNextItem(). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Diff of selected file")). - Tooltip(Contains("Disabled: Nothing to copy")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("Disabled: Nothing to copy")) - }). - Cancel() - }). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Diff of all files")). - Tooltip(Contains("Disabled: Nothing to copy")). - Confirm(). - Tap(func() { - t.ExpectToast(Equals("Disabled: Nothing to copy")) - }). - Cancel() - }) - - t.Shell(). - GitAdd("dir/1-unstaged_file"). - Commit("commit-unstaged"). - UpdateFile("dir/1-unstaged_file", "unstaged content (new)"). - CreateFileAndAdd("dir/2-staged_file", "staged content"). - Commit("commit-staged"). - UpdateFile("dir/2-staged_file", "staged content (new)"). - GitAdd("dir/2-staged_file") - - // Copy file name - t.Views().Files(). - Press(keys.Universal.Refresh). - Lines( - Contains("dir"), - Contains("unstaged_file").IsSelected(), - Contains("staged_file"), - ). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("File name")). - Confirm() - - t.ExpectToast(Equals("File name copied to clipboard")) - - expectClipboard(t, Equals("1-unstaged_file")) - }) - - // Copy relative file path - t.Views().Files(). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Relative path")). - Confirm() - - t.ExpectToast(Equals("File path copied to clipboard")) - - expectClipboard(t, Equals("dir/1-unstaged_file")) - }) - - // Copy absolute file path - t.Views().Files(). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Absolute path")). - Confirm() - - t.ExpectToast(Equals("File path copied to clipboard")) - - repoDir, _ := os.Getwd() - // On windows the following path would have backslashes, but we don't run integration tests on windows yet. - expectClipboard(t, Equals(repoDir+"/dir/1-unstaged_file")) - }) - - // Selected path diff on a single (unstaged) file - t.Views().Files(). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Diff of selected file")). - Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")). - Confirm() - - t.ExpectToast(Equals("File diff copied to clipboard")) - - expectClipboard(t, Contains("+unstaged content (new)")) - }) - - // Selected path diff with staged and unstaged files - t.Views().Files(). - SelectPreviousItem(). - Lines( - Contains("dir").IsSelected(), - Contains("unstaged_file"), - Contains("staged_file"), - ). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Diff of selected file")). - Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")). - Confirm() - - t.ExpectToast(Equals("File diff copied to clipboard")) - - expectClipboard(t, Contains("+staged content (new)")) - }) - - // All files diff with staged files - t.Views().Files(). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Diff of all files")). - Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")). - Confirm() - - t.ExpectToast(Equals("All files diff copied to clipboard")) - - expectClipboard(t, Contains("+staged content (new)")) - }) - - // All files diff with no staged files - t.Views().Files(). - SelectNextItem(). - SelectNextItem(). - Lines( - Contains("dir"), - Contains("unstaged_file"), - Contains("staged_file").IsSelected(), - ). - Press(keys.Universal.Select). - Press(keys.Files.CopyFileInfoToClipboard). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Copy to clipboard")). - Select(Contains("Diff of all files")). - Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")). - Confirm() - - t.ExpectToast(Equals("All files diff copied to clipboard")) - - expectClipboard(t, Contains("+staged content (new)").Contains("+unstaged content (new)")) - }) - }, -}) diff --git a/pkg/integration/tests/file/dir_with_untracked_file.go b/pkg/integration/tests/file/dir_with_untracked_file.go deleted file mode 100644 index 1def7fa89b5..00000000000 --- a/pkg/integration/tests/file/dir_with_untracked_file.go +++ /dev/null @@ -1,34 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DirWithUntrackedFile = NewIntegrationTest(NewIntegrationTestArgs{ - // notably, we currently _don't_ actually see the untracked file in the diff. Not sure how to get around that. - Description: "When selecting a directory that contains an untracked file, we should not get an error", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateDir("dir") - shell.CreateFile("dir/file", "foo") - shell.GitAddAll() - shell.Commit("first commit") - shell.CreateFile("dir/untracked", "bar") - shell.UpdateFile("dir/file", "baz") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("first commit"), - ) - - t.Views().Main(). - Content(DoesNotContain("error: Could not access")). - // we show baz because it's a modified file but we don't show bar because it's untracked - // (though it would be cool if we could show that too) - Content(Contains("baz")) - }, -}) diff --git a/pkg/integration/tests/file/discard_all_dir_changes.go b/pkg/integration/tests/file/discard_all_dir_changes.go deleted file mode 100644 index 6caa3d51993..00000000000 --- a/pkg/integration/tests/file/discard_all_dir_changes.go +++ /dev/null @@ -1,126 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiscardAllDirChanges = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Discarding all changes in a directory", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - // typically we would use more bespoke shell methods here, but I struggled to find a way to do that, - // and this is copied over from a legacy integration test which did everything in a big shell script - // so I'm just copying it across. - - shell.CreateDir("dir") - - // common stuff - shell.RunShellCommand(`echo test > dir/both-deleted.txt`) - shell.RunShellCommand(`git checkout -b conflict && git add dir/both-deleted.txt`) - shell.RunShellCommand(`echo bothmodded > dir/both-modded.txt && git add dir/both-modded.txt`) - shell.RunShellCommand(`echo haha > dir/deleted-them.txt && git add dir/deleted-them.txt`) - shell.RunShellCommand(`echo haha2 > dir/deleted-us.txt && git add dir/deleted-us.txt`) - shell.RunShellCommand(`echo mod > dir/modded.txt && git add dir/modded.txt`) - shell.RunShellCommand(`echo mod > dir/modded-staged.txt && git add dir/modded-staged.txt`) - shell.RunShellCommand(`echo del > dir/deleted.txt && git add dir/deleted.txt`) - shell.RunShellCommand(`echo del > dir/deleted-staged.txt && git add dir/deleted-staged.txt`) - shell.RunShellCommand(`echo change-delete > dir/change-delete.txt && git add dir/change-delete.txt`) - shell.RunShellCommand(`echo delete-change > dir/delete-change.txt && git add dir/delete-change.txt`) - shell.RunShellCommand(`echo double-modded > dir/double-modded.txt && git add dir/double-modded.txt`) - shell.RunShellCommand(`echo "renamed\nhaha" > dir/renamed.txt && git add dir/renamed.txt`) - shell.RunShellCommand(`git commit -m one`) - - // stuff on other branch - shell.RunShellCommand(`git branch conflict_second && git mv dir/both-deleted.txt dir/added-them-changed-us.txt`) - shell.RunShellCommand(`git commit -m "dir/both-deleted.txt renamed in dir/added-them-changed-us.txt"`) - shell.RunShellCommand(`echo blah > dir/both-added.txt && git add dir/both-added.txt`) - shell.RunShellCommand(`echo mod1 > dir/both-modded.txt && git add dir/both-modded.txt`) - shell.RunShellCommand(`rm dir/deleted-them.txt && git add dir/deleted-them.txt`) - shell.RunShellCommand(`echo modded > dir/deleted-us.txt && git add dir/deleted-us.txt`) - shell.RunShellCommand(`git commit -m "two"`) - - // stuff on our branch - shell.RunShellCommand(`git checkout conflict_second`) - shell.RunShellCommand(`git mv dir/both-deleted.txt dir/changed-them-added-us.txt`) - shell.RunShellCommand(`git commit -m "both-deleted.txt renamed in dir/changed-them-added-us.txt"`) - shell.RunShellCommand(`echo mod2 > dir/both-modded.txt && git add dir/both-modded.txt`) - shell.RunShellCommand(`echo blah2 > dir/both-added.txt && git add dir/both-added.txt`) - shell.RunShellCommand(`echo modded > dir/deleted-them.txt && git add dir/deleted-them.txt`) - shell.RunShellCommand(`rm dir/deleted-us.txt && git add dir/deleted-us.txt`) - shell.RunShellCommand(`git commit -m "three"`) - shell.RunShellCommand(`git reset --hard conflict_second`) - shell.RunCommandExpectError([]string{"git", "merge", "conflict"}) - - shell.RunShellCommand(`echo "new" > dir/new.txt`) - shell.RunShellCommand(`echo "new staged" > dir/new-staged.txt && git add dir/new-staged.txt`) - shell.RunShellCommand(`echo mod2 > dir/modded.txt`) - shell.RunShellCommand(`echo mod2 > dir/modded-staged.txt && git add dir/modded-staged.txt`) - shell.RunShellCommand(`rm dir/deleted.txt`) - shell.RunShellCommand(`rm dir/deleted-staged.txt && git add dir/deleted-staged.txt`) - shell.RunShellCommand(`echo change-delete2 > dir/change-delete.txt && git add dir/change-delete.txt`) - shell.RunShellCommand(`rm dir/change-delete.txt`) - shell.RunShellCommand(`rm dir/delete-change.txt && git add dir/delete-change.txt`) - shell.RunShellCommand(`echo "changed" > dir/delete-change.txt`) - shell.RunShellCommand(`echo "change1" > dir/double-modded.txt && git add dir/double-modded.txt`) - shell.RunShellCommand(`echo "change2" > dir/double-modded.txt`) - shell.RunShellCommand(`echo before > dir/added-changed.txt && git add dir/added-changed.txt`) - shell.RunShellCommand(`echo after > dir/added-changed.txt`) - shell.RunShellCommand(`rm dir/renamed.txt && git add dir/renamed.txt`) - shell.RunShellCommand(`echo "renamed\nhaha" > dir/renamed2.txt && git add dir/renamed2.txt`) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Contains("dir").IsSelected(), - Contains("UA").Contains("added-them-changed-us.txt"), - Contains("AA").Contains("both-added.txt"), - Contains("DD").Contains("both-deleted.txt"), - Contains("UU").Contains("both-modded.txt"), - Contains("AU").Contains("changed-them-added-us.txt"), - Contains("UD").Contains("deleted-them.txt"), - Contains("DU").Contains("deleted-us.txt"), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - }). - Tap(func() { - t.Common().ContinueOnConflictsResolved("merge") - t.ExpectPopup().Confirmation(). - Title(Equals("Continue")). - Content(Contains("Files have been modified since conflicts were resolved. Auto-stage them and continue?")). - Cancel() - t.GlobalPress(keys.Universal.CreateRebaseOptionsMenu) - t.ExpectPopup().Menu(). - Title(Equals("Merge options")). - Select(Contains("continue")). - Confirm() - }). - Lines( - Contains("dir").IsSelected(), - Contains(" M").Contains("added-changed.txt"), - Contains(" D").Contains("change-delete.txt"), - Contains("??").Contains("delete-change.txt"), - Contains(" D").Contains("deleted.txt"), - Contains(" M").Contains("double-modded.txt"), - Contains(" M").Contains("modded.txt"), - Contains("??").Contains("new.txt"), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - }). - IsEmpty() - }, -}) diff --git a/pkg/integration/tests/file/discard_range_select.go b/pkg/integration/tests/file/discard_range_select.go deleted file mode 100644 index 27492cf691e..00000000000 --- a/pkg/integration/tests/file/discard_range_select.go +++ /dev/null @@ -1,106 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiscardRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Discard a range of files using range select", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("dir2/file-2b", "old content") - shell.CreateFileAndAdd("dir3/file-3b", "old content") - shell.Commit("first commit") - shell.UpdateFile("dir2/file-2b", "new content") - shell.UpdateFile("dir3/file-3b", "new content") - - shell.CreateFile("dir1/file-1a", "") - shell.CreateFile("dir1/file-1b", "") - shell.CreateFile("dir2/file-2a", "") - shell.CreateFile("dir3/file-3a", "") - shell.CreateFile("file-a", "") - shell.CreateFile("file-b", "") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ dir1"), - Equals(" ?? file-1a"), - Equals(" ?? file-1b"), - Equals(" ▼ dir2"), - Equals(" ?? file-2a"), - Equals(" M file-2b"), - Equals(" ▼ dir3"), - Equals(" ?? file-3a"), - Equals(" M file-3b"), - Equals(" ?? file-a"), - Equals(" ?? file-b"), - ). - NavigateToLine(Contains("file-1b")). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("file-2a")). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ?? file-1a"), - Equals(" ?? file-1b").IsSelected(), - Equals(" ▼ dir2").IsSelected(), - Equals(" ?? file-2a").IsSelected(), - Equals(" M file-2b"), - Equals(" ▼ dir3"), - Equals(" ?? file-3a"), - Equals(" M file-3b"), - Equals(" ?? file-a"), - Equals(" ?? file-b"), - ). - // Discard - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - }). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ?? file-1a"), - Equals(" ▼ dir3").IsSelected(), - Equals(" ?? file-3a"), - Equals(" M file-3b"), - Equals(" ?? file-a"), - Equals(" ?? file-b"), - ). - // Verify you can discard collapsed directories in range select - PressEnter(). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("file-a")). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ?? file-1a"), - Equals(" ▶ dir3").IsSelected(), - Equals(" ?? file-a").IsSelected(), - Equals(" ?? file-b"), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - }). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ?? file-1a"), - Equals(" ?? file-b").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/file/discard_staged_changes.go b/pkg/integration/tests/file/discard_staged_changes.go deleted file mode 100644 index 06322567fb6..00000000000 --- a/pkg/integration/tests/file/discard_staged_changes.go +++ /dev/null @@ -1,55 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiscardStagedChanges = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Discarding staged changes", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("fileToRemove", "original content") - shell.CreateFileAndAdd("file2", "original content") - shell.Commit("first commit") - - shell.CreateFile("file3", "original content") - shell.UpdateFile("fileToRemove", "new content") - shell.UpdateFile("file2", "new content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" M file2"), - Equals(" ?? file3"), - Equals(" M fileToRemove"), - ). - NavigateToLine(Contains(`fileToRemove`)). - PressPrimaryAction(). - Lines( - Equals("▼ /"), - Equals(" M file2"), - Equals(" ?? file3"), - Equals(" M fileToRemove").IsSelected(), - ). - Press(keys.Files.ViewResetOptions) - - t.ExpectPopup().Menu().Title(Equals("")).Select(Contains("Discard staged changes")).Confirm() - - // staged file has been removed - t.Views().Files(). - Lines( - Equals("▼ /"), - Equals(" M file2"), - Equals(" ?? file3").IsSelected(), - ) - - // the file should have the same content that it originally had, given that that was committed already - t.FileSystem().FileContent("fileToRemove", Equals("original content")) - }, -}) diff --git a/pkg/integration/tests/file/discard_unstaged_dir_changes.go b/pkg/integration/tests/file/discard_unstaged_dir_changes.go deleted file mode 100644 index 572194572bc..00000000000 --- a/pkg/integration/tests/file/discard_unstaged_dir_changes.go +++ /dev/null @@ -1,59 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiscardUnstagedDirChanges = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Discarding unstaged changes in a directory", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateDir("dir") - shell.CreateFileAndAdd("dir/file-one", "original content\n") - - shell.Commit("first commit") - - shell.UpdateFileAndAdd("dir/file-one", "original content\nnew content\n") - shell.UpdateFile("dir/file-one", "original content\nnew content\neven newer content\n") - - shell.CreateDir("dir/subdir") - shell.CreateFile("dir/subdir/unstaged-file-one", "unstaged file") - shell.CreateFile("dir/unstaged-file-two", "unstaged file") - - shell.CreateFile("unstaged-file-three", "unstaged file") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ dir"), - Equals(" ▼ subdir"), - Equals(" ?? unstaged-file-one"), - Equals(" MM file-one"), - Equals(" ?? unstaged-file-two"), - Equals(" ?? unstaged-file-three"), - ). - SelectNextItem(). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard unstaged changes")). - Confirm() - }). - Lines( - Equals("▼ /"), - Equals(" ▼ dir").IsSelected(), - Equals(" M file-one"), - // this guy remains untouched because it wasn't inside the 'dir' directory - Equals(" ?? unstaged-file-three"), - ) - - t.FileSystem().FileContent("dir/file-one", Equals("original content\nnew content\n")) - }, -}) diff --git a/pkg/integration/tests/file/discard_unstaged_file_changes.go b/pkg/integration/tests/file/discard_unstaged_file_changes.go deleted file mode 100644 index f9dcaf6bb9a..00000000000 --- a/pkg/integration/tests/file/discard_unstaged_file_changes.go +++ /dev/null @@ -1,68 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiscardUnstagedFileChanges = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Discarding unstaged changes in a file", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file-one", "original content\n") - - shell.Commit("first commit") - - shell.UpdateFileAndAdd("file-one", "original content\nnew content\n") - shell.UpdateFile("file-one", "original content\nnew content\neven newer content\n") - - shell.CreateFileAndAdd("file-two", "original content\n") - shell.UpdateFile("file-two", "original content\nnew content\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" MM file-one"), - Equals(" AM file-two"), - ). - SelectNextItem(). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard unstaged changes")). - Confirm() - }). - Lines( - Equals("▼ /"), - Equals(" M file-one").IsSelected(), - Equals(" AM file-two"), - ). - SelectNextItem(). - Lines( - Equals("▼ /"), - Equals(" M file-one"), - Equals(" AM file-two").IsSelected(), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard unstaged changes")). - Confirm() - }). - Lines( - Equals("▼ /"), - Equals(" M file-one"), - Equals(" A file-two").IsSelected(), - ) - - t.FileSystem().FileContent("file-one", Equals("original content\nnew content\n")) - t.FileSystem().FileContent("file-two", Equals("original content\n")) - }, -}) diff --git a/pkg/integration/tests/file/discard_unstaged_range_select.go b/pkg/integration/tests/file/discard_unstaged_range_select.go deleted file mode 100644 index fc99c750256..00000000000 --- a/pkg/integration/tests/file/discard_unstaged_range_select.go +++ /dev/null @@ -1,76 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiscardUnstagedRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Discard unstaged changed in a range of files using range select", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("dir2/file-d", "old content") - shell.Commit("first commit") - shell.UpdateFile("dir2/file-d", "new content") - - shell.CreateFile("dir1/file-a", "") - shell.CreateFile("dir1/file-b", "") - shell.CreateFileAndAdd("dir2/file-c", "") - shell.CreateFile("file-e", "") - shell.CreateFile("file-f", "") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ dir1"), - Equals(" ?? file-a"), - Equals(" ?? file-b"), - Equals(" ▼ dir2"), - Equals(" A file-c"), - Equals(" M file-d"), - Equals(" ?? file-e"), - Equals(" ?? file-f"), - ). - NavigateToLine(Contains("file-b")). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("file-c")). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ?? file-a"), - Equals(" ?? file-b").IsSelected(), - Equals(" ▼ dir2").IsSelected(), - Equals(" A file-c").IsSelected(), - Equals(" M file-d"), - Equals(" ?? file-e"), - Equals(" ?? file-f"), - ). - // Discard - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard unstaged changes")). - Confirm() - }). - // file-b is gone because it was selected and contained no staged changes. - // file-c is still there because it contained no unstaged changes - // file-d is gone because it was selected via dir2 and contained only unstaged changes - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ?? file-a"), - Equals(" ▼ dir2"), - // Re-selecting file-c because it's where the selected line index - // was before performing the action. - Equals(" A file-c").IsSelected(), - Equals(" ?? file-e"), - Equals(" ?? file-f"), - ) - }, -}) diff --git a/pkg/integration/tests/file/discard_various_changes.go b/pkg/integration/tests/file/discard_various_changes.go deleted file mode 100644 index bc68fd218ec..00000000000 --- a/pkg/integration/tests/file/discard_various_changes.go +++ /dev/null @@ -1,76 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiscardVariousChanges = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Discarding all possible permutations of changed files", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - createAllPossiblePermutationsOfChangedFiles(shell) - }, - - Run: func(t *TestDriver, keys config.KeybindingConfig) { - type statusFile struct { - status string - label string - } - - t.Views().Files(). - IsFocused(). - TopLines( - Equals("▼ /").IsSelected(), - ) - - discardOneByOne := func(files []statusFile) { - for _, file := range files { - t.Views().Files(). - IsFocused(). - NavigateToLine(Contains(file.status + " " + file.label)). - Press(keys.Universal.Remove) - - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - } - } - - discardOneByOne([]statusFile{ - {status: "UA", label: "added-them-changed-us.txt"}, - {status: "AA", label: "both-added.txt"}, - {status: "DD", label: "both-deleted.txt"}, - {status: "UU", label: "both-modded.txt"}, - {status: "AU", label: "changed-them-added-us.txt"}, - {status: "UD", label: "deleted-them.txt"}, - {status: "DU", label: "deleted-us.txt"}, - }) - - t.ExpectPopup().Confirmation(). - Title(Equals("Continue")). - Content(Contains("All merge conflicts resolved. Continue the merge?")). - Cancel() - - discardOneByOne([]statusFile{ - {status: "AM", label: "added-changed.txt"}, - {status: "MD", label: "change-delete.txt"}, - {status: "D ", label: "delete-change.txt"}, - {status: "D ", label: "deleted-staged.txt"}, - {status: " D", label: "deleted.txt"}, - {status: "MM", label: "double-modded.txt"}, - {status: "M ", label: "modded-staged.txt"}, - {status: " M", label: "modded.txt"}, - {status: "A ", label: "new-staged.txt"}, - {status: "??", label: "new.txt"}, - // the menu title only includes the new file - {status: "R ", label: "renamed.txt → renamed2.txt"}, - }) - - t.Views().Files().IsEmpty() - }, -}) diff --git a/pkg/integration/tests/file/discard_various_changes_range_select.go b/pkg/integration/tests/file/discard_various_changes_range_select.go deleted file mode 100644 index 937c50114b8..00000000000 --- a/pkg/integration/tests/file/discard_various_changes_range_select.go +++ /dev/null @@ -1,72 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiscardVariousChangesRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Discarding all possible permutations of changed files via range select", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - createAllPossiblePermutationsOfChangedFiles(shell) - }, - - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" UA added-them-changed-us.txt"), - Equals(" AA both-added.txt"), - Equals(" DD both-deleted.txt"), - Equals(" UU both-modded.txt"), - Equals(" AU changed-them-added-us.txt"), - Equals(" UD deleted-them.txt"), - Equals(" DU deleted-us.txt"), - ). - SelectNextItem(). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("deleted-us.txt")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - - t.ExpectPopup().Confirmation(). - Title(Equals("Continue")). - Content(Contains("All merge conflicts resolved. Continue the merge?")). - Cancel() - }). - Lines( - Equals("▼ /").IsSelected(), - Equals(" AM added-changed.txt"), - Equals(" MD change-delete.txt"), - Equals(" D delete-change.txt"), - Equals(" D deleted-staged.txt"), - Equals(" D deleted.txt"), - Equals(" MM double-modded.txt"), - Equals(" M modded-staged.txt"), - Equals(" M modded.txt"), - Equals(" A new-staged.txt"), - Equals(" ?? new.txt"), - Equals(" R renamed.txt → renamed2.txt"), - ). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("renamed.txt")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - }) - - t.Views().Files().IsEmpty() - }, -}) diff --git a/pkg/integration/tests/file/gitignore.go b/pkg/integration/tests/file/gitignore.go deleted file mode 100644 index 0d7c1a019ac..00000000000 --- a/pkg/integration/tests/file/gitignore.go +++ /dev/null @@ -1,65 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Gitignore = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that we can't ignore the .gitignore file, then ignore/exclude other files", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFile(".gitignore", "") - shell.CreateFile("toExclude", "") - shell.CreateFile("toIgnore", "") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ?? .gitignore"), - Equals(" ?? toExclude"), - Equals(" ?? toIgnore"), - ). - SelectNextItem(). - Press(keys.Files.IgnoreFile). - // ensure we can't exclude the .gitignore file - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .git/info/exclude")).Confirm() - - t.ExpectPopup().Alert().Title(Equals("Error")).Content(Equals("Cannot exclude .gitignore")).Confirm() - }). - Press(keys.Files.IgnoreFile). - // ensure we can't ignore the .gitignore file - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .gitignore")).Confirm() - - t.ExpectPopup().Alert().Title(Equals("Error")).Content(Equals("Cannot ignore .gitignore")).Confirm() - - t.FileSystem().FileContent(".gitignore", Equals("")) - t.FileSystem().FileContent(".git/info/exclude", DoesNotContain(".gitignore")) - }). - SelectNextItem(). - Press(keys.Files.IgnoreFile). - // exclude a file - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .git/info/exclude")).Confirm() - - t.FileSystem().FileContent(".gitignore", Equals("")) - t.FileSystem().FileContent(".git/info/exclude", Contains("toExclude")) - }). - SelectNextItem(). - Press(keys.Files.IgnoreFile). - // ignore a file - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .gitignore")).Confirm() - - t.FileSystem().FileContent(".gitignore", Equals("toIgnore\n")) - t.FileSystem().FileContent(".git/info/exclude", Contains("toExclude")) - }) - }, -}) diff --git a/pkg/integration/tests/file/gitignore_special_characters.go b/pkg/integration/tests/file/gitignore_special_characters.go deleted file mode 100644 index 84aa57ec33b..00000000000 --- a/pkg/integration/tests/file/gitignore_special_characters.go +++ /dev/null @@ -1,66 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var GitignoreSpecialCharacters = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Ignore files with special characters in their names", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFile(".gitignore", "") - shell.CreateFile("#file", "") - shell.CreateFile("file#abc", "") - shell.CreateFile("!file", "") - shell.CreateFile("file!abc", "") - shell.CreateFile("abc*def", "") - shell.CreateFile("abc_def", "") - shell.CreateFile("file[x]", "") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - excludeFile := func(fileName string) { - t.Views().Files(). - NavigateToLine(Contains(fileName)). - Press(keys.Files.IgnoreFile) - - t.ExpectPopup().Menu(). - Title(Equals("Ignore or exclude file")). - Select(Contains("Add to .gitignore")). - Confirm() - } - - t.Views().Files(). - Focus(). - Lines( - Equals("▼ /"), - Equals(" ?? !file"), - Equals(" ?? #file"), - Equals(" ?? .gitignore"), - Equals(" ?? abc*def"), - Equals(" ?? abc_def"), - Equals(" ?? file!abc"), - Equals(" ?? file#abc"), - Equals(" ?? file[x]"), - ) - - excludeFile("#file") - excludeFile("file#abc") - excludeFile("!file") - excludeFile("file!abc") - excludeFile("abc*def") - excludeFile("file[x]") - - t.Views().Files(). - Lines( - Equals("▼ /"), - Equals(" ?? .gitignore"), - Equals(" ?? abc_def"), - ) - - t.FileSystem().FileContent(".gitignore", Equals("\\#file\nfile#abc\n\\!file\nfile!abc\nabc\\*def\nfile\\[x\\]\n")) - }, -}) diff --git a/pkg/integration/tests/file/remember_commit_message_after_fail.go b/pkg/integration/tests/file/remember_commit_message_after_fail.go deleted file mode 100644 index b6938ba5520..00000000000 --- a/pkg/integration/tests/file/remember_commit_message_after_fail.go +++ /dev/null @@ -1,67 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var preCommitHook = `#!/bin/bash - -if [[ -f bad ]]; then - exit 1 -fi -` - -var RememberCommitMessageAfterFail = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that the commit message is remembered after a failed attempt at committing", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFile(".git/hooks/pre-commit", preCommitHook) - shell.MakeExecutable(".git/hooks/pre-commit") - - shell.CreateFileAndAdd("one", "one") - - // the presence of this file will cause the pre-commit hook to fail - shell.CreateFile("bad", "bad") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /"), - Contains("bad"), - Contains("one"), - ). - Press(keys.Files.CommitChanges). - Tap(func() { - t.ExpectPopup().CommitMessagePanel().Type("my message").Confirm() - - t.ExpectPopup().Alert().Title(Equals("Error")).Content(Contains("Git command failed")).Confirm() - }). - NavigateToLine(Contains("bad")). - Press(keys.Universal.Remove). // remove file that triggers pre-commit hook to fail - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - }). - Lines( - Contains("one"), - ). - Press(keys.Files.CommitChanges). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("my message")). // it remembered the commit message - Confirm() - - t.Views().Commits(). - Lines( - Contains("my message"), - ) - }) - }, -}) diff --git a/pkg/integration/tests/file/rename_similarity_threshold_change.go b/pkg/integration/tests/file/rename_similarity_threshold_change.go deleted file mode 100644 index ac3ae37d32f..00000000000 --- a/pkg/integration/tests/file/rename_similarity_threshold_change.go +++ /dev/null @@ -1,47 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RenameSimilarityThresholdChange = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Change the rename similarity threshold while in the files panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("original", "one\ntwo\nthree\nfour\nfive\n") - shell.Commit("add original") - - shell.DeleteFileAndAdd("original") - shell.CreateFileAndAdd("renamed", "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /"), - Equals(" D original"), - Equals(" A renamed"), - ). - Press(keys.Universal.DecreaseRenameSimilarityThreshold). - Tap(func() { - t.ExpectToast(Equals("Changed rename similarity threshold to 45%")) - }). - Lines( - Equals("R original → renamed"), - ). - Press(keys.Universal.FocusMainView). - Tap(func() { - t.Views().Main(). - Press(keys.Universal.IncreaseRenameSimilarityThreshold) - t.ExpectToast(Equals("Changed rename similarity threshold to 50%")) - }). - Lines( - Equals("▼ /"), - Equals(" D original"), - Equals(" A renamed"), - ) - }, -}) diff --git a/pkg/integration/tests/file/renamed_files.go b/pkg/integration/tests/file/renamed_files.go deleted file mode 100644 index ec2ecc15191..00000000000 --- a/pkg/integration/tests/file/renamed_files.go +++ /dev/null @@ -1,36 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RenamedFiles = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Regression test for the display of renamed files in the file tree", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateDir("dir") - shell.CreateDir("dir/nested") - shell.CreateFileAndAdd("file1", "file1 content\n") - shell.CreateFileAndAdd("dir/file2", "file2 content\n") - shell.CreateFileAndAdd("dir/nested/file3", "file3 content\n") - shell.Commit("initial commit") - shell.RunCommand([]string{"git", "mv", "file1", "dir/file1"}) - shell.RunCommand([]string{"git", "mv", "dir/file2", "dir/file2-renamed"}) - shell.RunCommand([]string{"git", "mv", "dir/nested/file3", "file3"}) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir"), - Equals(" R file1 → file1"), - Equals(" R file2 → file2-renamed"), - Equals(" R dir/nested/file3 → file3"), - ) - }, -}) diff --git a/pkg/integration/tests/file/renamed_files_no_root_item.go b/pkg/integration/tests/file/renamed_files_no_root_item.go deleted file mode 100644 index 3ed8b22513c..00000000000 --- a/pkg/integration/tests/file/renamed_files_no_root_item.go +++ /dev/null @@ -1,36 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RenamedFilesNoRootItem = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Regression test for the display of renamed files in the file tree, when the root item is disabled", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.ShowRootItemInFileTree = false - }, - SetupRepo: func(shell *Shell) { - shell.CreateDir("dir") - shell.CreateDir("dir/nested") - shell.CreateFileAndAdd("file1", "file1 content\n") - shell.CreateFileAndAdd("dir/file2", "file2 content\n") - shell.CreateFileAndAdd("dir/nested/file3", "file3 content\n") - shell.Commit("initial commit") - shell.RunCommand([]string{"git", "mv", "file1", "dir/file1"}) - shell.RunCommand([]string{"git", "mv", "dir/file2", "dir/file2-renamed"}) - shell.RunCommand([]string{"git", "mv", "dir/nested/file3", "file3"}) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ dir"), - Equals(" R file1 → file1"), - Equals(" R file2 → file2-renamed"), - Equals("R dir/nested/file3 → file3"), - ) - }, -}) diff --git a/pkg/integration/tests/file/shared.go b/pkg/integration/tests/file/shared.go deleted file mode 100644 index 3e20512ea0b..00000000000 --- a/pkg/integration/tests/file/shared.go +++ /dev/null @@ -1,65 +0,0 @@ -package file - -import ( - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -func createAllPossiblePermutationsOfChangedFiles(shell *Shell) { - // typically we would use more bespoke shell methods here, but I struggled to find a way to do that, - // and this is copied over from a legacy integration test which did everything in a big shell script - // so I'm just copying it across. - - // common stuff - shell.RunShellCommand(`echo test > both-deleted.txt`) - shell.RunShellCommand(`git checkout -b conflict && git add both-deleted.txt`) - shell.RunShellCommand(`echo bothmodded > both-modded.txt && git add both-modded.txt`) - shell.RunShellCommand(`echo haha > deleted-them.txt && git add deleted-them.txt`) - shell.RunShellCommand(`echo haha2 > deleted-us.txt && git add deleted-us.txt`) - shell.RunShellCommand(`echo mod > modded.txt && git add modded.txt`) - shell.RunShellCommand(`echo mod > modded-staged.txt && git add modded-staged.txt`) - shell.RunShellCommand(`echo del > deleted.txt && git add deleted.txt`) - shell.RunShellCommand(`echo del > deleted-staged.txt && git add deleted-staged.txt`) - shell.RunShellCommand(`echo change-delete > change-delete.txt && git add change-delete.txt`) - shell.RunShellCommand(`echo delete-change > delete-change.txt && git add delete-change.txt`) - shell.RunShellCommand(`echo double-modded > double-modded.txt && git add double-modded.txt`) - shell.RunShellCommand(`echo "renamed\nhaha" > renamed.txt && git add renamed.txt`) - shell.RunShellCommand(`git commit -m one`) - - // stuff on other branch - shell.RunShellCommand(`git branch conflict_second && git mv both-deleted.txt added-them-changed-us.txt`) - shell.RunShellCommand(`git commit -m "both-deleted.txt renamed in added-them-changed-us.txt"`) - shell.RunShellCommand(`echo blah > both-added.txt && git add both-added.txt`) - shell.RunShellCommand(`echo mod1 > both-modded.txt && git add both-modded.txt`) - shell.RunShellCommand(`rm deleted-them.txt && git add deleted-them.txt`) - shell.RunShellCommand(`echo modded > deleted-us.txt && git add deleted-us.txt`) - shell.RunShellCommand(`git commit -m "two"`) - - // stuff on our branch - shell.RunShellCommand(`git checkout conflict_second`) - shell.RunShellCommand(`git mv both-deleted.txt changed-them-added-us.txt`) - shell.RunShellCommand(`git commit -m "both-deleted.txt renamed in changed-them-added-us.txt"`) - shell.RunShellCommand(`echo mod2 > both-modded.txt && git add both-modded.txt`) - shell.RunShellCommand(`echo blah2 > both-added.txt && git add both-added.txt`) - shell.RunShellCommand(`echo modded > deleted-them.txt && git add deleted-them.txt`) - shell.RunShellCommand(`rm deleted-us.txt && git add deleted-us.txt`) - shell.RunShellCommand(`git commit -m "three"`) - shell.RunShellCommand(`git reset --hard conflict_second`) - shell.RunCommandExpectError([]string{"git", "merge", "conflict"}) - - shell.RunShellCommand(`echo "new" > new.txt`) - shell.RunShellCommand(`echo "new staged" > new-staged.txt && git add new-staged.txt`) - shell.RunShellCommand(`echo mod2 > modded.txt`) - shell.RunShellCommand(`echo mod2 > modded-staged.txt && git add modded-staged.txt`) - shell.RunShellCommand(`rm deleted.txt`) - shell.RunShellCommand(`rm deleted-staged.txt && git add deleted-staged.txt`) - shell.RunShellCommand(`echo change-delete2 > change-delete.txt && git add change-delete.txt`) - shell.RunShellCommand(`rm change-delete.txt`) - shell.RunShellCommand(`rm delete-change.txt && git add delete-change.txt`) - shell.RunShellCommand(`echo "changed" > delete-change.txt`) - shell.RunShellCommand(`echo "change1" > double-modded.txt && git add double-modded.txt`) - shell.RunShellCommand(`echo "change2" > double-modded.txt`) - shell.RunShellCommand(`echo before > added-changed.txt && git add added-changed.txt`) - shell.RunShellCommand(`echo after > added-changed.txt`) - shell.RunShellCommand(`rm renamed.txt && git add renamed.txt`) - shell.RunShellCommand(`echo "renamed\nhaha" > renamed2.txt && git add renamed2.txt`) -} diff --git a/pkg/integration/tests/file/stage_children_range_select.go b/pkg/integration/tests/file/stage_children_range_select.go deleted file mode 100644 index 5bcf3033664..00000000000 --- a/pkg/integration/tests/file/stage_children_range_select.go +++ /dev/null @@ -1,47 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StageChildrenRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stage a range of files/folders and their children using range select", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFile("foo", "") - shell.CreateFile("foobar", "") - shell.CreateFile("baz/file", "") - shell.CreateFile("bazbam/file", "") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ baz"), - Equals(" ?? file"), - Equals(" ▼ bazbam"), - Equals(" ?? file"), - Equals(" ?? foo"), - Equals(" ?? foobar"), - ). - // Select everything - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("foobar")). - // Stage - PressPrimaryAction(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ baz").IsSelected(), - Equals(" A file").IsSelected(), - Equals(" ▼ bazbam").IsSelected(), - Equals(" A file").IsSelected(), - Equals(" A foo").IsSelected(), - Equals(" A foobar").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/file/stage_deleted_range_select.go b/pkg/integration/tests/file/stage_deleted_range_select.go deleted file mode 100644 index 702f1bf6c12..00000000000 --- a/pkg/integration/tests/file/stage_deleted_range_select.go +++ /dev/null @@ -1,55 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StageDeletedRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stage a range of deleted files using range select", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file-a", "") - shell.CreateFileAndAdd("file-b", "") - shell.Commit("first commit") - - shell.DeleteFile("file-a") - shell.DeleteFile("file-b") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" D file-a"), - Equals(" D file-b"), - ). - SelectNextItem(). - // Stage a single deleted file - PressPrimaryAction(). - Lines( - Equals("▼ /"), - Equals(" D file-a").IsSelected(), - Equals(" D file-b"), - ). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("file-b")). - // Stage both files while a deleted file is already staged - PressPrimaryAction(). - Lines( - Equals("▼ /"), - Equals(" D file-a").IsSelected(), - Equals(" D file-b").IsSelected(), - ). - // Unstage; back to everything being unstaged - PressPrimaryAction(). - Lines( - Equals("▼ /"), - Equals(" D file-a").IsSelected(), - Equals(" D file-b").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/file/stage_range_select.go b/pkg/integration/tests/file/stage_range_select.go deleted file mode 100644 index ea80e3ff6a0..00000000000 --- a/pkg/integration/tests/file/stage_range_select.go +++ /dev/null @@ -1,112 +0,0 @@ -package file - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StageRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stage/unstage a range of files using range select", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("dir2/file-d", "old content") - shell.Commit("first commit") - shell.UpdateFile("dir2/file-d", "new content") - - shell.CreateFile("dir1/file-a", "") - shell.CreateFile("dir1/file-b", "") - shell.CreateFile("dir2/file-c", "") - shell.CreateFile("file-e", "") - shell.CreateFile("file-f", "") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ dir1"), - Equals(" ?? file-a"), - Equals(" ?? file-b"), - Equals(" ▼ dir2"), - Equals(" ?? file-c"), - Equals(" M file-d"), - Equals(" ?? file-e"), - Equals(" ?? file-f"), - ). - NavigateToLine(Contains("file-b")). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("file-c")). - // Stage - PressPrimaryAction(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ?? file-a"), - Equals(" A file-b").IsSelected(), - Equals(" ▼ dir2").IsSelected(), - Equals(" A file-c").IsSelected(), - // Staged because dir2 was part of the selection when he hit space - Equals(" M file-d"), - Equals(" ?? file-e"), - Equals(" ?? file-f"), - ). - // Unstage; back to everything being unstaged - PressPrimaryAction(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ?? file-a"), - Equals(" ?? file-b").IsSelected(), - Equals(" ▼ dir2").IsSelected(), - Equals(" ?? file-c").IsSelected(), - Equals(" M file-d"), - Equals(" ?? file-e"), - Equals(" ?? file-f"), - ). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("dir2")). - // Verify that collapsed directories can be included in the range. - // Collapse the directory - PressEnter(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ?? file-a"), - Equals(" ?? file-b"), - Equals(" ▶ dir2").IsSelected(), - Equals(" ?? file-e"), - Equals(" ?? file-f"), - ). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("file-e")). - // Stage - PressPrimaryAction(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ?? file-a"), - Equals(" ?? file-b"), - Equals(" ▶ dir2").IsSelected(), - Equals(" A file-e").IsSelected(), - Equals(" ?? file-f"), - ). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("dir2")). - // Expand the directory again to verify it's been staged - PressEnter(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ?? file-a"), - Equals(" ?? file-b"), - Equals(" ▼ dir2").IsSelected(), - Equals(" A file-c"), - Equals(" M file-d"), - Equals(" A file-e"), - Equals(" ?? file-f"), - ) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_by_file_status.go b/pkg/integration/tests/filter_and_search/filter_by_file_status.go deleted file mode 100644 index 2e706936ffb..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_by_file_status.go +++ /dev/null @@ -1,71 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterByFileStatus = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filtering to show untracked files in repo that hides them by default", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - // need to set untracked files to not be displayed in git config - shell.SetConfig("status.showUntrackedFiles", "no") - - shell.CreateFileAndAdd("file-tracked", "foo") - - shell.Commit("first commit") - - shell.CreateFile("file-untracked", "bar") - shell.UpdateFile("file-tracked", "baz") - - shell.CreateFile("file-staged-but-untracked", "qux") - shell.GitAdd("file-staged-but-untracked") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Focus(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" A file-staged-but-untracked"), - Equals(" M file-tracked"), - ). - Press(keys.Files.OpenStatusFilter). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Show only untracked files")). - Confirm() - }). - Lines( - Equals("?? file-untracked").IsSelected(), - ). - Press(keys.Files.OpenStatusFilter). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Show only tracked files")). - Confirm() - }). - Lines( - Equals("▼ /").IsSelected(), - Equals(" A file-staged-but-untracked"), - Equals(" M file-tracked"), - ). - Press(keys.Files.OpenStatusFilter). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("No filter")). - Confirm() - }). - Lines( - Equals("▼ /").IsSelected(), - Equals(" A file-staged-but-untracked"), - Equals(" M file-tracked"), - ) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_commit_files.go b/pkg/integration/tests/filter_and_search/filter_commit_files.go deleted file mode 100644 index 953eaf34d39..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_commit_files.go +++ /dev/null @@ -1,84 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterCommitFiles = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Basic commit file filtering by text", - ExtraCmdArgs: []string{}, - Skip: true, // skipping until we have implemented file view filtering - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateDir("folder1") - shell.CreateFileAndAdd("folder1/apple-grape", "apple-grape") - shell.CreateFileAndAdd("folder1/apple-orange", "apple-orange") - shell.CreateFileAndAdd("folder1/grape-orange", "grape-orange") - shell.Commit("first commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains(`first commit`).IsSelected(), - ). - Press(keys.Universal.Confirm) - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains(`folder1`).IsSelected(), - Contains(`apple-grape`), - Contains(`apple-orange`), - Contains(`grape-orange`), - ). - Press(keys.Files.ToggleTreeView). - Lines( - Contains(`folder1/apple-grape`).IsSelected(), - Contains(`folder1/apple-orange`), - Contains(`folder1/grape-orange`), - ). - FilterOrSearch("apple"). - Lines( - Contains(`folder1/apple-grape`).IsSelected(), - Contains(`folder1/apple-orange`), - ). - Press(keys.Files.ToggleTreeView). - // filter still applies when we toggle tree view - Lines( - Contains(`folder1`), - Contains(`apple-grape`).IsSelected(), - Contains(`apple-orange`), - ). - Press(keys.Files.ToggleTreeView). - Lines( - Contains(`folder1/apple-grape`).IsSelected(), - Contains(`folder1/apple-orange`), - ). - NavigateToLine(Contains(`folder1/apple-orange`)). - Press(keys.Universal.Return). - Lines( - Contains(`folder1/apple-grape`), - // selection is retained after escaping filter mode - Contains(`folder1/apple-orange`).IsSelected(), - Contains(`folder1/grape-orange`), - ). - Tap(func() { - t.Views().Search().IsInvisible() - }). - Press(keys.Files.ToggleTreeView). - Lines( - Contains(`folder1`), - Contains(`apple-grape`), - Contains(`apple-orange`).IsSelected(), - Contains(`grape-orange`), - ). - FilterOrSearch("folder1/grape"). - Lines( - // first item is always selected after filtering - Contains(`folder1`).IsSelected(), - Contains(`grape-orange`), - ) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_files.go b/pkg/integration/tests/filter_and_search/filter_files.go deleted file mode 100644 index 6eae90c18e0..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_files.go +++ /dev/null @@ -1,76 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterFiles = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Basic file filtering by text", - ExtraCmdArgs: []string{}, - Skip: true, // Skipping until we have implemented file view filtering - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateDir("folder1") - shell.CreateFile("folder1/apple-grape", "apple-grape") - shell.CreateFile("folder1/apple-orange", "apple-orange") - shell.CreateFile("folder1/grape-orange", "grape-orange") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Focus(). - Lines( - Contains(`folder1`).IsSelected(), - Contains(`apple-grape`), - Contains(`apple-orange`), - Contains(`grape-orange`), - ). - Press(keys.Files.ToggleTreeView). - Lines( - Contains(`folder1/apple-grape`).IsSelected(), - Contains(`folder1/apple-orange`), - Contains(`folder1/grape-orange`), - ). - FilterOrSearch("apple"). - Lines( - Contains(`folder1/apple-grape`).IsSelected(), - Contains(`folder1/apple-orange`), - ). - Press(keys.Files.ToggleTreeView). - // filter still applies when we toggle tree view - Lines( - Contains(`folder1`), - Contains(`apple-grape`).IsSelected(), - Contains(`apple-orange`), - ). - Press(keys.Files.ToggleTreeView). - Lines( - Contains(`folder1/apple-grape`).IsSelected(), - Contains(`folder1/apple-orange`), - ). - NavigateToLine(Contains(`folder1/apple-orange`)). - Press(keys.Universal.Return). - Lines( - Contains(`folder1/apple-grape`), - // selection is retained after escaping filter mode - Contains(`folder1/apple-orange`).IsSelected(), - Contains(`folder1/grape-orange`), - ). - Tap(func() { - t.Views().Search().IsInvisible() - }). - Press(keys.Files.ToggleTreeView). - Lines( - Contains(`folder1`), - Contains(`apple-grape`), - Contains(`apple-orange`).IsSelected(), - Contains(`grape-orange`), - ). - FilterOrSearch("folder1/grape"). - Lines( - // first item is always selected after filtering - Contains(`folder1`).IsSelected(), - Contains(`grape-orange`), - ) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_fuzzy.go b/pkg/integration/tests/filter_and_search/filter_fuzzy.go deleted file mode 100644 index 1412f2da251..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_fuzzy.go +++ /dev/null @@ -1,37 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterFuzzy = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that fuzzy filtering works (not just exact matches)", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.FilterMode = "fuzzy" - }, - SetupRepo: func(shell *Shell) { - shell.NewBranch("this-is-my-branch") - shell.EmptyCommit("first commit") - shell.NewBranch("other-branch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains(`other-branch`).IsSelected(), - Contains(`this-is-my-branch`), - ). - FilterOrSearch("timb"). // using first letters of words - Lines( - Contains(`this-is-my-branch`).IsSelected(), - ). - FilterOrSearch("brnch"). // allows missing letter - Lines( - Contains(`other-branch`).IsSelected(), - Contains(`this-is-my-branch`), - ) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_menu.go b/pkg/integration/tests/filter_and_search/filter_menu.go deleted file mode 100644 index e5b6b216e09..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_menu.go +++ /dev/null @@ -1,54 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterMenu = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filtering the keybindings menu", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFile("myfile", "myfile") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Contains(`??`).Contains(`myfile`).IsSelected(), - ). - Press(keys.Universal.OptionMenu). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Keybindings")). - Filter("Ignore"). - Lines( - // menu has filtered down to the one item that matches the filter - Contains(`--- Local ---`), - Contains(`Ignore`).IsSelected(), - ). - Confirm() - - t.ExpectPopup().Menu(). - Title(Equals("Ignore or exclude file")). - Select(Contains("Add to .gitignore")). - Confirm() - }) - - t.Views().Files(). - IsFocused(). - Lines( - // file has been ignored - Contains(`.gitignore`).IsSelected(), - ). - // Upon opening the menu again, the filter should have been reset - Press(keys.Universal.OptionMenu). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Keybindings")). - LineCount(GreaterThan(2)) - }) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_menu_by_keybinding.go b/pkg/integration/tests/filter_and_search/filter_menu_by_keybinding.go deleted file mode 100644 index aee4b907abd..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_menu_by_keybinding.go +++ /dev/null @@ -1,38 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterMenuByKeybinding = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filtering the keybindings menu by keybinding", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Press(keys.Universal.OptionMenu). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Keybindings")). - Filter("@+"). - Lines( - // menu has filtered down to the one item that matches the filter - Contains("--- Global ---"), - Contains("+ Next screen mode").IsSelected(), - ). - Confirm() - }). - - // Upon opening the menu again, the filter should have been reset - Press(keys.Universal.OptionMenu). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Keybindings")). - LineCount(GreaterThan(1)) - }) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_menu_cancel_filter_with_escape.go b/pkg/integration/tests/filter_and_search/filter_menu_cancel_filter_with_escape.go deleted file mode 100644 index daf55fd0d21..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_menu_cancel_filter_with_escape.go +++ /dev/null @@ -1,37 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterMenuCancelFilterWithEscape = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filtering the keybindings menu, then pressing esc to turn off the filter", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files().IsFocused(). - Press(keys.Universal.OptionMenu) - - t.ExpectPopup().Menu(). - Title(Equals("Keybindings")). - Filter("Ignore"). - Lines( - // menu has filtered down to the one item that matches the filter - Contains(`--- Local ---`), - Contains(`Ignore`).IsSelected(), - ) - - // Escape should cancel the filter, not close the menu - t.GlobalPress(keys.Universal.Return) - t.ExpectPopup().Menu(). - Title(Equals("Keybindings")). - LineCount(GreaterThan(1)) - - // Another escape closes the menu - t.GlobalPress(keys.Universal.Return) - t.Views().Files().IsFocused() - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_menu_with_no_keybindings.go b/pkg/integration/tests/filter_and_search/filter_menu_with_no_keybindings.go deleted file mode 100644 index 1d9ef589f6d..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_menu_with_no_keybindings.go +++ /dev/null @@ -1,32 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterMenuWithNoKeybindings = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filtering the keybindings menu so that only entries without keybinding are left", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Keybinding.Universal.ToggleWhitespaceInDiffView = "" - }, - SetupRepo: func(shell *Shell) { - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Press(keys.Universal.OptionMenu) - - t.ExpectPopup().Menu(). - Title(Equals("Keybindings")). - Filter("whitespace"). - Lines( - // menu has filtered down to the one item that matches the - // filter, and it doesn't have a keybinding - Equals("--- Global ---"), - Equals("Toggle whitespace").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_remote_branches.go b/pkg/integration/tests/filter_and_search/filter_remote_branches.go deleted file mode 100644 index 11cfea30b3d..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_remote_branches.go +++ /dev/null @@ -1,59 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterRemoteBranches = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filtering remote branches", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("branch-apple") - shell.EmptyCommit("commit-one") - shell.NewBranch("branch-grape") - shell.NewBranch("branch-orange") - - shell.CloneIntoRemote("origin") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Remotes(). - Focus(). - Lines( - Contains(`origin`).IsSelected(), - ). - PressEnter() - - t.Views().RemoteBranches(). - IsFocused(). - Lines( - Contains(`branch-apple`).IsSelected(), - Contains(`branch-grape`), - Contains(`branch-orange`), - ). - FilterOrSearch("grape"). - Lines( - Contains(`branch-grape`).IsSelected(), - ). - // cancel the filter - PressEscape(). - Tap(func() { - t.Views().Search().IsInvisible() - }). - Lines( - Contains(`branch-apple`), - Contains(`branch-grape`).IsSelected(), - Contains(`branch-orange`), - ). - // return to remotes view - PressEscape() - - t.Views().Remotes(). - IsFocused(). - Lines( - Contains(`origin`).IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_remotes.go b/pkg/integration/tests/filter_and_search/filter_remotes.go deleted file mode 100644 index b905a06ed4e..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_remotes.go +++ /dev/null @@ -1,42 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterRemotes = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filtering remotes", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("commit-one") - shell.CloneIntoRemote("remote1") - shell.CloneIntoRemote("remote2") - shell.CloneIntoRemote("remote3") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Remotes(). - Focus(). - Lines( - Contains("remote1").IsSelected(), - Contains("remote2"), - Contains("remote3"), - ). - FilterOrSearch("2"). - Lines( - Contains("remote2").IsSelected(), - ). - // cancel the filter - PressEscape(). - Tap(func() { - t.Views().Search().IsInvisible() - }). - Lines( - Contains("remote1"), - Contains("remote2").IsSelected(), - Contains("remote3"), - ) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_search_history.go b/pkg/integration/tests/filter_and_search/filter_search_history.go deleted file mode 100644 index 1b906319f28..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_search_history.go +++ /dev/null @@ -1,77 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterSearchHistory = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Navigating search history", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - // populate search history with some values - FilterOrSearch("1"). - FilterOrSearch("2"). - FilterOrSearch("3"). - Press(keys.Universal.StartSearch). - // clear initial search value - Tap(func() { - t.ExpectSearch().Clear() - }). - // test main search history functionality - Tap(func() { - t.Views().Search(). - Press(keys.Universal.PrevItem). - Content(Contains("3")). - Press(keys.Universal.PrevItem). - Content(Contains("2")). - Press(keys.Universal.PrevItem). - Content(Contains("1")). - Press(keys.Universal.PrevItem). - Content(Contains("1")). - Press(keys.Universal.NextItem). - Content(Contains("2")). - Press(keys.Universal.NextItem). - Content(Contains("3")). - Press(keys.Universal.NextItem). - Content(Contains("")). - Press(keys.Universal.NextItem). - Content(Contains("")). - Press(keys.Universal.PrevItem). - Content(Contains("3")). - PressEscape() - }). - // test that it resets after you enter and exit a search - Press(keys.Universal.StartSearch). - Tap(func() { - t.Views().Search(). - Press(keys.Universal.PrevItem). - Content(Contains("3")). - PressEscape() - }) - - // test that the histories are separate for each view - t.Views().Commits(). - Focus(). - FilterOrSearch("a"). - FilterOrSearch("b"). - FilterOrSearch("c"). - Press(keys.Universal.StartSearch). - Tap(func() { - t.ExpectSearch().Clear() - }). - Tap(func() { - t.Views().Search(). - Press(keys.Universal.PrevItem). - Content(Contains("c")). - Press(keys.Universal.PrevItem). - Content(Contains("b")). - Press(keys.Universal.PrevItem). - Content(Contains("a")) - }) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/filter_updates_when_model_changes.go b/pkg/integration/tests/filter_and_search/filter_updates_when_model_changes.go deleted file mode 100644 index 5def8bfe567..00000000000 --- a/pkg/integration/tests/filter_and_search/filter_updates_when_model_changes.go +++ /dev/null @@ -1,73 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterUpdatesWhenModelChanges = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that after deleting a branch the filter is reapplied to show only the remaining branches", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - shell.NewBranch("branch-to-delete") - shell.NewBranch("other") - shell.NewBranch("checked-out-branch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("checked-out-branch").IsSelected(), - Contains("branch-to-delete"), - Contains("master"), - Contains("other"), - ). - FilterOrSearch("branch"). - Lines( - Contains("checked-out-branch").IsSelected(), - Contains("branch-to-delete"), - ). - SelectNextItem(). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'branch-to-delete'?")). - Select(Contains("Delete local branch")). - Confirm() - }). - Lines( - Contains("checked-out-branch").IsSelected(), - ) - - // Verify that updating the filter works even if the view is not the active one - t.Views().Files().Focus() - - // To do that, we use a custom command to create a new branch that matches the filter - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - Type("git branch new-branch"). - Confirm() - - t.Views().Branches(). - Lines( - Contains("checked-out-branch").IsSelected(), - Contains("new-branch"), - ) - - t.Views().Branches(). - Focus(). - // cancel the filter - PressEscape(). - Lines( - Contains("checked-out-branch").IsSelected(), - Contains("master"), - Contains("new-branch"), - Contains("other"), - ) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/nested_filter.go b/pkg/integration/tests/filter_and_search/nested_filter.go deleted file mode 100644 index 703c7ccf145..00000000000 --- a/pkg/integration/tests/filter_and_search/nested_filter.go +++ /dev/null @@ -1,157 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var NestedFilter = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter in the several nested panels and verify the filters are preserved as you escape back to the surface", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - // need to create some branches, each with their own commits - shell.NewBranch("branch-gold") - shell.CreateFileAndAdd("apple", "apple") - shell.CreateFileAndAdd("orange", "orange") - shell.CreateFileAndAdd("grape", "grape") - shell.Commit("commit-knife") - - shell.NewBranch("branch-silver") - shell.UpdateFileAndAdd("apple", "apple-2") - shell.UpdateFileAndAdd("orange", "orange-2") - shell.UpdateFileAndAdd("grape", "grape-2") - shell.Commit("commit-spoon") - - shell.NewBranch("branch-bronze") - shell.UpdateFileAndAdd("apple", "apple-3") - shell.UpdateFileAndAdd("orange", "orange-3") - shell.UpdateFileAndAdd("grape", "grape-3") - shell.Commit("commit-fork") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains(`branch-bronze`).IsSelected(), - Contains(`branch-gold`), - Contains(`branch-silver`), - ). - FilterOrSearch("sil"). - Lines( - Contains(`branch-silver`).IsSelected(), - ). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains(`commit-spoon`).IsSelected(), - Contains(`commit-knife`), - ). - FilterOrSearch("knife"). - Lines( - // sub-commits view searches, it doesn't filter, so we haven't filtered down the list - Contains(`commit-spoon`), - Contains(`commit-knife`).IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" A apple"), - Equals(" A grape"), - Equals(" A orange"), - ). - FilterOrSearch("grape"). - Lines( - Equals("▼ /"), - Equals(" A apple"), - Equals(" A grape").IsSelected(), - Equals(" A orange"), - ). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - FilterOrSearch("newline"). - SelectedLine(Contains("No newline at end of file")). - PressEscape(). // cancel search - Tap(func() { - t.Views().Search().IsInvisible() - }). - // escape to commit-files view - PressEscape() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /"), - Equals(" A apple"), - Equals(" A grape").IsSelected(), - Equals(" A orange"), - ). - Tap(func() { - t.Views().Search().IsVisible().Content(Contains("matches for 'grape'")) - }). - // cancel search - PressEscape(). - Tap(func() { - t.Views().Search().IsInvisible() - }). - Lines( - Equals("▼ /"), - Equals(" A apple"), - Equals(" A grape").IsSelected(), - Equals(" A orange"), - ). - // escape to sub-commits view - PressEscape() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains(`commit-spoon`), - Contains(`commit-knife`).IsSelected(), - ). - Tap(func() { - t.Views().Search().IsVisible().Content(Contains("matches for 'knife'")) - }). - // cancel search - PressEscape(). - Tap(func() { - t.Views().Search().IsInvisible() - }). - Lines( - Contains(`commit-spoon`), - // still selected - Contains(`commit-knife`).IsSelected(), - ). - // escape to branches view - PressEscape() - - t.Views().Branches(). - IsFocused(). - Lines( - Contains(`branch-silver`).IsSelected(), - ). - Tap(func() { - t.Views().Search().IsVisible().Content(Contains("matches for 'sil'")) - }). - // cancel search - PressEscape(). - Tap(func() { - t.Views().Search().IsInvisible() - }). - Lines( - Contains(`branch-bronze`), - Contains(`branch-gold`), - Contains(`branch-silver`).IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/nested_filter_transient.go b/pkg/integration/tests/filter_and_search/nested_filter_transient.go deleted file mode 100644 index 8548d68c001..00000000000 --- a/pkg/integration/tests/filter_and_search/nested_filter_transient.go +++ /dev/null @@ -1,109 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// This one requires some explanation: the sub-commits and diff-file contexts are -// 'transient' in that they are spawned inside a window when you need them, but -// can be relocated elsewhere if you need them somewhere else. So for example if -// I hit enter on a branch I'll see the sub-commits view, but if I then navigate -// to the reflog context and hit enter on a reflog, the sub-commits view is moved -// to the reflog window. This is because we reuse the same view (it's a limitation -// that would be nice to remove in the future). -// Nonetheless, we need to ensure that upon moving the view, the filter is cancelled. - -var NestedFilterTransient = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter in a transient panel (sub-commits and diff-files) and ensure filter is cancelled when the panel is moved", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - // need to create some branches, each with their own commits - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("file-one", "file-one") - shell.CreateFileAndAdd("file-two", "file-two") - shell.Commit("commit-one") - shell.EmptyCommit("commit-two") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains(`mybranch`).IsSelected(), - ). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains(`commit-two`).IsSelected(), - Contains(`commit-one`), - ). - FilterOrSearch("one"). - Lines( - Contains(`commit-two`), - Contains(`commit-one`).IsSelected(), - ) - - t.Views().ReflogCommits(). - Focus(). - SelectedLine(Contains("commit: commit-two")). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - // the search on the sub-commits context has been cancelled - Lines( - Contains(`commit-two`).IsSelected(), - Contains(`commit-one`), - ). - Tap(func() { - t.Views().Search().IsInvisible() - }). - NavigateToLine(Contains("commit-one")). - PressEnter() - - // Now let's test the commit files context - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" A file-one"), - Equals(" A file-two"), - ). - FilterOrSearch("two"). - Lines( - Equals("▼ /"), - Equals(" A file-one"), - Equals(" A file-two").IsSelected(), - ) - - t.Views().Branches(). - Focus(). - SelectedLine(Contains("mybranch")). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains(`commit-two`).IsSelected(), - Contains(`commit-one`), - ). - NavigateToLine(Contains("commit-one")). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - // the search on the commit-files context has been cancelled - Lines( - Equals("▼ /").IsSelected(), - Equals(" A file-one"), - Equals(" A file-two"), - ). - Tap(func() { - t.Views().Search().IsInvisible() - }) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/new_search.go b/pkg/integration/tests/filter_and_search/new_search.go deleted file mode 100644 index 9186dc085e6..00000000000 --- a/pkg/integration/tests/filter_and_search/new_search.go +++ /dev/null @@ -1,39 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// This is a regression test to ensure https://github.com/jesseduffield/lazygit/issues/2971 -// doesn't happen again - -var NewSearch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Start a new search and verify the search begins from the current cursor position, not from the current search match", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - // need to create some branches, each with their own commits - shell.EmptyCommit("Add foo") - shell.EmptyCommit("Remove foo") - shell.EmptyCommit("Add bar") - shell.EmptyCommit("Remove bar") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains(`Remove bar`).IsSelected(), - Contains(`Add bar`), - Contains(`Remove foo`), - Contains(`Add foo`), - ). - FilterOrSearch("Add"). - SelectedLine(Contains(`Add bar`)). - SelectPreviousItem(). - SelectedLine(Contains(`Remove bar`)). - FilterOrSearch("Remove"). - SelectedLine(Contains(`Remove bar`)) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/stage_all_stages_only_tracked_files_in_tracked_only_filter.go b/pkg/integration/tests/filter_and_search/stage_all_stages_only_tracked_files_in_tracked_only_filter.go deleted file mode 100644 index 004cb5eb704..00000000000 --- a/pkg/integration/tests/filter_and_search/stage_all_stages_only_tracked_files_in_tracked_only_filter.go +++ /dev/null @@ -1,54 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StageAllStagesOnlyTrackedFilesInTrackedOnlyFilter = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Staging all files in tracked only view should stage only tracked files", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file-tracked", "foo") - - shell.Commit("first commit") - - shell.CreateFile("file-untracked", "bar") - shell.UpdateFile("file-tracked", "baz") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Focus(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" M file-tracked"), - Equals(" ?? file-untracked"), - ). - Press(keys.Files.OpenStatusFilter). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Show only tracked files")). - Confirm() - }). - Lines( - Equals(" M file-tracked"), - ). - Press(keys.Files.ToggleStagedAll). - Press(keys.Files.OpenStatusFilter). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("No filter")). - Confirm() - }). - Lines( - Equals("▼ /").IsSelected(), - Equals(" M file-tracked"), // 'M' is now in the left column, so file is staged - Equals(" ?? file-untracked"), - ) - }, -}) diff --git a/pkg/integration/tests/filter_and_search/staging_folder_stages_only_tracked_files_in_tracked_only_filter.go b/pkg/integration/tests/filter_and_search/staging_folder_stages_only_tracked_files_in_tracked_only_filter.go deleted file mode 100644 index aa9220b95e8..00000000000 --- a/pkg/integration/tests/filter_and_search/staging_folder_stages_only_tracked_files_in_tracked_only_filter.go +++ /dev/null @@ -1,56 +0,0 @@ -package filter_and_search - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StagingFolderStagesOnlyTrackedFilesInTrackedOnlyFilter = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Staging entire folder in tracked only view, should stage only tracked files", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateDir("test") - shell.CreateFileAndAdd("test/file-tracked", "foo") - - shell.Commit("first commit") - - shell.CreateFile("test/file-untracked", "bar") - shell.UpdateFile("test/file-tracked", "baz") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Focus(). - Lines( - Equals("▼ test").IsSelected(), - Equals(" M file-tracked"), - Equals(" ?? file-untracked"), - ). - Press(keys.Files.OpenStatusFilter). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Show only tracked files")). - Confirm() - }). - Lines( - Equals("▼ test").IsSelected(), - Equals(" M file-tracked"), - ). - PressPrimaryAction(). - Press(keys.Files.OpenStatusFilter). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("No filter")). - Confirm() - }). - Lines( - Equals("▼ test").IsSelected(), - Equals(" M file-tracked"), // 'M' is now in the left column, so file is staged - Equals(" ?? file-untracked"), - ) - }, -}) diff --git a/pkg/integration/tests/filter_by_author/select_author.go b/pkg/integration/tests/filter_by_author/select_author.go deleted file mode 100644 index 281034c12b6..00000000000 --- a/pkg/integration/tests/filter_by_author/select_author.go +++ /dev/null @@ -1,70 +0,0 @@ -package filter_by_author - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SelectAuthor = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter commits using the currently highlighted commit's author when the commit view is active", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - commonSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - SelectedLineIdx(0). - Press(keys.Universal.FilteringMenu) - - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Filter by 'Paul Oberstein '")). - Confirm() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("commit 7"), - Contains("commit 6"), - Contains("commit 5"), - Contains("commit 4"), - Contains("commit 3"), - Contains("commit 2"), - Contains("commit 1"), - Contains("commit 0"), - ) - - t.Views().Information().Content(Contains("Filtering by 'Paul Oberstein '")) - - t.Views().Commits(). - Press(keys.Universal.FilteringMenu) - - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Stop filtering")). - Confirm() - - t.Views().Commits(). - IsFocused(). - NavigateToLine(Contains("SK commit 0")). - Press(keys.Universal.FilteringMenu) - - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Filter by 'Siegfried Kircheis '")). - Confirm() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("commit 0"), - ) - - t.Views().Information().Content(Contains("Filtering by 'Siegfried Kircheis '")) - }, -}) diff --git a/pkg/integration/tests/filter_by_author/shared.go b/pkg/integration/tests/filter_by_author/shared.go deleted file mode 100644 index 22d08ad5cad..00000000000 --- a/pkg/integration/tests/filter_by_author/shared.go +++ /dev/null @@ -1,30 +0,0 @@ -package filter_by_author - -import ( - "fmt" - "strings" - - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -type AuthorInfo struct { - name string - numberOfCommits int -} - -func commonSetup(shell *Shell) { - authors := []AuthorInfo{{"Yang Wen-li", 3}, {"Siegfried Kircheis", 1}, {"Paul Oberstein", 8}} - totalCommits := 0 - repoStartDaysAgo := 100 - - for _, authorInfo := range authors { - for i := range authorInfo.numberOfCommits { - authorEmail := strings.ToLower(strings.ReplaceAll(authorInfo.name, " ", ".")) + "@email.com" - commitMessage := fmt.Sprintf("commit %d", i) - - shell.SetAuthor(authorInfo.name, authorEmail) - shell.EmptyCommitDaysAgo(commitMessage, repoStartDaysAgo-totalCommits) - totalCommits++ - } - } -} diff --git a/pkg/integration/tests/filter_by_author/type_author.go b/pkg/integration/tests/filter_by_author/type_author.go deleted file mode 100644 index cb84d5757fb..00000000000 --- a/pkg/integration/tests/filter_by_author/type_author.go +++ /dev/null @@ -1,66 +0,0 @@ -package filter_by_author - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var TypeAuthor = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter commits by author using the typed in author", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - commonSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Status(). - Focus(). - Press(keys.Universal.FilteringMenu) - - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Enter author to filter by")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("Enter author:")). - Type("Yang"). - SuggestionLines(Equals("Yang Wen-li ")). - ConfirmFirstSuggestion() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("commit 2"), - Contains("commit 1"), - Contains("commit 0"), - ) - - t.Views().Information().Content(Contains("Filtering by 'Yang Wen-li '")) - - t.Views().Status(). - Focus(). - Press(keys.Universal.FilteringMenu) - - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Enter author to filter by")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("Enter author:")). - Type("Siegfried"). - SuggestionLines(Equals("Siegfried Kircheis ")). - ConfirmFirstSuggestion() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("commit 0"), - ) - - t.Views().Information().Content(Contains("Filtering by 'Siegfried Kircheis '")) - }, -}) diff --git a/pkg/integration/tests/filter_by_path/cli_arg.go b/pkg/integration/tests/filter_by_path/cli_arg.go deleted file mode 100644 index 5b3912829ac..00000000000 --- a/pkg/integration/tests/filter_by_path/cli_arg.go +++ /dev/null @@ -1,20 +0,0 @@ -package filter_by_path - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CliArg = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter commits by file path, using CLI arg", - ExtraCmdArgs: []string{"-f=filterFile"}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - commonSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - postFilterTest(t) - }, -}) diff --git a/pkg/integration/tests/filter_by_path/drop_commit_in_filtering_mode.go b/pkg/integration/tests/filter_by_path/drop_commit_in_filtering_mode.go deleted file mode 100644 index 7498730ef87..00000000000 --- a/pkg/integration/tests/filter_by_path/drop_commit_in_filtering_mode.go +++ /dev/null @@ -1,43 +0,0 @@ -package filter_by_path - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DropCommitInFilteringMode = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter commits by file path, then drop a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - commonSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - filterByFilterFile(t, keys) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains(`both files`).IsSelected(), - Contains(`only filterFile`), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Drop commit")). - Content(Equals("Are you sure you want to drop the selected commit(s)?")). - Confirm() - }). - Lines( - Contains(`only filterFile`).IsSelected(), - ). - Press(keys.Universal.Return). - Lines( - Contains(`none of the two`), - Contains(`only otherFile`), - Contains(`only filterFile`).IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/filter_by_path/keep_same_commit_selected_on_exit.go b/pkg/integration/tests/filter_by_path/keep_same_commit_selected_on_exit.go deleted file mode 100644 index a657bdedb3a..00000000000 --- a/pkg/integration/tests/filter_by_path/keep_same_commit_selected_on_exit.go +++ /dev/null @@ -1,52 +0,0 @@ -package filter_by_path - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var KeepSameCommitSelectedOnExit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "When exiting filtering mode, keep the same commit selected if possible", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - commonSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - filterByFilterFile(t, keys) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains(`both files`).IsSelected(), - Contains(`only filterFile`), - ). - Tap(func() { - t.Views().Main(). - ContainsLines( - Equals(" both files"), - Equals("---"), - Equals(" filterFile | 2 +-"), - Equals(" 1 file changed, 1 insertion(+), 1 deletion(-)"), - ) - }). - PressEscape(). - Lines( - Contains(`none of the two`), - Contains(`both files`).IsSelected(), - Contains(`only otherFile`), - Contains(`only filterFile`), - ) - - t.Views().Main(). - ContainsLines( - Equals(" both files"), - Equals("---"), - Equals(" filterFile | 2 +-"), - Equals(" otherFile | 2 +-"), - Equals(" 2 files changed, 2 insertions(+), 2 deletions(-)"), - ) - }, -}) diff --git a/pkg/integration/tests/filter_by_path/reword_commit_in_filtering_mode.go b/pkg/integration/tests/filter_by_path/reword_commit_in_filtering_mode.go deleted file mode 100644 index 8c28a7eac2b..00000000000 --- a/pkg/integration/tests/filter_by_path/reword_commit_in_filtering_mode.go +++ /dev/null @@ -1,46 +0,0 @@ -package filter_by_path - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RewordCommitInFilteringMode = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter commits by file path, then reword a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - commonSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - filterByFilterFile(t, keys) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains(`both files`).IsSelected(), - Contains(`only filterFile`), - ). - SelectNextItem(). - Press(keys.Commits.RenameCommit). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Clear(). - Type("new message"). - Confirm() - }). - Lines( - Contains(`both files`), - Contains(`new message`).IsSelected(), - ). - Press(keys.Universal.Return). - Lines( - Contains(`none of the two`), - Contains(`both files`), - Contains(`only otherFile`), - Contains(`new message`).IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/filter_by_path/select_file.go b/pkg/integration/tests/filter_by_path/select_file.go deleted file mode 100644 index a01081a22d1..00000000000 --- a/pkg/integration/tests/filter_by_path/select_file.go +++ /dev/null @@ -1,44 +0,0 @@ -package filter_by_path - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SelectFile = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter commits by file path, by finding file in UI and filtering on it", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - commonSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains(`none of the two`).IsSelected(), - Contains(`both files`), - Contains(`only otherFile`), - Contains(`only filterFile`), - ). - NavigateToLine(Contains(`both files`)). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals(`▼ /`).IsSelected(), - Equals(` M filterFile`), - Equals(` M otherFile`), - ). - SelectNextItem(). - Press(keys.Universal.FilteringMenu) - - t.ExpectPopup().Menu().Title(Equals("Filtering")). - Select(Contains("Filter by 'filterFile'")).Confirm() - - postFilterTest(t) - }, -}) diff --git a/pkg/integration/tests/filter_by_path/select_filtered_file_when_entering_commit.go b/pkg/integration/tests/filter_by_path/select_filtered_file_when_entering_commit.go deleted file mode 100644 index 5b73d00a51c..00000000000 --- a/pkg/integration/tests/filter_by_path/select_filtered_file_when_entering_commit.go +++ /dev/null @@ -1,47 +0,0 @@ -package filter_by_path - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SelectFilteredFileWhenEnteringCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter commits by file path, then enter a commit and ensure the file is selected", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "") - shell.CreateFileAndAdd("dir/file2", "") - shell.Commit("add files") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.GlobalPress(keys.Universal.FilteringMenu) - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Enter path to filter by")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("Enter path:")). - Type("dir/file2"). - Confirm() - - t.Views().Commits(). - Focus(). - Lines( - Contains("add files").IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir"), - Equals(" A file2").IsSelected(), - Equals(" A file1"), - ) - }, -}) diff --git a/pkg/integration/tests/filter_by_path/select_filtered_file_when_entering_commit_no_root_item.go b/pkg/integration/tests/filter_by_path/select_filtered_file_when_entering_commit_no_root_item.go deleted file mode 100644 index 9846d109a59..00000000000 --- a/pkg/integration/tests/filter_by_path/select_filtered_file_when_entering_commit_no_root_item.go +++ /dev/null @@ -1,47 +0,0 @@ -package filter_by_path - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SelectFilteredFileWhenEnteringCommitNoRootItem = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter commits by file path, then enter a commit and ensure the file is selected (with the show root item config off)", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.ShowRootItemInFileTree = false - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "") - shell.CreateFileAndAdd("dir/file2", "") - shell.Commit("add files") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.GlobalPress(keys.Universal.FilteringMenu) - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Enter path to filter by")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("Enter path:")). - Type("dir/file2"). - Confirm() - - t.Views().Commits(). - Focus(). - Lines( - Contains("add files").IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ dir"), - Equals(" A file2").IsSelected(), - Equals("A file1"), - ) - }, -}) diff --git a/pkg/integration/tests/filter_by_path/shared.go b/pkg/integration/tests/filter_by_path/shared.go deleted file mode 100644 index 015a11f97ea..00000000000 --- a/pkg/integration/tests/filter_by_path/shared.go +++ /dev/null @@ -1,73 +0,0 @@ -package filter_by_path - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -func commonSetup(shell *Shell) { - shell.CreateFileAndAdd("filterFile", "original filterFile content") - shell.Commit("only filterFile") - shell.CreateFileAndAdd("otherFile", "original otherFile content") - shell.Commit("only otherFile") - - shell.UpdateFileAndAdd("otherFile", "new otherFile content") - shell.UpdateFileAndAdd("filterFile", "new filterFile content") - shell.Commit("both files") - - shell.EmptyCommit("none of the two") -} - -func filterByFilterFile(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains(`none of the two`).IsSelected(), - Contains(`both files`), - Contains(`only otherFile`), - Contains(`only filterFile`), - ). - Press(keys.Universal.FilteringMenu) - - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Enter path to filter by")). - Confirm() - t.ExpectPopup().Prompt(). - Title(Equals("Enter path:")). - Type("filterF"). - SuggestionLines(Equals("filterFile")). - ConfirmFirstSuggestion() -} - -func postFilterTest(t *TestDriver) { - t.Views().Information().Content(Contains("Filtering by 'filterFile'")) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains(`both files`).IsSelected(), - Contains(`only filterFile`), - ) - - // we only show the filtered file's changes in the main view - t.Views().Main(). - ContainsLines( - Equals(" both files"), - Equals("---"), - Equals(" filterFile | 2 +-"), - Equals(" 1 file changed, 1 insertion(+), 1 deletion(-)"), - ) - - t.Views().Commits(). - PressEnter() - - // when you click into the commit itself, you see all files from that commit - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /"), - Contains(`filterFile`), - Contains(`otherFile`), - ) -} diff --git a/pkg/integration/tests/filter_by_path/show_diffs_for_renamed_file.go b/pkg/integration/tests/filter_by_path/show_diffs_for_renamed_file.go deleted file mode 100644 index 720a681e15e..00000000000 --- a/pkg/integration/tests/filter_by_path/show_diffs_for_renamed_file.go +++ /dev/null @@ -1,135 +0,0 @@ -package filter_by_path - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ShowDiffsForRenamedFile = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter commits by file path for a file that was renamed, and verify that it shows the diffs correctly", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("oldFile", "a\nb\nc\n") - shell.Commit("add old file") - shell.UpdateFileAndAdd("oldFile", "x\nb\nc\n") - shell.Commit("update old file") - shell.CreateFileAndAdd("unrelatedFile", "content of unrelated file\n") - shell.Commit("add unrelated file") - shell.RenameFileInGit("oldFile", "newFile") - shell.Commit("rename file") - shell.UpdateFileAndAdd("newFile", "y\nb\nc\n") - shell.UpdateFileAndAdd("unrelatedFile", "updated content of unrelated file\n") - shell.Commit("update both files") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("update both files").IsSelected(), - Contains("rename file"), - Contains("add unrelated file"), - Contains("update old file"), - Contains("add old file"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" M newFile"), - Equals(" M unrelatedFile"), - ). - SelectNextItem(). - Press(keys.Universal.FilteringMenu) - - t.ExpectPopup().Menu().Title(Equals("Filtering")). - Select(Contains("Filter by 'newFile'")).Confirm() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("update both files").IsSelected(), - Contains("rename file"), - Contains("update old file"), - Contains("add old file"), - ) - - t.Views().Main().ContainsLines( - Equals(" update both files"), - Equals("---"), - Equals(" newFile | 2 +-"), - Equals(" 1 file changed, 1 insertion(+), 1 deletion(-)"), - Equals(""), - Equals("diff --git a/newFile b/newFile"), - Contains("index"), - Equals("--- a/newFile"), - Equals("+++ b/newFile"), - Equals("@@ -1,3 +1,3 @@"), - Equals("-x"), - Equals("+y"), - Equals(" b"), - Equals(" c"), - ) - - t.Views().Commits().SelectNextItem() - - t.Views().Main().ContainsLines( - Equals(" rename file"), - Equals("---"), - Equals(" oldFile => newFile | 0"), - Equals(" 1 file changed, 0 insertions(+), 0 deletions(-)"), - Equals(""), - Equals("diff --git a/oldFile b/newFile"), - Equals("similarity index 100%"), - Equals("rename from oldFile"), - Equals("rename to newFile"), - ) - - t.Views().Commits().SelectNextItem() - - t.Views().Main().ContainsLines( - Equals(" update old file"), - Equals("---"), - Equals(" oldFile | 2 +-"), - Equals(" 1 file changed, 1 insertion(+), 1 deletion(-)"), - Equals(""), - Equals("diff --git a/oldFile b/oldFile"), - Contains("index"), - Equals("--- a/oldFile"), - Equals("+++ b/oldFile"), - Equals("@@ -1,3 +1,3 @@"), - Equals("-a"), - Equals("+x"), - Equals(" b"), - Equals(" c"), - ) - - t.Views().Commits(). - Press(keys.Universal.RangeSelectUp). - Press(keys.Universal.RangeSelectUp) - - t.Views().Main().ContainsLines( - Contains("Showing diff for range"), - Equals(""), - Equals(" oldFile => newFile | 2 +-"), - Equals(" 1 file changed, 1 insertion(+), 1 deletion(-)"), - Equals(""), - Equals("diff --git a/oldFile b/newFile"), - Equals("similarity index 66%"), - Equals("rename from oldFile"), - Equals("rename to newFile"), - Contains("index"), - Equals("--- a/oldFile"), - Equals("+++ b/newFile"), - Equals("@@ -1,3 +1,3 @@"), - Equals("-a"), - Equals("+y"), - Equals(" b"), - Equals(" c"), - ) - }, -}) diff --git a/pkg/integration/tests/filter_by_path/type_file.go b/pkg/integration/tests/filter_by_path/type_file.go deleted file mode 100644 index 650c12cbf69..00000000000 --- a/pkg/integration/tests/filter_by_path/type_file.go +++ /dev/null @@ -1,35 +0,0 @@ -package filter_by_path - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var TypeFile = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter commits by file path, by finding file in UI and filtering on it", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - commonSetup(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Press(keys.Universal.FilteringMenu) - - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Enter path to filter by")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("Enter path:")). - Type("filterF"). - SuggestionLines(Equals("filterFile")). - ConfirmFirstSuggestion() - - postFilterTest(t) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go b/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go deleted file mode 100644 index 1162a20c7ef..00000000000 --- a/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go +++ /dev/null @@ -1,71 +0,0 @@ -package interactive_rebase - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -const ( - BASE_BRANCH = "base-branch" - TOP_BRANCH = "top-branch" - BASE_COMMIT = "base-commit" - TOP_COMMIT = "top-commit" -) - -var AdvancedInteractiveRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "It begins an interactive rebase and verifies to have the possibility of editing the commits of the branch before proceeding with the actual rebase", - ExtraCmdArgs: []string{}, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - NewBranch(BASE_BRANCH). - EmptyCommit(BASE_COMMIT). - NewBranch(TOP_BRANCH). - EmptyCommit(TOP_COMMIT) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains(TOP_COMMIT), - Contains(BASE_COMMIT), - ) - - t.Views().Branches(). - Focus(). - NavigateToLine(Contains(BASE_BRANCH)). - Press(keys.Branches.RebaseBranch) - - t.ExpectPopup().Menu(). - Title(Equals(fmt.Sprintf("Rebase '%s'", TOP_BRANCH))). - Select(Contains("Interactive rebase")). - Confirm() - t.Views().Commits(). - IsFocused(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains(TOP_COMMIT), - Contains("--- Commits ---"), - Contains(BASE_COMMIT), - ). - NavigateToLine(Contains(TOP_COMMIT)). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains(TOP_COMMIT).Contains("edit"), - Contains("--- Commits ---"), - Contains(BASE_COMMIT), - ). - Tap(func() { - t.Common().ContinueRebase() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("--- Commits ---"), - Contains(TOP_COMMIT), - Contains(BASE_COMMIT), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/amend_commit_with_conflict.go b/pkg/integration/tests/interactive_rebase/amend_commit_with_conflict.go deleted file mode 100644 index ef01739dc8f..00000000000 --- a/pkg/integration/tests/interactive_rebase/amend_commit_with_conflict.go +++ /dev/null @@ -1,78 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AmendCommitWithConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Amends a staged file to a commit, causing a conflict there.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file", "1\n").Commit("one") - shell.UpdateFileAndAdd("file", "1\n2\n").Commit("two") - shell.UpdateFileAndAdd("file", "1\n2\n3\n").Commit("three") - shell.UpdateFileAndAdd("file", "1\n2\n4\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("three"), - Contains("two"), - Contains("one"), - ). - NavigateToLine(Contains("two")). - Press(keys.Commits.AmendToCommit). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Amend commit")). - Content(Contains("Are you sure you want to amend this commit with your staged files?")). - Confirm() - t.Common().AcknowledgeConflicts() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("three"), - Contains("fixup").Contains("<-- CONFLICT --- fixup! two"), - Contains("--- Commits ---"), - Contains("two"), - Contains("one"), - ) - - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU file"), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - TopLines( - Contains("1"), - Contains("2"), - Contains("<<<<<<< HEAD"), - Contains("======="), - Contains("4"), - Contains(">>>>>>>"), - ). - SelectNextItem(). - PressPrimaryAction() // pick "4" - - t.Common().ContinueOnConflictsResolved("rebase") - - t.Common().AcknowledgeConflicts() - - t.Views().Commits(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("<-- CONFLICT --- three"), - Contains("--- Commits ---"), - Contains("two"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/amend_first_commit.go b/pkg/integration/tests/interactive_rebase/amend_first_commit.go deleted file mode 100644 index 02ce4e112fb..00000000000 --- a/pkg/integration/tests/interactive_rebase/amend_first_commit.go +++ /dev/null @@ -1,41 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AmendFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Amends a staged file to the first (initial) commit.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(2). - CreateFileAndAdd("fixup-file", "fixup content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 01")). - Press(keys.Commits.AmendToCommit). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Amend commit")). - Content(Contains("Are you sure you want to amend this commit with your staged files?")). - Confirm() - }). - Lines( - Contains("commit 02"), - Contains("commit 01").IsSelected(), - ) - - t.Views().Main(). - Content(Contains("fixup content")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/amend_fixup_commit.go b/pkg/integration/tests/interactive_rebase/amend_fixup_commit.go deleted file mode 100644 index 3140899bec7..00000000000 --- a/pkg/integration/tests/interactive_rebase/amend_fixup_commit.go +++ /dev/null @@ -1,50 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AmendFixupCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Amends a staged file to a fixup commit, and checks that other unrelated fixup commits are not auto-squashed.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(1). - CreateFileAndAdd("first-fixup-file", "").Commit("fixup! commit 01"). - CreateNCommitsStartingAt(2, 2). - CreateFileAndAdd("unrelated-fixup-file", "fixup 03").Commit("fixup! commit 03"). - CreateFileAndAdd("fixup-file", "fixup 01") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("fixup! commit 03"), - Contains("commit 03"), - Contains("commit 02"), - Contains("fixup! commit 01"), - Contains("commit 01"), - ). - NavigateToLine(Contains("fixup! commit 01")). - Press(keys.Commits.AmendToCommit). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Amend commit")). - Content(Contains("Are you sure you want to amend this commit with your staged files?")). - Confirm() - }). - Lines( - Contains("fixup! commit 03"), - Contains("commit 03"), - Contains("commit 02"), - Contains("fixup! commit 01").IsSelected(), - Contains("commit 01"), - ) - - t.Views().Main(). - Content(Contains("fixup 01")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/amend_head_commit_during_rebase.go b/pkg/integration/tests/interactive_rebase/amend_head_commit_during_rebase.go deleted file mode 100644 index 66be297f06f..00000000000 --- a/pkg/integration/tests/interactive_rebase/amend_head_commit_during_rebase.go +++ /dev/null @@ -1,63 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AmendHeadCommitDuringRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Amends the current head commit from the commits panel during a rebase.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(3) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ) - - t.Shell().CreateFile("fixup-file", "fixup content") - t.Views().Files(). - Focus(). - Press(keys.Files.RefreshFiles). - Lines( - Contains("??").Contains("fixup-file").IsSelected(), - ). - PressPrimaryAction() - - t.Views().Commits(). - Focus(). - Press(keys.Commits.AmendToCommit). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Amend commit")). - Content(Contains("Are you sure you want to amend this commit with your staged files?")). - Confirm() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ) - - t.Views().Main(). - Content(Contains("fixup content")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/amend_merge.go b/pkg/integration/tests/interactive_rebase/amend_merge.go deleted file mode 100644 index 3f01688ff59..00000000000 --- a/pkg/integration/tests/interactive_rebase/amend_merge.go +++ /dev/null @@ -1,62 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ( - postMergeFileContent = "post merge file content" - postMergeFilename = "post-merge-file" -) - -var AmendMerge = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Amends a staged file to a merge commit.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - NewBranch("development-branch"). - CreateFileAndAdd("initial-file", "initial file content"). - Commit("initial commit"). - NewBranch("feature-branch"). // it's also checked out automatically - CreateFileAndAdd("new-feature-file", "new content"). - Commit("new feature commit"). - Checkout("development-branch"). - Merge("feature-branch"). - CreateFileAndAdd(postMergeFilename, postMergeFileContent) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - mergeCommitMessage := "Merge branch 'feature-branch' into development-branch" - - t.Views().Commits(). - Lines( - Contains(mergeCommitMessage), - Contains("new feature commit"), - Contains("initial commit"), - ) - - t.Views().Commits(). - Focus(). - Press(keys.Commits.AmendToCommit) - - t.ExpectPopup().Confirmation(). - Title(Equals("Amend commit")). - Content(Contains("Are you sure you want to amend this commit with your staged files?")). - Confirm() - - // assuring we haven't added a brand new commit - t.Views().Commits(). - Lines( - Contains(mergeCommitMessage), - Contains("new feature commit"), - Contains("initial commit"), - ) - - // assuring the post-merge file shows up in the merge commit. - t.Views().Main(). - Content(Contains(postMergeFilename)). - Content(Contains("++" + postMergeFileContent)) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/amend_non_head_commit_during_rebase.go b/pkg/integration/tests/interactive_rebase/amend_non_head_commit_during_rebase.go deleted file mode 100644 index 1216655e8d9..00000000000 --- a/pkg/integration/tests/interactive_rebase/amend_non_head_commit_during_rebase.go +++ /dev/null @@ -1,42 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AmendNonHeadCommitDuringRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Tries to amend a commit that is not the head while already rebasing, resulting in an error message", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(3) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 02"), - Contains("commit 01"), - ) - - for _, commit := range []string{"commit 01", "commit 03"} { - t.Views().Commits(). - NavigateToLine(Contains(commit)). - Press(keys.Commits.AmendToCommit) - - t.ExpectToast(Contains("Can't perform this action during a rebase")) - } - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/delete_update_ref_todo.go b/pkg/integration/tests/interactive_rebase/delete_update_ref_todo.go deleted file mode 100644 index 4ae20160f81..00000000000 --- a/pkg/integration/tests/interactive_rebase/delete_update_ref_todo.go +++ /dev/null @@ -1,76 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DeleteUpdateRefTodo = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Delete an update-ref item from the rebase todo list", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - NewBranch("branch1"). - CreateNCommits(3). - NewBranch("branch2"). - CreateNCommitsStartingAt(3, 4) - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - NavigateToLine(Contains("commit 01")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("CI commit 06"), - Contains("pick").Contains("CI commit 05"), - Contains("pick").Contains("CI commit 04"), - Contains("update-ref").Contains("branch1"), - Contains("pick").Contains("CI commit 03"), - Contains("pick").Contains("CI commit 02"), - Contains("--- Commits ---"), - Contains("CI ◯ commit 01"), - ). - NavigateToLine(Contains("update-ref")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Drop commit")). - Content(Contains("Are you sure you want to delete the selected update-ref todo(s)?")). - Confirm() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("CI commit 06"), - Contains("pick").Contains("CI commit 05"), - Contains("pick").Contains("CI commit 04"), - Contains("pick").Contains("CI commit 03").IsSelected(), - Contains("pick").Contains("CI commit 02"), - Contains("--- Commits ---"), - Contains("CI ◯ commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Universal.Remove). - Tap(func() { - t.Common().ContinueRebase() - }). - Lines( - Contains("CI ◯ commit 06"), - Contains("CI ◯ commit 05"), - Contains("CI ◯ commit 04"), - Contains("CI ◯ commit 03"), // No star on this commit, so there's no branch head here - Contains("CI ◯ commit 01"), - ) - - t.Views().Branches(). - Lines( - Contains("branch2"), - Contains("branch1"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/dont_show_branch_heads_for_todo_items.go b/pkg/integration/tests/interactive_rebase/dont_show_branch_heads_for_todo_items.go deleted file mode 100644 index e5b43ee8171..00000000000 --- a/pkg/integration/tests/interactive_rebase/dont_show_branch_heads_for_todo_items.go +++ /dev/null @@ -1,58 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DontShowBranchHeadsForTodoItems = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Check that branch heads are shown for normal commits during interactive rebase, but not for todo items", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - NewBranch("branch1"). - CreateNCommits(2). - NewBranch("branch2"). - CreateNCommitsStartingAt(4, 3). - NewBranch("branch3"). - CreateNCommitsStartingAt(3, 7) - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI commit 09"), - Contains("CI commit 08"), - Contains("CI commit 07"), - Contains("CI * commit 06"), - Contains("CI commit 05"), - Contains("CI commit 04"), - Contains("CI commit 03"), - Contains("CI * commit 02"), - Contains("CI commit 01"), - ). - NavigateToLine(Contains("commit 04")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("CI commit 09"), - Contains("pick").Contains("CI commit 08"), - Contains("pick").Contains("CI commit 07"), - Contains("update-ref").Contains("branch2"), - Contains("pick").Contains("CI commit 06"), // no star on this entry, even though branch2 points to it - Contains("pick").Contains("CI commit 05"), - Contains("--- Commits ---"), - Contains("CI commit 04"), - Contains("CI commit 03"), - Contains("CI * commit 02"), // this star is fine though - Contains("CI commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/drop_commit_in_copied_branch_with_update_ref.go b/pkg/integration/tests/interactive_rebase/drop_commit_in_copied_branch_with_update_ref.go deleted file mode 100644 index 81462296ba4..00000000000 --- a/pkg/integration/tests/interactive_rebase/drop_commit_in_copied_branch_with_update_ref.go +++ /dev/null @@ -1,56 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DropCommitInCopiedBranchWithUpdateRef = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Drops a commit in a branch that is a copy of another branch, and verify that the other branch is left alone", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - NewBranch("branch1"). - CreateNCommits(3). - NewBranch("branch2") - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI * commit 03").IsSelected(), - Contains("CI commit 02"), - Contains("CI commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Drop commit")). - Content(Equals("Are you sure you want to drop the selected commit(s)?")). - Confirm() - }). - Lines( - Contains("CI commit 03"), // no start on this commit because branch1 is no longer pointing to it - Contains("CI commit 01"), - ) - - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("branch1")). - PressPrimaryAction() - - t.Views().Commits().Lines( - Contains("CI commit 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/drop_merge_commit.go b/pkg/integration/tests/interactive_rebase/drop_merge_commit.go deleted file mode 100644 index 0011baf7dc6..00000000000 --- a/pkg/integration/tests/interactive_rebase/drop_merge_commit.go +++ /dev/null @@ -1,45 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var DropMergeCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Drops a merge commit outside of an interactive rebase", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shared.CreateMergeCommit(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI ⏣─╮ Merge branch 'second-change-branch' into first-change-branch").IsSelected(), - Contains("CI │ ◯ * second-change-branch unrelated change"), - Contains("CI │ ◯ second change"), - Contains("CI ◯ │ first change"), - Contains("CI ◯─╯ * original"), - Contains("CI ◯ three"), - Contains("CI ◯ two"), - Contains("CI ◯ one"), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Drop commit")). - Content(Equals("Are you sure you want to drop the selected merge commit? Note that it will also drop all the commits that were merged in by it.")). - Confirm() - }). - Lines( - Contains("CI ◯ first change").IsSelected(), - Contains("CI ◯ * original"), - Contains("CI ◯ three"), - Contains("CI ◯ two"), - Contains("CI ◯ one"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/drop_todo_commit_with_update_ref.go b/pkg/integration/tests/interactive_rebase/drop_todo_commit_with_update_ref.go deleted file mode 100644 index ca481e98626..00000000000 --- a/pkg/integration/tests/interactive_rebase/drop_todo_commit_with_update_ref.go +++ /dev/null @@ -1,72 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DropTodoCommitWithUpdateRef = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Drops a commit during interactive rebase when there is an update-ref in the git-rebase-todo file", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.MainBranches = []string{"master"} - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(1). - NewBranch("branch1"). - CreateNCommitsStartingAt(3, 2). - NewBranch("branch2"). - CreateNCommitsStartingAt(3, 5) - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI commit 07").IsSelected(), - Contains("CI commit 06"), - Contains("CI commit 05"), - Contains("CI * commit 04"), - Contains("CI commit 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("CI commit 07"), - Contains("pick").Contains("CI commit 06"), - Contains("pick").Contains("CI commit 05"), - Contains("update-ref").Contains("branch1").DoesNotContain("*"), - Contains("pick").Contains("CI commit 04"), - Contains("pick").Contains("CI commit 03"), - Contains("--- Commits ---"), - Contains("CI commit 02").IsSelected(), - Contains("CI commit 01"), - ). - Tap(func() { - t.Views().Main().Content(Contains("commit 02")) - }). - NavigateToLine(Contains("commit 06")). - Press(keys.Universal.Remove) - - t.Common().ContinueRebase() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("CI commit 07"), - Contains("CI commit 05"), - Contains("CI * commit 04"), - Contains("CI commit 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/drop_with_custom_comment_char.go b/pkg/integration/tests/interactive_rebase/drop_with_custom_comment_char.go deleted file mode 100644 index a6868e44fd0..00000000000 --- a/pkg/integration/tests/interactive_rebase/drop_with_custom_comment_char.go +++ /dev/null @@ -1,34 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DropWithCustomCommentChar = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Drops a commit with the 'core.commentChar' option set to a custom character", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.SetConfig("core.commentChar", ";") - shell.CreateNCommits(2) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Focus(). - Lines( - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Drop commit")). - Content(Equals("Are you sure you want to drop the selected commit(s)?")). - Confirm() - }). - Lines( - Contains("commit 01").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/edit_and_auto_amend.go b/pkg/integration/tests/interactive_rebase/edit_and_auto_amend.go deleted file mode 100644 index 2107c8a5872..00000000000 --- a/pkg/integration/tests/interactive_rebase/edit_and_auto_amend.go +++ /dev/null @@ -1,57 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var EditAndAutoAmend = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Edit a commit, make a change and stage it, then continue the rebase to auto-amend the commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(3) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ) - - t.Shell().CreateFile("fixup-file", "fixup content") - t.Views().Files(). - Focus(). - Press(keys.Files.RefreshFiles). - Lines( - Contains("??").Contains("fixup-file").IsSelected(), - ). - PressPrimaryAction() - - t.Common().ContinueRebase() - - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 03"), - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ) - - t.Views().Main(). - Content(Contains("fixup content")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/edit_first_commit.go b/pkg/integration/tests/interactive_rebase/edit_first_commit.go deleted file mode 100644 index f09b7f27d27..00000000000 --- a/pkg/integration/tests/interactive_rebase/edit_first_commit.go +++ /dev/null @@ -1,40 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var EditFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Edits the first commit, just to show that it's possible", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(2) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 01")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 02"), - Contains("--- Commits ---"), - Contains("commit 01").IsSelected(), - ). - Tap(func() { - t.Common().ContinueRebase() - }). - Lines( - Contains("commit 02"), - Contains("commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/edit_last_commit_of_stacked_branch.go b/pkg/integration/tests/interactive_rebase/edit_last_commit_of_stacked_branch.go deleted file mode 100644 index 528afb7a413..00000000000 --- a/pkg/integration/tests/interactive_rebase/edit_last_commit_of_stacked_branch.go +++ /dev/null @@ -1,76 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var EditLastCommitOfStackedBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Edit and amend the last commit of a branch in a stack of branches, and ensure that it doesn't break the stack", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.MainBranches = []string{"master"} - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(1). - NewBranch("branch1"). - CreateNCommitsStartingAt(2, 2). - NewBranch("branch2"). - CreateNCommitsStartingAt(2, 4) - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI commit 05").IsSelected(), - Contains("CI commit 04"), - Contains("CI * commit 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ). - NavigateToLine(Contains("commit 03")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("CI commit 05"), - Contains("pick").Contains("CI commit 04"), - Contains("update-ref").Contains("branch1"), - Contains("--- Commits ---"), - Contains("CI * commit 03").IsSelected(), - Contains("CI commit 02"), - Contains("CI commit 01"), - ) - - t.Shell().CreateFile("fixup-file", "fixup content") - t.Views().Files(). - Focus(). - Press(keys.Files.RefreshFiles). - Lines( - Contains("??").Contains("fixup-file").IsSelected(), - ). - PressPrimaryAction(). - Press(keys.Files.AmendLastCommit) - t.ExpectPopup().Confirmation(). - Title(Equals("Amend last commit")). - Content(Contains("Are you sure you want to amend last commit?")). - Confirm() - - t.Common().ContinueRebase() - - t.Views().Commits(). - Focus(). - Lines( - Contains("CI commit 05"), - Contains("CI commit 04"), - Contains("CI * commit 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/edit_non_todo_commit_during_rebase.go b/pkg/integration/tests/interactive_rebase/edit_non_todo_commit_during_rebase.go deleted file mode 100644 index 6a21412de90..00000000000 --- a/pkg/integration/tests/interactive_rebase/edit_non_todo_commit_during_rebase.go +++ /dev/null @@ -1,36 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var EditNonTodoCommitDuringRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Tries to edit a non-todo commit while already rebasing, resulting in an error message", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(2) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("--- Commits ---"), - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 01")). - Press(keys.Universal.Edit) - - t.ExpectToast(Contains("Disabled: When rebasing, this action only works on a selection of TODO commits.")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/edit_range_select_down_to_merge_outside_rebase.go b/pkg/integration/tests/interactive_rebase/edit_range_select_down_to_merge_outside_rebase.go deleted file mode 100644 index 4a6135b2807..00000000000 --- a/pkg/integration/tests/interactive_rebase/edit_range_select_down_to_merge_outside_rebase.go +++ /dev/null @@ -1,44 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var EditRangeSelectDownToMergeOutsideRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Select a range of commits (the last one being a merge commit) to edit outside of a rebase", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shared.CreateMergeCommit(shell) - shell.CreateNCommits(2) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - TopLines( - Contains("CI ◯ commit 02").IsSelected(), - Contains("CI ◯ commit 01"), - Contains("Merge branch 'second-change-branch' into first-change-branch"), - ). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("edit CI commit 02").IsSelected(), - Contains("edit CI commit 01").IsSelected(), - Contains("--- Commits ---").IsSelected(), - Contains(" CI ⏣─╮ Merge branch 'second-change-branch' into first-change-branch").IsSelected(), - Contains(" CI │ ◯ * second-change-branch unrelated change"), - Contains(" CI │ ◯ second change"), - Contains(" CI ◯ │ first change"), - Contains(" CI ◯─╯ * original"), - Contains(" CI ◯ three"), - Contains(" CI ◯ two"), - Contains(" CI ◯ one"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/edit_range_select_outside_rebase.go b/pkg/integration/tests/interactive_rebase/edit_range_select_outside_rebase.go deleted file mode 100644 index a86a5f0e2f5..00000000000 --- a/pkg/integration/tests/interactive_rebase/edit_range_select_outside_rebase.go +++ /dev/null @@ -1,52 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var EditRangeSelectOutsideRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Select a range of commits to edit outside of a rebase", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shared.CreateMergeCommit(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - TopLines( - Contains("Merge branch 'second-change-branch' into first-change-branch").IsSelected(), - ). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.RangeSelectDown). - Lines( - Contains("CI ⏣─╮ Merge branch 'second-change-branch' into first-change-branch").IsSelected(), - Contains("CI │ ◯ * second-change-branch unrelated change").IsSelected(), - Contains("CI │ ◯ second change").IsSelected(), - Contains("CI ◯ │ first change").IsSelected(), - Contains("CI ◯─╯ * original").IsSelected(), - Contains("CI ◯ three").IsSelected(), - Contains("CI ◯ two"), - Contains("CI ◯ one"), - ). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("merge CI Merge branch 'second-change-branch' into first-change-branch").IsSelected(), - Contains("edit CI first change").IsSelected(), - Contains("edit CI * second-change-branch unrelated change").IsSelected(), - Contains("edit CI second change").IsSelected(), - Contains("edit CI * original").IsSelected(), - Contains("--- Commits ---").IsSelected(), - Contains(" CI ◯ three").IsSelected(), - Contains(" CI ◯ two"), - Contains(" CI ◯ one"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/edit_the_confl_commit.go b/pkg/integration/tests/interactive_rebase/edit_the_confl_commit.go deleted file mode 100644 index 5e03acdd5e8..00000000000 --- a/pkg/integration/tests/interactive_rebase/edit_the_confl_commit.go +++ /dev/null @@ -1,46 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var EditTheConflCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Swap two commits, causing a conflict; then try to interact with the 'confl' commit, which results in an error.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", "one") - shell.Commit("commit one") - shell.UpdateFileAndAdd("myfile", "two") - shell.Commit("commit two") - shell.UpdateFileAndAdd("myfile", "three") - shell.Commit("commit three") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit three").IsSelected(), - Contains("commit two"), - Contains("commit one"), - ). - Press(keys.Commits.MoveDownCommit). - Tap(func() { - t.Common().AcknowledgeConflicts() - }). - Focus(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit two"), - Contains("pick").Contains("<-- CONFLICT --- commit three"), - Contains("--- Commits ---"), - Contains("commit one"), - ). - NavigateToLine(Contains("<-- CONFLICT --- commit three")). - Press(keys.Commits.RenameCommit) - - t.ExpectToast(Contains("Disabled: Rewording commits while interactively rebasing is not currently supported")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/fixup_first_commit.go b/pkg/integration/tests/interactive_rebase/fixup_first_commit.go deleted file mode 100644 index ff099d76099..00000000000 --- a/pkg/integration/tests/interactive_rebase/fixup_first_commit.go +++ /dev/null @@ -1,34 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FixupFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Tries to fixup the first commit, which results in an error message", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(2) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 01")). - Press(keys.Commits.MarkCommitAsFixup). - Tap(func() { - t.ExpectToast(Equals("Disabled: There's no commit below to squash into")) - }). - Lines( - Contains("commit 02"), - Contains("commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/fixup_second_commit.go b/pkg/integration/tests/interactive_rebase/fixup_second_commit.go deleted file mode 100644 index c5eec4a8237..00000000000 --- a/pkg/integration/tests/interactive_rebase/fixup_second_commit.go +++ /dev/null @@ -1,49 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FixupSecondCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Fixup the second commit into the first (initial)", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateFileAndAdd("file1.txt", "File1 Content\n").Commit("First Commit"). - CreateFileAndAdd("file2.txt", "Fixup Content\n").Commit("Fixup Commit Message"). - CreateFileAndAdd("file3.txt", "File3 Content\n").Commit("Third Commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("Third Commit"), - Contains("Fixup Commit Message"), - Contains("First Commit"), - ). - NavigateToLine(Contains("Fixup Commit Message")). - Press(keys.Commits.MarkCommitAsFixup). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Fixup")). - Content(Equals("Are you sure you want to 'fixup' the selected commit(s) into the commit below?")). - Confirm() - }). - Lines( - Contains("Third Commit"), - Contains("First Commit").IsSelected(), - ) - - t.Views().Main(). - // Make sure that the resulting commit message doesn't contain the - // message of the fixup commit; compare this to - // squash_down_second_commit.go, where it does. - Content(Contains("First Commit")). - Content(DoesNotContain("Fixup Commit Message")). - Content(Contains("+File1 Content")). - Content(Contains("+Fixup Content")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/interactive_rebase_of_copied_branch.go b/pkg/integration/tests/interactive_rebase/interactive_rebase_of_copied_branch.go deleted file mode 100644 index 73ace910541..00000000000 --- a/pkg/integration/tests/interactive_rebase/interactive_rebase_of_copied_branch.go +++ /dev/null @@ -1,43 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var InteractiveRebaseOfCopiedBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Check that interactively rebasing a branch that is a copy of another branch doesn't affect the original branch", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - NewBranch("branch1"). - CreateNCommits(3). - NewBranch("branch2") - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI * commit 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ). - NavigateToLine(Contains("commit 01")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - // No update-ref todo for branch1 here, even though command-line git would have added it - Contains("pick").Contains("CI commit 03"), - Contains("pick").Contains("CI commit 02"), - Contains("--- Commits ---"), - Contains("CI commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/interactive_rebase_with_conflict_for_edit_command.go b/pkg/integration/tests/interactive_rebase/interactive_rebase_with_conflict_for_edit_command.go deleted file mode 100644 index 11596e75896..00000000000 --- a/pkg/integration/tests/interactive_rebase/interactive_rebase_with_conflict_for_edit_command.go +++ /dev/null @@ -1,65 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var InteractiveRebaseWithConflictForEditCommand = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rebase a branch interactively, and edit a commit that will conflict", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CreateFileAndAdd("file.txt", "master content") - shell.Commit("master commit") - shell.NewBranchFrom("branch", "master^") - shell.CreateNCommits(3) - shell.CreateFileAndAdd("file.txt", "branch content") - shell.Commit("this will conflict") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("this will conflict").IsSelected(), - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - Contains("initial commit"), - ) - - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("master")). - Press(keys.Branches.RebaseBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'branch'")). - Select(Contains("Interactive rebase")). - Confirm() - - t.Views().Commits(). - IsFocused(). - NavigateToLine(Contains("this will conflict")). - Press(keys.Universal.Edit) - - t.Common().ContinueRebase() - t.ExpectPopup().Menu(). - Title(Equals("Conflicts!")). - Cancel() - - t.Views().Commits(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("edit").Contains("<-- CONFLICT --- this will conflict").IsSelected(), - Contains("--- Commits ---"), - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - Contains("master commit"), - Contains("initial commit"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/mid_rebase_range_select.go b/pkg/integration/tests/interactive_rebase/mid_rebase_range_select.go deleted file mode 100644 index cb96b830888..00000000000 --- a/pkg/integration/tests/interactive_rebase/mid_rebase_range_select.go +++ /dev/null @@ -1,210 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MidRebaseRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Do various things with range selection in the commits view when mid-rebase", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(10) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - TopLines( - Contains("commit 10").IsSelected(), - ). - NavigateToLine(Contains("commit 05")). - // Start a rebase - Press(keys.Universal.Edit). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08"), - Contains("pick").Contains("commit 07"), - Contains("pick").Contains("commit 06"), - Contains("--- Commits ---"), - Contains("commit 05").IsSelected(), - Contains("commit 04"), - ). - SelectPreviousItem(). - // perform various actions on a range of commits - Press(keys.Universal.RangeSelectUp). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08"), - Contains("pick").Contains("commit 07").IsSelected(), - Contains("pick").Contains("commit 06").IsSelected(), - Contains("--- Commits ---"), - Contains("commit 05"), - Contains("commit 04"), - ). - Press(keys.Commits.MarkCommitAsFixup). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08"), - Contains("fixup").Contains("commit 07").IsSelected(), - Contains("fixup").Contains("commit 06").IsSelected(), - Contains("--- Commits ---"), - Contains("commit 05"), - Contains("commit 04"), - ). - Press(keys.Commits.PickCommit). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08"), - Contains("pick").Contains("commit 07").IsSelected(), - Contains("pick").Contains("commit 06").IsSelected(), - Contains("--- Commits ---"), - Contains("commit 05"), - Contains("commit 04"), - ). - Press(keys.Universal.Edit). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08"), - Contains("edit").Contains("commit 07").IsSelected(), - Contains("edit").Contains("commit 06").IsSelected(), - Contains("--- Commits ---"), - Contains("commit 05"), - Contains("commit 04"), - ). - Press(keys.Commits.SquashDown). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08"), - Contains("squash").Contains("commit 07").IsSelected(), - Contains("squash").Contains("commit 06").IsSelected(), - Contains("--- Commits ---"), - Contains("commit 05"), - Contains("commit 04"), - ). - Press(keys.Commits.MoveDownCommit). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08"), - Contains("squash").Contains("commit 07").IsSelected(), - Contains("squash").Contains("commit 06").IsSelected(), - Contains("--- Commits ---"), - Contains("commit 05"), - Contains("commit 04"), - ). - Tap(func() { - t.ExpectToast(Contains("Disabled: Cannot move any further")) - }). - Press(keys.Commits.MoveUpCommit). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("squash").Contains("commit 07").IsSelected(), - Contains("squash").Contains("commit 06").IsSelected(), - Contains("pick").Contains("commit 08"), - Contains("--- Commits ---"), - Contains("commit 05"), - Contains("commit 04"), - ). - Press(keys.Commits.MoveUpCommit). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit 10"), - Contains("squash").Contains("commit 07").IsSelected(), - Contains("squash").Contains("commit 06").IsSelected(), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08"), - Contains("--- Commits ---"), - Contains("commit 05"), - Contains("commit 04"), - ). - Press(keys.Commits.MoveUpCommit). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("squash").Contains("commit 07").IsSelected(), - Contains("squash").Contains("commit 06").IsSelected(), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08"), - Contains("--- Commits ---"), - Contains("commit 05"), - Contains("commit 04"), - ). - Press(keys.Commits.MoveUpCommit). - Tap(func() { - t.ExpectToast(Contains("Disabled: Cannot move any further")) - }). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("squash").Contains("commit 07").IsSelected(), - Contains("squash").Contains("commit 06").IsSelected(), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08"), - Contains("--- Commits ---"), - Contains("commit 05"), - Contains("commit 04"), - ). - // Verify we can't perform an action on a range that includes both - // TODO and non-TODO commits - NavigateToLine(Contains("commit 08")). - Press(keys.Universal.RangeSelectDown). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("squash").Contains("commit 07"), - Contains("squash").Contains("commit 06"), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08").IsSelected(), - Contains("--- Commits ---").IsSelected(), - Contains("commit 05").IsSelected(), - Contains("commit 04"), - ). - Press(keys.Commits.MarkCommitAsFixup). - Tap(func() { - t.ExpectToast(Contains("Disabled: When rebasing, this action only works on a selection of TODO commits.")) - }). - TopLines( - Contains("--- Pending rebase todos ---"), - Contains("squash").Contains("commit 07"), - Contains("squash").Contains("commit 06"), - Contains("pick").Contains("commit 10"), - Contains("pick").Contains("commit 09"), - Contains("pick").Contains("commit 08").IsSelected(), - Contains("--- Commits ---").IsSelected(), - Contains("commit 05").IsSelected(), - Contains("commit 04"), - ). - // continue the rebase - Tap(func() { - t.Common().ContinueRebase() - }). - TopLines( - Contains("commit 10"), - Contains("commit 09"), - Contains("commit 08"), - Contains("commit 05"), - // selected indexes are retained, though we may want to clear it - // in future (not sure what the best behaviour is right now) - Contains("commit 04").IsSelected(), - Contains("commit 03").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/move.go b/pkg/integration/tests/interactive_rebase/move.go deleted file mode 100644 index 3f1f2375500..00000000000 --- a/pkg/integration/tests/interactive_rebase/move.go +++ /dev/null @@ -1,90 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Move = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Directly move a commit all the way down and all the way back up", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(4) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 04").IsSelected(), - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - ). - Press(keys.Commits.MoveDownCommit). - Lines( - Contains("commit 03"), - Contains("commit 04").IsSelected(), - Contains("commit 02"), - Contains("commit 01"), - ). - Press(keys.Commits.MoveDownCommit). - Lines( - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 04").IsSelected(), - Contains("commit 01"), - ). - Press(keys.Commits.MoveDownCommit). - Lines( - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - Contains("commit 04").IsSelected(), - ). - // assert nothing happens upon trying to move beyond the last commit - Press(keys.Commits.MoveDownCommit). - Tap(func() { - t.ExpectToast(Contains("Disabled: Cannot move any further")) - }). - Lines( - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - Contains("commit 04").IsSelected(), - ). - Press(keys.Commits.MoveUpCommit). - Lines( - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 04").IsSelected(), - Contains("commit 01"), - ). - Press(keys.Commits.MoveUpCommit). - Lines( - Contains("commit 03"), - Contains("commit 04").IsSelected(), - Contains("commit 02"), - Contains("commit 01"), - ). - Press(keys.Commits.MoveUpCommit). - Lines( - Contains("commit 04").IsSelected(), - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - ). - // assert nothing happens upon trying to move beyond the first commit - Press(keys.Commits.MoveUpCommit). - Tap(func() { - t.ExpectToast(Contains("Disabled: Cannot move any further")) - }). - Lines( - Contains("commit 04").IsSelected(), - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/move_across_branch_boundary_outside_rebase.go b/pkg/integration/tests/interactive_rebase/move_across_branch_boundary_outside_rebase.go deleted file mode 100644 index 0f341d5b5b3..00000000000 --- a/pkg/integration/tests/interactive_rebase/move_across_branch_boundary_outside_rebase.go +++ /dev/null @@ -1,47 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveAcrossBranchBoundaryOutsideRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a commit across a branch boundary in a stack of branches", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.MainBranches = []string{"master"} - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(1). - NewBranch("branch1"). - CreateNCommitsStartingAt(2, 2). - NewBranch("branch2"). - CreateNCommitsStartingAt(2, 4) - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI commit 05").IsSelected(), - Contains("CI commit 04"), - Contains("CI * commit 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ). - NavigateToLine(Contains("commit 04")). - Press(keys.Commits.MoveDownCommit). - Lines( - Contains("CI commit 05"), - Contains("CI * commit 03"), - Contains("CI commit 04").IsSelected(), - Contains("CI commit 02"), - Contains("CI commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/move_in_rebase.go b/pkg/integration/tests/interactive_rebase/move_in_rebase.go deleted file mode 100644 index 1cc9dd785f3..00000000000 --- a/pkg/integration/tests/interactive_rebase/move_in_rebase.go +++ /dev/null @@ -1,118 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveInRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Via a single interactive rebase move a commit all the way up then back down then slightly back up again and apply the change", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(4) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 04").IsSelected(), - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 01")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 04"), - Contains("commit 03"), - Contains("commit 02"), - Contains("--- Commits ---"), - Contains("commit 01").IsSelected(), - ). - SelectPreviousItem(). - Press(keys.Commits.MoveUpCommit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 04"), - Contains("commit 02").IsSelected(), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 01"), - ). - Press(keys.Commits.MoveUpCommit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 02").IsSelected(), - Contains("commit 04"), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 01"), - ). - // assert we can't move past the top - Press(keys.Commits.MoveUpCommit). - Tap(func() { - t.ExpectToast(Contains("Disabled: Cannot move any further")) - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 02").IsSelected(), - Contains("commit 04"), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 01"), - ). - Press(keys.Commits.MoveDownCommit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 04"), - Contains("commit 02").IsSelected(), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 01"), - ). - Press(keys.Commits.MoveDownCommit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 04"), - Contains("commit 03"), - Contains("commit 02").IsSelected(), - Contains("--- Commits ---"), - Contains("commit 01"), - ). - // assert we can't move past the bottom - Press(keys.Commits.MoveDownCommit). - Tap(func() { - t.ExpectToast(Contains("Disabled: Cannot move any further")) - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 04"), - Contains("commit 03"), - Contains("commit 02").IsSelected(), - Contains("--- Commits ---"), - Contains("commit 01"), - ). - // move it back up one so that we land in a different order than we started with - Press(keys.Commits.MoveUpCommit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 04"), - Contains("commit 02").IsSelected(), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 01"), - ). - Tap(func() { - t.Common().ContinueRebase() - }). - Lines( - Contains("commit 04"), - Contains("commit 02").IsSelected(), - Contains("commit 03"), - Contains("commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/move_update_ref_todo.go b/pkg/integration/tests/interactive_rebase/move_update_ref_todo.go deleted file mode 100644 index 00f4ba11f5d..00000000000 --- a/pkg/integration/tests/interactive_rebase/move_update_ref_todo.go +++ /dev/null @@ -1,65 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveUpdateRefTodo = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move an update-ref item in the rebase todo list", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - NewBranch("branch1"). - CreateNCommits(3). - NewBranch("branch2"). - CreateNCommitsStartingAt(3, 4) - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - NavigateToLine(Contains("commit 01")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("CI commit 06"), - Contains("pick").Contains("CI commit 05"), - Contains("pick").Contains("CI commit 04"), - Contains("update-ref").Contains("branch1"), - Contains("pick").Contains("CI commit 03"), - Contains("pick").Contains("CI commit 02"), - Contains("--- Commits ---"), - Contains("CI ◯ commit 01"), - ). - NavigateToLine(Contains("update-ref")). - Press(keys.Commits.MoveUpCommit). - Press(keys.Commits.MoveUpCommit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("CI commit 06"), - Contains("update-ref").Contains("branch1"), - Contains("pick").Contains("CI commit 05"), - Contains("pick").Contains("CI commit 04"), - Contains("pick").Contains("CI commit 03"), - Contains("pick").Contains("CI commit 02"), - Contains("--- Commits ---"), - Contains("CI ◯ commit 01"), - ). - Tap(func() { - t.Common().ContinueRebase() - }). - Lines( - Contains("CI ◯ commit 06"), - Contains("CI ◯ * commit 05"), - Contains("CI ◯ commit 04"), - Contains("CI ◯ commit 03"), - Contains("CI ◯ commit 02"), - Contains("CI ◯ commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/move_with_custom_comment_char.go b/pkg/integration/tests/interactive_rebase/move_with_custom_comment_char.go deleted file mode 100644 index eefbcea337e..00000000000 --- a/pkg/integration/tests/interactive_rebase/move_with_custom_comment_char.go +++ /dev/null @@ -1,34 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveWithCustomCommentChar = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Directly moves a commit down and back up with the 'core.commentChar' option set to a custom character", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.SetConfig("core.commentChar", ";") - shell.CreateNCommits(2) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Focus(). - Lines( - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ). - Press(keys.Commits.MoveDownCommit). - Lines( - Contains("commit 01"), - Contains("commit 02").IsSelected(), - ). - Press(keys.Commits.MoveUpCommit). - Lines( - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/outside_rebase_range_select.go b/pkg/integration/tests/interactive_rebase/outside_rebase_range_select.go deleted file mode 100644 index d30ae0d6470..00000000000 --- a/pkg/integration/tests/interactive_rebase/outside_rebase_range_select.go +++ /dev/null @@ -1,155 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var OutsideRebaseRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Do various things with range selection in the commits view when outside rebase", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(10) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - TopLines( - Contains("commit 10").IsSelected(), - ). - Press(keys.Universal.RangeSelectDown). - TopLines( - Contains("commit 10").IsSelected(), - Contains("commit 09").IsSelected(), - Contains("commit 08"), - ). - // Drop commits - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Drop commit")). - Content(Contains("Are you sure you want to drop the selected commit(s)?")). - Confirm() - }). - TopLines( - Contains("commit 08").IsSelected(), - Contains("commit 07"), - ). - Press(keys.Universal.RangeSelectDown). - TopLines( - Contains("commit 08").IsSelected(), - Contains("commit 07").IsSelected(), - Contains("commit 06"), - ). - // Squash commits - Press(keys.Commits.SquashDown). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Squash")). - Content(Contains("Are you sure you want to squash the selected commit(s) into the commit below?")). - Confirm() - }). - TopLines( - Contains("commit 06").IsSelected(), - Contains("commit 05"), - Contains("commit 04"), - ). - // Verify commit messages are concatenated - Tap(func() { - t.Views().Main(). - ContainsLines( - Contains("commit 06"), - AnyString(), - Contains("commit 07"), - AnyString(), - Contains("commit 08"), - ) - }). - // Fixup commits - Press(keys.Universal.RangeSelectDown). - TopLines( - Contains("commit 06").IsSelected(), - Contains("commit 05").IsSelected(), - Contains("commit 04"), - ). - Press(keys.Commits.MarkCommitAsFixup). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Fixup")). - Content(Contains("Are you sure you want to 'fixup' the selected commit(s) into the commit below?")). - Confirm() - }). - TopLines( - Contains("commit 04").IsSelected(), - Contains("commit 03"), - Contains("commit 02"), - ). - // Verify commit messages are dropped - Tap(func() { - t.Views().Main(). - Content( - Contains("commit 04"). - DoesNotContain("commit 06"). - DoesNotContain("commit 05"), - ) - }). - Press(keys.Universal.RangeSelectDown). - TopLines( - Contains("commit 04").IsSelected(), - Contains("commit 03").IsSelected(), - Contains("commit 02"), - ). - // Move commits - Press(keys.Commits.MoveDownCommit). - TopLines( - Contains("commit 02"), - Contains("commit 04").IsSelected(), - Contains("commit 03").IsSelected(), - Contains("commit 01"), - ). - Press(keys.Commits.MoveDownCommit). - TopLines( - Contains("commit 02"), - Contains("commit 01"), - Contains("commit 04").IsSelected(), - Contains("commit 03").IsSelected(), - ). - Press(keys.Commits.MoveDownCommit). - TopLines( - Contains("commit 02"), - Contains("commit 01"), - Contains("commit 04").IsSelected(), - Contains("commit 03").IsSelected(), - ). - Tap(func() { - t.ExpectToast(Contains("Disabled: Cannot move any further")) - }). - Press(keys.Commits.MoveUpCommit). - TopLines( - Contains("commit 02"), - Contains("commit 04").IsSelected(), - Contains("commit 03").IsSelected(), - Contains("commit 01"), - ). - Press(keys.Commits.MoveUpCommit). - TopLines( - Contains("commit 04").IsSelected(), - Contains("commit 03").IsSelected(), - Contains("commit 02"), - Contains("commit 01"), - ). - Press(keys.Commits.MoveUpCommit). - Tap(func() { - t.ExpectToast(Contains("Disabled: Cannot move any further")) - }). - TopLines( - Contains("commit 04").IsSelected(), - Contains("commit 03").IsSelected(), - Contains("commit 02"), - Contains("commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/pick_rescheduled.go b/pkg/integration/tests/interactive_rebase/pick_rescheduled.go deleted file mode 100644 index af948b7cd13..00000000000 --- a/pkg/integration/tests/interactive_rebase/pick_rescheduled.go +++ /dev/null @@ -1,51 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PickRescheduled = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Makes a pick during a rebase fail because it would overwrite an untracked file", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "1\n").Commit("one") - shell.UpdateFileAndAdd("file2", "2\n").Commit("two") - shell.UpdateFileAndAdd("file3", "3\n").Commit("three") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ). - NavigateToLine(Contains("one")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("three"), - Contains("pick").Contains("two"), - Contains("--- Commits ---"), - Contains("one").IsSelected(), - ). - Tap(func() { - t.Shell().CreateFile("file3", "other content\n") - t.Common().ContinueRebase() - t.ExpectPopup().Alert().Title(Equals("Error")). - Content(Contains("The following untracked working tree files would be overwritten by merge"). - Contains("Please move or remove them before you merge.")). - Confirm() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("three"), - Contains("--- Commits ---"), - Contains("two"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/quick_start.go b/pkg/integration/tests/interactive_rebase/quick_start.go deleted file mode 100644 index 07baa061608..00000000000 --- a/pkg/integration/tests/interactive_rebase/quick_start.go +++ /dev/null @@ -1,121 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var QuickStart = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Quick-starts an interactive rebase in several contexts", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - // we're going to test the following: - // * quick start from main fails - // * quick start from feature branch starts from main - // * quick start from branch with merge commit starts from merge commit - - shell.NewBranch("main") - shell.EmptyCommit("initial commit") - shell.EmptyCommit("last main commit") - - shell.NewBranch("feature-branch") - shell.NewBranch("branch-to-merge") - shell.NewBranch("branch-with-merge-commit") - - shell.Checkout("feature-branch") - shell.EmptyCommit("feature-branch one") - shell.EmptyCommit("feature-branch two") - - shell.Checkout("branch-to-merge") - shell.EmptyCommit("branch-to-merge one") - shell.EmptyCommit("branch-to-merge two") - - shell.Checkout("branch-with-merge-commit") - shell.EmptyCommit("branch-with-merge one") - shell.EmptyCommit("branch-with-merge two") - - shell.Merge("branch-to-merge") - - shell.EmptyCommit("branch-with-merge three") - - shell.Checkout("main") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("last main commit"), - Contains("initial commit"), - ). - // Verify we can't quick start from main - Press(keys.Commits.StartInteractiveRebase) - - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Equals("Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `e`.")). - Confirm() - - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("feature-branch")). - Press(keys.Universal.Select) - - t.Views().Commits(). - Focus(). - Lines( - Contains("feature-branch two").IsSelected(), - Contains("feature-branch one"), - Contains("last main commit"), - Contains("initial commit"), - ). - // Verify quick start picks the last commit on the main branch - Press(keys.Commits.StartInteractiveRebase). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("feature-branch two").IsSelected(), - Contains("feature-branch one"), - Contains("--- Commits ---"), - Contains("last main commit"), - Contains("initial commit"), - ). - // Try again, verify we fail because we're already rebasing - Press(keys.Commits.StartInteractiveRebase) - - t.ExpectToast(Equals("Disabled: Can't perform this action during a rebase")) - t.Common().AbortRebase() - - // Verify if a merge commit is present on the branch we start from there - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("branch-with-merge-commit")). - Press(keys.Universal.Select) - - t.Views().Commits(). - Focus(). - Lines( - Contains("branch-with-merge three").IsSelected(), - Contains("Merge branch 'branch-to-merge'"), - Contains("branch-to-merge two"), - Contains("branch-to-merge one"), - Contains("branch-with-merge two"), - Contains("branch-with-merge one"), - Contains("last main commit"), - Contains("initial commit"), - ). - Press(keys.Commits.StartInteractiveRebase). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("branch-with-merge three").IsSelected(), - Contains("--- Commits ---"), - Contains("Merge branch 'branch-to-merge'"), - Contains("branch-to-merge two"), - Contains("branch-to-merge one"), - Contains("branch-with-merge two"), - Contains("branch-with-merge one"), - Contains("last main commit"), - Contains("initial commit"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/quick_start_keep_selection.go b/pkg/integration/tests/interactive_rebase/quick_start_keep_selection.go deleted file mode 100644 index 55be5ea4adb..00000000000 --- a/pkg/integration/tests/interactive_rebase/quick_start_keep_selection.go +++ /dev/null @@ -1,54 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var QuickStartKeepSelection = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Starts an interactive rebase and checks that the same commit stays selected", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.MainBranches = []string{"master"} - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(1). - NewBranch("branch1"). - CreateNCommitsStartingAt(3, 2). - NewBranch("branch2"). - CreateNCommitsStartingAt(3, 5) - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI commit 07").IsSelected(), - Contains("CI commit 06"), - Contains("CI commit 05"), - Contains("CI * commit 04"), - Contains("CI commit 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Commits.StartInteractiveRebase). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("CI commit 07"), - Contains("pick").Contains("CI commit 06"), - Contains("pick").Contains("CI commit 05"), - Contains("update-ref").Contains("branch1"), - Contains("pick").Contains("CI commit 04"), - Contains("pick").Contains("CI commit 03"), - Contains("CI commit 02").IsSelected(), - Contains("--- Commits ---"), - Contains("CI commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/quick_start_keep_selection_range.go b/pkg/integration/tests/interactive_rebase/quick_start_keep_selection_range.go deleted file mode 100644 index 8ff8f106571..00000000000 --- a/pkg/integration/tests/interactive_rebase/quick_start_keep_selection_range.go +++ /dev/null @@ -1,59 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var QuickStartKeepSelectionRange = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Starts an interactive rebase and checks that the same commit range stays selected", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.MainBranches = []string{"master"} - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(1). - NewBranch("branch1"). - CreateNCommitsStartingAt(2, 2). - NewBranch("branch2"). - CreateNCommitsStartingAt(2, 4). - NewBranch("branch3"). - CreateNCommitsStartingAt(2, 6) - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - NavigateToLine(Contains("commit 04")). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.RangeSelectDown). - Lines( - Contains("CI commit 07"), - Contains("CI commit 06"), - Contains("CI * commit 05"), - Contains("CI commit 04").IsSelected(), - Contains("CI * commit 03").IsSelected(), - Contains("CI commit 02").IsSelected(), - Contains("CI commit 01"), - ). - Press(keys.Commits.StartInteractiveRebase). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("CI commit 07"), - Contains("CI commit 06"), - Contains("update-ref").Contains("branch2"), - Contains("CI commit 05"), - Contains("CI commit 04").IsSelected(), - Contains("update-ref").Contains("branch1").IsSelected(), - Contains("CI commit 03").IsSelected(), - Contains("CI commit 02").IsSelected(), - Contains("--- Commits ---"), - Contains("CI commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/rebase.go b/pkg/integration/tests/interactive_rebase/rebase.go deleted file mode 100644 index e1940ff7a56..00000000000 --- a/pkg/integration/tests/interactive_rebase/rebase.go +++ /dev/null @@ -1,134 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Rebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Begins an interactive rebase, then fixups, drops, and squashes some commits", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.EmptyCommit("first commit to edit") - shell.EmptyCommit("commit to squash") - shell.EmptyCommit("second commit to edit") - shell.EmptyCommit("commit to drop") - - shell.CreateFileAndAdd("fixup-commit-file", "fixup-commit-file") - shell.Commit("commit to fixup") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit to fixup"), - Contains("commit to drop"), - Contains("second commit to edit"), - Contains("commit to squash"), - Contains("first commit to edit"), - Contains("initial commit"), - ). - NavigateToLine(Contains("first commit to edit")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - MatchesRegexp("pick.*commit to fixup"), - MatchesRegexp("pick.*commit to drop"), - MatchesRegexp("pick.*second commit to edit"), - MatchesRegexp("pick.*commit to squash"), - Contains("--- Commits ---"), - Contains("first commit to edit").IsSelected(), - Contains("initial commit"), - ). - SelectPreviousItem(). - Press(keys.Commits.SquashDown). - Lines( - Contains("--- Pending rebase todos ---"), - MatchesRegexp("pick.*commit to fixup"), - MatchesRegexp("pick.*commit to drop"), - MatchesRegexp("pick.*second commit to edit"), - MatchesRegexp("squash.*commit to squash").IsSelected(), - Contains("--- Commits ---"), - Contains("first commit to edit"), - Contains("initial commit"), - ). - SelectPreviousItem(). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - MatchesRegexp("pick.*commit to fixup"), - MatchesRegexp("pick.*commit to drop"), - MatchesRegexp("edit.*second commit to edit").IsSelected(), - MatchesRegexp("squash.*commit to squash"), - Contains("--- Commits ---"), - Contains("first commit to edit"), - Contains("initial commit"), - ). - SelectPreviousItem(). - Press(keys.Universal.Remove). - Lines( - Contains("--- Pending rebase todos ---"), - MatchesRegexp("pick.*commit to fixup"), - MatchesRegexp("drop.*commit to drop").IsSelected(), - MatchesRegexp("edit.*second commit to edit"), - MatchesRegexp("squash.*commit to squash"), - Contains("--- Commits ---"), - Contains("first commit to edit"), - Contains("initial commit"), - ). - SelectPreviousItem(). - Press(keys.Commits.MarkCommitAsFixup). - Lines( - Contains("--- Pending rebase todos ---"), - MatchesRegexp("fixup.*commit to fixup").IsSelected(), - MatchesRegexp("drop.*commit to drop"), - MatchesRegexp("edit.*second commit to edit"), - MatchesRegexp("squash.*commit to squash"), - Contains("--- Commits ---"), - Contains("first commit to edit"), - Contains("initial commit"), - ). - Tap(func() { - t.Common().ContinueRebase() - }). - Lines( - Contains("--- Pending rebase todos ---"), - MatchesRegexp("fixup.*commit to fixup").IsSelected(), - MatchesRegexp("drop.*commit to drop"), - Contains("--- Commits ---"), - Contains("second commit to edit"), - MatchesRegexp("first commit to edit"), - Contains("initial commit"), - ). - Tap(func() { - t.Common().ContinueRebase() - }). - Lines( - Contains("second commit to edit").IsSelected(), - Contains("first commit to edit"), - Contains("initial commit"), - ). - Tap(func() { - // commit 4 was squashed into 6 so we assert that their messages have been concatenated - t.Views().Main().Content( - Contains("second commit to edit"). - // file from fixup commit is present - Contains("fixup-commit-file"). - // but message is not (because it's a fixup, not a squash) - DoesNotContain("commit to fixup"), - ) - }). - SelectNextItem(). - Tap(func() { - // commit 4 was squashed into 6 so we assert that their messages have been concatenated - t.Views().Main().Content( - Contains("first commit to edit"). - // message from squashed commit has been concatenated with message other commit - Contains("commit to squash"), - ) - }) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/rebase_with_commit_that_becomes_empty.go b/pkg/integration/tests/interactive_rebase/rebase_with_commit_that_becomes_empty.go deleted file mode 100644 index e9b743856d0..00000000000 --- a/pkg/integration/tests/interactive_rebase/rebase_with_commit_that_becomes_empty.go +++ /dev/null @@ -1,45 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RebaseWithCommitThatBecomesEmpty = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Performs a rebase involving a commit that becomes empty during the rebase, and gets dropped.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - // It is important that we create two separate commits for the two - // changes to the file, but only one commit for the same changes on our - // branch; otherwise, the commit would be discarded at the start of the - // rebase already. - shell.CreateFileAndAdd("file", "change 1\n") - shell.Commit("master change 1") - shell.UpdateFileAndAdd("file", "change 1\nchange 2\n") - shell.Commit("master change 2") - shell.NewBranchFrom("branch", "HEAD^^") - shell.CreateFileAndAdd("file", "change 1\nchange 2\n") - shell.Commit("branch change") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("master")). - Press(keys.Branches.RebaseBranch) - - t.ExpectPopup().Menu(). - Title(Equals("Rebase 'branch'")). - Select(Contains("Simple rebase")). - Confirm() - - t.Views().Commits(). - Lines( - Contains("master change 2"), - Contains("master change 1"), - Contains("initial commit"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/revert_during_rebase_when_stopped_on_edit.go b/pkg/integration/tests/interactive_rebase/revert_during_rebase_when_stopped_on_edit.go deleted file mode 100644 index 16a2b8c25a2..00000000000 --- a/pkg/integration/tests/interactive_rebase/revert_during_rebase_when_stopped_on_edit.go +++ /dev/null @@ -1,64 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RevertDuringRebaseWhenStoppedOnEdit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Revert a series of commits while stopped in a rebase", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("master commit 1") - shell.EmptyCommit("master commit 2") - shell.NewBranch("branch") - shell.CreateNCommits(4) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 04").IsSelected(), - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - Contains("master commit 2"), - Contains("master commit 1"), - ). - NavigateToLine(Contains("commit 03")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit 04"), - Contains("--- Commits ---"), - Contains("commit 03").IsSelected(), - Contains("commit 02"), - Contains("commit 01"), - Contains("master commit 2"), - Contains("master commit 1"), - ). - SelectNextItem(). - Press(keys.Universal.RangeSelectDown). - Press(keys.Commits.RevertCommit). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Revert commit")). - Content(MatchesRegexp(`Are you sure you want to revert \w+?`)). - Confirm() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit 04"), - Contains("--- Commits ---"), - Contains(`Revert "commit 01"`), - Contains(`Revert "commit 02"`), - Contains("commit 03"), - Contains("commit 02").IsSelected(), - Contains("commit 01").IsSelected(), - Contains("master commit 2"), - Contains("master commit 1"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/revert_multiple_commits_in_interactive_rebase.go b/pkg/integration/tests/interactive_rebase/revert_multiple_commits_in_interactive_rebase.go deleted file mode 100644 index 529c0a5ec3b..00000000000 --- a/pkg/integration/tests/interactive_rebase/revert_multiple_commits_in_interactive_rebase.go +++ /dev/null @@ -1,115 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RevertMultipleCommitsInInteractiveRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Reverts a range of commits, the first of which conflicts, in the middle of an interactive rebase", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", "") - shell.Commit("add empty file") - shell.CreateFileAndAdd("otherfile", "") - shell.Commit("unrelated change 1") - shell.CreateFileAndAdd("myfile", "first line\n") - shell.Commit("add first line") - shell.UpdateFileAndAdd("myfile", "first line\nsecond line\n") - shell.Commit("add second line") - shell.EmptyCommit("unrelated change 2") - shell.EmptyCommit("unrelated change 3") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI ◯ unrelated change 3").IsSelected(), - Contains("CI ◯ unrelated change 2"), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line"), - Contains("CI ◯ unrelated change 1"), - Contains("CI ◯ add empty file"), - ). - NavigateToLine(Contains("add second line")). - Press(keys.Universal.Edit). - SelectNextItem(). - Press(keys.Universal.RangeSelectDown). - Press(keys.Commits.RevertCommit). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Revert commit")). - Content(Equals("Are you sure you want to revert the selected commits?")). - Confirm() - - t.ExpectPopup().Menu(). - Title(Equals("Conflicts!")). - Select(Contains("View conflicts")). - Confirm() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("CI unrelated change 3"), - Contains("CI unrelated change 2"), - Contains("--- Pending reverts ---"), - Contains("revert").Contains("CI unrelated change 1"), - Contains("revert").Contains("CI <-- CONFLICT --- add first line"), - Contains("--- Commits ---"), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line"), - Contains("CI ◯ unrelated change 1"), - Contains("CI ◯ add empty file"), - ) - - t.Views().Options().Content(Contains("View revert options: m")) - t.Views().Information().Content(Contains("Reverting (Reset)")) - - t.Views().Files().IsFocused(). - Lines( - Contains("UU myfile").IsSelected(), - ). - PressEnter() - - t.Views().MergeConflicts().IsFocused(). - SelectNextItem(). - PressPrimaryAction() - - t.ExpectPopup().Alert(). - Title(Equals("Continue")). - Content(Contains("All merge conflicts resolved. Continue the revert?")). - Confirm() - - t.Views().Commits(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("CI unrelated change 3"), - Contains("pick").Contains("CI unrelated change 2"), - Contains("--- Commits ---"), - Contains(`CI ◯ Revert "unrelated change 1"`), - Contains(`CI ◯ Revert "add first line"`), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line"), - Contains("CI ◯ unrelated change 1"), - Contains("CI ◯ add empty file"), - ) - - t.Views().Options().Content(Contains("View rebase options: m")) - t.Views().Information().Content(Contains("Rebasing (Reset)")) - - t.Common().ContinueRebase() - - t.Views().Commits(). - Lines( - Contains("CI ◯ unrelated change 3"), - Contains("CI ◯ unrelated change 2"), - Contains(`CI ◯ Revert "unrelated change 1"`), - Contains(`CI ◯ Revert "add first line"`), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line"), - Contains("CI ◯ unrelated change 1"), - Contains("CI ◯ add empty file"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/revert_single_commit_in_interactive_rebase.go b/pkg/integration/tests/interactive_rebase/revert_single_commit_in_interactive_rebase.go deleted file mode 100644 index 7126699dc08..00000000000 --- a/pkg/integration/tests/interactive_rebase/revert_single_commit_in_interactive_rebase.go +++ /dev/null @@ -1,112 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RevertSingleCommitInInteractiveRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Reverts a commit that conflicts in the middle of an interactive rebase", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", "") - shell.Commit("add empty file") - shell.CreateFileAndAdd("myfile", "first line\n") - shell.Commit("add first line") - shell.UpdateFileAndAdd("myfile", "first line\nsecond line\n") - shell.Commit("add second line") - shell.EmptyCommit("unrelated change 1") - shell.EmptyCommit("unrelated change 2") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI ◯ unrelated change 2").IsSelected(), - Contains("CI ◯ unrelated change 1"), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line"), - Contains("CI ◯ add empty file"), - ). - NavigateToLine(Contains("add second line")). - Press(keys.Universal.Edit). - SelectNextItem(). - Press(keys.Commits.RevertCommit). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Revert commit")). - Content(MatchesRegexp(`Are you sure you want to revert \w+?`)). - Confirm() - t.ExpectPopup().Menu(). - Title(Equals("Conflicts!")). - Select(Contains("View conflicts")). - Cancel() // stay in commits panel - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("CI unrelated change 2"), - Contains("CI unrelated change 1"), - Contains("--- Pending reverts ---"), - Contains("revert").Contains("CI <-- CONFLICT --- add first line"), - Contains("--- Commits ---"), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line").IsSelected(), - Contains("CI ◯ add empty file"), - ). - Press(keys.Commits.MoveDownCommit). - Tap(func() { - t.ExpectToast(Equals("Disabled: This action is not allowed while cherry-picking or reverting")) - }). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectToast(Equals("Disabled: This action is not allowed while cherry-picking or reverting")) - }) - - t.Views().Options().Content(Contains("View revert options: m")) - t.Views().Information().Content(Contains("Reverting (Reset)")) - - t.Views().Files().Focus(). - Lines( - Contains("UU myfile").IsSelected(), - ). - PressEnter() - - t.Views().MergeConflicts().IsFocused(). - SelectNextItem(). - PressPrimaryAction() - - t.ExpectPopup().Alert(). - Title(Equals("Continue")). - Content(Contains("All merge conflicts resolved. Continue the revert?")). - Confirm() - - t.Views().Commits(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("CI unrelated change 2"), - Contains("pick").Contains("CI unrelated change 1"), - Contains("--- Commits ---"), - Contains(`CI ◯ Revert "add first line"`), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line"), - Contains("CI ◯ add empty file"), - ) - - t.Views().Options().Content(Contains("View rebase options: m")) - t.Views().Information().Content(Contains("Rebasing (Reset)")) - - t.Common().ContinueRebase() - - t.Views().Commits(). - Lines( - Contains("CI ◯ unrelated change 2"), - Contains("CI ◯ unrelated change 1"), - Contains(`CI ◯ Revert "add first line"`), - Contains("CI ◯ add second line"), - Contains("CI ◯ add first line"), - Contains("CI ◯ add empty file"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/reword_commit_with_editor_and_fail.go b/pkg/integration/tests/interactive_rebase/reword_commit_with_editor_and_fail.go deleted file mode 100644 index df6486772e7..00000000000 --- a/pkg/integration/tests/interactive_rebase/reword_commit_with_editor_and_fail.go +++ /dev/null @@ -1,47 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RewordCommitWithEditorAndFail = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rewords a commit with editor, and fails because an empty commit message is given", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(3). - SetConfig("core.editor", "sh -c 'echo .git/COMMIT_EDITMSG'") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 03").IsSelected(), - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Commits.RenameCommitWithEditor). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Reword in editor")). - Content(Contains("Are you sure you want to reword this commit in your editor?")). - Confirm() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ) - - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Contains("exit status 1")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/reword_first_commit.go b/pkg/integration/tests/interactive_rebase/reword_first_commit.go deleted file mode 100644 index cb9afc3c457..00000000000 --- a/pkg/integration/tests/interactive_rebase/reword_first_commit.go +++ /dev/null @@ -1,42 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// Rewording the first commit is tricky because you can't rebase from its parent commit, -// hence having a specific test for this - -var RewordFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rewords the first commit, just to show that it's possible", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(2) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 01")). - Press(keys.Commits.RenameCommit). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Reword commit")). - InitialText(Equals("commit 01")). - Clear(). - Type("renamed 01"). - Confirm() - }). - Lines( - Contains("commit 02"), - Contains("renamed 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/reword_last_commit.go b/pkg/integration/tests/interactive_rebase/reword_last_commit.go deleted file mode 100644 index 5d3038feb30..00000000000 --- a/pkg/integration/tests/interactive_rebase/reword_last_commit.go +++ /dev/null @@ -1,38 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RewordLastCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rewords the last (HEAD) commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(2) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ). - Press(keys.Commits.RenameCommit). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Reword commit")). - InitialText(Equals("commit 02")). - Clear(). - Type("renamed 02"). - Confirm() - }). - Lines( - Contains("renamed 02"), - Contains("commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/reword_last_commit_of_stacked_branch.go b/pkg/integration/tests/interactive_rebase/reword_last_commit_of_stacked_branch.go deleted file mode 100644 index e9cdc3a1a49..00000000000 --- a/pkg/integration/tests/interactive_rebase/reword_last_commit_of_stacked_branch.go +++ /dev/null @@ -1,55 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RewordLastCommitOfStackedBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rewords the last commit of a branch in the middle of a stack", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.MainBranches = []string{"master"} - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(1). - NewBranch("branch1"). - CreateNCommitsStartingAt(2, 2). - NewBranch("branch2"). - CreateNCommitsStartingAt(2, 4) - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI commit 05").IsSelected(), - Contains("CI commit 04"), - Contains("CI * commit 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ). - NavigateToLine(Contains("commit 03")). - Press(keys.Commits.RenameCommit). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Reword commit")). - InitialText(Equals("commit 03")). - Clear(). - Type("renamed 03"). - Confirm() - }). - Lines( - Contains("CI commit 05"), - Contains("CI commit 04"), - Contains("CI * renamed 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/reword_merge_commit.go b/pkg/integration/tests/interactive_rebase/reword_merge_commit.go deleted file mode 100644 index 6c096906609..00000000000 --- a/pkg/integration/tests/interactive_rebase/reword_merge_commit.go +++ /dev/null @@ -1,50 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RewordMergeCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rewords a merge commit which is not the current head commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("base"). - NewBranch("first-branch"). - CreateFileAndAdd("file1.txt", "content"). - Commit("one"). - Checkout("master"). - Merge("first-branch"). - NewBranch("second-branch"). - EmptyCommit("two") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI ◯ two").IsSelected(), - Contains("CI ⏣─╮ Merge branch 'first-branch'"), - Contains("CI │ ◯ one"), - Contains("CI ◯─╯ base"), - ). - SelectNextItem(). - Press(keys.Commits.RenameCommit). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Reword commit")). - InitialText(Equals("Merge branch 'first-branch'")). - Clear(). - Type("renamed merge"). - Confirm() - }). - Lines( - Contains("CI ◯ two"), - Contains("CI ⏣─╮ renamed merge").IsSelected(), - Contains("CI │ ◯ one"), - Contains("CI ◯ ╯ base"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/reword_you_are_here_commit.go b/pkg/integration/tests/interactive_rebase/reword_you_are_here_commit.go deleted file mode 100644 index 92aaf1a43d1..00000000000 --- a/pkg/integration/tests/interactive_rebase/reword_you_are_here_commit.go +++ /dev/null @@ -1,51 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RewordYouAreHereCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rewords the current HEAD commit in an interactive rebase", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(3) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 03").IsSelected(), - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ). - Press(keys.Commits.RenameCommit). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Reword commit")). - InitialText(Equals("commit 02")). - Clear(). - Type("renamed 02"). - Confirm() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("renamed 02").IsSelected(), - Contains("commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/reword_you_are_here_commit_with_editor.go b/pkg/integration/tests/interactive_rebase/reword_you_are_here_commit_with_editor.go deleted file mode 100644 index b927684fefc..00000000000 --- a/pkg/integration/tests/interactive_rebase/reword_you_are_here_commit_with_editor.go +++ /dev/null @@ -1,51 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RewordYouAreHereCommitWithEditor = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rewords the current HEAD commit in an interactive rebase with editor", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(3). - SetConfig("core.editor", "sh -c 'echo renamed 02 >.git/COMMIT_EDITMSG'") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 03").IsSelected(), - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ). - Press(keys.Commits.RenameCommitWithEditor). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Reword in editor")). - Content(Contains("Are you sure you want to reword this commit in your editor?")). - Confirm() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit 03"), - Contains("--- Commits ---"), - Contains("renamed 02").IsSelected(), - Contains("commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/shared.go b/pkg/integration/tests/interactive_rebase/shared.go deleted file mode 100644 index ea6626fd652..00000000000 --- a/pkg/integration/tests/interactive_rebase/shared.go +++ /dev/null @@ -1,77 +0,0 @@ -package interactive_rebase - -import ( - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -func handleConflictsFromSwap(t *TestDriver, expectedCommand string) { - t.Common().AcknowledgeConflicts() - - t.Views().Commits(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("commit two"), - Contains(expectedCommand).Contains("<-- CONFLICT --- commit three"), - Contains("--- Commits ---"), - Contains("commit one"), - ) - - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU myfile"), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - TopLines( - Contains("<<<<<<< HEAD"), - Contains("one"), - Contains("======="), - Contains("three"), - Contains(">>>>>>>"), - ). - SelectNextItem(). - PressPrimaryAction() // pick "three" - - t.Common().ContinueOnConflictsResolved("rebase") - - t.Common().AcknowledgeConflicts() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU myfile"), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - TopLines( - Contains("<<<<<<< HEAD"), - Contains("three"), - Contains("======="), - Contains("two"), - Contains(">>>>>>>"), - ). - SelectNextItem(). - PressPrimaryAction() // pick "two" - - t.Common().ContinueOnConflictsResolved("rebase") - - t.Views().Commits(). - Focus(). - Lines( - Contains("commit two").IsSelected(), - Contains("commit three"), - Contains("commit one"), - ). - Tap(func() { - t.Views().Main().Content(Contains("-three").Contains("+two")) - }). - SelectNextItem(). - Tap(func() { - t.Views().Main().Content(Contains("-one").Contains("+three")) - }) -} diff --git a/pkg/integration/tests/interactive_rebase/show_exec_todos.go b/pkg/integration/tests/interactive_rebase/show_exec_todos.go deleted file mode 100644 index 1ae51584523..00000000000 --- a/pkg/integration/tests/interactive_rebase/show_exec_todos.go +++ /dev/null @@ -1,61 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ShowExecTodos = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Show exec todos in the rebase todo list", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "X", - Context: "commits", - Command: "git -c core.editor=: rebase -i -x false HEAD^^", - }, - } - }, - SetupRepo: func(shell *Shell) { - shell. - NewBranch("branch1"). - CreateNCommits(3) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Press("X"). - Tap(func() { - t.ExpectPopup().Alert().Title(Equals("Error")).Content(Contains("Rebasing (2/4)Executing: false")).Confirm() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("exec").Contains("false"), - Contains("pick").Contains("CI commit 03"), - Contains("--- Commits ---"), - Contains("CI ◯ commit 02"), - Contains("CI ◯ commit 01"), - ). - Tap(func() { - t.Common().ContinueRebase() - t.ExpectPopup().Alert().Title(Equals("Error")).Content(Contains("exit status 1")).Confirm() - }). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("--- Commits ---"), - Contains("CI ◯ commit 03"), - Contains("CI ◯ commit 02"), - Contains("CI ◯ commit 01"), - ). - Tap(func() { - t.Common().ContinueRebase() - }). - Lines( - Contains("CI ◯ commit 03"), - Contains("CI ◯ commit 02"), - Contains("CI ◯ commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/squash_down_first_commit.go b/pkg/integration/tests/interactive_rebase/squash_down_first_commit.go deleted file mode 100644 index 65d6bfaa7c9..00000000000 --- a/pkg/integration/tests/interactive_rebase/squash_down_first_commit.go +++ /dev/null @@ -1,34 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SquashDownFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Tries to squash down the first commit, which results in an error message", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(2) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 01")). - Press(keys.Commits.SquashDown). - Tap(func() { - t.ExpectToast(Equals("Disabled: There's no commit below to squash into")) - }). - Lines( - Contains("commit 02"), - Contains("commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/squash_down_second_commit.go b/pkg/integration/tests/interactive_rebase/squash_down_second_commit.go deleted file mode 100644 index 6ba313f7a71..00000000000 --- a/pkg/integration/tests/interactive_rebase/squash_down_second_commit.go +++ /dev/null @@ -1,43 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SquashDownSecondCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Squash down the second commit into the first (initial)", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(3) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Commits.SquashDown). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Squash")). - Content(Equals("Are you sure you want to squash the selected commit(s) into the commit below?")). - Confirm() - }). - Lines( - Contains("commit 03"), - Contains("commit 01").IsSelected(), - ) - - t.Views().Main(). - Content(Contains(" commit 01\n \n commit 02")). - Content(Contains("+file01 content")). - Content(Contains("+file02 content")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/squash_fixups_above.go b/pkg/integration/tests/interactive_rebase/squash_fixups_above.go deleted file mode 100644 index 467a66154be..00000000000 --- a/pkg/integration/tests/interactive_rebase/squash_fixups_above.go +++ /dev/null @@ -1,56 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SquashFixupsAbove = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Squashes all fixups above a commit and checks that the selected line stays correct.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(3). - CreateFileAndAdd("fixup-file", "fixup content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 03"), - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 02")). - Press(keys.Commits.CreateFixupCommit). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Create fixup commit")). - Select(Contains("fixup! commit")). - Confirm() - }). - Lines( - Contains("fixup! commit 02"), - Contains("commit 03"), - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ). - Press(keys.Commits.SquashAboveCommits). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Apply fixup commits")). - Select(Contains("Above the selected commit")). - Confirm() - }). - Lines( - Contains("commit 03"), - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ) - - t.Views().Main(). - Content(Contains("fixup content")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/squash_fixups_above_first_commit.go b/pkg/integration/tests/interactive_rebase/squash_fixups_above_first_commit.go deleted file mode 100644 index 2d71093ba88..00000000000 --- a/pkg/integration/tests/interactive_rebase/squash_fixups_above_first_commit.go +++ /dev/null @@ -1,49 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SquashFixupsAboveFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Squashes all fixups above the first (initial) commit.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(2). - CreateFileAndAdd("fixup-file", "fixup content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 02"), - Contains("commit 01"), - ). - NavigateToLine(Contains("commit 01")). - Press(keys.Commits.CreateFixupCommit). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Create fixup commit")). - Select(Contains("fixup! commit")). - Confirm() - }). - NavigateToLine(Contains("commit 01").DoesNotContain("fixup!")). - Press(keys.Commits.SquashAboveCommits). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Apply fixup commits")). - Select(Contains("Above the selected commit")). - Confirm() - }). - Lines( - Contains("commit 02"), - Contains("commit 01").IsSelected(), - ) - - t.Views().Main(). - Content(Contains("fixup content")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/squash_fixups_in_current_branch.go b/pkg/integration/tests/interactive_rebase/squash_fixups_in_current_branch.go deleted file mode 100644 index c6721d829e4..00000000000 --- a/pkg/integration/tests/interactive_rebase/squash_fixups_in_current_branch.go +++ /dev/null @@ -1,56 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SquashFixupsInCurrentBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Squashes all fixups in the current branch.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - CreateFileAndAdd("file1", "file1"). - Commit("master commit"). - NewBranch("branch"). - // Test the pathological case that the first commit of a branch is a - // fixup for the last master commit below it. We _don't_ want this to - // be squashed. - UpdateFileAndAdd("file1", "changed file1"). - Commit("fixup! master commit"). - CreateNCommits(2). - CreateFileAndAdd("fixup-file", "fixup content"). - Commit("fixup! commit 01") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - SelectNextItem(). - SelectNextItem(). - Lines( - Contains("fixup! commit 01"), - Contains("commit 02"), - Contains("commit 01").IsSelected(), - Contains("fixup! master commit"), - Contains("master commit"), - ). - Press(keys.Commits.SquashAboveCommits). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Apply fixup commits")). - Select(Contains("In current branch")). - Confirm() - }). - Lines( - Contains("commit 02"), - Contains("commit 01").IsSelected(), - Contains("fixup! master commit"), - Contains("master commit"), - ) - - t.Views().Main(). - Content(Contains("fixup content")) - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/swap_in_rebase_with_conflict.go b/pkg/integration/tests/interactive_rebase/swap_in_rebase_with_conflict.go deleted file mode 100644 index f6653f9b008..00000000000 --- a/pkg/integration/tests/interactive_rebase/swap_in_rebase_with_conflict.go +++ /dev/null @@ -1,53 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SwapInRebaseWithConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Via an edit-triggered rebase, swap two commits, causing a conflict. Then resolve the conflict and continue", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", "one") - shell.Commit("commit one") - shell.UpdateFileAndAdd("myfile", "two") - shell.Commit("commit two") - shell.UpdateFileAndAdd("myfile", "three") - shell.Commit("commit three") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit three").IsSelected(), - Contains("commit two"), - Contains("commit one"), - ). - NavigateToLine(Contains("commit one")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit three"), - Contains("commit two"), - Contains("--- Commits ---"), - Contains("commit one").IsSelected(), - ). - SelectPreviousItem(). - Press(keys.Commits.MoveUpCommit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit two").IsSelected(), - Contains("commit three"), - Contains("--- Commits ---"), - Contains("commit one"), - ). - Tap(func() { - t.Common().ContinueRebase() - }) - - handleConflictsFromSwap(t, "pick") - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/swap_in_rebase_with_conflict_and_edit.go b/pkg/integration/tests/interactive_rebase/swap_in_rebase_with_conflict_and_edit.go deleted file mode 100644 index f5beb437435..00000000000 --- a/pkg/integration/tests/interactive_rebase/swap_in_rebase_with_conflict_and_edit.go +++ /dev/null @@ -1,56 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SwapInRebaseWithConflictAndEdit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Via an edit-triggered rebase, swap two commits, causing a conflict, then edit the commit that will conflict.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", "one") - shell.Commit("commit one") - shell.UpdateFileAndAdd("myfile", "two") - shell.Commit("commit two") - shell.UpdateFileAndAdd("myfile", "three") - shell.Commit("commit three") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit three").IsSelected(), - Contains("commit two"), - Contains("commit one"), - ). - NavigateToLine(Contains("commit one")). - Press(keys.Universal.Edit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit three"), - Contains("commit two"), - Contains("--- Commits ---"), - Contains("commit one").IsSelected(), - ). - NavigateToLine(Contains("commit two")). - Press(keys.Commits.MoveUpCommit). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("commit two").IsSelected(), - Contains("commit three"), - Contains("--- Commits ---"), - Contains("commit one"), - ). - NavigateToLine(Contains("commit three")). - Press(keys.Universal.Edit). - SelectPreviousItem(). - Tap(func() { - t.Common().ContinueRebase() - }) - - handleConflictsFromSwap(t, "edit") - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/swap_with_conflict.go b/pkg/integration/tests/interactive_rebase/swap_with_conflict.go deleted file mode 100644 index 1ea71356e28..00000000000 --- a/pkg/integration/tests/interactive_rebase/swap_with_conflict.go +++ /dev/null @@ -1,33 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SwapWithConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Directly swap two commits, causing a conflict. Then resolve the conflict and continue", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("myfile", "one") - shell.Commit("commit one") - shell.UpdateFileAndAdd("myfile", "two") - shell.Commit("commit two") - shell.UpdateFileAndAdd("myfile", "three") - shell.Commit("commit three") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit three").IsSelected(), - Contains("commit two"), - Contains("commit one"), - ). - Press(keys.Commits.MoveDownCommit) - - handleConflictsFromSwap(t, "pick") - }, -}) diff --git a/pkg/integration/tests/interactive_rebase/view_files_of_todo_entries.go b/pkg/integration/tests/interactive_rebase/view_files_of_todo_entries.go deleted file mode 100644 index f52e8070319..00000000000 --- a/pkg/integration/tests/interactive_rebase/view_files_of_todo_entries.go +++ /dev/null @@ -1,54 +0,0 @@ -package interactive_rebase - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ViewFilesOfTodoEntries = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Check that files of a pick todo can be viewed, but files of an update-ref todo can't", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - CreateNCommits(1). - NewBranch("branch1"). - CreateNCommitsStartingAt(1, 2). - NewBranch("branch2"). - CreateNCommitsStartingAt(1, 3) - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Press(keys.Commits.StartInteractiveRebase). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("CI commit 03").IsSelected(), - Contains("update-ref").Contains("branch1"), - Contains("pick").Contains("CI commit 02"), - Contains("--- Commits ---"), - Contains("CI commit 01"), - ). - Press(keys.Universal.GoInto) - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file03.txt"), - ). - PressEscape() - - t.Views().Commits(). - IsFocused(). - NavigateToLine(Contains("update-ref")). - Press(keys.Universal.GoInto) - - t.ExpectToast(Equals("Disabled: Selected item does not have files to view")) - }, -}) diff --git a/pkg/integration/tests/misc/confirm_on_quit.go b/pkg/integration/tests/misc/confirm_on_quit.go deleted file mode 100644 index 676c11a3eee..00000000000 --- a/pkg/integration/tests/misc/confirm_on_quit.go +++ /dev/null @@ -1,26 +0,0 @@ -package misc - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ConfirmOnQuit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Quitting with a confirm prompt", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().ConfirmOnQuit = true - }, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Quit) - - t.ExpectPopup().Confirmation(). - Title(Equals("")). - Content(Contains("Are you sure you want to quit?")). - Confirm() - }, -}) diff --git a/pkg/integration/tests/misc/copy_confirmation_message_to_clipboard.go b/pkg/integration/tests/misc/copy_confirmation_message_to_clipboard.go deleted file mode 100644 index 6d98f856ad5..00000000000 --- a/pkg/integration/tests/misc/copy_confirmation_message_to_clipboard.go +++ /dev/null @@ -1,40 +0,0 @@ -package misc - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CopyConfirmationMessageToClipboard = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Copy the text of a confirmation popup to the clipboard", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > clipboard" - }, - - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("commit") - }, - - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit").IsSelected(), - ). - Press(keys.Universal.Remove) - - t.ExpectPopup().Alert(). - Title(Equals("Drop commit")). - Content(Equals("Are you sure you want to drop the selected commit(s)?")). - Tap(func() { - t.GlobalPress(keys.Universal.CopyToClipboard) - t.ExpectToast(Equals("Message copied to clipboard")) - }). - Confirm() - - t.FileSystem().FileContent("clipboard", - Equals("Are you sure you want to drop the selected commit(s)?")) - }, -}) diff --git a/pkg/integration/tests/misc/copy_to_clipboard.go b/pkg/integration/tests/misc/copy_to_clipboard.go deleted file mode 100644 index 594925614c6..00000000000 --- a/pkg/integration/tests/misc/copy_to_clipboard.go +++ /dev/null @@ -1,34 +0,0 @@ -package misc - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// We're emulating the clipboard by writing to a file called clipboard - -var CopyToClipboard = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Copy a branch name to the clipboard using custom clipboard command template", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > clipboard" - }, - - SetupRepo: func(shell *Shell) { - shell.NewBranch("branch-a") - }, - - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("branch-a").IsSelected(), - ). - Press(keys.Universal.CopyToClipboard) - - t.ExpectToast(Equals("'branch-a' copied to clipboard")) - - t.FileSystem().FileContent("clipboard", Equals("branch-a")) - }, -}) diff --git a/pkg/integration/tests/misc/disabled_keybindings.go b/pkg/integration/tests/misc/disabled_keybindings.go deleted file mode 100644 index 7ab1ba42a7d..00000000000 --- a/pkg/integration/tests/misc/disabled_keybindings.go +++ /dev/null @@ -1,26 +0,0 @@ -package misc - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DisabledKeybindings = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Confirms you can disable keybindings by setting them to ", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Keybinding.Universal.PrevItem = "" - config.GetUserConfig().Keybinding.Universal.NextItem = "" - config.GetUserConfig().Keybinding.Universal.NextTab = "" - config.GetUserConfig().Keybinding.Universal.PrevTab = "" - }, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Press("") - - t.Views().Worktrees().IsFocused() - }, -}) diff --git a/pkg/integration/tests/misc/initial_open.go b/pkg/integration/tests/misc/initial_open.go deleted file mode 100644 index 4237a6feae3..00000000000 --- a/pkg/integration/tests/misc/initial_open.go +++ /dev/null @@ -1,24 +0,0 @@ -package misc - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var InitialOpen = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Confirms a popup appears on first opening Lazygit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().DisableStartupPopups = false - }, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.ExpectPopup().Confirmation(). - Title(Equals("")). - Content(Contains("Thanks for using lazygit!")). - Confirm() - - t.Views().Files().IsFocused() - }, -}) diff --git a/pkg/integration/tests/misc/recent_repos_on_launch.go b/pkg/integration/tests/misc/recent_repos_on_launch.go deleted file mode 100644 index 8cf4382a59d..00000000000 --- a/pkg/integration/tests/misc/recent_repos_on_launch.go +++ /dev/null @@ -1,28 +0,0 @@ -package misc - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// Couldn't find an easy way to actually reproduce the situation of opening outside a repo, -// so I'm introducing a hacky env var to force lazygit to show the recent repos menu upon opening. - -var RecentReposOnLaunch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "When opening to a menu, focus is correctly given to the menu", - ExtraCmdArgs: []string{}, - ExtraEnvVars: map[string]string{ - "SHOW_RECENT_REPOS": "true", - }, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.ExpectPopup().Menu(). - Title(Equals("Recent repositories")). - Select(Contains("Cancel")). - Confirm() - - t.Views().Files().IsFocused() - }, -}) diff --git a/pkg/integration/tests/patch_building/apply.go b/pkg/integration/tests/patch_building/apply.go deleted file mode 100644 index 533ad1e23a0..00000000000 --- a/pkg/integration/tests/patch_building/apply.go +++ /dev/null @@ -1,64 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Apply = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Apply a custom patch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("branch-a") - shell.CreateFileAndAdd("file1", "first line\n") - shell.Commit("first commit") - - shell.NewBranch("branch-b") - shell.UpdateFileAndAdd("file1", "first line\nsecond line\n") - shell.Commit("update") - - shell.Checkout("branch-a") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("branch-a").IsSelected(), - Contains("branch-b"), - ). - Press(keys.Universal.NextItem). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("update").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("M file1").IsSelected(), - ). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content(Contains("second line")) - - t.Common().SelectPatchOption(MatchesRegexp(`Apply patch$`)) - - t.Views().Files(). - Focus(). - Lines( - Contains("file1").IsSelected(), - ) - - t.Views().Main(). - Content(Contains("second line")) - }, -}) diff --git a/pkg/integration/tests/patch_building/apply_in_reverse.go b/pkg/integration/tests/patch_building/apply_in_reverse.go deleted file mode 100644 index f2aa6b3a83f..00000000000 --- a/pkg/integration/tests/patch_building/apply_in_reverse.go +++ /dev/null @@ -1,51 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ApplyInReverse = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Apply a custom patch in reverse", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "file1 content\n") - shell.CreateFileAndAdd("file2", "file2 content\n") - shell.Commit("first commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("first commit").IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" A file1"), - Equals(" A file2"), - ). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content(Contains("+file1 content")) - - t.Common().SelectPatchOption(Contains("Apply patch in reverse")) - - t.Views().Files(). - Focus(). - Lines( - Contains("D").Contains("file1").IsSelected(), - ) - - t.Views().Main(). - Content(Contains("-file1 content")) - }, -}) diff --git a/pkg/integration/tests/patch_building/apply_in_reverse_with_conflict.go b/pkg/integration/tests/patch_building/apply_in_reverse_with_conflict.go deleted file mode 100644 index 1a09cea7af9..00000000000 --- a/pkg/integration/tests/patch_building/apply_in_reverse_with_conflict.go +++ /dev/null @@ -1,99 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ApplyInReverseWithConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Apply a custom patch in reverse, resulting in a conflict", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "file1 content\n") - shell.CreateFileAndAdd("file2", "file2 content\n") - shell.Commit("first commit") - shell.UpdateFileAndAdd("file1", "file1 content\nmore file1 content\n") - shell.UpdateFileAndAdd("file2", "file2 content\nmore file2 content\n") - shell.Commit("second commit") - shell.UpdateFileAndAdd("file1", "file1 content\nmore file1 content\neven more file1\n") - shell.Commit("third commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("third commit").IsSelected(), - Contains("second commit"), - Contains("first commit"), - ). - NavigateToLine(Contains("second commit")). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" M file1"), - Equals(" M file2"), - ). - SelectNextItem(). - // Add both files to the patch; the first will conflict, the second won't - PressPrimaryAction(). - Tap(func() { - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content( - Contains("+more file1 content")) - }). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Secondary().Content( - Contains("+more file1 content").Contains("+more file2 content")) - - t.Common().SelectPatchOption(Contains("Apply patch in reverse")) - - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Contains("Applied patch to 'file1' with conflicts.")). - Confirm() - - t.Views().Files(). - Focus(). - Lines( - Equals("UU file1").IsSelected(), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - ContainsLines( - Contains("file1 content"), - Contains("<<<<<<< ours").IsSelected(), - Contains("more file1 content").IsSelected(), - Contains("even more file1").IsSelected(), - Contains("=======").IsSelected(), - Contains(">>>>>>> theirs"), - ). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Files(). - Focus(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" M file1"), - Equals(" M file2"), - ). - SelectNextItem() - - t.Views().Main(). - ContainsLines( - Contains(" file1 content"), - Contains("-more file1 content"), - Contains("-even more file1"), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/apply_with_modified_file_conflict.go b/pkg/integration/tests/patch_building/apply_with_modified_file_conflict.go deleted file mode 100644 index 5b529ad706d..00000000000 --- a/pkg/integration/tests/patch_building/apply_with_modified_file_conflict.go +++ /dev/null @@ -1,83 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ApplyWithModifiedFileConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Apply a custom patch, with a modified file in the working tree that conflicts with the patch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("branch-a") - shell.CreateFileAndAdd("file1", "1\n2\n3\n") - shell.Commit("first commit") - - shell.NewBranch("branch-b") - shell.UpdateFileAndAdd("file1", "11\n2\n3\n") - shell.Commit("update") - - shell.Checkout("branch-a") - shell.UpdateFile("file1", "111\n2\n3\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("branch-a").IsSelected(), - Contains("branch-b"), - ). - Press(keys.Universal.NextItem). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("update").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("M file1").IsSelected(), - ). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content(Contains("-1\n+11\n")) - - t.Common().SelectPatchOption(MatchesRegexp(`Apply patch$`)) - - t.ExpectPopup().Confirmation().Title(Equals("Must stage files")). - Content(Contains("Applying a patch to the index requires staging the unstaged files that are affected by the patch.")). - Confirm() - - t.ExpectPopup().Alert().Title(Equals("Error")). - Content(Contains("Applied patch to 'file1' with conflicts.")). - Confirm() - - t.Views().Files(). - Focus(). - Lines( - Equals("UU file1").IsSelected(), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - Lines( - Equals("<<<<<<< ours"), - Equals("111"), - Equals("======="), - Equals("11"), - Equals(">>>>>>> theirs"), - Equals("2"), - Equals("3"), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/apply_with_modified_file_no_conflict.go b/pkg/integration/tests/patch_building/apply_with_modified_file_no_conflict.go deleted file mode 100644 index 66d32a654ec..00000000000 --- a/pkg/integration/tests/patch_building/apply_with_modified_file_no_conflict.go +++ /dev/null @@ -1,69 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ApplyWithModifiedFileNoConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Apply a custom patch, with a modified file in the working tree that does not conflict with the patch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("branch-a") - shell.CreateFileAndAdd("file1", "1\n2\n3\n") - shell.Commit("first commit") - - shell.NewBranch("branch-b") - shell.UpdateFileAndAdd("file1", "1\n2\n3\n4\n") - shell.Commit("update") - - shell.Checkout("branch-a") - shell.UpdateFile("file1", "11\n2\n3\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("branch-a").IsSelected(), - Contains("branch-b"), - ). - Press(keys.Universal.NextItem). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("update").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("M file1").IsSelected(), - ). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content(Contains("3\n+4")) - - t.Common().SelectPatchOption(MatchesRegexp(`Apply patch$`)) - - t.ExpectPopup().Confirmation().Title(Equals("Must stage files")). - Content(Contains("Applying a patch to the index requires staging the unstaged files that are affected by the patch.")). - Confirm() - - t.Views().Files(). - Focus(). - Lines( - Equals("M file1").IsSelected(), - ) - - t.Views().Main(). - Content(Contains("-1\n+11\n 2\n 3\n+4")) - }, -}) diff --git a/pkg/integration/tests/patch_building/edit_line_in_patch_building_panel.go b/pkg/integration/tests/patch_building/edit_line_in_patch_building_panel.go deleted file mode 100644 index 2be88b7b8d1..00000000000 --- a/pkg/integration/tests/patch_building/edit_line_in_patch_building_panel.go +++ /dev/null @@ -1,47 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var EditLineInPatchBuildingPanel = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Edit a line in the patch building panel; make sure we end up on the right line", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.EditAtLine = "echo {{filename}}:{{line}} > edit-command" - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file.txt", "4\n5\n6\n") - shell.Commit("01") - shell.UpdateFileAndAdd("file.txt", "1\n2a\n2b\n3\n4\n5\n6\n") - shell.Commit("02") - shell.UpdateFile("file.txt", "1\n2\n3\n4\n5\n6\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("02").IsSelected(), - Contains("01"), - ). - Press(keys.Universal.NextItem). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("A file.txt").IsSelected(), - ). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - Content(Contains("+4\n+5\n+6")). - NavigateToLine(Contains("+5")). - Press(keys.Universal.Edit) - - t.FileSystem().FileContent("edit-command", Contains("file.txt:5\n")) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_range_to_index.go b/pkg/integration/tests/patch_building/move_range_to_index.go deleted file mode 100644 index c8d379c97dc..00000000000 --- a/pkg/integration/tests/patch_building/move_range_to_index.go +++ /dev/null @@ -1,73 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveRangeToIndex = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Apply a custom patch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "first line\n") - shell.Commit("first commit") - - shell.UpdateFileAndAdd("file1", "first line\nsecond line\n") - shell.CreateFileAndAdd("file2", "file two content\n") - shell.CreateFileAndAdd("file3", "file three content\n") - shell.Commit("second commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("second commit").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" M file1"), - Equals(" A file2"), - Equals(" A file3"), - ). - SelectNextItem(). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("file2")). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content(Contains("second line")) - t.Views().Secondary().Content(Contains("file two content")) - - t.Common().SelectPatchOption(MatchesRegexp(`Move patch out into index$`)) - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file3").IsSelected(), - ).PressEscape() - - t.Views().Files(). - Focus(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" M file1"), - Equals(" A file2"), - ) - - t.Views().Main(). - Content(Contains("second line")) - - t.Views().Files().Focus().NavigateToLine(Contains("file2")) - - t.Views().Main(). - Content(Contains("file two content")) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_earlier_commit.go b/pkg/integration/tests/patch_building/move_to_earlier_commit.go deleted file mode 100644 index 0c5e60f3557..00000000000 --- a/pkg/integration/tests/patch_building/move_to_earlier_commit.go +++ /dev/null @@ -1,89 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToEarlierCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to an earlier commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateDir("dir") - shell.CreateFileAndAdd("dir/file1", "file1 content") - shell.CreateFileAndAdd("dir/file2", "file2 content") - shell.Commit("first commit") - - shell.CreateFileAndAdd("unrelated-file", "") - shell.Commit("destination commit") - - shell.UpdateFileAndAdd("dir/file1", "file1 content with old changes") - shell.DeleteFileAndAdd("dir/file2") - shell.CreateFileAndAdd("dir/file3", "file3 content") - shell.Commit("commit to move from") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit to move from").IsSelected(), - Contains("destination commit"), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("dir").IsSelected(), - Contains(" M file1"), - Contains(" D file2"), - Contains(" A file3"), - ). - PressPrimaryAction(). - PressEscape() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Commits(). - IsFocused(). - SelectNextItem() - - t.Common().SelectPatchOption(Contains("Move patch to selected commit")) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("commit to move from"), - Contains("destination commit").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ dir"), - Equals(" M file1"), - Equals(" D file2"), - Equals(" A file3"), - Equals(" A unrelated-file"), - ). - PressEscape() - - t.Views().Commits(). - IsFocused(). - SelectPreviousItem(). - PressEnter() - - // the original commit has no more files in it - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("(none)"), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_earlier_commit_from_added_file.go b/pkg/integration/tests/patch_building/move_to_earlier_commit_from_added_file.go deleted file mode 100644 index a619128faca..00000000000 --- a/pkg/integration/tests/patch_building/move_to_earlier_commit_from_added_file.go +++ /dev/null @@ -1,115 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToEarlierCommitFromAddedFile = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a file that was added in a commit to an earlier commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - shell.EmptyCommit("destination commit") - shell.CreateFileAndAdd("file1", "1st line\n2nd line\n3rd line\n") - shell.Commit("commit to move from") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit to move from").IsSelected(), - Contains("destination commit"), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("A file").IsSelected(), - ). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Commits(). - Focus(). - SelectNextItem() - - t.Common().SelectPatchOption(Contains("Move patch to selected commit")) - - // This results in a conflict at the commit we're moving from, because - // it tries to add a file that already exists - t.Common().AcknowledgeConflicts() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("AA").Contains("file"), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - TopLines( - Contains("<<<<<<< HEAD"), - Contains("2nd line"), - Contains("======="), - Contains("1st line"), - Contains("2nd line"), - Contains("3rd line"), - Contains(">>>>>>>"), - ). - SelectNextItem(). - PressPrimaryAction() // choose the version with all three lines - - t.Common().ContinueOnConflictsResolved("rebase") - - t.Views().Commits(). - Focus(). - Lines( - Contains("commit to move from"), - Contains("destination commit").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("A file").IsSelected(), - ). - Tap(func() { - t.Views().Main().ContainsLines( - Equals("+2nd line"), - ) - }). - PressEscape() - - t.Views().Commits(). - IsFocused(). - NavigateToLine(Contains("commit to move from")). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("M file").IsSelected(), - ). - Tap(func() { - t.Views().Main().ContainsLines( - Equals("+1st line"), - Equals(" 2nd line"), - Equals("+3rd line"), - ) - }) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_index.go b/pkg/integration/tests/patch_building/move_to_index.go deleted file mode 100644 index 6eca8865f55..00000000000 --- a/pkg/integration/tests/patch_building/move_to_index.go +++ /dev/null @@ -1,68 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToIndex = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to the index", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "file1 content\n") - shell.CreateFileAndAdd("file2", "file2 content\n") - shell.Commit("first commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("first commit").IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Contains("file1"), - Contains("file2"), - ). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content(Contains("+file1 content")) - - t.Common().SelectPatchOption(Contains("Move patch out into index")) - - t.Views().Files(). - Lines( - Contains("A").Contains("file1"), - ) - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file2").IsSelected(), - ). - PressEscape() - - t.Views().Main(). - Content(Contains("+file2 content")) - - t.Views().Commits(). - Lines( - Contains("first commit").IsSelected(), - ) - - t.Views().Files(). - Focus() - - t.Views().Main(). - Content(Contains("file1 content")) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_index_from_added_file_with_conflict.go b/pkg/integration/tests/patch_building/move_to_index_from_added_file_with_conflict.go deleted file mode 100644 index 177e76e04eb..00000000000 --- a/pkg/integration/tests/patch_building/move_to_index_from_added_file_with_conflict.go +++ /dev/null @@ -1,96 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToIndexFromAddedFileWithConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a file that was added in a commit to the index, causing a conflict", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - - shell.CreateFileAndAdd("file1", "1st line\n2nd line\n3rd line\n") - shell.Commit("commit to move from") - shell.UpdateFileAndAdd("file1", "1st line\n2nd line changed\n3rd line\n") - shell.Commit("conflicting change") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("conflicting change").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Common().SelectPatchOption(Contains("Move patch out into index")) - - t.Common().AcknowledgeConflicts() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU").Contains("file1"), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - ContainsLines( - Contains("1st line"), - Contains("<<<<<<< HEAD"), - Contains("======="), - Contains("2nd line changed"), - Contains(">>>>>>>"), - Contains("3rd line"), - ). - SelectNextItem(). - PressPrimaryAction() - - t.Common().ContinueOnConflictsResolved("rebase") - - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Contains("Applied patch to 'file1' with conflicts")). - Confirm() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU").Contains("file1"), - ). - PressEnter() - - t.Views().MergeConflicts(). - TopLines( - Contains("1st line"), - Contains("<<<<<<< ours"), - Contains("2nd line changed"), - Contains("======="), - Contains("2nd line"), - Contains(">>>>>>> theirs"), - Contains("3rd line"), - ). - IsFocused() - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_index_part_of_adjacent_added_lines.go b/pkg/integration/tests/patch_building/move_to_index_part_of_adjacent_added_lines.go deleted file mode 100644 index bf06270b769..00000000000 --- a/pkg/integration/tests/patch_building/move_to_index_part_of_adjacent_added_lines.go +++ /dev/null @@ -1,69 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToIndexPartOfAdjacentAddedLines = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to the index, with only some lines of a range of adjacent added lines in the patch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "") - shell.Commit("first commit") - - shell.UpdateFileAndAdd("file1", "1st line\n2nd line\n") - shell.Commit("commit to move from") - - shell.UpdateFileAndAdd("unrelated-file", "") - shell.Commit("third commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("third commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Common().SelectPatchOption(Contains("Move patch out into index")) - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - Tap(func() { - t.Views().Main(). - Content(Contains("+2nd line"). - DoesNotContain("1st line")) - }) - - t.Views().Files(). - Focus(). - ContainsLines( - Contains("M").Contains("file1"), - ) - - t.Views().Main(). - Content(Contains("+1st line\n 2nd line")) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_index_partial.go b/pkg/integration/tests/patch_building/move_to_index_partial.go deleted file mode 100644 index 2f2e3ea4241..00000000000 --- a/pkg/integration/tests/patch_building/move_to_index_partial.go +++ /dev/null @@ -1,96 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToIndexPartial = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to the index. This is different from the MoveToIndex test in that we're only selecting a partial patch from a file", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "first line\nsecond line\nthird line\n") - shell.Commit("first commit") - - shell.UpdateFileAndAdd("file1", "first line2\nsecond line\nthird line2\n") - shell.Commit("second commit") - - shell.CreateFileAndAdd("file2", "file1 content") - shell.Commit("third commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("third commit").IsSelected(), - Contains("second commit"), - Contains("first commit"), - ). - NavigateToLine(Contains("second commit")). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - ContainsLines( - Contains(`-first line`).IsSelected(), - Contains(`+first line2`), - Contains(` second line`), - Contains(`-third line`), - Contains(`+third line2`), - ). - PressPrimaryAction(). - Tap(func() { - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary(). - ContainsLines( - Contains(`-first line`), - Contains(`+first line2`), - Contains(` second line`), - Contains(` third line`), - ) - - t.Common().SelectPatchOption(Contains("Move patch out into index")) - - t.Views().Files(). - Lines( - Contains("M").Contains("file1"), - ) - }) - - // Focus is automatically returned to the commit files panel. Arguably it shouldn't be. - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1"), - ) - - t.Views().Main(). - ContainsLines( - Contains(` first line`), - Contains(` second line`), - Contains(`-third line`), - Contains(`+third line2`), - ) - - t.Views().Files(). - Focus() - - t.Views().Main(). - ContainsLines( - Contains(`-first line`), - Contains(`+first line2`), - Contains(` second line`), - Contains(` third line2`), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_index_with_conflict.go b/pkg/integration/tests/patch_building/move_to_index_with_conflict.go deleted file mode 100644 index bdf0765d9ef..00000000000 --- a/pkg/integration/tests/patch_building/move_to_index_with_conflict.go +++ /dev/null @@ -1,89 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToIndexWithConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to the index, causing a conflict", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "file1 content") - shell.Commit("first commit") - - shell.UpdateFileAndAdd("file1", "file1 content with old changes") - shell.Commit("second commit") - - shell.UpdateFileAndAdd("file1", "file1 content with new changes") - shell.Commit("third commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("third commit").IsSelected(), - Contains("second commit"), - Contains("first commit"), - ). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Common().SelectPatchOption(Contains("Move patch out into index")) - - t.Common().AcknowledgeConflicts() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU").Contains("file1"), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - ContainsLines( - Contains("<<<<<<< HEAD").IsSelected(), - Contains("file1 content").IsSelected(), - Contains("=======").IsSelected(), - Contains("file1 content with new changes"), - Contains(">>>>>>>"), - ). - PressPrimaryAction() - - t.Common().ContinueOnConflictsResolved("rebase") - - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Contains("Applied patch to 'file1' with conflicts")). - Confirm() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU").Contains("file1"), - ). - PressEnter() - - t.Views().MergeConflicts(). - TopLines( - Contains("<<<<<<< ours"), - Contains("file1 content"), - Contains("======="), - Contains("file1 content with old changes"), - Contains(">>>>>>> theirs"), - ). - IsFocused() - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_index_with_modified_file.go b/pkg/integration/tests/patch_building/move_to_index_with_modified_file.go deleted file mode 100644 index 93aba6d4121..00000000000 --- a/pkg/integration/tests/patch_building/move_to_index_with_modified_file.go +++ /dev/null @@ -1,59 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToIndexWithModifiedFile = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to the index, with a modified file in the working tree that conflicts with the patch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "1\n2\n3\n4\n") - shell.Commit("first commit") - shell.UpdateFileAndAdd("file1", "11\n2\n3\n4\n") - shell.Commit("second commit") - shell.UpdateFile("file1", "111\n2\n3\n4\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("second commit").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("M file1"), - ). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content(Contains("-1\n+11")) - - t.Common().SelectPatchOption(Contains("Move patch out into index")) - - t.ExpectPopup().Confirmation().Title(Equals("Must stash")). - Content(Contains("Pulling a patch out into the index requires stashing and unstashing your changes.")). - Confirm() - - t.Views().Files(). - Focus(). - Lines( - Equals("MM file1"), - ) - - t.Views().Main(). - Content(Contains("-11\n+111\n")) - t.Views().Secondary(). - Content(Contains("-1\n+11\n")) - - t.Views().Stash().IsEmpty() - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_index_works_even_if_noprefix_is_set.go b/pkg/integration/tests/patch_building/move_to_index_works_even_if_noprefix_is_set.go deleted file mode 100644 index 8c2ba84b019..00000000000 --- a/pkg/integration/tests/patch_building/move_to_index_works_even_if_noprefix_is_set.go +++ /dev/null @@ -1,50 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToIndexWorksEvenIfNoprefixIsSet = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Moving a patch to the index works even if diff.noprefix or diff.external are set", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "file1 content\n") - shell.Commit("first commit") - - // Test that this works even if custom diff options are set - shell.SetConfig("diff.noprefix", "true") - shell.SetConfig("diff.external", "echo") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("first commit").IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressPrimaryAction() - - t.Views().Secondary().Content(Contains("+file1 content")) - - t.Common().SelectPatchOption(Contains("Move patch out into index")) - - t.Views().CommitFiles().IsFocused(). - Lines( - Equals("(none)"), - ) - - t.Views().Files(). - Lines( - Contains("A").Contains("file1"), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_later_commit.go b/pkg/integration/tests/patch_building/move_to_later_commit.go deleted file mode 100644 index aa97a950410..00000000000 --- a/pkg/integration/tests/patch_building/move_to_later_commit.go +++ /dev/null @@ -1,90 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToLaterCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to a later commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateDir("dir") - shell.CreateFileAndAdd("dir/file1", "file1 content") - shell.CreateFileAndAdd("dir/file2", "file2 content") - shell.Commit("first commit") - - shell.UpdateFileAndAdd("dir/file1", "file1 content with old changes") - shell.DeleteFileAndAdd("dir/file2") - shell.CreateFileAndAdd("dir/file3", "file3 content") - shell.Commit("commit to move from") - - shell.CreateFileAndAdd("unrelated-file", "") - shell.Commit("destination commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("destination commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("dir").IsSelected(), - Contains(" M file1"), - Contains(" D file2"), - Contains(" A file3"), - ). - PressPrimaryAction(). - PressEscape() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Commits(). - IsFocused(). - SelectPreviousItem() - - t.Common().SelectPatchOption(Contains("Move patch to selected commit")) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("destination commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ dir"), - Equals(" M file1"), - Equals(" D file2"), - Equals(" A file3"), - Equals(" A unrelated-file"), - ). - PressEscape() - - t.Views().Commits(). - IsFocused(). - SelectNextItem(). - PressEnter() - - // the original commit has no more files in it - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("(none)"), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_later_commit_partial_hunk.go b/pkg/integration/tests/patch_building/move_to_later_commit_partial_hunk.go deleted file mode 100644 index 974dd4ec60c..00000000000 --- a/pkg/integration/tests/patch_building/move_to_later_commit_partial_hunk.go +++ /dev/null @@ -1,97 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToLaterCommitPartialHunk = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to a later commit, with only parts of a hunk in the patch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "") - shell.Commit("first commit") - - shell.UpdateFileAndAdd("file1", "1st line\n2nd line\n") - shell.Commit("commit to move from") - - shell.UpdateFileAndAdd("unrelated-file", "") - shell.Commit("destination commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("destination commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - PressPrimaryAction(). - PressEscape() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().CommitFiles(). - IsFocused(). - PressEscape() - - t.Views().Commits(). - IsFocused(). - SelectPreviousItem() - - t.Common().SelectPatchOption(Contains("Move patch to selected commit")) - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("destination commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Contains("file1"), - Contains("unrelated-file"), - ). - SelectNextItem(). - Tap(func() { - t.Views().Main(). - Content(Contains("+1st line\n 2nd line")) - }). - PressEscape() - - t.Views().Commits(). - IsFocused(). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - Tap(func() { - t.Views().Main(). - Content(Contains("+2nd line"). - DoesNotContain("1st line")) - }) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_new_commit.go b/pkg/integration/tests/patch_building/move_to_new_commit.go deleted file mode 100644 index 8f1c773765b..00000000000 --- a/pkg/integration/tests/patch_building/move_to_new_commit.go +++ /dev/null @@ -1,95 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToNewCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to a new commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateDir("dir") - shell.CreateFileAndAdd("dir/file1", "file1 content") - shell.CreateFileAndAdd("dir/file2", "file2 content") - shell.Commit("first commit") - - shell.UpdateFileAndAdd("dir/file1", "file1 content with old changes") - shell.DeleteFileAndAdd("dir/file2") - shell.CreateFileAndAdd("dir/file3", "file3 content") - shell.Commit("commit to move from") - - shell.UpdateFileAndAdd("dir/file1", "file1 content with new changes") - shell.Commit("third commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("third commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("dir").IsSelected(), - Contains(" M file1"), - Contains(" D file2"), - Contains(" A file3"), - ). - PressPrimaryAction(). - PressEscape() - - t.Views().Information().Content(Contains("Building patch")) - - t.Common().SelectPatchOption(Contains("Move patch into new commit after the original commit")) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("")). - Type("new commit").Confirm() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("third commit"), - Contains("new commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("dir").IsSelected(), - Contains(" M file1"), - Contains(" D file2"), - Contains(" A file3"), - ). - PressEscape() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("third commit"), - Contains("new commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - SelectNextItem(). - PressEnter() - - // the original commit has no more files in it - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("(none)"), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_new_commit_before.go b/pkg/integration/tests/patch_building/move_to_new_commit_before.go deleted file mode 100644 index 41e59d5b00b..00000000000 --- a/pkg/integration/tests/patch_building/move_to_new_commit_before.go +++ /dev/null @@ -1,90 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToNewCommitBefore = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to a new commit before the original one", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateDir("dir") - shell.CreateFileAndAdd("dir/file1", "file1 content") - shell.CreateFileAndAdd("dir/file2", "file2 content") - shell.Commit("first commit") - - shell.UpdateFileAndAdd("dir/file1", "file1 content with old changes") - shell.DeleteFileAndAdd("dir/file2") - shell.CreateFileAndAdd("dir/file3", "file3 content") - shell.Commit("commit to move from") - - shell.UpdateFileAndAdd("dir/file1", "file1 content with new changes") - shell.Commit("third commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("third commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("dir").IsSelected(), - Contains(" M file1"), - Contains(" D file2"), - Contains(" A file3"), - ). - PressPrimaryAction(). - PressEscape() - - t.Views().Information().Content(Contains("Building patch")) - - t.Common().SelectPatchOption(Contains("Move patch into new commit before the original commit")) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("")). - Type("new commit").Confirm() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("third commit"), - Contains("commit to move from").IsSelected(), - Contains("new commit"), - Contains("first commit"), - ). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("dir").IsSelected(), - Contains(" M file1"), - Contains(" D file2"), - Contains(" A file3"), - ). - PressEscape() - - t.Views().Commits(). - IsFocused(). - SelectPreviousItem(). - PressEnter() - - // the original commit has no more files in it - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("(none)"), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_new_commit_from_added_file.go b/pkg/integration/tests/patch_building/move_to_new_commit_from_added_file.go deleted file mode 100644 index 11abe23f4eb..00000000000 --- a/pkg/integration/tests/patch_building/move_to_new_commit_from_added_file.go +++ /dev/null @@ -1,88 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToNewCommitFromAddedFile = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a file that was added in a commit to a new commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - - shell.CreateFileAndAdd("file1", "1st line\n2nd line\n3rd line\n") - shell.Commit("commit to move from") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit to move from").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Common().SelectPatchOption(Contains("Move patch into new commit after the original commit")) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("")). - Type("new commit").Confirm() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("new commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("M file1").IsSelected(), - ). - Tap(func() { - t.Views().Main().ContainsLines( - Equals(" 1st line"), - Equals("+2nd line"), - Equals(" 3rd line"), - ) - }). - PressEscape() - - t.Views().Commits(). - IsFocused(). - NavigateToLine(Contains("commit to move from")). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("A file1").IsSelected(), - ). - Tap(func() { - t.Views().Main().ContainsLines( - Equals("+1st line"), - Equals("+3rd line"), - ) - }) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_new_commit_from_deleted_file.go b/pkg/integration/tests/patch_building/move_to_new_commit_from_deleted_file.go deleted file mode 100644 index 9edc06fb2dc..00000000000 --- a/pkg/integration/tests/patch_building/move_to_new_commit_from_deleted_file.go +++ /dev/null @@ -1,88 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToNewCommitFromDeletedFile = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a file that was deleted in a commit to a new commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "1st line\n2nd line\n3rd line\n") - shell.Commit("first commit") - shell.DeleteFileAndAdd("file1") - shell.Commit("commit to move from") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit to move from").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("D file1").IsSelected(), - ). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Common().SelectPatchOption(Contains("Move patch into new commit after the original commit")) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("")). - Type("new commit").Confirm() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("new commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("D file1").IsSelected(), - ). - Tap(func() { - t.Views().Main().ContainsLines( - Equals("-2nd line"), - ) - }). - PressEscape() - - t.Views().Commits(). - IsFocused(). - NavigateToLine(Contains("commit to move from")). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - // In the original commit the file is no longer deleted, but modified - Contains("M file1").IsSelected(), - ). - Tap(func() { - t.Views().Main().ContainsLines( - Equals("-1st line"), - Equals(" 2nd line"), - Equals("-3rd line"), - ) - }) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_new_commit_in_last_commit_of_stacked_branch.go b/pkg/integration/tests/patch_building/move_to_new_commit_in_last_commit_of_stacked_branch.go deleted file mode 100644 index c9fd80d0e18..00000000000 --- a/pkg/integration/tests/patch_building/move_to_new_commit_in_last_commit_of_stacked_branch.go +++ /dev/null @@ -1,72 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToNewCommitInLastCommitOfStackedBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to a new commit, in the last commit of a branch in the middle of a stack", - ExtraCmdArgs: []string{}, - Skip: false, - GitVersion: AtLeast("2.38.0"), - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("commit 01"). - NewBranch("branch1"). - EmptyCommit("commit 02"). - CreateFileAndAdd("file1", "file1 content"). - CreateFileAndAdd("file2", "file2 content"). - Commit("commit 03"). - NewBranch("branch2"). - CreateNCommitsStartingAt(2, 4) - - shell.SetConfig("rebase.updateRefs", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("CI commit 05").IsSelected(), - Contains("CI commit 04"), - Contains("CI * commit 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ). - NavigateToLine(Contains("commit 03")). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" A file1"), - Equals(" A file2"), - ). - SelectNextItem(). - PressPrimaryAction(). - PressEscape() - - t.Views().Information().Content(Contains("Building patch")) - - t.Common().SelectPatchOption(Contains("Move patch into new commit after the original commit")) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("")). - Type("new commit").Confirm() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("CI commit 05"), - Contains("CI commit 04"), - Contains("CI * new commit").IsSelected(), - Contains("CI commit 03"), - Contains("CI commit 02"), - Contains("CI commit 01"), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/move_to_new_commit_partial_hunk.go b/pkg/integration/tests/patch_building/move_to_new_commit_partial_hunk.go deleted file mode 100644 index 4d12e0f90aa..00000000000 --- a/pkg/integration/tests/patch_building/move_to_new_commit_partial_hunk.go +++ /dev/null @@ -1,96 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var MoveToNewCommitPartialHunk = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Move a patch from a commit to a new commit, with only parts of a hunk in the patch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "") - shell.Commit("first commit") - - shell.UpdateFileAndAdd("file1", "1st line\n2nd line\n") - shell.Commit("commit to move from") - - shell.UpdateFileAndAdd("file1", "1st line\n2nd line\n3rd line\n") - shell.Commit("third commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("third commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Common().SelectPatchOption(Contains("Move patch into new commit after the original commit")) - - t.ExpectPopup().CommitMessagePanel(). - InitialText(Equals("")). - Type("new commit").Confirm() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("third commit"), - Contains("new commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - Tap(func() { - t.Views().Main(). - Content(Contains("+1st line\n 2nd line")) - }). - PressEscape() - - t.Views().Commits(). - IsFocused(). - Lines( - Contains("third commit"), - Contains("new commit").IsSelected(), - Contains("commit to move from"), - Contains("first commit"), - ). - SelectNextItem(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - Tap(func() { - t.Views().Main(). - Content(Contains("+2nd line"). - DoesNotContain("1st line")) - }) - }, -}) diff --git a/pkg/integration/tests/patch_building/remove_from_commit.go b/pkg/integration/tests/patch_building/remove_from_commit.go deleted file mode 100644 index fbd78fcf76d..00000000000 --- a/pkg/integration/tests/patch_building/remove_from_commit.go +++ /dev/null @@ -1,59 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RemoveFromCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Remove a custom patch from a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "file1 content\n") - shell.CreateFileAndAdd("file2", "file2 content\n") - shell.Commit("first commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("first commit").IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Contains("file1"), - Contains("file2"), - ). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content(Contains("+file1 content")) - - t.Common().SelectPatchOption(Contains("Remove patch from original commit")) - - t.Views().Files().IsEmpty() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file2").IsSelected(), - ). - PressEscape() - - t.Views().Main(). - Content(Contains("+file2 content")) - - t.Views().Commits(). - Lines( - Contains("first commit").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/remove_parts_of_added_file.go b/pkg/integration/tests/patch_building/remove_parts_of_added_file.go deleted file mode 100644 index 9a0b9a95174..00000000000 --- a/pkg/integration/tests/patch_building/remove_parts_of_added_file.go +++ /dev/null @@ -1,56 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RemovePartsOfAddedFile = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Remove a custom patch from a file that was added in a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - - shell.CreateFileAndAdd("file1", "1st line\n2nd line\n3rd line\n") - shell.Commit("commit to remove from") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit to remove from").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("A file1").IsSelected(), - ). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Common().SelectPatchOption(Contains("Remove patch from original commit")) - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("A file1").IsSelected(), - ). - PressEscape() - - t.Views().Main().ContainsLines( - Equals("+1st line"), - Equals("+3rd line"), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/reset_with_escape.go b/pkg/integration/tests/patch_building/reset_with_escape.go deleted file mode 100644 index 7046890d3cf..00000000000 --- a/pkg/integration/tests/patch_building/reset_with_escape.go +++ /dev/null @@ -1,43 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ResetWithEscape = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Reset a custom patch with the escape keybinding", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "file1 content") - shell.Commit("first commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("first commit").IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressPrimaryAction(). - Tap(func() { - t.Views().Information().Content(Contains("Building patch")) - }). - PressEscape() - - // hitting escape at the top level will reset the patch - t.Views().Commits(). - IsFocused(). - PressEscape() - - t.Views().Information().Content(DoesNotContain("Building patch")) - }, -}) diff --git a/pkg/integration/tests/patch_building/select_all_files.go b/pkg/integration/tests/patch_building/select_all_files.go deleted file mode 100644 index b4a2f633561..00000000000 --- a/pkg/integration/tests/patch_building/select_all_files.go +++ /dev/null @@ -1,43 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SelectAllFiles = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Add all files of a commit to a custom patch with the 'a' keybinding", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "file1 content") - shell.CreateFileAndAdd("file2", "file2 content") - shell.CreateFileAndAdd("file3", "file3 content") - shell.Commit("first commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("first commit").IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" A file1"), - Equals(" A file2"), - Equals(" A file3"), - ). - Press(keys.Files.ToggleStagedAll) - - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content( - Contains("file1").Contains("file3").Contains("file3"), - ) - }, -}) diff --git a/pkg/integration/tests/patch_building/specific_selection.go b/pkg/integration/tests/patch_building/specific_selection.go deleted file mode 100644 index 2e140e41c58..00000000000 --- a/pkg/integration/tests/patch_building/specific_selection.go +++ /dev/null @@ -1,159 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SpecificSelection = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Build a custom patch with a specific selection of lines, adding individual lines, as well as a range and hunk, and adding a file directly", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.UseHunkModeInStagingView = false - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("hunk-file", "1a\n1b\n1c\n1d\n1e\n1f\n1g\n1h\n1i\n1j\n1k\n1l\n1m\n1n\n1o\n1p\n1q\n1r\n1s\n1t\n1u\n1v\n1w\n1x\n1y\n1z\n") - shell.Commit("first commit") - - // making changes in two separate places for the sake of having two hunks - shell.UpdateFileAndAdd("hunk-file", "aa\n1b\ncc\n1d\n1e\n1f\n1g\n1h\n1i\n1j\n1k\n1l\n1m\n1n\n1o\n1p\n1q\n1r\n1s\ntt\nuu\nvv\n1w\n1x\n1y\n1z\n") - - shell.CreateFileAndAdd("line-file", "2a\n2b\n2c\n2d\n2e\n2f\n2g\n2h\n2i\n2j\n2k\n2l\n2m\n2n\n2o\n2p\n2q\n2r\n2s\n2t\n2u\n2v\n2w\n2x\n2y\n2z\n") - shell.CreateFileAndAdd("direct-file", "direct file content") - shell.Commit("second commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("second commit").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Contains("direct-file"), - Contains("hunk-file"), - Contains("line-file"), - ). - SelectNextItem(). - PressPrimaryAction(). - Tap(func() { - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content(Contains("direct file content")) - }). - NavigateToLine(Contains("hunk-file")). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - SelectedLines( - Contains("-1a"), - ). - Press(keys.Main.ToggleSelectHunk). - SelectedLines( - Contains(`-1a`), - Contains(`+aa`), - ). - PressPrimaryAction(). - SelectedLines( - Contains(`-1c`), - Contains(`+cc`), - ). - Tap(func() { - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content( - // when we're inside the patch building panel, we only show the patch - // in the secondary panel that relates to the selected file - DoesNotContain("direct file content"). - Contains("@@ -1,6 +1,6 @@"). - Contains(" 1f"), - ) - }). - // Cancel hunk select - PressEscape(). - // Escape the view - PressEscape() - - t.Views().CommitFiles(). - IsFocused(). - NavigateToLine(Contains("line-file")). - PressEnter() - - t.Views().PatchBuilding(). - IsFocused(). - SelectedLines( - Contains("+2a"), - ). - PressPrimaryAction(). - SelectedLines( - Contains("+2b"), - ). - NavigateToLine(Contains("+2c")). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("+2e")). - PressPrimaryAction(). - SelectedLines( - Contains("+2f"), - ). - NavigateToLine(Contains("+2g")). - PressPrimaryAction(). - SelectedLines( - Contains("+2h"), - ). - Tap(func() { - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().ContainsLines( - Contains("+2a"), - Contains("+2c"), - Contains("+2d"), - Contains("+2e"), - Contains("+2g"), - ) - }). - PressEscape(). - Tap(func() { - t.Views().Secondary().ContainsLines( - // direct-file patch - Contains(`diff --git a/direct-file b/direct-file`), - Contains(`index`), - Contains(`--- a/direct-file`), - Contains(`+++ b/direct-file`), - Contains(`@@ -0,0 +1 @@`), - Contains(`+direct file content`), - Contains(`\ No newline at end of file`), - // hunk-file patch - Contains(`diff --git a/hunk-file b/hunk-file`), - Contains(`index`), - Contains(`--- a/hunk-file`), - Contains(`+++ b/hunk-file`), - Contains(`@@ -1,6 +1,6 @@`), - Contains(`-1a`), - Contains(`+aa`), - Contains(` 1b`), - Contains(` 1c`), - Contains(` 1d`), - Contains(` 1e`), - Contains(` 1f`), - // line-file patch - Contains(`diff --git a/line-file b/line-file`), - Contains(`index`), - Contains(`--- a/line-file`), - Contains(`+++ b/line-file`), - Contains(`@@ -0,0 +1,5 @@`), - Contains(`+2a`), - Contains(`+2c`), - Contains(`+2d`), - Contains(`+2e`), - Contains(`+2g`), - ) - }) - }, -}) diff --git a/pkg/integration/tests/patch_building/start_new_patch.go b/pkg/integration/tests/patch_building/start_new_patch.go deleted file mode 100644 index 88402a953e3..00000000000 --- a/pkg/integration/tests/patch_building/start_new_patch.go +++ /dev/null @@ -1,62 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StartNewPatch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Attempt to add a file from another commit to a patch, then agree to start a new patch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "file1 content") - shell.Commit("first commit") - - shell.CreateFileAndAdd("file2", "file2 content") - shell.Commit("second commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("second commit").IsSelected(), - Contains("first commit"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file2").IsSelected(), - ). - PressPrimaryAction(). - Tap(func() { - t.Views().Information().Content(Contains("Building patch")) - - t.Views().Secondary().Content(Contains("file2")) - }). - PressEscape() - - t.Views().Commits(). - IsFocused(). - NavigateToLine(Contains("first commit")). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressPrimaryAction(). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Contains("Discard patch")). - Content(Contains("You can only build a patch from one commit/stash-entry at a time. Discard current patch?")). - Confirm() - - t.Views().Secondary().Content(Contains("file1").DoesNotContain("file2")) - }) - }, -}) diff --git a/pkg/integration/tests/patch_building/toggle_range.go b/pkg/integration/tests/patch_building/toggle_range.go deleted file mode 100644 index c6fbd1f49fc..00000000000 --- a/pkg/integration/tests/patch_building/toggle_range.go +++ /dev/null @@ -1,112 +0,0 @@ -package patch_building - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ToggleRange = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Check multi select toggle logic", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateDir("dir1") - shell.CreateFileAndAdd("dir1/file1-a", "d2f1 first line\nsecond line\nthird line\n") - shell.CreateFileAndAdd("dir1/file2-a", "d1f2 first line\n") - shell.CreateFileAndAdd("dir1/file3-a", "d1f3 first line\n") - - shell.CreateDir("dir2") - shell.CreateFileAndAdd("dir2/file1-b", "d2f1 first line\nsecond line\nthird line\n") - shell.CreateFileAndAdd("dir2/file2-b", "d2f2 first line\n") - shell.CreateFileAndAdd("dir2/file3-b", "d2f3 first line\nsecond line\n") - - shell.Commit("first commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("first commit").IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ dir1"), - Equals(" A file1-a"), - Equals(" A file2-a"), - Equals(" A file3-a"), - Equals(" ▼ dir2"), - Equals(" A file1-b"), - Equals(" A file2-b"), - Equals(" A file3-b"), - ). - NavigateToLine(Contains("file1-a")). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("file3-a")). - PressPrimaryAction(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ● file1-a").IsSelected(), - Equals(" ● file2-a").IsSelected(), - Equals(" ● file3-a").IsSelected(), - Equals(" ▼ dir2"), - Equals(" A file1-b"), - Equals(" A file2-b"), - Equals(" A file3-b"), - ). - PressEscape(). - NavigateToLine(Contains("file3-b")). - PressEnter() - - t.Views().Main().IsFocused(). - NavigateToLine(Contains("second line")). - PressPrimaryAction(). - PressEscape() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1"), - Equals(" ● file1-a"), - Equals(" ● file2-a"), - Equals(" ● file3-a"), - Equals(" ▼ dir2"), - Equals(" A file1-b"), - Equals(" A file2-b"), - Equals(" ◐ file3-b").IsSelected(), - ). - NavigateToLine(Contains("dir1")). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("dir2")). - PressPrimaryAction(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1").IsSelected(), - Equals(" ● file1-a").IsSelected(), - Equals(" ● file2-a").IsSelected(), - Equals(" ● file3-a").IsSelected(), - Equals(" ▼ dir2").IsSelected(), - Equals(" ● file1-b"), - Equals(" ● file2-b"), - Equals(" ● file3-b"), - ). - PressPrimaryAction(). - Lines( - Equals("▼ /"), - Equals(" ▼ dir1").IsSelected(), - Equals(" A file1-a").IsSelected(), - Equals(" A file2-a").IsSelected(), - Equals(" A file3-a").IsSelected(), - Equals(" ▼ dir2").IsSelected(), - Equals(" A file1-b"), - Equals(" A file2-b"), - Equals(" A file3-b"), - ) - }, -}) diff --git a/pkg/integration/tests/reflog/checkout.go b/pkg/integration/tests/reflog/checkout.go deleted file mode 100644 index 90ba93a069e..00000000000 --- a/pkg/integration/tests/reflog/checkout.go +++ /dev/null @@ -1,55 +0,0 @@ -package reflog - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Checkout = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Checkout a reflog commit as a detached head", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.EmptyCommit("three") - shell.HardReset("HEAD^^") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().ReflogCommits(). - Focus(). - Lines( - Contains("reset: moving to HEAD^^").IsSelected(), - Contains("commit: three"), - Contains("commit: two"), - Contains("commit (initial): one"), - ). - SelectNextItem(). - PressPrimaryAction(). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Contains("Checkout branch or commit")). - Select(MatchesRegexp("Checkout commit [a-f0-9]+ as detached head")). - Confirm() - }). - TopLines( - Contains("checkout: moving from master to").IsSelected(), - Contains("reset: moving to HEAD^^"), - ) - - t.Views().Branches(). - Lines( - Contains("(HEAD detached at").IsSelected(), - Contains("master"), - ) - - t.Views().Commits(). - Focus(). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/reflog/cherry_pick.go b/pkg/integration/tests/reflog/cherry_pick.go deleted file mode 100644 index 1e223c9e059..00000000000 --- a/pkg/integration/tests/reflog/cherry_pick.go +++ /dev/null @@ -1,50 +0,0 @@ -package reflog - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cherry pick a reflog commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.EmptyCommit("three") - shell.HardReset("HEAD^^") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().ReflogCommits(). - Focus(). - Lines( - Contains("reset: moving to HEAD^^").IsSelected(), - Contains("commit: three"), - Contains("commit: two"), - Contains("commit (initial): one"), - ). - SelectNextItem(). - Press(keys.Commits.CherryPickCopy) - - t.Views().Information().Content(Contains("1 commit copied")) - - t.Views().Commits(). - Focus(). - Lines( - Contains("one").IsSelected(), - ). - Press(keys.Commits.PasteCommits). - Tap(func() { - t.ExpectPopup().Alert(). - Title(Equals("Cherry-pick")). - Content(Contains("Are you sure you want to cherry-pick the 1 copied commit(s) onto this branch?")). - Confirm() - }). - Lines( - Contains("three"), - Contains("one").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/reflog/do_not_show_branch_markers_in_reflog_subcommits.go b/pkg/integration/tests/reflog/do_not_show_branch_markers_in_reflog_subcommits.go deleted file mode 100644 index b8a0ea4dfee..00000000000 --- a/pkg/integration/tests/reflog/do_not_show_branch_markers_in_reflog_subcommits.go +++ /dev/null @@ -1,73 +0,0 @@ -package reflog - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DoNotShowBranchMarkersInReflogSubcommits = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that no branch heads are shown in the subcommits view of a reflog entry", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.Log.ShowGraph = "never" - }, - SetupRepo: func(shell *Shell) { - shell.NewBranch("branch1") - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.NewBranch("branch2") - shell.EmptyCommit("three") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // Check that the local commits view does show a branch marker for branch1 - t.Views().Commits(). - Lines( - Contains("CI three"), - Contains("CI * two"), - Contains("CI one"), - ) - - t.Views().Branches(). - Focus(). - // Check out branch1 - NavigateToLine(Contains("branch1")). - PressPrimaryAction(). - // Look at the subcommits of branch2 - NavigateToLine(Contains("branch2")). - PressEnter(). - // Check that we see a marker for branch1 here (but not for - // branch2), even though branch1 is checked out - Tap(func() { - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("CI three"), - Contains("CI * two"), - Contains("CI one"), - ). - PressEscape() - }). - // Check out branch2 again - NavigateToLine(Contains("branch2")). - PressPrimaryAction() - - t.Views().ReflogCommits(). - Focus(). - TopLines( - Contains("checkout: moving from branch1 to branch2").IsSelected(), - ). - PressEnter(). - // Check that the subcommits view for a reflog entry doesn't show - // any branch markers - Tap(func() { - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("CI three"), - Contains("CI two"), - Contains("CI one"), - ) - }) - }, -}) diff --git a/pkg/integration/tests/reflog/patch.go b/pkg/integration/tests/reflog/patch.go deleted file mode 100644 index 3db994ba4b2..00000000000 --- a/pkg/integration/tests/reflog/patch.go +++ /dev/null @@ -1,72 +0,0 @@ -package reflog - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Patch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Build a patch from a reflog commit and apply it", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.CreateFileAndAdd("file1", "content1") - shell.CreateFileAndAdd("file2", "content2") - shell.Commit("three") - shell.HardReset("HEAD^^") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().ReflogCommits(). - Focus(). - Lines( - Contains("reset: moving to HEAD^^").IsSelected(), - Contains("commit: three"), - Contains("commit: two"), - Contains("commit (initial): one"), - ). - SelectNextItem(). - Lines( - Contains("reset: moving to HEAD^^"), - Contains("commit: three").IsSelected(), - Contains("commit: two"), - Contains("commit (initial): one"), - ). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Contains("file1"), - Contains("file2"), - ). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views(). - CommitFiles(). - Press(keys.Universal.CreatePatchOptionsMenu) - - t.ExpectPopup().Menu(). - Title(Equals("Patch options")). - Select(MatchesRegexp(`Apply patch$`)).Confirm() - - t.Views().Files().Lines( - Contains("file1"), - ) - }, -}) diff --git a/pkg/integration/tests/reflog/reset.go b/pkg/integration/tests/reflog/reset.go deleted file mode 100644 index 320daabf35a..00000000000 --- a/pkg/integration/tests/reflog/reset.go +++ /dev/null @@ -1,49 +0,0 @@ -package reflog - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Reset = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Hard reset to a reflog commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.EmptyCommit("three") - shell.HardReset("HEAD^^") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().ReflogCommits(). - Focus(). - Lines( - Contains("reset: moving to HEAD^^").IsSelected(), - Contains("commit: three"), - Contains("commit: two"), - Contains("commit (initial): one"), - ). - SelectNextItem(). - Press(keys.Commits.ViewResetOptions). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Contains("Reset to")). - Select(Contains("Hard reset")). - Confirm() - }). - TopLines( - Contains("reset: moving to").IsSelected(), - Contains("reset: moving to HEAD^^"), - ) - - t.Views().Commits(). - Focus(). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/shared/README.md b/pkg/integration/tests/shared/README.md deleted file mode 100644 index 2dc27a4287a..00000000000 --- a/pkg/integration/tests/shared/README.md +++ /dev/null @@ -1 +0,0 @@ -This package contains shared helper functions for tests. It is not intended to contain any actual tests itself. diff --git a/pkg/integration/tests/shared/conflicts.go b/pkg/integration/tests/shared/conflicts.go deleted file mode 100644 index b84c8c7add0..00000000000 --- a/pkg/integration/tests/shared/conflicts.go +++ /dev/null @@ -1,176 +0,0 @@ -package shared - -import ( - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var OriginalFileContent = ` -This -Is -The -Original -File -` - -var FirstChangeFileContent = ` -This -Is -The -First Change -File -` - -var SecondChangeFileContent = ` -This -Is -The -Second Change -File -` - -// prepares us for a rebase/merge that has conflicts -var MergeConflictsSetup = func(shell *Shell) { - shell. - NewBranch("original-branch"). - EmptyCommit("one"). - EmptyCommit("two"). - EmptyCommit("three"). - CreateFileAndAdd("file", OriginalFileContent). - Commit("original"). - NewBranch("first-change-branch"). - UpdateFileAndAdd("file", FirstChangeFileContent). - Commit("first change"). - Checkout("original-branch"). - NewBranch("second-change-branch"). - UpdateFileAndAdd("file", SecondChangeFileContent). - Commit("second change"). - EmptyCommit("second-change-branch unrelated change"). - Checkout("first-change-branch") -} - -var CreateMergeConflictFile = func(shell *Shell) { - MergeConflictsSetup(shell) - - shell.RunCommandExpectError([]string{"git", "merge", "--no-edit", "second-change-branch"}) -} - -var CreateMergeCommit = func(shell *Shell) { - CreateMergeConflictFile(shell) - shell.UpdateFileAndAdd("file", SecondChangeFileContent) - shell.ContinueMerge() -} - -// creates a merge conflict where there are two files with conflicts and a separate file without conflicts -var CreateMergeConflictFiles = func(shell *Shell) { - shell. - NewBranch("original-branch"). - EmptyCommit("one"). - EmptyCommit("two"). - EmptyCommit("three"). - CreateFileAndAdd("file1", OriginalFileContent). - CreateFileAndAdd("file2", OriginalFileContent). - Commit("original"). - NewBranch("first-change-branch"). - UpdateFileAndAdd("file1", FirstChangeFileContent). - UpdateFileAndAdd("file2", FirstChangeFileContent). - Commit("first change"). - Checkout("original-branch"). - NewBranch("second-change-branch"). - UpdateFileAndAdd("file1", SecondChangeFileContent). - UpdateFileAndAdd("file2", SecondChangeFileContent). - // this file is not changed in the second branch - CreateFileAndAdd("file3", "content"). - Commit("second change"). - EmptyCommit("second-change-branch unrelated change"). - Checkout("first-change-branch") - - shell.RunCommandExpectError([]string{"git", "merge", "--no-edit", "second-change-branch"}) -} - -// These 'multiple' variants are just like the short ones but with longer file contents and with multiple conflicts within the file. - -var OriginalFileContentMultiple = ` -This -Is -The -Original -File -.. -It -Is -Longer -Than -The -Other -Options -` - -var FirstChangeFileContentMultiple = ` -This -Is -The -First Change -File -.. -It -Is -Longer -Than -The -Other -Other First Change -` - -var SecondChangeFileContentMultiple = ` -This -Is -The -Second Change -File -.. -It -Is -Longer -Than -The -Other -Other Second Change -` - -var CreateMergeConflictFileMultiple = func(shell *Shell) { - shell. - NewBranch("original-branch"). - EmptyCommit("one"). - EmptyCommit("two"). - EmptyCommit("three"). - CreateFileAndAdd("file", OriginalFileContentMultiple). - Commit("original"). - NewBranch("first-change-branch"). - UpdateFileAndAdd("file", FirstChangeFileContentMultiple). - Commit("first change"). - Checkout("original-branch"). - NewBranch("second-change-branch"). - UpdateFileAndAdd("file", SecondChangeFileContentMultiple). - Commit("second change"). - EmptyCommit("second-change-branch unrelated change"). - Checkout("first-change-branch") - - shell.RunCommandExpectError([]string{"git", "merge", "--no-edit", "second-change-branch"}) -} - -var CreateMergeConflictFileForMergeFileTests = func(shell *Shell, originalFileContent string, currentChangeFileContent string, incomingChangeFileContent string) { - shell. - NewBranch("original-branch"). - EmptyCommit("one"). - CreateFileAndAdd("file", originalFileContent). - Commit("original"). - NewBranch("current-change-branch"). - UpdateFileAndAdd("file", currentChangeFileContent). - Commit("first change"). - Checkout("original-branch"). - NewBranch("incoming-change-branch"). - UpdateFileAndAdd("file", incomingChangeFileContent). - Commit("second change"). - Checkout("current-change-branch"). - RunCommandExpectError([]string{"git", "merge", "--no-edit", "incoming-change-branch"}) -} diff --git a/pkg/integration/tests/shell_commands/basic_shell_command.go b/pkg/integration/tests/shell_commands/basic_shell_command.go deleted file mode 100644 index e677b0672a9..00000000000 --- a/pkg/integration/tests/shell_commands/basic_shell_command.go +++ /dev/null @@ -1,35 +0,0 @@ -package shell_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var BasicShellCommand = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Using a custom command provided at runtime to create a new file", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("blah") - }, - SetupConfig: func(cfg *config.AppConfig) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsEmpty(). - IsFocused(). - Press(keys.Universal.ExecuteShellCommand) - - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - Type("touch file.txt"). - Confirm() - - t.GlobalPress(keys.Files.RefreshFiles) - - t.Views().Files(). - IsFocused(). - Lines( - Contains("file.txt"), - ) - }, -}) diff --git a/pkg/integration/tests/shell_commands/complex_shell_command.go b/pkg/integration/tests/shell_commands/complex_shell_command.go deleted file mode 100644 index a45454a4906..00000000000 --- a/pkg/integration/tests/shell_commands/complex_shell_command.go +++ /dev/null @@ -1,35 +0,0 @@ -package shell_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ComplexShellCommand = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Using a custom command provided at runtime to create a new file, via a shell command. We invoke custom commands through a shell already. This test proves that we can run a shell within a shell, which requires complex escaping.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("blah") - }, - SetupConfig: func(cfg *config.AppConfig) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsEmpty(). - IsFocused(). - Press(keys.Universal.ExecuteShellCommand) - - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - Type("sh -c \"touch file.txt\""). - Confirm() - - t.GlobalPress(keys.Files.RefreshFiles) - - t.Views().Files(). - IsFocused(). - Lines( - Contains("file.txt"), - ) - }, -}) diff --git a/pkg/integration/tests/shell_commands/delete_from_history.go b/pkg/integration/tests/shell_commands/delete_from_history.go deleted file mode 100644 index 0c6f734553b..00000000000 --- a/pkg/integration/tests/shell_commands/delete_from_history.go +++ /dev/null @@ -1,41 +0,0 @@ -package shell_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DeleteFromHistory = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Delete an entry from the custom commands history", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) {}, - SetupConfig: func(cfg *config.AppConfig) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - createCustomCommand := func(command string) { - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - Type(command). - Confirm() - } - - createCustomCommand("echo 1") - createCustomCommand("echo 2") - createCustomCommand("echo 3") - - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - SuggestionLines( - Contains("3"), - Contains("2"), - Contains("1"), - ). - DeleteSuggestion(Contains("2")). - SuggestionLines( - Contains("3"), - Contains("1"), - ) - }, -}) diff --git a/pkg/integration/tests/shell_commands/edit_history.go b/pkg/integration/tests/shell_commands/edit_history.go deleted file mode 100644 index 3a2195a34c3..00000000000 --- a/pkg/integration/tests/shell_commands/edit_history.go +++ /dev/null @@ -1,31 +0,0 @@ -package shell_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var EditHistory = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Edit an entry from the custom commands history", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) {}, - SetupConfig: func(cfg *config.AppConfig) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - Type("echo x"). - Confirm() - - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - Type("ec"). - SuggestionLines( - Equals("echo x"), - ). - EditSuggestion(Equals("echo x")). - InitialText(Equals("echo x")) - }, -}) diff --git a/pkg/integration/tests/shell_commands/history.go b/pkg/integration/tests/shell_commands/history.go deleted file mode 100644 index b4b638c2f0c..00000000000 --- a/pkg/integration/tests/shell_commands/history.go +++ /dev/null @@ -1,60 +0,0 @@ -package shell_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var History = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Test that the custom commands history is saved correctly", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) {}, - SetupConfig: func(cfg *config.AppConfig) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - Type("echo 1"). - Confirm() - - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - SuggestionLines(Contains("1")). - Type("echo 2"). - Confirm() - - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - SuggestionLines( - // "echo 2" was typed last, so it should come first - Contains("2"), - Contains("1"), - ). - Type("echo 3"). - Confirm() - - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - SuggestionLines( - Contains("3"), - Contains("2"), - Contains("1"), - ). - Type("echo 1"). - Confirm() - - // Executing a command again should move it to the front: - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - SuggestionLines( - Contains("1"), - Contains("3"), - Contains("2"), - ) - }, -}) diff --git a/pkg/integration/tests/shell_commands/omit_from_history.go b/pkg/integration/tests/shell_commands/omit_from_history.go deleted file mode 100644 index 1452ece4fb8..00000000000 --- a/pkg/integration/tests/shell_commands/omit_from_history.go +++ /dev/null @@ -1,38 +0,0 @@ -package shell_commands - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var OmitFromHistory = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Omitting a runtime custom command from history if it begins with space", - ExtraCmdArgs: []string{}, - Skip: false, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("blah") - }, - SetupConfig: func(cfg *config.AppConfig) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - Type("echo aubergine"). - Confirm() - - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - SuggestionLines(Contains("aubergine")). - SuggestionLines(DoesNotContain("tangerine")). - Type(" echo tangerine"). - Confirm() - - t.GlobalPress(keys.Universal.ExecuteShellCommand) - t.ExpectPopup().Prompt(). - Title(Equals("Shell command:")). - SuggestionLines(Contains("aubergine")). - SuggestionLines(DoesNotContain("tangerine")). - Cancel() - }, -}) diff --git a/pkg/integration/tests/staging/diff_change_screen_mode.go b/pkg/integration/tests/staging/diff_change_screen_mode.go deleted file mode 100644 index b42439cc8c5..00000000000 --- a/pkg/integration/tests/staging/diff_change_screen_mode.go +++ /dev/null @@ -1,47 +0,0 @@ -package staging - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiffChangeScreenMode = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Change the staged changes screen mode", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFile("file", "first line\nsecond line") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Focus(). - PressEnter() - - t.Views().Staging(). - IsFocused(). - PressPrimaryAction(). - Title(Equals("Unstaged changes")). - Content(Contains("+second line").DoesNotContain("+first line")). - PressTab() - - t.Views().StagingSecondary(). - IsFocused(). - Title(Equals("Staged changes")). - Content(Contains("+first line").DoesNotContain("+second line")). - Press(keys.Universal.NextScreenMode). - Tap(func() { - t.Views().AppStatus(). - IsInvisible() - t.Views().Staging(). - IsVisible() - }). - Press(keys.Universal.NextScreenMode). - Tap(func() { - t.Views().AppStatus(). - IsInvisible() - t.Views().Staging(). - IsInvisible() - }) - }, -}) diff --git a/pkg/integration/tests/staging/diff_context_change.go b/pkg/integration/tests/staging/diff_context_change.go deleted file mode 100644 index dae511971a6..00000000000 --- a/pkg/integration/tests/staging/diff_context_change.go +++ /dev/null @@ -1,123 +0,0 @@ -package staging - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiffContextChange = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Change the number of diff context lines while in the staging panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - // need to be working with a few lines so that git perceives it as two separate hunks - shell.CreateFileAndAdd("file1", "1a\n2a\n3a\n4a\n5a\n6a\n7a\n8a\n9a\n10a\n11a\n12a\n13a\n14a\n15a") - shell.Commit("one") - - shell.UpdateFile("file1", "1a\n2a\n3b\n4a\n5a\n6a\n7a\n8a\n9a\n10a\n11a\n12a\n13b\n14a\n15a") - - // hunk looks like: - // diff --git a/file1 b/file1 - // index 3653080..a6388b6 100644 - // --- a/file1 - // +++ b/file1 - // @@ -1,6 +1,6 @@ - // 1a - // 2a - // -3a - // +3b - // 4a - // 5a - // 6a - // @@ -10,6 +10,6 @@ - // 10a - // 11a - // 12a - // -13a - // +13b - // 14a - // 15a - // \ No newline at end of file - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressEnter() - - t.Views().Staging(). - IsFocused(). - SelectedLines( - Contains(`-3a`), - Contains(`+3b`), - ). - Press(keys.Universal.IncreaseContextInDiffView). - Tap(func() { - t.ExpectToast(Equals("Changed diff context size to 4")) - }). - SelectedLines( - Contains(`-3a`), - Contains(`+3b`), - ). - Press(keys.Universal.DecreaseContextInDiffView). - Tap(func() { - t.ExpectToast(Equals("Changed diff context size to 3")) - }). - SelectedLines( - Contains(`-3a`), - Contains(`+3b`), - ). - Press(keys.Universal.DecreaseContextInDiffView). - Tap(func() { - t.ExpectToast(Equals("Changed diff context size to 2")) - }). - SelectedLines( - Contains(`-3a`), - Contains(`+3b`), - ). - Press(keys.Universal.DecreaseContextInDiffView). - Tap(func() { - t.ExpectToast(Equals("Changed diff context size to 1")) - }). - SelectedLines( - Contains(`-3a`), - Contains(`+3b`), - ). - PressPrimaryAction(). - Press(keys.Universal.TogglePanel) - - t.Views().StagingSecondary(). - IsFocused(). - SelectedLines( - Contains(`-3a`), - Contains(`+3b`), - ). - Press(keys.Universal.DecreaseContextInDiffView). - Tap(func() { - t.ExpectToast(Equals("Changed diff context size to 0")) - }). - SelectedLines( - Contains(`-3a`), - Contains(`+3b`), - ). - Press(keys.Universal.IncreaseContextInDiffView). - Tap(func() { - t.ExpectToast(Equals("Changed diff context size to 1")) - }). - SelectedLines( - Contains(`-3a`), - Contains(`+3b`), - ). - Press(keys.Universal.IncreaseContextInDiffView). - Tap(func() { - t.ExpectToast(Equals("Changed diff context size to 2")) - }). - SelectedLines( - Contains(`-3a`), - Contains(`+3b`), - ) - }, -}) diff --git a/pkg/integration/tests/staging/discard_all_changes.go b/pkg/integration/tests/staging/discard_all_changes.go deleted file mode 100644 index 89725da782f..00000000000 --- a/pkg/integration/tests/staging/discard_all_changes.go +++ /dev/null @@ -1,57 +0,0 @@ -package staging - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DiscardAllChanges = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Discard all changes of a file in the staging panel, then assert we land in the staging panel of the next file", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "one\ntwo\n") - shell.CreateFileAndAdd("file2", "1\n2\n") - shell.Commit("one") - - shell.UpdateFile("file1", "one\ntwo\nthree\nfour\n") - shell.UpdateFile("file2", "1\n2\n3\n4\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" M file1"), - Equals(" M file2"), - ). - SelectNextItem(). - PressEnter() - - t.Views().Staging(). - IsFocused(). - Press(keys.Main.ToggleSelectHunk). - SelectedLines(Contains("+three")). - // discard the line - Press(keys.Universal.Remove). - Tap(func() { - t.Common().ConfirmDiscardLines() - }). - SelectedLines(Contains("+four")). - // discard the other line - Press(keys.Universal.Remove). - Tap(func() { - t.Common().ConfirmDiscardLines() - - // because there are no more changes in file1 we switch to file2 - t.Views().Files(). - Lines( - Equals(" M file2"), - ) - }). - // assert we are still in the staging panel, but now looking at the changes of the other file - IsFocused(). - SelectedLines(Contains("+3")) - }, -}) diff --git a/pkg/integration/tests/staging/search.go b/pkg/integration/tests/staging/search.go deleted file mode 100644 index 1f2c957773f..00000000000 --- a/pkg/integration/tests/staging/search.go +++ /dev/null @@ -1,42 +0,0 @@ -package staging - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Search = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Use the search feature in the staging panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFile("file1", "one\ntwo\nthree\nfour\nfive") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressEnter() - - t.Views().Staging(). - IsFocused(). - Press(keys.Universal.StartSearch). - Tap(func() { - t.ExpectSearch(). - Type("four"). - Confirm() - - t.Views().Search().IsVisible().Content(Contains("matches for 'four' (1 of 1)")) - }). - SelectedLine(Contains("+four")). // stage the line - PressPrimaryAction(). - Content(DoesNotContain("+four")). - Tap(func() { - t.Views().StagingSecondary(). - Content(Contains("+four")) - }) - }, -}) diff --git a/pkg/integration/tests/staging/stage_hunks.go b/pkg/integration/tests/staging/stage_hunks.go deleted file mode 100644 index 7afc3a24dcf..00000000000 --- a/pkg/integration/tests/staging/stage_hunks.go +++ /dev/null @@ -1,120 +0,0 @@ -package staging - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StageHunks = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stage and unstage various hunks of a file in the staging panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.UseHunkModeInStagingView = false - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "1a\n2a\n3a\n4a\n5a\n6a\n7a\n8a") - shell.Commit("one") - - shell.UpdateFile("file1", "1a\n2a\n3b\n4a\n5a\n6b\n7a\n8a") - - // hunk looks like: - // diff --git a/file1 b/file1 - // index 3653080..a6388b6 100644 - // --- a/file1 - // +++ b/file1 - // @@ -1,6 +1,6 @@ - // 1a - // 2a - // -3a - // +3b - // 4a - // 5a - // -6a - // +6b - // 7a - // 8a - // \ No newline at end of file - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressEnter() - - t.Views().Staging(). - IsFocused(). - SelectedLines( - Contains("-3a"), - ). - Press(keys.Universal.NextBlock). - SelectedLines( - Contains("-6a"), - ). - Press(keys.Main.ToggleSelectHunk). - SelectedLines( - Contains("-6a"), - Contains("+6b"), - ). - // when in hunk mode, pressing up/down moves us up/down by a hunk - SelectPreviousItem(). - SelectedLines( - Contains(`-3a`), - Contains(`+3b`), - ). - SelectNextItem(). - SelectedLines( - Contains("-6a"), - Contains("+6b"), - ). - // stage the second hunk - PressPrimaryAction(). - ContainsLines( - Contains("-3a"), - Contains("+3b"), - ). - Tap(func() { - t.Views().StagingSecondary(). - ContainsLines( - Contains("-6a"), - Contains("+6b"), - ) - }). - Press(keys.Universal.TogglePanel) - - t.Views().StagingSecondary(). - IsFocused(). - // after toggling panel, we're back to only having selected a single line - SelectedLines( - Contains("-6a"), - ). - PressPrimaryAction(). - SelectedLines( - Contains("+6b"), - ). - PressPrimaryAction(). - IsEmpty() - - t.Views().Staging(). - IsFocused(). - SelectedLines( - Contains("-3a"), - ). - Press(keys.Main.ToggleSelectHunk). - SelectedLines( - Contains(`-3a`), - Contains(`+3b`), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.Common().ConfirmDiscardLines() - }). - Content(DoesNotContain("-3a").DoesNotContain("+3b")). - SelectedLines( - Contains("-6a"), - Contains("+6b"), - ) - }, -}) diff --git a/pkg/integration/tests/staging/stage_lines.go b/pkg/integration/tests/staging/stage_lines.go deleted file mode 100644 index 39ea2d0bf7f..00000000000 --- a/pkg/integration/tests/staging/stage_lines.go +++ /dev/null @@ -1,122 +0,0 @@ -package staging - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StageLines = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stage and unstage various lines of a file in the staging panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.UseHunkModeInStagingView = false - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "one\ntwo\n") - shell.Commit("one") - - shell.UpdateFile("file1", "one\ntwo\nthree\nfour\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressEnter() - - t.Views().Staging(). - IsFocused(). - SelectedLines(Contains("+three")). - // stage 'three' - PressPrimaryAction(). - // 'three' moves over to the staging secondary panel - Content(DoesNotContain("+three")). - Tap(func() { - t.Views().StagingSecondary(). - ContainsLines( - Contains("+three"), - ) - }). - SelectedLines(Contains("+four")). - // stage 'four' - PressPrimaryAction(). - // nothing left in our staging panel - IsEmpty() - - // because we've staged everything we get moved to the staging secondary panel - // do the same thing as above, moving the lines back to the staging panel - t.Views().StagingSecondary(). - IsFocused(). - ContainsLines( - Contains("+three"), - Contains("+four"), - ). - SelectedLines(Contains("+three")). - PressPrimaryAction(). - Content(DoesNotContain("+three")). - Tap(func() { - t.Views().Staging(). - ContainsLines( - Contains("+three"), - ) - }). - SelectedLines(Contains("+four")). - // pressing 'remove' has the same effect as pressing space when in the staging secondary panel - Press(keys.Universal.Remove). - IsEmpty() - - // stage one line and then manually toggle to the staging secondary panel - t.Views().Staging(). - IsFocused(). - ContainsLines( - Contains("+three"), - Contains("+four"), - ). - SelectedLines(Contains("+three")). - PressPrimaryAction(). - Content(DoesNotContain("+three")). - Tap(func() { - t.Views().StagingSecondary(). - Content(Contains("+three")) - }). - Press(keys.Universal.TogglePanel) - - // manually toggle back to the staging panel - t.Views().StagingSecondary(). - IsFocused(). - Press(keys.Universal.TogglePanel) - - t.Views().Staging(). - SelectedLines(Contains("+four")). - // discard the line - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Discard change")). - Content(Contains("Are you sure you want to discard this change")). - Confirm() - }). - IsEmpty() - - t.Views().StagingSecondary(). - IsFocused(). - ContainsLines( - Contains("+three"), - ). - // return to file - PressEscape() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("M file1").IsSelected(), - ). - PressEnter() - - // because we only have a staged change we'll land in the staging secondary panel - t.Views().StagingSecondary(). - IsFocused() - }, -}) diff --git a/pkg/integration/tests/staging/stage_ranges.go b/pkg/integration/tests/staging/stage_ranges.go deleted file mode 100644 index 3d96d0610df..00000000000 --- a/pkg/integration/tests/staging/stage_ranges.go +++ /dev/null @@ -1,108 +0,0 @@ -package staging - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StageRanges = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stage and unstage various ranges of a file in the staging panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.UseHunkModeInStagingView = false - }, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file1", "one\ntwo\n") - shell.Commit("one") - - shell.UpdateFile("file1", "one\ntwo\nthree\nfour\nfive\nsix\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Lines( - Contains("file1").IsSelected(), - ). - PressEnter() - - t.Views().Staging(). - IsFocused(). - SelectedLines( - Contains("+three"), - ). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("+five")). - SelectedLines( - Contains("+three"), - Contains("+four"), - Contains("+five"), - ). - // stage the three lines we've just selected - PressPrimaryAction(). - SelectedLines( - Contains("+six"), - ). - ContainsLines( - Contains(" five"), - Contains("+six"), - ). - Tap(func() { - t.Views().StagingSecondary(). - ContainsLines( - Contains("+three"), - Contains("+four"), - Contains("+five"), - ) - }). - Press(keys.Universal.TogglePanel) - - t.Views().StagingSecondary(). - IsFocused(). - SelectedLines( - Contains("+three"), - ). - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("+five")). - SelectedLines( - Contains("+three"), - Contains("+four"), - Contains("+five"), - ). - // unstage the three selected lines - PressPrimaryAction(). - // nothing left in our staging secondary panel - IsEmpty(). - Tap(func() { - t.Views().Staging(). - ContainsLines( - Contains("+three"), - Contains("+four"), - Contains("+five"), - Contains("+six"), - ) - }) - - t.Views().Staging(). - IsFocused(). - // coincidentally we land at '+four' here. Maybe we should instead land - // at '+three'? given it's at the start of the hunk? - SelectedLines( - Contains("+four"), - ). - Press(keys.Universal.ToggleRangeSelect). - SelectNextItem(). - SelectedLines( - Contains("+four"), - Contains("+five"), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.Common().ConfirmDiscardLines() - }). - ContainsLines( - Contains("+three"), - Contains("+six"), - ) - }, -}) diff --git a/pkg/integration/tests/stash/apply.go b/pkg/integration/tests/stash/apply.go deleted file mode 100644 index ea4a01ca449..00000000000 --- a/pkg/integration/tests/stash/apply.go +++ /dev/null @@ -1,46 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Apply = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Apply a stash entry", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.NewBranch("stash") - shell.Checkout("master") - shell.CreateFile("file", "content") - shell.GitAddAll() - shell.Stash("stash one") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files().IsEmpty() - - t.Views().Stash(). - Focus(). - Lines( - Contains("stash one").IsSelected(), - ). - PressPrimaryAction(). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Stash apply")). - Content(Contains("Are you sure you want to apply this stash entry?")). - Confirm() - }). - Lines( - Contains("stash one").IsSelected(), - ) - - t.Views().Files(). - IsFocused(). - Lines( - Contains("file"), - ) - }, -}) diff --git a/pkg/integration/tests/stash/apply_patch.go b/pkg/integration/tests/stash/apply_patch.go deleted file mode 100644 index 7f47beca298..00000000000 --- a/pkg/integration/tests/stash/apply_patch.go +++ /dev/null @@ -1,55 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ApplyPatch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Restore part of a stash entry via applying a custom patch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CreateFile("myfile", "content") - shell.CreateFile("myfile2", "content") - shell.GitAddAll() - shell.Stash("stash one") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files().IsEmpty() - - t.Views().Stash(). - Focus(). - Lines( - Contains("stash one").IsSelected(), - ). - PressEnter(). - Tap(func() { - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Contains("myfile"), - Contains("myfile2"), - ). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Information().Content(Contains("Building patch")) - - t.Views(). - CommitFiles(). - Press(keys.Universal.CreatePatchOptionsMenu) - - t.ExpectPopup().Menu(). - Title(Equals("Patch options")). - Select(MatchesRegexp(`Apply patch$`)).Confirm() - }) - - t.Views().Files().Lines( - Contains("myfile"), - ) - }, -}) diff --git a/pkg/integration/tests/stash/create_branch.go b/pkg/integration/tests/stash/create_branch.go deleted file mode 100644 index e3ab285133a..00000000000 --- a/pkg/integration/tests/stash/create_branch.go +++ /dev/null @@ -1,57 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CreateBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create a branch from a stash entry", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.NewBranch("stash") - shell.Checkout("master") - shell.CreateFile("myfile", "content") - shell.GitAddAll() - shell.Stash("stash one") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files().IsEmpty() - - t.Views().Stash(). - Focus(). - Lines( - Contains("stash one").IsSelected(), - ). - Press(keys.Universal.New). - Tap(func() { - t.ExpectPopup().Prompt(). - Title(Contains("New branch name (branch is off of 'stash@{0}: On master: stash one'")). - Type("new_branch"). - Confirm() - }) - - t.Views().Files().IsEmpty() - - t.Views().Branches(). - IsFocused(). - Lines( - Contains("new_branch").IsSelected(), - Contains("master"), - Contains("stash"), - ). - PressEnter() - - t.Views().SubCommits(). - Lines( - Contains("On master: stash one").IsSelected(), - MatchesRegexp(`index on master:.*initial commit`), - Contains("initial commit"), - ) - - t.Views().Main().Content(Contains("myfile | 1 +")) - }, -}) diff --git a/pkg/integration/tests/stash/drop.go b/pkg/integration/tests/stash/drop.go deleted file mode 100644 index 4055525de7d..00000000000 --- a/pkg/integration/tests/stash/drop.go +++ /dev/null @@ -1,40 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Drop = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Drop a stash entry", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.NewBranch("stash") - shell.Checkout("master") - shell.CreateFile("file", "content") - shell.GitAddAll() - shell.Stash("stash one") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files().IsEmpty() - - t.Views().Stash(). - Focus(). - Lines( - Contains("stash one").IsSelected(), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Stash drop")). - Content(Contains("Are you sure you want to drop the selected stash entry(ies)?")). - Confirm() - }). - IsEmpty() - - t.Views().Files().IsEmpty() - }, -}) diff --git a/pkg/integration/tests/stash/drop_multiple.go b/pkg/integration/tests/stash/drop_multiple.go deleted file mode 100644 index 4bb40a573fb..00000000000 --- a/pkg/integration/tests/stash/drop_multiple.go +++ /dev/null @@ -1,51 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DropMultiple = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Drop multiple stash entries", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CreateFileAndAdd("file1", "content1") - shell.Stash("stash one") - shell.CreateFileAndAdd("file2", "content2") - shell.Stash("stash two") - shell.CreateFileAndAdd("file3", "content3") - shell.Stash("stash three") - shell.CreateFileAndAdd("file4", "content4") - shell.Stash("stash four") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files().IsEmpty() - - t.Views().Stash(). - Focus(). - SelectNextItem(). - Lines( - Contains("stash four"), - Contains("stash three").IsSelected(), - Contains("stash two"), - Contains("stash one"), - ). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Stash drop")). - Content(Contains("Are you sure you want to drop the selected stash entry(ies)?")). - Confirm() - }). - Lines( - Contains("stash four"), - Contains("stash one"), - ) - - t.Views().Files().IsEmpty() - }, -}) diff --git a/pkg/integration/tests/stash/drop_multiple_in_filtered_mode.go b/pkg/integration/tests/stash/drop_multiple_in_filtered_mode.go deleted file mode 100644 index b4a6e1c5db0..00000000000 --- a/pkg/integration/tests/stash/drop_multiple_in_filtered_mode.go +++ /dev/null @@ -1,71 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DropMultipleInFilteredMode = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Drop multiple stash entries when filtering by path", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CreateFileAndAdd("file1", "content1") - shell.Stash("stash one") - shell.CreateFileAndAdd("file2", "content2a") - shell.Stash("stash two-a") - shell.CreateFileAndAdd("file3", "content3") - shell.Stash("stash three") - shell.CreateFileAndAdd("file2", "content2b") - shell.Stash("stash two-b") - shell.CreateFileAndAdd("file4", "content4") - shell.Stash("stash four") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Stash(). - Lines( - Contains("stash four"), - Contains("stash two-b"), - Contains("stash three"), - Contains("stash two-a"), - Contains("stash one"), - ) - - t.GlobalPress(keys.Universal.FilteringMenu) - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Enter path to filter by")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("Enter path:")). - Type("file2"). - Confirm() - - t.Views().Stash(). - Focus(). - Lines( - Contains("stash two-b").IsSelected(), - Contains("stash two-a"), - ). - Press(keys.Universal.RangeSelectDown). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Stash drop")). - Content(Contains("Are you sure you want to drop the selected stash entry(ies)?")). - Confirm() - }). - IsEmpty() - - t.GlobalPress(keys.Universal.Return) // cancel filtering mode - t.Views().Stash(). - Lines( - Contains("stash four"), - Contains("stash three"), - Contains("stash one"), - ) - }, -}) diff --git a/pkg/integration/tests/stash/filter_by_path.go b/pkg/integration/tests/stash/filter_by_path.go deleted file mode 100644 index ea397d70eb7..00000000000 --- a/pkg/integration/tests/stash/filter_by_path.go +++ /dev/null @@ -1,60 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FilterByPath = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Filter the stash list by path", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CreateFileAndAdd("file1", "content") - shell.Stash("file1") - shell.CreateDir("subdir") - shell.CreateFileAndAdd("subdir/file2", "content") - shell.Stash("subdir/file2") - shell.CreateFileAndAdd("file1", "other content") - shell.Stash("file1 again") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - filterBy := func(path string) { - t.GlobalPress(keys.Universal.FilteringMenu) - t.ExpectPopup().Menu(). - Title(Equals("Filtering")). - Select(Contains("Enter path to filter by")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("Enter path:")). - Type(path). - Confirm() - } - - t.Views().Stash(). - Lines( - Contains("file1 again"), - Contains("subdir/file2"), - Contains("file1"), - ) - - filterBy("file1") - - t.Views().Stash(). - Lines( - Contains("file1 again"), - Contains("file1"), - ) - - t.GlobalPress(keys.Universal.Return) - filterBy("subdir") - - t.Views().Stash(). - Lines( - Contains("subdir/file2"), - ) - }, -}) diff --git a/pkg/integration/tests/stash/pop.go b/pkg/integration/tests/stash/pop.go deleted file mode 100644 index 5768a12526c..00000000000 --- a/pkg/integration/tests/stash/pop.go +++ /dev/null @@ -1,44 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Pop = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Pop a stash entry", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.NewBranch("stash") - shell.Checkout("master") - shell.CreateFile("file", "content") - shell.GitAddAll() - shell.Stash("stash one") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files().IsEmpty() - - t.Views().Stash(). - Focus(). - Lines( - Contains("stash one").IsSelected(), - ). - Press(keys.Stash.PopStash). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Stash pop")). - Content(Contains("Are you sure you want to pop this stash entry?")). - Confirm() - }). - IsEmpty() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("file"), - ) - }, -}) diff --git a/pkg/integration/tests/stash/prevent_discarding_file_changes.go b/pkg/integration/tests/stash/prevent_discarding_file_changes.go deleted file mode 100644 index 9ee16b75c9d..00000000000 --- a/pkg/integration/tests/stash/prevent_discarding_file_changes.go +++ /dev/null @@ -1,41 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PreventDiscardingFileChanges = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Check that it is not allowed to discard changes to a file of a stash", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CreateFile("file", "content") - shell.GitAddAll() - shell.Stash("stash one") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files().IsEmpty() - - t.Views().Stash(). - Focus(). - Lines( - Contains("stash one").IsSelected(), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file").IsSelected(), - ). - Press(keys.Universal.Remove) - - t.ExpectPopup().Confirmation(). - Title(Equals("Error")). - Content(Contains("Changes can only be discarded from local commits")). - Confirm() - }, -}) diff --git a/pkg/integration/tests/stash/rename.go b/pkg/integration/tests/stash/rename.go deleted file mode 100644 index 1f3fe2d1357..00000000000 --- a/pkg/integration/tests/stash/rename.go +++ /dev/null @@ -1,39 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Rename = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Try to rename the stash.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommit("blah"). - NewBranch("stash"). - Checkout("master"). - CreateFileAndAdd("file-1", "change to stash1"). - Stash("foo"). - CreateFileAndAdd("file-2", "change to stash2"). - Stash("bar") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Stash(). - Focus(). - Lines( - Contains("On master: bar"), - Contains("On master: foo"), - ). - SelectNextItem(). - Press(keys.Stash.RenameStash). - Tap(func() { - t.ExpectPopup().Prompt().Title(Equals("Rename stash: stash@{1}")).Type(" baz").Confirm() - }). - SelectedLine(Contains("On master: foo baz")) - - t.Views().Main().Content(Contains("file-1")) - }, -}) diff --git a/pkg/integration/tests/stash/show_with_branch_named_stash.go b/pkg/integration/tests/stash/show_with_branch_named_stash.go deleted file mode 100644 index a4b9cf2b20b..00000000000 --- a/pkg/integration/tests/stash/show_with_branch_named_stash.go +++ /dev/null @@ -1,43 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ShowWithBranchNamedStash = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "View stash when there is a branch also named 'stash'", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CreateFile("file", "content") - shell.GitAddAll() - - shell.NewBranch("stash") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Stash(). - IsEmpty() - - t.Views().Files(). - Lines( - Contains("file"), - ). - Press(keys.Files.StashAllChanges) - - t.ExpectPopup().Prompt().Title(Equals("Stash changes")).Type("my stashed file").Confirm() - - t.Views().Stash(). - Lines( - MatchesRegexp(`\ds .* my stashed file`), - ) - - t.Views().Files(). - IsEmpty() - - t.Views().Stash().Focus() - t.Views().Main().ContainsLines(Equals(" file | 1 +")) - }, -}) diff --git a/pkg/integration/tests/stash/stash.go b/pkg/integration/tests/stash/stash.go deleted file mode 100644 index a18c67ec29e..00000000000 --- a/pkg/integration/tests/stash/stash.go +++ /dev/null @@ -1,40 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Stash = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stashing files directly (not going through the stash menu)", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.NewBranch("stash") - shell.Checkout("master") - shell.CreateFile("file", "content") - shell.GitAddAll() - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Stash(). - IsEmpty() - - t.Views().Files(). - Lines( - Contains("file"), - ). - Press(keys.Files.StashAllChanges) - - t.ExpectPopup().Prompt().Title(Equals("Stash changes")).Type("my stashed file").Confirm() - - t.Views().Stash(). - Lines( - MatchesRegexp(`\ds .* my stashed file`), - ) - - t.Views().Files(). - IsEmpty() - }, -}) diff --git a/pkg/integration/tests/stash/stash_all.go b/pkg/integration/tests/stash/stash_all.go deleted file mode 100644 index 78ce15e243f..00000000000 --- a/pkg/integration/tests/stash/stash_all.go +++ /dev/null @@ -1,40 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StashAll = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stashing all changes (via the menu)", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CreateFile("file", "content") - shell.GitAddAll() - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Stash(). - IsEmpty() - - t.Views().Files(). - Lines( - Contains("file"), - ). - Press(keys.Files.ViewStashOptions) - - t.ExpectPopup().Menu().Title(Equals("Stash options")).Select(MatchesRegexp("Stash all changes$")).Confirm() - - t.ExpectPopup().Prompt().Title(Equals("Stash changes")).Type("my stashed file").Confirm() - - t.Views().Stash(). - Lines( - Contains("my stashed file"), - ) - - t.Views().Files(). - IsEmpty() - }, -}) diff --git a/pkg/integration/tests/stash/stash_and_keep_index.go b/pkg/integration/tests/stash/stash_and_keep_index.go deleted file mode 100644 index 9ba036164b7..00000000000 --- a/pkg/integration/tests/stash/stash_and_keep_index.go +++ /dev/null @@ -1,58 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StashAndKeepIndex = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stash staged changes", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file-staged", "content") - shell.CreateFileAndAdd("file-unstaged", "content") - shell.EmptyCommit("initial commit") - shell.UpdateFileAndAdd("file-staged", "new content") - shell.UpdateFile("file-unstaged", "new content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Stash(). - IsEmpty() - - t.Views().Files(). - Lines( - Equals("▼ /"), - Equals(" M file-staged"), - Equals(" M file-unstaged"), - ). - Press(keys.Files.ViewStashOptions) - - t.ExpectPopup().Menu().Title(Equals("Stash options")).Select(Contains("Stash all changes and keep index")).Confirm() - - t.ExpectPopup().Prompt().Title(Equals("Stash changes")).Type("my stashed file").Confirm() - - t.Views().Stash(). - Lines( - Contains("my stashed file"), - ) - - t.Views().Files(). - Lines( - Equals("M file-staged"), - ) - - t.Views().Stash(). - Focus(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Equals("▼ /"), - Equals(" M file-staged"), - Equals(" M file-unstaged"), - ) - }, -}) diff --git a/pkg/integration/tests/stash/stash_including_untracked_files.go b/pkg/integration/tests/stash/stash_including_untracked_files.go deleted file mode 100644 index 91400d8a269..00000000000 --- a/pkg/integration/tests/stash/stash_including_untracked_files.go +++ /dev/null @@ -1,43 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StashIncludingUntrackedFiles = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stashing all files including untracked ones", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CreateFile("file_1", "content") - shell.CreateFile("file_2", "content") - shell.GitAdd("file_1") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Stash(). - IsEmpty() - - t.Views().Files(). - Lines( - Equals("▼ /"), - Equals(" A file_1"), - Equals(" ?? file_2"), - ). - Press(keys.Files.ViewStashOptions) - - t.ExpectPopup().Menu().Title(Equals("Stash options")).Select(Contains("Stash all changes including untracked files")).Confirm() - - t.ExpectPopup().Prompt().Title(Equals("Stash changes")).Type("my stashed file").Confirm() - - t.Views().Stash(). - Lines( - Contains("my stashed file"), - ) - - t.Views().Files(). - IsEmpty() - }, -}) diff --git a/pkg/integration/tests/stash/stash_staged.go b/pkg/integration/tests/stash/stash_staged.go deleted file mode 100644 index 38d96a35429..00000000000 --- a/pkg/integration/tests/stash/stash_staged.go +++ /dev/null @@ -1,56 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StashStaged = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stash staged changes", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file-staged", "content") - shell.CreateFileAndAdd("file-unstaged", "content") - shell.EmptyCommit("initial commit") - shell.UpdateFileAndAdd("file-staged", "new content") - shell.UpdateFile("file-unstaged", "new content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Stash(). - IsEmpty() - - t.Views().Files(). - Lines( - Equals("▼ /"), - Equals(" M file-staged"), - Equals(" M file-unstaged"), - ). - Press(keys.Files.ViewStashOptions) - - t.ExpectPopup().Menu().Title(Equals("Stash options")).Select(MatchesRegexp("Stash staged changes$")).Confirm() - - t.ExpectPopup().Prompt().Title(Equals("Stash changes")).Type("my stashed file").Confirm() - - t.Views().Stash(). - Lines( - Contains("my stashed file"), - ) - - t.Views().Files(). - Lines( - Equals(" M file-unstaged"), - ) - - t.Views().Stash(). - Focus(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file-staged").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/stash/stash_staged_partial_file.go b/pkg/integration/tests/stash/stash_staged_partial_file.go deleted file mode 100644 index f77c6e16c39..00000000000 --- a/pkg/integration/tests/stash/stash_staged_partial_file.go +++ /dev/null @@ -1,70 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StashStagedPartialFile = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stash staged changes when a file is partially staged", - ExtraCmdArgs: []string{}, - GitVersion: AtLeast("git version 2.35.0"), - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file-staged", "line1\nline2\nline3\nline4\n") - shell.Commit("initial commit") - shell.UpdateFile("file-staged", "line1\nline2 mod\nline3\nline4 mod\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - PressEnter() - - t.Views().Staging(). - Content( - Contains(" line1\n-line2\n+line2 mod\n line3\n-line4\n+line4 mod"), - ). - PressPrimaryAction(). - Content( - Contains(" line1\n line2 mod\n line3\n-line4\n+line4 mod"), - ). - PressEscape() - - t.Views().Files(). - IsFocused(). - Press(keys.Files.ViewStashOptions) - - t.ExpectPopup().Menu().Title(Equals("Stash options")).Select(MatchesRegexp("Stash staged changes$")).Confirm() - - t.ExpectPopup().Prompt().Title(Equals("Stash changes")).Type("my stashed file").Confirm() - - t.Views().Stash(). - Focus(). - Lines( - Contains("my stashed file"), - ). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file-staged").IsSelected(), - ) - t.Views().Main(). - Content( - Contains(" line1\n-line2\n+line2 mod\n line3\n line4"), - ) - - t.Views().Files(). - Focus(). - Lines( - Contains("file-staged"), - ) - - t.Views().Main(). - Content( - Contains(" line1\n line2\n line3\n-line4\n+line4 mod"), - ) - }, -}) diff --git a/pkg/integration/tests/stash/stash_unstaged.go b/pkg/integration/tests/stash/stash_unstaged.go deleted file mode 100644 index 0604b1ccea8..00000000000 --- a/pkg/integration/tests/stash/stash_unstaged.go +++ /dev/null @@ -1,56 +0,0 @@ -package stash - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var StashUnstaged = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Stash unstaged changes", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file-staged", "content") - shell.CreateFileAndAdd("file-unstaged", "content") - shell.EmptyCommit("initial commit") - shell.UpdateFileAndAdd("file-staged", "new content") - shell.UpdateFile("file-unstaged", "new content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Stash(). - IsEmpty() - - t.Views().Files(). - Lines( - Equals("▼ /"), - Equals(" M file-staged"), - Equals(" M file-unstaged"), - ). - Press(keys.Files.ViewStashOptions) - - t.ExpectPopup().Menu().Title(Equals("Stash options")).Select(MatchesRegexp("Stash unstaged changes$")).Confirm() - - t.ExpectPopup().Prompt().Title(Equals("Stash changes")).Type("my stashed file").Confirm() - - t.Views().Stash(). - Lines( - Contains("my stashed file"), - ) - - t.Views().Files(). - Lines( - Contains("file-staged"), - ) - - t.Views().Stash(). - Focus(). - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - Lines( - Contains("file-unstaged").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/status/click_repo_name_to_open_repos_menu.go b/pkg/integration/tests/status/click_repo_name_to_open_repos_menu.go deleted file mode 100644 index 5e1eab092e8..00000000000 --- a/pkg/integration/tests/status/click_repo_name_to_open_repos_menu.go +++ /dev/null @@ -1,18 +0,0 @@ -package status - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ClickRepoNameToOpenReposMenu = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Click on the repo name in the status side panel to open the recent repositories menu", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Status().Click(1, 0) - t.ExpectPopup().Menu().Title(Equals("Recent repositories")) - }, -}) diff --git a/pkg/integration/tests/status/click_to_focus.go b/pkg/integration/tests/status/click_to_focus.go deleted file mode 100644 index 3f20733a19e..00000000000 --- a/pkg/integration/tests/status/click_to_focus.go +++ /dev/null @@ -1,35 +0,0 @@ -package status - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ClickToFocus = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Click in the status side panel to activate it", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files().Focus() - t.Views().Main().Lines( - Contains("No changed files"), - ) - - t.Views().Status().Click(0, 0) - t.Views().Status().IsFocused() - t.Views().Main().ContainsLines( - Contains(` _`), - Contains(` | | (_) |`), - Contains(` | | __ _ _____ _ __ _ _| |_`), - Contains(" | |/ _` |_ / | | |/ _` | | __|"), - Contains(` | | (_| |/ /| |_| | (_| | | |_`), - Contains(` |_|\__,_/___|\__, |\__, |_|\__|`), - Contains(` __/ | __/ |`), - Contains(` |___/ |___/`), - Contains(``), - Contains(`Copyright `), - ) - }, -}) diff --git a/pkg/integration/tests/status/click_working_tree_state_to_open_rebase_options_menu.go b/pkg/integration/tests/status/click_working_tree_state_to_open_rebase_options_menu.go deleted file mode 100644 index 42f221c5483..00000000000 --- a/pkg/integration/tests/status/click_working_tree_state_to_open_rebase_options_menu.go +++ /dev/null @@ -1,27 +0,0 @@ -package status - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ClickWorkingTreeStateToOpenRebaseOptionsMenu = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Click on the working tree state in the status side panel to open the rebase options menu", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(2) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Press(keys.Universal.Edit) - - t.Views().Status(). - Content(Contains("(rebasing) repo")). - Click(1, 0) - - t.ExpectPopup().Menu().Title(Equals("Rebase options")) - }, -}) diff --git a/pkg/integration/tests/status/log_cmd.go b/pkg/integration/tests/status/log_cmd.go deleted file mode 100644 index 03dd1a405d7..00000000000 --- a/pkg/integration/tests/status/log_cmd.go +++ /dev/null @@ -1,32 +0,0 @@ -package status - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var LogCmd = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cycle between two different log commands in the Status view", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.AllBranchesLogCmds = []string{`echo "view1"`, `echo "view2"`} - }, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Status(). - Focus(). - Press(keys.Status.AllBranchesLogGraph) - t.Views().Main().Content(Contains("view1")) - - t.Views().Status(). - Focus(). - Press(keys.Status.AllBranchesLogGraph) - t.Views().Main().Content(Contains("view2").DoesNotContain("view1")) - - t.Views().Status(). - Focus(). - Press(keys.Status.AllBranchesLogGraph) - t.Views().Main().Content(Contains("view1").DoesNotContain("view2")) - }, -}) diff --git a/pkg/integration/tests/status/log_cmd_status_panel_all_branches_log.go b/pkg/integration/tests/status/log_cmd_status_panel_all_branches_log.go deleted file mode 100644 index 82845942643..00000000000 --- a/pkg/integration/tests/status/log_cmd_status_panel_all_branches_log.go +++ /dev/null @@ -1,38 +0,0 @@ -package status - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var LogCmdStatusPanelAllBranchesLog = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Cycle between two different log commands in the Status view when it has status panel AllBranchesLog", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.AllBranchesLogCmds = []string{`echo "view1"`, `echo "view2"`} - config.GetUserConfig().Gui.StatusPanelView = "allBranchesLog" - }, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Status(). - Focus() - t.Views().Main().Content(Contains("view1")) - - // We head to the branches view and return - t.Views().Branches(). - Focus() - t.Views().Status(). - Focus() - - t.Views().Main().Content(Contains("view1").DoesNotContain("view2")) - - t.Views().Status(). - Press(keys.Status.AllBranchesLogGraph) - t.Views().Main().Content(Contains("view2").DoesNotContain("view1")) - - t.Views().Status(). - Press(keys.Status.AllBranchesLogGraph) - t.Views().Main().Content(Contains("view1").DoesNotContain("view2")) - }, -}) diff --git a/pkg/integration/tests/submodule/add.go b/pkg/integration/tests/submodule/add.go deleted file mode 100644 index a82a7122786..00000000000 --- a/pkg/integration/tests/submodule/add.go +++ /dev/null @@ -1,67 +0,0 @@ -package submodule - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Add = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Add a submodule", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - shell.Clone("other_repo") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Submodules().Focus(). - Press(keys.Universal.New). - Tap(func() { - t.ExpectPopup().Prompt(). - Title(Equals("New submodule URL:")). - Type("../other_repo").Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New submodule name:")). - InitialText(Equals("other_repo")). - Clear().Type("my_submodule").Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New submodule path:")). - InitialText(Equals("my_submodule")). - Clear().Type("my_submodule_path").Confirm() - }). - Lines( - Contains("my_submodule").IsSelected(), - ) - - t.Views().Main().TopLines( - Contains("Name: my_submodule"), - Contains("Path: my_submodule_path"), - Contains("Url: ../other_repo"), - ) - - t.Views().Files().Focus(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" A .gitmodules"), - Equals(" A my_submodule_path (submodule)"), - ). - SelectNextItem(). - Tap(func() { - t.Views().Main().Content( - Contains("[submodule \"my_submodule\"]"). - Contains("path = my_submodule_path"). - Contains("url = ../other_repo"), - ) - }). - SelectNextItem(). - Tap(func() { - t.Views().Main().Content( - Contains("Submodule my_submodule_path"). - Contains("(new submodule)"), - ) - }) - }, -}) diff --git a/pkg/integration/tests/submodule/enter.go b/pkg/integration/tests/submodule/enter.go deleted file mode 100644 index 588ae204922..00000000000 --- a/pkg/integration/tests/submodule/enter.go +++ /dev/null @@ -1,81 +0,0 @@ -package submodule - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Enter = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Enter a submodule, add a commit, and then stage the change in the parent repo", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "e", - Context: "files", - Command: "git commit --allow-empty -m \"empty commit\"", - }, - } - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - shell.CloneIntoSubmodule("my_submodule_name", "my_submodule_path") - shell.GitAddAll() - shell.Commit("add submodule") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - assertInParentRepo := func() { - t.Views().Status().Content(Contains("repo")) - } - assertInSubmodule := func() { - t.Views().Status().Content(Contains("my_submodule_path(my_submodule_name)")) - } - - assertInParentRepo() - - t.Views().Submodules().Focus(). - Lines( - Contains("my_submodule_name").IsSelected(), - ). - // enter the submodule - PressEnter() - - assertInSubmodule() - - t.Views().Files().IsFocused(). - Press("e"). - Tap(func() { - t.Views().Commits().Content(Contains("empty commit")) - }). - // return to the parent repo - PressEscape() - - assertInParentRepo() - - t.Views().Submodules().IsFocused() - - // we see the new commit in the submodule is ready to be staged in the parent repo - t.Views().Main().Content(Contains("> empty commit")) - - t.Views().Files().Focus(). - Lines( - MatchesRegexp(` M.*my_submodule_path \(submodule\)`).IsSelected(), - ). - Tap(func() { - // main view also shows the new commit when we're looking at the submodule within the files view - t.Views().Main().Content(Contains("> empty commit")) - }). - PressPrimaryAction(). - Press(keys.Files.CommitChanges). - Tap(func() { - t.ExpectPopup().CommitMessagePanel().Type("submodule change").Confirm() - }). - IsEmpty() - - t.Views().Submodules().Focus() - - // we no longer report a new commit because we've committed it in the parent repo - t.Views().Main().Content(DoesNotContain("> empty commit")) - }, -}) diff --git a/pkg/integration/tests/submodule/enter_nested.go b/pkg/integration/tests/submodule/enter_nested.go deleted file mode 100644 index 24cdf526154..00000000000 --- a/pkg/integration/tests/submodule/enter_nested.go +++ /dev/null @@ -1,48 +0,0 @@ -package submodule - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var EnterNested = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Enter a nested submodule", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - setupNestedSubmodules(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Submodules().Focus(). - Lines( - Equals("outerSubName").IsSelected(), - Equals(" - innerSubName"), - ). - Tap(func() { - t.Views().Main().ContainsLines( - Contains("Name: outerSubName"), - Contains("Path: modules/outerSubPath"), - Contains("Url: ../outerSubmodule"), - ) - }). - SelectNextItem(). - Tap(func() { - t.Views().Main().ContainsLines( - Contains("Name: outerSubName/innerSubName"), - Contains("Path: modules/outerSubPath/modules/innerSubPath"), - Contains("Url: ../innerSubmodule"), - ) - }). - // enter the nested submodule - PressEnter() - - t.Views().Status().Content(Contains("innerSubPath(innerSubName)")) - t.Views().Commits().ContainsLines( - Contains("initial inner commit"), - ) - - t.Views().Files().PressEscape() - t.Views().Status().Content(Contains("repo")) - }, -}) diff --git a/pkg/integration/tests/submodule/remove.go b/pkg/integration/tests/submodule/remove.go deleted file mode 100644 index ca9a90b3326..00000000000 --- a/pkg/integration/tests/submodule/remove.go +++ /dev/null @@ -1,52 +0,0 @@ -package submodule - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Remove = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Remove a submodule", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - shell.CloneIntoSubmodule("my_submodule_name", "my_submodule_path") - shell.GitAddAll() - shell.Commit("add submodule") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - gitDirSubmodulePath := ".git/modules/my_submodule_name" - t.FileSystem().PathPresent(gitDirSubmodulePath) - - t.Views().Submodules().Focus(). - Lines( - Contains("my_submodule_name").IsSelected(), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Remove submodule")). - Content(Equals("Are you sure you want to remove submodule 'my_submodule_name' and its corresponding directory? This is irreversible.")). - Confirm() - }). - IsEmpty() - - t.Views().Files().Focus(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" M .gitmodules"), - Equals(" D my_submodule_path"), - ). - SelectNextItem() - - t.Views().Main().Content( - Contains("-[submodule \"my_submodule_name\"]"). - Contains("- path = my_submodule_path"). - Contains("- url = ../my_submodule_name"), - ) - - t.FileSystem().PathNotPresent(gitDirSubmodulePath) - }, -}) diff --git a/pkg/integration/tests/submodule/remove_nested.go b/pkg/integration/tests/submodule/remove_nested.go deleted file mode 100644 index fe05c0fb023..00000000000 --- a/pkg/integration/tests/submodule/remove_nested.go +++ /dev/null @@ -1,57 +0,0 @@ -package submodule - -import ( - "path/filepath" - - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RemoveNested = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Remove a nested submodule", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - setupNestedSubmodules(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - gitDirSubmodulePath, _ := filepath.Abs(".git/modules/outerSubName/modules/innerSubName") - t.FileSystem().PathPresent(gitDirSubmodulePath) - - t.Views().Submodules().Focus(). - Lines( - Equals("outerSubName").IsSelected(), - Equals(" - innerSubName"), - ). - SelectNextItem(). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Remove submodule")). - Content(Equals("Are you sure you want to remove submodule 'outerSubName/innerSubName' and its corresponding directory? This is irreversible.")). - Confirm() - }). - Lines( - Equals("outerSubName").IsSelected(), - ). - Press(keys.Universal.GoInto) - - t.Views().Files().IsFocused(). - Lines( - Equals("▼ /").IsSelected(), - Equals(" ▼ modules"), - Equals(" D innerSubPath"), - Equals(" M .gitmodules"), - ). - NavigateToLine(Contains(".gitmodules")) - - t.Views().Main().Content( - Contains("-[submodule \"innerSubName\"]"). - Contains("- path = modules/innerSubPath"). - Contains("- url = ../innerSubmodule"), - ) - - t.FileSystem().PathNotPresent(gitDirSubmodulePath) - }, -}) diff --git a/pkg/integration/tests/submodule/reset.go b/pkg/integration/tests/submodule/reset.go deleted file mode 100644 index 5cd6d58aa25..00000000000 --- a/pkg/integration/tests/submodule/reset.go +++ /dev/null @@ -1,129 +0,0 @@ -package submodule - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Reset = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Enter a submodule, create a commit and stage some changes, then reset the submodule from back in the parent repo. This test captures functionality around getting a dirty submodule out of your files panel.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "e", - Context: "files", - Command: "git commit --allow-empty -m \"empty commit\" && echo \"my_file content\" > my_file", - }, - } - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - shell.CloneIntoSubmodule("my_submodule_name", "my_submodule_path") - shell.GitAddAll() - shell.Commit("add submodule") - - shell.CreateFile("other_file", "") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - assertInParentRepo := func() { - t.Views().Status().Content(Contains("repo")) - } - assertInSubmodule := func() { - t.Views().Status().Content(Contains("my_submodule_path(my_submodule_name)")) - } - - assertInParentRepo() - - t.Views().Submodules().Focus(). - Lines( - Contains("my_submodule_name").IsSelected(), - ). - // enter the submodule - PressEnter() - - assertInSubmodule() - - t.Views().Files().IsFocused(). - Press("e"). - Tap(func() { - t.Views().Commits().Content(Contains("empty commit")) - t.Views().Files().Content(Contains("my_file")) - }). - Lines( - Contains("my_file").IsSelected(), - ). - // stage my_file - PressPrimaryAction(). - // return to the parent repo - PressEscape() - - assertInParentRepo() - - t.Views().Submodules().IsFocused() - - t.Views().Main().Content(Contains("Submodule my_submodule_path contains modified content")) - - t.Views().Files().Focus(). - Lines( - Equals("▼ /"), - Equals(" M my_submodule_path (submodule)"), - Equals(" ?? other_file").IsSelected(), - ). - // Verify we can't reset a submodule and file change at the same time. - Press(keys.Universal.ToggleRangeSelect). - SelectPreviousItem(). - Lines( - Equals("▼ /"), - Equals(" M my_submodule_path (submodule)").IsSelected(), - Equals(" ?? other_file").IsSelected(), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectToast(Contains("Disabled: Multiselection not supported for submodules")) - }). - Press(keys.Universal.ToggleRangeSelect). - Lines( - Equals("▼ /"), - Equals(" M my_submodule_path (submodule)").IsSelected(), - Equals(" ?? other_file"), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("my_submodule_path")). - Select(Contains("Stash uncommitted submodule changes and update")). - Confirm() - }). - Lines( - Equals("?? other_file").IsSelected(), - ) - - t.Views().Submodules().Focus(). - PressEnter() - - assertInSubmodule() - - // submodule has been hard reset to the commit the parent repo specifies - t.Views().Branches().Lines( - Contains("HEAD detached"), - Contains("master").IsSelected(), - ) - - // empty commit is gone - t.Views().Commits().Lines( - Contains("first commit").IsSelected(), - ) - - // the staged change has been stashed - t.Views().Files().IsEmpty() - - t.Views().Stash().Focus(). - Lines( - Contains("WIP on master").IsSelected(), - ) - - t.Views().Main().Content(Contains("my_file content")) - }, -}) diff --git a/pkg/integration/tests/submodule/reset_folder.go b/pkg/integration/tests/submodule/reset_folder.go deleted file mode 100644 index 682e840c36a..00000000000 --- a/pkg/integration/tests/submodule/reset_folder.go +++ /dev/null @@ -1,126 +0,0 @@ -package submodule - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ResetFolder = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Reset submodule changes located in a nested folder.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - shell.CreateDir("dir") - shell.CloneIntoSubmodule("submodule1", "dir/submodule1") - shell.CloneIntoSubmodule("submodule2", "dir/submodule2") - shell.GitAddAll() - shell.Commit("add submodules") - - shell.CreateFile("dir/submodule1/file", "") - shell.CreateFile("dir/submodule2/file", "") - shell.CreateFile("dir/file", "") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files().Focus(). - Lines( - Equals("▼ dir").IsSelected(), - Equals(" ?? file"), - Equals(" M submodule1 (submodule)"), - Equals(" M submodule2 (submodule)"), - ). - // Verify we cannot reset the entire folder (has nested file and submodule changes). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectToast(Contains("Disabled: Multiselection not supported for submodules")) - }). - // Verify we cannot reset submodule + file or submodule + submodule via range select. - SelectNextItem(). - Press(keys.Universal.ToggleRangeSelect). - SelectNextItem(). - Lines( - Equals("▼ dir"), - Equals(" ?? file").IsSelected(), - Equals(" M submodule1 (submodule)").IsSelected(), - Equals(" M submodule2 (submodule)"), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectToast(Contains("Disabled: Multiselection not supported for submodules")) - }). - Press(keys.Universal.ToggleRangeSelect). - Press(keys.Universal.ToggleRangeSelect). - SelectNextItem(). - Lines( - Equals("▼ dir"), - Equals(" ?? file"), - Equals(" M submodule1 (submodule)").IsSelected(), - Equals(" M submodule2 (submodule)").IsSelected(), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectToast(Contains("Disabled: Multiselection not supported for submodules")) - }). - // Reset the file change. - Press(keys.Universal.ToggleRangeSelect). - NavigateToLine(Contains("file")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - }). - NavigateToLine(Contains("▼ dir")). - Lines( - Equals("▼ dir").IsSelected(), - Equals(" M submodule1 (submodule)"), - Equals(" M submodule2 (submodule)"), - ). - // Verify we still cannot reset the entire folder (has two submodule changes). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectToast(Contains("Disabled: Multiselection not supported for submodules")) - }). - // Reset one of the submodule changes. - SelectNextItem(). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("dir/submodule1")). - Select(Contains("Stash uncommitted submodule changes and update")). - Confirm() - }). - NavigateToLine(Contains("▼ dir")). - Lines( - Equals("▼ dir").IsSelected(), - Equals(" M submodule2 (submodule)"), - ). - // Now we can reset the folder (equivalent to resetting just the nested submodule change). - // Range selecting both the folder and submodule change is allowed. - Press(keys.Universal.ToggleRangeSelect). - SelectNextItem(). - Lines( - Equals("▼ dir").IsSelected(), - Equals(" M submodule2 (submodule)").IsSelected(), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("dir/submodule2")). - Select(Contains("Stash uncommitted submodule changes and update")). - Cancel() - }). - // Or just selecting the folder itself. - NavigateToLine(Contains("▼ dir")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("dir/submodule2")). - Select(Contains("Stash uncommitted submodule changes and update")). - Confirm() - }). - IsEmpty() - }, -}) diff --git a/pkg/integration/tests/submodule/shared.go b/pkg/integration/tests/submodule/shared.go deleted file mode 100644 index 43e0144abbc..00000000000 --- a/pkg/integration/tests/submodule/shared.go +++ /dev/null @@ -1,39 +0,0 @@ -package submodule - -import ( - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -func setupNestedSubmodules(shell *Shell) { - // we're going to have a directory structure like this: - // project - // - repo/modules/outerSubName/modules/innerSubName/ - // - shell.CreateFileAndAdd("rootFile", "rootStuff") - shell.Commit("initial repo commit") - - shell.Chdir("..") - shell.CreateDir("innerSubmodule") - shell.Chdir("innerSubmodule") - shell.Init() - shell.CreateFileAndAdd("inner", "inner") - shell.Commit("initial inner commit") - - shell.Chdir("..") - shell.CreateDir("outerSubmodule") - shell.Chdir("outerSubmodule") - shell.Init() - shell.CreateFileAndAdd("outer", "outer") - shell.Commit("initial outer commit") - shell.CreateDir("modules") - // the git config (-c) parameter below is required - // to let git create a file-protocol/path submodule - shell.RunCommand([]string{"git", "-c", "protocol.file.allow=always", "submodule", "add", "--name", "innerSubName", "../innerSubmodule", "modules/innerSubPath"}) - shell.Commit("add dependency as innerSubmodule") - - shell.Chdir("../repo") - shell.CreateDir("modules") - shell.RunCommand([]string{"git", "-c", "protocol.file.allow=always", "submodule", "add", "--name", "outerSubName", "../outerSubmodule", "modules/outerSubPath"}) - shell.Commit("add dependency as outerSubmodule") - shell.RunCommand([]string{"git", "-c", "protocol.file.allow=always", "submodule", "update", "--init", "--recursive"}) -} diff --git a/pkg/integration/tests/sync/fetch_and_auto_forward_branches_all_branches.go b/pkg/integration/tests/sync/fetch_and_auto_forward_branches_all_branches.go deleted file mode 100644 index 4e6b8445dc8..00000000000 --- a/pkg/integration/tests/sync/fetch_and_auto_forward_branches_all_branches.go +++ /dev/null @@ -1,55 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FetchAndAutoForwardBranchesAllBranches = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Fetch from remote and auto-forward branches with config set to 'allBranches'", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.AutoForwardBranches = "allBranches" - config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(3) - shell.NewBranch("feature") - shell.NewBranch("diverged") - shell.CloneIntoRemote("origin") - shell.SetBranchUpstream("master", "origin/master") - shell.SetBranchUpstream("feature", "origin/feature") - shell.SetBranchUpstream("diverged", "origin/diverged") - shell.Checkout("master") - shell.HardReset("HEAD^") - shell.Checkout("feature") - shell.HardReset("HEAD~2") - shell.Checkout("diverged") - shell.HardReset("HEAD~2") - shell.EmptyCommit("local") - shell.NewBranch("checked-out") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("checked-out").IsSelected(), - Contains("diverged ↓2↑1"), - Contains("feature ↓2").DoesNotContain("↑"), - Contains("master ↓1").DoesNotContain("↑"), - ) - - t.Views().Files(). - IsFocused(). - Press(keys.Files.Fetch) - - // AutoForwardBranches is "allBranches": both master and feature get forwarded - t.Views().Branches(). - Lines( - Contains("checked-out").IsSelected(), - Contains("diverged ↓2↑1"), - Contains("feature ✓"), - Contains("master ✓"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/fetch_and_auto_forward_branches_all_branches_checked_out_in_other_worktree.go b/pkg/integration/tests/sync/fetch_and_auto_forward_branches_all_branches_checked_out_in_other_worktree.go deleted file mode 100644 index bc1ecd294a6..00000000000 --- a/pkg/integration/tests/sync/fetch_and_auto_forward_branches_all_branches_checked_out_in_other_worktree.go +++ /dev/null @@ -1,57 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FetchAndAutoForwardBranchesAllBranchesCheckedOutInOtherWorktree = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Fetch from remote and auto-forward branches with config set to 'allBranches'; check that this skips branches checked out by another worktree", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.AutoForwardBranches = "allBranches" - config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(3) - shell.NewBranch("feature") - shell.NewBranch("diverged") - shell.CloneIntoRemote("origin") - shell.SetBranchUpstream("master", "origin/master") - shell.SetBranchUpstream("feature", "origin/feature") - shell.SetBranchUpstream("diverged", "origin/diverged") - shell.Checkout("master") - shell.HardReset("HEAD^") - shell.Checkout("feature") - shell.HardReset("HEAD~2") - shell.Checkout("diverged") - shell.HardReset("HEAD~2") - shell.EmptyCommit("local") - shell.NewBranch("checked-out") - - shell.AddWorktreeCheckout("master", "../linked-worktree") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("checked-out").IsSelected(), - Contains("diverged ↓2↑1"), - Contains("feature ↓2").DoesNotContain("↑"), - Contains("master (worktree) ↓1").DoesNotContain("↑"), - ) - - t.Views().Files(). - IsFocused(). - Press(keys.Files.Fetch) - - // AutoForwardBranches is "allBranches": both master and feature get forwarded - t.Views().Branches(). - Lines( - Contains("checked-out").IsSelected(), - Contains("diverged ↓2↑1"), - Contains("feature ✓"), - Contains("master (worktree) ↓1"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/fetch_and_auto_forward_branches_none.go b/pkg/integration/tests/sync/fetch_and_auto_forward_branches_none.go deleted file mode 100644 index 7d59ca7bcfc..00000000000 --- a/pkg/integration/tests/sync/fetch_and_auto_forward_branches_none.go +++ /dev/null @@ -1,55 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FetchAndAutoForwardBranchesNone = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Fetch from remote and auto-forward branches with config set to 'none'", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.AutoForwardBranches = "none" - config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(3) - shell.NewBranch("feature") - shell.NewBranch("diverged") - shell.CloneIntoRemote("origin") - shell.SetBranchUpstream("master", "origin/master") - shell.SetBranchUpstream("feature", "origin/feature") - shell.SetBranchUpstream("diverged", "origin/diverged") - shell.Checkout("master") - shell.HardReset("HEAD^") - shell.Checkout("feature") - shell.HardReset("HEAD~2") - shell.Checkout("diverged") - shell.HardReset("HEAD~2") - shell.EmptyCommit("local") - shell.NewBranch("checked-out") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("checked-out").IsSelected(), - Contains("diverged ↓2↑1"), - Contains("feature ↓2").DoesNotContain("↑"), - Contains("master ↓1").DoesNotContain("↑"), - ) - - t.Views().Files(). - IsFocused(). - Press(keys.Files.Fetch) - - // AutoForwardBranches is "none": nothing should happen - t.Views().Branches(). - Lines( - Contains("checked-out").IsSelected(), - Contains("diverged ↓2↑1"), - Contains("feature ↓2").DoesNotContain("↑"), - Contains("master ↓1").DoesNotContain("↑"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/fetch_and_auto_forward_branches_only_main_branches.go b/pkg/integration/tests/sync/fetch_and_auto_forward_branches_only_main_branches.go deleted file mode 100644 index 35481aa0fcb..00000000000 --- a/pkg/integration/tests/sync/fetch_and_auto_forward_branches_only_main_branches.go +++ /dev/null @@ -1,55 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FetchAndAutoForwardBranchesOnlyMainBranches = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Fetch from remote and auto-forward branches with config set to 'onlyMainBranches'", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Git.AutoForwardBranches = "onlyMainBranches" - config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" - }, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(3) - shell.NewBranch("feature") - shell.NewBranch("diverged") - shell.CloneIntoRemote("origin") - shell.SetBranchUpstream("master", "origin/master") - shell.SetBranchUpstream("feature", "origin/feature") - shell.SetBranchUpstream("diverged", "origin/diverged") - shell.Checkout("master") - shell.HardReset("HEAD^") - shell.Checkout("feature") - shell.HardReset("HEAD~2") - shell.Checkout("diverged") - shell.HardReset("HEAD~2") - shell.EmptyCommit("local") - shell.NewBranch("checked-out") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("checked-out").IsSelected(), - Contains("diverged ↓2↑1"), - Contains("feature ↓2").DoesNotContain("↑"), - Contains("master ↓1").DoesNotContain("↑"), - ) - - t.Views().Files(). - IsFocused(). - Press(keys.Files.Fetch) - - // AutoForwardBranches is "onlyMainBranches": master gets forwarded, but feature doesn't - t.Views().Branches(). - Lines( - Contains("checked-out").IsSelected(), - Contains("diverged ↓2↑1"), - Contains("feature ↓2").DoesNotContain("↑"), - Contains("master ✓"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/fetch_prune.go b/pkg/integration/tests/sync/fetch_prune.go deleted file mode 100644 index 7c3625ec989..00000000000 --- a/pkg/integration/tests/sync/fetch_prune.go +++ /dev/null @@ -1,47 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FetchPrune = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Fetch from the remote with the 'prune' option set in the git config", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - // This option makes it so that git checks for deleted branches in the remote - // upon fetching. - shell.SetConfig("fetch.prune", "true") - - shell.EmptyCommit("my commit message") - - shell.NewBranch("branch_to_remove") - shell.Checkout("master") - shell.CloneIntoRemote("origin") - shell.SetBranchUpstream("master", "origin/master") - shell.SetBranchUpstream("branch_to_remove", "origin/branch_to_remove") - - // # unbeknownst to our test repo we're removing the branch on the remote, so upon - // # fetching with prune: true we expect git to realise the remote branch is gone - shell.RemoveRemoteBranch("origin", "branch_to_remove") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("master"), - Contains("branch_to_remove").DoesNotContain("upstream gone"), - ) - - t.Views().Files(). - IsFocused(). - Press(keys.Files.Fetch) - - t.Views().Branches(). - Lines( - Contains("master"), - Contains("branch_to_remove").Contains("upstream gone"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/fetch_when_sorted_by_date.go b/pkg/integration/tests/sync/fetch_when_sorted_by_date.go deleted file mode 100644 index 4a7af151a29..00000000000 --- a/pkg/integration/tests/sync/fetch_when_sorted_by_date.go +++ /dev/null @@ -1,49 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FetchWhenSortedByDate = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Fetch a branch while sort order is by date; verify that branch stays selected", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell. - EmptyCommitWithDate("commit", "2023-04-07 10:00:00"). // first master commit, older than branch2 - EmptyCommitWithDate("commit", "2023-04-07 12:00:00"). // second master commit, newer than branch2 - NewBranch("branch1"). // branch1 will be checked out, so its date doesn't matter - EmptyCommitWithDate("commit", "2023-04-07 11:00:00"). // branch2 commit, date is between the two master commits - NewBranch("branch2"). - Checkout("master"). - CloneIntoRemote("origin"). - SetBranchUpstream("master", "origin/master"). // upstream points to second master commit - HardReset("HEAD^"). // rewind to first master commit - Checkout("branch1") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Press(keys.Branches.SortOrder) - - t.ExpectPopup().Menu().Title(Equals("Sort order")). - Select(Contains("-committerdate")). - Confirm() - - t.Views().Branches(). - Lines( - Contains("* branch1").IsSelected(), - Contains("branch2"), - Contains("master ↓1"), - ). - NavigateToLine(Contains("master")). - Press(keys.Branches.FetchRemote). - Lines( - Contains("* branch1"), - Contains("master").IsSelected(), - Contains("branch2"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/force_push.go b/pkg/integration/tests/sync/force_push.go deleted file mode 100644 index e563cfd2821..00000000000 --- a/pkg/integration/tests/sync/force_push.go +++ /dev/null @@ -1,56 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ForcePush = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Push to a remote with new commits, requiring a force push", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - - shell.CloneIntoRemote("origin") - shell.SetBranchUpstream("master", "origin/master") - - // remove the 'two' commit so that we have something to pull from the remote - shell.HardReset("HEAD^") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Status().Content(Equals("↓1 repo → master")) - - t.Views().Files().IsFocused().Press(keys.Universal.Push) - - t.ExpectPopup().Confirmation(). - Title(Equals("Force push")). - Content(Equals("Your branch has diverged from the remote branch. Press to cancel, or to force push.")). - Confirm() - - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Status().Content(Equals("✓ repo → master")) - - t.Views().Remotes().Focus(). - Lines(Contains("origin")). - PressEnter() - - t.Views().RemoteBranches().IsFocused(). - Lines(Contains("master")). - PressEnter() - - t.Views().SubCommits().IsFocused(). - Lines(Contains("one")) - }, -}) diff --git a/pkg/integration/tests/sync/force_push_multiple_matching.go b/pkg/integration/tests/sync/force_push_multiple_matching.go deleted file mode 100644 index 63825ee4f7c..00000000000 --- a/pkg/integration/tests/sync/force_push_multiple_matching.go +++ /dev/null @@ -1,53 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ForcePushMultipleMatching = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Force push to multiple branches because the user has push.default matching", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.SetConfig("push.default", "matching") - - createTwoBranchesReadyToForcePush(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Status().Content(Equals("↓1 repo → master")) - - t.Views().Branches(). - Lines( - Contains("master ↓1"), - Contains("other_branch ↓1"), - ) - - t.Views().Files().IsFocused().Press(keys.Universal.Push) - - t.ExpectPopup().Confirmation(). - Title(Equals("Force push")). - Content(Equals("Your branch has diverged from the remote branch. Press to cancel, or to force push.")). - Confirm() - - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Status().Content(Equals("✓ repo → master")) - - t.Views().Branches(). - Lines( - Contains("master ✓"), - Contains("other_branch ✓"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/force_push_multiple_upstream.go b/pkg/integration/tests/sync/force_push_multiple_upstream.go deleted file mode 100644 index 8c55b7e8ce9..00000000000 --- a/pkg/integration/tests/sync/force_push_multiple_upstream.go +++ /dev/null @@ -1,52 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ForcePushMultipleUpstream = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Force push to only the upstream branch of the current branch because the user has push.default upstream", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.SetConfig("push.default", "upstream") - - createTwoBranchesReadyToForcePush(shell) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Status().Content(Equals("↓1 repo → master")) - - t.Views().Branches(). - Lines( - Contains("master ↓1"), - Contains("other_branch ↓1"), - ) - - t.Views().Files().IsFocused().Press(keys.Universal.Push) - - t.ExpectPopup().Confirmation(). - Title(Equals("Force push")). - Content(Equals("Your branch has diverged from the remote branch. Press to cancel, or to force push.")). - Confirm() - - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Status().Content(Equals("✓ repo → master")) - - t.Views().Branches(). - Lines( - Contains("master ✓"), - Contains("other_branch ↓1"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/force_push_remote_branch_not_stored_locally.go b/pkg/integration/tests/sync/force_push_remote_branch_not_stored_locally.go deleted file mode 100644 index d6b8a7cfd78..00000000000 --- a/pkg/integration/tests/sync/force_push_remote_branch_not_stored_locally.go +++ /dev/null @@ -1,81 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ForcePushRemoteBranchNotStoredLocally = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Push a branch whose remote branch is not stored locally, requiring a force push", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - - shell.Clone("some-remote") - - // remove the 'two' commit so that we have something to pull from the remote - shell.HardReset("HEAD^") - - shell.SetConfig("branch.master.remote", "../some-remote") - shell.SetConfig("branch.master.pushRemote", "../some-remote") - shell.SetConfig("branch.master.merge", "refs/heads/master") - - shell.CreateFileAndAdd("file1", "file1 content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Status().Content(Contains("? repo → master")) - - // We're behind our upstream now, so we expect to be asked to force-push - t.Views().Files().IsFocused().Press(keys.Universal.Push) - - t.ExpectPopup().Confirmation(). - Title(Equals("Force push")). - Content(Equals("Your branch has diverged from the remote branch. Press to cancel, or to force push.")). - Confirm() - - // Make a new local commit - t.Views().Files().IsFocused().Press(keys.Files.CommitChanges) - t.ExpectPopup().CommitMessagePanel().Type("new").Confirm() - - t.Views().Commits(). - Lines( - Contains("new"), - Contains("one"), - ) - - // Pushing this works without needing to force push - t.Views().Files().IsFocused().Press(keys.Universal.Push) - - // Now add the clone as a remote just so that we can check if what we - // pushed arrived there correctly - t.Views().Remotes().Focus(). - Press(keys.Universal.New) - - t.ExpectPopup().Prompt(). - Title(Equals("New remote name:")).Type("some-remote").Confirm() - t.ExpectPopup().Prompt(). - Title(Equals("New remote url:")).Type("../some-remote").Confirm() - t.Views().Remotes().Lines( - Contains("some-remote").IsSelected(), - ). - PressEnter() - - t.Views().RemoteBranches().IsFocused().Lines( - Contains("master").IsSelected(), - ). - PressEnter() - - t.Views().SubCommits().IsFocused().Lines( - Contains("new"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/force_push_triangular.go b/pkg/integration/tests/sync/force_push_triangular.go deleted file mode 100644 index af965c23e81..00000000000 --- a/pkg/integration/tests/sync/force_push_triangular.go +++ /dev/null @@ -1,64 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ForcePushTriangular = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Push to a remote, requiring a force push because the branch is behind the remote push branch but not the upstream", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.SetConfig("push.default", "current") - - shell.EmptyCommit("one") - - shell.CloneIntoRemote("origin") - - shell.NewBranch("feature") - shell.SetBranchUpstream("feature", "origin/master") - shell.EmptyCommit("two") - shell.PushBranch("origin", "feature") - - // remove the 'two' commit so that we are behind the push branch - shell.HardReset("HEAD^") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Status().Content(Contains("✓ repo → feature")) - - t.Views().Files().IsFocused().Press(keys.Universal.Push) - - t.ExpectPopup().Confirmation(). - Title(Equals("Force push")). - Content(Equals("Your branch has diverged from the remote branch. Press to cancel, or to force push.")). - Confirm() - - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Status().Content(Contains("✓ repo → feature")) - - t.Views().Remotes().Focus(). - Lines(Contains("origin")). - PressEnter() - - t.Views().RemoteBranches().IsFocused(). - Lines( - Contains("feature"), - Contains("master"), - ). - PressEnter() - - t.Views().SubCommits().IsFocused(). - Lines(Contains("one")) - }, -}) diff --git a/pkg/integration/tests/sync/pull.go b/pkg/integration/tests/sync/pull.go deleted file mode 100644 index b30cbb40836..00000000000 --- a/pkg/integration/tests/sync/pull.go +++ /dev/null @@ -1,41 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Pull = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Pull a commit from the remote", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - - shell.CloneIntoRemote("origin") - shell.SetBranchUpstream("master", "origin/master") - - // remove the 'two' commit so that we have something to pull from the remote - shell.HardReset("HEAD^") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Status().Content(Equals("↓1 repo → master")) - - t.Views().Files().IsFocused().Press(keys.Universal.Pull) - - t.Views().Commits(). - Lines( - Contains("two"), - Contains("one"), - ) - - t.Views().Status().Content(Equals("✓ repo → master")) - }, -}) diff --git a/pkg/integration/tests/sync/pull_and_set_upstream.go b/pkg/integration/tests/sync/pull_and_set_upstream.go deleted file mode 100644 index acffa24be84..00000000000 --- a/pkg/integration/tests/sync/pull_and_set_upstream.go +++ /dev/null @@ -1,45 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PullAndSetUpstream = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Pull a commit from the remote, setting the upstream branch in the process", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - - shell.CloneIntoRemote("origin") - - // remove the 'two' commit so that we have something to pull from the remote - shell.HardReset("HEAD^") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Status().Content(Equals("repo → master")) - - t.Views().Files().IsFocused().Press(keys.Universal.Pull) - - t.ExpectPopup().Prompt(). - Title(Equals("Enter upstream as ' '")). - SuggestionLines(Equals("origin master")). - ConfirmFirstSuggestion() - - t.Views().Commits(). - Lines( - Contains("two"), - Contains("one"), - ) - - t.Views().Status().Content(Equals("✓ repo → master")) - }, -}) diff --git a/pkg/integration/tests/sync/pull_merge.go b/pkg/integration/tests/sync/pull_merge.go deleted file mode 100644 index 39e447ebc3b..00000000000 --- a/pkg/integration/tests/sync/pull_merge.go +++ /dev/null @@ -1,53 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PullMerge = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Pull with a merge strategy", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file", "content1") - shell.Commit("one") - shell.UpdateFileAndAdd("file", "content2") - shell.Commit("two") - shell.EmptyCommit("three") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - - shell.HardReset("HEAD^^") - shell.EmptyCommit("four") - - shell.SetConfig("pull.rebase", "false") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("four"), - Contains("one"), - ) - - t.Views().Status().Content(Equals("↓2↑1 repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Pull) - - t.Views().Status().Content(Equals("↑2 repo → master")) - - t.Views().Commits(). - Lines( - Contains("Merge branch 'master' of ../origin"), - Contains("three"), - Contains("two"), - Contains("four"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/pull_merge_conflict.go b/pkg/integration/tests/sync/pull_merge_conflict.go deleted file mode 100644 index 45ca39b1fbc..00000000000 --- a/pkg/integration/tests/sync/pull_merge_conflict.go +++ /dev/null @@ -1,84 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PullMergeConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Pull with a merge strategy, where a conflict occurs", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file", "content1") - shell.Commit("one") - shell.UpdateFileAndAdd("file", "content2") - shell.Commit("two") - shell.EmptyCommit("three") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - - shell.HardReset("HEAD^^") - shell.UpdateFileAndAdd("file", "content4") - shell.Commit("four") - - shell.SetConfig("pull.rebase", "false") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("four"), - Contains("one"), - ) - - t.Views().Status().Content(Equals("↓2↑1 repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Pull) - - t.Common().AcknowledgeConflicts() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU").Contains("file"), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - TopLines( - Contains("<<<<<<< HEAD"), - Contains("content4"), - Contains("======="), - Contains("content2"), - Contains(">>>>>>>"), - ). - PressPrimaryAction() // choose 'content4' - - t.Common().ContinueOnConflictsResolved("merge") - - t.Views().Status().Content(Equals("↑2 repo → master")) - - t.Views().Commits(). - Focus(). - Lines( - Contains("Merge branch 'master' of ../origin").IsSelected(), - Contains("three"), - Contains("two"), - Contains("four"), - Contains("one"), - ) - - t.Views().Main(). - Content( - Contains("- content4"). - Contains(" -content2"). - Contains("++content4"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/pull_rebase.go b/pkg/integration/tests/sync/pull_rebase.go deleted file mode 100644 index a2657ffe627..00000000000 --- a/pkg/integration/tests/sync/pull_rebase.go +++ /dev/null @@ -1,54 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PullRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Pull with a rebase strategy", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file", "content1") - shell.Commit("one") - shell.UpdateFileAndAdd("file", "content2") - shell.Commit("two") - shell.CreateFileAndAdd("file3", "content3") - shell.Commit("three") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - - shell.HardReset("HEAD^^") - shell.CreateFileAndAdd("file4", "content4") - shell.Commit("four") - - shell.SetConfig("pull.rebase", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("four"), - Contains("one"), - ) - - t.Views().Status().Content(Equals("↓2↑1 repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Pull) - - t.Views().Status().Content(Equals("↑1 repo → master")) - - t.Views().Commits(). - Lines( - Contains("four"), - Contains("three"), - Contains("two"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/pull_rebase_conflict.go b/pkg/integration/tests/sync/pull_rebase_conflict.go deleted file mode 100644 index 1d41b2ac351..00000000000 --- a/pkg/integration/tests/sync/pull_rebase_conflict.go +++ /dev/null @@ -1,83 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PullRebaseConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Pull with a rebase strategy, where a conflict occurs", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file", "content1") - shell.Commit("one") - shell.UpdateFileAndAdd("file", "content2") - shell.Commit("two") - shell.EmptyCommit("three") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - - shell.HardReset("HEAD^^") - shell.UpdateFileAndAdd("file", "content4") - shell.Commit("four") - - shell.SetConfig("pull.rebase", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("four"), - Contains("one"), - ) - - t.Views().Status().Content(Equals("↓2↑1 repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Pull) - - t.Common().AcknowledgeConflicts() - - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU").Contains("file"), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - TopLines( - Contains("<<<<<<< HEAD"), - Contains("content2"), - Contains("======="), - Contains("content4"), - Contains(">>>>>>>"), - ). - SelectNextItem(). - PressPrimaryAction() // choose 'content4' - - t.Common().ContinueOnConflictsResolved("rebase") - - t.Views().Status().Content(Equals("↑1 repo → master")) - - t.Views().Commits(). - Focus(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ) - - t.Views().Main(). - Content( - Contains("-content2"). - Contains("+content4"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/pull_rebase_interactive_conflict.go b/pkg/integration/tests/sync/pull_rebase_interactive_conflict.go deleted file mode 100644 index 2bb39e14fd4..00000000000 --- a/pkg/integration/tests/sync/pull_rebase_interactive_conflict.go +++ /dev/null @@ -1,100 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PullRebaseInteractiveConflict = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Pull with an interactive rebase strategy, where a conflict occurs", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file", "content1") - shell.Commit("one") - shell.UpdateFileAndAdd("file", "content2") - shell.Commit("two") - shell.CreateFileAndAdd("file3", "content3") - shell.Commit("three") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - - shell.HardReset("HEAD^^") - shell.UpdateFileAndAdd("file", "content4") - shell.Commit("four") - shell.CreateFileAndAdd("file5", "content5") - shell.Commit("five") - - shell.SetConfig("pull.rebase", "interactive") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("five"), - Contains("four"), - Contains("one"), - ) - - t.Views().Status().Content(Equals("↓2↑2 repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Pull) - - t.Common().AcknowledgeConflicts() - - t.Views().Commits(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("five"), - Contains("pick").Contains("CONFLICT").Contains("four"), - Contains("--- Commits ---"), - Contains("three"), - Contains("two"), - Contains("one"), - ) - - t.Views().Files(). - IsFocused(). - Lines( - Contains("UU").Contains("file"), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - TopLines( - Contains("<<<<<<< HEAD"), - Contains("content2"), - Contains("======="), - Contains("content4"), - Contains(">>>>>>>"), - ). - SelectNextItem(). - PressPrimaryAction() // choose 'content4' - - t.Common().ContinueOnConflictsResolved("rebase") - - t.Views().Status().Content(Equals("↑2 repo → master")) - - t.Views().Commits(). - Focus(). - Lines( - Contains("five").IsSelected(), - Contains("four"), - Contains("three"), - Contains("two"), - Contains("one"), - ). - SelectNextItem() - - t.Views().Main(). - Content( - Contains("-content2"). - Contains("+content4"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/pull_rebase_interactive_conflict_drop.go b/pkg/integration/tests/sync/pull_rebase_interactive_conflict_drop.go deleted file mode 100644 index ad7a4806f90..00000000000 --- a/pkg/integration/tests/sync/pull_rebase_interactive_conflict_drop.go +++ /dev/null @@ -1,109 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PullRebaseInteractiveConflictDrop = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Pull with an interactive rebase strategy, where a conflict occurs. Also drop a commit while rebasing", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("file", "content1") - shell.Commit("one") - shell.UpdateFileAndAdd("file", "content2") - shell.Commit("two") - shell.CreateFileAndAdd("file3", "content3") - shell.Commit("three") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - - shell.HardReset("HEAD^^") - shell.UpdateFileAndAdd("file", "content4") - shell.Commit("four") - shell.CreateFileAndAdd("fil5", "content5") - shell.Commit("five") - - shell.SetConfig("pull.rebase", "interactive") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("five"), - Contains("four"), - Contains("one"), - ) - - t.Views().Status().Content(Equals("↓2↑2 repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Pull) - - t.Common().AcknowledgeConflicts() - - t.Views().Commits(). - Focus(). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("pick").Contains("five").IsSelected(), - Contains("pick").Contains("CONFLICT").Contains("four"), - Contains("--- Commits ---"), - Contains("three"), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Remove). - Lines( - Contains("--- Pending rebase todos ---"), - Contains("drop").Contains("five").IsSelected(), - Contains("pick").Contains("CONFLICT").Contains("four"), - Contains("--- Commits ---"), - Contains("three"), - Contains("two"), - Contains("one"), - ) - - t.Views().Files(). - Focus(). - Lines( - Contains("UU").Contains("file"), - ). - PressEnter() - - t.Views().MergeConflicts(). - IsFocused(). - TopLines( - Contains("<<<<<<< HEAD"), - Contains("content2"), - Contains("======="), - Contains("content4"), - Contains(">>>>>>>"), - ). - SelectNextItem(). - PressPrimaryAction() // choose 'content4' - - t.Common().ContinueOnConflictsResolved("rebase") - - t.Views().Status().Content(Equals("↑1 repo → master")) - - t.Views().Commits(). - Focus(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ) - - t.Views().Main(). - Content( - Contains("-content2"). - Contains("+content4"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/push.go b/pkg/integration/tests/sync/push.go deleted file mode 100644 index cb1e11aa91b..00000000000 --- a/pkg/integration/tests/sync/push.go +++ /dev/null @@ -1,32 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Push = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Push a commit to a pre-configured upstream", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - - shell.EmptyCommit("two") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Status().Content(Equals("↑1 repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Push) - - assertSuccessfullyPushed(t) - }, -}) diff --git a/pkg/integration/tests/sync/push_and_auto_set_upstream.go b/pkg/integration/tests/sync/push_and_auto_set_upstream.go deleted file mode 100644 index d8a336ea45f..00000000000 --- a/pkg/integration/tests/sync/push_and_auto_set_upstream.go +++ /dev/null @@ -1,33 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PushAndAutoSetUpstream = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Push a commit and set the upstream automatically as configured by git", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - - shell.CloneIntoRemote("origin") - - shell.EmptyCommit("two") - - shell.SetConfig("push.default", "current") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // assert no mention of upstream/downstream changes - t.Views().Status().Content(Equals("repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Push) - - assertSuccessfullyPushed(t) - }, -}) diff --git a/pkg/integration/tests/sync/push_and_set_upstream.go b/pkg/integration/tests/sync/push_and_set_upstream.go deleted file mode 100644 index d900452eba0..00000000000 --- a/pkg/integration/tests/sync/push_and_set_upstream.go +++ /dev/null @@ -1,35 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PushAndSetUpstream = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Push a commit and set the upstream via a prompt", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - - shell.CloneIntoRemote("origin") - - shell.EmptyCommit("two") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // assert no mention of upstream/downstream changes - t.Views().Status().Content(Equals("repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Push) - - t.ExpectPopup().Prompt(). - Title(Equals("Enter upstream as ' '")). - SuggestionLines(Equals("origin master")). - ConfirmFirstSuggestion() - - assertSuccessfullyPushed(t) - }, -}) diff --git a/pkg/integration/tests/sync/push_follow_tags.go b/pkg/integration/tests/sync/push_follow_tags.go deleted file mode 100644 index c293cf005f8..00000000000 --- a/pkg/integration/tests/sync/push_follow_tags.go +++ /dev/null @@ -1,56 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PushFollowTags = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Push with --follow-tags configured in git config", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - - shell.EmptyCommit("two") - shell.CreateAnnotatedTag("mytag", "message", "HEAD") - - shell.SetConfig("push.followTags", "true") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Status().Content(Equals("↑1 repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Push) - - t.Views().Status().Content(Equals("✓ repo → master")) - - t.Views().Remotes(). - Focus(). - Lines( - Contains("origin"), - ). - PressEnter() - - t.Views().RemoteBranches(). - IsFocused(). - Lines( - Contains("master"), - ). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("two").Contains("mytag"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/push_no_follow_tags.go b/pkg/integration/tests/sync/push_no_follow_tags.go deleted file mode 100644 index 18a1cf62d93..00000000000 --- a/pkg/integration/tests/sync/push_no_follow_tags.go +++ /dev/null @@ -1,55 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PushNoFollowTags = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Push with --follow-tags NOT configured in git config", - ExtraCmdArgs: []string{}, - Skip: true, // turns out this actually DOES push the tag. I have no idea why - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - - shell.CreateAnnotatedTag("mytag", "message", "HEAD") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Status().Content(Equals("✓ repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Push) - - t.Views().Status().Content(Equals("✓ repo → master")) - - t.Views().Remotes(). - Focus(). - Lines( - Contains("origin"), - ). - PressEnter() - - t.Views().RemoteBranches(). - IsFocused(). - Lines( - Contains("master"), - ). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - // tag was not pushed to upstream - Contains("two").DoesNotContain("mytag"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/push_tag.go b/pkg/integration/tests/sync/push_tag.go deleted file mode 100644 index b83dc1665b3..00000000000 --- a/pkg/integration/tests/sync/push_tag.go +++ /dev/null @@ -1,59 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PushTag = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Push a specific tag", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - - shell.CloneIntoRemote("origin") - - shell.CreateAnnotatedTag("mytag", "message", "HEAD") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Tags(). - Focus(). - Lines( - Contains("mytag"), - ). - Press(keys.Branches.PushTag) - - t.ExpectPopup().Prompt(). - Title(Equals("Remote to push tag 'mytag' to:")). - InitialText(Equals("origin")). - SuggestionLines( - Contains("origin"), - ). - Confirm() - - t.Views().Remotes(). - Focus(). - Lines( - Contains("origin"), - ). - PressEnter() - - t.Views().RemoteBranches(). - IsFocused(). - Lines( - Contains("master"), - ). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("two").Contains("mytag"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/push_with_credential_prompt.go b/pkg/integration/tests/sync/push_with_credential_prompt.go deleted file mode 100644 index 62be89bf438..00000000000 --- a/pkg/integration/tests/sync/push_with_credential_prompt.go +++ /dev/null @@ -1,74 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var PushWithCredentialPrompt = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Push a commit to a pre-configured upstream, where credentials are required", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - - shell.EmptyCommit("two") - - // actually getting a password prompt is tricky: it requires SSH'ing into localhost under a newly created, restricted, user. - // This is not easy to do in a cross-platform way, nor is it easy to do in a docker container. - // If you can think of a way to do it, please let me know! - shell.CopyHelpFile("pre-push", ".git/hooks/pre-push") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Status().Content(Equals("↑1 repo → master")) - - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Push) - - // correct credentials are: username=username, password=password - - t.ExpectPopup().Prompt(). - Title(Equals("Username")). - Type("username"). - Confirm() - - // enter incorrect password - t.ExpectPopup().Prompt(). - Title(Equals("Password")). - Type("incorrect password"). - Confirm() - - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Contains("incorrect username/password")). - Confirm() - - t.Views().Status().Content(Equals("↑1 repo → master")) - - // try again with correct password - t.Views().Files(). - IsFocused(). - Press(keys.Universal.Push) - - t.ExpectPopup().Prompt(). - Title(Equals("Username")). - Type("username"). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("Password")). - Type("password"). - Confirm() - - t.Views().Status().Content(Equals("✓ repo → master")) - - assertSuccessfullyPushed(t) - }, -}) diff --git a/pkg/integration/tests/sync/rename_branch_and_pull.go b/pkg/integration/tests/sync/rename_branch_and_pull.go deleted file mode 100644 index 38f2cf52a38..00000000000 --- a/pkg/integration/tests/sync/rename_branch_and_pull.go +++ /dev/null @@ -1,55 +0,0 @@ -package sync - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RenameBranchAndPull = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Rename a branch to no longer match its upstream, then pull from the upstream", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - - shell.CloneIntoRemote("origin") - shell.SetBranchUpstream("master", "origin/master") - - // remove the 'two' commit so that we have something to pull from the remote - shell.HardReset("HEAD^") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Lines( - Contains("one"), - ) - - t.Views().Branches(). - Focus(). - Lines( - Contains("master"), - ). - Press(keys.Branches.RenameBranch). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Rename branch")). - Content(Equals("This branch is tracking a remote. This action will only rename the local branch name, not the name of the remote branch. Continue?")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Contains("Enter new branch name")). - InitialText(Equals("master")). - Type("-local"). - Confirm() - }). - Press(keys.Universal.Pull) - - t.Views().Commits(). - Lines( - Contains("two"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/sync/shared.go b/pkg/integration/tests/sync/shared.go deleted file mode 100644 index 3e3d5c017c9..00000000000 --- a/pkg/integration/tests/sync/shared.go +++ /dev/null @@ -1,49 +0,0 @@ -package sync - -import ( - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -func createTwoBranchesReadyToForcePush(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - - shell.NewBranch("other_branch") - - shell.CloneIntoRemote("origin") - - shell.SetBranchUpstream("master", "origin/master") - shell.SetBranchUpstream("other_branch", "origin/other_branch") - - // remove the 'two' commit so that we have something to pull from the remote - shell.HardReset("HEAD^") - - shell.Checkout("master") - // doing the same for master - shell.HardReset("HEAD^") -} - -func assertSuccessfullyPushed(t *TestDriver) { - t.Views().Status().Content(Equals("✓ repo → master")) - - t.Views().Remotes(). - Focus(). - Lines( - Contains("origin"), - ). - PressEnter() - - t.Views().RemoteBranches(). - IsFocused(). - Lines( - Contains("master"), - ). - PressEnter() - - t.Views().SubCommits(). - IsFocused(). - Lines( - Contains("two"), - Contains("one"), - ) -} diff --git a/pkg/integration/tests/tag/checkout.go b/pkg/integration/tests/tag/checkout.go deleted file mode 100644 index 10a2bd78682..00000000000 --- a/pkg/integration/tests/tag/checkout.go +++ /dev/null @@ -1,31 +0,0 @@ -package tag - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Checkout = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Checkout a tag", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.CreateLightweightTag("tag", "HEAD^") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Tags(). - Focus(). - Lines( - Contains("tag").IsSelected(), - ). - PressPrimaryAction() // checkout tag - - t.Views().Branches().IsFocused().Lines( - Contains("HEAD detached at tag").IsSelected(), - Contains("master"), - ) - }, -}) diff --git a/pkg/integration/tests/tag/checkout_when_branch_with_same_name_exists.go b/pkg/integration/tests/tag/checkout_when_branch_with_same_name_exists.go deleted file mode 100644 index 73e597f8f41..00000000000 --- a/pkg/integration/tests/tag/checkout_when_branch_with_same_name_exists.go +++ /dev/null @@ -1,34 +0,0 @@ -package tag - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CheckoutWhenBranchWithSameNameExists = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Checkout a tag when there's a branch with the same name", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.NewBranch("tag") - shell.Checkout("master") - shell.EmptyCommit("two") - shell.CreateLightweightTag("tag", "HEAD") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Tags(). - Focus(). - Lines( - Contains("tag").IsSelected(), - ). - PressPrimaryAction() // checkout tag - - t.Views().Branches().IsFocused().Lines( - Contains("HEAD detached at tag").IsSelected(), - Contains("master"), - Contains("tag"), - ) - }, -}) diff --git a/pkg/integration/tests/tag/copy_to_clipboard.go b/pkg/integration/tests/tag/copy_to_clipboard.go deleted file mode 100644 index 9f6b3835966..00000000000 --- a/pkg/integration/tests/tag/copy_to_clipboard.go +++ /dev/null @@ -1,31 +0,0 @@ -package tag - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CopyToClipboard = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Copy the tag to the clipboard", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > clipboard" - }, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.CreateLightweightTag("super.l000ongtag", "HEAD") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Tags(). - Focus(). - Lines( - Contains("tag").IsSelected(), - ). - Press(keys.Universal.CopyToClipboard) - - t.ExpectToast(Equals("'super.l000ongtag' copied to clipboard")) - - t.FileSystem().FileContent("clipboard", Equals("super.l000ongtag")) - }, -}) diff --git a/pkg/integration/tests/tag/create_while_committing.go b/pkg/integration/tests/tag/create_while_committing.go deleted file mode 100644 index 88d31f7599b..00000000000 --- a/pkg/integration/tests/tag/create_while_committing.go +++ /dev/null @@ -1,37 +0,0 @@ -package tag - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CreateWhileCommitting = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Draft a commit message, escape out, and make a tag. Verify the draft message doesn't appear in the tag create prompt", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CreateFileAndAdd("file.txt", "file contents") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - Press(keys.Files.CommitChanges). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - Type("draft message"). - Cancel() - }) - - t.Views().Tags(). - Focus(). - IsEmpty(). - Press(keys.Universal.New). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Tag name")). - InitialText(Equals("")) - }) - }, -}) diff --git a/pkg/integration/tests/tag/crud_annotated.go b/pkg/integration/tests/tag/crud_annotated.go deleted file mode 100644 index d2c3b98c062..00000000000 --- a/pkg/integration/tests/tag/crud_annotated.go +++ /dev/null @@ -1,106 +0,0 @@ -package tag - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CrudAnnotated = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create and delete an annotated tag in the tags panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CloneIntoRemote("origin") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Tags(). - Focus(). - IsEmpty(). - Press(keys.Universal.New). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Tag name")). - Type("new-tag"). - SwitchToDescription(). - Title(Equals("Tag description")). - Type("message"). - SwitchToSummary(). - Confirm() - }). - Lines( - MatchesRegexp(`new-tag.*message`).IsSelected(), - ). - Tap(func() { - t.Views().Main().ContainsLines( - Equals("Annotated tag: new-tag"), - Equals(""), - Contains("Tagger:"), - Contains("TaggerDate:"), - Equals(""), - Equals("message"), - Equals(""), - Equals("---"), - ) - }). - Press(keys.Universal.Push). - Tap(func() { - t.ExpectPopup().Prompt(). - Title(Equals("Remote to push tag 'new-tag' to:")). - InitialText(Equals("origin")). - SuggestionLines( - Contains("origin"), - ). - Confirm() - }). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete tag 'new-tag'?")). - Select(Contains("Delete remote tag")). - Confirm() - }). - Tap(func() { - t.ExpectPopup().Prompt(). - Title(Equals("Remote from which to remove tag 'new-tag':")). - InitialText(Equals("origin")). - SuggestionLines( - Contains("origin"), - ). - Confirm() - }). - Tap(func() { - t.ExpectPopup(). - Confirmation(). - Title(Equals("Delete tag 'new-tag'?")). - Content(Equals("Are you sure you want to delete the remote tag 'new-tag' from 'origin'?")). - Confirm() - t.ExpectToast(Equals("Remote tag deleted")) - }). - Lines( - MatchesRegexp(`new-tag.*message`).IsSelected(), - ). - Tap(func() { - t.Git(). - RemoteTagDeleted("origin", "new-tag") - }). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete tag 'new-tag'?")). - Select(Contains("Delete local tag")). - Confirm() - }). - IsEmpty(). - Press(keys.Universal.New). - Tap(func() { - // confirm content is cleared on next tag create - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Tag name")). - InitialText(Equals("")) - }) - }, -}) diff --git a/pkg/integration/tests/tag/crud_lightweight.go b/pkg/integration/tests/tag/crud_lightweight.go deleted file mode 100644 index 736205ecd38..00000000000 --- a/pkg/integration/tests/tag/crud_lightweight.go +++ /dev/null @@ -1,99 +0,0 @@ -package tag - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CrudLightweight = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create and delete a lightweight tag in the tags panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CloneIntoRemote("origin") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Tags(). - Focus(). - IsEmpty(). - Press(keys.Universal.New). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Tag name")). - Type("new-tag"). - Confirm() - }). - Lines( - MatchesRegexp(`new-tag.*initial commit`).IsSelected(), - ). - Tap(func() { - t.Views().Main().ContainsLines( - Equals("Lightweight tag: new-tag"), - Equals(""), - Equals("---"), - ) - }). - PressEnter(). - Tap(func() { - // view the commits of the tag - t.Views().SubCommits().IsFocused(). - Lines( - Contains("initial commit"), - ). - PressEscape() - }). - Press(keys.Universal.Push). - Tap(func() { - t.ExpectPopup().Prompt(). - Title(Equals("Remote to push tag 'new-tag' to:")). - InitialText(Equals("origin")). - SuggestionLines( - Contains("origin"), - ). - Confirm() - }). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete tag 'new-tag'?")). - Select(Contains("Delete remote tag")). - Confirm() - }). - Tap(func() { - t.ExpectPopup().Prompt(). - Title(Equals("Remote from which to remove tag 'new-tag':")). - InitialText(Equals("origin")). - SuggestionLines( - Contains("origin"), - ). - Confirm() - }). - Tap(func() { - t.ExpectPopup(). - Confirmation(). - Title(Equals("Delete tag 'new-tag'?")). - Content(Equals("Are you sure you want to delete the remote tag 'new-tag' from 'origin'?")). - Confirm() - t.ExpectToast(Equals("Remote tag deleted")) - }). - Lines( - MatchesRegexp(`new-tag.*initial commit`).IsSelected(), - ). - Tap(func() { - t.Git(). - RemoteTagDeleted("origin", "new-tag") - }). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete tag 'new-tag'?")). - Select(Contains("Delete local tag")). - Confirm() - }). - IsEmpty() - }, -}) diff --git a/pkg/integration/tests/tag/delete_local_and_remote.go b/pkg/integration/tests/tag/delete_local_and_remote.go deleted file mode 100644 index 35b9bc25de1..00000000000 --- a/pkg/integration/tests/tag/delete_local_and_remote.go +++ /dev/null @@ -1,75 +0,0 @@ -package tag - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DeleteLocalAndRemote = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Create and delete both local and remote annotated tag", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("initial commit") - shell.CloneIntoRemote("origin") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Tags(). - Focus(). - IsEmpty(). - Press(keys.Universal.New). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Tag name")). - Type("new-tag"). - SwitchToDescription(). - Title(Equals("Tag description")). - Type("message"). - SwitchToSummary(). - Confirm() - }). - Lines( - MatchesRegexp(`new-tag.*message`).IsSelected(), - ). - Press(keys.Universal.Push). - Tap(func() { - t.ExpectPopup().Prompt(). - Title(Equals("Remote to push tag 'new-tag' to:")). - InitialText(Equals("origin")). - SuggestionLines( - Contains("origin"), - ). - Confirm() - }). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete tag 'new-tag'?")). - Select(Contains("Delete local and remote tag")). - Confirm() - }). - Tap(func() { - t.ExpectPopup().Prompt(). - Title(Equals("Remote from which to remove tag 'new-tag':")). - InitialText(Equals("origin")). - SuggestionLines( - Contains("origin"), - ). - Confirm() - }). - Tap(func() { - t.ExpectPopup(). - Confirmation(). - Title(Equals("Delete tag 'new-tag'?")). - Content(Equals("Are you sure you want to delete 'new-tag' from both your machine and from 'origin'?")). - Confirm() - }). - IsEmpty(). - Press(keys.Universal.New). - Tap(func() { - t.Shell().AssertRemoteTagNotFound("origin", "new-tag") - }) - }, -}) diff --git a/pkg/integration/tests/tag/force_tag_annotated.go b/pkg/integration/tests/tag/force_tag_annotated.go deleted file mode 100644 index 1f9e2f09b8a..00000000000 --- a/pkg/integration/tests/tag/force_tag_annotated.go +++ /dev/null @@ -1,47 +0,0 @@ -package tag - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ForceTagAnnotated = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Overwrite an annotated tag that already exists", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - shell.CreateAnnotatedTag("new-tag", "message", "HEAD") - shell.EmptyCommit("second commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("second commit").IsSelected(), - Contains("new-tag").Contains("first commit"), - ). - Press(keys.Commits.CreateTag). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Tag name")). - Type("new-tag"). - SwitchToDescription(). - Title(Equals("Tag description")). - Type("message"). - SwitchToSummary(). - Confirm() - }). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Force Tag")). - Content(Contains("The tag 'new-tag' exists already. Press to cancel, or to overwrite.")). - Confirm() - }). - Lines( - Contains("new-tag").Contains("second commit"), - DoesNotContain("new-tag").Contains("first commit"), - ) - }, -}) diff --git a/pkg/integration/tests/tag/force_tag_lightweight.go b/pkg/integration/tests/tag/force_tag_lightweight.go deleted file mode 100644 index 1f24b624c3c..00000000000 --- a/pkg/integration/tests/tag/force_tag_lightweight.go +++ /dev/null @@ -1,43 +0,0 @@ -package tag - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ForceTagLightweight = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Overwrite a lightweight tag that already exists", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("first commit") - shell.CreateLightweightTag("new-tag", "HEAD") - shell.EmptyCommit("second commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("second commit").IsSelected(), - Contains("new-tag").Contains("first commit"), - ). - Press(keys.Commits.CreateTag). - Tap(func() { - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Tag name")). - Type("new-tag"). - Confirm() - }). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Force Tag")). - Content(Contains("The tag 'new-tag' exists already. Press to cancel, or to overwrite.")). - Confirm() - }). - Lines( - Contains("new-tag").Contains("second commit"), - DoesNotContain("new-tag").Contains("first commit"), - ) - }, -}) diff --git a/pkg/integration/tests/tag/reset.go b/pkg/integration/tests/tag/reset.go deleted file mode 100644 index 657f698246f..00000000000 --- a/pkg/integration/tests/tag/reset.go +++ /dev/null @@ -1,40 +0,0 @@ -package tag - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Reset = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Hard reset to a tag", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.CreateLightweightTag("tag", "HEAD^") // creating tag on commit "one" - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Lines( - Contains("two"), - Contains("one"), - ) - - t.Views().Tags(). - Focus(). - Lines( - Contains("tag").IsSelected(), - ). - Press(keys.Commits.ViewResetOptions) - - t.ExpectPopup().Menu(). - Title(Contains("Reset to tag")). - Select(Contains("Hard reset")). - Confirm() - - t.Views().Commits().Lines( - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/tag/reset_to_duplicate_named_branch.go b/pkg/integration/tests/tag/reset_to_duplicate_named_branch.go deleted file mode 100644 index 096bcff04e2..00000000000 --- a/pkg/integration/tests/tag/reset_to_duplicate_named_branch.go +++ /dev/null @@ -1,48 +0,0 @@ -package tag - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ResetToDuplicateNamedBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Hard reset to a tag when a branch shares the same name", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("current-branch") - - shell.EmptyCommit("other-branch-tag commit") - shell.CreateLightweightTag("other-branch", "HEAD") - - shell.EmptyCommit("other-branch commit") - shell.NewBranch("other-branch") - - shell.Checkout("current-branch") - shell.EmptyCommit("current-branch commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits().Lines( - Contains("current-branch commit"), - Contains("other-branch commit"), - Contains("other-branch-tag commit"), - ) - - t.Views().Tags(). - Focus(). - Lines( - Contains("other-branch").IsSelected(), - ). - Press(keys.Commits.ViewResetOptions) - - t.ExpectPopup().Menu(). - Title(Contains("Reset to other-branch")). - Select(Contains("Hard reset")). - Confirm() - - t.Views().Commits().Lines( - Contains("other-branch-tag commit"), - ) - }, -}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go deleted file mode 100644 index da21d1f0d3c..00000000000 --- a/pkg/integration/tests/test_list.go +++ /dev/null @@ -1,467 +0,0 @@ -// THIS FILE IS AUTO-GENERATED. You can regenerate it by running `go generate ./...` at the root of the lazygit repo. - -package tests - -import ( - "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/bisect" - "github.com/jesseduffield/lazygit/pkg/integration/tests/branch" - "github.com/jesseduffield/lazygit/pkg/integration/tests/cherry_pick" - "github.com/jesseduffield/lazygit/pkg/integration/tests/commit" - "github.com/jesseduffield/lazygit/pkg/integration/tests/config" - "github.com/jesseduffield/lazygit/pkg/integration/tests/conflicts" - "github.com/jesseduffield/lazygit/pkg/integration/tests/custom_commands" - "github.com/jesseduffield/lazygit/pkg/integration/tests/demo" - "github.com/jesseduffield/lazygit/pkg/integration/tests/diff" - "github.com/jesseduffield/lazygit/pkg/integration/tests/file" - "github.com/jesseduffield/lazygit/pkg/integration/tests/filter_and_search" - "github.com/jesseduffield/lazygit/pkg/integration/tests/filter_by_author" - "github.com/jesseduffield/lazygit/pkg/integration/tests/filter_by_path" - "github.com/jesseduffield/lazygit/pkg/integration/tests/interactive_rebase" - "github.com/jesseduffield/lazygit/pkg/integration/tests/misc" - "github.com/jesseduffield/lazygit/pkg/integration/tests/patch_building" - "github.com/jesseduffield/lazygit/pkg/integration/tests/reflog" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shell_commands" - "github.com/jesseduffield/lazygit/pkg/integration/tests/staging" - "github.com/jesseduffield/lazygit/pkg/integration/tests/stash" - "github.com/jesseduffield/lazygit/pkg/integration/tests/status" - "github.com/jesseduffield/lazygit/pkg/integration/tests/submodule" - "github.com/jesseduffield/lazygit/pkg/integration/tests/sync" - "github.com/jesseduffield/lazygit/pkg/integration/tests/tag" - "github.com/jesseduffield/lazygit/pkg/integration/tests/ui" - "github.com/jesseduffield/lazygit/pkg/integration/tests/undo" - "github.com/jesseduffield/lazygit/pkg/integration/tests/worktree" -) - -var tests = []*components.IntegrationTest{ - bisect.Basic, - bisect.ChooseTerms, - bisect.FromOtherBranch, - bisect.Skip, - branch.CheckoutAutostash, - branch.CheckoutByName, - branch.CheckoutPreviousBranch, - branch.CreateTag, - branch.Delete, - branch.DeleteMultiple, - branch.DeleteRemoteBranchWithCredentialPrompt, - branch.DeleteRemoteBranchWithDifferentName, - branch.DeleteWhileFiltering, - branch.DetachedHead, - branch.MergeFastForward, - branch.MergeNonFastForward, - branch.MoveCommitsToNewBranchFromBaseBranch, - branch.MoveCommitsToNewBranchFromMainBranch, - branch.MoveCommitsToNewBranchKeepStacked, - branch.NewBranchAutostash, - branch.NewBranchFromRemoteTrackingDifferentName, - branch.NewBranchFromRemoteTrackingSameName, - branch.NewBranchWithPrefix, - branch.NewBranchWithPrefixUsingRunCommand, - branch.OpenPullRequestInvalidTargetRemoteName, - branch.OpenPullRequestNoUpstream, - branch.OpenPullRequestSelectRemoteAndTargetBranch, - branch.OpenWithCliArg, - branch.Rebase, - branch.RebaseAbortOnConflict, - branch.RebaseAndDrop, - branch.RebaseCancelOnConflict, - branch.RebaseConflictsFixBuildErrors, - branch.RebaseCopiedBranch, - branch.RebaseDoesNotAutosquash, - branch.RebaseFromMarkedBase, - branch.RebaseOntoBaseBranch, - branch.RebaseToUpstream, - branch.Rename, - branch.Reset, - branch.ResetToDuplicateNamedTag, - branch.ResetToDuplicateNamedUpstream, - branch.ResetToUpstream, - branch.SelectCommitsOfCurrentBranch, - branch.SetUpstream, - branch.ShowDivergenceFromBaseBranch, - branch.ShowDivergenceFromUpstream, - branch.ShowDivergenceFromUpstreamNoDivergence, - branch.SortLocalBranches, - branch.SortRemoteBranches, - branch.SquashMerge, - branch.Suggestions, - branch.UnsetUpstream, - cherry_pick.CherryPick, - cherry_pick.CherryPickCommitThatBecomesEmpty, - cherry_pick.CherryPickConflicts, - cherry_pick.CherryPickConflictsEmptyCommitAfterResolving, - cherry_pick.CherryPickDuringRebase, - cherry_pick.CherryPickMerge, - cherry_pick.CherryPickRange, - commit.AddCoAuthor, - commit.AddCoAuthorRange, - commit.AddCoAuthorWhileCommitting, - commit.Amend, - commit.AmendWhenThereAreConflictsAndAmend, - commit.AmendWhenThereAreConflictsAndCancel, - commit.AmendWhenThereAreConflictsAndContinue, - commit.AutoWrapMessage, - commit.Checkout, - commit.CheckoutFileFromCommit, - commit.CheckoutFileFromRangeSelectionOfCommits, - commit.Commit, - commit.CommitMultiline, - commit.CommitSkipHooks, - commit.CommitSwitchToEditor, - commit.CommitSwitchToEditorSkipHooks, - commit.CommitWipWithPrefix, - commit.CommitWithFallthroughPrefix, - commit.CommitWithGlobalPrefix, - commit.CommitWithNonMatchingBranchName, - commit.CommitWithPrefix, - commit.CopyAuthorToClipboard, - commit.CopyMessageBodyToClipboard, - commit.CopyTagToClipboard, - commit.CreateAmendCommit, - commit.CreateFixupCommitInBranchStack, - commit.CreateTag, - commit.DisableCopyCommitMessageBody, - commit.DiscardOldFileChanges, - commit.DiscardSubmoduleChanges, - commit.DoNotShowBranchMarkerForHeadCommit, - commit.FailHooksThenCommitNoHooks, - commit.FindBaseCommitForFixup, - commit.FindBaseCommitForFixupDisregardMainBranch, - commit.FindBaseCommitForFixupOnlyAddedLines, - commit.FindBaseCommitForFixupWarningForAddedLines, - commit.Highlight, - commit.History, - commit.HistoryComplex, - commit.NewBranch, - commit.PasteCommitMessage, - commit.PasteCommitMessageOverExisting, - commit.PreserveCommitMessage, - commit.ResetAuthor, - commit.ResetAuthorRange, - commit.Revert, - commit.RevertMerge, - commit.RevertWithConflictMultipleCommits, - commit.RevertWithConflictSingleCommit, - commit.Reword, - commit.Search, - commit.SetAuthor, - commit.SetAuthorRange, - commit.StageRangeOfLines, - commit.Staged, - commit.StagedWithoutHooks, - commit.Unstaged, - config.CustomCommandsInPerRepoConfig, - config.NegativeRefspec, - config.RemoteNamedStar, - conflicts.Filter, - conflicts.MergeFileBoth, - conflicts.MergeFileCurrent, - conflicts.MergeFileIncoming, - conflicts.ResolveExternally, - conflicts.ResolveMultipleFiles, - conflicts.ResolveNoAutoStage, - conflicts.ResolveNonTextualConflicts, - conflicts.ResolveWithoutTrailingLf, - conflicts.UndoChooseHunk, - custom_commands.AccessCommitProperties, - custom_commands.BasicCommand, - custom_commands.CheckForConflicts, - custom_commands.CustomCommandsSubmenu, - custom_commands.FormPrompts, - custom_commands.GlobalContext, - custom_commands.MenuFromCommand, - custom_commands.MenuFromCommandsOutput, - custom_commands.MultipleContexts, - custom_commands.MultiplePrompts, - custom_commands.RunCommand, - custom_commands.SelectedCommit, - custom_commands.SelectedCommitRange, - custom_commands.SelectedPath, - custom_commands.ShowOutputInPanel, - custom_commands.SuggestionsCommand, - custom_commands.SuggestionsPreset, - demo.AmendOldCommit, - demo.Bisect, - demo.CherryPick, - demo.CommitAndPush, - demo.CommitGraph, - demo.CustomCommand, - demo.CustomPatch, - demo.DiffCommits, - demo.Filter, - demo.InteractiveRebase, - demo.NukeWorkingTree, - demo.RebaseOnto, - demo.StageLines, - demo.Undo, - demo.WorktreeCreateFromBranches, - diff.CopyToClipboard, - diff.Diff, - diff.DiffAndApplyPatch, - diff.DiffCommits, - diff.DiffNonStickyRange, - diff.IgnoreWhitespace, - diff.RenameSimilarityThresholdChange, - file.CollapseExpand, - file.CopyMenu, - file.DirWithUntrackedFile, - file.DiscardAllDirChanges, - file.DiscardRangeSelect, - file.DiscardStagedChanges, - file.DiscardUnstagedDirChanges, - file.DiscardUnstagedFileChanges, - file.DiscardUnstagedRangeSelect, - file.DiscardVariousChanges, - file.DiscardVariousChangesRangeSelect, - file.Gitignore, - file.GitignoreSpecialCharacters, - file.RememberCommitMessageAfterFail, - file.RenameSimilarityThresholdChange, - file.RenamedFiles, - file.RenamedFilesNoRootItem, - file.StageChildrenRangeSelect, - file.StageDeletedRangeSelect, - file.StageRangeSelect, - filter_and_search.FilterByFileStatus, - filter_and_search.FilterCommitFiles, - filter_and_search.FilterFiles, - filter_and_search.FilterFuzzy, - filter_and_search.FilterMenu, - filter_and_search.FilterMenuByKeybinding, - filter_and_search.FilterMenuCancelFilterWithEscape, - filter_and_search.FilterMenuWithNoKeybindings, - filter_and_search.FilterRemoteBranches, - filter_and_search.FilterRemotes, - filter_and_search.FilterSearchHistory, - filter_and_search.FilterUpdatesWhenModelChanges, - filter_and_search.NestedFilter, - filter_and_search.NestedFilterTransient, - filter_and_search.NewSearch, - filter_and_search.StageAllStagesOnlyTrackedFilesInTrackedOnlyFilter, - filter_and_search.StagingFolderStagesOnlyTrackedFilesInTrackedOnlyFilter, - filter_by_author.SelectAuthor, - filter_by_author.TypeAuthor, - filter_by_path.CliArg, - filter_by_path.DropCommitInFilteringMode, - filter_by_path.KeepSameCommitSelectedOnExit, - filter_by_path.RewordCommitInFilteringMode, - filter_by_path.SelectFile, - filter_by_path.SelectFilteredFileWhenEnteringCommit, - filter_by_path.SelectFilteredFileWhenEnteringCommitNoRootItem, - filter_by_path.ShowDiffsForRenamedFile, - filter_by_path.TypeFile, - interactive_rebase.AdvancedInteractiveRebase, - interactive_rebase.AmendCommitWithConflict, - interactive_rebase.AmendFirstCommit, - interactive_rebase.AmendFixupCommit, - interactive_rebase.AmendHeadCommitDuringRebase, - interactive_rebase.AmendMerge, - interactive_rebase.AmendNonHeadCommitDuringRebase, - interactive_rebase.DeleteUpdateRefTodo, - interactive_rebase.DontShowBranchHeadsForTodoItems, - interactive_rebase.DropCommitInCopiedBranchWithUpdateRef, - interactive_rebase.DropMergeCommit, - interactive_rebase.DropTodoCommitWithUpdateRef, - interactive_rebase.DropWithCustomCommentChar, - interactive_rebase.EditAndAutoAmend, - interactive_rebase.EditFirstCommit, - interactive_rebase.EditLastCommitOfStackedBranch, - interactive_rebase.EditNonTodoCommitDuringRebase, - interactive_rebase.EditRangeSelectDownToMergeOutsideRebase, - interactive_rebase.EditRangeSelectOutsideRebase, - interactive_rebase.EditTheConflCommit, - interactive_rebase.FixupFirstCommit, - interactive_rebase.FixupSecondCommit, - interactive_rebase.InteractiveRebaseOfCopiedBranch, - interactive_rebase.InteractiveRebaseWithConflictForEditCommand, - interactive_rebase.MidRebaseRangeSelect, - interactive_rebase.Move, - interactive_rebase.MoveAcrossBranchBoundaryOutsideRebase, - interactive_rebase.MoveInRebase, - interactive_rebase.MoveUpdateRefTodo, - interactive_rebase.MoveWithCustomCommentChar, - interactive_rebase.OutsideRebaseRangeSelect, - interactive_rebase.PickRescheduled, - interactive_rebase.QuickStart, - interactive_rebase.QuickStartKeepSelection, - interactive_rebase.QuickStartKeepSelectionRange, - interactive_rebase.Rebase, - interactive_rebase.RebaseWithCommitThatBecomesEmpty, - interactive_rebase.RevertDuringRebaseWhenStoppedOnEdit, - interactive_rebase.RevertMultipleCommitsInInteractiveRebase, - interactive_rebase.RevertSingleCommitInInteractiveRebase, - interactive_rebase.RewordCommitWithEditorAndFail, - interactive_rebase.RewordFirstCommit, - interactive_rebase.RewordLastCommit, - interactive_rebase.RewordLastCommitOfStackedBranch, - interactive_rebase.RewordMergeCommit, - interactive_rebase.RewordYouAreHereCommit, - interactive_rebase.RewordYouAreHereCommitWithEditor, - interactive_rebase.ShowExecTodos, - interactive_rebase.SquashDownFirstCommit, - interactive_rebase.SquashDownSecondCommit, - interactive_rebase.SquashFixupsAbove, - interactive_rebase.SquashFixupsAboveFirstCommit, - interactive_rebase.SquashFixupsInCurrentBranch, - interactive_rebase.SwapInRebaseWithConflict, - interactive_rebase.SwapInRebaseWithConflictAndEdit, - interactive_rebase.SwapWithConflict, - interactive_rebase.ViewFilesOfTodoEntries, - misc.ConfirmOnQuit, - misc.CopyConfirmationMessageToClipboard, - misc.CopyToClipboard, - misc.DisabledKeybindings, - misc.InitialOpen, - misc.RecentReposOnLaunch, - patch_building.Apply, - patch_building.ApplyInReverse, - patch_building.ApplyInReverseWithConflict, - patch_building.ApplyWithModifiedFileConflict, - patch_building.ApplyWithModifiedFileNoConflict, - patch_building.EditLineInPatchBuildingPanel, - patch_building.MoveRangeToIndex, - patch_building.MoveToEarlierCommit, - patch_building.MoveToEarlierCommitFromAddedFile, - patch_building.MoveToIndex, - patch_building.MoveToIndexFromAddedFileWithConflict, - patch_building.MoveToIndexPartOfAdjacentAddedLines, - patch_building.MoveToIndexPartial, - patch_building.MoveToIndexWithConflict, - patch_building.MoveToIndexWithModifiedFile, - patch_building.MoveToIndexWorksEvenIfNoprefixIsSet, - patch_building.MoveToLaterCommit, - patch_building.MoveToLaterCommitPartialHunk, - patch_building.MoveToNewCommit, - patch_building.MoveToNewCommitBefore, - patch_building.MoveToNewCommitFromAddedFile, - patch_building.MoveToNewCommitFromDeletedFile, - patch_building.MoveToNewCommitInLastCommitOfStackedBranch, - patch_building.MoveToNewCommitPartialHunk, - patch_building.RemoveFromCommit, - patch_building.RemovePartsOfAddedFile, - patch_building.ResetWithEscape, - patch_building.SelectAllFiles, - patch_building.SpecificSelection, - patch_building.StartNewPatch, - patch_building.ToggleRange, - reflog.Checkout, - reflog.CherryPick, - reflog.DoNotShowBranchMarkersInReflogSubcommits, - reflog.Patch, - reflog.Reset, - shell_commands.BasicShellCommand, - shell_commands.ComplexShellCommand, - shell_commands.DeleteFromHistory, - shell_commands.EditHistory, - shell_commands.History, - shell_commands.OmitFromHistory, - staging.DiffChangeScreenMode, - staging.DiffContextChange, - staging.DiscardAllChanges, - staging.Search, - staging.StageHunks, - staging.StageLines, - staging.StageRanges, - stash.Apply, - stash.ApplyPatch, - stash.CreateBranch, - stash.Drop, - stash.DropMultiple, - stash.DropMultipleInFilteredMode, - stash.FilterByPath, - stash.Pop, - stash.PreventDiscardingFileChanges, - stash.Rename, - stash.ShowWithBranchNamedStash, - stash.Stash, - stash.StashAll, - stash.StashAndKeepIndex, - stash.StashIncludingUntrackedFiles, - stash.StashStaged, - stash.StashStagedPartialFile, - stash.StashUnstaged, - status.ClickRepoNameToOpenReposMenu, - status.ClickToFocus, - status.ClickWorkingTreeStateToOpenRebaseOptionsMenu, - status.LogCmd, - status.LogCmdStatusPanelAllBranchesLog, - submodule.Add, - submodule.Enter, - submodule.EnterNested, - submodule.Remove, - submodule.RemoveNested, - submodule.Reset, - submodule.ResetFolder, - sync.FetchAndAutoForwardBranchesAllBranches, - sync.FetchAndAutoForwardBranchesAllBranchesCheckedOutInOtherWorktree, - sync.FetchAndAutoForwardBranchesNone, - sync.FetchAndAutoForwardBranchesOnlyMainBranches, - sync.FetchPrune, - sync.FetchWhenSortedByDate, - sync.ForcePush, - sync.ForcePushMultipleMatching, - sync.ForcePushMultipleUpstream, - sync.ForcePushRemoteBranchNotStoredLocally, - sync.ForcePushTriangular, - sync.Pull, - sync.PullAndSetUpstream, - sync.PullMerge, - sync.PullMergeConflict, - sync.PullRebase, - sync.PullRebaseConflict, - sync.PullRebaseInteractiveConflict, - sync.PullRebaseInteractiveConflictDrop, - sync.Push, - sync.PushAndAutoSetUpstream, - sync.PushAndSetUpstream, - sync.PushFollowTags, - sync.PushNoFollowTags, - sync.PushTag, - sync.PushWithCredentialPrompt, - sync.RenameBranchAndPull, - tag.Checkout, - tag.CheckoutWhenBranchWithSameNameExists, - tag.CopyToClipboard, - tag.CreateWhileCommitting, - tag.CrudAnnotated, - tag.CrudLightweight, - tag.DeleteLocalAndRemote, - tag.ForceTagAnnotated, - tag.ForceTagLightweight, - tag.Reset, - tag.ResetToDuplicateNamedBranch, - ui.Accordion, - ui.DisableSwitchTabWithPanelJumpKeys, - ui.EmptyMenu, - ui.KeybindingSuggestionsWhenSwitchingRepos, - ui.ModeSpecificKeybindingSuggestions, - ui.OpenLinkFailure, - ui.RangeSelect, - ui.SwitchTabFromMenu, - ui.SwitchTabWithPanelJumpKeys, - undo.UndoCheckoutAndDrop, - undo.UndoCommit, - undo.UndoDrop, - worktree.AddFromBranch, - worktree.AddFromBranchDetached, - worktree.AddFromCommit, - worktree.AssociateBranchBisect, - worktree.AssociateBranchRebase, - worktree.BareRepo, - worktree.BareRepoWorktreeConfig, - worktree.Crud, - worktree.CustomCommand, - worktree.DetachWorktreeFromBranch, - worktree.DotfileBareRepo, - worktree.DoubleNestedLinkedSubmodule, - worktree.ExcludeFileInWorktree, - worktree.FastForwardWorktreeBranch, - worktree.FastForwardWorktreeBranchShouldNotPolluteCurrentWorktree, - worktree.ForceRemoveWorktree, - worktree.ForceRemoveWorktreeWithSubmodules, - worktree.RemoveWorktreeFromBranch, - worktree.ResetWindowTabs, - worktree.SymlinkIntoRepoSubdir, - worktree.WorktreeInRepo, -} diff --git a/pkg/integration/tests/test_list_generator.go b/pkg/integration/tests/test_list_generator.go deleted file mode 100644 index 3d5407bf691..00000000000 --- a/pkg/integration/tests/test_list_generator.go +++ /dev/null @@ -1,116 +0,0 @@ -//go:build ignore - -// This file is invoked with `go generate ./...` and it generates the test_list.go file -// The test_list.go file is a list of all the integration tests. -// It's annoying to have to manually add an entry in that file for each test you -// create, so this generator is here to make the process easier. - -package main - -import ( - "bytes" - "fmt" - "go/format" - "io/fs" - "os" - "strings" - - "github.com/samber/lo" -) - -func main() { - println("Generating test_list.go...") - - code := generateCode() - - formattedCode, err := format.Source(code) - if err != nil { - panic(err) - } - if err := os.WriteFile("test_list.go", formattedCode, 0o644); err != nil { - panic(err) - } -} - -func generateCode() []byte { - // traverse parent directory to get all sibling directories - directories, err := os.ReadDir("../tests") - if err != nil { - panic(err) - } - - directories = lo.Filter(directories, func(file fs.DirEntry, _ int) bool { - // 'shared' is a special folder containing shared test code so we - // ignore it here - return file.IsDir() && file.Name() != "shared" - }) - - var buf bytes.Buffer - fmt.Fprintf(&buf, "// THIS FILE IS AUTO-GENERATED. You can regenerate it by running `go generate ./...` at the root of the lazygit repo.\n\n") - fmt.Fprintf(&buf, "package tests\n\n") - fmt.Fprintf(&buf, "import (\n") - fmt.Fprintf(&buf, "\t\"github.com/jesseduffield/lazygit/pkg/integration/components\"\n") - for _, dir := range directories { - fmt.Fprintf(&buf, "\t\"github.com/jesseduffield/lazygit/pkg/integration/tests/%s\"\n", dir.Name()) - } - fmt.Fprintf(&buf, ")\n\n") - fmt.Fprintf(&buf, "var tests = []*components.IntegrationTest{\n") - for _, dir := range directories { - appendDirTests(dir, &buf) - } - fmt.Fprintf(&buf, "}\n") - - return buf.Bytes() -} - -func appendDirTests(dir fs.DirEntry, buf *bytes.Buffer) { - files, err := os.ReadDir(fmt.Sprintf("../tests/%s", dir.Name())) - if err != nil { - panic(err) - } - - for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".go") { - continue - } - - testName := snakeToPascal( - strings.TrimSuffix(file.Name(), ".go"), - ) - - fileContents, err := os.ReadFile(fmt.Sprintf("../tests/%s/%s", dir.Name(), file.Name())) - if err != nil { - panic(err) - } - - fileContentsStr := string(fileContents) - - if !strings.Contains(fileContentsStr, "NewIntegrationTest(") { - // the file does not define a test so it probably just contains shared test code - continue - } - - if !strings.Contains(fileContentsStr, fmt.Sprintf("var %s = NewIntegrationTest(NewIntegrationTestArgs{", testName)) { - panic(fmt.Sprintf("expected test %s to be defined in file %s. Perhaps you misspelt it? The name of the test should be the name of the file but converted from snake_case to PascalCase", testName, file.Name())) - } - - fmt.Fprintf(buf, "\t%s.%s,\n", dir.Name(), testName) - } -} - -// thanks ChatGPT -func snakeToPascal(s string) string { - // Split the input string into words. - words := strings.Split(s, "_") - - // Convert the first letter of each word to uppercase and concatenate them. - var builder strings.Builder - for _, w := range words { - if len(w) > 0 { - builder.WriteString(strings.ToUpper(w[:1])) - builder.WriteString(w[1:]) - } - } - - return builder.String() -} diff --git a/pkg/integration/tests/tests.go b/pkg/integration/tests/tests.go deleted file mode 100644 index 22ca7d8d7a9..00000000000 --- a/pkg/integration/tests/tests.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:generate go run test_list_generator.go - -package tests - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/jesseduffield/generics/set" - "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/samber/lo" -) - -func GetTests(lazygitRootDir string) []*components.IntegrationTest { - // first we ensure that each test in this directory has actually been added to the above list. - testCount := 0 - - testNamesSet := set.NewFromSlice(lo.Map( - tests, - func(test *components.IntegrationTest, _ int) string { - return test.Name() - }, - )) - - missingTestNames := []string{} - - if err := filepath.Walk(filepath.Join(lazygitRootDir, "pkg/integration/tests"), func(path string, info os.FileInfo, err error) error { - if !info.IsDir() && strings.HasSuffix(path, ".go") { - // ignoring non-test files - if filepath.Base(path) == "tests.go" || filepath.Base(path) == "test_list.go" || filepath.Base(path) == "test_list_generator.go" { - return nil - } - - // the shared directory won't itself contain tests: only shared helper functions - if filepath.Base(filepath.Dir(path)) == "shared" { - return nil - } - - // any file named shared.go will also be ignored, because those files are only used for shared helper functions - if filepath.Base(path) == "shared.go" { - return nil - } - - nameFromPath := components.TestNameFromFilePath(path) - if !testNamesSet.Includes(nameFromPath) { - missingTestNames = append(missingTestNames, nameFromPath) - } - testCount++ - } - return nil - }); err != nil { - panic(fmt.Sprintf("failed to walk tests: %v", err)) - } - - if len(missingTestNames) > 0 { - panic(fmt.Sprintf("The following tests are missing from the list of tests: %s. You need to add them to `pkg/integration/tests/test_list.go`. Use `go generate ./...` to regenerate the tests list.", strings.Join(missingTestNames, ", "))) - } - - if testCount > len(tests) { - panic("you have not added all of the tests to the tests list in `pkg/integration/tests/test_list.go`. Use `go generate ./...` to regenerate the tests list.") - } else if testCount < len(tests) { - panic("There are more tests in `pkg/integration/tests/test_list.go` than there are test files in the tests directory. Ensure that you only have one test per file and you haven't included the same test twice in the tests list. Use `go generate ./...` to regenerate the tests list.") - } - - return tests -} diff --git a/pkg/integration/tests/ui/accordion.go b/pkg/integration/tests/ui/accordion.go deleted file mode 100644 index ef1fbaea327..00000000000 --- a/pkg/integration/tests/ui/accordion.go +++ /dev/null @@ -1,61 +0,0 @@ -package ui - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// When in accordion mode, Lazygit looks like this: -// -// ╶─Status─────────────────────────╴┌─Patch──────────────────────────────────────────────────────────┐ -// ╶─Files - Submodules──────0 of 0─╴│commit 6e56dd04b70e548976f7f2928c4d9c359574e2bc ▲ -// ╶─Local branches - Remotes1 of 1─╴│Author: CI █ -// ┌─Commits - Reflog───────────────┐│Date: Wed Jul 19 22:00:03 2023 +1000 │ -// │7fe02805 CI commit 12 ▲│ ▼ -// │6e56dd04 CI commit 11 █└────────────────────────────────────────────────────────────────┘ -// │a35c687d CI commit 10 ▼┌─Command log────────────────────────────────────────────────────┐ -// └───────────────────────10 of 20─┘│Random tip: To filter commits by path, press '' │ -// ╶─Stash───────────────────0 of 0─╴└────────────────────────────────────────────────────────────────┘ -// /: Scroll, : Cancel, q: Quit, ?: Keybindings, 1-Donate Ask Question unversioned - -var Accordion = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify accordion mode kicks in when the screen height is too small", - ExtraCmdArgs: []string{}, - Width: 100, - Height: 10, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(20) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - VisibleLines( - Contains("commit 20").IsSelected(), - Contains("commit 19"), - Contains("commit 18"), - ). - // go past commit 11, then come back, so that it ends up in the centre of the viewport - NavigateToLine(Contains("commit 11")). - NavigateToLine(Contains("commit 10")). - NavigateToLine(Contains("commit 11")). - VisibleLines( - Contains("commit 12"), - Contains("commit 11").IsSelected(), - Contains("commit 10"), - ) - - t.Views().Files(). - Focus() - - // ensure we retain the same viewport upon re-focus - t.Views().Commits(). - Focus(). - VisibleLines( - Contains("commit 12"), - Contains("commit 11").IsSelected(), - Contains("commit 10"), - ) - }, -}) diff --git a/pkg/integration/tests/ui/disable_switch_tab_with_panel_jump_keys.go b/pkg/integration/tests/ui/disable_switch_tab_with_panel_jump_keys.go deleted file mode 100644 index fb1ba5aba2a..00000000000 --- a/pkg/integration/tests/ui/disable_switch_tab_with_panel_jump_keys.go +++ /dev/null @@ -1,26 +0,0 @@ -package ui - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DisableSwitchTabWithPanelJumpKeys = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that the tab does not change by default when jumping to an already focused panel", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - }, - SetupRepo: func(shell *Shell) { - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Status().Focus(). - Press(keys.Universal.JumpToBlock[1]) - t.Views().Files().IsFocused(). - Press(keys.Universal.JumpToBlock[1]) - - // Despite jumping to an already focused panel, - // the tab should not change from the base files view - t.Views().Files().IsFocused() - }, -}) diff --git a/pkg/integration/tests/ui/empty_menu.go b/pkg/integration/tests/ui/empty_menu.go deleted file mode 100644 index 35c3d4560f9..00000000000 --- a/pkg/integration/tests/ui/empty_menu.go +++ /dev/null @@ -1,38 +0,0 @@ -package ui - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var EmptyMenu = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that we don't crash on an empty menu", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files(). - IsFocused(). - Press(keys.Universal.OptionMenu) - - t.Views().Menu(). - IsFocused(). - // a string that filters everything out - FilterOrSearch("ljasldkjaslkdjalskdjalsdjaslkd"). - IsEmpty(). - Press(keys.Universal.Select). - Tap(func() { - t.ExpectToast(Equals("Disabled: No item selected")) - }). - // escape the search - PressEscape(). - // escape the view - PressEscape() - - // back in the files view, selecting the non-existing menu item was a no-op - t.Views().Files(). - IsFocused() - }, -}) diff --git a/pkg/integration/tests/ui/keybinding_suggestions_when_switching_repos.go b/pkg/integration/tests/ui/keybinding_suggestions_when_switching_repos.go deleted file mode 100644 index def9a53c742..00000000000 --- a/pkg/integration/tests/ui/keybinding_suggestions_when_switching_repos.go +++ /dev/null @@ -1,42 +0,0 @@ -package ui - -import ( - "path/filepath" - - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var KeybindingSuggestionsWhenSwitchingRepos = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Show correct keybinding suggestions after switching between repos", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - otherRepo, _ := filepath.Abs("../other") - config.GetAppState().RecentRepos = []string{otherRepo} - }, - SetupRepo: func(shell *Shell) { - shell.CloneNonBare("other") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - switchToRepo := func(repo string) { - t.GlobalPress(keys.Universal.OpenRecentRepos) - t.ExpectPopup().Menu().Title(Equals("Recent repositories")). - Lines( - Contains(repo).IsSelected(), - Contains("Cancel"), - ).Confirm() - t.Views().Status().Content(Contains(repo + " → master")) - } - - t.Views().Files().Focus() - t.Views().Options().Content( - Equals("Commit: c | Stash: s | Reset: D | Keybindings: ?")) - - switchToRepo("other") - switchToRepo("repo") - - t.Views().Options().Content( - Equals("Commit: c | Stash: s | Reset: D | Keybindings: ?")) - }, -}) diff --git a/pkg/integration/tests/ui/mode_specific_keybinding_suggestions.go b/pkg/integration/tests/ui/mode_specific_keybinding_suggestions.go deleted file mode 100644 index d64a22a38cc..00000000000 --- a/pkg/integration/tests/ui/mode_specific_keybinding_suggestions.go +++ /dev/null @@ -1,118 +0,0 @@ -package ui - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" - "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" -) - -var ModeSpecificKeybindingSuggestions = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "When in various modes, we should corresponding keybinding suggestions onscreen", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateNCommits(2) - shell.NewBranch("base-branch") - shared.MergeConflictsSetup(shell) - shell.Checkout("base-branch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - rebaseSuggestion := "View rebase options: m" - cherryPickSuggestion := "Paste (cherry-pick): V" - bisectSuggestion := "View bisect options: b" - customPatchSuggestion := "View custom patch options: " - mergeSuggestion := "View merge options: m" - - t.Views().Commits(). - Focus(). - Lines( - Contains("commit 02").IsSelected(), - Contains("commit 01"), - ). - Tap(func() { - // These suggestions are mode-specific so are not shown by default - t.Views().Options().Content( - DoesNotContain(rebaseSuggestion). - DoesNotContain(mergeSuggestion). - DoesNotContain(cherryPickSuggestion). - DoesNotContain(bisectSuggestion). - DoesNotContain(customPatchSuggestion), - ) - }). - // Start an interactive rebase - Press(keys.Universal.Edit). - Tap(func() { - // Confirm the rebase suggestion now appears - t.Views().Options().Content(Contains(rebaseSuggestion)) - }). - Press(keys.Commits.CherryPickCopy). - Tap(func() { - // Confirm the cherry pick suggestion now appears - t.Views().Options().Content(Contains(cherryPickSuggestion)) - // Importantly, we show multiple of these suggestions at once - t.Views().Options().Content(Contains(rebaseSuggestion)) - }). - // Cancel the cherry pick - PressEscape(). - Tap(func() { - t.Views().Options().Content(DoesNotContain(cherryPickSuggestion)) - }). - // Cancel the rebase - Tap(func() { - t.Common().AbortRebase() - - t.Views().Options().Content(DoesNotContain(rebaseSuggestion)) - }). - Press(keys.Commits.ViewBisectOptions). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Bisect")). - Select(MatchesRegexp("Mark.* as bad")). - Confirm() - - t.Views().Options().Content(Contains(bisectSuggestion)) - - // Cancel bisect - t.Common().ResetBisect() - - t.Views().Options().Content(DoesNotContain(bisectSuggestion)) - }). - // Enter commit files view - PressEnter() - - t.Views().CommitFiles(). - IsFocused(). - // Add a commit file to the patch - Press(keys.Universal.Select). - Tap(func() { - t.Views().Options().Content(Contains(customPatchSuggestion)) - - t.Common().ResetCustomPatch() - - t.Views().Options().Content(DoesNotContain(customPatchSuggestion)) - }) - - // Test merge options suggestion - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("first-change-branch")). - Press(keys.Universal.Select). - NavigateToLine(Contains("second-change-branch")). - Press(keys.Branches.MergeIntoCurrentBranch). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Merge")). - Select(Contains("Regular merge (with merge commit)")). - Confirm() - - t.Common().AcknowledgeConflicts() - - t.Views().Options().Content(Contains(mergeSuggestion)) - - t.Common().AbortMerge() - - t.Views().Options().Content(DoesNotContain(mergeSuggestion)) - }) - }, -}) diff --git a/pkg/integration/tests/ui/open_link_failure.go b/pkg/integration/tests/ui/open_link_failure.go deleted file mode 100644 index c4b27241ea3..00000000000 --- a/pkg/integration/tests/ui/open_link_failure.go +++ /dev/null @@ -1,24 +0,0 @@ -package ui - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var OpenLinkFailure = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "When opening links via the OS fails, show a dialog instead.", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().OS.OpenLink = "exit 42" - }, - SetupRepo: func(shell *Shell) {}, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Information().Click(0, 0) - - t.ExpectPopup().Confirmation(). - Title(Equals("Error")). - Content(Equals("Failed to open URL https://github.com/sponsors/jesseduffield\n\nError: exit status 42")). - Confirm() - }, -}) diff --git a/pkg/integration/tests/ui/range_select.go b/pkg/integration/tests/ui/range_select.go deleted file mode 100644 index b021ea65d13..00000000000 --- a/pkg/integration/tests/ui/range_select.go +++ /dev/null @@ -1,183 +0,0 @@ -package ui - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// Here's the state machine we need to verify: -// (no range, press 'v') -> sticky range -// (no range, press arrow) -> no range -// (no range, press shift+arrow) -> nonsticky range -// (sticky range, press 'v') -> no range -// (sticky range, press 'escape') -> no range -// (sticky range, press arrow) -> sticky range -// (sticky range, press `<`/`>` or `,`/`.`) -> sticky range -// (sticky range, press shift+arrow) -> nonsticky range -// (nonsticky range, press 'v') -> no range -// (nonsticky range, press 'escape') -> no range -// (nonsticky range, press arrow) -> no range -// (nonsticky range, press shift+arrow) -> nonsticky range - -// Importantly, if you press 'v' when in a nonsticky range, it clears the range, -// so no matter which mode you're in, 'v' will cancel the range. -// And, if you press shift+up/down when in a sticky range, it switches to a non- -// sticky range, meaning if you then press up/down without shift, it clears -// the range. - -var RangeSelect = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify range select works as expected in list views and in patch explorer views", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.UseHunkModeInStagingView = false - }, - SetupRepo: func(shell *Shell) { - // We're testing the commits view as our representative list context, - // as well as the staging view, and we're using the exact same code to test - // both to ensure they have the exact same behaviour (they are currently implemented - // separately) - // In both views we're going to have 10 lines starting from 'line 1' going down to - // 'line 10'. - fileContent := "staged\n" - total := 10 - for i := 1; i <= total; i++ { - remaining := total - i + 1 - // Commits are displayed in reverse order so to we need to create them in reverse to have them appear as 'line 1', 'line 2' etc. - shell.EmptyCommit(fmt.Sprintf("line %d", remaining)) - fileContent = fmt.Sprintf("%sline %d\n", fileContent, i) - } - shell.CreateFileAndAdd("file1", "staged\n") - shell.UpdateFile("file1", fileContent) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - assertRangeSelectBehaviour := func(v *ViewDriver, focusOtherView func(), lineIdxOfFirstItem int) { - v. - SelectedLines( - Contains("line 1"), - ). - // (no range, press 'v') -> sticky range - Press(keys.Universal.ToggleRangeSelect). - SelectedLines( - Contains("line 1"), - ). - // (sticky range, press arrow) -> sticky range - SelectNextItem(). - SelectedLines( - Contains("line 1"), - Contains("line 2"), - ). - // (sticky range, press 'v') -> no range - Press(keys.Universal.ToggleRangeSelect). - SelectedLines( - Contains("line 2"), - ). - // (no range, press arrow) -> no range - SelectPreviousItem(). - SelectedLines( - Contains("line 1"), - ). - // (no range, press shift+arrow) -> nonsticky range - Press(keys.Universal.RangeSelectDown). - SelectedLines( - Contains("line 1"), - Contains("line 2"), - ). - // (nonsticky range, press shift+arrow) -> nonsticky range - Press(keys.Universal.RangeSelectDown). - SelectedLines( - Contains("line 1"), - Contains("line 2"), - Contains("line 3"), - ). - Press(keys.Universal.RangeSelectUp). - SelectedLines( - Contains("line 1"), - Contains("line 2"), - ). - // (nonsticky range, press arrow) -> no range - SelectNextItem(). - SelectedLines( - Contains("line 3"), - ). - Press(keys.Universal.ToggleRangeSelect). - SelectedLines( - Contains("line 3"), - ). - SelectNextItem(). - SelectedLines( - Contains("line 3"), - Contains("line 4"), - ). - // (sticky range, press shift+arrow) -> nonsticky range - Press(keys.Universal.RangeSelectDown). - SelectedLines( - Contains("line 3"), - Contains("line 4"), - Contains("line 5"), - ). - SelectNextItem(). - SelectedLines( - Contains("line 6"), - ). - Press(keys.Universal.RangeSelectDown). - SelectedLines( - Contains("line 6"), - Contains("line 7"), - ). - // (nonsticky range, press 'v') -> no range - Press(keys.Universal.ToggleRangeSelect). - SelectedLines( - Contains("line 7"), - ). - Press(keys.Universal.RangeSelectDown). - SelectedLines( - Contains("line 7"), - Contains("line 8"), - ). - // (nonsticky range, press 'escape') -> no range - PressEscape(). - SelectedLines( - Contains("line 8"), - ). - // (sticky range, press '>') -> sticky range - Press(keys.Universal.ToggleRangeSelect). - Press(keys.Universal.GotoBottom). - SelectedLines( - Contains("line 8"), - Contains("line 9"), - Contains("line 10"), - ). - // (sticky range, press 'escape') -> no range - PressEscape(). - SelectedLines( - Contains("line 10"), - ) - - // Click in view, press shift+arrow -> nonsticky range - focusOtherView() - v.Click(1, lineIdxOfFirstItem). - SelectedLines( - Contains("line 1"), - ). - Press(keys.Universal.RangeSelectDown). - SelectedLines( - Contains("line 1"), - Contains("line 2"), - ) - } - - assertRangeSelectBehaviour(t.Views().Commits().Focus(), func() { t.Views().Branches().Focus() }, 0) - - t.Views().Files(). - Focus(). - SelectedLine( - Contains("file1"), - ). - PressEnter() - - assertRangeSelectBehaviour(t.Views().Staging().IsFocused(), func() { t.Views().Staging().PressTab() }, 6) - }, -}) diff --git a/pkg/integration/tests/ui/switch_tab_from_menu.go b/pkg/integration/tests/ui/switch_tab_from_menu.go deleted file mode 100644 index 61bd991abe7..00000000000 --- a/pkg/integration/tests/ui/switch_tab_from_menu.go +++ /dev/null @@ -1,25 +0,0 @@ -package ui - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SwitchTabFromMenu = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Switch tab via the options menu", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Files().IsFocused(). - Press(keys.Universal.OptionMenuAlt1) - - t.ExpectPopup().Menu().Title(Equals("Keybindings")). - Select(Contains("Next tab")). - Confirm() - - t.Views().Worktrees().IsFocused() - }, -}) diff --git a/pkg/integration/tests/ui/switch_tab_with_panel_jump_keys.go b/pkg/integration/tests/ui/switch_tab_with_panel_jump_keys.go deleted file mode 100644 index 4411cb3c6a7..00000000000 --- a/pkg/integration/tests/ui/switch_tab_with_panel_jump_keys.go +++ /dev/null @@ -1,36 +0,0 @@ -package ui - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SwitchTabWithPanelJumpKeys = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Switch tab with the panel jump keys after enabling the feature", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.SwitchTabsWithPanelJumpKeys = true - }, - SetupRepo: func(shell *Shell) { - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Worktrees().Focus(). - Press(keys.Universal.JumpToBlock[2]) - - t.Views().Branches().IsFocused(). - Press(keys.Universal.JumpToBlock[2]) - - t.Views().Remotes().IsFocused(). - Press(keys.Universal.JumpToBlock[2]) - - t.Views().Tags().IsFocused(). - Press(keys.Universal.JumpToBlock[2]) - - t.Views().Branches().IsFocused(). - Press(keys.Universal.JumpToBlock[1]) - - // When jumping to a panel from a different one, keep its current tab: - t.Views().Worktrees().IsFocused() - }, -}) diff --git a/pkg/integration/tests/undo/undo_checkout_and_drop.go b/pkg/integration/tests/undo/undo_checkout_and_drop.go deleted file mode 100644 index 6c095b029d1..00000000000 --- a/pkg/integration/tests/undo/undo_checkout_and_drop.go +++ /dev/null @@ -1,151 +0,0 @@ -package undo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var UndoCheckoutAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Drop some commits and then undo/redo the actions", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.EmptyCommit("three") - shell.EmptyCommit("four") - - shell.NewBranch("other_branch") - shell.Checkout("master") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // we're going to drop a commit, switch branch, drop a commit there, then undo everything, then redo everything. - - confirmCommitDrop := func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Drop commit")). - Content(Equals("Are you sure you want to drop the selected commit(s)?")). - Confirm() - } - - confirmUndoDrop := func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Undo")). - Content(MatchesRegexp(`Are you sure you want to hard reset to '.*'\? An auto-stash will be performed if necessary\.`)). - Confirm() - } - - confirmRedoDrop := func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Redo")). - Content(MatchesRegexp(`Are you sure you want to hard reset to '.*'\? An auto-stash will be performed if necessary\.`)). - Confirm() - } - - t.Views().Commits().Focus(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Remove). - Tap(confirmCommitDrop). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ) - - t.Views().Branches().Focus(). - Lines( - Contains("master").IsSelected(), - Contains("other_branch"), - ). - SelectNextItem(). - // checkout branch - PressPrimaryAction(). - Lines( - Contains("other_branch").IsSelected(), - Contains("master"), - ) - - // drop the commit in the 'other_branch' branch too - t.Views().Commits().Focus(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Remove). - Tap(confirmCommitDrop). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Undo). - Tap(confirmUndoDrop). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Undo). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Undo")). - Content(Contains("Are you sure you want to checkout 'master'?")). - Confirm() - - t.Views().Branches(). - Lines( - Contains("master").IsSelected(), - Contains("other_branch"), - ) - }). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Undo). - Tap(confirmUndoDrop). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Redo). - Tap(confirmRedoDrop). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Redo). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Redo")). - Content(Contains("Are you sure you want to checkout 'other_branch'?")). - Confirm() - - t.Views().Branches(). - Lines( - Contains("other_branch").IsSelected(), - Contains("master"), - ) - }). - Press(keys.Universal.Redo). - Tap(confirmRedoDrop). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/undo/undo_commit.go b/pkg/integration/tests/undo/undo_commit.go deleted file mode 100644 index 7fe61238c5a..00000000000 --- a/pkg/integration/tests/undo/undo_commit.go +++ /dev/null @@ -1,112 +0,0 @@ -package undo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var UndoCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Undo/redo a commit", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.CreateFileAndAdd("other-file", "other-file-1") - shell.Commit("one") - shell.CreateFileAndAdd("file", "file-1") - shell.Commit("two") - shell.UpdateFile("other-file", "other-file-2") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - confirmUndo := func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Undo")). - Content(MatchesRegexp(`Are you sure you want to soft reset to '.*'\?`)). - Confirm() - } - - confirmRedo := func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Redo")). - Content(MatchesRegexp(`Are you sure you want to hard reset to '.*'\? An auto-stash will be performed if necessary\.`)). - Confirm() - } - - confirmDiscardFile := func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - } - - t.Views().Files(). - Lines( - Contains(" M other-file"), - ) - - t.Views().Commits().Focus(). - Lines( - Contains("two").IsSelected(), - Contains("one"), - ). - Press(keys.Universal.Undo). - Tap(confirmUndo). - Lines( - Contains("one").IsSelected(), - ) - - t.Views().Files(). - Lines( - Equals("▼ /"), - Equals(" A file"), - Equals(" M other-file"), - ) - - t.Views().Commits().Focus(). - Press(keys.Universal.Redo). - Tap(confirmRedo). - Lines( - Contains("two").IsSelected(), - Contains("one"), - ) - - t.Views().Files(). - Lines( - Equals(" M other-file"), - ) - - // Undo again, this time discarding the original change before redoing again - t.Views().Commits().Focus(). - Press(keys.Universal.Undo). - Tap(confirmUndo). - Lines( - Contains("one").IsSelected(), - ) - - t.Views().Files().Focus(). - Lines( - Equals("▼ /"), - Equals(" A file"), - Equals(" M other-file").IsSelected(), - ). - Press(keys.Universal.PrevItem). - Press(keys.Universal.Remove). - Tap(confirmDiscardFile). - Lines( - Equals(" M other-file"), - ). - Press(keys.Universal.Redo). - Tap(confirmRedo) - - t.Views().Commits(). - Lines( - Contains("two"), - Contains("one"), - ) - - t.Views().Files(). - Lines( - Equals(" M other-file"), - ) - }, -}) diff --git a/pkg/integration/tests/undo/undo_drop.go b/pkg/integration/tests/undo/undo_drop.go deleted file mode 100644 index 2d4d2d6a95d..00000000000 --- a/pkg/integration/tests/undo/undo_drop.go +++ /dev/null @@ -1,90 +0,0 @@ -package undo - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var UndoDrop = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Drop some commits and then undo/redo the actions", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("one") - shell.EmptyCommit("two") - shell.EmptyCommit("three") - shell.EmptyCommit("four") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - confirmCommitDrop := func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Drop commit")). - Content(Equals("Are you sure you want to drop the selected commit(s)?")). - Confirm() - } - - confirmUndo := func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Undo")). - Content(MatchesRegexp(`Are you sure you want to hard reset to '.*'\? An auto-stash will be performed if necessary\.`)). - Confirm() - } - - confirmRedo := func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Redo")). - Content(MatchesRegexp(`Are you sure you want to hard reset to '.*'\? An auto-stash will be performed if necessary\.`)). - Confirm() - } - - t.Views().Commits().Focus(). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Remove). - Tap(confirmCommitDrop). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Remove). - Tap(confirmCommitDrop). - Lines( - Contains("two").IsSelected(), - Contains("one"), - ). - Press(keys.Universal.Undo). - Tap(confirmUndo). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Undo). - Tap(confirmUndo). - Lines( - Contains("four").IsSelected(), - Contains("three"), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Redo). - Tap(confirmRedo). - Lines( - Contains("three").IsSelected(), - Contains("two"), - Contains("one"), - ). - Press(keys.Universal.Redo). - Tap(confirmRedo). - Lines( - Contains("two").IsSelected(), - Contains("one"), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/add_from_branch.go b/pkg/integration/tests/worktree/add_from_branch.go deleted file mode 100644 index 70aa4dd6003..00000000000 --- a/pkg/integration/tests/worktree/add_from_branch.go +++ /dev/null @@ -1,65 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AddFromBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Add a worktree via the branches view, then switch back to the main worktree via the branches view", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("mybranch"), - ). - Press(keys.Worktrees.ViewWorktreeOptions). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Worktree")). - Select(Contains(`Create worktree from mybranch`).DoesNotContain("detached")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New worktree path")). - Type("../linked-worktree"). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New branch name")). - Type("newbranch"). - Confirm() - }). - // confirm we're still focused on the branches view - IsFocused(). - Lines( - Contains("newbranch").IsSelected(), - Contains("mybranch (worktree)"), - ). - NavigateToLine(Contains("mybranch")). - Press(keys.Universal.Select). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Switch to worktree")). - Content(Equals("This branch is checked out by worktree repo. Do you want to switch to that worktree?")). - Confirm() - }). - Lines( - Contains("mybranch").IsSelected(), - Contains("newbranch (worktree)"), - ). - // Confirm the files view is still showing in the files window - Press(keys.Universal.PrevBlock) - - t.Views().Files(). - IsFocused() - }, -}) diff --git a/pkg/integration/tests/worktree/add_from_branch_detached.go b/pkg/integration/tests/worktree/add_from_branch_detached.go deleted file mode 100644 index 584de344e09..00000000000 --- a/pkg/integration/tests/worktree/add_from_branch_detached.go +++ /dev/null @@ -1,46 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AddFromBranchDetached = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Add a detached worktree via the branches view", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("mybranch"), - ). - Press(keys.Worktrees.ViewWorktreeOptions). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Worktree")). - Select(Contains(`Create worktree from mybranch (detached)`)). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New worktree path")). - Type("../linked-worktree"). - Confirm() - }). - // confirm we're still focused on the branches view - IsFocused(). - Lines( - Contains("(no branch)").IsSelected(), - Contains("mybranch (worktree)"), - ) - - t.Views().Status(). - Content(Contains("repo(linked-worktree)")) - }, -}) diff --git a/pkg/integration/tests/worktree/add_from_commit.go b/pkg/integration/tests/worktree/add_from_commit.go deleted file mode 100644 index a171f74a3f0..00000000000 --- a/pkg/integration/tests/worktree/add_from_commit.go +++ /dev/null @@ -1,56 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var AddFromCommit = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Add a worktree via the commits view", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - shell.EmptyCommit("commit two") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Commits(). - Focus(). - Lines( - Contains("commit two").IsSelected(), - Contains("initial commit"), - ). - NavigateToLine(Contains("initial commit")). - Press(keys.Worktrees.ViewWorktreeOptions). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Worktree")). - Select(MatchesRegexp(`Create worktree from .*`).DoesNotContain("detached")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New worktree path")). - Type("../linked-worktree"). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New branch name")). - Type("newbranch"). - Confirm() - }). - Lines( - Contains("initial commit"), - ) - - // Confirm we're now in the branches view - t.Views().Branches(). - IsFocused(). - Lines( - Contains("newbranch").IsSelected(), - Contains("mybranch (worktree)"), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/associate_branch_bisect.go b/pkg/integration/tests/worktree/associate_branch_bisect.go deleted file mode 100644 index 77d46e17601..00000000000 --- a/pkg/integration/tests/worktree/associate_branch_bisect.go +++ /dev/null @@ -1,88 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// This is important because `git worktree list` will show a worktree being in a detached head state (which is true) -// when it's in the middle of a bisect, but it won't tell you about the branch it's on. -// Even so, if you attempt to check out that branch from another worktree git won't let you, so we need to -// keep track of the association ourselves. - -// not bothering to test the linked worktree here because it's the same logic as the rebase test - -var AssociateBranchBisect = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that when you start a bisect in a linked worktree, Lazygit still associates the worktree with the branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - shell.EmptyCommit("commit 2") - shell.EmptyCommit("commit 3") - shell.AddWorktree("mybranch", "../linked-worktree", "newbranch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("mybranch").IsSelected(), - Contains("newbranch (worktree)"), - ) - - // start a bisect on the main worktree - t.Views().Commits(). - Focus(). - SelectedLine(Contains("commit 3")). - Press(keys.Commits.ViewBisectOptions). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Bisect")). - Select(MatchesRegexp(`Mark .* as bad`)). - Confirm() - - t.Views().Information().Content(Contains("Bisecting")) - }). - NavigateToLine(Contains("initial commit")). - Press(keys.Commits.ViewBisectOptions). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Bisect")). - Select(MatchesRegexp(`Mark .* as good`)). - Confirm() - }) - - t.Views().Branches(). - Focus(). - // switch to linked worktree - NavigateToLine(Contains("newbranch")). - Press(keys.Universal.Select). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Switch to worktree")). - Content(Equals("This branch is checked out by worktree linked-worktree. Do you want to switch to that worktree?")). - Confirm() - - t.Views().Information().Content(DoesNotContain("Bisecting")) - }). - Lines( - Contains("newbranch").IsSelected(), - Contains("mybranch (worktree)"), - ) - - // switch back to main worktree - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("mybranch")). - Press(keys.Universal.Select). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Switch to worktree")). - Content(Equals("This branch is checked out by worktree repo. Do you want to switch to that worktree?")). - Confirm() - }) - }, -}) diff --git a/pkg/integration/tests/worktree/associate_branch_rebase.go b/pkg/integration/tests/worktree/associate_branch_rebase.go deleted file mode 100644 index b0b04b805db..00000000000 --- a/pkg/integration/tests/worktree/associate_branch_rebase.go +++ /dev/null @@ -1,89 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// This is important because `git worktree list` will show a worktree being in a detached head state (which is true) -// when it's in the middle of a rebase, but it won't tell you about the branch it's on. -// Even so, if you attempt to check out that branch from another worktree git won't let you, so we need to -// keep track of the association ourselves. - -// We need different logic for associated the branch depending on whether it's a main worktree or -// linked worktree, so this test handles both. - -var AssociateBranchRebase = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that when you start a rebase in a linked or main worktree, Lazygit still associates the worktree with the branch", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - shell.EmptyCommit("commit 2") - shell.EmptyCommit("commit 3") - shell.AddWorktree("mybranch", "../linked-worktree", "newbranch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("mybranch").IsSelected(), - Contains("newbranch (worktree)"), - ) - - // start a rebase on the main worktree - t.Views().Commits(). - Focus(). - NavigateToLine(Contains("commit 2")). - Press(keys.Universal.Edit) - - t.Views().Information().Content(Contains("Rebasing")) - - t.Views().Branches(). - Focus(). - // switch to linked worktree - NavigateToLine(Contains("newbranch")). - Press(keys.Universal.Select). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Switch to worktree")). - Content(Equals("This branch is checked out by worktree linked-worktree. Do you want to switch to that worktree?")). - Confirm() - - t.Views().Information().Content(DoesNotContain("Rebasing")) - }). - Lines( - Contains("newbranch").IsSelected(), - Contains("mybranch (worktree)"), - ) - - // start a rebase on the linked worktree - t.Views().Commits(). - Focus(). - NavigateToLine(Contains("commit 2")). - Press(keys.Universal.Edit) - - t.Views().Information().Content(Contains("Rebasing")) - - // switch back to main worktree - t.Views().Branches(). - Focus(). - NavigateToLine(Contains("mybranch")). - Press(keys.Universal.Select). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Switch to worktree")). - Content(Equals("This branch is checked out by worktree repo. Do you want to switch to that worktree?")). - Confirm() - }). - Lines( - Contains("(no branch").IsSelected(), - Contains("mybranch"), - // even though the linked worktree is rebasing, we still associate it with the branch - Contains("newbranch (worktree)"), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/bare_repo.go b/pkg/integration/tests/worktree/bare_repo.go deleted file mode 100644 index cfcaa028020..00000000000 --- a/pkg/integration/tests/worktree/bare_repo.go +++ /dev/null @@ -1,104 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var BareRepo = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Open lazygit in the worktree of a bare repo and do a rebase/bisect", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - // we're going to have a directory structure like this: - // project - // - .bare - // - repo (a worktree) - // - worktree2 (another worktree) - // - // The first repo is called 'repo' because that's the - // directory that all lazygit tests start in - - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("blah", "blah") - shell.Commit("initial commit") - shell.EmptyCommit("commit two") - shell.EmptyCommit("commit three") - - shell.RunCommand([]string{"git", "clone", "--bare", ".", "../.bare"}) - - shell.DeleteFile(".git") - - shell.Chdir("..") - - // This is the dir we were just in (and the dir that lazygit starts in when the test runs) - // We're going to replace it with a worktree - shell.DeleteFile("repo") - - shell.RunCommand([]string{"git", "--git-dir", ".bare", "worktree", "add", "-b", "repo", "repo", "mybranch"}) - shell.RunCommand([]string{"git", "--git-dir", ".bare", "worktree", "add", "-b", "worktree2", "worktree2", "mybranch"}) - - shell.Chdir("repo") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("repo"), - Contains("mybranch"), - Contains("worktree2 (worktree)"), - ) - - // test that a rebase works fine - // (rebase uses the git dir of the worktree so we're confirming that it points - // to the right git dir) - t.Views().Commits(). - Focus(). - Lines( - Contains("commit three").IsSelected(), - Contains("commit two"), - Contains("initial commit"), - ). - Press(keys.Commits.MoveDownCommit). - Lines( - Contains("commit two"), - Contains("commit three").IsSelected(), - Contains("initial commit"), - ). - // test that bisect works fine (same logic as above) - NavigateToLine(Contains("commit two")). - Press(keys.Commits.ViewBisectOptions). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Bisect")). - Select(MatchesRegexp(`Mark .* as bad`)). - Confirm() - - t.Views().Information().Content(Contains("Bisecting")) - }). - NavigateToLine(Contains("initial commit")). - Press(keys.Commits.ViewBisectOptions). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Bisect")). - Select(MatchesRegexp(`Mark .* as good`)). - Confirm() - - t.Views().Information().Content(Contains("Bisecting")) - }) - - // switch to other worktree - t.Views().Worktrees(). - Focus(). - Lines( - Contains("repo").IsSelected(), - Contains("worktree2"), - ). - NavigateToLine(Contains("worktree2")). - Press(keys.Universal.Select). - Lines( - Contains("worktree2").IsSelected(), - Contains("repo"), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/bare_repo_worktree_config.go b/pkg/integration/tests/worktree/bare_repo_worktree_config.go deleted file mode 100644 index c66aa507610..00000000000 --- a/pkg/integration/tests/worktree/bare_repo_worktree_config.go +++ /dev/null @@ -1,92 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// This case is identical to dotfile_bare_repo.go, except -// that it invokes lazygit with $GIT_DIR set but not -// $GIT_WORK_TREE. Instead, the repo uses the core.worktree -// config to identify the main worktree. - -var BareRepoWorktreeConfig = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Open lazygit in the worktree of a vcsh-style bare repo and add a file and commit", - ExtraCmdArgs: []string{"--git-dir={{.actualPath}}/.bare"}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.ShowFileTree = false - }, - SetupRepo: func(shell *Shell) { - // we're going to have a directory structure like this: - // project - // - .bare - // - . (a worktree at the same path as .bare) - // - // - // 'repo' is the repository/directory that all lazygit tests start in - - shell.CreateFileAndAdd("a/b/c/blah", "blah\n") - shell.Commit("initial commit") - - shell.CreateFileAndAdd(".gitignore", ".bare/\n/repo\n") - shell.Commit("add .gitignore") - - shell.Chdir("..") - - // configure this "fake bare"" repo using the vcsh convention - // of core.bare=false and core.worktree set to the actual - // worktree path (a homedir root). This allows $GIT_DIR - // alone to make this repo "self worktree identifying" - shell.RunCommand([]string{"git", "--git-dir=./.bare", "init", "--shared=false"}) - shell.RunCommand([]string{"git", "--git-dir=./.bare", "config", "core.bare", "false"}) - shell.RunCommand([]string{"git", "--git-dir=./.bare", "config", "core.worktree", ".."}) - shell.RunCommand([]string{"git", "--git-dir=./.bare", "remote", "add", "origin", "./repo"}) - shell.RunCommand([]string{"git", "--git-dir=./.bare", "checkout", "-b", "main"}) - shell.RunCommand([]string{"git", "--git-dir=./.bare", "config", "branch.main.remote", "origin"}) - shell.RunCommand([]string{"git", "--git-dir=./.bare", "config", "branch.main.merge", "refs/heads/master"}) - shell.RunCommand([]string{"git", "--git-dir=./.bare", "fetch", "origin", "master"}) - shell.RunCommand([]string{"git", "--git-dir=./.bare", "-c", "merge.ff=true", "merge", "origin/master"}) - - // we no longer need the original repo so remove it - shell.DeleteFile("repo") - - shell.UpdateFile("a/b/c/blah", "updated content\n") - shell.Chdir("a/b/c") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("main"), - ) - - t.Views().Commits(). - Lines( - Contains("add .gitignore"), - Contains("initial commit"), - ) - - t.Views().Files(). - IsFocused(). - Lines( - Contains(" M a/b/c/blah"), // shows as modified - ). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - Type("Add blah"). - Confirm() - - t.Views().Files(). - IsEmpty() - - t.Views().Commits(). - Lines( - Contains("Add blah"), - Contains("add .gitignore"), - Contains("initial commit"), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/crud.go b/pkg/integration/tests/worktree/crud.go deleted file mode 100644 index 504d47b726a..00000000000 --- a/pkg/integration/tests/worktree/crud.go +++ /dev/null @@ -1,120 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var Crud = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "From the worktrees view, add a work tree, switch to it, switch back, and remove it", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("mybranch"), - ) - - t.Views().Status(). - Lines( - Contains("repo → mybranch"), - ) - - t.Views().Worktrees(). - Focus(). - Lines( - Contains("repo (main)"), - ). - Press(keys.Universal.New). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Worktree")). - Select(Contains(`Create worktree from ref`).DoesNotContain(("detached"))). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New worktree base ref")). - InitialText(Equals("mybranch")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New worktree path")). - Type("../linked-worktree"). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New branch name (leave blank to checkout mybranch)")). - Type("newbranch"). - Confirm() - }). - Lines( - Contains("linked-worktree").IsSelected(), - Contains("repo (main)"), - ). - // confirm we're still in the same view - IsFocused() - - // status panel includes the worktree if it's a linked worktree - t.Views().Status(). - Lines( - Contains("repo(linked-worktree) → newbranch"), - ) - - t.Views().Branches(). - Lines( - Contains("newbranch"), - Contains("mybranch"), - ) - - t.Views().Worktrees(). - // confirm we can't remove the current worktree - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Equals("You cannot remove the current worktree!")). - Confirm() - }). - // confirm we cannot remove the main worktree - NavigateToLine(Contains("repo (main)")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Alert(). - Title(Equals("Error")). - Content(Equals("You cannot remove the main worktree!")). - Confirm() - }). - // switch back to main worktree - Press(keys.Universal.Select). - Lines( - Contains("repo (main)").IsSelected(), - Contains("linked-worktree"), - ) - - t.Views().Branches(). - Lines( - Contains("mybranch"), - Contains("newbranch"), - ) - - t.Views().Worktrees(). - // remove linked worktree - NavigateToLine(Contains("linked-worktree")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Remove worktree")). - Content(Contains("Are you sure you want to remove worktree 'linked-worktree'?")). - Confirm() - }). - Lines( - Contains("repo (main)").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/custom_command.go b/pkg/integration/tests/worktree/custom_command.go deleted file mode 100644 index c10ed674966..00000000000 --- a/pkg/integration/tests/worktree/custom_command.go +++ /dev/null @@ -1,40 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var CustomCommand = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that custom commands work with worktrees by deleting a worktree via a custom command", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(cfg *config.AppConfig) { - cfg.GetUserConfig().CustomCommands = []config.CustomCommand{ - { - Key: "d", - Context: "worktrees", - Command: "git worktree remove {{ .SelectedWorktree.Path | quote }}", - }, - } - }, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - shell.AddWorktree("mybranch", "../linked-worktree", "newbranch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Worktrees(). - Focus(). - Lines( - Contains("repo (main)"), - Contains("linked-worktree"), - ). - NavigateToLine(Contains("linked-worktree")). - Press("d"). - Lines( - Contains("repo (main)"), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/detach_worktree_from_branch.go b/pkg/integration/tests/worktree/detach_worktree_from_branch.go deleted file mode 100644 index f6724ef3a8e..00000000000 --- a/pkg/integration/tests/worktree/detach_worktree_from_branch.go +++ /dev/null @@ -1,55 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var DetachWorktreeFromBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Detach a worktree from the branches view", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - shell.EmptyCommit("commit 2") - shell.EmptyCommit("commit 3") - shell.AddWorktree("mybranch", "../linked-worktree", "newbranch") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("mybranch").IsSelected(), - Contains("newbranch (worktree)"), - ). - NavigateToLine(Contains("newbranch")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'newbranch'?")). - Select(Contains("Delete local branch")). - Confirm() - }). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Branch newbranch is checked out by worktree linked-worktree")). - Select(Equals("Detach worktree")). - Confirm() - }). - Lines( - Contains("mybranch"), - Contains("newbranch").DoesNotContain("(worktree)").IsSelected(), - ) - - t.Views().Worktrees(). - Focus(). - Lines( - Contains("repo (main)").IsSelected(), - Contains("linked-worktree"), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/dotfile_bare_repo.go b/pkg/integration/tests/worktree/dotfile_bare_repo.go deleted file mode 100644 index 7b8f68af1af..00000000000 --- a/pkg/integration/tests/worktree/dotfile_bare_repo.go +++ /dev/null @@ -1,72 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// Can't think of a better name than 'dotfile' repo: I'm using that -// because that's the case we're typically dealing with. - -var DotfileBareRepo = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Open lazygit in the worktree of a dotfile bare repo and add a file and commit", - ExtraCmdArgs: []string{"--git-dir={{.actualPath}}/.bare", "--work-tree={{.actualPath}}/repo"}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - // we're going to have a directory structure like this: - // project - // - .bare - // - repo (the worktree) - // - // The first repo is called 'repo' because that's the - // directory that all lazygit tests start in - - // Delete the .git dir that all tests start with by default - shell.DeleteFile(".git") - - // Create a bare repo in the parent directory - shell.RunCommand([]string{"git", "init", "--bare", "../.bare"}) - shell.RunCommand([]string{"git", "--git-dir=../.bare", "--work-tree=.", "checkout", "-b", "mybranch"}) - shell.CreateFile("blah", "original content\n") - - // Add a file and commit - shell.RunCommand([]string{"git", "--git-dir=../.bare", "--work-tree=.", "add", "blah"}) - shell.RunCommand([]string{"git", "--git-dir=../.bare", "--work-tree=.", "commit", "-m", "initial commit"}) - - shell.UpdateFile("blah", "updated content\n") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("mybranch"), - ) - - t.Views().Commits(). - Lines( - Contains("initial commit"), - ) - - t.Views().Files(). - IsFocused(). - Lines( - Contains(" M blah"), // shows as modified - ). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - Type("Add blah"). - Confirm() - - t.Views().Files(). - IsEmpty() - - t.Views().Commits(). - Lines( - Contains("Add blah"), - Contains("initial commit"), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/double_nested_linked_submodule.go b/pkg/integration/tests/worktree/double_nested_linked_submodule.go deleted file mode 100644 index aa49f058cf9..00000000000 --- a/pkg/integration/tests/worktree/double_nested_linked_submodule.go +++ /dev/null @@ -1,93 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// Even though this involves submodules, it's a worktree test since -// it's really exercising lazygit's ability to correctly do pathfinding -// in a complex use case. -var DoubleNestedLinkedSubmodule = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Open lazygit in a link to a repo's double nested submodules", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.ShowFileTree = false - }, - SetupRepo: func(shell *Shell) { - // we're going to have a directory structure like this: - // project - // - repo/outerSubmodule/innerSubmodule/a/b/c - // - link (symlink to repo/outerSubmodule/innerSubmodule/a/b/c) - // - shell.CreateFileAndAdd("rootFile", "rootStuff") - shell.Commit("initial repo commit") - - shell.Chdir("..") - shell.CreateDir("innerSubmodule") - shell.Chdir("innerSubmodule") - shell.Init() - shell.CreateFileAndAdd("a/b/c/blah", "blah\n") - shell.Commit("initial inner commit") - - shell.Chdir("..") - shell.CreateDir("outerSubmodule") - shell.Chdir("outerSubmodule") - shell.Init() - shell.CreateFileAndAdd("foo", "foo") - shell.Commit("initial outer commit") - // the git config (-c) parameter below is required - // to let git create a file-protocol/path submodule - shell.RunCommand([]string{"git", "-c", "protocol.file.allow=always", "submodule", "add", "../innerSubmodule"}) - shell.Commit("add dependency as innerSubmodule") - - shell.Chdir("../repo") - shell.RunCommand([]string{"git", "-c", "protocol.file.allow=always", "submodule", "add", "../outerSubmodule"}) - shell.Commit("add dependency as outerSubmodule") - shell.Chdir("outerSubmodule") - shell.RunCommand([]string{"git", "-c", "protocol.file.allow=always", "submodule", "update", "--init", "--recursive"}) - - shell.Chdir("innerSubmodule") - shell.UpdateFile("a/b/c/blah", "updated content\n") - - shell.Chdir("../../..") - shell.RunCommand([]string{"ln", "-s", "repo/outerSubmodule/innerSubmodule/a/b/c", "link"}) - - shell.Chdir("link") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("HEAD detached"), - Contains("master"), - ) - - t.Views().Commits(). - Lines( - Contains("initial inner commit"), - ) - - t.Views().Files(). - IsFocused(). - Lines( - Contains(" M a/b/c/blah"), // shows as modified - ). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - Type("Update blah"). - Confirm() - - t.Views().Files(). - IsEmpty() - - t.Views().Commits(). - Lines( - Contains("Update blah"), - Contains("initial inner commit"), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/exclude_file_in_worktree.go b/pkg/integration/tests/worktree/exclude_file_in_worktree.go deleted file mode 100644 index ba3f9e5a5f4..00000000000 --- a/pkg/integration/tests/worktree/exclude_file_in_worktree.go +++ /dev/null @@ -1,41 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ExcludeFileInWorktree = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Add a file to .git/info/exclude in a worktree", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.EmptyCommit("commit1") - shell.AddWorktree("HEAD", "../linked-worktree", "mybranch") - shell.CreateFile("../linked-worktree/toExclude", "") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Worktrees(). - Focus(). - Lines( - Contains("repo (main)").IsSelected(), - Contains("linked-worktree"), - ). - SelectNextItem(). - PressPrimaryAction() - - t.Views().Files(). - Focus(). - Lines( - Contains("toExclude"), - ). - Press(keys.Files.IgnoreFile). - Tap(func() { - t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .git/info/exclude")).Confirm() - }). - IsEmpty() - - t.FileSystem().FileContent("../repo/.git/info/exclude", Contains("toExclude")) - }, -}) diff --git a/pkg/integration/tests/worktree/fast_forward_worktree_branch.go b/pkg/integration/tests/worktree/fast_forward_worktree_branch.go deleted file mode 100644 index 6c382a867f7..00000000000 --- a/pkg/integration/tests/worktree/fast_forward_worktree_branch.go +++ /dev/null @@ -1,52 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FastForwardWorktreeBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Fast-forward a linked worktree branch from another worktree", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - // both main and linked worktree will have changed to fast-forward - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - shell.EmptyCommit("two") - shell.EmptyCommit("three") - shell.NewBranch("newbranch") - - shell.CloneIntoRemote("origin") - shell.SetBranchUpstream("mybranch", "origin/mybranch") - shell.SetBranchUpstream("newbranch", "origin/newbranch") - - // remove the 'three' commit so that we have something to pull from the remote - shell.HardReset("HEAD^") - shell.Checkout("mybranch") - shell.HardReset("HEAD^") - - shell.AddWorktreeCheckout("newbranch", "../linked-worktree") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("mybranch").Contains("↓1").IsSelected(), - Contains("newbranch (worktree)").Contains("↓1"), - ). - Press(keys.Branches.FastForward). - Lines( - Contains("mybranch").Contains("✓").IsSelected(), - Contains("newbranch (worktree)").Contains("↓1"), - ). - NavigateToLine(Contains("newbranch (worktree)")). - Press(keys.Branches.FastForward). - Lines( - Contains("mybranch").Contains("✓"), - Contains("newbranch (worktree)").Contains("✓").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/fast_forward_worktree_branch_should_not_pollute_current_worktree.go b/pkg/integration/tests/worktree/fast_forward_worktree_branch_should_not_pollute_current_worktree.go deleted file mode 100644 index cbe1ed94394..00000000000 --- a/pkg/integration/tests/worktree/fast_forward_worktree_branch_should_not_pollute_current_worktree.go +++ /dev/null @@ -1,59 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var FastForwardWorktreeBranchShouldNotPolluteCurrentWorktree = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Fast-forward a linked worktree branch from another worktree", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - // both main and linked worktree will have changed to fast-forward - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - shell.EmptyCommit("two") - shell.EmptyCommit("three") - shell.NewBranch("newbranch") - - shell.CloneIntoRemote("origin") - shell.SetBranchUpstream("mybranch", "origin/mybranch") - shell.SetBranchUpstream("newbranch", "origin/newbranch") - - // remove the 'three' commit so that we have something to pull from the remote - shell.HardReset("HEAD^") - shell.Checkout("mybranch") - shell.HardReset("HEAD^") - - shell.AddWorktreeCheckout("newbranch", "../linked-worktree") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("mybranch").Contains("↓1").IsSelected(), - Contains("newbranch (worktree)").Contains("↓1"), - ). - Press(keys.Branches.FastForward). - Lines( - Contains("mybranch").Contains("✓").IsSelected(), - Contains("newbranch (worktree)").Contains("↓1"), - ). - NavigateToLine(Contains("newbranch (worktree)")). - Press(keys.Branches.FastForward). - Lines( - Contains("mybranch").Contains("✓"), - Contains("newbranch (worktree)").Contains("✓").IsSelected(), - ). - NavigateToLine(Contains("mybranch")) - - // check the current worktree that it has no lines in the File changes pane - t.Views().Files(). - Focus(). - Press(keys.Files.RefreshFiles). - LineCount(EqualsInt(0)) - }, -}) diff --git a/pkg/integration/tests/worktree/force_remove_worktree.go b/pkg/integration/tests/worktree/force_remove_worktree.go deleted file mode 100644 index e716b4e2ea6..00000000000 --- a/pkg/integration/tests/worktree/force_remove_worktree.go +++ /dev/null @@ -1,46 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ForceRemoveWorktree = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Force remove a dirty worktree", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - shell.EmptyCommit("commit 2") - shell.EmptyCommit("commit 3") - shell.AddWorktree("mybranch", "../linked-worktree", "newbranch") - shell.AddFileInWorktreeOrSubmodule("../linked-worktree", "file", "content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Worktrees(). - Focus(). - Lines( - Contains("repo (main)").IsSelected(), - Contains("linked-worktree"), - ). - NavigateToLine(Contains("linked-worktree")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Remove worktree")). - Content(Equals("Are you sure you want to remove worktree 'linked-worktree'?")). - Confirm() - - t.ExpectPopup().Confirmation(). - Title(Equals("Remove worktree")). - Content(Equals("'linked-worktree' contains modified or untracked files, or submodules (or all of these). Are you sure you want to remove it?")). - Confirm() - }). - Lines( - Contains("repo (main)").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/force_remove_worktree_with_submodules.go b/pkg/integration/tests/worktree/force_remove_worktree_with_submodules.go deleted file mode 100644 index dd54eb97c2a..00000000000 --- a/pkg/integration/tests/worktree/force_remove_worktree_with_submodules.go +++ /dev/null @@ -1,46 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var ForceRemoveWorktreeWithSubmodules = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Force remove a worktree that contains submodules", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - shell.CloneIntoSubmodule("submodule", "submodule") - shell.Commit("Add submodule") - shell.AddWorktree("mybranch", "../linked-worktree", "newbranch") - shell.RunCommand([]string{"git", "-C", "../linked-worktree", "submodule", "update", "--init"}) - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Worktrees(). - Focus(). - Lines( - Contains("repo (main)").IsSelected(), - Contains("linked-worktree"), - ). - NavigateToLine(Contains("linked-worktree")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Confirmation(). - Title(Equals("Remove worktree")). - Content(Equals("Are you sure you want to remove worktree 'linked-worktree'?")). - Confirm() - - t.ExpectPopup().Confirmation(). - Title(Equals("Remove worktree")). - Content(Equals("'linked-worktree' contains modified or untracked files, or submodules (or all of these). Are you sure you want to remove it?")). - Confirm() - }). - Lines( - Contains("repo (main)").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/remove_worktree_from_branch.go b/pkg/integration/tests/worktree/remove_worktree_from_branch.go deleted file mode 100644 index b1e8cc1cec8..00000000000 --- a/pkg/integration/tests/worktree/remove_worktree_from_branch.go +++ /dev/null @@ -1,65 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var RemoveWorktreeFromBranch = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Remove a worktree from the branches view", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - shell.EmptyCommit("commit 2") - shell.EmptyCommit("commit 3") - shell.AddWorktree("mybranch", "../linked-worktree", "newbranch") - shell.AddFileInWorktreeOrSubmodule("../linked-worktree", "file", "content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Focus(). - Lines( - Contains("mybranch").IsSelected(), - Contains("newbranch (worktree)"), - ). - NavigateToLine(Contains("newbranch")). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup(). - Menu(). - Title(Equals("Delete branch 'newbranch'?")). - Select(Contains("Delete local branch")). - Confirm() - }). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Branch newbranch is checked out by worktree linked-worktree")). - Select(Equals("Remove worktree")). - Confirm() - - t.ExpectPopup().Confirmation(). - Title(Equals("Remove worktree")). - Content(Equals("Are you sure you want to remove worktree 'linked-worktree'?")). - Confirm() - - t.ExpectPopup().Confirmation(). - Title(Equals("Remove worktree")). - Content(Equals("'linked-worktree' contains modified or untracked files, or submodules (or all of these). Are you sure you want to remove it?")). - Confirm() - }). - Lines( - Contains("mybranch"), - Contains("newbranch").DoesNotContain("(worktree)").IsSelected(), - ) - - t.Views().Worktrees(). - Focus(). - Lines( - Contains("repo (main)").IsSelected(), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/reset_window_tabs.go b/pkg/integration/tests/worktree/reset_window_tabs.go deleted file mode 100644 index 12c4526eb77..00000000000 --- a/pkg/integration/tests/worktree/reset_window_tabs.go +++ /dev/null @@ -1,52 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -// This is verifying logic that is subject to change (we're just doing the easiest approach) -// There are two other UX flows we could have: -// 1) associate window tab states with the repo, so that when you switch back to a repo you get the same window tab states -// 2) retain the same window tab states when switching repos -// Option 1 is straightforward, but option 2 is harder because you'd need to deactivate any views containing dependent -// content e.g. the sub-commits view. - -var ResetWindowTabs = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Verify that window tabs are reset whenever switching repos", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - shell.EmptyCommit("commit 2") - shell.EmptyCommit("commit 3") - shell.AddWorktree("mybranch", "../linked-worktree", "newbranch") - shell.AddFileInWorktreeOrSubmodule("../linked-worktree", "file", "content") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - // focus the remotes tab i.e. the second tab in the branches window - t.Views().Remotes(). - Focus() - - t.Views().Worktrees(). - Focus(). - Lines( - Contains("repo (main)").IsSelected(), - Contains("linked-worktree"), - ). - NavigateToLine(Contains("linked-worktree")). - Press(keys.Universal.Select). - Lines( - Contains("linked-worktree").IsSelected(), - Contains("repo (main)"), - ). - // navigate back to the branches window - Press(keys.Universal.NextBlock) - - t.Views().Branches(). - IsFocused() - }, -}) diff --git a/pkg/integration/tests/worktree/symlink_into_repo_subdir.go b/pkg/integration/tests/worktree/symlink_into_repo_subdir.go deleted file mode 100644 index 66bc28a1ec2..00000000000 --- a/pkg/integration/tests/worktree/symlink_into_repo_subdir.go +++ /dev/null @@ -1,63 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var SymlinkIntoRepoSubdir = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Open lazygit in a symlink into a repo's subdirectory", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) { - config.GetUserConfig().Gui.ShowFileTree = false - }, - SetupRepo: func(shell *Shell) { - // we're going to have a directory structure like this: - // project - // - repo/a/b/c (main worktree with subdirs) - // - link (symlink to repo/a/b/c) - // - shell.CreateFileAndAdd("a/b/c/blah", "blah\n") - shell.Commit("initial commit") - shell.UpdateFile("a/b/c/blah", "updated content\n") - - shell.Chdir("..") - shell.RunCommand([]string{"ln", "-s", "repo/a/b/c", "link"}) - - shell.Chdir("link") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("master"), - ) - - t.Views().Commits(). - Lines( - Contains("initial commit"), - ) - - t.Views().Files(). - IsFocused(). - Lines( - Contains(" M a/b/c/blah"), // shows as modified - ). - PressPrimaryAction(). - Press(keys.Files.CommitChanges) - - t.ExpectPopup().CommitMessagePanel(). - Title(Equals("Commit summary")). - Type("Add blah"). - Confirm() - - t.Views().Files(). - IsEmpty() - - t.Views().Commits(). - Lines( - Contains("Add blah"), - Contains("initial commit"), - ) - }, -}) diff --git a/pkg/integration/tests/worktree/worktree_in_repo.go b/pkg/integration/tests/worktree/worktree_in_repo.go deleted file mode 100644 index f91dc13cc89..00000000000 --- a/pkg/integration/tests/worktree/worktree_in_repo.go +++ /dev/null @@ -1,85 +0,0 @@ -package worktree - -import ( - "github.com/jesseduffield/lazygit/pkg/config" - . "github.com/jesseduffield/lazygit/pkg/integration/components" -) - -var WorktreeInRepo = NewIntegrationTest(NewIntegrationTestArgs{ - Description: "Add a worktree inside the repo, then remove the directory and confirm the worktree is removed", - ExtraCmdArgs: []string{}, - Skip: false, - SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell *Shell) { - shell.NewBranch("mybranch") - shell.CreateFileAndAdd("README.md", "hello world") - shell.Commit("initial commit") - }, - Run: func(t *TestDriver, keys config.KeybindingConfig) { - t.Views().Branches(). - Lines( - Contains("mybranch"), - ) - - t.Views().Worktrees(). - Focus(). - Lines( - Contains("repo (main)"), - ). - Press(keys.Universal.New). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Worktree")). - Select(Contains(`Create worktree from ref`).DoesNotContain(("detached"))). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New worktree base ref")). - InitialText(Equals("mybranch")). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New worktree path")). - Type("linked-worktree"). - Confirm() - - t.ExpectPopup().Prompt(). - Title(Equals("New branch name (leave blank to checkout mybranch)")). - Type("newbranch"). - Confirm() - }). - Lines( - Contains("linked-worktree").IsSelected(), - Contains("repo (main)"), - ). - // switch back to main worktree - NavigateToLine(Contains("repo (main)")). - Press(keys.Universal.Select). - Lines( - Contains("repo (main)").IsSelected(), - Contains("linked-worktree"), - ) - - t.Views().Files(). - Focus(). - Lines( - Contains("linked-worktree"), - ). - Press(keys.Universal.Remove). - Tap(func() { - t.ExpectPopup().Menu(). - Title(Equals("Discard changes")). - Select(Contains("Discard all changes")). - Confirm() - }). - IsEmpty() - - // confirm worktree appears as missing - t.Views().Worktrees(). - Focus(). - Lines( - Contains("repo (main)").IsSelected(), - Contains("linked-worktree (missing)"), - ) - }, -}) diff --git a/pkg/integration/types/types.go b/pkg/integration/types/types.go deleted file mode 100644 index 9c4f057d2d9..00000000000 --- a/pkg/integration/types/types.go +++ /dev/null @@ -1,50 +0,0 @@ -package types - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -// these interfaces are used by the gui package so that it knows what it needs -// to provide to a test in order for the test to run. - -type IntegrationTest interface { - Run(GuiDriver) - SetupConfig(config *config.AppConfig) - RequiresHeadless() bool - // width and height when running headless - HeadlessDimensions() (int, int) - // If true, we are recording/replaying a demo - IsDemo() bool -} - -// this is the interface through which our integration tests interact with the lazygit gui -type GuiDriver interface { - PressKey(string) - Click(int, int) - Keys() config.KeybindingConfig - CurrentContext() types.Context - ContextForView(viewName string) types.Context - Fail(message string) - // These two log methods are for the sake of debugging while testing. There's no need to actually - // commit any logging. - // logs to the normal place that you log to i.e. viewable with `lazygit --logs` - Log(message string) - // logs in the actual UI (in the commands panel) - LogUI(message string) - CheckedOutRef() *models.Branch - // the view that appears to the right of the side panel - MainView() *gocui.View - // the other view that sometimes appears to the right of the side panel - // e.g. when we're showing both staged and unstaged changes - SecondaryView() *gocui.View - View(viewName string) *gocui.View - SetCaption(caption string) - SetCaptionPrefix(prefix string) - // Pop the next toast that was displayed; returns nil if there was none - NextToast() *string - CheckAllToastsAcknowledged() - Headless() bool -} diff --git a/pkg/jsonschema/generate.go b/pkg/jsonschema/generate.go deleted file mode 100644 index 5350ca46cc9..00000000000 --- a/pkg/jsonschema/generate.go +++ /dev/null @@ -1,168 +0,0 @@ -//go:generate go run generator.go - -package jsonschema - -import ( - "encoding/json" - "fmt" - "os" - "reflect" - "strings" - - "github.com/jesseduffield/lazycore/pkg/utils" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/karimkhaleel/jsonschema" - "github.com/samber/lo" -) - -func GetSchemaDir() string { - return utils.GetLazyRootDirectory() + "/schema" -} - -func GenerateSchema() *jsonschema.Schema { - schema := customReflect(&config.UserConfig{}) - obj, _ := json.MarshalIndent(schema, "", " ") - obj = append(obj, '\n') - - if err := os.WriteFile(GetSchemaDir()+"/config.json", obj, 0o644); err != nil { - fmt.Println("Error writing to file:", err) - return nil - } - return schema -} - -func getSubSchema(rootSchema, parentSchema *jsonschema.Schema, key string) *jsonschema.Schema { - subSchema, found := parentSchema.Properties.Get(key) - if !found { - panic(fmt.Sprintf("Failed to find subSchema at %s on parent", key)) - } - - // This means the schema is defined on the rootSchema's Definitions - if subSchema.Ref != "" { - key, _ = strings.CutPrefix(subSchema.Ref, "#/$defs/") - refSchema, ok := rootSchema.Definitions[key] - if !ok { - panic(fmt.Sprintf("Failed to find #/$defs/%s", key)) - } - refSchema.Description = subSchema.Description - return refSchema - } - - return subSchema -} - -func customReflect(v *config.UserConfig) *jsonschema.Schema { - r := &jsonschema.Reflector{FieldNameTag: "yaml", RequiredFromJSONSchemaTags: true} - if err := r.AddGoComments("github.com/jesseduffield/lazygit/pkg/config", "../config"); err != nil { - panic(err) - } - filterOutDevComments(r) - schema := r.Reflect(v) - defaultConfig := config.GetDefaultConfig() - userConfigSchema := schema.Definitions["UserConfig"] - - defaultValue := reflect.ValueOf(defaultConfig).Elem() - - yamlToFieldNames := lo.Invert(userConfigSchema.OriginalPropertiesMapping) - - for pair := userConfigSchema.Properties.Oldest(); pair != nil; pair = pair.Next() { - yamlName := pair.Key - fieldName := yamlToFieldNames[yamlName] - - subSchema := getSubSchema(schema, userConfigSchema, yamlName) - - setDefaultVals(schema, subSchema, defaultValue.FieldByName(fieldName).Interface()) - } - - return schema -} - -func filterOutDevComments(r *jsonschema.Reflector) { - for k, v := range r.CommentMap { - commentLines := strings.Split(v, "\n") - filteredCommentLines := lo.Filter(commentLines, func(line string, _ int) bool { - return !strings.Contains(line, "[dev]") - }) - r.CommentMap[k] = strings.Join(filteredCommentLines, "\n") - } -} - -func setDefaultVals(rootSchema, schema *jsonschema.Schema, defaults any) { - t := reflect.TypeOf(defaults) - v := reflect.ValueOf(defaults) - - if t.Kind() == reflect.Ptr || t.Kind() == reflect.Interface { - t = t.Elem() - v = v.Elem() - } - - k := t.Kind() - _ = k - - switch t.Kind() { - case reflect.Bool: - schema.Default = v.Bool() - case reflect.Int: - schema.Default = v.Int() - case reflect.String: - schema.Default = v.String() - default: - // Do nothing - } - - if t.Kind() != reflect.Struct { - return - } - - for i := range t.NumField() { - value := v.Field(i).Interface() - parentKey := t.Field(i).Name - - key, ok := schema.OriginalPropertiesMapping[parentKey] - if !ok { - continue - } - - subSchema := getSubSchema(rootSchema, schema, key) - - if isStruct(value) { - setDefaultVals(rootSchema, subSchema, value) - } else if !isZeroValue(value) { - subSchema.Default = value - } - } -} - -func isZeroValue(v any) bool { - switch v := v.(type) { - case int, int32, int64, float32, float64: - return v == 0 - case string: - return v == "" - case bool: - return false - case nil: - return true - } - - rv := reflect.ValueOf(v) - switch rv.Kind() { - case reflect.Slice, reflect.Map: - return rv.Len() == 0 - case reflect.Ptr, reflect.Interface: - return rv.IsNil() - case reflect.Struct: - for i := range rv.NumField() { - if !isZeroValue(rv.Field(i).Interface()) { - return false - } - } - return true - default: - return false - } -} - -func isStruct(v any) bool { - return reflect.TypeOf(v).Kind() == reflect.Struct -} diff --git a/pkg/jsonschema/generate_config_docs.go b/pkg/jsonschema/generate_config_docs.go deleted file mode 100644 index f062f437f29..00000000000 --- a/pkg/jsonschema/generate_config_docs.go +++ /dev/null @@ -1,261 +0,0 @@ -package jsonschema - -import ( - "bytes" - "errors" - "fmt" - "os" - "strings" - - "github.com/jesseduffield/lazycore/pkg/utils" - "github.com/karimkhaleel/jsonschema" - "github.com/samber/lo" - - "gopkg.in/yaml.v3" -) - -type Node struct { - Name string - Description string - Default any - Children []*Node -} - -const ( - IndentLevel = 2 - DocumentationCommentStart = "\n" - DocumentationCommentEnd = "" - DocumentationCommentStartLen = len(DocumentationCommentStart) -) - -func insertBlankLines(buffer bytes.Buffer) bytes.Buffer { - lines := strings.Split(strings.TrimRight(buffer.String(), "\n"), "\n") - - var newBuffer bytes.Buffer - - previousIndent := -1 - wasComment := false - - for _, line := range lines { - trimmedLine := strings.TrimLeft(line, " ") - indent := len(line) - len(trimmedLine) - isComment := strings.HasPrefix(trimmedLine, "#") - if isComment && !wasComment && indent <= previousIndent { - newBuffer.WriteString("\n") - } - newBuffer.WriteString(line) - newBuffer.WriteString("\n") - previousIndent = indent - wasComment = isComment - } - - return newBuffer -} - -func prepareMarshalledConfig(buffer bytes.Buffer) []byte { - buffer = insertBlankLines(buffer) - - // Remove all `---` lines - lines := strings.Split(strings.TrimRight(buffer.String(), "\n"), "\n") - - var newBuffer bytes.Buffer - - for _, line := range lines { - if strings.TrimSpace(line) != "---" { - newBuffer.WriteString(line) - newBuffer.WriteString("\n") - } - } - - config := newBuffer.Bytes() - - // Add markdown yaml block tag - config = append([]byte("```yaml\n"), config...) - config = append(config, []byte("```\n")...) - - return config -} - -func wrapLine(line string, maxLineLength int) []string { - result := []string{} - startOfLine := 0 - lastSpaceIdx := -1 - for i, r := range line { - // Don't break on "See https://..." lines - if r == ' ' && line[startOfLine:i] != "See" { - lastSpaceIdx = i + 1 - } else if i-startOfLine >= maxLineLength && lastSpaceIdx != -1 { - result = append(result, line[startOfLine:lastSpaceIdx-1]) - startOfLine = lastSpaceIdx - lastSpaceIdx = -1 - } - } - result = append(result, line[startOfLine:]) - return result -} - -func setComment(yamlNode *yaml.Node, description string) { - lines := strings.Split(description, "\n") - wrappedLines := lo.Flatten(lo.Map(lines, - func(line string, _ int) []string { return wrapLine(line, 78) })) - - // Workaround for the way yaml formats the HeadComment if it contains - // blank lines: it renders these without a leading "#", but we want a - // leading "#" even on blank lines. However, yaml respects it if the - // HeadComment already contains a leading "#", so we prefix all lines - // (including blank ones) with "#". - yamlNode.HeadComment = strings.Join( - lo.Map(wrappedLines, func(s string, _ int) string { - if s == "" { - return "#" // avoid trailing space on blank lines - } - return "# " + s - }), - "\n") -} - -func (n *Node) MarshalYAML() (interface{}, error) { - node := yaml.Node{ - Kind: yaml.MappingNode, - } - - keyNode := yaml.Node{ - Kind: yaml.ScalarNode, - Value: n.Name, - } - if n.Description != "" { - setComment(&keyNode, n.Description) - } - - if len(n.Children) > 0 { - childrenNode := yaml.Node{ - Kind: yaml.MappingNode, - } - for _, child := range n.Children { - childYaml, err := child.MarshalYAML() - if err != nil { - return nil, err - } - - childKey := yaml.Node{ - Kind: yaml.ScalarNode, - Value: child.Name, - } - if child.Description != "" { - setComment(&childKey, child.Description) - } - childYaml = childYaml.(*yaml.Node) - childrenNode.Content = append(childrenNode.Content, childYaml.(*yaml.Node).Content...) - } - node.Content = append(node.Content, &keyNode, &childrenNode) - } else { - valueNode := yaml.Node{ - Kind: yaml.ScalarNode, - } - err := valueNode.Encode(n.Default) - if err != nil { - return nil, err - } - node.Content = append(node.Content, &keyNode, &valueNode) - } - - return &node, nil -} - -func writeToConfigDocs(config []byte) error { - configPath := utils.GetLazyRootDirectory() + "/docs/Config.md" - markdown, err := os.ReadFile(configPath) - if err != nil { - return fmt.Errorf("Error reading Config.md file %w", err) - } - - startConfigSectionIndex := bytes.Index(markdown, []byte(DocumentationCommentStart)) - if startConfigSectionIndex == -1 { - return errors.New("Default config starting comment not found") - } - - endConfigSectionIndex := bytes.Index(markdown[startConfigSectionIndex+DocumentationCommentStartLen:], []byte(DocumentationCommentEnd)) - if endConfigSectionIndex == -1 { - return errors.New("Default config closing comment not found") - } - - endConfigSectionIndex = endConfigSectionIndex + startConfigSectionIndex + DocumentationCommentStartLen - - newMarkdown := make([]byte, 0, len(markdown)-endConfigSectionIndex+startConfigSectionIndex+len(config)) - newMarkdown = append(newMarkdown, markdown[:startConfigSectionIndex+DocumentationCommentStartLen]...) - newMarkdown = append(newMarkdown, config...) - newMarkdown = append(newMarkdown, markdown[endConfigSectionIndex:]...) - - if err := os.WriteFile(configPath, newMarkdown, 0o644); err != nil { - return fmt.Errorf("Error writing to file %w", err) - } - return nil -} - -func GenerateConfigDocs(schema *jsonschema.Schema) { - rootNode := &Node{ - Children: make([]*Node, 0), - } - - recurseOverSchema(schema, schema.Definitions["UserConfig"], rootNode) - - var buffer bytes.Buffer - encoder := yaml.NewEncoder(&buffer) - encoder.SetIndent(IndentLevel) - - for _, child := range rootNode.Children { - err := encoder.Encode(child) - if err != nil { - panic("Failed to Marshal document") - } - } - encoder.Close() - - config := prepareMarshalledConfig(buffer) - - err := writeToConfigDocs(config) - if err != nil { - panic(err) - } -} - -func recurseOverSchema(rootSchema, schema *jsonschema.Schema, parent *Node) { - if schema == nil || schema.Properties == nil || schema.Properties.Len() == 0 { - return - } - - for pair := schema.Properties.Oldest(); pair != nil; pair = pair.Next() { - subSchema := getSubSchema(rootSchema, schema, pair.Key) - - if strings.Contains(strings.ToLower(subSchema.Description), "deprecated") { - continue - } - - node := Node{ - Name: pair.Key, - Description: subSchema.Description, - Default: getZeroValue(subSchema.Default, subSchema.Type), - } - parent.Children = append(parent.Children, &node) - recurseOverSchema(rootSchema, subSchema, &node) - } -} - -func getZeroValue(val any, t string) any { - if !isZeroValue(val) { - return val - } - - switch t { - case "string": - return "" - case "boolean": - return false - case "object": - return map[string]any{} - case "array": - return []any{} - default: - return nil - } -} diff --git a/pkg/jsonschema/generator.go b/pkg/jsonschema/generator.go deleted file mode 100644 index 33739fa9e47..00000000000 --- a/pkg/jsonschema/generator.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build ignore - -package main - -import ( - "fmt" - - "github.com/jesseduffield/lazygit/pkg/jsonschema" -) - -func main() { - fmt.Printf("Generating jsonschema in %s...\n", jsonschema.GetSchemaDir()) - schema := jsonschema.GenerateSchema() - jsonschema.GenerateConfigDocs(schema) -} diff --git a/pkg/logs/logs.go b/pkg/logs/logs.go deleted file mode 100644 index 7ec1b91b4aa..00000000000 --- a/pkg/logs/logs.go +++ /dev/null @@ -1,64 +0,0 @@ -package logs - -import ( - "io" - "log" - "os" - - "github.com/sirupsen/logrus" -) - -// It's important that this package does not depend on any other package because we -// may want to import it from anywhere, and we don't want to create a circular dependency -// (because Go refuses to compile circular dependencies). - -// Global is a global logger that can be used anywhere in the app, for -// _development purposes only_. I want to avoid global variables when possible, -// so if you want to log something that's printed when the -debug flag is set, -// you'll need to ensure the struct you're working with has a logger field ( -// and most of them do). -// Global is only available if the LAZYGIT_LOG_PATH environment variable is set. -var Global *logrus.Entry - -func init() { - logPath := os.Getenv("LAZYGIT_LOG_PATH") - if logPath != "" { - Global = NewDevelopmentLogger(logPath) - } -} - -func NewProductionLogger() *logrus.Entry { - logger := logrus.New() - logger.Out = io.Discard - logger.SetLevel(logrus.ErrorLevel) - return formatted(logger) -} - -func NewDevelopmentLogger(logPath string) *logrus.Entry { - logger := logrus.New() - logger.SetLevel(getLogLevel()) - - file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666) - if err != nil { - log.Fatalf("Unable to log to log file: %v", err) - } - logger.SetOutput(file) - return formatted(logger) -} - -func formatted(log *logrus.Logger) *logrus.Entry { - // highly recommended: tail -f development.log | humanlog - // https://github.com/aybabtme/humanlog - log.Formatter = &logrus.JSONFormatter{} - - return log.WithFields(logrus.Fields{}) -} - -func getLogLevel() logrus.Level { - strLevel := os.Getenv("LOG_LEVEL") - level, err := logrus.ParseLevel(strLevel) - if err != nil { - return logrus.DebugLevel - } - return level -} diff --git a/pkg/logs/tail/logs_default.go b/pkg/logs/tail/logs_default.go deleted file mode 100644 index 3daa6c4b14e..00000000000 --- a/pkg/logs/tail/logs_default.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build !windows - -package tail - -import ( - "log" - "os" - "os/exec" - - "github.com/aybabtme/humanlog" -) - -func tailLogsForPlatform(logFilePath string, opts *humanlog.HandlerOptions) { - cmd := exec.Command("tail", "-f", logFilePath) - - stdout, _ := cmd.StdoutPipe() - if err := cmd.Start(); err != nil { - log.Fatal(err) - } - - if err := humanlog.Scanner(stdout, os.Stdout, opts); err != nil { - log.Fatal(err) - } - - if err := cmd.Wait(); err != nil { - log.Fatal(err) - } - - os.Exit(0) -} diff --git a/pkg/logs/tail/logs_windows.go b/pkg/logs/tail/logs_windows.go deleted file mode 100644 index 3c45d70af53..00000000000 --- a/pkg/logs/tail/logs_windows.go +++ /dev/null @@ -1,70 +0,0 @@ -package tail - -import ( - "bufio" - "log" - "os" - "strings" - "time" - - "github.com/aybabtme/humanlog" -) - -func tailLogsForPlatform(logFilePath string, opts *humanlog.HandlerOptions) { - var lastModified int64 = 0 - var lastOffset int64 = 0 - for { - stat, err := os.Stat(logFilePath) - if err != nil { - log.Fatal(err) - } - if stat.ModTime().Unix() > lastModified { - err = tailFrom(lastOffset, logFilePath, opts) - if err != nil { - log.Fatal(err) - } - } - lastOffset = stat.Size() - time.Sleep(1 * time.Second) - } -} - -func openAndSeek(filepath string, offset int64) (*os.File, error) { - file, err := os.Open(filepath) - if err != nil { - return nil, err - } - - _, err = file.Seek(offset, 0) - if err != nil { - _ = file.Close() - return nil, err - } - return file, nil -} - -func tailFrom(lastOffset int64, logFilePath string, opts *humanlog.HandlerOptions) error { - file, err := openAndSeek(logFilePath, lastOffset) - if err != nil { - return err - } - - fileScanner := bufio.NewScanner(file) - var lines []string - for fileScanner.Scan() { - lines = append(lines, fileScanner.Text()) - } - file.Close() - lineCount := len(lines) - lastTen := lines - if lineCount > 10 { - lastTen = lines[lineCount-10:] - } - for _, line := range lastTen { - reader := strings.NewReader(line) - if err := humanlog.Scanner(reader, os.Stdout, opts); err != nil { - log.Fatal(err) - } - } - return nil -} diff --git a/pkg/logs/tail/tail.go b/pkg/logs/tail/tail.go deleted file mode 100644 index b21bc21e4ef..00000000000 --- a/pkg/logs/tail/tail.go +++ /dev/null @@ -1,28 +0,0 @@ -package tail - -import ( - "fmt" - "log" - "os" - - "github.com/aybabtme/humanlog" -) - -// TailLogs lets us run `lazygit --logs` to print the logs produced by other lazygit processes. -// This makes for easier debugging. -func TailLogs(logFilePath string) { - fmt.Printf("Tailing log file %s\n\n", logFilePath) - - opts := humanlog.DefaultOptions - opts.Truncates = false - - _, err := os.Stat(logFilePath) - if err != nil { - if os.IsNotExist(err) { - log.Fatal("Log file does not exist. Run `lazygit --debug` first to create the log file") - } - log.Fatal(err) - } - - tailLogsForPlatform(logFilePath, opts) -} diff --git a/pkg/snake/snake.go b/pkg/snake/snake.go deleted file mode 100644 index 62fc0ddfd2a..00000000000 --- a/pkg/snake/snake.go +++ /dev/null @@ -1,209 +0,0 @@ -package snake - -import ( - "math/rand" - "time" - - "github.com/samber/lo" -) - -type Game struct { - // width/height of the board - width int - height int - - // function for rendering the game. If alive is false, the cells are expected - // to be ignored. - render func(cells [][]CellType, alive bool) - - // closed when the game is exited - exit chan (struct{}) - - // channel for specifying the direction the player wants the snake to go in - setNewDir chan (Direction) - - // allows logging for debugging - logger func(string) - - // putting this on the struct for deterministic testing - randIntFn func(int) int -} - -type State struct { - // first element is the head, final element is the tail - snakePositions []Position - - foodPosition Position - - // direction of the snake - direction Direction - // direction as of the end of the last tick. We hold onto this so that - // the snake can't do a 180 turn inbetween ticks - lastTickDirection Direction -} - -type Position struct { - x int - y int -} - -type Direction int - -const ( - Up Direction = iota - Down - Left - Right -) - -type CellType int - -const ( - None CellType = iota - Snake - Food -) - -func NewGame(width, height int, render func(cells [][]CellType, alive bool), logger func(string)) *Game { - return &Game{ - width: width, - height: height, - render: render, - randIntFn: rand.Intn, - exit: make(chan struct{}), - logger: logger, - setNewDir: make(chan Direction), - } -} - -func (self *Game) Start() { - go self.gameLoop() -} - -func (self *Game) Exit() { - close(self.exit) -} - -func (self *Game) SetDirection(direction Direction) { - self.setNewDir <- direction -} - -func (self *Game) gameLoop() { - state := self.initializeState() - var alive bool - - self.render(self.getCells(state), true) - - ticker := time.NewTicker(time.Duration(75) * time.Millisecond) - - for { - select { - case <-self.exit: - return - case dir := <-self.setNewDir: - state.direction = self.newDirection(state, dir) - case <-ticker.C: - state, alive = self.tick(state) - self.render(self.getCells(state), alive) - if !alive { - return - } - } - } -} - -func (self *Game) initializeState() State { - centerOfScreen := Position{self.width / 2, self.height / 2} - snakePositions := []Position{centerOfScreen} - - state := State{ - snakePositions: snakePositions, - direction: Right, - foodPosition: self.newFoodPos(snakePositions), - } - - return state -} - -func (self *Game) newFoodPos(snakePositions []Position) Position { - // arbitrarily setting a limit of attempts to place food - attemptLimit := 1000 - - for range attemptLimit { - newFoodPos := Position{self.randIntFn(self.width), self.randIntFn(self.height)} - - if !lo.Contains(snakePositions, newFoodPos) { - return newFoodPos - } - } - - panic("SORRY, BUT I WAS TOO LAZY TO MAKE THE SNAKE GAME SMART ENOUGH TO PUT THE FOOD SOMEWHERE SENSIBLE NO MATTER WHAT, AND I ALSO WAS TOO LAZY TO ADD A WIN CONDITION") -} - -// returns whether the snake is alive -func (self *Game) tick(currentState State) (State, bool) { - nextState := currentState // copy by value - newHeadPos := nextState.snakePositions[0] - - nextState.lastTickDirection = nextState.direction - - switch nextState.direction { - case Up: - newHeadPos.y-- - case Down: - newHeadPos.y++ - case Left: - newHeadPos.x-- - case Right: - newHeadPos.x++ - } - - outOfBounds := newHeadPos.x < 0 || newHeadPos.x >= self.width || newHeadPos.y < 0 || newHeadPos.y >= self.height - eatingOwnTail := lo.Contains(nextState.snakePositions, newHeadPos) - - if outOfBounds || eatingOwnTail { - return State{}, false - } - - nextState.snakePositions = append([]Position{newHeadPos}, nextState.snakePositions...) - - if newHeadPos == nextState.foodPosition { - nextState.foodPosition = self.newFoodPos(nextState.snakePositions) - } else { - nextState.snakePositions = nextState.snakePositions[:len(nextState.snakePositions)-1] - } - - return nextState, true -} - -func (self *Game) getCells(state State) [][]CellType { - cells := make([][]CellType, self.height) - - setCell := func(pos Position, value CellType) { - cells[pos.y][pos.x] = value - } - - for i := range self.height { - cells[i] = make([]CellType, self.width) - } - - for _, pos := range state.snakePositions { - setCell(pos, Snake) - } - - setCell(state.foodPosition, Food) - - return cells -} - -func (self *Game) newDirection(state State, direction Direction) Direction { - // don't allow the snake to turn 180 degrees - if (state.lastTickDirection == Up && direction == Down) || - (state.lastTickDirection == Down && direction == Up) || - (state.lastTickDirection == Left && direction == Right) || - (state.lastTickDirection == Right && direction == Left) { - return state.direction - } - - return direction -} diff --git a/pkg/snake/snake_test.go b/pkg/snake/snake_test.go deleted file mode 100644 index 7a7ed038a0f..00000000000 --- a/pkg/snake/snake_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package snake - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSnake(t *testing.T) { - scenarios := []struct { - state State - expectedState State - expectedAlive bool - }{ - { - state: State{ - snakePositions: []Position{{x: 5, y: 5}}, - direction: Right, - lastTickDirection: Right, - foodPosition: Position{x: 9, y: 9}, - }, - expectedState: State{ - snakePositions: []Position{{x: 6, y: 5}}, - direction: Right, - lastTickDirection: Right, - foodPosition: Position{x: 9, y: 9}, - }, - expectedAlive: true, - }, - { - state: State{ - snakePositions: []Position{{x: 5, y: 5}, {x: 4, y: 5}, {x: 4, y: 4}, {x: 5, y: 4}}, - direction: Up, - lastTickDirection: Up, - foodPosition: Position{x: 9, y: 9}, - }, - expectedState: State{}, - expectedAlive: false, - }, - { - state: State{ - snakePositions: []Position{{x: 5, y: 5}}, - direction: Right, - lastTickDirection: Right, - foodPosition: Position{x: 6, y: 5}, - }, - expectedState: State{ - snakePositions: []Position{{x: 6, y: 5}, {x: 5, y: 5}}, - direction: Right, - lastTickDirection: Right, - foodPosition: Position{x: 8, y: 8}, - }, - expectedAlive: true, - }, - } - - for _, scenario := range scenarios { - game := NewGame(10, 10, nil, func(string) {}) - game.randIntFn = func(int) int { return 8 } - state, alive := game.tick(scenario.state) - assert.Equal(t, scenario.expectedAlive, alive) - assert.EqualValues(t, scenario.expectedState, state) - } -} diff --git a/pkg/tasks/async_handler.go b/pkg/tasks/async_handler.go deleted file mode 100644 index 658687af9c3..00000000000 --- a/pkg/tasks/async_handler.go +++ /dev/null @@ -1,58 +0,0 @@ -package tasks - -import ( - "github.com/jesseduffield/gocui" - "github.com/sasha-s/go-deadlock" -) - -// the purpose of an AsyncHandler is to ensure that if we have multiple long-running -// requests, we only handle the result of the latest one. For example, if I am -// searching for 'abc' and I have to type 'a' then 'b' then 'c' and each keypress -// dispatches a request to search for things with the string so-far, we'll be searching -// for 'a', 'ab', and 'abc', and it may be that 'abc' comes back first, then 'ab', -// then 'a' and we don't want to display the result for 'a' just because it came -// back last. AsyncHandler keeps track of the order in which things were dispatched -// so that we can ignore anything that comes back late. -type AsyncHandler struct { - currentId int - lastId int - mutex deadlock.Mutex - onReject func() - onWorker func(func(gocui.Task) error) -} - -func NewAsyncHandler(onWorker func(func(gocui.Task) error)) *AsyncHandler { - return &AsyncHandler{ - mutex: deadlock.Mutex{}, - onWorker: onWorker, - } -} - -func (self *AsyncHandler) Do(f func() func()) { - self.mutex.Lock() - self.currentId++ - id := self.currentId - self.mutex.Unlock() - - self.onWorker(func(gocui.Task) error { - after := f() - self.handle(after, id) - return nil - }) -} - -// f here is expected to be a function that doesn't take long to run -func (self *AsyncHandler) handle(f func(), id int) { - self.mutex.Lock() - defer self.mutex.Unlock() - - if id < self.lastId { - if self.onReject != nil { - self.onReject() - } - return - } - - self.lastId = id - f() -} diff --git a/pkg/tasks/async_handler_test.go b/pkg/tasks/async_handler_test.go deleted file mode 100644 index 6da363cd964..00000000000 --- a/pkg/tasks/async_handler_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package tasks - -import ( - "fmt" - "sync" - "testing" - - "github.com/jesseduffield/gocui" - "github.com/stretchr/testify/assert" -) - -func TestAsyncHandler(t *testing.T) { - wg := sync.WaitGroup{} - wg.Add(2) - - onWorker := func(f func(gocui.Task) error) { - go func() { _ = f(gocui.NewFakeTask()) }() - } - handler := NewAsyncHandler(onWorker) - handler.onReject = func() { - wg.Done() - } - - result := 0 - - wg2 := sync.WaitGroup{} - wg2.Add(1) - - handler.Do(func() func() { - wg2.Wait() - return func() { - fmt.Println("setting to 1") - result = 1 - } - }) - handler.Do(func() func() { - return func() { - fmt.Println("setting to 2") - result = 2 - wg.Done() - wg2.Done() - } - }) - - wg.Wait() - - assert.EqualValues(t, 2, result) -} diff --git a/pkg/tasks/tasks.go b/pkg/tasks/tasks.go deleted file mode 100644 index 5c5875fa879..00000000000 --- a/pkg/tasks/tasks.go +++ /dev/null @@ -1,431 +0,0 @@ -package tasks - -import ( - "bufio" - "fmt" - "io" - "os/exec" - "sync" - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/sasha-s/go-deadlock" - "github.com/sirupsen/logrus" -) - -// This file revolves around running commands that will be output to the main panel -// in the gui. If we're flicking through the commits panel, we want to invoke a -// `git show` command for each commit, but we don't want to read the entire output -// at once (because that would slow things down); we just want to fill the panel -// and then read more as the user scrolls down. We also want to ensure that we're only -// ever running one `git show` command at time, and that we only have one command -// writing its output to the main panel at a time. - -const THROTTLE_TIME = time.Millisecond * 30 - -// we use this to check if the system is under stress right now. Hopefully this makes sense on other machines -const COMMAND_START_THRESHOLD = time.Millisecond * 10 - -type ViewBufferManager struct { - // this blocks until the task has been properly stopped - stopCurrentTask func() - - // this is what we write the output of the task to. It's typically a view - writer io.Writer - - waitingMutex deadlock.Mutex - taskIDMutex deadlock.Mutex - Log *logrus.Entry - newTaskID int - readLines chan LinesToRead - taskKey string - onNewKey func() - - // beforeStart is the function that is called before starting a new task - beforeStart func() - refreshView func() - onEndOfInput func() - - // see docs/dev/Busy.md - // A gocui task is not the same thing as the tasks defined in this file. - // A gocui task simply represents the fact that lazygit is busy doing something, - // whereas the tasks in this file are about rendering content to a view. - newGocuiTask func() gocui.Task - - // if the user flicks through a heap of items, with each one - // spawning a process to render something to the main view, - // it can slow things down quite a bit. In these situations we - // want to throttle the spawning of processes. - throttle bool -} - -type LinesToRead struct { - // Total number of lines to read - Total int - - // Number of lines after which we have read enough to fill the view, and can - // do an initial refresh. Only set for the initial read request; -1 for - // subsequent requests. - InitialRefreshAfter int - - // Function to call after reading the lines is done - Then func() -} - -func (self *ViewBufferManager) GetTaskKey() string { - return self.taskKey -} - -func NewViewBufferManager( - log *logrus.Entry, - writer io.Writer, - beforeStart func(), - refreshView func(), - onEndOfInput func(), - onNewKey func(), - newGocuiTask func() gocui.Task, -) *ViewBufferManager { - return &ViewBufferManager{ - Log: log, - writer: writer, - beforeStart: beforeStart, - refreshView: refreshView, - onEndOfInput: onEndOfInput, - readLines: nil, - onNewKey: onNewKey, - newGocuiTask: newGocuiTask, - } -} - -func (self *ViewBufferManager) ReadLines(n int) { - if self.readLines != nil { - go utils.Safe(func() { - self.readLines <- LinesToRead{Total: n, InitialRefreshAfter: -1} - }) - } -} - -func (self *ViewBufferManager) ReadToEnd(then func()) { - if self.readLines != nil { - go utils.Safe(func() { - self.readLines <- LinesToRead{Total: -1, InitialRefreshAfter: -1, Then: then} - }) - } else if then != nil { - then() - } -} - -func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), prefix string, linesToRead LinesToRead, onDoneFn func()) func(TaskOpts) error { - return func(opts TaskOpts) error { - var onDoneOnce sync.Once - var onFirstPageShownOnce sync.Once - - onFirstPageShown := func() { - onFirstPageShownOnce.Do(func() { - opts.InitialContentLoaded() - }) - } - - onDone := func() { - if onDoneFn != nil { - onDoneOnce.Do(onDoneFn) - } - onFirstPageShown() - } - - if self.throttle { - self.Log.Info("throttling task") - time.Sleep(THROTTLE_TIME) - } - - select { - case <-opts.Stop: - onDone() - return nil - default: - } - - startTime := time.Now() - cmd, r := start() - timeToStart := time.Since(startTime) - - done := make(chan struct{}) - - go utils.Safe(func() { - select { - case <-done: - // The command finished and did not have to be preemptively stopped before the next command. - // No need to throttle. - self.throttle = false - case <-opts.Stop: - // we use the time it took to start the program as a way of checking if things - // are running slow at the moment. This is admittedly a crude estimate, but - // the point is that we only want to throttle when things are running slow - // and the user is flicking through a bunch of items. - self.throttle = time.Since(startTime) < THROTTLE_TIME && timeToStart > COMMAND_START_THRESHOLD - - // Kill the still-running command. The only reason to do this is to save CPU usage - // when flicking through several very long diffs when diff.algorithm = histogram is - // being used, in which case multiple git processes continue to calculate expensive - // diffs in the background even though they have been stopped already. - // - // Unfortunately this will do nothing on Windows, so Windows users will have to live - // with the higher CPU usage. - if err := oscommands.TerminateProcessGracefully(cmd); err != nil { - self.Log.Errorf("error when trying to terminate cmd task: %v; Command: %v %v", err, cmd.Path, cmd.Args) - } - - // close the task's stdout pipe (or the pty if we're using one) to make the command terminate - onDone() - } - }) - - loadingMutex := deadlock.Mutex{} - - self.readLines = make(chan LinesToRead, 1024) - - scanner := bufio.NewScanner(r) - scanner.Split(utils.ScanLinesAndTruncateWhenLongerThanBuffer(bufio.MaxScanTokenSize)) - - lineChan := make(chan []byte) - lineWrittenChan := make(chan struct{}) - - // We're reading from the scanner in a separate goroutine because on windows - // if running git through a shim, we sometimes kill the parent process without - // killing its children, meaning the scanner blocks forever. This solution - // leaves us with a dead goroutine, but it's better than blocking all - // rendering to main views. - go utils.Safe(func() { - defer close(lineChan) - for scanner.Scan() { - select { - case <-opts.Stop: - return - case lineChan <- scanner.Bytes(): - // We need to confirm the data has been fed into the view before we - // pull more from the scanner because the scanner uses the same backing - // array and we don't want to be mutating that while it's being written - <-lineWrittenChan - } - } - }) - - loaded := false - - go utils.Safe(func() { - ticker := time.NewTicker(time.Millisecond * 200) - defer ticker.Stop() - select { - case <-opts.Stop: - return - case <-ticker.C: - loadingMutex.Lock() - if !loaded { - self.beforeStart() - _, _ = self.writer.Write([]byte("loading...")) - self.refreshView() - } - loadingMutex.Unlock() - } - }) - - go utils.Safe(func() { - isViewStale := true - writeToView := func(content []byte) { - isViewStale = true - _, _ = self.writer.Write(content) - } - refreshViewIfStale := func() { - if isViewStale { - self.refreshView() - isViewStale = false - } - } - - outer: - for { - select { - case <-opts.Stop: - break outer - case linesToRead := <-self.readLines: - callThen := func() { - if linesToRead.Then != nil { - linesToRead.Then() - } - } - for i := 0; linesToRead.Total == -1 || i < linesToRead.Total; i++ { - var ok bool - var line []byte - select { - case <-opts.Stop: - callThen() - break outer - case line, ok = <-lineChan: - // process line below - } - - loadingMutex.Lock() - if !loaded { - self.beforeStart() - if prefix != "" { - writeToView([]byte(prefix)) - } - loaded = true - } - loadingMutex.Unlock() - - if !ok { - // if we're here then there's nothing left to scan from the source - // so we're at the EOF and can flush the stale content - self.onEndOfInput() - callThen() - break outer - } - writeToView(append(line, '\n')) - lineWrittenChan <- struct{}{} - - if i+1 == linesToRead.InitialRefreshAfter { - // We have read enough lines to fill the view, so do a first refresh - // here to show what we have. Continue reading and refresh again at - // the end to make sure the scrollbar has the right size. - refreshViewIfStale() - } - } - refreshViewIfStale() - onFirstPageShown() - callThen() - } - } - - self.readLines = nil - - refreshViewIfStale() - - select { - case <-opts.Stop: - // If we stopped the task, don't block waiting for it; this could cause a delay if - // the process takes a while until it actually terminates. We still want to call - // Wait to reclaim any resources, but do it on a background goroutine, and ignore - // any errors. - go func() { _ = cmd.Wait() }() - default: - if err := cmd.Wait(); err != nil { - self.Log.Errorf("Unexpected error when running cmd task: %v; Failed command: %v %v", err, cmd.Path, cmd.Args) - } - } - - // calling this here again in case the program ended on its own accord - onDone() - - close(done) - close(lineWrittenChan) - }) - - self.readLines <- linesToRead - - <-done - - return nil - } -} - -// Close closes the task manager, killing whatever task may currently be running -func (self *ViewBufferManager) Close() { - if self.stopCurrentTask == nil { - return - } - - c := make(chan struct{}) - - go utils.Safe(func() { - self.stopCurrentTask() - c <- struct{}{} - }) - - select { - case <-c: - return - case <-time.After(3 * time.Second): - fmt.Println("cannot kill child process") - } -} - -// different kinds of tasks: -// 1) command based, where the manager can be asked to read more lines, but the command can be killed -// 2) string based, where the manager can also be asked to read more lines - -type TaskOpts struct { - // Channel that tells the task to stop, because another task wants to run. - Stop chan struct{} - - // Only for tasks which are long-running, where we read more lines sporadically. - // We use this to keep track of when a user's action is complete (i.e. all views - // have been refreshed to display the results of their action) - InitialContentLoaded func() -} - -func (self *ViewBufferManager) NewTask(f func(TaskOpts) error, key string) error { - gocuiTask := self.newGocuiTask() - - var completeTaskOnce sync.Once - - completeGocuiTask := func() { - completeTaskOnce.Do(func() { - gocuiTask.Done() - }) - } - - go utils.Safe(func() { - defer completeGocuiTask() - - self.taskIDMutex.Lock() - self.newTaskID++ - taskID := self.newTaskID - - if self.GetTaskKey() != key && self.onNewKey != nil { - self.onNewKey() - } - self.taskKey = key - - self.taskIDMutex.Unlock() - - self.waitingMutex.Lock() - - self.taskIDMutex.Lock() - if taskID < self.newTaskID { - self.waitingMutex.Unlock() - self.taskIDMutex.Unlock() - return - } - self.taskIDMutex.Unlock() - - if self.stopCurrentTask != nil { - self.stopCurrentTask() - } - - self.readLines = nil - - stop := make(chan struct{}) - notifyStopped := make(chan struct{}) - - var once sync.Once - onStop := func() { - close(stop) - <-notifyStopped - } - - self.stopCurrentTask = func() { once.Do(onStop) } - - self.waitingMutex.Unlock() - - if err := f(TaskOpts{Stop: stop, InitialContentLoaded: completeGocuiTask}); err != nil { - self.Log.Error(err) // might need an onError callback - } - - close(notifyStopped) - }) - - return nil -} diff --git a/pkg/tasks/tasks_test.go b/pkg/tasks/tasks_test.go deleted file mode 100644 index c7a499d46bf..00000000000 --- a/pkg/tasks/tasks_test.go +++ /dev/null @@ -1,270 +0,0 @@ -package tasks - -import ( - "bytes" - "io" - "os/exec" - "reflect" - "strings" - "sync" - "testing" - "time" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -func getCounter() (func(), func() int) { - counter := 0 - return func() { counter++ }, func() int { return counter } -} - -func TestNewCmdTaskInstantStop(t *testing.T) { - writer := bytes.NewBuffer(nil) - beforeStart, getBeforeStartCallCount := getCounter() - refreshView, getRefreshViewCallCount := getCounter() - onEndOfInput, getOnEndOfInputCallCount := getCounter() - onNewKey, getOnNewKeyCallCount := getCounter() - onDone, getOnDoneCallCount := getCounter() - task := gocui.NewFakeTask() - newTask := func() gocui.Task { - return task - } - - manager := NewViewBufferManager( - utils.NewDummyLog(), - writer, - beforeStart, - refreshView, - onEndOfInput, - onNewKey, - newTask, - ) - - stop := make(chan struct{}) - reader := bytes.NewBufferString("test") - start := func() (*exec.Cmd, io.Reader) { - // not actually starting this because it's not necessary - cmd := exec.Command("blah") - - close(stop) - - return cmd, reader - } - - fn := manager.NewCmdTask(start, "prefix\n", LinesToRead{20, -1, nil}, onDone) - - _ = fn(TaskOpts{Stop: stop, InitialContentLoaded: func() { task.Done() }}) - - callCountExpectations := []struct { - expected int - actual int - name string - }{ - {0, getBeforeStartCallCount(), "beforeStart"}, - {1, getRefreshViewCallCount(), "refreshView"}, - {0, getOnEndOfInputCallCount(), "onEndOfInput"}, - {0, getOnNewKeyCallCount(), "onNewKey"}, - {1, getOnDoneCallCount(), "onDone"}, - } - for _, expectation := range callCountExpectations { - if expectation.actual != expectation.expected { - t.Errorf("expected %s to be called %d times, got %d", expectation.name, expectation.expected, expectation.actual) - } - } - - if task.Status() != gocui.TaskStatusDone { - t.Errorf("expected task status to be 'done', got '%s'", task.FormatStatus()) - } - - expectedContent := "" - actualContent := writer.String() - if actualContent != expectedContent { - t.Errorf("expected writer to receive the following content: \n%s\n. But instead it received: %s", expectedContent, actualContent) - } -} - -func TestNewCmdTask(t *testing.T) { - writer := bytes.NewBuffer(nil) - beforeStart, getBeforeStartCallCount := getCounter() - refreshView, getRefreshViewCallCount := getCounter() - onEndOfInput, getOnEndOfInputCallCount := getCounter() - onNewKey, getOnNewKeyCallCount := getCounter() - onDone, getOnDoneCallCount := getCounter() - task := gocui.NewFakeTask() - newTask := func() gocui.Task { - return task - } - - manager := NewViewBufferManager( - utils.NewDummyLog(), - writer, - beforeStart, - refreshView, - onEndOfInput, - onNewKey, - newTask, - ) - - stop := make(chan struct{}) - reader := bytes.NewBufferString("test") - start := func() (*exec.Cmd, io.Reader) { - // not actually starting this because it's not necessary - cmd := exec.Command("blah") - - return cmd, reader - } - - fn := manager.NewCmdTask(start, "prefix\n", LinesToRead{20, -1, nil}, onDone) - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - time.Sleep(100 * time.Millisecond) - close(stop) - wg.Done() - }() - _ = fn(TaskOpts{Stop: stop, InitialContentLoaded: func() { task.Done() }}) - - wg.Wait() - - callCountExpectations := []struct { - expected int - actual int - name string - }{ - {1, getBeforeStartCallCount(), "beforeStart"}, - {1, getRefreshViewCallCount(), "refreshView"}, - {1, getOnEndOfInputCallCount(), "onEndOfInput"}, - {0, getOnNewKeyCallCount(), "onNewKey"}, - {1, getOnDoneCallCount(), "onDone"}, - } - for _, expectation := range callCountExpectations { - if expectation.actual != expectation.expected { - t.Errorf("expected %s to be called %d times, got %d", expectation.name, expectation.expected, expectation.actual) - } - } - - if task.Status() != gocui.TaskStatusDone { - t.Errorf("expected task status to be 'done', got '%s'", task.FormatStatus()) - } - - expectedContent := "prefix\ntest\n" - actualContent := writer.String() - if actualContent != expectedContent { - t.Errorf("expected writer to receive the following content: \n%s\n. But instead it received: %s", expectedContent, actualContent) - } -} - -// A dummy reader that simply yields as many blank lines as requested. The only -// thing we want to do with the output is count the number of lines. -type BlankLineReader struct { - totalLinesToYield int - linesYielded int -} - -func (d *BlankLineReader) Read(p []byte) (n int, err error) { - if d.totalLinesToYield == d.linesYielded { - return 0, io.EOF - } - - d.linesYielded++ - p[0] = '\n' - return 1, nil -} - -func TestNewCmdTaskRefresh(t *testing.T) { - type scenario struct { - name string - totalTaskLines int - linesToRead LinesToRead - expectedLineCountsOnRefresh []int - } - - scenarios := []scenario{ - { - "total < initialRefreshAfter", - 150, - LinesToRead{100, 120, nil}, - []int{100}, - }, - { - "total == initialRefreshAfter", - 150, - LinesToRead{100, 100, nil}, - []int{100}, - }, - { - "total > initialRefreshAfter", - 150, - LinesToRead{100, 50, nil}, - []int{50, 100}, - }, - { - "initialRefreshAfter == -1", - 150, - LinesToRead{100, -1, nil}, - []int{100}, - }, - { - "totalTaskLines < initialRefreshAfter", - 25, - LinesToRead{100, 50, nil}, - []int{25}, - }, - { - "totalTaskLines between total and initialRefreshAfter", - 75, - LinesToRead{100, 50, nil}, - []int{50, 75}, - }, - } - - for _, s := range scenarios { - writer := bytes.NewBuffer(nil) - lineCountsOnRefresh := []int{} - refreshView := func() { - lineCountsOnRefresh = append(lineCountsOnRefresh, strings.Count(writer.String(), "\n")) - } - - task := gocui.NewFakeTask() - newTask := func() gocui.Task { - return task - } - - manager := NewViewBufferManager( - utils.NewDummyLog(), - writer, - func() {}, - refreshView, - func() {}, - func() {}, - newTask, - ) - - stop := make(chan struct{}) - reader := BlankLineReader{totalLinesToYield: s.totalTaskLines} - start := func() (*exec.Cmd, io.Reader) { - // not actually starting this because it's not necessary - cmd := exec.Command("blah") - - return cmd, &reader - } - - fn := manager.NewCmdTask(start, "", s.linesToRead, func() {}) - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - time.Sleep(100 * time.Millisecond) - close(stop) - wg.Done() - }() - _ = fn(TaskOpts{Stop: stop, InitialContentLoaded: func() { task.Done() }}) - - wg.Wait() - - if !reflect.DeepEqual(lineCountsOnRefresh, s.expectedLineCountsOnRefresh) { - t.Errorf("%s: expected line counts on refresh: %v, got %v", - s.name, s.expectedLineCountsOnRefresh, lineCountsOnRefresh) - } - } -} diff --git a/pkg/theme/gocui.go b/pkg/theme/gocui.go deleted file mode 100644 index 5f8e6a611c4..00000000000 --- a/pkg/theme/gocui.go +++ /dev/null @@ -1,45 +0,0 @@ -package theme - -import ( - "github.com/gookit/color" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -var gocuiColorMap = map[string]gocui.Attribute{ - "default": gocui.ColorDefault, - "black": gocui.ColorBlack, - "red": gocui.ColorRed, - "green": gocui.ColorGreen, - "yellow": gocui.ColorYellow, - "blue": gocui.ColorBlue, - "magenta": gocui.ColorMagenta, - "cyan": gocui.ColorCyan, - "white": gocui.ColorWhite, - "bold": gocui.AttrBold, - "reverse": gocui.AttrReverse, - "underline": gocui.AttrUnderline, -} - -// GetGocuiAttribute gets the gocui color attribute from the string -func GetGocuiAttribute(key string) gocui.Attribute { - if utils.IsValidHexValue(key) { - values := color.HEX(key).Values() - return gocui.NewRGBColor(int32(values[0]), int32(values[1]), int32(values[2])) - } - - value, present := gocuiColorMap[key] - if present { - return value - } - return gocui.ColorWhite -} - -// GetGocuiStyle bitwise OR's a list of attributes obtained via the given keys -func GetGocuiStyle(keys []string) gocui.Attribute { - var attribute gocui.Attribute - for _, key := range keys { - attribute |= GetGocuiAttribute(key) - } - return attribute -} diff --git a/pkg/theme/style.go b/pkg/theme/style.go deleted file mode 100644 index 43e81315d07..00000000000 --- a/pkg/theme/style.go +++ /dev/null @@ -1,44 +0,0 @@ -package theme - -import ( - "github.com/gookit/color" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -func GetTextStyle(keys []string, background bool) style.TextStyle { - s := style.New() - - for _, key := range keys { - switch key { - case "bold": - s = s.SetBold() - case "reverse": - s = s.SetReverse() - case "underline": - s = s.SetUnderline() - case "strikethrough": - s = s.SetStrikethrough() - default: - value, present := style.ColorMap[key] - if present { - var c style.TextStyle - if background { - c = value.Background - } else { - c = value.Foreground - } - s = s.MergeStyle(c) - } else if utils.IsValidHexValue(key) { - c := style.NewRGBColor(color.HEX(key, background)) - if background { - s = s.SetBg(c) - } else { - s = s.SetFg(c) - } - } - } - } - - return s -} diff --git a/pkg/theme/style_test.go b/pkg/theme/style_test.go deleted file mode 100644 index e20191b55d7..00000000000 --- a/pkg/theme/style_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package theme - -import ( - "reflect" - "testing" - - "github.com/gookit/color" - "github.com/jesseduffield/lazygit/pkg/gui/style" -) - -func TestGetTextStyle(t *testing.T) { - scenarios := []struct { - name string - keys []string - background bool - expected style.TextStyle - }{ - { - name: "empty", - keys: []string{""}, - background: true, - expected: style.New(), - }, - { - name: "named color, fg", - keys: []string{"blue"}, - background: false, - expected: style.New().SetFg(style.NewBasicColor(color.FgBlue)), - }, - { - name: "named color, bg", - keys: []string{"blue"}, - background: true, - expected: style.New().SetBg(style.NewBasicColor(color.BgBlue)), - }, - { - name: "hex color, fg", - keys: []string{"#123456"}, - background: false, - expected: style.New().SetFg(style.NewRGBColor(color.RGBColor{0x12, 0x34, 0x56, 0})), - }, - { - name: "hex color, bg", - keys: []string{"#abcdef"}, - background: true, - expected: style.New().SetBg(style.NewRGBColor(color.RGBColor{0xab, 0xcd, 0xef, 1})), - }, - } - - for _, scenario := range scenarios { - t.Run(scenario.name, func(t *testing.T) { - if actual := GetTextStyle(scenario.keys, scenario.background); !reflect.DeepEqual(actual, scenario.expected) { - t.Errorf("GetTextStyle() = %v, expected %v", actual, scenario.expected) - } - }) - } -} diff --git a/pkg/theme/theme.go b/pkg/theme/theme.go deleted file mode 100644 index acd8ebf7161..00000000000 --- a/pkg/theme/theme.go +++ /dev/null @@ -1,76 +0,0 @@ -package theme - -import ( - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/style" -) - -var ( - // DefaultTextColor is the default text color - DefaultTextColor = style.FgDefault - - // GocuiDefaultTextColor does the same as DefaultTextColor but this one only colors gocui default text colors - GocuiDefaultTextColor = gocui.ColorDefault - - // ActiveBorderColor is the border color of the active frame - ActiveBorderColor gocui.Attribute - - // InactiveBorderColor is the border color of the inactive active frames - InactiveBorderColor gocui.Attribute - - // FilteredActiveBorderColor is the border color of the active frame, when it's being searched/filtered - SearchingActiveBorderColor gocui.Attribute - - // GocuiSelectedLineBgColor is the background color for the selected line in gocui - GocuiSelectedLineBgColor gocui.Attribute - // GocuiInactiveViewSelectedLineBgColor is the background color for the selected line in gocui if the view doesn't have focus - GocuiInactiveViewSelectedLineBgColor gocui.Attribute - - OptionsColor gocui.Attribute - - // SelectedLineBgColor is the background color for the selected line - SelectedLineBgColor = style.New() - // InactiveViewSelectedLineBgColor is the background color for the selected line if the view doesn't have the focus - InactiveViewSelectedLineBgColor = style.New() - - // CherryPickedCommitColor is the text style when cherry picking a commit - CherryPickedCommitTextStyle = style.New() - - // MarkedBaseCommitTextStyle is the text style of the marked rebase base commit - MarkedBaseCommitTextStyle = style.New() - - OptionsFgColor = style.New() - - DiffTerminalColor = style.FgMagenta - - UnstagedChangesColor = style.New() -) - -// UpdateTheme updates all theme variables -func UpdateTheme(themeConfig config.ThemeConfig) { - ActiveBorderColor = GetGocuiStyle(themeConfig.ActiveBorderColor) - InactiveBorderColor = GetGocuiStyle(themeConfig.InactiveBorderColor) - SearchingActiveBorderColor = GetGocuiStyle(themeConfig.SearchingActiveBorderColor) - SelectedLineBgColor = GetTextStyle(themeConfig.SelectedLineBgColor, true) - InactiveViewSelectedLineBgColor = GetTextStyle(themeConfig.InactiveViewSelectedLineBgColor, true) - - cherryPickedCommitBgTextStyle := GetTextStyle(themeConfig.CherryPickedCommitBgColor, true) - cherryPickedCommitFgTextStyle := GetTextStyle(themeConfig.CherryPickedCommitFgColor, false) - CherryPickedCommitTextStyle = cherryPickedCommitBgTextStyle.MergeStyle(cherryPickedCommitFgTextStyle) - - markedBaseCommitBgTextStyle := GetTextStyle(themeConfig.MarkedBaseCommitBgColor, true) - markedBaseCommitFgTextStyle := GetTextStyle(themeConfig.MarkedBaseCommitFgColor, false) - MarkedBaseCommitTextStyle = markedBaseCommitBgTextStyle.MergeStyle(markedBaseCommitFgTextStyle) - - unstagedChangesTextStyle := GetTextStyle(themeConfig.UnstagedChangesColor, false) - UnstagedChangesColor = unstagedChangesTextStyle - - GocuiSelectedLineBgColor = GetGocuiStyle(themeConfig.SelectedLineBgColor) - GocuiInactiveViewSelectedLineBgColor = GetGocuiStyle(themeConfig.InactiveViewSelectedLineBgColor) - OptionsColor = GetGocuiStyle(themeConfig.OptionsTextColor) - OptionsFgColor = GetTextStyle(themeConfig.OptionsTextColor, false) - - DefaultTextColor = GetTextStyle(themeConfig.DefaultFgColor, false) - GocuiDefaultTextColor = GetGocuiStyle(themeConfig.DefaultFgColor) -} diff --git a/pkg/updates/updates.go b/pkg/updates/updates.go deleted file mode 100644 index 60788de8e98..00000000000 --- a/pkg/updates/updates.go +++ /dev/null @@ -1,329 +0,0 @@ -package updates - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "runtime" - "strings" - "time" - - "github.com/go-errors/errors" - - "github.com/kardianos/osext" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/common" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/constants" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// Updater checks for updates and does updates -type Updater struct { - *common.Common - Config config.AppConfigurer - OSCommand *oscommands.OSCommand -} - -// Updaterer implements the check and update methods -type Updaterer interface { - CheckForNewUpdate() - Update() -} - -// NewUpdater creates a new updater -func NewUpdater(cmn *common.Common, config config.AppConfigurer, osCommand *oscommands.OSCommand) (*Updater, error) { - return &Updater{ - Common: cmn, - Config: config, - OSCommand: osCommand, - }, nil -} - -func (u *Updater) getLatestVersionNumber() (string, error) { - req, err := http.NewRequest("GET", constants.Links.RepoUrl+"/releases/latest", nil) - if err != nil { - return "", err - } - req.Header.Set("Accept", "application/json") - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - dec := json.NewDecoder(resp.Body) - data := struct { - TagName string `json:"tag_name"` - }{} - if err := dec.Decode(&data); err != nil { - return "", err - } - - return data.TagName, nil -} - -// RecordLastUpdateCheck records last time an update check was performed -func (u *Updater) RecordLastUpdateCheck() error { - u.Config.GetAppState().LastUpdateCheck = time.Now().Unix() - return u.Config.SaveAppState() -} - -// expecting version to be of the form `v12.34.56` -func (u *Updater) majorVersionDiffers(oldVersion, newVersion string) bool { - if oldVersion == "unversioned" { - return false - } - oldVersion = strings.TrimPrefix(oldVersion, "v") - newVersion = strings.TrimPrefix(newVersion, "v") - return strings.Split(oldVersion, ".")[0] != strings.Split(newVersion, ".")[0] -} - -func (u *Updater) currentVersion() string { - versionNumber := u.Config.GetVersion() - if versionNumber == "unversioned" { - return versionNumber - } - - return fmt.Sprintf("v%s", u.Config.GetVersion()) -} - -func (u *Updater) checkForNewUpdate() (string, error) { - u.Log.Info("Checking for an updated version") - currentVersion := u.currentVersion() - if err := u.RecordLastUpdateCheck(); err != nil { - return "", err - } - - newVersion, err := u.getLatestVersionNumber() - if err != nil { - return "", err - } - u.Log.Info("Current version is " + currentVersion) - u.Log.Info("New version is " + newVersion) - - if newVersion == currentVersion { - return "", errors.New(u.Tr.OnLatestVersionErr) - } - - if u.majorVersionDiffers(currentVersion, newVersion) { - errMessage := utils.ResolvePlaceholderString( - u.Tr.MajorVersionErr, map[string]string{ - "newVersion": newVersion, - "currentVersion": currentVersion, - }, - ) - return "", errors.New(errMessage) - } - - rawUrl := u.getBinaryUrl(newVersion) - - u.Log.Info("Checking for resource at url " + rawUrl) - if !u.verifyResourceFound(rawUrl) { - errMessage := utils.ResolvePlaceholderString( - u.Tr.CouldNotFindBinaryErr, map[string]string{ - "url": rawUrl, - }, - ) - - return "", errors.New(errMessage) - } - u.Log.Info("Verified resource is available, ready to update") - - return newVersion, nil -} - -// CheckForNewUpdate checks if there is an available update -func (u *Updater) CheckForNewUpdate(onFinish func(string, error) error, userRequested bool) { - if !userRequested && u.skipUpdateCheck() { - return - } - - newVersion, err := u.checkForNewUpdate() - if err = onFinish(newVersion, err); err != nil { - u.Log.Error(err) - } -} - -func (u *Updater) skipUpdateCheck() bool { - // will remove the check for windows after adding a manifest file asking for - // the required permissions - if runtime.GOOS == "windows" { - u.Log.Info("Updating is currently not supported for windows until we can fix permission issues") - return true - } - - if u.Config.GetVersion() == "unversioned" { - u.Log.Info("Current version is not built from an official release so we won't check for an update") - return true - } - - if u.Config.GetBuildSource() != "buildBinary" { - u.Log.Info("Binary is not built with the buildBinary flag so we won't check for an update") - return true - } - - userConfig := u.UserConfig() - if userConfig.Update.Method == "never" { - u.Log.Info("Update method is set to never so we won't check for an update") - return true - } - - currentTimestamp := time.Now().Unix() - lastUpdateCheck := u.Config.GetAppState().LastUpdateCheck - days := userConfig.Update.Days - - if (currentTimestamp-lastUpdateCheck)/(60*60*24) < days { - u.Log.Info("Last update was too recent so we won't check for an update") - return true - } - - return false -} - -func (u *Updater) mappedOs(os string) string { - osMap := map[string]string{ - "darwin": "Darwin", - "linux": "Linux", - "windows": "Windows", - } - result, found := osMap[os] - if found { - return result - } - return os -} - -func (u *Updater) mappedArch(arch string) string { - archMap := map[string]string{ - "386": "32-bit", - "amd64": "x86_64", - } - result, found := archMap[arch] - if found { - return result - } - return arch -} - -func (u *Updater) zipExtension() string { - if runtime.GOOS == "windows" { - return "zip" - } - - return "tar.gz" -} - -// example: https://github.com/jesseduffield/lazygit/releases/download/v0.1.73/lazygit_0.1.73_Darwin_x86_64.tar.gz -func (u *Updater) getBinaryUrl(newVersion string) string { - url := fmt.Sprintf( - "%s/releases/download/%s/lazygit_%s_%s_%s.%s", - constants.Links.RepoUrl, - newVersion, - newVersion[1:], - u.mappedOs(runtime.GOOS), - u.mappedArch(runtime.GOARCH), - u.zipExtension(), - ) - u.Log.Info("Url for latest release is " + url) - return url -} - -// Update downloads the latest binary and replaces the current binary with it -func (u *Updater) Update(newVersion string) error { - return u.update(newVersion) -} - -func (u *Updater) update(newVersion string) error { - rawUrl := u.getBinaryUrl(newVersion) - u.Log.Info("Updating with url " + rawUrl) - return u.downloadAndInstall(rawUrl) -} - -func (u *Updater) downloadAndInstall(rawUrl string) error { - configDir := u.Config.GetUserConfigDir() - u.Log.Info("Download directory is " + configDir) - - zipPath := filepath.Join(configDir, "temp_lazygit."+u.zipExtension()) - u.Log.Info("Temp path to tarball/zip file is " + zipPath) - - // remove existing zip file - if err := os.RemoveAll(zipPath); err != nil && !os.IsNotExist(err) { - return err - } - - // Create the zip file - out, err := os.Create(zipPath) - if err != nil { - return err - } - defer out.Close() - - // Get the data - resp, err := http.Get(rawUrl) - if err != nil { - return err - } - defer resp.Body.Close() - - // Check server response - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("error while trying to download latest lazygit: %s", resp.Status) - } - - // Write the body to file - _, err = io.Copy(out, resp.Body) - if err != nil { - return err - } - - u.Log.Info("untarring tarball/unzipping zip file") - err = u.OSCommand.Cmd.New([]string{"tar", "-zxf", zipPath, "lazygit"}).Run() - if err != nil { - return err - } - - // the `tar` terminal cannot store things in a new location without permission - // so it creates it in the current directory. As such our path is fairly simple. - // You won't see it because it's gitignored. - tempLazygitFilePath := "lazygit" - - u.Log.Infof("Path to temp binary is %s", tempLazygitFilePath) - - // get the path of the current binary - binaryPath, err := osext.Executable() - if err != nil { - return err - } - u.Log.Info("Binary path is " + binaryPath) - - // Verify the main file exists - if _, err := os.Stat(zipPath); err != nil { - return err - } - - // swap out the old binary for the new one - err = os.Rename(tempLazygitFilePath, binaryPath) - if err != nil { - return err - } - u.Log.Info("Update complete!") - - return nil -} - -func (u *Updater) verifyResourceFound(rawUrl string) bool { - resp, err := http.Head(rawUrl) - if err != nil { - return false - } - defer resp.Body.Close() - u.Log.Info("Received status code ", resp.StatusCode) - // OK (200) indicates that the resource is present. - return resp.StatusCode == http.StatusOK -} diff --git a/pkg/utils/color.go b/pkg/utils/color.go deleted file mode 100644 index 17e40033731..00000000000 --- a/pkg/utils/color.go +++ /dev/null @@ -1,68 +0,0 @@ -package utils - -import ( - "regexp" - "sync" - - "github.com/gookit/color" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/samber/lo" -) - -var ( - decoloriseCache = make(map[string]string) - decoloriseMutex sync.RWMutex -) - -// Decolorise strips a string of color -func Decolorise(str string) string { - decoloriseMutex.RLock() - val := decoloriseCache[str] - decoloriseMutex.RUnlock() - - if val != "" { - return val - } - - re := regexp.MustCompile(`\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[mGK]`) - linkRe := regexp.MustCompile(`\x1B]8;[^;]*;(.*?)(\x1B.|\x07)`) - ret := re.ReplaceAllString(str, "") - ret = linkRe.ReplaceAllString(ret, "") - - decoloriseMutex.Lock() - decoloriseCache[str] = ret - decoloriseMutex.Unlock() - - return ret -} - -func IsValidHexValue(v string) bool { - if len(v) != 4 && len(v) != 7 { - return false - } - - if v[0] != '#' { - return false - } - - for _, char := range v[1:] { - switch char { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F': - continue - default: - return false - } - } - - return true -} - -func SetCustomColors(customColors map[string]string) map[string]*style.TextStyle { - return lo.MapValues(customColors, func(c string, key string) *style.TextStyle { - if s, ok := style.ColorMap[c]; ok { - return &s.Foreground - } - value := style.New().SetFg(style.NewRGBColor(color.HEX(c, false))) - return &value - }) -} diff --git a/pkg/utils/color_test.go b/pkg/utils/color_test.go deleted file mode 100644 index 19770d63e74..00000000000 --- a/pkg/utils/color_test.go +++ /dev/null @@ -1,206 +0,0 @@ -package utils - -import ( - "testing" - - "github.com/jesseduffield/lazygit/pkg/gui/style" -) - -func TestDecolorise(t *testing.T) { - tests := []struct { - input string - output string - }{ - { - input: "", - output: "", - }, - { - input: "hello", - output: "hello", - }, - { - input: "hello\x1b[31m", - output: "hello", - }, - { - input: "hello\x1b[31mworld", - output: "helloworld", - }, - { - input: "hello\x1b[31m\x1b[32mworld", - output: "helloworld", - }, - { - input: "hello\x1b[31m\x1b[32m\x1b[33mworld", - output: "helloworld", - }, - { - input: "hello\x1b[31m\x1b[32m\x1b[33m\x1b[34mworld", - output: "helloworld", - }, - { - input: "hello\x1b[31m\x1b[32m\x1b[33m\x1b[34m\x1b[35mworld", - output: "helloworld", - }, - { - input: "hello\x1b[31m\x1b[32m\x1b[33m\x1b[34m\x1b[35m\x1b[36mworld", - output: "helloworld", - }, - { - input: "hello\x1b[31m\x1b[32m\x1b[33m\x1b[34m\x1b[35m\x1b[36m\x1b[37mworld", - output: "helloworld", - }, - { - input: "hello\x1b[31m\x1b[32m\x1b[33m\x1b[34m\x1b[35m\x1b[36m\x1b[37mworld", - output: "helloworld", - }, - { - input: "\x1b[38;2;47;228;2mJD\x1b[0m", - output: "JD", - }, - { - input: "\x1b[38;2;160;47;213mRy\x1b[0m", - output: "Ry", - }, - { - input: "\x1b[38;2;179;217;72mSB\x1b[0m", - output: "SB", - }, - { - input: "\x1b[38;2;48;34;214mMK\x1b[0m", - output: "MK", - }, - { - input: "\x1b[38;2;28;152;222mAŁ\x1b[0m", - output: "AŁ", - }, - { - input: "\x1b[38;2;237;230;56mHH\x1b[0m", - output: "HH", - }, - { - input: "\x1b[38;2;63;232;69mmj\x1b[0m", - output: "mj", - }, - { - input: "\x1b[38;2;111;207;16mbl\x1b[0m", - output: "bl", - }, - { - input: "\x1b[38;2;250;31;163msa\x1b[0m", - output: "sa", - }, - { - input: "\x1b[38;2;195;10;54mbt\x1b[0m", - output: "bt", - }, - { - input: "\x1b[38;2;232;147;68mco\x1b[0m", - output: "co", - }, - { - input: "\x1b[38;2;116;180;35mDY\x1b[0m", - output: "DY", - }, - { - input: "\x1b[38;2;232;1;195mDB\x1b[0m", - output: "DB", - }, - { - input: "\x1b[38;2;245;101;55mLi\x1b[0m", - output: "Li", - }, - { - input: "\x1b[38;2;47;4;217mRy\x1b[0m", - output: "Ry", - }, - { - input: "\x1b[38;2;252;197;1mEl\x1b[0m", - output: "El", - }, - { - input: "\x1b[38;2;41;131;237mMG\x1b[0m", - output: "MG", - }, - { - input: "\x1b[38;2;65;240;62mDP\x1b[0m", - output: "DP", - }, - { - input: "\x1b[38;2;29;201;139mFM\x1b[0m", - output: "FM", - }, - { - input: "\x1b[38;2;141;20;198mEB\x1b[0m", - output: "EB", - }, - { - input: "\x1b[38;2;60;215;140mDM\x1b[0m", - output: "DM", - }, - { - input: "\x1b[38;2;247;63;38mDE\x1b[0m", - output: "DE", - }, - { - input: "\x1b[38;2;67;210;17mCB\x1b[0m", - output: "CB", - }, - { - input: "\x1b[38;2;220;190;84mST\x1b[0m", - output: "ST", - }, - { - input: "\x1b[38;2;137;239;6mER\x1b[0m", - output: "ER", - }, - { - input: "\x1b[38;2;47;249;225mAY\x1b[0m", - output: "AY", - }, - { - input: "\x1b[38;2;215;16;195mca\x1b[0m", - output: "ca", - }, - { - input: "\x1b[38;2;73;215;122mRV\x1b[0m", - output: "RV", - }, - { - input: "\x1b[38;2;118;15;221mJP\x1b[0m", - output: "JP", - }, - { - input: "\x1b[38;2;186;163;39mHJ\x1b[0m", - output: "HJ", - }, - { - input: "\x1b[38;2;54;222;111mDD\x1b[0m", - output: "DD", - }, - { - input: "\x1b[38;2;56;209;108mPZ\x1b[0m", - output: "PZ", - }, - { - input: "\x1b[38;2;9;179;216mPM\x1b[0m", - output: "PM", - }, - { - input: "\x1b[38;2;157;205;18mta\x1b[0m", - output: "ta", - }, - { - input: "a_" + style.PrintSimpleHyperlink("xyz") + "_b", - output: "a_xyz_b", - }, - } - - for _, test := range tests { - output := Decolorise(test.input) - if output != test.output { - t.Errorf("Decolorise(%s) = %s, want %s", test.input, output, test.output) - } - } -} diff --git a/pkg/utils/date.go b/pkg/utils/date.go deleted file mode 100644 index 9e8c8444598..00000000000 --- a/pkg/utils/date.go +++ /dev/null @@ -1,67 +0,0 @@ -package utils - -import ( - "fmt" - "time" -) - -func UnixToTimeAgo(timestamp int64) string { - now := time.Now().Unix() - return formatSecondsAgo(now - timestamp) -} - -const ( - SECONDS_IN_SECOND = 1 - SECONDS_IN_MINUTE = 60 - SECONDS_IN_HOUR = 3600 - SECONDS_IN_DAY = 86400 - SECONDS_IN_WEEK = 604800 - SECONDS_IN_YEAR = 31536000 - SECONDS_IN_MONTH = SECONDS_IN_YEAR / 12 -) - -type period struct { - label string - secondsInPeriod int64 -} - -var periods = []period{ - {"s", SECONDS_IN_SECOND}, - {"m", SECONDS_IN_MINUTE}, - {"h", SECONDS_IN_HOUR}, - {"d", SECONDS_IN_DAY}, - {"w", SECONDS_IN_WEEK}, - {"M", SECONDS_IN_MONTH}, - {"y", SECONDS_IN_YEAR}, -} - -func formatSecondsAgo(secondsAgo int64) string { - for i, period := range periods { - if i == 0 { - continue - } - - if secondsAgo < period.secondsInPeriod { - return fmt.Sprintf("%d%s", - secondsAgo/periods[i-1].secondsInPeriod, - periods[i-1].label, - ) - } - } - - return fmt.Sprintf("%d%s", - secondsAgo/periods[len(periods)-1].secondsInPeriod, - periods[len(periods)-1].label, - ) -} - -// formats the date in a smart way, if the date is today, it will show the time, otherwise it will show the date -func UnixToDateSmart(now time.Time, timestamp int64, longTimeFormat string, shortTimeFormat string) string { - date := time.Unix(timestamp, 0) - - if date.Day() == now.Day() && date.Month() == now.Month() && date.Year() == now.Year() { - return date.Format(shortTimeFormat) - } - - return date.Format(longTimeFormat) -} diff --git a/pkg/utils/date_test.go b/pkg/utils/date_test.go deleted file mode 100644 index 0162f5f6705..00000000000 --- a/pkg/utils/date_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package utils - -import ( - "testing" -) - -func TestFormatSecondsAgo(t *testing.T) { - tests := []struct { - name string - args int64 - want string - }{ - { - name: "zero", - args: 0, - want: "0s", - }, - { - name: "one second", - args: 1, - want: "1s", - }, - { - name: "almost a minute", - args: 59, - want: "59s", - }, - { - name: "one minute", - args: 60, - want: "1m", - }, - { - name: "one minute and one second", - args: 61, - want: "1m", - }, - { - name: "almost one hour", - args: 3599, - want: "59m", - }, - { - name: "one hour", - args: 3600, - want: "1h", - }, - { - name: "almost one day", - args: 86399, - want: "23h", - }, - { - name: "one day", - args: 86400, - want: "1d", - }, - { - name: "almost a week", - args: 604799, - want: "6d", - }, - { - name: "one week", - args: 604800, - want: "1w", - }, - { - name: "six months", - args: SECONDS_IN_YEAR / 2, - want: "6M", - }, - { - name: "almost one year", - args: 31535999, - want: "11M", - }, - { - name: "one year", - args: SECONDS_IN_YEAR, - want: "1y", - }, - { - name: "50 years", - args: SECONDS_IN_YEAR * 50, - want: "50y", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := formatSecondsAgo(tt.args); got != tt.want { - t.Errorf("formatSecondsAgo(%d) = %v, want %v", tt.args, got, tt.want) - } - }) - } -} diff --git a/pkg/utils/dummies.go b/pkg/utils/dummies.go deleted file mode 100644 index 2d9e73b035d..00000000000 --- a/pkg/utils/dummies.go +++ /dev/null @@ -1,14 +0,0 @@ -package utils - -import ( - "io" - - "github.com/sirupsen/logrus" -) - -// NewDummyLog creates a new dummy Log for testing -func NewDummyLog() *logrus.Entry { - log := logrus.New() - log.Out = io.Discard - return log.WithField("test", "test") -} diff --git a/pkg/utils/errors.go b/pkg/utils/errors.go deleted file mode 100644 index cd3cb686ca2..00000000000 --- a/pkg/utils/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -package utils - -import "github.com/go-errors/errors" - -// WrapError wraps an error for the sake of showing a stack trace at the top level -// the go-errors package, for some reason, does not return nil when you try to wrap -// a non-error, so we're just doing it here -func WrapError(err error) error { - if err == nil { - return err - } - - return errors.Wrap(err, 0) -} diff --git a/pkg/utils/formatting.go b/pkg/utils/formatting.go deleted file mode 100644 index 0e5a6ee031c..00000000000 --- a/pkg/utils/formatting.go +++ /dev/null @@ -1,209 +0,0 @@ -package utils - -import ( - "fmt" - "strings" - "unicode" - - "github.com/mattn/go-runewidth" - "github.com/samber/lo" - "golang.org/x/exp/slices" -) - -type Alignment int - -const ( - AlignLeft Alignment = iota - AlignRight -) - -type ColumnConfig struct { - Width int - Alignment Alignment -} - -func StringWidth(s string) int { - // We are intentionally not using a range loop here, because that would - // convert the characters to runes, which is unnecessary work in this case. - for i := range len(s) { - if s[i] > unicode.MaxASCII { - return runewidth.StringWidth(s) - } - } - - return len(s) -} - -// WithPadding pads a string as much as you want -func WithPadding(str string, padding int, alignment Alignment) string { - uncoloredStr := Decolorise(str) - width := StringWidth(uncoloredStr) - if padding < width { - return str - } - space := strings.Repeat(" ", padding-width) - if alignment == AlignLeft { - return str + space - } - return space + str -} - -// defaults to left-aligning each column. If you want to set the alignment of -// each column, pass in a slice of Alignment values. -// returns a list of strings that should be joined with "\n", and an array of -// the column positions -func RenderDisplayStrings(displayStringsArr [][]string, columnAlignments []Alignment) ([]string, []int) { - if len(displayStringsArr) == 0 { - return []string{}, nil - } - - displayStringsArr, columnAlignments, removedColumns := excludeBlankColumns(displayStringsArr, columnAlignments) - padWidths := getPadWidths(displayStringsArr) - columnConfigs := make([]ColumnConfig, len(padWidths)) - columnPositions := make([]int, len(padWidths)+1) - columnPositions[0] = 0 - for i, padWidth := range padWidths { - // gracefully handle when columnAlignments is shorter than padWidths - alignment := AlignLeft - if len(columnAlignments) > i { - alignment = columnAlignments[i] - } - - columnConfigs[i] = ColumnConfig{ - Width: padWidth, - Alignment: alignment, - } - columnPositions[i+1] = columnPositions[i] + padWidth + 1 - } - // Add the removed columns back into columnPositions (a removed column gets - // the same position as the following column); clients should be able to rely - // on them all to be there - for _, removedColumn := range removedColumns { - if removedColumn < len(columnPositions) { - columnPositions = slices.Insert(columnPositions, removedColumn, columnPositions[removedColumn]) - } - } - return getPaddedDisplayStrings(displayStringsArr, columnConfigs), columnPositions -} - -// NOTE: this mutates the input slice for the sake of performance -func excludeBlankColumns(displayStringsArr [][]string, columnAlignments []Alignment) ([][]string, []Alignment, []int) { - if len(displayStringsArr) == 0 { - return displayStringsArr, columnAlignments, []int{} - } - - // if all rows share a blank column, we want to remove that column - toRemove := []int{} -outer: - for i := range displayStringsArr[0] { - for _, strings := range displayStringsArr { - if strings[i] != "" { - continue outer - } - } - toRemove = append(toRemove, i) - } - - if len(toRemove) == 0 { - return displayStringsArr, columnAlignments, []int{} - } - - // remove the columns - for i, strings := range displayStringsArr { - for j := len(toRemove) - 1; j >= 0; j-- { - strings = slices.Delete(strings, toRemove[j], toRemove[j]+1) - } - displayStringsArr[i] = strings - } - - for j := len(toRemove) - 1; j >= 0; j-- { - if columnAlignments != nil && toRemove[j] < len(columnAlignments) { - columnAlignments = slices.Delete(columnAlignments, toRemove[j], toRemove[j]+1) - } - } - - return displayStringsArr, columnAlignments, toRemove -} - -func getPaddedDisplayStrings(stringArrays [][]string, columnConfigs []ColumnConfig) []string { - result := make([]string, 0, len(stringArrays)) - for _, stringArray := range stringArrays { - if len(stringArray) == 0 { - continue - } - builder := strings.Builder{} - for j, columnConfig := range columnConfigs { - if len(stringArray)-1 < j { - continue - } - builder.WriteString(WithPadding(stringArray[j], columnConfig.Width, columnConfig.Alignment)) - builder.WriteString(" ") - } - if len(stringArray)-1 < len(columnConfigs) { - continue - } - builder.WriteString(stringArray[len(columnConfigs)]) - result = append(result, builder.String()) - } - return result -} - -func getPadWidths(stringArrays [][]string) []int { - maxWidth := MaxFn(stringArrays, func(stringArray []string) int { - return len(stringArray) - }) - - if maxWidth-1 < 0 { - return []int{} - } - return lo.Map(lo.Range(maxWidth-1), func(i int, _ int) int { - return MaxFn(stringArrays, func(stringArray []string) int { - uncoloredStr := Decolorise(stringArray[i]) - - return StringWidth(uncoloredStr) - }) - }) -} - -func MaxFn[T any](items []T, fn func(T) int) int { - max := 0 - for _, item := range items { - if fn(item) > max { - max = fn(item) - } - } - return max -} - -// TruncateWithEllipsis returns a string, truncated to a certain length, with an ellipsis -func TruncateWithEllipsis(str string, limit int) string { - if StringWidth(str) > limit && limit <= 2 { - return strings.Repeat(".", limit) - } - return runewidth.Truncate(str, limit, "…") -} - -func SafeTruncate(str string, limit int) string { - if len(str) > limit { - return str[0:limit] - } - return str -} - -const COMMIT_HASH_SHORT_SIZE = 8 - -func ShortHash(hash string) string { - if len(hash) < COMMIT_HASH_SHORT_SIZE { - return hash - } - return hash[:COMMIT_HASH_SHORT_SIZE] -} - -// Returns comma-separated list of paths, with ellipsis if there are more than 3 -// e.g. "foo, bar, baz, [...3 more]" -func FormatPaths(paths []string) string { - if len(paths) <= 3 { - return strings.Join(paths, ", ") - } - return fmt.Sprintf("%s, %s, %s, [...%d more]", paths[0], paths[1], paths[2], len(paths)-3) -} diff --git a/pkg/utils/formatting_test.go b/pkg/utils/formatting_test.go deleted file mode 100644 index 37e6702a5f9..00000000000 --- a/pkg/utils/formatting_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package utils - -import ( - "strings" - "testing" - - "github.com/mattn/go-runewidth" - "github.com/stretchr/testify/assert" -) - -func TestWithPadding(t *testing.T) { - type scenario struct { - str string - padding int - alignment Alignment - expected string - } - - scenarios := []scenario{ - { - str: "hello world !", - padding: 1, - alignment: AlignLeft, - expected: "hello world !", - }, - { - str: "hello world !", - padding: 14, - alignment: AlignLeft, - expected: "hello world ! ", - }, - { - str: "hello world !", - padding: 14, - alignment: AlignRight, - expected: " hello world !", - }, - { - str: "Güçlü", - padding: 7, - alignment: AlignLeft, - expected: "Güçlü ", - }, - { - str: "Güçlü", - padding: 7, - alignment: AlignRight, - expected: " Güçlü", - }, - } - - for _, s := range scenarios { - assert.EqualValues(t, s.expected, WithPadding(s.str, s.padding, s.alignment)) - } -} - -func TestGetPadWidths(t *testing.T) { - type scenario struct { - input [][]string - expected []int - } - - tests := []scenario{ - { - [][]string{{""}, {""}}, - []int{}, - }, - { - [][]string{{"a"}, {""}}, - []int{}, - }, - { - [][]string{{"aa", "b", "ccc"}, {"c", "d", "e"}}, - []int{2, 1}, - }, - { - [][]string{{"AŁ", "b", "ccc"}, {"c", "d", "e"}}, - []int{2, 1}, - }, - } - - for _, test := range tests { - output := getPadWidths(test.input) - assert.EqualValues(t, test.expected, output) - } -} - -func TestTruncateWithEllipsis(t *testing.T) { - // will need to check chinese characters as well - // important that we have a three dot ellipsis within the limit - type scenario struct { - str string - limit int - expected string - } - - scenarios := []scenario{ - { - "hello world !", - 1, - ".", - }, - { - "hello world !", - 2, - "..", - }, - { - "hello world !", - 3, - "he…", - }, - { - "hello world !", - 4, - "hel…", - }, - { - "hello world !", - 5, - "hell…", - }, - { - "hello world !", - 12, - "hello world…", - }, - { - "hello world !", - 13, - "hello world !", - }, - { - "hello world !", - 14, - "hello world !", - }, - { - "大大大大", - 5, - "大大…", - }, - { - "大大大大", - 2, - "..", - }, - { - "大大大大", - 1, - ".", - }, - { - "大大大大", - 0, - "", - }, - } - - for _, s := range scenarios { - assert.EqualValues(t, s.expected, TruncateWithEllipsis(s.str, s.limit)) - } -} - -func TestRenderDisplayStrings(t *testing.T) { - type scenario struct { - input [][]string - columnAlignments []Alignment - expectedOutput string - expectedColumnPositions []int - } - - tests := []scenario{ - { - input: [][]string{{""}, {""}}, - columnAlignments: nil, - expectedOutput: "", - expectedColumnPositions: []int{0, 0}, - }, - { - input: [][]string{{"a"}, {""}}, - columnAlignments: nil, - expectedOutput: "a\n", - expectedColumnPositions: []int{0}, - }, - { - input: [][]string{{"a"}, {"b"}}, - columnAlignments: nil, - expectedOutput: "a\nb", - expectedColumnPositions: []int{0}, - }, - { - input: [][]string{{"a", "b"}, {"c", "d"}}, - columnAlignments: nil, - expectedOutput: "a b\nc d", - expectedColumnPositions: []int{0, 2}, - }, - { - input: [][]string{{"a", "", "c"}, {"d", "", "f"}}, - columnAlignments: nil, - expectedOutput: "a c\nd f", - expectedColumnPositions: []int{0, 2, 2}, - }, - { - input: [][]string{{"a", "", "c", ""}, {"d", "", "f", ""}}, - columnAlignments: nil, - expectedOutput: "a c\nd f", - expectedColumnPositions: []int{0, 2, 2}, - }, - { - input: [][]string{{"abc", "", "d", ""}, {"e", "", "f", ""}}, - columnAlignments: nil, - expectedOutput: "abc d\ne f", - expectedColumnPositions: []int{0, 4, 4}, - }, - { - input: [][]string{{"", "abc", "", "", "d", "e"}, {"", "f", "", "", "g", "h"}}, - columnAlignments: nil, - expectedOutput: "abc d e\nf g h", - expectedColumnPositions: []int{0, 0, 4, 4, 4, 6}, - }, - { - input: [][]string{{"abc", "", "d", ""}, {"e", "", "f", ""}}, - columnAlignments: []Alignment{AlignLeft, AlignLeft}, // same as nil (default) - expectedOutput: "abc d\ne f", - expectedColumnPositions: []int{0, 4, 4}, - }, - { - input: [][]string{{"abc", "", "d", ""}, {"e", "", "f", ""}}, - columnAlignments: []Alignment{AlignRight, AlignLeft}, - expectedOutput: "abc d\n e f", - expectedColumnPositions: []int{0, 4, 4}, - }, - { - input: [][]string{{"a", "", "bcd", "efg", "h"}, {"i", "", "j", "k", "l"}}, - columnAlignments: []Alignment{AlignLeft, AlignLeft, AlignRight, AlignLeft}, - expectedOutput: "a bcd efg h\ni j k l", - expectedColumnPositions: []int{0, 2, 2, 6, 10}, - }, - { - input: [][]string{{"abc", "", "d", ""}, {"e", "", "f", ""}}, - columnAlignments: []Alignment{AlignRight}, // gracefully defaults unspecified columns to left-align - expectedOutput: "abc d\n e f", - expectedColumnPositions: []int{0, 4, 4}, - }, - } - - for _, test := range tests { - output, columnPositions := RenderDisplayStrings(test.input, test.columnAlignments) - assert.EqualValues(t, test.expectedOutput, strings.Join(output, "\n")) - assert.EqualValues(t, test.expectedColumnPositions, columnPositions) - } -} - -func BenchmarkStringWidthAsciiOriginal(b *testing.B) { - for b.Loop() { - runewidth.StringWidth("some ASCII string") - } -} - -func BenchmarkStringWidthAsciiOptimized(b *testing.B) { - for b.Loop() { - StringWidth("some ASCII string") - } -} - -func BenchmarkStringWidthNonAsciiOriginal(b *testing.B) { - for b.Loop() { - runewidth.StringWidth("some non-ASCII string 🍉") - } -} - -func BenchmarkStringWidthNonAsciiOptimized(b *testing.B) { - for b.Loop() { - StringWidth("some non-ASCII string 🍉") - } -} diff --git a/pkg/utils/history_buffer.go b/pkg/utils/history_buffer.go deleted file mode 100644 index 670004d022b..00000000000 --- a/pkg/utils/history_buffer.go +++ /dev/null @@ -1,38 +0,0 @@ -package utils - -import ( - "errors" -) - -type HistoryBuffer[T any] struct { - maxSize int - items []T -} - -func NewHistoryBuffer[T any](maxSize int) *HistoryBuffer[T] { - return &HistoryBuffer[T]{ - maxSize: maxSize, - items: make([]T, 0, maxSize), - } -} - -func (self *HistoryBuffer[T]) Push(item T) { - if len(self.items) == self.maxSize { - self.items = self.items[:len(self.items)-1] - } - self.items = append([]T{item}, self.items...) -} - -func (self *HistoryBuffer[T]) PeekAt(index int) (T, error) { - var item T - if len(self.items) == 0 { - return item, errors.New("Buffer is empty") - } - if len(self.items) <= index || index < -1 { - return item, errors.New("Index out of range") - } - if index == -1 { - return item, nil - } - return self.items[index], nil -} diff --git a/pkg/utils/history_buffer_test.go b/pkg/utils/history_buffer_test.go deleted file mode 100644 index 51644d42d7f..00000000000 --- a/pkg/utils/history_buffer_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package utils - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNewHistoryBuffer(t *testing.T) { - hb := NewHistoryBuffer[int](5) - assert.NotNil(t, hb) - assert.Equal(t, 5, hb.maxSize) - assert.Equal(t, 0, len(hb.items)) -} - -func TestPush(t *testing.T) { - hb := NewHistoryBuffer[int](3) - hb.Push(1) - hb.Push(2) - hb.Push(3) - hb.Push(4) - - assert.Equal(t, 3, len(hb.items)) - assert.Equal(t, []int{4, 3, 2}, hb.items) -} - -func TestPeekAt(t *testing.T) { - hb := NewHistoryBuffer[int](3) - hb.Push(1) - hb.Push(2) - hb.Push(3) - - item, err := hb.PeekAt(0) - assert.Nil(t, err) - assert.Equal(t, 3, item) - - item, err = hb.PeekAt(1) - assert.Nil(t, err) - assert.Equal(t, 2, item) - - item, err = hb.PeekAt(2) - assert.Nil(t, err) - assert.Equal(t, 1, item) - - item, err = hb.PeekAt(-1) - assert.Nil(t, err) - assert.Equal(t, 0, item) - - _, err = hb.PeekAt(3) - assert.NotNil(t, err) - assert.Equal(t, "Index out of range", err.Error()) - - _, err = hb.PeekAt(-2) - assert.NotNil(t, err) - assert.Equal(t, "Index out of range", err.Error()) -} - -func TestPeekAtEmptyBuffer(t *testing.T) { - hb := NewHistoryBuffer[int](3) - - _, err := hb.PeekAt(0) - assert.NotNil(t, err) - assert.Equal(t, "Buffer is empty", err.Error()) -} diff --git a/pkg/utils/io.go b/pkg/utils/io.go deleted file mode 100644 index 1d222746b95..00000000000 --- a/pkg/utils/io.go +++ /dev/null @@ -1,30 +0,0 @@ -package utils - -import ( - "bufio" - "io" - "os" -) - -func ForEachLineInFile(path string, f func(string, int)) error { - file, err := os.Open(path) - if err != nil { - return err - } - defer file.Close() - - forEachLineInStream(file, f) - - return nil -} - -func forEachLineInStream(reader io.Reader, f func(string, int)) { - bufferedReader := bufio.NewReader(reader) - for i := 0; true; i++ { - line, _ := bufferedReader.ReadString('\n') - if len(line) == 0 { - break - } - f(line, i) - } -} diff --git a/pkg/utils/io_test.go b/pkg/utils/io_test.go deleted file mode 100644 index b8dfa957c43..00000000000 --- a/pkg/utils/io_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package utils - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_forEachLineInStream(t *testing.T) { - scenarios := []struct { - name string - input string - expectedLines []string - }{ - { - name: "empty input", - input: "", - expectedLines: []string{}, - }, - { - name: "single line", - input: "abc\n", - expectedLines: []string{"abc\n"}, - }, - { - name: "single line without line feed", - input: "abc", - expectedLines: []string{"abc"}, - }, - { - name: "multiple lines", - input: "abc\ndef\n", - expectedLines: []string{"abc\n", "def\n"}, - }, - { - name: "multiple lines including empty lines", - input: "abc\n\ndef\n", - expectedLines: []string{"abc\n", "\n", "def\n"}, - }, - { - name: "multiple lines without linefeed at end of file", - input: "abc\ndef\nghi", - expectedLines: []string{"abc\n", "def\n", "ghi"}, - }, - } - - for _, s := range scenarios { - t.Run(s.name, func(t *testing.T) { - lines := []string{} - forEachLineInStream(strings.NewReader(s.input), func(line string, i int) { - lines = append(lines, line) - }) - assert.EqualValues(t, s.expectedLines, lines) - }) - } -} diff --git a/pkg/utils/lines.go b/pkg/utils/lines.go deleted file mode 100644 index 576f4837183..00000000000 --- a/pkg/utils/lines.go +++ /dev/null @@ -1,189 +0,0 @@ -package utils - -import ( - "bytes" - "strings" - - "github.com/mattn/go-runewidth" -) - -// SplitLines takes a multiline string and splits it on newlines -// currently we are also stripping \r's which may have adverse effects for -// windows users (but no issues have been raised yet) -func SplitLines(multilineString string) []string { - multilineString = strings.ReplaceAll(multilineString, "\r", "") - if multilineString == "" || multilineString == "\n" { - return make([]string, 0) - } - lines := strings.Split(multilineString, "\n") - if lines[len(lines)-1] == "" { - return lines[:len(lines)-1] - } - return lines -} - -func SplitNul(str string) []string { - if str == "" { - return make([]string, 0) - } - str = strings.TrimSuffix(str, "\x00") - return strings.Split(str, "\x00") -} - -// NormalizeLinefeeds - Removes all Windows and Mac style line feeds -func NormalizeLinefeeds(str string) string { - str = strings.ReplaceAll(str, "\r\n", "\n") - str = strings.ReplaceAll(str, "\r", "") - return str -} - -// EscapeSpecialChars - Replaces all special chars like \n with \\n -func EscapeSpecialChars(str string) string { - return strings.NewReplacer( - "\n", "\\n", - "\r", "\\r", - "\t", "\\t", - "\b", "\\b", - "\f", "\\f", - "\v", "\\v", - ).Replace(str) -} - -func dropCR(data []byte) []byte { - if len(data) > 0 && data[len(data)-1] == '\r' { - return data[0 : len(data)-1] - } - return data -} - -// ScanLinesAndTruncateWhenLongerThanBuffer returns a split function that can be -// used with bufio.Scanner.Split(). It is very similar to bufio.ScanLines, -// except that it will truncate lines that are longer than the scanner's read -// buffer (whereas bufio.ScanLines will return an error in that case, which is -// often difficult to handle). -// -// If you are using your own buffer for the scanner, you must set maxBufferSize -// to the same value as the max parameter that you passed to scanner.Buffer(). -// Otherwise, maxBufferSize must be set to bufio.MaxScanTokenSize. -func ScanLinesAndTruncateWhenLongerThanBuffer(maxBufferSize int) func(data []byte, atEOF bool) (int, []byte, error) { - skipOverRemainderOfLongLine := false - - return func(data []byte, atEOF bool) (int, []byte, error) { - if atEOF && len(data) == 0 { - // Done - return 0, nil, nil - } - if i := bytes.IndexByte(data, '\n'); i >= 0 { - if skipOverRemainderOfLongLine { - skipOverRemainderOfLongLine = false - return i + 1, nil, nil - } - return i + 1, dropCR(data[0:i]), nil - } - if atEOF { - if skipOverRemainderOfLongLine { - return len(data), nil, nil - } - - return len(data), dropCR(data), nil - } - - // Buffer is full, so we can't get more data - if len(data) >= maxBufferSize { - if skipOverRemainderOfLongLine { - return len(data), nil, nil - } - - skipOverRemainderOfLongLine = true - return len(data), data, nil - } - - // Request more data. - return 0, nil, nil - } -} - -// Wrap lines to a given width, and return: -// - the wrapped lines -// - the line indices of the wrapped lines, indexed by the original line indices -// - the line indices of the original lines, indexed by the wrapped line indices -// If wrap is false, the text is returned as is. -// This code needs to behave the same as `gocui.lineWrap` does. -func WrapViewLinesToWidth(wrap bool, editable bool, text string, width int, tabWidth int) ([]string, []int, []int) { - if !editable { - text = strings.TrimSuffix(text, "\n") - } - lines := strings.Split(text, "\n") - if !wrap { - indices := make([]int, len(lines)) - for i := range lines { - indices[i] = i - } - return lines, indices, indices - } - - wrappedLines := make([]string, 0, len(lines)) - wrappedLineIndices := make([]int, 0, len(lines)) - originalLineIndices := make([]int, 0, len(lines)) - - if tabWidth < 1 { - tabWidth = 4 - } - - for originalLineIdx, line := range lines { - wrappedLineIndices = append(wrappedLineIndices, len(wrappedLines)) - - // convert tabs to spaces - for i := 0; i < len(line); i++ { - if line[i] == '\t' { - numSpaces := tabWidth - (i % tabWidth) - line = line[:i] + strings.Repeat(" ", numSpaces) + line[i+1:] - i += numSpaces - 1 - } - } - - appendWrappedLine := func(str string) { - wrappedLines = append(wrappedLines, str) - originalLineIndices = append(originalLineIndices, originalLineIdx) - } - - n := 0 - offset := 0 - lastWhitespaceIndex := -1 - for i, currChr := range line { - rw := runewidth.RuneWidth(currChr) - n += rw - - if n > width { - if currChr == ' ' { - appendWrappedLine(line[offset:i]) - offset = i + 1 - n = 0 - } else if currChr == '-' { - appendWrappedLine(line[offset:i]) - offset = i - n = rw - } else if lastWhitespaceIndex != -1 { - if line[lastWhitespaceIndex] == '-' { - appendWrappedLine(line[offset : lastWhitespaceIndex+1]) - } else { - appendWrappedLine(line[offset:lastWhitespaceIndex]) - } - offset = lastWhitespaceIndex + 1 - n = runewidth.StringWidth(line[offset : i+1]) - } else { - appendWrappedLine(line[offset:i]) - offset = i - n = rw - } - lastWhitespaceIndex = -1 - } else if currChr == ' ' || currChr == '-' { - lastWhitespaceIndex = i - } - } - - appendWrappedLine(line[offset:]) - } - - return wrappedLines, wrappedLineIndices, originalLineIndices -} diff --git a/pkg/utils/lines_test.go b/pkg/utils/lines_test.go deleted file mode 100644 index 973310a33ca..00000000000 --- a/pkg/utils/lines_test.go +++ /dev/null @@ -1,449 +0,0 @@ -package utils - -import ( - "bufio" - "strings" - "testing" - - "github.com/jesseduffield/gocui" - "github.com/stretchr/testify/assert" -) - -// TestSplitLines is a function. -func TestSplitLines(t *testing.T) { - type scenario struct { - multilineString string - expected []string - } - - scenarios := []scenario{ - { - "", - []string{}, - }, - { - "\n", - []string{}, - }, - { - "hello world !\nhello universe !\n", - []string{ - "hello world !", - "hello universe !", - }, - }, - } - - for _, s := range scenarios { - assert.EqualValues(t, s.expected, SplitLines(s.multilineString)) - } -} - -func TestSplitNul(t *testing.T) { - type scenario struct { - multilineString string - expected []string - } - - scenarios := []scenario{ - { - "", - []string{}, - }, - { - "\x00", - []string{ - "", - }, - }, - { - "hello world !\x00hello universe !\x00", - []string{ - "hello world !", - "hello universe !", - }, - }, - } - - for _, s := range scenarios { - assert.EqualValues(t, s.expected, SplitNul(s.multilineString)) - } -} - -// TestNormalizeLinefeeds is a function. -func TestNormalizeLinefeeds(t *testing.T) { - type scenario struct { - byteArray []byte - expected []byte - } - scenarios := []scenario{ - { - // \r\n - []byte{97, 115, 100, 102, 13, 10}, - []byte{97, 115, 100, 102, 10}, - }, - { - // bash\r\nblah - []byte{97, 115, 100, 102, 13, 10, 97, 115, 100, 102}, - []byte{97, 115, 100, 102, 10, 97, 115, 100, 102}, - }, - { - // \r - []byte{97, 115, 100, 102, 13}, - []byte{97, 115, 100, 102}, - }, - { - // \n - []byte{97, 115, 100, 102, 10}, - []byte{97, 115, 100, 102, 10}, - }, - } - - for _, s := range scenarios { - assert.EqualValues(t, string(s.expected), NormalizeLinefeeds(string(s.byteArray))) - } -} - -func TestScanLinesAndTruncateWhenLongerThanBuffer(t *testing.T) { - type scenario struct { - input string - expectedLines []string - } - - scenarios := []scenario{ - { - "", - []string{}, - }, - { - "\n", - []string{""}, - }, - { - "abc", - []string{"abc"}, - }, - { - "abc\ndef", - []string{"abc", "def"}, - }, - { - "abc\n\ndef", - []string{"abc", "", "def"}, - }, - { - "abc\r\ndef\r", - []string{"abc", "def"}, - }, - { - "abcdef", - []string{"abcde"}, - }, - { - "abcdef\n", - []string{"abcde"}, - }, - { - "abcdef\nghijkl\nx", - []string{"abcde", "ghijk", "x"}, - }, - { - "abc\ndefghijklmnopqrstuvw\nx", - []string{"abc", "defgh", "x"}, - }, - } - - for _, s := range scenarios { - scanner := bufio.NewScanner(strings.NewReader(s.input)) - scanner.Buffer(make([]byte, 5), 5) - scanner.Split(ScanLinesAndTruncateWhenLongerThanBuffer(5)) - result := []string{} - for scanner.Scan() { - result = append(result, scanner.Text()) - } - assert.NoError(t, scanner.Err()) - assert.EqualValues(t, s.expectedLines, result) - } -} - -func TestWrapViewLinesToWidth(t *testing.T) { - tests := []struct { - name string - wrap bool - editable bool - text string - width int - tabWidth int - expectedWrappedLines []string - expectedWrappedLinesIndices []int - expectedOriginalLinesIndices []int - }{ - { - name: "Wrap off", - wrap: false, - text: "1st line\n2nd line\n3rd line", - width: 5, - expectedWrappedLines: []string{ - "1st line", - "2nd line", - "3rd line", - }, - expectedWrappedLinesIndices: []int{0, 1, 2}, - expectedOriginalLinesIndices: []int{0, 1, 2}, - }, - { - name: "Wrap on space", - wrap: true, - text: "Hello World", - width: 5, - expectedWrappedLines: []string{ - "Hello", - "World", - }, - expectedWrappedLinesIndices: []int{0}, - expectedOriginalLinesIndices: []int{0, 0}, - }, - { - name: "Wrap on hyphen", - wrap: true, - text: "Hello-World", - width: 6, - expectedWrappedLines: []string{ - "Hello-", - "World", - }, - }, - { - name: "Wrap on hyphen 2", - wrap: true, - text: "Blah Hello-World", - width: 12, - expectedWrappedLines: []string{ - "Blah Hello-", - "World", - }, - }, - { - name: "Wrap on hyphen 3", - wrap: true, - text: "Blah Hello-World", - width: 11, - expectedWrappedLines: []string{ - "Blah Hello-", - "World", - }, - }, - { - name: "Wrap on hyphen 4", - wrap: true, - text: "Blah Hello-World", - width: 10, - expectedWrappedLines: []string{ - "Blah Hello", - "-World", - }, - }, - { - name: "Wrap on space 2", - wrap: true, - text: "Blah Hello World", - width: 10, - expectedWrappedLines: []string{ - "Blah Hello", - "World", - }, - }, - { - name: "Wrap on space with more words", - wrap: true, - text: "Longer word here", - width: 10, - expectedWrappedLines: []string{ - "Longer", - "word here", - }, - }, - { - name: "Split word that's too long", - wrap: true, - text: "ThisWordIsWayTooLong", - width: 10, - expectedWrappedLines: []string{ - "ThisWordIs", - "WayTooLong", - }, - }, - { - name: "Split word that's too long over multiple lines", - wrap: true, - text: "ThisWordIsWayTooLong", - width: 5, - expectedWrappedLines: []string{ - "ThisW", - "ordIs", - "WayTo", - "oLong", - }, - }, - { - name: "Lots of hyphens", - wrap: true, - text: "one-two-three-four-five", - width: 8, - expectedWrappedLines: []string{ - "one-two-", - "three-", - "four-", - "five", - }, - }, - { - name: "Several lines using all the available width", - wrap: true, - text: "aaa bb cc ddd-ee ff", - width: 5, - expectedWrappedLines: []string{ - "aaa", - "bb cc", - "ddd-", - "ee ff", - }, - }, - { - name: "Several lines using all the available width, with multi-cell runes", - wrap: true, - text: "🐤🐤🐤 🐝🐝 🙉🙉 🦊🦊🦊-🐬🐬 🦢🦢", - width: 9, - expectedWrappedLines: []string{ - "🐤🐤🐤", - "🐝🐝 🙉🙉", - "🦊🦊🦊-", - "🐬🐬 🦢🦢", - }, - }, - { - name: "Space in last column", - wrap: true, - text: "hello world", - width: 6, - expectedWrappedLines: []string{ - "hello", - "world", - }, - }, - { - name: "Hyphen in last column", - wrap: true, - text: "hello-world", - width: 6, - expectedWrappedLines: []string{ - "hello-", - "world", - }, - }, - { - name: "English text", - wrap: true, - text: "+The sea reach of the Thames stretched before us like the bedinnind of an interminable waterway. In the offind the sea and the sky were welded todether without a joint, and in the luminous space the tanned sails of the bardes drifting blah blah", - width: 81, - expectedWrappedLines: []string{ - "+The sea reach of the Thames stretched before us like the bedinnind of an", - "interminable waterway. In the offind the sea and the sky were welded todether", - "without a joint, and in the luminous space the tanned sails of the bardes", - "drifting blah blah", - }, - }, - { - name: "Tabs, width 4", - wrap: true, - text: "\ta\tbb\tccc\tdddd\teeeee", - width: 50, - tabWidth: 4, - expectedWrappedLines: []string{ - " a bb ccc dddd eeeee", - }, - }, - { - name: "Tabs, width 8", - wrap: true, - text: "\ta\tbb\tccc\tdddddddd\teeeee", - width: 100, - tabWidth: 8, - expectedWrappedLines: []string{ - " a bb ccc dddddddd eeeee", - }, - }, - { - name: "Multiple lines", - wrap: true, - text: "First paragraph\nThe second paragraph is a bit longer.\nThird paragraph\n", - width: 10, - expectedWrappedLines: []string{ - "First", - "paragraph", - "The second", - "paragraph", - "is a bit", - "longer.", - "Third", - "paragraph", - }, - expectedWrappedLinesIndices: []int{0, 2, 6}, - expectedOriginalLinesIndices: []int{0, 0, 1, 1, 1, 1, 2, 2}, - }, - { - name: "Avoid blank line at end if not editable", - wrap: true, - editable: false, - text: "First\nSecond\nThird\n", - width: 10, - expectedWrappedLines: []string{ - "First", - "Second", - "Third", - }, - expectedWrappedLinesIndices: []int{0, 1, 2}, - expectedOriginalLinesIndices: []int{0, 1, 2}, - }, - { - name: "Keep blank line at end if editable", - wrap: true, - editable: true, - text: "First\nSecond\nThird\n", - width: 10, - expectedWrappedLines: []string{ - "First", - "Second", - "Third", - "", - }, - expectedWrappedLinesIndices: []int{0, 1, 2, 3}, - expectedOriginalLinesIndices: []int{0, 1, 2, 3}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tabWidth := tt.tabWidth - if tabWidth == 0 { - tabWidth = 4 - } - wrappedLines, wrappedLinesIndices, originalLinesIndices := WrapViewLinesToWidth(tt.wrap, tt.editable, tt.text, tt.width, tabWidth) - assert.Equal(t, tt.expectedWrappedLines, wrappedLines) - if tt.expectedWrappedLinesIndices != nil { - assert.Equal(t, tt.expectedWrappedLinesIndices, wrappedLinesIndices) - } - if tt.expectedOriginalLinesIndices != nil { - assert.Equal(t, tt.expectedOriginalLinesIndices, originalLinesIndices) - } - - // As a sanity check, also test that gocui's line wrapping behaves the same way - view := gocui.NewView("", 0, 0, tt.width+1, 1000, gocui.OutputNormal) - view.TabWidth = tabWidth - assert.Equal(t, tt.width, view.InnerWidth()) - view.Wrap = tt.wrap - view.Editable = tt.editable - view.SetContent(tt.text) - assert.Equal(t, wrappedLines, view.ViewBufferLines()) - }) - } -} diff --git a/pkg/utils/once_writer.go b/pkg/utils/once_writer.go deleted file mode 100644 index aecf20369c7..00000000000 --- a/pkg/utils/once_writer.go +++ /dev/null @@ -1,31 +0,0 @@ -package utils - -import ( - "io" - "sync" -) - -// This wraps a writer and ensures that before we actually write anything we call a given function first - -type OnceWriter struct { - writer io.Writer - once sync.Once - f func() -} - -var _ io.Writer = &OnceWriter{} - -func NewOnceWriter(writer io.Writer, f func()) *OnceWriter { - return &OnceWriter{ - writer: writer, - f: f, - } -} - -func (self *OnceWriter) Write(p []byte) (n int, err error) { - self.once.Do(func() { - self.f() - }) - - return self.writer.Write(p) -} diff --git a/pkg/utils/once_writer_test.go b/pkg/utils/once_writer_test.go deleted file mode 100644 index aa57ff16319..00000000000 --- a/pkg/utils/once_writer_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package utils - -import ( - "bytes" - "testing" -) - -func TestOnceWriter(t *testing.T) { - innerWriter := bytes.NewBuffer(nil) - counter := 0 - onceWriter := NewOnceWriter(innerWriter, func() { - counter++ - }) - _, _ = onceWriter.Write([]byte("hello")) - _, _ = onceWriter.Write([]byte("hello")) - if counter != 1 { - t.Errorf("expected counter to be 1, got %d", counter) - } -} diff --git a/pkg/utils/rebase_todo.go b/pkg/utils/rebase_todo.go deleted file mode 100644 index e2c9dc4421b..00000000000 --- a/pkg/utils/rebase_todo.go +++ /dev/null @@ -1,318 +0,0 @@ -package utils - -import ( - "bytes" - "errors" - "fmt" - "os" - "slices" - - "github.com/samber/lo" - "github.com/stefanhaller/git-todo-parser/todo" -) - -type Todo struct { - Hash string // for todos that have one, e.g. pick, drop, fixup, etc. - Ref string // for update-ref todos -} - -type TodoChange struct { - Hash string - NewAction todo.TodoCommand -} - -// Read a git-rebase-todo file, change the actions for the given commits, -// and write it back -func EditRebaseTodo(filePath string, changes []TodoChange, commentChar byte) error { - todos, err := ReadRebaseTodoFile(filePath, commentChar) - if err != nil { - return err - } - - matchCount := 0 - for i := range todos { - t := &todos[i] - // This is a nested loop, but it's ok because the number of todos should be small - for _, change := range changes { - if equalHash(t.Commit, change.Hash) { - matchCount++ - t.Command = change.NewAction - } - } - } - - if matchCount < len(changes) { - // Should never get here - return errors.New("Some todos not found in git-rebase-todo") - } - - return WriteRebaseTodoFile(filePath, todos, commentChar) -} - -func equalHash(a, b string) bool { - if len(a) == 0 && len(b) == 0 { - return true - } - - commonLength := min(len(a), len(b)) - return commonLength > 0 && a[:commonLength] == b[:commonLength] -} - -func findTodo(todos []todo.Todo, todoToFind Todo) (int, bool) { - _, idx, ok := lo.FindIndexOf(todos, func(t todo.Todo) bool { - // For update-ref todos we also must compare the Ref (they have an empty hash) - return equalHash(t.Commit, todoToFind.Hash) && t.Ref == todoToFind.Ref - }) - return idx, ok -} - -func ReadRebaseTodoFile(fileName string, commentChar byte) ([]todo.Todo, error) { - f, err := os.Open(fileName) - if err != nil { - return nil, err - } - - todos, err := todo.Parse(f, commentChar) - err2 := f.Close() - if err == nil { - err = err2 - } - return todos, err -} - -func WriteRebaseTodoFile(fileName string, todos []todo.Todo, commentChar byte) error { - f, err := os.Create(fileName) - if err != nil { - return err - } - err = todo.Write(f, todos, commentChar) - err2 := f.Close() - if err == nil { - err = err2 - } - return err -} - -func todosToString(todos []todo.Todo, commentChar byte) ([]byte, error) { - buffer := bytes.Buffer{} - err := todo.Write(&buffer, todos, commentChar) - return buffer.Bytes(), err -} - -func PrependStrToTodoFile(filePath string, linesToPrepend []byte) error { - existingContent, err := os.ReadFile(filePath) - if err != nil { - return err - } - - linesToPrepend = append(linesToPrepend, existingContent...) - return os.WriteFile(filePath, linesToPrepend, 0o644) -} - -// Unlike the other functions in this file, which write the changed todos file -// back to disk, this one returns the new content as a byte slice. This is -// because when deleting update-ref todos, we must perform a "git rebase -// --edit-todo" command to pass the changed todos to git so that it can do some -// housekeeping around the deleted todos. This can only be done by our caller. -func DeleteTodos(fileName string, todosToDelete []Todo, commentChar byte) ([]byte, error) { - todos, err := ReadRebaseTodoFile(fileName, commentChar) - if err != nil { - return nil, err - } - rearrangedTodos, err := deleteTodos(todos, todosToDelete) - if err != nil { - return nil, err - } - return todosToString(rearrangedTodos, commentChar) -} - -func deleteTodos(todos []todo.Todo, todosToDelete []Todo) ([]todo.Todo, error) { - for _, todoToDelete := range todosToDelete { - idx, ok := findTodo(todos, todoToDelete) - - if !ok { - // Should never happen - return []todo.Todo{}, fmt.Errorf("Todo %s not found in git-rebase-todo", todoToDelete.Hash) - } - - todos = Remove(todos, idx) - } - - return todos, nil -} - -func MoveTodosDown(fileName string, todosToMove []Todo, isInRebase bool, commentChar byte) error { - todos, err := ReadRebaseTodoFile(fileName, commentChar) - if err != nil { - return err - } - rearrangedTodos, err := moveTodosDown(todos, todosToMove, isInRebase) - if err != nil { - return err - } - return WriteRebaseTodoFile(fileName, rearrangedTodos, commentChar) -} - -func MoveTodosUp(fileName string, todosToMove []Todo, isInRebase bool, commentChar byte) error { - todos, err := ReadRebaseTodoFile(fileName, commentChar) - if err != nil { - return err - } - rearrangedTodos, err := moveTodosUp(todos, todosToMove, isInRebase) - if err != nil { - return err - } - return WriteRebaseTodoFile(fileName, rearrangedTodos, commentChar) -} - -func moveTodoDown(todos []todo.Todo, todoToMove Todo, isInRebase bool) ([]todo.Todo, error) { - rearrangedTodos, err := moveTodoUp(lo.Reverse(todos), todoToMove, isInRebase) - return lo.Reverse(rearrangedTodos), err -} - -func moveTodosDown(todos []todo.Todo, todosToMove []Todo, isInRebase bool) ([]todo.Todo, error) { - rearrangedTodos, err := moveTodosUp(lo.Reverse(todos), lo.Reverse(todosToMove), isInRebase) - return lo.Reverse(rearrangedTodos), err -} - -func moveTodoUp(todos []todo.Todo, todoToMove Todo, isInRebase bool) ([]todo.Todo, error) { - sourceIdx, ok := findTodo(todos, todoToMove) - - if !ok { - // Should never happen - return []todo.Todo{}, fmt.Errorf("Todo %s not found in git-rebase-todo", todoToMove.Hash) - } - - // The todos are ordered backwards compared to our model commits, so - // actually move the commit _down_ in the todos slice (i.e. towards - // the end of the slice) - - // Find the next todo that we show in lazygit's commits view (skipping the rest) - _, skip, ok := lo.FindIndexOf(todos[sourceIdx+1:], func(t todo.Todo) bool { return isRenderedTodo(t, isInRebase) }) - - if !ok { - // We expect callers to guard against this - return []todo.Todo{}, errors.New("Destination position for moving todo is out of range") - } - - destinationIdx := sourceIdx + 1 + skip - - rearrangedTodos := MoveElement(todos, sourceIdx, destinationIdx) - - return rearrangedTodos, nil -} - -func moveTodosUp(todos []todo.Todo, todosToMove []Todo, isInRebase bool) ([]todo.Todo, error) { - for _, todoToMove := range todosToMove { - var newTodos []todo.Todo - newTodos, err := moveTodoUp(todos, todoToMove, isInRebase) - if err != nil { - return nil, err - } - todos = newTodos - } - - return todos, nil -} - -func MoveFixupCommitDown(fileName string, originalHash string, fixupHash string, changeToFixup bool, commentChar byte) error { - todos, err := ReadRebaseTodoFile(fileName, commentChar) - if err != nil { - return err - } - - newTodos, err := moveFixupCommitDown(todos, originalHash, fixupHash, changeToFixup) - if err != nil { - return err - } - - return WriteRebaseTodoFile(fileName, newTodos, commentChar) -} - -func moveFixupCommitDown(todos []todo.Todo, originalHash string, fixupHash string, changeToFixup bool) ([]todo.Todo, error) { - isOriginal := func(t todo.Todo) bool { - return (t.Command == todo.Pick || t.Command == todo.Merge) && equalHash(t.Commit, originalHash) - } - - isFixup := func(t todo.Todo) bool { - return t.Command == todo.Pick && equalHash(t.Commit, fixupHash) - } - - originalHashCount := lo.CountBy(todos, isOriginal) - if originalHashCount != 1 { - return nil, fmt.Errorf("Expected exactly one original hash, found %d", originalHashCount) - } - - fixupHashCount := lo.CountBy(todos, isFixup) - if fixupHashCount != 1 { - return nil, fmt.Errorf("Expected exactly one fixup hash, found %d", fixupHashCount) - } - - _, fixupIndex, _ := lo.FindIndexOf(todos, isFixup) - _, originalIndex, _ := lo.FindIndexOf(todos, isOriginal) - - newTodos := MoveElement(todos, fixupIndex, originalIndex+1) - - if changeToFixup { - newTodos[originalIndex+1].Command = todo.Fixup - } - - return newTodos, nil -} - -func RemoveUpdateRefsForCopiedBranch(fileName string, commentChar byte) error { - todos, err := ReadRebaseTodoFile(fileName, commentChar) - if err != nil { - return err - } - - // Filter out comments - todos = lo.Filter(todos, func(t todo.Todo, _ int) bool { - return t.Command != todo.Comment - }) - - // Delete any update-ref todos at the end of the todo list. These are not - // part of a stack of branches, and so shouldn't be updated. This makes it - // possible to create a copy of a branch and rebase the copy without - // affecting the original branch. - if _, i, found := lo.FindLastIndexOf(todos, func(t todo.Todo) bool { - return t.Command != todo.UpdateRef - }); found && i < len(todos)-1 { - todos = slices.Delete(todos, i+1, len(todos)) - return WriteRebaseTodoFile(fileName, todos, commentChar) - } - - return nil -} - -// We render a todo in the commits view if it's a commit or if it's an -// update-ref or exec. We don't render label, reset, or comment lines. -func isRenderedTodo(t todo.Todo, isInRebase bool) bool { - return t.Commit != "" || (isInRebase && (t.Command == todo.UpdateRef || t.Command == todo.Exec)) -} - -func DropMergeCommit(fileName string, hash string, commentChar byte) error { - todos, err := ReadRebaseTodoFile(fileName, commentChar) - if err != nil { - return err - } - - newTodos, err := dropMergeCommit(todos, hash) - if err != nil { - return err - } - - return WriteRebaseTodoFile(fileName, newTodos, commentChar) -} - -func dropMergeCommit(todos []todo.Todo, hash string) ([]todo.Todo, error) { - isMerge := func(t todo.Todo) bool { - return t.Command == todo.Merge && t.Flag == "-C" && equalHash(t.Commit, hash) - } - if lo.CountBy(todos, isMerge) != 1 { - return nil, fmt.Errorf("Expected exactly one merge commit with hash %s", hash) - } - - _, idx, _ := lo.FindIndexOf(todos, isMerge) - return slices.Delete(todos, idx, idx+1), nil -} diff --git a/pkg/utils/rebase_todo_test.go b/pkg/utils/rebase_todo_test.go deleted file mode 100644 index 9daf7db01c1..00000000000 --- a/pkg/utils/rebase_todo_test.go +++ /dev/null @@ -1,577 +0,0 @@ -package utils - -import ( - "errors" - "fmt" - "testing" - - "github.com/stefanhaller/git-todo-parser/todo" - "github.com/stretchr/testify/assert" -) - -func TestRebaseCommands_moveTodoDown(t *testing.T) { - type scenario struct { - testName string - todos []todo.Todo - todoToMoveDown Todo - isInRebase bool - expectedErr string - expectedTodos []todo.Todo - } - - scenarios := []scenario{ - { - testName: "simple case 1 - move to beginning", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "abcd"}, - }, - todoToMoveDown: Todo{Hash: "5678"}, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "abcd"}, - }, - }, - { - testName: "simple case 2 - move from end", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "abcd"}, - }, - todoToMoveDown: Todo{Hash: "abcd"}, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "abcd"}, - {Command: todo.Pick, Commit: "5678"}, - }, - }, - { - testName: "move update-ref todo", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - }, - todoToMoveDown: Todo{Ref: "refs/heads/some_branch"}, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - {Command: todo.Pick, Commit: "5678"}, - }, - }, - { - testName: "move across update-ref todo in rebase", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - {Command: todo.Pick, Commit: "5678"}, - }, - todoToMoveDown: Todo{Hash: "5678"}, - isInRebase: true, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - }, - }, - { - testName: "move across update-ref todo outside of rebase", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - {Command: todo.Pick, Commit: "5678"}, - }, - todoToMoveDown: Todo{Hash: "5678"}, - isInRebase: false, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - }, - }, - { - testName: "move across exec todo", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Exec, ExecCommand: "make test"}, - {Command: todo.Pick, Commit: "5678"}, - }, - todoToMoveDown: Todo{Hash: "5678"}, - isInRebase: true, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Exec, ExecCommand: "make test"}, - }, - }, - { - testName: "skip an invisible todo", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "abcd"}, - {Command: todo.Label, Label: "myLabel"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "def0"}, - }, - todoToMoveDown: Todo{Hash: "5678"}, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "abcd"}, - {Command: todo.Label, Label: "myLabel"}, - {Command: todo.Pick, Commit: "def0"}, - }, - }, - - // Error cases - { - testName: "commit not found", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "abcd"}, - }, - todoToMoveDown: Todo{Hash: "def0"}, - expectedErr: "Todo def0 not found in git-rebase-todo", - expectedTodos: []todo.Todo{}, - }, - { - testName: "trying to move first commit down", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "abcd"}, - }, - todoToMoveDown: Todo{Hash: "1234"}, - expectedErr: "Destination position for moving todo is out of range", - expectedTodos: []todo.Todo{}, - }, - { - testName: "trying to move commit down when all commits before are invisible", - todos: []todo.Todo{ - {Command: todo.Label, Label: "myLabel"}, - {Command: todo.Reset, Label: "otherlabel"}, - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - }, - todoToMoveDown: Todo{Hash: "1234"}, - expectedErr: "Destination position for moving todo is out of range", - expectedTodos: []todo.Todo{}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - rearrangedTodos, err := moveTodoDown(s.todos, s.todoToMoveDown, s.isInRebase) - if s.expectedErr == "" { - assert.NoError(t, err) - } else { - assert.ErrorContains(t, err, s.expectedErr) - } - assert.Equal(t, s.expectedTodos, rearrangedTodos) - }, - ) - } -} - -func TestRebaseCommands_moveTodoUp(t *testing.T) { - type scenario struct { - testName string - todos []todo.Todo - todoToMoveUp Todo - isInRebase bool - expectedErr string - expectedTodos []todo.Todo - } - - scenarios := []scenario{ - { - testName: "simple case 1 - move to end", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "abcd"}, - }, - todoToMoveUp: Todo{Hash: "5678"}, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "abcd"}, - {Command: todo.Pick, Commit: "5678"}, - }, - }, - { - testName: "simple case 2 - move from beginning", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "abcd"}, - }, - todoToMoveUp: Todo{Hash: "1234"}, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "abcd"}, - }, - }, - { - testName: "move update-ref todo", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - {Command: todo.Pick, Commit: "5678"}, - }, - todoToMoveUp: Todo{Ref: "refs/heads/some_branch"}, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - }, - }, - { - testName: "move across update-ref todo in rebase", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - {Command: todo.Pick, Commit: "5678"}, - }, - todoToMoveUp: Todo{Hash: "1234"}, - isInRebase: true, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - }, - }, - { - testName: "move across update-ref todo outside of rebase", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - {Command: todo.Pick, Commit: "5678"}, - }, - todoToMoveUp: Todo{Hash: "1234"}, - isInRebase: false, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "1234"}, - }, - }, - { - testName: "move across exec todo", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Exec, ExecCommand: "make test"}, - {Command: todo.Pick, Commit: "5678"}, - }, - todoToMoveUp: Todo{Hash: "1234"}, - isInRebase: true, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Exec, ExecCommand: "make test"}, - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - }, - }, - { - testName: "skip an invisible todo", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "abcd"}, - {Command: todo.Label, Label: "myLabel"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "def0"}, - }, - todoToMoveUp: Todo{Hash: "abcd"}, - expectedErr: "", - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Label, Label: "myLabel"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "abcd"}, - {Command: todo.Pick, Commit: "def0"}, - }, - }, - - // Error cases - { - testName: "commit not found", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "abcd"}, - }, - todoToMoveUp: Todo{Hash: "def0"}, - expectedErr: "Todo def0 not found in git-rebase-todo", - expectedTodos: []todo.Todo{}, - }, - { - testName: "trying to move last commit up", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "abcd"}, - }, - todoToMoveUp: Todo{Hash: "abcd"}, - expectedErr: "Destination position for moving todo is out of range", - expectedTodos: []todo.Todo{}, - }, - { - testName: "trying to move commit up when all commits after it are invisible", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Label, Label: "myLabel"}, - {Command: todo.Reset, Label: "otherlabel"}, - }, - todoToMoveUp: Todo{Hash: "5678"}, - expectedErr: "Destination position for moving todo is out of range", - expectedTodos: []todo.Todo{}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - rearrangedTodos, err := moveTodoUp(s.todos, s.todoToMoveUp, s.isInRebase) - if s.expectedErr == "" { - assert.NoError(t, err) - } else { - assert.ErrorContains(t, err, s.expectedErr) - } - assert.Equal(t, s.expectedTodos, rearrangedTodos) - }, - ) - } -} - -func TestRebaseCommands_moveFixupCommitDown(t *testing.T) { - scenarios := []struct { - name string - todos []todo.Todo - originalHash string - fixupHash string - changeToFixup bool - expectedTodos []todo.Todo - expectedErr error - }{ - { - name: "fixup commit is the last commit (change to fixup)", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "original"}, - {Command: todo.Pick, Commit: "fixup"}, - }, - originalHash: "original", - fixupHash: "fixup", - changeToFixup: true, - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "original"}, - {Command: todo.Fixup, Commit: "fixup"}, - }, - expectedErr: nil, - }, - { - name: "fixup commit is the last commit (don't change to fixup)", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "original"}, - {Command: todo.Pick, Commit: "fixup"}, - }, - originalHash: "original", - fixupHash: "fixup", - changeToFixup: false, - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "original"}, - {Command: todo.Pick, Commit: "fixup"}, - }, - expectedErr: nil, - }, - { - name: "fixup commit is separated from original commit", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "original"}, - {Command: todo.Pick, Commit: "other"}, - {Command: todo.Pick, Commit: "fixup"}, - }, - originalHash: "original", - fixupHash: "fixup", - changeToFixup: true, - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "original"}, - {Command: todo.Fixup, Commit: "fixup"}, - {Command: todo.Pick, Commit: "other"}, - }, - expectedErr: nil, - }, - { - name: "fixup commit is separated from original merge commit", - todos: []todo.Todo{ - {Command: todo.Merge, Commit: "original"}, - {Command: todo.Pick, Commit: "other"}, - {Command: todo.Pick, Commit: "fixup"}, - }, - originalHash: "original", - fixupHash: "fixup", - changeToFixup: true, - expectedTodos: []todo.Todo{ - {Command: todo.Merge, Commit: "original"}, - {Command: todo.Fixup, Commit: "fixup"}, - {Command: todo.Pick, Commit: "other"}, - }, - expectedErr: nil, - }, - { - name: "More original hashes than expected", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "original"}, - {Command: todo.Pick, Commit: "original"}, - {Command: todo.Pick, Commit: "fixup"}, - }, - originalHash: "original", - fixupHash: "fixup", - changeToFixup: true, - expectedTodos: nil, - expectedErr: errors.New("Expected exactly one original hash, found 2"), - }, - { - name: "More fixup hashes than expected", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "original"}, - {Command: todo.Pick, Commit: "fixup"}, - {Command: todo.Pick, Commit: "fixup"}, - }, - originalHash: "original", - fixupHash: "fixup", - changeToFixup: true, - expectedTodos: nil, - expectedErr: errors.New("Expected exactly one fixup hash, found 2"), - }, - { - name: "No fixup hashes found", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "original"}, - }, - originalHash: "original", - fixupHash: "fixup", - changeToFixup: true, - expectedTodos: nil, - expectedErr: errors.New("Expected exactly one fixup hash, found 0"), - }, - { - name: "No original hashes found", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "fixup"}, - }, - originalHash: "original", - fixupHash: "fixup", - changeToFixup: true, - expectedTodos: nil, - expectedErr: errors.New("Expected exactly one original hash, found 0"), - }, - } - - for _, scenario := range scenarios { - t.Run(scenario.name, func(t *testing.T) { - actualTodos, actualErr := moveFixupCommitDown(scenario.todos, scenario.originalHash, scenario.fixupHash, scenario.changeToFixup) - - if scenario.expectedErr == nil { - assert.NoError(t, actualErr) - } else { - assert.EqualError(t, actualErr, scenario.expectedErr.Error()) - } - - assert.EqualValues(t, scenario.expectedTodos, actualTodos) - }) - } -} - -func TestRebaseCommands_deleteTodos(t *testing.T) { - scenarios := []struct { - name string - todos []todo.Todo - todosToDelete []Todo - expectedTodos []todo.Todo - expectedErr error - }{ - { - name: "success", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.UpdateRef, Ref: "refs/heads/some_branch"}, - {Command: todo.Pick, Commit: "5678"}, - {Command: todo.Pick, Commit: "abcd"}, - }, - todosToDelete: []Todo{ - {Ref: "refs/heads/some_branch"}, - {Hash: "abcd"}, - }, - expectedTodos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - }, - expectedErr: nil, - }, - { - name: "failure", - todos: []todo.Todo{ - {Command: todo.Pick, Commit: "1234"}, - {Command: todo.Pick, Commit: "5678"}, - }, - todosToDelete: []Todo{ - {Hash: "abcd"}, - }, - expectedTodos: []todo.Todo{}, - expectedErr: errors.New("Todo abcd not found in git-rebase-todo"), - }, - } - - for _, scenario := range scenarios { - t.Run(scenario.name, func(t *testing.T) { - actualTodos, actualErr := deleteTodos(scenario.todos, scenario.todosToDelete) - - if scenario.expectedErr == nil { - assert.NoError(t, actualErr) - } else { - assert.EqualError(t, actualErr, scenario.expectedErr.Error()) - } - - assert.EqualValues(t, scenario.expectedTodos, actualTodos) - }) - } -} - -func Test_equalHash(t *testing.T) { - scenarios := []struct { - a string - b string - expected bool - }{ - {"", "", true}, - {"", "123", false}, - {"123", "", false}, - {"123", "123", true}, - {"123", "123abc", true}, - {"123abc", "123", true}, - {"123", "a", false}, - {"1", "abc", false}, - } - - for _, scenario := range scenarios { - t.Run(fmt.Sprintf("'%s' vs. '%s'", scenario.a, scenario.b), func(t *testing.T) { - assert.Equal(t, scenario.expected, equalHash(scenario.a, scenario.b)) - }) - } -} diff --git a/pkg/utils/regexp.go b/pkg/utils/regexp.go deleted file mode 100644 index 2ceab1d719b..00000000000 --- a/pkg/utils/regexp.go +++ /dev/null @@ -1,17 +0,0 @@ -package utils - -import "regexp" - -func FindNamedMatches(regex *regexp.Regexp, str string) map[string]string { - match := regex.FindStringSubmatch(str) - - if len(match) == 0 { - return nil - } - - results := map[string]string{} - for i, value := range match[1:] { - results[regex.SubexpNames()[i+1]] = value - } - return results -} diff --git a/pkg/utils/regexp_test.go b/pkg/utils/regexp_test.go deleted file mode 100644 index 03afe706533..00000000000 --- a/pkg/utils/regexp_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package utils - -import ( - "reflect" - "regexp" - "testing" -) - -func TestFindNamedMatches(t *testing.T) { - scenarios := []struct { - regex *regexp.Regexp - input string - expected map[string]string - }{ - { - regexp.MustCompile(`^(?P\w+)`), - "hello world", - map[string]string{ - "name": "hello", - }, - }, - { - regexp.MustCompile(`^https?://.*/(?P.*)/(?P.*?)(\.git)?$`), - "https://my_username@bitbucket.org/johndoe/social_network.git", - map[string]string{ - "owner": "johndoe", - "repo": "social_network", - "": ".git", // unnamed capture group - }, - }, - { - regexp.MustCompile(`(?Phello) world`), - "yo world", - nil, - }, - } - - for _, scenario := range scenarios { - actual := FindNamedMatches(scenario.regex, scenario.input) - if !reflect.DeepEqual(actual, scenario.expected) { - t.Errorf("FindNamedMatches(%s, %s) == %s, expected %s", scenario.regex, scenario.input, actual, scenario.expected) - } - } -} diff --git a/pkg/utils/search.go b/pkg/utils/search.go deleted file mode 100644 index c96cda4abbe..00000000000 --- a/pkg/utils/search.go +++ /dev/null @@ -1,95 +0,0 @@ -package utils - -import ( - "strings" - - "github.com/sahilm/fuzzy" - "github.com/samber/lo" -) - -func FilterStrings(needle string, haystack []string, useFuzzySearch bool) []string { - if needle == "" { - return []string{} - } - - matches := Find(needle, haystack, useFuzzySearch) - - return lo.Map(matches, func(match fuzzy.Match, _ int) string { - return match.Str - }) -} - -// Duplicated from the fuzzy package because it's private there -type stringSource []string - -func (ss stringSource) String(i int) string { - return ss[i] -} - -func (ss stringSource) Len() int { return len(ss) } - -// Drop-in replacement for fuzzy.Find (except that it doesn't fill out -// MatchedIndexes or Score, but we are not using these) -func FindSubstrings(pattern string, data []string) fuzzy.Matches { - return FindSubstringsFrom(pattern, stringSource(data)) -} - -// Drop-in replacement for fuzzy.FindFrom (except that it doesn't fill out -// MatchedIndexes or Score, but we are not using these) -func FindSubstringsFrom(pattern string, data fuzzy.Source) fuzzy.Matches { - substrings := strings.Fields(pattern) - result := fuzzy.Matches{} - -outer: - for i := range data.Len() { - s := data.String(i) - for _, sub := range substrings { - if !CaseAwareContains(s, sub) { - continue outer - } - } - result = append(result, fuzzy.Match{Str: s, Index: i}) - } - - return result -} - -func Find(pattern string, data []string, useFuzzySearch bool) fuzzy.Matches { - if useFuzzySearch { - return fuzzy.Find(pattern, data) - } - return FindSubstrings(pattern, data) -} - -func FindFrom(pattern string, data fuzzy.Source, useFuzzySearch bool) fuzzy.Matches { - if useFuzzySearch { - return fuzzy.FindFrom(pattern, data) - } - return FindSubstringsFrom(pattern, data) -} - -func CaseAwareContains(haystack, needle string) bool { - // if needle contains an uppercase letter, we'll do a case sensitive search - if ContainsUppercase(needle) { - return strings.Contains(haystack, needle) - } - - return CaseInsensitiveContains(haystack, needle) -} - -func ContainsUppercase(s string) bool { - for _, r := range s { - if r >= 'A' && r <= 'Z' { - return true - } - } - - return false -} - -func CaseInsensitiveContains(haystack, needle string) bool { - return strings.Contains( - strings.ToLower(haystack), - strings.ToLower(needle), - ) -} diff --git a/pkg/utils/search_test.go b/pkg/utils/search_test.go deleted file mode 100644 index ad5dd12253d..00000000000 --- a/pkg/utils/search_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package utils - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestFilterStrings(t *testing.T) { - type scenario struct { - needle string - haystack []string - useFuzzySearch bool - expected []string - } - - scenarios := []scenario{ - { - needle: "", - haystack: []string{"test"}, - useFuzzySearch: true, - expected: []string{}, - }, - { - needle: "test", - haystack: []string{"test"}, - useFuzzySearch: true, - expected: []string{"test"}, - }, - { - needle: "o", - haystack: []string{"a", "o", "e"}, - useFuzzySearch: true, - expected: []string{"o"}, - }, - { - needle: "mybranch", - haystack: []string{"my_branch", "mybranch", "branch", "this is my branch"}, - useFuzzySearch: true, - expected: []string{"mybranch", "my_branch", "this is my branch"}, - }, - { - needle: "test", - haystack: []string{"not a good match", "this 'test' is a good match", "test"}, - useFuzzySearch: true, - expected: []string{"test", "this 'test' is a good match"}, - }, - { - needle: "test", - haystack: []string{"Test"}, - useFuzzySearch: true, - expected: []string{"Test"}, - }, - { - needle: "test", - haystack: []string{"integration-testing", "t_e_s_t"}, - useFuzzySearch: false, - expected: []string{"integration-testing"}, - }, - { - needle: "integr test", - haystack: []string{"integration-testing", "testing-integration"}, - useFuzzySearch: false, - expected: []string{"integration-testing", "testing-integration"}, - }, - } - - for _, s := range scenarios { - assert.EqualValues(t, s.expected, FilterStrings(s.needle, s.haystack, s.useFuzzySearch)) - } -} - -func TestCaseInsensitiveContains(t *testing.T) { - testCases := []struct { - haystack string - needle string - expected bool - }{ - {"Hello, World!", "world", true}, // Case-insensitive match - {"Hello, World!", "WORLD", true}, // Case-insensitive match - {"Hello, World!", "orl", true}, // Case-insensitive match - {"Hello, World!", "o, W", true}, // Case-insensitive match - {"Hello, World!", "hello", true}, // Case-insensitive match - {"Hello, World!", "Foo", false}, // No match - {"Hello, World!", "Hello, World!!", false}, // No match - {"Hello, World!", "", true}, // Empty needle matches - {"", "Hello", false}, // Empty haystack doesn't match - {"", "", true}, // Empty strings match - {"", " ", false}, // Empty haystack, non-empty needle - {" ", "", true}, // Non-empty haystack, empty needle - } - - for i, testCase := range testCases { - result := CaseInsensitiveContains(testCase.haystack, testCase.needle) - assert.Equal(t, testCase.expected, result, fmt.Sprintf("Test case %d failed. Expected '%v', got '%v' for '%s' in '%s'", i, testCase.expected, result, testCase.needle, testCase.haystack)) - } -} diff --git a/pkg/utils/slice.go b/pkg/utils/slice.go deleted file mode 100644 index 0dc9e64e47c..00000000000 --- a/pkg/utils/slice.go +++ /dev/null @@ -1,181 +0,0 @@ -package utils - -import "golang.org/x/exp/slices" - -// NextIndex returns the index of the element that comes after the given number -func NextIndex(numbers []int, currentNumber int) int { - for index, number := range numbers { - if number > currentNumber { - return index - } - } - return len(numbers) - 1 -} - -// PrevIndex returns the index that comes before the given number, cycling if we reach the end -func PrevIndex(numbers []int, currentNumber int) int { - end := len(numbers) - 1 - for i := end; i >= 0; i-- { - if numbers[i] < currentNumber { - return i - } - } - return 0 -} - -// NextIntInCycle returns the next int in a slice, returning to the first index if we've reached the end -func NextIntInCycle(sl []int, current int) int { - for i, val := range sl { - if val == current { - if i == len(sl)-1 { - return sl[0] - } - return sl[i+1] - } - } - return sl[0] -} - -// PrevIntInCycle returns the prev int in a slice, returning to the first index if we've reached the end -func PrevIntInCycle(sl []int, current int) int { - for i, val := range sl { - if val == current { - if i > 0 { - return sl[i-1] - } - return sl[len(sl)-1] - } - } - return sl[len(sl)-1] -} - -func StringArraysOverlap(strArrA []string, strArrB []string) bool { - for _, first := range strArrA { - for _, second := range strArrB { - if first == second { - return true - } - } - } - - return false -} - -func Limit(values []string, limit int) []string { - if len(values) > limit { - return values[:limit] - } - return values -} - -func LimitStr(value string, limit int) string { - n := 0 - for i := range value { - if n >= limit { - return value[:i] - } - n++ - } - return value -} - -// Similar to a regular GroupBy, except that each item can be grouped under multiple keys, -// so the callback returns a slice of keys instead of just one key. -func MuiltiGroupBy[T any, K comparable](slice []T, f func(T) []K) map[K][]T { - result := map[K][]T{} - for _, item := range slice { - for _, key := range f(item) { - if _, ok := result[key]; !ok { - result[key] = []T{item} - } else { - result[key] = append(result[key], item) - } - } - } - return result -} - -// Returns a new slice with the element at index 'from' moved to index 'to'. -// Does not mutate original slice. -func MoveElement[T any](slice []T, from int, to int) []T { - newSlice := make([]T, len(slice)) - copy(newSlice, slice) - - if from == to { - return newSlice - } - - if from < to { - copy(newSlice[from:to+1], newSlice[from+1:to+1]) - } else { - copy(newSlice[to+1:from+1], newSlice[to:from]) - } - - newSlice[to] = slice[from] - - return newSlice -} - -func ValuesAtIndices[T any](slice []T, indices []int) []T { - result := make([]T, len(indices)) - for i, index := range indices { - // gracefully handling the situation where the index is out of bounds - if index < len(slice) { - result[i] = slice[index] - } - } - return result -} - -// returns two slices: the first is for elements that pass the test, the second for those that don't. -func Partition[T any](slice []T, test func(T) bool) ([]T, []T) { - left := make([]T, 0, len(slice)) - right := make([]T, 0, len(slice)) - - for _, value := range slice { - if test(value) { - left = append(left, value) - } else { - right = append(right, value) - } - } - - return left, right -} - -// Prepends items to the beginning of a slice. -// E.g. Prepend([]int{1,2}, 3, 4) = []int{3,4,1,2} -// Mutates original slice. Intended usage is to reassign the slice result to the input slice. -func Prepend[T any](slice []T, values ...T) []T { - return append(values, slice...) -} - -// Removes the element at the given index. Intended usage is to reassign the result to the input slice. -func Remove[T any](slice []T, index int) []T { - return slices.Delete(slice, index, index+1) -} - -// Removes the element at the 'fromIndex' and then inserts it at 'toIndex'. -// Operates on the input slice. Expected use is to reassign the result to the input slice. -func Move[T any](slice []T, fromIndex int, toIndex int) []T { - item := slice[fromIndex] - slice = Remove(slice, fromIndex) - return slices.Insert(slice, toIndex, item) -} - -// Pops item from the end of the slice and returns it, along with the updated slice -// Mutates original slice. Intended usage is to reassign the slice result to the input slice. -func Pop[T any](slice []T) (T, []T) { - index := len(slice) - 1 - value := slice[index] - slice = slice[0:index] - return value, slice -} - -// Shifts item from the beginning of the slice and returns it, along with the updated slice. -// Mutates original slice. Intended usage is to reassign the slice result to the input slice. -func Shift[T any](slice []T) (T, []T) { - value := slice[0] - slice = slice[1:] - return value, slice -} diff --git a/pkg/utils/slice_test.go b/pkg/utils/slice_test.go deleted file mode 100644 index df6a89141a0..00000000000 --- a/pkg/utils/slice_test.go +++ /dev/null @@ -1,313 +0,0 @@ -package utils - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNextIndex(t *testing.T) { - type scenario struct { - testName string - list []int - element int - expected int - } - - scenarios := []scenario{ - { - // I'm not really fussed about how it behaves here - "no elements", - []int{}, - 1, - -1, - }, - { - "one element", - []int{1}, - 1, - 0, - }, - { - "two elements", - []int{1, 2}, - 1, - 1, - }, - { - "two elements, giving second one", - []int{1, 2}, - 2, - 1, - }, - { - "three elements, giving second one", - []int{1, 2, 3}, - 2, - 2, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - assert.EqualValues(t, s.expected, NextIndex(s.list, s.element)) - }) - } -} - -func TestPrevIndex(t *testing.T) { - type scenario struct { - testName string - list []int - element int - expected int - } - - scenarios := []scenario{ - { - // I'm not really fussed about how it behaves here - "no elements", - []int{}, - 1, - 0, - }, - { - "one element", - []int{1}, - 1, - 0, - }, - { - "two elements", - []int{1, 2}, - 1, - 0, - }, - { - "three elements, giving second one", - []int{1, 2, 3}, - 2, - 0, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - assert.EqualValues(t, s.expected, PrevIndex(s.list, s.element)) - }) - } -} - -func TestEscapeSpecialChars(t *testing.T) { - type scenario struct { - testName string - input string - expected string - } - - scenarios := []scenario{ - { - "normal string", - "ab", - "ab", - }, - { - "string with a special char", - "a\nb", - "a\\nb", - }, - { - "multiple special chars", - "\n\r\t\b\f\v", - "\\n\\r\\t\\b\\f\\v", - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - assert.EqualValues(t, s.expected, EscapeSpecialChars(s.input)) - }) - } -} - -func TestLimit(t *testing.T) { - for _, test := range []struct { - values []string - limit int - want []string - }{ - { - values: []string{"a", "b", "c"}, - limit: 3, - want: []string{"a", "b", "c"}, - }, - { - values: []string{"a", "b", "c"}, - limit: 4, - want: []string{"a", "b", "c"}, - }, - { - values: []string{"a", "b", "c"}, - limit: 2, - want: []string{"a", "b"}, - }, - { - values: []string{"a", "b", "c"}, - limit: 1, - want: []string{"a"}, - }, - { - values: []string{"a", "b", "c"}, - limit: 0, - want: []string{}, - }, - { - values: []string{}, - limit: 0, - want: []string{}, - }, - } { - if got := Limit(test.values, test.limit); !assert.EqualValues(t, got, test.want) { - t.Errorf("Limit(%v, %d) = %v; want %v", test.values, test.limit, got, test.want) - } - } -} - -func TestLimitStr(t *testing.T) { - for _, test := range []struct { - values string - limit int - want string - }{ - { - values: "", - limit: 10, - want: "", - }, - { - values: "", - limit: 0, - want: "", - }, - { - values: "a", - limit: 1, - want: "a", - }, - { - values: "ab", - limit: 2, - want: "ab", - }, - { - values: "abc", - limit: 3, - want: "abc", - }, - { - values: "abcd", - limit: 3, - want: "abc", - }, - { - values: "abcde", - limit: 3, - want: "abc", - }, - { - values: "あいう", - limit: 1, - want: "あ", - }, - { - values: "あいう", - limit: 2, - want: "あい", - }, - } { - if got := LimitStr(test.values, test.limit); !assert.EqualValues(t, got, test.want) { - t.Errorf("LimitString(%v, %d) = %v; want %v", test.values, test.limit, got, test.want) - } - } -} - -func TestMoveElement(t *testing.T) { - type scenario struct { - testName string - list []int - from int - to int - expected []int - } - - scenarios := []scenario{ - { - "no elements", - []int{}, - 0, - 0, - []int{}, - }, - { - "one element", - []int{1}, - 0, - 0, - []int{1}, - }, - { - "two elements, moving first to second", - []int{1, 2}, - 0, - 1, - []int{2, 1}, - }, - { - "two elements, moving second to first", - []int{1, 2}, - 1, - 0, - []int{2, 1}, - }, - { - "three elements, moving first to second", - []int{1, 2, 3}, - 0, - 1, - []int{2, 1, 3}, - }, - { - "three elements, moving second to first", - []int{1, 2, 3}, - 1, - 0, - []int{2, 1, 3}, - }, - { - "three elements, moving second to third", - []int{1, 2, 3}, - 1, - 2, - []int{1, 3, 2}, - }, - { - "three elements, moving third to second", - []int{1, 2, 3}, - 2, - 1, - []int{1, 3, 2}, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - assert.EqualValues(t, s.expected, MoveElement(s.list, s.from, s.to)) - }) - } - - t.Run("from out of bounds", func(t *testing.T) { - assert.Panics(t, func() { - MoveElement([]int{1, 2, 3}, 3, 0) - }) - }) -} diff --git a/pkg/utils/string_pool.go b/pkg/utils/string_pool.go deleted file mode 100644 index 98932f4d567..00000000000 --- a/pkg/utils/string_pool.go +++ /dev/null @@ -1,14 +0,0 @@ -package utils - -import "sync" - -// A simple string pool implementation that can help reduce memory usage for -// cases where the same string is used multiple times. -type StringPool struct { - sync.Map -} - -func (self *StringPool) Add(s string) *string { - poolEntry, _ := self.LoadOrStore(s, &s) - return poolEntry.(*string) -} diff --git a/pkg/utils/string_stack.go b/pkg/utils/string_stack.go deleted file mode 100644 index c2d18c70cb0..00000000000 --- a/pkg/utils/string_stack.go +++ /dev/null @@ -1,27 +0,0 @@ -package utils - -type StringStack struct { - stack []string -} - -func (self *StringStack) Push(s string) { - self.stack = append(self.stack, s) -} - -func (self *StringStack) Pop() string { - if len(self.stack) == 0 { - return "" - } - n := len(self.stack) - 1 - last := self.stack[n] - self.stack = self.stack[:n] - return last -} - -func (self *StringStack) IsEmpty() bool { - return len(self.stack) == 0 -} - -func (self *StringStack) Clear() { - self.stack = []string{} -} diff --git a/pkg/utils/template.go b/pkg/utils/template.go deleted file mode 100644 index 9b7f544d1c6..00000000000 --- a/pkg/utils/template.go +++ /dev/null @@ -1,33 +0,0 @@ -package utils - -import ( - "bytes" - "strings" - "text/template" -) - -func ResolveTemplate(templateStr string, object interface{}, funcs template.FuncMap) (string, error) { - tmpl, err := template.New("template").Funcs(funcs).Option("missingkey=error").Parse(templateStr) - if err != nil { - return "", err - } - - var buf bytes.Buffer - if err := tmpl.Execute(&buf, object); err != nil { - return "", err - } - - return buf.String(), nil -} - -// ResolvePlaceholderString populates a template with values -func ResolvePlaceholderString(str string, arguments map[string]string) string { - oldnews := make([]string, 0, len(arguments)*4) - for key, value := range arguments { - oldnews = append(oldnews, - "{{"+key+"}}", value, - "{{."+key+"}}", value, - ) - } - return strings.NewReplacer(oldnews...).Replace(str) -} diff --git a/pkg/utils/template_test.go b/pkg/utils/template_test.go deleted file mode 100644 index 236c2327804..00000000000 --- a/pkg/utils/template_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package utils - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// TestResolvePlaceholderString is a function. -func TestResolvePlaceholderString(t *testing.T) { - type scenario struct { - templateString string - arguments map[string]string - expected string - } - - scenarios := []scenario{ - { - "", - map[string]string{}, - "", - }, - { - "hello", - map[string]string{}, - "hello", - }, - { - "hello {{arg}}", - map[string]string{}, - "hello {{arg}}", - }, - { - "hello {{arg}}", - map[string]string{"arg": "there"}, - "hello there", - }, - { - "hello", - map[string]string{"arg": "there"}, - "hello", - }, - { - "{{nothing}}", - map[string]string{"nothing": ""}, - "", - }, - { - "{{}} {{ this }} { should not throw}} an {{{{}}}} error", - map[string]string{ - "blah": "blah", - "this": "won't match", - }, - "{{}} {{ this }} { should not throw}} an {{{{}}}} error", - }, - { - "{{a}}", - map[string]string{ - "a": "X{{.a}}X", - }, - "X{{.a}}X", - }, - } - - for _, s := range scenarios { - assert.EqualValues(t, s.expected, ResolvePlaceholderString(s.templateString, s.arguments)) - } -} diff --git a/pkg/utils/thread_safe_map.go b/pkg/utils/thread_safe_map.go deleted file mode 100644 index a70cefc7d0d..00000000000 --- a/pkg/utils/thread_safe_map.go +++ /dev/null @@ -1,90 +0,0 @@ -package utils - -import "sync" - -type ThreadSafeMap[K comparable, V any] struct { - mutex sync.RWMutex - - innerMap map[K]V -} - -func NewThreadSafeMap[K comparable, V any]() *ThreadSafeMap[K, V] { - return &ThreadSafeMap[K, V]{ - innerMap: make(map[K]V), - } -} - -func (m *ThreadSafeMap[K, V]) Get(key K) (V, bool) { - m.mutex.RLock() - defer m.mutex.RUnlock() - - value, ok := m.innerMap[key] - return value, ok -} - -func (m *ThreadSafeMap[K, V]) Set(key K, value V) { - m.mutex.Lock() - defer m.mutex.Unlock() - - m.innerMap[key] = value -} - -func (m *ThreadSafeMap[K, V]) Delete(key K) { - m.mutex.Lock() - defer m.mutex.Unlock() - - delete(m.innerMap, key) -} - -func (m *ThreadSafeMap[K, V]) Keys() []K { - m.mutex.RLock() - defer m.mutex.RUnlock() - - keys := make([]K, 0, len(m.innerMap)) - for key := range m.innerMap { - keys = append(keys, key) - } - - return keys -} - -func (m *ThreadSafeMap[K, V]) Values() []V { - m.mutex.RLock() - defer m.mutex.RUnlock() - - values := make([]V, 0, len(m.innerMap)) - for _, value := range m.innerMap { - values = append(values, value) - } - - return values -} - -func (m *ThreadSafeMap[K, V]) Len() int { - m.mutex.RLock() - defer m.mutex.RUnlock() - - return len(m.innerMap) -} - -func (m *ThreadSafeMap[K, V]) Clear() { - m.mutex.Lock() - defer m.mutex.Unlock() - - m.innerMap = make(map[K]V) -} - -func (m *ThreadSafeMap[K, V]) IsEmpty() bool { - m.mutex.RLock() - defer m.mutex.RUnlock() - - return len(m.innerMap) == 0 -} - -func (m *ThreadSafeMap[K, V]) Has(key K) bool { - m.mutex.RLock() - defer m.mutex.RUnlock() - - _, ok := m.innerMap[key] - return ok -} diff --git a/pkg/utils/thread_safe_map_test.go b/pkg/utils/thread_safe_map_test.go deleted file mode 100644 index 768aa392cc5..00000000000 --- a/pkg/utils/thread_safe_map_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package utils - -import ( - "testing" -) - -func TestThreadSafeMap(t *testing.T) { - m := NewThreadSafeMap[int, int]() - - m.Set(1, 1) - m.Set(2, 2) - m.Set(3, 3) - - if m.Len() != 3 { - t.Errorf("Expected length to be 3, got %d", m.Len()) - } - - if !m.Has(1) { - t.Errorf("Expected to have key 1") - } - - if m.Has(4) { - t.Errorf("Expected to not have key 4") - } - - if _, ok := m.Get(1); !ok { - t.Errorf("Expected to have key 1") - } - - if _, ok := m.Get(4); ok { - t.Errorf("Expected to not have key 4") - } - - m.Delete(1) - - if m.Has(1) { - t.Errorf("Expected to not have key 1") - } - - m.Clear() - - if m.Len() != 0 { - t.Errorf("Expected length to be 0, got %d", m.Len()) - } -} - -func TestThreadSafeMapConcurrentReadWrite(t *testing.T) { - m := NewThreadSafeMap[int, int]() - - go func() { - for range 10000 { - m.Set(0, 0) - } - }() - - for range 10000 { - m.Get(0) - } -} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go deleted file mode 100644 index 95055551273..00000000000 --- a/pkg/utils/utils.go +++ /dev/null @@ -1,98 +0,0 @@ -package utils - -import ( - "encoding/json" - "fmt" - "os" - "regexp" - "runtime" - "strconv" - "strings" - - "github.com/jesseduffield/gocui" -) - -// GetProjectRoot returns the path to the root of the project. Only to be used -// in testing contexts, as with binaries it's unlikely this path will exist on -// the machine -func GetProjectRoot() string { - dir, err := os.Getwd() - if err != nil { - panic(err) - } - return strings.Split(dir, "lazygit")[0] + "lazygit" -} - -func SortRange(x int, y int) (int, int) { - if x < y { - return x, y - } - return y, x -} - -func AsJson(i interface{}) string { - bytes, _ := json.MarshalIndent(i, "", " ") - return string(bytes) -} - -// used to keep a number n between 0 and max, allowing for wraparounds -func ModuloWithWrap(n, max int) int { - if max == 0 { - return 0 - } - - if n >= max { - return n % max - } else if n < 0 { - return max + n - } - return n -} - -func FindStringSubmatch(str string, regexpStr string) (bool, []string) { - re := regexp.MustCompile(regexpStr) - match := re.FindStringSubmatch(str) - return len(match) > 0, match -} - -func MustConvertToInt(s string) int { - i, err := strconv.Atoi(s) - if err != nil { - panic(err) - } - return i -} - -// Safe will close tcell if a panic occurs so that we don't end up in a malformed -// terminal state -func Safe(f func()) { - _ = SafeWithError(func() error { f(); return nil }) -} - -func SafeWithError(f func() error) error { - panicking := true - defer func() { - if panicking && gocui.Screen != nil { - gocui.Screen.Fini() - } - }() - - err := f() - - panicking = false - - return err -} - -func StackTrace() string { - buf := make([]byte, 10000) - n := runtime.Stack(buf, false) - return fmt.Sprintf("%s\n", buf[:n]) -} - -// returns the path of the file that calls the function. -// 'skip' is the number of stack frames to skip. -func FilePath(skip int) string { - _, path, _, _ := runtime.Caller(skip) - return path -} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go deleted file mode 100644 index 41b40cd9f12..00000000000 --- a/pkg/utils/utils_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package utils - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAsJson(t *testing.T) { - type myStruct struct { - a string - } - - output := AsJson(&myStruct{a: "foo"}) - - // no idea why this is returning empty hashes but it's works in the app ¯\_(ツ)_/¯ - assert.EqualValues(t, "{}", output) -} - -func TestSafeTruncate(t *testing.T) { - type scenario struct { - str string - limit int - expected string - } - - scenarios := []scenario{ - { - str: "", - limit: 0, - expected: "", - }, - { - str: "12345", - limit: 3, - expected: "123", - }, - { - str: "12345", - limit: 4, - expected: "1234", - }, - { - str: "12345", - limit: 5, - expected: "12345", - }, - { - str: "12345", - limit: 6, - expected: "12345", - }, - } - - for _, s := range scenarios { - assert.EqualValues(t, s.expected, SafeTruncate(s.str, s.limit)) - } -} - -func TestModuloWithWrap(t *testing.T) { - type scenario struct { - n int - max int - expected int - } - - scenarios := []scenario{ - { - n: 0, - max: 0, - expected: 0, - }, - { - n: 0, - max: 1, - expected: 0, - }, - { - n: 1, - max: 0, - expected: 0, - }, - { - n: 3, - max: 2, - expected: 1, - }, - { - n: -1, - max: 2, - expected: 1, - }, - } - - for _, s := range scenarios { - if s.expected != ModuloWithWrap(s.n, s.max) { - t.Errorf("expected %d, got %d, for n: %d, max: %d", s.expected, ModuloWithWrap(s.n, s.max), s.n, s.max) - } - } -} diff --git a/pkg/utils/yaml_utils/yaml_utils.go b/pkg/utils/yaml_utils/yaml_utils.go deleted file mode 100644 index f8f7f067964..00000000000 --- a/pkg/utils/yaml_utils/yaml_utils.go +++ /dev/null @@ -1,165 +0,0 @@ -package yaml_utils - -import ( - "bytes" - "errors" - "fmt" - "slices" - - "gopkg.in/yaml.v3" -) - -func LookupKey(node *yaml.Node, key string) (*yaml.Node, *yaml.Node) { - for i := 0; i < len(node.Content)-1; i += 2 { - if node.Content[i].Value == key { - return node.Content[i], node.Content[i+1] - } - } - - return nil, nil -} - -// Returns the key and value if they were present -func RemoveKey(node *yaml.Node, key string) (*yaml.Node, *yaml.Node) { - for i := 0; i < len(node.Content)-1; i += 2 { - if node.Content[i].Value == key { - key, value := node.Content[i], node.Content[i+1] - node.Content = slices.Delete(node.Content, i, i+2) - return key, value - } - } - - return nil, nil -} - -// Walks a yaml document from the root node to the specified path, and then applies the transformation to that node. -// If the requested path is not defined in the document, no changes are made to the document. -func TransformNode(rootNode *yaml.Node, path []string, transform func(node *yaml.Node) error) error { - // Empty document: nothing to do. - if len(rootNode.Content) == 0 { - return nil - } - - body := rootNode.Content[0] - - if err := transformNode(body, path, transform); err != nil { - return err - } - - return nil -} - -// A recursive function to walk down the tree. See TransformNode for more details. -func transformNode(node *yaml.Node, path []string, transform func(node *yaml.Node) error) error { - if len(path) == 0 { - return transform(node) - } - - keyNode, valueNode := LookupKey(node, path[0]) - if keyNode == nil { - return nil - } - - return transformNode(valueNode, path[1:], transform) -} - -// Takes the root node of a yaml document, a path to a key, and a new name for the key. -// Will rename the key to the new name if it exists, and do nothing otherwise. -func RenameYamlKey(rootNode *yaml.Node, path []string, newKey string) (error, bool) { - // Empty document: nothing to do. - if len(rootNode.Content) == 0 { - return nil, false - } - - body := rootNode.Content[0] - - return renameYamlKey(body, path, newKey) -} - -// Recursive function to rename the YAML key. -func renameYamlKey(node *yaml.Node, path []string, newKey string) (error, bool) { - if node.Kind != yaml.MappingNode { - return errors.New("yaml node in path is not a dictionary"), false - } - - keyNode, valueNode := LookupKey(node, path[0]) - if keyNode == nil { - return nil, false - } - - // end of path reached: rename key - if len(path) == 1 { - // Check that new key doesn't exist yet - if newKeyNode, _ := LookupKey(node, newKey); newKeyNode != nil { - return fmt.Errorf("new key `%s' already exists", newKey), false - } - - keyNode.Value = newKey - return nil, true - } - - return renameYamlKey(valueNode, path[1:], newKey) -} - -// Traverses a yaml document, calling the callback function for each node. The -// callback is expected to modify the node in place -func Walk(rootNode *yaml.Node, callback func(node *yaml.Node, path string)) error { - // Empty document: nothing to do. - if len(rootNode.Content) == 0 { - return nil - } - - body := rootNode.Content[0] - - if err := walk(body, "", callback); err != nil { - return err - } - - return nil -} - -func walk(node *yaml.Node, path string, callback func(*yaml.Node, string)) error { - callback(node, path) - switch node.Kind { - case yaml.DocumentNode: - return errors.New("Unexpected document node in the middle of a yaml tree") - case yaml.MappingNode: - for i := 0; i < len(node.Content); i += 2 { - name := node.Content[i].Value - childNode := node.Content[i+1] - var childPath string - if path == "" { - childPath = name - } else { - childPath = fmt.Sprintf("%s.%s", path, name) - } - err := walk(childNode, childPath, callback) - if err != nil { - return err - } - } - case yaml.SequenceNode: - for i := range len(node.Content) { - childPath := fmt.Sprintf("%s[%d]", path, i) - err := walk(node.Content[i], childPath, callback) - if err != nil { - return err - } - } - case yaml.ScalarNode: - // nothing to do - case yaml.AliasNode: - return errors.New("Alias nodes are not supported") - } - - return nil -} - -func YamlMarshal(node *yaml.Node) ([]byte, error) { - var buffer bytes.Buffer - encoder := yaml.NewEncoder(&buffer) - encoder.SetIndent(2) - - err := encoder.Encode(node) - return buffer.Bytes(), err -} diff --git a/pkg/utils/yaml_utils/yaml_utils_test.go b/pkg/utils/yaml_utils/yaml_utils_test.go deleted file mode 100644 index d4d1fe074f1..00000000000 --- a/pkg/utils/yaml_utils/yaml_utils_test.go +++ /dev/null @@ -1,359 +0,0 @@ -package yaml_utils - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" -) - -func TestRenameYamlKey(t *testing.T) { - tests := []struct { - name string - in string - path []string - newKey string - expectedOut string - expectedDidRename bool - expectedErr string - }{ - { - name: "rename key", - in: "foo: 5\n", - path: []string{"foo"}, - newKey: "bar", - expectedOut: "bar: 5\n", - expectedDidRename: true, - expectedErr: "", - }, - { - name: "rename key, nested", - in: "foo:\n bar: 5\n", - path: []string{"foo", "bar"}, - newKey: "baz", - expectedOut: "foo:\n baz: 5\n", - expectedDidRename: true, - expectedErr: "", - }, - { - name: "rename non-scalar key", - in: "foo:\n bar: 5\n", - path: []string{"foo"}, - newKey: "qux", - expectedOut: "qux:\n bar: 5\n", - expectedDidRename: true, - expectedErr: "", - }, - { - name: "don't rewrite file if value didn't change", - in: "foo:\n bar: 5\n", - path: []string{"nonExistingKey"}, - newKey: "qux", - expectedOut: "foo:\n bar: 5\n", - expectedDidRename: false, - expectedErr: "", - }, - - // Error cases - { - name: "existing document is not a dictionary", - in: "42\n", - path: []string{"foo"}, - newKey: "bar", - expectedOut: "42\n", - expectedDidRename: false, - expectedErr: "yaml node in path is not a dictionary", - }, - { - name: "not all path elements are dictionaries", - in: "foo:\n bar: [1, 2, 3]\n", - path: []string{"foo", "bar", "baz"}, - newKey: "qux", - expectedOut: "foo:\n bar: [1, 2, 3]\n", - expectedDidRename: false, - expectedErr: "yaml node in path is not a dictionary", - }, - { - name: "new key exists", - in: "foo: 5\nbar: 7\n", - path: []string{"foo"}, - newKey: "bar", - expectedOut: "foo: 5\nbar: 7\n", - expectedDidRename: false, - expectedErr: "new key `bar' already exists", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - node := unmarshalForTest(t, test.in) - actualErr, didRename := RenameYamlKey(&node, test.path, test.newKey) - if test.expectedErr == "" { - assert.NoError(t, actualErr) - } else { - assert.EqualError(t, actualErr, test.expectedErr) - } - out := marshalForTest(t, &node) - - assert.Equal(t, test.expectedOut, out) - - assert.Equal(t, test.expectedDidRename, didRename) - }) - } -} - -func TestWalk_paths(t *testing.T) { - tests := []struct { - name string - document string - expectedPaths []string - }{ - { - name: "empty document", - document: "", - expectedPaths: []string{}, - }, - { - name: "scalar", - document: "x: 5", - expectedPaths: []string{"", "x"}, // called with an empty path for the root node - }, - { - name: "nested", - document: "foo:\n x: 5", - expectedPaths: []string{"", "foo", "foo.x"}, - }, - { - name: "deeply nested", - document: "foo:\n bar:\n baz: 5", - expectedPaths: []string{"", "foo", "foo.bar", "foo.bar.baz"}, - }, - { - name: "array", - document: "foo:\n bar: [3, 7]", - expectedPaths: []string{"", "foo", "foo.bar", "foo.bar[0]", "foo.bar[1]"}, - }, - { - name: "nested arrays", - document: "foo:\n bar: [[3, 7], [8, 9]]", - expectedPaths: []string{"", "foo", "foo.bar", "foo.bar[0]", "foo.bar[0][0]", "foo.bar[0][1]", "foo.bar[1]", "foo.bar[1][0]", "foo.bar[1][1]"}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - node := unmarshalForTest(t, test.document) - paths := []string{} - err := Walk(&node, func(node *yaml.Node, path string) { - paths = append(paths, path) - }) - - assert.NoError(t, err) - assert.Equal(t, test.expectedPaths, paths) - }) - } -} - -func TestWalk_inPlaceChanges(t *testing.T) { - tests := []struct { - name string - in string - callback func(node *yaml.Node, path string) - expectedOut string - }{ - { - name: "no change", - in: "x: 5", - callback: func(node *yaml.Node, path string) {}, - }, - { - name: "change value", - in: "x: 5\ny: 3", - callback: func(node *yaml.Node, path string) { - if path == "x" { - node.Value = "7" - } - }, - expectedOut: "x: 7\ny: 3\n", - }, - { - name: "change nested value", - in: "x:\n y: 5", - callback: func(node *yaml.Node, path string) { - if path == "x.y" { - node.Value = "7" - } - }, - expectedOut: "x:\n y: 7\n", - }, - { - name: "change array value", - in: "x:\n - y: 5", - callback: func(node *yaml.Node, path string) { - if path == "x[0].y" { - node.Value = "7" - } - }, - expectedOut: "x:\n - y: 7\n", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - node := unmarshalForTest(t, test.in) - err := Walk(&node, test.callback) - assert.NoError(t, err) - if test.expectedOut == "" { - unmodifiedOriginal := unmarshalForTest(t, test.in) - assert.Equal(t, unmodifiedOriginal, node) - } else { - result := marshalForTest(t, &node) - assert.Equal(t, test.expectedOut, result) - } - }) - } -} - -func TestTransformNode(t *testing.T) { - transformIntValueToString := func(node *yaml.Node) error { - if node.Kind == yaml.ScalarNode { - if node.ShortTag() == "!!int" { - node.Tag = "!!str" - return nil - } else if node.ShortTag() == "!!str" { - // We have already transformed it, - return nil - } - return fmt.Errorf("Node was of bad type") - } - return fmt.Errorf("Node was not a scalar") - } - - tests := []struct { - name string - in string - path []string - transform func(node *yaml.Node) error - expectedOut string - }{ - { - name: "Path not present", - in: "foo: 1", - path: []string{"bar"}, - transform: transformIntValueToString, - }, - { - name: "Part of path present", - in: ` -foo: - bar: 2`, - path: []string{"foo", "baz"}, - transform: transformIntValueToString, - }, - { - name: "Successfully Transforms to string", - in: ` -foo: - bar: 2`, - path: []string{"foo", "bar"}, - transform: transformIntValueToString, - expectedOut: `foo: - bar: "2" -`, // Note the trailing newline changes because of how it re-marshalls - }, - { - name: "Does nothing when already transformed", - in: ` -foo: - bar: "2"`, - path: []string{"foo", "bar"}, - transform: transformIntValueToString, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - node := unmarshalForTest(t, test.in) - err := TransformNode(&node, test.path, test.transform) - if err != nil { - t.Fatal(err) - } - if test.expectedOut == "" { - unmodifiedOriginal := unmarshalForTest(t, test.in) - assert.Equal(t, unmodifiedOriginal, node) - } else { - result := marshalForTest(t, &node) - assert.Equal(t, test.expectedOut, result) - } - }) - } -} - -func TestRemoveKey(t *testing.T) { - tests := []struct { - name string - in string - key string - expectedOut string - expectedRemovedKey string - expectedRemovedValue string - }{ - { - name: "Key not present", - in: "foo: 1", - key: "bar", - }, - { - name: "Key present", - in: "foo: 1\nbar: 2\nbaz: 3\n", - key: "bar", - expectedOut: "foo: 1\nbaz: 3\n", - expectedRemovedKey: "bar", - expectedRemovedValue: "2", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - node := unmarshalForTest(t, test.in) - removedKey, removedValue := RemoveKey(node.Content[0], test.key) - if test.expectedOut == "" { - unmodifiedOriginal := unmarshalForTest(t, test.in) - assert.Equal(t, unmodifiedOriginal, node) - } else { - result := marshalForTest(t, &node) - assert.Equal(t, test.expectedOut, result) - } - if test.expectedRemovedKey == "" { - assert.Nil(t, removedKey) - } else { - assert.Equal(t, test.expectedRemovedKey, removedKey.Value) - } - if test.expectedRemovedValue == "" { - assert.Nil(t, removedValue) - } else { - assert.Equal(t, test.expectedRemovedValue, removedValue.Value) - } - }) - } -} - -func unmarshalForTest(t *testing.T, input string) yaml.Node { - t.Helper() - var node yaml.Node - err := yaml.Unmarshal([]byte(input), &node) - if err != nil { - t.Fatal(err) - } - return node -} - -func marshalForTest(t *testing.T, node *yaml.Node) string { - t.Helper() - result, err := YamlMarshal(node) - if err != nil { - t.Fatal(err) - } - return string(result) -} diff --git a/rebase.gif b/rebase.gif new file mode 100644 index 00000000000..03bfff2984c Binary files /dev/null and b/rebase.gif differ diff --git a/resolving-merge-conflicts.gif b/resolving-merge-conflicts.gif new file mode 100644 index 00000000000..0b943fd49ac Binary files /dev/null and b/resolving-merge-conflicts.gif differ diff --git a/schema/config.json b/schema/config.json deleted file mode 100644 index 4aaa7a655e4..00000000000 --- a/schema/config.json +++ /dev/null @@ -1,1999 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/jesseduffield/lazygit/pkg/config/user-config", - "$ref": "#/$defs/UserConfig", - "$defs": { - "CommitConfig": { - "properties": { - "signOff": { - "type": "boolean", - "description": "If true, pass '--signoff' flag when committing", - "default": false - }, - "autoWrapCommitMessage": { - "type": "boolean", - "description": "Automatic WYSIWYG wrapping of the commit message as you type", - "default": true - }, - "autoWrapWidth": { - "type": "integer", - "description": "If autoWrapCommitMessage is true, the width to wrap to", - "default": 72 - } - }, - "additionalProperties": false, - "type": "object", - "description": "Config relating to committing" - }, - "CommitLengthConfig": { - "properties": { - "show": { - "type": "boolean", - "description": "If true, show an indicator of commit message length", - "default": true - } - }, - "additionalProperties": false, - "type": "object", - "description": "Config relating to the commit length indicator" - }, - "CommitPrefixConfig": { - "properties": { - "pattern": { - "type": "string", - "description": "pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use \"^\\\\w+\\\\/(\\\\w+-\\\\w+).*\"", - "examples": [ - "^\\w+\\/(\\w+-\\w+).*" - ] - }, - "replace": { - "type": "string", - "description": "Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use \"[$1] \"", - "examples": [ - "[$1]" - ] - } - }, - "additionalProperties": false, - "type": "object" - }, - "CustomCommand": { - "properties": { - "key": { - "type": "string", - "description": "The key to trigger the command. Use a single letter or one of the values from https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md" - }, - "commandMenu": { - "items": { - "$ref": "#/$defs/CustomCommand" - }, - "type": "array", - "description": "Instead of defining a single custom command, create a menu of custom commands. Useful for grouping related commands together under a single keybinding, and for keeping them out of the global keybindings menu.\nWhen using this, all other fields except Key and Description are ignored and must be empty." - }, - "context": { - "type": "string", - "description": "The context in which to listen for the key. Valid values are: status, files, worktrees, localBranches, remotes, remoteBranches, tags, commits, reflogCommits, subCommits, commitFiles, stash, and global. Multiple contexts separated by comma are allowed; most useful for \"commits, subCommits\" or \"files, commitFiles\".", - "examples": [ - "status", - "files", - "worktrees", - "localBranches", - "remotes", - "remoteBranches", - "tags", - "commits", - "reflogCommits", - "subCommits", - "commitFiles", - "stash", - "global" - ] - }, - "command": { - "type": "string", - "description": "The command to run (using Go template syntax for placeholder values)", - "examples": [ - "git fetch {{.Form.Remote}} {{.Form.Branch}} \u0026\u0026 git checkout FETCH_HEAD" - ] - }, - "prompts": { - "items": { - "$ref": "#/$defs/CustomCommandPrompt" - }, - "type": "array", - "description": "A list of prompts that will request user input before running the final command" - }, - "loadingText": { - "type": "string", - "description": "Text to display while waiting for command to finish", - "examples": [ - "Loading..." - ] - }, - "description": { - "type": "string", - "description": "Label for the custom command when displayed in the keybindings menu" - }, - "output": { - "type": "string", - "enum": [ - "none", - "terminal", - "log", - "logWithPty", - "popup" - ], - "description": "Where the output of the command should go. 'none' discards it, 'terminal' suspends lazygit and runs the command in the terminal (useful for commands that require user input), 'log' streams it to the command log, 'logWithPty' is like 'log' but runs the command in a pseudo terminal (can be useful for commands that produce colored output when the output is a terminal), and 'popup' shows it in a popup." - }, - "outputTitle": { - "type": "string", - "description": "The title to display in the popup panel if output is set to 'popup'. If left unset, the command will be used as the title." - }, - "after": { - "$ref": "#/$defs/CustomCommandAfterHook", - "description": "Actions to take after the command has completed" - } - }, - "additionalProperties": false, - "type": "object" - }, - "CustomCommandAfterHook": { - "properties": { - "checkForConflicts": { - "type": "boolean" - } - }, - "additionalProperties": false, - "type": "object" - }, - "CustomCommandMenuOption": { - "properties": { - "name": { - "type": "string", - "description": "The first part of the label" - }, - "description": { - "type": "string", - "description": "The second part of the label" - }, - "value": { - "type": "string", - "minLength": 1, - "description": "The value that will be used in the command", - "examples": [ - "feature" - ] - } - }, - "additionalProperties": false, - "type": "object" - }, - "CustomCommandPrompt": { - "properties": { - "type": { - "type": "string", - "description": "One of: 'input' | 'menu' | 'confirm' | 'menuFromCommand'" - }, - "key": { - "type": "string", - "description": "Used to reference the entered value from within the custom command. E.g. a prompt with `key: 'Branch'` can be referred to as `{{.Form.Branch}}` in the command" - }, - "title": { - "type": "string", - "description": "The title to display in the popup panel" - }, - "initialValue": { - "type": "string", - "description": "The initial value to appear in the text box.\nOnly for input prompts." - }, - "suggestions": { - "$ref": "#/$defs/CustomCommandSuggestions", - "description": "Shows suggestions as the input is entered\nOnly for input prompts." - }, - "body": { - "type": "string", - "description": "The message of the confirmation prompt.\nOnly for confirm prompts.", - "examples": [ - "Are you sure you want to push to the remote?" - ] - }, - "options": { - "items": { - "$ref": "#/$defs/CustomCommandMenuOption" - }, - "type": "array", - "description": "Menu options.\nOnly for menu prompts." - }, - "command": { - "type": "string", - "description": "The command to run to generate menu options\nOnly for menuFromCommand prompts.", - "examples": [ - "git fetch {{.Form.Remote}} {{.Form.Branch}} \u0026\u0026 git checkout FETCH_HEAD" - ] - }, - "filter": { - "type": "string", - "description": "The regexp to run specifying groups which are going to be kept from the command's output.\nOnly for menuFromCommand prompts.", - "examples": [ - ".*{{.SelectedRemote.Name }}/(?P\u003cbranch\u003e.*)" - ] - }, - "valueFormat": { - "type": "string", - "description": "How to format matched groups from the filter to construct a menu item's value.\nOnly for menuFromCommand prompts.", - "examples": [ - "{{ .branch }}" - ] - }, - "labelFormat": { - "type": "string", - "description": "Like valueFormat but for the labels. If `labelFormat` is not specified, `valueFormat` is shown instead.\nOnly for menuFromCommand prompts.", - "examples": [ - "{{ .branch | green }}" - ] - } - }, - "additionalProperties": false, - "type": "object" - }, - "CustomCommandSuggestions": { - "properties": { - "preset": { - "type": "string", - "enum": [ - "authors", - "branches", - "files", - "refs", - "remotes", - "remoteBranches", - "tags" - ], - "description": "Uses built-in logic to obtain the suggestions. One of 'authors' | 'branches' | 'files' | 'refs' | 'remotes' | 'remoteBranches' | 'tags'" - }, - "command": { - "type": "string", - "description": "Command to run such that each line in the output becomes a suggestion. Mutually exclusive with 'preset' field.", - "examples": [ - "git fetch {{.Form.Remote}} {{.Form.Branch}} \u0026\u0026 git checkout FETCH_HEAD" - ] - } - }, - "additionalProperties": false, - "type": "object" - }, - "CustomIconsConfig": { - "properties": { - "filenames": { - "additionalProperties": { - "$ref": "#/$defs/IconProperties" - }, - "type": "object", - "description": "Map of filenames to icon properties (icon and color)" - }, - "extensions": { - "additionalProperties": { - "$ref": "#/$defs/IconProperties" - }, - "type": "object", - "description": "Map of file extensions (including the dot) to icon properties (icon and color)" - } - }, - "additionalProperties": false, - "type": "object", - "description": "Custom icons for filenames and file extensions\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-files-icon--color" - }, - "GitConfig": { - "properties": { - "pagers": { - "items": { - "$ref": "#/$defs/PagingConfig" - }, - "type": "array", - "description": "Array of pagers. Each entry has the following format:\n\n # Value of the --color arg in the git diff command. Some pagers want\n # this to be set to 'always' and some want it set to 'never'\n colorArg: \"always\"\n\n # e.g.\n # diff-so-fancy\n # delta --dark --paging=never\n # ydiff -p cat -s --wrap --width={{columnWidth}}\n pager: \"\"\n\n # e.g. 'difft --color=always'\n externalDiffCommand: \"\"\n\n # If true, Lazygit will use git's `diff.external` config for paging.\n # The advantage over `externalDiffCommand` is that this can be\n # configured per file type in .gitattributes; see\n # https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.\n useExternalDiffGitConfig: false\n\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md for more information." - }, - "commit": { - "$ref": "#/$defs/CommitConfig", - "description": "Config relating to committing" - }, - "merging": { - "$ref": "#/$defs/MergingConfig", - "description": "Config relating to merging" - }, - "mainBranches": { - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": true, - "description": "list of branches that are considered 'main' branches, used when displaying commits", - "default": [ - "master", - "main" - ] - }, - "skipHookPrefix": { - "type": "string", - "description": "Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP'", - "default": "WIP" - }, - "autoFetch": { - "type": "boolean", - "description": "If true, periodically fetch from remote", - "default": true - }, - "autoRefresh": { - "type": "boolean", - "description": "If true, periodically refresh files and submodules", - "default": true - }, - "autoForwardBranches": { - "type": "string", - "enum": [ - "none", - "onlyMainBranches", - "allBranches" - ], - "description": "If not \"none\", lazygit will automatically fast-forward local branches to match their upstream after fetching. Applies to branches that are not the currently checked out branch, and only to those that are strictly behind their upstream (as opposed to diverged).\nPossible values: 'none' | 'onlyMainBranches' | 'allBranches'", - "default": "onlyMainBranches" - }, - "fetchAll": { - "type": "boolean", - "description": "If true, pass the --all arg to git fetch", - "default": true - }, - "autoStageResolvedConflicts": { - "type": "boolean", - "description": "If true, lazygit will automatically stage files that used to have merge conflicts but no longer do; and it will also ask you if you want to continue a merge or rebase if you've resolved all conflicts. If false, it won't do either of these things.", - "default": true - }, - "branchLogCmd": { - "type": "string", - "description": "Command used when displaying the current branch git log in the main window", - "default": "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --" - }, - "allBranchesLogCmds": { - "items": { - "type": "string" - }, - "type": "array", - "description": "Commands used to display git log of all branches in the main window, they will be cycled in order of appearance (array of strings)", - "default": [ - "git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium" - ] - }, - "ignoreWhitespaceInDiffView": { - "type": "boolean", - "description": "If true, git diffs are rendered with the `--ignore-all-space` flag, which ignores whitespace changes. Can be toggled from within Lazygit with `\u003cc-w\u003e`.", - "default": false - }, - "diffContextSize": { - "type": "integer", - "description": "The number of lines of context to show around each diff hunk. Can be changed from within Lazygit with the `{` and `}` keys.", - "default": 3 - }, - "renameSimilarityThreshold": { - "type": "integer", - "maximum": 100, - "minimum": 0, - "description": "The threshold for considering a file to be renamed, in percent. Can be changed from within Lazygit with the `(` and `)` keys.", - "default": 50 - }, - "overrideGpg": { - "type": "boolean", - "description": "If true, do not spawn a separate process when using GPG", - "default": false - }, - "disableForcePushing": { - "type": "boolean", - "description": "If true, do not allow force pushes", - "default": false - }, - "commitPrefix": { - "items": { - "$ref": "#/$defs/CommitPrefixConfig" - }, - "type": "array", - "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix" - }, - "commitPrefixes": { - "additionalProperties": { - "items": { - "$ref": "#/$defs/CommitPrefixConfig" - }, - "type": "array" - }, - "type": "object", - "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix" - }, - "branchPrefix": { - "type": "string", - "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix" - }, - "parseEmoji": { - "type": "boolean", - "description": "If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀\n(This should really be under 'gui', not 'git')", - "default": false - }, - "log": { - "$ref": "#/$defs/LogConfig", - "description": "Config for showing the log in the commits view" - }, - "localBranchSortOrder": { - "type": "string", - "enum": [ - "date", - "recency", - "alphabetical" - ], - "description": "How branches are sorted in the local branches view.\nOne of: 'date' (default) | 'recency' | 'alphabetical'\nCan be changed from within Lazygit with the Sort Order menu (`s`) in the branches panel.", - "default": "date" - }, - "remoteBranchSortOrder": { - "type": "string", - "enum": [ - "date", - "alphabetical" - ], - "description": "How branches are sorted in the remote branches view.\nOne of: 'date' (default) | 'alphabetical'\nCan be changed from within Lazygit with the Sort Order menu (`s`) in the remote branches panel.", - "default": "date" - }, - "truncateCopiedCommitHashesTo": { - "type": "integer", - "description": "When copying commit hashes to the clipboard, truncate them to this length. Set to 40 to disable truncation.", - "default": 12 - } - }, - "additionalProperties": false, - "type": "object", - "description": "Config relating to git" - }, - "GuiConfig": { - "properties": { - "authorColors": { - "additionalProperties": { - "type": "string" - }, - "type": "object", - "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color" - }, - "branchColors": { - "additionalProperties": { - "type": "string" - }, - "type": "object", - "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color\nDeprecated: use branchColorPatterns instead" - }, - "branchColorPatterns": { - "additionalProperties": { - "type": "string" - }, - "type": "object", - "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color" - }, - "customIcons": { - "$ref": "#/$defs/CustomIconsConfig", - "description": "Custom icons for filenames and file extensions\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-files-icon--color" - }, - "scrollHeight": { - "type": "integer", - "minimum": 1, - "description": "The number of lines you scroll by when scrolling the main window", - "default": 2 - }, - "scrollPastBottom": { - "type": "boolean", - "description": "If true, allow scrolling past the bottom of the content in the main window", - "default": true - }, - "scrollOffMargin": { - "type": "integer", - "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#scroll-off-margin", - "default": 2 - }, - "scrollOffBehavior": { - "type": "string", - "description": "One of: 'margin' (default) | 'jump'", - "default": "margin" - }, - "tabWidth": { - "type": "integer", - "minimum": 1, - "description": "The number of spaces per tab; used for everything that's shown in the main view, but probably mostly relevant for diffs.\nNote that when using a pager, the pager has its own tab width setting, so you need to pass it separately in the pager command.", - "default": 4 - }, - "mouseEvents": { - "type": "boolean", - "description": "If true, capture mouse events.\nWhen mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.", - "default": true - }, - "skipAmendWarning": { - "type": "boolean", - "description": "If true, do not show a warning when amending a commit.", - "default": false - }, - "skipDiscardChangeWarning": { - "type": "boolean", - "description": "If true, do not show a warning when discarding changes in the staging view.", - "default": false - }, - "skipStashWarning": { - "type": "boolean", - "description": "If true, do not show warning when applying/popping the stash", - "default": false - }, - "skipNoStagedFilesWarning": { - "type": "boolean", - "description": "If true, do not show a warning when attempting to commit without any staged files; instead stage all unstaged files.", - "default": false - }, - "skipRewordInEditorWarning": { - "type": "boolean", - "description": "If true, do not show a warning when rewording a commit via an external editor", - "default": false - }, - "skipSwitchWorktreeOnCheckoutWarning": { - "type": "boolean", - "description": "If true, switch to a different worktree without confirmation when checking out a branch that is checked out in that worktree", - "default": false - }, - "sidePanelWidth": { - "type": "number", - "maximum": 1, - "minimum": 0, - "description": "Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section.\nNumber from 0 to 1.0.", - "default": 0.3333 - }, - "expandFocusedSidePanel": { - "type": "boolean", - "description": "If true, increase the height of the focused side window; creating an accordion effect.", - "default": false - }, - "expandedSidePanelWeight": { - "type": "integer", - "description": "The weight of the expanded side panel, relative to the other panels. 2 means twice as tall as the other panels. Only relevant if `expandFocusedSidePanel` is true.", - "default": 2 - }, - "mainPanelSplitMode": { - "type": "string", - "enum": [ - "horizontal", - "flexible", - "vertical" - ], - "description": "Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split.\nOptions are:\n- 'horizontal': split the window horizontally\n- 'vertical': split the window vertically\n- 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically", - "default": "flexible" - }, - "enlargedSideViewLocation": { - "type": "string", - "description": "How the window is split when in half screen mode (i.e. after hitting '+' once).\nPossible values:\n- 'left': split the window horizontally (side panel on the left, main view on the right)\n- 'top': split the window vertically (side panel on top, main view below)", - "default": "left" - }, - "wrapLinesInStagingView": { - "type": "boolean", - "description": "If true, wrap lines in the staging view to the width of the view. This makes it much easier to work with diffs that have long lines, e.g. paragraphs of markdown text.", - "default": true - }, - "useHunkModeInStagingView": { - "type": "boolean", - "description": "If true, hunk selection mode will be enabled by default when entering the staging view.", - "default": true - }, - "language": { - "type": "string", - "enum": [ - "auto", - "en", - "zh-TW", - "zh-CN", - "pl", - "nl", - "ja", - "ko", - "ru" - ], - "description": "One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru' | 'pt'", - "default": "auto" - }, - "timeFormat": { - "type": "string", - "description": "Format used when displaying time e.g. commit time.\nUses Go's time format syntax: https://pkg.go.dev/time#Time.Format", - "default": "02 Jan 06" - }, - "shortTimeFormat": { - "type": "string", - "description": "Format used when displaying time if the time is less than 24 hours ago.\nUses Go's time format syntax: https://pkg.go.dev/time#Time.Format", - "default": "3:04PM" - }, - "theme": { - "$ref": "#/$defs/ThemeConfig", - "description": "Config relating to colors and styles.\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes" - }, - "commitLength": { - "$ref": "#/$defs/CommitLengthConfig", - "description": "Config relating to the commit length indicator" - }, - "showListFooter": { - "type": "boolean", - "description": "If true, show the '5 of 20' footer at the bottom of list views", - "default": true - }, - "showFileTree": { - "type": "boolean", - "description": "If true, display the files in the file views as a tree. If false, display the files as a flat list.\nThis can be toggled from within Lazygit with the '`' key, but that will not change the default.", - "default": true - }, - "showRootItemInFileTree": { - "type": "boolean", - "description": "If true, add a \"/\" root item in the file tree representing the root of the repository. It is only added when necessary, i.e. when there is more than one item at top level.", - "default": true - }, - "showNumstatInFilesView": { - "type": "boolean", - "description": "If true, show the number of lines changed per file in the Files view", - "default": false - }, - "showRandomTip": { - "type": "boolean", - "description": "If true, show a random tip in the command log when Lazygit starts", - "default": true - }, - "showCommandLog": { - "type": "boolean", - "description": "If true, show the command log", - "default": true - }, - "showBottomLine": { - "type": "boolean", - "description": "If true, show the bottom line that contains keybinding info and useful buttons. If false, this line will be hidden except to display a loader for an in-progress action.", - "default": true - }, - "showPanelJumps": { - "type": "boolean", - "description": "If true, show jump-to-window keybindings in window titles.", - "default": true - }, - "showIcons": { - "type": "boolean", - "description": "Deprecated: use nerdFontsVersion instead", - "default": false - }, - "nerdFontsVersion": { - "type": "string", - "enum": [ - "2", - "3", - "" - ], - "description": "Nerd fonts version to use.\nOne of: '2' | '3' | empty string (default)\nIf empty, do not show icons." - }, - "showFileIcons": { - "type": "boolean", - "description": "If true (default), file icons are shown in the file views. Only relevant if NerdFontsVersion is not empty.", - "default": true - }, - "commitAuthorShortLength": { - "type": "integer", - "description": "Length of author name in (non-expanded) commits view. 2 means show initials only.", - "default": 2 - }, - "commitAuthorLongLength": { - "type": "integer", - "description": "Length of author name in expanded commits view. 2 means show initials only.", - "default": 17 - }, - "commitHashLength": { - "type": "integer", - "minimum": 0, - "description": "Length of commit hash in commits view. 0 shows '*' if NF icons aren't on.", - "default": 8 - }, - "showBranchCommitHash": { - "type": "boolean", - "description": "If true, show commit hashes alongside branch names in the branches view.", - "default": false - }, - "showDivergenceFromBaseBranch": { - "type": "string", - "enum": [ - "none", - "onlyArrow", - "arrowAndNumber" - ], - "description": "Whether to show the divergence from the base branch in the branches view.\nOne of: 'none' | 'onlyArrow' | 'arrowAndNumber'", - "default": "none" - }, - "commandLogSize": { - "type": "integer", - "minimum": 0, - "description": "Height of the command log view", - "default": 8 - }, - "splitDiff": { - "type": "string", - "enum": [ - "auto", - "always" - ], - "description": "Whether to split the main window when viewing file changes.\nOne of: 'auto' | 'always'\nIf 'auto', only split the main window when a file has both staged and unstaged changes", - "default": "auto" - }, - "screenMode": { - "type": "string", - "enum": [ - "normal", - "half", - "full" - ], - "description": "Default size for focused window. Can be changed from within Lazygit with '+' and '_' (but this won't change the default).\nOne of: 'normal' (default) | 'half' | 'full'", - "default": "normal" - }, - "border": { - "type": "string", - "enum": [ - "single", - "double", - "rounded", - "hidden", - "bold" - ], - "description": "Window border style.\nOne of 'rounded' (default) | 'single' | 'double' | 'hidden' | 'bold'", - "default": "rounded" - }, - "animateExplosion": { - "type": "boolean", - "description": "If true, show a seriously epic explosion animation when nuking the working tree.", - "default": true - }, - "portraitMode": { - "type": "string", - "description": "Whether to stack UI components on top of each other.\nOne of 'auto' (default) | 'always' | 'never'", - "default": "auto" - }, - "filterMode": { - "type": "string", - "enum": [ - "substring", - "fuzzy" - ], - "description": "How things are filtered when typing '/'.\nOne of 'substring' (default) | 'fuzzy'", - "default": "substring" - }, - "spinner": { - "$ref": "#/$defs/SpinnerConfig", - "description": "Config relating to the spinner." - }, - "statusPanelView": { - "type": "string", - "enum": [ - "dashboard", - "allBranchesLog" - ], - "description": "Status panel view.\nOne of 'dashboard' (default) | 'allBranchesLog'", - "default": "dashboard" - }, - "switchToFilesAfterStashPop": { - "type": "boolean", - "description": "If true, jump to the Files panel after popping a stash", - "default": true - }, - "switchToFilesAfterStashApply": { - "type": "boolean", - "description": "If true, jump to the Files panel after applying a stash", - "default": true - }, - "switchTabsWithPanelJumpKeys": { - "type": "boolean", - "description": "If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead", - "default": false - } - }, - "additionalProperties": false, - "type": "object", - "description": "Config relating to the Lazygit UI" - }, - "IconProperties": { - "properties": { - "icon": { - "type": "string" - }, - "color": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingAmendAttributeConfig": { - "properties": { - "resetAuthor": { - "type": "string", - "default": "a" - }, - "setAuthor": { - "type": "string", - "default": "A" - }, - "addCoAuthor": { - "type": "string", - "default": "c" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingBranchesConfig": { - "properties": { - "createPullRequest": { - "type": "string", - "default": "o" - }, - "viewPullRequestOptions": { - "type": "string", - "default": "O" - }, - "copyPullRequestURL": { - "type": "string", - "default": "\u003cc-y\u003e" - }, - "checkoutBranchByName": { - "type": "string", - "default": "c" - }, - "forceCheckoutBranch": { - "type": "string", - "default": "F" - }, - "checkoutPreviousBranch": { - "type": "string", - "default": "-" - }, - "rebaseBranch": { - "type": "string", - "default": "r" - }, - "renameBranch": { - "type": "string", - "default": "R" - }, - "mergeIntoCurrentBranch": { - "type": "string", - "default": "M" - }, - "moveCommitsToNewBranch": { - "type": "string", - "default": "N" - }, - "viewGitFlowOptions": { - "type": "string", - "default": "i" - }, - "fastForward": { - "type": "string", - "default": "f" - }, - "createTag": { - "type": "string", - "default": "T" - }, - "pushTag": { - "type": "string", - "default": "P" - }, - "setUpstream": { - "type": "string", - "default": "u" - }, - "fetchRemote": { - "type": "string", - "default": "f" - }, - "sortOrder": { - "type": "string", - "default": "s" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingCommitFilesConfig": { - "properties": { - "checkoutCommitFile": { - "type": "string", - "default": "c" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingCommitMessageConfig": { - "properties": { - "commitMenu": { - "type": "string", - "default": "\u003cc-o\u003e" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingCommitsConfig": { - "properties": { - "squashDown": { - "type": "string", - "default": "s" - }, - "renameCommit": { - "type": "string", - "default": "r" - }, - "renameCommitWithEditor": { - "type": "string", - "default": "R" - }, - "viewResetOptions": { - "type": "string", - "default": "g" - }, - "markCommitAsFixup": { - "type": "string", - "default": "f" - }, - "createFixupCommit": { - "type": "string", - "default": "F" - }, - "squashAboveCommits": { - "type": "string", - "default": "S" - }, - "moveDownCommit": { - "type": "string", - "default": "\u003cc-j\u003e" - }, - "moveUpCommit": { - "type": "string", - "default": "\u003cc-k\u003e" - }, - "amendToCommit": { - "type": "string", - "default": "A" - }, - "resetCommitAuthor": { - "type": "string", - "default": "a" - }, - "pickCommit": { - "type": "string", - "default": "p" - }, - "revertCommit": { - "type": "string", - "default": "t" - }, - "cherryPickCopy": { - "type": "string", - "default": "C" - }, - "pasteCommits": { - "type": "string", - "default": "V" - }, - "markCommitAsBaseForRebase": { - "type": "string", - "default": "B" - }, - "tagCommit": { - "type": "string", - "default": "T" - }, - "checkoutCommit": { - "type": "string", - "default": "\u003cspace\u003e" - }, - "resetCherryPick": { - "type": "string", - "default": "\u003cc-R\u003e" - }, - "copyCommitAttributeToClipboard": { - "type": "string", - "default": "y" - }, - "openLogMenu": { - "type": "string", - "default": "\u003cc-l\u003e" - }, - "openInBrowser": { - "type": "string", - "default": "o" - }, - "viewBisectOptions": { - "type": "string", - "default": "b" - }, - "startInteractiveRebase": { - "type": "string", - "default": "i" - }, - "selectCommitsOfCurrentBranch": { - "type": "string", - "default": "*" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingConfig": { - "properties": { - "universal": { - "$ref": "#/$defs/KeybindingUniversalConfig" - }, - "status": { - "$ref": "#/$defs/KeybindingStatusConfig" - }, - "files": { - "$ref": "#/$defs/KeybindingFilesConfig" - }, - "branches": { - "$ref": "#/$defs/KeybindingBranchesConfig" - }, - "worktrees": { - "$ref": "#/$defs/KeybindingWorktreesConfig" - }, - "commits": { - "$ref": "#/$defs/KeybindingCommitsConfig" - }, - "amendAttribute": { - "$ref": "#/$defs/KeybindingAmendAttributeConfig" - }, - "stash": { - "$ref": "#/$defs/KeybindingStashConfig" - }, - "commitFiles": { - "$ref": "#/$defs/KeybindingCommitFilesConfig" - }, - "main": { - "$ref": "#/$defs/KeybindingMainConfig" - }, - "submodules": { - "$ref": "#/$defs/KeybindingSubmodulesConfig" - }, - "commitMessage": { - "$ref": "#/$defs/KeybindingCommitMessageConfig" - } - }, - "additionalProperties": false, - "type": "object", - "description": "Keybindings" - }, - "KeybindingFilesConfig": { - "properties": { - "commitChanges": { - "type": "string", - "default": "c" - }, - "commitChangesWithoutHook": { - "type": "string", - "default": "w" - }, - "amendLastCommit": { - "type": "string", - "default": "A" - }, - "commitChangesWithEditor": { - "type": "string", - "default": "C" - }, - "findBaseCommitForFixup": { - "type": "string", - "default": "\u003cc-f\u003e" - }, - "confirmDiscard": { - "type": "string", - "default": "x" - }, - "ignoreFile": { - "type": "string", - "default": "i" - }, - "refreshFiles": { - "type": "string", - "default": "r" - }, - "stashAllChanges": { - "type": "string", - "default": "s" - }, - "viewStashOptions": { - "type": "string", - "default": "S" - }, - "toggleStagedAll": { - "type": "string", - "default": "a" - }, - "viewResetOptions": { - "type": "string", - "default": "D" - }, - "fetch": { - "type": "string", - "default": "f" - }, - "toggleTreeView": { - "type": "string", - "default": "`" - }, - "openMergeOptions": { - "type": "string", - "default": "M" - }, - "openStatusFilter": { - "type": "string", - "default": "\u003cc-b\u003e" - }, - "copyFileInfoToClipboard": { - "type": "string", - "default": "y" - }, - "collapseAll": { - "type": "string", - "default": "-" - }, - "expandAll": { - "type": "string", - "default": "=" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingMainConfig": { - "properties": { - "toggleSelectHunk": { - "type": "string", - "default": "a" - }, - "pickBothHunks": { - "type": "string", - "default": "b" - }, - "editSelectHunk": { - "type": "string", - "default": "E" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingStashConfig": { - "properties": { - "popStash": { - "type": "string", - "default": "g" - }, - "renameStash": { - "type": "string", - "default": "r" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingStatusConfig": { - "properties": { - "checkForUpdate": { - "type": "string", - "default": "u" - }, - "recentRepos": { - "type": "string", - "default": "\u003center\u003e" - }, - "allBranchesLogGraph": { - "type": "string", - "default": "a" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingSubmodulesConfig": { - "properties": { - "init": { - "type": "string", - "default": "i" - }, - "update": { - "type": "string", - "default": "u" - }, - "bulkMenu": { - "type": "string", - "default": "b" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingUniversalConfig": { - "properties": { - "quit": { - "type": "string", - "default": "q" - }, - "quit-alt1": { - "type": "string", - "default": "\u003cc-c\u003e" - }, - "suspendApp": { - "type": "string", - "default": "\u003cc-z\u003e" - }, - "return": { - "type": "string", - "default": "\u003cesc\u003e" - }, - "quitWithoutChangingDirectory": { - "type": "string", - "default": "Q" - }, - "togglePanel": { - "type": "string", - "default": "\u003ctab\u003e" - }, - "prevItem": { - "type": "string", - "default": "\u003cup\u003e" - }, - "nextItem": { - "type": "string", - "default": "\u003cdown\u003e" - }, - "prevItem-alt": { - "type": "string", - "default": "k" - }, - "nextItem-alt": { - "type": "string", - "default": "j" - }, - "prevPage": { - "type": "string", - "default": "," - }, - "nextPage": { - "type": "string", - "default": "." - }, - "scrollLeft": { - "type": "string", - "default": "H" - }, - "scrollRight": { - "type": "string", - "default": "L" - }, - "gotoTop": { - "type": "string", - "default": "\u003c" - }, - "gotoBottom": { - "type": "string", - "default": "\u003e" - }, - "gotoTop-alt": { - "type": "string", - "default": "\u003chome\u003e" - }, - "gotoBottom-alt": { - "type": "string", - "default": "\u003cend\u003e" - }, - "toggleRangeSelect": { - "type": "string", - "default": "v" - }, - "rangeSelectDown": { - "type": "string", - "default": "\u003cs-down\u003e" - }, - "rangeSelectUp": { - "type": "string", - "default": "\u003cs-up\u003e" - }, - "prevBlock": { - "type": "string", - "default": "\u003cleft\u003e" - }, - "nextBlock": { - "type": "string", - "default": "\u003cright\u003e" - }, - "prevBlock-alt": { - "type": "string", - "default": "h" - }, - "nextBlock-alt": { - "type": "string", - "default": "l" - }, - "nextBlock-alt2": { - "type": "string", - "default": "\u003ctab\u003e" - }, - "prevBlock-alt2": { - "type": "string", - "default": "\u003cbacktab\u003e" - }, - "jumpToBlock": { - "items": { - "type": "string" - }, - "type": "array", - "default": [ - "1", - "2", - "3", - "4", - "5" - ] - }, - "focusMainView": { - "type": "string", - "default": "0" - }, - "nextMatch": { - "type": "string", - "default": "n" - }, - "prevMatch": { - "type": "string", - "default": "N" - }, - "startSearch": { - "type": "string", - "default": "/" - }, - "optionMenu": { - "type": "string", - "default": "\u003cdisabled\u003e" - }, - "optionMenu-alt1": { - "type": "string", - "default": "?" - }, - "select": { - "type": "string", - "default": "\u003cspace\u003e" - }, - "goInto": { - "type": "string", - "default": "\u003center\u003e" - }, - "confirm": { - "type": "string", - "default": "\u003center\u003e" - }, - "confirmMenu": { - "type": "string", - "default": "\u003center\u003e" - }, - "confirmSuggestion": { - "type": "string", - "default": "\u003center\u003e" - }, - "confirmInEditor": { - "type": "string", - "default": "\u003ca-enter\u003e" - }, - "confirmInEditor-alt": { - "type": "string", - "default": "\u003cc-s\u003e" - }, - "remove": { - "type": "string", - "default": "d" - }, - "new": { - "type": "string", - "default": "n" - }, - "edit": { - "type": "string", - "default": "e" - }, - "openFile": { - "type": "string", - "default": "o" - }, - "scrollUpMain": { - "type": "string", - "default": "\u003cpgup\u003e" - }, - "scrollDownMain": { - "type": "string", - "default": "\u003cpgdown\u003e" - }, - "scrollUpMain-alt1": { - "type": "string", - "default": "K" - }, - "scrollDownMain-alt1": { - "type": "string", - "default": "J" - }, - "scrollUpMain-alt2": { - "type": "string", - "default": "\u003cc-u\u003e" - }, - "scrollDownMain-alt2": { - "type": "string", - "default": "\u003cc-d\u003e" - }, - "executeShellCommand": { - "type": "string", - "default": ":" - }, - "createRebaseOptionsMenu": { - "type": "string", - "default": "m" - }, - "pushFiles": { - "type": "string", - "description": "'Files' appended for legacy reasons", - "default": "P" - }, - "pullFiles": { - "type": "string", - "description": "'Files' appended for legacy reasons", - "default": "p" - }, - "refresh": { - "type": "string", - "default": "R" - }, - "createPatchOptionsMenu": { - "type": "string", - "default": "\u003cc-p\u003e" - }, - "nextTab": { - "type": "string", - "default": "]" - }, - "prevTab": { - "type": "string", - "default": "[" - }, - "nextScreenMode": { - "type": "string", - "default": "+" - }, - "prevScreenMode": { - "type": "string", - "default": "_" - }, - "cyclePagers": { - "type": "string", - "default": "|" - }, - "undo": { - "type": "string", - "default": "z" - }, - "redo": { - "type": "string", - "default": "Z" - }, - "filteringMenu": { - "type": "string", - "default": "\u003cc-s\u003e" - }, - "diffingMenu": { - "type": "string", - "default": "W" - }, - "diffingMenu-alt": { - "type": "string", - "default": "\u003cc-e\u003e" - }, - "copyToClipboard": { - "type": "string", - "default": "\u003cc-o\u003e" - }, - "openRecentRepos": { - "type": "string", - "default": "\u003cc-r\u003e" - }, - "submitEditorText": { - "type": "string", - "default": "\u003center\u003e" - }, - "extrasMenu": { - "type": "string", - "default": "@" - }, - "toggleWhitespaceInDiffView": { - "type": "string", - "default": "\u003cc-w\u003e" - }, - "increaseContextInDiffView": { - "type": "string", - "default": "}" - }, - "decreaseContextInDiffView": { - "type": "string", - "default": "{" - }, - "increaseRenameSimilarityThreshold": { - "type": "string", - "default": ")" - }, - "decreaseRenameSimilarityThreshold": { - "type": "string", - "default": "(" - }, - "openDiffTool": { - "type": "string", - "default": "\u003cc-t\u003e" - } - }, - "additionalProperties": false, - "type": "object" - }, - "KeybindingWorktreesConfig": { - "properties": { - "viewWorktreeOptions": { - "type": "string", - "default": "w" - } - }, - "additionalProperties": false, - "type": "object" - }, - "LogConfig": { - "properties": { - "order": { - "type": "string", - "enum": [ - "date-order", - "author-date-order", - "topo-order", - "default" - ], - "description": "One of: 'date-order' | 'author-date-order' | 'topo-order' | 'default'\n'topo-order' makes it easier to read the git log graph, but commits may not appear chronologically. See https://git-scm.com/docs/\n\nCan be changed from within Lazygit with `Log menu -\u003e Commit sort order` (`\u003cc-l\u003e` in the commits window by default).", - "default": "topo-order" - }, - "showGraph": { - "type": "string", - "enum": [ - "always", - "never", - "when-maximised" - ], - "description": "This determines whether the git graph is rendered in the commits panel\nOne of 'always' | 'never' | 'when-maximised'\n\nCan be toggled from within lazygit with `Log menu -\u003e Show git graph` (`\u003cc-l\u003e` in the commits window by default).", - "default": "always" - }, - "showWholeGraph": { - "type": "boolean", - "description": "displays the whole git graph by default in the commits view (equivalent to passing the `--all` argument to `git log`)", - "default": false - } - }, - "additionalProperties": false, - "type": "object", - "description": "Config for showing the log in the commits view" - }, - "MergingConfig": { - "properties": { - "manualCommit": { - "type": "boolean", - "description": "If true, run merges in a subprocess so that if a commit message is required, Lazygit will not hang\nOnly applicable to unix users.", - "default": false - }, - "args": { - "type": "string", - "description": "Extra args passed to `git merge`, e.g. --no-ff", - "examples": [ - "--no-ff" - ] - }, - "squashMergeMessage": { - "type": "string", - "description": "The commit message to use for a squash merge commit. Can contain \"{{selectedRef}}\" and \"{{currentBranch}}\" placeholders.", - "default": "Squash merge {{selectedRef}} into {{currentBranch}}" - } - }, - "additionalProperties": false, - "type": "object", - "description": "Config relating to merging" - }, - "OSConfig": { - "properties": { - "edit": { - "type": "string", - "description": "Command for editing a file. Should contain \"{{filename}}\"." - }, - "editAtLine": { - "type": "string", - "description": "Command for editing a file at a given line number. Should contain \"{{filename}}\", and may optionally contain \"{{line}}\"." - }, - "editAtLineAndWait": { - "type": "string", - "description": "Same as EditAtLine, except that the command needs to wait until the window is closed." - }, - "editInTerminal": { - "type": "boolean", - "description": "Whether lazygit suspends until an edit process returns" - }, - "openDirInEditor": { - "type": "string", - "description": "For opening a directory in an editor" - }, - "editPreset": { - "type": "string", - "description": "A built-in preset that sets all of the above settings. Supported presets are defined in the getPreset function in editor_presets.go.", - "examples": [ - "vim", - "nvim", - "emacs", - "nano", - "vscode", - "sublime", - "kakoune", - "helix", - "xcode", - "zed", - "acme" - ] - }, - "open": { - "type": "string", - "description": "Command for opening a file, as if the file is double-clicked. Should contain \"{{filename}}\", but doesn't support \"{{line}}\"." - }, - "openLink": { - "type": "string", - "description": "Command for opening a link. Should contain \"{{link}}\"." - }, - "copyToClipboardCmd": { - "type": "string", - "description": "CopyToClipboardCmd is the command for copying to clipboard.\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard" - }, - "readFromClipboardCmd": { - "type": "string", - "description": "ReadFromClipboardCmd is the command for reading the clipboard.\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard" - }, - "shellFunctionsFile": { - "type": "string", - "description": "A shell startup file containing shell aliases or shell functions. This will be sourced before running any shell commands, so that shell functions are available in the `:` command prompt or even in custom commands.\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#using-aliases-or-functions-in-shell-commands" - } - }, - "additionalProperties": false, - "type": "object", - "description": "Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc" - }, - "PagingConfig": { - "properties": { - "colorArg": { - "type": "string", - "enum": [ - "always", - "never" - ], - "description": "Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never'" - }, - "pager": { - "type": "string", - "description": "e.g.\ndiff-so-fancy\ndelta --dark --paging=never\nydiff -p cat -s --wrap --width={{columnWidth}}", - "examples": [ - "delta --dark --paging=never", - "diff-so-fancy", - "ydiff -p cat -s --wrap --width={{columnWidth}}" - ] - }, - "externalDiffCommand": { - "type": "string", - "description": "e.g. 'difft --color=always'" - }, - "useExternalDiffGitConfig": { - "type": "boolean", - "description": "If true, Lazygit will use git's `diff.external` config for paging. The advantage over `externalDiffCommand` is that this can be configured per file type in .gitattributes; see https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver." - } - }, - "additionalProperties": false, - "type": "object" - }, - "RefresherConfig": { - "properties": { - "refreshInterval": { - "type": "integer", - "minimum": 0, - "description": "File/submodule refresh interval in seconds.\nAuto-refresh can be disabled via option 'git.autoRefresh'.", - "default": 10 - }, - "fetchInterval": { - "type": "integer", - "minimum": 0, - "description": "Re-fetch interval in seconds.\nAuto-fetch can be disabled via option 'git.autoFetch'.", - "default": 60 - } - }, - "additionalProperties": false, - "type": "object", - "description": "Background refreshes" - }, - "SpinnerConfig": { - "properties": { - "frames": { - "items": { - "type": "string" - }, - "type": "array", - "description": "The frames of the spinner animation.", - "default": [ - "|", - "/", - "-", - "\\" - ] - }, - "rate": { - "type": "integer", - "minimum": 1, - "description": "The \"speed\" of the spinner in milliseconds.", - "default": 50 - } - }, - "additionalProperties": false, - "type": "object", - "description": "Config relating to the spinner." - }, - "ThemeConfig": { - "properties": { - "activeBorderColor": { - "items": { - "type": "string" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true, - "description": "Border color of focused window", - "default": [ - "green", - "bold" - ] - }, - "inactiveBorderColor": { - "items": { - "type": "string" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true, - "description": "Border color of non-focused windows", - "default": [ - "default" - ] - }, - "searchingActiveBorderColor": { - "items": { - "type": "string" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true, - "description": "Border color of focused window when searching in that window", - "default": [ - "cyan", - "bold" - ] - }, - "optionsTextColor": { - "items": { - "type": "string" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true, - "description": "Color of keybindings help text in the bottom line", - "default": [ - "blue" - ] - }, - "selectedLineBgColor": { - "items": { - "type": "string" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true, - "description": "Background color of selected line.\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line", - "default": [ - "blue" - ] - }, - "inactiveViewSelectedLineBgColor": { - "items": { - "type": "string" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true, - "description": "Background color of selected line when view doesn't have focus.", - "default": [ - "bold" - ] - }, - "cherryPickedCommitFgColor": { - "items": { - "type": "string" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true, - "description": "Foreground color of copied commit", - "default": [ - "blue" - ] - }, - "cherryPickedCommitBgColor": { - "items": { - "type": "string" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true, - "description": "Background color of copied commit", - "default": [ - "cyan" - ] - }, - "markedBaseCommitFgColor": { - "items": { - "type": "string" - }, - "type": "array", - "description": "Foreground color of marked base commit (for rebase)", - "default": [ - "blue" - ] - }, - "markedBaseCommitBgColor": { - "items": { - "type": "string" - }, - "type": "array", - "description": "Background color of marked base commit (for rebase)", - "default": [ - "yellow" - ] - }, - "unstagedChangesColor": { - "items": { - "type": "string" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true, - "description": "Color for file with unstaged changes", - "default": [ - "red" - ] - }, - "defaultFgColor": { - "items": { - "type": "string" - }, - "type": "array", - "minItems": 1, - "uniqueItems": true, - "description": "Default text color", - "default": [ - "default" - ] - } - }, - "additionalProperties": false, - "type": "object", - "description": "Config relating to colors and styles.\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes" - }, - "UpdateConfig": { - "properties": { - "method": { - "type": "string", - "enum": [ - "prompt", - "background", - "never" - ], - "description": "One of: 'prompt' (default) | 'background' | 'never'", - "default": "prompt" - }, - "days": { - "type": "integer", - "minimum": 0, - "description": "Period in days between update checks", - "default": 14 - } - }, - "additionalProperties": false, - "type": "object", - "description": "Periodic update checks" - }, - "UserConfig": { - "properties": { - "gui": { - "$ref": "#/$defs/GuiConfig", - "description": "Config relating to the Lazygit UI" - }, - "git": { - "$ref": "#/$defs/GitConfig", - "description": "Config relating to git" - }, - "update": { - "$ref": "#/$defs/UpdateConfig", - "description": "Periodic update checks" - }, - "refresher": { - "$ref": "#/$defs/RefresherConfig", - "description": "Background refreshes" - }, - "confirmOnQuit": { - "type": "boolean", - "description": "If true, show a confirmation popup before quitting Lazygit", - "default": false - }, - "quitOnTopLevelReturn": { - "type": "boolean", - "description": "If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close", - "default": false - }, - "os": { - "$ref": "#/$defs/OSConfig", - "description": "Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc" - }, - "disableStartupPopups": { - "type": "boolean", - "description": "If true, don't display introductory popups upon opening Lazygit.", - "default": false - }, - "customCommands": { - "items": { - "$ref": "#/$defs/CustomCommand" - }, - "type": "array", - "uniqueItems": true, - "description": "User-configured commands that can be invoked from within Lazygit\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Command_Keybindings.md" - }, - "services": { - "additionalProperties": { - "type": "string" - }, - "type": "object", - "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-pull-request-urls" - }, - "notARepository": { - "type": "string", - "enum": [ - "prompt", - "create", - "skip", - "quit" - ], - "description": "What to do when opening Lazygit outside of a git repo.\n- 'prompt': (default) ask whether to initialize a new repo or open in the most recent repo\n- 'create': initialize a new repo\n- 'skip': open most recent repo\n- 'quit': exit Lazygit", - "default": "prompt" - }, - "promptToReturnFromSubprocess": { - "type": "boolean", - "description": "If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit.", - "default": true - }, - "keybinding": { - "$ref": "#/$defs/KeybindingConfig", - "description": "Keybindings" - } - }, - "additionalProperties": false, - "type": "object" - } - } -} diff --git a/scripts/bisect.sh b/scripts/bisect.sh deleted file mode 100755 index 72017e80685..00000000000 --- a/scripts/bisect.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -# How to use: -# 1) find a commit that is working fine. -# 2) Create an integration test capturing the fact that it works (Don't commit it). See https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md -# 3) checkout the commit that's known to be failing -# 4) run this script supplying the commit hash / tag name that works and the name of the newly created test - -# usage: scripts/bisect.sh -# e.g. scripts/bisect.sh v0.32.1 mergeConflictsResolvedExternally -# It's assumed that the current commit (i.e. HEAD) is broken. - -if [[ $# -ne 3 ]] ; then - echo 'Usage: scripts/bisect.sh ' - exit 1 -fi - -git bisect start $1 $2 -git bisect run sh -c "(go build -o /dev/null || exit 125) && go test ./pkg/gui -run /$3" -git bisect reset diff --git a/scripts/bump_gocui.sh b/scripts/bump_gocui.sh deleted file mode 100755 index 13a1f575a6c..00000000000 --- a/scripts/bump_gocui.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -# Go's proxy servers are not very up-to-date so that's why we use `GOPROXY=direct` -# We specify the `master` branch to avoid the default behaviour of looking for a semver tag. -GOPROXY=direct go get -u github.com/jesseduffield/gocui@master && go mod vendor && go mod tidy - -# Note to self if you ever want to fork a repo be sure to use this same approach: it's important to use the branch name (e.g. master) diff --git a/scripts/bump_lazycore.sh b/scripts/bump_lazycore.sh deleted file mode 100755 index 810e5f08e0a..00000000000 --- a/scripts/bump_lazycore.sh +++ /dev/null @@ -1,5 +0,0 @@ -# Go's proxy servers are not very up-to-date so that's why we use `GOPROXY=direct` -# We specify the `awesome` branch to avoid the default behaviour of looking for a semver tag. -GOPROXY=direct go get -u github.com/jesseduffield/lazycore@master && go mod vendor && go mod tidy - -# Note to self if you ever want to fork a repo be sure to use this same approach: it's important to use the branch name (e.g. master) diff --git a/scripts/bump_modules.sh b/scripts/bump_modules.sh deleted file mode 100755 index ffcb0834faa..00000000000 --- a/scripts/bump_modules.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -GO111MODULE=on -mv go.mod /tmp/ -go mod init \ No newline at end of file diff --git a/scripts/check_filenames.sh b/scripts/check_filenames.sh deleted file mode 100755 index a9b3c242d03..00000000000 --- a/scripts/check_filenames.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# Find all Go files in the project directory and its subdirectories, except in the vendor directory -for file in $(find . -name "*.go" -not -path "./vendor/*"); do - - # Check if the file name contains uppercase letters - if [[ "$file" =~ [A-Z] ]]; then - echo "Error: $file contains uppercase letters. All Go files in the project (excluding vendor directory) must use snake_case" - exit 1 - fi -done - -echo "All Go files in the project (excluding vendor directory) use lowercase letters" -exit 0 diff --git a/scripts/check_for_fixups.sh b/scripts/check_for_fixups.sh deleted file mode 100755 index 3d5518360f3..00000000000 --- a/scripts/check_for_fixups.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -# We will have only done a shallow clone, so the git log will consist only of -# commits on the current PR -commits=$(git log --grep='^fixup!' --grep='^squash!' --grep='^amend!' --grep='^[^\n]*WIP' --grep='^[^\n]*DROPME' --format="%h %s") - -if [ -z "$commits" ]; then - echo "No fixup commits found." - exit 0 -else - echo "Fixup or WIP commits found:" - echo "$commits" - exit 1 -fi diff --git a/scripts/golangci-lint-shim.sh b/scripts/golangci-lint-shim.sh deleted file mode 100755 index a85ccc4d712..00000000000 --- a/scripts/golangci-lint-shim.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -set -e - -# Must be kept in sync with the version in .github/workflows/ci.yml -version="v2.4.0" - -go run "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$version" "$@" diff --git a/scripts/record_demo.sh b/scripts/record_demo.sh deleted file mode 100755 index 67b950a532a..00000000000 --- a/scripts/record_demo.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -demo/record_demo.sh "$@" diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh deleted file mode 100755 index 579e6d77c15..00000000000 --- a/scripts/run_integration_tests.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/sh - -echo "Running integration tests with $(git --version)" - -# This is ugly, but older versions of git don't support the GIT_CONFIG_GLOBAL -# env var; the only way to run tests for these old versions is to copy our test -# config file to the actual global location. Move an existing file out of the -# way so that we can restore it at the end. -if test -f ~/.gitconfig; then - mv ~/.gitconfig ~/.gitconfig.lazygit.bak -fi - -cp test/global_git_config ~/.gitconfig - -# if the LAZYGIT_GOCOVERDIR env var is set, we'll capture code coverage data -if [ -n "$LAZYGIT_GOCOVERDIR" ]; then - # Go expects us to either be running the test binary directly or running `go test`, but because - # we're doing both and because we want to combine coverage data for both, we need to be a little - # hacky. To capture the coverage data for the test runner we pass the test.gocoverdir positional - # arg, but if we do that then the GOCOVERDIR env var (which you typically pass to the test binary) will be overwritten by the test runner. So we're passing LAZYGIT_COCOVERDIR instead - # and then internally passing that to the test binary as GOCOVERDIR. - go test -cover -coverpkg=github.com/jesseduffield/lazygit/pkg/... pkg/integration/clients/*.go -args -test.gocoverdir="/tmp/code_coverage" - EXITCODE=$? - - # We're merging the coverage data for the sake of having fewer artefacts to upload. - # We can't merge inline so we're merging to a tmp dir then moving back to the original. - mkdir -p /tmp/code_coverage_merged - go tool covdata merge -i=/tmp/code_coverage -o=/tmp/code_coverage_merged - rm -rf /tmp/code_coverage - mv /tmp/code_coverage_merged /tmp/code_coverage -else - go test pkg/integration/clients/*.go - EXITCODE=$? -fi - -if test -f ~/.gitconfig.lazygit.bak; then - mv ~/.gitconfig.lazygit.bak ~/.gitconfig -fi - -exit $EXITCODE diff --git a/scripts/update_language_files.sh b/scripts/update_language_files.sh deleted file mode 100755 index 815b095a363..00000000000 --- a/scripts/update_language_files.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh - -set -e - -# Since I couldn't get crowdin-cli to work yet, I'm doing things a bit more -# manually for now. The process is as follows: -# -# 1. Download the translations from Crowdin as a zip file -# 2. Unzip the file -# 3. Run this script with the path to the unzipped directory as an argument -# -# Requires jq (1.7 or later): https://github.com/jqlang/jq - -if [ "$#" -ne 1 ]; then - echo "Usage: $0 " - exit 2 -fi - -download_dir="$1" - -# The Portuguese translation is named pt-PT, but we want to use pt instead (it -# is used both for Brasilian and European Portuguese). I couldn't figure out how -# to change this in Crowdin, so we'll do it here. -[ -d "$download_dir/pt-PT" ] && mv "$download_dir/pt-PT" "$download_dir/pt" - -for d in "$download_dir"/* -do - # We need to remove empty strings from the JSON files; those are the ones - # that haven't been translated yet. Crowdin has an option to skip these when - # exporting, but unfortunately it doesn't work for json files. - jq 'del(..|select(. == ""))' < "$d/en.json" > pkg/i18n/translations/$(basename "$d").json -done diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 459e1526e79..00000000000 --- a/shell.nix +++ /dev/null @@ -1,12 +0,0 @@ -(import ( - let - lock = builtins.fromJSON (builtins.readFile ./flake.lock); - nodeName = lock.nodes.root.inputs.flake-compat; - in - fetchTarball { - url = - lock.nodes.${nodeName}.locked.url - or "https://github.com/edolstra/flake-compat/archive/$%7Block.nodes.$%7BnodeName%7D.locked.rev%7D.tar.gz"; - sha256 = lock.nodes.${nodeName}.locked.narHash; - } -) { src = ./.; }).shellNix diff --git a/staging.gif b/staging.gif new file mode 100644 index 00000000000..90ef1f4cef0 Binary files /dev/null and b/staging.gif differ diff --git a/subble.webp b/subble.webp new file mode 100644 index 00000000000..7b460ac6ee2 Binary files /dev/null and b/subble.webp differ diff --git a/test/.gitconfig b/test/.gitconfig deleted file mode 120000 index b2405f3cd79..00000000000 --- a/test/.gitconfig +++ /dev/null @@ -1 +0,0 @@ -global_git_config \ No newline at end of file diff --git a/test/README.md b/test/README.md deleted file mode 100644 index 52442989476..00000000000 --- a/test/README.md +++ /dev/null @@ -1,2 +0,0 @@ -This directory contains some files used by out integration tests. The tests themselves live in [/pkg/integration/](/pkg/integration/). See [here](/pkg/integration/README.md) for more info - diff --git a/test/default_test_config/config.yml b/test/default_test_config/config.yml deleted file mode 100644 index 5a822ae774a..00000000000 --- a/test/default_test_config/config.yml +++ /dev/null @@ -1,22 +0,0 @@ -# This config is used in our integration tests. If we want to modify this for a specific test, you can do so in the SetupConfig function - -disableStartupPopups: true -promptToReturnFromSubprocess: false - -gui: - theme: - activeBorderColor: - - green - - bold - inactiveBorderColor: - - black - # Not important in tests but it creates clutter in demos - showRandomTip: false - animateExplosion: false # takes too long -git: - # We don't want to run any periodic background git commands because it'll introduce race conditions and flakiness. - # If we need to refresh something from within the test (which should only really happen if we've invoked a - # shell command in the background) we should have the user press shift+R to refresh. - # TODO: add tests which explicitly test auto-refresh functionality - autoRefresh: false - autoFetch: false diff --git a/test/files/pre-push b/test/files/pre-push deleted file mode 100755 index 3b758c1b1d8..00000000000 --- a/test/files/pre-push +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# test pre-push hook for testing the lazygit credentials view -# -# to enable, use: -# chmod +x .git/hooks/pre-push -# -# this will hang if you're using git from the command line, so only enable this -# when you are testing the credentials view in lazygit - -exec < /dev/tty - -echo -n "Username for 'github': " -read username - -echo -n "Password for 'github': " -# this will print the password to the log view but real git won't do that. -# We could use read -s but that's not POSIX compliant. -read password - -if [ "$username" = "username" -a "$password" = "password" ]; then - echo "success" - exit 0 -fi - ->&2 echo "incorrect username/password" -exit 1 diff --git a/test/global_git_config b/test/global_git_config deleted file mode 100644 index f4f47c0034f..00000000000 --- a/test/global_git_config +++ /dev/null @@ -1,10 +0,0 @@ -# This is the global git config we use for all our integration tests - -[user] - name = CI - email = CI@example.com -[protocol "file"] - # see https://vielmetti.typepad.com/logbook/2022/10/git-security-fixes-lead-to-fatal-transport-file-not-allowed-error-in-ci-systems-cve-2022-39253.html - allow = always -[commit] - gpgSign = false diff --git a/tuple.png b/tuple.png new file mode 100644 index 00000000000..1d7be47ca97 Binary files /dev/null and b/tuple.png differ diff --git a/undo2.gif b/undo2.gif new file mode 100644 index 00000000000..4beb87c1570 Binary files /dev/null and b/undo2.gif differ diff --git a/vendor/dario.cat/mergo/.deepsource.toml b/vendor/dario.cat/mergo/.deepsource.toml deleted file mode 100644 index a8bc979e02e..00000000000 --- a/vendor/dario.cat/mergo/.deepsource.toml +++ /dev/null @@ -1,12 +0,0 @@ -version = 1 - -test_patterns = [ - "*_test.go" -] - -[[analyzers]] -name = "go" -enabled = true - - [analyzers.meta] - import_path = "dario.cat/mergo" \ No newline at end of file diff --git a/vendor/dario.cat/mergo/.gitignore b/vendor/dario.cat/mergo/.gitignore deleted file mode 100644 index 45ad0f1ae30..00000000000 --- a/vendor/dario.cat/mergo/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -#### joe made this: http://goel.io/joe - -#### go #### -# Binaries for programs and plugins -*.exe -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Golang/Intellij -.idea - -# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 -.glide/ - -#### vim #### -# Swap -[._]*.s[a-v][a-z] -[._]*.sw[a-p] -[._]s[a-v][a-z] -[._]sw[a-p] - -# Session -Session.vim - -# Temporary -.netrwhist -*~ -# Auto-generated tag files -tags diff --git a/vendor/dario.cat/mergo/.travis.yml b/vendor/dario.cat/mergo/.travis.yml deleted file mode 100644 index d324c43ba4d..00000000000 --- a/vendor/dario.cat/mergo/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: go -arch: - - amd64 - - ppc64le -install: - - go get -t - - go get golang.org/x/tools/cmd/cover - - go get github.com/mattn/goveralls -script: - - go test -race -v ./... -after_script: - - $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN diff --git a/vendor/dario.cat/mergo/CODE_OF_CONDUCT.md b/vendor/dario.cat/mergo/CODE_OF_CONDUCT.md deleted file mode 100644 index 469b44907a0..00000000000 --- a/vendor/dario.cat/mergo/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/dario.cat/mergo/CONTRIBUTING.md b/vendor/dario.cat/mergo/CONTRIBUTING.md deleted file mode 100644 index 0a1ff9f94d8..00000000000 --- a/vendor/dario.cat/mergo/CONTRIBUTING.md +++ /dev/null @@ -1,112 +0,0 @@ - -# Contributing to mergo - -First off, thanks for taking the time to contribute! ❤️ - -All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 - -> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: -> - Star the project -> - Tweet about it -> - Refer this project in your project's readme -> - Mention the project at local meetups and tell your friends/colleagues - - -## Table of Contents - -- [Code of Conduct](#code-of-conduct) -- [I Have a Question](#i-have-a-question) -- [I Want To Contribute](#i-want-to-contribute) -- [Reporting Bugs](#reporting-bugs) -- [Suggesting Enhancements](#suggesting-enhancements) - -## Code of Conduct - -This project and everyone participating in it is governed by the -[mergo Code of Conduct](https://github.com/imdario/mergoblob/master/CODE_OF_CONDUCT.md). -By participating, you are expected to uphold this code. Please report unacceptable behavior -to <>. - - -## I Have a Question - -> If you want to ask a question, we assume that you have read the available [Documentation](https://pkg.go.dev/github.com/imdario/mergo). - -Before you ask a question, it is best to search for existing [Issues](https://github.com/imdario/mergo/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. - -If you then still feel the need to ask a question and need clarification, we recommend the following: - -- Open an [Issue](https://github.com/imdario/mergo/issues/new). -- Provide as much context as you can about what you're running into. -- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. - -We will then take care of the issue as soon as possible. - -## I Want To Contribute - -> ### Legal Notice -> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. - -### Reporting Bugs - - -#### Before Submitting a Bug Report - -A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. - -- Make sure that you are using the latest version. -- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)). -- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/imdario/mergoissues?q=label%3Abug). -- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. -- Collect information about the bug: -- Stack trace (Traceback) -- OS, Platform and Version (Windows, Linux, macOS, x86, ARM) -- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. -- Possibly your input and the output -- Can you reliably reproduce the issue? And can you also reproduce it with older versions? - - -#### How Do I Submit a Good Bug Report? - -> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . - - -We use GitHub issues to track bugs and errors. If you run into an issue with the project: - -- Open an [Issue](https://github.com/imdario/mergo/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) -- Explain the behavior you would expect and the actual behavior. -- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. -- Provide the information you collected in the previous section. - -Once it's filed: - -- The project team will label the issue accordingly. -- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. -- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone. - -### Suggesting Enhancements - -This section guides you through submitting an enhancement suggestion for mergo, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. - - -#### Before Submitting an Enhancement - -- Make sure that you are using the latest version. -- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration. -- Perform a [search](https://github.com/imdario/mergo/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. -- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. - - -#### How Do I Submit a Good Enhancement Suggestion? - -Enhancement suggestions are tracked as [GitHub issues](https://github.com/imdario/mergo/issues). - -- Use a **clear and descriptive title** for the issue to identify the suggestion. -- Provide a **step-by-step description of the suggested enhancement** in as many details as possible. -- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. -- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. -- **Explain why this enhancement would be useful** to most mergo users. You may also want to point out the other projects that solved it better and which could serve as inspiration. - - -## Attribution -This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)! diff --git a/vendor/dario.cat/mergo/LICENSE b/vendor/dario.cat/mergo/LICENSE deleted file mode 100644 index 686680298da..00000000000 --- a/vendor/dario.cat/mergo/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2013 Dario Castañé. All rights reserved. -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/dario.cat/mergo/README.md b/vendor/dario.cat/mergo/README.md deleted file mode 100644 index 0b3c488893b..00000000000 --- a/vendor/dario.cat/mergo/README.md +++ /dev/null @@ -1,258 +0,0 @@ -# Mergo - -[![GitHub release][5]][6] -[![GoCard][7]][8] -[![Test status][1]][2] -[![OpenSSF Scorecard][21]][22] -[![OpenSSF Best Practices][19]][20] -[![Coverage status][9]][10] -[![Sourcegraph][11]][12] -[![FOSSA status][13]][14] - -[![GoDoc][3]][4] -[![Become my sponsor][15]][16] -[![Tidelift][17]][18] - -[1]: https://github.com/imdario/mergo/workflows/tests/badge.svg?branch=master -[2]: https://github.com/imdario/mergo/actions/workflows/tests.yml -[3]: https://godoc.org/github.com/imdario/mergo?status.svg -[4]: https://godoc.org/github.com/imdario/mergo -[5]: https://img.shields.io/github/release/imdario/mergo.svg -[6]: https://github.com/imdario/mergo/releases -[7]: https://goreportcard.com/badge/imdario/mergo -[8]: https://goreportcard.com/report/github.com/imdario/mergo -[9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master -[10]: https://coveralls.io/github/imdario/mergo?branch=master -[11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg -[12]: https://sourcegraph.com/github.com/imdario/mergo?badge -[13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield -[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield -[15]: https://img.shields.io/github/sponsors/imdario -[16]: https://github.com/sponsors/imdario -[17]: https://tidelift.com/badges/package/go/github.com%2Fimdario%2Fmergo -[18]: https://tidelift.com/subscription/pkg/go-github.com-imdario-mergo -[19]: https://bestpractices.coreinfrastructure.org/projects/7177/badge -[20]: https://bestpractices.coreinfrastructure.org/projects/7177 -[21]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo/badge -[22]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo - -A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. - -Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). - -Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche. - -## Status - -Mergo is stable and frozen, ready for production. Check a short list of the projects using at large scale it [here](https://github.com/imdario/mergo#mergo-in-the-wild). - -No new features are accepted. They will be considered for a future v2 that improves the implementation and fixes bugs for corner cases. - -### Important notes - -#### 1.0.0 - -In [1.0.0](//github.com/imdario/mergo/releases/tag/1.0.0) Mergo moves to a vanity URL `dario.cat/mergo`. No more v1 versions will be released. - -If the vanity URL is causing issues in your project due to a dependency pulling Mergo - it isn't a direct dependency in your project - it is recommended to use [replace](https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive) to pin the version to the last one with the old import URL: - -``` -replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16 -``` - -#### 0.3.9 - -Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds support for go modules. - -Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code. - -If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u dario.cat/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). - -### Donations - -If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes: - -Donate using Liberapay -Become my sponsor - -### Mergo in the wild - -Mergo is used by [thousands](https://deps.dev/go/dario.cat%2Fmergo/v1.0.0/dependents) [of](https://deps.dev/go/github.com%2Fimdario%2Fmergo/v0.3.16/dependents) [projects](https://deps.dev/go/github.com%2Fimdario%2Fmergo/v0.3.12), including: - -* [containerd/containerd](https://github.com/containerd/containerd) -* [datadog/datadog-agent](https://github.com/datadog/datadog-agent) -* [docker/cli/](https://github.com/docker/cli/) -* [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser) -* [go-micro/go-micro](https://github.com/go-micro/go-micro) -* [grafana/loki](https://github.com/grafana/loki) -* [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) -* [masterminds/sprig](github.com/Masterminds/sprig) -* [moby/moby](https://github.com/moby/moby) -* [slackhq/nebula](https://github.com/slackhq/nebula) -* [volcano-sh/volcano](https://github.com/volcano-sh/volcano) - -## Install - - go get dario.cat/mergo - - // use in your .go code - import ( - "dario.cat/mergo" - ) - -## Usage - -You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). - -```go -if err := mergo.Merge(&dst, src); err != nil { - // ... -} -``` - -Also, you can merge overwriting values using the transformer `WithOverride`. - -```go -if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { - // ... -} -``` - -If you need to override pointers, so the source pointer's value is assigned to the destination's pointer, you must use `WithoutDereference`: - -```go -package main - -import ( - "fmt" - - "dario.cat/mergo" -) - -type Foo struct { - A *string - B int64 -} - -func main() { - first := "first" - second := "second" - src := Foo{ - A: &first, - B: 2, - } - - dest := Foo{ - A: &second, - B: 1, - } - - mergo.Merge(&dest, src, mergo.WithOverride, mergo.WithoutDereference) -} -``` - -Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field. - -```go -if err := mergo.Map(&dst, srcMap); err != nil { - // ... -} -``` - -Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values. - -Here is a nice example: - -```go -package main - -import ( - "fmt" - "dario.cat/mergo" -) - -type Foo struct { - A string - B int64 -} - -func main() { - src := Foo{ - A: "one", - B: 2, - } - dest := Foo{ - A: "two", - } - mergo.Merge(&dest, src) - fmt.Println(dest) - // Will print - // {two 2} -} -``` - -Note: if test are failing due missing package, please execute: - - go get gopkg.in/yaml.v3 - -### Transformers - -Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`? - -```go -package main - -import ( - "fmt" - "dario.cat/mergo" - "reflect" - "time" -) - -type timeTransformer struct { -} - -func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { - if typ == reflect.TypeOf(time.Time{}) { - return func(dst, src reflect.Value) error { - if dst.CanSet() { - isZero := dst.MethodByName("IsZero") - result := isZero.Call([]reflect.Value{}) - if result[0].Bool() { - dst.Set(src) - } - } - return nil - } - } - return nil -} - -type Snapshot struct { - Time time.Time - // ... -} - -func main() { - src := Snapshot{time.Now()} - dest := Snapshot{} - mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) - fmt.Println(dest) - // Will print - // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } -} -``` - -## Contact me - -If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario) - -## About - -Written by [Dario Castañé](http://dario.im). - -## License - -[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE). - -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large) diff --git a/vendor/dario.cat/mergo/SECURITY.md b/vendor/dario.cat/mergo/SECURITY.md deleted file mode 100644 index a5de61f77ba..00000000000 --- a/vendor/dario.cat/mergo/SECURITY.md +++ /dev/null @@ -1,14 +0,0 @@ -# Security Policy - -## Supported Versions - -| Version | Supported | -| ------- | ------------------ | -| 0.3.x | :white_check_mark: | -| < 0.3 | :x: | - -## Security contact information - -To report a security vulnerability, please use the -[Tidelift security contact](https://tidelift.com/security). -Tidelift will coordinate the fix and disclosure. diff --git a/vendor/dario.cat/mergo/doc.go b/vendor/dario.cat/mergo/doc.go deleted file mode 100644 index 7d96ec0546d..00000000000 --- a/vendor/dario.cat/mergo/doc.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2013 Dario Castañé. All rights reserved. -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. - -Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). - -# Status - -It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc. - -# Important notes - -1.0.0 - -In 1.0.0 Mergo moves to a vanity URL `dario.cat/mergo`. - -0.3.9 - -Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules. - -Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code. - -If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u dario.cat/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). - -# Install - -Do your usual installation procedure: - - go get dario.cat/mergo - - // use in your .go code - import ( - "dario.cat/mergo" - ) - -# Usage - -You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). - - if err := mergo.Merge(&dst, src); err != nil { - // ... - } - -Also, you can merge overwriting values using the transformer WithOverride. - - if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { - // ... - } - -Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field. - - if err := mergo.Map(&dst, srcMap); err != nil { - // ... - } - -Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values. - -Here is a nice example: - - package main - - import ( - "fmt" - "dario.cat/mergo" - ) - - type Foo struct { - A string - B int64 - } - - func main() { - src := Foo{ - A: "one", - B: 2, - } - dest := Foo{ - A: "two", - } - mergo.Merge(&dest, src) - fmt.Println(dest) - // Will print - // {two 2} - } - -# Transformers - -Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time? - - package main - - import ( - "fmt" - "dario.cat/mergo" - "reflect" - "time" - ) - - type timeTransformer struct { - } - - func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { - if typ == reflect.TypeOf(time.Time{}) { - return func(dst, src reflect.Value) error { - if dst.CanSet() { - isZero := dst.MethodByName("IsZero") - result := isZero.Call([]reflect.Value{}) - if result[0].Bool() { - dst.Set(src) - } - } - return nil - } - } - return nil - } - - type Snapshot struct { - Time time.Time - // ... - } - - func main() { - src := Snapshot{time.Now()} - dest := Snapshot{} - mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) - fmt.Println(dest) - // Will print - // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } - } - -# Contact me - -If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario - -# About - -Written by Dario Castañé: https://da.rio.hn - -# License - -BSD 3-Clause license, as Go language. -*/ -package mergo diff --git a/vendor/dario.cat/mergo/map.go b/vendor/dario.cat/mergo/map.go deleted file mode 100644 index 759b4f74fd5..00000000000 --- a/vendor/dario.cat/mergo/map.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2014 Dario Castañé. All rights reserved. -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Based on src/pkg/reflect/deepequal.go from official -// golang's stdlib. - -package mergo - -import ( - "fmt" - "reflect" - "unicode" - "unicode/utf8" -) - -func changeInitialCase(s string, mapper func(rune) rune) string { - if s == "" { - return s - } - r, n := utf8.DecodeRuneInString(s) - return string(mapper(r)) + s[n:] -} - -func isExported(field reflect.StructField) bool { - r, _ := utf8.DecodeRuneInString(field.Name) - return r >= 'A' && r <= 'Z' -} - -// Traverses recursively both values, assigning src's fields values to dst. -// The map argument tracks comparisons that have already been seen, which allows -// short circuiting on recursive types. -func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { - overwrite := config.Overwrite - if dst.CanAddr() { - addr := dst.UnsafeAddr() - h := 17 * addr - seen := visited[h] - typ := dst.Type() - for p := seen; p != nil; p = p.next { - if p.ptr == addr && p.typ == typ { - return nil - } - } - // Remember, remember... - visited[h] = &visit{typ, seen, addr} - } - zeroValue := reflect.Value{} - switch dst.Kind() { - case reflect.Map: - dstMap := dst.Interface().(map[string]interface{}) - for i, n := 0, src.NumField(); i < n; i++ { - srcType := src.Type() - field := srcType.Field(i) - if !isExported(field) { - continue - } - fieldName := field.Name - fieldName = changeInitialCase(fieldName, unicode.ToLower) - if _, ok := dstMap[fieldName]; !ok || (!isEmptyValue(reflect.ValueOf(src.Field(i).Interface()), !config.ShouldNotDereference) && overwrite) || config.overwriteWithEmptyValue { - dstMap[fieldName] = src.Field(i).Interface() - } - } - case reflect.Ptr: - if dst.IsNil() { - v := reflect.New(dst.Type().Elem()) - dst.Set(v) - } - dst = dst.Elem() - fallthrough - case reflect.Struct: - srcMap := src.Interface().(map[string]interface{}) - for key := range srcMap { - config.overwriteWithEmptyValue = true - srcValue := srcMap[key] - fieldName := changeInitialCase(key, unicode.ToUpper) - dstElement := dst.FieldByName(fieldName) - if dstElement == zeroValue { - // We discard it because the field doesn't exist. - continue - } - srcElement := reflect.ValueOf(srcValue) - dstKind := dstElement.Kind() - srcKind := srcElement.Kind() - if srcKind == reflect.Ptr && dstKind != reflect.Ptr { - srcElement = srcElement.Elem() - srcKind = reflect.TypeOf(srcElement.Interface()).Kind() - } else if dstKind == reflect.Ptr { - // Can this work? I guess it can't. - if srcKind != reflect.Ptr && srcElement.CanAddr() { - srcPtr := srcElement.Addr() - srcElement = reflect.ValueOf(srcPtr) - srcKind = reflect.Ptr - } - } - - if !srcElement.IsValid() { - continue - } - if srcKind == dstKind { - if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { - return - } - } else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface { - if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { - return - } - } else if srcKind == reflect.Map { - if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil { - return - } - } else { - return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind) - } - } - } - return -} - -// Map sets fields' values in dst from src. -// src can be a map with string keys or a struct. dst must be the opposite: -// if src is a map, dst must be a valid pointer to struct. If src is a struct, -// dst must be map[string]interface{}. -// It won't merge unexported (private) fields and will do recursively -// any exported field. -// If dst is a map, keys will be src fields' names in lower camel case. -// Missing key in src that doesn't match a field in dst will be skipped. This -// doesn't apply if dst is a map. -// This is separated method from Merge because it is cleaner and it keeps sane -// semantics: merging equal types, mapping different (restricted) types. -func Map(dst, src interface{}, opts ...func(*Config)) error { - return _map(dst, src, opts...) -} - -// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by -// non-empty src attribute values. -// Deprecated: Use Map(…) with WithOverride -func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { - return _map(dst, src, append(opts, WithOverride)...) -} - -func _map(dst, src interface{}, opts ...func(*Config)) error { - if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { - return ErrNonPointerArgument - } - var ( - vDst, vSrc reflect.Value - err error - ) - config := &Config{} - - for _, opt := range opts { - opt(config) - } - - if vDst, vSrc, err = resolveValues(dst, src); err != nil { - return err - } - // To be friction-less, we redirect equal-type arguments - // to deepMerge. Only because arguments can be anything. - if vSrc.Kind() == vDst.Kind() { - return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) - } - switch vSrc.Kind() { - case reflect.Struct: - if vDst.Kind() != reflect.Map { - return ErrExpectedMapAsDestination - } - case reflect.Map: - if vDst.Kind() != reflect.Struct { - return ErrExpectedStructAsDestination - } - default: - return ErrNotSupported - } - return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config) -} diff --git a/vendor/dario.cat/mergo/merge.go b/vendor/dario.cat/mergo/merge.go deleted file mode 100644 index fd47c95b2b8..00000000000 --- a/vendor/dario.cat/mergo/merge.go +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright 2013 Dario Castañé. All rights reserved. -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Based on src/pkg/reflect/deepequal.go from official -// golang's stdlib. - -package mergo - -import ( - "fmt" - "reflect" -) - -func hasMergeableFields(dst reflect.Value) (exported bool) { - for i, n := 0, dst.NumField(); i < n; i++ { - field := dst.Type().Field(i) - if field.Anonymous && dst.Field(i).Kind() == reflect.Struct { - exported = exported || hasMergeableFields(dst.Field(i)) - } else if isExportedComponent(&field) { - exported = exported || len(field.PkgPath) == 0 - } - } - return -} - -func isExportedComponent(field *reflect.StructField) bool { - pkgPath := field.PkgPath - if len(pkgPath) > 0 { - return false - } - c := field.Name[0] - if 'a' <= c && c <= 'z' || c == '_' { - return false - } - return true -} - -type Config struct { - Transformers Transformers - Overwrite bool - ShouldNotDereference bool - AppendSlice bool - TypeCheck bool - overwriteWithEmptyValue bool - overwriteSliceWithEmptyValue bool - sliceDeepCopy bool - debug bool -} - -type Transformers interface { - Transformer(reflect.Type) func(dst, src reflect.Value) error -} - -// Traverses recursively both values, assigning src's fields values to dst. -// The map argument tracks comparisons that have already been seen, which allows -// short circuiting on recursive types. -func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { - overwrite := config.Overwrite - typeCheck := config.TypeCheck - overwriteWithEmptySrc := config.overwriteWithEmptyValue - overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue - sliceDeepCopy := config.sliceDeepCopy - - if !src.IsValid() { - return - } - if dst.CanAddr() { - addr := dst.UnsafeAddr() - h := 17 * addr - seen := visited[h] - typ := dst.Type() - for p := seen; p != nil; p = p.next { - if p.ptr == addr && p.typ == typ { - return nil - } - } - // Remember, remember... - visited[h] = &visit{typ, seen, addr} - } - - if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() { - if fn := config.Transformers.Transformer(dst.Type()); fn != nil { - err = fn(dst, src) - return - } - } - - switch dst.Kind() { - case reflect.Struct: - if hasMergeableFields(dst) { - for i, n := 0, dst.NumField(); i < n; i++ { - if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil { - return - } - } - } else { - if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) { - dst.Set(src) - } - } - case reflect.Map: - if dst.IsNil() && !src.IsNil() { - if dst.CanSet() { - dst.Set(reflect.MakeMap(dst.Type())) - } else { - dst = src - return - } - } - - if src.Kind() != reflect.Map { - if overwrite && dst.CanSet() { - dst.Set(src) - } - return - } - - for _, key := range src.MapKeys() { - srcElement := src.MapIndex(key) - if !srcElement.IsValid() { - continue - } - dstElement := dst.MapIndex(key) - switch srcElement.Kind() { - case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice: - if srcElement.IsNil() { - if overwrite { - dst.SetMapIndex(key, srcElement) - } - continue - } - fallthrough - default: - if !srcElement.CanInterface() { - continue - } - switch reflect.TypeOf(srcElement.Interface()).Kind() { - case reflect.Struct: - fallthrough - case reflect.Ptr: - fallthrough - case reflect.Map: - srcMapElm := srcElement - dstMapElm := dstElement - if srcMapElm.CanInterface() { - srcMapElm = reflect.ValueOf(srcMapElm.Interface()) - if dstMapElm.IsValid() { - dstMapElm = reflect.ValueOf(dstMapElm.Interface()) - } - } - if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil { - return - } - case reflect.Slice: - srcSlice := reflect.ValueOf(srcElement.Interface()) - - var dstSlice reflect.Value - if !dstElement.IsValid() || dstElement.IsNil() { - dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len()) - } else { - dstSlice = reflect.ValueOf(dstElement.Interface()) - } - - if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy { - if typeCheck && srcSlice.Type() != dstSlice.Type() { - return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) - } - dstSlice = srcSlice - } else if config.AppendSlice { - if srcSlice.Type() != dstSlice.Type() { - return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) - } - dstSlice = reflect.AppendSlice(dstSlice, srcSlice) - } else if sliceDeepCopy { - i := 0 - for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ { - srcElement := srcSlice.Index(i) - dstElement := dstSlice.Index(i) - - if srcElement.CanInterface() { - srcElement = reflect.ValueOf(srcElement.Interface()) - } - if dstElement.CanInterface() { - dstElement = reflect.ValueOf(dstElement.Interface()) - } - - if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { - return - } - } - - } - dst.SetMapIndex(key, dstSlice) - } - } - - if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) { - if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice { - continue - } - if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map { - continue - } - } - - if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) { - if dst.IsNil() { - dst.Set(reflect.MakeMap(dst.Type())) - } - dst.SetMapIndex(key, srcElement) - } - } - - // Ensure that all keys in dst are deleted if they are not in src. - if overwriteWithEmptySrc { - for _, key := range dst.MapKeys() { - srcElement := src.MapIndex(key) - if !srcElement.IsValid() { - dst.SetMapIndex(key, reflect.Value{}) - } - } - } - case reflect.Slice: - if !dst.CanSet() { - break - } - if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy { - dst.Set(src) - } else if config.AppendSlice { - if src.Type() != dst.Type() { - return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type()) - } - dst.Set(reflect.AppendSlice(dst, src)) - } else if sliceDeepCopy { - for i := 0; i < src.Len() && i < dst.Len(); i++ { - srcElement := src.Index(i) - dstElement := dst.Index(i) - if srcElement.CanInterface() { - srcElement = reflect.ValueOf(srcElement.Interface()) - } - if dstElement.CanInterface() { - dstElement = reflect.ValueOf(dstElement.Interface()) - } - - if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { - return - } - } - } - case reflect.Ptr: - fallthrough - case reflect.Interface: - if isReflectNil(src) { - if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) { - dst.Set(src) - } - break - } - - if src.Kind() != reflect.Interface { - if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) { - if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) { - dst.Set(src) - } - } else if src.Kind() == reflect.Ptr { - if !config.ShouldNotDereference { - if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { - return - } - } else if src.Elem().Kind() != reflect.Struct { - if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() { - dst.Set(src) - } - } - } else if dst.Elem().Type() == src.Type() { - if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil { - return - } - } else { - return ErrDifferentArgumentsTypes - } - break - } - - if dst.IsNil() || overwrite { - if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) { - dst.Set(src) - } - break - } - - if dst.Elem().Kind() == src.Elem().Kind() { - if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { - return - } - break - } - default: - mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) - if mustSet { - if dst.CanSet() { - dst.Set(src) - } else { - dst = src - } - } - } - - return -} - -// Merge will fill any empty for value type attributes on the dst struct using corresponding -// src attributes if they themselves are not empty. dst and src must be valid same-type structs -// and dst must be a pointer to struct. -// It won't merge unexported (private) fields and will do recursively any exported field. -func Merge(dst, src interface{}, opts ...func(*Config)) error { - return merge(dst, src, opts...) -} - -// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by -// non-empty src attribute values. -// Deprecated: use Merge(…) with WithOverride -func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { - return merge(dst, src, append(opts, WithOverride)...) -} - -// WithTransformers adds transformers to merge, allowing to customize the merging of some types. -func WithTransformers(transformers Transformers) func(*Config) { - return func(config *Config) { - config.Transformers = transformers - } -} - -// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values. -func WithOverride(config *Config) { - config.Overwrite = true -} - -// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values. -func WithOverwriteWithEmptyValue(config *Config) { - config.Overwrite = true - config.overwriteWithEmptyValue = true -} - -// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice. -func WithOverrideEmptySlice(config *Config) { - config.overwriteSliceWithEmptyValue = true -} - -// WithoutDereference prevents dereferencing pointers when evaluating whether they are empty -// (i.e. a non-nil pointer is never considered empty). -func WithoutDereference(config *Config) { - config.ShouldNotDereference = true -} - -// WithAppendSlice will make merge append slices instead of overwriting it. -func WithAppendSlice(config *Config) { - config.AppendSlice = true -} - -// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride). -func WithTypeCheck(config *Config) { - config.TypeCheck = true -} - -// WithSliceDeepCopy will merge slice element one by one with Overwrite flag. -func WithSliceDeepCopy(config *Config) { - config.sliceDeepCopy = true - config.Overwrite = true -} - -func merge(dst, src interface{}, opts ...func(*Config)) error { - if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { - return ErrNonPointerArgument - } - var ( - vDst, vSrc reflect.Value - err error - ) - - config := &Config{} - - for _, opt := range opts { - opt(config) - } - - if vDst, vSrc, err = resolveValues(dst, src); err != nil { - return err - } - if vDst.Type() != vSrc.Type() { - return ErrDifferentArgumentsTypes - } - return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) -} - -// IsReflectNil is the reflect value provided nil -func isReflectNil(v reflect.Value) bool { - k := v.Kind() - switch k { - case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr: - // Both interface and slice are nil if first word is 0. - // Both are always bigger than a word; assume flagIndir. - return v.IsNil() - default: - return false - } -} diff --git a/vendor/dario.cat/mergo/mergo.go b/vendor/dario.cat/mergo/mergo.go deleted file mode 100644 index 0a721e2d858..00000000000 --- a/vendor/dario.cat/mergo/mergo.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2013 Dario Castañé. All rights reserved. -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Based on src/pkg/reflect/deepequal.go from official -// golang's stdlib. - -package mergo - -import ( - "errors" - "reflect" -) - -// Errors reported by Mergo when it finds invalid arguments. -var ( - ErrNilArguments = errors.New("src and dst must not be nil") - ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type") - ErrNotSupported = errors.New("only structs, maps, and slices are supported") - ErrExpectedMapAsDestination = errors.New("dst was expected to be a map") - ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct") - ErrNonPointerArgument = errors.New("dst must be a pointer") -) - -// During deepMerge, must keep track of checks that are -// in progress. The comparison algorithm assumes that all -// checks in progress are true when it reencounters them. -// Visited are stored in a map indexed by 17 * a1 + a2; -type visit struct { - typ reflect.Type - next *visit - ptr uintptr -} - -// From src/pkg/encoding/json/encode.go. -func isEmptyValue(v reflect.Value, shouldDereference bool) bool { - switch v.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - return v.Len() == 0 - case reflect.Bool: - return !v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Interface, reflect.Ptr: - if v.IsNil() { - return true - } - if shouldDereference { - return isEmptyValue(v.Elem(), shouldDereference) - } - return false - case reflect.Func: - return v.IsNil() - case reflect.Invalid: - return true - } - return false -} - -func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) { - if dst == nil || src == nil { - err = ErrNilArguments - return - } - vDst = reflect.ValueOf(dst).Elem() - if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map && vDst.Kind() != reflect.Slice { - err = ErrNotSupported - return - } - vSrc = reflect.ValueOf(src) - // We check if vSrc is a pointer to dereference it. - if vSrc.Kind() == reflect.Ptr { - vSrc = vSrc.Elem() - } - return -} diff --git a/vendor/github.com/Microsoft/go-winio/.gitattributes b/vendor/github.com/Microsoft/go-winio/.gitattributes deleted file mode 100644 index 94f480de94e..00000000000 --- a/vendor/github.com/Microsoft/go-winio/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto eol=lf \ No newline at end of file diff --git a/vendor/github.com/Microsoft/go-winio/.gitignore b/vendor/github.com/Microsoft/go-winio/.gitignore deleted file mode 100644 index 815e20660e5..00000000000 --- a/vendor/github.com/Microsoft/go-winio/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -.vscode/ - -*.exe - -# testing -testdata - -# go workspaces -go.work -go.work.sum diff --git a/vendor/github.com/Microsoft/go-winio/.golangci.yml b/vendor/github.com/Microsoft/go-winio/.golangci.yml deleted file mode 100644 index faedfe937a7..00000000000 --- a/vendor/github.com/Microsoft/go-winio/.golangci.yml +++ /dev/null @@ -1,147 +0,0 @@ -linters: - enable: - # style - - containedctx # struct contains a context - - dupl # duplicate code - - errname # erorrs are named correctly - - nolintlint # "//nolint" directives are properly explained - - revive # golint replacement - - unconvert # unnecessary conversions - - wastedassign - - # bugs, performance, unused, etc ... - - contextcheck # function uses a non-inherited context - - errorlint # errors not wrapped for 1.13 - - exhaustive # check exhaustiveness of enum switch statements - - gofmt # files are gofmt'ed - - gosec # security - - nilerr # returns nil even with non-nil error - - thelper # test helpers without t.Helper() - - unparam # unused function params - -issues: - exclude-dirs: - - pkg/etw/sample - - exclude-rules: - # err is very often shadowed in nested scopes - - linters: - - govet - text: '^shadow: declaration of "err" shadows declaration' - - # ignore long lines for skip autogen directives - - linters: - - revive - text: "^line-length-limit: " - source: "^//(go:generate|sys) " - - #TODO: remove after upgrading to go1.18 - # ignore comment spacing for nolint and sys directives - - linters: - - revive - text: "^comment-spacings: no space between comment delimiter and comment text" - source: "//(cspell:|nolint:|sys |todo)" - - # not on go 1.18 yet, so no any - - linters: - - revive - text: "^use-any: since GO 1.18 'interface{}' can be replaced by 'any'" - - # allow unjustified ignores of error checks in defer statements - - linters: - - nolintlint - text: "^directive `//nolint:errcheck` should provide explanation" - source: '^\s*defer ' - - # allow unjustified ignores of error lints for io.EOF - - linters: - - nolintlint - text: "^directive `//nolint:errorlint` should provide explanation" - source: '[=|!]= io.EOF' - - -linters-settings: - exhaustive: - default-signifies-exhaustive: true - govet: - enable-all: true - disable: - # struct order is often for Win32 compat - # also, ignore pointer bytes/GC issues for now until performance becomes an issue - - fieldalignment - nolintlint: - require-explanation: true - require-specific: true - revive: - # revive is more configurable than static check, so likely the preferred alternative to static-check - # (once the perf issue is solved: https://github.com/golangci/golangci-lint/issues/2997) - enable-all-rules: - true - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md - rules: - # rules with required arguments - - name: argument-limit - disabled: true - - name: banned-characters - disabled: true - - name: cognitive-complexity - disabled: true - - name: cyclomatic - disabled: true - - name: file-header - disabled: true - - name: function-length - disabled: true - - name: function-result-limit - disabled: true - - name: max-public-structs - disabled: true - # geneally annoying rules - - name: add-constant # complains about any and all strings and integers - disabled: true - - name: confusing-naming # we frequently use "Foo()" and "foo()" together - disabled: true - - name: flag-parameter # excessive, and a common idiom we use - disabled: true - - name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead - disabled: true - # general config - - name: line-length-limit - arguments: - - 140 - - name: var-naming - arguments: - - [] - - - CID - - CRI - - CTRD - - DACL - - DLL - - DOS - - ETW - - FSCTL - - GCS - - GMSA - - HCS - - HV - - IO - - LCOW - - LDAP - - LPAC - - LTSC - - MMIO - - NT - - OCI - - PMEM - - PWSH - - RX - - SACl - - SID - - SMB - - TX - - VHD - - VHDX - - VMID - - VPCI - - WCOW - - WIM diff --git a/vendor/github.com/Microsoft/go-winio/CODEOWNERS b/vendor/github.com/Microsoft/go-winio/CODEOWNERS deleted file mode 100644 index ae1b4942b91..00000000000 --- a/vendor/github.com/Microsoft/go-winio/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ - * @microsoft/containerplat diff --git a/vendor/github.com/Microsoft/go-winio/LICENSE b/vendor/github.com/Microsoft/go-winio/LICENSE deleted file mode 100644 index b8b569d7746..00000000000 --- a/vendor/github.com/Microsoft/go-winio/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/vendor/github.com/Microsoft/go-winio/README.md b/vendor/github.com/Microsoft/go-winio/README.md deleted file mode 100644 index 7474b4f0b65..00000000000 --- a/vendor/github.com/Microsoft/go-winio/README.md +++ /dev/null @@ -1,89 +0,0 @@ -# go-winio [![Build Status](https://github.com/microsoft/go-winio/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/go-winio/actions/workflows/ci.yml) - -This repository contains utilities for efficiently performing Win32 IO operations in -Go. Currently, this is focused on accessing named pipes and other file handles, and -for using named pipes as a net transport. - -This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go -to reuse the thread to schedule another goroutine. This limits support to Windows Vista and -newer operating systems. This is similar to the implementation of network sockets in Go's net -package. - -Please see the LICENSE file for licensing information. - -## Contributing - -This project welcomes contributions and suggestions. -Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that -you have the right to, and actually do, grant us the rights to use your contribution. -For details, visit [Microsoft CLA](https://cla.microsoft.com). - -When you submit a pull request, a CLA-bot will automatically determine whether you need to -provide a CLA and decorate the PR appropriately (e.g., label, comment). -Simply follow the instructions provided by the bot. -You will only need to do this once across all repos using our CLA. - -Additionally, the pull request pipeline requires the following steps to be performed before -mergining. - -### Code Sign-Off - -We require that contributors sign their commits using [`git commit --signoff`][git-commit-s] -to certify they either authored the work themselves or otherwise have permission to use it in this project. - -A range of commits can be signed off using [`git rebase --signoff`][git-rebase-s]. - -Please see [the developer certificate](https://developercertificate.org) for more info, -as well as to make sure that you can attest to the rules listed. -Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off. - -### Linting - -Code must pass a linting stage, which uses [`golangci-lint`][lint]. -The linting settings are stored in [`.golangci.yaml`](./.golangci.yaml), and can be run -automatically with VSCode by adding the following to your workspace or folder settings: - -```json - "go.lintTool": "golangci-lint", - "go.lintOnSave": "package", -``` - -Additional editor [integrations options are also available][lint-ide]. - -Alternatively, `golangci-lint` can be [installed locally][lint-install] and run from the repo root: - -```shell -# use . or specify a path to only lint a package -# to show all lint errors, use flags "--max-issues-per-linter=0 --max-same-issues=0" -> golangci-lint run ./... -``` - -### Go Generate - -The pipeline checks that auto-generated code, via `go generate`, are up to date. - -This can be done for the entire repo: - -```shell -> go generate ./... -``` - -## Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -## Special Thanks - -Thanks to [natefinch][natefinch] for the inspiration for this library. -See [npipe](https://github.com/natefinch/npipe) for another named pipe implementation. - -[lint]: https://golangci-lint.run/ -[lint-ide]: https://golangci-lint.run/usage/integrations/#editor-integration -[lint-install]: https://golangci-lint.run/usage/install/#local-installation - -[git-commit-s]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s -[git-rebase-s]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---signoff - -[natefinch]: https://github.com/natefinch diff --git a/vendor/github.com/Microsoft/go-winio/SECURITY.md b/vendor/github.com/Microsoft/go-winio/SECURITY.md deleted file mode 100644 index 869fdfe2b24..00000000000 --- a/vendor/github.com/Microsoft/go-winio/SECURITY.md +++ /dev/null @@ -1,41 +0,0 @@ - - -## Security - -Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). - -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. - -## Reporting Security Issues - -**Please do not report security vulnerabilities through public GitHub issues.** - -Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). - -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). - -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). - -Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: - - * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) - * Full paths of source file(s) related to the manifestation of the issue - * The location of the affected source code (tag/branch/commit or direct URL) - * Any special configuration required to reproduce the issue - * Step-by-step instructions to reproduce the issue - * Proof-of-concept or exploit code (if possible) - * Impact of the issue, including how an attacker might exploit the issue - -This information will help us triage your report more quickly. - -If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. - -## Preferred Languages - -We prefer all communications to be in English. - -## Policy - -Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). - - diff --git a/vendor/github.com/Microsoft/go-winio/backup.go b/vendor/github.com/Microsoft/go-winio/backup.go deleted file mode 100644 index b54341daacb..00000000000 --- a/vendor/github.com/Microsoft/go-winio/backup.go +++ /dev/null @@ -1,287 +0,0 @@ -//go:build windows -// +build windows - -package winio - -import ( - "encoding/binary" - "errors" - "fmt" - "io" - "os" - "runtime" - "unicode/utf16" - - "github.com/Microsoft/go-winio/internal/fs" - "golang.org/x/sys/windows" -) - -//sys backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead -//sys backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite - -const ( - BackupData = uint32(iota + 1) - BackupEaData - BackupSecurity - BackupAlternateData - BackupLink - BackupPropertyData - BackupObjectId //revive:disable-line:var-naming ID, not Id - BackupReparseData - BackupSparseBlock - BackupTxfsData -) - -const ( - StreamSparseAttributes = uint32(8) -) - -//nolint:revive // var-naming: ALL_CAPS -const ( - WRITE_DAC = windows.WRITE_DAC - WRITE_OWNER = windows.WRITE_OWNER - ACCESS_SYSTEM_SECURITY = windows.ACCESS_SYSTEM_SECURITY -) - -// BackupHeader represents a backup stream of a file. -type BackupHeader struct { - //revive:disable-next-line:var-naming ID, not Id - Id uint32 // The backup stream ID - Attributes uint32 // Stream attributes - Size int64 // The size of the stream in bytes - Name string // The name of the stream (for BackupAlternateData only). - Offset int64 // The offset of the stream in the file (for BackupSparseBlock only). -} - -type win32StreamID struct { - StreamID uint32 - Attributes uint32 - Size uint64 - NameSize uint32 -} - -// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series -// of BackupHeader values. -type BackupStreamReader struct { - r io.Reader - bytesLeft int64 -} - -// NewBackupStreamReader produces a BackupStreamReader from any io.Reader. -func NewBackupStreamReader(r io.Reader) *BackupStreamReader { - return &BackupStreamReader{r, 0} -} - -// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if -// it was not completely read. -func (r *BackupStreamReader) Next() (*BackupHeader, error) { - if r.bytesLeft > 0 { //nolint:nestif // todo: flatten this - if s, ok := r.r.(io.Seeker); ok { - // Make sure Seek on io.SeekCurrent sometimes succeeds - // before trying the actual seek. - if _, err := s.Seek(0, io.SeekCurrent); err == nil { - if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil { - return nil, err - } - r.bytesLeft = 0 - } - } - if _, err := io.Copy(io.Discard, r); err != nil { - return nil, err - } - } - var wsi win32StreamID - if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil { - return nil, err - } - hdr := &BackupHeader{ - Id: wsi.StreamID, - Attributes: wsi.Attributes, - Size: int64(wsi.Size), - } - if wsi.NameSize != 0 { - name := make([]uint16, int(wsi.NameSize/2)) - if err := binary.Read(r.r, binary.LittleEndian, name); err != nil { - return nil, err - } - hdr.Name = windows.UTF16ToString(name) - } - if wsi.StreamID == BackupSparseBlock { - if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil { - return nil, err - } - hdr.Size -= 8 - } - r.bytesLeft = hdr.Size - return hdr, nil -} - -// Read reads from the current backup stream. -func (r *BackupStreamReader) Read(b []byte) (int, error) { - if r.bytesLeft == 0 { - return 0, io.EOF - } - if int64(len(b)) > r.bytesLeft { - b = b[:r.bytesLeft] - } - n, err := r.r.Read(b) - r.bytesLeft -= int64(n) - if err == io.EOF { - err = io.ErrUnexpectedEOF - } else if r.bytesLeft == 0 && err == nil { - err = io.EOF - } - return n, err -} - -// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API. -type BackupStreamWriter struct { - w io.Writer - bytesLeft int64 -} - -// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer. -func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter { - return &BackupStreamWriter{w, 0} -} - -// WriteHeader writes the next backup stream header and prepares for calls to Write(). -func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error { - if w.bytesLeft != 0 { - return fmt.Errorf("missing %d bytes", w.bytesLeft) - } - name := utf16.Encode([]rune(hdr.Name)) - wsi := win32StreamID{ - StreamID: hdr.Id, - Attributes: hdr.Attributes, - Size: uint64(hdr.Size), - NameSize: uint32(len(name) * 2), - } - if hdr.Id == BackupSparseBlock { - // Include space for the int64 block offset - wsi.Size += 8 - } - if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil { - return err - } - if len(name) != 0 { - if err := binary.Write(w.w, binary.LittleEndian, name); err != nil { - return err - } - } - if hdr.Id == BackupSparseBlock { - if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil { - return err - } - } - w.bytesLeft = hdr.Size - return nil -} - -// Write writes to the current backup stream. -func (w *BackupStreamWriter) Write(b []byte) (int, error) { - if w.bytesLeft < int64(len(b)) { - return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft) - } - n, err := w.w.Write(b) - w.bytesLeft -= int64(n) - return n, err -} - -// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API. -type BackupFileReader struct { - f *os.File - includeSecurity bool - ctx uintptr -} - -// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true, -// Read will attempt to read the security descriptor of the file. -func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader { - r := &BackupFileReader{f, includeSecurity, 0} - return r -} - -// Read reads a backup stream from the file by calling the Win32 API BackupRead(). -func (r *BackupFileReader) Read(b []byte) (int, error) { - var bytesRead uint32 - err := backupRead(windows.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx) - if err != nil { - return 0, &os.PathError{Op: "BackupRead", Path: r.f.Name(), Err: err} - } - runtime.KeepAlive(r.f) - if bytesRead == 0 { - return 0, io.EOF - } - return int(bytesRead), nil -} - -// Close frees Win32 resources associated with the BackupFileReader. It does not close -// the underlying file. -func (r *BackupFileReader) Close() error { - if r.ctx != 0 { - _ = backupRead(windows.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx) - runtime.KeepAlive(r.f) - r.ctx = 0 - } - return nil -} - -// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API. -type BackupFileWriter struct { - f *os.File - includeSecurity bool - ctx uintptr -} - -// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true, -// Write() will attempt to restore the security descriptor from the stream. -func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter { - w := &BackupFileWriter{f, includeSecurity, 0} - return w -} - -// Write restores a portion of the file using the provided backup stream. -func (w *BackupFileWriter) Write(b []byte) (int, error) { - var bytesWritten uint32 - err := backupWrite(windows.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx) - if err != nil { - return 0, &os.PathError{Op: "BackupWrite", Path: w.f.Name(), Err: err} - } - runtime.KeepAlive(w.f) - if int(bytesWritten) != len(b) { - return int(bytesWritten), errors.New("not all bytes could be written") - } - return len(b), nil -} - -// Close frees Win32 resources associated with the BackupFileWriter. It does not -// close the underlying file. -func (w *BackupFileWriter) Close() error { - if w.ctx != 0 { - _ = backupWrite(windows.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx) - runtime.KeepAlive(w.f) - w.ctx = 0 - } - return nil -} - -// OpenForBackup opens a file or directory, potentially skipping access checks if the backup -// or restore privileges have been acquired. -// -// If the file opened was a directory, it cannot be used with Readdir(). -func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) { - h, err := fs.CreateFile(path, - fs.AccessMask(access), - fs.FileShareMode(share), - nil, - fs.FileCreationDisposition(createmode), - fs.FILE_FLAG_BACKUP_SEMANTICS|fs.FILE_FLAG_OPEN_REPARSE_POINT, - 0, - ) - if err != nil { - err = &os.PathError{Op: "open", Path: path, Err: err} - return nil, err - } - return os.NewFile(uintptr(h), path), nil -} diff --git a/vendor/github.com/Microsoft/go-winio/doc.go b/vendor/github.com/Microsoft/go-winio/doc.go deleted file mode 100644 index 1f5bfe2d548..00000000000 --- a/vendor/github.com/Microsoft/go-winio/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -// This package provides utilities for efficiently performing Win32 IO operations in Go. -// Currently, this package is provides support for genreal IO and management of -// - named pipes -// - files -// - [Hyper-V sockets] -// -// This code is similar to Go's [net] package, and uses IO completion ports to avoid -// blocking IO on system threads, allowing Go to reuse the thread to schedule other goroutines. -// -// This limits support to Windows Vista and newer operating systems. -// -// Additionally, this package provides support for: -// - creating and managing GUIDs -// - writing to [ETW] -// - opening and manageing VHDs -// - parsing [Windows Image files] -// - auto-generating Win32 API code -// -// [Hyper-V sockets]: https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service -// [ETW]: https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw- -// [Windows Image files]: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/work-with-windows-images -package winio diff --git a/vendor/github.com/Microsoft/go-winio/ea.go b/vendor/github.com/Microsoft/go-winio/ea.go deleted file mode 100644 index e104dbdfdf9..00000000000 --- a/vendor/github.com/Microsoft/go-winio/ea.go +++ /dev/null @@ -1,137 +0,0 @@ -package winio - -import ( - "bytes" - "encoding/binary" - "errors" -) - -type fileFullEaInformation struct { - NextEntryOffset uint32 - Flags uint8 - NameLength uint8 - ValueLength uint16 -} - -var ( - fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) - - errInvalidEaBuffer = errors.New("invalid extended attribute buffer") - errEaNameTooLarge = errors.New("extended attribute name too large") - errEaValueTooLarge = errors.New("extended attribute value too large") -) - -// ExtendedAttribute represents a single Windows EA. -type ExtendedAttribute struct { - Name string - Value []byte - Flags uint8 -} - -func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { - var info fileFullEaInformation - err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) - if err != nil { - err = errInvalidEaBuffer - return ea, nb, err - } - - nameOffset := fileFullEaInformationSize - nameLen := int(info.NameLength) - valueOffset := nameOffset + int(info.NameLength) + 1 - valueLen := int(info.ValueLength) - nextOffset := int(info.NextEntryOffset) - if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { - err = errInvalidEaBuffer - return ea, nb, err - } - - ea.Name = string(b[nameOffset : nameOffset+nameLen]) - ea.Value = b[valueOffset : valueOffset+valueLen] - ea.Flags = info.Flags - if info.NextEntryOffset != 0 { - nb = b[info.NextEntryOffset:] - } - return ea, nb, err -} - -// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION -// buffer retrieved from BackupRead, ZwQueryEaFile, etc. -func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { - for len(b) != 0 { - ea, nb, err := parseEa(b) - if err != nil { - return nil, err - } - - eas = append(eas, ea) - b = nb - } - return eas, err -} - -func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { - if int(uint8(len(ea.Name))) != len(ea.Name) { - return errEaNameTooLarge - } - if int(uint16(len(ea.Value))) != len(ea.Value) { - return errEaValueTooLarge - } - entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) - withPadding := (entrySize + 3) &^ 3 - nextOffset := uint32(0) - if !last { - nextOffset = withPadding - } - info := fileFullEaInformation{ - NextEntryOffset: nextOffset, - Flags: ea.Flags, - NameLength: uint8(len(ea.Name)), - ValueLength: uint16(len(ea.Value)), - } - - err := binary.Write(buf, binary.LittleEndian, &info) - if err != nil { - return err - } - - _, err = buf.Write([]byte(ea.Name)) - if err != nil { - return err - } - - err = buf.WriteByte(0) - if err != nil { - return err - } - - _, err = buf.Write(ea.Value) - if err != nil { - return err - } - - _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) - if err != nil { - return err - } - - return nil -} - -// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION -// buffer for use with BackupWrite, ZwSetEaFile, etc. -func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { - var buf bytes.Buffer - for i := range eas { - last := false - if i == len(eas)-1 { - last = true - } - - err := writeEa(&buf, &eas[i], last) - if err != nil { - return nil, err - } - } - return buf.Bytes(), nil -} diff --git a/vendor/github.com/Microsoft/go-winio/file.go b/vendor/github.com/Microsoft/go-winio/file.go deleted file mode 100644 index fe82a180dbd..00000000000 --- a/vendor/github.com/Microsoft/go-winio/file.go +++ /dev/null @@ -1,320 +0,0 @@ -//go:build windows -// +build windows - -package winio - -import ( - "errors" - "io" - "runtime" - "sync" - "sync/atomic" - "syscall" - "time" - - "golang.org/x/sys/windows" -) - -//sys cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) = CancelIoEx -//sys createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) = CreateIoCompletionPort -//sys getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus -//sys setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes -//sys wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult - -var ( - ErrFileClosed = errors.New("file has already been closed") - ErrTimeout = &timeoutError{} -) - -type timeoutError struct{} - -func (*timeoutError) Error() string { return "i/o timeout" } -func (*timeoutError) Timeout() bool { return true } -func (*timeoutError) Temporary() bool { return true } - -type timeoutChan chan struct{} - -var ioInitOnce sync.Once -var ioCompletionPort windows.Handle - -// ioResult contains the result of an asynchronous IO operation. -type ioResult struct { - bytes uint32 - err error -} - -// ioOperation represents an outstanding asynchronous Win32 IO. -type ioOperation struct { - o windows.Overlapped - ch chan ioResult -} - -func initIO() { - h, err := createIoCompletionPort(windows.InvalidHandle, 0, 0, 0xffffffff) - if err != nil { - panic(err) - } - ioCompletionPort = h - go ioCompletionProcessor(h) -} - -// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall. -// It takes ownership of this handle and will close it if it is garbage collected. -type win32File struct { - handle windows.Handle - wg sync.WaitGroup - wgLock sync.RWMutex - closing atomic.Bool - socket bool - readDeadline deadlineHandler - writeDeadline deadlineHandler -} - -type deadlineHandler struct { - setLock sync.Mutex - channel timeoutChan - channelLock sync.RWMutex - timer *time.Timer - timedout atomic.Bool -} - -// makeWin32File makes a new win32File from an existing file handle. -func makeWin32File(h windows.Handle) (*win32File, error) { - f := &win32File{handle: h} - ioInitOnce.Do(initIO) - _, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff) - if err != nil { - return nil, err - } - err = setFileCompletionNotificationModes(h, windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS|windows.FILE_SKIP_SET_EVENT_ON_HANDLE) - if err != nil { - return nil, err - } - f.readDeadline.channel = make(timeoutChan) - f.writeDeadline.channel = make(timeoutChan) - return f, nil -} - -// Deprecated: use NewOpenFile instead. -func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) { - return NewOpenFile(windows.Handle(h)) -} - -func NewOpenFile(h windows.Handle) (io.ReadWriteCloser, error) { - // If we return the result of makeWin32File directly, it can result in an - // interface-wrapped nil, rather than a nil interface value. - f, err := makeWin32File(h) - if err != nil { - return nil, err - } - return f, nil -} - -// closeHandle closes the resources associated with a Win32 handle. -func (f *win32File) closeHandle() { - f.wgLock.Lock() - // Atomically set that we are closing, releasing the resources only once. - if !f.closing.Swap(true) { - f.wgLock.Unlock() - // cancel all IO and wait for it to complete - _ = cancelIoEx(f.handle, nil) - f.wg.Wait() - // at this point, no new IO can start - windows.Close(f.handle) - f.handle = 0 - } else { - f.wgLock.Unlock() - } -} - -// Close closes a win32File. -func (f *win32File) Close() error { - f.closeHandle() - return nil -} - -// IsClosed checks if the file has been closed. -func (f *win32File) IsClosed() bool { - return f.closing.Load() -} - -// prepareIO prepares for a new IO operation. -// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. -func (f *win32File) prepareIO() (*ioOperation, error) { - f.wgLock.RLock() - if f.closing.Load() { - f.wgLock.RUnlock() - return nil, ErrFileClosed - } - f.wg.Add(1) - f.wgLock.RUnlock() - c := &ioOperation{} - c.ch = make(chan ioResult) - return c, nil -} - -// ioCompletionProcessor processes completed async IOs forever. -func ioCompletionProcessor(h windows.Handle) { - for { - var bytes uint32 - var key uintptr - var op *ioOperation - err := getQueuedCompletionStatus(h, &bytes, &key, &op, windows.INFINITE) - if op == nil { - panic(err) - } - op.ch <- ioResult{bytes, err} - } -} - -// todo: helsaawy - create an asyncIO version that takes a context - -// asyncIO processes the return value from ReadFile or WriteFile, blocking until -// the operation has actually completed. -func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) { - if err != windows.ERROR_IO_PENDING { //nolint:errorlint // err is Errno - return int(bytes), err - } - - if f.closing.Load() { - _ = cancelIoEx(f.handle, &c.o) - } - - var timeout timeoutChan - if d != nil { - d.channelLock.Lock() - timeout = d.channel - d.channelLock.Unlock() - } - - var r ioResult - select { - case r = <-c.ch: - err = r.err - if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno - if f.closing.Load() { - err = ErrFileClosed - } - } else if err != nil && f.socket { - // err is from Win32. Query the overlapped structure to get the winsock error. - var bytes, flags uint32 - err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags) - } - case <-timeout: - _ = cancelIoEx(f.handle, &c.o) - r = <-c.ch - err = r.err - if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno - err = ErrTimeout - } - } - - // runtime.KeepAlive is needed, as c is passed via native - // code to ioCompletionProcessor, c must remain alive - // until the channel read is complete. - // todo: (de)allocate *ioOperation via win32 heap functions, instead of needing to KeepAlive? - runtime.KeepAlive(c) - return int(r.bytes), err -} - -// Read reads from a file handle. -func (f *win32File) Read(b []byte) (int, error) { - c, err := f.prepareIO() - if err != nil { - return 0, err - } - defer f.wg.Done() - - if f.readDeadline.timedout.Load() { - return 0, ErrTimeout - } - - var bytes uint32 - err = windows.ReadFile(f.handle, b, &bytes, &c.o) - n, err := f.asyncIO(c, &f.readDeadline, bytes, err) - runtime.KeepAlive(b) - - // Handle EOF conditions. - if err == nil && n == 0 && len(b) != 0 { - return 0, io.EOF - } else if err == windows.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno - return 0, io.EOF - } - return n, err -} - -// Write writes to a file handle. -func (f *win32File) Write(b []byte) (int, error) { - c, err := f.prepareIO() - if err != nil { - return 0, err - } - defer f.wg.Done() - - if f.writeDeadline.timedout.Load() { - return 0, ErrTimeout - } - - var bytes uint32 - err = windows.WriteFile(f.handle, b, &bytes, &c.o) - n, err := f.asyncIO(c, &f.writeDeadline, bytes, err) - runtime.KeepAlive(b) - return n, err -} - -func (f *win32File) SetReadDeadline(deadline time.Time) error { - return f.readDeadline.set(deadline) -} - -func (f *win32File) SetWriteDeadline(deadline time.Time) error { - return f.writeDeadline.set(deadline) -} - -func (f *win32File) Flush() error { - return windows.FlushFileBuffers(f.handle) -} - -func (f *win32File) Fd() uintptr { - return uintptr(f.handle) -} - -func (d *deadlineHandler) set(deadline time.Time) error { - d.setLock.Lock() - defer d.setLock.Unlock() - - if d.timer != nil { - if !d.timer.Stop() { - <-d.channel - } - d.timer = nil - } - d.timedout.Store(false) - - select { - case <-d.channel: - d.channelLock.Lock() - d.channel = make(chan struct{}) - d.channelLock.Unlock() - default: - } - - if deadline.IsZero() { - return nil - } - - timeoutIO := func() { - d.timedout.Store(true) - close(d.channel) - } - - now := time.Now() - duration := deadline.Sub(now) - if deadline.After(now) { - // Deadline is in the future, set a timer to wait - d.timer = time.AfterFunc(duration, timeoutIO) - } else { - // Deadline is in the past. Cancel all pending IO now. - timeoutIO() - } - return nil -} diff --git a/vendor/github.com/Microsoft/go-winio/fileinfo.go b/vendor/github.com/Microsoft/go-winio/fileinfo.go deleted file mode 100644 index c860eb9917a..00000000000 --- a/vendor/github.com/Microsoft/go-winio/fileinfo.go +++ /dev/null @@ -1,106 +0,0 @@ -//go:build windows -// +build windows - -package winio - -import ( - "os" - "runtime" - "unsafe" - - "golang.org/x/sys/windows" -) - -// FileBasicInfo contains file access time and file attributes information. -type FileBasicInfo struct { - CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime - FileAttributes uint32 - _ uint32 // padding -} - -// alignedFileBasicInfo is a FileBasicInfo, but aligned to uint64 by containing -// uint64 rather than windows.Filetime. Filetime contains two uint32s. uint64 -// alignment is necessary to pass this as FILE_BASIC_INFO. -type alignedFileBasicInfo struct { - CreationTime, LastAccessTime, LastWriteTime, ChangeTime uint64 - FileAttributes uint32 - _ uint32 // padding -} - -// GetFileBasicInfo retrieves times and attributes for a file. -func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { - bi := &alignedFileBasicInfo{} - if err := windows.GetFileInformationByHandleEx( - windows.Handle(f.Fd()), - windows.FileBasicInfo, - (*byte)(unsafe.Pointer(bi)), - uint32(unsafe.Sizeof(*bi)), - ); err != nil { - return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} - } - runtime.KeepAlive(f) - // Reinterpret the alignedFileBasicInfo as a FileBasicInfo so it matches the - // public API of this module. The data may be unnecessarily aligned. - return (*FileBasicInfo)(unsafe.Pointer(bi)), nil -} - -// SetFileBasicInfo sets times and attributes for a file. -func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error { - // Create an alignedFileBasicInfo based on a FileBasicInfo. The copy is - // suitable to pass to GetFileInformationByHandleEx. - biAligned := *(*alignedFileBasicInfo)(unsafe.Pointer(bi)) - if err := windows.SetFileInformationByHandle( - windows.Handle(f.Fd()), - windows.FileBasicInfo, - (*byte)(unsafe.Pointer(&biAligned)), - uint32(unsafe.Sizeof(biAligned)), - ); err != nil { - return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err} - } - runtime.KeepAlive(f) - return nil -} - -// FileStandardInfo contains extended information for the file. -// FILE_STANDARD_INFO in WinBase.h -// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info -type FileStandardInfo struct { - AllocationSize, EndOfFile int64 - NumberOfLinks uint32 - DeletePending, Directory bool -} - -// GetFileStandardInfo retrieves ended information for the file. -func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) { - si := &FileStandardInfo{} - if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), - windows.FileStandardInfo, - (*byte)(unsafe.Pointer(si)), - uint32(unsafe.Sizeof(*si))); err != nil { - return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} - } - runtime.KeepAlive(f) - return si, nil -} - -// FileIDInfo contains the volume serial number and file ID for a file. This pair should be -// unique on a system. -type FileIDInfo struct { - VolumeSerialNumber uint64 - FileID [16]byte -} - -// GetFileID retrieves the unique (volume, file ID) pair for a file. -func GetFileID(f *os.File) (*FileIDInfo, error) { - fileID := &FileIDInfo{} - if err := windows.GetFileInformationByHandleEx( - windows.Handle(f.Fd()), - windows.FileIdInfo, - (*byte)(unsafe.Pointer(fileID)), - uint32(unsafe.Sizeof(*fileID)), - ); err != nil { - return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} - } - runtime.KeepAlive(f) - return fileID, nil -} diff --git a/vendor/github.com/Microsoft/go-winio/hvsock.go b/vendor/github.com/Microsoft/go-winio/hvsock.go deleted file mode 100644 index c4fdd9d4aec..00000000000 --- a/vendor/github.com/Microsoft/go-winio/hvsock.go +++ /dev/null @@ -1,582 +0,0 @@ -//go:build windows -// +build windows - -package winio - -import ( - "context" - "errors" - "fmt" - "io" - "net" - "os" - "time" - "unsafe" - - "golang.org/x/sys/windows" - - "github.com/Microsoft/go-winio/internal/socket" - "github.com/Microsoft/go-winio/pkg/guid" -) - -const afHVSock = 34 // AF_HYPERV - -// Well known Service and VM IDs -// https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards - -// HvsockGUIDWildcard is the wildcard VmId for accepting connections from all partitions. -func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000 - return guid.GUID{} -} - -// HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions. -func HvsockGUIDBroadcast() guid.GUID { // ffffffff-ffff-ffff-ffff-ffffffffffff - return guid.GUID{ - Data1: 0xffffffff, - Data2: 0xffff, - Data3: 0xffff, - Data4: [8]uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, - } -} - -// HvsockGUIDLoopback is the Loopback VmId for accepting connections to the same partition as the connector. -func HvsockGUIDLoopback() guid.GUID { // e0e16197-dd56-4a10-9195-5ee7a155a838 - return guid.GUID{ - Data1: 0xe0e16197, - Data2: 0xdd56, - Data3: 0x4a10, - Data4: [8]uint8{0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38}, - } -} - -// HvsockGUIDSiloHost is the address of a silo's host partition: -// - The silo host of a hosted silo is the utility VM. -// - The silo host of a silo on a physical host is the physical host. -func HvsockGUIDSiloHost() guid.GUID { // 36bd0c5c-7276-4223-88ba-7d03b654c568 - return guid.GUID{ - Data1: 0x36bd0c5c, - Data2: 0x7276, - Data3: 0x4223, - Data4: [8]byte{0x88, 0xba, 0x7d, 0x03, 0xb6, 0x54, 0xc5, 0x68}, - } -} - -// HvsockGUIDChildren is the wildcard VmId for accepting connections from the connector's child partitions. -func HvsockGUIDChildren() guid.GUID { // 90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd - return guid.GUID{ - Data1: 0x90db8b89, - Data2: 0xd35, - Data3: 0x4f79, - Data4: [8]uint8{0x8c, 0xe9, 0x49, 0xea, 0xa, 0xc8, 0xb7, 0xcd}, - } -} - -// HvsockGUIDParent is the wildcard VmId for accepting connections from the connector's parent partition. -// Listening on this VmId accepts connection from: -// - Inside silos: silo host partition. -// - Inside hosted silo: host of the VM. -// - Inside VM: VM host. -// - Physical host: Not supported. -func HvsockGUIDParent() guid.GUID { // a42e7cda-d03f-480c-9cc2-a4de20abb878 - return guid.GUID{ - Data1: 0xa42e7cda, - Data2: 0xd03f, - Data3: 0x480c, - Data4: [8]uint8{0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78}, - } -} - -// hvsockVsockServiceTemplate is the Service GUID used for the VSOCK protocol. -func hvsockVsockServiceTemplate() guid.GUID { // 00000000-facb-11e6-bd58-64006a7986d3 - return guid.GUID{ - Data2: 0xfacb, - Data3: 0x11e6, - Data4: [8]uint8{0xbd, 0x58, 0x64, 0x00, 0x6a, 0x79, 0x86, 0xd3}, - } -} - -// An HvsockAddr is an address for a AF_HYPERV socket. -type HvsockAddr struct { - VMID guid.GUID - ServiceID guid.GUID -} - -type rawHvsockAddr struct { - Family uint16 - _ uint16 - VMID guid.GUID - ServiceID guid.GUID -} - -var _ socket.RawSockaddr = &rawHvsockAddr{} - -// Network returns the address's network name, "hvsock". -func (*HvsockAddr) Network() string { - return "hvsock" -} - -func (addr *HvsockAddr) String() string { - return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID) -} - -// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port. -func VsockServiceID(port uint32) guid.GUID { - g := hvsockVsockServiceTemplate() // make a copy - g.Data1 = port - return g -} - -func (addr *HvsockAddr) raw() rawHvsockAddr { - return rawHvsockAddr{ - Family: afHVSock, - VMID: addr.VMID, - ServiceID: addr.ServiceID, - } -} - -func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) { - addr.VMID = raw.VMID - addr.ServiceID = raw.ServiceID -} - -// Sockaddr returns a pointer to and the size of this struct. -// -// Implements the [socket.RawSockaddr] interface, and allows use in -// [socket.Bind] and [socket.ConnectEx]. -func (r *rawHvsockAddr) Sockaddr() (unsafe.Pointer, int32, error) { - return unsafe.Pointer(r), int32(unsafe.Sizeof(rawHvsockAddr{})), nil -} - -// Sockaddr interface allows use with `sockets.Bind()` and `.ConnectEx()`. -func (r *rawHvsockAddr) FromBytes(b []byte) error { - n := int(unsafe.Sizeof(rawHvsockAddr{})) - - if len(b) < n { - return fmt.Errorf("got %d, want %d: %w", len(b), n, socket.ErrBufferSize) - } - - copy(unsafe.Slice((*byte)(unsafe.Pointer(r)), n), b[:n]) - if r.Family != afHVSock { - return fmt.Errorf("got %d, want %d: %w", r.Family, afHVSock, socket.ErrAddrFamily) - } - - return nil -} - -// HvsockListener is a socket listener for the AF_HYPERV address family. -type HvsockListener struct { - sock *win32File - addr HvsockAddr -} - -var _ net.Listener = &HvsockListener{} - -// HvsockConn is a connected socket of the AF_HYPERV address family. -type HvsockConn struct { - sock *win32File - local, remote HvsockAddr -} - -var _ net.Conn = &HvsockConn{} - -func newHVSocket() (*win32File, error) { - fd, err := windows.Socket(afHVSock, windows.SOCK_STREAM, 1) - if err != nil { - return nil, os.NewSyscallError("socket", err) - } - f, err := makeWin32File(fd) - if err != nil { - windows.Close(fd) - return nil, err - } - f.socket = true - return f, nil -} - -// ListenHvsock listens for connections on the specified hvsock address. -func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) { - l := &HvsockListener{addr: *addr} - - var sock *win32File - sock, err = newHVSocket() - if err != nil { - return nil, l.opErr("listen", err) - } - defer func() { - if err != nil { - _ = sock.Close() - } - }() - - sa := addr.raw() - err = socket.Bind(sock.handle, &sa) - if err != nil { - return nil, l.opErr("listen", os.NewSyscallError("socket", err)) - } - err = windows.Listen(sock.handle, 16) - if err != nil { - return nil, l.opErr("listen", os.NewSyscallError("listen", err)) - } - return &HvsockListener{sock: sock, addr: *addr}, nil -} - -func (l *HvsockListener) opErr(op string, err error) error { - return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err} -} - -// Addr returns the listener's network address. -func (l *HvsockListener) Addr() net.Addr { - return &l.addr -} - -// Accept waits for the next connection and returns it. -func (l *HvsockListener) Accept() (_ net.Conn, err error) { - sock, err := newHVSocket() - if err != nil { - return nil, l.opErr("accept", err) - } - defer func() { - if sock != nil { - sock.Close() - } - }() - c, err := l.sock.prepareIO() - if err != nil { - return nil, l.opErr("accept", err) - } - defer l.sock.wg.Done() - - // AcceptEx, per documentation, requires an extra 16 bytes per address. - // - // https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-acceptex - const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{})) - var addrbuf [addrlen * 2]byte - - var bytes uint32 - err = windows.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0 /* rxdatalen */, addrlen, addrlen, &bytes, &c.o) - if _, err = l.sock.asyncIO(c, nil, bytes, err); err != nil { - return nil, l.opErr("accept", os.NewSyscallError("acceptex", err)) - } - - conn := &HvsockConn{ - sock: sock, - } - // The local address returned in the AcceptEx buffer is the same as the Listener socket's - // address. However, the service GUID reported by GetSockName is different from the Listeners - // socket, and is sometimes the same as the local address of the socket that dialed the - // address, with the service GUID.Data1 incremented, but othertimes is different. - // todo: does the local address matter? is the listener's address or the actual address appropriate? - conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0]))) - conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen]))) - - // initialize the accepted socket and update its properties with those of the listening socket - if err = windows.Setsockopt(sock.handle, - windows.SOL_SOCKET, windows.SO_UPDATE_ACCEPT_CONTEXT, - (*byte)(unsafe.Pointer(&l.sock.handle)), int32(unsafe.Sizeof(l.sock.handle))); err != nil { - return nil, conn.opErr("accept", os.NewSyscallError("setsockopt", err)) - } - - sock = nil - return conn, nil -} - -// Close closes the listener, causing any pending Accept calls to fail. -func (l *HvsockListener) Close() error { - return l.sock.Close() -} - -// HvsockDialer configures and dials a Hyper-V Socket (ie, [HvsockConn]). -type HvsockDialer struct { - // Deadline is the time the Dial operation must connect before erroring. - Deadline time.Time - - // Retries is the number of additional connects to try if the connection times out, is refused, - // or the host is unreachable - Retries uint - - // RetryWait is the time to wait after a connection error to retry - RetryWait time.Duration - - rt *time.Timer // redial wait timer -} - -// Dial the Hyper-V socket at addr. -// -// See [HvsockDialer.Dial] for more information. -func Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) { - return (&HvsockDialer{}).Dial(ctx, addr) -} - -// Dial attempts to connect to the Hyper-V socket at addr, and returns a connection if successful. -// Will attempt (HvsockDialer).Retries if dialing fails, waiting (HvsockDialer).RetryWait between -// retries. -// -// Dialing can be cancelled either by providing (HvsockDialer).Deadline, or cancelling ctx. -func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) { - op := "dial" - // create the conn early to use opErr() - conn = &HvsockConn{ - remote: *addr, - } - - if !d.Deadline.IsZero() { - var cancel context.CancelFunc - ctx, cancel = context.WithDeadline(ctx, d.Deadline) - defer cancel() - } - - // preemptive timeout/cancellation check - if err = ctx.Err(); err != nil { - return nil, conn.opErr(op, err) - } - - sock, err := newHVSocket() - if err != nil { - return nil, conn.opErr(op, err) - } - defer func() { - if sock != nil { - sock.Close() - } - }() - - sa := addr.raw() - err = socket.Bind(sock.handle, &sa) - if err != nil { - return nil, conn.opErr(op, os.NewSyscallError("bind", err)) - } - - c, err := sock.prepareIO() - if err != nil { - return nil, conn.opErr(op, err) - } - defer sock.wg.Done() - var bytes uint32 - for i := uint(0); i <= d.Retries; i++ { - err = socket.ConnectEx( - sock.handle, - &sa, - nil, // sendBuf - 0, // sendDataLen - &bytes, - (*windows.Overlapped)(unsafe.Pointer(&c.o))) - _, err = sock.asyncIO(c, nil, bytes, err) - if i < d.Retries && canRedial(err) { - if err = d.redialWait(ctx); err == nil { - continue - } - } - break - } - if err != nil { - return nil, conn.opErr(op, os.NewSyscallError("connectex", err)) - } - - // update the connection properties, so shutdown can be used - if err = windows.Setsockopt( - sock.handle, - windows.SOL_SOCKET, - windows.SO_UPDATE_CONNECT_CONTEXT, - nil, // optvalue - 0, // optlen - ); err != nil { - return nil, conn.opErr(op, os.NewSyscallError("setsockopt", err)) - } - - // get the local name - var sal rawHvsockAddr - err = socket.GetSockName(sock.handle, &sal) - if err != nil { - return nil, conn.opErr(op, os.NewSyscallError("getsockname", err)) - } - conn.local.fromRaw(&sal) - - // one last check for timeout, since asyncIO doesn't check the context - if err = ctx.Err(); err != nil { - return nil, conn.opErr(op, err) - } - - conn.sock = sock - sock = nil - - return conn, nil -} - -// redialWait waits before attempting to redial, resetting the timer as appropriate. -func (d *HvsockDialer) redialWait(ctx context.Context) (err error) { - if d.RetryWait == 0 { - return nil - } - - if d.rt == nil { - d.rt = time.NewTimer(d.RetryWait) - } else { - // should already be stopped and drained - d.rt.Reset(d.RetryWait) - } - - select { - case <-ctx.Done(): - case <-d.rt.C: - return nil - } - - // stop and drain the timer - if !d.rt.Stop() { - <-d.rt.C - } - return ctx.Err() -} - -// assumes error is a plain, unwrapped windows.Errno provided by direct syscall. -func canRedial(err error) bool { - //nolint:errorlint // guaranteed to be an Errno - switch err { - case windows.WSAECONNREFUSED, windows.WSAENETUNREACH, windows.WSAETIMEDOUT, - windows.ERROR_CONNECTION_REFUSED, windows.ERROR_CONNECTION_UNAVAIL: - return true - default: - return false - } -} - -func (conn *HvsockConn) opErr(op string, err error) error { - // translate from "file closed" to "socket closed" - if errors.Is(err, ErrFileClosed) { - err = socket.ErrSocketClosed - } - return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err} -} - -func (conn *HvsockConn) Read(b []byte) (int, error) { - c, err := conn.sock.prepareIO() - if err != nil { - return 0, conn.opErr("read", err) - } - defer conn.sock.wg.Done() - buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))} - var flags, bytes uint32 - err = windows.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil) - n, err := conn.sock.asyncIO(c, &conn.sock.readDeadline, bytes, err) - if err != nil { - var eno windows.Errno - if errors.As(err, &eno) { - err = os.NewSyscallError("wsarecv", eno) - } - return 0, conn.opErr("read", err) - } else if n == 0 { - err = io.EOF - } - return n, err -} - -func (conn *HvsockConn) Write(b []byte) (int, error) { - t := 0 - for len(b) != 0 { - n, err := conn.write(b) - if err != nil { - return t + n, err - } - t += n - b = b[n:] - } - return t, nil -} - -func (conn *HvsockConn) write(b []byte) (int, error) { - c, err := conn.sock.prepareIO() - if err != nil { - return 0, conn.opErr("write", err) - } - defer conn.sock.wg.Done() - buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))} - var bytes uint32 - err = windows.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil) - n, err := conn.sock.asyncIO(c, &conn.sock.writeDeadline, bytes, err) - if err != nil { - var eno windows.Errno - if errors.As(err, &eno) { - err = os.NewSyscallError("wsasend", eno) - } - return 0, conn.opErr("write", err) - } - return n, err -} - -// Close closes the socket connection, failing any pending read or write calls. -func (conn *HvsockConn) Close() error { - return conn.sock.Close() -} - -func (conn *HvsockConn) IsClosed() bool { - return conn.sock.IsClosed() -} - -// shutdown disables sending or receiving on a socket. -func (conn *HvsockConn) shutdown(how int) error { - if conn.IsClosed() { - return socket.ErrSocketClosed - } - - err := windows.Shutdown(conn.sock.handle, how) - if err != nil { - // If the connection was closed, shutdowns fail with "not connected" - if errors.Is(err, windows.WSAENOTCONN) || - errors.Is(err, windows.WSAESHUTDOWN) { - err = socket.ErrSocketClosed - } - return os.NewSyscallError("shutdown", err) - } - return nil -} - -// CloseRead shuts down the read end of the socket, preventing future read operations. -func (conn *HvsockConn) CloseRead() error { - err := conn.shutdown(windows.SHUT_RD) - if err != nil { - return conn.opErr("closeread", err) - } - return nil -} - -// CloseWrite shuts down the write end of the socket, preventing future write operations and -// notifying the other endpoint that no more data will be written. -func (conn *HvsockConn) CloseWrite() error { - err := conn.shutdown(windows.SHUT_WR) - if err != nil { - return conn.opErr("closewrite", err) - } - return nil -} - -// LocalAddr returns the local address of the connection. -func (conn *HvsockConn) LocalAddr() net.Addr { - return &conn.local -} - -// RemoteAddr returns the remote address of the connection. -func (conn *HvsockConn) RemoteAddr() net.Addr { - return &conn.remote -} - -// SetDeadline implements the net.Conn SetDeadline method. -func (conn *HvsockConn) SetDeadline(t time.Time) error { - // todo: implement `SetDeadline` for `win32File` - if err := conn.SetReadDeadline(t); err != nil { - return fmt.Errorf("set read deadline: %w", err) - } - if err := conn.SetWriteDeadline(t); err != nil { - return fmt.Errorf("set write deadline: %w", err) - } - return nil -} - -// SetReadDeadline implements the net.Conn SetReadDeadline method. -func (conn *HvsockConn) SetReadDeadline(t time.Time) error { - return conn.sock.SetReadDeadline(t) -} - -// SetWriteDeadline implements the net.Conn SetWriteDeadline method. -func (conn *HvsockConn) SetWriteDeadline(t time.Time) error { - return conn.sock.SetWriteDeadline(t) -} diff --git a/vendor/github.com/Microsoft/go-winio/internal/fs/doc.go b/vendor/github.com/Microsoft/go-winio/internal/fs/doc.go deleted file mode 100644 index 1f653881783..00000000000 --- a/vendor/github.com/Microsoft/go-winio/internal/fs/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// This package contains Win32 filesystem functionality. -package fs diff --git a/vendor/github.com/Microsoft/go-winio/internal/fs/fs.go b/vendor/github.com/Microsoft/go-winio/internal/fs/fs.go deleted file mode 100644 index 0cd9621df78..00000000000 --- a/vendor/github.com/Microsoft/go-winio/internal/fs/fs.go +++ /dev/null @@ -1,262 +0,0 @@ -//go:build windows - -package fs - -import ( - "golang.org/x/sys/windows" - - "github.com/Microsoft/go-winio/internal/stringbuffer" -) - -//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go fs.go - -// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew -//sys CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateFileW - -const NullHandle windows.Handle = 0 - -// AccessMask defines standard, specific, and generic rights. -// -// Used with CreateFile and NtCreateFile (and co.). -// -// Bitmask: -// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 -// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 -// +---------------+---------------+-------------------------------+ -// |G|G|G|G|Resvd|A| StandardRights| SpecificRights | -// |R|W|E|A| |S| | | -// +-+-------------+---------------+-------------------------------+ -// -// GR Generic Read -// GW Generic Write -// GE Generic Exectue -// GA Generic All -// Resvd Reserved -// AS Access Security System -// -// https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask -// -// https://learn.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights -// -// https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants -type AccessMask = windows.ACCESS_MASK - -//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. -const ( - // Not actually any. - // - // For CreateFile: "query certain metadata such as file, directory, or device attributes without accessing that file or device" - // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#parameters - FILE_ANY_ACCESS AccessMask = 0 - - GENERIC_READ AccessMask = 0x8000_0000 - GENERIC_WRITE AccessMask = 0x4000_0000 - GENERIC_EXECUTE AccessMask = 0x2000_0000 - GENERIC_ALL AccessMask = 0x1000_0000 - ACCESS_SYSTEM_SECURITY AccessMask = 0x0100_0000 - - // Specific Object Access - // from ntioapi.h - - FILE_READ_DATA AccessMask = (0x0001) // file & pipe - FILE_LIST_DIRECTORY AccessMask = (0x0001) // directory - - FILE_WRITE_DATA AccessMask = (0x0002) // file & pipe - FILE_ADD_FILE AccessMask = (0x0002) // directory - - FILE_APPEND_DATA AccessMask = (0x0004) // file - FILE_ADD_SUBDIRECTORY AccessMask = (0x0004) // directory - FILE_CREATE_PIPE_INSTANCE AccessMask = (0x0004) // named pipe - - FILE_READ_EA AccessMask = (0x0008) // file & directory - FILE_READ_PROPERTIES AccessMask = FILE_READ_EA - - FILE_WRITE_EA AccessMask = (0x0010) // file & directory - FILE_WRITE_PROPERTIES AccessMask = FILE_WRITE_EA - - FILE_EXECUTE AccessMask = (0x0020) // file - FILE_TRAVERSE AccessMask = (0x0020) // directory - - FILE_DELETE_CHILD AccessMask = (0x0040) // directory - - FILE_READ_ATTRIBUTES AccessMask = (0x0080) // all - - FILE_WRITE_ATTRIBUTES AccessMask = (0x0100) // all - - FILE_ALL_ACCESS AccessMask = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF) - FILE_GENERIC_READ AccessMask = (STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE) - FILE_GENERIC_WRITE AccessMask = (STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE) - FILE_GENERIC_EXECUTE AccessMask = (STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE) - - SPECIFIC_RIGHTS_ALL AccessMask = 0x0000FFFF - - // Standard Access - // from ntseapi.h - - DELETE AccessMask = 0x0001_0000 - READ_CONTROL AccessMask = 0x0002_0000 - WRITE_DAC AccessMask = 0x0004_0000 - WRITE_OWNER AccessMask = 0x0008_0000 - SYNCHRONIZE AccessMask = 0x0010_0000 - - STANDARD_RIGHTS_REQUIRED AccessMask = 0x000F_0000 - - STANDARD_RIGHTS_READ AccessMask = READ_CONTROL - STANDARD_RIGHTS_WRITE AccessMask = READ_CONTROL - STANDARD_RIGHTS_EXECUTE AccessMask = READ_CONTROL - - STANDARD_RIGHTS_ALL AccessMask = 0x001F_0000 -) - -type FileShareMode uint32 - -//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. -const ( - FILE_SHARE_NONE FileShareMode = 0x00 - FILE_SHARE_READ FileShareMode = 0x01 - FILE_SHARE_WRITE FileShareMode = 0x02 - FILE_SHARE_DELETE FileShareMode = 0x04 - FILE_SHARE_VALID_FLAGS FileShareMode = 0x07 -) - -type FileCreationDisposition uint32 - -//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. -const ( - // from winbase.h - - CREATE_NEW FileCreationDisposition = 0x01 - CREATE_ALWAYS FileCreationDisposition = 0x02 - OPEN_EXISTING FileCreationDisposition = 0x03 - OPEN_ALWAYS FileCreationDisposition = 0x04 - TRUNCATE_EXISTING FileCreationDisposition = 0x05 -) - -// Create disposition values for NtCreate* -type NTFileCreationDisposition uint32 - -//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. -const ( - // From ntioapi.h - - FILE_SUPERSEDE NTFileCreationDisposition = 0x00 - FILE_OPEN NTFileCreationDisposition = 0x01 - FILE_CREATE NTFileCreationDisposition = 0x02 - FILE_OPEN_IF NTFileCreationDisposition = 0x03 - FILE_OVERWRITE NTFileCreationDisposition = 0x04 - FILE_OVERWRITE_IF NTFileCreationDisposition = 0x05 - FILE_MAXIMUM_DISPOSITION NTFileCreationDisposition = 0x05 -) - -// CreateFile and co. take flags or attributes together as one parameter. -// Define alias until we can use generics to allow both -// -// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants -type FileFlagOrAttribute uint32 - -//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. -const ( - // from winnt.h - - FILE_FLAG_WRITE_THROUGH FileFlagOrAttribute = 0x8000_0000 - FILE_FLAG_OVERLAPPED FileFlagOrAttribute = 0x4000_0000 - FILE_FLAG_NO_BUFFERING FileFlagOrAttribute = 0x2000_0000 - FILE_FLAG_RANDOM_ACCESS FileFlagOrAttribute = 0x1000_0000 - FILE_FLAG_SEQUENTIAL_SCAN FileFlagOrAttribute = 0x0800_0000 - FILE_FLAG_DELETE_ON_CLOSE FileFlagOrAttribute = 0x0400_0000 - FILE_FLAG_BACKUP_SEMANTICS FileFlagOrAttribute = 0x0200_0000 - FILE_FLAG_POSIX_SEMANTICS FileFlagOrAttribute = 0x0100_0000 - FILE_FLAG_OPEN_REPARSE_POINT FileFlagOrAttribute = 0x0020_0000 - FILE_FLAG_OPEN_NO_RECALL FileFlagOrAttribute = 0x0010_0000 - FILE_FLAG_FIRST_PIPE_INSTANCE FileFlagOrAttribute = 0x0008_0000 -) - -// NtCreate* functions take a dedicated CreateOptions parameter. -// -// https://learn.microsoft.com/en-us/windows/win32/api/Winternl/nf-winternl-ntcreatefile -// -// https://learn.microsoft.com/en-us/windows/win32/devnotes/nt-create-named-pipe-file -type NTCreateOptions uint32 - -//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. -const ( - // From ntioapi.h - - FILE_DIRECTORY_FILE NTCreateOptions = 0x0000_0001 - FILE_WRITE_THROUGH NTCreateOptions = 0x0000_0002 - FILE_SEQUENTIAL_ONLY NTCreateOptions = 0x0000_0004 - FILE_NO_INTERMEDIATE_BUFFERING NTCreateOptions = 0x0000_0008 - - FILE_SYNCHRONOUS_IO_ALERT NTCreateOptions = 0x0000_0010 - FILE_SYNCHRONOUS_IO_NONALERT NTCreateOptions = 0x0000_0020 - FILE_NON_DIRECTORY_FILE NTCreateOptions = 0x0000_0040 - FILE_CREATE_TREE_CONNECTION NTCreateOptions = 0x0000_0080 - - FILE_COMPLETE_IF_OPLOCKED NTCreateOptions = 0x0000_0100 - FILE_NO_EA_KNOWLEDGE NTCreateOptions = 0x0000_0200 - FILE_DISABLE_TUNNELING NTCreateOptions = 0x0000_0400 - FILE_RANDOM_ACCESS NTCreateOptions = 0x0000_0800 - - FILE_DELETE_ON_CLOSE NTCreateOptions = 0x0000_1000 - FILE_OPEN_BY_FILE_ID NTCreateOptions = 0x0000_2000 - FILE_OPEN_FOR_BACKUP_INTENT NTCreateOptions = 0x0000_4000 - FILE_NO_COMPRESSION NTCreateOptions = 0x0000_8000 -) - -type FileSQSFlag = FileFlagOrAttribute - -//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. -const ( - // from winbase.h - - SECURITY_ANONYMOUS FileSQSFlag = FileSQSFlag(SecurityAnonymous << 16) - SECURITY_IDENTIFICATION FileSQSFlag = FileSQSFlag(SecurityIdentification << 16) - SECURITY_IMPERSONATION FileSQSFlag = FileSQSFlag(SecurityImpersonation << 16) - SECURITY_DELEGATION FileSQSFlag = FileSQSFlag(SecurityDelegation << 16) - - SECURITY_SQOS_PRESENT FileSQSFlag = 0x0010_0000 - SECURITY_VALID_SQOS_FLAGS FileSQSFlag = 0x001F_0000 -) - -// GetFinalPathNameByHandle flags -// -// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew#parameters -type GetFinalPathFlag uint32 - -//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. -const ( - GetFinalPathDefaultFlag GetFinalPathFlag = 0x0 - - FILE_NAME_NORMALIZED GetFinalPathFlag = 0x0 - FILE_NAME_OPENED GetFinalPathFlag = 0x8 - - VOLUME_NAME_DOS GetFinalPathFlag = 0x0 - VOLUME_NAME_GUID GetFinalPathFlag = 0x1 - VOLUME_NAME_NT GetFinalPathFlag = 0x2 - VOLUME_NAME_NONE GetFinalPathFlag = 0x4 -) - -// getFinalPathNameByHandle facilitates calling the Windows API GetFinalPathNameByHandle -// with the given handle and flags. It transparently takes care of creating a buffer of the -// correct size for the call. -// -// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew -func GetFinalPathNameByHandle(h windows.Handle, flags GetFinalPathFlag) (string, error) { - b := stringbuffer.NewWString() - //TODO: can loop infinitely if Win32 keeps returning the same (or a larger) n? - for { - n, err := windows.GetFinalPathNameByHandle(h, b.Pointer(), b.Cap(), uint32(flags)) - if err != nil { - return "", err - } - // If the buffer wasn't large enough, n will be the total size needed (including null terminator). - // Resize and try again. - if n > b.Cap() { - b.ResizeTo(n) - continue - } - // If the buffer is large enough, n will be the size not including the null terminator. - // Convert to a Go string and return. - return b.String(), nil - } -} diff --git a/vendor/github.com/Microsoft/go-winio/internal/fs/security.go b/vendor/github.com/Microsoft/go-winio/internal/fs/security.go deleted file mode 100644 index 81760ac67e9..00000000000 --- a/vendor/github.com/Microsoft/go-winio/internal/fs/security.go +++ /dev/null @@ -1,12 +0,0 @@ -package fs - -// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level -type SecurityImpersonationLevel int32 // C default enums underlying type is `int`, which is Go `int32` - -// Impersonation levels -const ( - SecurityAnonymous SecurityImpersonationLevel = 0 - SecurityIdentification SecurityImpersonationLevel = 1 - SecurityImpersonation SecurityImpersonationLevel = 2 - SecurityDelegation SecurityImpersonationLevel = 3 -) diff --git a/vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.go b/vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.go deleted file mode 100644 index a94e234c706..00000000000 --- a/vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.go +++ /dev/null @@ -1,61 +0,0 @@ -//go:build windows - -// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. - -package fs - -import ( - "syscall" - "unsafe" - - "golang.org/x/sys/windows" -) - -var _ unsafe.Pointer - -// Do the interface allocations only once for common -// Errno values. -const ( - errnoERROR_IO_PENDING = 997 -) - -var ( - errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) - errERROR_EINVAL error = syscall.EINVAL -) - -// errnoErr returns common boxed Errno values, to prevent -// allocations at runtime. -func errnoErr(e syscall.Errno) error { - switch e { - case 0: - return errERROR_EINVAL - case errnoERROR_IO_PENDING: - return errERROR_IO_PENDING - } - return e -} - -var ( - modkernel32 = windows.NewLazySystemDLL("kernel32.dll") - - procCreateFileW = modkernel32.NewProc("CreateFileW") -) - -func CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) { - var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(name) - if err != nil { - return - } - return _CreateFile(_p0, access, mode, sa, createmode, attrs, templatefile) -} - -func _CreateFile(name *uint16, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) { - r0, _, e1 := syscall.SyscallN(procCreateFileW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile)) - handle = windows.Handle(r0) - if handle == windows.InvalidHandle { - err = errnoErr(e1) - } - return -} diff --git a/vendor/github.com/Microsoft/go-winio/internal/socket/rawaddr.go b/vendor/github.com/Microsoft/go-winio/internal/socket/rawaddr.go deleted file mode 100644 index 7e82f9afa95..00000000000 --- a/vendor/github.com/Microsoft/go-winio/internal/socket/rawaddr.go +++ /dev/null @@ -1,20 +0,0 @@ -package socket - -import ( - "unsafe" -) - -// RawSockaddr allows structs to be used with [Bind] and [ConnectEx]. The -// struct must meet the Win32 sockaddr requirements specified here: -// https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2 -// -// Specifically, the struct size must be least larger than an int16 (unsigned short) -// for the address family. -type RawSockaddr interface { - // Sockaddr returns a pointer to the RawSockaddr and its struct size, allowing - // for the RawSockaddr's data to be overwritten by syscalls (if necessary). - // - // It is the callers responsibility to validate that the values are valid; invalid - // pointers or size can cause a panic. - Sockaddr() (unsafe.Pointer, int32, error) -} diff --git a/vendor/github.com/Microsoft/go-winio/internal/socket/socket.go b/vendor/github.com/Microsoft/go-winio/internal/socket/socket.go deleted file mode 100644 index 88580d974ec..00000000000 --- a/vendor/github.com/Microsoft/go-winio/internal/socket/socket.go +++ /dev/null @@ -1,177 +0,0 @@ -//go:build windows - -package socket - -import ( - "errors" - "fmt" - "net" - "sync" - "syscall" - "unsafe" - - "github.com/Microsoft/go-winio/pkg/guid" - "golang.org/x/sys/windows" -) - -//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go socket.go - -//sys getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getsockname -//sys getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getpeername -//sys bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind - -const socketError = uintptr(^uint32(0)) - -var ( - // todo(helsaawy): create custom error types to store the desired vs actual size and addr family? - - ErrBufferSize = errors.New("buffer size") - ErrAddrFamily = errors.New("address family") - ErrInvalidPointer = errors.New("invalid pointer") - ErrSocketClosed = fmt.Errorf("socket closed: %w", net.ErrClosed) -) - -// todo(helsaawy): replace these with generics, ie: GetSockName[S RawSockaddr](s windows.Handle) (S, error) - -// GetSockName writes the local address of socket s to the [RawSockaddr] rsa. -// If rsa is not large enough, the [windows.WSAEFAULT] is returned. -func GetSockName(s windows.Handle, rsa RawSockaddr) error { - ptr, l, err := rsa.Sockaddr() - if err != nil { - return fmt.Errorf("could not retrieve socket pointer and size: %w", err) - } - - // although getsockname returns WSAEFAULT if the buffer is too small, it does not set - // &l to the correct size, so--apart from doubling the buffer repeatedly--there is no remedy - return getsockname(s, ptr, &l) -} - -// GetPeerName returns the remote address the socket is connected to. -// -// See [GetSockName] for more information. -func GetPeerName(s windows.Handle, rsa RawSockaddr) error { - ptr, l, err := rsa.Sockaddr() - if err != nil { - return fmt.Errorf("could not retrieve socket pointer and size: %w", err) - } - - return getpeername(s, ptr, &l) -} - -func Bind(s windows.Handle, rsa RawSockaddr) (err error) { - ptr, l, err := rsa.Sockaddr() - if err != nil { - return fmt.Errorf("could not retrieve socket pointer and size: %w", err) - } - - return bind(s, ptr, l) -} - -// "golang.org/x/sys/windows".ConnectEx and .Bind only accept internal implementations of the -// their sockaddr interface, so they cannot be used with HvsockAddr -// Replicate functionality here from -// https://cs.opensource.google/go/x/sys/+/master:windows/syscall_windows.go - -// The function pointers to `AcceptEx`, `ConnectEx` and `GetAcceptExSockaddrs` must be loaded at -// runtime via a WSAIoctl call: -// https://docs.microsoft.com/en-us/windows/win32/api/Mswsock/nc-mswsock-lpfn_connectex#remarks - -type runtimeFunc struct { - id guid.GUID - once sync.Once - addr uintptr - err error -} - -func (f *runtimeFunc) Load() error { - f.once.Do(func() { - var s windows.Handle - s, f.err = windows.Socket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP) - if f.err != nil { - return - } - defer windows.CloseHandle(s) //nolint:errcheck - - var n uint32 - f.err = windows.WSAIoctl(s, - windows.SIO_GET_EXTENSION_FUNCTION_POINTER, - (*byte)(unsafe.Pointer(&f.id)), - uint32(unsafe.Sizeof(f.id)), - (*byte)(unsafe.Pointer(&f.addr)), - uint32(unsafe.Sizeof(f.addr)), - &n, - nil, // overlapped - 0, // completionRoutine - ) - }) - return f.err -} - -var ( - // todo: add `AcceptEx` and `GetAcceptExSockaddrs` - WSAID_CONNECTEX = guid.GUID{ //revive:disable-line:var-naming ALL_CAPS - Data1: 0x25a207b9, - Data2: 0xddf3, - Data3: 0x4660, - Data4: [8]byte{0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e}, - } - - connectExFunc = runtimeFunc{id: WSAID_CONNECTEX} -) - -func ConnectEx( - fd windows.Handle, - rsa RawSockaddr, - sendBuf *byte, - sendDataLen uint32, - bytesSent *uint32, - overlapped *windows.Overlapped, -) error { - if err := connectExFunc.Load(); err != nil { - return fmt.Errorf("failed to load ConnectEx function pointer: %w", err) - } - ptr, n, err := rsa.Sockaddr() - if err != nil { - return err - } - return connectEx(fd, ptr, n, sendBuf, sendDataLen, bytesSent, overlapped) -} - -// BOOL LpfnConnectex( -// [in] SOCKET s, -// [in] const sockaddr *name, -// [in] int namelen, -// [in, optional] PVOID lpSendBuffer, -// [in] DWORD dwSendDataLength, -// [out] LPDWORD lpdwBytesSent, -// [in] LPOVERLAPPED lpOverlapped -// ) - -func connectEx( - s windows.Handle, - name unsafe.Pointer, - namelen int32, - sendBuf *byte, - sendDataLen uint32, - bytesSent *uint32, - overlapped *windows.Overlapped, -) (err error) { - r1, _, e1 := syscall.SyscallN(connectExFunc.addr, - uintptr(s), - uintptr(name), - uintptr(namelen), - uintptr(unsafe.Pointer(sendBuf)), - uintptr(sendDataLen), - uintptr(unsafe.Pointer(bytesSent)), - uintptr(unsafe.Pointer(overlapped)), - ) - - if r1 == 0 { - if e1 != 0 { - err = error(e1) - } else { - err = syscall.EINVAL - } - } - return err -} diff --git a/vendor/github.com/Microsoft/go-winio/internal/socket/zsyscall_windows.go b/vendor/github.com/Microsoft/go-winio/internal/socket/zsyscall_windows.go deleted file mode 100644 index e1504126aa6..00000000000 --- a/vendor/github.com/Microsoft/go-winio/internal/socket/zsyscall_windows.go +++ /dev/null @@ -1,69 +0,0 @@ -//go:build windows - -// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. - -package socket - -import ( - "syscall" - "unsafe" - - "golang.org/x/sys/windows" -) - -var _ unsafe.Pointer - -// Do the interface allocations only once for common -// Errno values. -const ( - errnoERROR_IO_PENDING = 997 -) - -var ( - errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) - errERROR_EINVAL error = syscall.EINVAL -) - -// errnoErr returns common boxed Errno values, to prevent -// allocations at runtime. -func errnoErr(e syscall.Errno) error { - switch e { - case 0: - return errERROR_EINVAL - case errnoERROR_IO_PENDING: - return errERROR_IO_PENDING - } - return e -} - -var ( - modws2_32 = windows.NewLazySystemDLL("ws2_32.dll") - - procbind = modws2_32.NewProc("bind") - procgetpeername = modws2_32.NewProc("getpeername") - procgetsockname = modws2_32.NewProc("getsockname") -) - -func bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) { - r1, _, e1 := syscall.SyscallN(procbind.Addr(), uintptr(s), uintptr(name), uintptr(namelen)) - if r1 == socketError { - err = errnoErr(e1) - } - return -} - -func getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) { - r1, _, e1 := syscall.SyscallN(procgetpeername.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen))) - if r1 == socketError { - err = errnoErr(e1) - } - return -} - -func getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) { - r1, _, e1 := syscall.SyscallN(procgetsockname.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen))) - if r1 == socketError { - err = errnoErr(e1) - } - return -} diff --git a/vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.go b/vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.go deleted file mode 100644 index 42ebc019fcb..00000000000 --- a/vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.go +++ /dev/null @@ -1,132 +0,0 @@ -package stringbuffer - -import ( - "sync" - "unicode/utf16" -) - -// TODO: worth exporting and using in mkwinsyscall? - -// Uint16BufferSize is the buffer size in the pool, chosen somewhat arbitrarily to accommodate -// large path strings: -// MAX_PATH (260) + size of volume GUID prefix (49) + null terminator = 310. -const MinWStringCap = 310 - -// use *[]uint16 since []uint16 creates an extra allocation where the slice header -// is copied to heap and then referenced via pointer in the interface header that sync.Pool -// stores. -var pathPool = sync.Pool{ // if go1.18+ adds Pool[T], use that to store []uint16 directly - New: func() interface{} { - b := make([]uint16, MinWStringCap) - return &b - }, -} - -func newBuffer() []uint16 { return *(pathPool.Get().(*[]uint16)) } - -// freeBuffer copies the slice header data, and puts a pointer to that in the pool. -// This avoids taking a pointer to the slice header in WString, which can be set to nil. -func freeBuffer(b []uint16) { pathPool.Put(&b) } - -// WString is a wide string buffer ([]uint16) meant for storing UTF-16 encoded strings -// for interacting with Win32 APIs. -// Sizes are specified as uint32 and not int. -// -// It is not thread safe. -type WString struct { - // type-def allows casting to []uint16 directly, use struct to prevent that and allow adding fields in the future. - - // raw buffer - b []uint16 -} - -// NewWString returns a [WString] allocated from a shared pool with an -// initial capacity of at least [MinWStringCap]. -// Since the buffer may have been previously used, its contents are not guaranteed to be empty. -// -// The buffer should be freed via [WString.Free] -func NewWString() *WString { - return &WString{ - b: newBuffer(), - } -} - -func (b *WString) Free() { - if b.empty() { - return - } - freeBuffer(b.b) - b.b = nil -} - -// ResizeTo grows the buffer to at least c and returns the new capacity, freeing the -// previous buffer back into pool. -func (b *WString) ResizeTo(c uint32) uint32 { - // already sufficient (or n is 0) - if c <= b.Cap() { - return b.Cap() - } - - if c <= MinWStringCap { - c = MinWStringCap - } - // allocate at-least double buffer size, as is done in [bytes.Buffer] and other places - if c <= 2*b.Cap() { - c = 2 * b.Cap() - } - - b2 := make([]uint16, c) - if !b.empty() { - copy(b2, b.b) - freeBuffer(b.b) - } - b.b = b2 - return c -} - -// Buffer returns the underlying []uint16 buffer. -func (b *WString) Buffer() []uint16 { - if b.empty() { - return nil - } - return b.b -} - -// Pointer returns a pointer to the first uint16 in the buffer. -// If the [WString.Free] has already been called, the pointer will be nil. -func (b *WString) Pointer() *uint16 { - if b.empty() { - return nil - } - return &b.b[0] -} - -// String returns the returns the UTF-8 encoding of the UTF-16 string in the buffer. -// -// It assumes that the data is null-terminated. -func (b *WString) String() string { - // Using [windows.UTF16ToString] would require importing "golang.org/x/sys/windows" - // and would make this code Windows-only, which makes no sense. - // So copy UTF16ToString code into here. - // If other windows-specific code is added, switch to [windows.UTF16ToString] - - s := b.b - for i, v := range s { - if v == 0 { - s = s[:i] - break - } - } - return string(utf16.Decode(s)) -} - -// Cap returns the underlying buffer capacity. -func (b *WString) Cap() uint32 { - if b.empty() { - return 0 - } - return b.cap() -} - -func (b *WString) cap() uint32 { return uint32(cap(b.b)) } -func (b *WString) empty() bool { return b == nil || b.cap() == 0 } diff --git a/vendor/github.com/Microsoft/go-winio/pipe.go b/vendor/github.com/Microsoft/go-winio/pipe.go deleted file mode 100644 index a2da6639d00..00000000000 --- a/vendor/github.com/Microsoft/go-winio/pipe.go +++ /dev/null @@ -1,586 +0,0 @@ -//go:build windows -// +build windows - -package winio - -import ( - "context" - "errors" - "fmt" - "io" - "net" - "os" - "runtime" - "time" - "unsafe" - - "golang.org/x/sys/windows" - - "github.com/Microsoft/go-winio/internal/fs" -) - -//sys connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) = ConnectNamedPipe -//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateNamedPipeW -//sys disconnectNamedPipe(pipe windows.Handle) (err error) = DisconnectNamedPipe -//sys getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo -//sys getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW -//sys ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) = ntdll.NtCreateNamedPipeFile -//sys rtlNtStatusToDosError(status ntStatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb -//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) = ntdll.RtlDosPathNameToNtPathName_U -//sys rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) = ntdll.RtlDefaultNpAcl - -type PipeConn interface { - net.Conn - Disconnect() error - Flush() error -} - -// type aliases for mkwinsyscall code -type ( - ntAccessMask = fs.AccessMask - ntFileShareMode = fs.FileShareMode - ntFileCreationDisposition = fs.NTFileCreationDisposition - ntFileOptions = fs.NTCreateOptions -) - -type ioStatusBlock struct { - Status, Information uintptr -} - -// typedef struct _OBJECT_ATTRIBUTES { -// ULONG Length; -// HANDLE RootDirectory; -// PUNICODE_STRING ObjectName; -// ULONG Attributes; -// PVOID SecurityDescriptor; -// PVOID SecurityQualityOfService; -// } OBJECT_ATTRIBUTES; -// -// https://learn.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_object_attributes -type objectAttributes struct { - Length uintptr - RootDirectory uintptr - ObjectName *unicodeString - Attributes uintptr - SecurityDescriptor *securityDescriptor - SecurityQoS uintptr -} - -type unicodeString struct { - Length uint16 - MaximumLength uint16 - Buffer uintptr -} - -// typedef struct _SECURITY_DESCRIPTOR { -// BYTE Revision; -// BYTE Sbz1; -// SECURITY_DESCRIPTOR_CONTROL Control; -// PSID Owner; -// PSID Group; -// PACL Sacl; -// PACL Dacl; -// } SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR; -// -// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_descriptor -type securityDescriptor struct { - Revision byte - Sbz1 byte - Control uint16 - Owner uintptr - Group uintptr - Sacl uintptr //revive:disable-line:var-naming SACL, not Sacl - Dacl uintptr //revive:disable-line:var-naming DACL, not Dacl -} - -type ntStatus int32 - -func (status ntStatus) Err() error { - if status >= 0 { - return nil - } - return rtlNtStatusToDosError(status) -} - -var ( - // ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed. - ErrPipeListenerClosed = net.ErrClosed - - errPipeWriteClosed = errors.New("pipe has been closed for write") -) - -type win32Pipe struct { - *win32File - path string -} - -var _ PipeConn = (*win32Pipe)(nil) - -type win32MessageBytePipe struct { - win32Pipe - writeClosed bool - readEOF bool -} - -type pipeAddress string - -func (f *win32Pipe) LocalAddr() net.Addr { - return pipeAddress(f.path) -} - -func (f *win32Pipe) RemoteAddr() net.Addr { - return pipeAddress(f.path) -} - -func (f *win32Pipe) SetDeadline(t time.Time) error { - if err := f.SetReadDeadline(t); err != nil { - return err - } - return f.SetWriteDeadline(t) -} - -func (f *win32Pipe) Disconnect() error { - return disconnectNamedPipe(f.win32File.handle) -} - -// CloseWrite closes the write side of a message pipe in byte mode. -func (f *win32MessageBytePipe) CloseWrite() error { - if f.writeClosed { - return errPipeWriteClosed - } - err := f.win32File.Flush() - if err != nil { - return err - } - _, err = f.win32File.Write(nil) - if err != nil { - return err - } - f.writeClosed = true - return nil -} - -// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since -// they are used to implement CloseWrite(). -func (f *win32MessageBytePipe) Write(b []byte) (int, error) { - if f.writeClosed { - return 0, errPipeWriteClosed - } - if len(b) == 0 { - return 0, nil - } - return f.win32File.Write(b) -} - -// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message -// mode pipe will return io.EOF, as will all subsequent reads. -func (f *win32MessageBytePipe) Read(b []byte) (int, error) { - if f.readEOF { - return 0, io.EOF - } - n, err := f.win32File.Read(b) - if err == io.EOF { //nolint:errorlint - // If this was the result of a zero-byte read, then - // it is possible that the read was due to a zero-size - // message. Since we are simulating CloseWrite with a - // zero-byte message, ensure that all future Read() calls - // also return EOF. - f.readEOF = true - } else if err == windows.ERROR_MORE_DATA { //nolint:errorlint // err is Errno - // ERROR_MORE_DATA indicates that the pipe's read mode is message mode - // and the message still has more bytes. Treat this as a success, since - // this package presents all named pipes as byte streams. - err = nil - } - return n, err -} - -func (pipeAddress) Network() string { - return "pipe" -} - -func (s pipeAddress) String() string { - return string(s) -} - -// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout. -func tryDialPipe(ctx context.Context, path *string, access fs.AccessMask, impLevel PipeImpLevel) (windows.Handle, error) { - for { - select { - case <-ctx.Done(): - return windows.Handle(0), ctx.Err() - default: - h, err := fs.CreateFile(*path, - access, - 0, // mode - nil, // security attributes - fs.OPEN_EXISTING, - fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.FileSQSFlag(impLevel), - 0, // template file handle - ) - if err == nil { - return h, nil - } - if err != windows.ERROR_PIPE_BUSY { //nolint:errorlint // err is Errno - return h, &os.PathError{Err: err, Op: "open", Path: *path} - } - // Wait 10 msec and try again. This is a rather simplistic - // view, as we always try each 10 milliseconds. - time.Sleep(10 * time.Millisecond) - } - } -} - -// DialPipe connects to a named pipe by path, timing out if the connection -// takes longer than the specified duration. If timeout is nil, then we use -// a default timeout of 2 seconds. (We do not use WaitNamedPipe.) -func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { - var absTimeout time.Time - if timeout != nil { - absTimeout = time.Now().Add(*timeout) - } else { - absTimeout = time.Now().Add(2 * time.Second) - } - ctx, cancel := context.WithDeadline(context.Background(), absTimeout) - defer cancel() - conn, err := DialPipeContext(ctx, path) - if errors.Is(err, context.DeadlineExceeded) { - return nil, ErrTimeout - } - return conn, err -} - -// DialPipeContext attempts to connect to a named pipe by `path` until `ctx` -// cancellation or timeout. -func DialPipeContext(ctx context.Context, path string) (net.Conn, error) { - return DialPipeAccess(ctx, path, uint32(fs.GENERIC_READ|fs.GENERIC_WRITE)) -} - -// PipeImpLevel is an enumeration of impersonation levels that may be set -// when calling DialPipeAccessImpersonation. -type PipeImpLevel uint32 - -const ( - PipeImpLevelAnonymous = PipeImpLevel(fs.SECURITY_ANONYMOUS) - PipeImpLevelIdentification = PipeImpLevel(fs.SECURITY_IDENTIFICATION) - PipeImpLevelImpersonation = PipeImpLevel(fs.SECURITY_IMPERSONATION) - PipeImpLevelDelegation = PipeImpLevel(fs.SECURITY_DELEGATION) -) - -// DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx` -// cancellation or timeout. -func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) { - return DialPipeAccessImpLevel(ctx, path, access, PipeImpLevelAnonymous) -} - -// DialPipeAccessImpLevel attempts to connect to a named pipe by `path` with -// `access` at `impLevel` until `ctx` cancellation or timeout. The other -// DialPipe* implementations use PipeImpLevelAnonymous. -func DialPipeAccessImpLevel(ctx context.Context, path string, access uint32, impLevel PipeImpLevel) (net.Conn, error) { - var err error - var h windows.Handle - h, err = tryDialPipe(ctx, &path, fs.AccessMask(access), impLevel) - if err != nil { - return nil, err - } - - var flags uint32 - err = getNamedPipeInfo(h, &flags, nil, nil, nil) - if err != nil { - return nil, err - } - - f, err := makeWin32File(h) - if err != nil { - windows.Close(h) - return nil, err - } - - // If the pipe is in message mode, return a message byte pipe, which - // supports CloseWrite(). - if flags&windows.PIPE_TYPE_MESSAGE != 0 { - return &win32MessageBytePipe{ - win32Pipe: win32Pipe{win32File: f, path: path}, - }, nil - } - return &win32Pipe{win32File: f, path: path}, nil -} - -type acceptResponse struct { - f *win32File - err error -} - -type win32PipeListener struct { - firstHandle windows.Handle - path string - config PipeConfig - acceptCh chan (chan acceptResponse) - closeCh chan int - doneCh chan int -} - -func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (windows.Handle, error) { - path16, err := windows.UTF16FromString(path) - if err != nil { - return 0, &os.PathError{Op: "open", Path: path, Err: err} - } - - var oa objectAttributes - oa.Length = unsafe.Sizeof(oa) - - var ntPath unicodeString - if err := rtlDosPathNameToNtPathName(&path16[0], - &ntPath, - 0, - 0, - ).Err(); err != nil { - return 0, &os.PathError{Op: "open", Path: path, Err: err} - } - defer windows.LocalFree(windows.Handle(ntPath.Buffer)) //nolint:errcheck - oa.ObjectName = &ntPath - oa.Attributes = windows.OBJ_CASE_INSENSITIVE - - // The security descriptor is only needed for the first pipe. - if first { - if sd != nil { - //todo: does `sdb` need to be allocated on the heap, or can go allocate it? - l := uint32(len(sd)) - sdb, err := windows.LocalAlloc(0, l) - if err != nil { - return 0, fmt.Errorf("LocalAlloc for security descriptor with of length %d: %w", l, err) - } - defer windows.LocalFree(windows.Handle(sdb)) //nolint:errcheck - copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd) - oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb)) - } else { - // Construct the default named pipe security descriptor. - var dacl uintptr - if err := rtlDefaultNpAcl(&dacl).Err(); err != nil { - return 0, fmt.Errorf("getting default named pipe ACL: %w", err) - } - defer windows.LocalFree(windows.Handle(dacl)) //nolint:errcheck - - sdb := &securityDescriptor{ - Revision: 1, - Control: windows.SE_DACL_PRESENT, - Dacl: dacl, - } - oa.SecurityDescriptor = sdb - } - } - - typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS) - if c.MessageMode { - typ |= windows.FILE_PIPE_MESSAGE_TYPE - } - - disposition := fs.FILE_OPEN - access := fs.GENERIC_READ | fs.GENERIC_WRITE | fs.SYNCHRONIZE - if first { - disposition = fs.FILE_CREATE - // By not asking for read or write access, the named pipe file system - // will put this pipe into an initially disconnected state, blocking - // client connections until the next call with first == false. - access = fs.SYNCHRONIZE - } - - timeout := int64(-50 * 10000) // 50ms - - var ( - h windows.Handle - iosb ioStatusBlock - ) - err = ntCreateNamedPipeFile(&h, - access, - &oa, - &iosb, - fs.FILE_SHARE_READ|fs.FILE_SHARE_WRITE, - disposition, - 0, - typ, - 0, - 0, - 0xffffffff, - uint32(c.InputBufferSize), - uint32(c.OutputBufferSize), - &timeout).Err() - if err != nil { - return 0, &os.PathError{Op: "open", Path: path, Err: err} - } - - runtime.KeepAlive(ntPath) - return h, nil -} - -func (l *win32PipeListener) makeServerPipe() (*win32File, error) { - h, err := makeServerPipeHandle(l.path, nil, &l.config, false) - if err != nil { - return nil, err - } - f, err := makeWin32File(h) - if err != nil { - windows.Close(h) - return nil, err - } - return f, nil -} - -func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) { - p, err := l.makeServerPipe() - if err != nil { - return nil, err - } - - // Wait for the client to connect. - ch := make(chan error) - go func(p *win32File) { - ch <- connectPipe(p) - }(p) - - select { - case err = <-ch: - if err != nil { - p.Close() - p = nil - } - case <-l.closeCh: - // Abort the connect request by closing the handle. - p.Close() - p = nil - err = <-ch - if err == nil || err == ErrFileClosed { //nolint:errorlint // err is Errno - err = ErrPipeListenerClosed - } - } - return p, err -} - -func (l *win32PipeListener) listenerRoutine() { - closed := false - for !closed { - select { - case <-l.closeCh: - closed = true - case responseCh := <-l.acceptCh: - var ( - p *win32File - err error - ) - for { - p, err = l.makeConnectedServerPipe() - // If the connection was immediately closed by the client, try - // again. - if err != windows.ERROR_NO_DATA { //nolint:errorlint // err is Errno - break - } - } - responseCh <- acceptResponse{p, err} - closed = err == ErrPipeListenerClosed //nolint:errorlint // err is Errno - } - } - windows.Close(l.firstHandle) - l.firstHandle = 0 - // Notify Close() and Accept() callers that the handle has been closed. - close(l.doneCh) -} - -// PipeConfig contain configuration for the pipe listener. -type PipeConfig struct { - // SecurityDescriptor contains a Windows security descriptor in SDDL format. - SecurityDescriptor string - - // MessageMode determines whether the pipe is in byte or message mode. In either - // case the pipe is read in byte mode by default. The only practical difference in - // this implementation is that CloseWrite() is only supported for message mode pipes; - // CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only - // transferred to the reader (and returned as io.EOF in this implementation) - // when the pipe is in message mode. - MessageMode bool - - // InputBufferSize specifies the size of the input buffer, in bytes. - InputBufferSize int32 - - // OutputBufferSize specifies the size of the output buffer, in bytes. - OutputBufferSize int32 -} - -// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe. -// The pipe must not already exist. -func ListenPipe(path string, c *PipeConfig) (net.Listener, error) { - var ( - sd []byte - err error - ) - if c == nil { - c = &PipeConfig{} - } - if c.SecurityDescriptor != "" { - sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor) - if err != nil { - return nil, err - } - } - h, err := makeServerPipeHandle(path, sd, c, true) - if err != nil { - return nil, err - } - l := &win32PipeListener{ - firstHandle: h, - path: path, - config: *c, - acceptCh: make(chan (chan acceptResponse)), - closeCh: make(chan int), - doneCh: make(chan int), - } - go l.listenerRoutine() - return l, nil -} - -func connectPipe(p *win32File) error { - c, err := p.prepareIO() - if err != nil { - return err - } - defer p.wg.Done() - - err = connectNamedPipe(p.handle, &c.o) - _, err = p.asyncIO(c, nil, 0, err) - if err != nil && err != windows.ERROR_PIPE_CONNECTED { //nolint:errorlint // err is Errno - return err - } - return nil -} - -func (l *win32PipeListener) Accept() (net.Conn, error) { - ch := make(chan acceptResponse) - select { - case l.acceptCh <- ch: - response := <-ch - err := response.err - if err != nil { - return nil, err - } - if l.config.MessageMode { - return &win32MessageBytePipe{ - win32Pipe: win32Pipe{win32File: response.f, path: l.path}, - }, nil - } - return &win32Pipe{win32File: response.f, path: l.path}, nil - case <-l.doneCh: - return nil, ErrPipeListenerClosed - } -} - -func (l *win32PipeListener) Close() error { - select { - case l.closeCh <- 1: - <-l.doneCh - case <-l.doneCh: - } - return nil -} - -func (l *win32PipeListener) Addr() net.Addr { - return pipeAddress(l.path) -} diff --git a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go deleted file mode 100644 index 48ce4e92436..00000000000 --- a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go +++ /dev/null @@ -1,232 +0,0 @@ -// Package guid provides a GUID type. The backing structure for a GUID is -// identical to that used by the golang.org/x/sys/windows GUID type. -// There are two main binary encodings used for a GUID, the big-endian encoding, -// and the Windows (mixed-endian) encoding. See here for details: -// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding -package guid - -import ( - "crypto/rand" - "crypto/sha1" //nolint:gosec // not used for secure application - "encoding" - "encoding/binary" - "fmt" - "strconv" -) - -//go:generate go run golang.org/x/tools/cmd/stringer -type=Variant -trimprefix=Variant -linecomment - -// Variant specifies which GUID variant (or "type") of the GUID. It determines -// how the entirety of the rest of the GUID is interpreted. -type Variant uint8 - -// The variants specified by RFC 4122 section 4.1.1. -const ( - // VariantUnknown specifies a GUID variant which does not conform to one of - // the variant encodings specified in RFC 4122. - VariantUnknown Variant = iota - VariantNCS - VariantRFC4122 // RFC 4122 - VariantMicrosoft - VariantFuture -) - -// Version specifies how the bits in the GUID were generated. For instance, a -// version 4 GUID is randomly generated, and a version 5 is generated from the -// hash of an input string. -type Version uint8 - -func (v Version) String() string { - return strconv.FormatUint(uint64(v), 10) -} - -var _ = (encoding.TextMarshaler)(GUID{}) -var _ = (encoding.TextUnmarshaler)(&GUID{}) - -// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122. -func NewV4() (GUID, error) { - var b [16]byte - if _, err := rand.Read(b[:]); err != nil { - return GUID{}, err - } - - g := FromArray(b) - g.setVersion(4) // Version 4 means randomly generated. - g.setVariant(VariantRFC4122) - - return g, nil -} - -// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing) -// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name, -// and the sample code treats it as a series of bytes, so we do the same here. -// -// Some implementations, such as those found on Windows, treat the name as a -// big-endian UTF16 stream of bytes. If that is desired, the string can be -// encoded as such before being passed to this function. -func NewV5(namespace GUID, name []byte) (GUID, error) { - b := sha1.New() //nolint:gosec // not used for secure application - namespaceBytes := namespace.ToArray() - b.Write(namespaceBytes[:]) - b.Write(name) - - a := [16]byte{} - copy(a[:], b.Sum(nil)) - - g := FromArray(a) - g.setVersion(5) // Version 5 means generated from a string. - g.setVariant(VariantRFC4122) - - return g, nil -} - -func fromArray(b [16]byte, order binary.ByteOrder) GUID { - var g GUID - g.Data1 = order.Uint32(b[0:4]) - g.Data2 = order.Uint16(b[4:6]) - g.Data3 = order.Uint16(b[6:8]) - copy(g.Data4[:], b[8:16]) - return g -} - -func (g GUID) toArray(order binary.ByteOrder) [16]byte { - b := [16]byte{} - order.PutUint32(b[0:4], g.Data1) - order.PutUint16(b[4:6], g.Data2) - order.PutUint16(b[6:8], g.Data3) - copy(b[8:16], g.Data4[:]) - return b -} - -// FromArray constructs a GUID from a big-endian encoding array of 16 bytes. -func FromArray(b [16]byte) GUID { - return fromArray(b, binary.BigEndian) -} - -// ToArray returns an array of 16 bytes representing the GUID in big-endian -// encoding. -func (g GUID) ToArray() [16]byte { - return g.toArray(binary.BigEndian) -} - -// FromWindowsArray constructs a GUID from a Windows encoding array of bytes. -func FromWindowsArray(b [16]byte) GUID { - return fromArray(b, binary.LittleEndian) -} - -// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows -// encoding. -func (g GUID) ToWindowsArray() [16]byte { - return g.toArray(binary.LittleEndian) -} - -func (g GUID) String() string { - return fmt.Sprintf( - "%08x-%04x-%04x-%04x-%012x", - g.Data1, - g.Data2, - g.Data3, - g.Data4[:2], - g.Data4[2:]) -} - -// FromString parses a string containing a GUID and returns the GUID. The only -// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` -// format. -func FromString(s string) (GUID, error) { - if len(s) != 36 { - return GUID{}, fmt.Errorf("invalid GUID %q", s) - } - if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { - return GUID{}, fmt.Errorf("invalid GUID %q", s) - } - - var g GUID - - data1, err := strconv.ParseUint(s[0:8], 16, 32) - if err != nil { - return GUID{}, fmt.Errorf("invalid GUID %q", s) - } - g.Data1 = uint32(data1) - - data2, err := strconv.ParseUint(s[9:13], 16, 16) - if err != nil { - return GUID{}, fmt.Errorf("invalid GUID %q", s) - } - g.Data2 = uint16(data2) - - data3, err := strconv.ParseUint(s[14:18], 16, 16) - if err != nil { - return GUID{}, fmt.Errorf("invalid GUID %q", s) - } - g.Data3 = uint16(data3) - - for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} { - v, err := strconv.ParseUint(s[x:x+2], 16, 8) - if err != nil { - return GUID{}, fmt.Errorf("invalid GUID %q", s) - } - g.Data4[i] = uint8(v) - } - - return g, nil -} - -func (g *GUID) setVariant(v Variant) { - d := g.Data4[0] - switch v { - case VariantNCS: - d = (d & 0x7f) - case VariantRFC4122: - d = (d & 0x3f) | 0x80 - case VariantMicrosoft: - d = (d & 0x1f) | 0xc0 - case VariantFuture: - d = (d & 0x0f) | 0xe0 - case VariantUnknown: - fallthrough - default: - panic(fmt.Sprintf("invalid variant: %d", v)) - } - g.Data4[0] = d -} - -// Variant returns the GUID variant, as defined in RFC 4122. -func (g GUID) Variant() Variant { - b := g.Data4[0] - if b&0x80 == 0 { - return VariantNCS - } else if b&0xc0 == 0x80 { - return VariantRFC4122 - } else if b&0xe0 == 0xc0 { - return VariantMicrosoft - } else if b&0xe0 == 0xe0 { - return VariantFuture - } - return VariantUnknown -} - -func (g *GUID) setVersion(v Version) { - g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12) -} - -// Version returns the GUID version, as defined in RFC 4122. -func (g GUID) Version() Version { - return Version((g.Data3 & 0xF000) >> 12) -} - -// MarshalText returns the textual representation of the GUID. -func (g GUID) MarshalText() ([]byte, error) { - return []byte(g.String()), nil -} - -// UnmarshalText takes the textual representation of a GUID, and unmarhals it -// into this GUID. -func (g *GUID) UnmarshalText(text []byte) error { - g2, err := FromString(string(text)) - if err != nil { - return err - } - *g = g2 - return nil -} diff --git a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go deleted file mode 100644 index 805bd354842..00000000000 --- a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !windows -// +build !windows - -package guid - -// GUID represents a GUID/UUID. It has the same structure as -// golang.org/x/sys/windows.GUID so that it can be used with functions expecting -// that type. It is defined as its own type as that is only available to builds -// targeted at `windows`. The representation matches that used by native Windows -// code. -type GUID struct { - Data1 uint32 - Data2 uint16 - Data3 uint16 - Data4 [8]byte -} diff --git a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go deleted file mode 100644 index 27e45ee5ccf..00000000000 --- a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build windows -// +build windows - -package guid - -import "golang.org/x/sys/windows" - -// GUID represents a GUID/UUID. It has the same structure as -// golang.org/x/sys/windows.GUID so that it can be used with functions expecting -// that type. It is defined as its own type so that stringification and -// marshaling can be supported. The representation matches that used by native -// Windows code. -type GUID windows.GUID diff --git a/vendor/github.com/Microsoft/go-winio/pkg/guid/variant_string.go b/vendor/github.com/Microsoft/go-winio/pkg/guid/variant_string.go deleted file mode 100644 index 4076d3132fd..00000000000 --- a/vendor/github.com/Microsoft/go-winio/pkg/guid/variant_string.go +++ /dev/null @@ -1,27 +0,0 @@ -// Code generated by "stringer -type=Variant -trimprefix=Variant -linecomment"; DO NOT EDIT. - -package guid - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[VariantUnknown-0] - _ = x[VariantNCS-1] - _ = x[VariantRFC4122-2] - _ = x[VariantMicrosoft-3] - _ = x[VariantFuture-4] -} - -const _Variant_name = "UnknownNCSRFC 4122MicrosoftFuture" - -var _Variant_index = [...]uint8{0, 7, 10, 18, 27, 33} - -func (i Variant) String() string { - if i >= Variant(len(_Variant_index)-1) { - return "Variant(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _Variant_name[_Variant_index[i]:_Variant_index[i+1]] -} diff --git a/vendor/github.com/Microsoft/go-winio/privilege.go b/vendor/github.com/Microsoft/go-winio/privilege.go deleted file mode 100644 index d9b90b6e861..00000000000 --- a/vendor/github.com/Microsoft/go-winio/privilege.go +++ /dev/null @@ -1,196 +0,0 @@ -//go:build windows -// +build windows - -package winio - -import ( - "bytes" - "encoding/binary" - "fmt" - "runtime" - "sync" - "unicode/utf16" - - "golang.org/x/sys/windows" -) - -//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges -//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf -//sys revertToSelf() (err error) = advapi32.RevertToSelf -//sys openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken -//sys getCurrentThread() (h windows.Handle) = GetCurrentThread -//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW -//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW -//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW - -const ( - //revive:disable-next-line:var-naming ALL_CAPS - SE_PRIVILEGE_ENABLED = windows.SE_PRIVILEGE_ENABLED - - //revive:disable-next-line:var-naming ALL_CAPS - ERROR_NOT_ALL_ASSIGNED windows.Errno = windows.ERROR_NOT_ALL_ASSIGNED - - SeBackupPrivilege = "SeBackupPrivilege" - SeRestorePrivilege = "SeRestorePrivilege" - SeSecurityPrivilege = "SeSecurityPrivilege" -) - -var ( - privNames = make(map[string]uint64) - privNameMutex sync.Mutex -) - -// PrivilegeError represents an error enabling privileges. -type PrivilegeError struct { - privileges []uint64 -} - -func (e *PrivilegeError) Error() string { - s := "Could not enable privilege " - if len(e.privileges) > 1 { - s = "Could not enable privileges " - } - for i, p := range e.privileges { - if i != 0 { - s += ", " - } - s += `"` - s += getPrivilegeName(p) - s += `"` - } - return s -} - -// RunWithPrivilege enables a single privilege for a function call. -func RunWithPrivilege(name string, fn func() error) error { - return RunWithPrivileges([]string{name}, fn) -} - -// RunWithPrivileges enables privileges for a function call. -func RunWithPrivileges(names []string, fn func() error) error { - privileges, err := mapPrivileges(names) - if err != nil { - return err - } - runtime.LockOSThread() - defer runtime.UnlockOSThread() - token, err := newThreadToken() - if err != nil { - return err - } - defer releaseThreadToken(token) - err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED) - if err != nil { - return err - } - return fn() -} - -func mapPrivileges(names []string) ([]uint64, error) { - privileges := make([]uint64, 0, len(names)) - privNameMutex.Lock() - defer privNameMutex.Unlock() - for _, name := range names { - p, ok := privNames[name] - if !ok { - err := lookupPrivilegeValue("", name, &p) - if err != nil { - return nil, err - } - privNames[name] = p - } - privileges = append(privileges, p) - } - return privileges, nil -} - -// EnableProcessPrivileges enables privileges globally for the process. -func EnableProcessPrivileges(names []string) error { - return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED) -} - -// DisableProcessPrivileges disables privileges globally for the process. -func DisableProcessPrivileges(names []string) error { - return enableDisableProcessPrivilege(names, 0) -} - -func enableDisableProcessPrivilege(names []string, action uint32) error { - privileges, err := mapPrivileges(names) - if err != nil { - return err - } - - p := windows.CurrentProcess() - var token windows.Token - err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token) - if err != nil { - return err - } - - defer token.Close() - return adjustPrivileges(token, privileges, action) -} - -func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error { - var b bytes.Buffer - _ = binary.Write(&b, binary.LittleEndian, uint32(len(privileges))) - for _, p := range privileges { - _ = binary.Write(&b, binary.LittleEndian, p) - _ = binary.Write(&b, binary.LittleEndian, action) - } - prevState := make([]byte, b.Len()) - reqSize := uint32(0) - success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize) - if !success { - return err - } - if err == ERROR_NOT_ALL_ASSIGNED { //nolint:errorlint // err is Errno - return &PrivilegeError{privileges} - } - return nil -} - -func getPrivilegeName(luid uint64) string { - var nameBuffer [256]uint16 - bufSize := uint32(len(nameBuffer)) - err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize) - if err != nil { - return fmt.Sprintf("", luid) - } - - var displayNameBuffer [256]uint16 - displayBufSize := uint32(len(displayNameBuffer)) - var langID uint32 - err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID) - if err != nil { - return fmt.Sprintf("", string(utf16.Decode(nameBuffer[:bufSize]))) - } - - return string(utf16.Decode(displayNameBuffer[:displayBufSize])) -} - -func newThreadToken() (windows.Token, error) { - err := impersonateSelf(windows.SecurityImpersonation) - if err != nil { - return 0, err - } - - var token windows.Token - err = openThreadToken(getCurrentThread(), windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, false, &token) - if err != nil { - rerr := revertToSelf() - if rerr != nil { - panic(rerr) - } - return 0, err - } - return token, nil -} - -func releaseThreadToken(h windows.Token) { - err := revertToSelf() - if err != nil { - panic(err) - } - h.Close() -} diff --git a/vendor/github.com/Microsoft/go-winio/reparse.go b/vendor/github.com/Microsoft/go-winio/reparse.go deleted file mode 100644 index 67d1a104a63..00000000000 --- a/vendor/github.com/Microsoft/go-winio/reparse.go +++ /dev/null @@ -1,131 +0,0 @@ -//go:build windows -// +build windows - -package winio - -import ( - "bytes" - "encoding/binary" - "fmt" - "strings" - "unicode/utf16" - "unsafe" -) - -const ( - reparseTagMountPoint = 0xA0000003 - reparseTagSymlink = 0xA000000C -) - -type reparseDataBuffer struct { - ReparseTag uint32 - ReparseDataLength uint16 - Reserved uint16 - SubstituteNameOffset uint16 - SubstituteNameLength uint16 - PrintNameOffset uint16 - PrintNameLength uint16 -} - -// ReparsePoint describes a Win32 symlink or mount point. -type ReparsePoint struct { - Target string - IsMountPoint bool -} - -// UnsupportedReparsePointError is returned when trying to decode a non-symlink or -// mount point reparse point. -type UnsupportedReparsePointError struct { - Tag uint32 -} - -func (e *UnsupportedReparsePointError) Error() string { - return fmt.Sprintf("unsupported reparse point %x", e.Tag) -} - -// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink -// or a mount point. -func DecodeReparsePoint(b []byte) (*ReparsePoint, error) { - tag := binary.LittleEndian.Uint32(b[0:4]) - return DecodeReparsePointData(tag, b[8:]) -} - -func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) { - isMountPoint := false - switch tag { - case reparseTagMountPoint: - isMountPoint = true - case reparseTagSymlink: - default: - return nil, &UnsupportedReparsePointError{tag} - } - nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6]) - if !isMountPoint { - nameOffset += 4 - } - nameLength := binary.LittleEndian.Uint16(b[6:8]) - name := make([]uint16, nameLength/2) - err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name) - if err != nil { - return nil, err - } - return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil -} - -func isDriveLetter(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') -} - -// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or -// mount point. -func EncodeReparsePoint(rp *ReparsePoint) []byte { - // Generate an NT path and determine if this is a relative path. - var ntTarget string - relative := false - if strings.HasPrefix(rp.Target, `\\?\`) { - ntTarget = `\??\` + rp.Target[4:] - } else if strings.HasPrefix(rp.Target, `\\`) { - ntTarget = `\??\UNC\` + rp.Target[2:] - } else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' { - ntTarget = `\??\` + rp.Target - } else { - ntTarget = rp.Target - relative = true - } - - // The paths must be NUL-terminated even though they are counted strings. - target16 := utf16.Encode([]rune(rp.Target + "\x00")) - ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00")) - - size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8 - size += len(ntTarget16)*2 + len(target16)*2 - - tag := uint32(reparseTagMountPoint) - if !rp.IsMountPoint { - tag = reparseTagSymlink - size += 4 // Add room for symlink flags - } - - data := reparseDataBuffer{ - ReparseTag: tag, - ReparseDataLength: uint16(size), - SubstituteNameOffset: 0, - SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2), - PrintNameOffset: uint16(len(ntTarget16) * 2), - PrintNameLength: uint16((len(target16) - 1) * 2), - } - - var b bytes.Buffer - _ = binary.Write(&b, binary.LittleEndian, &data) - if !rp.IsMountPoint { - flags := uint32(0) - if relative { - flags |= 1 - } - _ = binary.Write(&b, binary.LittleEndian, flags) - } - - _ = binary.Write(&b, binary.LittleEndian, ntTarget16) - _ = binary.Write(&b, binary.LittleEndian, target16) - return b.Bytes() -} diff --git a/vendor/github.com/Microsoft/go-winio/sd.go b/vendor/github.com/Microsoft/go-winio/sd.go deleted file mode 100644 index c3685e98e14..00000000000 --- a/vendor/github.com/Microsoft/go-winio/sd.go +++ /dev/null @@ -1,133 +0,0 @@ -//go:build windows -// +build windows - -package winio - -import ( - "errors" - "fmt" - "unsafe" - - "golang.org/x/sys/windows" -) - -//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW -//sys lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountSidW -//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW -//sys convertStringSidToSid(str *uint16, sid **byte) (err error) = advapi32.ConvertStringSidToSidW - -type AccountLookupError struct { - Name string - Err error -} - -func (e *AccountLookupError) Error() string { - if e.Name == "" { - return "lookup account: empty account name specified" - } - var s string - switch { - case errors.Is(e.Err, windows.ERROR_INVALID_SID): - s = "the security ID structure is invalid" - case errors.Is(e.Err, windows.ERROR_NONE_MAPPED): - s = "not found" - default: - s = e.Err.Error() - } - return "lookup account " + e.Name + ": " + s -} - -func (e *AccountLookupError) Unwrap() error { return e.Err } - -type SddlConversionError struct { - Sddl string - Err error -} - -func (e *SddlConversionError) Error() string { - return "convert " + e.Sddl + ": " + e.Err.Error() -} - -func (e *SddlConversionError) Unwrap() error { return e.Err } - -// LookupSidByName looks up the SID of an account by name -// -//revive:disable-next-line:var-naming SID, not Sid -func LookupSidByName(name string) (sid string, err error) { - if name == "" { - return "", &AccountLookupError{name, windows.ERROR_NONE_MAPPED} - } - - var sidSize, sidNameUse, refDomainSize uint32 - err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse) - if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno - return "", &AccountLookupError{name, err} - } - sidBuffer := make([]byte, sidSize) - refDomainBuffer := make([]uint16, refDomainSize) - err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse) - if err != nil { - return "", &AccountLookupError{name, err} - } - var strBuffer *uint16 - err = convertSidToStringSid(&sidBuffer[0], &strBuffer) - if err != nil { - return "", &AccountLookupError{name, err} - } - sid = windows.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:]) - _, _ = windows.LocalFree(windows.Handle(unsafe.Pointer(strBuffer))) - return sid, nil -} - -// LookupNameBySid looks up the name of an account by SID -// -//revive:disable-next-line:var-naming SID, not Sid -func LookupNameBySid(sid string) (name string, err error) { - if sid == "" { - return "", &AccountLookupError{sid, windows.ERROR_NONE_MAPPED} - } - - sidBuffer, err := windows.UTF16PtrFromString(sid) - if err != nil { - return "", &AccountLookupError{sid, err} - } - - var sidPtr *byte - if err = convertStringSidToSid(sidBuffer, &sidPtr); err != nil { - return "", &AccountLookupError{sid, err} - } - defer windows.LocalFree(windows.Handle(unsafe.Pointer(sidPtr))) //nolint:errcheck - - var nameSize, refDomainSize, sidNameUse uint32 - err = lookupAccountSid(nil, sidPtr, nil, &nameSize, nil, &refDomainSize, &sidNameUse) - if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno - return "", &AccountLookupError{sid, err} - } - - nameBuffer := make([]uint16, nameSize) - refDomainBuffer := make([]uint16, refDomainSize) - err = lookupAccountSid(nil, sidPtr, &nameBuffer[0], &nameSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse) - if err != nil { - return "", &AccountLookupError{sid, err} - } - - name = windows.UTF16ToString(nameBuffer) - return name, nil -} - -func SddlToSecurityDescriptor(sddl string) ([]byte, error) { - sd, err := windows.SecurityDescriptorFromString(sddl) - if err != nil { - return nil, &SddlConversionError{Sddl: sddl, Err: err} - } - b := unsafe.Slice((*byte)(unsafe.Pointer(sd)), sd.Length()) - return b, nil -} - -func SecurityDescriptorToSddl(sd []byte) (string, error) { - if l := int(unsafe.Sizeof(windows.SECURITY_DESCRIPTOR{})); len(sd) < l { - return "", fmt.Errorf("SecurityDescriptor (%d) smaller than expected (%d): %w", len(sd), l, windows.ERROR_INCORRECT_SIZE) - } - s := (*windows.SECURITY_DESCRIPTOR)(unsafe.Pointer(&sd[0])) - return s.String(), nil -} diff --git a/vendor/github.com/Microsoft/go-winio/syscall.go b/vendor/github.com/Microsoft/go-winio/syscall.go deleted file mode 100644 index a6ca111b39c..00000000000 --- a/vendor/github.com/Microsoft/go-winio/syscall.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build windows - -package winio - -//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./*.go diff --git a/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go b/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go deleted file mode 100644 index 89b66eda8cc..00000000000 --- a/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go +++ /dev/null @@ -1,378 +0,0 @@ -//go:build windows - -// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT. - -package winio - -import ( - "syscall" - "unsafe" - - "golang.org/x/sys/windows" -) - -var _ unsafe.Pointer - -// Do the interface allocations only once for common -// Errno values. -const ( - errnoERROR_IO_PENDING = 997 -) - -var ( - errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) - errERROR_EINVAL error = syscall.EINVAL -) - -// errnoErr returns common boxed Errno values, to prevent -// allocations at runtime. -func errnoErr(e syscall.Errno) error { - switch e { - case 0: - return errERROR_EINVAL - case errnoERROR_IO_PENDING: - return errERROR_IO_PENDING - } - return e -} - -var ( - modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") - modkernel32 = windows.NewLazySystemDLL("kernel32.dll") - modntdll = windows.NewLazySystemDLL("ntdll.dll") - modws2_32 = windows.NewLazySystemDLL("ws2_32.dll") - - procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges") - procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW") - procConvertStringSidToSidW = modadvapi32.NewProc("ConvertStringSidToSidW") - procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf") - procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW") - procLookupAccountSidW = modadvapi32.NewProc("LookupAccountSidW") - procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW") - procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW") - procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW") - procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken") - procRevertToSelf = modadvapi32.NewProc("RevertToSelf") - procBackupRead = modkernel32.NewProc("BackupRead") - procBackupWrite = modkernel32.NewProc("BackupWrite") - procCancelIoEx = modkernel32.NewProc("CancelIoEx") - procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") - procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") - procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") - procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe") - procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") - procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW") - procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo") - procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus") - procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes") - procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile") - procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl") - procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U") - procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb") - procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult") -) - -func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) { - var _p0 uint32 - if releaseAll { - _p0 = 1 - } - r0, _, e1 := syscall.SyscallN(procAdjustTokenPrivileges.Addr(), uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize))) - success = r0 != 0 - if true { - err = errnoErr(e1) - } - return -} - -func convertSidToStringSid(sid *byte, str **uint16) (err error) { - r1, _, e1 := syscall.SyscallN(procConvertSidToStringSidW.Addr(), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func convertStringSidToSid(str *uint16, sid **byte) (err error) { - r1, _, e1 := syscall.SyscallN(procConvertStringSidToSidW.Addr(), uintptr(unsafe.Pointer(str)), uintptr(unsafe.Pointer(sid))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func impersonateSelf(level uint32) (err error) { - r1, _, e1 := syscall.SyscallN(procImpersonateSelf.Addr(), uintptr(level)) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { - var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(accountName) - if err != nil { - return - } - return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse) -} - -func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { - r1, _, e1 := syscall.SyscallN(procLookupAccountNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { - r1, _, e1 := syscall.SyscallN(procLookupAccountSidW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) { - var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(systemName) - if err != nil { - return - } - return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId) -} - -func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) { - r1, _, e1 := syscall.SyscallN(procLookupPrivilegeDisplayNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) { - var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(systemName) - if err != nil { - return - } - return _lookupPrivilegeName(_p0, luid, buffer, size) -} - -func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) { - r1, _, e1 := syscall.SyscallN(procLookupPrivilegeNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) { - var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(systemName) - if err != nil { - return - } - var _p1 *uint16 - _p1, err = syscall.UTF16PtrFromString(name) - if err != nil { - return - } - return _lookupPrivilegeValue(_p0, _p1, luid) -} - -func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) { - r1, _, e1 := syscall.SyscallN(procLookupPrivilegeValueW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) { - var _p0 uint32 - if openAsSelf { - _p0 = 1 - } - r1, _, e1 := syscall.SyscallN(procOpenThreadToken.Addr(), uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func revertToSelf() (err error) { - r1, _, e1 := syscall.SyscallN(procRevertToSelf.Addr()) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) { - var _p0 *byte - if len(b) > 0 { - _p0 = &b[0] - } - var _p1 uint32 - if abort { - _p1 = 1 - } - var _p2 uint32 - if processSecurity { - _p2 = 1 - } - r1, _, e1 := syscall.SyscallN(procBackupRead.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) { - var _p0 *byte - if len(b) > 0 { - _p0 = &b[0] - } - var _p1 uint32 - if abort { - _p1 = 1 - } - var _p2 uint32 - if processSecurity { - _p2 = 1 - } - r1, _, e1 := syscall.SyscallN(procBackupWrite.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) { - r1, _, e1 := syscall.SyscallN(procCancelIoEx.Addr(), uintptr(file), uintptr(unsafe.Pointer(o))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) { - r1, _, e1 := syscall.SyscallN(procConnectNamedPipe.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(o))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) { - r0, _, e1 := syscall.SyscallN(procCreateIoCompletionPort.Addr(), uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount)) - newport = windows.Handle(r0) - if newport == 0 { - err = errnoErr(e1) - } - return -} - -func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) { - var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(name) - if err != nil { - return - } - return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa) -} - -func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) { - r0, _, e1 := syscall.SyscallN(procCreateNamedPipeW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa))) - handle = windows.Handle(r0) - if handle == windows.InvalidHandle { - err = errnoErr(e1) - } - return -} - -func disconnectNamedPipe(pipe windows.Handle) (err error) { - r1, _, e1 := syscall.SyscallN(procDisconnectNamedPipe.Addr(), uintptr(pipe)) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func getCurrentThread() (h windows.Handle) { - r0, _, _ := syscall.SyscallN(procGetCurrentThread.Addr()) - h = windows.Handle(r0) - return -} - -func getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) { - r1, _, e1 := syscall.SyscallN(procGetNamedPipeHandleStateW.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize)) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) { - r1, _, e1 := syscall.SyscallN(procGetNamedPipeInfo.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) { - r1, _, e1 := syscall.SyscallN(procGetQueuedCompletionStatus.Addr(), uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout)) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) { - r1, _, e1 := syscall.SyscallN(procSetFileCompletionNotificationModes.Addr(), uintptr(h), uintptr(flags)) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - -func ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) { - r0, _, _ := syscall.SyscallN(procNtCreateNamedPipeFile.Addr(), uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout))) - status = ntStatus(r0) - return -} - -func rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) { - r0, _, _ := syscall.SyscallN(procRtlDefaultNpAcl.Addr(), uintptr(unsafe.Pointer(dacl))) - status = ntStatus(r0) - return -} - -func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) { - r0, _, _ := syscall.SyscallN(procRtlDosPathNameToNtPathName_U.Addr(), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved)) - status = ntStatus(r0) - return -} - -func rtlNtStatusToDosError(status ntStatus) (winerr error) { - r0, _, _ := syscall.SyscallN(procRtlNtStatusToDosErrorNoTeb.Addr(), uintptr(status)) - if r0 != 0 { - winerr = syscall.Errno(r0) - } - return -} - -func wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) { - var _p0 uint32 - if wait { - _p0 = 1 - } - r1, _, e1 := syscall.SyscallN(procWSAGetOverlappedResult.Addr(), uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags))) - if r1 == 0 { - err = errnoErr(e1) - } - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/AUTHORS b/vendor/github.com/ProtonMail/go-crypto/AUTHORS deleted file mode 100644 index 2b00ddba0df..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/AUTHORS +++ /dev/null @@ -1,3 +0,0 @@ -# This source code refers to The Go Authors for copyright purposes. -# The master list of authors is in the main Go distribution, -# visible at https://tip.golang.org/AUTHORS. diff --git a/vendor/github.com/ProtonMail/go-crypto/CONTRIBUTORS b/vendor/github.com/ProtonMail/go-crypto/CONTRIBUTORS deleted file mode 100644 index 1fbd3e976fa..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/CONTRIBUTORS +++ /dev/null @@ -1,3 +0,0 @@ -# This source code was written by the Go contributors. -# The master list of contributors is in the main Go distribution, -# visible at https://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/github.com/ProtonMail/go-crypto/LICENSE b/vendor/github.com/ProtonMail/go-crypto/LICENSE deleted file mode 100644 index 6a66aea5eaf..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/ProtonMail/go-crypto/PATENTS b/vendor/github.com/ProtonMail/go-crypto/PATENTS deleted file mode 100644 index 733099041f8..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/PATENTS +++ /dev/null @@ -1,22 +0,0 @@ -Additional IP Rights Grant (Patents) - -"This implementation" means the copyrightable works distributed by -Google as part of the Go project. - -Google hereby grants to You a perpetual, worldwide, non-exclusive, -no-charge, royalty-free, irrevocable (except as stated in this section) -patent license to make, have made, use, offer to sell, sell, import, -transfer and otherwise run, modify and propagate the contents of this -implementation of Go, where such license applies only to those patent -claims, both currently owned or controlled by Google and acquired in -the future, licensable by Google that are necessarily infringed by this -implementation of Go. This grant does not include claims that would be -infringed only as a consequence of further modification of this -implementation. If you or your agent or exclusive licensee institute or -order or agree to the institution of patent litigation against any -entity (including a cross-claim or counterclaim in a lawsuit) alleging -that this implementation of Go or any code incorporated within this -implementation of Go constitutes direct or contributory patent -infringement, or inducement of patent infringement, then any patent -rights granted to you under this License for this implementation of Go -shall terminate as of the date such litigation is filed. diff --git a/vendor/github.com/ProtonMail/go-crypto/bitcurves/bitcurve.go b/vendor/github.com/ProtonMail/go-crypto/bitcurves/bitcurve.go deleted file mode 100644 index c85e6befeca..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/bitcurves/bitcurve.go +++ /dev/null @@ -1,381 +0,0 @@ -package bitcurves - -// Copyright 2010 The Go Authors. All rights reserved. -// Copyright 2011 ThePiachu. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package bitelliptic implements several Koblitz elliptic curves over prime -// fields. - -// This package operates, internally, on Jacobian coordinates. For a given -// (x, y) position on the curve, the Jacobian coordinates are (x1, y1, z1) -// where x = x1/z1² and y = y1/z1³. The greatest speedups come when the whole -// calculation can be performed within the transform (as in ScalarMult and -// ScalarBaseMult). But even for Add and Double, it's faster to apply and -// reverse the transform than to operate in affine coordinates. - -import ( - "crypto/elliptic" - "io" - "math/big" - "sync" -) - -// A BitCurve represents a Koblitz Curve with a=0. -// See http://www.hyperelliptic.org/EFD/g1p/auto-shortw.html -type BitCurve struct { - Name string - P *big.Int // the order of the underlying field - N *big.Int // the order of the base point - B *big.Int // the constant of the BitCurve equation - Gx, Gy *big.Int // (x,y) of the base point - BitSize int // the size of the underlying field -} - -// Params returns the parameters of the given BitCurve (see BitCurve struct) -func (bitCurve *BitCurve) Params() (cp *elliptic.CurveParams) { - cp = new(elliptic.CurveParams) - cp.Name = bitCurve.Name - cp.P = bitCurve.P - cp.N = bitCurve.N - cp.Gx = bitCurve.Gx - cp.Gy = bitCurve.Gy - cp.BitSize = bitCurve.BitSize - return cp -} - -// IsOnCurve returns true if the given (x,y) lies on the BitCurve. -func (bitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool { - // y² = x³ + b - y2 := new(big.Int).Mul(y, y) //y² - y2.Mod(y2, bitCurve.P) //y²%P - - x3 := new(big.Int).Mul(x, x) //x² - x3.Mul(x3, x) //x³ - - x3.Add(x3, bitCurve.B) //x³+B - x3.Mod(x3, bitCurve.P) //(x³+B)%P - - return x3.Cmp(y2) == 0 -} - -// affineFromJacobian reverses the Jacobian transform. See the comment at the -// top of the file. -func (bitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) { - if z.Cmp(big.NewInt(0)) == 0 { - panic("bitcurve: Can't convert to affine with Jacobian Z = 0") - } - // x = YZ^2 mod P - zinv := new(big.Int).ModInverse(z, bitCurve.P) - zinvsq := new(big.Int).Mul(zinv, zinv) - - xOut = new(big.Int).Mul(x, zinvsq) - xOut.Mod(xOut, bitCurve.P) - // y = YZ^3 mod P - zinvsq.Mul(zinvsq, zinv) - yOut = new(big.Int).Mul(y, zinvsq) - yOut.Mod(yOut, bitCurve.P) - return xOut, yOut -} - -// Add returns the sum of (x1,y1) and (x2,y2) -func (bitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) { - z := new(big.Int).SetInt64(1) - x, y, z := bitCurve.addJacobian(x1, y1, z, x2, y2, z) - return bitCurve.affineFromJacobian(x, y, z) -} - -// addJacobian takes two points in Jacobian coordinates, (x1, y1, z1) and -// (x2, y2, z2) and returns their sum, also in Jacobian form. -func (bitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int, *big.Int, *big.Int) { - // See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl - z1z1 := new(big.Int).Mul(z1, z1) - z1z1.Mod(z1z1, bitCurve.P) - z2z2 := new(big.Int).Mul(z2, z2) - z2z2.Mod(z2z2, bitCurve.P) - - u1 := new(big.Int).Mul(x1, z2z2) - u1.Mod(u1, bitCurve.P) - u2 := new(big.Int).Mul(x2, z1z1) - u2.Mod(u2, bitCurve.P) - h := new(big.Int).Sub(u2, u1) - if h.Sign() == -1 { - h.Add(h, bitCurve.P) - } - i := new(big.Int).Lsh(h, 1) - i.Mul(i, i) - j := new(big.Int).Mul(h, i) - - s1 := new(big.Int).Mul(y1, z2) - s1.Mul(s1, z2z2) - s1.Mod(s1, bitCurve.P) - s2 := new(big.Int).Mul(y2, z1) - s2.Mul(s2, z1z1) - s2.Mod(s2, bitCurve.P) - r := new(big.Int).Sub(s2, s1) - if r.Sign() == -1 { - r.Add(r, bitCurve.P) - } - r.Lsh(r, 1) - v := new(big.Int).Mul(u1, i) - - x3 := new(big.Int).Set(r) - x3.Mul(x3, x3) - x3.Sub(x3, j) - x3.Sub(x3, v) - x3.Sub(x3, v) - x3.Mod(x3, bitCurve.P) - - y3 := new(big.Int).Set(r) - v.Sub(v, x3) - y3.Mul(y3, v) - s1.Mul(s1, j) - s1.Lsh(s1, 1) - y3.Sub(y3, s1) - y3.Mod(y3, bitCurve.P) - - z3 := new(big.Int).Add(z1, z2) - z3.Mul(z3, z3) - z3.Sub(z3, z1z1) - if z3.Sign() == -1 { - z3.Add(z3, bitCurve.P) - } - z3.Sub(z3, z2z2) - if z3.Sign() == -1 { - z3.Add(z3, bitCurve.P) - } - z3.Mul(z3, h) - z3.Mod(z3, bitCurve.P) - - return x3, y3, z3 -} - -// Double returns 2*(x,y) -func (bitCurve *BitCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) { - z1 := new(big.Int).SetInt64(1) - return bitCurve.affineFromJacobian(bitCurve.doubleJacobian(x1, y1, z1)) -} - -// doubleJacobian takes a point in Jacobian coordinates, (x, y, z), and -// returns its double, also in Jacobian form. -func (bitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, *big.Int) { - // See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l - - a := new(big.Int).Mul(x, x) //X1² - b := new(big.Int).Mul(y, y) //Y1² - c := new(big.Int).Mul(b, b) //B² - - d := new(big.Int).Add(x, b) //X1+B - d.Mul(d, d) //(X1+B)² - d.Sub(d, a) //(X1+B)²-A - d.Sub(d, c) //(X1+B)²-A-C - d.Mul(d, big.NewInt(2)) //2*((X1+B)²-A-C) - - e := new(big.Int).Mul(big.NewInt(3), a) //3*A - f := new(big.Int).Mul(e, e) //E² - - x3 := new(big.Int).Mul(big.NewInt(2), d) //2*D - x3.Sub(f, x3) //F-2*D - x3.Mod(x3, bitCurve.P) - - y3 := new(big.Int).Sub(d, x3) //D-X3 - y3.Mul(e, y3) //E*(D-X3) - y3.Sub(y3, new(big.Int).Mul(big.NewInt(8), c)) //E*(D-X3)-8*C - y3.Mod(y3, bitCurve.P) - - z3 := new(big.Int).Mul(y, z) //Y1*Z1 - z3.Mul(big.NewInt(2), z3) //3*Y1*Z1 - z3.Mod(z3, bitCurve.P) - - return x3, y3, z3 -} - -// TODO: double check if it is okay -// ScalarMult returns k*(Bx,By) where k is a number in big-endian form. -func (bitCurve *BitCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) { - // We have a slight problem in that the identity of the group (the - // point at infinity) cannot be represented in (x, y) form on a finite - // machine. Thus the standard add/double algorithm has to be tweaked - // slightly: our initial state is not the identity, but x, and we - // ignore the first true bit in |k|. If we don't find any true bits in - // |k|, then we return nil, nil, because we cannot return the identity - // element. - - Bz := new(big.Int).SetInt64(1) - x := Bx - y := By - z := Bz - - seenFirstTrue := false - for _, byte := range k { - for bitNum := 0; bitNum < 8; bitNum++ { - if seenFirstTrue { - x, y, z = bitCurve.doubleJacobian(x, y, z) - } - if byte&0x80 == 0x80 { - if !seenFirstTrue { - seenFirstTrue = true - } else { - x, y, z = bitCurve.addJacobian(Bx, By, Bz, x, y, z) - } - } - byte <<= 1 - } - } - - if !seenFirstTrue { - return nil, nil - } - - return bitCurve.affineFromJacobian(x, y, z) -} - -// ScalarBaseMult returns k*G, where G is the base point of the group and k is -// an integer in big-endian form. -func (bitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) { - return bitCurve.ScalarMult(bitCurve.Gx, bitCurve.Gy, k) -} - -var mask = []byte{0xff, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f} - -// TODO: double check if it is okay -// GenerateKey returns a public/private key pair. The private key is generated -// using the given reader, which must return random data. -func (bitCurve *BitCurve) GenerateKey(rand io.Reader) (priv []byte, x, y *big.Int, err error) { - byteLen := (bitCurve.BitSize + 7) >> 3 - priv = make([]byte, byteLen) - - for x == nil { - _, err = io.ReadFull(rand, priv) - if err != nil { - return - } - // We have to mask off any excess bits in the case that the size of the - // underlying field is not a whole number of bytes. - priv[0] &= mask[bitCurve.BitSize%8] - // This is because, in tests, rand will return all zeros and we don't - // want to get the point at infinity and loop forever. - priv[1] ^= 0x42 - x, y = bitCurve.ScalarBaseMult(priv) - } - return -} - -// Marshal converts a point into the form specified in section 4.3.6 of ANSI -// X9.62. -func (bitCurve *BitCurve) Marshal(x, y *big.Int) []byte { - byteLen := (bitCurve.BitSize + 7) >> 3 - - ret := make([]byte, 1+2*byteLen) - ret[0] = 4 // uncompressed point - - xBytes := x.Bytes() - copy(ret[1+byteLen-len(xBytes):], xBytes) - yBytes := y.Bytes() - copy(ret[1+2*byteLen-len(yBytes):], yBytes) - return ret -} - -// Unmarshal converts a point, serialised by Marshal, into an x, y pair. On -// error, x = nil. -func (bitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) { - byteLen := (bitCurve.BitSize + 7) >> 3 - if len(data) != 1+2*byteLen { - return - } - if data[0] != 4 { // uncompressed form - return - } - x = new(big.Int).SetBytes(data[1 : 1+byteLen]) - y = new(big.Int).SetBytes(data[1+byteLen:]) - return -} - -//curve parameters taken from: -//http://www.secg.org/collateral/sec2_final.pdf - -var initonce sync.Once -var secp160k1 *BitCurve -var secp192k1 *BitCurve -var secp224k1 *BitCurve -var secp256k1 *BitCurve - -func initAll() { - initS160() - initS192() - initS224() - initS256() -} - -func initS160() { - // See SEC 2 section 2.4.1 - secp160k1 = new(BitCurve) - secp160k1.Name = "secp160k1" - secp160k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73", 16) - secp160k1.N, _ = new(big.Int).SetString("0100000000000000000001B8FA16DFAB9ACA16B6B3", 16) - secp160k1.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000007", 16) - secp160k1.Gx, _ = new(big.Int).SetString("3B4C382CE37AA192A4019E763036F4F5DD4D7EBB", 16) - secp160k1.Gy, _ = new(big.Int).SetString("938CF935318FDCED6BC28286531733C3F03C4FEE", 16) - secp160k1.BitSize = 160 -} - -func initS192() { - // See SEC 2 section 2.5.1 - secp192k1 = new(BitCurve) - secp192k1.Name = "secp192k1" - secp192k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37", 16) - secp192k1.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D", 16) - secp192k1.B, _ = new(big.Int).SetString("000000000000000000000000000000000000000000000003", 16) - secp192k1.Gx, _ = new(big.Int).SetString("DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D", 16) - secp192k1.Gy, _ = new(big.Int).SetString("9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D", 16) - secp192k1.BitSize = 192 -} - -func initS224() { - // See SEC 2 section 2.6.1 - secp224k1 = new(BitCurve) - secp224k1.Name = "secp224k1" - secp224k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D", 16) - secp224k1.N, _ = new(big.Int).SetString("010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7", 16) - secp224k1.B, _ = new(big.Int).SetString("00000000000000000000000000000000000000000000000000000005", 16) - secp224k1.Gx, _ = new(big.Int).SetString("A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C", 16) - secp224k1.Gy, _ = new(big.Int).SetString("7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5", 16) - secp224k1.BitSize = 224 -} - -func initS256() { - // See SEC 2 section 2.7.1 - secp256k1 = new(BitCurve) - secp256k1.Name = "secp256k1" - secp256k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16) - secp256k1.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) - secp256k1.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16) - secp256k1.Gx, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16) - secp256k1.Gy, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16) - secp256k1.BitSize = 256 -} - -// S160 returns a BitCurve which implements secp160k1 (see SEC 2 section 2.4.1) -func S160() *BitCurve { - initonce.Do(initAll) - return secp160k1 -} - -// S192 returns a BitCurve which implements secp192k1 (see SEC 2 section 2.5.1) -func S192() *BitCurve { - initonce.Do(initAll) - return secp192k1 -} - -// S224 returns a BitCurve which implements secp224k1 (see SEC 2 section 2.6.1) -func S224() *BitCurve { - initonce.Do(initAll) - return secp224k1 -} - -// S256 returns a BitCurve which implements bitcurves (see SEC 2 section 2.7.1) -func S256() *BitCurve { - initonce.Do(initAll) - return secp256k1 -} diff --git a/vendor/github.com/ProtonMail/go-crypto/brainpool/brainpool.go b/vendor/github.com/ProtonMail/go-crypto/brainpool/brainpool.go deleted file mode 100644 index cb6676de24b..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/brainpool/brainpool.go +++ /dev/null @@ -1,134 +0,0 @@ -// Package brainpool implements Brainpool elliptic curves. -// Implementation of rcurves is from github.com/ebfe/brainpool -// Note that these curves are implemented with naive, non-constant time operations -// and are likely not suitable for environments where timing attacks are a concern. -package brainpool - -import ( - "crypto/elliptic" - "math/big" - "sync" -) - -var ( - once sync.Once - p256t1, p384t1, p512t1 *elliptic.CurveParams - p256r1, p384r1, p512r1 *rcurve -) - -func initAll() { - initP256t1() - initP384t1() - initP512t1() - initP256r1() - initP384r1() - initP512r1() -} - -func initP256t1() { - p256t1 = &elliptic.CurveParams{Name: "brainpoolP256t1"} - p256t1.P, _ = new(big.Int).SetString("A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377", 16) - p256t1.N, _ = new(big.Int).SetString("A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7", 16) - p256t1.B, _ = new(big.Int).SetString("662C61C430D84EA4FE66A7733D0B76B7BF93EBC4AF2F49256AE58101FEE92B04", 16) - p256t1.Gx, _ = new(big.Int).SetString("A3E8EB3CC1CFE7B7732213B23A656149AFA142C47AAFBC2B79A191562E1305F4", 16) - p256t1.Gy, _ = new(big.Int).SetString("2D996C823439C56D7F7B22E14644417E69BCB6DE39D027001DABE8F35B25C9BE", 16) - p256t1.BitSize = 256 -} - -func initP256r1() { - twisted := p256t1 - params := &elliptic.CurveParams{ - Name: "brainpoolP256r1", - P: twisted.P, - N: twisted.N, - BitSize: twisted.BitSize, - } - params.Gx, _ = new(big.Int).SetString("8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262", 16) - params.Gy, _ = new(big.Int).SetString("547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997", 16) - z, _ := new(big.Int).SetString("3E2D4BD9597B58639AE7AA669CAB9837CF5CF20A2C852D10F655668DFC150EF0", 16) - p256r1 = newrcurve(twisted, params, z) -} - -func initP384t1() { - p384t1 = &elliptic.CurveParams{Name: "brainpoolP384t1"} - p384t1.P, _ = new(big.Int).SetString("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53", 16) - p384t1.N, _ = new(big.Int).SetString("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565", 16) - p384t1.B, _ = new(big.Int).SetString("7F519EADA7BDA81BD826DBA647910F8C4B9346ED8CCDC64E4B1ABD11756DCE1D2074AA263B88805CED70355A33B471EE", 16) - p384t1.Gx, _ = new(big.Int).SetString("18DE98B02DB9A306F2AFCD7235F72A819B80AB12EBD653172476FECD462AABFFC4FF191B946A5F54D8D0AA2F418808CC", 16) - p384t1.Gy, _ = new(big.Int).SetString("25AB056962D30651A114AFD2755AD336747F93475B7A1FCA3B88F2B6A208CCFE469408584DC2B2912675BF5B9E582928", 16) - p384t1.BitSize = 384 -} - -func initP384r1() { - twisted := p384t1 - params := &elliptic.CurveParams{ - Name: "brainpoolP384r1", - P: twisted.P, - N: twisted.N, - BitSize: twisted.BitSize, - } - params.Gx, _ = new(big.Int).SetString("1D1C64F068CF45FFA2A63A81B7C13F6B8847A3E77EF14FE3DB7FCAFE0CBD10E8E826E03436D646AAEF87B2E247D4AF1E", 16) - params.Gy, _ = new(big.Int).SetString("8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E4646217791811142820341263C5315", 16) - z, _ := new(big.Int).SetString("41DFE8DD399331F7166A66076734A89CD0D2BCDB7D068E44E1F378F41ECBAE97D2D63DBC87BCCDDCCC5DA39E8589291C", 16) - p384r1 = newrcurve(twisted, params, z) -} - -func initP512t1() { - p512t1 = &elliptic.CurveParams{Name: "brainpoolP512t1"} - p512t1.P, _ = new(big.Int).SetString("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3", 16) - p512t1.N, _ = new(big.Int).SetString("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069", 16) - p512t1.B, _ = new(big.Int).SetString("7CBBBCF9441CFAB76E1890E46884EAE321F70C0BCB4981527897504BEC3E36A62BCDFA2304976540F6450085F2DAE145C22553B465763689180EA2571867423E", 16) - p512t1.Gx, _ = new(big.Int).SetString("640ECE5C12788717B9C1BA06CBC2A6FEBA85842458C56DDE9DB1758D39C0313D82BA51735CDB3EA499AA77A7D6943A64F7A3F25FE26F06B51BAA2696FA9035DA", 16) - p512t1.Gy, _ = new(big.Int).SetString("5B534BD595F5AF0FA2C892376C84ACE1BB4E3019B71634C01131159CAE03CEE9D9932184BEEF216BD71DF2DADF86A627306ECFF96DBB8BACE198B61E00F8B332", 16) - p512t1.BitSize = 512 -} - -func initP512r1() { - twisted := p512t1 - params := &elliptic.CurveParams{ - Name: "brainpoolP512r1", - P: twisted.P, - N: twisted.N, - BitSize: twisted.BitSize, - } - params.Gx, _ = new(big.Int).SetString("81AEE4BDD82ED9645A21322E9C4C6A9385ED9F70B5D916C1B43B62EEF4D0098EFF3B1F78E2D0D48D50D1687B93B97D5F7C6D5047406A5E688B352209BCB9F822", 16) - params.Gy, _ = new(big.Int).SetString("7DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892", 16) - z, _ := new(big.Int).SetString("12EE58E6764838B69782136F0F2D3BA06E27695716054092E60A80BEDB212B64E585D90BCE13761F85C3F1D2A64E3BE8FEA2220F01EBA5EEB0F35DBD29D922AB", 16) - p512r1 = newrcurve(twisted, params, z) -} - -// P256t1 returns a Curve which implements Brainpool P256t1 (see RFC 5639, section 3.4) -func P256t1() elliptic.Curve { - once.Do(initAll) - return p256t1 -} - -// P256r1 returns a Curve which implements Brainpool P256r1 (see RFC 5639, section 3.4) -func P256r1() elliptic.Curve { - once.Do(initAll) - return p256r1 -} - -// P384t1 returns a Curve which implements Brainpool P384t1 (see RFC 5639, section 3.6) -func P384t1() elliptic.Curve { - once.Do(initAll) - return p384t1 -} - -// P384r1 returns a Curve which implements Brainpool P384r1 (see RFC 5639, section 3.6) -func P384r1() elliptic.Curve { - once.Do(initAll) - return p384r1 -} - -// P512t1 returns a Curve which implements Brainpool P512t1 (see RFC 5639, section 3.7) -func P512t1() elliptic.Curve { - once.Do(initAll) - return p512t1 -} - -// P512r1 returns a Curve which implements Brainpool P512r1 (see RFC 5639, section 3.7) -func P512r1() elliptic.Curve { - once.Do(initAll) - return p512r1 -} diff --git a/vendor/github.com/ProtonMail/go-crypto/brainpool/rcurve.go b/vendor/github.com/ProtonMail/go-crypto/brainpool/rcurve.go deleted file mode 100644 index 7e291d6aa4e..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/brainpool/rcurve.go +++ /dev/null @@ -1,83 +0,0 @@ -package brainpool - -import ( - "crypto/elliptic" - "math/big" -) - -var _ elliptic.Curve = (*rcurve)(nil) - -type rcurve struct { - twisted elliptic.Curve - params *elliptic.CurveParams - z *big.Int - zinv *big.Int - z2 *big.Int - z3 *big.Int - zinv2 *big.Int - zinv3 *big.Int -} - -var ( - two = big.NewInt(2) - three = big.NewInt(3) -) - -func newrcurve(twisted elliptic.Curve, params *elliptic.CurveParams, z *big.Int) *rcurve { - zinv := new(big.Int).ModInverse(z, params.P) - return &rcurve{ - twisted: twisted, - params: params, - z: z, - zinv: zinv, - z2: new(big.Int).Exp(z, two, params.P), - z3: new(big.Int).Exp(z, three, params.P), - zinv2: new(big.Int).Exp(zinv, two, params.P), - zinv3: new(big.Int).Exp(zinv, three, params.P), - } -} - -func (curve *rcurve) toTwisted(x, y *big.Int) (*big.Int, *big.Int) { - var tx, ty big.Int - tx.Mul(x, curve.z2) - tx.Mod(&tx, curve.params.P) - ty.Mul(y, curve.z3) - ty.Mod(&ty, curve.params.P) - return &tx, &ty -} - -func (curve *rcurve) fromTwisted(tx, ty *big.Int) (*big.Int, *big.Int) { - var x, y big.Int - x.Mul(tx, curve.zinv2) - x.Mod(&x, curve.params.P) - y.Mul(ty, curve.zinv3) - y.Mod(&y, curve.params.P) - return &x, &y -} - -func (curve *rcurve) Params() *elliptic.CurveParams { - return curve.params -} - -func (curve *rcurve) IsOnCurve(x, y *big.Int) bool { - return curve.twisted.IsOnCurve(curve.toTwisted(x, y)) -} - -func (curve *rcurve) Add(x1, y1, x2, y2 *big.Int) (x, y *big.Int) { - tx1, ty1 := curve.toTwisted(x1, y1) - tx2, ty2 := curve.toTwisted(x2, y2) - return curve.fromTwisted(curve.twisted.Add(tx1, ty1, tx2, ty2)) -} - -func (curve *rcurve) Double(x1, y1 *big.Int) (x, y *big.Int) { - return curve.fromTwisted(curve.twisted.Double(curve.toTwisted(x1, y1))) -} - -func (curve *rcurve) ScalarMult(x1, y1 *big.Int, scalar []byte) (x, y *big.Int) { - tx1, ty1 := curve.toTwisted(x1, y1) - return curve.fromTwisted(curve.twisted.ScalarMult(tx1, ty1, scalar)) -} - -func (curve *rcurve) ScalarBaseMult(scalar []byte) (x, y *big.Int) { - return curve.fromTwisted(curve.twisted.ScalarBaseMult(scalar)) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/eax/eax.go b/vendor/github.com/ProtonMail/go-crypto/eax/eax.go deleted file mode 100644 index 3ae91d594cd..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/eax/eax.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (C) 2019 ProtonTech AG - -// Package eax provides an implementation of the EAX -// (encrypt-authenticate-translate) mode of operation, as described in -// Bellare, Rogaway, and Wagner "THE EAX MODE OF OPERATION: A TWO-PASS -// AUTHENTICATED-ENCRYPTION SCHEME OPTIMIZED FOR SIMPLICITY AND EFFICIENCY." -// In FSE'04, volume 3017 of LNCS, 2004 -package eax - -import ( - "crypto/cipher" - "crypto/subtle" - "errors" - "github.com/ProtonMail/go-crypto/internal/byteutil" -) - -const ( - defaultTagSize = 16 - defaultNonceSize = 16 -) - -type eax struct { - block cipher.Block // Only AES-{128, 192, 256} supported - tagSize int // At least 12 bytes recommended - nonceSize int -} - -func (e *eax) NonceSize() int { - return e.nonceSize -} - -func (e *eax) Overhead() int { - return e.tagSize -} - -// NewEAX returns an EAX instance with AES-{KEYLENGTH} and default nonce and -// tag lengths. Supports {128, 192, 256}- bit key length. -func NewEAX(block cipher.Block) (cipher.AEAD, error) { - return NewEAXWithNonceAndTagSize(block, defaultNonceSize, defaultTagSize) -} - -// NewEAXWithNonceAndTagSize returns an EAX instance with AES-{keyLength} and -// given nonce and tag lengths in bytes. Panics on zero nonceSize and -// exceedingly long tags. -// -// It is recommended to use at least 12 bytes as tag length (see, for instance, -// NIST SP 800-38D). -// -// Only to be used for compatibility with existing cryptosystems with -// non-standard parameters. For all other cases, prefer NewEAX. -func NewEAXWithNonceAndTagSize( - block cipher.Block, nonceSize, tagSize int) (cipher.AEAD, error) { - if nonceSize < 1 { - return nil, eaxError("Cannot initialize EAX with nonceSize = 0") - } - if tagSize > block.BlockSize() { - return nil, eaxError("Custom tag length exceeds blocksize") - } - return &eax{ - block: block, - tagSize: tagSize, - nonceSize: nonceSize, - }, nil -} - -func (e *eax) Seal(dst, nonce, plaintext, adata []byte) []byte { - if len(nonce) > e.nonceSize { - panic("crypto/eax: Nonce too long for this instance") - } - ret, out := byteutil.SliceForAppend(dst, len(plaintext)+e.tagSize) - omacNonce := e.omacT(0, nonce) - omacAdata := e.omacT(1, adata) - - // Encrypt message using CTR mode and omacNonce as IV - ctr := cipher.NewCTR(e.block, omacNonce) - ciphertextData := out[:len(plaintext)] - ctr.XORKeyStream(ciphertextData, plaintext) - - omacCiphertext := e.omacT(2, ciphertextData) - - tag := out[len(plaintext):] - for i := 0; i < e.tagSize; i++ { - tag[i] = omacCiphertext[i] ^ omacNonce[i] ^ omacAdata[i] - } - return ret -} - -func (e *eax) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) { - if len(nonce) > e.nonceSize { - panic("crypto/eax: Nonce too long for this instance") - } - if len(ciphertext) < e.tagSize { - return nil, eaxError("Ciphertext shorter than tag length") - } - sep := len(ciphertext) - e.tagSize - - // Compute tag - omacNonce := e.omacT(0, nonce) - omacAdata := e.omacT(1, adata) - omacCiphertext := e.omacT(2, ciphertext[:sep]) - - tag := make([]byte, e.tagSize) - for i := 0; i < e.tagSize; i++ { - tag[i] = omacCiphertext[i] ^ omacNonce[i] ^ omacAdata[i] - } - - // Compare tags - if subtle.ConstantTimeCompare(ciphertext[sep:], tag) != 1 { - return nil, eaxError("Tag authentication failed") - } - - // Decrypt ciphertext - ret, out := byteutil.SliceForAppend(dst, len(ciphertext)) - ctr := cipher.NewCTR(e.block, omacNonce) - ctr.XORKeyStream(out, ciphertext[:sep]) - - return ret[:sep], nil -} - -// Tweakable OMAC - Calls OMAC_K([t]_n || plaintext) -func (e *eax) omacT(t byte, plaintext []byte) []byte { - blockSize := e.block.BlockSize() - byteT := make([]byte, blockSize) - byteT[blockSize-1] = t - concat := append(byteT, plaintext...) - return e.omac(concat) -} - -func (e *eax) omac(plaintext []byte) []byte { - blockSize := e.block.BlockSize() - // L ← E_K(0^n); B ← 2L; P ← 4L - L := make([]byte, blockSize) - e.block.Encrypt(L, L) - B := byteutil.GfnDouble(L) - P := byteutil.GfnDouble(B) - - // CBC with IV = 0 - cbc := cipher.NewCBCEncrypter(e.block, make([]byte, blockSize)) - padded := e.pad(plaintext, B, P) - cbcCiphertext := make([]byte, len(padded)) - cbc.CryptBlocks(cbcCiphertext, padded) - - return cbcCiphertext[len(cbcCiphertext)-blockSize:] -} - -func (e *eax) pad(plaintext, B, P []byte) []byte { - // if |M| in {n, 2n, 3n, ...} - blockSize := e.block.BlockSize() - if len(plaintext) != 0 && len(plaintext)%blockSize == 0 { - return byteutil.RightXor(plaintext, B) - } - - // else return (M || 1 || 0^(n−1−(|M| % n))) xor→ P - ending := make([]byte, blockSize-len(plaintext)%blockSize) - ending[0] = 0x80 - padded := append(plaintext, ending...) - return byteutil.RightXor(padded, P) -} - -func eaxError(err string) error { - return errors.New("crypto/eax: " + err) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/eax/eax_test_vectors.go b/vendor/github.com/ProtonMail/go-crypto/eax/eax_test_vectors.go deleted file mode 100644 index ddb53d07905..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/eax/eax_test_vectors.go +++ /dev/null @@ -1,58 +0,0 @@ -package eax - -// Test vectors from -// https://web.cs.ucdavis.edu/~rogaway/papers/eax.pdf -var testVectors = []struct { - msg, key, nonce, header, ciphertext string -}{ - {"", - "233952DEE4D5ED5F9B9C6D6FF80FF478", - "62EC67F9C3A4A407FCB2A8C49031A8B3", - "6BFB914FD07EAE6B", - "E037830E8389F27B025A2D6527E79D01"}, - {"F7FB", - "91945D3F4DCBEE0BF45EF52255F095A4", - "BECAF043B0A23D843194BA972C66DEBD", - "FA3BFD4806EB53FA", - "19DD5C4C9331049D0BDAB0277408F67967E5"}, - {"1A47CB4933", - "01F74AD64077F2E704C0F60ADA3DD523", - "70C3DB4F0D26368400A10ED05D2BFF5E", - "234A3463C1264AC6", - "D851D5BAE03A59F238A23E39199DC9266626C40F80"}, - {"481C9E39B1", - "D07CF6CBB7F313BDDE66B727AFD3C5E8", - "8408DFFF3C1A2B1292DC199E46B7D617", - "33CCE2EABFF5A79D", - "632A9D131AD4C168A4225D8E1FF755939974A7BEDE"}, - {"40D0C07DA5E4", - "35B6D0580005BBC12B0587124557D2C2", - "FDB6B06676EEDC5C61D74276E1F8E816", - "AEB96EAEBE2970E9", - "071DFE16C675CB0677E536F73AFE6A14B74EE49844DD"}, - {"4DE3B35C3FC039245BD1FB7D", - "BD8E6E11475E60B268784C38C62FEB22", - "6EAC5C93072D8E8513F750935E46DA1B", - "D4482D1CA78DCE0F", - "835BB4F15D743E350E728414ABB8644FD6CCB86947C5E10590210A4F"}, - {"8B0A79306C9CE7ED99DAE4F87F8DD61636", - "7C77D6E813BED5AC98BAA417477A2E7D", - "1A8C98DCD73D38393B2BF1569DEEFC19", - "65D2017990D62528", - "02083E3979DA014812F59F11D52630DA30137327D10649B0AA6E1C181DB617D7F2"}, - {"1BDA122BCE8A8DBAF1877D962B8592DD2D56", - "5FFF20CAFAB119CA2FC73549E20F5B0D", - "DDE59B97D722156D4D9AFF2BC7559826", - "54B9F04E6A09189A", - "2EC47B2C4954A489AFC7BA4897EDCDAE8CC33B60450599BD02C96382902AEF7F832A"}, - {"6CF36720872B8513F6EAB1A8A44438D5EF11", - "A4A4782BCFFD3EC5E7EF6D8C34A56123", - "B781FCF2F75FA5A8DE97A9CA48E522EC", - "899A175897561D7E", - "0DE18FD0FDD91E7AF19F1D8EE8733938B1E8E7F6D2231618102FDB7FE55FF1991700"}, - {"CA40D7446E545FFAED3BD12A740A659FFBBB3CEAB7", - "8395FCF1E95BEBD697BD010BC766AAC3", - "22E7ADD93CFC6393C57EC0B3C17D6B44", - "126735FCC320D25A", - "CB8920F87A6C75CFF39627B56E3ED197C552D295A7CFC46AFC253B4652B1AF3795B124AB6E"}, -} diff --git a/vendor/github.com/ProtonMail/go-crypto/eax/random_vectors.go b/vendor/github.com/ProtonMail/go-crypto/eax/random_vectors.go deleted file mode 100644 index 4eb19f28d9c..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/eax/random_vectors.go +++ /dev/null @@ -1,131 +0,0 @@ -// These vectors include key length in {128, 192, 256}, tag size 128, and -// random nonce, header, and plaintext lengths. - -// This file was automatically generated. - -package eax - -var randomVectors = []struct { - key, nonce, header, plaintext, ciphertext string -}{ - {"DFDE093F36B0356E5A81F609786982E3", - "1D8AC604419001816905BA72B14CED7E", - "152A1517A998D7A24163FCDD146DE81AC347C8B97088F502093C1ABB8F6E33D9A219C34D7603A18B1F5ABE02E56661B7D7F67E81EC08C1302EF38D80A859486D450E94A4F26AD9E68EEBBC0C857A0FC5CF9E641D63D565A7E361BC8908F5A8DC8FD6", - "1C8EAAB71077FE18B39730A3156ADE29C5EE824C7EE86ED2A253B775603FB237116E654F6FEC588DD27F523A0E01246FE73FE348491F2A8E9ABC6CA58D663F71CDBCF4AD798BE46C42AE6EE8B599DB44A1A48D7BBBBA0F7D2750181E1C5E66967F7D57CBD30AFBDA5727", - "79E7E150934BBEBF7013F61C60462A14D8B15AF7A248AFB8A344EF021C1500E16666891D6E973D8BB56B71A371F12CA34660C4410C016982B20F547E3762A58B7BF4F20236CADCF559E2BE7D783B13723B2741FC7CDC8997D839E39A3DDD2BADB96743DD7049F1BDB0516A262869915B3F70498AFB7B191BF960"}, - {"F10619EF02E5D94D7550EB84ED364A21", - "8DC0D4F2F745BBAE835CC5574B942D20", - "FE561358F2E8DF7E1024FF1AE9A8D36EBD01352214505CB99D644777A8A1F6027FA2BDBFC529A9B91136D5F2416CFC5F0F4EC3A1AFD32BDDA23CA504C5A5CB451785FABF4DFE4CD50D817491991A60615B30286361C100A95D1712F2A45F8E374461F4CA2B", - "D7B5A971FC219631D30EFC3664AE3127D9CF3097DAD9C24AC7905D15E8D9B25B026B31D68CAE00975CDB81EB1FD96FD5E1A12E2BB83FA25F1B1D91363457657FC03875C27F2946C5", - "2F336ED42D3CC38FC61660C4CD60BA4BD438B05F5965D8B7B399D2E7167F5D34F792D318F94DB15D67463AC449E13D568CC09BFCE32A35EE3EE96A041927680AE329811811E27F2D1E8E657707AF99BA96D13A478D695D59"}, - {"429F514EFC64D98A698A9247274CFF45", - "976AA5EB072F912D126ACEBC954FEC38", - "A71D89DC5B6CEDBB7451A27C3C2CAE09126DB4C421", - "5632FE62AB1DC549D54D3BC3FC868ACCEDEFD9ECF5E9F8", - "848AE4306CA8C7F416F8707625B7F55881C0AB430353A5C967CDA2DA787F581A70E34DBEBB2385"}, - {"398138F309085F47F8457CDF53895A63", - "F8A8A7F2D28E5FFF7BBC2F24353F7A36", - "5D633C21BA7764B8855CAB586F3746E236AD486039C83C6B56EFA9C651D38A41D6B20DAEE3418BFEA44B8BD6", - "A3BBAA91920AF5E10659818B1B3B300AC79BFC129C8329E75251F73A66D3AE0128EB91D5031E0A65C329DB7D1E9C0493E268", - "D078097267606E5FB07CFB7E2B4B718172A82C6A4CEE65D549A4DFB9838003BD2FBF64A7A66988AC1A632FD88F9E9FBB57C5A78AD2E086EACBA3DB68511D81C2970A"}, - {"7A4151EBD3901B42CBA45DAFB2E931BA", - "0FC88ACEE74DD538040321C330974EB8", - "250464FB04733BAB934C59E6AD2D6AE8D662CBCFEFBE61E5A308D4211E58C4C25935B72C69107722E946BFCBF416796600542D76AEB73F2B25BF53BAF97BDEB36ED3A7A51C31E7F170EB897457E7C17571D1BA0A908954E9", - "88C41F3EBEC23FAB8A362D969CAC810FAD4F7CA6A7F7D0D44F060F92E37E1183768DD4A8C733F71C96058D362A39876D183B86C103DE", - "74A25B2182C51096D48A870D80F18E1CE15867778E34FCBA6BD7BFB3739FDCD42AD0F2D9F4EBA29085285C6048C15BCE5E5166F1F962D3337AA88E6062F05523029D0A7F0BF9"}, - {"BFB147E1CD5459424F8C0271FC0E0DC5", - "EABCC126442BF373969EA3015988CC45", - "4C0880E1D71AA2C7", - "BE1B5EC78FBF73E7A6682B21BA7E0E5D2D1C7ABE", - "5660D7C1380E2F306895B1402CB2D6C37876504276B414D120F4CF92FDDDBB293A238EA0"}, - {"595DD6F52D18BC2CA8EB4EDAA18D9FA3", - "0F84B5D36CF4BC3B863313AF3B4D2E97", - "30AE6CC5F99580F12A779D98BD379A60948020C0B6FBD5746B30BA3A15C6CD33DAF376C70A9F15B6C0EB410A93161F7958AE23", - "8EF3687A1642B070970B0B91462229D1D76ABC154D18211F7152AA9FF368", - "317C1DDB11417E5A9CC4DDE7FDFF6659A5AC4B31DE025212580A05CDAC6024D3E4AE7C2966E52B9129E9ECDBED86"}, - {"44E6F2DC8FDC778AD007137D11410F50", - "270A237AD977F7187AA6C158A0BAB24F", - "509B0F0EB12E2AA5C5BA2DE553C07FAF4CE0C9E926531AA709A3D6224FCB783ACCF1559E10B1123EBB7D52E8AB54E6B5352A9ED0D04124BF0E9D9BACFD7E32B817B2E625F5EE94A64EDE9E470DE7FE6886C19B294F9F828209FE257A78", - "8B3D7815DF25618A5D0C55A601711881483878F113A12EC36CF64900549A3199555528559DC118F789788A55FAFD944E6E99A9CA3F72F238CD3F4D88223F7A745992B3FAED1848", - "1CC00D79F7AD82FDA71B58D286E5F34D0CC4CEF30704E771CC1E50746BDF83E182B078DB27149A42BAE619DF0F85B0B1090AD55D3B4471B0D6F6ECCD09C8F876B30081F0E7537A9624F8AAF29DA85E324122EFB4D68A56"}, - {"BB7BC352A03044B4428D8DBB4B0701FDEC4649FD17B81452", - "8B4BBE26CCD9859DCD84884159D6B0A4", - "2212BEB0E78E0F044A86944CF33C8D5C80D9DBE1034BF3BCF73611835C7D3A52F5BD2D81B68FD681B68540A496EE5DA16FD8AC8824E60E1EC2042BE28FB0BFAD4E4B03596446BDD8C37D936D9B3D5295BE19F19CF5ACE1D33A46C952CE4DE5C12F92C1DD051E04AEED", - "9037234CC44FFF828FABED3A7084AF40FA7ABFF8E0C0EFB57A1CC361E18FC4FAC1AB54F3ABFE9FF77263ACE16C3A", - "A9391B805CCD956081E0B63D282BEA46E7025126F1C1631239C33E92AA6F92CD56E5A4C56F00FF9658E93D48AF4EF0EF81628E34AD4DB0CDAEDCD2A17EE7"}, - {"99C0AD703196D2F60A74E6B378B838B31F82EA861F06FC4E", - "92745C018AA708ECFEB1667E9F3F1B01", - "828C69F376C0C0EC651C67749C69577D589EE39E51404D80EBF70C8660A8F5FD375473F4A7C611D59CB546A605D67446CE2AA844135FCD78BB5FBC90222A00D42920BB1D7EEDFB0C4672554F583EF23184F89063CDECBE482367B5F9AF3ACBC3AF61392BD94CBCD9B64677", - "A879214658FD0A5B0E09836639BF82E05EC7A5EF71D4701934BDA228435C68AC3D5CEB54997878B06A655EEACEFB1345C15867E7FE6C6423660C8B88DF128EBD6BCD85118DBAE16E9252FFB204324E5C8F38CA97759BDBF3CB0083", - "51FE87996F194A2585E438B023B345439EA60D1AEBED4650CDAF48A4D4EEC4FC77DC71CC4B09D3BEEF8B7B7AF716CE2B4EFFB3AC9E6323C18AC35E0AA6E2BBBC8889490EB6226C896B0D105EAB42BFE7053CCF00ED66BA94C1BA09A792AA873F0C3B26C5C5F9A936E57B25"}, - {"7086816D00D648FB8304AA8C9E552E1B69A9955FB59B25D1", - "0F45CF7F0BF31CCEB85D9DA10F4D749F", - "93F27C60A417D9F0669E86ACC784FC8917B502DAF30A6338F11B30B94D74FEFE2F8BE1BBE2EAD10FAB7EED3C6F72B7C3ECEE1937C32ED4970A6404E139209C05", - "877F046601F3CBE4FB1491943FA29487E738F94B99AF206262A1D6FF856C9AA0B8D4D08A54370C98F8E88FA3DCC2B14C1F76D71B2A4C7963AEE8AF960464C5BEC8357AD00DC8", - "FE96906B895CE6A8E72BC72344E2C8BB3C63113D70EAFA26C299BAFE77A8A6568172EB447FB3E86648A0AF3512DEB1AAC0819F3EC553903BF28A9FB0F43411237A774BF9EE03E445D280FBB9CD12B9BAAB6EF5E52691"}, - {"062F65A896D5BF1401BADFF70E91B458E1F9BD4888CB2E4D", - "5B11EA1D6008EBB41CF892FCA5B943D1", - "BAF4FF5C8242", - "A8870E091238355984EB2F7D61A865B9170F440BFF999A5993DD41A10F4440D21FF948DDA2BF663B2E03AC3324492DC5E40262ECC6A65C07672353BE23E7FB3A9D79FF6AA38D97960905A38DECC312CB6A59E5467ECF06C311CD43ADC0B543EDF34FE8BE611F176460D5627CA51F8F8D9FED71F55C", - "B10E127A632172CF8AA7539B140D2C9C2590E6F28C3CB892FC498FCE56A34F732FBFF32E79C7B9747D9094E8635A0C084D6F0247F9768FB5FF83493799A9BEC6C39572120C40E9292C8C947AE8573462A9108C36D9D7112E6995AE5867E6C8BB387D1C5D4BEF524F391B9FD9F0A3B4BFA079E915BCD920185CFD38D114C558928BD7D47877"}, - {"38A8E45D6D705A11AF58AED5A1344896998EACF359F2E26A", - "FD82B5B31804FF47D44199B533D0CF84", - "DE454D4E62FE879F2050EE3E25853623D3E9AC52EEC1A1779A48CFAF5ECA0BFDE44749391866D1", - "B804", - "164BB965C05EBE0931A1A63293EDF9C38C27"}, - {"34C33C97C6D7A0850DA94D78A58DC61EC717CD7574833068", - "343BE00DA9483F05C14F2E9EB8EA6AE8", - "78312A43EFDE3CAE34A65796FF059A3FE15304EEA5CF1D9306949FE5BF3349D4977D4EBE76C040FE894C5949E4E4D6681153DA87FB9AC5062063CA2EA183566343362370944CE0362D25FC195E124FD60E8682E665D13F2229DDA3E4B2CB1DCA", - "CC11BB284B1153578E4A5ED9D937B869DAF00F5B1960C23455CA9CC43F486A3BE0B66254F1041F04FDF459C8640465B6E1D2CF899A381451E8E7FCB50CF87823BE77E24B132BBEEDC72E53369B275E1D8F49ECE59F4F215230AC4FE133FC80E4F634EE80BA4682B62C86", - "E7F703DC31A95E3A4919FF957836CB76C063D81702AEA4703E1C2BF30831E58C4609D626EC6810E12EAA5B930F049FF9EFC22C3E3F1EBD4A1FB285CB02A1AC5AD46B425199FC0A85670A5C4E3DAA9636C8F64C199F42F18AAC8EA7457FD377F322DD7752D7D01B946C8F0A97E6113F0D50106F319AFD291AAACE"}, - {"C6ECF7F053573E403E61B83052A343D93CBCC179D1E835BE", - "E280E13D7367042E3AA09A80111B6184", - "21486C9D7A9647", - "5F2639AFA6F17931853791CD8C92382BBB677FD72D0AB1A080D0E49BFAA21810E963E4FACD422E92F65CBFAD5884A60CD94740DF31AF02F95AA57DA0C4401B0ED906", - "5C51DB20755302070C45F52E50128A67C8B2E4ED0EACB7E29998CCE2E8C289DD5655913EC1A51CC3AABE5CDC2402B2BE7D6D4BF6945F266FBD70BA9F37109067157AE7530678B45F64475D4EBFCB5FFF46A5"}, - {"5EC6CF7401BC57B18EF154E8C38ACCA8959E57D2F3975FF5", - "656B41CB3F9CF8C08BAD7EBFC80BD225", - "6B817C2906E2AF425861A7EF59BA5801F143EE2A139EE72697CDE168B4", - "2C0E1DDC9B1E5389BA63845B18B1F8A1DB062037151BCC56EF7C21C0BB4DAE366636BBA975685D7CC5A94AFBE89C769016388C56FB7B57CE750A12B718A8BDCF70E80E8659A8330EFC8F86640F21735E8C80E23FE43ABF23507CE3F964AE4EC99D", - "ED780CF911E6D1AA8C979B889B0B9DC1ABE261832980BDBFB576901D9EF5AB8048998E31A15BE54B3E5845A4D136AD24D0BDA1C3006168DF2F8AC06729CB0818867398150020131D8F04EDF1923758C9EABB5F735DE5EA1758D4BC0ACFCA98AFD202E9839B8720253693B874C65586C6F0"}, - {"C92F678EB2208662F5BCF3403EC05F5961E957908A3E79421E1D25FC19054153", - "DA0F3A40983D92F2D4C01FED33C7A192", - "2B6E9D26DB406A0FAB47608657AA10EFC2B4AA5F459B29FF85AC9A40BFFE7AEB04F77E9A11FAAA116D7F6D4DA417671A9AB02C588E0EF59CB1BFB4B1CC931B63A3B3A159FCEC97A04D1E6F0C7E6A9CEF6B0ABB04758A69F1FE754DF4C2610E8C46B6CF413BDB31351D55BEDCB7B4A13A1C98E10984475E0F2F957853", - "F37326A80E08", - "83519E53E321D334F7C10B568183775C0E9AAE55F806"}, - {"6847E0491BE57E72995D186D50094B0B3593957A5146798FCE68B287B2FB37B5", - "3EE1182AEBB19A02B128F28E1D5F7F99", - "D9F35ABB16D776CE", - "DB7566ED8EA95BDF837F23DB277BAFBC5E70D1105ADFD0D9EF15475051B1EF94709C67DCA9F8D5", - "2CDCED0C9EBD6E2A508822A685F7DCD1CDD99E7A5FCA786C234E7F7F1D27EC49751AD5DCFA30C5EDA87C43CAE3B919B6BBCFE34C8EDA59"}, - {"82B019673642C08388D3E42075A4D5D587558C229E4AB8F660E37650C4C41A0A", - "336F5D681E0410FAE7B607246092C6DC", - "D430CBD8FE435B64214E9E9CDC5DE99D31CFCFB8C10AA0587A49DF276611", - "998404153AD77003E1737EDE93ED79859EE6DCCA93CB40C4363AA817ABF2DBBD46E42A14A7183B6CC01E12A577888141363D0AE011EB6E8D28C0B235", - "9BEF69EEB60BD3D6065707B7557F25292A8872857CFBD24F2F3C088E4450995333088DA50FD9121221C504DF1D0CD5EFE6A12666C5D5BB12282CF4C19906E9CFAB97E9BDF7F49DC17CFC384B"}, - {"747B2E269B1859F0622C15C8BAD6A725028B1F94B8DB7326948D1E6ED663A8BC", - "AB91F7245DDCE3F1C747872D47BE0A8A", - "3B03F786EF1DDD76E1D42646DA4CD2A5165DC5383CE86D1A0B5F13F910DC278A4E451EE0192CBA178E13B3BA27FDC7840DF73D2E104B", - "6B803F4701114F3E5FE21718845F8416F70F626303F545BE197189E0A2BA396F37CE06D389EB2658BC7D56D67868708F6D0D32", - "1570DDB0BCE75AA25D1957A287A2C36B1A5F2270186DA81BA6112B7F43B0F3D1D0ED072591DCF1F1C99BBB25621FC39B896FF9BD9413A2845363A9DCD310C32CF98E57"}, - {"02E59853FB29AEDA0FE1C5F19180AD99A12FF2F144670BB2B8BADF09AD812E0A", - "C691294EF67CD04D1B9242AF83DD1421", - "879334DAE3", - "1E17F46A98FEF5CBB40759D95354", - "FED8C3FF27DDF6313AED444A2985B36CBA268AAD6AAC563C0BA28F6DB5DB"}, - {"F6C1FB9B4188F2288FF03BD716023198C3582CF2A037FC2F29760916C2B7FCDB", - "4228DA0678CA3534588859E77DFF014C", - "D8153CAF35539A61DD8D05B3C9B44F01E564FB9348BCD09A1C23B84195171308861058F0A3CD2A55B912A3AAEE06FF4D356C77275828F2157C2FC7C115DA39E443210CCC56BEDB0CC99BBFB227ABD5CC454F4E7F547C7378A659EEB6A7E809101A84F866503CB18D4484E1FA09B3EC7FC75EB2E35270800AA7", - "23B660A779AD285704B12EC1C580387A47BEC7B00D452C6570", - "5AA642BBABA8E49849002A2FAF31DB8FC7773EFDD656E469CEC19B3206D4174C9A263D0A05484261F6"}, - {"8FF6086F1FADB9A3FBE245EAC52640C43B39D43F89526BB5A6EBA47710931446", - "943188480C99437495958B0AE4831AA9", - "AD5CD0BDA426F6EBA23C8EB23DC73FF9FEC173355EDBD6C9344C4C4383F211888F7CE6B29899A6801DF6B38651A7C77150941A", - "80CD5EA8D7F81DDF5070B934937912E8F541A5301877528EB41AB60C020968D459960ED8FB73083329841A", - "ABAE8EB7F36FCA2362551E72DAC890BA1BB6794797E0FC3B67426EC9372726ED4725D379EA0AC9147E48DCD0005C502863C2C5358A38817C8264B5"}, - {"A083B54E6B1FE01B65D42FCD248F97BB477A41462BBFE6FD591006C022C8FD84", - "B0490F5BD68A52459556B3749ACDF40E", - "8892E047DA5CFBBDF7F3CFCBD1BD21C6D4C80774B1826999234394BD3E513CC7C222BB40E1E3140A152F19B3802F0D036C24A590512AD0E8", - "D7B15752789DC94ED0F36778A5C7BBB207BEC32BAC66E702B39966F06E381E090C6757653C3D26A81EC6AD6C364D66867A334C91BB0B8A8A4B6EACDF0783D09010AEBA2DD2062308FE99CC1F", - "C071280A732ADC93DF272BF1E613B2BB7D46FC6665EF2DC1671F3E211D6BDE1D6ADDD28DF3AA2E47053FC8BB8AE9271EC8BC8B2CFFA320D225B451685B6D23ACEFDD241FE284F8ADC8DB07F456985B14330BBB66E0FB212213E05B3E"}, -} diff --git a/vendor/github.com/ProtonMail/go-crypto/internal/byteutil/byteutil.go b/vendor/github.com/ProtonMail/go-crypto/internal/byteutil/byteutil.go deleted file mode 100644 index d558b9bd82e..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/internal/byteutil/byteutil.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (C) 2019 ProtonTech AG -// This file contains necessary tools for the aex and ocb packages. -// -// These functions SHOULD NOT be used elsewhere, since they are optimized for -// specific input nature in the EAX and OCB modes of operation. - -package byteutil - -// GfnDouble computes 2 * input in the field of 2^n elements. -// The irreducible polynomial in the finite field for n=128 is -// x^128 + x^7 + x^2 + x + 1 (equals 0x87) -// Constant-time execution in order to avoid side-channel attacks -func GfnDouble(input []byte) []byte { - if len(input) != 16 { - panic("Doubling in GFn only implemented for n = 128") - } - // If the first bit is zero, return 2L = L << 1 - // Else return (L << 1) xor 0^120 10000111 - shifted := ShiftBytesLeft(input) - shifted[15] ^= ((input[0] >> 7) * 0x87) - return shifted -} - -// ShiftBytesLeft outputs the byte array corresponding to x << 1 in binary. -func ShiftBytesLeft(x []byte) []byte { - l := len(x) - dst := make([]byte, l) - for i := 0; i < l-1; i++ { - dst[i] = (x[i] << 1) | (x[i+1] >> 7) - } - dst[l-1] = x[l-1] << 1 - return dst -} - -// ShiftNBytesLeft puts in dst the byte array corresponding to x << n in binary. -func ShiftNBytesLeft(dst, x []byte, n int) { - // Erase first n / 8 bytes - copy(dst, x[n/8:]) - - // Shift the remaining n % 8 bits - bits := uint(n % 8) - l := len(dst) - for i := 0; i < l-1; i++ { - dst[i] = (dst[i] << bits) | (dst[i+1] >> uint(8-bits)) - } - dst[l-1] = dst[l-1] << bits - - // Append trailing zeroes - dst = append(dst, make([]byte, n/8)...) -} - -// XorBytesMut replaces X with X XOR Y. len(X) must be >= len(Y). -func XorBytesMut(X, Y []byte) { - for i := 0; i < len(Y); i++ { - X[i] ^= Y[i] - } -} - -// XorBytes puts X XOR Y into Z. len(Z) and len(X) must be >= len(Y). -func XorBytes(Z, X, Y []byte) { - for i := 0; i < len(Y); i++ { - Z[i] = X[i] ^ Y[i] - } -} - -// RightXor XORs smaller input (assumed Y) at the right of the larger input (assumed X) -func RightXor(X, Y []byte) []byte { - offset := len(X) - len(Y) - xored := make([]byte, len(X)) - copy(xored, X) - for i := 0; i < len(Y); i++ { - xored[offset+i] ^= Y[i] - } - return xored -} - -// SliceForAppend takes a slice and a requested number of bytes. It returns a -// slice with the contents of the given slice followed by that many bytes and a -// second slice that aliases into it and contains only the extra bytes. If the -// original slice has sufficient capacity then no allocation is performed. -func SliceForAppend(in []byte, n int) (head, tail []byte) { - if total := len(in) + n; cap(in) >= total { - head = in[:total] - } else { - head = make([]byte, total) - copy(head, in) - } - tail = head[len(in):] - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/ocb/ocb.go b/vendor/github.com/ProtonMail/go-crypto/ocb/ocb.go deleted file mode 100644 index 24f893017b3..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/ocb/ocb.go +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (C) 2019 ProtonTech AG - -// Package ocb provides an implementation of the OCB (offset codebook) mode of -// operation, as described in RFC-7253 of the IRTF and in Rogaway, Bellare, -// Black and Krovetz - OCB: A BLOCK-CIPHER MODE OF OPERATION FOR EFFICIENT -// AUTHENTICATED ENCRYPTION (2003). -// Security considerations (from RFC-7253): A private key MUST NOT be used to -// encrypt more than 2^48 blocks. Tag length should be at least 12 bytes (a -// brute-force forging adversary succeeds after 2^{tag length} attempts). A -// single key SHOULD NOT be used to decrypt ciphertext with different tag -// lengths. Nonces need not be secret, but MUST NOT be reused. -// This package only supports underlying block ciphers with 128-bit blocks, -// such as AES-{128, 192, 256}, but may be extended to other sizes. -package ocb - -import ( - "bytes" - "crypto/cipher" - "crypto/subtle" - "errors" - "math/bits" - - "github.com/ProtonMail/go-crypto/internal/byteutil" -) - -type ocb struct { - block cipher.Block - tagSize int - nonceSize int - mask mask - // Optimized en/decrypt: For each nonce N used to en/decrypt, the 'Ktop' - // internal variable can be reused for en/decrypting with nonces sharing - // all but the last 6 bits with N. The prefix of the first nonce used to - // compute the new Ktop, and the Ktop value itself, are stored in - // reusableKtop. If using incremental nonces, this saves one block cipher - // call every 63 out of 64 OCB encryptions, and stores one nonce and one - // output of the block cipher in memory only. - reusableKtop reusableKtop -} - -type mask struct { - // L_*, L_$, (L_i)_{i ∈ N} - lAst []byte - lDol []byte - L [][]byte -} - -type reusableKtop struct { - noncePrefix []byte - Ktop []byte -} - -const ( - defaultTagSize = 16 - defaultNonceSize = 15 -) - -const ( - enc = iota - dec -) - -func (o *ocb) NonceSize() int { - return o.nonceSize -} - -func (o *ocb) Overhead() int { - return o.tagSize -} - -// NewOCB returns an OCB instance with the given block cipher and default -// tag and nonce sizes. -func NewOCB(block cipher.Block) (cipher.AEAD, error) { - return NewOCBWithNonceAndTagSize(block, defaultNonceSize, defaultTagSize) -} - -// NewOCBWithNonceAndTagSize returns an OCB instance with the given block -// cipher, nonce length, and tag length. Panics on zero nonceSize and -// exceedingly long tag size. -// -// It is recommended to use at least 12 bytes as tag length. -func NewOCBWithNonceAndTagSize( - block cipher.Block, nonceSize, tagSize int) (cipher.AEAD, error) { - if block.BlockSize() != 16 { - return nil, ocbError("Block cipher must have 128-bit blocks") - } - if nonceSize < 1 { - return nil, ocbError("Incorrect nonce length") - } - if nonceSize >= block.BlockSize() { - return nil, ocbError("Nonce length exceeds blocksize - 1") - } - if tagSize > block.BlockSize() { - return nil, ocbError("Custom tag length exceeds blocksize") - } - return &ocb{ - block: block, - tagSize: tagSize, - nonceSize: nonceSize, - mask: initializeMaskTable(block), - reusableKtop: reusableKtop{ - noncePrefix: nil, - Ktop: nil, - }, - }, nil -} - -func (o *ocb) Seal(dst, nonce, plaintext, adata []byte) []byte { - if len(nonce) > o.nonceSize { - panic("crypto/ocb: Incorrect nonce length given to OCB") - } - sep := len(plaintext) - ret, out := byteutil.SliceForAppend(dst, sep+o.tagSize) - tag := o.crypt(enc, out[:sep], nonce, adata, plaintext) - copy(out[sep:], tag) - return ret -} - -func (o *ocb) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) { - if len(nonce) > o.nonceSize { - panic("Nonce too long for this instance") - } - if len(ciphertext) < o.tagSize { - return nil, ocbError("Ciphertext shorter than tag length") - } - sep := len(ciphertext) - o.tagSize - ret, out := byteutil.SliceForAppend(dst, sep) - ciphertextData := ciphertext[:sep] - tag := o.crypt(dec, out, nonce, adata, ciphertextData) - if subtle.ConstantTimeCompare(tag, ciphertext[sep:]) == 1 { - return ret, nil - } - for i := range out { - out[i] = 0 - } - return nil, ocbError("Tag authentication failed") -} - -// On instruction enc (resp. dec), crypt is the encrypt (resp. decrypt) -// function. It writes the resulting plain/ciphertext into Y and returns -// the tag. -func (o *ocb) crypt(instruction int, Y, nonce, adata, X []byte) []byte { - // - // Consider X as a sequence of 128-bit blocks - // - // Note: For encryption (resp. decryption), X is the plaintext (resp., the - // ciphertext without the tag). - blockSize := o.block.BlockSize() - - // - // Nonce-dependent and per-encryption variables - // - // Zero out the last 6 bits of the nonce into truncatedNonce to see if Ktop - // is already computed. - truncatedNonce := make([]byte, len(nonce)) - copy(truncatedNonce, nonce) - truncatedNonce[len(truncatedNonce)-1] &= 192 - var Ktop []byte - if bytes.Equal(truncatedNonce, o.reusableKtop.noncePrefix) { - Ktop = o.reusableKtop.Ktop - } else { - // Nonce = num2str(TAGLEN mod 128, 7) || zeros(120 - bitlen(N)) || 1 || N - paddedNonce := append(make([]byte, blockSize-1-len(nonce)), 1) - paddedNonce = append(paddedNonce, truncatedNonce...) - paddedNonce[0] |= byte(((8 * o.tagSize) % (8 * blockSize)) << 1) - // Last 6 bits of paddedNonce are already zero. Encrypt into Ktop - paddedNonce[blockSize-1] &= 192 - Ktop = paddedNonce - o.block.Encrypt(Ktop, Ktop) - o.reusableKtop.noncePrefix = truncatedNonce - o.reusableKtop.Ktop = Ktop - } - - // Stretch = Ktop || ((lower half of Ktop) XOR (lower half of Ktop << 8)) - xorHalves := make([]byte, blockSize/2) - byteutil.XorBytes(xorHalves, Ktop[:blockSize/2], Ktop[1:1+blockSize/2]) - stretch := append(Ktop, xorHalves...) - bottom := int(nonce[len(nonce)-1] & 63) - offset := make([]byte, len(stretch)) - byteutil.ShiftNBytesLeft(offset, stretch, bottom) - offset = offset[:blockSize] - - // - // Process any whole blocks - // - // Note: For encryption Y is ciphertext || tag, for decryption Y is - // plaintext || tag. - checksum := make([]byte, blockSize) - m := len(X) / blockSize - for i := 0; i < m; i++ { - index := bits.TrailingZeros(uint(i + 1)) - if len(o.mask.L)-1 < index { - o.mask.extendTable(index) - } - byteutil.XorBytesMut(offset, o.mask.L[bits.TrailingZeros(uint(i+1))]) - blockX := X[i*blockSize : (i+1)*blockSize] - blockY := Y[i*blockSize : (i+1)*blockSize] - switch instruction { - case enc: - byteutil.XorBytesMut(checksum, blockX) - byteutil.XorBytes(blockY, blockX, offset) - o.block.Encrypt(blockY, blockY) - byteutil.XorBytesMut(blockY, offset) - case dec: - byteutil.XorBytes(blockY, blockX, offset) - o.block.Decrypt(blockY, blockY) - byteutil.XorBytesMut(blockY, offset) - byteutil.XorBytesMut(checksum, blockY) - } - } - // - // Process any final partial block and compute raw tag - // - tag := make([]byte, blockSize) - if len(X)%blockSize != 0 { - byteutil.XorBytesMut(offset, o.mask.lAst) - pad := make([]byte, blockSize) - o.block.Encrypt(pad, offset) - chunkX := X[blockSize*m:] - chunkY := Y[blockSize*m : len(X)] - switch instruction { - case enc: - byteutil.XorBytesMut(checksum, chunkX) - checksum[len(chunkX)] ^= 128 - byteutil.XorBytes(chunkY, chunkX, pad[:len(chunkX)]) - // P_* || bit(1) || zeroes(127) - len(P_*) - case dec: - byteutil.XorBytes(chunkY, chunkX, pad[:len(chunkX)]) - // P_* || bit(1) || zeroes(127) - len(P_*) - byteutil.XorBytesMut(checksum, chunkY) - checksum[len(chunkY)] ^= 128 - } - } - byteutil.XorBytes(tag, checksum, offset) - byteutil.XorBytesMut(tag, o.mask.lDol) - o.block.Encrypt(tag, tag) - byteutil.XorBytesMut(tag, o.hash(adata)) - return tag[:o.tagSize] -} - -// This hash function is used to compute the tag. Per design, on empty input it -// returns a slice of zeros, of the same length as the underlying block cipher -// block size. -func (o *ocb) hash(adata []byte) []byte { - // - // Consider A as a sequence of 128-bit blocks - // - A := make([]byte, len(adata)) - copy(A, adata) - blockSize := o.block.BlockSize() - - // - // Process any whole blocks - // - sum := make([]byte, blockSize) - offset := make([]byte, blockSize) - m := len(A) / blockSize - for i := 0; i < m; i++ { - chunk := A[blockSize*i : blockSize*(i+1)] - index := bits.TrailingZeros(uint(i + 1)) - // If the mask table is too short - if len(o.mask.L)-1 < index { - o.mask.extendTable(index) - } - byteutil.XorBytesMut(offset, o.mask.L[index]) - byteutil.XorBytesMut(chunk, offset) - o.block.Encrypt(chunk, chunk) - byteutil.XorBytesMut(sum, chunk) - } - - // - // Process any final partial block; compute final hash value - // - if len(A)%blockSize != 0 { - byteutil.XorBytesMut(offset, o.mask.lAst) - // Pad block with 1 || 0 ^ 127 - bitlength(a) - ending := make([]byte, blockSize-len(A)%blockSize) - ending[0] = 0x80 - encrypted := append(A[blockSize*m:], ending...) - byteutil.XorBytesMut(encrypted, offset) - o.block.Encrypt(encrypted, encrypted) - byteutil.XorBytesMut(sum, encrypted) - } - return sum -} - -func initializeMaskTable(block cipher.Block) mask { - // - // Key-dependent variables - // - lAst := make([]byte, block.BlockSize()) - block.Encrypt(lAst, lAst) - lDol := byteutil.GfnDouble(lAst) - L := make([][]byte, 1) - L[0] = byteutil.GfnDouble(lDol) - - return mask{ - lAst: lAst, - lDol: lDol, - L: L, - } -} - -// Extends the L array of mask m up to L[limit], with L[i] = GfnDouble(L[i-1]) -func (m *mask) extendTable(limit int) { - for i := len(m.L); i <= limit; i++ { - m.L = append(m.L, byteutil.GfnDouble(m.L[i-1])) - } -} - -func ocbError(err string) error { - return errors.New("crypto/ocb: " + err) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/ocb/random_vectors.go b/vendor/github.com/ProtonMail/go-crypto/ocb/random_vectors.go deleted file mode 100644 index 0efaf344fd5..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/ocb/random_vectors.go +++ /dev/null @@ -1,136 +0,0 @@ -// In the test vectors provided by RFC 7253, the "bottom" -// internal variable, which defines "offset" for the first time, does not -// exceed 15. However, it can attain values up to 63. - -// These vectors include key length in {128, 192, 256}, tag size 128, and -// random nonce, header, and plaintext lengths. - -// This file was automatically generated. - -package ocb - -var randomVectors = []struct { - key, nonce, header, plaintext, ciphertext string -}{ - - {"9438C5D599308EAF13F800D2D31EA7F0", - "C38EE4801BEBFFA1CD8635BE", - "0E507B7DADD8A98CDFE272D3CB6B3E8332B56AE583FB049C0874D4200BED16BD1A044182434E9DA0E841F182DFD5B3016B34641CED0784F1745F63AB3D0DA22D3351C9EF9A658B8081E24498EBF61FCE40DA6D8E184536", - "962D227786FB8913A8BAD5DC3250", - "EEDEF5FFA5986D1E3BF86DDD33EF9ADC79DCA06E215FA772CCBA814F63AD"}, - {"BA7DE631C7D6712167C6724F5B9A2B1D", - "35263EBDA05765DC0E71F1F5", - "0103257B4224507C0242FEFE821EA7FA42E0A82863E5F8B68F7D881B4B44FA428A2B6B21D2F591260802D8AB6D83", - "9D6D1FC93AE8A64E7889B7B2E3521EFA9B920A8DDB692E6F833DDC4A38AFA535E5E2A3ED82CB7E26404AB86C54D01C4668F28398C2DF33D5D561CBA1C8DCFA7A912F5048E545B59483C0E3221F54B14DAA2E4EB657B3BEF9554F34CAD69B2724AE962D3D8A", - "E93852D1985C5E775655E937FA79CE5BF28A585F2AF53A5018853B9634BE3C84499AC0081918FDCE0624494D60E25F76ACD6853AC7576E3C350F332249BFCABD4E73CEABC36BE4EDDA40914E598AE74174A0D7442149B26990899491BDDFE8FC54D6C18E83AE9E9A6FFBF5D376565633862EEAD88D"}, - {"2E74B25289F6FD3E578C24866E9C72A5", - "FD912F15025AF8414642BA1D1D", - "FB5FB8C26F365EEDAB5FE260C6E3CCD27806729C8335F146063A7F9EA93290E56CF84576EB446350D22AD730547C267B1F0BBB97EB34E1E2C41A", - "6C092EBF78F76EE8C1C6E592277D9545BA16EDB67BC7D8480B9827702DC2F8A129E2B08A2CE710CA7E1DA45CE162BB6CD4B512E632116E2211D3C90871EFB06B8D4B902681C7FB", - "6AC0A77F26531BF4F354A1737F99E49BE32ECD909A7A71AD69352906F54B08A9CE9B8CA5D724CBFFC5673437F23F630697F3B84117A1431D6FA8CC13A974FB4AD360300522E09511B99E71065D5AC4BBCB1D791E864EF4"}, - {"E7EC507C802528F790AFF5303A017B17", - "4B97A7A568940A9E3CE7A99E93031E", - "28349BDC5A09390C480F9B8AA3EDEA3DDB8B9D64BCA322C570B8225DF0E31190DAB25A4014BA39519E02ABFB12B89AA28BBFD29E486E7FB28734258C817B63CED9912DBAFEBB93E2798AB2890DE3B0ACFCFF906AB15563EF7823CE83D27CDB251195E22BD1337BCBDE65E7C2C427321C463C2777BFE5AEAA", - "9455B3EA706B74", - "7F33BA3EA848D48A96B9530E26888F43EBD4463C9399B6"}, - {"6C928AA3224736F28EE7378DE0090191", - "8936138E2E4C6A13280017A1622D", - "6202717F2631565BDCDC57C6584543E72A7C8BD444D0D108ED35069819633C", - "DA0691439E5F035F3E455269D14FE5C201C8C9B0A3FE2D3F86BCC59387C868FE65733D388360B31E3CE28B4BF6A8BE636706B536D5720DB66B47CF1C7A5AFD6F61E0EF90F1726D6B0E169F9A768B2B7AE4EE00A17F630AC905FCAAA1B707FFF25B3A1AAE83B504837C64A5639B2A34002B300EC035C9B43654DA55", - "B8804D182AB0F0EEB464FA7BD1329AD6154F982013F3765FEDFE09E26DAC078C9C1439BFC1159D6C02A25E3FF83EF852570117B315852AD5EE20E0FA3AA0A626B0E43BC0CEA38B44579DD36803455FB46989B90E6D229F513FD727AF8372517E9488384C515D6067704119C931299A0982EDDFB9C2E86A90C450C077EB222511EC9CCABC9FCFDB19F70088"}, - {"ECEA315CA4B3F425B0C9957A17805EA4", - "664CDAE18403F4F9BA13015A44FC", - "642AFB090D6C6DB46783F08B01A3EF2A8FEB5736B531EAC226E7888FCC8505F396818F83105065FACB3267485B9E5E4A0261F621041C08FCCB2A809A49AB5252A91D0971BCC620B9D614BD77E57A0EED2FA5", - "6852C31F8083E20E364CEA21BB7854D67CEE812FE1C9ED2425C0932A90D3780728D1BB", - "2ECEF962A9695A463ADABB275BDA9FF8B2BA57AEC2F52EFFB700CD9271A74D2A011C24AEA946051BD6291776429B7E681BA33E"}, - {"4EE616C4A58AAA380878F71A373461F6", - "91B8C9C176D9C385E9C47E52", - "CDA440B7F9762C572A718AC754EDEECC119E5EE0CCB9FEA4FFB22EEE75087C032EBF3DA9CDD8A28CC010B99ED45143B41A4BA50EA2A005473F89639237838867A57F23B0F0ED3BF22490E4501DAC9C658A9B9F", - "D6E645FA9AE410D15B8123FD757FA356A8DBE9258DDB5BE88832E615910993F497EC", - "B70ED7BF959FB2AAED4F36174A2A99BFB16992C8CDF369C782C4DB9C73DE78C5DB8E0615F647243B97ACDB24503BC9CADC48"}, - {"DCD475773136C830D5E3D0C5FE05B7FF", - "BB8E1FBB483BE7616A922C4A", - "36FEF2E1CB29E76A6EA663FC3AF66ECD7404F466382F7B040AABED62293302B56E8783EF7EBC21B4A16C3E78A7483A0A403F253A2CDC5BBF79DC3DAE6C73F39A961D8FBBE8D41B", - "441E886EA38322B2437ECA7DEB5282518865A66780A454E510878E61BFEC3106A3CD93D2A02052E6F9E1832F9791053E3B76BF4C07EFDD6D4106E3027FABB752E60C1AA425416A87D53938163817A1051EBA1D1DEEB4B9B25C7E97368B52E5911A31810B0EC5AF547559B6142D9F4C4A6EF24A4CF75271BF9D48F62B", - "1BE4DD2F4E25A6512C2CC71D24BBB07368589A94C2714962CD0ACE5605688F06342587521E75F0ACAFFD86212FB5C34327D238DB36CF2B787794B9A4412E7CD1410EA5DDD2450C265F29CF96013CD213FD2880657694D718558964BC189B4A84AFCF47EB012935483052399DBA5B088B0A0477F20DFE0E85DCB735E21F22A439FB837DD365A93116D063E607"}, - {"3FBA2B3D30177FFE15C1C59ED2148BB2C091F5615FBA7C07", - "FACF804A4BEBF998505FF9DE", - "8213B9263B2971A5BDA18DBD02208EE1", - "15B323926993B326EA19F892D704439FC478828322AF72118748284A1FD8A6D814E641F70512FD706980337379F31DC63355974738D7FEA87AD2858C0C2EBBFBE74371C21450072373C7B651B334D7C4D43260B9D7CCD3AF9EDB", - "6D35DC1469B26E6AAB26272A41B46916397C24C485B61162E640A062D9275BC33DDCFD3D9E1A53B6C8F51AC89B66A41D59B3574197A40D9B6DCF8A4E2A001409C8112F16B9C389E0096179DB914E05D6D11ED0005AD17E1CE105A2F0BAB8F6B1540DEB968B7A5428FF44"}, - {"53B52B8D4D748BCDF1DDE68857832FA46227FA6E2F32EFA1", - "0B0EF53D4606B28D1398355F", - "F23882436349094AF98BCACA8218E81581A043B19009E28EFBF2DE37883E04864148CC01D240552CA8844EC1456F42034653067DA67E80F87105FD06E14FF771246C9612867BE4D215F6D761", - "F15030679BD4088D42CAC9BF2E9606EAD4798782FA3ED8C57EBE7F84A53236F51B25967C6489D0CD20C9EEA752F9BC", - "67B96E2D67C3729C96DAEAEDF821D61C17E648643A2134C5621FEC621186915AD80864BFD1EB5B238BF526A679385E012A457F583AFA78134242E9D9C1B4E4"}, - {"0272DD80F23399F49BFC320381A5CD8225867245A49A7D41", - "5C83F4896D0738E1366B1836", - "69B0337289B19F73A12BAEEA857CCAF396C11113715D9500CCCF48BA08CFF12BC8B4BADB3084E63B85719DB5058FA7C2C11DEB096D7943CFA7CAF5", - "C01AD10FC8B562CD17C7BC2FAB3E26CBDFF8D7F4DEA816794BBCC12336991712972F52816AABAB244EB43B0137E2BAC1DD413CE79531E78BEF782E6B439612BB3AEF154DE3502784F287958EBC159419F9EBA27916A28D6307324129F506B1DE80C1755A929F87", - "FEFE52DD7159C8DD6E8EC2D3D3C0F37AB6CB471A75A071D17EC4ACDD8F3AA4D7D4F7BB559F3C09099E3D9003E5E8AA1F556B79CECDE66F85B08FA5955E6976BF2695EA076388A62D2AD5BAB7CBF1A7F3F4C8D5CDF37CDE99BD3E30B685D9E5EEE48C7C89118EF4878EB89747F28271FA2CC45F8E9E7601"}, - {"3EEAED04A455D6E5E5AB53CFD5AFD2F2BC625C7BF4BE49A5", - "36B88F63ADBB5668588181D774", - "D367E3CB3703E762D23C6533188EF7028EFF9D935A3977150361997EC9DEAF1E4794BDE26AA8B53C124980B1362EC86FCDDFC7A90073171C1BAEE351A53234B86C66E8AB92FAE99EC6967A6D3428892D80", - "573454C719A9A55E04437BF7CBAAF27563CCCD92ADD5E515CD63305DFF0687E5EEF790C5DCA5C0033E9AB129505E2775438D92B38F08F3B0356BA142C6F694", - "E9F79A5B432D9E682C9AAA5661CFC2E49A0FCB81A431E54B42EB73DD3BED3F377FEC556ABA81624BA64A5D739AD41467460088F8D4F442180A9382CA635745473794C382FCDDC49BA4EB6D8A44AE3C"}, - {"B695C691538F8CBD60F039D0E28894E3693CC7C36D92D79D", - "BC099AEB637361BAC536B57618", - "BFFF1A65AE38D1DC142C71637319F5F6508E2CB33C9DCB94202B359ED5A5ED8042E7F4F09231D32A7242976677E6F4C549BF65FADC99E5AF43F7A46FD95E16C2", - "081DF3FD85B415D803F0BE5AC58CFF0023FDDED99788296C3731D8", - "E50C64E3614D94FE69C47092E46ACC9957C6FEA2CCBF96BC62FBABE7424753C75F9C147C42AE26FE171531"}, - {"C9ACBD2718F0689A1BE9802A551B6B8D9CF5614DAF5E65ED", - "B1B0AAF373B8B026EB80422051D8", - "6648C0E61AC733C76119D23FB24548D637751387AA2EAE9D80E912B7BD486CAAD9EAF4D7A5FE2B54AAD481E8EC94BB4D558000896E2010462B70C9FED1E7273080D1", - "189F591F6CB6D59AFEDD14C341741A8F1037DC0DF00FC57CE65C30F49E860255CEA5DC6019380CC0FE8880BC1A9E685F41C239C38F36E3F2A1388865C5C311059C0A", - "922A5E949B61D03BE34AB5F4E58607D4504EA14017BB363DAE3C873059EA7A1C77A746FB78981671D26C2CF6D9F24952D510044CE02A10177E9DB42D0145211DFE6E84369C5E3BC2669EAB4147B2822895F9"}, - {"7A832BD2CF5BF4919F353CE2A8C86A5E406DA2D52BE16A72", - "2F2F17CECF7E5A756D10785A3CB9DB", - "61DA05E3788CC2D8405DBA70C7A28E5AF699863C9F72E6C6770126929F5D6FA267F005EBCF49495CB46400958A3AE80D1289D1C671", - "44E91121195A41AF14E8CFDBD39A4B517BE0DF1A72977ED8A3EEF8EEDA1166B2EB6DB2C4AE2E74FA0F0C74537F659BFBD141E5DDEC67E64EDA85AABD3F52C85A785B9FB3CECD70E7DF", - "BEDF596EA21288D2B84901E188F6EE1468B14D5161D3802DBFE00D60203A24E2AB62714BF272A45551489838C3A7FEAADC177B591836E73684867CCF4E12901DCF2064058726BBA554E84ADC5136F507E961188D4AF06943D3"}, - {"1508E8AE9079AA15F1CEC4F776B4D11BCCB061B58AA56C18", - "BCA625674F41D1E3AB47672DC0C3", - "8B12CF84F16360F0EAD2A41BC021530FFCEC7F3579CAE658E10E2D3D81870F65AFCED0C77C6C4C6E6BA424FF23088C796BA6195ABA35094BF1829E089662E7A95FC90750AE16D0C8AFA55DAC789D7735B970B58D4BE7CEC7341DA82A0179A01929C27A59C5063215B859EA43", - "E525422519ECE070E82C", - "B47BC07C3ED1C0A43BA52C43CBACBCDBB29CAF1001E09FDF7107"}, - {"7550C2761644E911FE9ADD119BAC07376BEA442845FEAD876D7E7AC1B713E464", - "36D2EC25ADD33CDEDF495205BBC923", - "7FCFE81A3790DE97FFC3DE160C470847EA7E841177C2F759571CBD837EA004A6CA8C6F4AEBFF2E9FD552D73EB8A30705D58D70C0B67AEEA280CBBF0A477358ACEF1E7508F2735CD9A0E4F9AC92B8C008F575D3B6278F1C18BD01227E3502E5255F3AB1893632AD00C717C588EF652A51A43209E7EE90", - "2B1A62F8FDFAA3C16470A21AD307C9A7D03ADE8EF72C69B06F8D738CDE578D7AEFD0D40BD9C022FB9F580DF5394C998ACCCEFC5471A3996FB8F1045A81FDC6F32D13502EA65A211390C8D882B8E0BEFD8DD8CBEF51D1597B124E9F7F", - "C873E02A22DB89EB0787DB6A60B99F7E4A0A085D5C4232A81ADCE2D60AA36F92DDC33F93DD8640AC0E08416B187FB382B3EC3EE85A64B0E6EE41C1366A5AD2A282F66605E87031CCBA2FA7B2DA201D975994AADE3DD1EE122AE09604AD489B84BF0C1AB7129EE16C6934850E"}, - {"A51300285E554FDBDE7F771A9A9A80955639DD87129FAEF74987C91FB9687C71", - "81691D5D20EC818FCFF24B33DECC", - "C948093218AA9EB2A8E44A87EEA73FC8B6B75A196819A14BD83709EA323E8DF8B491045220E1D88729A38DBCFFB60D3056DAD4564498FD6574F74512945DEB34B69329ACED9FFC05D5D59DFCD5B973E2ACAFE6AD1EF8BBBC49351A2DD12508ED89ED", - "EB861165DAF7625F827C6B574ED703F03215", - "C6CD1CE76D2B3679C1B5AA1CFD67CCB55444B6BFD3E22C81CBC9BB738796B83E54E3"}, - {"8CE0156D26FAEB7E0B9B800BBB2E9D4075B5EAC5C62358B0E7F6FCE610223282", - "D2A7B94DD12CDACA909D3AD7", - "E021A78F374FC271389AB9A3E97077D755", - "7C26000B58929F5095E1CEE154F76C2A299248E299F9B5ADE6C403AA1FD4A67FD4E0232F214CE7B919EE7A1027D2B76C57475715CD078461", - "C556FB38DF069B56F337B5FF5775CE6EAA16824DFA754F20B78819028EA635C3BB7AA731DE8776B2DCB67DCA2D33EEDF3C7E52EA450013722A41755A0752433ED17BDD5991AAE77A"}, - {"1E8000A2CE00A561C9920A30BF0D7B983FEF8A1014C8F04C35CA6970E6BA02BD", - "65ED3D63F79F90BBFD19775E", - "336A8C0B7243582A46B221AA677647FCAE91", - "134A8B34824A290E7B", - "914FBEF80D0E6E17F8BDBB6097EBF5FBB0554952DC2B9E5151"}, - {"53D5607BBE690B6E8D8F6D97F3DF2BA853B682597A214B8AA0EA6E598650AF15", - "C391A856B9FE234E14BA1AC7BB40FF", - "479682BC21349C4BE1641D5E78FE2C79EC1B9CF5470936DCAD9967A4DCD7C4EFADA593BC9EDE71E6A08829B8580901B61E274227E9D918502DE3", - "EAD154DC09C5E26C5D26FF33ED148B27120C7F2C23225CC0D0631B03E1F6C6D96FEB88C1A4052ACB4CE746B884B6502931F407021126C6AAB8C514C077A5A38438AE88EE", - "938821286EBB671D999B87C032E1D6055392EB564E57970D55E545FC5E8BAB90E6E3E3C0913F6320995FC636D72CD9919657CC38BD51552F4A502D8D1FE56DB33EBAC5092630E69EBB986F0E15CEE9FC8C052501"}, - {"294362FCC984F440CEA3E9F7D2C06AF20C53AAC1B3738CA2186C914A6E193ABB", - "B15B61C8BB39261A8F55AB178EC3", - "D0729B6B75BB", - "2BD089ADCE9F334BAE3B065996C7D616DD0C27DF4218DCEEA0FBCA0F968837CE26B0876083327E25681FDDD620A32EC0DA12F73FAE826CC94BFF2B90A54D2651", - "AC94B25E4E21DE2437B806966CCD5D9385EF0CD4A51AB9FA6DE675C7B8952D67802E9FEC1FDE9F5D1EAB06057498BC0EEA454804FC9D2068982A3E24182D9AC2E7AB9994DDC899A604264583F63D066B"}, - {"959DBFEB039B1A5B8CE6A44649B602AAA5F98A906DB96143D202CD2024F749D9", - "01D7BDB1133E9C347486C1EFA6", - "F3843955BD741F379DD750585EDC55E2CDA05CCBA8C1F4622AC2FE35214BC3A019B8BD12C4CC42D9213D1E1556941E8D8450830287FFB3B763A13722DD4140ED9846FB5FFF745D7B0B967D810A068222E10B259AF1D392035B0D83DC1498A6830B11B2418A840212599171E0258A1C203B05362978", - "A21811232C950FA8B12237C2EBD6A7CD2C3A155905E9E0C7C120", - "63C1CE397B22F1A03F1FA549B43178BC405B152D3C95E977426D519B3DFCA28498823240592B6EEE7A14"}, - {"096AE499F5294173F34FF2B375F0E5D5AB79D0D03B33B1A74D7D576826345DF4", - "0C52B3D11D636E5910A4DD76D32C", - "229E9ECA3053789E937447BC719467075B6138A142DA528DA8F0CF8DDF022FD9AF8E74779BA3AC306609", - "8B7A00038783E8BAF6EDEAE0C4EAB48FC8FD501A588C7E4A4DB71E3604F2155A97687D3D2FFF8569261375A513CF4398CE0F87CA1658A1050F6EF6C4EA3E25", - "C20B6CF8D3C8241825FD90B2EDAC7593600646E579A8D8DAAE9E2E40C3835FE801B2BE4379131452BC5182C90307B176DFBE2049544222FE7783147B690774F6D9D7CEF52A91E61E298E9AA15464AC"}, -} diff --git a/vendor/github.com/ProtonMail/go-crypto/ocb/rfc7253_test_vectors_suite_a.go b/vendor/github.com/ProtonMail/go-crypto/ocb/rfc7253_test_vectors_suite_a.go deleted file mode 100644 index 330309ff5f8..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/ocb/rfc7253_test_vectors_suite_a.go +++ /dev/null @@ -1,78 +0,0 @@ -package ocb - -import ( - "encoding/hex" -) - -// Test vectors from https://tools.ietf.org/html/rfc7253. Note that key is -// shared across tests. -var testKey, _ = hex.DecodeString("000102030405060708090A0B0C0D0E0F") - -var rfc7253testVectors = []struct { - nonce, header, plaintext, ciphertext string -}{ - {"BBAA99887766554433221100", - "", - "", - "785407BFFFC8AD9EDCC5520AC9111EE6"}, - {"BBAA99887766554433221101", - "0001020304050607", - "0001020304050607", - "6820B3657B6F615A5725BDA0D3B4EB3A257C9AF1F8F03009"}, - {"BBAA99887766554433221102", - "0001020304050607", - "", - "81017F8203F081277152FADE694A0A00"}, - {"BBAA99887766554433221103", - "", - "0001020304050607", - "45DD69F8F5AAE72414054CD1F35D82760B2CD00D2F99BFA9"}, - {"BBAA99887766554433221104", - "000102030405060708090A0B0C0D0E0F", - "000102030405060708090A0B0C0D0E0F", - "571D535B60B277188BE5147170A9A22C3AD7A4FF3835B8C5701C1CCEC8FC3358"}, - {"BBAA99887766554433221105", - "000102030405060708090A0B0C0D0E0F", - "", - "8CF761B6902EF764462AD86498CA6B97"}, - {"BBAA99887766554433221106", - "", - "000102030405060708090A0B0C0D0E0F", - "5CE88EC2E0692706A915C00AEB8B2396F40E1C743F52436BDF06D8FA1ECA343D"}, - {"BBAA99887766554433221107", - "000102030405060708090A0B0C0D0E0F1011121314151617", - "000102030405060708090A0B0C0D0E0F1011121314151617", - "1CA2207308C87C010756104D8840CE1952F09673A448A122C92C62241051F57356D7F3C90BB0E07F"}, - {"BBAA99887766554433221108", - "000102030405060708090A0B0C0D0E0F1011121314151617", - "", - "6DC225A071FC1B9F7C69F93B0F1E10DE"}, - {"BBAA99887766554433221109", - "", - "000102030405060708090A0B0C0D0E0F1011121314151617", - "221BD0DE7FA6FE993ECCD769460A0AF2D6CDED0C395B1C3CE725F32494B9F914D85C0B1EB38357FF"}, - {"BBAA9988776655443322110A", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", - "BD6F6C496201C69296C11EFD138A467ABD3C707924B964DEAFFC40319AF5A48540FBBA186C5553C68AD9F592A79A4240"}, - {"BBAA9988776655443322110B", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", - "", - "FE80690BEE8A485D11F32965BC9D2A32"}, - {"BBAA9988776655443322110C", - "", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", - "2942BFC773BDA23CABC6ACFD9BFD5835BD300F0973792EF46040C53F1432BCDFB5E1DDE3BC18A5F840B52E653444D5DF"}, - {"BBAA9988776655443322110D", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", - "D5CA91748410C1751FF8A2F618255B68A0A12E093FF454606E59F9C1D0DDC54B65E8628E568BAD7AED07BA06A4A69483A7035490C5769E60"}, - {"BBAA9988776655443322110E", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", - "", - "C5CD9D1850C141E358649994EE701B68"}, - {"BBAA9988776655443322110F", - "", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", - "4412923493C57D5DE0D700F753CCE0D1D2D95060122E9F15A5DDBFC5787E50B5CC55EE507BCB084E479AD363AC366B95A98CA5F3000B1479"}, -} diff --git a/vendor/github.com/ProtonMail/go-crypto/ocb/rfc7253_test_vectors_suite_b.go b/vendor/github.com/ProtonMail/go-crypto/ocb/rfc7253_test_vectors_suite_b.go deleted file mode 100644 index 14a3c336fbc..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/ocb/rfc7253_test_vectors_suite_b.go +++ /dev/null @@ -1,25 +0,0 @@ -package ocb - -// Second set of test vectors from https://tools.ietf.org/html/rfc7253 -var rfc7253TestVectorTaglen96 = struct { - key, nonce, header, plaintext, ciphertext string -}{"0F0E0D0C0B0A09080706050403020100", - "BBAA9988776655443322110D", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", - "1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6AD0C515F4D1CDD4FDAC4F02AA"} - -var rfc7253AlgorithmTest = []struct { - KEYLEN, TAGLEN int - OUTPUT string -}{ - {128, 128, "67E944D23256C5E0B6C61FA22FDF1EA2"}, - {192, 128, "F673F2C3E7174AAE7BAE986CA9F29E17"}, - {256, 128, "D90EB8E9C977C88B79DD793D7FFA161C"}, - {128, 96, "77A3D8E73589158D25D01209"}, - {192, 96, "05D56EAD2752C86BE6932C5E"}, - {256, 96, "5458359AC23B0CBA9E6330DD"}, - {128, 64, "192C9B7BD90BA06A"}, - {192, 64, "0066BC6E0EF34E24"}, - {256, 64, "7D4EA5D445501CBE"}, -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/aes/keywrap/keywrap.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/aes/keywrap/keywrap.go deleted file mode 100644 index 3c6251d1ce6..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/aes/keywrap/keywrap.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2014 Matthew Endsley -// All rights reserved -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted providing that the following conditions -// are met: -// 1. Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING -// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -// Package keywrap is an implementation of the RFC 3394 AES key wrapping -// algorithm. This is used in OpenPGP with elliptic curve keys. -package keywrap - -import ( - "crypto/aes" - "encoding/binary" - "errors" -) - -var ( - // ErrWrapPlaintext is returned if the plaintext is not a multiple - // of 64 bits. - ErrWrapPlaintext = errors.New("keywrap: plainText must be a multiple of 64 bits") - - // ErrUnwrapCiphertext is returned if the ciphertext is not a - // multiple of 64 bits. - ErrUnwrapCiphertext = errors.New("keywrap: cipherText must by a multiple of 64 bits") - - // ErrUnwrapFailed is returned if unwrapping a key fails. - ErrUnwrapFailed = errors.New("keywrap: failed to unwrap key") - - // NB: the AES NewCipher call only fails if the key is an invalid length. - - // ErrInvalidKey is returned when the AES key is invalid. - ErrInvalidKey = errors.New("keywrap: invalid AES key") -) - -// Wrap a key using the RFC 3394 AES Key Wrap Algorithm. -func Wrap(key, plainText []byte) ([]byte, error) { - if len(plainText)%8 != 0 { - return nil, ErrWrapPlaintext - } - - c, err := aes.NewCipher(key) - if err != nil { - return nil, ErrInvalidKey - } - - nblocks := len(plainText) / 8 - - // 1) Initialize variables. - var block [aes.BlockSize]byte - // - Set A = IV, an initial value (see 2.2.3) - for ii := 0; ii < 8; ii++ { - block[ii] = 0xA6 - } - - // - For i = 1 to n - // - Set R[i] = P[i] - intermediate := make([]byte, len(plainText)) - copy(intermediate, plainText) - - // 2) Calculate intermediate values. - for ii := 0; ii < 6; ii++ { - for jj := 0; jj < nblocks; jj++ { - // - B = AES(K, A | R[i]) - copy(block[8:], intermediate[jj*8:jj*8+8]) - c.Encrypt(block[:], block[:]) - - // - A = MSB(64, B) ^ t where t = (n*j)+1 - t := uint64(ii*nblocks + jj + 1) - val := binary.BigEndian.Uint64(block[:8]) ^ t - binary.BigEndian.PutUint64(block[:8], val) - - // - R[i] = LSB(64, B) - copy(intermediate[jj*8:jj*8+8], block[8:]) - } - } - - // 3) Output results. - // - Set C[0] = A - // - For i = 1 to n - // - C[i] = R[i] - return append(block[:8], intermediate...), nil -} - -// Unwrap a key using the RFC 3394 AES Key Wrap Algorithm. -func Unwrap(key, cipherText []byte) ([]byte, error) { - if len(cipherText)%8 != 0 { - return nil, ErrUnwrapCiphertext - } - - c, err := aes.NewCipher(key) - if err != nil { - return nil, ErrInvalidKey - } - - nblocks := len(cipherText)/8 - 1 - - // 1) Initialize variables. - var block [aes.BlockSize]byte - // - Set A = C[0] - copy(block[:8], cipherText[:8]) - - // - For i = 1 to n - // - Set R[i] = C[i] - intermediate := make([]byte, len(cipherText)-8) - copy(intermediate, cipherText[8:]) - - // 2) Compute intermediate values. - for jj := 5; jj >= 0; jj-- { - for ii := nblocks - 1; ii >= 0; ii-- { - // - B = AES-1(K, (A ^ t) | R[i]) where t = n*j+1 - // - A = MSB(64, B) - t := uint64(jj*nblocks + ii + 1) - val := binary.BigEndian.Uint64(block[:8]) ^ t - binary.BigEndian.PutUint64(block[:8], val) - - copy(block[8:], intermediate[ii*8:ii*8+8]) - c.Decrypt(block[:], block[:]) - - // - R[i] = LSB(B, 64) - copy(intermediate[ii*8:ii*8+8], block[8:]) - } - } - - // 3) Output results. - // - If A is an appropriate initial value (see 2.2.3), - for ii := 0; ii < 8; ii++ { - if block[ii] != 0xA6 { - return nil, ErrUnwrapFailed - } - } - - // - For i = 1 to n - // - P[i] = R[i] - return intermediate, nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/armor/armor.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/armor/armor.go deleted file mode 100644 index e0a677f2843..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/armor/armor.go +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is -// very similar to PEM except that it has an additional CRC checksum. -package armor // import "github.com/ProtonMail/go-crypto/openpgp/armor" - -import ( - "bufio" - "bytes" - "encoding/base64" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" -) - -// A Block represents an OpenPGP armored structure. -// -// The encoded form is: -// -// -----BEGIN Type----- -// Headers -// -// base64-encoded Bytes -// '=' base64 encoded checksum (optional) not checked anymore -// -----END Type----- -// -// where Headers is a possibly empty sequence of Key: Value lines. -// -// Since the armored data can be very large, this package presents a streaming -// interface. -type Block struct { - Type string // The type, taken from the preamble (i.e. "PGP SIGNATURE"). - Header map[string]string // Optional headers. - Body io.Reader // A Reader from which the contents can be read - lReader lineReader - oReader openpgpReader -} - -var ArmorCorrupt error = errors.StructuralError("armor invalid") - -var armorStart = []byte("-----BEGIN ") -var armorEnd = []byte("-----END ") -var armorEndOfLine = []byte("-----") - -// lineReader wraps a line based reader. It watches for the end of an armor block -type lineReader struct { - in *bufio.Reader - buf []byte - eof bool -} - -func (l *lineReader) Read(p []byte) (n int, err error) { - if l.eof { - return 0, io.EOF - } - - if len(l.buf) > 0 { - n = copy(p, l.buf) - l.buf = l.buf[n:] - return - } - - line, isPrefix, err := l.in.ReadLine() - if err != nil { - return - } - if isPrefix { - return 0, ArmorCorrupt - } - - if bytes.HasPrefix(line, armorEnd) { - l.eof = true - return 0, io.EOF - } - - if len(line) == 5 && line[0] == '=' { - // This is the checksum line - // Don't check the checksum - - l.eof = true - return 0, io.EOF - } - - if len(line) > 96 { - return 0, ArmorCorrupt - } - - n = copy(p, line) - bytesToSave := len(line) - n - if bytesToSave > 0 { - if cap(l.buf) < bytesToSave { - l.buf = make([]byte, 0, bytesToSave) - } - l.buf = l.buf[0:bytesToSave] - copy(l.buf, line[n:]) - } - - return -} - -// openpgpReader passes Read calls to the underlying base64 decoder. -type openpgpReader struct { - lReader *lineReader - b64Reader io.Reader -} - -func (r *openpgpReader) Read(p []byte) (n int, err error) { - n, err = r.b64Reader.Read(p) - return -} - -// Decode reads a PGP armored block from the given Reader. It will ignore -// leading garbage. If it doesn't find a block, it will return nil, io.EOF. The -// given Reader is not usable after calling this function: an arbitrary amount -// of data may have been read past the end of the block. -func Decode(in io.Reader) (p *Block, err error) { - r := bufio.NewReaderSize(in, 100) - var line []byte - ignoreNext := false - -TryNextBlock: - p = nil - - // Skip leading garbage - for { - ignoreThis := ignoreNext - line, ignoreNext, err = r.ReadLine() - if err != nil { - return - } - if ignoreNext || ignoreThis { - continue - } - line = bytes.TrimSpace(line) - if len(line) > len(armorStart)+len(armorEndOfLine) && bytes.HasPrefix(line, armorStart) { - break - } - } - - p = new(Block) - p.Type = string(line[len(armorStart) : len(line)-len(armorEndOfLine)]) - p.Header = make(map[string]string) - nextIsContinuation := false - var lastKey string - - // Read headers - for { - isContinuation := nextIsContinuation - line, nextIsContinuation, err = r.ReadLine() - if err != nil { - p = nil - return - } - if isContinuation { - p.Header[lastKey] += string(line) - continue - } - line = bytes.TrimSpace(line) - if len(line) == 0 { - break - } - - i := bytes.Index(line, []byte(":")) - if i == -1 { - goto TryNextBlock - } - lastKey = string(line[:i]) - var value string - if len(line) > i+2 { - value = string(line[i+2:]) - } - p.Header[lastKey] = value - } - - p.lReader.in = r - p.oReader.lReader = &p.lReader - p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader) - p.Body = &p.oReader - - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/armor/encode.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/armor/encode.go deleted file mode 100644 index 550efddf056..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/armor/encode.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package armor - -import ( - "encoding/base64" - "io" - "sort" -) - -var armorHeaderSep = []byte(": ") -var blockEnd = []byte("\n=") -var newline = []byte("\n") -var armorEndOfLineOut = []byte("-----\n") - -const crc24Init = 0xb704ce -const crc24Poly = 0x1864cfb - -// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1 -func crc24(crc uint32, d []byte) uint32 { - for _, b := range d { - crc ^= uint32(b) << 16 - for i := 0; i < 8; i++ { - crc <<= 1 - if crc&0x1000000 != 0 { - crc ^= crc24Poly - } - } - } - return crc -} - -// writeSlices writes its arguments to the given Writer. -func writeSlices(out io.Writer, slices ...[]byte) (err error) { - for _, s := range slices { - _, err = out.Write(s) - if err != nil { - return err - } - } - return -} - -// lineBreaker breaks data across several lines, all of the same byte length -// (except possibly the last). Lines are broken with a single '\n'. -type lineBreaker struct { - lineLength int - line []byte - used int - out io.Writer - haveWritten bool -} - -func newLineBreaker(out io.Writer, lineLength int) *lineBreaker { - return &lineBreaker{ - lineLength: lineLength, - line: make([]byte, lineLength), - used: 0, - out: out, - } -} - -func (l *lineBreaker) Write(b []byte) (n int, err error) { - n = len(b) - - if n == 0 { - return - } - - if l.used == 0 && l.haveWritten { - _, err = l.out.Write([]byte{'\n'}) - if err != nil { - return - } - } - - if l.used+len(b) < l.lineLength { - l.used += copy(l.line[l.used:], b) - return - } - - l.haveWritten = true - _, err = l.out.Write(l.line[0:l.used]) - if err != nil { - return - } - excess := l.lineLength - l.used - l.used = 0 - - _, err = l.out.Write(b[0:excess]) - if err != nil { - return - } - - _, err = l.Write(b[excess:]) - return -} - -func (l *lineBreaker) Close() (err error) { - if l.used > 0 { - _, err = l.out.Write(l.line[0:l.used]) - if err != nil { - return - } - } - - return -} - -// encoding keeps track of a running CRC24 over the data which has been written -// to it and outputs a OpenPGP checksum when closed, followed by an armor -// trailer. -// -// It's built into a stack of io.Writers: -// -// encoding -> base64 encoder -> lineBreaker -> out -type encoding struct { - out io.Writer - breaker *lineBreaker - b64 io.WriteCloser - crc uint32 - crcEnabled bool - blockType []byte -} - -func (e *encoding) Write(data []byte) (n int, err error) { - if e.crcEnabled { - e.crc = crc24(e.crc, data) - } - return e.b64.Write(data) -} - -func (e *encoding) Close() (err error) { - err = e.b64.Close() - if err != nil { - return - } - e.breaker.Close() - - if e.crcEnabled { - var checksumBytes [3]byte - checksumBytes[0] = byte(e.crc >> 16) - checksumBytes[1] = byte(e.crc >> 8) - checksumBytes[2] = byte(e.crc) - - var b64ChecksumBytes [4]byte - base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:]) - - return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine) - } - return writeSlices(e.out, newline, armorEnd, e.blockType, armorEndOfLine) -} - -func encode(out io.Writer, blockType string, headers map[string]string, checksum bool) (w io.WriteCloser, err error) { - bType := []byte(blockType) - err = writeSlices(out, armorStart, bType, armorEndOfLineOut) - if err != nil { - return - } - - keys := make([]string, len(headers)) - i := 0 - for k := range headers { - keys[i] = k - i++ - } - sort.Strings(keys) - for _, k := range keys { - err = writeSlices(out, []byte(k), armorHeaderSep, []byte(headers[k]), newline) - if err != nil { - return - } - } - - _, err = out.Write(newline) - if err != nil { - return - } - - e := &encoding{ - out: out, - breaker: newLineBreaker(out, 64), - blockType: bType, - crc: crc24Init, - crcEnabled: checksum, - } - e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker) - return e, nil -} - -// Encode returns a WriteCloser which will encode the data written to it in -// OpenPGP armor. -func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) { - return encode(out, blockType, headers, true) -} - -// EncodeWithChecksumOption returns a WriteCloser which will encode the data written to it in -// OpenPGP armor and provides the option to include a checksum. -// When forming ASCII Armor, the CRC24 footer SHOULD NOT be generated, -// unless interoperability with implementations that require the CRC24 footer -// to be present is a concern. -func EncodeWithChecksumOption(out io.Writer, blockType string, headers map[string]string, doChecksum bool) (w io.WriteCloser, err error) { - return encode(out, blockType, headers, doChecksum) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/canonical_text.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/canonical_text.go deleted file mode 100644 index 5b40e1375de..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/canonical_text.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package openpgp - -import ( - "hash" - "io" -) - -// NewCanonicalTextHash reformats text written to it into the canonical -// form and then applies the hash h. See RFC 4880, section 5.2.1. -func NewCanonicalTextHash(h hash.Hash) hash.Hash { - return &canonicalTextHash{h, 0} -} - -type canonicalTextHash struct { - h hash.Hash - s int -} - -var newline = []byte{'\r', '\n'} - -func writeCanonical(cw io.Writer, buf []byte, s *int) (int, error) { - start := 0 - for i, c := range buf { - switch *s { - case 0: - if c == '\r' { - *s = 1 - } else if c == '\n' { - if _, err := cw.Write(buf[start:i]); err != nil { - return 0, err - } - if _, err := cw.Write(newline); err != nil { - return 0, err - } - start = i + 1 - } - case 1: - *s = 0 - } - } - - if _, err := cw.Write(buf[start:]); err != nil { - return 0, err - } - return len(buf), nil -} - -func (cth *canonicalTextHash) Write(buf []byte) (int, error) { - return writeCanonical(cth.h, buf, &cth.s) -} - -func (cth *canonicalTextHash) Sum(in []byte) []byte { - return cth.h.Sum(in) -} - -func (cth *canonicalTextHash) Reset() { - cth.h.Reset() - cth.s = 0 -} - -func (cth *canonicalTextHash) Size() int { - return cth.h.Size() -} - -func (cth *canonicalTextHash) BlockSize() int { - return cth.h.BlockSize() -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/ecdh/ecdh.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/ecdh/ecdh.go deleted file mode 100644 index db8fb163b61..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/ecdh/ecdh.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package ecdh implements ECDH encryption, suitable for OpenPGP, -// as specified in RFC 6637, section 8. -package ecdh - -import ( - "bytes" - "errors" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/aes/keywrap" - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" - "github.com/ProtonMail/go-crypto/openpgp/internal/ecc" -) - -type KDF struct { - Hash algorithm.Hash - Cipher algorithm.Cipher -} - -type PublicKey struct { - curve ecc.ECDHCurve - Point []byte - KDF -} - -type PrivateKey struct { - PublicKey - D []byte -} - -func NewPublicKey(curve ecc.ECDHCurve, kdfHash algorithm.Hash, kdfCipher algorithm.Cipher) *PublicKey { - return &PublicKey{ - curve: curve, - KDF: KDF{ - Hash: kdfHash, - Cipher: kdfCipher, - }, - } -} - -func NewPrivateKey(key PublicKey) *PrivateKey { - return &PrivateKey{ - PublicKey: key, - } -} - -func (pk *PublicKey) GetCurve() ecc.ECDHCurve { - return pk.curve -} - -func (pk *PublicKey) MarshalPoint() []byte { - return pk.curve.MarshalBytePoint(pk.Point) -} - -func (pk *PublicKey) UnmarshalPoint(p []byte) error { - pk.Point = pk.curve.UnmarshalBytePoint(p) - if pk.Point == nil { - return errors.New("ecdh: failed to parse EC point") - } - return nil -} - -func (sk *PrivateKey) MarshalByteSecret() []byte { - return sk.curve.MarshalByteSecret(sk.D) -} - -func (sk *PrivateKey) UnmarshalByteSecret(d []byte) error { - sk.D = sk.curve.UnmarshalByteSecret(d) - - if sk.D == nil { - return errors.New("ecdh: failed to parse scalar") - } - return nil -} - -func GenerateKey(rand io.Reader, c ecc.ECDHCurve, kdf KDF) (priv *PrivateKey, err error) { - priv = new(PrivateKey) - priv.PublicKey.curve = c - priv.PublicKey.KDF = kdf - priv.PublicKey.Point, priv.D, err = c.GenerateECDH(rand) - return -} - -func Encrypt(random io.Reader, pub *PublicKey, msg, curveOID, fingerprint []byte) (vsG, c []byte, err error) { - if len(msg) > 40 { - return nil, nil, errors.New("ecdh: message too long") - } - // the sender MAY use 21, 13, and 5 bytes of padding for AES-128, - // AES-192, and AES-256, respectively, to provide the same number of - // octets, 40 total, as an input to the key wrapping method. - padding := make([]byte, 40-len(msg)) - for i := range padding { - padding[i] = byte(40 - len(msg)) - } - m := append(msg, padding...) - - ephemeral, zb, err := pub.curve.Encaps(random, pub.Point) - if err != nil { - return nil, nil, err - } - - vsG = pub.curve.MarshalBytePoint(ephemeral) - - z, err := buildKey(pub, zb, curveOID, fingerprint, false, false) - if err != nil { - return nil, nil, err - } - - if c, err = keywrap.Wrap(z, m); err != nil { - return nil, nil, err - } - - return vsG, c, nil - -} - -func Decrypt(priv *PrivateKey, vsG, c, curveOID, fingerprint []byte) (msg []byte, err error) { - var m []byte - zb, err := priv.PublicKey.curve.Decaps(priv.curve.UnmarshalBytePoint(vsG), priv.D) - - // Try buildKey three times to workaround an old bug, see comments in buildKey. - for i := 0; i < 3; i++ { - var z []byte - // RFC6637 §8: "Compute Z = KDF( S, Z_len, Param );" - z, err = buildKey(&priv.PublicKey, zb, curveOID, fingerprint, i == 1, i == 2) - if err != nil { - return nil, err - } - - // RFC6637 §8: "Compute C = AESKeyWrap( Z, c ) as per [RFC3394]" - m, err = keywrap.Unwrap(z, c) - if err == nil { - break - } - } - - // Only return an error after we've tried all (required) variants of buildKey. - if err != nil { - return nil, err - } - - // RFC6637 §8: "m = symm_alg_ID || session key || checksum || pkcs5_padding" - // The last byte should be the length of the padding, as per PKCS5; strip it off. - return m[:len(m)-int(m[len(m)-1])], nil -} - -func buildKey(pub *PublicKey, zb []byte, curveOID, fingerprint []byte, stripLeading, stripTrailing bool) ([]byte, error) { - // Param = curve_OID_len || curve_OID || public_key_alg_ID || 03 - // || 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap - // || "Anonymous Sender " || recipient_fingerprint; - param := new(bytes.Buffer) - if _, err := param.Write(curveOID); err != nil { - return nil, err - } - algKDF := []byte{18, 3, 1, pub.KDF.Hash.Id(), pub.KDF.Cipher.Id()} - if _, err := param.Write(algKDF); err != nil { - return nil, err - } - if _, err := param.Write([]byte("Anonymous Sender ")); err != nil { - return nil, err - } - if _, err := param.Write(fingerprint[:]); err != nil { - return nil, err - } - - // MB = Hash ( 00 || 00 || 00 || 01 || ZB || Param ); - h := pub.KDF.Hash.New() - if _, err := h.Write([]byte{0x0, 0x0, 0x0, 0x1}); err != nil { - return nil, err - } - zbLen := len(zb) - i := 0 - j := zbLen - 1 - if stripLeading { - // Work around old go crypto bug where the leading zeros are missing. - for i < zbLen && zb[i] == 0 { - i++ - } - } - if stripTrailing { - // Work around old OpenPGP.js bug where insignificant trailing zeros in - // this little-endian number are missing. - // (See https://github.com/openpgpjs/openpgpjs/pull/853.) - for j >= 0 && zb[j] == 0 { - j-- - } - } - if _, err := h.Write(zb[i : j+1]); err != nil { - return nil, err - } - if _, err := h.Write(param.Bytes()); err != nil { - return nil, err - } - mb := h.Sum(nil) - - return mb[:pub.KDF.Cipher.KeySize()], nil // return oBits leftmost bits of MB. - -} - -func Validate(priv *PrivateKey) error { - return priv.curve.ValidateECDH(priv.Point, priv.D) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/ecdsa/ecdsa.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/ecdsa/ecdsa.go deleted file mode 100644 index f94ae1b2f50..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/ecdsa/ecdsa.go +++ /dev/null @@ -1,80 +0,0 @@ -// Package ecdsa implements ECDSA signature, suitable for OpenPGP, -// as specified in RFC 6637, section 5. -package ecdsa - -import ( - "errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/ecc" - "io" - "math/big" -) - -type PublicKey struct { - X, Y *big.Int - curve ecc.ECDSACurve -} - -type PrivateKey struct { - PublicKey - D *big.Int -} - -func NewPublicKey(curve ecc.ECDSACurve) *PublicKey { - return &PublicKey{ - curve: curve, - } -} - -func NewPrivateKey(key PublicKey) *PrivateKey { - return &PrivateKey{ - PublicKey: key, - } -} - -func (pk *PublicKey) GetCurve() ecc.ECDSACurve { - return pk.curve -} - -func (pk *PublicKey) MarshalPoint() []byte { - return pk.curve.MarshalIntegerPoint(pk.X, pk.Y) -} - -func (pk *PublicKey) UnmarshalPoint(p []byte) error { - pk.X, pk.Y = pk.curve.UnmarshalIntegerPoint(p) - if pk.X == nil { - return errors.New("ecdsa: failed to parse EC point") - } - return nil -} - -func (sk *PrivateKey) MarshalIntegerSecret() []byte { - return sk.curve.MarshalIntegerSecret(sk.D) -} - -func (sk *PrivateKey) UnmarshalIntegerSecret(d []byte) error { - sk.D = sk.curve.UnmarshalIntegerSecret(d) - - if sk.D == nil { - return errors.New("ecdsa: failed to parse scalar") - } - return nil -} - -func GenerateKey(rand io.Reader, c ecc.ECDSACurve) (priv *PrivateKey, err error) { - priv = new(PrivateKey) - priv.PublicKey.curve = c - priv.PublicKey.X, priv.PublicKey.Y, priv.D, err = c.GenerateECDSA(rand) - return -} - -func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) { - return priv.PublicKey.curve.Sign(rand, priv.X, priv.Y, priv.D, hash) -} - -func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool { - return pub.curve.Verify(pub.X, pub.Y, hash, r, s) -} - -func Validate(priv *PrivateKey) error { - return priv.curve.ValidateECDSA(priv.X, priv.Y, priv.D.Bytes()) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/ed25519/ed25519.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/ed25519/ed25519.go deleted file mode 100644 index 6abdf7c4466..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/ed25519/ed25519.go +++ /dev/null @@ -1,115 +0,0 @@ -// Package ed25519 implements the ed25519 signature algorithm for OpenPGP -// as defined in the Open PGP crypto refresh. -package ed25519 - -import ( - "crypto/subtle" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - ed25519lib "github.com/cloudflare/circl/sign/ed25519" -) - -const ( - // PublicKeySize is the size, in bytes, of public keys in this package. - PublicKeySize = ed25519lib.PublicKeySize - // SeedSize is the size, in bytes, of private key seeds. - // The private key representation used by RFC 8032. - SeedSize = ed25519lib.SeedSize - // SignatureSize is the size, in bytes, of signatures generated and verified by this package. - SignatureSize = ed25519lib.SignatureSize -) - -type PublicKey struct { - // Point represents the elliptic curve point of the public key. - Point []byte -} - -type PrivateKey struct { - PublicKey - // Key the private key representation by RFC 8032, - // encoded as seed | pub key point. - Key []byte -} - -// NewPublicKey creates a new empty ed25519 public key. -func NewPublicKey() *PublicKey { - return &PublicKey{} -} - -// NewPrivateKey creates a new empty private key referencing the public key. -func NewPrivateKey(key PublicKey) *PrivateKey { - return &PrivateKey{ - PublicKey: key, - } -} - -// Seed returns the ed25519 private key secret seed. -// The private key representation by RFC 8032. -func (pk *PrivateKey) Seed() []byte { - return pk.Key[:SeedSize] -} - -// MarshalByteSecret returns the underlying 32 byte seed of the private key. -func (pk *PrivateKey) MarshalByteSecret() []byte { - return pk.Seed() -} - -// UnmarshalByteSecret computes the private key from the secret seed -// and stores it in the private key object. -func (sk *PrivateKey) UnmarshalByteSecret(seed []byte) error { - sk.Key = ed25519lib.NewKeyFromSeed(seed) - return nil -} - -// GenerateKey generates a fresh private key with the provided randomness source. -func GenerateKey(rand io.Reader) (*PrivateKey, error) { - publicKey, privateKey, err := ed25519lib.GenerateKey(rand) - if err != nil { - return nil, err - } - privateKeyOut := new(PrivateKey) - privateKeyOut.PublicKey.Point = publicKey[:] - privateKeyOut.Key = privateKey[:] - return privateKeyOut, nil -} - -// Sign signs a message with the ed25519 algorithm. -// priv MUST be a valid key! Check this with Validate() before use. -func Sign(priv *PrivateKey, message []byte) ([]byte, error) { - return ed25519lib.Sign(priv.Key, message), nil -} - -// Verify verifies an ed25519 signature. -func Verify(pub *PublicKey, message []byte, signature []byte) bool { - return ed25519lib.Verify(pub.Point, message, signature) -} - -// Validate checks if the ed25519 private key is valid. -func Validate(priv *PrivateKey) error { - expectedPrivateKey := ed25519lib.NewKeyFromSeed(priv.Seed()) - if subtle.ConstantTimeCompare(priv.Key, expectedPrivateKey) == 0 { - return errors.KeyInvalidError("ed25519: invalid ed25519 secret") - } - if subtle.ConstantTimeCompare(priv.PublicKey.Point, expectedPrivateKey[SeedSize:]) == 0 { - return errors.KeyInvalidError("ed25519: invalid ed25519 public key") - } - return nil -} - -// ENCODING/DECODING signature: - -// WriteSignature encodes and writes an ed25519 signature to writer. -func WriteSignature(writer io.Writer, signature []byte) error { - _, err := writer.Write(signature) - return err -} - -// ReadSignature decodes an ed25519 signature from a reader. -func ReadSignature(reader io.Reader) ([]byte, error) { - signature := make([]byte, SignatureSize) - if _, err := io.ReadFull(reader, signature); err != nil { - return nil, err - } - return signature, nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/ed448/ed448.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/ed448/ed448.go deleted file mode 100644 index b11fb4fb179..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/ed448/ed448.go +++ /dev/null @@ -1,119 +0,0 @@ -// Package ed448 implements the ed448 signature algorithm for OpenPGP -// as defined in the Open PGP crypto refresh. -package ed448 - -import ( - "crypto/subtle" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - ed448lib "github.com/cloudflare/circl/sign/ed448" -) - -const ( - // PublicKeySize is the size, in bytes, of public keys in this package. - PublicKeySize = ed448lib.PublicKeySize - // SeedSize is the size, in bytes, of private key seeds. - // The private key representation used by RFC 8032. - SeedSize = ed448lib.SeedSize - // SignatureSize is the size, in bytes, of signatures generated and verified by this package. - SignatureSize = ed448lib.SignatureSize -) - -type PublicKey struct { - // Point represents the elliptic curve point of the public key. - Point []byte -} - -type PrivateKey struct { - PublicKey - // Key the private key representation by RFC 8032, - // encoded as seed | public key point. - Key []byte -} - -// NewPublicKey creates a new empty ed448 public key. -func NewPublicKey() *PublicKey { - return &PublicKey{} -} - -// NewPrivateKey creates a new empty private key referencing the public key. -func NewPrivateKey(key PublicKey) *PrivateKey { - return &PrivateKey{ - PublicKey: key, - } -} - -// Seed returns the ed448 private key secret seed. -// The private key representation by RFC 8032. -func (pk *PrivateKey) Seed() []byte { - return pk.Key[:SeedSize] -} - -// MarshalByteSecret returns the underlying seed of the private key. -func (pk *PrivateKey) MarshalByteSecret() []byte { - return pk.Seed() -} - -// UnmarshalByteSecret computes the private key from the secret seed -// and stores it in the private key object. -func (sk *PrivateKey) UnmarshalByteSecret(seed []byte) error { - sk.Key = ed448lib.NewKeyFromSeed(seed) - return nil -} - -// GenerateKey generates a fresh private key with the provided randomness source. -func GenerateKey(rand io.Reader) (*PrivateKey, error) { - publicKey, privateKey, err := ed448lib.GenerateKey(rand) - if err != nil { - return nil, err - } - privateKeyOut := new(PrivateKey) - privateKeyOut.PublicKey.Point = publicKey[:] - privateKeyOut.Key = privateKey[:] - return privateKeyOut, nil -} - -// Sign signs a message with the ed448 algorithm. -// priv MUST be a valid key! Check this with Validate() before use. -func Sign(priv *PrivateKey, message []byte) ([]byte, error) { - // Ed448 is used with the empty string as a context string. - // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-08#section-13.7 - return ed448lib.Sign(priv.Key, message, ""), nil -} - -// Verify verifies a ed448 signature -func Verify(pub *PublicKey, message []byte, signature []byte) bool { - // Ed448 is used with the empty string as a context string. - // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-08#section-13.7 - return ed448lib.Verify(pub.Point, message, signature, "") -} - -// Validate checks if the ed448 private key is valid -func Validate(priv *PrivateKey) error { - expectedPrivateKey := ed448lib.NewKeyFromSeed(priv.Seed()) - if subtle.ConstantTimeCompare(priv.Key, expectedPrivateKey) == 0 { - return errors.KeyInvalidError("ed448: invalid ed448 secret") - } - if subtle.ConstantTimeCompare(priv.PublicKey.Point, expectedPrivateKey[SeedSize:]) == 0 { - return errors.KeyInvalidError("ed448: invalid ed448 public key") - } - return nil -} - -// ENCODING/DECODING signature: - -// WriteSignature encodes and writes an ed448 signature to writer. -func WriteSignature(writer io.Writer, signature []byte) error { - _, err := writer.Write(signature) - return err -} - -// ReadSignature decodes an ed448 signature from a reader. -func ReadSignature(reader io.Reader) ([]byte, error) { - signature := make([]byte, SignatureSize) - if _, err := io.ReadFull(reader, signature); err != nil { - return nil, err - } - return signature, nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/eddsa/eddsa.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/eddsa/eddsa.go deleted file mode 100644 index 99ecfc7f12d..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/eddsa/eddsa.go +++ /dev/null @@ -1,91 +0,0 @@ -// Package eddsa implements EdDSA signature, suitable for OpenPGP, as specified in -// https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-13.7 -package eddsa - -import ( - "errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/ecc" - "io" -) - -type PublicKey struct { - X []byte - curve ecc.EdDSACurve -} - -type PrivateKey struct { - PublicKey - D []byte -} - -func NewPublicKey(curve ecc.EdDSACurve) *PublicKey { - return &PublicKey{ - curve: curve, - } -} - -func NewPrivateKey(key PublicKey) *PrivateKey { - return &PrivateKey{ - PublicKey: key, - } -} - -func (pk *PublicKey) GetCurve() ecc.EdDSACurve { - return pk.curve -} - -func (pk *PublicKey) MarshalPoint() []byte { - return pk.curve.MarshalBytePoint(pk.X) -} - -func (pk *PublicKey) UnmarshalPoint(x []byte) error { - pk.X = pk.curve.UnmarshalBytePoint(x) - - if pk.X == nil { - return errors.New("eddsa: failed to parse EC point") - } - return nil -} - -func (sk *PrivateKey) MarshalByteSecret() []byte { - return sk.curve.MarshalByteSecret(sk.D) -} - -func (sk *PrivateKey) UnmarshalByteSecret(d []byte) error { - sk.D = sk.curve.UnmarshalByteSecret(d) - - if sk.D == nil { - return errors.New("eddsa: failed to parse scalar") - } - return nil -} - -func GenerateKey(rand io.Reader, c ecc.EdDSACurve) (priv *PrivateKey, err error) { - priv = new(PrivateKey) - priv.PublicKey.curve = c - priv.PublicKey.X, priv.D, err = c.GenerateEdDSA(rand) - return -} - -func Sign(priv *PrivateKey, message []byte) (r, s []byte, err error) { - sig, err := priv.PublicKey.curve.Sign(priv.PublicKey.X, priv.D, message) - if err != nil { - return nil, nil, err - } - - r, s = priv.PublicKey.curve.MarshalSignature(sig) - return -} - -func Verify(pub *PublicKey, message, r, s []byte) bool { - sig := pub.curve.UnmarshalSignature(r, s) - if sig == nil { - return false - } - - return pub.curve.Verify(pub.X, message, sig) -} - -func Validate(priv *PrivateKey) error { - return priv.curve.ValidateEdDSA(priv.PublicKey.X, priv.D) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/elgamal/elgamal.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/elgamal/elgamal.go deleted file mode 100644 index bad27743445..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/elgamal/elgamal.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package elgamal implements ElGamal encryption, suitable for OpenPGP, -// as specified in "A Public-Key Cryptosystem and a Signature Scheme Based on -// Discrete Logarithms," IEEE Transactions on Information Theory, v. IT-31, -// n. 4, 1985, pp. 469-472. -// -// This form of ElGamal embeds PKCS#1 v1.5 padding, which may make it -// unsuitable for other protocols. RSA should be used in preference in any -// case. -package elgamal // import "github.com/ProtonMail/go-crypto/openpgp/elgamal" - -import ( - "crypto/rand" - "crypto/subtle" - "errors" - "io" - "math/big" -) - -// PublicKey represents an ElGamal public key. -type PublicKey struct { - G, P, Y *big.Int -} - -// PrivateKey represents an ElGamal private key. -type PrivateKey struct { - PublicKey - X *big.Int -} - -// Encrypt encrypts the given message to the given public key. The result is a -// pair of integers. Errors can result from reading random, or because msg is -// too large to be encrypted to the public key. -func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err error) { - pLen := (pub.P.BitLen() + 7) / 8 - if len(msg) > pLen-11 { - err = errors.New("elgamal: message too long") - return - } - - // EM = 0x02 || PS || 0x00 || M - em := make([]byte, pLen-1) - em[0] = 2 - ps, mm := em[1:len(em)-len(msg)-1], em[len(em)-len(msg):] - err = nonZeroRandomBytes(ps, random) - if err != nil { - return - } - em[len(em)-len(msg)-1] = 0 - copy(mm, msg) - - m := new(big.Int).SetBytes(em) - - k, err := rand.Int(random, pub.P) - if err != nil { - return - } - - c1 = new(big.Int).Exp(pub.G, k, pub.P) - s := new(big.Int).Exp(pub.Y, k, pub.P) - c2 = s.Mul(s, m) - c2.Mod(c2, pub.P) - - return -} - -// Decrypt takes two integers, resulting from an ElGamal encryption, and -// returns the plaintext of the message. An error can result only if the -// ciphertext is invalid. Users should keep in mind that this is a padding -// oracle and thus, if exposed to an adaptive chosen ciphertext attack, can -// be used to break the cryptosystem. See “Chosen Ciphertext Attacks -// Against Protocols Based on the RSA Encryption Standard PKCS #1”, Daniel -// Bleichenbacher, Advances in Cryptology (Crypto '98), -func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) { - s := new(big.Int).Exp(c1, priv.X, priv.P) - if s.ModInverse(s, priv.P) == nil { - return nil, errors.New("elgamal: invalid private key") - } - s.Mul(s, c2) - s.Mod(s, priv.P) - em := s.Bytes() - - firstByteIsTwo := subtle.ConstantTimeByteEq(em[0], 2) - - // The remainder of the plaintext must be a string of non-zero random - // octets, followed by a 0, followed by the message. - // lookingForIndex: 1 iff we are still looking for the zero. - // index: the offset of the first zero byte. - var lookingForIndex, index int - lookingForIndex = 1 - - for i := 1; i < len(em); i++ { - equals0 := subtle.ConstantTimeByteEq(em[i], 0) - index = subtle.ConstantTimeSelect(lookingForIndex&equals0, i, index) - lookingForIndex = subtle.ConstantTimeSelect(equals0, 0, lookingForIndex) - } - - if firstByteIsTwo != 1 || lookingForIndex != 0 || index < 9 { - return nil, errors.New("elgamal: decryption error") - } - return em[index+1:], nil -} - -// nonZeroRandomBytes fills the given slice with non-zero random octets. -func nonZeroRandomBytes(s []byte, rand io.Reader) (err error) { - _, err = io.ReadFull(rand, s) - if err != nil { - return - } - - for i := 0; i < len(s); i++ { - for s[i] == 0 { - _, err = io.ReadFull(rand, s[i:i+1]) - if err != nil { - return - } - } - } - - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/errors/errors.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/errors/errors.go deleted file mode 100644 index e44b45734d4..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/errors/errors.go +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package errors contains common error types for the OpenPGP packages. -package errors // import "github.com/ProtonMail/go-crypto/openpgp/errors" - -import ( - "fmt" - "strconv" -) - -var ( - // ErrDecryptSessionKeyParsing is a generic error message for parsing errors in decrypted data - // to reduce the risk of oracle attacks. - ErrDecryptSessionKeyParsing = DecryptWithSessionKeyError("parsing error") - // ErrAEADTagVerification is returned if one of the tag verifications in SEIPDv2 fails - ErrAEADTagVerification error = DecryptWithSessionKeyError("AEAD tag verification failed") - // ErrMDCHashMismatch - ErrMDCHashMismatch error = SignatureError("MDC hash mismatch") - // ErrMDCMissing - ErrMDCMissing error = SignatureError("MDC packet not found") -) - -// A StructuralError is returned when OpenPGP data is found to be syntactically -// invalid. -type StructuralError string - -func (s StructuralError) Error() string { - return "openpgp: invalid data: " + string(s) -} - -// A DecryptWithSessionKeyError is returned when a failure occurs when reading from symmetrically decrypted data or -// an authentication tag verification fails. -// Such an error indicates that the supplied session key is likely wrong or the data got corrupted. -type DecryptWithSessionKeyError string - -func (s DecryptWithSessionKeyError) Error() string { - return "openpgp: decryption with session key failed: " + string(s) -} - -// HandleSensitiveParsingError handles parsing errors when reading data from potentially decrypted data. -// The function makes parsing errors generic to reduce the risk of oracle attacks in SEIPDv1. -func HandleSensitiveParsingError(err error, decrypted bool) error { - if !decrypted { - // Data was not encrypted so we return the inner error. - return err - } - // The data is read from a stream that decrypts using a session key; - // therefore, we need to handle parsing errors appropriately. - // This is essential to mitigate the risk of oracle attacks. - if decError, ok := err.(*DecryptWithSessionKeyError); ok { - return decError - } - if decError, ok := err.(DecryptWithSessionKeyError); ok { - return decError - } - return ErrDecryptSessionKeyParsing -} - -// UnsupportedError indicates that, although the OpenPGP data is valid, it -// makes use of currently unimplemented features. -type UnsupportedError string - -func (s UnsupportedError) Error() string { - return "openpgp: unsupported feature: " + string(s) -} - -// InvalidArgumentError indicates that the caller is in error and passed an -// incorrect value. -type InvalidArgumentError string - -func (i InvalidArgumentError) Error() string { - return "openpgp: invalid argument: " + string(i) -} - -// SignatureError indicates that a syntactically valid signature failed to -// validate. -type SignatureError string - -func (b SignatureError) Error() string { - return "openpgp: invalid signature: " + string(b) -} - -type signatureExpiredError int - -func (se signatureExpiredError) Error() string { - return "openpgp: signature expired" -} - -var ErrSignatureExpired error = signatureExpiredError(0) - -type keyExpiredError int - -func (ke keyExpiredError) Error() string { - return "openpgp: key expired" -} - -var ErrSignatureOlderThanKey error = signatureOlderThanKeyError(0) - -type signatureOlderThanKeyError int - -func (ske signatureOlderThanKeyError) Error() string { - return "openpgp: signature is older than the key" -} - -var ErrKeyExpired error = keyExpiredError(0) - -type keyIncorrectError int - -func (ki keyIncorrectError) Error() string { - return "openpgp: incorrect key" -} - -var ErrKeyIncorrect error = keyIncorrectError(0) - -// KeyInvalidError indicates that the public key parameters are invalid -// as they do not match the private ones -type KeyInvalidError string - -func (e KeyInvalidError) Error() string { - return "openpgp: invalid key: " + string(e) -} - -type unknownIssuerError int - -func (unknownIssuerError) Error() string { - return "openpgp: signature made by unknown entity" -} - -var ErrUnknownIssuer error = unknownIssuerError(0) - -type keyRevokedError int - -func (keyRevokedError) Error() string { - return "openpgp: signature made by revoked key" -} - -var ErrKeyRevoked error = keyRevokedError(0) - -type WeakAlgorithmError string - -func (e WeakAlgorithmError) Error() string { - return "openpgp: weak algorithms are rejected: " + string(e) -} - -type UnknownPacketTypeError uint8 - -func (upte UnknownPacketTypeError) Error() string { - return "openpgp: unknown packet type: " + strconv.Itoa(int(upte)) -} - -type CriticalUnknownPacketTypeError uint8 - -func (upte CriticalUnknownPacketTypeError) Error() string { - return "openpgp: unknown critical packet type: " + strconv.Itoa(int(upte)) -} - -// AEADError indicates that there is a problem when initializing or using a -// AEAD instance, configuration struct, nonces or index values. -type AEADError string - -func (ae AEADError) Error() string { - return "openpgp: aead error: " + string(ae) -} - -// ErrDummyPrivateKey results when operations are attempted on a private key -// that is just a dummy key. See -// https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;h=fe55ae16ab4e26d8356dc574c9e8bc935e71aef1;hb=23191d7851eae2217ecdac6484349849a24fd94a#l1109 -type ErrDummyPrivateKey string - -func (dke ErrDummyPrivateKey) Error() string { - return "openpgp: s2k GNU dummy key: " + string(dke) -} - -// ErrMalformedMessage results when the packet sequence is incorrect -type ErrMalformedMessage string - -func (dke ErrMalformedMessage) Error() string { - return "openpgp: malformed message " + string(dke) -} - -// ErrEncryptionKeySelection is returned if encryption key selection fails (v2 API). -type ErrEncryptionKeySelection struct { - PrimaryKeyId string - PrimaryKeyErr error - EncSelectionKeyId *string - EncSelectionErr error -} - -func (eks ErrEncryptionKeySelection) Error() string { - prefix := fmt.Sprintf("openpgp: key selection for primary key %s:", eks.PrimaryKeyId) - if eks.PrimaryKeyErr != nil { - return fmt.Sprintf("%s invalid primary key: %s", prefix, eks.PrimaryKeyErr) - } - if eks.EncSelectionKeyId != nil { - return fmt.Sprintf("%s invalid encryption key %s: %s", prefix, *eks.EncSelectionKeyId, eks.EncSelectionErr) - } - return fmt.Sprintf("%s no encryption key: %s", prefix, eks.EncSelectionErr) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/hash.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/hash.go deleted file mode 100644 index 526bd7777f8..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/hash.go +++ /dev/null @@ -1,24 +0,0 @@ -package openpgp - -import ( - "crypto" - - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" -) - -// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP -// hash id. -func HashIdToHash(id byte) (h crypto.Hash, ok bool) { - return algorithm.HashIdToHash(id) -} - -// HashIdToString returns the name of the hash function corresponding to the -// given OpenPGP hash id. -func HashIdToString(id byte) (name string, ok bool) { - return algorithm.HashIdToString(id) -} - -// HashToHashId returns an OpenPGP hash id which corresponds the given Hash. -func HashToHashId(h crypto.Hash) (id byte, ok bool) { - return algorithm.HashToHashId(h) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/algorithm/aead.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/algorithm/aead.go deleted file mode 100644 index d0670651866..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/algorithm/aead.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2019 ProtonTech AG - -package algorithm - -import ( - "crypto/cipher" - "github.com/ProtonMail/go-crypto/eax" - "github.com/ProtonMail/go-crypto/ocb" -) - -// AEADMode defines the Authenticated Encryption with Associated Data mode of -// operation. -type AEADMode uint8 - -// Supported modes of operation (see RFC4880bis [EAX] and RFC7253) -const ( - AEADModeEAX = AEADMode(1) - AEADModeOCB = AEADMode(2) - AEADModeGCM = AEADMode(3) -) - -// TagLength returns the length in bytes of authentication tags. -func (mode AEADMode) TagLength() int { - switch mode { - case AEADModeEAX: - return 16 - case AEADModeOCB: - return 16 - case AEADModeGCM: - return 16 - default: - return 0 - } -} - -// NonceLength returns the length in bytes of nonces. -func (mode AEADMode) NonceLength() int { - switch mode { - case AEADModeEAX: - return 16 - case AEADModeOCB: - return 15 - case AEADModeGCM: - return 12 - default: - return 0 - } -} - -// New returns a fresh instance of the given mode -func (mode AEADMode) New(block cipher.Block) (alg cipher.AEAD) { - var err error - switch mode { - case AEADModeEAX: - alg, err = eax.NewEAX(block) - case AEADModeOCB: - alg, err = ocb.NewOCB(block) - case AEADModeGCM: - alg, err = cipher.NewGCM(block) - } - if err != nil { - panic(err.Error()) - } - return alg -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/algorithm/cipher.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/algorithm/cipher.go deleted file mode 100644 index c76a75bcda5..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/algorithm/cipher.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package algorithm - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/des" - - "golang.org/x/crypto/cast5" -) - -// Cipher is an official symmetric key cipher algorithm. See RFC 4880, -// section 9.2. -type Cipher interface { - // Id returns the algorithm ID, as a byte, of the cipher. - Id() uint8 - // KeySize returns the key size, in bytes, of the cipher. - KeySize() int - // BlockSize returns the block size, in bytes, of the cipher. - BlockSize() int - // New returns a fresh instance of the given cipher. - New(key []byte) cipher.Block -} - -// The following constants mirror the OpenPGP standard (RFC 4880). -const ( - TripleDES = CipherFunction(2) - CAST5 = CipherFunction(3) - AES128 = CipherFunction(7) - AES192 = CipherFunction(8) - AES256 = CipherFunction(9) -) - -// CipherById represents the different block ciphers specified for OpenPGP. See -// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-13 -var CipherById = map[uint8]Cipher{ - TripleDES.Id(): TripleDES, - CAST5.Id(): CAST5, - AES128.Id(): AES128, - AES192.Id(): AES192, - AES256.Id(): AES256, -} - -type CipherFunction uint8 - -// ID returns the algorithm Id, as a byte, of cipher. -func (sk CipherFunction) Id() uint8 { - return uint8(sk) -} - -// KeySize returns the key size, in bytes, of cipher. -func (cipher CipherFunction) KeySize() int { - switch cipher { - case CAST5: - return cast5.KeySize - case AES128: - return 16 - case AES192, TripleDES: - return 24 - case AES256: - return 32 - } - return 0 -} - -// BlockSize returns the block size, in bytes, of cipher. -func (cipher CipherFunction) BlockSize() int { - switch cipher { - case TripleDES: - return des.BlockSize - case CAST5: - return 8 - case AES128, AES192, AES256: - return 16 - } - return 0 -} - -// New returns a fresh instance of the given cipher. -func (cipher CipherFunction) New(key []byte) (block cipher.Block) { - var err error - switch cipher { - case TripleDES: - block, err = des.NewTripleDESCipher(key) - case CAST5: - block, err = cast5.NewCipher(key) - case AES128, AES192, AES256: - block, err = aes.NewCipher(key) - } - if err != nil { - panic(err.Error()) - } - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/algorithm/hash.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/algorithm/hash.go deleted file mode 100644 index d1a00fc7495..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/algorithm/hash.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package algorithm - -import ( - "crypto" - "fmt" - "hash" -) - -// Hash is an official hash function algorithm. See RFC 4880, section 9.4. -type Hash interface { - // Id returns the algorithm ID, as a byte, of Hash. - Id() uint8 - // Available reports whether the given hash function is linked into the binary. - Available() bool - // HashFunc simply returns the value of h so that Hash implements SignerOpts. - HashFunc() crypto.Hash - // New returns a new hash.Hash calculating the given hash function. New - // panics if the hash function is not linked into the binary. - New() hash.Hash - // Size returns the length, in bytes, of a digest resulting from the given - // hash function. It doesn't require that the hash function in question be - // linked into the program. - Size() int - // String is the name of the hash function corresponding to the given - // OpenPGP hash id. - String() string -} - -// The following vars mirror the crypto/Hash supported hash functions. -var ( - SHA1 Hash = cryptoHash{2, crypto.SHA1} - SHA256 Hash = cryptoHash{8, crypto.SHA256} - SHA384 Hash = cryptoHash{9, crypto.SHA384} - SHA512 Hash = cryptoHash{10, crypto.SHA512} - SHA224 Hash = cryptoHash{11, crypto.SHA224} - SHA3_256 Hash = cryptoHash{12, crypto.SHA3_256} - SHA3_512 Hash = cryptoHash{14, crypto.SHA3_512} -) - -// HashById represents the different hash functions specified for OpenPGP. See -// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-14 -var ( - HashById = map[uint8]Hash{ - SHA256.Id(): SHA256, - SHA384.Id(): SHA384, - SHA512.Id(): SHA512, - SHA224.Id(): SHA224, - SHA3_256.Id(): SHA3_256, - SHA3_512.Id(): SHA3_512, - } -) - -// cryptoHash contains pairs relating OpenPGP's hash identifier with -// Go's crypto.Hash type. See RFC 4880, section 9.4. -type cryptoHash struct { - id uint8 - crypto.Hash -} - -// Id returns the algorithm ID, as a byte, of cryptoHash. -func (h cryptoHash) Id() uint8 { - return h.id -} - -var hashNames = map[uint8]string{ - SHA256.Id(): "SHA256", - SHA384.Id(): "SHA384", - SHA512.Id(): "SHA512", - SHA224.Id(): "SHA224", - SHA3_256.Id(): "SHA3-256", - SHA3_512.Id(): "SHA3-512", -} - -func (h cryptoHash) String() string { - s, ok := hashNames[h.id] - if !ok { - panic(fmt.Sprintf("Unsupported hash function %d", h.id)) - } - return s -} - -// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP -// hash id. -func HashIdToHash(id byte) (h crypto.Hash, ok bool) { - if hash, ok := HashById[id]; ok { - return hash.HashFunc(), true - } - return 0, false -} - -// HashIdToHashWithSha1 returns a crypto.Hash which corresponds to the given OpenPGP -// hash id, allowing sha1. -func HashIdToHashWithSha1(id byte) (h crypto.Hash, ok bool) { - if hash, ok := HashById[id]; ok { - return hash.HashFunc(), true - } - - if id == SHA1.Id() { - return SHA1.HashFunc(), true - } - - return 0, false -} - -// HashIdToString returns the name of the hash function corresponding to the -// given OpenPGP hash id. -func HashIdToString(id byte) (name string, ok bool) { - if hash, ok := HashById[id]; ok { - return hash.String(), true - } - return "", false -} - -// HashToHashId returns an OpenPGP hash id which corresponds the given Hash. -func HashToHashId(h crypto.Hash) (id byte, ok bool) { - for id, hash := range HashById { - if hash.HashFunc() == h { - return id, true - } - } - - return 0, false -} - -// HashToHashIdWithSha1 returns an OpenPGP hash id which corresponds the given Hash, -// allowing instances of SHA1 -func HashToHashIdWithSha1(h crypto.Hash) (id byte, ok bool) { - for id, hash := range HashById { - if hash.HashFunc() == h { - return id, true - } - } - - if h == SHA1.HashFunc() { - return SHA1.Id(), true - } - - return 0, false -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/curve25519.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/curve25519.go deleted file mode 100644 index 888767c4e43..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/curve25519.go +++ /dev/null @@ -1,171 +0,0 @@ -// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA. -package ecc - -import ( - "crypto/subtle" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - x25519lib "github.com/cloudflare/circl/dh/x25519" -) - -type curve25519 struct{} - -func NewCurve25519() *curve25519 { - return &curve25519{} -} - -func (c *curve25519) GetCurveName() string { - return "curve25519" -} - -// MarshalBytePoint encodes the public point from native format, adding the prefix. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6 -func (c *curve25519) MarshalBytePoint(point []byte) []byte { - return append([]byte{0x40}, point...) -} - -// UnmarshalBytePoint decodes the public point to native format, removing the prefix. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6 -func (c *curve25519) UnmarshalBytePoint(point []byte) []byte { - if len(point) != x25519lib.Size+1 { - return nil - } - - // Remove prefix - return point[1:] -} - -// MarshalByteSecret encodes the secret scalar from native format. -// Note that the EC secret scalar differs from the definition of public keys in -// [Curve25519] in two ways: (1) the byte-ordering is big-endian, which is -// more uniform with how big integers are represented in OpenPGP, and (2) the -// leading zeros are truncated. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.1 -// Note that leading zero bytes are stripped later when encoding as an MPI. -func (c *curve25519) MarshalByteSecret(secret []byte) []byte { - d := make([]byte, x25519lib.Size) - copyReversed(d, secret) - - // The following ensures that the private key is a number of the form - // 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of - // the curve. - // - // This masking is done internally in the underlying lib and so is unnecessary - // for security, but OpenPGP implementations require that private keys be - // pre-masked. - d[0] &= 127 - d[0] |= 64 - d[31] &= 248 - - return d -} - -// UnmarshalByteSecret decodes the secret scalar from native format. -// Note that the EC secret scalar differs from the definition of public keys in -// [Curve25519] in two ways: (1) the byte-ordering is big-endian, which is -// more uniform with how big integers are represented in OpenPGP, and (2) the -// leading zeros are truncated. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.1 -func (c *curve25519) UnmarshalByteSecret(d []byte) []byte { - if len(d) > x25519lib.Size { - return nil - } - - // Ensure truncated leading bytes are re-added - secret := make([]byte, x25519lib.Size) - copyReversed(secret, d) - - return secret -} - -// generateKeyPairBytes Generates a private-public key-pair. -// 'priv' is a private key; a little-endian scalar belonging to the set -// 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of the -// curve. 'pub' is simply 'priv' * G where G is the base point. -// See https://cr.yp.to/ecdh.html and RFC7748, sec 5. -func (c *curve25519) generateKeyPairBytes(rand io.Reader) (priv, pub x25519lib.Key, err error) { - _, err = io.ReadFull(rand, priv[:]) - if err != nil { - return - } - - x25519lib.KeyGen(&pub, &priv) - return -} - -func (c *curve25519) GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error) { - priv, pub, err := c.generateKeyPairBytes(rand) - if err != nil { - return - } - - return pub[:], priv[:], nil -} - -func (c *genericCurve) MaskSecret(secret []byte) []byte { - return secret -} - -func (c *curve25519) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) { - // RFC6637 §8: "Generate an ephemeral key pair {v, V=vG}" - // ephemeralPrivate corresponds to `v`. - // ephemeralPublic corresponds to `V`. - ephemeralPrivate, ephemeralPublic, err := c.generateKeyPairBytes(rand) - if err != nil { - return nil, nil, err - } - - // RFC6637 §8: "Obtain the authenticated recipient public key R" - // pubKey corresponds to `R`. - var pubKey x25519lib.Key - copy(pubKey[:], point) - - // RFC6637 §8: "Compute the shared point S = vR" - // "VB = convert point V to the octet string" - // sharedPoint corresponds to `VB`. - var sharedPoint x25519lib.Key - x25519lib.Shared(&sharedPoint, &ephemeralPrivate, &pubKey) - - return ephemeralPublic[:], sharedPoint[:], nil -} - -func (c *curve25519) Decaps(vsG, secret []byte) (sharedSecret []byte, err error) { - var ephemeralPublic, decodedPrivate, sharedPoint x25519lib.Key - // RFC6637 §8: "The decryption is the inverse of the method given." - // All quoted descriptions in comments below describe encryption, and - // the reverse is performed. - // vsG corresponds to `VB` in RFC6637 §8 . - - // RFC6637 §8: "VB = convert point V to the octet string" - copy(ephemeralPublic[:], vsG) - - // decodedPrivate corresponds to `r` in RFC6637 §8 . - copy(decodedPrivate[:], secret) - - // RFC6637 §8: "Note that the recipient obtains the shared secret by calculating - // S = rV = rvG, where (r,R) is the recipient's key pair." - // sharedPoint corresponds to `S`. - x25519lib.Shared(&sharedPoint, &decodedPrivate, &ephemeralPublic) - - return sharedPoint[:], nil -} - -func (c *curve25519) ValidateECDH(point []byte, secret []byte) (err error) { - var pk, sk x25519lib.Key - copy(sk[:], secret) - x25519lib.KeyGen(&pk, &sk) - - if subtle.ConstantTimeCompare(point, pk[:]) == 0 { - return errors.KeyInvalidError("ecc: invalid curve25519 public point") - } - - return nil -} - -func copyReversed(out []byte, in []byte) { - l := len(in) - for i := 0; i < l; i++ { - out[i] = in[l-i-1] - } -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/curve_info.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/curve_info.go deleted file mode 100644 index 0da2d0d852e..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/curve_info.go +++ /dev/null @@ -1,143 +0,0 @@ -// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA. -package ecc - -import ( - "bytes" - "crypto/elliptic" - - "github.com/ProtonMail/go-crypto/bitcurves" - "github.com/ProtonMail/go-crypto/brainpool" - "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" -) - -const Curve25519GenName = "Curve25519" - -type CurveInfo struct { - GenName string - Oid *encoding.OID - Curve Curve -} - -var Curves = []CurveInfo{ - { - // NIST P-256 - GenName: "P256", - Oid: encoding.NewOID([]byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}), - Curve: NewGenericCurve(elliptic.P256()), - }, - { - // NIST P-384 - GenName: "P384", - Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x22}), - Curve: NewGenericCurve(elliptic.P384()), - }, - { - // NIST P-521 - GenName: "P521", - Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x23}), - Curve: NewGenericCurve(elliptic.P521()), - }, - { - // SecP256k1 - GenName: "SecP256k1", - Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x0A}), - Curve: NewGenericCurve(bitcurves.S256()), - }, - { - // Curve25519 - GenName: Curve25519GenName, - Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01}), - Curve: NewCurve25519(), - }, - { - // x448 - GenName: "Curve448", - Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x6F}), - Curve: NewX448(), - }, - { - // Ed25519 - GenName: Curve25519GenName, - Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01}), - Curve: NewEd25519(), - }, - { - // Ed448 - GenName: "Curve448", - Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x71}), - Curve: NewEd448(), - }, - { - // BrainpoolP256r1 - GenName: "BrainpoolP256", - Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07}), - Curve: NewGenericCurve(brainpool.P256r1()), - }, - { - // BrainpoolP384r1 - GenName: "BrainpoolP384", - Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B}), - Curve: NewGenericCurve(brainpool.P384r1()), - }, - { - // BrainpoolP512r1 - GenName: "BrainpoolP512", - Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D}), - Curve: NewGenericCurve(brainpool.P512r1()), - }, -} - -func FindByCurve(curve Curve) *CurveInfo { - for _, curveInfo := range Curves { - if curveInfo.Curve.GetCurveName() == curve.GetCurveName() { - return &curveInfo - } - } - return nil -} - -func FindByOid(oid encoding.Field) *CurveInfo { - var rawBytes = oid.Bytes() - for _, curveInfo := range Curves { - if bytes.Equal(curveInfo.Oid.Bytes(), rawBytes) { - return &curveInfo - } - } - return nil -} - -func FindEdDSAByGenName(curveGenName string) EdDSACurve { - for _, curveInfo := range Curves { - if curveInfo.GenName == curveGenName { - curve, ok := curveInfo.Curve.(EdDSACurve) - if ok { - return curve - } - } - } - return nil -} - -func FindECDSAByGenName(curveGenName string) ECDSACurve { - for _, curveInfo := range Curves { - if curveInfo.GenName == curveGenName { - curve, ok := curveInfo.Curve.(ECDSACurve) - if ok { - return curve - } - } - } - return nil -} - -func FindECDHByGenName(curveGenName string) ECDHCurve { - for _, curveInfo := range Curves { - if curveInfo.GenName == curveGenName { - curve, ok := curveInfo.Curve.(ECDHCurve) - if ok { - return curve - } - } - } - return nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/curves.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/curves.go deleted file mode 100644 index 5ed9c93b3d6..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/curves.go +++ /dev/null @@ -1,48 +0,0 @@ -// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA. -package ecc - -import ( - "io" - "math/big" -) - -type Curve interface { - GetCurveName() string -} - -type ECDSACurve interface { - Curve - MarshalIntegerPoint(x, y *big.Int) []byte - UnmarshalIntegerPoint([]byte) (x, y *big.Int) - MarshalIntegerSecret(d *big.Int) []byte - UnmarshalIntegerSecret(d []byte) *big.Int - GenerateECDSA(rand io.Reader) (x, y, secret *big.Int, err error) - Sign(rand io.Reader, x, y, d *big.Int, hash []byte) (r, s *big.Int, err error) - Verify(x, y *big.Int, hash []byte, r, s *big.Int) bool - ValidateECDSA(x, y *big.Int, secret []byte) error -} - -type EdDSACurve interface { - Curve - MarshalBytePoint(x []byte) []byte - UnmarshalBytePoint([]byte) (x []byte) - MarshalByteSecret(d []byte) []byte - UnmarshalByteSecret(d []byte) []byte - MarshalSignature(sig []byte) (r, s []byte) - UnmarshalSignature(r, s []byte) (sig []byte) - GenerateEdDSA(rand io.Reader) (pub, priv []byte, err error) - Sign(publicKey, privateKey, message []byte) (sig []byte, err error) - Verify(publicKey, message, sig []byte) bool - ValidateEdDSA(publicKey, privateKey []byte) (err error) -} -type ECDHCurve interface { - Curve - MarshalBytePoint([]byte) (encoded []byte) - UnmarshalBytePoint(encoded []byte) []byte - MarshalByteSecret(d []byte) []byte - UnmarshalByteSecret(d []byte) []byte - GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error) - Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) - Decaps(ephemeral, secret []byte) (sharedSecret []byte, err error) - ValidateECDH(public []byte, secret []byte) error -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/ed25519.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/ed25519.go deleted file mode 100644 index 5a4c3a8596b..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/ed25519.go +++ /dev/null @@ -1,120 +0,0 @@ -// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA. -package ecc - -import ( - "bytes" - "crypto/subtle" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - ed25519lib "github.com/cloudflare/circl/sign/ed25519" -) - -const ed25519Size = 32 - -type ed25519 struct{} - -func NewEd25519() *ed25519 { - return &ed25519{} -} - -func (c *ed25519) GetCurveName() string { - return "ed25519" -} - -// MarshalBytePoint encodes the public point from native format, adding the prefix. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5 -func (c *ed25519) MarshalBytePoint(x []byte) []byte { - return append([]byte{0x40}, x...) -} - -// UnmarshalBytePoint decodes a point from prefixed format to native. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5 -func (c *ed25519) UnmarshalBytePoint(point []byte) (x []byte) { - if len(point) != ed25519lib.PublicKeySize+1 { - return nil - } - - // Return unprefixed - return point[1:] -} - -// MarshalByteSecret encodes a scalar in native format. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5 -func (c *ed25519) MarshalByteSecret(d []byte) []byte { - return d -} - -// UnmarshalByteSecret decodes a scalar in native format and re-adds the stripped leading zeroes -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5 -func (c *ed25519) UnmarshalByteSecret(s []byte) (d []byte) { - if len(s) > ed25519lib.SeedSize { - return nil - } - - // Handle stripped leading zeroes - d = make([]byte, ed25519lib.SeedSize) - copy(d[ed25519lib.SeedSize-len(s):], s) - return -} - -// MarshalSignature splits a signature in R and S. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.1 -func (c *ed25519) MarshalSignature(sig []byte) (r, s []byte) { - return sig[:ed25519Size], sig[ed25519Size:] -} - -// UnmarshalSignature decodes R and S in the native format, re-adding the stripped leading zeroes -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.1 -func (c *ed25519) UnmarshalSignature(r, s []byte) (sig []byte) { - // Check size - if len(r) > 32 || len(s) > 32 { - return nil - } - - sig = make([]byte, ed25519lib.SignatureSize) - - // Handle stripped leading zeroes - copy(sig[ed25519Size-len(r):ed25519Size], r) - copy(sig[ed25519lib.SignatureSize-len(s):], s) - return sig -} - -func (c *ed25519) GenerateEdDSA(rand io.Reader) (pub, priv []byte, err error) { - pk, sk, err := ed25519lib.GenerateKey(rand) - - if err != nil { - return nil, nil, err - } - - return pk, sk[:ed25519lib.SeedSize], nil -} - -func getEd25519Sk(publicKey, privateKey []byte) ed25519lib.PrivateKey { - privateKeyCap, privateKeyLen, publicKeyLen := cap(privateKey), len(privateKey), len(publicKey) - - if privateKeyCap >= privateKeyLen+publicKeyLen && - bytes.Equal(privateKey[privateKeyLen:privateKeyLen+publicKeyLen], publicKey) { - return privateKey[:privateKeyLen+publicKeyLen] - } - - return append(privateKey[:privateKeyLen:privateKeyLen], publicKey...) -} - -func (c *ed25519) Sign(publicKey, privateKey, message []byte) (sig []byte, err error) { - sig = ed25519lib.Sign(getEd25519Sk(publicKey, privateKey), message) - return sig, nil -} - -func (c *ed25519) Verify(publicKey, message, sig []byte) bool { - return ed25519lib.Verify(publicKey, message, sig) -} - -func (c *ed25519) ValidateEdDSA(publicKey, privateKey []byte) (err error) { - priv := getEd25519Sk(publicKey, privateKey) - expectedPriv := ed25519lib.NewKeyFromSeed(priv.Seed()) - if subtle.ConstantTimeCompare(priv, expectedPriv) == 0 { - return errors.KeyInvalidError("ecc: invalid ed25519 secret") - } - return nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/ed448.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/ed448.go deleted file mode 100644 index b6edda74802..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/ed448.go +++ /dev/null @@ -1,119 +0,0 @@ -// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA. -package ecc - -import ( - "bytes" - "crypto/subtle" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - ed448lib "github.com/cloudflare/circl/sign/ed448" -) - -type ed448 struct{} - -func NewEd448() *ed448 { - return &ed448{} -} - -func (c *ed448) GetCurveName() string { - return "ed448" -} - -// MarshalBytePoint encodes the public point from native format, adding the prefix. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5 -func (c *ed448) MarshalBytePoint(x []byte) []byte { - // Return prefixed - return append([]byte{0x40}, x...) -} - -// UnmarshalBytePoint decodes a point from prefixed format to native. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5 -func (c *ed448) UnmarshalBytePoint(point []byte) (x []byte) { - if len(point) != ed448lib.PublicKeySize+1 { - return nil - } - - // Strip prefix - return point[1:] -} - -// MarshalByteSecret encoded a scalar from native format to prefixed. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5 -func (c *ed448) MarshalByteSecret(d []byte) []byte { - // Return prefixed - return append([]byte{0x40}, d...) -} - -// UnmarshalByteSecret decodes a scalar from prefixed format to native. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5 -func (c *ed448) UnmarshalByteSecret(s []byte) (d []byte) { - // Check prefixed size - if len(s) != ed448lib.SeedSize+1 { - return nil - } - - // Strip prefix - return s[1:] -} - -// MarshalSignature splits a signature in R and S, where R is in prefixed native format and -// S is an MPI with value zero. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.2 -func (c *ed448) MarshalSignature(sig []byte) (r, s []byte) { - return append([]byte{0x40}, sig...), []byte{} -} - -// UnmarshalSignature decodes R and S in the native format. Only R is used, in prefixed native format. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.2 -func (c *ed448) UnmarshalSignature(r, s []byte) (sig []byte) { - if len(r) != ed448lib.SignatureSize+1 { - return nil - } - - return r[1:] -} - -func (c *ed448) GenerateEdDSA(rand io.Reader) (pub, priv []byte, err error) { - pk, sk, err := ed448lib.GenerateKey(rand) - - if err != nil { - return nil, nil, err - } - - return pk, sk[:ed448lib.SeedSize], nil -} - -func getEd448Sk(publicKey, privateKey []byte) ed448lib.PrivateKey { - privateKeyCap, privateKeyLen, publicKeyLen := cap(privateKey), len(privateKey), len(publicKey) - - if privateKeyCap >= privateKeyLen+publicKeyLen && - bytes.Equal(privateKey[privateKeyLen:privateKeyLen+publicKeyLen], publicKey) { - return privateKey[:privateKeyLen+publicKeyLen] - } - - return append(privateKey[:privateKeyLen:privateKeyLen], publicKey...) -} - -func (c *ed448) Sign(publicKey, privateKey, message []byte) (sig []byte, err error) { - // Ed448 is used with the empty string as a context string. - // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-13.7 - sig = ed448lib.Sign(getEd448Sk(publicKey, privateKey), message, "") - - return sig, nil -} - -func (c *ed448) Verify(publicKey, message, sig []byte) bool { - // Ed448 is used with the empty string as a context string. - // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-13.7 - return ed448lib.Verify(publicKey, message, sig, "") -} - -func (c *ed448) ValidateEdDSA(publicKey, privateKey []byte) (err error) { - priv := getEd448Sk(publicKey, privateKey) - expectedPriv := ed448lib.NewKeyFromSeed(priv.Seed()) - if subtle.ConstantTimeCompare(priv, expectedPriv) == 0 { - return errors.KeyInvalidError("ecc: invalid ed448 secret") - } - return nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/generic.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/generic.go deleted file mode 100644 index e28d7c7106a..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/generic.go +++ /dev/null @@ -1,149 +0,0 @@ -// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA. -package ecc - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "fmt" - "github.com/ProtonMail/go-crypto/openpgp/errors" - "io" - "math/big" -) - -type genericCurve struct { - Curve elliptic.Curve -} - -func NewGenericCurve(c elliptic.Curve) *genericCurve { - return &genericCurve{ - Curve: c, - } -} - -func (c *genericCurve) GetCurveName() string { - return c.Curve.Params().Name -} - -func (c *genericCurve) MarshalBytePoint(point []byte) []byte { - return point -} - -func (c *genericCurve) UnmarshalBytePoint(point []byte) []byte { - return point -} - -func (c *genericCurve) MarshalIntegerPoint(x, y *big.Int) []byte { - return elliptic.Marshal(c.Curve, x, y) -} - -func (c *genericCurve) UnmarshalIntegerPoint(point []byte) (x, y *big.Int) { - return elliptic.Unmarshal(c.Curve, point) -} - -func (c *genericCurve) MarshalByteSecret(d []byte) []byte { - return d -} - -func (c *genericCurve) UnmarshalByteSecret(d []byte) []byte { - return d -} - -func (c *genericCurve) MarshalIntegerSecret(d *big.Int) []byte { - return d.Bytes() -} - -func (c *genericCurve) UnmarshalIntegerSecret(d []byte) *big.Int { - return new(big.Int).SetBytes(d) -} - -func (c *genericCurve) GenerateECDH(rand io.Reader) (point, secret []byte, err error) { - secret, x, y, err := elliptic.GenerateKey(c.Curve, rand) - if err != nil { - return nil, nil, err - } - - point = elliptic.Marshal(c.Curve, x, y) - return point, secret, nil -} - -func (c *genericCurve) GenerateECDSA(rand io.Reader) (x, y, secret *big.Int, err error) { - priv, err := ecdsa.GenerateKey(c.Curve, rand) - if err != nil { - return - } - - return priv.X, priv.Y, priv.D, nil -} - -func (c *genericCurve) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) { - xP, yP := elliptic.Unmarshal(c.Curve, point) - if xP == nil { - panic("invalid point") - } - - d, x, y, err := elliptic.GenerateKey(c.Curve, rand) - if err != nil { - return nil, nil, err - } - - vsG := elliptic.Marshal(c.Curve, x, y) - zbBig, _ := c.Curve.ScalarMult(xP, yP, d) - - byteLen := (c.Curve.Params().BitSize + 7) >> 3 - zb := make([]byte, byteLen) - zbBytes := zbBig.Bytes() - copy(zb[byteLen-len(zbBytes):], zbBytes) - - return vsG, zb, nil -} - -func (c *genericCurve) Decaps(ephemeral, secret []byte) (sharedSecret []byte, err error) { - x, y := elliptic.Unmarshal(c.Curve, ephemeral) - zbBig, _ := c.Curve.ScalarMult(x, y, secret) - byteLen := (c.Curve.Params().BitSize + 7) >> 3 - zb := make([]byte, byteLen) - zbBytes := zbBig.Bytes() - copy(zb[byteLen-len(zbBytes):], zbBytes) - - return zb, nil -} - -func (c *genericCurve) Sign(rand io.Reader, x, y, d *big.Int, hash []byte) (r, s *big.Int, err error) { - priv := &ecdsa.PrivateKey{D: d, PublicKey: ecdsa.PublicKey{X: x, Y: y, Curve: c.Curve}} - return ecdsa.Sign(rand, priv, hash) -} - -func (c *genericCurve) Verify(x, y *big.Int, hash []byte, r, s *big.Int) bool { - pub := &ecdsa.PublicKey{X: x, Y: y, Curve: c.Curve} - return ecdsa.Verify(pub, hash, r, s) -} - -func (c *genericCurve) validate(xP, yP *big.Int, secret []byte) error { - // the public point should not be at infinity (0,0) - zero := new(big.Int) - if xP.Cmp(zero) == 0 && yP.Cmp(zero) == 0 { - return errors.KeyInvalidError(fmt.Sprintf("ecc (%s): infinity point", c.Curve.Params().Name)) - } - - // re-derive the public point Q' = (X,Y) = dG - // to compare to declared Q in public key - expectedX, expectedY := c.Curve.ScalarBaseMult(secret) - if xP.Cmp(expectedX) != 0 || yP.Cmp(expectedY) != 0 { - return errors.KeyInvalidError(fmt.Sprintf("ecc (%s): invalid point", c.Curve.Params().Name)) - } - - return nil -} - -func (c *genericCurve) ValidateECDSA(xP, yP *big.Int, secret []byte) error { - return c.validate(xP, yP, secret) -} - -func (c *genericCurve) ValidateECDH(point []byte, secret []byte) error { - xP, yP := elliptic.Unmarshal(c.Curve, point) - if xP == nil { - return errors.KeyInvalidError(fmt.Sprintf("ecc (%s): invalid point", c.Curve.Params().Name)) - } - - return c.validate(xP, yP, secret) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/x448.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/x448.go deleted file mode 100644 index df04262e9e6..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/x448.go +++ /dev/null @@ -1,107 +0,0 @@ -// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA. -package ecc - -import ( - "crypto/subtle" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - x448lib "github.com/cloudflare/circl/dh/x448" -) - -type x448 struct{} - -func NewX448() *x448 { - return &x448{} -} - -func (c *x448) GetCurveName() string { - return "x448" -} - -// MarshalBytePoint encodes the public point from native format, adding the prefix. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6 -func (c *x448) MarshalBytePoint(point []byte) []byte { - return append([]byte{0x40}, point...) -} - -// UnmarshalBytePoint decodes a point from prefixed format to native. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6 -func (c *x448) UnmarshalBytePoint(point []byte) []byte { - if len(point) != x448lib.Size+1 { - return nil - } - - return point[1:] -} - -// MarshalByteSecret encoded a scalar from native format to prefixed. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.2 -func (c *x448) MarshalByteSecret(d []byte) []byte { - return append([]byte{0x40}, d...) -} - -// UnmarshalByteSecret decodes a scalar from prefixed format to native. -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.2 -func (c *x448) UnmarshalByteSecret(d []byte) []byte { - if len(d) != x448lib.Size+1 { - return nil - } - - // Store without prefix - return d[1:] -} - -func (c *x448) generateKeyPairBytes(rand io.Reader) (sk, pk x448lib.Key, err error) { - if _, err = rand.Read(sk[:]); err != nil { - return - } - - x448lib.KeyGen(&pk, &sk) - return -} - -func (c *x448) GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error) { - priv, pub, err := c.generateKeyPairBytes(rand) - if err != nil { - return - } - - return pub[:], priv[:], nil -} - -func (c *x448) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) { - var pk, ss x448lib.Key - seed, e, err := c.generateKeyPairBytes(rand) - if err != nil { - return nil, nil, err - } - copy(pk[:], point) - x448lib.Shared(&ss, &seed, &pk) - - return e[:], ss[:], nil -} - -func (c *x448) Decaps(ephemeral, secret []byte) (sharedSecret []byte, err error) { - var ss, sk, e x448lib.Key - - copy(sk[:], secret) - copy(e[:], ephemeral) - x448lib.Shared(&ss, &sk, &e) - - return ss[:], nil -} - -func (c *x448) ValidateECDH(point []byte, secret []byte) error { - var sk, pk, expectedPk x448lib.Key - - copy(pk[:], point) - copy(sk[:], secret) - x448lib.KeyGen(&expectedPk, &sk) - - if subtle.ConstantTimeCompare(expectedPk[:], pk[:]) == 0 { - return errors.KeyInvalidError("ecc: invalid curve25519 public point") - } - - return nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/encoding/encoding.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/encoding/encoding.go deleted file mode 100644 index 6c921481b7b..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/encoding/encoding.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package encoding implements openpgp packet field encodings as specified in -// RFC 4880 and 6637. -package encoding - -import "io" - -// Field is an encoded field of an openpgp packet. -type Field interface { - // Bytes returns the decoded data. - Bytes() []byte - - // BitLength is the size in bits of the decoded data. - BitLength() uint16 - - // EncodedBytes returns the encoded data. - EncodedBytes() []byte - - // EncodedLength is the size in bytes of the encoded data. - EncodedLength() uint16 - - // ReadFrom reads the next Field from r. - ReadFrom(r io.Reader) (int64, error) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/encoding/mpi.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/encoding/mpi.go deleted file mode 100644 index 02e5e695c38..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/encoding/mpi.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package encoding - -import ( - "io" - "math/big" - "math/bits" -) - -// An MPI is used to store the contents of a big integer, along with the bit -// length that was specified in the original input. This allows the MPI to be -// reserialized exactly. -type MPI struct { - bytes []byte - bitLength uint16 -} - -// NewMPI returns a MPI initialized with bytes. -func NewMPI(bytes []byte) *MPI { - for len(bytes) != 0 && bytes[0] == 0 { - bytes = bytes[1:] - } - if len(bytes) == 0 { - bitLength := uint16(0) - return &MPI{bytes, bitLength} - } - bitLength := 8*uint16(len(bytes)-1) + uint16(bits.Len8(bytes[0])) - return &MPI{bytes, bitLength} -} - -// Bytes returns the decoded data. -func (m *MPI) Bytes() []byte { - return m.bytes -} - -// BitLength is the size in bits of the decoded data. -func (m *MPI) BitLength() uint16 { - return m.bitLength -} - -// EncodedBytes returns the encoded data. -func (m *MPI) EncodedBytes() []byte { - return append([]byte{byte(m.bitLength >> 8), byte(m.bitLength)}, m.bytes...) -} - -// EncodedLength is the size in bytes of the encoded data. -func (m *MPI) EncodedLength() uint16 { - return uint16(2 + len(m.bytes)) -} - -// ReadFrom reads into m the next MPI from r. -func (m *MPI) ReadFrom(r io.Reader) (int64, error) { - var buf [2]byte - n, err := io.ReadFull(r, buf[0:]) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return int64(n), err - } - - m.bitLength = uint16(buf[0])<<8 | uint16(buf[1]) - m.bytes = make([]byte, (int(m.bitLength)+7)/8) - - nn, err := io.ReadFull(r, m.bytes) - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - - // remove leading zero bytes from malformed GnuPG encoded MPIs: - // https://bugs.gnupg.org/gnupg/issue1853 - // for _, b := range m.bytes { - // if b != 0 { - // break - // } - // m.bytes = m.bytes[1:] - // m.bitLength -= 8 - // } - - return int64(n) + int64(nn), err -} - -// SetBig initializes m with the bits from n. -func (m *MPI) SetBig(n *big.Int) *MPI { - m.bytes = n.Bytes() - m.bitLength = uint16(n.BitLen()) - return m -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/encoding/oid.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/encoding/oid.go deleted file mode 100644 index c9df9fe2322..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/internal/encoding/oid.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package encoding - -import ( - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" -) - -// OID is used to store a variable-length field with a one-octet size -// prefix. See https://tools.ietf.org/html/rfc6637#section-9. -type OID struct { - bytes []byte -} - -const ( - // maxOID is the maximum number of bytes in a OID. - maxOID = 254 - // reservedOIDLength1 and reservedOIDLength2 are OID lengths that the RFC - // specifies are reserved. - reservedOIDLength1 = 0 - reservedOIDLength2 = 0xff -) - -// NewOID returns a OID initialized with bytes. -func NewOID(bytes []byte) *OID { - switch len(bytes) { - case reservedOIDLength1, reservedOIDLength2: - panic("encoding: NewOID argument length is reserved") - default: - if len(bytes) > maxOID { - panic("encoding: NewOID argument too large") - } - } - - return &OID{ - bytes: bytes, - } -} - -// Bytes returns the decoded data. -func (o *OID) Bytes() []byte { - return o.bytes -} - -// BitLength is the size in bits of the decoded data. -func (o *OID) BitLength() uint16 { - return uint16(len(o.bytes) * 8) -} - -// EncodedBytes returns the encoded data. -func (o *OID) EncodedBytes() []byte { - return append([]byte{byte(len(o.bytes))}, o.bytes...) -} - -// EncodedLength is the size in bytes of the encoded data. -func (o *OID) EncodedLength() uint16 { - return uint16(1 + len(o.bytes)) -} - -// ReadFrom reads into b the next OID from r. -func (o *OID) ReadFrom(r io.Reader) (int64, error) { - var buf [1]byte - n, err := io.ReadFull(r, buf[:]) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return int64(n), err - } - - switch buf[0] { - case reservedOIDLength1, reservedOIDLength2: - return int64(n), errors.UnsupportedError("reserved for future extensions") - } - - o.bytes = make([]byte, buf[0]) - - nn, err := io.ReadFull(r, o.bytes) - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - - return int64(n) + int64(nn), err -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/key_generation.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/key_generation.go deleted file mode 100644 index 77213f66be0..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/key_generation.go +++ /dev/null @@ -1,456 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package openpgp - -import ( - "crypto" - "crypto/rand" - "crypto/rsa" - goerrors "errors" - "io" - "math/big" - "time" - - "github.com/ProtonMail/go-crypto/openpgp/ecdh" - "github.com/ProtonMail/go-crypto/openpgp/ecdsa" - "github.com/ProtonMail/go-crypto/openpgp/ed25519" - "github.com/ProtonMail/go-crypto/openpgp/ed448" - "github.com/ProtonMail/go-crypto/openpgp/eddsa" - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" - "github.com/ProtonMail/go-crypto/openpgp/internal/ecc" - "github.com/ProtonMail/go-crypto/openpgp/packet" - "github.com/ProtonMail/go-crypto/openpgp/x25519" - "github.com/ProtonMail/go-crypto/openpgp/x448" -) - -// NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a -// single identity composed of the given full name, comment and email, any of -// which may be empty but must not contain any of "()<>\x00". -// If config is nil, sensible defaults will be used. -func NewEntity(name, comment, email string, config *packet.Config) (*Entity, error) { - creationTime := config.Now() - keyLifetimeSecs := config.KeyLifetime() - - // Generate a primary signing key - primaryPrivRaw, err := newSigner(config) - if err != nil { - return nil, err - } - primary := packet.NewSignerPrivateKey(creationTime, primaryPrivRaw) - if config.V6() { - if err := primary.UpgradeToV6(); err != nil { - return nil, err - } - } - - e := &Entity{ - PrimaryKey: &primary.PublicKey, - PrivateKey: primary, - Identities: make(map[string]*Identity), - Subkeys: []Subkey{}, - Signatures: []*packet.Signature{}, - } - - if config.V6() { - // In v6 keys algorithm preferences should be stored in direct key signatures - selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypeDirectSignature, config) - err = writeKeyProperties(selfSignature, creationTime, keyLifetimeSecs, config) - if err != nil { - return nil, err - } - err = selfSignature.SignDirectKeyBinding(&primary.PublicKey, primary, config) - if err != nil { - return nil, err - } - e.Signatures = append(e.Signatures, selfSignature) - e.SelfSignature = selfSignature - } - - err = e.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs, !config.V6()) - if err != nil { - return nil, err - } - - // NOTE: No key expiry here, but we will not return this subkey in EncryptionKey() - // if the primary/master key has expired. - err = e.addEncryptionSubkey(config, creationTime, 0) - if err != nil { - return nil, err - } - - return e, nil -} - -func (t *Entity) AddUserId(name, comment, email string, config *packet.Config) error { - creationTime := config.Now() - keyLifetimeSecs := config.KeyLifetime() - return t.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs, !config.V6()) -} - -func writeKeyProperties(selfSignature *packet.Signature, creationTime time.Time, keyLifetimeSecs uint32, config *packet.Config) error { - advertiseAead := config.AEAD() != nil - - selfSignature.CreationTime = creationTime - selfSignature.KeyLifetimeSecs = &keyLifetimeSecs - selfSignature.FlagsValid = true - selfSignature.FlagSign = true - selfSignature.FlagCertify = true - selfSignature.SEIPDv1 = true // true by default, see 5.8 vs. 5.14 - selfSignature.SEIPDv2 = advertiseAead - - // Set the PreferredHash for the SelfSignature from the packet.Config. - // If it is not the must-implement algorithm from rfc4880bis, append that. - hash, ok := algorithm.HashToHashId(config.Hash()) - if !ok { - return errors.UnsupportedError("unsupported preferred hash function") - } - - selfSignature.PreferredHash = []uint8{hash} - if config.Hash() != crypto.SHA256 { - selfSignature.PreferredHash = append(selfSignature.PreferredHash, hashToHashId(crypto.SHA256)) - } - - // Likewise for DefaultCipher. - selfSignature.PreferredSymmetric = []uint8{uint8(config.Cipher())} - if config.Cipher() != packet.CipherAES128 { - selfSignature.PreferredSymmetric = append(selfSignature.PreferredSymmetric, uint8(packet.CipherAES128)) - } - - // We set CompressionNone as the preferred compression algorithm because - // of compression side channel attacks, then append the configured - // DefaultCompressionAlgo if any is set (to signal support for cases - // where the application knows that using compression is safe). - selfSignature.PreferredCompression = []uint8{uint8(packet.CompressionNone)} - if config.Compression() != packet.CompressionNone { - selfSignature.PreferredCompression = append(selfSignature.PreferredCompression, uint8(config.Compression())) - } - - if advertiseAead { - // Get the preferred AEAD mode from the packet.Config. - // If it is not the must-implement algorithm from rfc9580, append that. - modes := []uint8{uint8(config.AEAD().Mode())} - if config.AEAD().Mode() != packet.AEADModeOCB { - modes = append(modes, uint8(packet.AEADModeOCB)) - } - - // For preferred (AES256, GCM), we'll generate (AES256, GCM), (AES256, OCB), (AES128, GCM), (AES128, OCB) - for _, cipher := range selfSignature.PreferredSymmetric { - for _, mode := range modes { - selfSignature.PreferredCipherSuites = append(selfSignature.PreferredCipherSuites, [2]uint8{cipher, mode}) - } - } - } - return nil -} - -func (t *Entity) addUserId(name, comment, email string, config *packet.Config, creationTime time.Time, keyLifetimeSecs uint32, writeProperties bool) error { - uid := packet.NewUserId(name, comment, email) - if uid == nil { - return errors.InvalidArgumentError("user id field contained invalid characters") - } - - if _, ok := t.Identities[uid.Id]; ok { - return errors.InvalidArgumentError("user id exist") - } - - primary := t.PrivateKey - isPrimaryId := len(t.Identities) == 0 - selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypePositiveCert, config) - if writeProperties { - err := writeKeyProperties(selfSignature, creationTime, keyLifetimeSecs, config) - if err != nil { - return err - } - } - selfSignature.IsPrimaryId = &isPrimaryId - - // User ID binding signature - err := selfSignature.SignUserId(uid.Id, &primary.PublicKey, primary, config) - if err != nil { - return err - } - t.Identities[uid.Id] = &Identity{ - Name: uid.Id, - UserId: uid, - SelfSignature: selfSignature, - Signatures: []*packet.Signature{selfSignature}, - } - return nil -} - -// AddSigningSubkey adds a signing keypair as a subkey to the Entity. -// If config is nil, sensible defaults will be used. -func (e *Entity) AddSigningSubkey(config *packet.Config) error { - creationTime := config.Now() - keyLifetimeSecs := config.KeyLifetime() - - subPrivRaw, err := newSigner(config) - if err != nil { - return err - } - sub := packet.NewSignerPrivateKey(creationTime, subPrivRaw) - sub.IsSubkey = true - if config.V6() { - if err := sub.UpgradeToV6(); err != nil { - return err - } - } - - subkey := Subkey{ - PublicKey: &sub.PublicKey, - PrivateKey: sub, - } - subkey.Sig = createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyBinding, config) - subkey.Sig.CreationTime = creationTime - subkey.Sig.KeyLifetimeSecs = &keyLifetimeSecs - subkey.Sig.FlagsValid = true - subkey.Sig.FlagSign = true - subkey.Sig.EmbeddedSignature = createSignaturePacket(subkey.PublicKey, packet.SigTypePrimaryKeyBinding, config) - subkey.Sig.EmbeddedSignature.CreationTime = creationTime - - err = subkey.Sig.EmbeddedSignature.CrossSignKey(subkey.PublicKey, e.PrimaryKey, subkey.PrivateKey, config) - if err != nil { - return err - } - - err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config) - if err != nil { - return err - } - - e.Subkeys = append(e.Subkeys, subkey) - return nil -} - -// AddEncryptionSubkey adds an encryption keypair as a subkey to the Entity. -// If config is nil, sensible defaults will be used. -func (e *Entity) AddEncryptionSubkey(config *packet.Config) error { - creationTime := config.Now() - keyLifetimeSecs := config.KeyLifetime() - return e.addEncryptionSubkey(config, creationTime, keyLifetimeSecs) -} - -func (e *Entity) addEncryptionSubkey(config *packet.Config, creationTime time.Time, keyLifetimeSecs uint32) error { - subPrivRaw, err := newDecrypter(config) - if err != nil { - return err - } - sub := packet.NewDecrypterPrivateKey(creationTime, subPrivRaw) - sub.IsSubkey = true - if config.V6() { - if err := sub.UpgradeToV6(); err != nil { - return err - } - } - - subkey := Subkey{ - PublicKey: &sub.PublicKey, - PrivateKey: sub, - } - subkey.Sig = createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyBinding, config) - subkey.Sig.CreationTime = creationTime - subkey.Sig.KeyLifetimeSecs = &keyLifetimeSecs - subkey.Sig.FlagsValid = true - subkey.Sig.FlagEncryptStorage = true - subkey.Sig.FlagEncryptCommunications = true - - err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config) - if err != nil { - return err - } - - e.Subkeys = append(e.Subkeys, subkey) - return nil -} - -// Generates a signing key -func newSigner(config *packet.Config) (signer interface{}, err error) { - switch config.PublicKeyAlgorithm() { - case packet.PubKeyAlgoRSA: - bits := config.RSAModulusBits() - if bits < 1024 { - return nil, errors.InvalidArgumentError("bits must be >= 1024") - } - if config != nil && len(config.RSAPrimes) >= 2 { - primes := config.RSAPrimes[0:2] - config.RSAPrimes = config.RSAPrimes[2:] - return generateRSAKeyWithPrimes(config.Random(), 2, bits, primes) - } - return rsa.GenerateKey(config.Random(), bits) - case packet.PubKeyAlgoEdDSA: - if config.V6() { - // Implementations MUST NOT accept or generate v6 key material - // using the deprecated OIDs. - return nil, errors.InvalidArgumentError("EdDSALegacy cannot be used for v6 keys") - } - curve := ecc.FindEdDSAByGenName(string(config.CurveName())) - if curve == nil { - return nil, errors.InvalidArgumentError("unsupported curve") - } - - priv, err := eddsa.GenerateKey(config.Random(), curve) - if err != nil { - return nil, err - } - return priv, nil - case packet.PubKeyAlgoECDSA: - curve := ecc.FindECDSAByGenName(string(config.CurveName())) - if curve == nil { - return nil, errors.InvalidArgumentError("unsupported curve") - } - - priv, err := ecdsa.GenerateKey(config.Random(), curve) - if err != nil { - return nil, err - } - return priv, nil - case packet.PubKeyAlgoEd25519: - priv, err := ed25519.GenerateKey(config.Random()) - if err != nil { - return nil, err - } - return priv, nil - case packet.PubKeyAlgoEd448: - priv, err := ed448.GenerateKey(config.Random()) - if err != nil { - return nil, err - } - return priv, nil - default: - return nil, errors.InvalidArgumentError("unsupported public key algorithm") - } -} - -// Generates an encryption/decryption key -func newDecrypter(config *packet.Config) (decrypter interface{}, err error) { - switch config.PublicKeyAlgorithm() { - case packet.PubKeyAlgoRSA: - bits := config.RSAModulusBits() - if bits < 1024 { - return nil, errors.InvalidArgumentError("bits must be >= 1024") - } - if config != nil && len(config.RSAPrimes) >= 2 { - primes := config.RSAPrimes[0:2] - config.RSAPrimes = config.RSAPrimes[2:] - return generateRSAKeyWithPrimes(config.Random(), 2, bits, primes) - } - return rsa.GenerateKey(config.Random(), bits) - case packet.PubKeyAlgoEdDSA, packet.PubKeyAlgoECDSA: - fallthrough // When passing EdDSA or ECDSA, we generate an ECDH subkey - case packet.PubKeyAlgoECDH: - if config.V6() && - (config.CurveName() == packet.Curve25519 || - config.CurveName() == packet.Curve448) { - // Implementations MUST NOT accept or generate v6 key material - // using the deprecated OIDs. - return nil, errors.InvalidArgumentError("ECDH with Curve25519/448 legacy cannot be used for v6 keys") - } - var kdf = ecdh.KDF{ - Hash: algorithm.SHA512, - Cipher: algorithm.AES256, - } - curve := ecc.FindECDHByGenName(string(config.CurveName())) - if curve == nil { - return nil, errors.InvalidArgumentError("unsupported curve") - } - return ecdh.GenerateKey(config.Random(), curve, kdf) - case packet.PubKeyAlgoEd25519, packet.PubKeyAlgoX25519: // When passing Ed25519, we generate an x25519 subkey - return x25519.GenerateKey(config.Random()) - case packet.PubKeyAlgoEd448, packet.PubKeyAlgoX448: // When passing Ed448, we generate an x448 subkey - return x448.GenerateKey(config.Random()) - default: - return nil, errors.InvalidArgumentError("unsupported public key algorithm") - } -} - -var bigOne = big.NewInt(1) - -// generateRSAKeyWithPrimes generates a multi-prime RSA keypair of the -// given bit size, using the given random source and pre-populated primes. -func generateRSAKeyWithPrimes(random io.Reader, nprimes int, bits int, prepopulatedPrimes []*big.Int) (*rsa.PrivateKey, error) { - priv := new(rsa.PrivateKey) - priv.E = 65537 - - if nprimes < 2 { - return nil, goerrors.New("generateRSAKeyWithPrimes: nprimes must be >= 2") - } - - if bits < 1024 { - return nil, goerrors.New("generateRSAKeyWithPrimes: bits must be >= 1024") - } - - primes := make([]*big.Int, nprimes) - -NextSetOfPrimes: - for { - todo := bits - // crypto/rand should set the top two bits in each prime. - // Thus each prime has the form - // p_i = 2^bitlen(p_i) × 0.11... (in base 2). - // And the product is: - // P = 2^todo × α - // where α is the product of nprimes numbers of the form 0.11... - // - // If α < 1/2 (which can happen for nprimes > 2), we need to - // shift todo to compensate for lost bits: the mean value of 0.11... - // is 7/8, so todo + shift - nprimes * log2(7/8) ~= bits - 1/2 - // will give good results. - if nprimes >= 7 { - todo += (nprimes - 2) / 5 - } - for i := 0; i < nprimes; i++ { - var err error - if len(prepopulatedPrimes) == 0 { - primes[i], err = rand.Prime(random, todo/(nprimes-i)) - if err != nil { - return nil, err - } - } else { - primes[i] = prepopulatedPrimes[0] - prepopulatedPrimes = prepopulatedPrimes[1:] - } - - todo -= primes[i].BitLen() - } - - // Make sure that primes is pairwise unequal. - for i, prime := range primes { - for j := 0; j < i; j++ { - if prime.Cmp(primes[j]) == 0 { - continue NextSetOfPrimes - } - } - } - - n := new(big.Int).Set(bigOne) - totient := new(big.Int).Set(bigOne) - pminus1 := new(big.Int) - for _, prime := range primes { - n.Mul(n, prime) - pminus1.Sub(prime, bigOne) - totient.Mul(totient, pminus1) - } - if n.BitLen() != bits { - // This should never happen for nprimes == 2 because - // crypto/rand should set the top two bits in each prime. - // For nprimes > 2 we hope it does not happen often. - continue NextSetOfPrimes - } - - priv.D = new(big.Int) - e := big.NewInt(int64(priv.E)) - ok := priv.D.ModInverse(e, totient) - - if ok != nil { - priv.Primes = primes - priv.N = n - break - } - } - - priv.Precompute() - return priv, nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/keys.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/keys.go deleted file mode 100644 index a071353e2ec..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/keys.go +++ /dev/null @@ -1,901 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package openpgp - -import ( - goerrors "errors" - "fmt" - "io" - "time" - - "github.com/ProtonMail/go-crypto/openpgp/armor" - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/packet" -) - -// PublicKeyType is the armor type for a PGP public key. -var PublicKeyType = "PGP PUBLIC KEY BLOCK" - -// PrivateKeyType is the armor type for a PGP private key. -var PrivateKeyType = "PGP PRIVATE KEY BLOCK" - -// An Entity represents the components of an OpenPGP key: a primary public key -// (which must be a signing key), one or more identities claimed by that key, -// and zero or more subkeys, which may be encryption keys. -type Entity struct { - PrimaryKey *packet.PublicKey - PrivateKey *packet.PrivateKey - Identities map[string]*Identity // indexed by Identity.Name - Revocations []*packet.Signature - Subkeys []Subkey - SelfSignature *packet.Signature // Direct-key self signature of the PrimaryKey (contains primary key properties in v6) - Signatures []*packet.Signature // all (potentially unverified) self-signatures, revocations, and third-party signatures -} - -// An Identity represents an identity claimed by an Entity and zero or more -// assertions by other entities about that claim. -type Identity struct { - Name string // by convention, has the form "Full Name (comment) " - UserId *packet.UserId - SelfSignature *packet.Signature - Revocations []*packet.Signature - Signatures []*packet.Signature // all (potentially unverified) self-signatures, revocations, and third-party signatures -} - -// A Subkey is an additional public key in an Entity. Subkeys can be used for -// encryption. -type Subkey struct { - PublicKey *packet.PublicKey - PrivateKey *packet.PrivateKey - Sig *packet.Signature - Revocations []*packet.Signature -} - -// A Key identifies a specific public key in an Entity. This is either the -// Entity's primary key or a subkey. -type Key struct { - Entity *Entity - PublicKey *packet.PublicKey - PrivateKey *packet.PrivateKey - SelfSignature *packet.Signature - Revocations []*packet.Signature -} - -// A KeyRing provides access to public and private keys. -type KeyRing interface { - // KeysById returns the set of keys that have the given key id. - KeysById(id uint64) []Key - // KeysByIdAndUsage returns the set of keys with the given id - // that also meet the key usage given by requiredUsage. - // The requiredUsage is expressed as the bitwise-OR of - // packet.KeyFlag* values. - KeysByIdUsage(id uint64, requiredUsage byte) []Key - // DecryptionKeys returns all private keys that are valid for - // decryption. - DecryptionKeys() []Key -} - -// PrimaryIdentity returns an Identity, preferring non-revoked identities, -// identities marked as primary, or the latest-created identity, in that order. -func (e *Entity) PrimaryIdentity() *Identity { - var primaryIdentity *Identity - for _, ident := range e.Identities { - if shouldPreferIdentity(primaryIdentity, ident) { - primaryIdentity = ident - } - } - return primaryIdentity -} - -func shouldPreferIdentity(existingId, potentialNewId *Identity) bool { - if existingId == nil { - return true - } - - if len(existingId.Revocations) > len(potentialNewId.Revocations) { - return true - } - - if len(existingId.Revocations) < len(potentialNewId.Revocations) { - return false - } - - if existingId.SelfSignature == nil { - return true - } - - if existingId.SelfSignature.IsPrimaryId != nil && *existingId.SelfSignature.IsPrimaryId && - !(potentialNewId.SelfSignature.IsPrimaryId != nil && *potentialNewId.SelfSignature.IsPrimaryId) { - return false - } - - if !(existingId.SelfSignature.IsPrimaryId != nil && *existingId.SelfSignature.IsPrimaryId) && - potentialNewId.SelfSignature.IsPrimaryId != nil && *potentialNewId.SelfSignature.IsPrimaryId { - return true - } - - return potentialNewId.SelfSignature.CreationTime.After(existingId.SelfSignature.CreationTime) -} - -// EncryptionKey returns the best candidate Key for encrypting a message to the -// given Entity. -func (e *Entity) EncryptionKey(now time.Time) (Key, bool) { - // Fail to find any encryption key if the... - primarySelfSignature, primaryIdentity := e.PrimarySelfSignature() - if primarySelfSignature == nil || // no self-signature found - e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired - e.Revoked(now) || // primary key has been revoked - primarySelfSignature.SigExpired(now) || // user ID or or direct self-signature has expired - (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user ID has been revoked (for v4 keys) - return Key{}, false - } - - // Iterate the keys to find the newest, unexpired one - candidateSubkey := -1 - var maxTime time.Time - for i, subkey := range e.Subkeys { - if subkey.Sig.FlagsValid && - subkey.Sig.FlagEncryptCommunications && - subkey.PublicKey.PubKeyAlgo.CanEncrypt() && - !subkey.PublicKey.KeyExpired(subkey.Sig, now) && - !subkey.Sig.SigExpired(now) && - !subkey.Revoked(now) && - (maxTime.IsZero() || subkey.Sig.CreationTime.After(maxTime)) { - candidateSubkey = i - maxTime = subkey.Sig.CreationTime - } - } - - if candidateSubkey != -1 { - subkey := e.Subkeys[candidateSubkey] - return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig, subkey.Revocations}, true - } - - // If we don't have any subkeys for encryption and the primary key - // is marked as OK to encrypt with, then we can use it. - if primarySelfSignature.FlagsValid && primarySelfSignature.FlagEncryptCommunications && - e.PrimaryKey.PubKeyAlgo.CanEncrypt() { - return Key{e, e.PrimaryKey, e.PrivateKey, primarySelfSignature, e.Revocations}, true - } - - return Key{}, false -} - -// CertificationKey return the best candidate Key for certifying a key with this -// Entity. -func (e *Entity) CertificationKey(now time.Time) (Key, bool) { - return e.CertificationKeyById(now, 0) -} - -// CertificationKeyById return the Key for key certification with this -// Entity and keyID. -func (e *Entity) CertificationKeyById(now time.Time, id uint64) (Key, bool) { - return e.signingKeyByIdUsage(now, id, packet.KeyFlagCertify) -} - -// SigningKey return the best candidate Key for signing a message with this -// Entity. -func (e *Entity) SigningKey(now time.Time) (Key, bool) { - return e.SigningKeyById(now, 0) -} - -// SigningKeyById return the Key for signing a message with this -// Entity and keyID. -func (e *Entity) SigningKeyById(now time.Time, id uint64) (Key, bool) { - return e.signingKeyByIdUsage(now, id, packet.KeyFlagSign) -} - -func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key, bool) { - // Fail to find any signing key if the... - primarySelfSignature, primaryIdentity := e.PrimarySelfSignature() - if primarySelfSignature == nil || // no self-signature found - e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired - e.Revoked(now) || // primary key has been revoked - primarySelfSignature.SigExpired(now) || // user ID or direct self-signature has expired - (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user ID has been revoked (for v4 keys) - return Key{}, false - } - - // Iterate the keys to find the newest, unexpired one - candidateSubkey := -1 - var maxTime time.Time - for idx, subkey := range e.Subkeys { - if subkey.Sig.FlagsValid && - (flags&packet.KeyFlagCertify == 0 || subkey.Sig.FlagCertify) && - (flags&packet.KeyFlagSign == 0 || subkey.Sig.FlagSign) && - subkey.PublicKey.PubKeyAlgo.CanSign() && - !subkey.PublicKey.KeyExpired(subkey.Sig, now) && - !subkey.Sig.SigExpired(now) && - !subkey.Revoked(now) && - (maxTime.IsZero() || subkey.Sig.CreationTime.After(maxTime)) && - (id == 0 || subkey.PublicKey.KeyId == id) { - candidateSubkey = idx - maxTime = subkey.Sig.CreationTime - } - } - - if candidateSubkey != -1 { - subkey := e.Subkeys[candidateSubkey] - return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig, subkey.Revocations}, true - } - - // If we don't have any subkeys for signing and the primary key - // is marked as OK to sign with, then we can use it. - if primarySelfSignature.FlagsValid && - (flags&packet.KeyFlagCertify == 0 || primarySelfSignature.FlagCertify) && - (flags&packet.KeyFlagSign == 0 || primarySelfSignature.FlagSign) && - e.PrimaryKey.PubKeyAlgo.CanSign() && - (id == 0 || e.PrimaryKey.KeyId == id) { - return Key{e, e.PrimaryKey, e.PrivateKey, primarySelfSignature, e.Revocations}, true - } - - // No keys with a valid Signing Flag or no keys matched the id passed in - return Key{}, false -} - -func revoked(revocations []*packet.Signature, now time.Time) bool { - for _, revocation := range revocations { - if revocation.RevocationReason != nil && *revocation.RevocationReason == packet.KeyCompromised { - // If the key is compromised, the key is considered revoked even before the revocation date. - return true - } - if !revocation.SigExpired(now) { - return true - } - } - return false -} - -// Revoked returns whether the entity has any direct key revocation signatures. -// Note that third-party revocation signatures are not supported. -// Note also that Identity and Subkey revocation should be checked separately. -func (e *Entity) Revoked(now time.Time) bool { - return revoked(e.Revocations, now) -} - -// EncryptPrivateKeys encrypts all non-encrypted keys in the entity with the same key -// derived from the provided passphrase. Public keys and dummy keys are ignored, -// and don't cause an error to be returned. -func (e *Entity) EncryptPrivateKeys(passphrase []byte, config *packet.Config) error { - var keysToEncrypt []*packet.PrivateKey - // Add entity private key to encrypt. - if e.PrivateKey != nil && !e.PrivateKey.Dummy() && !e.PrivateKey.Encrypted { - keysToEncrypt = append(keysToEncrypt, e.PrivateKey) - } - - // Add subkeys to encrypt. - for _, sub := range e.Subkeys { - if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && !sub.PrivateKey.Encrypted { - keysToEncrypt = append(keysToEncrypt, sub.PrivateKey) - } - } - return packet.EncryptPrivateKeys(keysToEncrypt, passphrase, config) -} - -// DecryptPrivateKeys decrypts all encrypted keys in the entity with the given passphrase. -// Avoids recomputation of similar s2k key derivations. Public keys and dummy keys are ignored, -// and don't cause an error to be returned. -func (e *Entity) DecryptPrivateKeys(passphrase []byte) error { - var keysToDecrypt []*packet.PrivateKey - // Add entity private key to decrypt. - if e.PrivateKey != nil && !e.PrivateKey.Dummy() && e.PrivateKey.Encrypted { - keysToDecrypt = append(keysToDecrypt, e.PrivateKey) - } - - // Add subkeys to decrypt. - for _, sub := range e.Subkeys { - if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && sub.PrivateKey.Encrypted { - keysToDecrypt = append(keysToDecrypt, sub.PrivateKey) - } - } - return packet.DecryptPrivateKeys(keysToDecrypt, passphrase) -} - -// Revoked returns whether the identity has been revoked by a self-signature. -// Note that third-party revocation signatures are not supported. -func (i *Identity) Revoked(now time.Time) bool { - return revoked(i.Revocations, now) -} - -// Revoked returns whether the subkey has been revoked by a self-signature. -// Note that third-party revocation signatures are not supported. -func (s *Subkey) Revoked(now time.Time) bool { - return revoked(s.Revocations, now) -} - -// Revoked returns whether the key or subkey has been revoked by a self-signature. -// Note that third-party revocation signatures are not supported. -// Note also that Identity revocation should be checked separately. -// Normally, it's not necessary to call this function, except on keys returned by -// KeysById or KeysByIdUsage. -func (key *Key) Revoked(now time.Time) bool { - return revoked(key.Revocations, now) -} - -// An EntityList contains one or more Entities. -type EntityList []*Entity - -// KeysById returns the set of keys that have the given key id. -func (el EntityList) KeysById(id uint64) (keys []Key) { - for _, e := range el { - if e.PrimaryKey.KeyId == id { - selfSig, _ := e.PrimarySelfSignature() - keys = append(keys, Key{e, e.PrimaryKey, e.PrivateKey, selfSig, e.Revocations}) - } - - for _, subKey := range e.Subkeys { - if subKey.PublicKey.KeyId == id { - keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig, subKey.Revocations}) - } - } - } - return -} - -// KeysByIdAndUsage returns the set of keys with the given id that also meet -// the key usage given by requiredUsage. The requiredUsage is expressed as -// the bitwise-OR of packet.KeyFlag* values. -func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) { - for _, key := range el.KeysById(id) { - if requiredUsage != 0 { - if key.SelfSignature == nil || !key.SelfSignature.FlagsValid { - continue - } - - var usage byte - if key.SelfSignature.FlagCertify { - usage |= packet.KeyFlagCertify - } - if key.SelfSignature.FlagSign { - usage |= packet.KeyFlagSign - } - if key.SelfSignature.FlagEncryptCommunications { - usage |= packet.KeyFlagEncryptCommunications - } - if key.SelfSignature.FlagEncryptStorage { - usage |= packet.KeyFlagEncryptStorage - } - if usage&requiredUsage != requiredUsage { - continue - } - } - - keys = append(keys, key) - } - return -} - -// DecryptionKeys returns all private keys that are valid for decryption. -func (el EntityList) DecryptionKeys() (keys []Key) { - for _, e := range el { - for _, subKey := range e.Subkeys { - if subKey.PrivateKey != nil && subKey.Sig.FlagsValid && (subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) { - keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig, subKey.Revocations}) - } - } - } - return -} - -// ReadArmoredKeyRing reads one or more public/private keys from an armor keyring file. -func ReadArmoredKeyRing(r io.Reader) (EntityList, error) { - block, err := armor.Decode(r) - if err == io.EOF { - return nil, errors.InvalidArgumentError("no armored data found") - } - if err != nil { - return nil, err - } - if block.Type != PublicKeyType && block.Type != PrivateKeyType { - return nil, errors.InvalidArgumentError("expected public or private key block, got: " + block.Type) - } - - return ReadKeyRing(block.Body) -} - -// ReadKeyRing reads one or more public/private keys. Unsupported keys are -// ignored as long as at least a single valid key is found. -func ReadKeyRing(r io.Reader) (el EntityList, err error) { - packets := packet.NewReader(r) - var lastUnsupportedError error - - for { - var e *Entity - e, err = ReadEntity(packets) - if err != nil { - // TODO: warn about skipped unsupported/unreadable keys - if _, ok := err.(errors.UnsupportedError); ok { - lastUnsupportedError = err - err = readToNextPublicKey(packets) - } else if _, ok := err.(errors.StructuralError); ok { - // Skip unreadable, badly-formatted keys - lastUnsupportedError = err - err = readToNextPublicKey(packets) - } - if err == io.EOF { - err = nil - break - } - if err != nil { - el = nil - break - } - } else { - el = append(el, e) - } - } - - if len(el) == 0 && err == nil { - err = lastUnsupportedError - } - return -} - -// readToNextPublicKey reads packets until the start of the entity and leaves -// the first packet of the new entity in the Reader. -func readToNextPublicKey(packets *packet.Reader) (err error) { - var p packet.Packet - for { - p, err = packets.Next() - if err == io.EOF { - return - } else if err != nil { - if _, ok := err.(errors.UnsupportedError); ok { - continue - } - return - } - - if pk, ok := p.(*packet.PublicKey); ok && !pk.IsSubkey { - packets.Unread(p) - return - } - } -} - -// ReadEntity reads an entity (public key, identities, subkeys etc) from the -// given Reader. -func ReadEntity(packets *packet.Reader) (*Entity, error) { - e := new(Entity) - e.Identities = make(map[string]*Identity) - - p, err := packets.Next() - if err != nil { - return nil, err - } - - var ok bool - if e.PrimaryKey, ok = p.(*packet.PublicKey); !ok { - if e.PrivateKey, ok = p.(*packet.PrivateKey); !ok { - packets.Unread(p) - return nil, errors.StructuralError("first packet was not a public/private key") - } - e.PrimaryKey = &e.PrivateKey.PublicKey - } - - if !e.PrimaryKey.PubKeyAlgo.CanSign() { - return nil, errors.StructuralError("primary key cannot be used for signatures") - } - - var revocations []*packet.Signature - var directSignatures []*packet.Signature -EachPacket: - for { - p, err := packets.Next() - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - - switch pkt := p.(type) { - case *packet.UserId: - if err := addUserID(e, packets, pkt); err != nil { - return nil, err - } - case *packet.Signature: - if pkt.SigType == packet.SigTypeKeyRevocation { - revocations = append(revocations, pkt) - } else if pkt.SigType == packet.SigTypeDirectSignature { - directSignatures = append(directSignatures, pkt) - } - // Else, ignoring the signature as it does not follow anything - // we would know to attach it to. - case *packet.PrivateKey: - if !pkt.IsSubkey { - packets.Unread(p) - break EachPacket - } - err = addSubkey(e, packets, &pkt.PublicKey, pkt) - if err != nil { - return nil, err - } - case *packet.PublicKey: - if !pkt.IsSubkey { - packets.Unread(p) - break EachPacket - } - err = addSubkey(e, packets, pkt, nil) - if err != nil { - return nil, err - } - default: - // we ignore unknown packets. - } - } - - if len(e.Identities) == 0 && e.PrimaryKey.Version < 6 { - return nil, errors.StructuralError(fmt.Sprintf("v%d entity without any identities", e.PrimaryKey.Version)) - } - - // An implementation MUST ensure that a valid direct-key signature is present before using a v6 key. - if e.PrimaryKey.Version == 6 { - if len(directSignatures) == 0 { - return nil, errors.StructuralError("v6 entity without a valid direct-key signature") - } - // Select main direct key signature. - var mainDirectKeySelfSignature *packet.Signature - for _, directSignature := range directSignatures { - if directSignature.SigType == packet.SigTypeDirectSignature && - directSignature.CheckKeyIdOrFingerprint(e.PrimaryKey) && - (mainDirectKeySelfSignature == nil || - directSignature.CreationTime.After(mainDirectKeySelfSignature.CreationTime)) { - mainDirectKeySelfSignature = directSignature - } - } - if mainDirectKeySelfSignature == nil { - return nil, errors.StructuralError("no valid direct-key self-signature for v6 primary key found") - } - // Check that the main self-signature is valid. - err = e.PrimaryKey.VerifyDirectKeySignature(mainDirectKeySelfSignature) - if err != nil { - return nil, errors.StructuralError("invalid direct-key self-signature for v6 primary key") - } - e.SelfSignature = mainDirectKeySelfSignature - e.Signatures = directSignatures - } - - for _, revocation := range revocations { - err = e.PrimaryKey.VerifyRevocationSignature(revocation) - if err == nil { - e.Revocations = append(e.Revocations, revocation) - } else { - // TODO: RFC 4880 5.2.3.15 defines revocation keys. - return nil, errors.StructuralError("revocation signature signed by alternate key") - } - } - - return e, nil -} - -func addUserID(e *Entity, packets *packet.Reader, pkt *packet.UserId) error { - // Make a new Identity object, that we might wind up throwing away. - // We'll only add it if we get a valid self-signature over this - // userID. - identity := new(Identity) - identity.Name = pkt.Id - identity.UserId = pkt - - for { - p, err := packets.Next() - if err == io.EOF { - break - } else if err != nil { - return err - } - - sig, ok := p.(*packet.Signature) - if !ok { - packets.Unread(p) - break - } - - if sig.SigType != packet.SigTypeGenericCert && - sig.SigType != packet.SigTypePersonaCert && - sig.SigType != packet.SigTypeCasualCert && - sig.SigType != packet.SigTypePositiveCert && - sig.SigType != packet.SigTypeCertificationRevocation { - return errors.StructuralError("user ID signature with wrong type") - } - - if sig.CheckKeyIdOrFingerprint(e.PrimaryKey) { - if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, e.PrimaryKey, sig); err != nil { - return errors.StructuralError("user ID self-signature invalid: " + err.Error()) - } - if sig.SigType == packet.SigTypeCertificationRevocation { - identity.Revocations = append(identity.Revocations, sig) - } else if identity.SelfSignature == nil || sig.CreationTime.After(identity.SelfSignature.CreationTime) { - identity.SelfSignature = sig - } - identity.Signatures = append(identity.Signatures, sig) - e.Identities[pkt.Id] = identity - } else { - identity.Signatures = append(identity.Signatures, sig) - } - } - - return nil -} - -func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *packet.PrivateKey) error { - var subKey Subkey - subKey.PublicKey = pub - subKey.PrivateKey = priv - - for { - p, err := packets.Next() - if err == io.EOF { - break - } else if err != nil { - return errors.StructuralError("subkey signature invalid: " + err.Error()) - } - - sig, ok := p.(*packet.Signature) - if !ok { - packets.Unread(p) - break - } - - if sig.SigType != packet.SigTypeSubkeyBinding && sig.SigType != packet.SigTypeSubkeyRevocation { - return errors.StructuralError("subkey signature with wrong type") - } - - if err := e.PrimaryKey.VerifyKeySignature(subKey.PublicKey, sig); err != nil { - return errors.StructuralError("subkey signature invalid: " + err.Error()) - } - - switch sig.SigType { - case packet.SigTypeSubkeyRevocation: - subKey.Revocations = append(subKey.Revocations, sig) - case packet.SigTypeSubkeyBinding: - if subKey.Sig == nil || sig.CreationTime.After(subKey.Sig.CreationTime) { - subKey.Sig = sig - } - } - } - - if subKey.Sig == nil { - return errors.StructuralError("subkey packet not followed by signature") - } - - e.Subkeys = append(e.Subkeys, subKey) - - return nil -} - -// SerializePrivate serializes an Entity, including private key material, but -// excluding signatures from other entities, to the given Writer. -// Identities and subkeys are re-signed in case they changed since NewEntry. -// If config is nil, sensible defaults will be used. -func (e *Entity) SerializePrivate(w io.Writer, config *packet.Config) (err error) { - if e.PrivateKey.Dummy() { - return errors.ErrDummyPrivateKey("dummy private key cannot re-sign identities") - } - return e.serializePrivate(w, config, true) -} - -// SerializePrivateWithoutSigning serializes an Entity, including private key -// material, but excluding signatures from other entities, to the given Writer. -// Self-signatures of identities and subkeys are not re-signed. This is useful -// when serializing GNU dummy keys, among other things. -// If config is nil, sensible defaults will be used. -func (e *Entity) SerializePrivateWithoutSigning(w io.Writer, config *packet.Config) (err error) { - return e.serializePrivate(w, config, false) -} - -func (e *Entity) serializePrivate(w io.Writer, config *packet.Config, reSign bool) (err error) { - if e.PrivateKey == nil { - return goerrors.New("openpgp: private key is missing") - } - err = e.PrivateKey.Serialize(w) - if err != nil { - return - } - for _, revocation := range e.Revocations { - err := revocation.Serialize(w) - if err != nil { - return err - } - } - for _, directSignature := range e.Signatures { - err := directSignature.Serialize(w) - if err != nil { - return err - } - } - for _, ident := range e.Identities { - err = ident.UserId.Serialize(w) - if err != nil { - return - } - if reSign { - if ident.SelfSignature == nil { - return goerrors.New("openpgp: can't re-sign identity without valid self-signature") - } - err = ident.SelfSignature.SignUserId(ident.UserId.Id, e.PrimaryKey, e.PrivateKey, config) - if err != nil { - return - } - } - for _, sig := range ident.Signatures { - err = sig.Serialize(w) - if err != nil { - return err - } - } - } - for _, subkey := range e.Subkeys { - err = subkey.PrivateKey.Serialize(w) - if err != nil { - return - } - if reSign { - err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config) - if err != nil { - return - } - if subkey.Sig.EmbeddedSignature != nil { - err = subkey.Sig.EmbeddedSignature.CrossSignKey(subkey.PublicKey, e.PrimaryKey, - subkey.PrivateKey, config) - if err != nil { - return - } - } - } - for _, revocation := range subkey.Revocations { - err := revocation.Serialize(w) - if err != nil { - return err - } - } - err = subkey.Sig.Serialize(w) - if err != nil { - return - } - } - return nil -} - -// Serialize writes the public part of the given Entity to w, including -// signatures from other entities. No private key material will be output. -func (e *Entity) Serialize(w io.Writer) error { - err := e.PrimaryKey.Serialize(w) - if err != nil { - return err - } - for _, revocation := range e.Revocations { - err := revocation.Serialize(w) - if err != nil { - return err - } - } - for _, directSignature := range e.Signatures { - err := directSignature.Serialize(w) - if err != nil { - return err - } - } - for _, ident := range e.Identities { - err = ident.UserId.Serialize(w) - if err != nil { - return err - } - for _, sig := range ident.Signatures { - err = sig.Serialize(w) - if err != nil { - return err - } - } - } - for _, subkey := range e.Subkeys { - err = subkey.PublicKey.Serialize(w) - if err != nil { - return err - } - for _, revocation := range subkey.Revocations { - err := revocation.Serialize(w) - if err != nil { - return err - } - } - err = subkey.Sig.Serialize(w) - if err != nil { - return err - } - } - return nil -} - -// SignIdentity adds a signature to e, from signer, attesting that identity is -// associated with e. The provided identity must already be an element of -// e.Identities and the private key of signer must have been decrypted if -// necessary. -// If config is nil, sensible defaults will be used. -func (e *Entity) SignIdentity(identity string, signer *Entity, config *packet.Config) error { - certificationKey, ok := signer.CertificationKey(config.Now()) - if !ok { - return errors.InvalidArgumentError("no valid certification key found") - } - - if certificationKey.PrivateKey.Encrypted { - return errors.InvalidArgumentError("signing Entity's private key must be decrypted") - } - - ident, ok := e.Identities[identity] - if !ok { - return errors.InvalidArgumentError("given identity string not found in Entity") - } - - sig := createSignaturePacket(certificationKey.PublicKey, packet.SigTypeGenericCert, config) - - signingUserID := config.SigningUserId() - if signingUserID != "" { - if _, ok := signer.Identities[signingUserID]; !ok { - return errors.InvalidArgumentError("signer identity string not found in signer Entity") - } - sig.SignerUserId = &signingUserID - } - - if err := sig.SignUserId(identity, e.PrimaryKey, certificationKey.PrivateKey, config); err != nil { - return err - } - ident.Signatures = append(ident.Signatures, sig) - return nil -} - -// RevokeKey generates a key revocation signature (packet.SigTypeKeyRevocation) with the -// specified reason code and text (RFC4880 section-5.2.3.23). -// If config is nil, sensible defaults will be used. -func (e *Entity) RevokeKey(reason packet.ReasonForRevocation, reasonText string, config *packet.Config) error { - revSig := createSignaturePacket(e.PrimaryKey, packet.SigTypeKeyRevocation, config) - revSig.RevocationReason = &reason - revSig.RevocationReasonText = reasonText - - if err := revSig.RevokeKey(e.PrimaryKey, e.PrivateKey, config); err != nil { - return err - } - e.Revocations = append(e.Revocations, revSig) - return nil -} - -// RevokeSubkey generates a subkey revocation signature (packet.SigTypeSubkeyRevocation) for -// a subkey with the specified reason code and text (RFC4880 section-5.2.3.23). -// If config is nil, sensible defaults will be used. -func (e *Entity) RevokeSubkey(sk *Subkey, reason packet.ReasonForRevocation, reasonText string, config *packet.Config) error { - if err := e.PrimaryKey.VerifyKeySignature(sk.PublicKey, sk.Sig); err != nil { - return errors.InvalidArgumentError("given subkey is not associated with this key") - } - - revSig := createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyRevocation, config) - revSig.RevocationReason = &reason - revSig.RevocationReasonText = reasonText - - if err := revSig.RevokeSubkey(sk.PublicKey, e.PrivateKey, config); err != nil { - return err - } - - sk.Revocations = append(sk.Revocations, revSig) - return nil -} - -func (e *Entity) primaryDirectSignature() *packet.Signature { - return e.SelfSignature -} - -// PrimarySelfSignature searches the entity for the self-signature that stores key preferences. -// For V4 keys, returns the self-signature of the primary identity, and the identity. -// For V6 keys, returns the latest valid direct-key self-signature, and no identity (nil). -// This self-signature is to be used to check the key expiration, -// algorithm preferences, and so on. -func (e *Entity) PrimarySelfSignature() (*packet.Signature, *Identity) { - if e.PrimaryKey.Version == 6 { - return e.primaryDirectSignature(), nil - } - primaryIdentity := e.PrimaryIdentity() - if primaryIdentity == nil { - return nil, nil - } - return primaryIdentity.SelfSignature, primaryIdentity -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/keys_test_data.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/keys_test_data.go deleted file mode 100644 index 108fd096f3c..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/keys_test_data.go +++ /dev/null @@ -1,538 +0,0 @@ -package openpgp - -const expiringKeyHex = "c6c04d0451d0c680010800abbb021fd03ffc4e96618901180c3fdcb060ee69eeead97b91256d11420d80b5f1b51930248044130bd300605cf8a05b7a40d3d8cfb0a910be2e3db50dcd50a9c54064c2a5550801daa834ff4480b33d3d3ca495ff8a4e84a886977d17d998f881241a874083d8b995beab555b6d22b8a4817ab17ac3e7304f7d4d2c05c495fb2218348d3bc13651db1d92732e368a9dd7dcefa6eddff30b94706a9aaee47e9d39321460b740c59c6fc3c2fd8ab6c0fb868cb87c0051f0321301fe0f0e1820b15e7fb7063395769b525005c7e30a7ce85984f5cac00504e7b4fdc45d74958de8388436fd5c7ba9ea121f1c851b5911dd1b47a14d81a09e92ef37721e2325b6790011010001cd00c2c07b041001080025050251d0c680050900278d00060b09070803020415080a0203160201021901021b03021e01000a0910e7b484133a890a35ae4b0800a1beb82e7f28eaf5273d6af9d3391314f6280b2b624eaca2851f89a9ebcaf80ac589ebd509f168bc4322106ca2e2ce77a76e071a3c7444787d65216b5f05e82c77928860b92aace3b7d0327db59492f422eb9dfab7249266d37429870b091a98aba8724c2259ebf8f85093f21255eafa75aa841e31d94f2ac891b9755fed455e539044ee69fc47950b80e003fc9f298d695660f28329eaa38037c367efde1727458e514faf990d439a21461b719edaddf9296d3d0647b43ca56cb8dbf63b4fcf8b9968e7928c463470fab3b98e44d0d95645062f94b2d04fe56bd52822b71934db8ce845622c40b92fcbe765a142e7f38b61a6aa9606c8e8858dcd3b6eb1894acec04d0451d1f06b01080088bea67444e1789390e7c0335c86775502d58ec783d99c8ef4e06de235ed3dd4b0467f6f358d818c7d8989d43ec6d69fcbc8c32632d5a1b605e3fa8e41d695fcdcaa535936cd0157f9040dce362519803b908eafe838bb13216c885c6f93e9e8d5745607f0d062322085d6bdc760969149a8ff8dd9f5c18d9bfe2e6f63a06e17694cf1f67587c6fb70e9aebf90ffc528ca3b615ac7c9d4a21ea4f7c06f2e98fbbd90a859b8608bf9ea638e3a54289ce44c283110d0c45fa458de6251cd6e7baf71f80f12c8978340490fd90c92b81736ae902ed958e478dceae2835953d189c45d182aff02ea2be61b81d8e94430f041d638647b43e2fcb45fd512fbf5068b810011010001c2c06504180108000f050251d1f06b050900081095021b0c000a0910e7b484133a890a35e63407fe2ec88d6d1e6c9ce7553ece0cb2524747217bad29f251d33df84599ffcc900141a355abd62126800744068a5e05dc167056aa9205273dc7765a2ed49db15c2a83b8d6e6429c902136f1e12229086c1c10c0053242c2a4ae1930db58163387a48cad64607ff2153c320e42843dec28e3fce90e7399d63ac0affa2fee1f0adc0953c89eb3f46ef1d6c04328ed13b491669d5120a3782e3ffb7c69575fb77eebd108794f4dda9d34be2bae57e8e59ec8ebfda2f6f06104b2321be408ea146e2db482b00c5055c8618de36ac9716f80da2617e225556d0fce61b01c8cea2d1e0ea982c31711060ca370f2739366e1e708f38405d784b49d16a26cf62d152eae734327cec04d0451d1f07b010800d5af91c5e7c2fd8951c8d254eab0c97cdcb66822f868b79b78c366255059a68fd74ebca9adb9b970cd9e586690e6e0756705432306878c897b10a4b4ca0005966f99ac8fa4e6f9caf54bf8e53844544beee9872a7ac64c119cf1393d96e674254b661f61ee975633d0e8a8672531edb6bb8e211204e7754a9efa802342118eee850beea742bac95a3f706cc2024cf6037a308bb68162b2f53b9a6346a96e6d31871a2456186e24a1c7a82b82ac04afdfd57cd7fb9ba77a9c760d40b76a170f7be525e5fb6a9848cc726e806187710d9b190387df28700f321f988a392899f93815cc937f309129eb94d5299c5547cb2c085898e6639496e70d746c9d3fb9881d0011010001c2c06504180108000f050251d1f07b050900266305021b0c000a0910e7b484133a890a35bff207fd10dfe8c4a6ea1dd30568012b6fd6891a763c87ad0f7a1d112aad9e8e3239378a3b85588c235865bac2e614348cb4f216d7217f53b3ef48c192e0a4d31d64d7bfa5faccf21155965fa156e887056db644a05ad08a85cc6152d1377d9e37b46f4ff462bbe68ace2dc586ef90070314576c985d8037c2ba63f0a7dc17a62e15bd77e88bc61d9d00858979709f12304264a4cf4225c5cf86f12c8e19486cb9cdcc69f18f027e5f16f4ca8b50e28b3115eaff3a345acd21f624aef81f6ede515c1b55b26b84c1e32264754eab672d5489b287e7277ea855e0a5ff2aa9e8b8c76d579a964ec225255f4d57bf66639ccb34b64798846943e162a41096a7002ca21c7f56" -const subkeyUsageHex = "988d04533a52bc010400d26af43085558f65b9e7dbc90cb9238015259aed5e954637adcfa2181548b2d0b60c65f1f42ec5081cbf1bc0a8aa4900acfb77070837c58f26012fbce297d70afe96e759ad63531f0037538e70dbf8e384569b9720d99d8eb39d8d0a2947233ed242436cb6ac7dfe74123354b3d0119b5c235d3dd9c9d6c004f8ffaf67ad8583001101000188b7041f010200210502533b8552170c8001ce094aa433f7040bb2ddf0be3893cb843d0fe70c020700000a0910a42704b92866382aa98404009d63d916a27543da4221c60087c33f1c44bec9998c5438018ed370cca4962876c748e94b73eb39c58eb698063f3fd6346d58dd2a11c0247934c4a9d71f24754f7468f96fb24c3e791dd2392b62f626148ad724189498cbf993db2df7c0cdc2d677c35da0f16cb16c9ce7c33b4de65a4a91b1d21a130ae9cc26067718910ef8e2b417556d627261203c756d627261407379642e65642e61753e88b80413010200220502533a52bc021b03060b090807030206150802090a0b0416020301021e01021780000a0910a42704b92866382a47840400c0c2bd04f5fca586de408b395b3c280a278259c93eaaa8b79a53b97003f8ed502a8a00446dd9947fb462677e4fcac0dac2f0701847d15130aadb6cd9e0705ea0cf5f92f129136c7be21a718d46c8e641eb7f044f2adae573e11ae423a0a9ca51324f03a8a2f34b91fa40c3cc764bee4dccadedb54c768ba0469b683ea53f1c29b88d04533a52bc01040099c92a5d6f8b744224da27bc2369127c35269b58bec179de6bbc038f749344222f85a31933224f26b70243c4e4b2d242f0c4777eaef7b5502f9dad6d8bf3aaeb471210674b74de2d7078af497d55f5cdad97c7bedfbc1b41e8065a97c9c3d344b21fc81d27723af8e374bc595da26ea242dccb6ae497be26eea57e563ed517e90011010001889f0418010200090502533a52bc021b0c000a0910a42704b92866382afa1403ff70284c2de8a043ff51d8d29772602fa98009b7861c540535f874f2c230af8caf5638151a636b21f8255003997ccd29747fdd06777bb24f9593bd7d98a3e887689bf902f999915fcc94625ae487e5d13e6616f89090ebc4fdc7eb5cad8943e4056995bb61c6af37f8043016876a958ec7ebf39c43d20d53b7f546cfa83e8d2604b88d04533b8283010400c0b529316dbdf58b4c54461e7e669dc11c09eb7f73819f178ccd4177b9182b91d138605fcf1e463262fabefa73f94a52b5e15d1904635541c7ea540f07050ce0fb51b73e6f88644cec86e91107c957a114f69554548a85295d2b70bd0b203992f76eb5d493d86d9eabcaa7ef3fc7db7e458438db3fcdb0ca1cc97c638439a9170011010001889f0418010200090502533b8283021b0c000a0910a42704b92866382adc6d0400cfff6258485a21675adb7a811c3e19ebca18851533f75a7ba317950b9997fda8d1a4c8c76505c08c04b6c2cc31dc704d33da36a21273f2b388a1a706f7c3378b66d887197a525936ed9a69acb57fe7f718133da85ec742001c5d1864e9c6c8ea1b94f1c3759cebfd93b18606066c063a63be86085b7e37bdbc65f9a915bf084bb901a204533b85cd110400aed3d2c52af2b38b5b67904b0ef73d6dd7aef86adb770e2b153cd22489654dcc91730892087bb9856ae2d9f7ed1eb48f214243fe86bfe87b349ebd7c30e630e49c07b21fdabf78b7a95c8b7f969e97e3d33f2e074c63552ba64a2ded7badc05ce0ea2be6d53485f6900c7860c7aa76560376ce963d7271b9b54638a4028b573f00a0d8854bfcdb04986141568046202192263b9b67350400aaa1049dbc7943141ef590a70dcb028d730371d92ea4863de715f7f0f16d168bd3dc266c2450457d46dcbbf0b071547e5fbee7700a820c3750b236335d8d5848adb3c0da010e998908dfd93d961480084f3aea20b247034f8988eccb5546efaa35a92d0451df3aaf1aee5aa36a4c4d462c760ecd9cebcabfbe1412b1f21450f203fd126687cd486496e971a87fd9e1a8a765fe654baa219a6871ab97768596ab05c26c1aeea8f1a2c72395a58dbc12ef9640d2b95784e974a4d2d5a9b17c25fedacfe551bda52602de8f6d2e48443f5dd1a2a2a8e6a5e70ecdb88cd6e766ad9745c7ee91d78cc55c3d06536b49c3fee6c3d0b6ff0fb2bf13a314f57c953b8f4d93bf88e70418010200090502533b85cd021b0200520910a42704b92866382a47200419110200060502533b85cd000a091042ce2c64bc0ba99214b2009e26b26852c8b13b10c35768e40e78fbbb48bd084100a0c79d9ea0844fa5853dd3c85ff3ecae6f2c9dd6c557aa04008bbbc964cd65b9b8299d4ebf31f41cc7264b8cf33a00e82c5af022331fac79efc9563a822497ba012953cefe2629f1242fcdcb911dbb2315985bab060bfd58261ace3c654bdbbe2e8ed27a46e836490145c86dc7bae15c011f7e1ffc33730109b9338cd9f483e7cef3d2f396aab5bd80efb6646d7e778270ee99d934d187dd98" -const revokedKeyHex = "988d045331ce82010400c4fdf7b40a5477f206e6ee278eaef888ca73bf9128a9eef9f2f1ddb8b7b71a4c07cfa241f028a04edb405e4d916c61d6beabc333813dc7b484d2b3c52ee233c6a79b1eea4e9cc51596ba9cd5ac5aeb9df62d86ea051055b79d03f8a4fa9f38386f5bd17529138f3325d46801514ea9047977e0829ed728e68636802796801be10011010001889f04200102000905025331d0e3021d03000a0910a401d9f09a34f7c042aa040086631196405b7e6af71026b88e98012eab44aa9849f6ef3fa930c7c9f23deaedba9db1538830f8652fb7648ec3fcade8dbcbf9eaf428e83c6cbcc272201bfe2fbb90d41963397a7c0637a1a9d9448ce695d9790db2dc95433ad7be19eb3de72dacf1d6db82c3644c13eae2a3d072b99bb341debba012c5ce4006a7d34a1f4b94b444526567205265766f6b657220283c52656727732022424d204261726973746122204b657920262530305c303e5c29203c72656740626d626172697374612e636f2e61753e88b704130102002205025331ce82021b03060b090807030206150802090a0b0416020301021e01021780000a0910a401d9f09a34f7c0019c03f75edfbeb6a73e7225ad3cc52724e2872e04260d7daf0d693c170d8c4b243b8767bc7785763533febc62ec2600c30603c433c095453ede59ff2fcabeb84ce32e0ed9d5cf15ffcbc816202b64370d4d77c1e9077d74e94a16fb4fa2e5bec23a56d7a73cf275f91691ae1801a976fcde09e981a2f6327ac27ea1fecf3185df0d56889c04100102000605025331cfb5000a0910fe9645554e8266b64b4303fc084075396674fb6f778d302ac07cef6bc0b5d07b66b2004c44aef711cbac79617ef06d836b4957522d8772dd94bf41a2f4ac8b1ee6d70c57503f837445a74765a076d07b829b8111fc2a918423ddb817ead7ca2a613ef0bfb9c6b3562aec6c3cf3c75ef3031d81d95f6563e4cdcc9960bcb386c5d757b104fcca5fe11fc709df884604101102000605025331cfe7000a09107b15a67f0b3ddc0317f6009e360beea58f29c1d963a22b962b80788c3fa6c84e009d148cfde6b351469b8eae91187eff07ad9d08fcaab88d045331ce820104009f25e20a42b904f3fa555530fe5c46737cf7bd076c35a2a0d22b11f7e0b61a69320b768f4a80fe13980ce380d1cfc4a0cd8fbe2d2e2ef85416668b77208baa65bf973fe8e500e78cc310d7c8705cdb34328bf80e24f0385fce5845c33bc7943cf6b11b02348a23da0bf6428e57c05135f2dc6bd7c1ce325d666d5a5fd2fd5e410011010001889f04180102000905025331ce82021b0c000a0910a401d9f09a34f7c0418003fe34feafcbeaef348a800a0d908a7a6809cc7304017d820f70f0474d5e23cb17e38b67dc6dca282c6ca00961f4ec9edf2738d0f087b1d81e4871ef08e1798010863afb4eac4c44a376cb343be929c5be66a78cfd4456ae9ec6a99d97f4e1c3ff3583351db2147a65c0acef5c003fb544ab3a2e2dc4d43646f58b811a6c3a369d1f" -const revokedSubkeyHex = "988d04533121f6010400aefc803a3e4bb1a61c86e8a86d2726c6a43e0079e9f2713f1fa017e9854c83877f4aced8e331d675c67ea83ddab80aacbfa0b9040bb12d96f5a3d6be09455e2a76546cbd21677537db941cab710216b6d24ec277ee0bd65b910f416737ed120f6b93a9d3b306245c8cfd8394606fdb462e5cf43c551438d2864506c63367fc890011010001b41d416c696365203c616c69636540626d626172697374612e636f2e61753e88bb041301020025021b03060b090807030206150802090a0b0416020301021e01021780050253312798021901000a09104ef7e4beccde97f015a803ff5448437780f63263b0df8442a995e7f76c221351a51edd06f2063d8166cf3157aada4923dfc44aa0f2a6a4da5cf83b7fe722ba8ab416c976e77c6b5682e7f1069026673bd0de56ba06fd5d7a9f177607f277d9b55ff940a638c3e68525c67517e2b3d976899b93ca267f705b3e5efad7d61220e96b618a4497eab8d04403d23f8846041011020006050253312910000a09107b15a67f0b3ddc03d96e009f50b6365d86c4be5d5e9d0ea42d5e56f5794c617700a0ab274e19c2827780016d23417ce89e0a2c0d987d889c04100102000605025331cf7a000a0910a401d9f09a34f7c0ee970400aca292f213041c9f3b3fc49148cbda9d84afee6183c8dd6c5ff2600b29482db5fecd4303797be1ee6d544a20a858080fec43412061c9a71fae4039fd58013b4ae341273e6c66ad4c7cdd9e68245bedb260562e7b166f2461a1032f2b38c0e0e5715fb3d1656979e052b55ca827a76f872b78a9fdae64bc298170bfcebedc1271b41a416c696365203c616c696365407379646973702e6f722e61753e88b804130102002205025331278b021b03060b090807030206150802090a0b0416020301021e01021780000a09104ef7e4beccde97f06a7003fa03c3af68d272ebc1fa08aa72a03b02189c26496a2833d90450801c4e42c5b5f51ad96ce2d2c9cef4b7c02a6a2fcf1412d6a2d486098eb762f5010a201819c17fd2888aec8eda20c65a3b75744de7ee5cc8ac7bfc470cbe3cb982720405a27a3c6a8c229cfe36905f881b02ed5680f6a8f05866efb9d6c5844897e631deb949ca8846041011020006050253312910000a09107b15a67f0b3ddc0347bc009f7fa35db59147469eb6f2c5aaf6428accb138b22800a0caa2f5f0874bacc5909c652a57a31beda65eddd5889c04100102000605025331cf7a000a0910a401d9f09a34f7c0316403ff46f2a5c101256627f16384d34a38fb47a6c88ba60506843e532d91614339fccae5f884a5741e7582ffaf292ba38ee10a270a05f139bde3814b6a077e8cd2db0f105ebea2a83af70d385f13b507fac2ad93ff79d84950328bb86f3074745a8b7f9b64990fb142e2a12976e27e8d09a28dc5621f957ac49091116da410ac3cbde1b88d04533121f6010400cbd785b56905e4192e2fb62a720727d43c4fa487821203cf72138b884b78b701093243e1d8c92a0248a6c0203a5a88693da34af357499abacaf4b3309c640797d03093870a323b4b6f37865f6eaa2838148a67df4735d43a90ca87942554cdf1c4a751b1e75f9fd4ce4e97e278d6c1c7ed59d33441df7d084f3f02beb68896c70011010001889f0418010200090502533121f6021b0c000a09104ef7e4beccde97f0b98b03fc0a5ccf6a372995835a2f5da33b282a7d612c0ab2a97f59cf9fff73e9110981aac2858c41399afa29624a7fd8a0add11654e3d882c0fd199e161bdad65e5e2548f7b68a437ea64293db1246e3011cbb94dc1bcdeaf0f2539bd88ff16d95547144d97cead6a8c5927660a91e6db0d16eb36b7b49a3525b54d1644e65599b032b7eb901a204533127a0110400bd3edaa09eff9809c4edc2c2a0ebe52e53c50a19c1e49ab78e6167bf61473bb08f2050d78a5cbbc6ed66aff7b42cd503f16b4a0b99fa1609681fca9b7ce2bbb1a5b3864d6cdda4d7ef7849d156d534dea30fb0efb9e4cf8959a2b2ce623905882d5430b995a15c3b9fe92906086788b891002924f94abe139b42cbbfaaabe42f00a0b65dc1a1ad27d798adbcb5b5ad02d2688c89477b03ff4eebb6f7b15a73b96a96bed201c0e5e4ea27e4c6e2dd1005b94d4b90137a5b1cf5e01c6226c070c4cc999938101578877ee76d296b9aab8246d57049caacf489e80a3f40589cade790a020b1ac146d6f7a6241184b8c7fcde680eae3188f5dcbe846d7f7bdad34f6fcfca08413e19c1d5df83fc7c7c627d493492e009c2f52a80400a2fe82de87136fd2e8845888c4431b032ba29d9a29a804277e31002a8201fb8591a3e55c7a0d0881496caf8b9fb07544a5a4879291d0dc026a0ea9e5bd88eb4aa4947bbd694b25012e208a250d65ddc6f1eea59d3aed3b4ec15fcab85e2afaa23a40ab1ef9ce3e11e1bc1c34a0e758e7aa64deb8739276df0af7d4121f834a9b88e70418010200090502533127a0021b02005209104ef7e4beccde97f047200419110200060502533127a0000a0910dbce4ee19529437fe045009c0b32f5ead48ee8a7e98fac0dea3d3e6c0e2c552500a0ad71fadc5007cfaf842d9b7db3335a8cdad15d3d1a6404009b08e2c68fe8f3b45c1bb72a4b3278cdf3012aa0f229883ad74aa1f6000bb90b18301b2f85372ca5d6b9bf478d235b733b1b197d19ccca48e9daf8e890cb64546b4ce1b178faccfff07003c172a2d4f5ebaba9f57153955f3f61a9b80a4f5cb959908f8b211b03b7026a8a82fc612bfedd3794969bcf458c4ce92be215a1176ab88d045331d144010400a5063000c5aaf34953c1aa3bfc95045b3aab9882b9a8027fecfe2142dc6b47ba8aca667399990244d513dd0504716908c17d92c65e74219e004f7b83fc125e575dd58efec3ab6dd22e3580106998523dea42ec75bf9aa111734c82df54630bebdff20fe981cfc36c76f865eb1c2fb62c9e85bc3a6e5015a361a2eb1c8431578d0011010001889f04280102000905025331d433021d03000a09104ef7e4beccde97f02e5503ff5e0630d1b65291f4882b6d40a29da4616bb5088717d469fbcc3648b8276de04a04988b1f1b9f3e18f52265c1f8b6c85861691c1a6b8a3a25a1809a0b32ad330aec5667cb4262f4450649184e8113849b05e5ad06a316ea80c001e8e71838190339a6e48bbde30647bcf245134b9a97fa875c1d83a9862cae87ffd7e2c4ce3a1b89013d04180102000905025331d144021b0200a809104ef7e4beccde97f09d2004190102000605025331d144000a0910677815e371c2fd23522203fe22ab62b8e7a151383cea3edd3a12995693911426f8ccf125e1f6426388c0010f88d9ca7da2224aee8d1c12135998640c5e1813d55a93df472faae75bef858457248db41b4505827590aeccf6f9eb646da7f980655dd3050c6897feddddaca90676dee856d66db8923477d251712bb9b3186b4d0114daf7d6b59272b53218dd1da94a03ff64006fcbe71211e5daecd9961fba66cdb6de3f914882c58ba5beddeba7dcb950c1156d7fba18c19ea880dccc800eae335deec34e3b84ac75ffa24864f782f87815cda1c0f634b3dd2fa67cea30811d21723d21d9551fa12ccbcfa62b6d3a15d01307b99925707992556d50065505b090aadb8579083a20fe65bd2a270da9b011" - -const missingCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- -Charset: UTF-8 - -mQENBFMYynYBCACVOZ3/e8Bm2b9KH9QyIlHGo/i1bnkpqsgXj8tpJ2MIUOnXMMAY -ztW7kKFLCmgVdLIC0vSoLA4yhaLcMojznh/2CcUglZeb6Ao8Gtelr//Rd5DRfPpG -zqcfUo+m+eO1co2Orabw0tZDfGpg5p3AYl0hmxhUyYSc/xUq93xL1UJzBFgYXY54 -QsM8dgeQgFseSk/YvdP5SMx1ev+eraUyiiUtWzWrWC1TdyRa5p4UZg6Rkoppf+WJ -QrW6BWrhAtqATHc8ozV7uJjeONjUEq24roRc/OFZdmQQGK6yrzKnnbA6MdHhqpdo -9kWDcXYb7pSE63Lc+OBa5X2GUVvXJLS/3nrtABEBAAG0F2ludmFsaWQtc2lnbmlu -Zy1zdWJrZXlziQEoBBMBAgASBQJTnKB5AhsBAgsHAhUIAh4BAAoJEO3UDQUIHpI/ -dN4H/idX4FQ1LIZCnpHS/oxoWQWfpRgdKAEM0qCqjMgiipJeEwSQbqjTCynuh5/R -JlODDz85ABR06aoF4l5ebGLQWFCYifPnJZ/Yf5OYcMGtb7dIbqxWVFL9iLMO/oDL -ioI3dotjPui5e+2hI9pVH1UHB/bZ/GvMGo6Zg0XxLPolKQODMVjpjLAQ0YJ3spew -RAmOGre6tIvbDsMBnm8qREt7a07cBJ6XK7xjxYaZHQBiHVxyEWDa6gyANONx8duW -/fhQ/zDTnyVM/ik6VO0Ty9BhPpcEYLFwh5c1ilFari1ta3e6qKo6ZGa9YMk/REhu -yBHd9nTkI+0CiQUmbckUiVjDKKe5AQ0EUxjKdgEIAJcXQeP+NmuciE99YcJoffxv -2gVLU4ZXBNHEaP0mgaJ1+tmMD089vUQAcyGRvw8jfsNsVZQIOAuRxY94aHQhIRHR -bUzBN28ofo/AJJtfx62C15xt6fDKRV6HXYqAiygrHIpEoRLyiN69iScUsjIJeyFL -C8wa72e8pSL6dkHoaV1N9ZH/xmrJ+k0vsgkQaAh9CzYufncDxcwkoP+aOlGtX1gP -WwWoIbz0JwLEMPHBWvDDXQcQPQTYQyj+LGC9U6f9VZHN25E94subM1MjuT9OhN9Y -MLfWaaIc5WyhLFyQKW2Upofn9wSFi8ubyBnv640Dfd0rVmaWv7LNTZpoZ/GbJAMA -EQEAAYkBHwQYAQIACQUCU5ygeQIbAgAKCRDt1A0FCB6SP0zCB/sEzaVR38vpx+OQ -MMynCBJrakiqDmUZv9xtplY7zsHSQjpd6xGflbU2n+iX99Q+nav0ETQZifNUEd4N -1ljDGQejcTyKD6Pkg6wBL3x9/RJye7Zszazm4+toJXZ8xJ3800+BtaPoI39akYJm -+ijzbskvN0v/j5GOFJwQO0pPRAFtdHqRs9Kf4YanxhedB4dIUblzlIJuKsxFit6N -lgGRblagG3Vv2eBszbxzPbJjHCgVLR3RmrVezKOsZjr/2i7X+xLWIR0uD3IN1qOW -CXQxLBizEEmSNVNxsp7KPGTLnqO3bPtqFirxS9PJLIMPTPLNBY7ZYuPNTMqVIUWF -4artDmrG -=7FfJ ------END PGP PUBLIC KEY BLOCK-----` - -const invalidCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -mQENBFMYynYBCACVOZ3/e8Bm2b9KH9QyIlHGo/i1bnkpqsgXj8tpJ2MIUOnXMMAY -ztW7kKFLCmgVdLIC0vSoLA4yhaLcMojznh/2CcUglZeb6Ao8Gtelr//Rd5DRfPpG -zqcfUo+m+eO1co2Orabw0tZDfGpg5p3AYl0hmxhUyYSc/xUq93xL1UJzBFgYXY54 -QsM8dgeQgFseSk/YvdP5SMx1ev+eraUyiiUtWzWrWC1TdyRa5p4UZg6Rkoppf+WJ -QrW6BWrhAtqATHc8ozV7uJjeONjUEq24roRc/OFZdmQQGK6yrzKnnbA6MdHhqpdo -9kWDcXYb7pSE63Lc+OBa5X2GUVvXJLS/3nrtABEBAAG0F2ludmFsaWQtc2lnbmlu -Zy1zdWJrZXlziQEoBBMBAgASBQJTnKB5AhsBAgsHAhUIAh4BAAoJEO3UDQUIHpI/ -dN4H/idX4FQ1LIZCnpHS/oxoWQWfpRgdKAEM0qCqjMgiipJeEwSQbqjTCynuh5/R -JlODDz85ABR06aoF4l5ebGLQWFCYifPnJZ/Yf5OYcMGtb7dIbqxWVFL9iLMO/oDL -ioI3dotjPui5e+2hI9pVH1UHB/bZ/GvMGo6Zg0XxLPolKQODMVjpjLAQ0YJ3spew -RAmOGre6tIvbDsMBnm8qREt7a07cBJ6XK7xjxYaZHQBiHVxyEWDa6gyANONx8duW -/fhQ/zDTnyVM/ik6VO0Ty9BhPpcEYLFwh5c1ilFari1ta3e6qKo6ZGa9YMk/REhu -yBHd9nTkI+0CiQUmbckUiVjDKKe5AQ0EUxjKdgEIAIINDqlj7X6jYKc6DjwrOkjQ -UIRWbQQar0LwmNilehmt70g5DCL1SYm9q4LcgJJ2Nhxj0/5qqsYib50OSWMcKeEe -iRXpXzv1ObpcQtI5ithp0gR53YPXBib80t3bUzomQ5UyZqAAHzMp3BKC54/vUrSK -FeRaxDzNLrCeyI00+LHNUtwghAqHvdNcsIf8VRumK8oTm3RmDh0TyjASWYbrt9c8 -R1Um3zuoACOVy+mEIgIzsfHq0u7dwYwJB5+KeM7ZLx+HGIYdUYzHuUE1sLwVoELh -+SHIGHI1HDicOjzqgajShuIjj5hZTyQySVprrsLKiXS6NEwHAP20+XjayJ/R3tEA -EQEAAYkCPgQYAQIBKAUCU5ygeQIbAsBdIAQZAQIABgUCU5ygeQAKCRCpVlnFZmhO -52RJB/9uD1MSa0wjY6tHOIgquZcP3bHBvHmrHNMw9HR2wRCMO91ZkhrpdS3ZHtgb -u3/55etj0FdvDo1tb8P8FGSVtO5Vcwf5APM8sbbqoi8L951Q3i7qt847lfhu6sMl -w0LWFvPTOLHrliZHItPRjOltS1WAWfr2jUYhsU9ytaDAJmvf9DujxEOsN5G1YJep -54JCKVCkM/y585Zcnn+yxk/XwqoNQ0/iJUT9qRrZWvoeasxhl1PQcwihCwss44A+ -YXaAt3hbk+6LEQuZoYS73yR3WHj+42tfm7YxRGeubXfgCEz/brETEWXMh4pe0vCL -bfWrmfSPq2rDegYcAybxRQz0lF8PAAoJEO3UDQUIHpI/exkH/0vQfdHA8g/N4T6E -i6b1CUVBAkvtdJpCATZjWPhXmShOw62gkDw306vHPilL4SCvEEi4KzG72zkp6VsB -DSRcpxCwT4mHue+duiy53/aRMtSJ+vDfiV1Vhq+3sWAck/yUtfDU9/u4eFaiNok1 -8/Gd7reyuZt5CiJnpdPpjCwelK21l2w7sHAnJF55ITXdOxI8oG3BRKufz0z5lyDY -s2tXYmhhQIggdgelN8LbcMhWs/PBbtUr6uZlNJG2lW1yscD4aI529VjwJlCeo745 -U7pO4eF05VViUJ2mmfoivL3tkhoTUWhx8xs8xCUcCg8DoEoSIhxtOmoTPR22Z9BL -6LCg2mg= -=Dhm4 ------END PGP PUBLIC KEY BLOCK-----` - -const goodCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -mI0EVUqeVwEEAMufHRrMPWK3gyvi0O0tABCs/oON9zV9KDZlr1a1M91ShCSFwCPo -7r80PxdWVWcj0V5h50/CJYtpN3eE/mUIgW2z1uDYQF1OzrQ8ubrksfsJvpAhENom -lTQEppv9mV8qhcM278teb7TX0pgrUHLYF5CfPdp1L957JLLXoQR/lwLVABEBAAG0 -E2dvb2Qtc2lnbmluZy1zdWJrZXmIuAQTAQIAIgUCVUqeVwIbAwYLCQgHAwIGFQgC -CQoLBBYCAwECHgECF4AACgkQNRjL95IRWP69XQQAlH6+eyXJN4DZTLX78KGjHrsw -6FCvxxClEPtPUjcJy/1KCRQmtLAt9PbbA78dvgzjDeZMZqRAwdjyJhjyg/fkU2OH -7wq4ktjUu+dLcOBb+BFMEY+YjKZhf6EJuVfxoTVr5f82XNPbYHfTho9/OABKH6kv -X70PaKZhbwnwij8Nts65AaIEVUqftREEAJ3WxZfqAX0bTDbQPf2CMT2IVMGDfhK7 -GyubOZgDFFjwUJQvHNvsrbeGLZ0xOBumLINyPO1amIfTgJNm1iiWFWfmnHReGcDl -y5mpYG60Mb79Whdcer7CMm3AqYh/dW4g6IB02NwZMKoUHo3PXmFLxMKXnWyJ0clw -R0LI/Qn509yXAKDh1SO20rqrBM+EAP2c5bfI98kyNwQAi3buu94qo3RR1ZbvfxgW -CKXDVm6N99jdZGNK7FbRifXqzJJDLcXZKLnstnC4Sd3uyfyf1uFhmDLIQRryn5m+ -LBYHfDBPN3kdm7bsZDDq9GbTHiFZUfm/tChVKXWxkhpAmHhU/tH6GGzNSMXuIWSO -aOz3Rqq0ED4NXyNKjdF9MiwD/i83S0ZBc0LmJYt4Z10jtH2B6tYdqnAK29uQaadx -yZCX2scE09UIm32/w7pV77CKr1Cp/4OzAXS1tmFzQ+bX7DR+Gl8t4wxr57VeEMvl -BGw4Vjh3X8//m3xynxycQU18Q1zJ6PkiMyPw2owZ/nss3hpSRKFJsxMLhW3fKmKr -Ey2KiOcEGAECAAkFAlVKn7UCGwIAUgkQNRjL95IRWP5HIAQZEQIABgUCVUqftQAK -CRD98VjDN10SqkWrAKDTpEY8D8HC02E/KVC5YUI01B30wgCgurpILm20kXEDCeHp -C5pygfXw1DJrhAP+NyPJ4um/bU1I+rXaHHJYroYJs8YSweiNcwiHDQn0Engh/mVZ -SqLHvbKh2dL/RXymC3+rjPvQf5cup9bPxNMa6WagdYBNAfzWGtkVISeaQW+cTEp/ -MtgVijRGXR/lGLGETPg2X3Afwn9N9bLMBkBprKgbBqU7lpaoPupxT61bL70= -=vtbN ------END PGP PUBLIC KEY BLOCK-----` - -const revokedUserIDKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -mQENBFsgO5EBCADhREPmcjsPkXe1z7ctvyWL0S7oa9JaoGZ9oPDHFDlQxd0qlX2e -DZJZDg0qYvVixmaULIulApq1puEsaJCn3lHUbHlb4PYKwLEywYXM28JN91KtLsz/ -uaEX2KC5WqeP40utmzkNLq+oRX/xnRMgwbO7yUNVG2UlEa6eI+xOXO3YtLdmJMBW -ClQ066ZnOIzEo1JxnIwha1CDBMWLLfOLrg6l8InUqaXbtEBbnaIYO6fXVXELUjkx -nmk7t/QOk0tXCy8muH9UDqJkwDUESY2l79XwBAcx9riX8vY7vwC34pm22fAUVLCJ -x1SJx0J8bkeNp38jKM2Zd9SUQqSbfBopQ4pPABEBAAG0I0dvbGFuZyBHb3BoZXIg -PG5vLXJlcGx5QGdvbGFuZy5jb20+iQFUBBMBCgA+FiEE5Ik5JLcNx6l6rZfw1oFy -9I6cUoMFAlsgO5ECGwMFCQPCZwAFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQ -1oFy9I6cUoMIkwf8DNPeD23i4jRwd/pylbvxwZintZl1fSwTJW1xcOa1emXaEtX2 -depuqhP04fjlRQGfsYAQh7X9jOJxAHjTmhqFBi5sD7QvKU00cPFYbJ/JTx0B41bl -aXnSbGhRPh63QtEZL7ACAs+shwvvojJqysx7kyVRu0EW2wqjXdHwR/SJO6nhNBa2 -DXzSiOU/SUA42mmG+5kjF8Aabq9wPwT9wjraHShEweNerNMmOqJExBOy3yFeyDpa -XwEZFzBfOKoxFNkIaVf5GSdIUGhFECkGvBMB935khftmgR8APxdU4BE7XrXexFJU -8RCuPXonm4WQOwTWR0vQg64pb2WKAzZ8HhwTGbQiR29sYW5nIEdvcGhlciA8cmV2 -b2tlZEBnb2xhbmcuY29tPokBNgQwAQoAIBYhBOSJOSS3Dcepeq2X8NaBcvSOnFKD -BQJbIDv3Ah0AAAoJENaBcvSOnFKDfWMIAKhI/Tvu3h8fSUxp/gSAcduT6bC1JttG -0lYQ5ilKB/58lBUA5CO3ZrKDKlzW3M8VEcvohVaqeTMKeoQd5rCZq8KxHn/KvN6N -s85REfXfniCKfAbnGgVXX3kDmZ1g63pkxrFu0fDZjVDXC6vy+I0sGyI/Inro0Pzb -tvn0QCsxjapKK15BtmSrpgHgzVqVg0cUp8vqZeKFxarYbYB2idtGRci4b9tObOK0 -BSTVFy26+I/mrFGaPrySYiy2Kz5NMEcRhjmTxJ8jSwEr2O2sUR0yjbgUAXbTxDVE -/jg5fQZ1ACvBRQnB7LvMHcInbzjyeTM3FazkkSYQD6b97+dkWwb1iWG5AQ0EWyA7 -kQEIALkg04REDZo1JgdYV4x8HJKFS4xAYWbIva1ZPqvDNmZRUbQZR2+gpJGEwn7z -VofGvnOYiGW56AS5j31SFf5kro1+1bZQ5iOONBng08OOo58/l1hRseIIVGB5TGSa -PCdChKKHreJI6hS3mShxH6hdfFtiZuB45rwoaArMMsYcjaezLwKeLc396cpUwwcZ -snLUNd1Xu5EWEF2OdFkZ2a1qYdxBvAYdQf4+1Nr+NRIx1u1NS9c8jp3PuMOkrQEi -bNtc1v6v0Jy52mKLG4y7mC/erIkvkQBYJdxPaP7LZVaPYc3/xskcyijrJ/5ufoD8 -K71/ShtsZUXSQn9jlRaYR0EbojMAEQEAAYkBPAQYAQoAJhYhBOSJOSS3Dcepeq2X -8NaBcvSOnFKDBQJbIDuRAhsMBQkDwmcAAAoJENaBcvSOnFKDkFMIAIt64bVZ8x7+ -TitH1bR4pgcNkaKmgKoZz6FXu80+SnbuEt2NnDyf1cLOSimSTILpwLIuv9Uft5Pb -OraQbYt3xi9yrqdKqGLv80bxqK0NuryNkvh9yyx5WoG1iKqMj9/FjGghuPrRaT4l -QinNAghGVkEy1+aXGFrG2DsOC1FFI51CC2WVTzZ5RwR2GpiNRfESsU1rZAUqf/2V -yJl9bD5R4SUNy8oQmhOxi+gbhD4Ao34e4W0ilibslI/uawvCiOwlu5NGd8zv5n+U -heiQvzkApQup5c+BhH5zFDFdKJ2CBByxw9+7QjMFI/wgLixKuE0Ob2kAokXf7RlB -7qTZOahrETw= -=IKnw ------END PGP PUBLIC KEY BLOCK-----` - -const keyWithFirstUserIDRevoked = `-----BEGIN PGP PUBLIC KEY BLOCK----- -Version: OpenPGP.js v4.10.10 -Comment: https://openpgpjs.org - -xsBNBFsgO5EBCADhREPmcjsPkXe1z7ctvyWL0S7oa9JaoGZ9oPDHFDlQxd0q -lX2eDZJZDg0qYvVixmaULIulApq1puEsaJCn3lHUbHlb4PYKwLEywYXM28JN -91KtLsz/uaEX2KC5WqeP40utmzkNLq+oRX/xnRMgwbO7yUNVG2UlEa6eI+xO -XO3YtLdmJMBWClQ066ZnOIzEo1JxnIwha1CDBMWLLfOLrg6l8InUqaXbtEBb -naIYO6fXVXELUjkxnmk7t/QOk0tXCy8muH9UDqJkwDUESY2l79XwBAcx9riX -8vY7vwC34pm22fAUVLCJx1SJx0J8bkeNp38jKM2Zd9SUQqSbfBopQ4pPABEB -AAHNIkdvbGFuZyBHb3BoZXIgPHJldm9rZWRAZ29sYW5nLmNvbT7CwI0EMAEK -ACAWIQTkiTkktw3HqXqtl/DWgXL0jpxSgwUCWyA79wIdAAAhCRDWgXL0jpxS -gxYhBOSJOSS3Dcepeq2X8NaBcvSOnFKDfWMIAKhI/Tvu3h8fSUxp/gSAcduT -6bC1JttG0lYQ5ilKB/58lBUA5CO3ZrKDKlzW3M8VEcvohVaqeTMKeoQd5rCZ -q8KxHn/KvN6Ns85REfXfniCKfAbnGgVXX3kDmZ1g63pkxrFu0fDZjVDXC6vy -+I0sGyI/Inro0Pzbtvn0QCsxjapKK15BtmSrpgHgzVqVg0cUp8vqZeKFxarY -bYB2idtGRci4b9tObOK0BSTVFy26+I/mrFGaPrySYiy2Kz5NMEcRhjmTxJ8j -SwEr2O2sUR0yjbgUAXbTxDVE/jg5fQZ1ACvBRQnB7LvMHcInbzjyeTM3Fazk -kSYQD6b97+dkWwb1iWHNI0dvbGFuZyBHb3BoZXIgPG5vLXJlcGx5QGdvbGFu -Zy5jb20+wsCrBBMBCgA+FiEE5Ik5JLcNx6l6rZfw1oFy9I6cUoMFAlsgO5EC -GwMFCQPCZwAFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AAIQkQ1oFy9I6cUoMW -IQTkiTkktw3HqXqtl/DWgXL0jpxSgwiTB/wM094PbeLiNHB3+nKVu/HBmKe1 -mXV9LBMlbXFw5rV6ZdoS1fZ16m6qE/Th+OVFAZ+xgBCHtf2M4nEAeNOaGoUG -LmwPtC8pTTRw8Vhsn8lPHQHjVuVpedJsaFE+HrdC0RkvsAICz6yHC++iMmrK -zHuTJVG7QRbbCqNd0fBH9Ik7qeE0FrYNfNKI5T9JQDjaaYb7mSMXwBpur3A/ -BP3COtodKETB416s0yY6okTEE7LfIV7IOlpfARkXMF84qjEU2QhpV/kZJ0hQ -aEUQKQa8EwH3fmSF+2aBHwA/F1TgETtetd7EUlTxEK49eiebhZA7BNZHS9CD -rilvZYoDNnweHBMZzsBNBFsgO5EBCAC5INOERA2aNSYHWFeMfByShUuMQGFm -yL2tWT6rwzZmUVG0GUdvoKSRhMJ+81aHxr5zmIhluegEuY99UhX+ZK6NftW2 -UOYjjjQZ4NPDjqOfP5dYUbHiCFRgeUxkmjwnQoSih63iSOoUt5kocR+oXXxb -YmbgeOa8KGgKzDLGHI2nsy8Cni3N/enKVMMHGbJy1DXdV7uRFhBdjnRZGdmt -amHcQbwGHUH+PtTa/jUSMdbtTUvXPI6dz7jDpK0BImzbXNb+r9CcudpiixuM -u5gv3qyJL5EAWCXcT2j+y2VWj2HN/8bJHMoo6yf+bn6A/Cu9f0obbGVF0kJ/ -Y5UWmEdBG6IzABEBAAHCwJMEGAEKACYWIQTkiTkktw3HqXqtl/DWgXL0jpxS -gwUCWyA7kQIbDAUJA8JnAAAhCRDWgXL0jpxSgxYhBOSJOSS3Dcepeq2X8NaB -cvSOnFKDkFMIAIt64bVZ8x7+TitH1bR4pgcNkaKmgKoZz6FXu80+SnbuEt2N -nDyf1cLOSimSTILpwLIuv9Uft5PbOraQbYt3xi9yrqdKqGLv80bxqK0NuryN -kvh9yyx5WoG1iKqMj9/FjGghuPrRaT4lQinNAghGVkEy1+aXGFrG2DsOC1FF -I51CC2WVTzZ5RwR2GpiNRfESsU1rZAUqf/2VyJl9bD5R4SUNy8oQmhOxi+gb -hD4Ao34e4W0ilibslI/uawvCiOwlu5NGd8zv5n+UheiQvzkApQup5c+BhH5z -FDFdKJ2CBByxw9+7QjMFI/wgLixKuE0Ob2kAokXf7RlB7qTZOahrETw= -=+2T8 ------END PGP PUBLIC KEY BLOCK----- -` - -const keyWithOnlyUserIDRevoked = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -mDMEYYwB7RYJKwYBBAHaRw8BAQdARimqhPPzyGAXmfQJjcqM1QVPzLtURJSzNVll -JV4tEaW0KVJldm9rZWQgUHJpbWFyeSBVc2VyIElEIDxyZXZva2VkQGtleS5jb20+ -iHgEMBYIACAWIQSpyJZAXYqVEFkjyKutFcS0yeB0LQUCYYwCtgIdAAAKCRCtFcS0 -yeB0LbSsAQD8OYMaaBjrdzzpwIkP1stgmPd4/kzN/ZG28Ywl6a5F5QEA5Xg7aq4e -/t6Fsb4F5iqB956kSPe6YJrikobD/tBbMwSIkAQTFggAOBYhBKnIlkBdipUQWSPI -q60VxLTJ4HQtBQJhjAHtAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEK0V -xLTJ4HQtBaoBAPZL7luTCji+Tqhn7XNfFE/0QIahCt8k9wfO1cGlB3inAQDf8Tzw -ZGR5fNluUcNoVxQT7bUSFStbaGo3k0BaOYPbCLg4BGGMAe0SCisGAQQBl1UBBQEB -B0DLwSpveSrbIO/IVZD13yrs1XuB3FURZUnafGrRq7+jUAMBCAeIeAQYFggAIBYh -BKnIlkBdipUQWSPIq60VxLTJ4HQtBQJhjAHtAhsMAAoJEK0VxLTJ4HQtZ1oA/j9u -8+p3xTNzsmabTL6BkNbMeB/RUKCrlm6woM6AV+vxAQCcXTn3JC2sNoNrLoXuVzaA -mcG3/TwG5GSQUUPkrDsGDA== -=mFWy ------END PGP PUBLIC KEY BLOCK----- -` - -const keyWithSubKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -mI0EWyKwKQEEALwXhKBnyaaNFeK3ljfc/qn9X/QFw+28EUfgZPHjRmHubuXLE2uR -s3ZoSXY2z7Dkv+NyHYMt8p+X8q5fR7JvUjK2XbPyKoiJVnHINll83yl67DaWfKNL -EjNoO0kIfbXfCkZ7EG6DL+iKtuxniGTcnGT47e+HJSqb/STpLMnWwXjBABEBAAG0 -I0dvbGFuZyBHb3BoZXIgPG5vLXJlcGx5QGdvbGFuZy5jb20+iM4EEwEKADgWIQQ/ -lRafP/p9PytHbwxMvYJsOQdOOAUCWyKwKQIbAwULCQgHAwUVCgkICwUWAgMBAAIe -AQIXgAAKCRBMvYJsOQdOOOsFBAC62mXww8XuqvYLcVOvHkWLT6mhxrQOJXnlfpn7 -2uBV9CMhoG/Ycd43NONsJrB95Apr9TDIqWnVszNbqPCuBhZQSGLdbiDKjxnCWBk0 -69qv4RNtkpOhYB7jK4s8F5oQZqId6JasT/PmJTH92mhBYhhTQr0GYFuPX2UJdkw9 -Sn9C67iNBFsisDUBBAC3A+Yo9lgCnxi/pfskyLrweYif6kIXWLAtLTsM6g/6jt7b -wTrknuCPyTv0QKGXsAEe/cK/Xq3HvX9WfXPGIHc/X56ZIsHQ+RLowbZV/Lhok1IW -FAuQm8axr/by80cRwFnzhfPc/ukkAq2Qyj4hLsGblu6mxeAhzcp8aqmWOO2H9QAR -AQABiLYEKAEKACAWIQQ/lRafP/p9PytHbwxMvYJsOQdOOAUCWyK16gIdAAAKCRBM -vYJsOQdOOB1vA/4u4uLONsE+2GVOyBsHyy7uTdkuxaR9b54A/cz6jT/tzUbeIzgx -22neWhgvIEghnUZd0vEyK9k1wy5vbDlEo6nKzHso32N1QExGr5upRERAxweDxGOj -7luDwNypI7QcifE64lS/JmlnunwRCdRWMKc0Fp+7jtRc5mpwyHN/Suf5RokBagQY -AQoAIBYhBD+VFp8/+n0/K0dvDEy9gmw5B044BQJbIrA1AhsCAL8JEEy9gmw5B044 -tCAEGQEKAB0WIQSNdnkaWY6t62iX336UXbGvYdhXJwUCWyKwNQAKCRCUXbGvYdhX -JxJSA/9fCPHP6sUtGF1o3G1a3yvOUDGr1JWcct9U+QpbCt1mZoNopCNDDQAJvDWl -mvDgHfuogmgNJRjOMznvahbF+wpTXmB7LS0SK412gJzl1fFIpK4bgnhu0TwxNsO1 -8UkCZWqxRMgcNUn9z6XWONK8dgt5JNvHSHrwF4CxxwjL23AAtK+FA/UUoi3U4kbC -0XnSr1Sl+mrzQi1+H7xyMe7zjqe+gGANtskqexHzwWPUJCPZ5qpIa2l8ghiUim6b -4ymJ+N8/T8Yva1FaPEqfMzzqJr8McYFm0URioXJPvOAlRxdHPteZ0qUopt/Jawxl -Xt6B9h1YpeLoJwjwsvbi98UTRs0jXwoY -=3fWu ------END PGP PUBLIC KEY BLOCK-----` - -const keyWithSubKeyAndBadSelfSigOrder = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -mI0EWyLLDQEEAOqIOpJ/ha1OYAGduu9tS3rBz5vyjbNgJO4sFveEM0mgsHQ0X9/L -plonW+d0gRoO1dhJ8QICjDAc6+cna1DE3tEb5m6JtQ30teLZuqrR398Cf6w7NNVz -r3lrlmnH9JaKRuXl7tZciwyovneBfZVCdtsRZjaLI1uMQCz/BToiYe3DABEBAAG0 -I0dvbGFuZyBHb3BoZXIgPG5vLXJlcGx5QGdvbGFuZy5jb20+iM4EEwEKADgWIQRZ -sixZOfQcZdW0wUqmgmdsv1O9xgUCWyLLDQIbAwULCQgHAwUVCgkICwUWAgMBAAIe -AQIXgAAKCRCmgmdsv1O9xql2A/4pix98NxjhdsXtazA9agpAKeADf9tG4Za27Gj+ -3DCww/E4iP2X35jZimSm/30QRB6j08uGCqd9vXkkJxtOt63y/IpVOtWX6vMWSTUm -k8xKkaYMP0/IzKNJ1qC/qYEUYpwERBKg9Z+k99E2Ql4kRHdxXUHq6OzY79H18Y+s -GdeM/riNBFsiyxsBBAC54Pxg/8ZWaZX1phGdwfe5mek27SOYpC0AxIDCSOdMeQ6G -HPk38pywl1d+S+KmF/F4Tdi+kWro62O4eG2uc/T8JQuRDUhSjX0Qa51gPzJrUOVT -CFyUkiZ/3ZDhtXkgfuso8ua2ChBgR9Ngr4v43tSqa9y6AK7v0qjxD1x+xMrjXQAR -AQABiQFxBBgBCgAmAhsCFiEEWbIsWTn0HGXVtMFKpoJnbL9TvcYFAlsizTIFCQAN -MRcAv7QgBBkBCgAdFiEEJcoVUVJIk5RWj1c/o62jUpRPICQFAlsiyxsACgkQo62j -UpRPICQq5gQApoWIigZxXFoM0uw4uJBS5JFZtirTANvirZV5RhndwHeMN6JttaBS -YnjyA4+n1D+zB2VqliD2QrsX12KJN6rGOehCtEIClQ1Hodo9nC6kMzzAwW1O8bZs -nRJmXV+bsvD4sidLZLjdwOVa3Cxh6pvq4Uur6a7/UYx121hEY0Qx0s8JEKaCZ2y/ -U73GGi0D/i20VW8AWYAPACm2zMlzExKTOAV01YTQH/3vW0WLrOse53WcIVZga6es -HuO4So0SOEAvxKMe5HpRIu2dJxTvd99Bo9xk9xJU0AoFrO0vNCRnL+5y68xMlODK -lEw5/kl0jeaTBp6xX0HDQOEVOpPGUwWV4Ij2EnvfNDXaE1vK1kffiQFrBBgBCgAg -AhsCFiEEWbIsWTn0HGXVtMFKpoJnbL9TvcYFAlsi0AYAv7QgBBkBCgAdFiEEJcoV -UVJIk5RWj1c/o62jUpRPICQFAlsiyxsACgkQo62jUpRPICQq5gQApoWIigZxXFoM -0uw4uJBS5JFZtirTANvirZV5RhndwHeMN6JttaBSYnjyA4+n1D+zB2VqliD2QrsX -12KJN6rGOehCtEIClQ1Hodo9nC6kMzzAwW1O8bZsnRJmXV+bsvD4sidLZLjdwOVa -3Cxh6pvq4Uur6a7/UYx121hEY0Qx0s8JEKaCZ2y/U73GRl0EAJokkXmy4zKDHWWi -wvK9gi2gQgRkVnu2AiONxJb5vjeLhM/07BRmH6K1o+w3fOeEQp4FjXj1eQ5fPSM6 -Hhwx2CTl9SDnPSBMiKXsEFRkmwQ2AAsQZLmQZvKBkLZYeBiwf+IY621eYDhZfo+G -1dh1WoUCyREZsJQg2YoIpWIcvw+a -=bNRo ------END PGP PUBLIC KEY BLOCK----- -` - -const onlySubkeyNoPrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1 - -lQCVBFggvocBBAC7vBsHn7MKmS6IiiZNTXdciplVgS9cqVd+RTdIAoyNTcsiV1H0 -GQ3QtodOPeDlQDNoqinqaobd7R9g3m3hS53Nor7yBZkCWQ5x9v9JxRtoAq0sklh1 -I1X2zEqZk2l6YrfBF/64zWrhjnW3j23szkrAIVu0faQXbQ4z56tmZrw11wARAQAB -/gdlAkdOVQG0CUdOVSBEdW1teYi4BBMBAgAiBQJYIL6HAhsDBgsJCAcDAgYVCAIJ -CgsEFgIDAQIeAQIXgAAKCRCd1xxWp1CYAnjGA/9synn6ZXJUKAXQzySgmCZvCIbl -rqBfEpxwLG4Q/lONhm5vthAE0z49I8hj5Gc5e2tLYUtq0o0OCRdCrYHa/efOYWpJ -6RsK99bePOisVzmOABLIgZkcr022kHoMCmkPgv9CUGKP1yqbGl+zzAwQfUjRUmvD -ZIcWLHi2ge4GzPMPi50B2ARYIL6cAQQAxWHnicKejAFcFcF1/3gUSgSH7eiwuBPX -M7vDdgGzlve1o1jbV4tzrjN9jsCl6r0nJPDMfBSzgLr1auNTRG6HpJ4abcOx86ED -Ad+avDcQPZb7z3dPhH/gb2lQejZsHh7bbeOS8WMSzHV3RqCLd8J/xwWPNR5zKn1f -yp4IGfopidMAEQEAAQAD+wQOelnR82+dxyM2IFmZdOB9wSXQeCVOvxSaNMh6Y3lk -UOOkO8Nlic4x0ungQRvjoRs4wBmCuwFK/MII6jKui0B7dn/NDf51i7rGdNGuJXDH -e676By1sEY/NGkc74jr74T+5GWNU64W0vkpfgVmjSAzsUtpmhJMXsc7beBhJdnVl -AgDKCb8hZqj1alcdmLoNvb7ibA3K/V8J462CPD7bMySPBa/uayoFhNxibpoXml2r -oOtHa5izF3b0/9JY97F6rqkdAgD6GdTJ+xmlCoz1Sewoif1I6krq6xoa7gOYpIXo -UL1Afr+LiJeyAnF/M34j/kjIVmPanZJjry0kkjHE5ILjH3uvAf4/6n9np+Th8ujS -YDCIzKwR7639+H+qccOaddCep8Y6KGUMVdD/vTKEx1rMtK+hK/CDkkkxnFslifMJ -kqoqv3WUqCWJAT0EGAECAAkFAlggvpwCGwIAqAkQndccVqdQmAKdIAQZAQIABgUC -WCC+nAAKCRDmGUholQPwvQk+A/9latnSsR5s5/1A9TFki11GzSEnfLbx46FYOdkW -n3YBxZoPQGxNA1vIn8GmouxZInw9CF4jdOJxEdzLlYQJ9YLTLtN5tQEMl/19/bR8 -/qLacAZ9IOezYRWxxZsyn6//jfl7A0Y+FV59d4YajKkEfItcIIlgVBSW6T+TNQT3 -R+EH5HJ/A/4/AN0CmBhhE2vGzTnVU0VPrE4V64pjn1rufFdclgpixNZCuuqpKpoE -VVHn6mnBf4njKjZrAGPs5kfQ+H4NsM7v3Zz4yV6deu9FZc4O6E+V1WJ38rO8eBix -7G2jko106CC6vtxsCPVIzY7aaG3H5pjRtomw+pX7SzrQ7FUg2PGumg== -=F/T0 ------END PGP PRIVATE KEY BLOCK-----` - -const ecdsaPrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -xaUEX1KsSRMIKoZIzj0DAQcCAwTpYqJsnJiFhKKh+8TulWD+lVmerBFNS+Ii -B+nlG3T0xQQ4Sy5eIjJ0CExIQQzi3EElF/Z2l4F3WC5taFA11NgA/gkDCHSS -PThf1M2K4LN8F1MRcvR+sb7i0nH55ojkwuVB1DE6jqIT9m9i+mX1tzjSAS+6 -lPQiweCJvG7xTC7Hs3AzRapf/r1At4TB+v+5G2/CKynNFEJpbGwgPGJpbGxA -aG9tZS5jb20+wncEEBMIAB8FAl9SrEkGCwkHCAMCBBUICgIDFgIBAhkBAhsD -Ah4BAAoJEMpwT3+q3+xqw5UBAMebZN9isEZ1ML+R/jWAAWMwa/knMugrEZ1v -Bl9+ZwM0AQCZdf80/wYY4Nve01qSRFv8OmKswLli3TvDv6FKc4cLz8epBF9S -rEkSCCqGSM49AwEHAgMEAjKnT9b5wY2bf9TpAV3d7OUfPOxKj9c4VzeVzSrH -AtQgo/MuI1cdYVURicV4i76DNjFhQHQFTk7BrC+C2u1yqQMBCAf+CQMIHImA -iYfzQtjgQWSFZYUkCFpbbwhNF0ch+3HNaZkaHCnZRIsWsRnc6FCb6lRQyK9+ -Dq59kHlduE5QgY40894jfmP2JdJHU6nBdYrivbEdbMJhBBgTCAAJBQJfUqxJ -AhsMAAoJEMpwT3+q3+xqUI0BAMykhV08kQ4Ip9Qlbss6Jdufv7YrU0Vd5hou -b5TmiPd0APoDBh3qIic+aLLUcAuG3+Gt1P1AbUlmqV61ozn1WfHxfw== -=KLN8 ------END PGP PRIVATE KEY BLOCK-----` - -const dsaPrivateKeyWithElGamalSubkey = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -lQOBBF9/MLsRCACeaF6BI0jTgDAs86t8/kXPfwlPvR2MCYzB0BCqAdcq1hV/GTYd -oNmJRna/ZJfsI/vf+d8Nv+EYOQkPheFS1MJVBitkAXjQPgm8i1tQWen1FCWZxqGk -/vwZYF4yo8GhZ+Wxi3w09W9Cp9QM/CTmyE1Xe7wpPBGe+oD+me8Zxjyt8JBS4Qx+ -gvWbfHxfHnggh4pz7U8QkItlLsBNQEdX4R5+zwRN66g2ZSX/shaa/EkVnihUhD7r -njP9I51ORWucTQD6OvgooaNQZCkQ/Se9TzdakwWKS2XSIFXiY/e2E5ZgKI/pfKDU -iA/KessxddPb7nP/05OIJqg9AoDrD4vmehLzAQD+zsUS3LDU1m9/cG4LMsQbT2VK -Te4HqbGIAle+eu/asQf8DDJMrbZpiJZvADum9j0TJ0oep6VdMbzo9RSDKvlLKT9m -kG63H8oDWnCZm1a+HmGq9YIX+JHWmsLXXsFLeEouLzHO+mZo0X28eji3V2T87hyR -MmUM0wFo4k7jK8uVmkDXv3XwNp2uByWxUKZd7EnWmcEZWqIiexJ7XpCS0Pg3tRaI -zxve0SRe/dxfUPnTk/9KQ9hS6DWroBKquL182zx1Fggh4LIWWE2zq+UYn8BI0E8A -rmIDFJdF8ymFQGRrEy6g79NnkPmkrZWsgMRYY65P6v4zLVmqohJKkpm3/Uxa6QAP -CCoPh/JTOvPeCP2bOJH8z4Z9Py3ouMIjofQW8sXqRgf/RIHbh0KsINHrwwZ4gVIr -MK3RofpaYxw1ztPIWb4cMWoWZHH1Pxh7ggTGSBpAhKXkiWw2Rxat8QF5aA7e962c -bLvVv8dqsPrD/RnVJHag89cbPTzjn7gY9elE8EM8ithV3oQkwHTr4avYlpDZsgNd -hUW3YgRwGo31tdzxoG04AcpV2t+07P8XMPr9hsfWs4rHohXPi38Hseu1Ji+dBoWQ -3+1w/HH3o55s+jy4Ruaz78AIrjbmAJq+6rA2mIcCgrhw3DnzuwQAKeBvSeqn9zfS -ZC812osMBVmkycwelpaIh64WZ0vWL3GvdXDctV2kXM+qVpDTLEny0LuiXxrwCKQL -Ev4HAwK9uQBcreDEEud7pfRb8EYP5lzO2ZA7RaIvje6EWAGBvJGMRT0QQE5SGqc7 -Fw5geigBdt+vVyRuNNhg3c2fdn/OBQaYu0J/8AiOogG8EaM8tCFlbGdhbWFsQGRz -YS5jb20gPGVsZ2FtYWxAZHNhLmNvbT6IkAQTEQgAOBYhBI+gnfiHQxB35/Dp0XAQ -aE/rsWC5BQJffzC7AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEHAQaE/r -sWC5A4EA/0GcJmyPtN+Klc7b9sVT3JgKTRnB/URxOJfYJofP0hZLAQCkqyMO+adV -JvbgDH0zaITQWZSSXPqpgMpCA6juTrDsd50CawRffzC7EAgAxFFFSAAEQzWTgKU5 -EBtpxxoPzHqcChawTHRxHxjcELXzmUBS5PzfA1HXSPnNqK/x3Ut5ycC3CsW41Fnt -Gm3706Wu9VFbFZVn55F9lPiplUo61n5pqMvOr1gmuQsdXiTa0t5FRa4TZ2VSiHFw -vdAVSPTUsT4ZxJ1rPyFYRtq1n3pQcvdZowd07r0JnzTMjLLMFYCKhwIowoOC4zqJ -iB8enjwOlpaqBATRm9xpVF7SJkroPF6/B1vdhj7E3c1aJyHlo0PYBAg756sSHWHg -UuLyUQ4TA0hcCVenn/L/aSY2LnbdZB1EBhlYjA7dTCgwIqsQhfQmPkjz6g64A7+Y -HbbrLwADBQgAk14QIEQ+J/VHetpQV/jt2pNsFK1kVK7mXK0spTExaC2yj2sXlHjL -Ie3bO5T/KqmIaBEB5db5fA5xK9cZt79qrQHDKsEqUetUeMUWLBx77zBsus3grIgy -bwDZKseRzQ715pwxquxQlScGoDIBKEh08HpwHkq140eIj3w+MAIfndaZaSCNaxaP -Snky7BQmJ7Wc7qrIwoQP6yrnUqyW2yNi81nJYUhxjChqaFSlwzLs/iNGryBKo0ic -BqVIRjikKHBlwBng6WyrltQo/Vt9GG8w+lqaAVXbJRlaBZJUR+2NKi/YhP3qQse3 -v8fi4kns0gh5LK+2C01RvdX4T49QSExuIf4HAwLJqYIGwadA2uem5v7/765ZtFWV -oL0iZ0ueTJDby4wTFDpLVzzDi/uVcB0ZRFrGOp7w6OYcNYTtV8n3xmli2Q5Trw0c -wZVzvg+ABKWiv7faBjMczIFF8y6WZKOIeAQYEQgAIBYhBI+gnfiHQxB35/Dp0XAQ -aE/rsWC5BQJffzC7AhsMAAoJEHAQaE/rsWC5ZmIA/jhS4r4lClbvjuPWt0Yqdn7R -fss2SPMYvMrrDh42aE0OAQD8xn4G6CN8UtW9xihXOY6FpxiJ/sMc2VaneeUd34oa -4g== -=XZm8 ------END PGP PRIVATE KEY BLOCK-----` - -// https://tests.sequoia-pgp.org/#Certificate_expiration -// P _ U p -const expiringPrimaryUIDKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv -/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz -/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/ -5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3 -X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv -9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0 -qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb -SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb -vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w -bGU+wsFcBBMBCgCQBYJhesp/BYkEWQPJBQsJCAcCCRD7/MgqAV5zMEcUAAAAAAAe -ACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeEOQlNyTLFkc9I/elp+BpY -495V7KatqtDmsyDr+zDAdwYVCgkICwIEFgIDAQIXgAIbAwIeARYhBNGmbhojsYLJ -mA94jPv8yCoBXnMwAABSCQv/av8hKyynMtXVKFuWOGJw0mR8auDm84WdhMFRZg8t -yTJ1L88+Ny4WUAFeqo2j7DU2yPGrm5rmuvzlEedFYFeOWt+A4adz+oumgRd0nsgG -Lf3QYUWQhLWVlz+H7zubgKqSB2A2RqV65S7mTTVro42nb2Mng6rvGWiqeKG5nrXN -/01p1mIBQGR/KnZSqYLzA2Pw2PiJoSkXT26PDz/kiEMXpjKMR6sicV4bKVlEdUvm -pIImIPBHZq1EsKXEyWtWC41w/pc+FofGE+uSFs2aef1vvEHFkj3BHSK8gRcH3kfR -eFroTET8C2q9V1AOELWm+Ys6PzGzF72URK1MKXlThuL4t4LjvXWGNA78IKW+/RQH -DzK4U0jqSO0mL6qxqVS5Ij6jjL6OTrVEGdtDf5n0vI8tcUTBKtVqYAYk+t2YGT05 -ayxALtb7viVKo8f10WEcCuKshn0gdsEFMRZQzJ89uQIY3R3FbsdRCaE6OEaDgKMQ -UTFROyfhthgzRKbRxfcplMUCzsDNBF2lnPIBDADWML9cbGMrp12CtF9b2P6z9TTT -74S8iyBOzaSvdGDQY/sUtZXRg21HWamXnn9sSXvIDEINOQ6A9QxdxoqWdCHrOuW3 -ofneYXoG+zeKc4dC86wa1TR2q9vW+RMXSO4uImA+Uzula/6k1DogDf28qhCxMwG/ -i/m9g1c/0aApuDyKdQ1PXsHHNlgd/Dn6rrd5y2AObaifV7wIhEJnvqgFXDN2RXGj -LeCOHV4Q2WTYPg/S4k1nMXVDwZXrvIsA0YwIMgIT86Rafp1qKlgPNbiIlC1g9RY/ -iFaGN2b4Ir6GDohBQSfZW2+LXoPZuVE/wGlQ01rh827KVZW4lXvqsge+wtnWlszc -selGATyzqOK9LdHPdZGzROZYI2e8c+paLNDdVPL6vdRBUnkCaEkOtl1mr2JpQi5n -TU+gTX4IeInC7E+1a9UDF/Y85ybUz8XV8rUnR76UqVC7KidNepdHbZjjXCt8/Zo+ -Tec9JNbYNQB/e9ExmDntmlHEsSEQzFwzj8sxH48AEQEAAcLA9gQYAQoAIBYhBNGm -bhojsYLJmA94jPv8yCoBXnMwBQJdpZzyAhsMAAoJEPv8yCoBXnMw6f8L/26C34dk -jBffTzMj5Bdzm8MtF67OYneJ4TQMw7+41IL4rVcSKhIhk/3Ud5knaRtP2ef1+5F6 -6h9/RPQOJ5+tvBwhBAcUWSupKnUrdVaZQanYmtSxcVV2PL9+QEiNN3tzluhaWO// -rACxJ+K/ZXQlIzwQVTpNhfGzAaMVV9zpf3u0k14itcv6alKY8+rLZvO1wIIeRZLm -U0tZDD5HtWDvUV7rIFI1WuoLb+KZgbYn3OWjCPHVdTrdZ2CqnZbG3SXw6awH9bzR -LV9EXkbhIMez0deCVdeo+wFFklh8/5VK2b0vk/+wqMJxfpa1lHvJLobzOP9fvrsw -sr92MA2+k901WeISR7qEzcI0Fdg8AyFAExaEK6VyjP7SXGLwvfisw34OxuZr3qmx -1Sufu4toH3XrB7QJN8XyqqbsGxUCBqWif9RSK4xjzRTe56iPeiSJJOIciMP9i2ld -I+KgLycyeDvGoBj0HCLO3gVaBe4ubVrj5KjhX2PVNEJd3XZRzaXZE2aAMQ== -=AmgT ------END PGP PUBLIC KEY BLOCK-----` - -const rsa2048PrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- -Comment: gpg (GnuPG) 2.2.27 with libgcrypt 1.9.4 - -lQPGBGL07P0BCADL0etN8efyAXA6sL2WfQvHe5wEKYXPWeN2+jiqSppfeRZAOlzP -kZ3U+cloeJriplYvVJwI3ID2aw52Z/TRn8iKRP5eOUFrEgcgl06lazLtOndK7o7p -oBV5mLtHEirFHm6W61fNt10jzM0jx0PV6nseLhFB2J42F1cmU/aBgFo41wjLSZYr -owR+v+O9S5sUXblQF6sEDcY01sBEu09zrIgT49VFwQ1Cvdh9XZEOTQBfdiugoj5a -DS3fAqAka3r1VoQK4eR7/upnYSgSACGeaQ4pUelKku5rpm50gdWTY8ppq0k9e1eT -y2x0OQcW3hWE+j4os1ca0ZEADMdqr/99MOxrABEBAAH+BwMCJWxU4VOZOJ7/I6vX -FxdfBhIBEXlJ52FM3S/oYtXqLhkGyrtmZOeEazVvUtuCe3M3ScHI8xCthcmE8E0j -bi+ZEHPS2NiBZtgHFF27BLn7zZuTc+oD5WKduZdK3463egnyThTqIIMl25WZBuab -k5ycwYrWwBH0jfA4gwJ13ai4pufKC2RM8qIu6YAVPglYBKFLKGvvJHa5vI+LuA0E -K+k35hIic7yVUcQneNnAF2598X5yWiieYnOZpmHlRw1zfbMwOJr3ZNj2v94u7b+L -sTa/1Uv9887Vb6sJp0c2Sh4cwEccoPYkvMqFn3ZrJUr3UdDu1K2vWohPtswzhrYV -+RdPZE5RLoCQufKvlPezk0Pzhzb3bBU7XjUbdGY1nH/EyQeBNp+Gw6qldKvzcBaB -cyOK1c6hPSszpJX93m5UxCN55IeifmcNjmbDh8vGCCdajy6d56qV2n4F3k7vt1J1 -0UlxIGhqijJoaTCX66xjLMC6VXkSz6aHQ35rnXosm/cqPcQshsZTdlfSyWkorfdr -4Hj8viBER26mjYurTMLBKDtUN724ZrR0Ev5jorX9uoKlgl87bDZHty2Ku2S+vR68 -VAvnj6Fi1BYNclnDoqxdRB2z5T9JbWE52HuG83/QsplhEqXxESDxriTyTHMbNxEe -88soVCDh4tgflZFa2ucUr6gEKJKij7jgahARnyaXfPZlQBUAS1YUeILYmN+VR+M/ -sHENpwDWc7TInn8VN638nJV+ScZGMih3AwWZTIoiLju3MMt1K0YZ3NuiqwGH4Jwg -/BbEdTWeCci9y3NEQHQ3uZZ5p6j2CwFVlK11idemCMvAiTVxF+gKdaLMkeCwKxru -J3YzhKEo+iDVYbPYBYizx/EHBn2U5kITQ5SBXzjTaaFMNZJEf9JYsL1ybPB6HOFY -VNVB2KT8CGVwtCJHb2xhbmcgR29waGVyIDxnb2xhbmdAZXhhbXBsZS5vcmc+iQFO -BBMBCgA4FiEEC6K7U7f4qesybTnqSkra7gHusm0FAmL07P0CGwMFCwkIBwIGFQoJ -CAsCBBYCAwECHgECF4AACgkQSkra7gHusm1MvwgAxpClWkeSqIhMQfbiuz0+lOkE -89y1DCFw8bHjZoUf4/4K8hFA3dGkk+q72XFgiyaCpfXxMt6Gi+dN47t+tTv9NIqC -sukbaoJBmJDhN6+djmJOgOYy+FWsW2LAk2LOwKYulpnBZdcA5rlMAhBg7gevQpF+ -ruSU69P7UUaFJl/DC7hDmaIcj+4cjBE/HO26SnVQjoTfjZT82rDh1Wsuf8LnkJUk -b3wezBLpXKjDvdHikdv4gdlR4AputVM38aZntYYglh/EASo5TneyZ7ZscdLNRdcF -r5O2fKqrOJLOdaoYRFZZWOvP5GtEVFDU7WGivOSVfiszBE0wZR3dgZRJipHCXJ0D -xgRi9Oz9AQgAtMJcJqLLVANJHl90tWuoizDkm+Imcwq2ubQAjpclnNrODnDK+7o4 -pBsWmXbZSdkC4gY+LhOQA6bPDD0JEHM58DOnrm49BddxXAyK0HPsk4sGGt2SS86B -OawWNdfJVyqw4bAiHWDmQg4PcjBbt3ocOIxAR6I5kBSiQVxuGQs9T+Zvg3G1r3Or -fS6DzlgY3HFUML5YsGH4lOxNSOoKAP68GIH/WNdUZ+feiRg9knIib6I3Hgtf5eO8 -JRH7aWE/TD7eNu36bLLjT5TZPq5r6xaD2plbtPOyXbNPWs9qI1yG+VnErfaLY0w8 -Qo0aqzbgID+CTZVomXSOpOcQseaFKw8ZfQARAQAB/gcDArha6+/+d4OY/w9N32K9 -hFNYt4LufTETMQ+k/sBeaMuAVzmT47DlAXzkrZhGW4dZOtXMu1rXaUwHlqkhEyzL -L4MYEWVXfD+LbZNEK3MEFss6RK+UAMeT/PTV9aA8cXQVPcSJYzfBXHQ1U1hnOgrO -apn92MN8RmkhX8wJLyeWTMMuP4lXByJMmmGo8WvifeRD2kFY4y0WVBDAXJAV4Ljf -Di/bBiwoc5a+gxHuZT2W9ZSxBQJNXdt4Un2IlyZuo58s5MLx2N0EaNJ8PwRUE6fM -RZYO8aZCEPUtINE4njbvsWOMCtrblsMPwZ1B0SiIaWmLaNyGdCNKea+fCIW7kasC -JYMhnLumpUTXg5HNexkCsl7ABWj0PYBflOE61h8EjWpnQ7JBBVKS2ua4lMjwHRX7 -5o5yxym9k5UZNFdGoXVL7xpizCcdGawxTJvwhs3vBqu1ZWYCegOAZWDrOkCyhUpq -8uKMROZFbn+FwE+7tjt+v2ed62FVEvD6g4V3ThCA6mQqeOARfJWN8GZY8BDm8lht -crOXriUkrx+FlrgGtm2CkwjW5/9Xd7AhFpHnQdFeozOHyq1asNSgJF9sNi9Lz94W -skQSVRi0IExxSXYGI3Y0nnAZUe2BAQflYPJdEveSr3sKlUqXiETTA1VXsTPK3kOC -92CbLzj/Hz199jZvywwyu53I+GKMpF42rMq7zxr2oa61YWY4YE/GDezwwys/wLx/ -QpCW4X3ppI7wJjCSSqEV0baYZSSli1ayheS6dxi8QnSpX1Bmpz6gU7m/M9Sns+hl -J7ZvgpjCAiV7KJTjtclr5/S02zP78LTVkoTWoz/6MOTROwaP63VBUXX8pbJhf/vu -DLmNnDk8joMJxoDXWeNU0EnNl4hP7Z/jExRBOEO4oAnUf/Sf6gCWQhL5qcajtg6w -tGv7vx3f2IkBNgQYAQoAIBYhBAuiu1O3+KnrMm056kpK2u4B7rJtBQJi9Oz9AhsM -AAoJEEpK2u4B7rJt6lgIAMBWqP4BCOGnQXBbgJ0+ACVghpkFUXZTb/tXJc8UUvTM -8uov6k/RsqDGZrvhhufD7Wwt7j9v7dD7VPp7bPyjVWyimglQzWguTUUqLDGlstYH -5uYv1pzma0ZsAGNqFeGlTLsKOSGKFMH4rB2KfN2n51L8POvtp1y7GKZQbWIWneaB -cZr3BINU5GMvYYU7pAYcoR+mJPdJx5Up3Ocn+bn8Tu1sy9C/ArtCQucazGnoE9u1 -HhNLrh0CdzzX7TNH6TQ8LwPOvq0K5l/WqbN9lE0WBBhMv2HydxhluO8AhU+A5GqC -C+wET7nVDnhoOm/fstIeb7/LN7OYejKPeHdFBJEL9GA= -=u442 ------END PGP PRIVATE KEY BLOCK-----` - -const curve25519PrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- -Comment: gpg (GnuPG) 2.2.27 with libgcrypt 1.9.4 - -lFgEYvTtQBYJKwYBBAHaRw8BAQdAxsNXLbrk5xOjpO24VhOMvQ0/F+JcyIkckMDH -X3FIGxcAAQDFOlunZWYuPsCx5JLp78vKqUTfgef9TGG4oD6I/Sa0zBMstCJHb2xh -bmcgR29waGVyIDxnb2xhbmdAZXhhbXBsZS5vcmc+iJAEExYIADgWIQSFQHEOazmo -h1ldII4MvfnLQ4JBNwUCYvTtQAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAK -CRAMvfnLQ4JBN5yeAQCKdry8B5ScCPrev2+UByMCss7Sdu5RhomCFsHdNPLcKAEA -8ugei+1owHsV+3cGwWWzKk6sLa8ZN87i3SKuOGp9DQycXQRi9O1AEgorBgEEAZdV -AQUBAQdA5CubPp8l7lrVQ25h7Hx5XN2C8xanRnnpcjzEooCaEA0DAQgHAAD/Rpc+ -sOZUXrFk9HOWB1XU41LoWbDBoG8sP8RWAVYwD5AQRYh4BBgWCAAgFiEEhUBxDms5 -qIdZXSCODL35y0OCQTcFAmL07UACGwwACgkQDL35y0OCQTcvdwEA7lb5g/YisrEf -iq660uwMGoepLUfvtqKzuQ6heYe83y0BAN65Ffg5HYOJzUEi0kZQRf7OhdtuL2kJ -SRXn8DmCTfEB -=cELM ------END PGP PRIVATE KEY BLOCK-----` - -const curve448PrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- -Comment: C1DB 65D5 80D7 B922 7254 4B1E A699 9895 FABA CE52 - -xYUEYV2UmRYDK2VxAc9AFyxgh5xnSbyt50TWl558mw9xdMN+/UBLr5+UMP8IsrvV -MdXuTIE8CyaUQKSotHtH2RkYEXj5nsMAAAHPQIbTMSzjIWug8UFECzAex5FHgAgH -gYF3RK+TS8D24wX8kOu2C/NoVxwGY+p+i0JHaB+7yljriSKAGxs6wsBEBB8WCgCD -BYJhXZSZBYkFpI+9AwsJBwkQppmYlfq6zlJHFAAAAAAAHgAgc2FsdEBub3RhdGlv -bnMuc2VxdW9pYS1wZ3Aub3Jn5wSpIutJ5HncJWk4ruUV8GzQF390rR5+qWEAnAoY -akcDFQoIApsBAh4BFiEEwdtl1YDXuSJyVEseppmYlfq6zlIAALzdA5dA/fsgYg/J -qaQriYKaPUkyHL7EB3BXhV2d1h/gk+qJLvXQuU2WEJ/XSs3GrsBRiiZwvPH4o+7b -mleAxjy5wpS523vqrrBR2YZ5FwIku7WS4litSdn4AtVam/TlLdMNIf41CtFeZKBe -c5R5VNdQy8y7qy8AAADNEUN1cnZlNDQ4IE9wdGlvbiA4wsBHBBMWCgCGBYJhXZSZ -BYkFpI+9AwsJBwkQppmYlfq6zlJHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2Vx -dW9pYS1wZ3Aub3JnD55UsYMzE6OACP+mgw5zvT+BBgol8/uFQjHg4krjUCMDFQoI -ApkBApsBAh4BFiEEwdtl1YDXuSJyVEseppmYlfq6zlIAAPQJA5dA0Xqwzn/0uwCq -RlsOVCB3f5NOj1exKnlBvRw0xT1VBee1yxvlUt5eIAoCxWoRlWBJob3TTkhm9AEA -8dyhwPmyGfWHzPw5NFG3xsXrZdNXNvit9WMVAPcmsyR7teXuDlJItxRAdJJc/qfJ -YVbBFoaNrhYAAADHhQRhXZSZFgMrZXEBz0BL7THZ9MnCLfSPJ1FMLim9eGkQ3Bfn -M3he5rOwO3t14QI1LjI96OjkeJipMgcFAmEP1Bq/ZHGO7oAAAc9AFnE8iNBaT3OU -EFtxkmWHXtdaYMmGGRdopw9JPXr/UxuunDln5o9dxPxf7q7z26zXrZen+qed/Isa -HsDCwSwEGBYKAWsFgmFdlJkFiQWkj70JEKaZmJX6us5SRxQAAAAAAB4AIHNhbHRA -bm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZxREUizdTcepBzgSMOv2VWQCWbl++3CZ -EbgAWDryvSsyApsCwDGgBBkWCgBvBYJhXZSZCRBKo3SL4S5djkcUAAAAAAAeACBz -YWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmemoGTDjmNQiIzw6HOEddvS0OB7 -UZ/P07jM/EVmnYxTlBYhBAxsnkGpx1UCiH6gUUqjdIvhLl2OAAALYQOXQAMB1oKq -OWxSFmvmgCKNcbAAyA3piF5ERIqs4z07oJvqDYrOWt75UsEIH/04gU/vHc4EmfG2 -JDLJgOLlyTUPkL/08f0ydGZPofFQBhn8HkuFFjnNtJ5oz3GIP4cdWMQFaUw0uvjb -PM9Tm3ptENGd6Ts1AAAAFiEEwdtl1YDXuSJyVEseppmYlfq6zlIAAGpTA5dATR6i -U2GrpUcQgpG+JqfAsGmF4yAOhgFxc1UfidFk3nTup3fLgjipkYY170WLRNbyKkVO -Sodx93GAs58rizO1acDAWiLq3cyEPBFXbyFThbcNPcLl+/77Uk/mgkYrPQFAQWdK -1kSRm4SizDBK37K8ChAAAADHhwRhXZSZEgMrZW8Bx0DMhzvhQo+OsXeqQ6QVw4sF -CaexHh6rLohh7TzL3hQSjoJ27fV6JBkIWdn0LfrMlJIDbSv2SLdlgQMBCgkAAcdA -MO7Dc1myF6Co1fAH+EuP+OxhxP/7V6ljuSCZENDfA49tQkzTta+PniG+pOVB2LHb -huyaKBkqiaogo8LAOQQYFgoAeAWCYV2UmQWJBaSPvQkQppmYlfq6zlJHFAAAAAAA -HgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnEjBMQAmc/2u45u5FQGmB -QAytjSG2LM3JQN+PPVl5vEkCmwwWIQTB22XVgNe5InJUSx6mmZiV+rrOUgAASdYD -l0DXEHQ9ykNP2rZP35ET1dmiFagFtTj/hLQcWlg16LqvJNGqOgYXuqTerbiOOt02 -XLCBln+wdewpU4ChEffMUDRBfqfQco/YsMqWV7bHJHAO0eC/DMKCjyU90xdH7R/d -QgqsfguR1PqPuJxpXV4bSr6CGAAAAA== -=MSvh ------END PGP PRIVATE KEY BLOCK-----` - -const keyWithNotation = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -xVgEY9gIshYJKwYBBAHaRw8BAQdAF25fSM8OpFlXZhop4Qpqo5ywGZ4jgWlR -ppjhIKDthREAAQC+LFpzFcMJYcjxGKzBGHN0Px2jU4d04YSRnFAik+lVVQ6u -zRdUZXN0IDx0ZXN0QGV4YW1wbGUuY29tPsLACgQQFgoAfAUCY9gIsgQLCQcI -CRD/utJOCym8pR0UgAAAAAAQAAR0ZXh0QGV4YW1wbGUuY29tdGVzdB8UAAAA -AAASAARiaW5hcnlAZXhhbXBsZS5jb20AAQIDAxUICgQWAAIBAhkBAhsDAh4B -FiEEEMCQTUVGKgCX5rDQ/7rSTgspvKUAAPl5AP9Npz90LxzrB97Qr2DrGwfG -wuYn4FSYwtuPfZHHeoIabwD/QEbvpQJ/NBb9EAZuow4Rirlt1yv19mmnF+j5 -8yUzhQjHXQRj2AiyEgorBgEEAZdVAQUBAQdARXAo30DmKcyUg6co7OUm0RNT -z9iqFbDBzA8A47JEt1MDAQgHAAD/XKK3lBm0SqMR558HLWdBrNG6NqKuqb5X -joCML987ZNgRD8J4BBgWCAAqBQJj2AiyCRD/utJOCym8pQIbDBYhBBDAkE1F -RioAl+aw0P+60k4LKbylAADRxgEAg7UfBDiDPp5LHcW9D+SgFHk6+GyEU4ev -VppQxdtxPvAA/34snHBX7Twnip1nMt7P4e2hDiw/hwQ7oqioOvc6jMkP -=Z8YJ ------END PGP PRIVATE KEY BLOCK----- -` diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_config.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_config.go deleted file mode 100644 index fec41a0e73f..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_config.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (C) 2019 ProtonTech AG - -package packet - -import "math/bits" - -// CipherSuite contains a combination of Cipher and Mode -type CipherSuite struct { - // The cipher function - Cipher CipherFunction - // The AEAD mode of operation. - Mode AEADMode -} - -// AEADConfig collects a number of AEAD parameters along with sensible defaults. -// A nil AEADConfig is valid and results in all default values. -type AEADConfig struct { - // The AEAD mode of operation. - DefaultMode AEADMode - // Amount of octets in each chunk of data - ChunkSize uint64 -} - -// Mode returns the AEAD mode of operation. -func (conf *AEADConfig) Mode() AEADMode { - // If no preference is specified, OCB is used (which is mandatory to implement). - if conf == nil || conf.DefaultMode == 0 { - return AEADModeOCB - } - - mode := conf.DefaultMode - if mode != AEADModeEAX && mode != AEADModeOCB && mode != AEADModeGCM { - panic("AEAD mode unsupported") - } - return mode -} - -// ChunkSizeByte returns the byte indicating the chunk size. The effective -// chunk size is computed with the formula uint64(1) << (chunkSizeByte + 6) -// limit to 16 = 4 MiB -// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2 -func (conf *AEADConfig) ChunkSizeByte() byte { - if conf == nil || conf.ChunkSize == 0 { - return 12 // 1 << (12 + 6) == 262144 bytes - } - - chunkSize := conf.ChunkSize - exponent := bits.Len64(chunkSize) - 1 - switch { - case exponent < 6: - exponent = 6 - case exponent > 16: - exponent = 16 - } - - return byte(exponent - 6) -} - -// decodeAEADChunkSize returns the effective chunk size. In 32-bit systems, the -// maximum returned value is 1 << 30. -func decodeAEADChunkSize(c byte) int { - size := uint64(1 << (c + 6)) - if size != uint64(int(size)) { - return 1 << 30 - } - return int(size) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_crypter.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_crypter.go deleted file mode 100644 index 5e460465631..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_crypter.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (C) 2019 ProtonTech AG - -package packet - -import ( - "crypto/cipher" - "encoding/binary" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" -) - -// aeadCrypter is an AEAD opener/sealer, its configuration, and data for en/decryption. -type aeadCrypter struct { - aead cipher.AEAD - chunkSize int - nonce []byte - associatedData []byte // Chunk-independent associated data - chunkIndex []byte // Chunk counter - packetTag packetType // SEIP packet (v2) or AEAD Encrypted Data packet - bytesProcessed int // Amount of plaintext bytes encrypted/decrypted -} - -// computeNonce takes the incremental index and computes an eXclusive OR with -// the least significant 8 bytes of the receivers' initial nonce (see sec. -// 5.16.1 and 5.16.2). It returns the resulting nonce. -func (wo *aeadCrypter) computeNextNonce() (nonce []byte) { - if wo.packetTag == packetTypeSymmetricallyEncryptedIntegrityProtected { - return wo.nonce - } - - nonce = make([]byte, len(wo.nonce)) - copy(nonce, wo.nonce) - offset := len(wo.nonce) - 8 - for i := 0; i < 8; i++ { - nonce[i+offset] ^= wo.chunkIndex[i] - } - return -} - -// incrementIndex performs an integer increment by 1 of the integer represented by the -// slice, modifying it accordingly. -func (wo *aeadCrypter) incrementIndex() error { - index := wo.chunkIndex - if len(index) == 0 { - return errors.AEADError("Index has length 0") - } - for i := len(index) - 1; i >= 0; i-- { - if index[i] < 255 { - index[i]++ - return nil - } - index[i] = 0 - } - return errors.AEADError("cannot further increment index") -} - -// aeadDecrypter reads and decrypts bytes. It buffers extra decrypted bytes when -// necessary, similar to aeadEncrypter. -type aeadDecrypter struct { - aeadCrypter // Embedded ciphertext opener - reader io.Reader // 'reader' is a partialLengthReader - chunkBytes []byte - peekedBytes []byte // Used to detect last chunk - buffer []byte // Buffered decrypted bytes -} - -// Read decrypts bytes and reads them into dst. It decrypts when necessary and -// buffers extra decrypted bytes. It returns the number of bytes copied into dst -// and an error. -func (ar *aeadDecrypter) Read(dst []byte) (n int, err error) { - // Return buffered plaintext bytes from previous calls - if len(ar.buffer) > 0 { - n = copy(dst, ar.buffer) - ar.buffer = ar.buffer[n:] - return - } - - // Read a chunk - tagLen := ar.aead.Overhead() - copy(ar.chunkBytes, ar.peekedBytes) // Copy bytes peeked in previous chunk or in initialization - bytesRead, errRead := io.ReadFull(ar.reader, ar.chunkBytes[tagLen:]) - if errRead != nil && errRead != io.EOF && errRead != io.ErrUnexpectedEOF { - return 0, errRead - } - - if bytesRead > 0 { - ar.peekedBytes = ar.chunkBytes[bytesRead:bytesRead+tagLen] - - decrypted, errChunk := ar.openChunk(ar.chunkBytes[:bytesRead]) - if errChunk != nil { - return 0, errChunk - } - - // Return decrypted bytes, buffering if necessary - n = copy(dst, decrypted) - ar.buffer = decrypted[n:] - return - } - - return 0, io.EOF -} - -// Close checks the final authentication tag of the stream. -// In the future, this function could also be used to wipe the reader -// and peeked & decrypted bytes, if necessary. -func (ar *aeadDecrypter) Close() (err error) { - errChunk := ar.validateFinalTag(ar.peekedBytes) - if errChunk != nil { - return errChunk - } - return nil -} - -// openChunk decrypts and checks integrity of an encrypted chunk, returning -// the underlying plaintext and an error. It accesses peeked bytes from next -// chunk, to identify the last chunk and decrypt/validate accordingly. -func (ar *aeadDecrypter) openChunk(data []byte) ([]byte, error) { - adata := ar.associatedData - if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted { - adata = append(ar.associatedData, ar.chunkIndex...) - } - - nonce := ar.computeNextNonce() - plainChunk, err := ar.aead.Open(data[:0:len(data)], nonce, data, adata) - if err != nil { - return nil, errors.ErrAEADTagVerification - } - ar.bytesProcessed += len(plainChunk) - if err = ar.aeadCrypter.incrementIndex(); err != nil { - return nil, err - } - return plainChunk, nil -} - -// Checks the summary tag. It takes into account the total decrypted bytes into -// the associated data. It returns an error, or nil if the tag is valid. -func (ar *aeadDecrypter) validateFinalTag(tag []byte) error { - // Associated: tag, version, cipher, aead, chunk size, ... - amountBytes := make([]byte, 8) - binary.BigEndian.PutUint64(amountBytes, uint64(ar.bytesProcessed)) - - adata := ar.associatedData - if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted { - // ... index ... - adata = append(ar.associatedData, ar.chunkIndex...) - } - - // ... and total number of encrypted octets - adata = append(adata, amountBytes...) - nonce := ar.computeNextNonce() - if _, err := ar.aead.Open(nil, nonce, tag, adata); err != nil { - return errors.ErrAEADTagVerification - } - return nil -} - -// aeadEncrypter encrypts and writes bytes. It encrypts when necessary according -// to the AEAD block size, and buffers the extra encrypted bytes for next write. -type aeadEncrypter struct { - aeadCrypter // Embedded plaintext sealer - writer io.WriteCloser // 'writer' is a partialLengthWriter - chunkBytes []byte - offset int -} - -// Write encrypts and writes bytes. It encrypts when necessary and buffers extra -// plaintext bytes for next call. When the stream is finished, Close() MUST be -// called to append the final tag. -func (aw *aeadEncrypter) Write(plaintextBytes []byte) (n int, err error) { - for n != len(plaintextBytes) { - copied := copy(aw.chunkBytes[aw.offset:aw.chunkSize], plaintextBytes[n:]) - n += copied - aw.offset += copied - - if aw.offset == aw.chunkSize { - encryptedChunk, err := aw.sealChunk(aw.chunkBytes[:aw.offset]) - if err != nil { - return n, err - } - _, err = aw.writer.Write(encryptedChunk) - if err != nil { - return n, err - } - aw.offset = 0 - } - } - return -} - -// Close encrypts and writes the remaining buffered plaintext if any, appends -// the final authentication tag, and closes the embedded writer. This function -// MUST be called at the end of a stream. -func (aw *aeadEncrypter) Close() (err error) { - // Encrypt and write a chunk if there's buffered data left, or if we haven't - // written any chunks yet. - if aw.offset > 0 || aw.bytesProcessed == 0 { - lastEncryptedChunk, err := aw.sealChunk(aw.chunkBytes[:aw.offset]) - if err != nil { - return err - } - _, err = aw.writer.Write(lastEncryptedChunk) - if err != nil { - return err - } - } - // Compute final tag (associated data: packet tag, version, cipher, aead, - // chunk size... - adata := aw.associatedData - - if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted { - // ... index ... - adata = append(aw.associatedData, aw.chunkIndex...) - } - - // ... and total number of encrypted octets - amountBytes := make([]byte, 8) - binary.BigEndian.PutUint64(amountBytes, uint64(aw.bytesProcessed)) - adata = append(adata, amountBytes...) - - nonce := aw.computeNextNonce() - finalTag := aw.aead.Seal(nil, nonce, nil, adata) - _, err = aw.writer.Write(finalTag) - if err != nil { - return err - } - return aw.writer.Close() -} - -// sealChunk Encrypts and authenticates the given chunk. -func (aw *aeadEncrypter) sealChunk(data []byte) ([]byte, error) { - if len(data) > aw.chunkSize { - return nil, errors.AEADError("chunk exceeds maximum length") - } - if aw.associatedData == nil { - return nil, errors.AEADError("can't seal without headers") - } - adata := aw.associatedData - if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted { - adata = append(aw.associatedData, aw.chunkIndex...) - } - - nonce := aw.computeNextNonce() - encrypted := aw.aead.Seal(data[:0], nonce, data, adata) - aw.bytesProcessed += len(data) - if err := aw.aeadCrypter.incrementIndex(); err != nil { - return nil, err - } - return encrypted, nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_encrypted.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_encrypted.go deleted file mode 100644 index 583765d87ca..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_encrypted.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (C) 2019 ProtonTech AG - -package packet - -import ( - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" -) - -// AEADEncrypted represents an AEAD Encrypted Packet. -// See https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-00.html#name-aead-encrypted-data-packet-t -type AEADEncrypted struct { - cipher CipherFunction - mode AEADMode - chunkSizeByte byte - Contents io.Reader // Encrypted chunks and tags - initialNonce []byte // Referred to as IV in RFC4880-bis -} - -// Only currently defined version -const aeadEncryptedVersion = 1 - -func (ae *AEADEncrypted) parse(buf io.Reader) error { - headerData := make([]byte, 4) - if n, err := io.ReadFull(buf, headerData); n < 4 { - return errors.AEADError("could not read aead header:" + err.Error()) - } - // Read initial nonce - mode := AEADMode(headerData[2]) - nonceLen := mode.IvLength() - - // This packet supports only EAX and OCB - // https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-00.html#name-aead-encrypted-data-packet-t - if nonceLen == 0 || mode > AEADModeOCB { - return errors.AEADError("unknown mode") - } - - initialNonce := make([]byte, nonceLen) - if n, err := io.ReadFull(buf, initialNonce); n < nonceLen { - return errors.AEADError("could not read aead nonce:" + err.Error()) - } - ae.Contents = buf - ae.initialNonce = initialNonce - c := headerData[1] - if _, ok := algorithm.CipherById[c]; !ok { - return errors.UnsupportedError("unknown cipher: " + string(c)) - } - ae.cipher = CipherFunction(c) - ae.mode = mode - ae.chunkSizeByte = headerData[3] - return nil -} - -// Decrypt returns a io.ReadCloser from which decrypted bytes can be read, or -// an error. -func (ae *AEADEncrypted) Decrypt(ciph CipherFunction, key []byte) (io.ReadCloser, error) { - return ae.decrypt(key) -} - -// decrypt prepares an aeadCrypter and returns a ReadCloser from which -// decrypted bytes can be read (see aeadDecrypter.Read()). -func (ae *AEADEncrypted) decrypt(key []byte) (io.ReadCloser, error) { - blockCipher := ae.cipher.new(key) - aead := ae.mode.new(blockCipher) - // Carry the first tagLen bytes - chunkSize := decodeAEADChunkSize(ae.chunkSizeByte) - tagLen := ae.mode.TagLength() - chunkBytes := make([]byte, chunkSize+tagLen*2) - peekedBytes := chunkBytes[chunkSize+tagLen:] - n, err := io.ReadFull(ae.Contents, peekedBytes) - if n < tagLen || (err != nil && err != io.EOF) { - return nil, errors.AEADError("Not enough data to decrypt:" + err.Error()) - } - - return &aeadDecrypter{ - aeadCrypter: aeadCrypter{ - aead: aead, - chunkSize: chunkSize, - nonce: ae.initialNonce, - associatedData: ae.associatedData(), - chunkIndex: make([]byte, 8), - packetTag: packetTypeAEADEncrypted, - }, - reader: ae.Contents, - chunkBytes: chunkBytes, - peekedBytes: peekedBytes, - }, nil -} - -// associatedData for chunks: tag, version, cipher, mode, chunk size byte -func (ae *AEADEncrypted) associatedData() []byte { - return []byte{ - 0xD4, - aeadEncryptedVersion, - byte(ae.cipher), - byte(ae.mode), - ae.chunkSizeByte} -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/compressed.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/compressed.go deleted file mode 100644 index 0bcb38cacac..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/compressed.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "compress/bzip2" - "compress/flate" - "compress/zlib" - "io" - "strconv" - - "github.com/ProtonMail/go-crypto/openpgp/errors" -) - -// Compressed represents a compressed OpenPGP packet. The decompressed contents -// will contain more OpenPGP packets. See RFC 4880, section 5.6. -type Compressed struct { - Body io.Reader -} - -const ( - NoCompression = flate.NoCompression - BestSpeed = flate.BestSpeed - BestCompression = flate.BestCompression - DefaultCompression = flate.DefaultCompression -) - -// CompressionConfig contains compressor configuration settings. -type CompressionConfig struct { - // Level is the compression level to use. It must be set to - // between -1 and 9, with -1 causing the compressor to use the - // default compression level, 0 causing the compressor to use - // no compression and 1 to 9 representing increasing (better, - // slower) compression levels. If Level is less than -1 or - // more then 9, a non-nil error will be returned during - // encryption. See the constants above for convenient common - // settings for Level. - Level int -} - -// decompressionReader ensures that the whole compression packet is read. -type decompressionReader struct { - compressed io.Reader - decompressed io.ReadCloser - readAll bool -} - -func newDecompressionReader(r io.Reader, decompressor io.ReadCloser) *decompressionReader { - return &decompressionReader{ - compressed: r, - decompressed: decompressor, - } -} - -func (dr *decompressionReader) Read(data []byte) (n int, err error) { - if dr.readAll { - return 0, io.EOF - } - n, err = dr.decompressed.Read(data) - if err == io.EOF { - dr.readAll = true - // Close the decompressor. - if errDec := dr.decompressed.Close(); errDec != nil { - return n, errDec - } - // Consume all remaining data from the compressed packet. - consumeAll(dr.compressed) - } - return n, err -} - -func (c *Compressed) parse(r io.Reader) error { - var buf [1]byte - _, err := readFull(r, buf[:]) - if err != nil { - return err - } - - switch buf[0] { - case 0: - c.Body = r - case 1: - c.Body = newDecompressionReader(r, flate.NewReader(r)) - case 2: - decompressor, err := zlib.NewReader(r) - if err != nil { - return err - } - c.Body = newDecompressionReader(r, decompressor) - case 3: - c.Body = newDecompressionReader(r, io.NopCloser(bzip2.NewReader(r))) - default: - err = errors.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0]))) - } - - return err -} - -// compressedWriterCloser represents the serialized compression stream -// header and the compressor. Its Close() method ensures that both the -// compressor and serialized stream header are closed. Its Write() -// method writes to the compressor. -type compressedWriteCloser struct { - sh io.Closer // Stream Header - c io.WriteCloser // Compressor -} - -func (cwc compressedWriteCloser) Write(p []byte) (int, error) { - return cwc.c.Write(p) -} - -func (cwc compressedWriteCloser) Close() (err error) { - err = cwc.c.Close() - if err != nil { - return err - } - - return cwc.sh.Close() -} - -// SerializeCompressed serializes a compressed data packet to w and -// returns a WriteCloser to which the literal data packets themselves -// can be written and which MUST be closed on completion. If cc is -// nil, sensible defaults will be used to configure the compression -// algorithm. -func SerializeCompressed(w io.WriteCloser, algo CompressionAlgo, cc *CompressionConfig) (literaldata io.WriteCloser, err error) { - compressed, err := serializeStreamHeader(w, packetTypeCompressed) - if err != nil { - return - } - - _, err = compressed.Write([]byte{uint8(algo)}) - if err != nil { - return - } - - level := DefaultCompression - if cc != nil { - level = cc.Level - } - - var compressor io.WriteCloser - switch algo { - case CompressionZIP: - compressor, err = flate.NewWriter(compressed, level) - case CompressionZLIB: - compressor, err = zlib.NewWriterLevel(compressed, level) - default: - s := strconv.Itoa(int(algo)) - err = errors.UnsupportedError("Unsupported compression algorithm: " + s) - } - if err != nil { - return - } - - literaldata = compressedWriteCloser{compressed, compressor} - - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/config.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/config.go deleted file mode 100644 index 257398d9dde..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/config.go +++ /dev/null @@ -1,422 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "crypto" - "crypto/rand" - "io" - "math/big" - "time" - - "github.com/ProtonMail/go-crypto/openpgp/s2k" -) - -var ( - defaultRejectPublicKeyAlgorithms = map[PublicKeyAlgorithm]bool{ - PubKeyAlgoElGamal: true, - PubKeyAlgoDSA: true, - } - defaultRejectHashAlgorithms = map[crypto.Hash]bool{ - crypto.MD5: true, - crypto.RIPEMD160: true, - } - defaultRejectMessageHashAlgorithms = map[crypto.Hash]bool{ - crypto.SHA1: true, - crypto.MD5: true, - crypto.RIPEMD160: true, - } - defaultRejectCurves = map[Curve]bool{ - CurveSecP256k1: true, - } -) - -// A global feature flag to indicate v5 support. -// Can be set via a build tag, e.g.: `go build -tags v5 ./...` -// If the build tag is missing config_v5.go will set it to true. -// -// Disables parsing of v5 keys and v5 signatures. -// These are non-standard entities, which in the crypto-refresh have been superseded -// by v6 keys, v6 signatures and SEIPDv2 encrypted data, respectively. -var V5Disabled = false - -// Config collects a number of parameters along with sensible defaults. -// A nil *Config is valid and results in all default values. -type Config struct { - // Rand provides the source of entropy. - // If nil, the crypto/rand Reader is used. - Rand io.Reader - // DefaultHash is the default hash function to be used. - // If zero, SHA-256 is used. - DefaultHash crypto.Hash - // DefaultCipher is the cipher to be used. - // If zero, AES-128 is used. - DefaultCipher CipherFunction - // Time returns the current time as the number of seconds since the - // epoch. If Time is nil, time.Now is used. - Time func() time.Time - // DefaultCompressionAlgo is the compression algorithm to be - // applied to the plaintext before encryption. If zero, no - // compression is done. - DefaultCompressionAlgo CompressionAlgo - // CompressionConfig configures the compression settings. - CompressionConfig *CompressionConfig - // S2K (String to Key) config, used for key derivation in the context of secret key encryption - // and password-encrypted data. - // If nil, the default configuration is used - S2KConfig *s2k.Config - // Iteration count for Iterated S2K (String to Key). - // Only used if sk2.Mode is nil. - // This value is duplicated here from s2k.Config for backwards compatibility. - // It determines the strength of the passphrase stretching when - // the said passphrase is hashed to produce a key. S2KCount - // should be between 65536 and 65011712, inclusive. If Config - // is nil or S2KCount is 0, the value 16777216 used. Not all - // values in the above range can be represented. S2KCount will - // be rounded up to the next representable value if it cannot - // be encoded exactly. When set, it is strongly encrouraged to - // use a value that is at least 65536. See RFC 4880 Section - // 3.7.1.3. - // - // Deprecated: SK2Count should be configured in S2KConfig instead. - S2KCount int - // RSABits is the number of bits in new RSA keys made with NewEntity. - // If zero, then 2048 bit keys are created. - RSABits int - // The public key algorithm to use - will always create a signing primary - // key and encryption subkey. - Algorithm PublicKeyAlgorithm - // Some known primes that are optionally prepopulated by the caller - RSAPrimes []*big.Int - // Curve configures the desired packet.Curve if the Algorithm is PubKeyAlgoECDSA, - // PubKeyAlgoEdDSA, or PubKeyAlgoECDH. If empty Curve25519 is used. - Curve Curve - // AEADConfig configures the use of the new AEAD Encrypted Data Packet, - // defined in the draft of the next version of the OpenPGP specification. - // If a non-nil AEADConfig is passed, usage of this packet is enabled. By - // default, it is disabled. See the documentation of AEADConfig for more - // configuration options related to AEAD. - // **Note: using this option may break compatibility with other OpenPGP - // implementations, as well as future versions of this library.** - AEADConfig *AEADConfig - // V6Keys configures version 6 key generation. If false, this package still - // supports version 6 keys, but produces version 4 keys. - V6Keys bool - // Minimum RSA key size allowed for key generation and message signing, verification and encryption. - MinRSABits uint16 - // Reject insecure algorithms, only works with v2 api - RejectPublicKeyAlgorithms map[PublicKeyAlgorithm]bool - RejectHashAlgorithms map[crypto.Hash]bool - RejectMessageHashAlgorithms map[crypto.Hash]bool - RejectCurves map[Curve]bool - // "The validity period of the key. This is the number of seconds after - // the key creation time that the key expires. If this is not present - // or has a value of zero, the key never expires. This is found only on - // a self-signature."" - // https://tools.ietf.org/html/rfc4880#section-5.2.3.6 - KeyLifetimeSecs uint32 - // "The validity period of the signature. This is the number of seconds - // after the signature creation time that the signature expires. If - // this is not present or has a value of zero, it never expires." - // https://tools.ietf.org/html/rfc4880#section-5.2.3.10 - SigLifetimeSecs uint32 - // SigningKeyId is used to specify the signing key to use (by Key ID). - // By default, the signing key is selected automatically, preferring - // signing subkeys if available. - SigningKeyId uint64 - // SigningIdentity is used to specify a user ID (packet Signer's User ID, type 28) - // when producing a generic certification signature onto an existing user ID. - // The identity must be present in the signer Entity. - SigningIdentity string - // InsecureAllowUnauthenticatedMessages controls, whether it is tolerated to read - // encrypted messages without Modification Detection Code (MDC). - // MDC is mandated by the IETF OpenPGP Crypto Refresh draft and has long been implemented - // in most OpenPGP implementations. Messages without MDC are considered unnecessarily - // insecure and should be prevented whenever possible. - // In case one needs to deal with messages from very old OpenPGP implementations, there - // might be no other way than to tolerate the missing MDC. Setting this flag, allows this - // mode of operation. It should be considered a measure of last resort. - InsecureAllowUnauthenticatedMessages bool - // InsecureAllowDecryptionWithSigningKeys allows decryption with keys marked as signing keys in the v2 API. - // This setting is potentially insecure, but it is needed as some libraries - // ignored key flags when selecting a key for encryption. - // Not relevant for the v1 API, as all keys were allowed in decryption. - InsecureAllowDecryptionWithSigningKeys bool - // KnownNotations is a map of Notation Data names to bools, which controls - // the notation names that are allowed to be present in critical Notation Data - // signature subpackets. - KnownNotations map[string]bool - // SignatureNotations is a list of Notations to be added to any signatures. - SignatureNotations []*Notation - // CheckIntendedRecipients controls, whether the OpenPGP Intended Recipient Fingerprint feature - // should be enabled for encryption and decryption. - // (See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-12.html#name-intended-recipient-fingerpr). - // When the flag is set, encryption produces Intended Recipient Fingerprint signature sub-packets and decryption - // checks whether the key it was encrypted to is one of the included fingerprints in the signature. - // If the flag is disabled, no Intended Recipient Fingerprint sub-packets are created or checked. - // The default behavior, when the config or flag is nil, is to enable the feature. - CheckIntendedRecipients *bool - // CacheSessionKey controls if decryption should return the session key used for decryption. - // If the flag is set, the session key is cached in the message details struct. - CacheSessionKey bool - // CheckPacketSequence is a flag that controls if the pgp message reader should strictly check - // that the packet sequence conforms with the grammar mandated by rfc4880. - // The default behavior, when the config or flag is nil, is to check the packet sequence. - CheckPacketSequence *bool - // NonDeterministicSignaturesViaNotation is a flag to enable randomization of signatures. - // If true, a salt notation is used to randomize signatures generated by v4 and v5 keys - // (v6 signatures are always non-deterministic, by design). - // This protects EdDSA signatures from potentially leaking the secret key in case of faults (i.e. bitflips) which, in principle, could occur - // during the signing computation. It is added to signatures of any algo for simplicity, and as it may also serve as protection in case of - // weaknesses in the hash algo, potentially hindering e.g. some chosen-prefix attacks. - // The default behavior, when the config or flag is nil, is to enable the feature. - NonDeterministicSignaturesViaNotation *bool - - // InsecureAllowAllKeyFlagsWhenMissing determines how a key without valid key flags is handled. - // When set to true, a key without flags is treated as if all flags are enabled. - // This behavior is consistent with GPG. - InsecureAllowAllKeyFlagsWhenMissing bool -} - -func (c *Config) Random() io.Reader { - if c == nil || c.Rand == nil { - return rand.Reader - } - return c.Rand -} - -func (c *Config) Hash() crypto.Hash { - if c == nil || uint(c.DefaultHash) == 0 { - return crypto.SHA256 - } - return c.DefaultHash -} - -func (c *Config) Cipher() CipherFunction { - if c == nil || uint8(c.DefaultCipher) == 0 { - return CipherAES128 - } - return c.DefaultCipher -} - -func (c *Config) Now() time.Time { - if c == nil || c.Time == nil { - return time.Now().Truncate(time.Second) - } - return c.Time().Truncate(time.Second) -} - -// KeyLifetime returns the validity period of the key. -func (c *Config) KeyLifetime() uint32 { - if c == nil { - return 0 - } - return c.KeyLifetimeSecs -} - -// SigLifetime returns the validity period of the signature. -func (c *Config) SigLifetime() uint32 { - if c == nil { - return 0 - } - return c.SigLifetimeSecs -} - -func (c *Config) Compression() CompressionAlgo { - if c == nil { - return CompressionNone - } - return c.DefaultCompressionAlgo -} - -func (c *Config) RSAModulusBits() int { - if c == nil || c.RSABits == 0 { - return 2048 - } - return c.RSABits -} - -func (c *Config) PublicKeyAlgorithm() PublicKeyAlgorithm { - if c == nil || c.Algorithm == 0 { - return PubKeyAlgoRSA - } - return c.Algorithm -} - -func (c *Config) CurveName() Curve { - if c == nil || c.Curve == "" { - return Curve25519 - } - return c.Curve -} - -// Deprecated: The hash iterations should now be queried via the S2K() method. -func (c *Config) PasswordHashIterations() int { - if c == nil || c.S2KCount == 0 { - return 0 - } - return c.S2KCount -} - -func (c *Config) S2K() *s2k.Config { - if c == nil { - return nil - } - // for backwards compatibility - if c.S2KCount > 0 && c.S2KConfig == nil { - return &s2k.Config{ - S2KCount: c.S2KCount, - } - } - return c.S2KConfig -} - -func (c *Config) AEAD() *AEADConfig { - if c == nil { - return nil - } - return c.AEADConfig -} - -func (c *Config) SigningKey() uint64 { - if c == nil { - return 0 - } - return c.SigningKeyId -} - -func (c *Config) SigningUserId() string { - if c == nil { - return "" - } - return c.SigningIdentity -} - -func (c *Config) AllowUnauthenticatedMessages() bool { - if c == nil { - return false - } - return c.InsecureAllowUnauthenticatedMessages -} - -func (c *Config) AllowDecryptionWithSigningKeys() bool { - if c == nil { - return false - } - return c.InsecureAllowDecryptionWithSigningKeys -} - -func (c *Config) KnownNotation(notationName string) bool { - if c == nil { - return false - } - return c.KnownNotations[notationName] -} - -func (c *Config) Notations() []*Notation { - if c == nil { - return nil - } - return c.SignatureNotations -} - -func (c *Config) V6() bool { - if c == nil { - return false - } - return c.V6Keys -} - -func (c *Config) IntendedRecipients() bool { - if c == nil || c.CheckIntendedRecipients == nil { - return true - } - return *c.CheckIntendedRecipients -} - -func (c *Config) RetrieveSessionKey() bool { - if c == nil { - return false - } - return c.CacheSessionKey -} - -func (c *Config) MinimumRSABits() uint16 { - if c == nil || c.MinRSABits == 0 { - return 2047 - } - return c.MinRSABits -} - -func (c *Config) RejectPublicKeyAlgorithm(alg PublicKeyAlgorithm) bool { - var rejectedAlgorithms map[PublicKeyAlgorithm]bool - if c == nil || c.RejectPublicKeyAlgorithms == nil { - // Default - rejectedAlgorithms = defaultRejectPublicKeyAlgorithms - } else { - rejectedAlgorithms = c.RejectPublicKeyAlgorithms - } - return rejectedAlgorithms[alg] -} - -func (c *Config) RejectHashAlgorithm(hash crypto.Hash) bool { - var rejectedAlgorithms map[crypto.Hash]bool - if c == nil || c.RejectHashAlgorithms == nil { - // Default - rejectedAlgorithms = defaultRejectHashAlgorithms - } else { - rejectedAlgorithms = c.RejectHashAlgorithms - } - return rejectedAlgorithms[hash] -} - -func (c *Config) RejectMessageHashAlgorithm(hash crypto.Hash) bool { - var rejectedAlgorithms map[crypto.Hash]bool - if c == nil || c.RejectMessageHashAlgorithms == nil { - // Default - rejectedAlgorithms = defaultRejectMessageHashAlgorithms - } else { - rejectedAlgorithms = c.RejectMessageHashAlgorithms - } - return rejectedAlgorithms[hash] -} - -func (c *Config) RejectCurve(curve Curve) bool { - var rejectedCurve map[Curve]bool - if c == nil || c.RejectCurves == nil { - // Default - rejectedCurve = defaultRejectCurves - } else { - rejectedCurve = c.RejectCurves - } - return rejectedCurve[curve] -} - -func (c *Config) StrictPacketSequence() bool { - if c == nil || c.CheckPacketSequence == nil { - return true - } - return *c.CheckPacketSequence -} - -func (c *Config) RandomizeSignaturesViaNotation() bool { - if c == nil || c.NonDeterministicSignaturesViaNotation == nil { - return true - } - return *c.NonDeterministicSignaturesViaNotation -} - -func (c *Config) AllowAllKeyFlagsWhenMissing() bool { - if c == nil { - return false - } - return c.InsecureAllowAllKeyFlagsWhenMissing -} - -// BoolPointer is a helper function to set a boolean pointer in the Config. -// e.g., config.CheckPacketSequence = BoolPointer(true) -func BoolPointer(value bool) *bool { - return &value -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/config_v5.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/config_v5.go deleted file mode 100644 index f2415906b9d..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/config_v5.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !v5 - -package packet - -func init() { - V5Disabled = true -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/encrypted_key.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/encrypted_key.go deleted file mode 100644 index b90bb289119..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/encrypted_key.go +++ /dev/null @@ -1,584 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "bytes" - "crypto" - "crypto/rsa" - "encoding/binary" - "encoding/hex" - "io" - "math/big" - "strconv" - - "github.com/ProtonMail/go-crypto/openpgp/ecdh" - "github.com/ProtonMail/go-crypto/openpgp/elgamal" - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" - "github.com/ProtonMail/go-crypto/openpgp/x25519" - "github.com/ProtonMail/go-crypto/openpgp/x448" -) - -// EncryptedKey represents a public-key encrypted session key. See RFC 4880, -// section 5.1. -type EncryptedKey struct { - Version int - KeyId uint64 - KeyVersion int // v6 - KeyFingerprint []byte // v6 - Algo PublicKeyAlgorithm - CipherFunc CipherFunction // only valid after a successful Decrypt for a v3 packet - Key []byte // only valid after a successful Decrypt - - encryptedMPI1, encryptedMPI2 encoding.Field - ephemeralPublicX25519 *x25519.PublicKey // used for x25519 - ephemeralPublicX448 *x448.PublicKey // used for x448 - encryptedSession []byte // used for x25519 and x448 -} - -func (e *EncryptedKey) parse(r io.Reader) (err error) { - var buf [8]byte - _, err = readFull(r, buf[:versionSize]) - if err != nil { - return - } - e.Version = int(buf[0]) - if e.Version != 3 && e.Version != 6 { - return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0]))) - } - if e.Version == 6 { - //Read a one-octet size of the following two fields. - if _, err = readFull(r, buf[:1]); err != nil { - return - } - // The size may also be zero, and the key version and - // fingerprint omitted for an "anonymous recipient" - if buf[0] != 0 { - // non-anonymous case - _, err = readFull(r, buf[:versionSize]) - if err != nil { - return - } - e.KeyVersion = int(buf[0]) - if e.KeyVersion != 4 && e.KeyVersion != 6 { - return errors.UnsupportedError("unknown public key version " + strconv.Itoa(e.KeyVersion)) - } - var fingerprint []byte - if e.KeyVersion == 6 { - fingerprint = make([]byte, fingerprintSizeV6) - } else if e.KeyVersion == 4 { - fingerprint = make([]byte, fingerprintSize) - } - _, err = readFull(r, fingerprint) - if err != nil { - return - } - e.KeyFingerprint = fingerprint - if e.KeyVersion == 6 { - e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[:keyIdSize]) - } else if e.KeyVersion == 4 { - e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[fingerprintSize-keyIdSize : fingerprintSize]) - } - } - } else { - _, err = readFull(r, buf[:8]) - if err != nil { - return - } - e.KeyId = binary.BigEndian.Uint64(buf[:keyIdSize]) - } - - _, err = readFull(r, buf[:1]) - if err != nil { - return - } - e.Algo = PublicKeyAlgorithm(buf[0]) - var cipherFunction byte - switch e.Algo { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: - e.encryptedMPI1 = new(encoding.MPI) - if _, err = e.encryptedMPI1.ReadFrom(r); err != nil { - return - } - case PubKeyAlgoElGamal: - e.encryptedMPI1 = new(encoding.MPI) - if _, err = e.encryptedMPI1.ReadFrom(r); err != nil { - return - } - - e.encryptedMPI2 = new(encoding.MPI) - if _, err = e.encryptedMPI2.ReadFrom(r); err != nil { - return - } - case PubKeyAlgoECDH: - e.encryptedMPI1 = new(encoding.MPI) - if _, err = e.encryptedMPI1.ReadFrom(r); err != nil { - return - } - - e.encryptedMPI2 = new(encoding.OID) - if _, err = e.encryptedMPI2.ReadFrom(r); err != nil { - return - } - case PubKeyAlgoX25519: - e.ephemeralPublicX25519, e.encryptedSession, cipherFunction, err = x25519.DecodeFields(r, e.Version == 6) - if err != nil { - return - } - case PubKeyAlgoX448: - e.ephemeralPublicX448, e.encryptedSession, cipherFunction, err = x448.DecodeFields(r, e.Version == 6) - if err != nil { - return - } - } - if e.Version < 6 { - switch e.Algo { - case PubKeyAlgoX25519, PubKeyAlgoX448: - e.CipherFunc = CipherFunction(cipherFunction) - // Check for validiy is in the Decrypt method - } - } - - _, err = consumeAll(r) - return -} - -// Decrypt decrypts an encrypted session key with the given private key. The -// private key must have been decrypted first. -// If config is nil, sensible defaults will be used. -func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { - if e.Version < 6 && e.KeyId != 0 && e.KeyId != priv.KeyId { - return errors.InvalidArgumentError("cannot decrypt encrypted session key for key id " + strconv.FormatUint(e.KeyId, 16) + " with private key id " + strconv.FormatUint(priv.KeyId, 16)) - } - if e.Version == 6 && e.KeyVersion != 0 && !bytes.Equal(e.KeyFingerprint, priv.Fingerprint) { - return errors.InvalidArgumentError("cannot decrypt encrypted session key for key fingerprint " + hex.EncodeToString(e.KeyFingerprint) + " with private key fingerprint " + hex.EncodeToString(priv.Fingerprint)) - } - if e.Algo != priv.PubKeyAlgo { - return errors.InvalidArgumentError("cannot decrypt encrypted session key of type " + strconv.Itoa(int(e.Algo)) + " with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo))) - } - if priv.Dummy() { - return errors.ErrDummyPrivateKey("dummy key found") - } - - var err error - var b []byte - - // TODO(agl): use session key decryption routines here to avoid - // padding oracle attacks. - switch priv.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: - // Supports both *rsa.PrivateKey and crypto.Decrypter - k := priv.PrivateKey.(crypto.Decrypter) - b, err = k.Decrypt(config.Random(), padToKeySize(k.Public().(*rsa.PublicKey), e.encryptedMPI1.Bytes()), nil) - case PubKeyAlgoElGamal: - c1 := new(big.Int).SetBytes(e.encryptedMPI1.Bytes()) - c2 := new(big.Int).SetBytes(e.encryptedMPI2.Bytes()) - b, err = elgamal.Decrypt(priv.PrivateKey.(*elgamal.PrivateKey), c1, c2) - case PubKeyAlgoECDH: - vsG := e.encryptedMPI1.Bytes() - m := e.encryptedMPI2.Bytes() - oid := priv.PublicKey.oid.EncodedBytes() - fp := priv.PublicKey.Fingerprint[:] - if priv.PublicKey.Version == 5 { - // For v5 the, the fingerprint must be restricted to 20 bytes - fp = fp[:20] - } - b, err = ecdh.Decrypt(priv.PrivateKey.(*ecdh.PrivateKey), vsG, m, oid, fp) - case PubKeyAlgoX25519: - b, err = x25519.Decrypt(priv.PrivateKey.(*x25519.PrivateKey), e.ephemeralPublicX25519, e.encryptedSession) - case PubKeyAlgoX448: - b, err = x448.Decrypt(priv.PrivateKey.(*x448.PrivateKey), e.ephemeralPublicX448, e.encryptedSession) - default: - err = errors.InvalidArgumentError("cannot decrypt encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo))) - } - if err != nil { - return err - } - - var key []byte - switch priv.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH: - keyOffset := 0 - if e.Version < 6 { - e.CipherFunc = CipherFunction(b[0]) - keyOffset = 1 - if !e.CipherFunc.IsSupported() { - return errors.UnsupportedError("unsupported encryption function") - } - } - key, err = decodeChecksumKey(b[keyOffset:]) - if err != nil { - return err - } - case PubKeyAlgoX25519, PubKeyAlgoX448: - if e.Version < 6 { - switch e.CipherFunc { - case CipherAES128, CipherAES192, CipherAES256: - break - default: - return errors.StructuralError("v3 PKESK mandates AES as cipher function for x25519 and x448") - } - } - key = b[:] - default: - return errors.UnsupportedError("unsupported algorithm for decryption") - } - e.Key = key - return nil -} - -// Serialize writes the encrypted key packet, e, to w. -func (e *EncryptedKey) Serialize(w io.Writer) error { - var encodedLength int - switch e.Algo { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: - encodedLength = int(e.encryptedMPI1.EncodedLength()) - case PubKeyAlgoElGamal: - encodedLength = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength()) - case PubKeyAlgoECDH: - encodedLength = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength()) - case PubKeyAlgoX25519: - encodedLength = x25519.EncodedFieldsLength(e.encryptedSession, e.Version == 6) - case PubKeyAlgoX448: - encodedLength = x448.EncodedFieldsLength(e.encryptedSession, e.Version == 6) - default: - return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo))) - } - - packetLen := versionSize /* version */ + keyIdSize /* key id */ + algorithmSize /* algo */ + encodedLength - if e.Version == 6 { - packetLen = versionSize /* version */ + algorithmSize /* algo */ + encodedLength + keyVersionSize /* key version */ - if e.KeyVersion == 6 { - packetLen += fingerprintSizeV6 - } else if e.KeyVersion == 4 { - packetLen += fingerprintSize - } - } - - err := serializeHeader(w, packetTypeEncryptedKey, packetLen) - if err != nil { - return err - } - - _, err = w.Write([]byte{byte(e.Version)}) - if err != nil { - return err - } - if e.Version == 6 { - _, err = w.Write([]byte{byte(e.KeyVersion)}) - if err != nil { - return err - } - // The key version number may also be zero, - // and the fingerprint omitted - if e.KeyVersion != 0 { - _, err = w.Write(e.KeyFingerprint) - if err != nil { - return err - } - } - } else { - // Write KeyID - err = binary.Write(w, binary.BigEndian, e.KeyId) - if err != nil { - return err - } - } - _, err = w.Write([]byte{byte(e.Algo)}) - if err != nil { - return err - } - - switch e.Algo { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: - _, err := w.Write(e.encryptedMPI1.EncodedBytes()) - return err - case PubKeyAlgoElGamal: - if _, err := w.Write(e.encryptedMPI1.EncodedBytes()); err != nil { - return err - } - _, err := w.Write(e.encryptedMPI2.EncodedBytes()) - return err - case PubKeyAlgoECDH: - if _, err := w.Write(e.encryptedMPI1.EncodedBytes()); err != nil { - return err - } - _, err := w.Write(e.encryptedMPI2.EncodedBytes()) - return err - case PubKeyAlgoX25519: - err := x25519.EncodeFields(w, e.ephemeralPublicX25519, e.encryptedSession, byte(e.CipherFunc), e.Version == 6) - return err - case PubKeyAlgoX448: - err := x448.EncodeFields(w, e.ephemeralPublicX448, e.encryptedSession, byte(e.CipherFunc), e.Version == 6) - return err - default: - panic("internal error") - } -} - -// SerializeEncryptedKeyAEAD serializes an encrypted key packet to w that contains -// key, encrypted to pub. -// If aeadSupported is set, PKESK v6 is used, otherwise v3. -// Note: aeadSupported MUST match the value passed to SerializeSymmetricallyEncrypted. -// If config is nil, sensible defaults will be used. -func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, aeadSupported bool, key []byte, config *Config) error { - return SerializeEncryptedKeyAEADwithHiddenOption(w, pub, cipherFunc, aeadSupported, key, false, config) -} - -// SerializeEncryptedKeyAEADwithHiddenOption serializes an encrypted key packet to w that contains -// key, encrypted to pub. -// Offers the hidden flag option to indicated if the PKESK packet should include a wildcard KeyID. -// If aeadSupported is set, PKESK v6 is used, otherwise v3. -// Note: aeadSupported MUST match the value passed to SerializeSymmetricallyEncrypted. -// If config is nil, sensible defaults will be used. -func SerializeEncryptedKeyAEADwithHiddenOption(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, aeadSupported bool, key []byte, hidden bool, config *Config) error { - var buf [36]byte // max possible header size is v6 - lenHeaderWritten := versionSize - version := 3 - - if aeadSupported { - version = 6 - } - // An implementation MUST NOT generate ElGamal v6 PKESKs. - if version == 6 && pub.PubKeyAlgo == PubKeyAlgoElGamal { - return errors.InvalidArgumentError("ElGamal v6 PKESK are not allowed") - } - // In v3 PKESKs, for x25519 and x448, mandate using AES - if version == 3 && (pub.PubKeyAlgo == PubKeyAlgoX25519 || pub.PubKeyAlgo == PubKeyAlgoX448) { - switch cipherFunc { - case CipherAES128, CipherAES192, CipherAES256: - break - default: - return errors.InvalidArgumentError("v3 PKESK mandates AES for x25519 and x448") - } - } - - buf[0] = byte(version) - - // If hidden is set, the key should be hidden - // An implementation MAY accept or use a Key ID of all zeros, - // or a key version of zero and no key fingerprint, to hide the intended decryption key. - // See Section 5.1.8. in the open pgp crypto refresh - if version == 6 { - if !hidden { - // A one-octet size of the following two fields. - buf[1] = byte(keyVersionSize + len(pub.Fingerprint)) - // A one octet key version number. - buf[2] = byte(pub.Version) - lenHeaderWritten += keyVersionSize + 1 - // The fingerprint of the public key - copy(buf[lenHeaderWritten:lenHeaderWritten+len(pub.Fingerprint)], pub.Fingerprint) - lenHeaderWritten += len(pub.Fingerprint) - } else { - // The size may also be zero, and the key version - // and fingerprint omitted for an "anonymous recipient" - buf[1] = 0 - lenHeaderWritten += 1 - } - } else { - if !hidden { - binary.BigEndian.PutUint64(buf[versionSize:(versionSize+keyIdSize)], pub.KeyId) - } - lenHeaderWritten += keyIdSize - } - buf[lenHeaderWritten] = byte(pub.PubKeyAlgo) - lenHeaderWritten += algorithmSize - - var keyBlock []byte - switch pub.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH: - lenKeyBlock := len(key) + 2 - if version < 6 { - lenKeyBlock += 1 // cipher type included - } - keyBlock = make([]byte, lenKeyBlock) - keyOffset := 0 - if version < 6 { - keyBlock[0] = byte(cipherFunc) - keyOffset = 1 - } - encodeChecksumKey(keyBlock[keyOffset:], key) - case PubKeyAlgoX25519, PubKeyAlgoX448: - // algorithm is added in plaintext below - keyBlock = key - } - - switch pub.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: - return serializeEncryptedKeyRSA(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*rsa.PublicKey), keyBlock) - case PubKeyAlgoElGamal: - return serializeEncryptedKeyElGamal(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*elgamal.PublicKey), keyBlock) - case PubKeyAlgoECDH: - return serializeEncryptedKeyECDH(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*ecdh.PublicKey), keyBlock, pub.oid, pub.Fingerprint) - case PubKeyAlgoX25519: - return serializeEncryptedKeyX25519(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*x25519.PublicKey), keyBlock, byte(cipherFunc), version) - case PubKeyAlgoX448: - return serializeEncryptedKeyX448(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*x448.PublicKey), keyBlock, byte(cipherFunc), version) - case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly: - return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo))) - } - - return errors.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo))) -} - -// SerializeEncryptedKey serializes an encrypted key packet to w that contains -// key, encrypted to pub. -// PKESKv6 is used if config.AEAD() is not nil. -// If config is nil, sensible defaults will be used. -// Deprecated: Use SerializeEncryptedKeyAEAD instead. -func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, config *Config) error { - return SerializeEncryptedKeyAEAD(w, pub, cipherFunc, config.AEAD() != nil, key, config) -} - -// SerializeEncryptedKeyWithHiddenOption serializes an encrypted key packet to w that contains -// key, encrypted to pub. PKESKv6 is used if config.AEAD() is not nil. -// The hidden option controls if the packet should be anonymous, i.e., omit key metadata. -// If config is nil, sensible defaults will be used. -// Deprecated: Use SerializeEncryptedKeyAEADwithHiddenOption instead. -func SerializeEncryptedKeyWithHiddenOption(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, hidden bool, config *Config) error { - return SerializeEncryptedKeyAEADwithHiddenOption(w, pub, cipherFunc, config.AEAD() != nil, key, hidden, config) -} - -func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header []byte, pub *rsa.PublicKey, keyBlock []byte) error { - cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock) - if err != nil { - return errors.InvalidArgumentError("RSA encryption failed: " + err.Error()) - } - - cipherMPI := encoding.NewMPI(cipherText) - packetLen := len(header) /* header length */ + int(cipherMPI.EncodedLength()) - - err = serializeHeader(w, packetTypeEncryptedKey, packetLen) - if err != nil { - return err - } - _, err = w.Write(header[:]) - if err != nil { - return err - } - _, err = w.Write(cipherMPI.EncodedBytes()) - return err -} - -func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header []byte, pub *elgamal.PublicKey, keyBlock []byte) error { - c1, c2, err := elgamal.Encrypt(rand, pub, keyBlock) - if err != nil { - return errors.InvalidArgumentError("ElGamal encryption failed: " + err.Error()) - } - - packetLen := len(header) /* header length */ - packetLen += 2 /* mpi size */ + (c1.BitLen()+7)/8 - packetLen += 2 /* mpi size */ + (c2.BitLen()+7)/8 - - err = serializeHeader(w, packetTypeEncryptedKey, packetLen) - if err != nil { - return err - } - _, err = w.Write(header[:]) - if err != nil { - return err - } - if _, err = w.Write(new(encoding.MPI).SetBig(c1).EncodedBytes()); err != nil { - return err - } - _, err = w.Write(new(encoding.MPI).SetBig(c2).EncodedBytes()) - return err -} - -func serializeEncryptedKeyECDH(w io.Writer, rand io.Reader, header []byte, pub *ecdh.PublicKey, keyBlock []byte, oid encoding.Field, fingerprint []byte) error { - vsG, c, err := ecdh.Encrypt(rand, pub, keyBlock, oid.EncodedBytes(), fingerprint) - if err != nil { - return errors.InvalidArgumentError("ECDH encryption failed: " + err.Error()) - } - - g := encoding.NewMPI(vsG) - m := encoding.NewOID(c) - - packetLen := len(header) /* header length */ - packetLen += int(g.EncodedLength()) + int(m.EncodedLength()) - - err = serializeHeader(w, packetTypeEncryptedKey, packetLen) - if err != nil { - return err - } - - _, err = w.Write(header[:]) - if err != nil { - return err - } - if _, err = w.Write(g.EncodedBytes()); err != nil { - return err - } - _, err = w.Write(m.EncodedBytes()) - return err -} - -func serializeEncryptedKeyX25519(w io.Writer, rand io.Reader, header []byte, pub *x25519.PublicKey, keyBlock []byte, cipherFunc byte, version int) error { - ephemeralPublicX25519, ciphertext, err := x25519.Encrypt(rand, pub, keyBlock) - if err != nil { - return errors.InvalidArgumentError("x25519 encryption failed: " + err.Error()) - } - - packetLen := len(header) /* header length */ - packetLen += x25519.EncodedFieldsLength(ciphertext, version == 6) - - err = serializeHeader(w, packetTypeEncryptedKey, packetLen) - if err != nil { - return err - } - - _, err = w.Write(header[:]) - if err != nil { - return err - } - return x25519.EncodeFields(w, ephemeralPublicX25519, ciphertext, cipherFunc, version == 6) -} - -func serializeEncryptedKeyX448(w io.Writer, rand io.Reader, header []byte, pub *x448.PublicKey, keyBlock []byte, cipherFunc byte, version int) error { - ephemeralPublicX448, ciphertext, err := x448.Encrypt(rand, pub, keyBlock) - if err != nil { - return errors.InvalidArgumentError("x448 encryption failed: " + err.Error()) - } - - packetLen := len(header) /* header length */ - packetLen += x448.EncodedFieldsLength(ciphertext, version == 6) - - err = serializeHeader(w, packetTypeEncryptedKey, packetLen) - if err != nil { - return err - } - - _, err = w.Write(header[:]) - if err != nil { - return err - } - return x448.EncodeFields(w, ephemeralPublicX448, ciphertext, cipherFunc, version == 6) -} - -func checksumKeyMaterial(key []byte) uint16 { - var checksum uint16 - for _, v := range key { - checksum += uint16(v) - } - return checksum -} - -func decodeChecksumKey(msg []byte) (key []byte, err error) { - key = msg[:len(msg)-2] - expectedChecksum := uint16(msg[len(msg)-2])<<8 | uint16(msg[len(msg)-1]) - checksum := checksumKeyMaterial(key) - if checksum != expectedChecksum { - err = errors.StructuralError("session key checksum is incorrect") - } - return -} - -func encodeChecksumKey(buffer []byte, key []byte) { - copy(buffer, key) - checksum := checksumKeyMaterial(key) - buffer[len(key)] = byte(checksum >> 8) - buffer[len(key)+1] = byte(checksum) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/literal.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/literal.go deleted file mode 100644 index 8a028c8a171..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/literal.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "encoding/binary" - "io" -) - -// LiteralData represents an encrypted file. See RFC 4880, section 5.9. -type LiteralData struct { - Format uint8 - IsBinary bool - FileName string - Time uint32 // Unix epoch time. Either creation time or modification time. 0 means undefined. - Body io.Reader -} - -// ForEyesOnly returns whether the contents of the LiteralData have been marked -// as especially sensitive. -func (l *LiteralData) ForEyesOnly() bool { - return l.FileName == "_CONSOLE" -} - -func (l *LiteralData) parse(r io.Reader) (err error) { - var buf [256]byte - - _, err = readFull(r, buf[:2]) - if err != nil { - return - } - - l.Format = buf[0] - l.IsBinary = l.Format == 'b' - fileNameLen := int(buf[1]) - - _, err = readFull(r, buf[:fileNameLen]) - if err != nil { - return - } - - l.FileName = string(buf[:fileNameLen]) - - _, err = readFull(r, buf[:4]) - if err != nil { - return - } - - l.Time = binary.BigEndian.Uint32(buf[:4]) - l.Body = r - return -} - -// SerializeLiteral serializes a literal data packet to w and returns a -// WriteCloser to which the data itself can be written and which MUST be closed -// on completion. The fileName is truncated to 255 bytes. -func SerializeLiteral(w io.WriteCloser, isBinary bool, fileName string, time uint32) (plaintext io.WriteCloser, err error) { - var buf [4]byte - buf[0] = 'b' - if !isBinary { - buf[0] = 'u' - } - if len(fileName) > 255 { - fileName = fileName[:255] - } - buf[1] = byte(len(fileName)) - - inner, err := serializeStreamHeader(w, packetTypeLiteralData) - if err != nil { - return - } - - _, err = inner.Write(buf[:2]) - if err != nil { - return - } - _, err = inner.Write([]byte(fileName)) - if err != nil { - return - } - binary.BigEndian.PutUint32(buf[:], time) - _, err = inner.Write(buf[:]) - if err != nil { - return - } - - plaintext = inner - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/marker.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/marker.go deleted file mode 100644 index 1ee378ba3c1..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/marker.go +++ /dev/null @@ -1,33 +0,0 @@ -package packet - -import ( - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" -) - -type Marker struct{} - -const markerString = "PGP" - -// parse just checks if the packet contains "PGP". -func (m *Marker) parse(reader io.Reader) error { - var buffer [3]byte - if _, err := io.ReadFull(reader, buffer[:]); err != nil { - return err - } - if string(buffer[:]) != markerString { - return errors.StructuralError("invalid marker packet") - } - return nil -} - -// SerializeMarker writes a marker packet to writer. -func SerializeMarker(writer io.Writer) error { - err := serializeHeader(writer, packetTypeMarker, len(markerString)) - if err != nil { - return err - } - _, err = writer.Write([]byte(markerString)) - return err -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/notation.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/notation.go deleted file mode 100644 index 2c3e3f50b25..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/notation.go +++ /dev/null @@ -1,29 +0,0 @@ -package packet - -// Notation type represents a Notation Data subpacket -// see https://tools.ietf.org/html/rfc4880#section-5.2.3.16 -type Notation struct { - Name string - Value []byte - IsCritical bool - IsHumanReadable bool -} - -func (notation *Notation) getData() []byte { - nameData := []byte(notation.Name) - nameLen := len(nameData) - valueLen := len(notation.Value) - - data := make([]byte, 8+nameLen+valueLen) - if notation.IsHumanReadable { - data[0] = 0x80 - } - - data[4] = byte(nameLen >> 8) - data[5] = byte(nameLen) - data[6] = byte(valueLen >> 8) - data[7] = byte(valueLen) - copy(data[8:8+nameLen], nameData) - copy(data[8+nameLen:], notation.Value) - return data -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/ocfb.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/ocfb.go deleted file mode 100644 index 4f26d0a00b7..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/ocfb.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// OpenPGP CFB Mode. http://tools.ietf.org/html/rfc4880#section-13.9 - -package packet - -import ( - "crypto/cipher" -) - -type ocfbEncrypter struct { - b cipher.Block - fre []byte - outUsed int -} - -// An OCFBResyncOption determines if the "resynchronization step" of OCFB is -// performed. -type OCFBResyncOption bool - -const ( - OCFBResync OCFBResyncOption = true - OCFBNoResync OCFBResyncOption = false -) - -// NewOCFBEncrypter returns a cipher.Stream which encrypts data with OpenPGP's -// cipher feedback mode using the given cipher.Block, and an initial amount of -// ciphertext. randData must be random bytes and be the same length as the -// cipher.Block's block size. Resync determines if the "resynchronization step" -// from RFC 4880, 13.9 step 7 is performed. Different parts of OpenPGP vary on -// this point. -func NewOCFBEncrypter(block cipher.Block, randData []byte, resync OCFBResyncOption) (cipher.Stream, []byte) { - blockSize := block.BlockSize() - if len(randData) != blockSize { - return nil, nil - } - - x := &ocfbEncrypter{ - b: block, - fre: make([]byte, blockSize), - outUsed: 0, - } - prefix := make([]byte, blockSize+2) - - block.Encrypt(x.fre, x.fre) - for i := 0; i < blockSize; i++ { - prefix[i] = randData[i] ^ x.fre[i] - } - - block.Encrypt(x.fre, prefix[:blockSize]) - prefix[blockSize] = x.fre[0] ^ randData[blockSize-2] - prefix[blockSize+1] = x.fre[1] ^ randData[blockSize-1] - - if resync { - block.Encrypt(x.fre, prefix[2:]) - } else { - x.fre[0] = prefix[blockSize] - x.fre[1] = prefix[blockSize+1] - x.outUsed = 2 - } - return x, prefix -} - -func (x *ocfbEncrypter) XORKeyStream(dst, src []byte) { - for i := 0; i < len(src); i++ { - if x.outUsed == len(x.fre) { - x.b.Encrypt(x.fre, x.fre) - x.outUsed = 0 - } - - x.fre[x.outUsed] ^= src[i] - dst[i] = x.fre[x.outUsed] - x.outUsed++ - } -} - -type ocfbDecrypter struct { - b cipher.Block - fre []byte - outUsed int -} - -// NewOCFBDecrypter returns a cipher.Stream which decrypts data with OpenPGP's -// cipher feedback mode using the given cipher.Block. Prefix must be the first -// blockSize + 2 bytes of the ciphertext, where blockSize is the cipher.Block's -// block size. On successful exit, blockSize+2 bytes of decrypted data are written into -// prefix. Resync determines if the "resynchronization step" from RFC 4880, -// 13.9 step 7 is performed. Different parts of OpenPGP vary on this point. -func NewOCFBDecrypter(block cipher.Block, prefix []byte, resync OCFBResyncOption) cipher.Stream { - blockSize := block.BlockSize() - if len(prefix) != blockSize+2 { - return nil - } - - x := &ocfbDecrypter{ - b: block, - fre: make([]byte, blockSize), - outUsed: 0, - } - prefixCopy := make([]byte, len(prefix)) - copy(prefixCopy, prefix) - - block.Encrypt(x.fre, x.fre) - for i := 0; i < blockSize; i++ { - prefixCopy[i] ^= x.fre[i] - } - - block.Encrypt(x.fre, prefix[:blockSize]) - prefixCopy[blockSize] ^= x.fre[0] - prefixCopy[blockSize+1] ^= x.fre[1] - - if resync { - block.Encrypt(x.fre, prefix[2:]) - } else { - x.fre[0] = prefix[blockSize] - x.fre[1] = prefix[blockSize+1] - x.outUsed = 2 - } - copy(prefix, prefixCopy) - return x -} - -func (x *ocfbDecrypter) XORKeyStream(dst, src []byte) { - for i := 0; i < len(src); i++ { - if x.outUsed == len(x.fre) { - x.b.Encrypt(x.fre, x.fre) - x.outUsed = 0 - } - - c := src[i] - dst[i] = x.fre[x.outUsed] ^ src[i] - x.fre[x.outUsed] = c - x.outUsed++ - } -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/one_pass_signature.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/one_pass_signature.go deleted file mode 100644 index f393c4063b8..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/one_pass_signature.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "crypto" - "encoding/binary" - "io" - "strconv" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" -) - -// OnePassSignature represents a one-pass signature packet. See RFC 4880, -// section 5.4. -type OnePassSignature struct { - Version int - SigType SignatureType - Hash crypto.Hash - PubKeyAlgo PublicKeyAlgorithm - KeyId uint64 - IsLast bool - Salt []byte // v6 only - KeyFingerprint []byte // v6 only -} - -func (ops *OnePassSignature) parse(r io.Reader) (err error) { - var buf [8]byte - // Read: version | signature type | hash algorithm | public-key algorithm - _, err = readFull(r, buf[:4]) - if err != nil { - return - } - if buf[0] != 3 && buf[0] != 6 { - return errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0]))) - } - ops.Version = int(buf[0]) - - var ok bool - ops.Hash, ok = algorithm.HashIdToHashWithSha1(buf[2]) - if !ok { - return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2]))) - } - - ops.SigType = SignatureType(buf[1]) - ops.PubKeyAlgo = PublicKeyAlgorithm(buf[3]) - - if ops.Version == 6 { - // Only for v6, a variable-length field containing the salt - _, err = readFull(r, buf[:1]) - if err != nil { - return - } - saltLength := int(buf[0]) - var expectedSaltLength int - expectedSaltLength, err = SaltLengthForHash(ops.Hash) - if err != nil { - return - } - if saltLength != expectedSaltLength { - err = errors.StructuralError("unexpected salt size for the given hash algorithm") - return - } - salt := make([]byte, expectedSaltLength) - _, err = readFull(r, salt) - if err != nil { - return - } - ops.Salt = salt - - // Only for v6 packets, 32 octets of the fingerprint of the signing key. - fingerprint := make([]byte, 32) - _, err = readFull(r, fingerprint) - if err != nil { - return - } - ops.KeyFingerprint = fingerprint - ops.KeyId = binary.BigEndian.Uint64(ops.KeyFingerprint[:8]) - } else { - _, err = readFull(r, buf[:8]) - if err != nil { - return - } - ops.KeyId = binary.BigEndian.Uint64(buf[:8]) - } - - _, err = readFull(r, buf[:1]) - if err != nil { - return - } - ops.IsLast = buf[0] != 0 - return -} - -// Serialize marshals the given OnePassSignature to w. -func (ops *OnePassSignature) Serialize(w io.Writer) error { - //v3 length 1+1+1+1+8+1 = - packetLength := 13 - if ops.Version == 6 { - // v6 length 1+1+1+1+1+len(salt)+32+1 = - packetLength = 38 + len(ops.Salt) - } - - if err := serializeHeader(w, packetTypeOnePassSignature, packetLength); err != nil { - return err - } - - var buf [8]byte - buf[0] = byte(ops.Version) - buf[1] = uint8(ops.SigType) - var ok bool - buf[2], ok = algorithm.HashToHashIdWithSha1(ops.Hash) - if !ok { - return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash))) - } - buf[3] = uint8(ops.PubKeyAlgo) - - _, err := w.Write(buf[:4]) - if err != nil { - return err - } - - if ops.Version == 6 { - // write salt for v6 signatures - _, err := w.Write([]byte{uint8(len(ops.Salt))}) - if err != nil { - return err - } - _, err = w.Write(ops.Salt) - if err != nil { - return err - } - - // write fingerprint v6 signatures - _, err = w.Write(ops.KeyFingerprint) - if err != nil { - return err - } - } else { - binary.BigEndian.PutUint64(buf[:8], ops.KeyId) - _, err := w.Write(buf[:8]) - if err != nil { - return err - } - } - - isLast := []byte{byte(0)} - if ops.IsLast { - isLast[0] = 1 - } - - _, err = w.Write(isLast) - return err -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/opaque.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/opaque.go deleted file mode 100644 index cef7c661d3f..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/opaque.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "bytes" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" -) - -// OpaquePacket represents an OpenPGP packet as raw, unparsed data. This is -// useful for splitting and storing the original packet contents separately, -// handling unsupported packet types or accessing parts of the packet not yet -// implemented by this package. -type OpaquePacket struct { - // Packet type - Tag uint8 - // Reason why the packet was parsed opaquely - Reason error - // Binary contents of the packet data - Contents []byte -} - -func (op *OpaquePacket) parse(r io.Reader) (err error) { - op.Contents, err = io.ReadAll(r) - return -} - -// Serialize marshals the packet to a writer in its original form, including -// the packet header. -func (op *OpaquePacket) Serialize(w io.Writer) (err error) { - err = serializeHeader(w, packetType(op.Tag), len(op.Contents)) - if err == nil { - _, err = w.Write(op.Contents) - } - return -} - -// Parse attempts to parse the opaque contents into a structure supported by -// this package. If the packet is not known then the result will be another -// OpaquePacket. -func (op *OpaquePacket) Parse() (p Packet, err error) { - hdr := bytes.NewBuffer(nil) - err = serializeHeader(hdr, packetType(op.Tag), len(op.Contents)) - if err != nil { - op.Reason = err - return op, err - } - p, err = Read(io.MultiReader(hdr, bytes.NewBuffer(op.Contents))) - if err != nil { - op.Reason = err - p = op - } - return -} - -// OpaqueReader reads OpaquePackets from an io.Reader. -type OpaqueReader struct { - r io.Reader -} - -func NewOpaqueReader(r io.Reader) *OpaqueReader { - return &OpaqueReader{r: r} -} - -// Read the next OpaquePacket. -func (or *OpaqueReader) Next() (op *OpaquePacket, err error) { - tag, _, contents, err := readHeader(or.r) - if err != nil { - return - } - op = &OpaquePacket{Tag: uint8(tag), Reason: err} - err = op.parse(contents) - if err != nil { - consumeAll(contents) - } - return -} - -// OpaqueSubpacket represents an unparsed OpenPGP subpacket, -// as found in signature and user attribute packets. -type OpaqueSubpacket struct { - SubType uint8 - EncodedLength []byte // Store the original encoded length for signature verifications. - Contents []byte -} - -// OpaqueSubpackets extracts opaque, unparsed OpenPGP subpackets from -// their byte representation. -func OpaqueSubpackets(contents []byte) (result []*OpaqueSubpacket, err error) { - var ( - subHeaderLen int - subPacket *OpaqueSubpacket - ) - for len(contents) > 0 { - subHeaderLen, subPacket, err = nextSubpacket(contents) - if err != nil { - break - } - result = append(result, subPacket) - contents = contents[subHeaderLen+len(subPacket.Contents):] - } - return -} - -func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacket, err error) { - // RFC 4880, section 5.2.3.1 - var subLen uint32 - var encodedLength []byte - if len(contents) < 1 { - goto Truncated - } - subPacket = &OpaqueSubpacket{} - switch { - case contents[0] < 192: - subHeaderLen = 2 // 1 length byte, 1 subtype byte - if len(contents) < subHeaderLen { - goto Truncated - } - encodedLength = contents[0:1] - subLen = uint32(contents[0]) - contents = contents[1:] - case contents[0] < 255: - subHeaderLen = 3 // 2 length bytes, 1 subtype - if len(contents) < subHeaderLen { - goto Truncated - } - encodedLength = contents[0:2] - subLen = uint32(contents[0]-192)<<8 + uint32(contents[1]) + 192 - contents = contents[2:] - default: - subHeaderLen = 6 // 5 length bytes, 1 subtype - if len(contents) < subHeaderLen { - goto Truncated - } - encodedLength = contents[0:5] - subLen = uint32(contents[1])<<24 | - uint32(contents[2])<<16 | - uint32(contents[3])<<8 | - uint32(contents[4]) - contents = contents[5:] - - } - if subLen > uint32(len(contents)) || subLen == 0 { - goto Truncated - } - subPacket.SubType = contents[0] - subPacket.EncodedLength = encodedLength - subPacket.Contents = contents[1:subLen] - return -Truncated: - err = errors.StructuralError("subpacket truncated") - return -} - -func (osp *OpaqueSubpacket) Serialize(w io.Writer) (err error) { - buf := make([]byte, 6) - copy(buf, osp.EncodedLength) - n := len(osp.EncodedLength) - - buf[n] = osp.SubType - if _, err = w.Write(buf[:n+1]); err != nil { - return - } - _, err = w.Write(osp.Contents) - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/packet.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/packet.go deleted file mode 100644 index 1e92e22c976..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/packet.go +++ /dev/null @@ -1,675 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package packet implements parsing and serialization of OpenPGP packets, as -// specified in RFC 4880. -package packet // import "github.com/ProtonMail/go-crypto/openpgp/packet" - -import ( - "bytes" - "crypto/cipher" - "crypto/rsa" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" -) - -// readFull is the same as io.ReadFull except that reading zero bytes returns -// ErrUnexpectedEOF rather than EOF. -func readFull(r io.Reader, buf []byte) (n int, err error) { - n, err = io.ReadFull(r, buf) - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return -} - -// readLength reads an OpenPGP length from r. See RFC 4880, section 4.2.2. -func readLength(r io.Reader) (length int64, isPartial bool, err error) { - var buf [4]byte - _, err = readFull(r, buf[:1]) - if err != nil { - return - } - switch { - case buf[0] < 192: - length = int64(buf[0]) - case buf[0] < 224: - length = int64(buf[0]-192) << 8 - _, err = readFull(r, buf[0:1]) - if err != nil { - return - } - length += int64(buf[0]) + 192 - case buf[0] < 255: - length = int64(1) << (buf[0] & 0x1f) - isPartial = true - default: - _, err = readFull(r, buf[0:4]) - if err != nil { - return - } - length = int64(buf[0])<<24 | - int64(buf[1])<<16 | - int64(buf[2])<<8 | - int64(buf[3]) - } - return -} - -// partialLengthReader wraps an io.Reader and handles OpenPGP partial lengths. -// The continuation lengths are parsed and removed from the stream and EOF is -// returned at the end of the packet. See RFC 4880, section 4.2.2.4. -type partialLengthReader struct { - r io.Reader - remaining int64 - isPartial bool -} - -func (r *partialLengthReader) Read(p []byte) (n int, err error) { - for r.remaining == 0 { - if !r.isPartial { - return 0, io.EOF - } - r.remaining, r.isPartial, err = readLength(r.r) - if err != nil { - return 0, err - } - } - - toRead := int64(len(p)) - if toRead > r.remaining { - toRead = r.remaining - } - - n, err = r.r.Read(p[:int(toRead)]) - r.remaining -= int64(n) - if n < int(toRead) && err == io.EOF { - err = io.ErrUnexpectedEOF - } - return -} - -// partialLengthWriter writes a stream of data using OpenPGP partial lengths. -// See RFC 4880, section 4.2.2.4. -type partialLengthWriter struct { - w io.WriteCloser - buf bytes.Buffer - lengthByte [1]byte -} - -func (w *partialLengthWriter) Write(p []byte) (n int, err error) { - bufLen := w.buf.Len() - if bufLen > 512 { - for power := uint(30); ; power-- { - l := 1 << power - if bufLen >= l { - w.lengthByte[0] = 224 + uint8(power) - _, err = w.w.Write(w.lengthByte[:]) - if err != nil { - return - } - var m int - m, err = w.w.Write(w.buf.Next(l)) - if err != nil { - return - } - if m != l { - return 0, io.ErrShortWrite - } - break - } - } - } - return w.buf.Write(p) -} - -func (w *partialLengthWriter) Close() (err error) { - len := w.buf.Len() - err = serializeLength(w.w, len) - if err != nil { - return err - } - _, err = w.buf.WriteTo(w.w) - if err != nil { - return err - } - return w.w.Close() -} - -// A spanReader is an io.LimitReader, but it returns ErrUnexpectedEOF if the -// underlying Reader returns EOF before the limit has been reached. -type spanReader struct { - r io.Reader - n int64 -} - -func (l *spanReader) Read(p []byte) (n int, err error) { - if l.n <= 0 { - return 0, io.EOF - } - if int64(len(p)) > l.n { - p = p[0:l.n] - } - n, err = l.r.Read(p) - l.n -= int64(n) - if l.n > 0 && err == io.EOF { - err = io.ErrUnexpectedEOF - } - return -} - -// readHeader parses a packet header and returns an io.Reader which will return -// the contents of the packet. See RFC 4880, section 4.2. -func readHeader(r io.Reader) (tag packetType, length int64, contents io.Reader, err error) { - var buf [4]byte - _, err = io.ReadFull(r, buf[:1]) - if err != nil { - return - } - if buf[0]&0x80 == 0 { - err = errors.StructuralError("tag byte does not have MSB set") - return - } - if buf[0]&0x40 == 0 { - // Old format packet - tag = packetType((buf[0] & 0x3f) >> 2) - lengthType := buf[0] & 3 - if lengthType == 3 { - length = -1 - contents = r - return - } - lengthBytes := 1 << lengthType - _, err = readFull(r, buf[0:lengthBytes]) - if err != nil { - return - } - for i := 0; i < lengthBytes; i++ { - length <<= 8 - length |= int64(buf[i]) - } - contents = &spanReader{r, length} - return - } - - // New format packet - tag = packetType(buf[0] & 0x3f) - length, isPartial, err := readLength(r) - if err != nil { - return - } - if isPartial { - contents = &partialLengthReader{ - remaining: length, - isPartial: true, - r: r, - } - length = -1 - } else { - contents = &spanReader{r, length} - } - return -} - -// serializeHeader writes an OpenPGP packet header to w. See RFC 4880, section -// 4.2. -func serializeHeader(w io.Writer, ptype packetType, length int) (err error) { - err = serializeType(w, ptype) - if err != nil { - return - } - return serializeLength(w, length) -} - -// serializeType writes an OpenPGP packet type to w. See RFC 4880, section -// 4.2. -func serializeType(w io.Writer, ptype packetType) (err error) { - var buf [1]byte - buf[0] = 0x80 | 0x40 | byte(ptype) - _, err = w.Write(buf[:]) - return -} - -// serializeLength writes an OpenPGP packet length to w. See RFC 4880, section -// 4.2.2. -func serializeLength(w io.Writer, length int) (err error) { - var buf [5]byte - var n int - - if length < 192 { - buf[0] = byte(length) - n = 1 - } else if length < 8384 { - length -= 192 - buf[0] = 192 + byte(length>>8) - buf[1] = byte(length) - n = 2 - } else { - buf[0] = 255 - buf[1] = byte(length >> 24) - buf[2] = byte(length >> 16) - buf[3] = byte(length >> 8) - buf[4] = byte(length) - n = 5 - } - - _, err = w.Write(buf[:n]) - return -} - -// serializeStreamHeader writes an OpenPGP packet header to w where the -// length of the packet is unknown. It returns a io.WriteCloser which can be -// used to write the contents of the packet. See RFC 4880, section 4.2. -func serializeStreamHeader(w io.WriteCloser, ptype packetType) (out io.WriteCloser, err error) { - err = serializeType(w, ptype) - if err != nil { - return - } - out = &partialLengthWriter{w: w} - return -} - -// Packet represents an OpenPGP packet. Users are expected to try casting -// instances of this interface to specific packet types. -type Packet interface { - parse(io.Reader) error -} - -// consumeAll reads from the given Reader until error, returning the number of -// bytes read. -func consumeAll(r io.Reader) (n int64, err error) { - var m int - var buf [1024]byte - - for { - m, err = r.Read(buf[:]) - n += int64(m) - if err == io.EOF { - err = nil - return - } - if err != nil { - return - } - } -} - -// packetType represents the numeric ids of the different OpenPGP packet types. See -// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-2 -type packetType uint8 - -const ( - packetTypeEncryptedKey packetType = 1 - packetTypeSignature packetType = 2 - packetTypeSymmetricKeyEncrypted packetType = 3 - packetTypeOnePassSignature packetType = 4 - packetTypePrivateKey packetType = 5 - packetTypePublicKey packetType = 6 - packetTypePrivateSubkey packetType = 7 - packetTypeCompressed packetType = 8 - packetTypeSymmetricallyEncrypted packetType = 9 - packetTypeMarker packetType = 10 - packetTypeLiteralData packetType = 11 - packetTypeTrust packetType = 12 - packetTypeUserId packetType = 13 - packetTypePublicSubkey packetType = 14 - packetTypeUserAttribute packetType = 17 - packetTypeSymmetricallyEncryptedIntegrityProtected packetType = 18 - packetTypeAEADEncrypted packetType = 20 - packetPadding packetType = 21 -) - -// EncryptedDataPacket holds encrypted data. It is currently implemented by -// SymmetricallyEncrypted and AEADEncrypted. -type EncryptedDataPacket interface { - Decrypt(CipherFunction, []byte) (io.ReadCloser, error) -} - -// Read reads a single OpenPGP packet from the given io.Reader. If there is an -// error parsing a packet, the whole packet is consumed from the input. -func Read(r io.Reader) (p Packet, err error) { - tag, len, contents, err := readHeader(r) - if err != nil { - return - } - - switch tag { - case packetTypeEncryptedKey: - p = new(EncryptedKey) - case packetTypeSignature: - p = new(Signature) - case packetTypeSymmetricKeyEncrypted: - p = new(SymmetricKeyEncrypted) - case packetTypeOnePassSignature: - p = new(OnePassSignature) - case packetTypePrivateKey, packetTypePrivateSubkey: - pk := new(PrivateKey) - if tag == packetTypePrivateSubkey { - pk.IsSubkey = true - } - p = pk - case packetTypePublicKey, packetTypePublicSubkey: - isSubkey := tag == packetTypePublicSubkey - p = &PublicKey{IsSubkey: isSubkey} - case packetTypeCompressed: - p = new(Compressed) - case packetTypeSymmetricallyEncrypted: - p = new(SymmetricallyEncrypted) - case packetTypeLiteralData: - p = new(LiteralData) - case packetTypeUserId: - p = new(UserId) - case packetTypeUserAttribute: - p = new(UserAttribute) - case packetTypeSymmetricallyEncryptedIntegrityProtected: - se := new(SymmetricallyEncrypted) - se.IntegrityProtected = true - p = se - case packetTypeAEADEncrypted: - p = new(AEADEncrypted) - case packetPadding: - p = Padding(len) - case packetTypeMarker: - p = new(Marker) - case packetTypeTrust: - // Not implemented, just consume - err = errors.UnknownPacketTypeError(tag) - default: - // Packet Tags from 0 to 39 are critical. - // Packet Tags from 40 to 63 are non-critical. - if tag < 40 { - err = errors.CriticalUnknownPacketTypeError(tag) - } else { - err = errors.UnknownPacketTypeError(tag) - } - } - if p != nil { - err = p.parse(contents) - } - if err != nil { - consumeAll(contents) - } - return -} - -// ReadWithCheck reads a single OpenPGP message packet from the given io.Reader. If there is an -// error parsing a packet, the whole packet is consumed from the input. -// ReadWithCheck additionally checks if the OpenPGP message packet sequence adheres -// to the packet composition rules in rfc4880, if not throws an error. -func ReadWithCheck(r io.Reader, sequence *SequenceVerifier) (p Packet, msgErr error, err error) { - tag, len, contents, err := readHeader(r) - if err != nil { - return - } - switch tag { - case packetTypeEncryptedKey: - msgErr = sequence.Next(ESKSymbol) - p = new(EncryptedKey) - case packetTypeSignature: - msgErr = sequence.Next(SigSymbol) - p = new(Signature) - case packetTypeSymmetricKeyEncrypted: - msgErr = sequence.Next(ESKSymbol) - p = new(SymmetricKeyEncrypted) - case packetTypeOnePassSignature: - msgErr = sequence.Next(OPSSymbol) - p = new(OnePassSignature) - case packetTypeCompressed: - msgErr = sequence.Next(CompSymbol) - p = new(Compressed) - case packetTypeSymmetricallyEncrypted: - msgErr = sequence.Next(EncSymbol) - p = new(SymmetricallyEncrypted) - case packetTypeLiteralData: - msgErr = sequence.Next(LDSymbol) - p = new(LiteralData) - case packetTypeSymmetricallyEncryptedIntegrityProtected: - msgErr = sequence.Next(EncSymbol) - se := new(SymmetricallyEncrypted) - se.IntegrityProtected = true - p = se - case packetTypeAEADEncrypted: - msgErr = sequence.Next(EncSymbol) - p = new(AEADEncrypted) - case packetPadding: - p = Padding(len) - case packetTypeMarker: - p = new(Marker) - case packetTypeTrust: - // Not implemented, just consume - err = errors.UnknownPacketTypeError(tag) - case packetTypePrivateKey, - packetTypePrivateSubkey, - packetTypePublicKey, - packetTypePublicSubkey, - packetTypeUserId, - packetTypeUserAttribute: - msgErr = sequence.Next(UnknownSymbol) - consumeAll(contents) - default: - // Packet Tags from 0 to 39 are critical. - // Packet Tags from 40 to 63 are non-critical. - if tag < 40 { - err = errors.CriticalUnknownPacketTypeError(tag) - } else { - err = errors.UnknownPacketTypeError(tag) - } - } - if p != nil { - err = p.parse(contents) - } - if err != nil { - consumeAll(contents) - } - return -} - -// SignatureType represents the different semantic meanings of an OpenPGP -// signature. See RFC 4880, section 5.2.1. -type SignatureType uint8 - -const ( - SigTypeBinary SignatureType = 0x00 - SigTypeText SignatureType = 0x01 - SigTypeGenericCert SignatureType = 0x10 - SigTypePersonaCert SignatureType = 0x11 - SigTypeCasualCert SignatureType = 0x12 - SigTypePositiveCert SignatureType = 0x13 - SigTypeSubkeyBinding SignatureType = 0x18 - SigTypePrimaryKeyBinding SignatureType = 0x19 - SigTypeDirectSignature SignatureType = 0x1F - SigTypeKeyRevocation SignatureType = 0x20 - SigTypeSubkeyRevocation SignatureType = 0x28 - SigTypeCertificationRevocation SignatureType = 0x30 -) - -// PublicKeyAlgorithm represents the different public key system specified for -// OpenPGP. See -// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-12 -type PublicKeyAlgorithm uint8 - -const ( - PubKeyAlgoRSA PublicKeyAlgorithm = 1 - PubKeyAlgoElGamal PublicKeyAlgorithm = 16 - PubKeyAlgoDSA PublicKeyAlgorithm = 17 - // RFC 6637, Section 5. - PubKeyAlgoECDH PublicKeyAlgorithm = 18 - PubKeyAlgoECDSA PublicKeyAlgorithm = 19 - // https://www.ietf.org/archive/id/draft-koch-eddsa-for-openpgp-04.txt - PubKeyAlgoEdDSA PublicKeyAlgorithm = 22 - // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh - PubKeyAlgoX25519 PublicKeyAlgorithm = 25 - PubKeyAlgoX448 PublicKeyAlgorithm = 26 - PubKeyAlgoEd25519 PublicKeyAlgorithm = 27 - PubKeyAlgoEd448 PublicKeyAlgorithm = 28 - - // Deprecated in RFC 4880, Section 13.5. Use key flags instead. - PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2 - PubKeyAlgoRSASignOnly PublicKeyAlgorithm = 3 -) - -// CanEncrypt returns true if it's possible to encrypt a message to a public -// key of the given type. -func (pka PublicKeyAlgorithm) CanEncrypt() bool { - switch pka { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH, PubKeyAlgoX25519, PubKeyAlgoX448: - return true - } - return false -} - -// CanSign returns true if it's possible for a public key of the given type to -// sign a message. -func (pka PublicKeyAlgorithm) CanSign() bool { - switch pka { - case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA, PubKeyAlgoEdDSA, PubKeyAlgoEd25519, PubKeyAlgoEd448: - return true - } - return false -} - -// CipherFunction represents the different block ciphers specified for OpenPGP. See -// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-13 -type CipherFunction algorithm.CipherFunction - -const ( - Cipher3DES CipherFunction = 2 - CipherCAST5 CipherFunction = 3 - CipherAES128 CipherFunction = 7 - CipherAES192 CipherFunction = 8 - CipherAES256 CipherFunction = 9 -) - -// KeySize returns the key size, in bytes, of cipher. -func (cipher CipherFunction) KeySize() int { - return algorithm.CipherFunction(cipher).KeySize() -} - -// IsSupported returns true if the cipher is supported from the library -func (cipher CipherFunction) IsSupported() bool { - return algorithm.CipherFunction(cipher).KeySize() > 0 -} - -// blockSize returns the block size, in bytes, of cipher. -func (cipher CipherFunction) blockSize() int { - return algorithm.CipherFunction(cipher).BlockSize() -} - -// new returns a fresh instance of the given cipher. -func (cipher CipherFunction) new(key []byte) (block cipher.Block) { - return algorithm.CipherFunction(cipher).New(key) -} - -// padToKeySize left-pads a MPI with zeroes to match the length of the -// specified RSA public. -func padToKeySize(pub *rsa.PublicKey, b []byte) []byte { - k := (pub.N.BitLen() + 7) / 8 - if len(b) >= k { - return b - } - bb := make([]byte, k) - copy(bb[len(bb)-len(b):], b) - return bb -} - -// CompressionAlgo Represents the different compression algorithms -// supported by OpenPGP (except for BZIP2, which is not currently -// supported). See Section 9.3 of RFC 4880. -type CompressionAlgo uint8 - -const ( - CompressionNone CompressionAlgo = 0 - CompressionZIP CompressionAlgo = 1 - CompressionZLIB CompressionAlgo = 2 -) - -// AEADMode represents the different Authenticated Encryption with Associated -// Data specified for OpenPGP. -// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.6 -type AEADMode algorithm.AEADMode - -const ( - AEADModeEAX AEADMode = 1 - AEADModeOCB AEADMode = 2 - AEADModeGCM AEADMode = 3 -) - -func (mode AEADMode) IvLength() int { - return algorithm.AEADMode(mode).NonceLength() -} - -func (mode AEADMode) TagLength() int { - return algorithm.AEADMode(mode).TagLength() -} - -// IsSupported returns true if the aead mode is supported from the library -func (mode AEADMode) IsSupported() bool { - return algorithm.AEADMode(mode).TagLength() > 0 -} - -// new returns a fresh instance of the given mode. -func (mode AEADMode) new(block cipher.Block) cipher.AEAD { - return algorithm.AEADMode(mode).New(block) -} - -// ReasonForRevocation represents a revocation reason code as per RFC4880 -// section 5.2.3.23. -type ReasonForRevocation uint8 - -const ( - NoReason ReasonForRevocation = 0 - KeySuperseded ReasonForRevocation = 1 - KeyCompromised ReasonForRevocation = 2 - KeyRetired ReasonForRevocation = 3 - UserIDNotValid ReasonForRevocation = 32 - Unknown ReasonForRevocation = 200 -) - -func NewReasonForRevocation(value byte) ReasonForRevocation { - if value < 4 || value == 32 { - return ReasonForRevocation(value) - } - return Unknown -} - -// Curve is a mapping to supported ECC curves for key generation. -// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#name-curve-specific-wire-formats -type Curve string - -const ( - Curve25519 Curve = "Curve25519" - Curve448 Curve = "Curve448" - CurveNistP256 Curve = "P256" - CurveNistP384 Curve = "P384" - CurveNistP521 Curve = "P521" - CurveSecP256k1 Curve = "SecP256k1" - CurveBrainpoolP256 Curve = "BrainpoolP256" - CurveBrainpoolP384 Curve = "BrainpoolP384" - CurveBrainpoolP512 Curve = "BrainpoolP512" -) - -// TrustLevel represents a trust level per RFC4880 5.2.3.13 -type TrustLevel uint8 - -// TrustAmount represents a trust amount per RFC4880 5.2.3.13 -type TrustAmount uint8 - -const ( - // versionSize is the length in bytes of the version value. - versionSize = 1 - // algorithmSize is the length in bytes of the key algorithm value. - algorithmSize = 1 - // keyVersionSize is the length in bytes of the key version value - keyVersionSize = 1 - // keyIdSize is the length in bytes of the key identifier value. - keyIdSize = 8 - // timestampSize is the length in bytes of encoded timestamps. - timestampSize = 4 - // fingerprintSizeV6 is the length in bytes of the key fingerprint in v6. - fingerprintSizeV6 = 32 - // fingerprintSize is the length in bytes of the key fingerprint. - fingerprintSize = 20 -) diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/packet_sequence.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/packet_sequence.go deleted file mode 100644 index 55a8a56c2d1..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/packet_sequence.go +++ /dev/null @@ -1,222 +0,0 @@ -package packet - -// This file implements the pushdown automata (PDA) from PGPainless (Paul Schaub) -// to verify pgp packet sequences. See Paul's blogpost for more details: -// https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/ -import ( - "fmt" - - "github.com/ProtonMail/go-crypto/openpgp/errors" -) - -func NewErrMalformedMessage(from State, input InputSymbol, stackSymbol StackSymbol) errors.ErrMalformedMessage { - return errors.ErrMalformedMessage(fmt.Sprintf("state %d, input symbol %d, stack symbol %d ", from, input, stackSymbol)) -} - -// InputSymbol defines the input alphabet of the PDA -type InputSymbol uint8 - -const ( - LDSymbol InputSymbol = iota - SigSymbol - OPSSymbol - CompSymbol - ESKSymbol - EncSymbol - EOSSymbol - UnknownSymbol -) - -// StackSymbol defines the stack alphabet of the PDA -type StackSymbol int8 - -const ( - MsgStackSymbol StackSymbol = iota - OpsStackSymbol - KeyStackSymbol - EndStackSymbol - EmptyStackSymbol -) - -// State defines the states of the PDA -type State int8 - -const ( - OpenPGPMessage State = iota - ESKMessage - LiteralMessage - CompressedMessage - EncryptedMessage - ValidMessage -) - -// transition represents a state transition in the PDA -type transition func(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) - -// SequenceVerifier is a pushdown automata to verify -// PGP messages packet sequences according to rfc4880. -type SequenceVerifier struct { - stack []StackSymbol - state State -} - -// Next performs a state transition with the given input symbol. -// If the transition fails a ErrMalformedMessage is returned. -func (sv *SequenceVerifier) Next(input InputSymbol) error { - for { - stackSymbol := sv.popStack() - transitionFunc := getTransition(sv.state) - nextState, newStackSymbols, redo, err := transitionFunc(input, stackSymbol) - if err != nil { - return err - } - if redo { - sv.pushStack(stackSymbol) - } - for _, newStackSymbol := range newStackSymbols { - sv.pushStack(newStackSymbol) - } - sv.state = nextState - if !redo { - break - } - } - return nil -} - -// Valid returns true if RDA is in a valid state. -func (sv *SequenceVerifier) Valid() bool { - return sv.state == ValidMessage && len(sv.stack) == 0 -} - -func (sv *SequenceVerifier) AssertValid() error { - if !sv.Valid() { - return errors.ErrMalformedMessage("invalid message") - } - return nil -} - -func NewSequenceVerifier() *SequenceVerifier { - return &SequenceVerifier{ - stack: []StackSymbol{EndStackSymbol, MsgStackSymbol}, - state: OpenPGPMessage, - } -} - -func (sv *SequenceVerifier) popStack() StackSymbol { - if len(sv.stack) == 0 { - return EmptyStackSymbol - } - elemIndex := len(sv.stack) - 1 - stackSymbol := sv.stack[elemIndex] - sv.stack = sv.stack[:elemIndex] - return stackSymbol -} - -func (sv *SequenceVerifier) pushStack(stackSymbol StackSymbol) { - sv.stack = append(sv.stack, stackSymbol) -} - -func getTransition(from State) transition { - switch from { - case OpenPGPMessage: - return fromOpenPGPMessage - case LiteralMessage: - return fromLiteralMessage - case CompressedMessage: - return fromCompressedMessage - case EncryptedMessage: - return fromEncryptedMessage - case ESKMessage: - return fromESKMessage - case ValidMessage: - return fromValidMessage - } - return nil -} - -// fromOpenPGPMessage is the transition for the state OpenPGPMessage. -func fromOpenPGPMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { - if stackSymbol != MsgStackSymbol { - return 0, nil, false, NewErrMalformedMessage(OpenPGPMessage, input, stackSymbol) - } - switch input { - case LDSymbol: - return LiteralMessage, nil, false, nil - case SigSymbol: - return OpenPGPMessage, []StackSymbol{MsgStackSymbol}, false, nil - case OPSSymbol: - return OpenPGPMessage, []StackSymbol{OpsStackSymbol, MsgStackSymbol}, false, nil - case CompSymbol: - return CompressedMessage, nil, false, nil - case ESKSymbol: - return ESKMessage, []StackSymbol{KeyStackSymbol}, false, nil - case EncSymbol: - return EncryptedMessage, nil, false, nil - } - return 0, nil, false, NewErrMalformedMessage(OpenPGPMessage, input, stackSymbol) -} - -// fromESKMessage is the transition for the state ESKMessage. -func fromESKMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { - if stackSymbol != KeyStackSymbol { - return 0, nil, false, NewErrMalformedMessage(ESKMessage, input, stackSymbol) - } - switch input { - case ESKSymbol: - return ESKMessage, []StackSymbol{KeyStackSymbol}, false, nil - case EncSymbol: - return EncryptedMessage, nil, false, nil - } - return 0, nil, false, NewErrMalformedMessage(ESKMessage, input, stackSymbol) -} - -// fromLiteralMessage is the transition for the state LiteralMessage. -func fromLiteralMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { - switch input { - case SigSymbol: - if stackSymbol == OpsStackSymbol { - return LiteralMessage, nil, false, nil - } - case EOSSymbol: - if stackSymbol == EndStackSymbol { - return ValidMessage, nil, false, nil - } - } - return 0, nil, false, NewErrMalformedMessage(LiteralMessage, input, stackSymbol) -} - -// fromLiteralMessage is the transition for the state CompressedMessage. -func fromCompressedMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { - switch input { - case SigSymbol: - if stackSymbol == OpsStackSymbol { - return CompressedMessage, nil, false, nil - } - case EOSSymbol: - if stackSymbol == EndStackSymbol { - return ValidMessage, nil, false, nil - } - } - return OpenPGPMessage, []StackSymbol{MsgStackSymbol}, true, nil -} - -// fromEncryptedMessage is the transition for the state EncryptedMessage. -func fromEncryptedMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { - switch input { - case SigSymbol: - if stackSymbol == OpsStackSymbol { - return EncryptedMessage, nil, false, nil - } - case EOSSymbol: - if stackSymbol == EndStackSymbol { - return ValidMessage, nil, false, nil - } - } - return OpenPGPMessage, []StackSymbol{MsgStackSymbol}, true, nil -} - -// fromValidMessage is the transition for the state ValidMessage. -func fromValidMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { - return 0, nil, false, NewErrMalformedMessage(ValidMessage, input, stackSymbol) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/packet_unsupported.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/packet_unsupported.go deleted file mode 100644 index 2d714723cf8..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/packet_unsupported.go +++ /dev/null @@ -1,24 +0,0 @@ -package packet - -import ( - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" -) - -// UnsupportedPackage represents a OpenPGP packet with a known packet type -// but with unsupported content. -type UnsupportedPacket struct { - IncompletePacket Packet - Error errors.UnsupportedError -} - -// Implements the Packet interface -func (up *UnsupportedPacket) parse(read io.Reader) error { - err := up.IncompletePacket.parse(read) - if castedErr, ok := err.(errors.UnsupportedError); ok { - up.Error = castedErr - return nil - } - return err -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/padding.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/padding.go deleted file mode 100644 index 3b6a7045d14..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/padding.go +++ /dev/null @@ -1,26 +0,0 @@ -package packet - -import ( - "io" -) - -// Padding type represents a Padding Packet (Tag 21). -// The padding type is represented by the length of its padding. -// see https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#name-padding-packet-tag-21 -type Padding int - -// parse just ignores the padding content. -func (pad Padding) parse(reader io.Reader) error { - _, err := io.CopyN(io.Discard, reader, int64(pad)) - return err -} - -// SerializePadding writes the padding to writer. -func (pad Padding) SerializePadding(writer io.Writer, rand io.Reader) error { - err := serializeHeader(writer, packetPadding, int(pad)) - if err != nil { - return err - } - _, err = io.CopyN(writer, rand, int64(pad)) - return err -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/private_key.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/private_key.go deleted file mode 100644 index f04e6c6b871..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/private_key.go +++ /dev/null @@ -1,1191 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "bytes" - "crypto" - "crypto/cipher" - "crypto/dsa" - "crypto/rsa" - "crypto/sha1" - "crypto/sha256" - "crypto/subtle" - "fmt" - "io" - "math/big" - "strconv" - "time" - - "github.com/ProtonMail/go-crypto/openpgp/ecdh" - "github.com/ProtonMail/go-crypto/openpgp/ecdsa" - "github.com/ProtonMail/go-crypto/openpgp/ed25519" - "github.com/ProtonMail/go-crypto/openpgp/ed448" - "github.com/ProtonMail/go-crypto/openpgp/eddsa" - "github.com/ProtonMail/go-crypto/openpgp/elgamal" - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" - "github.com/ProtonMail/go-crypto/openpgp/s2k" - "github.com/ProtonMail/go-crypto/openpgp/x25519" - "github.com/ProtonMail/go-crypto/openpgp/x448" - "golang.org/x/crypto/hkdf" -) - -// PrivateKey represents a possibly encrypted private key. See RFC 4880, -// section 5.5.3. -type PrivateKey struct { - PublicKey - Encrypted bool // if true then the private key is unavailable until Decrypt has been called. - encryptedData []byte - cipher CipherFunction - s2k func(out, in []byte) - aead AEADMode // only relevant if S2KAEAD is enabled - // An *{rsa|dsa|elgamal|ecdh|ecdsa|ed25519|ed448}.PrivateKey or - // crypto.Signer/crypto.Decrypter (Decryptor RSA only). - PrivateKey interface{} - iv []byte - - // Type of encryption of the S2K packet - // Allowed values are 0 (Not encrypted), 253 (AEAD), 254 (SHA1), or - // 255 (2-byte checksum) - s2kType S2KType - // Full parameters of the S2K packet - s2kParams *s2k.Params -} - -// S2KType s2k packet type -type S2KType uint8 - -const ( - // S2KNON unencrypt - S2KNON S2KType = 0 - // S2KAEAD use authenticated encryption - S2KAEAD S2KType = 253 - // S2KSHA1 sha1 sum check - S2KSHA1 S2KType = 254 - // S2KCHECKSUM sum check - S2KCHECKSUM S2KType = 255 -) - -func NewRSAPrivateKey(creationTime time.Time, priv *rsa.PrivateKey) *PrivateKey { - pk := new(PrivateKey) - pk.PublicKey = *NewRSAPublicKey(creationTime, &priv.PublicKey) - pk.PrivateKey = priv - return pk -} - -func NewDSAPrivateKey(creationTime time.Time, priv *dsa.PrivateKey) *PrivateKey { - pk := new(PrivateKey) - pk.PublicKey = *NewDSAPublicKey(creationTime, &priv.PublicKey) - pk.PrivateKey = priv - return pk -} - -func NewElGamalPrivateKey(creationTime time.Time, priv *elgamal.PrivateKey) *PrivateKey { - pk := new(PrivateKey) - pk.PublicKey = *NewElGamalPublicKey(creationTime, &priv.PublicKey) - pk.PrivateKey = priv - return pk -} - -func NewECDSAPrivateKey(creationTime time.Time, priv *ecdsa.PrivateKey) *PrivateKey { - pk := new(PrivateKey) - pk.PublicKey = *NewECDSAPublicKey(creationTime, &priv.PublicKey) - pk.PrivateKey = priv - return pk -} - -func NewEdDSAPrivateKey(creationTime time.Time, priv *eddsa.PrivateKey) *PrivateKey { - pk := new(PrivateKey) - pk.PublicKey = *NewEdDSAPublicKey(creationTime, &priv.PublicKey) - pk.PrivateKey = priv - return pk -} - -func NewECDHPrivateKey(creationTime time.Time, priv *ecdh.PrivateKey) *PrivateKey { - pk := new(PrivateKey) - pk.PublicKey = *NewECDHPublicKey(creationTime, &priv.PublicKey) - pk.PrivateKey = priv - return pk -} - -func NewX25519PrivateKey(creationTime time.Time, priv *x25519.PrivateKey) *PrivateKey { - pk := new(PrivateKey) - pk.PublicKey = *NewX25519PublicKey(creationTime, &priv.PublicKey) - pk.PrivateKey = priv - return pk -} - -func NewX448PrivateKey(creationTime time.Time, priv *x448.PrivateKey) *PrivateKey { - pk := new(PrivateKey) - pk.PublicKey = *NewX448PublicKey(creationTime, &priv.PublicKey) - pk.PrivateKey = priv - return pk -} - -func NewEd25519PrivateKey(creationTime time.Time, priv *ed25519.PrivateKey) *PrivateKey { - pk := new(PrivateKey) - pk.PublicKey = *NewEd25519PublicKey(creationTime, &priv.PublicKey) - pk.PrivateKey = priv - return pk -} - -func NewEd448PrivateKey(creationTime time.Time, priv *ed448.PrivateKey) *PrivateKey { - pk := new(PrivateKey) - pk.PublicKey = *NewEd448PublicKey(creationTime, &priv.PublicKey) - pk.PrivateKey = priv - return pk -} - -// NewSignerPrivateKey creates a PrivateKey from a crypto.Signer that -// implements RSA, ECDSA or EdDSA. -func NewSignerPrivateKey(creationTime time.Time, signer interface{}) *PrivateKey { - pk := new(PrivateKey) - // In general, the public Keys should be used as pointers. We still - // type-switch on the values, for backwards-compatibility. - switch pubkey := signer.(type) { - case *rsa.PrivateKey: - pk.PublicKey = *NewRSAPublicKey(creationTime, &pubkey.PublicKey) - case rsa.PrivateKey: - pk.PublicKey = *NewRSAPublicKey(creationTime, &pubkey.PublicKey) - case *ecdsa.PrivateKey: - pk.PublicKey = *NewECDSAPublicKey(creationTime, &pubkey.PublicKey) - case ecdsa.PrivateKey: - pk.PublicKey = *NewECDSAPublicKey(creationTime, &pubkey.PublicKey) - case *eddsa.PrivateKey: - pk.PublicKey = *NewEdDSAPublicKey(creationTime, &pubkey.PublicKey) - case eddsa.PrivateKey: - pk.PublicKey = *NewEdDSAPublicKey(creationTime, &pubkey.PublicKey) - case *ed25519.PrivateKey: - pk.PublicKey = *NewEd25519PublicKey(creationTime, &pubkey.PublicKey) - case ed25519.PrivateKey: - pk.PublicKey = *NewEd25519PublicKey(creationTime, &pubkey.PublicKey) - case *ed448.PrivateKey: - pk.PublicKey = *NewEd448PublicKey(creationTime, &pubkey.PublicKey) - case ed448.PrivateKey: - pk.PublicKey = *NewEd448PublicKey(creationTime, &pubkey.PublicKey) - default: - panic("openpgp: unknown signer type in NewSignerPrivateKey") - } - pk.PrivateKey = signer - return pk -} - -// NewDecrypterPrivateKey creates a PrivateKey from a *{rsa|elgamal|ecdh|x25519|x448}.PrivateKey. -func NewDecrypterPrivateKey(creationTime time.Time, decrypter interface{}) *PrivateKey { - pk := new(PrivateKey) - switch priv := decrypter.(type) { - case *rsa.PrivateKey: - pk.PublicKey = *NewRSAPublicKey(creationTime, &priv.PublicKey) - case *elgamal.PrivateKey: - pk.PublicKey = *NewElGamalPublicKey(creationTime, &priv.PublicKey) - case *ecdh.PrivateKey: - pk.PublicKey = *NewECDHPublicKey(creationTime, &priv.PublicKey) - case *x25519.PrivateKey: - pk.PublicKey = *NewX25519PublicKey(creationTime, &priv.PublicKey) - case *x448.PrivateKey: - pk.PublicKey = *NewX448PublicKey(creationTime, &priv.PublicKey) - default: - panic("openpgp: unknown decrypter type in NewDecrypterPrivateKey") - } - pk.PrivateKey = decrypter - return pk -} - -func (pk *PrivateKey) parse(r io.Reader) (err error) { - err = (&pk.PublicKey).parse(r) - if err != nil { - return - } - v5 := pk.PublicKey.Version == 5 - v6 := pk.PublicKey.Version == 6 - - if V5Disabled && v5 { - return errors.UnsupportedError("support for parsing v5 entities is disabled; build with `-tags v5` if needed") - } - - var buf [1]byte - _, err = readFull(r, buf[:]) - if err != nil { - return - } - pk.s2kType = S2KType(buf[0]) - var optCount [1]byte - if v5 || (v6 && pk.s2kType != S2KNON) { - if _, err = readFull(r, optCount[:]); err != nil { - return - } - } - - switch pk.s2kType { - case S2KNON: - pk.s2k = nil - pk.Encrypted = false - case S2KSHA1, S2KCHECKSUM, S2KAEAD: - if (v5 || v6) && pk.s2kType == S2KCHECKSUM { - return errors.StructuralError(fmt.Sprintf("wrong s2k identifier for version %d", pk.Version)) - } - _, err = readFull(r, buf[:]) - if err != nil { - return - } - pk.cipher = CipherFunction(buf[0]) - if pk.cipher != 0 && !pk.cipher.IsSupported() { - return errors.UnsupportedError("unsupported cipher function in private key") - } - // [Optional] If string-to-key usage octet was 253, - // a one-octet AEAD algorithm. - if pk.s2kType == S2KAEAD { - _, err = readFull(r, buf[:]) - if err != nil { - return - } - pk.aead = AEADMode(buf[0]) - if !pk.aead.IsSupported() { - return errors.UnsupportedError("unsupported aead mode in private key") - } - } - - // [Optional] Only for a version 6 packet, - // and if string-to-key usage octet was 255, 254, or 253, - // an one-octet count of the following field. - if v6 { - _, err = readFull(r, buf[:]) - if err != nil { - return - } - } - - pk.s2kParams, err = s2k.ParseIntoParams(r) - if err != nil { - return - } - if pk.s2kParams.Dummy() { - return - } - if pk.s2kParams.Mode() == s2k.Argon2S2K && pk.s2kType != S2KAEAD { - return errors.StructuralError("using Argon2 S2K without AEAD is not allowed") - } - if pk.s2kParams.Mode() == s2k.SimpleS2K && pk.Version == 6 { - return errors.StructuralError("using Simple S2K with version 6 keys is not allowed") - } - pk.s2k, err = pk.s2kParams.Function() - if err != nil { - return - } - pk.Encrypted = true - default: - return errors.UnsupportedError("deprecated s2k function in private key") - } - - if pk.Encrypted { - var ivSize int - // If the S2K usage octet was 253, the IV is of the size expected by the AEAD mode, - // unless it's a version 5 key, in which case it's the size of the symmetric cipher's block size. - // For all other S2K modes, it's always the block size. - if !v5 && pk.s2kType == S2KAEAD { - ivSize = pk.aead.IvLength() - } else { - ivSize = pk.cipher.blockSize() - } - - if ivSize == 0 { - return errors.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher))) - } - pk.iv = make([]byte, ivSize) - _, err = readFull(r, pk.iv) - if err != nil { - return - } - if v5 && pk.s2kType == S2KAEAD { - pk.iv = pk.iv[:pk.aead.IvLength()] - } - } - - var privateKeyData []byte - if v5 { - var n [4]byte /* secret material four octet count */ - _, err = readFull(r, n[:]) - if err != nil { - return - } - count := uint32(uint32(n[0])<<24 | uint32(n[1])<<16 | uint32(n[2])<<8 | uint32(n[3])) - if !pk.Encrypted { - count = count + 2 /* two octet checksum */ - } - privateKeyData = make([]byte, count) - _, err = readFull(r, privateKeyData) - if err != nil { - return - } - } else { - privateKeyData, err = io.ReadAll(r) - if err != nil { - return - } - } - if !pk.Encrypted { - if len(privateKeyData) < 2 { - return errors.StructuralError("truncated private key data") - } - if pk.Version != 6 { - // checksum - var sum uint16 - for i := 0; i < len(privateKeyData)-2; i++ { - sum += uint16(privateKeyData[i]) - } - if privateKeyData[len(privateKeyData)-2] != uint8(sum>>8) || - privateKeyData[len(privateKeyData)-1] != uint8(sum) { - return errors.StructuralError("private key checksum failure") - } - privateKeyData = privateKeyData[:len(privateKeyData)-2] - return pk.parsePrivateKey(privateKeyData) - } else { - // No checksum - return pk.parsePrivateKey(privateKeyData) - } - } - - pk.encryptedData = privateKeyData - return -} - -// Dummy returns true if the private key is a dummy key. This is a GNU extension. -func (pk *PrivateKey) Dummy() bool { - return pk.s2kParams.Dummy() -} - -func mod64kHash(d []byte) uint16 { - var h uint16 - for _, b := range d { - h += uint16(b) - } - return h -} - -func (pk *PrivateKey) Serialize(w io.Writer) (err error) { - contents := bytes.NewBuffer(nil) - err = pk.PublicKey.serializeWithoutHeaders(contents) - if err != nil { - return - } - if _, err = contents.Write([]byte{uint8(pk.s2kType)}); err != nil { - return - } - - optional := bytes.NewBuffer(nil) - if pk.Encrypted || pk.Dummy() { - // [Optional] If string-to-key usage octet was 255, 254, or 253, - // a one-octet symmetric encryption algorithm. - if _, err = optional.Write([]byte{uint8(pk.cipher)}); err != nil { - return - } - // [Optional] If string-to-key usage octet was 253, - // a one-octet AEAD algorithm. - if pk.s2kType == S2KAEAD { - if _, err = optional.Write([]byte{uint8(pk.aead)}); err != nil { - return - } - } - - s2kBuffer := bytes.NewBuffer(nil) - if err := pk.s2kParams.Serialize(s2kBuffer); err != nil { - return err - } - // [Optional] Only for a version 6 packet, and if string-to-key - // usage octet was 255, 254, or 253, an one-octet - // count of the following field. - if pk.Version == 6 { - if _, err = optional.Write([]byte{uint8(s2kBuffer.Len())}); err != nil { - return - } - } - // [Optional] If string-to-key usage octet was 255, 254, or 253, - // a string-to-key (S2K) specifier. The length of the string-to-key specifier - // depends on its type - if _, err = io.Copy(optional, s2kBuffer); err != nil { - return - } - - // IV - if pk.Encrypted { - if _, err = optional.Write(pk.iv); err != nil { - return - } - if pk.Version == 5 && pk.s2kType == S2KAEAD { - // Add padding for version 5 - padding := make([]byte, pk.cipher.blockSize()-len(pk.iv)) - if _, err = optional.Write(padding); err != nil { - return - } - } - } - } - if pk.Version == 5 || (pk.Version == 6 && pk.s2kType != S2KNON) { - contents.Write([]byte{uint8(optional.Len())}) - } - - if _, err := io.Copy(contents, optional); err != nil { - return err - } - - if !pk.Dummy() { - l := 0 - var priv []byte - if !pk.Encrypted { - buf := bytes.NewBuffer(nil) - err = pk.serializePrivateKey(buf) - if err != nil { - return err - } - l = buf.Len() - if pk.Version != 6 { - checksum := mod64kHash(buf.Bytes()) - buf.Write([]byte{byte(checksum >> 8), byte(checksum)}) - } - priv = buf.Bytes() - } else { - priv, l = pk.encryptedData, len(pk.encryptedData) - } - - if pk.Version == 5 { - contents.Write([]byte{byte(l >> 24), byte(l >> 16), byte(l >> 8), byte(l)}) - } - contents.Write(priv) - } - - ptype := packetTypePrivateKey - if pk.IsSubkey { - ptype = packetTypePrivateSubkey - } - err = serializeHeader(w, ptype, contents.Len()) - if err != nil { - return - } - _, err = io.Copy(w, contents) - if err != nil { - return - } - return -} - -func serializeRSAPrivateKey(w io.Writer, priv *rsa.PrivateKey) error { - if _, err := w.Write(new(encoding.MPI).SetBig(priv.D).EncodedBytes()); err != nil { - return err - } - if _, err := w.Write(new(encoding.MPI).SetBig(priv.Primes[1]).EncodedBytes()); err != nil { - return err - } - if _, err := w.Write(new(encoding.MPI).SetBig(priv.Primes[0]).EncodedBytes()); err != nil { - return err - } - _, err := w.Write(new(encoding.MPI).SetBig(priv.Precomputed.Qinv).EncodedBytes()) - return err -} - -func serializeDSAPrivateKey(w io.Writer, priv *dsa.PrivateKey) error { - _, err := w.Write(new(encoding.MPI).SetBig(priv.X).EncodedBytes()) - return err -} - -func serializeElGamalPrivateKey(w io.Writer, priv *elgamal.PrivateKey) error { - _, err := w.Write(new(encoding.MPI).SetBig(priv.X).EncodedBytes()) - return err -} - -func serializeECDSAPrivateKey(w io.Writer, priv *ecdsa.PrivateKey) error { - _, err := w.Write(encoding.NewMPI(priv.MarshalIntegerSecret()).EncodedBytes()) - return err -} - -func serializeEdDSAPrivateKey(w io.Writer, priv *eddsa.PrivateKey) error { - _, err := w.Write(encoding.NewMPI(priv.MarshalByteSecret()).EncodedBytes()) - return err -} - -func serializeECDHPrivateKey(w io.Writer, priv *ecdh.PrivateKey) error { - _, err := w.Write(encoding.NewMPI(priv.MarshalByteSecret()).EncodedBytes()) - return err -} - -func serializeX25519PrivateKey(w io.Writer, priv *x25519.PrivateKey) error { - _, err := w.Write(priv.Secret) - return err -} - -func serializeX448PrivateKey(w io.Writer, priv *x448.PrivateKey) error { - _, err := w.Write(priv.Secret) - return err -} - -func serializeEd25519PrivateKey(w io.Writer, priv *ed25519.PrivateKey) error { - _, err := w.Write(priv.MarshalByteSecret()) - return err -} - -func serializeEd448PrivateKey(w io.Writer, priv *ed448.PrivateKey) error { - _, err := w.Write(priv.MarshalByteSecret()) - return err -} - -// decrypt decrypts an encrypted private key using a decryption key. -func (pk *PrivateKey) decrypt(decryptionKey []byte) error { - if pk.Dummy() { - return errors.ErrDummyPrivateKey("dummy key found") - } - if !pk.Encrypted { - return nil - } - block := pk.cipher.new(decryptionKey) - var data []byte - switch pk.s2kType { - case S2KAEAD: - aead := pk.aead.new(block) - additionalData, err := pk.additionalData() - if err != nil { - return err - } - // Decrypt the encrypted key material with aead - data, err = aead.Open(nil, pk.iv, pk.encryptedData, additionalData) - if err != nil { - return err - } - case S2KSHA1, S2KCHECKSUM: - cfb := cipher.NewCFBDecrypter(block, pk.iv) - data = make([]byte, len(pk.encryptedData)) - cfb.XORKeyStream(data, pk.encryptedData) - if pk.s2kType == S2KSHA1 { - if len(data) < sha1.Size { - return errors.StructuralError("truncated private key data") - } - h := sha1.New() - h.Write(data[:len(data)-sha1.Size]) - sum := h.Sum(nil) - if !bytes.Equal(sum, data[len(data)-sha1.Size:]) { - return errors.StructuralError("private key checksum failure") - } - data = data[:len(data)-sha1.Size] - } else { - if len(data) < 2 { - return errors.StructuralError("truncated private key data") - } - var sum uint16 - for i := 0; i < len(data)-2; i++ { - sum += uint16(data[i]) - } - if data[len(data)-2] != uint8(sum>>8) || - data[len(data)-1] != uint8(sum) { - return errors.StructuralError("private key checksum failure") - } - data = data[:len(data)-2] - } - default: - return errors.InvalidArgumentError("invalid s2k type") - } - - err := pk.parsePrivateKey(data) - if _, ok := err.(errors.KeyInvalidError); ok { - return errors.KeyInvalidError("invalid key parameters") - } - if err != nil { - return err - } - - // Mark key as unencrypted - pk.s2kType = S2KNON - pk.s2k = nil - pk.Encrypted = false - pk.encryptedData = nil - return nil -} - -func (pk *PrivateKey) decryptWithCache(passphrase []byte, keyCache *s2k.Cache) error { - if pk.Dummy() { - return errors.ErrDummyPrivateKey("dummy key found") - } - if !pk.Encrypted { - return nil - } - - key, err := keyCache.GetOrComputeDerivedKey(passphrase, pk.s2kParams, pk.cipher.KeySize()) - if err != nil { - return err - } - if pk.s2kType == S2KAEAD { - key = pk.applyHKDF(key) - } - return pk.decrypt(key) -} - -// Decrypt decrypts an encrypted private key using a passphrase. -func (pk *PrivateKey) Decrypt(passphrase []byte) error { - if pk.Dummy() { - return errors.ErrDummyPrivateKey("dummy key found") - } - if !pk.Encrypted { - return nil - } - - key := make([]byte, pk.cipher.KeySize()) - pk.s2k(key, passphrase) - if pk.s2kType == S2KAEAD { - key = pk.applyHKDF(key) - } - return pk.decrypt(key) -} - -// DecryptPrivateKeys decrypts all encrypted keys with the given config and passphrase. -// Avoids recomputation of similar s2k key derivations. -func DecryptPrivateKeys(keys []*PrivateKey, passphrase []byte) error { - // Create a cache to avoid recomputation of key derviations for the same passphrase. - s2kCache := &s2k.Cache{} - for _, key := range keys { - if key != nil && !key.Dummy() && key.Encrypted { - err := key.decryptWithCache(passphrase, s2kCache) - if err != nil { - return err - } - } - } - return nil -} - -// encrypt encrypts an unencrypted private key. -func (pk *PrivateKey) encrypt(key []byte, params *s2k.Params, s2kType S2KType, cipherFunction CipherFunction, rand io.Reader) error { - if pk.Dummy() { - return errors.ErrDummyPrivateKey("dummy key found") - } - if pk.Encrypted { - return nil - } - // check if encryptionKey has the correct size - if len(key) != cipherFunction.KeySize() { - return errors.InvalidArgumentError("supplied encryption key has the wrong size") - } - - if params.Mode() == s2k.Argon2S2K && s2kType != S2KAEAD { - return errors.InvalidArgumentError("using Argon2 S2K without AEAD is not allowed") - } - if params.Mode() != s2k.Argon2S2K && params.Mode() != s2k.IteratedSaltedS2K && - params.Mode() != s2k.SaltedS2K { // only allowed for high-entropy passphrases - return errors.InvalidArgumentError("insecure S2K mode") - } - - priv := bytes.NewBuffer(nil) - err := pk.serializePrivateKey(priv) - if err != nil { - return err - } - - pk.cipher = cipherFunction - pk.s2kParams = params - pk.s2k, err = pk.s2kParams.Function() - if err != nil { - return err - } - - privateKeyBytes := priv.Bytes() - pk.s2kType = s2kType - block := pk.cipher.new(key) - switch s2kType { - case S2KAEAD: - if pk.aead == 0 { - return errors.StructuralError("aead mode is not set on key") - } - aead := pk.aead.new(block) - additionalData, err := pk.additionalData() - if err != nil { - return err - } - pk.iv = make([]byte, aead.NonceSize()) - _, err = io.ReadFull(rand, pk.iv) - if err != nil { - return err - } - // Decrypt the encrypted key material with aead - pk.encryptedData = aead.Seal(nil, pk.iv, privateKeyBytes, additionalData) - case S2KSHA1, S2KCHECKSUM: - pk.iv = make([]byte, pk.cipher.blockSize()) - _, err = io.ReadFull(rand, pk.iv) - if err != nil { - return err - } - cfb := cipher.NewCFBEncrypter(block, pk.iv) - if s2kType == S2KSHA1 { - h := sha1.New() - h.Write(privateKeyBytes) - sum := h.Sum(nil) - privateKeyBytes = append(privateKeyBytes, sum...) - } else { - var sum uint16 - for _, b := range privateKeyBytes { - sum += uint16(b) - } - privateKeyBytes = append(privateKeyBytes, []byte{uint8(sum >> 8), uint8(sum)}...) - } - pk.encryptedData = make([]byte, len(privateKeyBytes)) - cfb.XORKeyStream(pk.encryptedData, privateKeyBytes) - default: - return errors.InvalidArgumentError("invalid s2k type for encryption") - } - - pk.Encrypted = true - pk.PrivateKey = nil - return err -} - -// EncryptWithConfig encrypts an unencrypted private key using the passphrase and the config. -func (pk *PrivateKey) EncryptWithConfig(passphrase []byte, config *Config) error { - params, err := s2k.Generate(config.Random(), config.S2K()) - if err != nil { - return err - } - // Derive an encryption key with the configured s2k function. - key := make([]byte, config.Cipher().KeySize()) - s2k, err := params.Function() - if err != nil { - return err - } - s2k(key, passphrase) - s2kType := S2KSHA1 - if config.AEAD() != nil { - s2kType = S2KAEAD - pk.aead = config.AEAD().Mode() - pk.cipher = config.Cipher() - key = pk.applyHKDF(key) - } - // Encrypt the private key with the derived encryption key. - return pk.encrypt(key, params, s2kType, config.Cipher(), config.Random()) -} - -// EncryptPrivateKeys encrypts all unencrypted keys with the given config and passphrase. -// Only derives one key from the passphrase, which is then used to encrypt each key. -func EncryptPrivateKeys(keys []*PrivateKey, passphrase []byte, config *Config) error { - params, err := s2k.Generate(config.Random(), config.S2K()) - if err != nil { - return err - } - // Derive an encryption key with the configured s2k function. - encryptionKey := make([]byte, config.Cipher().KeySize()) - s2k, err := params.Function() - if err != nil { - return err - } - s2k(encryptionKey, passphrase) - for _, key := range keys { - if key != nil && !key.Dummy() && !key.Encrypted { - s2kType := S2KSHA1 - if config.AEAD() != nil { - s2kType = S2KAEAD - key.aead = config.AEAD().Mode() - key.cipher = config.Cipher() - derivedKey := key.applyHKDF(encryptionKey) - err = key.encrypt(derivedKey, params, s2kType, config.Cipher(), config.Random()) - } else { - err = key.encrypt(encryptionKey, params, s2kType, config.Cipher(), config.Random()) - } - if err != nil { - return err - } - } - } - return nil -} - -// Encrypt encrypts an unencrypted private key using a passphrase. -func (pk *PrivateKey) Encrypt(passphrase []byte) error { - // Default config of private key encryption - config := &Config{ - S2KConfig: &s2k.Config{ - S2KMode: s2k.IteratedSaltedS2K, - S2KCount: 65536, - Hash: crypto.SHA256, - }, - DefaultCipher: CipherAES256, - } - return pk.EncryptWithConfig(passphrase, config) -} - -func (pk *PrivateKey) serializePrivateKey(w io.Writer) (err error) { - switch priv := pk.PrivateKey.(type) { - case *rsa.PrivateKey: - err = serializeRSAPrivateKey(w, priv) - case *dsa.PrivateKey: - err = serializeDSAPrivateKey(w, priv) - case *elgamal.PrivateKey: - err = serializeElGamalPrivateKey(w, priv) - case *ecdsa.PrivateKey: - err = serializeECDSAPrivateKey(w, priv) - case *eddsa.PrivateKey: - err = serializeEdDSAPrivateKey(w, priv) - case *ecdh.PrivateKey: - err = serializeECDHPrivateKey(w, priv) - case *x25519.PrivateKey: - err = serializeX25519PrivateKey(w, priv) - case *x448.PrivateKey: - err = serializeX448PrivateKey(w, priv) - case *ed25519.PrivateKey: - err = serializeEd25519PrivateKey(w, priv) - case *ed448.PrivateKey: - err = serializeEd448PrivateKey(w, priv) - default: - err = errors.InvalidArgumentError("unknown private key type") - } - return -} - -func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) { - switch pk.PublicKey.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoRSAEncryptOnly: - return pk.parseRSAPrivateKey(data) - case PubKeyAlgoDSA: - return pk.parseDSAPrivateKey(data) - case PubKeyAlgoElGamal: - return pk.parseElGamalPrivateKey(data) - case PubKeyAlgoECDSA: - return pk.parseECDSAPrivateKey(data) - case PubKeyAlgoECDH: - return pk.parseECDHPrivateKey(data) - case PubKeyAlgoEdDSA: - return pk.parseEdDSAPrivateKey(data) - case PubKeyAlgoX25519: - return pk.parseX25519PrivateKey(data) - case PubKeyAlgoX448: - return pk.parseX448PrivateKey(data) - case PubKeyAlgoEd25519: - return pk.parseEd25519PrivateKey(data) - case PubKeyAlgoEd448: - return pk.parseEd448PrivateKey(data) - default: - err = errors.StructuralError("unknown private key type") - return - } -} - -func (pk *PrivateKey) parseRSAPrivateKey(data []byte) (err error) { - rsaPub := pk.PublicKey.PublicKey.(*rsa.PublicKey) - rsaPriv := new(rsa.PrivateKey) - rsaPriv.PublicKey = *rsaPub - - buf := bytes.NewBuffer(data) - d := new(encoding.MPI) - if _, err := d.ReadFrom(buf); err != nil { - return err - } - - p := new(encoding.MPI) - if _, err := p.ReadFrom(buf); err != nil { - return err - } - - q := new(encoding.MPI) - if _, err := q.ReadFrom(buf); err != nil { - return err - } - - rsaPriv.D = new(big.Int).SetBytes(d.Bytes()) - rsaPriv.Primes = make([]*big.Int, 2) - rsaPriv.Primes[0] = new(big.Int).SetBytes(p.Bytes()) - rsaPriv.Primes[1] = new(big.Int).SetBytes(q.Bytes()) - if err := rsaPriv.Validate(); err != nil { - return errors.KeyInvalidError(err.Error()) - } - rsaPriv.Precompute() - pk.PrivateKey = rsaPriv - - return nil -} - -func (pk *PrivateKey) parseDSAPrivateKey(data []byte) (err error) { - dsaPub := pk.PublicKey.PublicKey.(*dsa.PublicKey) - dsaPriv := new(dsa.PrivateKey) - dsaPriv.PublicKey = *dsaPub - - buf := bytes.NewBuffer(data) - x := new(encoding.MPI) - if _, err := x.ReadFrom(buf); err != nil { - return err - } - - dsaPriv.X = new(big.Int).SetBytes(x.Bytes()) - if err := validateDSAParameters(dsaPriv); err != nil { - return err - } - pk.PrivateKey = dsaPriv - - return nil -} - -func (pk *PrivateKey) parseElGamalPrivateKey(data []byte) (err error) { - pub := pk.PublicKey.PublicKey.(*elgamal.PublicKey) - priv := new(elgamal.PrivateKey) - priv.PublicKey = *pub - - buf := bytes.NewBuffer(data) - x := new(encoding.MPI) - if _, err := x.ReadFrom(buf); err != nil { - return err - } - - priv.X = new(big.Int).SetBytes(x.Bytes()) - if err := validateElGamalParameters(priv); err != nil { - return err - } - pk.PrivateKey = priv - - return nil -} - -func (pk *PrivateKey) parseECDSAPrivateKey(data []byte) (err error) { - ecdsaPub := pk.PublicKey.PublicKey.(*ecdsa.PublicKey) - ecdsaPriv := ecdsa.NewPrivateKey(*ecdsaPub) - - buf := bytes.NewBuffer(data) - d := new(encoding.MPI) - if _, err := d.ReadFrom(buf); err != nil { - return err - } - - if err := ecdsaPriv.UnmarshalIntegerSecret(d.Bytes()); err != nil { - return err - } - if err := ecdsa.Validate(ecdsaPriv); err != nil { - return err - } - pk.PrivateKey = ecdsaPriv - - return nil -} - -func (pk *PrivateKey) parseECDHPrivateKey(data []byte) (err error) { - ecdhPub := pk.PublicKey.PublicKey.(*ecdh.PublicKey) - ecdhPriv := ecdh.NewPrivateKey(*ecdhPub) - - buf := bytes.NewBuffer(data) - d := new(encoding.MPI) - if _, err := d.ReadFrom(buf); err != nil { - return err - } - - if err := ecdhPriv.UnmarshalByteSecret(d.Bytes()); err != nil { - return err - } - - if err := ecdh.Validate(ecdhPriv); err != nil { - return err - } - - pk.PrivateKey = ecdhPriv - - return nil -} - -func (pk *PrivateKey) parseX25519PrivateKey(data []byte) (err error) { - publicKey := pk.PublicKey.PublicKey.(*x25519.PublicKey) - privateKey := x25519.NewPrivateKey(*publicKey) - privateKey.PublicKey = *publicKey - - privateKey.Secret = make([]byte, x25519.KeySize) - - if len(data) != x25519.KeySize { - err = errors.StructuralError("wrong x25519 key size") - return err - } - subtle.ConstantTimeCopy(1, privateKey.Secret, data) - if err = x25519.Validate(privateKey); err != nil { - return err - } - pk.PrivateKey = privateKey - return nil -} - -func (pk *PrivateKey) parseX448PrivateKey(data []byte) (err error) { - publicKey := pk.PublicKey.PublicKey.(*x448.PublicKey) - privateKey := x448.NewPrivateKey(*publicKey) - privateKey.PublicKey = *publicKey - - privateKey.Secret = make([]byte, x448.KeySize) - - if len(data) != x448.KeySize { - err = errors.StructuralError("wrong x448 key size") - return err - } - subtle.ConstantTimeCopy(1, privateKey.Secret, data) - if err = x448.Validate(privateKey); err != nil { - return err - } - pk.PrivateKey = privateKey - return nil -} - -func (pk *PrivateKey) parseEd25519PrivateKey(data []byte) (err error) { - publicKey := pk.PublicKey.PublicKey.(*ed25519.PublicKey) - privateKey := ed25519.NewPrivateKey(*publicKey) - privateKey.PublicKey = *publicKey - - if len(data) != ed25519.SeedSize { - err = errors.StructuralError("wrong ed25519 key size") - return err - } - err = privateKey.UnmarshalByteSecret(data) - if err != nil { - return err - } - err = ed25519.Validate(privateKey) - if err != nil { - return err - } - pk.PrivateKey = privateKey - return nil -} - -func (pk *PrivateKey) parseEd448PrivateKey(data []byte) (err error) { - publicKey := pk.PublicKey.PublicKey.(*ed448.PublicKey) - privateKey := ed448.NewPrivateKey(*publicKey) - privateKey.PublicKey = *publicKey - - if len(data) != ed448.SeedSize { - err = errors.StructuralError("wrong ed448 key size") - return err - } - err = privateKey.UnmarshalByteSecret(data) - if err != nil { - return err - } - err = ed448.Validate(privateKey) - if err != nil { - return err - } - pk.PrivateKey = privateKey - return nil -} - -func (pk *PrivateKey) parseEdDSAPrivateKey(data []byte) (err error) { - eddsaPub := pk.PublicKey.PublicKey.(*eddsa.PublicKey) - eddsaPriv := eddsa.NewPrivateKey(*eddsaPub) - eddsaPriv.PublicKey = *eddsaPub - - buf := bytes.NewBuffer(data) - d := new(encoding.MPI) - if _, err := d.ReadFrom(buf); err != nil { - return err - } - - if err = eddsaPriv.UnmarshalByteSecret(d.Bytes()); err != nil { - return err - } - - if err := eddsa.Validate(eddsaPriv); err != nil { - return err - } - - pk.PrivateKey = eddsaPriv - - return nil -} - -func (pk *PrivateKey) additionalData() ([]byte, error) { - additionalData := bytes.NewBuffer(nil) - // Write additional data prefix based on packet type - var packetByte byte - if pk.PublicKey.IsSubkey { - packetByte = 0xc7 - } else { - packetByte = 0xc5 - } - // Write public key to additional data - _, err := additionalData.Write([]byte{packetByte}) - if err != nil { - return nil, err - } - err = pk.PublicKey.serializeWithoutHeaders(additionalData) - if err != nil { - return nil, err - } - return additionalData.Bytes(), nil -} - -func (pk *PrivateKey) applyHKDF(inputKey []byte) []byte { - var packetByte byte - if pk.PublicKey.IsSubkey { - packetByte = 0xc7 - } else { - packetByte = 0xc5 - } - associatedData := []byte{packetByte, byte(pk.Version), byte(pk.cipher), byte(pk.aead)} - hkdfReader := hkdf.New(sha256.New, inputKey, []byte{}, associatedData) - encryptionKey := make([]byte, pk.cipher.KeySize()) - _, _ = readFull(hkdfReader, encryptionKey) - return encryptionKey -} - -func validateDSAParameters(priv *dsa.PrivateKey) error { - p := priv.P // group prime - q := priv.Q // subgroup order - g := priv.G // g has order q mod p - x := priv.X // secret - y := priv.Y // y == g**x mod p - one := big.NewInt(1) - // expect g, y >= 2 and g < p - if g.Cmp(one) <= 0 || y.Cmp(one) <= 0 || g.Cmp(p) > 0 { - return errors.KeyInvalidError("dsa: invalid group") - } - // expect p > q - if p.Cmp(q) <= 0 { - return errors.KeyInvalidError("dsa: invalid group prime") - } - // q should be large enough and divide p-1 - pSub1 := new(big.Int).Sub(p, one) - if q.BitLen() < 150 || new(big.Int).Mod(pSub1, q).Cmp(big.NewInt(0)) != 0 { - return errors.KeyInvalidError("dsa: invalid order") - } - // confirm that g has order q mod p - if !q.ProbablyPrime(32) || new(big.Int).Exp(g, q, p).Cmp(one) != 0 { - return errors.KeyInvalidError("dsa: invalid order") - } - // check y - if new(big.Int).Exp(g, x, p).Cmp(y) != 0 { - return errors.KeyInvalidError("dsa: mismatching values") - } - - return nil -} - -func validateElGamalParameters(priv *elgamal.PrivateKey) error { - p := priv.P // group prime - g := priv.G // g has order p-1 mod p - x := priv.X // secret - y := priv.Y // y == g**x mod p - one := big.NewInt(1) - // Expect g, y >= 2 and g < p - if g.Cmp(one) <= 0 || y.Cmp(one) <= 0 || g.Cmp(p) > 0 { - return errors.KeyInvalidError("elgamal: invalid group") - } - if p.BitLen() < 1024 { - return errors.KeyInvalidError("elgamal: group order too small") - } - pSub1 := new(big.Int).Sub(p, one) - if new(big.Int).Exp(g, pSub1, p).Cmp(one) != 0 { - return errors.KeyInvalidError("elgamal: invalid group") - } - // Since p-1 is not prime, g might have a smaller order that divides p-1. - // We cannot confirm the exact order of g, but we make sure it is not too small. - gExpI := new(big.Int).Set(g) - i := 1 - threshold := 2 << 17 // we want order > threshold - for i < threshold { - i++ // we check every order to make sure key validation is not easily bypassed by guessing y' - gExpI.Mod(new(big.Int).Mul(gExpI, g), p) - if gExpI.Cmp(one) == 0 { - return errors.KeyInvalidError("elgamal: order too small") - } - } - // Check y - if new(big.Int).Exp(g, x, p).Cmp(y) != 0 { - return errors.KeyInvalidError("elgamal: mismatching values") - } - - return nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/private_key_test_data.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/private_key_test_data.go deleted file mode 100644 index 029b8f1aab2..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/private_key_test_data.go +++ /dev/null @@ -1,12 +0,0 @@ -package packet - -// Generated with `gpg --export-secret-keys "Test Key 2"` -const privKeyRSAHex = "9501fe044cc349a8010400b70ca0010e98c090008d45d1ee8f9113bd5861fd57b88bacb7c68658747663f1e1a3b5a98f32fda6472373c024b97359cd2efc88ff60f77751adfbf6af5e615e6a1408cfad8bf0cea30b0d5f53aa27ad59089ba9b15b7ebc2777a25d7b436144027e3bcd203909f147d0e332b240cf63d3395f5dfe0df0a6c04e8655af7eacdf0011010001fe0303024a252e7d475fd445607de39a265472aa74a9320ba2dac395faa687e9e0336aeb7e9a7397e511b5afd9dc84557c80ac0f3d4d7bfec5ae16f20d41c8c84a04552a33870b930420e230e179564f6d19bb153145e76c33ae993886c388832b0fa042ddda7f133924f3854481533e0ede31d51278c0519b29abc3bf53da673e13e3e1214b52413d179d7f66deee35cac8eacb060f78379d70ef4af8607e68131ff529439668fc39c9ce6dfef8a5ac234d234802cbfb749a26107db26406213ae5c06d4673253a3cbee1fcbae58d6ab77e38d6e2c0e7c6317c48e054edadb5a40d0d48acb44643d998139a8a66bb820be1f3f80185bc777d14b5954b60effe2448a036d565c6bc0b915fcea518acdd20ab07bc1529f561c58cd044f723109b93f6fd99f876ff891d64306b5d08f48bab59f38695e9109c4dec34013ba3153488ce070268381ba923ee1eb77125b36afcb4347ec3478c8f2735b06ef17351d872e577fa95d0c397c88c71b59629a36aec" - -// Generated by `gpg --export-secret-keys` followed by a manual extraction of -// the ElGamal subkey from the packets. -const privKeyElGamalHex = "9d0157044df9ee1a100400eb8e136a58ec39b582629cdadf830bc64e0a94ed8103ca8bb247b27b11b46d1d25297ef4bcc3071785ba0c0bedfe89eabc5287fcc0edf81ab5896c1c8e4b20d27d79813c7aede75320b33eaeeaa586edc00fd1036c10133e6ba0ff277245d0d59d04b2b3421b7244aca5f4a8d870c6f1c1fbff9e1c26699a860b9504f35ca1d700030503fd1ededd3b840795be6d9ccbe3c51ee42e2f39233c432b831ddd9c4e72b7025a819317e47bf94f9ee316d7273b05d5fcf2999c3a681f519b1234bbfa6d359b4752bd9c3f77d6b6456cde152464763414ca130f4e91d91041432f90620fec0e6d6b5116076c2985d5aeaae13be492b9b329efcaf7ee25120159a0a30cd976b42d7afe030302dae7eb80db744d4960c4df930d57e87fe81412eaace9f900e6c839817a614ddb75ba6603b9417c33ea7b6c93967dfa2bcff3fa3c74a5ce2c962db65b03aece14c96cbd0038fc" - -// pkcs1PrivKeyHex is a PKCS#1, RSA private key. -// Generated by `openssl genrsa 1024 | openssl rsa -outform DER | xxd -p` -const pkcs1PrivKeyHex = "3082025d02010002818100e98edfa1c3b35884a54d0b36a6a603b0290fa85e49e30fa23fc94fef9c6790bc4849928607aa48d809da326fb42a969d06ad756b98b9c1a90f5d4a2b6d0ac05953c97f4da3120164a21a679793ce181c906dc01d235cc085ddcdf6ea06c389b6ab8885dfd685959e693138856a68a7e5db263337ff82a088d583a897cf2d59e9020301000102818100b6d5c9eb70b02d5369b3ee5b520a14490b5bde8a317d36f7e4c74b7460141311d1e5067735f8f01d6f5908b2b96fbd881f7a1ab9a84d82753e39e19e2d36856be960d05ac9ef8e8782ea1b6d65aee28fdfe1d61451e8cff0adfe84322f12cf455028b581cf60eb9e0e140ba5d21aeba6c2634d7c65318b9a665fc01c3191ca21024100fa5e818da3705b0fa33278bb28d4b6f6050388af2d4b75ec9375dd91ccf2e7d7068086a8b82a8f6282e4fbbdb8a7f2622eb97295249d87acea7f5f816f54d347024100eecf9406d7dc49cdfb95ab1eff4064de84c7a30f64b2798936a0d2018ba9eb52e4b636f82e96c49cc63b80b675e91e40d1b2e4017d4b9adaf33ab3d9cf1c214f024100c173704ace742c082323066226a4655226819a85304c542b9dacbeacbf5d1881ee863485fcf6f59f3a604f9b42289282067447f2b13dfeed3eab7851fc81e0550240741fc41f3fc002b382eed8730e33c5d8de40256e4accee846667f536832f711ab1d4590e7db91a8a116ac5bff3be13d3f9243ff2e976662aa9b395d907f8e9c9024046a5696c9ef882363e06c9fa4e2f5b580906452befba03f4a99d0f873697ef1f851d2226ca7934b30b7c3e80cb634a67172bbbf4781735fe3e09263e2dd723e7" diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/public_key.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/public_key.go deleted file mode 100644 index e2813396e38..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/public_key.go +++ /dev/null @@ -1,1125 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "crypto/dsa" - "crypto/rsa" - "crypto/sha1" - "crypto/sha256" - _ "crypto/sha512" - "encoding/binary" - "fmt" - "hash" - "io" - "math/big" - "strconv" - "time" - - "github.com/ProtonMail/go-crypto/openpgp/ecdh" - "github.com/ProtonMail/go-crypto/openpgp/ecdsa" - "github.com/ProtonMail/go-crypto/openpgp/ed25519" - "github.com/ProtonMail/go-crypto/openpgp/ed448" - "github.com/ProtonMail/go-crypto/openpgp/eddsa" - "github.com/ProtonMail/go-crypto/openpgp/elgamal" - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" - "github.com/ProtonMail/go-crypto/openpgp/internal/ecc" - "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" - "github.com/ProtonMail/go-crypto/openpgp/x25519" - "github.com/ProtonMail/go-crypto/openpgp/x448" -) - -// PublicKey represents an OpenPGP public key. See RFC 4880, section 5.5.2. -type PublicKey struct { - Version int - CreationTime time.Time - PubKeyAlgo PublicKeyAlgorithm - PublicKey interface{} // *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey or *eddsa.PublicKey, *x25519.PublicKey, *x448.PublicKey, *ed25519.PublicKey, *ed448.PublicKey - Fingerprint []byte - KeyId uint64 - IsSubkey bool - - // RFC 4880 fields - n, e, p, q, g, y encoding.Field - - // RFC 6637 fields - // oid contains the OID byte sequence identifying the elliptic curve used - oid encoding.Field - - // kdf stores key derivation function parameters - // used for ECDH encryption. See RFC 6637, Section 9. - kdf encoding.Field -} - -// UpgradeToV5 updates the version of the key to v5, and updates all necessary -// fields. -func (pk *PublicKey) UpgradeToV5() { - pk.Version = 5 - pk.setFingerprintAndKeyId() -} - -// UpgradeToV6 updates the version of the key to v6, and updates all necessary -// fields. -func (pk *PublicKey) UpgradeToV6() error { - pk.Version = 6 - pk.setFingerprintAndKeyId() - return pk.checkV6Compatibility() -} - -// signingKey provides a convenient abstraction over signature verification -// for v3 and v4 public keys. -type signingKey interface { - SerializeForHash(io.Writer) error - SerializeSignaturePrefix(io.Writer) error - serializeWithoutHeaders(io.Writer) error -} - -// NewRSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey. -func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey) *PublicKey { - pk := &PublicKey{ - Version: 4, - CreationTime: creationTime, - PubKeyAlgo: PubKeyAlgoRSA, - PublicKey: pub, - n: new(encoding.MPI).SetBig(pub.N), - e: new(encoding.MPI).SetBig(big.NewInt(int64(pub.E))), - } - - pk.setFingerprintAndKeyId() - return pk -} - -// NewDSAPublicKey returns a PublicKey that wraps the given dsa.PublicKey. -func NewDSAPublicKey(creationTime time.Time, pub *dsa.PublicKey) *PublicKey { - pk := &PublicKey{ - Version: 4, - CreationTime: creationTime, - PubKeyAlgo: PubKeyAlgoDSA, - PublicKey: pub, - p: new(encoding.MPI).SetBig(pub.P), - q: new(encoding.MPI).SetBig(pub.Q), - g: new(encoding.MPI).SetBig(pub.G), - y: new(encoding.MPI).SetBig(pub.Y), - } - - pk.setFingerprintAndKeyId() - return pk -} - -// NewElGamalPublicKey returns a PublicKey that wraps the given elgamal.PublicKey. -func NewElGamalPublicKey(creationTime time.Time, pub *elgamal.PublicKey) *PublicKey { - pk := &PublicKey{ - Version: 4, - CreationTime: creationTime, - PubKeyAlgo: PubKeyAlgoElGamal, - PublicKey: pub, - p: new(encoding.MPI).SetBig(pub.P), - g: new(encoding.MPI).SetBig(pub.G), - y: new(encoding.MPI).SetBig(pub.Y), - } - - pk.setFingerprintAndKeyId() - return pk -} - -func NewECDSAPublicKey(creationTime time.Time, pub *ecdsa.PublicKey) *PublicKey { - pk := &PublicKey{ - Version: 4, - CreationTime: creationTime, - PubKeyAlgo: PubKeyAlgoECDSA, - PublicKey: pub, - p: encoding.NewMPI(pub.MarshalPoint()), - } - - curveInfo := ecc.FindByCurve(pub.GetCurve()) - if curveInfo == nil { - panic("unknown elliptic curve") - } - pk.oid = curveInfo.Oid - pk.setFingerprintAndKeyId() - return pk -} - -func NewECDHPublicKey(creationTime time.Time, pub *ecdh.PublicKey) *PublicKey { - var pk *PublicKey - var kdf = encoding.NewOID([]byte{0x1, pub.Hash.Id(), pub.Cipher.Id()}) - pk = &PublicKey{ - Version: 4, - CreationTime: creationTime, - PubKeyAlgo: PubKeyAlgoECDH, - PublicKey: pub, - p: encoding.NewMPI(pub.MarshalPoint()), - kdf: kdf, - } - - curveInfo := ecc.FindByCurve(pub.GetCurve()) - - if curveInfo == nil { - panic("unknown elliptic curve") - } - - pk.oid = curveInfo.Oid - pk.setFingerprintAndKeyId() - return pk -} - -func NewEdDSAPublicKey(creationTime time.Time, pub *eddsa.PublicKey) *PublicKey { - curveInfo := ecc.FindByCurve(pub.GetCurve()) - pk := &PublicKey{ - Version: 4, - CreationTime: creationTime, - PubKeyAlgo: PubKeyAlgoEdDSA, - PublicKey: pub, - oid: curveInfo.Oid, - // Native point format, see draft-koch-eddsa-for-openpgp-04, Appendix B - p: encoding.NewMPI(pub.MarshalPoint()), - } - - pk.setFingerprintAndKeyId() - return pk -} - -func NewX25519PublicKey(creationTime time.Time, pub *x25519.PublicKey) *PublicKey { - pk := &PublicKey{ - Version: 4, - CreationTime: creationTime, - PubKeyAlgo: PubKeyAlgoX25519, - PublicKey: pub, - } - - pk.setFingerprintAndKeyId() - return pk -} - -func NewX448PublicKey(creationTime time.Time, pub *x448.PublicKey) *PublicKey { - pk := &PublicKey{ - Version: 4, - CreationTime: creationTime, - PubKeyAlgo: PubKeyAlgoX448, - PublicKey: pub, - } - - pk.setFingerprintAndKeyId() - return pk -} - -func NewEd25519PublicKey(creationTime time.Time, pub *ed25519.PublicKey) *PublicKey { - pk := &PublicKey{ - Version: 4, - CreationTime: creationTime, - PubKeyAlgo: PubKeyAlgoEd25519, - PublicKey: pub, - } - - pk.setFingerprintAndKeyId() - return pk -} - -func NewEd448PublicKey(creationTime time.Time, pub *ed448.PublicKey) *PublicKey { - pk := &PublicKey{ - Version: 4, - CreationTime: creationTime, - PubKeyAlgo: PubKeyAlgoEd448, - PublicKey: pub, - } - - pk.setFingerprintAndKeyId() - return pk -} - -func (pk *PublicKey) parse(r io.Reader) (err error) { - // RFC 4880, section 5.5.2 - var buf [6]byte - _, err = readFull(r, buf[:]) - if err != nil { - return - } - - pk.Version = int(buf[0]) - if pk.Version != 4 && pk.Version != 5 && pk.Version != 6 { - return errors.UnsupportedError("public key version " + strconv.Itoa(int(buf[0]))) - } - - if V5Disabled && pk.Version == 5 { - return errors.UnsupportedError("support for parsing v5 entities is disabled; build with `-tags v5` if needed") - } - - if pk.Version >= 5 { - // Read the four-octet scalar octet count - // The count is not used in this implementation - var n [4]byte - _, err = readFull(r, n[:]) - if err != nil { - return - } - } - pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0) - pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5]) - // Ignore four-ocet length - switch pk.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: - err = pk.parseRSA(r) - case PubKeyAlgoDSA: - err = pk.parseDSA(r) - case PubKeyAlgoElGamal: - err = pk.parseElGamal(r) - case PubKeyAlgoECDSA: - err = pk.parseECDSA(r) - case PubKeyAlgoECDH: - err = pk.parseECDH(r) - case PubKeyAlgoEdDSA: - err = pk.parseEdDSA(r) - case PubKeyAlgoX25519: - err = pk.parseX25519(r) - case PubKeyAlgoX448: - err = pk.parseX448(r) - case PubKeyAlgoEd25519: - err = pk.parseEd25519(r) - case PubKeyAlgoEd448: - err = pk.parseEd448(r) - default: - err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo))) - } - if err != nil { - return - } - - pk.setFingerprintAndKeyId() - return -} - -func (pk *PublicKey) setFingerprintAndKeyId() { - // RFC 4880, section 12.2 - if pk.Version >= 5 { - fingerprint := sha256.New() - if err := pk.SerializeForHash(fingerprint); err != nil { - // Should not happen for a hash. - panic(err) - } - pk.Fingerprint = make([]byte, 32) - copy(pk.Fingerprint, fingerprint.Sum(nil)) - pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[:8]) - } else { - fingerprint := sha1.New() - if err := pk.SerializeForHash(fingerprint); err != nil { - // Should not happen for a hash. - panic(err) - } - pk.Fingerprint = make([]byte, 20) - copy(pk.Fingerprint, fingerprint.Sum(nil)) - pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[12:20]) - } -} - -func (pk *PublicKey) checkV6Compatibility() error { - // Implementations MUST NOT accept or generate version 6 key material using the deprecated OIDs. - switch pk.PubKeyAlgo { - case PubKeyAlgoECDH: - curveInfo := ecc.FindByOid(pk.oid) - if curveInfo == nil { - return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid)) - } - if curveInfo.GenName == ecc.Curve25519GenName { - return errors.StructuralError("cannot generate v6 key with deprecated OID: Curve25519Legacy") - } - case PubKeyAlgoEdDSA: - return errors.StructuralError("cannot generate v6 key with deprecated algorithm: EdDSALegacy") - } - return nil -} - -// parseRSA parses RSA public key material from the given Reader. See RFC 4880, -// section 5.5.2. -func (pk *PublicKey) parseRSA(r io.Reader) (err error) { - pk.n = new(encoding.MPI) - if _, err = pk.n.ReadFrom(r); err != nil { - return - } - pk.e = new(encoding.MPI) - if _, err = pk.e.ReadFrom(r); err != nil { - return - } - - if len(pk.e.Bytes()) > 3 { - err = errors.UnsupportedError("large public exponent") - return - } - rsa := &rsa.PublicKey{ - N: new(big.Int).SetBytes(pk.n.Bytes()), - E: 0, - } - for i := 0; i < len(pk.e.Bytes()); i++ { - rsa.E <<= 8 - rsa.E |= int(pk.e.Bytes()[i]) - } - pk.PublicKey = rsa - return -} - -// parseDSA parses DSA public key material from the given Reader. See RFC 4880, -// section 5.5.2. -func (pk *PublicKey) parseDSA(r io.Reader) (err error) { - pk.p = new(encoding.MPI) - if _, err = pk.p.ReadFrom(r); err != nil { - return - } - pk.q = new(encoding.MPI) - if _, err = pk.q.ReadFrom(r); err != nil { - return - } - pk.g = new(encoding.MPI) - if _, err = pk.g.ReadFrom(r); err != nil { - return - } - pk.y = new(encoding.MPI) - if _, err = pk.y.ReadFrom(r); err != nil { - return - } - - dsa := new(dsa.PublicKey) - dsa.P = new(big.Int).SetBytes(pk.p.Bytes()) - dsa.Q = new(big.Int).SetBytes(pk.q.Bytes()) - dsa.G = new(big.Int).SetBytes(pk.g.Bytes()) - dsa.Y = new(big.Int).SetBytes(pk.y.Bytes()) - pk.PublicKey = dsa - return -} - -// parseElGamal parses ElGamal public key material from the given Reader. See -// RFC 4880, section 5.5.2. -func (pk *PublicKey) parseElGamal(r io.Reader) (err error) { - pk.p = new(encoding.MPI) - if _, err = pk.p.ReadFrom(r); err != nil { - return - } - pk.g = new(encoding.MPI) - if _, err = pk.g.ReadFrom(r); err != nil { - return - } - pk.y = new(encoding.MPI) - if _, err = pk.y.ReadFrom(r); err != nil { - return - } - - elgamal := new(elgamal.PublicKey) - elgamal.P = new(big.Int).SetBytes(pk.p.Bytes()) - elgamal.G = new(big.Int).SetBytes(pk.g.Bytes()) - elgamal.Y = new(big.Int).SetBytes(pk.y.Bytes()) - pk.PublicKey = elgamal - return -} - -// parseECDSA parses ECDSA public key material from the given Reader. See -// RFC 6637, Section 9. -func (pk *PublicKey) parseECDSA(r io.Reader) (err error) { - pk.oid = new(encoding.OID) - if _, err = pk.oid.ReadFrom(r); err != nil { - return - } - - curveInfo := ecc.FindByOid(pk.oid) - if curveInfo == nil { - return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid)) - } - - pk.p = new(encoding.MPI) - if _, err = pk.p.ReadFrom(r); err != nil { - return - } - - c, ok := curveInfo.Curve.(ecc.ECDSACurve) - if !ok { - return errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", pk.oid)) - } - - ecdsaKey := ecdsa.NewPublicKey(c) - err = ecdsaKey.UnmarshalPoint(pk.p.Bytes()) - pk.PublicKey = ecdsaKey - - return -} - -// parseECDH parses ECDH public key material from the given Reader. See -// RFC 6637, Section 9. -func (pk *PublicKey) parseECDH(r io.Reader) (err error) { - pk.oid = new(encoding.OID) - if _, err = pk.oid.ReadFrom(r); err != nil { - return - } - - curveInfo := ecc.FindByOid(pk.oid) - if curveInfo == nil { - return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid)) - } - - if pk.Version == 6 && curveInfo.GenName == ecc.Curve25519GenName { - // Implementations MUST NOT accept or generate version 6 key material using the deprecated OIDs. - return errors.StructuralError("cannot read v6 key with deprecated OID: Curve25519Legacy") - } - - pk.p = new(encoding.MPI) - if _, err = pk.p.ReadFrom(r); err != nil { - return - } - pk.kdf = new(encoding.OID) - if _, err = pk.kdf.ReadFrom(r); err != nil { - return - } - - c, ok := curveInfo.Curve.(ecc.ECDHCurve) - if !ok { - return errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", pk.oid)) - } - - if kdfLen := len(pk.kdf.Bytes()); kdfLen < 3 { - return errors.UnsupportedError("unsupported ECDH KDF length: " + strconv.Itoa(kdfLen)) - } - if reserved := pk.kdf.Bytes()[0]; reserved != 0x01 { - return errors.UnsupportedError("unsupported KDF reserved field: " + strconv.Itoa(int(reserved))) - } - kdfHash, ok := algorithm.HashById[pk.kdf.Bytes()[1]] - if !ok { - return errors.UnsupportedError("unsupported ECDH KDF hash: " + strconv.Itoa(int(pk.kdf.Bytes()[1]))) - } - kdfCipher, ok := algorithm.CipherById[pk.kdf.Bytes()[2]] - if !ok { - return errors.UnsupportedError("unsupported ECDH KDF cipher: " + strconv.Itoa(int(pk.kdf.Bytes()[2]))) - } - - ecdhKey := ecdh.NewPublicKey(c, kdfHash, kdfCipher) - err = ecdhKey.UnmarshalPoint(pk.p.Bytes()) - pk.PublicKey = ecdhKey - - return -} - -func (pk *PublicKey) parseEdDSA(r io.Reader) (err error) { - if pk.Version == 6 { - // Implementations MUST NOT accept or generate version 6 key material using the deprecated OIDs. - return errors.StructuralError("cannot generate v6 key with deprecated algorithm: EdDSALegacy") - } - - pk.oid = new(encoding.OID) - if _, err = pk.oid.ReadFrom(r); err != nil { - return - } - - curveInfo := ecc.FindByOid(pk.oid) - if curveInfo == nil { - return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid)) - } - - c, ok := curveInfo.Curve.(ecc.EdDSACurve) - if !ok { - return errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", pk.oid)) - } - - pk.p = new(encoding.MPI) - if _, err = pk.p.ReadFrom(r); err != nil { - return - } - - if len(pk.p.Bytes()) == 0 { - return errors.StructuralError("empty EdDSA public key") - } - - pub := eddsa.NewPublicKey(c) - - switch flag := pk.p.Bytes()[0]; flag { - case 0x04: - // TODO: see _grcy_ecc_eddsa_ensure_compact in grcypt - return errors.UnsupportedError("unsupported EdDSA compression: " + strconv.Itoa(int(flag))) - case 0x40: - err = pub.UnmarshalPoint(pk.p.Bytes()) - default: - return errors.UnsupportedError("unsupported EdDSA compression: " + strconv.Itoa(int(flag))) - } - - pk.PublicKey = pub - return -} - -func (pk *PublicKey) parseX25519(r io.Reader) (err error) { - point := make([]byte, x25519.KeySize) - _, err = io.ReadFull(r, point) - if err != nil { - return - } - pub := &x25519.PublicKey{ - Point: point, - } - pk.PublicKey = pub - return -} - -func (pk *PublicKey) parseX448(r io.Reader) (err error) { - point := make([]byte, x448.KeySize) - _, err = io.ReadFull(r, point) - if err != nil { - return - } - pub := &x448.PublicKey{ - Point: point, - } - pk.PublicKey = pub - return -} - -func (pk *PublicKey) parseEd25519(r io.Reader) (err error) { - point := make([]byte, ed25519.PublicKeySize) - _, err = io.ReadFull(r, point) - if err != nil { - return - } - pub := &ed25519.PublicKey{ - Point: point, - } - pk.PublicKey = pub - return -} - -func (pk *PublicKey) parseEd448(r io.Reader) (err error) { - point := make([]byte, ed448.PublicKeySize) - _, err = io.ReadFull(r, point) - if err != nil { - return - } - pub := &ed448.PublicKey{ - Point: point, - } - pk.PublicKey = pub - return -} - -// SerializeForHash serializes the PublicKey to w with the special packet -// header format needed for hashing. -func (pk *PublicKey) SerializeForHash(w io.Writer) error { - if err := pk.SerializeSignaturePrefix(w); err != nil { - return err - } - return pk.serializeWithoutHeaders(w) -} - -// SerializeSignaturePrefix writes the prefix for this public key to the given Writer. -// The prefix is used when calculating a signature over this public key. See -// RFC 4880, section 5.2.4. -func (pk *PublicKey) SerializeSignaturePrefix(w io.Writer) error { - var pLength = pk.algorithmSpecificByteCount() - // version, timestamp, algorithm - pLength += versionSize + timestampSize + algorithmSize - if pk.Version >= 5 { - // key octet count (4). - pLength += 4 - _, err := w.Write([]byte{ - // When a v4 signature is made over a key, the hash data starts with the octet 0x99, followed by a two-octet length - // of the key, and then the body of the key packet. When a v6 signature is made over a key, the hash data starts - // with the salt, then octet 0x9B, followed by a four-octet length of the key, and then the body of the key packet. - 0x95 + byte(pk.Version), - byte(pLength >> 24), - byte(pLength >> 16), - byte(pLength >> 8), - byte(pLength), - }) - return err - } - if _, err := w.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)}); err != nil { - return err - } - return nil -} - -func (pk *PublicKey) Serialize(w io.Writer) (err error) { - length := uint32(versionSize + timestampSize + algorithmSize) // 6 byte header - length += pk.algorithmSpecificByteCount() - if pk.Version >= 5 { - length += 4 // octet key count - } - packetType := packetTypePublicKey - if pk.IsSubkey { - packetType = packetTypePublicSubkey - } - err = serializeHeader(w, packetType, int(length)) - if err != nil { - return - } - return pk.serializeWithoutHeaders(w) -} - -func (pk *PublicKey) algorithmSpecificByteCount() uint32 { - length := uint32(0) - switch pk.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: - length += uint32(pk.n.EncodedLength()) - length += uint32(pk.e.EncodedLength()) - case PubKeyAlgoDSA: - length += uint32(pk.p.EncodedLength()) - length += uint32(pk.q.EncodedLength()) - length += uint32(pk.g.EncodedLength()) - length += uint32(pk.y.EncodedLength()) - case PubKeyAlgoElGamal: - length += uint32(pk.p.EncodedLength()) - length += uint32(pk.g.EncodedLength()) - length += uint32(pk.y.EncodedLength()) - case PubKeyAlgoECDSA: - length += uint32(pk.oid.EncodedLength()) - length += uint32(pk.p.EncodedLength()) - case PubKeyAlgoECDH: - length += uint32(pk.oid.EncodedLength()) - length += uint32(pk.p.EncodedLength()) - length += uint32(pk.kdf.EncodedLength()) - case PubKeyAlgoEdDSA: - length += uint32(pk.oid.EncodedLength()) - length += uint32(pk.p.EncodedLength()) - case PubKeyAlgoX25519: - length += x25519.KeySize - case PubKeyAlgoX448: - length += x448.KeySize - case PubKeyAlgoEd25519: - length += ed25519.PublicKeySize - case PubKeyAlgoEd448: - length += ed448.PublicKeySize - default: - panic("unknown public key algorithm") - } - return length -} - -// serializeWithoutHeaders marshals the PublicKey to w in the form of an -// OpenPGP public key packet, not including the packet header. -func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) { - t := uint32(pk.CreationTime.Unix()) - if _, err = w.Write([]byte{ - byte(pk.Version), - byte(t >> 24), byte(t >> 16), byte(t >> 8), byte(t), - byte(pk.PubKeyAlgo), - }); err != nil { - return - } - - if pk.Version >= 5 { - n := pk.algorithmSpecificByteCount() - if _, err = w.Write([]byte{ - byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n), - }); err != nil { - return - } - } - - switch pk.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: - if _, err = w.Write(pk.n.EncodedBytes()); err != nil { - return - } - _, err = w.Write(pk.e.EncodedBytes()) - return - case PubKeyAlgoDSA: - if _, err = w.Write(pk.p.EncodedBytes()); err != nil { - return - } - if _, err = w.Write(pk.q.EncodedBytes()); err != nil { - return - } - if _, err = w.Write(pk.g.EncodedBytes()); err != nil { - return - } - _, err = w.Write(pk.y.EncodedBytes()) - return - case PubKeyAlgoElGamal: - if _, err = w.Write(pk.p.EncodedBytes()); err != nil { - return - } - if _, err = w.Write(pk.g.EncodedBytes()); err != nil { - return - } - _, err = w.Write(pk.y.EncodedBytes()) - return - case PubKeyAlgoECDSA: - if _, err = w.Write(pk.oid.EncodedBytes()); err != nil { - return - } - _, err = w.Write(pk.p.EncodedBytes()) - return - case PubKeyAlgoECDH: - if _, err = w.Write(pk.oid.EncodedBytes()); err != nil { - return - } - if _, err = w.Write(pk.p.EncodedBytes()); err != nil { - return - } - _, err = w.Write(pk.kdf.EncodedBytes()) - return - case PubKeyAlgoEdDSA: - if _, err = w.Write(pk.oid.EncodedBytes()); err != nil { - return - } - _, err = w.Write(pk.p.EncodedBytes()) - return - case PubKeyAlgoX25519: - publicKey := pk.PublicKey.(*x25519.PublicKey) - _, err = w.Write(publicKey.Point) - return - case PubKeyAlgoX448: - publicKey := pk.PublicKey.(*x448.PublicKey) - _, err = w.Write(publicKey.Point) - return - case PubKeyAlgoEd25519: - publicKey := pk.PublicKey.(*ed25519.PublicKey) - _, err = w.Write(publicKey.Point) - return - case PubKeyAlgoEd448: - publicKey := pk.PublicKey.(*ed448.PublicKey) - _, err = w.Write(publicKey.Point) - return - } - return errors.InvalidArgumentError("bad public-key algorithm") -} - -// CanSign returns true iff this public key can generate signatures -func (pk *PublicKey) CanSign() bool { - return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly && pk.PubKeyAlgo != PubKeyAlgoElGamal && pk.PubKeyAlgo != PubKeyAlgoECDH -} - -// VerifyHashTag returns nil iff sig appears to be a plausible signature of the data -// hashed into signed, based solely on its HashTag. signed is mutated by this call. -func VerifyHashTag(signed hash.Hash, sig *Signature) (err error) { - if sig.Version == 5 && (sig.SigType == 0x00 || sig.SigType == 0x01) { - sig.AddMetadataToHashSuffix() - } - signed.Write(sig.HashSuffix) - hashBytes := signed.Sum(nil) - if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] { - return errors.SignatureError("hash tag doesn't match") - } - return nil -} - -// VerifySignature returns nil iff sig is a valid signature, made by this -// public key, of the data hashed into signed. signed is mutated by this call. -func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err error) { - if !pk.CanSign() { - return errors.InvalidArgumentError("public key cannot generate signatures") - } - if sig.Version == 5 && (sig.SigType == 0x00 || sig.SigType == 0x01) { - sig.AddMetadataToHashSuffix() - } - signed.Write(sig.HashSuffix) - hashBytes := signed.Sum(nil) - // see discussion https://github.com/ProtonMail/go-crypto/issues/107 - if sig.Version >= 5 && (hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1]) { - return errors.SignatureError("hash tag doesn't match") - } - - if pk.PubKeyAlgo != sig.PubKeyAlgo { - return errors.InvalidArgumentError("public key and signature use different algorithms") - } - - switch pk.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: - rsaPublicKey, _ := pk.PublicKey.(*rsa.PublicKey) - err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, padToKeySize(rsaPublicKey, sig.RSASignature.Bytes())) - if err != nil { - return errors.SignatureError("RSA verification failure") - } - return nil - case PubKeyAlgoDSA: - dsaPublicKey, _ := pk.PublicKey.(*dsa.PublicKey) - // Need to truncate hashBytes to match FIPS 186-3 section 4.6. - subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8 - if len(hashBytes) > subgroupSize { - hashBytes = hashBytes[:subgroupSize] - } - if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.Bytes()), new(big.Int).SetBytes(sig.DSASigS.Bytes())) { - return errors.SignatureError("DSA verification failure") - } - return nil - case PubKeyAlgoECDSA: - ecdsaPublicKey := pk.PublicKey.(*ecdsa.PublicKey) - if !ecdsa.Verify(ecdsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.ECDSASigR.Bytes()), new(big.Int).SetBytes(sig.ECDSASigS.Bytes())) { - return errors.SignatureError("ECDSA verification failure") - } - return nil - case PubKeyAlgoEdDSA: - eddsaPublicKey := pk.PublicKey.(*eddsa.PublicKey) - if !eddsa.Verify(eddsaPublicKey, hashBytes, sig.EdDSASigR.Bytes(), sig.EdDSASigS.Bytes()) { - return errors.SignatureError("EdDSA verification failure") - } - return nil - case PubKeyAlgoEd25519: - ed25519PublicKey := pk.PublicKey.(*ed25519.PublicKey) - if !ed25519.Verify(ed25519PublicKey, hashBytes, sig.EdSig) { - return errors.SignatureError("Ed25519 verification failure") - } - return nil - case PubKeyAlgoEd448: - ed448PublicKey := pk.PublicKey.(*ed448.PublicKey) - if !ed448.Verify(ed448PublicKey, hashBytes, sig.EdSig) { - return errors.SignatureError("ed448 verification failure") - } - return nil - default: - return errors.SignatureError("Unsupported public key algorithm used in signature") - } -} - -// keySignatureHash returns a Hash of the message that needs to be signed for -// pk to assert a subkey relationship to signed. -func keySignatureHash(pk, signed signingKey, hashFunc hash.Hash) (h hash.Hash, err error) { - h = hashFunc - - // RFC 4880, section 5.2.4 - err = pk.SerializeForHash(h) - if err != nil { - return nil, err - } - - err = signed.SerializeForHash(h) - return -} - -// VerifyKeyHashTag returns nil iff sig appears to be a plausible signature over this -// primary key and subkey, based solely on its HashTag. -func (pk *PublicKey) VerifyKeyHashTag(signed *PublicKey, sig *Signature) error { - preparedHash, err := sig.PrepareVerify() - if err != nil { - return err - } - h, err := keySignatureHash(pk, signed, preparedHash) - if err != nil { - return err - } - return VerifyHashTag(h, sig) -} - -// VerifyKeySignature returns nil iff sig is a valid signature, made by this -// public key, of signed. -func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error { - preparedHash, err := sig.PrepareVerify() - if err != nil { - return err - } - h, err := keySignatureHash(pk, signed, preparedHash) - if err != nil { - return err - } - if err = pk.VerifySignature(h, sig); err != nil { - return err - } - - if sig.FlagSign { - // Signing subkeys must be cross-signed. See - // https://www.gnupg.org/faq/subkey-cross-certify.html. - if sig.EmbeddedSignature == nil { - return errors.StructuralError("signing subkey is missing cross-signature") - } - preparedHashEmbedded, err := sig.EmbeddedSignature.PrepareVerify() - if err != nil { - return err - } - // Verify the cross-signature. This is calculated over the same - // data as the main signature, so we cannot just recursively - // call signed.VerifyKeySignature(...) - if h, err = keySignatureHash(pk, signed, preparedHashEmbedded); err != nil { - return errors.StructuralError("error while hashing for cross-signature: " + err.Error()) - } - if err := signed.VerifySignature(h, sig.EmbeddedSignature); err != nil { - return errors.StructuralError("error while verifying cross-signature: " + err.Error()) - } - } - - return nil -} - -func keyRevocationHash(pk signingKey, hashFunc hash.Hash) (err error) { - return pk.SerializeForHash(hashFunc) -} - -// VerifyRevocationHashTag returns nil iff sig appears to be a plausible signature -// over this public key, based solely on its HashTag. -func (pk *PublicKey) VerifyRevocationHashTag(sig *Signature) (err error) { - preparedHash, err := sig.PrepareVerify() - if err != nil { - return err - } - if err = keyRevocationHash(pk, preparedHash); err != nil { - return err - } - return VerifyHashTag(preparedHash, sig) -} - -// VerifyRevocationSignature returns nil iff sig is a valid signature, made by this -// public key. -func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) { - preparedHash, err := sig.PrepareVerify() - if err != nil { - return err - } - if err = keyRevocationHash(pk, preparedHash); err != nil { - return err - } - return pk.VerifySignature(preparedHash, sig) -} - -// VerifySubkeyRevocationSignature returns nil iff sig is a valid subkey revocation signature, -// made by this public key, of signed. -func (pk *PublicKey) VerifySubkeyRevocationSignature(sig *Signature, signed *PublicKey) (err error) { - preparedHash, err := sig.PrepareVerify() - if err != nil { - return err - } - h, err := keySignatureHash(pk, signed, preparedHash) - if err != nil { - return err - } - return pk.VerifySignature(h, sig) -} - -// userIdSignatureHash returns a Hash of the message that needs to be signed -// to assert that pk is a valid key for id. -func userIdSignatureHash(id string, pk *PublicKey, h hash.Hash) (err error) { - - // RFC 4880, section 5.2.4 - if err := pk.SerializeSignaturePrefix(h); err != nil { - return err - } - if err := pk.serializeWithoutHeaders(h); err != nil { - return err - } - - var buf [5]byte - buf[0] = 0xb4 - buf[1] = byte(len(id) >> 24) - buf[2] = byte(len(id) >> 16) - buf[3] = byte(len(id) >> 8) - buf[4] = byte(len(id)) - h.Write(buf[:]) - h.Write([]byte(id)) - - return nil -} - -// directKeySignatureHash returns a Hash of the message that needs to be signed. -func directKeySignatureHash(pk *PublicKey, h hash.Hash) (err error) { - return pk.SerializeForHash(h) -} - -// VerifyUserIdHashTag returns nil iff sig appears to be a plausible signature over this -// public key and UserId, based solely on its HashTag -func (pk *PublicKey) VerifyUserIdHashTag(id string, sig *Signature) (err error) { - preparedHash, err := sig.PrepareVerify() - if err != nil { - return err - } - err = userIdSignatureHash(id, pk, preparedHash) - if err != nil { - return err - } - return VerifyHashTag(preparedHash, sig) -} - -// VerifyUserIdSignature returns nil iff sig is a valid signature, made by this -// public key, that id is the identity of pub. -func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signature) (err error) { - h, err := sig.PrepareVerify() - if err != nil { - return err - } - if err := userIdSignatureHash(id, pub, h); err != nil { - return err - } - return pk.VerifySignature(h, sig) -} - -// VerifyDirectKeySignature returns nil iff sig is a valid signature, made by this -// public key. -func (pk *PublicKey) VerifyDirectKeySignature(sig *Signature) (err error) { - h, err := sig.PrepareVerify() - if err != nil { - return err - } - if err := directKeySignatureHash(pk, h); err != nil { - return err - } - return pk.VerifySignature(h, sig) -} - -// KeyIdString returns the public key's fingerprint in capital hex -// (e.g. "6C7EE1B8621CC013"). -func (pk *PublicKey) KeyIdString() string { - return fmt.Sprintf("%016X", pk.KeyId) -} - -// KeyIdShortString returns the short form of public key's fingerprint -// in capital hex, as shown by gpg --list-keys (e.g. "621CC013"). -// This function will return the full key id for v5 and v6 keys -// since the short key id is undefined for them. -func (pk *PublicKey) KeyIdShortString() string { - if pk.Version >= 5 { - return pk.KeyIdString() - } - return fmt.Sprintf("%X", pk.Fingerprint[16:20]) -} - -// BitLength returns the bit length for the given public key. -func (pk *PublicKey) BitLength() (bitLength uint16, err error) { - switch pk.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: - bitLength = pk.n.BitLength() - case PubKeyAlgoDSA: - bitLength = pk.p.BitLength() - case PubKeyAlgoElGamal: - bitLength = pk.p.BitLength() - case PubKeyAlgoECDSA: - bitLength = pk.p.BitLength() - case PubKeyAlgoECDH: - bitLength = pk.p.BitLength() - case PubKeyAlgoEdDSA: - bitLength = pk.p.BitLength() - case PubKeyAlgoX25519: - bitLength = x25519.KeySize * 8 - case PubKeyAlgoX448: - bitLength = x448.KeySize * 8 - case PubKeyAlgoEd25519: - bitLength = ed25519.PublicKeySize * 8 - case PubKeyAlgoEd448: - bitLength = ed448.PublicKeySize * 8 - default: - err = errors.InvalidArgumentError("bad public-key algorithm") - } - return -} - -// Curve returns the used elliptic curve of this public key. -// Returns an error if no elliptic curve is used. -func (pk *PublicKey) Curve() (curve Curve, err error) { - switch pk.PubKeyAlgo { - case PubKeyAlgoECDSA, PubKeyAlgoECDH, PubKeyAlgoEdDSA: - curveInfo := ecc.FindByOid(pk.oid) - if curveInfo == nil { - return "", errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid)) - } - curve = Curve(curveInfo.GenName) - case PubKeyAlgoEd25519, PubKeyAlgoX25519: - curve = Curve25519 - case PubKeyAlgoEd448, PubKeyAlgoX448: - curve = Curve448 - default: - err = errors.InvalidArgumentError("public key does not operate with an elliptic curve") - } - return -} - -// KeyExpired returns whether sig is a self-signature of a key that has -// expired or is created in the future. -func (pk *PublicKey) KeyExpired(sig *Signature, currentTime time.Time) bool { - if pk.CreationTime.Unix() > currentTime.Unix() { - return true - } - if sig.KeyLifetimeSecs == nil || *sig.KeyLifetimeSecs == 0 { - return false - } - expiry := pk.CreationTime.Add(time.Duration(*sig.KeyLifetimeSecs) * time.Second) - return currentTime.Unix() > expiry.Unix() -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/public_key_test_data.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/public_key_test_data.go deleted file mode 100644 index b255f1f6f8f..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/public_key_test_data.go +++ /dev/null @@ -1,24 +0,0 @@ -package packet - -const rsaFingerprintHex = "5fb74b1d03b1e3cb31bc2f8aa34d7e18c20c31bb" - -const rsaPkDataHex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001" - -const dsaFingerprintHex = "eece4c094db002103714c63c8e8fbe54062f19ed" - -const dsaPkDataHex = "9901a2044d432f89110400cd581334f0d7a1e1bdc8b9d6d8c0baf68793632735d2bb0903224cbaa1dfbf35a60ee7a13b92643421e1eb41aa8d79bea19a115a677f6b8ba3c7818ce53a6c2a24a1608bd8b8d6e55c5090cbde09dd26e356267465ae25e69ec8bdd57c7bbb2623e4d73336f73a0a9098f7f16da2e25252130fd694c0e8070c55a812a423ae7f00a0ebf50e70c2f19c3520a551bd4b08d30f23530d3d03ff7d0bf4a53a64a09dc5e6e6e35854b7d70c882b0c60293401958b1bd9e40abec3ea05ba87cf64899299d4bd6aa7f459c201d3fbbd6c82004bdc5e8a9eb8082d12054cc90fa9d4ec251a843236a588bf49552441817436c4f43326966fe85447d4e6d0acf8fa1ef0f014730770603ad7634c3088dc52501c237328417c31c89ed70400b2f1a98b0bf42f11fefc430704bebbaa41d9f355600c3facee1e490f64208e0e094ea55e3a598a219a58500bf78ac677b670a14f4e47e9cf8eab4f368cc1ddcaa18cc59309d4cc62dd4f680e73e6cc3e1ce87a84d0925efbcb26c575c093fc42eecf45135fabf6403a25c2016e1774c0484e440a18319072c617cc97ac0a3bb0" - -const ecdsaFingerprintHex = "9892270b38b8980b05c8d56d43fe956c542ca00b" - -const ecdsaPkDataHex = "9893045071c29413052b8104002304230401f4867769cedfa52c325018896245443968e52e51d0c2df8d939949cb5b330f2921711fbee1c9b9dddb95d15cb0255e99badeddda7cc23d9ddcaacbc290969b9f24019375d61c2e4e3b36953a28d8b2bc95f78c3f1d592fb24499be348656a7b17e3963187b4361afe497bc5f9f81213f04069f8e1fb9e6a6290ae295ca1a92b894396cb4" - -const ecdhFingerprintHex = "722354df2475a42164d1d49faa8b938f9a201946" - -const ecdhPkDataHex = "b90073044d53059212052b810400220303042faa84024a20b6735c4897efa5bfb41bf85b7eefeab5ca0cb9ffc8ea04a46acb25534a577694f9e25340a4ab5223a9dd1eda530c8aa2e6718db10d7e672558c7736fe09369ea5739a2a3554bf16d41faa50562f11c6d39bbd5dffb6b9a9ec91803010909" - -const eddsaFingerprintHex = "b2d5e5ec0e6deca6bc8eeeb00907e75e1dd99ad8" - -const eddsaPkDataHex = "98330456e2132b16092b06010401da470f01010740bbda39266affa511a8c2d02edf690fb784b0499c4406185811a163539ef11dc1b41d74657374696e67203c74657374696e674074657374696e672e636f6d3e8879041316080021050256e2132b021b03050b09080702061508090a0b020416020301021e01021780000a09100907e75e1dd99ad86d0c00fe39d2008359352782bc9b61ac382584cd8eff3f57a18c2287e3afeeb05d1f04ba00fe2d0bc1ddf3ff8adb9afa3e7d9287244b4ec567f3db4d60b74a9b5465ed528203" - -// Source: https://sites.google.com/site/brainhub/pgpecckeys#TOC-ECC-NIST-P-384-key -const ecc384PubHex = `99006f044d53059213052b81040022030304f6b8c5aced5b84ef9f4a209db2e4a9dfb70d28cb8c10ecd57674a9fa5a67389942b62d5e51367df4c7bfd3f8e500feecf07ed265a621a8ebbbe53e947ec78c677eba143bd1533c2b350e1c29f82313e1e1108eba063be1e64b10e6950e799c2db42465635f6473615f64685f333834203c6f70656e70677040627261696e6875622e6f72673e8900cb04101309005305024d530592301480000000002000077072656665727265642d656d61696c2d656e636f64696e67407067702e636f6d7067706d696d65040b090807021901051b03000000021602051e010000000415090a08000a0910098033880f54719fca2b0180aa37350968bd5f115afd8ce7bc7b103822152dbff06d0afcda835329510905b98cb469ba208faab87c7412b799e7b633017f58364ea480e8a1a3f253a0c5f22c446e8be9a9fce6210136ee30811abbd49139de28b5bdf8dc36d06ae748579e9ff503b90073044d53059212052b810400220303042faa84024a20b6735c4897efa5bfb41bf85b7eefeab5ca0cb9ffc8ea04a46acb25534a577694f9e25340a4ab5223a9dd1eda530c8aa2e6718db10d7e672558c7736fe09369ea5739a2a3554bf16d41faa50562f11c6d39bbd5dffb6b9a9ec9180301090989008404181309000c05024d530592051b0c000000000a0910098033880f54719f80970180eee7a6d8fcee41ee4f9289df17f9bcf9d955dca25c583b94336f3a2b2d4986dc5cf417b8d2dc86f741a9e1a6d236c0e3017d1c76575458a0cfb93ae8a2b274fcc65ceecd7a91eec83656ba13219969f06945b48c56bd04152c3a0553c5f2f4bd1267` diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/reader.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/reader.go deleted file mode 100644 index dd84092392a..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/reader.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" -) - -type PacketReader interface { - Next() (p Packet, err error) - Push(reader io.Reader) (err error) - Unread(p Packet) -} - -// Reader reads packets from an io.Reader and allows packets to be 'unread' so -// that they result from the next call to Next. -type Reader struct { - q []Packet - readers []io.Reader -} - -// New io.Readers are pushed when a compressed or encrypted packet is processed -// and recursively treated as a new source of packets. However, a carefully -// crafted packet can trigger an infinite recursive sequence of packets. See -// http://mumble.net/~campbell/misc/pgp-quine -// https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2013-4402 -// This constant limits the number of recursive packets that may be pushed. -const maxReaders = 32 - -// Next returns the most recently unread Packet, or reads another packet from -// the top-most io.Reader. Unknown/unsupported/Marker packet types are skipped. -func (r *Reader) Next() (p Packet, err error) { - for { - p, err := r.read() - if err == io.EOF { - break - } else if err != nil { - if _, ok := err.(errors.UnknownPacketTypeError); ok { - continue - } - if _, ok := err.(errors.UnsupportedError); ok { - switch p.(type) { - case *SymmetricallyEncrypted, *AEADEncrypted, *Compressed, *LiteralData: - return nil, err - } - continue - } - return nil, err - } else { - //A marker packet MUST be ignored when received - switch p.(type) { - case *Marker: - continue - } - return p, nil - } - } - return nil, io.EOF -} - -// Next returns the most recently unread Packet, or reads another packet from -// the top-most io.Reader. Unknown/Marker packet types are skipped while unsupported -// packets are returned as UnsupportedPacket type. -func (r *Reader) NextWithUnsupported() (p Packet, err error) { - for { - p, err = r.read() - if err == io.EOF { - break - } else if err != nil { - if _, ok := err.(errors.UnknownPacketTypeError); ok { - continue - } - if casteErr, ok := err.(errors.UnsupportedError); ok { - return &UnsupportedPacket{ - IncompletePacket: p, - Error: casteErr, - }, nil - } - return - } else { - //A marker packet MUST be ignored when received - switch p.(type) { - case *Marker: - continue - } - return - } - } - return nil, io.EOF -} - -func (r *Reader) read() (p Packet, err error) { - if len(r.q) > 0 { - p = r.q[len(r.q)-1] - r.q = r.q[:len(r.q)-1] - return - } - for len(r.readers) > 0 { - p, err = Read(r.readers[len(r.readers)-1]) - if err == io.EOF { - r.readers = r.readers[:len(r.readers)-1] - continue - } - return p, err - } - return nil, io.EOF -} - -// Push causes the Reader to start reading from a new io.Reader. When an EOF -// error is seen from the new io.Reader, it is popped and the Reader continues -// to read from the next most recent io.Reader. Push returns a StructuralError -// if pushing the reader would exceed the maximum recursion level, otherwise it -// returns nil. -func (r *Reader) Push(reader io.Reader) (err error) { - if len(r.readers) >= maxReaders { - return errors.StructuralError("too many layers of packets") - } - r.readers = append(r.readers, reader) - return nil -} - -// Unread causes the given Packet to be returned from the next call to Next. -func (r *Reader) Unread(p Packet) { - r.q = append(r.q, p) -} - -func NewReader(r io.Reader) *Reader { - return &Reader{ - q: nil, - readers: []io.Reader{r}, - } -} - -// CheckReader is similar to Reader but additionally -// uses the pushdown automata to verify the read packet sequence. -type CheckReader struct { - Reader - verifier *SequenceVerifier - fullyRead bool -} - -// Next returns the most recently unread Packet, or reads another packet from -// the top-most io.Reader. Unknown packet types are skipped. -// If the read packet sequence does not conform to the packet composition -// rules in rfc4880, it returns an error. -func (r *CheckReader) Next() (p Packet, err error) { - if r.fullyRead { - return nil, io.EOF - } - if len(r.q) > 0 { - p = r.q[len(r.q)-1] - r.q = r.q[:len(r.q)-1] - return - } - var errMsg error - for len(r.readers) > 0 { - p, errMsg, err = ReadWithCheck(r.readers[len(r.readers)-1], r.verifier) - if errMsg != nil { - err = errMsg - return - } - if err == nil { - return - } - if err == io.EOF { - r.readers = r.readers[:len(r.readers)-1] - continue - } - //A marker packet MUST be ignored when received - switch p.(type) { - case *Marker: - continue - } - if _, ok := err.(errors.UnknownPacketTypeError); ok { - continue - } - if _, ok := err.(errors.UnsupportedError); ok { - switch p.(type) { - case *SymmetricallyEncrypted, *AEADEncrypted, *Compressed, *LiteralData: - return nil, err - } - continue - } - return nil, err - } - if errMsg = r.verifier.Next(EOSSymbol); errMsg != nil { - return nil, errMsg - } - if errMsg = r.verifier.AssertValid(); errMsg != nil { - return nil, errMsg - } - r.fullyRead = true - return nil, io.EOF -} - -func NewCheckReader(r io.Reader) *CheckReader { - return &CheckReader{ - Reader: Reader{ - q: nil, - readers: []io.Reader{r}, - }, - verifier: NewSequenceVerifier(), - fullyRead: false, - } -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/recipient.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/recipient.go deleted file mode 100644 index fb2e362e4a8..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/recipient.go +++ /dev/null @@ -1,15 +0,0 @@ -package packet - -// Recipient type represents a Intended Recipient Fingerprint subpacket -// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#name-intended-recipient-fingerpr -type Recipient struct { - KeyVersion int - Fingerprint []byte -} - -func (r *Recipient) Serialize() []byte { - packet := make([]byte, len(r.Fingerprint)+1) - packet[0] = byte(r.KeyVersion) - copy(packet[1:], r.Fingerprint) - return packet -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/signature.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/signature.go deleted file mode 100644 index 84dd3b86f8a..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/signature.go +++ /dev/null @@ -1,1511 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "bytes" - "crypto" - "crypto/dsa" - "encoding/asn1" - "encoding/binary" - "hash" - "io" - "math/big" - "strconv" - "time" - - "github.com/ProtonMail/go-crypto/openpgp/ecdsa" - "github.com/ProtonMail/go-crypto/openpgp/ed25519" - "github.com/ProtonMail/go-crypto/openpgp/ed448" - "github.com/ProtonMail/go-crypto/openpgp/eddsa" - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" - "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" -) - -const ( - // First octet of key flags. - // See RFC 9580, section 5.2.3.29 for details. - KeyFlagCertify = 1 << iota - KeyFlagSign - KeyFlagEncryptCommunications - KeyFlagEncryptStorage - KeyFlagSplitKey - KeyFlagAuthenticate - _ - KeyFlagGroupKey -) - -const ( - // First octet of keyserver preference flags. - // See RFC 9580, section 5.2.3.25 for details. - _ = 1 << iota - _ - _ - _ - _ - _ - _ - KeyserverPrefNoModify -) - -const SaltNotationName = "salt@notations.openpgpjs.org" - -// Signature represents a signature. See RFC 9580, section 5.2. -type Signature struct { - Version int - SigType SignatureType - PubKeyAlgo PublicKeyAlgorithm - Hash crypto.Hash - // salt contains a random salt value for v6 signatures - // See RFC 9580 Section 5.2.4. - salt []byte - - // HashSuffix is extra data that is hashed in after the signed data. - HashSuffix []byte - // HashTag contains the first two bytes of the hash for fast rejection - // of bad signed data. - HashTag [2]byte - - // Metadata includes format, filename and time, and is protected by v5 - // signatures of type 0x00 or 0x01. This metadata is included into the hash - // computation; if nil, six 0x00 bytes are used instead. See section 5.2.4. - Metadata *LiteralData - - CreationTime time.Time - - RSASignature encoding.Field - DSASigR, DSASigS encoding.Field - ECDSASigR, ECDSASigS encoding.Field - EdDSASigR, EdDSASigS encoding.Field - EdSig []byte - - // rawSubpackets contains the unparsed subpackets, in order. - rawSubpackets []outputSubpacket - - // The following are optional so are nil when not included in the - // signature. - - SigLifetimeSecs, KeyLifetimeSecs *uint32 - PreferredSymmetric, PreferredHash, PreferredCompression []uint8 - PreferredCipherSuites [][2]uint8 - IssuerKeyId *uint64 - IssuerFingerprint []byte - SignerUserId *string - IsPrimaryId *bool - Notations []*Notation - IntendedRecipients []*Recipient - - // TrustLevel and TrustAmount can be set by the signer to assert that - // the key is not only valid but also trustworthy at the specified - // level. - // See RFC 9580, section 5.2.3.21 for details. - TrustLevel TrustLevel - TrustAmount TrustAmount - - // TrustRegularExpression can be used in conjunction with trust Signature - // packets to limit the scope of the trust that is extended. - // See RFC 9580, section 5.2.3.22 for details. - TrustRegularExpression *string - - // KeyserverPrefsValid is set if any keyserver preferences were given. See RFC 9580, section - // 5.2.3.25 for details. - KeyserverPrefsValid bool - KeyserverPrefNoModify bool - - // PreferredKeyserver can be set to a URI where the latest version of the - // key that this signature is made over can be found. See RFC 9580, section - // 5.2.3.26 for details. - PreferredKeyserver string - - // PolicyURI can be set to the URI of a document that describes the - // policy under which the signature was issued. See RFC 9580, section - // 5.2.3.28 for details. - PolicyURI string - - // FlagsValid is set if any flags were given. See RFC 9580, section - // 5.2.3.29 for details. - FlagsValid bool - FlagCertify, FlagSign, FlagEncryptCommunications, FlagEncryptStorage, FlagSplitKey, FlagAuthenticate, FlagGroupKey bool - - // RevocationReason is set if this signature has been revoked. - // See RFC 9580, section 5.2.3.31 for details. - RevocationReason *ReasonForRevocation - RevocationReasonText string - - // In a self-signature, these flags are set there is a features subpacket - // indicating that the issuer implementation supports these features - // see https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#features-subpacket - SEIPDv1, SEIPDv2 bool - - // EmbeddedSignature, if non-nil, is a signature of the parent key, by - // this key. This prevents an attacker from claiming another's signing - // subkey as their own. - EmbeddedSignature *Signature - - outSubpackets []outputSubpacket -} - -// VerifiableSignature internally keeps state if the -// the signature has been verified before. -type VerifiableSignature struct { - Valid *bool // nil if it has not been verified yet - Packet *Signature -} - -// NewVerifiableSig returns a struct of type VerifiableSignature referencing the input signature. -func NewVerifiableSig(signature *Signature) *VerifiableSignature { - return &VerifiableSignature{ - Packet: signature, - } -} - -// Salt returns the signature salt for v6 signatures. -func (sig *Signature) Salt() []byte { - if sig == nil { - return nil - } - return sig.salt -} - -func (sig *Signature) parse(r io.Reader) (err error) { - // RFC 9580, section 5.2.3 - var buf [7]byte - _, err = readFull(r, buf[:1]) - if err != nil { - return - } - sig.Version = int(buf[0]) - if sig.Version != 4 && sig.Version != 5 && sig.Version != 6 { - err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0]))) - return - } - - if V5Disabled && sig.Version == 5 { - return errors.UnsupportedError("support for parsing v5 entities is disabled; build with `-tags v5` if needed") - } - - if sig.Version == 6 { - _, err = readFull(r, buf[:7]) - } else { - _, err = readFull(r, buf[:5]) - } - if err != nil { - return - } - sig.SigType = SignatureType(buf[0]) - sig.PubKeyAlgo = PublicKeyAlgorithm(buf[1]) - switch sig.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA, PubKeyAlgoEdDSA, PubKeyAlgoEd25519, PubKeyAlgoEd448: - default: - err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo))) - return - } - - var ok bool - - if sig.Version < 5 { - sig.Hash, ok = algorithm.HashIdToHashWithSha1(buf[2]) - } else { - sig.Hash, ok = algorithm.HashIdToHash(buf[2]) - } - - if !ok { - return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2]))) - } - - var hashedSubpacketsLength int - if sig.Version == 6 { - // For a v6 signature, a four-octet length is used. - hashedSubpacketsLength = - int(buf[3])<<24 | - int(buf[4])<<16 | - int(buf[5])<<8 | - int(buf[6]) - } else { - hashedSubpacketsLength = int(buf[3])<<8 | int(buf[4]) - } - hashedSubpackets := make([]byte, hashedSubpacketsLength) - _, err = readFull(r, hashedSubpackets) - if err != nil { - return - } - err = sig.buildHashSuffix(hashedSubpackets) - if err != nil { - return - } - - err = parseSignatureSubpackets(sig, hashedSubpackets, true) - if err != nil { - return - } - - if sig.Version == 6 { - _, err = readFull(r, buf[:4]) - } else { - _, err = readFull(r, buf[:2]) - } - - if err != nil { - return - } - var unhashedSubpacketsLength uint32 - if sig.Version == 6 { - unhashedSubpacketsLength = uint32(buf[0])<<24 | uint32(buf[1])<<16 | uint32(buf[2])<<8 | uint32(buf[3]) - } else { - unhashedSubpacketsLength = uint32(buf[0])<<8 | uint32(buf[1]) - } - unhashedSubpackets := make([]byte, unhashedSubpacketsLength) - _, err = readFull(r, unhashedSubpackets) - if err != nil { - return - } - err = parseSignatureSubpackets(sig, unhashedSubpackets, false) - if err != nil { - return - } - - _, err = readFull(r, sig.HashTag[:2]) - if err != nil { - return - } - - if sig.Version == 6 { - // Only for v6 signatures, a variable-length field containing the salt - _, err = readFull(r, buf[:1]) - if err != nil { - return - } - saltLength := int(buf[0]) - var expectedSaltLength int - expectedSaltLength, err = SaltLengthForHash(sig.Hash) - if err != nil { - return - } - if saltLength != expectedSaltLength { - err = errors.StructuralError("unexpected salt size for the given hash algorithm") - return - } - salt := make([]byte, expectedSaltLength) - _, err = readFull(r, salt) - if err != nil { - return - } - sig.salt = salt - } - - switch sig.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: - sig.RSASignature = new(encoding.MPI) - _, err = sig.RSASignature.ReadFrom(r) - case PubKeyAlgoDSA: - sig.DSASigR = new(encoding.MPI) - if _, err = sig.DSASigR.ReadFrom(r); err != nil { - return - } - - sig.DSASigS = new(encoding.MPI) - _, err = sig.DSASigS.ReadFrom(r) - case PubKeyAlgoECDSA: - sig.ECDSASigR = new(encoding.MPI) - if _, err = sig.ECDSASigR.ReadFrom(r); err != nil { - return - } - - sig.ECDSASigS = new(encoding.MPI) - _, err = sig.ECDSASigS.ReadFrom(r) - case PubKeyAlgoEdDSA: - sig.EdDSASigR = new(encoding.MPI) - if _, err = sig.EdDSASigR.ReadFrom(r); err != nil { - return - } - - sig.EdDSASigS = new(encoding.MPI) - if _, err = sig.EdDSASigS.ReadFrom(r); err != nil { - return - } - case PubKeyAlgoEd25519: - sig.EdSig, err = ed25519.ReadSignature(r) - if err != nil { - return - } - case PubKeyAlgoEd448: - sig.EdSig, err = ed448.ReadSignature(r) - if err != nil { - return - } - default: - panic("unreachable") - } - return -} - -// parseSignatureSubpackets parses subpackets of the main signature packet. See -// RFC 9580, section 5.2.3.1. -func parseSignatureSubpackets(sig *Signature, subpackets []byte, isHashed bool) (err error) { - for len(subpackets) > 0 { - subpackets, err = parseSignatureSubpacket(sig, subpackets, isHashed) - if err != nil { - return - } - } - - if sig.CreationTime.IsZero() { - err = errors.StructuralError("no creation time in signature") - } - - return -} - -type signatureSubpacketType uint8 - -const ( - creationTimeSubpacket signatureSubpacketType = 2 - signatureExpirationSubpacket signatureSubpacketType = 3 - exportableCertSubpacket signatureSubpacketType = 4 - trustSubpacket signatureSubpacketType = 5 - regularExpressionSubpacket signatureSubpacketType = 6 - keyExpirationSubpacket signatureSubpacketType = 9 - prefSymmetricAlgosSubpacket signatureSubpacketType = 11 - issuerSubpacket signatureSubpacketType = 16 - notationDataSubpacket signatureSubpacketType = 20 - prefHashAlgosSubpacket signatureSubpacketType = 21 - prefCompressionSubpacket signatureSubpacketType = 22 - keyserverPrefsSubpacket signatureSubpacketType = 23 - prefKeyserverSubpacket signatureSubpacketType = 24 - primaryUserIdSubpacket signatureSubpacketType = 25 - policyUriSubpacket signatureSubpacketType = 26 - keyFlagsSubpacket signatureSubpacketType = 27 - signerUserIdSubpacket signatureSubpacketType = 28 - reasonForRevocationSubpacket signatureSubpacketType = 29 - featuresSubpacket signatureSubpacketType = 30 - embeddedSignatureSubpacket signatureSubpacketType = 32 - issuerFingerprintSubpacket signatureSubpacketType = 33 - intendedRecipientSubpacket signatureSubpacketType = 35 - prefCipherSuitesSubpacket signatureSubpacketType = 39 -) - -// parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1. -func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (rest []byte, err error) { - // RFC 9580, section 5.2.3.7 - var ( - length uint32 - packetType signatureSubpacketType - isCritical bool - ) - if len(subpacket) == 0 { - err = errors.StructuralError("zero length signature subpacket") - return - } - switch { - case subpacket[0] < 192: - length = uint32(subpacket[0]) - subpacket = subpacket[1:] - case subpacket[0] < 255: - if len(subpacket) < 2 { - goto Truncated - } - length = uint32(subpacket[0]-192)<<8 + uint32(subpacket[1]) + 192 - subpacket = subpacket[2:] - default: - if len(subpacket) < 5 { - goto Truncated - } - length = uint32(subpacket[1])<<24 | - uint32(subpacket[2])<<16 | - uint32(subpacket[3])<<8 | - uint32(subpacket[4]) - subpacket = subpacket[5:] - } - if length > uint32(len(subpacket)) { - goto Truncated - } - rest = subpacket[length:] - subpacket = subpacket[:length] - if len(subpacket) == 0 { - err = errors.StructuralError("zero length signature subpacket") - return - } - packetType = signatureSubpacketType(subpacket[0] & 0x7f) - isCritical = subpacket[0]&0x80 == 0x80 - subpacket = subpacket[1:] - sig.rawSubpackets = append(sig.rawSubpackets, outputSubpacket{isHashed, packetType, isCritical, subpacket}) - if !isHashed && - packetType != issuerSubpacket && - packetType != issuerFingerprintSubpacket && - packetType != embeddedSignatureSubpacket { - return - } - switch packetType { - case creationTimeSubpacket: - if len(subpacket) != 4 { - err = errors.StructuralError("signature creation time not four bytes") - return - } - t := binary.BigEndian.Uint32(subpacket) - sig.CreationTime = time.Unix(int64(t), 0) - case signatureExpirationSubpacket: - // Signature expiration time, section 5.2.3.18 - if len(subpacket) != 4 { - err = errors.StructuralError("expiration subpacket with bad length") - return - } - sig.SigLifetimeSecs = new(uint32) - *sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket) - case exportableCertSubpacket: - if subpacket[0] == 0 { - err = errors.UnsupportedError("signature with non-exportable certification") - return - } - case trustSubpacket: - if len(subpacket) != 2 { - err = errors.StructuralError("trust subpacket with bad length") - return - } - // Trust level and amount, section 5.2.3.21 - sig.TrustLevel = TrustLevel(subpacket[0]) - sig.TrustAmount = TrustAmount(subpacket[1]) - case regularExpressionSubpacket: - if len(subpacket) == 0 { - err = errors.StructuralError("regexp subpacket with bad length") - return - } - // Trust regular expression, section 5.2.3.22 - // RFC specifies the string should be null-terminated; remove a null byte from the end - if subpacket[len(subpacket)-1] != 0x00 { - err = errors.StructuralError("expected regular expression to be null-terminated") - return - } - trustRegularExpression := string(subpacket[:len(subpacket)-1]) - sig.TrustRegularExpression = &trustRegularExpression - case keyExpirationSubpacket: - // Key expiration time, section 5.2.3.13 - if len(subpacket) != 4 { - err = errors.StructuralError("key expiration subpacket with bad length") - return - } - sig.KeyLifetimeSecs = new(uint32) - *sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket) - case prefSymmetricAlgosSubpacket: - // Preferred symmetric algorithms, section 5.2.3.14 - sig.PreferredSymmetric = make([]byte, len(subpacket)) - copy(sig.PreferredSymmetric, subpacket) - case issuerSubpacket: - // Issuer, section 5.2.3.12 - if sig.Version > 4 && isHashed { - err = errors.StructuralError("issuer subpacket found in v6 key") - return - } - if len(subpacket) != 8 { - err = errors.StructuralError("issuer subpacket with bad length") - return - } - if sig.Version <= 4 { - sig.IssuerKeyId = new(uint64) - *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket) - } - case notationDataSubpacket: - // Notation data, section 5.2.3.24 - if len(subpacket) < 8 { - err = errors.StructuralError("notation data subpacket with bad length") - return - } - - nameLength := uint32(subpacket[4])<<8 | uint32(subpacket[5]) - valueLength := uint32(subpacket[6])<<8 | uint32(subpacket[7]) - if len(subpacket) != int(nameLength)+int(valueLength)+8 { - err = errors.StructuralError("notation data subpacket with bad length") - return - } - - notation := Notation{ - IsHumanReadable: (subpacket[0] & 0x80) == 0x80, - Name: string(subpacket[8:(nameLength + 8)]), - Value: subpacket[(nameLength + 8):(valueLength + nameLength + 8)], - IsCritical: isCritical, - } - - sig.Notations = append(sig.Notations, ¬ation) - case prefHashAlgosSubpacket: - // Preferred hash algorithms, section 5.2.3.16 - sig.PreferredHash = make([]byte, len(subpacket)) - copy(sig.PreferredHash, subpacket) - case prefCompressionSubpacket: - // Preferred compression algorithms, section 5.2.3.17 - sig.PreferredCompression = make([]byte, len(subpacket)) - copy(sig.PreferredCompression, subpacket) - case keyserverPrefsSubpacket: - // Keyserver preferences, section 5.2.3.25 - sig.KeyserverPrefsValid = true - if len(subpacket) == 0 { - return - } - if subpacket[0]&KeyserverPrefNoModify != 0 { - sig.KeyserverPrefNoModify = true - } - case prefKeyserverSubpacket: - // Preferred keyserver, section 5.2.3.26 - sig.PreferredKeyserver = string(subpacket) - case primaryUserIdSubpacket: - // Primary User ID, section 5.2.3.27 - if len(subpacket) != 1 { - err = errors.StructuralError("primary user id subpacket with bad length") - return - } - sig.IsPrimaryId = new(bool) - if subpacket[0] > 0 { - *sig.IsPrimaryId = true - } - case keyFlagsSubpacket: - // Key flags, section 5.2.3.29 - sig.FlagsValid = true - if len(subpacket) == 0 { - return - } - if subpacket[0]&KeyFlagCertify != 0 { - sig.FlagCertify = true - } - if subpacket[0]&KeyFlagSign != 0 { - sig.FlagSign = true - } - if subpacket[0]&KeyFlagEncryptCommunications != 0 { - sig.FlagEncryptCommunications = true - } - if subpacket[0]&KeyFlagEncryptStorage != 0 { - sig.FlagEncryptStorage = true - } - if subpacket[0]&KeyFlagSplitKey != 0 { - sig.FlagSplitKey = true - } - if subpacket[0]&KeyFlagAuthenticate != 0 { - sig.FlagAuthenticate = true - } - if subpacket[0]&KeyFlagGroupKey != 0 { - sig.FlagGroupKey = true - } - case signerUserIdSubpacket: - userId := string(subpacket) - sig.SignerUserId = &userId - case reasonForRevocationSubpacket: - // Reason For Revocation, section 5.2.3.31 - if len(subpacket) == 0 { - err = errors.StructuralError("empty revocation reason subpacket") - return - } - sig.RevocationReason = new(ReasonForRevocation) - *sig.RevocationReason = NewReasonForRevocation(subpacket[0]) - sig.RevocationReasonText = string(subpacket[1:]) - case featuresSubpacket: - // Features subpacket, section 5.2.3.32 specifies a very general - // mechanism for OpenPGP implementations to signal support for new - // features. - if len(subpacket) > 0 { - if subpacket[0]&0x01 != 0 { - sig.SEIPDv1 = true - } - // 0x02 and 0x04 are reserved - if subpacket[0]&0x08 != 0 { - sig.SEIPDv2 = true - } - } - case embeddedSignatureSubpacket: - // Only usage is in signatures that cross-certify - // signing subkeys. section 5.2.3.34 describes the - // format, with its usage described in section 11.1 - if sig.EmbeddedSignature != nil { - err = errors.StructuralError("Cannot have multiple embedded signatures") - return - } - sig.EmbeddedSignature = new(Signature) - if err := sig.EmbeddedSignature.parse(bytes.NewBuffer(subpacket)); err != nil { - return nil, err - } - if sigType := sig.EmbeddedSignature.SigType; sigType != SigTypePrimaryKeyBinding { - return nil, errors.StructuralError("cross-signature has unexpected type " + strconv.Itoa(int(sigType))) - } - case policyUriSubpacket: - // Policy URI, section 5.2.3.28 - sig.PolicyURI = string(subpacket) - case issuerFingerprintSubpacket: - if len(subpacket) == 0 { - err = errors.StructuralError("empty issuer fingerprint subpacket") - return - } - v, l := subpacket[0], len(subpacket[1:]) - if v >= 5 && l != 32 || v < 5 && l != 20 { - return nil, errors.StructuralError("bad fingerprint length") - } - sig.IssuerFingerprint = make([]byte, l) - copy(sig.IssuerFingerprint, subpacket[1:]) - sig.IssuerKeyId = new(uint64) - if v >= 5 { - *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket[1:9]) - } else { - *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket[13:21]) - } - case intendedRecipientSubpacket: - // Intended Recipient Fingerprint, section 5.2.3.36 - if len(subpacket) < 1 { - return nil, errors.StructuralError("invalid intended recipient fingerpring length") - } - version, length := subpacket[0], len(subpacket[1:]) - if version >= 5 && length != 32 || version < 5 && length != 20 { - return nil, errors.StructuralError("invalid fingerprint length") - } - fingerprint := make([]byte, length) - copy(fingerprint, subpacket[1:]) - sig.IntendedRecipients = append(sig.IntendedRecipients, &Recipient{int(version), fingerprint}) - case prefCipherSuitesSubpacket: - // Preferred AEAD cipher suites, section 5.2.3.15 - if len(subpacket)%2 != 0 { - err = errors.StructuralError("invalid aead cipher suite length") - return - } - - sig.PreferredCipherSuites = make([][2]byte, len(subpacket)/2) - - for i := 0; i < len(subpacket)/2; i++ { - sig.PreferredCipherSuites[i] = [2]uint8{subpacket[2*i], subpacket[2*i+1]} - } - default: - if isCritical { - err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType))) - return - } - } - return - -Truncated: - err = errors.StructuralError("signature subpacket truncated") - return -} - -// subpacketLengthLength returns the length, in bytes, of an encoded length value. -func subpacketLengthLength(length int) int { - if length < 192 { - return 1 - } - if length < 16320 { - return 2 - } - return 5 -} - -func (sig *Signature) CheckKeyIdOrFingerprint(pk *PublicKey) bool { - if sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) >= 20 { - return bytes.Equal(sig.IssuerFingerprint, pk.Fingerprint) - } - return sig.IssuerKeyId != nil && *sig.IssuerKeyId == pk.KeyId -} - -func (sig *Signature) CheckKeyIdOrFingerprintExplicit(fingerprint []byte, keyId uint64) bool { - if sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) >= 20 && fingerprint != nil { - return bytes.Equal(sig.IssuerFingerprint, fingerprint) - } - return sig.IssuerKeyId != nil && *sig.IssuerKeyId == keyId -} - -// serializeSubpacketLength marshals the given length into to. -func serializeSubpacketLength(to []byte, length int) int { - // RFC 9580, Section 4.2.1. - if length < 192 { - to[0] = byte(length) - return 1 - } - if length < 16320 { - length -= 192 - to[0] = byte((length >> 8) + 192) - to[1] = byte(length) - return 2 - } - to[0] = 255 - to[1] = byte(length >> 24) - to[2] = byte(length >> 16) - to[3] = byte(length >> 8) - to[4] = byte(length) - return 5 -} - -// subpacketsLength returns the serialized length, in bytes, of the given -// subpackets. -func subpacketsLength(subpackets []outputSubpacket, hashed bool) (length int) { - for _, subpacket := range subpackets { - if subpacket.hashed == hashed { - length += subpacketLengthLength(len(subpacket.contents) + 1) - length += 1 // type byte - length += len(subpacket.contents) - } - } - return -} - -// serializeSubpackets marshals the given subpackets into to. -func serializeSubpackets(to []byte, subpackets []outputSubpacket, hashed bool) { - for _, subpacket := range subpackets { - if subpacket.hashed == hashed { - n := serializeSubpacketLength(to, len(subpacket.contents)+1) - to[n] = byte(subpacket.subpacketType) - if subpacket.isCritical { - to[n] |= 0x80 - } - to = to[1+n:] - n = copy(to, subpacket.contents) - to = to[n:] - } - } -} - -// SigExpired returns whether sig is a signature that has expired or is created -// in the future. -func (sig *Signature) SigExpired(currentTime time.Time) bool { - if sig.CreationTime.Unix() > currentTime.Unix() { - return true - } - if sig.SigLifetimeSecs == nil || *sig.SigLifetimeSecs == 0 { - return false - } - expiry := sig.CreationTime.Add(time.Duration(*sig.SigLifetimeSecs) * time.Second) - return currentTime.Unix() > expiry.Unix() -} - -// buildHashSuffix constructs the HashSuffix member of sig in preparation for signing. -func (sig *Signature) buildHashSuffix(hashedSubpackets []byte) (err error) { - var hashId byte - var ok bool - - if sig.Version < 5 { - hashId, ok = algorithm.HashToHashIdWithSha1(sig.Hash) - } else { - hashId, ok = algorithm.HashToHashId(sig.Hash) - } - - if !ok { - sig.HashSuffix = nil - return errors.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash))) - } - - hashedFields := bytes.NewBuffer([]byte{ - uint8(sig.Version), - uint8(sig.SigType), - uint8(sig.PubKeyAlgo), - uint8(hashId), - }) - hashedSubpacketsLength := len(hashedSubpackets) - if sig.Version == 6 { - // v6 signatures store the length in 4 octets - hashedFields.Write([]byte{ - uint8(hashedSubpacketsLength >> 24), - uint8(hashedSubpacketsLength >> 16), - uint8(hashedSubpacketsLength >> 8), - uint8(hashedSubpacketsLength), - }) - } else { - hashedFields.Write([]byte{ - uint8(hashedSubpacketsLength >> 8), - uint8(hashedSubpacketsLength), - }) - } - lenPrefix := hashedFields.Len() - hashedFields.Write(hashedSubpackets) - - var l uint64 = uint64(lenPrefix + len(hashedSubpackets)) - if sig.Version == 5 { - // v5 case - hashedFields.Write([]byte{0x05, 0xff}) - hashedFields.Write([]byte{ - uint8(l >> 56), uint8(l >> 48), uint8(l >> 40), uint8(l >> 32), - uint8(l >> 24), uint8(l >> 16), uint8(l >> 8), uint8(l), - }) - } else { - // v4 and v6 case - hashedFields.Write([]byte{byte(sig.Version), 0xff}) - hashedFields.Write([]byte{ - uint8(l >> 24), uint8(l >> 16), uint8(l >> 8), uint8(l), - }) - } - sig.HashSuffix = make([]byte, hashedFields.Len()) - copy(sig.HashSuffix, hashedFields.Bytes()) - return -} - -func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) { - hashedSubpacketsLen := subpacketsLength(sig.outSubpackets, true) - hashedSubpackets := make([]byte, hashedSubpacketsLen) - serializeSubpackets(hashedSubpackets, sig.outSubpackets, true) - err = sig.buildHashSuffix(hashedSubpackets) - if err != nil { - return - } - if sig.Version == 5 && (sig.SigType == 0x00 || sig.SigType == 0x01) { - sig.AddMetadataToHashSuffix() - } - - h.Write(sig.HashSuffix) - digest = h.Sum(nil) - copy(sig.HashTag[:], digest) - return -} - -// PrepareSign must be called to create a hash object before Sign for v6 signatures. -// The created hash object initially hashes a randomly generated salt -// as required by v6 signatures. The generated salt is stored in sig. If the signature is not v6, -// the method returns an empty hash object. -// See RFC 9580 Section 5.2.4. -func (sig *Signature) PrepareSign(config *Config) (hash.Hash, error) { - if !sig.Hash.Available() { - return nil, errors.UnsupportedError("hash function") - } - hasher := sig.Hash.New() - if sig.Version == 6 { - if sig.salt == nil { - var err error - sig.salt, err = SignatureSaltForHash(sig.Hash, config.Random()) - if err != nil { - return nil, err - } - } - hasher.Write(sig.salt) - } - return hasher, nil -} - -// SetSalt sets the signature salt for v6 signatures. -// Assumes salt is generated correctly and checks if length matches. -// If the signature is not v6, the method ignores the salt. -// Use PrepareSign whenever possible instead of generating and -// hashing the salt externally. -// See RFC 9580 Section 5.2.4. -func (sig *Signature) SetSalt(salt []byte) error { - if sig.Version == 6 { - expectedSaltLength, err := SaltLengthForHash(sig.Hash) - if err != nil { - return err - } - if salt == nil || len(salt) != expectedSaltLength { - return errors.InvalidArgumentError("unexpected salt size for the given hash algorithm") - } - sig.salt = salt - } - return nil -} - -// PrepareVerify must be called to create a hash object before verifying v6 signatures. -// The created hash object initially hashes the internally stored salt. -// If the signature is not v6, the method returns an empty hash object. -// See RFC 9580 Section 5.2.4. -func (sig *Signature) PrepareVerify() (hash.Hash, error) { - if !sig.Hash.Available() { - return nil, errors.UnsupportedError("hash function") - } - hasher := sig.Hash.New() - if sig.Version == 6 { - if sig.salt == nil { - return nil, errors.StructuralError("v6 requires a salt for the hash to be signed") - } - hasher.Write(sig.salt) - } - return hasher, nil -} - -// Sign signs a message with a private key. The hash, h, must contain -// the hash of the message to be signed and will be mutated by this function. -// On success, the signature is stored in sig. Call Serialize to write it out. -// If config is nil, sensible defaults will be used. -func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err error) { - if priv.Dummy() { - return errors.ErrDummyPrivateKey("dummy key found") - } - sig.Version = priv.PublicKey.Version - sig.IssuerFingerprint = priv.PublicKey.Fingerprint - if sig.Version < 6 && config.RandomizeSignaturesViaNotation() { - sig.removeNotationsWithName(SaltNotationName) - salt, err := SignatureSaltForHash(sig.Hash, config.Random()) - if err != nil { - return err - } - notation := Notation{ - Name: SaltNotationName, - Value: salt, - IsCritical: false, - IsHumanReadable: false, - } - sig.Notations = append(sig.Notations, ¬ation) - } - sig.outSubpackets, err = sig.buildSubpackets(priv.PublicKey) - if err != nil { - return err - } - digest, err := sig.signPrepareHash(h) - if err != nil { - return - } - switch priv.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: - // supports both *rsa.PrivateKey and crypto.Signer - sigdata, err := priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, sig.Hash) - if err == nil { - sig.RSASignature = encoding.NewMPI(sigdata) - } - case PubKeyAlgoDSA: - dsaPriv := priv.PrivateKey.(*dsa.PrivateKey) - - // Need to truncate hashBytes to match FIPS 186-3 section 4.6. - subgroupSize := (dsaPriv.Q.BitLen() + 7) / 8 - if len(digest) > subgroupSize { - digest = digest[:subgroupSize] - } - r, s, err := dsa.Sign(config.Random(), dsaPriv, digest) - if err == nil { - sig.DSASigR = new(encoding.MPI).SetBig(r) - sig.DSASigS = new(encoding.MPI).SetBig(s) - } - case PubKeyAlgoECDSA: - var r, s *big.Int - if sk, ok := priv.PrivateKey.(*ecdsa.PrivateKey); ok { - r, s, err = ecdsa.Sign(config.Random(), sk, digest) - } else { - var b []byte - b, err = priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, sig.Hash) - if err == nil { - r, s, err = unwrapECDSASig(b) - } - } - - if err == nil { - sig.ECDSASigR = new(encoding.MPI).SetBig(r) - sig.ECDSASigS = new(encoding.MPI).SetBig(s) - } - case PubKeyAlgoEdDSA: - sk := priv.PrivateKey.(*eddsa.PrivateKey) - r, s, err := eddsa.Sign(sk, digest) - if err == nil { - sig.EdDSASigR = encoding.NewMPI(r) - sig.EdDSASigS = encoding.NewMPI(s) - } - case PubKeyAlgoEd25519: - sk := priv.PrivateKey.(*ed25519.PrivateKey) - signature, err := ed25519.Sign(sk, digest) - if err == nil { - sig.EdSig = signature - } - case PubKeyAlgoEd448: - sk := priv.PrivateKey.(*ed448.PrivateKey) - signature, err := ed448.Sign(sk, digest) - if err == nil { - sig.EdSig = signature - } - default: - err = errors.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo))) - } - - return -} - -// unwrapECDSASig parses the two integer components of an ASN.1-encoded ECDSA signature. -func unwrapECDSASig(b []byte) (r, s *big.Int, err error) { - var ecsdaSig struct { - R, S *big.Int - } - _, err = asn1.Unmarshal(b, &ecsdaSig) - if err != nil { - return - } - return ecsdaSig.R, ecsdaSig.S, nil -} - -// SignUserId computes a signature from priv, asserting that pub is a valid -// key for the identity id. On success, the signature is stored in sig. Call -// Serialize to write it out. -// If config is nil, sensible defaults will be used. -func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, config *Config) error { - if priv.Dummy() { - return errors.ErrDummyPrivateKey("dummy key found") - } - prepareHash, err := sig.PrepareSign(config) - if err != nil { - return err - } - if err := userIdSignatureHash(id, pub, prepareHash); err != nil { - return err - } - return sig.Sign(prepareHash, priv, config) -} - -// SignDirectKeyBinding computes a signature from priv -// On success, the signature is stored in sig. -// Call Serialize to write it out. -// If config is nil, sensible defaults will be used. -func (sig *Signature) SignDirectKeyBinding(pub *PublicKey, priv *PrivateKey, config *Config) error { - if priv.Dummy() { - return errors.ErrDummyPrivateKey("dummy key found") - } - prepareHash, err := sig.PrepareSign(config) - if err != nil { - return err - } - if err := directKeySignatureHash(pub, prepareHash); err != nil { - return err - } - return sig.Sign(prepareHash, priv, config) -} - -// CrossSignKey computes a signature from signingKey on pub hashed using hashKey. On success, -// the signature is stored in sig. Call Serialize to write it out. -// If config is nil, sensible defaults will be used. -func (sig *Signature) CrossSignKey(pub *PublicKey, hashKey *PublicKey, signingKey *PrivateKey, - config *Config) error { - prepareHash, err := sig.PrepareSign(config) - if err != nil { - return err - } - h, err := keySignatureHash(hashKey, pub, prepareHash) - if err != nil { - return err - } - return sig.Sign(h, signingKey, config) -} - -// SignKey computes a signature from priv, asserting that pub is a subkey. On -// success, the signature is stored in sig. Call Serialize to write it out. -// If config is nil, sensible defaults will be used. -func (sig *Signature) SignKey(pub *PublicKey, priv *PrivateKey, config *Config) error { - if priv.Dummy() { - return errors.ErrDummyPrivateKey("dummy key found") - } - prepareHash, err := sig.PrepareSign(config) - if err != nil { - return err - } - h, err := keySignatureHash(&priv.PublicKey, pub, prepareHash) - if err != nil { - return err - } - return sig.Sign(h, priv, config) -} - -// RevokeKey computes a revocation signature of pub using priv. On success, the signature is -// stored in sig. Call Serialize to write it out. -// If config is nil, sensible defaults will be used. -func (sig *Signature) RevokeKey(pub *PublicKey, priv *PrivateKey, config *Config) error { - prepareHash, err := sig.PrepareSign(config) - if err != nil { - return err - } - if err := keyRevocationHash(pub, prepareHash); err != nil { - return err - } - return sig.Sign(prepareHash, priv, config) -} - -// RevokeSubkey computes a subkey revocation signature of pub using priv. -// On success, the signature is stored in sig. Call Serialize to write it out. -// If config is nil, sensible defaults will be used. -func (sig *Signature) RevokeSubkey(pub *PublicKey, priv *PrivateKey, config *Config) error { - // Identical to a subkey binding signature - return sig.SignKey(pub, priv, config) -} - -// Serialize marshals sig to w. Sign, SignUserId or SignKey must have been -// called first. -func (sig *Signature) Serialize(w io.Writer) (err error) { - if len(sig.outSubpackets) == 0 { - sig.outSubpackets = sig.rawSubpackets - } - if sig.RSASignature == nil && sig.DSASigR == nil && sig.ECDSASigR == nil && sig.EdDSASigR == nil && sig.EdSig == nil { - return errors.InvalidArgumentError("Signature: need to call Sign, SignUserId or SignKey before Serialize") - } - - sigLength := 0 - switch sig.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: - sigLength = int(sig.RSASignature.EncodedLength()) - case PubKeyAlgoDSA: - sigLength = int(sig.DSASigR.EncodedLength()) - sigLength += int(sig.DSASigS.EncodedLength()) - case PubKeyAlgoECDSA: - sigLength = int(sig.ECDSASigR.EncodedLength()) - sigLength += int(sig.ECDSASigS.EncodedLength()) - case PubKeyAlgoEdDSA: - sigLength = int(sig.EdDSASigR.EncodedLength()) - sigLength += int(sig.EdDSASigS.EncodedLength()) - case PubKeyAlgoEd25519: - sigLength = ed25519.SignatureSize - case PubKeyAlgoEd448: - sigLength = ed448.SignatureSize - default: - panic("impossible") - } - - hashedSubpacketsLen := subpacketsLength(sig.outSubpackets, true) - unhashedSubpacketsLen := subpacketsLength(sig.outSubpackets, false) - length := 4 + /* length of version|signature type|public-key algorithm|hash algorithm */ - 2 /* length of hashed subpackets */ + hashedSubpacketsLen + - 2 /* length of unhashed subpackets */ + unhashedSubpacketsLen + - 2 /* hash tag */ + sigLength - if sig.Version == 6 { - length += 4 + /* the two length fields are four-octet instead of two */ - 1 + /* salt length */ - len(sig.salt) /* length salt */ - } - err = serializeHeader(w, packetTypeSignature, length) - if err != nil { - return - } - err = sig.serializeBody(w) - if err != nil { - return err - } - return -} - -func (sig *Signature) serializeBody(w io.Writer) (err error) { - var fields []byte - if sig.Version == 6 { - // v6 signatures use 4 octets for length - hashedSubpacketsLen := - uint32(uint32(sig.HashSuffix[4])<<24) | - uint32(uint32(sig.HashSuffix[5])<<16) | - uint32(uint32(sig.HashSuffix[6])<<8) | - uint32(sig.HashSuffix[7]) - fields = sig.HashSuffix[:8+hashedSubpacketsLen] - } else { - hashedSubpacketsLen := uint16(uint16(sig.HashSuffix[4])<<8) | - uint16(sig.HashSuffix[5]) - fields = sig.HashSuffix[:6+hashedSubpacketsLen] - - } - _, err = w.Write(fields) - if err != nil { - return - } - - unhashedSubpacketsLen := subpacketsLength(sig.outSubpackets, false) - var unhashedSubpackets []byte - if sig.Version == 6 { - unhashedSubpackets = make([]byte, 4+unhashedSubpacketsLen) - unhashedSubpackets[0] = byte(unhashedSubpacketsLen >> 24) - unhashedSubpackets[1] = byte(unhashedSubpacketsLen >> 16) - unhashedSubpackets[2] = byte(unhashedSubpacketsLen >> 8) - unhashedSubpackets[3] = byte(unhashedSubpacketsLen) - serializeSubpackets(unhashedSubpackets[4:], sig.outSubpackets, false) - } else { - unhashedSubpackets = make([]byte, 2+unhashedSubpacketsLen) - unhashedSubpackets[0] = byte(unhashedSubpacketsLen >> 8) - unhashedSubpackets[1] = byte(unhashedSubpacketsLen) - serializeSubpackets(unhashedSubpackets[2:], sig.outSubpackets, false) - } - - _, err = w.Write(unhashedSubpackets) - if err != nil { - return - } - _, err = w.Write(sig.HashTag[:]) - if err != nil { - return - } - - if sig.Version == 6 { - // write salt for v6 signatures - _, err = w.Write([]byte{uint8(len(sig.salt))}) - if err != nil { - return - } - _, err = w.Write(sig.salt) - if err != nil { - return - } - } - - switch sig.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: - _, err = w.Write(sig.RSASignature.EncodedBytes()) - case PubKeyAlgoDSA: - if _, err = w.Write(sig.DSASigR.EncodedBytes()); err != nil { - return - } - _, err = w.Write(sig.DSASigS.EncodedBytes()) - case PubKeyAlgoECDSA: - if _, err = w.Write(sig.ECDSASigR.EncodedBytes()); err != nil { - return - } - _, err = w.Write(sig.ECDSASigS.EncodedBytes()) - case PubKeyAlgoEdDSA: - if _, err = w.Write(sig.EdDSASigR.EncodedBytes()); err != nil { - return - } - _, err = w.Write(sig.EdDSASigS.EncodedBytes()) - case PubKeyAlgoEd25519: - err = ed25519.WriteSignature(w, sig.EdSig) - case PubKeyAlgoEd448: - err = ed448.WriteSignature(w, sig.EdSig) - default: - panic("impossible") - } - return -} - -// outputSubpacket represents a subpacket to be marshaled. -type outputSubpacket struct { - hashed bool // true if this subpacket is in the hashed area. - subpacketType signatureSubpacketType - isCritical bool - contents []byte -} - -func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubpacket, err error) { - creationTime := make([]byte, 4) - binary.BigEndian.PutUint32(creationTime, uint32(sig.CreationTime.Unix())) - // Signature Creation Time - subpackets = append(subpackets, outputSubpacket{true, creationTimeSubpacket, true, creationTime}) - // Signature Expiration Time - if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 { - sigLifetime := make([]byte, 4) - binary.BigEndian.PutUint32(sigLifetime, *sig.SigLifetimeSecs) - subpackets = append(subpackets, outputSubpacket{true, signatureExpirationSubpacket, true, sigLifetime}) - } - // Trust Signature - if sig.TrustLevel != 0 { - subpackets = append(subpackets, outputSubpacket{true, trustSubpacket, true, []byte{byte(sig.TrustLevel), byte(sig.TrustAmount)}}) - } - // Regular Expression - if sig.TrustRegularExpression != nil { - // RFC specifies the string should be null-terminated; add a null byte to the end - subpackets = append(subpackets, outputSubpacket{true, regularExpressionSubpacket, true, []byte(*sig.TrustRegularExpression + "\000")}) - } - // Key Expiration Time - if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 { - keyLifetime := make([]byte, 4) - binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs) - subpackets = append(subpackets, outputSubpacket{true, keyExpirationSubpacket, true, keyLifetime}) - } - // Preferred Symmetric Ciphers for v1 SEIPD - if len(sig.PreferredSymmetric) > 0 { - subpackets = append(subpackets, outputSubpacket{true, prefSymmetricAlgosSubpacket, false, sig.PreferredSymmetric}) - } - // Issuer Key ID - if sig.IssuerKeyId != nil && sig.Version == 4 { - keyId := make([]byte, 8) - binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId) - // Note: making this critical breaks RPM <=4.16. - // See: https://github.com/ProtonMail/go-crypto/issues/263 - subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId}) - } - // Notation Data - for _, notation := range sig.Notations { - subpackets = append( - subpackets, - outputSubpacket{ - true, - notationDataSubpacket, - notation.IsCritical, - notation.getData(), - }) - } - // Preferred Hash Algorithms - if len(sig.PreferredHash) > 0 { - subpackets = append(subpackets, outputSubpacket{true, prefHashAlgosSubpacket, false, sig.PreferredHash}) - } - // Preferred Compression Algorithms - if len(sig.PreferredCompression) > 0 { - subpackets = append(subpackets, outputSubpacket{true, prefCompressionSubpacket, false, sig.PreferredCompression}) - } - // Keyserver Preferences - // Keyserver preferences may only appear in self-signatures or certification signatures. - if sig.KeyserverPrefsValid { - var prefs byte - if sig.KeyserverPrefNoModify { - prefs |= KeyserverPrefNoModify - } - subpackets = append(subpackets, outputSubpacket{true, keyserverPrefsSubpacket, false, []byte{prefs}}) - } - // Preferred Keyserver - if len(sig.PreferredKeyserver) > 0 { - subpackets = append(subpackets, outputSubpacket{true, prefKeyserverSubpacket, false, []uint8(sig.PreferredKeyserver)}) - } - // Primary User ID - if sig.IsPrimaryId != nil && *sig.IsPrimaryId { - subpackets = append(subpackets, outputSubpacket{true, primaryUserIdSubpacket, false, []byte{1}}) - } - // Policy URI - if len(sig.PolicyURI) > 0 { - subpackets = append(subpackets, outputSubpacket{true, policyUriSubpacket, false, []uint8(sig.PolicyURI)}) - } - // Key Flags - // Key flags may only appear in self-signatures or certification signatures. - if sig.FlagsValid { - var flags byte - if sig.FlagCertify { - flags |= KeyFlagCertify - } - if sig.FlagSign { - flags |= KeyFlagSign - } - if sig.FlagEncryptCommunications { - flags |= KeyFlagEncryptCommunications - } - if sig.FlagEncryptStorage { - flags |= KeyFlagEncryptStorage - } - if sig.FlagSplitKey { - flags |= KeyFlagSplitKey - } - if sig.FlagAuthenticate { - flags |= KeyFlagAuthenticate - } - if sig.FlagGroupKey { - flags |= KeyFlagGroupKey - } - subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, true, []byte{flags}}) - } - // Signer's User ID - if sig.SignerUserId != nil { - subpackets = append(subpackets, outputSubpacket{true, signerUserIdSubpacket, false, []byte(*sig.SignerUserId)}) - } - // Reason for Revocation - // Revocation reason appears only in revocation signatures and is serialized as per section 5.2.3.31. - if sig.RevocationReason != nil { - subpackets = append(subpackets, outputSubpacket{true, reasonForRevocationSubpacket, true, - append([]uint8{uint8(*sig.RevocationReason)}, []uint8(sig.RevocationReasonText)...)}) - } - // Features - var features = byte(0x00) - if sig.SEIPDv1 { - features |= 0x01 - } - if sig.SEIPDv2 { - features |= 0x08 - } - if features != 0x00 { - subpackets = append(subpackets, outputSubpacket{true, featuresSubpacket, false, []byte{features}}) - } - // Embedded Signature - // EmbeddedSignature appears only in subkeys capable of signing and is serialized as per section 5.2.3.34. - if sig.EmbeddedSignature != nil { - var buf bytes.Buffer - err = sig.EmbeddedSignature.serializeBody(&buf) - if err != nil { - return - } - subpackets = append(subpackets, outputSubpacket{true, embeddedSignatureSubpacket, true, buf.Bytes()}) - } - // Issuer Fingerprint - if sig.IssuerFingerprint != nil { - contents := append([]uint8{uint8(issuer.Version)}, sig.IssuerFingerprint...) - subpackets = append(subpackets, outputSubpacket{true, issuerFingerprintSubpacket, sig.Version >= 5, contents}) - } - // Intended Recipient Fingerprint - for _, recipient := range sig.IntendedRecipients { - subpackets = append( - subpackets, - outputSubpacket{ - true, - intendedRecipientSubpacket, - false, - recipient.Serialize(), - }) - } - // Preferred AEAD Ciphersuites - if len(sig.PreferredCipherSuites) > 0 { - serialized := make([]byte, len(sig.PreferredCipherSuites)*2) - for i, cipherSuite := range sig.PreferredCipherSuites { - serialized[2*i] = cipherSuite[0] - serialized[2*i+1] = cipherSuite[1] - } - subpackets = append(subpackets, outputSubpacket{true, prefCipherSuitesSubpacket, false, serialized}) - } - return -} - -// AddMetadataToHashSuffix modifies the current hash suffix to include metadata -// (format, filename, and time). Version 5 keys protect this data including it -// in the hash computation. See section 5.2.4. -func (sig *Signature) AddMetadataToHashSuffix() { - if sig == nil || sig.Version != 5 { - return - } - if sig.SigType != 0x00 && sig.SigType != 0x01 { - return - } - lit := sig.Metadata - if lit == nil { - // This will translate into six 0x00 bytes. - lit = &LiteralData{} - } - - // Extract the current byte count - n := sig.HashSuffix[len(sig.HashSuffix)-8:] - l := uint64( - uint64(n[0])<<56 | uint64(n[1])<<48 | uint64(n[2])<<40 | uint64(n[3])<<32 | - uint64(n[4])<<24 | uint64(n[5])<<16 | uint64(n[6])<<8 | uint64(n[7])) - - suffix := bytes.NewBuffer(nil) - suffix.Write(sig.HashSuffix[:l]) - - // Add the metadata - var buf [4]byte - buf[0] = lit.Format - fileName := lit.FileName - if len(lit.FileName) > 255 { - fileName = fileName[:255] - } - buf[1] = byte(len(fileName)) - suffix.Write(buf[:2]) - suffix.Write([]byte(lit.FileName)) - binary.BigEndian.PutUint32(buf[:], lit.Time) - suffix.Write(buf[:]) - - suffix.Write([]byte{0x05, 0xff}) - suffix.Write([]byte{ - uint8(l >> 56), uint8(l >> 48), uint8(l >> 40), uint8(l >> 32), - uint8(l >> 24), uint8(l >> 16), uint8(l >> 8), uint8(l), - }) - sig.HashSuffix = suffix.Bytes() -} - -// SaltLengthForHash selects the required salt length for the given hash algorithm, -// as per Table 23 (Hash algorithm registry) of the crypto refresh. -// See RFC 9580 Section 9.5. -func SaltLengthForHash(hash crypto.Hash) (int, error) { - switch hash { - case crypto.SHA256, crypto.SHA224, crypto.SHA3_256: - return 16, nil - case crypto.SHA384: - return 24, nil - case crypto.SHA512, crypto.SHA3_512: - return 32, nil - default: - return 0, errors.UnsupportedError("hash function not supported for V6 signatures") - } -} - -// SignatureSaltForHash generates a random signature salt -// with the length for the given hash algorithm. -// See RFC 9580 Section 9.5. -func SignatureSaltForHash(hash crypto.Hash, randReader io.Reader) ([]byte, error) { - saltLength, err := SaltLengthForHash(hash) - if err != nil { - return nil, err - } - salt := make([]byte, saltLength) - _, err = io.ReadFull(randReader, salt) - if err != nil { - return nil, err - } - return salt, nil -} - -// removeNotationsWithName removes all notations in this signature with the given name. -func (sig *Signature) removeNotationsWithName(name string) { - if sig == nil || sig.Notations == nil { - return - } - updatedNotations := make([]*Notation, 0, len(sig.Notations)) - for _, notation := range sig.Notations { - if notation.Name != name { - updatedNotations = append(updatedNotations, notation) - } - } - sig.Notations = updatedNotations -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetric_key_encrypted.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetric_key_encrypted.go deleted file mode 100644 index 2812a1db88d..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetric_key_encrypted.go +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "bytes" - "crypto/cipher" - "crypto/sha256" - "io" - "strconv" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/s2k" - "golang.org/x/crypto/hkdf" -) - -// This is the largest session key that we'll support. Since at most 256-bit cipher -// is supported in OpenPGP, this is large enough to contain also the auth tag. -const maxSessionKeySizeInBytes = 64 - -// SymmetricKeyEncrypted represents a passphrase protected session key. See RFC -// 4880, section 5.3. -type SymmetricKeyEncrypted struct { - Version int - CipherFunc CipherFunction - Mode AEADMode - s2k func(out, in []byte) - iv []byte - encryptedKey []byte // Contains also the authentication tag for AEAD -} - -// parse parses an SymmetricKeyEncrypted packet as specified in -// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#name-symmetric-key-encrypted-ses -func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error { - var buf [1]byte - - // Version - if _, err := readFull(r, buf[:]); err != nil { - return err - } - ske.Version = int(buf[0]) - if ske.Version != 4 && ske.Version != 5 && ske.Version != 6 { - return errors.UnsupportedError("unknown SymmetricKeyEncrypted version") - } - - if V5Disabled && ske.Version == 5 { - return errors.UnsupportedError("support for parsing v5 entities is disabled; build with `-tags v5` if needed") - } - - if ske.Version > 5 { - // Scalar octet count - if _, err := readFull(r, buf[:]); err != nil { - return err - } - } - - // Cipher function - if _, err := readFull(r, buf[:]); err != nil { - return err - } - ske.CipherFunc = CipherFunction(buf[0]) - if !ske.CipherFunc.IsSupported() { - return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[0]))) - } - - if ske.Version >= 5 { - // AEAD mode - if _, err := readFull(r, buf[:]); err != nil { - return errors.StructuralError("cannot read AEAD octet from packet") - } - ske.Mode = AEADMode(buf[0]) - } - - if ske.Version > 5 { - // Scalar octet count - if _, err := readFull(r, buf[:]); err != nil { - return err - } - } - - var err error - if ske.s2k, err = s2k.Parse(r); err != nil { - if _, ok := err.(errors.ErrDummyPrivateKey); ok { - return errors.UnsupportedError("missing key GNU extension in session key") - } - return err - } - - if ske.Version >= 5 { - // AEAD IV - iv := make([]byte, ske.Mode.IvLength()) - _, err := readFull(r, iv) - if err != nil { - return errors.StructuralError("cannot read AEAD IV") - } - - ske.iv = iv - } - - encryptedKey := make([]byte, maxSessionKeySizeInBytes) - // The session key may follow. We just have to try and read to find - // out. If it exists then we limit it to maxSessionKeySizeInBytes. - n, err := readFull(r, encryptedKey) - if err != nil && err != io.ErrUnexpectedEOF { - return err - } - - if n != 0 { - if n == maxSessionKeySizeInBytes { - return errors.UnsupportedError("oversized encrypted session key") - } - ske.encryptedKey = encryptedKey[:n] - } - return nil -} - -// Decrypt attempts to decrypt an encrypted session key and returns the key and -// the cipher to use when decrypting a subsequent Symmetrically Encrypted Data -// packet. -func (ske *SymmetricKeyEncrypted) Decrypt(passphrase []byte) ([]byte, CipherFunction, error) { - key := make([]byte, ske.CipherFunc.KeySize()) - ske.s2k(key, passphrase) - if len(ske.encryptedKey) == 0 { - return key, ske.CipherFunc, nil - } - switch ske.Version { - case 4: - plaintextKey, cipherFunc, err := ske.decryptV4(key) - return plaintextKey, cipherFunc, err - case 5, 6: - plaintextKey, err := ske.aeadDecrypt(ske.Version, key) - return plaintextKey, CipherFunction(0), err - } - err := errors.UnsupportedError("unknown SymmetricKeyEncrypted version") - return nil, CipherFunction(0), err -} - -func (ske *SymmetricKeyEncrypted) decryptV4(key []byte) ([]byte, CipherFunction, error) { - // the IV is all zeros - iv := make([]byte, ske.CipherFunc.blockSize()) - c := cipher.NewCFBDecrypter(ske.CipherFunc.new(key), iv) - plaintextKey := make([]byte, len(ske.encryptedKey)) - c.XORKeyStream(plaintextKey, ske.encryptedKey) - cipherFunc := CipherFunction(plaintextKey[0]) - if cipherFunc.blockSize() == 0 { - return nil, ske.CipherFunc, errors.UnsupportedError( - "unknown cipher: " + strconv.Itoa(int(cipherFunc))) - } - plaintextKey = plaintextKey[1:] - if len(plaintextKey) != cipherFunc.KeySize() { - return nil, cipherFunc, errors.StructuralError( - "length of decrypted key not equal to cipher keysize") - } - return plaintextKey, cipherFunc, nil -} - -func (ske *SymmetricKeyEncrypted) aeadDecrypt(version int, key []byte) ([]byte, error) { - adata := []byte{0xc3, byte(version), byte(ske.CipherFunc), byte(ske.Mode)} - aead := getEncryptedKeyAeadInstance(ske.CipherFunc, ske.Mode, key, adata, version) - - plaintextKey, err := aead.Open(nil, ske.iv, ske.encryptedKey, adata) - if err != nil { - return nil, err - } - return plaintextKey, nil -} - -// SerializeSymmetricKeyEncrypted serializes a symmetric key packet to w. -// The packet contains a random session key, encrypted by a key derived from -// the given passphrase. The session key is returned and must be passed to -// SerializeSymmetricallyEncrypted. -// If config is nil, sensible defaults will be used. -func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Config) (key []byte, err error) { - cipherFunc := config.Cipher() - - sessionKey := make([]byte, cipherFunc.KeySize()) - _, err = io.ReadFull(config.Random(), sessionKey) - if err != nil { - return - } - - err = SerializeSymmetricKeyEncryptedReuseKey(w, sessionKey, passphrase, config) - if err != nil { - return - } - - key = sessionKey - return -} - -// SerializeSymmetricKeyEncryptedReuseKey serializes a symmetric key packet to w. -// The packet contains the given session key, encrypted by a key derived from -// the given passphrase. The returned session key must be passed to -// SerializeSymmetricallyEncrypted. -// If config is nil, sensible defaults will be used. -// Deprecated: Use SerializeSymmetricKeyEncryptedAEADReuseKey instead. -func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, passphrase []byte, config *Config) (err error) { - return SerializeSymmetricKeyEncryptedAEADReuseKey(w, sessionKey, passphrase, config.AEAD() != nil, config) -} - -// SerializeSymmetricKeyEncryptedAEADReuseKey serializes a symmetric key packet to w. -// The packet contains the given session key, encrypted by a key derived from -// the given passphrase. The returned session key must be passed to -// SerializeSymmetricallyEncrypted. -// If aeadSupported is set, SKESK v6 is used, otherwise v4. -// Note: aeadSupported MUST match the value passed to SerializeSymmetricallyEncrypted. -// If config is nil, sensible defaults will be used. -func SerializeSymmetricKeyEncryptedAEADReuseKey(w io.Writer, sessionKey []byte, passphrase []byte, aeadSupported bool, config *Config) (err error) { - var version int - if aeadSupported { - version = 6 - } else { - version = 4 - } - cipherFunc := config.Cipher() - // cipherFunc must be AES - if !cipherFunc.IsSupported() || cipherFunc < CipherAES128 || cipherFunc > CipherAES256 { - return errors.UnsupportedError("unsupported cipher: " + strconv.Itoa(int(cipherFunc))) - } - - keySize := cipherFunc.KeySize() - s2kBuf := new(bytes.Buffer) - keyEncryptingKey := make([]byte, keySize) - // s2k.Serialize salts and stretches the passphrase, and writes the - // resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf. - err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, config.S2K()) - if err != nil { - return - } - s2kBytes := s2kBuf.Bytes() - - var packetLength int - switch version { - case 4: - packetLength = 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize - case 5, 6: - ivLen := config.AEAD().Mode().IvLength() - tagLen := config.AEAD().Mode().TagLength() - packetLength = 3 + len(s2kBytes) + ivLen + keySize + tagLen - } - if version > 5 { - packetLength += 2 // additional octet count fields - } - - err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength) - if err != nil { - return - } - - // Symmetric Key Encrypted Version - buf := []byte{byte(version)} - - if version > 5 { - // Scalar octet count - buf = append(buf, byte(3+len(s2kBytes)+config.AEAD().Mode().IvLength())) - } - - // Cipher function - buf = append(buf, byte(cipherFunc)) - - if version >= 5 { - // AEAD mode - buf = append(buf, byte(config.AEAD().Mode())) - } - if version > 5 { - // Scalar octet count - buf = append(buf, byte(len(s2kBytes))) - } - _, err = w.Write(buf) - if err != nil { - return - } - _, err = w.Write(s2kBytes) - if err != nil { - return - } - - switch version { - case 4: - iv := make([]byte, cipherFunc.blockSize()) - c := cipher.NewCFBEncrypter(cipherFunc.new(keyEncryptingKey), iv) - encryptedCipherAndKey := make([]byte, keySize+1) - c.XORKeyStream(encryptedCipherAndKey, buf[1:]) - c.XORKeyStream(encryptedCipherAndKey[1:], sessionKey) - _, err = w.Write(encryptedCipherAndKey) - if err != nil { - return - } - case 5, 6: - mode := config.AEAD().Mode() - adata := []byte{0xc3, byte(version), byte(cipherFunc), byte(mode)} - aead := getEncryptedKeyAeadInstance(cipherFunc, mode, keyEncryptingKey, adata, version) - - // Sample iv using random reader - iv := make([]byte, config.AEAD().Mode().IvLength()) - _, err = io.ReadFull(config.Random(), iv) - if err != nil { - return - } - // Seal and write (encryptedData includes auth. tag) - - encryptedData := aead.Seal(nil, iv, sessionKey, adata) - _, err = w.Write(iv) - if err != nil { - return - } - _, err = w.Write(encryptedData) - if err != nil { - return - } - } - - return -} - -func getEncryptedKeyAeadInstance(c CipherFunction, mode AEADMode, inputKey, associatedData []byte, version int) (aead cipher.AEAD) { - var blockCipher cipher.Block - if version > 5 { - hkdfReader := hkdf.New(sha256.New, inputKey, []byte{}, associatedData) - - encryptionKey := make([]byte, c.KeySize()) - _, _ = readFull(hkdfReader, encryptionKey) - - blockCipher = c.new(encryptionKey) - } else { - blockCipher = c.new(inputKey) - } - return mode.new(blockCipher) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted.go deleted file mode 100644 index 0e898742cf0..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "io" - - "github.com/ProtonMail/go-crypto/openpgp/errors" -) - -const aeadSaltSize = 32 - -// SymmetricallyEncrypted represents a symmetrically encrypted byte string. The -// encrypted Contents will consist of more OpenPGP packets. See RFC 4880, -// sections 5.7 and 5.13. -type SymmetricallyEncrypted struct { - Version int - Contents io.Reader // contains tag for version 2 - IntegrityProtected bool // If true it is type 18 (with MDC or AEAD). False is packet type 9 - - // Specific to version 1 - prefix []byte - - // Specific to version 2 - Cipher CipherFunction - Mode AEADMode - ChunkSizeByte byte - Salt [aeadSaltSize]byte -} - -const ( - symmetricallyEncryptedVersionMdc = 1 - symmetricallyEncryptedVersionAead = 2 -) - -func (se *SymmetricallyEncrypted) parse(r io.Reader) error { - if se.IntegrityProtected { - // See RFC 4880, section 5.13. - var buf [1]byte - _, err := readFull(r, buf[:]) - if err != nil { - return err - } - - switch buf[0] { - case symmetricallyEncryptedVersionMdc: - se.Version = symmetricallyEncryptedVersionMdc - case symmetricallyEncryptedVersionAead: - se.Version = symmetricallyEncryptedVersionAead - if err := se.parseAead(r); err != nil { - return err - } - default: - return errors.UnsupportedError("unknown SymmetricallyEncrypted version") - } - } - se.Contents = r - return nil -} - -// Decrypt returns a ReadCloser, from which the decrypted Contents of the -// packet can be read. An incorrect key will only be detected after trying -// to decrypt the entire data. -func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) { - if se.Version == symmetricallyEncryptedVersionAead { - return se.decryptAead(key) - } - - return se.decryptMdc(c, key) -} - -// SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet -// to w and returns a WriteCloser to which the to-be-encrypted packets can be -// written. -// If aeadSupported is set to true, SEIPDv2 is used with the indicated CipherSuite. -// Otherwise, SEIPDv1 is used with the indicated CipherFunction. -// Note: aeadSupported MUST match the value passed to SerializeEncryptedKeyAEAD -// and/or SerializeSymmetricKeyEncryptedAEADReuseKey. -// If config is nil, sensible defaults will be used. -func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, aeadSupported bool, cipherSuite CipherSuite, key []byte, config *Config) (Contents io.WriteCloser, err error) { - writeCloser := noOpCloser{w} - ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedIntegrityProtected) - if err != nil { - return - } - - if aeadSupported { - return serializeSymmetricallyEncryptedAead(ciphertext, cipherSuite, config.AEADConfig.ChunkSizeByte(), config.Random(), key) - } - - return serializeSymmetricallyEncryptedMdc(ciphertext, c, key, config) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_aead.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_aead.go deleted file mode 100644 index 3ddc4fe4a9f..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_aead.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2023 Proton AG. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "crypto/cipher" - "crypto/sha256" - "fmt" - "io" - "strconv" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - "golang.org/x/crypto/hkdf" -) - -// parseAead parses a V2 SEIPD packet (AEAD) as specified in -// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2 -func (se *SymmetricallyEncrypted) parseAead(r io.Reader) error { - headerData := make([]byte, 3) - if n, err := io.ReadFull(r, headerData); n < 3 { - return errors.StructuralError("could not read aead header: " + err.Error()) - } - - // Cipher - se.Cipher = CipherFunction(headerData[0]) - // cipherFunc must have block size 16 to use AEAD - if se.Cipher.blockSize() != 16 { - return errors.UnsupportedError("invalid aead cipher: " + strconv.Itoa(int(se.Cipher))) - } - - // Mode - se.Mode = AEADMode(headerData[1]) - if se.Mode.TagLength() == 0 { - return errors.UnsupportedError("unknown aead mode: " + strconv.Itoa(int(se.Mode))) - } - - // Chunk size - se.ChunkSizeByte = headerData[2] - if se.ChunkSizeByte > 16 { - return errors.UnsupportedError("invalid aead chunk size byte: " + strconv.Itoa(int(se.ChunkSizeByte))) - } - - // Salt - if n, err := io.ReadFull(r, se.Salt[:]); n < aeadSaltSize { - return errors.StructuralError("could not read aead salt: " + err.Error()) - } - - return nil -} - -// associatedData for chunks: tag, version, cipher, mode, chunk size byte -func (se *SymmetricallyEncrypted) associatedData() []byte { - return []byte{ - 0xD2, - symmetricallyEncryptedVersionAead, - byte(se.Cipher), - byte(se.Mode), - se.ChunkSizeByte, - } -} - -// decryptAead decrypts a V2 SEIPD packet (AEAD) as specified in -// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2 -func (se *SymmetricallyEncrypted) decryptAead(inputKey []byte) (io.ReadCloser, error) { - if se.Cipher.KeySize() != len(inputKey) { - return nil, errors.StructuralError(fmt.Sprintf("invalid session key length for cipher: got %d bytes, but expected %d bytes", len(inputKey), se.Cipher.KeySize())) - } - - aead, nonce := getSymmetricallyEncryptedAeadInstance(se.Cipher, se.Mode, inputKey, se.Salt[:], se.associatedData()) - // Carry the first tagLen bytes - chunkSize := decodeAEADChunkSize(se.ChunkSizeByte) - tagLen := se.Mode.TagLength() - chunkBytes := make([]byte, chunkSize+tagLen*2) - peekedBytes := chunkBytes[chunkSize+tagLen:] - n, err := io.ReadFull(se.Contents, peekedBytes) - if n < tagLen || (err != nil && err != io.EOF) { - return nil, errors.StructuralError("not enough data to decrypt:" + err.Error()) - } - - return &aeadDecrypter{ - aeadCrypter: aeadCrypter{ - aead: aead, - chunkSize: decodeAEADChunkSize(se.ChunkSizeByte), - nonce: nonce, - associatedData: se.associatedData(), - chunkIndex: nonce[len(nonce)-8:], - packetTag: packetTypeSymmetricallyEncryptedIntegrityProtected, - }, - reader: se.Contents, - chunkBytes: chunkBytes, - peekedBytes: peekedBytes, - }, nil -} - -// serializeSymmetricallyEncryptedAead encrypts to a writer a V2 SEIPD packet (AEAD) as specified in -// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2 -func serializeSymmetricallyEncryptedAead(ciphertext io.WriteCloser, cipherSuite CipherSuite, chunkSizeByte byte, rand io.Reader, inputKey []byte) (Contents io.WriteCloser, err error) { - // cipherFunc must have block size 16 to use AEAD - if cipherSuite.Cipher.blockSize() != 16 { - return nil, errors.InvalidArgumentError("invalid aead cipher function") - } - - if cipherSuite.Cipher.KeySize() != len(inputKey) { - return nil, errors.InvalidArgumentError("error in aead serialization: bad key length") - } - - // Data for en/decryption: tag, version, cipher, aead mode, chunk size - prefix := []byte{ - 0xD2, - symmetricallyEncryptedVersionAead, - byte(cipherSuite.Cipher), - byte(cipherSuite.Mode), - chunkSizeByte, - } - - // Write header (that correspond to prefix except first byte) - n, err := ciphertext.Write(prefix[1:]) - if err != nil || n < 4 { - return nil, err - } - - // Random salt - salt := make([]byte, aeadSaltSize) - if _, err := io.ReadFull(rand, salt); err != nil { - return nil, err - } - - if _, err := ciphertext.Write(salt); err != nil { - return nil, err - } - - aead, nonce := getSymmetricallyEncryptedAeadInstance(cipherSuite.Cipher, cipherSuite.Mode, inputKey, salt, prefix) - - chunkSize := decodeAEADChunkSize(chunkSizeByte) - tagLen := aead.Overhead() - chunkBytes := make([]byte, chunkSize+tagLen) - return &aeadEncrypter{ - aeadCrypter: aeadCrypter{ - aead: aead, - chunkSize: chunkSize, - associatedData: prefix, - nonce: nonce, - chunkIndex: nonce[len(nonce)-8:], - packetTag: packetTypeSymmetricallyEncryptedIntegrityProtected, - }, - writer: ciphertext, - chunkBytes: chunkBytes, - }, nil -} - -func getSymmetricallyEncryptedAeadInstance(c CipherFunction, mode AEADMode, inputKey, salt, associatedData []byte) (aead cipher.AEAD, nonce []byte) { - hkdfReader := hkdf.New(sha256.New, inputKey, salt, associatedData) - - encryptionKey := make([]byte, c.KeySize()) - _, _ = readFull(hkdfReader, encryptionKey) - - nonce = make([]byte, mode.IvLength()) - - // Last 64 bits of nonce are the counter - _, _ = readFull(hkdfReader, nonce[:len(nonce)-8]) - - blockCipher := c.new(encryptionKey) - aead = mode.new(blockCipher) - - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_mdc.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_mdc.go deleted file mode 100644 index 8b186236849..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_mdc.go +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "crypto/cipher" - "crypto/sha1" - "crypto/subtle" - "hash" - "io" - "strconv" - - "github.com/ProtonMail/go-crypto/openpgp/errors" -) - -// seMdcReader wraps an io.Reader with a no-op Close method. -type seMdcReader struct { - in io.Reader -} - -func (ser seMdcReader) Read(buf []byte) (int, error) { - return ser.in.Read(buf) -} - -func (ser seMdcReader) Close() error { - return nil -} - -func (se *SymmetricallyEncrypted) decryptMdc(c CipherFunction, key []byte) (io.ReadCloser, error) { - if !c.IsSupported() { - return nil, errors.UnsupportedError("unsupported cipher: " + strconv.Itoa(int(c))) - } - - if len(key) != c.KeySize() { - return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length") - } - - if se.prefix == nil { - se.prefix = make([]byte, c.blockSize()+2) - _, err := readFull(se.Contents, se.prefix) - if err != nil { - return nil, err - } - } else if len(se.prefix) != c.blockSize()+2 { - return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths") - } - - ocfbResync := OCFBResync - if se.IntegrityProtected { - // MDC packets use a different form of OCFB mode. - ocfbResync = OCFBNoResync - } - - s := NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync) - - plaintext := cipher.StreamReader{S: s, R: se.Contents} - - if se.IntegrityProtected { - // IntegrityProtected packets have an embedded hash that we need to check. - h := sha1.New() - h.Write(se.prefix) - return &seMDCReader{in: plaintext, h: h}, nil - } - - // Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser. - return seMdcReader{plaintext}, nil -} - -const mdcTrailerSize = 1 /* tag byte */ + 1 /* length byte */ + sha1.Size - -// An seMDCReader wraps an io.Reader, maintains a running hash and keeps hold -// of the most recent 22 bytes (mdcTrailerSize). Upon EOF, those bytes form an -// MDC packet containing a hash of the previous Contents which is checked -// against the running hash. See RFC 4880, section 5.13. -type seMDCReader struct { - in io.Reader - h hash.Hash - trailer [mdcTrailerSize]byte - scratch [mdcTrailerSize]byte - trailerUsed int - error bool - eof bool -} - -func (ser *seMDCReader) Read(buf []byte) (n int, err error) { - if ser.error { - err = io.ErrUnexpectedEOF - return - } - if ser.eof { - err = io.EOF - return - } - - // If we haven't yet filled the trailer buffer then we must do that - // first. - for ser.trailerUsed < mdcTrailerSize { - n, err = ser.in.Read(ser.trailer[ser.trailerUsed:]) - ser.trailerUsed += n - if err == io.EOF { - if ser.trailerUsed != mdcTrailerSize { - n = 0 - err = io.ErrUnexpectedEOF - ser.error = true - return - } - ser.eof = true - n = 0 - return - } - - if err != nil { - n = 0 - return - } - } - - // If it's a short read then we read into a temporary buffer and shift - // the data into the caller's buffer. - if len(buf) <= mdcTrailerSize { - n, err = readFull(ser.in, ser.scratch[:len(buf)]) - copy(buf, ser.trailer[:n]) - ser.h.Write(buf[:n]) - copy(ser.trailer[:], ser.trailer[n:]) - copy(ser.trailer[mdcTrailerSize-n:], ser.scratch[:]) - if n < len(buf) { - ser.eof = true - err = io.EOF - } - return - } - - n, err = ser.in.Read(buf[mdcTrailerSize:]) - copy(buf, ser.trailer[:]) - ser.h.Write(buf[:n]) - copy(ser.trailer[:], buf[n:]) - - if err == io.EOF { - ser.eof = true - } - return -} - -// This is a new-format packet tag byte for a type 19 (Integrity Protected) packet. -const mdcPacketTagByte = byte(0x80) | 0x40 | 19 - -func (ser *seMDCReader) Close() error { - if ser.error { - return errors.ErrMDCHashMismatch - } - - for !ser.eof { - // We haven't seen EOF so we need to read to the end - var buf [1024]byte - _, err := ser.Read(buf[:]) - if err == io.EOF { - break - } - if err != nil { - return errors.ErrMDCHashMismatch - } - } - - ser.h.Write(ser.trailer[:2]) - - final := ser.h.Sum(nil) - if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 { - return errors.ErrMDCHashMismatch - } - // The hash already includes the MDC header, but we still check its value - // to confirm encryption correctness - if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size { - return errors.ErrMDCHashMismatch - } - return nil -} - -// An seMDCWriter writes through to an io.WriteCloser while maintains a running -// hash of the data written. On close, it emits an MDC packet containing the -// running hash. -type seMDCWriter struct { - w io.WriteCloser - h hash.Hash -} - -func (w *seMDCWriter) Write(buf []byte) (n int, err error) { - w.h.Write(buf) - return w.w.Write(buf) -} - -func (w *seMDCWriter) Close() (err error) { - var buf [mdcTrailerSize]byte - - buf[0] = mdcPacketTagByte - buf[1] = sha1.Size - w.h.Write(buf[:2]) - digest := w.h.Sum(nil) - copy(buf[2:], digest) - - _, err = w.w.Write(buf[:]) - if err != nil { - return - } - return w.w.Close() -} - -// noOpCloser is like an ioutil.NopCloser, but for an io.Writer. -type noOpCloser struct { - w io.Writer -} - -func (c noOpCloser) Write(data []byte) (n int, err error) { - return c.w.Write(data) -} - -func (c noOpCloser) Close() error { - return nil -} - -func serializeSymmetricallyEncryptedMdc(ciphertext io.WriteCloser, c CipherFunction, key []byte, config *Config) (Contents io.WriteCloser, err error) { - // Disallow old cipher suites - if !c.IsSupported() || c < CipherAES128 { - return nil, errors.InvalidArgumentError("invalid mdc cipher function") - } - - if c.KeySize() != len(key) { - return nil, errors.InvalidArgumentError("error in mdc serialization: bad key length") - } - - _, err = ciphertext.Write([]byte{symmetricallyEncryptedVersionMdc}) - if err != nil { - return - } - - block := c.new(key) - blockSize := block.BlockSize() - iv := make([]byte, blockSize) - _, err = io.ReadFull(config.Random(), iv) - if err != nil { - return nil, err - } - s, prefix := NewOCFBEncrypter(block, iv, OCFBNoResync) - _, err = ciphertext.Write(prefix) - if err != nil { - return - } - plaintext := cipher.StreamWriter{S: s, W: ciphertext} - - h := sha1.New() - h.Write(iv) - h.Write(iv[blockSize-2:]) - Contents = &seMDCWriter{w: plaintext, h: h} - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/userattribute.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/userattribute.go deleted file mode 100644 index 63814ed1324..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/userattribute.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "bytes" - "image" - "image/jpeg" - "io" -) - -const UserAttrImageSubpacket = 1 - -// UserAttribute is capable of storing other types of data about a user -// beyond name, email and a text comment. In practice, user attributes are typically used -// to store a signed thumbnail photo JPEG image of the user. -// See RFC 4880, section 5.12. -type UserAttribute struct { - Contents []*OpaqueSubpacket -} - -// NewUserAttributePhoto creates a user attribute packet -// containing the given images. -func NewUserAttributePhoto(photos ...image.Image) (uat *UserAttribute, err error) { - uat = new(UserAttribute) - for _, photo := range photos { - var buf bytes.Buffer - // RFC 4880, Section 5.12.1. - data := []byte{ - 0x10, 0x00, // Little-endian image header length (16 bytes) - 0x01, // Image header version 1 - 0x01, // JPEG - 0, 0, 0, 0, // 12 reserved octets, must be all zero. - 0, 0, 0, 0, - 0, 0, 0, 0} - if _, err = buf.Write(data); err != nil { - return - } - if err = jpeg.Encode(&buf, photo, nil); err != nil { - return - } - - lengthBuf := make([]byte, 5) - n := serializeSubpacketLength(lengthBuf, len(buf.Bytes())+1) - lengthBuf = lengthBuf[:n] - - uat.Contents = append(uat.Contents, &OpaqueSubpacket{ - SubType: UserAttrImageSubpacket, - EncodedLength: lengthBuf, - Contents: buf.Bytes(), - }) - } - return -} - -// NewUserAttribute creates a new user attribute packet containing the given subpackets. -func NewUserAttribute(contents ...*OpaqueSubpacket) *UserAttribute { - return &UserAttribute{Contents: contents} -} - -func (uat *UserAttribute) parse(r io.Reader) (err error) { - // RFC 4880, section 5.13 - b, err := io.ReadAll(r) - if err != nil { - return - } - uat.Contents, err = OpaqueSubpackets(b) - return -} - -// Serialize marshals the user attribute to w in the form of an OpenPGP packet, including -// header. -func (uat *UserAttribute) Serialize(w io.Writer) (err error) { - var buf bytes.Buffer - for _, sp := range uat.Contents { - err = sp.Serialize(&buf) - if err != nil { - return err - } - } - if err = serializeHeader(w, packetTypeUserAttribute, buf.Len()); err != nil { - return err - } - _, err = w.Write(buf.Bytes()) - return -} - -// ImageData returns zero or more byte slices, each containing -// JPEG File Interchange Format (JFIF), for each photo in the -// user attribute packet. -func (uat *UserAttribute) ImageData() (imageData [][]byte) { - for _, sp := range uat.Contents { - if sp.SubType == UserAttrImageSubpacket && len(sp.Contents) > 16 { - imageData = append(imageData, sp.Contents[16:]) - } - } - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/userid.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/userid.go deleted file mode 100644 index 3c7451a3c36..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/userid.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packet - -import ( - "io" - "strings" -) - -// UserId contains text that is intended to represent the name and email -// address of the key holder. See RFC 4880, section 5.11. By convention, this -// takes the form "Full Name (Comment) " -type UserId struct { - Id string // By convention, this takes the form "Full Name (Comment) " which is split out in the fields below. - - Name, Comment, Email string -} - -func hasInvalidCharacters(s string) bool { - for _, c := range s { - switch c { - case '(', ')', '<', '>', 0: - return true - } - } - return false -} - -// NewUserId returns a UserId or nil if any of the arguments contain invalid -// characters. The invalid characters are '\x00', '(', ')', '<' and '>' -func NewUserId(name, comment, email string) *UserId { - // RFC 4880 doesn't deal with the structure of userid strings; the - // name, comment and email form is just a convention. However, there's - // no convention about escaping the metacharacters and GPG just refuses - // to create user ids where, say, the name contains a '('. We mirror - // this behaviour. - - if hasInvalidCharacters(name) || hasInvalidCharacters(comment) || hasInvalidCharacters(email) { - return nil - } - - uid := new(UserId) - uid.Name, uid.Comment, uid.Email = name, comment, email - uid.Id = name - if len(comment) > 0 { - if len(uid.Id) > 0 { - uid.Id += " " - } - uid.Id += "(" - uid.Id += comment - uid.Id += ")" - } - if len(email) > 0 { - if len(uid.Id) > 0 { - uid.Id += " " - } - uid.Id += "<" - uid.Id += email - uid.Id += ">" - } - return uid -} - -func (uid *UserId) parse(r io.Reader) (err error) { - // RFC 4880, section 5.11 - b, err := io.ReadAll(r) - if err != nil { - return - } - uid.Id = string(b) - uid.Name, uid.Comment, uid.Email = parseUserId(uid.Id) - return -} - -// Serialize marshals uid to w in the form of an OpenPGP packet, including -// header. -func (uid *UserId) Serialize(w io.Writer) error { - err := serializeHeader(w, packetTypeUserId, len(uid.Id)) - if err != nil { - return err - } - _, err = w.Write([]byte(uid.Id)) - return err -} - -// parseUserId extracts the name, comment and email from a user id string that -// is formatted as "Full Name (Comment) ". -func parseUserId(id string) (name, comment, email string) { - var n, c, e struct { - start, end int - } - var state int - - for offset, rune := range id { - switch state { - case 0: - // Entering name - n.start = offset - state = 1 - fallthrough - case 1: - // In name - if rune == '(' { - state = 2 - n.end = offset - } else if rune == '<' { - state = 5 - n.end = offset - } - case 2: - // Entering comment - c.start = offset - state = 3 - fallthrough - case 3: - // In comment - if rune == ')' { - state = 4 - c.end = offset - } - case 4: - // Between comment and email - if rune == '<' { - state = 5 - } - case 5: - // Entering email - e.start = offset - state = 6 - fallthrough - case 6: - // In email - if rune == '>' { - state = 7 - e.end = offset - } - default: - // After email - } - } - switch state { - case 1: - // ended in the name - n.end = len(id) - case 3: - // ended in comment - c.end = len(id) - case 6: - // ended in email - e.end = len(id) - } - - name = strings.TrimSpace(id[n.start:n.end]) - comment = strings.TrimSpace(id[c.start:c.end]) - email = strings.TrimSpace(id[e.start:e.end]) - - // RFC 2822 3.4: alternate simple form of a mailbox - if email == "" && strings.ContainsRune(name, '@') { - email = name - name = "" - } - - return -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/read.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/read.go deleted file mode 100644 index e6dd9b5fd30..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/read.go +++ /dev/null @@ -1,619 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package openpgp implements high level operations on OpenPGP messages. -package openpgp // import "github.com/ProtonMail/go-crypto/openpgp" - -import ( - "crypto" - _ "crypto/sha256" - _ "crypto/sha512" - "hash" - "io" - "strconv" - - "github.com/ProtonMail/go-crypto/openpgp/armor" - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" - "github.com/ProtonMail/go-crypto/openpgp/packet" - _ "golang.org/x/crypto/sha3" -) - -// SignatureType is the armor type for a PGP signature. -var SignatureType = "PGP SIGNATURE" - -// readArmored reads an armored block with the given type. -func readArmored(r io.Reader, expectedType string) (body io.Reader, err error) { - block, err := armor.Decode(r) - if err != nil { - return - } - - if block.Type != expectedType { - return nil, errors.InvalidArgumentError("expected '" + expectedType + "', got: " + block.Type) - } - - return block.Body, nil -} - -// MessageDetails contains the result of parsing an OpenPGP encrypted and/or -// signed message. -type MessageDetails struct { - IsEncrypted bool // true if the message was encrypted. - EncryptedToKeyIds []uint64 // the list of recipient key ids. - IsSymmetricallyEncrypted bool // true if a passphrase could have decrypted the message. - DecryptedWith Key // the private key used to decrypt the message, if any. - IsSigned bool // true if the message is signed. - SignedByKeyId uint64 // the key id of the signer, if any. - SignedByFingerprint []byte // the key fingerprint of the signer, if any. - SignedBy *Key // the key of the signer, if available. - LiteralData *packet.LiteralData // the metadata of the contents - UnverifiedBody io.Reader // the contents of the message. - - // If IsSigned is true and SignedBy is non-zero then the signature will - // be verified as UnverifiedBody is read. The signature cannot be - // checked until the whole of UnverifiedBody is read so UnverifiedBody - // must be consumed until EOF before the data can be trusted. Even if a - // message isn't signed (or the signer is unknown) the data may contain - // an authentication code that is only checked once UnverifiedBody has - // been consumed. Once EOF has been seen, the following fields are - // valid. (An authentication code failure is reported as a - // SignatureError error when reading from UnverifiedBody.) - Signature *packet.Signature // the signature packet itself. - SignatureError error // nil if the signature is good. - UnverifiedSignatures []*packet.Signature // all other unverified signature packets. - - decrypted io.ReadCloser -} - -// A PromptFunction is used as a callback by functions that may need to decrypt -// a private key, or prompt for a passphrase. It is called with a list of -// acceptable, encrypted private keys and a boolean that indicates whether a -// passphrase is usable. It should either decrypt a private key or return a -// passphrase to try. If the decrypted private key or given passphrase isn't -// correct, the function will be called again, forever. Any error returned will -// be passed up. -type PromptFunction func(keys []Key, symmetric bool) ([]byte, error) - -// A keyEnvelopePair is used to store a private key with the envelope that -// contains a symmetric key, encrypted with that key. -type keyEnvelopePair struct { - key Key - encryptedKey *packet.EncryptedKey -} - -// ReadMessage parses an OpenPGP message that may be signed and/or encrypted. -// The given KeyRing should contain both public keys (for signature -// verification) and, possibly encrypted, private keys for decrypting. -// If config is nil, sensible defaults will be used. -func ReadMessage(r io.Reader, keyring KeyRing, prompt PromptFunction, config *packet.Config) (md *MessageDetails, err error) { - var p packet.Packet - - var symKeys []*packet.SymmetricKeyEncrypted - var pubKeys []keyEnvelopePair - // Integrity protected encrypted packet: SymmetricallyEncrypted or AEADEncrypted - var edp packet.EncryptedDataPacket - - packets := packet.NewReader(r) - md = new(MessageDetails) - md.IsEncrypted = true - - // The message, if encrypted, starts with a number of packets - // containing an encrypted decryption key. The decryption key is either - // encrypted to a public key, or with a passphrase. This loop - // collects these packets. -ParsePackets: - for { - p, err = packets.Next() - if err != nil { - return nil, err - } - switch p := p.(type) { - case *packet.SymmetricKeyEncrypted: - // This packet contains the decryption key encrypted with a passphrase. - md.IsSymmetricallyEncrypted = true - symKeys = append(symKeys, p) - case *packet.EncryptedKey: - // This packet contains the decryption key encrypted to a public key. - md.EncryptedToKeyIds = append(md.EncryptedToKeyIds, p.KeyId) - switch p.Algo { - case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoElGamal, packet.PubKeyAlgoECDH, packet.PubKeyAlgoX25519, packet.PubKeyAlgoX448: - break - default: - continue - } - if keyring != nil { - var keys []Key - if p.KeyId == 0 { - keys = keyring.DecryptionKeys() - } else { - keys = keyring.KeysById(p.KeyId) - } - for _, k := range keys { - pubKeys = append(pubKeys, keyEnvelopePair{k, p}) - } - } - case *packet.SymmetricallyEncrypted: - if !p.IntegrityProtected && !config.AllowUnauthenticatedMessages() { - return nil, errors.UnsupportedError("message is not integrity protected") - } - edp = p - break ParsePackets - case *packet.AEADEncrypted: - edp = p - break ParsePackets - case *packet.Compressed, *packet.LiteralData, *packet.OnePassSignature: - // This message isn't encrypted. - if len(symKeys) != 0 || len(pubKeys) != 0 { - return nil, errors.StructuralError("key material not followed by encrypted message") - } - packets.Unread(p) - return readSignedMessage(packets, nil, keyring, config) - } - } - - var candidates []Key - var decrypted io.ReadCloser - - // Now that we have the list of encrypted keys we need to decrypt at - // least one of them or, if we cannot, we need to call the prompt - // function so that it can decrypt a key or give us a passphrase. -FindKey: - for { - // See if any of the keys already have a private key available - candidates = candidates[:0] - candidateFingerprints := make(map[string]bool) - - for _, pk := range pubKeys { - if pk.key.PrivateKey == nil { - continue - } - if !pk.key.PrivateKey.Encrypted { - if len(pk.encryptedKey.Key) == 0 { - errDec := pk.encryptedKey.Decrypt(pk.key.PrivateKey, config) - if errDec != nil { - continue - } - } - // Try to decrypt symmetrically encrypted - decrypted, err = edp.Decrypt(pk.encryptedKey.CipherFunc, pk.encryptedKey.Key) - if err != nil && err != errors.ErrKeyIncorrect { - return nil, err - } - if decrypted != nil { - md.DecryptedWith = pk.key - break FindKey - } - } else { - fpr := string(pk.key.PublicKey.Fingerprint[:]) - if v := candidateFingerprints[fpr]; v { - continue - } - candidates = append(candidates, pk.key) - candidateFingerprints[fpr] = true - } - } - - if len(candidates) == 0 && len(symKeys) == 0 { - return nil, errors.ErrKeyIncorrect - } - - if prompt == nil { - return nil, errors.ErrKeyIncorrect - } - - passphrase, err := prompt(candidates, len(symKeys) != 0) - if err != nil { - return nil, err - } - - // Try the symmetric passphrase first - if len(symKeys) != 0 && passphrase != nil { - for _, s := range symKeys { - key, cipherFunc, err := s.Decrypt(passphrase) - // In v4, on wrong passphrase, session key decryption is very likely to result in an invalid cipherFunc: - // only for < 5% of cases we will proceed to decrypt the data - if err == nil { - decrypted, err = edp.Decrypt(cipherFunc, key) - if err != nil { - return nil, err - } - if decrypted != nil { - break FindKey - } - } - } - } - } - - md.decrypted = decrypted - if err := packets.Push(decrypted); err != nil { - return nil, err - } - mdFinal, sensitiveParsingErr := readSignedMessage(packets, md, keyring, config) - if sensitiveParsingErr != nil { - return nil, errors.HandleSensitiveParsingError(sensitiveParsingErr, md.decrypted != nil) - } - return mdFinal, nil -} - -// readSignedMessage reads a possibly signed message if mdin is non-zero then -// that structure is updated and returned. Otherwise a fresh MessageDetails is -// used. -func readSignedMessage(packets *packet.Reader, mdin *MessageDetails, keyring KeyRing, config *packet.Config) (md *MessageDetails, err error) { - if mdin == nil { - mdin = new(MessageDetails) - } - md = mdin - - var p packet.Packet - var h hash.Hash - var wrappedHash hash.Hash - var prevLast bool -FindLiteralData: - for { - p, err = packets.Next() - if err != nil { - return nil, err - } - switch p := p.(type) { - case *packet.Compressed: - if err := packets.Push(p.Body); err != nil { - return nil, err - } - case *packet.OnePassSignature: - if prevLast { - return nil, errors.UnsupportedError("nested signature packets") - } - - if p.IsLast { - prevLast = true - } - - h, wrappedHash, err = hashForSignature(p.Hash, p.SigType, p.Salt) - if err != nil { - md.SignatureError = err - } - - md.IsSigned = true - if p.Version == 6 { - md.SignedByFingerprint = p.KeyFingerprint - } - md.SignedByKeyId = p.KeyId - - if keyring != nil { - keys := keyring.KeysByIdUsage(p.KeyId, packet.KeyFlagSign) - if len(keys) > 0 { - md.SignedBy = &keys[0] - } - } - case *packet.LiteralData: - md.LiteralData = p - break FindLiteralData - } - } - - if md.IsSigned && md.SignatureError == nil { - md.UnverifiedBody = &signatureCheckReader{packets, h, wrappedHash, md, config} - } else if md.decrypted != nil { - md.UnverifiedBody = &checkReader{md, false} - } else { - md.UnverifiedBody = md.LiteralData.Body - } - - return md, nil -} - -func wrapHashForSignature(hashFunc hash.Hash, sigType packet.SignatureType) (hash.Hash, error) { - switch sigType { - case packet.SigTypeBinary: - return hashFunc, nil - case packet.SigTypeText: - return NewCanonicalTextHash(hashFunc), nil - } - return nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType))) -} - -// hashForSignature returns a pair of hashes that can be used to verify a -// signature. The signature may specify that the contents of the signed message -// should be preprocessed (i.e. to normalize line endings). Thus this function -// returns two hashes. The second should be used to hash the message itself and -// performs any needed preprocessing. -func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType, sigSalt []byte) (hash.Hash, hash.Hash, error) { - if _, ok := algorithm.HashToHashIdWithSha1(hashFunc); !ok { - return nil, nil, errors.UnsupportedError("unsupported hash function") - } - if !hashFunc.Available() { - return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashFunc))) - } - h := hashFunc.New() - if sigSalt != nil { - h.Write(sigSalt) - } - wrappedHash, err := wrapHashForSignature(h, sigType) - if err != nil { - return nil, nil, err - } - switch sigType { - case packet.SigTypeBinary: - return h, wrappedHash, nil - case packet.SigTypeText: - return h, wrappedHash, nil - } - return nil, nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType))) -} - -// checkReader wraps an io.Reader from a LiteralData packet. When it sees EOF -// it closes the ReadCloser from any SymmetricallyEncrypted packet to trigger -// MDC checks. -type checkReader struct { - md *MessageDetails - checked bool -} - -func (cr *checkReader) Read(buf []byte) (int, error) { - n, sensitiveParsingError := cr.md.LiteralData.Body.Read(buf) - if sensitiveParsingError == io.EOF { - if cr.checked { - // Only check once - return n, io.EOF - } - mdcErr := cr.md.decrypted.Close() - if mdcErr != nil { - return n, mdcErr - } - cr.checked = true - return n, io.EOF - } - - if sensitiveParsingError != nil { - return n, errors.HandleSensitiveParsingError(sensitiveParsingError, true) - } - - return n, nil -} - -// signatureCheckReader wraps an io.Reader from a LiteralData packet and hashes -// the data as it is read. When it sees an EOF from the underlying io.Reader -// it parses and checks a trailing Signature packet and triggers any MDC checks. -type signatureCheckReader struct { - packets *packet.Reader - h, wrappedHash hash.Hash - md *MessageDetails - config *packet.Config -} - -func (scr *signatureCheckReader) Read(buf []byte) (int, error) { - n, sensitiveParsingError := scr.md.LiteralData.Body.Read(buf) - - // Hash only if required - if scr.md.SignedBy != nil { - scr.wrappedHash.Write(buf[:n]) - } - - readsDecryptedData := scr.md.decrypted != nil - if sensitiveParsingError == io.EOF { - var p packet.Packet - var readError error - var sig *packet.Signature - - p, readError = scr.packets.Next() - for readError == nil { - var ok bool - if sig, ok = p.(*packet.Signature); ok { - if sig.Version == 5 && (sig.SigType == 0x00 || sig.SigType == 0x01) { - sig.Metadata = scr.md.LiteralData - } - - // If signature KeyID matches - if scr.md.SignedBy != nil && *sig.IssuerKeyId == scr.md.SignedByKeyId { - key := scr.md.SignedBy - signatureError := key.PublicKey.VerifySignature(scr.h, sig) - if signatureError == nil { - signatureError = checkMessageSignatureDetails(key, sig, scr.config) - } - scr.md.Signature = sig - scr.md.SignatureError = signatureError - } else { - scr.md.UnverifiedSignatures = append(scr.md.UnverifiedSignatures, sig) - } - } - - p, readError = scr.packets.Next() - } - - if scr.md.SignedBy != nil && scr.md.Signature == nil { - if scr.md.UnverifiedSignatures == nil { - scr.md.SignatureError = errors.StructuralError("LiteralData not followed by signature") - } else { - scr.md.SignatureError = errors.StructuralError("No matching signature found") - } - } - - // The SymmetricallyEncrypted packet, if any, might have an - // unsigned hash of its own. In order to check this we need to - // close that Reader. - if scr.md.decrypted != nil { - if sensitiveParsingError := scr.md.decrypted.Close(); sensitiveParsingError != nil { - return n, errors.HandleSensitiveParsingError(sensitiveParsingError, true) - } - } - return n, io.EOF - } - - if sensitiveParsingError != nil { - return n, errors.HandleSensitiveParsingError(sensitiveParsingError, readsDecryptedData) - } - - return n, nil -} - -// VerifyDetachedSignature takes a signed file and a detached signature and -// returns the signature packet and the entity the signature was signed by, -// if any, and a possible signature verification error. -// If the signer isn't known, ErrUnknownIssuer is returned. -func VerifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { - return verifyDetachedSignature(keyring, signed, signature, nil, false, config) -} - -// VerifyDetachedSignatureAndHash performs the same actions as -// VerifyDetachedSignature and checks that the expected hash functions were used. -func VerifyDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { - return verifyDetachedSignature(keyring, signed, signature, expectedHashes, true, config) -} - -// CheckDetachedSignature takes a signed file and a detached signature and -// returns the entity the signature was signed by, if any, and a possible -// signature verification error. If the signer isn't known, -// ErrUnknownIssuer is returned. -func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (signer *Entity, err error) { - _, signer, err = verifyDetachedSignature(keyring, signed, signature, nil, false, config) - return -} - -// CheckDetachedSignatureAndHash performs the same actions as -// CheckDetachedSignature and checks that the expected hash functions were used. -func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (signer *Entity, err error) { - _, signer, err = verifyDetachedSignature(keyring, signed, signature, expectedHashes, true, config) - return -} - -func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, checkHashes bool, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { - var issuerKeyId uint64 - var hashFunc crypto.Hash - var sigType packet.SignatureType - var keys []Key - var p packet.Packet - - packets := packet.NewReader(signature) - for { - p, err = packets.Next() - if err == io.EOF { - return nil, nil, errors.ErrUnknownIssuer - } - if err != nil { - return nil, nil, err - } - - var ok bool - sig, ok = p.(*packet.Signature) - if !ok { - return nil, nil, errors.StructuralError("non signature packet found") - } - if sig.IssuerKeyId == nil { - return nil, nil, errors.StructuralError("signature doesn't have an issuer") - } - issuerKeyId = *sig.IssuerKeyId - hashFunc = sig.Hash - sigType = sig.SigType - if checkHashes { - matchFound := false - // check for hashes - for _, expectedHash := range expectedHashes { - if hashFunc == expectedHash { - matchFound = true - break - } - } - if !matchFound { - return nil, nil, errors.StructuralError("hash algorithm or salt mismatch with cleartext message headers") - } - } - keys = keyring.KeysByIdUsage(issuerKeyId, packet.KeyFlagSign) - if len(keys) > 0 { - break - } - } - - if len(keys) == 0 { - panic("unreachable") - } - - h, err := sig.PrepareVerify() - if err != nil { - return nil, nil, err - } - wrappedHash, err := wrapHashForSignature(h, sigType) - if err != nil { - return nil, nil, err - } - - if _, err := io.Copy(wrappedHash, signed); err != nil && err != io.EOF { - return nil, nil, err - } - - for _, key := range keys { - err = key.PublicKey.VerifySignature(h, sig) - if err == nil { - return sig, key.Entity, checkMessageSignatureDetails(&key, sig, config) - } - } - - return nil, nil, err -} - -// CheckArmoredDetachedSignature performs the same actions as -// CheckDetachedSignature but expects the signature to be armored. -func CheckArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (signer *Entity, err error) { - body, err := readArmored(signature, SignatureType) - if err != nil { - return - } - - return CheckDetachedSignature(keyring, signed, body, config) -} - -// checkMessageSignatureDetails returns an error if: -// - The signature (or one of the binding signatures mentioned below) -// has a unknown critical notation data subpacket -// - The primary key of the signing entity is revoked -// - The primary identity is revoked -// - The signature is expired -// - The primary key of the signing entity is expired according to the -// primary identity binding signature -// -// ... or, if the signature was signed by a subkey and: -// - The signing subkey is revoked -// - The signing subkey is expired according to the subkey binding signature -// - The signing subkey binding signature is expired -// - The signing subkey cross-signature is expired -// -// NOTE: The order of these checks is important, as the caller may choose to -// ignore ErrSignatureExpired or ErrKeyExpired errors, but should never -// ignore any other errors. -func checkMessageSignatureDetails(key *Key, signature *packet.Signature, config *packet.Config) error { - now := config.Now() - primarySelfSignature, primaryIdentity := key.Entity.PrimarySelfSignature() - signedBySubKey := key.PublicKey != key.Entity.PrimaryKey - sigsToCheck := []*packet.Signature{signature, primarySelfSignature} - if signedBySubKey { - sigsToCheck = append(sigsToCheck, key.SelfSignature, key.SelfSignature.EmbeddedSignature) - } - for _, sig := range sigsToCheck { - for _, notation := range sig.Notations { - if notation.IsCritical && !config.KnownNotation(notation.Name) { - return errors.SignatureError("unknown critical notation: " + notation.Name) - } - } - } - if key.Entity.Revoked(now) || // primary key is revoked - (signedBySubKey && key.Revoked(now)) || // subkey is revoked - (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // primary identity is revoked for v4 - return errors.ErrKeyRevoked - } - if key.Entity.PrimaryKey.KeyExpired(primarySelfSignature, now) { // primary key is expired - return errors.ErrKeyExpired - } - if signedBySubKey { - if key.PublicKey.KeyExpired(key.SelfSignature, now) { // subkey is expired - return errors.ErrKeyExpired - } - } - for _, sig := range sigsToCheck { - if sig.SigExpired(now) { // any of the relevant signatures are expired - return errors.ErrSignatureExpired - } - } - return nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/read_write_test_data.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/read_write_test_data.go deleted file mode 100644 index 670d60226a4..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/read_write_test_data.go +++ /dev/null @@ -1,457 +0,0 @@ -package openpgp - -const testKey1KeyId uint64 = 0xA34D7E18C20C31BB -const testKey3KeyId uint64 = 0x338934250CCC0360 -const testKeyP256KeyId uint64 = 0xd44a2c495918513e - -const signedInput = "Signed message\nline 2\nline 3\n" -const signedTextInput = "Signed message\r\nline 2\r\nline 3\r\n" - -const recipientUnspecifiedHex = "848c0300000000000000000103ff62d4d578d03cf40c3da998dfe216c074fa6ddec5e31c197c9666ba292830d91d18716a80f699f9d897389a90e6d62d0238f5f07a5248073c0f24920e4bc4a30c2d17ee4e0cae7c3d4aaa4e8dced50e3010a80ee692175fa0385f62ecca4b56ee6e9980aa3ec51b61b077096ac9e800edaf161268593eedb6cc7027ff5cb32745d250010d407a6221ae22ef18469b444f2822478c4d190b24d36371a95cb40087cdd42d9399c3d06a53c0673349bfb607927f20d1e122bde1e2bf3aa6cae6edf489629bcaa0689539ae3b718914d88ededc3b" - -const detachedSignatureHex = "889c04000102000605024d449cd1000a0910a34d7e18c20c31bb167603ff57718d09f28a519fdc7b5a68b6a3336da04df85e38c5cd5d5bd2092fa4629848a33d85b1729402a2aab39c3ac19f9d573f773cc62c264dc924c067a79dfd8a863ae06c7c8686120760749f5fd9b1e03a64d20a7df3446ddc8f0aeadeaeba7cbaee5c1e366d65b6a0c6cc749bcb912d2f15013f812795c2e29eb7f7b77f39ce77" - -const detachedSignatureTextHex = "889c04010102000605024d449d21000a0910a34d7e18c20c31bbc8c60400a24fbef7342603a41cb1165767bd18985d015fb72fe05db42db36cfb2f1d455967f1e491194fbf6cf88146222b23bf6ffbd50d17598d976a0417d3192ff9cc0034fd00f287b02e90418bbefe609484b09231e4e7a5f3562e199bf39909ab5276c4d37382fe088f6b5c3426fc1052865da8b3ab158672d58b6264b10823dc4b39" - -const detachedSignatureDSAHex = "884604001102000605024d6c4eac000a0910338934250ccc0360f18d00a087d743d6405ed7b87755476629600b8b694a39e900a0abff8126f46faf1547c1743c37b21b4ea15b8f83" - -const detachedSignatureP256Hex = "885e0400130a0006050256e5bb00000a0910d44a2c495918513edef001009841a4f792beb0befccb35c8838a6a87d9b936beaa86db6745ddc7b045eee0cf00fd1ac1f78306b17e965935dd3f8bae4587a76587e4af231efe19cc4011a8434817" - -// The plaintext is https://www.gutenberg.org/cache/epub/1080/pg1080.txt -const modestProposalSha512 = "lbbrB1+WP3T9AaC9OQqBdOcCjgeEQadlulXsNPgVx0tyqPzDHwUugZ2gE7V0ESKAw6kAVfgkcuvfgxAAGaeHtw==" - -const testKeys1And2Hex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b0020003b88d044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f0011010001889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab0020003988d044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b0020003b88d044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020003" - -const testKeys1And2PrivateHex = "9501d8044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd00110100010003ff4d91393b9a8e3430b14d6209df42f98dc927425b881f1209f319220841273a802a97c7bdb8b3a7740b3ab5866c4d1d308ad0d3a79bd1e883aacf1ac92dfe720285d10d08752a7efe3c609b1d00f17f2805b217be53999a7da7e493bfc3e9618fd17018991b8128aea70a05dbce30e4fbe626aa45775fa255dd9177aabf4df7cf0200c1ded12566e4bc2bb590455e5becfb2e2c9796482270a943343a7835de41080582c2be3caf5981aa838140e97afa40ad652a0b544f83eb1833b0957dce26e47b0200eacd6046741e9ce2ec5beb6fb5e6335457844fb09477f83b050a96be7da043e17f3a9523567ed40e7a521f818813a8b8a72209f1442844843ccc7eb9805442570200bdafe0438d97ac36e773c7162028d65844c4d463e2420aa2228c6e50dc2743c3d6c72d0d782a5173fe7be2169c8a9f4ef8a7cf3e37165e8c61b89c346cdc6c1799d2b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b00200009d01d8044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f00110100010003fd17a7490c22a79c59281fb7b20f5e6553ec0c1637ae382e8adaea295f50241037f8997cf42c1ce26417e015091451b15424b2c59eb8d4161b0975630408e394d3b00f88d4b4e18e2cc85e8251d4753a27c639c83f5ad4a571c4f19d7cd460b9b73c25ade730c99df09637bd173d8e3e981ac64432078263bb6dc30d3e974150dd0200d0ee05be3d4604d2146fb0457f31ba17c057560785aa804e8ca5530a7cd81d3440d0f4ba6851efcfd3954b7e68908fc0ba47f7ac37bf559c6c168b70d3a7c8cd0200da1c677c4bce06a068070f2b3733b0a714e88d62aa3f9a26c6f5216d48d5c2b5624144f3807c0df30be66b3268eeeca4df1fbded58faf49fc95dc3c35f134f8b01fd1396b6c0fc1b6c4f0eb8f5e44b8eace1e6073e20d0b8bc5385f86f1cf3f050f66af789f3ef1fc107b7f4421e19e0349c730c68f0a226981f4e889054fdb4dc149e8e889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab00200009501fe044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001fe030302e9030f3c783e14856063f16938530e148bc57a7aa3f3e4f90df9dceccdc779bc0835e1ad3d006e4a8d7b36d08b8e0de5a0d947254ecfbd22037e6572b426bcfdc517796b224b0036ff90bc574b5509bede85512f2eefb520fb4b02aa523ba739bff424a6fe81c5041f253f8d757e69a503d3563a104d0d49e9e890b9d0c26f96b55b743883b472caa7050c4acfd4a21f875bdf1258d88bd61224d303dc9df77f743137d51e6d5246b88c406780528fd9a3e15bab5452e5b93970d9dcc79f48b38651b9f15bfbcf6da452837e9cc70683d1bdca94507870f743e4ad902005812488dd342f836e72869afd00ce1850eea4cfa53ce10e3608e13d3c149394ee3cbd0e23d018fcbcb6e2ec5a1a22972d1d462ca05355d0d290dd2751e550d5efb38c6c89686344df64852bf4ff86638708f644e8ec6bd4af9b50d8541cb91891a431326ab2e332faa7ae86cfb6e0540aa63160c1e5cdd5a4add518b303fff0a20117c6bc77f7cfbaf36b04c865c6c2b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b00200009d01fe044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001fe030302e9030f3c783e148560f936097339ae381d63116efcf802ff8b1c9360767db5219cc987375702a4123fd8657d3e22700f23f95020d1b261eda5257e9a72f9a918e8ef22dd5b3323ae03bbc1923dd224db988cadc16acc04b120a9f8b7e84da9716c53e0334d7b66586ddb9014df604b41be1e960dcfcbc96f4ed150a1a0dd070b9eb14276b9b6be413a769a75b519a53d3ecc0c220e85cd91ca354d57e7344517e64b43b6e29823cbd87eae26e2b2e78e6dedfbb76e3e9f77bcb844f9a8932eb3db2c3f9e44316e6f5d60e9e2a56e46b72abe6b06dc9a31cc63f10023d1f5e12d2a3ee93b675c96f504af0001220991c88db759e231b3320dcedf814dcf723fd9857e3d72d66a0f2af26950b915abdf56c1596f46a325bf17ad4810d3535fb02a259b247ac3dbd4cc3ecf9c51b6c07cebb009c1506fba0a89321ec8683e3fd009a6e551d50243e2d5092fefb3321083a4bad91320dc624bd6b5dddf93553e3d53924c05bfebec1fb4bd47e89a1a889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020000" - -const dsaElGamalTestKeysHex = "9501e1044dfcb16a110400aa3e5c1a1f43dd28c2ffae8abf5cfce555ee874134d8ba0a0f7b868ce2214beddc74e5e1e21ded354a95d18acdaf69e5e342371a71fbb9093162e0c5f3427de413a7f2c157d83f5cd2f9d791256dc4f6f0e13f13c3302af27f2384075ab3021dff7a050e14854bbde0a1094174855fc02f0bae8e00a340d94a1f22b32e48485700a0cec672ac21258fb95f61de2ce1af74b2c4fa3e6703ff698edc9be22c02ae4d916e4fa223f819d46582c0516235848a77b577ea49018dcd5e9e15cff9dbb4663a1ae6dd7580fa40946d40c05f72814b0f88481207e6c0832c3bded4853ebba0a7e3bd8e8c66df33d5a537cd4acf946d1080e7a3dcea679cb2b11a72a33a2b6a9dc85f466ad2ddf4c3db6283fa645343286971e3dd700703fc0c4e290d45767f370831a90187e74e9972aae5bff488eeff7d620af0362bfb95c1a6c3413ab5d15a2e4139e5d07a54d72583914661ed6a87cce810be28a0aa8879a2dd39e52fb6fe800f4f181ac7e328f740cde3d09a05cecf9483e4cca4253e60d4429ffd679d9996a520012aad119878c941e3cf151459873bdfc2a9563472fe0303027a728f9feb3b864260a1babe83925ce794710cfd642ee4ae0e5b9d74cee49e9c67b6cd0ea5dfbb582132195a121356a1513e1bca73e5b80c58c7ccb4164453412f456c47616d616c2054657374204b65792031886204131102002205024dfcb16a021b03060b090807030206150802090a0b0416020301021e01021780000a091033af447ccd759b09fadd00a0b8fd6f5a790bad7e9f2dbb7632046dc4493588db009c087c6a9ba9f7f49fab221587a74788c00db4889ab00200009d0157044dfcb16a1004008dec3f9291205255ccff8c532318133a6840739dd68b03ba942676f9038612071447bf07d00d559c5c0875724ea16a4c774f80d8338b55fca691a0522e530e604215b467bbc9ccfd483a1da99d7bc2648b4318fdbd27766fc8bfad3fddb37c62b8ae7ccfe9577e9b8d1e77c1d417ed2c2ef02d52f4da11600d85d3229607943700030503ff506c94c87c8cab778e963b76cf63770f0a79bf48fb49d3b4e52234620fc9f7657f9f8d56c96a2b7c7826ae6b57ebb2221a3fe154b03b6637cea7e6d98e3e45d87cf8dc432f723d3d71f89c5192ac8d7290684d2c25ce55846a80c9a7823f6acd9bb29fa6cd71f20bc90eccfca20451d0c976e460e672b000df49466408d527affe0303027a728f9feb3b864260abd761730327bca2aaa4ea0525c175e92bf240682a0e83b226f97ecb2e935b62c9a133858ce31b271fa8eb41f6a1b3cd72a63025ce1a75ee4180dcc284884904181102000905024dfcb16a021b0c000a091033af447ccd759b09dd0b009e3c3e7296092c81bee5a19929462caaf2fff3ae26009e218c437a2340e7ea628149af1ec98ec091a43992b00200009501e1044dfcb1be1104009f61faa61aa43df75d128cbe53de528c4aec49ce9360c992e70c77072ad5623de0a3a6212771b66b39a30dad6781799e92608316900518ec01184a85d872365b7d2ba4bacfb5882ea3c2473d3750dc6178cc1cf82147fb58caa28b28e9f12f6d1efcb0534abed644156c91cca4ab78834268495160b2400bc422beb37d237c2300a0cac94911b6d493bda1e1fbc6feeca7cb7421d34b03fe22cec6ccb39675bb7b94a335c2b7be888fd3906a1125f33301d8aa6ec6ee6878f46f73961c8d57a3e9544d8ef2a2cbfd4d52da665b1266928cfe4cb347a58c412815f3b2d2369dec04b41ac9a71cc9547426d5ab941cccf3b18575637ccfb42df1a802df3cfe0a999f9e7109331170e3a221991bf868543960f8c816c28097e503fe319db10fb98049f3a57d7c80c420da66d56f3644371631fad3f0ff4040a19a4fedc2d07727a1b27576f75a4d28c47d8246f27071e12d7a8de62aad216ddbae6aa02efd6b8a3e2818cda48526549791ab277e447b3a36c57cefe9b592f5eab73959743fcc8e83cbefec03a329b55018b53eec196765ae40ef9e20521a603c551efe0303020950d53a146bf9c66034d00c23130cce95576a2ff78016ca471276e8227fb30b1ffbd92e61804fb0c3eff9e30b1a826ee8f3e4730b4d86273ca977b4164453412f456c47616d616c2054657374204b65792032886204131102002205024dfcb1be021b03060b090807030206150802090a0b0416020301021e01021780000a0910a86bf526325b21b22bd9009e34511620415c974750a20df5cb56b182f3b48e6600a0a9466cb1a1305a84953445f77d461593f1d42bc1b00200009d0157044dfcb1be1004009565a951da1ee87119d600c077198f1c1bceb0f7aa54552489298e41ff788fa8f0d43a69871f0f6f77ebdfb14a4260cf9fbeb65d5844b4272a1904dd95136d06c3da745dc46327dd44a0f16f60135914368c8039a34033862261806bb2c5ce1152e2840254697872c85441ccb7321431d75a747a4bfb1d2c66362b51ce76311700030503fc0ea76601c196768070b7365a200e6ddb09307f262d5f39eec467b5f5784e22abdf1aa49226f59ab37cb49969d8f5230ea65caf56015abda62604544ed526c5c522bf92bed178a078789f6c807b6d34885688024a5bed9e9f8c58d11d4b82487b44c5f470c5606806a0443b79cadb45e0f897a561a53f724e5349b9267c75ca17fe0303020950d53a146bf9c660bc5f4ce8f072465e2d2466434320c1e712272fafc20e342fe7608101580fa1a1a367e60486a7cd1246b7ef5586cf5e10b32762b710a30144f12dd17dd4884904181102000905024dfcb1be021b0c000a0910a86bf526325b21b2904c00a0b2b66b4b39ccffda1d10f3ea8d58f827e30a8b8e009f4255b2d8112a184e40cde43a34e8655ca7809370b0020000" - -const ed25519wX25519Key = "c54b0663877fe31b00000020f94da7bb48d60a61e567706a6587d0331999bb9d891a08242ead84543df895a3001972817b12be707e8d5f586ce61361201d344eb266a2c82fde6835762b65b0b7c2b1061f1b0a00000042058263877fe3030b090705150a0e080c021600029b03021e09222106cb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc905270902070200000000ad2820103e2d7d227ec0e6d7ce4471db36bfc97083253690271498a7ef0576c07faae14585b3b903b0127ec4fda2f023045a2ec76bcb4f9571a9651e14aee1137a1d668442c88f951e33c4ffd33fb9a17d511eed758fc6d9cc50cb5fd793b2039d5804c74b0663877fe319000000208693248367f9e5015db922f8f48095dda784987f2d5985b12fbad16caf5e4435004d600a4f794d44775c57a26e0feefed558e9afffd6ad0d582d57fb2ba2dcedb8c29b06181b0a0000002c050263877fe322a106cb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc9021b0c00000000defa20a6e9186d9d5935fc8fe56314cdb527486a5a5120f9b762a235a729f039010a56b89c658568341fbef3b894e9834ad9bc72afae2f4c9c47a43855e65f1cb0a3f77bbc5f61085c1f8249fe4e7ca59af5f0bcee9398e0fa8d76e522e1d8ab42bb0d" - -const signedMessageHex = "a3019bc0cbccc0c4b8d8b74ee2108fe16ec6d3ca490cbe362d3f8333d3f352531472538b8b13d353b97232f352158c20943157c71c16064626063656269052062e4e01987e9b6fccff4b7df3a34c534b23e679cbec3bc0f8f6e64dfb4b55fe3f8efa9ce110ddb5cd79faf1d753c51aecfa669f7e7aa043436596cccc3359cb7dd6bbe9ecaa69e5989d9e57209571edc0b2fa7f57b9b79a64ee6e99ce1371395fee92fec2796f7b15a77c386ff668ee27f6d38f0baa6c438b561657377bf6acff3c5947befd7bf4c196252f1d6e5c524d0300" - -const signedTextMessageHex = "a3019bc0cbccc8c4b8d8b74ee2108fe16ec6d36a250cbece0c178233d3f352531472538b8b13d35379b97232f352158ca0b4312f57c71c1646462606365626906a062e4e019811591798ff99bf8afee860b0d8a8c2a85c3387e3bcf0bb3b17987f2bbcfab2aa526d930cbfd3d98757184df3995c9f3e7790e36e3e9779f06089d4c64e9e47dd6202cb6e9bc73c5d11bb59fbaf89d22d8dc7cf199ddf17af96e77c5f65f9bbed56f427bd8db7af37f6c9984bf9385efaf5f184f986fb3e6adb0ecfe35bbf92d16a7aa2a344fb0bc52fb7624f0200" - -const signedEncryptedMessageHex = "c18c032a67d68660df41c70103ff5a84c9a72f80e74ef0384c2d6a9ebfe2b09e06a8f298394f6d2abf174e40934ab0ec01fb2d0ddf21211c6fe13eb238563663b017a6b44edca552eb4736c4b7dc6ed907dd9e12a21b51b64b46f902f76fb7aaf805c1db8070574d8d0431a23e324a750f77fb72340a17a42300ee4ca8207301e95a731da229a63ab9c6b44541fbd2c11d016d810b3b3b2b38f15b5b40f0a4910332829c2062f1f7cc61f5b03677d73c54cafa1004ced41f315d46444946faae571d6f426e6dbd45d9780eb466df042005298adabf7ce0ef766dfeb94cd449c7ed0046c880339599c4711af073ce649b1e237c40b50a5536283e03bdbb7afad78bd08707715c67fb43295f905b4c479178809d429a8e167a9a8c6dfd8ab20b4edebdc38d6dec879a3202e1b752690d9bb5b0c07c5a227c79cc200e713a99251a4219d62ad5556900cf69bd384b6c8e726c7be267471d0d23af956da165af4af757246c2ebcc302b39e8ef2fccb4971b234fcda22d759ddb20e27269ee7f7fe67898a9de721bfa02ab0becaa046d00ea16cb1afc4e2eab40d0ac17121c565686e5cbd0cbdfbd9d6db5c70278b9c9db5a83176d04f61fbfbc4471d721340ede2746e5c312ded4f26787985af92b64fae3f253dbdde97f6a5e1996fd4d865599e32ff76325d3e9abe93184c02988ee89a4504356a4ef3b9b7a57cbb9637ca90af34a7676b9ef559325c3cca4e29d69fec1887f5440bb101361d744ad292a8547f22b4f22b419a42aa836169b89190f46d9560824cb2ac6e8771de8223216a5e647e132ab9eebcba89569ab339cb1c3d70fe806b31f4f4c600b4103b8d7583ebff16e43dcda551e6530f975122eb8b29" - -const verifiedSignatureEncryptedMessageHex = "c2b304000108000605026048f6d600210910a34d7e18c20c31bb1621045fb74b1d03b1e3cb31bc2f8aa34d7e18c20c31bb9a3b0400a32ddac1af259c1b0abab0041327ea04970944401978fb647dd1cf9aba4f164e43f0d8a9389501886474bdd4a6e77f6aea945c07dfbf87743835b44cc2c39a1f9aeecfa83135abc92e18e50396f2e6a06c44e0188b0081effbfb4160d28f118d4ff73dd199a102e47cffd8c7ff2bacd83ae72b5820c021a486766dd587b5da61" - -const unverifiedSignatureEncryptedMessageHex = "c2b304000108000605026048f6d600210910a34d7e18c20c31bb1621045fb74b1d03b1e3cb31bc2f8aa34d7e18c20c31bb9a3b0400a32ddac1af259c1b0abab0041327ea04970944401978fb647dd1cf9aba4f164e43f0d8a9389501886474bdd4a6e77f6aea945c07dfbf87743835b44cc2c39a1f9aeecfa83135abc92e18e50396f2e6a06c44e0188b0081effbfb4160d28f118d4ff73dd199a102e47cffd8c7ff2bacd83ae72b5820c021a486766dd587b5da61" - -const signedEncryptedMessage2Hex = "85010e03cf6a7abcd43e36731003fb057f5495b79db367e277cdbe4ab90d924ddee0c0381494112ff8c1238fb0184af35d1731573b01bc4c55ecacd2aafbe2003d36310487d1ecc9ac994f3fada7f9f7f5c3a64248ab7782906c82c6ff1303b69a84d9a9529c31ecafbcdb9ba87e05439897d87e8a2a3dec55e14df19bba7f7bd316291c002ae2efd24f83f9e3441203fc081c0c23dc3092a454ca8a082b27f631abf73aca341686982e8fbda7e0e7d863941d68f3de4a755c2964407f4b5e0477b3196b8c93d551dd23c8beef7d0f03fbb1b6066f78907faf4bf1677d8fcec72651124080e0b7feae6b476e72ab207d38d90b958759fdedfc3c6c35717c9dbfc979b3cfbbff0a76d24a5e57056bb88acbd2a901ef64bc6e4db02adc05b6250ff378de81dca18c1910ab257dff1b9771b85bb9bbe0a69f5989e6d1710a35e6dfcceb7d8fb5ccea8db3932b3d9ff3fe0d327597c68b3622aec8e3716c83a6c93f497543b459b58ba504ed6bcaa747d37d2ca746fe49ae0a6ce4a8b694234e941b5159ff8bd34b9023da2814076163b86f40eed7c9472f81b551452d5ab87004a373c0172ec87ea6ce42ccfa7dbdad66b745496c4873d8019e8c28d6b3" - -const signatureEncryptedMessage2Hex = "c24604001102000605024dfd0166000a091033af447ccd759b09bae600a096ec5e63ecf0a403085e10f75cc3bab327663282009f51fad9df457ed8d2b70d8a73c76e0443eac0f377" - -const symmetricallyEncryptedCompressedHex = "c32e040903085a357c1a7b5614ed00cc0d1d92f428162058b3f558a0fb0980d221ebac6c97d5eda4e0fe32f6e706e94dd263012d6ca1ef8c4bbd324098225e603a10c85ebf09cbf7b5aeeb5ce46381a52edc51038b76a8454483be74e6dcd1e50d5689a8ae7eceaeefed98a0023d49b22eb1f65c2aa1ef1783bb5e1995713b0457102ec3c3075fe871267ffa4b686ad5d52000d857" - -const dsaTestKeyHex = "9901a2044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794" - -const dsaTestKeyPrivateHex = "9501bb044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4d00009f592e0619d823953577d4503061706843317e4fee083db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794" - -const p256TestKeyHex = "98520456e5b83813082a8648ce3d030107020304a2072cd6d21321266c758cc5b83fab0510f751cb8d91897cddb7047d8d6f185546e2107111b0a95cb8ef063c33245502af7a65f004d5919d93ee74eb71a66253b424502d3235362054657374204b6579203c696e76616c6964406578616d706c652e636f6d3e8879041313080021050256e5b838021b03050b09080702061508090a0b020416020301021e01021780000a0910d44a2c495918513e54e50100dfa64f97d9b47766fc1943c6314ba3f2b2a103d71ad286dc5b1efb96a345b0c80100dbc8150b54241f559da6ef4baacea6d31902b4f4b1bdc09b34bf0502334b7754b8560456e5b83812082a8648ce3d030107020304bfe3cea9cee13486f8d518aa487fecab451f25467d2bf08e58f63e5fa525d5482133e6a79299c274b068ef0be448152ad65cf11cf764348588ca4f6a0bcf22b6030108078861041813080009050256e5b838021b0c000a0910d44a2c495918513e4a4800ff49d589fa64024ad30be363a032e3a0e0e6f5db56ba4c73db850518bf0121b8f20100fd78e065f4c70ea5be9df319ea67e493b936fc78da834a71828043d3154af56e" - -const p256TestKeyPrivateHex = "94a50456e5b83813082a8648ce3d030107020304a2072cd6d21321266c758cc5b83fab0510f751cb8d91897cddb7047d8d6f185546e2107111b0a95cb8ef063c33245502af7a65f004d5919d93ee74eb71a66253fe070302f0c2bfb0b6c30f87ee1599472b8636477eab23ced13b271886a4b50ed34c9d8436af5af5b8f88921f0efba6ef8c37c459bbb88bc1c6a13bbd25c4ce9b1e97679569ee77645d469bf4b43de637f5561b424502d3235362054657374204b6579203c696e76616c6964406578616d706c652e636f6d3e8879041313080021050256e5b838021b03050b09080702061508090a0b020416020301021e01021780000a0910d44a2c495918513e54e50100dfa64f97d9b47766fc1943c6314ba3f2b2a103d71ad286dc5b1efb96a345b0c80100dbc8150b54241f559da6ef4baacea6d31902b4f4b1bdc09b34bf0502334b77549ca90456e5b83812082a8648ce3d030107020304bfe3cea9cee13486f8d518aa487fecab451f25467d2bf08e58f63e5fa525d5482133e6a79299c274b068ef0be448152ad65cf11cf764348588ca4f6a0bcf22b603010807fe0703027510012471a603cfee2968dce19f732721ddf03e966fd133b4e3c7a685b788705cbc46fb026dc94724b830c9edbaecd2fb2c662f23169516cacd1fe423f0475c364ecc10abcabcfd4bbbda1a36a1bd8861041813080009050256e5b838021b0c000a0910d44a2c495918513e4a4800ff49d589fa64024ad30be363a032e3a0e0e6f5db56ba4c73db850518bf0121b8f20100fd78e065f4c70ea5be9df319ea67e493b936fc78da834a71828043d3154af56e" - -const armoredPrivateKeyBlock = `-----BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -lQHYBE2rFNoBBADFwqWQIW/DSqcB4yCQqnAFTJ27qS5AnB46ccAdw3u4Greeu3Bp -idpoHdjULy7zSKlwR1EA873dO/k/e11Ml3dlAFUinWeejWaK2ugFP6JjiieSsrKn -vWNicdCS4HTWn0X4sjl0ZiAygw6GNhqEQ3cpLeL0g8E9hnYzJKQ0LWJa0QARAQAB -AAP/TB81EIo2VYNmTq0pK1ZXwUpxCrvAAIG3hwKjEzHcbQznsjNvPUihZ+NZQ6+X -0HCfPAdPkGDCLCb6NavcSW+iNnLTrdDnSI6+3BbIONqWWdRDYJhqZCkqmG6zqSfL -IdkJgCw94taUg5BWP/AAeQrhzjChvpMQTVKQL5mnuZbUCeMCAN5qrYMP2S9iKdnk -VANIFj7656ARKt/nf4CBzxcpHTyB8+d2CtPDKCmlJP6vL8t58Jmih+kHJMvC0dzn -gr5f5+sCAOOe5gt9e0am7AvQWhdbHVfJU0TQJx+m2OiCJAqGTB1nvtBLHdJnfdC9 -TnXXQ6ZXibqLyBies/xeY2sCKL5qtTMCAKnX9+9d/5yQxRyrQUHt1NYhaXZnJbHx -q4ytu0eWz+5i68IYUSK69jJ1NWPM0T6SkqpB3KCAIv68VFm9PxqG1KmhSrQIVGVz -dCBLZXmIuAQTAQIAIgUCTasU2gIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA -CgkQO9o98PRieSoLhgQAkLEZex02Qt7vGhZzMwuN0R22w3VwyYyjBx+fM3JFETy1 -ut4xcLJoJfIaF5ZS38UplgakHG0FQ+b49i8dMij0aZmDqGxrew1m4kBfjXw9B/v+ -eIqpODryb6cOSwyQFH0lQkXC040pjq9YqDsO5w0WYNXYKDnzRV0p4H1pweo2VDid -AdgETasU2gEEAN46UPeWRqKHvA99arOxee38fBt2CI08iiWyI8T3J6ivtFGixSqV -bRcPxYO/qLpVe5l84Nb3X71GfVXlc9hyv7CD6tcowL59hg1E/DC5ydI8K8iEpUmK -/UnHdIY5h8/kqgGxkY/T/hgp5fRQgW1ZoZxLajVlMRZ8W4tFtT0DeA+JABEBAAEA -A/0bE1jaaZKj6ndqcw86jd+QtD1SF+Cf21CWRNeLKnUds4FRRvclzTyUMuWPkUeX -TaNNsUOFqBsf6QQ2oHUBBK4VCHffHCW4ZEX2cd6umz7mpHW6XzN4DECEzOVksXtc -lUC1j4UB91DC/RNQqwX1IV2QLSwssVotPMPqhOi0ZLNY7wIA3n7DWKInxYZZ4K+6 -rQ+POsz6brEoRHwr8x6XlHenq1Oki855pSa1yXIARoTrSJkBtn5oI+f8AzrnN0BN -oyeQAwIA/7E++3HDi5aweWrViiul9cd3rcsS0dEnksPhvS0ozCJiHsq/6GFmy7J8 -QSHZPteedBnZyNp5jR+H7cIfVN3KgwH/Skq4PsuPhDq5TKK6i8Pc1WW8MA6DXTdU -nLkX7RGmMwjC0DBf7KWAlPjFaONAX3a8ndnz//fy1q7u2l9AZwrj1qa1iJ8EGAEC -AAkFAk2rFNoCGwwACgkQO9o98PRieSo2/QP/WTzr4ioINVsvN1akKuekmEMI3LAp -BfHwatufxxP1U+3Si/6YIk7kuPB9Hs+pRqCXzbvPRrI8NHZBmc8qIGthishdCYad -AHcVnXjtxrULkQFGbGvhKURLvS9WnzD/m1K2zzwxzkPTzT9/Yf06O6Mal5AdugPL -VrM0m72/jnpKo04= -=zNCn ------END PGP PRIVATE KEY BLOCK-----` - -const e2ePublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- -Charset: UTF-8 - -xv8AAABSBAAAAAATCCqGSM49AwEHAgME1LRoXSpOxtHXDUdmuvzchyg6005qIBJ4 -sfaSxX7QgH9RV2ONUhC+WiayCNADq+UMzuR/vunSr4aQffXvuGnR383/AAAAFDxk -Z2lsQHlhaG9vLWluYy5jb20+wv8AAACGBBATCAA4/wAAAAWCVGvAG/8AAAACiwn/ -AAAACZC2VkQCOjdvYf8AAAAFlQgJCgv/AAAAA5YBAv8AAAACngEAAE1BAP0X8veD -24IjmI5/C6ZAfVNXxgZZFhTAACFX75jUA3oD6AEAzoSwKf1aqH6oq62qhCN/pekX -+WAsVMBhNwzLpqtCRjLO/wAAAFYEAAAAABIIKoZIzj0DAQcCAwT50ain7vXiIRv8 -B1DO3x3cE/aattZ5sHNixJzRCXi2vQIA5QmOxZ6b5jjUekNbdHG3SZi1a2Ak5mfX -fRxC/5VGAwEIB8L/AAAAZQQYEwgAGP8AAAAFglRrwBz/AAAACZC2VkQCOjdvYQAA -FJAA9isX3xtGyMLYwp2F3nXm7QEdY5bq5VUcD/RJlj792VwA/1wH0pCzVLl4Q9F9 -ex7En5r7rHR5xwX82Msc+Rq9dSyO -=7MrZ ------END PGP PUBLIC KEY BLOCK-----` - -const dsaKeyWithSHA512 = `9901a2044f04b07f110400db244efecc7316553ee08d179972aab87bb1214de7692593fcf5b6feb1c80fba268722dd464748539b85b81d574cd2d7ad0ca2444de4d849b8756bad7768c486c83a824f9bba4af773d11742bdfb4ac3b89ef8cc9452d4aad31a37e4b630d33927bff68e879284a1672659b8b298222fc68f370f3e24dccacc4a862442b9438b00a0ea444a24088dc23e26df7daf8f43cba3bffc4fe703fe3d6cd7fdca199d54ed8ae501c30e3ec7871ea9cdd4cf63cfe6fc82281d70a5b8bb493f922cd99fba5f088935596af087c8d818d5ec4d0b9afa7f070b3d7c1dd32a84fca08d8280b4890c8da1dde334de8e3cad8450eed2a4a4fcc2db7b8e5528b869a74a7f0189e11ef097ef1253582348de072bb07a9fa8ab838e993cef0ee203ff49298723e2d1f549b00559f886cd417a41692ce58d0ac1307dc71d85a8af21b0cf6eaa14baf2922d3a70389bedf17cc514ba0febbd107675a372fe84b90162a9e88b14d4b1c6be855b96b33fb198c46f058568817780435b6936167ebb3724b680f32bf27382ada2e37a879b3d9de2abe0c3f399350afd1ad438883f4791e2e3b4184453412068617368207472756e636174696f6e207465737488620413110a002205024f04b07f021b03060b090807030206150802090a0b0416020301021e01021780000a0910ef20e0cefca131581318009e2bf3bf047a44d75a9bacd00161ee04d435522397009a03a60d51bd8a568c6c021c8d7cf1be8d990d6417b0020003` - -const unknownHashFunctionHex = `8a00000040040001990006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101` - -const rsaSignatureBadMPIlength = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101` - -const missingHashFunctionHex = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101` - -const campbellQuine = `a0b001000300fcffa0b001000d00f2ff000300fcffa0b001000d00f2ff8270a01c00000500faff8270a01c00000500faff000500faff001400ebff8270a01c00000500faff000500faff001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400000000ffff000000ffff000b00f4ff428821c400000000ffff000000ffff000b00f4ff0233214c40000100feff000233214c40000100feff0000` - -const keyV4forVerifyingSignedMessageV3 = `-----BEGIN PGP PUBLIC KEY BLOCK----- -Comment: GPGTools - https://gpgtools.org - -mI0EVfxoFQEEAMBIqmbDfYygcvP6Phr1wr1XI41IF7Qixqybs/foBF8qqblD9gIY -BKpXjnBOtbkcVOJ0nljd3/sQIfH4E0vQwK5/4YRQSI59eKOqd6Fx+fWQOLG+uu6z -tewpeCj9LLHvibx/Sc7VWRnrznia6ftrXxJ/wHMezSab3tnGC0YPVdGNABEBAAG0 -JEdvY3J5cHRvIFRlc3QgS2V5IDx0aGVtYXhAZ21haWwuY29tPoi5BBMBCgAjBQJV -/GgVAhsDBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQeXnQmhdGW9PFVAP+ -K7TU0qX5ArvIONIxh/WAweyOk884c5cE8f+3NOPOOCRGyVy0FId5A7MmD5GOQh4H -JseOZVEVCqlmngEvtHZb3U1VYtVGE5WZ+6rQhGsMcWP5qaT4soYwMBlSYxgYwQcx -YhN9qOr292f9j2Y//TTIJmZT4Oa+lMxhWdqTfX+qMgG4jQRV/GgVAQQArhFSiij1 -b+hT3dnapbEU+23Z1yTu1DfF6zsxQ4XQWEV3eR8v+8mEDDNcz8oyyF56k6UQ3rXi -UMTIwRDg4V6SbZmaFbZYCOwp/EmXJ3rfhm7z7yzXj2OFN22luuqbyVhuL7LRdB0M -pxgmjXb4tTvfgKd26x34S+QqUJ7W6uprY4sAEQEAAYifBBgBCgAJBQJV/GgVAhsM -AAoJEHl50JoXRlvT7y8D/02ckx4OMkKBZo7viyrBw0MLG92i+DC2bs35PooHR6zz -786mitjOp5z2QWNLBvxC70S0qVfCIz8jKupO1J6rq6Z8CcbLF3qjm6h1omUBf8Nd -EfXKD2/2HV6zMKVknnKzIEzauh+eCKS2CeJUSSSryap/QLVAjRnckaES/OsEWhNB -=RZia ------END PGP PUBLIC KEY BLOCK----- -` - -const signedMessageV3 = `-----BEGIN PGP MESSAGE----- -Comment: GPGTools - https://gpgtools.org - -owGbwMvMwMVYWXlhlrhb9GXG03JJDKF/MtxDMjKLFYAoUaEktbhEITe1uDgxPVWP -q5NhKjMrWAVcC9evD8z/bF/uWNjqtk/X3y5/38XGRQHm/57rrDRYuGnTw597Xqka -uM3137/hH3Os+Jf2dc0fXOITKwJvXJvecPVs0ta+Vg7ZO1MLn8w58Xx+6L58mbka -DGHyU9yTueZE8D+QF/Tz28Y78dqtF56R1VPn9Xw4uJqrWYdd7b3vIZ1V6R4Nh05d -iT57d/OhWwA= -=hG7R ------END PGP MESSAGE----- -` - -// https://mailarchive.ietf.org/arch/msg/openpgp/9SheW_LENE0Kxf7haNllovPyAdY/ -const v5PrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -lGEFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd -fj75iux+my8QAAAAAAAiAQCHZ1SnSUmWqxEsoI6facIVZQu6mph3cBFzzTvcm5lA -Ng5ctBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0e8mHJGQC -X5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyICAQYVCgkI -CwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3wwJAXRJy9 -M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2BZxmBVyR9OQSAAAA -MgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0YvYWWAoD -AQgHAAAAAAAiAP9OdAPppjU1WwpqjIItkxr+VPQRT8Zm/Riw7U3F6v3OiBFHiHoF -GBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVAUCXJH05AIb -DAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijhob2U5AQC+RtOHCHx7 -TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw== -=IiS2 ------END PGP PRIVATE KEY BLOCK-----` - -// See OpenPGP crypto refresh Section A.3. -const v6PrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB -exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ -BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 -2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh -RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe -7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/ -LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG -GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 -2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE -M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr -k0mXubZvyl4GBg== ------END PGP PRIVATE KEY BLOCK-----` - -// See OpenPGP crypto refresh merge request: -// https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/304 -const v6PrivKeyMsg = `-----BEGIN PGP MESSAGE----- - -wV0GIQYSyD8ecG9jCP4VGkF3Q6HwM3kOk+mXhIjR2zeNqZMIhRmHzxjV8bU/gXzO -WgBM85PMiVi93AZfJfhK9QmxfdNnZBjeo1VDeVZheQHgaVf7yopqR6W1FT6NOrfS -aQIHAgZhZBZTW+CwcW1g4FKlbExAf56zaw76/prQoN+bAzxpohup69LA7JW/Vp0l -yZnuSj3hcFj0DfqLTGgr4/u717J+sPWbtQBfgMfG9AOIwwrUBqsFE9zW+f1zdlYo -bhF30A+IitsxxA== ------END PGP MESSAGE-----` - -// See OpenPGP crypto refresh merge request: -// https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/305 -const v6PrivKeyInlineSignMsg = `-----BEGIN PGP MESSAGE----- - -wV0GIQYSyD8ecG9jCP4VGkF3Q6HwM3kOk+mXhIjR2zeNqZMIhRmHzxjV8bU/gXzO -WgBM85PMiVi93AZfJfhK9QmxfdNnZBjeo1VDeVZheQHgaVf7yopqR6W1FT6NOrfS -aQIHAgZhZBZTW+CwcW1g4FKlbExAf56zaw76/prQoN+bAzxpohup69LA7JW/Vp0l -yZnuSj3hcFj0DfqLTGgr4/u717J+sPWbtQBfgMfG9AOIwwrUBqsFE9zW+f1zdlYo -bhF30A+IitsxxA== ------END PGP MESSAGE-----` - -// See https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/274 -// decryption password: "correct horse battery staple" -const v6ArgonSealedPrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -xYIGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laP9JgkC -FARdb9ccngltHraRe25uHuyuAQQVtKipJ0+r5jL4dacGWSAheCWPpITYiyfyIOPS -3gIDyg8f7strd1OB4+LZsUhcIjOMpVHgmiY/IutJkulneoBYwrEGHxsKAAAAQgWC -Y4d/4wMLCQcFFQoOCAwCFgACmwMCHgkiIQbLGGxPBgmml+TVLfpscisMHx4nwYpW -cI9lJewnutmsyQUnCQIHAgAAAACtKCAQPi19In7A5tfORHHbNr/JcIMlNpAnFJin -7wV2wH+q4UWFs7kDsBJ+xP2i8CMEWi7Ha8tPlXGpZR4UruETeh1mhELIj5UeM8T/ -0z+5oX1RHu11j8bZzFDLX9eTsgOdWATHggZjh3/jGQAAACCGkySDZ/nlAV25Ivj0 -gJXdp4SYfy1ZhbEvutFsr15ENf0mCQIUBA5hhGgp2oaavg6mFUXcFMwBBBUuE8qf -9Ock+xwusd+GAglBr5LVyr/lup3xxQvHXFSjjA2haXfoN6xUGRdDEHI6+uevKjVR -v5oAxgu7eJpaXNjCmwYYGwoAAAAsBYJjh3/jApsMIiEGyxhsTwYJppfk1S36bHIr -DB8eJ8GKVnCPZSXsJ7rZrMkAAAAABAEgpukYbZ1ZNfyP5WMUzbUnSGpaUSD5t2Ki -Nacp8DkBClZRa2c3AMQzSDXa9jGhYzxjzVb5scHDzTkjyRZWRdTq8U6L4da+/+Kt -ruh8m7Xo2ehSSFyWRSuTSZe5tm/KXgYG ------END PGP PRIVATE KEY BLOCK-----` - -const v4Key25519 = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -xUkEZB3qzRto01j2k2pwN5ux9w70stPinAdXULLr20CRW7U7h2GSeACch0M+ -qzQg8yjFQ8VBvu3uwgKH9senoHmj72lLSCLTmhFKzQR0ZXN0wogEEBsIAD4F -gmQd6s0ECwkHCAmQIf45+TuC+xMDFQgKBBYAAgECGQECmwMCHgEWIQSWEzMi -jJUHvyIbVKIh/jn5O4L7EwAAUhaHNlgudvxARdPPETUzVgjuWi+YIz8w1xIb -lHQMvIrbe2sGCQIethpWofd0x7DHuv/ciHg+EoxJ/Td6h4pWtIoKx0kEZB3q -zRm4CyA7quliq7yx08AoOqHTuuCgvpkSdEhpp3pEyejQOgBo0p6ywIiLPllY -0t+jpNspHpAGfXID6oqjpYuJw3AfVRBlwnQEGBsIACoFgmQd6s0JkCH+Ofk7 -gvsTApsMFiEElhMzIoyVB78iG1SiIf45+TuC+xMAAGgQuN9G73446ykvJ/mL -sCZ7zGFId2gBd1EnG0FTC4npfOKpck0X8dngByrCxU8LDSfvjsEp/xDAiKsQ -aU71tdtNBQ== -=e7jT ------END PGP PRIVATE KEY BLOCK-----` - -const keyWithExpiredCrossSig = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv -/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz -/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/ -5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3 -X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv -9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0 -qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb -SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb -vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w -bGU+wsEABBMBCgATBYJeO2eVAgsJAxUICgKbAQIeAQAhCRD7/MgqAV5zMBYhBNGm -bhojsYLJmA94jPv8yCoBXnMwKWUMAJ3FKZfJ2mXvh+GFqgymvK4NoKkDRPB0CbUN -aDdG7ZOizQrWXo7Da2MYIZ6eZUDqBKLdhZ5gZfVnisDfu/yeCgpENaKib1MPHpA8 -nZQjnPejbBDomNqY8HRzr5jvXNlwywBpjWGtegCKUY9xbSynjbfzIlMrWL4S+Rfl -+bOOQKRyYJWXmECmVyqY8cz2VUYmETjNcwC8VCDUxQnhtcCJ7Aej22hfYwVEPb/J -BsJBPq8WECCiGfJ9Y2y6TF+62KzG9Kfs5hqUeHhQy8V4TSi479ewwL7DH86XmIIK -chSANBS+7iyMtctjNZfmF9zYdGJFvjI/mbBR/lK66E515Inuf75XnL8hqlXuwqvG -ni+i03Aet1DzULZEIio4uIU6ioc1lGO9h7K2Xn4S7QQH1QoISNMWqXibUR0RCGjw -FsEDTt2QwJl8XXxoJCooM7BCcCQo+rMNVUHDjIwrdoQjPld3YZsUQQRcqH6bLuln -cfn5ufl8zTGWKydoj/iTz8KcjZ7w187AzQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+ -s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNtR1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh -6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQ -sTMBv4v5vYNXP9GgKbg8inUNT17BxzZYHfw5+q63ectgDm2on1e8CIRCZ76oBVwz -dkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV67yLANGMCDICE/OkWn6daipYDzW4iJQt -YPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ -1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNnvHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9i -aUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm1M/F1fK1J0e+lKlQuyonTXqXR22Y41wr -fP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEhEMxcM4/LMR+PABEBAAHCwrIEGAEKAAkF -gl8sAVYCmwIB3QkQ+/zIKgFeczDA+qAEGQEKAAwFgl47Z5UFgwB4TOAAIQkQfC+q -Tfk8N7IWIQQd3OFfCSF87i87N2B8L6pN+Tw3st58C/0exp0X2U4LqicSHEOSqHZj -jiysdqIELHGyo5DSPv92UFPp36aqjF9OFgtNNwSa56fmAVCD4+hor/fKARRIeIjF -qdIC5Y/9a4B10NQFJa5lsvB38x/d39LI2kEoglZnqWgdJskROo3vNQF4KlIcm6FH -dn4WI8UkC5oUUcrpZVMSKoacIaxLwqnXT42nIVgYYuqrd/ZagZZjG5WlrTOd5+NI -zi/l0fWProcPHGLjmAh4Thu8i7omtVw1nQaMnq9I77ffg3cPDgXknYrLL+q8xXh/ -0mEJyIhnmPwllWCSZuLv9DrD5pOexFfdlwXhf6cLzNpW6QhXD/Tf5KrqIPr9aOv8 -9xaEEXWh0vEby2kIsI2++ft+vfdIyxYw/wKqx0awTSnuBV1rG3z1dswX4BfoY66x -Bz3KOVqlz9+mG/FTRQwrgPvR+qgLCHbuotxoGN7fzW+PI75hQG5JQAqhsC9sHjQH -UrI21/VUNwzfw3v5pYsWuFb5bdQ3ASJetICQiMy7IW8WIQTRpm4aI7GCyZgPeIz7 -/MgqAV5zMG6/C/wLpPl/9e6Hf5wmXIUwpZNQbNZvpiCcyx9sXsHXaycOQVxn3McZ -nYOUP9/mobl1tIeDQyTNbkxWjU0zzJl8XQsDZerb5098pg+x7oGIL7M1vn5s5JMl -owROourqF88JEtOBxLMxlAM7X4hB48xKQ3Hu9hS1GdnqLKki4MqRGl4l5FUwyGOM -GjyS3TzkfiDJNwQxybQiC9n57ij20ieNyLfuWCMLcNNnZUgZtnF6wCctoq/0ZIWu -a7nvuA/XC2WW9YjEJJiWdy5109pqac+qWiY11HWy/nms4gpMdxVpT0RhrKGWq4o0 -M5q3ZElOoeN70UO3OSbU5EVrG7gB1GuwF9mTHUVlV0veSTw0axkta3FGT//XfSpD -lRrCkyLzwq0M+UUHQAuYpAfobDlDdnxxOD2jm5GyTzak3GSVFfjW09QFVO6HlGp5 -01/jtzkUiS6nwoHHkfnyn0beZuR8X6KlcrzLB0VFgQFLmkSM9cSOgYhD0PTu9aHb -hW1Hj9AO8lzggBQ= -=Nt+N ------END PGP PUBLIC KEY BLOCK----- -` - -const sigFromKeyWithExpiredCrossSig = `-----BEGIN PGP SIGNATURE----- - -wsDzBAABCgAGBYJfLAFsACEJEHwvqk35PDeyFiEEHdzhXwkhfO4vOzdgfC+qTfk8 -N7KiqwwAts4QGB7v9bABCC2qkTxJhmStC0wQMcHRcjL/qAiVnmasQWmvE9KVsdm3 -AaXd8mIx4a37/RRvr9dYrY2eE4uw72cMqPxNja2tvVXkHQvk1oEUqfkvbXs4ypKI -NyeTWjXNOTZEbg0hbm3nMy+Wv7zgB1CEvAsEboLDJlhGqPcD+X8a6CJGrBGUBUrv -KVmZr3U6vEzClz3DBLpoddCQseJRhT4YM1nKmBlZ5quh2LFgTSpajv5OsZheqt9y -EZAPbqmLhDmWRQwGzkWHKceKS7nZ/ox2WK6OS7Ob8ZGZkM64iPo6/EGj5Yc19vQN -AGiIaPEGszBBWlOpHTPhNm0LB0nMWqqaT87oNYwP8CQuuxDb6rKJ2lffCmZH27Lb -UbQZcH8J+0UhpeaiadPZxH5ATJAcenmVtVVMLVOFnm+eIlxzov9ntpgGYt8hLdXB -ITEG9mMgp3TGS9ZzSifMZ8UGtHdp9QdBg8NEVPFzDOMGxpc/Bftav7RRRuPiAER+ -7A5CBid5 -=aQkm ------END PGP SIGNATURE----- -` - -const signedMessageWithCriticalNotation = `-----BEGIN PGP MESSAGE----- - -owGbwMvMwMH4oOW7S46CznTG09xJDDE3Wl1KUotLuDousDAwcjBYiSmyXL+48d6x -U1PSGUxcj8IUszKBVMpMaWAAAgEGZpAeh9SKxNyCnFS95PzcytRiBi5OAZjyXXzM -f8WYLqv7TXP61Sa4rqT12CI3xaN73YS2pt089f96odCKaEPnWJ3iSGmzJaW/ug10 -2Zo8Wj2k4s7t8wt4H3HtTu+y5UZfV3VOO+l//sdE/o+Lsub8FZH7/eOq7OnbNp4n -vwjE8mqJXetNMfj8r2SCyvkEnlVRYR+/mnge+ib56FdJ8uKtqSxyvgA= -=fRXs ------END PGP MESSAGE-----` - -const criticalNotationSigner = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -mI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+ -fIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5 -GLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0 -JFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS -YS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6 -AgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki -Bq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf -9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rC4jQRSYS9OAQQA6R/PtBFa -JaT4jq10yqASk4sqwVMsc6HcifM5lSdxzExFP74naUMMyEsKHP53QxTF0Grqusag -Qg/ZtgT0CN1HUM152y7ACOdp1giKjpMzOTQClqCoclyvWOFB+L/SwGEIJf7LSCEr -woBuJifJc8xAVr0XX0JthoW+uP91eTQ3XpsAEQEAAYkBPQQYAQIACQUCUmEvTgIb -LgCoCRBKY2E6TW5AlJ0gBBkBAgAGBQJSYS9OAAoJEOCE90RsICyXuqIEANmmiRCA -SF7YK7PvFkieJNwzeK0V3F2lGX+uu6Y3Q/Zxdtwc4xR+me/CSBmsURyXTO29OWhP -GLszPH9zSJU9BdDi6v0yNprmFPX/1Ng0Abn/sCkwetvjxC1YIvTLFwtUL/7v6NS2 -bZpsUxRTg9+cSrMWWSNjiY9qUKajm1tuzPDZXAUEAMNmAN3xXN/Kjyvj2OK2ck0X -W748sl/tc3qiKPMJ+0AkMF7Pjhmh9nxqE9+QCEl7qinFqqBLjuzgUhBU4QlwX1GD -AtNTq6ihLMD5v1d82ZC7tNatdlDMGWnIdvEMCv2GZcuIqDQ9rXWs49e7tq1NncLY -hz3tYjKhoFTKEIq3y3Pp -=h/aX ------END PGP PUBLIC KEY BLOCK-----` - -const keyv5Test = `-----BEGIN PGP PRIVATE KEY BLOCK----- -Comment: Bob's OpenPGP Transferable Secret Key - -lQVYBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv -/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz -/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/ -5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3 -X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv -9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0 -qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb -SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb -vLIwa3T4CyshfT0AEQEAAQAL/RZqbJW2IqQDCnJi4Ozm++gPqBPiX1RhTWSjwxfM -cJKUZfzLj414rMKm6Jh1cwwGY9jekROhB9WmwaaKT8HtcIgrZNAlYzANGRCM4TLK -3VskxfSwKKna8l+s+mZglqbAjUg3wmFuf9Tj2xcUZYmyRm1DEmcN2ZzpvRtHgX7z -Wn1mAKUlSDJZSQks0zjuMNbupcpyJokdlkUg2+wBznBOTKzgMxVNC9b2g5/tMPUs -hGGWmF1UH+7AHMTaS6dlmr2ZBIyogdnfUqdNg5sZwsxSNrbglKP4sqe7X61uEAIQ -bD7rT3LonLbhkrj3I8wilUD8usIwt5IecoHhd9HziqZjRCc1BUBkboUEoyedbDV4 -i4qfsFZ6CEWoLuD5pW7dEp0M+WeuHXO164Rc+LnH6i1VQrpb1Okl4qO6ejIpIjBI -1t3GshtUu/mwGBBxs60KBX5g77mFQ9lLCRj8lSYqOsHRKBhUp4qM869VA+fD0BRP -fqPT0I9IH4Oa/A3jYJcg622GwQYA1LhnP208Waf6PkQSJ6kyr8ymY1yVh9VBE/g6 -fRDYA+pkqKnw9wfH2Qho3ysAA+OmVOX8Hldg+Pc0Zs0e5pCavb0En8iFLvTA0Q2E -LR5rLue9uD7aFuKFU/VdcddY9Ww/vo4k5p/tVGp7F8RYCFn9rSjIWbfvvZi1q5Tx -+akoZbga+4qQ4WYzB/obdX6SCmi6BndcQ1QdjCCQU6gpYx0MddVERbIp9+2SXDyL -hpxjSyz+RGsZi/9UAshT4txP4+MZBgDfK3ZqtW+h2/eMRxkANqOJpxSjMyLO/FXN -WxzTDYeWtHNYiAlOwlQZEPOydZFty9IVzzNFQCIUCGjQ/nNyhw7adSgUk3+BXEx/ -MyJPYY0BYuhLxLYcrfQ9nrhaVKxRJj25SVHj2ASsiwGJRZW4CC3uw40OYxfKEvNC -mer/VxM3kg8qqGf9KUzJ1dVdAvjyx2Hz6jY2qWCyRQ6IMjWHyd43C4r3jxooYKUC -YnstRQyb/gCSKahveSEjo07CiXMr88UGALwzEr3npFAsPW3osGaFLj49y1oRe11E -he9gCHFm+fuzbXrWmdPjYU5/ZdqdojzDqfu4ThfnipknpVUM1o6MQqkjM896FHm8 -zbKVFSMhEP6DPHSCexMFrrSgN03PdwHTO6iBaIBBFqmGY01tmJ03SxvSpiBPON9P -NVvy/6UZFedTq8A07OUAxO62YUSNtT5pmK2vzs3SAZJmbFbMh+NN204TRI72GlqT -t5hcfkuv8hrmwPS/ZR6q312mKQ6w/1pqO9qitCFCb2IgQmFiYmFnZSA8Ym9iQG9w -ZW5wZ3AuZXhhbXBsZT6JAc4EEwEKADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC -F4AWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAUCXaWe+gAKCRD7/MgqAV5zMG9sC/9U -2T3RrqEbw533FPNfEflhEVRIZ8gDXKM8hU6cqqEzCmzZT6xYTe6sv4y+PJBGXJFX -yhj0g6FDkSyboM5litOcTupURObVqMgA/Y4UKERznm4fzzH9qek85c4ljtLyNufe -doL2pp3vkGtn7eD0QFRaLLmnxPKQ/TlZKdLE1G3u8Uot8QHicaR6GnAdc5UXQJE3 -BiV7jZuDyWmZ1cUNwJkKL6oRtp+ZNDOQCrLNLecKHcgCqrpjSQG5oouba1I1Q6Vl -sP44dhA1nkmLHtxlTOzpeHj4jnk1FaXmyasurrrI5CgU/L2Oi39DGKTH/A/cywDN -4ZplIQ9zR8enkbXquUZvFDe+Xz+6xRXtb5MwQyWODB3nHw85HocLwRoIN9WdQEI+ -L8a/56AuOwhs8llkSuiITjR7r9SgKJC2WlAHl7E8lhJ3VDW3ELC56KH308d6mwOG -ZRAqIAKzM1T5FGjMBhq7ZV0eqdEntBh3EcOIfj2M8rg1MzJv+0mHZOIjByawikad -BVgEXaWc8gEMANYwv1xsYyunXYK0X1vY/rP1NNPvhLyLIE7NpK90YNBj+xS1ldGD -bUdZqZeef2xJe8gMQg05DoD1DF3GipZ0Ies65beh+d5hegb7N4pzh0LzrBrVNHar -29b5ExdI7i4iYD5TO6Vr/qTUOiAN/byqELEzAb+L+b2DVz/RoCm4PIp1DU9ewcc2 -WB38Ofqut3nLYA5tqJ9XvAiEQme+qAVcM3ZFcaMt4I4dXhDZZNg+D9LiTWcxdUPB -leu8iwDRjAgyAhPzpFp+nWoqWA81uIiULWD1Fj+IVoY3ZvgivoYOiEFBJ9lbb4te -g9m5UT/AaVDTWuHzbspVlbiVe+qyB77C2daWzNyx6UYBPLOo4r0t0c91kbNE5lgj -Z7xz6los0N1U8vq91EFSeQJoSQ62XWavYmlCLmdNT6BNfgh4icLsT7Vr1QMX9jzn -JtTPxdXytSdHvpSpULsqJ016l0dtmONcK3z9mj5N5z0k1tg1AH970TGYOe2aUcSx -IRDMXDOPyzEfjwARAQABAAv9F2CwsjS+Sjh1M1vegJbZjei4gF1HHpEM0K0PSXsp -SfVvpR4AoSJ4He6CXSMWg0ot8XKtDuZoV9jnJaES5UL9pMAD7JwIOqZm/DYVJM5h -OASCh1c356/wSbFbzRHPtUdZO9Q30WFNJM5pHbCJPjtNoRmRGkf71RxtvHBzy7np -Ga+W6U/NVKHw0i0CYwMI0YlKDakYW3Pm+QL+gHZFvngGweTod0f9l2VLLAmeQR/c -+EZs7lNumhuZ8mXcwhUc9JQIhOkpO+wreDysEFkAcsKbkQP3UDUsA1gFx9pbMzT0 -tr1oZq2a4QBtxShHzP/ph7KLpN+6qtjks3xB/yjTgaGmtrwM8tSe0wD1RwXS+/1o -BHpXTnQ7TfeOGUAu4KCoOQLv6ELpKWbRBLWuiPwMdbGpvVFALO8+kvKAg9/r+/ny -zM2GQHY+J3Jh5JxPiJnHfXNZjIKLbFbIPdSKNyJBuazXW8xIa//mEHMI5OcvsZBK -clAIp7LXzjEjKXIwHwDcTn9pBgDpdOKTHOtJ3JUKx0rWVsDH6wq6iKV/FTVSY5jl -zN+puOEsskF1Lfxn9JsJihAVO3yNsp6RvkKtyNlFazaCVKtDAmkjoh60XNxcNRqr -gCnwdpbgdHP6v/hvZY54ZaJjz6L2e8unNEkYLxDt8cmAyGPgH2XgL7giHIp9jrsQ -aS381gnYwNX6wE1aEikgtY91nqJjwPlibF9avSyYQoMtEqM/1UjTjB2KdD/MitK5 -fP0VpvuXpNYZedmyq4UOMwdkiNMGAOrfmOeT0olgLrTMT5H97Cn3Yxbk13uXHNu/ -ZUZZNe8s+QtuLfUlKAJtLEUutN33TlWQY522FV0m17S+b80xJib3yZVJteVurrh5 -HSWHAM+zghQAvCesg5CLXa2dNMkTCmZKgCBvfDLZuZbjFwnwCI6u/NhOY9egKuUf -SA/je/RXaT8m5VxLYMxwqQXKApzD87fv0tLPlVIEvjEsaf992tFEFSNPcG1l/jpd -5AVXw6kKuf85UkJtYR1x2MkQDrqY1QX/XMw00kt8y9kMZUre19aCArcmor+hDhRJ -E3Gt4QJrD9z/bICESw4b4z2DbgD/Xz9IXsA/r9cKiM1h5QMtXvuhyfVeM01enhxM -GbOH3gjqqGNKysx0UODGEwr6AV9hAd8RWXMchJLaExK9J5SRawSg671ObAU24SdY -vMQ9Z4kAQ2+1ReUZzf3ogSMRZtMT+d18gT6L90/y+APZIaoArLPhebIAGq39HLmJ -26x3z0WAgrpA1kNsjXEXkoiZGPLKIGoe3hqJAbYEGAEKACAWIQTRpm4aI7GCyZgP -eIz7/MgqAV5zMAUCXaWc8gIbDAAKCRD7/MgqAV5zMOn/C/9ugt+HZIwX308zI+QX -c5vDLReuzmJ3ieE0DMO/uNSC+K1XEioSIZP91HeZJ2kbT9nn9fuReuoff0T0Dief -rbwcIQQHFFkrqSp1K3VWmUGp2JrUsXFVdjy/fkBIjTd7c5boWljv/6wAsSfiv2V0 -JSM8EFU6TYXxswGjFVfc6X97tJNeIrXL+mpSmPPqy2bztcCCHkWS5lNLWQw+R7Vg -71Fe6yBSNVrqC2/imYG2J9zlowjx1XU63Wdgqp2Wxt0l8OmsB/W80S1fRF5G4SDH -s9HXglXXqPsBRZJYfP+VStm9L5P/sKjCcX6WtZR7yS6G8zj/X767MLK/djANvpPd -NVniEke6hM3CNBXYPAMhQBMWhCulcoz+0lxi8L34rMN+Dsbma96psdUrn7uLaB91 -6we0CTfF8qqm7BsVAgalon/UUiuMY80U3ueoj3okiSTiHIjD/YtpXSPioC8nMng7 -xqAY9Bwizt4FWgXuLm1a4+So4V9j1TRCXd12Uc2l2RNmgDE= -=miES ------END PGP PRIVATE KEY BLOCK----- -` - -const certv5Test = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -lGEFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd -fj75iux+my8QAAAAAAAiAQCHZ1SnSUmWqxEsoI6facIVZQu6mph3cBFzzTvcm5lA -Ng5ctBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0e8mHJGQC -X5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyICAQYVCgkI -CwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3wwJAXRJy9 -M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2BZxmBVyR9OQSAAAA -MgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0YvYWWAoD -AQgHAAAAAAAiAP9OdAPppjU1WwpqjIItkxr+VPQRT8Zm/Riw7U3F6v3OiBFHiHoF -GBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVAUCXJH05AIb -DAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijhob2U5AQC+RtOHCHx7 -TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw== -=IiS2 ------END PGP PRIVATE KEY BLOCK----- -` - -const msgv5Test = `-----BEGIN PGP MESSAGE----- - -wcDMA3wvqk35PDeyAQv+PcQiLsoYTH30nJYQh3j3cJaO2+jErtVCrIQRIU0+ -rmgMddERYST4A9mA0DQIiTI4FQ0Lp440D3BWCgpq3LlNWewGzduaWwym5rN6 -cwHz5ccDqOcqbd9X0GXXGy/ZH/ljSgzuVMIytMAXKdF/vrRrVgH/+I7cxvm9 -HwnhjMN5dF0j4aEt996H2T7cbtzSr2GN9SWGW8Gyu7I8Zx73hgrGUI7gDiJB -Afaff+P6hfkkHSGOItr94dde8J/7AUF4VEwwxdVVPvsNEFyvv6gRIbYtOCa2 -6RE6h1V/QTxW2O7zZgzWALrE2ui0oaYr9QuqQSssd9CdgExLfdPbI+3/ZAnE -v31Idzpk3/6ILiakYHtXkElPXvf46mCNpobty8ysT34irF+fy3C1p3oGwAsx -5VDV9OSFU6z5U+UPbSPYAy9rkc5ZssuIKxCER2oTvZ2L8Q5cfUvEUiJtRGGn -CJlHrVDdp3FssKv2tlKgLkvxJLyoOjuEkj44H1qRk+D02FzmmUT/0sAHAYYx -lTir6mjHeLpcGjn4waUuWIAJyph8SxUexP60bic0L0NBa6Qp5SxxijKsPIDb -FPHxWwfJSDZRrgUyYT7089YFB/ZM4FHyH9TZcnxn0f0xIB7NS6YNDsxzN2zT -EVEYf+De4qT/dQTsdww78Chtcv9JY9r2kDm77dk2MUGHL2j7n8jasbLtgA7h -pn2DMIWLrGamMLWRmlwslolKr1sMV5x8w+5Ias6C33iBMl9phkg42an0gYmc -byVJHvLO/XErtC+GNIJeMg== -=liRq ------END PGP MESSAGE----- -` diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/s2k/s2k.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/s2k/s2k.go deleted file mode 100644 index 6871b84fc9f..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/s2k/s2k.go +++ /dev/null @@ -1,436 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package s2k implements the various OpenPGP string-to-key transforms as -// specified in RFC 4800 section 3.7.1, and Argon2 specified in -// draft-ietf-openpgp-crypto-refresh-08 section 3.7.1.4. -package s2k // import "github.com/ProtonMail/go-crypto/openpgp/s2k" - -import ( - "crypto" - "hash" - "io" - "strconv" - - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" - "golang.org/x/crypto/argon2" -) - -type Mode uint8 - -// Defines the default S2KMode constants -// -// 0 (simple), 1(salted), 3(iterated), 4(argon2) -const ( - SimpleS2K Mode = 0 - SaltedS2K Mode = 1 - IteratedSaltedS2K Mode = 3 - Argon2S2K Mode = 4 - GnuS2K Mode = 101 -) - -const Argon2SaltSize int = 16 - -// Params contains all the parameters of the s2k packet -type Params struct { - // mode is the mode of s2k function. - // It can be 0 (simple), 1(salted), 3(iterated) - // 2(reserved) 100-110(private/experimental). - mode Mode - // hashId is the ID of the hash function used in any of the modes - hashId byte - // salt is a byte array to use as a salt in hashing process or argon2 - saltBytes [Argon2SaltSize]byte - // countByte is used to determine how many rounds of hashing are to - // be performed in s2k mode 3. See RFC 4880 Section 3.7.1.3. - countByte byte - // passes is a parameter in Argon2 to determine the number of iterations - // See RFC the crypto refresh Section 3.7.1.4. - passes byte - // parallelism is a parameter in Argon2 to determine the degree of paralellism - // See RFC the crypto refresh Section 3.7.1.4. - parallelism byte - // memoryExp is a parameter in Argon2 to determine the memory usage - // i.e., 2 ** memoryExp kibibytes - // See RFC the crypto refresh Section 3.7.1.4. - memoryExp byte -} - -// encodeCount converts an iterative "count" in the range 1024 to -// 65011712, inclusive, to an encoded count. The return value is the -// octet that is actually stored in the GPG file. encodeCount panics -// if i is not in the above range (encodedCount above takes care to -// pass i in the correct range). See RFC 4880 Section 3.7.7.1. -func encodeCount(i int) uint8 { - if i < 65536 || i > 65011712 { - panic("count arg i outside the required range") - } - - for encoded := 96; encoded < 256; encoded++ { - count := decodeCount(uint8(encoded)) - if count >= i { - return uint8(encoded) - } - } - - return 255 -} - -// decodeCount returns the s2k mode 3 iterative "count" corresponding to -// the encoded octet c. -func decodeCount(c uint8) int { - return (16 + int(c&15)) << (uint32(c>>4) + 6) -} - -// encodeMemory converts the Argon2 "memory" in the range parallelism*8 to -// 2**31, inclusive, to an encoded memory. The return value is the -// octet that is actually stored in the GPG file. encodeMemory panics -// if is not in the above range -// See OpenPGP crypto refresh Section 3.7.1.4. -func encodeMemory(memory uint32, parallelism uint8) uint8 { - if memory < (8*uint32(parallelism)) || memory > uint32(2147483648) { - panic("Memory argument memory is outside the required range") - } - - for exp := 3; exp < 31; exp++ { - compare := decodeMemory(uint8(exp)) - if compare >= memory { - return uint8(exp) - } - } - - return 31 -} - -// decodeMemory computes the decoded memory in kibibytes as 2**memoryExponent -func decodeMemory(memoryExponent uint8) uint32 { - return uint32(1) << memoryExponent -} - -// Simple writes to out the result of computing the Simple S2K function (RFC -// 4880, section 3.7.1.1) using the given hash and input passphrase. -func Simple(out []byte, h hash.Hash, in []byte) { - Salted(out, h, in, nil) -} - -var zero [1]byte - -// Salted writes to out the result of computing the Salted S2K function (RFC -// 4880, section 3.7.1.2) using the given hash, input passphrase and salt. -func Salted(out []byte, h hash.Hash, in []byte, salt []byte) { - done := 0 - var digest []byte - - for i := 0; done < len(out); i++ { - h.Reset() - for j := 0; j < i; j++ { - h.Write(zero[:]) - } - h.Write(salt) - h.Write(in) - digest = h.Sum(digest[:0]) - n := copy(out[done:], digest) - done += n - } -} - -// Iterated writes to out the result of computing the Iterated and Salted S2K -// function (RFC 4880, section 3.7.1.3) using the given hash, input passphrase, -// salt and iteration count. -func Iterated(out []byte, h hash.Hash, in []byte, salt []byte, count int) { - combined := make([]byte, len(in)+len(salt)) - copy(combined, salt) - copy(combined[len(salt):], in) - - if count < len(combined) { - count = len(combined) - } - - done := 0 - var digest []byte - for i := 0; done < len(out); i++ { - h.Reset() - for j := 0; j < i; j++ { - h.Write(zero[:]) - } - written := 0 - for written < count { - if written+len(combined) > count { - todo := count - written - h.Write(combined[:todo]) - written = count - } else { - h.Write(combined) - written += len(combined) - } - } - digest = h.Sum(digest[:0]) - n := copy(out[done:], digest) - done += n - } -} - -// Argon2 writes to out the key derived from the password (in) with the Argon2 -// function (the crypto refresh, section 3.7.1.4) -func Argon2(out []byte, in []byte, salt []byte, passes uint8, paralellism uint8, memoryExp uint8) { - key := argon2.IDKey(in, salt, uint32(passes), decodeMemory(memoryExp), paralellism, uint32(len(out))) - copy(out[:], key) -} - -// Generate generates valid parameters from given configuration. -// It will enforce the Iterated and Salted or Argon2 S2K method. -func Generate(rand io.Reader, c *Config) (*Params, error) { - var params *Params - if c != nil && c.Mode() == Argon2S2K { - // handle Argon2 case - argonConfig := c.Argon2() - params = &Params{ - mode: Argon2S2K, - passes: argonConfig.Passes(), - parallelism: argonConfig.Parallelism(), - memoryExp: argonConfig.EncodedMemory(), - } - } else if c != nil && c.PassphraseIsHighEntropy && c.Mode() == SaltedS2K { // Allow SaltedS2K if PassphraseIsHighEntropy - hashId, ok := algorithm.HashToHashId(c.hash()) - if !ok { - return nil, errors.UnsupportedError("no such hash") - } - - params = &Params{ - mode: SaltedS2K, - hashId: hashId, - } - } else { // Enforce IteratedSaltedS2K method otherwise - hashId, ok := algorithm.HashToHashId(c.hash()) - if !ok { - return nil, errors.UnsupportedError("no such hash") - } - if c != nil { - c.S2KMode = IteratedSaltedS2K - } - params = &Params{ - mode: IteratedSaltedS2K, - hashId: hashId, - countByte: c.EncodedCount(), - } - } - if _, err := io.ReadFull(rand, params.salt()); err != nil { - return nil, err - } - return params, nil -} - -// Parse reads a binary specification for a string-to-key transformation from r -// and returns a function which performs that transform. If the S2K is a special -// GNU extension that indicates that the private key is missing, then the error -// returned is errors.ErrDummyPrivateKey. -func Parse(r io.Reader) (f func(out, in []byte), err error) { - params, err := ParseIntoParams(r) - if err != nil { - return nil, err - } - - return params.Function() -} - -// ParseIntoParams reads a binary specification for a string-to-key -// transformation from r and returns a struct describing the s2k parameters. -func ParseIntoParams(r io.Reader) (params *Params, err error) { - var buf [Argon2SaltSize + 3]byte - - _, err = io.ReadFull(r, buf[:1]) - if err != nil { - return - } - - params = &Params{ - mode: Mode(buf[0]), - } - - switch params.mode { - case SimpleS2K: - _, err = io.ReadFull(r, buf[:1]) - if err != nil { - return nil, err - } - params.hashId = buf[0] - return params, nil - case SaltedS2K: - _, err = io.ReadFull(r, buf[:9]) - if err != nil { - return nil, err - } - params.hashId = buf[0] - copy(params.salt(), buf[1:9]) - return params, nil - case IteratedSaltedS2K: - _, err = io.ReadFull(r, buf[:10]) - if err != nil { - return nil, err - } - params.hashId = buf[0] - copy(params.salt(), buf[1:9]) - params.countByte = buf[9] - return params, nil - case Argon2S2K: - _, err = io.ReadFull(r, buf[:Argon2SaltSize+3]) - if err != nil { - return nil, err - } - copy(params.salt(), buf[:Argon2SaltSize]) - params.passes = buf[Argon2SaltSize] - params.parallelism = buf[Argon2SaltSize+1] - params.memoryExp = buf[Argon2SaltSize+2] - if err := validateArgon2Params(params); err != nil { - return nil, err - } - return params, nil - case GnuS2K: - // This is a GNU extension. See - // https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;h=fe55ae16ab4e26d8356dc574c9e8bc935e71aef1;hb=23191d7851eae2217ecdac6484349849a24fd94a#l1109 - if _, err = io.ReadFull(r, buf[:5]); err != nil { - return nil, err - } - params.hashId = buf[0] - if buf[1] == 'G' && buf[2] == 'N' && buf[3] == 'U' && buf[4] == 1 { - return params, nil - } - return nil, errors.UnsupportedError("GNU S2K extension") - } - - return nil, errors.UnsupportedError("S2K function") -} - -func (params *Params) Mode() Mode { - return params.mode -} - -func (params *Params) Dummy() bool { - return params != nil && params.mode == GnuS2K -} - -func (params *Params) salt() []byte { - switch params.mode { - case SaltedS2K, IteratedSaltedS2K: - return params.saltBytes[:8] - case Argon2S2K: - return params.saltBytes[:Argon2SaltSize] - default: - return nil - } -} - -func (params *Params) Function() (f func(out, in []byte), err error) { - if params.Dummy() { - return nil, errors.ErrDummyPrivateKey("dummy key found") - } - var hashObj crypto.Hash - if params.mode != Argon2S2K { - var ok bool - hashObj, ok = algorithm.HashIdToHashWithSha1(params.hashId) - if !ok { - return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(params.hashId))) - } - if !hashObj.Available() { - return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashObj))) - } - } - - switch params.mode { - case SimpleS2K: - f := func(out, in []byte) { - Simple(out, hashObj.New(), in) - } - - return f, nil - case SaltedS2K: - f := func(out, in []byte) { - Salted(out, hashObj.New(), in, params.salt()) - } - - return f, nil - case IteratedSaltedS2K: - f := func(out, in []byte) { - Iterated(out, hashObj.New(), in, params.salt(), decodeCount(params.countByte)) - } - - return f, nil - case Argon2S2K: - f := func(out, in []byte) { - Argon2(out, in, params.salt(), params.passes, params.parallelism, params.memoryExp) - } - return f, nil - } - - return nil, errors.UnsupportedError("S2K function") -} - -func (params *Params) Serialize(w io.Writer) (err error) { - if _, err = w.Write([]byte{uint8(params.mode)}); err != nil { - return - } - if params.mode != Argon2S2K { - if _, err = w.Write([]byte{params.hashId}); err != nil { - return - } - } - if params.Dummy() { - _, err = w.Write(append([]byte("GNU"), 1)) - return - } - if params.mode > 0 { - if _, err = w.Write(params.salt()); err != nil { - return - } - if params.mode == IteratedSaltedS2K { - _, err = w.Write([]byte{params.countByte}) - } - if params.mode == Argon2S2K { - _, err = w.Write([]byte{params.passes, params.parallelism, params.memoryExp}) - } - } - return -} - -// Serialize salts and stretches the given passphrase and writes the -// resulting key into key. It also serializes an S2K descriptor to -// w. The key stretching can be configured with c, which may be -// nil. In that case, sensible defaults will be used. -func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte, c *Config) error { - params, err := Generate(rand, c) - if err != nil { - return err - } - err = params.Serialize(w) - if err != nil { - return err - } - - f, err := params.Function() - if err != nil { - return err - } - f(key, passphrase) - return nil -} - -// validateArgon2Params checks that the argon2 parameters are valid according to RFC9580. -func validateArgon2Params(params *Params) error { - // The number of passes t and the degree of parallelism p MUST be non-zero. - if params.parallelism == 0 { - return errors.StructuralError("invalid argon2 params: parallelism is 0") - } - if params.passes == 0 { - return errors.StructuralError("invalid argon2 params: iterations is 0") - } - - // The encoded memory size MUST be a value from 3+ceil(log2(p)) to 31, - // such that the decoded memory size m is a value from 8*p to 2^31. - if params.memoryExp > 31 || decodeMemory(params.memoryExp) < 8*uint32(params.parallelism) { - return errors.StructuralError("invalid argon2 params: memory is out of bounds") - } - - return nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/s2k/s2k_cache.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/s2k/s2k_cache.go deleted file mode 100644 index 616e0d12c6c..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/s2k/s2k_cache.go +++ /dev/null @@ -1,26 +0,0 @@ -package s2k - -// Cache stores keys derived with s2k functions from one passphrase -// to avoid recomputation if multiple items are encrypted with -// the same parameters. -type Cache map[Params][]byte - -// GetOrComputeDerivedKey tries to retrieve the key -// for the given s2k parameters from the cache. -// If there is no hit, it derives the key with the s2k function from the passphrase, -// updates the cache, and returns the key. -func (c *Cache) GetOrComputeDerivedKey(passphrase []byte, params *Params, expectedKeySize int) ([]byte, error) { - key, found := (*c)[*params] - if !found || len(key) != expectedKeySize { - var err error - derivedKey := make([]byte, expectedKeySize) - s2k, err := params.Function() - if err != nil { - return nil, err - } - s2k(derivedKey, passphrase) - (*c)[*params] = key - return derivedKey, nil - } - return key, nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/s2k/s2k_config.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/s2k/s2k_config.go deleted file mode 100644 index b93db1ab853..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/s2k/s2k_config.go +++ /dev/null @@ -1,129 +0,0 @@ -package s2k - -import "crypto" - -// Config collects configuration parameters for s2k key-stretching -// transformations. A nil *Config is valid and results in all default -// values. -type Config struct { - // S2K (String to Key) mode, used for key derivation in the context of secret key encryption - // and passphrase-encrypted data. Either s2k.Argon2S2K or s2k.IteratedSaltedS2K may be used. - // If the passphrase is a high-entropy key, indicated by setting PassphraseIsHighEntropy to true, - // s2k.SaltedS2K can also be used. - // Note: Argon2 is the strongest option but not all OpenPGP implementations are compatible with it - //(pending standardisation). - // 0 (simple), 1(salted), 3(iterated), 4(argon2) - // 2(reserved) 100-110(private/experimental). - S2KMode Mode - // Only relevant if S2KMode is not set to s2k.Argon2S2K. - // Hash is the default hash function to be used. If - // nil, SHA256 is used. - Hash crypto.Hash - // Argon2 parameters for S2K (String to Key). - // Only relevant if S2KMode is set to s2k.Argon2S2K. - // If nil, default parameters are used. - // For more details on the choice of parameters, see https://tools.ietf.org/html/rfc9106#section-4. - Argon2Config *Argon2Config - // Only relevant if S2KMode is set to s2k.IteratedSaltedS2K. - // Iteration count for Iterated S2K (String to Key). It - // determines the strength of the passphrase stretching when - // the said passphrase is hashed to produce a key. S2KCount - // should be between 65536 and 65011712, inclusive. If Config - // is nil or S2KCount is 0, the value 16777216 used. Not all - // values in the above range can be represented. S2KCount will - // be rounded up to the next representable value if it cannot - // be encoded exactly. When set, it is strongly encrouraged to - // use a value that is at least 65536. See RFC 4880 Section - // 3.7.1.3. - S2KCount int - // Indicates whether the passphrase passed by the application is a - // high-entropy key (e.g. it's randomly generated or derived from - // another passphrase using a strong key derivation function). - // When true, allows the S2KMode to be s2k.SaltedS2K. - // When the passphrase is not a high-entropy key, using SaltedS2K is - // insecure, and not allowed by draft-ietf-openpgp-crypto-refresh-08. - PassphraseIsHighEntropy bool -} - -// Argon2Config stores the Argon2 parameters -// A nil *Argon2Config is valid and results in all default -type Argon2Config struct { - NumberOfPasses uint8 - DegreeOfParallelism uint8 - // Memory specifies the desired Argon2 memory usage in kibibytes. - // For example memory=64*1024 sets the memory cost to ~64 MB. - Memory uint32 -} - -func (c *Config) Mode() Mode { - if c == nil { - return IteratedSaltedS2K - } - return c.S2KMode -} - -func (c *Config) hash() crypto.Hash { - if c == nil || uint(c.Hash) == 0 { - return crypto.SHA256 - } - - return c.Hash -} - -func (c *Config) Argon2() *Argon2Config { - if c == nil || c.Argon2Config == nil { - return nil - } - return c.Argon2Config -} - -// EncodedCount get encoded count -func (c *Config) EncodedCount() uint8 { - if c == nil || c.S2KCount == 0 { - return 224 // The common case. Corresponding to 16777216 - } - - i := c.S2KCount - - switch { - case i < 65536: - i = 65536 - case i > 65011712: - i = 65011712 - } - - return encodeCount(i) -} - -func (c *Argon2Config) Passes() uint8 { - if c == nil || c.NumberOfPasses == 0 { - return 3 - } - return c.NumberOfPasses -} - -func (c *Argon2Config) Parallelism() uint8 { - if c == nil || c.DegreeOfParallelism == 0 { - return 4 - } - return c.DegreeOfParallelism -} - -func (c *Argon2Config) EncodedMemory() uint8 { - if c == nil || c.Memory == 0 { - return 16 // 64 MiB of RAM - } - - memory := c.Memory - lowerBound := uint32(c.Parallelism()) * 8 - upperBound := uint32(2147483648) - - switch { - case memory < lowerBound: - memory = lowerBound - case memory > upperBound: - memory = upperBound - } - - return encodeMemory(memory, c.Parallelism()) -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/write.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/write.go deleted file mode 100644 index b0f6ef7b092..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/write.go +++ /dev/null @@ -1,620 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package openpgp - -import ( - "crypto" - "hash" - "io" - "strconv" - "time" - - "github.com/ProtonMail/go-crypto/openpgp/armor" - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" - "github.com/ProtonMail/go-crypto/openpgp/packet" -) - -// DetachSign signs message with the private key from signer (which must -// already have been decrypted) and writes the signature to w. -// If config is nil, sensible defaults will be used. -func DetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { - return detachSign(w, signer, message, packet.SigTypeBinary, config) -} - -// ArmoredDetachSign signs message with the private key from signer (which -// must already have been decrypted) and writes an armored signature to w. -// If config is nil, sensible defaults will be used. -func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) (err error) { - return armoredDetachSign(w, signer, message, packet.SigTypeBinary, config) -} - -// DetachSignText signs message (after canonicalising the line endings) with -// the private key from signer (which must already have been decrypted) and -// writes the signature to w. -// If config is nil, sensible defaults will be used. -func DetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { - return detachSign(w, signer, message, packet.SigTypeText, config) -} - -// ArmoredDetachSignText signs message (after canonicalising the line endings) -// with the private key from signer (which must already have been decrypted) -// and writes an armored signature to w. -// If config is nil, sensible defaults will be used. -func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { - return armoredDetachSign(w, signer, message, packet.SigTypeText, config) -} - -func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { - out, err := armor.Encode(w, SignatureType, nil) - if err != nil { - return - } - err = detachSign(out, signer, message, sigType, config) - if err != nil { - return - } - return out.Close() -} - -func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { - signingKey, ok := signer.SigningKeyById(config.Now(), config.SigningKey()) - if !ok { - return errors.InvalidArgumentError("no valid signing keys") - } - if signingKey.PrivateKey == nil { - return errors.InvalidArgumentError("signing key doesn't have a private key") - } - if signingKey.PrivateKey.Encrypted { - return errors.InvalidArgumentError("signing key is encrypted") - } - if _, ok := algorithm.HashToHashId(config.Hash()); !ok { - return errors.InvalidArgumentError("invalid hash function") - } - - sig := createSignaturePacket(signingKey.PublicKey, sigType, config) - - h, err := sig.PrepareSign(config) - if err != nil { - return - } - wrappedHash, err := wrapHashForSignature(h, sig.SigType) - if err != nil { - return - } - if _, err = io.Copy(wrappedHash, message); err != nil { - return err - } - - err = sig.Sign(h, signingKey.PrivateKey, config) - if err != nil { - return - } - - return sig.Serialize(w) -} - -// FileHints contains metadata about encrypted files. This metadata is, itself, -// encrypted. -type FileHints struct { - // IsBinary can be set to hint that the contents are binary data. - IsBinary bool - // FileName hints at the name of the file that should be written. It's - // truncated to 255 bytes if longer. It may be empty to suggest that the - // file should not be written to disk. It may be equal to "_CONSOLE" to - // suggest the data should not be written to disk. - FileName string - // ModTime contains the modification time of the file, or the zero time if not applicable. - ModTime time.Time -} - -// SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase. -// The resulting WriteCloser must be closed after the contents of the file have -// been written. -// If config is nil, sensible defaults will be used. -func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { - if hints == nil { - hints = &FileHints{} - } - - key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config) - if err != nil { - return - } - - var w io.WriteCloser - cipherSuite := packet.CipherSuite{ - Cipher: config.Cipher(), - Mode: config.AEAD().Mode(), - } - w, err = packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), config.AEAD() != nil, cipherSuite, key, config) - if err != nil { - return - } - - literalData := w - if algo := config.Compression(); algo != packet.CompressionNone { - var compConfig *packet.CompressionConfig - if config != nil { - compConfig = config.CompressionConfig - } - literalData, err = packet.SerializeCompressed(w, algo, compConfig) - if err != nil { - return - } - } - - var epochSeconds uint32 - if !hints.ModTime.IsZero() { - epochSeconds = uint32(hints.ModTime.Unix()) - } - return packet.SerializeLiteral(literalData, hints.IsBinary, hints.FileName, epochSeconds) -} - -// intersectPreferences mutates and returns a prefix of a that contains only -// the values in the intersection of a and b. The order of a is preserved. -func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) { - var j int - for _, v := range a { - for _, v2 := range b { - if v == v2 { - a[j] = v - j++ - break - } - } - } - - return a[:j] -} - -// intersectPreferences mutates and returns a prefix of a that contains only -// the values in the intersection of a and b. The order of a is preserved. -func intersectCipherSuites(a [][2]uint8, b [][2]uint8) (intersection [][2]uint8) { - var j int - for _, v := range a { - for _, v2 := range b { - if v[0] == v2[0] && v[1] == v2[1] { - a[j] = v - j++ - break - } - } - } - - return a[:j] -} - -func hashToHashId(h crypto.Hash) uint8 { - v, ok := algorithm.HashToHashId(h) - if !ok { - panic("tried to convert unknown hash") - } - return v -} - -// EncryptText encrypts a message to a number of recipients and, optionally, -// signs it. Optional information is contained in 'hints', also encrypted, that -// aids the recipients in processing the message. The resulting WriteCloser -// must be closed after the contents of the file have been written. If config -// is nil, sensible defaults will be used. The signing is done in text mode. -func EncryptText(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { - return encrypt(ciphertext, ciphertext, to, signed, hints, packet.SigTypeText, config) -} - -// Encrypt encrypts a message to a number of recipients and, optionally, signs -// it. hints contains optional information, that is also encrypted, that aids -// the recipients in processing the message. The resulting WriteCloser must -// be closed after the contents of the file have been written. -// If config is nil, sensible defaults will be used. -func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { - return encrypt(ciphertext, ciphertext, to, signed, hints, packet.SigTypeBinary, config) -} - -// EncryptSplit encrypts a message to a number of recipients and, optionally, signs -// it. hints contains optional information, that is also encrypted, that aids -// the recipients in processing the message. The resulting WriteCloser must -// be closed after the contents of the file have been written. -// If config is nil, sensible defaults will be used. -func EncryptSplit(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { - return encrypt(keyWriter, dataWriter, to, signed, hints, packet.SigTypeBinary, config) -} - -// EncryptTextSplit encrypts a message to a number of recipients and, optionally, signs -// it. hints contains optional information, that is also encrypted, that aids -// the recipients in processing the message. The resulting WriteCloser must -// be closed after the contents of the file have been written. -// If config is nil, sensible defaults will be used. -func EncryptTextSplit(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { - return encrypt(keyWriter, dataWriter, to, signed, hints, packet.SigTypeText, config) -} - -// writeAndSign writes the data as a payload package and, optionally, signs -// it. hints contains optional information, that is also encrypted, -// that aids the recipients in processing the message. The resulting -// WriteCloser must be closed after the contents of the file have been -// written. If config is nil, sensible defaults will be used. -func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entity, hints *FileHints, sigType packet.SignatureType, config *packet.Config) (plaintext io.WriteCloser, err error) { - var signer *packet.PrivateKey - if signed != nil { - signKey, ok := signed.SigningKeyById(config.Now(), config.SigningKey()) - if !ok { - return nil, errors.InvalidArgumentError("no valid signing keys") - } - signer = signKey.PrivateKey - if signer == nil { - return nil, errors.InvalidArgumentError("no private key in signing key") - } - if signer.Encrypted { - return nil, errors.InvalidArgumentError("signing key must be decrypted") - } - } - - var hash crypto.Hash - for _, hashId := range candidateHashes { - if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() { - hash = h - break - } - } - - // If the hash specified by config is a candidate, we'll use that. - if configuredHash := config.Hash(); configuredHash.Available() { - for _, hashId := range candidateHashes { - if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash { - hash = h - break - } - } - } - - if hash == 0 { - hashId := candidateHashes[0] - name, ok := algorithm.HashIdToString(hashId) - if !ok { - name = "#" + strconv.Itoa(int(hashId)) - } - return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)") - } - - var salt []byte - if signer != nil { - var opsVersion = 3 - if signer.Version == 6 { - opsVersion = signer.Version - } - ops := &packet.OnePassSignature{ - Version: opsVersion, - SigType: sigType, - Hash: hash, - PubKeyAlgo: signer.PubKeyAlgo, - KeyId: signer.KeyId, - IsLast: true, - } - if opsVersion == 6 { - ops.KeyFingerprint = signer.Fingerprint - salt, err = packet.SignatureSaltForHash(hash, config.Random()) - if err != nil { - return nil, err - } - ops.Salt = salt - } - if err := ops.Serialize(payload); err != nil { - return nil, err - } - } - - if hints == nil { - hints = &FileHints{} - } - - w := payload - if signer != nil { - // If we need to write a signature packet after the literal - // data then we need to stop literalData from closing - // encryptedData. - w = noOpCloser{w} - - } - var epochSeconds uint32 - if !hints.ModTime.IsZero() { - epochSeconds = uint32(hints.ModTime.Unix()) - } - literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds) - if err != nil { - return nil, err - } - - if signer != nil { - h, wrappedHash, err := hashForSignature(hash, sigType, salt) - if err != nil { - return nil, err - } - metadata := &packet.LiteralData{ - Format: 'u', - FileName: hints.FileName, - Time: epochSeconds, - } - if hints.IsBinary { - metadata.Format = 'b' - } - return signatureWriter{payload, literalData, hash, wrappedHash, h, salt, signer, sigType, config, metadata}, nil - } - return literalData, nil -} - -// encrypt encrypts a message to a number of recipients and, optionally, signs -// it. hints contains optional information, that is also encrypted, that aids -// the recipients in processing the message. The resulting WriteCloser must -// be closed after the contents of the file have been written. -// If config is nil, sensible defaults will be used. -func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, sigType packet.SignatureType, config *packet.Config) (plaintext io.WriteCloser, err error) { - if len(to) == 0 { - return nil, errors.InvalidArgumentError("no encryption recipient provided") - } - - // These are the possible ciphers that we'll use for the message. - candidateCiphers := []uint8{ - uint8(packet.CipherAES256), - uint8(packet.CipherAES128), - } - - // These are the possible hash functions that we'll use for the signature. - candidateHashes := []uint8{ - hashToHashId(crypto.SHA256), - hashToHashId(crypto.SHA384), - hashToHashId(crypto.SHA512), - hashToHashId(crypto.SHA3_256), - hashToHashId(crypto.SHA3_512), - } - - // Prefer GCM if everyone supports it - candidateCipherSuites := [][2]uint8{ - {uint8(packet.CipherAES256), uint8(packet.AEADModeGCM)}, - {uint8(packet.CipherAES256), uint8(packet.AEADModeEAX)}, - {uint8(packet.CipherAES256), uint8(packet.AEADModeOCB)}, - {uint8(packet.CipherAES128), uint8(packet.AEADModeGCM)}, - {uint8(packet.CipherAES128), uint8(packet.AEADModeEAX)}, - {uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)}, - } - - candidateCompression := []uint8{ - uint8(packet.CompressionNone), - uint8(packet.CompressionZIP), - uint8(packet.CompressionZLIB), - } - - encryptKeys := make([]Key, len(to)) - - // AEAD is used only if config enables it and every key supports it - aeadSupported := config.AEAD() != nil - - for i := range to { - var ok bool - encryptKeys[i], ok = to[i].EncryptionKey(config.Now()) - if !ok { - return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no valid encryption keys") - } - - primarySelfSignature, _ := to[i].PrimarySelfSignature() - if primarySelfSignature == nil { - return nil, errors.InvalidArgumentError("entity without a self-signature") - } - - if !primarySelfSignature.SEIPDv2 { - aeadSupported = false - } - - candidateCiphers = intersectPreferences(candidateCiphers, primarySelfSignature.PreferredSymmetric) - candidateHashes = intersectPreferences(candidateHashes, primarySelfSignature.PreferredHash) - candidateCipherSuites = intersectCipherSuites(candidateCipherSuites, primarySelfSignature.PreferredCipherSuites) - candidateCompression = intersectPreferences(candidateCompression, primarySelfSignature.PreferredCompression) - } - - // In the event that the intersection of supported algorithms is empty we use the ones - // labelled as MUST that every implementation supports. - if len(candidateCiphers) == 0 { - // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.3 - candidateCiphers = []uint8{uint8(packet.CipherAES128)} - } - if len(candidateHashes) == 0 { - // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#hash-algos - candidateHashes = []uint8{hashToHashId(crypto.SHA256)} - } - if len(candidateCipherSuites) == 0 { - // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.6 - candidateCipherSuites = [][2]uint8{{uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)}} - } - - cipher := packet.CipherFunction(candidateCiphers[0]) - aeadCipherSuite := packet.CipherSuite{ - Cipher: packet.CipherFunction(candidateCipherSuites[0][0]), - Mode: packet.AEADMode(candidateCipherSuites[0][1]), - } - - // If the cipher specified by config is a candidate, we'll use that. - configuredCipher := config.Cipher() - for _, c := range candidateCiphers { - cipherFunc := packet.CipherFunction(c) - if cipherFunc == configuredCipher { - cipher = cipherFunc - break - } - } - - var symKey []byte - if aeadSupported { - symKey = make([]byte, aeadCipherSuite.Cipher.KeySize()) - } else { - symKey = make([]byte, cipher.KeySize()) - } - - if _, err := io.ReadFull(config.Random(), symKey); err != nil { - return nil, err - } - - for _, key := range encryptKeys { - if err := packet.SerializeEncryptedKeyAEAD(keyWriter, key.PublicKey, cipher, aeadSupported, symKey, config); err != nil { - return nil, err - } - } - - var payload io.WriteCloser - payload, err = packet.SerializeSymmetricallyEncrypted(dataWriter, cipher, aeadSupported, aeadCipherSuite, symKey, config) - if err != nil { - return - } - - payload, err = handleCompression(payload, candidateCompression, config) - if err != nil { - return nil, err - } - - return writeAndSign(payload, candidateHashes, signed, hints, sigType, config) -} - -// Sign signs a message. The resulting WriteCloser must be closed after the -// contents of the file have been written. hints contains optional information -// that aids the recipients in processing the message. -// If config is nil, sensible defaults will be used. -func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) { - if signed == nil { - return nil, errors.InvalidArgumentError("no signer provided") - } - - // These are the possible hash functions that we'll use for the signature. - candidateHashes := []uint8{ - hashToHashId(crypto.SHA256), - hashToHashId(crypto.SHA384), - hashToHashId(crypto.SHA512), - hashToHashId(crypto.SHA3_256), - hashToHashId(crypto.SHA3_512), - } - defaultHashes := candidateHashes[0:1] - primarySelfSignature, _ := signed.PrimarySelfSignature() - if primarySelfSignature == nil { - return nil, errors.StructuralError("signed entity has no self-signature") - } - preferredHashes := primarySelfSignature.PreferredHash - if len(preferredHashes) == 0 { - preferredHashes = defaultHashes - } - candidateHashes = intersectPreferences(candidateHashes, preferredHashes) - if len(candidateHashes) == 0 { - return nil, errors.StructuralError("cannot sign because signing key shares no common algorithms with candidate hashes") - } - - return writeAndSign(noOpCloser{output}, candidateHashes, signed, hints, packet.SigTypeBinary, config) -} - -// signatureWriter hashes the contents of a message while passing it along to -// literalData. When closed, it closes literalData, writes a signature packet -// to encryptedData and then also closes encryptedData. -type signatureWriter struct { - encryptedData io.WriteCloser - literalData io.WriteCloser - hashType crypto.Hash - wrappedHash hash.Hash - h hash.Hash - salt []byte // v6 only - signer *packet.PrivateKey - sigType packet.SignatureType - config *packet.Config - metadata *packet.LiteralData // V5 signatures protect document metadata -} - -func (s signatureWriter) Write(data []byte) (int, error) { - s.wrappedHash.Write(data) - switch s.sigType { - case packet.SigTypeBinary: - return s.literalData.Write(data) - case packet.SigTypeText: - flag := 0 - return writeCanonical(s.literalData, data, &flag) - } - return 0, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(s.sigType))) -} - -func (s signatureWriter) Close() error { - sig := createSignaturePacket(&s.signer.PublicKey, s.sigType, s.config) - sig.Hash = s.hashType - sig.Metadata = s.metadata - - if err := sig.SetSalt(s.salt); err != nil { - return err - } - - if err := sig.Sign(s.h, s.signer, s.config); err != nil { - return err - } - if err := s.literalData.Close(); err != nil { - return err - } - if err := sig.Serialize(s.encryptedData); err != nil { - return err - } - return s.encryptedData.Close() -} - -func createSignaturePacket(signer *packet.PublicKey, sigType packet.SignatureType, config *packet.Config) *packet.Signature { - sigLifetimeSecs := config.SigLifetime() - return &packet.Signature{ - Version: signer.Version, - SigType: sigType, - PubKeyAlgo: signer.PubKeyAlgo, - Hash: config.Hash(), - CreationTime: config.Now(), - IssuerKeyId: &signer.KeyId, - IssuerFingerprint: signer.Fingerprint, - Notations: config.Notations(), - SigLifetimeSecs: &sigLifetimeSecs, - } -} - -// noOpCloser is like an ioutil.NopCloser, but for an io.Writer. -// TODO: we have two of these in OpenPGP packages alone. This probably needs -// to be promoted somewhere more common. -type noOpCloser struct { - w io.Writer -} - -func (c noOpCloser) Write(data []byte) (n int, err error) { - return c.w.Write(data) -} - -func (c noOpCloser) Close() error { - return nil -} - -func handleCompression(compressed io.WriteCloser, candidateCompression []uint8, config *packet.Config) (data io.WriteCloser, err error) { - data = compressed - confAlgo := config.Compression() - if confAlgo == packet.CompressionNone { - return - } - - // Set algorithm labelled as MUST as fallback - // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.4 - finalAlgo := packet.CompressionNone - // if compression specified by config available we will use it - for _, c := range candidateCompression { - if uint8(confAlgo) == c { - finalAlgo = confAlgo - break - } - } - - if finalAlgo != packet.CompressionNone { - var compConfig *packet.CompressionConfig - if config != nil { - compConfig = config.CompressionConfig - } - data, err = packet.SerializeCompressed(compressed, finalAlgo, compConfig) - if err != nil { - return - } - } - return data, nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/x25519/x25519.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/x25519/x25519.go deleted file mode 100644 index 38afcc74fa3..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/x25519/x25519.go +++ /dev/null @@ -1,221 +0,0 @@ -package x25519 - -import ( - "crypto/sha256" - "crypto/subtle" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/aes/keywrap" - "github.com/ProtonMail/go-crypto/openpgp/errors" - x25519lib "github.com/cloudflare/circl/dh/x25519" - "golang.org/x/crypto/hkdf" -) - -const ( - hkdfInfo = "OpenPGP X25519" - aes128KeySize = 16 - // The size of a public or private key in bytes. - KeySize = x25519lib.Size -) - -type PublicKey struct { - // Point represents the encoded elliptic curve point of the public key. - Point []byte -} - -type PrivateKey struct { - PublicKey - // Secret represents the secret of the private key. - Secret []byte -} - -// NewPrivateKey creates a new empty private key including the public key. -func NewPrivateKey(key PublicKey) *PrivateKey { - return &PrivateKey{ - PublicKey: key, - } -} - -// Validate validates that the provided public key matches the private key. -func Validate(pk *PrivateKey) (err error) { - var expectedPublicKey, privateKey x25519lib.Key - subtle.ConstantTimeCopy(1, privateKey[:], pk.Secret) - x25519lib.KeyGen(&expectedPublicKey, &privateKey) - if subtle.ConstantTimeCompare(expectedPublicKey[:], pk.PublicKey.Point) == 0 { - return errors.KeyInvalidError("x25519: invalid key") - } - return nil -} - -// GenerateKey generates a new x25519 key pair. -func GenerateKey(rand io.Reader) (*PrivateKey, error) { - var privateKey, publicKey x25519lib.Key - privateKeyOut := new(PrivateKey) - err := generateKey(rand, &privateKey, &publicKey) - if err != nil { - return nil, err - } - privateKeyOut.PublicKey.Point = publicKey[:] - privateKeyOut.Secret = privateKey[:] - return privateKeyOut, nil -} - -func generateKey(rand io.Reader, privateKey *x25519lib.Key, publicKey *x25519lib.Key) error { - maxRounds := 10 - isZero := true - for round := 0; isZero; round++ { - if round == maxRounds { - return errors.InvalidArgumentError("x25519: zero keys only, randomness source might be corrupt") - } - _, err := io.ReadFull(rand, privateKey[:]) - if err != nil { - return err - } - isZero = constantTimeIsZero(privateKey[:]) - } - x25519lib.KeyGen(publicKey, privateKey) - return nil -} - -// Encrypt encrypts a sessionKey with x25519 according to -// the OpenPGP crypto refresh specification section 5.1.6. The function assumes that the -// sessionKey has the correct format and padding according to the specification. -func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { - var ephemeralPrivate, ephemeralPublic, staticPublic, shared x25519lib.Key - // Check that the input static public key has 32 bytes - if len(publicKey.Point) != KeySize { - err = errors.KeyInvalidError("x25519: the public key has the wrong size") - return - } - copy(staticPublic[:], publicKey.Point) - // Generate ephemeral keyPair - err = generateKey(rand, &ephemeralPrivate, &ephemeralPublic) - if err != nil { - return - } - // Compute shared key - ok := x25519lib.Shared(&shared, &ephemeralPrivate, &staticPublic) - if !ok { - err = errors.KeyInvalidError("x25519: the public key is a low order point") - return - } - // Derive the encryption key from the shared secret - encryptionKey := applyHKDF(ephemeralPublic[:], publicKey.Point[:], shared[:]) - ephemeralPublicKey = &PublicKey{ - Point: ephemeralPublic[:], - } - // Encrypt the sessionKey with aes key wrapping - encryptedSessionKey, err = keywrap.Wrap(encryptionKey, sessionKey) - return -} - -// Decrypt decrypts a session key stored in ciphertext with the provided x25519 -// private key and ephemeral public key. -func Decrypt(privateKey *PrivateKey, ephemeralPublicKey *PublicKey, ciphertext []byte) (encodedSessionKey []byte, err error) { - var ephemeralPublic, staticPrivate, shared x25519lib.Key - // Check that the input ephemeral public key has 32 bytes - if len(ephemeralPublicKey.Point) != KeySize { - err = errors.KeyInvalidError("x25519: the public key has the wrong size") - return - } - copy(ephemeralPublic[:], ephemeralPublicKey.Point) - subtle.ConstantTimeCopy(1, staticPrivate[:], privateKey.Secret) - // Compute shared key - ok := x25519lib.Shared(&shared, &staticPrivate, &ephemeralPublic) - if !ok { - err = errors.KeyInvalidError("x25519: the ephemeral public key is a low order point") - return - } - // Derive the encryption key from the shared secret - encryptionKey := applyHKDF(ephemeralPublicKey.Point[:], privateKey.PublicKey.Point[:], shared[:]) - // Decrypt the session key with aes key wrapping - encodedSessionKey, err = keywrap.Unwrap(encryptionKey, ciphertext) - return -} - -func applyHKDF(ephemeralPublicKey []byte, publicKey []byte, sharedSecret []byte) []byte { - inputKey := make([]byte, 3*KeySize) - // ephemeral public key | recipient public key | shared secret - subtle.ConstantTimeCopy(1, inputKey[:KeySize], ephemeralPublicKey) - subtle.ConstantTimeCopy(1, inputKey[KeySize:2*KeySize], publicKey) - subtle.ConstantTimeCopy(1, inputKey[2*KeySize:], sharedSecret) - hkdfReader := hkdf.New(sha256.New, inputKey, []byte{}, []byte(hkdfInfo)) - encryptionKey := make([]byte, aes128KeySize) - _, _ = io.ReadFull(hkdfReader, encryptionKey) - return encryptionKey -} - -func constantTimeIsZero(bytes []byte) bool { - isZero := byte(0) - for _, b := range bytes { - isZero |= b - } - return isZero == 0 -} - -// ENCODING/DECODING ciphertexts: - -// EncodeFieldsLength returns the length of the ciphertext encoding -// given the encrypted session key. -func EncodedFieldsLength(encryptedSessionKey []byte, v6 bool) int { - lenCipherFunction := 0 - if !v6 { - lenCipherFunction = 1 - } - return KeySize + 1 + len(encryptedSessionKey) + lenCipherFunction -} - -// EncodeField encodes x25519 session key encryption fields as -// ephemeral x25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey -// and writes it to writer. -func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, v6 bool) (err error) { - lenAlgorithm := 0 - if !v6 { - lenAlgorithm = 1 - } - if _, err = writer.Write(ephemeralPublicKey.Point); err != nil { - return err - } - if _, err = writer.Write([]byte{byte(len(encryptedSessionKey) + lenAlgorithm)}); err != nil { - return err - } - if !v6 { - if _, err = writer.Write([]byte{cipherFunction}); err != nil { - return err - } - } - _, err = writer.Write(encryptedSessionKey) - return err -} - -// DecodeField decodes a x25519 session key encryption as -// ephemeral x25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey. -func DecodeFields(reader io.Reader, v6 bool) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, err error) { - var buf [1]byte - ephemeralPublicKey = &PublicKey{ - Point: make([]byte, KeySize), - } - // 32 octets representing an ephemeral x25519 public key. - if _, err = io.ReadFull(reader, ephemeralPublicKey.Point); err != nil { - return nil, nil, 0, err - } - // A one-octet size of the following fields. - if _, err = io.ReadFull(reader, buf[:]); err != nil { - return nil, nil, 0, err - } - followingLen := buf[0] - // The one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet). - if !v6 { - if _, err = io.ReadFull(reader, buf[:]); err != nil { - return nil, nil, 0, err - } - cipherFunction = buf[0] - followingLen -= 1 - } - // The encrypted session key. - encryptedSessionKey = make([]byte, followingLen) - if _, err = io.ReadFull(reader, encryptedSessionKey); err != nil { - return nil, nil, 0, err - } - return ephemeralPublicKey, encryptedSessionKey, cipherFunction, nil -} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/x448/x448.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/x448/x448.go deleted file mode 100644 index 65a082dabd7..00000000000 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/x448/x448.go +++ /dev/null @@ -1,229 +0,0 @@ -package x448 - -import ( - "crypto/sha512" - "crypto/subtle" - "io" - - "github.com/ProtonMail/go-crypto/openpgp/aes/keywrap" - "github.com/ProtonMail/go-crypto/openpgp/errors" - x448lib "github.com/cloudflare/circl/dh/x448" - "golang.org/x/crypto/hkdf" -) - -const ( - hkdfInfo = "OpenPGP X448" - aes256KeySize = 32 - // The size of a public or private key in bytes. - KeySize = x448lib.Size -) - -type PublicKey struct { - // Point represents the encoded elliptic curve point of the public key. - Point []byte -} - -type PrivateKey struct { - PublicKey - // Secret represents the secret of the private key. - Secret []byte -} - -// NewPrivateKey creates a new empty private key including the public key. -func NewPrivateKey(key PublicKey) *PrivateKey { - return &PrivateKey{ - PublicKey: key, - } -} - -// Validate validates that the provided public key matches -// the private key. -func Validate(pk *PrivateKey) (err error) { - var expectedPublicKey, privateKey x448lib.Key - subtle.ConstantTimeCopy(1, privateKey[:], pk.Secret) - x448lib.KeyGen(&expectedPublicKey, &privateKey) - if subtle.ConstantTimeCompare(expectedPublicKey[:], pk.PublicKey.Point) == 0 { - return errors.KeyInvalidError("x448: invalid key") - } - return nil -} - -// GenerateKey generates a new x448 key pair. -func GenerateKey(rand io.Reader) (*PrivateKey, error) { - var privateKey, publicKey x448lib.Key - privateKeyOut := new(PrivateKey) - err := generateKey(rand, &privateKey, &publicKey) - if err != nil { - return nil, err - } - privateKeyOut.PublicKey.Point = publicKey[:] - privateKeyOut.Secret = privateKey[:] - return privateKeyOut, nil -} - -func generateKey(rand io.Reader, privateKey *x448lib.Key, publicKey *x448lib.Key) error { - maxRounds := 10 - isZero := true - for round := 0; isZero; round++ { - if round == maxRounds { - return errors.InvalidArgumentError("x448: zero keys only, randomness source might be corrupt") - } - _, err := io.ReadFull(rand, privateKey[:]) - if err != nil { - return err - } - isZero = constantTimeIsZero(privateKey[:]) - } - x448lib.KeyGen(publicKey, privateKey) - return nil -} - -// Encrypt encrypts a sessionKey with x448 according to -// the OpenPGP crypto refresh specification section 5.1.7. The function assumes that the -// sessionKey has the correct format and padding according to the specification. -func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { - var ephemeralPrivate, ephemeralPublic, staticPublic, shared x448lib.Key - // Check that the input static public key has 56 bytes. - if len(publicKey.Point) != KeySize { - err = errors.KeyInvalidError("x448: the public key has the wrong size") - return nil, nil, err - } - copy(staticPublic[:], publicKey.Point) - // Generate ephemeral keyPair. - if err = generateKey(rand, &ephemeralPrivate, &ephemeralPublic); err != nil { - return nil, nil, err - } - // Compute shared key. - ok := x448lib.Shared(&shared, &ephemeralPrivate, &staticPublic) - if !ok { - err = errors.KeyInvalidError("x448: the public key is a low order point") - return nil, nil, err - } - // Derive the encryption key from the shared secret. - encryptionKey := applyHKDF(ephemeralPublic[:], publicKey.Point[:], shared[:]) - ephemeralPublicKey = &PublicKey{ - Point: ephemeralPublic[:], - } - // Encrypt the sessionKey with aes key wrapping. - encryptedSessionKey, err = keywrap.Wrap(encryptionKey, sessionKey) - if err != nil { - return nil, nil, err - } - return ephemeralPublicKey, encryptedSessionKey, nil -} - -// Decrypt decrypts a session key stored in ciphertext with the provided x448 -// private key and ephemeral public key. -func Decrypt(privateKey *PrivateKey, ephemeralPublicKey *PublicKey, ciphertext []byte) (encodedSessionKey []byte, err error) { - var ephemeralPublic, staticPrivate, shared x448lib.Key - // Check that the input ephemeral public key has 56 bytes. - if len(ephemeralPublicKey.Point) != KeySize { - err = errors.KeyInvalidError("x448: the public key has the wrong size") - return nil, err - } - copy(ephemeralPublic[:], ephemeralPublicKey.Point) - subtle.ConstantTimeCopy(1, staticPrivate[:], privateKey.Secret) - // Compute shared key. - ok := x448lib.Shared(&shared, &staticPrivate, &ephemeralPublic) - if !ok { - err = errors.KeyInvalidError("x448: the ephemeral public key is a low order point") - return nil, err - } - // Derive the encryption key from the shared secret. - encryptionKey := applyHKDF(ephemeralPublicKey.Point[:], privateKey.PublicKey.Point[:], shared[:]) - // Decrypt the session key with aes key wrapping. - encodedSessionKey, err = keywrap.Unwrap(encryptionKey, ciphertext) - if err != nil { - return nil, err - } - return encodedSessionKey, nil -} - -func applyHKDF(ephemeralPublicKey []byte, publicKey []byte, sharedSecret []byte) []byte { - inputKey := make([]byte, 3*KeySize) - // ephemeral public key | recipient public key | shared secret. - subtle.ConstantTimeCopy(1, inputKey[:KeySize], ephemeralPublicKey) - subtle.ConstantTimeCopy(1, inputKey[KeySize:2*KeySize], publicKey) - subtle.ConstantTimeCopy(1, inputKey[2*KeySize:], sharedSecret) - hkdfReader := hkdf.New(sha512.New, inputKey, []byte{}, []byte(hkdfInfo)) - encryptionKey := make([]byte, aes256KeySize) - _, _ = io.ReadFull(hkdfReader, encryptionKey) - return encryptionKey -} - -func constantTimeIsZero(bytes []byte) bool { - isZero := byte(0) - for _, b := range bytes { - isZero |= b - } - return isZero == 0 -} - -// ENCODING/DECODING ciphertexts: - -// EncodeFieldsLength returns the length of the ciphertext encoding -// given the encrypted session key. -func EncodedFieldsLength(encryptedSessionKey []byte, v6 bool) int { - lenCipherFunction := 0 - if !v6 { - lenCipherFunction = 1 - } - return KeySize + 1 + len(encryptedSessionKey) + lenCipherFunction -} - -// EncodeField encodes x448 session key encryption fields as -// ephemeral x448 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey -// and writes it to writer. -func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, v6 bool) (err error) { - lenAlgorithm := 0 - if !v6 { - lenAlgorithm = 1 - } - if _, err = writer.Write(ephemeralPublicKey.Point); err != nil { - return err - } - if _, err = writer.Write([]byte{byte(len(encryptedSessionKey) + lenAlgorithm)}); err != nil { - return err - } - if !v6 { - if _, err = writer.Write([]byte{cipherFunction}); err != nil { - return err - } - } - if _, err = writer.Write(encryptedSessionKey); err != nil { - return err - } - return nil -} - -// DecodeField decodes a x448 session key encryption as -// ephemeral x448 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey. -func DecodeFields(reader io.Reader, v6 bool) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, err error) { - var buf [1]byte - ephemeralPublicKey = &PublicKey{ - Point: make([]byte, KeySize), - } - // 56 octets representing an ephemeral x448 public key. - if _, err = io.ReadFull(reader, ephemeralPublicKey.Point); err != nil { - return nil, nil, 0, err - } - // A one-octet size of the following fields. - if _, err = io.ReadFull(reader, buf[:]); err != nil { - return nil, nil, 0, err - } - followingLen := buf[0] - // The one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet). - if !v6 { - if _, err = io.ReadFull(reader, buf[:]); err != nil { - return nil, nil, 0, err - } - cipherFunction = buf[0] - followingLen -= 1 - } - // The encrypted session key. - encryptedSessionKey = make([]byte, followingLen) - if _, err = io.ReadFull(reader, encryptedSessionKey); err != nil { - return nil, nil, 0, err - } - return ephemeralPublicKey, encryptedSessionKey, cipherFunction, nil -} diff --git a/vendor/github.com/adrg/xdg/CODE_OF_CONDUCT.md b/vendor/github.com/adrg/xdg/CODE_OF_CONDUCT.md deleted file mode 100644 index 75349e53ee7..00000000000 --- a/vendor/github.com/adrg/xdg/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,77 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, -body size, disability, ethnicity, sex characteristics, gender identity and -expression, level of experience, education, socio-economic status, nationality, -personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behaviour that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behaviour by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behaviour and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behaviour. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviour that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behaviour may be -reported by contacting the project team at adrg@epistack.com. All complaints -will be reviewed and investigated and will result in a response that is deemed -necessary and appropriate to the circumstances. The project team is obligated to -maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 1.4, available at -https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/vendor/github.com/adrg/xdg/CONTRIBUTING.md b/vendor/github.com/adrg/xdg/CONTRIBUTING.md deleted file mode 100644 index 006f146b38f..00000000000 --- a/vendor/github.com/adrg/xdg/CONTRIBUTING.md +++ /dev/null @@ -1,135 +0,0 @@ -# Contributing to this project - -Contributions in the form of pull requests, issues or just general feedback, -are always welcome. Please take a moment to review this document in order to -make the contribution process easy and effective for everyone involved. - -Following these guidelines helps to communicate that you respect the time of -the developers managing and developing this open source project. In return, -they should reciprocate that respect in addressing your issue or assessing -patches and features. - -## Using the issue tracker - -The issue tracker is the preferred channel for [bug reports](#bugs), -[features requests](#features) and [submitting pull -requests](#pull-requests), but please respect the following restrictions: - -* Please **do not** use the issue tracker for personal support requests (use - [Stack Overflow](http://stackoverflow.com) or IRC). -* Please **do not** derail or troll issues. Keep the discussion on topic and - respect the opinions of others. - - -## Bug reports - -A bug is a _demonstrable problem_ that is caused by the code in the repository. -Good bug reports are extremely helpful - thank you! - -Guidelines for bug reports: - -1. **Use the GitHub issue search** — check if the issue has already been - reported. -2. **Check if the issue has been fixed** — try to reproduce it using the - latest `master` or development branch in the repository. -3. **Isolate the problem** — create a reduced test case. - -A good bug report shouldn't leave others needing to chase you up for more -information. Please try to be as detailed as possible in your report. What is -your environment? What steps will reproduce the issue? What browser(s) and OS -experience the problem? What would you expect to be the outcome? All these -details will help people to fix any potential bugs. - -Example: - -> Short and descriptive example bug report title -> -> A summary of the issue and the browser/OS environment in which it occurs. If -> suitable, include the steps required to reproduce the bug. -> -> 1. This is the first step -> 2. This is the second step -> 3. Further steps, etc. -> -> `` - a link to the reduced test case -> -> Any other information you want to share that is relevant to the issue being -> reported. This might include the lines of code that you have identified as -> causing the bug, and potential solutions (and your opinions on their -> merits). - - - -## Feature requests - -Feature requests are welcome. But take a moment to find out whether your idea -fits with the scope and aims of the project. It's up to *you* to make a strong -case to convince the project's developers of the merits of this feature. Please -provide as much detail and context as possible. - - - -## Pull requests - -Good pull requests - patches, improvements, new features - are a fantastic -help. They should remain focused in scope and avoid containing unrelated -commits. - -**Please ask first** before embarking on any significant pull request (e.g. -implementing features, refactoring code, porting to a different language), -otherwise you risk spending a lot of time working on something that the -project's developers might not want to merge into the project. - -Please adhere to the coding conventions used throughout a project (indentation, -accurate comments, etc.) and any other requirements (such as test coverage). - -Follow this process if you'd like your work considered for inclusion in the -project: - -1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, - and configure the remotes: - - ```bash - # Clone your fork of the repo into the current directory - git clone https://github.com// - # Navigate to the newly cloned directory - cd - # Assign the original repo to a remote called "upstream" - git remote add upstream https://github.com// - ``` - -2. If you cloned a while ago, get the latest changes from upstream: - - ```bash - git checkout - git pull upstream - ``` - -3. Create a new topic branch (off the main project development branch) to - contain your feature, change, or fix: - - ```bash - git checkout -b - ``` - -4. Commit your changes in logical chunks and use descriptive commit messages. - Use [interactive rebase](https://help.github.com/articles/interactive-rebase) - to tidy up your commits before making them public. - -5. Locally merge (or rebase) the upstream development branch into your topic branch: - - ```bash - git pull [--rebase] upstream - ``` - -6. Push your topic branch up to your fork: - - ```bash - git push origin - ``` - -7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) - with a clear title and description. - -**IMPORTANT**: By submitting a patch, you agree to allow the project owner to -license your work under the same license as that used by the project. diff --git a/vendor/github.com/adrg/xdg/LICENSE b/vendor/github.com/adrg/xdg/LICENSE deleted file mode 100644 index 7307e1b7c89..00000000000 --- a/vendor/github.com/adrg/xdg/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Adrian-George Bostan - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/github.com/adrg/xdg/README.md b/vendor/github.com/adrg/xdg/README.md deleted file mode 100644 index b55403c27c0..00000000000 --- a/vendor/github.com/adrg/xdg/README.md +++ /dev/null @@ -1,280 +0,0 @@ -

-
- xdg logo -
-

- -

Go implementation of the XDG Base Directory Specification and XDG user directories.

- -

- - Build status - - - Code coverage - - - pkg.go.dev documentation - - - MIT license - -
- - Go report card - - - Awesome Go - - - GitHub contributors - - - GitHub open issues - - - Buy me a coffee - -

- -Provides an implementation of the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). -The specification defines a set of standard paths for storing application files, -including data and configuration files. For portability and flexibility reasons, -applications should use the XDG defined locations instead of hardcoding paths. -The package also includes the locations of well known [user directories](https://wiki.archlinux.org/index.php/XDG_user_directories), as well as -other common directories such as fonts and applications. - -The current implementation supports **most flavors of Unix**, **Windows**, **macOS** and **Plan 9**. -On Windows, where XDG environment variables are not usually set, the package uses [Known Folders](https://docs.microsoft.com/en-us/windows/win32/shell/known-folders) -as defaults. Therefore, appropriate locations are used for common [folders](https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid) which may have been redirected. - -See usage [examples](#usage) below. Full documentation can be found at https://pkg.go.dev/github.com/adrg/xdg. - -## Installation - go get github.com/adrg/xdg - -## Default locations - -The package defines sensible defaults for XDG variables which are empty or not -present in the environment. - -- On Unix-like operating systems, XDG environment variables are tipically defined. -Appropriate default locations are used for the environment variables which are not set. -- On Windows, XDG environment variables are usually not set. If that is the case, -the package relies on the appropriate [Known Folders](https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid). -Sensible fallback locations are used for the folders which are not set. - -### XDG Base Directory - -
- Unix-like operating systems -
- -| |

Unix

|

macOS

|

Plan 9

| -| :------------------------------------------------------------: | :-----------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------: | -| XDG_DATA_HOME | ~/.local/share | ~/Library/Application Support | $home/lib | -| XDG_DATA_DIRS | /usr/local/share
/usr/share | /Library/Application Support | /lib | -| XDG_CONFIG_HOME | ~/.config | ~/Library/Application Support | $home/lib | -| XDG_CONFIG_DIRS | /etc/xdg | ~/Library/Preferences
/Library/Application Support
/Library/Preferences | /lib | -| XDG_STATE_HOME | ~/.local/state | ~/Library/Application Support | $home/lib/state | -| XDG_CACHE_HOME | ~/.cache | ~/Library/Caches | $home/lib/cache | -| XDG_RUNTIME_DIR | /run/user/UID | ~/Library/Application Support | /tmp | - -
- -
- Microsoft Windows -
- -| |

Known Folder(s)

|

Fallback(s)

| -| :------------------------------------------------------------: | :---------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------: | -| XDG_DATA_HOME | LocalAppData | %LOCALAPPDATA% | -| XDG_DATA_DIRS | RoamingAppData
ProgramData | %APPADATA%
%ProgramData% | -| XDG_CONFIG_HOME | LocalAppData | %LOCALAPPDATA% | -| XDG_CONFIG_DIRS | ProgramData
RoamingAppData | %ProgramData%
%APPDATA% | -| XDG_STATE_HOME | LocalAppData | %LOCALAPPDATA% | -| XDG_CACHE_HOME | LocalAppData\cache | %LOCALAPPDATA%\cache | -| XDG_RUNTIME_DIR | LocalAppData | %LOCALAPPDATA% | - -
- -### XDG user directories - -
- Unix-like operating systems -
- -| |

Unix

|

macOS

|

Plan 9

| -| :--------------------------------------------------------------: | :-------------------------------------------------------------------------: | :---------------------------------------------------------------------------: | :---------------------------------------------------------------------------: | -| XDG_DESKTOP_DIR | ~/Desktop | ~/Desktop | $home/desktop | -| XDG_DOWNLOAD_DIR | ~/Downloads | ~/Downloads | $home/downloads | -| XDG_DOCUMENTS_DIR | ~/Documents | ~/Documents | $home/documents | -| XDG_MUSIC_DIR | ~/Music | ~/Music | $home/music | -| XDG_PICTURES_DIR | ~/Pictures | ~/Pictures | $home/pictures | -| XDG_VIDEOS_DIR | ~/Videos | ~/Movies | $home/videos | -| XDG_TEMPLATES_DIR | ~/Templates | ~/Templates | $home/templates | -| XDG_PUBLICSHARE_DIR | ~/Public | ~/Public | $home/public | - -
- -
- Microsoft Windows -
- -| |

Known Folder(s)

|

Fallback(s)

| -| :--------------------------------------------------------------: | :-----------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------: | -| XDG_DESKTOP_DIR | Desktop | %USERPROFILE%\Desktop | -| XDG_DOWNLOAD_DIR | Downloads | %USERPROFILE%\Downloads | -| XDG_DOCUMENTS_DIR | Documents | %USERPROFILE%\Documents | -| XDG_MUSIC_DIR | Music | %USERPROFILE%\Music | -| XDG_PICTURES_DIR | Pictures | %USERPROFILE%\Pictures | -| XDG_VIDEOS_DIR | Videos | %USERPROFILE%\Videos | -| XDG_TEMPLATES_DIR | Templates | %APPDATA%\Microsoft\Windows\Templates | -| XDG_PUBLICSHARE_DIR | Public | %PUBLIC% | - -
- -### Other directories - -
- Unix-like operating systems -
- -| |

Unix

|

macOS

|

Plan 9

| -| :-----------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------: | -| Home | $HOME | $HOME | $home | -| Applications | $XDG_DATA_HOME/applications
~/.local/share/applications
/usr/local/share/applications
/usr/share/applications
$XDG_DATA_DIRS/applications | /Applications | $home/bin
/bin | -| Fonts | $XDG_DATA_HOME/fonts
~/.fonts
~/.local/share/fonts
/usr/local/share/fonts
/usr/share/fonts
$XDG_DATA_DIRS/fonts | ~/Library/Fonts
/Library/Fonts
/System/Library/Fonts
/Network/Library/Fonts | $home/lib/font
/lib/font | - -
- -
- Microsoft Windows -
- -| |

Known Folder(s)

|

Fallback(s)

| -| :-----------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | -| Home | Profile | %USERPROFILE% | -| Applications | Programs
CommonPrograms | %APPDATA%\Microsoft\Windows\Start Menu\Programs
%ProgramData%\Microsoft\Windows\Start Menu\Programs | -| Fonts | Fonts
- | %SystemRoot%\Fonts
%LOCALAPPDATA%\Microsoft\Windows\Fonts | - -
- -## Usage - -#### XDG Base Directory - -```go -package main - -import ( - "log" - - "github.com/adrg/xdg" -) - -func main() { - // XDG Base Directory paths. - log.Println("Home data directory:", xdg.DataHome) - log.Println("Data directories:", xdg.DataDirs) - log.Println("Home config directory:", xdg.ConfigHome) - log.Println("Config directories:", xdg.ConfigDirs) - log.Println("Home state directory:", xdg.StateHome) - log.Println("Cache directory:", xdg.CacheHome) - log.Println("Runtime directory:", xdg.RuntimeDir) - - // Other common directories. - log.Println("Home directory:", xdg.Home) - log.Println("Application directories:", xdg.ApplicationDirs) - log.Println("Font directories:", xdg.FontDirs) - - // Obtain a suitable location for application config files. - // ConfigFile takes one parameter which must contain the name of the file, - // but it can also contain a set of parent directories. If the directories - // don't exist, they will be created relative to the base config directory. - configFilePath, err := xdg.ConfigFile("appname/config.yaml") - if err != nil { - log.Fatal(err) - } - log.Println("Save the config file at:", configFilePath) - - // For other types of application files use: - // xdg.DataFile() - // xdg.StateFile() - // xdg.CacheFile() - // xdg.RuntimeFile() - - // Finding application config files. - // SearchConfigFile takes one parameter which must contain the name of - // the file, but it can also contain a set of parent directories relative - // to the config search paths (xdg.ConfigHome and xdg.ConfigDirs). - configFilePath, err = xdg.SearchConfigFile("appname/config.yaml") - if err != nil { - log.Fatal(err) - } - log.Println("Config file was found at:", configFilePath) - - // For other types of application files use: - // xdg.SearchDataFile() - // xdg.SearchStateFile() - // xdg.SearchCacheFile() - // xdg.SearchRuntimeFile() -} -``` - -#### XDG user directories - -```go -package main - -import ( - "log" - - "github.com/adrg/xdg" -) - -func main() { - // XDG user directories. - log.Println("Desktop directory:", xdg.UserDirs.Desktop) - log.Println("Download directory:", xdg.UserDirs.Download) - log.Println("Documents directory:", xdg.UserDirs.Documents) - log.Println("Music directory:", xdg.UserDirs.Music) - log.Println("Pictures directory:", xdg.UserDirs.Pictures) - log.Println("Videos directory:", xdg.UserDirs.Videos) - log.Println("Templates directory:", xdg.UserDirs.Templates) - log.Println("Public directory:", xdg.UserDirs.PublicShare) -} -``` - -## Stargazers over time - -[![Stargazers over time](https://starchart.cc/adrg/xdg.svg)](https://starchart.cc/adrg/xdg) - -## Contributing - -Contributions in the form of pull requests, issues or just general feedback, -are always welcome. -See [CONTRIBUTING.MD](CONTRIBUTING.md). - -**Contributors**: -[adrg](https://github.com/adrg), -[wichert](https://github.com/wichert), -[bouncepaw](https://github.com/bouncepaw), -[gabriel-vasile](https://github.com/gabriel-vasile), -[KalleDK](https://github.com/KalleDK), -[nvkv](https://github.com/nvkv), -[djdv](https://github.com/djdv). - -## References - -For more information see: -* [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) -* [XDG user directories](https://wiki.archlinux.org/index.php/XDG_user_directories) -* [Windows Known Folders](https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid) - -## License - -Copyright (c) 2014 Adrian-George Bostan. - -This project is licensed under the [MIT license](https://opensource.org/licenses/MIT). -See [LICENSE](LICENSE) for more details. diff --git a/vendor/github.com/adrg/xdg/base_dirs.go b/vendor/github.com/adrg/xdg/base_dirs.go deleted file mode 100644 index a8a3fd55cca..00000000000 --- a/vendor/github.com/adrg/xdg/base_dirs.go +++ /dev/null @@ -1,68 +0,0 @@ -package xdg - -import "github.com/adrg/xdg/internal/pathutil" - -// XDG Base Directory environment variables. -const ( - envDataHome = "XDG_DATA_HOME" - envDataDirs = "XDG_DATA_DIRS" - envConfigHome = "XDG_CONFIG_HOME" - envConfigDirs = "XDG_CONFIG_DIRS" - envStateHome = "XDG_STATE_HOME" - envCacheHome = "XDG_CACHE_HOME" - envRuntimeDir = "XDG_RUNTIME_DIR" -) - -type baseDirectories struct { - dataHome string - data []string - configHome string - config []string - stateHome string - cacheHome string - runtime string - - // Non-standard directories. - fonts []string - applications []string -} - -func (bd baseDirectories) dataFile(relPath string) (string, error) { - return pathutil.Create(relPath, append([]string{bd.dataHome}, bd.data...)) -} - -func (bd baseDirectories) configFile(relPath string) (string, error) { - return pathutil.Create(relPath, append([]string{bd.configHome}, bd.config...)) -} - -func (bd baseDirectories) stateFile(relPath string) (string, error) { - return pathutil.Create(relPath, []string{bd.stateHome}) -} - -func (bd baseDirectories) cacheFile(relPath string) (string, error) { - return pathutil.Create(relPath, []string{bd.cacheHome}) -} - -func (bd baseDirectories) runtimeFile(relPath string) (string, error) { - return pathutil.Create(relPath, []string{bd.runtime}) -} - -func (bd baseDirectories) searchDataFile(relPath string) (string, error) { - return pathutil.Search(relPath, append([]string{bd.dataHome}, bd.data...)) -} - -func (bd baseDirectories) searchConfigFile(relPath string) (string, error) { - return pathutil.Search(relPath, append([]string{bd.configHome}, bd.config...)) -} - -func (bd baseDirectories) searchStateFile(relPath string) (string, error) { - return pathutil.Search(relPath, []string{bd.stateHome}) -} - -func (bd baseDirectories) searchCacheFile(relPath string) (string, error) { - return pathutil.Search(relPath, []string{bd.cacheHome}) -} - -func (bd baseDirectories) searchRuntimeFile(relPath string) (string, error) { - return pathutil.Search(relPath, []string{bd.runtime}) -} diff --git a/vendor/github.com/adrg/xdg/codecov.yml b/vendor/github.com/adrg/xdg/codecov.yml deleted file mode 100644 index 54ee338fd70..00000000000 --- a/vendor/github.com/adrg/xdg/codecov.yml +++ /dev/null @@ -1,11 +0,0 @@ -coverage: - status: - project: - default: - target: 90% - threshold: 1% - patch: - default: - target: 100% -ignore: - - "paths_plan9.go" diff --git a/vendor/github.com/adrg/xdg/doc.go b/vendor/github.com/adrg/xdg/doc.go deleted file mode 100644 index 7747b183e44..00000000000 --- a/vendor/github.com/adrg/xdg/doc.go +++ /dev/null @@ -1,99 +0,0 @@ -/* -Package xdg provides an implementation of the XDG Base Directory Specification. -The specification defines a set of standard paths for storing application files -including data and configuration files. For portability and flexibility reasons, -applications should use the XDG defined locations instead of hardcoding paths. -The package also includes the locations of well known user directories. - -The current implementation supports most flavors of Unix, Windows, Mac OS and Plan 9. - - For more information regarding the XDG Base Directory Specification see: - https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html - - For more information regarding the XDG user directories see: - https://wiki.archlinux.org/index.php/XDG_user_directories - - For more information regarding the Windows Known Folders see: - https://docs.microsoft.com/en-us/windows/win32/shell/known-folders - -Usage - -XDG Base Directory - package main - - import ( - "log" - - "github.com/adrg/xdg" - ) - - func main() { - // XDG Base Directory paths. - log.Println("Home data directory:", xdg.DataHome) - log.Println("Data directories:", xdg.DataDirs) - log.Println("Home config directory:", xdg.ConfigHome) - log.Println("Config directories:", xdg.ConfigDirs) - log.Println("Home state directory:", xdg.StateHome) - log.Println("Cache directory:", xdg.CacheHome) - log.Println("Runtime directory:", xdg.RuntimeDir) - - // Other common directories. - log.Println("Home directory:", xdg.Home) - log.Println("Application directories:", xdg.ApplicationDirs) - log.Println("Font directories:", xdg.FontDirs) - - // Obtain a suitable location for application config files. - // ConfigFile takes one parameter which must contain the name of the file, - // but it can also contain a set of parent directories. If the directories - // don't exist, they will be created relative to the base config directory. - configFilePath, err := xdg.ConfigFile("appname/config.yaml") - if err != nil { - log.Fatal(err) - } - log.Println("Save the config file at:", configFilePath) - - // For other types of application files use: - // xdg.DataFile() - // xdg.StateFile() - // xdg.CacheFile() - // xdg.RuntimeFile() - - // Finding application config files. - // SearchConfigFile takes one parameter which must contain the name of - // the file, but it can also contain a set of parent directories relative - // to the config search paths (xdg.ConfigHome and xdg.ConfigDirs). - configFilePath, err = xdg.SearchConfigFile("appname/config.yaml") - if err != nil { - log.Fatal(err) - } - log.Println("Config file was found at:", configFilePath) - - // For other types of application files use: - // xdg.SearchDataFile() - // xdg.SearchStateFile() - // xdg.SearchCacheFile() - // xdg.SearchRuntimeFile() - } - -XDG user directories - package main - - import ( - "log" - - "github.com/adrg/xdg" - ) - - func main() { - // XDG user directories. - log.Println("Desktop directory:", xdg.UserDirs.Desktop) - log.Println("Download directory:", xdg.UserDirs.Download) - log.Println("Documents directory:", xdg.UserDirs.Documents) - log.Println("Music directory:", xdg.UserDirs.Music) - log.Println("Pictures directory:", xdg.UserDirs.Pictures) - log.Println("Videos directory:", xdg.UserDirs.Videos) - log.Println("Templates directory:", xdg.UserDirs.Templates) - log.Println("Public directory:", xdg.UserDirs.PublicShare) - } -*/ -package xdg diff --git a/vendor/github.com/adrg/xdg/internal/pathutil/pathutil.go b/vendor/github.com/adrg/xdg/internal/pathutil/pathutil.go deleted file mode 100644 index 7422342b3c4..00000000000 --- a/vendor/github.com/adrg/xdg/internal/pathutil/pathutil.go +++ /dev/null @@ -1,78 +0,0 @@ -package pathutil - -import ( - "fmt" - "os" - "path/filepath" - "strings" -) - -// Unique eliminates the duplicate paths from the provided slice and returns -// the result. The items in the output slice are in the order in which they -// occur in the input slice. If a `home` location is provided, the paths are -// expanded using the `ExpandHome` function. -func Unique(paths []string, home string) []string { - var ( - uniq []string - registry = map[string]struct{}{} - ) - - for _, p := range paths { - p = ExpandHome(p, home) - if p != "" && filepath.IsAbs(p) { - if _, ok := registry[p]; ok { - continue - } - - registry[p] = struct{}{} - uniq = append(uniq, p) - } - } - - return uniq -} - -// Create returns a suitable location relative to which the file with the -// specified `name` can be written. The first path from the provided `paths` -// slice which is successfully created (or already exists) is used as a base -// path for the file. The `name` parameter should contain the name of the file -// which is going to be written in the location returned by this function, but -// it can also contain a set of parent directories, which will be created -// relative to the selected parent path. -func Create(name string, paths []string) (string, error) { - var searchedPaths []string - for _, p := range paths { - p = filepath.Join(p, name) - - dir := filepath.Dir(p) - if Exists(dir) { - return p, nil - } - if err := os.MkdirAll(dir, os.ModeDir|0700); err == nil { - return p, nil - } - - searchedPaths = append(searchedPaths, dir) - } - - return "", fmt.Errorf("could not create any of the following paths: %s", - strings.Join(searchedPaths, ", ")) -} - -// Search searches for the file with the specified `name` in the provided -// slice of `paths`. The `name` parameter must contain the name of the file, -// but it can also contain a set of parent directories. -func Search(name string, paths []string) (string, error) { - var searchedPaths []string - for _, p := range paths { - p = filepath.Join(p, name) - if Exists(p) { - return p, nil - } - - searchedPaths = append(searchedPaths, filepath.Dir(p)) - } - - return "", fmt.Errorf("could not locate `%s` in any of the following paths: %s", - filepath.Base(name), strings.Join(searchedPaths, ", ")) -} diff --git a/vendor/github.com/adrg/xdg/internal/pathutil/pathutil_plan9.go b/vendor/github.com/adrg/xdg/internal/pathutil/pathutil_plan9.go deleted file mode 100644 index 8ee4e8d2fbe..00000000000 --- a/vendor/github.com/adrg/xdg/internal/pathutil/pathutil_plan9.go +++ /dev/null @@ -1,29 +0,0 @@ -package pathutil - -import ( - "os" - "path/filepath" - "strings" -) - -// Exists returns true if the specified path exists. -func Exists(path string) bool { - _, err := os.Stat(path) - return err == nil || os.IsExist(err) -} - -// ExpandHome substitutes `~` and `$home` at the start of the specified -// `path` using the provided `home` location. -func ExpandHome(path, home string) string { - if path == "" || home == "" { - return path - } - if path[0] == '~' { - return filepath.Join(home, path[1:]) - } - if strings.HasPrefix(path, "$home") { - return filepath.Join(home, path[5:]) - } - - return path -} diff --git a/vendor/github.com/adrg/xdg/internal/pathutil/pathutil_unix.go b/vendor/github.com/adrg/xdg/internal/pathutil/pathutil_unix.go deleted file mode 100644 index a014c66ef67..00000000000 --- a/vendor/github.com/adrg/xdg/internal/pathutil/pathutil_unix.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || nacl || linux || netbsd || openbsd || solaris -// +build aix darwin dragonfly freebsd js,wasm nacl linux netbsd openbsd solaris - -package pathutil - -import ( - "os" - "path/filepath" - "strings" -) - -// Exists returns true if the specified path exists. -func Exists(path string) bool { - _, err := os.Stat(path) - return err == nil || os.IsExist(err) -} - -// ExpandHome substitutes `~` and `$HOME` at the start of the specified -// `path` using the provided `home` location. -func ExpandHome(path, home string) string { - if path == "" || home == "" { - return path - } - if path[0] == '~' { - return filepath.Join(home, path[1:]) - } - if strings.HasPrefix(path, "$HOME") { - return filepath.Join(home, path[5:]) - } - - return path -} diff --git a/vendor/github.com/adrg/xdg/internal/pathutil/pathutil_windows.go b/vendor/github.com/adrg/xdg/internal/pathutil/pathutil_windows.go deleted file mode 100644 index 44080e3ab61..00000000000 --- a/vendor/github.com/adrg/xdg/internal/pathutil/pathutil_windows.go +++ /dev/null @@ -1,64 +0,0 @@ -package pathutil - -import ( - "os" - "path/filepath" - "strings" - - "golang.org/x/sys/windows" -) - -// Exists returns true if the specified path exists. -func Exists(path string) bool { - fi, err := os.Lstat(path) - if fi != nil && fi.Mode()&os.ModeSymlink != 0 { - _, err = filepath.EvalSymlinks(path) - } - - return err == nil || os.IsExist(err) -} - -// ExpandHome substitutes `%USERPROFILE%` at the start of the specified -// `path` using the provided `home` location. -func ExpandHome(path, home string) string { - if path == "" || home == "" { - return path - } - if strings.HasPrefix(path, `%USERPROFILE%`) { - return filepath.Join(home, path[13:]) - } - - return path -} - -// KnownFolder returns the location of the folder with the specified ID. -// If that fails, the folder location is determined by reading the provided -// environment variables (the first non-empty read value is returned). -// If that fails as well, the first non-empty fallback is returned. -// If all of the above fails, the function returns an empty string. -func KnownFolder(id *windows.KNOWNFOLDERID, envVars []string, fallbacks []string) string { - if id != nil { - flags := []uint32{windows.KF_FLAG_DEFAULT, windows.KF_FLAG_DEFAULT_PATH} - for _, flag := range flags { - p, _ := windows.KnownFolderPath(id, flag|windows.KF_FLAG_DONT_VERIFY) - if p != "" { - return p - } - } - } - - for _, envVar := range envVars { - p := os.Getenv(envVar) - if p != "" { - return p - } - } - - for _, fallback := range fallbacks { - if fallback != "" { - return fallback - } - } - - return "" -} diff --git a/vendor/github.com/adrg/xdg/paths_darwin.go b/vendor/github.com/adrg/xdg/paths_darwin.go deleted file mode 100644 index bfe9ad9bc47..00000000000 --- a/vendor/github.com/adrg/xdg/paths_darwin.go +++ /dev/null @@ -1,60 +0,0 @@ -package xdg - -import ( - "os" - "path/filepath" -) - -func homeDir() string { - if home := os.Getenv("HOME"); home != "" { - return home - } - - return "/" -} - -func initDirs(home string) { - initBaseDirs(home) - initUserDirs(home) -} - -func initBaseDirs(home string) { - homeAppSupport := filepath.Join(home, "Library", "Application Support") - rootAppSupport := "/Library/Application Support" - - // Initialize standard directories. - baseDirs.dataHome = xdgPath(envDataHome, homeAppSupport) - baseDirs.data = xdgPaths(envDataDirs, rootAppSupport) - baseDirs.configHome = xdgPath(envConfigHome, homeAppSupport) - baseDirs.config = xdgPaths(envConfigDirs, - filepath.Join(home, "Library", "Preferences"), - rootAppSupport, - "/Library/Preferences", - ) - baseDirs.stateHome = xdgPath(envStateHome, homeAppSupport) - baseDirs.cacheHome = xdgPath(envCacheHome, filepath.Join(home, "Library", "Caches")) - baseDirs.runtime = xdgPath(envRuntimeDir, homeAppSupport) - - // Initialize non-standard directories. - baseDirs.applications = []string{ - "/Applications", - } - - baseDirs.fonts = []string{ - filepath.Join(home, "Library/Fonts"), - "/Library/Fonts", - "/System/Library/Fonts", - "/Network/Library/Fonts", - } -} - -func initUserDirs(home string) { - UserDirs.Desktop = xdgPath(envDesktopDir, filepath.Join(home, "Desktop")) - UserDirs.Download = xdgPath(envDownloadDir, filepath.Join(home, "Downloads")) - UserDirs.Documents = xdgPath(envDocumentsDir, filepath.Join(home, "Documents")) - UserDirs.Music = xdgPath(envMusicDir, filepath.Join(home, "Music")) - UserDirs.Pictures = xdgPath(envPicturesDir, filepath.Join(home, "Pictures")) - UserDirs.Videos = xdgPath(envVideosDir, filepath.Join(home, "Movies")) - UserDirs.Templates = xdgPath(envTemplatesDir, filepath.Join(home, "Templates")) - UserDirs.PublicShare = xdgPath(envPublicShareDir, filepath.Join(home, "Public")) -} diff --git a/vendor/github.com/adrg/xdg/paths_plan9.go b/vendor/github.com/adrg/xdg/paths_plan9.go deleted file mode 100644 index 2882f688b3c..00000000000 --- a/vendor/github.com/adrg/xdg/paths_plan9.go +++ /dev/null @@ -1,55 +0,0 @@ -package xdg - -import ( - "os" - "path/filepath" -) - -func homeDir() string { - if home := os.Getenv("home"); home != "" { - return home - } - - return "/" -} - -func initDirs(home string) { - initBaseDirs(home) - initUserDirs(home) -} - -func initBaseDirs(home string) { - homeLibDir := filepath.Join(home, "lib") - rootLibDir := "/lib" - - // Initialize standard directories. - baseDirs.dataHome = xdgPath(envDataHome, homeLibDir) - baseDirs.data = xdgPaths(envDataDirs, rootLibDir) - baseDirs.configHome = xdgPath(envConfigHome, homeLibDir) - baseDirs.config = xdgPaths(envConfigDirs, rootLibDir) - baseDirs.stateHome = xdgPath(envStateHome, filepath.Join(homeLibDir, "state")) - baseDirs.cacheHome = xdgPath(envCacheHome, filepath.Join(homeLibDir, "cache")) - baseDirs.runtime = xdgPath(envRuntimeDir, "/tmp") - - // Initialize non-standard directories. - baseDirs.applications = []string{ - filepath.Join(home, "bin"), - "/bin", - } - - baseDirs.fonts = []string{ - filepath.Join(homeLibDir, "font"), - "/lib/font", - } -} - -func initUserDirs(home string) { - UserDirs.Desktop = xdgPath(envDesktopDir, filepath.Join(home, "desktop")) - UserDirs.Download = xdgPath(envDownloadDir, filepath.Join(home, "downloads")) - UserDirs.Documents = xdgPath(envDocumentsDir, filepath.Join(home, "documents")) - UserDirs.Music = xdgPath(envMusicDir, filepath.Join(home, "music")) - UserDirs.Pictures = xdgPath(envPicturesDir, filepath.Join(home, "pictures")) - UserDirs.Videos = xdgPath(envVideosDir, filepath.Join(home, "videos")) - UserDirs.Templates = xdgPath(envTemplatesDir, filepath.Join(home, "templates")) - UserDirs.PublicShare = xdgPath(envPublicShareDir, filepath.Join(home, "public")) -} diff --git a/vendor/github.com/adrg/xdg/paths_unix.go b/vendor/github.com/adrg/xdg/paths_unix.go deleted file mode 100644 index ad571dfc811..00000000000 --- a/vendor/github.com/adrg/xdg/paths_unix.go +++ /dev/null @@ -1,71 +0,0 @@ -//go:build aix || dragonfly || freebsd || (js && wasm) || nacl || linux || netbsd || openbsd || solaris -// +build aix dragonfly freebsd js,wasm nacl linux netbsd openbsd solaris - -package xdg - -import ( - "os" - "path/filepath" - "strconv" - - "github.com/adrg/xdg/internal/pathutil" -) - -func homeDir() string { - if home := os.Getenv("HOME"); home != "" { - return home - } - - return "/" -} - -func initDirs(home string) { - initBaseDirs(home) - initUserDirs(home) -} - -func initBaseDirs(home string) { - // Initialize standard directories. - baseDirs.dataHome = xdgPath(envDataHome, filepath.Join(home, ".local", "share")) - baseDirs.data = xdgPaths(envDataDirs, "/usr/local/share", "/usr/share") - baseDirs.configHome = xdgPath(envConfigHome, filepath.Join(home, ".config")) - baseDirs.config = xdgPaths(envConfigDirs, "/etc/xdg") - baseDirs.stateHome = xdgPath(envStateHome, filepath.Join(home, ".local", "state")) - baseDirs.cacheHome = xdgPath(envCacheHome, filepath.Join(home, ".cache")) - baseDirs.runtime = xdgPath(envRuntimeDir, filepath.Join("/run/user", strconv.Itoa(os.Getuid()))) - - // Initialize non-standard directories. - appDirs := []string{ - filepath.Join(baseDirs.dataHome, "applications"), - filepath.Join(home, ".local/share/applications"), - "/usr/local/share/applications", - "/usr/share/applications", - } - - fontDirs := []string{ - filepath.Join(baseDirs.dataHome, "fonts"), - filepath.Join(home, ".fonts"), - filepath.Join(home, ".local/share/fonts"), - "/usr/local/share/fonts", - "/usr/share/fonts", - } - - for _, dir := range baseDirs.data { - appDirs = append(appDirs, filepath.Join(dir, "applications")) - fontDirs = append(fontDirs, filepath.Join(dir, "fonts")) - } - - baseDirs.applications = pathutil.Unique(appDirs, Home) - baseDirs.fonts = pathutil.Unique(fontDirs, Home) -} - -func initUserDirs(home string) { - UserDirs.Desktop = xdgPath(envDesktopDir, filepath.Join(home, "Desktop")) - UserDirs.Download = xdgPath(envDownloadDir, filepath.Join(home, "Downloads")) - UserDirs.Documents = xdgPath(envDocumentsDir, filepath.Join(home, "Documents")) - UserDirs.Music = xdgPath(envMusicDir, filepath.Join(home, "Music")) - UserDirs.Pictures = xdgPath(envPicturesDir, filepath.Join(home, "Pictures")) - UserDirs.Videos = xdgPath(envVideosDir, filepath.Join(home, "Videos")) - UserDirs.Templates = xdgPath(envTemplatesDir, filepath.Join(home, "Templates")) - UserDirs.PublicShare = xdgPath(envPublicShareDir, filepath.Join(home, "Public")) -} diff --git a/vendor/github.com/adrg/xdg/paths_windows.go b/vendor/github.com/adrg/xdg/paths_windows.go deleted file mode 100644 index 722d3e7856f..00000000000 --- a/vendor/github.com/adrg/xdg/paths_windows.go +++ /dev/null @@ -1,168 +0,0 @@ -package xdg - -import ( - "path/filepath" - - "github.com/adrg/xdg/internal/pathutil" - "golang.org/x/sys/windows" -) - -func homeDir() string { - return pathutil.KnownFolder( - windows.FOLDERID_Profile, - []string{"USERPROFILE"}, - nil, - ) -} - -func initDirs(home string) { - kf := initKnownFolders(home) - initBaseDirs(home, kf) - initUserDirs(home, kf) -} - -func initBaseDirs(home string, kf *knownFolders) { - // Initialize standard directories. - baseDirs.dataHome = xdgPath(envDataHome, kf.localAppData) - baseDirs.data = xdgPaths(envDataDirs, kf.roamingAppData, kf.programData) - baseDirs.configHome = xdgPath(envConfigHome, kf.localAppData) - baseDirs.config = xdgPaths(envConfigDirs, kf.programData, kf.roamingAppData) - baseDirs.stateHome = xdgPath(envStateHome, kf.localAppData) - baseDirs.cacheHome = xdgPath(envCacheHome, filepath.Join(kf.localAppData, "cache")) - baseDirs.runtime = xdgPath(envRuntimeDir, kf.localAppData) - - // Initialize non-standard directories. - baseDirs.applications = []string{ - kf.programs, - kf.commonPrograms, - } - baseDirs.fonts = []string{ - kf.fonts, - filepath.Join(kf.localAppData, "Microsoft", "Windows", "Fonts"), - } -} - -func initUserDirs(home string, kf *knownFolders) { - UserDirs.Desktop = xdgPath(envDesktopDir, kf.desktop) - UserDirs.Download = xdgPath(envDownloadDir, kf.downloads) - UserDirs.Documents = xdgPath(envDocumentsDir, kf.documents) - UserDirs.Music = xdgPath(envMusicDir, kf.music) - UserDirs.Pictures = xdgPath(envPicturesDir, kf.pictures) - UserDirs.Videos = xdgPath(envVideosDir, kf.videos) - UserDirs.Templates = xdgPath(envTemplatesDir, kf.templates) - UserDirs.PublicShare = xdgPath(envPublicShareDir, kf.public) -} - -type knownFolders struct { - systemDrive string - systemRoot string - programData string - userProfile string - userProfiles string - roamingAppData string - localAppData string - desktop string - downloads string - documents string - music string - pictures string - videos string - templates string - public string - fonts string - programs string - commonPrograms string -} - -func initKnownFolders(home string) *knownFolders { - kf := &knownFolders{ - userProfile: home, - } - kf.systemDrive = filepath.VolumeName(pathutil.KnownFolder( - windows.FOLDERID_Windows, - []string{"SystemDrive", "SystemRoot", "windir"}, - []string{home, `C:`}, - )) + string(filepath.Separator) - kf.systemRoot = pathutil.KnownFolder( - windows.FOLDERID_Windows, - []string{"SystemRoot", "windir"}, - []string{filepath.Join(kf.systemDrive, "Windows")}, - ) - kf.programData = pathutil.KnownFolder( - windows.FOLDERID_ProgramData, - []string{"ProgramData", "ALLUSERSPROFILE"}, - []string{filepath.Join(kf.systemDrive, "ProgramData")}, - ) - kf.userProfiles = pathutil.KnownFolder( - windows.FOLDERID_UserProfiles, - nil, - []string{filepath.Join(kf.systemDrive, "Users")}, - ) - kf.roamingAppData = pathutil.KnownFolder( - windows.FOLDERID_RoamingAppData, - []string{"APPDATA"}, - []string{filepath.Join(home, "AppData", "Roaming")}, - ) - kf.localAppData = pathutil.KnownFolder( - windows.FOLDERID_LocalAppData, - []string{"LOCALAPPDATA"}, - []string{filepath.Join(home, "AppData", "Local")}, - ) - kf.desktop = pathutil.KnownFolder( - windows.FOLDERID_Desktop, - nil, - []string{filepath.Join(home, "Desktop")}, - ) - kf.downloads = pathutil.KnownFolder( - windows.FOLDERID_Downloads, - nil, - []string{filepath.Join(home, "Downloads")}, - ) - kf.documents = pathutil.KnownFolder( - windows.FOLDERID_Documents, - nil, - []string{filepath.Join(home, "Documents")}, - ) - kf.music = pathutil.KnownFolder( - windows.FOLDERID_Music, - nil, - []string{filepath.Join(home, "Music")}, - ) - kf.pictures = pathutil.KnownFolder( - windows.FOLDERID_Pictures, - nil, - []string{filepath.Join(home, "Pictures")}, - ) - kf.videos = pathutil.KnownFolder( - windows.FOLDERID_Videos, - nil, - []string{filepath.Join(home, "Videos")}, - ) - kf.templates = pathutil.KnownFolder( - windows.FOLDERID_Templates, - nil, - []string{filepath.Join(kf.roamingAppData, "Microsoft", "Windows", "Templates")}, - ) - kf.public = pathutil.KnownFolder( - windows.FOLDERID_Public, - []string{"PUBLIC"}, - []string{filepath.Join(kf.userProfiles, "Public")}, - ) - kf.fonts = pathutil.KnownFolder( - windows.FOLDERID_Fonts, - nil, - []string{filepath.Join(kf.systemRoot, "Fonts")}, - ) - kf.programs = pathutil.KnownFolder( - windows.FOLDERID_Programs, - nil, - []string{filepath.Join(kf.roamingAppData, "Microsoft", "Windows", "Start Menu", "Programs")}, - ) - kf.commonPrograms = pathutil.KnownFolder( - windows.FOLDERID_CommonPrograms, - nil, - []string{filepath.Join(kf.programData, "Microsoft", "Windows", "Start Menu", "Programs")}, - ) - - return kf -} diff --git a/vendor/github.com/adrg/xdg/user_dirs.go b/vendor/github.com/adrg/xdg/user_dirs.go deleted file mode 100644 index 72088748d0c..00000000000 --- a/vendor/github.com/adrg/xdg/user_dirs.go +++ /dev/null @@ -1,40 +0,0 @@ -package xdg - -// XDG user directories environment variables. -const ( - envDesktopDir = "XDG_DESKTOP_DIR" - envDownloadDir = "XDG_DOWNLOAD_DIR" - envDocumentsDir = "XDG_DOCUMENTS_DIR" - envMusicDir = "XDG_MUSIC_DIR" - envPicturesDir = "XDG_PICTURES_DIR" - envVideosDir = "XDG_VIDEOS_DIR" - envTemplatesDir = "XDG_TEMPLATES_DIR" - envPublicShareDir = "XDG_PUBLICSHARE_DIR" -) - -// UserDirectories defines the locations of well known user directories. -type UserDirectories struct { - // Desktop defines the location of the user's desktop directory. - Desktop string - - // Download defines a suitable location for user downloaded files. - Download string - - // Documents defines a suitable location for user document files. - Documents string - - // Music defines a suitable location for user audio files. - Music string - - // Pictures defines a suitable location for user image files. - Pictures string - - // VideosDir defines a suitable location for user video files. - Videos string - - // Templates defines a suitable location for user template files. - Templates string - - // PublicShare defines a suitable location for user shared files. - PublicShare string -} diff --git a/vendor/github.com/adrg/xdg/xdg.go b/vendor/github.com/adrg/xdg/xdg.go deleted file mode 100644 index 3d33ca6e55d..00000000000 --- a/vendor/github.com/adrg/xdg/xdg.go +++ /dev/null @@ -1,218 +0,0 @@ -package xdg - -import ( - "os" - "path/filepath" - - "github.com/adrg/xdg/internal/pathutil" -) - -var ( - // Home contains the path of the user's home directory. - Home string - - // DataHome defines the base directory relative to which user-specific - // data files should be stored. This directory is defined by the - // $XDG_DATA_HOME environment variable. If the variable is not set, - // a default equal to $HOME/.local/share should be used. - DataHome string - - // DataDirs defines the preference-ordered set of base directories to - // search for data files in addition to the DataHome base directory. - // This set of directories is defined by the $XDG_DATA_DIRS environment - // variable. If the variable is not set, the default directories - // to be used are /usr/local/share and /usr/share, in that order. The - // DataHome directory is considered more important than any of the - // directories defined by DataDirs. Therefore, user data files should be - // written relative to the DataHome directory, if possible. - DataDirs []string - - // ConfigHome defines the base directory relative to which user-specific - // configuration files should be written. This directory is defined by - // the $XDG_CONFIG_HOME environment variable. If the variable is not - // not set, a default equal to $HOME/.config should be used. - ConfigHome string - - // ConfigDirs defines the preference-ordered set of base directories to - // search for configuration files in addition to the ConfigHome base - // directory. This set of directories is defined by the $XDG_CONFIG_DIRS - // environment variable. If the variable is not set, a default equal - // to /etc/xdg should be used. The ConfigHome directory is considered - // more important than any of the directories defined by ConfigDirs. - // Therefore, user config files should be written relative to the - // ConfigHome directory, if possible. - ConfigDirs []string - - // StateHome defines the base directory relative to which user-specific - // state files should be stored. This directory is defined by the - // $XDG_STATE_HOME environment variable. If the variable is not set, - // a default equal to ~/.local/state should be used. - StateHome string - - // CacheHome defines the base directory relative to which user-specific - // non-essential (cached) data should be written. This directory is - // defined by the $XDG_CACHE_HOME environment variable. If the variable - // is not set, a default equal to $HOME/.cache should be used. - CacheHome string - - // RuntimeDir defines the base directory relative to which user-specific - // non-essential runtime files and other file objects (such as sockets, - // named pipes, etc.) should be stored. This directory is defined by the - // $XDG_RUNTIME_DIR environment variable. If the variable is not set, - // applications should fall back to a replacement directory with similar - // capabilities. Applications should use this directory for communication - // and synchronization purposes and should not place larger files in it, - // since it might reside in runtime memory and cannot necessarily be - // swapped out to disk. - RuntimeDir string - - // UserDirs defines the locations of well known user directories. - UserDirs UserDirectories - - // FontDirs defines the common locations where font files are stored. - FontDirs []string - - // ApplicationDirs defines the common locations of applications. - ApplicationDirs []string - - // baseDirs defines the locations of base directories. - baseDirs baseDirectories -) - -func init() { - Reload() -} - -// Reload refreshes base and user directories by reading the environment. -// Defaults are applied for XDG variables which are empty or not present -// in the environment. -func Reload() { - // Initialize home directory. - Home = homeDir() - - // Initialize base and user directories. - initDirs(Home) - - // Set standard directories. - DataHome = baseDirs.dataHome - DataDirs = baseDirs.data - ConfigHome = baseDirs.configHome - ConfigDirs = baseDirs.config - StateHome = baseDirs.stateHome - CacheHome = baseDirs.cacheHome - RuntimeDir = baseDirs.runtime - - // Set non-standard directories. - FontDirs = baseDirs.fonts - ApplicationDirs = baseDirs.applications -} - -// DataFile returns a suitable location for the specified data file. -// The relPath parameter must contain the name of the data file, and -// optionally, a set of parent directories (e.g. appname/app.data). -// If the specified directories do not exist, they will be created relative -// to the base data directory. On failure, an error containing the -// attempted paths is returned. -func DataFile(relPath string) (string, error) { - return baseDirs.dataFile(relPath) -} - -// ConfigFile returns a suitable location for the specified config file. -// The relPath parameter must contain the name of the config file, and -// optionally, a set of parent directories (e.g. appname/app.yaml). -// If the specified directories do not exist, they will be created relative -// to the base config directory. On failure, an error containing the -// attempted paths is returned. -func ConfigFile(relPath string) (string, error) { - return baseDirs.configFile(relPath) -} - -// StateFile returns a suitable location for the specified state file. State -// files are usually volatile data files, not suitable to be stored relative -// to the $XDG_DATA_HOME directory. -// The relPath parameter must contain the name of the state file, and -// optionally, a set of parent directories (e.g. appname/app.state). -// If the specified directories do not exist, they will be created relative -// to the base state directory. On failure, an error containing the -// attempted paths is returned. -func StateFile(relPath string) (string, error) { - return baseDirs.stateFile(relPath) -} - -// CacheFile returns a suitable location for the specified cache file. -// The relPath parameter must contain the name of the cache file, and -// optionally, a set of parent directories (e.g. appname/app.cache). -// If the specified directories do not exist, they will be created relative -// to the base cache directory. On failure, an error containing the -// attempted paths is returned. -func CacheFile(relPath string) (string, error) { - return baseDirs.cacheFile(relPath) -} - -// RuntimeFile returns a suitable location for the specified runtime file. -// The relPath parameter must contain the name of the runtime file, and -// optionally, a set of parent directories (e.g. appname/app.pid). -// If the specified directories do not exist, they will be created relative -// to the base runtime directory. On failure, an error containing the -// attempted paths is returned. -func RuntimeFile(relPath string) (string, error) { - return baseDirs.runtimeFile(relPath) -} - -// SearchDataFile searches for specified file in the data search paths. -// The relPath parameter must contain the name of the data file, and -// optionally, a set of parent directories (e.g. appname/app.data). If the -// file cannot be found, an error specifying the searched paths is returned. -func SearchDataFile(relPath string) (string, error) { - return baseDirs.searchDataFile(relPath) -} - -// SearchConfigFile searches for the specified file in config search paths. -// The relPath parameter must contain the name of the config file, and -// optionally, a set of parent directories (e.g. appname/app.yaml). If the -// file cannot be found, an error specifying the searched paths is returned. -func SearchConfigFile(relPath string) (string, error) { - return baseDirs.searchConfigFile(relPath) -} - -// SearchStateFile searches for the specified file in the state search path. -// The relPath parameter must contain the name of the state file, and -// optionally, a set of parent directories (e.g. appname/app.state). If the -// file cannot be found, an error specifying the searched path is returned. -func SearchStateFile(relPath string) (string, error) { - return baseDirs.searchStateFile(relPath) -} - -// SearchCacheFile searches for the specified file in the cache search path. -// The relPath parameter must contain the name of the cache file, and -// optionally, a set of parent directories (e.g. appname/app.cache). If the -// file cannot be found, an error specifying the searched path is returned. -func SearchCacheFile(relPath string) (string, error) { - return baseDirs.searchCacheFile(relPath) -} - -// SearchRuntimeFile searches for the specified file in the runtime search path. -// The relPath parameter must contain the name of the runtime file, and -// optionally, a set of parent directories (e.g. appname/app.pid). If the -// file cannot be found, an error specifying the searched path is returned. -func SearchRuntimeFile(relPath string) (string, error) { - return baseDirs.searchRuntimeFile(relPath) -} - -func xdgPath(name, defaultPath string) string { - dir := pathutil.ExpandHome(os.Getenv(name), Home) - if dir != "" && filepath.IsAbs(dir) { - return dir - } - - return defaultPath -} - -func xdgPaths(name string, defaultPaths ...string) []string { - dirs := pathutil.Unique(filepath.SplitList(os.Getenv(name)), Home) - if len(dirs) != 0 { - return dirs - } - - return pathutil.Unique(defaultPaths, Home) -} diff --git a/vendor/github.com/atotto/clipboard/.travis.yml b/vendor/github.com/atotto/clipboard/.travis.yml deleted file mode 100644 index 23f21d836ec..00000000000 --- a/vendor/github.com/atotto/clipboard/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: go - -os: - - linux - - osx - - windows - -go: - - go1.13.x - - go1.x - -services: - - xvfb - -before_install: - - export DISPLAY=:99.0 - -script: - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xsel; fi - - go test -v . - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xclip; fi - - go test -v . diff --git a/vendor/github.com/atotto/clipboard/LICENSE b/vendor/github.com/atotto/clipboard/LICENSE deleted file mode 100644 index dee3257b0a1..00000000000 --- a/vendor/github.com/atotto/clipboard/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013 Ato Araki. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of @atotto. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/atotto/clipboard/README.md b/vendor/github.com/atotto/clipboard/README.md deleted file mode 100644 index 41fdd57b85f..00000000000 --- a/vendor/github.com/atotto/clipboard/README.md +++ /dev/null @@ -1,48 +0,0 @@ -[![Build Status](https://travis-ci.org/atotto/clipboard.svg?branch=master)](https://travis-ci.org/atotto/clipboard) - -[![GoDoc](https://godoc.org/github.com/atotto/clipboard?status.svg)](http://godoc.org/github.com/atotto/clipboard) - -# Clipboard for Go - -Provide copying and pasting to the Clipboard for Go. - -Build: - - $ go get github.com/atotto/clipboard - -Platforms: - -* OSX -* Windows 7 (probably work on other Windows) -* Linux, Unix (requires 'xclip' or 'xsel' command to be installed) - - -Document: - -* http://godoc.org/github.com/atotto/clipboard - -Notes: - -* Text string only -* UTF-8 text encoding only (no conversion) - -TODO: - -* Clipboard watcher(?) - -## Commands: - -paste shell command: - - $ go get github.com/atotto/clipboard/cmd/gopaste - $ # example: - $ gopaste > document.txt - -copy shell command: - - $ go get github.com/atotto/clipboard/cmd/gocopy - $ # example: - $ cat document.txt | gocopy - - - diff --git a/vendor/github.com/atotto/clipboard/clipboard.go b/vendor/github.com/atotto/clipboard/clipboard.go deleted file mode 100644 index d7907d3a71d..00000000000 --- a/vendor/github.com/atotto/clipboard/clipboard.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2013 @atotto. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package clipboard read/write on clipboard -package clipboard - -// ReadAll read string from clipboard -func ReadAll() (string, error) { - return readAll() -} - -// WriteAll write string to clipboard -func WriteAll(text string) error { - return writeAll(text) -} - -// Unsupported might be set true during clipboard init, to help callers decide -// whether or not to offer clipboard options. -var Unsupported bool diff --git a/vendor/github.com/atotto/clipboard/clipboard_darwin.go b/vendor/github.com/atotto/clipboard/clipboard_darwin.go deleted file mode 100644 index 6f33078db23..00000000000 --- a/vendor/github.com/atotto/clipboard/clipboard_darwin.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2013 @atotto. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin - -package clipboard - -import ( - "os/exec" -) - -var ( - pasteCmdArgs = "pbpaste" - copyCmdArgs = "pbcopy" -) - -func getPasteCommand() *exec.Cmd { - return exec.Command(pasteCmdArgs) -} - -func getCopyCommand() *exec.Cmd { - return exec.Command(copyCmdArgs) -} - -func readAll() (string, error) { - pasteCmd := getPasteCommand() - out, err := pasteCmd.Output() - if err != nil { - return "", err - } - return string(out), nil -} - -func writeAll(text string) error { - copyCmd := getCopyCommand() - in, err := copyCmd.StdinPipe() - if err != nil { - return err - } - - if err := copyCmd.Start(); err != nil { - return err - } - if _, err := in.Write([]byte(text)); err != nil { - return err - } - if err := in.Close(); err != nil { - return err - } - return copyCmd.Wait() -} diff --git a/vendor/github.com/atotto/clipboard/clipboard_plan9.go b/vendor/github.com/atotto/clipboard/clipboard_plan9.go deleted file mode 100644 index 9d2fef4ef20..00000000000 --- a/vendor/github.com/atotto/clipboard/clipboard_plan9.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2013 @atotto. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build plan9 - -package clipboard - -import ( - "os" - "io/ioutil" -) - -func readAll() (string, error) { - f, err := os.Open("/dev/snarf") - if err != nil { - return "", err - } - defer f.Close() - - str, err := ioutil.ReadAll(f) - if err != nil { - return "", err - } - - return string(str), nil -} - -func writeAll(text string) error { - f, err := os.OpenFile("/dev/snarf", os.O_WRONLY, 0666) - if err != nil { - return err - } - defer f.Close() - - _, err = f.Write([]byte(text)) - if err != nil { - return err - } - - return nil -} diff --git a/vendor/github.com/atotto/clipboard/clipboard_unix.go b/vendor/github.com/atotto/clipboard/clipboard_unix.go deleted file mode 100644 index d9f6a561003..00000000000 --- a/vendor/github.com/atotto/clipboard/clipboard_unix.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2013 @atotto. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build freebsd linux netbsd openbsd solaris dragonfly - -package clipboard - -import ( - "errors" - "os" - "os/exec" -) - -const ( - xsel = "xsel" - xclip = "xclip" - powershellExe = "powershell.exe" - clipExe = "clip.exe" - wlcopy = "wl-copy" - wlpaste = "wl-paste" - termuxClipboardGet = "termux-clipboard-get" - termuxClipboardSet = "termux-clipboard-set" -) - -var ( - Primary bool - trimDos bool - - pasteCmdArgs []string - copyCmdArgs []string - - xselPasteArgs = []string{xsel, "--output", "--clipboard"} - xselCopyArgs = []string{xsel, "--input", "--clipboard"} - - xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"} - xclipCopyArgs = []string{xclip, "-in", "-selection", "clipboard"} - - powershellExePasteArgs = []string{powershellExe, "Get-Clipboard"} - clipExeCopyArgs = []string{clipExe} - - wlpasteArgs = []string{wlpaste, "--no-newline"} - wlcopyArgs = []string{wlcopy} - - termuxPasteArgs = []string{termuxClipboardGet} - termuxCopyArgs = []string{termuxClipboardSet} - - missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.") -) - -func init() { - if os.Getenv("WAYLAND_DISPLAY") != "" { - pasteCmdArgs = wlpasteArgs - copyCmdArgs = wlcopyArgs - - if _, err := exec.LookPath(wlcopy); err == nil { - if _, err := exec.LookPath(wlpaste); err == nil { - return - } - } - } - - pasteCmdArgs = xclipPasteArgs - copyCmdArgs = xclipCopyArgs - - if _, err := exec.LookPath(xclip); err == nil { - return - } - - pasteCmdArgs = xselPasteArgs - copyCmdArgs = xselCopyArgs - - if _, err := exec.LookPath(xsel); err == nil { - return - } - - pasteCmdArgs = termuxPasteArgs - copyCmdArgs = termuxCopyArgs - - if _, err := exec.LookPath(termuxClipboardSet); err == nil { - if _, err := exec.LookPath(termuxClipboardGet); err == nil { - return - } - } - - pasteCmdArgs = powershellExePasteArgs - copyCmdArgs = clipExeCopyArgs - trimDos = true - - if _, err := exec.LookPath(clipExe); err == nil { - if _, err := exec.LookPath(powershellExe); err == nil { - return - } - } - - Unsupported = true -} - -func getPasteCommand() *exec.Cmd { - if Primary { - pasteCmdArgs = pasteCmdArgs[:1] - } - return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...) -} - -func getCopyCommand() *exec.Cmd { - if Primary { - copyCmdArgs = copyCmdArgs[:1] - } - return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...) -} - -func readAll() (string, error) { - if Unsupported { - return "", missingCommands - } - pasteCmd := getPasteCommand() - out, err := pasteCmd.Output() - if err != nil { - return "", err - } - result := string(out) - if trimDos && len(result) > 1 { - result = result[:len(result)-2] - } - return result, nil -} - -func writeAll(text string) error { - if Unsupported { - return missingCommands - } - copyCmd := getCopyCommand() - in, err := copyCmd.StdinPipe() - if err != nil { - return err - } - - if err := copyCmd.Start(); err != nil { - return err - } - if _, err := in.Write([]byte(text)); err != nil { - return err - } - if err := in.Close(); err != nil { - return err - } - return copyCmd.Wait() -} diff --git a/vendor/github.com/atotto/clipboard/clipboard_windows.go b/vendor/github.com/atotto/clipboard/clipboard_windows.go deleted file mode 100644 index 253bb932236..00000000000 --- a/vendor/github.com/atotto/clipboard/clipboard_windows.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2013 @atotto. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build windows - -package clipboard - -import ( - "runtime" - "syscall" - "time" - "unsafe" -) - -const ( - cfUnicodetext = 13 - gmemMoveable = 0x0002 -) - -var ( - user32 = syscall.MustLoadDLL("user32") - isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable") - openClipboard = user32.MustFindProc("OpenClipboard") - closeClipboard = user32.MustFindProc("CloseClipboard") - emptyClipboard = user32.MustFindProc("EmptyClipboard") - getClipboardData = user32.MustFindProc("GetClipboardData") - setClipboardData = user32.MustFindProc("SetClipboardData") - - kernel32 = syscall.NewLazyDLL("kernel32") - globalAlloc = kernel32.NewProc("GlobalAlloc") - globalFree = kernel32.NewProc("GlobalFree") - globalLock = kernel32.NewProc("GlobalLock") - globalUnlock = kernel32.NewProc("GlobalUnlock") - lstrcpy = kernel32.NewProc("lstrcpyW") -) - -// waitOpenClipboard opens the clipboard, waiting for up to a second to do so. -func waitOpenClipboard() error { - started := time.Now() - limit := started.Add(time.Second) - var r uintptr - var err error - for time.Now().Before(limit) { - r, _, err = openClipboard.Call(0) - if r != 0 { - return nil - } - time.Sleep(time.Millisecond) - } - return err -} - -func readAll() (string, error) { - // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). - // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. - runtime.LockOSThread() - defer runtime.UnlockOSThread() - if formatAvailable, _, err := isClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 { - return "", err - } - err := waitOpenClipboard() - if err != nil { - return "", err - } - - h, _, err := getClipboardData.Call(cfUnicodetext) - if h == 0 { - _, _, _ = closeClipboard.Call() - return "", err - } - - l, _, err := globalLock.Call(h) - if l == 0 { - _, _, _ = closeClipboard.Call() - return "", err - } - - text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:]) - - r, _, err := globalUnlock.Call(h) - if r == 0 { - _, _, _ = closeClipboard.Call() - return "", err - } - - closed, _, err := closeClipboard.Call() - if closed == 0 { - return "", err - } - return text, nil -} - -func writeAll(text string) error { - // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). - // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - err := waitOpenClipboard() - if err != nil { - return err - } - - r, _, err := emptyClipboard.Call(0) - if r == 0 { - _, _, _ = closeClipboard.Call() - return err - } - - data := syscall.StringToUTF16(text) - - // "If the hMem parameter identifies a memory object, the object must have - // been allocated using the function with the GMEM_MOVEABLE flag." - h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0])))) - if h == 0 { - _, _, _ = closeClipboard.Call() - return err - } - defer func() { - if h != 0 { - globalFree.Call(h) - } - }() - - l, _, err := globalLock.Call(h) - if l == 0 { - _, _, _ = closeClipboard.Call() - return err - } - - r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0]))) - if r == 0 { - _, _, _ = closeClipboard.Call() - return err - } - - r, _, err = globalUnlock.Call(h) - if r == 0 { - if err.(syscall.Errno) != 0 { - _, _, _ = closeClipboard.Call() - return err - } - } - - r, _, err = setClipboardData.Call(cfUnicodetext, h) - if r == 0 { - _, _, _ = closeClipboard.Call() - return err - } - h = 0 // suppress deferred cleanup - closed, _, err := closeClipboard.Call() - if closed == 0 { - return err - } - return nil -} diff --git a/vendor/github.com/aybabtme/humanlog/.gitignore b/vendor/github.com/aybabtme/humanlog/.gitignore deleted file mode 100644 index d81933f7594..00000000000 --- a/vendor/github.com/aybabtme/humanlog/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.ignore -dist diff --git a/vendor/github.com/aybabtme/humanlog/LICENSE.md b/vendor/github.com/aybabtme/humanlog/LICENSE.md deleted file mode 100644 index 8dada3edaf5..00000000000 --- a/vendor/github.com/aybabtme/humanlog/LICENSE.md +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/aybabtme/humanlog/README.md b/vendor/github.com/aybabtme/humanlog/README.md deleted file mode 100644 index 35aafe97494..00000000000 --- a/vendor/github.com/aybabtme/humanlog/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# humanlog - -Read logs from `stdin` and prints them back to `stdout`, but prettier. - -# Using it - -[Grab a release](https://github.com/aybabtme/humanlog/releases) or : - -## With Go installed -```bash -$ go get -u github.com/aybabtme/humanlog/... -``` - -## On linux - -```bash -wget -qO- https://github.com/aybabtme/humanlog/releases/download/0.4.0/humanlog_Linux_x86_64.tar.gz | tar xvz -``` - -## On OS X - -```bash -brew tap aybabtme/homebrew-tap -brew install humanlog -``` - -# Example - -If you emit logs in JSON or in [`logfmt`](https://brandur.org/logfmt), you will enjoy pretty logs when those -entries are encountered by `humanlog`. Unrecognized lines are left unchanged. - -``` -$ humanlog < /var/log/logfile.log -``` - -![2__fish___users_antoine_gocode_src_github_com_aybabtme_humanlog__fish_](https://cloud.githubusercontent.com/assets/1189716/4328545/f2330bb4-3f86-11e4-8242-4f49f6ae9efc.png) - -# Contributing - -How to help: - -* __support more log formats__: by submitting `human.Handler` implementations. -* __live querying__: add support for filtering in log output in real time. -* __charting__: some key-values have semantics that could be charted in real time. For -instance, durations, frequency of numeric values, etc. See the [l2met][] project. - -# Usage - -``` -NAME: - humanlog - reads structured logs from stdin, makes them pretty on stdout! - -USAGE: - humanlog [global options] command [command options] [arguments...] - -VERSION: - 0.4.0 - -AUTHOR: - Antoine Grondin - - -COMMANDS: - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS: - --skip '--skip option --skip option' keys to skip when parsing a log entry - --keep '--keep option --keep option' keys to keep when parsing a log entry - --sort-longest sort by longest key after having sorted lexicographically - --skip-unchanged skip keys that have the same value than the previous entry - --truncate truncates values that are longer than --truncate-length - --truncate-length '15' truncate values that are longer than this length - --help, -h show help - --version, -v print the version -``` -[l2met]: https://github.com/ryandotsmith/l2met diff --git a/vendor/github.com/aybabtme/humanlog/docker_compose_handler.go b/vendor/github.com/aybabtme/humanlog/docker_compose_handler.go deleted file mode 100644 index 1025f4c3818..00000000000 --- a/vendor/github.com/aybabtme/humanlog/docker_compose_handler.go +++ /dev/null @@ -1,29 +0,0 @@ -package humanlog - -import ( - "regexp" -) - -// dcLogsPrefixRe parses out a prefix like 'web_1 | ' from docker-compose -// The regex exists of five parts: -// 1. An optional color terminal escape sequence -// 2. The name of the service -// 3. Any number of spaces, and a pipe symbol -// 4. An optional color reset escape sequence -// 5. The rest of the line -var dcLogsPrefixRe = regexp.MustCompile("^(?:\x1b\\[\\d+m)?(?P[a-zA-Z0-9._-]+)\\s+\\|(?:\x1b\\[0m)? (?P.*)$") - -type handler interface { - TryHandle([]byte) bool - setField(key, val []byte) -} - -func tryDockerComposePrefix(d []byte, nextHandler handler) bool { - if matches := dcLogsPrefixRe.FindSubmatch(d); matches != nil { - if nextHandler.TryHandle(matches[2]) { - nextHandler.setField([]byte(`service`), matches[1]) - return true - } - } - return false -} diff --git a/vendor/github.com/aybabtme/humanlog/goreleaser.yaml b/vendor/github.com/aybabtme/humanlog/goreleaser.yaml deleted file mode 100644 index 96537dc7e50..00000000000 --- a/vendor/github.com/aybabtme/humanlog/goreleaser.yaml +++ /dev/null @@ -1,20 +0,0 @@ -build: - main: ./cmd/humanlog/main.go - binary: humanlog - ldflags: - - -s -w -X main.build={{.Version}} - goos: - - windows - - darwin - - linux - goarch: - - amd64 - -brews: - - github: - owner: aybabtme - name: homebrew-tap - -nfpms: - - formats: - - deb diff --git a/vendor/github.com/aybabtme/humanlog/handler.go b/vendor/github.com/aybabtme/humanlog/handler.go deleted file mode 100644 index 72cab99c077..00000000000 --- a/vendor/github.com/aybabtme/humanlog/handler.go +++ /dev/null @@ -1,108 +0,0 @@ -package humanlog - -import ( - "time" - - "github.com/fatih/color" - "github.com/kr/logfmt" -) - -// Handler can recognize it's log lines, parse them and prettify them. -type Handler interface { - CanHandle(line []byte) bool - Prettify(skipUnchanged bool) []byte - logfmt.Handler -} - -var DefaultOptions = &HandlerOptions{ - SortLongest: true, - SkipUnchanged: true, - Truncates: true, - LightBg: false, - TruncateLength: 15, - TimeFormat: time.Stamp, - - KeyColor: color.New(color.FgGreen), - ValColor: color.New(color.FgHiWhite), - TimeLightBgColor: color.New(color.FgBlack), - TimeDarkBgColor: color.New(color.FgWhite), - MsgLightBgColor: color.New(color.FgBlack), - MsgAbsentLightBgColor: color.New(color.FgHiBlack), - MsgDarkBgColor: color.New(color.FgHiWhite), - MsgAbsentDarkBgColor: color.New(color.FgWhite), - DebugLevelColor: color.New(color.FgMagenta), - InfoLevelColor: color.New(color.FgCyan), - WarnLevelColor: color.New(color.FgYellow), - ErrorLevelColor: color.New(color.FgRed), - PanicLevelColor: color.New(color.BgRed), - FatalLevelColor: color.New(color.BgHiRed, color.FgHiWhite), - UnknownLevelColor: color.New(color.FgMagenta), -} - -type HandlerOptions struct { - Skip map[string]struct{} - Keep map[string]struct{} - SortLongest bool - SkipUnchanged bool - Truncates bool - LightBg bool - TruncateLength int - TimeFormat string - - KeyColor *color.Color - ValColor *color.Color - TimeLightBgColor *color.Color - TimeDarkBgColor *color.Color - MsgLightBgColor *color.Color - MsgAbsentLightBgColor *color.Color - MsgDarkBgColor *color.Color - MsgAbsentDarkBgColor *color.Color - DebugLevelColor *color.Color - InfoLevelColor *color.Color - WarnLevelColor *color.Color - ErrorLevelColor *color.Color - PanicLevelColor *color.Color - FatalLevelColor *color.Color - UnknownLevelColor *color.Color -} - -func (h *HandlerOptions) shouldShowKey(key string) bool { - if len(h.Keep) != 0 { - if _, keep := h.Keep[key]; keep { - return true - } - } - if len(h.Skip) != 0 { - if _, skip := h.Skip[key]; skip { - return false - } - } - return true -} - -func (h *HandlerOptions) shouldShowUnchanged(key string) bool { - if len(h.Keep) != 0 { - if _, keep := h.Keep[key]; keep { - return true - } - } - return false -} - -func (h *HandlerOptions) SetSkip(skip []string) { - if h.Skip == nil { - h.Skip = make(map[string]struct{}) - } - for _, key := range skip { - h.Skip[key] = struct{}{} - } -} - -func (h *HandlerOptions) SetKeep(keep []string) { - if h.Keep == nil { - h.Keep = make(map[string]struct{}) - } - for _, key := range keep { - h.Keep[key] = struct{}{} - } -} diff --git a/vendor/github.com/aybabtme/humanlog/json_handler.go b/vendor/github.com/aybabtme/humanlog/json_handler.go deleted file mode 100644 index af25af8f8eb..00000000000 --- a/vendor/github.com/aybabtme/humanlog/json_handler.go +++ /dev/null @@ -1,261 +0,0 @@ -package humanlog - -import ( - "bytes" - "encoding/json" - "fmt" - "math" - "sort" - "strings" - "text/tabwriter" - "time" - - "github.com/fatih/color" -) - -// JSONHandler can handle logs emmited by logrus.TextFormatter loggers. -type JSONHandler struct { - buf *bytes.Buffer - out *tabwriter.Writer - truncKV int - - Opts *HandlerOptions - - Level string - Time time.Time - Message string - Fields map[string]string - - last map[string]string -} - -func checkEachUntilFound(fieldList []string, found func(string) bool) bool { - for _, field := range fieldList { - if found(field) { - return true - } - } - return false -} - -// supportedTimeFields enumerates supported timestamp field names -var supportedTimeFields = []string{"time", "ts", "@timestamp", "timestamp"} - -// supportedMessageFields enumarates supported Message field names -var supportedMessageFields = []string{"message", "msg"} - -// supportedLevelFields enumarates supported level field names -var supportedLevelFields = []string{"level", "lvl", "loglevel"} - -func (h *JSONHandler) clear() { - h.Level = "" - h.Time = time.Time{} - h.Message = "" - h.last = h.Fields - h.Fields = make(map[string]string) - if h.buf != nil { - h.buf.Reset() - } -} - -// TryHandle tells if this line was handled by this handler. -func (h *JSONHandler) TryHandle(d []byte) bool { - if !h.UnmarshalJSON(d) { - h.clear() - return false - } - return true -} - -// UnmarshalJSON sets the fields of the handler. -func (h *JSONHandler) UnmarshalJSON(data []byte) bool { - raw := make(map[string]interface{}) - err := json.Unmarshal(data, &raw) - if err != nil { - return false - } - - checkEachUntilFound(supportedTimeFields, func(field string) bool { - time, ok := tryParseTime(raw[field]) - if ok { - h.Time = time - delete(raw, field) - } - return ok - }) - - checkEachUntilFound(supportedMessageFields, func(field string) bool { - msg, ok := raw[field].(string) - if ok { - h.Message = msg - delete(raw, field) - } - return ok - }) - - checkEachUntilFound(supportedLevelFields, func(field string) bool { - lvl, ok := raw[field] - if !ok { - return false - } - if strLvl, ok := lvl.(string); ok { - h.Level = strLvl - } else if flLvl, ok := lvl.(float64); ok { - h.Level = convertBunyanLogLevel(flLvl) - } else { - h.Level = "???" - } - delete(raw, field) - return true - }) - - if h.Fields == nil { - h.Fields = make(map[string]string) - } - - for key, val := range raw { - switch v := val.(type) { - case float64: - if v-math.Floor(v) < 0.000001 && v < 1e9 { - // looks like an integer that's not too large - h.Fields[key] = fmt.Sprintf("%d", int(v)) - } else { - h.Fields[key] = fmt.Sprintf("%g", v) - } - case string: - h.Fields[key] = fmt.Sprintf("%q", v) - default: - h.Fields[key] = fmt.Sprintf("%v", v) - } - } - - return true -} -func (h *JSONHandler) setField(key, val []byte) { - if h.Fields == nil { - h.Fields = make(map[string]string) - } - h.Fields[string(key)] = string(val) -} - -// Prettify the output in a logrus like fashion. -func (h *JSONHandler) Prettify(skipUnchanged bool) []byte { - defer h.clear() - if h.out == nil { - if h.Opts == nil { - h.Opts = DefaultOptions - } - h.buf = bytes.NewBuffer(nil) - h.out = tabwriter.NewWriter(h.buf, 0, 1, 0, '\t', 0) - } - - var ( - msgColor *color.Color - msgAbsentColor *color.Color - ) - if h.Opts.LightBg { - msgColor = h.Opts.MsgLightBgColor - msgAbsentColor = h.Opts.MsgAbsentLightBgColor - } else { - msgColor = h.Opts.MsgDarkBgColor - msgAbsentColor = h.Opts.MsgAbsentDarkBgColor - } - msgColor = color.New(color.FgHiWhite) - msgAbsentColor = color.New(color.FgHiWhite) - - var msg string - if h.Message == "" { - msg = msgAbsentColor.Sprint("") - } else { - msg = msgColor.Sprint(h.Message) - } - - lvl := strings.ToUpper(h.Level)[:imin(4, len(h.Level))] - var level string - switch h.Level { - case "debug": - level = h.Opts.DebugLevelColor.Sprint(lvl) - case "info": - level = h.Opts.InfoLevelColor.Sprint(lvl) - case "warn", "warning": - level = h.Opts.WarnLevelColor.Sprint(lvl) - case "error": - level = h.Opts.ErrorLevelColor.Sprint(lvl) - case "fatal", "panic": - level = h.Opts.FatalLevelColor.Sprint(lvl) - default: - level = h.Opts.UnknownLevelColor.Sprint(lvl) - } - - var timeColor *color.Color - if h.Opts.LightBg { - timeColor = h.Opts.TimeLightBgColor - } else { - timeColor = h.Opts.TimeDarkBgColor - } - _, _ = fmt.Fprintf(h.out, "%s |%s| %s\t %s", - timeColor.Sprint(h.Time.Format(h.Opts.TimeFormat)), - level, - msg, - strings.Join(h.joinKVs(skipUnchanged, "="), "\t "), - ) - - _ = h.out.Flush() - - return h.buf.Bytes() -} - -func (h *JSONHandler) joinKVs(skipUnchanged bool, sep string) []string { - - kv := make([]string, 0, len(h.Fields)) - for k, v := range h.Fields { - if !h.Opts.shouldShowKey(k) { - continue - } - - if skipUnchanged { - if lastV, ok := h.last[k]; ok && lastV == v && !h.Opts.shouldShowUnchanged(k) { - continue - } - } - kstr := h.Opts.KeyColor.Sprint(k) - - var vstr string - if h.Opts.Truncates && len(v) > h.Opts.TruncateLength { - vstr = v[:h.Opts.TruncateLength] + "..." - } else { - vstr = v - } - vstr = h.Opts.ValColor.Sprint(vstr) - kv = append(kv, kstr+sep+vstr) - } - - sort.Strings(kv) - - if h.Opts.SortLongest { - sort.Stable(byLongest(kv)) - } - - return kv -} - -// convertBunyanLogLevel returns a human readable log level given a numerical bunyan level -// https://github.com/trentm/node-bunyan#levels -func convertBunyanLogLevel(level float64) string { - switch level { - case 10: - return "trace" - case 20: - return "debug" - case 30: - return "info" - case 40: - return "warn" - case 50: - return "error" - case 60: - return "fatal" - default: - return "???" - } -} diff --git a/vendor/github.com/aybabtme/humanlog/logfmt_handler.go b/vendor/github.com/aybabtme/humanlog/logfmt_handler.go deleted file mode 100644 index 10223ddb07b..00000000000 --- a/vendor/github.com/aybabtme/humanlog/logfmt_handler.go +++ /dev/null @@ -1,239 +0,0 @@ -package humanlog - -import ( - "bytes" - "fmt" - "sort" - "strconv" - "strings" - "text/tabwriter" - "time" - - "github.com/fatih/color" - "github.com/go-logfmt/logfmt" -) - -// LogfmtHandler can handle logs emmited by logrus.TextFormatter loggers. -type LogfmtHandler struct { - buf *bytes.Buffer - out *tabwriter.Writer - truncKV int - - Opts *HandlerOptions - - Level string - Time time.Time - Message string - Fields map[string]string - - last map[string]string -} - -func (h *LogfmtHandler) clear() { - h.Level = "" - h.Time = time.Time{} - h.Message = "" - h.last = h.Fields - h.Fields = make(map[string]string) - if h.buf != nil { - h.buf.Reset() - } -} - -// CanHandle tells if this line can be handled by this handler. -func (h *LogfmtHandler) TryHandle(d []byte) bool { - if !bytes.ContainsRune(d, '=') { - return false - } - - if !h.UnmarshalLogfmt(d) { - h.clear() - return false - } - return true -} - -// HandleLogfmt sets the fields of the handler. -func (h *LogfmtHandler) UnmarshalLogfmt(data []byte) bool { - dec := logfmt.NewDecoder(bytes.NewReader(data)) - for dec.ScanRecord() { - next_kv: - for dec.ScanKeyval() { - key := dec.Key() - val := dec.Value() - if h.Time.IsZero() { - foundTime := checkEachUntilFound(supportedTimeFields, func(field string) bool { - time, ok := tryParseTime(string(val)) - if ok { - h.Time = time - } - return ok - }) - if foundTime { - continue next_kv - } - } - - if len(h.Message) == 0 { - foundMessage := checkEachUntilFound(supportedMessageFields, func(field string) bool { - if !bytes.Equal(key, []byte(field)) { - return false - } - h.Message = string(val) - return true - }) - if foundMessage { - continue next_kv - } - } - - if len(h.Level) == 0 { - foundLevel := checkEachUntilFound(supportedLevelFields, func(field string) bool { - if !bytes.Equal(key, []byte(field)) { - return false - } - h.Level = string(val) - return true - }) - if foundLevel { - continue next_kv - } - } - - h.setField(key, val) - } - } - return dec.Err() == nil -} - -// Prettify the output in a logrus like fashion. -func (h *LogfmtHandler) Prettify(skipUnchanged bool) []byte { - defer h.clear() - if h.out == nil { - if h.Opts == nil { - h.Opts = DefaultOptions - } - h.buf = bytes.NewBuffer(nil) - h.out = tabwriter.NewWriter(h.buf, 0, 1, 0, '\t', 0) - } - - var ( - msgColor *color.Color - msgAbsentColor *color.Color - ) - if h.Opts.LightBg { - msgColor = h.Opts.MsgLightBgColor - msgAbsentColor = h.Opts.MsgAbsentLightBgColor - } else { - msgColor = h.Opts.MsgDarkBgColor - msgAbsentColor = h.Opts.MsgAbsentDarkBgColor - } - - var msg string - if h.Message == "" { - msg = msgAbsentColor.Sprint("") - } else { - msg = msgColor.Sprint(h.Message) - } - - lvl := strings.ToUpper(h.Level)[:imin(4, len(h.Level))] - var level string - switch h.Level { - case "debug": - level = h.Opts.DebugLevelColor.Sprint(lvl) - case "info": - level = h.Opts.InfoLevelColor.Sprint(lvl) - case "warn", "warning": - level = h.Opts.WarnLevelColor.Sprint(lvl) - case "error": - level = h.Opts.ErrorLevelColor.Sprint(lvl) - case "fatal", "panic": - level = h.Opts.FatalLevelColor.Sprint(lvl) - default: - level = h.Opts.UnknownLevelColor.Sprint(lvl) - } - - var timeColor *color.Color - if h.Opts.LightBg { - timeColor = h.Opts.TimeLightBgColor - } else { - timeColor = h.Opts.TimeDarkBgColor - } - _, _ = fmt.Fprintf(h.out, "%s |%s| %s\t %s", - timeColor.Sprint(h.Time.Format(h.Opts.TimeFormat)), - level, - msg, - strings.Join(h.joinKVs(skipUnchanged, "="), "\t "), - ) - - _ = h.out.Flush() - - return h.buf.Bytes() -} - -func (h *LogfmtHandler) setLevel(val []byte) { h.Level = string(val) } -func (h *LogfmtHandler) setMessage(val []byte) { h.Message = string(val) } -func (h *LogfmtHandler) setTime(val []byte) (parsed bool) { - valStr := string(val) - if valFloat, err := strconv.ParseFloat(valStr, 64); err == nil { - h.Time, parsed = tryParseTime(valFloat) - } else { - h.Time, parsed = tryParseTime(string(val)) - } - return -} - -func (h *LogfmtHandler) setField(key, val []byte) { - if h.Fields == nil { - h.Fields = make(map[string]string) - } - h.Fields[string(key)] = string(val) -} - -func (h *LogfmtHandler) joinKVs(skipUnchanged bool, sep string) []string { - - kv := make([]string, 0, len(h.Fields)) - for k, v := range h.Fields { - if !h.Opts.shouldShowKey(k) { - continue - } - - if skipUnchanged { - if lastV, ok := h.last[k]; ok && lastV == v && !h.Opts.shouldShowUnchanged(k) { - continue - } - } - - kstr := h.Opts.KeyColor.Sprint(k) - - var vstr string - if h.Opts.Truncates && len(v) > h.Opts.TruncateLength { - vstr = v[:h.Opts.TruncateLength] + "..." - } else { - vstr = v - } - vstr = h.Opts.ValColor.Sprint(vstr) - kv = append(kv, kstr+sep+vstr) - } - - sort.Strings(kv) - - if h.Opts.SortLongest { - sort.Stable(byLongest(kv)) - } - - return kv -} - -type byLongest []string - -func (s byLongest) Len() int { return len(s) } -func (s byLongest) Less(i, j int) bool { return len(s[i]) < len(s[j]) } -func (s byLongest) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -func imin(a, b int) int { - if a < b { - return a - } - return b -} diff --git a/vendor/github.com/aybabtme/humanlog/scanner.go b/vendor/github.com/aybabtme/humanlog/scanner.go deleted file mode 100644 index 7c785ffbc56..00000000000 --- a/vendor/github.com/aybabtme/humanlog/scanner.go +++ /dev/null @@ -1,68 +0,0 @@ -package humanlog - -import ( - "bufio" - "bytes" - "io" -) - -var ( - eol = [...]byte{'\n'} -) - -// Scanner reads logfmt'd lines from src and prettify them onto dst. -// If the lines aren't logfmt, it will simply write them out with no -// prettification. -func Scanner(src io.Reader, dst io.Writer, opts *HandlerOptions) error { - in := bufio.NewScanner(src) - in.Split(bufio.ScanLines) - - var line uint64 - - var lastLogfmt bool - var lastJSON bool - - logfmtEntry := LogfmtHandler{Opts: opts} - jsonEntry := JSONHandler{Opts: opts} - - for in.Scan() { - line++ - lineData := in.Bytes() - - // remove that pesky syslog crap - lineData = bytes.TrimPrefix(lineData, []byte("@cee: ")) - - switch { - - case jsonEntry.TryHandle(lineData): - dst.Write(jsonEntry.Prettify(opts.SkipUnchanged && lastJSON)) - lastJSON = true - - case logfmtEntry.TryHandle(lineData): - dst.Write(logfmtEntry.Prettify(opts.SkipUnchanged && lastLogfmt)) - lastLogfmt = true - - case tryDockerComposePrefix(lineData, &jsonEntry): - dst.Write(jsonEntry.Prettify(opts.SkipUnchanged && lastJSON)) - lastJSON = true - - case tryDockerComposePrefix(lineData, &logfmtEntry): - dst.Write(logfmtEntry.Prettify(opts.SkipUnchanged && lastLogfmt)) - lastLogfmt = true - - default: - lastLogfmt = false - lastJSON = false - dst.Write(lineData) - } - dst.Write(eol[:]) - - } - - switch err := in.Err(); err { - case nil, io.EOF: - return nil - default: - return err - } -} diff --git a/vendor/github.com/aybabtme/humanlog/time_parse.go b/vendor/github.com/aybabtme/humanlog/time_parse.go deleted file mode 100644 index e061477dd90..00000000000 --- a/vendor/github.com/aybabtme/humanlog/time_parse.go +++ /dev/null @@ -1,66 +0,0 @@ -package humanlog - -import ( - "time" -) - -var formats = []string{ - "2006-01-02 15:04:05.999999999 -0700 MST", - "2006-01-02 15:04:05", - time.RFC3339, - time.RFC3339Nano, - time.RFC822, - time.RFC822Z, - time.RFC850, - time.RFC1123, - time.RFC1123Z, - time.UnixDate, - time.RubyDate, - time.ANSIC, - time.Kitchen, - time.Stamp, - time.StampMilli, - time.StampMicro, - time.StampNano, -} - -func parseTimeFloat64(value float64) time.Time { - v := int64(value) - switch { - case v > 1e18: - case v > 1e15: - v *= 1e3 - case v > 1e12: - v *= 1e6 - default: - return time.Unix(v, 0) - } - - return time.Unix(v/1e9, v%1e9) -} - -// tries to parse time using a couple of formats before giving up -func tryParseTime(value interface{}) (time.Time, bool) { - var t time.Time - var err error - switch value.(type) { - case string: - for _, layout := range formats { - t, err = time.Parse(layout, value.(string)) - if err == nil { - return t, true - } - } - case float32: - return parseTimeFloat64(float64(value.(float32))), true - case float64: - return parseTimeFloat64(value.(float64)), true - case int: - return parseTimeFloat64(float64(value.(int))), true - case int32: - return parseTimeFloat64(float64(value.(int32))), true - case int64: - return parseTimeFloat64(float64(value.(int64))), true - } - return t, false -} diff --git a/vendor/github.com/bahlo/generic-list-go/LICENSE b/vendor/github.com/bahlo/generic-list-go/LICENSE deleted file mode 100644 index 6a66aea5eaf..00000000000 --- a/vendor/github.com/bahlo/generic-list-go/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/bahlo/generic-list-go/README.md b/vendor/github.com/bahlo/generic-list-go/README.md deleted file mode 100644 index 68bbce9fba7..00000000000 --- a/vendor/github.com/bahlo/generic-list-go/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# generic-list-go [![CI](https://github.com/bahlo/generic-list-go/actions/workflows/ci.yml/badge.svg)](https://github.com/bahlo/generic-list-go/actions/workflows/ci.yml) - -Go [container/list](https://pkg.go.dev/container/list) but with generics. - -The code is based on `container/list` in `go1.18beta2`. diff --git a/vendor/github.com/bahlo/generic-list-go/list.go b/vendor/github.com/bahlo/generic-list-go/list.go deleted file mode 100644 index a06a7c61299..00000000000 --- a/vendor/github.com/bahlo/generic-list-go/list.go +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package list implements a doubly linked list. -// -// To iterate over a list (where l is a *List): -// for e := l.Front(); e != nil; e = e.Next() { -// // do something with e.Value -// } -// -package list - -// Element is an element of a linked list. -type Element[T any] struct { - // Next and previous pointers in the doubly-linked list of elements. - // To simplify the implementation, internally a list l is implemented - // as a ring, such that &l.root is both the next element of the last - // list element (l.Back()) and the previous element of the first list - // element (l.Front()). - next, prev *Element[T] - - // The list to which this element belongs. - list *List[T] - - // The value stored with this element. - Value T -} - -// Next returns the next list element or nil. -func (e *Element[T]) Next() *Element[T] { - if p := e.next; e.list != nil && p != &e.list.root { - return p - } - return nil -} - -// Prev returns the previous list element or nil. -func (e *Element[T]) Prev() *Element[T] { - if p := e.prev; e.list != nil && p != &e.list.root { - return p - } - return nil -} - -// List represents a doubly linked list. -// The zero value for List is an empty list ready to use. -type List[T any] struct { - root Element[T] // sentinel list element, only &root, root.prev, and root.next are used - len int // current list length excluding (this) sentinel element -} - -// Init initializes or clears list l. -func (l *List[T]) Init() *List[T] { - l.root.next = &l.root - l.root.prev = &l.root - l.len = 0 - return l -} - -// New returns an initialized list. -func New[T any]() *List[T] { return new(List[T]).Init() } - -// Len returns the number of elements of list l. -// The complexity is O(1). -func (l *List[T]) Len() int { return l.len } - -// Front returns the first element of list l or nil if the list is empty. -func (l *List[T]) Front() *Element[T] { - if l.len == 0 { - return nil - } - return l.root.next -} - -// Back returns the last element of list l or nil if the list is empty. -func (l *List[T]) Back() *Element[T] { - if l.len == 0 { - return nil - } - return l.root.prev -} - -// lazyInit lazily initializes a zero List value. -func (l *List[T]) lazyInit() { - if l.root.next == nil { - l.Init() - } -} - -// insert inserts e after at, increments l.len, and returns e. -func (l *List[T]) insert(e, at *Element[T]) *Element[T] { - e.prev = at - e.next = at.next - e.prev.next = e - e.next.prev = e - e.list = l - l.len++ - return e -} - -// insertValue is a convenience wrapper for insert(&Element{Value: v}, at). -func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] { - return l.insert(&Element[T]{Value: v}, at) -} - -// remove removes e from its list, decrements l.len -func (l *List[T]) remove(e *Element[T]) { - e.prev.next = e.next - e.next.prev = e.prev - e.next = nil // avoid memory leaks - e.prev = nil // avoid memory leaks - e.list = nil - l.len-- -} - -// move moves e to next to at. -func (l *List[T]) move(e, at *Element[T]) { - if e == at { - return - } - e.prev.next = e.next - e.next.prev = e.prev - - e.prev = at - e.next = at.next - e.prev.next = e - e.next.prev = e -} - -// Remove removes e from l if e is an element of list l. -// It returns the element value e.Value. -// The element must not be nil. -func (l *List[T]) Remove(e *Element[T]) T { - if e.list == l { - // if e.list == l, l must have been initialized when e was inserted - // in l or l == nil (e is a zero Element) and l.remove will crash - l.remove(e) - } - return e.Value -} - -// PushFront inserts a new element e with value v at the front of list l and returns e. -func (l *List[T]) PushFront(v T) *Element[T] { - l.lazyInit() - return l.insertValue(v, &l.root) -} - -// PushBack inserts a new element e with value v at the back of list l and returns e. -func (l *List[T]) PushBack(v T) *Element[T] { - l.lazyInit() - return l.insertValue(v, l.root.prev) -} - -// InsertBefore inserts a new element e with value v immediately before mark and returns e. -// If mark is not an element of l, the list is not modified. -// The mark must not be nil. -func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] { - if mark.list != l { - return nil - } - // see comment in List.Remove about initialization of l - return l.insertValue(v, mark.prev) -} - -// InsertAfter inserts a new element e with value v immediately after mark and returns e. -// If mark is not an element of l, the list is not modified. -// The mark must not be nil. -func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] { - if mark.list != l { - return nil - } - // see comment in List.Remove about initialization of l - return l.insertValue(v, mark) -} - -// MoveToFront moves element e to the front of list l. -// If e is not an element of l, the list is not modified. -// The element must not be nil. -func (l *List[T]) MoveToFront(e *Element[T]) { - if e.list != l || l.root.next == e { - return - } - // see comment in List.Remove about initialization of l - l.move(e, &l.root) -} - -// MoveToBack moves element e to the back of list l. -// If e is not an element of l, the list is not modified. -// The element must not be nil. -func (l *List[T]) MoveToBack(e *Element[T]) { - if e.list != l || l.root.prev == e { - return - } - // see comment in List.Remove about initialization of l - l.move(e, l.root.prev) -} - -// MoveBefore moves element e to its new position before mark. -// If e or mark is not an element of l, or e == mark, the list is not modified. -// The element and mark must not be nil. -func (l *List[T]) MoveBefore(e, mark *Element[T]) { - if e.list != l || e == mark || mark.list != l { - return - } - l.move(e, mark.prev) -} - -// MoveAfter moves element e to its new position after mark. -// If e or mark is not an element of l, or e == mark, the list is not modified. -// The element and mark must not be nil. -func (l *List[T]) MoveAfter(e, mark *Element[T]) { - if e.list != l || e == mark || mark.list != l { - return - } - l.move(e, mark) -} - -// PushBackList inserts a copy of another list at the back of list l. -// The lists l and other may be the same. They must not be nil. -func (l *List[T]) PushBackList(other *List[T]) { - l.lazyInit() - for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() { - l.insertValue(e.Value, l.root.prev) - } -} - -// PushFrontList inserts a copy of another list at the front of list l. -// The lists l and other may be the same. They must not be nil. -func (l *List[T]) PushFrontList(other *List[T]) { - l.lazyInit() - for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() { - l.insertValue(e.Value, &l.root) - } -} diff --git a/vendor/github.com/buger/jsonparser/.gitignore b/vendor/github.com/buger/jsonparser/.gitignore deleted file mode 100644 index 5598d8a5691..00000000000 --- a/vendor/github.com/buger/jsonparser/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ - -*.test - -*.out - -*.mprof - -.idea - -vendor/github.com/buger/goterm/ -prof.cpu -prof.mem diff --git a/vendor/github.com/buger/jsonparser/.travis.yml b/vendor/github.com/buger/jsonparser/.travis.yml deleted file mode 100644 index dbfb7cf9883..00000000000 --- a/vendor/github.com/buger/jsonparser/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: go -arch: - - amd64 - - ppc64le -go: - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - - 1.11.x -script: go test -v ./. diff --git a/vendor/github.com/buger/jsonparser/Dockerfile b/vendor/github.com/buger/jsonparser/Dockerfile deleted file mode 100644 index 37fc9fd0b4d..00000000000 --- a/vendor/github.com/buger/jsonparser/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM golang:1.6 - -RUN go get github.com/Jeffail/gabs -RUN go get github.com/bitly/go-simplejson -RUN go get github.com/pquerna/ffjson -RUN go get github.com/antonholmquist/jason -RUN go get github.com/mreiferson/go-ujson -RUN go get -tags=unsafe -u github.com/ugorji/go/codec -RUN go get github.com/mailru/easyjson - -WORKDIR /go/src/github.com/buger/jsonparser -ADD . /go/src/github.com/buger/jsonparser \ No newline at end of file diff --git a/vendor/github.com/buger/jsonparser/LICENSE b/vendor/github.com/buger/jsonparser/LICENSE deleted file mode 100644 index ac25aeb7da2..00000000000 --- a/vendor/github.com/buger/jsonparser/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 Leonid Bugaev - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/buger/jsonparser/Makefile b/vendor/github.com/buger/jsonparser/Makefile deleted file mode 100644 index e843368cf10..00000000000 --- a/vendor/github.com/buger/jsonparser/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -SOURCE = parser.go -CONTAINER = jsonparser -SOURCE_PATH = /go/src/github.com/buger/jsonparser -BENCHMARK = JsonParser -BENCHTIME = 5s -TEST = . -DRUN = docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) - -build: - docker build -t $(CONTAINER) . - -race: - $(DRUN) --env GORACE="halt_on_error=1" go test ./. $(ARGS) -v -race -timeout 15s - -bench: - $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -benchtime $(BENCHTIME) -v - -bench_local: - $(DRUN) go test $(LDFLAGS) -test.benchmem -bench . $(ARGS) -benchtime $(BENCHTIME) -v - -profile: - $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -memprofile mem.mprof -v - $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -cpuprofile cpu.out -v - $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -c - -test: - $(DRUN) go test $(LDFLAGS) ./ -run $(TEST) -timeout 10s $(ARGS) -v - -fmt: - $(DRUN) go fmt ./... - -vet: - $(DRUN) go vet ./. - -bash: - $(DRUN) /bin/bash \ No newline at end of file diff --git a/vendor/github.com/buger/jsonparser/README.md b/vendor/github.com/buger/jsonparser/README.md deleted file mode 100644 index d7e0ec397af..00000000000 --- a/vendor/github.com/buger/jsonparser/README.md +++ /dev/null @@ -1,365 +0,0 @@ -[![Go Report Card](https://goreportcard.com/badge/github.com/buger/jsonparser)](https://goreportcard.com/report/github.com/buger/jsonparser) ![License](https://img.shields.io/dub/l/vibe-d.svg) -# Alternative JSON parser for Go (10x times faster standard library) - -It does not require you to know the structure of the payload (eg. create structs), and allows accessing fields by providing the path to them. It is up to **10 times faster** than standard `encoding/json` package (depending on payload size and usage), **allocates no memory**. See benchmarks below. - -## Rationale -Originally I made this for a project that relies on a lot of 3rd party APIs that can be unpredictable and complex. -I love simplicity and prefer to avoid external dependecies. `encoding/json` requires you to know exactly your data structures, or if you prefer to use `map[string]interface{}` instead, it will be very slow and hard to manage. -I investigated what's on the market and found that most libraries are just wrappers around `encoding/json`, there is few options with own parsers (`ffjson`, `easyjson`), but they still requires you to create data structures. - - -Goal of this project is to push JSON parser to the performance limits and not sacrifice with compliance and developer user experience. - -## Example -For the given JSON our goal is to extract the user's full name, number of github followers and avatar. - -```go -import "github.com/buger/jsonparser" - -... - -data := []byte(`{ - "person": { - "name": { - "first": "Leonid", - "last": "Bugaev", - "fullName": "Leonid Bugaev" - }, - "github": { - "handle": "buger", - "followers": 109 - }, - "avatars": [ - { "url": "https://avatars1.githubusercontent.com/u/14009?v=3&s=460", "type": "thumbnail" } - ] - }, - "company": { - "name": "Acme" - } -}`) - -// You can specify key path by providing arguments to Get function -jsonparser.Get(data, "person", "name", "fullName") - -// There is `GetInt` and `GetBoolean` helpers if you exactly know key data type -jsonparser.GetInt(data, "person", "github", "followers") - -// When you try to get object, it will return you []byte slice pointer to data containing it -// In `company` it will be `{"name": "Acme"}` -jsonparser.Get(data, "company") - -// If the key doesn't exist it will throw an error -var size int64 -if value, err := jsonparser.GetInt(data, "company", "size"); err == nil { - size = value -} - -// You can use `ArrayEach` helper to iterate items [item1, item2 .... itemN] -jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { - fmt.Println(jsonparser.Get(value, "url")) -}, "person", "avatars") - -// Or use can access fields by index! -jsonparser.GetString(data, "person", "avatars", "[0]", "url") - -// You can use `ObjectEach` helper to iterate objects { "key1":object1, "key2":object2, .... "keyN":objectN } -jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error { - fmt.Printf("Key: '%s'\n Value: '%s'\n Type: %s\n", string(key), string(value), dataType) - return nil -}, "person", "name") - -// The most efficient way to extract multiple keys is `EachKey` - -paths := [][]string{ - []string{"person", "name", "fullName"}, - []string{"person", "avatars", "[0]", "url"}, - []string{"company", "url"}, -} -jsonparser.EachKey(data, func(idx int, value []byte, vt jsonparser.ValueType, err error){ - switch idx { - case 0: // []string{"person", "name", "fullName"} - ... - case 1: // []string{"person", "avatars", "[0]", "url"} - ... - case 2: // []string{"company", "url"}, - ... - } -}, paths...) - -// For more information see docs below -``` - -## Need to speedup your app? - -I'm available for consulting and can help you push your app performance to the limits. Ping me at: leonsbox@gmail.com. - -## Reference - -Library API is really simple. You just need the `Get` method to perform any operation. The rest is just helpers around it. - -You also can view API at [godoc.org](https://godoc.org/github.com/buger/jsonparser) - - -### **`Get`** -```go -func Get(data []byte, keys ...string) (value []byte, dataType jsonparser.ValueType, offset int, err error) -``` -Receives data structure, and key path to extract value from. - -Returns: -* `value` - Pointer to original data structure containing key value, or just empty slice if nothing found or error -* `dataType` - Can be: `NotExist`, `String`, `Number`, `Object`, `Array`, `Boolean` or `Null` -* `offset` - Offset from provided data structure where key value ends. Used mostly internally, for example for `ArrayEach` helper. -* `err` - If the key is not found or any other parsing issue, it should return error. If key not found it also sets `dataType` to `NotExist` - -Accepts multiple keys to specify path to JSON value (in case of quering nested structures). -If no keys are provided it will try to extract the closest JSON value (simple ones or object/array), useful for reading streams or arrays, see `ArrayEach` implementation. - -Note that keys can be an array indexes: `jsonparser.GetInt("person", "avatars", "[0]", "url")`, pretty cool, yeah? - -### **`GetString`** -```go -func GetString(data []byte, keys ...string) (val string, err error) -``` -Returns strings properly handing escaped and unicode characters. Note that this will cause additional memory allocations. - -### **`GetUnsafeString`** -If you need string in your app, and ready to sacrifice with support of escaped symbols in favor of speed. It returns string mapped to existing byte slice memory, without any allocations: -```go -s, _, := jsonparser.GetUnsafeString(data, "person", "name", "title") -switch s { - case 'CEO': - ... - case 'Engineer' - ... - ... -} -``` -Note that `unsafe` here means that your string will exist until GC will free underlying byte slice, for most of cases it means that you can use this string only in current context, and should not pass it anywhere externally: through channels or any other way. - - -### **`GetBoolean`**, **`GetInt`** and **`GetFloat`** -```go -func GetBoolean(data []byte, keys ...string) (val bool, err error) - -func GetFloat(data []byte, keys ...string) (val float64, err error) - -func GetInt(data []byte, keys ...string) (val int64, err error) -``` -If you know the key type, you can use the helpers above. -If key data type do not match, it will return error. - -### **`ArrayEach`** -```go -func ArrayEach(data []byte, cb func(value []byte, dataType jsonparser.ValueType, offset int, err error), keys ...string) -``` -Needed for iterating arrays, accepts a callback function with the same return arguments as `Get`. - -### **`ObjectEach`** -```go -func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error) -``` -Needed for iterating object, accepts a callback function. Example: -```go -var handler func([]byte, []byte, jsonparser.ValueType, int) error -handler = func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error { - //do stuff here -} -jsonparser.ObjectEach(myJson, handler) -``` - - -### **`EachKey`** -```go -func EachKey(data []byte, cb func(idx int, value []byte, dataType jsonparser.ValueType, err error), paths ...[]string) -``` -When you need to read multiple keys, and you do not afraid of low-level API `EachKey` is your friend. It read payload only single time, and calls callback function once path is found. For example when you call multiple times `Get`, it has to process payload multiple times, each time you call it. Depending on payload `EachKey` can be multiple times faster than `Get`. Path can use nested keys as well! - -```go -paths := [][]string{ - []string{"uuid"}, - []string{"tz"}, - []string{"ua"}, - []string{"st"}, -} -var data SmallPayload - -jsonparser.EachKey(smallFixture, func(idx int, value []byte, vt jsonparser.ValueType, err error){ - switch idx { - case 0: - data.Uuid, _ = value - case 1: - v, _ := jsonparser.ParseInt(value) - data.Tz = int(v) - case 2: - data.Ua, _ = value - case 3: - v, _ := jsonparser.ParseInt(value) - data.St = int(v) - } -}, paths...) -``` - -### **`Set`** -```go -func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error) -``` -Receives existing data structure, key path to set, and value to set at that key. *This functionality is experimental.* - -Returns: -* `value` - Pointer to original data structure with updated or added key value. -* `err` - If any parsing issue, it should return error. - -Accepts multiple keys to specify path to JSON value (in case of updating or creating nested structures). - -Note that keys can be an array indexes: `jsonparser.Set(data, []byte("http://github.com/"), "person", "avatars", "[0]", "url")` - -### **`Delete`** -```go -func Delete(data []byte, keys ...string) value []byte -``` -Receives existing data structure, and key path to delete. *This functionality is experimental.* - -Returns: -* `value` - Pointer to original data structure with key path deleted if it can be found. If there is no key path, then the whole data structure is deleted. - -Accepts multiple keys to specify path to JSON value (in case of updating or creating nested structures). - -Note that keys can be an array indexes: `jsonparser.Delete(data, "person", "avatars", "[0]", "url")` - - -## What makes it so fast? -* It does not rely on `encoding/json`, `reflection` or `interface{}`, the only real package dependency is `bytes`. -* Operates with JSON payload on byte level, providing you pointers to the original data structure: no memory allocation. -* No automatic type conversions, by default everything is a []byte, but it provides you value type, so you can convert by yourself (there is few helpers included). -* Does not parse full record, only keys you specified - - -## Benchmarks - -There are 3 benchmark types, trying to simulate real-life usage for small, medium and large JSON payloads. -For each metric, the lower value is better. Time/op is in nanoseconds. Values better than standard encoding/json marked as bold text. -Benchmarks run on standard Linode 1024 box. - -Compared libraries: -* https://golang.org/pkg/encoding/json -* https://github.com/Jeffail/gabs -* https://github.com/a8m/djson -* https://github.com/bitly/go-simplejson -* https://github.com/antonholmquist/jason -* https://github.com/mreiferson/go-ujson -* https://github.com/ugorji/go/codec -* https://github.com/pquerna/ffjson -* https://github.com/mailru/easyjson -* https://github.com/buger/jsonparser - -#### TLDR -If you want to skip next sections we have 2 winner: `jsonparser` and `easyjson`. -`jsonparser` is up to 10 times faster than standard `encoding/json` package (depending on payload size and usage), and almost infinitely (literally) better in memory consumption because it operates with data on byte level, and provide direct slice pointers. -`easyjson` wins in CPU in medium tests and frankly i'm impressed with this package: it is remarkable results considering that it is almost drop-in replacement for `encoding/json` (require some code generation). - -It's hard to fully compare `jsonparser` and `easyjson` (or `ffson`), they a true parsers and fully process record, unlike `jsonparser` which parse only keys you specified. - -If you searching for replacement of `encoding/json` while keeping structs, `easyjson` is an amazing choice. If you want to process dynamic JSON, have memory constrains, or more control over your data you should try `jsonparser`. - -`jsonparser` performance heavily depends on usage, and it works best when you do not need to process full record, only some keys. The more calls you need to make, the slower it will be, in contrast `easyjson` (or `ffjson`, `encoding/json`) parser record only 1 time, and then you can make as many calls as you want. - -With great power comes great responsibility! :) - - -#### Small payload - -Each test processes 190 bytes of http log as a JSON record. -It should read multiple fields. -https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_small_payload_test.go - -Library | time/op | bytes/op | allocs/op - ------ | ------- | -------- | ------- -encoding/json struct | 7879 | 880 | 18 -encoding/json interface{} | 8946 | 1521 | 38 -Jeffail/gabs | 10053 | 1649 | 46 -bitly/go-simplejson | 10128 | 2241 | 36 -antonholmquist/jason | 27152 | 7237 | 101 -github.com/ugorji/go/codec | 8806 | 2176 | 31 -mreiferson/go-ujson | **7008** | **1409** | 37 -a8m/djson | 3862 | 1249 | 30 -pquerna/ffjson | **3769** | **624** | **15** -mailru/easyjson | **2002** | **192** | **9** -buger/jsonparser | **1367** | **0** | **0** -buger/jsonparser (EachKey API) | **809** | **0** | **0** - -Winners are ffjson, easyjson and jsonparser, where jsonparser is up to 9.8x faster than encoding/json and 4.6x faster than ffjson, and slightly faster than easyjson. -If you look at memory allocation, jsonparser has no rivals, as it makes no data copy and operates with raw []byte structures and pointers to it. - -#### Medium payload - -Each test processes a 2.4kb JSON record (based on Clearbit API). -It should read multiple nested fields and 1 array. - -https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_medium_payload_test.go - -| Library | time/op | bytes/op | allocs/op | -| ------- | ------- | -------- | --------- | -| encoding/json struct | 57749 | 1336 | 29 | -| encoding/json interface{} | 79297 | 10627 | 215 | -| Jeffail/gabs | 83807 | 11202 | 235 | -| bitly/go-simplejson | 88187 | 17187 | 220 | -| antonholmquist/jason | 94099 | 19013 | 247 | -| github.com/ugorji/go/codec | 114719 | 6712 | 152 | -| mreiferson/go-ujson | **56972** | 11547 | 270 | -| a8m/djson | 28525 | 10196 | 198 | -| pquerna/ffjson | **20298** | **856** | **20** | -| mailru/easyjson | **10512** | **336** | **12** | -| buger/jsonparser | **15955** | **0** | **0** | -| buger/jsonparser (EachKey API) | **8916** | **0** | **0** | - -The difference between ffjson and jsonparser in CPU usage is smaller, while the memory consumption difference is growing. On the other hand `easyjson` shows remarkable performance for medium payload. - -`gabs`, `go-simplejson` and `jason` are based on encoding/json and map[string]interface{} and actually only helpers for unstructured JSON, their performance correlate with `encoding/json interface{}`, and they will skip next round. -`go-ujson` while have its own parser, shows same performance as `encoding/json`, also skips next round. Same situation with `ugorji/go/codec`, but it showed unexpectedly bad performance for complex payloads. - - -#### Large payload - -Each test processes a 24kb JSON record (based on Discourse API) -It should read 2 arrays, and for each item in array get a few fields. -Basically it means processing a full JSON file. - -https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_large_payload_test.go - -| Library | time/op | bytes/op | allocs/op | -| --- | --- | --- | --- | -| encoding/json struct | 748336 | 8272 | 307 | -| encoding/json interface{} | 1224271 | 215425 | 3395 | -| a8m/djson | 510082 | 213682 | 2845 | -| pquerna/ffjson | **312271** | **7792** | **298** | -| mailru/easyjson | **154186** | **6992** | **288** | -| buger/jsonparser | **85308** | **0** | **0** | - -`jsonparser` now is a winner, but do not forget that it is way more lightweight parser than `ffson` or `easyjson`, and they have to parser all the data, while `jsonparser` parse only what you need. All `ffjson`, `easysjon` and `jsonparser` have their own parsing code, and does not depend on `encoding/json` or `interface{}`, thats one of the reasons why they are so fast. `easyjson` also use a bit of `unsafe` package to reduce memory consuption (in theory it can lead to some unexpected GC issue, but i did not tested enough) - -Also last benchmark did not included `EachKey` test, because in this particular case we need to read lot of Array values, and using `ArrayEach` is more efficient. - -## Questions and support - -All bug-reports and suggestions should go though Github Issues. - -## Contributing - -1. Fork it -2. Create your feature branch (git checkout -b my-new-feature) -3. Commit your changes (git commit -am 'Added some feature') -4. Push to the branch (git push origin my-new-feature) -5. Create new Pull Request - -## Development - -All my development happens using Docker, and repo include some Make tasks to simplify development. - -* `make build` - builds docker image, usually can be called only once -* `make test` - run tests -* `make fmt` - run go fmt -* `make bench` - run benchmarks (if you need to run only single benchmark modify `BENCHMARK` variable in make file) -* `make profile` - runs benchmark and generate 3 files- `cpu.out`, `mem.mprof` and `benchmark.test` binary, which can be used for `go tool pprof` -* `make bash` - enter container (i use it for running `go tool pprof` above) diff --git a/vendor/github.com/buger/jsonparser/bytes.go b/vendor/github.com/buger/jsonparser/bytes.go deleted file mode 100644 index 0bb0ff39562..00000000000 --- a/vendor/github.com/buger/jsonparser/bytes.go +++ /dev/null @@ -1,47 +0,0 @@ -package jsonparser - -import ( - bio "bytes" -) - -// minInt64 '-9223372036854775808' is the smallest representable number in int64 -const minInt64 = `9223372036854775808` - -// About 2x faster then strconv.ParseInt because it only supports base 10, which is enough for JSON -func parseInt(bytes []byte) (v int64, ok bool, overflow bool) { - if len(bytes) == 0 { - return 0, false, false - } - - var neg bool = false - if bytes[0] == '-' { - neg = true - bytes = bytes[1:] - } - - var b int64 = 0 - for _, c := range bytes { - if c >= '0' && c <= '9' { - b = (10 * v) + int64(c-'0') - } else { - return 0, false, false - } - if overflow = (b < v); overflow { - break - } - v = b - } - - if overflow { - if neg && bio.Equal(bytes, []byte(minInt64)) { - return b, true, false - } - return 0, false, true - } - - if neg { - return -v, true, false - } else { - return v, true, false - } -} diff --git a/vendor/github.com/buger/jsonparser/bytes_safe.go b/vendor/github.com/buger/jsonparser/bytes_safe.go deleted file mode 100644 index ff16a4a1955..00000000000 --- a/vendor/github.com/buger/jsonparser/bytes_safe.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build appengine appenginevm - -package jsonparser - -import ( - "strconv" -) - -// See fastbytes_unsafe.go for explanation on why *[]byte is used (signatures must be consistent with those in that file) - -func equalStr(b *[]byte, s string) bool { - return string(*b) == s -} - -func parseFloat(b *[]byte) (float64, error) { - return strconv.ParseFloat(string(*b), 64) -} - -func bytesToString(b *[]byte) string { - return string(*b) -} - -func StringToBytes(s string) []byte { - return []byte(s) -} diff --git a/vendor/github.com/buger/jsonparser/bytes_unsafe.go b/vendor/github.com/buger/jsonparser/bytes_unsafe.go deleted file mode 100644 index 589fea87eb3..00000000000 --- a/vendor/github.com/buger/jsonparser/bytes_unsafe.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build !appengine,!appenginevm - -package jsonparser - -import ( - "reflect" - "strconv" - "unsafe" - "runtime" -) - -// -// The reason for using *[]byte rather than []byte in parameters is an optimization. As of Go 1.6, -// the compiler cannot perfectly inline the function when using a non-pointer slice. That is, -// the non-pointer []byte parameter version is slower than if its function body is manually -// inlined, whereas the pointer []byte version is equally fast to the manually inlined -// version. Instruction count in assembly taken from "go tool compile" confirms this difference. -// -// TODO: Remove hack after Go 1.7 release -// -func equalStr(b *[]byte, s string) bool { - return *(*string)(unsafe.Pointer(b)) == s -} - -func parseFloat(b *[]byte) (float64, error) { - return strconv.ParseFloat(*(*string)(unsafe.Pointer(b)), 64) -} - -// A hack until issue golang/go#2632 is fixed. -// See: https://github.com/golang/go/issues/2632 -func bytesToString(b *[]byte) string { - return *(*string)(unsafe.Pointer(b)) -} - -func StringToBytes(s string) []byte { - b := make([]byte, 0, 0) - bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) - sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) - bh.Data = sh.Data - bh.Cap = sh.Len - bh.Len = sh.Len - runtime.KeepAlive(s) - return b -} diff --git a/vendor/github.com/buger/jsonparser/escape.go b/vendor/github.com/buger/jsonparser/escape.go deleted file mode 100644 index 49669b94207..00000000000 --- a/vendor/github.com/buger/jsonparser/escape.go +++ /dev/null @@ -1,173 +0,0 @@ -package jsonparser - -import ( - "bytes" - "unicode/utf8" -) - -// JSON Unicode stuff: see https://tools.ietf.org/html/rfc7159#section-7 - -const supplementalPlanesOffset = 0x10000 -const highSurrogateOffset = 0xD800 -const lowSurrogateOffset = 0xDC00 - -const basicMultilingualPlaneReservedOffset = 0xDFFF -const basicMultilingualPlaneOffset = 0xFFFF - -func combineUTF16Surrogates(high, low rune) rune { - return supplementalPlanesOffset + (high-highSurrogateOffset)<<10 + (low - lowSurrogateOffset) -} - -const badHex = -1 - -func h2I(c byte) int { - switch { - case c >= '0' && c <= '9': - return int(c - '0') - case c >= 'A' && c <= 'F': - return int(c - 'A' + 10) - case c >= 'a' && c <= 'f': - return int(c - 'a' + 10) - } - return badHex -} - -// decodeSingleUnicodeEscape decodes a single \uXXXX escape sequence. The prefix \u is assumed to be present and -// is not checked. -// In JSON, these escapes can either come alone or as part of "UTF16 surrogate pairs" that must be handled together. -// This function only handles one; decodeUnicodeEscape handles this more complex case. -func decodeSingleUnicodeEscape(in []byte) (rune, bool) { - // We need at least 6 characters total - if len(in) < 6 { - return utf8.RuneError, false - } - - // Convert hex to decimal - h1, h2, h3, h4 := h2I(in[2]), h2I(in[3]), h2I(in[4]), h2I(in[5]) - if h1 == badHex || h2 == badHex || h3 == badHex || h4 == badHex { - return utf8.RuneError, false - } - - // Compose the hex digits - return rune(h1<<12 + h2<<8 + h3<<4 + h4), true -} - -// isUTF16EncodedRune checks if a rune is in the range for non-BMP characters, -// which is used to describe UTF16 chars. -// Source: https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane -func isUTF16EncodedRune(r rune) bool { - return highSurrogateOffset <= r && r <= basicMultilingualPlaneReservedOffset -} - -func decodeUnicodeEscape(in []byte) (rune, int) { - if r, ok := decodeSingleUnicodeEscape(in); !ok { - // Invalid Unicode escape - return utf8.RuneError, -1 - } else if r <= basicMultilingualPlaneOffset && !isUTF16EncodedRune(r) { - // Valid Unicode escape in Basic Multilingual Plane - return r, 6 - } else if r2, ok := decodeSingleUnicodeEscape(in[6:]); !ok { // Note: previous decodeSingleUnicodeEscape success guarantees at least 6 bytes remain - // UTF16 "high surrogate" without manditory valid following Unicode escape for the "low surrogate" - return utf8.RuneError, -1 - } else if r2 < lowSurrogateOffset { - // Invalid UTF16 "low surrogate" - return utf8.RuneError, -1 - } else { - // Valid UTF16 surrogate pair - return combineUTF16Surrogates(r, r2), 12 - } -} - -// backslashCharEscapeTable: when '\X' is found for some byte X, it is to be replaced with backslashCharEscapeTable[X] -var backslashCharEscapeTable = [...]byte{ - '"': '"', - '\\': '\\', - '/': '/', - 'b': '\b', - 'f': '\f', - 'n': '\n', - 'r': '\r', - 't': '\t', -} - -// unescapeToUTF8 unescapes the single escape sequence starting at 'in' into 'out' and returns -// how many characters were consumed from 'in' and emitted into 'out'. -// If a valid escape sequence does not appear as a prefix of 'in', (-1, -1) to signal the error. -func unescapeToUTF8(in, out []byte) (inLen int, outLen int) { - if len(in) < 2 || in[0] != '\\' { - // Invalid escape due to insufficient characters for any escape or no initial backslash - return -1, -1 - } - - // https://tools.ietf.org/html/rfc7159#section-7 - switch e := in[1]; e { - case '"', '\\', '/', 'b', 'f', 'n', 'r', 't': - // Valid basic 2-character escapes (use lookup table) - out[0] = backslashCharEscapeTable[e] - return 2, 1 - case 'u': - // Unicode escape - if r, inLen := decodeUnicodeEscape(in); inLen == -1 { - // Invalid Unicode escape - return -1, -1 - } else { - // Valid Unicode escape; re-encode as UTF8 - outLen := utf8.EncodeRune(out, r) - return inLen, outLen - } - } - - return -1, -1 -} - -// unescape unescapes the string contained in 'in' and returns it as a slice. -// If 'in' contains no escaped characters: -// Returns 'in'. -// Else, if 'out' is of sufficient capacity (guaranteed if cap(out) >= len(in)): -// 'out' is used to build the unescaped string and is returned with no extra allocation -// Else: -// A new slice is allocated and returned. -func Unescape(in, out []byte) ([]byte, error) { - firstBackslash := bytes.IndexByte(in, '\\') - if firstBackslash == -1 { - return in, nil - } - - // Get a buffer of sufficient size (allocate if needed) - if cap(out) < len(in) { - out = make([]byte, len(in)) - } else { - out = out[0:len(in)] - } - - // Copy the first sequence of unescaped bytes to the output and obtain a buffer pointer (subslice) - copy(out, in[:firstBackslash]) - in = in[firstBackslash:] - buf := out[firstBackslash:] - - for len(in) > 0 { - // Unescape the next escaped character - inLen, bufLen := unescapeToUTF8(in, buf) - if inLen == -1 { - return nil, MalformedStringEscapeError - } - - in = in[inLen:] - buf = buf[bufLen:] - - // Copy everything up until the next backslash - nextBackslash := bytes.IndexByte(in, '\\') - if nextBackslash == -1 { - copy(buf, in) - buf = buf[len(in):] - break - } else { - copy(buf, in[:nextBackslash]) - buf = buf[nextBackslash:] - in = in[nextBackslash:] - } - } - - // Trim the out buffer to the amount that was actually emitted - return out[:len(out)-len(buf)], nil -} diff --git a/vendor/github.com/buger/jsonparser/fuzz.go b/vendor/github.com/buger/jsonparser/fuzz.go deleted file mode 100644 index 854bd11b2cd..00000000000 --- a/vendor/github.com/buger/jsonparser/fuzz.go +++ /dev/null @@ -1,117 +0,0 @@ -package jsonparser - -func FuzzParseString(data []byte) int { - r, err := ParseString(data) - if err != nil || r == "" { - return 0 - } - return 1 -} - -func FuzzEachKey(data []byte) int { - paths := [][]string{ - {"name"}, - {"order"}, - {"nested", "a"}, - {"nested", "b"}, - {"nested2", "a"}, - {"nested", "nested3", "b"}, - {"arr", "[1]", "b"}, - {"arrInt", "[3]"}, - {"arrInt", "[5]"}, - {"nested"}, - {"arr", "["}, - {"a\n", "b\n"}, - } - EachKey(data, func(idx int, value []byte, vt ValueType, err error) {}, paths...) - return 1 -} - -func FuzzDelete(data []byte) int { - Delete(data, "test") - return 1 -} - -func FuzzSet(data []byte) int { - _, err := Set(data, []byte(`"new value"`), "test") - if err != nil { - return 0 - } - return 1 -} - -func FuzzObjectEach(data []byte) int { - _ = ObjectEach(data, func(key, value []byte, valueType ValueType, off int) error { - return nil - }) - return 1 -} - -func FuzzParseFloat(data []byte) int { - _, err := ParseFloat(data) - if err != nil { - return 0 - } - return 1 -} - -func FuzzParseInt(data []byte) int { - _, err := ParseInt(data) - if err != nil { - return 0 - } - return 1 -} - -func FuzzParseBool(data []byte) int { - _, err := ParseBoolean(data) - if err != nil { - return 0 - } - return 1 -} - -func FuzzTokenStart(data []byte) int { - _ = tokenStart(data) - return 1 -} - -func FuzzGetString(data []byte) int { - _, err := GetString(data, "test") - if err != nil { - return 0 - } - return 1 -} - -func FuzzGetFloat(data []byte) int { - _, err := GetFloat(data, "test") - if err != nil { - return 0 - } - return 1 -} - -func FuzzGetInt(data []byte) int { - _, err := GetInt(data, "test") - if err != nil { - return 0 - } - return 1 -} - -func FuzzGetBoolean(data []byte) int { - _, err := GetBoolean(data, "test") - if err != nil { - return 0 - } - return 1 -} - -func FuzzGetUnsafeString(data []byte) int { - _, err := GetUnsafeString(data, "test") - if err != nil { - return 0 - } - return 1 -} diff --git a/vendor/github.com/buger/jsonparser/oss-fuzz-build.sh b/vendor/github.com/buger/jsonparser/oss-fuzz-build.sh deleted file mode 100644 index c573b0e2d10..00000000000 --- a/vendor/github.com/buger/jsonparser/oss-fuzz-build.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -eu - -git clone https://github.com/dvyukov/go-fuzz-corpus -zip corpus.zip go-fuzz-corpus/json/corpus/* - -cp corpus.zip $OUT/fuzzparsestring_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzParseString fuzzparsestring - -cp corpus.zip $OUT/fuzzeachkey_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzEachKey fuzzeachkey - -cp corpus.zip $OUT/fuzzdelete_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzDelete fuzzdelete - -cp corpus.zip $OUT/fuzzset_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzSet fuzzset - -cp corpus.zip $OUT/fuzzobjecteach_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzObjectEach fuzzobjecteach - -cp corpus.zip $OUT/fuzzparsefloat_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzParseFloat fuzzparsefloat - -cp corpus.zip $OUT/fuzzparseint_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzParseInt fuzzparseint - -cp corpus.zip $OUT/fuzzparsebool_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzParseBool fuzzparsebool - -cp corpus.zip $OUT/fuzztokenstart_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzTokenStart fuzztokenstart - -cp corpus.zip $OUT/fuzzgetstring_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzGetString fuzzgetstring - -cp corpus.zip $OUT/fuzzgetfloat_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzGetFloat fuzzgetfloat - -cp corpus.zip $OUT/fuzzgetint_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzGetInt fuzzgetint - -cp corpus.zip $OUT/fuzzgetboolean_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzGetBoolean fuzzgetboolean - -cp corpus.zip $OUT/fuzzgetunsafestring_seed_corpus.zip -compile_go_fuzzer github.com/buger/jsonparser FuzzGetUnsafeString fuzzgetunsafestring - diff --git a/vendor/github.com/buger/jsonparser/parser.go b/vendor/github.com/buger/jsonparser/parser.go deleted file mode 100644 index 14b80bc4838..00000000000 --- a/vendor/github.com/buger/jsonparser/parser.go +++ /dev/null @@ -1,1283 +0,0 @@ -package jsonparser - -import ( - "bytes" - "errors" - "fmt" - "strconv" -) - -// Errors -var ( - KeyPathNotFoundError = errors.New("Key path not found") - UnknownValueTypeError = errors.New("Unknown value type") - MalformedJsonError = errors.New("Malformed JSON error") - MalformedStringError = errors.New("Value is string, but can't find closing '\"' symbol") - MalformedArrayError = errors.New("Value is array, but can't find closing ']' symbol") - MalformedObjectError = errors.New("Value looks like object, but can't find closing '}' symbol") - MalformedValueError = errors.New("Value looks like Number/Boolean/None, but can't find its end: ',' or '}' symbol") - OverflowIntegerError = errors.New("Value is number, but overflowed while parsing") - MalformedStringEscapeError = errors.New("Encountered an invalid escape sequence in a string") -) - -// How much stack space to allocate for unescaping JSON strings; if a string longer -// than this needs to be escaped, it will result in a heap allocation -const unescapeStackBufSize = 64 - -func tokenEnd(data []byte) int { - for i, c := range data { - switch c { - case ' ', '\n', '\r', '\t', ',', '}', ']': - return i - } - } - - return len(data) -} - -func findTokenStart(data []byte, token byte) int { - for i := len(data) - 1; i >= 0; i-- { - switch data[i] { - case token: - return i - case '[', '{': - return 0 - } - } - - return 0 -} - -func findKeyStart(data []byte, key string) (int, error) { - i := 0 - ln := len(data) - if ln > 0 && (data[0] == '{' || data[0] == '[') { - i = 1 - } - var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings - - if ku, err := Unescape(StringToBytes(key), stackbuf[:]); err == nil { - key = bytesToString(&ku) - } - - for i < ln { - switch data[i] { - case '"': - i++ - keyBegin := i - - strEnd, keyEscaped := stringEnd(data[i:]) - if strEnd == -1 { - break - } - i += strEnd - keyEnd := i - 1 - - valueOffset := nextToken(data[i:]) - if valueOffset == -1 { - break - } - - i += valueOffset - - // if string is a key, and key level match - k := data[keyBegin:keyEnd] - // for unescape: if there are no escape sequences, this is cheap; if there are, it is a - // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize - if keyEscaped { - if ku, err := Unescape(k, stackbuf[:]); err != nil { - break - } else { - k = ku - } - } - - if data[i] == ':' && len(key) == len(k) && bytesToString(&k) == key { - return keyBegin - 1, nil - } - - case '[': - end := blockEnd(data[i:], data[i], ']') - if end != -1 { - i = i + end - } - case '{': - end := blockEnd(data[i:], data[i], '}') - if end != -1 { - i = i + end - } - } - i++ - } - - return -1, KeyPathNotFoundError -} - -func tokenStart(data []byte) int { - for i := len(data) - 1; i >= 0; i-- { - switch data[i] { - case '\n', '\r', '\t', ',', '{', '[': - return i - } - } - - return 0 -} - -// Find position of next character which is not whitespace -func nextToken(data []byte) int { - for i, c := range data { - switch c { - case ' ', '\n', '\r', '\t': - continue - default: - return i - } - } - - return -1 -} - -// Find position of last character which is not whitespace -func lastToken(data []byte) int { - for i := len(data) - 1; i >= 0; i-- { - switch data[i] { - case ' ', '\n', '\r', '\t': - continue - default: - return i - } - } - - return -1 -} - -// Tries to find the end of string -// Support if string contains escaped quote symbols. -func stringEnd(data []byte) (int, bool) { - escaped := false - for i, c := range data { - if c == '"' { - if !escaped { - return i + 1, false - } else { - j := i - 1 - for { - if j < 0 || data[j] != '\\' { - return i + 1, true // even number of backslashes - } - j-- - if j < 0 || data[j] != '\\' { - break // odd number of backslashes - } - j-- - - } - } - } else if c == '\\' { - escaped = true - } - } - - return -1, escaped -} - -// Find end of the data structure, array or object. -// For array openSym and closeSym will be '[' and ']', for object '{' and '}' -func blockEnd(data []byte, openSym byte, closeSym byte) int { - level := 0 - i := 0 - ln := len(data) - - for i < ln { - switch data[i] { - case '"': // If inside string, skip it - se, _ := stringEnd(data[i+1:]) - if se == -1 { - return -1 - } - i += se - case openSym: // If open symbol, increase level - level++ - case closeSym: // If close symbol, increase level - level-- - - // If we have returned to the original level, we're done - if level == 0 { - return i + 1 - } - } - i++ - } - - return -1 -} - -func searchKeys(data []byte, keys ...string) int { - keyLevel := 0 - level := 0 - i := 0 - ln := len(data) - lk := len(keys) - lastMatched := true - - if lk == 0 { - return 0 - } - - var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings - - for i < ln { - switch data[i] { - case '"': - i++ - keyBegin := i - - strEnd, keyEscaped := stringEnd(data[i:]) - if strEnd == -1 { - return -1 - } - i += strEnd - keyEnd := i - 1 - - valueOffset := nextToken(data[i:]) - if valueOffset == -1 { - return -1 - } - - i += valueOffset - - // if string is a key - if data[i] == ':' { - if level < 1 { - return -1 - } - - key := data[keyBegin:keyEnd] - - // for unescape: if there are no escape sequences, this is cheap; if there are, it is a - // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize - var keyUnesc []byte - if !keyEscaped { - keyUnesc = key - } else if ku, err := Unescape(key, stackbuf[:]); err != nil { - return -1 - } else { - keyUnesc = ku - } - - if level <= len(keys) { - if equalStr(&keyUnesc, keys[level-1]) { - lastMatched = true - - // if key level match - if keyLevel == level-1 { - keyLevel++ - // If we found all keys in path - if keyLevel == lk { - return i + 1 - } - } - } else { - lastMatched = false - } - } else { - return -1 - } - } else { - i-- - } - case '{': - - // in case parent key is matched then only we will increase the level otherwise can directly - // can move to the end of this block - if !lastMatched { - end := blockEnd(data[i:], '{', '}') - if end == -1 { - return -1 - } - i += end - 1 - } else { - level++ - } - case '}': - level-- - if level == keyLevel { - keyLevel-- - } - case '[': - // If we want to get array element by index - if keyLevel == level && keys[level][0] == '[' { - var keyLen = len(keys[level]) - if keyLen < 3 || keys[level][0] != '[' || keys[level][keyLen-1] != ']' { - return -1 - } - aIdx, err := strconv.Atoi(keys[level][1 : keyLen-1]) - if err != nil { - return -1 - } - var curIdx int - var valueFound []byte - var valueOffset int - var curI = i - ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) { - if curIdx == aIdx { - valueFound = value - valueOffset = offset - if dataType == String { - valueOffset = valueOffset - 2 - valueFound = data[curI+valueOffset : curI+valueOffset+len(value)+2] - } - } - curIdx += 1 - }) - - if valueFound == nil { - return -1 - } else { - subIndex := searchKeys(valueFound, keys[level+1:]...) - if subIndex < 0 { - return -1 - } - return i + valueOffset + subIndex - } - } else { - // Do not search for keys inside arrays - if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 { - return -1 - } else { - i += arraySkip - 1 - } - } - case ':': // If encountered, JSON data is malformed - return -1 - } - - i++ - } - - return -1 -} - -func sameTree(p1, p2 []string) bool { - minLen := len(p1) - if len(p2) < minLen { - minLen = len(p2) - } - - for pi_1, p_1 := range p1[:minLen] { - if p2[pi_1] != p_1 { - return false - } - } - - return true -} - -func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]string) int { - var x struct{} - pathFlags := make([]bool, len(paths)) - var level, pathsMatched, i int - ln := len(data) - - var maxPath int - for _, p := range paths { - if len(p) > maxPath { - maxPath = len(p) - } - } - - pathsBuf := make([]string, maxPath) - - for i < ln { - switch data[i] { - case '"': - i++ - keyBegin := i - - strEnd, keyEscaped := stringEnd(data[i:]) - if strEnd == -1 { - return -1 - } - i += strEnd - - keyEnd := i - 1 - - valueOffset := nextToken(data[i:]) - if valueOffset == -1 { - return -1 - } - - i += valueOffset - - // if string is a key, and key level match - if data[i] == ':' { - match := -1 - key := data[keyBegin:keyEnd] - - // for unescape: if there are no escape sequences, this is cheap; if there are, it is a - // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize - var keyUnesc []byte - if !keyEscaped { - keyUnesc = key - } else { - var stackbuf [unescapeStackBufSize]byte - if ku, err := Unescape(key, stackbuf[:]); err != nil { - return -1 - } else { - keyUnesc = ku - } - } - - if maxPath >= level { - if level < 1 { - cb(-1, nil, Unknown, MalformedJsonError) - return -1 - } - - pathsBuf[level-1] = bytesToString(&keyUnesc) - for pi, p := range paths { - if len(p) != level || pathFlags[pi] || !equalStr(&keyUnesc, p[level-1]) || !sameTree(p, pathsBuf[:level]) { - continue - } - - match = pi - - pathsMatched++ - pathFlags[pi] = true - - v, dt, _, e := Get(data[i+1:]) - cb(pi, v, dt, e) - - if pathsMatched == len(paths) { - break - } - } - if pathsMatched == len(paths) { - return i - } - } - - if match == -1 { - tokenOffset := nextToken(data[i+1:]) - i += tokenOffset - - if data[i] == '{' { - blockSkip := blockEnd(data[i:], '{', '}') - i += blockSkip + 1 - } - } - - if i < ln { - switch data[i] { - case '{', '}', '[', '"': - i-- - } - } - } else { - i-- - } - case '{': - level++ - case '}': - level-- - case '[': - var ok bool - arrIdxFlags := make(map[int]struct{}) - pIdxFlags := make([]bool, len(paths)) - - if level < 0 { - cb(-1, nil, Unknown, MalformedJsonError) - return -1 - } - - for pi, p := range paths { - if len(p) < level+1 || pathFlags[pi] || p[level][0] != '[' || !sameTree(p, pathsBuf[:level]) { - continue - } - if len(p[level]) >= 2 { - aIdx, _ := strconv.Atoi(p[level][1 : len(p[level])-1]) - arrIdxFlags[aIdx] = x - pIdxFlags[pi] = true - } - } - - if len(arrIdxFlags) > 0 { - level++ - - var curIdx int - arrOff, _ := ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) { - if _, ok = arrIdxFlags[curIdx]; ok { - for pi, p := range paths { - if pIdxFlags[pi] { - aIdx, _ := strconv.Atoi(p[level-1][1 : len(p[level-1])-1]) - - if curIdx == aIdx { - of := searchKeys(value, p[level:]...) - - pathsMatched++ - pathFlags[pi] = true - - if of != -1 { - v, dt, _, e := Get(value[of:]) - cb(pi, v, dt, e) - } - } - } - } - } - - curIdx += 1 - }) - - if pathsMatched == len(paths) { - return i - } - - i += arrOff - 1 - } else { - // Do not search for keys inside arrays - if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 { - return -1 - } else { - i += arraySkip - 1 - } - } - case ']': - level-- - } - - i++ - } - - return -1 -} - -// Data types available in valid JSON data. -type ValueType int - -const ( - NotExist = ValueType(iota) - String - Number - Object - Array - Boolean - Null - Unknown -) - -func (vt ValueType) String() string { - switch vt { - case NotExist: - return "non-existent" - case String: - return "string" - case Number: - return "number" - case Object: - return "object" - case Array: - return "array" - case Boolean: - return "boolean" - case Null: - return "null" - default: - return "unknown" - } -} - -var ( - trueLiteral = []byte("true") - falseLiteral = []byte("false") - nullLiteral = []byte("null") -) - -func createInsertComponent(keys []string, setValue []byte, comma, object bool) []byte { - isIndex := string(keys[0][0]) == "[" - offset := 0 - lk := calcAllocateSpace(keys, setValue, comma, object) - buffer := make([]byte, lk, lk) - if comma { - offset += WriteToBuffer(buffer[offset:], ",") - } - if isIndex && !comma { - offset += WriteToBuffer(buffer[offset:], "[") - } else { - if object { - offset += WriteToBuffer(buffer[offset:], "{") - } - if !isIndex { - offset += WriteToBuffer(buffer[offset:], "\"") - offset += WriteToBuffer(buffer[offset:], keys[0]) - offset += WriteToBuffer(buffer[offset:], "\":") - } - } - - for i := 1; i < len(keys); i++ { - if string(keys[i][0]) == "[" { - offset += WriteToBuffer(buffer[offset:], "[") - } else { - offset += WriteToBuffer(buffer[offset:], "{\"") - offset += WriteToBuffer(buffer[offset:], keys[i]) - offset += WriteToBuffer(buffer[offset:], "\":") - } - } - offset += WriteToBuffer(buffer[offset:], string(setValue)) - for i := len(keys) - 1; i > 0; i-- { - if string(keys[i][0]) == "[" { - offset += WriteToBuffer(buffer[offset:], "]") - } else { - offset += WriteToBuffer(buffer[offset:], "}") - } - } - if isIndex && !comma { - offset += WriteToBuffer(buffer[offset:], "]") - } - if object && !isIndex { - offset += WriteToBuffer(buffer[offset:], "}") - } - return buffer -} - -func calcAllocateSpace(keys []string, setValue []byte, comma, object bool) int { - isIndex := string(keys[0][0]) == "[" - lk := 0 - if comma { - // , - lk += 1 - } - if isIndex && !comma { - // [] - lk += 2 - } else { - if object { - // { - lk += 1 - } - if !isIndex { - // "keys[0]" - lk += len(keys[0]) + 3 - } - } - - - lk += len(setValue) - for i := 1; i < len(keys); i++ { - if string(keys[i][0]) == "[" { - // [] - lk += 2 - } else { - // {"keys[i]":setValue} - lk += len(keys[i]) + 5 - } - } - - if object && !isIndex { - // } - lk += 1 - } - - return lk -} - -func WriteToBuffer(buffer []byte, str string) int { - copy(buffer, str) - return len(str) -} - -/* - -Del - Receives existing data structure, path to delete. - -Returns: -`data` - return modified data - -*/ -func Delete(data []byte, keys ...string) []byte { - lk := len(keys) - if lk == 0 { - return data[:0] - } - - array := false - if len(keys[lk-1]) > 0 && string(keys[lk-1][0]) == "[" { - array = true - } - - var startOffset, keyOffset int - endOffset := len(data) - var err error - if !array { - if len(keys) > 1 { - _, _, startOffset, endOffset, err = internalGet(data, keys[:lk-1]...) - if err == KeyPathNotFoundError { - // problem parsing the data - return data - } - } - - keyOffset, err = findKeyStart(data[startOffset:endOffset], keys[lk-1]) - if err == KeyPathNotFoundError { - // problem parsing the data - return data - } - keyOffset += startOffset - _, _, _, subEndOffset, _ := internalGet(data[startOffset:endOffset], keys[lk-1]) - endOffset = startOffset + subEndOffset - tokEnd := tokenEnd(data[endOffset:]) - tokStart := findTokenStart(data[:keyOffset], ","[0]) - - if data[endOffset+tokEnd] == ","[0] { - endOffset += tokEnd + 1 - } else if data[endOffset+tokEnd] == " "[0] && len(data) > endOffset+tokEnd+1 && data[endOffset+tokEnd+1] == ","[0] { - endOffset += tokEnd + 2 - } else if data[endOffset+tokEnd] == "}"[0] && data[tokStart] == ","[0] { - keyOffset = tokStart - } - } else { - _, _, keyOffset, endOffset, err = internalGet(data, keys...) - if err == KeyPathNotFoundError { - // problem parsing the data - return data - } - - tokEnd := tokenEnd(data[endOffset:]) - tokStart := findTokenStart(data[:keyOffset], ","[0]) - - if data[endOffset+tokEnd] == ","[0] { - endOffset += tokEnd + 1 - } else if data[endOffset+tokEnd] == "]"[0] && data[tokStart] == ","[0] { - keyOffset = tokStart - } - } - - // We need to remove remaining trailing comma if we delete las element in the object - prevTok := lastToken(data[:keyOffset]) - remainedValue := data[endOffset:] - - var newOffset int - if nextToken(remainedValue) > -1 && remainedValue[nextToken(remainedValue)] == '}' && data[prevTok] == ',' { - newOffset = prevTok - } else { - newOffset = prevTok + 1 - } - - // We have to make a copy here if we don't want to mangle the original data, because byte slices are - // accessed by reference and not by value - dataCopy := make([]byte, len(data)) - copy(dataCopy, data) - data = append(dataCopy[:newOffset], dataCopy[endOffset:]...) - - return data -} - -/* - -Set - Receives existing data structure, path to set, and data to set at that key. - -Returns: -`value` - modified byte array -`err` - On any parsing error - -*/ -func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error) { - // ensure keys are set - if len(keys) == 0 { - return nil, KeyPathNotFoundError - } - - _, _, startOffset, endOffset, err := internalGet(data, keys...) - if err != nil { - if err != KeyPathNotFoundError { - // problem parsing the data - return nil, err - } - // full path doesnt exist - // does any subpath exist? - var depth int - for i := range keys { - _, _, start, end, sErr := internalGet(data, keys[:i+1]...) - if sErr != nil { - break - } else { - endOffset = end - startOffset = start - depth++ - } - } - comma := true - object := false - if endOffset == -1 { - firstToken := nextToken(data) - // We can't set a top-level key if data isn't an object - if firstToken < 0 || data[firstToken] != '{' { - return nil, KeyPathNotFoundError - } - // Don't need a comma if the input is an empty object - secondToken := firstToken + 1 + nextToken(data[firstToken+1:]) - if data[secondToken] == '}' { - comma = false - } - // Set the top level key at the end (accounting for any trailing whitespace) - // This assumes last token is valid like '}', could check and return error - endOffset = lastToken(data) - } - depthOffset := endOffset - if depth != 0 { - // if subpath is a non-empty object, add to it - // or if subpath is a non-empty array, add to it - if (data[startOffset] == '{' && data[startOffset+1+nextToken(data[startOffset+1:])] != '}') || - (data[startOffset] == '[' && data[startOffset+1+nextToken(data[startOffset+1:])] == '{') && keys[depth:][0][0] == 91 { - depthOffset-- - startOffset = depthOffset - // otherwise, over-write it with a new object - } else { - comma = false - object = true - } - } else { - startOffset = depthOffset - } - value = append(data[:startOffset], append(createInsertComponent(keys[depth:], setValue, comma, object), data[depthOffset:]...)...) - } else { - // path currently exists - startComponent := data[:startOffset] - endComponent := data[endOffset:] - - value = make([]byte, len(startComponent)+len(endComponent)+len(setValue)) - newEndOffset := startOffset + len(setValue) - copy(value[0:startOffset], startComponent) - copy(value[startOffset:newEndOffset], setValue) - copy(value[newEndOffset:], endComponent) - } - return value, nil -} - -func getType(data []byte, offset int) ([]byte, ValueType, int, error) { - var dataType ValueType - endOffset := offset - - // if string value - if data[offset] == '"' { - dataType = String - if idx, _ := stringEnd(data[offset+1:]); idx != -1 { - endOffset += idx + 1 - } else { - return nil, dataType, offset, MalformedStringError - } - } else if data[offset] == '[' { // if array value - dataType = Array - // break label, for stopping nested loops - endOffset = blockEnd(data[offset:], '[', ']') - - if endOffset == -1 { - return nil, dataType, offset, MalformedArrayError - } - - endOffset += offset - } else if data[offset] == '{' { // if object value - dataType = Object - // break label, for stopping nested loops - endOffset = blockEnd(data[offset:], '{', '}') - - if endOffset == -1 { - return nil, dataType, offset, MalformedObjectError - } - - endOffset += offset - } else { - // Number, Boolean or None - end := tokenEnd(data[endOffset:]) - - if end == -1 { - return nil, dataType, offset, MalformedValueError - } - - value := data[offset : endOffset+end] - - switch data[offset] { - case 't', 'f': // true or false - if bytes.Equal(value, trueLiteral) || bytes.Equal(value, falseLiteral) { - dataType = Boolean - } else { - return nil, Unknown, offset, UnknownValueTypeError - } - case 'u', 'n': // undefined or null - if bytes.Equal(value, nullLiteral) { - dataType = Null - } else { - return nil, Unknown, offset, UnknownValueTypeError - } - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-': - dataType = Number - default: - return nil, Unknown, offset, UnknownValueTypeError - } - - endOffset += end - } - return data[offset:endOffset], dataType, endOffset, nil -} - -/* -Get - Receives data structure, and key path to extract value from. - -Returns: -`value` - Pointer to original data structure containing key value, or just empty slice if nothing found or error -`dataType` - Can be: `NotExist`, `String`, `Number`, `Object`, `Array`, `Boolean` or `Null` -`offset` - Offset from provided data structure where key value ends. Used mostly internally, for example for `ArrayEach` helper. -`err` - If key not found or any other parsing issue it should return error. If key not found it also sets `dataType` to `NotExist` - -Accept multiple keys to specify path to JSON value (in case of quering nested structures). -If no keys provided it will try to extract closest JSON value (simple ones or object/array), useful for reading streams or arrays, see `ArrayEach` implementation. -*/ -func Get(data []byte, keys ...string) (value []byte, dataType ValueType, offset int, err error) { - a, b, _, d, e := internalGet(data, keys...) - return a, b, d, e -} - -func internalGet(data []byte, keys ...string) (value []byte, dataType ValueType, offset, endOffset int, err error) { - if len(keys) > 0 { - if offset = searchKeys(data, keys...); offset == -1 { - return nil, NotExist, -1, -1, KeyPathNotFoundError - } - } - - // Go to closest value - nO := nextToken(data[offset:]) - if nO == -1 { - return nil, NotExist, offset, -1, MalformedJsonError - } - - offset += nO - value, dataType, endOffset, err = getType(data, offset) - if err != nil { - return value, dataType, offset, endOffset, err - } - - // Strip quotes from string values - if dataType == String { - value = value[1 : len(value)-1] - } - - return value[:len(value):len(value)], dataType, offset, endOffset, nil -} - -// ArrayEach is used when iterating arrays, accepts a callback function with the same return arguments as `Get`. -func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error), keys ...string) (offset int, err error) { - if len(data) == 0 { - return -1, MalformedObjectError - } - - nT := nextToken(data) - if nT == -1 { - return -1, MalformedJsonError - } - - offset = nT + 1 - - if len(keys) > 0 { - if offset = searchKeys(data, keys...); offset == -1 { - return offset, KeyPathNotFoundError - } - - // Go to closest value - nO := nextToken(data[offset:]) - if nO == -1 { - return offset, MalformedJsonError - } - - offset += nO - - if data[offset] != '[' { - return offset, MalformedArrayError - } - - offset++ - } - - nO := nextToken(data[offset:]) - if nO == -1 { - return offset, MalformedJsonError - } - - offset += nO - - if data[offset] == ']' { - return offset, nil - } - - for true { - v, t, o, e := Get(data[offset:]) - - if e != nil { - return offset, e - } - - if o == 0 { - break - } - - if t != NotExist { - cb(v, t, offset+o-len(v), e) - } - - if e != nil { - break - } - - offset += o - - skipToToken := nextToken(data[offset:]) - if skipToToken == -1 { - return offset, MalformedArrayError - } - offset += skipToToken - - if data[offset] == ']' { - break - } - - if data[offset] != ',' { - return offset, MalformedArrayError - } - - offset++ - } - - return offset, nil -} - -// ObjectEach iterates over the key-value pairs of a JSON object, invoking a given callback for each such entry -func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error) { - offset := 0 - - // Descend to the desired key, if requested - if len(keys) > 0 { - if off := searchKeys(data, keys...); off == -1 { - return KeyPathNotFoundError - } else { - offset = off - } - } - - // Validate and skip past opening brace - if off := nextToken(data[offset:]); off == -1 { - return MalformedObjectError - } else if offset += off; data[offset] != '{' { - return MalformedObjectError - } else { - offset++ - } - - // Skip to the first token inside the object, or stop if we find the ending brace - if off := nextToken(data[offset:]); off == -1 { - return MalformedJsonError - } else if offset += off; data[offset] == '}' { - return nil - } - - // Loop pre-condition: data[offset] points to what should be either the next entry's key, or the closing brace (if it's anything else, the JSON is malformed) - for offset < len(data) { - // Step 1: find the next key - var key []byte - - // Check what the the next token is: start of string, end of object, or something else (error) - switch data[offset] { - case '"': - offset++ // accept as string and skip opening quote - case '}': - return nil // we found the end of the object; stop and return success - default: - return MalformedObjectError - } - - // Find the end of the key string - var keyEscaped bool - if off, esc := stringEnd(data[offset:]); off == -1 { - return MalformedJsonError - } else { - key, keyEscaped = data[offset:offset+off-1], esc - offset += off - } - - // Unescape the string if needed - if keyEscaped { - var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings - if keyUnescaped, err := Unescape(key, stackbuf[:]); err != nil { - return MalformedStringEscapeError - } else { - key = keyUnescaped - } - } - - // Step 2: skip the colon - if off := nextToken(data[offset:]); off == -1 { - return MalformedJsonError - } else if offset += off; data[offset] != ':' { - return MalformedJsonError - } else { - offset++ - } - - // Step 3: find the associated value, then invoke the callback - if value, valueType, off, err := Get(data[offset:]); err != nil { - return err - } else if err := callback(key, value, valueType, offset+off); err != nil { // Invoke the callback here! - return err - } else { - offset += off - } - - // Step 4: skip over the next comma to the following token, or stop if we hit the ending brace - if off := nextToken(data[offset:]); off == -1 { - return MalformedArrayError - } else { - offset += off - switch data[offset] { - case '}': - return nil // Stop if we hit the close brace - case ',': - offset++ // Ignore the comma - default: - return MalformedObjectError - } - } - - // Skip to the next token after the comma - if off := nextToken(data[offset:]); off == -1 { - return MalformedArrayError - } else { - offset += off - } - } - - return MalformedObjectError // we shouldn't get here; it's expected that we will return via finding the ending brace -} - -// GetUnsafeString returns the value retrieved by `Get`, use creates string without memory allocation by mapping string to slice memory. It does not handle escape symbols. -func GetUnsafeString(data []byte, keys ...string) (val string, err error) { - v, _, _, e := Get(data, keys...) - - if e != nil { - return "", e - } - - return bytesToString(&v), nil -} - -// GetString returns the value retrieved by `Get`, cast to a string if possible, trying to properly handle escape and utf8 symbols -// If key data type do not match, it will return an error. -func GetString(data []byte, keys ...string) (val string, err error) { - v, t, _, e := Get(data, keys...) - - if e != nil { - return "", e - } - - if t != String { - return "", fmt.Errorf("Value is not a string: %s", string(v)) - } - - // If no escapes return raw content - if bytes.IndexByte(v, '\\') == -1 { - return string(v), nil - } - - return ParseString(v) -} - -// GetFloat returns the value retrieved by `Get`, cast to a float64 if possible. -// The offset is the same as in `Get`. -// If key data type do not match, it will return an error. -func GetFloat(data []byte, keys ...string) (val float64, err error) { - v, t, _, e := Get(data, keys...) - - if e != nil { - return 0, e - } - - if t != Number { - return 0, fmt.Errorf("Value is not a number: %s", string(v)) - } - - return ParseFloat(v) -} - -// GetInt returns the value retrieved by `Get`, cast to a int64 if possible. -// If key data type do not match, it will return an error. -func GetInt(data []byte, keys ...string) (val int64, err error) { - v, t, _, e := Get(data, keys...) - - if e != nil { - return 0, e - } - - if t != Number { - return 0, fmt.Errorf("Value is not a number: %s", string(v)) - } - - return ParseInt(v) -} - -// GetBoolean returns the value retrieved by `Get`, cast to a bool if possible. -// The offset is the same as in `Get`. -// If key data type do not match, it will return error. -func GetBoolean(data []byte, keys ...string) (val bool, err error) { - v, t, _, e := Get(data, keys...) - - if e != nil { - return false, e - } - - if t != Boolean { - return false, fmt.Errorf("Value is not a boolean: %s", string(v)) - } - - return ParseBoolean(v) -} - -// ParseBoolean parses a Boolean ValueType into a Go bool (not particularly useful, but here for completeness) -func ParseBoolean(b []byte) (bool, error) { - switch { - case bytes.Equal(b, trueLiteral): - return true, nil - case bytes.Equal(b, falseLiteral): - return false, nil - default: - return false, MalformedValueError - } -} - -// ParseString parses a String ValueType into a Go string (the main parsing work is unescaping the JSON string) -func ParseString(b []byte) (string, error) { - var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings - if bU, err := Unescape(b, stackbuf[:]); err != nil { - return "", MalformedValueError - } else { - return string(bU), nil - } -} - -// ParseNumber parses a Number ValueType into a Go float64 -func ParseFloat(b []byte) (float64, error) { - if v, err := parseFloat(&b); err != nil { - return 0, MalformedValueError - } else { - return v, nil - } -} - -// ParseInt parses a Number ValueType into a Go int64 -func ParseInt(b []byte) (int64, error) { - if v, ok, overflow := parseInt(b); !ok { - if overflow { - return 0, OverflowIntegerError - } - return 0, MalformedValueError - } else { - return v, nil - } -} diff --git a/vendor/github.com/clipperhouse/uax29/v2/LICENSE b/vendor/github.com/clipperhouse/uax29/v2/LICENSE deleted file mode 100644 index 6ae86a9a190..00000000000 --- a/vendor/github.com/clipperhouse/uax29/v2/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Matt Sherman - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md b/vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md deleted file mode 100644 index 4d9a6d717b8..00000000000 --- a/vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md +++ /dev/null @@ -1,82 +0,0 @@ -An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode version 15.0.0. - -## Quick start - -``` -go get "github.com/clipperhouse/uax29/v2/graphemes" -``` - -```go -import "github.com/clipperhouse/uax29/v2/graphemes" - -text := "Hello, 世界. Nice dog! 👍🐶" - -tokens := graphemes.FromString(text) - -for tokens.Next() { // Next() returns true until end of data - fmt.Println(tokens.Value()) // Do something with the current grapheme -} -``` - -[![Documentation](https://pkg.go.dev/badge/github.com/clipperhouse/uax29/v2/graphemes.svg)](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes) - -_A grapheme is a “single visible character”, which might be a simple as a single letter, or a complex emoji that consists of several Unicode code points._ - -## Conformance - -We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29). Status: - -![Go](https://github.com/clipperhouse/uax29/actions/workflows/gotest.yml/badge.svg) - -## APIs - -### If you have a `string` - -```go -text := "Hello, 世界. Nice dog! 👍🐶" - -tokens := graphemes.FromString(text) - -for tokens.Next() { // Next() returns true until end of data - fmt.Println(tokens.Value()) // Do something with the current grapheme -} -``` - -### If you have an `io.Reader` - -`FromReader` embeds a [`bufio.Scanner`](https://pkg.go.dev/bufio#Scanner), so just use those methods. - -```go -r := getYourReader() // from a file or network maybe -tokens := graphemes.FromReader(r) - -for tokens.Scan() { // Scan() returns true until error or EOF - fmt.Println(tokens.Text()) // Do something with the current grapheme -} - -if tokens.Err() != nil { // Check the error - log.Fatal(tokens.Err()) -} -``` - -### If you have a `[]byte` - -```go -b := []byte("Hello, 世界. Nice dog! 👍🐶") - -tokens := graphemes.FromBytes(b) - -for tokens.Next() { // Next() returns true until end of data - fmt.Println(tokens.Value()) // Do something with the current grapheme -} -``` - -### Performance - -On a Mac M2 laptop, we see around 200MB/s, or around 100 million graphemes per second. You should see ~constant memory, and no allocations. - -### Invalid inputs - -Invalid UTF-8 input is considered undefined behavior. We test to ensure that bad inputs will not cause pathological outcomes, such as a panic or infinite loop. Callers should expect “garbage-in, garbage-out”. - -Your pipeline should probably include a call to [`utf8.Valid()`](https://pkg.go.dev/unicode/utf8#Valid). diff --git a/vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go b/vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go deleted file mode 100644 index 14b4ea2c461..00000000000 --- a/vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go +++ /dev/null @@ -1,28 +0,0 @@ -package graphemes - -import "github.com/clipperhouse/uax29/v2/internal/iterators" - -type Iterator[T iterators.Stringish] struct { - *iterators.Iterator[T] -} - -var ( - splitFuncString = splitFunc[string] - splitFuncBytes = splitFunc[[]byte] -) - -// FromString returns an iterator for the grapheme clusters in the input string. -// Iterate while Next() is true, and access the grapheme via Value(). -func FromString(s string) Iterator[string] { - return Iterator[string]{ - iterators.New(splitFuncString, s), - } -} - -// FromBytes returns an iterator for the grapheme clusters in the input bytes. -// Iterate while Next() is true, and access the grapheme via Value(). -func FromBytes(b []byte) Iterator[[]byte] { - return Iterator[[]byte]{ - iterators.New(splitFuncBytes, b), - } -} diff --git a/vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go b/vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go deleted file mode 100644 index 9aa0066183f..00000000000 --- a/vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go +++ /dev/null @@ -1,25 +0,0 @@ -// Package graphemes implements Unicode grapheme cluster boundaries: https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries -package graphemes - -import ( - "bufio" - "io" -) - -type Scanner struct { - *bufio.Scanner -} - -// FromReader returns a Scanner, to split graphemes per -// https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries. -// -// It embeds a [bufio.Scanner], so you can use its methods. -// -// Iterate through graphemes by calling Scan() until false, then check Err(). -func FromReader(r io.Reader) *Scanner { - sc := bufio.NewScanner(r) - sc.Split(SplitFunc) - return &Scanner{ - Scanner: sc, - } -} diff --git a/vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go b/vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go deleted file mode 100644 index 08987f549fc..00000000000 --- a/vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go +++ /dev/null @@ -1,174 +0,0 @@ -package graphemes - -import ( - "bufio" - - "github.com/clipperhouse/uax29/v2/internal/iterators" -) - -// is determines if lookup intersects propert(ies) -func (lookup property) is(properties property) bool { - return (lookup & properties) != 0 -} - -const _Ignore = _Extend - -// SplitFunc is a bufio.SplitFunc implementation of Unicode grapheme cluster segmentation, for use with bufio.Scanner. -// -// See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries. -var SplitFunc bufio.SplitFunc = splitFunc[[]byte] - -func splitFunc[T iterators.Stringish](data T, atEOF bool) (advance int, token T, err error) { - var empty T - if len(data) == 0 { - return 0, empty, nil - } - - // These vars are stateful across loop iterations - var pos int - var lastExIgnore property = 0 // "last excluding ignored categories" - var lastLastExIgnore property = 0 // "last one before that" - var regionalIndicatorCount int - - // Rules are usually of the form Cat1 × Cat2; "current" refers to the first property - // to the right of the ×, from which we look back or forward - - current, w := lookup(data[pos:]) - if w == 0 { - if !atEOF { - // Rune extends past current data, request more - return 0, empty, nil - } - pos = len(data) - return pos, data[:pos], nil - } - - // https://unicode.org/reports/tr29/#GB1 - // Start of text always advances - pos += w - - for { - eot := pos == len(data) // "end of text" - - if eot { - if !atEOF { - // Token extends past current data, request more - return 0, empty, nil - } - - // https://unicode.org/reports/tr29/#GB2 - break - } - - /* - We've switched the evaluation order of GB1↓ and GB2↑. It's ok: - because we've checked for len(data) at the top of this function, - sot and eot are mutually exclusive, order doesn't matter. - */ - - // Rules are usually of the form Cat1 × Cat2; "current" refers to the first property - // to the right of the ×, from which we look back or forward - - // Remember previous properties to avoid lookups/lookbacks - last := current - if !last.is(_Ignore) { - lastLastExIgnore = lastExIgnore - lastExIgnore = last - } - - current, w = lookup(data[pos:]) - if w == 0 { - if atEOF { - // Just return the bytes, we can't do anything with them - pos = len(data) - break - } - // Rune extends past current data, request more - return 0, empty, nil - } - - // Optimization: no rule can possibly apply - if current|last == 0 { // i.e. both are zero - break - } - - // https://unicode.org/reports/tr29/#GB3 - if current.is(_LF) && last.is(_CR) { - pos += w - continue - } - - // https://unicode.org/reports/tr29/#GB4 - // https://unicode.org/reports/tr29/#GB5 - if (current | last).is(_Control | _CR | _LF) { - break - } - - // https://unicode.org/reports/tr29/#GB6 - if current.is(_L|_V|_LV|_LVT) && last.is(_L) { - pos += w - continue - } - - // https://unicode.org/reports/tr29/#GB7 - if current.is(_V|_T) && last.is(_LV|_V) { - pos += w - continue - } - - // https://unicode.org/reports/tr29/#GB8 - if current.is(_T) && last.is(_LVT|_T) { - pos += w - continue - } - - // https://unicode.org/reports/tr29/#GB9 - if current.is(_Extend | _ZWJ) { - pos += w - continue - } - - // https://unicode.org/reports/tr29/#GB9a - if current.is(_SpacingMark) { - pos += w - continue - } - - // https://unicode.org/reports/tr29/#GB9b - if last.is(_Prepend) { - pos += w - continue - } - - // https://unicode.org/reports/tr29/#GB9c - // TODO(clipperhouse): - // It appears to be added in Unicode 15.1.0: - // https://unicode.org/versions/Unicode15.1.0/#Migration - // This package currently supports Unicode 15.0.0, so - // out of scope for now - - // https://unicode.org/reports/tr29/#GB11 - if current.is(_ExtendedPictographic) && last.is(_ZWJ) && lastLastExIgnore.is(_ExtendedPictographic) { - pos += w - continue - } - - // https://unicode.org/reports/tr29/#GB12 - // https://unicode.org/reports/tr29/#GB13 - if (current & last).is(_RegionalIndicator) { - regionalIndicatorCount++ - - odd := regionalIndicatorCount%2 == 1 - if odd { - pos += w - continue - } - } - - // If we fall through all the above rules, it's a grapheme cluster break - break - } - - // Return token - return pos, data[:pos], nil -} diff --git a/vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go b/vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go deleted file mode 100644 index c8c6c33bcd0..00000000000 --- a/vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go +++ /dev/null @@ -1,1409 +0,0 @@ -package graphemes - -// generated by github.com/clipperhouse/uax29/v2 -// from https://www.unicode.org/Public/15.0.0/ucd/auxiliary/GraphemeBreakProperty.txt - -import "github.com/clipperhouse/uax29/v2/internal/iterators" - -type property uint16 - -const ( - _CR property = 1 << iota - _Control - _Extend - _ExtendedPictographic - _L - _LF - _LV - _LVT - _Prepend - _RegionalIndicator - _SpacingMark - _T - _V - _ZWJ -) - -// lookup returns the trie value for the first UTF-8 encoding in s and -// the width in bytes of this encoding. The size will be 0 if s does not -// hold enough bytes to complete the encoding. len(s) must be greater than 0. -func lookup[T iterators.Stringish](s T) (v property, sz int) { - c0 := s[0] - switch { - case c0 < 0x80: // is ASCII - return graphemesValues[c0], 1 - case c0 < 0xC2: - return 0, 1 // Illegal UTF-8: not a starter, not ASCII. - case c0 < 0xE0: // 2-byte UTF-8 - if len(s) < 2 { - return 0, 0 - } - i := graphemesIndex[c0] - c1 := s[1] - if c1 < 0x80 || 0xC0 <= c1 { - return 0, 1 // Illegal UTF-8: not a continuation byte. - } - return lookupValue(uint32(i), c1), 2 - case c0 < 0xF0: // 3-byte UTF-8 - if len(s) < 3 { - return 0, 0 - } - i := graphemesIndex[c0] - c1 := s[1] - if c1 < 0x80 || 0xC0 <= c1 { - return 0, 1 // Illegal UTF-8: not a continuation byte. - } - o := uint32(i)<<6 + uint32(c1) - i = graphemesIndex[o] - c2 := s[2] - if c2 < 0x80 || 0xC0 <= c2 { - return 0, 2 // Illegal UTF-8: not a continuation byte. - } - return lookupValue(uint32(i), c2), 3 - case c0 < 0xF8: // 4-byte UTF-8 - if len(s) < 4 { - return 0, 0 - } - i := graphemesIndex[c0] - c1 := s[1] - if c1 < 0x80 || 0xC0 <= c1 { - return 0, 1 // Illegal UTF-8: not a continuation byte. - } - o := uint32(i)<<6 + uint32(c1) - i = graphemesIndex[o] - c2 := s[2] - if c2 < 0x80 || 0xC0 <= c2 { - return 0, 2 // Illegal UTF-8: not a continuation byte. - } - o = uint32(i)<<6 + uint32(c2) - i = graphemesIndex[o] - c3 := s[3] - if c3 < 0x80 || 0xC0 <= c3 { - return 0, 3 // Illegal UTF-8: not a continuation byte. - } - return lookupValue(uint32(i), c3), 4 - } - // Illegal rune - return 0, 1 -} - -// graphemesTrie. Total size: 29120 bytes (28.44 KiB). Checksum: 80ad0c5ab9375f7. -// type graphemesTrie struct { } - -// func newGraphemesTrie(i int) *graphemesTrie { -// return &graphemesTrie{} -// } - -// lookupValue determines the type of block n and looks up the value for b. -func lookupValue(n uint32, b byte) property { - switch { - default: - return property(graphemesValues[n<<6+uint32(b)]) - } -} - -// graphemesValues: 215 blocks, 13760 entries, 27520 bytes -// The third block is the zero block. -var graphemesValues = [13760]property{ - // Block 0x0, offset 0x0 - 0x00: 0x0002, 0x01: 0x0002, 0x02: 0x0002, 0x03: 0x0002, 0x04: 0x0002, 0x05: 0x0002, - 0x06: 0x0002, 0x07: 0x0002, 0x08: 0x0002, 0x09: 0x0002, 0x0a: 0x0020, 0x0b: 0x0002, - 0x0c: 0x0002, 0x0d: 0x0001, 0x0e: 0x0002, 0x0f: 0x0002, 0x10: 0x0002, 0x11: 0x0002, - 0x12: 0x0002, 0x13: 0x0002, 0x14: 0x0002, 0x15: 0x0002, 0x16: 0x0002, 0x17: 0x0002, - 0x18: 0x0002, 0x19: 0x0002, 0x1a: 0x0002, 0x1b: 0x0002, 0x1c: 0x0002, 0x1d: 0x0002, - 0x1e: 0x0002, 0x1f: 0x0002, - // Block 0x1, offset 0x40 - 0x7f: 0x0002, - // Block 0x2, offset 0x80 - // Block 0x3, offset 0xc0 - 0xc0: 0x0002, 0xc1: 0x0002, 0xc2: 0x0002, 0xc3: 0x0002, 0xc4: 0x0002, 0xc5: 0x0002, - 0xc6: 0x0002, 0xc7: 0x0002, 0xc8: 0x0002, 0xc9: 0x0002, 0xca: 0x0002, 0xcb: 0x0002, - 0xcc: 0x0002, 0xcd: 0x0002, 0xce: 0x0002, 0xcf: 0x0002, 0xd0: 0x0002, 0xd1: 0x0002, - 0xd2: 0x0002, 0xd3: 0x0002, 0xd4: 0x0002, 0xd5: 0x0002, 0xd6: 0x0002, 0xd7: 0x0002, - 0xd8: 0x0002, 0xd9: 0x0002, 0xda: 0x0002, 0xdb: 0x0002, 0xdc: 0x0002, 0xdd: 0x0002, - 0xde: 0x0002, 0xdf: 0x0002, - 0xe9: 0x0008, - 0xed: 0x0002, 0xee: 0x0008, - // Block 0x4, offset 0x100 - 0x100: 0x0004, 0x101: 0x0004, 0x102: 0x0004, 0x103: 0x0004, 0x104: 0x0004, 0x105: 0x0004, - 0x106: 0x0004, 0x107: 0x0004, 0x108: 0x0004, 0x109: 0x0004, 0x10a: 0x0004, 0x10b: 0x0004, - 0x10c: 0x0004, 0x10d: 0x0004, 0x10e: 0x0004, 0x10f: 0x0004, 0x110: 0x0004, 0x111: 0x0004, - 0x112: 0x0004, 0x113: 0x0004, 0x114: 0x0004, 0x115: 0x0004, 0x116: 0x0004, 0x117: 0x0004, - 0x118: 0x0004, 0x119: 0x0004, 0x11a: 0x0004, 0x11b: 0x0004, 0x11c: 0x0004, 0x11d: 0x0004, - 0x11e: 0x0004, 0x11f: 0x0004, 0x120: 0x0004, 0x121: 0x0004, 0x122: 0x0004, 0x123: 0x0004, - 0x124: 0x0004, 0x125: 0x0004, 0x126: 0x0004, 0x127: 0x0004, 0x128: 0x0004, 0x129: 0x0004, - 0x12a: 0x0004, 0x12b: 0x0004, 0x12c: 0x0004, 0x12d: 0x0004, 0x12e: 0x0004, 0x12f: 0x0004, - 0x130: 0x0004, 0x131: 0x0004, 0x132: 0x0004, 0x133: 0x0004, 0x134: 0x0004, 0x135: 0x0004, - 0x136: 0x0004, 0x137: 0x0004, 0x138: 0x0004, 0x139: 0x0004, 0x13a: 0x0004, 0x13b: 0x0004, - 0x13c: 0x0004, 0x13d: 0x0004, 0x13e: 0x0004, 0x13f: 0x0004, - // Block 0x5, offset 0x140 - 0x140: 0x0004, 0x141: 0x0004, 0x142: 0x0004, 0x143: 0x0004, 0x144: 0x0004, 0x145: 0x0004, - 0x146: 0x0004, 0x147: 0x0004, 0x148: 0x0004, 0x149: 0x0004, 0x14a: 0x0004, 0x14b: 0x0004, - 0x14c: 0x0004, 0x14d: 0x0004, 0x14e: 0x0004, 0x14f: 0x0004, 0x150: 0x0004, 0x151: 0x0004, - 0x152: 0x0004, 0x153: 0x0004, 0x154: 0x0004, 0x155: 0x0004, 0x156: 0x0004, 0x157: 0x0004, - 0x158: 0x0004, 0x159: 0x0004, 0x15a: 0x0004, 0x15b: 0x0004, 0x15c: 0x0004, 0x15d: 0x0004, - 0x15e: 0x0004, 0x15f: 0x0004, 0x160: 0x0004, 0x161: 0x0004, 0x162: 0x0004, 0x163: 0x0004, - 0x164: 0x0004, 0x165: 0x0004, 0x166: 0x0004, 0x167: 0x0004, 0x168: 0x0004, 0x169: 0x0004, - 0x16a: 0x0004, 0x16b: 0x0004, 0x16c: 0x0004, 0x16d: 0x0004, 0x16e: 0x0004, 0x16f: 0x0004, - // Block 0x6, offset 0x180 - 0x183: 0x0004, 0x184: 0x0004, 0x185: 0x0004, - 0x186: 0x0004, 0x187: 0x0004, 0x188: 0x0004, 0x189: 0x0004, - // Block 0x7, offset 0x1c0 - 0x1d1: 0x0004, - 0x1d2: 0x0004, 0x1d3: 0x0004, 0x1d4: 0x0004, 0x1d5: 0x0004, 0x1d6: 0x0004, 0x1d7: 0x0004, - 0x1d8: 0x0004, 0x1d9: 0x0004, 0x1da: 0x0004, 0x1db: 0x0004, 0x1dc: 0x0004, 0x1dd: 0x0004, - 0x1de: 0x0004, 0x1df: 0x0004, 0x1e0: 0x0004, 0x1e1: 0x0004, 0x1e2: 0x0004, 0x1e3: 0x0004, - 0x1e4: 0x0004, 0x1e5: 0x0004, 0x1e6: 0x0004, 0x1e7: 0x0004, 0x1e8: 0x0004, 0x1e9: 0x0004, - 0x1ea: 0x0004, 0x1eb: 0x0004, 0x1ec: 0x0004, 0x1ed: 0x0004, 0x1ee: 0x0004, 0x1ef: 0x0004, - 0x1f0: 0x0004, 0x1f1: 0x0004, 0x1f2: 0x0004, 0x1f3: 0x0004, 0x1f4: 0x0004, 0x1f5: 0x0004, - 0x1f6: 0x0004, 0x1f7: 0x0004, 0x1f8: 0x0004, 0x1f9: 0x0004, 0x1fa: 0x0004, 0x1fb: 0x0004, - 0x1fc: 0x0004, 0x1fd: 0x0004, 0x1ff: 0x0004, - // Block 0x8, offset 0x200 - 0x201: 0x0004, 0x202: 0x0004, 0x204: 0x0004, 0x205: 0x0004, - 0x207: 0x0004, - // Block 0x9, offset 0x240 - 0x240: 0x0100, 0x241: 0x0100, 0x242: 0x0100, 0x243: 0x0100, 0x244: 0x0100, 0x245: 0x0100, - 0x250: 0x0004, 0x251: 0x0004, - 0x252: 0x0004, 0x253: 0x0004, 0x254: 0x0004, 0x255: 0x0004, 0x256: 0x0004, 0x257: 0x0004, - 0x258: 0x0004, 0x259: 0x0004, 0x25a: 0x0004, 0x25c: 0x0002, - // Block 0xa, offset 0x280 - 0x28b: 0x0004, - 0x28c: 0x0004, 0x28d: 0x0004, 0x28e: 0x0004, 0x28f: 0x0004, 0x290: 0x0004, 0x291: 0x0004, - 0x292: 0x0004, 0x293: 0x0004, 0x294: 0x0004, 0x295: 0x0004, 0x296: 0x0004, 0x297: 0x0004, - 0x298: 0x0004, 0x299: 0x0004, 0x29a: 0x0004, 0x29b: 0x0004, 0x29c: 0x0004, 0x29d: 0x0004, - 0x29e: 0x0004, 0x29f: 0x0004, - 0x2b0: 0x0004, - // Block 0xb, offset 0x2c0 - 0x2d6: 0x0004, 0x2d7: 0x0004, - 0x2d8: 0x0004, 0x2d9: 0x0004, 0x2da: 0x0004, 0x2db: 0x0004, 0x2dc: 0x0004, 0x2dd: 0x0100, - 0x2df: 0x0004, 0x2e0: 0x0004, 0x2e1: 0x0004, 0x2e2: 0x0004, 0x2e3: 0x0004, - 0x2e4: 0x0004, 0x2e7: 0x0004, 0x2e8: 0x0004, - 0x2ea: 0x0004, 0x2eb: 0x0004, 0x2ec: 0x0004, 0x2ed: 0x0004, - // Block 0xc, offset 0x300 - 0x30f: 0x0100, 0x311: 0x0004, - 0x330: 0x0004, 0x331: 0x0004, 0x332: 0x0004, 0x333: 0x0004, 0x334: 0x0004, 0x335: 0x0004, - 0x336: 0x0004, 0x337: 0x0004, 0x338: 0x0004, 0x339: 0x0004, 0x33a: 0x0004, 0x33b: 0x0004, - 0x33c: 0x0004, 0x33d: 0x0004, 0x33e: 0x0004, 0x33f: 0x0004, - // Block 0xd, offset 0x340 - 0x340: 0x0004, 0x341: 0x0004, 0x342: 0x0004, 0x343: 0x0004, 0x344: 0x0004, 0x345: 0x0004, - 0x346: 0x0004, 0x347: 0x0004, 0x348: 0x0004, 0x349: 0x0004, 0x34a: 0x0004, - // Block 0xe, offset 0x380 - 0x3a6: 0x0004, 0x3a7: 0x0004, 0x3a8: 0x0004, 0x3a9: 0x0004, - 0x3aa: 0x0004, 0x3ab: 0x0004, 0x3ac: 0x0004, 0x3ad: 0x0004, 0x3ae: 0x0004, 0x3af: 0x0004, - 0x3b0: 0x0004, - // Block 0xf, offset 0x3c0 - 0x3eb: 0x0004, 0x3ec: 0x0004, 0x3ed: 0x0004, 0x3ee: 0x0004, 0x3ef: 0x0004, - 0x3f0: 0x0004, 0x3f1: 0x0004, 0x3f2: 0x0004, 0x3f3: 0x0004, - 0x3fd: 0x0004, - // Block 0x10, offset 0x400 - 0x416: 0x0004, 0x417: 0x0004, - 0x418: 0x0004, 0x419: 0x0004, 0x41b: 0x0004, 0x41c: 0x0004, 0x41d: 0x0004, - 0x41e: 0x0004, 0x41f: 0x0004, 0x420: 0x0004, 0x421: 0x0004, 0x422: 0x0004, 0x423: 0x0004, - 0x425: 0x0004, 0x426: 0x0004, 0x427: 0x0004, 0x429: 0x0004, - 0x42a: 0x0004, 0x42b: 0x0004, 0x42c: 0x0004, 0x42d: 0x0004, - // Block 0x11, offset 0x440 - 0x459: 0x0004, 0x45a: 0x0004, 0x45b: 0x0004, - // Block 0x12, offset 0x480 - 0x490: 0x0100, 0x491: 0x0100, - 0x498: 0x0004, 0x499: 0x0004, 0x49a: 0x0004, 0x49b: 0x0004, 0x49c: 0x0004, 0x49d: 0x0004, - 0x49e: 0x0004, 0x49f: 0x0004, - // Block 0x13, offset 0x4c0 - 0x4ca: 0x0004, 0x4cb: 0x0004, - 0x4cc: 0x0004, 0x4cd: 0x0004, 0x4ce: 0x0004, 0x4cf: 0x0004, 0x4d0: 0x0004, 0x4d1: 0x0004, - 0x4d2: 0x0004, 0x4d3: 0x0004, 0x4d4: 0x0004, 0x4d5: 0x0004, 0x4d6: 0x0004, 0x4d7: 0x0004, - 0x4d8: 0x0004, 0x4d9: 0x0004, 0x4da: 0x0004, 0x4db: 0x0004, 0x4dc: 0x0004, 0x4dd: 0x0004, - 0x4de: 0x0004, 0x4df: 0x0004, 0x4e0: 0x0004, 0x4e1: 0x0004, 0x4e2: 0x0100, 0x4e3: 0x0004, - 0x4e4: 0x0004, 0x4e5: 0x0004, 0x4e6: 0x0004, 0x4e7: 0x0004, 0x4e8: 0x0004, 0x4e9: 0x0004, - 0x4ea: 0x0004, 0x4eb: 0x0004, 0x4ec: 0x0004, 0x4ed: 0x0004, 0x4ee: 0x0004, 0x4ef: 0x0004, - 0x4f0: 0x0004, 0x4f1: 0x0004, 0x4f2: 0x0004, 0x4f3: 0x0004, 0x4f4: 0x0004, 0x4f5: 0x0004, - 0x4f6: 0x0004, 0x4f7: 0x0004, 0x4f8: 0x0004, 0x4f9: 0x0004, 0x4fa: 0x0004, 0x4fb: 0x0004, - 0x4fc: 0x0004, 0x4fd: 0x0004, 0x4fe: 0x0004, 0x4ff: 0x0004, - // Block 0x14, offset 0x500 - 0x500: 0x0004, 0x501: 0x0004, 0x502: 0x0004, 0x503: 0x0400, - 0x53a: 0x0004, 0x53b: 0x0400, - 0x53c: 0x0004, 0x53e: 0x0400, 0x53f: 0x0400, - // Block 0x15, offset 0x540 - 0x540: 0x0400, 0x541: 0x0004, 0x542: 0x0004, 0x543: 0x0004, 0x544: 0x0004, 0x545: 0x0004, - 0x546: 0x0004, 0x547: 0x0004, 0x548: 0x0004, 0x549: 0x0400, 0x54a: 0x0400, 0x54b: 0x0400, - 0x54c: 0x0400, 0x54d: 0x0004, 0x54e: 0x0400, 0x54f: 0x0400, 0x551: 0x0004, - 0x552: 0x0004, 0x553: 0x0004, 0x554: 0x0004, 0x555: 0x0004, 0x556: 0x0004, 0x557: 0x0004, - 0x562: 0x0004, 0x563: 0x0004, - // Block 0x16, offset 0x580 - 0x581: 0x0004, 0x582: 0x0400, 0x583: 0x0400, - 0x5bc: 0x0004, 0x5be: 0x0004, 0x5bf: 0x0400, - // Block 0x17, offset 0x5c0 - 0x5c0: 0x0400, 0x5c1: 0x0004, 0x5c2: 0x0004, 0x5c3: 0x0004, 0x5c4: 0x0004, - 0x5c7: 0x0400, 0x5c8: 0x0400, 0x5cb: 0x0400, - 0x5cc: 0x0400, 0x5cd: 0x0004, - 0x5d7: 0x0004, - 0x5e2: 0x0004, 0x5e3: 0x0004, - 0x5fe: 0x0004, - // Block 0x18, offset 0x600 - 0x601: 0x0004, 0x602: 0x0004, 0x603: 0x0400, - 0x63c: 0x0004, 0x63e: 0x0400, 0x63f: 0x0400, - // Block 0x19, offset 0x640 - 0x640: 0x0400, 0x641: 0x0004, 0x642: 0x0004, - 0x647: 0x0004, 0x648: 0x0004, 0x64b: 0x0004, - 0x64c: 0x0004, 0x64d: 0x0004, 0x651: 0x0004, - 0x670: 0x0004, 0x671: 0x0004, 0x675: 0x0004, - // Block 0x1a, offset 0x680 - 0x680: 0x0400, 0x681: 0x0004, 0x682: 0x0004, 0x683: 0x0004, 0x684: 0x0004, 0x685: 0x0004, - 0x687: 0x0004, 0x688: 0x0004, 0x689: 0x0400, 0x68b: 0x0400, - 0x68c: 0x0400, 0x68d: 0x0004, - 0x6a2: 0x0004, 0x6a3: 0x0004, - 0x6ba: 0x0004, 0x6bb: 0x0004, - 0x6bc: 0x0004, 0x6bd: 0x0004, 0x6be: 0x0004, 0x6bf: 0x0004, - // Block 0x1b, offset 0x6c0 - 0x6c1: 0x0004, 0x6c2: 0x0400, 0x6c3: 0x0400, - 0x6fc: 0x0004, 0x6fe: 0x0004, 0x6ff: 0x0004, - // Block 0x1c, offset 0x700 - 0x700: 0x0400, 0x701: 0x0004, 0x702: 0x0004, 0x703: 0x0004, 0x704: 0x0004, - 0x707: 0x0400, 0x708: 0x0400, 0x70b: 0x0400, - 0x70c: 0x0400, 0x70d: 0x0004, - 0x715: 0x0004, 0x716: 0x0004, 0x717: 0x0004, - 0x722: 0x0004, 0x723: 0x0004, - // Block 0x1d, offset 0x740 - 0x742: 0x0004, - 0x77e: 0x0004, 0x77f: 0x0400, - // Block 0x1e, offset 0x780 - 0x780: 0x0004, 0x781: 0x0400, 0x782: 0x0400, - 0x786: 0x0400, 0x787: 0x0400, 0x788: 0x0400, 0x78a: 0x0400, 0x78b: 0x0400, - 0x78c: 0x0400, 0x78d: 0x0004, - 0x797: 0x0004, - // Block 0x1f, offset 0x7c0 - 0x7c0: 0x0004, 0x7c1: 0x0400, 0x7c2: 0x0400, 0x7c3: 0x0400, 0x7c4: 0x0004, - 0x7fc: 0x0004, 0x7fe: 0x0004, 0x7ff: 0x0004, - // Block 0x20, offset 0x800 - 0x800: 0x0004, 0x801: 0x0400, 0x802: 0x0400, 0x803: 0x0400, 0x804: 0x0400, - 0x806: 0x0004, 0x807: 0x0004, 0x808: 0x0004, 0x80a: 0x0004, 0x80b: 0x0004, - 0x80c: 0x0004, 0x80d: 0x0004, - 0x815: 0x0004, 0x816: 0x0004, - 0x822: 0x0004, 0x823: 0x0004, - // Block 0x21, offset 0x840 - 0x841: 0x0004, 0x842: 0x0400, 0x843: 0x0400, - 0x87c: 0x0004, 0x87e: 0x0400, 0x87f: 0x0004, - // Block 0x22, offset 0x880 - 0x880: 0x0400, 0x881: 0x0400, 0x882: 0x0004, 0x883: 0x0400, 0x884: 0x0400, - 0x886: 0x0004, 0x887: 0x0400, 0x888: 0x0400, 0x88a: 0x0400, 0x88b: 0x0400, - 0x88c: 0x0004, 0x88d: 0x0004, - 0x895: 0x0004, 0x896: 0x0004, - 0x8a2: 0x0004, 0x8a3: 0x0004, - 0x8b3: 0x0400, - // Block 0x23, offset 0x8c0 - 0x8c0: 0x0004, 0x8c1: 0x0004, 0x8c2: 0x0400, 0x8c3: 0x0400, - 0x8fb: 0x0004, - 0x8fc: 0x0004, 0x8fe: 0x0004, 0x8ff: 0x0400, - // Block 0x24, offset 0x900 - 0x900: 0x0400, 0x901: 0x0004, 0x902: 0x0004, 0x903: 0x0004, 0x904: 0x0004, - 0x906: 0x0400, 0x907: 0x0400, 0x908: 0x0400, 0x90a: 0x0400, 0x90b: 0x0400, - 0x90c: 0x0400, 0x90d: 0x0004, 0x90e: 0x0100, - 0x917: 0x0004, - 0x922: 0x0004, 0x923: 0x0004, - // Block 0x25, offset 0x940 - 0x941: 0x0004, 0x942: 0x0400, 0x943: 0x0400, - // Block 0x26, offset 0x980 - 0x98a: 0x0004, - 0x98f: 0x0004, 0x990: 0x0400, 0x991: 0x0400, - 0x992: 0x0004, 0x993: 0x0004, 0x994: 0x0004, 0x996: 0x0004, - 0x998: 0x0400, 0x999: 0x0400, 0x99a: 0x0400, 0x99b: 0x0400, 0x99c: 0x0400, 0x99d: 0x0400, - 0x99e: 0x0400, 0x99f: 0x0004, - 0x9b2: 0x0400, 0x9b3: 0x0400, - // Block 0x27, offset 0x9c0 - 0x9f1: 0x0004, 0x9f3: 0x0400, 0x9f4: 0x0004, 0x9f5: 0x0004, - 0x9f6: 0x0004, 0x9f7: 0x0004, 0x9f8: 0x0004, 0x9f9: 0x0004, 0x9fa: 0x0004, - // Block 0x28, offset 0xa00 - 0xa07: 0x0004, 0xa08: 0x0004, 0xa09: 0x0004, 0xa0a: 0x0004, 0xa0b: 0x0004, - 0xa0c: 0x0004, 0xa0d: 0x0004, 0xa0e: 0x0004, - // Block 0x29, offset 0xa40 - 0xa71: 0x0004, 0xa73: 0x0400, 0xa74: 0x0004, 0xa75: 0x0004, - 0xa76: 0x0004, 0xa77: 0x0004, 0xa78: 0x0004, 0xa79: 0x0004, 0xa7a: 0x0004, 0xa7b: 0x0004, - 0xa7c: 0x0004, - // Block 0x2a, offset 0xa80 - 0xa88: 0x0004, 0xa89: 0x0004, 0xa8a: 0x0004, 0xa8b: 0x0004, - 0xa8c: 0x0004, 0xa8d: 0x0004, 0xa8e: 0x0004, - // Block 0x2b, offset 0xac0 - 0xad8: 0x0004, 0xad9: 0x0004, - 0xaf5: 0x0004, - 0xaf7: 0x0004, 0xaf9: 0x0004, - 0xafe: 0x0400, 0xaff: 0x0400, - // Block 0x2c, offset 0xb00 - 0xb31: 0x0004, 0xb32: 0x0004, 0xb33: 0x0004, 0xb34: 0x0004, 0xb35: 0x0004, - 0xb36: 0x0004, 0xb37: 0x0004, 0xb38: 0x0004, 0xb39: 0x0004, 0xb3a: 0x0004, 0xb3b: 0x0004, - 0xb3c: 0x0004, 0xb3d: 0x0004, 0xb3e: 0x0004, 0xb3f: 0x0400, - // Block 0x2d, offset 0xb40 - 0xb40: 0x0004, 0xb41: 0x0004, 0xb42: 0x0004, 0xb43: 0x0004, 0xb44: 0x0004, - 0xb46: 0x0004, 0xb47: 0x0004, - 0xb4d: 0x0004, 0xb4e: 0x0004, 0xb4f: 0x0004, 0xb50: 0x0004, 0xb51: 0x0004, - 0xb52: 0x0004, 0xb53: 0x0004, 0xb54: 0x0004, 0xb55: 0x0004, 0xb56: 0x0004, 0xb57: 0x0004, - 0xb59: 0x0004, 0xb5a: 0x0004, 0xb5b: 0x0004, 0xb5c: 0x0004, 0xb5d: 0x0004, - 0xb5e: 0x0004, 0xb5f: 0x0004, 0xb60: 0x0004, 0xb61: 0x0004, 0xb62: 0x0004, 0xb63: 0x0004, - 0xb64: 0x0004, 0xb65: 0x0004, 0xb66: 0x0004, 0xb67: 0x0004, 0xb68: 0x0004, 0xb69: 0x0004, - 0xb6a: 0x0004, 0xb6b: 0x0004, 0xb6c: 0x0004, 0xb6d: 0x0004, 0xb6e: 0x0004, 0xb6f: 0x0004, - 0xb70: 0x0004, 0xb71: 0x0004, 0xb72: 0x0004, 0xb73: 0x0004, 0xb74: 0x0004, 0xb75: 0x0004, - 0xb76: 0x0004, 0xb77: 0x0004, 0xb78: 0x0004, 0xb79: 0x0004, 0xb7a: 0x0004, 0xb7b: 0x0004, - 0xb7c: 0x0004, - // Block 0x2e, offset 0xb80 - 0xb86: 0x0004, - // Block 0x2f, offset 0xbc0 - 0xbed: 0x0004, 0xbee: 0x0004, 0xbef: 0x0004, - 0xbf0: 0x0004, 0xbf1: 0x0400, 0xbf2: 0x0004, 0xbf3: 0x0004, 0xbf4: 0x0004, 0xbf5: 0x0004, - 0xbf6: 0x0004, 0xbf7: 0x0004, 0xbf9: 0x0004, 0xbfa: 0x0004, 0xbfb: 0x0400, - 0xbfc: 0x0400, 0xbfd: 0x0004, 0xbfe: 0x0004, - // Block 0x30, offset 0xc00 - 0xc16: 0x0400, 0xc17: 0x0400, - 0xc18: 0x0004, 0xc19: 0x0004, - 0xc1e: 0x0004, 0xc1f: 0x0004, 0xc20: 0x0004, - 0xc31: 0x0004, 0xc32: 0x0004, 0xc33: 0x0004, 0xc34: 0x0004, - // Block 0x31, offset 0xc40 - 0xc42: 0x0004, 0xc44: 0x0400, 0xc45: 0x0004, - 0xc46: 0x0004, - 0xc4d: 0x0004, - 0xc5d: 0x0004, - // Block 0x32, offset 0xc80 - 0xc80: 0x0010, 0xc81: 0x0010, 0xc82: 0x0010, 0xc83: 0x0010, 0xc84: 0x0010, 0xc85: 0x0010, - 0xc86: 0x0010, 0xc87: 0x0010, 0xc88: 0x0010, 0xc89: 0x0010, 0xc8a: 0x0010, 0xc8b: 0x0010, - 0xc8c: 0x0010, 0xc8d: 0x0010, 0xc8e: 0x0010, 0xc8f: 0x0010, 0xc90: 0x0010, 0xc91: 0x0010, - 0xc92: 0x0010, 0xc93: 0x0010, 0xc94: 0x0010, 0xc95: 0x0010, 0xc96: 0x0010, 0xc97: 0x0010, - 0xc98: 0x0010, 0xc99: 0x0010, 0xc9a: 0x0010, 0xc9b: 0x0010, 0xc9c: 0x0010, 0xc9d: 0x0010, - 0xc9e: 0x0010, 0xc9f: 0x0010, 0xca0: 0x0010, 0xca1: 0x0010, 0xca2: 0x0010, 0xca3: 0x0010, - 0xca4: 0x0010, 0xca5: 0x0010, 0xca6: 0x0010, 0xca7: 0x0010, 0xca8: 0x0010, 0xca9: 0x0010, - 0xcaa: 0x0010, 0xcab: 0x0010, 0xcac: 0x0010, 0xcad: 0x0010, 0xcae: 0x0010, 0xcaf: 0x0010, - 0xcb0: 0x0010, 0xcb1: 0x0010, 0xcb2: 0x0010, 0xcb3: 0x0010, 0xcb4: 0x0010, 0xcb5: 0x0010, - 0xcb6: 0x0010, 0xcb7: 0x0010, 0xcb8: 0x0010, 0xcb9: 0x0010, 0xcba: 0x0010, 0xcbb: 0x0010, - 0xcbc: 0x0010, 0xcbd: 0x0010, 0xcbe: 0x0010, 0xcbf: 0x0010, - // Block 0x33, offset 0xcc0 - 0xcc0: 0x0010, 0xcc1: 0x0010, 0xcc2: 0x0010, 0xcc3: 0x0010, 0xcc4: 0x0010, 0xcc5: 0x0010, - 0xcc6: 0x0010, 0xcc7: 0x0010, 0xcc8: 0x0010, 0xcc9: 0x0010, 0xcca: 0x0010, 0xccb: 0x0010, - 0xccc: 0x0010, 0xccd: 0x0010, 0xcce: 0x0010, 0xccf: 0x0010, 0xcd0: 0x0010, 0xcd1: 0x0010, - 0xcd2: 0x0010, 0xcd3: 0x0010, 0xcd4: 0x0010, 0xcd5: 0x0010, 0xcd6: 0x0010, 0xcd7: 0x0010, - 0xcd8: 0x0010, 0xcd9: 0x0010, 0xcda: 0x0010, 0xcdb: 0x0010, 0xcdc: 0x0010, 0xcdd: 0x0010, - 0xcde: 0x0010, 0xcdf: 0x0010, 0xce0: 0x1000, 0xce1: 0x1000, 0xce2: 0x1000, 0xce3: 0x1000, - 0xce4: 0x1000, 0xce5: 0x1000, 0xce6: 0x1000, 0xce7: 0x1000, 0xce8: 0x1000, 0xce9: 0x1000, - 0xcea: 0x1000, 0xceb: 0x1000, 0xcec: 0x1000, 0xced: 0x1000, 0xcee: 0x1000, 0xcef: 0x1000, - 0xcf0: 0x1000, 0xcf1: 0x1000, 0xcf2: 0x1000, 0xcf3: 0x1000, 0xcf4: 0x1000, 0xcf5: 0x1000, - 0xcf6: 0x1000, 0xcf7: 0x1000, 0xcf8: 0x1000, 0xcf9: 0x1000, 0xcfa: 0x1000, 0xcfb: 0x1000, - 0xcfc: 0x1000, 0xcfd: 0x1000, 0xcfe: 0x1000, 0xcff: 0x1000, - // Block 0x34, offset 0xd00 - 0xd00: 0x1000, 0xd01: 0x1000, 0xd02: 0x1000, 0xd03: 0x1000, 0xd04: 0x1000, 0xd05: 0x1000, - 0xd06: 0x1000, 0xd07: 0x1000, 0xd08: 0x1000, 0xd09: 0x1000, 0xd0a: 0x1000, 0xd0b: 0x1000, - 0xd0c: 0x1000, 0xd0d: 0x1000, 0xd0e: 0x1000, 0xd0f: 0x1000, 0xd10: 0x1000, 0xd11: 0x1000, - 0xd12: 0x1000, 0xd13: 0x1000, 0xd14: 0x1000, 0xd15: 0x1000, 0xd16: 0x1000, 0xd17: 0x1000, - 0xd18: 0x1000, 0xd19: 0x1000, 0xd1a: 0x1000, 0xd1b: 0x1000, 0xd1c: 0x1000, 0xd1d: 0x1000, - 0xd1e: 0x1000, 0xd1f: 0x1000, 0xd20: 0x1000, 0xd21: 0x1000, 0xd22: 0x1000, 0xd23: 0x1000, - 0xd24: 0x1000, 0xd25: 0x1000, 0xd26: 0x1000, 0xd27: 0x1000, 0xd28: 0x0800, 0xd29: 0x0800, - 0xd2a: 0x0800, 0xd2b: 0x0800, 0xd2c: 0x0800, 0xd2d: 0x0800, 0xd2e: 0x0800, 0xd2f: 0x0800, - 0xd30: 0x0800, 0xd31: 0x0800, 0xd32: 0x0800, 0xd33: 0x0800, 0xd34: 0x0800, 0xd35: 0x0800, - 0xd36: 0x0800, 0xd37: 0x0800, 0xd38: 0x0800, 0xd39: 0x0800, 0xd3a: 0x0800, 0xd3b: 0x0800, - 0xd3c: 0x0800, 0xd3d: 0x0800, 0xd3e: 0x0800, 0xd3f: 0x0800, - // Block 0x35, offset 0xd40 - 0xd40: 0x0800, 0xd41: 0x0800, 0xd42: 0x0800, 0xd43: 0x0800, 0xd44: 0x0800, 0xd45: 0x0800, - 0xd46: 0x0800, 0xd47: 0x0800, 0xd48: 0x0800, 0xd49: 0x0800, 0xd4a: 0x0800, 0xd4b: 0x0800, - 0xd4c: 0x0800, 0xd4d: 0x0800, 0xd4e: 0x0800, 0xd4f: 0x0800, 0xd50: 0x0800, 0xd51: 0x0800, - 0xd52: 0x0800, 0xd53: 0x0800, 0xd54: 0x0800, 0xd55: 0x0800, 0xd56: 0x0800, 0xd57: 0x0800, - 0xd58: 0x0800, 0xd59: 0x0800, 0xd5a: 0x0800, 0xd5b: 0x0800, 0xd5c: 0x0800, 0xd5d: 0x0800, - 0xd5e: 0x0800, 0xd5f: 0x0800, 0xd60: 0x0800, 0xd61: 0x0800, 0xd62: 0x0800, 0xd63: 0x0800, - 0xd64: 0x0800, 0xd65: 0x0800, 0xd66: 0x0800, 0xd67: 0x0800, 0xd68: 0x0800, 0xd69: 0x0800, - 0xd6a: 0x0800, 0xd6b: 0x0800, 0xd6c: 0x0800, 0xd6d: 0x0800, 0xd6e: 0x0800, 0xd6f: 0x0800, - 0xd70: 0x0800, 0xd71: 0x0800, 0xd72: 0x0800, 0xd73: 0x0800, 0xd74: 0x0800, 0xd75: 0x0800, - 0xd76: 0x0800, 0xd77: 0x0800, 0xd78: 0x0800, 0xd79: 0x0800, 0xd7a: 0x0800, 0xd7b: 0x0800, - 0xd7c: 0x0800, 0xd7d: 0x0800, 0xd7e: 0x0800, 0xd7f: 0x0800, - // Block 0x36, offset 0xd80 - 0xd9d: 0x0004, - 0xd9e: 0x0004, 0xd9f: 0x0004, - // Block 0x37, offset 0xdc0 - 0xdd2: 0x0004, 0xdd3: 0x0004, 0xdd4: 0x0004, 0xdd5: 0x0400, - 0xdf2: 0x0004, 0xdf3: 0x0004, 0xdf4: 0x0400, - // Block 0x38, offset 0xe00 - 0xe12: 0x0004, 0xe13: 0x0004, - 0xe32: 0x0004, 0xe33: 0x0004, - // Block 0x39, offset 0xe40 - 0xe74: 0x0004, 0xe75: 0x0004, - 0xe76: 0x0400, 0xe77: 0x0004, 0xe78: 0x0004, 0xe79: 0x0004, 0xe7a: 0x0004, 0xe7b: 0x0004, - 0xe7c: 0x0004, 0xe7d: 0x0004, 0xe7e: 0x0400, 0xe7f: 0x0400, - // Block 0x3a, offset 0xe80 - 0xe80: 0x0400, 0xe81: 0x0400, 0xe82: 0x0400, 0xe83: 0x0400, 0xe84: 0x0400, 0xe85: 0x0400, - 0xe86: 0x0004, 0xe87: 0x0400, 0xe88: 0x0400, 0xe89: 0x0004, 0xe8a: 0x0004, 0xe8b: 0x0004, - 0xe8c: 0x0004, 0xe8d: 0x0004, 0xe8e: 0x0004, 0xe8f: 0x0004, 0xe90: 0x0004, 0xe91: 0x0004, - 0xe92: 0x0004, 0xe93: 0x0004, - 0xe9d: 0x0004, - // Block 0x3b, offset 0xec0 - 0xecb: 0x0004, - 0xecc: 0x0004, 0xecd: 0x0004, 0xece: 0x0002, 0xecf: 0x0004, - // Block 0x3c, offset 0xf00 - 0xf05: 0x0004, - 0xf06: 0x0004, - 0xf29: 0x0004, - // Block 0x3d, offset 0xf40 - 0xf60: 0x0004, 0xf61: 0x0004, 0xf62: 0x0004, 0xf63: 0x0400, - 0xf64: 0x0400, 0xf65: 0x0400, 0xf66: 0x0400, 0xf67: 0x0004, 0xf68: 0x0004, 0xf69: 0x0400, - 0xf6a: 0x0400, 0xf6b: 0x0400, - 0xf70: 0x0400, 0xf71: 0x0400, 0xf72: 0x0004, 0xf73: 0x0400, 0xf74: 0x0400, 0xf75: 0x0400, - 0xf76: 0x0400, 0xf77: 0x0400, 0xf78: 0x0400, 0xf79: 0x0004, 0xf7a: 0x0004, 0xf7b: 0x0004, - // Block 0x3e, offset 0xf80 - 0xf97: 0x0004, - 0xf98: 0x0004, 0xf99: 0x0400, 0xf9a: 0x0400, 0xf9b: 0x0004, - // Block 0x3f, offset 0xfc0 - 0xfd5: 0x0400, 0xfd6: 0x0004, 0xfd7: 0x0400, - 0xfd8: 0x0004, 0xfd9: 0x0004, 0xfda: 0x0004, 0xfdb: 0x0004, 0xfdc: 0x0004, 0xfdd: 0x0004, - 0xfde: 0x0004, 0xfe0: 0x0004, 0xfe2: 0x0004, - 0xfe5: 0x0004, 0xfe6: 0x0004, 0xfe7: 0x0004, 0xfe8: 0x0004, 0xfe9: 0x0004, - 0xfea: 0x0004, 0xfeb: 0x0004, 0xfec: 0x0004, 0xfed: 0x0400, 0xfee: 0x0400, 0xfef: 0x0400, - 0xff0: 0x0400, 0xff1: 0x0400, 0xff2: 0x0400, 0xff3: 0x0004, 0xff4: 0x0004, 0xff5: 0x0004, - 0xff6: 0x0004, 0xff7: 0x0004, 0xff8: 0x0004, 0xff9: 0x0004, 0xffa: 0x0004, 0xffb: 0x0004, - 0xffc: 0x0004, 0xfff: 0x0004, - // Block 0x40, offset 0x1000 - 0x1030: 0x0004, 0x1031: 0x0004, 0x1032: 0x0004, 0x1033: 0x0004, 0x1034: 0x0004, 0x1035: 0x0004, - 0x1036: 0x0004, 0x1037: 0x0004, 0x1038: 0x0004, 0x1039: 0x0004, 0x103a: 0x0004, 0x103b: 0x0004, - 0x103c: 0x0004, 0x103d: 0x0004, 0x103e: 0x0004, 0x103f: 0x0004, - // Block 0x41, offset 0x1040 - 0x1040: 0x0004, 0x1041: 0x0004, 0x1042: 0x0004, 0x1043: 0x0004, 0x1044: 0x0004, 0x1045: 0x0004, - 0x1046: 0x0004, 0x1047: 0x0004, 0x1048: 0x0004, 0x1049: 0x0004, 0x104a: 0x0004, 0x104b: 0x0004, - 0x104c: 0x0004, 0x104d: 0x0004, 0x104e: 0x0004, - // Block 0x42, offset 0x1080 - 0x1080: 0x0004, 0x1081: 0x0004, 0x1082: 0x0004, 0x1083: 0x0004, 0x1084: 0x0400, - 0x10b4: 0x0004, 0x10b5: 0x0004, - 0x10b6: 0x0004, 0x10b7: 0x0004, 0x10b8: 0x0004, 0x10b9: 0x0004, 0x10ba: 0x0004, 0x10bb: 0x0400, - 0x10bc: 0x0004, 0x10bd: 0x0400, 0x10be: 0x0400, 0x10bf: 0x0400, - // Block 0x43, offset 0x10c0 - 0x10c0: 0x0400, 0x10c1: 0x0400, 0x10c2: 0x0004, 0x10c3: 0x0400, 0x10c4: 0x0400, - 0x10eb: 0x0004, 0x10ec: 0x0004, 0x10ed: 0x0004, 0x10ee: 0x0004, 0x10ef: 0x0004, - 0x10f0: 0x0004, 0x10f1: 0x0004, 0x10f2: 0x0004, 0x10f3: 0x0004, - // Block 0x44, offset 0x1100 - 0x1100: 0x0004, 0x1101: 0x0004, 0x1102: 0x0400, - 0x1121: 0x0400, 0x1122: 0x0004, 0x1123: 0x0004, - 0x1124: 0x0004, 0x1125: 0x0004, 0x1126: 0x0400, 0x1127: 0x0400, 0x1128: 0x0004, 0x1129: 0x0004, - 0x112a: 0x0400, 0x112b: 0x0004, 0x112c: 0x0004, 0x112d: 0x0004, - // Block 0x45, offset 0x1140 - 0x1166: 0x0004, 0x1167: 0x0400, 0x1168: 0x0004, 0x1169: 0x0004, - 0x116a: 0x0400, 0x116b: 0x0400, 0x116c: 0x0400, 0x116d: 0x0004, 0x116e: 0x0400, 0x116f: 0x0004, - 0x1170: 0x0004, 0x1171: 0x0004, 0x1172: 0x0400, 0x1173: 0x0400, - // Block 0x46, offset 0x1180 - 0x11a4: 0x0400, 0x11a5: 0x0400, 0x11a6: 0x0400, 0x11a7: 0x0400, 0x11a8: 0x0400, 0x11a9: 0x0400, - 0x11aa: 0x0400, 0x11ab: 0x0400, 0x11ac: 0x0004, 0x11ad: 0x0004, 0x11ae: 0x0004, 0x11af: 0x0004, - 0x11b0: 0x0004, 0x11b1: 0x0004, 0x11b2: 0x0004, 0x11b3: 0x0004, 0x11b4: 0x0400, 0x11b5: 0x0400, - 0x11b6: 0x0004, 0x11b7: 0x0004, - // Block 0x47, offset 0x11c0 - 0x11d0: 0x0004, 0x11d1: 0x0004, - 0x11d2: 0x0004, 0x11d4: 0x0004, 0x11d5: 0x0004, 0x11d6: 0x0004, 0x11d7: 0x0004, - 0x11d8: 0x0004, 0x11d9: 0x0004, 0x11da: 0x0004, 0x11db: 0x0004, 0x11dc: 0x0004, 0x11dd: 0x0004, - 0x11de: 0x0004, 0x11df: 0x0004, 0x11e0: 0x0004, 0x11e1: 0x0400, 0x11e2: 0x0004, 0x11e3: 0x0004, - 0x11e4: 0x0004, 0x11e5: 0x0004, 0x11e6: 0x0004, 0x11e7: 0x0004, 0x11e8: 0x0004, - 0x11ed: 0x0004, - 0x11f4: 0x0004, - 0x11f7: 0x0400, 0x11f8: 0x0004, 0x11f9: 0x0004, - // Block 0x48, offset 0x1200 - 0x120b: 0x0002, - 0x120c: 0x0004, 0x120d: 0x2000, 0x120e: 0x0002, 0x120f: 0x0002, - 0x1228: 0x0002, 0x1229: 0x0002, - 0x122a: 0x0002, 0x122b: 0x0002, 0x122c: 0x0002, 0x122d: 0x0002, 0x122e: 0x0002, - 0x123c: 0x0008, - // Block 0x49, offset 0x1240 - 0x1249: 0x0008, - 0x1260: 0x0002, 0x1261: 0x0002, 0x1262: 0x0002, 0x1263: 0x0002, - 0x1264: 0x0002, 0x1265: 0x0002, 0x1266: 0x0002, 0x1267: 0x0002, 0x1268: 0x0002, 0x1269: 0x0002, - 0x126a: 0x0002, 0x126b: 0x0002, 0x126c: 0x0002, 0x126d: 0x0002, 0x126e: 0x0002, 0x126f: 0x0002, - // Block 0x4a, offset 0x1280 - 0x1290: 0x0004, 0x1291: 0x0004, - 0x1292: 0x0004, 0x1293: 0x0004, 0x1294: 0x0004, 0x1295: 0x0004, 0x1296: 0x0004, 0x1297: 0x0004, - 0x1298: 0x0004, 0x1299: 0x0004, 0x129a: 0x0004, 0x129b: 0x0004, 0x129c: 0x0004, 0x129d: 0x0004, - 0x129e: 0x0004, 0x129f: 0x0004, 0x12a0: 0x0004, 0x12a1: 0x0004, 0x12a2: 0x0004, 0x12a3: 0x0004, - 0x12a4: 0x0004, 0x12a5: 0x0004, 0x12a6: 0x0004, 0x12a7: 0x0004, 0x12a8: 0x0004, 0x12a9: 0x0004, - 0x12aa: 0x0004, 0x12ab: 0x0004, 0x12ac: 0x0004, 0x12ad: 0x0004, 0x12ae: 0x0004, 0x12af: 0x0004, - 0x12b0: 0x0004, - // Block 0x4b, offset 0x12c0 - 0x12e2: 0x0008, - 0x12f9: 0x0008, - // Block 0x4c, offset 0x1300 - 0x1314: 0x0008, 0x1315: 0x0008, 0x1316: 0x0008, 0x1317: 0x0008, - 0x1318: 0x0008, 0x1319: 0x0008, - 0x1329: 0x0008, - 0x132a: 0x0008, - // Block 0x4d, offset 0x1340 - 0x135a: 0x0008, 0x135b: 0x0008, - 0x1368: 0x0008, - // Block 0x4e, offset 0x1380 - 0x1388: 0x0008, - // Block 0x4f, offset 0x13c0 - 0x13cf: 0x0008, - 0x13e9: 0x0008, - 0x13ea: 0x0008, 0x13eb: 0x0008, 0x13ec: 0x0008, 0x13ed: 0x0008, 0x13ee: 0x0008, 0x13ef: 0x0008, - 0x13f0: 0x0008, 0x13f1: 0x0008, 0x13f2: 0x0008, 0x13f3: 0x0008, - 0x13f8: 0x0008, 0x13f9: 0x0008, 0x13fa: 0x0008, - // Block 0x50, offset 0x1400 - 0x1402: 0x0008, - // Block 0x51, offset 0x1440 - 0x146a: 0x0008, 0x146b: 0x0008, - 0x1476: 0x0008, - // Block 0x52, offset 0x1480 - 0x1480: 0x0008, - 0x14bb: 0x0008, - 0x14bc: 0x0008, 0x14bd: 0x0008, 0x14be: 0x0008, - // Block 0x53, offset 0x14c0 - 0x14c0: 0x0008, 0x14c1: 0x0008, 0x14c2: 0x0008, 0x14c3: 0x0008, 0x14c4: 0x0008, 0x14c5: 0x0008, - 0x14c7: 0x0008, 0x14c8: 0x0008, 0x14c9: 0x0008, 0x14ca: 0x0008, 0x14cb: 0x0008, - 0x14cc: 0x0008, 0x14cd: 0x0008, 0x14ce: 0x0008, 0x14cf: 0x0008, 0x14d0: 0x0008, 0x14d1: 0x0008, - 0x14d2: 0x0008, 0x14d4: 0x0008, 0x14d5: 0x0008, 0x14d6: 0x0008, 0x14d7: 0x0008, - 0x14d8: 0x0008, 0x14d9: 0x0008, 0x14da: 0x0008, 0x14db: 0x0008, 0x14dc: 0x0008, 0x14dd: 0x0008, - 0x14de: 0x0008, 0x14df: 0x0008, 0x14e0: 0x0008, 0x14e1: 0x0008, 0x14e2: 0x0008, 0x14e3: 0x0008, - 0x14e4: 0x0008, 0x14e5: 0x0008, 0x14e6: 0x0008, 0x14e7: 0x0008, 0x14e8: 0x0008, 0x14e9: 0x0008, - 0x14ea: 0x0008, 0x14eb: 0x0008, 0x14ec: 0x0008, 0x14ed: 0x0008, 0x14ee: 0x0008, 0x14ef: 0x0008, - 0x14f0: 0x0008, 0x14f1: 0x0008, 0x14f2: 0x0008, 0x14f3: 0x0008, 0x14f4: 0x0008, 0x14f5: 0x0008, - 0x14f6: 0x0008, 0x14f7: 0x0008, 0x14f8: 0x0008, 0x14f9: 0x0008, 0x14fa: 0x0008, 0x14fb: 0x0008, - 0x14fc: 0x0008, 0x14fd: 0x0008, 0x14fe: 0x0008, 0x14ff: 0x0008, - // Block 0x54, offset 0x1500 - 0x1500: 0x0008, 0x1501: 0x0008, 0x1502: 0x0008, 0x1503: 0x0008, 0x1504: 0x0008, 0x1505: 0x0008, - 0x1506: 0x0008, 0x1507: 0x0008, 0x1508: 0x0008, 0x1509: 0x0008, 0x150a: 0x0008, 0x150b: 0x0008, - 0x150c: 0x0008, 0x150d: 0x0008, 0x150e: 0x0008, 0x150f: 0x0008, 0x1510: 0x0008, 0x1511: 0x0008, - 0x1512: 0x0008, 0x1513: 0x0008, 0x1514: 0x0008, 0x1515: 0x0008, 0x1516: 0x0008, 0x1517: 0x0008, - 0x1518: 0x0008, 0x1519: 0x0008, 0x151a: 0x0008, 0x151b: 0x0008, 0x151c: 0x0008, 0x151d: 0x0008, - 0x151e: 0x0008, 0x151f: 0x0008, 0x1520: 0x0008, 0x1521: 0x0008, 0x1522: 0x0008, 0x1523: 0x0008, - 0x1524: 0x0008, 0x1525: 0x0008, 0x1526: 0x0008, 0x1527: 0x0008, 0x1528: 0x0008, 0x1529: 0x0008, - 0x152a: 0x0008, 0x152b: 0x0008, 0x152c: 0x0008, 0x152d: 0x0008, 0x152e: 0x0008, 0x152f: 0x0008, - 0x1530: 0x0008, 0x1531: 0x0008, 0x1532: 0x0008, 0x1533: 0x0008, 0x1534: 0x0008, 0x1535: 0x0008, - 0x1536: 0x0008, 0x1537: 0x0008, 0x1538: 0x0008, 0x1539: 0x0008, 0x153a: 0x0008, 0x153b: 0x0008, - 0x153c: 0x0008, 0x153d: 0x0008, 0x153e: 0x0008, 0x153f: 0x0008, - // Block 0x55, offset 0x1540 - 0x1540: 0x0008, 0x1541: 0x0008, 0x1542: 0x0008, 0x1543: 0x0008, 0x1544: 0x0008, 0x1545: 0x0008, - 0x1550: 0x0008, 0x1551: 0x0008, - 0x1552: 0x0008, 0x1553: 0x0008, 0x1554: 0x0008, 0x1555: 0x0008, 0x1556: 0x0008, 0x1557: 0x0008, - 0x1558: 0x0008, 0x1559: 0x0008, 0x155a: 0x0008, 0x155b: 0x0008, 0x155c: 0x0008, 0x155d: 0x0008, - 0x155e: 0x0008, 0x155f: 0x0008, 0x1560: 0x0008, 0x1561: 0x0008, 0x1562: 0x0008, 0x1563: 0x0008, - 0x1564: 0x0008, 0x1565: 0x0008, 0x1566: 0x0008, 0x1567: 0x0008, 0x1568: 0x0008, 0x1569: 0x0008, - 0x156a: 0x0008, 0x156b: 0x0008, 0x156c: 0x0008, 0x156d: 0x0008, 0x156e: 0x0008, 0x156f: 0x0008, - 0x1570: 0x0008, 0x1571: 0x0008, 0x1572: 0x0008, 0x1573: 0x0008, 0x1574: 0x0008, 0x1575: 0x0008, - 0x1576: 0x0008, 0x1577: 0x0008, 0x1578: 0x0008, 0x1579: 0x0008, 0x157a: 0x0008, 0x157b: 0x0008, - 0x157c: 0x0008, 0x157d: 0x0008, 0x157e: 0x0008, 0x157f: 0x0008, - // Block 0x56, offset 0x1580 - 0x1580: 0x0008, 0x1581: 0x0008, 0x1582: 0x0008, 0x1583: 0x0008, 0x1584: 0x0008, 0x1585: 0x0008, - 0x1588: 0x0008, 0x1589: 0x0008, 0x158a: 0x0008, 0x158b: 0x0008, - 0x158c: 0x0008, 0x158d: 0x0008, 0x158e: 0x0008, 0x158f: 0x0008, 0x1590: 0x0008, 0x1591: 0x0008, - 0x1592: 0x0008, 0x1594: 0x0008, 0x1596: 0x0008, - 0x159d: 0x0008, - 0x15a1: 0x0008, - 0x15a8: 0x0008, - 0x15b3: 0x0008, 0x15b4: 0x0008, - // Block 0x57, offset 0x15c0 - 0x15c4: 0x0008, - 0x15c7: 0x0008, - 0x15cc: 0x0008, 0x15ce: 0x0008, - 0x15d3: 0x0008, 0x15d4: 0x0008, 0x15d5: 0x0008, 0x15d7: 0x0008, - 0x15e3: 0x0008, - 0x15e4: 0x0008, 0x15e5: 0x0008, 0x15e6: 0x0008, 0x15e7: 0x0008, - // Block 0x58, offset 0x1600 - 0x1615: 0x0008, 0x1616: 0x0008, 0x1617: 0x0008, - 0x1621: 0x0008, - 0x1630: 0x0008, - 0x163f: 0x0008, - // Block 0x59, offset 0x1640 - 0x1674: 0x0008, 0x1675: 0x0008, - // Block 0x5a, offset 0x1680 - 0x1685: 0x0008, - 0x1686: 0x0008, 0x1687: 0x0008, - 0x169b: 0x0008, 0x169c: 0x0008, - // Block 0x5b, offset 0x16c0 - 0x16d0: 0x0008, - 0x16d5: 0x0008, - // Block 0x5c, offset 0x1700 - 0x172f: 0x0004, - 0x1730: 0x0004, 0x1731: 0x0004, - // Block 0x5d, offset 0x1740 - 0x177f: 0x0004, - // Block 0x5e, offset 0x1780 - 0x17a0: 0x0004, 0x17a1: 0x0004, 0x17a2: 0x0004, 0x17a3: 0x0004, - 0x17a4: 0x0004, 0x17a5: 0x0004, 0x17a6: 0x0004, 0x17a7: 0x0004, 0x17a8: 0x0004, 0x17a9: 0x0004, - 0x17aa: 0x0004, 0x17ab: 0x0004, 0x17ac: 0x0004, 0x17ad: 0x0004, 0x17ae: 0x0004, 0x17af: 0x0004, - 0x17b0: 0x0004, 0x17b1: 0x0004, 0x17b2: 0x0004, 0x17b3: 0x0004, 0x17b4: 0x0004, 0x17b5: 0x0004, - 0x17b6: 0x0004, 0x17b7: 0x0004, 0x17b8: 0x0004, 0x17b9: 0x0004, 0x17ba: 0x0004, 0x17bb: 0x0004, - 0x17bc: 0x0004, 0x17bd: 0x0004, 0x17be: 0x0004, 0x17bf: 0x0004, - // Block 0x5f, offset 0x17c0 - 0x17ea: 0x0004, 0x17eb: 0x0004, 0x17ec: 0x0004, 0x17ed: 0x0004, 0x17ee: 0x0004, 0x17ef: 0x0004, - 0x17f0: 0x0008, - 0x17fd: 0x0008, - // Block 0x60, offset 0x1800 - 0x1819: 0x0004, 0x181a: 0x0004, - // Block 0x61, offset 0x1840 - 0x1857: 0x0008, - 0x1859: 0x0008, - // Block 0x62, offset 0x1880 - 0x18af: 0x0004, - 0x18b0: 0x0004, 0x18b1: 0x0004, 0x18b2: 0x0004, 0x18b4: 0x0004, 0x18b5: 0x0004, - 0x18b6: 0x0004, 0x18b7: 0x0004, 0x18b8: 0x0004, 0x18b9: 0x0004, 0x18ba: 0x0004, 0x18bb: 0x0004, - 0x18bc: 0x0004, 0x18bd: 0x0004, - // Block 0x63, offset 0x18c0 - 0x18de: 0x0004, 0x18df: 0x0004, - // Block 0x64, offset 0x1900 - 0x1930: 0x0004, 0x1931: 0x0004, - // Block 0x65, offset 0x1940 - 0x1942: 0x0004, - 0x1946: 0x0004, 0x194b: 0x0004, - 0x1963: 0x0400, - 0x1964: 0x0400, 0x1965: 0x0004, 0x1966: 0x0004, 0x1967: 0x0400, - 0x196c: 0x0004, - // Block 0x66, offset 0x1980 - 0x1980: 0x0400, 0x1981: 0x0400, - 0x19b4: 0x0400, 0x19b5: 0x0400, - 0x19b6: 0x0400, 0x19b7: 0x0400, 0x19b8: 0x0400, 0x19b9: 0x0400, 0x19ba: 0x0400, 0x19bb: 0x0400, - 0x19bc: 0x0400, 0x19bd: 0x0400, 0x19be: 0x0400, 0x19bf: 0x0400, - // Block 0x67, offset 0x19c0 - 0x19c0: 0x0400, 0x19c1: 0x0400, 0x19c2: 0x0400, 0x19c3: 0x0400, 0x19c4: 0x0004, 0x19c5: 0x0004, - 0x19e0: 0x0004, 0x19e1: 0x0004, 0x19e2: 0x0004, 0x19e3: 0x0004, - 0x19e4: 0x0004, 0x19e5: 0x0004, 0x19e6: 0x0004, 0x19e7: 0x0004, 0x19e8: 0x0004, 0x19e9: 0x0004, - 0x19ea: 0x0004, 0x19eb: 0x0004, 0x19ec: 0x0004, 0x19ed: 0x0004, 0x19ee: 0x0004, 0x19ef: 0x0004, - 0x19f0: 0x0004, 0x19f1: 0x0004, - 0x19ff: 0x0004, - // Block 0x68, offset 0x1a00 - 0x1a26: 0x0004, 0x1a27: 0x0004, 0x1a28: 0x0004, 0x1a29: 0x0004, - 0x1a2a: 0x0004, 0x1a2b: 0x0004, 0x1a2c: 0x0004, 0x1a2d: 0x0004, - // Block 0x69, offset 0x1a40 - 0x1a47: 0x0004, 0x1a48: 0x0004, 0x1a49: 0x0004, 0x1a4a: 0x0004, 0x1a4b: 0x0004, - 0x1a4c: 0x0004, 0x1a4d: 0x0004, 0x1a4e: 0x0004, 0x1a4f: 0x0004, 0x1a50: 0x0004, 0x1a51: 0x0004, - 0x1a52: 0x0400, 0x1a53: 0x0400, - 0x1a60: 0x0010, 0x1a61: 0x0010, 0x1a62: 0x0010, 0x1a63: 0x0010, - 0x1a64: 0x0010, 0x1a65: 0x0010, 0x1a66: 0x0010, 0x1a67: 0x0010, 0x1a68: 0x0010, 0x1a69: 0x0010, - 0x1a6a: 0x0010, 0x1a6b: 0x0010, 0x1a6c: 0x0010, 0x1a6d: 0x0010, 0x1a6e: 0x0010, 0x1a6f: 0x0010, - 0x1a70: 0x0010, 0x1a71: 0x0010, 0x1a72: 0x0010, 0x1a73: 0x0010, 0x1a74: 0x0010, 0x1a75: 0x0010, - 0x1a76: 0x0010, 0x1a77: 0x0010, 0x1a78: 0x0010, 0x1a79: 0x0010, 0x1a7a: 0x0010, 0x1a7b: 0x0010, - 0x1a7c: 0x0010, - // Block 0x6a, offset 0x1a80 - 0x1a80: 0x0004, 0x1a81: 0x0004, 0x1a82: 0x0004, 0x1a83: 0x0400, - 0x1ab3: 0x0004, 0x1ab4: 0x0400, 0x1ab5: 0x0400, - 0x1ab6: 0x0004, 0x1ab7: 0x0004, 0x1ab8: 0x0004, 0x1ab9: 0x0004, 0x1aba: 0x0400, 0x1abb: 0x0400, - 0x1abc: 0x0004, 0x1abd: 0x0004, 0x1abe: 0x0400, 0x1abf: 0x0400, - // Block 0x6b, offset 0x1ac0 - 0x1ac0: 0x0400, - 0x1ae5: 0x0004, - // Block 0x6c, offset 0x1b00 - 0x1b29: 0x0004, - 0x1b2a: 0x0004, 0x1b2b: 0x0004, 0x1b2c: 0x0004, 0x1b2d: 0x0004, 0x1b2e: 0x0004, 0x1b2f: 0x0400, - 0x1b30: 0x0400, 0x1b31: 0x0004, 0x1b32: 0x0004, 0x1b33: 0x0400, 0x1b34: 0x0400, 0x1b35: 0x0004, - 0x1b36: 0x0004, - // Block 0x6d, offset 0x1b40 - 0x1b43: 0x0004, - 0x1b4c: 0x0004, 0x1b4d: 0x0400, - 0x1b7c: 0x0004, - // Block 0x6e, offset 0x1b80 - 0x1bb0: 0x0004, 0x1bb2: 0x0004, 0x1bb3: 0x0004, 0x1bb4: 0x0004, - 0x1bb7: 0x0004, 0x1bb8: 0x0004, - 0x1bbe: 0x0004, 0x1bbf: 0x0004, - // Block 0x6f, offset 0x1bc0 - 0x1bc1: 0x0004, - 0x1beb: 0x0400, 0x1bec: 0x0004, 0x1bed: 0x0004, 0x1bee: 0x0400, 0x1bef: 0x0400, - 0x1bf5: 0x0400, - 0x1bf6: 0x0004, - // Block 0x70, offset 0x1c00 - 0x1c23: 0x0400, - 0x1c24: 0x0400, 0x1c25: 0x0004, 0x1c26: 0x0400, 0x1c27: 0x0400, 0x1c28: 0x0004, 0x1c29: 0x0400, - 0x1c2a: 0x0400, 0x1c2c: 0x0400, 0x1c2d: 0x0004, - // Block 0x71, offset 0x1c40 - 0x1c40: 0x0040, 0x1c41: 0x0080, 0x1c42: 0x0080, 0x1c43: 0x0080, 0x1c44: 0x0080, 0x1c45: 0x0080, - 0x1c46: 0x0080, 0x1c47: 0x0080, 0x1c48: 0x0080, 0x1c49: 0x0080, 0x1c4a: 0x0080, 0x1c4b: 0x0080, - 0x1c4c: 0x0080, 0x1c4d: 0x0080, 0x1c4e: 0x0080, 0x1c4f: 0x0080, 0x1c50: 0x0080, 0x1c51: 0x0080, - 0x1c52: 0x0080, 0x1c53: 0x0080, 0x1c54: 0x0080, 0x1c55: 0x0080, 0x1c56: 0x0080, 0x1c57: 0x0080, - 0x1c58: 0x0080, 0x1c59: 0x0080, 0x1c5a: 0x0080, 0x1c5b: 0x0080, 0x1c5c: 0x0040, 0x1c5d: 0x0080, - 0x1c5e: 0x0080, 0x1c5f: 0x0080, 0x1c60: 0x0080, 0x1c61: 0x0080, 0x1c62: 0x0080, 0x1c63: 0x0080, - 0x1c64: 0x0080, 0x1c65: 0x0080, 0x1c66: 0x0080, 0x1c67: 0x0080, 0x1c68: 0x0080, 0x1c69: 0x0080, - 0x1c6a: 0x0080, 0x1c6b: 0x0080, 0x1c6c: 0x0080, 0x1c6d: 0x0080, 0x1c6e: 0x0080, 0x1c6f: 0x0080, - 0x1c70: 0x0080, 0x1c71: 0x0080, 0x1c72: 0x0080, 0x1c73: 0x0080, 0x1c74: 0x0080, 0x1c75: 0x0080, - 0x1c76: 0x0080, 0x1c77: 0x0080, 0x1c78: 0x0040, 0x1c79: 0x0080, 0x1c7a: 0x0080, 0x1c7b: 0x0080, - 0x1c7c: 0x0080, 0x1c7d: 0x0080, 0x1c7e: 0x0080, 0x1c7f: 0x0080, - // Block 0x72, offset 0x1c80 - 0x1c80: 0x0080, 0x1c81: 0x0080, 0x1c82: 0x0080, 0x1c83: 0x0080, 0x1c84: 0x0080, 0x1c85: 0x0080, - 0x1c86: 0x0080, 0x1c87: 0x0080, 0x1c88: 0x0080, 0x1c89: 0x0080, 0x1c8a: 0x0080, 0x1c8b: 0x0080, - 0x1c8c: 0x0080, 0x1c8d: 0x0080, 0x1c8e: 0x0080, 0x1c8f: 0x0080, 0x1c90: 0x0080, 0x1c91: 0x0080, - 0x1c92: 0x0080, 0x1c93: 0x0080, 0x1c94: 0x0040, 0x1c95: 0x0080, 0x1c96: 0x0080, 0x1c97: 0x0080, - 0x1c98: 0x0080, 0x1c99: 0x0080, 0x1c9a: 0x0080, 0x1c9b: 0x0080, 0x1c9c: 0x0080, 0x1c9d: 0x0080, - 0x1c9e: 0x0080, 0x1c9f: 0x0080, 0x1ca0: 0x0080, 0x1ca1: 0x0080, 0x1ca2: 0x0080, 0x1ca3: 0x0080, - 0x1ca4: 0x0080, 0x1ca5: 0x0080, 0x1ca6: 0x0080, 0x1ca7: 0x0080, 0x1ca8: 0x0080, 0x1ca9: 0x0080, - 0x1caa: 0x0080, 0x1cab: 0x0080, 0x1cac: 0x0080, 0x1cad: 0x0080, 0x1cae: 0x0080, 0x1caf: 0x0080, - 0x1cb0: 0x0040, 0x1cb1: 0x0080, 0x1cb2: 0x0080, 0x1cb3: 0x0080, 0x1cb4: 0x0080, 0x1cb5: 0x0080, - 0x1cb6: 0x0080, 0x1cb7: 0x0080, 0x1cb8: 0x0080, 0x1cb9: 0x0080, 0x1cba: 0x0080, 0x1cbb: 0x0080, - 0x1cbc: 0x0080, 0x1cbd: 0x0080, 0x1cbe: 0x0080, 0x1cbf: 0x0080, - // Block 0x73, offset 0x1cc0 - 0x1cc0: 0x0080, 0x1cc1: 0x0080, 0x1cc2: 0x0080, 0x1cc3: 0x0080, 0x1cc4: 0x0080, 0x1cc5: 0x0080, - 0x1cc6: 0x0080, 0x1cc7: 0x0080, 0x1cc8: 0x0080, 0x1cc9: 0x0080, 0x1cca: 0x0080, 0x1ccb: 0x0080, - 0x1ccc: 0x0040, 0x1ccd: 0x0080, 0x1cce: 0x0080, 0x1ccf: 0x0080, 0x1cd0: 0x0080, 0x1cd1: 0x0080, - 0x1cd2: 0x0080, 0x1cd3: 0x0080, 0x1cd4: 0x0080, 0x1cd5: 0x0080, 0x1cd6: 0x0080, 0x1cd7: 0x0080, - 0x1cd8: 0x0080, 0x1cd9: 0x0080, 0x1cda: 0x0080, 0x1cdb: 0x0080, 0x1cdc: 0x0080, 0x1cdd: 0x0080, - 0x1cde: 0x0080, 0x1cdf: 0x0080, 0x1ce0: 0x0080, 0x1ce1: 0x0080, 0x1ce2: 0x0080, 0x1ce3: 0x0080, - 0x1ce4: 0x0080, 0x1ce5: 0x0080, 0x1ce6: 0x0080, 0x1ce7: 0x0080, 0x1ce8: 0x0040, 0x1ce9: 0x0080, - 0x1cea: 0x0080, 0x1ceb: 0x0080, 0x1cec: 0x0080, 0x1ced: 0x0080, 0x1cee: 0x0080, 0x1cef: 0x0080, - 0x1cf0: 0x0080, 0x1cf1: 0x0080, 0x1cf2: 0x0080, 0x1cf3: 0x0080, 0x1cf4: 0x0080, 0x1cf5: 0x0080, - 0x1cf6: 0x0080, 0x1cf7: 0x0080, 0x1cf8: 0x0080, 0x1cf9: 0x0080, 0x1cfa: 0x0080, 0x1cfb: 0x0080, - 0x1cfc: 0x0080, 0x1cfd: 0x0080, 0x1cfe: 0x0080, 0x1cff: 0x0080, - // Block 0x74, offset 0x1d00 - 0x1d00: 0x0080, 0x1d01: 0x0080, 0x1d02: 0x0080, 0x1d03: 0x0080, 0x1d04: 0x0040, 0x1d05: 0x0080, - 0x1d06: 0x0080, 0x1d07: 0x0080, 0x1d08: 0x0080, 0x1d09: 0x0080, 0x1d0a: 0x0080, 0x1d0b: 0x0080, - 0x1d0c: 0x0080, 0x1d0d: 0x0080, 0x1d0e: 0x0080, 0x1d0f: 0x0080, 0x1d10: 0x0080, 0x1d11: 0x0080, - 0x1d12: 0x0080, 0x1d13: 0x0080, 0x1d14: 0x0080, 0x1d15: 0x0080, 0x1d16: 0x0080, 0x1d17: 0x0080, - 0x1d18: 0x0080, 0x1d19: 0x0080, 0x1d1a: 0x0080, 0x1d1b: 0x0080, 0x1d1c: 0x0080, 0x1d1d: 0x0080, - 0x1d1e: 0x0080, 0x1d1f: 0x0080, 0x1d20: 0x0040, 0x1d21: 0x0080, 0x1d22: 0x0080, 0x1d23: 0x0080, - 0x1d24: 0x0080, 0x1d25: 0x0080, 0x1d26: 0x0080, 0x1d27: 0x0080, 0x1d28: 0x0080, 0x1d29: 0x0080, - 0x1d2a: 0x0080, 0x1d2b: 0x0080, 0x1d2c: 0x0080, 0x1d2d: 0x0080, 0x1d2e: 0x0080, 0x1d2f: 0x0080, - 0x1d30: 0x0080, 0x1d31: 0x0080, 0x1d32: 0x0080, 0x1d33: 0x0080, 0x1d34: 0x0080, 0x1d35: 0x0080, - 0x1d36: 0x0080, 0x1d37: 0x0080, 0x1d38: 0x0080, 0x1d39: 0x0080, 0x1d3a: 0x0080, 0x1d3b: 0x0080, - 0x1d3c: 0x0040, 0x1d3d: 0x0080, 0x1d3e: 0x0080, 0x1d3f: 0x0080, - // Block 0x75, offset 0x1d40 - 0x1d40: 0x0080, 0x1d41: 0x0080, 0x1d42: 0x0080, 0x1d43: 0x0080, 0x1d44: 0x0080, 0x1d45: 0x0080, - 0x1d46: 0x0080, 0x1d47: 0x0080, 0x1d48: 0x0080, 0x1d49: 0x0080, 0x1d4a: 0x0080, 0x1d4b: 0x0080, - 0x1d4c: 0x0080, 0x1d4d: 0x0080, 0x1d4e: 0x0080, 0x1d4f: 0x0080, 0x1d50: 0x0080, 0x1d51: 0x0080, - 0x1d52: 0x0080, 0x1d53: 0x0080, 0x1d54: 0x0080, 0x1d55: 0x0080, 0x1d56: 0x0080, 0x1d57: 0x0080, - 0x1d58: 0x0040, 0x1d59: 0x0080, 0x1d5a: 0x0080, 0x1d5b: 0x0080, 0x1d5c: 0x0080, 0x1d5d: 0x0080, - 0x1d5e: 0x0080, 0x1d5f: 0x0080, 0x1d60: 0x0080, 0x1d61: 0x0080, 0x1d62: 0x0080, 0x1d63: 0x0080, - 0x1d64: 0x0080, 0x1d65: 0x0080, 0x1d66: 0x0080, 0x1d67: 0x0080, 0x1d68: 0x0080, 0x1d69: 0x0080, - 0x1d6a: 0x0080, 0x1d6b: 0x0080, 0x1d6c: 0x0080, 0x1d6d: 0x0080, 0x1d6e: 0x0080, 0x1d6f: 0x0080, - 0x1d70: 0x0080, 0x1d71: 0x0080, 0x1d72: 0x0080, 0x1d73: 0x0080, 0x1d74: 0x0040, 0x1d75: 0x0080, - 0x1d76: 0x0080, 0x1d77: 0x0080, 0x1d78: 0x0080, 0x1d79: 0x0080, 0x1d7a: 0x0080, 0x1d7b: 0x0080, - 0x1d7c: 0x0080, 0x1d7d: 0x0080, 0x1d7e: 0x0080, 0x1d7f: 0x0080, - // Block 0x76, offset 0x1d80 - 0x1d80: 0x0080, 0x1d81: 0x0080, 0x1d82: 0x0080, 0x1d83: 0x0080, 0x1d84: 0x0080, 0x1d85: 0x0080, - 0x1d86: 0x0080, 0x1d87: 0x0080, 0x1d88: 0x0080, 0x1d89: 0x0080, 0x1d8a: 0x0080, 0x1d8b: 0x0080, - 0x1d8c: 0x0080, 0x1d8d: 0x0080, 0x1d8e: 0x0080, 0x1d8f: 0x0080, 0x1d90: 0x0040, 0x1d91: 0x0080, - 0x1d92: 0x0080, 0x1d93: 0x0080, 0x1d94: 0x0080, 0x1d95: 0x0080, 0x1d96: 0x0080, 0x1d97: 0x0080, - 0x1d98: 0x0080, 0x1d99: 0x0080, 0x1d9a: 0x0080, 0x1d9b: 0x0080, 0x1d9c: 0x0080, 0x1d9d: 0x0080, - 0x1d9e: 0x0080, 0x1d9f: 0x0080, 0x1da0: 0x0080, 0x1da1: 0x0080, 0x1da2: 0x0080, 0x1da3: 0x0080, - 0x1da4: 0x0080, 0x1da5: 0x0080, 0x1da6: 0x0080, 0x1da7: 0x0080, 0x1da8: 0x0080, 0x1da9: 0x0080, - 0x1daa: 0x0080, 0x1dab: 0x0080, 0x1dac: 0x0040, 0x1dad: 0x0080, 0x1dae: 0x0080, 0x1daf: 0x0080, - 0x1db0: 0x0080, 0x1db1: 0x0080, 0x1db2: 0x0080, 0x1db3: 0x0080, 0x1db4: 0x0080, 0x1db5: 0x0080, - 0x1db6: 0x0080, 0x1db7: 0x0080, 0x1db8: 0x0080, 0x1db9: 0x0080, 0x1dba: 0x0080, 0x1dbb: 0x0080, - 0x1dbc: 0x0080, 0x1dbd: 0x0080, 0x1dbe: 0x0080, 0x1dbf: 0x0080, - // Block 0x77, offset 0x1dc0 - 0x1dc0: 0x0080, 0x1dc1: 0x0080, 0x1dc2: 0x0080, 0x1dc3: 0x0080, 0x1dc4: 0x0080, 0x1dc5: 0x0080, - 0x1dc6: 0x0080, 0x1dc7: 0x0080, 0x1dc8: 0x0040, 0x1dc9: 0x0080, 0x1dca: 0x0080, 0x1dcb: 0x0080, - 0x1dcc: 0x0080, 0x1dcd: 0x0080, 0x1dce: 0x0080, 0x1dcf: 0x0080, 0x1dd0: 0x0080, 0x1dd1: 0x0080, - 0x1dd2: 0x0080, 0x1dd3: 0x0080, 0x1dd4: 0x0080, 0x1dd5: 0x0080, 0x1dd6: 0x0080, 0x1dd7: 0x0080, - 0x1dd8: 0x0080, 0x1dd9: 0x0080, 0x1dda: 0x0080, 0x1ddb: 0x0080, 0x1ddc: 0x0080, 0x1ddd: 0x0080, - 0x1dde: 0x0080, 0x1ddf: 0x0080, 0x1de0: 0x0080, 0x1de1: 0x0080, 0x1de2: 0x0080, 0x1de3: 0x0080, - 0x1de4: 0x0040, 0x1de5: 0x0080, 0x1de6: 0x0080, 0x1de7: 0x0080, 0x1de8: 0x0080, 0x1de9: 0x0080, - 0x1dea: 0x0080, 0x1deb: 0x0080, 0x1dec: 0x0080, 0x1ded: 0x0080, 0x1dee: 0x0080, 0x1def: 0x0080, - 0x1df0: 0x0080, 0x1df1: 0x0080, 0x1df2: 0x0080, 0x1df3: 0x0080, 0x1df4: 0x0080, 0x1df5: 0x0080, - 0x1df6: 0x0080, 0x1df7: 0x0080, 0x1df8: 0x0080, 0x1df9: 0x0080, 0x1dfa: 0x0080, 0x1dfb: 0x0080, - 0x1dfc: 0x0080, 0x1dfd: 0x0080, 0x1dfe: 0x0080, 0x1dff: 0x0080, - // Block 0x78, offset 0x1e00 - 0x1e00: 0x0080, 0x1e01: 0x0080, 0x1e02: 0x0080, 0x1e03: 0x0080, 0x1e04: 0x0080, 0x1e05: 0x0080, - 0x1e06: 0x0080, 0x1e07: 0x0080, 0x1e08: 0x0040, 0x1e09: 0x0080, 0x1e0a: 0x0080, 0x1e0b: 0x0080, - 0x1e0c: 0x0080, 0x1e0d: 0x0080, 0x1e0e: 0x0080, 0x1e0f: 0x0080, 0x1e10: 0x0080, 0x1e11: 0x0080, - 0x1e12: 0x0080, 0x1e13: 0x0080, 0x1e14: 0x0080, 0x1e15: 0x0080, 0x1e16: 0x0080, 0x1e17: 0x0080, - 0x1e18: 0x0080, 0x1e19: 0x0080, 0x1e1a: 0x0080, 0x1e1b: 0x0080, 0x1e1c: 0x0080, 0x1e1d: 0x0080, - 0x1e1e: 0x0080, 0x1e1f: 0x0080, 0x1e20: 0x0080, 0x1e21: 0x0080, 0x1e22: 0x0080, 0x1e23: 0x0080, - 0x1e30: 0x1000, 0x1e31: 0x1000, 0x1e32: 0x1000, 0x1e33: 0x1000, 0x1e34: 0x1000, 0x1e35: 0x1000, - 0x1e36: 0x1000, 0x1e37: 0x1000, 0x1e38: 0x1000, 0x1e39: 0x1000, 0x1e3a: 0x1000, 0x1e3b: 0x1000, - 0x1e3c: 0x1000, 0x1e3d: 0x1000, 0x1e3e: 0x1000, 0x1e3f: 0x1000, - // Block 0x79, offset 0x1e40 - 0x1e40: 0x1000, 0x1e41: 0x1000, 0x1e42: 0x1000, 0x1e43: 0x1000, 0x1e44: 0x1000, 0x1e45: 0x1000, - 0x1e46: 0x1000, 0x1e4b: 0x0800, - 0x1e4c: 0x0800, 0x1e4d: 0x0800, 0x1e4e: 0x0800, 0x1e4f: 0x0800, 0x1e50: 0x0800, 0x1e51: 0x0800, - 0x1e52: 0x0800, 0x1e53: 0x0800, 0x1e54: 0x0800, 0x1e55: 0x0800, 0x1e56: 0x0800, 0x1e57: 0x0800, - 0x1e58: 0x0800, 0x1e59: 0x0800, 0x1e5a: 0x0800, 0x1e5b: 0x0800, 0x1e5c: 0x0800, 0x1e5d: 0x0800, - 0x1e5e: 0x0800, 0x1e5f: 0x0800, 0x1e60: 0x0800, 0x1e61: 0x0800, 0x1e62: 0x0800, 0x1e63: 0x0800, - 0x1e64: 0x0800, 0x1e65: 0x0800, 0x1e66: 0x0800, 0x1e67: 0x0800, 0x1e68: 0x0800, 0x1e69: 0x0800, - 0x1e6a: 0x0800, 0x1e6b: 0x0800, 0x1e6c: 0x0800, 0x1e6d: 0x0800, 0x1e6e: 0x0800, 0x1e6f: 0x0800, - 0x1e70: 0x0800, 0x1e71: 0x0800, 0x1e72: 0x0800, 0x1e73: 0x0800, 0x1e74: 0x0800, 0x1e75: 0x0800, - 0x1e76: 0x0800, 0x1e77: 0x0800, 0x1e78: 0x0800, 0x1e79: 0x0800, 0x1e7a: 0x0800, 0x1e7b: 0x0800, - // Block 0x7a, offset 0x1e80 - 0x1e9e: 0x0004, - // Block 0x7b, offset 0x1ec0 - 0x1ec0: 0x0004, 0x1ec1: 0x0004, 0x1ec2: 0x0004, 0x1ec3: 0x0004, 0x1ec4: 0x0004, 0x1ec5: 0x0004, - 0x1ec6: 0x0004, 0x1ec7: 0x0004, 0x1ec8: 0x0004, 0x1ec9: 0x0004, 0x1eca: 0x0004, 0x1ecb: 0x0004, - 0x1ecc: 0x0004, 0x1ecd: 0x0004, 0x1ece: 0x0004, 0x1ecf: 0x0004, - 0x1ee0: 0x0004, 0x1ee1: 0x0004, 0x1ee2: 0x0004, 0x1ee3: 0x0004, - 0x1ee4: 0x0004, 0x1ee5: 0x0004, 0x1ee6: 0x0004, 0x1ee7: 0x0004, 0x1ee8: 0x0004, 0x1ee9: 0x0004, - 0x1eea: 0x0004, 0x1eeb: 0x0004, 0x1eec: 0x0004, 0x1eed: 0x0004, 0x1eee: 0x0004, 0x1eef: 0x0004, - // Block 0x7c, offset 0x1f00 - 0x1f3f: 0x0002, - // Block 0x7d, offset 0x1f40 - 0x1f70: 0x0002, 0x1f71: 0x0002, 0x1f72: 0x0002, 0x1f73: 0x0002, 0x1f74: 0x0002, 0x1f75: 0x0002, - 0x1f76: 0x0002, 0x1f77: 0x0002, 0x1f78: 0x0002, 0x1f79: 0x0002, 0x1f7a: 0x0002, 0x1f7b: 0x0002, - // Block 0x7e, offset 0x1f80 - 0x1fbd: 0x0004, - // Block 0x7f, offset 0x1fc0 - 0x1fe0: 0x0004, - // Block 0x80, offset 0x2000 - 0x2036: 0x0004, 0x2037: 0x0004, 0x2038: 0x0004, 0x2039: 0x0004, 0x203a: 0x0004, - // Block 0x81, offset 0x2040 - 0x2041: 0x0004, 0x2042: 0x0004, 0x2043: 0x0004, 0x2045: 0x0004, - 0x2046: 0x0004, - 0x204c: 0x0004, 0x204d: 0x0004, 0x204e: 0x0004, 0x204f: 0x0004, - 0x2078: 0x0004, 0x2079: 0x0004, 0x207a: 0x0004, - 0x207f: 0x0004, - // Block 0x82, offset 0x2080 - 0x20a5: 0x0004, 0x20a6: 0x0004, - // Block 0x83, offset 0x20c0 - 0x20e4: 0x0004, 0x20e5: 0x0004, 0x20e6: 0x0004, 0x20e7: 0x0004, - // Block 0x84, offset 0x2100 - 0x212b: 0x0004, 0x212c: 0x0004, - // Block 0x85, offset 0x2140 - 0x217d: 0x0004, 0x217e: 0x0004, 0x217f: 0x0004, - // Block 0x86, offset 0x2180 - 0x2186: 0x0004, 0x2187: 0x0004, 0x2188: 0x0004, 0x2189: 0x0004, 0x218a: 0x0004, 0x218b: 0x0004, - 0x218c: 0x0004, 0x218d: 0x0004, 0x218e: 0x0004, 0x218f: 0x0004, 0x2190: 0x0004, - // Block 0x87, offset 0x21c0 - 0x21c2: 0x0004, 0x21c3: 0x0004, 0x21c4: 0x0004, 0x21c5: 0x0004, - // Block 0x88, offset 0x2200 - 0x2200: 0x0400, 0x2201: 0x0004, 0x2202: 0x0400, - 0x2238: 0x0004, 0x2239: 0x0004, 0x223a: 0x0004, 0x223b: 0x0004, - 0x223c: 0x0004, 0x223d: 0x0004, 0x223e: 0x0004, 0x223f: 0x0004, - // Block 0x89, offset 0x2240 - 0x2240: 0x0004, 0x2241: 0x0004, 0x2242: 0x0004, 0x2243: 0x0004, 0x2244: 0x0004, 0x2245: 0x0004, - 0x2246: 0x0004, - 0x2270: 0x0004, 0x2273: 0x0004, 0x2274: 0x0004, - 0x227f: 0x0004, - // Block 0x8a, offset 0x2280 - 0x2280: 0x0004, 0x2281: 0x0004, 0x2282: 0x0400, - 0x22b0: 0x0400, 0x22b1: 0x0400, 0x22b2: 0x0400, 0x22b3: 0x0004, 0x22b4: 0x0004, 0x22b5: 0x0004, - 0x22b6: 0x0004, 0x22b7: 0x0400, 0x22b8: 0x0400, 0x22b9: 0x0004, 0x22ba: 0x0004, - 0x22bd: 0x0100, - // Block 0x8b, offset 0x22c0 - 0x22c2: 0x0004, - 0x22cd: 0x0100, - // Block 0x8c, offset 0x2300 - 0x2300: 0x0004, 0x2301: 0x0004, 0x2302: 0x0004, - 0x2327: 0x0004, 0x2328: 0x0004, 0x2329: 0x0004, - 0x232a: 0x0004, 0x232b: 0x0004, 0x232c: 0x0400, 0x232d: 0x0004, 0x232e: 0x0004, 0x232f: 0x0004, - 0x2330: 0x0004, 0x2331: 0x0004, 0x2332: 0x0004, 0x2333: 0x0004, 0x2334: 0x0004, - // Block 0x8d, offset 0x2340 - 0x2345: 0x0400, - 0x2346: 0x0400, - 0x2373: 0x0004, - // Block 0x8e, offset 0x2380 - 0x2380: 0x0004, 0x2381: 0x0004, 0x2382: 0x0400, - 0x23b3: 0x0400, 0x23b4: 0x0400, 0x23b5: 0x0400, - 0x23b6: 0x0004, 0x23b7: 0x0004, 0x23b8: 0x0004, 0x23b9: 0x0004, 0x23ba: 0x0004, 0x23bb: 0x0004, - 0x23bc: 0x0004, 0x23bd: 0x0004, 0x23be: 0x0004, 0x23bf: 0x0400, - // Block 0x8f, offset 0x23c0 - 0x23c0: 0x0400, 0x23c2: 0x0100, 0x23c3: 0x0100, - 0x23c9: 0x0004, 0x23ca: 0x0004, 0x23cb: 0x0004, - 0x23cc: 0x0004, 0x23ce: 0x0400, 0x23cf: 0x0004, - // Block 0x90, offset 0x2400 - 0x242c: 0x0400, 0x242d: 0x0400, 0x242e: 0x0400, 0x242f: 0x0004, - 0x2430: 0x0004, 0x2431: 0x0004, 0x2432: 0x0400, 0x2433: 0x0400, 0x2434: 0x0004, 0x2435: 0x0400, - 0x2436: 0x0004, 0x2437: 0x0004, - 0x243e: 0x0004, - // Block 0x91, offset 0x2440 - 0x2441: 0x0004, - // Block 0x92, offset 0x2480 - 0x249f: 0x0004, 0x24a0: 0x0400, 0x24a1: 0x0400, 0x24a2: 0x0400, 0x24a3: 0x0004, - 0x24a4: 0x0004, 0x24a5: 0x0004, 0x24a6: 0x0004, 0x24a7: 0x0004, 0x24a8: 0x0004, 0x24a9: 0x0004, - 0x24aa: 0x0004, - // Block 0x93, offset 0x24c0 - 0x24c0: 0x0004, 0x24c1: 0x0400, 0x24c2: 0x0400, 0x24c3: 0x0400, 0x24c4: 0x0400, - 0x24c7: 0x0400, 0x24c8: 0x0400, 0x24cb: 0x0400, - 0x24cc: 0x0400, 0x24cd: 0x0400, - 0x24d7: 0x0004, - 0x24e2: 0x0400, 0x24e3: 0x0400, - 0x24e6: 0x0004, 0x24e7: 0x0004, 0x24e8: 0x0004, 0x24e9: 0x0004, - 0x24ea: 0x0004, 0x24eb: 0x0004, 0x24ec: 0x0004, - 0x24f0: 0x0004, 0x24f1: 0x0004, 0x24f2: 0x0004, 0x24f3: 0x0004, 0x24f4: 0x0004, - // Block 0x94, offset 0x2500 - 0x2535: 0x0400, - 0x2536: 0x0400, 0x2537: 0x0400, 0x2538: 0x0004, 0x2539: 0x0004, 0x253a: 0x0004, 0x253b: 0x0004, - 0x253c: 0x0004, 0x253d: 0x0004, 0x253e: 0x0004, 0x253f: 0x0004, - // Block 0x95, offset 0x2540 - 0x2540: 0x0400, 0x2541: 0x0400, 0x2542: 0x0004, 0x2543: 0x0004, 0x2544: 0x0004, 0x2545: 0x0400, - 0x2546: 0x0004, - 0x255e: 0x0004, - // Block 0x96, offset 0x2580 - 0x25b0: 0x0004, 0x25b1: 0x0400, 0x25b2: 0x0400, 0x25b3: 0x0004, 0x25b4: 0x0004, 0x25b5: 0x0004, - 0x25b6: 0x0004, 0x25b7: 0x0004, 0x25b8: 0x0004, 0x25b9: 0x0400, 0x25ba: 0x0004, 0x25bb: 0x0400, - 0x25bc: 0x0400, 0x25bd: 0x0004, 0x25be: 0x0400, 0x25bf: 0x0004, - // Block 0x97, offset 0x25c0 - 0x25c0: 0x0004, 0x25c1: 0x0400, 0x25c2: 0x0004, 0x25c3: 0x0004, - // Block 0x98, offset 0x2600 - 0x262f: 0x0004, - 0x2630: 0x0400, 0x2631: 0x0400, 0x2632: 0x0004, 0x2633: 0x0004, 0x2634: 0x0004, 0x2635: 0x0004, - 0x2638: 0x0400, 0x2639: 0x0400, 0x263a: 0x0400, 0x263b: 0x0400, - 0x263c: 0x0004, 0x263d: 0x0004, 0x263e: 0x0400, 0x263f: 0x0004, - // Block 0x99, offset 0x2640 - 0x2640: 0x0004, - 0x265c: 0x0004, 0x265d: 0x0004, - // Block 0x9a, offset 0x2680 - 0x26b0: 0x0400, 0x26b1: 0x0400, 0x26b2: 0x0400, 0x26b3: 0x0004, 0x26b4: 0x0004, 0x26b5: 0x0004, - 0x26b6: 0x0004, 0x26b7: 0x0004, 0x26b8: 0x0004, 0x26b9: 0x0004, 0x26ba: 0x0004, 0x26bb: 0x0400, - 0x26bc: 0x0400, 0x26bd: 0x0004, 0x26be: 0x0400, 0x26bf: 0x0004, - // Block 0x9b, offset 0x26c0 - 0x26c0: 0x0004, - // Block 0x9c, offset 0x2700 - 0x272b: 0x0004, 0x272c: 0x0400, 0x272d: 0x0004, 0x272e: 0x0400, 0x272f: 0x0400, - 0x2730: 0x0004, 0x2731: 0x0004, 0x2732: 0x0004, 0x2733: 0x0004, 0x2734: 0x0004, 0x2735: 0x0004, - 0x2736: 0x0400, 0x2737: 0x0004, - // Block 0x9d, offset 0x2740 - 0x275d: 0x0004, - 0x275e: 0x0004, 0x275f: 0x0004, 0x2762: 0x0004, 0x2763: 0x0004, - 0x2764: 0x0004, 0x2765: 0x0004, 0x2766: 0x0400, 0x2767: 0x0004, 0x2768: 0x0004, 0x2769: 0x0004, - 0x276a: 0x0004, 0x276b: 0x0004, - // Block 0x9e, offset 0x2780 - 0x27ac: 0x0400, 0x27ad: 0x0400, 0x27ae: 0x0400, 0x27af: 0x0004, - 0x27b0: 0x0004, 0x27b1: 0x0004, 0x27b2: 0x0004, 0x27b3: 0x0004, 0x27b4: 0x0004, 0x27b5: 0x0004, - 0x27b6: 0x0004, 0x27b7: 0x0004, 0x27b8: 0x0400, 0x27b9: 0x0004, 0x27ba: 0x0004, - // Block 0x9f, offset 0x27c0 - 0x27f0: 0x0004, 0x27f1: 0x0400, 0x27f2: 0x0400, 0x27f3: 0x0400, 0x27f4: 0x0400, 0x27f5: 0x0400, - 0x27f7: 0x0400, 0x27f8: 0x0400, 0x27fb: 0x0004, - 0x27fc: 0x0004, 0x27fd: 0x0400, 0x27fe: 0x0004, 0x27ff: 0x0100, - // Block 0xa0, offset 0x2800 - 0x2800: 0x0400, 0x2801: 0x0100, 0x2802: 0x0400, 0x2803: 0x0004, - // Block 0xa1, offset 0x2840 - 0x2851: 0x0400, - 0x2852: 0x0400, 0x2853: 0x0400, 0x2854: 0x0004, 0x2855: 0x0004, 0x2856: 0x0004, 0x2857: 0x0004, - 0x285a: 0x0004, 0x285b: 0x0004, 0x285c: 0x0400, 0x285d: 0x0400, - 0x285e: 0x0400, 0x285f: 0x0400, 0x2860: 0x0004, - 0x2864: 0x0400, - // Block 0xa2, offset 0x2880 - 0x2881: 0x0004, 0x2882: 0x0004, 0x2883: 0x0004, 0x2884: 0x0004, 0x2885: 0x0004, - 0x2886: 0x0004, 0x2887: 0x0004, 0x2888: 0x0004, 0x2889: 0x0004, 0x288a: 0x0004, - 0x28b3: 0x0004, 0x28b4: 0x0004, 0x28b5: 0x0004, - 0x28b6: 0x0004, 0x28b7: 0x0004, 0x28b8: 0x0004, 0x28b9: 0x0400, 0x28ba: 0x0100, 0x28bb: 0x0004, - 0x28bc: 0x0004, 0x28bd: 0x0004, 0x28be: 0x0004, - // Block 0xa3, offset 0x28c0 - 0x28c7: 0x0004, - 0x28d1: 0x0004, - 0x28d2: 0x0004, 0x28d3: 0x0004, 0x28d4: 0x0004, 0x28d5: 0x0004, 0x28d6: 0x0004, 0x28d7: 0x0400, - 0x28d8: 0x0400, 0x28d9: 0x0004, 0x28da: 0x0004, 0x28db: 0x0004, - // Block 0xa4, offset 0x2900 - 0x2904: 0x0100, 0x2905: 0x0100, - 0x2906: 0x0100, 0x2907: 0x0100, 0x2908: 0x0100, 0x2909: 0x0100, 0x290a: 0x0004, 0x290b: 0x0004, - 0x290c: 0x0004, 0x290d: 0x0004, 0x290e: 0x0004, 0x290f: 0x0004, 0x2910: 0x0004, 0x2911: 0x0004, - 0x2912: 0x0004, 0x2913: 0x0004, 0x2914: 0x0004, 0x2915: 0x0004, 0x2916: 0x0004, 0x2917: 0x0400, - 0x2918: 0x0004, 0x2919: 0x0004, - // Block 0xa5, offset 0x2940 - 0x296f: 0x0400, - 0x2970: 0x0004, 0x2971: 0x0004, 0x2972: 0x0004, 0x2973: 0x0004, 0x2974: 0x0004, 0x2975: 0x0004, - 0x2976: 0x0004, 0x2978: 0x0004, 0x2979: 0x0004, 0x297a: 0x0004, 0x297b: 0x0004, - 0x297c: 0x0004, 0x297d: 0x0004, 0x297e: 0x0400, 0x297f: 0x0004, - // Block 0xa6, offset 0x2980 - 0x2992: 0x0004, 0x2993: 0x0004, 0x2994: 0x0004, 0x2995: 0x0004, 0x2996: 0x0004, 0x2997: 0x0004, - 0x2998: 0x0004, 0x2999: 0x0004, 0x299a: 0x0004, 0x299b: 0x0004, 0x299c: 0x0004, 0x299d: 0x0004, - 0x299e: 0x0004, 0x299f: 0x0004, 0x29a0: 0x0004, 0x29a1: 0x0004, 0x29a2: 0x0004, 0x29a3: 0x0004, - 0x29a4: 0x0004, 0x29a5: 0x0004, 0x29a6: 0x0004, 0x29a7: 0x0004, 0x29a9: 0x0400, - 0x29aa: 0x0004, 0x29ab: 0x0004, 0x29ac: 0x0004, 0x29ad: 0x0004, 0x29ae: 0x0004, 0x29af: 0x0004, - 0x29b0: 0x0004, 0x29b1: 0x0400, 0x29b2: 0x0004, 0x29b3: 0x0004, 0x29b4: 0x0400, 0x29b5: 0x0004, - 0x29b6: 0x0004, - // Block 0xa7, offset 0x29c0 - 0x29f1: 0x0004, 0x29f2: 0x0004, 0x29f3: 0x0004, 0x29f4: 0x0004, 0x29f5: 0x0004, - 0x29f6: 0x0004, 0x29fa: 0x0004, - 0x29fc: 0x0004, 0x29fd: 0x0004, 0x29ff: 0x0004, - // Block 0xa8, offset 0x2a00 - 0x2a00: 0x0004, 0x2a01: 0x0004, 0x2a02: 0x0004, 0x2a03: 0x0004, 0x2a04: 0x0004, 0x2a05: 0x0004, - 0x2a06: 0x0100, 0x2a07: 0x0004, - // Block 0xa9, offset 0x2a40 - 0x2a4a: 0x0400, 0x2a4b: 0x0400, - 0x2a4c: 0x0400, 0x2a4d: 0x0400, 0x2a4e: 0x0400, 0x2a50: 0x0004, 0x2a51: 0x0004, - 0x2a53: 0x0400, 0x2a54: 0x0400, 0x2a55: 0x0004, 0x2a56: 0x0400, 0x2a57: 0x0004, - // Block 0xaa, offset 0x2a80 - 0x2ab3: 0x0004, 0x2ab4: 0x0004, 0x2ab5: 0x0400, - 0x2ab6: 0x0400, - // Block 0xab, offset 0x2ac0 - 0x2ac0: 0x0004, 0x2ac1: 0x0004, 0x2ac2: 0x0100, 0x2ac3: 0x0400, - 0x2af4: 0x0400, 0x2af5: 0x0400, - 0x2af6: 0x0004, 0x2af7: 0x0004, 0x2af8: 0x0004, 0x2af9: 0x0004, 0x2afa: 0x0004, - 0x2afe: 0x0400, 0x2aff: 0x0400, - // Block 0xac, offset 0x2b00 - 0x2b00: 0x0004, 0x2b01: 0x0400, 0x2b02: 0x0004, - // Block 0xad, offset 0x2b40 - 0x2b70: 0x0002, 0x2b71: 0x0002, 0x2b72: 0x0002, 0x2b73: 0x0002, 0x2b74: 0x0002, 0x2b75: 0x0002, - 0x2b76: 0x0002, 0x2b77: 0x0002, 0x2b78: 0x0002, 0x2b79: 0x0002, 0x2b7a: 0x0002, 0x2b7b: 0x0002, - 0x2b7c: 0x0002, 0x2b7d: 0x0002, 0x2b7e: 0x0002, 0x2b7f: 0x0002, - // Block 0xae, offset 0x2b80 - 0x2b80: 0x0004, - 0x2b87: 0x0004, 0x2b88: 0x0004, 0x2b89: 0x0004, 0x2b8a: 0x0004, 0x2b8b: 0x0004, - 0x2b8c: 0x0004, 0x2b8d: 0x0004, 0x2b8e: 0x0004, 0x2b8f: 0x0004, 0x2b90: 0x0004, 0x2b91: 0x0004, - 0x2b92: 0x0004, 0x2b93: 0x0004, 0x2b94: 0x0004, 0x2b95: 0x0004, - // Block 0xaf, offset 0x2bc0 - 0x2bf0: 0x0004, 0x2bf1: 0x0004, 0x2bf2: 0x0004, 0x2bf3: 0x0004, 0x2bf4: 0x0004, - // Block 0xb0, offset 0x2c00 - 0x2c30: 0x0004, 0x2c31: 0x0004, 0x2c32: 0x0004, 0x2c33: 0x0004, 0x2c34: 0x0004, 0x2c35: 0x0004, - 0x2c36: 0x0004, - // Block 0xb1, offset 0x2c40 - 0x2c4f: 0x0004, 0x2c51: 0x0400, - 0x2c52: 0x0400, 0x2c53: 0x0400, 0x2c54: 0x0400, 0x2c55: 0x0400, 0x2c56: 0x0400, 0x2c57: 0x0400, - 0x2c58: 0x0400, 0x2c59: 0x0400, 0x2c5a: 0x0400, 0x2c5b: 0x0400, 0x2c5c: 0x0400, 0x2c5d: 0x0400, - 0x2c5e: 0x0400, 0x2c5f: 0x0400, 0x2c60: 0x0400, 0x2c61: 0x0400, 0x2c62: 0x0400, 0x2c63: 0x0400, - 0x2c64: 0x0400, 0x2c65: 0x0400, 0x2c66: 0x0400, 0x2c67: 0x0400, 0x2c68: 0x0400, 0x2c69: 0x0400, - 0x2c6a: 0x0400, 0x2c6b: 0x0400, 0x2c6c: 0x0400, 0x2c6d: 0x0400, 0x2c6e: 0x0400, 0x2c6f: 0x0400, - 0x2c70: 0x0400, 0x2c71: 0x0400, 0x2c72: 0x0400, 0x2c73: 0x0400, 0x2c74: 0x0400, 0x2c75: 0x0400, - 0x2c76: 0x0400, 0x2c77: 0x0400, 0x2c78: 0x0400, 0x2c79: 0x0400, 0x2c7a: 0x0400, 0x2c7b: 0x0400, - 0x2c7c: 0x0400, 0x2c7d: 0x0400, 0x2c7e: 0x0400, 0x2c7f: 0x0400, - // Block 0xb2, offset 0x2c80 - 0x2c80: 0x0400, 0x2c81: 0x0400, 0x2c82: 0x0400, 0x2c83: 0x0400, 0x2c84: 0x0400, 0x2c85: 0x0400, - 0x2c86: 0x0400, 0x2c87: 0x0400, - 0x2c8f: 0x0004, 0x2c90: 0x0004, 0x2c91: 0x0004, - 0x2c92: 0x0004, - // Block 0xb3, offset 0x2cc0 - 0x2ce4: 0x0004, - 0x2cf0: 0x0400, 0x2cf1: 0x0400, - // Block 0xb4, offset 0x2d00 - 0x2d1d: 0x0004, - 0x2d1e: 0x0004, 0x2d20: 0x0002, 0x2d21: 0x0002, 0x2d22: 0x0002, 0x2d23: 0x0002, - // Block 0xb5, offset 0x2d40 - 0x2d40: 0x0004, 0x2d41: 0x0004, 0x2d42: 0x0004, 0x2d43: 0x0004, 0x2d44: 0x0004, 0x2d45: 0x0004, - 0x2d46: 0x0004, 0x2d47: 0x0004, 0x2d48: 0x0004, 0x2d49: 0x0004, 0x2d4a: 0x0004, 0x2d4b: 0x0004, - 0x2d4c: 0x0004, 0x2d4d: 0x0004, 0x2d4e: 0x0004, 0x2d4f: 0x0004, 0x2d50: 0x0004, 0x2d51: 0x0004, - 0x2d52: 0x0004, 0x2d53: 0x0004, 0x2d54: 0x0004, 0x2d55: 0x0004, 0x2d56: 0x0004, 0x2d57: 0x0004, - 0x2d58: 0x0004, 0x2d59: 0x0004, 0x2d5a: 0x0004, 0x2d5b: 0x0004, 0x2d5c: 0x0004, 0x2d5d: 0x0004, - 0x2d5e: 0x0004, 0x2d5f: 0x0004, 0x2d60: 0x0004, 0x2d61: 0x0004, 0x2d62: 0x0004, 0x2d63: 0x0004, - 0x2d64: 0x0004, 0x2d65: 0x0004, 0x2d66: 0x0004, 0x2d67: 0x0004, 0x2d68: 0x0004, 0x2d69: 0x0004, - 0x2d6a: 0x0004, 0x2d6b: 0x0004, 0x2d6c: 0x0004, 0x2d6d: 0x0004, - 0x2d70: 0x0004, 0x2d71: 0x0004, 0x2d72: 0x0004, 0x2d73: 0x0004, 0x2d74: 0x0004, 0x2d75: 0x0004, - 0x2d76: 0x0004, 0x2d77: 0x0004, 0x2d78: 0x0004, 0x2d79: 0x0004, 0x2d7a: 0x0004, 0x2d7b: 0x0004, - 0x2d7c: 0x0004, 0x2d7d: 0x0004, 0x2d7e: 0x0004, 0x2d7f: 0x0004, - // Block 0xb6, offset 0x2d80 - 0x2d80: 0x0004, 0x2d81: 0x0004, 0x2d82: 0x0004, 0x2d83: 0x0004, 0x2d84: 0x0004, 0x2d85: 0x0004, - 0x2d86: 0x0004, - // Block 0xb7, offset 0x2dc0 - 0x2de5: 0x0004, 0x2de6: 0x0400, 0x2de7: 0x0004, 0x2de8: 0x0004, 0x2de9: 0x0004, - 0x2ded: 0x0400, 0x2dee: 0x0004, 0x2def: 0x0004, - 0x2df0: 0x0004, 0x2df1: 0x0004, 0x2df2: 0x0004, 0x2df3: 0x0002, 0x2df4: 0x0002, 0x2df5: 0x0002, - 0x2df6: 0x0002, 0x2df7: 0x0002, 0x2df8: 0x0002, 0x2df9: 0x0002, 0x2dfa: 0x0002, 0x2dfb: 0x0004, - 0x2dfc: 0x0004, 0x2dfd: 0x0004, 0x2dfe: 0x0004, 0x2dff: 0x0004, - // Block 0xb8, offset 0x2e00 - 0x2e00: 0x0004, 0x2e01: 0x0004, 0x2e02: 0x0004, 0x2e05: 0x0004, - 0x2e06: 0x0004, 0x2e07: 0x0004, 0x2e08: 0x0004, 0x2e09: 0x0004, 0x2e0a: 0x0004, 0x2e0b: 0x0004, - 0x2e2a: 0x0004, 0x2e2b: 0x0004, 0x2e2c: 0x0004, 0x2e2d: 0x0004, - // Block 0xb9, offset 0x2e40 - 0x2e42: 0x0004, 0x2e43: 0x0004, 0x2e44: 0x0004, - // Block 0xba, offset 0x2e80 - 0x2e80: 0x0004, 0x2e81: 0x0004, 0x2e82: 0x0004, 0x2e83: 0x0004, 0x2e84: 0x0004, 0x2e85: 0x0004, - 0x2e86: 0x0004, 0x2e87: 0x0004, 0x2e88: 0x0004, 0x2e89: 0x0004, 0x2e8a: 0x0004, 0x2e8b: 0x0004, - 0x2e8c: 0x0004, 0x2e8d: 0x0004, 0x2e8e: 0x0004, 0x2e8f: 0x0004, 0x2e90: 0x0004, 0x2e91: 0x0004, - 0x2e92: 0x0004, 0x2e93: 0x0004, 0x2e94: 0x0004, 0x2e95: 0x0004, 0x2e96: 0x0004, 0x2e97: 0x0004, - 0x2e98: 0x0004, 0x2e99: 0x0004, 0x2e9a: 0x0004, 0x2e9b: 0x0004, 0x2e9c: 0x0004, 0x2e9d: 0x0004, - 0x2e9e: 0x0004, 0x2e9f: 0x0004, 0x2ea0: 0x0004, 0x2ea1: 0x0004, 0x2ea2: 0x0004, 0x2ea3: 0x0004, - 0x2ea4: 0x0004, 0x2ea5: 0x0004, 0x2ea6: 0x0004, 0x2ea7: 0x0004, 0x2ea8: 0x0004, 0x2ea9: 0x0004, - 0x2eaa: 0x0004, 0x2eab: 0x0004, 0x2eac: 0x0004, 0x2ead: 0x0004, 0x2eae: 0x0004, 0x2eaf: 0x0004, - 0x2eb0: 0x0004, 0x2eb1: 0x0004, 0x2eb2: 0x0004, 0x2eb3: 0x0004, 0x2eb4: 0x0004, 0x2eb5: 0x0004, - 0x2eb6: 0x0004, 0x2ebb: 0x0004, - 0x2ebc: 0x0004, 0x2ebd: 0x0004, 0x2ebe: 0x0004, 0x2ebf: 0x0004, - // Block 0xbb, offset 0x2ec0 - 0x2ec0: 0x0004, 0x2ec1: 0x0004, 0x2ec2: 0x0004, 0x2ec3: 0x0004, 0x2ec4: 0x0004, 0x2ec5: 0x0004, - 0x2ec6: 0x0004, 0x2ec7: 0x0004, 0x2ec8: 0x0004, 0x2ec9: 0x0004, 0x2eca: 0x0004, 0x2ecb: 0x0004, - 0x2ecc: 0x0004, 0x2ecd: 0x0004, 0x2ece: 0x0004, 0x2ecf: 0x0004, 0x2ed0: 0x0004, 0x2ed1: 0x0004, - 0x2ed2: 0x0004, 0x2ed3: 0x0004, 0x2ed4: 0x0004, 0x2ed5: 0x0004, 0x2ed6: 0x0004, 0x2ed7: 0x0004, - 0x2ed8: 0x0004, 0x2ed9: 0x0004, 0x2eda: 0x0004, 0x2edb: 0x0004, 0x2edc: 0x0004, 0x2edd: 0x0004, - 0x2ede: 0x0004, 0x2edf: 0x0004, 0x2ee0: 0x0004, 0x2ee1: 0x0004, 0x2ee2: 0x0004, 0x2ee3: 0x0004, - 0x2ee4: 0x0004, 0x2ee5: 0x0004, 0x2ee6: 0x0004, 0x2ee7: 0x0004, 0x2ee8: 0x0004, 0x2ee9: 0x0004, - 0x2eea: 0x0004, 0x2eeb: 0x0004, 0x2eec: 0x0004, - 0x2ef5: 0x0004, - // Block 0xbc, offset 0x2f00 - 0x2f04: 0x0004, - 0x2f1b: 0x0004, 0x2f1c: 0x0004, 0x2f1d: 0x0004, - 0x2f1e: 0x0004, 0x2f1f: 0x0004, 0x2f21: 0x0004, 0x2f22: 0x0004, 0x2f23: 0x0004, - 0x2f24: 0x0004, 0x2f25: 0x0004, 0x2f26: 0x0004, 0x2f27: 0x0004, 0x2f28: 0x0004, 0x2f29: 0x0004, - 0x2f2a: 0x0004, 0x2f2b: 0x0004, 0x2f2c: 0x0004, 0x2f2d: 0x0004, 0x2f2e: 0x0004, 0x2f2f: 0x0004, - // Block 0xbd, offset 0x2f40 - 0x2f40: 0x0004, 0x2f41: 0x0004, 0x2f42: 0x0004, 0x2f43: 0x0004, 0x2f44: 0x0004, 0x2f45: 0x0004, - 0x2f46: 0x0004, 0x2f48: 0x0004, 0x2f49: 0x0004, 0x2f4a: 0x0004, 0x2f4b: 0x0004, - 0x2f4c: 0x0004, 0x2f4d: 0x0004, 0x2f4e: 0x0004, 0x2f4f: 0x0004, 0x2f50: 0x0004, 0x2f51: 0x0004, - 0x2f52: 0x0004, 0x2f53: 0x0004, 0x2f54: 0x0004, 0x2f55: 0x0004, 0x2f56: 0x0004, 0x2f57: 0x0004, - 0x2f58: 0x0004, 0x2f5b: 0x0004, 0x2f5c: 0x0004, 0x2f5d: 0x0004, - 0x2f5e: 0x0004, 0x2f5f: 0x0004, 0x2f60: 0x0004, 0x2f61: 0x0004, 0x2f63: 0x0004, - 0x2f64: 0x0004, 0x2f66: 0x0004, 0x2f67: 0x0004, 0x2f68: 0x0004, 0x2f69: 0x0004, - 0x2f6a: 0x0004, - // Block 0xbe, offset 0x2f80 - 0x2f8f: 0x0004, - // Block 0xbf, offset 0x2fc0 - 0x2fee: 0x0004, - // Block 0xc0, offset 0x3000 - 0x302c: 0x0004, 0x302d: 0x0004, 0x302e: 0x0004, 0x302f: 0x0004, - // Block 0xc1, offset 0x3040 - 0x3050: 0x0004, 0x3051: 0x0004, - 0x3052: 0x0004, 0x3053: 0x0004, 0x3054: 0x0004, 0x3055: 0x0004, 0x3056: 0x0004, - // Block 0xc2, offset 0x3080 - 0x3084: 0x0004, 0x3085: 0x0004, - 0x3086: 0x0004, 0x3087: 0x0004, 0x3088: 0x0004, 0x3089: 0x0004, 0x308a: 0x0004, - // Block 0xc3, offset 0x30c0 - 0x30cd: 0x0008, 0x30ce: 0x0008, 0x30cf: 0x0008, - 0x30ef: 0x0008, - // Block 0xc4, offset 0x3100 - 0x312c: 0x0008, 0x312d: 0x0008, 0x312e: 0x0008, 0x312f: 0x0008, - 0x3130: 0x0008, 0x3131: 0x0008, - 0x313e: 0x0008, 0x313f: 0x0008, - // Block 0xc5, offset 0x3140 - 0x314e: 0x0008, 0x3151: 0x0008, - 0x3152: 0x0008, 0x3153: 0x0008, 0x3154: 0x0008, 0x3155: 0x0008, 0x3156: 0x0008, 0x3157: 0x0008, - 0x3158: 0x0008, 0x3159: 0x0008, 0x315a: 0x0008, - 0x316d: 0x0008, 0x316e: 0x0008, 0x316f: 0x0008, - 0x3170: 0x0008, 0x3171: 0x0008, 0x3172: 0x0008, 0x3173: 0x0008, 0x3174: 0x0008, 0x3175: 0x0008, - 0x3176: 0x0008, 0x3177: 0x0008, 0x3178: 0x0008, 0x3179: 0x0008, 0x317a: 0x0008, 0x317b: 0x0008, - 0x317c: 0x0008, 0x317d: 0x0008, 0x317e: 0x0008, 0x317f: 0x0008, - // Block 0xc6, offset 0x3180 - 0x3180: 0x0008, 0x3181: 0x0008, 0x3182: 0x0008, 0x3183: 0x0008, 0x3184: 0x0008, 0x3185: 0x0008, - 0x3186: 0x0008, 0x3187: 0x0008, 0x3188: 0x0008, 0x3189: 0x0008, 0x318a: 0x0008, 0x318b: 0x0008, - 0x318c: 0x0008, 0x318d: 0x0008, 0x318e: 0x0008, 0x318f: 0x0008, 0x3190: 0x0008, 0x3191: 0x0008, - 0x3192: 0x0008, 0x3193: 0x0008, 0x3194: 0x0008, 0x3195: 0x0008, 0x3196: 0x0008, 0x3197: 0x0008, - 0x3198: 0x0008, 0x3199: 0x0008, 0x319a: 0x0008, 0x319b: 0x0008, 0x319c: 0x0008, 0x319d: 0x0008, - 0x319e: 0x0008, 0x319f: 0x0008, 0x31a0: 0x0008, 0x31a1: 0x0008, 0x31a2: 0x0008, 0x31a3: 0x0008, - 0x31a4: 0x0008, 0x31a5: 0x0008, 0x31a6: 0x0200, 0x31a7: 0x0200, 0x31a8: 0x0200, 0x31a9: 0x0200, - 0x31aa: 0x0200, 0x31ab: 0x0200, 0x31ac: 0x0200, 0x31ad: 0x0200, 0x31ae: 0x0200, 0x31af: 0x0200, - 0x31b0: 0x0200, 0x31b1: 0x0200, 0x31b2: 0x0200, 0x31b3: 0x0200, 0x31b4: 0x0200, 0x31b5: 0x0200, - 0x31b6: 0x0200, 0x31b7: 0x0200, 0x31b8: 0x0200, 0x31b9: 0x0200, 0x31ba: 0x0200, 0x31bb: 0x0200, - 0x31bc: 0x0200, 0x31bd: 0x0200, 0x31be: 0x0200, 0x31bf: 0x0200, - // Block 0xc7, offset 0x31c0 - 0x31c1: 0x0008, 0x31c2: 0x0008, 0x31c3: 0x0008, 0x31c4: 0x0008, 0x31c5: 0x0008, - 0x31c6: 0x0008, 0x31c7: 0x0008, 0x31c8: 0x0008, 0x31c9: 0x0008, 0x31ca: 0x0008, 0x31cb: 0x0008, - 0x31cc: 0x0008, 0x31cd: 0x0008, 0x31ce: 0x0008, 0x31cf: 0x0008, - 0x31da: 0x0008, - 0x31ef: 0x0008, - 0x31f2: 0x0008, 0x31f3: 0x0008, 0x31f4: 0x0008, 0x31f5: 0x0008, - 0x31f6: 0x0008, 0x31f7: 0x0008, 0x31f8: 0x0008, 0x31f9: 0x0008, 0x31fa: 0x0008, - 0x31fc: 0x0008, 0x31fd: 0x0008, 0x31fe: 0x0008, 0x31ff: 0x0008, - // Block 0xc8, offset 0x3200 - 0x3209: 0x0008, 0x320a: 0x0008, 0x320b: 0x0008, - 0x320c: 0x0008, 0x320d: 0x0008, 0x320e: 0x0008, 0x320f: 0x0008, 0x3210: 0x0008, 0x3211: 0x0008, - 0x3212: 0x0008, 0x3213: 0x0008, 0x3214: 0x0008, 0x3215: 0x0008, 0x3216: 0x0008, 0x3217: 0x0008, - 0x3218: 0x0008, 0x3219: 0x0008, 0x321a: 0x0008, 0x321b: 0x0008, 0x321c: 0x0008, 0x321d: 0x0008, - 0x321e: 0x0008, 0x321f: 0x0008, 0x3220: 0x0008, 0x3221: 0x0008, 0x3222: 0x0008, 0x3223: 0x0008, - 0x3224: 0x0008, 0x3225: 0x0008, 0x3226: 0x0008, 0x3227: 0x0008, 0x3228: 0x0008, 0x3229: 0x0008, - 0x322a: 0x0008, 0x322b: 0x0008, 0x322c: 0x0008, 0x322d: 0x0008, 0x322e: 0x0008, 0x322f: 0x0008, - 0x3230: 0x0008, 0x3231: 0x0008, 0x3232: 0x0008, 0x3233: 0x0008, 0x3234: 0x0008, 0x3235: 0x0008, - 0x3236: 0x0008, 0x3237: 0x0008, 0x3238: 0x0008, 0x3239: 0x0008, 0x323a: 0x0008, 0x323b: 0x0008, - 0x323c: 0x0008, 0x323d: 0x0008, 0x323e: 0x0008, 0x323f: 0x0008, - // Block 0xc9, offset 0x3240 - 0x3240: 0x0008, 0x3241: 0x0008, 0x3242: 0x0008, 0x3243: 0x0008, 0x3244: 0x0008, 0x3245: 0x0008, - 0x3246: 0x0008, 0x3247: 0x0008, 0x3248: 0x0008, 0x3249: 0x0008, 0x324a: 0x0008, 0x324b: 0x0008, - 0x324c: 0x0008, 0x324d: 0x0008, 0x324e: 0x0008, 0x324f: 0x0008, 0x3250: 0x0008, 0x3251: 0x0008, - 0x3252: 0x0008, 0x3253: 0x0008, 0x3254: 0x0008, 0x3255: 0x0008, 0x3256: 0x0008, 0x3257: 0x0008, - 0x3258: 0x0008, 0x3259: 0x0008, 0x325a: 0x0008, 0x325b: 0x0008, 0x325c: 0x0008, 0x325d: 0x0008, - 0x325e: 0x0008, 0x325f: 0x0008, 0x3260: 0x0008, 0x3261: 0x0008, 0x3262: 0x0008, 0x3263: 0x0008, - 0x3264: 0x0008, 0x3265: 0x0008, 0x3266: 0x0008, 0x3267: 0x0008, 0x3268: 0x0008, 0x3269: 0x0008, - 0x326a: 0x0008, 0x326b: 0x0008, 0x326c: 0x0008, 0x326d: 0x0008, 0x326e: 0x0008, 0x326f: 0x0008, - 0x3270: 0x0008, 0x3271: 0x0008, 0x3272: 0x0008, 0x3273: 0x0008, 0x3274: 0x0008, 0x3275: 0x0008, - 0x3276: 0x0008, 0x3277: 0x0008, 0x3278: 0x0008, 0x3279: 0x0008, 0x327a: 0x0008, 0x327b: 0x0004, - 0x327c: 0x0004, 0x327d: 0x0004, 0x327e: 0x0004, 0x327f: 0x0004, - // Block 0xca, offset 0x3280 - 0x3280: 0x0008, 0x3281: 0x0008, 0x3282: 0x0008, 0x3283: 0x0008, 0x3284: 0x0008, 0x3285: 0x0008, - 0x3286: 0x0008, 0x3287: 0x0008, 0x3288: 0x0008, 0x3289: 0x0008, 0x328a: 0x0008, 0x328b: 0x0008, - 0x328c: 0x0008, 0x328d: 0x0008, 0x328e: 0x0008, 0x328f: 0x0008, 0x3290: 0x0008, 0x3291: 0x0008, - 0x3292: 0x0008, 0x3293: 0x0008, 0x3294: 0x0008, 0x3295: 0x0008, 0x3296: 0x0008, 0x3297: 0x0008, - 0x3298: 0x0008, 0x3299: 0x0008, 0x329a: 0x0008, 0x329b: 0x0008, 0x329c: 0x0008, 0x329d: 0x0008, - 0x329e: 0x0008, 0x329f: 0x0008, 0x32a0: 0x0008, 0x32a1: 0x0008, 0x32a2: 0x0008, 0x32a3: 0x0008, - 0x32a4: 0x0008, 0x32a5: 0x0008, 0x32a6: 0x0008, 0x32a7: 0x0008, 0x32a8: 0x0008, 0x32a9: 0x0008, - 0x32aa: 0x0008, 0x32ab: 0x0008, 0x32ac: 0x0008, 0x32ad: 0x0008, 0x32ae: 0x0008, 0x32af: 0x0008, - 0x32b0: 0x0008, 0x32b1: 0x0008, 0x32b2: 0x0008, 0x32b3: 0x0008, 0x32b4: 0x0008, 0x32b5: 0x0008, - 0x32b6: 0x0008, 0x32b7: 0x0008, 0x32b8: 0x0008, 0x32b9: 0x0008, 0x32ba: 0x0008, 0x32bb: 0x0008, - 0x32bc: 0x0008, 0x32bd: 0x0008, - // Block 0xcb, offset 0x32c0 - 0x32c6: 0x0008, 0x32c7: 0x0008, 0x32c8: 0x0008, 0x32c9: 0x0008, 0x32ca: 0x0008, 0x32cb: 0x0008, - 0x32cc: 0x0008, 0x32cd: 0x0008, 0x32ce: 0x0008, 0x32cf: 0x0008, 0x32d0: 0x0008, 0x32d1: 0x0008, - 0x32d2: 0x0008, 0x32d3: 0x0008, 0x32d4: 0x0008, 0x32d5: 0x0008, 0x32d6: 0x0008, 0x32d7: 0x0008, - 0x32d8: 0x0008, 0x32d9: 0x0008, 0x32da: 0x0008, 0x32db: 0x0008, 0x32dc: 0x0008, 0x32dd: 0x0008, - 0x32de: 0x0008, 0x32df: 0x0008, 0x32e0: 0x0008, 0x32e1: 0x0008, 0x32e2: 0x0008, 0x32e3: 0x0008, - 0x32e4: 0x0008, 0x32e5: 0x0008, 0x32e6: 0x0008, 0x32e7: 0x0008, 0x32e8: 0x0008, 0x32e9: 0x0008, - 0x32ea: 0x0008, 0x32eb: 0x0008, 0x32ec: 0x0008, 0x32ed: 0x0008, 0x32ee: 0x0008, 0x32ef: 0x0008, - 0x32f0: 0x0008, 0x32f1: 0x0008, 0x32f2: 0x0008, 0x32f3: 0x0008, 0x32f4: 0x0008, 0x32f5: 0x0008, - 0x32f6: 0x0008, 0x32f7: 0x0008, 0x32f8: 0x0008, 0x32f9: 0x0008, 0x32fa: 0x0008, 0x32fb: 0x0008, - 0x32fc: 0x0008, 0x32fd: 0x0008, 0x32fe: 0x0008, 0x32ff: 0x0008, - // Block 0xcc, offset 0x3300 - 0x3300: 0x0008, 0x3301: 0x0008, 0x3302: 0x0008, 0x3303: 0x0008, 0x3304: 0x0008, 0x3305: 0x0008, - 0x3306: 0x0008, 0x3307: 0x0008, 0x3308: 0x0008, 0x3309: 0x0008, 0x330a: 0x0008, 0x330b: 0x0008, - 0x330c: 0x0008, 0x330d: 0x0008, 0x330e: 0x0008, 0x330f: 0x0008, - // Block 0xcd, offset 0x3340 - 0x3374: 0x0008, 0x3375: 0x0008, - 0x3376: 0x0008, 0x3377: 0x0008, 0x3378: 0x0008, 0x3379: 0x0008, 0x337a: 0x0008, 0x337b: 0x0008, - 0x337c: 0x0008, 0x337d: 0x0008, 0x337e: 0x0008, 0x337f: 0x0008, - // Block 0xce, offset 0x3380 - 0x3395: 0x0008, 0x3396: 0x0008, 0x3397: 0x0008, - 0x3398: 0x0008, 0x3399: 0x0008, 0x339a: 0x0008, 0x339b: 0x0008, 0x339c: 0x0008, 0x339d: 0x0008, - 0x339e: 0x0008, 0x339f: 0x0008, 0x33a0: 0x0008, 0x33a1: 0x0008, 0x33a2: 0x0008, 0x33a3: 0x0008, - 0x33a4: 0x0008, 0x33a5: 0x0008, 0x33a6: 0x0008, 0x33a7: 0x0008, 0x33a8: 0x0008, 0x33a9: 0x0008, - 0x33aa: 0x0008, 0x33ab: 0x0008, 0x33ac: 0x0008, 0x33ad: 0x0008, 0x33ae: 0x0008, 0x33af: 0x0008, - 0x33b0: 0x0008, 0x33b1: 0x0008, 0x33b2: 0x0008, 0x33b3: 0x0008, 0x33b4: 0x0008, 0x33b5: 0x0008, - 0x33b6: 0x0008, 0x33b7: 0x0008, 0x33b8: 0x0008, 0x33b9: 0x0008, 0x33ba: 0x0008, 0x33bb: 0x0008, - 0x33bc: 0x0008, 0x33bd: 0x0008, 0x33be: 0x0008, 0x33bf: 0x0008, - // Block 0xcf, offset 0x33c0 - 0x33cc: 0x0008, 0x33cd: 0x0008, 0x33ce: 0x0008, 0x33cf: 0x0008, - // Block 0xd0, offset 0x3400 - 0x3408: 0x0008, 0x3409: 0x0008, 0x340a: 0x0008, 0x340b: 0x0008, - 0x340c: 0x0008, 0x340d: 0x0008, 0x340e: 0x0008, 0x340f: 0x0008, - 0x341a: 0x0008, 0x341b: 0x0008, 0x341c: 0x0008, 0x341d: 0x0008, - 0x341e: 0x0008, 0x341f: 0x0008, - // Block 0xd1, offset 0x3440 - 0x3448: 0x0008, 0x3449: 0x0008, 0x344a: 0x0008, 0x344b: 0x0008, - 0x344c: 0x0008, 0x344d: 0x0008, 0x344e: 0x0008, 0x344f: 0x0008, - 0x346e: 0x0008, 0x346f: 0x0008, - 0x3470: 0x0008, 0x3471: 0x0008, 0x3472: 0x0008, 0x3473: 0x0008, 0x3474: 0x0008, 0x3475: 0x0008, - 0x3476: 0x0008, 0x3477: 0x0008, 0x3478: 0x0008, 0x3479: 0x0008, 0x347a: 0x0008, 0x347b: 0x0008, - 0x347c: 0x0008, 0x347d: 0x0008, 0x347e: 0x0008, 0x347f: 0x0008, - // Block 0xd2, offset 0x3480 - 0x348c: 0x0008, 0x348d: 0x0008, 0x348e: 0x0008, 0x348f: 0x0008, 0x3490: 0x0008, 0x3491: 0x0008, - 0x3492: 0x0008, 0x3493: 0x0008, 0x3494: 0x0008, 0x3495: 0x0008, 0x3496: 0x0008, 0x3497: 0x0008, - 0x3498: 0x0008, 0x3499: 0x0008, 0x349a: 0x0008, 0x349b: 0x0008, 0x349c: 0x0008, 0x349d: 0x0008, - 0x349e: 0x0008, 0x349f: 0x0008, 0x34a0: 0x0008, 0x34a1: 0x0008, 0x34a2: 0x0008, 0x34a3: 0x0008, - 0x34a4: 0x0008, 0x34a5: 0x0008, 0x34a6: 0x0008, 0x34a7: 0x0008, 0x34a8: 0x0008, 0x34a9: 0x0008, - 0x34aa: 0x0008, 0x34ab: 0x0008, 0x34ac: 0x0008, 0x34ad: 0x0008, 0x34ae: 0x0008, 0x34af: 0x0008, - 0x34b0: 0x0008, 0x34b1: 0x0008, 0x34b2: 0x0008, 0x34b3: 0x0008, 0x34b4: 0x0008, 0x34b5: 0x0008, - 0x34b6: 0x0008, 0x34b7: 0x0008, 0x34b8: 0x0008, 0x34b9: 0x0008, 0x34ba: 0x0008, - 0x34bc: 0x0008, 0x34bd: 0x0008, 0x34be: 0x0008, 0x34bf: 0x0008, - // Block 0xd3, offset 0x34c0 - 0x34c0: 0x0008, 0x34c1: 0x0008, 0x34c2: 0x0008, 0x34c3: 0x0008, 0x34c4: 0x0008, 0x34c5: 0x0008, - 0x34c7: 0x0008, 0x34c8: 0x0008, 0x34c9: 0x0008, 0x34ca: 0x0008, 0x34cb: 0x0008, - 0x34cc: 0x0008, 0x34cd: 0x0008, 0x34ce: 0x0008, 0x34cf: 0x0008, 0x34d0: 0x0008, 0x34d1: 0x0008, - 0x34d2: 0x0008, 0x34d3: 0x0008, 0x34d4: 0x0008, 0x34d5: 0x0008, 0x34d6: 0x0008, 0x34d7: 0x0008, - 0x34d8: 0x0008, 0x34d9: 0x0008, 0x34da: 0x0008, 0x34db: 0x0008, 0x34dc: 0x0008, 0x34dd: 0x0008, - 0x34de: 0x0008, 0x34df: 0x0008, 0x34e0: 0x0008, 0x34e1: 0x0008, 0x34e2: 0x0008, 0x34e3: 0x0008, - 0x34e4: 0x0008, 0x34e5: 0x0008, 0x34e6: 0x0008, 0x34e7: 0x0008, 0x34e8: 0x0008, 0x34e9: 0x0008, - 0x34ea: 0x0008, 0x34eb: 0x0008, 0x34ec: 0x0008, 0x34ed: 0x0008, 0x34ee: 0x0008, 0x34ef: 0x0008, - 0x34f0: 0x0008, 0x34f1: 0x0008, 0x34f2: 0x0008, 0x34f3: 0x0008, 0x34f4: 0x0008, 0x34f5: 0x0008, - 0x34f6: 0x0008, 0x34f7: 0x0008, 0x34f8: 0x0008, 0x34f9: 0x0008, 0x34fa: 0x0008, 0x34fb: 0x0008, - 0x34fc: 0x0008, 0x34fd: 0x0008, 0x34fe: 0x0008, 0x34ff: 0x0008, - // Block 0xd4, offset 0x3500 - 0x3500: 0x0002, 0x3501: 0x0002, 0x3502: 0x0002, 0x3503: 0x0002, 0x3504: 0x0002, 0x3505: 0x0002, - 0x3506: 0x0002, 0x3507: 0x0002, 0x3508: 0x0002, 0x3509: 0x0002, 0x350a: 0x0002, 0x350b: 0x0002, - 0x350c: 0x0002, 0x350d: 0x0002, 0x350e: 0x0002, 0x350f: 0x0002, 0x3510: 0x0002, 0x3511: 0x0002, - 0x3512: 0x0002, 0x3513: 0x0002, 0x3514: 0x0002, 0x3515: 0x0002, 0x3516: 0x0002, 0x3517: 0x0002, - 0x3518: 0x0002, 0x3519: 0x0002, 0x351a: 0x0002, 0x351b: 0x0002, 0x351c: 0x0002, 0x351d: 0x0002, - 0x351e: 0x0002, 0x351f: 0x0002, 0x3520: 0x0004, 0x3521: 0x0004, 0x3522: 0x0004, 0x3523: 0x0004, - 0x3524: 0x0004, 0x3525: 0x0004, 0x3526: 0x0004, 0x3527: 0x0004, 0x3528: 0x0004, 0x3529: 0x0004, - 0x352a: 0x0004, 0x352b: 0x0004, 0x352c: 0x0004, 0x352d: 0x0004, 0x352e: 0x0004, 0x352f: 0x0004, - 0x3530: 0x0004, 0x3531: 0x0004, 0x3532: 0x0004, 0x3533: 0x0004, 0x3534: 0x0004, 0x3535: 0x0004, - 0x3536: 0x0004, 0x3537: 0x0004, 0x3538: 0x0004, 0x3539: 0x0004, 0x353a: 0x0004, 0x353b: 0x0004, - 0x353c: 0x0004, 0x353d: 0x0004, 0x353e: 0x0004, 0x353f: 0x0004, - // Block 0xd5, offset 0x3540 - 0x3540: 0x0002, 0x3541: 0x0002, 0x3542: 0x0002, 0x3543: 0x0002, 0x3544: 0x0002, 0x3545: 0x0002, - 0x3546: 0x0002, 0x3547: 0x0002, 0x3548: 0x0002, 0x3549: 0x0002, 0x354a: 0x0002, 0x354b: 0x0002, - 0x354c: 0x0002, 0x354d: 0x0002, 0x354e: 0x0002, 0x354f: 0x0002, 0x3550: 0x0002, 0x3551: 0x0002, - 0x3552: 0x0002, 0x3553: 0x0002, 0x3554: 0x0002, 0x3555: 0x0002, 0x3556: 0x0002, 0x3557: 0x0002, - 0x3558: 0x0002, 0x3559: 0x0002, 0x355a: 0x0002, 0x355b: 0x0002, 0x355c: 0x0002, 0x355d: 0x0002, - 0x355e: 0x0002, 0x355f: 0x0002, 0x3560: 0x0002, 0x3561: 0x0002, 0x3562: 0x0002, 0x3563: 0x0002, - 0x3564: 0x0002, 0x3565: 0x0002, 0x3566: 0x0002, 0x3567: 0x0002, 0x3568: 0x0002, 0x3569: 0x0002, - 0x356a: 0x0002, 0x356b: 0x0002, 0x356c: 0x0002, 0x356d: 0x0002, 0x356e: 0x0002, 0x356f: 0x0002, - 0x3570: 0x0002, 0x3571: 0x0002, 0x3572: 0x0002, 0x3573: 0x0002, 0x3574: 0x0002, 0x3575: 0x0002, - 0x3576: 0x0002, 0x3577: 0x0002, 0x3578: 0x0002, 0x3579: 0x0002, 0x357a: 0x0002, 0x357b: 0x0002, - 0x357c: 0x0002, 0x357d: 0x0002, 0x357e: 0x0002, 0x357f: 0x0002, - // Block 0xd6, offset 0x3580 - 0x3580: 0x0004, 0x3581: 0x0004, 0x3582: 0x0004, 0x3583: 0x0004, 0x3584: 0x0004, 0x3585: 0x0004, - 0x3586: 0x0004, 0x3587: 0x0004, 0x3588: 0x0004, 0x3589: 0x0004, 0x358a: 0x0004, 0x358b: 0x0004, - 0x358c: 0x0004, 0x358d: 0x0004, 0x358e: 0x0004, 0x358f: 0x0004, 0x3590: 0x0004, 0x3591: 0x0004, - 0x3592: 0x0004, 0x3593: 0x0004, 0x3594: 0x0004, 0x3595: 0x0004, 0x3596: 0x0004, 0x3597: 0x0004, - 0x3598: 0x0004, 0x3599: 0x0004, 0x359a: 0x0004, 0x359b: 0x0004, 0x359c: 0x0004, 0x359d: 0x0004, - 0x359e: 0x0004, 0x359f: 0x0004, 0x35a0: 0x0004, 0x35a1: 0x0004, 0x35a2: 0x0004, 0x35a3: 0x0004, - 0x35a4: 0x0004, 0x35a5: 0x0004, 0x35a6: 0x0004, 0x35a7: 0x0004, 0x35a8: 0x0004, 0x35a9: 0x0004, - 0x35aa: 0x0004, 0x35ab: 0x0004, 0x35ac: 0x0004, 0x35ad: 0x0004, 0x35ae: 0x0004, 0x35af: 0x0004, - 0x35b0: 0x0002, 0x35b1: 0x0002, 0x35b2: 0x0002, 0x35b3: 0x0002, 0x35b4: 0x0002, 0x35b5: 0x0002, - 0x35b6: 0x0002, 0x35b7: 0x0002, 0x35b8: 0x0002, 0x35b9: 0x0002, 0x35ba: 0x0002, 0x35bb: 0x0002, - 0x35bc: 0x0002, 0x35bd: 0x0002, 0x35be: 0x0002, 0x35bf: 0x0002, -} - -// graphemesIndex: 25 blocks, 1600 entries, 1600 bytes -// Block 0 is the zero block. -var graphemesIndex = [1600]property{ - // Block 0x0, offset 0x0 - // Block 0x1, offset 0x40 - // Block 0x2, offset 0x80 - // Block 0x3, offset 0xc0 - 0xc2: 0x01, - 0xcc: 0x02, 0xcd: 0x03, - 0xd2: 0x04, 0xd6: 0x05, 0xd7: 0x06, - 0xd8: 0x07, 0xd9: 0x08, 0xdb: 0x09, 0xdc: 0x0a, 0xdd: 0x0b, 0xde: 0x0c, 0xdf: 0x0d, - 0xe0: 0x02, 0xe1: 0x03, 0xe2: 0x04, 0xe3: 0x05, - 0xea: 0x06, 0xeb: 0x07, 0xec: 0x08, 0xed: 0x09, 0xef: 0x0a, - 0xf0: 0x14, 0xf3: 0x16, - // Block 0x4, offset 0x100 - 0x120: 0x0e, 0x121: 0x0f, 0x122: 0x10, 0x123: 0x11, 0x124: 0x12, 0x125: 0x13, 0x126: 0x14, 0x127: 0x15, - 0x128: 0x16, 0x129: 0x17, 0x12a: 0x16, 0x12b: 0x18, 0x12c: 0x19, 0x12d: 0x1a, 0x12e: 0x1b, 0x12f: 0x1c, - 0x130: 0x1d, 0x131: 0x1e, 0x132: 0x1f, 0x133: 0x20, 0x134: 0x21, 0x135: 0x22, 0x136: 0x23, 0x137: 0x24, - 0x138: 0x25, 0x139: 0x26, 0x13a: 0x27, 0x13b: 0x28, 0x13c: 0x29, 0x13d: 0x2a, 0x13e: 0x2b, 0x13f: 0x2c, - // Block 0x5, offset 0x140 - 0x140: 0x2d, 0x141: 0x2e, 0x142: 0x2f, 0x144: 0x30, 0x145: 0x31, 0x146: 0x32, 0x147: 0x33, - 0x14d: 0x34, - 0x15c: 0x35, 0x15d: 0x36, 0x15e: 0x37, 0x15f: 0x38, - 0x160: 0x39, 0x162: 0x3a, 0x164: 0x3b, - 0x168: 0x3c, 0x169: 0x3d, 0x16a: 0x3e, 0x16b: 0x3f, 0x16c: 0x40, 0x16d: 0x41, 0x16e: 0x42, 0x16f: 0x43, - 0x170: 0x44, 0x173: 0x45, 0x177: 0x02, - // Block 0x6, offset 0x180 - 0x180: 0x46, 0x181: 0x47, 0x183: 0x48, 0x184: 0x49, 0x186: 0x4a, - 0x18c: 0x4b, 0x18e: 0x4c, 0x18f: 0x4d, - 0x193: 0x4e, 0x196: 0x4f, 0x197: 0x50, - 0x198: 0x51, 0x199: 0x52, 0x19a: 0x53, 0x19b: 0x52, 0x19c: 0x54, 0x19d: 0x55, 0x19e: 0x56, - 0x1a4: 0x57, - 0x1ac: 0x58, 0x1ad: 0x59, - 0x1b3: 0x5a, 0x1b5: 0x5b, 0x1b7: 0x5c, - // Block 0x7, offset 0x1c0 - 0x1c0: 0x5d, 0x1c2: 0x5e, - 0x1ca: 0x5f, - // Block 0x8, offset 0x200 - 0x219: 0x60, 0x21a: 0x61, 0x21b: 0x62, - 0x220: 0x63, 0x222: 0x64, 0x223: 0x65, 0x224: 0x66, 0x225: 0x67, 0x226: 0x68, 0x227: 0x69, - 0x228: 0x6a, 0x229: 0x6b, 0x22a: 0x6c, 0x22b: 0x6d, 0x22f: 0x6e, - 0x230: 0x6f, 0x231: 0x70, 0x232: 0x71, 0x233: 0x72, 0x234: 0x73, 0x235: 0x74, 0x236: 0x75, 0x237: 0x6f, - 0x238: 0x70, 0x239: 0x71, 0x23a: 0x72, 0x23b: 0x73, 0x23c: 0x74, 0x23d: 0x75, 0x23e: 0x6f, 0x23f: 0x70, - // Block 0x9, offset 0x240 - 0x240: 0x71, 0x241: 0x72, 0x242: 0x73, 0x243: 0x74, 0x244: 0x75, 0x245: 0x6f, 0x246: 0x70, 0x247: 0x71, - 0x248: 0x72, 0x249: 0x73, 0x24a: 0x74, 0x24b: 0x75, 0x24c: 0x6f, 0x24d: 0x70, 0x24e: 0x71, 0x24f: 0x72, - 0x250: 0x73, 0x251: 0x74, 0x252: 0x75, 0x253: 0x6f, 0x254: 0x70, 0x255: 0x71, 0x256: 0x72, 0x257: 0x73, - 0x258: 0x74, 0x259: 0x75, 0x25a: 0x6f, 0x25b: 0x70, 0x25c: 0x71, 0x25d: 0x72, 0x25e: 0x73, 0x25f: 0x74, - 0x260: 0x75, 0x261: 0x6f, 0x262: 0x70, 0x263: 0x71, 0x264: 0x72, 0x265: 0x73, 0x266: 0x74, 0x267: 0x75, - 0x268: 0x6f, 0x269: 0x70, 0x26a: 0x71, 0x26b: 0x72, 0x26c: 0x73, 0x26d: 0x74, 0x26e: 0x75, 0x26f: 0x6f, - 0x270: 0x70, 0x271: 0x71, 0x272: 0x72, 0x273: 0x73, 0x274: 0x74, 0x275: 0x75, 0x276: 0x6f, 0x277: 0x70, - 0x278: 0x71, 0x279: 0x72, 0x27a: 0x73, 0x27b: 0x74, 0x27c: 0x75, 0x27d: 0x6f, 0x27e: 0x70, 0x27f: 0x71, - // Block 0xa, offset 0x280 - 0x280: 0x72, 0x281: 0x73, 0x282: 0x74, 0x283: 0x75, 0x284: 0x6f, 0x285: 0x70, 0x286: 0x71, 0x287: 0x72, - 0x288: 0x73, 0x289: 0x74, 0x28a: 0x75, 0x28b: 0x6f, 0x28c: 0x70, 0x28d: 0x71, 0x28e: 0x72, 0x28f: 0x73, - 0x290: 0x74, 0x291: 0x75, 0x292: 0x6f, 0x293: 0x70, 0x294: 0x71, 0x295: 0x72, 0x296: 0x73, 0x297: 0x74, - 0x298: 0x75, 0x299: 0x6f, 0x29a: 0x70, 0x29b: 0x71, 0x29c: 0x72, 0x29d: 0x73, 0x29e: 0x74, 0x29f: 0x75, - 0x2a0: 0x6f, 0x2a1: 0x70, 0x2a2: 0x71, 0x2a3: 0x72, 0x2a4: 0x73, 0x2a5: 0x74, 0x2a6: 0x75, 0x2a7: 0x6f, - 0x2a8: 0x70, 0x2a9: 0x71, 0x2aa: 0x72, 0x2ab: 0x73, 0x2ac: 0x74, 0x2ad: 0x75, 0x2ae: 0x6f, 0x2af: 0x70, - 0x2b0: 0x71, 0x2b1: 0x72, 0x2b2: 0x73, 0x2b3: 0x74, 0x2b4: 0x75, 0x2b5: 0x6f, 0x2b6: 0x70, 0x2b7: 0x71, - 0x2b8: 0x72, 0x2b9: 0x73, 0x2ba: 0x74, 0x2bb: 0x75, 0x2bc: 0x6f, 0x2bd: 0x70, 0x2be: 0x71, 0x2bf: 0x72, - // Block 0xb, offset 0x2c0 - 0x2c0: 0x73, 0x2c1: 0x74, 0x2c2: 0x75, 0x2c3: 0x6f, 0x2c4: 0x70, 0x2c5: 0x71, 0x2c6: 0x72, 0x2c7: 0x73, - 0x2c8: 0x74, 0x2c9: 0x75, 0x2ca: 0x6f, 0x2cb: 0x70, 0x2cc: 0x71, 0x2cd: 0x72, 0x2ce: 0x73, 0x2cf: 0x74, - 0x2d0: 0x75, 0x2d1: 0x6f, 0x2d2: 0x70, 0x2d3: 0x71, 0x2d4: 0x72, 0x2d5: 0x73, 0x2d6: 0x74, 0x2d7: 0x75, - 0x2d8: 0x6f, 0x2d9: 0x70, 0x2da: 0x71, 0x2db: 0x72, 0x2dc: 0x73, 0x2dd: 0x74, 0x2de: 0x76, 0x2df: 0x77, - // Block 0xc, offset 0x300 - 0x32c: 0x78, - 0x338: 0x79, 0x33b: 0x7a, 0x33e: 0x61, 0x33f: 0x7b, - // Block 0xd, offset 0x340 - 0x347: 0x7c, - 0x34b: 0x7d, 0x34d: 0x7e, - 0x368: 0x7f, 0x36b: 0x80, - 0x374: 0x81, - 0x37a: 0x82, 0x37b: 0x83, 0x37d: 0x84, 0x37e: 0x85, - // Block 0xe, offset 0x380 - 0x380: 0x86, 0x381: 0x87, 0x382: 0x88, 0x383: 0x89, 0x384: 0x8a, 0x385: 0x8b, 0x386: 0x8c, 0x387: 0x8d, - 0x388: 0x8e, 0x389: 0x8f, 0x38b: 0x90, 0x38c: 0x21, 0x38d: 0x91, - 0x390: 0x92, 0x391: 0x93, 0x392: 0x94, 0x393: 0x95, 0x396: 0x96, 0x397: 0x97, - 0x398: 0x98, 0x399: 0x99, 0x39a: 0x9a, 0x39c: 0x9b, - 0x3a0: 0x9c, 0x3a4: 0x9d, 0x3a5: 0x9e, 0x3a7: 0x9f, - 0x3a8: 0xa0, 0x3a9: 0xa1, 0x3aa: 0xa2, - 0x3b0: 0xa3, 0x3b2: 0xa4, 0x3b4: 0xa5, 0x3b5: 0xa6, 0x3b6: 0xa7, - 0x3bb: 0xa8, 0x3bc: 0xa9, 0x3bd: 0xaa, - // Block 0xf, offset 0x3c0 - 0x3d0: 0xab, 0x3d1: 0xac, - // Block 0x10, offset 0x400 - 0x42b: 0xad, 0x42c: 0xae, - 0x43d: 0xaf, 0x43e: 0xb0, 0x43f: 0xb1, - // Block 0x11, offset 0x440 - 0x472: 0xb2, - // Block 0x12, offset 0x480 - 0x4bc: 0xb3, 0x4bd: 0xb4, - // Block 0x13, offset 0x4c0 - 0x4c5: 0xb5, 0x4c6: 0xb6, - 0x4c9: 0xb7, - 0x4e8: 0xb8, 0x4e9: 0xb9, 0x4ea: 0xba, - // Block 0x14, offset 0x500 - 0x500: 0xbb, 0x502: 0xbc, 0x504: 0xae, - 0x50a: 0xbd, 0x50b: 0xbe, - 0x513: 0xbe, - 0x523: 0xbf, 0x525: 0xc0, - // Block 0x15, offset 0x540 - 0x540: 0x52, 0x541: 0x52, 0x542: 0x52, 0x543: 0x52, 0x544: 0xc1, 0x545: 0xc2, 0x546: 0xc3, 0x547: 0xc4, - 0x548: 0xc5, 0x549: 0xc6, 0x54a: 0x52, 0x54b: 0x52, 0x54c: 0x52, 0x54d: 0x52, 0x54e: 0x52, 0x54f: 0xc7, - 0x550: 0x52, 0x551: 0x52, 0x552: 0x52, 0x553: 0x52, 0x554: 0xc8, 0x555: 0xc9, 0x556: 0x52, 0x557: 0x52, - 0x558: 0x52, 0x559: 0xca, 0x55a: 0x52, 0x55b: 0x52, 0x55d: 0xcb, 0x55f: 0xcc, - 0x560: 0xcd, 0x561: 0xce, 0x562: 0xcf, 0x563: 0x52, 0x564: 0xd0, 0x565: 0xd1, 0x566: 0x52, 0x567: 0x52, - 0x568: 0x52, 0x569: 0x52, 0x56a: 0x52, 0x56b: 0x52, - 0x570: 0x52, 0x571: 0x52, 0x572: 0x52, 0x573: 0x52, 0x574: 0x52, 0x575: 0x52, 0x576: 0x52, 0x577: 0x52, - 0x578: 0x52, 0x579: 0x52, 0x57a: 0x52, 0x57b: 0x52, 0x57c: 0x52, 0x57d: 0x52, 0x57e: 0x52, 0x57f: 0xc8, - // Block 0x16, offset 0x580 - 0x590: 0x0b, 0x591: 0x0c, 0x593: 0x0d, 0x596: 0x0e, - 0x59b: 0x0f, 0x59c: 0x10, 0x59d: 0x11, 0x59e: 0x12, 0x59f: 0x13, - // Block 0x17, offset 0x5c0 - 0x5c0: 0xd2, 0x5c1: 0x02, 0x5c2: 0xd3, 0x5c3: 0xd3, 0x5c4: 0x02, 0x5c5: 0x02, 0x5c6: 0x02, 0x5c7: 0xd4, - 0x5c8: 0xd3, 0x5c9: 0xd3, 0x5ca: 0xd3, 0x5cb: 0xd3, 0x5cc: 0xd3, 0x5cd: 0xd3, 0x5ce: 0xd3, 0x5cf: 0xd3, - 0x5d0: 0xd3, 0x5d1: 0xd3, 0x5d2: 0xd3, 0x5d3: 0xd3, 0x5d4: 0xd3, 0x5d5: 0xd3, 0x5d6: 0xd3, 0x5d7: 0xd3, - 0x5d8: 0xd3, 0x5d9: 0xd3, 0x5da: 0xd3, 0x5db: 0xd3, 0x5dc: 0xd3, 0x5dd: 0xd3, 0x5de: 0xd3, 0x5df: 0xd3, - 0x5e0: 0xd3, 0x5e1: 0xd3, 0x5e2: 0xd3, 0x5e3: 0xd3, 0x5e4: 0xd3, 0x5e5: 0xd3, 0x5e6: 0xd3, 0x5e7: 0xd3, - 0x5e8: 0xd3, 0x5e9: 0xd3, 0x5ea: 0xd3, 0x5eb: 0xd3, 0x5ec: 0xd3, 0x5ed: 0xd3, 0x5ee: 0xd3, 0x5ef: 0xd3, - 0x5f0: 0xd3, 0x5f1: 0xd3, 0x5f2: 0xd3, 0x5f3: 0xd3, 0x5f4: 0xd3, 0x5f5: 0xd3, 0x5f6: 0xd3, 0x5f7: 0xd3, - 0x5f8: 0xd3, 0x5f9: 0xd3, 0x5fa: 0xd3, 0x5fb: 0xd3, 0x5fc: 0xd3, 0x5fd: 0xd3, 0x5fe: 0xd3, 0x5ff: 0xd3, - // Block 0x18, offset 0x600 - 0x620: 0x15, -} diff --git a/vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go b/vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go deleted file mode 100644 index 17e9b550b55..00000000000 --- a/vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go +++ /dev/null @@ -1,85 +0,0 @@ -package iterators - -type Stringish interface { - []byte | string -} - -type SplitFunc[T Stringish] func(T, bool) (int, T, error) - -// Iterator is a generic iterator for words that are either []byte or string. -// Iterate while Next() is true, and access the word via Value(). -type Iterator[T Stringish] struct { - split SplitFunc[T] - data T - start int - pos int -} - -// New creates a new Iterator for the given data and SplitFunc. -func New[T Stringish](split SplitFunc[T], data T) *Iterator[T] { - return &Iterator[T]{ - split: split, - data: data, - } -} - -// SetText sets the text for the iterator to operate on, and resets all state. -func (iter *Iterator[T]) SetText(data T) { - iter.data = data - iter.start = 0 - iter.pos = 0 -} - -// Split sets the SplitFunc for the Iterator. -func (iter *Iterator[T]) Split(split SplitFunc[T]) { - iter.split = split -} - -// Next advances the iterator to the next token. It returns false when there -// are no remaining tokens or an error occurred. -func (iter *Iterator[T]) Next() bool { - if iter.pos == len(iter.data) { - return false - } - if iter.pos > len(iter.data) { - panic("SplitFunc advanced beyond the end of the data") - } - - iter.start = iter.pos - - advance, _, err := iter.split(iter.data[iter.pos:], true) - if err != nil { - panic(err) - } - if advance <= 0 { - panic("SplitFunc returned a zero or negative advance") - } - - iter.pos += advance - if iter.pos > len(iter.data) { - panic("SplitFunc advanced beyond the end of the data") - } - - return true -} - -// Value returns the current token. -func (iter *Iterator[T]) Value() T { - return iter.data[iter.start:iter.pos] -} - -// Start returns the byte position of the current token in the original data. -func (iter *Iterator[T]) Start() int { - return iter.start -} - -// End returns the byte position after the current token in the original data. -func (iter *Iterator[T]) End() int { - return iter.pos -} - -// Reset resets the iterator to the beginning of the data. -func (iter *Iterator[T]) Reset() { - iter.start = 0 - iter.pos = 0 -} diff --git a/vendor/github.com/cloudflare/circl/LICENSE b/vendor/github.com/cloudflare/circl/LICENSE deleted file mode 100644 index 67edaa90a04..00000000000 --- a/vendor/github.com/cloudflare/circl/LICENSE +++ /dev/null @@ -1,57 +0,0 @@ -Copyright (c) 2019 Cloudflare. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Cloudflare nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -======================================================================== - -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/cloudflare/circl/dh/x25519/curve.go b/vendor/github.com/cloudflare/circl/dh/x25519/curve.go deleted file mode 100644 index f9057c2b866..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x25519/curve.go +++ /dev/null @@ -1,96 +0,0 @@ -package x25519 - -import ( - fp "github.com/cloudflare/circl/math/fp25519" -) - -// ladderJoye calculates a fixed-point multiplication with the generator point. -// The algorithm is the right-to-left Joye's ladder as described -// in "How to precompute a ladder" in SAC'2017. -func ladderJoye(k *Key) { - w := [5]fp.Elt{} // [mu,x1,z1,x2,z2] order must be preserved. - fp.SetOne(&w[1]) // x1 = 1 - fp.SetOne(&w[2]) // z1 = 1 - w[3] = fp.Elt{ // x2 = G-S - 0xbd, 0xaa, 0x2f, 0xc8, 0xfe, 0xe1, 0x94, 0x7e, - 0xf8, 0xed, 0xb2, 0x14, 0xae, 0x95, 0xf0, 0xbb, - 0xe2, 0x48, 0x5d, 0x23, 0xb9, 0xa0, 0xc7, 0xad, - 0x34, 0xab, 0x7c, 0xe2, 0xee, 0xcd, 0xae, 0x1e, - } - fp.SetOne(&w[4]) // z2 = 1 - - const n = 255 - const h = 3 - swap := uint(1) - for s := 0; s < n-h; s++ { - i := (s + h) / 8 - j := (s + h) % 8 - bit := uint((k[i] >> uint(j)) & 1) - copy(w[0][:], tableGenerator[s*Size:(s+1)*Size]) - diffAdd(&w, swap^bit) - swap = bit - } - for s := 0; s < h; s++ { - double(&w[1], &w[2]) - } - toAffine((*[fp.Size]byte)(k), &w[1], &w[2]) -} - -// ladderMontgomery calculates a generic scalar point multiplication -// The algorithm implemented is the left-to-right Montgomery's ladder. -func ladderMontgomery(k, xP *Key) { - w := [5]fp.Elt{} // [x1, x2, z2, x3, z3] order must be preserved. - w[0] = *(*fp.Elt)(xP) // x1 = xP - fp.SetOne(&w[1]) // x2 = 1 - w[3] = *(*fp.Elt)(xP) // x3 = xP - fp.SetOne(&w[4]) // z3 = 1 - - move := uint(0) - for s := 255 - 1; s >= 0; s-- { - i := s / 8 - j := s % 8 - bit := uint((k[i] >> uint(j)) & 1) - ladderStep(&w, move^bit) - move = bit - } - toAffine((*[fp.Size]byte)(k), &w[1], &w[2]) -} - -func toAffine(k *[fp.Size]byte, x, z *fp.Elt) { - fp.Inv(z, z) - fp.Mul(x, x, z) - _ = fp.ToBytes(k[:], x) -} - -var lowOrderPoints = [5]fp.Elt{ - { /* (0,_,1) point of order 2 on Curve25519 */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }, - { /* (1,_,1) point of order 4 on Curve25519 */ - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }, - { /* (x,_,1) first point of order 8 on Curve25519 */ - 0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, - 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4, 0x6a, - 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, - 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00, - }, - { /* (x,_,1) second point of order 8 on Curve25519 */ - 0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, - 0xb1, 0xd0, 0xb1, 0x55, 0x9c, 0x83, 0xef, 0x5b, - 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, 0x8e, 0x86, - 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0x57, - }, - { /* (-1,_,1) a point of order 4 on the twist of Curve25519 */ - 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, - }, -} diff --git a/vendor/github.com/cloudflare/circl/dh/x25519/curve_amd64.go b/vendor/github.com/cloudflare/circl/dh/x25519/curve_amd64.go deleted file mode 100644 index 8a3d54c570f..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x25519/curve_amd64.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build amd64 && !purego -// +build amd64,!purego - -package x25519 - -import ( - fp "github.com/cloudflare/circl/math/fp25519" - "golang.org/x/sys/cpu" -) - -var hasBmi2Adx = cpu.X86.HasBMI2 && cpu.X86.HasADX - -var _ = hasBmi2Adx - -func double(x, z *fp.Elt) { doubleAmd64(x, z) } -func diffAdd(w *[5]fp.Elt, b uint) { diffAddAmd64(w, b) } -func ladderStep(w *[5]fp.Elt, b uint) { ladderStepAmd64(w, b) } -func mulA24(z, x *fp.Elt) { mulA24Amd64(z, x) } - -//go:noescape -func ladderStepAmd64(w *[5]fp.Elt, b uint) - -//go:noescape -func diffAddAmd64(w *[5]fp.Elt, b uint) - -//go:noescape -func doubleAmd64(x, z *fp.Elt) - -//go:noescape -func mulA24Amd64(z, x *fp.Elt) diff --git a/vendor/github.com/cloudflare/circl/dh/x25519/curve_amd64.h b/vendor/github.com/cloudflare/circl/dh/x25519/curve_amd64.h deleted file mode 100644 index 8c1ae4d0fbb..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x25519/curve_amd64.h +++ /dev/null @@ -1,111 +0,0 @@ -#define ladderStepLeg \ - addSub(x2,z2) \ - addSub(x3,z3) \ - integerMulLeg(b0,x2,z3) \ - integerMulLeg(b1,x3,z2) \ - reduceFromDoubleLeg(t0,b0) \ - reduceFromDoubleLeg(t1,b1) \ - addSub(t0,t1) \ - cselect(x2,x3,regMove) \ - cselect(z2,z3,regMove) \ - integerSqrLeg(b0,t0) \ - integerSqrLeg(b1,t1) \ - reduceFromDoubleLeg(x3,b0) \ - reduceFromDoubleLeg(z3,b1) \ - integerMulLeg(b0,x1,z3) \ - reduceFromDoubleLeg(z3,b0) \ - integerSqrLeg(b0,x2) \ - integerSqrLeg(b1,z2) \ - reduceFromDoubleLeg(x2,b0) \ - reduceFromDoubleLeg(z2,b1) \ - subtraction(t0,x2,z2) \ - multiplyA24Leg(t1,t0) \ - additionLeg(t1,t1,z2) \ - integerMulLeg(b0,x2,z2) \ - integerMulLeg(b1,t0,t1) \ - reduceFromDoubleLeg(x2,b0) \ - reduceFromDoubleLeg(z2,b1) - -#define ladderStepBmi2Adx \ - addSub(x2,z2) \ - addSub(x3,z3) \ - integerMulAdx(b0,x2,z3) \ - integerMulAdx(b1,x3,z2) \ - reduceFromDoubleAdx(t0,b0) \ - reduceFromDoubleAdx(t1,b1) \ - addSub(t0,t1) \ - cselect(x2,x3,regMove) \ - cselect(z2,z3,regMove) \ - integerSqrAdx(b0,t0) \ - integerSqrAdx(b1,t1) \ - reduceFromDoubleAdx(x3,b0) \ - reduceFromDoubleAdx(z3,b1) \ - integerMulAdx(b0,x1,z3) \ - reduceFromDoubleAdx(z3,b0) \ - integerSqrAdx(b0,x2) \ - integerSqrAdx(b1,z2) \ - reduceFromDoubleAdx(x2,b0) \ - reduceFromDoubleAdx(z2,b1) \ - subtraction(t0,x2,z2) \ - multiplyA24Adx(t1,t0) \ - additionAdx(t1,t1,z2) \ - integerMulAdx(b0,x2,z2) \ - integerMulAdx(b1,t0,t1) \ - reduceFromDoubleAdx(x2,b0) \ - reduceFromDoubleAdx(z2,b1) - -#define difAddLeg \ - addSub(x1,z1) \ - integerMulLeg(b0,z1,ui) \ - reduceFromDoubleLeg(z1,b0) \ - addSub(x1,z1) \ - integerSqrLeg(b0,x1) \ - integerSqrLeg(b1,z1) \ - reduceFromDoubleLeg(x1,b0) \ - reduceFromDoubleLeg(z1,b1) \ - integerMulLeg(b0,x1,z2) \ - integerMulLeg(b1,z1,x2) \ - reduceFromDoubleLeg(x1,b0) \ - reduceFromDoubleLeg(z1,b1) - -#define difAddBmi2Adx \ - addSub(x1,z1) \ - integerMulAdx(b0,z1,ui) \ - reduceFromDoubleAdx(z1,b0) \ - addSub(x1,z1) \ - integerSqrAdx(b0,x1) \ - integerSqrAdx(b1,z1) \ - reduceFromDoubleAdx(x1,b0) \ - reduceFromDoubleAdx(z1,b1) \ - integerMulAdx(b0,x1,z2) \ - integerMulAdx(b1,z1,x2) \ - reduceFromDoubleAdx(x1,b0) \ - reduceFromDoubleAdx(z1,b1) - -#define doubleLeg \ - addSub(x1,z1) \ - integerSqrLeg(b0,x1) \ - integerSqrLeg(b1,z1) \ - reduceFromDoubleLeg(x1,b0) \ - reduceFromDoubleLeg(z1,b1) \ - subtraction(t0,x1,z1) \ - multiplyA24Leg(t1,t0) \ - additionLeg(t1,t1,z1) \ - integerMulLeg(b0,x1,z1) \ - integerMulLeg(b1,t0,t1) \ - reduceFromDoubleLeg(x1,b0) \ - reduceFromDoubleLeg(z1,b1) - -#define doubleBmi2Adx \ - addSub(x1,z1) \ - integerSqrAdx(b0,x1) \ - integerSqrAdx(b1,z1) \ - reduceFromDoubleAdx(x1,b0) \ - reduceFromDoubleAdx(z1,b1) \ - subtraction(t0,x1,z1) \ - multiplyA24Adx(t1,t0) \ - additionAdx(t1,t1,z1) \ - integerMulAdx(b0,x1,z1) \ - integerMulAdx(b1,t0,t1) \ - reduceFromDoubleAdx(x1,b0) \ - reduceFromDoubleAdx(z1,b1) diff --git a/vendor/github.com/cloudflare/circl/dh/x25519/curve_amd64.s b/vendor/github.com/cloudflare/circl/dh/x25519/curve_amd64.s deleted file mode 100644 index ce9f062894a..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x25519/curve_amd64.s +++ /dev/null @@ -1,157 +0,0 @@ -//go:build amd64 && !purego -// +build amd64,!purego - -#include "textflag.h" - -// Depends on circl/math/fp25519 package -#include "../../math/fp25519/fp_amd64.h" -#include "curve_amd64.h" - -// CTE_A24 is (A+2)/4 from Curve25519 -#define CTE_A24 121666 - -#define Size 32 - -// multiplyA24Leg multiplies x times CTE_A24 and stores in z -// Uses: AX, DX, R8-R13, FLAGS -// Instr: x86_64, cmov -#define multiplyA24Leg(z,x) \ - MOVL $CTE_A24, AX; MULQ 0+x; MOVQ AX, R8; MOVQ DX, R9; \ - MOVL $CTE_A24, AX; MULQ 8+x; MOVQ AX, R12; MOVQ DX, R10; \ - MOVL $CTE_A24, AX; MULQ 16+x; MOVQ AX, R13; MOVQ DX, R11; \ - MOVL $CTE_A24, AX; MULQ 24+x; \ - ADDQ R12, R9; \ - ADCQ R13, R10; \ - ADCQ AX, R11; \ - ADCQ $0, DX; \ - MOVL $38, AX; /* 2*C = 38 = 2^256 MOD 2^255-19*/ \ - IMULQ AX, DX; \ - ADDQ DX, R8; \ - ADCQ $0, R9; MOVQ R9, 8+z; \ - ADCQ $0, R10; MOVQ R10, 16+z; \ - ADCQ $0, R11; MOVQ R11, 24+z; \ - MOVQ $0, DX; \ - CMOVQCS AX, DX; \ - ADDQ DX, R8; MOVQ R8, 0+z; - -// multiplyA24Adx multiplies x times CTE_A24 and stores in z -// Uses: AX, DX, R8-R12, FLAGS -// Instr: x86_64, cmov, bmi2 -#define multiplyA24Adx(z,x) \ - MOVQ $CTE_A24, DX; \ - MULXQ 0+x, R8, R10; \ - MULXQ 8+x, R9, R11; ADDQ R10, R9; \ - MULXQ 16+x, R10, AX; ADCQ R11, R10; \ - MULXQ 24+x, R11, R12; ADCQ AX, R11; \ - ;;;;;;;;;;;;;;;;;;;;; ADCQ $0, R12; \ - MOVL $38, DX; /* 2*C = 38 = 2^256 MOD 2^255-19*/ \ - IMULQ DX, R12; \ - ADDQ R12, R8; \ - ADCQ $0, R9; MOVQ R9, 8+z; \ - ADCQ $0, R10; MOVQ R10, 16+z; \ - ADCQ $0, R11; MOVQ R11, 24+z; \ - MOVQ $0, R12; \ - CMOVQCS DX, R12; \ - ADDQ R12, R8; MOVQ R8, 0+z; - -#define mulA24Legacy \ - multiplyA24Leg(0(DI),0(SI)) -#define mulA24Bmi2Adx \ - multiplyA24Adx(0(DI),0(SI)) - -// func mulA24Amd64(z, x *fp255.Elt) -TEXT ·mulA24Amd64(SB),NOSPLIT,$0-16 - MOVQ z+0(FP), DI - MOVQ x+8(FP), SI - CHECK_BMI2ADX(LMA24, mulA24Legacy, mulA24Bmi2Adx) - - -// func ladderStepAmd64(w *[5]fp255.Elt, b uint) -// ladderStepAmd64 calculates a point addition and doubling as follows: -// (x2,z2) = 2*(x2,z2) and (x3,z3) = (x2,z2)+(x3,z3) using as a difference (x1,-). -// work = (x1,x2,z2,x3,z3) are five fp255.Elt of 32 bytes. -// stack = (t0,t1) are two fp.Elt of fp.Size bytes, and -// (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes. -TEXT ·ladderStepAmd64(SB),NOSPLIT,$192-16 - // Parameters - #define regWork DI - #define regMove SI - #define x1 0*Size(regWork) - #define x2 1*Size(regWork) - #define z2 2*Size(regWork) - #define x3 3*Size(regWork) - #define z3 4*Size(regWork) - // Local variables - #define t0 0*Size(SP) - #define t1 1*Size(SP) - #define b0 2*Size(SP) - #define b1 4*Size(SP) - MOVQ w+0(FP), regWork - MOVQ b+8(FP), regMove - CHECK_BMI2ADX(LLADSTEP, ladderStepLeg, ladderStepBmi2Adx) - #undef regWork - #undef regMove - #undef x1 - #undef x2 - #undef z2 - #undef x3 - #undef z3 - #undef t0 - #undef t1 - #undef b0 - #undef b1 - -// func diffAddAmd64(w *[5]fp255.Elt, b uint) -// diffAddAmd64 calculates a differential point addition using a precomputed point. -// (x1,z1) = (x1,z1)+(mu) using a difference point (x2,z2) -// w = (mu,x1,z1,x2,z2) are five fp.Elt, and -// stack = (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes. -TEXT ·diffAddAmd64(SB),NOSPLIT,$128-16 - // Parameters - #define regWork DI - #define regSwap SI - #define ui 0*Size(regWork) - #define x1 1*Size(regWork) - #define z1 2*Size(regWork) - #define x2 3*Size(regWork) - #define z2 4*Size(regWork) - // Local variables - #define b0 0*Size(SP) - #define b1 2*Size(SP) - MOVQ w+0(FP), regWork - MOVQ b+8(FP), regSwap - cswap(x1,x2,regSwap) - cswap(z1,z2,regSwap) - CHECK_BMI2ADX(LDIFADD, difAddLeg, difAddBmi2Adx) - #undef regWork - #undef regSwap - #undef ui - #undef x1 - #undef z1 - #undef x2 - #undef z2 - #undef b0 - #undef b1 - -// func doubleAmd64(x, z *fp255.Elt) -// doubleAmd64 calculates a point doubling (x1,z1) = 2*(x1,z1). -// stack = (t0,t1) are two fp.Elt of fp.Size bytes, and -// (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes. -TEXT ·doubleAmd64(SB),NOSPLIT,$192-16 - // Parameters - #define x1 0(DI) - #define z1 0(SI) - // Local variables - #define t0 0*Size(SP) - #define t1 1*Size(SP) - #define b0 2*Size(SP) - #define b1 4*Size(SP) - MOVQ x+0(FP), DI - MOVQ z+8(FP), SI - CHECK_BMI2ADX(LDOUB,doubleLeg,doubleBmi2Adx) - #undef x1 - #undef z1 - #undef t0 - #undef t1 - #undef b0 - #undef b1 diff --git a/vendor/github.com/cloudflare/circl/dh/x25519/curve_generic.go b/vendor/github.com/cloudflare/circl/dh/x25519/curve_generic.go deleted file mode 100644 index dae67ea37df..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x25519/curve_generic.go +++ /dev/null @@ -1,85 +0,0 @@ -package x25519 - -import ( - "encoding/binary" - "math/bits" - - fp "github.com/cloudflare/circl/math/fp25519" -) - -func doubleGeneric(x, z *fp.Elt) { - t0, t1 := &fp.Elt{}, &fp.Elt{} - fp.AddSub(x, z) - fp.Sqr(x, x) - fp.Sqr(z, z) - fp.Sub(t0, x, z) - mulA24Generic(t1, t0) - fp.Add(t1, t1, z) - fp.Mul(x, x, z) - fp.Mul(z, t0, t1) -} - -func diffAddGeneric(w *[5]fp.Elt, b uint) { - mu, x1, z1, x2, z2 := &w[0], &w[1], &w[2], &w[3], &w[4] - fp.Cswap(x1, x2, b) - fp.Cswap(z1, z2, b) - fp.AddSub(x1, z1) - fp.Mul(z1, z1, mu) - fp.AddSub(x1, z1) - fp.Sqr(x1, x1) - fp.Sqr(z1, z1) - fp.Mul(x1, x1, z2) - fp.Mul(z1, z1, x2) -} - -func ladderStepGeneric(w *[5]fp.Elt, b uint) { - x1, x2, z2, x3, z3 := &w[0], &w[1], &w[2], &w[3], &w[4] - t0 := &fp.Elt{} - t1 := &fp.Elt{} - fp.AddSub(x2, z2) - fp.AddSub(x3, z3) - fp.Mul(t0, x2, z3) - fp.Mul(t1, x3, z2) - fp.AddSub(t0, t1) - fp.Cmov(x2, x3, b) - fp.Cmov(z2, z3, b) - fp.Sqr(x3, t0) - fp.Sqr(z3, t1) - fp.Mul(z3, x1, z3) - fp.Sqr(x2, x2) - fp.Sqr(z2, z2) - fp.Sub(t0, x2, z2) - mulA24Generic(t1, t0) - fp.Add(t1, t1, z2) - fp.Mul(x2, x2, z2) - fp.Mul(z2, t0, t1) -} - -func mulA24Generic(z, x *fp.Elt) { - const A24 = 121666 - const n = 8 - var xx [4]uint64 - for i := range xx { - xx[i] = binary.LittleEndian.Uint64(x[i*n : (i+1)*n]) - } - - h0, l0 := bits.Mul64(xx[0], A24) - h1, l1 := bits.Mul64(xx[1], A24) - h2, l2 := bits.Mul64(xx[2], A24) - h3, l3 := bits.Mul64(xx[3], A24) - - var c3 uint64 - l1, c0 := bits.Add64(h0, l1, 0) - l2, c1 := bits.Add64(h1, l2, c0) - l3, c2 := bits.Add64(h2, l3, c1) - l4, _ := bits.Add64(h3, 0, c2) - _, l4 = bits.Mul64(l4, 38) - l0, c0 = bits.Add64(l0, l4, 0) - xx[1], c1 = bits.Add64(l1, 0, c0) - xx[2], c2 = bits.Add64(l2, 0, c1) - xx[3], c3 = bits.Add64(l3, 0, c2) - xx[0], _ = bits.Add64(l0, (-c3)&38, 0) - for i := range xx { - binary.LittleEndian.PutUint64(z[i*n:(i+1)*n], xx[i]) - } -} diff --git a/vendor/github.com/cloudflare/circl/dh/x25519/curve_noasm.go b/vendor/github.com/cloudflare/circl/dh/x25519/curve_noasm.go deleted file mode 100644 index 07fab97d2af..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x25519/curve_noasm.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !amd64 || purego -// +build !amd64 purego - -package x25519 - -import fp "github.com/cloudflare/circl/math/fp25519" - -func double(x, z *fp.Elt) { doubleGeneric(x, z) } -func diffAdd(w *[5]fp.Elt, b uint) { diffAddGeneric(w, b) } -func ladderStep(w *[5]fp.Elt, b uint) { ladderStepGeneric(w, b) } -func mulA24(z, x *fp.Elt) { mulA24Generic(z, x) } diff --git a/vendor/github.com/cloudflare/circl/dh/x25519/doc.go b/vendor/github.com/cloudflare/circl/dh/x25519/doc.go deleted file mode 100644 index 3ce102d1457..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x25519/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -Package x25519 provides Diffie-Hellman functions as specified in RFC-7748. - -Validation of public keys. - -The Diffie-Hellman function, as described in RFC-7748 [1], works for any -public key. However, if a different protocol requires contributory -behaviour [2,3], then the public keys must be validated against low-order -points [3,4]. To do that, the Shared function performs this validation -internally and returns false when the public key is invalid (i.e., it -is a low-order point). - -References: - - [1] RFC7748 by Langley, Hamburg, Turner (https://rfc-editor.org/rfc/rfc7748.txt) - - [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html) - - [3] Bernstein (https://cr.yp.to/ecdh.html#validate) - - [4] Cremers&Jackson (https://eprint.iacr.org/2019/526) -*/ -package x25519 diff --git a/vendor/github.com/cloudflare/circl/dh/x25519/key.go b/vendor/github.com/cloudflare/circl/dh/x25519/key.go deleted file mode 100644 index c76f72ac7fa..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x25519/key.go +++ /dev/null @@ -1,47 +0,0 @@ -package x25519 - -import ( - "crypto/subtle" - - fp "github.com/cloudflare/circl/math/fp25519" -) - -// Size is the length in bytes of a X25519 key. -const Size = 32 - -// Key represents a X25519 key. -type Key [Size]byte - -func (k *Key) clamp(in *Key) *Key { - *k = *in - k[0] &= 248 - k[31] = (k[31] & 127) | 64 - return k -} - -// isValidPubKey verifies if the public key is not a low-order point. -func (k *Key) isValidPubKey() bool { - fp.Modp((*fp.Elt)(k)) - var isLowOrder int - for _, P := range lowOrderPoints { - isLowOrder |= subtle.ConstantTimeCompare(P[:], k[:]) - } - return isLowOrder == 0 -} - -// KeyGen obtains a public key given a secret key. -func KeyGen(public, secret *Key) { - ladderJoye(public.clamp(secret)) -} - -// Shared calculates Alice's shared key from Alice's secret key and Bob's -// public key returning true on success. A failure case happens when the public -// key is a low-order point, thus the shared key is all-zeros and the function -// returns false. -func Shared(shared, secret, public *Key) bool { - validPk := *public - validPk[31] &= (1 << (255 % 8)) - 1 - ok := validPk.isValidPubKey() - ladderMontgomery(shared.clamp(secret), &validPk) - return ok -} diff --git a/vendor/github.com/cloudflare/circl/dh/x25519/table.go b/vendor/github.com/cloudflare/circl/dh/x25519/table.go deleted file mode 100644 index 28c8c4ac032..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x25519/table.go +++ /dev/null @@ -1,268 +0,0 @@ -package x25519 - -import "github.com/cloudflare/circl/math/fp25519" - -// tableGenerator contains the set of points: -// -// t[i] = (xi+1)/(xi-1), -// -// where (xi,yi) = 2^iG and G is the generator point -// Size = (256)*(256/8) = 8192 bytes. -var tableGenerator = [256 * fp25519.Size]byte{ - /* (2^ 0)P */ 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5f, - /* (2^ 1)P */ 0x96, 0xfe, 0xaa, 0x16, 0xf4, 0x20, 0x82, 0x6b, 0x34, 0x6a, 0x56, 0x4f, 0x2b, 0xeb, 0xeb, 0x82, 0x0f, 0x95, 0xa5, 0x75, 0xb0, 0xa5, 0xa9, 0xd5, 0xf4, 0x88, 0x24, 0x4b, 0xcf, 0xb2, 0x42, 0x51, - /* (2^ 2)P */ 0x0c, 0x68, 0x69, 0x00, 0x75, 0xbc, 0xae, 0x6a, 0x41, 0x9c, 0xf9, 0xa0, 0x20, 0x78, 0xcf, 0x89, 0xf4, 0xd0, 0x56, 0x3b, 0x18, 0xd9, 0x58, 0x2a, 0xa4, 0x11, 0x60, 0xe3, 0x80, 0xca, 0x5a, 0x4b, - /* (2^ 3)P */ 0x5d, 0x74, 0x29, 0x8c, 0x34, 0x32, 0x91, 0x32, 0xd7, 0x2f, 0x64, 0xe1, 0x16, 0xe6, 0xa2, 0xf4, 0x34, 0xbc, 0x67, 0xff, 0x03, 0xbb, 0x45, 0x1e, 0x4a, 0x9b, 0x2a, 0xf4, 0xd0, 0x12, 0x69, 0x30, - /* (2^ 4)P */ 0x54, 0x71, 0xaf, 0xe6, 0x07, 0x65, 0x88, 0xff, 0x2f, 0xc8, 0xee, 0xdf, 0x13, 0x0e, 0xf5, 0x04, 0xce, 0xb5, 0xba, 0x2a, 0xe8, 0x2f, 0x51, 0xaa, 0x22, 0xf2, 0xd5, 0x68, 0x1a, 0x25, 0x4e, 0x17, - /* (2^ 5)P */ 0x98, 0x88, 0x02, 0x82, 0x0d, 0x70, 0x96, 0xcf, 0xc5, 0x02, 0x2c, 0x0a, 0x37, 0xe3, 0x43, 0x17, 0xaa, 0x6e, 0xe8, 0xb4, 0x98, 0xec, 0x9e, 0x37, 0x2e, 0x48, 0xe0, 0x51, 0x8a, 0x88, 0x59, 0x0c, - /* (2^ 6)P */ 0x89, 0xd1, 0xb5, 0x99, 0xd6, 0xf1, 0xcb, 0xfb, 0x84, 0xdc, 0x9f, 0x8e, 0xd5, 0xf0, 0xae, 0xac, 0x14, 0x76, 0x1f, 0x23, 0x06, 0x0d, 0xc2, 0xc1, 0x72, 0xf9, 0x74, 0xa2, 0x8d, 0x21, 0x38, 0x29, - /* (2^ 7)P */ 0x18, 0x7f, 0x1d, 0xff, 0xbe, 0x49, 0xaf, 0xf6, 0xc2, 0xc9, 0x7a, 0x38, 0x22, 0x1c, 0x54, 0xcc, 0x6b, 0xc5, 0x15, 0x40, 0xef, 0xc9, 0xfc, 0x96, 0xa9, 0x13, 0x09, 0x69, 0x7c, 0x62, 0xc1, 0x69, - /* (2^ 8)P */ 0x0e, 0xdb, 0x33, 0x47, 0x2f, 0xfd, 0x86, 0x7a, 0xe9, 0x7d, 0x08, 0x9e, 0xf2, 0xc4, 0xb8, 0xfd, 0x29, 0xa2, 0xa2, 0x8e, 0x1a, 0x4b, 0x5e, 0x09, 0x79, 0x7a, 0xb3, 0x29, 0xc8, 0xa7, 0xd7, 0x1a, - /* (2^ 9)P */ 0xc0, 0xa0, 0x7e, 0xd1, 0xca, 0x89, 0x2d, 0x34, 0x51, 0x20, 0xed, 0xcc, 0xa6, 0xdd, 0xbe, 0x67, 0x74, 0x2f, 0xb4, 0x2b, 0xbf, 0x31, 0xca, 0x19, 0xbb, 0xac, 0x80, 0x49, 0xc8, 0xb4, 0xf7, 0x3d, - /* (2^ 10)P */ 0x83, 0xd8, 0x0a, 0xc8, 0x4d, 0x44, 0xc6, 0xa8, 0x85, 0xab, 0xe3, 0x66, 0x03, 0x44, 0x1e, 0xb9, 0xd8, 0xf6, 0x64, 0x01, 0xa0, 0xcd, 0x15, 0xc2, 0x68, 0xe6, 0x47, 0xf2, 0x6e, 0x7c, 0x86, 0x3d, - /* (2^ 11)P */ 0x8c, 0x65, 0x3e, 0xcc, 0x2b, 0x58, 0xdd, 0xc7, 0x28, 0x55, 0x0e, 0xee, 0x48, 0x47, 0x2c, 0xfd, 0x71, 0x4f, 0x9f, 0xcc, 0x95, 0x9b, 0xfd, 0xa0, 0xdf, 0x5d, 0x67, 0xb0, 0x71, 0xd8, 0x29, 0x75, - /* (2^ 12)P */ 0x78, 0xbd, 0x3c, 0x2d, 0xb4, 0x68, 0xf5, 0xb8, 0x82, 0xda, 0xf3, 0x91, 0x1b, 0x01, 0x33, 0x12, 0x62, 0x3b, 0x7c, 0x4a, 0xcd, 0x6c, 0xce, 0x2d, 0x03, 0x86, 0x49, 0x9e, 0x8e, 0xfc, 0xe7, 0x75, - /* (2^ 13)P */ 0xec, 0xb6, 0xd0, 0xfc, 0xf1, 0x13, 0x4f, 0x2f, 0x45, 0x7a, 0xff, 0x29, 0x1f, 0xca, 0xa8, 0xf1, 0x9b, 0xe2, 0x81, 0x29, 0xa7, 0xc1, 0x49, 0xc2, 0x6a, 0xb5, 0x83, 0x8c, 0xbb, 0x0d, 0xbe, 0x6e, - /* (2^ 14)P */ 0x22, 0xb2, 0x0b, 0x17, 0x8d, 0xfa, 0x14, 0x71, 0x5f, 0x93, 0x93, 0xbf, 0xd5, 0xdc, 0xa2, 0x65, 0x9a, 0x97, 0x9c, 0xb5, 0x68, 0x1f, 0xc4, 0xbd, 0x89, 0x92, 0xce, 0xa2, 0x79, 0xef, 0x0e, 0x2f, - /* (2^ 15)P */ 0xce, 0x37, 0x3c, 0x08, 0x0c, 0xbf, 0xec, 0x42, 0x22, 0x63, 0x49, 0xec, 0x09, 0xbc, 0x30, 0x29, 0x0d, 0xac, 0xfe, 0x9c, 0xc1, 0xb0, 0x94, 0xf2, 0x80, 0xbb, 0xfa, 0xed, 0x4b, 0xaa, 0x80, 0x37, - /* (2^ 16)P */ 0x29, 0xd9, 0xea, 0x7c, 0x3e, 0x7d, 0xc1, 0x56, 0xc5, 0x22, 0x57, 0x2e, 0xeb, 0x4b, 0xcb, 0xe7, 0x5a, 0xe1, 0xbf, 0x2d, 0x73, 0x31, 0xe9, 0x0c, 0xf8, 0x52, 0x10, 0x62, 0xc7, 0x83, 0xb8, 0x41, - /* (2^ 17)P */ 0x50, 0x53, 0xd2, 0xc3, 0xa0, 0x5c, 0xf7, 0xdb, 0x51, 0xe3, 0xb1, 0x6e, 0x08, 0xbe, 0x36, 0x29, 0x12, 0xb2, 0xa9, 0xb4, 0x3c, 0xe0, 0x36, 0xc9, 0xaa, 0x25, 0x22, 0x32, 0x82, 0xbf, 0x45, 0x1d, - /* (2^ 18)P */ 0xc5, 0x4c, 0x02, 0x6a, 0x03, 0xb1, 0x1a, 0xe8, 0x72, 0x9a, 0x4c, 0x30, 0x1c, 0x20, 0x12, 0xe2, 0xfc, 0xb1, 0x32, 0x68, 0xba, 0x3f, 0xd7, 0xc5, 0x81, 0x95, 0x83, 0x4d, 0x5a, 0xdb, 0xff, 0x20, - /* (2^ 19)P */ 0xad, 0x0f, 0x5d, 0xbe, 0x67, 0xd3, 0x83, 0xa2, 0x75, 0x44, 0x16, 0x8b, 0xca, 0x25, 0x2b, 0x6c, 0x2e, 0xf2, 0xaa, 0x7c, 0x46, 0x35, 0x49, 0x9d, 0x49, 0xff, 0x85, 0xee, 0x8e, 0x40, 0x66, 0x51, - /* (2^ 20)P */ 0x61, 0xe3, 0xb4, 0xfa, 0xa2, 0xba, 0x67, 0x3c, 0xef, 0x5c, 0xf3, 0x7e, 0xc6, 0x33, 0xe4, 0xb3, 0x1c, 0x9b, 0x15, 0x41, 0x92, 0x72, 0x59, 0x52, 0x33, 0xab, 0xb0, 0xd5, 0x92, 0x18, 0x62, 0x6a, - /* (2^ 21)P */ 0xcb, 0xcd, 0x55, 0x75, 0x38, 0x4a, 0xb7, 0x20, 0x3f, 0x92, 0x08, 0x12, 0x0e, 0xa1, 0x2a, 0x53, 0xd1, 0x1d, 0x28, 0x62, 0x77, 0x7b, 0xa1, 0xea, 0xbf, 0x44, 0x5c, 0xf0, 0x43, 0x34, 0xab, 0x61, - /* (2^ 22)P */ 0xf8, 0xde, 0x24, 0x23, 0x42, 0x6c, 0x7a, 0x25, 0x7f, 0xcf, 0xe3, 0x17, 0x10, 0x6c, 0x1c, 0x13, 0x57, 0xa2, 0x30, 0xf6, 0x39, 0x87, 0x75, 0x23, 0x80, 0x85, 0xa7, 0x01, 0x7a, 0x40, 0x5a, 0x29, - /* (2^ 23)P */ 0xd9, 0xa8, 0x5d, 0x6d, 0x24, 0x43, 0xc4, 0xf8, 0x5d, 0xfa, 0x52, 0x0c, 0x45, 0x75, 0xd7, 0x19, 0x3d, 0xf8, 0x1b, 0x73, 0x92, 0xfc, 0xfc, 0x2a, 0x00, 0x47, 0x2b, 0x1b, 0xe8, 0xc8, 0x10, 0x7d, - /* (2^ 24)P */ 0x0b, 0xa2, 0xba, 0x70, 0x1f, 0x27, 0xe0, 0xc8, 0x57, 0x39, 0xa6, 0x7c, 0x86, 0x48, 0x37, 0x99, 0xbb, 0xd4, 0x7e, 0xcb, 0xb3, 0xef, 0x12, 0x54, 0x75, 0x29, 0xe6, 0x73, 0x61, 0xd3, 0x96, 0x31, - /* (2^ 25)P */ 0xfc, 0xdf, 0xc7, 0x41, 0xd1, 0xca, 0x5b, 0xde, 0x48, 0xc8, 0x95, 0xb3, 0xd2, 0x8c, 0xcc, 0x47, 0xcb, 0xf3, 0x1a, 0xe1, 0x42, 0xd9, 0x4c, 0xa3, 0xc2, 0xce, 0x4e, 0xd0, 0xf2, 0xdb, 0x56, 0x02, - /* (2^ 26)P */ 0x7f, 0x66, 0x0e, 0x4b, 0xe9, 0xb7, 0x5a, 0x87, 0x10, 0x0d, 0x85, 0xc0, 0x83, 0xdd, 0xd4, 0xca, 0x9f, 0xc7, 0x72, 0x4e, 0x8f, 0x2e, 0xf1, 0x47, 0x9b, 0xb1, 0x85, 0x8c, 0xbb, 0x87, 0x1a, 0x5f, - /* (2^ 27)P */ 0xb8, 0x51, 0x7f, 0x43, 0xb6, 0xd0, 0xe9, 0x7a, 0x65, 0x90, 0x87, 0x18, 0x55, 0xce, 0xc7, 0x12, 0xee, 0x7a, 0xf7, 0x5c, 0xfe, 0x09, 0xde, 0x2a, 0x27, 0x56, 0x2c, 0x7d, 0x2f, 0x5a, 0xa0, 0x23, - /* (2^ 28)P */ 0x9a, 0x16, 0x7c, 0xf1, 0x28, 0xe1, 0x08, 0x59, 0x2d, 0x85, 0xd0, 0x8a, 0xdd, 0x98, 0x74, 0xf7, 0x64, 0x2f, 0x10, 0xab, 0xce, 0xc4, 0xb4, 0x74, 0x45, 0x98, 0x13, 0x10, 0xdd, 0xba, 0x3a, 0x18, - /* (2^ 29)P */ 0xac, 0xaa, 0x92, 0xaa, 0x8d, 0xba, 0x65, 0xb1, 0x05, 0x67, 0x38, 0x99, 0x95, 0xef, 0xc5, 0xd5, 0xd1, 0x40, 0xfc, 0xf8, 0x0c, 0x8f, 0x2f, 0xbe, 0x14, 0x45, 0x20, 0xee, 0x35, 0xe6, 0x01, 0x27, - /* (2^ 30)P */ 0x14, 0x65, 0x15, 0x20, 0x00, 0xa8, 0x9f, 0x62, 0xce, 0xc1, 0xa8, 0x64, 0x87, 0x86, 0x23, 0xf2, 0x0e, 0x06, 0x3f, 0x0b, 0xff, 0x4f, 0x89, 0x5b, 0xfa, 0xa3, 0x08, 0xf7, 0x4c, 0x94, 0xd9, 0x60, - /* (2^ 31)P */ 0x1f, 0x20, 0x7a, 0x1c, 0x1a, 0x00, 0xea, 0xae, 0x63, 0xce, 0xe2, 0x3e, 0x63, 0x6a, 0xf1, 0xeb, 0xe1, 0x07, 0x7a, 0x4c, 0x59, 0x09, 0x77, 0x6f, 0xcb, 0x08, 0x02, 0x0d, 0x15, 0x58, 0xb9, 0x79, - /* (2^ 32)P */ 0xe7, 0x10, 0xd4, 0x01, 0x53, 0x5e, 0xb5, 0x24, 0x4d, 0xc8, 0xfd, 0xf3, 0xdf, 0x4e, 0xa3, 0xe3, 0xd8, 0x32, 0x40, 0x90, 0xe4, 0x68, 0x87, 0xd8, 0xec, 0xae, 0x3a, 0x7b, 0x42, 0x84, 0x13, 0x13, - /* (2^ 33)P */ 0x14, 0x4f, 0x23, 0x86, 0x12, 0xe5, 0x05, 0x84, 0x29, 0xc5, 0xb4, 0xad, 0x39, 0x47, 0xdc, 0x14, 0xfd, 0x4f, 0x63, 0x50, 0xb2, 0xb5, 0xa2, 0xb8, 0x93, 0xff, 0xa7, 0xd8, 0x4a, 0xa9, 0xe2, 0x2f, - /* (2^ 34)P */ 0xdd, 0xfa, 0x43, 0xe8, 0xef, 0x57, 0x5c, 0xec, 0x18, 0x99, 0xbb, 0xf0, 0x40, 0xce, 0x43, 0x28, 0x05, 0x63, 0x3d, 0xcf, 0xd6, 0x61, 0xb5, 0xa4, 0x7e, 0x77, 0xfb, 0xe8, 0xbd, 0x29, 0x36, 0x74, - /* (2^ 35)P */ 0x8f, 0x73, 0xaf, 0xbb, 0x46, 0xdd, 0x3e, 0x34, 0x51, 0xa6, 0x01, 0xb1, 0x28, 0x18, 0x98, 0xed, 0x7a, 0x79, 0x2c, 0x88, 0x0b, 0x76, 0x01, 0xa4, 0x30, 0x87, 0xc8, 0x8d, 0xe2, 0x23, 0xc2, 0x1f, - /* (2^ 36)P */ 0x0e, 0xba, 0x0f, 0xfc, 0x91, 0x4e, 0x60, 0x48, 0xa4, 0x6f, 0x2c, 0x05, 0x8f, 0xf7, 0x37, 0xb6, 0x9c, 0x23, 0xe9, 0x09, 0x3d, 0xac, 0xcc, 0x91, 0x7c, 0x68, 0x7a, 0x43, 0xd4, 0xee, 0xf7, 0x23, - /* (2^ 37)P */ 0x00, 0xd8, 0x9b, 0x8d, 0x11, 0xb1, 0x73, 0x51, 0xa7, 0xd4, 0x89, 0x31, 0xb6, 0x41, 0xd6, 0x29, 0x86, 0xc5, 0xbb, 0x88, 0x79, 0x17, 0xbf, 0xfd, 0xf5, 0x1d, 0xd8, 0xca, 0x4f, 0x89, 0x59, 0x29, - /* (2^ 38)P */ 0x99, 0xc8, 0xbb, 0xb4, 0xf3, 0x8e, 0xbc, 0xae, 0xb9, 0x92, 0x69, 0xb2, 0x5a, 0x99, 0x48, 0x41, 0xfb, 0x2c, 0xf9, 0x34, 0x01, 0x0b, 0xe2, 0x24, 0xe8, 0xde, 0x05, 0x4a, 0x89, 0x58, 0xd1, 0x40, - /* (2^ 39)P */ 0xf6, 0x76, 0xaf, 0x85, 0x11, 0x0b, 0xb0, 0x46, 0x79, 0x7a, 0x18, 0x73, 0x78, 0xc7, 0xba, 0x26, 0x5f, 0xff, 0x8f, 0xab, 0x95, 0xbf, 0xc0, 0x3d, 0xd7, 0x24, 0x55, 0x94, 0xd8, 0x8b, 0x60, 0x2a, - /* (2^ 40)P */ 0x02, 0x63, 0x44, 0xbd, 0x88, 0x95, 0x44, 0x26, 0x9c, 0x43, 0x88, 0x03, 0x1c, 0xc2, 0x4b, 0x7c, 0xb2, 0x11, 0xbd, 0x83, 0xf3, 0xa4, 0x98, 0x8e, 0xb9, 0x76, 0xd8, 0xc9, 0x7b, 0x8d, 0x21, 0x26, - /* (2^ 41)P */ 0x8a, 0x17, 0x7c, 0x99, 0x42, 0x15, 0x08, 0xe3, 0x6f, 0x60, 0xb6, 0x6f, 0xa8, 0x29, 0x2d, 0x3c, 0x74, 0x93, 0x27, 0xfa, 0x36, 0x77, 0x21, 0x5c, 0xfa, 0xb1, 0xfe, 0x4a, 0x73, 0x05, 0xde, 0x7d, - /* (2^ 42)P */ 0xab, 0x2b, 0xd4, 0x06, 0x39, 0x0e, 0xf1, 0x3b, 0x9c, 0x64, 0x80, 0x19, 0x3e, 0x80, 0xf7, 0xe4, 0x7a, 0xbf, 0x95, 0x95, 0xf8, 0x3b, 0x05, 0xe6, 0x30, 0x55, 0x24, 0xda, 0x38, 0xaf, 0x4f, 0x39, - /* (2^ 43)P */ 0xf4, 0x28, 0x69, 0x89, 0x58, 0xfb, 0x8e, 0x7a, 0x3c, 0x11, 0x6a, 0xcc, 0xe9, 0x78, 0xc7, 0xfb, 0x6f, 0x59, 0xaf, 0x30, 0xe3, 0x0c, 0x67, 0x72, 0xf7, 0x6c, 0x3d, 0x1d, 0xa8, 0x22, 0xf2, 0x48, - /* (2^ 44)P */ 0xa7, 0xca, 0x72, 0x0d, 0x41, 0xce, 0x1f, 0xf0, 0x95, 0x55, 0x3b, 0x21, 0xc7, 0xec, 0x20, 0x5a, 0x83, 0x14, 0xfa, 0xc1, 0x65, 0x11, 0xc2, 0x7b, 0x41, 0xa7, 0xa8, 0x1d, 0xe3, 0x9a, 0xf8, 0x07, - /* (2^ 45)P */ 0xf9, 0x0f, 0x83, 0xc6, 0xb4, 0xc2, 0xd2, 0x05, 0x93, 0x62, 0x31, 0xc6, 0x0f, 0x33, 0x3e, 0xd4, 0x04, 0xa9, 0xd3, 0x96, 0x0a, 0x59, 0xa5, 0xa5, 0xb6, 0x33, 0x53, 0xa6, 0x91, 0xdb, 0x5e, 0x70, - /* (2^ 46)P */ 0xf7, 0xa5, 0xb9, 0x0b, 0x5e, 0xe1, 0x8e, 0x04, 0x5d, 0xaf, 0x0a, 0x9e, 0xca, 0xcf, 0x40, 0x32, 0x0b, 0xa4, 0xc4, 0xed, 0xce, 0x71, 0x4b, 0x8f, 0x6d, 0x4a, 0x54, 0xde, 0xa3, 0x0d, 0x1c, 0x62, - /* (2^ 47)P */ 0x91, 0x40, 0x8c, 0xa0, 0x36, 0x28, 0x87, 0x92, 0x45, 0x14, 0xc9, 0x10, 0xb0, 0x75, 0x83, 0xce, 0x94, 0x63, 0x27, 0x4f, 0x52, 0xeb, 0x72, 0x8a, 0x35, 0x36, 0xc8, 0x7e, 0xfa, 0xfc, 0x67, 0x26, - /* (2^ 48)P */ 0x2a, 0x75, 0xe8, 0x45, 0x33, 0x17, 0x4c, 0x7f, 0xa5, 0x79, 0x70, 0xee, 0xfe, 0x47, 0x1b, 0x06, 0x34, 0xff, 0x86, 0x9f, 0xfa, 0x9a, 0xdd, 0x25, 0x9c, 0xc8, 0x5d, 0x42, 0xf5, 0xce, 0x80, 0x37, - /* (2^ 49)P */ 0xe9, 0xb4, 0x3b, 0x51, 0x5a, 0x03, 0x46, 0x1a, 0xda, 0x5a, 0x57, 0xac, 0x79, 0xf3, 0x1e, 0x3e, 0x50, 0x4b, 0xa2, 0x5f, 0x1c, 0x5f, 0x8c, 0xc7, 0x22, 0x9f, 0xfd, 0x34, 0x76, 0x96, 0x1a, 0x32, - /* (2^ 50)P */ 0xfa, 0x27, 0x6e, 0x82, 0xb8, 0x07, 0x67, 0x94, 0xd0, 0x6f, 0x50, 0x4c, 0xd6, 0x84, 0xca, 0x3d, 0x36, 0x14, 0xe9, 0x75, 0x80, 0x21, 0x89, 0xc1, 0x84, 0x84, 0x3b, 0x9b, 0x16, 0x84, 0x92, 0x6d, - /* (2^ 51)P */ 0xdf, 0x2d, 0x3f, 0x38, 0x40, 0xe8, 0x67, 0x3a, 0x75, 0x9b, 0x4f, 0x0c, 0xa3, 0xc9, 0xee, 0x33, 0x47, 0xef, 0x83, 0xa7, 0x6f, 0xc8, 0xc7, 0x3e, 0xc4, 0xfb, 0xc9, 0xba, 0x9f, 0x44, 0xec, 0x26, - /* (2^ 52)P */ 0x7d, 0x9e, 0x9b, 0xa0, 0xcb, 0x38, 0x0f, 0x5c, 0x8c, 0x47, 0xa3, 0x62, 0xc7, 0x8c, 0x16, 0x81, 0x1c, 0x12, 0xfc, 0x06, 0xd3, 0xb0, 0x23, 0x3e, 0xdd, 0xdc, 0xef, 0xa5, 0xa0, 0x8a, 0x23, 0x5a, - /* (2^ 53)P */ 0xff, 0x43, 0xea, 0xc4, 0x21, 0x61, 0xa2, 0x1b, 0xb5, 0x32, 0x88, 0x7c, 0x7f, 0xc7, 0xf8, 0x36, 0x9a, 0xf9, 0xdc, 0x0a, 0x0b, 0xea, 0xfb, 0x88, 0xf9, 0xeb, 0x5b, 0xc2, 0x8e, 0x93, 0xa9, 0x5c, - /* (2^ 54)P */ 0xa0, 0xcd, 0xfc, 0x51, 0x5e, 0x6a, 0x43, 0xd5, 0x3b, 0x89, 0xcd, 0xc2, 0x97, 0x47, 0xbc, 0x1d, 0x08, 0x4a, 0x22, 0xd3, 0x65, 0x6a, 0x34, 0x19, 0x66, 0xf4, 0x9a, 0x9b, 0xe4, 0x34, 0x50, 0x0f, - /* (2^ 55)P */ 0x6e, 0xb9, 0xe0, 0xa1, 0x67, 0x39, 0x3c, 0xf2, 0x88, 0x4d, 0x7a, 0x86, 0xfa, 0x08, 0x8b, 0xe5, 0x79, 0x16, 0x34, 0xa7, 0xc6, 0xab, 0x2f, 0xfb, 0x46, 0x69, 0x02, 0xb6, 0x1e, 0x38, 0x75, 0x2a, - /* (2^ 56)P */ 0xac, 0x20, 0x94, 0xc1, 0xe4, 0x3b, 0x0a, 0xc8, 0xdc, 0xb6, 0xf2, 0x81, 0xc6, 0xf6, 0xb1, 0x66, 0x88, 0x33, 0xe9, 0x61, 0x67, 0x03, 0xf7, 0x7c, 0xc4, 0xa4, 0x60, 0xa6, 0xd8, 0xbb, 0xab, 0x25, - /* (2^ 57)P */ 0x98, 0x51, 0xfd, 0x14, 0xba, 0x12, 0xea, 0x91, 0xa9, 0xff, 0x3c, 0x4a, 0xfc, 0x50, 0x49, 0x68, 0x28, 0xad, 0xf5, 0x30, 0x21, 0x84, 0x26, 0xf8, 0x41, 0xa4, 0x01, 0x53, 0xf7, 0x88, 0xa9, 0x3e, - /* (2^ 58)P */ 0x6f, 0x8c, 0x5f, 0x69, 0x9a, 0x10, 0x78, 0xc9, 0xf3, 0xc3, 0x30, 0x05, 0x4a, 0xeb, 0x46, 0x17, 0x95, 0x99, 0x45, 0xb4, 0x77, 0x6d, 0x4d, 0x44, 0xc7, 0x5c, 0x4e, 0x05, 0x8c, 0x2b, 0x95, 0x75, - /* (2^ 59)P */ 0xaa, 0xd6, 0xf4, 0x15, 0x79, 0x3f, 0x70, 0xa3, 0xd8, 0x47, 0x26, 0x2f, 0x20, 0x46, 0xc3, 0x66, 0x4b, 0x64, 0x1d, 0x81, 0xdf, 0x69, 0x14, 0xd0, 0x1f, 0xd7, 0xa5, 0x81, 0x7d, 0xa4, 0xfe, 0x77, - /* (2^ 60)P */ 0x81, 0xa3, 0x7c, 0xf5, 0x9e, 0x52, 0xe9, 0xc5, 0x1a, 0x88, 0x2f, 0xce, 0xb9, 0xb4, 0xee, 0x6e, 0xd6, 0x9b, 0x00, 0xe8, 0x28, 0x1a, 0xe9, 0xb6, 0xec, 0x3f, 0xfc, 0x9a, 0x3e, 0xbe, 0x80, 0x4b, - /* (2^ 61)P */ 0xc5, 0xd2, 0xae, 0x26, 0xc5, 0x73, 0x37, 0x7e, 0x9d, 0xa4, 0xc9, 0x53, 0xb4, 0xfc, 0x4a, 0x1b, 0x4d, 0xb2, 0xff, 0xba, 0xd7, 0xbd, 0x20, 0xa9, 0x0e, 0x40, 0x2d, 0x12, 0x9f, 0x69, 0x54, 0x7c, - /* (2^ 62)P */ 0xc8, 0x4b, 0xa9, 0x4f, 0xe1, 0xc8, 0x46, 0xef, 0x5e, 0xed, 0x52, 0x29, 0xce, 0x74, 0xb0, 0xe0, 0xd5, 0x85, 0xd8, 0xdb, 0xe1, 0x50, 0xa4, 0xbe, 0x2c, 0x71, 0x0f, 0x32, 0x49, 0x86, 0xb6, 0x61, - /* (2^ 63)P */ 0xd1, 0xbd, 0xcc, 0x09, 0x73, 0x5f, 0x48, 0x8a, 0x2d, 0x1a, 0x4d, 0x7d, 0x0d, 0x32, 0x06, 0xbd, 0xf4, 0xbe, 0x2d, 0x32, 0x73, 0x29, 0x23, 0x25, 0x70, 0xf7, 0x17, 0x8c, 0x75, 0xc4, 0x5d, 0x44, - /* (2^ 64)P */ 0x3c, 0x93, 0xc8, 0x7c, 0x17, 0x34, 0x04, 0xdb, 0x9f, 0x05, 0xea, 0x75, 0x21, 0xe8, 0x6f, 0xed, 0x34, 0xdb, 0x53, 0xc0, 0xfd, 0xbe, 0xfe, 0x1e, 0x99, 0xaf, 0x5d, 0xc6, 0x67, 0xe8, 0xdb, 0x4a, - /* (2^ 65)P */ 0xdf, 0x09, 0x06, 0xa9, 0xa2, 0x71, 0xcd, 0x3a, 0x50, 0x40, 0xd0, 0x6d, 0x85, 0x91, 0xe9, 0xe5, 0x3c, 0xc2, 0x57, 0x81, 0x68, 0x9b, 0xc6, 0x1e, 0x4d, 0xfe, 0x5c, 0x88, 0xf6, 0x27, 0x74, 0x69, - /* (2^ 66)P */ 0x51, 0xa8, 0xe1, 0x65, 0x9b, 0x7b, 0xbe, 0xd7, 0xdd, 0x36, 0xc5, 0x22, 0xd5, 0x28, 0x3d, 0xa0, 0x45, 0xb6, 0xd2, 0x8f, 0x65, 0x9d, 0x39, 0x28, 0xe1, 0x41, 0x26, 0x7c, 0xe1, 0xb7, 0xe5, 0x49, - /* (2^ 67)P */ 0xa4, 0x57, 0x04, 0x70, 0x98, 0x3a, 0x8c, 0x6f, 0x78, 0x67, 0xbb, 0x5e, 0xa2, 0xf0, 0x78, 0x50, 0x0f, 0x96, 0x82, 0xc3, 0xcb, 0x3c, 0x3c, 0xd1, 0xb1, 0x84, 0xdf, 0xa7, 0x58, 0x32, 0x00, 0x2e, - /* (2^ 68)P */ 0x1c, 0x6a, 0x29, 0xe6, 0x9b, 0xf3, 0xd1, 0x8a, 0xb2, 0xbf, 0x5f, 0x2a, 0x65, 0xaa, 0xee, 0xc1, 0xcb, 0xf3, 0x26, 0xfd, 0x73, 0x06, 0xee, 0x33, 0xcc, 0x2c, 0x9d, 0xa6, 0x73, 0x61, 0x25, 0x59, - /* (2^ 69)P */ 0x41, 0xfc, 0x18, 0x4e, 0xaa, 0x07, 0xea, 0x41, 0x1e, 0xa5, 0x87, 0x7c, 0x52, 0x19, 0xfc, 0xd9, 0x6f, 0xca, 0x31, 0x58, 0x80, 0xcb, 0xaa, 0xbd, 0x4f, 0x69, 0x16, 0xc9, 0x2d, 0x65, 0x5b, 0x44, - /* (2^ 70)P */ 0x15, 0x23, 0x17, 0xf2, 0xa7, 0xa3, 0x92, 0xce, 0x64, 0x99, 0x1b, 0xe1, 0x2d, 0x28, 0xdc, 0x1e, 0x4a, 0x31, 0x4c, 0xe0, 0xaf, 0x3a, 0x82, 0xa1, 0x86, 0xf5, 0x7c, 0x43, 0x94, 0x2d, 0x0a, 0x79, - /* (2^ 71)P */ 0x09, 0xe0, 0xf6, 0x93, 0xfb, 0x47, 0xc4, 0x71, 0x76, 0x52, 0x84, 0x22, 0x67, 0xa5, 0x22, 0x89, 0x69, 0x51, 0x4f, 0x20, 0x3b, 0x90, 0x70, 0xbf, 0xfe, 0x19, 0xa3, 0x1b, 0x89, 0x89, 0x7a, 0x2f, - /* (2^ 72)P */ 0x0c, 0x14, 0xe2, 0x77, 0xb5, 0x8e, 0xa0, 0x02, 0xf4, 0xdc, 0x7b, 0x42, 0xd4, 0x4e, 0x9a, 0xed, 0xd1, 0x3c, 0x32, 0xe4, 0x44, 0xec, 0x53, 0x52, 0x5b, 0x35, 0xe9, 0x14, 0x3c, 0x36, 0x88, 0x3e, - /* (2^ 73)P */ 0x8c, 0x0b, 0x11, 0x77, 0x42, 0xc1, 0x66, 0xaa, 0x90, 0x33, 0xa2, 0x10, 0x16, 0x39, 0xe0, 0x1a, 0xa2, 0xc2, 0x3f, 0xc9, 0x12, 0xbd, 0x30, 0x20, 0xab, 0xc7, 0x55, 0x95, 0x57, 0x41, 0xe1, 0x3e, - /* (2^ 74)P */ 0x41, 0x7d, 0x6e, 0x6d, 0x3a, 0xde, 0x14, 0x92, 0xfe, 0x7e, 0xf1, 0x07, 0x86, 0xd8, 0xcd, 0x3c, 0x17, 0x12, 0xe1, 0xf8, 0x88, 0x12, 0x4f, 0x67, 0xd0, 0x93, 0x9f, 0x32, 0x0f, 0x25, 0x82, 0x56, - /* (2^ 75)P */ 0x6e, 0x39, 0x2e, 0x6d, 0x13, 0x0b, 0xf0, 0x6c, 0xbf, 0xde, 0x14, 0x10, 0x6f, 0xf8, 0x4c, 0x6e, 0x83, 0x4e, 0xcc, 0xbf, 0xb5, 0xb1, 0x30, 0x59, 0xb6, 0x16, 0xba, 0x8a, 0xb4, 0x69, 0x70, 0x04, - /* (2^ 76)P */ 0x93, 0x07, 0xb2, 0x69, 0xab, 0xe4, 0x4c, 0x0d, 0x9e, 0xfb, 0xd0, 0x97, 0x1a, 0xb9, 0x4d, 0xb2, 0x1d, 0xd0, 0x00, 0x4e, 0xf5, 0x50, 0xfa, 0xcd, 0xb5, 0xdd, 0x8b, 0x36, 0x85, 0x10, 0x1b, 0x22, - /* (2^ 77)P */ 0xd2, 0xd8, 0xe3, 0xb1, 0x68, 0x94, 0xe5, 0xe7, 0x93, 0x2f, 0x12, 0xbd, 0x63, 0x65, 0xc5, 0x53, 0x09, 0x3f, 0x66, 0xe0, 0x03, 0xa9, 0xe8, 0xee, 0x42, 0x3d, 0xbe, 0xcb, 0x62, 0xa6, 0xef, 0x61, - /* (2^ 78)P */ 0x2a, 0xab, 0x6e, 0xde, 0xdd, 0xdd, 0xf8, 0x2c, 0x31, 0xf2, 0x35, 0x14, 0xd5, 0x0a, 0xf8, 0x9b, 0x73, 0x49, 0xf0, 0xc9, 0xce, 0xda, 0xea, 0x5d, 0x27, 0x9b, 0xd2, 0x41, 0x5d, 0x5b, 0x27, 0x29, - /* (2^ 79)P */ 0x4f, 0xf1, 0xeb, 0x95, 0x08, 0x0f, 0xde, 0xcf, 0xa7, 0x05, 0x49, 0x05, 0x6b, 0xb9, 0xaa, 0xb9, 0xfd, 0x20, 0xc4, 0xa1, 0xd9, 0x0d, 0xe8, 0xca, 0xc7, 0xbb, 0x73, 0x16, 0x2f, 0xbf, 0x63, 0x0a, - /* (2^ 80)P */ 0x8c, 0xbc, 0x8f, 0x95, 0x11, 0x6e, 0x2f, 0x09, 0xad, 0x2f, 0x82, 0x04, 0xe8, 0x81, 0x2a, 0x67, 0x17, 0x25, 0xd5, 0x60, 0x15, 0x35, 0xc8, 0xca, 0xf8, 0x92, 0xf1, 0xc8, 0x22, 0x77, 0x3f, 0x6f, - /* (2^ 81)P */ 0xb7, 0x94, 0xe8, 0xc2, 0xcc, 0x90, 0xba, 0xf8, 0x0d, 0x9f, 0xff, 0x38, 0xa4, 0x57, 0x75, 0x2c, 0x59, 0x23, 0xe5, 0x5a, 0x85, 0x1d, 0x4d, 0x89, 0x69, 0x3d, 0x74, 0x7b, 0x15, 0x22, 0xe1, 0x68, - /* (2^ 82)P */ 0xf3, 0x19, 0xb9, 0xcf, 0x70, 0x55, 0x7e, 0xd8, 0xb9, 0x8d, 0x79, 0x95, 0xcd, 0xde, 0x2c, 0x3f, 0xce, 0xa2, 0xc0, 0x10, 0x47, 0x15, 0x21, 0x21, 0xb2, 0xc5, 0x6d, 0x24, 0x15, 0xa1, 0x66, 0x3c, - /* (2^ 83)P */ 0x72, 0xcb, 0x4e, 0x29, 0x62, 0xc5, 0xed, 0xcb, 0x16, 0x0b, 0x28, 0x6a, 0xc3, 0x43, 0x71, 0xba, 0x67, 0x8b, 0x07, 0xd4, 0xef, 0xc2, 0x10, 0x96, 0x1e, 0x4b, 0x6a, 0x94, 0x5d, 0x73, 0x44, 0x61, - /* (2^ 84)P */ 0x50, 0x33, 0x5b, 0xd7, 0x1e, 0x11, 0x6f, 0x53, 0x1b, 0xd8, 0x41, 0x20, 0x8c, 0xdb, 0x11, 0x02, 0x3c, 0x41, 0x10, 0x0e, 0x00, 0xb1, 0x3c, 0xf9, 0x76, 0x88, 0x9e, 0x03, 0x3c, 0xfd, 0x9d, 0x14, - /* (2^ 85)P */ 0x5b, 0x15, 0x63, 0x6b, 0xe4, 0xdd, 0x79, 0xd4, 0x76, 0x79, 0x83, 0x3c, 0xe9, 0x15, 0x6e, 0xb6, 0x38, 0xe0, 0x13, 0x1f, 0x3b, 0xe4, 0xfd, 0xda, 0x35, 0x0b, 0x4b, 0x2e, 0x1a, 0xda, 0xaf, 0x5f, - /* (2^ 86)P */ 0x81, 0x75, 0x19, 0x17, 0xdf, 0xbb, 0x00, 0x36, 0xc2, 0xd2, 0x3c, 0xbe, 0x0b, 0x05, 0x72, 0x39, 0x86, 0xbe, 0xd5, 0xbd, 0x6d, 0x90, 0x38, 0x59, 0x0f, 0x86, 0x9b, 0x3f, 0xe4, 0xe5, 0xfc, 0x34, - /* (2^ 87)P */ 0x02, 0x4d, 0xd1, 0x42, 0xcd, 0xa4, 0xa8, 0x75, 0x65, 0xdf, 0x41, 0x34, 0xc5, 0xab, 0x8d, 0x82, 0xd3, 0x31, 0xe1, 0xd2, 0xed, 0xab, 0xdc, 0x33, 0x5f, 0xd2, 0x14, 0xb8, 0x6f, 0xd7, 0xba, 0x3e, - /* (2^ 88)P */ 0x0f, 0xe1, 0x70, 0x6f, 0x56, 0x6f, 0x90, 0xd4, 0x5a, 0x0f, 0x69, 0x51, 0xaa, 0xf7, 0x12, 0x5d, 0xf2, 0xfc, 0xce, 0x76, 0x6e, 0xb1, 0xad, 0x45, 0x99, 0x29, 0x23, 0xad, 0xae, 0x68, 0xf7, 0x01, - /* (2^ 89)P */ 0xbd, 0xfe, 0x48, 0x62, 0x7b, 0xc7, 0x6c, 0x2b, 0xfd, 0xaf, 0x3a, 0xec, 0x28, 0x06, 0xd3, 0x3c, 0x6a, 0x48, 0xef, 0xd4, 0x80, 0x0b, 0x1c, 0xce, 0x23, 0x6c, 0xf6, 0xa6, 0x2e, 0xff, 0x3b, 0x4c, - /* (2^ 90)P */ 0x5f, 0xeb, 0xea, 0x4a, 0x09, 0xc4, 0x2e, 0x3f, 0xa7, 0x2c, 0x37, 0x6e, 0x28, 0x9b, 0xb1, 0x61, 0x1d, 0x70, 0x2a, 0xde, 0x66, 0xa9, 0xef, 0x5e, 0xef, 0xe3, 0x55, 0xde, 0x65, 0x05, 0xb2, 0x23, - /* (2^ 91)P */ 0x57, 0x85, 0xd5, 0x79, 0x52, 0xca, 0x01, 0xe3, 0x4f, 0x87, 0xc2, 0x27, 0xce, 0xd4, 0xb2, 0x07, 0x67, 0x1d, 0xcf, 0x9d, 0x8a, 0xcd, 0x32, 0xa5, 0x56, 0xff, 0x2b, 0x3f, 0xe2, 0xfe, 0x52, 0x2a, - /* (2^ 92)P */ 0x3d, 0x66, 0xd8, 0x7c, 0xb3, 0xef, 0x24, 0x86, 0x94, 0x75, 0xbd, 0xff, 0x20, 0xac, 0xc7, 0xbb, 0x45, 0x74, 0xd3, 0x82, 0x9c, 0x5e, 0xb8, 0x57, 0x66, 0xec, 0xa6, 0x86, 0xcb, 0x52, 0x30, 0x7b, - /* (2^ 93)P */ 0x1e, 0xe9, 0x25, 0x25, 0xad, 0xf0, 0x82, 0x34, 0xa0, 0xdc, 0x8e, 0xd2, 0x43, 0x80, 0xb6, 0x2c, 0x3a, 0x00, 0x1b, 0x2e, 0x05, 0x6d, 0x4f, 0xaf, 0x0a, 0x1b, 0x78, 0x29, 0x25, 0x8c, 0x5f, 0x18, - /* (2^ 94)P */ 0xd6, 0xe0, 0x0c, 0xd8, 0x5b, 0xde, 0x41, 0xaa, 0xd6, 0xe9, 0x53, 0x68, 0x41, 0xb2, 0x07, 0x94, 0x3a, 0x4c, 0x7f, 0x35, 0x6e, 0xc3, 0x3e, 0x56, 0xce, 0x7b, 0x29, 0x0e, 0xdd, 0xb8, 0xc4, 0x4c, - /* (2^ 95)P */ 0x0e, 0x73, 0xb8, 0xff, 0x52, 0x1a, 0xfc, 0xa2, 0x37, 0x8e, 0x05, 0x67, 0x6e, 0xf1, 0x11, 0x18, 0xe1, 0x4e, 0xdf, 0xcd, 0x66, 0xa3, 0xf9, 0x10, 0x99, 0xf0, 0xb9, 0xa0, 0xc4, 0xa0, 0xf4, 0x72, - /* (2^ 96)P */ 0xa7, 0x4e, 0x3f, 0x66, 0x6f, 0xc0, 0x16, 0x8c, 0xba, 0x0f, 0x97, 0x4e, 0xf7, 0x3a, 0x3b, 0x69, 0x45, 0xc3, 0x9e, 0xd6, 0xf1, 0xe7, 0x02, 0x21, 0x89, 0x80, 0x8a, 0x96, 0xbc, 0x3c, 0xa5, 0x0b, - /* (2^ 97)P */ 0x37, 0x55, 0xa1, 0xfe, 0xc7, 0x9d, 0x3d, 0xca, 0x93, 0x64, 0x53, 0x51, 0xbb, 0x24, 0x68, 0x4c, 0xb1, 0x06, 0x40, 0x84, 0x14, 0x63, 0x88, 0xb9, 0x60, 0xcc, 0x54, 0xb4, 0x2a, 0xa7, 0xd2, 0x40, - /* (2^ 98)P */ 0x75, 0x09, 0x57, 0x12, 0xb7, 0xa1, 0x36, 0x59, 0x57, 0xa6, 0xbd, 0xde, 0x48, 0xd6, 0xb9, 0x91, 0xea, 0x30, 0x43, 0xb6, 0x4b, 0x09, 0x44, 0x33, 0xd0, 0x51, 0xee, 0x12, 0x0d, 0xa1, 0x6b, 0x00, - /* (2^ 99)P */ 0x58, 0x5d, 0xde, 0xf5, 0x68, 0x84, 0x22, 0x19, 0xb0, 0x05, 0xcc, 0x38, 0x4c, 0x2f, 0xb1, 0x0e, 0x90, 0x19, 0x60, 0xd5, 0x9d, 0x9f, 0x03, 0xa1, 0x0b, 0x0e, 0xff, 0x4f, 0xce, 0xd4, 0x02, 0x45, - /* (2^100)P */ 0x89, 0xc1, 0x37, 0x68, 0x10, 0x54, 0x20, 0xeb, 0x3c, 0xb9, 0xd3, 0x6d, 0x4c, 0x54, 0xf6, 0xd0, 0x4f, 0xd7, 0x16, 0xc4, 0x64, 0x70, 0x72, 0x40, 0xf0, 0x2e, 0x50, 0x4b, 0x11, 0xc6, 0x15, 0x6e, - /* (2^101)P */ 0x6b, 0xa7, 0xb1, 0xcf, 0x98, 0xa3, 0xf2, 0x4d, 0xb1, 0xf6, 0xf2, 0x19, 0x74, 0x6c, 0x25, 0x11, 0x43, 0x60, 0x6e, 0x06, 0x62, 0x79, 0x49, 0x4a, 0x44, 0x5b, 0x35, 0x41, 0xab, 0x3a, 0x5b, 0x70, - /* (2^102)P */ 0xd8, 0xb1, 0x97, 0xd7, 0x36, 0xf5, 0x5e, 0x36, 0xdb, 0xf0, 0xdd, 0x22, 0xd6, 0x6b, 0x07, 0x00, 0x88, 0x5a, 0x57, 0xe0, 0xb0, 0x33, 0xbf, 0x3b, 0x4d, 0xca, 0xe4, 0xc8, 0x05, 0xaa, 0x77, 0x37, - /* (2^103)P */ 0x5f, 0xdb, 0x78, 0x55, 0xc8, 0x45, 0x27, 0x39, 0xe2, 0x5a, 0xae, 0xdb, 0x49, 0x41, 0xda, 0x6f, 0x67, 0x98, 0xdc, 0x8a, 0x0b, 0xb0, 0xf0, 0xb1, 0xa3, 0x1d, 0x6f, 0xd3, 0x37, 0x34, 0x96, 0x09, - /* (2^104)P */ 0x53, 0x38, 0xdc, 0xa5, 0x90, 0x4e, 0x82, 0x7e, 0xbd, 0x5c, 0x13, 0x1f, 0x64, 0xf6, 0xb5, 0xcc, 0xcc, 0x8f, 0xce, 0x87, 0x6c, 0xd8, 0x36, 0x67, 0x9f, 0x24, 0x04, 0x66, 0xe2, 0x3c, 0x5f, 0x62, - /* (2^105)P */ 0x3f, 0xf6, 0x02, 0x95, 0x05, 0xc8, 0x8a, 0xaf, 0x69, 0x14, 0x35, 0x2e, 0x0a, 0xe7, 0x05, 0x0c, 0x05, 0x63, 0x4b, 0x76, 0x9c, 0x2e, 0x29, 0x35, 0xc3, 0x3a, 0xe2, 0xc7, 0x60, 0x43, 0x39, 0x1a, - /* (2^106)P */ 0x64, 0x32, 0x18, 0x51, 0x32, 0xd5, 0xc6, 0xd5, 0x4f, 0xb7, 0xc2, 0x43, 0xbd, 0x5a, 0x06, 0x62, 0x9b, 0x3f, 0x97, 0x3b, 0xd0, 0xf5, 0xfb, 0xb5, 0x5e, 0x6e, 0x20, 0x61, 0x36, 0xda, 0xa3, 0x13, - /* (2^107)P */ 0xe5, 0x94, 0x5d, 0x72, 0x37, 0x58, 0xbd, 0xc6, 0xc5, 0x16, 0x50, 0x20, 0x12, 0x09, 0xe3, 0x18, 0x68, 0x3c, 0x03, 0x70, 0x15, 0xce, 0x88, 0x20, 0x87, 0x79, 0x83, 0x5c, 0x49, 0x1f, 0xba, 0x7f, - /* (2^108)P */ 0x9d, 0x07, 0xf9, 0xf2, 0x23, 0x74, 0x8c, 0x5a, 0xc5, 0x3f, 0x02, 0x34, 0x7b, 0x15, 0x35, 0x17, 0x51, 0xb3, 0xfa, 0xd2, 0x9a, 0xb4, 0xf9, 0xe4, 0x3c, 0xe3, 0x78, 0xc8, 0x72, 0xff, 0x91, 0x66, - /* (2^109)P */ 0x3e, 0xff, 0x5e, 0xdc, 0xde, 0x2a, 0x2c, 0x12, 0xf4, 0x6c, 0x95, 0xd8, 0xf1, 0x4b, 0xdd, 0xf8, 0xda, 0x5b, 0x9e, 0x9e, 0x5d, 0x20, 0x86, 0xeb, 0x43, 0xc7, 0x75, 0xd9, 0xb9, 0x92, 0x9b, 0x04, - /* (2^110)P */ 0x5a, 0xc0, 0xf6, 0xb0, 0x30, 0x97, 0x37, 0xa5, 0x53, 0xa5, 0xf3, 0xc6, 0xac, 0xff, 0xa0, 0x72, 0x6d, 0xcd, 0x0d, 0xb2, 0x34, 0x2c, 0x03, 0xb0, 0x4a, 0x16, 0xd5, 0x88, 0xbc, 0x9d, 0x0e, 0x47, - /* (2^111)P */ 0x47, 0xc0, 0x37, 0xa2, 0x0c, 0xf1, 0x9c, 0xb1, 0xa2, 0x81, 0x6c, 0x1f, 0x71, 0x66, 0x54, 0xb6, 0x43, 0x0b, 0xd8, 0x6d, 0xd1, 0x1b, 0x32, 0xb3, 0x8e, 0xbe, 0x5f, 0x0c, 0x60, 0x4f, 0xc1, 0x48, - /* (2^112)P */ 0x03, 0xc8, 0xa6, 0x4a, 0x26, 0x1c, 0x45, 0x66, 0xa6, 0x7d, 0xfa, 0xa4, 0x04, 0x39, 0x6e, 0xb6, 0x95, 0x83, 0x12, 0xb3, 0xb0, 0x19, 0x5f, 0xd4, 0x10, 0xbc, 0xc9, 0xc3, 0x27, 0x26, 0x60, 0x31, - /* (2^113)P */ 0x0d, 0xe1, 0xe4, 0x32, 0x48, 0xdc, 0x20, 0x31, 0xf7, 0x17, 0xc7, 0x56, 0x67, 0xc4, 0x20, 0xeb, 0x94, 0x02, 0x28, 0x67, 0x3f, 0x2e, 0xf5, 0x00, 0x09, 0xc5, 0x30, 0x47, 0xc1, 0x4f, 0x6d, 0x56, - /* (2^114)P */ 0x06, 0x72, 0x83, 0xfd, 0x40, 0x5d, 0x3a, 0x7e, 0x7a, 0x54, 0x59, 0x71, 0xdc, 0x26, 0xe9, 0xc1, 0x95, 0x60, 0x8d, 0xa6, 0xfb, 0x30, 0x67, 0x21, 0xa7, 0xce, 0x69, 0x3f, 0x84, 0xc3, 0xe8, 0x22, - /* (2^115)P */ 0x2b, 0x4b, 0x0e, 0x93, 0xe8, 0x74, 0xd0, 0x33, 0x16, 0x58, 0xd1, 0x84, 0x0e, 0x35, 0xe4, 0xb6, 0x65, 0x23, 0xba, 0xd6, 0x6a, 0xc2, 0x34, 0x55, 0xf3, 0xf3, 0xf1, 0x89, 0x2f, 0xc1, 0x73, 0x77, - /* (2^116)P */ 0xaa, 0x62, 0x79, 0xa5, 0x4d, 0x40, 0xba, 0x8c, 0x56, 0xce, 0x99, 0x19, 0xa8, 0x97, 0x98, 0x5b, 0xfc, 0x92, 0x16, 0x12, 0x2f, 0x86, 0x8e, 0x50, 0x91, 0xc2, 0x93, 0xa0, 0x7f, 0x90, 0x81, 0x3a, - /* (2^117)P */ 0x10, 0xa5, 0x25, 0x47, 0xff, 0xd0, 0xde, 0x0d, 0x03, 0xc5, 0x3f, 0x67, 0x10, 0xcc, 0xd8, 0x10, 0x89, 0x4e, 0x1f, 0x9f, 0x1c, 0x15, 0x9d, 0x5b, 0x4c, 0xa4, 0x09, 0xcb, 0xd5, 0xc1, 0xa5, 0x32, - /* (2^118)P */ 0xfb, 0x41, 0x05, 0xb9, 0x42, 0xa4, 0x0a, 0x1e, 0xdb, 0x85, 0xb4, 0xc1, 0x7c, 0xeb, 0x85, 0x5f, 0xe5, 0xf2, 0x9d, 0x8a, 0xce, 0x95, 0xe5, 0xbe, 0x36, 0x22, 0x42, 0x22, 0xc7, 0x96, 0xe4, 0x25, - /* (2^119)P */ 0xb9, 0xe5, 0x0f, 0xcd, 0x46, 0x3c, 0xdf, 0x5e, 0x88, 0x33, 0xa4, 0xd2, 0x7e, 0x5a, 0xe7, 0x34, 0x52, 0xe3, 0x61, 0xd7, 0x11, 0xde, 0x88, 0xe4, 0x5c, 0x54, 0x85, 0xa0, 0x01, 0x8a, 0x87, 0x0e, - /* (2^120)P */ 0x04, 0xbb, 0x21, 0xe0, 0x77, 0x3c, 0x49, 0xba, 0x9a, 0x89, 0xdf, 0xc7, 0x43, 0x18, 0x4d, 0x2b, 0x67, 0x0d, 0xe8, 0x7a, 0x48, 0x7a, 0xa3, 0x9e, 0x94, 0x17, 0xe4, 0x11, 0x80, 0x95, 0xa9, 0x67, - /* (2^121)P */ 0x65, 0xb0, 0x97, 0x66, 0x1a, 0x05, 0x58, 0x4b, 0xd4, 0xa6, 0x6b, 0x8d, 0x7d, 0x3f, 0xe3, 0x47, 0xc1, 0x46, 0xca, 0x83, 0xd4, 0xa8, 0x4d, 0xbb, 0x0d, 0xdb, 0xc2, 0x81, 0xa1, 0xca, 0xbe, 0x68, - /* (2^122)P */ 0xa5, 0x9a, 0x98, 0x0b, 0xe9, 0x80, 0x89, 0x8d, 0x9b, 0xc9, 0x93, 0x2c, 0x4a, 0xb1, 0x5e, 0xf9, 0xa2, 0x73, 0x6e, 0x79, 0xc4, 0xc7, 0xc6, 0x51, 0x69, 0xb5, 0xef, 0xb5, 0x63, 0x83, 0x22, 0x6e, - /* (2^123)P */ 0xc8, 0x24, 0xd6, 0x2d, 0xb0, 0xc0, 0xbb, 0xc6, 0xee, 0x70, 0x81, 0xec, 0x7d, 0xb4, 0x7e, 0x77, 0xa9, 0xaf, 0xcf, 0x04, 0xa0, 0x15, 0xde, 0x3c, 0x9b, 0xbf, 0x60, 0x71, 0x08, 0xbc, 0xc6, 0x1d, - /* (2^124)P */ 0x02, 0x40, 0xc3, 0xee, 0x43, 0xe0, 0x07, 0x2e, 0x7f, 0xdc, 0x68, 0x7a, 0x67, 0xfc, 0xe9, 0x18, 0x9a, 0x5b, 0xd1, 0x8b, 0x18, 0x03, 0xda, 0xd8, 0x53, 0x82, 0x56, 0x00, 0xbb, 0xc3, 0xfb, 0x48, - /* (2^125)P */ 0xe1, 0x4c, 0x65, 0xfb, 0x4c, 0x7d, 0x54, 0x57, 0xad, 0xe2, 0x58, 0xa0, 0x82, 0x5b, 0x56, 0xd3, 0x78, 0x44, 0x15, 0xbf, 0x0b, 0xaf, 0x3e, 0xf6, 0x18, 0xbb, 0xdf, 0x14, 0xf1, 0x1e, 0x53, 0x47, - /* (2^126)P */ 0x87, 0xc5, 0x78, 0x42, 0x0a, 0x63, 0xec, 0xe1, 0xf3, 0x83, 0x8e, 0xca, 0x46, 0xd5, 0x07, 0x55, 0x2b, 0x0c, 0xdc, 0x3a, 0xc6, 0x35, 0xe1, 0x85, 0x4e, 0x84, 0x82, 0x56, 0xa8, 0xef, 0xa7, 0x0a, - /* (2^127)P */ 0x15, 0xf6, 0xe1, 0xb3, 0xa8, 0x1b, 0x69, 0x72, 0xfa, 0x3f, 0xbe, 0x1f, 0x70, 0xe9, 0xb4, 0x32, 0x68, 0x78, 0xbb, 0x39, 0x2e, 0xd9, 0xb6, 0x97, 0xe8, 0x39, 0x2e, 0xa0, 0xde, 0x53, 0xfe, 0x2c, - /* (2^128)P */ 0xb0, 0x52, 0xcd, 0x85, 0xcd, 0x92, 0x73, 0x68, 0x31, 0x98, 0xe2, 0x10, 0xc9, 0x66, 0xff, 0x27, 0x06, 0x2d, 0x83, 0xa9, 0x56, 0x45, 0x13, 0x97, 0xa0, 0xf8, 0x84, 0x0a, 0x36, 0xb0, 0x9b, 0x26, - /* (2^129)P */ 0x5c, 0xf8, 0x43, 0x76, 0x45, 0x55, 0x6e, 0x70, 0x1b, 0x7d, 0x59, 0x9b, 0x8c, 0xa4, 0x34, 0x37, 0x72, 0xa4, 0xef, 0xc6, 0xe8, 0x91, 0xee, 0x7a, 0xe0, 0xd9, 0xa9, 0x98, 0xc1, 0xab, 0xd6, 0x5c, - /* (2^130)P */ 0x1a, 0xe4, 0x3c, 0xcb, 0x06, 0xde, 0x04, 0x0e, 0x38, 0xe1, 0x02, 0x34, 0x89, 0xeb, 0xc6, 0xd8, 0x72, 0x37, 0x6e, 0x68, 0xbb, 0x59, 0x46, 0x90, 0xc8, 0xa8, 0x6b, 0x74, 0x71, 0xc3, 0x15, 0x72, - /* (2^131)P */ 0xd9, 0xa2, 0xe4, 0xea, 0x7e, 0xa9, 0x12, 0xfd, 0xc5, 0xf2, 0x94, 0x63, 0x51, 0xb7, 0x14, 0x95, 0x94, 0xf2, 0x08, 0x92, 0x80, 0xd5, 0x6f, 0x26, 0xb9, 0x26, 0x9a, 0x61, 0x85, 0x70, 0x84, 0x5c, - /* (2^132)P */ 0xea, 0x94, 0xd6, 0xfe, 0x10, 0x54, 0x98, 0x52, 0x54, 0xd2, 0x2e, 0x4a, 0x93, 0x5b, 0x90, 0x3c, 0x67, 0xe4, 0x3b, 0x2d, 0x69, 0x47, 0xbb, 0x10, 0xe1, 0xe9, 0xe5, 0x69, 0x2d, 0x3d, 0x3b, 0x06, - /* (2^133)P */ 0xeb, 0x7d, 0xa5, 0xdd, 0xee, 0x26, 0x27, 0x47, 0x91, 0x18, 0xf4, 0x10, 0xae, 0xc4, 0xb6, 0xef, 0x14, 0x76, 0x30, 0x7b, 0x91, 0x41, 0x16, 0x2b, 0x7c, 0x5b, 0xf4, 0xc4, 0x4f, 0x55, 0x7c, 0x11, - /* (2^134)P */ 0x12, 0x88, 0x9d, 0x8f, 0x11, 0xf3, 0x7c, 0xc0, 0x39, 0x79, 0x01, 0x50, 0x20, 0xd8, 0xdb, 0x01, 0x27, 0x28, 0x1b, 0x17, 0xf4, 0x03, 0xe8, 0xd7, 0xea, 0x25, 0xd2, 0x87, 0x74, 0xe8, 0x15, 0x10, - /* (2^135)P */ 0x4d, 0xcc, 0x3a, 0xd2, 0xfe, 0xe3, 0x8d, 0xc5, 0x2d, 0xbe, 0xa7, 0x94, 0xc2, 0x91, 0xdb, 0x50, 0x57, 0xf4, 0x9c, 0x1c, 0x3d, 0xd4, 0x94, 0x0b, 0x4a, 0x52, 0x37, 0x6e, 0xfa, 0x40, 0x16, 0x6b, - /* (2^136)P */ 0x09, 0x0d, 0xda, 0x5f, 0x6c, 0x34, 0x2f, 0x69, 0x51, 0x31, 0x4d, 0xfa, 0x59, 0x1c, 0x0b, 0x20, 0x96, 0xa2, 0x77, 0x07, 0x76, 0x6f, 0xc4, 0xb8, 0xcf, 0xfb, 0xfd, 0x3f, 0x5f, 0x39, 0x38, 0x4b, - /* (2^137)P */ 0x71, 0xd6, 0x54, 0xbe, 0x00, 0x5e, 0xd2, 0x18, 0xa6, 0xab, 0xc8, 0xbe, 0x82, 0x05, 0xd5, 0x60, 0x82, 0xb9, 0x78, 0x3b, 0x26, 0x8f, 0xad, 0x87, 0x32, 0x04, 0xda, 0x9c, 0x4e, 0xf6, 0xfd, 0x50, - /* (2^138)P */ 0xf0, 0xdc, 0x78, 0xc5, 0xaa, 0x67, 0xf5, 0x90, 0x3b, 0x13, 0xa3, 0xf2, 0x0e, 0x9b, 0x1e, 0xef, 0x71, 0xde, 0xd9, 0x42, 0x92, 0xba, 0xeb, 0x0e, 0xc7, 0x01, 0x31, 0xf0, 0x9b, 0x3c, 0x47, 0x15, - /* (2^139)P */ 0x95, 0x80, 0xb7, 0x56, 0xae, 0xe8, 0x77, 0x7c, 0x8e, 0x07, 0x6f, 0x6e, 0x66, 0xe7, 0x78, 0xb6, 0x1f, 0xba, 0x48, 0x53, 0x61, 0xb9, 0xa0, 0x2d, 0x0b, 0x3f, 0x73, 0xff, 0xc1, 0x31, 0xf9, 0x7c, - /* (2^140)P */ 0x6c, 0x36, 0x0a, 0x0a, 0xf5, 0x57, 0xb3, 0x26, 0x32, 0xd7, 0x87, 0x2b, 0xf4, 0x8c, 0x70, 0xe9, 0xc0, 0xb2, 0x1c, 0xf9, 0xa5, 0xee, 0x3a, 0xc1, 0x4c, 0xbb, 0x43, 0x11, 0x99, 0x0c, 0xd9, 0x35, - /* (2^141)P */ 0xdc, 0xd9, 0xa0, 0xa9, 0x04, 0xc4, 0xc1, 0x47, 0x51, 0xd2, 0x72, 0x19, 0x45, 0x58, 0x9e, 0x65, 0x31, 0x8c, 0xb3, 0x73, 0xc4, 0xa8, 0x75, 0x38, 0x24, 0x1f, 0x56, 0x79, 0xd3, 0x9e, 0xbd, 0x1f, - /* (2^142)P */ 0x8d, 0xc2, 0x1e, 0xd4, 0x6f, 0xbc, 0xfa, 0x11, 0xca, 0x2d, 0x2a, 0xcd, 0xe3, 0xdf, 0xf8, 0x7e, 0x95, 0x45, 0x40, 0x8c, 0x5d, 0x3b, 0xe7, 0x72, 0x27, 0x2f, 0xb7, 0x54, 0x49, 0xfa, 0x35, 0x61, - /* (2^143)P */ 0x9c, 0xb6, 0x24, 0xde, 0xa2, 0x32, 0xfc, 0xcc, 0x88, 0x5d, 0x09, 0x1f, 0x8c, 0x69, 0x55, 0x3f, 0x29, 0xf9, 0xc3, 0x5a, 0xed, 0x50, 0x33, 0xbe, 0xeb, 0x7e, 0x47, 0xca, 0x06, 0xf8, 0x9b, 0x5e, - /* (2^144)P */ 0x68, 0x9f, 0x30, 0x3c, 0xb6, 0x8f, 0xce, 0xe9, 0xf4, 0xf9, 0xe1, 0x65, 0x35, 0xf6, 0x76, 0x53, 0xf1, 0x93, 0x63, 0x5a, 0xb3, 0xcf, 0xaf, 0xd1, 0x06, 0x35, 0x62, 0xe5, 0xed, 0xa1, 0x32, 0x66, - /* (2^145)P */ 0x4c, 0xed, 0x2d, 0x0c, 0x39, 0x6c, 0x7d, 0x0b, 0x1f, 0xcb, 0x04, 0xdf, 0x81, 0x32, 0xcb, 0x56, 0xc7, 0xc3, 0xec, 0x49, 0x12, 0x5a, 0x30, 0x66, 0x2a, 0xa7, 0x8c, 0xa3, 0x60, 0x8b, 0x58, 0x5d, - /* (2^146)P */ 0x2d, 0xf4, 0xe5, 0xe8, 0x78, 0xbf, 0xec, 0xa6, 0xec, 0x3e, 0x8a, 0x3c, 0x4b, 0xb4, 0xee, 0x86, 0x04, 0x16, 0xd2, 0xfb, 0x48, 0x9c, 0x21, 0xec, 0x31, 0x67, 0xc3, 0x17, 0xf5, 0x1a, 0xaf, 0x1a, - /* (2^147)P */ 0xe7, 0xbd, 0x69, 0x67, 0x83, 0xa2, 0x06, 0xc3, 0xdb, 0x2a, 0x1e, 0x2b, 0x62, 0x80, 0x82, 0x20, 0xa6, 0x94, 0xff, 0xfb, 0x1f, 0xf5, 0x27, 0x80, 0x6b, 0xf2, 0x24, 0x11, 0xce, 0xa1, 0xcf, 0x76, - /* (2^148)P */ 0xb6, 0xab, 0x22, 0x24, 0x56, 0x00, 0xeb, 0x18, 0xc3, 0x29, 0x8c, 0x8f, 0xd5, 0xc4, 0x77, 0xf3, 0x1a, 0x56, 0x31, 0xf5, 0x07, 0xc2, 0xbb, 0x4d, 0x27, 0x8a, 0x12, 0x82, 0xf0, 0xb7, 0x53, 0x02, - /* (2^149)P */ 0xe0, 0x17, 0x2c, 0xb6, 0x1c, 0x09, 0x1f, 0x3d, 0xa9, 0x28, 0x46, 0xd6, 0xab, 0xe1, 0x60, 0x48, 0x53, 0x42, 0x9d, 0x30, 0x36, 0x74, 0xd1, 0x52, 0x76, 0xe5, 0xfa, 0x3e, 0xe1, 0x97, 0x6f, 0x35, - /* (2^150)P */ 0x5b, 0x53, 0x50, 0xa1, 0x1a, 0xe1, 0x51, 0xd3, 0xcc, 0x78, 0xd8, 0x1d, 0xbb, 0x45, 0x6b, 0x3e, 0x98, 0x2c, 0xd9, 0xbe, 0x28, 0x61, 0x77, 0x0c, 0xb8, 0x85, 0x28, 0x03, 0x93, 0xae, 0x34, 0x1d, - /* (2^151)P */ 0xc3, 0xa4, 0x5b, 0xa8, 0x8c, 0x48, 0xa0, 0x4b, 0xce, 0xe6, 0x9c, 0x3c, 0xc3, 0x48, 0x53, 0x98, 0x70, 0xa7, 0xbd, 0x97, 0x6f, 0x4c, 0x12, 0x66, 0x4a, 0x12, 0x54, 0x06, 0x29, 0xa0, 0x81, 0x0f, - /* (2^152)P */ 0xfd, 0x86, 0x9b, 0x56, 0xa6, 0x9c, 0xd0, 0x9e, 0x2d, 0x9a, 0xaf, 0x18, 0xfd, 0x09, 0x10, 0x81, 0x0a, 0xc2, 0xd8, 0x93, 0x3f, 0xd0, 0x08, 0xff, 0x6b, 0xf2, 0xae, 0x9f, 0x19, 0x48, 0xa1, 0x52, - /* (2^153)P */ 0x73, 0x1b, 0x8d, 0x2d, 0xdc, 0xf9, 0x03, 0x3e, 0x70, 0x1a, 0x96, 0x73, 0x18, 0x80, 0x05, 0x42, 0x70, 0x59, 0xa3, 0x41, 0xf0, 0x87, 0xd9, 0xc0, 0x49, 0xd5, 0xc0, 0xa1, 0x15, 0x1f, 0xaa, 0x07, - /* (2^154)P */ 0x24, 0x72, 0xd2, 0x8c, 0xe0, 0x6c, 0xd4, 0xdf, 0x39, 0x42, 0x4e, 0x93, 0x4f, 0x02, 0x0a, 0x6d, 0x59, 0x7b, 0x89, 0x99, 0x63, 0x7a, 0x8a, 0x80, 0xa2, 0x95, 0x3d, 0xe1, 0xe9, 0x56, 0x45, 0x0a, - /* (2^155)P */ 0x45, 0x30, 0xc1, 0xe9, 0x1f, 0x99, 0x1a, 0xd2, 0xb8, 0x51, 0x77, 0xfe, 0x48, 0x85, 0x0e, 0x9b, 0x35, 0x00, 0xf3, 0x4b, 0xcb, 0x43, 0xa6, 0x5d, 0x21, 0xf7, 0x40, 0x39, 0xd6, 0x28, 0xdb, 0x77, - /* (2^156)P */ 0x11, 0x90, 0xdc, 0x4a, 0x61, 0xeb, 0x5e, 0xfc, 0xeb, 0x11, 0xc4, 0xe8, 0x9a, 0x41, 0x29, 0x52, 0x74, 0xcf, 0x1d, 0x7d, 0x78, 0xe7, 0xc3, 0x9e, 0xb5, 0x4c, 0x6e, 0x21, 0x3e, 0x05, 0x0d, 0x34, - /* (2^157)P */ 0xb4, 0xf2, 0x8d, 0xb4, 0x39, 0xaf, 0xc7, 0xca, 0x94, 0x0a, 0xa1, 0x71, 0x28, 0xec, 0xfa, 0xc0, 0xed, 0x75, 0xa5, 0x5c, 0x24, 0x69, 0x0a, 0x14, 0x4c, 0x3a, 0x27, 0x34, 0x71, 0xc3, 0xf1, 0x0c, - /* (2^158)P */ 0xa5, 0xb8, 0x24, 0xc2, 0x6a, 0x30, 0xee, 0xc8, 0xb0, 0x30, 0x49, 0xcb, 0x7c, 0xee, 0xea, 0x57, 0x4f, 0xe7, 0xcb, 0xaa, 0xbd, 0x06, 0xe8, 0xa1, 0x7d, 0x65, 0xeb, 0x2e, 0x74, 0x62, 0x9a, 0x7d, - /* (2^159)P */ 0x30, 0x48, 0x6c, 0x54, 0xef, 0xb6, 0xb6, 0x9e, 0x2e, 0x6e, 0xb3, 0xdd, 0x1f, 0xca, 0x5c, 0x88, 0x05, 0x71, 0x0d, 0xef, 0x83, 0xf3, 0xb9, 0xe6, 0x12, 0x04, 0x2e, 0x9d, 0xef, 0x4f, 0x65, 0x58, - /* (2^160)P */ 0x26, 0x8e, 0x0e, 0xbe, 0xff, 0xc4, 0x05, 0xa9, 0x6e, 0x81, 0x31, 0x9b, 0xdf, 0xe5, 0x2d, 0x94, 0xe1, 0x88, 0x2e, 0x80, 0x3f, 0x72, 0x7d, 0x49, 0x8d, 0x40, 0x2f, 0x60, 0xea, 0x4d, 0x68, 0x30, - /* (2^161)P */ 0x34, 0xcb, 0xe6, 0xa3, 0x78, 0xa2, 0xe5, 0x21, 0xc4, 0x1d, 0x15, 0x5b, 0x6f, 0x6e, 0xfb, 0xae, 0x15, 0xca, 0x77, 0x9d, 0x04, 0x8e, 0x0b, 0xb3, 0x81, 0x89, 0xb9, 0x53, 0xcf, 0xc9, 0xc3, 0x28, - /* (2^162)P */ 0x2a, 0xdd, 0x6c, 0x55, 0x21, 0xb7, 0x7f, 0x28, 0x74, 0x22, 0x02, 0x97, 0xa8, 0x7c, 0x31, 0x0d, 0x58, 0x32, 0x54, 0x3a, 0x42, 0xc7, 0x68, 0x74, 0x2f, 0x64, 0xb5, 0x4e, 0x46, 0x11, 0x7f, 0x4a, - /* (2^163)P */ 0xa6, 0x3a, 0x19, 0x4d, 0x77, 0xa4, 0x37, 0xa2, 0xa1, 0x29, 0x21, 0xa9, 0x6e, 0x98, 0x65, 0xd8, 0x88, 0x1a, 0x7c, 0xf8, 0xec, 0x15, 0xc5, 0x24, 0xeb, 0xf5, 0x39, 0x5f, 0x57, 0x03, 0x40, 0x60, - /* (2^164)P */ 0x27, 0x9b, 0x0a, 0x57, 0x89, 0xf1, 0xb9, 0x47, 0x78, 0x4b, 0x5e, 0x46, 0xde, 0xce, 0x98, 0x2b, 0x20, 0x5c, 0xb8, 0xdb, 0x51, 0xf5, 0x6d, 0x02, 0x01, 0x19, 0xe2, 0x47, 0x10, 0xd9, 0xfc, 0x74, - /* (2^165)P */ 0xa3, 0xbf, 0xc1, 0x23, 0x0a, 0xa9, 0xe2, 0x13, 0xf6, 0x19, 0x85, 0x47, 0x4e, 0x07, 0xb0, 0x0c, 0x44, 0xcf, 0xf6, 0x3a, 0xbe, 0xcb, 0xf1, 0x5f, 0xbe, 0x2d, 0x81, 0xbe, 0x38, 0x54, 0xfe, 0x67, - /* (2^166)P */ 0xb0, 0x05, 0x0f, 0xa4, 0x4f, 0xf6, 0x3c, 0xd1, 0x87, 0x37, 0x28, 0x32, 0x2f, 0xfb, 0x4d, 0x05, 0xea, 0x2a, 0x0d, 0x7f, 0x5b, 0x91, 0x73, 0x41, 0x4e, 0x0d, 0x61, 0x1f, 0x4f, 0x14, 0x2f, 0x48, - /* (2^167)P */ 0x34, 0x82, 0x7f, 0xb4, 0x01, 0x02, 0x21, 0xf6, 0x90, 0xb9, 0x70, 0x9e, 0x92, 0xe1, 0x0a, 0x5d, 0x7c, 0x56, 0x49, 0xb0, 0x55, 0xf4, 0xd7, 0xdc, 0x01, 0x6f, 0x91, 0xf0, 0xf1, 0xd0, 0x93, 0x7e, - /* (2^168)P */ 0xfa, 0xb4, 0x7d, 0x8a, 0xf1, 0xcb, 0x79, 0xdd, 0x2f, 0xc6, 0x74, 0x6f, 0xbf, 0x91, 0x83, 0xbe, 0xbd, 0x91, 0x82, 0x4b, 0xd1, 0x45, 0x71, 0x02, 0x05, 0x17, 0xbf, 0x2c, 0xea, 0x73, 0x5a, 0x58, - /* (2^169)P */ 0xb2, 0x0d, 0x8a, 0x92, 0x3e, 0xa0, 0x5c, 0x48, 0xe7, 0x57, 0x28, 0x74, 0xa5, 0x01, 0xfc, 0x10, 0xa7, 0x51, 0xd5, 0xd6, 0xdb, 0x2e, 0x48, 0x2f, 0x8a, 0xdb, 0x8f, 0x04, 0xb5, 0x33, 0x04, 0x0f, - /* (2^170)P */ 0x47, 0x62, 0xdc, 0xd7, 0x8d, 0x2e, 0xda, 0x60, 0x9a, 0x81, 0xd4, 0x8c, 0xd3, 0xc9, 0xb4, 0x88, 0x97, 0x66, 0xf6, 0x01, 0xc0, 0x3a, 0x03, 0x13, 0x75, 0x7d, 0x36, 0x3b, 0xfe, 0x24, 0x3b, 0x27, - /* (2^171)P */ 0xd4, 0xb9, 0xb3, 0x31, 0x6a, 0xf6, 0xe8, 0xc6, 0xd5, 0x49, 0xdf, 0x94, 0xa4, 0x14, 0x15, 0x28, 0xa7, 0x3d, 0xb2, 0xc8, 0xdf, 0x6f, 0x72, 0xd1, 0x48, 0xe5, 0xde, 0x03, 0xd1, 0xe7, 0x3a, 0x4b, - /* (2^172)P */ 0x7e, 0x9d, 0x4b, 0xce, 0x19, 0x6e, 0x25, 0xc6, 0x1c, 0xc6, 0xe3, 0x86, 0xf1, 0x5c, 0x5c, 0xff, 0x45, 0xc1, 0x8e, 0x4b, 0xa3, 0x3c, 0xc6, 0xac, 0x74, 0x65, 0xe6, 0xfe, 0x88, 0x18, 0x62, 0x74, - /* (2^173)P */ 0x1e, 0x0a, 0x29, 0x45, 0x96, 0x40, 0x6f, 0x95, 0x2e, 0x96, 0x3a, 0x26, 0xe3, 0xf8, 0x0b, 0xef, 0x7b, 0x64, 0xc2, 0x5e, 0xeb, 0x50, 0x6a, 0xed, 0x02, 0x75, 0xca, 0x9d, 0x3a, 0x28, 0x94, 0x06, - /* (2^174)P */ 0xd1, 0xdc, 0xa2, 0x43, 0x36, 0x96, 0x9b, 0x76, 0x53, 0x53, 0xfc, 0x09, 0xea, 0xc8, 0xb7, 0x42, 0xab, 0x7e, 0x39, 0x13, 0xee, 0x2a, 0x00, 0x4f, 0x3a, 0xd6, 0xb7, 0x19, 0x2c, 0x5e, 0x00, 0x63, - /* (2^175)P */ 0xea, 0x3b, 0x02, 0x63, 0xda, 0x36, 0x67, 0xca, 0xb7, 0x99, 0x2a, 0xb1, 0x6d, 0x7f, 0x6c, 0x96, 0xe1, 0xc5, 0x37, 0xc5, 0x90, 0x93, 0xe0, 0xac, 0xee, 0x89, 0xaa, 0xa1, 0x63, 0x60, 0x69, 0x0b, - /* (2^176)P */ 0xe5, 0x56, 0x8c, 0x28, 0x97, 0x3e, 0xb0, 0xeb, 0xe8, 0x8b, 0x8c, 0x93, 0x9f, 0x9f, 0x2a, 0x43, 0x71, 0x7f, 0x71, 0x5b, 0x3d, 0xa9, 0xa5, 0xa6, 0x97, 0x9d, 0x8f, 0xe1, 0xc3, 0xb4, 0x5f, 0x1a, - /* (2^177)P */ 0xce, 0xcd, 0x60, 0x1c, 0xad, 0xe7, 0x94, 0x1c, 0xa0, 0xc4, 0x02, 0xfc, 0x43, 0x2a, 0x20, 0xee, 0x20, 0x6a, 0xc4, 0x67, 0xd8, 0xe4, 0xaf, 0x8d, 0x58, 0x7b, 0xc2, 0x8a, 0x3c, 0x26, 0x10, 0x0a, - /* (2^178)P */ 0x4a, 0x2a, 0x43, 0xe4, 0xdf, 0xa9, 0xde, 0xd0, 0xc5, 0x77, 0x92, 0xbe, 0x7b, 0xf8, 0x6a, 0x85, 0x1a, 0xc7, 0x12, 0xc2, 0xac, 0x72, 0x84, 0xce, 0x91, 0x1e, 0xbb, 0x9b, 0x6d, 0x1b, 0x15, 0x6f, - /* (2^179)P */ 0x6a, 0xd5, 0xee, 0x7c, 0x52, 0x6c, 0x77, 0x26, 0xec, 0xfa, 0xf8, 0xfb, 0xb7, 0x1c, 0x21, 0x7d, 0xcc, 0x09, 0x46, 0xfd, 0xa6, 0x66, 0xae, 0x37, 0x42, 0x0c, 0x77, 0xd2, 0x02, 0xb7, 0x81, 0x1f, - /* (2^180)P */ 0x92, 0x83, 0xc5, 0xea, 0x57, 0xb0, 0xb0, 0x2f, 0x9d, 0x4e, 0x74, 0x29, 0xfe, 0x89, 0xdd, 0xe1, 0xf8, 0xb4, 0xbe, 0x17, 0xeb, 0xf8, 0x64, 0xc9, 0x1e, 0xd4, 0xa2, 0xc9, 0x73, 0x10, 0x57, 0x29, - /* (2^181)P */ 0x54, 0xe2, 0xc0, 0x81, 0x89, 0xa1, 0x48, 0xa9, 0x30, 0x28, 0xb2, 0x65, 0x9b, 0x36, 0xf6, 0x2d, 0xc6, 0xd3, 0xcf, 0x5f, 0xd7, 0xb2, 0x3e, 0xa3, 0x1f, 0xa0, 0x99, 0x41, 0xec, 0xd6, 0x8c, 0x07, - /* (2^182)P */ 0x2f, 0x0d, 0x90, 0xad, 0x41, 0x4a, 0x58, 0x4a, 0x52, 0x4c, 0xc7, 0xe2, 0x78, 0x2b, 0x14, 0x32, 0x78, 0xc9, 0x31, 0x84, 0x33, 0xe8, 0xc4, 0x68, 0xc2, 0x9f, 0x68, 0x08, 0x90, 0xea, 0x69, 0x7f, - /* (2^183)P */ 0x65, 0x82, 0xa3, 0x46, 0x1e, 0xc8, 0xf2, 0x52, 0xfd, 0x32, 0xa8, 0x04, 0x2d, 0x07, 0x78, 0xfd, 0x94, 0x9e, 0x35, 0x25, 0xfa, 0xd5, 0xd7, 0x8c, 0xd2, 0x29, 0xcc, 0x54, 0x74, 0x1b, 0xe7, 0x4d, - /* (2^184)P */ 0xc9, 0x6a, 0xda, 0x1e, 0xad, 0x60, 0xeb, 0x42, 0x3a, 0x9c, 0xc0, 0xdb, 0xdf, 0x37, 0xad, 0x0a, 0x91, 0xc1, 0x3c, 0xe3, 0x71, 0x4b, 0x00, 0x81, 0x3c, 0x80, 0x22, 0x51, 0x34, 0xbe, 0xe6, 0x44, - /* (2^185)P */ 0xdb, 0x20, 0x19, 0xba, 0x88, 0x83, 0xfe, 0x03, 0x08, 0xb0, 0x0d, 0x15, 0x32, 0x7c, 0xd5, 0xf5, 0x29, 0x0c, 0xf6, 0x1a, 0x28, 0xc4, 0xc8, 0x49, 0xee, 0x1a, 0x70, 0xde, 0x18, 0xb5, 0xed, 0x21, - /* (2^186)P */ 0x99, 0xdc, 0x06, 0x8f, 0x41, 0x3e, 0xb6, 0x7f, 0xb8, 0xd7, 0x66, 0xc1, 0x99, 0x0d, 0x46, 0xa4, 0x83, 0x0a, 0x52, 0xce, 0x48, 0x52, 0xdd, 0x24, 0x58, 0x83, 0x92, 0x2b, 0x71, 0xad, 0xc3, 0x5e, - /* (2^187)P */ 0x0f, 0x93, 0x17, 0xbd, 0x5f, 0x2a, 0x02, 0x15, 0xe3, 0x70, 0x25, 0xd8, 0x77, 0x4a, 0xf6, 0xa4, 0x12, 0x37, 0x78, 0x15, 0x69, 0x8d, 0xbc, 0x12, 0xbb, 0x0a, 0x62, 0xfc, 0xc0, 0x94, 0x81, 0x49, - /* (2^188)P */ 0x82, 0x6c, 0x68, 0x55, 0xd2, 0xd9, 0xa2, 0x38, 0xf0, 0x21, 0x3e, 0x19, 0xd9, 0x6b, 0x5c, 0x78, 0x84, 0x54, 0x4a, 0xb2, 0x1a, 0xc8, 0xd5, 0xe4, 0x89, 0x09, 0xe2, 0xb2, 0x60, 0x78, 0x30, 0x56, - /* (2^189)P */ 0xc4, 0x74, 0x4d, 0x8b, 0xf7, 0x55, 0x9d, 0x42, 0x31, 0x01, 0x35, 0x43, 0x46, 0x83, 0xf1, 0x22, 0xff, 0x1f, 0xc7, 0x98, 0x45, 0xc2, 0x60, 0x1e, 0xef, 0x83, 0x99, 0x97, 0x14, 0xf0, 0xf2, 0x59, - /* (2^190)P */ 0x44, 0x4a, 0x49, 0xeb, 0x56, 0x7d, 0xa4, 0x46, 0x8e, 0xa1, 0x36, 0xd6, 0x54, 0xa8, 0x22, 0x3e, 0x3b, 0x1c, 0x49, 0x74, 0x52, 0xe1, 0x46, 0xb3, 0xe7, 0xcd, 0x90, 0x53, 0x4e, 0xfd, 0xea, 0x2c, - /* (2^191)P */ 0x75, 0x66, 0x0d, 0xbe, 0x38, 0x85, 0x8a, 0xba, 0x23, 0x8e, 0x81, 0x50, 0xbb, 0x74, 0x90, 0x4b, 0xc3, 0x04, 0xd3, 0x85, 0x90, 0xb8, 0xda, 0xcb, 0xc4, 0x92, 0x61, 0xe5, 0xe0, 0x4f, 0xa2, 0x61, - /* (2^192)P */ 0xcb, 0x5b, 0x52, 0xdb, 0xe6, 0x15, 0x76, 0xcb, 0xca, 0xe4, 0x67, 0xa5, 0x35, 0x8c, 0x7d, 0xdd, 0x69, 0xdd, 0xfc, 0xca, 0x3a, 0x15, 0xb4, 0xe6, 0x66, 0x97, 0x3c, 0x7f, 0x09, 0x8e, 0x66, 0x2d, - /* (2^193)P */ 0xf0, 0x5e, 0xe5, 0x5c, 0x26, 0x7e, 0x7e, 0xa5, 0x67, 0xb9, 0xd4, 0x7c, 0x52, 0x4e, 0x9f, 0x5d, 0xe5, 0xd1, 0x2f, 0x49, 0x06, 0x36, 0xc8, 0xfb, 0xae, 0xf7, 0xc3, 0xb7, 0xbe, 0x52, 0x0d, 0x09, - /* (2^194)P */ 0x7c, 0x4d, 0x7b, 0x1e, 0x5a, 0x51, 0xb9, 0x09, 0xc0, 0x44, 0xda, 0x99, 0x25, 0x6a, 0x26, 0x1f, 0x04, 0x55, 0xc5, 0xe2, 0x48, 0x95, 0xc4, 0xa1, 0xcc, 0x15, 0x6f, 0x12, 0x87, 0x42, 0xf0, 0x7e, - /* (2^195)P */ 0x15, 0xef, 0x30, 0xbd, 0x9d, 0x65, 0xd1, 0xfe, 0x7b, 0x27, 0xe0, 0xc4, 0xee, 0xb9, 0x4a, 0x8b, 0x91, 0x32, 0xdf, 0xa5, 0x36, 0x62, 0x4d, 0x88, 0x88, 0xf7, 0x5c, 0xbf, 0xa6, 0x6e, 0xd9, 0x1f, - /* (2^196)P */ 0x9a, 0x0d, 0x19, 0x1f, 0x98, 0x61, 0xa1, 0x42, 0xc1, 0x52, 0x60, 0x7e, 0x50, 0x49, 0xd8, 0x61, 0xd5, 0x2c, 0x5a, 0x28, 0xbf, 0x13, 0xe1, 0x9f, 0xd8, 0x85, 0xad, 0xdb, 0x76, 0xd6, 0x22, 0x7c, - /* (2^197)P */ 0x7d, 0xd2, 0xfb, 0x2b, 0xed, 0x70, 0xe7, 0x82, 0xa5, 0xf5, 0x96, 0xe9, 0xec, 0xb2, 0x05, 0x4c, 0x50, 0x01, 0x90, 0xb0, 0xc2, 0xa9, 0x40, 0xcd, 0x64, 0xbf, 0xd9, 0x13, 0x92, 0x31, 0x95, 0x58, - /* (2^198)P */ 0x08, 0x2e, 0xea, 0x3f, 0x70, 0x5d, 0xcc, 0xe7, 0x8c, 0x18, 0xe2, 0x58, 0x12, 0x49, 0x0c, 0xb5, 0xf0, 0x5b, 0x20, 0x48, 0xaa, 0x0b, 0xe3, 0xcc, 0x62, 0x2d, 0xa3, 0xcf, 0x9c, 0x65, 0x7c, 0x53, - /* (2^199)P */ 0x88, 0xc0, 0xcf, 0x98, 0x3a, 0x62, 0xb6, 0x37, 0xa4, 0xac, 0xd6, 0xa4, 0x1f, 0xed, 0x9b, 0xfe, 0xb0, 0xd1, 0xa8, 0x56, 0x8e, 0x9b, 0xd2, 0x04, 0x75, 0x95, 0x51, 0x0b, 0xc4, 0x71, 0x5f, 0x72, - /* (2^200)P */ 0xe6, 0x9c, 0x33, 0xd0, 0x9c, 0xf8, 0xc7, 0x28, 0x8b, 0xc1, 0xdd, 0x69, 0x44, 0xb1, 0x67, 0x83, 0x2c, 0x65, 0xa1, 0xa6, 0x83, 0xda, 0x3a, 0x88, 0x17, 0x6c, 0x4d, 0x03, 0x74, 0x19, 0x5f, 0x58, - /* (2^201)P */ 0x88, 0x91, 0xb1, 0xf1, 0x66, 0xb2, 0xcf, 0x89, 0x17, 0x52, 0xc3, 0xe7, 0x63, 0x48, 0x3b, 0xe6, 0x6a, 0x52, 0xc0, 0xb4, 0xa6, 0x9d, 0x8c, 0xd8, 0x35, 0x46, 0x95, 0xf0, 0x9d, 0x5c, 0x03, 0x3e, - /* (2^202)P */ 0x9d, 0xde, 0x45, 0xfb, 0x12, 0x54, 0x9d, 0xdd, 0x0d, 0xf4, 0xcf, 0xe4, 0x32, 0x45, 0x68, 0xdd, 0x1c, 0x67, 0x1d, 0x15, 0x9b, 0x99, 0x5c, 0x4b, 0x90, 0xf6, 0xe7, 0x11, 0xc8, 0x2c, 0x8c, 0x2d, - /* (2^203)P */ 0x40, 0x5d, 0x05, 0x90, 0x1d, 0xbe, 0x54, 0x7f, 0x40, 0xaf, 0x4a, 0x46, 0xdf, 0xc5, 0x64, 0xa4, 0xbe, 0x17, 0xe9, 0xf0, 0x24, 0x96, 0x97, 0x33, 0x30, 0x6b, 0x35, 0x27, 0xc5, 0x8d, 0x01, 0x2c, - /* (2^204)P */ 0xd4, 0xb3, 0x30, 0xe3, 0x24, 0x50, 0x41, 0xa5, 0xd3, 0x52, 0x16, 0x69, 0x96, 0x3d, 0xff, 0x73, 0xf1, 0x59, 0x9b, 0xef, 0xc4, 0x42, 0xec, 0x94, 0x5a, 0x8e, 0xd0, 0x18, 0x16, 0x20, 0x47, 0x07, - /* (2^205)P */ 0x53, 0x1c, 0x41, 0xca, 0x8a, 0xa4, 0x6c, 0x4d, 0x19, 0x61, 0xa6, 0xcf, 0x2f, 0x5f, 0x41, 0x66, 0xff, 0x27, 0xe2, 0x51, 0x00, 0xd4, 0x4d, 0x9c, 0xeb, 0xf7, 0x02, 0x9a, 0xc0, 0x0b, 0x81, 0x59, - /* (2^206)P */ 0x1d, 0x10, 0xdc, 0xb3, 0x71, 0xb1, 0x7e, 0x2a, 0x8e, 0xf6, 0xfe, 0x9f, 0xb9, 0x5a, 0x1c, 0x44, 0xea, 0x59, 0xb3, 0x93, 0x9b, 0x5c, 0x02, 0x32, 0x2f, 0x11, 0x9d, 0x1e, 0xa7, 0xe0, 0x8c, 0x5e, - /* (2^207)P */ 0xfd, 0x03, 0x95, 0x42, 0x92, 0xcb, 0xcc, 0xbf, 0x55, 0x5d, 0x09, 0x2f, 0x75, 0xba, 0x71, 0xd2, 0x1e, 0x09, 0x2d, 0x97, 0x5e, 0xad, 0x5e, 0x34, 0xba, 0x03, 0x31, 0xa8, 0x11, 0xdf, 0xc8, 0x18, - /* (2^208)P */ 0x4c, 0x0f, 0xed, 0x9a, 0x9a, 0x94, 0xcd, 0x90, 0x7e, 0xe3, 0x60, 0x66, 0xcb, 0xf4, 0xd1, 0xc5, 0x0b, 0x2e, 0xc5, 0x56, 0x2d, 0xc5, 0xca, 0xb8, 0x0d, 0x8e, 0x80, 0xc5, 0x00, 0xe4, 0x42, 0x6e, - /* (2^209)P */ 0x23, 0xfd, 0xae, 0xee, 0x66, 0x69, 0xb4, 0xa3, 0xca, 0xcd, 0x9e, 0xe3, 0x0b, 0x1f, 0x4f, 0x0c, 0x1d, 0xa5, 0x83, 0xd6, 0xc9, 0xc8, 0x9d, 0x18, 0x1b, 0x35, 0x09, 0x4c, 0x05, 0x7f, 0xf2, 0x51, - /* (2^210)P */ 0x82, 0x06, 0x32, 0x2a, 0xcd, 0x7c, 0x48, 0x4c, 0x96, 0x1c, 0xdf, 0xb3, 0x5b, 0xa9, 0x7e, 0x58, 0xe8, 0xb8, 0x5c, 0x55, 0x9e, 0xf7, 0xcc, 0xc8, 0x3d, 0xd7, 0x06, 0xa2, 0x29, 0xc8, 0x7d, 0x54, - /* (2^211)P */ 0x06, 0x9b, 0xc3, 0x80, 0xcd, 0xa6, 0x22, 0xb8, 0xc6, 0xd4, 0x00, 0x20, 0x73, 0x54, 0x6d, 0xe9, 0x4d, 0x3b, 0x46, 0x91, 0x6f, 0x5b, 0x53, 0x28, 0x1d, 0x6e, 0x48, 0xe2, 0x60, 0x46, 0x8f, 0x22, - /* (2^212)P */ 0xbf, 0x3a, 0x8d, 0xde, 0x38, 0x95, 0x79, 0x98, 0x6e, 0xca, 0xeb, 0x45, 0x00, 0x33, 0xd8, 0x8c, 0x38, 0xe7, 0x21, 0x82, 0x00, 0x2a, 0x95, 0x79, 0xbb, 0xd2, 0x5c, 0x53, 0xa7, 0xe1, 0x22, 0x43, - /* (2^213)P */ 0x1c, 0x80, 0xd1, 0x19, 0x18, 0xc1, 0x14, 0xb1, 0xc7, 0x5e, 0x3f, 0x4f, 0xd8, 0xe4, 0x16, 0x20, 0x4c, 0x0f, 0x26, 0x09, 0xf4, 0x2d, 0x0e, 0xdd, 0x66, 0x72, 0x5f, 0xae, 0xc0, 0x62, 0xc3, 0x5e, - /* (2^214)P */ 0xee, 0xb4, 0xb2, 0xb8, 0x18, 0x2b, 0x46, 0xc0, 0xfb, 0x1a, 0x4d, 0x27, 0x50, 0xd9, 0xc8, 0x7c, 0xd2, 0x02, 0x6b, 0x43, 0x05, 0x71, 0x5f, 0xf2, 0xd3, 0xcc, 0xf9, 0xbf, 0xdc, 0xf8, 0xbb, 0x43, - /* (2^215)P */ 0xdf, 0xe9, 0x39, 0xa0, 0x67, 0x17, 0xad, 0xb6, 0x83, 0x35, 0x9d, 0xf6, 0xa8, 0x4d, 0x71, 0xb0, 0xf5, 0x31, 0x29, 0xb4, 0x18, 0xfa, 0x55, 0x5e, 0x61, 0x09, 0xc6, 0x33, 0x8f, 0x55, 0xd5, 0x4e, - /* (2^216)P */ 0xdd, 0xa5, 0x47, 0xc6, 0x01, 0x79, 0xe3, 0x1f, 0x57, 0xd3, 0x81, 0x80, 0x1f, 0xdf, 0x3d, 0x59, 0xa6, 0xd7, 0x3f, 0x81, 0xfd, 0xa4, 0x49, 0x02, 0x61, 0xaf, 0x9c, 0x4e, 0x27, 0xca, 0xac, 0x69, - /* (2^217)P */ 0xc9, 0x21, 0x07, 0x33, 0xea, 0xa3, 0x7b, 0x04, 0xa0, 0x1e, 0x7e, 0x0e, 0xc2, 0x3f, 0x42, 0x83, 0x60, 0x4a, 0x31, 0x01, 0xaf, 0xc0, 0xf4, 0x1d, 0x27, 0x95, 0x28, 0x89, 0xab, 0x2d, 0xa6, 0x09, - /* (2^218)P */ 0x00, 0xcb, 0xc6, 0x9c, 0xa4, 0x25, 0xb3, 0xa5, 0xb6, 0x6c, 0xb5, 0x54, 0xc6, 0x5d, 0x4b, 0xe9, 0xa0, 0x94, 0xc9, 0xad, 0x79, 0x87, 0xe2, 0x3b, 0xad, 0x4a, 0x3a, 0xba, 0xf8, 0xe8, 0x96, 0x42, - /* (2^219)P */ 0xab, 0x1e, 0x45, 0x1e, 0x76, 0x89, 0x86, 0x32, 0x4a, 0x59, 0x59, 0xff, 0x8b, 0x59, 0x4d, 0x2e, 0x4a, 0x08, 0xa7, 0xd7, 0x53, 0x68, 0xb9, 0x49, 0xa8, 0x20, 0x14, 0x60, 0x19, 0xa3, 0x80, 0x49, - /* (2^220)P */ 0x42, 0x2c, 0x55, 0x2f, 0xe1, 0xb9, 0x65, 0x95, 0x96, 0xfe, 0x00, 0x71, 0xdb, 0x18, 0x53, 0x8a, 0xd7, 0xd0, 0xad, 0x43, 0x4d, 0x0b, 0xc9, 0x05, 0xda, 0x4e, 0x5d, 0x6a, 0xd6, 0x4c, 0x8b, 0x53, - /* (2^221)P */ 0x9f, 0x03, 0x9f, 0xe8, 0xc3, 0x4f, 0xe9, 0xf4, 0x45, 0x80, 0x61, 0x6f, 0xf2, 0x9a, 0x2c, 0x59, 0x50, 0x95, 0x4b, 0xfd, 0xb5, 0x6e, 0xa3, 0x08, 0x19, 0x14, 0xed, 0xc2, 0xf6, 0xfa, 0xff, 0x25, - /* (2^222)P */ 0x54, 0xd3, 0x79, 0xcc, 0x59, 0x44, 0x43, 0x34, 0x6b, 0x47, 0xd5, 0xb1, 0xb4, 0xbf, 0xec, 0xee, 0x99, 0x5d, 0x61, 0x61, 0xa0, 0x34, 0xeb, 0xdd, 0x73, 0xb7, 0x64, 0xeb, 0xcc, 0xce, 0x29, 0x51, - /* (2^223)P */ 0x20, 0x35, 0x99, 0x94, 0x58, 0x21, 0x43, 0xee, 0x3b, 0x0b, 0x4c, 0xf1, 0x7c, 0x9c, 0x2f, 0x77, 0xd5, 0xda, 0xbe, 0x06, 0xe3, 0xfc, 0xe2, 0xd2, 0x97, 0x6a, 0xf0, 0x46, 0xb5, 0x42, 0x5f, 0x71, - /* (2^224)P */ 0x1a, 0x5f, 0x5b, 0xda, 0xce, 0xcd, 0x4e, 0x43, 0xa9, 0x41, 0x97, 0xa4, 0x15, 0x71, 0xa1, 0x0d, 0x2e, 0xad, 0xed, 0x73, 0x7c, 0xd7, 0x0b, 0x68, 0x41, 0x90, 0xdd, 0x4e, 0x35, 0x02, 0x7c, 0x48, - /* (2^225)P */ 0xc4, 0xd9, 0x0e, 0xa7, 0xf3, 0xef, 0xef, 0xb8, 0x02, 0xe3, 0x57, 0xe8, 0xa3, 0x2a, 0xa3, 0x56, 0xa0, 0xa5, 0xa2, 0x48, 0xbd, 0x68, 0x3a, 0xdf, 0x44, 0xc4, 0x76, 0x31, 0xb7, 0x50, 0xf6, 0x07, - /* (2^226)P */ 0xb1, 0xcc, 0xe0, 0x26, 0x16, 0x9b, 0x8b, 0xe3, 0x36, 0xfb, 0x09, 0x8b, 0xc1, 0x53, 0xe0, 0x79, 0x64, 0x49, 0xf9, 0xc9, 0x19, 0x03, 0xd9, 0x56, 0xc4, 0xf5, 0x9f, 0xac, 0xe7, 0x41, 0xa9, 0x1c, - /* (2^227)P */ 0xbb, 0xa0, 0x2f, 0x16, 0x29, 0xdf, 0xc4, 0x49, 0x05, 0x33, 0xb3, 0x82, 0x32, 0xcf, 0x88, 0x84, 0x7d, 0x43, 0xbb, 0xca, 0x14, 0xda, 0xdf, 0x95, 0x86, 0xad, 0xd5, 0x64, 0x82, 0xf7, 0x91, 0x33, - /* (2^228)P */ 0x5d, 0x09, 0xb5, 0xe2, 0x6a, 0xe0, 0x9a, 0x72, 0x46, 0xa9, 0x59, 0x32, 0xd7, 0x58, 0x8a, 0xd5, 0xed, 0x21, 0x39, 0xd1, 0x62, 0x42, 0x83, 0xe9, 0x92, 0xb5, 0x4b, 0xa5, 0xfa, 0xda, 0xfe, 0x27, - /* (2^229)P */ 0xbb, 0x48, 0xad, 0x29, 0xb8, 0xc5, 0x9d, 0xa9, 0x60, 0xe2, 0x9e, 0x49, 0x42, 0x57, 0x02, 0x5f, 0xfd, 0x13, 0x75, 0x5d, 0xcd, 0x8e, 0x2c, 0x80, 0x38, 0xd9, 0x6d, 0x3f, 0xef, 0xb3, 0xce, 0x78, - /* (2^230)P */ 0x94, 0x5d, 0x13, 0x8a, 0x4f, 0xf4, 0x42, 0xc3, 0xa3, 0xdd, 0x8c, 0x82, 0x44, 0xdb, 0x9e, 0x7b, 0xe7, 0xcf, 0x37, 0x05, 0x1a, 0xd1, 0x36, 0x94, 0xc8, 0xb4, 0x1a, 0xec, 0x64, 0xb1, 0x64, 0x50, - /* (2^231)P */ 0xfc, 0xb2, 0x7e, 0xd3, 0xcf, 0xec, 0x20, 0x70, 0xfc, 0x25, 0x0d, 0xd9, 0x3e, 0xea, 0x31, 0x1f, 0x34, 0xbb, 0xa1, 0xdf, 0x7b, 0x0d, 0x93, 0x1b, 0x44, 0x30, 0x11, 0x48, 0x7a, 0x46, 0x44, 0x53, - /* (2^232)P */ 0xfb, 0x6d, 0x5e, 0xf2, 0x70, 0x31, 0x07, 0x70, 0xc8, 0x4c, 0x11, 0x50, 0x1a, 0xdc, 0x85, 0xe3, 0x00, 0x4f, 0xfc, 0xc8, 0x8a, 0x69, 0x48, 0x23, 0xd8, 0x40, 0xdd, 0x84, 0x52, 0xa5, 0x77, 0x2a, - /* (2^233)P */ 0xe4, 0x6c, 0x8c, 0xc9, 0xe0, 0xaf, 0x06, 0xfe, 0xe4, 0xd6, 0xdf, 0xdd, 0x96, 0xdf, 0x35, 0xc2, 0xd3, 0x1e, 0xbf, 0x33, 0x1e, 0xd0, 0x28, 0x14, 0xaf, 0xbd, 0x00, 0x93, 0xec, 0x68, 0x57, 0x78, - /* (2^234)P */ 0x3b, 0xb6, 0xde, 0x91, 0x7a, 0xe5, 0x02, 0x97, 0x80, 0x8b, 0xce, 0xe5, 0xbf, 0xb8, 0xbd, 0x61, 0xac, 0x58, 0x1d, 0x3d, 0x6f, 0x42, 0x5b, 0x64, 0xbc, 0x57, 0xa5, 0x27, 0x22, 0xa8, 0x04, 0x48, - /* (2^235)P */ 0x01, 0x26, 0x4d, 0xb4, 0x8a, 0x04, 0x57, 0x8e, 0x35, 0x69, 0x3a, 0x4b, 0x1a, 0x50, 0xd6, 0x68, 0x93, 0xc2, 0xe1, 0xf9, 0xc3, 0x9e, 0x9c, 0xc3, 0xe2, 0x63, 0xde, 0xd4, 0x57, 0xf2, 0x72, 0x41, - /* (2^236)P */ 0x01, 0x64, 0x0c, 0x33, 0x50, 0xb4, 0x68, 0xd3, 0x91, 0x23, 0x8f, 0x41, 0x17, 0x30, 0x0d, 0x04, 0x0d, 0xd9, 0xb7, 0x90, 0x60, 0xbb, 0x34, 0x2c, 0x1f, 0xd5, 0xdf, 0x8f, 0x22, 0x49, 0xf6, 0x16, - /* (2^237)P */ 0xf5, 0x8e, 0x92, 0x2b, 0x8e, 0x81, 0xa6, 0xbe, 0x72, 0x1e, 0xc1, 0xcd, 0x91, 0xcf, 0x8c, 0xe2, 0xcd, 0x36, 0x7a, 0xe7, 0x68, 0xaa, 0x4a, 0x59, 0x0f, 0xfd, 0x7f, 0x6c, 0x80, 0x34, 0x30, 0x31, - /* (2^238)P */ 0x65, 0xbd, 0x49, 0x22, 0xac, 0x27, 0x9d, 0x8a, 0x12, 0x95, 0x8e, 0x01, 0x64, 0xb4, 0xa3, 0x19, 0xc7, 0x7e, 0xb3, 0x52, 0xf3, 0xcf, 0x6c, 0xc2, 0x21, 0x7b, 0x79, 0x1d, 0x34, 0x68, 0x6f, 0x05, - /* (2^239)P */ 0x27, 0x23, 0xfd, 0x7e, 0x75, 0xd6, 0x79, 0x5e, 0x15, 0xfe, 0x3a, 0x55, 0xb6, 0xbc, 0xbd, 0xfa, 0x60, 0x5a, 0xaf, 0x6e, 0x2c, 0x22, 0xe7, 0xd3, 0x3b, 0x74, 0xae, 0x4d, 0x6d, 0xc7, 0x46, 0x70, - /* (2^240)P */ 0x55, 0x4a, 0x8d, 0xb1, 0x72, 0xe8, 0x0b, 0x66, 0x96, 0x14, 0x4e, 0x57, 0x18, 0x25, 0x99, 0x19, 0xbb, 0xdc, 0x2b, 0x30, 0x3a, 0x05, 0x03, 0xc1, 0x8e, 0x8e, 0x21, 0x0b, 0x80, 0xe9, 0xd8, 0x3e, - /* (2^241)P */ 0x3e, 0xe0, 0x75, 0xfa, 0x39, 0x92, 0x0b, 0x7b, 0x83, 0xc0, 0x33, 0x46, 0x68, 0xfb, 0xe9, 0xef, 0x93, 0x77, 0x1a, 0x39, 0xbe, 0x5f, 0xa3, 0x98, 0x34, 0xfe, 0xd0, 0xe2, 0x0f, 0x51, 0x65, 0x60, - /* (2^242)P */ 0x0c, 0xad, 0xab, 0x48, 0x85, 0x66, 0xcb, 0x55, 0x27, 0xe5, 0x87, 0xda, 0x48, 0x45, 0x58, 0xb4, 0xdd, 0xc1, 0x07, 0x01, 0xea, 0xec, 0x43, 0x2c, 0x35, 0xde, 0x72, 0x93, 0x80, 0x28, 0x60, 0x52, - /* (2^243)P */ 0x1f, 0x3b, 0x21, 0xf9, 0x6a, 0xc5, 0x15, 0x34, 0xdb, 0x98, 0x7e, 0x01, 0x4d, 0x1a, 0xee, 0x5b, 0x9b, 0x70, 0xcf, 0xb5, 0x05, 0xb1, 0xf6, 0x13, 0xb6, 0x9a, 0xb2, 0x82, 0x34, 0x0e, 0xf2, 0x5f, - /* (2^244)P */ 0x90, 0x6c, 0x2e, 0xcc, 0x75, 0x9c, 0xa2, 0x0a, 0x06, 0xe2, 0x70, 0x3a, 0xca, 0x73, 0x7d, 0xfc, 0x15, 0xc5, 0xb5, 0xc4, 0x8f, 0xc3, 0x9f, 0x89, 0x07, 0xc2, 0xff, 0x24, 0xb1, 0x86, 0x03, 0x25, - /* (2^245)P */ 0x56, 0x2b, 0x3d, 0xae, 0xd5, 0x28, 0xea, 0x54, 0xce, 0x60, 0xde, 0xd6, 0x9d, 0x14, 0x13, 0x99, 0xc1, 0xd6, 0x06, 0x8f, 0xc5, 0x4f, 0x69, 0x16, 0xc7, 0x8f, 0x01, 0xeb, 0x75, 0x39, 0xb2, 0x46, - /* (2^246)P */ 0xe2, 0xb4, 0xb7, 0xb4, 0x0f, 0x6a, 0x0a, 0x47, 0xde, 0x53, 0x72, 0x8f, 0x5a, 0x47, 0x92, 0x5d, 0xdb, 0x3a, 0xbd, 0x2f, 0xb5, 0xe5, 0xee, 0xab, 0x68, 0x69, 0x80, 0xa0, 0x01, 0x08, 0xa2, 0x7f, - /* (2^247)P */ 0xd2, 0x14, 0x77, 0x9f, 0xf1, 0xfa, 0xf3, 0x76, 0xc3, 0x60, 0x46, 0x2f, 0xc1, 0x40, 0xe8, 0xb3, 0x4e, 0x74, 0x12, 0xf2, 0x8d, 0xcd, 0xb4, 0x0f, 0xd2, 0x2d, 0x3a, 0x1d, 0x25, 0x5a, 0x06, 0x4b, - /* (2^248)P */ 0x4a, 0xcd, 0x77, 0x3d, 0x38, 0xde, 0xeb, 0x5c, 0xb1, 0x9c, 0x2c, 0x88, 0xdf, 0x39, 0xdf, 0x6a, 0x59, 0xf7, 0x9a, 0xb0, 0x2e, 0x24, 0xdd, 0xa2, 0x22, 0x64, 0x5f, 0x0e, 0xe5, 0xc0, 0x47, 0x31, - /* (2^249)P */ 0xdb, 0x50, 0x13, 0x1d, 0x10, 0xa5, 0x4c, 0x16, 0x62, 0xc9, 0x3f, 0xc3, 0x79, 0x34, 0xd1, 0xf8, 0x08, 0xda, 0xe5, 0x13, 0x4d, 0xce, 0x40, 0xe6, 0xba, 0xf8, 0x61, 0x50, 0xc4, 0xe0, 0xde, 0x4b, - /* (2^250)P */ 0xc9, 0xb1, 0xed, 0xa4, 0xc1, 0x6d, 0xc4, 0xd7, 0x8a, 0xd9, 0x7f, 0x43, 0xb6, 0xd7, 0x14, 0x55, 0x0b, 0xc0, 0xa1, 0xb2, 0x6b, 0x2f, 0x94, 0x58, 0x0e, 0x71, 0x70, 0x1d, 0xab, 0xb2, 0xff, 0x2d, - /* (2^251)P */ 0x68, 0x6d, 0x8b, 0xc1, 0x2f, 0xcf, 0xdf, 0xcc, 0x67, 0x61, 0x80, 0xb7, 0xa8, 0xcb, 0xeb, 0xa8, 0xe3, 0x37, 0x29, 0x5e, 0xf9, 0x97, 0x06, 0x98, 0x8c, 0x6e, 0x12, 0xd0, 0x1c, 0xba, 0xfb, 0x02, - /* (2^252)P */ 0x65, 0x45, 0xff, 0xad, 0x60, 0xc3, 0x98, 0xcb, 0x19, 0x15, 0xdb, 0x4b, 0xd2, 0x01, 0x71, 0x44, 0xd5, 0x15, 0xfb, 0x75, 0x74, 0xc8, 0xc4, 0x98, 0x7d, 0xa2, 0x22, 0x6e, 0x6d, 0xc7, 0xf8, 0x05, - /* (2^253)P */ 0x94, 0xf4, 0xb9, 0xfe, 0xdf, 0xe5, 0x69, 0xab, 0x75, 0x6b, 0x40, 0x18, 0x9d, 0xc7, 0x09, 0xae, 0x1d, 0x2d, 0xa4, 0x94, 0xfb, 0x45, 0x9b, 0x19, 0x84, 0xfa, 0x2a, 0xae, 0xeb, 0x0a, 0x71, 0x79, - /* (2^254)P */ 0xdf, 0xd2, 0x34, 0xf3, 0xa7, 0xed, 0xad, 0xa6, 0xb4, 0x57, 0x2a, 0xaf, 0x51, 0x9c, 0xde, 0x7b, 0xa8, 0xea, 0xdc, 0x86, 0x4f, 0xc6, 0x8f, 0xa9, 0x7b, 0xd0, 0x0e, 0xc2, 0x35, 0x03, 0xbe, 0x6b, - /* (2^255)P */ 0x44, 0x43, 0x98, 0x53, 0xbe, 0xdc, 0x7f, 0x66, 0xa8, 0x49, 0x59, 0x00, 0x1c, 0xbc, 0x72, 0x07, 0x8e, 0xd6, 0xbe, 0x4e, 0x9f, 0xa4, 0x07, 0xba, 0xbf, 0x30, 0xdf, 0xba, 0x85, 0xb0, 0xa7, 0x1f, -} diff --git a/vendor/github.com/cloudflare/circl/dh/x448/curve.go b/vendor/github.com/cloudflare/circl/dh/x448/curve.go deleted file mode 100644 index d59564e4b42..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x448/curve.go +++ /dev/null @@ -1,104 +0,0 @@ -package x448 - -import ( - fp "github.com/cloudflare/circl/math/fp448" -) - -// ladderJoye calculates a fixed-point multiplication with the generator point. -// The algorithm is the right-to-left Joye's ladder as described -// in "How to precompute a ladder" in SAC'2017. -func ladderJoye(k *Key) { - w := [5]fp.Elt{} // [mu,x1,z1,x2,z2] order must be preserved. - w[1] = fp.Elt{ // x1 = S - 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - } - fp.SetOne(&w[2]) // z1 = 1 - w[3] = fp.Elt{ // x2 = G-S - 0x20, 0x27, 0x9d, 0xc9, 0x7d, 0x19, 0xb1, 0xac, - 0xf8, 0xba, 0x69, 0x1c, 0xff, 0x33, 0xac, 0x23, - 0x51, 0x1b, 0xce, 0x3a, 0x64, 0x65, 0xbd, 0xf1, - 0x23, 0xf8, 0xc1, 0x84, 0x9d, 0x45, 0x54, 0x29, - 0x67, 0xb9, 0x81, 0x1c, 0x03, 0xd1, 0xcd, 0xda, - 0x7b, 0xeb, 0xff, 0x1a, 0x88, 0x03, 0xcf, 0x3a, - 0x42, 0x44, 0x32, 0x01, 0x25, 0xb7, 0xfa, 0xf0, - } - fp.SetOne(&w[4]) // z2 = 1 - - const n = 448 - const h = 2 - swap := uint(1) - for s := 0; s < n-h; s++ { - i := (s + h) / 8 - j := (s + h) % 8 - bit := uint((k[i] >> uint(j)) & 1) - copy(w[0][:], tableGenerator[s*Size:(s+1)*Size]) - diffAdd(&w, swap^bit) - swap = bit - } - for s := 0; s < h; s++ { - double(&w[1], &w[2]) - } - toAffine((*[fp.Size]byte)(k), &w[1], &w[2]) -} - -// ladderMontgomery calculates a generic scalar point multiplication -// The algorithm implemented is the left-to-right Montgomery's ladder. -func ladderMontgomery(k, xP *Key) { - w := [5]fp.Elt{} // [x1, x2, z2, x3, z3] order must be preserved. - w[0] = *(*fp.Elt)(xP) // x1 = xP - fp.SetOne(&w[1]) // x2 = 1 - w[3] = *(*fp.Elt)(xP) // x3 = xP - fp.SetOne(&w[4]) // z3 = 1 - - move := uint(0) - for s := 448 - 1; s >= 0; s-- { - i := s / 8 - j := s % 8 - bit := uint((k[i] >> uint(j)) & 1) - ladderStep(&w, move^bit) - move = bit - } - toAffine((*[fp.Size]byte)(k), &w[1], &w[2]) -} - -func toAffine(k *[fp.Size]byte, x, z *fp.Elt) { - fp.Inv(z, z) - fp.Mul(x, x, z) - _ = fp.ToBytes(k[:], x) -} - -var lowOrderPoints = [3]fp.Elt{ - { /* (0,_,1) point of order 2 on Curve448 */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }, - { /* (1,_,1) a point of order 4 on the twist of Curve448 */ - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }, - { /* (-1,_,1) point of order 4 on Curve448 */ - 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - }, -} diff --git a/vendor/github.com/cloudflare/circl/dh/x448/curve_amd64.go b/vendor/github.com/cloudflare/circl/dh/x448/curve_amd64.go deleted file mode 100644 index a0622666136..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x448/curve_amd64.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build amd64 && !purego -// +build amd64,!purego - -package x448 - -import ( - fp "github.com/cloudflare/circl/math/fp448" - "golang.org/x/sys/cpu" -) - -var hasBmi2Adx = cpu.X86.HasBMI2 && cpu.X86.HasADX - -var _ = hasBmi2Adx - -func double(x, z *fp.Elt) { doubleAmd64(x, z) } -func diffAdd(w *[5]fp.Elt, b uint) { diffAddAmd64(w, b) } -func ladderStep(w *[5]fp.Elt, b uint) { ladderStepAmd64(w, b) } -func mulA24(z, x *fp.Elt) { mulA24Amd64(z, x) } - -//go:noescape -func doubleAmd64(x, z *fp.Elt) - -//go:noescape -func diffAddAmd64(w *[5]fp.Elt, b uint) - -//go:noescape -func ladderStepAmd64(w *[5]fp.Elt, b uint) - -//go:noescape -func mulA24Amd64(z, x *fp.Elt) diff --git a/vendor/github.com/cloudflare/circl/dh/x448/curve_amd64.h b/vendor/github.com/cloudflare/circl/dh/x448/curve_amd64.h deleted file mode 100644 index 8c1ae4d0fbb..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x448/curve_amd64.h +++ /dev/null @@ -1,111 +0,0 @@ -#define ladderStepLeg \ - addSub(x2,z2) \ - addSub(x3,z3) \ - integerMulLeg(b0,x2,z3) \ - integerMulLeg(b1,x3,z2) \ - reduceFromDoubleLeg(t0,b0) \ - reduceFromDoubleLeg(t1,b1) \ - addSub(t0,t1) \ - cselect(x2,x3,regMove) \ - cselect(z2,z3,regMove) \ - integerSqrLeg(b0,t0) \ - integerSqrLeg(b1,t1) \ - reduceFromDoubleLeg(x3,b0) \ - reduceFromDoubleLeg(z3,b1) \ - integerMulLeg(b0,x1,z3) \ - reduceFromDoubleLeg(z3,b0) \ - integerSqrLeg(b0,x2) \ - integerSqrLeg(b1,z2) \ - reduceFromDoubleLeg(x2,b0) \ - reduceFromDoubleLeg(z2,b1) \ - subtraction(t0,x2,z2) \ - multiplyA24Leg(t1,t0) \ - additionLeg(t1,t1,z2) \ - integerMulLeg(b0,x2,z2) \ - integerMulLeg(b1,t0,t1) \ - reduceFromDoubleLeg(x2,b0) \ - reduceFromDoubleLeg(z2,b1) - -#define ladderStepBmi2Adx \ - addSub(x2,z2) \ - addSub(x3,z3) \ - integerMulAdx(b0,x2,z3) \ - integerMulAdx(b1,x3,z2) \ - reduceFromDoubleAdx(t0,b0) \ - reduceFromDoubleAdx(t1,b1) \ - addSub(t0,t1) \ - cselect(x2,x3,regMove) \ - cselect(z2,z3,regMove) \ - integerSqrAdx(b0,t0) \ - integerSqrAdx(b1,t1) \ - reduceFromDoubleAdx(x3,b0) \ - reduceFromDoubleAdx(z3,b1) \ - integerMulAdx(b0,x1,z3) \ - reduceFromDoubleAdx(z3,b0) \ - integerSqrAdx(b0,x2) \ - integerSqrAdx(b1,z2) \ - reduceFromDoubleAdx(x2,b0) \ - reduceFromDoubleAdx(z2,b1) \ - subtraction(t0,x2,z2) \ - multiplyA24Adx(t1,t0) \ - additionAdx(t1,t1,z2) \ - integerMulAdx(b0,x2,z2) \ - integerMulAdx(b1,t0,t1) \ - reduceFromDoubleAdx(x2,b0) \ - reduceFromDoubleAdx(z2,b1) - -#define difAddLeg \ - addSub(x1,z1) \ - integerMulLeg(b0,z1,ui) \ - reduceFromDoubleLeg(z1,b0) \ - addSub(x1,z1) \ - integerSqrLeg(b0,x1) \ - integerSqrLeg(b1,z1) \ - reduceFromDoubleLeg(x1,b0) \ - reduceFromDoubleLeg(z1,b1) \ - integerMulLeg(b0,x1,z2) \ - integerMulLeg(b1,z1,x2) \ - reduceFromDoubleLeg(x1,b0) \ - reduceFromDoubleLeg(z1,b1) - -#define difAddBmi2Adx \ - addSub(x1,z1) \ - integerMulAdx(b0,z1,ui) \ - reduceFromDoubleAdx(z1,b0) \ - addSub(x1,z1) \ - integerSqrAdx(b0,x1) \ - integerSqrAdx(b1,z1) \ - reduceFromDoubleAdx(x1,b0) \ - reduceFromDoubleAdx(z1,b1) \ - integerMulAdx(b0,x1,z2) \ - integerMulAdx(b1,z1,x2) \ - reduceFromDoubleAdx(x1,b0) \ - reduceFromDoubleAdx(z1,b1) - -#define doubleLeg \ - addSub(x1,z1) \ - integerSqrLeg(b0,x1) \ - integerSqrLeg(b1,z1) \ - reduceFromDoubleLeg(x1,b0) \ - reduceFromDoubleLeg(z1,b1) \ - subtraction(t0,x1,z1) \ - multiplyA24Leg(t1,t0) \ - additionLeg(t1,t1,z1) \ - integerMulLeg(b0,x1,z1) \ - integerMulLeg(b1,t0,t1) \ - reduceFromDoubleLeg(x1,b0) \ - reduceFromDoubleLeg(z1,b1) - -#define doubleBmi2Adx \ - addSub(x1,z1) \ - integerSqrAdx(b0,x1) \ - integerSqrAdx(b1,z1) \ - reduceFromDoubleAdx(x1,b0) \ - reduceFromDoubleAdx(z1,b1) \ - subtraction(t0,x1,z1) \ - multiplyA24Adx(t1,t0) \ - additionAdx(t1,t1,z1) \ - integerMulAdx(b0,x1,z1) \ - integerMulAdx(b1,t0,t1) \ - reduceFromDoubleAdx(x1,b0) \ - reduceFromDoubleAdx(z1,b1) diff --git a/vendor/github.com/cloudflare/circl/dh/x448/curve_amd64.s b/vendor/github.com/cloudflare/circl/dh/x448/curve_amd64.s deleted file mode 100644 index ed33ba3d032..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x448/curve_amd64.s +++ /dev/null @@ -1,194 +0,0 @@ -//go:build amd64 && !purego -// +build amd64,!purego - -#include "textflag.h" - -// Depends on circl/math/fp448 package -#include "../../math/fp448/fp_amd64.h" -#include "curve_amd64.h" - -// CTE_A24 is (A+2)/4 from Curve448 -#define CTE_A24 39082 - -#define Size 56 - -// multiplyA24Leg multiplies x times CTE_A24 and stores in z -// Uses: AX, DX, R8-R15, FLAGS -// Instr: x86_64, cmov, adx -#define multiplyA24Leg(z,x) \ - MOVQ $CTE_A24, R15; \ - MOVQ 0+x, AX; MULQ R15; MOVQ AX, R8; ;;;;;;;;;;;; MOVQ DX, R9; \ - MOVQ 8+x, AX; MULQ R15; ADDQ AX, R9; ADCQ $0, DX; MOVQ DX, R10; \ - MOVQ 16+x, AX; MULQ R15; ADDQ AX, R10; ADCQ $0, DX; MOVQ DX, R11; \ - MOVQ 24+x, AX; MULQ R15; ADDQ AX, R11; ADCQ $0, DX; MOVQ DX, R12; \ - MOVQ 32+x, AX; MULQ R15; ADDQ AX, R12; ADCQ $0, DX; MOVQ DX, R13; \ - MOVQ 40+x, AX; MULQ R15; ADDQ AX, R13; ADCQ $0, DX; MOVQ DX, R14; \ - MOVQ 48+x, AX; MULQ R15; ADDQ AX, R14; ADCQ $0, DX; \ - MOVQ DX, AX; \ - SHLQ $32, AX; \ - ADDQ DX, R8; MOVQ $0, DX; \ - ADCQ $0, R9; \ - ADCQ $0, R10; \ - ADCQ AX, R11; \ - ADCQ $0, R12; \ - ADCQ $0, R13; \ - ADCQ $0, R14; \ - ADCQ $0, DX; \ - MOVQ DX, AX; \ - SHLQ $32, AX; \ - ADDQ DX, R8; \ - ADCQ $0, R9; \ - ADCQ $0, R10; \ - ADCQ AX, R11; \ - ADCQ $0, R12; \ - ADCQ $0, R13; \ - ADCQ $0, R14; \ - MOVQ R8, 0+z; \ - MOVQ R9, 8+z; \ - MOVQ R10, 16+z; \ - MOVQ R11, 24+z; \ - MOVQ R12, 32+z; \ - MOVQ R13, 40+z; \ - MOVQ R14, 48+z; - -// multiplyA24Adx multiplies x times CTE_A24 and stores in z -// Uses: AX, DX, R8-R14, FLAGS -// Instr: x86_64, bmi2 -#define multiplyA24Adx(z,x) \ - MOVQ $CTE_A24, DX; \ - MULXQ 0+x, R8, R9; \ - MULXQ 8+x, AX, R10; ADDQ AX, R9; \ - MULXQ 16+x, AX, R11; ADCQ AX, R10; \ - MULXQ 24+x, AX, R12; ADCQ AX, R11; \ - MULXQ 32+x, AX, R13; ADCQ AX, R12; \ - MULXQ 40+x, AX, R14; ADCQ AX, R13; \ - MULXQ 48+x, AX, DX; ADCQ AX, R14; \ - ;;;;;;;;;;;;;;;;;;;; ADCQ $0, DX; \ - MOVQ DX, AX; \ - SHLQ $32, AX; \ - ADDQ DX, R8; MOVQ $0, DX; \ - ADCQ $0, R9; \ - ADCQ $0, R10; \ - ADCQ AX, R11; \ - ADCQ $0, R12; \ - ADCQ $0, R13; \ - ADCQ $0, R14; \ - ADCQ $0, DX; \ - MOVQ DX, AX; \ - SHLQ $32, AX; \ - ADDQ DX, R8; \ - ADCQ $0, R9; \ - ADCQ $0, R10; \ - ADCQ AX, R11; \ - ADCQ $0, R12; \ - ADCQ $0, R13; \ - ADCQ $0, R14; \ - MOVQ R8, 0+z; \ - MOVQ R9, 8+z; \ - MOVQ R10, 16+z; \ - MOVQ R11, 24+z; \ - MOVQ R12, 32+z; \ - MOVQ R13, 40+z; \ - MOVQ R14, 48+z; - -#define mulA24Legacy \ - multiplyA24Leg(0(DI),0(SI)) -#define mulA24Bmi2Adx \ - multiplyA24Adx(0(DI),0(SI)) - -// func mulA24Amd64(z, x *fp448.Elt) -TEXT ·mulA24Amd64(SB),NOSPLIT,$0-16 - MOVQ z+0(FP), DI - MOVQ x+8(FP), SI - CHECK_BMI2ADX(LMA24, mulA24Legacy, mulA24Bmi2Adx) - -// func ladderStepAmd64(w *[5]fp448.Elt, b uint) -// ladderStepAmd64 calculates a point addition and doubling as follows: -// (x2,z2) = 2*(x2,z2) and (x3,z3) = (x2,z2)+(x3,z3) using as a difference (x1,-). -// w = {x1,x2,z2,x3,z4} are five fp255.Elt of 56 bytes. -// stack = (t0,t1) are two fp.Elt of fp.Size bytes, and -// (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes. -TEXT ·ladderStepAmd64(SB),NOSPLIT,$336-16 - // Parameters - #define regWork DI - #define regMove SI - #define x1 0*Size(regWork) - #define x2 1*Size(regWork) - #define z2 2*Size(regWork) - #define x3 3*Size(regWork) - #define z3 4*Size(regWork) - // Local variables - #define t0 0*Size(SP) - #define t1 1*Size(SP) - #define b0 2*Size(SP) - #define b1 4*Size(SP) - MOVQ w+0(FP), regWork - MOVQ b+8(FP), regMove - CHECK_BMI2ADX(LLADSTEP, ladderStepLeg, ladderStepBmi2Adx) - #undef regWork - #undef regMove - #undef x1 - #undef x2 - #undef z2 - #undef x3 - #undef z3 - #undef t0 - #undef t1 - #undef b0 - #undef b1 - -// func diffAddAmd64(work *[5]fp.Elt, swap uint) -// diffAddAmd64 calculates a differential point addition using a precomputed point. -// (x1,z1) = (x1,z1)+(mu) using a difference point (x2,z2) -// work = {mu,x1,z1,x2,z2} are five fp448.Elt of 56 bytes, and -// stack = (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes. -// This is Equation 7 at https://eprint.iacr.org/2017/264. -TEXT ·diffAddAmd64(SB),NOSPLIT,$224-16 - // Parameters - #define regWork DI - #define regSwap SI - #define ui 0*Size(regWork) - #define x1 1*Size(regWork) - #define z1 2*Size(regWork) - #define x2 3*Size(regWork) - #define z2 4*Size(regWork) - // Local variables - #define b0 0*Size(SP) - #define b1 2*Size(SP) - MOVQ w+0(FP), regWork - MOVQ b+8(FP), regSwap - cswap(x1,x2,regSwap) - cswap(z1,z2,regSwap) - CHECK_BMI2ADX(LDIFADD, difAddLeg, difAddBmi2Adx) - #undef regWork - #undef regSwap - #undef ui - #undef x1 - #undef z1 - #undef x2 - #undef z2 - #undef b0 - #undef b1 - -// func doubleAmd64(x, z *fp448.Elt) -// doubleAmd64 calculates a point doubling (x1,z1) = 2*(x1,z1). -// stack = (t0,t1) are two fp.Elt of fp.Size bytes, and -// (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes. -TEXT ·doubleAmd64(SB),NOSPLIT,$336-16 - // Parameters - #define x1 0(DI) - #define z1 0(SI) - // Local variables - #define t0 0*Size(SP) - #define t1 1*Size(SP) - #define b0 2*Size(SP) - #define b1 4*Size(SP) - MOVQ x+0(FP), DI - MOVQ z+8(FP), SI - CHECK_BMI2ADX(LDOUB,doubleLeg,doubleBmi2Adx) - #undef x1 - #undef z1 - #undef t0 - #undef t1 - #undef b0 - #undef b1 diff --git a/vendor/github.com/cloudflare/circl/dh/x448/curve_generic.go b/vendor/github.com/cloudflare/circl/dh/x448/curve_generic.go deleted file mode 100644 index b0b65ccf7eb..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x448/curve_generic.go +++ /dev/null @@ -1,100 +0,0 @@ -package x448 - -import ( - "encoding/binary" - "math/bits" - - "github.com/cloudflare/circl/math/fp448" -) - -func doubleGeneric(x, z *fp448.Elt) { - t0, t1 := &fp448.Elt{}, &fp448.Elt{} - fp448.AddSub(x, z) - fp448.Sqr(x, x) - fp448.Sqr(z, z) - fp448.Sub(t0, x, z) - mulA24Generic(t1, t0) - fp448.Add(t1, t1, z) - fp448.Mul(x, x, z) - fp448.Mul(z, t0, t1) -} - -func diffAddGeneric(w *[5]fp448.Elt, b uint) { - mu, x1, z1, x2, z2 := &w[0], &w[1], &w[2], &w[3], &w[4] - fp448.Cswap(x1, x2, b) - fp448.Cswap(z1, z2, b) - fp448.AddSub(x1, z1) - fp448.Mul(z1, z1, mu) - fp448.AddSub(x1, z1) - fp448.Sqr(x1, x1) - fp448.Sqr(z1, z1) - fp448.Mul(x1, x1, z2) - fp448.Mul(z1, z1, x2) -} - -func ladderStepGeneric(w *[5]fp448.Elt, b uint) { - x1, x2, z2, x3, z3 := &w[0], &w[1], &w[2], &w[3], &w[4] - t0 := &fp448.Elt{} - t1 := &fp448.Elt{} - fp448.AddSub(x2, z2) - fp448.AddSub(x3, z3) - fp448.Mul(t0, x2, z3) - fp448.Mul(t1, x3, z2) - fp448.AddSub(t0, t1) - fp448.Cmov(x2, x3, b) - fp448.Cmov(z2, z3, b) - fp448.Sqr(x3, t0) - fp448.Sqr(z3, t1) - fp448.Mul(z3, x1, z3) - fp448.Sqr(x2, x2) - fp448.Sqr(z2, z2) - fp448.Sub(t0, x2, z2) - mulA24Generic(t1, t0) - fp448.Add(t1, t1, z2) - fp448.Mul(x2, x2, z2) - fp448.Mul(z2, t0, t1) -} - -func mulA24Generic(z, x *fp448.Elt) { - const A24 = 39082 - const n = 8 - var xx [7]uint64 - for i := range xx { - xx[i] = binary.LittleEndian.Uint64(x[i*n : (i+1)*n]) - } - h0, l0 := bits.Mul64(xx[0], A24) - h1, l1 := bits.Mul64(xx[1], A24) - h2, l2 := bits.Mul64(xx[2], A24) - h3, l3 := bits.Mul64(xx[3], A24) - h4, l4 := bits.Mul64(xx[4], A24) - h5, l5 := bits.Mul64(xx[5], A24) - h6, l6 := bits.Mul64(xx[6], A24) - - l1, c0 := bits.Add64(h0, l1, 0) - l2, c1 := bits.Add64(h1, l2, c0) - l3, c2 := bits.Add64(h2, l3, c1) - l4, c3 := bits.Add64(h3, l4, c2) - l5, c4 := bits.Add64(h4, l5, c3) - l6, c5 := bits.Add64(h5, l6, c4) - l7, _ := bits.Add64(h6, 0, c5) - - l0, c0 = bits.Add64(l0, l7, 0) - l1, c1 = bits.Add64(l1, 0, c0) - l2, c2 = bits.Add64(l2, 0, c1) - l3, c3 = bits.Add64(l3, l7<<32, c2) - l4, c4 = bits.Add64(l4, 0, c3) - l5, c5 = bits.Add64(l5, 0, c4) - l6, l7 = bits.Add64(l6, 0, c5) - - xx[0], c0 = bits.Add64(l0, l7, 0) - xx[1], c1 = bits.Add64(l1, 0, c0) - xx[2], c2 = bits.Add64(l2, 0, c1) - xx[3], c3 = bits.Add64(l3, l7<<32, c2) - xx[4], c4 = bits.Add64(l4, 0, c3) - xx[5], c5 = bits.Add64(l5, 0, c4) - xx[6], _ = bits.Add64(l6, 0, c5) - - for i := range xx { - binary.LittleEndian.PutUint64(z[i*n:(i+1)*n], xx[i]) - } -} diff --git a/vendor/github.com/cloudflare/circl/dh/x448/curve_noasm.go b/vendor/github.com/cloudflare/circl/dh/x448/curve_noasm.go deleted file mode 100644 index 3755b7c83b3..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x448/curve_noasm.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !amd64 || purego -// +build !amd64 purego - -package x448 - -import fp "github.com/cloudflare/circl/math/fp448" - -func double(x, z *fp.Elt) { doubleGeneric(x, z) } -func diffAdd(w *[5]fp.Elt, b uint) { diffAddGeneric(w, b) } -func ladderStep(w *[5]fp.Elt, b uint) { ladderStepGeneric(w, b) } -func mulA24(z, x *fp.Elt) { mulA24Generic(z, x) } diff --git a/vendor/github.com/cloudflare/circl/dh/x448/doc.go b/vendor/github.com/cloudflare/circl/dh/x448/doc.go deleted file mode 100644 index c02904fedae..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x448/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -Package x448 provides Diffie-Hellman functions as specified in RFC-7748. - -Validation of public keys. - -The Diffie-Hellman function, as described in RFC-7748 [1], works for any -public key. However, if a different protocol requires contributory -behaviour [2,3], then the public keys must be validated against low-order -points [3,4]. To do that, the Shared function performs this validation -internally and returns false when the public key is invalid (i.e., it -is a low-order point). - -References: - - [1] RFC7748 by Langley, Hamburg, Turner (https://rfc-editor.org/rfc/rfc7748.txt) - - [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html) - - [3] Bernstein (https://cr.yp.to/ecdh.html#validate) - - [4] Cremers&Jackson (https://eprint.iacr.org/2019/526) -*/ -package x448 diff --git a/vendor/github.com/cloudflare/circl/dh/x448/key.go b/vendor/github.com/cloudflare/circl/dh/x448/key.go deleted file mode 100644 index 2fdde51168a..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x448/key.go +++ /dev/null @@ -1,46 +0,0 @@ -package x448 - -import ( - "crypto/subtle" - - fp "github.com/cloudflare/circl/math/fp448" -) - -// Size is the length in bytes of a X448 key. -const Size = 56 - -// Key represents a X448 key. -type Key [Size]byte - -func (k *Key) clamp(in *Key) *Key { - *k = *in - k[0] &= 252 - k[55] |= 128 - return k -} - -// isValidPubKey verifies if the public key is not a low-order point. -func (k *Key) isValidPubKey() bool { - fp.Modp((*fp.Elt)(k)) - var isLowOrder int - for _, P := range lowOrderPoints { - isLowOrder |= subtle.ConstantTimeCompare(P[:], k[:]) - } - return isLowOrder == 0 -} - -// KeyGen obtains a public key given a secret key. -func KeyGen(public, secret *Key) { - ladderJoye(public.clamp(secret)) -} - -// Shared calculates Alice's shared key from Alice's secret key and Bob's -// public key returning true on success. A failure case happens when the public -// key is a low-order point, thus the shared key is all-zeros and the function -// returns false. -func Shared(shared, secret, public *Key) bool { - validPk := *public - ok := validPk.isValidPubKey() - ladderMontgomery(shared.clamp(secret), &validPk) - return ok -} diff --git a/vendor/github.com/cloudflare/circl/dh/x448/table.go b/vendor/github.com/cloudflare/circl/dh/x448/table.go deleted file mode 100644 index eef53c30f80..00000000000 --- a/vendor/github.com/cloudflare/circl/dh/x448/table.go +++ /dev/null @@ -1,460 +0,0 @@ -package x448 - -import fp "github.com/cloudflare/circl/math/fp448" - -// tableGenerator contains the set of points: -// -// t[i] = (xi+1)/(xi-1), -// -// where (xi,yi) = 2^iG and G is the generator point -// Size = (448)*(448/8) = 25088 bytes. -var tableGenerator = [448 * fp.Size]byte{ - /* (2^ 0)P */ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, - /* (2^ 1)P */ 0x37, 0xfa, 0xaa, 0x0d, 0x86, 0xa6, 0x24, 0xe9, 0x6c, 0x95, 0x08, 0x34, 0xba, 0x1a, 0x81, 0x3a, 0xae, 0x01, 0xa5, 0xa7, 0x05, 0x85, 0x96, 0x00, 0x06, 0x5a, 0xd7, 0xff, 0xee, 0x8e, 0x8f, 0x94, 0xd2, 0xdc, 0xd7, 0xfc, 0xe7, 0xe5, 0x99, 0x1d, 0x05, 0x46, 0x43, 0xe8, 0xbc, 0x12, 0xb7, 0xeb, 0x30, 0x5e, 0x7a, 0x85, 0x68, 0xed, 0x9d, 0x28, - /* (2^ 2)P */ 0xf1, 0x7d, 0x08, 0x2b, 0x32, 0x4a, 0x62, 0x80, 0x36, 0xe7, 0xa4, 0x76, 0x5a, 0x2a, 0x1e, 0xf7, 0x9e, 0x3c, 0x40, 0x46, 0x9a, 0x1b, 0x61, 0xc1, 0xbf, 0x1a, 0x1b, 0xae, 0x91, 0x80, 0xa3, 0x76, 0x6c, 0xd4, 0x8f, 0xa4, 0xee, 0x26, 0x39, 0x23, 0xa4, 0x80, 0xf4, 0x66, 0x92, 0xe4, 0xe1, 0x18, 0x76, 0xc5, 0xe2, 0x19, 0x87, 0xd5, 0xc3, 0xe8, - /* (2^ 3)P */ 0xfb, 0xc9, 0xf0, 0x07, 0xf2, 0x93, 0xd8, 0x50, 0x36, 0xed, 0xfb, 0xbd, 0xb2, 0xd3, 0xfc, 0xdf, 0xd5, 0x2a, 0x6e, 0x26, 0x09, 0xce, 0xd4, 0x07, 0x64, 0x9f, 0x40, 0x74, 0xad, 0x98, 0x2f, 0x1c, 0xb6, 0xdc, 0x2d, 0x42, 0xff, 0xbf, 0x97, 0xd8, 0xdb, 0xef, 0x99, 0xca, 0x73, 0x99, 0x1a, 0x04, 0x3b, 0x56, 0x2c, 0x1f, 0x87, 0x9d, 0x9f, 0x03, - /* (2^ 4)P */ 0x4c, 0x35, 0x97, 0xf7, 0x81, 0x2c, 0x84, 0xa6, 0xe0, 0xcb, 0xce, 0x37, 0x4c, 0x21, 0x1c, 0x67, 0xfa, 0xab, 0x18, 0x4d, 0xef, 0xd0, 0xf0, 0x44, 0xa9, 0xfb, 0xc0, 0x8e, 0xda, 0x57, 0xa1, 0xd8, 0xeb, 0x87, 0xf4, 0x17, 0xea, 0x66, 0x0f, 0x16, 0xea, 0xcd, 0x5f, 0x3e, 0x88, 0xea, 0x09, 0x68, 0x40, 0xdf, 0x43, 0xcc, 0x54, 0x61, 0x58, 0xaa, - /* (2^ 5)P */ 0x8d, 0xe7, 0x59, 0xd7, 0x5e, 0x63, 0x37, 0xa7, 0x3f, 0xd1, 0x49, 0x85, 0x01, 0xdd, 0x5e, 0xb3, 0xe6, 0x29, 0xcb, 0x25, 0x93, 0xdd, 0x08, 0x96, 0x83, 0x52, 0x76, 0x85, 0xf5, 0x5d, 0x02, 0xbf, 0xe9, 0x6d, 0x15, 0x27, 0xc1, 0x09, 0xd1, 0x14, 0x4d, 0x6e, 0xe8, 0xaf, 0x59, 0x58, 0x34, 0x9d, 0x2a, 0x99, 0x85, 0x26, 0xbe, 0x4b, 0x1e, 0xb9, - /* (2^ 6)P */ 0x8d, 0xce, 0x94, 0xe2, 0x18, 0x56, 0x0d, 0x82, 0x8e, 0xdf, 0x85, 0x01, 0x8f, 0x93, 0x3c, 0xc6, 0xbd, 0x61, 0xfb, 0xf4, 0x22, 0xc5, 0x16, 0x87, 0xd1, 0xb1, 0x9e, 0x09, 0xc5, 0x83, 0x2e, 0x4a, 0x07, 0x88, 0xee, 0xe0, 0x29, 0x8d, 0x2e, 0x1f, 0x88, 0xad, 0xfd, 0x18, 0x93, 0xb7, 0xed, 0x42, 0x86, 0x78, 0xf0, 0xb8, 0x70, 0xbe, 0x01, 0x67, - /* (2^ 7)P */ 0xdf, 0x62, 0x2d, 0x94, 0xc7, 0x35, 0x23, 0xda, 0x27, 0xbb, 0x2b, 0xdb, 0x30, 0x80, 0x68, 0x16, 0xa3, 0xae, 0xd7, 0xd2, 0xa7, 0x7c, 0xbf, 0x6a, 0x1d, 0x83, 0xde, 0x96, 0x0a, 0x43, 0xb6, 0x30, 0x37, 0xd6, 0xee, 0x63, 0x59, 0x9a, 0xbf, 0xa3, 0x30, 0x6c, 0xaf, 0x0c, 0xee, 0x3d, 0xcb, 0x35, 0x4b, 0x55, 0x5f, 0x84, 0x85, 0xcb, 0x4f, 0x1e, - /* (2^ 8)P */ 0x9d, 0x04, 0x68, 0x89, 0xa4, 0xa9, 0x0d, 0x87, 0xc1, 0x70, 0xf1, 0xeb, 0xfb, 0x47, 0x0a, 0xf0, 0xde, 0x67, 0xb7, 0x94, 0xcd, 0x36, 0x43, 0xa5, 0x49, 0x43, 0x67, 0xc3, 0xee, 0x3c, 0x6b, 0xec, 0xd0, 0x1a, 0xf4, 0xad, 0xef, 0x06, 0x4a, 0xe8, 0x46, 0x24, 0xd7, 0x93, 0xbf, 0xf0, 0xe3, 0x81, 0x61, 0xec, 0xea, 0x64, 0xfe, 0x67, 0xeb, 0xc7, - /* (2^ 9)P */ 0x95, 0x45, 0x79, 0xcf, 0x2c, 0xfd, 0x9b, 0xfe, 0x84, 0x46, 0x4b, 0x8f, 0xa1, 0xcf, 0xc3, 0x04, 0x94, 0x78, 0xdb, 0xc9, 0xa6, 0x01, 0x75, 0xa4, 0xb4, 0x93, 0x72, 0x43, 0xa7, 0x7d, 0xda, 0x31, 0x38, 0x54, 0xab, 0x4e, 0x3f, 0x89, 0xa6, 0xab, 0x57, 0xc0, 0x16, 0x65, 0xdb, 0x92, 0x96, 0xe4, 0xc8, 0xae, 0xe7, 0x4c, 0x7a, 0xeb, 0xbb, 0x5a, - /* (2^ 10)P */ 0xbe, 0xfe, 0x86, 0xc3, 0x97, 0xe0, 0x6a, 0x18, 0x20, 0x21, 0xca, 0x22, 0x55, 0xa1, 0xeb, 0xf5, 0x74, 0xe5, 0xc9, 0x59, 0xa7, 0x92, 0x65, 0x15, 0x08, 0x71, 0xd1, 0x09, 0x7e, 0x83, 0xfc, 0xbc, 0x5a, 0x93, 0x38, 0x0d, 0x43, 0x42, 0xfd, 0x76, 0x30, 0xe8, 0x63, 0x60, 0x09, 0x8d, 0x6c, 0xd3, 0xf8, 0x56, 0x3d, 0x68, 0x47, 0xab, 0xa0, 0x1d, - /* (2^ 11)P */ 0x38, 0x50, 0x1c, 0xb1, 0xac, 0x88, 0x8f, 0x38, 0xe3, 0x69, 0xe6, 0xfc, 0x4f, 0x8f, 0xe1, 0x9b, 0xb1, 0x1a, 0x09, 0x39, 0x19, 0xdf, 0xcd, 0x98, 0x7b, 0x64, 0x42, 0xf6, 0x11, 0xea, 0xc7, 0xe8, 0x92, 0x65, 0x00, 0x2c, 0x75, 0xb5, 0x94, 0x1e, 0x5b, 0xa6, 0x66, 0x81, 0x77, 0xf3, 0x39, 0x94, 0xac, 0xbd, 0xe4, 0x2a, 0x66, 0x84, 0x9c, 0x60, - /* (2^ 12)P */ 0xb5, 0xb6, 0xd9, 0x03, 0x67, 0xa4, 0xa8, 0x0a, 0x4a, 0x2b, 0x9d, 0xfa, 0x13, 0xe1, 0x99, 0x25, 0x4a, 0x5c, 0x67, 0xb9, 0xb2, 0xb7, 0xdd, 0x1e, 0xaf, 0xeb, 0x63, 0x41, 0xb6, 0xb9, 0xa0, 0x87, 0x0a, 0xe0, 0x06, 0x07, 0xaa, 0x97, 0xf8, 0xf9, 0x38, 0x4f, 0xdf, 0x0c, 0x40, 0x7c, 0xc3, 0x98, 0xa9, 0x74, 0xf1, 0x5d, 0xda, 0xd1, 0xc0, 0x0a, - /* (2^ 13)P */ 0xf2, 0x0a, 0xab, 0xab, 0x94, 0x50, 0xf0, 0xa3, 0x6f, 0xc6, 0x66, 0xba, 0xa6, 0xdc, 0x44, 0xdd, 0xd6, 0x08, 0xf4, 0xd3, 0xed, 0xb1, 0x40, 0x93, 0xee, 0xf6, 0xb8, 0x8e, 0xb4, 0x7c, 0xb9, 0x82, 0xc9, 0x9d, 0x45, 0x3b, 0x8e, 0x10, 0xcb, 0x70, 0x1e, 0xba, 0x3c, 0x62, 0x50, 0xda, 0xa9, 0x93, 0xb5, 0xd7, 0xd0, 0x6f, 0x29, 0x52, 0x95, 0xae, - /* (2^ 14)P */ 0x14, 0x68, 0x69, 0x23, 0xa8, 0x44, 0x87, 0x9e, 0x22, 0x91, 0xe8, 0x92, 0xdf, 0xf7, 0xae, 0xba, 0x1c, 0x96, 0xe1, 0xc3, 0x94, 0xed, 0x6c, 0x95, 0xae, 0x96, 0xa7, 0x15, 0x9f, 0xf1, 0x17, 0x11, 0x92, 0x42, 0xd5, 0xcd, 0x18, 0xe7, 0xa9, 0xb5, 0x2f, 0xcd, 0xde, 0x6c, 0xc9, 0x7d, 0xfc, 0x7e, 0xbd, 0x7f, 0x10, 0x3d, 0x01, 0x00, 0x8d, 0x95, - /* (2^ 15)P */ 0x3b, 0x76, 0x72, 0xae, 0xaf, 0x84, 0xf2, 0xf7, 0xd1, 0x6d, 0x13, 0x9c, 0x47, 0xe1, 0xb7, 0xa3, 0x19, 0x16, 0xee, 0x75, 0x45, 0xf6, 0x1a, 0x7b, 0x78, 0x49, 0x79, 0x05, 0x86, 0xf0, 0x7f, 0x9f, 0xfc, 0xc4, 0xbd, 0x86, 0xf3, 0x41, 0xa7, 0xfe, 0x01, 0xd5, 0x67, 0x16, 0x10, 0x5b, 0xa5, 0x16, 0xf3, 0x7f, 0x60, 0xce, 0xd2, 0x0c, 0x8e, 0x4b, - /* (2^ 16)P */ 0x4a, 0x07, 0x99, 0x4a, 0x0f, 0x74, 0x91, 0x14, 0x68, 0xb9, 0x48, 0xb7, 0x44, 0x77, 0x9b, 0x4a, 0xe0, 0x68, 0x0e, 0x43, 0x4d, 0x98, 0x98, 0xbf, 0xa8, 0x3a, 0xb7, 0x6d, 0x2a, 0x9a, 0x77, 0x5f, 0x62, 0xf5, 0x6b, 0x4a, 0xb7, 0x7d, 0xe5, 0x09, 0x6b, 0xc0, 0x8b, 0x9c, 0x88, 0x37, 0x33, 0xf2, 0x41, 0xac, 0x22, 0x1f, 0xcf, 0x3b, 0x82, 0x34, - /* (2^ 17)P */ 0x00, 0xc3, 0x78, 0x42, 0x32, 0x2e, 0xdc, 0xda, 0xb1, 0x96, 0x21, 0xa4, 0xe4, 0xbb, 0xe9, 0x9d, 0xbb, 0x0f, 0x93, 0xed, 0x26, 0x3d, 0xb5, 0xdb, 0x94, 0x31, 0x37, 0x07, 0xa2, 0xb2, 0xd5, 0x99, 0x0d, 0x93, 0xe1, 0xce, 0x3f, 0x0b, 0x96, 0x82, 0x47, 0xfe, 0x60, 0x6f, 0x8f, 0x61, 0x88, 0xd7, 0x05, 0x95, 0x0b, 0x46, 0x06, 0xb7, 0x32, 0x06, - /* (2^ 18)P */ 0x44, 0xf5, 0x34, 0xdf, 0x2f, 0x9c, 0x5d, 0x9f, 0x53, 0x5c, 0x42, 0x8f, 0xc9, 0xdc, 0xd8, 0x40, 0xa2, 0xe7, 0x6a, 0x4a, 0x05, 0xf7, 0x86, 0x77, 0x2b, 0xae, 0x37, 0xed, 0x48, 0xfb, 0xf7, 0x62, 0x7c, 0x17, 0x59, 0x92, 0x41, 0x61, 0x93, 0x38, 0x30, 0xd1, 0xef, 0x54, 0x54, 0x03, 0x17, 0x57, 0x91, 0x15, 0x11, 0x33, 0xb5, 0xfa, 0xfb, 0x17, - /* (2^ 19)P */ 0x29, 0xbb, 0xd4, 0xb4, 0x9c, 0xf1, 0x72, 0x94, 0xce, 0x6a, 0x29, 0xa8, 0x89, 0x18, 0x19, 0xf7, 0xb7, 0xcc, 0xee, 0x9a, 0x02, 0xe3, 0xc0, 0xb1, 0xe0, 0xee, 0x83, 0x78, 0xb4, 0x9e, 0x07, 0x87, 0xdf, 0xb0, 0x82, 0x26, 0x4e, 0xa4, 0x0c, 0x33, 0xaf, 0x40, 0x59, 0xb6, 0xdd, 0x52, 0x45, 0xf0, 0xb4, 0xf6, 0xe8, 0x4e, 0x4e, 0x79, 0x1a, 0x5d, - /* (2^ 20)P */ 0x27, 0x33, 0x4d, 0x4c, 0x6b, 0x4f, 0x75, 0xb1, 0xbc, 0x1f, 0xab, 0x5b, 0x2b, 0xf0, 0x1c, 0x57, 0x86, 0xdd, 0xfd, 0x60, 0xb0, 0x8c, 0xe7, 0x9a, 0xe5, 0x5c, 0xeb, 0x11, 0x3a, 0xda, 0x22, 0x25, 0x99, 0x06, 0x8d, 0xf4, 0xaf, 0x29, 0x7a, 0xc9, 0xe5, 0xd2, 0x16, 0x9e, 0xd4, 0x63, 0x1d, 0x64, 0xa6, 0x47, 0x96, 0x37, 0x6f, 0x93, 0x2c, 0xcc, - /* (2^ 21)P */ 0xc1, 0x94, 0x74, 0x86, 0x75, 0xf2, 0x91, 0x58, 0x23, 0x85, 0x63, 0x76, 0x54, 0xc7, 0xb4, 0x8c, 0xbc, 0x4e, 0xc4, 0xa7, 0xba, 0xa0, 0x55, 0x26, 0x71, 0xd5, 0x33, 0x72, 0xc9, 0xad, 0x1e, 0xf9, 0x5d, 0x78, 0x70, 0x93, 0x4e, 0x85, 0xfc, 0x39, 0x06, 0x73, 0x76, 0xff, 0xe8, 0x64, 0x69, 0x42, 0x45, 0xb2, 0x69, 0xb5, 0x32, 0xe7, 0x2c, 0xde, - /* (2^ 22)P */ 0xde, 0x16, 0xd8, 0x33, 0x49, 0x32, 0xe9, 0x0e, 0x3a, 0x60, 0xee, 0x2e, 0x24, 0x75, 0xe3, 0x9c, 0x92, 0x07, 0xdb, 0xad, 0x92, 0xf5, 0x11, 0xdf, 0xdb, 0xb0, 0x17, 0x5c, 0xd6, 0x1a, 0x70, 0x00, 0xb7, 0xe2, 0x18, 0xec, 0xdc, 0xc2, 0x02, 0x93, 0xb3, 0xc8, 0x3f, 0x4f, 0x1b, 0x96, 0xe6, 0x33, 0x8c, 0xfb, 0xcc, 0xa5, 0x4e, 0xe8, 0xe7, 0x11, - /* (2^ 23)P */ 0x05, 0x7a, 0x74, 0x52, 0xf8, 0xdf, 0x0d, 0x7c, 0x6a, 0x1a, 0x4e, 0x9a, 0x02, 0x1d, 0xae, 0x77, 0xf8, 0x8e, 0xf9, 0xa2, 0x38, 0x54, 0x50, 0xb2, 0x2c, 0x08, 0x9d, 0x9b, 0x9f, 0xfb, 0x2b, 0x06, 0xde, 0x9d, 0xc2, 0x03, 0x0b, 0x22, 0x2b, 0x10, 0x5b, 0x3a, 0x73, 0x29, 0x8e, 0x3e, 0x37, 0x08, 0x2c, 0x3b, 0xf8, 0x80, 0xc1, 0x66, 0x1e, 0x98, - /* (2^ 24)P */ 0xd8, 0xd6, 0x3e, 0xcd, 0x63, 0x8c, 0x2b, 0x41, 0x81, 0xc0, 0x0c, 0x06, 0x87, 0xd6, 0xe7, 0x92, 0xfe, 0xf1, 0x0c, 0x4a, 0x84, 0x5b, 0xaf, 0x40, 0x53, 0x6f, 0x60, 0xd6, 0x6b, 0x76, 0x4b, 0xc2, 0xad, 0xc9, 0xb6, 0xb6, 0x6a, 0xa2, 0xb3, 0xf5, 0xf5, 0xc2, 0x55, 0x83, 0xb2, 0xd3, 0xe9, 0x41, 0x6c, 0x63, 0x51, 0xb8, 0x81, 0x74, 0xc8, 0x2c, - /* (2^ 25)P */ 0xb2, 0xaf, 0x1c, 0xee, 0x07, 0xb0, 0x58, 0xa8, 0x2c, 0x6a, 0xc9, 0x2d, 0x62, 0x28, 0x75, 0x0c, 0x40, 0xb6, 0x11, 0x33, 0x96, 0x80, 0x28, 0x6d, 0xd5, 0x9e, 0x87, 0x90, 0x01, 0x66, 0x1d, 0x1c, 0xf8, 0xb4, 0x92, 0xac, 0x38, 0x18, 0x05, 0xc2, 0x4c, 0x4b, 0x54, 0x7d, 0x80, 0x46, 0x87, 0x2d, 0x99, 0x8e, 0x70, 0x80, 0x69, 0x71, 0x8b, 0xed, - /* (2^ 26)P */ 0x37, 0xa7, 0x6b, 0x71, 0x36, 0x75, 0x8e, 0xff, 0x0f, 0x42, 0xda, 0x5a, 0x46, 0xa6, 0x97, 0x79, 0x7e, 0x30, 0xb3, 0x8f, 0xc7, 0x3a, 0xa0, 0xcb, 0x1d, 0x9c, 0x78, 0x77, 0x36, 0xc2, 0xe7, 0xf4, 0x2f, 0x29, 0x07, 0xb1, 0x07, 0xfd, 0xed, 0x1b, 0x39, 0x77, 0x06, 0x38, 0x77, 0x0f, 0x50, 0x31, 0x12, 0xbf, 0x92, 0xbf, 0x72, 0x79, 0x54, 0xa9, - /* (2^ 27)P */ 0xbd, 0x4d, 0x46, 0x6b, 0x1a, 0x80, 0x46, 0x2d, 0xed, 0xfd, 0x64, 0x6d, 0x94, 0xbc, 0x4a, 0x6e, 0x0c, 0x12, 0xf6, 0x12, 0xab, 0x54, 0x88, 0xd3, 0x85, 0xac, 0x51, 0xae, 0x6f, 0xca, 0xc4, 0xb7, 0xec, 0x22, 0x54, 0x6d, 0x80, 0xb2, 0x1c, 0x63, 0x33, 0x76, 0x6b, 0x8e, 0x6d, 0x59, 0xcd, 0x73, 0x92, 0x5f, 0xff, 0xad, 0x10, 0x35, 0x70, 0x5f, - /* (2^ 28)P */ 0xb3, 0x84, 0xde, 0xc8, 0x04, 0x43, 0x63, 0xfa, 0x29, 0xd9, 0xf0, 0x69, 0x65, 0x5a, 0x0c, 0xe8, 0x2e, 0x0b, 0xfe, 0xb0, 0x7a, 0x42, 0xb3, 0xc3, 0xfc, 0xe6, 0xb8, 0x92, 0x29, 0xae, 0xed, 0xec, 0xd5, 0xe8, 0x4a, 0xa1, 0xbd, 0x3b, 0xd3, 0xc0, 0x07, 0xab, 0x65, 0x65, 0x35, 0x9a, 0xa6, 0x5e, 0x78, 0x18, 0x76, 0x1c, 0x15, 0x49, 0xe6, 0x75, - /* (2^ 29)P */ 0x45, 0xb3, 0x92, 0xa9, 0xc3, 0xb8, 0x11, 0x68, 0x64, 0x3a, 0x83, 0x5d, 0xa8, 0x94, 0x6a, 0x9d, 0xaa, 0x27, 0x9f, 0x98, 0x5d, 0xc0, 0x29, 0xf0, 0xc0, 0x4b, 0x14, 0x3c, 0x05, 0xe7, 0xf8, 0xbd, 0x38, 0x22, 0x96, 0x75, 0x65, 0x5e, 0x0d, 0x3f, 0xbb, 0x6f, 0xe8, 0x3f, 0x96, 0x76, 0x9f, 0xba, 0xd9, 0x44, 0x92, 0x96, 0x22, 0xe7, 0x52, 0xe7, - /* (2^ 30)P */ 0xf4, 0xa3, 0x95, 0x90, 0x47, 0xdf, 0x7d, 0xdc, 0xf4, 0x13, 0x87, 0x67, 0x7d, 0x4f, 0x9d, 0xa0, 0x00, 0x46, 0x72, 0x08, 0xc3, 0xa2, 0x7a, 0x3e, 0xe7, 0x6d, 0x52, 0x7c, 0x11, 0x36, 0x50, 0x83, 0x89, 0x64, 0xcb, 0x1f, 0x08, 0x83, 0x46, 0xcb, 0xac, 0xa6, 0xd8, 0x9c, 0x1b, 0xe8, 0x05, 0x47, 0xc7, 0x26, 0x06, 0x83, 0x39, 0xe9, 0xb1, 0x1c, - /* (2^ 31)P */ 0x11, 0xe8, 0xc8, 0x42, 0xbf, 0x30, 0x9c, 0xa3, 0xf1, 0x85, 0x96, 0x95, 0x4f, 0x4f, 0x52, 0xa2, 0xf5, 0x8b, 0x68, 0x24, 0x16, 0xac, 0x9b, 0xa9, 0x27, 0x28, 0x0e, 0x84, 0x03, 0x46, 0x22, 0x5f, 0xf7, 0x0d, 0xa6, 0x85, 0x88, 0xc1, 0x45, 0x4b, 0x85, 0x1a, 0x10, 0x7f, 0xc9, 0x94, 0x20, 0xb0, 0x04, 0x28, 0x12, 0x30, 0xb9, 0xe6, 0x40, 0x6b, - /* (2^ 32)P */ 0xac, 0x1b, 0x57, 0xb6, 0x42, 0xdb, 0x81, 0x8d, 0x76, 0xfd, 0x9b, 0x1c, 0x29, 0x30, 0xd5, 0x3a, 0xcc, 0x53, 0xd9, 0x26, 0x7a, 0x0f, 0x9c, 0x2e, 0x79, 0xf5, 0x62, 0xeb, 0x61, 0x9d, 0x9b, 0x80, 0x39, 0xcd, 0x60, 0x2e, 0x1f, 0x08, 0x22, 0xbc, 0x19, 0xb3, 0x2a, 0x43, 0x44, 0xf2, 0x4e, 0x66, 0xf4, 0x36, 0xa6, 0xa7, 0xbc, 0xa4, 0x15, 0x7e, - /* (2^ 33)P */ 0xc1, 0x90, 0x8a, 0xde, 0xff, 0x78, 0xc3, 0x73, 0x16, 0xee, 0x76, 0xa0, 0x84, 0x60, 0x8d, 0xe6, 0x82, 0x0f, 0xde, 0x4e, 0xc5, 0x99, 0x34, 0x06, 0x90, 0x44, 0x55, 0xf8, 0x91, 0xd8, 0xe1, 0xe4, 0x2c, 0x8a, 0xde, 0x94, 0x1e, 0x78, 0x25, 0x3d, 0xfd, 0xd8, 0x59, 0x7d, 0xaf, 0x6e, 0xbe, 0x96, 0xbe, 0x3c, 0x16, 0x23, 0x0f, 0x4c, 0xa4, 0x28, - /* (2^ 34)P */ 0xba, 0x11, 0x35, 0x57, 0x03, 0xb6, 0xf4, 0x24, 0x89, 0xb8, 0x5a, 0x0d, 0x50, 0x9c, 0xaa, 0x51, 0x7f, 0xa4, 0x0e, 0xfc, 0x71, 0xb3, 0x3b, 0xf1, 0x96, 0x50, 0x23, 0x15, 0xf5, 0xf5, 0xd4, 0x23, 0xdc, 0x8b, 0x26, 0x9e, 0xae, 0xb7, 0x50, 0xcd, 0xc4, 0x25, 0xf6, 0x75, 0x40, 0x9c, 0x37, 0x79, 0x33, 0x60, 0xd4, 0x4b, 0x13, 0x32, 0xee, 0xe2, - /* (2^ 35)P */ 0x43, 0xb8, 0x56, 0x59, 0xf0, 0x68, 0x23, 0xb3, 0xea, 0x70, 0x58, 0x4c, 0x1e, 0x5a, 0x16, 0x54, 0x03, 0xb2, 0xf4, 0x73, 0xb6, 0xd9, 0x5c, 0x9c, 0x6f, 0xcf, 0x82, 0x2e, 0x54, 0x15, 0x46, 0x2c, 0xa3, 0xda, 0x4e, 0x87, 0xf5, 0x2b, 0xba, 0x91, 0xa3, 0xa0, 0x89, 0xba, 0x48, 0x2b, 0xfa, 0x64, 0x02, 0x7f, 0x78, 0x03, 0xd1, 0xe8, 0x3b, 0xe9, - /* (2^ 36)P */ 0x15, 0xa4, 0x71, 0xd4, 0x0c, 0x24, 0xe9, 0x07, 0xa1, 0x43, 0xf4, 0x7f, 0xbb, 0xa2, 0xa6, 0x6b, 0xfa, 0xb7, 0xea, 0x58, 0xd1, 0x96, 0xb0, 0x24, 0x5c, 0xc7, 0x37, 0x4e, 0x60, 0x0f, 0x40, 0xf2, 0x2f, 0x44, 0x70, 0xea, 0x80, 0x63, 0xfe, 0xfc, 0x46, 0x59, 0x12, 0x27, 0xb5, 0x27, 0xfd, 0xb7, 0x73, 0x0b, 0xca, 0x8b, 0xc2, 0xd3, 0x71, 0x08, - /* (2^ 37)P */ 0x26, 0x0e, 0xd7, 0x52, 0x6f, 0xf1, 0xf2, 0x9d, 0xb8, 0x3d, 0xbd, 0xd4, 0x75, 0x97, 0xd8, 0xbf, 0xa8, 0x86, 0x96, 0xa5, 0x80, 0xa0, 0x45, 0x75, 0xf6, 0x77, 0x71, 0xdb, 0x77, 0x96, 0x55, 0x99, 0x31, 0xd0, 0x4f, 0x34, 0xf4, 0x35, 0x39, 0x41, 0xd3, 0x7d, 0xf7, 0xe2, 0x74, 0xde, 0xbe, 0x5b, 0x1f, 0x39, 0x10, 0x21, 0xa3, 0x4d, 0x3b, 0xc8, - /* (2^ 38)P */ 0x04, 0x00, 0x2a, 0x45, 0xb2, 0xaf, 0x9b, 0x18, 0x6a, 0xeb, 0x96, 0x28, 0xa4, 0x77, 0xd0, 0x13, 0xcf, 0x17, 0x65, 0xe8, 0xc5, 0x81, 0x28, 0xad, 0x39, 0x7a, 0x0b, 0xaa, 0x55, 0x2b, 0xf3, 0xfc, 0x86, 0x40, 0xad, 0x0d, 0x1e, 0x28, 0xa2, 0x2d, 0xc5, 0xd6, 0x04, 0x15, 0xa2, 0x30, 0x3d, 0x12, 0x8e, 0xd6, 0xb5, 0xf7, 0x69, 0xbb, 0x84, 0x20, - /* (2^ 39)P */ 0xd7, 0x7a, 0x77, 0x2c, 0xfb, 0x81, 0x80, 0xe9, 0x1e, 0xc6, 0x36, 0x31, 0x79, 0xc3, 0x7c, 0xa9, 0x57, 0x6b, 0xb5, 0x70, 0xfb, 0xe4, 0xa1, 0xff, 0xfd, 0x21, 0xa5, 0x7c, 0xfa, 0x44, 0xba, 0x0d, 0x96, 0x3d, 0xc4, 0x5c, 0x39, 0x52, 0x87, 0xd7, 0x22, 0x0f, 0x52, 0x88, 0x91, 0x87, 0x96, 0xac, 0xfa, 0x3b, 0xdf, 0xdc, 0x83, 0x8c, 0x99, 0x29, - /* (2^ 40)P */ 0x98, 0x6b, 0x3a, 0x8d, 0x83, 0x17, 0xe1, 0x62, 0xd8, 0x80, 0x4c, 0x97, 0xce, 0x6b, 0xaa, 0x10, 0xa7, 0xc4, 0xe9, 0xeb, 0xa5, 0xfb, 0xc9, 0xdd, 0x2d, 0xeb, 0xfc, 0x9a, 0x71, 0xcd, 0x68, 0x6e, 0xc0, 0x35, 0x64, 0x62, 0x1b, 0x95, 0x12, 0xe8, 0x53, 0xec, 0xf0, 0xf4, 0x86, 0x86, 0x78, 0x18, 0xc4, 0xc6, 0xbc, 0x5a, 0x59, 0x8f, 0x7c, 0x7e, - /* (2^ 41)P */ 0x7f, 0xd7, 0x1e, 0xc5, 0x83, 0xdc, 0x1f, 0xbe, 0x0b, 0xcf, 0x2e, 0x01, 0x01, 0xed, 0xac, 0x17, 0x3b, 0xed, 0xa4, 0x30, 0x96, 0x0e, 0x14, 0x7e, 0x19, 0x2b, 0xa5, 0x67, 0x1e, 0xb3, 0x34, 0x03, 0xa8, 0xbb, 0x0a, 0x7d, 0x08, 0x2d, 0xd5, 0x53, 0x19, 0x6f, 0x13, 0xd5, 0xc0, 0x90, 0x8a, 0xcc, 0xc9, 0x5c, 0xab, 0x24, 0xd7, 0x03, 0xf6, 0x57, - /* (2^ 42)P */ 0x49, 0xcb, 0xb4, 0x96, 0x5f, 0xa6, 0xf8, 0x71, 0x6f, 0x59, 0xad, 0x05, 0x24, 0x2d, 0xaf, 0x67, 0xa8, 0xbe, 0x95, 0xdf, 0x0d, 0x28, 0x5a, 0x7f, 0x6e, 0x87, 0x8c, 0x6e, 0x67, 0x0c, 0xf4, 0xe0, 0x1c, 0x30, 0xc2, 0x66, 0xae, 0x20, 0xa1, 0x34, 0xec, 0x9c, 0xbc, 0xae, 0x3d, 0xa1, 0x28, 0x28, 0x95, 0x1d, 0xc9, 0x3a, 0xa8, 0xfd, 0xfc, 0xa1, - /* (2^ 43)P */ 0xe2, 0x2b, 0x9d, 0xed, 0x02, 0x99, 0x67, 0xbb, 0x2e, 0x16, 0x62, 0x05, 0x70, 0xc7, 0x27, 0xb9, 0x1c, 0x3f, 0xf2, 0x11, 0x01, 0xd8, 0x51, 0xa4, 0x18, 0x92, 0xa9, 0x5d, 0xfb, 0xa9, 0xe4, 0x42, 0xba, 0x38, 0x34, 0x1a, 0x4a, 0xc5, 0x6a, 0x37, 0xde, 0xa7, 0x0c, 0xb4, 0x7e, 0x7f, 0xde, 0xa6, 0xee, 0xcd, 0x55, 0x57, 0x05, 0x06, 0xfd, 0x5d, - /* (2^ 44)P */ 0x2f, 0x32, 0xcf, 0x2e, 0x2c, 0x7b, 0xbe, 0x9a, 0x0c, 0x57, 0x35, 0xf8, 0x87, 0xda, 0x9c, 0xec, 0x48, 0xf2, 0xbb, 0xe2, 0xda, 0x10, 0x58, 0x20, 0xc6, 0xd3, 0x87, 0xe9, 0xc7, 0x26, 0xd1, 0x9a, 0x46, 0x87, 0x90, 0xda, 0xdc, 0xde, 0xc3, 0xb3, 0xf2, 0xe8, 0x6f, 0x4a, 0xe6, 0xe8, 0x9d, 0x98, 0x36, 0x20, 0x03, 0x47, 0x15, 0x3f, 0x64, 0x59, - /* (2^ 45)P */ 0xd4, 0x71, 0x49, 0x0a, 0x67, 0x97, 0xaa, 0x3f, 0xf4, 0x1b, 0x3a, 0x6e, 0x5e, 0x17, 0xcc, 0x0a, 0x8f, 0x81, 0x6a, 0x41, 0x38, 0x77, 0x40, 0x8a, 0x11, 0x42, 0x62, 0xd2, 0x50, 0x32, 0x79, 0x78, 0x28, 0xc2, 0x2e, 0x10, 0x01, 0x94, 0x30, 0x4f, 0x7f, 0x18, 0x17, 0x56, 0x85, 0x4e, 0xad, 0xf7, 0xcb, 0x87, 0x3c, 0x3f, 0x50, 0x2c, 0xc0, 0xba, - /* (2^ 46)P */ 0xbc, 0x30, 0x8e, 0x65, 0x8e, 0x57, 0x5b, 0x38, 0x7a, 0xd4, 0x95, 0x52, 0x7a, 0x32, 0x59, 0x69, 0xcd, 0x9d, 0x47, 0x34, 0x5b, 0x55, 0xa5, 0x24, 0x60, 0xdd, 0xc0, 0xc1, 0x62, 0x73, 0x44, 0xae, 0x4c, 0x9c, 0x65, 0x55, 0x1b, 0x9d, 0x8a, 0x29, 0xb0, 0x1a, 0x52, 0xa8, 0xf1, 0xe6, 0x9a, 0xb3, 0xf6, 0xa3, 0xc9, 0x0a, 0x70, 0x7d, 0x0f, 0xee, - /* (2^ 47)P */ 0x77, 0xd3, 0xe5, 0x8e, 0xfa, 0x00, 0xeb, 0x1b, 0x7f, 0xdc, 0x68, 0x3f, 0x92, 0xbd, 0xb7, 0x0b, 0xb7, 0xb5, 0x24, 0xdf, 0xc5, 0x67, 0x53, 0xd4, 0x36, 0x79, 0xc4, 0x7b, 0x57, 0xbc, 0x99, 0x97, 0x60, 0xef, 0xe4, 0x01, 0xa1, 0xa7, 0xaa, 0x12, 0x36, 0x29, 0xb1, 0x03, 0xc2, 0x83, 0x1c, 0x2b, 0x83, 0xef, 0x2e, 0x2c, 0x23, 0x92, 0xfd, 0xd1, - /* (2^ 48)P */ 0x94, 0xef, 0x03, 0x59, 0xfa, 0x8a, 0x18, 0x76, 0xee, 0x58, 0x08, 0x4d, 0x44, 0xce, 0xf1, 0x52, 0x33, 0x49, 0xf6, 0x69, 0x71, 0xe3, 0xa9, 0xbc, 0x86, 0xe3, 0x43, 0xde, 0x33, 0x7b, 0x90, 0x8b, 0x3e, 0x7d, 0xd5, 0x4a, 0xf0, 0x23, 0x99, 0xa6, 0xea, 0x5f, 0x08, 0xe5, 0xb9, 0x49, 0x8b, 0x0d, 0x6a, 0x21, 0xab, 0x07, 0x62, 0xcd, 0xc4, 0xbe, - /* (2^ 49)P */ 0x61, 0xbf, 0x70, 0x14, 0xfa, 0x4e, 0x9e, 0x7c, 0x0c, 0xf8, 0xb2, 0x48, 0x71, 0x62, 0x83, 0xd6, 0xd1, 0xdc, 0x9c, 0x29, 0x66, 0xb1, 0x34, 0x9c, 0x8d, 0xe6, 0x88, 0xaf, 0xbe, 0xdc, 0x4d, 0xeb, 0xb0, 0xe7, 0x28, 0xae, 0xb2, 0x05, 0x56, 0xc6, 0x0e, 0x10, 0x26, 0xab, 0x2c, 0x59, 0x72, 0x03, 0x66, 0xfe, 0x8f, 0x2c, 0x51, 0x2d, 0xdc, 0xae, - /* (2^ 50)P */ 0xdc, 0x63, 0xf1, 0x8b, 0x5c, 0x65, 0x0b, 0xf1, 0xa6, 0x22, 0xe2, 0xd9, 0xdb, 0x49, 0xb1, 0x3c, 0x47, 0xc2, 0xfe, 0xac, 0x86, 0x07, 0x52, 0xec, 0xb0, 0x08, 0x69, 0xfb, 0xd1, 0x06, 0xdc, 0x48, 0x5c, 0x3d, 0xb2, 0x4d, 0xb8, 0x1a, 0x4e, 0xda, 0xb9, 0xc1, 0x2b, 0xab, 0x4b, 0x62, 0x81, 0x21, 0x9a, 0xfc, 0x3d, 0x39, 0x83, 0x11, 0x36, 0xeb, - /* (2^ 51)P */ 0x94, 0xf3, 0x17, 0xef, 0xf9, 0x60, 0x54, 0xc3, 0xd7, 0x27, 0x35, 0xc5, 0x98, 0x5e, 0xf6, 0x63, 0x6c, 0xa0, 0x4a, 0xd3, 0xa3, 0x98, 0xd9, 0x42, 0xe3, 0xf1, 0xf8, 0x81, 0x96, 0xa9, 0xea, 0x6d, 0x4b, 0x8e, 0x33, 0xca, 0x94, 0x0d, 0xa0, 0xf7, 0xbb, 0x64, 0xa3, 0x36, 0x6f, 0xdc, 0x5a, 0x94, 0x42, 0xca, 0x06, 0xb2, 0x2b, 0x9a, 0x9f, 0x71, - /* (2^ 52)P */ 0xec, 0xdb, 0xa6, 0x1f, 0xdf, 0x15, 0x36, 0xa3, 0xda, 0x8a, 0x7a, 0xb6, 0xa7, 0xe3, 0xaf, 0x52, 0xe0, 0x8d, 0xe8, 0xf2, 0x44, 0x20, 0xeb, 0xa1, 0x20, 0xc4, 0x65, 0x3c, 0x7c, 0x6c, 0x49, 0xed, 0x2f, 0x66, 0x23, 0x68, 0x61, 0x91, 0x40, 0x9f, 0x50, 0x19, 0xd1, 0x84, 0xa7, 0xe2, 0xed, 0x34, 0x37, 0xe3, 0xe4, 0x11, 0x7f, 0x87, 0x55, 0x0f, - /* (2^ 53)P */ 0xb3, 0xa1, 0x0f, 0xb0, 0x48, 0xc0, 0x4d, 0x96, 0xa7, 0xcf, 0x5a, 0x81, 0xb8, 0x4a, 0x46, 0xef, 0x0a, 0xd3, 0x40, 0x7e, 0x02, 0xe3, 0x63, 0xaa, 0x50, 0xd1, 0x2a, 0x37, 0x22, 0x4a, 0x7f, 0x4f, 0xb6, 0xf9, 0x01, 0x82, 0x78, 0x3d, 0x93, 0x14, 0x11, 0x8a, 0x90, 0x60, 0xcd, 0x45, 0x4e, 0x7b, 0x42, 0xb9, 0x3e, 0x6e, 0x68, 0x1f, 0x36, 0x41, - /* (2^ 54)P */ 0x13, 0x73, 0x0e, 0x4f, 0x79, 0x93, 0x9e, 0x29, 0x70, 0x7b, 0x4a, 0x59, 0x1a, 0x9a, 0xf4, 0x55, 0x08, 0xf0, 0xdb, 0x17, 0x58, 0xec, 0x64, 0xad, 0x7f, 0x29, 0xeb, 0x3f, 0x85, 0x4e, 0x60, 0x28, 0x98, 0x1f, 0x73, 0x4e, 0xe6, 0xa8, 0xab, 0xd5, 0xd6, 0xfc, 0xa1, 0x36, 0x6d, 0x15, 0xc6, 0x13, 0x83, 0xa0, 0xc2, 0x6e, 0xd9, 0xdb, 0xc9, 0xcc, - /* (2^ 55)P */ 0xff, 0xd8, 0x52, 0xa3, 0xdc, 0x99, 0xcf, 0x3e, 0x19, 0xb3, 0x68, 0xd0, 0xb5, 0x0d, 0xb8, 0xee, 0x3f, 0xef, 0x6e, 0xc0, 0x38, 0x28, 0x44, 0x92, 0x78, 0x91, 0x1a, 0x08, 0x78, 0x6c, 0x65, 0x24, 0xf3, 0xa2, 0x3d, 0xf2, 0xe5, 0x79, 0x62, 0x69, 0x29, 0xf4, 0x22, 0xc5, 0xdb, 0x6a, 0xae, 0xf4, 0x44, 0xa3, 0x6f, 0xc7, 0x86, 0xab, 0xef, 0xef, - /* (2^ 56)P */ 0xbf, 0x54, 0x9a, 0x09, 0x5d, 0x17, 0xd0, 0xde, 0xfb, 0xf5, 0xca, 0xff, 0x13, 0x20, 0x88, 0x82, 0x3a, 0xe2, 0xd0, 0x3b, 0xfb, 0x05, 0x76, 0xd1, 0xc0, 0x02, 0x71, 0x3b, 0x94, 0xe8, 0xc9, 0x84, 0xcf, 0xa4, 0xe9, 0x28, 0x7b, 0xf5, 0x09, 0xc3, 0x2b, 0x22, 0x40, 0xf1, 0x68, 0x24, 0x24, 0x7d, 0x9f, 0x6e, 0xcd, 0xfe, 0xb0, 0x19, 0x61, 0xf5, - /* (2^ 57)P */ 0xe8, 0x63, 0x51, 0xb3, 0x95, 0x6b, 0x7b, 0x74, 0x92, 0x52, 0x45, 0xa4, 0xed, 0xea, 0x0e, 0x0d, 0x2b, 0x01, 0x1e, 0x2c, 0xbc, 0x91, 0x06, 0x69, 0xdb, 0x1f, 0xb5, 0x77, 0x1d, 0x56, 0xf5, 0xb4, 0x02, 0x80, 0x49, 0x56, 0x12, 0xce, 0x86, 0x05, 0xc9, 0xd9, 0xae, 0xf3, 0x6d, 0xe6, 0x3f, 0x40, 0x52, 0xe9, 0x49, 0x2b, 0x31, 0x06, 0x86, 0x14, - /* (2^ 58)P */ 0xf5, 0x09, 0x3b, 0xd2, 0xff, 0xdf, 0x11, 0xa5, 0x1c, 0x99, 0xe8, 0x1b, 0xa4, 0x2c, 0x7d, 0x8e, 0xc8, 0xf7, 0x03, 0x46, 0xfa, 0xb6, 0xde, 0x73, 0x91, 0x7e, 0x5a, 0x7a, 0xd7, 0x9a, 0x5b, 0x80, 0x24, 0x62, 0x5e, 0x92, 0xf1, 0xa3, 0x45, 0xa3, 0x43, 0x92, 0x8a, 0x2a, 0x5b, 0x0c, 0xb4, 0xc8, 0xad, 0x1c, 0xb6, 0x6c, 0x5e, 0x81, 0x18, 0x91, - /* (2^ 59)P */ 0x96, 0xb3, 0xca, 0x2b, 0xe3, 0x7a, 0x59, 0x72, 0x17, 0x74, 0x29, 0x21, 0xe7, 0x78, 0x07, 0xad, 0xda, 0xb6, 0xcd, 0xf9, 0x27, 0x4d, 0xc8, 0xf2, 0x98, 0x22, 0xca, 0xf2, 0x33, 0x74, 0x7a, 0xdd, 0x1e, 0x71, 0xec, 0xe3, 0x3f, 0xe2, 0xa2, 0xd2, 0x38, 0x75, 0xb0, 0xd0, 0x0a, 0xcf, 0x7d, 0x36, 0xdc, 0x49, 0x38, 0x25, 0x34, 0x4f, 0x20, 0x9a, - /* (2^ 60)P */ 0x2b, 0x6e, 0x04, 0x0d, 0x4f, 0x3d, 0x3b, 0x24, 0xf6, 0x4e, 0x5e, 0x0a, 0xbd, 0x48, 0x96, 0xba, 0x81, 0x8f, 0x39, 0x82, 0x13, 0xe6, 0x72, 0xf3, 0x0f, 0xb6, 0x94, 0xf4, 0xc5, 0x90, 0x74, 0x91, 0xa8, 0xf2, 0xc9, 0xca, 0x9a, 0x4d, 0x98, 0xf2, 0xdf, 0x52, 0x4e, 0x97, 0x2f, 0xeb, 0x84, 0xd3, 0xaf, 0xc2, 0xcc, 0xfb, 0x4c, 0x26, 0x4b, 0xe4, - /* (2^ 61)P */ 0x12, 0x9e, 0xfb, 0x9d, 0x78, 0x79, 0x99, 0xdd, 0xb3, 0x0b, 0x2e, 0x56, 0x41, 0x8e, 0x3f, 0x39, 0xb8, 0x97, 0x89, 0x53, 0x9b, 0x8a, 0x3c, 0x40, 0x9d, 0xa4, 0x6c, 0x2e, 0x31, 0x71, 0xc6, 0x0a, 0x41, 0xd4, 0x95, 0x06, 0x5e, 0xc1, 0xab, 0xc2, 0x14, 0xc4, 0xc7, 0x15, 0x08, 0x3a, 0xad, 0x7a, 0xb4, 0x62, 0xa3, 0x0c, 0x90, 0xf4, 0x47, 0x08, - /* (2^ 62)P */ 0x7f, 0xec, 0x09, 0x82, 0xf5, 0x94, 0x09, 0x93, 0x32, 0xd3, 0xdc, 0x56, 0x80, 0x7b, 0x5b, 0x22, 0x80, 0x6a, 0x96, 0x72, 0xb1, 0xc2, 0xd9, 0xa1, 0x8b, 0x66, 0x42, 0x16, 0xe2, 0x07, 0xb3, 0x2d, 0xf1, 0x75, 0x35, 0x72, 0xc7, 0x98, 0xbe, 0x63, 0x3b, 0x20, 0x75, 0x05, 0xc1, 0x3e, 0x31, 0x5a, 0xf7, 0xaa, 0xae, 0x4b, 0xdb, 0x1d, 0xd0, 0x74, - /* (2^ 63)P */ 0x36, 0x5c, 0x74, 0xe6, 0x5d, 0x59, 0x3f, 0x15, 0x4b, 0x4d, 0x4e, 0x67, 0x41, 0xfe, 0x98, 0x1f, 0x49, 0x76, 0x91, 0x0f, 0x9b, 0xf4, 0xaf, 0x86, 0xaf, 0x66, 0x19, 0xed, 0x46, 0xf1, 0x05, 0x9a, 0xcc, 0xd1, 0x14, 0x1f, 0x82, 0x12, 0x8e, 0xe6, 0xf4, 0xc3, 0x42, 0x5c, 0x4e, 0x33, 0x93, 0xbe, 0x30, 0xe7, 0x64, 0xa9, 0x35, 0x00, 0x4d, 0xf9, - /* (2^ 64)P */ 0x1f, 0xc1, 0x1e, 0xb7, 0xe3, 0x7c, 0xfa, 0xa3, 0x6b, 0x76, 0xaf, 0x9c, 0x05, 0x85, 0x4a, 0xa9, 0xfb, 0xe3, 0x7e, 0xf2, 0x49, 0x56, 0xdc, 0x2f, 0x57, 0x10, 0xba, 0x37, 0xb2, 0x62, 0xf5, 0x6b, 0xe5, 0x8f, 0x0a, 0x87, 0xd1, 0x6a, 0xcb, 0x9d, 0x07, 0xd0, 0xf6, 0x38, 0x99, 0x2c, 0x61, 0x4a, 0x4e, 0xd8, 0xd2, 0x88, 0x29, 0x99, 0x11, 0x95, - /* (2^ 65)P */ 0x6f, 0xdc, 0xd5, 0xd6, 0xd6, 0xa7, 0x4c, 0x46, 0x93, 0x65, 0x62, 0x23, 0x95, 0x32, 0x9c, 0xde, 0x40, 0x41, 0x68, 0x2c, 0x18, 0x4e, 0x5a, 0x8c, 0xc0, 0xc5, 0xc5, 0xea, 0x5c, 0x45, 0x0f, 0x60, 0x78, 0x39, 0xb6, 0x36, 0x23, 0x12, 0xbc, 0x21, 0x9a, 0xf8, 0x91, 0xac, 0xc4, 0x70, 0xdf, 0x85, 0x8e, 0x3c, 0xec, 0x22, 0x04, 0x98, 0xa8, 0xaa, - /* (2^ 66)P */ 0xcc, 0x52, 0x10, 0x5b, 0x4b, 0x6c, 0xc5, 0xfa, 0x3e, 0xd4, 0xf8, 0x1c, 0x04, 0x14, 0x48, 0x33, 0xd9, 0xfc, 0x5f, 0xb0, 0xa5, 0x48, 0x8c, 0x45, 0x8a, 0xee, 0x3e, 0xa7, 0xc1, 0x2e, 0x34, 0xca, 0xf6, 0xc9, 0xeb, 0x10, 0xbb, 0xe1, 0x59, 0x84, 0x25, 0xe8, 0x81, 0x70, 0xc0, 0x09, 0x42, 0xa7, 0x3b, 0x0d, 0x33, 0x00, 0xb5, 0x77, 0xbe, 0x25, - /* (2^ 67)P */ 0xcd, 0x1f, 0xbc, 0x7d, 0xef, 0xe5, 0xca, 0x91, 0xaf, 0xa9, 0x59, 0x6a, 0x09, 0xca, 0xd6, 0x1b, 0x3d, 0x55, 0xde, 0xa2, 0x6a, 0x80, 0xd6, 0x95, 0x47, 0xe4, 0x5f, 0x68, 0x54, 0x08, 0xdf, 0x29, 0xba, 0x2a, 0x02, 0x84, 0xe8, 0xe9, 0x00, 0x77, 0x99, 0x36, 0x03, 0xf6, 0x4a, 0x3e, 0x21, 0x81, 0x7d, 0xb8, 0xa4, 0x8a, 0xa2, 0x05, 0xef, 0xbc, - /* (2^ 68)P */ 0x7c, 0x59, 0x5f, 0x66, 0xd9, 0xb7, 0x83, 0x43, 0x8a, 0xa1, 0x8d, 0x51, 0x70, 0xba, 0xf2, 0x9b, 0x95, 0xc0, 0x4b, 0x4c, 0xa0, 0x14, 0xd3, 0xa4, 0x5d, 0x4a, 0x37, 0x36, 0x97, 0x31, 0x1e, 0x12, 0xe7, 0xbb, 0x08, 0x67, 0xa5, 0x23, 0xd7, 0xfb, 0x97, 0xd8, 0x6a, 0x03, 0xb1, 0xf8, 0x7f, 0xda, 0x58, 0xd9, 0x3f, 0x73, 0x4a, 0x53, 0xe1, 0x7b, - /* (2^ 69)P */ 0x55, 0x83, 0x98, 0x78, 0x6c, 0x56, 0x5e, 0xed, 0xf7, 0x23, 0x3e, 0x4c, 0x7d, 0x09, 0x2d, 0x09, 0x9c, 0x58, 0x8b, 0x32, 0xca, 0xfe, 0xbf, 0x47, 0x03, 0xeb, 0x4d, 0xe7, 0xeb, 0x9c, 0x83, 0x05, 0x68, 0xaa, 0x80, 0x89, 0x44, 0xf9, 0xd4, 0xdc, 0xdb, 0xb1, 0xdb, 0x77, 0xac, 0xf9, 0x2a, 0xae, 0x35, 0xac, 0x74, 0xb5, 0x95, 0x62, 0x18, 0x85, - /* (2^ 70)P */ 0xab, 0x82, 0x7e, 0x10, 0xd7, 0xe6, 0x57, 0xd1, 0x66, 0x12, 0x31, 0x9c, 0x9c, 0xa6, 0x27, 0x59, 0x71, 0x2e, 0xeb, 0xa0, 0x68, 0xc5, 0x87, 0x51, 0xf4, 0xca, 0x3f, 0x98, 0x56, 0xb0, 0x89, 0xb1, 0xc7, 0x7b, 0x46, 0xb3, 0xae, 0x36, 0xf2, 0xee, 0x15, 0x1a, 0x60, 0xf4, 0x50, 0x76, 0x4f, 0xc4, 0x53, 0x0d, 0x36, 0x4d, 0x31, 0xb1, 0x20, 0x51, - /* (2^ 71)P */ 0xf7, 0x1d, 0x8c, 0x1b, 0x5e, 0xe5, 0x02, 0x6f, 0xc5, 0xa5, 0xe0, 0x5f, 0xc6, 0xb6, 0x63, 0x43, 0xaf, 0x3c, 0x19, 0x6c, 0xf4, 0xaf, 0xa4, 0x33, 0xb1, 0x0a, 0x37, 0x3d, 0xd9, 0x4d, 0xe2, 0x29, 0x24, 0x26, 0x94, 0x7c, 0x02, 0xe4, 0xe2, 0xf2, 0xbe, 0xbd, 0xac, 0x1b, 0x48, 0xb8, 0xdd, 0xe9, 0x0d, 0x9a, 0x50, 0x1a, 0x98, 0x71, 0x6e, 0xdc, - /* (2^ 72)P */ 0x9f, 0x40, 0xb1, 0xb3, 0x66, 0x28, 0x6c, 0xfe, 0xa6, 0x7d, 0xf8, 0x3e, 0xb8, 0xf3, 0xde, 0x52, 0x76, 0x52, 0xa3, 0x92, 0x98, 0x23, 0xab, 0x4f, 0x88, 0x97, 0xfc, 0x22, 0xe1, 0x6b, 0x67, 0xcd, 0x13, 0x95, 0xda, 0x65, 0xdd, 0x3b, 0x67, 0x3f, 0x5f, 0x4c, 0xf2, 0x8a, 0xad, 0x98, 0xa7, 0x94, 0x24, 0x45, 0x87, 0x11, 0x7c, 0x75, 0x79, 0x85, - /* (2^ 73)P */ 0x70, 0xbf, 0xf9, 0x3b, 0xa9, 0x44, 0x57, 0x72, 0x96, 0xc9, 0xa4, 0x98, 0x65, 0xbf, 0x87, 0xb3, 0x3a, 0x39, 0x12, 0xde, 0xe5, 0x39, 0x01, 0x4f, 0xf7, 0xc0, 0x71, 0x52, 0x36, 0x85, 0xb3, 0x18, 0xf8, 0x14, 0xc0, 0x6d, 0xae, 0x9e, 0x4f, 0xb0, 0x72, 0x87, 0xac, 0x5c, 0xd1, 0x6c, 0x41, 0x6c, 0x90, 0x9d, 0x22, 0x81, 0xe4, 0x2b, 0xea, 0xe5, - /* (2^ 74)P */ 0xfc, 0xea, 0x1a, 0x65, 0xd9, 0x49, 0x6a, 0x39, 0xb5, 0x96, 0x72, 0x7b, 0x32, 0xf1, 0xd0, 0xe9, 0x45, 0xd9, 0x31, 0x55, 0xc7, 0x34, 0xe9, 0x5a, 0xec, 0x73, 0x0b, 0x03, 0xc4, 0xb3, 0xe6, 0xc9, 0x5e, 0x0a, 0x17, 0xfe, 0x53, 0x66, 0x7f, 0x21, 0x18, 0x74, 0x54, 0x1b, 0xc9, 0x49, 0x16, 0xd2, 0x48, 0xaf, 0x5b, 0x47, 0x7b, 0xeb, 0xaa, 0xc9, - /* (2^ 75)P */ 0x47, 0x04, 0xf5, 0x5a, 0x87, 0x77, 0x9e, 0x21, 0x34, 0x4e, 0x83, 0x88, 0xaf, 0x02, 0x1d, 0xb0, 0x5a, 0x1d, 0x1d, 0x7d, 0x8d, 0x2c, 0xd3, 0x8d, 0x63, 0xa9, 0x45, 0xfb, 0x15, 0x6d, 0x86, 0x45, 0xcd, 0x38, 0x0e, 0xf7, 0x37, 0x79, 0xed, 0x6d, 0x5a, 0xbc, 0x32, 0xcc, 0x66, 0xf1, 0x3a, 0xb2, 0x87, 0x6f, 0x70, 0x71, 0xd9, 0xf2, 0xfa, 0x7b, - /* (2^ 76)P */ 0x68, 0x07, 0xdc, 0x61, 0x40, 0xe4, 0xec, 0x32, 0xc8, 0xbe, 0x66, 0x30, 0x54, 0x80, 0xfd, 0x13, 0x7a, 0xef, 0xae, 0xed, 0x2e, 0x00, 0x6d, 0x3f, 0xbd, 0xfc, 0x91, 0x24, 0x53, 0x7f, 0x63, 0x9d, 0x2e, 0xe3, 0x76, 0xe0, 0xf3, 0xe1, 0x8f, 0x7a, 0xc4, 0x77, 0x0c, 0x91, 0xc0, 0xc2, 0x18, 0x6b, 0x04, 0xad, 0xb6, 0x70, 0x9a, 0x64, 0xc5, 0x82, - /* (2^ 77)P */ 0x7f, 0xea, 0x13, 0xd8, 0x9e, 0xfc, 0x5b, 0x06, 0xb5, 0x4f, 0xda, 0x38, 0xe0, 0x9c, 0xd2, 0x3a, 0xc1, 0x1c, 0x62, 0x70, 0x7f, 0xc6, 0x24, 0x0a, 0x47, 0x04, 0x01, 0xc4, 0x55, 0x09, 0xd1, 0x7a, 0x07, 0xba, 0xa3, 0x80, 0x4f, 0xc1, 0x65, 0x36, 0x6d, 0xc0, 0x10, 0xcf, 0x94, 0xa9, 0xa2, 0x01, 0x44, 0xd1, 0xf9, 0x1c, 0x4c, 0xfb, 0xf8, 0x99, - /* (2^ 78)P */ 0x6c, 0xb9, 0x6b, 0xee, 0x43, 0x5b, 0xb9, 0xbb, 0xee, 0x2e, 0x52, 0xc1, 0xc6, 0xb9, 0x61, 0xd2, 0x93, 0xa5, 0xaf, 0x52, 0xf4, 0xa4, 0x1a, 0x51, 0x61, 0xa7, 0xcb, 0x9e, 0xbb, 0x56, 0x65, 0xe2, 0xbf, 0x75, 0xb9, 0x9c, 0x50, 0x96, 0x60, 0x81, 0x74, 0x47, 0xc0, 0x04, 0x88, 0x71, 0x76, 0x39, 0x9a, 0xa7, 0xb1, 0x4e, 0x43, 0x15, 0xe0, 0xbb, - /* (2^ 79)P */ 0xbb, 0xce, 0xe2, 0xbb, 0xf9, 0x17, 0x0f, 0x82, 0x40, 0xad, 0x73, 0xe3, 0xeb, 0x3b, 0x06, 0x1a, 0xcf, 0x8e, 0x6e, 0x28, 0xb8, 0x26, 0xd9, 0x5b, 0xb7, 0xb3, 0xcf, 0xb4, 0x6a, 0x1c, 0xbf, 0x7f, 0xb8, 0xb5, 0x79, 0xcf, 0x45, 0x68, 0x7d, 0xc5, 0xeb, 0xf3, 0xbe, 0x39, 0x40, 0xfc, 0x07, 0x90, 0x7a, 0x62, 0xad, 0x86, 0x08, 0x71, 0x25, 0xe1, - /* (2^ 80)P */ 0x9b, 0x46, 0xac, 0xef, 0xc1, 0x4e, 0xa1, 0x97, 0x95, 0x76, 0xf9, 0x1b, 0xc2, 0xb2, 0x6a, 0x41, 0xea, 0x80, 0x3d, 0xe9, 0x08, 0x52, 0x5a, 0xe3, 0xf2, 0x08, 0xc5, 0xea, 0x39, 0x3f, 0x44, 0x71, 0x4d, 0xea, 0x0d, 0x05, 0x23, 0xe4, 0x2e, 0x3c, 0x89, 0xfe, 0x12, 0x8a, 0x95, 0x42, 0x0a, 0x68, 0xea, 0x5a, 0x28, 0x06, 0x9e, 0xe3, 0x5f, 0xe0, - /* (2^ 81)P */ 0x00, 0x61, 0x6c, 0x98, 0x9b, 0xe7, 0xb9, 0x06, 0x1c, 0xc5, 0x1b, 0xed, 0xbe, 0xc8, 0xb3, 0xea, 0x87, 0xf0, 0xc4, 0x24, 0x7d, 0xbb, 0x5d, 0xa4, 0x1d, 0x7a, 0x16, 0x00, 0x55, 0x94, 0x67, 0x78, 0xbd, 0x58, 0x02, 0x82, 0x90, 0x53, 0x76, 0xd4, 0x72, 0x99, 0x51, 0x6f, 0x7b, 0xcf, 0x80, 0x30, 0x31, 0x3b, 0x01, 0xc7, 0xc1, 0xef, 0xe6, 0x42, - /* (2^ 82)P */ 0xe2, 0x35, 0xaf, 0x4b, 0x79, 0xc6, 0x12, 0x24, 0x99, 0xc0, 0x68, 0xb0, 0x43, 0x3e, 0xe5, 0xef, 0xe2, 0x29, 0xea, 0xb8, 0xb3, 0xbc, 0x6a, 0x53, 0x2c, 0x69, 0x18, 0x5a, 0xf9, 0x15, 0xae, 0x66, 0x58, 0x18, 0xd3, 0x2d, 0x4b, 0x00, 0xfd, 0x84, 0xab, 0x4f, 0xae, 0x70, 0x6b, 0x9e, 0x9a, 0xdf, 0x83, 0xfd, 0x2e, 0x3c, 0xcf, 0xf8, 0x88, 0x5b, - /* (2^ 83)P */ 0xa4, 0x90, 0x31, 0x85, 0x13, 0xcd, 0xdf, 0x64, 0xc9, 0xa1, 0x0b, 0xe7, 0xb6, 0x73, 0x8a, 0x1b, 0x22, 0x78, 0x4c, 0xd4, 0xae, 0x48, 0x18, 0x00, 0x00, 0xa8, 0x9f, 0x06, 0xf9, 0xfb, 0x2d, 0xc3, 0xb1, 0x2a, 0xbc, 0x13, 0x99, 0x57, 0xaf, 0xf0, 0x8d, 0x61, 0x54, 0x29, 0xd5, 0xf2, 0x72, 0x00, 0x96, 0xd1, 0x85, 0x12, 0x8a, 0xf0, 0x23, 0xfb, - /* (2^ 84)P */ 0x69, 0xc7, 0xdb, 0xd9, 0x92, 0x75, 0x08, 0x9b, 0xeb, 0xa5, 0x93, 0xd1, 0x1a, 0xf4, 0xf5, 0xaf, 0xe6, 0xc4, 0x4a, 0x0d, 0x35, 0x26, 0x39, 0x9d, 0xd3, 0x17, 0x3e, 0xae, 0x2d, 0xbf, 0x73, 0x9f, 0xb7, 0x74, 0x91, 0xd1, 0xd8, 0x5c, 0x14, 0xf9, 0x75, 0xdf, 0xeb, 0xc2, 0x22, 0xd8, 0x14, 0x8d, 0x86, 0x23, 0x4d, 0xd1, 0x2d, 0xdb, 0x6b, 0x42, - /* (2^ 85)P */ 0x8c, 0xda, 0xc6, 0xf8, 0x71, 0xba, 0x2b, 0x06, 0x78, 0xae, 0xcc, 0x3a, 0xe3, 0xe3, 0xa1, 0x8b, 0xe2, 0x34, 0x6d, 0x28, 0x9e, 0x46, 0x13, 0x4d, 0x9e, 0xa6, 0x73, 0x49, 0x65, 0x79, 0x88, 0xb9, 0x3a, 0xd1, 0x6d, 0x2f, 0x48, 0x2b, 0x0a, 0x7f, 0x58, 0x20, 0x37, 0xf4, 0x0e, 0xbb, 0x4a, 0x95, 0x58, 0x0c, 0x88, 0x30, 0xc4, 0x74, 0xdd, 0xfd, - /* (2^ 86)P */ 0x6d, 0x13, 0x4e, 0x89, 0x2d, 0xa9, 0xa3, 0xed, 0x09, 0xe3, 0x0e, 0x71, 0x3e, 0x4a, 0xab, 0x90, 0xde, 0x03, 0xeb, 0x56, 0x46, 0x60, 0x06, 0xf5, 0x71, 0xe5, 0xee, 0x9b, 0xef, 0xff, 0xc4, 0x2c, 0x9f, 0x37, 0x48, 0x45, 0x94, 0x12, 0x41, 0x81, 0x15, 0x70, 0x91, 0x99, 0x5e, 0x56, 0x6b, 0xf4, 0xa6, 0xc9, 0xf5, 0x69, 0x9d, 0x78, 0x37, 0x57, - /* (2^ 87)P */ 0xf3, 0x51, 0x57, 0x7e, 0x43, 0x6f, 0xc6, 0x67, 0x59, 0x0c, 0xcf, 0x94, 0xe6, 0x3d, 0xb5, 0x07, 0xc9, 0x77, 0x48, 0xc9, 0x68, 0x0d, 0x98, 0x36, 0x62, 0x35, 0x38, 0x1c, 0xf5, 0xc5, 0xec, 0x66, 0x78, 0xfe, 0x47, 0xab, 0x26, 0xd6, 0x44, 0xb6, 0x06, 0x0f, 0x89, 0xe3, 0x19, 0x40, 0x1a, 0xe7, 0xd8, 0x65, 0x55, 0xf7, 0x1a, 0xfc, 0xa3, 0x0e, - /* (2^ 88)P */ 0x0e, 0x30, 0xa6, 0xb7, 0x58, 0x60, 0x62, 0x2a, 0x6c, 0x13, 0xa8, 0x14, 0x9b, 0xb8, 0xf2, 0x70, 0xd8, 0xb1, 0x71, 0x88, 0x8c, 0x18, 0x31, 0x25, 0x93, 0x90, 0xb4, 0xc7, 0x49, 0xd8, 0xd4, 0xdb, 0x1e, 0x1e, 0x7f, 0xaa, 0xba, 0xc9, 0xf2, 0x5d, 0xa9, 0x3a, 0x43, 0xb4, 0x5c, 0xee, 0x7b, 0xc7, 0x97, 0xb7, 0x66, 0xd7, 0x23, 0xd9, 0x22, 0x59, - /* (2^ 89)P */ 0x28, 0x19, 0xa6, 0xf9, 0x89, 0x20, 0x78, 0xd4, 0x6d, 0xcb, 0x79, 0x8f, 0x61, 0x6f, 0xb2, 0x5c, 0x4f, 0xa6, 0x54, 0x84, 0x95, 0x24, 0x36, 0x64, 0xcb, 0x39, 0xe7, 0x8f, 0x97, 0x9c, 0x5c, 0x3c, 0xfb, 0x51, 0x11, 0x01, 0x17, 0xdb, 0xc9, 0x9b, 0x51, 0x03, 0x9a, 0xe9, 0xe5, 0x24, 0x1e, 0xf5, 0xda, 0xe0, 0x48, 0x02, 0x23, 0xd0, 0x2c, 0x81, - /* (2^ 90)P */ 0x42, 0x1b, 0xe4, 0x91, 0x85, 0x2a, 0x0c, 0xd2, 0x28, 0x66, 0x57, 0x9e, 0x33, 0x8d, 0x25, 0x71, 0x10, 0x65, 0x76, 0xa2, 0x8c, 0x21, 0x86, 0x81, 0x15, 0xc2, 0x27, 0xeb, 0x54, 0x2d, 0x4f, 0x6c, 0xe6, 0xd6, 0x24, 0x9c, 0x1a, 0x12, 0xb8, 0x81, 0xe2, 0x0a, 0xf3, 0xd3, 0xf0, 0xd3, 0xe1, 0x74, 0x1f, 0x9b, 0x11, 0x47, 0xd0, 0xcf, 0xb6, 0x54, - /* (2^ 91)P */ 0x26, 0x45, 0xa2, 0x10, 0xd4, 0x2d, 0xae, 0xc0, 0xb0, 0xe8, 0x86, 0xb3, 0xc7, 0xea, 0x70, 0x87, 0x61, 0xb5, 0xa5, 0x55, 0xbe, 0x88, 0x1d, 0x7a, 0xd9, 0x6f, 0xeb, 0x83, 0xe2, 0x44, 0x7f, 0x98, 0x04, 0xd6, 0x50, 0x9d, 0xa7, 0x86, 0x66, 0x09, 0x63, 0xe1, 0xed, 0x72, 0xb1, 0xe4, 0x1d, 0x3a, 0xfd, 0x47, 0xce, 0x1c, 0xaa, 0x3b, 0x8f, 0x1b, - /* (2^ 92)P */ 0xf4, 0x3c, 0x4a, 0xb6, 0xc2, 0x9c, 0xe0, 0x2e, 0xb7, 0x38, 0xea, 0x61, 0x35, 0x97, 0x10, 0x90, 0xae, 0x22, 0x48, 0xb3, 0xa9, 0xc6, 0x7a, 0xbb, 0x23, 0xf2, 0xf8, 0x1b, 0xa7, 0xa1, 0x79, 0xcc, 0xc4, 0xf8, 0x08, 0x76, 0x8a, 0x5a, 0x1c, 0x1b, 0xc5, 0x33, 0x91, 0xa9, 0xb8, 0xb9, 0xd3, 0xf8, 0x49, 0xcd, 0xe5, 0x82, 0x43, 0xf7, 0xca, 0x68, - /* (2^ 93)P */ 0x38, 0xba, 0xae, 0x44, 0xfe, 0x57, 0x64, 0x56, 0x7c, 0x0e, 0x9c, 0xca, 0xff, 0xa9, 0x82, 0xbb, 0x38, 0x4a, 0xa7, 0xf7, 0x47, 0xab, 0xbe, 0x6d, 0x23, 0x0b, 0x8a, 0xed, 0xc2, 0xb9, 0x8f, 0xf1, 0xec, 0x91, 0x44, 0x73, 0x64, 0xba, 0xd5, 0x8f, 0x37, 0x38, 0x0d, 0xd5, 0xf8, 0x73, 0x57, 0xb6, 0xc2, 0x45, 0xdc, 0x25, 0xb2, 0xb6, 0xea, 0xd9, - /* (2^ 94)P */ 0xbf, 0xe9, 0x1a, 0x40, 0x4d, 0xcc, 0xe6, 0x1d, 0x70, 0x1a, 0x65, 0xcc, 0x34, 0x2c, 0x37, 0x2c, 0x2d, 0x6b, 0x6d, 0xe5, 0x2f, 0x19, 0x9e, 0xe4, 0xe1, 0xaa, 0xd4, 0xab, 0x54, 0xf4, 0xa8, 0xe4, 0x69, 0x2d, 0x8e, 0x4d, 0xd7, 0xac, 0xb0, 0x5b, 0xfe, 0xe3, 0x26, 0x07, 0xc3, 0xf8, 0x1b, 0x43, 0xa8, 0x1d, 0x64, 0xa5, 0x25, 0x88, 0xbb, 0x77, - /* (2^ 95)P */ 0x92, 0xcd, 0x6e, 0xa0, 0x79, 0x04, 0x18, 0xf4, 0x11, 0x58, 0x48, 0xb5, 0x3c, 0x7b, 0xd1, 0xcc, 0xd3, 0x14, 0x2c, 0xa0, 0xdd, 0x04, 0x44, 0x11, 0xb3, 0x6d, 0x2f, 0x0d, 0xf5, 0x2a, 0x75, 0x5d, 0x1d, 0xda, 0x86, 0x8d, 0x7d, 0x6b, 0x32, 0x68, 0xb6, 0x6c, 0x64, 0x9e, 0xde, 0x80, 0x88, 0xce, 0x08, 0xbf, 0x0b, 0xe5, 0x8e, 0x4f, 0x1d, 0xfb, - /* (2^ 96)P */ 0xaf, 0xe8, 0x85, 0xbf, 0x7f, 0x37, 0x8d, 0x66, 0x7c, 0xd5, 0xd3, 0x96, 0xa5, 0x81, 0x67, 0x95, 0xff, 0x48, 0xde, 0xde, 0xd7, 0x7a, 0x46, 0x34, 0xb1, 0x13, 0x70, 0x29, 0xed, 0x87, 0x90, 0xb0, 0x40, 0x2c, 0xa6, 0x43, 0x6e, 0xb6, 0xbc, 0x48, 0x8a, 0xc1, 0xae, 0xb8, 0xd4, 0xe2, 0xc0, 0x32, 0xb2, 0xa6, 0x2a, 0x8f, 0xb5, 0x16, 0x9e, 0xc3, - /* (2^ 97)P */ 0xff, 0x4d, 0xd2, 0xd6, 0x74, 0xef, 0x2c, 0x96, 0xc1, 0x11, 0xa8, 0xb8, 0xfe, 0x94, 0x87, 0x3e, 0xa0, 0xfb, 0x57, 0xa3, 0xfc, 0x7a, 0x7e, 0x6a, 0x59, 0x6c, 0x54, 0xbb, 0xbb, 0xa2, 0x25, 0x38, 0x1b, 0xdf, 0x5d, 0x7b, 0x94, 0x14, 0xde, 0x07, 0x6e, 0xd3, 0xab, 0x02, 0x26, 0x74, 0x16, 0x12, 0xdf, 0x2e, 0x2a, 0xa7, 0xb0, 0xe8, 0x29, 0xc0, - /* (2^ 98)P */ 0x6a, 0x38, 0x0b, 0xd3, 0xba, 0x45, 0x23, 0xe0, 0x04, 0x3b, 0x83, 0x39, 0xc5, 0x11, 0xe6, 0xcf, 0x39, 0x0a, 0xb3, 0xb0, 0x3b, 0x27, 0x29, 0x63, 0x1c, 0xf3, 0x00, 0xe6, 0xd2, 0x55, 0x21, 0x1f, 0x84, 0x97, 0x9f, 0x01, 0x49, 0x43, 0x30, 0x5f, 0xe0, 0x1d, 0x24, 0xc4, 0x4e, 0xa0, 0x2b, 0x0b, 0x12, 0x55, 0xc3, 0x27, 0xae, 0x08, 0x83, 0x7c, - /* (2^ 99)P */ 0x5d, 0x1a, 0xb7, 0xa9, 0xf5, 0xfd, 0xec, 0xad, 0xb7, 0x87, 0x02, 0x5f, 0x0d, 0x30, 0x4d, 0xe2, 0x65, 0x87, 0xa4, 0x41, 0x45, 0x1d, 0x67, 0xe0, 0x30, 0x5c, 0x13, 0x87, 0xf6, 0x2e, 0x08, 0xc1, 0xc7, 0x12, 0x45, 0xc8, 0x9b, 0xad, 0xb8, 0xd5, 0x57, 0xbb, 0x5c, 0x48, 0x3a, 0xe1, 0x91, 0x5e, 0xf6, 0x4d, 0x8a, 0x63, 0x75, 0x69, 0x0c, 0x01, - /* (2^100)P */ 0x8f, 0x53, 0x2d, 0xa0, 0x71, 0x3d, 0xfc, 0x45, 0x10, 0x96, 0xcf, 0x56, 0xf9, 0xbb, 0x40, 0x3c, 0x86, 0x52, 0x76, 0xbe, 0x84, 0xf9, 0xa6, 0x9d, 0x3d, 0x27, 0xbe, 0xb4, 0x00, 0x49, 0x94, 0xf5, 0x5d, 0xe1, 0x62, 0x85, 0x66, 0xe5, 0xb8, 0x20, 0x2c, 0x09, 0x7d, 0x9d, 0x3d, 0x6e, 0x74, 0x39, 0xab, 0xad, 0xa0, 0x90, 0x97, 0x5f, 0xbb, 0xa7, - /* (2^101)P */ 0xdb, 0x2d, 0x99, 0x08, 0x16, 0x46, 0x83, 0x7a, 0xa8, 0xea, 0x3d, 0x28, 0x5b, 0x49, 0xfc, 0xb9, 0x6d, 0x00, 0x9e, 0x54, 0x4f, 0x47, 0x64, 0x9b, 0x58, 0x4d, 0x07, 0x0c, 0x6f, 0x29, 0x56, 0x0b, 0x00, 0x14, 0x85, 0x96, 0x41, 0x04, 0xb9, 0x5c, 0xa4, 0xf6, 0x16, 0x73, 0x6a, 0xc7, 0x62, 0x0c, 0x65, 0x2f, 0x93, 0xbf, 0xf7, 0xb9, 0xb7, 0xf1, - /* (2^102)P */ 0xeb, 0x6d, 0xb3, 0x46, 0x32, 0xd2, 0xcb, 0x08, 0x94, 0x14, 0xbf, 0x3f, 0xc5, 0xcb, 0x5f, 0x9f, 0x8a, 0x89, 0x0c, 0x1b, 0x45, 0xad, 0x4c, 0x50, 0xb4, 0xe1, 0xa0, 0x6b, 0x11, 0x92, 0xaf, 0x1f, 0x00, 0xcc, 0xe5, 0x13, 0x7e, 0xe4, 0x2e, 0xa0, 0x57, 0xf3, 0xa7, 0x84, 0x79, 0x7a, 0xc2, 0xb7, 0xb7, 0xfc, 0x5d, 0xa5, 0xa9, 0x64, 0xcc, 0xd8, - /* (2^103)P */ 0xa9, 0xc4, 0x12, 0x8b, 0x34, 0x78, 0x3e, 0x38, 0xfd, 0x3f, 0x87, 0xfa, 0x88, 0x94, 0xd5, 0xd9, 0x7f, 0xeb, 0x58, 0xff, 0xb9, 0x45, 0xdb, 0xa1, 0xed, 0x22, 0x28, 0x1d, 0x00, 0x6d, 0x79, 0x85, 0x7a, 0x75, 0x5d, 0xf0, 0xb1, 0x9e, 0x47, 0x28, 0x8c, 0x62, 0xdf, 0xfb, 0x4c, 0x7b, 0xc5, 0x1a, 0x42, 0x95, 0xef, 0x9a, 0xb7, 0x27, 0x7e, 0xda, - /* (2^104)P */ 0xca, 0xd5, 0xc0, 0x17, 0xa1, 0x66, 0x79, 0x9c, 0x2a, 0xb7, 0x0a, 0xfe, 0x62, 0xe4, 0x26, 0x78, 0x90, 0xa7, 0xcb, 0xb0, 0x4f, 0x6d, 0xf9, 0x8f, 0xf7, 0x7d, 0xac, 0xb8, 0x78, 0x1f, 0x41, 0xea, 0x97, 0x1e, 0x62, 0x97, 0x43, 0x80, 0x58, 0x80, 0xb6, 0x69, 0x7d, 0xee, 0x16, 0xd2, 0xa1, 0x81, 0xd7, 0xb1, 0x27, 0x03, 0x48, 0xda, 0xab, 0xec, - /* (2^105)P */ 0x5b, 0xed, 0x40, 0x8e, 0x8c, 0xc1, 0x66, 0x90, 0x7f, 0x0c, 0xb2, 0xfc, 0xbd, 0x16, 0xac, 0x7d, 0x4c, 0x6a, 0xf9, 0xae, 0xe7, 0x4e, 0x11, 0x12, 0xe9, 0xbe, 0x17, 0x09, 0xc6, 0xc1, 0x5e, 0xb5, 0x7b, 0x50, 0x5c, 0x27, 0xfb, 0x80, 0xab, 0x01, 0xfa, 0x5b, 0x9b, 0x75, 0x16, 0x6e, 0xb2, 0x5c, 0x8c, 0x2f, 0xa5, 0x6a, 0x1a, 0x68, 0xa6, 0x90, - /* (2^106)P */ 0x75, 0xfe, 0xb6, 0x96, 0x96, 0x87, 0x4c, 0x12, 0xa9, 0xd1, 0xd8, 0x03, 0xa3, 0xc1, 0x15, 0x96, 0xe8, 0xa0, 0x75, 0x82, 0xa0, 0x6d, 0xea, 0x54, 0xdc, 0x5f, 0x0d, 0x7e, 0xf6, 0x70, 0xb5, 0xdc, 0x7a, 0xf6, 0xc4, 0xd4, 0x21, 0x49, 0xf5, 0xd4, 0x14, 0x6d, 0x48, 0x1d, 0x7c, 0x99, 0x42, 0xdf, 0x78, 0x6b, 0x9d, 0xb9, 0x30, 0x3c, 0xd0, 0x29, - /* (2^107)P */ 0x85, 0xd6, 0xd8, 0xf3, 0x91, 0x74, 0xdd, 0xbd, 0x72, 0x96, 0x10, 0xe4, 0x76, 0x02, 0x5a, 0x72, 0x67, 0xd3, 0x17, 0x72, 0x14, 0x9a, 0x20, 0x5b, 0x0f, 0x8d, 0xed, 0x6d, 0x4e, 0xe3, 0xd9, 0x82, 0xc2, 0x99, 0xee, 0x39, 0x61, 0x69, 0x8a, 0x24, 0x01, 0x92, 0x15, 0xe7, 0xfc, 0xf9, 0x4d, 0xac, 0xf1, 0x30, 0x49, 0x01, 0x0b, 0x6e, 0x0f, 0x20, - /* (2^108)P */ 0xd8, 0x25, 0x94, 0x5e, 0x43, 0x29, 0xf5, 0xcc, 0xe8, 0xe3, 0x55, 0x41, 0x3c, 0x9f, 0x58, 0x5b, 0x00, 0xeb, 0xc5, 0xdf, 0xcf, 0xfb, 0xfd, 0x6e, 0x92, 0xec, 0x99, 0x30, 0xd6, 0x05, 0xdd, 0x80, 0x7a, 0x5d, 0x6d, 0x16, 0x85, 0xd8, 0x9d, 0x43, 0x65, 0xd8, 0x2c, 0x33, 0x2f, 0x5c, 0x41, 0xea, 0xb7, 0x95, 0x77, 0xf2, 0x9e, 0x59, 0x09, 0xe8, - /* (2^109)P */ 0x00, 0xa0, 0x03, 0x80, 0xcd, 0x60, 0xe5, 0x17, 0xd4, 0x15, 0x99, 0xdd, 0x4f, 0xbf, 0x66, 0xb8, 0xc0, 0xf5, 0xf9, 0xfc, 0x6d, 0x42, 0x18, 0x34, 0x1c, 0x7d, 0x5b, 0xb5, 0x09, 0xd0, 0x99, 0x57, 0x81, 0x0b, 0x62, 0xb3, 0xa2, 0xf9, 0x0b, 0xae, 0x95, 0xb8, 0xc2, 0x3b, 0x0d, 0x5b, 0x00, 0xf1, 0xed, 0xbc, 0x05, 0x9d, 0x61, 0xbc, 0x73, 0x9d, - /* (2^110)P */ 0xd4, 0xdb, 0x29, 0xe5, 0x85, 0xe9, 0xc6, 0x89, 0x2a, 0xa8, 0x54, 0xab, 0xb3, 0x7f, 0x88, 0xc0, 0x4d, 0xe0, 0xd1, 0x74, 0x6e, 0xa3, 0xa7, 0x39, 0xd5, 0xcc, 0xa1, 0x8a, 0xcb, 0x5b, 0x34, 0xad, 0x92, 0xb4, 0xd8, 0xd5, 0x17, 0xf6, 0x77, 0x18, 0x9e, 0xaf, 0x45, 0x3b, 0x03, 0xe2, 0xf8, 0x52, 0x60, 0xdc, 0x15, 0x20, 0x9e, 0xdf, 0xd8, 0x5d, - /* (2^111)P */ 0x02, 0xc1, 0xac, 0x1a, 0x15, 0x8e, 0x6c, 0xf5, 0x1e, 0x1e, 0xba, 0x7e, 0xc2, 0xda, 0x7d, 0x02, 0xda, 0x43, 0xae, 0x04, 0x70, 0x28, 0x54, 0x78, 0x94, 0xf5, 0x4f, 0x07, 0x84, 0x8f, 0xed, 0xaa, 0xc0, 0xb8, 0xcd, 0x7f, 0x7e, 0x33, 0xa3, 0xbe, 0x21, 0x29, 0xc8, 0x56, 0x34, 0xc0, 0x76, 0x87, 0x8f, 0xc7, 0x73, 0x58, 0x90, 0x16, 0xfc, 0xd6, - /* (2^112)P */ 0xb8, 0x3f, 0xe1, 0xdf, 0x3a, 0x91, 0x25, 0x0c, 0xf6, 0x47, 0xa8, 0x89, 0xc4, 0xc6, 0x61, 0xec, 0x86, 0x2c, 0xfd, 0xbe, 0xa4, 0x6f, 0xc2, 0xd4, 0x46, 0x19, 0x70, 0x5d, 0x09, 0x02, 0x86, 0xd3, 0x4b, 0xe9, 0x16, 0x7b, 0xf0, 0x0d, 0x6c, 0xff, 0x91, 0x05, 0xbf, 0x55, 0xb4, 0x00, 0x8d, 0xe5, 0x6d, 0x68, 0x20, 0x90, 0x12, 0xb5, 0x5c, 0x32, - /* (2^113)P */ 0x80, 0x45, 0xc8, 0x51, 0x87, 0xba, 0x1c, 0x5c, 0xcf, 0x5f, 0x4b, 0x3c, 0x9e, 0x3b, 0x36, 0xd2, 0x26, 0xa2, 0x7f, 0xab, 0xb7, 0xbf, 0xda, 0x68, 0x23, 0x8f, 0xc3, 0xa0, 0xfd, 0xad, 0xf1, 0x56, 0x3b, 0xd0, 0x75, 0x2b, 0x44, 0x61, 0xd8, 0xf4, 0xf1, 0x05, 0x49, 0x53, 0x07, 0xee, 0x47, 0xef, 0xc0, 0x7c, 0x9d, 0xe4, 0x15, 0x88, 0xc5, 0x47, - /* (2^114)P */ 0x2d, 0xb5, 0x09, 0x80, 0xb9, 0xd3, 0xd8, 0xfe, 0x4c, 0xd2, 0xa6, 0x6e, 0xd3, 0x75, 0xcf, 0xb0, 0x99, 0xcb, 0x50, 0x8d, 0xe9, 0x67, 0x9b, 0x20, 0xe8, 0x57, 0xd8, 0x14, 0x85, 0x73, 0x6a, 0x74, 0xe0, 0x99, 0xf0, 0x6b, 0x6e, 0x59, 0x30, 0x31, 0x33, 0x96, 0x5f, 0xa1, 0x0c, 0x1b, 0xf4, 0xca, 0x09, 0xe1, 0x9b, 0xb5, 0xcf, 0x6d, 0x0b, 0xeb, - /* (2^115)P */ 0x1a, 0xde, 0x50, 0xa9, 0xac, 0x3e, 0x10, 0x43, 0x4f, 0x82, 0x4f, 0xc0, 0xfe, 0x3f, 0x33, 0xd2, 0x64, 0x86, 0x50, 0xa9, 0x51, 0x76, 0x5e, 0x50, 0x97, 0x6c, 0x73, 0x8d, 0x77, 0xa3, 0x75, 0x03, 0xbc, 0xc9, 0xfb, 0x50, 0xd9, 0x6d, 0x16, 0xad, 0x5d, 0x32, 0x3d, 0xac, 0x44, 0xdf, 0x51, 0xf7, 0x19, 0xd4, 0x0b, 0x57, 0x78, 0x0b, 0x81, 0x4e, - /* (2^116)P */ 0x32, 0x24, 0xf1, 0x6c, 0x55, 0x62, 0x1d, 0xb3, 0x1f, 0xda, 0xfa, 0x6a, 0x8f, 0x98, 0x01, 0x16, 0xde, 0x44, 0x50, 0x0d, 0x2e, 0x6c, 0x0b, 0xa2, 0xd3, 0x74, 0x0e, 0xa9, 0xbf, 0x8d, 0xa9, 0xc8, 0xc8, 0x2f, 0x62, 0xc1, 0x35, 0x5e, 0xfd, 0x3a, 0xb3, 0x83, 0x2d, 0xee, 0x4e, 0xfd, 0x5c, 0x5e, 0xad, 0x85, 0xa5, 0x10, 0xb5, 0x4f, 0x34, 0xa7, - /* (2^117)P */ 0xd1, 0x58, 0x6f, 0xe6, 0x54, 0x2c, 0xc2, 0xcd, 0xcf, 0x83, 0xdc, 0x88, 0x0c, 0xb9, 0xb4, 0x62, 0x18, 0x89, 0x65, 0x28, 0xe9, 0x72, 0x4b, 0x65, 0xcf, 0xd6, 0x90, 0x88, 0xd7, 0x76, 0x17, 0x4f, 0x74, 0x64, 0x1e, 0xcb, 0xd3, 0xf5, 0x4b, 0xaa, 0x2e, 0x4d, 0x2d, 0x7c, 0x13, 0x1f, 0xfd, 0xd9, 0x60, 0x83, 0x7e, 0xda, 0x64, 0x1c, 0xdc, 0x9f, - /* (2^118)P */ 0xad, 0xef, 0xac, 0x1b, 0xc1, 0x30, 0x5a, 0x15, 0xc9, 0x1f, 0xac, 0xf1, 0xca, 0x44, 0x95, 0x95, 0xea, 0xf2, 0x22, 0xe7, 0x8d, 0x25, 0xf0, 0xff, 0xd8, 0x71, 0xf7, 0xf8, 0x8f, 0x8f, 0xcd, 0xf4, 0x1e, 0xfe, 0x6c, 0x68, 0x04, 0xb8, 0x78, 0xa1, 0x5f, 0xa6, 0x5d, 0x5e, 0xf9, 0x8d, 0xea, 0x80, 0xcb, 0xf3, 0x17, 0xa6, 0x03, 0xc9, 0x38, 0xd5, - /* (2^119)P */ 0x79, 0x14, 0x31, 0xc3, 0x38, 0xe5, 0xaa, 0xbf, 0x17, 0xa3, 0x04, 0x4e, 0x80, 0x59, 0x9c, 0x9f, 0x19, 0x39, 0xe4, 0x2d, 0x23, 0x54, 0x4a, 0x7f, 0x3e, 0xf3, 0xd9, 0xc7, 0xba, 0x6c, 0x8f, 0x6b, 0xfa, 0x34, 0xb5, 0x23, 0x17, 0x1d, 0xff, 0x1d, 0xea, 0x1f, 0xd7, 0xba, 0x61, 0xb2, 0xe0, 0x38, 0x6a, 0xe9, 0xcf, 0x48, 0x5d, 0x6a, 0x10, 0x9c, - /* (2^120)P */ 0xc8, 0xbb, 0x13, 0x1c, 0x3f, 0x3c, 0x34, 0xfd, 0xac, 0x37, 0x52, 0x44, 0x25, 0xa8, 0xde, 0x1d, 0x63, 0xf4, 0x81, 0x9a, 0xbe, 0x0b, 0x74, 0x2e, 0xc8, 0x51, 0x16, 0xd3, 0xac, 0x4a, 0xaf, 0xe2, 0x5f, 0x3a, 0x89, 0x32, 0xd1, 0x9b, 0x7c, 0x90, 0x0d, 0xac, 0xdc, 0x8b, 0x73, 0x45, 0x45, 0x97, 0xb1, 0x90, 0x2c, 0x1b, 0x31, 0xca, 0xb1, 0x94, - /* (2^121)P */ 0x07, 0x28, 0xdd, 0x10, 0x14, 0xa5, 0x95, 0x7e, 0xf3, 0xe4, 0xd4, 0x14, 0xb4, 0x7e, 0x76, 0xdb, 0x42, 0xd6, 0x94, 0x3e, 0xeb, 0x44, 0x64, 0x88, 0x0d, 0xec, 0xc1, 0x21, 0xf0, 0x79, 0xe0, 0x83, 0x67, 0x55, 0x53, 0xc2, 0xf6, 0xc5, 0xc5, 0x89, 0x39, 0xe8, 0x42, 0xd0, 0x17, 0xbd, 0xff, 0x35, 0x59, 0x0e, 0xc3, 0x06, 0x86, 0xd4, 0x64, 0xcf, - /* (2^122)P */ 0x91, 0xa8, 0xdb, 0x57, 0x9b, 0xe2, 0x96, 0x31, 0x10, 0x6e, 0xd7, 0x9a, 0x97, 0xb3, 0xab, 0xb5, 0x15, 0x66, 0xbe, 0xcc, 0x6d, 0x9a, 0xac, 0x06, 0xb3, 0x0d, 0xaa, 0x4b, 0x9c, 0x96, 0x79, 0x6c, 0x34, 0xee, 0x9e, 0x53, 0x4d, 0x6e, 0xbd, 0x88, 0x02, 0xbf, 0x50, 0x54, 0x12, 0x5d, 0x01, 0x02, 0x46, 0xc6, 0x74, 0x02, 0x8c, 0x24, 0xae, 0xb1, - /* (2^123)P */ 0xf5, 0x22, 0xea, 0xac, 0x7d, 0x9c, 0x33, 0x8a, 0xa5, 0x36, 0x79, 0x6a, 0x4f, 0xa4, 0xdc, 0xa5, 0x73, 0x64, 0xc4, 0x6f, 0x43, 0x02, 0x3b, 0x94, 0x66, 0xd2, 0x4b, 0x4f, 0xf6, 0x45, 0x33, 0x5d, 0x10, 0x33, 0x18, 0x1e, 0xa3, 0xfc, 0xf7, 0xd2, 0xb8, 0xc8, 0xa7, 0xe0, 0x76, 0x8a, 0xcd, 0xff, 0x4f, 0x99, 0x34, 0x47, 0x84, 0x91, 0x96, 0x9f, - /* (2^124)P */ 0x8a, 0x48, 0x3b, 0x48, 0x4a, 0xbc, 0xac, 0xe2, 0x80, 0xd6, 0xd2, 0x35, 0xde, 0xd0, 0x56, 0x42, 0x33, 0xb3, 0x56, 0x5a, 0xcd, 0xb8, 0x3d, 0xb5, 0x25, 0xc1, 0xed, 0xff, 0x87, 0x0b, 0x79, 0xff, 0xf2, 0x62, 0xe1, 0x76, 0xc6, 0xa2, 0x0f, 0xa8, 0x9b, 0x0d, 0xcc, 0x3f, 0x3d, 0x35, 0x27, 0x8d, 0x0b, 0x74, 0xb0, 0xc3, 0x78, 0x8c, 0xcc, 0xc8, - /* (2^125)P */ 0xfc, 0x9a, 0x0c, 0xa8, 0x49, 0x42, 0xb8, 0xdf, 0xcf, 0xb3, 0x19, 0xa6, 0x64, 0x57, 0xfe, 0xe8, 0xf8, 0xa6, 0x4b, 0x86, 0xa1, 0xd5, 0x83, 0x7f, 0x14, 0x99, 0x18, 0x0c, 0x7d, 0x5b, 0xf7, 0x3d, 0xf9, 0x4b, 0x79, 0xb1, 0x86, 0x30, 0xb4, 0x5e, 0x6a, 0xe8, 0x9d, 0xfa, 0x8a, 0x41, 0xc4, 0x30, 0xfc, 0x56, 0x74, 0x14, 0x42, 0xc8, 0x96, 0x0e, - /* (2^126)P */ 0xdf, 0x66, 0xec, 0xbc, 0x44, 0xdb, 0x19, 0xce, 0xd4, 0xb5, 0x49, 0x40, 0x07, 0x49, 0xe0, 0x3a, 0x61, 0x10, 0xfb, 0x7d, 0xba, 0xb1, 0xe0, 0x28, 0x5b, 0x99, 0x59, 0x96, 0xa2, 0xee, 0xe0, 0x23, 0x37, 0x39, 0x1f, 0xe6, 0x57, 0x9f, 0xf8, 0xf8, 0xdc, 0x74, 0xf6, 0x8f, 0x4f, 0x5e, 0x51, 0xa4, 0x12, 0xac, 0xbe, 0xe4, 0xf3, 0xd1, 0xf0, 0x24, - /* (2^127)P */ 0x1e, 0x3e, 0x9a, 0x5f, 0xdf, 0x9f, 0xd6, 0x4e, 0x8a, 0x28, 0xc3, 0xcd, 0x96, 0x9d, 0x57, 0xc7, 0x61, 0x81, 0x90, 0xff, 0xae, 0xb1, 0x4f, 0xc2, 0x96, 0x8b, 0x1a, 0x18, 0xf4, 0x50, 0xcb, 0x31, 0xe1, 0x57, 0xf4, 0x90, 0xa8, 0xea, 0xac, 0xe7, 0x61, 0x98, 0xb6, 0x15, 0xc1, 0x7b, 0x29, 0xa4, 0xc3, 0x18, 0xef, 0xb9, 0xd8, 0xdf, 0xf6, 0xac, - /* (2^128)P */ 0xca, 0xa8, 0x6c, 0xf1, 0xb4, 0xca, 0xfe, 0x31, 0xee, 0x48, 0x38, 0x8b, 0x0e, 0xbb, 0x7a, 0x30, 0xaa, 0xf9, 0xee, 0x27, 0x53, 0x24, 0xdc, 0x2e, 0x15, 0xa6, 0x48, 0x8f, 0xa0, 0x7e, 0xf1, 0xdc, 0x93, 0x87, 0x39, 0xeb, 0x7f, 0x38, 0x92, 0x92, 0x4c, 0x29, 0xe9, 0x57, 0xd8, 0x59, 0xfc, 0xe9, 0x9c, 0x44, 0xc0, 0x65, 0xcf, 0xac, 0x4b, 0xdc, - /* (2^129)P */ 0xa3, 0xd0, 0x37, 0x8f, 0x86, 0x2f, 0xc6, 0x47, 0x55, 0x46, 0x65, 0x26, 0x4b, 0x91, 0xe2, 0x18, 0x5c, 0x4f, 0x23, 0xc1, 0x37, 0x29, 0xb9, 0xc1, 0x27, 0xc5, 0x3c, 0xbf, 0x7e, 0x23, 0xdb, 0x73, 0x99, 0xbd, 0x1b, 0xb2, 0x31, 0x68, 0x3a, 0xad, 0xb7, 0xb0, 0x10, 0xc5, 0xe5, 0x11, 0x51, 0xba, 0xa7, 0x60, 0x66, 0x54, 0xf0, 0x08, 0xd7, 0x69, - /* (2^130)P */ 0x89, 0x41, 0x79, 0xcc, 0xeb, 0x0a, 0xf5, 0x4b, 0xa3, 0x4c, 0xce, 0x52, 0xb0, 0xa7, 0xe4, 0x41, 0x75, 0x7d, 0x04, 0xbb, 0x09, 0x4c, 0x50, 0x9f, 0xdf, 0xea, 0x74, 0x61, 0x02, 0xad, 0xb4, 0x9d, 0xb7, 0x05, 0xb9, 0xea, 0xeb, 0x91, 0x35, 0xe7, 0x49, 0xea, 0xd3, 0x4f, 0x3c, 0x60, 0x21, 0x7a, 0xde, 0xc7, 0xe2, 0x5a, 0xee, 0x8e, 0x93, 0xc7, - /* (2^131)P */ 0x00, 0xe8, 0xed, 0xd0, 0xb3, 0x0d, 0xaf, 0xb2, 0xde, 0x2c, 0xf6, 0x00, 0xe2, 0xea, 0x6d, 0xf8, 0x0e, 0xd9, 0x67, 0x59, 0xa9, 0x50, 0xbb, 0x17, 0x8f, 0xff, 0xb1, 0x9f, 0x17, 0xb6, 0xf2, 0xb5, 0xba, 0x80, 0xf7, 0x0f, 0xba, 0xd5, 0x09, 0x43, 0xaa, 0x4e, 0x3a, 0x67, 0x6a, 0x89, 0x9b, 0x18, 0x65, 0x35, 0xf8, 0x3a, 0x49, 0x91, 0x30, 0x51, - /* (2^132)P */ 0x8d, 0x25, 0xe9, 0x0e, 0x7d, 0x50, 0x76, 0xe4, 0x58, 0x7e, 0xb9, 0x33, 0xe6, 0x65, 0x90, 0xc2, 0x50, 0x9d, 0x50, 0x2e, 0x11, 0xad, 0xd5, 0x43, 0x52, 0x32, 0x41, 0x4f, 0x7b, 0xb6, 0xa0, 0xec, 0x81, 0x75, 0x36, 0x7c, 0x77, 0x85, 0x59, 0x70, 0xe4, 0xf9, 0xef, 0x66, 0x8d, 0x35, 0xc8, 0x2a, 0x6e, 0x5b, 0xc6, 0x0d, 0x0b, 0x29, 0x60, 0x68, - /* (2^133)P */ 0xf8, 0xce, 0xb0, 0x3a, 0x56, 0x7d, 0x51, 0x9a, 0x25, 0x73, 0xea, 0xdd, 0xe4, 0xe0, 0x0e, 0xf0, 0x07, 0xc0, 0x31, 0x00, 0x73, 0x35, 0xd0, 0x39, 0xc4, 0x9b, 0xb7, 0x95, 0xe0, 0x62, 0x70, 0x36, 0x0b, 0xcb, 0xa0, 0x42, 0xde, 0x51, 0xcf, 0x41, 0xe0, 0xb8, 0xb4, 0xc0, 0xe5, 0x46, 0x99, 0x9f, 0x02, 0x7f, 0x14, 0x8c, 0xc1, 0x4e, 0xef, 0xe8, - /* (2^134)P */ 0x10, 0x01, 0x57, 0x0a, 0xbe, 0x8b, 0x18, 0xc8, 0xca, 0x00, 0x28, 0x77, 0x4a, 0x9a, 0xc7, 0x55, 0x2a, 0xcc, 0x0c, 0x7b, 0xb9, 0xe9, 0xc8, 0x97, 0x7c, 0x02, 0xe3, 0x09, 0x2f, 0x62, 0x30, 0xb8, 0x40, 0x09, 0x65, 0xe9, 0x55, 0x63, 0xb5, 0x07, 0xca, 0x9f, 0x00, 0xdf, 0x9d, 0x5c, 0xc7, 0xee, 0x57, 0xa5, 0x90, 0x15, 0x1e, 0x22, 0xa0, 0x12, - /* (2^135)P */ 0x71, 0x2d, 0xc9, 0xef, 0x27, 0xb9, 0xd8, 0x12, 0x43, 0x6b, 0xa8, 0xce, 0x3b, 0x6d, 0x6e, 0x91, 0x43, 0x23, 0xbc, 0x32, 0xb3, 0xbf, 0xe1, 0xc7, 0x39, 0xcf, 0x7c, 0x42, 0x4c, 0xb1, 0x30, 0xe2, 0xdd, 0x69, 0x06, 0xe5, 0xea, 0xf0, 0x2a, 0x16, 0x50, 0x71, 0xca, 0x92, 0xdf, 0xc1, 0xcc, 0xec, 0xe6, 0x54, 0x07, 0xf3, 0x18, 0x8d, 0xd8, 0x29, - /* (2^136)P */ 0x98, 0x51, 0x48, 0x8f, 0xfa, 0x2e, 0x5e, 0x67, 0xb0, 0xc6, 0x17, 0x12, 0xb6, 0x7d, 0xc9, 0xad, 0x81, 0x11, 0xad, 0x0c, 0x1c, 0x2d, 0x45, 0xdf, 0xac, 0x66, 0xbd, 0x08, 0x6f, 0x7c, 0xc7, 0x06, 0x6e, 0x19, 0x08, 0x39, 0x64, 0xd7, 0xe4, 0xd1, 0x11, 0x5f, 0x1c, 0xf4, 0x67, 0xc3, 0x88, 0x6a, 0xe6, 0x07, 0xa3, 0x83, 0xd7, 0xfd, 0x2a, 0xf9, - /* (2^137)P */ 0x87, 0xed, 0xeb, 0xd9, 0xdf, 0xff, 0x43, 0x8b, 0xaa, 0x20, 0x58, 0xb0, 0xb4, 0x6b, 0x14, 0xb8, 0x02, 0xc5, 0x40, 0x20, 0x22, 0xbb, 0xf7, 0xb4, 0xf3, 0x05, 0x1e, 0x4d, 0x94, 0xff, 0xe3, 0xc5, 0x22, 0x82, 0xfe, 0xaf, 0x90, 0x42, 0x98, 0x6b, 0x76, 0x8b, 0x3e, 0x89, 0x3f, 0x42, 0x2a, 0xa7, 0x26, 0x00, 0xda, 0x5c, 0xa2, 0x2b, 0xec, 0xdd, - /* (2^138)P */ 0x5c, 0x21, 0x16, 0x0d, 0x46, 0xb8, 0xd0, 0xa7, 0x88, 0xe7, 0x25, 0xcb, 0x3e, 0x50, 0x73, 0x61, 0xe7, 0xaf, 0x5a, 0x3f, 0x47, 0x8b, 0x3d, 0x97, 0x79, 0x2c, 0xe6, 0x6d, 0x95, 0x74, 0x65, 0x70, 0x36, 0xfd, 0xd1, 0x9e, 0x13, 0x18, 0x63, 0xb1, 0x2d, 0x0b, 0xb5, 0x36, 0x3e, 0xe7, 0x35, 0x42, 0x3b, 0xe6, 0x1f, 0x4d, 0x9d, 0x59, 0xa2, 0x43, - /* (2^139)P */ 0x8c, 0x0c, 0x7c, 0x24, 0x9e, 0xe0, 0xf8, 0x05, 0x1c, 0x9e, 0x1f, 0x31, 0xc0, 0x70, 0xb3, 0xfb, 0x4e, 0xf8, 0x0a, 0x57, 0xb7, 0x49, 0xb5, 0x73, 0xa1, 0x5f, 0x9b, 0x6a, 0x07, 0x6c, 0x87, 0x71, 0x87, 0xd4, 0xbe, 0x98, 0x1e, 0x98, 0xee, 0x52, 0xc1, 0x7b, 0x95, 0x0f, 0x28, 0x32, 0x36, 0x28, 0xd0, 0x3a, 0x0f, 0x7d, 0x2a, 0xa9, 0x62, 0xb9, - /* (2^140)P */ 0x97, 0xe6, 0x18, 0x77, 0xf9, 0x34, 0xac, 0xbc, 0xe0, 0x62, 0x9f, 0x42, 0xde, 0xbd, 0x2f, 0xf7, 0x1f, 0xb7, 0x14, 0x52, 0x8a, 0x79, 0xb2, 0x3f, 0xd2, 0x95, 0x71, 0x01, 0xe8, 0xaf, 0x8c, 0xa4, 0xa4, 0xa7, 0x27, 0xf3, 0x5c, 0xdf, 0x3e, 0x57, 0x7a, 0xf1, 0x76, 0x49, 0xe6, 0x42, 0x3f, 0x8f, 0x1e, 0x63, 0x4a, 0x65, 0xb5, 0x41, 0xf5, 0x02, - /* (2^141)P */ 0x72, 0x85, 0xc5, 0x0b, 0xe1, 0x47, 0x64, 0x02, 0xc5, 0x4d, 0x81, 0x69, 0xb2, 0xcf, 0x0f, 0x6c, 0xd4, 0x6d, 0xd0, 0xc7, 0xb4, 0x1c, 0xd0, 0x32, 0x59, 0x89, 0xe2, 0xe0, 0x96, 0x8b, 0x12, 0x98, 0xbf, 0x63, 0x7a, 0x4c, 0x76, 0x7e, 0x58, 0x17, 0x8f, 0x5b, 0x0a, 0x59, 0x65, 0x75, 0xbc, 0x61, 0x1f, 0xbe, 0xc5, 0x6e, 0x0a, 0x57, 0x52, 0x70, - /* (2^142)P */ 0x92, 0x1c, 0x77, 0xbb, 0x62, 0x02, 0x6c, 0x25, 0x9c, 0x66, 0x07, 0x83, 0xab, 0xcc, 0x80, 0x5d, 0xd2, 0x76, 0x0c, 0xa4, 0xc5, 0xb4, 0x8a, 0x68, 0x23, 0x31, 0x32, 0x29, 0x8a, 0x47, 0x92, 0x12, 0x80, 0xb3, 0xfa, 0x18, 0xe4, 0x8d, 0xc0, 0x4d, 0xfe, 0x97, 0x5f, 0x72, 0x41, 0xb5, 0x5c, 0x7a, 0xbd, 0xf0, 0xcf, 0x5e, 0x97, 0xaa, 0x64, 0x32, - /* (2^143)P */ 0x35, 0x3f, 0x75, 0xc1, 0x7a, 0x75, 0x7e, 0xa9, 0xc6, 0x0b, 0x4e, 0x32, 0x62, 0xec, 0xe3, 0x5c, 0xfb, 0x01, 0x43, 0xb6, 0xd4, 0x5b, 0x75, 0xd2, 0xee, 0x7f, 0x5d, 0x23, 0x2b, 0xb3, 0x54, 0x34, 0x4c, 0xd3, 0xb4, 0x32, 0x84, 0x81, 0xb5, 0x09, 0x76, 0x19, 0xda, 0x58, 0xda, 0x7c, 0xdb, 0x2e, 0xdd, 0x4c, 0x8e, 0xdd, 0x5d, 0x89, 0x10, 0x10, - /* (2^144)P */ 0x57, 0x25, 0x6a, 0x08, 0x37, 0x92, 0xa8, 0xdf, 0x24, 0xef, 0x8f, 0x33, 0x34, 0x52, 0xa4, 0x4c, 0xf0, 0x77, 0x9f, 0x69, 0x77, 0xd5, 0x8f, 0xd2, 0x9a, 0xb3, 0xb6, 0x1d, 0x2d, 0xa6, 0xf7, 0x1f, 0xda, 0xd7, 0xcb, 0x75, 0x11, 0xc3, 0x6b, 0xc0, 0x38, 0xb1, 0xd5, 0x2d, 0x96, 0x84, 0x16, 0xfa, 0x26, 0xb9, 0xcc, 0x3f, 0x16, 0x47, 0x23, 0x74, - /* (2^145)P */ 0x9b, 0x61, 0x2a, 0x1c, 0xdd, 0x39, 0xa5, 0xfa, 0x1c, 0x7d, 0x63, 0x50, 0xca, 0xe6, 0x9d, 0xfa, 0xb7, 0xc4, 0x4c, 0x6a, 0x97, 0x5f, 0x36, 0x4e, 0x47, 0xdd, 0x17, 0xf7, 0xf9, 0x19, 0xce, 0x75, 0x17, 0xad, 0xce, 0x2a, 0xf3, 0xfe, 0x27, 0x8f, 0x3e, 0x48, 0xc0, 0x60, 0x87, 0x24, 0x19, 0xae, 0x59, 0xe4, 0x5a, 0x00, 0x2a, 0xba, 0xa2, 0x1f, - /* (2^146)P */ 0x26, 0x88, 0x42, 0x60, 0x9f, 0x6e, 0x2c, 0x7c, 0x39, 0x0f, 0x47, 0x6a, 0x0e, 0x02, 0xbb, 0x4b, 0x34, 0x29, 0x55, 0x18, 0x36, 0xcf, 0x3b, 0x47, 0xf1, 0x2e, 0xfc, 0x6e, 0x94, 0xff, 0xe8, 0x6b, 0x06, 0xd2, 0xba, 0x77, 0x5e, 0x60, 0xd7, 0x19, 0xef, 0x02, 0x9d, 0x3a, 0xc2, 0xb7, 0xa9, 0xd8, 0x57, 0xee, 0x7e, 0x2b, 0xf2, 0x6d, 0x28, 0xda, - /* (2^147)P */ 0xdf, 0xd9, 0x92, 0x11, 0x98, 0x23, 0xe2, 0x45, 0x2f, 0x74, 0x70, 0xee, 0x0e, 0x55, 0x65, 0x79, 0x86, 0x38, 0x17, 0x92, 0x85, 0x87, 0x99, 0x50, 0xd9, 0x7c, 0xdb, 0xa1, 0x10, 0xec, 0x30, 0xb7, 0x40, 0xa3, 0x23, 0x9b, 0x0e, 0x27, 0x49, 0x29, 0x03, 0x94, 0xff, 0x53, 0xdc, 0xd7, 0xed, 0x49, 0xa9, 0x5a, 0x3b, 0xee, 0xd7, 0xc7, 0x65, 0xaf, - /* (2^148)P */ 0xa0, 0xbd, 0xbe, 0x03, 0xee, 0x0c, 0xbe, 0x32, 0x00, 0x7b, 0x52, 0xcb, 0x92, 0x29, 0xbf, 0xa0, 0xc6, 0xd9, 0xd2, 0xd6, 0x15, 0xe8, 0x3a, 0x75, 0x61, 0x65, 0x56, 0xae, 0xad, 0x3c, 0x2a, 0x64, 0x14, 0x3f, 0x8e, 0xc1, 0x2d, 0x0c, 0x8d, 0x20, 0xdb, 0x58, 0x4b, 0xe5, 0x40, 0x15, 0x4b, 0xdc, 0xa8, 0xbd, 0xef, 0x08, 0xa7, 0xd1, 0xf4, 0xb0, - /* (2^149)P */ 0xa9, 0x0f, 0x05, 0x94, 0x66, 0xac, 0x1f, 0x65, 0x3f, 0xe1, 0xb8, 0xe1, 0x34, 0x5e, 0x1d, 0x8f, 0xe3, 0x93, 0x03, 0x15, 0xff, 0xb6, 0x65, 0xb6, 0x6e, 0xc0, 0x2f, 0xd4, 0x2e, 0xb9, 0x2c, 0x13, 0x3c, 0x99, 0x1c, 0xb5, 0x87, 0xba, 0x79, 0xcb, 0xf0, 0x18, 0x06, 0x86, 0x04, 0x14, 0x25, 0x09, 0xcd, 0x1c, 0x14, 0xda, 0x35, 0xd0, 0x38, 0x3b, - /* (2^150)P */ 0x1b, 0x04, 0xa3, 0x27, 0xb4, 0xd3, 0x37, 0x48, 0x1e, 0x8f, 0x69, 0xd3, 0x5a, 0x2f, 0x20, 0x02, 0x36, 0xbe, 0x06, 0x7b, 0x6b, 0x6c, 0x12, 0x5b, 0x80, 0x74, 0x44, 0xe6, 0xf8, 0xf5, 0x95, 0x59, 0x29, 0xab, 0x51, 0x47, 0x83, 0x28, 0xe0, 0xad, 0xde, 0xaa, 0xd3, 0xb1, 0x1a, 0xcb, 0xa3, 0xcd, 0x8b, 0x6a, 0xb1, 0xa7, 0x0a, 0xd1, 0xf9, 0xbe, - /* (2^151)P */ 0xce, 0x2f, 0x85, 0xca, 0x74, 0x6d, 0x49, 0xb8, 0xce, 0x80, 0x44, 0xe0, 0xda, 0x5b, 0xcf, 0x2f, 0x79, 0x74, 0xfe, 0xb4, 0x2c, 0x99, 0x20, 0x6e, 0x09, 0x04, 0xfb, 0x6d, 0x57, 0x5b, 0x95, 0x0c, 0x45, 0xda, 0x4f, 0x7f, 0x63, 0xcc, 0x85, 0x5a, 0x67, 0x50, 0x68, 0x71, 0xb4, 0x67, 0xb1, 0x2e, 0xc1, 0x1c, 0xdc, 0xff, 0x2a, 0x7c, 0x10, 0x5e, - /* (2^152)P */ 0xa6, 0xde, 0xf3, 0xd4, 0x22, 0x30, 0x24, 0x9e, 0x0b, 0x30, 0x54, 0x59, 0x7e, 0xa2, 0xeb, 0x89, 0x54, 0x65, 0x3e, 0x40, 0xd1, 0xde, 0xe6, 0xee, 0x4d, 0xbf, 0x5e, 0x40, 0x1d, 0xee, 0x4f, 0x68, 0xd9, 0xa7, 0x2f, 0xb3, 0x64, 0xb3, 0xf5, 0xc8, 0xd3, 0xaa, 0x70, 0x70, 0x3d, 0xef, 0xd3, 0x95, 0x54, 0xdb, 0x3e, 0x94, 0x95, 0x92, 0x1f, 0x45, - /* (2^153)P */ 0x22, 0x80, 0x1d, 0x9d, 0x96, 0xa5, 0x78, 0x6f, 0xe0, 0x1e, 0x1b, 0x66, 0x42, 0xc8, 0xae, 0x9e, 0x46, 0x45, 0x08, 0x41, 0xdf, 0x80, 0xae, 0x6f, 0xdb, 0x15, 0x5a, 0x21, 0x31, 0x7a, 0xd0, 0xf2, 0x54, 0x15, 0x88, 0xd3, 0x0f, 0x7f, 0x14, 0x5a, 0x14, 0x97, 0xab, 0xf4, 0x58, 0x6a, 0x9f, 0xea, 0x74, 0xe5, 0x6b, 0x90, 0x59, 0x2b, 0x48, 0xd9, - /* (2^154)P */ 0x12, 0x24, 0x04, 0xf5, 0x50, 0xc2, 0x8c, 0xb0, 0x7c, 0x46, 0x98, 0xd5, 0x24, 0xad, 0xf6, 0x72, 0xdc, 0x82, 0x1a, 0x60, 0xc1, 0xeb, 0x48, 0xef, 0x7f, 0x6e, 0xe6, 0xcc, 0xdb, 0x7b, 0xae, 0xbe, 0x5e, 0x1e, 0x5c, 0xe6, 0x0a, 0x70, 0xdf, 0xa4, 0xa3, 0x85, 0x1b, 0x1b, 0x7f, 0x72, 0xb9, 0x96, 0x6f, 0xdc, 0x03, 0x76, 0x66, 0xfb, 0xa0, 0x33, - /* (2^155)P */ 0x37, 0x40, 0xbb, 0xbc, 0x68, 0x58, 0x86, 0xca, 0xbb, 0xa5, 0x24, 0x76, 0x3d, 0x48, 0xd1, 0xad, 0xb4, 0xa8, 0xcf, 0xc3, 0xb6, 0xa8, 0xba, 0x1a, 0x3a, 0xbe, 0x33, 0x75, 0x04, 0x5c, 0x13, 0x8c, 0x0d, 0x70, 0x8d, 0xa6, 0x4e, 0x2a, 0xeb, 0x17, 0x3c, 0x22, 0xdd, 0x3e, 0x96, 0x40, 0x11, 0x9e, 0x4e, 0xae, 0x3d, 0xf8, 0x91, 0xd7, 0x50, 0xc8, - /* (2^156)P */ 0xd8, 0xca, 0xde, 0x19, 0xcf, 0x00, 0xe4, 0x73, 0x18, 0x7f, 0x9b, 0x9f, 0xf4, 0x5b, 0x49, 0x49, 0x99, 0xdc, 0xa4, 0x46, 0x21, 0xb5, 0xd7, 0x3e, 0xb7, 0x47, 0x1b, 0xa9, 0x9f, 0x4c, 0x69, 0x7d, 0xec, 0x33, 0xd6, 0x1c, 0x51, 0x7f, 0x47, 0x74, 0x7a, 0x6c, 0xf3, 0xd2, 0x2e, 0xbf, 0xdf, 0x6c, 0x9e, 0x77, 0x3b, 0x34, 0xf6, 0x73, 0x80, 0xed, - /* (2^157)P */ 0x16, 0xfb, 0x16, 0xc3, 0xc2, 0x83, 0xe4, 0xf4, 0x03, 0x7f, 0x52, 0xb0, 0x67, 0x51, 0x7b, 0x24, 0x5a, 0x51, 0xd3, 0xb6, 0x4e, 0x59, 0x76, 0xcd, 0x08, 0x7b, 0x1d, 0x7a, 0x9c, 0x65, 0xae, 0xce, 0xaa, 0xd2, 0x1c, 0x85, 0x66, 0x68, 0x06, 0x15, 0xa8, 0x06, 0xe6, 0x16, 0x37, 0xf4, 0x49, 0x9e, 0x0f, 0x50, 0x37, 0xb1, 0xb2, 0x93, 0x70, 0x43, - /* (2^158)P */ 0x18, 0x3a, 0x16, 0xe5, 0x8d, 0xc8, 0x35, 0xd6, 0x7b, 0x09, 0xec, 0x61, 0x5f, 0x5c, 0x2a, 0x19, 0x96, 0x2e, 0xc3, 0xfd, 0xab, 0xe6, 0x23, 0xae, 0xab, 0xc5, 0xcb, 0xb9, 0x7b, 0x2d, 0x34, 0x51, 0xb9, 0x41, 0x9e, 0x7d, 0xca, 0xda, 0x25, 0x45, 0x14, 0xb0, 0xc7, 0x4d, 0x26, 0x2b, 0xfe, 0x43, 0xb0, 0x21, 0x5e, 0xfa, 0xdc, 0x7c, 0xf9, 0x5a, - /* (2^159)P */ 0x94, 0xad, 0x42, 0x17, 0xf5, 0xcd, 0x1c, 0x0d, 0xf6, 0x41, 0xd2, 0x55, 0xbb, 0x50, 0xf1, 0xc6, 0xbc, 0xa6, 0xc5, 0x3a, 0xfd, 0x9b, 0x75, 0x3e, 0xf6, 0x1a, 0xa7, 0xb2, 0x6e, 0x64, 0x12, 0xdc, 0x3c, 0xe5, 0xf6, 0xfc, 0x3b, 0xfa, 0x43, 0x81, 0xd4, 0xa5, 0xee, 0xf5, 0x9c, 0x47, 0x2f, 0xd0, 0x9c, 0xde, 0xa1, 0x48, 0x91, 0x9a, 0x34, 0xc1, - /* (2^160)P */ 0x37, 0x1b, 0xb3, 0x88, 0xc9, 0x98, 0x4e, 0xfb, 0x84, 0x4f, 0x2b, 0x0a, 0xb6, 0x8f, 0x35, 0x15, 0xcd, 0x61, 0x7a, 0x5f, 0x5c, 0xa0, 0xca, 0x23, 0xa0, 0x93, 0x1f, 0xcc, 0x3c, 0x39, 0x3a, 0x24, 0xa7, 0x49, 0xad, 0x8d, 0x59, 0xcc, 0x94, 0x5a, 0x16, 0xf5, 0x70, 0xe8, 0x52, 0x1e, 0xee, 0x20, 0x30, 0x17, 0x7e, 0xf0, 0x4c, 0x93, 0x06, 0x5a, - /* (2^161)P */ 0x81, 0xba, 0x3b, 0xd7, 0x3e, 0xb4, 0x32, 0x3a, 0x22, 0x39, 0x2a, 0xfc, 0x19, 0xd9, 0xd2, 0xf6, 0xc5, 0x79, 0x6c, 0x0e, 0xde, 0xda, 0x01, 0xff, 0x52, 0xfb, 0xb6, 0x95, 0x4e, 0x7a, 0x10, 0xb8, 0x06, 0x86, 0x3c, 0xcd, 0x56, 0xd6, 0x15, 0xbf, 0x6e, 0x3e, 0x4f, 0x35, 0x5e, 0xca, 0xbc, 0xa5, 0x95, 0xa2, 0xdf, 0x2d, 0x1d, 0xaf, 0x59, 0xf9, - /* (2^162)P */ 0x69, 0xe5, 0xe2, 0xfa, 0xc9, 0x7f, 0xdd, 0x09, 0xf5, 0x6b, 0x4e, 0x2e, 0xbe, 0xb4, 0xbf, 0x3e, 0xb2, 0xf2, 0x81, 0x30, 0xe1, 0x07, 0xa8, 0x0d, 0x2b, 0xd2, 0x5a, 0x55, 0xbe, 0x4b, 0x86, 0x5d, 0xb0, 0x5e, 0x7c, 0x8f, 0xc1, 0x3c, 0x81, 0x4c, 0xf7, 0x6d, 0x7d, 0xe6, 0x4f, 0x8a, 0x85, 0xc2, 0x2f, 0x28, 0xef, 0x8c, 0x69, 0xc2, 0xc2, 0x1a, - /* (2^163)P */ 0xd9, 0xe4, 0x0e, 0x1e, 0xc2, 0xf7, 0x2f, 0x9f, 0xa1, 0x40, 0xfe, 0x46, 0x16, 0xaf, 0x2e, 0xd1, 0xec, 0x15, 0x9b, 0x61, 0x92, 0xce, 0xfc, 0x10, 0x43, 0x1d, 0x00, 0xf6, 0xbe, 0x20, 0x80, 0x80, 0x6f, 0x3c, 0x16, 0x94, 0x59, 0xba, 0x03, 0x53, 0x6e, 0xb6, 0xdd, 0x25, 0x7b, 0x86, 0xbf, 0x96, 0xf4, 0x2f, 0xa1, 0x96, 0x8d, 0xf9, 0xb3, 0x29, - /* (2^164)P */ 0x3b, 0x04, 0x60, 0x6e, 0xce, 0xab, 0xd2, 0x63, 0x18, 0x53, 0x88, 0x16, 0x4a, 0x6a, 0xab, 0x72, 0x03, 0x68, 0xa5, 0xd4, 0x0d, 0xb2, 0x82, 0x81, 0x1f, 0x2b, 0x5c, 0x75, 0xe8, 0xd2, 0x1d, 0x7f, 0xe7, 0x1b, 0x35, 0x02, 0xde, 0xec, 0xbd, 0xcb, 0xc7, 0x01, 0xd3, 0x95, 0x61, 0xfe, 0xb2, 0x7a, 0x66, 0x09, 0x4c, 0x6d, 0xfd, 0x39, 0xf7, 0x52, - /* (2^165)P */ 0x42, 0xc1, 0x5f, 0xf8, 0x35, 0x52, 0xc1, 0xfe, 0xc5, 0x11, 0x80, 0x1c, 0x11, 0x46, 0x31, 0x11, 0xbe, 0xd0, 0xc4, 0xb6, 0x07, 0x13, 0x38, 0xa0, 0x8d, 0x65, 0xf0, 0x56, 0x9e, 0x16, 0xbf, 0x9d, 0xcd, 0x51, 0x34, 0xf9, 0x08, 0x48, 0x7b, 0x76, 0x0c, 0x7b, 0x30, 0x07, 0xa8, 0x76, 0xaf, 0xa3, 0x29, 0x38, 0xb0, 0x58, 0xde, 0x72, 0x4b, 0x45, - /* (2^166)P */ 0xd4, 0x16, 0xa7, 0xc0, 0xb4, 0x9f, 0xdf, 0x1a, 0x37, 0xc8, 0x35, 0xed, 0xc5, 0x85, 0x74, 0x64, 0x09, 0x22, 0xef, 0xe9, 0x0c, 0xaf, 0x12, 0x4c, 0x9e, 0xf8, 0x47, 0x56, 0xe0, 0x7f, 0x4e, 0x24, 0x6b, 0x0c, 0xe7, 0xad, 0xc6, 0x47, 0x1d, 0xa4, 0x0d, 0x86, 0x89, 0x65, 0xe8, 0x5f, 0x71, 0xc7, 0xe9, 0xcd, 0xec, 0x6c, 0x62, 0xc7, 0xe3, 0xb3, - /* (2^167)P */ 0xb5, 0xea, 0x86, 0xe3, 0x15, 0x18, 0x3f, 0x6d, 0x7b, 0x05, 0x95, 0x15, 0x53, 0x26, 0x1c, 0xeb, 0xbe, 0x7e, 0x16, 0x42, 0x4b, 0xa2, 0x3d, 0xdd, 0x0e, 0xff, 0xba, 0x67, 0xb5, 0xae, 0x7a, 0x17, 0xde, 0x23, 0xad, 0x14, 0xcc, 0xd7, 0xaf, 0x57, 0x01, 0xe0, 0xdd, 0x48, 0xdd, 0xd7, 0xe3, 0xdf, 0xe9, 0x2d, 0xda, 0x67, 0xa4, 0x9f, 0x29, 0x04, - /* (2^168)P */ 0x16, 0x53, 0xe6, 0x9c, 0x4e, 0xe5, 0x1e, 0x70, 0x81, 0x25, 0x02, 0x9b, 0x47, 0x6d, 0xd2, 0x08, 0x73, 0xbe, 0x0a, 0xf1, 0x7b, 0xeb, 0x24, 0xeb, 0x38, 0x23, 0x5c, 0xb6, 0x3e, 0xce, 0x1e, 0xe3, 0xbc, 0x82, 0x35, 0x1f, 0xaf, 0x3a, 0x3a, 0xe5, 0x4e, 0xc1, 0xca, 0xbf, 0x47, 0xb4, 0xbb, 0xbc, 0x5f, 0xea, 0xc6, 0xca, 0xf3, 0xa0, 0xa2, 0x73, - /* (2^169)P */ 0xef, 0xa4, 0x7a, 0x4e, 0xe4, 0xc7, 0xb6, 0x43, 0x2e, 0xa5, 0xe4, 0xa5, 0xba, 0x1e, 0xa5, 0xfe, 0x9e, 0xce, 0xa9, 0x80, 0x04, 0xcb, 0x4f, 0xd8, 0x74, 0x05, 0x48, 0xfa, 0x99, 0x11, 0x5d, 0x97, 0x3b, 0x07, 0x0d, 0xdd, 0xe6, 0xb1, 0x74, 0x87, 0x1a, 0xd3, 0x26, 0xb7, 0x8f, 0xe1, 0x63, 0x3d, 0xec, 0x53, 0x93, 0xb0, 0x81, 0x78, 0x34, 0xa4, - /* (2^170)P */ 0xe1, 0xe7, 0xd4, 0x58, 0x9d, 0x0e, 0x8b, 0x65, 0x66, 0x37, 0x16, 0x48, 0x6f, 0xaa, 0x42, 0x37, 0x77, 0xad, 0xb1, 0x56, 0x48, 0xdf, 0x65, 0x36, 0x30, 0xb8, 0x00, 0x12, 0xd8, 0x32, 0x28, 0x7f, 0xc1, 0x71, 0xeb, 0x93, 0x0f, 0x48, 0x04, 0xe1, 0x5a, 0x6a, 0x96, 0xc1, 0xca, 0x89, 0x6d, 0x1b, 0x82, 0x4c, 0x18, 0x6d, 0x55, 0x4b, 0xea, 0xfd, - /* (2^171)P */ 0x62, 0x1a, 0x53, 0xb4, 0xb1, 0xbe, 0x6f, 0x15, 0x18, 0x88, 0xd4, 0x66, 0x61, 0xc7, 0x12, 0x69, 0x02, 0xbd, 0x03, 0x23, 0x2b, 0xef, 0xf9, 0x54, 0xa4, 0x85, 0xa8, 0xe3, 0xb7, 0xbd, 0xa9, 0xa3, 0xf3, 0x2a, 0xdd, 0xf1, 0xd4, 0x03, 0x0f, 0xa9, 0xa1, 0xd8, 0xa3, 0xcd, 0xb2, 0x71, 0x90, 0x4b, 0x35, 0x62, 0xf2, 0x2f, 0xce, 0x67, 0x1f, 0xaa, - /* (2^172)P */ 0x9e, 0x1e, 0xcd, 0x43, 0x7e, 0x87, 0x37, 0x94, 0x3a, 0x97, 0x4c, 0x7e, 0xee, 0xc9, 0x37, 0x85, 0xf1, 0xd9, 0x4f, 0xbf, 0xf9, 0x6f, 0x39, 0x9a, 0x39, 0x87, 0x2e, 0x25, 0x84, 0x42, 0xc3, 0x80, 0xcb, 0x07, 0x22, 0xae, 0x30, 0xd5, 0x50, 0xa1, 0x23, 0xcc, 0x31, 0x81, 0x9d, 0xf1, 0x30, 0xd9, 0x2b, 0x73, 0x41, 0x16, 0x50, 0xab, 0x2d, 0xa2, - /* (2^173)P */ 0xa4, 0x69, 0x4f, 0xa1, 0x4e, 0xb9, 0xbf, 0x14, 0xe8, 0x2b, 0x04, 0x93, 0xb7, 0x6e, 0x9f, 0x7d, 0x73, 0x0a, 0xc5, 0x14, 0xb8, 0xde, 0x8c, 0xc1, 0xfe, 0xc0, 0xa7, 0xa4, 0xcc, 0x42, 0x42, 0x81, 0x15, 0x65, 0x8a, 0x80, 0xb9, 0xde, 0x1f, 0x60, 0x33, 0x0e, 0xcb, 0xfc, 0xe0, 0xdb, 0x83, 0xa1, 0xe5, 0xd0, 0x16, 0x86, 0x2c, 0xe2, 0x87, 0xed, - /* (2^174)P */ 0x7a, 0xc0, 0xeb, 0x6b, 0xf6, 0x0d, 0x4c, 0x6d, 0x1e, 0xdb, 0xab, 0xe7, 0x19, 0x45, 0xc6, 0xe3, 0xb2, 0x06, 0xbb, 0xbc, 0x70, 0x99, 0x83, 0x33, 0xeb, 0x28, 0xc8, 0x77, 0xf6, 0x4d, 0x01, 0xb7, 0x59, 0xa0, 0xd2, 0xb3, 0x2a, 0x72, 0x30, 0xe7, 0x11, 0x39, 0xb6, 0x41, 0x29, 0x65, 0x5a, 0x14, 0xb9, 0x86, 0x08, 0xe0, 0x7d, 0x32, 0x8c, 0xf0, - /* (2^175)P */ 0x5c, 0x11, 0x30, 0x9e, 0x05, 0x27, 0xf5, 0x45, 0x0f, 0xb3, 0xc9, 0x75, 0xc3, 0xd7, 0xe1, 0x82, 0x3b, 0x8e, 0x87, 0x23, 0x00, 0x15, 0x19, 0x07, 0xd9, 0x21, 0x53, 0xc7, 0xf1, 0xa3, 0xbf, 0x70, 0x64, 0x15, 0x18, 0xca, 0x23, 0x9e, 0xd3, 0x08, 0xc3, 0x2a, 0x8b, 0xe5, 0x83, 0x04, 0x89, 0x14, 0xfd, 0x28, 0x25, 0x1c, 0xe3, 0x26, 0xa7, 0x22, - /* (2^176)P */ 0xdc, 0xd4, 0x75, 0x60, 0x99, 0x94, 0xea, 0x09, 0x8e, 0x8a, 0x3c, 0x1b, 0xf9, 0xbd, 0x33, 0x0d, 0x51, 0x3d, 0x12, 0x6f, 0x4e, 0x72, 0xe0, 0x17, 0x20, 0xe9, 0x75, 0xe6, 0x3a, 0xb2, 0x13, 0x83, 0x4e, 0x7a, 0x08, 0x9e, 0xd1, 0x04, 0x5f, 0x6b, 0x42, 0x0b, 0x76, 0x2a, 0x2d, 0x77, 0x53, 0x6c, 0x65, 0x6d, 0x8e, 0x25, 0x3c, 0xb6, 0x8b, 0x69, - /* (2^177)P */ 0xb9, 0x49, 0x28, 0xd0, 0xdc, 0x6c, 0x8f, 0x4c, 0xc9, 0x14, 0x8a, 0x38, 0xa3, 0xcb, 0xc4, 0x9d, 0x53, 0xcf, 0xe9, 0xe3, 0xcf, 0xe0, 0xb1, 0xf2, 0x1b, 0x4c, 0x7f, 0x83, 0x2a, 0x7a, 0xe9, 0x8b, 0x3b, 0x86, 0x61, 0x30, 0xe9, 0x99, 0xbd, 0xba, 0x19, 0x6e, 0x65, 0x2a, 0x12, 0x3e, 0x9c, 0xa8, 0xaf, 0xc3, 0xcf, 0xf8, 0x1f, 0x77, 0x86, 0xea, - /* (2^178)P */ 0x30, 0xde, 0xe7, 0xff, 0x54, 0xf7, 0xa2, 0x59, 0xf6, 0x0b, 0xfb, 0x7a, 0xf2, 0x39, 0xf0, 0xdb, 0x39, 0xbc, 0xf0, 0xfa, 0x60, 0xeb, 0x6b, 0x4f, 0x47, 0x17, 0xc8, 0x00, 0x65, 0x6d, 0x25, 0x1c, 0xd0, 0x48, 0x56, 0x53, 0x45, 0x11, 0x30, 0x02, 0x49, 0x20, 0x27, 0xac, 0xf2, 0x4c, 0xac, 0x64, 0x3d, 0x52, 0xb8, 0x89, 0xe0, 0x93, 0x16, 0x0f, - /* (2^179)P */ 0x84, 0x09, 0xba, 0x40, 0xb2, 0x2f, 0xa3, 0xa8, 0xc2, 0xba, 0x46, 0x33, 0x05, 0x9d, 0x62, 0xad, 0xa1, 0x3c, 0x33, 0xef, 0x0d, 0xeb, 0xf0, 0x77, 0x11, 0x5a, 0xb0, 0x21, 0x9c, 0xdf, 0x55, 0x24, 0x25, 0x35, 0x51, 0x61, 0x92, 0xf0, 0xb1, 0xce, 0xf5, 0xd4, 0x7b, 0x6c, 0x21, 0x9d, 0x56, 0x52, 0xf8, 0xa1, 0x4c, 0xe9, 0x27, 0x55, 0xac, 0x91, - /* (2^180)P */ 0x03, 0x3e, 0x30, 0xd2, 0x0a, 0xfa, 0x7d, 0x82, 0x3d, 0x1f, 0x8b, 0xcb, 0xb6, 0x04, 0x5c, 0xcc, 0x8b, 0xda, 0xe2, 0x68, 0x74, 0x08, 0x8c, 0x44, 0x83, 0x57, 0x6d, 0x6f, 0x80, 0xb0, 0x7e, 0xa9, 0x82, 0x91, 0x7b, 0x4c, 0x37, 0x97, 0xd1, 0x63, 0xd1, 0xbd, 0x45, 0xe6, 0x8a, 0x86, 0xd6, 0x89, 0x54, 0xfd, 0xd2, 0xb1, 0xd7, 0x54, 0xad, 0xaf, - /* (2^181)P */ 0x8b, 0x33, 0x62, 0x49, 0x9f, 0x63, 0xf9, 0x87, 0x42, 0x58, 0xbf, 0xb3, 0xe6, 0x68, 0x02, 0x60, 0x5c, 0x76, 0x62, 0xf7, 0x61, 0xd7, 0x36, 0x31, 0xf7, 0x9c, 0xb5, 0xe5, 0x13, 0x6c, 0xea, 0x78, 0xae, 0xcf, 0xde, 0xbf, 0xb6, 0xeb, 0x4f, 0xc8, 0x2a, 0xb4, 0x9a, 0x9f, 0xf3, 0xd1, 0x6a, 0xec, 0x0c, 0xbd, 0x85, 0x98, 0x40, 0x06, 0x1c, 0x2a, - /* (2^182)P */ 0x74, 0x3b, 0xe7, 0x81, 0xd5, 0xae, 0x54, 0x56, 0x03, 0xe8, 0x97, 0x16, 0x76, 0xcf, 0x24, 0x96, 0x96, 0x5b, 0xcc, 0x09, 0xab, 0x23, 0x6f, 0x54, 0xae, 0x8f, 0xe4, 0x12, 0xcb, 0xfd, 0xbc, 0xac, 0x93, 0x45, 0x3d, 0x68, 0x08, 0x22, 0x59, 0xc6, 0xf0, 0x47, 0x19, 0x8c, 0x79, 0x93, 0x1e, 0x0e, 0x30, 0xb0, 0x94, 0xfb, 0x17, 0x1d, 0x5a, 0x12, - /* (2^183)P */ 0x85, 0xff, 0x40, 0x18, 0x85, 0xff, 0x44, 0x37, 0x69, 0x23, 0x4d, 0x34, 0xe1, 0xeb, 0xa3, 0x1b, 0x55, 0x40, 0xc1, 0x64, 0xf4, 0xd4, 0x13, 0x0a, 0x9f, 0xb9, 0x19, 0xfc, 0x88, 0x7d, 0xc0, 0x72, 0xcf, 0x69, 0x2f, 0xd2, 0x0c, 0x82, 0x0f, 0xda, 0x08, 0xba, 0x0f, 0xaa, 0x3b, 0xe9, 0xe5, 0x83, 0x7a, 0x06, 0xe8, 0x1b, 0x38, 0x43, 0xc3, 0x54, - /* (2^184)P */ 0x14, 0xaa, 0xb3, 0x6e, 0xe6, 0x28, 0xee, 0xc5, 0x22, 0x6c, 0x7c, 0xf9, 0xa8, 0x71, 0xcc, 0xfe, 0x68, 0x7e, 0xd3, 0xb8, 0x37, 0x96, 0xca, 0x0b, 0xd9, 0xb6, 0x06, 0xa9, 0xf6, 0x71, 0xe8, 0x31, 0xf7, 0xd8, 0xf1, 0x5d, 0xab, 0xb9, 0xf0, 0x5c, 0x98, 0xcf, 0x22, 0xa2, 0x2a, 0xf6, 0xd0, 0x59, 0xf0, 0x9d, 0xd9, 0x6a, 0x4f, 0x59, 0x57, 0xad, - /* (2^185)P */ 0xd7, 0x2b, 0x3d, 0x38, 0x4c, 0x2e, 0x23, 0x4d, 0x49, 0xa2, 0x62, 0x62, 0xf9, 0x0f, 0xde, 0x08, 0xf3, 0x86, 0x71, 0xb6, 0xc7, 0xf9, 0x85, 0x9c, 0x33, 0xa1, 0xcf, 0x16, 0xaa, 0x60, 0xb9, 0xb7, 0xea, 0xed, 0x01, 0x1c, 0x59, 0xdb, 0x3f, 0x3f, 0x97, 0x2e, 0xf0, 0x09, 0x9f, 0x10, 0x85, 0x5f, 0x53, 0x39, 0xf3, 0x13, 0x40, 0x56, 0x95, 0xf9, - /* (2^186)P */ 0xb4, 0xe3, 0xda, 0xc6, 0x1f, 0x78, 0x8e, 0xac, 0xd4, 0x20, 0x1d, 0xa0, 0xbf, 0x4c, 0x09, 0x16, 0xa7, 0x30, 0xb5, 0x8d, 0x9e, 0xa1, 0x5f, 0x6d, 0x52, 0xf4, 0x71, 0xb6, 0x32, 0x2d, 0x21, 0x51, 0xc6, 0xfc, 0x2f, 0x08, 0xf4, 0x13, 0x6c, 0x55, 0xba, 0x72, 0x81, 0x24, 0x49, 0x0e, 0x4f, 0x06, 0x36, 0x39, 0x6a, 0xc5, 0x81, 0xfc, 0xeb, 0xb2, - /* (2^187)P */ 0x7d, 0x8d, 0xc8, 0x6c, 0xea, 0xb4, 0xb9, 0xe8, 0x40, 0xc9, 0x69, 0xc9, 0x30, 0x05, 0xfd, 0x34, 0x46, 0xfd, 0x94, 0x05, 0x16, 0xf5, 0x4b, 0x13, 0x3d, 0x24, 0x1a, 0xd6, 0x64, 0x2b, 0x9c, 0xe2, 0xa5, 0xd9, 0x98, 0xe0, 0xe8, 0xf4, 0xbc, 0x2c, 0xbd, 0xa2, 0x56, 0xe3, 0x9e, 0x14, 0xdb, 0xbf, 0x05, 0xbf, 0x9a, 0x13, 0x5d, 0xf7, 0x91, 0xa3, - /* (2^188)P */ 0x8b, 0xcb, 0x27, 0xf3, 0x15, 0x26, 0x05, 0x40, 0x0f, 0xa6, 0x15, 0x13, 0x71, 0x95, 0xa2, 0xc6, 0x38, 0x04, 0x67, 0xf8, 0x9a, 0x83, 0x06, 0xaa, 0x25, 0x36, 0x72, 0x01, 0x6f, 0x74, 0x5f, 0xe5, 0x6e, 0x44, 0x99, 0xce, 0x13, 0xbc, 0x82, 0xc2, 0x0d, 0xa4, 0x98, 0x50, 0x38, 0xf3, 0xa2, 0xc5, 0xe5, 0x24, 0x1f, 0x6f, 0x56, 0x3e, 0x07, 0xb2, - /* (2^189)P */ 0xbd, 0x0f, 0x32, 0x60, 0x07, 0xb1, 0xd7, 0x0b, 0x11, 0x07, 0x57, 0x02, 0x89, 0xe8, 0x8b, 0xe8, 0x5a, 0x1f, 0xee, 0x54, 0x6b, 0xff, 0xb3, 0x04, 0x07, 0x57, 0x13, 0x0b, 0x94, 0xa8, 0x4d, 0x81, 0xe2, 0x17, 0x16, 0x45, 0xd4, 0x4b, 0xf7, 0x7e, 0x64, 0x66, 0x20, 0xe8, 0x0b, 0x26, 0xfd, 0xa9, 0x8a, 0x47, 0x52, 0x89, 0x14, 0xd0, 0xd1, 0xa1, - /* (2^190)P */ 0xdc, 0x03, 0xe6, 0x20, 0x44, 0x47, 0x8f, 0x04, 0x16, 0x24, 0x22, 0xc1, 0x55, 0x5c, 0xbe, 0x43, 0xc3, 0x92, 0xc5, 0x54, 0x3d, 0x5d, 0xd1, 0x05, 0x9c, 0xc6, 0x7c, 0xbf, 0x23, 0x84, 0x1a, 0xba, 0x4f, 0x1f, 0xfc, 0xa1, 0xae, 0x1a, 0x64, 0x02, 0x51, 0xf1, 0xcb, 0x7a, 0x20, 0xce, 0xb2, 0x34, 0x3c, 0xca, 0xe0, 0xe4, 0xba, 0x22, 0xd4, 0x7b, - /* (2^191)P */ 0xca, 0xfd, 0xca, 0xd7, 0xde, 0x61, 0xae, 0xf0, 0x79, 0x0c, 0x20, 0xab, 0xbc, 0x6f, 0x4d, 0x61, 0xf0, 0xc7, 0x9c, 0x8d, 0x4b, 0x52, 0xf3, 0xb9, 0x48, 0x63, 0x0b, 0xb6, 0xd2, 0x25, 0x9a, 0x96, 0x72, 0xc1, 0x6b, 0x0c, 0xb5, 0xfb, 0x71, 0xaa, 0xad, 0x47, 0x5b, 0xe7, 0xc0, 0x0a, 0x55, 0xb2, 0xd4, 0x16, 0x2f, 0xb1, 0x01, 0xfd, 0xce, 0x27, - /* (2^192)P */ 0x64, 0x11, 0x4b, 0xab, 0x57, 0x09, 0xc6, 0x49, 0x4a, 0x37, 0xc3, 0x36, 0xc4, 0x7b, 0x81, 0x1f, 0x42, 0xed, 0xbb, 0xe0, 0xa0, 0x8d, 0x51, 0xe6, 0xca, 0x8b, 0xb9, 0xcd, 0x99, 0x2d, 0x91, 0x53, 0xa9, 0x47, 0xcb, 0x32, 0xc7, 0xa4, 0x92, 0xec, 0x46, 0x74, 0x44, 0x6d, 0x71, 0x9f, 0x6d, 0x0c, 0x69, 0xa4, 0xf8, 0xbe, 0x9f, 0x7f, 0xa0, 0xd7, - /* (2^193)P */ 0x5f, 0x33, 0xb6, 0x91, 0xc8, 0xa5, 0x3f, 0x5d, 0x7f, 0x38, 0x6e, 0x74, 0x20, 0x4a, 0xd6, 0x2b, 0x98, 0x2a, 0x41, 0x4b, 0x83, 0x64, 0x0b, 0x92, 0x7a, 0x06, 0x1e, 0xc6, 0x2c, 0xf6, 0xe4, 0x91, 0xe5, 0xb1, 0x2e, 0x6e, 0x4e, 0xa8, 0xc8, 0x14, 0x32, 0x57, 0x44, 0x1c, 0xe4, 0xb9, 0x7f, 0x54, 0x51, 0x08, 0x81, 0xaa, 0x4e, 0xce, 0xa1, 0x5d, - /* (2^194)P */ 0x5c, 0xd5, 0x9b, 0x5e, 0x7c, 0xb5, 0xb1, 0x52, 0x73, 0x00, 0x41, 0x56, 0x79, 0x08, 0x7e, 0x07, 0x28, 0x06, 0xa6, 0xfb, 0x7f, 0x69, 0xbd, 0x7a, 0x3c, 0xae, 0x9f, 0x39, 0xbb, 0x54, 0xa2, 0x79, 0xb9, 0x0e, 0x7f, 0xbb, 0xe0, 0xe6, 0xb7, 0x27, 0x64, 0x38, 0x45, 0xdb, 0x84, 0xe4, 0x61, 0x72, 0x3f, 0xe2, 0x24, 0xfe, 0x7a, 0x31, 0x9a, 0xc9, - /* (2^195)P */ 0xa1, 0xd2, 0xa4, 0xee, 0x24, 0x96, 0xe5, 0x5b, 0x79, 0x78, 0x3c, 0x7b, 0x82, 0x3b, 0x8b, 0x58, 0x0b, 0xa3, 0x63, 0x2d, 0xbc, 0x75, 0x46, 0xe8, 0x83, 0x1a, 0xc0, 0x2a, 0x92, 0x61, 0xa8, 0x75, 0x37, 0x3c, 0xbf, 0x0f, 0xef, 0x8f, 0x6c, 0x97, 0x75, 0x10, 0x05, 0x7a, 0xde, 0x23, 0xe8, 0x2a, 0x35, 0xeb, 0x41, 0x64, 0x7d, 0xcf, 0xe0, 0x52, - /* (2^196)P */ 0x4a, 0xd0, 0x49, 0x93, 0xae, 0xf3, 0x24, 0x8c, 0xe1, 0x09, 0x98, 0x45, 0xd8, 0xb9, 0xfe, 0x8e, 0x8c, 0xa8, 0x2c, 0xc9, 0x9f, 0xce, 0x01, 0xdc, 0x38, 0x11, 0xab, 0x85, 0xb9, 0xe8, 0x00, 0x51, 0xfd, 0x82, 0xe1, 0x9b, 0x4e, 0xfc, 0xb5, 0x2a, 0x0f, 0x8b, 0xda, 0x4e, 0x02, 0xca, 0xcc, 0xe3, 0x91, 0xc4, 0xe0, 0xcf, 0x7b, 0xd6, 0xe6, 0x6a, - /* (2^197)P */ 0xfe, 0x11, 0xd7, 0xaa, 0xe3, 0x0c, 0x52, 0x2e, 0x04, 0xe0, 0xe0, 0x61, 0xc8, 0x05, 0xd7, 0x31, 0x4c, 0xc3, 0x9b, 0x2d, 0xce, 0x59, 0xbe, 0x12, 0xb7, 0x30, 0x21, 0xfc, 0x81, 0xb8, 0x5e, 0x57, 0x73, 0xd0, 0xad, 0x8e, 0x9e, 0xe4, 0xeb, 0xcd, 0xcf, 0xd2, 0x0f, 0x01, 0x35, 0x16, 0xed, 0x7a, 0x43, 0x8e, 0x42, 0xdc, 0xea, 0x4c, 0xa8, 0x7c, - /* (2^198)P */ 0x37, 0x26, 0xcc, 0x76, 0x0b, 0xe5, 0x76, 0xdd, 0x3e, 0x19, 0x3c, 0xc4, 0x6c, 0x7f, 0xd0, 0x03, 0xc1, 0xb8, 0x59, 0x82, 0xca, 0x36, 0xc1, 0xe4, 0xc8, 0xb2, 0x83, 0x69, 0x9c, 0xc5, 0x9d, 0x12, 0x82, 0x1c, 0xea, 0xb2, 0x84, 0x9f, 0xf3, 0x52, 0x6b, 0xbb, 0xd8, 0x81, 0x56, 0x83, 0x04, 0x66, 0x05, 0x22, 0x49, 0x37, 0x93, 0xb1, 0xfd, 0xd5, - /* (2^199)P */ 0xaf, 0x96, 0xbf, 0x03, 0xbe, 0xe6, 0x5d, 0x78, 0x19, 0xba, 0x37, 0x46, 0x0a, 0x2b, 0x52, 0x7c, 0xd8, 0x51, 0x9e, 0x3d, 0x29, 0x42, 0xdb, 0x0e, 0x31, 0x20, 0x94, 0xf8, 0x43, 0x9a, 0x2d, 0x22, 0xd3, 0xe3, 0xa1, 0x79, 0x68, 0xfb, 0x2d, 0x7e, 0xd6, 0x79, 0xda, 0x0b, 0xc6, 0x5b, 0x76, 0x68, 0xf0, 0xfe, 0x72, 0x59, 0xbb, 0xa1, 0x9c, 0x74, - /* (2^200)P */ 0x0a, 0xd9, 0xec, 0xc5, 0xbd, 0xf0, 0xda, 0xcf, 0x82, 0xab, 0x46, 0xc5, 0x32, 0x13, 0xdc, 0x5b, 0xac, 0xc3, 0x53, 0x9a, 0x7f, 0xef, 0xa5, 0x40, 0x5a, 0x1f, 0xc1, 0x12, 0x91, 0x54, 0x83, 0x6a, 0xb0, 0x9a, 0x85, 0x4d, 0xbf, 0x36, 0x8e, 0xd3, 0xa2, 0x2b, 0xe5, 0xd6, 0xc6, 0xe1, 0x58, 0x5b, 0x82, 0x9b, 0xc8, 0xf2, 0x03, 0xba, 0xf5, 0x92, - /* (2^201)P */ 0xfb, 0x21, 0x7e, 0xde, 0xe7, 0xb4, 0xc0, 0x56, 0x86, 0x3a, 0x5b, 0x78, 0xf8, 0xf0, 0xf4, 0xe7, 0x5c, 0x00, 0xd2, 0xd7, 0xd6, 0xf8, 0x75, 0x5e, 0x0f, 0x3e, 0xd1, 0x4b, 0x77, 0xd8, 0xad, 0xb0, 0xc9, 0x8b, 0x59, 0x7d, 0x30, 0x76, 0x64, 0x7a, 0x76, 0xd9, 0x51, 0x69, 0xfc, 0xbd, 0x8e, 0xb5, 0x55, 0xe0, 0xd2, 0x07, 0x15, 0xa9, 0xf7, 0xa4, - /* (2^202)P */ 0xaa, 0x2d, 0x2f, 0x2b, 0x3c, 0x15, 0xdd, 0xcd, 0xe9, 0x28, 0x82, 0x4f, 0xa2, 0xaa, 0x31, 0x48, 0xcc, 0xfa, 0x07, 0x73, 0x8a, 0x34, 0x74, 0x0d, 0xab, 0x1a, 0xca, 0xd2, 0xbf, 0x3a, 0xdb, 0x1a, 0x5f, 0x50, 0x62, 0xf4, 0x6b, 0x83, 0x38, 0x43, 0x96, 0xee, 0x6b, 0x39, 0x1e, 0xf0, 0x17, 0x80, 0x1e, 0x9b, 0xed, 0x2b, 0x2f, 0xcc, 0x65, 0xf7, - /* (2^203)P */ 0x03, 0xb3, 0x23, 0x9c, 0x0d, 0xd1, 0xeb, 0x7e, 0x34, 0x17, 0x8a, 0x4c, 0xde, 0x54, 0x39, 0xc4, 0x11, 0x82, 0xd3, 0xa4, 0x00, 0x32, 0x95, 0x9c, 0xa6, 0x64, 0x76, 0x6e, 0xd6, 0x53, 0x27, 0xb4, 0x6a, 0x14, 0x8c, 0x54, 0xf6, 0x58, 0x9e, 0x22, 0x4a, 0x55, 0x18, 0x77, 0xd0, 0x08, 0x6b, 0x19, 0x8a, 0xb5, 0xe7, 0x19, 0xb8, 0x60, 0x92, 0xb1, - /* (2^204)P */ 0x66, 0xec, 0xf3, 0x12, 0xde, 0x67, 0x7f, 0xd4, 0x5b, 0xf6, 0x70, 0x64, 0x0a, 0xb5, 0xc2, 0xf9, 0xb3, 0x64, 0xab, 0x56, 0x46, 0xc7, 0x93, 0xc2, 0x8b, 0x2d, 0xd0, 0xd6, 0x39, 0x3b, 0x1f, 0xcd, 0xb3, 0xac, 0xcc, 0x2c, 0x27, 0x6a, 0xbc, 0xb3, 0x4b, 0xa8, 0x3c, 0x69, 0x20, 0xe2, 0x18, 0x35, 0x17, 0xe1, 0x8a, 0xd3, 0x11, 0x74, 0xaa, 0x4d, - /* (2^205)P */ 0x96, 0xc4, 0x16, 0x7e, 0xfd, 0xf5, 0xd0, 0x7d, 0x1f, 0x32, 0x1b, 0xdb, 0xa6, 0xfd, 0x51, 0x75, 0x4d, 0xd7, 0x00, 0xe5, 0x7f, 0x58, 0x5b, 0xeb, 0x4b, 0x6a, 0x78, 0xfe, 0xe5, 0xd6, 0x8f, 0x99, 0x17, 0xca, 0x96, 0x45, 0xf7, 0x52, 0xdf, 0x84, 0x06, 0x77, 0xb9, 0x05, 0x63, 0x5d, 0xe9, 0x91, 0xb1, 0x4b, 0x82, 0x5a, 0xdb, 0xd7, 0xca, 0x69, - /* (2^206)P */ 0x02, 0xd3, 0x38, 0x38, 0x87, 0xea, 0xbd, 0x9f, 0x11, 0xca, 0xf3, 0x21, 0xf1, 0x9b, 0x35, 0x97, 0x98, 0xff, 0x8e, 0x6d, 0x3d, 0xd6, 0xb2, 0xfa, 0x68, 0xcb, 0x7e, 0x62, 0x85, 0xbb, 0xc7, 0x5d, 0xee, 0x32, 0x30, 0x2e, 0x71, 0x96, 0x63, 0x43, 0x98, 0xc4, 0xa7, 0xde, 0x60, 0xb2, 0xd9, 0x43, 0x4a, 0xfa, 0x97, 0x2d, 0x5f, 0x21, 0xd4, 0xfe, - /* (2^207)P */ 0x3b, 0x20, 0x29, 0x07, 0x07, 0xb5, 0x78, 0xc3, 0xc7, 0xab, 0x56, 0xba, 0x40, 0xde, 0x1d, 0xcf, 0xc3, 0x00, 0x56, 0x21, 0x0c, 0xc8, 0x42, 0xd9, 0x0e, 0xcd, 0x02, 0x7c, 0x07, 0xb9, 0x11, 0xd7, 0x96, 0xaf, 0xff, 0xad, 0xc5, 0xba, 0x30, 0x6d, 0x82, 0x3a, 0xbf, 0xef, 0x7b, 0xf7, 0x0a, 0x74, 0xbd, 0x31, 0x0c, 0xe4, 0xec, 0x1a, 0xe5, 0xc5, - /* (2^208)P */ 0xcc, 0xf2, 0x28, 0x16, 0x12, 0xbf, 0xef, 0x85, 0xbc, 0xf7, 0xcb, 0x9f, 0xdb, 0xa8, 0xb2, 0x49, 0x53, 0x48, 0xa8, 0x24, 0xa8, 0x68, 0x8d, 0xbb, 0x21, 0x0a, 0x5a, 0xbd, 0xb2, 0x91, 0x61, 0x47, 0xc4, 0x43, 0x08, 0xa6, 0x19, 0xef, 0x8e, 0x88, 0x39, 0xc6, 0x33, 0x30, 0xf3, 0x0e, 0xc5, 0x92, 0x66, 0xd6, 0xfe, 0xc5, 0x12, 0xd9, 0x4c, 0x2d, - /* (2^209)P */ 0x30, 0x34, 0x07, 0xbf, 0x9c, 0x5a, 0x4e, 0x65, 0xf1, 0x39, 0x35, 0x38, 0xae, 0x7b, 0x55, 0xac, 0x6a, 0x92, 0x24, 0x7e, 0x50, 0xd3, 0xba, 0x78, 0x51, 0xfe, 0x4d, 0x32, 0x05, 0x11, 0xf5, 0x52, 0xf1, 0x31, 0x45, 0x39, 0x98, 0x7b, 0x28, 0x56, 0xc3, 0x5d, 0x4f, 0x07, 0x6f, 0x84, 0xb8, 0x1a, 0x58, 0x0b, 0xc4, 0x7c, 0xc4, 0x8d, 0x32, 0x8e, - /* (2^210)P */ 0x7e, 0xaf, 0x98, 0xce, 0xc5, 0x2b, 0x9d, 0xf6, 0xfa, 0x2c, 0xb6, 0x2a, 0x5a, 0x1d, 0xc0, 0x24, 0x8d, 0xa4, 0xce, 0xb1, 0x12, 0x01, 0xf9, 0x79, 0xc6, 0x79, 0x38, 0x0c, 0xd4, 0x07, 0xc9, 0xf7, 0x37, 0xa1, 0x0b, 0xfe, 0x72, 0xec, 0x5d, 0xd6, 0xb0, 0x1c, 0x70, 0xbe, 0x70, 0x01, 0x13, 0xe0, 0x86, 0x95, 0xc7, 0x2e, 0x12, 0x3b, 0xe6, 0xa6, - /* (2^211)P */ 0x24, 0x82, 0x67, 0xe0, 0x14, 0x7b, 0x56, 0x08, 0x38, 0x44, 0xdb, 0xa0, 0x3a, 0x05, 0x47, 0xb2, 0xc0, 0xac, 0xd1, 0xcc, 0x3f, 0x82, 0xb8, 0x8a, 0x88, 0xbc, 0xf5, 0x33, 0xa1, 0x35, 0x0f, 0xf6, 0xe2, 0xef, 0x6c, 0xf7, 0x37, 0x9e, 0xe8, 0x10, 0xca, 0xb0, 0x8e, 0x80, 0x86, 0x00, 0x23, 0xd0, 0x4a, 0x76, 0x9f, 0xf7, 0x2c, 0x52, 0x15, 0x0e, - /* (2^212)P */ 0x5e, 0x49, 0xe1, 0x2c, 0x9a, 0x01, 0x76, 0xa6, 0xb3, 0x07, 0x5b, 0xa4, 0x07, 0xef, 0x1d, 0xc3, 0x6a, 0xbb, 0x64, 0xbe, 0x71, 0x15, 0x6e, 0x32, 0x31, 0x46, 0x9a, 0x9e, 0x8f, 0x45, 0x73, 0xce, 0x0b, 0x94, 0x1a, 0x52, 0x07, 0xf4, 0x50, 0x30, 0x49, 0x53, 0x50, 0xfb, 0x71, 0x1f, 0x5a, 0x03, 0xa9, 0x76, 0xf2, 0x8f, 0x42, 0xff, 0xed, 0xed, - /* (2^213)P */ 0xed, 0x08, 0xdb, 0x91, 0x1c, 0xee, 0xa2, 0xb4, 0x47, 0xa2, 0xfa, 0xcb, 0x03, 0xd1, 0xff, 0x8c, 0xad, 0x64, 0x50, 0x61, 0xcd, 0xfc, 0x88, 0xa0, 0x31, 0x95, 0x30, 0xb9, 0x58, 0xdd, 0xd7, 0x43, 0xe4, 0x46, 0xc2, 0x16, 0xd9, 0x72, 0x4a, 0x56, 0x51, 0x70, 0x85, 0xf1, 0xa1, 0x80, 0x40, 0xd5, 0xba, 0x67, 0x81, 0xda, 0xcd, 0x03, 0xea, 0x51, - /* (2^214)P */ 0x42, 0x50, 0xf0, 0xef, 0x37, 0x61, 0x72, 0x85, 0xe1, 0xf1, 0xff, 0x6f, 0x3d, 0xe8, 0x7b, 0x21, 0x5c, 0xe5, 0x50, 0x03, 0xde, 0x00, 0xc1, 0xf7, 0x3a, 0x55, 0x12, 0x1c, 0x9e, 0x1e, 0xce, 0xd1, 0x2f, 0xaf, 0x05, 0x70, 0x5b, 0x47, 0xf2, 0x04, 0x7a, 0x89, 0xbc, 0x78, 0xa6, 0x65, 0x6c, 0xaa, 0x3c, 0xa2, 0x3c, 0x8b, 0x5c, 0xa9, 0x22, 0x48, - /* (2^215)P */ 0x7e, 0x8c, 0x8f, 0x2f, 0x60, 0xe3, 0x5a, 0x94, 0xd4, 0xce, 0xdd, 0x9d, 0x83, 0x3b, 0x77, 0x78, 0x43, 0x1d, 0xfd, 0x8f, 0xc8, 0xe8, 0x02, 0x90, 0xab, 0xf6, 0xc9, 0xfc, 0xf1, 0x63, 0xaa, 0x5f, 0x42, 0xf1, 0x78, 0x34, 0x64, 0x16, 0x75, 0x9c, 0x7d, 0xd0, 0xe4, 0x74, 0x5a, 0xa8, 0xfb, 0xcb, 0xac, 0x20, 0xa3, 0xc2, 0xa6, 0x20, 0xf8, 0x1b, - /* (2^216)P */ 0x00, 0x4f, 0x1e, 0x56, 0xb5, 0x34, 0xb2, 0x87, 0x31, 0xe5, 0xee, 0x8d, 0xf1, 0x41, 0x67, 0xb7, 0x67, 0x3a, 0x54, 0x86, 0x5c, 0xf0, 0x0b, 0x37, 0x2f, 0x1b, 0x92, 0x5d, 0x58, 0x93, 0xdc, 0xd8, 0x58, 0xcc, 0x9e, 0x67, 0xd0, 0x97, 0x3a, 0xaf, 0x49, 0x39, 0x2d, 0x3b, 0xd8, 0x98, 0xfb, 0x76, 0x6b, 0xe7, 0xaf, 0xc3, 0x45, 0x44, 0x53, 0x94, - /* (2^217)P */ 0x30, 0xbd, 0x90, 0x75, 0xd3, 0xbd, 0x3b, 0x58, 0x27, 0x14, 0x9f, 0x6b, 0xd4, 0x31, 0x99, 0xcd, 0xde, 0x3a, 0x21, 0x1e, 0xb4, 0x02, 0xe4, 0x33, 0x04, 0x02, 0xb0, 0x50, 0x66, 0x68, 0x90, 0xdd, 0x7b, 0x69, 0x31, 0xd9, 0xcf, 0x68, 0x73, 0xf1, 0x60, 0xdd, 0xc8, 0x1d, 0x5d, 0xe3, 0xd6, 0x5b, 0x2a, 0xa4, 0xea, 0xc4, 0x3f, 0x08, 0xcd, 0x9c, - /* (2^218)P */ 0x6b, 0x1a, 0xbf, 0x55, 0xc1, 0x1b, 0x0c, 0x05, 0x09, 0xdf, 0xf5, 0x5e, 0xa3, 0x77, 0x95, 0xe9, 0xdf, 0x19, 0xdd, 0xc7, 0x94, 0xcb, 0x06, 0x73, 0xd0, 0x88, 0x02, 0x33, 0x94, 0xca, 0x7a, 0x2f, 0x8e, 0x3d, 0x72, 0x61, 0x2d, 0x4d, 0xa6, 0x61, 0x1f, 0x32, 0x5e, 0x87, 0x53, 0x36, 0x11, 0x15, 0x20, 0xb3, 0x5a, 0x57, 0x51, 0x93, 0x20, 0xd8, - /* (2^219)P */ 0xb7, 0x56, 0xf4, 0xab, 0x7d, 0x0c, 0xfb, 0x99, 0x1a, 0x30, 0x29, 0xb0, 0x75, 0x2a, 0xf8, 0x53, 0x71, 0x23, 0xbd, 0xa7, 0xd8, 0x0a, 0xe2, 0x27, 0x65, 0xe9, 0x74, 0x26, 0x98, 0x4a, 0x69, 0x19, 0xb2, 0x4d, 0x0a, 0x17, 0x98, 0xb2, 0xa9, 0x57, 0x4e, 0xf6, 0x86, 0xc8, 0x01, 0xa4, 0xc6, 0x98, 0xad, 0x5a, 0x90, 0x2c, 0x05, 0x46, 0x64, 0xb7, - /* (2^220)P */ 0x7b, 0x91, 0xdf, 0xfc, 0xf8, 0x1c, 0x8c, 0x15, 0x9e, 0xf7, 0xd5, 0xa8, 0xe8, 0xe7, 0xe3, 0xa3, 0xb0, 0x04, 0x74, 0xfa, 0x78, 0xfb, 0x26, 0xbf, 0x67, 0x42, 0xf9, 0x8c, 0x9b, 0xb4, 0x69, 0x5b, 0x02, 0x13, 0x6d, 0x09, 0x6c, 0xd6, 0x99, 0x61, 0x7b, 0x89, 0x4a, 0x67, 0x75, 0xa3, 0x98, 0x13, 0x23, 0x1d, 0x18, 0x24, 0x0e, 0xef, 0x41, 0x79, - /* (2^221)P */ 0x86, 0x33, 0xab, 0x08, 0xcb, 0xbf, 0x1e, 0x76, 0x3c, 0x0b, 0xbd, 0x30, 0xdb, 0xe9, 0xa3, 0x35, 0x87, 0x1b, 0xe9, 0x07, 0x00, 0x66, 0x7f, 0x3b, 0x35, 0x0c, 0x8a, 0x3f, 0x61, 0xbc, 0xe0, 0xae, 0xf6, 0xcc, 0x54, 0xe1, 0x72, 0x36, 0x2d, 0xee, 0x93, 0x24, 0xf8, 0xd7, 0xc5, 0xf9, 0xcb, 0xb0, 0xe5, 0x88, 0x0d, 0x23, 0x4b, 0x76, 0x15, 0xa2, - /* (2^222)P */ 0x37, 0xdb, 0x83, 0xd5, 0x6d, 0x06, 0x24, 0x37, 0x1b, 0x15, 0x85, 0x15, 0xe2, 0xc0, 0x4e, 0x02, 0xa9, 0x6d, 0x0a, 0x3a, 0x94, 0x4a, 0x6f, 0x49, 0x00, 0x01, 0x72, 0xbb, 0x60, 0x14, 0x35, 0xae, 0xb4, 0xc6, 0x01, 0x0a, 0x00, 0x9e, 0xc3, 0x58, 0xc5, 0xd1, 0x5e, 0x30, 0x73, 0x96, 0x24, 0x85, 0x9d, 0xf0, 0xf9, 0xec, 0x09, 0xd3, 0xe7, 0x70, - /* (2^223)P */ 0xf3, 0xbd, 0x96, 0x87, 0xe9, 0x71, 0xbd, 0xd6, 0xa2, 0x45, 0xeb, 0x0a, 0xcd, 0x2c, 0xf1, 0x72, 0xa6, 0x31, 0xa9, 0x6f, 0x09, 0xa1, 0x5e, 0xdd, 0xc8, 0x8d, 0x0d, 0xbc, 0x5a, 0x8d, 0xb1, 0x2c, 0x9a, 0xcc, 0x37, 0x74, 0xc2, 0xa9, 0x4e, 0xd6, 0xc0, 0x3c, 0xa0, 0x23, 0xb0, 0xa0, 0x77, 0x14, 0x80, 0x45, 0x71, 0x6a, 0x2d, 0x41, 0xc3, 0x82, - /* (2^224)P */ 0x37, 0x44, 0xec, 0x8a, 0x3e, 0xc1, 0x0c, 0xa9, 0x12, 0x9c, 0x08, 0x88, 0xcb, 0xd9, 0xf8, 0xba, 0x00, 0xd6, 0xc3, 0xdf, 0xef, 0x7a, 0x44, 0x7e, 0x25, 0x69, 0xc9, 0xc1, 0x46, 0xe5, 0x20, 0x9e, 0xcc, 0x0b, 0x05, 0x3e, 0xf4, 0x78, 0x43, 0x0c, 0xa6, 0x2f, 0xc1, 0xfa, 0x70, 0xb2, 0x3c, 0x31, 0x7a, 0x63, 0x58, 0xab, 0x17, 0xcf, 0x4c, 0x4f, - /* (2^225)P */ 0x2b, 0x08, 0x31, 0x59, 0x75, 0x8b, 0xec, 0x0a, 0xa9, 0x79, 0x70, 0xdd, 0xf1, 0x11, 0xc3, 0x11, 0x1f, 0xab, 0x37, 0xaa, 0x26, 0xea, 0x53, 0xc4, 0x79, 0xa7, 0x91, 0x00, 0xaa, 0x08, 0x42, 0xeb, 0x8b, 0x8b, 0xe8, 0xc3, 0x2f, 0xb8, 0x78, 0x90, 0x38, 0x0e, 0x8a, 0x42, 0x0c, 0x0f, 0xbf, 0x3e, 0xf8, 0xd8, 0x07, 0xcf, 0x6a, 0x34, 0xc9, 0xfa, - /* (2^226)P */ 0x11, 0xe0, 0x76, 0x4d, 0x23, 0xc5, 0xa6, 0xcc, 0x9f, 0x9a, 0x2a, 0xde, 0x3a, 0xb5, 0x92, 0x39, 0x19, 0x8a, 0xf1, 0x8d, 0xf9, 0x4d, 0xc9, 0xb4, 0x39, 0x9f, 0x57, 0xd8, 0x72, 0xab, 0x1d, 0x61, 0x6a, 0xb2, 0xff, 0x52, 0xba, 0x54, 0x0e, 0xfb, 0x83, 0x30, 0x8a, 0xf7, 0x3b, 0xf4, 0xd8, 0xae, 0x1a, 0x94, 0x3a, 0xec, 0x63, 0xfe, 0x6e, 0x7c, - /* (2^227)P */ 0xdc, 0x70, 0x8e, 0x55, 0x44, 0xbf, 0xd2, 0x6a, 0xa0, 0x14, 0x61, 0x89, 0xd5, 0x55, 0x45, 0x3c, 0xf6, 0x40, 0x0d, 0x83, 0x85, 0x44, 0xb4, 0x62, 0x56, 0xfe, 0x60, 0xd7, 0x07, 0x1d, 0x47, 0x30, 0x3b, 0x73, 0xa4, 0xb5, 0xb7, 0xea, 0xac, 0xda, 0xf1, 0x17, 0xaa, 0x60, 0xdf, 0xe9, 0x84, 0xda, 0x31, 0x32, 0x61, 0xbf, 0xd0, 0x7e, 0x8a, 0x02, - /* (2^228)P */ 0xb9, 0x51, 0xb3, 0x89, 0x21, 0x5d, 0xa2, 0xfe, 0x79, 0x2a, 0xb3, 0x2a, 0x3b, 0xe6, 0x6f, 0x2b, 0x22, 0x03, 0xea, 0x7b, 0x1f, 0xaf, 0x85, 0xc3, 0x38, 0x55, 0x5b, 0x8e, 0xb4, 0xaa, 0x77, 0xfe, 0x03, 0x6e, 0xda, 0x91, 0x24, 0x0c, 0x48, 0x39, 0x27, 0x43, 0x16, 0xd2, 0x0a, 0x0d, 0x43, 0xa3, 0x0e, 0xca, 0x45, 0xd1, 0x7f, 0xf5, 0xd3, 0x16, - /* (2^229)P */ 0x3d, 0x32, 0x9b, 0x38, 0xf8, 0x06, 0x93, 0x78, 0x5b, 0x50, 0x2b, 0x06, 0xd8, 0x66, 0xfe, 0xab, 0x9b, 0x58, 0xc7, 0xd1, 0x4d, 0xd5, 0xf8, 0x3b, 0x10, 0x7e, 0x85, 0xde, 0x58, 0x4e, 0xdf, 0x53, 0xd9, 0x58, 0xe0, 0x15, 0x81, 0x9f, 0x1a, 0x78, 0xfc, 0x9f, 0x10, 0xc2, 0x23, 0xd6, 0x78, 0xd1, 0x9d, 0xd2, 0xd5, 0x1c, 0x53, 0xe2, 0xc9, 0x76, - /* (2^230)P */ 0x98, 0x1e, 0x38, 0x7b, 0x71, 0x18, 0x4b, 0x15, 0xaf, 0xa1, 0xa6, 0x98, 0xcb, 0x26, 0xa3, 0xc8, 0x07, 0x46, 0xda, 0x3b, 0x70, 0x65, 0xec, 0x7a, 0x2b, 0x34, 0x94, 0xa8, 0xb6, 0x14, 0xf8, 0x1a, 0xce, 0xf7, 0xc8, 0x60, 0xf3, 0x88, 0xf4, 0x33, 0x60, 0x7b, 0xd1, 0x02, 0xe7, 0xda, 0x00, 0x4a, 0xea, 0xd2, 0xfd, 0x88, 0xd2, 0x99, 0x28, 0xf3, - /* (2^231)P */ 0x28, 0x24, 0x1d, 0x26, 0xc2, 0xeb, 0x8b, 0x3b, 0xb4, 0x6b, 0xbe, 0x6b, 0x77, 0xff, 0xf3, 0x21, 0x3b, 0x26, 0x6a, 0x8c, 0x8e, 0x2a, 0x44, 0xa8, 0x01, 0x2b, 0x71, 0xea, 0x64, 0x30, 0xfd, 0xfd, 0x95, 0xcb, 0x39, 0x38, 0x48, 0xfa, 0x96, 0x97, 0x8c, 0x2f, 0x33, 0xca, 0x03, 0xe6, 0xd7, 0x94, 0x55, 0x6c, 0xc3, 0xb3, 0xa8, 0xf7, 0xae, 0x8c, - /* (2^232)P */ 0xea, 0x62, 0x8a, 0xb4, 0xeb, 0x74, 0xf7, 0xb8, 0xae, 0xc5, 0x20, 0x71, 0x06, 0xd6, 0x7c, 0x62, 0x9b, 0x69, 0x74, 0xef, 0xa7, 0x6d, 0xd6, 0x8c, 0x37, 0xb9, 0xbf, 0xcf, 0xeb, 0xe4, 0x2f, 0x04, 0x02, 0x21, 0x7d, 0x75, 0x6b, 0x92, 0x48, 0xf8, 0x70, 0xad, 0x69, 0xe2, 0xea, 0x0e, 0x88, 0x67, 0x72, 0xcc, 0x2d, 0x10, 0xce, 0x2d, 0xcf, 0x65, - /* (2^233)P */ 0x49, 0xf3, 0x57, 0x64, 0xe5, 0x5c, 0xc5, 0x65, 0x49, 0x97, 0xc4, 0x8a, 0xcc, 0xa9, 0xca, 0x94, 0x7b, 0x86, 0x88, 0xb6, 0x51, 0x27, 0x69, 0xa5, 0x0f, 0x8b, 0x06, 0x59, 0xa0, 0x94, 0xef, 0x63, 0x1a, 0x01, 0x9e, 0x4f, 0xd2, 0x5a, 0x93, 0xc0, 0x7c, 0xe6, 0x61, 0x77, 0xb6, 0xf5, 0x40, 0xd9, 0x98, 0x43, 0x5b, 0x56, 0x68, 0xe9, 0x37, 0x8f, - /* (2^234)P */ 0xee, 0x87, 0xd2, 0x05, 0x1b, 0x39, 0x89, 0x10, 0x07, 0x6d, 0xe8, 0xfd, 0x8b, 0x4d, 0xb2, 0xa7, 0x7b, 0x1e, 0xa0, 0x6c, 0x0d, 0x3d, 0x3d, 0x49, 0xba, 0x61, 0x36, 0x1f, 0xc2, 0x84, 0x4a, 0xcc, 0x87, 0xa9, 0x1b, 0x23, 0x04, 0xe2, 0x3e, 0x97, 0xe1, 0xdb, 0xd5, 0x5a, 0xe8, 0x41, 0x6b, 0xe5, 0x5a, 0xa1, 0x99, 0xe5, 0x7b, 0xa7, 0xe0, 0x3b, - /* (2^235)P */ 0xea, 0xa3, 0x6a, 0xdd, 0x77, 0x7f, 0x77, 0x41, 0xc5, 0x6a, 0xe4, 0xaf, 0x11, 0x5f, 0x88, 0xa5, 0x10, 0xee, 0xd0, 0x8c, 0x0c, 0xb4, 0xa5, 0x2a, 0xd0, 0xd8, 0x1d, 0x47, 0x06, 0xc0, 0xd5, 0xce, 0x51, 0x54, 0x9b, 0x2b, 0xe6, 0x2f, 0xe7, 0xe7, 0x31, 0x5f, 0x5c, 0x23, 0x81, 0x3e, 0x03, 0x93, 0xaa, 0x2d, 0x71, 0x84, 0xa0, 0x89, 0x32, 0xa6, - /* (2^236)P */ 0x55, 0xa3, 0x13, 0x92, 0x4e, 0x93, 0x7d, 0xec, 0xca, 0x57, 0xfb, 0x37, 0xae, 0xd2, 0x18, 0x2e, 0x54, 0x05, 0x6c, 0xd1, 0x28, 0xca, 0x90, 0x40, 0x82, 0x2e, 0x79, 0xc6, 0x5a, 0xc7, 0xdd, 0x84, 0x93, 0xdf, 0x15, 0xb8, 0x1f, 0xb1, 0xf9, 0xaf, 0x2c, 0xe5, 0x32, 0xcd, 0xc2, 0x99, 0x6d, 0xac, 0x85, 0x5c, 0x63, 0xd3, 0xe2, 0xff, 0x24, 0xda, - /* (2^237)P */ 0x2d, 0x8d, 0xfd, 0x65, 0xcc, 0xe5, 0x02, 0xa0, 0xe5, 0xb9, 0xec, 0x59, 0x09, 0x50, 0x27, 0xb7, 0x3d, 0x2a, 0x79, 0xb2, 0x76, 0x5d, 0x64, 0x95, 0xf8, 0xc5, 0xaf, 0x8a, 0x62, 0x11, 0x5c, 0x56, 0x1c, 0x05, 0x64, 0x9e, 0x5e, 0xbd, 0x54, 0x04, 0xe6, 0x9e, 0xab, 0xe6, 0x22, 0x7e, 0x42, 0x54, 0xb5, 0xa5, 0xd0, 0x8d, 0x28, 0x6b, 0x0f, 0x0b, - /* (2^238)P */ 0x2d, 0xb2, 0x8c, 0x59, 0x10, 0x37, 0x84, 0x3b, 0x9b, 0x65, 0x1b, 0x0f, 0x10, 0xf9, 0xea, 0x60, 0x1b, 0x02, 0xf5, 0xee, 0x8b, 0xe6, 0x32, 0x7d, 0x10, 0x7f, 0x5f, 0x8c, 0x72, 0x09, 0x4e, 0x1f, 0x29, 0xff, 0x65, 0xcb, 0x3e, 0x3a, 0xd2, 0x96, 0x50, 0x1e, 0xea, 0x64, 0x99, 0xb5, 0x4c, 0x7a, 0x69, 0xb8, 0x95, 0xae, 0x48, 0xc0, 0x7c, 0xb1, - /* (2^239)P */ 0xcd, 0x7c, 0x4f, 0x3e, 0xea, 0xf3, 0x90, 0xcb, 0x12, 0x76, 0xd1, 0x17, 0xdc, 0x0d, 0x13, 0x0f, 0xfd, 0x4d, 0xb5, 0x1f, 0xe4, 0xdd, 0xf2, 0x4d, 0x58, 0xea, 0xa5, 0x66, 0x92, 0xcf, 0xe5, 0x54, 0xea, 0x9b, 0x35, 0x83, 0x1a, 0x44, 0x8e, 0x62, 0x73, 0x45, 0x98, 0xa3, 0x89, 0x95, 0x52, 0x93, 0x1a, 0x8d, 0x63, 0x0f, 0xc2, 0x57, 0x3c, 0xb1, - /* (2^240)P */ 0x72, 0xb4, 0xdf, 0x51, 0xb7, 0xf6, 0x52, 0xa2, 0x14, 0x56, 0xe5, 0x0a, 0x2e, 0x75, 0x81, 0x02, 0xee, 0x93, 0x48, 0x0a, 0x92, 0x4e, 0x0c, 0x0f, 0xdf, 0x09, 0x89, 0x99, 0xf6, 0xf9, 0x22, 0xa2, 0x32, 0xf8, 0xb0, 0x76, 0x0c, 0xb2, 0x4d, 0x6e, 0xbe, 0x83, 0x35, 0x61, 0x44, 0xd2, 0x58, 0xc7, 0xdd, 0x14, 0xcf, 0xc3, 0x4b, 0x7c, 0x07, 0xee, - /* (2^241)P */ 0x8b, 0x03, 0xee, 0xcb, 0xa7, 0x2e, 0x28, 0xbd, 0x97, 0xd1, 0x4c, 0x2b, 0xd1, 0x92, 0x67, 0x5b, 0x5a, 0x12, 0xbf, 0x29, 0x17, 0xfc, 0x50, 0x09, 0x74, 0x76, 0xa2, 0xd4, 0x82, 0xfd, 0x2c, 0x0c, 0x90, 0xf7, 0xe7, 0xe5, 0x9a, 0x2c, 0x16, 0x40, 0xb9, 0x6c, 0xd9, 0xe0, 0x22, 0x9e, 0xf8, 0xdd, 0x73, 0xe4, 0x7b, 0x9e, 0xbe, 0x4f, 0x66, 0x22, - /* (2^242)P */ 0xa4, 0x10, 0xbe, 0xb8, 0x83, 0x3a, 0x77, 0x8e, 0xea, 0x0a, 0xc4, 0x97, 0x3e, 0xb6, 0x6c, 0x81, 0xd7, 0x65, 0xd9, 0xf7, 0xae, 0xe6, 0xbe, 0xab, 0x59, 0x81, 0x29, 0x4b, 0xff, 0xe1, 0x0f, 0xc3, 0x2b, 0xad, 0x4b, 0xef, 0xc4, 0x50, 0x9f, 0x88, 0x31, 0xf2, 0xde, 0x80, 0xd6, 0xf4, 0x20, 0x9c, 0x77, 0x9b, 0xbe, 0xbe, 0x08, 0xf5, 0xf0, 0x95, - /* (2^243)P */ 0x0e, 0x7c, 0x7b, 0x7c, 0xb3, 0xd8, 0x83, 0xfc, 0x8c, 0x75, 0x51, 0x74, 0x1b, 0xe1, 0x6d, 0x11, 0x05, 0x46, 0x24, 0x0d, 0xa4, 0x2b, 0x32, 0xfd, 0x2c, 0x4e, 0x21, 0xdf, 0x39, 0x6b, 0x96, 0xfc, 0xff, 0x92, 0xfc, 0x35, 0x0d, 0x9a, 0x4b, 0xc0, 0x70, 0x46, 0x32, 0x7d, 0xc0, 0xc4, 0x04, 0xe0, 0x2d, 0x83, 0xa7, 0x00, 0xc7, 0xcb, 0xb4, 0x8f, - /* (2^244)P */ 0xa9, 0x5a, 0x7f, 0x0e, 0xdd, 0x2c, 0x85, 0xaa, 0x4d, 0xac, 0xde, 0xb3, 0xb6, 0xaf, 0xe6, 0xd1, 0x06, 0x7b, 0x2c, 0xa4, 0x01, 0x19, 0x22, 0x7d, 0x78, 0xf0, 0x3a, 0xea, 0x89, 0xfe, 0x21, 0x61, 0x6d, 0xb8, 0xfe, 0xa5, 0x2a, 0xab, 0x0d, 0x7b, 0x51, 0x39, 0xb6, 0xde, 0xbc, 0xf0, 0xc5, 0x48, 0xd7, 0x09, 0x82, 0x6e, 0x66, 0x75, 0xc5, 0xcd, - /* (2^245)P */ 0xee, 0xdf, 0x2b, 0x6c, 0xa8, 0xde, 0x61, 0xe1, 0x27, 0xfa, 0x2a, 0x0f, 0x68, 0xe7, 0x7a, 0x9b, 0x13, 0xe9, 0x56, 0xd2, 0x1c, 0x3d, 0x2f, 0x3c, 0x7a, 0xf6, 0x6f, 0x45, 0xee, 0xe8, 0xf4, 0xa0, 0xa6, 0xe8, 0xa5, 0x27, 0xee, 0xf2, 0x85, 0xa9, 0xd5, 0x0e, 0xa9, 0x26, 0x60, 0xfe, 0xee, 0xc7, 0x59, 0x99, 0x5e, 0xa3, 0xdf, 0x23, 0x36, 0xd5, - /* (2^246)P */ 0x15, 0x66, 0x6f, 0xd5, 0x78, 0xa4, 0x0a, 0xf7, 0xb1, 0xe8, 0x75, 0x6b, 0x48, 0x7d, 0xa6, 0x4d, 0x3d, 0x36, 0x9b, 0xc7, 0xcc, 0x68, 0x9a, 0xfe, 0x2f, 0x39, 0x2a, 0x51, 0x31, 0x39, 0x7d, 0x73, 0x6f, 0xc8, 0x74, 0x72, 0x6f, 0x6e, 0xda, 0x5f, 0xad, 0x48, 0xc8, 0x40, 0xe1, 0x06, 0x01, 0x36, 0xa1, 0x88, 0xc8, 0x99, 0x9c, 0xd1, 0x11, 0x8f, - /* (2^247)P */ 0xab, 0xc5, 0xcb, 0xcf, 0xbd, 0x73, 0x21, 0xd0, 0x82, 0xb1, 0x2e, 0x2d, 0xd4, 0x36, 0x1b, 0xed, 0xa9, 0x8a, 0x26, 0x79, 0xc4, 0x17, 0xae, 0xe5, 0x09, 0x0a, 0x0c, 0xa4, 0x21, 0xa0, 0x6e, 0xdd, 0x62, 0x8e, 0x44, 0x62, 0xcc, 0x50, 0xff, 0x93, 0xb3, 0x9a, 0x72, 0x8c, 0x3f, 0xa1, 0xa6, 0x4d, 0x87, 0xd5, 0x1c, 0x5a, 0xc0, 0x0b, 0x1a, 0xd6, - /* (2^248)P */ 0x67, 0x36, 0x6a, 0x1f, 0x96, 0xe5, 0x80, 0x20, 0xa9, 0xe8, 0x0b, 0x0e, 0x21, 0x29, 0x3f, 0xc8, 0x0a, 0x6d, 0x27, 0x47, 0xca, 0xd9, 0x05, 0x55, 0xbf, 0x11, 0xcf, 0x31, 0x7a, 0x37, 0xc7, 0x90, 0xa9, 0xf4, 0x07, 0x5e, 0xd5, 0xc3, 0x92, 0xaa, 0x95, 0xc8, 0x23, 0x2a, 0x53, 0x45, 0xe3, 0x3a, 0x24, 0xe9, 0x67, 0x97, 0x3a, 0x82, 0xf9, 0xa6, - /* (2^249)P */ 0x92, 0x9e, 0x6d, 0x82, 0x67, 0xe9, 0xf9, 0x17, 0x96, 0x2c, 0xa7, 0xd3, 0x89, 0xf9, 0xdb, 0xd8, 0x20, 0xc6, 0x2e, 0xec, 0x4a, 0x76, 0x64, 0xbf, 0x27, 0x40, 0xe2, 0xb4, 0xdf, 0x1f, 0xa0, 0xef, 0x07, 0x80, 0xfb, 0x8e, 0x12, 0xf8, 0xb8, 0xe1, 0xc6, 0xdf, 0x7c, 0x69, 0x35, 0x5a, 0xe1, 0x8e, 0x5d, 0x69, 0x84, 0x56, 0xb6, 0x31, 0x1c, 0x0b, - /* (2^250)P */ 0xd6, 0x94, 0x5c, 0xef, 0xbb, 0x46, 0x45, 0x44, 0x5b, 0xa1, 0xae, 0x03, 0x65, 0xdd, 0xb5, 0x66, 0x88, 0x35, 0x29, 0x95, 0x16, 0x54, 0xa6, 0xf5, 0xc9, 0x78, 0x34, 0xe6, 0x0f, 0xc4, 0x2b, 0x5b, 0x79, 0x51, 0x68, 0x48, 0x3a, 0x26, 0x87, 0x05, 0x70, 0xaf, 0x8b, 0xa6, 0xc7, 0x2e, 0xb3, 0xa9, 0x10, 0x01, 0xb0, 0xb9, 0x31, 0xfd, 0xdc, 0x80, - /* (2^251)P */ 0x25, 0xf2, 0xad, 0xd6, 0x75, 0xa3, 0x04, 0x05, 0x64, 0x8a, 0x97, 0x60, 0x27, 0x2a, 0xe5, 0x6d, 0xb0, 0x73, 0xf4, 0x07, 0x2a, 0x9d, 0xe9, 0x46, 0xb4, 0x1c, 0x51, 0xf8, 0x63, 0x98, 0x7e, 0xe5, 0x13, 0x51, 0xed, 0x98, 0x65, 0x98, 0x4f, 0x8f, 0xe7, 0x7e, 0x72, 0xd7, 0x64, 0x11, 0x2f, 0xcd, 0x12, 0xf8, 0xc4, 0x63, 0x52, 0x0f, 0x7f, 0xc4, - /* (2^252)P */ 0x5c, 0xd9, 0x85, 0x63, 0xc7, 0x8a, 0x65, 0x9a, 0x25, 0x83, 0x31, 0x73, 0x49, 0xf0, 0x93, 0x96, 0x70, 0x67, 0x6d, 0xb1, 0xff, 0x95, 0x54, 0xe4, 0xf8, 0x15, 0x6c, 0x5f, 0xbd, 0xf6, 0x0f, 0x38, 0x7b, 0x68, 0x7d, 0xd9, 0x3d, 0xf0, 0xa9, 0xa0, 0xe4, 0xd1, 0xb6, 0x34, 0x6d, 0x14, 0x16, 0xc2, 0x4c, 0x30, 0x0e, 0x67, 0xd3, 0xbe, 0x2e, 0xc0, - /* (2^253)P */ 0x06, 0x6b, 0x52, 0xc8, 0x14, 0xcd, 0xae, 0x03, 0x93, 0xea, 0xc1, 0xf2, 0xf6, 0x8b, 0xc5, 0xb6, 0xdc, 0x82, 0x42, 0x29, 0x94, 0xe0, 0x25, 0x6c, 0x3f, 0x9f, 0x5d, 0xe4, 0x96, 0xf6, 0x8e, 0x3f, 0xf9, 0x72, 0xc4, 0x77, 0x60, 0x8b, 0xa4, 0xf9, 0xa8, 0xc3, 0x0a, 0x81, 0xb1, 0x97, 0x70, 0x18, 0xab, 0xea, 0x37, 0x8a, 0x08, 0xc7, 0xe2, 0x95, - /* (2^254)P */ 0x94, 0x49, 0xd9, 0x5f, 0x76, 0x72, 0x82, 0xad, 0x2d, 0x50, 0x1a, 0x7a, 0x5b, 0xe6, 0x95, 0x1e, 0x95, 0x65, 0x87, 0x1c, 0x52, 0xd7, 0x44, 0xe6, 0x9b, 0x56, 0xcd, 0x6f, 0x05, 0xff, 0x67, 0xc5, 0xdb, 0xa2, 0xac, 0xe4, 0xa2, 0x28, 0x63, 0x5f, 0xfb, 0x0c, 0x3b, 0xf1, 0x87, 0xc3, 0x36, 0x78, 0x3f, 0x77, 0xfa, 0x50, 0x85, 0xf9, 0xd7, 0x82, - /* (2^255)P */ 0x64, 0xc0, 0xe0, 0xd8, 0x2d, 0xed, 0xcb, 0x6a, 0xfd, 0xcd, 0xbc, 0x7e, 0x9f, 0xc8, 0x85, 0xe9, 0xc1, 0x7c, 0x0f, 0xe5, 0x18, 0xea, 0xd4, 0x51, 0xad, 0x59, 0x13, 0x75, 0xd9, 0x3d, 0xd4, 0x8a, 0xb2, 0xbe, 0x78, 0x52, 0x2b, 0x52, 0x94, 0x37, 0x41, 0xd6, 0xb4, 0xb6, 0x45, 0x20, 0x76, 0xe0, 0x1f, 0x31, 0xdb, 0xb1, 0xa1, 0x43, 0xf0, 0x18, - /* (2^256)P */ 0x74, 0xa9, 0xa4, 0xa9, 0xdd, 0x6e, 0x3e, 0x68, 0xe5, 0xc3, 0x2e, 0x92, 0x17, 0xa4, 0xcb, 0x80, 0xb1, 0xf0, 0x06, 0x93, 0xef, 0xe6, 0x00, 0xe6, 0x3b, 0xb1, 0x32, 0x65, 0x7b, 0x83, 0xb6, 0x8a, 0x49, 0x1b, 0x14, 0x89, 0xee, 0xba, 0xf5, 0x6a, 0x8d, 0x36, 0xef, 0xb0, 0xd8, 0xb2, 0x16, 0x99, 0x17, 0x35, 0x02, 0x16, 0x55, 0x58, 0xdd, 0x82, - /* (2^257)P */ 0x36, 0x95, 0xe8, 0xf4, 0x36, 0x42, 0xbb, 0xc5, 0x3e, 0xfa, 0x30, 0x84, 0x9e, 0x59, 0xfd, 0xd2, 0x95, 0x42, 0xf8, 0x64, 0xd9, 0xb9, 0x0e, 0x9f, 0xfa, 0xd0, 0x7b, 0x20, 0x31, 0x77, 0x48, 0x29, 0x4d, 0xd0, 0x32, 0x57, 0x56, 0x30, 0xa6, 0x17, 0x53, 0x04, 0xbf, 0x08, 0x28, 0xec, 0xb8, 0x46, 0xc1, 0x03, 0x89, 0xdc, 0xed, 0xa0, 0x35, 0x53, - /* (2^258)P */ 0xc5, 0x7f, 0x9e, 0xd8, 0xc5, 0xba, 0x5f, 0x68, 0xc8, 0x23, 0x75, 0xea, 0x0d, 0xd9, 0x5a, 0xfd, 0x61, 0x1a, 0xa3, 0x2e, 0x45, 0x63, 0x14, 0x55, 0x86, 0x21, 0x29, 0xbe, 0xef, 0x5e, 0x50, 0xe5, 0x18, 0x59, 0xe7, 0xe3, 0xce, 0x4d, 0x8c, 0x15, 0x8f, 0x89, 0x66, 0x44, 0x52, 0x3d, 0xfa, 0xc7, 0x9a, 0x59, 0x90, 0x8e, 0xc0, 0x06, 0x3f, 0xc9, - /* (2^259)P */ 0x8e, 0x04, 0xd9, 0x16, 0x50, 0x1d, 0x8c, 0x9f, 0xd5, 0xe3, 0xce, 0xfd, 0x47, 0x04, 0x27, 0x4d, 0xc2, 0xfa, 0x71, 0xd9, 0x0b, 0xb8, 0x65, 0xf4, 0x11, 0xf3, 0x08, 0xee, 0x81, 0xc8, 0x67, 0x99, 0x0b, 0x8d, 0x77, 0xa3, 0x4f, 0xb5, 0x9b, 0xdb, 0x26, 0xf1, 0x97, 0xeb, 0x04, 0x54, 0xeb, 0x80, 0x08, 0x1d, 0x1d, 0xf6, 0x3d, 0x1f, 0x5a, 0xb8, - /* (2^260)P */ 0xb7, 0x9c, 0x9d, 0xee, 0xb9, 0x5c, 0xad, 0x0d, 0x9e, 0xfd, 0x60, 0x3c, 0x27, 0x4e, 0xa2, 0x95, 0xfb, 0x64, 0x7e, 0x79, 0x64, 0x87, 0x10, 0xb4, 0x73, 0xe0, 0x9d, 0x46, 0x4d, 0x3d, 0xee, 0x83, 0xe4, 0x16, 0x88, 0x97, 0xe6, 0x4d, 0xba, 0x70, 0xb6, 0x96, 0x7b, 0xff, 0x4b, 0xc8, 0xcf, 0x72, 0x83, 0x3e, 0x5b, 0x24, 0x2e, 0x57, 0xf1, 0x82, - /* (2^261)P */ 0x30, 0x71, 0x40, 0x51, 0x4f, 0x44, 0xbb, 0xc7, 0xf0, 0x54, 0x6e, 0x9d, 0xeb, 0x15, 0xad, 0xf8, 0x61, 0x43, 0x5a, 0xef, 0xc0, 0xb1, 0x57, 0xae, 0x03, 0x40, 0xe8, 0x68, 0x6f, 0x03, 0x20, 0x4f, 0x8a, 0x51, 0x2a, 0x9e, 0xd2, 0x45, 0xaf, 0xb4, 0xf5, 0xd4, 0x95, 0x7f, 0x3d, 0x3d, 0xb7, 0xb6, 0x28, 0xc5, 0x08, 0x8b, 0x44, 0xd6, 0x3f, 0xe7, - /* (2^262)P */ 0xa9, 0x52, 0x04, 0x67, 0xcb, 0x20, 0x63, 0xf8, 0x18, 0x01, 0x44, 0x21, 0x6a, 0x8a, 0x83, 0x48, 0xd4, 0xaf, 0x23, 0x0f, 0x35, 0x8d, 0xe5, 0x5a, 0xc4, 0x7c, 0x55, 0x46, 0x19, 0x5f, 0x35, 0xe0, 0x5d, 0x97, 0x4c, 0x2d, 0x04, 0xed, 0x59, 0xd4, 0xb0, 0xb2, 0xc6, 0xe3, 0x51, 0xe1, 0x38, 0xc6, 0x30, 0x49, 0x8f, 0xae, 0x61, 0x64, 0xce, 0xa8, - /* (2^263)P */ 0x9b, 0x64, 0x83, 0x3c, 0xd3, 0xdf, 0xb9, 0x27, 0xe7, 0x5b, 0x7f, 0xeb, 0xf3, 0x26, 0xcf, 0xb1, 0x8f, 0xaf, 0x26, 0xc8, 0x48, 0xce, 0xa1, 0xac, 0x7d, 0x10, 0x34, 0x28, 0xe1, 0x1f, 0x69, 0x03, 0x64, 0x77, 0x61, 0xdd, 0x4a, 0x9b, 0x18, 0x47, 0xf8, 0xca, 0x63, 0xc9, 0x03, 0x2d, 0x20, 0x2a, 0x69, 0x6e, 0x42, 0xd0, 0xe7, 0xaa, 0xb5, 0xf3, - /* (2^264)P */ 0xea, 0x31, 0x0c, 0x57, 0x0f, 0x3e, 0xe3, 0x35, 0xd8, 0x30, 0xa5, 0x6f, 0xdd, 0x95, 0x43, 0xc6, 0x66, 0x07, 0x4f, 0x34, 0xc3, 0x7e, 0x04, 0x10, 0x2d, 0xc4, 0x1c, 0x94, 0x52, 0x2e, 0x5b, 0x9a, 0x65, 0x2f, 0x91, 0xaa, 0x4f, 0x3c, 0xdc, 0x23, 0x18, 0xe1, 0x4f, 0x85, 0xcd, 0xf4, 0x8c, 0x51, 0xf7, 0xab, 0x4f, 0xdc, 0x15, 0x5c, 0x9e, 0xc5, - /* (2^265)P */ 0x54, 0x57, 0x23, 0x17, 0xe7, 0x82, 0x2f, 0x04, 0x7d, 0xfe, 0xe7, 0x1f, 0xa2, 0x57, 0x79, 0xe9, 0x58, 0x9b, 0xbe, 0xc6, 0x16, 0x4a, 0x17, 0x50, 0x90, 0x4a, 0x34, 0x70, 0x87, 0x37, 0x01, 0x26, 0xd8, 0xa3, 0x5f, 0x07, 0x7c, 0xd0, 0x7d, 0x05, 0x8a, 0x93, 0x51, 0x2f, 0x99, 0xea, 0xcf, 0x00, 0xd8, 0xc7, 0xe6, 0x9b, 0x8c, 0x62, 0x45, 0x87, - /* (2^266)P */ 0xc3, 0xfd, 0x29, 0x66, 0xe7, 0x30, 0x29, 0x77, 0xe0, 0x0d, 0x63, 0x5b, 0xe6, 0x90, 0x1a, 0x1e, 0x99, 0xc2, 0xa7, 0xab, 0xff, 0xa7, 0xbd, 0x79, 0x01, 0x97, 0xfd, 0x27, 0x1b, 0x43, 0x2b, 0xe6, 0xfe, 0x5e, 0xf1, 0xb9, 0x35, 0x38, 0x08, 0x25, 0x55, 0x90, 0x68, 0x2e, 0xc3, 0x67, 0x39, 0x9f, 0x2b, 0x2c, 0x70, 0x48, 0x8c, 0x47, 0xee, 0x56, - /* (2^267)P */ 0xf7, 0x32, 0x70, 0xb5, 0xe6, 0x42, 0xfd, 0x0a, 0x39, 0x9b, 0x07, 0xfe, 0x0e, 0xf4, 0x47, 0xba, 0x6a, 0x3f, 0xf5, 0x2c, 0x15, 0xf3, 0x60, 0x3f, 0xb1, 0x83, 0x7b, 0x2e, 0x34, 0x58, 0x1a, 0x6e, 0x4a, 0x49, 0x05, 0x45, 0xca, 0xdb, 0x00, 0x01, 0x0c, 0x42, 0x5e, 0x60, 0x40, 0x5f, 0xd9, 0xc7, 0x3a, 0x9e, 0x1c, 0x8d, 0xab, 0x11, 0x55, 0x65, - /* (2^268)P */ 0x87, 0x40, 0xb7, 0x0d, 0xaa, 0x34, 0x89, 0x90, 0x75, 0x6d, 0xa2, 0xfe, 0x3b, 0x6d, 0x5c, 0x39, 0x98, 0x10, 0x9e, 0x15, 0xc5, 0x35, 0xa2, 0x27, 0x23, 0x0a, 0x2d, 0x60, 0xe2, 0xa8, 0x7f, 0x3e, 0x77, 0x8f, 0xcc, 0x44, 0xcc, 0x30, 0x28, 0xe2, 0xf0, 0x04, 0x8c, 0xee, 0xe4, 0x5f, 0x68, 0x8c, 0xdf, 0x70, 0xbf, 0x31, 0xee, 0x2a, 0xfc, 0xce, - /* (2^269)P */ 0x92, 0xf2, 0xa0, 0xd9, 0x58, 0x3b, 0x7c, 0x1a, 0x99, 0x46, 0x59, 0x54, 0x60, 0x06, 0x8d, 0x5e, 0xf0, 0x22, 0xa1, 0xed, 0x92, 0x8a, 0x4d, 0x76, 0x95, 0x05, 0x0b, 0xff, 0xfc, 0x9a, 0xd1, 0xcc, 0x05, 0xb9, 0x5e, 0x99, 0xe8, 0x2a, 0x76, 0x7b, 0xfd, 0xa6, 0xe2, 0xd1, 0x1a, 0xd6, 0x76, 0x9f, 0x2f, 0x0e, 0xd1, 0xa8, 0x77, 0x5a, 0x40, 0x5a, - /* (2^270)P */ 0xff, 0xf9, 0x3f, 0xa9, 0xa6, 0x6c, 0x6d, 0x03, 0x8b, 0xa7, 0x10, 0x5d, 0x3f, 0xec, 0x3e, 0x1c, 0x0b, 0x6b, 0xa2, 0x6a, 0x22, 0xa9, 0x28, 0xd0, 0x66, 0xc9, 0xc2, 0x3d, 0x47, 0x20, 0x7d, 0xa6, 0x1d, 0xd8, 0x25, 0xb5, 0xf2, 0xf9, 0x70, 0x19, 0x6b, 0xf8, 0x43, 0x36, 0xc5, 0x1f, 0xe4, 0x5a, 0x4c, 0x13, 0xe4, 0x6d, 0x08, 0x0b, 0x1d, 0xb1, - /* (2^271)P */ 0x3f, 0x20, 0x9b, 0xfb, 0xec, 0x7d, 0x31, 0xc5, 0xfc, 0x88, 0x0b, 0x30, 0xed, 0x36, 0xc0, 0x63, 0xb1, 0x7d, 0x10, 0xda, 0xb6, 0x2e, 0xad, 0xf3, 0xec, 0x94, 0xe7, 0xec, 0xb5, 0x9c, 0xfe, 0xf5, 0x35, 0xf0, 0xa2, 0x2d, 0x7f, 0xca, 0x6b, 0x67, 0x1a, 0xf6, 0xb3, 0xda, 0x09, 0x2a, 0xaa, 0xdf, 0xb1, 0xca, 0x9b, 0xfb, 0xeb, 0xb3, 0xcd, 0xc0, - /* (2^272)P */ 0xcd, 0x4d, 0x89, 0x00, 0xa4, 0x3b, 0x48, 0xf0, 0x76, 0x91, 0x35, 0xa5, 0xf8, 0xc9, 0xb6, 0x46, 0xbc, 0xf6, 0x9a, 0x45, 0x47, 0x17, 0x96, 0x80, 0x5b, 0x3a, 0x28, 0x33, 0xf9, 0x5a, 0xef, 0x43, 0x07, 0xfe, 0x3b, 0xf4, 0x8e, 0x19, 0xce, 0xd2, 0x94, 0x4b, 0x6d, 0x8e, 0x67, 0x20, 0xc7, 0x4f, 0x2f, 0x59, 0x8e, 0xe1, 0xa1, 0xa9, 0xf9, 0x0e, - /* (2^273)P */ 0xdc, 0x7b, 0xb5, 0x50, 0x2e, 0xe9, 0x7e, 0x8b, 0x78, 0xa1, 0x38, 0x96, 0x22, 0xc3, 0x61, 0x67, 0x6d, 0xc8, 0x58, 0xed, 0x41, 0x1d, 0x5d, 0x86, 0x98, 0x7f, 0x2f, 0x1b, 0x8d, 0x3e, 0xaa, 0xc1, 0xd2, 0x0a, 0xf3, 0xbf, 0x95, 0x04, 0xf3, 0x10, 0x3c, 0x2b, 0x7f, 0x90, 0x46, 0x04, 0xaa, 0x6a, 0xa9, 0x35, 0x76, 0xac, 0x49, 0xb5, 0x00, 0x45, - /* (2^274)P */ 0xb1, 0x93, 0x79, 0x84, 0x4a, 0x2a, 0x30, 0x78, 0x16, 0xaa, 0xc5, 0x74, 0x06, 0xce, 0xa5, 0xa7, 0x32, 0x86, 0xe0, 0xf9, 0x10, 0xd2, 0x58, 0x76, 0xfb, 0x66, 0x49, 0x76, 0x3a, 0x90, 0xba, 0xb5, 0xcc, 0x99, 0xcd, 0x09, 0xc1, 0x9a, 0x74, 0x23, 0xdf, 0x0c, 0xfe, 0x99, 0x52, 0x80, 0xa3, 0x7c, 0x1c, 0x71, 0x5f, 0x2c, 0x49, 0x57, 0xf4, 0xf9, - /* (2^275)P */ 0x6d, 0xbf, 0x52, 0xe6, 0x25, 0x98, 0xed, 0xcf, 0xe3, 0xbc, 0x08, 0xa2, 0x1a, 0x90, 0xae, 0xa0, 0xbf, 0x07, 0x15, 0xad, 0x0a, 0x9f, 0x3e, 0x47, 0x44, 0xc2, 0x10, 0x46, 0xa6, 0x7a, 0x9e, 0x2f, 0x57, 0xbc, 0xe2, 0xf0, 0x1d, 0xd6, 0x9a, 0x06, 0xed, 0xfc, 0x54, 0x95, 0x92, 0x15, 0xa2, 0xf7, 0x8d, 0x6b, 0xef, 0xb2, 0x05, 0xed, 0x5c, 0x63, - /* (2^276)P */ 0xbc, 0x0b, 0x27, 0x3a, 0x3a, 0xf8, 0xe1, 0x48, 0x02, 0x7e, 0x27, 0xe6, 0x81, 0x62, 0x07, 0x73, 0x74, 0xe5, 0x52, 0xd7, 0xf8, 0x26, 0xca, 0x93, 0x4d, 0x3e, 0x9b, 0x55, 0x09, 0x8e, 0xe3, 0xd7, 0xa6, 0xe3, 0xb6, 0x2a, 0xa9, 0xb3, 0xb0, 0xa0, 0x8c, 0x01, 0xbb, 0x07, 0x90, 0x78, 0x6d, 0x6d, 0xe9, 0xf0, 0x7a, 0x90, 0xbd, 0xdc, 0x0c, 0x36, - /* (2^277)P */ 0x7f, 0x20, 0x12, 0x0f, 0x40, 0x00, 0x53, 0xd8, 0x0c, 0x27, 0x47, 0x47, 0x22, 0x80, 0xfb, 0x62, 0xe4, 0xa7, 0xf7, 0xbd, 0x42, 0xa5, 0xc3, 0x2b, 0xb2, 0x7f, 0x50, 0xcc, 0xe2, 0xfb, 0xd5, 0xc0, 0x63, 0xdd, 0x24, 0x5f, 0x7c, 0x08, 0x91, 0xbf, 0x6e, 0x47, 0x44, 0xd4, 0x6a, 0xc0, 0xc3, 0x09, 0x39, 0x27, 0xdd, 0xc7, 0xca, 0x06, 0x29, 0x55, - /* (2^278)P */ 0x76, 0x28, 0x58, 0xb0, 0xd2, 0xf3, 0x0f, 0x04, 0xe9, 0xc9, 0xab, 0x66, 0x5b, 0x75, 0x51, 0xdc, 0xe5, 0x8f, 0xe8, 0x1f, 0xdb, 0x03, 0x0f, 0xb0, 0x7d, 0xf9, 0x20, 0x64, 0x89, 0xe9, 0xdc, 0xe6, 0x24, 0xc3, 0xd5, 0xd2, 0x41, 0xa6, 0xe4, 0xe3, 0xc4, 0x79, 0x7c, 0x0f, 0xa1, 0x61, 0x2f, 0xda, 0xa4, 0xc9, 0xfd, 0xad, 0x5c, 0x65, 0x6a, 0xf3, - /* (2^279)P */ 0xd5, 0xab, 0x72, 0x7a, 0x3b, 0x59, 0xea, 0xcf, 0xd5, 0x17, 0xd2, 0xb2, 0x5f, 0x2d, 0xab, 0xad, 0x9e, 0x88, 0x64, 0x55, 0x96, 0x6e, 0xf3, 0x44, 0xa9, 0x11, 0xf5, 0xf8, 0x3a, 0xf1, 0xcd, 0x79, 0x4c, 0x99, 0x6d, 0x23, 0x6a, 0xa0, 0xc2, 0x1a, 0x19, 0x45, 0xb5, 0xd8, 0x95, 0x2f, 0x49, 0xe9, 0x46, 0x39, 0x26, 0x60, 0x04, 0x15, 0x8b, 0xcc, - /* (2^280)P */ 0x66, 0x0c, 0xf0, 0x54, 0x41, 0x02, 0x91, 0xab, 0xe5, 0x85, 0x8a, 0x44, 0xa6, 0x34, 0x96, 0x32, 0xc0, 0xdf, 0x6c, 0x41, 0x39, 0xd4, 0xc6, 0xe1, 0xe3, 0x81, 0xb0, 0x4c, 0x34, 0x4f, 0xe5, 0xf4, 0x35, 0x46, 0x1f, 0xeb, 0x75, 0xfd, 0x43, 0x37, 0x50, 0x99, 0xab, 0xad, 0xb7, 0x8c, 0xa1, 0x57, 0xcb, 0xe6, 0xce, 0x16, 0x2e, 0x85, 0xcc, 0xf9, - /* (2^281)P */ 0x63, 0xd1, 0x3f, 0x9e, 0xa2, 0x17, 0x2e, 0x1d, 0x3e, 0xce, 0x48, 0x2d, 0xbb, 0x8f, 0x69, 0xc9, 0xa6, 0x3d, 0x4e, 0xfe, 0x09, 0x56, 0xb3, 0x02, 0x5f, 0x99, 0x97, 0x0c, 0x54, 0xda, 0x32, 0x97, 0x9b, 0xf4, 0x95, 0xf1, 0xad, 0xe3, 0x2b, 0x04, 0xa7, 0x9b, 0x3f, 0xbb, 0xe7, 0x87, 0x2e, 0x1f, 0x8b, 0x4b, 0x7a, 0xa4, 0x43, 0x0c, 0x0f, 0x35, - /* (2^282)P */ 0x05, 0xdc, 0xe0, 0x2c, 0xa1, 0xc1, 0xd0, 0xf1, 0x1f, 0x4e, 0xc0, 0x6c, 0x35, 0x7b, 0xca, 0x8f, 0x8b, 0x02, 0xb1, 0xf7, 0xd6, 0x2e, 0xe7, 0x93, 0x80, 0x85, 0x18, 0x88, 0x19, 0xb9, 0xb4, 0x4a, 0xbc, 0xeb, 0x5a, 0x78, 0x38, 0xed, 0xc6, 0x27, 0x2a, 0x74, 0x76, 0xf0, 0x1b, 0x79, 0x92, 0x2f, 0xd2, 0x81, 0x98, 0xdf, 0xa9, 0x50, 0x19, 0xeb, - /* (2^283)P */ 0xb5, 0xe7, 0xb4, 0x11, 0x3a, 0x81, 0xb6, 0xb4, 0xf8, 0xa2, 0xb3, 0x6c, 0xfc, 0x9d, 0xe0, 0xc0, 0xe0, 0x59, 0x7f, 0x05, 0x37, 0xef, 0x2c, 0xa9, 0x3a, 0x24, 0xac, 0x7b, 0x25, 0xa0, 0x55, 0xd2, 0x44, 0x82, 0x82, 0x6e, 0x64, 0xa3, 0x58, 0xc8, 0x67, 0xae, 0x26, 0xa7, 0x0f, 0x42, 0x63, 0xe1, 0x93, 0x01, 0x52, 0x19, 0xaf, 0x49, 0x3e, 0x33, - /* (2^284)P */ 0x05, 0x85, 0xe6, 0x66, 0xaf, 0x5f, 0xdf, 0xbf, 0x9d, 0x24, 0x62, 0x60, 0x90, 0xe2, 0x4c, 0x7d, 0x4e, 0xc3, 0x74, 0x5d, 0x4f, 0x53, 0xf3, 0x63, 0x13, 0xf4, 0x74, 0x28, 0x6b, 0x7d, 0x57, 0x0c, 0x9d, 0x84, 0xa7, 0x1a, 0xff, 0xa0, 0x79, 0xdf, 0xfc, 0x65, 0x98, 0x8e, 0x22, 0x0d, 0x62, 0x7e, 0xf2, 0x34, 0x60, 0x83, 0x05, 0x14, 0xb1, 0xc1, - /* (2^285)P */ 0x64, 0x22, 0xcc, 0xdf, 0x5c, 0xbc, 0x88, 0x68, 0x4c, 0xd9, 0xbc, 0x0e, 0xc9, 0x8b, 0xb4, 0x23, 0x52, 0xad, 0xb0, 0xb3, 0xf1, 0x17, 0xd8, 0x15, 0x04, 0x6b, 0x99, 0xf0, 0xc4, 0x7d, 0x48, 0x22, 0x4a, 0xf8, 0x6f, 0xaa, 0x88, 0x0d, 0xc5, 0x5e, 0xa9, 0x1c, 0x61, 0x3d, 0x95, 0xa9, 0x7b, 0x6a, 0x79, 0x33, 0x0a, 0x2b, 0x99, 0xe3, 0x4e, 0x48, - /* (2^286)P */ 0x6b, 0x9b, 0x6a, 0x2a, 0xf1, 0x60, 0x31, 0xb4, 0x73, 0xd1, 0x87, 0x45, 0x9c, 0x15, 0x58, 0x4b, 0x91, 0x6d, 0x94, 0x1c, 0x41, 0x11, 0x4a, 0x83, 0xec, 0xaf, 0x65, 0xbc, 0x34, 0xaa, 0x26, 0xe2, 0xaf, 0xed, 0x46, 0x05, 0x4e, 0xdb, 0xc6, 0x4e, 0x10, 0x28, 0x4e, 0x72, 0xe5, 0x31, 0xa3, 0x20, 0xd7, 0xb1, 0x96, 0x64, 0xf6, 0xce, 0x08, 0x08, - /* (2^287)P */ 0x16, 0xa9, 0x5c, 0x9f, 0x9a, 0xb4, 0xb8, 0xc8, 0x32, 0x78, 0xc0, 0x3a, 0xd9, 0x5f, 0x94, 0xac, 0x3a, 0x42, 0x1f, 0x43, 0xd6, 0x80, 0x47, 0x2c, 0xdc, 0x76, 0x27, 0xfa, 0x50, 0xe5, 0xa1, 0xe4, 0xc3, 0xcb, 0x61, 0x31, 0xe1, 0x2e, 0xde, 0x81, 0x3b, 0x77, 0x1c, 0x39, 0x3c, 0xdb, 0xda, 0x87, 0x4b, 0x84, 0x12, 0xeb, 0xdd, 0x54, 0xbf, 0xe7, - /* (2^288)P */ 0xbf, 0xcb, 0x73, 0x21, 0x3d, 0x7e, 0x13, 0x8c, 0xa6, 0x34, 0x21, 0x2b, 0xa5, 0xe4, 0x9f, 0x8e, 0x9c, 0x01, 0x9c, 0x43, 0xd9, 0xc7, 0xb9, 0xf1, 0xbe, 0x7f, 0x45, 0x51, 0x97, 0xa1, 0x8e, 0x01, 0xf8, 0xbd, 0xd2, 0xbf, 0x81, 0x3a, 0x8b, 0xab, 0xe4, 0x89, 0xb7, 0xbd, 0xf2, 0xcd, 0xa9, 0x8a, 0x8a, 0xde, 0xfb, 0x8a, 0x55, 0x12, 0x7b, 0x17, - /* (2^289)P */ 0x1b, 0x95, 0x58, 0x4d, 0xe6, 0x51, 0x31, 0x52, 0x1c, 0xd8, 0x15, 0x84, 0xb1, 0x0d, 0x36, 0x25, 0x88, 0x91, 0x46, 0x71, 0x42, 0x56, 0xe2, 0x90, 0x08, 0x9e, 0x77, 0x1b, 0xee, 0x22, 0x3f, 0xec, 0xee, 0x8c, 0x7b, 0x2e, 0x79, 0xc4, 0x6c, 0x07, 0xa1, 0x7e, 0x52, 0xf5, 0x26, 0x5c, 0x84, 0x2a, 0x50, 0x6e, 0x82, 0xb3, 0x76, 0xda, 0x35, 0x16, - /* (2^290)P */ 0x0a, 0x6f, 0x99, 0x87, 0xc0, 0x7d, 0x8a, 0xb2, 0xca, 0xae, 0xe8, 0x65, 0x98, 0x0f, 0xb3, 0x44, 0xe1, 0xdc, 0x52, 0x79, 0x75, 0xec, 0x8f, 0x95, 0x87, 0x45, 0xd1, 0x32, 0x18, 0x55, 0x15, 0xce, 0x64, 0x9b, 0x08, 0x4f, 0x2c, 0xea, 0xba, 0x1c, 0x57, 0x06, 0x63, 0xc8, 0xb1, 0xfd, 0xc5, 0x67, 0xe7, 0x1f, 0x87, 0x9e, 0xde, 0x72, 0x7d, 0xec, - /* (2^291)P */ 0x36, 0x8b, 0x4d, 0x2c, 0xc2, 0x46, 0xe8, 0x96, 0xac, 0x0b, 0x8c, 0xc5, 0x09, 0x10, 0xfc, 0xf2, 0xda, 0xea, 0x22, 0xb2, 0xd3, 0x89, 0xeb, 0xb2, 0x85, 0x0f, 0xff, 0x59, 0x50, 0x2c, 0x99, 0x5a, 0x1f, 0xec, 0x2a, 0x6f, 0xec, 0xcf, 0xe9, 0xce, 0x12, 0x6b, 0x19, 0xd8, 0xde, 0x9b, 0xce, 0x0e, 0x6a, 0xaa, 0xe1, 0x32, 0xea, 0x4c, 0xfe, 0x92, - /* (2^292)P */ 0x5f, 0x17, 0x70, 0x53, 0x26, 0x03, 0x0b, 0xab, 0xd1, 0xc1, 0x42, 0x0b, 0xab, 0x2b, 0x3d, 0x31, 0xa4, 0xd5, 0x2b, 0x5e, 0x00, 0xd5, 0x9a, 0x22, 0x34, 0xe0, 0x53, 0x3f, 0x59, 0x7f, 0x2c, 0x6d, 0x72, 0x9a, 0xa4, 0xbe, 0x3d, 0x42, 0x05, 0x1b, 0xf2, 0x7f, 0x88, 0x56, 0xd1, 0x7c, 0x7d, 0x6b, 0x9f, 0x43, 0xfe, 0x65, 0x19, 0xae, 0x9c, 0x4c, - /* (2^293)P */ 0xf3, 0x7c, 0x20, 0xa9, 0xfc, 0xf2, 0xf2, 0x3b, 0x3c, 0x57, 0x41, 0x94, 0xe5, 0xcc, 0x6a, 0x37, 0x5d, 0x09, 0xf2, 0xab, 0xc2, 0xca, 0x60, 0x38, 0x6b, 0x7a, 0xe1, 0x78, 0x2b, 0xc1, 0x1d, 0xe8, 0xfd, 0xbc, 0x3d, 0x5c, 0xa2, 0xdb, 0x49, 0x20, 0x79, 0xe6, 0x1b, 0x9b, 0x65, 0xd9, 0x6d, 0xec, 0x57, 0x1d, 0xd2, 0xe9, 0x90, 0xeb, 0x43, 0x7b, - /* (2^294)P */ 0x2a, 0x8b, 0x2e, 0x19, 0x18, 0x10, 0xb8, 0x83, 0xe7, 0x7d, 0x2d, 0x9a, 0x3a, 0xe5, 0xd1, 0xe4, 0x7c, 0x38, 0xe5, 0x59, 0x2a, 0x6e, 0xd9, 0x01, 0x29, 0x3d, 0x23, 0xf7, 0x52, 0xba, 0x61, 0x04, 0x9a, 0xde, 0xc4, 0x31, 0x50, 0xeb, 0x1b, 0xaa, 0xde, 0x39, 0x58, 0xd8, 0x1b, 0x1e, 0xfc, 0x57, 0x9a, 0x28, 0x43, 0x9e, 0x97, 0x5e, 0xaa, 0xa3, - /* (2^295)P */ 0x97, 0x0a, 0x74, 0xc4, 0x39, 0x99, 0x6b, 0x40, 0xc7, 0x3e, 0x8c, 0xa7, 0xb1, 0x4e, 0x9a, 0x59, 0x6e, 0x1c, 0xfe, 0xfc, 0x2a, 0x5e, 0x73, 0x2b, 0x8c, 0xa9, 0x71, 0xf5, 0xda, 0x6b, 0x15, 0xab, 0xf7, 0xbe, 0x2a, 0x44, 0x5f, 0xba, 0xae, 0x67, 0x93, 0xc5, 0x86, 0xc1, 0xb8, 0xdf, 0xdc, 0xcb, 0xd7, 0xff, 0xb1, 0x71, 0x7c, 0x6f, 0x88, 0xf8, - /* (2^296)P */ 0x3f, 0x89, 0xb1, 0xbf, 0x24, 0x16, 0xac, 0x56, 0xfe, 0xdf, 0x94, 0x71, 0xbf, 0xd6, 0x57, 0x0c, 0xb4, 0x77, 0x37, 0xaa, 0x2a, 0x70, 0x76, 0x49, 0xaf, 0x0c, 0x97, 0x8e, 0x78, 0x2a, 0x67, 0xc9, 0x3b, 0x3d, 0x5b, 0x01, 0x2f, 0xda, 0xd5, 0xa8, 0xde, 0x02, 0xa9, 0xac, 0x76, 0x00, 0x0b, 0x46, 0xc6, 0x2d, 0xdc, 0x08, 0xf4, 0x10, 0x2c, 0xbe, - /* (2^297)P */ 0xcb, 0x07, 0xf9, 0x91, 0xc6, 0xd5, 0x3e, 0x54, 0x63, 0xae, 0xfc, 0x10, 0xbe, 0x3a, 0x20, 0x73, 0x4e, 0x65, 0x0e, 0x2d, 0x86, 0x77, 0x83, 0x9d, 0xe2, 0x0a, 0xe9, 0xac, 0x22, 0x52, 0x76, 0xd4, 0x6e, 0xfa, 0xe0, 0x09, 0xef, 0x78, 0x82, 0x9f, 0x26, 0xf9, 0x06, 0xb5, 0xe7, 0x05, 0x0e, 0xf2, 0x46, 0x72, 0x93, 0xd3, 0x24, 0xbd, 0x87, 0x60, - /* (2^298)P */ 0x14, 0x55, 0x84, 0x7b, 0x6c, 0x60, 0x80, 0x73, 0x8c, 0xbe, 0x2d, 0xd6, 0x69, 0xd6, 0x17, 0x26, 0x44, 0x9f, 0x88, 0xa2, 0x39, 0x7c, 0x89, 0xbc, 0x6d, 0x9e, 0x46, 0xb6, 0x68, 0x66, 0xea, 0xdc, 0x31, 0xd6, 0x21, 0x51, 0x9f, 0x28, 0x28, 0xaf, 0x9e, 0x47, 0x2c, 0x4c, 0x8f, 0xf3, 0xaf, 0x1f, 0xe4, 0xab, 0xac, 0xe9, 0x0c, 0x91, 0x3a, 0x61, - /* (2^299)P */ 0xb0, 0x37, 0x55, 0x4b, 0xe9, 0xc3, 0xb1, 0xce, 0x42, 0xe6, 0xc5, 0x11, 0x7f, 0x2c, 0x11, 0xfc, 0x4e, 0x71, 0x17, 0x00, 0x74, 0x7f, 0xbf, 0x07, 0x4d, 0xfd, 0x40, 0xb2, 0x87, 0xb0, 0xef, 0x1f, 0x35, 0x2c, 0x2d, 0xd7, 0xe1, 0xe4, 0xad, 0x0e, 0x7f, 0x63, 0x66, 0x62, 0x23, 0x41, 0xf6, 0xc1, 0x14, 0xa6, 0xd7, 0xa9, 0x11, 0x56, 0x9d, 0x1b, - /* (2^300)P */ 0x02, 0x82, 0x42, 0x18, 0x4f, 0x1b, 0xc9, 0x5d, 0x78, 0x5f, 0xee, 0xed, 0x01, 0x49, 0x8f, 0xf2, 0xa0, 0xe2, 0x6e, 0xbb, 0x6b, 0x04, 0x8d, 0xb2, 0x41, 0xae, 0xc8, 0x1b, 0x59, 0x34, 0xb8, 0x2a, 0xdb, 0x1f, 0xd2, 0x52, 0xdf, 0x3f, 0x35, 0x00, 0x8b, 0x61, 0xbc, 0x97, 0xa0, 0xc4, 0x77, 0xd1, 0xe4, 0x2c, 0x59, 0x68, 0xff, 0x30, 0xf2, 0xe2, - /* (2^301)P */ 0x79, 0x08, 0xb1, 0xdb, 0x55, 0xae, 0xd0, 0xed, 0xda, 0xa0, 0xec, 0x6c, 0xae, 0x68, 0xf2, 0x0b, 0x61, 0xb3, 0xf5, 0x21, 0x69, 0x87, 0x0b, 0x03, 0xea, 0x8a, 0x15, 0xd9, 0x7e, 0xca, 0xf7, 0xcd, 0xf3, 0x33, 0xb3, 0x4c, 0x5b, 0x23, 0x4e, 0x6f, 0x90, 0xad, 0x91, 0x4b, 0x4f, 0x46, 0x37, 0xe5, 0xe8, 0xb7, 0xeb, 0xd5, 0xca, 0x34, 0x4e, 0x23, - /* (2^302)P */ 0x09, 0x02, 0xdd, 0xfd, 0x70, 0xac, 0x56, 0x80, 0x36, 0x5e, 0x49, 0xd0, 0x3f, 0xc2, 0xe0, 0xba, 0x46, 0x7f, 0x5c, 0xf7, 0xc5, 0xbd, 0xd5, 0x55, 0x7d, 0x3f, 0xd5, 0x7d, 0x06, 0xdf, 0x27, 0x20, 0x4f, 0xe9, 0x30, 0xec, 0x1b, 0xa0, 0x0c, 0xd4, 0x2c, 0xe1, 0x2b, 0x65, 0x73, 0xea, 0x75, 0x35, 0xe8, 0xe6, 0x56, 0xd6, 0x07, 0x15, 0x99, 0xdf, - /* (2^303)P */ 0x4e, 0x10, 0xb7, 0xd0, 0x63, 0x8c, 0xcf, 0x16, 0x00, 0x7c, 0x58, 0xdf, 0x86, 0xdc, 0x4e, 0xca, 0x9c, 0x40, 0x5a, 0x42, 0xfd, 0xec, 0x98, 0xa4, 0x42, 0x53, 0xae, 0x16, 0x9d, 0xfd, 0x75, 0x5a, 0x12, 0x56, 0x1e, 0xc6, 0x57, 0xcc, 0x79, 0x27, 0x96, 0x00, 0xcf, 0x80, 0x4f, 0x8a, 0x36, 0x5c, 0xbb, 0xe9, 0x12, 0xdb, 0xb6, 0x2b, 0xad, 0x96, - /* (2^304)P */ 0x92, 0x32, 0x1f, 0xfd, 0xc6, 0x02, 0x94, 0x08, 0x1b, 0x60, 0x6a, 0x9f, 0x8b, 0xd6, 0xc8, 0xad, 0xd5, 0x1b, 0x27, 0x4e, 0xa4, 0x4d, 0x4a, 0x00, 0x10, 0x5f, 0x86, 0x11, 0xf5, 0xe3, 0x14, 0x32, 0x43, 0xee, 0xb9, 0xc7, 0xab, 0xf4, 0x6f, 0xe5, 0x66, 0x0c, 0x06, 0x0d, 0x96, 0x79, 0x28, 0xaf, 0x45, 0x2b, 0x56, 0xbe, 0xe4, 0x4a, 0x52, 0xd6, - /* (2^305)P */ 0x15, 0x16, 0x69, 0xef, 0x60, 0xca, 0x82, 0x25, 0x0f, 0xc6, 0x30, 0xa0, 0x0a, 0xd1, 0x83, 0x29, 0xcd, 0xb6, 0x89, 0x6c, 0xf5, 0xb2, 0x08, 0x38, 0xe6, 0xca, 0x6b, 0x19, 0x93, 0xc6, 0x5f, 0x75, 0x8e, 0x60, 0x34, 0x23, 0xc4, 0x13, 0x17, 0x69, 0x55, 0xcc, 0x72, 0x9c, 0x2b, 0x6c, 0x80, 0xf4, 0x4b, 0x8b, 0xb6, 0x97, 0x65, 0x07, 0xb6, 0xfb, - /* (2^306)P */ 0x01, 0x99, 0x74, 0x28, 0xa6, 0x67, 0xa3, 0xe5, 0x25, 0xfb, 0xdf, 0x82, 0x93, 0xe7, 0x35, 0x74, 0xce, 0xe3, 0x15, 0x1c, 0x1d, 0x79, 0x52, 0x84, 0x08, 0x04, 0x2f, 0x5c, 0xb8, 0xcd, 0x7f, 0x89, 0xb0, 0x39, 0x93, 0x63, 0xc9, 0x5d, 0x06, 0x01, 0x59, 0xf7, 0x7e, 0xf1, 0x4c, 0x3d, 0x12, 0x8d, 0x69, 0x1d, 0xb7, 0x21, 0x5e, 0x88, 0x82, 0xa2, - /* (2^307)P */ 0x8e, 0x69, 0xaf, 0x9a, 0x41, 0x0d, 0x9d, 0xcf, 0x8e, 0x8d, 0x5c, 0x51, 0x6e, 0xde, 0x0e, 0x48, 0x23, 0x89, 0xe5, 0x37, 0x80, 0xd6, 0x9d, 0x72, 0x32, 0x26, 0x38, 0x2d, 0x63, 0xa0, 0xfa, 0xd3, 0x40, 0xc0, 0x8c, 0x68, 0x6f, 0x2b, 0x1e, 0x9a, 0x39, 0x51, 0x78, 0x74, 0x9a, 0x7b, 0x4a, 0x8f, 0x0c, 0xa0, 0x88, 0x60, 0xa5, 0x21, 0xcd, 0xc7, - /* (2^308)P */ 0x3a, 0x7f, 0x73, 0x14, 0xbf, 0x89, 0x6a, 0x4c, 0x09, 0x5d, 0xf2, 0x93, 0x20, 0x2d, 0xc4, 0x29, 0x86, 0x06, 0x95, 0xab, 0x22, 0x76, 0x4c, 0x54, 0xe1, 0x7e, 0x80, 0x6d, 0xab, 0x29, 0x61, 0x87, 0x77, 0xf6, 0xc0, 0x3e, 0xda, 0xab, 0x65, 0x7e, 0x39, 0x12, 0xa1, 0x6b, 0x42, 0xf7, 0xc5, 0x97, 0x77, 0xec, 0x6f, 0x22, 0xbe, 0x44, 0xc7, 0x03, - /* (2^309)P */ 0xa5, 0x23, 0x90, 0x41, 0xa3, 0xc5, 0x3e, 0xe0, 0xa5, 0x32, 0x49, 0x1f, 0x39, 0x78, 0xb1, 0xd8, 0x24, 0xea, 0xd4, 0x87, 0x53, 0x42, 0x51, 0xf4, 0xd9, 0x46, 0x25, 0x2f, 0x62, 0xa9, 0x90, 0x9a, 0x4a, 0x25, 0x8a, 0xd2, 0x10, 0xe7, 0x3c, 0xbc, 0x58, 0x8d, 0x16, 0x14, 0x96, 0xa4, 0x6f, 0xf8, 0x12, 0x69, 0x91, 0x73, 0xe2, 0xfa, 0xf4, 0x57, - /* (2^310)P */ 0x51, 0x45, 0x3f, 0x96, 0xdc, 0x97, 0x38, 0xa6, 0x01, 0x63, 0x09, 0xea, 0xc2, 0x13, 0x30, 0xb0, 0x00, 0xb8, 0x0a, 0xce, 0xd1, 0x8f, 0x3e, 0x69, 0x62, 0x46, 0x33, 0x9c, 0xbf, 0x4b, 0xcb, 0x0c, 0x90, 0x1c, 0x45, 0xcf, 0x37, 0x5b, 0xf7, 0x4b, 0x5e, 0x95, 0xc3, 0x28, 0x9f, 0x08, 0x83, 0x53, 0x74, 0xab, 0x0c, 0xb4, 0xc0, 0xa1, 0xbc, 0x89, - /* (2^311)P */ 0x06, 0xb1, 0x51, 0x15, 0x65, 0x60, 0x21, 0x17, 0x7a, 0x20, 0x65, 0xee, 0x12, 0x35, 0x4d, 0x46, 0xf4, 0xf8, 0xd0, 0xb1, 0xca, 0x09, 0x30, 0x08, 0x89, 0x23, 0x3b, 0xe7, 0xab, 0x8b, 0x77, 0xa6, 0xad, 0x25, 0xdd, 0xea, 0x3c, 0x7d, 0xa5, 0x24, 0xb3, 0xe8, 0xfa, 0xfb, 0xc9, 0xf2, 0x71, 0xe9, 0xfa, 0xf2, 0xdc, 0x54, 0xdd, 0x55, 0x2e, 0x2f, - /* (2^312)P */ 0x7f, 0x96, 0x96, 0xfb, 0x52, 0x86, 0xcf, 0xea, 0x62, 0x18, 0xf1, 0x53, 0x1f, 0x61, 0x2a, 0x9f, 0x8c, 0x51, 0xca, 0x2c, 0xde, 0x6d, 0xce, 0xab, 0x58, 0x32, 0x0b, 0x33, 0x9b, 0x99, 0xb4, 0x5c, 0x88, 0x2a, 0x76, 0xcc, 0x3e, 0x54, 0x1e, 0x9d, 0xa2, 0x89, 0xe4, 0x19, 0xba, 0x80, 0xc8, 0x39, 0x32, 0x7f, 0x0f, 0xc7, 0x84, 0xbb, 0x43, 0x56, - /* (2^313)P */ 0x9b, 0x07, 0xb4, 0x42, 0xa9, 0xa0, 0x78, 0x4f, 0x28, 0x70, 0x2b, 0x7e, 0x61, 0xe0, 0xdd, 0x02, 0x98, 0xfc, 0xed, 0x31, 0x80, 0xf1, 0x15, 0x52, 0x89, 0x23, 0xcd, 0x5d, 0x2b, 0xc5, 0x19, 0x32, 0xfb, 0x70, 0x50, 0x7a, 0x97, 0x6b, 0x42, 0xdb, 0xca, 0xdb, 0xc4, 0x59, 0x99, 0xe0, 0x12, 0x1f, 0x17, 0xba, 0x8b, 0xf0, 0xc4, 0x38, 0x5d, 0x27, - /* (2^314)P */ 0x29, 0x1d, 0xdc, 0x2b, 0xf6, 0x5b, 0x04, 0x61, 0x36, 0x76, 0xa0, 0x56, 0x36, 0x6e, 0xd7, 0x24, 0x4d, 0xe7, 0xef, 0x44, 0xd2, 0xd5, 0x07, 0xcd, 0xc4, 0x9d, 0x80, 0x48, 0xc3, 0x38, 0xcf, 0xd8, 0xa3, 0xdd, 0xb2, 0x5e, 0xb5, 0x70, 0x15, 0xbb, 0x36, 0x85, 0x8a, 0xd7, 0xfb, 0x56, 0x94, 0x73, 0x9c, 0x81, 0xbe, 0xb1, 0x44, 0x28, 0xf1, 0x37, - /* (2^315)P */ 0xbf, 0xcf, 0x5c, 0xd2, 0xe2, 0xea, 0xc2, 0xcd, 0x70, 0x7a, 0x9d, 0xcb, 0x81, 0xc1, 0xe9, 0xf1, 0x56, 0x71, 0x52, 0xf7, 0x1b, 0x87, 0xc6, 0xd8, 0xcc, 0xb2, 0x69, 0xf3, 0xb0, 0xbd, 0xba, 0x83, 0x12, 0x26, 0xc4, 0xce, 0x72, 0xde, 0x3b, 0x21, 0x28, 0x9e, 0x5a, 0x94, 0xf5, 0x04, 0xa3, 0xc8, 0x0f, 0x5e, 0xbc, 0x71, 0xf9, 0x0d, 0xce, 0xf5, - /* (2^316)P */ 0x93, 0x97, 0x00, 0x85, 0xf4, 0xb4, 0x40, 0xec, 0xd9, 0x2b, 0x6c, 0xd6, 0x63, 0x9e, 0x93, 0x0a, 0x5a, 0xf4, 0xa7, 0x9a, 0xe3, 0x3c, 0xf0, 0x55, 0xd1, 0x96, 0x6c, 0xf5, 0x2a, 0xce, 0xd7, 0x95, 0x72, 0xbf, 0xc5, 0x0c, 0xce, 0x79, 0xa2, 0x0a, 0x78, 0xe0, 0x72, 0xd0, 0x66, 0x28, 0x05, 0x75, 0xd3, 0x23, 0x09, 0x91, 0xed, 0x7e, 0xc4, 0xbc, - /* (2^317)P */ 0x77, 0xc2, 0x9a, 0xf7, 0xa6, 0xe6, 0x18, 0xb4, 0xe7, 0xf6, 0xda, 0xec, 0x44, 0x6d, 0xfb, 0x08, 0xee, 0x65, 0xa8, 0x92, 0x85, 0x1f, 0xba, 0x38, 0x93, 0x20, 0x5c, 0x4d, 0xd2, 0x18, 0x0f, 0x24, 0xbe, 0x1a, 0x96, 0x44, 0x7d, 0xeb, 0xb3, 0xda, 0x95, 0xf4, 0xaf, 0x6c, 0x06, 0x0f, 0x47, 0x37, 0xc8, 0x77, 0x63, 0xe1, 0x29, 0xef, 0xff, 0xa5, - /* (2^318)P */ 0x16, 0x12, 0xd9, 0x47, 0x90, 0x22, 0x9b, 0x05, 0xf2, 0xa5, 0x9a, 0xae, 0x83, 0x98, 0xb5, 0xac, 0xab, 0x29, 0xaa, 0xdc, 0x5f, 0xde, 0xcd, 0xf7, 0x42, 0xad, 0x3b, 0x96, 0xd6, 0x3e, 0x6e, 0x52, 0x47, 0xb1, 0xab, 0x51, 0xde, 0x49, 0x7c, 0x87, 0x8d, 0x86, 0xe2, 0x70, 0x13, 0x21, 0x51, 0x1c, 0x0c, 0x25, 0xc1, 0xb0, 0xe6, 0x19, 0xcf, 0x12, - /* (2^319)P */ 0xf0, 0xbc, 0x97, 0x8f, 0x4b, 0x2f, 0xd1, 0x1f, 0x8c, 0x57, 0xed, 0x3c, 0xf4, 0x26, 0x19, 0xbb, 0x60, 0xca, 0x24, 0xc5, 0xd9, 0x97, 0xe2, 0x5f, 0x76, 0x49, 0x39, 0x7e, 0x2d, 0x12, 0x21, 0x98, 0xda, 0xe6, 0xdb, 0xd2, 0xd8, 0x9f, 0x18, 0xd8, 0x83, 0x6c, 0xba, 0x89, 0x8d, 0x29, 0xfa, 0x46, 0x33, 0x8c, 0x28, 0xdf, 0x6a, 0xb3, 0x69, 0x28, - /* (2^320)P */ 0x86, 0x17, 0xbc, 0xd6, 0x7c, 0xba, 0x1e, 0x83, 0xbb, 0x84, 0xb5, 0x8c, 0xad, 0xdf, 0xa1, 0x24, 0x81, 0x70, 0x40, 0x0f, 0xad, 0xad, 0x3b, 0x23, 0xd0, 0x93, 0xa0, 0x49, 0x5c, 0x4b, 0x51, 0xbe, 0x20, 0x49, 0x4e, 0xda, 0x2d, 0xd3, 0xad, 0x1b, 0x74, 0x08, 0x41, 0xf0, 0xef, 0x19, 0xe9, 0x45, 0x5d, 0x02, 0xae, 0x26, 0x25, 0xd9, 0xd1, 0xc2, - /* (2^321)P */ 0x48, 0x81, 0x3e, 0xb2, 0x83, 0xf8, 0x4d, 0xb3, 0xd0, 0x4c, 0x75, 0xb3, 0xa0, 0x52, 0x26, 0xf2, 0xaf, 0x5d, 0x36, 0x70, 0x72, 0xd6, 0xb7, 0x88, 0x08, 0x69, 0xbd, 0x15, 0x25, 0xb1, 0x45, 0x1b, 0xb7, 0x0b, 0x5f, 0x71, 0x5d, 0x83, 0x49, 0xb9, 0x84, 0x3b, 0x7c, 0xc1, 0x50, 0x93, 0x05, 0x53, 0xe0, 0x61, 0xea, 0xc1, 0xef, 0xdb, 0x82, 0x97, - /* (2^322)P */ 0x00, 0xd5, 0xc3, 0x3a, 0x4d, 0x8a, 0x23, 0x7a, 0xef, 0xff, 0x37, 0xef, 0xf3, 0xbc, 0xa9, 0xb6, 0xae, 0xd7, 0x3a, 0x7b, 0xfd, 0x3e, 0x8e, 0x9b, 0xab, 0x44, 0x54, 0x60, 0x28, 0x6c, 0xbf, 0x15, 0x24, 0x4a, 0x56, 0x60, 0x7f, 0xa9, 0x7a, 0x28, 0x59, 0x2c, 0x8a, 0xd1, 0x7d, 0x6b, 0x00, 0xfd, 0xa5, 0xad, 0xbc, 0x19, 0x3f, 0xcb, 0x73, 0xe0, - /* (2^323)P */ 0xcf, 0x9e, 0x66, 0x06, 0x4d, 0x2b, 0xf5, 0x9c, 0xc2, 0x9d, 0x9e, 0xed, 0x5a, 0x5c, 0x2d, 0x00, 0xbf, 0x29, 0x90, 0x88, 0xe4, 0x5d, 0xfd, 0xe2, 0xf0, 0x38, 0xec, 0x4d, 0x26, 0xea, 0x54, 0xf0, 0x3c, 0x84, 0x10, 0x6a, 0xf9, 0x66, 0x9c, 0xe7, 0x21, 0xfd, 0x0f, 0xc7, 0x13, 0x50, 0x81, 0xb6, 0x50, 0xf9, 0x04, 0x7f, 0xa4, 0x37, 0x85, 0x14, - /* (2^324)P */ 0xdb, 0x87, 0x49, 0xc7, 0xa8, 0x39, 0x0c, 0x32, 0x98, 0x0c, 0xb9, 0x1a, 0x1b, 0x4d, 0xe0, 0x8a, 0x9a, 0x8e, 0x8f, 0xab, 0x5a, 0x17, 0x3d, 0x04, 0x21, 0xce, 0x3e, 0x2c, 0xf9, 0xa3, 0x97, 0xe4, 0x77, 0x95, 0x0e, 0xb6, 0xa5, 0x15, 0xad, 0x3a, 0x1e, 0x46, 0x53, 0x17, 0x09, 0x83, 0x71, 0x4e, 0x86, 0x38, 0xd5, 0x23, 0x44, 0x16, 0x8d, 0xc8, - /* (2^325)P */ 0x05, 0x5e, 0x99, 0x08, 0xbb, 0xc3, 0xc0, 0xb7, 0x6c, 0x12, 0xf2, 0xf3, 0xf4, 0x7c, 0x6a, 0x4d, 0x9e, 0xeb, 0x3d, 0xb9, 0x63, 0x94, 0xce, 0x81, 0xd8, 0x11, 0xcb, 0x55, 0x69, 0x4a, 0x20, 0x0b, 0x4c, 0x2e, 0x14, 0xb8, 0xd4, 0x6a, 0x7c, 0xf0, 0xed, 0xfc, 0x8f, 0xef, 0xa0, 0xeb, 0x6c, 0x01, 0xe2, 0xdc, 0x10, 0x22, 0xa2, 0x01, 0x85, 0x64, - /* (2^326)P */ 0x58, 0xe1, 0x9c, 0x27, 0x55, 0xc6, 0x25, 0xa6, 0x7d, 0x67, 0x88, 0x65, 0x99, 0x6c, 0xcb, 0xdb, 0x27, 0x4f, 0x44, 0x29, 0xf5, 0x4a, 0x23, 0x10, 0xbc, 0x03, 0x3f, 0x36, 0x1e, 0xef, 0xb0, 0xba, 0x75, 0xe8, 0x74, 0x5f, 0x69, 0x3e, 0x26, 0x40, 0xb4, 0x2f, 0xdc, 0x43, 0xbf, 0xa1, 0x8b, 0xbd, 0xca, 0x6e, 0xc1, 0x6e, 0x21, 0x79, 0xa0, 0xd0, - /* (2^327)P */ 0x78, 0x93, 0x4a, 0x2d, 0x22, 0x6e, 0x6e, 0x7d, 0x74, 0xd2, 0x66, 0x58, 0xce, 0x7b, 0x1d, 0x97, 0xb1, 0xf2, 0xda, 0x1c, 0x79, 0xfb, 0xba, 0xd1, 0xc0, 0xc5, 0x6e, 0xc9, 0x11, 0x89, 0xd2, 0x41, 0x8d, 0x70, 0xb9, 0xcc, 0xea, 0x6a, 0xb3, 0x45, 0xb6, 0x05, 0x2e, 0xf2, 0x17, 0xf1, 0x27, 0xb8, 0xed, 0x06, 0x1f, 0xdb, 0x9d, 0x1f, 0x69, 0x28, - /* (2^328)P */ 0x93, 0x12, 0xa8, 0x11, 0xe1, 0x92, 0x30, 0x8d, 0xac, 0xe1, 0x1c, 0x60, 0x7c, 0xed, 0x2d, 0x2e, 0xd3, 0x03, 0x5c, 0x9c, 0xc5, 0xbd, 0x64, 0x4a, 0x8c, 0xba, 0x76, 0xfe, 0xc6, 0xc1, 0xea, 0xc2, 0x4f, 0xbe, 0x70, 0x3d, 0x64, 0xcf, 0x8e, 0x18, 0xcb, 0xcd, 0x57, 0xa7, 0xf7, 0x36, 0xa9, 0x6b, 0x3e, 0xb8, 0x69, 0xee, 0x47, 0xa2, 0x7e, 0xb2, - /* (2^329)P */ 0x96, 0xaf, 0x3a, 0xf5, 0xed, 0xcd, 0xaf, 0xf7, 0x82, 0xaf, 0x59, 0x62, 0x0b, 0x36, 0x85, 0xf9, 0xaf, 0xd6, 0x38, 0xff, 0x87, 0x2e, 0x1d, 0x6c, 0x8b, 0xaf, 0x3b, 0xdf, 0x28, 0xa2, 0xd6, 0x4d, 0x80, 0x92, 0xc3, 0x0f, 0x34, 0xa8, 0xae, 0x69, 0x5d, 0x7b, 0x9d, 0xbc, 0xf5, 0xfd, 0x1d, 0xb1, 0x96, 0x55, 0x86, 0xe1, 0x5c, 0xb6, 0xac, 0xb9, - /* (2^330)P */ 0x50, 0x9e, 0x37, 0x28, 0x7d, 0xa8, 0x33, 0x63, 0xda, 0x3f, 0x20, 0x98, 0x0e, 0x09, 0xa8, 0x77, 0x3b, 0x7a, 0xfc, 0x16, 0x85, 0x44, 0x64, 0x77, 0x65, 0x68, 0x92, 0x41, 0xc6, 0x1f, 0xdf, 0x27, 0xf9, 0xec, 0xa0, 0x61, 0x22, 0xea, 0x19, 0xe7, 0x75, 0x8b, 0x4e, 0xe5, 0x0f, 0xb7, 0xf7, 0xd2, 0x53, 0xf4, 0xdd, 0x4a, 0xaa, 0x78, 0x40, 0xb7, - /* (2^331)P */ 0xd4, 0x89, 0xe3, 0x79, 0xba, 0xb6, 0xc3, 0xda, 0xe6, 0x78, 0x65, 0x7d, 0x6e, 0x22, 0x62, 0xb1, 0x3d, 0xea, 0x90, 0x84, 0x30, 0x5e, 0xd4, 0x39, 0x84, 0x78, 0xd9, 0x75, 0xd6, 0xce, 0x2a, 0x11, 0x29, 0x69, 0xa4, 0x5e, 0xaa, 0x2a, 0x98, 0x5a, 0xe5, 0x91, 0x8f, 0xb2, 0xfb, 0xda, 0x97, 0xe8, 0x83, 0x6f, 0x04, 0xb9, 0x5d, 0xaf, 0xe1, 0x9b, - /* (2^332)P */ 0x8b, 0xe4, 0xe1, 0x48, 0x9c, 0xc4, 0x83, 0x89, 0xdf, 0x65, 0xd3, 0x35, 0x55, 0x13, 0xf4, 0x1f, 0x36, 0x92, 0x33, 0x38, 0xcb, 0xed, 0x15, 0xe6, 0x60, 0x2d, 0x25, 0xf5, 0x36, 0x60, 0x3a, 0x37, 0x9b, 0x71, 0x9d, 0x42, 0xb0, 0x14, 0xc8, 0xba, 0x62, 0xa3, 0x49, 0xb0, 0x88, 0xc1, 0x72, 0x73, 0xdd, 0x62, 0x40, 0xa9, 0x62, 0x88, 0x99, 0xca, - /* (2^333)P */ 0x47, 0x7b, 0xea, 0xda, 0x46, 0x2f, 0x45, 0xc6, 0xe3, 0xb4, 0x4d, 0x8d, 0xac, 0x0b, 0x54, 0x22, 0x06, 0x31, 0x16, 0x66, 0x3e, 0xe4, 0x38, 0x12, 0xcd, 0xf3, 0xe7, 0x99, 0x37, 0xd9, 0x62, 0x24, 0x4b, 0x05, 0xf2, 0x58, 0xe6, 0x29, 0x4b, 0x0d, 0xf6, 0xc1, 0xba, 0xa0, 0x1e, 0x0f, 0xcb, 0x1f, 0xc6, 0x2b, 0x19, 0xfc, 0x82, 0x01, 0xd0, 0x86, - /* (2^334)P */ 0xa2, 0xae, 0x77, 0x20, 0xfb, 0xa8, 0x18, 0xb4, 0x61, 0xef, 0xe8, 0x52, 0x79, 0xbb, 0x86, 0x90, 0x5d, 0x2e, 0x76, 0xed, 0x66, 0x60, 0x5d, 0x00, 0xb5, 0xa4, 0x00, 0x40, 0x89, 0xec, 0xd1, 0xd2, 0x0d, 0x26, 0xb9, 0x30, 0xb2, 0xd2, 0xb8, 0xe8, 0x0e, 0x56, 0xf9, 0x67, 0x94, 0x2e, 0x62, 0xe1, 0x79, 0x48, 0x2b, 0xa9, 0xfa, 0xea, 0xdb, 0x28, - /* (2^335)P */ 0x35, 0xf1, 0xb0, 0x43, 0xbd, 0x27, 0xef, 0x18, 0x44, 0xa2, 0x04, 0xb4, 0x69, 0xa1, 0x97, 0x1f, 0x8c, 0x04, 0x82, 0x9b, 0x00, 0x6d, 0xf8, 0xbf, 0x7d, 0xc1, 0x5b, 0xab, 0xe8, 0xb2, 0x34, 0xbd, 0xaf, 0x7f, 0xb2, 0x0d, 0xf3, 0xed, 0xfc, 0x5b, 0x50, 0xee, 0xe7, 0x4a, 0x20, 0xd9, 0xf5, 0xc6, 0x9a, 0x97, 0x6d, 0x07, 0x2f, 0xb9, 0x31, 0x02, - /* (2^336)P */ 0xf9, 0x54, 0x4a, 0xc5, 0x61, 0x7e, 0x1d, 0xa6, 0x0e, 0x1a, 0xa8, 0xd3, 0x8c, 0x36, 0x7d, 0xf1, 0x06, 0xb1, 0xac, 0x93, 0xcd, 0xe9, 0x8f, 0x61, 0x6c, 0x5d, 0x03, 0x23, 0xdf, 0x85, 0x53, 0x39, 0x63, 0x5e, 0xeb, 0xf3, 0xd3, 0xd3, 0x75, 0x97, 0x9b, 0x62, 0x9b, 0x01, 0xb3, 0x19, 0xd8, 0x2b, 0x36, 0xf2, 0x2c, 0x2c, 0x6f, 0x36, 0xc6, 0x3c, - /* (2^337)P */ 0x05, 0x74, 0x43, 0x10, 0xb6, 0xb0, 0xf8, 0xbf, 0x02, 0x46, 0x9a, 0xee, 0xc1, 0xaf, 0xc1, 0xe5, 0x5a, 0x2e, 0xbb, 0xe1, 0xdc, 0xc6, 0xce, 0x51, 0x29, 0x50, 0xbf, 0x1b, 0xde, 0xff, 0xba, 0x4d, 0x8d, 0x8b, 0x7e, 0xe7, 0xbd, 0x5b, 0x8f, 0xbe, 0xe3, 0x75, 0x71, 0xff, 0x37, 0x05, 0x5a, 0x10, 0xeb, 0x54, 0x7e, 0x44, 0x72, 0x2c, 0xd4, 0xfc, - /* (2^338)P */ 0x03, 0x12, 0x1c, 0xb2, 0x08, 0x90, 0xa1, 0x2d, 0x50, 0xa0, 0xad, 0x7f, 0x8d, 0xa6, 0x97, 0xc1, 0xbd, 0xdc, 0xc3, 0xa7, 0xad, 0x31, 0xdf, 0xb8, 0x03, 0x84, 0xc3, 0xb9, 0x29, 0x3d, 0x92, 0x2e, 0xc3, 0x90, 0x07, 0xe8, 0xa7, 0xc7, 0xbc, 0x61, 0xe9, 0x3e, 0xa0, 0x35, 0xda, 0x1d, 0xab, 0x48, 0xfe, 0x50, 0xc9, 0x25, 0x59, 0x23, 0x69, 0x3f, - /* (2^339)P */ 0x8e, 0x91, 0xab, 0x6b, 0x91, 0x4f, 0x89, 0x76, 0x67, 0xad, 0xb2, 0x65, 0x9d, 0xad, 0x02, 0x36, 0xdc, 0xac, 0x96, 0x93, 0x97, 0x21, 0x14, 0xd0, 0xe8, 0x11, 0x60, 0x1e, 0xeb, 0x96, 0x06, 0xf2, 0x53, 0xf2, 0x6d, 0xb7, 0x93, 0x6f, 0x26, 0x91, 0x23, 0xe3, 0x34, 0x04, 0x92, 0x91, 0x37, 0x08, 0x50, 0xd6, 0x28, 0x09, 0x27, 0xa1, 0x0c, 0x00, - /* (2^340)P */ 0x1f, 0xbb, 0x21, 0x26, 0x33, 0xcb, 0xa4, 0xd1, 0xee, 0x85, 0xf9, 0xd9, 0x3c, 0x90, 0xc3, 0xd1, 0x26, 0xa2, 0x25, 0x93, 0x43, 0x61, 0xed, 0x91, 0x6e, 0x54, 0x03, 0x2e, 0x42, 0x9d, 0xf7, 0xa6, 0x02, 0x0f, 0x2f, 0x9c, 0x7a, 0x8d, 0x12, 0xc2, 0x18, 0xfc, 0x41, 0xff, 0x85, 0x26, 0x1a, 0x44, 0x55, 0x0b, 0x89, 0xab, 0x6f, 0x62, 0x33, 0x8c, - /* (2^341)P */ 0xe0, 0x3c, 0x5d, 0x70, 0x64, 0x87, 0x81, 0x35, 0xf2, 0x37, 0xa6, 0x24, 0x3e, 0xe0, 0x62, 0xd5, 0x71, 0xe7, 0x93, 0xfb, 0xac, 0xc3, 0xe7, 0xc7, 0x04, 0xe2, 0x70, 0xd3, 0x29, 0x5b, 0x21, 0xbf, 0xf4, 0x26, 0x5d, 0xf3, 0x95, 0xb4, 0x2a, 0x6a, 0x07, 0x55, 0xa6, 0x4b, 0x3b, 0x15, 0xf2, 0x25, 0x8a, 0x95, 0x3f, 0x63, 0x2f, 0x7a, 0x23, 0x96, - /* (2^342)P */ 0x0d, 0x3d, 0xd9, 0x13, 0xa7, 0xb3, 0x5e, 0x67, 0xf7, 0x02, 0x23, 0xee, 0x84, 0xff, 0x99, 0xda, 0xb9, 0x53, 0xf8, 0xf0, 0x0e, 0x39, 0x2f, 0x3c, 0x64, 0x34, 0xe3, 0x09, 0xfd, 0x2b, 0x33, 0xc7, 0xfe, 0x62, 0x2b, 0x84, 0xdf, 0x2b, 0xd2, 0x7c, 0x26, 0x01, 0x70, 0x66, 0x5b, 0x85, 0xc2, 0xbe, 0x88, 0x37, 0xf1, 0x30, 0xac, 0xb8, 0x76, 0xa3, - /* (2^343)P */ 0x6e, 0x01, 0xf0, 0x55, 0x35, 0xe4, 0xbd, 0x43, 0x62, 0x9d, 0xd6, 0x11, 0xef, 0x6f, 0xb8, 0x8c, 0xaa, 0x98, 0x87, 0xc6, 0x6d, 0xc4, 0xcc, 0x74, 0x92, 0x53, 0x4a, 0xdf, 0xe4, 0x08, 0x89, 0x17, 0xd0, 0x0f, 0xf4, 0x00, 0x60, 0x78, 0x08, 0x44, 0xb5, 0xda, 0x18, 0xed, 0x98, 0xc8, 0x61, 0x3d, 0x39, 0xdb, 0xcf, 0x1d, 0x49, 0x40, 0x65, 0x75, - /* (2^344)P */ 0x8e, 0x10, 0xae, 0x5f, 0x06, 0xd2, 0x95, 0xfd, 0x20, 0x16, 0x49, 0x5b, 0x57, 0xbe, 0x22, 0x8b, 0x43, 0xfb, 0xe6, 0xcc, 0x26, 0xa5, 0x5d, 0xd3, 0x68, 0xc5, 0xf9, 0x5a, 0x86, 0x24, 0x87, 0x27, 0x05, 0xfd, 0xe2, 0xff, 0xb3, 0xa3, 0x7b, 0x37, 0x59, 0xc5, 0x4e, 0x14, 0x94, 0xf9, 0x3b, 0xcb, 0x7c, 0xed, 0xca, 0x1d, 0xb2, 0xac, 0x05, 0x4a, - /* (2^345)P */ 0xf4, 0xd1, 0x81, 0xeb, 0x89, 0xbf, 0xfe, 0x1e, 0x41, 0x92, 0x29, 0xee, 0xe1, 0x43, 0xf5, 0x86, 0x1d, 0x2f, 0xbb, 0x1e, 0x84, 0x5d, 0x7b, 0x8d, 0xd5, 0xda, 0xee, 0x1e, 0x8a, 0xd0, 0x27, 0xf2, 0x60, 0x51, 0x59, 0x82, 0xf4, 0x84, 0x2b, 0x5b, 0x14, 0x2d, 0x81, 0x82, 0x3e, 0x2b, 0xb4, 0x6d, 0x51, 0x4f, 0xc5, 0xcb, 0xbf, 0x74, 0xe3, 0xb4, - /* (2^346)P */ 0x19, 0x2f, 0x22, 0xb3, 0x04, 0x5f, 0x81, 0xca, 0x05, 0x60, 0xb9, 0xaa, 0xee, 0x0e, 0x2f, 0x48, 0x38, 0xf9, 0x91, 0xb4, 0x66, 0xe4, 0x57, 0x28, 0x54, 0x10, 0xe9, 0x61, 0x9d, 0xd4, 0x90, 0x75, 0xb1, 0x39, 0x23, 0xb6, 0xfc, 0x82, 0xe0, 0xfa, 0xbb, 0x5c, 0x6e, 0xc3, 0x44, 0x13, 0x00, 0x83, 0x55, 0x9e, 0x8e, 0x10, 0x61, 0x81, 0x91, 0x04, - /* (2^347)P */ 0x5f, 0x2a, 0xd7, 0x81, 0xd9, 0x9c, 0xbb, 0x79, 0xbc, 0x62, 0x56, 0x98, 0x03, 0x5a, 0x18, 0x85, 0x2a, 0x9c, 0xd0, 0xfb, 0xd2, 0xb1, 0xaf, 0xef, 0x0d, 0x24, 0xc5, 0xfa, 0x39, 0xbb, 0x6b, 0xed, 0xa4, 0xdf, 0xe4, 0x87, 0xcd, 0x41, 0xd3, 0x72, 0x32, 0xc6, 0x28, 0x21, 0xb1, 0xba, 0x8b, 0xa3, 0x91, 0x79, 0x76, 0x22, 0x25, 0x10, 0x61, 0xd1, - /* (2^348)P */ 0x73, 0xb5, 0x32, 0x97, 0xdd, 0xeb, 0xdd, 0x22, 0x22, 0xf1, 0x33, 0x3c, 0x77, 0x56, 0x7d, 0x6b, 0x48, 0x2b, 0x05, 0x81, 0x03, 0x03, 0x91, 0x9a, 0xe3, 0x5e, 0xd4, 0xee, 0x3f, 0xf8, 0xbb, 0x50, 0x21, 0x32, 0x4c, 0x4a, 0x58, 0x49, 0xde, 0x0c, 0xde, 0x30, 0x82, 0x3d, 0x92, 0xf0, 0x6c, 0xcc, 0x32, 0x3e, 0xd2, 0x78, 0x8a, 0x6e, 0x2c, 0xd0, - /* (2^349)P */ 0xf0, 0xf7, 0xa1, 0x0b, 0xc1, 0x74, 0x85, 0xa8, 0xe9, 0xdd, 0x48, 0xa1, 0xc0, 0x16, 0xd8, 0x2b, 0x61, 0x08, 0xc2, 0x2b, 0x30, 0x26, 0x79, 0xce, 0x9e, 0xfd, 0x39, 0xd7, 0x81, 0xa4, 0x63, 0x8c, 0xd5, 0x74, 0xa0, 0x88, 0xfa, 0x03, 0x30, 0xe9, 0x7f, 0x2b, 0xc6, 0x02, 0xc9, 0x5e, 0xe4, 0xd5, 0x4d, 0x92, 0xd0, 0xf6, 0xf2, 0x5b, 0x79, 0x08, - /* (2^350)P */ 0x34, 0x89, 0x81, 0x43, 0xd1, 0x94, 0x2c, 0x10, 0x54, 0x9b, 0xa0, 0xe5, 0x44, 0xe8, 0xc2, 0x2f, 0x3e, 0x0e, 0x74, 0xae, 0xba, 0xe2, 0xac, 0x85, 0x6b, 0xd3, 0x5c, 0x97, 0xf7, 0x90, 0xf1, 0x12, 0xc0, 0x03, 0xc8, 0x1f, 0x37, 0x72, 0x8c, 0x9b, 0x9c, 0x17, 0x96, 0x9d, 0xc7, 0xbf, 0xa3, 0x3f, 0x44, 0x3d, 0x87, 0x81, 0xbd, 0x81, 0xa6, 0x5f, - /* (2^351)P */ 0xe4, 0xff, 0x78, 0x62, 0x82, 0x5b, 0x76, 0x58, 0xf5, 0x5b, 0xa6, 0xc4, 0x53, 0x11, 0x3b, 0x7b, 0xaa, 0x67, 0xf8, 0xea, 0x3b, 0x5d, 0x9a, 0x2e, 0x04, 0xeb, 0x4a, 0x24, 0xfb, 0x56, 0xf0, 0xa8, 0xd4, 0x14, 0xed, 0x0f, 0xfd, 0xc5, 0x26, 0x17, 0x2a, 0xf0, 0xb9, 0x13, 0x8c, 0xbd, 0x65, 0x14, 0x24, 0x95, 0x27, 0x12, 0x63, 0x2a, 0x09, 0x18, - /* (2^352)P */ 0xe1, 0x5c, 0xe7, 0xe0, 0x00, 0x6a, 0x96, 0xf2, 0x49, 0x6a, 0x39, 0xa5, 0xe0, 0x17, 0x79, 0x4a, 0x63, 0x07, 0x62, 0x09, 0x61, 0x1b, 0x6e, 0xa9, 0xb5, 0x62, 0xb7, 0xde, 0xdf, 0x80, 0x4c, 0x5a, 0x99, 0x73, 0x59, 0x9d, 0xfb, 0xb1, 0x5e, 0xbe, 0xb8, 0xb7, 0x63, 0x93, 0xe8, 0xad, 0x5e, 0x1f, 0xae, 0x59, 0x1c, 0xcd, 0xb4, 0xc2, 0xb3, 0x8a, - /* (2^353)P */ 0x78, 0x53, 0xa1, 0x4c, 0x70, 0x9c, 0x63, 0x7e, 0xb3, 0x12, 0x40, 0x5f, 0xbb, 0x23, 0xa7, 0xf7, 0x77, 0x96, 0x5b, 0x4d, 0x91, 0x10, 0x52, 0x85, 0x9e, 0xa5, 0x38, 0x0b, 0xfd, 0x25, 0x01, 0x4b, 0xfa, 0x4d, 0xd3, 0x3f, 0x78, 0x74, 0x42, 0xff, 0x62, 0x2d, 0x27, 0xdc, 0x9d, 0xd1, 0x29, 0x76, 0x2e, 0x78, 0xb3, 0x35, 0xfa, 0x15, 0xd5, 0x38, - /* (2^354)P */ 0x8b, 0xc7, 0x43, 0xce, 0xf0, 0x5e, 0xf1, 0x0d, 0x02, 0x38, 0xe8, 0x82, 0xc9, 0x25, 0xad, 0x2d, 0x27, 0xa4, 0x54, 0x18, 0xb2, 0x30, 0x73, 0xa4, 0x41, 0x08, 0xe4, 0x86, 0xe6, 0x8c, 0xe9, 0x2a, 0x34, 0xb3, 0xd6, 0x61, 0x8f, 0x66, 0x26, 0x08, 0xb6, 0x06, 0x33, 0xaa, 0x12, 0xac, 0x72, 0xec, 0x2e, 0x52, 0xa3, 0x25, 0x3e, 0xd7, 0x62, 0xe8, - /* (2^355)P */ 0xc4, 0xbb, 0x89, 0xc8, 0x40, 0xcc, 0x84, 0xec, 0x4a, 0xd9, 0xc4, 0x55, 0x78, 0x00, 0xcf, 0xd8, 0xe9, 0x24, 0x59, 0xdc, 0x5e, 0xf0, 0x66, 0xa1, 0x83, 0xae, 0x97, 0x18, 0xc5, 0x54, 0x27, 0xa2, 0x21, 0x52, 0x03, 0x31, 0x5b, 0x11, 0x67, 0xf6, 0x12, 0x00, 0x87, 0x2f, 0xff, 0x59, 0x70, 0x8f, 0x6d, 0x71, 0xab, 0xab, 0x24, 0xb8, 0xba, 0x35, - /* (2^356)P */ 0x69, 0x43, 0xa7, 0x14, 0x06, 0x96, 0xe9, 0xc2, 0xe3, 0x2b, 0x45, 0x22, 0xc0, 0xd0, 0x2f, 0x34, 0xd1, 0x01, 0x99, 0xfc, 0x99, 0x38, 0xa1, 0x25, 0x2e, 0x59, 0x6c, 0x27, 0xc9, 0xeb, 0x7b, 0xdc, 0x4e, 0x26, 0x68, 0xba, 0xfa, 0xec, 0x02, 0x05, 0x64, 0x80, 0x30, 0x20, 0x5c, 0x26, 0x7f, 0xaf, 0x95, 0x17, 0x3d, 0x5c, 0x9e, 0x96, 0x96, 0xaf, - /* (2^357)P */ 0xa6, 0xba, 0x21, 0x29, 0x32, 0xe2, 0x98, 0xde, 0x9b, 0x6d, 0x0b, 0x44, 0x91, 0xa8, 0x3e, 0xd4, 0xb8, 0x04, 0x6c, 0xf6, 0x04, 0x39, 0xbd, 0x52, 0x05, 0x15, 0x27, 0x78, 0x8e, 0x55, 0xac, 0x79, 0xc5, 0xe6, 0x00, 0x7f, 0x90, 0xa2, 0xdd, 0x07, 0x13, 0xe0, 0x24, 0x70, 0x5c, 0x0f, 0x4d, 0xa9, 0xf9, 0xae, 0xcb, 0x34, 0x10, 0x9d, 0x89, 0x9d, - /* (2^358)P */ 0x12, 0xe0, 0xb3, 0x9f, 0xc4, 0x96, 0x1d, 0xcf, 0xed, 0x99, 0x64, 0x28, 0x8d, 0xc7, 0x31, 0x82, 0xee, 0x5e, 0x75, 0x48, 0xff, 0x3a, 0xf2, 0x09, 0x34, 0x03, 0x93, 0x52, 0x19, 0xb2, 0xc5, 0x81, 0x93, 0x45, 0x5e, 0x59, 0x21, 0x2b, 0xec, 0x89, 0xba, 0x36, 0x6e, 0xf9, 0x82, 0x75, 0x7e, 0x82, 0x3f, 0xaa, 0xe2, 0xe3, 0x3b, 0x94, 0xfd, 0x98, - /* (2^359)P */ 0x7c, 0xdb, 0x75, 0x31, 0x61, 0xfb, 0x15, 0x28, 0x94, 0xd7, 0xc3, 0x5a, 0xa9, 0xa1, 0x0a, 0x66, 0x0f, 0x2b, 0x13, 0x3e, 0x42, 0xb5, 0x28, 0x3a, 0xca, 0x83, 0xf3, 0x61, 0x22, 0xf4, 0x40, 0xc5, 0xdf, 0xe7, 0x31, 0x9f, 0x7e, 0x51, 0x75, 0x06, 0x9d, 0x51, 0xc8, 0xe7, 0x9f, 0xc3, 0x71, 0x4f, 0x3d, 0x5b, 0xfb, 0xe9, 0x8e, 0x08, 0x40, 0x8e, - /* (2^360)P */ 0xf7, 0x31, 0xad, 0x50, 0x5d, 0x25, 0x93, 0x73, 0x68, 0xf6, 0x7c, 0x89, 0x5a, 0x3d, 0x9f, 0x9b, 0x05, 0x82, 0xe7, 0x70, 0x4b, 0x19, 0xaa, 0xcf, 0xff, 0xde, 0x50, 0x8f, 0x2f, 0x69, 0xd3, 0xf0, 0x99, 0x51, 0x6b, 0x9d, 0xb6, 0x56, 0x6f, 0xf8, 0x4c, 0x74, 0x8b, 0x4c, 0x91, 0xf9, 0xa9, 0xb1, 0x3e, 0x07, 0xdf, 0x0b, 0x27, 0x8a, 0xb1, 0xed, - /* (2^361)P */ 0xfb, 0x67, 0xd9, 0x48, 0xd2, 0xe4, 0x44, 0x9b, 0x43, 0x15, 0x8a, 0xeb, 0x00, 0x53, 0xad, 0x25, 0xc7, 0x7e, 0x19, 0x30, 0x87, 0xb7, 0xd5, 0x5f, 0x04, 0xf8, 0xaa, 0xdd, 0x57, 0xae, 0x34, 0x75, 0xe2, 0x84, 0x4b, 0x54, 0x60, 0x37, 0x95, 0xe4, 0xd3, 0xec, 0xac, 0xef, 0x47, 0x31, 0xa3, 0xc8, 0x31, 0x22, 0xdb, 0x26, 0xe7, 0x6a, 0xb5, 0xad, - /* (2^362)P */ 0x44, 0x09, 0x5c, 0x95, 0xe4, 0x72, 0x3c, 0x1a, 0xd1, 0xac, 0x42, 0x51, 0x99, 0x6f, 0xfa, 0x1f, 0xf2, 0x22, 0xbe, 0xff, 0x7b, 0x66, 0xf5, 0x6c, 0xb3, 0x66, 0xc7, 0x4d, 0x78, 0x31, 0x83, 0x80, 0xf5, 0x41, 0xe9, 0x7f, 0xbe, 0xf7, 0x23, 0x49, 0x6b, 0x84, 0x4e, 0x7e, 0x47, 0x07, 0x6e, 0x74, 0xdf, 0xe5, 0x9d, 0x9e, 0x56, 0x2a, 0xc0, 0xbc, - /* (2^363)P */ 0xac, 0x10, 0x80, 0x8c, 0x7c, 0xfa, 0x83, 0xdf, 0xb3, 0xd0, 0xc4, 0xbe, 0xfb, 0x9f, 0xac, 0xc9, 0xc3, 0x40, 0x95, 0x0b, 0x09, 0x23, 0xda, 0x63, 0x67, 0xcf, 0xe7, 0x9f, 0x7d, 0x7b, 0x6b, 0xe2, 0xe6, 0x6d, 0xdb, 0x87, 0x9e, 0xa6, 0xff, 0x6d, 0xab, 0xbd, 0xfb, 0x54, 0x84, 0x68, 0xcf, 0x89, 0xf1, 0xd0, 0xe2, 0x85, 0x61, 0xdc, 0x22, 0xd1, - /* (2^364)P */ 0xa8, 0x48, 0xfb, 0x8c, 0x6a, 0x63, 0x01, 0x72, 0x43, 0x43, 0xeb, 0x21, 0xa3, 0x00, 0x8a, 0xc0, 0x87, 0x51, 0x9e, 0x86, 0x75, 0x16, 0x79, 0xf9, 0x6b, 0x11, 0x80, 0x62, 0xc2, 0x9d, 0xb8, 0x8c, 0x30, 0x8e, 0x8d, 0x03, 0x52, 0x7e, 0x31, 0x59, 0x38, 0xf9, 0x25, 0xc7, 0x0f, 0xc7, 0xa8, 0x2b, 0x5c, 0x80, 0xfa, 0x90, 0xa2, 0x63, 0xca, 0xe7, - /* (2^365)P */ 0xf1, 0x5d, 0xb5, 0xd9, 0x20, 0x10, 0x7d, 0x0f, 0xc5, 0x50, 0x46, 0x07, 0xff, 0x02, 0x75, 0x2b, 0x4a, 0xf3, 0x39, 0x91, 0x72, 0xb7, 0xd5, 0xcc, 0x38, 0xb8, 0xe7, 0x36, 0x26, 0x5e, 0x11, 0x97, 0x25, 0xfb, 0x49, 0x68, 0xdc, 0xb4, 0x46, 0x87, 0x5c, 0xc2, 0x7f, 0xaa, 0x7d, 0x36, 0x23, 0xa6, 0xc6, 0x53, 0xec, 0xbc, 0x57, 0x47, 0xc1, 0x2b, - /* (2^366)P */ 0x25, 0x5d, 0x7d, 0x95, 0xda, 0x0b, 0x8f, 0x78, 0x1e, 0x19, 0x09, 0xfa, 0x67, 0xe0, 0xa0, 0x17, 0x24, 0x76, 0x6c, 0x30, 0x1f, 0x62, 0x3d, 0xbe, 0x45, 0x70, 0xcc, 0xb6, 0x1e, 0x68, 0x06, 0x25, 0x68, 0x16, 0x1a, 0x33, 0x3f, 0x90, 0xc7, 0x78, 0x2d, 0x98, 0x3c, 0x2f, 0xb9, 0x2d, 0x94, 0x0b, 0xfb, 0x49, 0x56, 0x30, 0xd7, 0xc1, 0xe6, 0x48, - /* (2^367)P */ 0x7a, 0xd1, 0xe0, 0x8e, 0x67, 0xfc, 0x0b, 0x50, 0x1f, 0x84, 0x98, 0xfa, 0xaf, 0xae, 0x2e, 0x31, 0x27, 0xcf, 0x3f, 0xf2, 0x6e, 0x8d, 0x81, 0x8f, 0xd2, 0x5f, 0xde, 0xd3, 0x5e, 0xe9, 0xe7, 0x13, 0x48, 0x83, 0x5a, 0x4e, 0x84, 0xd1, 0x58, 0xcf, 0x6b, 0x84, 0xdf, 0x13, 0x1d, 0x91, 0x85, 0xe8, 0xcb, 0x29, 0x79, 0xd2, 0xca, 0xac, 0x6a, 0x93, - /* (2^368)P */ 0x53, 0x82, 0xce, 0x61, 0x96, 0x88, 0x6f, 0xe1, 0x4a, 0x4c, 0x1e, 0x30, 0x73, 0xe8, 0x74, 0xde, 0x40, 0x2b, 0xe0, 0xc4, 0xb5, 0xd8, 0x7c, 0x15, 0xe7, 0xe1, 0xb1, 0xe0, 0xd6, 0x88, 0xb1, 0x6a, 0x57, 0x19, 0x6a, 0x22, 0x66, 0x57, 0xf6, 0x8d, 0xfd, 0xc0, 0xf2, 0xa3, 0x03, 0x56, 0xfb, 0x2e, 0x75, 0x5e, 0xc7, 0x8e, 0x22, 0x96, 0x5c, 0x06, - /* (2^369)P */ 0x98, 0x7e, 0xbf, 0x3e, 0xbf, 0x24, 0x9d, 0x15, 0xd3, 0xf6, 0xd3, 0xd2, 0xf0, 0x11, 0xf2, 0xdb, 0x36, 0x23, 0x38, 0xf7, 0x1d, 0x71, 0x20, 0xd2, 0x54, 0x7f, 0x1e, 0x24, 0x8f, 0xe2, 0xaa, 0xf7, 0x3f, 0x6b, 0x41, 0x4e, 0xdc, 0x0e, 0xec, 0xe8, 0x35, 0x0a, 0x08, 0x6d, 0x89, 0x5b, 0x32, 0x91, 0x01, 0xb6, 0xe0, 0x2c, 0xc6, 0xa1, 0xbe, 0xb4, - /* (2^370)P */ 0x29, 0xf2, 0x1e, 0x1c, 0xdc, 0x68, 0x8a, 0x43, 0x87, 0x2c, 0x48, 0xb3, 0x9e, 0xed, 0xd2, 0x82, 0x46, 0xac, 0x2f, 0xef, 0x93, 0x34, 0x37, 0xca, 0x64, 0x8d, 0xc9, 0x06, 0x90, 0xbb, 0x78, 0x0a, 0x3c, 0x4c, 0xcf, 0x35, 0x7a, 0x0f, 0xf7, 0xa7, 0xf4, 0x2f, 0x45, 0x69, 0x3f, 0xa9, 0x5d, 0xce, 0x7b, 0x8a, 0x84, 0xc3, 0xae, 0xf4, 0xda, 0xd5, - /* (2^371)P */ 0xca, 0xba, 0x95, 0x43, 0x05, 0x7b, 0x06, 0xd9, 0x5c, 0x0a, 0x18, 0x5f, 0x6a, 0x6a, 0xce, 0xc0, 0x3d, 0x95, 0x51, 0x0e, 0x1a, 0xbe, 0x85, 0x7a, 0xf2, 0x69, 0xec, 0xc0, 0x8c, 0xca, 0xa3, 0x32, 0x0a, 0x76, 0x50, 0xc6, 0x76, 0x61, 0x00, 0x89, 0xbf, 0x6e, 0x0f, 0x48, 0x90, 0x31, 0x93, 0xec, 0x34, 0x70, 0xf0, 0xc3, 0x8d, 0xf0, 0x0f, 0xb5, - /* (2^372)P */ 0xbe, 0x23, 0xe2, 0x18, 0x99, 0xf1, 0xed, 0x8a, 0xf6, 0xc9, 0xac, 0xb8, 0x1e, 0x9a, 0x3c, 0x15, 0xae, 0xd7, 0x6d, 0xb3, 0x04, 0xee, 0x5b, 0x0d, 0x1e, 0x79, 0xb7, 0xf9, 0xf9, 0x8d, 0xad, 0xf9, 0x8f, 0x5a, 0x6a, 0x7b, 0xd7, 0x9b, 0xca, 0x62, 0xfe, 0x9c, 0xc0, 0x6f, 0x6d, 0x9d, 0x76, 0xa3, 0x69, 0xb9, 0x4c, 0xa1, 0xc4, 0x0c, 0x76, 0xaa, - /* (2^373)P */ 0x1c, 0x06, 0xfe, 0x3f, 0x45, 0x70, 0xcd, 0x97, 0xa9, 0xa2, 0xb1, 0xd3, 0xf2, 0xa5, 0x0c, 0x49, 0x2c, 0x75, 0x73, 0x1f, 0xcf, 0x00, 0xaf, 0xd5, 0x2e, 0xde, 0x0d, 0x8f, 0x8f, 0x7c, 0xc4, 0x58, 0xce, 0xd4, 0xf6, 0x24, 0x19, 0x2e, 0xd8, 0xc5, 0x1d, 0x1a, 0x3f, 0xb8, 0x4f, 0xbc, 0x7d, 0xbd, 0x68, 0xe3, 0x81, 0x98, 0x1b, 0xa8, 0xc9, 0xd9, - /* (2^374)P */ 0x39, 0x95, 0x78, 0x24, 0x6c, 0x38, 0xe4, 0xe7, 0xd0, 0x8d, 0xb9, 0x38, 0x71, 0x5e, 0xc1, 0x62, 0x80, 0xcc, 0xcb, 0x8c, 0x97, 0xca, 0xf8, 0xb9, 0xd9, 0x9c, 0xce, 0x72, 0x7b, 0x70, 0xee, 0x5f, 0xea, 0xa2, 0xdf, 0xa9, 0x14, 0x10, 0xf9, 0x6e, 0x59, 0x9f, 0x9c, 0xe0, 0x0c, 0xb2, 0x07, 0x97, 0xcd, 0xd2, 0x89, 0x16, 0xfd, 0x9c, 0xa8, 0xa5, - /* (2^375)P */ 0x5a, 0x61, 0xf1, 0x59, 0x7c, 0x38, 0xda, 0xe2, 0x85, 0x99, 0x68, 0xe9, 0xc9, 0xf7, 0x32, 0x7e, 0xc4, 0xca, 0xb7, 0x11, 0x08, 0x69, 0x2b, 0x66, 0x02, 0xf7, 0x2e, 0x18, 0xc3, 0x8e, 0xe1, 0xf9, 0xc5, 0x19, 0x9a, 0x0a, 0x9c, 0x07, 0xba, 0xc7, 0x9c, 0x03, 0x34, 0x89, 0x99, 0x67, 0x0b, 0x16, 0x4b, 0x07, 0x36, 0x16, 0x36, 0x2c, 0xe2, 0xa1, - /* (2^376)P */ 0x70, 0x10, 0x91, 0x27, 0xa8, 0x24, 0x8e, 0x29, 0x04, 0x6f, 0x79, 0x1f, 0xd3, 0xa5, 0x68, 0xd3, 0x0b, 0x7d, 0x56, 0x4d, 0x14, 0x57, 0x7b, 0x2e, 0x00, 0x9f, 0x9a, 0xfd, 0x6c, 0x63, 0x18, 0x81, 0xdb, 0x9d, 0xb7, 0xd7, 0xa4, 0x1e, 0xe8, 0x40, 0xf1, 0x4c, 0xa3, 0x01, 0xd5, 0x4b, 0x75, 0xea, 0xdd, 0x97, 0xfd, 0x5b, 0xb2, 0x66, 0x6a, 0x24, - /* (2^377)P */ 0x72, 0x11, 0xfe, 0x73, 0x1b, 0xd3, 0xea, 0x7f, 0x93, 0x15, 0x15, 0x05, 0xfe, 0x40, 0xe8, 0x28, 0xd8, 0x50, 0x47, 0x66, 0xfa, 0xb7, 0xb5, 0x04, 0xba, 0x35, 0x1e, 0x32, 0x9f, 0x5f, 0x32, 0xba, 0x3d, 0xd1, 0xed, 0x9a, 0x76, 0xca, 0xa3, 0x3e, 0x77, 0xd8, 0xd8, 0x7c, 0x5f, 0x68, 0x42, 0xb5, 0x86, 0x7f, 0x3b, 0xc9, 0xc1, 0x89, 0x64, 0xda, - /* (2^378)P */ 0xd5, 0xd4, 0x17, 0x31, 0xfc, 0x6a, 0xfd, 0xb8, 0xe8, 0xe5, 0x3e, 0x39, 0x06, 0xe4, 0xd1, 0x90, 0x2a, 0xca, 0xf6, 0x54, 0x6c, 0x1b, 0x2f, 0x49, 0x97, 0xb1, 0x2a, 0x82, 0x43, 0x3d, 0x1f, 0x8b, 0xe2, 0x47, 0xc5, 0x24, 0xa8, 0xd5, 0x53, 0x29, 0x7d, 0xc6, 0x87, 0xa6, 0x25, 0x3a, 0x64, 0xdd, 0x71, 0x08, 0x9e, 0xcd, 0xe9, 0x45, 0xc7, 0xba, - /* (2^379)P */ 0x37, 0x72, 0x6d, 0x13, 0x7a, 0x8d, 0x04, 0x31, 0xe6, 0xe3, 0x9e, 0x36, 0x71, 0x3e, 0xc0, 0x1e, 0xe3, 0x71, 0xd3, 0x49, 0x4e, 0x4a, 0x36, 0x42, 0x68, 0x68, 0x61, 0xc7, 0x3c, 0xdb, 0x81, 0x49, 0xf7, 0x91, 0x4d, 0xea, 0x4c, 0x4f, 0x98, 0xc6, 0x7e, 0x60, 0x84, 0x4b, 0x6a, 0x37, 0xbb, 0x52, 0xf7, 0xce, 0x02, 0xe4, 0xad, 0xd1, 0x3c, 0xa7, - /* (2^380)P */ 0x51, 0x06, 0x2d, 0xf8, 0x08, 0xe8, 0xf1, 0x0c, 0xe5, 0xa9, 0xac, 0x29, 0x73, 0x3b, 0xed, 0x98, 0x5f, 0x55, 0x08, 0x38, 0x51, 0x44, 0x36, 0x5d, 0xea, 0xc3, 0xb8, 0x0e, 0xa0, 0x4f, 0xd2, 0x79, 0xe9, 0x98, 0xc3, 0xf5, 0x00, 0xb9, 0x26, 0x27, 0x42, 0xa8, 0x07, 0xc1, 0x12, 0x31, 0xc1, 0xc3, 0x3c, 0x3b, 0x7a, 0x72, 0x97, 0xc2, 0x70, 0x3a, - /* (2^381)P */ 0xf4, 0xb2, 0xba, 0x32, 0xbc, 0xa9, 0x2f, 0x87, 0xc7, 0x3c, 0x45, 0xcd, 0xae, 0xe2, 0x13, 0x6d, 0x3a, 0xf2, 0xf5, 0x66, 0x97, 0x29, 0xaf, 0x53, 0x9f, 0xda, 0xea, 0x14, 0xdf, 0x04, 0x98, 0x19, 0x95, 0x9e, 0x2a, 0x00, 0x5c, 0x9d, 0x1d, 0xf0, 0x39, 0x23, 0xff, 0xfc, 0xca, 0x36, 0xb7, 0xde, 0xdf, 0x37, 0x78, 0x52, 0x21, 0xfa, 0x19, 0x10, - /* (2^382)P */ 0x50, 0x20, 0x73, 0x74, 0x62, 0x21, 0xf2, 0xf7, 0x9b, 0x66, 0x85, 0x34, 0x74, 0xd4, 0x9d, 0x60, 0xd7, 0xbc, 0xc8, 0x46, 0x3b, 0xb8, 0x80, 0x42, 0x15, 0x0a, 0x6c, 0x35, 0x1a, 0x69, 0xf0, 0x1d, 0x4b, 0x29, 0x54, 0x5a, 0x9a, 0x48, 0xec, 0x9f, 0x37, 0x74, 0x91, 0xd0, 0xd1, 0x9e, 0x00, 0xc2, 0x76, 0x56, 0xd6, 0xa0, 0x15, 0x14, 0x83, 0x59, - /* (2^383)P */ 0xc2, 0xf8, 0x22, 0x20, 0x23, 0x07, 0xbd, 0x1d, 0x6f, 0x1e, 0x8c, 0x56, 0x06, 0x6a, 0x4b, 0x9f, 0xe2, 0xa9, 0x92, 0x46, 0x4b, 0x46, 0x59, 0xd7, 0xe1, 0xda, 0x14, 0x98, 0x07, 0x65, 0x7e, 0x28, 0x20, 0xf2, 0x9d, 0x4f, 0x36, 0x5c, 0x92, 0xe0, 0x9d, 0xfe, 0x3e, 0xda, 0xe4, 0x47, 0x19, 0x3c, 0x00, 0x7f, 0x22, 0xf2, 0x9e, 0x51, 0xae, 0x4d, - /* (2^384)P */ 0xbe, 0x8c, 0x1b, 0x10, 0xb6, 0xad, 0xcc, 0xcc, 0xd8, 0x5e, 0x21, 0xa6, 0xfb, 0xf1, 0xf6, 0xbd, 0x0a, 0x24, 0x67, 0xb4, 0x57, 0x7a, 0xbc, 0xe8, 0xe9, 0xff, 0xee, 0x0a, 0x1f, 0xee, 0xbd, 0xc8, 0x44, 0xed, 0x2b, 0xbb, 0x55, 0x1f, 0xdd, 0x7c, 0xb3, 0xeb, 0x3f, 0x63, 0xa1, 0x28, 0x91, 0x21, 0xab, 0x71, 0xc6, 0x4c, 0xd0, 0xe9, 0xb0, 0x21, - /* (2^385)P */ 0xad, 0xc9, 0x77, 0x2b, 0xee, 0x89, 0xa4, 0x7b, 0xfd, 0xf9, 0xf6, 0x14, 0xe4, 0xed, 0x1a, 0x16, 0x9b, 0x78, 0x41, 0x43, 0xa8, 0x83, 0x72, 0x06, 0x2e, 0x7c, 0xdf, 0xeb, 0x7e, 0xdd, 0xd7, 0x8b, 0xea, 0x9a, 0x2b, 0x03, 0xba, 0x57, 0xf3, 0xf1, 0xd9, 0xe5, 0x09, 0xc5, 0x98, 0x61, 0x1c, 0x51, 0x6d, 0x5d, 0x6e, 0xfb, 0x5e, 0x95, 0x9f, 0xb5, - /* (2^386)P */ 0x23, 0xe2, 0x1e, 0x95, 0xa3, 0x5e, 0x42, 0x10, 0xc7, 0xc3, 0x70, 0xbf, 0x4b, 0x6b, 0x83, 0x36, 0x93, 0xb7, 0x68, 0x47, 0x88, 0x3a, 0x10, 0x88, 0x48, 0x7f, 0x8c, 0xae, 0x54, 0x10, 0x02, 0xa4, 0x52, 0x8f, 0x8d, 0xf7, 0x26, 0x4f, 0x50, 0xc3, 0x6a, 0xe2, 0x4e, 0x3b, 0x4c, 0xb9, 0x8a, 0x14, 0x15, 0x6d, 0x21, 0x29, 0xb3, 0x6e, 0x4e, 0xd0, - /* (2^387)P */ 0x4c, 0x8a, 0x18, 0x3f, 0xb7, 0x20, 0xfd, 0x3e, 0x54, 0xca, 0x68, 0x3c, 0xea, 0x6f, 0xf4, 0x6b, 0xa2, 0xbd, 0x01, 0xbd, 0xfe, 0x08, 0xa8, 0xd8, 0xc2, 0x20, 0x36, 0x05, 0xcd, 0xe9, 0xf3, 0x9e, 0xfa, 0x85, 0x66, 0x8f, 0x4b, 0x1d, 0x8c, 0x64, 0x4f, 0xb8, 0xc6, 0x0f, 0x5b, 0x57, 0xd8, 0x24, 0x19, 0x5a, 0x14, 0x4b, 0x92, 0xd3, 0x96, 0xbc, - /* (2^388)P */ 0xa9, 0x3f, 0xc9, 0x6c, 0xca, 0x64, 0x1e, 0x6f, 0xdf, 0x65, 0x7f, 0x9a, 0x47, 0x6b, 0x8a, 0x60, 0x31, 0xa6, 0x06, 0xac, 0x69, 0x30, 0xe6, 0xea, 0x63, 0x42, 0x26, 0x5f, 0xdb, 0xd0, 0xf2, 0x8e, 0x34, 0x0a, 0x3a, 0xeb, 0xf3, 0x79, 0xc8, 0xb7, 0x60, 0x56, 0x5c, 0x37, 0x95, 0x71, 0xf8, 0x7f, 0x49, 0x3e, 0x9e, 0x01, 0x26, 0x1e, 0x80, 0x9f, - /* (2^389)P */ 0xf8, 0x16, 0x9a, 0xaa, 0xb0, 0x28, 0xb5, 0x8e, 0xd0, 0x60, 0xe5, 0x26, 0xa9, 0x47, 0xc4, 0x5c, 0xa9, 0x39, 0xfe, 0x0a, 0xd8, 0x07, 0x2b, 0xb3, 0xce, 0xf1, 0xea, 0x1a, 0xf4, 0x7b, 0x98, 0x31, 0x3d, 0x13, 0x29, 0x80, 0xe8, 0x0d, 0xcf, 0x56, 0x39, 0x86, 0x50, 0x0c, 0xb3, 0x18, 0xf4, 0xc5, 0xca, 0xf2, 0x6f, 0xcd, 0x8d, 0xd5, 0x02, 0xb0, - /* (2^390)P */ 0xbf, 0x39, 0x3f, 0xac, 0x6d, 0x1a, 0x6a, 0xe4, 0x42, 0x24, 0xd6, 0x41, 0x9d, 0xb9, 0x5b, 0x46, 0x73, 0x93, 0x76, 0xaa, 0xb7, 0x37, 0x36, 0xa6, 0x09, 0xe5, 0x04, 0x3b, 0x66, 0xc4, 0x29, 0x3e, 0x41, 0xc2, 0xcb, 0xe5, 0x17, 0xd7, 0x34, 0x67, 0x1d, 0x2c, 0x12, 0xec, 0x24, 0x7a, 0x40, 0xa2, 0x45, 0x41, 0xf0, 0x75, 0xed, 0x43, 0x30, 0xc9, - /* (2^391)P */ 0x80, 0xf6, 0x47, 0x5b, 0xad, 0x54, 0x02, 0xbc, 0xdd, 0xa4, 0xb2, 0xd7, 0x42, 0x95, 0xf2, 0x0d, 0x1b, 0xef, 0x37, 0xa7, 0xb4, 0x34, 0x04, 0x08, 0x71, 0x1b, 0xd3, 0xdf, 0xa1, 0xf0, 0x2b, 0xfa, 0xc0, 0x1f, 0xf3, 0x44, 0xb5, 0xc6, 0x47, 0x3d, 0x65, 0x67, 0x45, 0x4d, 0x2f, 0xde, 0x52, 0x73, 0xfc, 0x30, 0x01, 0x6b, 0xc1, 0x03, 0xd8, 0xd7, - /* (2^392)P */ 0x1c, 0x67, 0x55, 0x3e, 0x01, 0x17, 0x0f, 0x3e, 0xe5, 0x34, 0x58, 0xfc, 0xcb, 0x71, 0x24, 0x74, 0x5d, 0x36, 0x1e, 0x89, 0x2a, 0x63, 0xf8, 0xf8, 0x9f, 0x50, 0x9f, 0x32, 0x92, 0x29, 0xd8, 0x1a, 0xec, 0x76, 0x57, 0x6c, 0x67, 0x12, 0x6a, 0x6e, 0xef, 0x97, 0x1f, 0xc3, 0x77, 0x60, 0x3c, 0x22, 0xcb, 0xc7, 0x04, 0x1a, 0x89, 0x2d, 0x10, 0xa6, - /* (2^393)P */ 0x12, 0xf5, 0xa9, 0x26, 0x16, 0xd9, 0x3c, 0x65, 0x5d, 0x83, 0xab, 0xd1, 0x70, 0x6b, 0x1c, 0xdb, 0xe7, 0x86, 0x0d, 0xfb, 0xe7, 0xf8, 0x2a, 0x58, 0x6e, 0x7a, 0x66, 0x13, 0x53, 0x3a, 0x6f, 0x8d, 0x43, 0x5f, 0x14, 0x23, 0x14, 0xff, 0x3d, 0x52, 0x7f, 0xee, 0xbd, 0x7a, 0x34, 0x8b, 0x35, 0x24, 0xc3, 0x7a, 0xdb, 0xcf, 0x22, 0x74, 0x9a, 0x8f, - /* (2^394)P */ 0xdb, 0x20, 0xfc, 0xe5, 0x39, 0x4e, 0x7d, 0x78, 0xee, 0x0b, 0xbf, 0x1d, 0x80, 0xd4, 0x05, 0x4f, 0xb9, 0xd7, 0x4e, 0x94, 0x88, 0x9a, 0x50, 0x78, 0x1a, 0x70, 0x8c, 0xcc, 0x25, 0xb6, 0x61, 0x09, 0xdc, 0x7b, 0xea, 0x3f, 0x7f, 0xea, 0x2a, 0x0d, 0x47, 0x1c, 0x8e, 0xa6, 0x5b, 0xd2, 0xa3, 0x61, 0x93, 0x3c, 0x68, 0x9f, 0x8b, 0xea, 0xb0, 0xcb, - /* (2^395)P */ 0xff, 0x54, 0x02, 0x19, 0xae, 0x8b, 0x4c, 0x2c, 0x3a, 0xe0, 0xe4, 0xac, 0x87, 0xf7, 0x51, 0x45, 0x41, 0x43, 0xdc, 0xaa, 0xcd, 0xcb, 0xdc, 0x40, 0xe3, 0x44, 0x3b, 0x1d, 0x9e, 0x3d, 0xb9, 0x82, 0xcc, 0x7a, 0xc5, 0x12, 0xf8, 0x1e, 0xdd, 0xdb, 0x8d, 0xb0, 0x2a, 0xe8, 0xe6, 0x6c, 0x94, 0x3b, 0xb7, 0x2d, 0xba, 0x79, 0x3b, 0xb5, 0x86, 0xfb, - /* (2^396)P */ 0x82, 0x88, 0x13, 0xdd, 0x6c, 0xcd, 0x85, 0x2b, 0x90, 0x86, 0xb7, 0xac, 0x16, 0xa6, 0x6e, 0x6a, 0x94, 0xd8, 0x1e, 0x4e, 0x41, 0x0f, 0xce, 0x81, 0x6a, 0xa8, 0x26, 0x56, 0x43, 0x52, 0x52, 0xe6, 0xff, 0x88, 0xcf, 0x47, 0x05, 0x1d, 0xff, 0xf3, 0xa0, 0x10, 0xb2, 0x97, 0x87, 0xeb, 0x47, 0xbb, 0xfa, 0x1f, 0xe8, 0x4c, 0xce, 0xc4, 0xcd, 0x93, - /* (2^397)P */ 0xf4, 0x11, 0xf5, 0x8d, 0x89, 0x29, 0x79, 0xb3, 0x59, 0x0b, 0x29, 0x7d, 0x9c, 0x12, 0x4a, 0x65, 0x72, 0x3a, 0xf9, 0xec, 0x37, 0x18, 0x86, 0xef, 0x44, 0x07, 0x25, 0x74, 0x76, 0x53, 0xed, 0x51, 0x01, 0xc6, 0x28, 0xc5, 0xc3, 0x4a, 0x0f, 0x99, 0xec, 0xc8, 0x40, 0x5a, 0x83, 0x30, 0x79, 0xa2, 0x3e, 0x63, 0x09, 0x2d, 0x6f, 0x23, 0x54, 0x1c, - /* (2^398)P */ 0x5c, 0x6f, 0x3b, 0x1c, 0x30, 0x77, 0x7e, 0x87, 0x66, 0x83, 0x2e, 0x7e, 0x85, 0x50, 0xfd, 0xa0, 0x7a, 0xc2, 0xf5, 0x0f, 0xc1, 0x64, 0xe7, 0x0b, 0xbd, 0x59, 0xa7, 0xe7, 0x65, 0x53, 0xc3, 0xf5, 0x55, 0x5b, 0xe1, 0x82, 0x30, 0x5a, 0x61, 0xcd, 0xa0, 0x89, 0x32, 0xdb, 0x87, 0xfc, 0x21, 0x8a, 0xab, 0x6d, 0x82, 0xa8, 0x42, 0x81, 0x4f, 0xf2, - /* (2^399)P */ 0xb3, 0xeb, 0x88, 0x18, 0xf6, 0x56, 0x96, 0xbf, 0xba, 0x5d, 0x71, 0xa1, 0x5a, 0xd1, 0x04, 0x7b, 0xd5, 0x46, 0x01, 0x74, 0xfe, 0x15, 0x25, 0xb7, 0xff, 0x0c, 0x24, 0x47, 0xac, 0xfd, 0xab, 0x47, 0x32, 0xe1, 0x6a, 0x4e, 0xca, 0xcf, 0x7f, 0xdd, 0xf8, 0xd2, 0x4b, 0x3b, 0xf5, 0x17, 0xba, 0xba, 0x8b, 0xa1, 0xec, 0x28, 0x3f, 0x97, 0xab, 0x2a, - /* (2^400)P */ 0x51, 0x38, 0xc9, 0x5e, 0xc6, 0xb3, 0x64, 0xf2, 0x24, 0x4d, 0x04, 0x7d, 0xc8, 0x39, 0x0c, 0x4a, 0xc9, 0x73, 0x74, 0x1b, 0x5c, 0xb2, 0xc5, 0x41, 0x62, 0xa0, 0x4c, 0x6d, 0x8d, 0x91, 0x9a, 0x7b, 0x88, 0xab, 0x9c, 0x7e, 0x23, 0xdb, 0x6f, 0xb5, 0x72, 0xd6, 0x47, 0x40, 0xef, 0x22, 0x58, 0x62, 0x19, 0x6c, 0x38, 0xba, 0x5b, 0x00, 0x30, 0x9f, - /* (2^401)P */ 0x65, 0xbb, 0x3b, 0x9b, 0xe9, 0xae, 0xbf, 0xbe, 0xe4, 0x13, 0x95, 0xf3, 0xe3, 0x77, 0xcb, 0xe4, 0x9a, 0x22, 0xb5, 0x4a, 0x08, 0x9d, 0xb3, 0x9e, 0x27, 0xe0, 0x15, 0x6c, 0x9f, 0x7e, 0x9a, 0x5e, 0x15, 0x45, 0x25, 0x8d, 0x01, 0x0a, 0xd2, 0x2b, 0xbd, 0x48, 0x06, 0x0d, 0x18, 0x97, 0x4b, 0xdc, 0xbc, 0xf0, 0xcd, 0xb2, 0x52, 0x3c, 0xac, 0xf5, - /* (2^402)P */ 0x3e, 0xed, 0x47, 0x6b, 0x5c, 0xf6, 0x76, 0xd0, 0xe9, 0x15, 0xa3, 0xcb, 0x36, 0x00, 0x21, 0xa3, 0x79, 0x20, 0xa5, 0x3e, 0x88, 0x03, 0xcb, 0x7e, 0x63, 0xbb, 0xed, 0xa9, 0x13, 0x35, 0x16, 0xaf, 0x2e, 0xb4, 0x70, 0x14, 0x93, 0xfb, 0xc4, 0x9b, 0xd8, 0xb1, 0xbe, 0x43, 0xd1, 0x85, 0xb8, 0x97, 0xef, 0xea, 0x88, 0xa1, 0x25, 0x52, 0x62, 0x75, - /* (2^403)P */ 0x8e, 0x4f, 0xaa, 0x23, 0x62, 0x7e, 0x2b, 0x37, 0x89, 0x00, 0x11, 0x30, 0xc5, 0x33, 0x4a, 0x89, 0x8a, 0xe2, 0xfc, 0x5c, 0x6a, 0x75, 0xe5, 0xf7, 0x02, 0x4a, 0x9b, 0xf7, 0xb5, 0x6a, 0x85, 0x31, 0xd3, 0x5a, 0xcf, 0xc3, 0xf8, 0xde, 0x2f, 0xcf, 0xb5, 0x24, 0xf4, 0xe3, 0xa1, 0xad, 0x42, 0xae, 0x09, 0xb9, 0x2e, 0x04, 0x2d, 0x01, 0x22, 0x3f, - /* (2^404)P */ 0x41, 0x16, 0xfb, 0x7d, 0x50, 0xfd, 0xb5, 0xba, 0x88, 0x24, 0xba, 0xfd, 0x3d, 0xb2, 0x90, 0x15, 0xb7, 0xfa, 0xa2, 0xe1, 0x4c, 0x7d, 0xb9, 0xc6, 0xff, 0x81, 0x57, 0xb6, 0xc2, 0x9e, 0xcb, 0xc4, 0x35, 0xbd, 0x01, 0xb7, 0xaa, 0xce, 0xd0, 0xe9, 0xb5, 0xd6, 0x72, 0xbf, 0xd2, 0xee, 0xc7, 0xac, 0x94, 0xff, 0x29, 0x57, 0x02, 0x49, 0x09, 0xad, - /* (2^405)P */ 0x27, 0xa5, 0x78, 0x1b, 0xbf, 0x6b, 0xaf, 0x0b, 0x8c, 0xd9, 0xa8, 0x37, 0xb0, 0x67, 0x18, 0xb6, 0xc7, 0x05, 0x8a, 0x67, 0x03, 0x30, 0x62, 0x6e, 0x56, 0x82, 0xa9, 0x54, 0x3e, 0x0c, 0x4e, 0x07, 0xe1, 0x5a, 0x38, 0xed, 0xfa, 0xc8, 0x55, 0x6b, 0x08, 0xa3, 0x6b, 0x64, 0x2a, 0x15, 0xd6, 0x39, 0x6f, 0x47, 0x99, 0x42, 0x3f, 0x33, 0x84, 0x8f, - /* (2^406)P */ 0xbc, 0x45, 0x29, 0x81, 0x0e, 0xa4, 0xc5, 0x72, 0x3a, 0x10, 0xe1, 0xc4, 0x1e, 0xda, 0xc3, 0xfe, 0xb0, 0xce, 0xd2, 0x13, 0x34, 0x67, 0x21, 0xc6, 0x7e, 0xf9, 0x8c, 0xff, 0x39, 0x50, 0xae, 0x92, 0x60, 0x35, 0x2f, 0x8b, 0x6e, 0xc9, 0xc1, 0x27, 0x3a, 0x94, 0x66, 0x3e, 0x26, 0x84, 0x93, 0xc8, 0x6c, 0xcf, 0xd2, 0x03, 0xa1, 0x10, 0xcf, 0xb7, - /* (2^407)P */ 0x64, 0xda, 0x19, 0xf6, 0xc5, 0x73, 0x17, 0x44, 0x88, 0x81, 0x07, 0x0d, 0x34, 0xb2, 0x75, 0xf9, 0xd9, 0xe2, 0xe0, 0x8b, 0x71, 0xcf, 0x72, 0x34, 0x83, 0xb4, 0xce, 0xfc, 0xd7, 0x29, 0x09, 0x5a, 0x98, 0xbf, 0x14, 0xac, 0x77, 0x55, 0x38, 0x47, 0x5b, 0x0f, 0x40, 0x24, 0xe5, 0xa5, 0xa6, 0xac, 0x2d, 0xa6, 0xff, 0x9c, 0x73, 0xfe, 0x5c, 0x7e, - /* (2^408)P */ 0x1e, 0x33, 0xcc, 0x68, 0xb2, 0xbc, 0x8c, 0x93, 0xaf, 0xcc, 0x38, 0xf8, 0xd9, 0x16, 0x72, 0x50, 0xac, 0xd9, 0xb5, 0x0b, 0x9a, 0xbe, 0x46, 0x7a, 0xf1, 0xee, 0xf1, 0xad, 0xec, 0x5b, 0x59, 0x27, 0x9c, 0x05, 0xa3, 0x87, 0xe0, 0x37, 0x2c, 0x83, 0xce, 0xb3, 0x65, 0x09, 0x8e, 0xc3, 0x9c, 0xbf, 0x6a, 0xa2, 0x00, 0xcc, 0x12, 0x36, 0xc5, 0x95, - /* (2^409)P */ 0x36, 0x11, 0x02, 0x14, 0x9c, 0x3c, 0xeb, 0x2f, 0x23, 0x5b, 0x6b, 0x2b, 0x08, 0x54, 0x53, 0xac, 0xb2, 0xa3, 0xe0, 0x26, 0x62, 0x3c, 0xe4, 0xe1, 0x81, 0xee, 0x13, 0x3e, 0xa4, 0x97, 0xef, 0xf9, 0x92, 0x27, 0x01, 0xce, 0x54, 0x8b, 0x3e, 0x31, 0xbe, 0xa7, 0x88, 0xcf, 0x47, 0x99, 0x3c, 0x10, 0x6f, 0x60, 0xb3, 0x06, 0x4e, 0xee, 0x1b, 0xf0, - /* (2^410)P */ 0x59, 0x49, 0x66, 0xcf, 0x22, 0xe6, 0xf6, 0x73, 0xfe, 0xa3, 0x1c, 0x09, 0xfa, 0x5f, 0x65, 0xa8, 0xf0, 0x82, 0xc2, 0xef, 0x16, 0x63, 0x6e, 0x79, 0x69, 0x51, 0x39, 0x07, 0x65, 0xc4, 0x81, 0xec, 0x73, 0x0f, 0x15, 0x93, 0xe1, 0x30, 0x33, 0xe9, 0x37, 0x86, 0x42, 0x4c, 0x1f, 0x9b, 0xad, 0xee, 0x3f, 0xf1, 0x2a, 0x8e, 0x6a, 0xa3, 0xc8, 0x35, - /* (2^411)P */ 0x1e, 0x49, 0xf1, 0xdd, 0xd2, 0x9c, 0x8e, 0x78, 0xb2, 0x06, 0xe4, 0x6a, 0xab, 0x3a, 0xdc, 0xcd, 0xf4, 0xeb, 0xe1, 0xe7, 0x2f, 0xaa, 0xeb, 0x40, 0x31, 0x9f, 0xb9, 0xab, 0x13, 0xa9, 0x78, 0xbf, 0x38, 0x89, 0x0e, 0x85, 0x14, 0x8b, 0x46, 0x76, 0x14, 0xda, 0xcf, 0x33, 0xc8, 0x79, 0xd3, 0xd5, 0xa3, 0x6a, 0x69, 0x45, 0x70, 0x34, 0xc3, 0xe9, - /* (2^412)P */ 0x5e, 0xe7, 0x78, 0xe9, 0x24, 0xcc, 0xe9, 0xf4, 0xc8, 0x6b, 0xe0, 0xfb, 0x3a, 0xbe, 0xcc, 0x42, 0x4a, 0x00, 0x22, 0xf8, 0xe6, 0x32, 0xbe, 0x6d, 0x18, 0x55, 0x60, 0xe9, 0x72, 0x69, 0x50, 0x56, 0xca, 0x04, 0x18, 0x38, 0xa1, 0xee, 0xd8, 0x38, 0x3c, 0xa7, 0x70, 0xe2, 0xb9, 0x4c, 0xa0, 0xc8, 0x89, 0x72, 0xcf, 0x49, 0x7f, 0xdf, 0xbc, 0x67, - /* (2^413)P */ 0x1d, 0x17, 0xcb, 0x0b, 0xbd, 0xb2, 0x36, 0xe3, 0xa8, 0x99, 0x31, 0xb6, 0x26, 0x9c, 0x0c, 0x74, 0xaf, 0x4d, 0x24, 0x61, 0xcf, 0x31, 0x7b, 0xed, 0xdd, 0xc3, 0xf6, 0x32, 0x70, 0xfe, 0x17, 0xf6, 0x51, 0x37, 0x65, 0xce, 0x5d, 0xaf, 0xa5, 0x2f, 0x2a, 0xfe, 0x00, 0x71, 0x7c, 0x50, 0xbe, 0x21, 0xc7, 0xed, 0xc6, 0xfc, 0x67, 0xcf, 0x9c, 0xdd, - /* (2^414)P */ 0x26, 0x3e, 0xf8, 0xbb, 0xd0, 0xb1, 0x01, 0xd8, 0xeb, 0x0b, 0x62, 0x87, 0x35, 0x4c, 0xde, 0xca, 0x99, 0x9c, 0x6d, 0xf7, 0xb6, 0xf0, 0x57, 0x0a, 0x52, 0x29, 0x6a, 0x3f, 0x26, 0x31, 0x04, 0x07, 0x2a, 0xc9, 0xfa, 0x9b, 0x0e, 0x62, 0x8e, 0x72, 0xf2, 0xad, 0xce, 0xb6, 0x35, 0x7a, 0xc1, 0xae, 0x35, 0xc7, 0xa3, 0x14, 0xcf, 0x0c, 0x28, 0xb7, - /* (2^415)P */ 0xa6, 0xf1, 0x32, 0x3a, 0x20, 0xd2, 0x24, 0x97, 0xcf, 0x5d, 0x37, 0x99, 0xaf, 0x33, 0x7a, 0x5b, 0x7a, 0xcc, 0x4e, 0x41, 0x38, 0xb1, 0x4e, 0xad, 0xc9, 0xd9, 0x71, 0x7e, 0xb2, 0xf5, 0xd5, 0x01, 0x6c, 0x4d, 0xfd, 0xa1, 0xda, 0x03, 0x38, 0x9b, 0x3d, 0x92, 0x92, 0xf2, 0xca, 0xbf, 0x1f, 0x24, 0xa4, 0xbb, 0x30, 0x6a, 0x74, 0x56, 0xc8, 0xce, - /* (2^416)P */ 0x27, 0xf4, 0xed, 0xc9, 0xc3, 0xb1, 0x79, 0x85, 0xbe, 0xf6, 0xeb, 0xf3, 0x55, 0xc7, 0xaa, 0xa6, 0xe9, 0x07, 0x5d, 0xf4, 0xeb, 0xa6, 0x81, 0xe3, 0x0e, 0xcf, 0xa3, 0xc1, 0xef, 0xe7, 0x34, 0xb2, 0x03, 0x73, 0x8a, 0x91, 0xf1, 0xad, 0x05, 0xc7, 0x0b, 0x43, 0x99, 0x12, 0x31, 0xc8, 0xc7, 0xc5, 0xa4, 0x3d, 0xcd, 0xe5, 0x4e, 0x6d, 0x24, 0xdd, - /* (2^417)P */ 0x61, 0x54, 0xd0, 0x95, 0x2c, 0x45, 0x75, 0xac, 0xb5, 0x1a, 0x9d, 0x11, 0xeb, 0xed, 0x6b, 0x57, 0xa3, 0xe6, 0xcd, 0x77, 0xd4, 0x83, 0x8e, 0x39, 0xf1, 0x0f, 0x98, 0xcb, 0x40, 0x02, 0x6e, 0x10, 0x82, 0x9e, 0xb4, 0x93, 0x76, 0xd7, 0x97, 0xa3, 0x53, 0x12, 0x86, 0xc6, 0x15, 0x78, 0x73, 0x93, 0xe7, 0x7f, 0xcf, 0x1f, 0xbf, 0xcd, 0xd2, 0x7a, - /* (2^418)P */ 0xc2, 0x21, 0xdc, 0xd5, 0x69, 0xff, 0xca, 0x49, 0x3a, 0xe1, 0xc3, 0x69, 0x41, 0x56, 0xc1, 0x76, 0x63, 0x24, 0xbd, 0x64, 0x1b, 0x3d, 0x92, 0xf9, 0x13, 0x04, 0x25, 0xeb, 0x27, 0xa6, 0xef, 0x39, 0x3a, 0x80, 0xe0, 0xf8, 0x27, 0xee, 0xc9, 0x49, 0x77, 0xef, 0x3f, 0x29, 0x3d, 0x5e, 0xe6, 0x66, 0x83, 0xd1, 0xf6, 0xfe, 0x9d, 0xbc, 0xf1, 0x96, - /* (2^419)P */ 0x6b, 0xc6, 0x99, 0x26, 0x3c, 0xf3, 0x63, 0xf9, 0xc7, 0x29, 0x8c, 0x52, 0x62, 0x2d, 0xdc, 0x8a, 0x66, 0xce, 0x2c, 0xa7, 0xe4, 0xf0, 0xd7, 0x37, 0x17, 0x1e, 0xe4, 0xa3, 0x53, 0x7b, 0x29, 0x8e, 0x60, 0x99, 0xf9, 0x0c, 0x7c, 0x6f, 0xa2, 0xcc, 0x9f, 0x80, 0xdd, 0x5e, 0x46, 0xaa, 0x0d, 0x6c, 0xc9, 0x6c, 0xf7, 0x78, 0x5b, 0x38, 0xe3, 0x24, - /* (2^420)P */ 0x4b, 0x75, 0x6a, 0x2f, 0x08, 0xe1, 0x72, 0x76, 0xab, 0x82, 0x96, 0xdf, 0x3b, 0x1f, 0x9b, 0xd8, 0xed, 0xdb, 0xcd, 0x15, 0x09, 0x5a, 0x1e, 0xb7, 0xc5, 0x26, 0x72, 0x07, 0x0c, 0x50, 0xcd, 0x3b, 0x4d, 0x3f, 0xa2, 0x67, 0xc2, 0x02, 0x61, 0x2e, 0x68, 0xe9, 0x6f, 0xf0, 0x21, 0x2a, 0xa7, 0x3b, 0x88, 0x04, 0x11, 0x64, 0x49, 0x0d, 0xb4, 0x46, - /* (2^421)P */ 0x63, 0x85, 0xf3, 0xc5, 0x2b, 0x5a, 0x9f, 0xf0, 0x17, 0xcb, 0x45, 0x0a, 0xf3, 0x6e, 0x7e, 0xb0, 0x7c, 0xbc, 0xf0, 0x4f, 0x3a, 0xb0, 0xbc, 0x36, 0x36, 0x52, 0x51, 0xcb, 0xfe, 0x9a, 0xcb, 0xe8, 0x7e, 0x4b, 0x06, 0x7f, 0xaa, 0x35, 0xc8, 0x0e, 0x7a, 0x30, 0xa3, 0xb1, 0x09, 0xbb, 0x86, 0x4c, 0xbe, 0xb8, 0xbd, 0xe0, 0x32, 0xa5, 0xd4, 0xf7, - /* (2^422)P */ 0x7d, 0x50, 0x37, 0x68, 0x4e, 0x22, 0xb2, 0x2c, 0xd5, 0x0f, 0x2b, 0x6d, 0xb1, 0x51, 0xf2, 0x82, 0xe9, 0x98, 0x7c, 0x50, 0xc7, 0x96, 0x7e, 0x0e, 0xdc, 0xb1, 0x0e, 0xb2, 0x63, 0x8c, 0x30, 0x37, 0x72, 0x21, 0x9c, 0x61, 0xc2, 0xa7, 0x33, 0xd9, 0xb2, 0x63, 0x93, 0xd1, 0x6b, 0x6a, 0x73, 0xa5, 0x58, 0x80, 0xff, 0x04, 0xc7, 0x83, 0x21, 0x29, - /* (2^423)P */ 0x29, 0x04, 0xbc, 0x99, 0x39, 0xc9, 0x58, 0xc9, 0x6b, 0x17, 0xe8, 0x90, 0xb3, 0xe6, 0xa9, 0xb6, 0x28, 0x9b, 0xcb, 0x3b, 0x28, 0x90, 0x68, 0x71, 0xff, 0xcf, 0x08, 0x78, 0xc9, 0x8d, 0xa8, 0x4e, 0x43, 0xd1, 0x1c, 0x9e, 0xa4, 0xe3, 0xdf, 0xbf, 0x92, 0xf4, 0xf9, 0x41, 0xba, 0x4d, 0x1c, 0xf9, 0xdd, 0x74, 0x76, 0x1c, 0x6e, 0x3e, 0x94, 0x87, - /* (2^424)P */ 0xe4, 0xda, 0xc5, 0xd7, 0xfb, 0x87, 0xc5, 0x4d, 0x6b, 0x19, 0xaa, 0xb9, 0xbc, 0x8c, 0xf2, 0x8a, 0xd8, 0x5d, 0xdb, 0x4d, 0xef, 0xa6, 0xf2, 0x65, 0xf1, 0x22, 0x9c, 0xf1, 0x46, 0x30, 0x71, 0x7c, 0xe4, 0x53, 0x8e, 0x55, 0x2e, 0x9c, 0x9a, 0x31, 0x2a, 0xc3, 0xab, 0x0f, 0xde, 0xe4, 0xbe, 0xd8, 0x96, 0x50, 0x6e, 0x0c, 0x54, 0x49, 0xe6, 0xec, - /* (2^425)P */ 0x3c, 0x1d, 0x5a, 0xa5, 0xda, 0xad, 0xdd, 0xc2, 0xae, 0xac, 0x6f, 0x86, 0x75, 0x31, 0x91, 0x64, 0x45, 0x9d, 0xa4, 0xf0, 0x81, 0xf1, 0x0e, 0xba, 0x74, 0xaf, 0x7b, 0xcd, 0x6f, 0xfe, 0xac, 0x4e, 0xdb, 0x4e, 0x45, 0x35, 0x36, 0xc5, 0xc0, 0x6c, 0x3d, 0x64, 0xf4, 0xd8, 0x07, 0x62, 0xd1, 0xec, 0xf3, 0xfc, 0x93, 0xc9, 0x28, 0x0c, 0x2c, 0xf3, - /* (2^426)P */ 0x0c, 0x69, 0x2b, 0x5c, 0xb6, 0x41, 0x69, 0xf1, 0xa4, 0xf1, 0x5b, 0x75, 0x4c, 0x42, 0x8b, 0x47, 0xeb, 0x69, 0xfb, 0xa8, 0xe6, 0xf9, 0x7b, 0x48, 0x50, 0xaf, 0xd3, 0xda, 0xb2, 0x35, 0x10, 0xb5, 0x5b, 0x40, 0x90, 0x39, 0xc9, 0x07, 0x06, 0x73, 0x26, 0x20, 0x95, 0x01, 0xa4, 0x2d, 0xf0, 0xe7, 0x2e, 0x00, 0x7d, 0x41, 0x09, 0x68, 0x13, 0xc4, - /* (2^427)P */ 0xbe, 0x38, 0x78, 0xcf, 0xc9, 0x4f, 0x36, 0xca, 0x09, 0x61, 0x31, 0x3c, 0x57, 0x2e, 0xec, 0x17, 0xa4, 0x7d, 0x19, 0x2b, 0x9b, 0x5b, 0xbe, 0x8f, 0xd6, 0xc5, 0x2f, 0x86, 0xf2, 0x64, 0x76, 0x17, 0x00, 0x6e, 0x1a, 0x8c, 0x67, 0x1b, 0x68, 0xeb, 0x15, 0xa2, 0xd6, 0x09, 0x91, 0xdd, 0x23, 0x0d, 0x98, 0xb2, 0x10, 0x19, 0x55, 0x9b, 0x63, 0xf2, - /* (2^428)P */ 0x51, 0x1f, 0x93, 0xea, 0x2a, 0x3a, 0xfa, 0x41, 0xc0, 0x57, 0xfb, 0x74, 0xa6, 0x65, 0x09, 0x56, 0x14, 0xb6, 0x12, 0xaa, 0xb3, 0x1a, 0x8d, 0x3b, 0x76, 0x91, 0x7a, 0x23, 0x56, 0x9c, 0x6a, 0xc0, 0xe0, 0x3c, 0x3f, 0xb5, 0x1a, 0xf4, 0x57, 0x71, 0x93, 0x2b, 0xb1, 0xa7, 0x70, 0x57, 0x22, 0x80, 0xf5, 0xb8, 0x07, 0x77, 0x87, 0x0c, 0xbe, 0x83, - /* (2^429)P */ 0x07, 0x9b, 0x0e, 0x52, 0x38, 0x63, 0x13, 0x86, 0x6a, 0xa6, 0xb4, 0xd2, 0x60, 0x68, 0x9a, 0x99, 0x82, 0x0a, 0x04, 0x5f, 0x89, 0x7a, 0x1a, 0x2a, 0xae, 0x2d, 0x35, 0x0c, 0x1e, 0xad, 0xef, 0x4f, 0x9a, 0xfc, 0xc8, 0xd9, 0xcf, 0x9d, 0x48, 0x71, 0xa5, 0x55, 0x79, 0x73, 0x39, 0x1b, 0xd8, 0x73, 0xec, 0x9b, 0x03, 0x16, 0xd8, 0x82, 0xf7, 0x67, - /* (2^430)P */ 0x52, 0x67, 0x42, 0x21, 0xc9, 0x40, 0x78, 0x82, 0x2b, 0x95, 0x2d, 0x20, 0x92, 0xd1, 0xe2, 0x61, 0x25, 0xb0, 0xc6, 0x9c, 0x20, 0x59, 0x8e, 0x28, 0x6f, 0xf3, 0xfd, 0xd3, 0xc1, 0x32, 0x43, 0xc9, 0xa6, 0x08, 0x7a, 0x77, 0x9c, 0x4c, 0x8c, 0x33, 0x71, 0x13, 0x69, 0xe3, 0x52, 0x30, 0xa7, 0xf5, 0x07, 0x67, 0xac, 0xad, 0x46, 0x8a, 0x26, 0x25, - /* (2^431)P */ 0xda, 0x86, 0xc4, 0xa2, 0x71, 0x56, 0xdd, 0xd2, 0x48, 0xd3, 0xde, 0x42, 0x63, 0x01, 0xa7, 0x2c, 0x92, 0x83, 0x6f, 0x2e, 0xd8, 0x1e, 0x3f, 0xc1, 0xc5, 0x42, 0x4e, 0x34, 0x19, 0x54, 0x6e, 0x35, 0x2c, 0x51, 0x2e, 0xfd, 0x0f, 0x9a, 0x45, 0x66, 0x5e, 0x4a, 0x83, 0xda, 0x0a, 0x53, 0x68, 0x63, 0xfa, 0xce, 0x47, 0x20, 0xd3, 0x34, 0xba, 0x0d, - /* (2^432)P */ 0xd0, 0xe9, 0x64, 0xa4, 0x61, 0x4b, 0x86, 0xe5, 0x93, 0x6f, 0xda, 0x0e, 0x31, 0x7e, 0x6e, 0xe3, 0xc6, 0x73, 0xd8, 0xa3, 0x08, 0x57, 0x52, 0xcd, 0x51, 0x63, 0x1d, 0x9f, 0x93, 0x00, 0x62, 0x91, 0x26, 0x21, 0xa7, 0xdd, 0x25, 0x0f, 0x09, 0x0d, 0x35, 0xad, 0xcf, 0x11, 0x8e, 0x6e, 0xe8, 0xae, 0x1d, 0x95, 0xcb, 0x88, 0xf8, 0x70, 0x7b, 0x91, - /* (2^433)P */ 0x0c, 0x19, 0x5c, 0xd9, 0x8d, 0xda, 0x9d, 0x2c, 0x90, 0x54, 0x65, 0xe8, 0xb6, 0x35, 0x50, 0xae, 0xea, 0xae, 0x43, 0xb7, 0x1e, 0x99, 0x8b, 0x4c, 0x36, 0x4e, 0xe4, 0x1e, 0xc4, 0x64, 0x43, 0xb6, 0xeb, 0xd4, 0xe9, 0x60, 0x22, 0xee, 0xcf, 0xb8, 0x52, 0x1b, 0xf0, 0x04, 0xce, 0xbc, 0x2b, 0xf0, 0xbe, 0xcd, 0x44, 0x74, 0x1e, 0x1f, 0x63, 0xf9, - /* (2^434)P */ 0xe1, 0x3f, 0x95, 0x94, 0xb2, 0xb6, 0x31, 0xa9, 0x1b, 0xdb, 0xfd, 0x0e, 0xdb, 0xdd, 0x1a, 0x22, 0x78, 0x60, 0x9f, 0x75, 0x5f, 0x93, 0x06, 0x0c, 0xd8, 0xbb, 0xa2, 0x85, 0x2b, 0x5e, 0xc0, 0x9b, 0xa8, 0x5d, 0xaf, 0x93, 0x91, 0x91, 0x47, 0x41, 0x1a, 0xfc, 0xb4, 0x51, 0x85, 0xad, 0x69, 0x4d, 0x73, 0x69, 0xd5, 0x4e, 0x82, 0xfb, 0x66, 0xcb, - /* (2^435)P */ 0x7c, 0xbe, 0xc7, 0x51, 0xc4, 0x74, 0x6e, 0xab, 0xfd, 0x41, 0x4f, 0x76, 0x4f, 0x24, 0x03, 0xd6, 0x2a, 0xb7, 0x42, 0xb4, 0xda, 0x41, 0x2c, 0x82, 0x48, 0x4c, 0x7f, 0x6f, 0x25, 0x5d, 0x36, 0xd4, 0x69, 0xf5, 0xef, 0x02, 0x81, 0xea, 0x6f, 0x19, 0x69, 0xe8, 0x6f, 0x5b, 0x2f, 0x14, 0x0e, 0x6f, 0x89, 0xb4, 0xb5, 0xd8, 0xae, 0xef, 0x7b, 0x87, - /* (2^436)P */ 0xe9, 0x91, 0xa0, 0x8b, 0xc9, 0xe0, 0x01, 0x90, 0x37, 0xc1, 0x6f, 0xdc, 0x5e, 0xf7, 0xbf, 0x43, 0x00, 0xaa, 0x10, 0x76, 0x76, 0x18, 0x6e, 0x19, 0x1e, 0x94, 0x50, 0x11, 0x0a, 0xd1, 0xe2, 0xdb, 0x08, 0x21, 0xa0, 0x1f, 0xdb, 0x54, 0xfe, 0xea, 0x6e, 0xa3, 0x68, 0x56, 0x87, 0x0b, 0x22, 0x4e, 0x66, 0xf3, 0x82, 0x82, 0x00, 0xcd, 0xd4, 0x12, - /* (2^437)P */ 0x25, 0x8e, 0x24, 0x77, 0x64, 0x4c, 0xe0, 0xf8, 0x18, 0xc0, 0xdc, 0xc7, 0x1b, 0x35, 0x65, 0xde, 0x67, 0x41, 0x5e, 0x6f, 0x90, 0x82, 0xa7, 0x2e, 0x6d, 0xf1, 0x47, 0xb4, 0x92, 0x9c, 0xfd, 0x6a, 0x9a, 0x41, 0x36, 0x20, 0x24, 0x58, 0xc3, 0x59, 0x07, 0x9a, 0xfa, 0x9f, 0x03, 0xcb, 0xc7, 0x69, 0x37, 0x60, 0xe1, 0xab, 0x13, 0x72, 0xee, 0xa2, - /* (2^438)P */ 0x74, 0x78, 0xfb, 0x13, 0xcb, 0x8e, 0x37, 0x1a, 0xf6, 0x1d, 0x17, 0x83, 0x06, 0xd4, 0x27, 0x06, 0x21, 0xe8, 0xda, 0xdf, 0x6b, 0xf3, 0x83, 0x6b, 0x34, 0x8a, 0x8c, 0xee, 0x01, 0x05, 0x5b, 0xed, 0xd3, 0x1b, 0xc9, 0x64, 0x83, 0xc9, 0x49, 0xc2, 0x57, 0x1b, 0xdd, 0xcf, 0xf1, 0x9d, 0x63, 0xee, 0x1c, 0x0d, 0xa0, 0x0a, 0x73, 0x1f, 0x5b, 0x32, - /* (2^439)P */ 0x29, 0xce, 0x1e, 0xc0, 0x6a, 0xf5, 0xeb, 0x99, 0x5a, 0x39, 0x23, 0xe9, 0xdd, 0xac, 0x44, 0x88, 0xbc, 0x80, 0x22, 0xde, 0x2c, 0xcb, 0xa8, 0x3b, 0xff, 0xf7, 0x6f, 0xc7, 0x71, 0x72, 0xa8, 0xa3, 0xf6, 0x4d, 0xc6, 0x75, 0xda, 0x80, 0xdc, 0xd9, 0x30, 0xd9, 0x07, 0x50, 0x5a, 0x54, 0x7d, 0xda, 0x39, 0x6f, 0x78, 0x94, 0xbf, 0x25, 0x98, 0xdc, - /* (2^440)P */ 0x01, 0x26, 0x62, 0x44, 0xfb, 0x0f, 0x11, 0x72, 0x73, 0x0a, 0x16, 0xc7, 0x16, 0x9c, 0x9b, 0x37, 0xd8, 0xff, 0x4f, 0xfe, 0x57, 0xdb, 0xae, 0xef, 0x7d, 0x94, 0x30, 0x04, 0x70, 0x83, 0xde, 0x3c, 0xd4, 0xb5, 0x70, 0xda, 0xa7, 0x55, 0xc8, 0x19, 0xe1, 0x36, 0x15, 0x61, 0xe7, 0x3b, 0x7d, 0x85, 0xbb, 0xf3, 0x42, 0x5a, 0x94, 0xf4, 0x53, 0x2a, - /* (2^441)P */ 0x14, 0x60, 0xa6, 0x0b, 0x83, 0xe1, 0x23, 0x77, 0xc0, 0xce, 0x50, 0xed, 0x35, 0x8d, 0x98, 0x99, 0x7d, 0xf5, 0x8d, 0xce, 0x94, 0x25, 0xc8, 0x0f, 0x6d, 0xfa, 0x4a, 0xa4, 0x3a, 0x1f, 0x66, 0xfb, 0x5a, 0x64, 0xaf, 0x8b, 0x54, 0x54, 0x44, 0x3f, 0x5b, 0x88, 0x61, 0xe4, 0x48, 0x45, 0x26, 0x20, 0xbe, 0x0d, 0x06, 0xbb, 0x65, 0x59, 0xe1, 0x36, - /* (2^442)P */ 0xb7, 0x98, 0xce, 0xa3, 0xe3, 0xee, 0x11, 0x1b, 0x9e, 0x24, 0x59, 0x75, 0x31, 0x37, 0x44, 0x6f, 0x6b, 0x9e, 0xec, 0xb7, 0x44, 0x01, 0x7e, 0xab, 0xbb, 0x69, 0x5d, 0x11, 0xb0, 0x30, 0x64, 0xea, 0x91, 0xb4, 0x7a, 0x8c, 0x02, 0x4c, 0xb9, 0x10, 0xa7, 0xc7, 0x79, 0xe6, 0xdc, 0x77, 0xe3, 0xc8, 0xef, 0x3e, 0xf9, 0x38, 0x81, 0xce, 0x9a, 0xb2, - /* (2^443)P */ 0x91, 0x12, 0x76, 0xd0, 0x10, 0xb4, 0xaf, 0xe1, 0x89, 0x3a, 0x93, 0x6b, 0x5c, 0x19, 0x5f, 0x24, 0xed, 0x04, 0x92, 0xc7, 0xf0, 0x00, 0x08, 0xc1, 0x92, 0xff, 0x90, 0xdb, 0xb2, 0xbf, 0xdf, 0x49, 0xcd, 0xbd, 0x5c, 0x6e, 0xbf, 0x16, 0xbb, 0x61, 0xf9, 0x20, 0x33, 0x35, 0x93, 0x11, 0xbc, 0x59, 0x69, 0xce, 0x18, 0x9f, 0xf8, 0x7b, 0xa1, 0x6e, - /* (2^444)P */ 0xa1, 0xf4, 0xaf, 0xad, 0xf8, 0xe6, 0x99, 0xd2, 0xa1, 0x4d, 0xde, 0x56, 0xc9, 0x7b, 0x0b, 0x11, 0x3e, 0xbf, 0x89, 0x1a, 0x9a, 0x90, 0xe5, 0xe2, 0xa6, 0x37, 0x88, 0xa1, 0x68, 0x59, 0xae, 0x8c, 0xec, 0x02, 0x14, 0x8d, 0xb7, 0x2e, 0x25, 0x75, 0x7f, 0x76, 0x1a, 0xd3, 0x4d, 0xad, 0x8a, 0x00, 0x6c, 0x96, 0x49, 0xa4, 0xc3, 0x2e, 0x5c, 0x7b, - /* (2^445)P */ 0x26, 0x53, 0xf7, 0xda, 0xa8, 0x01, 0x14, 0xb1, 0x63, 0xe3, 0xc3, 0x89, 0x88, 0xb0, 0x85, 0x40, 0x2b, 0x26, 0x9a, 0x10, 0x1a, 0x70, 0x33, 0xf4, 0x50, 0x9d, 0x4d, 0xd8, 0x64, 0xc6, 0x0f, 0xe1, 0x17, 0xc8, 0x10, 0x4b, 0xfc, 0xa0, 0xc9, 0xba, 0x2c, 0x98, 0x09, 0xf5, 0x84, 0xb6, 0x7c, 0x4e, 0xa3, 0xe3, 0x81, 0x1b, 0x32, 0x60, 0x02, 0xdd, - /* (2^446)P */ 0xa3, 0xe5, 0x86, 0xd4, 0x43, 0xa8, 0xd1, 0x98, 0x9d, 0x9d, 0xdb, 0x04, 0xcf, 0x6e, 0x35, 0x05, 0x30, 0x53, 0x3b, 0xbc, 0x90, 0x00, 0x4a, 0xc5, 0x40, 0x2a, 0x0f, 0xde, 0x1a, 0xd7, 0x36, 0x27, 0x44, 0x62, 0xa6, 0xac, 0x9d, 0xd2, 0x70, 0x69, 0x14, 0x39, 0x9b, 0xd1, 0xc3, 0x0a, 0x3a, 0x82, 0x0e, 0xf1, 0x94, 0xd7, 0x42, 0x94, 0xd5, 0x7d, - /* (2^447)P */ 0x04, 0xc0, 0x6e, 0x12, 0x90, 0x70, 0xf9, 0xdf, 0xf7, 0xc9, 0x86, 0xc0, 0xe6, 0x92, 0x8b, 0x0a, 0xa1, 0xc1, 0x3b, 0xcc, 0x33, 0xb7, 0xf0, 0xeb, 0x51, 0x50, 0x80, 0x20, 0x69, 0x1c, 0x4f, 0x89, 0x05, 0x1e, 0xe4, 0x7a, 0x0a, 0xc2, 0xf0, 0xf5, 0x78, 0x91, 0x76, 0x34, 0x45, 0xdc, 0x24, 0x53, 0x24, 0x98, 0xe2, 0x73, 0x6f, 0xe6, 0x46, 0x67, -} diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/constants.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/constants.go deleted file mode 100644 index b6b236e5d3d..00000000000 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/constants.go +++ /dev/null @@ -1,71 +0,0 @@ -package goldilocks - -import fp "github.com/cloudflare/circl/math/fp448" - -var ( - // genX is the x-coordinate of the generator of Goldilocks curve. - genX = fp.Elt{ - 0x5e, 0xc0, 0x0c, 0xc7, 0x2b, 0xa8, 0x26, 0x26, - 0x8e, 0x93, 0x00, 0x8b, 0xe1, 0x80, 0x3b, 0x43, - 0x11, 0x65, 0xb6, 0x2a, 0xf7, 0x1a, 0xae, 0x12, - 0x64, 0xa4, 0xd3, 0xa3, 0x24, 0xe3, 0x6d, 0xea, - 0x67, 0x17, 0x0f, 0x47, 0x70, 0x65, 0x14, 0x9e, - 0xda, 0x36, 0xbf, 0x22, 0xa6, 0x15, 0x1d, 0x22, - 0xed, 0x0d, 0xed, 0x6b, 0xc6, 0x70, 0x19, 0x4f, - } - // genY is the y-coordinate of the generator of Goldilocks curve. - genY = fp.Elt{ - 0x14, 0xfa, 0x30, 0xf2, 0x5b, 0x79, 0x08, 0x98, - 0xad, 0xc8, 0xd7, 0x4e, 0x2c, 0x13, 0xbd, 0xfd, - 0xc4, 0x39, 0x7c, 0xe6, 0x1c, 0xff, 0xd3, 0x3a, - 0xd7, 0xc2, 0xa0, 0x05, 0x1e, 0x9c, 0x78, 0x87, - 0x40, 0x98, 0xa3, 0x6c, 0x73, 0x73, 0xea, 0x4b, - 0x62, 0xc7, 0xc9, 0x56, 0x37, 0x20, 0x76, 0x88, - 0x24, 0xbc, 0xb6, 0x6e, 0x71, 0x46, 0x3f, 0x69, - } - // paramD is -39081 in Fp. - paramD = fp.Elt{ - 0x56, 0x67, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - } - // order is 2^446-0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d, - // which is the number of points in the prime subgroup. - order = Scalar{ - 0xf3, 0x44, 0x58, 0xab, 0x92, 0xc2, 0x78, 0x23, - 0x55, 0x8f, 0xc5, 0x8d, 0x72, 0xc2, 0x6c, 0x21, - 0x90, 0x36, 0xd6, 0xae, 0x49, 0xdb, 0x4e, 0xc4, - 0xe9, 0x23, 0xca, 0x7c, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, - } - // residue448 is 2^448 mod order. - residue448 = [4]uint64{ - 0x721cf5b5529eec34, 0x7a4cf635c8e9c2ab, 0xeec492d944a725bf, 0x20cd77058, - } - // invFour is 1/4 mod order. - invFour = Scalar{ - 0x3d, 0x11, 0xd6, 0xaa, 0xa4, 0x30, 0xde, 0x48, - 0xd5, 0x63, 0x71, 0xa3, 0x9c, 0x30, 0x5b, 0x08, - 0xa4, 0x8d, 0xb5, 0x6b, 0xd2, 0xb6, 0x13, 0x71, - 0xfa, 0x88, 0x32, 0xdf, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, - } - // paramDTwist is -39082 in Fp. The D parameter of the twist curve. - paramDTwist = fp.Elt{ - 0x55, 0x67, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - } -) diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/curve.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/curve.go deleted file mode 100644 index 1f165141a9a..00000000000 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/curve.go +++ /dev/null @@ -1,84 +0,0 @@ -// Package goldilocks provides elliptic curve operations over the goldilocks curve. -package goldilocks - -import fp "github.com/cloudflare/circl/math/fp448" - -// Curve is the Goldilocks curve x^2+y^2=z^2-39081x^2y^2. -type Curve struct{} - -// Identity returns the identity point. -func (Curve) Identity() *Point { - return &Point{ - y: fp.One(), - z: fp.One(), - } -} - -// IsOnCurve returns true if the point lies on the curve. -func (Curve) IsOnCurve(P *Point) bool { - x2, y2, t, t2, z2 := &fp.Elt{}, &fp.Elt{}, &fp.Elt{}, &fp.Elt{}, &fp.Elt{} - rhs, lhs := &fp.Elt{}, &fp.Elt{} - // Check z != 0 - eq0 := !fp.IsZero(&P.z) - - fp.Mul(t, &P.ta, &P.tb) // t = ta*tb - fp.Sqr(x2, &P.x) // x^2 - fp.Sqr(y2, &P.y) // y^2 - fp.Sqr(z2, &P.z) // z^2 - fp.Sqr(t2, t) // t^2 - fp.Add(lhs, x2, y2) // x^2 + y^2 - fp.Mul(rhs, t2, ¶mD) // dt^2 - fp.Add(rhs, rhs, z2) // z^2 + dt^2 - fp.Sub(lhs, lhs, rhs) // x^2 + y^2 - (z^2 + dt^2) - eq1 := fp.IsZero(lhs) - - fp.Mul(lhs, &P.x, &P.y) // xy - fp.Mul(rhs, t, &P.z) // tz - fp.Sub(lhs, lhs, rhs) // xy - tz - eq2 := fp.IsZero(lhs) - - return eq0 && eq1 && eq2 -} - -// Generator returns the generator point. -func (Curve) Generator() *Point { - return &Point{ - x: genX, - y: genY, - z: fp.One(), - ta: genX, - tb: genY, - } -} - -// Order returns the number of points in the prime subgroup. -func (Curve) Order() Scalar { return order } - -// Double returns 2P. -func (Curve) Double(P *Point) *Point { R := *P; R.Double(); return &R } - -// Add returns P+Q. -func (Curve) Add(P, Q *Point) *Point { R := *P; R.Add(Q); return &R } - -// ScalarMult returns kP. This function runs in constant time. -func (e Curve) ScalarMult(k *Scalar, P *Point) *Point { - k4 := &Scalar{} - k4.divBy4(k) - return e.pull(twistCurve{}.ScalarMult(k4, e.push(P))) -} - -// ScalarBaseMult returns kG where G is the generator point. This function runs in constant time. -func (e Curve) ScalarBaseMult(k *Scalar) *Point { - k4 := &Scalar{} - k4.divBy4(k) - return e.pull(twistCurve{}.ScalarBaseMult(k4)) -} - -// CombinedMult returns mG+nP, where G is the generator point. This function is non-constant time. -func (e Curve) CombinedMult(m, n *Scalar, P *Point) *Point { - m4 := &Scalar{} - n4 := &Scalar{} - m4.divBy4(m) - n4.divBy4(n) - return e.pull(twistCurve{}.CombinedMult(m4, n4, twistCurve{}.pull(P))) -} diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/isogeny.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/isogeny.go deleted file mode 100644 index b1daab851c5..00000000000 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/isogeny.go +++ /dev/null @@ -1,52 +0,0 @@ -package goldilocks - -import fp "github.com/cloudflare/circl/math/fp448" - -func (Curve) pull(P *twistPoint) *Point { return twistCurve{}.push(P) } -func (twistCurve) pull(P *Point) *twistPoint { return Curve{}.push(P) } - -// push sends a point on the Goldilocks curve to a point on the twist curve. -func (Curve) push(P *Point) *twistPoint { - Q := &twistPoint{} - Px, Py, Pz := &P.x, &P.y, &P.z - a, b, c, d, e, f, g, h := &Q.x, &Q.y, &Q.z, &fp.Elt{}, &Q.ta, &Q.x, &Q.y, &Q.tb - fp.Add(e, Px, Py) // x+y - fp.Sqr(a, Px) // A = x^2 - fp.Sqr(b, Py) // B = y^2 - fp.Sqr(c, Pz) // z^2 - fp.Add(c, c, c) // C = 2*z^2 - *d = *a // D = A - fp.Sqr(e, e) // (x+y)^2 - fp.Sub(e, e, a) // (x+y)^2-A - fp.Sub(e, e, b) // E = (x+y)^2-A-B - fp.Add(h, b, d) // H = B+D - fp.Sub(g, b, d) // G = B-D - fp.Sub(f, c, h) // F = C-H - fp.Mul(&Q.z, f, g) // Z = F * G - fp.Mul(&Q.x, e, f) // X = E * F - fp.Mul(&Q.y, g, h) // Y = G * H, // T = E * H - return Q -} - -// push sends a point on the twist curve to a point on the Goldilocks curve. -func (twistCurve) push(P *twistPoint) *Point { - Q := &Point{} - Px, Py, Pz := &P.x, &P.y, &P.z - a, b, c, d, e, f, g, h := &Q.x, &Q.y, &Q.z, &fp.Elt{}, &Q.ta, &Q.x, &Q.y, &Q.tb - fp.Add(e, Px, Py) // x+y - fp.Sqr(a, Px) // A = x^2 - fp.Sqr(b, Py) // B = y^2 - fp.Sqr(c, Pz) // z^2 - fp.Add(c, c, c) // C = 2*z^2 - fp.Neg(d, a) // D = -A - fp.Sqr(e, e) // (x+y)^2 - fp.Sub(e, e, a) // (x+y)^2-A - fp.Sub(e, e, b) // E = (x+y)^2-A-B - fp.Add(h, b, d) // H = B+D - fp.Sub(g, b, d) // G = B-D - fp.Sub(f, c, h) // F = C-H - fp.Mul(&Q.z, f, g) // Z = F * G - fp.Mul(&Q.x, e, f) // X = E * F - fp.Mul(&Q.y, g, h) // Y = G * H, // T = E * H - return Q -} diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/point.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/point.go deleted file mode 100644 index 11f73de0542..00000000000 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/point.go +++ /dev/null @@ -1,171 +0,0 @@ -package goldilocks - -import ( - "errors" - "fmt" - - fp "github.com/cloudflare/circl/math/fp448" -) - -// Point is a point on the Goldilocks Curve. -type Point struct{ x, y, z, ta, tb fp.Elt } - -func (P Point) String() string { - return fmt.Sprintf("x: %v\ny: %v\nz: %v\nta: %v\ntb: %v", P.x, P.y, P.z, P.ta, P.tb) -} - -// FromAffine creates a point from affine coordinates. -func FromAffine(x, y *fp.Elt) (*Point, error) { - P := &Point{ - x: *x, - y: *y, - z: fp.One(), - ta: *x, - tb: *y, - } - if !(Curve{}).IsOnCurve(P) { - return P, errors.New("point not on curve") - } - return P, nil -} - -// isLessThan returns true if 0 <= x < y, and assumes that slices are of the -// same length and are interpreted in little-endian order. -func isLessThan(x, y []byte) bool { - i := len(x) - 1 - for i > 0 && x[i] == y[i] { - i-- - } - return x[i] < y[i] -} - -// FromBytes returns a point from the input buffer. -func FromBytes(in []byte) (*Point, error) { - if len(in) < fp.Size+1 { - return nil, errors.New("wrong input length") - } - err := errors.New("invalid decoding") - P := &Point{} - signX := in[fp.Size] >> 7 - copy(P.y[:], in[:fp.Size]) - p := fp.P() - if !isLessThan(P.y[:], p[:]) { - return nil, err - } - - u, v := &fp.Elt{}, &fp.Elt{} - one := fp.One() - fp.Sqr(u, &P.y) // u = y^2 - fp.Mul(v, u, ¶mD) // v = dy^2 - fp.Sub(u, u, &one) // u = y^2-1 - fp.Sub(v, v, &one) // v = dy^2-1 - isQR := fp.InvSqrt(&P.x, u, v) // x = sqrt(u/v) - if !isQR { - return nil, err - } - fp.Modp(&P.x) // x = x mod p - if fp.IsZero(&P.x) && signX == 1 { - return nil, err - } - if signX != (P.x[0] & 1) { - fp.Neg(&P.x, &P.x) - } - P.ta = P.x - P.tb = P.y - P.z = fp.One() - return P, nil -} - -// IsIdentity returns true is P is the identity Point. -func (P *Point) IsIdentity() bool { - return fp.IsZero(&P.x) && !fp.IsZero(&P.y) && !fp.IsZero(&P.z) && P.y == P.z -} - -// IsEqual returns true if P is equivalent to Q. -func (P *Point) IsEqual(Q *Point) bool { - l, r := &fp.Elt{}, &fp.Elt{} - fp.Mul(l, &P.x, &Q.z) - fp.Mul(r, &Q.x, &P.z) - fp.Sub(l, l, r) - b := fp.IsZero(l) - fp.Mul(l, &P.y, &Q.z) - fp.Mul(r, &Q.y, &P.z) - fp.Sub(l, l, r) - b = b && fp.IsZero(l) - fp.Mul(l, &P.ta, &P.tb) - fp.Mul(l, l, &Q.z) - fp.Mul(r, &Q.ta, &Q.tb) - fp.Mul(r, r, &P.z) - fp.Sub(l, l, r) - b = b && fp.IsZero(l) - return b -} - -// Neg obtains the inverse of the Point. -func (P *Point) Neg() { fp.Neg(&P.x, &P.x); fp.Neg(&P.ta, &P.ta) } - -// ToAffine returns the x,y affine coordinates of P. -func (P *Point) ToAffine() (x, y fp.Elt) { - fp.Inv(&P.z, &P.z) // 1/z - fp.Mul(&P.x, &P.x, &P.z) // x/z - fp.Mul(&P.y, &P.y, &P.z) // y/z - fp.Modp(&P.x) - fp.Modp(&P.y) - fp.SetOne(&P.z) - P.ta = P.x - P.tb = P.y - return P.x, P.y -} - -// ToBytes stores P into a slice of bytes. -func (P *Point) ToBytes(out []byte) error { - if len(out) < fp.Size+1 { - return errors.New("invalid decoding") - } - x, y := P.ToAffine() - out[fp.Size] = (x[0] & 1) << 7 - return fp.ToBytes(out[:fp.Size], &y) -} - -// MarshalBinary encodes the receiver into a binary form and returns the result. -func (P *Point) MarshalBinary() (data []byte, err error) { - data = make([]byte, fp.Size+1) - err = P.ToBytes(data[:fp.Size+1]) - return data, err -} - -// UnmarshalBinary must be able to decode the form generated by MarshalBinary. -func (P *Point) UnmarshalBinary(data []byte) error { Q, err := FromBytes(data); *P = *Q; return err } - -// Double sets P = 2Q. -func (P *Point) Double() { P.Add(P) } - -// Add sets P =P+Q.. -func (P *Point) Add(Q *Point) { - // This is formula (5) from "Twisted Edwards Curves Revisited" by - // Hisil H., Wong K.KH., Carter G., Dawson E. (2008) - // https://doi.org/10.1007/978-3-540-89255-7_20 - x1, y1, z1, ta1, tb1 := &P.x, &P.y, &P.z, &P.ta, &P.tb - x2, y2, z2, ta2, tb2 := &Q.x, &Q.y, &Q.z, &Q.ta, &Q.tb - x3, y3, z3, E, H := &P.x, &P.y, &P.z, &P.ta, &P.tb - A, B, C, D := &fp.Elt{}, &fp.Elt{}, &fp.Elt{}, &fp.Elt{} - t1, t2, F, G := C, D, &fp.Elt{}, &fp.Elt{} - fp.Mul(t1, ta1, tb1) // t1 = ta1*tb1 - fp.Mul(t2, ta2, tb2) // t2 = ta2*tb2 - fp.Mul(A, x1, x2) // A = x1*x2 - fp.Mul(B, y1, y2) // B = y1*y2 - fp.Mul(C, t1, t2) // t1*t2 - fp.Mul(C, C, ¶mD) // C = d*t1*t2 - fp.Mul(D, z1, z2) // D = z1*z2 - fp.Add(F, x1, y1) // x1+y1 - fp.Add(E, x2, y2) // x2+y2 - fp.Mul(E, E, F) // (x1+y1)*(x2+y2) - fp.Sub(E, E, A) // (x1+y1)*(x2+y2)-A - fp.Sub(E, E, B) // E = (x1+y1)*(x2+y2)-A-B - fp.Sub(F, D, C) // F = D-C - fp.Add(G, D, C) // G = D+C - fp.Sub(H, B, A) // H = B-A - fp.Mul(z3, F, G) // Z = F * G - fp.Mul(x3, E, F) // X = E * F - fp.Mul(y3, G, H) // Y = G * H, T = E * H -} diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/scalar.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/scalar.go deleted file mode 100644 index f98117b2527..00000000000 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/scalar.go +++ /dev/null @@ -1,203 +0,0 @@ -package goldilocks - -import ( - "encoding/binary" - "math/bits" -) - -// ScalarSize is the size (in bytes) of scalars. -const ScalarSize = 56 // 448 / 8 - -// _N is the number of 64-bit words to store scalars. -const _N = 7 // 448 / 64 - -// Scalar represents a positive integer stored in little-endian order. -type Scalar [ScalarSize]byte - -type scalar64 [_N]uint64 - -func (z *scalar64) fromScalar(x *Scalar) { - z[0] = binary.LittleEndian.Uint64(x[0*8 : 1*8]) - z[1] = binary.LittleEndian.Uint64(x[1*8 : 2*8]) - z[2] = binary.LittleEndian.Uint64(x[2*8 : 3*8]) - z[3] = binary.LittleEndian.Uint64(x[3*8 : 4*8]) - z[4] = binary.LittleEndian.Uint64(x[4*8 : 5*8]) - z[5] = binary.LittleEndian.Uint64(x[5*8 : 6*8]) - z[6] = binary.LittleEndian.Uint64(x[6*8 : 7*8]) -} - -func (z *scalar64) toScalar(x *Scalar) { - binary.LittleEndian.PutUint64(x[0*8:1*8], z[0]) - binary.LittleEndian.PutUint64(x[1*8:2*8], z[1]) - binary.LittleEndian.PutUint64(x[2*8:3*8], z[2]) - binary.LittleEndian.PutUint64(x[3*8:4*8], z[3]) - binary.LittleEndian.PutUint64(x[4*8:5*8], z[4]) - binary.LittleEndian.PutUint64(x[5*8:6*8], z[5]) - binary.LittleEndian.PutUint64(x[6*8:7*8], z[6]) -} - -// add calculates z = x + y. Assumes len(z) > max(len(x),len(y)). -func add(z, x, y []uint64) uint64 { - l, L, zz := len(x), len(y), y - if l > L { - l, L, zz = L, l, x - } - c := uint64(0) - for i := 0; i < l; i++ { - z[i], c = bits.Add64(x[i], y[i], c) - } - for i := l; i < L; i++ { - z[i], c = bits.Add64(zz[i], 0, c) - } - return c -} - -// sub calculates z = x - y. Assumes len(z) > max(len(x),len(y)). -func sub(z, x, y []uint64) uint64 { - l, L, zz := len(x), len(y), y - if l > L { - l, L, zz = L, l, x - } - c := uint64(0) - for i := 0; i < l; i++ { - z[i], c = bits.Sub64(x[i], y[i], c) - } - for i := l; i < L; i++ { - z[i], c = bits.Sub64(zz[i], 0, c) - } - return c -} - -// mulWord calculates z = x * y. Assumes len(z) >= len(x)+1. -func mulWord(z, x []uint64, y uint64) { - for i := range z { - z[i] = 0 - } - carry := uint64(0) - for i := range x { - hi, lo := bits.Mul64(x[i], y) - lo, cc := bits.Add64(lo, z[i], 0) - hi, _ = bits.Add64(hi, 0, cc) - z[i], cc = bits.Add64(lo, carry, 0) - carry, _ = bits.Add64(hi, 0, cc) - } - z[len(x)] = carry -} - -// Cmov moves x into z if b=1. -func (z *scalar64) Cmov(b uint64, x *scalar64) { - m := uint64(0) - b - for i := range z { - z[i] = (z[i] &^ m) | (x[i] & m) - } -} - -// leftShift shifts to the left the words of z returning the more significant word. -func (z *scalar64) leftShift(low uint64) uint64 { - high := z[_N-1] - for i := _N - 1; i > 0; i-- { - z[i] = z[i-1] - } - z[0] = low - return high -} - -// reduceOneWord calculates z = z + 2^448*x such that the result fits in a Scalar. -func (z *scalar64) reduceOneWord(x uint64) { - prod := (&scalar64{})[:] - mulWord(prod, residue448[:], x) - cc := add(z[:], z[:], prod) - mulWord(prod, residue448[:], cc) - add(z[:], z[:], prod) -} - -// modOrder reduces z mod order. -func (z *scalar64) modOrder() { - var o64, x scalar64 - o64.fromScalar(&order) - // Performs: while (z >= order) { z = z-order } - // At most 8 (eight) iterations reduce 3 bits by subtracting. - for i := 0; i < 8; i++ { - c := sub(x[:], z[:], o64[:]) // (c || x) = z-order - z.Cmov(1-c, &x) // if c != 0 { z = x } - } -} - -// FromBytes stores z = x mod order, where x is a number stored in little-endian order. -func (z *Scalar) FromBytes(x []byte) { - n := len(x) - nCeil := (n + 7) >> 3 - for i := range z { - z[i] = 0 - } - if nCeil < _N { - copy(z[:], x) - return - } - copy(z[:], x[8*(nCeil-_N):]) - var z64 scalar64 - z64.fromScalar(z) - for i := nCeil - _N - 1; i >= 0; i-- { - low := binary.LittleEndian.Uint64(x[8*i:]) - high := z64.leftShift(low) - z64.reduceOneWord(high) - } - z64.modOrder() - z64.toScalar(z) -} - -// divBy4 calculates z = x/4 mod order. -func (z *Scalar) divBy4(x *Scalar) { z.Mul(x, &invFour) } - -// Red reduces z mod order. -func (z *Scalar) Red() { var t scalar64; t.fromScalar(z); t.modOrder(); t.toScalar(z) } - -// Neg calculates z = -z mod order. -func (z *Scalar) Neg() { z.Sub(&order, z) } - -// Add calculates z = x+y mod order. -func (z *Scalar) Add(x, y *Scalar) { - var z64, x64, y64, t scalar64 - x64.fromScalar(x) - y64.fromScalar(y) - c := add(z64[:], x64[:], y64[:]) - add(t[:], z64[:], residue448[:]) - z64.Cmov(c, &t) - z64.modOrder() - z64.toScalar(z) -} - -// Sub calculates z = x-y mod order. -func (z *Scalar) Sub(x, y *Scalar) { - var z64, x64, y64, t scalar64 - x64.fromScalar(x) - y64.fromScalar(y) - c := sub(z64[:], x64[:], y64[:]) - sub(t[:], z64[:], residue448[:]) - z64.Cmov(c, &t) - z64.modOrder() - z64.toScalar(z) -} - -// Mul calculates z = x*y mod order. -func (z *Scalar) Mul(x, y *Scalar) { - var z64, x64, y64 scalar64 - prod := (&[_N + 1]uint64{})[:] - x64.fromScalar(x) - y64.fromScalar(y) - mulWord(prod, x64[:], y64[_N-1]) - copy(z64[:], prod[:_N]) - z64.reduceOneWord(prod[_N]) - for i := _N - 2; i >= 0; i-- { - h := z64.leftShift(0) - z64.reduceOneWord(h) - mulWord(prod, x64[:], y64[i]) - c := add(z64[:], z64[:], prod[:_N]) - z64.reduceOneWord(prod[_N] + c) - } - z64.modOrder() - z64.toScalar(z) -} - -// IsZero returns true if z=0. -func (z *Scalar) IsZero() bool { z.Red(); return *z == Scalar{} } diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist.go deleted file mode 100644 index 83d7cdadd3e..00000000000 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist.go +++ /dev/null @@ -1,138 +0,0 @@ -package goldilocks - -import ( - "crypto/subtle" - "math/bits" - - "github.com/cloudflare/circl/internal/conv" - "github.com/cloudflare/circl/math" - fp "github.com/cloudflare/circl/math/fp448" -) - -// twistCurve is -x^2+y^2=1-39082x^2y^2 and is 4-isogenous to Goldilocks. -type twistCurve struct{} - -// Identity returns the identity point. -func (twistCurve) Identity() *twistPoint { - return &twistPoint{ - y: fp.One(), - z: fp.One(), - } -} - -// subYDiv16 update x = (x - y) / 16. -func subYDiv16(x *scalar64, y int64) { - s := uint64(y >> 63) - x0, b0 := bits.Sub64((*x)[0], uint64(y), 0) - x1, b1 := bits.Sub64((*x)[1], s, b0) - x2, b2 := bits.Sub64((*x)[2], s, b1) - x3, b3 := bits.Sub64((*x)[3], s, b2) - x4, b4 := bits.Sub64((*x)[4], s, b3) - x5, b5 := bits.Sub64((*x)[5], s, b4) - x6, _ := bits.Sub64((*x)[6], s, b5) - x[0] = (x0 >> 4) | (x1 << 60) - x[1] = (x1 >> 4) | (x2 << 60) - x[2] = (x2 >> 4) | (x3 << 60) - x[3] = (x3 >> 4) | (x4 << 60) - x[4] = (x4 >> 4) | (x5 << 60) - x[5] = (x5 >> 4) | (x6 << 60) - x[6] = (x6 >> 4) -} - -func recodeScalar(d *[113]int8, k *Scalar) { - var k64 scalar64 - k64.fromScalar(k) - for i := 0; i < 112; i++ { - d[i] = int8((k64[0] & 0x1f) - 16) - subYDiv16(&k64, int64(d[i])) - } - d[112] = int8(k64[0]) -} - -// ScalarMult returns kP. -func (e twistCurve) ScalarMult(k *Scalar, P *twistPoint) *twistPoint { - var TabP [8]preTwistPointProy - var S preTwistPointProy - var d [113]int8 - - var isZero int - if k.IsZero() { - isZero = 1 - } - subtle.ConstantTimeCopy(isZero, k[:], order[:]) - - minusK := *k - isEven := 1 - int(k[0]&0x1) - minusK.Neg() - subtle.ConstantTimeCopy(isEven, k[:], minusK[:]) - recodeScalar(&d, k) - - P.oddMultiples(TabP[:]) - Q := e.Identity() - for i := 112; i >= 0; i-- { - Q.Double() - Q.Double() - Q.Double() - Q.Double() - mask := d[i] >> 7 - absDi := (d[i] + mask) ^ mask - inx := int32((absDi - 1) >> 1) - sig := int((d[i] >> 7) & 0x1) - for j := range TabP { - S.cmov(&TabP[j], uint(subtle.ConstantTimeEq(inx, int32(j)))) - } - S.cneg(sig) - Q.mixAdd(&S) - } - Q.cneg(uint(isEven)) - return Q -} - -const ( - omegaFix = 7 - omegaVar = 5 -) - -// CombinedMult returns mG+nP. -func (e twistCurve) CombinedMult(m, n *Scalar, P *twistPoint) *twistPoint { - nafFix := math.OmegaNAF(conv.BytesLe2BigInt(m[:]), omegaFix) - nafVar := math.OmegaNAF(conv.BytesLe2BigInt(n[:]), omegaVar) - - if len(nafFix) > len(nafVar) { - nafVar = append(nafVar, make([]int32, len(nafFix)-len(nafVar))...) - } else if len(nafFix) < len(nafVar) { - nafFix = append(nafFix, make([]int32, len(nafVar)-len(nafFix))...) - } - - var TabQ [1 << (omegaVar - 2)]preTwistPointProy - P.oddMultiples(TabQ[:]) - Q := e.Identity() - for i := len(nafFix) - 1; i >= 0; i-- { - Q.Double() - // Generator point - if nafFix[i] != 0 { - idxM := absolute(nafFix[i]) >> 1 - R := tabVerif[idxM] - if nafFix[i] < 0 { - R.neg() - } - Q.mixAddZ1(&R) - } - // Variable input point - if nafVar[i] != 0 { - idxN := absolute(nafVar[i]) >> 1 - S := TabQ[idxN] - if nafVar[i] < 0 { - S.neg() - } - Q.mixAdd(&S) - } - } - return Q -} - -// absolute returns always a positive value. -func absolute(x int32) int32 { - mask := x >> 31 - return (x + mask) ^ mask -} diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twistPoint.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/twistPoint.go deleted file mode 100644 index c55db77b069..00000000000 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twistPoint.go +++ /dev/null @@ -1,135 +0,0 @@ -package goldilocks - -import ( - "fmt" - - fp "github.com/cloudflare/circl/math/fp448" -) - -type twistPoint struct{ x, y, z, ta, tb fp.Elt } - -type preTwistPointAffine struct{ addYX, subYX, dt2 fp.Elt } - -type preTwistPointProy struct { - preTwistPointAffine - z2 fp.Elt -} - -func (P *twistPoint) String() string { - return fmt.Sprintf("x: %v\ny: %v\nz: %v\nta: %v\ntb: %v", P.x, P.y, P.z, P.ta, P.tb) -} - -// cneg conditionally negates the point if b=1. -func (P *twistPoint) cneg(b uint) { - t := &fp.Elt{} - fp.Neg(t, &P.x) - fp.Cmov(&P.x, t, b) - fp.Neg(t, &P.ta) - fp.Cmov(&P.ta, t, b) -} - -// Double updates P with 2P. -func (P *twistPoint) Double() { - // This is formula (7) from "Twisted Edwards Curves Revisited" by - // Hisil H., Wong K.KH., Carter G., Dawson E. (2008) - // https://doi.org/10.1007/978-3-540-89255-7_20 - Px, Py, Pz, Pta, Ptb := &P.x, &P.y, &P.z, &P.ta, &P.tb - a, b, c, e, f, g, h := Px, Py, Pz, Pta, Px, Py, Ptb - fp.Add(e, Px, Py) // x+y - fp.Sqr(a, Px) // A = x^2 - fp.Sqr(b, Py) // B = y^2 - fp.Sqr(c, Pz) // z^2 - fp.Add(c, c, c) // C = 2*z^2 - fp.Add(h, a, b) // H = A+B - fp.Sqr(e, e) // (x+y)^2 - fp.Sub(e, e, h) // E = (x+y)^2-A-B - fp.Sub(g, b, a) // G = B-A - fp.Sub(f, c, g) // F = C-G - fp.Mul(Pz, f, g) // Z = F * G - fp.Mul(Px, e, f) // X = E * F - fp.Mul(Py, g, h) // Y = G * H, T = E * H -} - -// mixAdd calculates P= P+Q, where Q is a precomputed point with Z_Q = 1. -func (P *twistPoint) mixAddZ1(Q *preTwistPointAffine) { - fp.Add(&P.z, &P.z, &P.z) // D = 2*z1 (z2=1) - P.coreAddition(Q) -} - -// coreAddition calculates P=P+Q for curves with A=-1. -func (P *twistPoint) coreAddition(Q *preTwistPointAffine) { - // This is the formula following (5) from "Twisted Edwards Curves Revisited" by - // Hisil H., Wong K.KH., Carter G., Dawson E. (2008) - // https://doi.org/10.1007/978-3-540-89255-7_20 - Px, Py, Pz, Pta, Ptb := &P.x, &P.y, &P.z, &P.ta, &P.tb - addYX2, subYX2, dt2 := &Q.addYX, &Q.subYX, &Q.dt2 - a, b, c, d, e, f, g, h := Px, Py, &fp.Elt{}, Pz, Pta, Px, Py, Ptb - fp.Mul(c, Pta, Ptb) // t1 = ta*tb - fp.Sub(h, Py, Px) // y1-x1 - fp.Add(b, Py, Px) // y1+x1 - fp.Mul(a, h, subYX2) // A = (y1-x1)*(y2-x2) - fp.Mul(b, b, addYX2) // B = (y1+x1)*(y2+x2) - fp.Mul(c, c, dt2) // C = 2*D*t1*t2 - fp.Sub(e, b, a) // E = B-A - fp.Add(h, b, a) // H = B+A - fp.Sub(f, d, c) // F = D-C - fp.Add(g, d, c) // G = D+C - fp.Mul(Pz, f, g) // Z = F * G - fp.Mul(Px, e, f) // X = E * F - fp.Mul(Py, g, h) // Y = G * H, T = E * H -} - -func (P *preTwistPointAffine) neg() { - P.addYX, P.subYX = P.subYX, P.addYX - fp.Neg(&P.dt2, &P.dt2) -} - -func (P *preTwistPointAffine) cneg(b int) { - t := &fp.Elt{} - fp.Cswap(&P.addYX, &P.subYX, uint(b)) - fp.Neg(t, &P.dt2) - fp.Cmov(&P.dt2, t, uint(b)) -} - -func (P *preTwistPointAffine) cmov(Q *preTwistPointAffine, b uint) { - fp.Cmov(&P.addYX, &Q.addYX, b) - fp.Cmov(&P.subYX, &Q.subYX, b) - fp.Cmov(&P.dt2, &Q.dt2, b) -} - -// mixAdd calculates P= P+Q, where Q is a precomputed point with Z_Q != 1. -func (P *twistPoint) mixAdd(Q *preTwistPointProy) { - fp.Mul(&P.z, &P.z, &Q.z2) // D = 2*z1*z2 - P.coreAddition(&Q.preTwistPointAffine) -} - -// oddMultiples calculates T[i] = (2*i-1)P for 0 < i < len(T). -func (P *twistPoint) oddMultiples(T []preTwistPointProy) { - if n := len(T); n > 0 { - T[0].FromTwistPoint(P) - _2P := *P - _2P.Double() - R := &preTwistPointProy{} - R.FromTwistPoint(&_2P) - for i := 1; i < n; i++ { - P.mixAdd(R) - T[i].FromTwistPoint(P) - } - } -} - -// cmov conditionally moves Q into P if b=1. -func (P *preTwistPointProy) cmov(Q *preTwistPointProy, b uint) { - P.preTwistPointAffine.cmov(&Q.preTwistPointAffine, b) - fp.Cmov(&P.z2, &Q.z2, b) -} - -// FromTwistPoint precomputes some coordinates of Q for missed addition. -func (P *preTwistPointProy) FromTwistPoint(Q *twistPoint) { - fp.Add(&P.addYX, &Q.y, &Q.x) // addYX = X + Y - fp.Sub(&P.subYX, &Q.y, &Q.x) // subYX = Y - X - fp.Mul(&P.dt2, &Q.ta, &Q.tb) // T = ta*tb - fp.Mul(&P.dt2, &P.dt2, ¶mDTwist) // D*T - fp.Add(&P.dt2, &P.dt2, &P.dt2) // dt2 = 2*D*T - fp.Add(&P.z2, &Q.z, &Q.z) // z2 = 2*Z -} diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twistTables.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/twistTables.go deleted file mode 100644 index ed432e02c78..00000000000 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twistTables.go +++ /dev/null @@ -1,216 +0,0 @@ -package goldilocks - -import fp "github.com/cloudflare/circl/math/fp448" - -var tabFixMult = [fxV][fx2w1]preTwistPointAffine{ - { - { - addYX: fp.Elt{0x65, 0x4a, 0xdd, 0xdf, 0xb4, 0x79, 0x60, 0xc8, 0xa1, 0x70, 0xb4, 0x3a, 0x1e, 0x0c, 0x9b, 0x19, 0xe5, 0x48, 0x3f, 0xd7, 0x44, 0x18, 0x18, 0x14, 0x14, 0x27, 0x45, 0xd0, 0x2b, 0x24, 0xd5, 0x93, 0xc3, 0x74, 0x4c, 0x50, 0x70, 0x43, 0x26, 0x05, 0x08, 0x24, 0xca, 0x78, 0x30, 0xc1, 0x06, 0x8d, 0xd4, 0x86, 0x42, 0xf0, 0x14, 0xde, 0x08, 0x05}, - subYX: fp.Elt{0x64, 0x4a, 0xdd, 0xdf, 0xb4, 0x79, 0x60, 0xc8, 0xa1, 0x70, 0xb4, 0x3a, 0x1e, 0x0c, 0x9b, 0x19, 0xe5, 0x48, 0x3f, 0xd7, 0x44, 0x18, 0x18, 0x14, 0x14, 0x27, 0x45, 0xd0, 0x2d, 0x24, 0xd5, 0x93, 0xc3, 0x74, 0x4c, 0x50, 0x70, 0x43, 0x26, 0x05, 0x08, 0x24, 0xca, 0x78, 0x30, 0xc1, 0x06, 0x8d, 0xd4, 0x86, 0x42, 0xf0, 0x14, 0xde, 0x08, 0x05}, - dt2: fp.Elt{0x1a, 0x33, 0xea, 0x64, 0x45, 0x1c, 0xdf, 0x17, 0x1d, 0x16, 0x34, 0x28, 0xd6, 0x61, 0x19, 0x67, 0x79, 0xb4, 0x13, 0xcf, 0x3e, 0x7c, 0x0e, 0x72, 0xda, 0xf1, 0x5f, 0xda, 0xe6, 0xcf, 0x42, 0xd3, 0xb6, 0x17, 0xc2, 0x68, 0x13, 0x2d, 0xd9, 0x60, 0x3e, 0xae, 0xf0, 0x5b, 0x96, 0xf0, 0xcd, 0xaf, 0xea, 0xb7, 0x0d, 0x59, 0x16, 0xa7, 0xff, 0x55}, - }, - { - addYX: fp.Elt{0xca, 0xd8, 0x7d, 0x86, 0x1a, 0xef, 0xad, 0x11, 0xe3, 0x27, 0x41, 0x7e, 0x7f, 0x3e, 0xa9, 0xd2, 0xb5, 0x4e, 0x50, 0xe0, 0x77, 0x91, 0xc2, 0x13, 0x52, 0x73, 0x41, 0x09, 0xa6, 0x57, 0x9a, 0xc8, 0xa8, 0x90, 0x9d, 0x26, 0x14, 0xbb, 0xa1, 0x2a, 0xf7, 0x45, 0x43, 0x4e, 0xea, 0x35, 0x62, 0xe1, 0x08, 0x85, 0x46, 0xb8, 0x24, 0x05, 0x2d, 0xab}, - subYX: fp.Elt{0x9b, 0xe6, 0xd3, 0xe5, 0xfe, 0x50, 0x36, 0x3c, 0x3c, 0x6d, 0x74, 0x1d, 0x74, 0xc0, 0xde, 0x5b, 0x45, 0x27, 0xe5, 0x12, 0xee, 0x63, 0x35, 0x6b, 0x13, 0xe2, 0x41, 0x6b, 0x3a, 0x05, 0x2b, 0xb1, 0x89, 0x26, 0xb6, 0xc6, 0xd1, 0x84, 0xff, 0x0e, 0x9b, 0xa3, 0xfb, 0x21, 0x36, 0x6b, 0x01, 0xf7, 0x9f, 0x7c, 0xeb, 0xf5, 0x18, 0x7a, 0x2a, 0x70}, - dt2: fp.Elt{0x09, 0xad, 0x99, 0x1a, 0x38, 0xd3, 0xdf, 0x22, 0x37, 0x32, 0x61, 0x8b, 0xf3, 0x19, 0x48, 0x08, 0xe8, 0x49, 0xb6, 0x4a, 0xa7, 0xed, 0xa4, 0xa2, 0xee, 0x86, 0xd7, 0x31, 0x5e, 0xce, 0x95, 0x76, 0x86, 0x42, 0x1c, 0x9d, 0x07, 0x14, 0x8c, 0x34, 0x18, 0x9c, 0x6d, 0x3a, 0xdf, 0xa9, 0xe8, 0x36, 0x7e, 0xe4, 0x95, 0xbe, 0xb5, 0x09, 0xf8, 0x9c}, - }, - { - addYX: fp.Elt{0x51, 0xdb, 0x49, 0xa8, 0x9f, 0xe3, 0xd7, 0xec, 0x0d, 0x0f, 0x49, 0xe8, 0xb6, 0xc5, 0x0f, 0x5a, 0x1c, 0xce, 0x54, 0x0d, 0xb1, 0x8d, 0x5b, 0xbf, 0xf4, 0xaa, 0x34, 0x77, 0xc4, 0x5d, 0x59, 0xb6, 0xc5, 0x0e, 0x5a, 0xd8, 0x5b, 0x30, 0xc2, 0x1d, 0xec, 0x85, 0x1c, 0x42, 0xbe, 0x24, 0x2e, 0x50, 0x55, 0x44, 0xb2, 0x3a, 0x01, 0xaa, 0x98, 0xfb}, - subYX: fp.Elt{0xe7, 0x29, 0xb7, 0xd0, 0xaa, 0x4f, 0x32, 0x53, 0x56, 0xde, 0xbc, 0xd1, 0x92, 0x5d, 0x19, 0xbe, 0xa3, 0xe3, 0x75, 0x48, 0xe0, 0x7a, 0x1b, 0x54, 0x7a, 0xb7, 0x41, 0x77, 0x84, 0x38, 0xdd, 0x14, 0x9f, 0xca, 0x3f, 0xa3, 0xc8, 0xa7, 0x04, 0x70, 0xf1, 0x4d, 0x3d, 0xb3, 0x84, 0x79, 0xcb, 0xdb, 0xe4, 0xc5, 0x42, 0x9b, 0x57, 0x19, 0xf1, 0x2d}, - dt2: fp.Elt{0x20, 0xb4, 0x94, 0x9e, 0xdf, 0x31, 0x44, 0x0b, 0xc9, 0x7b, 0x75, 0x40, 0x9d, 0xd1, 0x96, 0x39, 0x70, 0x71, 0x15, 0xc8, 0x93, 0xd5, 0xc5, 0xe5, 0xba, 0xfe, 0xee, 0x08, 0x6a, 0x98, 0x0a, 0x1b, 0xb2, 0xaa, 0x3a, 0xf4, 0xa4, 0x79, 0xf9, 0x8e, 0x4d, 0x65, 0x10, 0x9b, 0x3a, 0x6e, 0x7c, 0x87, 0x94, 0x92, 0x11, 0x65, 0xbf, 0x1a, 0x09, 0xde}, - }, - { - addYX: fp.Elt{0xf3, 0x84, 0x76, 0x77, 0xa5, 0x6b, 0x27, 0x3b, 0x83, 0x3d, 0xdf, 0xa0, 0xeb, 0x32, 0x6d, 0x58, 0x81, 0x57, 0x64, 0xc2, 0x21, 0x7c, 0x9b, 0xea, 0xe6, 0xb0, 0x93, 0xf9, 0xe7, 0xc3, 0xed, 0x5a, 0x8e, 0xe2, 0xb4, 0x72, 0x76, 0x66, 0x0f, 0x22, 0x29, 0x94, 0x3e, 0x63, 0x48, 0x5e, 0x80, 0xcb, 0xac, 0xfa, 0x95, 0xb6, 0x4b, 0xc4, 0x95, 0x33}, - subYX: fp.Elt{0x0c, 0x55, 0xd1, 0x5e, 0x5f, 0xbf, 0xbf, 0xe2, 0x4c, 0xfc, 0x37, 0x4a, 0xc4, 0xb1, 0xf4, 0x83, 0x61, 0x93, 0x60, 0x8e, 0x9f, 0x31, 0xf0, 0xa0, 0x41, 0xff, 0x1d, 0xe2, 0x7f, 0xca, 0x40, 0xd6, 0x88, 0xe8, 0x91, 0x61, 0xe2, 0x11, 0x18, 0x83, 0xf3, 0x25, 0x2f, 0x3f, 0x49, 0x40, 0xd4, 0x83, 0xe2, 0xd7, 0x74, 0x6a, 0x16, 0x86, 0x4e, 0xab}, - dt2: fp.Elt{0xdd, 0x58, 0x65, 0xd8, 0x9f, 0xdd, 0x70, 0x7f, 0x0f, 0xec, 0xbd, 0x5c, 0x5c, 0x9b, 0x7e, 0x1b, 0x9f, 0x79, 0x36, 0x1f, 0xfd, 0x79, 0x10, 0x1c, 0x52, 0xf3, 0x22, 0xa4, 0x1f, 0x71, 0x6e, 0x63, 0x14, 0xf4, 0xa7, 0x3e, 0xbe, 0xad, 0x43, 0x30, 0x38, 0x8c, 0x29, 0xc6, 0xcf, 0x50, 0x75, 0x21, 0xe5, 0x78, 0xfd, 0xb0, 0x9a, 0xc4, 0x6d, 0xd4}, - }, - }, - { - { - addYX: fp.Elt{0x7a, 0xa1, 0x38, 0xa6, 0xfd, 0x0e, 0x96, 0xd5, 0x26, 0x76, 0x86, 0x70, 0x80, 0x30, 0xa6, 0x67, 0xeb, 0xf4, 0x39, 0xdb, 0x22, 0xf5, 0x9f, 0x98, 0xe4, 0xb5, 0x3a, 0x0c, 0x59, 0xbf, 0x85, 0xc6, 0xf0, 0x0b, 0x1c, 0x41, 0x38, 0x09, 0x01, 0xdb, 0xd6, 0x3c, 0xb7, 0xf1, 0x08, 0x6b, 0x4b, 0x9e, 0x63, 0x53, 0x83, 0xd3, 0xab, 0xa3, 0x72, 0x0d}, - subYX: fp.Elt{0x84, 0x68, 0x25, 0xe8, 0xe9, 0x8f, 0x91, 0xbf, 0xf7, 0xa4, 0x30, 0xae, 0xea, 0x9f, 0xdd, 0x56, 0x64, 0x09, 0xc9, 0x54, 0x68, 0x4e, 0x33, 0xc5, 0x6f, 0x7b, 0x2d, 0x52, 0x2e, 0x42, 0xbe, 0xbe, 0xf5, 0x64, 0xbf, 0x77, 0x54, 0xdf, 0xb0, 0x10, 0xd2, 0x16, 0x5d, 0xce, 0xaf, 0x9f, 0xfb, 0xa3, 0x63, 0x50, 0xcb, 0xc0, 0xd0, 0x88, 0x44, 0xa3}, - dt2: fp.Elt{0xc3, 0x8b, 0xa5, 0xf1, 0x44, 0xe4, 0x41, 0xcd, 0x75, 0xe3, 0x17, 0x69, 0x5b, 0xb9, 0xbb, 0xee, 0x82, 0xbb, 0xce, 0x57, 0xdf, 0x2a, 0x9c, 0x12, 0xab, 0x66, 0x08, 0x68, 0x05, 0x1b, 0x87, 0xee, 0x5d, 0x1e, 0x18, 0x14, 0x22, 0x4b, 0x99, 0x61, 0x75, 0x28, 0xe7, 0x65, 0x1c, 0x36, 0xb6, 0x18, 0x09, 0xa8, 0xdf, 0xef, 0x30, 0x35, 0xbc, 0x58}, - }, - { - addYX: fp.Elt{0xc5, 0xd3, 0x0e, 0x6f, 0xaf, 0x06, 0x69, 0xc4, 0x07, 0x9e, 0x58, 0x6e, 0x3f, 0x49, 0xd9, 0x0a, 0x3c, 0x2c, 0x37, 0xcd, 0x27, 0x4d, 0x87, 0x91, 0x7a, 0xb0, 0x28, 0xad, 0x2f, 0x68, 0x92, 0x05, 0x97, 0xf1, 0x30, 0x5f, 0x4c, 0x10, 0x20, 0x30, 0xd3, 0x08, 0x3f, 0xc1, 0xc6, 0xb7, 0xb5, 0xd1, 0x71, 0x7b, 0xa8, 0x0a, 0xd8, 0xf5, 0x17, 0xcf}, - subYX: fp.Elt{0x64, 0xd4, 0x8f, 0x91, 0x40, 0xab, 0x6e, 0x1a, 0x62, 0x83, 0xdc, 0xd7, 0x30, 0x1a, 0x4a, 0x2a, 0x4c, 0x54, 0x86, 0x19, 0x81, 0x5d, 0x04, 0x52, 0xa3, 0xca, 0x82, 0x38, 0xdc, 0x1e, 0xf0, 0x7a, 0x78, 0x76, 0x49, 0x4f, 0x71, 0xc4, 0x74, 0x2f, 0xf0, 0x5b, 0x2e, 0x5e, 0xac, 0xef, 0x17, 0xe4, 0x8e, 0x6e, 0xed, 0x43, 0x23, 0x61, 0x99, 0x49}, - dt2: fp.Elt{0x64, 0x90, 0x72, 0x76, 0xf8, 0x2c, 0x7d, 0x57, 0xf9, 0x30, 0x5e, 0x7a, 0x10, 0x74, 0x19, 0x39, 0xd9, 0xaf, 0x0a, 0xf1, 0x43, 0xed, 0x88, 0x9c, 0x8b, 0xdc, 0x9b, 0x1c, 0x90, 0xe7, 0xf7, 0xa3, 0xa5, 0x0d, 0xc6, 0xbc, 0x30, 0xfb, 0x91, 0x1a, 0x51, 0xba, 0x2d, 0xbe, 0x89, 0xdf, 0x1d, 0xdc, 0x53, 0xa8, 0x82, 0x8a, 0xd3, 0x8d, 0x16, 0x68}, - }, - { - addYX: fp.Elt{0xef, 0x5c, 0xe3, 0x74, 0xbf, 0x13, 0x4a, 0xbf, 0x66, 0x73, 0x64, 0xb7, 0xd4, 0xce, 0x98, 0x82, 0x05, 0xfa, 0x98, 0x0c, 0x0a, 0xae, 0xe5, 0x6b, 0x9f, 0xac, 0xbb, 0x6e, 0x1f, 0xcf, 0xff, 0xa6, 0x71, 0x9a, 0xa8, 0x7a, 0x9e, 0x64, 0x1f, 0x20, 0x4a, 0x61, 0xa2, 0xd6, 0x50, 0xe3, 0xba, 0x81, 0x0c, 0x50, 0x59, 0x69, 0x59, 0x15, 0x55, 0xdb}, - subYX: fp.Elt{0xe8, 0x77, 0x4d, 0xe8, 0x66, 0x3d, 0xc1, 0x00, 0x3c, 0xf2, 0x25, 0x00, 0xdc, 0xb2, 0xe5, 0x9b, 0x12, 0x89, 0xf3, 0xd6, 0xea, 0x85, 0x60, 0xfe, 0x67, 0x91, 0xfd, 0x04, 0x7c, 0xe0, 0xf1, 0x86, 0x06, 0x11, 0x66, 0xee, 0xd4, 0xd5, 0xbe, 0x3b, 0x0f, 0xe3, 0x59, 0xb3, 0x4f, 0x00, 0xb6, 0xce, 0x80, 0xc1, 0x61, 0xf7, 0xaf, 0x04, 0x6a, 0x3c}, - dt2: fp.Elt{0x00, 0xd7, 0x32, 0x93, 0x67, 0x70, 0x6f, 0xd7, 0x69, 0xab, 0xb1, 0xd3, 0xdc, 0xd6, 0xa8, 0xdd, 0x35, 0x25, 0xca, 0xd3, 0x8a, 0x6d, 0xce, 0xfb, 0xfd, 0x2b, 0x83, 0xf0, 0xd4, 0xac, 0x66, 0xfb, 0x72, 0x87, 0x7e, 0x55, 0xb7, 0x91, 0x58, 0x10, 0xc3, 0x11, 0x7e, 0x15, 0xfe, 0x7c, 0x55, 0x90, 0xa3, 0x9e, 0xed, 0x9a, 0x7f, 0xa7, 0xb7, 0xeb}, - }, - { - addYX: fp.Elt{0x25, 0x0f, 0xc2, 0x09, 0x9c, 0x10, 0xc8, 0x7c, 0x93, 0xa7, 0xbe, 0xe9, 0x26, 0x25, 0x7c, 0x21, 0xfe, 0xe7, 0x5f, 0x3c, 0x02, 0x83, 0xa7, 0x9e, 0xdf, 0xc0, 0x94, 0x2b, 0x7d, 0x1a, 0xd0, 0x1d, 0xcc, 0x2e, 0x7d, 0xd4, 0x85, 0xe7, 0xc1, 0x15, 0x66, 0xd6, 0xd6, 0x32, 0xb8, 0xf7, 0x63, 0xaa, 0x3b, 0xa5, 0xea, 0x49, 0xad, 0x88, 0x9b, 0x66}, - subYX: fp.Elt{0x09, 0x97, 0x79, 0x36, 0x41, 0x56, 0x9b, 0xdf, 0x15, 0xd8, 0x43, 0x28, 0x17, 0x5b, 0x96, 0xc9, 0xcf, 0x39, 0x1f, 0x13, 0xf7, 0x4d, 0x1d, 0x1f, 0xda, 0x51, 0x56, 0xe7, 0x0a, 0x5a, 0x65, 0xb6, 0x2a, 0x87, 0x49, 0x86, 0xc2, 0x2b, 0xcd, 0xfe, 0x07, 0xf6, 0x4c, 0xe2, 0x1d, 0x9b, 0xd8, 0x82, 0x09, 0x5b, 0x11, 0x10, 0x62, 0x56, 0x89, 0xbd}, - dt2: fp.Elt{0xd9, 0x15, 0x73, 0xf2, 0x96, 0x35, 0x53, 0xb0, 0xe7, 0xa8, 0x0b, 0x93, 0x35, 0x0b, 0x3a, 0x00, 0xf5, 0x18, 0xb1, 0xc3, 0x12, 0x3f, 0x91, 0x17, 0xc1, 0x4c, 0x15, 0x5a, 0x86, 0x92, 0x11, 0xbd, 0x44, 0x40, 0x5a, 0x7b, 0x15, 0x89, 0xba, 0xc1, 0xc1, 0xbc, 0x43, 0x45, 0xe6, 0x52, 0x02, 0x73, 0x0a, 0xd0, 0x2a, 0x19, 0xda, 0x47, 0xa8, 0xff}, - }, - }, -} - -// tabVerif contains the odd multiples of P. The entry T[i] = (2i+1)P, where -// P = phi(G) and G is the generator of the Goldilocks curve, and phi is a -// 4-degree isogeny. -var tabVerif = [1 << (omegaFix - 2)]preTwistPointAffine{ - { /* 1P*/ - addYX: fp.Elt{0x65, 0x4a, 0xdd, 0xdf, 0xb4, 0x79, 0x60, 0xc8, 0xa1, 0x70, 0xb4, 0x3a, 0x1e, 0x0c, 0x9b, 0x19, 0xe5, 0x48, 0x3f, 0xd7, 0x44, 0x18, 0x18, 0x14, 0x14, 0x27, 0x45, 0xd0, 0x2b, 0x24, 0xd5, 0x93, 0xc3, 0x74, 0x4c, 0x50, 0x70, 0x43, 0x26, 0x05, 0x08, 0x24, 0xca, 0x78, 0x30, 0xc1, 0x06, 0x8d, 0xd4, 0x86, 0x42, 0xf0, 0x14, 0xde, 0x08, 0x05}, - subYX: fp.Elt{0x64, 0x4a, 0xdd, 0xdf, 0xb4, 0x79, 0x60, 0xc8, 0xa1, 0x70, 0xb4, 0x3a, 0x1e, 0x0c, 0x9b, 0x19, 0xe5, 0x48, 0x3f, 0xd7, 0x44, 0x18, 0x18, 0x14, 0x14, 0x27, 0x45, 0xd0, 0x2d, 0x24, 0xd5, 0x93, 0xc3, 0x74, 0x4c, 0x50, 0x70, 0x43, 0x26, 0x05, 0x08, 0x24, 0xca, 0x78, 0x30, 0xc1, 0x06, 0x8d, 0xd4, 0x86, 0x42, 0xf0, 0x14, 0xde, 0x08, 0x05}, - dt2: fp.Elt{0x1a, 0x33, 0xea, 0x64, 0x45, 0x1c, 0xdf, 0x17, 0x1d, 0x16, 0x34, 0x28, 0xd6, 0x61, 0x19, 0x67, 0x79, 0xb4, 0x13, 0xcf, 0x3e, 0x7c, 0x0e, 0x72, 0xda, 0xf1, 0x5f, 0xda, 0xe6, 0xcf, 0x42, 0xd3, 0xb6, 0x17, 0xc2, 0x68, 0x13, 0x2d, 0xd9, 0x60, 0x3e, 0xae, 0xf0, 0x5b, 0x96, 0xf0, 0xcd, 0xaf, 0xea, 0xb7, 0x0d, 0x59, 0x16, 0xa7, 0xff, 0x55}, - }, - { /* 3P*/ - addYX: fp.Elt{0xd1, 0xe9, 0xa8, 0x33, 0x20, 0x76, 0x18, 0x08, 0x45, 0x2a, 0xc9, 0x67, 0x2a, 0xc3, 0x15, 0x24, 0xf9, 0x74, 0x21, 0x30, 0x99, 0x59, 0x8b, 0xb2, 0xf0, 0xa4, 0x07, 0xe2, 0x6a, 0x36, 0x8d, 0xd9, 0xd2, 0x4a, 0x7f, 0x73, 0x50, 0x39, 0x3d, 0xaa, 0xa7, 0x51, 0x73, 0x0d, 0x2b, 0x8b, 0x96, 0x47, 0xac, 0x3c, 0x5d, 0xaa, 0x39, 0x9c, 0xcf, 0xd5}, - subYX: fp.Elt{0x6b, 0x11, 0x5d, 0x1a, 0xf9, 0x41, 0x9d, 0xc5, 0x30, 0x3e, 0xad, 0x25, 0x2c, 0x04, 0x45, 0xea, 0xcc, 0x67, 0x07, 0x85, 0xe9, 0xda, 0x0e, 0xb5, 0x40, 0xb7, 0x32, 0xb4, 0x49, 0xdd, 0xff, 0xaa, 0xfc, 0xbb, 0x19, 0xca, 0x8b, 0x79, 0x2b, 0x8f, 0x8d, 0x00, 0x33, 0xc2, 0xad, 0xe9, 0xd3, 0x12, 0xa8, 0xaa, 0x87, 0x62, 0xad, 0x2d, 0xff, 0xa4}, - dt2: fp.Elt{0xb0, 0xaf, 0x3b, 0xea, 0xf0, 0x42, 0x0b, 0x5e, 0x88, 0xd3, 0x98, 0x08, 0x87, 0x59, 0x72, 0x0a, 0xc2, 0xdf, 0xcb, 0x7f, 0x59, 0xb5, 0x4c, 0x63, 0x68, 0xe8, 0x41, 0x38, 0x67, 0x4f, 0xe9, 0xc6, 0xb2, 0x6b, 0x08, 0xa7, 0xf7, 0x0e, 0xcd, 0xea, 0xca, 0x3d, 0xaf, 0x8e, 0xda, 0x4b, 0x2e, 0xd2, 0x88, 0x64, 0x8d, 0xc5, 0x5f, 0x76, 0x0f, 0x3d}, - }, - { /* 5P*/ - addYX: fp.Elt{0xe5, 0x65, 0xc9, 0xe2, 0x75, 0xf0, 0x7d, 0x1a, 0xba, 0xa4, 0x40, 0x4b, 0x93, 0x12, 0xa2, 0x80, 0x95, 0x0d, 0x03, 0x93, 0xe8, 0xa5, 0x4d, 0xe2, 0x3d, 0x81, 0xf5, 0xce, 0xd4, 0x2d, 0x25, 0x59, 0x16, 0x5c, 0xe7, 0xda, 0xc7, 0x45, 0xd2, 0x7e, 0x2c, 0x38, 0xd4, 0x37, 0x64, 0xb2, 0xc2, 0x28, 0xc5, 0x72, 0x16, 0x32, 0x45, 0x36, 0x6f, 0x9f}, - subYX: fp.Elt{0x09, 0xf4, 0x7e, 0xbd, 0x89, 0xdb, 0x19, 0x58, 0xe1, 0x08, 0x00, 0x8a, 0xf4, 0x5f, 0x2a, 0x32, 0x40, 0xf0, 0x2c, 0x3f, 0x5d, 0xe4, 0xfc, 0x89, 0x11, 0x24, 0xb4, 0x2f, 0x97, 0xad, 0xac, 0x8f, 0x19, 0xab, 0xfa, 0x12, 0xe5, 0xf9, 0x50, 0x4e, 0x50, 0x6f, 0x32, 0x30, 0x88, 0xa6, 0xe5, 0x48, 0x28, 0xa2, 0x1b, 0x9f, 0xcd, 0xe2, 0x43, 0x38}, - dt2: fp.Elt{0xa9, 0xcc, 0x53, 0x39, 0x86, 0x02, 0x60, 0x75, 0x34, 0x99, 0x57, 0xbd, 0xfc, 0x5a, 0x8e, 0xce, 0x5e, 0x98, 0x22, 0xd0, 0xa5, 0x24, 0xff, 0x90, 0x28, 0x9f, 0x58, 0xf3, 0x39, 0xe9, 0xba, 0x36, 0x23, 0xfb, 0x7f, 0x41, 0xcc, 0x2b, 0x5a, 0x25, 0x3f, 0x4c, 0x2a, 0xf1, 0x52, 0x6f, 0x2f, 0x07, 0xe3, 0x88, 0x81, 0x77, 0xdd, 0x7c, 0x88, 0x82}, - }, - { /* 7P*/ - addYX: fp.Elt{0xf7, 0xee, 0x88, 0xfd, 0x3a, 0xbf, 0x7e, 0x28, 0x39, 0x23, 0x79, 0xe6, 0x5c, 0x56, 0xcb, 0xb5, 0x48, 0x6a, 0x80, 0x6d, 0x37, 0x60, 0x6c, 0x10, 0x35, 0x49, 0x4b, 0x46, 0x60, 0xd4, 0x79, 0xd4, 0x53, 0xd3, 0x67, 0x88, 0xd0, 0x41, 0xd5, 0x43, 0x85, 0xc8, 0x71, 0xe3, 0x1c, 0xb6, 0xda, 0x22, 0x64, 0x8f, 0x80, 0xac, 0xad, 0x7d, 0xd5, 0x82}, - subYX: fp.Elt{0x92, 0x40, 0xc1, 0x83, 0x21, 0x9b, 0xd5, 0x7d, 0x3f, 0x29, 0xb6, 0x26, 0xef, 0x12, 0xb9, 0x27, 0x39, 0x42, 0x37, 0x97, 0x09, 0x9a, 0x08, 0xe1, 0x68, 0xb6, 0x7a, 0x3f, 0x9f, 0x45, 0xf8, 0x37, 0x19, 0x83, 0x97, 0xe6, 0x73, 0x30, 0x32, 0x35, 0xcf, 0xae, 0x5c, 0x12, 0x68, 0xdf, 0x6e, 0x2b, 0xde, 0x83, 0xa0, 0x44, 0x74, 0x2e, 0x4a, 0xe9}, - dt2: fp.Elt{0xcb, 0x22, 0x0a, 0xda, 0x6b, 0xc1, 0x8a, 0x29, 0xa1, 0xac, 0x8b, 0x5b, 0x8b, 0x32, 0x20, 0xf2, 0x21, 0xae, 0x0c, 0x43, 0xc4, 0xd7, 0x19, 0x37, 0x3d, 0x79, 0x25, 0x98, 0x6c, 0x9c, 0x22, 0x31, 0x2a, 0x55, 0x9f, 0xda, 0x5e, 0xa8, 0x13, 0xdb, 0x8e, 0x2e, 0x16, 0x39, 0xf4, 0x91, 0x6f, 0xec, 0x71, 0x71, 0xc9, 0x10, 0xf2, 0xa4, 0x8f, 0x11}, - }, - { /* 9P*/ - addYX: fp.Elt{0x85, 0xdd, 0x37, 0x62, 0x74, 0x8e, 0x33, 0x5b, 0x25, 0x12, 0x1b, 0xe7, 0xdf, 0x47, 0xe5, 0x12, 0xfd, 0x3a, 0x3a, 0xf5, 0x5d, 0x4c, 0xa2, 0x29, 0x3c, 0x5c, 0x2f, 0xee, 0x18, 0x19, 0x0a, 0x2b, 0xef, 0x67, 0x50, 0x7a, 0x0d, 0x29, 0xae, 0x55, 0x82, 0xcd, 0xd6, 0x41, 0x90, 0xb4, 0x13, 0x31, 0x5d, 0x11, 0xb8, 0xaa, 0x12, 0x86, 0x08, 0xac}, - subYX: fp.Elt{0xcc, 0x37, 0x8d, 0x83, 0x5f, 0xfd, 0xde, 0xd5, 0xf7, 0xf1, 0xae, 0x0a, 0xa7, 0x0b, 0xeb, 0x6d, 0x19, 0x8a, 0xb6, 0x1a, 0x59, 0xd8, 0xff, 0x3c, 0xbc, 0xbc, 0xef, 0x9c, 0xda, 0x7b, 0x75, 0x12, 0xaf, 0x80, 0x8f, 0x2c, 0x3c, 0xaa, 0x0b, 0x17, 0x86, 0x36, 0x78, 0x18, 0xc8, 0x8a, 0xf6, 0xb8, 0x2c, 0x2f, 0x57, 0x2c, 0x62, 0x57, 0xf6, 0x90}, - dt2: fp.Elt{0x83, 0xbc, 0xa2, 0x07, 0xa5, 0x38, 0x96, 0xea, 0xfe, 0x11, 0x46, 0x1d, 0x3b, 0xcd, 0x42, 0xc5, 0xee, 0x67, 0x04, 0x72, 0x08, 0xd8, 0xd9, 0x96, 0x07, 0xf7, 0xac, 0xc3, 0x64, 0xf1, 0x98, 0x2c, 0x55, 0xd7, 0x7d, 0xc8, 0x6c, 0xbd, 0x2c, 0xff, 0x15, 0xd6, 0x6e, 0xb8, 0x17, 0x8e, 0xa8, 0x27, 0x66, 0xb1, 0x73, 0x79, 0x96, 0xff, 0x29, 0x10}, - }, - { /* 11P*/ - addYX: fp.Elt{0x76, 0xcb, 0x9b, 0x0c, 0x5b, 0xfe, 0xe1, 0x2a, 0xdd, 0x6f, 0x6c, 0xdd, 0x6f, 0xb4, 0xc0, 0xc2, 0x1b, 0x4b, 0x38, 0xe8, 0x66, 0x8c, 0x1e, 0x31, 0x63, 0xb9, 0x94, 0xcd, 0xc3, 0x8c, 0x44, 0x25, 0x7b, 0xd5, 0x39, 0x80, 0xfc, 0x01, 0xaa, 0xf7, 0x2a, 0x61, 0x8a, 0x25, 0xd2, 0x5f, 0xc5, 0x66, 0x38, 0xa4, 0x17, 0xcf, 0x3e, 0x11, 0x0f, 0xa3}, - subYX: fp.Elt{0xe0, 0xb6, 0xd1, 0x9c, 0x71, 0x49, 0x2e, 0x7b, 0xde, 0x00, 0xda, 0x6b, 0xf1, 0xec, 0xe6, 0x7a, 0x15, 0x38, 0x71, 0xe9, 0x7b, 0xdb, 0xf8, 0x98, 0xc0, 0x91, 0x2e, 0x53, 0xee, 0x92, 0x87, 0x25, 0xc9, 0xb0, 0xbb, 0x33, 0x15, 0x46, 0x7f, 0xfd, 0x4f, 0x8b, 0x77, 0x05, 0x96, 0xb6, 0xe2, 0x08, 0xdb, 0x0d, 0x09, 0xee, 0x5b, 0xd1, 0x2a, 0x63}, - dt2: fp.Elt{0x8f, 0x7b, 0x57, 0x8c, 0xbf, 0x06, 0x0d, 0x43, 0x21, 0x92, 0x94, 0x2d, 0x6a, 0x38, 0x07, 0x0f, 0xa0, 0xf1, 0xe3, 0xd8, 0x2a, 0xbf, 0x46, 0xc6, 0x9e, 0x1f, 0x8f, 0x2b, 0x46, 0x84, 0x0b, 0x74, 0xed, 0xff, 0xf8, 0xa5, 0x94, 0xae, 0xf1, 0x67, 0xb1, 0x9b, 0xdd, 0x4a, 0xd0, 0xdb, 0xc2, 0xb5, 0x58, 0x49, 0x0c, 0xa9, 0x1d, 0x7d, 0xa9, 0xd3}, - }, - { /* 13P*/ - addYX: fp.Elt{0x73, 0x84, 0x2e, 0x31, 0x1f, 0xdc, 0xed, 0x9f, 0x74, 0xfa, 0xe0, 0x35, 0xb1, 0x85, 0x6a, 0x8d, 0x86, 0xd0, 0xff, 0xd6, 0x08, 0x43, 0x73, 0x1a, 0xd5, 0xf8, 0x43, 0xd4, 0xb3, 0xe5, 0x3f, 0xa8, 0x84, 0x17, 0x59, 0x65, 0x4e, 0xe6, 0xee, 0x54, 0x9c, 0xda, 0x5e, 0x7e, 0x98, 0x29, 0x6d, 0x73, 0x34, 0x1f, 0x99, 0x80, 0x54, 0x54, 0x81, 0x0b}, - subYX: fp.Elt{0xb1, 0xe5, 0xbb, 0x80, 0x22, 0x9c, 0x81, 0x6d, 0xaf, 0x27, 0x65, 0x6f, 0x7e, 0x9c, 0xb6, 0x8d, 0x35, 0x5c, 0x2e, 0x20, 0x48, 0x7a, 0x28, 0xf0, 0x97, 0xfe, 0xb7, 0x71, 0xce, 0xd6, 0xad, 0x3a, 0x81, 0xf6, 0x74, 0x5e, 0xf3, 0xfd, 0x1b, 0xd4, 0x1e, 0x7c, 0xc2, 0xb7, 0xc8, 0xa6, 0xc9, 0x89, 0x03, 0x47, 0xec, 0x24, 0xd6, 0x0e, 0xec, 0x9c}, - dt2: fp.Elt{0x91, 0x0a, 0x43, 0x34, 0x20, 0xc2, 0x64, 0xf7, 0x4e, 0x48, 0xc8, 0xd2, 0x95, 0x83, 0xd1, 0xa4, 0xfb, 0x4e, 0x41, 0x3b, 0x0d, 0xd5, 0x07, 0xd9, 0xf1, 0x13, 0x16, 0x78, 0x54, 0x57, 0xd0, 0xf1, 0x4f, 0x20, 0xac, 0xcf, 0x9c, 0x3b, 0x33, 0x0b, 0x99, 0x54, 0xc3, 0x7f, 0x3e, 0x57, 0x26, 0x86, 0xd5, 0xa5, 0x2b, 0x8d, 0xe3, 0x19, 0x36, 0xf7}, - }, - { /* 15P*/ - addYX: fp.Elt{0x23, 0x69, 0x47, 0x14, 0xf9, 0x9a, 0x50, 0xff, 0x64, 0xd1, 0x50, 0x35, 0xc3, 0x11, 0xd3, 0x19, 0xcf, 0x87, 0xda, 0x30, 0x0b, 0x50, 0xda, 0xc0, 0xe0, 0x25, 0x00, 0xe5, 0x68, 0x93, 0x04, 0xc2, 0xaf, 0xbd, 0x2f, 0x36, 0x5f, 0x47, 0x96, 0x10, 0xa8, 0xbd, 0xe4, 0x88, 0xac, 0x80, 0x52, 0x61, 0x73, 0xe9, 0x63, 0xdd, 0x99, 0xad, 0x20, 0x5b}, - subYX: fp.Elt{0x1b, 0x5e, 0xa2, 0x2a, 0x25, 0x0f, 0x86, 0xc0, 0xb1, 0x2e, 0x0c, 0x13, 0x40, 0x8d, 0xf0, 0xe6, 0x00, 0x55, 0x08, 0xc5, 0x7d, 0xf4, 0xc9, 0x31, 0x25, 0x3a, 0x99, 0x69, 0xdd, 0x67, 0x63, 0x9a, 0xd6, 0x89, 0x2e, 0xa1, 0x19, 0xca, 0x2c, 0xd9, 0x59, 0x5f, 0x5d, 0xc3, 0x6e, 0x62, 0x36, 0x12, 0x59, 0x15, 0xe1, 0xdc, 0xa4, 0xad, 0xc9, 0xd0}, - dt2: fp.Elt{0xbc, 0xea, 0xfc, 0xaf, 0x66, 0x23, 0xb7, 0x39, 0x6b, 0x2a, 0x96, 0xa8, 0x54, 0x43, 0xe9, 0xaa, 0x32, 0x40, 0x63, 0x92, 0x5e, 0xdf, 0x35, 0xc2, 0x9f, 0x24, 0x0c, 0xed, 0xfc, 0xde, 0x73, 0x8f, 0xa7, 0xd5, 0xa3, 0x2b, 0x18, 0x1f, 0xb0, 0xf8, 0xeb, 0x55, 0xd9, 0xc3, 0xfd, 0x28, 0x7c, 0x4f, 0xce, 0x0d, 0xf7, 0xae, 0xc2, 0x83, 0xc3, 0x78}, - }, - { /* 17P*/ - addYX: fp.Elt{0x71, 0xe6, 0x60, 0x93, 0x37, 0xdb, 0x01, 0xa5, 0x4c, 0xba, 0xe8, 0x8e, 0xd5, 0xf9, 0xd3, 0x98, 0xe5, 0xeb, 0xab, 0x3a, 0x15, 0x8b, 0x35, 0x60, 0xbe, 0xe5, 0x9c, 0x2d, 0x10, 0x9b, 0x2e, 0xcf, 0x65, 0x64, 0xea, 0x8f, 0x72, 0xce, 0xf5, 0x18, 0xe5, 0xe2, 0xf0, 0x0e, 0xae, 0x04, 0xec, 0xa0, 0x20, 0x65, 0x63, 0x07, 0xb1, 0x9f, 0x03, 0x97}, - subYX: fp.Elt{0x9e, 0x41, 0x64, 0x30, 0x95, 0x7f, 0x3a, 0x89, 0x7b, 0x0a, 0x79, 0x59, 0x23, 0x9a, 0x3b, 0xfe, 0xa4, 0x13, 0x08, 0xb2, 0x2e, 0x04, 0x50, 0x10, 0x30, 0xcd, 0x2e, 0xa4, 0x91, 0x71, 0x50, 0x36, 0x4a, 0x02, 0xf4, 0x8d, 0xa3, 0x36, 0x1b, 0xf4, 0x52, 0xba, 0x15, 0x04, 0x8b, 0x80, 0x25, 0xd9, 0xae, 0x67, 0x20, 0xd9, 0x88, 0x8f, 0x97, 0xa6}, - dt2: fp.Elt{0xb5, 0xe7, 0x46, 0xbd, 0x55, 0x23, 0xa0, 0x68, 0xc0, 0x12, 0xd9, 0xf1, 0x0a, 0x75, 0xe2, 0xda, 0xf4, 0x6b, 0xca, 0x14, 0xe4, 0x9f, 0x0f, 0xb5, 0x3c, 0xa6, 0xa5, 0xa2, 0x63, 0x94, 0xd1, 0x1c, 0x39, 0x58, 0x57, 0x02, 0x27, 0x98, 0xb6, 0x47, 0xc6, 0x61, 0x4b, 0x5c, 0xab, 0x6f, 0x2d, 0xab, 0xe3, 0xc1, 0x69, 0xf9, 0x12, 0xb0, 0xc8, 0xd5}, - }, - { /* 19P*/ - addYX: fp.Elt{0x19, 0x7d, 0xd5, 0xac, 0x79, 0xa2, 0x82, 0x9b, 0x28, 0x31, 0x22, 0xc0, 0x73, 0x02, 0x76, 0x17, 0x10, 0x70, 0x79, 0x57, 0xc9, 0x84, 0x62, 0x8e, 0x04, 0x04, 0x61, 0x67, 0x08, 0x48, 0xb4, 0x4b, 0xde, 0x53, 0x8c, 0xff, 0x36, 0x1b, 0x62, 0x86, 0x5d, 0xe1, 0x9b, 0xb1, 0xe5, 0xe8, 0x44, 0x64, 0xa1, 0x68, 0x3f, 0xa8, 0x45, 0x52, 0x91, 0xed}, - subYX: fp.Elt{0x42, 0x1a, 0x36, 0x1f, 0x90, 0x15, 0x24, 0x8d, 0x24, 0x80, 0xe6, 0xfe, 0x1e, 0xf0, 0xad, 0xaf, 0x6a, 0x93, 0xf0, 0xa6, 0x0d, 0x5d, 0xea, 0xf6, 0x62, 0x96, 0x7a, 0x05, 0x76, 0x85, 0x74, 0x32, 0xc7, 0xc8, 0x64, 0x53, 0x62, 0xe7, 0x54, 0x84, 0xe0, 0x40, 0x66, 0x19, 0x70, 0x40, 0x95, 0x35, 0x68, 0x64, 0x43, 0xcd, 0xba, 0x29, 0x32, 0xa8}, - dt2: fp.Elt{0x3e, 0xf6, 0xd6, 0xe4, 0x99, 0xeb, 0x20, 0x66, 0x08, 0x2e, 0x26, 0x64, 0xd7, 0x76, 0xf3, 0xb4, 0xc5, 0xa4, 0x35, 0x92, 0xd2, 0x99, 0x70, 0x5a, 0x1a, 0xe9, 0xe9, 0x3d, 0x3b, 0xe1, 0xcd, 0x0e, 0xee, 0x24, 0x13, 0x03, 0x22, 0xd6, 0xd6, 0x72, 0x08, 0x2b, 0xde, 0xfd, 0x93, 0xed, 0x0c, 0x7f, 0x5e, 0x31, 0x22, 0x4d, 0x80, 0x78, 0xc0, 0x48}, - }, - { /* 21P*/ - addYX: fp.Elt{0x8f, 0x72, 0xd2, 0x9e, 0xc4, 0xcd, 0x2c, 0xbf, 0xa8, 0xd3, 0x24, 0x62, 0x28, 0xee, 0x39, 0x0a, 0x19, 0x3a, 0x58, 0xff, 0x21, 0x2e, 0x69, 0x6c, 0x6e, 0x18, 0xd0, 0xcd, 0x61, 0xc1, 0x18, 0x02, 0x5a, 0xe9, 0xe3, 0xef, 0x1f, 0x8e, 0x10, 0xe8, 0x90, 0x2b, 0x48, 0xcd, 0xee, 0x38, 0xbd, 0x3a, 0xca, 0xbc, 0x2d, 0xe2, 0x3a, 0x03, 0x71, 0x02}, - subYX: fp.Elt{0xf8, 0xa4, 0x32, 0x26, 0x66, 0xaf, 0x3b, 0x53, 0xe7, 0xb0, 0x91, 0x92, 0xf5, 0x3c, 0x74, 0xce, 0xf2, 0xdd, 0x68, 0xa9, 0xf4, 0xcd, 0x5f, 0x60, 0xab, 0x71, 0xdf, 0xcd, 0x5c, 0x5d, 0x51, 0x72, 0x3a, 0x96, 0xea, 0xd6, 0xde, 0x54, 0x8e, 0x55, 0x4c, 0x08, 0x4c, 0x60, 0xdd, 0x34, 0xa9, 0x6f, 0xf3, 0x04, 0x02, 0xa8, 0xa6, 0x4e, 0x4d, 0x62}, - dt2: fp.Elt{0x76, 0x4a, 0xae, 0x38, 0x62, 0x69, 0x72, 0xdc, 0xe8, 0x43, 0xbe, 0x1d, 0x61, 0xde, 0x31, 0xc3, 0x42, 0x8f, 0x33, 0x9d, 0xca, 0xc7, 0x9c, 0xec, 0x6a, 0xe2, 0xaa, 0x01, 0x49, 0x78, 0x8d, 0x72, 0x4f, 0x38, 0xea, 0x52, 0xc2, 0xd3, 0xc9, 0x39, 0x71, 0xba, 0xb9, 0x09, 0x9b, 0xa3, 0x7f, 0x45, 0x43, 0x65, 0x36, 0x29, 0xca, 0xe7, 0x5c, 0x5f}, - }, - { /* 23P*/ - addYX: fp.Elt{0x89, 0x42, 0x35, 0x48, 0x6d, 0x74, 0xe5, 0x1f, 0xc3, 0xdd, 0x28, 0x5b, 0x84, 0x41, 0x33, 0x9f, 0x42, 0xf3, 0x1d, 0x5d, 0x15, 0x6d, 0x76, 0x33, 0x36, 0xaf, 0xe9, 0xdd, 0xfa, 0x63, 0x4f, 0x7a, 0x9c, 0xeb, 0x1c, 0x4f, 0x34, 0x65, 0x07, 0x54, 0xbb, 0x4c, 0x8b, 0x62, 0x9d, 0xd0, 0x06, 0x99, 0xb3, 0xe9, 0xda, 0x85, 0x19, 0xb0, 0x3d, 0x3c}, - subYX: fp.Elt{0xbb, 0x99, 0xf6, 0xbf, 0xaf, 0x2c, 0x22, 0x0d, 0x7a, 0xaa, 0x98, 0x6f, 0x01, 0x82, 0x99, 0xcf, 0x88, 0xbd, 0x0e, 0x3a, 0x89, 0xe0, 0x9c, 0x8c, 0x17, 0x20, 0xc4, 0xe0, 0xcf, 0x43, 0x7a, 0xef, 0x0d, 0x9f, 0x87, 0xd4, 0xfb, 0xf2, 0x96, 0xb8, 0x03, 0xe8, 0xcb, 0x5c, 0xec, 0x65, 0x5f, 0x49, 0xa4, 0x7c, 0x85, 0xb4, 0xf6, 0xc7, 0xdb, 0xa3}, - dt2: fp.Elt{0x11, 0xf3, 0x32, 0xa3, 0xa7, 0xb2, 0x7d, 0x51, 0x82, 0x44, 0xeb, 0xa2, 0x7d, 0x72, 0xcb, 0xc6, 0xf6, 0xc7, 0xb2, 0x38, 0x0e, 0x0f, 0x4f, 0x29, 0x00, 0xe4, 0x5b, 0x94, 0x46, 0x86, 0x66, 0xa1, 0x83, 0xb3, 0xeb, 0x15, 0xb6, 0x31, 0x50, 0x28, 0xeb, 0xed, 0x0d, 0x32, 0x39, 0xe9, 0x23, 0x81, 0x99, 0x3e, 0xff, 0x17, 0x4c, 0x11, 0x43, 0xd1}, - }, - { /* 25P*/ - addYX: fp.Elt{0xce, 0xe7, 0xf8, 0x94, 0x8f, 0x96, 0xf8, 0x96, 0xe6, 0x72, 0x20, 0x44, 0x2c, 0xa7, 0xfc, 0xba, 0xc8, 0xe1, 0xbb, 0xc9, 0x16, 0x85, 0xcd, 0x0b, 0xe5, 0xb5, 0x5a, 0x7f, 0x51, 0x43, 0x63, 0x8b, 0x23, 0x8e, 0x1d, 0x31, 0xff, 0x46, 0x02, 0x66, 0xcc, 0x9e, 0x4d, 0xa2, 0xca, 0xe2, 0xc7, 0xfd, 0x22, 0xb1, 0xdb, 0xdf, 0x6f, 0xe6, 0xa5, 0x82}, - subYX: fp.Elt{0xd0, 0xf5, 0x65, 0x40, 0xec, 0x8e, 0x65, 0x42, 0x78, 0xc1, 0x65, 0xe4, 0x10, 0xc8, 0x0b, 0x1b, 0xdd, 0x96, 0x68, 0xce, 0xee, 0x45, 0x55, 0xd8, 0x6e, 0xd3, 0xe6, 0x77, 0x19, 0xae, 0xc2, 0x8d, 0x8d, 0x3e, 0x14, 0x3f, 0x6d, 0x00, 0x2f, 0x9b, 0xd1, 0x26, 0x60, 0x28, 0x0f, 0x3a, 0x47, 0xb3, 0xe6, 0x68, 0x28, 0x24, 0x25, 0xca, 0xc8, 0x06}, - dt2: fp.Elt{0x54, 0xbb, 0x60, 0x92, 0xdb, 0x8f, 0x0f, 0x38, 0xe0, 0xe6, 0xe4, 0xc9, 0xcc, 0x14, 0x62, 0x01, 0xc4, 0x2b, 0x0f, 0xcf, 0xed, 0x7d, 0x8e, 0xa4, 0xd9, 0x73, 0x0b, 0xba, 0x0c, 0xaf, 0x0c, 0xf9, 0xe2, 0xeb, 0x29, 0x2a, 0x53, 0xdf, 0x2c, 0x5a, 0xfa, 0x8f, 0xc1, 0x01, 0xd7, 0xb1, 0x45, 0x73, 0x92, 0x32, 0x83, 0x85, 0x12, 0x74, 0x89, 0x44}, - }, - { /* 27P*/ - addYX: fp.Elt{0x0b, 0x73, 0x3c, 0xc2, 0xb1, 0x2e, 0xe1, 0xa7, 0xf5, 0xc9, 0x7a, 0xfb, 0x3d, 0x2d, 0xac, 0x59, 0xdb, 0xfa, 0x36, 0x11, 0xd1, 0x13, 0x04, 0x51, 0x1d, 0xab, 0x9b, 0x6b, 0x93, 0xfe, 0xda, 0xb0, 0x8e, 0xb4, 0x79, 0x11, 0x21, 0x0f, 0x65, 0xb9, 0xbb, 0x79, 0x96, 0x2a, 0xfd, 0x30, 0xe0, 0xb4, 0x2d, 0x9a, 0x55, 0x25, 0x5d, 0xd4, 0xad, 0x2a}, - subYX: fp.Elt{0x9e, 0xc5, 0x04, 0xfe, 0xec, 0x3c, 0x64, 0x1c, 0xed, 0x95, 0xed, 0xae, 0xaf, 0x5c, 0x6e, 0x08, 0x9e, 0x02, 0x29, 0x59, 0x7e, 0x5f, 0xc4, 0x9a, 0xd5, 0x32, 0x72, 0x86, 0xe1, 0x4e, 0x3c, 0xce, 0x99, 0x69, 0x3b, 0xc4, 0xdd, 0x4d, 0xb7, 0xbb, 0xda, 0x3b, 0x1a, 0x99, 0xaa, 0x62, 0x15, 0xc1, 0xf0, 0xb6, 0x6c, 0xec, 0x56, 0xc1, 0xff, 0x0c}, - dt2: fp.Elt{0x2f, 0xf1, 0x3f, 0x7a, 0x2d, 0x56, 0x19, 0x7f, 0xea, 0xbe, 0x59, 0x2e, 0x13, 0x67, 0x81, 0xfb, 0xdb, 0xc8, 0xa3, 0x1d, 0xd5, 0xe9, 0x13, 0x8b, 0x29, 0xdf, 0xcf, 0x9f, 0xe7, 0xd9, 0x0b, 0x70, 0xd3, 0x15, 0x57, 0x4a, 0xe9, 0x50, 0x12, 0x1b, 0x81, 0x4b, 0x98, 0x98, 0xa8, 0x31, 0x1d, 0x27, 0x47, 0x38, 0xed, 0x57, 0x99, 0x26, 0xb2, 0xee}, - }, - { /* 29P*/ - addYX: fp.Elt{0x1c, 0xb2, 0xb2, 0x67, 0x3b, 0x8b, 0x3d, 0x5a, 0x30, 0x7e, 0x38, 0x7e, 0x3c, 0x3d, 0x28, 0x56, 0x59, 0xd8, 0x87, 0x53, 0x8b, 0xe6, 0x6c, 0x5d, 0xe5, 0x0a, 0x33, 0x10, 0xce, 0xa2, 0x17, 0x0d, 0xe8, 0x76, 0xee, 0x68, 0xa8, 0x72, 0x54, 0xbd, 0xa6, 0x24, 0x94, 0x6e, 0x77, 0xc7, 0x53, 0xb7, 0x89, 0x1c, 0x7a, 0xe9, 0x78, 0x9a, 0x74, 0x5f}, - subYX: fp.Elt{0x76, 0x96, 0x1c, 0xcf, 0x08, 0x55, 0xd8, 0x1e, 0x0d, 0xa3, 0x59, 0x95, 0x32, 0xf4, 0xc2, 0x8e, 0x84, 0x5e, 0x4b, 0x04, 0xda, 0x71, 0xc9, 0x78, 0x52, 0xde, 0x14, 0xb4, 0x31, 0xf4, 0xd4, 0xb8, 0x58, 0xc5, 0x20, 0xe8, 0xdd, 0x15, 0xb5, 0xee, 0xea, 0x61, 0xe0, 0xf5, 0xd6, 0xae, 0x55, 0x59, 0x05, 0x3e, 0xaf, 0x74, 0xac, 0x1f, 0x17, 0x82}, - dt2: fp.Elt{0x59, 0x24, 0xcd, 0xfc, 0x11, 0x7e, 0x85, 0x18, 0x3d, 0x69, 0xf7, 0x71, 0x31, 0x66, 0x98, 0x42, 0x95, 0x00, 0x8c, 0xb2, 0xae, 0x39, 0x7e, 0x85, 0xd6, 0xb0, 0x02, 0xec, 0xce, 0xfc, 0x25, 0xb2, 0xe3, 0x99, 0x8e, 0x5b, 0x61, 0x96, 0x2e, 0x6d, 0x96, 0x57, 0x71, 0xa5, 0x93, 0x41, 0x0e, 0x6f, 0xfd, 0x0a, 0xbf, 0xa9, 0xf7, 0x56, 0xa9, 0x3e}, - }, - { /* 31P*/ - addYX: fp.Elt{0xa2, 0x2e, 0x0c, 0x17, 0x4d, 0xcc, 0x85, 0x2c, 0x18, 0xa0, 0xd2, 0x08, 0xba, 0x11, 0xfa, 0x47, 0x71, 0x86, 0xaf, 0x36, 0x6a, 0xd7, 0xfe, 0xb9, 0xb0, 0x2f, 0x89, 0x98, 0x49, 0x69, 0xf8, 0x6a, 0xad, 0x27, 0x5e, 0x0a, 0x22, 0x60, 0x5e, 0x5d, 0xca, 0x06, 0x51, 0x27, 0x99, 0x29, 0x85, 0x68, 0x98, 0xe1, 0xc4, 0x21, 0x50, 0xa0, 0xe9, 0xc1}, - subYX: fp.Elt{0x4d, 0x70, 0xee, 0x91, 0x92, 0x3f, 0xb7, 0xd3, 0x1d, 0xdb, 0x8d, 0x6e, 0x16, 0xf5, 0x65, 0x7d, 0x5f, 0xb5, 0x6c, 0x59, 0x26, 0x70, 0x4b, 0xf2, 0xfc, 0xe7, 0xdf, 0x86, 0xfe, 0xa5, 0xa7, 0xa6, 0x5d, 0xfb, 0x06, 0xe9, 0xf9, 0xcc, 0xc0, 0x37, 0xcc, 0xd8, 0x09, 0x04, 0xd2, 0xa5, 0x1d, 0xd7, 0xb7, 0xce, 0x92, 0xac, 0x3c, 0xad, 0xfb, 0xae}, - dt2: fp.Elt{0x17, 0xa3, 0x9a, 0xc7, 0x86, 0x2a, 0x51, 0xf7, 0x96, 0x79, 0x49, 0x22, 0x2e, 0x5a, 0x01, 0x5c, 0xb5, 0x95, 0xd4, 0xe8, 0xcb, 0x00, 0xca, 0x2d, 0x55, 0xb6, 0x34, 0x36, 0x0b, 0x65, 0x46, 0xf0, 0x49, 0xfc, 0x87, 0x86, 0xe5, 0xc3, 0x15, 0xdb, 0x32, 0xcd, 0xf2, 0xd3, 0x82, 0x4c, 0xe6, 0x61, 0x8a, 0xaf, 0xd4, 0x9e, 0x0f, 0x5a, 0xf2, 0x81}, - }, - { /* 33P*/ - addYX: fp.Elt{0x88, 0x10, 0xc0, 0xcb, 0xf5, 0x77, 0xae, 0xa5, 0xbe, 0xf6, 0xcd, 0x2e, 0x8b, 0x7e, 0xbd, 0x79, 0x62, 0x4a, 0xeb, 0x69, 0xc3, 0x28, 0xaa, 0x72, 0x87, 0xa9, 0x25, 0x87, 0x46, 0xea, 0x0e, 0x62, 0xa3, 0x6a, 0x1a, 0xe2, 0xba, 0xdc, 0x81, 0x10, 0x33, 0x01, 0xf6, 0x16, 0x89, 0x80, 0xc6, 0xcd, 0xdb, 0xdc, 0xba, 0x0e, 0x09, 0x4a, 0x35, 0x4a}, - subYX: fp.Elt{0x86, 0xb2, 0x2b, 0xd0, 0xb8, 0x4a, 0x6d, 0x66, 0x7b, 0x32, 0xdf, 0x3b, 0x1a, 0x19, 0x1f, 0x63, 0xee, 0x1f, 0x3d, 0x1c, 0x5c, 0x14, 0x60, 0x5b, 0x72, 0x49, 0x07, 0xb1, 0x0d, 0x72, 0xc6, 0x35, 0xf0, 0xbc, 0x5e, 0xda, 0x80, 0x6b, 0x64, 0x5b, 0xe5, 0x34, 0x54, 0x39, 0xdd, 0xe6, 0x3c, 0xcb, 0xe5, 0x29, 0x32, 0x06, 0xc6, 0xb1, 0x96, 0x34}, - dt2: fp.Elt{0x85, 0x86, 0xf5, 0x84, 0x86, 0xe6, 0x77, 0x8a, 0x71, 0x85, 0x0c, 0x4f, 0x81, 0x5b, 0x29, 0x06, 0xb5, 0x2e, 0x26, 0x71, 0x07, 0x78, 0x07, 0xae, 0xbc, 0x95, 0x46, 0xc3, 0x65, 0xac, 0xe3, 0x76, 0x51, 0x7d, 0xd4, 0x85, 0x31, 0xe3, 0x43, 0xf3, 0x1b, 0x7c, 0xf7, 0x6b, 0x2c, 0xf8, 0x1c, 0xbb, 0x8d, 0xca, 0xab, 0x4b, 0xba, 0x7f, 0xa4, 0xe2}, - }, - { /* 35P*/ - addYX: fp.Elt{0x1a, 0xee, 0xe7, 0xa4, 0x8a, 0x9d, 0x53, 0x80, 0xc6, 0xb8, 0x4e, 0xdc, 0x89, 0xe0, 0xc4, 0x2b, 0x60, 0x52, 0x6f, 0xec, 0x81, 0xd2, 0x55, 0x6b, 0x1b, 0x6f, 0x17, 0x67, 0x8e, 0x42, 0x26, 0x4c, 0x65, 0x23, 0x29, 0xc6, 0x7b, 0xcd, 0x9f, 0xad, 0x4b, 0x42, 0xd3, 0x0c, 0x75, 0xc3, 0x8a, 0xf5, 0xbe, 0x9e, 0x55, 0xf7, 0x47, 0x5d, 0xbd, 0x3a}, - subYX: fp.Elt{0x0d, 0xa8, 0x3b, 0xf9, 0xc7, 0x7e, 0xc6, 0x86, 0x94, 0xc0, 0x01, 0xff, 0x27, 0xce, 0x43, 0xac, 0xe5, 0xe1, 0xd2, 0x8d, 0xc1, 0x22, 0x31, 0xbe, 0xe1, 0xaf, 0xf9, 0x4a, 0x78, 0xa1, 0x0c, 0xaa, 0xd4, 0x80, 0xe4, 0x09, 0x8d, 0xfb, 0x1d, 0x52, 0xc8, 0x60, 0x2d, 0xf2, 0xa2, 0x89, 0x02, 0x56, 0x3d, 0x56, 0x27, 0x85, 0xc7, 0xf0, 0x2b, 0x9a}, - dt2: fp.Elt{0x62, 0x7c, 0xc7, 0x6b, 0x2c, 0x9d, 0x0a, 0x7c, 0xe5, 0x50, 0x3c, 0xe6, 0x87, 0x1c, 0x82, 0x30, 0x67, 0x3c, 0x39, 0xb6, 0xa0, 0x31, 0xfb, 0x03, 0x7b, 0xa1, 0x58, 0xdf, 0x12, 0x76, 0x5d, 0x5d, 0x0a, 0x8f, 0x9b, 0x37, 0x32, 0xc3, 0x60, 0x33, 0xea, 0x9f, 0x0a, 0x99, 0xfa, 0x20, 0xd0, 0x33, 0x21, 0xc3, 0x94, 0xd4, 0x86, 0x49, 0x7c, 0x4e}, - }, - { /* 37P*/ - addYX: fp.Elt{0xc7, 0x0c, 0x71, 0xfe, 0x55, 0xd1, 0x95, 0x8f, 0x43, 0xbb, 0x6b, 0x74, 0x30, 0xbd, 0xe8, 0x6f, 0x1c, 0x1b, 0x06, 0x62, 0xf5, 0xfc, 0x65, 0xa0, 0xeb, 0x81, 0x12, 0xc9, 0x64, 0x66, 0x61, 0xde, 0xf3, 0x6d, 0xd4, 0xae, 0x8e, 0xb1, 0x72, 0xe0, 0xcd, 0x37, 0x01, 0x28, 0x52, 0xd7, 0x39, 0x46, 0x0c, 0x55, 0xcf, 0x47, 0x70, 0xef, 0xa1, 0x17}, - subYX: fp.Elt{0x8d, 0x58, 0xde, 0x83, 0x88, 0x16, 0x0e, 0x12, 0x42, 0x03, 0x50, 0x60, 0x4b, 0xdf, 0xbf, 0x95, 0xcc, 0x7d, 0x18, 0x17, 0x7e, 0x31, 0x5d, 0x8a, 0x66, 0xc1, 0xcf, 0x14, 0xea, 0xf4, 0xf4, 0xe5, 0x63, 0x2d, 0x32, 0x86, 0x9b, 0xed, 0x1f, 0x4f, 0x03, 0xaf, 0x33, 0x92, 0xcb, 0xaf, 0x9c, 0x05, 0x0d, 0x47, 0x1b, 0x42, 0xba, 0x13, 0x22, 0x98}, - dt2: fp.Elt{0xb5, 0x48, 0xeb, 0x7d, 0x3d, 0x10, 0x9f, 0x59, 0xde, 0xf8, 0x1c, 0x4f, 0x7d, 0x9d, 0x40, 0x4d, 0x9e, 0x13, 0x24, 0xb5, 0x21, 0x09, 0xb7, 0xee, 0x98, 0x5c, 0x56, 0xbc, 0x5e, 0x2b, 0x78, 0x38, 0x06, 0xac, 0xe3, 0xe0, 0xfa, 0x2e, 0xde, 0x4f, 0xd2, 0xb3, 0xfb, 0x2d, 0x71, 0x84, 0xd1, 0x9d, 0x12, 0x5b, 0x35, 0xc8, 0x03, 0x68, 0x67, 0xc7}, - }, - { /* 39P*/ - addYX: fp.Elt{0xb6, 0x65, 0xfb, 0xa7, 0x06, 0x35, 0xbb, 0xe0, 0x31, 0x8d, 0x91, 0x40, 0x98, 0xab, 0x30, 0xe4, 0xca, 0x12, 0x59, 0x89, 0xed, 0x65, 0x5d, 0x7f, 0xae, 0x69, 0xa0, 0xa4, 0xfa, 0x78, 0xb4, 0xf7, 0xed, 0xae, 0x86, 0x78, 0x79, 0x64, 0x24, 0xa6, 0xd4, 0xe1, 0xf6, 0xd3, 0xa0, 0x89, 0xba, 0x20, 0xf4, 0x54, 0x0d, 0x8f, 0xdb, 0x1a, 0x79, 0xdb}, - subYX: fp.Elt{0xe1, 0x82, 0x0c, 0x4d, 0xde, 0x9f, 0x40, 0xf0, 0xc1, 0xbd, 0x8b, 0xd3, 0x24, 0x03, 0xcd, 0xf2, 0x92, 0x7d, 0xe2, 0x68, 0x7f, 0xf1, 0xbe, 0x69, 0xde, 0x34, 0x67, 0x4c, 0x85, 0x3b, 0xec, 0x98, 0xcc, 0x4d, 0x3e, 0xc0, 0x96, 0x27, 0xe6, 0x75, 0xfc, 0xdf, 0x37, 0xc0, 0x1e, 0x27, 0xe0, 0xf6, 0xc2, 0xbd, 0xbc, 0x3d, 0x9b, 0x39, 0xdc, 0xe2}, - dt2: fp.Elt{0xd8, 0x29, 0xa7, 0x39, 0xe3, 0x9f, 0x2f, 0x0e, 0x4b, 0x24, 0x21, 0x70, 0xef, 0xfd, 0x91, 0xea, 0xbf, 0xe1, 0x72, 0x90, 0xcc, 0xc9, 0x84, 0x0e, 0xad, 0xd5, 0xe6, 0xbb, 0xc5, 0x99, 0x7f, 0xa4, 0xf0, 0x2e, 0xcc, 0x95, 0x64, 0x27, 0x19, 0xd8, 0x4c, 0x27, 0x0d, 0xff, 0xb6, 0x29, 0xe2, 0x6c, 0xfa, 0xbb, 0x4d, 0x9c, 0xbb, 0xaf, 0xa5, 0xec}, - }, - { /* 41P*/ - addYX: fp.Elt{0xd6, 0x33, 0x3f, 0x9f, 0xcf, 0xfd, 0x4c, 0xd1, 0xfe, 0xe5, 0xeb, 0x64, 0x27, 0xae, 0x7a, 0xa2, 0x82, 0x50, 0x6d, 0xaa, 0xe3, 0x5d, 0xe2, 0x48, 0x60, 0xb3, 0x76, 0x04, 0xd9, 0x19, 0xa7, 0xa1, 0x73, 0x8d, 0x38, 0xa9, 0xaf, 0x45, 0xb5, 0xb2, 0x62, 0x9b, 0xf1, 0x35, 0x7b, 0x84, 0x66, 0xeb, 0x06, 0xef, 0xf1, 0xb2, 0x2d, 0x6a, 0x61, 0x15}, - subYX: fp.Elt{0x86, 0x50, 0x42, 0xf7, 0xda, 0x59, 0xb2, 0xcf, 0x0d, 0x3d, 0xee, 0x8e, 0x53, 0x5d, 0xf7, 0x9e, 0x6a, 0x26, 0x2d, 0xc7, 0x8c, 0x8e, 0x18, 0x50, 0x6d, 0xb7, 0x51, 0x4c, 0xa7, 0x52, 0x6e, 0x0e, 0x0a, 0x16, 0x74, 0xb2, 0x81, 0x8b, 0x56, 0x27, 0x22, 0x84, 0xf4, 0x56, 0xc5, 0x06, 0xe1, 0x8b, 0xca, 0x2d, 0xdb, 0x9a, 0xf6, 0x10, 0x9c, 0x51}, - dt2: fp.Elt{0x1f, 0x16, 0xa2, 0x78, 0x96, 0x1b, 0x85, 0x9c, 0x76, 0x49, 0xd4, 0x0f, 0xac, 0xb0, 0xf4, 0xd0, 0x06, 0x2c, 0x7e, 0x6d, 0x6e, 0x8e, 0xc7, 0x9f, 0x18, 0xad, 0xfc, 0x88, 0x0c, 0x0c, 0x09, 0x05, 0x05, 0xa0, 0x79, 0x72, 0x32, 0x72, 0x87, 0x0f, 0x49, 0x87, 0x0c, 0xb4, 0x12, 0xc2, 0x09, 0xf8, 0x9f, 0x30, 0x72, 0xa9, 0x47, 0x13, 0x93, 0x49}, - }, - { /* 43P*/ - addYX: fp.Elt{0xcc, 0xb1, 0x4c, 0xd3, 0xc0, 0x9e, 0x9e, 0x4d, 0x6d, 0x28, 0x0b, 0xa5, 0x94, 0xa7, 0x2e, 0xc2, 0xc7, 0xaf, 0x29, 0x73, 0xc9, 0x68, 0xea, 0x0f, 0x34, 0x37, 0x8d, 0x96, 0x8f, 0x3a, 0x3d, 0x73, 0x1e, 0x6d, 0x9f, 0xcf, 0x8d, 0x83, 0xb5, 0x71, 0xb9, 0xe1, 0x4b, 0x67, 0x71, 0xea, 0xcf, 0x56, 0xe5, 0xeb, 0x72, 0x15, 0x2f, 0x9e, 0xa8, 0xaa}, - subYX: fp.Elt{0xf4, 0x3e, 0x85, 0x1c, 0x1a, 0xef, 0x50, 0xd1, 0xb4, 0x20, 0xb2, 0x60, 0x05, 0x98, 0xfe, 0x47, 0x3b, 0xc1, 0x76, 0xca, 0x2c, 0x4e, 0x5a, 0x42, 0xa3, 0xf7, 0x20, 0xaa, 0x57, 0x39, 0xee, 0x34, 0x1f, 0xe1, 0x68, 0xd3, 0x7e, 0x06, 0xc4, 0x6c, 0xc7, 0x76, 0x2b, 0xe4, 0x1c, 0x48, 0x44, 0xe6, 0xe5, 0x44, 0x24, 0x8d, 0xb3, 0xb6, 0x88, 0x32}, - dt2: fp.Elt{0x18, 0xa7, 0xba, 0xd0, 0x44, 0x6f, 0x33, 0x31, 0x00, 0xf8, 0xf6, 0x12, 0xe3, 0xc5, 0xc7, 0xb5, 0x91, 0x9c, 0x91, 0xb5, 0x75, 0x18, 0x18, 0x8a, 0xab, 0xed, 0x24, 0x11, 0x2e, 0xce, 0x5a, 0x0f, 0x94, 0x5f, 0x2e, 0xca, 0xd3, 0x80, 0xea, 0xe5, 0x34, 0x96, 0x67, 0x8b, 0x6a, 0x26, 0x5e, 0xc8, 0x9d, 0x2c, 0x5e, 0x6c, 0xa2, 0x0c, 0xbf, 0xf0}, - }, - { /* 45P*/ - addYX: fp.Elt{0xb3, 0xbf, 0xa3, 0x85, 0xee, 0xf6, 0x58, 0x02, 0x78, 0xc4, 0x30, 0xd6, 0x57, 0x59, 0x8c, 0x88, 0x08, 0x7c, 0xbc, 0xbe, 0x0a, 0x74, 0xa9, 0xde, 0x69, 0xe7, 0x41, 0xd8, 0xbf, 0x66, 0x8d, 0x3d, 0x28, 0x00, 0x8c, 0x47, 0x65, 0x34, 0xfe, 0x86, 0x9e, 0x6a, 0xf2, 0x41, 0x6a, 0x94, 0xc4, 0x88, 0x75, 0x23, 0x0d, 0x52, 0x69, 0xee, 0x07, 0x89}, - subYX: fp.Elt{0x22, 0x3c, 0xa1, 0x70, 0x58, 0x97, 0x93, 0xbe, 0x59, 0xa8, 0x0b, 0x8a, 0x46, 0x2a, 0x38, 0x1e, 0x08, 0x6b, 0x61, 0x9f, 0xf2, 0x4a, 0x8b, 0x80, 0x68, 0x6e, 0xc8, 0x92, 0x60, 0xf3, 0xc9, 0x89, 0xb2, 0x6d, 0x63, 0xb0, 0xeb, 0x83, 0x15, 0x63, 0x0e, 0x64, 0xbb, 0xb8, 0xfe, 0xb4, 0x81, 0x90, 0x01, 0x28, 0x10, 0xb9, 0x74, 0x6e, 0xde, 0xa4}, - dt2: fp.Elt{0x1a, 0x23, 0x45, 0xa8, 0x6f, 0x4e, 0xa7, 0x4a, 0x0c, 0xeb, 0xb0, 0x43, 0xf9, 0xef, 0x99, 0x60, 0x5b, 0xdb, 0x66, 0xc0, 0x86, 0x71, 0x43, 0xb1, 0x22, 0x7b, 0x1c, 0xe7, 0x8d, 0x09, 0x1d, 0x83, 0x76, 0x9c, 0xd3, 0x5a, 0xdd, 0x42, 0xd9, 0x2f, 0x2d, 0xba, 0x7a, 0xc2, 0xd9, 0x6b, 0xd4, 0x7a, 0xf1, 0xd5, 0x5f, 0x6b, 0x85, 0xbf, 0x0b, 0xf1}, - }, - { /* 47P*/ - addYX: fp.Elt{0xb2, 0x83, 0xfa, 0x1f, 0xd2, 0xce, 0xb6, 0xf2, 0x2d, 0xea, 0x1b, 0xe5, 0x29, 0xa5, 0x72, 0xf9, 0x25, 0x48, 0x4e, 0xf2, 0x50, 0x1b, 0x39, 0xda, 0x34, 0xc5, 0x16, 0x13, 0xb4, 0x0c, 0xa1, 0x00, 0x79, 0x7a, 0xf5, 0x8b, 0xf3, 0x70, 0x14, 0xb6, 0xfc, 0x9a, 0x47, 0x68, 0x1e, 0x42, 0x70, 0x64, 0x2a, 0x84, 0x3e, 0x3d, 0x20, 0x58, 0xf9, 0x6a}, - subYX: fp.Elt{0xd9, 0xee, 0xc0, 0xc4, 0xf5, 0xc2, 0x86, 0xaf, 0x45, 0xd2, 0xd2, 0x87, 0x1b, 0x64, 0xd5, 0xe0, 0x8c, 0x44, 0x00, 0x4f, 0x43, 0x89, 0x04, 0x48, 0x4a, 0x0b, 0xca, 0x94, 0x06, 0x2f, 0x23, 0x5b, 0x6c, 0x8d, 0x44, 0x66, 0x53, 0xf5, 0x5a, 0x20, 0x72, 0x28, 0x58, 0x84, 0xcc, 0x73, 0x22, 0x5e, 0xd1, 0x0b, 0x56, 0x5e, 0x6a, 0xa3, 0x11, 0x91}, - dt2: fp.Elt{0x6e, 0x9f, 0x88, 0xa8, 0x68, 0x2f, 0x12, 0x37, 0x88, 0xfc, 0x92, 0x8f, 0x24, 0xeb, 0x5b, 0x2a, 0x2a, 0xd0, 0x14, 0x40, 0x4c, 0xa9, 0xa4, 0x03, 0x0c, 0x45, 0x48, 0x13, 0xe8, 0xa6, 0x37, 0xab, 0xc0, 0x06, 0x38, 0x6c, 0x96, 0x73, 0x40, 0x6c, 0xc6, 0xea, 0x56, 0xc6, 0xe9, 0x1a, 0x69, 0xeb, 0x7a, 0xd1, 0x33, 0x69, 0x58, 0x2b, 0xea, 0x2f}, - }, - { /* 49P*/ - addYX: fp.Elt{0x58, 0xa8, 0x05, 0x41, 0x00, 0x9d, 0xaa, 0xd9, 0x98, 0xcf, 0xb9, 0x41, 0xb5, 0x4a, 0x8d, 0xe2, 0xe7, 0xc0, 0x72, 0xef, 0xc8, 0x28, 0x6b, 0x68, 0x9d, 0xc9, 0xdf, 0x05, 0x8b, 0xd0, 0x04, 0x74, 0x79, 0x45, 0x52, 0x05, 0xa3, 0x6e, 0x35, 0x3a, 0xe3, 0xef, 0xb2, 0xdc, 0x08, 0x6f, 0x4e, 0x76, 0x85, 0x67, 0xba, 0x23, 0x8f, 0xdd, 0xaf, 0x09}, - subYX: fp.Elt{0xb4, 0x38, 0xc8, 0xff, 0x4f, 0x65, 0x2a, 0x7e, 0xad, 0xb1, 0xc6, 0xb9, 0x3d, 0xd6, 0xf7, 0x14, 0xcf, 0xf6, 0x98, 0x75, 0xbb, 0x47, 0x83, 0x90, 0xe7, 0xe1, 0xf6, 0x14, 0x99, 0x7e, 0xfa, 0xe4, 0x77, 0x24, 0xe3, 0xe7, 0xf0, 0x1e, 0xdb, 0x27, 0x4e, 0x16, 0x04, 0xf2, 0x08, 0x52, 0xfc, 0xec, 0x55, 0xdb, 0x2e, 0x67, 0xe1, 0x94, 0x32, 0x89}, - dt2: fp.Elt{0x00, 0xad, 0x03, 0x35, 0x1a, 0xb1, 0x88, 0xf0, 0xc9, 0x11, 0xe4, 0x12, 0x52, 0x61, 0xfd, 0x8a, 0x1b, 0x6a, 0x0a, 0x4c, 0x42, 0x46, 0x22, 0x0e, 0xa5, 0xf9, 0xe2, 0x50, 0xf2, 0xb2, 0x1f, 0x20, 0x78, 0x10, 0xf6, 0xbf, 0x7f, 0x0c, 0x9c, 0xad, 0x40, 0x8b, 0x82, 0xd4, 0xba, 0x69, 0x09, 0xac, 0x4b, 0x6d, 0xc4, 0x49, 0x17, 0x81, 0x57, 0x3b}, - }, - { /* 51P*/ - addYX: fp.Elt{0x0d, 0xfe, 0xb4, 0x35, 0x11, 0xbd, 0x1d, 0x6b, 0xc2, 0xc5, 0x3b, 0xd2, 0x23, 0x2c, 0x72, 0xe3, 0x48, 0xb1, 0x48, 0x73, 0xfb, 0xa3, 0x21, 0x6e, 0xc0, 0x09, 0x69, 0xac, 0xe1, 0x60, 0xbc, 0x24, 0x03, 0x99, 0x63, 0x0a, 0x00, 0xf0, 0x75, 0xf6, 0x92, 0xc5, 0xd6, 0xdb, 0x51, 0xd4, 0x7d, 0xe6, 0xf4, 0x11, 0x79, 0xd7, 0xc3, 0xaf, 0x48, 0xd0}, - subYX: fp.Elt{0xf4, 0x4f, 0xaf, 0x31, 0xe3, 0x10, 0x89, 0x95, 0xf0, 0x8a, 0xf6, 0x31, 0x9f, 0x48, 0x02, 0xba, 0x42, 0x2b, 0x3c, 0x22, 0x8b, 0xcc, 0x12, 0x98, 0x6e, 0x7a, 0x64, 0x3a, 0xc4, 0xca, 0x32, 0x2a, 0x72, 0xf8, 0x2c, 0xcf, 0x78, 0x5e, 0x7a, 0x75, 0x6e, 0x72, 0x46, 0x48, 0x62, 0x28, 0xac, 0x58, 0x1a, 0xc6, 0x59, 0x88, 0x2a, 0x44, 0x9e, 0x83}, - dt2: fp.Elt{0xb3, 0xde, 0x36, 0xfd, 0xeb, 0x1b, 0xd4, 0x24, 0x1b, 0x08, 0x8c, 0xfe, 0xa9, 0x41, 0xa1, 0x64, 0xf2, 0x6d, 0xdb, 0xf9, 0x94, 0xae, 0x86, 0x71, 0xab, 0x10, 0xbf, 0xa3, 0xb2, 0xa0, 0xdf, 0x10, 0x8c, 0x74, 0xce, 0xb3, 0xfc, 0xdb, 0xba, 0x15, 0xf6, 0x91, 0x7a, 0x9c, 0x36, 0x1e, 0x45, 0x07, 0x3c, 0xec, 0x1a, 0x61, 0x26, 0x93, 0xe3, 0x50}, - }, - { /* 53P*/ - addYX: fp.Elt{0xc5, 0x50, 0xc5, 0x83, 0xb0, 0xbd, 0xd9, 0xf6, 0x6d, 0x15, 0x5e, 0xc1, 0x1a, 0x33, 0xa0, 0xce, 0x13, 0x70, 0x3b, 0xe1, 0x31, 0xc6, 0xc4, 0x02, 0xec, 0x8c, 0xd5, 0x9c, 0x97, 0xd3, 0x12, 0xc4, 0xa2, 0xf9, 0xd5, 0xfb, 0x22, 0x69, 0x94, 0x09, 0x2f, 0x59, 0xce, 0xdb, 0xf2, 0xf2, 0x00, 0xe0, 0xa9, 0x08, 0x44, 0x2e, 0x8b, 0x6b, 0xf5, 0xb3}, - subYX: fp.Elt{0x90, 0xdd, 0xec, 0xa2, 0x65, 0xb7, 0x61, 0xbc, 0xaa, 0x70, 0xa2, 0x15, 0xd8, 0xb0, 0xf8, 0x8e, 0x23, 0x3d, 0x9f, 0x46, 0xa3, 0x29, 0x20, 0xd1, 0xa1, 0x15, 0x81, 0xc6, 0xb6, 0xde, 0xbe, 0x60, 0x63, 0x24, 0xac, 0x15, 0xfb, 0xeb, 0xd3, 0xea, 0x57, 0x13, 0x86, 0x38, 0x1e, 0x22, 0xf4, 0x8c, 0x5d, 0xaf, 0x1b, 0x27, 0x21, 0x4f, 0xa3, 0x63}, - dt2: fp.Elt{0x07, 0x15, 0x87, 0xc4, 0xfd, 0xa1, 0x97, 0x7a, 0x07, 0x1f, 0x56, 0xcc, 0xe3, 0x6a, 0x01, 0x90, 0xce, 0xf9, 0xfa, 0x50, 0xb2, 0xe0, 0x87, 0x8b, 0x6c, 0x63, 0x6c, 0xf6, 0x2a, 0x09, 0xef, 0xef, 0xd2, 0x31, 0x40, 0x25, 0xf6, 0x84, 0xcb, 0xe0, 0xc4, 0x23, 0xc1, 0xcb, 0xe2, 0x02, 0x83, 0x2d, 0xed, 0x74, 0x74, 0x8b, 0xf8, 0x7c, 0x81, 0x18}, - }, - { /* 55P*/ - addYX: fp.Elt{0x9e, 0xe5, 0x59, 0x95, 0x63, 0x2e, 0xac, 0x8b, 0x03, 0x3c, 0xc1, 0x8e, 0xe1, 0x5b, 0x56, 0x3c, 0x16, 0x41, 0xe4, 0xc2, 0x60, 0x0c, 0x6d, 0x65, 0x9f, 0xfc, 0x27, 0x68, 0x43, 0x44, 0x05, 0x12, 0x6c, 0xda, 0x04, 0xef, 0xcf, 0xcf, 0xdc, 0x0a, 0x1a, 0x7f, 0x12, 0xd3, 0xeb, 0x02, 0xb6, 0x04, 0xca, 0xd6, 0xcb, 0xf0, 0x22, 0xba, 0x35, 0x6d}, - subYX: fp.Elt{0x09, 0x6d, 0xf9, 0x64, 0x4c, 0xe6, 0x41, 0xff, 0x01, 0x4d, 0xce, 0x1e, 0xfa, 0x38, 0xa2, 0x25, 0x62, 0xff, 0x03, 0x39, 0x18, 0x91, 0xbb, 0x9d, 0xce, 0x02, 0xf0, 0xf1, 0x3c, 0x55, 0x18, 0xa9, 0xab, 0x4d, 0xd2, 0x35, 0xfd, 0x8d, 0xa9, 0xb2, 0xad, 0xb7, 0x06, 0x6e, 0xc6, 0x69, 0x49, 0xd6, 0x98, 0x98, 0x0b, 0x22, 0x81, 0x6b, 0xbd, 0xa0}, - dt2: fp.Elt{0x22, 0xf4, 0x85, 0x5d, 0x2b, 0xf1, 0x55, 0xa5, 0xd6, 0x27, 0x86, 0x57, 0x12, 0x1f, 0x16, 0x0a, 0x5a, 0x9b, 0xf2, 0x38, 0xb6, 0x28, 0xd8, 0x99, 0x0c, 0x89, 0x1d, 0x7f, 0xca, 0x21, 0x17, 0x1a, 0x0b, 0x02, 0x5f, 0x77, 0x2f, 0x73, 0x30, 0x7c, 0xc8, 0xd7, 0x2b, 0xcc, 0xe7, 0xf3, 0x21, 0xac, 0x53, 0xa7, 0x11, 0x5d, 0xd8, 0x1d, 0x9b, 0xf5}, - }, - { /* 57P*/ - addYX: fp.Elt{0x94, 0x63, 0x5d, 0xef, 0xfd, 0x6d, 0x25, 0x4e, 0x6d, 0x29, 0x03, 0xed, 0x24, 0x28, 0x27, 0x57, 0x47, 0x3e, 0x6a, 0x1a, 0xfe, 0x37, 0xee, 0x5f, 0x83, 0x29, 0x14, 0xfd, 0x78, 0x25, 0x8a, 0xe1, 0x02, 0x38, 0xd8, 0xca, 0x65, 0x55, 0x40, 0x7d, 0x48, 0x2c, 0x7c, 0x7e, 0x60, 0xb6, 0x0c, 0x6d, 0xf7, 0xe8, 0xb3, 0x62, 0x53, 0xd6, 0x9c, 0x2b}, - subYX: fp.Elt{0x47, 0x25, 0x70, 0x62, 0xf5, 0x65, 0x93, 0x62, 0x08, 0xac, 0x59, 0x66, 0xdb, 0x08, 0xd9, 0x1a, 0x19, 0xaf, 0xf4, 0xef, 0x02, 0xa2, 0x78, 0xa9, 0x55, 0x1c, 0xfa, 0x08, 0x11, 0xcb, 0xa3, 0x71, 0x74, 0xb1, 0x62, 0xe7, 0xc7, 0xf3, 0x5a, 0xb5, 0x8b, 0xd4, 0xf6, 0x10, 0x57, 0x79, 0x72, 0x2f, 0x13, 0x86, 0x7b, 0x44, 0x5f, 0x48, 0xfd, 0x88}, - dt2: fp.Elt{0x10, 0x02, 0xcd, 0x05, 0x9a, 0xc3, 0x32, 0x6d, 0x10, 0x3a, 0x74, 0xba, 0x06, 0xc4, 0x3b, 0x34, 0xbc, 0x36, 0xed, 0xa3, 0xba, 0x9a, 0xdb, 0x6d, 0xd4, 0x69, 0x99, 0x97, 0xd0, 0xe4, 0xdd, 0xf5, 0xd4, 0x7c, 0xd3, 0x4e, 0xab, 0xd1, 0x3b, 0xbb, 0xe9, 0xc7, 0x6a, 0x94, 0x25, 0x61, 0xf0, 0x06, 0xc5, 0x12, 0xa8, 0x86, 0xe5, 0x35, 0x46, 0xeb}, - }, - { /* 59P*/ - addYX: fp.Elt{0x9e, 0x95, 0x11, 0xc6, 0xc7, 0xe8, 0xee, 0x5a, 0x26, 0xa0, 0x72, 0x72, 0x59, 0x91, 0x59, 0x16, 0x49, 0x99, 0x7e, 0xbb, 0xd7, 0x15, 0xb4, 0xf2, 0x40, 0xf9, 0x5a, 0x4d, 0xc8, 0xa0, 0xe2, 0x34, 0x7b, 0x34, 0xf3, 0x99, 0xbf, 0xa9, 0xf3, 0x79, 0xc1, 0x1a, 0x0c, 0xf4, 0x86, 0x74, 0x4e, 0xcb, 0xbc, 0x90, 0xad, 0xb6, 0x51, 0x6d, 0xaa, 0x33}, - subYX: fp.Elt{0x9f, 0xd1, 0xc5, 0xa2, 0x6c, 0x24, 0x88, 0x15, 0x71, 0x68, 0xf6, 0x07, 0x45, 0x02, 0xc4, 0x73, 0x7e, 0x75, 0x87, 0xca, 0x7c, 0xf0, 0x92, 0x00, 0x75, 0xd6, 0x5a, 0xdd, 0xe0, 0x64, 0x16, 0x9d, 0x62, 0x80, 0x33, 0x9f, 0xf4, 0x8e, 0x1a, 0x15, 0x1c, 0xd3, 0x0f, 0x4d, 0x4f, 0x62, 0x2d, 0xd7, 0xa5, 0x77, 0xe3, 0xea, 0xf0, 0xfb, 0x1a, 0xdb}, - dt2: fp.Elt{0x6a, 0xa2, 0xb1, 0xaa, 0xfb, 0x5a, 0x32, 0x4e, 0xff, 0x47, 0x06, 0xd5, 0x9a, 0x4f, 0xce, 0x83, 0x5b, 0x82, 0x34, 0x3e, 0x47, 0xb8, 0xf8, 0xe9, 0x7c, 0x67, 0x69, 0x8d, 0x9c, 0xb7, 0xde, 0x57, 0xf4, 0x88, 0x41, 0x56, 0x0c, 0x87, 0x1e, 0xc9, 0x2f, 0x54, 0xbf, 0x5c, 0x68, 0x2c, 0xd9, 0xc4, 0xef, 0x53, 0x73, 0x1e, 0xa6, 0x38, 0x02, 0x10}, - }, - { /* 61P*/ - addYX: fp.Elt{0x08, 0x80, 0x4a, 0xc9, 0xb7, 0xa8, 0x88, 0xd9, 0xfc, 0x6a, 0xc0, 0x3e, 0xc2, 0x33, 0x4d, 0x2b, 0x2a, 0xa3, 0x6d, 0x72, 0x3e, 0xdc, 0x34, 0x68, 0x08, 0xbf, 0x27, 0xef, 0xf4, 0xff, 0xe2, 0x0c, 0x31, 0x0c, 0xa2, 0x0a, 0x1f, 0x65, 0xc1, 0x4c, 0x61, 0xd3, 0x1b, 0xbc, 0x25, 0xb1, 0xd0, 0xd4, 0x89, 0xb2, 0x53, 0xfb, 0x43, 0xa5, 0xaf, 0x04}, - subYX: fp.Elt{0xe3, 0xe1, 0x37, 0xad, 0x58, 0xa9, 0x55, 0x81, 0xee, 0x64, 0x21, 0xb9, 0xf5, 0x4c, 0x35, 0xea, 0x4a, 0xd3, 0x26, 0xaa, 0x90, 0xd4, 0x60, 0x46, 0x09, 0x4b, 0x4a, 0x62, 0xf9, 0xcd, 0xe1, 0xee, 0xbb, 0xc2, 0x09, 0x0b, 0xb0, 0x96, 0x8e, 0x43, 0x77, 0xaf, 0x25, 0x20, 0x5e, 0x47, 0xe4, 0x1d, 0x50, 0x69, 0x74, 0x08, 0xd7, 0xb9, 0x90, 0x13}, - dt2: fp.Elt{0x51, 0x91, 0x95, 0x64, 0x03, 0x16, 0xfd, 0x6e, 0x26, 0x94, 0x6b, 0x61, 0xe7, 0xd9, 0xe0, 0x4a, 0x6d, 0x7c, 0xfa, 0xc0, 0xe2, 0x43, 0x23, 0x53, 0x70, 0xf5, 0x6f, 0x73, 0x8b, 0x81, 0xb0, 0x0c, 0xee, 0x2e, 0x46, 0xf2, 0x8d, 0xa6, 0xfb, 0xb5, 0x1c, 0x33, 0xbf, 0x90, 0x59, 0xc9, 0x7c, 0xb8, 0x6f, 0xad, 0x75, 0x02, 0x90, 0x8e, 0x59, 0x75}, - }, - { /* 63P*/ - addYX: fp.Elt{0x36, 0x4d, 0x77, 0x04, 0xb8, 0x7d, 0x4a, 0xd1, 0xc5, 0xbb, 0x7b, 0x50, 0x5f, 0x8d, 0x9d, 0x62, 0x0f, 0x66, 0x71, 0xec, 0x87, 0xc5, 0x80, 0x82, 0xc8, 0xf4, 0x6a, 0x94, 0x92, 0x5b, 0xb0, 0x16, 0x9b, 0xb2, 0xc9, 0x6f, 0x2b, 0x2d, 0xee, 0x95, 0x73, 0x2e, 0xc2, 0x1b, 0xc5, 0x55, 0x36, 0x86, 0x24, 0xf8, 0x20, 0x05, 0x0d, 0x93, 0xd7, 0x76}, - subYX: fp.Elt{0x7f, 0x01, 0xeb, 0x2e, 0x48, 0x4d, 0x1d, 0xf1, 0x06, 0x7e, 0x7c, 0x2a, 0x43, 0xbf, 0x28, 0xac, 0xe9, 0x58, 0x13, 0xc8, 0xbf, 0x8e, 0xc0, 0xef, 0xe8, 0x4f, 0x46, 0x8a, 0xe7, 0xc0, 0xf6, 0x0f, 0x0a, 0x03, 0x48, 0x91, 0x55, 0x39, 0x2a, 0xe3, 0xdc, 0xf6, 0x22, 0x9d, 0x4d, 0x71, 0x55, 0x68, 0x25, 0x6e, 0x95, 0x52, 0xee, 0x4c, 0xd9, 0x01}, - dt2: fp.Elt{0xac, 0x33, 0x3f, 0x7c, 0x27, 0x35, 0x15, 0x91, 0x33, 0x8d, 0xf9, 0xc4, 0xf4, 0xf3, 0x90, 0x09, 0x75, 0x69, 0x62, 0x9f, 0x61, 0x35, 0x83, 0x92, 0x04, 0xef, 0x96, 0x38, 0x80, 0x9e, 0x88, 0xb3, 0x67, 0x95, 0xbe, 0x79, 0x3c, 0x35, 0xd8, 0xdc, 0xb2, 0x3e, 0x2d, 0xe6, 0x46, 0xbe, 0x81, 0xf3, 0x32, 0x0e, 0x37, 0x23, 0x75, 0x2a, 0x3d, 0xa0}, - }, -} diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist_basemult.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist_basemult.go deleted file mode 100644 index f6ac5edbbbc..00000000000 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist_basemult.go +++ /dev/null @@ -1,62 +0,0 @@ -package goldilocks - -import ( - "crypto/subtle" - - mlsb "github.com/cloudflare/circl/math/mlsbset" -) - -const ( - // MLSBRecoding parameters - fxT = 448 - fxV = 2 - fxW = 3 - fx2w1 = 1 << (uint(fxW) - 1) -) - -// ScalarBaseMult returns kG where G is the generator point. -func (e twistCurve) ScalarBaseMult(k *Scalar) *twistPoint { - m, err := mlsb.New(fxT, fxV, fxW) - if err != nil { - panic(err) - } - if m.IsExtended() { - panic("not extended") - } - - var isZero int - if k.IsZero() { - isZero = 1 - } - subtle.ConstantTimeCopy(isZero, k[:], order[:]) - - minusK := *k - isEven := 1 - int(k[0]&0x1) - minusK.Neg() - subtle.ConstantTimeCopy(isEven, k[:], minusK[:]) - c, err := m.Encode(k[:]) - if err != nil { - panic(err) - } - - gP := c.Exp(groupMLSB{}) - P := gP.(*twistPoint) - P.cneg(uint(isEven)) - return P -} - -type groupMLSB struct{} - -func (e groupMLSB) ExtendedEltP() mlsb.EltP { return nil } -func (e groupMLSB) Sqr(x mlsb.EltG) { x.(*twistPoint).Double() } -func (e groupMLSB) Mul(x mlsb.EltG, y mlsb.EltP) { x.(*twistPoint).mixAddZ1(y.(*preTwistPointAffine)) } -func (e groupMLSB) Identity() mlsb.EltG { return twistCurve{}.Identity() } -func (e groupMLSB) NewEltP() mlsb.EltP { return &preTwistPointAffine{} } -func (e groupMLSB) Lookup(a mlsb.EltP, v uint, s, u int32) { - Tabj := &tabFixMult[v] - P := a.(*preTwistPointAffine) - for k := range Tabj { - P.cmov(&Tabj[k], uint(subtle.ConstantTimeEq(int32(k), u))) - } - P.cneg(int(s >> 31)) -} diff --git a/vendor/github.com/cloudflare/circl/internal/conv/conv.go b/vendor/github.com/cloudflare/circl/internal/conv/conv.go deleted file mode 100644 index 3fd0df496fd..00000000000 --- a/vendor/github.com/cloudflare/circl/internal/conv/conv.go +++ /dev/null @@ -1,173 +0,0 @@ -package conv - -import ( - "encoding/binary" - "fmt" - "math/big" - "strings" - - "golang.org/x/crypto/cryptobyte" -) - -// BytesLe2Hex returns an hexadecimal string of a number stored in a -// little-endian order slice x. -func BytesLe2Hex(x []byte) string { - b := &strings.Builder{} - b.Grow(2*len(x) + 2) - fmt.Fprint(b, "0x") - if len(x) == 0 { - fmt.Fprint(b, "00") - } - for i := len(x) - 1; i >= 0; i-- { - fmt.Fprintf(b, "%02x", x[i]) - } - return b.String() -} - -// BytesLe2BigInt converts a little-endian slice x into a big-endian -// math/big.Int. -func BytesLe2BigInt(x []byte) *big.Int { - n := len(x) - b := new(big.Int) - if len(x) > 0 { - y := make([]byte, n) - for i := 0; i < n; i++ { - y[n-1-i] = x[i] - } - b.SetBytes(y) - } - return b -} - -// BytesBe2Uint64Le converts a big-endian slice x to a little-endian slice of uint64. -func BytesBe2Uint64Le(x []byte) []uint64 { - l := len(x) - z := make([]uint64, (l+7)/8) - blocks := l / 8 - for i := 0; i < blocks; i++ { - z[i] = binary.BigEndian.Uint64(x[l-8*(i+1):]) - } - remBytes := l % 8 - for i := 0; i < remBytes; i++ { - z[blocks] |= uint64(x[l-1-8*blocks-i]) << uint(8*i) - } - return z -} - -// BigInt2BytesLe stores a positive big.Int number x into a little-endian slice z. -// The slice is modified if the bitlength of x <= 8*len(z) (padding with zeros). -// If x does not fit in the slice or is negative, z is not modified. -func BigInt2BytesLe(z []byte, x *big.Int) { - xLen := (x.BitLen() + 7) >> 3 - zLen := len(z) - if zLen >= xLen && x.Sign() >= 0 { - y := x.Bytes() - for i := 0; i < xLen; i++ { - z[i] = y[xLen-1-i] - } - for i := xLen; i < zLen; i++ { - z[i] = 0 - } - } -} - -// Uint64Le2BigInt converts a little-endian slice x into a big number. -func Uint64Le2BigInt(x []uint64) *big.Int { - n := len(x) - b := new(big.Int) - var bi big.Int - for i := n - 1; i >= 0; i-- { - bi.SetUint64(x[i]) - b.Lsh(b, 64) - b.Add(b, &bi) - } - return b -} - -// Uint64Le2BytesLe converts a little-endian slice x to a little-endian slice of bytes. -func Uint64Le2BytesLe(x []uint64) []byte { - b := make([]byte, 8*len(x)) - n := len(x) - for i := 0; i < n; i++ { - binary.LittleEndian.PutUint64(b[i*8:], x[i]) - } - return b -} - -// Uint64Le2BytesBe converts a little-endian slice x to a big-endian slice of bytes. -func Uint64Le2BytesBe(x []uint64) []byte { - b := make([]byte, 8*len(x)) - n := len(x) - for i := 0; i < n; i++ { - binary.BigEndian.PutUint64(b[i*8:], x[n-1-i]) - } - return b -} - -// Uint64Le2Hex returns an hexadecimal string of a number stored in a -// little-endian order slice x. -func Uint64Le2Hex(x []uint64) string { - b := new(strings.Builder) - b.Grow(16*len(x) + 2) - fmt.Fprint(b, "0x") - if len(x) == 0 { - fmt.Fprint(b, "00") - } - for i := len(x) - 1; i >= 0; i-- { - fmt.Fprintf(b, "%016x", x[i]) - } - return b.String() -} - -// BigInt2Uint64Le stores a positive big.Int number x into a little-endian slice z. -// The slice is modified if the bitlength of x <= 8*len(z) (padding with zeros). -// If x does not fit in the slice or is negative, z is not modified. -func BigInt2Uint64Le(z []uint64, x *big.Int) { - xLen := (x.BitLen() + 63) >> 6 // number of 64-bit words - zLen := len(z) - if zLen >= xLen && x.Sign() > 0 { - var y, yi big.Int - y.Set(x) - two64 := big.NewInt(1) - two64.Lsh(two64, 64).Sub(two64, big.NewInt(1)) - for i := 0; i < xLen; i++ { - yi.And(&y, two64) - z[i] = yi.Uint64() - y.Rsh(&y, 64) - } - } - for i := xLen; i < zLen; i++ { - z[i] = 0 - } -} - -// MarshalBinary encodes a value into a byte array in a format readable by UnmarshalBinary. -func MarshalBinary(v cryptobyte.MarshalingValue) ([]byte, error) { - const DefaultSize = 32 - b := cryptobyte.NewBuilder(make([]byte, 0, DefaultSize)) - b.AddValue(v) - return b.Bytes() -} - -// MarshalBinaryLen encodes a value into an array of n bytes in a format readable by UnmarshalBinary. -func MarshalBinaryLen(v cryptobyte.MarshalingValue, length uint) ([]byte, error) { - b := cryptobyte.NewFixedBuilder(make([]byte, 0, length)) - b.AddValue(v) - return b.Bytes() -} - -// A UnmarshalingValue decodes itself from a cryptobyte.String and advances the pointer. -// It reports whether the read was successful. -type UnmarshalingValue interface { - Unmarshal(*cryptobyte.String) bool -} - -// UnmarshalBinary recovers a value from a byte array. -// It returns an error if the read was unsuccessful. -func UnmarshalBinary(v UnmarshalingValue, data []byte) (err error) { - s := cryptobyte.String(data) - if data == nil || !v.Unmarshal(&s) || !s.Empty() { - err = fmt.Errorf("cannot read %T from input string", v) - } - return -} diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/doc.go b/vendor/github.com/cloudflare/circl/internal/sha3/doc.go deleted file mode 100644 index 7e023090707..00000000000 --- a/vendor/github.com/cloudflare/circl/internal/sha3/doc.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package sha3 implements the SHA-3 fixed-output-length hash functions and -// the SHAKE variable-output-length hash functions defined by FIPS-202. -// -// Both types of hash function use the "sponge" construction and the Keccak -// permutation. For a detailed specification see http://keccak.noekeon.org/ -// -// # Guidance -// -// If you aren't sure what function you need, use SHAKE256 with at least 64 -// bytes of output. The SHAKE instances are faster than the SHA3 instances; -// the latter have to allocate memory to conform to the hash.Hash interface. -// -// If you need a secret-key MAC (message authentication code), prepend the -// secret key to the input, hash with SHAKE256 and read at least 32 bytes of -// output. -// -// # Security strengths -// -// The SHA3-x (x equals 224, 256, 384, or 512) functions have a security -// strength against preimage attacks of x bits. Since they only produce "x" -// bits of output, their collision-resistance is only "x/2" bits. -// -// The SHAKE-256 and -128 functions have a generic security strength of 256 and -// 128 bits against all attacks, provided that at least 2x bits of their output -// is used. Requesting more than 64 or 32 bytes of output, respectively, does -// not increase the collision-resistance of the SHAKE functions. -// -// # The sponge construction -// -// A sponge builds a pseudo-random function from a public pseudo-random -// permutation, by applying the permutation to a state of "rate + capacity" -// bytes, but hiding "capacity" of the bytes. -// -// A sponge starts out with a zero state. To hash an input using a sponge, up -// to "rate" bytes of the input are XORed into the sponge's state. The sponge -// is then "full" and the permutation is applied to "empty" it. This process is -// repeated until all the input has been "absorbed". The input is then padded. -// The digest is "squeezed" from the sponge in the same way, except that output -// is copied out instead of input being XORed in. -// -// A sponge is parameterized by its generic security strength, which is equal -// to half its capacity; capacity + rate is equal to the permutation's width. -// Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means -// that the security strength of a sponge instance is equal to (1600 - bitrate) / 2. -// -// # Recommendations -// -// The SHAKE functions are recommended for most new uses. They can produce -// output of arbitrary length. SHAKE256, with an output length of at least -// 64 bytes, provides 256-bit security against all attacks. The Keccak team -// recommends it for most applications upgrading from SHA2-512. (NIST chose a -// much stronger, but much slower, sponge instance for SHA3-512.) -// -// The SHA-3 functions are "drop-in" replacements for the SHA-2 functions. -// They produce output of the same length, with the same security strengths -// against all attacks. This means, in particular, that SHA3-256 only has -// 128-bit collision resistance, because its output length is 32 bytes. -package sha3 diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/hashes.go b/vendor/github.com/cloudflare/circl/internal/sha3/hashes.go deleted file mode 100644 index 7d2365a76ed..00000000000 --- a/vendor/github.com/cloudflare/circl/internal/sha3/hashes.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package sha3 - -// This file provides functions for creating instances of the SHA-3 -// and SHAKE hash functions, as well as utility functions for hashing -// bytes. - -// New224 creates a new SHA3-224 hash. -// Its generic security strength is 224 bits against preimage attacks, -// and 112 bits against collision attacks. -func New224() State { - return State{rate: 144, outputLen: 28, dsbyte: 0x06} -} - -// New256 creates a new SHA3-256 hash. -// Its generic security strength is 256 bits against preimage attacks, -// and 128 bits against collision attacks. -func New256() State { - return State{rate: 136, outputLen: 32, dsbyte: 0x06} -} - -// New384 creates a new SHA3-384 hash. -// Its generic security strength is 384 bits against preimage attacks, -// and 192 bits against collision attacks. -func New384() State { - return State{rate: 104, outputLen: 48, dsbyte: 0x06} -} - -// New512 creates a new SHA3-512 hash. -// Its generic security strength is 512 bits against preimage attacks, -// and 256 bits against collision attacks. -func New512() State { - return State{rate: 72, outputLen: 64, dsbyte: 0x06} -} - -// Sum224 returns the SHA3-224 digest of the data. -func Sum224(data []byte) (digest [28]byte) { - h := New224() - _, _ = h.Write(data) - h.Sum(digest[:0]) - return -} - -// Sum256 returns the SHA3-256 digest of the data. -func Sum256(data []byte) (digest [32]byte) { - h := New256() - _, _ = h.Write(data) - h.Sum(digest[:0]) - return -} - -// Sum384 returns the SHA3-384 digest of the data. -func Sum384(data []byte) (digest [48]byte) { - h := New384() - _, _ = h.Write(data) - h.Sum(digest[:0]) - return -} - -// Sum512 returns the SHA3-512 digest of the data. -func Sum512(data []byte) (digest [64]byte) { - h := New512() - _, _ = h.Write(data) - h.Sum(digest[:0]) - return -} diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/keccakf.go b/vendor/github.com/cloudflare/circl/internal/sha3/keccakf.go deleted file mode 100644 index 1755fd1e6dc..00000000000 --- a/vendor/github.com/cloudflare/circl/internal/sha3/keccakf.go +++ /dev/null @@ -1,391 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package sha3 - -// KeccakF1600 applies the Keccak permutation to a 1600b-wide -// state represented as a slice of 25 uint64s. -// If turbo is true, applies the 12-round variant instead of the -// regular 24-round variant. -// nolint:funlen -func KeccakF1600(a *[25]uint64, turbo bool) { - // Implementation translated from Keccak-inplace.c - // in the keccak reference code. - var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 - - i := 0 - - if turbo { - i = 12 - } - - for ; i < 24; i += 4 { - // Combines the 5 steps in each round into 2 steps. - // Unrolls 4 rounds per loop and spreads some steps across rounds. - - // Round 1 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[6] ^ d1 - bc1 = t<<44 | t>>(64-44) - t = a[12] ^ d2 - bc2 = t<<43 | t>>(64-43) - t = a[18] ^ d3 - bc3 = t<<21 | t>>(64-21) - t = a[24] ^ d4 - bc4 = t<<14 | t>>(64-14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ RC[i] - a[6] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc2 = t<<3 | t>>(64-3) - t = a[16] ^ d1 - bc3 = t<<45 | t>>(64-45) - t = a[22] ^ d2 - bc4 = t<<61 | t>>(64-61) - t = a[3] ^ d3 - bc0 = t<<28 | t>>(64-28) - t = a[9] ^ d4 - bc1 = t<<20 | t>>(64-20) - a[10] = bc0 ^ (bc2 &^ bc1) - a[16] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc4 = t<<18 | t>>(64-18) - t = a[1] ^ d1 - bc0 = t<<1 | t>>(64-1) - t = a[7] ^ d2 - bc1 = t<<6 | t>>(64-6) - t = a[13] ^ d3 - bc2 = t<<25 | t>>(64-25) - t = a[19] ^ d4 - bc3 = t<<8 | t>>(64-8) - a[20] = bc0 ^ (bc2 &^ bc1) - a[1] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc1 = t<<36 | t>>(64-36) - t = a[11] ^ d1 - bc2 = t<<10 | t>>(64-10) - t = a[17] ^ d2 - bc3 = t<<15 | t>>(64-15) - t = a[23] ^ d3 - bc4 = t<<56 | t>>(64-56) - t = a[4] ^ d4 - bc0 = t<<27 | t>>(64-27) - a[5] = bc0 ^ (bc2 &^ bc1) - a[11] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc3 = t<<41 | t>>(64-41) - t = a[21] ^ d1 - bc4 = t<<2 | t>>(64-2) - t = a[2] ^ d2 - bc0 = t<<62 | t>>(64-62) - t = a[8] ^ d3 - bc1 = t<<55 | t>>(64-55) - t = a[14] ^ d4 - bc2 = t<<39 | t>>(64-39) - a[15] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - // Round 2 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[16] ^ d1 - bc1 = t<<44 | t>>(64-44) - t = a[7] ^ d2 - bc2 = t<<43 | t>>(64-43) - t = a[23] ^ d3 - bc3 = t<<21 | t>>(64-21) - t = a[14] ^ d4 - bc4 = t<<14 | t>>(64-14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ RC[i+1] - a[16] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc2 = t<<3 | t>>(64-3) - t = a[11] ^ d1 - bc3 = t<<45 | t>>(64-45) - t = a[2] ^ d2 - bc4 = t<<61 | t>>(64-61) - t = a[18] ^ d3 - bc0 = t<<28 | t>>(64-28) - t = a[9] ^ d4 - bc1 = t<<20 | t>>(64-20) - a[20] = bc0 ^ (bc2 &^ bc1) - a[11] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc4 = t<<18 | t>>(64-18) - t = a[6] ^ d1 - bc0 = t<<1 | t>>(64-1) - t = a[22] ^ d2 - bc1 = t<<6 | t>>(64-6) - t = a[13] ^ d3 - bc2 = t<<25 | t>>(64-25) - t = a[4] ^ d4 - bc3 = t<<8 | t>>(64-8) - a[15] = bc0 ^ (bc2 &^ bc1) - a[6] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc1 = t<<36 | t>>(64-36) - t = a[1] ^ d1 - bc2 = t<<10 | t>>(64-10) - t = a[17] ^ d2 - bc3 = t<<15 | t>>(64-15) - t = a[8] ^ d3 - bc4 = t<<56 | t>>(64-56) - t = a[24] ^ d4 - bc0 = t<<27 | t>>(64-27) - a[10] = bc0 ^ (bc2 &^ bc1) - a[1] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc3 = t<<41 | t>>(64-41) - t = a[21] ^ d1 - bc4 = t<<2 | t>>(64-2) - t = a[12] ^ d2 - bc0 = t<<62 | t>>(64-62) - t = a[3] ^ d3 - bc1 = t<<55 | t>>(64-55) - t = a[19] ^ d4 - bc2 = t<<39 | t>>(64-39) - a[5] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - // Round 3 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[11] ^ d1 - bc1 = t<<44 | t>>(64-44) - t = a[22] ^ d2 - bc2 = t<<43 | t>>(64-43) - t = a[8] ^ d3 - bc3 = t<<21 | t>>(64-21) - t = a[19] ^ d4 - bc4 = t<<14 | t>>(64-14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ RC[i+2] - a[11] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc2 = t<<3 | t>>(64-3) - t = a[1] ^ d1 - bc3 = t<<45 | t>>(64-45) - t = a[12] ^ d2 - bc4 = t<<61 | t>>(64-61) - t = a[23] ^ d3 - bc0 = t<<28 | t>>(64-28) - t = a[9] ^ d4 - bc1 = t<<20 | t>>(64-20) - a[15] = bc0 ^ (bc2 &^ bc1) - a[1] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc4 = t<<18 | t>>(64-18) - t = a[16] ^ d1 - bc0 = t<<1 | t>>(64-1) - t = a[2] ^ d2 - bc1 = t<<6 | t>>(64-6) - t = a[13] ^ d3 - bc2 = t<<25 | t>>(64-25) - t = a[24] ^ d4 - bc3 = t<<8 | t>>(64-8) - a[5] = bc0 ^ (bc2 &^ bc1) - a[16] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc1 = t<<36 | t>>(64-36) - t = a[6] ^ d1 - bc2 = t<<10 | t>>(64-10) - t = a[17] ^ d2 - bc3 = t<<15 | t>>(64-15) - t = a[3] ^ d3 - bc4 = t<<56 | t>>(64-56) - t = a[14] ^ d4 - bc0 = t<<27 | t>>(64-27) - a[20] = bc0 ^ (bc2 &^ bc1) - a[6] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc3 = t<<41 | t>>(64-41) - t = a[21] ^ d1 - bc4 = t<<2 | t>>(64-2) - t = a[7] ^ d2 - bc0 = t<<62 | t>>(64-62) - t = a[18] ^ d3 - bc1 = t<<55 | t>>(64-55) - t = a[4] ^ d4 - bc2 = t<<39 | t>>(64-39) - a[10] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - // Round 4 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[1] ^ d1 - bc1 = t<<44 | t>>(64-44) - t = a[2] ^ d2 - bc2 = t<<43 | t>>(64-43) - t = a[3] ^ d3 - bc3 = t<<21 | t>>(64-21) - t = a[4] ^ d4 - bc4 = t<<14 | t>>(64-14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ RC[i+3] - a[1] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc2 = t<<3 | t>>(64-3) - t = a[6] ^ d1 - bc3 = t<<45 | t>>(64-45) - t = a[7] ^ d2 - bc4 = t<<61 | t>>(64-61) - t = a[8] ^ d3 - bc0 = t<<28 | t>>(64-28) - t = a[9] ^ d4 - bc1 = t<<20 | t>>(64-20) - a[5] = bc0 ^ (bc2 &^ bc1) - a[6] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc4 = t<<18 | t>>(64-18) - t = a[11] ^ d1 - bc0 = t<<1 | t>>(64-1) - t = a[12] ^ d2 - bc1 = t<<6 | t>>(64-6) - t = a[13] ^ d3 - bc2 = t<<25 | t>>(64-25) - t = a[14] ^ d4 - bc3 = t<<8 | t>>(64-8) - a[10] = bc0 ^ (bc2 &^ bc1) - a[11] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc1 = t<<36 | t>>(64-36) - t = a[16] ^ d1 - bc2 = t<<10 | t>>(64-10) - t = a[17] ^ d2 - bc3 = t<<15 | t>>(64-15) - t = a[18] ^ d3 - bc4 = t<<56 | t>>(64-56) - t = a[19] ^ d4 - bc0 = t<<27 | t>>(64-27) - a[15] = bc0 ^ (bc2 &^ bc1) - a[16] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc3 = t<<41 | t>>(64-41) - t = a[21] ^ d1 - bc4 = t<<2 | t>>(64-2) - t = a[22] ^ d2 - bc0 = t<<62 | t>>(64-62) - t = a[23] ^ d3 - bc1 = t<<55 | t>>(64-55) - t = a[24] ^ d4 - bc2 = t<<39 | t>>(64-39) - a[20] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - } -} diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/rc.go b/vendor/github.com/cloudflare/circl/internal/sha3/rc.go deleted file mode 100644 index 6a3df42f305..00000000000 --- a/vendor/github.com/cloudflare/circl/internal/sha3/rc.go +++ /dev/null @@ -1,29 +0,0 @@ -package sha3 - -// RC stores the round constants for use in the ι step. -var RC = [24]uint64{ - 0x0000000000000001, - 0x0000000000008082, - 0x800000000000808A, - 0x8000000080008000, - 0x000000000000808B, - 0x0000000080000001, - 0x8000000080008081, - 0x8000000000008009, - 0x000000000000008A, - 0x0000000000000088, - 0x0000000080008009, - 0x000000008000000A, - 0x000000008000808B, - 0x800000000000008B, - 0x8000000000008089, - 0x8000000000008003, - 0x8000000000008002, - 0x8000000000000080, - 0x000000000000800A, - 0x800000008000000A, - 0x8000000080008081, - 0x8000000000008080, - 0x0000000080000001, - 0x8000000080008008, -} diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/sha3.go b/vendor/github.com/cloudflare/circl/internal/sha3/sha3.go deleted file mode 100644 index a0df5aa6c59..00000000000 --- a/vendor/github.com/cloudflare/circl/internal/sha3/sha3.go +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package sha3 - -// spongeDirection indicates the direction bytes are flowing through the sponge. -type spongeDirection int - -const ( - // spongeAbsorbing indicates that the sponge is absorbing input. - spongeAbsorbing spongeDirection = iota - // spongeSqueezing indicates that the sponge is being squeezed. - spongeSqueezing -) - -const ( - // maxRate is the maximum size of the internal buffer. SHAKE-256 - // currently needs the largest buffer. - maxRate = 168 -) - -func (d *State) buf() []byte { - return d.storage.asBytes()[d.bufo:d.bufe] -} - -type State struct { - // Generic sponge components. - a [25]uint64 // main state of the hash - rate int // the number of bytes of state to use - - bufo int // offset of buffer in storage - bufe int // end of buffer in storage - - // dsbyte contains the "domain separation" bits and the first bit of - // the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the - // SHA-3 and SHAKE functions by appending bitstrings to the message. - // Using a little-endian bit-ordering convention, these are "01" for SHA-3 - // and "1111" for SHAKE, or 00000010b and 00001111b, respectively. Then the - // padding rule from section 5.1 is applied to pad the message to a multiple - // of the rate, which involves adding a "1" bit, zero or more "0" bits, and - // a final "1" bit. We merge the first "1" bit from the padding into dsbyte, - // giving 00000110b (0x06) and 00011111b (0x1f). - // [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf - // "Draft FIPS 202: SHA-3 Standard: Permutation-Based Hash and - // Extendable-Output Functions (May 2014)" - dsbyte byte - - storage storageBuf - - // Specific to SHA-3 and SHAKE. - outputLen int // the default output size in bytes - state spongeDirection // whether the sponge is absorbing or squeezing - turbo bool // Whether we're using 12 rounds instead of 24 -} - -// BlockSize returns the rate of sponge underlying this hash function. -func (d *State) BlockSize() int { return d.rate } - -// Size returns the output size of the hash function in bytes. -func (d *State) Size() int { return d.outputLen } - -// Reset clears the internal state by zeroing the sponge state and -// the byte buffer, and setting Sponge.state to absorbing. -func (d *State) Reset() { - // Zero the permutation's state. - for i := range d.a { - d.a[i] = 0 - } - d.state = spongeAbsorbing - d.bufo = 0 - d.bufe = 0 -} - -func (d *State) clone() *State { - ret := *d - return &ret -} - -// permute applies the KeccakF-1600 permutation. It handles -// any input-output buffering. -func (d *State) permute() { - switch d.state { - case spongeAbsorbing: - // If we're absorbing, we need to xor the input into the state - // before applying the permutation. - xorIn(d, d.buf()) - d.bufe = 0 - d.bufo = 0 - KeccakF1600(&d.a, d.turbo) - case spongeSqueezing: - // If we're squeezing, we need to apply the permutation before - // copying more output. - KeccakF1600(&d.a, d.turbo) - d.bufe = d.rate - d.bufo = 0 - copyOut(d, d.buf()) - } -} - -// pads appends the domain separation bits in dsbyte, applies -// the multi-bitrate 10..1 padding rule, and permutes the state. -func (d *State) padAndPermute(dsbyte byte) { - // Pad with this instance's domain-separator bits. We know that there's - // at least one byte of space in d.buf() because, if it were full, - // permute would have been called to empty it. dsbyte also contains the - // first one bit for the padding. See the comment in the state struct. - zerosStart := d.bufe + 1 - d.bufe = d.rate - buf := d.buf() - buf[zerosStart-1] = dsbyte - for i := zerosStart; i < d.rate; i++ { - buf[i] = 0 - } - // This adds the final one bit for the padding. Because of the way that - // bits are numbered from the LSB upwards, the final bit is the MSB of - // the last byte. - buf[d.rate-1] ^= 0x80 - // Apply the permutation - d.permute() - d.state = spongeSqueezing - d.bufe = d.rate - copyOut(d, buf) -} - -// Write absorbs more data into the hash's state. It produces an error -// if more data is written to the ShakeHash after writing -func (d *State) Write(p []byte) (written int, err error) { - if d.state != spongeAbsorbing { - panic("sha3: write to sponge after read") - } - written = len(p) - - for len(p) > 0 { - bufl := d.bufe - d.bufo - if bufl == 0 && len(p) >= d.rate { - // The fast path; absorb a full "rate" bytes of input and apply the permutation. - xorIn(d, p[:d.rate]) - p = p[d.rate:] - KeccakF1600(&d.a, d.turbo) - } else { - // The slow path; buffer the input until we can fill the sponge, and then xor it in. - todo := d.rate - bufl - if todo > len(p) { - todo = len(p) - } - d.bufe += todo - buf := d.buf() - copy(buf[bufl:], p[:todo]) - p = p[todo:] - - // If the sponge is full, apply the permutation. - if d.bufe == d.rate { - d.permute() - } - } - } - - return written, nil -} - -// Read squeezes an arbitrary number of bytes from the sponge. -func (d *State) Read(out []byte) (n int, err error) { - // If we're still absorbing, pad and apply the permutation. - if d.state == spongeAbsorbing { - d.padAndPermute(d.dsbyte) - } - - n = len(out) - - // Now, do the squeezing. - for len(out) > 0 { - buf := d.buf() - n := copy(out, buf) - d.bufo += n - out = out[n:] - - // Apply the permutation if we've squeezed the sponge dry. - if d.bufo == d.bufe { - d.permute() - } - } - - return -} - -// Sum applies padding to the hash state and then squeezes out the desired -// number of output bytes. -func (d *State) Sum(in []byte) []byte { - // Make a copy of the original hash so that caller can keep writing - // and summing. - dup := d.clone() - hash := make([]byte, dup.outputLen) - _, _ = dup.Read(hash) - return append(in, hash...) -} - -func (d *State) IsAbsorbing() bool { - return d.state == spongeAbsorbing -} diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/sha3_s390x.s b/vendor/github.com/cloudflare/circl/internal/sha3/sha3_s390x.s deleted file mode 100644 index 8a4458f63f9..00000000000 --- a/vendor/github.com/cloudflare/circl/internal/sha3/sha3_s390x.s +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !gccgo,!appengine - -#include "textflag.h" - -// func kimd(function code, chain *[200]byte, src []byte) -TEXT ·kimd(SB), NOFRAME|NOSPLIT, $0-40 - MOVD function+0(FP), R0 - MOVD chain+8(FP), R1 - LMG src+16(FP), R2, R3 // R2=base, R3=len - -continue: - WORD $0xB93E0002 // KIMD --, R2 - BVS continue // continue if interrupted - MOVD $0, R0 // reset R0 for pre-go1.8 compilers - RET - -// func klmd(function code, chain *[200]byte, dst, src []byte) -TEXT ·klmd(SB), NOFRAME|NOSPLIT, $0-64 - // TODO: SHAKE support - MOVD function+0(FP), R0 - MOVD chain+8(FP), R1 - LMG dst+16(FP), R2, R3 // R2=base, R3=len - LMG src+40(FP), R4, R5 // R4=base, R5=len - -continue: - WORD $0xB93F0024 // KLMD R2, R4 - BVS continue // continue if interrupted - MOVD $0, R0 // reset R0 for pre-go1.8 compilers - RET diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/shake.go b/vendor/github.com/cloudflare/circl/internal/sha3/shake.go deleted file mode 100644 index 77817f758cb..00000000000 --- a/vendor/github.com/cloudflare/circl/internal/sha3/shake.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package sha3 - -// This file defines the ShakeHash interface, and provides -// functions for creating SHAKE and cSHAKE instances, as well as utility -// functions for hashing bytes to arbitrary-length output. -// -// -// SHAKE implementation is based on FIPS PUB 202 [1] -// cSHAKE implementations is based on NIST SP 800-185 [2] -// -// [1] https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf -// [2] https://doi.org/10.6028/NIST.SP.800-185 - -import ( - "io" -) - -// ShakeHash defines the interface to hash functions that -// support arbitrary-length output. -type ShakeHash interface { - // Write absorbs more data into the hash's state. It panics if input is - // written to it after output has been read from it. - io.Writer - - // Read reads more output from the hash; reading affects the hash's - // state. (ShakeHash.Read is thus very different from Hash.Sum) - // It never returns an error. - io.Reader - - // Clone returns a copy of the ShakeHash in its current state. - Clone() ShakeHash - - // Reset resets the ShakeHash to its initial state. - Reset() -} - -// Consts for configuring initial SHA-3 state -const ( - dsbyteShake = 0x1f - rate128 = 168 - rate256 = 136 -) - -// Clone returns copy of SHAKE context within its current state. -func (d *State) Clone() ShakeHash { - return d.clone() -} - -// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash. -// Its generic security strength is 128 bits against all attacks if at -// least 32 bytes of its output are used. -func NewShake128() State { - return State{rate: rate128, dsbyte: dsbyteShake} -} - -// NewTurboShake128 creates a new TurboSHAKE128 variable-output-length ShakeHash. -// Its generic security strength is 128 bits against all attacks if at -// least 32 bytes of its output are used. -// D is the domain separation byte and must be between 0x01 and 0x7f inclusive. -func NewTurboShake128(D byte) State { - if D == 0 || D > 0x7f { - panic("turboshake: D out of range") - } - return State{rate: rate128, dsbyte: D, turbo: true} -} - -// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash. -// Its generic security strength is 256 bits against all attacks if -// at least 64 bytes of its output are used. -func NewShake256() State { - return State{rate: rate256, dsbyte: dsbyteShake} -} - -// NewTurboShake256 creates a new TurboSHAKE256 variable-output-length ShakeHash. -// Its generic security strength is 256 bits against all attacks if -// at least 64 bytes of its output are used. -// D is the domain separation byte and must be between 0x01 and 0x7f inclusive. -func NewTurboShake256(D byte) State { - if D == 0 || D > 0x7f { - panic("turboshake: D out of range") - } - return State{rate: rate256, dsbyte: D, turbo: true} -} - -// ShakeSum128 writes an arbitrary-length digest of data into hash. -func ShakeSum128(hash, data []byte) { - h := NewShake128() - _, _ = h.Write(data) - _, _ = h.Read(hash) -} - -// ShakeSum256 writes an arbitrary-length digest of data into hash. -func ShakeSum256(hash, data []byte) { - h := NewShake256() - _, _ = h.Write(data) - _, _ = h.Read(hash) -} - -// TurboShakeSum128 writes an arbitrary-length digest of data into hash. -func TurboShakeSum128(hash, data []byte, D byte) { - h := NewTurboShake128(D) - _, _ = h.Write(data) - _, _ = h.Read(hash) -} - -// TurboShakeSum256 writes an arbitrary-length digest of data into hash. -func TurboShakeSum256(hash, data []byte, D byte) { - h := NewTurboShake256(D) - _, _ = h.Write(data) - _, _ = h.Read(hash) -} - -func (d *State) SwitchDS(D byte) { - d.dsbyte = D -} diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/xor.go b/vendor/github.com/cloudflare/circl/internal/sha3/xor.go deleted file mode 100644 index 1e21337454f..00000000000 --- a/vendor/github.com/cloudflare/circl/internal/sha3/xor.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build (!amd64 && !386 && !ppc64le) || appengine -// +build !amd64,!386,!ppc64le appengine - -package sha3 - -// A storageBuf is an aligned array of maxRate bytes. -type storageBuf [maxRate]byte - -func (b *storageBuf) asBytes() *[maxRate]byte { - return (*[maxRate]byte)(b) -} diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/xor_generic.go b/vendor/github.com/cloudflare/circl/internal/sha3/xor_generic.go deleted file mode 100644 index 2b0c6617906..00000000000 --- a/vendor/github.com/cloudflare/circl/internal/sha3/xor_generic.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build (!amd64 || appengine) && (!386 || appengine) && (!ppc64le || appengine) -// +build !amd64 appengine -// +build !386 appengine -// +build !ppc64le appengine - -package sha3 - -import "encoding/binary" - -// xorIn xors the bytes in buf into the state; it -// makes no non-portable assumptions about memory layout -// or alignment. -func xorIn(d *State, buf []byte) { - n := len(buf) / 8 - - for i := 0; i < n; i++ { - a := binary.LittleEndian.Uint64(buf) - d.a[i] ^= a - buf = buf[8:] - } -} - -// copyOut copies ulint64s to a byte buffer. -func copyOut(d *State, b []byte) { - for i := 0; len(b) >= 8; i++ { - binary.LittleEndian.PutUint64(b, d.a[i]) - b = b[8:] - } -} diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go b/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go deleted file mode 100644 index 052fc8d32d2..00000000000 --- a/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build (amd64 || 386 || ppc64le) && !appengine -// +build amd64 386 ppc64le -// +build !appengine - -package sha3 - -import "unsafe" - -// A storageBuf is an aligned array of maxRate bytes. -type storageBuf [maxRate / 8]uint64 - -func (b *storageBuf) asBytes() *[maxRate]byte { - return (*[maxRate]byte)(unsafe.Pointer(b)) -} - -// xorInuses unaligned reads and writes to update d.a to contain d.a -// XOR buf. -func xorIn(d *State, buf []byte) { - n := len(buf) - bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8] - if n >= 72 { - d.a[0] ^= bw[0] - d.a[1] ^= bw[1] - d.a[2] ^= bw[2] - d.a[3] ^= bw[3] - d.a[4] ^= bw[4] - d.a[5] ^= bw[5] - d.a[6] ^= bw[6] - d.a[7] ^= bw[7] - d.a[8] ^= bw[8] - } - if n >= 104 { - d.a[9] ^= bw[9] - d.a[10] ^= bw[10] - d.a[11] ^= bw[11] - d.a[12] ^= bw[12] - } - if n >= 136 { - d.a[13] ^= bw[13] - d.a[14] ^= bw[14] - d.a[15] ^= bw[15] - d.a[16] ^= bw[16] - } - if n >= 144 { - d.a[17] ^= bw[17] - } - if n >= 168 { - d.a[18] ^= bw[18] - d.a[19] ^= bw[19] - d.a[20] ^= bw[20] - } -} - -func copyOut(d *State, buf []byte) { - ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) - copy(buf, ab[:]) -} diff --git a/vendor/github.com/cloudflare/circl/math/fp25519/fp.go b/vendor/github.com/cloudflare/circl/math/fp25519/fp.go deleted file mode 100644 index 57a50ff5e9b..00000000000 --- a/vendor/github.com/cloudflare/circl/math/fp25519/fp.go +++ /dev/null @@ -1,205 +0,0 @@ -// Package fp25519 provides prime field arithmetic over GF(2^255-19). -package fp25519 - -import ( - "errors" - - "github.com/cloudflare/circl/internal/conv" -) - -// Size in bytes of an element. -const Size = 32 - -// Elt is a prime field element. -type Elt [Size]byte - -func (e Elt) String() string { return conv.BytesLe2Hex(e[:]) } - -// p is the prime modulus 2^255-19. -var p = Elt{ - 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, -} - -// P returns the prime modulus 2^255-19. -func P() Elt { return p } - -// ToBytes stores in b the little-endian byte representation of x. -func ToBytes(b []byte, x *Elt) error { - if len(b) != Size { - return errors.New("wrong size") - } - Modp(x) - copy(b, x[:]) - return nil -} - -// IsZero returns true if x is equal to 0. -func IsZero(x *Elt) bool { Modp(x); return *x == Elt{} } - -// SetOne assigns x=1. -func SetOne(x *Elt) { *x = Elt{}; x[0] = 1 } - -// Neg calculates z = -x. -func Neg(z, x *Elt) { Sub(z, &p, x) } - -// InvSqrt calculates z = sqrt(x/y) iff x/y is a quadratic-residue, which is -// indicated by returning isQR = true. Otherwise, when x/y is a quadratic -// non-residue, z will have an undetermined value and isQR = false. -func InvSqrt(z, x, y *Elt) (isQR bool) { - sqrtMinusOne := &Elt{ - 0xb0, 0xa0, 0x0e, 0x4a, 0x27, 0x1b, 0xee, 0xc4, - 0x78, 0xe4, 0x2f, 0xad, 0x06, 0x18, 0x43, 0x2f, - 0xa7, 0xd7, 0xfb, 0x3d, 0x99, 0x00, 0x4d, 0x2b, - 0x0b, 0xdf, 0xc1, 0x4f, 0x80, 0x24, 0x83, 0x2b, - } - t0, t1, t2, t3 := &Elt{}, &Elt{}, &Elt{}, &Elt{} - - Mul(t0, x, y) // t0 = u*v - Sqr(t1, y) // t1 = v^2 - Mul(t2, t0, t1) // t2 = u*v^3 - Sqr(t0, t1) // t0 = v^4 - Mul(t1, t0, t2) // t1 = u*v^7 - - var Tab [4]*Elt - Tab[0] = &Elt{} - Tab[1] = &Elt{} - Tab[2] = t3 - Tab[3] = t1 - - *Tab[0] = *t1 - Sqr(Tab[0], Tab[0]) - Sqr(Tab[1], Tab[0]) - Sqr(Tab[1], Tab[1]) - Mul(Tab[1], Tab[1], Tab[3]) - Mul(Tab[0], Tab[0], Tab[1]) - Sqr(Tab[0], Tab[0]) - Mul(Tab[0], Tab[0], Tab[1]) - Sqr(Tab[1], Tab[0]) - for i := 0; i < 4; i++ { - Sqr(Tab[1], Tab[1]) - } - Mul(Tab[1], Tab[1], Tab[0]) - Sqr(Tab[2], Tab[1]) - for i := 0; i < 4; i++ { - Sqr(Tab[2], Tab[2]) - } - Mul(Tab[2], Tab[2], Tab[0]) - Sqr(Tab[1], Tab[2]) - for i := 0; i < 14; i++ { - Sqr(Tab[1], Tab[1]) - } - Mul(Tab[1], Tab[1], Tab[2]) - Sqr(Tab[2], Tab[1]) - for i := 0; i < 29; i++ { - Sqr(Tab[2], Tab[2]) - } - Mul(Tab[2], Tab[2], Tab[1]) - Sqr(Tab[1], Tab[2]) - for i := 0; i < 59; i++ { - Sqr(Tab[1], Tab[1]) - } - Mul(Tab[1], Tab[1], Tab[2]) - for i := 0; i < 5; i++ { - Sqr(Tab[1], Tab[1]) - } - Mul(Tab[1], Tab[1], Tab[0]) - Sqr(Tab[2], Tab[1]) - for i := 0; i < 124; i++ { - Sqr(Tab[2], Tab[2]) - } - Mul(Tab[2], Tab[2], Tab[1]) - Sqr(Tab[2], Tab[2]) - Sqr(Tab[2], Tab[2]) - Mul(Tab[2], Tab[2], Tab[3]) - - Mul(z, t3, t2) // z = xy^(p+3)/8 = xy^3*(xy^7)^(p-5)/8 - // Checking whether y z^2 == x - Sqr(t0, z) // t0 = z^2 - Mul(t0, t0, y) // t0 = yz^2 - Sub(t1, t0, x) // t1 = t0-u - Add(t2, t0, x) // t2 = t0+u - if IsZero(t1) { - return true - } else if IsZero(t2) { - Mul(z, z, sqrtMinusOne) // z = z*sqrt(-1) - return true - } else { - return false - } -} - -// Inv calculates z = 1/x mod p. -func Inv(z, x *Elt) { - x0, x1, x2 := &Elt{}, &Elt{}, &Elt{} - Sqr(x1, x) - Sqr(x0, x1) - Sqr(x0, x0) - Mul(x0, x0, x) - Mul(z, x0, x1) - Sqr(x1, z) - Mul(x0, x0, x1) - Sqr(x1, x0) - for i := 0; i < 4; i++ { - Sqr(x1, x1) - } - Mul(x0, x0, x1) - Sqr(x1, x0) - for i := 0; i < 9; i++ { - Sqr(x1, x1) - } - Mul(x1, x1, x0) - Sqr(x2, x1) - for i := 0; i < 19; i++ { - Sqr(x2, x2) - } - Mul(x2, x2, x1) - for i := 0; i < 10; i++ { - Sqr(x2, x2) - } - Mul(x2, x2, x0) - Sqr(x0, x2) - for i := 0; i < 49; i++ { - Sqr(x0, x0) - } - Mul(x0, x0, x2) - Sqr(x1, x0) - for i := 0; i < 99; i++ { - Sqr(x1, x1) - } - Mul(x1, x1, x0) - for i := 0; i < 50; i++ { - Sqr(x1, x1) - } - Mul(x1, x1, x2) - for i := 0; i < 5; i++ { - Sqr(x1, x1) - } - Mul(z, z, x1) -} - -// Cmov assigns y to x if n is 1. -func Cmov(x, y *Elt, n uint) { cmov(x, y, n) } - -// Cswap interchanges x and y if n is 1. -func Cswap(x, y *Elt, n uint) { cswap(x, y, n) } - -// Add calculates z = x+y mod p. -func Add(z, x, y *Elt) { add(z, x, y) } - -// Sub calculates z = x-y mod p. -func Sub(z, x, y *Elt) { sub(z, x, y) } - -// AddSub calculates (x,y) = (x+y mod p, x-y mod p). -func AddSub(x, y *Elt) { addsub(x, y) } - -// Mul calculates z = x*y mod p. -func Mul(z, x, y *Elt) { mul(z, x, y) } - -// Sqr calculates z = x^2 mod p. -func Sqr(z, x *Elt) { sqr(z, x) } - -// Modp ensures that z is between [0,p-1]. -func Modp(z *Elt) { modp(z) } diff --git a/vendor/github.com/cloudflare/circl/math/fp25519/fp_amd64.go b/vendor/github.com/cloudflare/circl/math/fp25519/fp_amd64.go deleted file mode 100644 index 057f0d2803f..00000000000 --- a/vendor/github.com/cloudflare/circl/math/fp25519/fp_amd64.go +++ /dev/null @@ -1,45 +0,0 @@ -//go:build amd64 && !purego -// +build amd64,!purego - -package fp25519 - -import ( - "golang.org/x/sys/cpu" -) - -var hasBmi2Adx = cpu.X86.HasBMI2 && cpu.X86.HasADX - -var _ = hasBmi2Adx - -func cmov(x, y *Elt, n uint) { cmovAmd64(x, y, n) } -func cswap(x, y *Elt, n uint) { cswapAmd64(x, y, n) } -func add(z, x, y *Elt) { addAmd64(z, x, y) } -func sub(z, x, y *Elt) { subAmd64(z, x, y) } -func addsub(x, y *Elt) { addsubAmd64(x, y) } -func mul(z, x, y *Elt) { mulAmd64(z, x, y) } -func sqr(z, x *Elt) { sqrAmd64(z, x) } -func modp(z *Elt) { modpAmd64(z) } - -//go:noescape -func cmovAmd64(x, y *Elt, n uint) - -//go:noescape -func cswapAmd64(x, y *Elt, n uint) - -//go:noescape -func addAmd64(z, x, y *Elt) - -//go:noescape -func subAmd64(z, x, y *Elt) - -//go:noescape -func addsubAmd64(x, y *Elt) - -//go:noescape -func mulAmd64(z, x, y *Elt) - -//go:noescape -func sqrAmd64(z, x *Elt) - -//go:noescape -func modpAmd64(z *Elt) diff --git a/vendor/github.com/cloudflare/circl/math/fp25519/fp_amd64.h b/vendor/github.com/cloudflare/circl/math/fp25519/fp_amd64.h deleted file mode 100644 index b884b584ab3..00000000000 --- a/vendor/github.com/cloudflare/circl/math/fp25519/fp_amd64.h +++ /dev/null @@ -1,351 +0,0 @@ -// This code was imported from https://github.com/armfazh/rfc7748_precomputed - -// CHECK_BMI2ADX triggers bmi2adx if supported, -// otherwise it fallbacks to legacy code. -#define CHECK_BMI2ADX(label, legacy, bmi2adx) \ - CMPB ·hasBmi2Adx(SB), $0 \ - JE label \ - bmi2adx \ - RET \ - label: \ - legacy \ - RET - -// cselect is a conditional move -// if b=1: it copies y into x; -// if b=0: x remains with the same value; -// if b<> 0,1: undefined. -// Uses: AX, DX, FLAGS -// Instr: x86_64, cmov -#define cselect(x,y,b) \ - TESTQ b, b \ - MOVQ 0+x, AX; MOVQ 0+y, DX; CMOVQNE DX, AX; MOVQ AX, 0+x; \ - MOVQ 8+x, AX; MOVQ 8+y, DX; CMOVQNE DX, AX; MOVQ AX, 8+x; \ - MOVQ 16+x, AX; MOVQ 16+y, DX; CMOVQNE DX, AX; MOVQ AX, 16+x; \ - MOVQ 24+x, AX; MOVQ 24+y, DX; CMOVQNE DX, AX; MOVQ AX, 24+x; - -// cswap is a conditional swap -// if b=1: x,y <- y,x; -// if b=0: x,y remain with the same values; -// if b<> 0,1: undefined. -// Uses: AX, DX, R8, FLAGS -// Instr: x86_64, cmov -#define cswap(x,y,b) \ - TESTQ b, b \ - MOVQ 0+x, AX; MOVQ AX, R8; MOVQ 0+y, DX; CMOVQNE DX, AX; CMOVQNE R8, DX; MOVQ AX, 0+x; MOVQ DX, 0+y; \ - MOVQ 8+x, AX; MOVQ AX, R8; MOVQ 8+y, DX; CMOVQNE DX, AX; CMOVQNE R8, DX; MOVQ AX, 8+x; MOVQ DX, 8+y; \ - MOVQ 16+x, AX; MOVQ AX, R8; MOVQ 16+y, DX; CMOVQNE DX, AX; CMOVQNE R8, DX; MOVQ AX, 16+x; MOVQ DX, 16+y; \ - MOVQ 24+x, AX; MOVQ AX, R8; MOVQ 24+y, DX; CMOVQNE DX, AX; CMOVQNE R8, DX; MOVQ AX, 24+x; MOVQ DX, 24+y; - -// additionLeg adds x and y and stores in z -// Uses: AX, DX, R8-R11, FLAGS -// Instr: x86_64, cmov -#define additionLeg(z,x,y) \ - MOVL $38, AX; \ - MOVL $0, DX; \ - MOVQ 0+x, R8; ADDQ 0+y, R8; \ - MOVQ 8+x, R9; ADCQ 8+y, R9; \ - MOVQ 16+x, R10; ADCQ 16+y, R10; \ - MOVQ 24+x, R11; ADCQ 24+y, R11; \ - CMOVQCS AX, DX; \ - ADDQ DX, R8; \ - ADCQ $0, R9; MOVQ R9, 8+z; \ - ADCQ $0, R10; MOVQ R10, 16+z; \ - ADCQ $0, R11; MOVQ R11, 24+z; \ - MOVL $0, DX; \ - CMOVQCS AX, DX; \ - ADDQ DX, R8; MOVQ R8, 0+z; - -// additionAdx adds x and y and stores in z -// Uses: AX, DX, R8-R11, FLAGS -// Instr: x86_64, cmov, adx -#define additionAdx(z,x,y) \ - MOVL $38, AX; \ - XORL DX, DX; \ - MOVQ 0+x, R8; ADCXQ 0+y, R8; \ - MOVQ 8+x, R9; ADCXQ 8+y, R9; \ - MOVQ 16+x, R10; ADCXQ 16+y, R10; \ - MOVQ 24+x, R11; ADCXQ 24+y, R11; \ - CMOVQCS AX, DX ; \ - XORL AX, AX; \ - ADCXQ DX, R8; \ - ADCXQ AX, R9; MOVQ R9, 8+z; \ - ADCXQ AX, R10; MOVQ R10, 16+z; \ - ADCXQ AX, R11; MOVQ R11, 24+z; \ - MOVL $38, DX; \ - CMOVQCS DX, AX; \ - ADDQ AX, R8; MOVQ R8, 0+z; - -// subtraction subtracts y from x and stores in z -// Uses: AX, DX, R8-R11, FLAGS -// Instr: x86_64, cmov -#define subtraction(z,x,y) \ - MOVL $38, AX; \ - MOVQ 0+x, R8; SUBQ 0+y, R8; \ - MOVQ 8+x, R9; SBBQ 8+y, R9; \ - MOVQ 16+x, R10; SBBQ 16+y, R10; \ - MOVQ 24+x, R11; SBBQ 24+y, R11; \ - MOVL $0, DX; \ - CMOVQCS AX, DX; \ - SUBQ DX, R8; \ - SBBQ $0, R9; MOVQ R9, 8+z; \ - SBBQ $0, R10; MOVQ R10, 16+z; \ - SBBQ $0, R11; MOVQ R11, 24+z; \ - MOVL $0, DX; \ - CMOVQCS AX, DX; \ - SUBQ DX, R8; MOVQ R8, 0+z; - -// integerMulAdx multiplies x and y and stores in z -// Uses: AX, DX, R8-R15, FLAGS -// Instr: x86_64, bmi2, adx -#define integerMulAdx(z,x,y) \ - MOVL $0,R15; \ - MOVQ 0+y, DX; XORL AX, AX; \ - MULXQ 0+x, AX, R8; MOVQ AX, 0+z; \ - MULXQ 8+x, AX, R9; ADCXQ AX, R8; \ - MULXQ 16+x, AX, R10; ADCXQ AX, R9; \ - MULXQ 24+x, AX, R11; ADCXQ AX, R10; \ - MOVL $0, AX;;;;;;;;; ADCXQ AX, R11; \ - MOVQ 8+y, DX; XORL AX, AX; \ - MULXQ 0+x, AX, R12; ADCXQ R8, AX; MOVQ AX, 8+z; \ - MULXQ 8+x, AX, R13; ADCXQ R9, R12; ADOXQ AX, R12; \ - MULXQ 16+x, AX, R14; ADCXQ R10, R13; ADOXQ AX, R13; \ - MULXQ 24+x, AX, R15; ADCXQ R11, R14; ADOXQ AX, R14; \ - MOVL $0, AX;;;;;;;;; ADCXQ AX, R15; ADOXQ AX, R15; \ - MOVQ 16+y, DX; XORL AX, AX; \ - MULXQ 0+x, AX, R8; ADCXQ R12, AX; MOVQ AX, 16+z; \ - MULXQ 8+x, AX, R9; ADCXQ R13, R8; ADOXQ AX, R8; \ - MULXQ 16+x, AX, R10; ADCXQ R14, R9; ADOXQ AX, R9; \ - MULXQ 24+x, AX, R11; ADCXQ R15, R10; ADOXQ AX, R10; \ - MOVL $0, AX;;;;;;;;; ADCXQ AX, R11; ADOXQ AX, R11; \ - MOVQ 24+y, DX; XORL AX, AX; \ - MULXQ 0+x, AX, R12; ADCXQ R8, AX; MOVQ AX, 24+z; \ - MULXQ 8+x, AX, R13; ADCXQ R9, R12; ADOXQ AX, R12; MOVQ R12, 32+z; \ - MULXQ 16+x, AX, R14; ADCXQ R10, R13; ADOXQ AX, R13; MOVQ R13, 40+z; \ - MULXQ 24+x, AX, R15; ADCXQ R11, R14; ADOXQ AX, R14; MOVQ R14, 48+z; \ - MOVL $0, AX;;;;;;;;; ADCXQ AX, R15; ADOXQ AX, R15; MOVQ R15, 56+z; - -// integerMulLeg multiplies x and y and stores in z -// Uses: AX, DX, R8-R15, FLAGS -// Instr: x86_64 -#define integerMulLeg(z,x,y) \ - MOVQ 0+y, R8; \ - MOVQ 0+x, AX; MULQ R8; MOVQ AX, 0+z; MOVQ DX, R15; \ - MOVQ 8+x, AX; MULQ R8; MOVQ AX, R13; MOVQ DX, R10; \ - MOVQ 16+x, AX; MULQ R8; MOVQ AX, R14; MOVQ DX, R11; \ - MOVQ 24+x, AX; MULQ R8; \ - ADDQ R13, R15; \ - ADCQ R14, R10; MOVQ R10, 16+z; \ - ADCQ AX, R11; MOVQ R11, 24+z; \ - ADCQ $0, DX; MOVQ DX, 32+z; \ - MOVQ 8+y, R8; \ - MOVQ 0+x, AX; MULQ R8; MOVQ AX, R12; MOVQ DX, R9; \ - MOVQ 8+x, AX; MULQ R8; MOVQ AX, R13; MOVQ DX, R10; \ - MOVQ 16+x, AX; MULQ R8; MOVQ AX, R14; MOVQ DX, R11; \ - MOVQ 24+x, AX; MULQ R8; \ - ADDQ R12, R15; MOVQ R15, 8+z; \ - ADCQ R13, R9; \ - ADCQ R14, R10; \ - ADCQ AX, R11; \ - ADCQ $0, DX; \ - ADCQ 16+z, R9; MOVQ R9, R15; \ - ADCQ 24+z, R10; MOVQ R10, 24+z; \ - ADCQ 32+z, R11; MOVQ R11, 32+z; \ - ADCQ $0, DX; MOVQ DX, 40+z; \ - MOVQ 16+y, R8; \ - MOVQ 0+x, AX; MULQ R8; MOVQ AX, R12; MOVQ DX, R9; \ - MOVQ 8+x, AX; MULQ R8; MOVQ AX, R13; MOVQ DX, R10; \ - MOVQ 16+x, AX; MULQ R8; MOVQ AX, R14; MOVQ DX, R11; \ - MOVQ 24+x, AX; MULQ R8; \ - ADDQ R12, R15; MOVQ R15, 16+z; \ - ADCQ R13, R9; \ - ADCQ R14, R10; \ - ADCQ AX, R11; \ - ADCQ $0, DX; \ - ADCQ 24+z, R9; MOVQ R9, R15; \ - ADCQ 32+z, R10; MOVQ R10, 32+z; \ - ADCQ 40+z, R11; MOVQ R11, 40+z; \ - ADCQ $0, DX; MOVQ DX, 48+z; \ - MOVQ 24+y, R8; \ - MOVQ 0+x, AX; MULQ R8; MOVQ AX, R12; MOVQ DX, R9; \ - MOVQ 8+x, AX; MULQ R8; MOVQ AX, R13; MOVQ DX, R10; \ - MOVQ 16+x, AX; MULQ R8; MOVQ AX, R14; MOVQ DX, R11; \ - MOVQ 24+x, AX; MULQ R8; \ - ADDQ R12, R15; MOVQ R15, 24+z; \ - ADCQ R13, R9; \ - ADCQ R14, R10; \ - ADCQ AX, R11; \ - ADCQ $0, DX; \ - ADCQ 32+z, R9; MOVQ R9, 32+z; \ - ADCQ 40+z, R10; MOVQ R10, 40+z; \ - ADCQ 48+z, R11; MOVQ R11, 48+z; \ - ADCQ $0, DX; MOVQ DX, 56+z; - -// integerSqrLeg squares x and stores in z -// Uses: AX, CX, DX, R8-R15, FLAGS -// Instr: x86_64 -#define integerSqrLeg(z,x) \ - MOVQ 0+x, R8; \ - MOVQ 8+x, AX; MULQ R8; MOVQ AX, R9; MOVQ DX, R10; /* A[0]*A[1] */ \ - MOVQ 16+x, AX; MULQ R8; MOVQ AX, R14; MOVQ DX, R11; /* A[0]*A[2] */ \ - MOVQ 24+x, AX; MULQ R8; MOVQ AX, R15; MOVQ DX, R12; /* A[0]*A[3] */ \ - MOVQ 24+x, R8; \ - MOVQ 8+x, AX; MULQ R8; MOVQ AX, CX; MOVQ DX, R13; /* A[3]*A[1] */ \ - MOVQ 16+x, AX; MULQ R8; /* A[3]*A[2] */ \ - \ - ADDQ R14, R10;\ - ADCQ R15, R11; MOVL $0, R15;\ - ADCQ CX, R12;\ - ADCQ AX, R13;\ - ADCQ $0, DX; MOVQ DX, R14;\ - MOVQ 8+x, AX; MULQ 16+x;\ - \ - ADDQ AX, R11;\ - ADCQ DX, R12;\ - ADCQ $0, R13;\ - ADCQ $0, R14;\ - ADCQ $0, R15;\ - \ - SHLQ $1, R14, R15; MOVQ R15, 56+z;\ - SHLQ $1, R13, R14; MOVQ R14, 48+z;\ - SHLQ $1, R12, R13; MOVQ R13, 40+z;\ - SHLQ $1, R11, R12; MOVQ R12, 32+z;\ - SHLQ $1, R10, R11; MOVQ R11, 24+z;\ - SHLQ $1, R9, R10; MOVQ R10, 16+z;\ - SHLQ $1, R9; MOVQ R9, 8+z;\ - \ - MOVQ 0+x,AX; MULQ AX; MOVQ AX, 0+z; MOVQ DX, R9;\ - MOVQ 8+x,AX; MULQ AX; MOVQ AX, R10; MOVQ DX, R11;\ - MOVQ 16+x,AX; MULQ AX; MOVQ AX, R12; MOVQ DX, R13;\ - MOVQ 24+x,AX; MULQ AX; MOVQ AX, R14; MOVQ DX, R15;\ - \ - ADDQ 8+z, R9; MOVQ R9, 8+z;\ - ADCQ 16+z, R10; MOVQ R10, 16+z;\ - ADCQ 24+z, R11; MOVQ R11, 24+z;\ - ADCQ 32+z, R12; MOVQ R12, 32+z;\ - ADCQ 40+z, R13; MOVQ R13, 40+z;\ - ADCQ 48+z, R14; MOVQ R14, 48+z;\ - ADCQ 56+z, R15; MOVQ R15, 56+z; - -// integerSqrAdx squares x and stores in z -// Uses: AX, CX, DX, R8-R15, FLAGS -// Instr: x86_64, bmi2, adx -#define integerSqrAdx(z,x) \ - MOVQ 0+x, DX; /* A[0] */ \ - MULXQ 8+x, R8, R14; /* A[1]*A[0] */ XORL R15, R15; \ - MULXQ 16+x, R9, R10; /* A[2]*A[0] */ ADCXQ R14, R9; \ - MULXQ 24+x, AX, CX; /* A[3]*A[0] */ ADCXQ AX, R10; \ - MOVQ 24+x, DX; /* A[3] */ \ - MULXQ 8+x, R11, R12; /* A[1]*A[3] */ ADCXQ CX, R11; \ - MULXQ 16+x, AX, R13; /* A[2]*A[3] */ ADCXQ AX, R12; \ - MOVQ 8+x, DX; /* A[1] */ ADCXQ R15, R13; \ - MULXQ 16+x, AX, CX; /* A[2]*A[1] */ MOVL $0, R14; \ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ADCXQ R15, R14; \ - XORL R15, R15; \ - ADOXQ AX, R10; ADCXQ R8, R8; \ - ADOXQ CX, R11; ADCXQ R9, R9; \ - ADOXQ R15, R12; ADCXQ R10, R10; \ - ADOXQ R15, R13; ADCXQ R11, R11; \ - ADOXQ R15, R14; ADCXQ R12, R12; \ - ;;;;;;;;;;;;;;; ADCXQ R13, R13; \ - ;;;;;;;;;;;;;;; ADCXQ R14, R14; \ - MOVQ 0+x, DX; MULXQ DX, AX, CX; /* A[0]^2 */ \ - ;;;;;;;;;;;;;;; MOVQ AX, 0+z; \ - ADDQ CX, R8; MOVQ R8, 8+z; \ - MOVQ 8+x, DX; MULXQ DX, AX, CX; /* A[1]^2 */ \ - ADCQ AX, R9; MOVQ R9, 16+z; \ - ADCQ CX, R10; MOVQ R10, 24+z; \ - MOVQ 16+x, DX; MULXQ DX, AX, CX; /* A[2]^2 */ \ - ADCQ AX, R11; MOVQ R11, 32+z; \ - ADCQ CX, R12; MOVQ R12, 40+z; \ - MOVQ 24+x, DX; MULXQ DX, AX, CX; /* A[3]^2 */ \ - ADCQ AX, R13; MOVQ R13, 48+z; \ - ADCQ CX, R14; MOVQ R14, 56+z; - -// reduceFromDouble finds z congruent to x modulo p such that 0> 63) - // PUT BIT 255 IN CARRY FLAG AND CLEAR - x3 &^= 1 << 63 - - x0, c0 := bits.Add64(x0, cx, 0) - x1, c1 := bits.Add64(x1, 0, c0) - x2, c2 := bits.Add64(x2, 0, c1) - x3, _ = bits.Add64(x3, 0, c2) - - // TEST FOR BIT 255 AGAIN; ONLY TRIGGERED ON OVERFLOW MODULO 2^255-19 - // cx = C[255] ? 0 : 19 - cx = uint64(19) &^ (-(x3 >> 63)) - // CLEAR BIT 255 - x3 &^= 1 << 63 - - x0, c0 = bits.Sub64(x0, cx, 0) - x1, c1 = bits.Sub64(x1, 0, c0) - x2, c2 = bits.Sub64(x2, 0, c1) - x3, _ = bits.Sub64(x3, 0, c2) - - binary.LittleEndian.PutUint64(x[0*8:1*8], x0) - binary.LittleEndian.PutUint64(x[1*8:2*8], x1) - binary.LittleEndian.PutUint64(x[2*8:3*8], x2) - binary.LittleEndian.PutUint64(x[3*8:4*8], x3) -} - -func red64(z *Elt, x0, x1, x2, x3, x4, x5, x6, x7 uint64) { - h0, l0 := bits.Mul64(x4, 38) - h1, l1 := bits.Mul64(x5, 38) - h2, l2 := bits.Mul64(x6, 38) - h3, l3 := bits.Mul64(x7, 38) - - l1, c0 := bits.Add64(h0, l1, 0) - l2, c1 := bits.Add64(h1, l2, c0) - l3, c2 := bits.Add64(h2, l3, c1) - l4, _ := bits.Add64(h3, 0, c2) - - l0, c0 = bits.Add64(l0, x0, 0) - l1, c1 = bits.Add64(l1, x1, c0) - l2, c2 = bits.Add64(l2, x2, c1) - l3, c3 := bits.Add64(l3, x3, c2) - l4, _ = bits.Add64(l4, 0, c3) - - _, l4 = bits.Mul64(l4, 38) - l0, c0 = bits.Add64(l0, l4, 0) - z1, c1 := bits.Add64(l1, 0, c0) - z2, c2 := bits.Add64(l2, 0, c1) - z3, c3 := bits.Add64(l3, 0, c2) - z0, _ := bits.Add64(l0, (-c3)&38, 0) - - binary.LittleEndian.PutUint64(z[0*8:1*8], z0) - binary.LittleEndian.PutUint64(z[1*8:2*8], z1) - binary.LittleEndian.PutUint64(z[2*8:3*8], z2) - binary.LittleEndian.PutUint64(z[3*8:4*8], z3) -} diff --git a/vendor/github.com/cloudflare/circl/math/fp25519/fp_noasm.go b/vendor/github.com/cloudflare/circl/math/fp25519/fp_noasm.go deleted file mode 100644 index 26ca4d01b7e..00000000000 --- a/vendor/github.com/cloudflare/circl/math/fp25519/fp_noasm.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build !amd64 || purego -// +build !amd64 purego - -package fp25519 - -func cmov(x, y *Elt, n uint) { cmovGeneric(x, y, n) } -func cswap(x, y *Elt, n uint) { cswapGeneric(x, y, n) } -func add(z, x, y *Elt) { addGeneric(z, x, y) } -func sub(z, x, y *Elt) { subGeneric(z, x, y) } -func addsub(x, y *Elt) { addsubGeneric(x, y) } -func mul(z, x, y *Elt) { mulGeneric(z, x, y) } -func sqr(z, x *Elt) { sqrGeneric(z, x) } -func modp(z *Elt) { modpGeneric(z) } diff --git a/vendor/github.com/cloudflare/circl/math/fp448/fp.go b/vendor/github.com/cloudflare/circl/math/fp448/fp.go deleted file mode 100644 index a5e36600bb6..00000000000 --- a/vendor/github.com/cloudflare/circl/math/fp448/fp.go +++ /dev/null @@ -1,164 +0,0 @@ -// Package fp448 provides prime field arithmetic over GF(2^448-2^224-1). -package fp448 - -import ( - "errors" - - "github.com/cloudflare/circl/internal/conv" -) - -// Size in bytes of an element. -const Size = 56 - -// Elt is a prime field element. -type Elt [Size]byte - -func (e Elt) String() string { return conv.BytesLe2Hex(e[:]) } - -// p is the prime modulus 2^448-2^224-1. -var p = Elt{ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -} - -// P returns the prime modulus 2^448-2^224-1. -func P() Elt { return p } - -// ToBytes stores in b the little-endian byte representation of x. -func ToBytes(b []byte, x *Elt) error { - if len(b) != Size { - return errors.New("wrong size") - } - Modp(x) - copy(b, x[:]) - return nil -} - -// IsZero returns true if x is equal to 0. -func IsZero(x *Elt) bool { Modp(x); return *x == Elt{} } - -// IsOne returns true if x is equal to 1. -func IsOne(x *Elt) bool { Modp(x); return *x == Elt{1} } - -// SetOne assigns x=1. -func SetOne(x *Elt) { *x = Elt{1} } - -// One returns the 1 element. -func One() (x Elt) { x = Elt{1}; return } - -// Neg calculates z = -x. -func Neg(z, x *Elt) { Sub(z, &p, x) } - -// Modp ensures that z is between [0,p-1]. -func Modp(z *Elt) { Sub(z, z, &p) } - -// InvSqrt calculates z = sqrt(x/y) iff x/y is a quadratic-residue. If so, -// isQR = true; otherwise, isQR = false, since x/y is a quadratic non-residue, -// and z = sqrt(-x/y). -func InvSqrt(z, x, y *Elt) (isQR bool) { - // First note that x^(2(k+1)) = x^(p-1)/2 * x = legendre(x) * x - // so that's x if x is a quadratic residue and -x otherwise. - // Next, y^(6k+3) = y^(4k+2) * y^(2k+1) = y^(p-1) * y^((p-1)/2) = legendre(y). - // So the z we compute satisfies z^2 y = x^(2(k+1)) y^(6k+3) = legendre(x)*legendre(y). - // Thus if x and y are quadratic residues, then z is indeed sqrt(x/y). - t0, t1 := &Elt{}, &Elt{} - Mul(t0, x, y) // x*y - Sqr(t1, y) // y^2 - Mul(t1, t0, t1) // x*y^3 - powPminus3div4(z, t1) // (x*y^3)^k - Mul(z, z, t0) // z = x*y*(x*y^3)^k = x^(k+1) * y^(3k+1) - - // Check if x/y is a quadratic residue - Sqr(t0, z) // z^2 - Mul(t0, t0, y) // y*z^2 - Sub(t0, t0, x) // y*z^2-x - return IsZero(t0) -} - -// Inv calculates z = 1/x mod p. -func Inv(z, x *Elt) { - // Calculates z = x^(4k+1) = x^(p-3+1) = x^(p-2) = x^-1, where k = (p-3)/4. - t := &Elt{} - powPminus3div4(t, x) // t = x^k - Sqr(t, t) // t = x^2k - Sqr(t, t) // t = x^4k - Mul(z, t, x) // z = x^(4k+1) -} - -// powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4. -func powPminus3div4(z, x *Elt) { - x0, x1 := &Elt{}, &Elt{} - Sqr(z, x) - Mul(z, z, x) - Sqr(x0, z) - Mul(x0, x0, x) - Sqr(z, x0) - Sqr(z, z) - Sqr(z, z) - Mul(z, z, x0) - Sqr(x1, z) - for i := 0; i < 5; i++ { - Sqr(x1, x1) - } - Mul(x1, x1, z) - Sqr(z, x1) - for i := 0; i < 11; i++ { - Sqr(z, z) - } - Mul(z, z, x1) - Sqr(z, z) - Sqr(z, z) - Sqr(z, z) - Mul(z, z, x0) - Sqr(x1, z) - for i := 0; i < 26; i++ { - Sqr(x1, x1) - } - Mul(x1, x1, z) - Sqr(z, x1) - for i := 0; i < 53; i++ { - Sqr(z, z) - } - Mul(z, z, x1) - Sqr(z, z) - Sqr(z, z) - Sqr(z, z) - Mul(z, z, x0) - Sqr(x1, z) - for i := 0; i < 110; i++ { - Sqr(x1, x1) - } - Mul(x1, x1, z) - Sqr(z, x1) - Mul(z, z, x) - for i := 0; i < 223; i++ { - Sqr(z, z) - } - Mul(z, z, x1) -} - -// Cmov assigns y to x if n is 1. -func Cmov(x, y *Elt, n uint) { cmov(x, y, n) } - -// Cswap interchanges x and y if n is 1. -func Cswap(x, y *Elt, n uint) { cswap(x, y, n) } - -// Add calculates z = x+y mod p. -func Add(z, x, y *Elt) { add(z, x, y) } - -// Sub calculates z = x-y mod p. -func Sub(z, x, y *Elt) { sub(z, x, y) } - -// AddSub calculates (x,y) = (x+y mod p, x-y mod p). -func AddSub(x, y *Elt) { addsub(x, y) } - -// Mul calculates z = x*y mod p. -func Mul(z, x, y *Elt) { mul(z, x, y) } - -// Sqr calculates z = x^2 mod p. -func Sqr(z, x *Elt) { sqr(z, x) } diff --git a/vendor/github.com/cloudflare/circl/math/fp448/fp_amd64.go b/vendor/github.com/cloudflare/circl/math/fp448/fp_amd64.go deleted file mode 100644 index 6a12209a704..00000000000 --- a/vendor/github.com/cloudflare/circl/math/fp448/fp_amd64.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build amd64 && !purego -// +build amd64,!purego - -package fp448 - -import ( - "golang.org/x/sys/cpu" -) - -var hasBmi2Adx = cpu.X86.HasBMI2 && cpu.X86.HasADX - -var _ = hasBmi2Adx - -func cmov(x, y *Elt, n uint) { cmovAmd64(x, y, n) } -func cswap(x, y *Elt, n uint) { cswapAmd64(x, y, n) } -func add(z, x, y *Elt) { addAmd64(z, x, y) } -func sub(z, x, y *Elt) { subAmd64(z, x, y) } -func addsub(x, y *Elt) { addsubAmd64(x, y) } -func mul(z, x, y *Elt) { mulAmd64(z, x, y) } -func sqr(z, x *Elt) { sqrAmd64(z, x) } - -/* Functions defined in fp_amd64.s */ - -//go:noescape -func cmovAmd64(x, y *Elt, n uint) - -//go:noescape -func cswapAmd64(x, y *Elt, n uint) - -//go:noescape -func addAmd64(z, x, y *Elt) - -//go:noescape -func subAmd64(z, x, y *Elt) - -//go:noescape -func addsubAmd64(x, y *Elt) - -//go:noescape -func mulAmd64(z, x, y *Elt) - -//go:noescape -func sqrAmd64(z, x *Elt) diff --git a/vendor/github.com/cloudflare/circl/math/fp448/fp_amd64.h b/vendor/github.com/cloudflare/circl/math/fp448/fp_amd64.h deleted file mode 100644 index 536fe5bdfe0..00000000000 --- a/vendor/github.com/cloudflare/circl/math/fp448/fp_amd64.h +++ /dev/null @@ -1,591 +0,0 @@ -// This code was imported from https://github.com/armfazh/rfc7748_precomputed - -// CHECK_BMI2ADX triggers bmi2adx if supported, -// otherwise it fallbacks to legacy code. -#define CHECK_BMI2ADX(label, legacy, bmi2adx) \ - CMPB ·hasBmi2Adx(SB), $0 \ - JE label \ - bmi2adx \ - RET \ - label: \ - legacy \ - RET - -// cselect is a conditional move -// if b=1: it copies y into x; -// if b=0: x remains with the same value; -// if b<> 0,1: undefined. -// Uses: AX, DX, FLAGS -// Instr: x86_64, cmov -#define cselect(x,y,b) \ - TESTQ b, b \ - MOVQ 0+x, AX; MOVQ 0+y, DX; CMOVQNE DX, AX; MOVQ AX, 0+x; \ - MOVQ 8+x, AX; MOVQ 8+y, DX; CMOVQNE DX, AX; MOVQ AX, 8+x; \ - MOVQ 16+x, AX; MOVQ 16+y, DX; CMOVQNE DX, AX; MOVQ AX, 16+x; \ - MOVQ 24+x, AX; MOVQ 24+y, DX; CMOVQNE DX, AX; MOVQ AX, 24+x; \ - MOVQ 32+x, AX; MOVQ 32+y, DX; CMOVQNE DX, AX; MOVQ AX, 32+x; \ - MOVQ 40+x, AX; MOVQ 40+y, DX; CMOVQNE DX, AX; MOVQ AX, 40+x; \ - MOVQ 48+x, AX; MOVQ 48+y, DX; CMOVQNE DX, AX; MOVQ AX, 48+x; - -// cswap is a conditional swap -// if b=1: x,y <- y,x; -// if b=0: x,y remain with the same values; -// if b<> 0,1: undefined. -// Uses: AX, DX, R8, FLAGS -// Instr: x86_64, cmov -#define cswap(x,y,b) \ - TESTQ b, b \ - MOVQ 0+x, AX; MOVQ AX, R8; MOVQ 0+y, DX; CMOVQNE DX, AX; CMOVQNE R8, DX; MOVQ AX, 0+x; MOVQ DX, 0+y; \ - MOVQ 8+x, AX; MOVQ AX, R8; MOVQ 8+y, DX; CMOVQNE DX, AX; CMOVQNE R8, DX; MOVQ AX, 8+x; MOVQ DX, 8+y; \ - MOVQ 16+x, AX; MOVQ AX, R8; MOVQ 16+y, DX; CMOVQNE DX, AX; CMOVQNE R8, DX; MOVQ AX, 16+x; MOVQ DX, 16+y; \ - MOVQ 24+x, AX; MOVQ AX, R8; MOVQ 24+y, DX; CMOVQNE DX, AX; CMOVQNE R8, DX; MOVQ AX, 24+x; MOVQ DX, 24+y; \ - MOVQ 32+x, AX; MOVQ AX, R8; MOVQ 32+y, DX; CMOVQNE DX, AX; CMOVQNE R8, DX; MOVQ AX, 32+x; MOVQ DX, 32+y; \ - MOVQ 40+x, AX; MOVQ AX, R8; MOVQ 40+y, DX; CMOVQNE DX, AX; CMOVQNE R8, DX; MOVQ AX, 40+x; MOVQ DX, 40+y; \ - MOVQ 48+x, AX; MOVQ AX, R8; MOVQ 48+y, DX; CMOVQNE DX, AX; CMOVQNE R8, DX; MOVQ AX, 48+x; MOVQ DX, 48+y; - -// additionLeg adds x and y and stores in z -// Uses: AX, DX, R8-R14, FLAGS -// Instr: x86_64 -#define additionLeg(z,x,y) \ - MOVQ 0+x, R8; ADDQ 0+y, R8; \ - MOVQ 8+x, R9; ADCQ 8+y, R9; \ - MOVQ 16+x, R10; ADCQ 16+y, R10; \ - MOVQ 24+x, R11; ADCQ 24+y, R11; \ - MOVQ 32+x, R12; ADCQ 32+y, R12; \ - MOVQ 40+x, R13; ADCQ 40+y, R13; \ - MOVQ 48+x, R14; ADCQ 48+y, R14; \ - MOVQ $0, AX; ADCQ $0, AX; \ - MOVQ AX, DX; \ - SHLQ $32, DX; \ - ADDQ AX, R8; MOVQ $0, AX; \ - ADCQ $0, R9; \ - ADCQ $0, R10; \ - ADCQ DX, R11; \ - ADCQ $0, R12; \ - ADCQ $0, R13; \ - ADCQ $0, R14; \ - ADCQ $0, AX; \ - MOVQ AX, DX; \ - SHLQ $32, DX; \ - ADDQ AX, R8; MOVQ R8, 0+z; \ - ADCQ $0, R9; MOVQ R9, 8+z; \ - ADCQ $0, R10; MOVQ R10, 16+z; \ - ADCQ DX, R11; MOVQ R11, 24+z; \ - ADCQ $0, R12; MOVQ R12, 32+z; \ - ADCQ $0, R13; MOVQ R13, 40+z; \ - ADCQ $0, R14; MOVQ R14, 48+z; - - -// additionAdx adds x and y and stores in z -// Uses: AX, DX, R8-R15, FLAGS -// Instr: x86_64, adx -#define additionAdx(z,x,y) \ - MOVL $32, R15; \ - XORL DX, DX; \ - MOVQ 0+x, R8; ADCXQ 0+y, R8; \ - MOVQ 8+x, R9; ADCXQ 8+y, R9; \ - MOVQ 16+x, R10; ADCXQ 16+y, R10; \ - MOVQ 24+x, R11; ADCXQ 24+y, R11; \ - MOVQ 32+x, R12; ADCXQ 32+y, R12; \ - MOVQ 40+x, R13; ADCXQ 40+y, R13; \ - MOVQ 48+x, R14; ADCXQ 48+y, R14; \ - ;;;;;;;;;;;;;;; ADCXQ DX, DX; \ - XORL AX, AX; \ - ADCXQ DX, R8; SHLXQ R15, DX, DX; \ - ADCXQ AX, R9; \ - ADCXQ AX, R10; \ - ADCXQ DX, R11; \ - ADCXQ AX, R12; \ - ADCXQ AX, R13; \ - ADCXQ AX, R14; \ - ADCXQ AX, AX; \ - XORL DX, DX; \ - ADCXQ AX, R8; MOVQ R8, 0+z; SHLXQ R15, AX, AX; \ - ADCXQ DX, R9; MOVQ R9, 8+z; \ - ADCXQ DX, R10; MOVQ R10, 16+z; \ - ADCXQ AX, R11; MOVQ R11, 24+z; \ - ADCXQ DX, R12; MOVQ R12, 32+z; \ - ADCXQ DX, R13; MOVQ R13, 40+z; \ - ADCXQ DX, R14; MOVQ R14, 48+z; - -// subtraction subtracts y from x and stores in z -// Uses: AX, DX, R8-R14, FLAGS -// Instr: x86_64 -#define subtraction(z,x,y) \ - MOVQ 0+x, R8; SUBQ 0+y, R8; \ - MOVQ 8+x, R9; SBBQ 8+y, R9; \ - MOVQ 16+x, R10; SBBQ 16+y, R10; \ - MOVQ 24+x, R11; SBBQ 24+y, R11; \ - MOVQ 32+x, R12; SBBQ 32+y, R12; \ - MOVQ 40+x, R13; SBBQ 40+y, R13; \ - MOVQ 48+x, R14; SBBQ 48+y, R14; \ - MOVQ $0, AX; SETCS AX; \ - MOVQ AX, DX; \ - SHLQ $32, DX; \ - SUBQ AX, R8; MOVQ $0, AX; \ - SBBQ $0, R9; \ - SBBQ $0, R10; \ - SBBQ DX, R11; \ - SBBQ $0, R12; \ - SBBQ $0, R13; \ - SBBQ $0, R14; \ - SETCS AX; \ - MOVQ AX, DX; \ - SHLQ $32, DX; \ - SUBQ AX, R8; MOVQ R8, 0+z; \ - SBBQ $0, R9; MOVQ R9, 8+z; \ - SBBQ $0, R10; MOVQ R10, 16+z; \ - SBBQ DX, R11; MOVQ R11, 24+z; \ - SBBQ $0, R12; MOVQ R12, 32+z; \ - SBBQ $0, R13; MOVQ R13, 40+z; \ - SBBQ $0, R14; MOVQ R14, 48+z; - -// maddBmi2Adx multiplies x and y and accumulates in z -// Uses: AX, DX, R15, FLAGS -// Instr: x86_64, bmi2, adx -#define maddBmi2Adx(z,x,y,i,r0,r1,r2,r3,r4,r5,r6) \ - MOVQ i+y, DX; XORL AX, AX; \ - MULXQ 0+x, AX, R8; ADOXQ AX, r0; ADCXQ R8, r1; MOVQ r0,i+z; \ - MULXQ 8+x, AX, r0; ADOXQ AX, r1; ADCXQ r0, r2; MOVQ $0, R8; \ - MULXQ 16+x, AX, r0; ADOXQ AX, r2; ADCXQ r0, r3; \ - MULXQ 24+x, AX, r0; ADOXQ AX, r3; ADCXQ r0, r4; \ - MULXQ 32+x, AX, r0; ADOXQ AX, r4; ADCXQ r0, r5; \ - MULXQ 40+x, AX, r0; ADOXQ AX, r5; ADCXQ r0, r6; \ - MULXQ 48+x, AX, r0; ADOXQ AX, r6; ADCXQ R8, r0; \ - ;;;;;;;;;;;;;;;;;;; ADOXQ R8, r0; - -// integerMulAdx multiplies x and y and stores in z -// Uses: AX, DX, R8-R15, FLAGS -// Instr: x86_64, bmi2, adx -#define integerMulAdx(z,x,y) \ - MOVL $0,R15; \ - MOVQ 0+y, DX; XORL AX, AX; MOVQ $0, R8; \ - MULXQ 0+x, AX, R9; MOVQ AX, 0+z; \ - MULXQ 8+x, AX, R10; ADCXQ AX, R9; \ - MULXQ 16+x, AX, R11; ADCXQ AX, R10; \ - MULXQ 24+x, AX, R12; ADCXQ AX, R11; \ - MULXQ 32+x, AX, R13; ADCXQ AX, R12; \ - MULXQ 40+x, AX, R14; ADCXQ AX, R13; \ - MULXQ 48+x, AX, R15; ADCXQ AX, R14; \ - ;;;;;;;;;;;;;;;;;;;; ADCXQ R8, R15; \ - maddBmi2Adx(z,x,y, 8, R9,R10,R11,R12,R13,R14,R15) \ - maddBmi2Adx(z,x,y,16,R10,R11,R12,R13,R14,R15, R9) \ - maddBmi2Adx(z,x,y,24,R11,R12,R13,R14,R15, R9,R10) \ - maddBmi2Adx(z,x,y,32,R12,R13,R14,R15, R9,R10,R11) \ - maddBmi2Adx(z,x,y,40,R13,R14,R15, R9,R10,R11,R12) \ - maddBmi2Adx(z,x,y,48,R14,R15, R9,R10,R11,R12,R13) \ - MOVQ R15, 56+z; \ - MOVQ R9, 64+z; \ - MOVQ R10, 72+z; \ - MOVQ R11, 80+z; \ - MOVQ R12, 88+z; \ - MOVQ R13, 96+z; \ - MOVQ R14, 104+z; - -// maddLegacy multiplies x and y and accumulates in z -// Uses: AX, DX, R15, FLAGS -// Instr: x86_64 -#define maddLegacy(z,x,y,i) \ - MOVQ i+y, R15; \ - MOVQ 0+x, AX; MULQ R15; MOVQ AX, R8; ;;;;;;;;;;;; MOVQ DX, R9; \ - MOVQ 8+x, AX; MULQ R15; ADDQ AX, R9; ADCQ $0, DX; MOVQ DX, R10; \ - MOVQ 16+x, AX; MULQ R15; ADDQ AX, R10; ADCQ $0, DX; MOVQ DX, R11; \ - MOVQ 24+x, AX; MULQ R15; ADDQ AX, R11; ADCQ $0, DX; MOVQ DX, R12; \ - MOVQ 32+x, AX; MULQ R15; ADDQ AX, R12; ADCQ $0, DX; MOVQ DX, R13; \ - MOVQ 40+x, AX; MULQ R15; ADDQ AX, R13; ADCQ $0, DX; MOVQ DX, R14; \ - MOVQ 48+x, AX; MULQ R15; ADDQ AX, R14; ADCQ $0, DX; \ - ADDQ 0+i+z, R8; MOVQ R8, 0+i+z; \ - ADCQ 8+i+z, R9; MOVQ R9, 8+i+z; \ - ADCQ 16+i+z, R10; MOVQ R10, 16+i+z; \ - ADCQ 24+i+z, R11; MOVQ R11, 24+i+z; \ - ADCQ 32+i+z, R12; MOVQ R12, 32+i+z; \ - ADCQ 40+i+z, R13; MOVQ R13, 40+i+z; \ - ADCQ 48+i+z, R14; MOVQ R14, 48+i+z; \ - ADCQ $0, DX; MOVQ DX, 56+i+z; - -// integerMulLeg multiplies x and y and stores in z -// Uses: AX, DX, R8-R15, FLAGS -// Instr: x86_64 -#define integerMulLeg(z,x,y) \ - MOVQ 0+y, R15; \ - MOVQ 0+x, AX; MULQ R15; MOVQ AX, 0+z; ;;;;;;;;;;;; MOVQ DX, R8; \ - MOVQ 8+x, AX; MULQ R15; ADDQ AX, R8; ADCQ $0, DX; MOVQ DX, R9; MOVQ R8, 8+z; \ - MOVQ 16+x, AX; MULQ R15; ADDQ AX, R9; ADCQ $0, DX; MOVQ DX, R10; MOVQ R9, 16+z; \ - MOVQ 24+x, AX; MULQ R15; ADDQ AX, R10; ADCQ $0, DX; MOVQ DX, R11; MOVQ R10, 24+z; \ - MOVQ 32+x, AX; MULQ R15; ADDQ AX, R11; ADCQ $0, DX; MOVQ DX, R12; MOVQ R11, 32+z; \ - MOVQ 40+x, AX; MULQ R15; ADDQ AX, R12; ADCQ $0, DX; MOVQ DX, R13; MOVQ R12, 40+z; \ - MOVQ 48+x, AX; MULQ R15; ADDQ AX, R13; ADCQ $0, DX; MOVQ DX,56+z; MOVQ R13, 48+z; \ - maddLegacy(z,x,y, 8) \ - maddLegacy(z,x,y,16) \ - maddLegacy(z,x,y,24) \ - maddLegacy(z,x,y,32) \ - maddLegacy(z,x,y,40) \ - maddLegacy(z,x,y,48) - -// integerSqrLeg squares x and stores in z -// Uses: AX, CX, DX, R8-R15, FLAGS -// Instr: x86_64 -#define integerSqrLeg(z,x) \ - XORL R15, R15; \ - MOVQ 0+x, CX; \ - MOVQ CX, AX; MULQ CX; MOVQ AX, 0+z; MOVQ DX, R8; \ - ADDQ CX, CX; ADCQ $0, R15; \ - MOVQ 8+x, AX; MULQ CX; ADDQ AX, R8; ADCQ $0, DX; MOVQ DX, R9; MOVQ R8, 8+z; \ - MOVQ 16+x, AX; MULQ CX; ADDQ AX, R9; ADCQ $0, DX; MOVQ DX, R10; \ - MOVQ 24+x, AX; MULQ CX; ADDQ AX, R10; ADCQ $0, DX; MOVQ DX, R11; \ - MOVQ 32+x, AX; MULQ CX; ADDQ AX, R11; ADCQ $0, DX; MOVQ DX, R12; \ - MOVQ 40+x, AX; MULQ CX; ADDQ AX, R12; ADCQ $0, DX; MOVQ DX, R13; \ - MOVQ 48+x, AX; MULQ CX; ADDQ AX, R13; ADCQ $0, DX; MOVQ DX, R14; \ - \ - MOVQ 8+x, CX; \ - MOVQ CX, AX; ADDQ R15, CX; MOVQ $0, R15; ADCQ $0, R15; \ - ;;;;;;;;;;;;;; MULQ CX; ADDQ AX, R9; ADCQ $0, DX; MOVQ R9,16+z; \ - MOVQ R15, AX; NEGQ AX; ANDQ 8+x, AX; ADDQ AX, DX; ADCQ $0, R11; MOVQ DX, R8; \ - ADDQ 8+x, CX; ADCQ $0, R15; \ - MOVQ 16+x, AX; MULQ CX; ADDQ AX, R10; ADCQ $0, DX; ADDQ R8, R10; ADCQ $0, DX; MOVQ DX, R8; MOVQ R10, 24+z; \ - MOVQ 24+x, AX; MULQ CX; ADDQ AX, R11; ADCQ $0, DX; ADDQ R8, R11; ADCQ $0, DX; MOVQ DX, R8; \ - MOVQ 32+x, AX; MULQ CX; ADDQ AX, R12; ADCQ $0, DX; ADDQ R8, R12; ADCQ $0, DX; MOVQ DX, R8; \ - MOVQ 40+x, AX; MULQ CX; ADDQ AX, R13; ADCQ $0, DX; ADDQ R8, R13; ADCQ $0, DX; MOVQ DX, R8; \ - MOVQ 48+x, AX; MULQ CX; ADDQ AX, R14; ADCQ $0, DX; ADDQ R8, R14; ADCQ $0, DX; MOVQ DX, R9; \ - \ - MOVQ 16+x, CX; \ - MOVQ CX, AX; ADDQ R15, CX; MOVQ $0, R15; ADCQ $0, R15; \ - ;;;;;;;;;;;;;; MULQ CX; ADDQ AX, R11; ADCQ $0, DX; MOVQ R11, 32+z; \ - MOVQ R15, AX; NEGQ AX; ANDQ 16+x,AX; ADDQ AX, DX; ADCQ $0, R13; MOVQ DX, R8; \ - ADDQ 16+x, CX; ADCQ $0, R15; \ - MOVQ 24+x, AX; MULQ CX; ADDQ AX, R12; ADCQ $0, DX; ADDQ R8, R12; ADCQ $0, DX; MOVQ DX, R8; MOVQ R12, 40+z; \ - MOVQ 32+x, AX; MULQ CX; ADDQ AX, R13; ADCQ $0, DX; ADDQ R8, R13; ADCQ $0, DX; MOVQ DX, R8; \ - MOVQ 40+x, AX; MULQ CX; ADDQ AX, R14; ADCQ $0, DX; ADDQ R8, R14; ADCQ $0, DX; MOVQ DX, R8; \ - MOVQ 48+x, AX; MULQ CX; ADDQ AX, R9; ADCQ $0, DX; ADDQ R8, R9; ADCQ $0, DX; MOVQ DX,R10; \ - \ - MOVQ 24+x, CX; \ - MOVQ CX, AX; ADDQ R15, CX; MOVQ $0, R15; ADCQ $0, R15; \ - ;;;;;;;;;;;;;; MULQ CX; ADDQ AX, R13; ADCQ $0, DX; MOVQ R13, 48+z; \ - MOVQ R15, AX; NEGQ AX; ANDQ 24+x,AX; ADDQ AX, DX; ADCQ $0, R9; MOVQ DX, R8; \ - ADDQ 24+x, CX; ADCQ $0, R15; \ - MOVQ 32+x, AX; MULQ CX; ADDQ AX, R14; ADCQ $0, DX; ADDQ R8, R14; ADCQ $0, DX; MOVQ DX, R8; MOVQ R14, 56+z; \ - MOVQ 40+x, AX; MULQ CX; ADDQ AX, R9; ADCQ $0, DX; ADDQ R8, R9; ADCQ $0, DX; MOVQ DX, R8; \ - MOVQ 48+x, AX; MULQ CX; ADDQ AX, R10; ADCQ $0, DX; ADDQ R8, R10; ADCQ $0, DX; MOVQ DX,R11; \ - \ - MOVQ 32+x, CX; \ - MOVQ CX, AX; ADDQ R15, CX; MOVQ $0, R15; ADCQ $0, R15; \ - ;;;;;;;;;;;;;; MULQ CX; ADDQ AX, R9; ADCQ $0, DX; MOVQ R9, 64+z; \ - MOVQ R15, AX; NEGQ AX; ANDQ 32+x,AX; ADDQ AX, DX; ADCQ $0, R11; MOVQ DX, R8; \ - ADDQ 32+x, CX; ADCQ $0, R15; \ - MOVQ 40+x, AX; MULQ CX; ADDQ AX, R10; ADCQ $0, DX; ADDQ R8, R10; ADCQ $0, DX; MOVQ DX, R8; MOVQ R10, 72+z; \ - MOVQ 48+x, AX; MULQ CX; ADDQ AX, R11; ADCQ $0, DX; ADDQ R8, R11; ADCQ $0, DX; MOVQ DX,R12; \ - \ - XORL R13, R13; \ - XORL R14, R14; \ - MOVQ 40+x, CX; \ - MOVQ CX, AX; ADDQ R15, CX; MOVQ $0, R15; ADCQ $0, R15; \ - ;;;;;;;;;;;;;; MULQ CX; ADDQ AX, R11; ADCQ $0, DX; MOVQ R11, 80+z; \ - MOVQ R15, AX; NEGQ AX; ANDQ 40+x,AX; ADDQ AX, DX; ADCQ $0, R13; MOVQ DX, R8; \ - ADDQ 40+x, CX; ADCQ $0, R15; \ - MOVQ 48+x, AX; MULQ CX; ADDQ AX, R12; ADCQ $0, DX; ADDQ R8, R12; ADCQ $0, DX; MOVQ DX, R8; MOVQ R12, 88+z; \ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ADDQ R8, R13; ADCQ $0,R14; \ - \ - XORL R9, R9; \ - MOVQ 48+x, CX; \ - MOVQ CX, AX; ADDQ R15, CX; MOVQ $0, R15; ADCQ $0, R15; \ - ;;;;;;;;;;;;;; MULQ CX; ADDQ AX, R13; ADCQ $0, DX; MOVQ R13, 96+z; \ - MOVQ R15, AX; NEGQ AX; ANDQ 48+x,AX; ADDQ AX, DX; ADCQ $0, R9; MOVQ DX, R8; \ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ADDQ R8,R14; ADCQ $0, R9; MOVQ R14, 104+z; - - -// integerSqrAdx squares x and stores in z -// Uses: AX, CX, DX, R8-R15, FLAGS -// Instr: x86_64, bmi2, adx -#define integerSqrAdx(z,x) \ - XORL R15, R15; \ - MOVQ 0+x, DX; \ - ;;;;;;;;;;;;;; MULXQ DX, AX, R8; MOVQ AX, 0+z; \ - ADDQ DX, DX; ADCQ $0, R15; CLC; \ - MULXQ 8+x, AX, R9; ADCXQ AX, R8; MOVQ R8, 8+z; \ - MULXQ 16+x, AX, R10; ADCXQ AX, R9; MOVQ $0, R8;\ - MULXQ 24+x, AX, R11; ADCXQ AX, R10; \ - MULXQ 32+x, AX, R12; ADCXQ AX, R11; \ - MULXQ 40+x, AX, R13; ADCXQ AX, R12; \ - MULXQ 48+x, AX, R14; ADCXQ AX, R13; \ - ;;;;;;;;;;;;;;;;;;;; ADCXQ R8, R14; \ - \ - MOVQ 8+x, DX; \ - MOVQ DX, AX; ADDQ R15, DX; MOVQ $0, R15; ADCQ $0, R15; \ - MULXQ AX, AX, CX; \ - MOVQ R15, R8; NEGQ R8; ANDQ 8+x, R8; \ - ADDQ AX, R9; MOVQ R9, 16+z; \ - ADCQ CX, R8; \ - ADCQ $0, R11; \ - ADDQ 8+x, DX; \ - ADCQ $0, R15; \ - XORL R9, R9; ;;;;;;;;;;;;;;;;;;;;; ADOXQ R8, R10; \ - MULXQ 16+x, AX, CX; ADCXQ AX, R10; ADOXQ CX, R11; MOVQ R10, 24+z; \ - MULXQ 24+x, AX, CX; ADCXQ AX, R11; ADOXQ CX, R12; MOVQ $0, R10; \ - MULXQ 32+x, AX, CX; ADCXQ AX, R12; ADOXQ CX, R13; \ - MULXQ 40+x, AX, CX; ADCXQ AX, R13; ADOXQ CX, R14; \ - MULXQ 48+x, AX, CX; ADCXQ AX, R14; ADOXQ CX, R9; \ - ;;;;;;;;;;;;;;;;;;; ADCXQ R10, R9; \ - \ - MOVQ 16+x, DX; \ - MOVQ DX, AX; ADDQ R15, DX; MOVQ $0, R15; ADCQ $0, R15; \ - MULXQ AX, AX, CX; \ - MOVQ R15, R8; NEGQ R8; ANDQ 16+x, R8; \ - ADDQ AX, R11; MOVQ R11, 32+z; \ - ADCQ CX, R8; \ - ADCQ $0, R13; \ - ADDQ 16+x, DX; \ - ADCQ $0, R15; \ - XORL R11, R11; ;;;;;;;;;;;;;;;;;;; ADOXQ R8, R12; \ - MULXQ 24+x, AX, CX; ADCXQ AX, R12; ADOXQ CX, R13; MOVQ R12, 40+z; \ - MULXQ 32+x, AX, CX; ADCXQ AX, R13; ADOXQ CX, R14; MOVQ $0, R12; \ - MULXQ 40+x, AX, CX; ADCXQ AX, R14; ADOXQ CX, R9; \ - MULXQ 48+x, AX, CX; ADCXQ AX, R9; ADOXQ CX, R10; \ - ;;;;;;;;;;;;;;;;;;; ADCXQ R11,R10; \ - \ - MOVQ 24+x, DX; \ - MOVQ DX, AX; ADDQ R15, DX; MOVQ $0, R15; ADCQ $0, R15; \ - MULXQ AX, AX, CX; \ - MOVQ R15, R8; NEGQ R8; ANDQ 24+x, R8; \ - ADDQ AX, R13; MOVQ R13, 48+z; \ - ADCQ CX, R8; \ - ADCQ $0, R9; \ - ADDQ 24+x, DX; \ - ADCQ $0, R15; \ - XORL R13, R13; ;;;;;;;;;;;;;;;;;;; ADOXQ R8, R14; \ - MULXQ 32+x, AX, CX; ADCXQ AX, R14; ADOXQ CX, R9; MOVQ R14, 56+z; \ - MULXQ 40+x, AX, CX; ADCXQ AX, R9; ADOXQ CX, R10; MOVQ $0, R14; \ - MULXQ 48+x, AX, CX; ADCXQ AX, R10; ADOXQ CX, R11; \ - ;;;;;;;;;;;;;;;;;;; ADCXQ R12,R11; \ - \ - MOVQ 32+x, DX; \ - MOVQ DX, AX; ADDQ R15, DX; MOVQ $0, R15; ADCQ $0, R15; \ - MULXQ AX, AX, CX; \ - MOVQ R15, R8; NEGQ R8; ANDQ 32+x, R8; \ - ADDQ AX, R9; MOVQ R9, 64+z; \ - ADCQ CX, R8; \ - ADCQ $0, R11; \ - ADDQ 32+x, DX; \ - ADCQ $0, R15; \ - XORL R9, R9; ;;;;;;;;;;;;;;;;;;;;; ADOXQ R8, R10; \ - MULXQ 40+x, AX, CX; ADCXQ AX, R10; ADOXQ CX, R11; MOVQ R10, 72+z; \ - MULXQ 48+x, AX, CX; ADCXQ AX, R11; ADOXQ CX, R12; \ - ;;;;;;;;;;;;;;;;;;; ADCXQ R13,R12; \ - \ - MOVQ 40+x, DX; \ - MOVQ DX, AX; ADDQ R15, DX; MOVQ $0, R15; ADCQ $0, R15; \ - MULXQ AX, AX, CX; \ - MOVQ R15, R8; NEGQ R8; ANDQ 40+x, R8; \ - ADDQ AX, R11; MOVQ R11, 80+z; \ - ADCQ CX, R8; \ - ADCQ $0, R13; \ - ADDQ 40+x, DX; \ - ADCQ $0, R15; \ - XORL R11, R11; ;;;;;;;;;;;;;;;;;;; ADOXQ R8, R12; \ - MULXQ 48+x, AX, CX; ADCXQ AX, R12; ADOXQ CX, R13; MOVQ R12, 88+z; \ - ;;;;;;;;;;;;;;;;;;; ADCXQ R14,R13; \ - \ - MOVQ 48+x, DX; \ - MOVQ DX, AX; ADDQ R15, DX; MOVQ $0, R15; ADCQ $0, R15; \ - MULXQ AX, AX, CX; \ - MOVQ R15, R8; NEGQ R8; ANDQ 48+x, R8; \ - XORL R10, R10; ;;;;;;;;;;;;;; ADOXQ CX, R14; \ - ;;;;;;;;;;;;;; ADCXQ AX, R13; ;;;;;;;;;;;;;; MOVQ R13, 96+z; \ - ;;;;;;;;;;;;;; ADCXQ R8, R14; MOVQ R14, 104+z; - -// reduceFromDoubleLeg finds a z=x modulo p such that z<2^448 and stores in z -// Uses: AX, R8-R15, FLAGS -// Instr: x86_64 -#define reduceFromDoubleLeg(z,x) \ - /* ( ,2C13,2C12,2C11,2C10|C10,C9,C8, C7) + (C6,...,C0) */ \ - /* (r14, r13, r12, r11, r10,r9,r8,r15) */ \ - MOVQ 80+x,AX; MOVQ AX,R10; \ - MOVQ $0xFFFFFFFF00000000, R8; \ - ANDQ R8,R10; \ - \ - MOVQ $0,R14; \ - MOVQ 104+x,R13; SHLQ $1,R13,R14; \ - MOVQ 96+x,R12; SHLQ $1,R12,R13; \ - MOVQ 88+x,R11; SHLQ $1,R11,R12; \ - MOVQ 72+x, R9; SHLQ $1,R10,R11; \ - MOVQ 64+x, R8; SHLQ $1,R10; \ - MOVQ $0xFFFFFFFF,R15; ANDQ R15,AX; ORQ AX,R10; \ - MOVQ 56+x,R15; \ - \ - ADDQ 0+x,R15; MOVQ R15, 0+z; MOVQ 56+x,R15; \ - ADCQ 8+x, R8; MOVQ R8, 8+z; MOVQ 64+x, R8; \ - ADCQ 16+x, R9; MOVQ R9,16+z; MOVQ 72+x, R9; \ - ADCQ 24+x,R10; MOVQ R10,24+z; MOVQ 80+x,R10; \ - ADCQ 32+x,R11; MOVQ R11,32+z; MOVQ 88+x,R11; \ - ADCQ 40+x,R12; MOVQ R12,40+z; MOVQ 96+x,R12; \ - ADCQ 48+x,R13; MOVQ R13,48+z; MOVQ 104+x,R13; \ - ADCQ $0,R14; \ - /* (c10c9,c9c8,c8c7,c7c13,c13c12,c12c11,c11c10) + (c6,...,c0) */ \ - /* ( r9, r8, r15, r13, r12, r11, r10) */ \ - MOVQ R10, AX; \ - SHRQ $32,R11,R10; \ - SHRQ $32,R12,R11; \ - SHRQ $32,R13,R12; \ - SHRQ $32,R15,R13; \ - SHRQ $32, R8,R15; \ - SHRQ $32, R9, R8; \ - SHRQ $32, AX, R9; \ - \ - ADDQ 0+z,R10; \ - ADCQ 8+z,R11; \ - ADCQ 16+z,R12; \ - ADCQ 24+z,R13; \ - ADCQ 32+z,R15; \ - ADCQ 40+z, R8; \ - ADCQ 48+z, R9; \ - ADCQ $0,R14; \ - /* ( c7) + (c6,...,c0) */ \ - /* (r14) */ \ - MOVQ R14, AX; SHLQ $32, AX; \ - ADDQ R14,R10; MOVQ $0,R14; \ - ADCQ $0,R11; \ - ADCQ $0,R12; \ - ADCQ AX,R13; \ - ADCQ $0,R15; \ - ADCQ $0, R8; \ - ADCQ $0, R9; \ - ADCQ $0,R14; \ - /* ( c7) + (c6,...,c0) */ \ - /* (r14) */ \ - MOVQ R14, AX; SHLQ $32,AX; \ - ADDQ R14,R10; MOVQ R10, 0+z; \ - ADCQ $0,R11; MOVQ R11, 8+z; \ - ADCQ $0,R12; MOVQ R12,16+z; \ - ADCQ AX,R13; MOVQ R13,24+z; \ - ADCQ $0,R15; MOVQ R15,32+z; \ - ADCQ $0, R8; MOVQ R8,40+z; \ - ADCQ $0, R9; MOVQ R9,48+z; - -// reduceFromDoubleAdx finds a z=x modulo p such that z<2^448 and stores in z -// Uses: AX, R8-R15, FLAGS -// Instr: x86_64, adx -#define reduceFromDoubleAdx(z,x) \ - /* ( ,2C13,2C12,2C11,2C10|C10,C9,C8, C7) + (C6,...,C0) */ \ - /* (r14, r13, r12, r11, r10,r9,r8,r15) */ \ - MOVQ 80+x,AX; MOVQ AX,R10; \ - MOVQ $0xFFFFFFFF00000000, R8; \ - ANDQ R8,R10; \ - \ - MOVQ $0,R14; \ - MOVQ 104+x,R13; SHLQ $1,R13,R14; \ - MOVQ 96+x,R12; SHLQ $1,R12,R13; \ - MOVQ 88+x,R11; SHLQ $1,R11,R12; \ - MOVQ 72+x, R9; SHLQ $1,R10,R11; \ - MOVQ 64+x, R8; SHLQ $1,R10; \ - MOVQ $0xFFFFFFFF,R15; ANDQ R15,AX; ORQ AX,R10; \ - MOVQ 56+x,R15; \ - \ - XORL AX,AX; \ - ADCXQ 0+x,R15; MOVQ R15, 0+z; MOVQ 56+x,R15; \ - ADCXQ 8+x, R8; MOVQ R8, 8+z; MOVQ 64+x, R8; \ - ADCXQ 16+x, R9; MOVQ R9,16+z; MOVQ 72+x, R9; \ - ADCXQ 24+x,R10; MOVQ R10,24+z; MOVQ 80+x,R10; \ - ADCXQ 32+x,R11; MOVQ R11,32+z; MOVQ 88+x,R11; \ - ADCXQ 40+x,R12; MOVQ R12,40+z; MOVQ 96+x,R12; \ - ADCXQ 48+x,R13; MOVQ R13,48+z; MOVQ 104+x,R13; \ - ADCXQ AX,R14; \ - /* (c10c9,c9c8,c8c7,c7c13,c13c12,c12c11,c11c10) + (c6,...,c0) */ \ - /* ( r9, r8, r15, r13, r12, r11, r10) */ \ - MOVQ R10, AX; \ - SHRQ $32,R11,R10; \ - SHRQ $32,R12,R11; \ - SHRQ $32,R13,R12; \ - SHRQ $32,R15,R13; \ - SHRQ $32, R8,R15; \ - SHRQ $32, R9, R8; \ - SHRQ $32, AX, R9; \ - \ - XORL AX,AX; \ - ADCXQ 0+z,R10; \ - ADCXQ 8+z,R11; \ - ADCXQ 16+z,R12; \ - ADCXQ 24+z,R13; \ - ADCXQ 32+z,R15; \ - ADCXQ 40+z, R8; \ - ADCXQ 48+z, R9; \ - ADCXQ AX,R14; \ - /* ( c7) + (c6,...,c0) */ \ - /* (r14) */ \ - MOVQ R14, AX; SHLQ $32, AX; \ - CLC; \ - ADCXQ R14,R10; MOVQ $0,R14; \ - ADCXQ R14,R11; \ - ADCXQ R14,R12; \ - ADCXQ AX,R13; \ - ADCXQ R14,R15; \ - ADCXQ R14, R8; \ - ADCXQ R14, R9; \ - ADCXQ R14,R14; \ - /* ( c7) + (c6,...,c0) */ \ - /* (r14) */ \ - MOVQ R14, AX; SHLQ $32, AX; \ - CLC; \ - ADCXQ R14,R10; MOVQ R10, 0+z; MOVQ $0,R14; \ - ADCXQ R14,R11; MOVQ R11, 8+z; \ - ADCXQ R14,R12; MOVQ R12,16+z; \ - ADCXQ AX,R13; MOVQ R13,24+z; \ - ADCXQ R14,R15; MOVQ R15,32+z; \ - ADCXQ R14, R8; MOVQ R8,40+z; \ - ADCXQ R14, R9; MOVQ R9,48+z; - -// addSub calculates two operations: x,y = x+y,x-y -// Uses: AX, DX, R8-R15, FLAGS -#define addSub(x,y) \ - MOVQ 0+x, R8; ADDQ 0+y, R8; \ - MOVQ 8+x, R9; ADCQ 8+y, R9; \ - MOVQ 16+x, R10; ADCQ 16+y, R10; \ - MOVQ 24+x, R11; ADCQ 24+y, R11; \ - MOVQ 32+x, R12; ADCQ 32+y, R12; \ - MOVQ 40+x, R13; ADCQ 40+y, R13; \ - MOVQ 48+x, R14; ADCQ 48+y, R14; \ - MOVQ $0, AX; ADCQ $0, AX; \ - MOVQ AX, DX; \ - SHLQ $32, DX; \ - ADDQ AX, R8; MOVQ $0, AX; \ - ADCQ $0, R9; \ - ADCQ $0, R10; \ - ADCQ DX, R11; \ - ADCQ $0, R12; \ - ADCQ $0, R13; \ - ADCQ $0, R14; \ - ADCQ $0, AX; \ - MOVQ AX, DX; \ - SHLQ $32, DX; \ - ADDQ AX, R8; MOVQ 0+x,AX; MOVQ R8, 0+x; MOVQ AX, R8; \ - ADCQ $0, R9; MOVQ 8+x,AX; MOVQ R9, 8+x; MOVQ AX, R9; \ - ADCQ $0, R10; MOVQ 16+x,AX; MOVQ R10, 16+x; MOVQ AX, R10; \ - ADCQ DX, R11; MOVQ 24+x,AX; MOVQ R11, 24+x; MOVQ AX, R11; \ - ADCQ $0, R12; MOVQ 32+x,AX; MOVQ R12, 32+x; MOVQ AX, R12; \ - ADCQ $0, R13; MOVQ 40+x,AX; MOVQ R13, 40+x; MOVQ AX, R13; \ - ADCQ $0, R14; MOVQ 48+x,AX; MOVQ R14, 48+x; MOVQ AX, R14; \ - SUBQ 0+y, R8; \ - SBBQ 8+y, R9; \ - SBBQ 16+y, R10; \ - SBBQ 24+y, R11; \ - SBBQ 32+y, R12; \ - SBBQ 40+y, R13; \ - SBBQ 48+y, R14; \ - MOVQ $0, AX; SETCS AX; \ - MOVQ AX, DX; \ - SHLQ $32, DX; \ - SUBQ AX, R8; MOVQ $0, AX; \ - SBBQ $0, R9; \ - SBBQ $0, R10; \ - SBBQ DX, R11; \ - SBBQ $0, R12; \ - SBBQ $0, R13; \ - SBBQ $0, R14; \ - SETCS AX; \ - MOVQ AX, DX; \ - SHLQ $32, DX; \ - SUBQ AX, R8; MOVQ R8, 0+y; \ - SBBQ $0, R9; MOVQ R9, 8+y; \ - SBBQ $0, R10; MOVQ R10, 16+y; \ - SBBQ DX, R11; MOVQ R11, 24+y; \ - SBBQ $0, R12; MOVQ R12, 32+y; \ - SBBQ $0, R13; MOVQ R13, 40+y; \ - SBBQ $0, R14; MOVQ R14, 48+y; diff --git a/vendor/github.com/cloudflare/circl/math/fp448/fp_amd64.s b/vendor/github.com/cloudflare/circl/math/fp448/fp_amd64.s deleted file mode 100644 index 3f1f07c9862..00000000000 --- a/vendor/github.com/cloudflare/circl/math/fp448/fp_amd64.s +++ /dev/null @@ -1,75 +0,0 @@ -//go:build amd64 && !purego -// +build amd64,!purego - -#include "textflag.h" -#include "fp_amd64.h" - -// func cmovAmd64(x, y *Elt, n uint) -TEXT ·cmovAmd64(SB),NOSPLIT,$0-24 - MOVQ x+0(FP), DI - MOVQ y+8(FP), SI - MOVQ n+16(FP), BX - cselect(0(DI),0(SI),BX) - RET - -// func cswapAmd64(x, y *Elt, n uint) -TEXT ·cswapAmd64(SB),NOSPLIT,$0-24 - MOVQ x+0(FP), DI - MOVQ y+8(FP), SI - MOVQ n+16(FP), BX - cswap(0(DI),0(SI),BX) - RET - -// func subAmd64(z, x, y *Elt) -TEXT ·subAmd64(SB),NOSPLIT,$0-24 - MOVQ z+0(FP), DI - MOVQ x+8(FP), SI - MOVQ y+16(FP), BX - subtraction(0(DI),0(SI),0(BX)) - RET - -// func addsubAmd64(x, y *Elt) -TEXT ·addsubAmd64(SB),NOSPLIT,$0-16 - MOVQ x+0(FP), DI - MOVQ y+8(FP), SI - addSub(0(DI),0(SI)) - RET - -#define addLegacy \ - additionLeg(0(DI),0(SI),0(BX)) -#define addBmi2Adx \ - additionAdx(0(DI),0(SI),0(BX)) - -#define mulLegacy \ - integerMulLeg(0(SP),0(SI),0(BX)) \ - reduceFromDoubleLeg(0(DI),0(SP)) -#define mulBmi2Adx \ - integerMulAdx(0(SP),0(SI),0(BX)) \ - reduceFromDoubleAdx(0(DI),0(SP)) - -#define sqrLegacy \ - integerSqrLeg(0(SP),0(SI)) \ - reduceFromDoubleLeg(0(DI),0(SP)) -#define sqrBmi2Adx \ - integerSqrAdx(0(SP),0(SI)) \ - reduceFromDoubleAdx(0(DI),0(SP)) - -// func addAmd64(z, x, y *Elt) -TEXT ·addAmd64(SB),NOSPLIT,$0-24 - MOVQ z+0(FP), DI - MOVQ x+8(FP), SI - MOVQ y+16(FP), BX - CHECK_BMI2ADX(LADD, addLegacy, addBmi2Adx) - -// func mulAmd64(z, x, y *Elt) -TEXT ·mulAmd64(SB),NOSPLIT,$112-24 - MOVQ z+0(FP), DI - MOVQ x+8(FP), SI - MOVQ y+16(FP), BX - CHECK_BMI2ADX(LMUL, mulLegacy, mulBmi2Adx) - -// func sqrAmd64(z, x *Elt) -TEXT ·sqrAmd64(SB),NOSPLIT,$112-16 - MOVQ z+0(FP), DI - MOVQ x+8(FP), SI - CHECK_BMI2ADX(LSQR, sqrLegacy, sqrBmi2Adx) diff --git a/vendor/github.com/cloudflare/circl/math/fp448/fp_generic.go b/vendor/github.com/cloudflare/circl/math/fp448/fp_generic.go deleted file mode 100644 index 47a0b63205f..00000000000 --- a/vendor/github.com/cloudflare/circl/math/fp448/fp_generic.go +++ /dev/null @@ -1,339 +0,0 @@ -package fp448 - -import ( - "encoding/binary" - "math/bits" -) - -func cmovGeneric(x, y *Elt, n uint) { - m := -uint64(n & 0x1) - x0 := binary.LittleEndian.Uint64(x[0*8 : 1*8]) - x1 := binary.LittleEndian.Uint64(x[1*8 : 2*8]) - x2 := binary.LittleEndian.Uint64(x[2*8 : 3*8]) - x3 := binary.LittleEndian.Uint64(x[3*8 : 4*8]) - x4 := binary.LittleEndian.Uint64(x[4*8 : 5*8]) - x5 := binary.LittleEndian.Uint64(x[5*8 : 6*8]) - x6 := binary.LittleEndian.Uint64(x[6*8 : 7*8]) - - y0 := binary.LittleEndian.Uint64(y[0*8 : 1*8]) - y1 := binary.LittleEndian.Uint64(y[1*8 : 2*8]) - y2 := binary.LittleEndian.Uint64(y[2*8 : 3*8]) - y3 := binary.LittleEndian.Uint64(y[3*8 : 4*8]) - y4 := binary.LittleEndian.Uint64(y[4*8 : 5*8]) - y5 := binary.LittleEndian.Uint64(y[5*8 : 6*8]) - y6 := binary.LittleEndian.Uint64(y[6*8 : 7*8]) - - x0 = (x0 &^ m) | (y0 & m) - x1 = (x1 &^ m) | (y1 & m) - x2 = (x2 &^ m) | (y2 & m) - x3 = (x3 &^ m) | (y3 & m) - x4 = (x4 &^ m) | (y4 & m) - x5 = (x5 &^ m) | (y5 & m) - x6 = (x6 &^ m) | (y6 & m) - - binary.LittleEndian.PutUint64(x[0*8:1*8], x0) - binary.LittleEndian.PutUint64(x[1*8:2*8], x1) - binary.LittleEndian.PutUint64(x[2*8:3*8], x2) - binary.LittleEndian.PutUint64(x[3*8:4*8], x3) - binary.LittleEndian.PutUint64(x[4*8:5*8], x4) - binary.LittleEndian.PutUint64(x[5*8:6*8], x5) - binary.LittleEndian.PutUint64(x[6*8:7*8], x6) -} - -func cswapGeneric(x, y *Elt, n uint) { - m := -uint64(n & 0x1) - x0 := binary.LittleEndian.Uint64(x[0*8 : 1*8]) - x1 := binary.LittleEndian.Uint64(x[1*8 : 2*8]) - x2 := binary.LittleEndian.Uint64(x[2*8 : 3*8]) - x3 := binary.LittleEndian.Uint64(x[3*8 : 4*8]) - x4 := binary.LittleEndian.Uint64(x[4*8 : 5*8]) - x5 := binary.LittleEndian.Uint64(x[5*8 : 6*8]) - x6 := binary.LittleEndian.Uint64(x[6*8 : 7*8]) - - y0 := binary.LittleEndian.Uint64(y[0*8 : 1*8]) - y1 := binary.LittleEndian.Uint64(y[1*8 : 2*8]) - y2 := binary.LittleEndian.Uint64(y[2*8 : 3*8]) - y3 := binary.LittleEndian.Uint64(y[3*8 : 4*8]) - y4 := binary.LittleEndian.Uint64(y[4*8 : 5*8]) - y5 := binary.LittleEndian.Uint64(y[5*8 : 6*8]) - y6 := binary.LittleEndian.Uint64(y[6*8 : 7*8]) - - t0 := m & (x0 ^ y0) - t1 := m & (x1 ^ y1) - t2 := m & (x2 ^ y2) - t3 := m & (x3 ^ y3) - t4 := m & (x4 ^ y4) - t5 := m & (x5 ^ y5) - t6 := m & (x6 ^ y6) - x0 ^= t0 - x1 ^= t1 - x2 ^= t2 - x3 ^= t3 - x4 ^= t4 - x5 ^= t5 - x6 ^= t6 - y0 ^= t0 - y1 ^= t1 - y2 ^= t2 - y3 ^= t3 - y4 ^= t4 - y5 ^= t5 - y6 ^= t6 - - binary.LittleEndian.PutUint64(x[0*8:1*8], x0) - binary.LittleEndian.PutUint64(x[1*8:2*8], x1) - binary.LittleEndian.PutUint64(x[2*8:3*8], x2) - binary.LittleEndian.PutUint64(x[3*8:4*8], x3) - binary.LittleEndian.PutUint64(x[4*8:5*8], x4) - binary.LittleEndian.PutUint64(x[5*8:6*8], x5) - binary.LittleEndian.PutUint64(x[6*8:7*8], x6) - - binary.LittleEndian.PutUint64(y[0*8:1*8], y0) - binary.LittleEndian.PutUint64(y[1*8:2*8], y1) - binary.LittleEndian.PutUint64(y[2*8:3*8], y2) - binary.LittleEndian.PutUint64(y[3*8:4*8], y3) - binary.LittleEndian.PutUint64(y[4*8:5*8], y4) - binary.LittleEndian.PutUint64(y[5*8:6*8], y5) - binary.LittleEndian.PutUint64(y[6*8:7*8], y6) -} - -func addGeneric(z, x, y *Elt) { - x0 := binary.LittleEndian.Uint64(x[0*8 : 1*8]) - x1 := binary.LittleEndian.Uint64(x[1*8 : 2*8]) - x2 := binary.LittleEndian.Uint64(x[2*8 : 3*8]) - x3 := binary.LittleEndian.Uint64(x[3*8 : 4*8]) - x4 := binary.LittleEndian.Uint64(x[4*8 : 5*8]) - x5 := binary.LittleEndian.Uint64(x[5*8 : 6*8]) - x6 := binary.LittleEndian.Uint64(x[6*8 : 7*8]) - - y0 := binary.LittleEndian.Uint64(y[0*8 : 1*8]) - y1 := binary.LittleEndian.Uint64(y[1*8 : 2*8]) - y2 := binary.LittleEndian.Uint64(y[2*8 : 3*8]) - y3 := binary.LittleEndian.Uint64(y[3*8 : 4*8]) - y4 := binary.LittleEndian.Uint64(y[4*8 : 5*8]) - y5 := binary.LittleEndian.Uint64(y[5*8 : 6*8]) - y6 := binary.LittleEndian.Uint64(y[6*8 : 7*8]) - - z0, c0 := bits.Add64(x0, y0, 0) - z1, c1 := bits.Add64(x1, y1, c0) - z2, c2 := bits.Add64(x2, y2, c1) - z3, c3 := bits.Add64(x3, y3, c2) - z4, c4 := bits.Add64(x4, y4, c3) - z5, c5 := bits.Add64(x5, y5, c4) - z6, z7 := bits.Add64(x6, y6, c5) - - z0, c0 = bits.Add64(z0, z7, 0) - z1, c1 = bits.Add64(z1, 0, c0) - z2, c2 = bits.Add64(z2, 0, c1) - z3, c3 = bits.Add64(z3, z7<<32, c2) - z4, c4 = bits.Add64(z4, 0, c3) - z5, c5 = bits.Add64(z5, 0, c4) - z6, z7 = bits.Add64(z6, 0, c5) - - z0, c0 = bits.Add64(z0, z7, 0) - z1, c1 = bits.Add64(z1, 0, c0) - z2, c2 = bits.Add64(z2, 0, c1) - z3, c3 = bits.Add64(z3, z7<<32, c2) - z4, c4 = bits.Add64(z4, 0, c3) - z5, c5 = bits.Add64(z5, 0, c4) - z6, _ = bits.Add64(z6, 0, c5) - - binary.LittleEndian.PutUint64(z[0*8:1*8], z0) - binary.LittleEndian.PutUint64(z[1*8:2*8], z1) - binary.LittleEndian.PutUint64(z[2*8:3*8], z2) - binary.LittleEndian.PutUint64(z[3*8:4*8], z3) - binary.LittleEndian.PutUint64(z[4*8:5*8], z4) - binary.LittleEndian.PutUint64(z[5*8:6*8], z5) - binary.LittleEndian.PutUint64(z[6*8:7*8], z6) -} - -func subGeneric(z, x, y *Elt) { - x0 := binary.LittleEndian.Uint64(x[0*8 : 1*8]) - x1 := binary.LittleEndian.Uint64(x[1*8 : 2*8]) - x2 := binary.LittleEndian.Uint64(x[2*8 : 3*8]) - x3 := binary.LittleEndian.Uint64(x[3*8 : 4*8]) - x4 := binary.LittleEndian.Uint64(x[4*8 : 5*8]) - x5 := binary.LittleEndian.Uint64(x[5*8 : 6*8]) - x6 := binary.LittleEndian.Uint64(x[6*8 : 7*8]) - - y0 := binary.LittleEndian.Uint64(y[0*8 : 1*8]) - y1 := binary.LittleEndian.Uint64(y[1*8 : 2*8]) - y2 := binary.LittleEndian.Uint64(y[2*8 : 3*8]) - y3 := binary.LittleEndian.Uint64(y[3*8 : 4*8]) - y4 := binary.LittleEndian.Uint64(y[4*8 : 5*8]) - y5 := binary.LittleEndian.Uint64(y[5*8 : 6*8]) - y6 := binary.LittleEndian.Uint64(y[6*8 : 7*8]) - - z0, c0 := bits.Sub64(x0, y0, 0) - z1, c1 := bits.Sub64(x1, y1, c0) - z2, c2 := bits.Sub64(x2, y2, c1) - z3, c3 := bits.Sub64(x3, y3, c2) - z4, c4 := bits.Sub64(x4, y4, c3) - z5, c5 := bits.Sub64(x5, y5, c4) - z6, z7 := bits.Sub64(x6, y6, c5) - - z0, c0 = bits.Sub64(z0, z7, 0) - z1, c1 = bits.Sub64(z1, 0, c0) - z2, c2 = bits.Sub64(z2, 0, c1) - z3, c3 = bits.Sub64(z3, z7<<32, c2) - z4, c4 = bits.Sub64(z4, 0, c3) - z5, c5 = bits.Sub64(z5, 0, c4) - z6, z7 = bits.Sub64(z6, 0, c5) - - z0, c0 = bits.Sub64(z0, z7, 0) - z1, c1 = bits.Sub64(z1, 0, c0) - z2, c2 = bits.Sub64(z2, 0, c1) - z3, c3 = bits.Sub64(z3, z7<<32, c2) - z4, c4 = bits.Sub64(z4, 0, c3) - z5, c5 = bits.Sub64(z5, 0, c4) - z6, _ = bits.Sub64(z6, 0, c5) - - binary.LittleEndian.PutUint64(z[0*8:1*8], z0) - binary.LittleEndian.PutUint64(z[1*8:2*8], z1) - binary.LittleEndian.PutUint64(z[2*8:3*8], z2) - binary.LittleEndian.PutUint64(z[3*8:4*8], z3) - binary.LittleEndian.PutUint64(z[4*8:5*8], z4) - binary.LittleEndian.PutUint64(z[5*8:6*8], z5) - binary.LittleEndian.PutUint64(z[6*8:7*8], z6) -} - -func addsubGeneric(x, y *Elt) { - z := &Elt{} - addGeneric(z, x, y) - subGeneric(y, x, y) - *x = *z -} - -func mulGeneric(z, x, y *Elt) { - x0 := binary.LittleEndian.Uint64(x[0*8 : 1*8]) - x1 := binary.LittleEndian.Uint64(x[1*8 : 2*8]) - x2 := binary.LittleEndian.Uint64(x[2*8 : 3*8]) - x3 := binary.LittleEndian.Uint64(x[3*8 : 4*8]) - x4 := binary.LittleEndian.Uint64(x[4*8 : 5*8]) - x5 := binary.LittleEndian.Uint64(x[5*8 : 6*8]) - x6 := binary.LittleEndian.Uint64(x[6*8 : 7*8]) - - y0 := binary.LittleEndian.Uint64(y[0*8 : 1*8]) - y1 := binary.LittleEndian.Uint64(y[1*8 : 2*8]) - y2 := binary.LittleEndian.Uint64(y[2*8 : 3*8]) - y3 := binary.LittleEndian.Uint64(y[3*8 : 4*8]) - y4 := binary.LittleEndian.Uint64(y[4*8 : 5*8]) - y5 := binary.LittleEndian.Uint64(y[5*8 : 6*8]) - y6 := binary.LittleEndian.Uint64(y[6*8 : 7*8]) - - yy := [7]uint64{y0, y1, y2, y3, y4, y5, y6} - zz := [7]uint64{} - - yi := yy[0] - h0, l0 := bits.Mul64(x0, yi) - h1, l1 := bits.Mul64(x1, yi) - h2, l2 := bits.Mul64(x2, yi) - h3, l3 := bits.Mul64(x3, yi) - h4, l4 := bits.Mul64(x4, yi) - h5, l5 := bits.Mul64(x5, yi) - h6, l6 := bits.Mul64(x6, yi) - - zz[0] = l0 - a0, c0 := bits.Add64(h0, l1, 0) - a1, c1 := bits.Add64(h1, l2, c0) - a2, c2 := bits.Add64(h2, l3, c1) - a3, c3 := bits.Add64(h3, l4, c2) - a4, c4 := bits.Add64(h4, l5, c3) - a5, c5 := bits.Add64(h5, l6, c4) - a6, _ := bits.Add64(h6, 0, c5) - - for i := 1; i < 7; i++ { - yi = yy[i] - h0, l0 = bits.Mul64(x0, yi) - h1, l1 = bits.Mul64(x1, yi) - h2, l2 = bits.Mul64(x2, yi) - h3, l3 = bits.Mul64(x3, yi) - h4, l4 = bits.Mul64(x4, yi) - h5, l5 = bits.Mul64(x5, yi) - h6, l6 = bits.Mul64(x6, yi) - - zz[i], c0 = bits.Add64(a0, l0, 0) - a0, c1 = bits.Add64(a1, l1, c0) - a1, c2 = bits.Add64(a2, l2, c1) - a2, c3 = bits.Add64(a3, l3, c2) - a3, c4 = bits.Add64(a4, l4, c3) - a4, c5 = bits.Add64(a5, l5, c4) - a5, a6 = bits.Add64(a6, l6, c5) - - a0, c0 = bits.Add64(a0, h0, 0) - a1, c1 = bits.Add64(a1, h1, c0) - a2, c2 = bits.Add64(a2, h2, c1) - a3, c3 = bits.Add64(a3, h3, c2) - a4, c4 = bits.Add64(a4, h4, c3) - a5, c5 = bits.Add64(a5, h5, c4) - a6, _ = bits.Add64(a6, h6, c5) - } - red64(z, &zz, &[7]uint64{a0, a1, a2, a3, a4, a5, a6}) -} - -func sqrGeneric(z, x *Elt) { mulGeneric(z, x, x) } - -func red64(z *Elt, l, h *[7]uint64) { - /* (2C13, 2C12, 2C11, 2C10|C10, C9, C8, C7) + (C6,...,C0) */ - h0 := h[0] - h1 := h[1] - h2 := h[2] - h3 := ((h[3] & (0xFFFFFFFF << 32)) << 1) | (h[3] & 0xFFFFFFFF) - h4 := (h[3] >> 63) | (h[4] << 1) - h5 := (h[4] >> 63) | (h[5] << 1) - h6 := (h[5] >> 63) | (h[6] << 1) - h7 := (h[6] >> 63) - - l0, c0 := bits.Add64(h0, l[0], 0) - l1, c1 := bits.Add64(h1, l[1], c0) - l2, c2 := bits.Add64(h2, l[2], c1) - l3, c3 := bits.Add64(h3, l[3], c2) - l4, c4 := bits.Add64(h4, l[4], c3) - l5, c5 := bits.Add64(h5, l[5], c4) - l6, c6 := bits.Add64(h6, l[6], c5) - l7, _ := bits.Add64(h7, 0, c6) - - /* (C10C9, C9C8,C8C7,C7C13,C13C12,C12C11,C11C10) + (C6,...,C0) */ - h0 = (h[3] >> 32) | (h[4] << 32) - h1 = (h[4] >> 32) | (h[5] << 32) - h2 = (h[5] >> 32) | (h[6] << 32) - h3 = (h[6] >> 32) | (h[0] << 32) - h4 = (h[0] >> 32) | (h[1] << 32) - h5 = (h[1] >> 32) | (h[2] << 32) - h6 = (h[2] >> 32) | (h[3] << 32) - - l0, c0 = bits.Add64(l0, h0, 0) - l1, c1 = bits.Add64(l1, h1, c0) - l2, c2 = bits.Add64(l2, h2, c1) - l3, c3 = bits.Add64(l3, h3, c2) - l4, c4 = bits.Add64(l4, h4, c3) - l5, c5 = bits.Add64(l5, h5, c4) - l6, c6 = bits.Add64(l6, h6, c5) - l7, _ = bits.Add64(l7, 0, c6) - - /* (C7) + (C6,...,C0) */ - l0, c0 = bits.Add64(l0, l7, 0) - l1, c1 = bits.Add64(l1, 0, c0) - l2, c2 = bits.Add64(l2, 0, c1) - l3, c3 = bits.Add64(l3, l7<<32, c2) - l4, c4 = bits.Add64(l4, 0, c3) - l5, c5 = bits.Add64(l5, 0, c4) - l6, l7 = bits.Add64(l6, 0, c5) - - /* (C7) + (C6,...,C0) */ - l0, c0 = bits.Add64(l0, l7, 0) - l1, c1 = bits.Add64(l1, 0, c0) - l2, c2 = bits.Add64(l2, 0, c1) - l3, c3 = bits.Add64(l3, l7<<32, c2) - l4, c4 = bits.Add64(l4, 0, c3) - l5, c5 = bits.Add64(l5, 0, c4) - l6, _ = bits.Add64(l6, 0, c5) - - binary.LittleEndian.PutUint64(z[0*8:1*8], l0) - binary.LittleEndian.PutUint64(z[1*8:2*8], l1) - binary.LittleEndian.PutUint64(z[2*8:3*8], l2) - binary.LittleEndian.PutUint64(z[3*8:4*8], l3) - binary.LittleEndian.PutUint64(z[4*8:5*8], l4) - binary.LittleEndian.PutUint64(z[5*8:6*8], l5) - binary.LittleEndian.PutUint64(z[6*8:7*8], l6) -} diff --git a/vendor/github.com/cloudflare/circl/math/fp448/fp_noasm.go b/vendor/github.com/cloudflare/circl/math/fp448/fp_noasm.go deleted file mode 100644 index a62225d2962..00000000000 --- a/vendor/github.com/cloudflare/circl/math/fp448/fp_noasm.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !amd64 || purego -// +build !amd64 purego - -package fp448 - -func cmov(x, y *Elt, n uint) { cmovGeneric(x, y, n) } -func cswap(x, y *Elt, n uint) { cswapGeneric(x, y, n) } -func add(z, x, y *Elt) { addGeneric(z, x, y) } -func sub(z, x, y *Elt) { subGeneric(z, x, y) } -func addsub(x, y *Elt) { addsubGeneric(x, y) } -func mul(z, x, y *Elt) { mulGeneric(z, x, y) } -func sqr(z, x *Elt) { sqrGeneric(z, x) } diff --git a/vendor/github.com/cloudflare/circl/math/fp448/fuzzer.go b/vendor/github.com/cloudflare/circl/math/fp448/fuzzer.go deleted file mode 100644 index 2d7afc80598..00000000000 --- a/vendor/github.com/cloudflare/circl/math/fp448/fuzzer.go +++ /dev/null @@ -1,75 +0,0 @@ -//go:build gofuzz -// +build gofuzz - -// How to run the fuzzer: -// -// $ go get -u github.com/dvyukov/go-fuzz/go-fuzz -// $ go get -u github.com/dvyukov/go-fuzz/go-fuzz-build -// $ go-fuzz-build -libfuzzer -func FuzzReduction -o lib.a -// $ clang -fsanitize=fuzzer lib.a -o fu.exe -// $ ./fu.exe -package fp448 - -import ( - "encoding/binary" - "fmt" - "math/big" - - "github.com/cloudflare/circl/internal/conv" -) - -// FuzzReduction is a fuzzer target for red64 function, which reduces t -// (112 bits) to a number t' (56 bits) congruent modulo p448. -func FuzzReduction(data []byte) int { - if len(data) != 2*Size { - return -1 - } - var got, want Elt - var lo, hi [7]uint64 - a := data[:Size] - b := data[Size:] - lo[0] = binary.LittleEndian.Uint64(a[0*8 : 1*8]) - lo[1] = binary.LittleEndian.Uint64(a[1*8 : 2*8]) - lo[2] = binary.LittleEndian.Uint64(a[2*8 : 3*8]) - lo[3] = binary.LittleEndian.Uint64(a[3*8 : 4*8]) - lo[4] = binary.LittleEndian.Uint64(a[4*8 : 5*8]) - lo[5] = binary.LittleEndian.Uint64(a[5*8 : 6*8]) - lo[6] = binary.LittleEndian.Uint64(a[6*8 : 7*8]) - - hi[0] = binary.LittleEndian.Uint64(b[0*8 : 1*8]) - hi[1] = binary.LittleEndian.Uint64(b[1*8 : 2*8]) - hi[2] = binary.LittleEndian.Uint64(b[2*8 : 3*8]) - hi[3] = binary.LittleEndian.Uint64(b[3*8 : 4*8]) - hi[4] = binary.LittleEndian.Uint64(b[4*8 : 5*8]) - hi[5] = binary.LittleEndian.Uint64(b[5*8 : 6*8]) - hi[6] = binary.LittleEndian.Uint64(b[6*8 : 7*8]) - - red64(&got, &lo, &hi) - - t := conv.BytesLe2BigInt(data[:2*Size]) - - two448 := big.NewInt(1) - two448.Lsh(two448, 448) // 2^448 - mask448 := big.NewInt(1) - mask448.Sub(two448, mask448) // 2^448-1 - two224plus1 := big.NewInt(1) - two224plus1.Lsh(two224plus1, 224) - two224plus1.Add(two224plus1, big.NewInt(1)) // 2^224+1 - - var loBig, hiBig big.Int - for t.Cmp(two448) >= 0 { - loBig.And(t, mask448) - hiBig.Rsh(t, 448) - t.Mul(&hiBig, two224plus1) - t.Add(t, &loBig) - } - conv.BigInt2BytesLe(want[:], t) - - if got != want { - fmt.Printf("in: %v\n", conv.BytesLe2BigInt(data[:2*Size])) - fmt.Printf("got: %v\n", got) - fmt.Printf("want: %v\n", want) - panic("error found") - } - return 1 -} diff --git a/vendor/github.com/cloudflare/circl/math/integer.go b/vendor/github.com/cloudflare/circl/math/integer.go deleted file mode 100644 index 9c80c23b59c..00000000000 --- a/vendor/github.com/cloudflare/circl/math/integer.go +++ /dev/null @@ -1,16 +0,0 @@ -package math - -import "math/bits" - -// NextPow2 finds the next power of two (N=2^k, k>=0) greater than n. -// If n is already a power of two, then this function returns n, and log2(n). -func NextPow2(n uint) (N uint, k uint) { - if bits.OnesCount(n) == 1 { - k = uint(bits.TrailingZeros(n)) - N = n - } else { - k = uint(bits.Len(n)) - N = uint(1) << k - } - return -} diff --git a/vendor/github.com/cloudflare/circl/math/mlsbset/mlsbset.go b/vendor/github.com/cloudflare/circl/math/mlsbset/mlsbset.go deleted file mode 100644 index a43851b8bb2..00000000000 --- a/vendor/github.com/cloudflare/circl/math/mlsbset/mlsbset.go +++ /dev/null @@ -1,122 +0,0 @@ -// Package mlsbset provides a constant-time exponentiation method with precomputation. -// -// References: "Efficient and secure algorithms for GLV-based scalar -// multiplication and their implementation on GLV–GLS curves" by (Faz-Hernandez et al.) -// - https://doi.org/10.1007/s13389-014-0085-7 -// - https://eprint.iacr.org/2013/158 -package mlsbset - -import ( - "errors" - "fmt" - "math/big" - - "github.com/cloudflare/circl/internal/conv" -) - -// EltG is a group element. -type EltG interface{} - -// EltP is a precomputed group element. -type EltP interface{} - -// Group defines the operations required by MLSBSet exponentiation method. -type Group interface { - Identity() EltG // Returns the identity of the group. - Sqr(x EltG) // Calculates x = x^2. - Mul(x EltG, y EltP) // Calculates x = x*y. - NewEltP() EltP // Returns an arbitrary precomputed element. - ExtendedEltP() EltP // Returns the precomputed element x^(2^(w*d)). - Lookup(a EltP, v uint, s, u int32) // Sets a = s*T[v][u]. -} - -// Params contains the parameters of the encoding. -type Params struct { - T uint // T is the maximum size (in bits) of exponents. - V uint // V is the number of tables. - W uint // W is the window size. - E uint // E is the number of digits per table. - D uint // D is the number of digits in total. - L uint // L is the length of the code. -} - -// Encoder allows to convert integers into valid powers. -type Encoder struct{ p Params } - -// New produces an encoder of the MLSBSet algorithm. -func New(t, v, w uint) (Encoder, error) { - if !(t > 1 && v >= 1 && w >= 2) { - return Encoder{}, errors.New("t>1, v>=1, w>=2") - } - e := (t + w*v - 1) / (w * v) - d := e * v - l := d * w - return Encoder{Params{t, v, w, e, d, l}}, nil -} - -// Encode converts an odd integer k into a valid power for exponentiation. -func (m Encoder) Encode(k []byte) (*Power, error) { - if len(k) == 0 { - return nil, errors.New("empty slice") - } - if !(len(k) <= int(m.p.L+7)>>3) { - return nil, errors.New("k too big") - } - if k[0]%2 == 0 { - return nil, errors.New("k must be odd") - } - ap := int((m.p.L+7)/8) - len(k) - k = append(k, make([]byte, ap)...) - s := m.signs(k) - b := make([]int32, m.p.L-m.p.D) - c := conv.BytesLe2BigInt(k) - c.Rsh(c, m.p.D) - var bi big.Int - for i := m.p.D; i < m.p.L; i++ { - c0 := int32(c.Bit(0)) - b[i-m.p.D] = s[i%m.p.D] * c0 - bi.SetInt64(int64(b[i-m.p.D] >> 1)) - c.Rsh(c, 1) - c.Sub(c, &bi) - } - carry := int(c.Int64()) - return &Power{m, s, b, carry}, nil -} - -// signs calculates the set of signs. -func (m Encoder) signs(k []byte) []int32 { - s := make([]int32, m.p.D) - s[m.p.D-1] = 1 - for i := uint(1); i < m.p.D; i++ { - ki := int32((k[i>>3] >> (i & 0x7)) & 0x1) - s[i-1] = 2*ki - 1 - } - return s -} - -// GetParams returns the complementary parameters of the encoding. -func (m Encoder) GetParams() Params { return m.p } - -// tableSize returns the size of each table. -func (m Encoder) tableSize() uint { return 1 << (m.p.W - 1) } - -// Elts returns the total number of elements that must be precomputed. -func (m Encoder) Elts() uint { return m.p.V * m.tableSize() } - -// IsExtended returns true if the element x^(2^(wd)) must be calculated. -func (m Encoder) IsExtended() bool { q := m.p.T / (m.p.V * m.p.W); return m.p.T == q*m.p.V*m.p.W } - -// Ops returns the number of squares and multiplications executed during an exponentiation. -func (m Encoder) Ops() (S uint, M uint) { - S = m.p.E - M = m.p.E * m.p.V - if m.IsExtended() { - M++ - } - return -} - -func (m Encoder) String() string { - return fmt.Sprintf("T: %v W: %v V: %v e: %v d: %v l: %v wv|t: %v", - m.p.T, m.p.W, m.p.V, m.p.E, m.p.D, m.p.L, m.IsExtended()) -} diff --git a/vendor/github.com/cloudflare/circl/math/mlsbset/power.go b/vendor/github.com/cloudflare/circl/math/mlsbset/power.go deleted file mode 100644 index 3f214c3046a..00000000000 --- a/vendor/github.com/cloudflare/circl/math/mlsbset/power.go +++ /dev/null @@ -1,64 +0,0 @@ -package mlsbset - -import "fmt" - -// Power is a valid exponent produced by the MLSBSet encoding algorithm. -type Power struct { - set Encoder // parameters of code. - s []int32 // set of signs. - b []int32 // set of digits. - c int // carry is {0,1}. -} - -// Exp is calculates x^k, where x is a predetermined element of a group G. -func (p *Power) Exp(G Group) EltG { - a, b := G.Identity(), G.NewEltP() - for e := int(p.set.p.E - 1); e >= 0; e-- { - G.Sqr(a) - for v := uint(0); v < p.set.p.V; v++ { - sgnElt, idElt := p.Digit(v, uint(e)) - G.Lookup(b, v, sgnElt, idElt) - G.Mul(a, b) - } - } - if p.set.IsExtended() && p.c == 1 { - G.Mul(a, G.ExtendedEltP()) - } - return a -} - -// Digit returns the (v,e)-th digit and its sign. -func (p *Power) Digit(v, e uint) (sgn, dig int32) { - sgn = p.bit(0, v, e) - dig = 0 - for i := p.set.p.W - 1; i > 0; i-- { - dig = 2*dig + p.bit(i, v, e) - } - mask := dig >> 31 - dig = (dig + mask) ^ mask - return sgn, dig -} - -// bit returns the (w,v,e)-th bit of the code. -func (p *Power) bit(w, v, e uint) int32 { - if !(w < p.set.p.W && - v < p.set.p.V && - e < p.set.p.E) { - panic(fmt.Errorf("indexes outside (%v,%v,%v)", w, v, e)) - } - if w == 0 { - return p.s[p.set.p.E*v+e] - } - return p.b[p.set.p.D*(w-1)+p.set.p.E*v+e] -} - -func (p *Power) String() string { - dig := "" - for j := uint(0); j < p.set.p.V; j++ { - for i := uint(0); i < p.set.p.E; i++ { - s, d := p.Digit(j, i) - dig += fmt.Sprintf("(%2v,%2v) = %+2v %+2v\n", j, i, s, d) - } - } - return fmt.Sprintf("len: %v\ncarry: %v\ndigits:\n%v", len(p.b)+len(p.s), p.c, dig) -} diff --git a/vendor/github.com/cloudflare/circl/math/primes.go b/vendor/github.com/cloudflare/circl/math/primes.go deleted file mode 100644 index 158fd83a7aa..00000000000 --- a/vendor/github.com/cloudflare/circl/math/primes.go +++ /dev/null @@ -1,34 +0,0 @@ -package math - -import ( - "crypto/rand" - "io" - "math/big" -) - -// IsSafePrime reports whether p is (probably) a safe prime. -// The prime p=2*q+1 is safe prime if both p and q are primes. -// Note that ProbablyPrime is not suitable for judging primes -// that an adversary may have crafted to fool the test. -func IsSafePrime(p *big.Int) bool { - pdiv2 := new(big.Int).Rsh(p, 1) - return p.ProbablyPrime(20) && pdiv2.ProbablyPrime(20) -} - -// SafePrime returns a number of the given bit length that is a safe prime with high probability. -// The number returned p=2*q+1 is a safe prime if both p and q are primes. -// SafePrime will return error for any error returned by rand.Read or if bits < 2. -func SafePrime(random io.Reader, bits int) (*big.Int, error) { - one := big.NewInt(1) - p := new(big.Int) - for { - q, err := rand.Prime(random, bits-1) - if err != nil { - return nil, err - } - p.Lsh(q, 1).Add(p, one) - if p.ProbablyPrime(20) { - return p, nil - } - } -} diff --git a/vendor/github.com/cloudflare/circl/math/wnaf.go b/vendor/github.com/cloudflare/circl/math/wnaf.go deleted file mode 100644 index 94a1ec50429..00000000000 --- a/vendor/github.com/cloudflare/circl/math/wnaf.go +++ /dev/null @@ -1,84 +0,0 @@ -// Package math provides some utility functions for big integers. -package math - -import "math/big" - -// SignedDigit obtains the signed-digit recoding of n and returns a list L of -// digits such that n = sum( L[i]*2^(i*(w-1)) ), and each L[i] is an odd number -// in the set {±1, ±3, ..., ±2^(w-1)-1}. The third parameter ensures that the -// output has ceil(l/(w-1)) digits. -// -// Restrictions: -// - n is odd and n > 0. -// - 1 < w < 32. -// - l >= bit length of n. -// -// References: -// - Alg.6 in "Exponent Recoding and Regular Exponentiation Algorithms" -// by Joye-Tunstall. http://doi.org/10.1007/978-3-642-02384-2_21 -// - Alg.6 in "Selecting Elliptic Curves for Cryptography: An Efficiency and -// Security Analysis" by Bos et al. http://doi.org/10.1007/s13389-015-0097-y -func SignedDigit(n *big.Int, w, l uint) []int32 { - if n.Sign() <= 0 || n.Bit(0) == 0 { - panic("n must be non-zero, odd, and positive") - } - if w <= 1 || w >= 32 { - panic("Verify that 1 < w < 32") - } - if uint(n.BitLen()) > l { - panic("n is too big to fit in l digits") - } - lenN := (l + (w - 1) - 1) / (w - 1) // ceil(l/(w-1)) - L := make([]int32, lenN+1) - var k, v big.Int - k.Set(n) - - var i uint - for i = 0; i < lenN; i++ { - words := k.Bits() - value := int32(words[0] & ((1 << w) - 1)) - value -= int32(1) << (w - 1) - L[i] = value - v.SetInt64(int64(value)) - k.Sub(&k, &v) - k.Rsh(&k, w-1) - } - L[i] = int32(k.Int64()) - return L -} - -// OmegaNAF obtains the window-w Non-Adjacent Form of a positive number n and -// 1 < w < 32. The returned slice L holds n = sum( L[i]*2^i ). -// -// Reference: -// - Alg.9 "Efficient arithmetic on Koblitz curves" by Solinas. -// http://doi.org/10.1023/A:1008306223194 -func OmegaNAF(n *big.Int, w uint) (L []int32) { - if n.Sign() < 0 { - panic("n must be positive") - } - if w <= 1 || w >= 32 { - panic("Verify that 1 < w < 32") - } - - L = make([]int32, n.BitLen()+1) - var k, v big.Int - k.Set(n) - - i := 0 - for ; k.Sign() > 0; i++ { - value := int32(0) - if k.Bit(0) == 1 { - words := k.Bits() - value = int32(words[0] & ((1 << w) - 1)) - if value >= (int32(1) << (w - 1)) { - value -= int32(1) << w - } - v.SetInt64(int64(value)) - k.Sub(&k, &v) - } - L[i] = value - k.Rsh(&k, 1) - } - return L[:i] -} diff --git a/vendor/github.com/cloudflare/circl/sign/ed25519/ed25519.go b/vendor/github.com/cloudflare/circl/sign/ed25519/ed25519.go deleted file mode 100644 index 2c73c26fb1f..00000000000 --- a/vendor/github.com/cloudflare/circl/sign/ed25519/ed25519.go +++ /dev/null @@ -1,453 +0,0 @@ -// Package ed25519 implements Ed25519 signature scheme as described in RFC-8032. -// -// This package provides optimized implementations of the three signature -// variants and maintaining closer compatibility with crypto/ed25519. -// -// | Scheme Name | Sign Function | Verification | Context | -// |-------------|-------------------|---------------|-------------------| -// | Ed25519 | Sign | Verify | None | -// | Ed25519Ph | SignPh | VerifyPh | Yes, can be empty | -// | Ed25519Ctx | SignWithCtx | VerifyWithCtx | Yes, non-empty | -// | All above | (PrivateKey).Sign | VerifyAny | As above | -// -// Specific functions for sign and verify are defined. A generic signing -// function for all schemes is available through the crypto.Signer interface, -// which is implemented by the PrivateKey type. A correspond all-in-one -// verification method is provided by the VerifyAny function. -// -// Signing with Ed25519Ph or Ed25519Ctx requires a context string for domain -// separation. This parameter is passed using a SignerOptions struct defined -// in this package. While Ed25519Ph accepts an empty context, Ed25519Ctx -// enforces non-empty context strings. -// -// # Compatibility with crypto.ed25519 -// -// These functions are compatible with the “Ed25519” function defined in -// RFC-8032. However, unlike RFC 8032's formulation, this package's private -// key representation includes a public key suffix to make multiple signing -// operations with the same key more efficient. This package refers to the -// RFC-8032 private key as the “seed”. -// -// References -// -// - RFC-8032: https://rfc-editor.org/rfc/rfc8032.txt -// - Ed25519: https://ed25519.cr.yp.to/ -// - EdDSA: High-speed high-security signatures. https://doi.org/10.1007/s13389-012-0027-1 -package ed25519 - -import ( - "bytes" - "crypto" - cryptoRand "crypto/rand" - "crypto/sha512" - "crypto/subtle" - "errors" - "fmt" - "io" - "strconv" - - "github.com/cloudflare/circl/sign" -) - -const ( - // ContextMaxSize is the maximum length (in bytes) allowed for context. - ContextMaxSize = 255 - // PublicKeySize is the size, in bytes, of public keys as used in this package. - PublicKeySize = 32 - // PrivateKeySize is the size, in bytes, of private keys as used in this package. - PrivateKeySize = 64 - // SignatureSize is the size, in bytes, of signatures generated and verified by this package. - SignatureSize = 64 - // SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032. - SeedSize = 32 -) - -const ( - paramB = 256 / 8 // Size of keys in bytes. -) - -// SignerOptions implements crypto.SignerOpts and augments with parameters -// that are specific to the Ed25519 signature schemes. -type SignerOptions struct { - // Hash must be crypto.Hash(0) for Ed25519/Ed25519ctx, or crypto.SHA512 - // for Ed25519ph. - crypto.Hash - - // Context is an optional domain separation string for Ed25519ph and a - // must for Ed25519ctx. Its length must be less or equal than 255 bytes. - Context string - - // Scheme is an identifier for choosing a signature scheme. The zero value - // is ED25519. - Scheme SchemeID -} - -// SchemeID is an identifier for each signature scheme. -type SchemeID uint - -const ( - ED25519 SchemeID = iota - ED25519Ph - ED25519Ctx -) - -// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer. -type PrivateKey []byte - -// Equal reports whether priv and x have the same value. -func (priv PrivateKey) Equal(x crypto.PrivateKey) bool { - xx, ok := x.(PrivateKey) - return ok && subtle.ConstantTimeCompare(priv, xx) == 1 -} - -// Public returns the PublicKey corresponding to priv. -func (priv PrivateKey) Public() crypto.PublicKey { - publicKey := make(PublicKey, PublicKeySize) - copy(publicKey, priv[SeedSize:]) - return publicKey -} - -// Seed returns the private key seed corresponding to priv. It is provided for -// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds -// in this package. -func (priv PrivateKey) Seed() []byte { - seed := make([]byte, SeedSize) - copy(seed, priv[:SeedSize]) - return seed -} - -func (priv PrivateKey) Scheme() sign.Scheme { return sch } - -func (pub PublicKey) Scheme() sign.Scheme { return sch } - -func (priv PrivateKey) MarshalBinary() (data []byte, err error) { - privateKey := make(PrivateKey, PrivateKeySize) - copy(privateKey, priv) - return privateKey, nil -} - -func (pub PublicKey) MarshalBinary() (data []byte, err error) { - publicKey := make(PublicKey, PublicKeySize) - copy(publicKey, pub) - return publicKey, nil -} - -// Equal reports whether pub and x have the same value. -func (pub PublicKey) Equal(x crypto.PublicKey) bool { - xx, ok := x.(PublicKey) - return ok && bytes.Equal(pub, xx) -} - -// Sign creates a signature of a message with priv key. -// This function is compatible with crypto.ed25519 and also supports the -// three signature variants defined in RFC-8032, namely Ed25519 (or pure -// EdDSA), Ed25519Ph, and Ed25519Ctx. -// The opts.HashFunc() must return zero to specify either Ed25519 or Ed25519Ctx -// variant. This can be achieved by passing crypto.Hash(0) as the value for -// opts. -// The opts.HashFunc() must return SHA512 to specify the Ed25519Ph variant. -// This can be achieved by passing crypto.SHA512 as the value for opts. -// Use a SignerOptions struct (defined in this package) to pass a context -// string for signing. -func (priv PrivateKey) Sign( - rand io.Reader, - message []byte, - opts crypto.SignerOpts, -) (signature []byte, err error) { - var ctx string - var scheme SchemeID - if o, ok := opts.(SignerOptions); ok { - ctx = o.Context - scheme = o.Scheme - } - - switch true { - case scheme == ED25519 && opts.HashFunc() == crypto.Hash(0): - return Sign(priv, message), nil - case scheme == ED25519Ph && opts.HashFunc() == crypto.SHA512: - return SignPh(priv, message, ctx), nil - case scheme == ED25519Ctx && opts.HashFunc() == crypto.Hash(0) && len(ctx) > 0: - return SignWithCtx(priv, message, ctx), nil - default: - return nil, errors.New("ed25519: bad hash algorithm") - } -} - -// GenerateKey generates a public/private key pair using entropy from rand. -// If rand is nil, crypto/rand.Reader will be used. -func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) { - if rand == nil { - rand = cryptoRand.Reader - } - - seed := make([]byte, SeedSize) - if _, err := io.ReadFull(rand, seed); err != nil { - return nil, nil, err - } - - privateKey := NewKeyFromSeed(seed) - publicKey := make(PublicKey, PublicKeySize) - copy(publicKey, privateKey[SeedSize:]) - - return publicKey, privateKey, nil -} - -// NewKeyFromSeed calculates a private key from a seed. It will panic if -// len(seed) is not SeedSize. This function is provided for interoperability -// with RFC 8032. RFC 8032's private keys correspond to seeds in this -// package. -func NewKeyFromSeed(seed []byte) PrivateKey { - privateKey := make(PrivateKey, PrivateKeySize) - newKeyFromSeed(privateKey, seed) - return privateKey -} - -func newKeyFromSeed(privateKey, seed []byte) { - if l := len(seed); l != SeedSize { - panic("ed25519: bad seed length: " + strconv.Itoa(l)) - } - var P pointR1 - k := sha512.Sum512(seed) - clamp(k[:]) - reduceModOrder(k[:paramB], false) - P.fixedMult(k[:paramB]) - copy(privateKey[:SeedSize], seed) - _ = P.ToBytes(privateKey[SeedSize:]) -} - -func signAll(signature []byte, privateKey PrivateKey, message, ctx []byte, preHash bool) { - if l := len(privateKey); l != PrivateKeySize { - panic("ed25519: bad private key length: " + strconv.Itoa(l)) - } - - H := sha512.New() - var PHM []byte - - if preHash { - _, _ = H.Write(message) - PHM = H.Sum(nil) - H.Reset() - } else { - PHM = message - } - - // 1. Hash the 32-byte private key using SHA-512. - _, _ = H.Write(privateKey[:SeedSize]) - h := H.Sum(nil) - clamp(h[:]) - prefix, s := h[paramB:], h[:paramB] - - // 2. Compute SHA-512(dom2(F, C) || prefix || PH(M)) - H.Reset() - - writeDom(H, ctx, preHash) - - _, _ = H.Write(prefix) - _, _ = H.Write(PHM) - r := H.Sum(nil) - reduceModOrder(r[:], true) - - // 3. Compute the point [r]B. - var P pointR1 - P.fixedMult(r[:paramB]) - R := (&[paramB]byte{})[:] - if err := P.ToBytes(R); err != nil { - panic(err) - } - - // 4. Compute SHA512(dom2(F, C) || R || A || PH(M)). - H.Reset() - - writeDom(H, ctx, preHash) - - _, _ = H.Write(R) - _, _ = H.Write(privateKey[SeedSize:]) - _, _ = H.Write(PHM) - hRAM := H.Sum(nil) - - reduceModOrder(hRAM[:], true) - - // 5. Compute S = (r + k * s) mod order. - S := (&[paramB]byte{})[:] - calculateS(S, r[:paramB], hRAM[:paramB], s) - - // 6. The signature is the concatenation of R and S. - copy(signature[:paramB], R[:]) - copy(signature[paramB:], S[:]) -} - -// Sign signs the message with privateKey and returns a signature. -// This function supports the signature variant defined in RFC-8032: Ed25519, -// also known as the pure version of EdDSA. -// It will panic if len(privateKey) is not PrivateKeySize. -func Sign(privateKey PrivateKey, message []byte) []byte { - signature := make([]byte, SignatureSize) - signAll(signature, privateKey, message, []byte(""), false) - return signature -} - -// SignPh creates a signature of a message with private key and context. -// This function supports the signature variant defined in RFC-8032: Ed25519ph, -// meaning it internally hashes the message using SHA-512, and optionally -// accepts a context string. -// It will panic if len(privateKey) is not PrivateKeySize. -// Context could be passed to this function, which length should be no more than -// ContextMaxSize=255. It can be empty. -func SignPh(privateKey PrivateKey, message []byte, ctx string) []byte { - if len(ctx) > ContextMaxSize { - panic(fmt.Errorf("ed25519: bad context length: %v", len(ctx))) - } - - signature := make([]byte, SignatureSize) - signAll(signature, privateKey, message, []byte(ctx), true) - return signature -} - -// SignWithCtx creates a signature of a message with private key and context. -// This function supports the signature variant defined in RFC-8032: Ed25519ctx, -// meaning it accepts a non-empty context string. -// It will panic if len(privateKey) is not PrivateKeySize. -// Context must be passed to this function, which length should be no more than -// ContextMaxSize=255 and cannot be empty. -func SignWithCtx(privateKey PrivateKey, message []byte, ctx string) []byte { - if len(ctx) == 0 || len(ctx) > ContextMaxSize { - panic(fmt.Errorf("ed25519: bad context length: %v > %v", len(ctx), ContextMaxSize)) - } - - signature := make([]byte, SignatureSize) - signAll(signature, privateKey, message, []byte(ctx), false) - return signature -} - -func verify(public PublicKey, message, signature, ctx []byte, preHash bool) bool { - if len(public) != PublicKeySize || - len(signature) != SignatureSize || - !isLessThanOrder(signature[paramB:]) { - return false - } - - var P pointR1 - if ok := P.FromBytes(public); !ok { - return false - } - - H := sha512.New() - var PHM []byte - - if preHash { - _, _ = H.Write(message) - PHM = H.Sum(nil) - H.Reset() - } else { - PHM = message - } - - R := signature[:paramB] - - writeDom(H, ctx, preHash) - - _, _ = H.Write(R) - _, _ = H.Write(public) - _, _ = H.Write(PHM) - hRAM := H.Sum(nil) - reduceModOrder(hRAM[:], true) - - var Q pointR1 - encR := (&[paramB]byte{})[:] - P.neg() - Q.doubleMult(&P, signature[paramB:], hRAM[:paramB]) - _ = Q.ToBytes(encR) - return bytes.Equal(R, encR) -} - -// VerifyAny returns true if the signature is valid. Failure cases are invalid -// signature, or when the public key cannot be decoded. -// This function supports all the three signature variants defined in RFC-8032, -// namely Ed25519 (or pure EdDSA), Ed25519Ph, and Ed25519Ctx. -// The opts.HashFunc() must return zero to specify either Ed25519 or Ed25519Ctx -// variant. This can be achieved by passing crypto.Hash(0) as the value for opts. -// The opts.HashFunc() must return SHA512 to specify the Ed25519Ph variant. -// This can be achieved by passing crypto.SHA512 as the value for opts. -// Use a SignerOptions struct to pass a context string for signing. -func VerifyAny(public PublicKey, message, signature []byte, opts crypto.SignerOpts) bool { - var ctx string - var scheme SchemeID - if o, ok := opts.(SignerOptions); ok { - ctx = o.Context - scheme = o.Scheme - } - - switch true { - case scheme == ED25519 && opts.HashFunc() == crypto.Hash(0): - return Verify(public, message, signature) - case scheme == ED25519Ph && opts.HashFunc() == crypto.SHA512: - return VerifyPh(public, message, signature, ctx) - case scheme == ED25519Ctx && opts.HashFunc() == crypto.Hash(0) && len(ctx) > 0: - return VerifyWithCtx(public, message, signature, ctx) - default: - return false - } -} - -// Verify returns true if the signature is valid. Failure cases are invalid -// signature, or when the public key cannot be decoded. -// This function supports the signature variant defined in RFC-8032: Ed25519, -// also known as the pure version of EdDSA. -func Verify(public PublicKey, message, signature []byte) bool { - return verify(public, message, signature, []byte(""), false) -} - -// VerifyPh returns true if the signature is valid. Failure cases are invalid -// signature, or when the public key cannot be decoded. -// This function supports the signature variant defined in RFC-8032: Ed25519ph, -// meaning it internally hashes the message using SHA-512. -// Context could be passed to this function, which length should be no more than -// 255. It can be empty. -func VerifyPh(public PublicKey, message, signature []byte, ctx string) bool { - return verify(public, message, signature, []byte(ctx), true) -} - -// VerifyWithCtx returns true if the signature is valid. Failure cases are invalid -// signature, or when the public key cannot be decoded, or when context is -// not provided. -// This function supports the signature variant defined in RFC-8032: Ed25519ctx, -// meaning it does not handle prehashed messages. Non-empty context string must be -// provided, and must not be more than 255 of length. -func VerifyWithCtx(public PublicKey, message, signature []byte, ctx string) bool { - if len(ctx) == 0 || len(ctx) > ContextMaxSize { - return false - } - - return verify(public, message, signature, []byte(ctx), false) -} - -func clamp(k []byte) { - k[0] &= 248 - k[paramB-1] = (k[paramB-1] & 127) | 64 -} - -// isLessThanOrder returns true if 0 <= x < order. -func isLessThanOrder(x []byte) bool { - i := len(order) - 1 - for i > 0 && x[i] == order[i] { - i-- - } - return x[i] < order[i] -} - -func writeDom(h io.Writer, ctx []byte, preHash bool) { - dom2 := "SigEd25519 no Ed25519 collisions" - - if len(ctx) > 0 { - _, _ = h.Write([]byte(dom2)) - if preHash { - _, _ = h.Write([]byte{byte(0x01), byte(len(ctx))}) - } else { - _, _ = h.Write([]byte{byte(0x00), byte(len(ctx))}) - } - _, _ = h.Write(ctx) - } else if preHash { - _, _ = h.Write([]byte(dom2)) - _, _ = h.Write([]byte{0x01, 0x00}) - } -} diff --git a/vendor/github.com/cloudflare/circl/sign/ed25519/modular.go b/vendor/github.com/cloudflare/circl/sign/ed25519/modular.go deleted file mode 100644 index 10efafdcafb..00000000000 --- a/vendor/github.com/cloudflare/circl/sign/ed25519/modular.go +++ /dev/null @@ -1,175 +0,0 @@ -package ed25519 - -import ( - "encoding/binary" - "math/bits" -) - -var order = [paramB]byte{ - 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, - 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, -} - -// isLessThan returns true if 0 <= x < y, and assumes that slices have the same length. -func isLessThan(x, y []byte) bool { - i := len(x) - 1 - for i > 0 && x[i] == y[i] { - i-- - } - return x[i] < y[i] -} - -// reduceModOrder calculates k = k mod order of the curve. -func reduceModOrder(k []byte, is512Bit bool) { - var X [((2 * paramB) * 8) / 64]uint64 - numWords := len(k) >> 3 - for i := 0; i < numWords; i++ { - X[i] = binary.LittleEndian.Uint64(k[i*8 : (i+1)*8]) - } - red512(&X, is512Bit) - for i := 0; i < numWords; i++ { - binary.LittleEndian.PutUint64(k[i*8:(i+1)*8], X[i]) - } -} - -// red512 calculates x = x mod Order of the curve. -func red512(x *[8]uint64, full bool) { - // Implementation of Algs.(14.47)+(14.52) of Handbook of Applied - // Cryptography, by A. Menezes, P. van Oorschot, and S. Vanstone. - const ( - ell0 = uint64(0x5812631a5cf5d3ed) - ell1 = uint64(0x14def9dea2f79cd6) - ell160 = uint64(0x812631a5cf5d3ed0) - ell161 = uint64(0x4def9dea2f79cd65) - ell162 = uint64(0x0000000000000001) - ) - - var c0, c1, c2, c3 uint64 - r0, r1, r2, r3, r4 := x[0], x[1], x[2], x[3], uint64(0) - - if full { - q0, q1, q2, q3 := x[4], x[5], x[6], x[7] - - for i := 0; i < 3; i++ { - h0, s0 := bits.Mul64(q0, ell160) - h1, s1 := bits.Mul64(q1, ell160) - h2, s2 := bits.Mul64(q2, ell160) - h3, s3 := bits.Mul64(q3, ell160) - - s1, c0 = bits.Add64(h0, s1, 0) - s2, c1 = bits.Add64(h1, s2, c0) - s3, c2 = bits.Add64(h2, s3, c1) - s4, _ := bits.Add64(h3, 0, c2) - - h0, l0 := bits.Mul64(q0, ell161) - h1, l1 := bits.Mul64(q1, ell161) - h2, l2 := bits.Mul64(q2, ell161) - h3, l3 := bits.Mul64(q3, ell161) - - l1, c0 = bits.Add64(h0, l1, 0) - l2, c1 = bits.Add64(h1, l2, c0) - l3, c2 = bits.Add64(h2, l3, c1) - l4, _ := bits.Add64(h3, 0, c2) - - s1, c0 = bits.Add64(s1, l0, 0) - s2, c1 = bits.Add64(s2, l1, c0) - s3, c2 = bits.Add64(s3, l2, c1) - s4, c3 = bits.Add64(s4, l3, c2) - s5, s6 := bits.Add64(l4, 0, c3) - - s2, c0 = bits.Add64(s2, q0, 0) - s3, c1 = bits.Add64(s3, q1, c0) - s4, c2 = bits.Add64(s4, q2, c1) - s5, c3 = bits.Add64(s5, q3, c2) - s6, s7 := bits.Add64(s6, 0, c3) - - q := q0 | q1 | q2 | q3 - m := -((q | -q) >> 63) // if q=0 then m=0...0 else m=1..1 - s0 &= m - s1 &= m - s2 &= m - s3 &= m - q0, q1, q2, q3 = s4, s5, s6, s7 - - if (i+1)%2 == 0 { - r0, c0 = bits.Add64(r0, s0, 0) - r1, c1 = bits.Add64(r1, s1, c0) - r2, c2 = bits.Add64(r2, s2, c1) - r3, c3 = bits.Add64(r3, s3, c2) - r4, _ = bits.Add64(r4, 0, c3) - } else { - r0, c0 = bits.Sub64(r0, s0, 0) - r1, c1 = bits.Sub64(r1, s1, c0) - r2, c2 = bits.Sub64(r2, s2, c1) - r3, c3 = bits.Sub64(r3, s3, c2) - r4, _ = bits.Sub64(r4, 0, c3) - } - } - - m := -(r4 >> 63) - r0, c0 = bits.Add64(r0, m&ell160, 0) - r1, c1 = bits.Add64(r1, m&ell161, c0) - r2, c2 = bits.Add64(r2, m&ell162, c1) - r3, c3 = bits.Add64(r3, 0, c2) - r4, _ = bits.Add64(r4, m&1, c3) - x[4], x[5], x[6], x[7] = 0, 0, 0, 0 - } - - q0 := (r4 << 4) | (r3 >> 60) - r3 &= (uint64(1) << 60) - 1 - - h0, s0 := bits.Mul64(ell0, q0) - h1, s1 := bits.Mul64(ell1, q0) - s1, c0 = bits.Add64(h0, s1, 0) - s2, _ := bits.Add64(h1, 0, c0) - - r0, c0 = bits.Sub64(r0, s0, 0) - r1, c1 = bits.Sub64(r1, s1, c0) - r2, c2 = bits.Sub64(r2, s2, c1) - r3, _ = bits.Sub64(r3, 0, c2) - - x[0], x[1], x[2], x[3] = r0, r1, r2, r3 -} - -// calculateS performs s = r+k*a mod Order of the curve. -func calculateS(s, r, k, a []byte) { - K := [4]uint64{ - binary.LittleEndian.Uint64(k[0*8 : 1*8]), - binary.LittleEndian.Uint64(k[1*8 : 2*8]), - binary.LittleEndian.Uint64(k[2*8 : 3*8]), - binary.LittleEndian.Uint64(k[3*8 : 4*8]), - } - S := [8]uint64{ - binary.LittleEndian.Uint64(r[0*8 : 1*8]), - binary.LittleEndian.Uint64(r[1*8 : 2*8]), - binary.LittleEndian.Uint64(r[2*8 : 3*8]), - binary.LittleEndian.Uint64(r[3*8 : 4*8]), - } - var c3 uint64 - for i := range K { - ai := binary.LittleEndian.Uint64(a[i*8 : (i+1)*8]) - - h0, l0 := bits.Mul64(K[0], ai) - h1, l1 := bits.Mul64(K[1], ai) - h2, l2 := bits.Mul64(K[2], ai) - h3, l3 := bits.Mul64(K[3], ai) - - l1, c0 := bits.Add64(h0, l1, 0) - l2, c1 := bits.Add64(h1, l2, c0) - l3, c2 := bits.Add64(h2, l3, c1) - l4, _ := bits.Add64(h3, 0, c2) - - S[i+0], c0 = bits.Add64(S[i+0], l0, 0) - S[i+1], c1 = bits.Add64(S[i+1], l1, c0) - S[i+2], c2 = bits.Add64(S[i+2], l2, c1) - S[i+3], c3 = bits.Add64(S[i+3], l3, c2) - S[i+4], _ = bits.Add64(S[i+4], l4, c3) - } - red512(&S, true) - binary.LittleEndian.PutUint64(s[0*8:1*8], S[0]) - binary.LittleEndian.PutUint64(s[1*8:2*8], S[1]) - binary.LittleEndian.PutUint64(s[2*8:3*8], S[2]) - binary.LittleEndian.PutUint64(s[3*8:4*8], S[3]) -} diff --git a/vendor/github.com/cloudflare/circl/sign/ed25519/mult.go b/vendor/github.com/cloudflare/circl/sign/ed25519/mult.go deleted file mode 100644 index 3216aae303c..00000000000 --- a/vendor/github.com/cloudflare/circl/sign/ed25519/mult.go +++ /dev/null @@ -1,180 +0,0 @@ -package ed25519 - -import ( - "crypto/subtle" - "encoding/binary" - "math/bits" - - "github.com/cloudflare/circl/internal/conv" - "github.com/cloudflare/circl/math" - fp "github.com/cloudflare/circl/math/fp25519" -) - -var paramD = fp.Elt{ - 0xa3, 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75, - 0xab, 0xd8, 0x41, 0x41, 0x4d, 0x0a, 0x70, 0x00, - 0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c, - 0x73, 0xfe, 0x6f, 0x2b, 0xee, 0x6c, 0x03, 0x52, -} - -// mLSBRecoding parameters. -const ( - fxT = 257 - fxV = 2 - fxW = 3 - fx2w1 = 1 << (uint(fxW) - 1) - numWords64 = (paramB * 8 / 64) -) - -// mLSBRecoding is the odd-only modified LSB-set. -// -// Reference: -// -// "Efficient and secure algorithms for GLV-based scalar multiplication and -// their implementation on GLV–GLS curves" by (Faz-Hernandez et al.) -// http://doi.org/10.1007/s13389-014-0085-7. -func mLSBRecoding(L []int8, k []byte) { - const ee = (fxT + fxW*fxV - 1) / (fxW * fxV) - const dd = ee * fxV - const ll = dd * fxW - if len(L) == (ll + 1) { - var m [numWords64 + 1]uint64 - for i := 0; i < numWords64; i++ { - m[i] = binary.LittleEndian.Uint64(k[8*i : 8*i+8]) - } - condAddOrderN(&m) - L[dd-1] = 1 - for i := 0; i < dd-1; i++ { - kip1 := (m[(i+1)/64] >> (uint(i+1) % 64)) & 0x1 - L[i] = int8(kip1<<1) - 1 - } - { // right-shift by d - right := uint(dd % 64) - left := uint(64) - right - lim := ((numWords64+1)*64 - dd) / 64 - j := dd / 64 - for i := 0; i < lim; i++ { - m[i] = (m[i+j] >> right) | (m[i+j+1] << left) - } - m[lim] = m[lim+j] >> right - } - for i := dd; i < ll; i++ { - L[i] = L[i%dd] * int8(m[0]&0x1) - div2subY(m[:], int64(L[i]>>1), numWords64) - } - L[ll] = int8(m[0]) - } -} - -// absolute returns always a positive value. -func absolute(x int32) int32 { - mask := x >> 31 - return (x + mask) ^ mask -} - -// condAddOrderN updates x = x+order if x is even, otherwise x remains unchanged. -func condAddOrderN(x *[numWords64 + 1]uint64) { - isOdd := (x[0] & 0x1) - 1 - c := uint64(0) - for i := 0; i < numWords64; i++ { - orderWord := binary.LittleEndian.Uint64(order[8*i : 8*i+8]) - o := isOdd & orderWord - x0, c0 := bits.Add64(x[i], o, c) - x[i] = x0 - c = c0 - } - x[numWords64], _ = bits.Add64(x[numWords64], 0, c) -} - -// div2subY update x = (x/2) - y. -func div2subY(x []uint64, y int64, l int) { - s := uint64(y >> 63) - for i := 0; i < l-1; i++ { - x[i] = (x[i] >> 1) | (x[i+1] << 63) - } - x[l-1] = (x[l-1] >> 1) - - b := uint64(0) - x0, b0 := bits.Sub64(x[0], uint64(y), b) - x[0] = x0 - b = b0 - for i := 1; i < l-1; i++ { - x0, b0 := bits.Sub64(x[i], s, b) - x[i] = x0 - b = b0 - } - x[l-1], _ = bits.Sub64(x[l-1], s, b) -} - -func (P *pointR1) fixedMult(scalar []byte) { - if len(scalar) != paramB { - panic("wrong scalar size") - } - const ee = (fxT + fxW*fxV - 1) / (fxW * fxV) - const dd = ee * fxV - const ll = dd * fxW - - L := make([]int8, ll+1) - mLSBRecoding(L[:], scalar) - S := &pointR3{} - P.SetIdentity() - for ii := ee - 1; ii >= 0; ii-- { - P.double() - for j := 0; j < fxV; j++ { - dig := L[fxW*dd-j*ee+ii-ee] - for i := (fxW-1)*dd - j*ee + ii - ee; i >= (2*dd - j*ee + ii - ee); i = i - dd { - dig = 2*dig + L[i] - } - idx := absolute(int32(dig)) - sig := L[dd-j*ee+ii-ee] - Tabj := &tabSign[fxV-j-1] - for k := 0; k < fx2w1; k++ { - S.cmov(&Tabj[k], subtle.ConstantTimeEq(int32(k), idx)) - } - S.cneg(subtle.ConstantTimeEq(int32(sig), -1)) - P.mixAdd(S) - } - } -} - -const ( - omegaFix = 7 - omegaVar = 5 -) - -// doubleMult returns P=mG+nQ. -func (P *pointR1) doubleMult(Q *pointR1, m, n []byte) { - nafFix := math.OmegaNAF(conv.BytesLe2BigInt(m), omegaFix) - nafVar := math.OmegaNAF(conv.BytesLe2BigInt(n), omegaVar) - - if len(nafFix) > len(nafVar) { - nafVar = append(nafVar, make([]int32, len(nafFix)-len(nafVar))...) - } else if len(nafFix) < len(nafVar) { - nafFix = append(nafFix, make([]int32, len(nafVar)-len(nafFix))...) - } - - var TabQ [1 << (omegaVar - 2)]pointR2 - Q.oddMultiples(TabQ[:]) - P.SetIdentity() - for i := len(nafFix) - 1; i >= 0; i-- { - P.double() - // Generator point - if nafFix[i] != 0 { - idxM := absolute(nafFix[i]) >> 1 - R := tabVerif[idxM] - if nafFix[i] < 0 { - R.neg() - } - P.mixAdd(&R) - } - // Variable input point - if nafVar[i] != 0 { - idxN := absolute(nafVar[i]) >> 1 - S := TabQ[idxN] - if nafVar[i] < 0 { - S.neg() - } - P.add(&S) - } - } -} diff --git a/vendor/github.com/cloudflare/circl/sign/ed25519/point.go b/vendor/github.com/cloudflare/circl/sign/ed25519/point.go deleted file mode 100644 index d1c3b146b72..00000000000 --- a/vendor/github.com/cloudflare/circl/sign/ed25519/point.go +++ /dev/null @@ -1,195 +0,0 @@ -package ed25519 - -import fp "github.com/cloudflare/circl/math/fp25519" - -type ( - pointR1 struct{ x, y, z, ta, tb fp.Elt } - pointR2 struct { - pointR3 - z2 fp.Elt - } -) -type pointR3 struct{ addYX, subYX, dt2 fp.Elt } - -func (P *pointR1) neg() { - fp.Neg(&P.x, &P.x) - fp.Neg(&P.ta, &P.ta) -} - -func (P *pointR1) SetIdentity() { - P.x = fp.Elt{} - fp.SetOne(&P.y) - fp.SetOne(&P.z) - P.ta = fp.Elt{} - P.tb = fp.Elt{} -} - -func (P *pointR1) toAffine() { - fp.Inv(&P.z, &P.z) - fp.Mul(&P.x, &P.x, &P.z) - fp.Mul(&P.y, &P.y, &P.z) - fp.Modp(&P.x) - fp.Modp(&P.y) - fp.SetOne(&P.z) - P.ta = P.x - P.tb = P.y -} - -func (P *pointR1) ToBytes(k []byte) error { - P.toAffine() - var x [fp.Size]byte - err := fp.ToBytes(k[:fp.Size], &P.y) - if err != nil { - return err - } - err = fp.ToBytes(x[:], &P.x) - if err != nil { - return err - } - b := x[0] & 1 - k[paramB-1] = k[paramB-1] | (b << 7) - return nil -} - -func (P *pointR1) FromBytes(k []byte) bool { - if len(k) != paramB { - panic("wrong size") - } - signX := k[paramB-1] >> 7 - copy(P.y[:], k[:fp.Size]) - P.y[fp.Size-1] &= 0x7F - p := fp.P() - if !isLessThan(P.y[:], p[:]) { - return false - } - - one, u, v := &fp.Elt{}, &fp.Elt{}, &fp.Elt{} - fp.SetOne(one) - fp.Sqr(u, &P.y) // u = y^2 - fp.Mul(v, u, ¶mD) // v = dy^2 - fp.Sub(u, u, one) // u = y^2-1 - fp.Add(v, v, one) // v = dy^2+1 - isQR := fp.InvSqrt(&P.x, u, v) // x = sqrt(u/v) - if !isQR { - return false - } - fp.Modp(&P.x) // x = x mod p - if fp.IsZero(&P.x) && signX == 1 { - return false - } - if signX != (P.x[0] & 1) { - fp.Neg(&P.x, &P.x) - } - P.ta = P.x - P.tb = P.y - fp.SetOne(&P.z) - return true -} - -// double calculates 2P for curves with A=-1. -func (P *pointR1) double() { - Px, Py, Pz, Pta, Ptb := &P.x, &P.y, &P.z, &P.ta, &P.tb - a, b, c, e, f, g, h := Px, Py, Pz, Pta, Px, Py, Ptb - fp.Add(e, Px, Py) // x+y - fp.Sqr(a, Px) // A = x^2 - fp.Sqr(b, Py) // B = y^2 - fp.Sqr(c, Pz) // z^2 - fp.Add(c, c, c) // C = 2*z^2 - fp.Add(h, a, b) // H = A+B - fp.Sqr(e, e) // (x+y)^2 - fp.Sub(e, e, h) // E = (x+y)^2-A-B - fp.Sub(g, b, a) // G = B-A - fp.Sub(f, c, g) // F = C-G - fp.Mul(Pz, f, g) // Z = F * G - fp.Mul(Px, e, f) // X = E * F - fp.Mul(Py, g, h) // Y = G * H, T = E * H -} - -func (P *pointR1) mixAdd(Q *pointR3) { - fp.Add(&P.z, &P.z, &P.z) // D = 2*z1 - P.coreAddition(Q) -} - -func (P *pointR1) add(Q *pointR2) { - fp.Mul(&P.z, &P.z, &Q.z2) // D = 2*z1*z2 - P.coreAddition(&Q.pointR3) -} - -// coreAddition calculates P=P+Q for curves with A=-1. -func (P *pointR1) coreAddition(Q *pointR3) { - Px, Py, Pz, Pta, Ptb := &P.x, &P.y, &P.z, &P.ta, &P.tb - addYX2, subYX2, dt2 := &Q.addYX, &Q.subYX, &Q.dt2 - a, b, c, d, e, f, g, h := Px, Py, &fp.Elt{}, Pz, Pta, Px, Py, Ptb - fp.Mul(c, Pta, Ptb) // t1 = ta*tb - fp.Sub(h, Py, Px) // y1-x1 - fp.Add(b, Py, Px) // y1+x1 - fp.Mul(a, h, subYX2) // A = (y1-x1)*(y2-x2) - fp.Mul(b, b, addYX2) // B = (y1+x1)*(y2+x2) - fp.Mul(c, c, dt2) // C = 2*D*t1*t2 - fp.Sub(e, b, a) // E = B-A - fp.Add(h, b, a) // H = B+A - fp.Sub(f, d, c) // F = D-C - fp.Add(g, d, c) // G = D+C - fp.Mul(Pz, f, g) // Z = F * G - fp.Mul(Px, e, f) // X = E * F - fp.Mul(Py, g, h) // Y = G * H, T = E * H -} - -func (P *pointR1) oddMultiples(T []pointR2) { - var R pointR2 - n := len(T) - T[0].fromR1(P) - _2P := *P - _2P.double() - R.fromR1(&_2P) - for i := 1; i < n; i++ { - P.add(&R) - T[i].fromR1(P) - } -} - -func (P *pointR1) isEqual(Q *pointR1) bool { - l, r := &fp.Elt{}, &fp.Elt{} - fp.Mul(l, &P.x, &Q.z) - fp.Mul(r, &Q.x, &P.z) - fp.Sub(l, l, r) - b := fp.IsZero(l) - fp.Mul(l, &P.y, &Q.z) - fp.Mul(r, &Q.y, &P.z) - fp.Sub(l, l, r) - b = b && fp.IsZero(l) - fp.Mul(l, &P.ta, &P.tb) - fp.Mul(l, l, &Q.z) - fp.Mul(r, &Q.ta, &Q.tb) - fp.Mul(r, r, &P.z) - fp.Sub(l, l, r) - b = b && fp.IsZero(l) - return b && !fp.IsZero(&P.z) && !fp.IsZero(&Q.z) -} - -func (P *pointR3) neg() { - P.addYX, P.subYX = P.subYX, P.addYX - fp.Neg(&P.dt2, &P.dt2) -} - -func (P *pointR2) fromR1(Q *pointR1) { - fp.Add(&P.addYX, &Q.y, &Q.x) - fp.Sub(&P.subYX, &Q.y, &Q.x) - fp.Mul(&P.dt2, &Q.ta, &Q.tb) - fp.Mul(&P.dt2, &P.dt2, ¶mD) - fp.Add(&P.dt2, &P.dt2, &P.dt2) - fp.Add(&P.z2, &Q.z, &Q.z) -} - -func (P *pointR3) cneg(b int) { - t := &fp.Elt{} - fp.Cswap(&P.addYX, &P.subYX, uint(b)) - fp.Neg(t, &P.dt2) - fp.Cmov(&P.dt2, t, uint(b)) -} - -func (P *pointR3) cmov(Q *pointR3, b int) { - fp.Cmov(&P.addYX, &Q.addYX, uint(b)) - fp.Cmov(&P.subYX, &Q.subYX, uint(b)) - fp.Cmov(&P.dt2, &Q.dt2, uint(b)) -} diff --git a/vendor/github.com/cloudflare/circl/sign/ed25519/pubkey.go b/vendor/github.com/cloudflare/circl/sign/ed25519/pubkey.go deleted file mode 100644 index c3505b67ace..00000000000 --- a/vendor/github.com/cloudflare/circl/sign/ed25519/pubkey.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -package ed25519 - -import cryptoEd25519 "crypto/ed25519" - -// PublicKey is the type of Ed25519 public keys. -type PublicKey cryptoEd25519.PublicKey diff --git a/vendor/github.com/cloudflare/circl/sign/ed25519/pubkey112.go b/vendor/github.com/cloudflare/circl/sign/ed25519/pubkey112.go deleted file mode 100644 index d57d86eff08..00000000000 --- a/vendor/github.com/cloudflare/circl/sign/ed25519/pubkey112.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !go1.13 -// +build !go1.13 - -package ed25519 - -// PublicKey is the type of Ed25519 public keys. -type PublicKey []byte diff --git a/vendor/github.com/cloudflare/circl/sign/ed25519/signapi.go b/vendor/github.com/cloudflare/circl/sign/ed25519/signapi.go deleted file mode 100644 index e4520f52034..00000000000 --- a/vendor/github.com/cloudflare/circl/sign/ed25519/signapi.go +++ /dev/null @@ -1,87 +0,0 @@ -package ed25519 - -import ( - "crypto/rand" - "encoding/asn1" - - "github.com/cloudflare/circl/sign" -) - -var sch sign.Scheme = &scheme{} - -// Scheme returns a signature interface. -func Scheme() sign.Scheme { return sch } - -type scheme struct{} - -func (*scheme) Name() string { return "Ed25519" } -func (*scheme) PublicKeySize() int { return PublicKeySize } -func (*scheme) PrivateKeySize() int { return PrivateKeySize } -func (*scheme) SignatureSize() int { return SignatureSize } -func (*scheme) SeedSize() int { return SeedSize } -func (*scheme) TLSIdentifier() uint { return 0x0807 } -func (*scheme) SupportsContext() bool { return false } -func (*scheme) Oid() asn1.ObjectIdentifier { - return asn1.ObjectIdentifier{1, 3, 101, 112} -} - -func (*scheme) GenerateKey() (sign.PublicKey, sign.PrivateKey, error) { - return GenerateKey(rand.Reader) -} - -func (*scheme) Sign( - sk sign.PrivateKey, - message []byte, - opts *sign.SignatureOpts, -) []byte { - priv, ok := sk.(PrivateKey) - if !ok { - panic(sign.ErrTypeMismatch) - } - if opts != nil && opts.Context != "" { - panic(sign.ErrContextNotSupported) - } - return Sign(priv, message) -} - -func (*scheme) Verify( - pk sign.PublicKey, - message, signature []byte, - opts *sign.SignatureOpts, -) bool { - pub, ok := pk.(PublicKey) - if !ok { - panic(sign.ErrTypeMismatch) - } - if opts != nil { - if opts.Context != "" { - panic(sign.ErrContextNotSupported) - } - } - return Verify(pub, message, signature) -} - -func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { - privateKey := NewKeyFromSeed(seed) - publicKey := make(PublicKey, PublicKeySize) - copy(publicKey, privateKey[SeedSize:]) - return publicKey, privateKey -} - -func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { - if len(buf) < PublicKeySize { - return nil, sign.ErrPubKeySize - } - pub := make(PublicKey, PublicKeySize) - copy(pub, buf[:PublicKeySize]) - return pub, nil -} - -func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (sign.PrivateKey, error) { - if len(buf) < PrivateKeySize { - return nil, sign.ErrPrivKeySize - } - priv := make(PrivateKey, PrivateKeySize) - copy(priv, buf[:PrivateKeySize]) - return priv, nil -} diff --git a/vendor/github.com/cloudflare/circl/sign/ed25519/tables.go b/vendor/github.com/cloudflare/circl/sign/ed25519/tables.go deleted file mode 100644 index 8763b426fc0..00000000000 --- a/vendor/github.com/cloudflare/circl/sign/ed25519/tables.go +++ /dev/null @@ -1,213 +0,0 @@ -package ed25519 - -import fp "github.com/cloudflare/circl/math/fp25519" - -var tabSign = [fxV][fx2w1]pointR3{ - { - pointR3{ - addYX: fp.Elt{0x85, 0x3b, 0x8c, 0xf5, 0xc6, 0x93, 0xbc, 0x2f, 0x19, 0x0e, 0x8c, 0xfb, 0xc6, 0x2d, 0x93, 0xcf, 0xc2, 0x42, 0x3d, 0x64, 0x98, 0x48, 0x0b, 0x27, 0x65, 0xba, 0xd4, 0x33, 0x3a, 0x9d, 0xcf, 0x07}, - subYX: fp.Elt{0x3e, 0x91, 0x40, 0xd7, 0x05, 0x39, 0x10, 0x9d, 0xb3, 0xbe, 0x40, 0xd1, 0x05, 0x9f, 0x39, 0xfd, 0x09, 0x8a, 0x8f, 0x68, 0x34, 0x84, 0xc1, 0xa5, 0x67, 0x12, 0xf8, 0x98, 0x92, 0x2f, 0xfd, 0x44}, - dt2: fp.Elt{0x68, 0xaa, 0x7a, 0x87, 0x05, 0x12, 0xc9, 0xab, 0x9e, 0xc4, 0xaa, 0xcc, 0x23, 0xe8, 0xd9, 0x26, 0x8c, 0x59, 0x43, 0xdd, 0xcb, 0x7d, 0x1b, 0x5a, 0xa8, 0x65, 0x0c, 0x9f, 0x68, 0x7b, 0x11, 0x6f}, - }, - { - addYX: fp.Elt{0x7c, 0xb0, 0x9e, 0xe6, 0xc5, 0xbf, 0xfa, 0x13, 0x8e, 0x0d, 0x22, 0xde, 0xc8, 0xd1, 0xce, 0x52, 0x02, 0xd5, 0x62, 0x31, 0x71, 0x0e, 0x8e, 0x9d, 0xb0, 0xd6, 0x00, 0xa5, 0x5a, 0x0e, 0xce, 0x72}, - subYX: fp.Elt{0x1a, 0x8e, 0x5c, 0xdc, 0xa4, 0xb3, 0x6c, 0x51, 0x18, 0xa0, 0x09, 0x80, 0x9a, 0x46, 0x33, 0xd5, 0xe0, 0x3c, 0x4d, 0x3b, 0xfc, 0x49, 0xa2, 0x43, 0x29, 0xe1, 0x29, 0xa9, 0x93, 0xea, 0x7c, 0x35}, - dt2: fp.Elt{0x08, 0x46, 0x6f, 0x68, 0x7f, 0x0b, 0x7c, 0x9e, 0xad, 0xba, 0x07, 0x61, 0x74, 0x83, 0x2f, 0xfc, 0x26, 0xd6, 0x09, 0xb9, 0x00, 0x34, 0x36, 0x4f, 0x01, 0xf3, 0x48, 0xdb, 0x43, 0xba, 0x04, 0x44}, - }, - { - addYX: fp.Elt{0x4c, 0xda, 0x0d, 0x13, 0x66, 0xfd, 0x82, 0x84, 0x9f, 0x75, 0x5b, 0xa2, 0x17, 0xfe, 0x34, 0xbf, 0x1f, 0xcb, 0xba, 0x90, 0x55, 0x80, 0x83, 0xfd, 0x63, 0xb9, 0x18, 0xf8, 0x5b, 0x5d, 0x94, 0x1e}, - subYX: fp.Elt{0xb9, 0xdb, 0x6c, 0x04, 0x88, 0x22, 0xd8, 0x79, 0x83, 0x2f, 0x8d, 0x65, 0x6b, 0xd2, 0xab, 0x1b, 0xdd, 0x65, 0xe5, 0x93, 0x63, 0xf8, 0xa2, 0xd8, 0x3c, 0xf1, 0x4b, 0xc5, 0x99, 0xd1, 0xf2, 0x12}, - dt2: fp.Elt{0x05, 0x4c, 0xb8, 0x3b, 0xfe, 0xf5, 0x9f, 0x2e, 0xd1, 0xb2, 0xb8, 0xff, 0xfe, 0x6d, 0xd9, 0x37, 0xe0, 0xae, 0xb4, 0x5a, 0x51, 0x80, 0x7e, 0x9b, 0x1d, 0xd1, 0x8d, 0x8c, 0x56, 0xb1, 0x84, 0x35}, - }, - { - addYX: fp.Elt{0x39, 0x71, 0x43, 0x34, 0xe3, 0x42, 0x45, 0xa1, 0xf2, 0x68, 0x71, 0xa7, 0xe8, 0x23, 0xfd, 0x9f, 0x86, 0x48, 0xff, 0xe5, 0x96, 0x74, 0xcf, 0x05, 0x49, 0xe2, 0xb3, 0x6c, 0x17, 0x77, 0x2f, 0x6d}, - subYX: fp.Elt{0x73, 0x3f, 0xc1, 0xc7, 0x6a, 0x66, 0xa1, 0x20, 0xdd, 0x11, 0xfb, 0x7a, 0x6e, 0xa8, 0x51, 0xb8, 0x3f, 0x9d, 0xa2, 0x97, 0x84, 0xb5, 0xc7, 0x90, 0x7c, 0xab, 0x48, 0xd6, 0x84, 0xa3, 0xd5, 0x1a}, - dt2: fp.Elt{0x63, 0x27, 0x3c, 0x49, 0x4b, 0xfc, 0x22, 0xf2, 0x0b, 0x50, 0xc2, 0x0f, 0xb4, 0x1f, 0x31, 0x0c, 0x2f, 0x53, 0xab, 0xaa, 0x75, 0x6f, 0xe0, 0x69, 0x39, 0x56, 0xe0, 0x3b, 0xb7, 0xa8, 0xbf, 0x45}, - }, - }, - { - { - addYX: fp.Elt{0x00, 0x45, 0xd9, 0x0d, 0x58, 0x03, 0xfc, 0x29, 0x93, 0xec, 0xbb, 0x6f, 0xa4, 0x7a, 0xd2, 0xec, 0xf8, 0xa7, 0xe2, 0xc2, 0x5f, 0x15, 0x0a, 0x13, 0xd5, 0xa1, 0x06, 0xb7, 0x1a, 0x15, 0x6b, 0x41}, - subYX: fp.Elt{0x85, 0x8c, 0xb2, 0x17, 0xd6, 0x3b, 0x0a, 0xd3, 0xea, 0x3b, 0x77, 0x39, 0xb7, 0x77, 0xd3, 0xc5, 0xbf, 0x5c, 0x6a, 0x1e, 0x8c, 0xe7, 0xc6, 0xc6, 0xc4, 0xb7, 0x2a, 0x8b, 0xf7, 0xb8, 0x61, 0x0d}, - dt2: fp.Elt{0xb0, 0x36, 0xc1, 0xe9, 0xef, 0xd7, 0xa8, 0x56, 0x20, 0x4b, 0xe4, 0x58, 0xcd, 0xe5, 0x07, 0xbd, 0xab, 0xe0, 0x57, 0x1b, 0xda, 0x2f, 0xe6, 0xaf, 0xd2, 0xe8, 0x77, 0x42, 0xf7, 0x2a, 0x1a, 0x19}, - }, - { - addYX: fp.Elt{0x6a, 0x6d, 0x6d, 0xd1, 0xfa, 0xf5, 0x03, 0x30, 0xbd, 0x6d, 0xc2, 0xc8, 0xf5, 0x38, 0x80, 0x4f, 0xb2, 0xbe, 0xa1, 0x76, 0x50, 0x1a, 0x73, 0xf2, 0x78, 0x2b, 0x8e, 0x3a, 0x1e, 0x34, 0x47, 0x7b}, - subYX: fp.Elt{0xc3, 0x2c, 0x36, 0xdc, 0xc5, 0x45, 0xbc, 0xef, 0x1b, 0x64, 0xd6, 0x65, 0x28, 0xe9, 0xda, 0x84, 0x13, 0xbe, 0x27, 0x8e, 0x3f, 0x98, 0x2a, 0x37, 0xee, 0x78, 0x97, 0xd6, 0xc0, 0x6f, 0xb4, 0x53}, - dt2: fp.Elt{0x58, 0x5d, 0xa7, 0xa3, 0x68, 0xbb, 0x20, 0x30, 0x2e, 0x03, 0xe9, 0xb1, 0xd4, 0x90, 0x72, 0xe3, 0x71, 0xb2, 0x36, 0x3e, 0x73, 0xa0, 0x2e, 0x3d, 0xd1, 0x85, 0x33, 0x62, 0x4e, 0xa7, 0x7b, 0x31}, - }, - { - addYX: fp.Elt{0xbf, 0xc4, 0x38, 0x53, 0xfb, 0x68, 0xa9, 0x77, 0xce, 0x55, 0xf9, 0x05, 0xcb, 0xeb, 0xfb, 0x8c, 0x46, 0xc2, 0x32, 0x7c, 0xf0, 0xdb, 0xd7, 0x2c, 0x62, 0x8e, 0xdd, 0x54, 0x75, 0xcf, 0x3f, 0x33}, - subYX: fp.Elt{0x49, 0x50, 0x1f, 0x4e, 0x6e, 0x55, 0x55, 0xde, 0x8c, 0x4e, 0x77, 0x96, 0x38, 0x3b, 0xfe, 0xb6, 0x43, 0x3c, 0x86, 0x69, 0xc2, 0x72, 0x66, 0x1f, 0x6b, 0xf9, 0x87, 0xbc, 0x4f, 0x37, 0x3e, 0x3c}, - dt2: fp.Elt{0xd2, 0x2f, 0x06, 0x6b, 0x08, 0x07, 0x69, 0x77, 0xc0, 0x94, 0xcc, 0xae, 0x43, 0x00, 0x59, 0x6e, 0xa3, 0x63, 0xa8, 0xdd, 0xfa, 0x24, 0x18, 0xd0, 0x35, 0xc7, 0x78, 0xf7, 0x0d, 0xd4, 0x5a, 0x1e}, - }, - { - addYX: fp.Elt{0x45, 0xc1, 0x17, 0x51, 0xf8, 0xed, 0x7e, 0xc7, 0xa9, 0x1a, 0x11, 0x6e, 0x2d, 0xef, 0x0b, 0xd5, 0x3f, 0x98, 0xb0, 0xa3, 0x9d, 0x65, 0xf1, 0xcd, 0x53, 0x4a, 0x8a, 0x18, 0x70, 0x0a, 0x7f, 0x23}, - subYX: fp.Elt{0xdd, 0xef, 0xbe, 0x3a, 0x31, 0xe0, 0xbc, 0xbe, 0x6d, 0x5d, 0x79, 0x87, 0xd6, 0xbe, 0x68, 0xe3, 0x59, 0x76, 0x8c, 0x86, 0x0e, 0x7a, 0x92, 0x13, 0x14, 0x8f, 0x67, 0xb3, 0xcb, 0x1a, 0x76, 0x76}, - dt2: fp.Elt{0x56, 0x7a, 0x1c, 0x9d, 0xca, 0x96, 0xf9, 0xf9, 0x03, 0x21, 0xd4, 0xe8, 0xb3, 0xd5, 0xe9, 0x52, 0xc8, 0x54, 0x1e, 0x1b, 0x13, 0xb6, 0xfd, 0x47, 0x7d, 0x02, 0x32, 0x33, 0x27, 0xe2, 0x1f, 0x19}, - }, - }, -} - -var tabVerif = [1 << (omegaFix - 2)]pointR3{ - { /* 1P */ - addYX: fp.Elt{0x85, 0x3b, 0x8c, 0xf5, 0xc6, 0x93, 0xbc, 0x2f, 0x19, 0x0e, 0x8c, 0xfb, 0xc6, 0x2d, 0x93, 0xcf, 0xc2, 0x42, 0x3d, 0x64, 0x98, 0x48, 0x0b, 0x27, 0x65, 0xba, 0xd4, 0x33, 0x3a, 0x9d, 0xcf, 0x07}, - subYX: fp.Elt{0x3e, 0x91, 0x40, 0xd7, 0x05, 0x39, 0x10, 0x9d, 0xb3, 0xbe, 0x40, 0xd1, 0x05, 0x9f, 0x39, 0xfd, 0x09, 0x8a, 0x8f, 0x68, 0x34, 0x84, 0xc1, 0xa5, 0x67, 0x12, 0xf8, 0x98, 0x92, 0x2f, 0xfd, 0x44}, - dt2: fp.Elt{0x68, 0xaa, 0x7a, 0x87, 0x05, 0x12, 0xc9, 0xab, 0x9e, 0xc4, 0xaa, 0xcc, 0x23, 0xe8, 0xd9, 0x26, 0x8c, 0x59, 0x43, 0xdd, 0xcb, 0x7d, 0x1b, 0x5a, 0xa8, 0x65, 0x0c, 0x9f, 0x68, 0x7b, 0x11, 0x6f}, - }, - { /* 3P */ - addYX: fp.Elt{0x30, 0x97, 0xee, 0x4c, 0xa8, 0xb0, 0x25, 0xaf, 0x8a, 0x4b, 0x86, 0xe8, 0x30, 0x84, 0x5a, 0x02, 0x32, 0x67, 0x01, 0x9f, 0x02, 0x50, 0x1b, 0xc1, 0xf4, 0xf8, 0x80, 0x9a, 0x1b, 0x4e, 0x16, 0x7a}, - subYX: fp.Elt{0x65, 0xd2, 0xfc, 0xa4, 0xe8, 0x1f, 0x61, 0x56, 0x7d, 0xba, 0xc1, 0xe5, 0xfd, 0x53, 0xd3, 0x3b, 0xbd, 0xd6, 0x4b, 0x21, 0x1a, 0xf3, 0x31, 0x81, 0x62, 0xda, 0x5b, 0x55, 0x87, 0x15, 0xb9, 0x2a}, - dt2: fp.Elt{0x89, 0xd8, 0xd0, 0x0d, 0x3f, 0x93, 0xae, 0x14, 0x62, 0xda, 0x35, 0x1c, 0x22, 0x23, 0x94, 0x58, 0x4c, 0xdb, 0xf2, 0x8c, 0x45, 0xe5, 0x70, 0xd1, 0xc6, 0xb4, 0xb9, 0x12, 0xaf, 0x26, 0x28, 0x5a}, - }, - { /* 5P */ - addYX: fp.Elt{0x33, 0xbb, 0xa5, 0x08, 0x44, 0xbc, 0x12, 0xa2, 0x02, 0xed, 0x5e, 0xc7, 0xc3, 0x48, 0x50, 0x8d, 0x44, 0xec, 0xbf, 0x5a, 0x0c, 0xeb, 0x1b, 0xdd, 0xeb, 0x06, 0xe2, 0x46, 0xf1, 0xcc, 0x45, 0x29}, - subYX: fp.Elt{0xba, 0xd6, 0x47, 0xa4, 0xc3, 0x82, 0x91, 0x7f, 0xb7, 0x29, 0x27, 0x4b, 0xd1, 0x14, 0x00, 0xd5, 0x87, 0xa0, 0x64, 0xb8, 0x1c, 0xf1, 0x3c, 0xe3, 0xf3, 0x55, 0x1b, 0xeb, 0x73, 0x7e, 0x4a, 0x15}, - dt2: fp.Elt{0x85, 0x82, 0x2a, 0x81, 0xf1, 0xdb, 0xbb, 0xbc, 0xfc, 0xd1, 0xbd, 0xd0, 0x07, 0x08, 0x0e, 0x27, 0x2d, 0xa7, 0xbd, 0x1b, 0x0b, 0x67, 0x1b, 0xb4, 0x9a, 0xb6, 0x3b, 0x6b, 0x69, 0xbe, 0xaa, 0x43}, - }, - { /* 7P */ - addYX: fp.Elt{0xbf, 0xa3, 0x4e, 0x94, 0xd0, 0x5c, 0x1a, 0x6b, 0xd2, 0xc0, 0x9d, 0xb3, 0x3a, 0x35, 0x70, 0x74, 0x49, 0x2e, 0x54, 0x28, 0x82, 0x52, 0xb2, 0x71, 0x7e, 0x92, 0x3c, 0x28, 0x69, 0xea, 0x1b, 0x46}, - subYX: fp.Elt{0xb1, 0x21, 0x32, 0xaa, 0x9a, 0x2c, 0x6f, 0xba, 0xa7, 0x23, 0xba, 0x3b, 0x53, 0x21, 0xa0, 0x6c, 0x3a, 0x2c, 0x19, 0x92, 0x4f, 0x76, 0xea, 0x9d, 0xe0, 0x17, 0x53, 0x2e, 0x5d, 0xdd, 0x6e, 0x1d}, - dt2: fp.Elt{0xa2, 0xb3, 0xb8, 0x01, 0xc8, 0x6d, 0x83, 0xf1, 0x9a, 0xa4, 0x3e, 0x05, 0x47, 0x5f, 0x03, 0xb3, 0xf3, 0xad, 0x77, 0x58, 0xba, 0x41, 0x9c, 0x52, 0xa7, 0x90, 0x0f, 0x6a, 0x1c, 0xbb, 0x9f, 0x7a}, - }, - { /* 9P */ - addYX: fp.Elt{0x2f, 0x63, 0xa8, 0xa6, 0x8a, 0x67, 0x2e, 0x9b, 0xc5, 0x46, 0xbc, 0x51, 0x6f, 0x9e, 0x50, 0xa6, 0xb5, 0xf5, 0x86, 0xc6, 0xc9, 0x33, 0xb2, 0xce, 0x59, 0x7f, 0xdd, 0x8a, 0x33, 0xed, 0xb9, 0x34}, - subYX: fp.Elt{0x64, 0x80, 0x9d, 0x03, 0x7e, 0x21, 0x6e, 0xf3, 0x9b, 0x41, 0x20, 0xf5, 0xb6, 0x81, 0xa0, 0x98, 0x44, 0xb0, 0x5e, 0xe7, 0x08, 0xc6, 0xcb, 0x96, 0x8f, 0x9c, 0xdc, 0xfa, 0x51, 0x5a, 0xc0, 0x49}, - dt2: fp.Elt{0x1b, 0xaf, 0x45, 0x90, 0xbf, 0xe8, 0xb4, 0x06, 0x2f, 0xd2, 0x19, 0xa7, 0xe8, 0x83, 0xff, 0xe2, 0x16, 0xcf, 0xd4, 0x93, 0x29, 0xfc, 0xf6, 0xaa, 0x06, 0x8b, 0x00, 0x1b, 0x02, 0x72, 0xc1, 0x73}, - }, - { /* 11P */ - addYX: fp.Elt{0xde, 0x2a, 0x80, 0x8a, 0x84, 0x00, 0xbf, 0x2f, 0x27, 0x2e, 0x30, 0x02, 0xcf, 0xfe, 0xd9, 0xe5, 0x06, 0x34, 0x70, 0x17, 0x71, 0x84, 0x3e, 0x11, 0xaf, 0x8f, 0x6d, 0x54, 0xe2, 0xaa, 0x75, 0x42}, - subYX: fp.Elt{0x48, 0x43, 0x86, 0x49, 0x02, 0x5b, 0x5f, 0x31, 0x81, 0x83, 0x08, 0x77, 0x69, 0xb3, 0xd6, 0x3e, 0x95, 0xeb, 0x8d, 0x6a, 0x55, 0x75, 0xa0, 0xa3, 0x7f, 0xc7, 0xd5, 0x29, 0x80, 0x59, 0xab, 0x18}, - dt2: fp.Elt{0xe9, 0x89, 0x60, 0xfd, 0xc5, 0x2c, 0x2b, 0xd8, 0xa4, 0xe4, 0x82, 0x32, 0xa1, 0xb4, 0x1e, 0x03, 0x22, 0x86, 0x1a, 0xb5, 0x99, 0x11, 0x31, 0x44, 0x48, 0xf9, 0x3d, 0xb5, 0x22, 0x55, 0xc6, 0x3d}, - }, - { /* 13P */ - addYX: fp.Elt{0x6d, 0x7f, 0x00, 0xa2, 0x22, 0xc2, 0x70, 0xbf, 0xdb, 0xde, 0xbc, 0xb5, 0x9a, 0xb3, 0x84, 0xbf, 0x07, 0xba, 0x07, 0xfb, 0x12, 0x0e, 0x7a, 0x53, 0x41, 0xf2, 0x46, 0xc3, 0xee, 0xd7, 0x4f, 0x23}, - subYX: fp.Elt{0x93, 0xbf, 0x7f, 0x32, 0x3b, 0x01, 0x6f, 0x50, 0x6b, 0x6f, 0x77, 0x9b, 0xc9, 0xeb, 0xfc, 0xae, 0x68, 0x59, 0xad, 0xaa, 0x32, 0xb2, 0x12, 0x9d, 0xa7, 0x24, 0x60, 0x17, 0x2d, 0x88, 0x67, 0x02}, - dt2: fp.Elt{0x78, 0xa3, 0x2e, 0x73, 0x19, 0xa1, 0x60, 0x53, 0x71, 0xd4, 0x8d, 0xdf, 0xb1, 0xe6, 0x37, 0x24, 0x33, 0xe5, 0xa7, 0x91, 0xf8, 0x37, 0xef, 0xa2, 0x63, 0x78, 0x09, 0xaa, 0xfd, 0xa6, 0x7b, 0x49}, - }, - { /* 15P */ - addYX: fp.Elt{0xa0, 0xea, 0xcf, 0x13, 0x03, 0xcc, 0xce, 0x24, 0x6d, 0x24, 0x9c, 0x18, 0x8d, 0xc2, 0x48, 0x86, 0xd0, 0xd4, 0xf2, 0xc1, 0xfa, 0xbd, 0xbd, 0x2d, 0x2b, 0xe7, 0x2d, 0xf1, 0x17, 0x29, 0xe2, 0x61}, - subYX: fp.Elt{0x0b, 0xcf, 0x8c, 0x46, 0x86, 0xcd, 0x0b, 0x04, 0xd6, 0x10, 0x99, 0x2a, 0xa4, 0x9b, 0x82, 0xd3, 0x92, 0x51, 0xb2, 0x07, 0x08, 0x30, 0x08, 0x75, 0xbf, 0x5e, 0xd0, 0x18, 0x42, 0xcd, 0xb5, 0x43}, - dt2: fp.Elt{0x16, 0xb5, 0xd0, 0x9b, 0x2f, 0x76, 0x9a, 0x5d, 0xee, 0xde, 0x3f, 0x37, 0x4e, 0xaf, 0x38, 0xeb, 0x70, 0x42, 0xd6, 0x93, 0x7d, 0x5a, 0x2e, 0x03, 0x42, 0xd8, 0xe4, 0x0a, 0x21, 0x61, 0x1d, 0x51}, - }, - { /* 17P */ - addYX: fp.Elt{0x81, 0x9d, 0x0e, 0x95, 0xef, 0x76, 0xc6, 0x92, 0x4f, 0x04, 0xd7, 0xc0, 0xcd, 0x20, 0x46, 0xa5, 0x48, 0x12, 0x8f, 0x6f, 0x64, 0x36, 0x9b, 0xaa, 0xe3, 0x55, 0xb8, 0xdd, 0x24, 0x59, 0x32, 0x6d}, - subYX: fp.Elt{0x87, 0xde, 0x20, 0x44, 0x48, 0x86, 0x13, 0x08, 0xb4, 0xed, 0x92, 0xb5, 0x16, 0xf0, 0x1c, 0x8a, 0x25, 0x2d, 0x94, 0x29, 0x27, 0x4e, 0xfa, 0x39, 0x10, 0x28, 0x48, 0xe2, 0x6f, 0xfe, 0xa7, 0x71}, - dt2: fp.Elt{0x54, 0xc8, 0xc8, 0xa5, 0xb8, 0x82, 0x71, 0x6c, 0x03, 0x2a, 0x5f, 0xfe, 0x79, 0x14, 0xfd, 0x33, 0x0c, 0x8d, 0x77, 0x83, 0x18, 0x59, 0xcf, 0x72, 0xa9, 0xea, 0x9e, 0x55, 0xb6, 0xc4, 0x46, 0x47}, - }, - { /* 19P */ - addYX: fp.Elt{0x2b, 0x9a, 0xc6, 0x6d, 0x3c, 0x7b, 0x77, 0xd3, 0x17, 0xf6, 0x89, 0x6f, 0x27, 0xb2, 0xfa, 0xde, 0xb5, 0x16, 0x3a, 0xb5, 0xf7, 0x1c, 0x65, 0x45, 0xb7, 0x9f, 0xfe, 0x34, 0xde, 0x51, 0x9a, 0x5c}, - subYX: fp.Elt{0x47, 0x11, 0x74, 0x64, 0xc8, 0x46, 0x85, 0x34, 0x49, 0xc8, 0xfc, 0x0e, 0xdd, 0xae, 0x35, 0x7d, 0x32, 0xa3, 0x72, 0x06, 0x76, 0x9a, 0x93, 0xff, 0xd6, 0xe6, 0xb5, 0x7d, 0x49, 0x63, 0x96, 0x21}, - dt2: fp.Elt{0x67, 0x0e, 0xf1, 0x79, 0xcf, 0xf1, 0x10, 0xf5, 0x5b, 0x51, 0x58, 0xe6, 0xa1, 0xda, 0xdd, 0xff, 0x77, 0x22, 0x14, 0x10, 0x17, 0xa7, 0xc3, 0x09, 0xbb, 0x23, 0x82, 0x60, 0x3c, 0x50, 0x04, 0x48}, - }, - { /* 21P */ - addYX: fp.Elt{0xc7, 0x7f, 0xa3, 0x2c, 0xd0, 0x9e, 0x24, 0xc4, 0xab, 0xac, 0x15, 0xa6, 0xe3, 0xa0, 0x59, 0xa0, 0x23, 0x0e, 0x6e, 0xc9, 0xd7, 0x6e, 0xa9, 0x88, 0x6d, 0x69, 0x50, 0x16, 0xa5, 0x98, 0x33, 0x55}, - subYX: fp.Elt{0x75, 0xd1, 0x36, 0x3a, 0xd2, 0x21, 0x68, 0x3b, 0x32, 0x9e, 0x9b, 0xe9, 0xa7, 0x0a, 0xb4, 0xbb, 0x47, 0x8a, 0x83, 0x20, 0xe4, 0x5c, 0x9e, 0x5d, 0x5e, 0x4c, 0xde, 0x58, 0x88, 0x09, 0x1e, 0x77}, - dt2: fp.Elt{0xdf, 0x1e, 0x45, 0x78, 0xd2, 0xf5, 0x12, 0x9a, 0xcb, 0x9c, 0x89, 0x85, 0x79, 0x5d, 0xda, 0x3a, 0x08, 0x95, 0xa5, 0x9f, 0x2d, 0x4a, 0x7f, 0x47, 0x11, 0xa6, 0xf5, 0x8f, 0xd6, 0xd1, 0x5e, 0x5a}, - }, - { /* 23P */ - addYX: fp.Elt{0x83, 0x0e, 0x15, 0xfe, 0x2a, 0x12, 0x95, 0x11, 0xd8, 0x35, 0x4b, 0x7e, 0x25, 0x9a, 0x20, 0xcf, 0x20, 0x1e, 0x71, 0x1e, 0x29, 0xf8, 0x87, 0x73, 0xf0, 0x92, 0xbf, 0xd8, 0x97, 0xb8, 0xac, 0x44}, - subYX: fp.Elt{0x59, 0x73, 0x52, 0x58, 0xc5, 0xe0, 0xe5, 0xba, 0x7e, 0x9d, 0xdb, 0xca, 0x19, 0x5c, 0x2e, 0x39, 0xe9, 0xab, 0x1c, 0xda, 0x1e, 0x3c, 0x65, 0x28, 0x44, 0xdc, 0xef, 0x5f, 0x13, 0x60, 0x9b, 0x01}, - dt2: fp.Elt{0x83, 0x4b, 0x13, 0x5e, 0x14, 0x68, 0x60, 0x1e, 0x16, 0x4c, 0x30, 0x24, 0x4f, 0xe6, 0xf5, 0xc4, 0xd7, 0x3e, 0x1a, 0xfc, 0xa8, 0x88, 0x6e, 0x50, 0x92, 0x2f, 0xad, 0xe6, 0xfd, 0x49, 0x0c, 0x15}, - }, - { /* 25P */ - addYX: fp.Elt{0x38, 0x11, 0x47, 0x09, 0x95, 0xf2, 0x7b, 0x8e, 0x51, 0xa6, 0x75, 0x4f, 0x39, 0xef, 0x6f, 0x5d, 0xad, 0x08, 0xa7, 0x25, 0xc4, 0x79, 0xaf, 0x10, 0x22, 0x99, 0xb9, 0x5b, 0x07, 0x5a, 0x2b, 0x6b}, - subYX: fp.Elt{0x68, 0xa8, 0xdc, 0x9c, 0x3c, 0x86, 0x49, 0xb8, 0xd0, 0x4a, 0x71, 0xb8, 0xdb, 0x44, 0x3f, 0xc8, 0x8d, 0x16, 0x36, 0x0c, 0x56, 0xe3, 0x3e, 0xfe, 0xc1, 0xfb, 0x05, 0x1e, 0x79, 0xd7, 0xa6, 0x78}, - dt2: fp.Elt{0x76, 0xb9, 0xa0, 0x47, 0x4b, 0x70, 0xbf, 0x58, 0xd5, 0x48, 0x17, 0x74, 0x55, 0xb3, 0x01, 0xa6, 0x90, 0xf5, 0x42, 0xd5, 0xb1, 0x1f, 0x2b, 0xaa, 0x00, 0x5d, 0xd5, 0x4a, 0xfc, 0x7f, 0x5c, 0x72}, - }, - { /* 27P */ - addYX: fp.Elt{0xb2, 0x99, 0xcf, 0xd1, 0x15, 0x67, 0x42, 0xe4, 0x34, 0x0d, 0xa2, 0x02, 0x11, 0xd5, 0x52, 0x73, 0x9f, 0x10, 0x12, 0x8b, 0x7b, 0x15, 0xd1, 0x23, 0xa3, 0xf3, 0xb1, 0x7c, 0x27, 0xc9, 0x4c, 0x79}, - subYX: fp.Elt{0xc0, 0x98, 0xd0, 0x1c, 0xf7, 0x2b, 0x80, 0x91, 0x66, 0x63, 0x5e, 0xed, 0xa4, 0x6c, 0x41, 0xfe, 0x4c, 0x99, 0x02, 0x49, 0x71, 0x5d, 0x58, 0xdf, 0xe7, 0xfa, 0x55, 0xf8, 0x25, 0x46, 0xd5, 0x4c}, - dt2: fp.Elt{0x53, 0x50, 0xac, 0xc2, 0x26, 0xc4, 0xf6, 0x4a, 0x58, 0x72, 0xf6, 0x32, 0xad, 0xed, 0x9a, 0xbc, 0x21, 0x10, 0x31, 0x0a, 0xf1, 0x32, 0xd0, 0x2a, 0x85, 0x8e, 0xcc, 0x6f, 0x7b, 0x35, 0x08, 0x70}, - }, - { /* 29P */ - addYX: fp.Elt{0x01, 0x3f, 0x77, 0x38, 0x27, 0x67, 0x88, 0x0b, 0xfb, 0xcc, 0xfb, 0x95, 0xfa, 0xc8, 0xcc, 0xb8, 0xb6, 0x29, 0xad, 0xb9, 0xa3, 0xd5, 0x2d, 0x8d, 0x6a, 0x0f, 0xad, 0x51, 0x98, 0x7e, 0xef, 0x06}, - subYX: fp.Elt{0x34, 0x4a, 0x58, 0x82, 0xbb, 0x9f, 0x1b, 0xd0, 0x2b, 0x79, 0xb4, 0xd2, 0x63, 0x64, 0xab, 0x47, 0x02, 0x62, 0x53, 0x48, 0x9c, 0x63, 0x31, 0xb6, 0x28, 0xd4, 0xd6, 0x69, 0x36, 0x2a, 0xa9, 0x13}, - dt2: fp.Elt{0xe5, 0x7d, 0x57, 0xc0, 0x1c, 0x77, 0x93, 0xca, 0x5c, 0xdc, 0x35, 0x50, 0x1e, 0xe4, 0x40, 0x75, 0x71, 0xe0, 0x02, 0xd8, 0x01, 0x0f, 0x68, 0x24, 0x6a, 0xf8, 0x2a, 0x8a, 0xdf, 0x6d, 0x29, 0x3c}, - }, - { /* 31P */ - addYX: fp.Elt{0x13, 0xa7, 0x14, 0xd9, 0xf9, 0x15, 0xad, 0xae, 0x12, 0xf9, 0x8f, 0x8c, 0xf9, 0x7b, 0x2f, 0xa9, 0x30, 0xd7, 0x53, 0x9f, 0x17, 0x23, 0xf8, 0xaf, 0xba, 0x77, 0x0c, 0x49, 0x93, 0xd3, 0x99, 0x7a}, - subYX: fp.Elt{0x41, 0x25, 0x1f, 0xbb, 0x2e, 0x4d, 0xeb, 0xfc, 0x1f, 0xb9, 0xad, 0x40, 0xc7, 0x10, 0x95, 0xb8, 0x05, 0xad, 0xa1, 0xd0, 0x7d, 0xa3, 0x71, 0xfc, 0x7b, 0x71, 0x47, 0x07, 0x70, 0x2c, 0x89, 0x0a}, - dt2: fp.Elt{0xe8, 0xa3, 0xbd, 0x36, 0x24, 0xed, 0x52, 0x8f, 0x94, 0x07, 0xe8, 0x57, 0x41, 0xc8, 0xa8, 0x77, 0xe0, 0x9c, 0x2f, 0x26, 0x63, 0x65, 0xa9, 0xa5, 0xd2, 0xf7, 0x02, 0x83, 0xd2, 0x62, 0x67, 0x28}, - }, - { /* 33P */ - addYX: fp.Elt{0x25, 0x5b, 0xe3, 0x3c, 0x09, 0x36, 0x78, 0x4e, 0x97, 0xaa, 0x6b, 0xb2, 0x1d, 0x18, 0xe1, 0x82, 0x3f, 0xb8, 0xc7, 0xcb, 0xd3, 0x92, 0xc1, 0x0c, 0x3a, 0x9d, 0x9d, 0x6a, 0x04, 0xda, 0xf1, 0x32}, - subYX: fp.Elt{0xbd, 0xf5, 0x2e, 0xce, 0x2b, 0x8e, 0x55, 0x7c, 0x63, 0xbc, 0x47, 0x67, 0xb4, 0x6c, 0x98, 0xe4, 0xb8, 0x89, 0xbb, 0x3b, 0x9f, 0x17, 0x4a, 0x15, 0x7a, 0x76, 0xf1, 0xd6, 0xa3, 0xf2, 0x86, 0x76}, - dt2: fp.Elt{0x6a, 0x7c, 0x59, 0x6d, 0xa6, 0x12, 0x8d, 0xaa, 0x2b, 0x85, 0xd3, 0x04, 0x03, 0x93, 0x11, 0x8f, 0x22, 0xb0, 0x09, 0xc2, 0x73, 0xdc, 0x91, 0x3f, 0xa6, 0x28, 0xad, 0xa9, 0xf8, 0x05, 0x13, 0x56}, - }, - { /* 35P */ - addYX: fp.Elt{0xd1, 0xae, 0x92, 0xec, 0x8d, 0x97, 0x0c, 0x10, 0xe5, 0x73, 0x6d, 0x4d, 0x43, 0xd5, 0x43, 0xca, 0x48, 0xba, 0x47, 0xd8, 0x22, 0x1b, 0x13, 0x83, 0x2c, 0x4d, 0x5d, 0xe3, 0x53, 0xec, 0xaa}, - subYX: fp.Elt{0xd5, 0xc0, 0xb0, 0xe7, 0x28, 0xcc, 0x22, 0x67, 0x53, 0x5c, 0x07, 0xdb, 0xbb, 0xe9, 0x9d, 0x70, 0x61, 0x0a, 0x01, 0xd7, 0xa7, 0x8d, 0xf6, 0xca, 0x6c, 0xcc, 0x57, 0x2c, 0xef, 0x1a, 0x0a, 0x03}, - dt2: fp.Elt{0xaa, 0xd2, 0x3a, 0x00, 0x73, 0xf7, 0xb1, 0x7b, 0x08, 0x66, 0x21, 0x2b, 0x80, 0x29, 0x3f, 0x0b, 0x3e, 0xd2, 0x0e, 0x52, 0x86, 0xdc, 0x21, 0x78, 0x80, 0x54, 0x06, 0x24, 0x1c, 0x9c, 0xbe, 0x20}, - }, - { /* 37P */ - addYX: fp.Elt{0xa6, 0x73, 0x96, 0x24, 0xd8, 0x87, 0x53, 0xe1, 0x93, 0xe4, 0x46, 0xf5, 0x2d, 0xbc, 0x43, 0x59, 0xb5, 0x63, 0x6f, 0xc3, 0x81, 0x9a, 0x7f, 0x1c, 0xde, 0xc1, 0x0a, 0x1f, 0x36, 0xb3, 0x0a, 0x75}, - subYX: fp.Elt{0x60, 0x5e, 0x02, 0xe2, 0x4a, 0xe4, 0xe0, 0x20, 0x38, 0xb9, 0xdc, 0xcb, 0x2f, 0x3b, 0x3b, 0xb0, 0x1c, 0x0d, 0x5a, 0xf9, 0x9c, 0x63, 0x5d, 0x10, 0x11, 0xe3, 0x67, 0x50, 0x54, 0x4c, 0x76, 0x69}, - dt2: fp.Elt{0x37, 0x10, 0xf8, 0xa2, 0x83, 0x32, 0x8a, 0x1e, 0xf1, 0xcb, 0x7f, 0xbd, 0x23, 0xda, 0x2e, 0x6f, 0x63, 0x25, 0x2e, 0xac, 0x5b, 0xd1, 0x2f, 0xb7, 0x40, 0x50, 0x07, 0xb7, 0x3f, 0x6b, 0xf9, 0x54}, - }, - { /* 39P */ - addYX: fp.Elt{0x79, 0x92, 0x66, 0x29, 0x04, 0xf2, 0xad, 0x0f, 0x4a, 0x72, 0x7d, 0x7d, 0x04, 0xa2, 0xdd, 0x3a, 0xf1, 0x60, 0x57, 0x8c, 0x82, 0x94, 0x3d, 0x6f, 0x9e, 0x53, 0xb7, 0x2b, 0xc5, 0xe9, 0x7f, 0x3d}, - subYX: fp.Elt{0xcd, 0x1e, 0xb1, 0x16, 0xc6, 0xaf, 0x7d, 0x17, 0x79, 0x64, 0x57, 0xfa, 0x9c, 0x4b, 0x76, 0x89, 0x85, 0xe7, 0xec, 0xe6, 0x10, 0xa1, 0xa8, 0xb7, 0xf0, 0xdb, 0x85, 0xbe, 0x9f, 0x83, 0xe6, 0x78}, - dt2: fp.Elt{0x6b, 0x85, 0xb8, 0x37, 0xf7, 0x2d, 0x33, 0x70, 0x8a, 0x17, 0x1a, 0x04, 0x43, 0x5d, 0xd0, 0x75, 0x22, 0x9e, 0xe5, 0xa0, 0x4a, 0xf7, 0x0f, 0x32, 0x42, 0x82, 0x08, 0x50, 0xf3, 0x68, 0xf2, 0x70}, - }, - { /* 41P */ - addYX: fp.Elt{0x47, 0x5f, 0x80, 0xb1, 0x83, 0x45, 0x86, 0x66, 0x19, 0x7c, 0xdd, 0x60, 0xd1, 0xc5, 0x35, 0xf5, 0x06, 0xb0, 0x4c, 0x1e, 0xb7, 0x4e, 0x87, 0xe9, 0xd9, 0x89, 0xd8, 0xfa, 0x5c, 0x34, 0x0d, 0x7c}, - subYX: fp.Elt{0x55, 0xf3, 0xdc, 0x70, 0x20, 0x11, 0x24, 0x23, 0x17, 0xe1, 0xfc, 0xe7, 0x7e, 0xc9, 0x0c, 0x38, 0x98, 0xb6, 0x52, 0x35, 0xed, 0xde, 0x1d, 0xb3, 0xb9, 0xc4, 0xb8, 0x39, 0xc0, 0x56, 0x4e, 0x40}, - dt2: fp.Elt{0x8a, 0x33, 0x78, 0x8c, 0x4b, 0x1f, 0x1f, 0x59, 0xe1, 0xb5, 0xe0, 0x67, 0xb1, 0x6a, 0x36, 0xa0, 0x44, 0x3d, 0x5f, 0xb4, 0x52, 0x41, 0xbc, 0x5c, 0x77, 0xc7, 0xae, 0x2a, 0x76, 0x54, 0xd7, 0x20}, - }, - { /* 43P */ - addYX: fp.Elt{0x58, 0xb7, 0x3b, 0xc7, 0x6f, 0xc3, 0x8f, 0x5e, 0x9a, 0xbb, 0x3c, 0x36, 0xa5, 0x43, 0xe5, 0xac, 0x22, 0xc9, 0x3b, 0x90, 0x7d, 0x4a, 0x93, 0xa9, 0x62, 0xec, 0xce, 0xf3, 0x46, 0x1e, 0x8f, 0x2b}, - subYX: fp.Elt{0x43, 0xf5, 0xb9, 0x35, 0xb1, 0xfe, 0x74, 0x9d, 0x6c, 0x95, 0x8c, 0xde, 0xf1, 0x7d, 0xb3, 0x84, 0xa9, 0x8b, 0x13, 0x57, 0x07, 0x2b, 0x32, 0xe9, 0xe1, 0x4c, 0x0b, 0x79, 0xa8, 0xad, 0xb8, 0x38}, - dt2: fp.Elt{0x5d, 0xf9, 0x51, 0xdf, 0x9c, 0x4a, 0xc0, 0xb5, 0xac, 0xde, 0x1f, 0xcb, 0xae, 0x52, 0x39, 0x2b, 0xda, 0x66, 0x8b, 0x32, 0x8b, 0x6d, 0x10, 0x1d, 0x53, 0x19, 0xba, 0xce, 0x32, 0xeb, 0x9a, 0x04}, - }, - { /* 45P */ - addYX: fp.Elt{0x31, 0x79, 0xfc, 0x75, 0x0b, 0x7d, 0x50, 0xaa, 0xd3, 0x25, 0x67, 0x7a, 0x4b, 0x92, 0xef, 0x0f, 0x30, 0x39, 0x6b, 0x39, 0x2b, 0x54, 0x82, 0x1d, 0xfc, 0x74, 0xf6, 0x30, 0x75, 0xe1, 0x5e, 0x79}, - subYX: fp.Elt{0x7e, 0xfe, 0xdc, 0x63, 0x3c, 0x7d, 0x76, 0xd7, 0x40, 0x6e, 0x85, 0x97, 0x48, 0x59, 0x9c, 0x20, 0x13, 0x7c, 0x4f, 0xe1, 0x61, 0x68, 0x67, 0xb6, 0xfc, 0x25, 0xd6, 0xc8, 0xe0, 0x65, 0xc6, 0x51}, - dt2: fp.Elt{0x81, 0xbd, 0xec, 0x52, 0x0a, 0x5b, 0x4a, 0x25, 0xe7, 0xaf, 0x34, 0xe0, 0x6e, 0x1f, 0x41, 0x5d, 0x31, 0x4a, 0xee, 0xca, 0x0d, 0x4d, 0xa2, 0xe6, 0x77, 0x44, 0xc5, 0x9d, 0xf4, 0x9b, 0xd1, 0x6c}, - }, - { /* 47P */ - addYX: fp.Elt{0x86, 0xc3, 0xaf, 0x65, 0x21, 0x61, 0xfe, 0x1f, 0x10, 0x1b, 0xd5, 0xb8, 0x88, 0x2a, 0x2a, 0x08, 0xaa, 0x0b, 0x99, 0x20, 0x7e, 0x62, 0xf6, 0x76, 0xe7, 0x43, 0x9e, 0x42, 0xa7, 0xb3, 0x01, 0x5e}, - subYX: fp.Elt{0xa3, 0x9c, 0x17, 0x52, 0x90, 0x61, 0x87, 0x7e, 0x85, 0x9f, 0x2c, 0x0b, 0x06, 0x0a, 0x1d, 0x57, 0x1e, 0x71, 0x99, 0x84, 0xa8, 0xba, 0xa2, 0x80, 0x38, 0xe6, 0xb2, 0x40, 0xdb, 0xf3, 0x20, 0x75}, - dt2: fp.Elt{0xa1, 0x57, 0x93, 0xd3, 0xe3, 0x0b, 0xb5, 0x3d, 0xa5, 0x94, 0x9e, 0x59, 0xdd, 0x6c, 0x7b, 0x96, 0x6e, 0x1e, 0x31, 0xdf, 0x64, 0x9a, 0x30, 0x1a, 0x86, 0xc9, 0xf3, 0xce, 0x9c, 0x2c, 0x09, 0x71}, - }, - { /* 49P */ - addYX: fp.Elt{0xcf, 0x1d, 0x05, 0x74, 0xac, 0xd8, 0x6b, 0x85, 0x1e, 0xaa, 0xb7, 0x55, 0x08, 0xa4, 0xf6, 0x03, 0xeb, 0x3c, 0x74, 0xc9, 0xcb, 0xe7, 0x4a, 0x3a, 0xde, 0xab, 0x37, 0x71, 0xbb, 0xa5, 0x73, 0x41}, - subYX: fp.Elt{0x8c, 0x91, 0x64, 0x03, 0x3f, 0x52, 0xd8, 0x53, 0x1c, 0x6b, 0xab, 0x3f, 0xf4, 0x04, 0xb4, 0xa2, 0xa4, 0xe5, 0x81, 0x66, 0x9e, 0x4a, 0x0b, 0x08, 0xa7, 0x7b, 0x25, 0xd0, 0x03, 0x5b, 0xa1, 0x0e}, - dt2: fp.Elt{0x8a, 0x21, 0xf9, 0xf0, 0x31, 0x6e, 0xc5, 0x17, 0x08, 0x47, 0xfc, 0x1a, 0x2b, 0x6e, 0x69, 0x5a, 0x76, 0xf1, 0xb2, 0xf4, 0x68, 0x16, 0x93, 0xf7, 0x67, 0x3a, 0x4e, 0x4a, 0x61, 0x65, 0xc5, 0x5f}, - }, - { /* 51P */ - addYX: fp.Elt{0x8e, 0x98, 0x90, 0x77, 0xe6, 0xe1, 0x92, 0x48, 0x22, 0xd7, 0x5c, 0x1c, 0x0f, 0x95, 0xd5, 0x01, 0xed, 0x3e, 0x92, 0xe5, 0x9a, 0x81, 0xb0, 0xe3, 0x1b, 0x65, 0x46, 0x9d, 0x40, 0xc7, 0x14, 0x32}, - subYX: fp.Elt{0xe5, 0x7a, 0x6d, 0xc4, 0x0d, 0x57, 0x6e, 0x13, 0x8f, 0xdc, 0xf8, 0x54, 0xcc, 0xaa, 0xd0, 0x0f, 0x86, 0xad, 0x0d, 0x31, 0x03, 0x9f, 0x54, 0x59, 0xa1, 0x4a, 0x45, 0x4c, 0x41, 0x1c, 0x71, 0x62}, - dt2: fp.Elt{0x70, 0x17, 0x65, 0x06, 0x74, 0x82, 0x29, 0x13, 0x36, 0x94, 0x27, 0x8a, 0x66, 0xa0, 0xa4, 0x3b, 0x3c, 0x22, 0x5d, 0x18, 0xec, 0xb8, 0xb6, 0xd9, 0x3c, 0x83, 0xcb, 0x3e, 0x07, 0x94, 0xea, 0x5b}, - }, - { /* 53P */ - addYX: fp.Elt{0xf8, 0xd2, 0x43, 0xf3, 0x63, 0xce, 0x70, 0xb4, 0xf1, 0xe8, 0x43, 0x05, 0x8f, 0xba, 0x67, 0x00, 0x6f, 0x7b, 0x11, 0xa2, 0xa1, 0x51, 0xda, 0x35, 0x2f, 0xbd, 0xf1, 0x44, 0x59, 0x78, 0xd0, 0x4a}, - subYX: fp.Elt{0xe4, 0x9b, 0xc8, 0x12, 0x09, 0xbf, 0x1d, 0x64, 0x9c, 0x57, 0x6e, 0x7d, 0x31, 0x8b, 0xf3, 0xac, 0x65, 0xb0, 0x97, 0xf6, 0x02, 0x9e, 0xfe, 0xab, 0xec, 0x1e, 0xf6, 0x48, 0xc1, 0xd5, 0xac, 0x3a}, - dt2: fp.Elt{0x01, 0x83, 0x31, 0xc3, 0x34, 0x3b, 0x8e, 0x85, 0x26, 0x68, 0x31, 0x07, 0x47, 0xc0, 0x99, 0xdc, 0x8c, 0xa8, 0x9d, 0xd3, 0x2e, 0x5b, 0x08, 0x34, 0x3d, 0x85, 0x02, 0xd9, 0xb1, 0x0c, 0xff, 0x3a}, - }, - { /* 55P */ - addYX: fp.Elt{0x05, 0x35, 0xc5, 0xf4, 0x0b, 0x43, 0x26, 0x92, 0x83, 0x22, 0x1f, 0x26, 0x13, 0x9c, 0xe4, 0x68, 0xc6, 0x27, 0xd3, 0x8f, 0x78, 0x33, 0xef, 0x09, 0x7f, 0x9e, 0xd9, 0x2b, 0x73, 0x9f, 0xcf, 0x2c}, - subYX: fp.Elt{0x5e, 0x40, 0x20, 0x3a, 0xeb, 0xc7, 0xc5, 0x87, 0xc9, 0x56, 0xad, 0xed, 0xef, 0x11, 0xe3, 0x8e, 0xf9, 0xd5, 0x29, 0xad, 0x48, 0x2e, 0x25, 0x29, 0x1d, 0x25, 0xcd, 0xf4, 0x86, 0x7e, 0x0e, 0x11}, - dt2: fp.Elt{0xe4, 0xf5, 0x03, 0xd6, 0x9e, 0xd8, 0xc0, 0x57, 0x0c, 0x20, 0xb0, 0xf0, 0x28, 0x86, 0x88, 0x12, 0xb7, 0x3b, 0x2e, 0xa0, 0x09, 0x27, 0x17, 0x53, 0x37, 0x3a, 0x69, 0xb9, 0xe0, 0x57, 0xc5, 0x05}, - }, - { /* 57P */ - addYX: fp.Elt{0xb0, 0x0e, 0xc2, 0x89, 0xb0, 0xbb, 0x76, 0xf7, 0x5c, 0xd8, 0x0f, 0xfa, 0xf6, 0x5b, 0xf8, 0x61, 0xfb, 0x21, 0x44, 0x63, 0x4e, 0x3f, 0xb9, 0xb6, 0x05, 0x12, 0x86, 0x41, 0x08, 0xef, 0x9f, 0x28}, - subYX: fp.Elt{0x6f, 0x7e, 0xc9, 0x1f, 0x31, 0xce, 0xf9, 0xd8, 0xae, 0xfd, 0xf9, 0x11, 0x30, 0x26, 0x3f, 0x7a, 0xdd, 0x25, 0xed, 0x8b, 0xa0, 0x7e, 0x5b, 0xe1, 0x5a, 0x87, 0xe9, 0x8f, 0x17, 0x4c, 0x15, 0x6e}, - dt2: fp.Elt{0xbf, 0x9a, 0xd6, 0xfe, 0x36, 0x63, 0x61, 0xcf, 0x4f, 0xc9, 0x35, 0x83, 0xe7, 0xe4, 0x16, 0x9b, 0xe7, 0x7f, 0x3a, 0x75, 0x65, 0x97, 0x78, 0x13, 0x19, 0xa3, 0x5c, 0xa9, 0x42, 0xf6, 0xfb, 0x6a}, - }, - { /* 59P */ - addYX: fp.Elt{0xcc, 0xa8, 0x13, 0xf9, 0x70, 0x50, 0xe5, 0x5d, 0x61, 0xf5, 0x0c, 0x2b, 0x7b, 0x16, 0x1d, 0x7d, 0x89, 0xd4, 0xea, 0x90, 0xb6, 0x56, 0x29, 0xda, 0xd9, 0x1e, 0x80, 0xdb, 0xce, 0x93, 0xc0, 0x12}, - subYX: fp.Elt{0xc1, 0xd2, 0xf5, 0x62, 0x0c, 0xde, 0xa8, 0x7d, 0x9a, 0x7b, 0x0e, 0xb0, 0xa4, 0x3d, 0xfc, 0x98, 0xe0, 0x70, 0xad, 0x0d, 0xda, 0x6a, 0xeb, 0x7d, 0xc4, 0x38, 0x50, 0xb9, 0x51, 0xb8, 0xb4, 0x0d}, - dt2: fp.Elt{0x0f, 0x19, 0xb8, 0x08, 0x93, 0x7f, 0x14, 0xfc, 0x10, 0xe3, 0x1a, 0xa1, 0xa0, 0x9d, 0x96, 0x06, 0xfd, 0xd7, 0xc7, 0xda, 0x72, 0x55, 0xe7, 0xce, 0xe6, 0x5c, 0x63, 0xc6, 0x99, 0x87, 0xaa, 0x33}, - }, - { /* 61P */ - addYX: fp.Elt{0xb1, 0x6c, 0x15, 0xfc, 0x88, 0xf5, 0x48, 0x83, 0x27, 0x6d, 0x0a, 0x1a, 0x9b, 0xba, 0xa2, 0x6d, 0xb6, 0x5a, 0xca, 0x87, 0x5c, 0x2d, 0x26, 0xe2, 0xa6, 0x89, 0xd5, 0xc8, 0xc1, 0xd0, 0x2c, 0x21}, - subYX: fp.Elt{0xf2, 0x5c, 0x08, 0xbd, 0x1e, 0xf5, 0x0f, 0xaf, 0x1f, 0x3f, 0xd3, 0x67, 0x89, 0x1a, 0xf5, 0x78, 0x3c, 0x03, 0x60, 0x50, 0xe1, 0xbf, 0xc2, 0x6e, 0x86, 0x1a, 0xe2, 0xe8, 0x29, 0x6f, 0x3c, 0x23}, - dt2: fp.Elt{0x81, 0xc7, 0x18, 0x7f, 0x10, 0xd5, 0xf4, 0xd2, 0x28, 0x9d, 0x7e, 0x52, 0xf2, 0xcd, 0x2e, 0x12, 0x41, 0x33, 0x3d, 0x3d, 0x2a, 0x86, 0x0a, 0xa7, 0xe3, 0x4c, 0x91, 0x11, 0x89, 0x77, 0xb7, 0x1d}, - }, - { /* 63P */ - addYX: fp.Elt{0xb6, 0x1a, 0x70, 0xdd, 0x69, 0x47, 0x39, 0xb3, 0xa5, 0x8d, 0xcf, 0x19, 0xd4, 0xde, 0xb8, 0xe2, 0x52, 0xc8, 0x2a, 0xfd, 0x61, 0x41, 0xdf, 0x15, 0xbe, 0x24, 0x7d, 0x01, 0x8a, 0xca, 0xe2, 0x7a}, - subYX: fp.Elt{0x6f, 0xc2, 0x6b, 0x7c, 0x39, 0x52, 0xf3, 0xdd, 0x13, 0x01, 0xd5, 0x53, 0xcc, 0xe2, 0x97, 0x7a, 0x30, 0xa3, 0x79, 0xbf, 0x3a, 0xf4, 0x74, 0x7c, 0xfc, 0xad, 0xe2, 0x26, 0xad, 0x97, 0xad, 0x31}, - dt2: fp.Elt{0x62, 0xb9, 0x20, 0x09, 0xed, 0x17, 0xe8, 0xb7, 0x9d, 0xda, 0x19, 0x3f, 0xcc, 0x18, 0x85, 0x1e, 0x64, 0x0a, 0x56, 0x25, 0x4f, 0xc1, 0x91, 0xe4, 0x83, 0x2c, 0x62, 0xa6, 0x53, 0xfc, 0xd1, 0x1e}, - }, -} diff --git a/vendor/github.com/cloudflare/circl/sign/ed448/ed448.go b/vendor/github.com/cloudflare/circl/sign/ed448/ed448.go deleted file mode 100644 index c368b181b49..00000000000 --- a/vendor/github.com/cloudflare/circl/sign/ed448/ed448.go +++ /dev/null @@ -1,411 +0,0 @@ -// Package ed448 implements Ed448 signature scheme as described in RFC-8032. -// -// This package implements two signature variants. -// -// | Scheme Name | Sign Function | Verification | Context | -// |-------------|-------------------|---------------|-------------------| -// | Ed448 | Sign | Verify | Yes, can be empty | -// | Ed448Ph | SignPh | VerifyPh | Yes, can be empty | -// | All above | (PrivateKey).Sign | VerifyAny | As above | -// -// Specific functions for sign and verify are defined. A generic signing -// function for all schemes is available through the crypto.Signer interface, -// which is implemented by the PrivateKey type. A correspond all-in-one -// verification method is provided by the VerifyAny function. -// -// Both schemes require a context string for domain separation. This parameter -// is passed using a SignerOptions struct defined in this package. -// -// References: -// -// - RFC8032: https://rfc-editor.org/rfc/rfc8032.txt -// - EdDSA for more curves: https://eprint.iacr.org/2015/677 -// - High-speed high-security signatures: https://doi.org/10.1007/s13389-012-0027-1 -package ed448 - -import ( - "bytes" - "crypto" - cryptoRand "crypto/rand" - "crypto/subtle" - "errors" - "fmt" - "io" - "strconv" - - "github.com/cloudflare/circl/ecc/goldilocks" - "github.com/cloudflare/circl/internal/sha3" - "github.com/cloudflare/circl/sign" -) - -const ( - // ContextMaxSize is the maximum length (in bytes) allowed for context. - ContextMaxSize = 255 - // PublicKeySize is the length in bytes of Ed448 public keys. - PublicKeySize = 57 - // PrivateKeySize is the length in bytes of Ed448 private keys. - PrivateKeySize = 114 - // SignatureSize is the length in bytes of signatures. - SignatureSize = 114 - // SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032. - SeedSize = 57 -) - -const ( - paramB = 456 / 8 // Size of keys in bytes. - hashSize = 2 * paramB // Size of the hash function's output. -) - -// SignerOptions implements crypto.SignerOpts and augments with parameters -// that are specific to the Ed448 signature schemes. -type SignerOptions struct { - // Hash must be crypto.Hash(0) for both Ed448 and Ed448Ph. - crypto.Hash - - // Context is an optional domain separation string for signing. - // Its length must be less or equal than 255 bytes. - Context string - - // Scheme is an identifier for choosing a signature scheme. - Scheme SchemeID -} - -// SchemeID is an identifier for each signature scheme. -type SchemeID uint - -const ( - ED448 SchemeID = iota - ED448Ph -) - -// PublicKey is the type of Ed448 public keys. -type PublicKey []byte - -// Equal reports whether pub and x have the same value. -func (pub PublicKey) Equal(x crypto.PublicKey) bool { - xx, ok := x.(PublicKey) - return ok && bytes.Equal(pub, xx) -} - -// PrivateKey is the type of Ed448 private keys. It implements crypto.Signer. -type PrivateKey []byte - -// Equal reports whether priv and x have the same value. -func (priv PrivateKey) Equal(x crypto.PrivateKey) bool { - xx, ok := x.(PrivateKey) - return ok && subtle.ConstantTimeCompare(priv, xx) == 1 -} - -// Public returns the PublicKey corresponding to priv. -func (priv PrivateKey) Public() crypto.PublicKey { - publicKey := make([]byte, PublicKeySize) - copy(publicKey, priv[SeedSize:]) - return PublicKey(publicKey) -} - -// Seed returns the private key seed corresponding to priv. It is provided for -// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds -// in this package. -func (priv PrivateKey) Seed() []byte { - seed := make([]byte, SeedSize) - copy(seed, priv[:SeedSize]) - return seed -} - -func (priv PrivateKey) Scheme() sign.Scheme { return sch } - -func (pub PublicKey) Scheme() sign.Scheme { return sch } - -func (priv PrivateKey) MarshalBinary() (data []byte, err error) { - privateKey := make(PrivateKey, PrivateKeySize) - copy(privateKey, priv) - return privateKey, nil -} - -func (pub PublicKey) MarshalBinary() (data []byte, err error) { - publicKey := make(PublicKey, PublicKeySize) - copy(publicKey, pub) - return publicKey, nil -} - -// Sign creates a signature of a message given a key pair. -// This function supports all the two signature variants defined in RFC-8032, -// namely Ed448 (or pure EdDSA) and Ed448Ph. -// The opts.HashFunc() must return zero to the specify Ed448 variant. This can -// be achieved by passing crypto.Hash(0) as the value for opts. -// Use an Options struct to pass a bool indicating that the ed448Ph variant -// should be used. -// The struct can also be optionally used to pass a context string for signing. -func (priv PrivateKey) Sign( - rand io.Reader, - message []byte, - opts crypto.SignerOpts, -) (signature []byte, err error) { - var ctx string - var scheme SchemeID - - if o, ok := opts.(SignerOptions); ok { - ctx = o.Context - scheme = o.Scheme - } - - switch true { - case scheme == ED448 && opts.HashFunc() == crypto.Hash(0): - return Sign(priv, message, ctx), nil - case scheme == ED448Ph && opts.HashFunc() == crypto.Hash(0): - return SignPh(priv, message, ctx), nil - default: - return nil, errors.New("ed448: bad hash algorithm") - } -} - -// GenerateKey generates a public/private key pair using entropy from rand. -// If rand is nil, crypto/rand.Reader will be used. -func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) { - if rand == nil { - rand = cryptoRand.Reader - } - - seed := make(PrivateKey, SeedSize) - if _, err := io.ReadFull(rand, seed); err != nil { - return nil, nil, err - } - - privateKey := NewKeyFromSeed(seed) - publicKey := make([]byte, PublicKeySize) - copy(publicKey, privateKey[SeedSize:]) - - return publicKey, privateKey, nil -} - -// NewKeyFromSeed calculates a private key from a seed. It will panic if -// len(seed) is not SeedSize. This function is provided for interoperability -// with RFC 8032. RFC 8032's private keys correspond to seeds in this -// package. -func NewKeyFromSeed(seed []byte) PrivateKey { - privateKey := make([]byte, PrivateKeySize) - newKeyFromSeed(privateKey, seed) - return privateKey -} - -func newKeyFromSeed(privateKey, seed []byte) { - if l := len(seed); l != SeedSize { - panic("ed448: bad seed length: " + strconv.Itoa(l)) - } - - var h [hashSize]byte - H := sha3.NewShake256() - _, _ = H.Write(seed) - _, _ = H.Read(h[:]) - s := &goldilocks.Scalar{} - deriveSecretScalar(s, h[:paramB]) - - copy(privateKey[:SeedSize], seed) - _ = goldilocks.Curve{}.ScalarBaseMult(s).ToBytes(privateKey[SeedSize:]) -} - -func signAll(signature []byte, privateKey PrivateKey, message, ctx []byte, preHash bool) { - if len(ctx) > ContextMaxSize { - panic(fmt.Errorf("ed448: bad context length: %v", len(ctx))) - } - - H := sha3.NewShake256() - var PHM []byte - - if preHash { - var h [64]byte - _, _ = H.Write(message) - _, _ = H.Read(h[:]) - PHM = h[:] - H.Reset() - } else { - PHM = message - } - - // 1. Hash the 57-byte private key using SHAKE256(x, 114). - var h [hashSize]byte - _, _ = H.Write(privateKey[:SeedSize]) - _, _ = H.Read(h[:]) - s := &goldilocks.Scalar{} - deriveSecretScalar(s, h[:paramB]) - prefix := h[paramB:] - - // 2. Compute SHAKE256(dom4(F, C) || prefix || PH(M), 114). - var rPM [hashSize]byte - H.Reset() - - writeDom(&H, ctx, preHash) - - _, _ = H.Write(prefix) - _, _ = H.Write(PHM) - _, _ = H.Read(rPM[:]) - - // 3. Compute the point [r]B. - r := &goldilocks.Scalar{} - r.FromBytes(rPM[:]) - R := (&[paramB]byte{})[:] - if err := (goldilocks.Curve{}.ScalarBaseMult(r).ToBytes(R)); err != nil { - panic(err) - } - // 4. Compute SHAKE256(dom4(F, C) || R || A || PH(M), 114) - var hRAM [hashSize]byte - H.Reset() - - writeDom(&H, ctx, preHash) - - _, _ = H.Write(R) - _, _ = H.Write(privateKey[SeedSize:]) - _, _ = H.Write(PHM) - _, _ = H.Read(hRAM[:]) - - // 5. Compute S = (r + k * s) mod order. - k := &goldilocks.Scalar{} - k.FromBytes(hRAM[:]) - S := &goldilocks.Scalar{} - S.Mul(k, s) - S.Add(S, r) - - // 6. The signature is the concatenation of R and S. - copy(signature[:paramB], R[:]) - copy(signature[paramB:], S[:]) -} - -// Sign signs the message with privateKey and returns a signature. -// This function supports the signature variant defined in RFC-8032: Ed448, -// also known as the pure version of EdDSA. -// It will panic if len(privateKey) is not PrivateKeySize. -func Sign(priv PrivateKey, message []byte, ctx string) []byte { - signature := make([]byte, SignatureSize) - signAll(signature, priv, message, []byte(ctx), false) - return signature -} - -// SignPh creates a signature of a message given a keypair. -// This function supports the signature variant defined in RFC-8032: Ed448ph, -// meaning it internally hashes the message using SHAKE-256. -// Context could be passed to this function, which length should be no more than -// 255. It can be empty. -func SignPh(priv PrivateKey, message []byte, ctx string) []byte { - signature := make([]byte, SignatureSize) - signAll(signature, priv, message, []byte(ctx), true) - return signature -} - -func verify(public PublicKey, message, signature, ctx []byte, preHash bool) bool { - if len(public) != PublicKeySize || - len(signature) != SignatureSize || - len(ctx) > ContextMaxSize || - !isLessThanOrder(signature[paramB:]) { - return false - } - - P, err := goldilocks.FromBytes(public) - if err != nil { - return false - } - - H := sha3.NewShake256() - var PHM []byte - - if preHash { - var h [64]byte - _, _ = H.Write(message) - _, _ = H.Read(h[:]) - PHM = h[:] - H.Reset() - } else { - PHM = message - } - - var hRAM [hashSize]byte - R := signature[:paramB] - - writeDom(&H, ctx, preHash) - - _, _ = H.Write(R) - _, _ = H.Write(public) - _, _ = H.Write(PHM) - _, _ = H.Read(hRAM[:]) - - k := &goldilocks.Scalar{} - k.FromBytes(hRAM[:]) - S := &goldilocks.Scalar{} - S.FromBytes(signature[paramB:]) - - encR := (&[paramB]byte{})[:] - P.Neg() - _ = goldilocks.Curve{}.CombinedMult(S, k, P).ToBytes(encR) - return bytes.Equal(R, encR) -} - -// VerifyAny returns true if the signature is valid. Failure cases are invalid -// signature, or when the public key cannot be decoded. -// This function supports all the two signature variants defined in RFC-8032, -// namely Ed448 (or pure EdDSA) and Ed448Ph. -// The opts.HashFunc() must return zero, this can be achieved by passing -// crypto.Hash(0) as the value for opts. -// Use a SignerOptions struct to pass a context string for signing. -func VerifyAny(public PublicKey, message, signature []byte, opts crypto.SignerOpts) bool { - var ctx string - var scheme SchemeID - if o, ok := opts.(SignerOptions); ok { - ctx = o.Context - scheme = o.Scheme - } - - switch true { - case scheme == ED448 && opts.HashFunc() == crypto.Hash(0): - return Verify(public, message, signature, ctx) - case scheme == ED448Ph && opts.HashFunc() == crypto.Hash(0): - return VerifyPh(public, message, signature, ctx) - default: - return false - } -} - -// Verify returns true if the signature is valid. Failure cases are invalid -// signature, or when the public key cannot be decoded. -// This function supports the signature variant defined in RFC-8032: Ed448, -// also known as the pure version of EdDSA. -func Verify(public PublicKey, message, signature []byte, ctx string) bool { - return verify(public, message, signature, []byte(ctx), false) -} - -// VerifyPh returns true if the signature is valid. Failure cases are invalid -// signature, or when the public key cannot be decoded. -// This function supports the signature variant defined in RFC-8032: Ed448ph, -// meaning it internally hashes the message using SHAKE-256. -// Context could be passed to this function, which length should be no more than -// 255. It can be empty. -func VerifyPh(public PublicKey, message, signature []byte, ctx string) bool { - return verify(public, message, signature, []byte(ctx), true) -} - -func deriveSecretScalar(s *goldilocks.Scalar, h []byte) { - h[0] &= 0xFC // The two least significant bits of the first octet are cleared, - h[paramB-1] = 0x00 // all eight bits the last octet are cleared, and - h[paramB-2] |= 0x80 // the highest bit of the second to last octet is set. - s.FromBytes(h[:paramB]) -} - -// isLessThanOrder returns true if 0 <= x < order and if the last byte of x is zero. -func isLessThanOrder(x []byte) bool { - order := goldilocks.Curve{}.Order() - i := len(order) - 1 - for i > 0 && x[i] == order[i] { - i-- - } - return x[paramB-1] == 0 && x[i] < order[i] -} - -func writeDom(h io.Writer, ctx []byte, preHash bool) { - dom4 := "SigEd448" - _, _ = h.Write([]byte(dom4)) - - if preHash { - _, _ = h.Write([]byte{byte(0x01), byte(len(ctx))}) - } else { - _, _ = h.Write([]byte{byte(0x00), byte(len(ctx))}) - } - _, _ = h.Write(ctx) -} diff --git a/vendor/github.com/cloudflare/circl/sign/ed448/signapi.go b/vendor/github.com/cloudflare/circl/sign/ed448/signapi.go deleted file mode 100644 index 22da8bc0a57..00000000000 --- a/vendor/github.com/cloudflare/circl/sign/ed448/signapi.go +++ /dev/null @@ -1,87 +0,0 @@ -package ed448 - -import ( - "crypto/rand" - "encoding/asn1" - - "github.com/cloudflare/circl/sign" -) - -var sch sign.Scheme = &scheme{} - -// Scheme returns a signature interface. -func Scheme() sign.Scheme { return sch } - -type scheme struct{} - -func (*scheme) Name() string { return "Ed448" } -func (*scheme) PublicKeySize() int { return PublicKeySize } -func (*scheme) PrivateKeySize() int { return PrivateKeySize } -func (*scheme) SignatureSize() int { return SignatureSize } -func (*scheme) SeedSize() int { return SeedSize } -func (*scheme) TLSIdentifier() uint { return 0x0808 } -func (*scheme) SupportsContext() bool { return true } -func (*scheme) Oid() asn1.ObjectIdentifier { - return asn1.ObjectIdentifier{1, 3, 101, 113} -} - -func (*scheme) GenerateKey() (sign.PublicKey, sign.PrivateKey, error) { - return GenerateKey(rand.Reader) -} - -func (*scheme) Sign( - sk sign.PrivateKey, - message []byte, - opts *sign.SignatureOpts, -) []byte { - priv, ok := sk.(PrivateKey) - if !ok { - panic(sign.ErrTypeMismatch) - } - ctx := "" - if opts != nil { - ctx = opts.Context - } - return Sign(priv, message, ctx) -} - -func (*scheme) Verify( - pk sign.PublicKey, - message, signature []byte, - opts *sign.SignatureOpts, -) bool { - pub, ok := pk.(PublicKey) - if !ok { - panic(sign.ErrTypeMismatch) - } - ctx := "" - if opts != nil { - ctx = opts.Context - } - return Verify(pub, message, signature, ctx) -} - -func (*scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) { - privateKey := NewKeyFromSeed(seed) - publicKey := make(PublicKey, PublicKeySize) - copy(publicKey, privateKey[SeedSize:]) - return publicKey, privateKey -} - -func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (sign.PublicKey, error) { - if len(buf) < PublicKeySize { - return nil, sign.ErrPubKeySize - } - pub := make(PublicKey, PublicKeySize) - copy(pub, buf[:PublicKeySize]) - return pub, nil -} - -func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (sign.PrivateKey, error) { - if len(buf) < PrivateKeySize { - return nil, sign.ErrPrivKeySize - } - priv := make(PrivateKey, PrivateKeySize) - copy(priv, buf[:PrivateKeySize]) - return priv, nil -} diff --git a/vendor/github.com/cloudflare/circl/sign/sign.go b/vendor/github.com/cloudflare/circl/sign/sign.go deleted file mode 100644 index 557d6f09605..00000000000 --- a/vendor/github.com/cloudflare/circl/sign/sign.go +++ /dev/null @@ -1,113 +0,0 @@ -// Package sign provides unified interfaces for signature schemes. -// -// A register of schemes is available in the package -// -// github.com/cloudflare/circl/sign/schemes -package sign - -import ( - "crypto" - "encoding" - "errors" -) - -type SignatureOpts struct { - // If non-empty, includes the given context in the signature if supported - // and will cause an error during signing otherwise. - Context string -} - -// A public key is used to verify a signature set by the corresponding private -// key. -type PublicKey interface { - // Returns the signature scheme for this public key. - Scheme() Scheme - Equal(crypto.PublicKey) bool - encoding.BinaryMarshaler - crypto.PublicKey -} - -// A private key allows one to create signatures. -type PrivateKey interface { - // Returns the signature scheme for this private key. - Scheme() Scheme - Equal(crypto.PrivateKey) bool - // For compatibility with Go standard library - crypto.Signer - crypto.PrivateKey - encoding.BinaryMarshaler -} - -// A Scheme represents a specific instance of a signature scheme. -type Scheme interface { - // Name of the scheme. - Name() string - - // GenerateKey creates a new key-pair. - GenerateKey() (PublicKey, PrivateKey, error) - - // Creates a signature using the PrivateKey on the given message and - // returns the signature. opts are additional options which can be nil. - // - // Panics if key is nil or wrong type or opts context is not supported. - Sign(sk PrivateKey, message []byte, opts *SignatureOpts) []byte - - // Checks whether the given signature is a valid signature set by - // the private key corresponding to the given public key on the - // given message. opts are additional options which can be nil. - // - // Panics if key is nil or wrong type or opts context is not supported. - Verify(pk PublicKey, message []byte, signature []byte, opts *SignatureOpts) bool - - // Deterministically derives a keypair from a seed. If you're unsure, - // you're better off using GenerateKey(). - // - // Panics if seed is not of length SeedSize(). - DeriveKey(seed []byte) (PublicKey, PrivateKey) - - // Unmarshals a PublicKey from the provided buffer. - UnmarshalBinaryPublicKey([]byte) (PublicKey, error) - - // Unmarshals a PublicKey from the provided buffer. - UnmarshalBinaryPrivateKey([]byte) (PrivateKey, error) - - // Size of binary marshalled public keys. - PublicKeySize() int - - // Size of binary marshalled public keys. - PrivateKeySize() int - - // Size of signatures. - SignatureSize() int - - // Size of seeds. - SeedSize() int - - // Returns whether contexts are supported. - SupportsContext() bool -} - -var ( - // ErrTypeMismatch is the error used if types of, for instance, private - // and public keys don't match. - ErrTypeMismatch = errors.New("types mismatch") - - // ErrSeedSize is the error used if the provided seed is of the wrong - // size. - ErrSeedSize = errors.New("wrong seed size") - - // ErrPubKeySize is the error used if the provided public key is of - // the wrong size. - ErrPubKeySize = errors.New("wrong size for public key") - - // ErrPrivKeySize is the error used if the provided private key is of - // the wrong size. - ErrPrivKeySize = errors.New("wrong size for private key") - - // ErrContextNotSupported is the error used if a context is not - // supported. - ErrContextNotSupported = errors.New("context not supported") - - // ErrContextTooLong is the error used if the context string is too long. - ErrContextTooLong = errors.New("context string too long") -) diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/.travis.yml b/vendor/github.com/cloudfoundry/jibber_jabber/.travis.yml deleted file mode 100644 index b19c2e53535..00000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: go -go: - - 1.2 -before_install: -- go get github.com/onsi/ginkgo/... -- go get github.com/onsi/gomega/... -- go install github.com/onsi/ginkgo/ginkgo -script: PATH=$PATH:$HOME/gopath/bin ginkgo -r . -branches: - only: - - master diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/LICENSE b/vendor/github.com/cloudfoundry/jibber_jabber/LICENSE deleted file mode 100644 index 915b208920b..00000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright 2014 Pivotal - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/README.md b/vendor/github.com/cloudfoundry/jibber_jabber/README.md deleted file mode 100644 index d696eb6b6d0..00000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Jibber Jabber [![Build Status](https://travis-ci.org/cloudfoundry/jibber_jabber.svg?branch=master)](https://travis-ci.org/cloudfoundry/jibber_jabber) -Jibber Jabber is a GoLang Library that can be used to detect an operating system's current language. - -### OS Support - -OSX and Linux via the `LC_ALL` and `LANG` environment variables. These are standard variables that are used in ALL versions of UNIX for language detection. - -Windows via [GetUserDefaultLocaleName](http://msdn.microsoft.com/en-us/library/windows/desktop/dd318136.aspx) and [GetSystemDefaultLocaleName](http://msdn.microsoft.com/en-us/library/windows/desktop/dd318122.aspx) system calls. These calls are supported in Windows Vista and up. - -# Usage -Add the following line to your go `import`: - -``` - "github.com/cloudfoundry/jibber_jabber" -``` - -### DetectIETF -`DetectIETF` will return the current locale as a string. The format of the locale will be the [ISO 639](http://en.wikipedia.org/wiki/ISO_639) two-letter language code, a DASH, then an [ISO 3166](http://en.wikipedia.org/wiki/ISO_3166-1) two-letter country code. - -``` - userLocale, err := jibber_jabber.DetectIETF() - println("Locale:", userLocale) -``` - -### DetectLanguage -`DetectLanguage` will return the current languge as a string. The format will be the [ISO 639](http://en.wikipedia.org/wiki/ISO_639) two-letter language code. - -``` - userLanguage, err := jibber_jabber.DetectLanguage() - println("Language:", userLanguage) -``` - -### DetectTerritory -`DetectTerritory` will return the current locale territory as a string. The format will be the [ISO 3166](http://en.wikipedia.org/wiki/ISO_3166-1) two-letter country code. - -``` - localeTerritory, err := jibber_jabber.DetectTerritory() - println("Territory:", localeTerritory) -``` - -### Errors -All the Detect commands will return an error if they are unable to read the Locale from the system. - -For Windows, additional error information is provided due to the nature of the system call being used. diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber.go deleted file mode 100644 index 45d288ea87a..00000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber.go +++ /dev/null @@ -1,22 +0,0 @@ -package jibber_jabber - -import ( - "strings" -) - -const ( - COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE = "Could not detect Language" -) - -func splitLocale(locale string) (string, string) { - formattedLocale := strings.Split(locale, ".")[0] - formattedLocale = strings.Replace(formattedLocale, "-", "_", -1) - - pieces := strings.Split(formattedLocale, "_") - language := pieces[0] - territory := "" - if len(pieces) > 1 { - territory = strings.Split(formattedLocale, "_")[1] - } - return language, territory -} diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix.go deleted file mode 100644 index 374d7617630..00000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix.go +++ /dev/null @@ -1,57 +0,0 @@ -// +build darwin freebsd linux netbsd openbsd - -package jibber_jabber - -import ( - "errors" - "os" - "strings" -) - -func getLangFromEnv() (locale string) { - locale = os.Getenv("LC_ALL") - if locale == "" { - locale = os.Getenv("LANG") - } - return -} - -func getUnixLocale() (unix_locale string, err error) { - unix_locale = getLangFromEnv() - if unix_locale == "" { - err = errors.New(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE) - } - - return -} - -func DetectIETF() (locale string, err error) { - unix_locale, err := getUnixLocale() - if err == nil { - language, territory := splitLocale(unix_locale) - locale = language - if territory != "" { - locale = strings.Join([]string{language, territory}, "-") - } - } - - return -} - -func DetectLanguage() (language string, err error) { - unix_locale, err := getUnixLocale() - if err == nil { - language, _ = splitLocale(unix_locale) - } - - return -} - -func DetectTerritory() (territory string, err error) { - unix_locale, err := getUnixLocale() - if err == nil { - _, territory = splitLocale(unix_locale) - } - - return -} diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows.go deleted file mode 100644 index 1acd96c38b1..00000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows.go +++ /dev/null @@ -1,114 +0,0 @@ -// +build windows - -package jibber_jabber - -import ( - "errors" - "syscall" - "unsafe" -) - -const LOCALE_NAME_MAX_LENGTH uint32 = 85 - -var SUPPORTED_LOCALES = map[uintptr]string{ - 0x0407: "de-DE", - 0x0409: "en-US", - 0x0c0a: "es-ES", //or is it 0x040a - 0x040c: "fr-FR", - 0x0410: "it-IT", - 0x0411: "ja-JA", - 0x0412: "ko_KR", - 0x0416: "pt-BR", - //0x0419: "ru_RU", - Will add support for Russian when nicksnyder/go-i18n supports Russian - 0x0804: "zh-CN", - 0x0c04: "zh-HK", - 0x0404: "zh-TW", -} - -func getWindowsLocaleFrom(sysCall string) (locale string, err error) { - buffer := make([]uint16, LOCALE_NAME_MAX_LENGTH) - - dll := syscall.MustLoadDLL("kernel32") - proc := dll.MustFindProc(sysCall) - r, _, dllError := proc.Call(uintptr(unsafe.Pointer(&buffer[0])), uintptr(LOCALE_NAME_MAX_LENGTH)) - if r == 0 { - err = errors.New(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE + ":\n" + dllError.Error()) - return - } - - locale = syscall.UTF16ToString(buffer) - - return -} - -func getAllWindowsLocaleFrom(sysCall string) (string, error) { - dll, err := syscall.LoadDLL("kernel32") - if err != nil { - return "", errors.New("Could not find kernel32 dll") - } - - proc, err := dll.FindProc(sysCall) - if err != nil { - return "", err - } - - locale, _, dllError := proc.Call() - if locale == 0 { - return "", errors.New(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE + ":\n" + dllError.Error()) - } - - return SUPPORTED_LOCALES[locale], nil -} - -func getWindowsLocale() (locale string, err error) { - dll, err := syscall.LoadDLL("kernel32") - if err != nil { - return "", errors.New("Could not find kernel32 dll") - } - - proc, err := dll.FindProc("GetVersion") - if err != nil { - return "", err - } - - v, _, _ := proc.Call() - windowsVersion := byte(v) - isVistaOrGreater := (windowsVersion >= 6) - - if isVistaOrGreater { - locale, err = getWindowsLocaleFrom("GetUserDefaultLocaleName") - if err != nil { - locale, err = getWindowsLocaleFrom("GetSystemDefaultLocaleName") - } - } else if !isVistaOrGreater { - locale, err = getAllWindowsLocaleFrom("GetUserDefaultLCID") - if err != nil { - locale, err = getAllWindowsLocaleFrom("GetSystemDefaultLCID") - } - } else { - panic(v) - } - return -} -func DetectIETF() (locale string, err error) { - locale, err = getWindowsLocale() - return -} - -func DetectLanguage() (language string, err error) { - windows_locale, err := getWindowsLocale() - if err == nil { - language, _ = splitLocale(windows_locale) - } - - return -} - -func DetectTerritory() (territory string, err error) { - windows_locale, err := getWindowsLocale() - if err == nil { - _, territory = splitLocale(windows_locale) - } - - return -} diff --git a/vendor/github.com/creack/pty/.gitignore b/vendor/github.com/creack/pty/.gitignore deleted file mode 100644 index 1f0a99f2f2b..00000000000 --- a/vendor/github.com/creack/pty/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -[568].out -_go* -_test* -_obj diff --git a/vendor/github.com/creack/pty/Dockerfile.riscv b/vendor/github.com/creack/pty/Dockerfile.riscv deleted file mode 100644 index adfdf82c895..00000000000 --- a/vendor/github.com/creack/pty/Dockerfile.riscv +++ /dev/null @@ -1,14 +0,0 @@ -FROM golang:1.13 - -# Clone and complie a riscv compatible version of the go compiler. -RUN git clone https://review.gerrithub.io/riscv/riscv-go /riscv-go -# riscvdev branch HEAD as of 2019-06-29. -RUN cd /riscv-go && git checkout 04885fddd096d09d4450726064d06dd107e374bf -ENV PATH=/riscv-go/misc/riscv:/riscv-go/bin:$PATH -RUN cd /riscv-go/src && GOROOT_BOOTSTRAP=$(go env GOROOT) ./make.bash -ENV GOROOT=/riscv-go - -# Make sure we compile. -WORKDIR pty -ADD . . -RUN GOOS=linux GOARCH=riscv go build diff --git a/vendor/github.com/creack/pty/LICENSE b/vendor/github.com/creack/pty/LICENSE deleted file mode 100644 index 6b7558b6b42..00000000000 --- a/vendor/github.com/creack/pty/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2011 Keith Rarick - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall -be included in all copies or substantial portions of the -Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY -KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/creack/pty/README.md b/vendor/github.com/creack/pty/README.md deleted file mode 100644 index 5275014a7a7..00000000000 --- a/vendor/github.com/creack/pty/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# pty - -Pty is a Go package for using unix pseudo-terminals. - -## Install - - go get github.com/creack/pty - -## Example - -### Command - -```go -package main - -import ( - "github.com/creack/pty" - "io" - "os" - "os/exec" -) - -func main() { - c := exec.Command("grep", "--color=auto", "bar") - f, err := pty.Start(c) - if err != nil { - panic(err) - } - - go func() { - f.Write([]byte("foo\n")) - f.Write([]byte("bar\n")) - f.Write([]byte("baz\n")) - f.Write([]byte{4}) // EOT - }() - io.Copy(os.Stdout, f) -} -``` - -### Shell - -```go -package main - -import ( - "io" - "log" - "os" - "os/exec" - "os/signal" - "syscall" - - "github.com/creack/pty" - "golang.org/x/crypto/ssh/terminal" -) - -func test() error { - // Create arbitrary command. - c := exec.Command("bash") - - // Start the command with a pty. - ptmx, err := pty.Start(c) - if err != nil { - return err - } - // Make sure to close the pty at the end. - defer func() { _ = ptmx.Close() }() // Best effort. - - // Handle pty size. - ch := make(chan os.Signal, 1) - signal.Notify(ch, syscall.SIGWINCH) - go func() { - for range ch { - if err := pty.InheritSize(os.Stdin, ptmx); err != nil { - log.Printf("error resizing pty: %s", err) - } - } - }() - ch <- syscall.SIGWINCH // Initial resize. - - // Set stdin in raw mode. - oldState, err := terminal.MakeRaw(int(os.Stdin.Fd())) - if err != nil { - panic(err) - } - defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort. - - // Copy stdin to the pty and the pty to stdout. - go func() { _, _ = io.Copy(ptmx, os.Stdin) }() - _, _ = io.Copy(os.Stdout, ptmx) - - return nil -} - -func main() { - if err := test(); err != nil { - log.Fatal(err) - } -} -``` diff --git a/vendor/github.com/creack/pty/doc.go b/vendor/github.com/creack/pty/doc.go deleted file mode 100644 index 190cfbea929..00000000000 --- a/vendor/github.com/creack/pty/doc.go +++ /dev/null @@ -1,16 +0,0 @@ -// Package pty provides functions for working with Unix terminals. -package pty - -import ( - "errors" - "os" -) - -// ErrUnsupported is returned if a function is not -// available on the current platform. -var ErrUnsupported = errors.New("unsupported") - -// Opens a pty and its corresponding tty. -func Open() (pty, tty *os.File, err error) { - return open() -} diff --git a/vendor/github.com/creack/pty/ioctl.go b/vendor/github.com/creack/pty/ioctl.go deleted file mode 100644 index c85cdcd14a0..00000000000 --- a/vendor/github.com/creack/pty/ioctl.go +++ /dev/null @@ -1,13 +0,0 @@ -// +build !windows,!solaris - -package pty - -import "syscall" - -func ioctl(fd, cmd, ptr uintptr) error { - _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) - if e != 0 { - return e - } - return nil -} diff --git a/vendor/github.com/creack/pty/ioctl_bsd.go b/vendor/github.com/creack/pty/ioctl_bsd.go deleted file mode 100644 index 73b12c53cf4..00000000000 --- a/vendor/github.com/creack/pty/ioctl_bsd.go +++ /dev/null @@ -1,39 +0,0 @@ -// +build darwin dragonfly freebsd netbsd openbsd - -package pty - -// from -const ( - _IOC_VOID uintptr = 0x20000000 - _IOC_OUT uintptr = 0x40000000 - _IOC_IN uintptr = 0x80000000 - _IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN - _IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN - - _IOC_PARAM_SHIFT = 13 - _IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1 -) - -func _IOC_PARM_LEN(ioctl uintptr) uintptr { - return (ioctl >> 16) & _IOC_PARAM_MASK -} - -func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr { - return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num -} - -func _IO(group byte, ioctl_num uintptr) uintptr { - return _IOC(_IOC_VOID, group, ioctl_num, 0) -} - -func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr { - return _IOC(_IOC_OUT, group, ioctl_num, param_len) -} - -func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr { - return _IOC(_IOC_IN, group, ioctl_num, param_len) -} - -func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr { - return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len) -} diff --git a/vendor/github.com/creack/pty/ioctl_solaris.go b/vendor/github.com/creack/pty/ioctl_solaris.go deleted file mode 100644 index f63985f34ce..00000000000 --- a/vendor/github.com/creack/pty/ioctl_solaris.go +++ /dev/null @@ -1,30 +0,0 @@ -package pty - -import ( - "golang.org/x/sys/unix" - "unsafe" -) - -const ( - // see /usr/include/sys/stropts.h - I_PUSH = uintptr((int32('S')<<8 | 002)) - I_STR = uintptr((int32('S')<<8 | 010)) - I_FIND = uintptr((int32('S')<<8 | 013)) - // see /usr/include/sys/ptms.h - ISPTM = (int32('P') << 8) | 1 - UNLKPT = (int32('P') << 8) | 2 - PTSSTTY = (int32('P') << 8) | 3 - ZONEPT = (int32('P') << 8) | 4 - OWNERPT = (int32('P') << 8) | 5 -) - -type strioctl struct { - ic_cmd int32 - ic_timout int32 - ic_len int32 - ic_dp unsafe.Pointer -} - -func ioctl(fd, cmd, ptr uintptr) error { - return unix.IoctlSetInt(int(fd), uint(cmd), int(ptr)) -} diff --git a/vendor/github.com/creack/pty/mktypes.bash b/vendor/github.com/creack/pty/mktypes.bash deleted file mode 100644 index 82ee16721cb..00000000000 --- a/vendor/github.com/creack/pty/mktypes.bash +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -GOOSARCH="${GOOS}_${GOARCH}" -case "$GOOSARCH" in -_* | *_ | _) - echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2 - exit 1 - ;; -esac - -GODEFS="go tool cgo -godefs" - -$GODEFS types.go |gofmt > ztypes_$GOARCH.go - -case $GOOS in -freebsd|dragonfly|openbsd) - $GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go - ;; -esac diff --git a/vendor/github.com/creack/pty/pty_darwin.go b/vendor/github.com/creack/pty/pty_darwin.go deleted file mode 100644 index 6344b6b0efb..00000000000 --- a/vendor/github.com/creack/pty/pty_darwin.go +++ /dev/null @@ -1,65 +0,0 @@ -package pty - -import ( - "errors" - "os" - "syscall" - "unsafe" -) - -func open() (pty, tty *os.File, err error) { - pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0) - if err != nil { - return nil, nil, err - } - p := os.NewFile(uintptr(pFD), "/dev/ptmx") - // In case of error after this point, make sure we close the ptmx fd. - defer func() { - if err != nil { - _ = p.Close() // Best effort. - } - }() - - sname, err := ptsname(p) - if err != nil { - return nil, nil, err - } - - if err := grantpt(p); err != nil { - return nil, nil, err - } - - if err := unlockpt(p); err != nil { - return nil, nil, err - } - - t, err := os.OpenFile(sname, os.O_RDWR, 0) - if err != nil { - return nil, nil, err - } - return p, t, nil -} - -func ptsname(f *os.File) (string, error) { - n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME)) - - err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0]))) - if err != nil { - return "", err - } - - for i, c := range n { - if c == 0 { - return string(n[:i]), nil - } - } - return "", errors.New("TIOCPTYGNAME string not NUL-terminated") -} - -func grantpt(f *os.File) error { - return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0) -} - -func unlockpt(f *os.File) error { - return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0) -} diff --git a/vendor/github.com/creack/pty/pty_dragonfly.go b/vendor/github.com/creack/pty/pty_dragonfly.go deleted file mode 100644 index b7d1f20f29e..00000000000 --- a/vendor/github.com/creack/pty/pty_dragonfly.go +++ /dev/null @@ -1,80 +0,0 @@ -package pty - -import ( - "errors" - "os" - "strings" - "syscall" - "unsafe" -) - -// same code as pty_darwin.go -func open() (pty, tty *os.File, err error) { - p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) - if err != nil { - return nil, nil, err - } - // In case of error after this point, make sure we close the ptmx fd. - defer func() { - if err != nil { - _ = p.Close() // Best effort. - } - }() - - sname, err := ptsname(p) - if err != nil { - return nil, nil, err - } - - if err := grantpt(p); err != nil { - return nil, nil, err - } - - if err := unlockpt(p); err != nil { - return nil, nil, err - } - - t, err := os.OpenFile(sname, os.O_RDWR, 0) - if err != nil { - return nil, nil, err - } - return p, t, nil -} - -func grantpt(f *os.File) error { - _, err := isptmaster(f.Fd()) - return err -} - -func unlockpt(f *os.File) error { - _, err := isptmaster(f.Fd()) - return err -} - -func isptmaster(fd uintptr) (bool, error) { - err := ioctl(fd, syscall.TIOCISPTMASTER, 0) - return err == nil, err -} - -var ( - emptyFiodgnameArg fiodgnameArg - ioctl_FIODNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg)) -) - -func ptsname(f *os.File) (string, error) { - name := make([]byte, _C_SPECNAMELEN) - fa := fiodgnameArg{Name: (*byte)(unsafe.Pointer(&name[0])), Len: _C_SPECNAMELEN, Pad_cgo_0: [4]byte{0, 0, 0, 0}} - - err := ioctl(f.Fd(), ioctl_FIODNAME, uintptr(unsafe.Pointer(&fa))) - if err != nil { - return "", err - } - - for i, c := range name { - if c == 0 { - s := "/dev/" + string(name[:i]) - return strings.Replace(s, "ptm", "pts", -1), nil - } - } - return "", errors.New("TIOCPTYGNAME string not NUL-terminated") -} diff --git a/vendor/github.com/creack/pty/pty_freebsd.go b/vendor/github.com/creack/pty/pty_freebsd.go deleted file mode 100644 index 63b6d91337a..00000000000 --- a/vendor/github.com/creack/pty/pty_freebsd.go +++ /dev/null @@ -1,78 +0,0 @@ -package pty - -import ( - "errors" - "os" - "syscall" - "unsafe" -) - -func posixOpenpt(oflag int) (fd int, err error) { - r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0) - fd = int(r0) - if e1 != 0 { - err = e1 - } - return fd, err -} - -func open() (pty, tty *os.File, err error) { - fd, err := posixOpenpt(syscall.O_RDWR | syscall.O_CLOEXEC) - if err != nil { - return nil, nil, err - } - p := os.NewFile(uintptr(fd), "/dev/pts") - // In case of error after this point, make sure we close the pts fd. - defer func() { - if err != nil { - _ = p.Close() // Best effort. - } - }() - - sname, err := ptsname(p) - if err != nil { - return nil, nil, err - } - - t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0) - if err != nil { - return nil, nil, err - } - return p, t, nil -} - -func isptmaster(fd uintptr) (bool, error) { - err := ioctl(fd, syscall.TIOCPTMASTER, 0) - return err == nil, err -} - -var ( - emptyFiodgnameArg fiodgnameArg - ioctlFIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg)) -) - -func ptsname(f *os.File) (string, error) { - master, err := isptmaster(f.Fd()) - if err != nil { - return "", err - } - if !master { - return "", syscall.EINVAL - } - - const n = _C_SPECNAMELEN + 1 - var ( - buf = make([]byte, n) - arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))} - ) - if err := ioctl(f.Fd(), ioctlFIODGNAME, uintptr(unsafe.Pointer(&arg))); err != nil { - return "", err - } - - for i, c := range buf { - if c == 0 { - return string(buf[:i]), nil - } - } - return "", errors.New("FIODGNAME string not NUL-terminated") -} diff --git a/vendor/github.com/creack/pty/pty_linux.go b/vendor/github.com/creack/pty/pty_linux.go deleted file mode 100644 index 4a833de1849..00000000000 --- a/vendor/github.com/creack/pty/pty_linux.go +++ /dev/null @@ -1,51 +0,0 @@ -package pty - -import ( - "os" - "strconv" - "syscall" - "unsafe" -) - -func open() (pty, tty *os.File, err error) { - p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) - if err != nil { - return nil, nil, err - } - // In case of error after this point, make sure we close the ptmx fd. - defer func() { - if err != nil { - _ = p.Close() // Best effort. - } - }() - - sname, err := ptsname(p) - if err != nil { - return nil, nil, err - } - - if err := unlockpt(p); err != nil { - return nil, nil, err - } - - t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0) - if err != nil { - return nil, nil, err - } - return p, t, nil -} - -func ptsname(f *os.File) (string, error) { - var n _C_uint - err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))) - if err != nil { - return "", err - } - return "/dev/pts/" + strconv.Itoa(int(n)), nil -} - -func unlockpt(f *os.File) error { - var u _C_int - // use TIOCSPTLCK with a pointer to zero to clear the lock - return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) -} diff --git a/vendor/github.com/creack/pty/pty_openbsd.go b/vendor/github.com/creack/pty/pty_openbsd.go deleted file mode 100644 index a6a35d1e677..00000000000 --- a/vendor/github.com/creack/pty/pty_openbsd.go +++ /dev/null @@ -1,33 +0,0 @@ -package pty - -import ( - "os" - "syscall" - "unsafe" -) - -func open() (pty, tty *os.File, err error) { - /* - * from ptm(4): - * The PTMGET command allocates a free pseudo terminal, changes its - * ownership to the caller, revokes the access privileges for all previous - * users, opens the file descriptors for the pty and tty devices and - * returns them to the caller in struct ptmget. - */ - - p, err := os.OpenFile("/dev/ptm", os.O_RDWR|syscall.O_CLOEXEC, 0) - if err != nil { - return nil, nil, err - } - defer p.Close() - - var ptm ptmget - if err := ioctl(p.Fd(), uintptr(ioctl_PTMGET), uintptr(unsafe.Pointer(&ptm))); err != nil { - return nil, nil, err - } - - pty = os.NewFile(uintptr(ptm.Cfd), "/dev/ptm") - tty = os.NewFile(uintptr(ptm.Sfd), "/dev/ptm") - - return pty, tty, nil -} diff --git a/vendor/github.com/creack/pty/pty_solaris.go b/vendor/github.com/creack/pty/pty_solaris.go deleted file mode 100644 index 09ec1b7978a..00000000000 --- a/vendor/github.com/creack/pty/pty_solaris.go +++ /dev/null @@ -1,139 +0,0 @@ -package pty - -/* based on: -http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/pt.c -*/ - -import ( - "errors" - "golang.org/x/sys/unix" - "os" - "strconv" - "syscall" - "unsafe" -) - -const NODEV = ^uint64(0) - -func open() (pty, tty *os.File, err error) { - masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|unix.O_NOCTTY, 0) - //masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC|unix.O_NOCTTY, 0) - if err != nil { - return nil, nil, err - } - p := os.NewFile(uintptr(masterfd), "/dev/ptmx") - - sname, err := ptsname(p) - if err != nil { - return nil, nil, err - } - - err = grantpt(p) - if err != nil { - return nil, nil, err - } - - err = unlockpt(p) - if err != nil { - return nil, nil, err - } - - slavefd, err := syscall.Open(sname, os.O_RDWR|unix.O_NOCTTY, 0) - if err != nil { - return nil, nil, err - } - t := os.NewFile(uintptr(slavefd), sname) - - // pushing terminal driver STREAMS modules as per pts(7) - for _, mod := range([]string{"ptem", "ldterm", "ttcompat"}) { - err = streams_push(t, mod) - if err != nil { - return nil, nil, err - } - } - - return p, t, nil -} - -func minor(x uint64) uint64 { - return x & 0377 -} - -func ptsdev(fd uintptr) uint64 { - istr := strioctl{ISPTM, 0, 0, nil} - err := ioctl(fd, I_STR, uintptr(unsafe.Pointer(&istr))) - if err != nil { - return NODEV - } - var status unix.Stat_t - err = unix.Fstat(int(fd), &status) - if err != nil { - return NODEV - } - return uint64(minor(status.Rdev)) -} - -func ptsname(f *os.File) (string, error) { - dev := ptsdev(f.Fd()) - if dev == NODEV { - return "", errors.New("not a master pty") - } - fn := "/dev/pts/" + strconv.FormatInt(int64(dev), 10) - // access(2) creates the slave device (if the pty exists) - // F_OK == 0 (unistd.h) - err := unix.Access(fn, 0) - if err != nil { - return "", err - } - return fn, nil -} - -type pt_own struct { - pto_ruid int32 - pto_rgid int32 -} - -func grantpt(f *os.File) error { - if ptsdev(f.Fd()) == NODEV { - return errors.New("not a master pty") - } - var pto pt_own - pto.pto_ruid = int32(os.Getuid()) - // XXX should first attempt to get gid of DEFAULT_TTY_GROUP="tty" - pto.pto_rgid = int32(os.Getgid()) - var istr strioctl - istr.ic_cmd = OWNERPT - istr.ic_timout = 0 - istr.ic_len = int32(unsafe.Sizeof(istr)) - istr.ic_dp = unsafe.Pointer(&pto) - err := ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr))) - if err != nil { - return errors.New("access denied") - } - return nil -} - -func unlockpt(f *os.File) error { - istr := strioctl{UNLKPT, 0, 0, nil} - return ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr))) -} - -// push STREAMS modules if not already done so -func streams_push(f *os.File, mod string) error { - var err error - buf := []byte(mod) - // XXX I_FIND is not returning an error when the module - // is already pushed even though truss reports a return - // value of 1. A bug in the Go Solaris syscall interface? - // XXX without this we are at risk of the issue - // https://www.illumos.org/issues/9042 - // but since we are not using libc or XPG4.2, we should not be - // double-pushing modules - - err = ioctl(f.Fd(), I_FIND, uintptr(unsafe.Pointer(&buf[0]))) - if err != nil { - return nil - } - err = ioctl(f.Fd(), I_PUSH, uintptr(unsafe.Pointer(&buf[0]))) - return err -} diff --git a/vendor/github.com/creack/pty/pty_unsupported.go b/vendor/github.com/creack/pty/pty_unsupported.go deleted file mode 100644 index ceb425b19c9..00000000000 --- a/vendor/github.com/creack/pty/pty_unsupported.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd,!solaris - -package pty - -import ( - "os" -) - -func open() (pty, tty *os.File, err error) { - return nil, nil, ErrUnsupported -} diff --git a/vendor/github.com/creack/pty/run.go b/vendor/github.com/creack/pty/run.go deleted file mode 100644 index b07942514d6..00000000000 --- a/vendor/github.com/creack/pty/run.go +++ /dev/null @@ -1,74 +0,0 @@ -// +build !windows - -package pty - -import ( - "os" - "os/exec" - "syscall" -) - -// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout, -// and c.Stderr, calls c.Start, and returns the File of the tty's -// corresponding pty. -// -// Starts the process in a new session and sets the controlling terminal. -func Start(c *exec.Cmd) (pty *os.File, err error) { - return StartWithSize(c, nil) -} - -// StartWithSize assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout, -// and c.Stderr, calls c.Start, and returns the File of the tty's -// corresponding pty. -// -// This will resize the pty to the specified size before starting the command. -// Starts the process in a new session and sets the controlling terminal. -func StartWithSize(c *exec.Cmd, sz *Winsize) (pty *os.File, err error) { - if c.SysProcAttr == nil { - c.SysProcAttr = &syscall.SysProcAttr{} - } - c.SysProcAttr.Setsid = true - c.SysProcAttr.Setctty = true - return StartWithAttrs(c, sz, c.SysProcAttr) -} - -// StartWithAttrs assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout, -// and c.Stderr, calls c.Start, and returns the File of the tty's -// corresponding pty. -// -// This will resize the pty to the specified size before starting the command if a size is provided. -// The `attrs` parameter overrides the one set in c.SysProcAttr. -// -// This should generally not be needed. Used in some edge cases where it is needed to create a pty -// without a controlling terminal. -func StartWithAttrs(c *exec.Cmd, sz *Winsize, attrs *syscall.SysProcAttr) (pty *os.File, err error) { - pty, tty, err := Open() - if err != nil { - return nil, err - } - defer tty.Close() - - if sz != nil { - if err := Setsize(pty, sz); err != nil { - pty.Close() - return nil, err - } - } - if c.Stdout == nil { - c.Stdout = tty - } - if c.Stderr == nil { - c.Stderr = tty - } - if c.Stdin == nil { - c.Stdin = tty - } - - c.SysProcAttr = attrs - - if err := c.Start(); err != nil { - _ = pty.Close() - return nil, err - } - return pty, err -} diff --git a/vendor/github.com/creack/pty/test_crosscompile.sh b/vendor/github.com/creack/pty/test_crosscompile.sh deleted file mode 100644 index c4b9e3734cf..00000000000 --- a/vendor/github.com/creack/pty/test_crosscompile.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env sh - -# Test script checking that all expected os/arch compile properly. -# Does not actually test the logic, just the compilation so we make sure we don't break code depending on the lib. - -echo2() { - echo $@ >&2 -} - -trap end 0 -end() { - [ "$?" = 0 ] && echo2 "Pass." || (echo2 "Fail."; exit 1) -} - -cross() { - os=$1 - shift - echo2 "Build for $os." - for arch in $@; do - echo2 " - $os/$arch" - GOOS=$os GOARCH=$arch go build - done - echo2 -} - -set -e - -cross linux amd64 386 arm arm64 ppc64 ppc64le s390x mips mipsle mips64 mips64le -cross darwin amd64 386 arm arm64 -cross freebsd amd64 386 arm -cross netbsd amd64 386 arm -cross openbsd amd64 386 arm arm64 -cross dragonfly amd64 -cross solaris amd64 - -# Not expected to work but should still compile. -cross windows amd64 386 arm - -# TODO: Fix compilation error on openbsd/arm. -# TODO: Merge the solaris PR. - -# Some os/arch require a different compiler. Run in docker. -if ! hash docker; then - # If docker is not present, stop here. - return -fi - -echo2 "Build for linux." -echo2 " - linux/riscv" -docker build -t test -f Dockerfile.riscv . diff --git a/vendor/github.com/creack/pty/util.go b/vendor/github.com/creack/pty/util.go deleted file mode 100644 index 8fdde0bab98..00000000000 --- a/vendor/github.com/creack/pty/util.go +++ /dev/null @@ -1,64 +0,0 @@ -// +build !windows,!solaris - -package pty - -import ( - "os" - "syscall" - "unsafe" -) - -// InheritSize applies the terminal size of pty to tty. This should be run -// in a signal handler for syscall.SIGWINCH to automatically resize the tty when -// the pty receives a window size change notification. -func InheritSize(pty, tty *os.File) error { - size, err := GetsizeFull(pty) - if err != nil { - return err - } - err = Setsize(tty, size) - if err != nil { - return err - } - return nil -} - -// Setsize resizes t to s. -func Setsize(t *os.File, ws *Winsize) error { - return windowRectCall(ws, t.Fd(), syscall.TIOCSWINSZ) -} - -// GetsizeFull returns the full terminal size description. -func GetsizeFull(t *os.File) (size *Winsize, err error) { - var ws Winsize - err = windowRectCall(&ws, t.Fd(), syscall.TIOCGWINSZ) - return &ws, err -} - -// Getsize returns the number of rows (lines) and cols (positions -// in each line) in terminal t. -func Getsize(t *os.File) (rows, cols int, err error) { - ws, err := GetsizeFull(t) - return int(ws.Rows), int(ws.Cols), err -} - -// Winsize describes the terminal size. -type Winsize struct { - Rows uint16 // ws_row: Number of rows (in cells) - Cols uint16 // ws_col: Number of columns (in cells) - X uint16 // ws_xpixel: Width in pixels - Y uint16 // ws_ypixel: Height in pixels -} - -func windowRectCall(ws *Winsize, fd, a2 uintptr) error { - _, _, errno := syscall.Syscall( - syscall.SYS_IOCTL, - fd, - a2, - uintptr(unsafe.Pointer(ws)), - ) - if errno != 0 { - return syscall.Errno(errno) - } - return nil -} diff --git a/vendor/github.com/creack/pty/util_solaris.go b/vendor/github.com/creack/pty/util_solaris.go deleted file mode 100644 index e8896924824..00000000000 --- a/vendor/github.com/creack/pty/util_solaris.go +++ /dev/null @@ -1,51 +0,0 @@ -// - -package pty - -import ( - "os" - "golang.org/x/sys/unix" -) - -const ( - TIOCGWINSZ = 21608 // 'T' << 8 | 104 - TIOCSWINSZ = 21607 // 'T' << 8 | 103 -) - -// Winsize describes the terminal size. -type Winsize struct { - Rows uint16 // ws_row: Number of rows (in cells) - Cols uint16 // ws_col: Number of columns (in cells) - X uint16 // ws_xpixel: Width in pixels - Y uint16 // ws_ypixel: Height in pixels -} - -// GetsizeFull returns the full terminal size description. -func GetsizeFull(t *os.File) (size *Winsize, err error) { - var wsz *unix.Winsize - wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ) - - if err != nil { - return nil, err - } else { - return &Winsize{wsz.Row, wsz.Col, wsz.Xpixel, wsz.Ypixel}, nil - } -} - -// Get Windows Size -func Getsize(t *os.File) (rows, cols int, err error) { - var wsz *unix.Winsize - wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ) - - if err != nil { - return 80, 25, err - } else { - return int(wsz.Row), int(wsz.Col), nil - } -} - -// Setsize resizes t to s. -func Setsize(t *os.File, ws *Winsize) error { - wsz := unix.Winsize{ws.Rows, ws.Cols, ws.X, ws.Y} - return unix.IoctlSetWinsize(int(t.Fd()), TIOCSWINSZ, &wsz) -} diff --git a/vendor/github.com/creack/pty/ztypes_386.go b/vendor/github.com/creack/pty/ztypes_386.go deleted file mode 100644 index ff0b8fd838f..00000000000 --- a/vendor/github.com/creack/pty/ztypes_386.go +++ /dev/null @@ -1,9 +0,0 @@ -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types.go - -package pty - -type ( - _C_int int32 - _C_uint uint32 -) diff --git a/vendor/github.com/creack/pty/ztypes_amd64.go b/vendor/github.com/creack/pty/ztypes_amd64.go deleted file mode 100644 index ff0b8fd838f..00000000000 --- a/vendor/github.com/creack/pty/ztypes_amd64.go +++ /dev/null @@ -1,9 +0,0 @@ -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types.go - -package pty - -type ( - _C_int int32 - _C_uint uint32 -) diff --git a/vendor/github.com/creack/pty/ztypes_arm.go b/vendor/github.com/creack/pty/ztypes_arm.go deleted file mode 100644 index ff0b8fd838f..00000000000 --- a/vendor/github.com/creack/pty/ztypes_arm.go +++ /dev/null @@ -1,9 +0,0 @@ -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types.go - -package pty - -type ( - _C_int int32 - _C_uint uint32 -) diff --git a/vendor/github.com/creack/pty/ztypes_arm64.go b/vendor/github.com/creack/pty/ztypes_arm64.go deleted file mode 100644 index 6c29a4b9188..00000000000 --- a/vendor/github.com/creack/pty/ztypes_arm64.go +++ /dev/null @@ -1,11 +0,0 @@ -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types.go - -// +build arm64 - -package pty - -type ( - _C_int int32 - _C_uint uint32 -) diff --git a/vendor/github.com/creack/pty/ztypes_dragonfly_amd64.go b/vendor/github.com/creack/pty/ztypes_dragonfly_amd64.go deleted file mode 100644 index 6b0ba037f89..00000000000 --- a/vendor/github.com/creack/pty/ztypes_dragonfly_amd64.go +++ /dev/null @@ -1,14 +0,0 @@ -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types_dragonfly.go - -package pty - -const ( - _C_SPECNAMELEN = 0x3f -) - -type fiodgnameArg struct { - Name *byte - Len uint32 - Pad_cgo_0 [4]byte -} diff --git a/vendor/github.com/creack/pty/ztypes_freebsd_386.go b/vendor/github.com/creack/pty/ztypes_freebsd_386.go deleted file mode 100644 index d9975374e3c..00000000000 --- a/vendor/github.com/creack/pty/ztypes_freebsd_386.go +++ /dev/null @@ -1,13 +0,0 @@ -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types_freebsd.go - -package pty - -const ( - _C_SPECNAMELEN = 0x3f -) - -type fiodgnameArg struct { - Len int32 - Buf *byte -} diff --git a/vendor/github.com/creack/pty/ztypes_freebsd_amd64.go b/vendor/github.com/creack/pty/ztypes_freebsd_amd64.go deleted file mode 100644 index 5fa102fcdf6..00000000000 --- a/vendor/github.com/creack/pty/ztypes_freebsd_amd64.go +++ /dev/null @@ -1,14 +0,0 @@ -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types_freebsd.go - -package pty - -const ( - _C_SPECNAMELEN = 0x3f -) - -type fiodgnameArg struct { - Len int32 - Pad_cgo_0 [4]byte - Buf *byte -} diff --git a/vendor/github.com/creack/pty/ztypes_freebsd_arm.go b/vendor/github.com/creack/pty/ztypes_freebsd_arm.go deleted file mode 100644 index d9975374e3c..00000000000 --- a/vendor/github.com/creack/pty/ztypes_freebsd_arm.go +++ /dev/null @@ -1,13 +0,0 @@ -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types_freebsd.go - -package pty - -const ( - _C_SPECNAMELEN = 0x3f -) - -type fiodgnameArg struct { - Len int32 - Buf *byte -} diff --git a/vendor/github.com/creack/pty/ztypes_freebsd_arm64.go b/vendor/github.com/creack/pty/ztypes_freebsd_arm64.go deleted file mode 100644 index 4418139b26f..00000000000 --- a/vendor/github.com/creack/pty/ztypes_freebsd_arm64.go +++ /dev/null @@ -1,13 +0,0 @@ -// Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs types_freebsd.go - -package pty - -const ( - _C_SPECNAMELEN = 0xff -) - -type fiodgnameArg struct { - Len int32 - Buf *byte -} diff --git a/vendor/github.com/creack/pty/ztypes_mipsx.go b/vendor/github.com/creack/pty/ztypes_mipsx.go deleted file mode 100644 index f0ce74086ae..00000000000 --- a/vendor/github.com/creack/pty/ztypes_mipsx.go +++ /dev/null @@ -1,12 +0,0 @@ -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types.go - -// +build linux -// +build mips mipsle mips64 mips64le - -package pty - -type ( - _C_int int32 - _C_uint uint32 -) diff --git a/vendor/github.com/creack/pty/ztypes_openbsd_32bit_int.go b/vendor/github.com/creack/pty/ztypes_openbsd_32bit_int.go deleted file mode 100644 index d7cab4a2abe..00000000000 --- a/vendor/github.com/creack/pty/ztypes_openbsd_32bit_int.go +++ /dev/null @@ -1,13 +0,0 @@ -// +build openbsd -// +build 386 amd64 arm arm64 - -package pty - -type ptmget struct { - Cfd int32 - Sfd int32 - Cn [16]int8 - Sn [16]int8 -} - -var ioctl_PTMGET = 0x40287401 diff --git a/vendor/github.com/creack/pty/ztypes_ppc64.go b/vendor/github.com/creack/pty/ztypes_ppc64.go deleted file mode 100644 index 4e1af84312b..00000000000 --- a/vendor/github.com/creack/pty/ztypes_ppc64.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build ppc64 - -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types.go - -package pty - -type ( - _C_int int32 - _C_uint uint32 -) diff --git a/vendor/github.com/creack/pty/ztypes_ppc64le.go b/vendor/github.com/creack/pty/ztypes_ppc64le.go deleted file mode 100644 index e6780f4e237..00000000000 --- a/vendor/github.com/creack/pty/ztypes_ppc64le.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build ppc64le - -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types.go - -package pty - -type ( - _C_int int32 - _C_uint uint32 -) diff --git a/vendor/github.com/creack/pty/ztypes_riscvx.go b/vendor/github.com/creack/pty/ztypes_riscvx.go deleted file mode 100644 index 99eec8ecbe0..00000000000 --- a/vendor/github.com/creack/pty/ztypes_riscvx.go +++ /dev/null @@ -1,11 +0,0 @@ -// Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs types.go - -// +build riscv riscv64 - -package pty - -type ( - _C_int int32 - _C_uint uint32 -) diff --git a/vendor/github.com/creack/pty/ztypes_s390x.go b/vendor/github.com/creack/pty/ztypes_s390x.go deleted file mode 100644 index a7452b61cb3..00000000000 --- a/vendor/github.com/creack/pty/ztypes_s390x.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build s390x - -// Created by cgo -godefs - DO NOT EDIT -// cgo -godefs types.go - -package pty - -type ( - _C_int int32 - _C_uint uint32 -) diff --git a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md deleted file mode 100644 index ca0e3c62c76..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md +++ /dev/null @@ -1,256 +0,0 @@ -# Changelog # -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/) -and this project adheres to [Semantic Versioning](http://semver.org/). - -## [Unreleased] ## - -## [0.4.1] - 2025-01-28 ## - -### Fixed ### -- The restrictions added for `root` paths passed to `SecureJoin` in 0.4.0 was - found to be too strict and caused some regressions when folks tried to - update, so this restriction has been relaxed to only return an error if the - path contains a `..` component. We still recommend users use `filepath.Clean` - (and even `filepath.EvalSymlinks`) on the `root` path they are using, but at - least you will no longer be punished for "trivial" unclean paths. - -## [0.4.0] - 2025-01-13 ## - -### Breaking #### -- `SecureJoin(VFS)` will now return an error if the provided `root` is not a - `filepath.Clean`'d path. - - While it is ultimately the responsibility of the caller to ensure the root is - a safe path to use, passing a path like `/symlink/..` as a root would result - in the `SecureJoin`'d path being placed in `/` even though `/symlink/..` - might be a different directory, and so we should more strongly discourage - such usage. - - All major users of `securejoin.SecureJoin` already ensure that the paths they - provide are safe (and this is ultimately a question of user error), but - removing this foot-gun is probably a good idea. Of course, this is - necessarily a breaking API change (though we expect no real users to be - affected by it). - - Thanks to [Erik Sjölund](https://github.com/eriksjolund), who initially - reported this issue as a possible security issue. - -- `MkdirAll` and `MkdirHandle` now take an `os.FileMode`-style mode argument - instead of a raw `unix.S_*`-style mode argument, which may cause compile-time - type errors depending on how you use `filepath-securejoin`. For most users, - there will be no change in behaviour aside from the type change (as the - bottom `0o777` bits are the same in both formats, and most users are probably - only using those bits). - - However, if you were using `unix.S_ISVTX` to set the sticky bit with - `MkdirAll(Handle)` you will need to switch to `os.ModeSticky` otherwise you - will get a runtime error with this update. In addition, the error message you - will get from passing `unix.S_ISUID` and `unix.S_ISGID` will be different as - they are treated as invalid bits now (note that previously passing said bits - was also an error). - -## [0.3.6] - 2024-12-17 ## - -### Compatibility ### -- The minimum Go version requirement for `filepath-securejoin` is now Go 1.18 - (we use generics internally). - - For reference, `filepath-securejoin@v0.3.0` somewhat-arbitrarily bumped the - Go version requirement to 1.21. - - While we did make some use of Go 1.21 stdlib features (and in principle Go - versions <= 1.21 are no longer even supported by upstream anymore), some - downstreams have complained that the version bump has meant that they have to - do workarounds when backporting fixes that use the new `filepath-securejoin` - API onto old branches. This is not an ideal situation, but since using this - library is probably better for most downstreams than a hand-rolled - workaround, we now have compatibility shims that allow us to build on older - Go versions. -- Lower minimum version requirement for `golang.org/x/sys` to `v0.18.0` (we - need the wrappers for `fsconfig(2)`), which should also make backporting - patches to older branches easier. - -## [0.3.5] - 2024-12-06 ## - -### Fixed ### -- `MkdirAll` will now no longer return an `EEXIST` error if two racing - processes are creating the same directory. We will still verify that the path - is a directory, but this will avoid spurious errors when multiple threads or - programs are trying to `MkdirAll` the same path. opencontainers/runc#4543 - -## [0.3.4] - 2024-10-09 ## - -### Fixed ### -- Previously, some testing mocks we had resulted in us doing `import "testing"` - in non-`_test.go` code, which made some downstreams like Kubernetes unhappy. - This has been fixed. (#32) - -## [0.3.3] - 2024-09-30 ## - -### Fixed ### -- The mode and owner verification logic in `MkdirAll` has been removed. This - was originally intended to protect against some theoretical attacks but upon - further consideration these protections don't actually buy us anything and - they were causing spurious errors with more complicated filesystem setups. -- The "is the created directory empty" logic in `MkdirAll` has also been - removed. This was not causing us issues yet, but some pseudofilesystems (such - as `cgroup`) create non-empty directories and so this logic would've been - wrong for such cases. - -## [0.3.2] - 2024-09-13 ## - -### Changed ### -- Passing the `S_ISUID` or `S_ISGID` modes to `MkdirAllInRoot` will now return - an explicit error saying that those bits are ignored by `mkdirat(2)`. In the - past a different error was returned, but since the silent ignoring behaviour - is codified in the man pages a more explicit error seems apt. While silently - ignoring these bits would be the most compatible option, it could lead to - users thinking their code sets these bits when it doesn't. Programs that need - to deal with compatibility can mask the bits themselves. (#23, #25) - -### Fixed ### -- If a directory has `S_ISGID` set, then all child directories will have - `S_ISGID` set when created and a different gid will be used for any inode - created under the directory. Previously, the "expected owner and mode" - validation in `securejoin.MkdirAll` did not correctly handle this. We now - correctly handle this case. (#24, #25) - -## [0.3.1] - 2024-07-23 ## - -### Changed ### -- By allowing `Open(at)InRoot` to opt-out of the extra work done by `MkdirAll` - to do the necessary "partial lookups", `Open(at)InRoot` now does less work - for both implementations (resulting in a many-fold decrease in the number of - operations for `openat2`, and a modest improvement for non-`openat2`) and is - far more guaranteed to match the correct `openat2(RESOLVE_IN_ROOT)` - behaviour. -- We now use `readlinkat(fd, "")` where possible. For `Open(at)InRoot` this - effectively just means that we no longer risk getting spurious errors during - rename races. However, for our hardened procfs handler, this in theory should - prevent mount attacks from tricking us when doing magic-link readlinks (even - when using the unsafe host `/proc` handle). Unfortunately `Reopen` is still - potentially vulnerable to those kinds of somewhat-esoteric attacks. - - Technically this [will only work on post-2.6.39 kernels][linux-readlinkat-emptypath] - but it seems incredibly unlikely anyone is using `filepath-securejoin` on a - pre-2011 kernel. - -### Fixed ### -- Several improvements were made to the errors returned by `Open(at)InRoot` and - `MkdirAll` when dealing with invalid paths under the emulated (ie. - non-`openat2`) implementation. Previously, some paths would return the wrong - error (`ENOENT` when the last component was a non-directory), and other paths - would be returned as though they were acceptable (trailing-slash components - after a non-directory would be ignored by `Open(at)InRoot`). - - These changes were done to match `openat2`'s behaviour and purely is a - consistency fix (most users are going to be using `openat2` anyway). - -[linux-readlinkat-emptypath]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=65cfc6722361570bfe255698d9cd4dccaf47570d - -## [0.3.0] - 2024-07-11 ## - -### Added ### -- A new set of `*os.File`-based APIs have been added. These are adapted from - [libpathrs][] and we strongly suggest using them if possible (as they provide - far more protection against attacks than `SecureJoin`): - - - `Open(at)InRoot` resolves a path inside a rootfs and returns an `*os.File` - handle to the path. Note that the handle returned is an `O_PATH` handle, - which cannot be used for reading or writing (as well as some other - operations -- [see open(2) for more details][open.2]) - - - `Reopen` takes an `O_PATH` file handle and safely re-opens it to upgrade - it to a regular handle. This can also be used with non-`O_PATH` handles, - but `O_PATH` is the most obvious application. - - - `MkdirAll` is an implementation of `os.MkdirAll` that is safe to use to - create a directory tree within a rootfs. - - As these are new APIs, they may change in the future. However, they should be - safe to start migrating to as we have extensive tests ensuring they behave - correctly and are safe against various races and other attacks. - -[libpathrs]: https://github.com/openSUSE/libpathrs -[open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html - -## [0.2.5] - 2024-05-03 ## - -### Changed ### -- Some minor changes were made to how lexical components (like `..` and `.`) - are handled during path generation in `SecureJoin`. There is no behaviour - change as a result of this fix (the resulting paths are the same). - -### Fixed ### -- The error returned when we hit a symlink loop now references the correct - path. (#10) - -## [0.2.4] - 2023-09-06 ## - -### Security ### -- This release fixes a potential security issue in filepath-securejoin when - used on Windows ([GHSA-6xv5-86q9-7xr8][], which could be used to generate - paths outside of the provided rootfs in certain cases), as well as improving - the overall behaviour of filepath-securejoin when dealing with Windows paths - that contain volume names. Thanks to Paulo Gomes for discovering and fixing - these issues. - -### Fixed ### -- Switch to GitHub Actions for CI so we can test on Windows as well as Linux - and MacOS. - -[GHSA-6xv5-86q9-7xr8]: https://github.com/advisories/GHSA-6xv5-86q9-7xr8 - -## [0.2.3] - 2021-06-04 ## - -### Changed ### -- Switch to Go 1.13-style `%w` error wrapping, letting us drop the dependency - on `github.com/pkg/errors`. - -## [0.2.2] - 2018-09-05 ## - -### Changed ### -- Use `syscall.ELOOP` as the base error for symlink loops, rather than our own - (internal) error. This allows callers to more easily use `errors.Is` to check - for this case. - -## [0.2.1] - 2018-09-05 ## - -### Fixed ### -- Use our own `IsNotExist` implementation, which lets us handle `ENOTDIR` - properly within `SecureJoin`. - -## [0.2.0] - 2017-07-19 ## - -We now have 100% test coverage! - -### Added ### -- Add a `SecureJoinVFS` API that can be used for mocking (as we do in our new - tests) or for implementing custom handling of lookup operations (such as for - rootless containers, where work is necessary to access directories with weird - modes because we don't have `CAP_DAC_READ_SEARCH` or `CAP_DAC_OVERRIDE`). - -## 0.1.0 - 2017-07-19 - -This is our first release of `github.com/cyphar/filepath-securejoin`, -containing a full implementation with a coverage of 93.5% (the only missing -cases are the error cases, which are hard to mocktest at the moment). - -[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...HEAD -[0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1 -[0.4.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.6...v0.4.0 -[0.3.6]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.5...v0.3.6 -[0.3.5]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.4...v0.3.5 -[0.3.4]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.3...v0.3.4 -[0.3.3]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.2...v0.3.3 -[0.3.2]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.1...v0.3.2 -[0.3.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.0...v0.3.1 -[0.3.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.5...v0.3.0 -[0.2.5]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.4...v0.2.5 -[0.2.4]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.3...v0.2.4 -[0.2.3]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.2...v0.2.3 -[0.2.2]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.1...v0.2.2 -[0.2.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.0...v0.2.1 -[0.2.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.1.0...v0.2.0 diff --git a/vendor/github.com/cyphar/filepath-securejoin/LICENSE b/vendor/github.com/cyphar/filepath-securejoin/LICENSE deleted file mode 100644 index cb1ab88da0f..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. -Copyright (C) 2017-2024 SUSE LLC. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/cyphar/filepath-securejoin/README.md b/vendor/github.com/cyphar/filepath-securejoin/README.md deleted file mode 100644 index eaeb53fcd0a..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/README.md +++ /dev/null @@ -1,169 +0,0 @@ -## `filepath-securejoin` ## - -[![Go Documentation](https://pkg.go.dev/badge/github.com/cyphar/filepath-securejoin.svg)](https://pkg.go.dev/github.com/cyphar/filepath-securejoin) -[![Build Status](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml/badge.svg)](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml) - -### Old API ### - -This library was originally just an implementation of `SecureJoin` which was -[intended to be included in the Go standard library][go#20126] as a safer -`filepath.Join` that would restrict the path lookup to be inside a root -directory. - -The implementation was based on code that existed in several container -runtimes. Unfortunately, this API is **fundamentally unsafe** against attackers -that can modify path components after `SecureJoin` returns and before the -caller uses the path, allowing for some fairly trivial TOCTOU attacks. - -`SecureJoin` (and `SecureJoinVFS`) are still provided by this library to -support legacy users, but new users are strongly suggested to avoid using -`SecureJoin` and instead use the [new api](#new-api) or switch to -[libpathrs][libpathrs]. - -With the above limitations in mind, this library guarantees the following: - -* If no error is set, the resulting string **must** be a child path of - `root` and will not contain any symlink path components (they will all be - expanded). - -* When expanding symlinks, all symlink path components **must** be resolved - relative to the provided root. In particular, this can be considered a - userspace implementation of how `chroot(2)` operates on file paths. Note that - these symlinks will **not** be expanded lexically (`filepath.Clean` is not - called on the input before processing). - -* Non-existent path components are unaffected by `SecureJoin` (similar to - `filepath.EvalSymlinks`'s semantics). - -* The returned path will always be `filepath.Clean`ed and thus not contain any - `..` components. - -A (trivial) implementation of this function on GNU/Linux systems could be done -with the following (note that this requires root privileges and is far more -opaque than the implementation in this library, and also requires that -`readlink` is inside the `root` path and is trustworthy): - -```go -package securejoin - -import ( - "os/exec" - "path/filepath" -) - -func SecureJoin(root, unsafePath string) (string, error) { - unsafePath = string(filepath.Separator) + unsafePath - cmd := exec.Command("chroot", root, - "readlink", "--canonicalize-missing", "--no-newline", unsafePath) - output, err := cmd.CombinedOutput() - if err != nil { - return "", err - } - expanded := string(output) - return filepath.Join(root, expanded), nil -} -``` - -[libpathrs]: https://github.com/openSUSE/libpathrs -[go#20126]: https://github.com/golang/go/issues/20126 - -### New API ### - -While we recommend users switch to [libpathrs][libpathrs] as soon as it has a -stable release, some methods implemented by libpathrs have been ported to this -library to ease the transition. These APIs are only supported on Linux. - -These APIs are implemented such that `filepath-securejoin` will -opportunistically use certain newer kernel APIs that make these operations far -more secure. In particular: - -* All of the lookup operations will use [`openat2`][openat2.2] on new enough - kernels (Linux 5.6 or later) to restrict lookups through magic-links and - bind-mounts (for certain operations) and to make use of `RESOLVE_IN_ROOT` to - efficiently resolve symlinks within a rootfs. - -* The APIs provide hardening against a malicious `/proc` mount to either detect - or avoid being tricked by a `/proc` that is not legitimate. This is done - using [`openat2`][openat2.2] for all users, and privileged users will also be - further protected by using [`fsopen`][fsopen.2] and [`open_tree`][open_tree.2] - (Linux 5.2 or later). - -[openat2.2]: https://www.man7.org/linux/man-pages/man2/openat2.2.html -[fsopen.2]: https://github.com/brauner/man-pages-md/blob/main/fsopen.md -[open_tree.2]: https://github.com/brauner/man-pages-md/blob/main/open_tree.md - -#### `OpenInRoot` #### - -```go -func OpenInRoot(root, unsafePath string) (*os.File, error) -func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) -func Reopen(handle *os.File, flags int) (*os.File, error) -``` - -`OpenInRoot` is a much safer version of - -```go -path, err := securejoin.SecureJoin(root, unsafePath) -file, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC) -``` - -that protects against various race attacks that could lead to serious security -issues, depending on the application. Note that the returned `*os.File` is an -`O_PATH` file descriptor, which is quite restricted. Callers will probably need -to use `Reopen` to get a more usable handle (this split is done to provide -useful features like PTY spawning and to avoid users accidentally opening bad -inodes that could cause a DoS). - -Callers need to be careful in how they use the returned `*os.File`. Usually it -is only safe to operate on the handle directly, and it is very easy to create a -security issue. [libpathrs][libpathrs] provides far more helpers to make using -these handles safer -- there is currently no plan to port them to -`filepath-securejoin`. - -`OpenatInRoot` is like `OpenInRoot` except that the root is provided using an -`*os.File`. This allows you to ensure that multiple `OpenatInRoot` (or -`MkdirAllHandle`) calls are operating on the same rootfs. - -> **NOTE**: Unlike `SecureJoin`, `OpenInRoot` will error out as soon as it hits -> a dangling symlink or non-existent path. This is in contrast to `SecureJoin` -> which treated non-existent components as though they were real directories, -> and would allow for partial resolution of dangling symlinks. These behaviours -> are at odds with how Linux treats non-existent paths and dangling symlinks, -> and so these are no longer allowed. - -#### `MkdirAll` #### - -```go -func MkdirAll(root, unsafePath string, mode int) error -func MkdirAllHandle(root *os.File, unsafePath string, mode int) (*os.File, error) -``` - -`MkdirAll` is a much safer version of - -```go -path, err := securejoin.SecureJoin(root, unsafePath) -err = os.MkdirAll(path, mode) -``` - -that protects against the same kinds of races that `OpenInRoot` protects -against. - -`MkdirAllHandle` is like `MkdirAll` except that the root is provided using an -`*os.File` (the reason for this is the same as with `OpenatInRoot`) and an -`*os.File` of the final created directory is returned (this directory is -guaranteed to be effectively identical to the directory created by -`MkdirAllHandle`, which is not possible to ensure by just using `OpenatInRoot` -after `MkdirAll`). - -> **NOTE**: Unlike `SecureJoin`, `MkdirAll` will error out as soon as it hits -> a dangling symlink or non-existent path. This is in contrast to `SecureJoin` -> which treated non-existent components as though they were real directories, -> and would allow for partial resolution of dangling symlinks. These behaviours -> are at odds with how Linux treats non-existent paths and dangling symlinks, -> and so these are no longer allowed. This means that `MkdirAll` will not -> create non-existent directories referenced by a dangling symlink. - -### License ### - -The license of this project is the same as Go, which is a BSD 3-clause license -available in the `LICENSE` file. diff --git a/vendor/github.com/cyphar/filepath-securejoin/VERSION b/vendor/github.com/cyphar/filepath-securejoin/VERSION deleted file mode 100644 index 267577d47e4..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.4.1 diff --git a/vendor/github.com/cyphar/filepath-securejoin/doc.go b/vendor/github.com/cyphar/filepath-securejoin/doc.go deleted file mode 100644 index 1ec7d065ef4..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/doc.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. -// Copyright (C) 2017-2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package securejoin implements a set of helpers to make it easier to write Go -// code that is safe against symlink-related escape attacks. The primary idea -// is to let you resolve a path within a rootfs directory as if the rootfs was -// a chroot. -// -// securejoin has two APIs, a "legacy" API and a "modern" API. -// -// The legacy API is [SecureJoin] and [SecureJoinVFS]. These methods are -// **not** safe against race conditions where an attacker changes the -// filesystem after (or during) the [SecureJoin] operation. -// -// The new API is made up of [OpenInRoot] and [MkdirAll] (and derived -// functions). These are safe against racing attackers and have several other -// protections that are not provided by the legacy API. There are many more -// operations that most programs expect to be able to do safely, but we do not -// provide explicit support for them because we want to encourage users to -// switch to [libpathrs](https://github.com/openSUSE/libpathrs) which is a -// cross-language next-generation library that is entirely designed around -// operating on paths safely. -// -// securejoin has been used by several container runtimes (Docker, runc, -// Kubernetes, etc) for quite a few years as a de-facto standard for operating -// on container filesystem paths "safely". However, most users still use the -// legacy API which is unsafe against various attacks (there is a fairly long -// history of CVEs in dependent as a result). Users should switch to the modern -// API as soon as possible (or even better, switch to libpathrs). -// -// This project was initially intended to be included in the Go standard -// library, but [it was rejected](https://go.dev/issue/20126). There is now a -// [new Go proposal](https://go.dev/issue/67002) for a safe path resolution API -// that shares some of the goals of filepath-securejoin. However, that design -// is intended to work like `openat2(RESOLVE_BENEATH)` which does not fit the -// usecase of container runtimes and most system tools. -package securejoin diff --git a/vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_go120.go b/vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_go120.go deleted file mode 100644 index 42452bbf9b0..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_go120.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build linux && go1.20 - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "fmt" -) - -// wrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except -// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap) -// is only guaranteed to give you baseErr. -func wrapBaseError(baseErr, extraErr error) error { - return fmt.Errorf("%w: %w", extraErr, baseErr) -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_unsupported.go b/vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_unsupported.go deleted file mode 100644 index e7adca3fd12..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_unsupported.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build linux && !go1.20 - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "fmt" -) - -type wrappedError struct { - inner error - isError error -} - -func (err wrappedError) Is(target error) bool { - return err.isError == target -} - -func (err wrappedError) Unwrap() error { - return err.inner -} - -func (err wrappedError) Error() string { - return fmt.Sprintf("%v: %v", err.isError, err.inner) -} - -// wrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except -// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap) -// is only guaranteed to give you baseErr. -func wrapBaseError(baseErr, extraErr error) error { - return wrappedError{ - inner: baseErr, - isError: extraErr, - } -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go b/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go deleted file mode 100644 index ddd6fa9a41c..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build linux && go1.21 - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "slices" - "sync" -) - -func slices_DeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S { - return slices.DeleteFunc(slice, delFn) -} - -func slices_Contains[S ~[]E, E comparable](slice S, val E) bool { - return slices.Contains(slice, val) -} - -func slices_Clone[S ~[]E, E any](slice S) S { - return slices.Clone(slice) -} - -func sync_OnceValue[T any](f func() T) func() T { - return sync.OnceValue(f) -} - -func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { - return sync.OnceValues(f) -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go b/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go deleted file mode 100644 index f1e6fe7e717..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go +++ /dev/null @@ -1,124 +0,0 @@ -//go:build linux && !go1.21 - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "sync" -) - -// These are very minimal implementations of functions that appear in Go 1.21's -// stdlib, included so that we can build on older Go versions. Most are -// borrowed directly from the stdlib, and a few are modified to be "obviously -// correct" without needing to copy too many other helpers. - -// clearSlice is equivalent to the builtin clear from Go 1.21. -// Copied from the Go 1.24 stdlib implementation. -func clearSlice[S ~[]E, E any](slice S) { - var zero E - for i := range slice { - slice[i] = zero - } -} - -// Copied from the Go 1.24 stdlib implementation. -func slices_IndexFunc[S ~[]E, E any](s S, f func(E) bool) int { - for i := range s { - if f(s[i]) { - return i - } - } - return -1 -} - -// Copied from the Go 1.24 stdlib implementation. -func slices_DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { - i := slices_IndexFunc(s, del) - if i == -1 { - return s - } - // Don't start copying elements until we find one to delete. - for j := i + 1; j < len(s); j++ { - if v := s[j]; !del(v) { - s[i] = v - i++ - } - } - clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC - return s[:i] -} - -// Similar to the stdlib slices.Contains, except that we don't have -// slices.Index so we need to use slices.IndexFunc for this non-Func helper. -func slices_Contains[S ~[]E, E comparable](s S, v E) bool { - return slices_IndexFunc(s, func(e E) bool { return e == v }) >= 0 -} - -// Copied from the Go 1.24 stdlib implementation. -func slices_Clone[S ~[]E, E any](s S) S { - // Preserve nil in case it matters. - if s == nil { - return nil - } - return append(S([]E{}), s...) -} - -// Copied from the Go 1.24 stdlib implementation. -func sync_OnceValue[T any](f func() T) func() T { - var ( - once sync.Once - valid bool - p any - result T - ) - g := func() { - defer func() { - p = recover() - if !valid { - panic(p) - } - }() - result = f() - f = nil - valid = true - } - return func() T { - once.Do(g) - if !valid { - panic(p) - } - return result - } -} - -// Copied from the Go 1.24 stdlib implementation. -func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { - var ( - once sync.Once - valid bool - p any - r1 T1 - r2 T2 - ) - g := func() { - defer func() { - p = recover() - if !valid { - panic(p) - } - }() - r1, r2 = f() - f = nil - valid = true - } - return func() (T1, T2) { - once.Do(g) - if !valid { - panic(p) - } - return r1, r2 - } -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/join.go b/vendor/github.com/cyphar/filepath-securejoin/join.go deleted file mode 100644 index e6634d4778f..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/join.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. -// Copyright (C) 2017-2025 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "errors" - "os" - "path/filepath" - "strings" - "syscall" -) - -const maxSymlinkLimit = 255 - -// IsNotExist tells you if err is an error that implies that either the path -// accessed does not exist (or path components don't exist). This is -// effectively a more broad version of [os.IsNotExist]. -func IsNotExist(err error) bool { - // Check that it's not actually an ENOTDIR, which in some cases is a more - // convoluted case of ENOENT (usually involving weird paths). - return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) || errors.Is(err, syscall.ENOENT) -} - -// errUnsafeRoot is returned if the user provides SecureJoinVFS with a path -// that contains ".." components. -var errUnsafeRoot = errors.New("root path provided to SecureJoin contains '..' components") - -// stripVolume just gets rid of the Windows volume included in a path. Based on -// some godbolt tests, the Go compiler is smart enough to make this a no-op on -// Linux. -func stripVolume(path string) string { - return path[len(filepath.VolumeName(path)):] -} - -// hasDotDot checks if the path contains ".." components in a platform-agnostic -// way. -func hasDotDot(path string) bool { - // If we are on Windows, strip any volume letters. It turns out that - // C:..\foo may (or may not) be a valid pathname and we need to handle that - // leading "..". - path = stripVolume(path) - // Look for "/../" in the path, but we need to handle leading and trailing - // ".."s by adding separators. Doing this with filepath.Separator is ugly - // so just convert to Unix-style "/" first. - path = filepath.ToSlash(path) - return strings.Contains("/"+path+"/", "/../") -} - -// SecureJoinVFS joins the two given path components (similar to [filepath.Join]) except -// that the returned path is guaranteed to be scoped inside the provided root -// path (when evaluated). Any symbolic links in the path are evaluated with the -// given root treated as the root of the filesystem, similar to a chroot. The -// filesystem state is evaluated through the given [VFS] interface (if nil, the -// standard [os].* family of functions are used). -// -// Note that the guarantees provided by this function only apply if the path -// components in the returned string are not modified (in other words are not -// replaced with symlinks on the filesystem) after this function has returned. -// Such a symlink race is necessarily out-of-scope of SecureJoinVFS. -// -// NOTE: Due to the above limitation, Linux users are strongly encouraged to -// use [OpenInRoot] instead, which does safely protect against these kinds of -// attacks. There is no way to solve this problem with SecureJoinVFS because -// the API is fundamentally wrong (you cannot return a "safe" path string and -// guarantee it won't be modified afterwards). -// -// Volume names in unsafePath are always discarded, regardless if they are -// provided via direct input or when evaluating symlinks. Therefore: -// -// "C:\Temp" + "D:\path\to\file.txt" results in "C:\Temp\path\to\file.txt" -// -// If the provided root is not [filepath.Clean] then an error will be returned, -// as such root paths are bordering on somewhat unsafe and using such paths is -// not best practice. We also strongly suggest that any root path is first -// fully resolved using [filepath.EvalSymlinks] or otherwise constructed to -// avoid containing symlink components. Of course, the root also *must not* be -// attacker-controlled. -func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { - // The root path must not contain ".." components, otherwise when we join - // the subpath we will end up with a weird path. We could work around this - // in other ways but users shouldn't be giving us non-lexical root paths in - // the first place. - if hasDotDot(root) { - return "", errUnsafeRoot - } - - // Use the os.* VFS implementation if none was specified. - if vfs == nil { - vfs = osVFS{} - } - - unsafePath = filepath.FromSlash(unsafePath) - var ( - currentPath string - remainingPath = unsafePath - linksWalked int - ) - for remainingPath != "" { - // On Windows, if we managed to end up at a path referencing a volume, - // drop the volume to make sure we don't end up with broken paths or - // escaping the root volume. - remainingPath = stripVolume(remainingPath) - - // Get the next path component. - var part string - if i := strings.IndexRune(remainingPath, filepath.Separator); i == -1 { - part, remainingPath = remainingPath, "" - } else { - part, remainingPath = remainingPath[:i], remainingPath[i+1:] - } - - // Apply the component lexically to the path we are building. - // currentPath does not contain any symlinks, and we are lexically - // dealing with a single component, so it's okay to do a filepath.Clean - // here. - nextPath := filepath.Join(string(filepath.Separator), currentPath, part) - if nextPath == string(filepath.Separator) { - currentPath = "" - continue - } - fullPath := root + string(filepath.Separator) + nextPath - - // Figure out whether the path is a symlink. - fi, err := vfs.Lstat(fullPath) - if err != nil && !IsNotExist(err) { - return "", err - } - // Treat non-existent path components the same as non-symlinks (we - // can't do any better here). - if IsNotExist(err) || fi.Mode()&os.ModeSymlink == 0 { - currentPath = nextPath - continue - } - - // It's a symlink, so get its contents and expand it by prepending it - // to the yet-unparsed path. - linksWalked++ - if linksWalked > maxSymlinkLimit { - return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP} - } - - dest, err := vfs.Readlink(fullPath) - if err != nil { - return "", err - } - remainingPath = dest + string(filepath.Separator) + remainingPath - // Absolute symlinks reset any work we've already done. - if filepath.IsAbs(dest) { - currentPath = "" - } - } - - // There should be no lexical components like ".." left in the path here, - // but for safety clean up the path before joining it to the root. - finalPath := filepath.Join(string(filepath.Separator), currentPath) - return filepath.Join(root, finalPath), nil -} - -// SecureJoin is a wrapper around [SecureJoinVFS] that just uses the [os].* library -// of functions as the [VFS]. If in doubt, use this function over [SecureJoinVFS]. -func SecureJoin(root, unsafePath string) (string, error) { - return SecureJoinVFS(root, unsafePath, nil) -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/lookup_linux.go b/vendor/github.com/cyphar/filepath-securejoin/lookup_linux.go deleted file mode 100644 index be81e498d72..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/lookup_linux.go +++ /dev/null @@ -1,388 +0,0 @@ -//go:build linux - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "errors" - "fmt" - "os" - "path" - "path/filepath" - "strings" - - "golang.org/x/sys/unix" -) - -type symlinkStackEntry struct { - // (dir, remainingPath) is what we would've returned if the link didn't - // exist. This matches what openat2(RESOLVE_IN_ROOT) would return in - // this case. - dir *os.File - remainingPath string - // linkUnwalked is the remaining path components from the original - // Readlink which we have yet to walk. When this slice is empty, we - // drop the link from the stack. - linkUnwalked []string -} - -func (se symlinkStackEntry) String() string { - return fmt.Sprintf("<%s>/%s [->%s]", se.dir.Name(), se.remainingPath, strings.Join(se.linkUnwalked, "/")) -} - -func (se symlinkStackEntry) Close() { - _ = se.dir.Close() -} - -type symlinkStack []*symlinkStackEntry - -func (s *symlinkStack) IsEmpty() bool { - return s == nil || len(*s) == 0 -} - -func (s *symlinkStack) Close() { - if s != nil { - for _, link := range *s { - link.Close() - } - // TODO: Switch to clear once we switch to Go 1.21. - *s = nil - } -} - -var ( - errEmptyStack = errors.New("[internal] stack is empty") - errBrokenSymlinkStack = errors.New("[internal error] broken symlink stack") -) - -func (s *symlinkStack) popPart(part string) error { - if s == nil || s.IsEmpty() { - // If there is nothing in the symlink stack, then the part was from the - // real path provided by the user, and this is a no-op. - return errEmptyStack - } - if part == "." { - // "." components are no-ops -- we drop them when doing SwapLink. - return nil - } - - tailEntry := (*s)[len(*s)-1] - - // Double-check that we are popping the component we expect. - if len(tailEntry.linkUnwalked) == 0 { - return fmt.Errorf("%w: trying to pop component %q of empty stack entry %s", errBrokenSymlinkStack, part, tailEntry) - } - headPart := tailEntry.linkUnwalked[0] - if headPart != part { - return fmt.Errorf("%w: trying to pop component %q but the last stack entry is %s (%q)", errBrokenSymlinkStack, part, tailEntry, headPart) - } - - // Drop the component, but keep the entry around in case we are dealing - // with a "tail-chained" symlink. - tailEntry.linkUnwalked = tailEntry.linkUnwalked[1:] - return nil -} - -func (s *symlinkStack) PopPart(part string) error { - if err := s.popPart(part); err != nil { - if errors.Is(err, errEmptyStack) { - // Skip empty stacks. - err = nil - } - return err - } - - // Clean up any of the trailing stack entries that are empty. - for lastGood := len(*s) - 1; lastGood >= 0; lastGood-- { - entry := (*s)[lastGood] - if len(entry.linkUnwalked) > 0 { - break - } - entry.Close() - (*s) = (*s)[:lastGood] - } - return nil -} - -func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) error { - if s == nil { - return nil - } - // Split the link target and clean up any "" parts. - linkTargetParts := slices_DeleteFunc( - strings.Split(linkTarget, "/"), - func(part string) bool { return part == "" || part == "." }) - - // Copy the directory so the caller doesn't close our copy. - dirCopy, err := dupFile(dir) - if err != nil { - return err - } - - // Add to the stack. - *s = append(*s, &symlinkStackEntry{ - dir: dirCopy, - remainingPath: remainingPath, - linkUnwalked: linkTargetParts, - }) - return nil -} - -func (s *symlinkStack) SwapLink(linkPart string, dir *os.File, remainingPath, linkTarget string) error { - // If we are currently inside a symlink resolution, remove the symlink - // component from the last symlink entry, but don't remove the entry even - // if it's empty. If we are a "tail-chained" symlink (a trailing symlink we - // hit during a symlink resolution) we need to keep the old symlink until - // we finish the resolution. - if err := s.popPart(linkPart); err != nil { - if !errors.Is(err, errEmptyStack) { - return err - } - // Push the component regardless of whether the stack was empty. - } - return s.push(dir, remainingPath, linkTarget) -} - -func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) { - if s == nil || s.IsEmpty() { - return nil, "", false - } - tailEntry := (*s)[0] - *s = (*s)[1:] - return tailEntry.dir, tailEntry.remainingPath, true -} - -// partialLookupInRoot tries to lookup as much of the request path as possible -// within the provided root (a-la RESOLVE_IN_ROOT) and opens the final existing -// component of the requested path, returning a file handle to the final -// existing component and a string containing the remaining path components. -func partialLookupInRoot(root *os.File, unsafePath string) (*os.File, string, error) { - return lookupInRoot(root, unsafePath, true) -} - -func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) { - handle, remainingPath, err := lookupInRoot(root, unsafePath, false) - if remainingPath != "" && err == nil { - // should never happen - err = fmt.Errorf("[bug] non-empty remaining path when doing a non-partial lookup: %q", remainingPath) - } - // lookupInRoot(partial=false) will always close the handle if an error is - // returned, so no need to double-check here. - return handle, err -} - -func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) { - unsafePath = filepath.ToSlash(unsafePath) // noop - - // This is very similar to SecureJoin, except that we operate on the - // components using file descriptors. We then return the last component we - // managed open, along with the remaining path components not opened. - - // Try to use openat2 if possible. - if hasOpenat2() { - return lookupOpenat2(root, unsafePath, partial) - } - - // Get the "actual" root path from /proc/self/fd. This is necessary if the - // root is some magic-link like /proc/$pid/root, in which case we want to - // make sure when we do checkProcSelfFdPath that we are using the correct - // root path. - logicalRootPath, err := procSelfFdReadlink(root) - if err != nil { - return nil, "", fmt.Errorf("get real root path: %w", err) - } - - currentDir, err := dupFile(root) - if err != nil { - return nil, "", fmt.Errorf("clone root fd: %w", err) - } - defer func() { - // If a handle is not returned, close the internal handle. - if Handle == nil { - _ = currentDir.Close() - } - }() - - // symlinkStack is used to emulate how openat2(RESOLVE_IN_ROOT) treats - // dangling symlinks. If we hit a non-existent path while resolving a - // symlink, we need to return the (dir, remainingPath) that we had when we - // hit the symlink (treating the symlink as though it were a regular file). - // The set of (dir, remainingPath) sets is stored within the symlinkStack - // and we add and remove parts when we hit symlink and non-symlink - // components respectively. We need a stack because of recursive symlinks - // (symlinks that contain symlink components in their target). - // - // Note that the stack is ONLY used for book-keeping. All of the actual - // path walking logic is still based on currentPath/remainingPath and - // currentDir (as in SecureJoin). - var symStack *symlinkStack - if partial { - symStack = new(symlinkStack) - defer symStack.Close() - } - - var ( - linksWalked int - currentPath string - remainingPath = unsafePath - ) - for remainingPath != "" { - // Save the current remaining path so if the part is not real we can - // return the path including the component. - oldRemainingPath := remainingPath - - // Get the next path component. - var part string - if i := strings.IndexByte(remainingPath, '/'); i == -1 { - part, remainingPath = remainingPath, "" - } else { - part, remainingPath = remainingPath[:i], remainingPath[i+1:] - } - // If we hit an empty component, we need to treat it as though it is - // "." so that trailing "/" and "//" components on a non-directory - // correctly return the right error code. - if part == "" { - part = "." - } - - // Apply the component lexically to the path we are building. - // currentPath does not contain any symlinks, and we are lexically - // dealing with a single component, so it's okay to do a filepath.Clean - // here. - nextPath := path.Join("/", currentPath, part) - // If we logically hit the root, just clone the root rather than - // opening the part and doing all of the other checks. - if nextPath == "/" { - if err := symStack.PopPart(part); err != nil { - return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err) - } - // Jump to root. - rootClone, err := dupFile(root) - if err != nil { - return nil, "", fmt.Errorf("clone root fd: %w", err) - } - _ = currentDir.Close() - currentDir = rootClone - currentPath = nextPath - continue - } - - // Try to open the next component. - nextDir, err := openatFile(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) - switch { - case err == nil: - st, err := nextDir.Stat() - if err != nil { - _ = nextDir.Close() - return nil, "", fmt.Errorf("stat component %q: %w", part, err) - } - - switch st.Mode() & os.ModeType { - case os.ModeSymlink: - // readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See - // Linux commit 65cfc6722361 ("readlinkat(), fchownat() and - // fstatat() with empty relative pathnames"). - linkDest, err := readlinkatFile(nextDir, "") - // We don't need the handle anymore. - _ = nextDir.Close() - if err != nil { - return nil, "", err - } - - linksWalked++ - if linksWalked > maxSymlinkLimit { - return nil, "", &os.PathError{Op: "securejoin.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP} - } - - // Swap out the symlink's component for the link entry itself. - if err := symStack.SwapLink(part, currentDir, oldRemainingPath, linkDest); err != nil { - return nil, "", fmt.Errorf("walking into symlink %q failed: push symlink: %w", part, err) - } - - // Update our logical remaining path. - remainingPath = linkDest + "/" + remainingPath - // Absolute symlinks reset any work we've already done. - if path.IsAbs(linkDest) { - // Jump to root. - rootClone, err := dupFile(root) - if err != nil { - return nil, "", fmt.Errorf("clone root fd: %w", err) - } - _ = currentDir.Close() - currentDir = rootClone - currentPath = "/" - } - - default: - // If we are dealing with a directory, simply walk into it. - _ = currentDir.Close() - currentDir = nextDir - currentPath = nextPath - - // The part was real, so drop it from the symlink stack. - if err := symStack.PopPart(part); err != nil { - return nil, "", fmt.Errorf("walking into directory %q failed: %w", part, err) - } - - // If we are operating on a .., make sure we haven't escaped. - // We only have to check for ".." here because walking down - // into a regular component component cannot cause you to - // escape. This mirrors the logic in RESOLVE_IN_ROOT, except we - // have to check every ".." rather than only checking after a - // rename or mount on the system. - if part == ".." { - // Make sure the root hasn't moved. - if err := checkProcSelfFdPath(logicalRootPath, root); err != nil { - return nil, "", fmt.Errorf("root path moved during lookup: %w", err) - } - // Make sure the path is what we expect. - fullPath := logicalRootPath + nextPath - if err := checkProcSelfFdPath(fullPath, currentDir); err != nil { - return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err) - } - } - } - - default: - if !partial { - return nil, "", err - } - // If there are any remaining components in the symlink stack, we - // are still within a symlink resolution and thus we hit a dangling - // symlink. So pretend that the first symlink in the stack we hit - // was an ENOENT (to match openat2). - if oldDir, remainingPath, ok := symStack.PopTopSymlink(); ok { - _ = currentDir.Close() - return oldDir, remainingPath, err - } - // We have hit a final component that doesn't exist, so we have our - // partial open result. Note that we have to use the OLD remaining - // path, since the lookup failed. - return currentDir, oldRemainingPath, err - } - } - - // If the unsafePath had a trailing slash, we need to make sure we try to - // do a relative "." open so that we will correctly return an error when - // the final component is a non-directory (to match openat2). In the - // context of openat2, a trailing slash and a trailing "/." are completely - // equivalent. - if strings.HasSuffix(unsafePath, "/") { - nextDir, err := openatFile(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) - if err != nil { - if !partial { - _ = currentDir.Close() - currentDir = nil - } - return currentDir, "", err - } - _ = currentDir.Close() - currentDir = nextDir - } - - // All of the components existed! - return currentDir, "", nil -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/mkdir_linux.go b/vendor/github.com/cyphar/filepath-securejoin/mkdir_linux.go deleted file mode 100644 index a17ae3b0387..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/mkdir_linux.go +++ /dev/null @@ -1,236 +0,0 @@ -//go:build linux - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "strings" - - "golang.org/x/sys/unix" -) - -var ( - errInvalidMode = errors.New("invalid permission mode") - errPossibleAttack = errors.New("possible attack detected") -) - -// modePermExt is like os.ModePerm except that it also includes the set[ug]id -// and sticky bits. -const modePermExt = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky - -//nolint:cyclop // this function needs to handle a lot of cases -func toUnixMode(mode os.FileMode) (uint32, error) { - sysMode := uint32(mode.Perm()) - if mode&os.ModeSetuid != 0 { - sysMode |= unix.S_ISUID - } - if mode&os.ModeSetgid != 0 { - sysMode |= unix.S_ISGID - } - if mode&os.ModeSticky != 0 { - sysMode |= unix.S_ISVTX - } - // We don't allow file type bits. - if mode&os.ModeType != 0 { - return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", errInvalidMode, mode, mode) - } - // We don't allow other unknown modes. - if mode&^modePermExt != 0 || sysMode&unix.S_IFMT != 0 { - return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", errInvalidMode, mode, mode) - } - return sysMode, nil -} - -// MkdirAllHandle is equivalent to [MkdirAll], except that it is safer to use -// in two respects: -// -// - The caller provides the root directory as an *[os.File] (preferably O_PATH) -// handle. This means that the caller can be sure which root directory is -// being used. Note that this can be emulated by using /proc/self/fd/... as -// the root path with [os.MkdirAll]. -// -// - Once all of the directories have been created, an *[os.File] O_PATH handle -// to the directory at unsafePath is returned to the caller. This is done in -// an effectively-race-free way (an attacker would only be able to swap the -// final directory component), which is not possible to emulate with -// [MkdirAll]. -// -// In addition, the returned handle is obtained far more efficiently than doing -// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after -// doing [MkdirAll]. If you intend to open the directory after creating it, you -// should use MkdirAllHandle. -func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.File, Err error) { - unixMode, err := toUnixMode(mode) - if err != nil { - return nil, err - } - // On Linux, mkdirat(2) (and os.Mkdir) silently ignore the suid and sgid - // bits. We could also silently ignore them but since we have very few - // users it seems more prudent to return an error so users notice that - // these bits will not be set. - if unixMode&^0o1777 != 0 { - return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", errInvalidMode, mode) - } - - // Try to open as much of the path as possible. - currentDir, remainingPath, err := partialLookupInRoot(root, unsafePath) - defer func() { - if Err != nil { - _ = currentDir.Close() - } - }() - if err != nil && !errors.Is(err, unix.ENOENT) { - return nil, fmt.Errorf("find existing subpath of %q: %w", unsafePath, err) - } - - // If there is an attacker deleting directories as we walk into them, - // detect this proactively. Note this is guaranteed to detect if the - // attacker deleted any part of the tree up to currentDir. - // - // Once we walk into a dead directory, partialLookupInRoot would not be - // able to walk further down the tree (directories must be empty before - // they are deleted), and if the attacker has removed the entire tree we - // can be sure that anything that was originally inside a dead directory - // must also be deleted and thus is a dead directory in its own right. - // - // This is mostly a quality-of-life check, because mkdir will simply fail - // later if the attacker deletes the tree after this check. - if err := isDeadInode(currentDir); err != nil { - return nil, fmt.Errorf("finding existing subpath of %q: %w", unsafePath, err) - } - - // Re-open the path to match the O_DIRECTORY reopen loop later (so that we - // always return a non-O_PATH handle). We also check that we actually got a - // directory. - if reopenDir, err := Reopen(currentDir, unix.O_DIRECTORY|unix.O_CLOEXEC); errors.Is(err, unix.ENOTDIR) { - return nil, fmt.Errorf("cannot create subdirectories in %q: %w", currentDir.Name(), unix.ENOTDIR) - } else if err != nil { - return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err) - } else { - _ = currentDir.Close() - currentDir = reopenDir - } - - remainingParts := strings.Split(remainingPath, string(filepath.Separator)) - if slices_Contains(remainingParts, "..") { - // The path contained ".." components after the end of the "real" - // components. We could try to safely resolve ".." here but that would - // add a bunch of extra logic for something that it's not clear even - // needs to be supported. So just return an error. - // - // If we do filepath.Clean(remainingPath) then we end up with the - // problem that ".." can erase a trailing dangling symlink and produce - // a path that doesn't quite match what the user asked for. - return nil, fmt.Errorf("%w: yet-to-be-created path %q contains '..' components", unix.ENOENT, remainingPath) - } - - // Create the remaining components. - for _, part := range remainingParts { - switch part { - case "", ".": - // Skip over no-op paths. - continue - } - - // NOTE: mkdir(2) will not follow trailing symlinks, so we can safely - // create the final component without worrying about symlink-exchange - // attacks. - // - // If we get -EEXIST, it's possible that another program created the - // directory at the same time as us. In that case, just continue on as - // if we created it (if the created inode is not a directory, the - // following open call will fail). - if err := unix.Mkdirat(int(currentDir.Fd()), part, unixMode); err != nil && !errors.Is(err, unix.EEXIST) { - err = &os.PathError{Op: "mkdirat", Path: currentDir.Name() + "/" + part, Err: err} - // Make the error a bit nicer if the directory is dead. - if deadErr := isDeadInode(currentDir); deadErr != nil { - // TODO: Once we bump the minimum Go version to 1.20, we can use - // multiple %w verbs for this wrapping. For now we need to use a - // compatibility shim for older Go versions. - //err = fmt.Errorf("%w (%w)", err, deadErr) - err = wrapBaseError(err, deadErr) - } - return nil, err - } - - // Get a handle to the next component. O_DIRECTORY means we don't need - // to use O_PATH. - var nextDir *os.File - if hasOpenat2() { - nextDir, err = openat2File(currentDir, part, &unix.OpenHow{ - Flags: unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC, - Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV, - }) - } else { - nextDir, err = openatFile(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) - } - if err != nil { - return nil, err - } - _ = currentDir.Close() - currentDir = nextDir - - // It's possible that the directory we just opened was swapped by an - // attacker. Unfortunately there isn't much we can do to protect - // against this, and MkdirAll's behaviour is that we will reuse - // existing directories anyway so the need to protect against this is - // incredibly limited (and arguably doesn't even deserve mention here). - // - // Ideally we might want to check that the owner and mode match what we - // would've created -- unfortunately, it is non-trivial to verify that - // the owner and mode of the created directory match. While plain Unix - // DAC rules seem simple enough to emulate, there are a bunch of other - // factors that can change the mode or owner of created directories - // (default POSIX ACLs, mount options like uid=1,gid=2,umask=0 on - // filesystems like vfat, etc etc). We used to try to verify this but - // it just lead to a series of spurious errors. - // - // We could also check that the directory is non-empty, but - // unfortunately some pseduofilesystems (like cgroupfs) create - // non-empty directories, which would result in different spurious - // errors. - } - return currentDir, nil -} - -// MkdirAll is a race-safe alternative to the [os.MkdirAll] function, -// where the new directory is guaranteed to be within the root directory (if an -// attacker can move directories from inside the root to outside the root, the -// created directory tree might be outside of the root but the key constraint -// is that at no point will we walk outside of the directory tree we are -// creating). -// -// Effectively, MkdirAll(root, unsafePath, mode) is equivalent to -// -// path, _ := securejoin.SecureJoin(root, unsafePath) -// err := os.MkdirAll(path, mode) -// -// But is much safer. The above implementation is unsafe because if an attacker -// can modify the filesystem tree between [SecureJoin] and [os.MkdirAll], it is -// possible for MkdirAll to resolve unsafe symlink components and create -// directories outside of the root. -// -// If you plan to open the directory after you have created it or want to use -// an open directory handle as the root, you should use [MkdirAllHandle] instead. -// This function is a wrapper around [MkdirAllHandle]. -func MkdirAll(root, unsafePath string, mode os.FileMode) error { - rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) - if err != nil { - return err - } - defer rootDir.Close() - - f, err := MkdirAllHandle(rootDir, unsafePath, mode) - if err != nil { - return err - } - _ = f.Close() - return nil -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/open_linux.go b/vendor/github.com/cyphar/filepath-securejoin/open_linux.go deleted file mode 100644 index 230be73f0eb..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/open_linux.go +++ /dev/null @@ -1,103 +0,0 @@ -//go:build linux - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "fmt" - "os" - "strconv" - - "golang.org/x/sys/unix" -) - -// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided -// using an *[os.File] handle, to ensure that the correct root directory is used. -func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) { - handle, err := completeLookupInRoot(root, unsafePath) - if err != nil { - return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: err} - } - return handle, nil -} - -// OpenInRoot safely opens the provided unsafePath within the root. -// Effectively, OpenInRoot(root, unsafePath) is equivalent to -// -// path, _ := securejoin.SecureJoin(root, unsafePath) -// handle, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC) -// -// But is much safer. The above implementation is unsafe because if an attacker -// can modify the filesystem tree between [SecureJoin] and [os.OpenFile], it is -// possible for the returned file to be outside of the root. -// -// Note that the returned handle is an O_PATH handle, meaning that only a very -// limited set of operations will work on the handle. This is done to avoid -// accidentally opening an untrusted file that could cause issues (such as a -// disconnected TTY that could cause a DoS, or some other issue). In order to -// use the returned handle, you can "upgrade" it to a proper handle using -// [Reopen]. -func OpenInRoot(root, unsafePath string) (*os.File, error) { - rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) - if err != nil { - return nil, err - } - defer rootDir.Close() - return OpenatInRoot(rootDir, unsafePath) -} - -// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd. -// Reopen(file, flags) is effectively equivalent to -// -// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd()) -// os.OpenFile(fdPath, flags|unix.O_CLOEXEC) -// -// But with some extra hardenings to ensure that we are not tricked by a -// maliciously-configured /proc mount. While this attack scenario is not -// common, in container runtimes it is possible for higher-level runtimes to be -// tricked into configuring an unsafe /proc that can be used to attack file -// operations. See [CVE-2019-19921] for more details. -// -// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw -func Reopen(handle *os.File, flags int) (*os.File, error) { - procRoot, err := getProcRoot() - if err != nil { - return nil, err - } - - // We can't operate on /proc/thread-self/fd/$n directly when doing a - // re-open, so we need to open /proc/thread-self/fd and then open a single - // final component. - procFdDir, closer, err := procThreadSelf(procRoot, "fd/") - if err != nil { - return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err) - } - defer procFdDir.Close() - defer closer() - - // Try to detect if there is a mount on top of the magic-link we are about - // to open. If we are using unsafeHostProcRoot(), this could change after - // we check it (and there's nothing we can do about that) but for - // privateProcRoot() this should be guaranteed to be safe (at least since - // Linux 5.12[1], when anonymous mount namespaces were completely isolated - // from external mounts including mount propagation events). - // - // [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts - // onto targets that reside on shared mounts"). - fdStr := strconv.Itoa(int(handle.Fd())) - if err := checkSymlinkOvermount(procRoot, procFdDir, fdStr); err != nil { - return nil, fmt.Errorf("check safety of /proc/thread-self/fd/%s magiclink: %w", fdStr, err) - } - - flags |= unix.O_CLOEXEC - // Rather than just wrapping openatFile, open-code it so we can copy - // handle.Name(). - reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0) - if err != nil { - return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err) - } - return os.NewFile(uintptr(reopenFd), handle.Name()), nil -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go deleted file mode 100644 index f7a13e69ce8..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go +++ /dev/null @@ -1,127 +0,0 @@ -//go:build linux - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "strings" - - "golang.org/x/sys/unix" -) - -var hasOpenat2 = sync_OnceValue(func() bool { - fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{ - Flags: unix.O_PATH | unix.O_CLOEXEC, - Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT, - }) - if err != nil { - return false - } - _ = unix.Close(fd) - return true -}) - -func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool { - // RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve - // ".." while a mount or rename occurs anywhere on the system. This could - // happen spuriously, or as the result of an attacker trying to mess with - // us during lookup. - // - // In addition, scoped lookups have a "safety check" at the end of - // complete_walk which will return -EXDEV if the final path is not in the - // root. - return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 && - (errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV)) -} - -const scopedLookupMaxRetries = 10 - -func openat2File(dir *os.File, path string, how *unix.OpenHow) (*os.File, error) { - fullPath := dir.Name() + "/" + path - // Make sure we always set O_CLOEXEC. - how.Flags |= unix.O_CLOEXEC - var tries int - for tries < scopedLookupMaxRetries { - fd, err := unix.Openat2(int(dir.Fd()), path, how) - if err != nil { - if scopedLookupShouldRetry(how, err) { - // We retry a couple of times to avoid the spurious errors, and - // if we are being attacked then returning -EAGAIN is the best - // we can do. - tries++ - continue - } - return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err} - } - // If we are using RESOLVE_IN_ROOT, the name we generated may be wrong. - // NOTE: The procRoot code MUST NOT use RESOLVE_IN_ROOT, otherwise - // you'll get infinite recursion here. - if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT { - if actualPath, err := rawProcSelfFdReadlink(fd); err == nil { - fullPath = actualPath - } - } - return os.NewFile(uintptr(fd), fullPath), nil - } - return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: errPossibleAttack} -} - -func lookupOpenat2(root *os.File, unsafePath string, partial bool) (*os.File, string, error) { - if !partial { - file, err := openat2File(root, unsafePath, &unix.OpenHow{ - Flags: unix.O_PATH | unix.O_CLOEXEC, - Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, - }) - return file, "", err - } - return partialLookupOpenat2(root, unsafePath) -} - -// partialLookupOpenat2 is an alternative implementation of -// partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a -// handle to the deepest existing child of the requested path within the root. -func partialLookupOpenat2(root *os.File, unsafePath string) (*os.File, string, error) { - // TODO: Implement this as a git-bisect-like binary search. - - unsafePath = filepath.ToSlash(unsafePath) // noop - endIdx := len(unsafePath) - var lastError error - for endIdx > 0 { - subpath := unsafePath[:endIdx] - - handle, err := openat2File(root, subpath, &unix.OpenHow{ - Flags: unix.O_PATH | unix.O_CLOEXEC, - Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, - }) - if err == nil { - // Jump over the slash if we have a non-"" remainingPath. - if endIdx < len(unsafePath) { - endIdx += 1 - } - // We found a subpath! - return handle, unsafePath[endIdx:], lastError - } - if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) { - // That path doesn't exist, let's try the next directory up. - endIdx = strings.LastIndexByte(subpath, '/') - lastError = err - continue - } - return nil, "", fmt.Errorf("open subpath: %w", err) - } - // If we couldn't open anything, the whole subpath is missing. Return a - // copy of the root fd so that the caller doesn't close this one by - // accident. - rootClone, err := dupFile(root) - if err != nil { - return nil, "", err - } - return rootClone, unsafePath, lastError -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/openat_linux.go b/vendor/github.com/cyphar/filepath-securejoin/openat_linux.go deleted file mode 100644 index 949fb5f2d82..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/openat_linux.go +++ /dev/null @@ -1,59 +0,0 @@ -//go:build linux - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "os" - "path/filepath" - - "golang.org/x/sys/unix" -) - -func dupFile(f *os.File) (*os.File, error) { - fd, err := unix.FcntlInt(f.Fd(), unix.F_DUPFD_CLOEXEC, 0) - if err != nil { - return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err) - } - return os.NewFile(uintptr(fd), f.Name()), nil -} - -func openatFile(dir *os.File, path string, flags int, mode int) (*os.File, error) { - // Make sure we always set O_CLOEXEC. - flags |= unix.O_CLOEXEC - fd, err := unix.Openat(int(dir.Fd()), path, flags, uint32(mode)) - if err != nil { - return nil, &os.PathError{Op: "openat", Path: dir.Name() + "/" + path, Err: err} - } - // All of the paths we use with openatFile(2) are guaranteed to be - // lexically safe, so we can use path.Join here. - fullPath := filepath.Join(dir.Name(), path) - return os.NewFile(uintptr(fd), fullPath), nil -} - -func fstatatFile(dir *os.File, path string, flags int) (unix.Stat_t, error) { - var stat unix.Stat_t - if err := unix.Fstatat(int(dir.Fd()), path, &stat, flags); err != nil { - return stat, &os.PathError{Op: "fstatat", Path: dir.Name() + "/" + path, Err: err} - } - return stat, nil -} - -func readlinkatFile(dir *os.File, path string) (string, error) { - size := 4096 - for { - linkBuf := make([]byte, size) - n, err := unix.Readlinkat(int(dir.Fd()), path, linkBuf) - if err != nil { - return "", &os.PathError{Op: "readlinkat", Path: dir.Name() + "/" + path, Err: err} - } - if n != size { - return string(linkBuf[:n]), nil - } - // Possible truncation, resize the buffer. - size *= 2 - } -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go b/vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go deleted file mode 100644 index 809a579cbdb..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go +++ /dev/null @@ -1,452 +0,0 @@ -//go:build linux - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "errors" - "fmt" - "os" - "runtime" - "strconv" - - "golang.org/x/sys/unix" -) - -func fstat(f *os.File) (unix.Stat_t, error) { - var stat unix.Stat_t - if err := unix.Fstat(int(f.Fd()), &stat); err != nil { - return stat, &os.PathError{Op: "fstat", Path: f.Name(), Err: err} - } - return stat, nil -} - -func fstatfs(f *os.File) (unix.Statfs_t, error) { - var statfs unix.Statfs_t - if err := unix.Fstatfs(int(f.Fd()), &statfs); err != nil { - return statfs, &os.PathError{Op: "fstatfs", Path: f.Name(), Err: err} - } - return statfs, nil -} - -// The kernel guarantees that the root inode of a procfs mount has an -// f_type of PROC_SUPER_MAGIC and st_ino of PROC_ROOT_INO. -const ( - procSuperMagic = 0x9fa0 // PROC_SUPER_MAGIC - procRootIno = 1 // PROC_ROOT_INO -) - -func verifyProcRoot(procRoot *os.File) error { - if statfs, err := fstatfs(procRoot); err != nil { - return err - } else if statfs.Type != procSuperMagic { - return fmt.Errorf("%w: incorrect procfs root filesystem type 0x%x", errUnsafeProcfs, statfs.Type) - } - if stat, err := fstat(procRoot); err != nil { - return err - } else if stat.Ino != procRootIno { - return fmt.Errorf("%w: incorrect procfs root inode number %d", errUnsafeProcfs, stat.Ino) - } - return nil -} - -var hasNewMountApi = sync_OnceValue(func() bool { - // All of the pieces of the new mount API we use (fsopen, fsconfig, - // fsmount, open_tree) were added together in Linux 5.1[1,2], so we can - // just check for one of the syscalls and the others should also be - // available. - // - // Just try to use open_tree(2) to open a file without OPEN_TREE_CLONE. - // This is equivalent to openat(2), but tells us if open_tree is - // available (and thus all of the other basic new mount API syscalls). - // open_tree(2) is most light-weight syscall to test here. - // - // [1]: merge commit 400913252d09 - // [2]: - fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC) - if err != nil { - return false - } - _ = unix.Close(fd) - return true -}) - -func fsopen(fsName string, flags int) (*os.File, error) { - // Make sure we always set O_CLOEXEC. - flags |= unix.FSOPEN_CLOEXEC - fd, err := unix.Fsopen(fsName, flags) - if err != nil { - return nil, os.NewSyscallError("fsopen "+fsName, err) - } - return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil -} - -func fsmount(ctx *os.File, flags, mountAttrs int) (*os.File, error) { - // Make sure we always set O_CLOEXEC. - flags |= unix.FSMOUNT_CLOEXEC - fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs) - if err != nil { - return nil, os.NewSyscallError("fsmount "+ctx.Name(), err) - } - return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil -} - -func newPrivateProcMount() (*os.File, error) { - procfsCtx, err := fsopen("proc", unix.FSOPEN_CLOEXEC) - if err != nil { - return nil, err - } - defer procfsCtx.Close() - - // Try to configure hidepid=ptraceable,subset=pid if possible, but ignore errors. - _ = unix.FsconfigSetString(int(procfsCtx.Fd()), "hidepid", "ptraceable") - _ = unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid") - - // Get an actual handle. - if err := unix.FsconfigCreate(int(procfsCtx.Fd())); err != nil { - return nil, os.NewSyscallError("fsconfig create procfs", err) - } - return fsmount(procfsCtx, unix.FSMOUNT_CLOEXEC, unix.MS_RDONLY|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_NOSUID) -} - -func openTree(dir *os.File, path string, flags uint) (*os.File, error) { - dirFd := -int(unix.EBADF) - dirName := "." - if dir != nil { - dirFd = int(dir.Fd()) - dirName = dir.Name() - } - // Make sure we always set O_CLOEXEC. - flags |= unix.OPEN_TREE_CLOEXEC - fd, err := unix.OpenTree(dirFd, path, flags) - if err != nil { - return nil, &os.PathError{Op: "open_tree", Path: path, Err: err} - } - return os.NewFile(uintptr(fd), dirName+"/"+path), nil -} - -func clonePrivateProcMount() (_ *os.File, Err error) { - // Try to make a clone without using AT_RECURSIVE if we can. If this works, - // we can be sure there are no over-mounts and so if the root is valid then - // we're golden. Otherwise, we have to deal with over-mounts. - procfsHandle, err := openTree(nil, "/proc", unix.OPEN_TREE_CLONE) - if err != nil || hookForcePrivateProcRootOpenTreeAtRecursive(procfsHandle) { - procfsHandle, err = openTree(nil, "/proc", unix.OPEN_TREE_CLONE|unix.AT_RECURSIVE) - } - if err != nil { - return nil, fmt.Errorf("creating a detached procfs clone: %w", err) - } - defer func() { - if Err != nil { - _ = procfsHandle.Close() - } - }() - if err := verifyProcRoot(procfsHandle); err != nil { - return nil, err - } - return procfsHandle, nil -} - -func privateProcRoot() (*os.File, error) { - if !hasNewMountApi() || hookForceGetProcRootUnsafe() { - return nil, fmt.Errorf("new mount api: %w", unix.ENOTSUP) - } - // Try to create a new procfs mount from scratch if we can. This ensures we - // can get a procfs mount even if /proc is fake (for whatever reason). - procRoot, err := newPrivateProcMount() - if err != nil || hookForcePrivateProcRootOpenTree(procRoot) { - // Try to clone /proc then... - procRoot, err = clonePrivateProcMount() - } - return procRoot, err -} - -func unsafeHostProcRoot() (_ *os.File, Err error) { - procRoot, err := os.OpenFile("/proc", unix.O_PATH|unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) - if err != nil { - return nil, err - } - defer func() { - if Err != nil { - _ = procRoot.Close() - } - }() - if err := verifyProcRoot(procRoot); err != nil { - return nil, err - } - return procRoot, nil -} - -func doGetProcRoot() (*os.File, error) { - procRoot, err := privateProcRoot() - if err != nil { - // Fall back to using a /proc handle if making a private mount failed. - // If we have openat2, at least we can avoid some kinds of over-mount - // attacks, but without openat2 there's not much we can do. - procRoot, err = unsafeHostProcRoot() - } - return procRoot, err -} - -var getProcRoot = sync_OnceValues(func() (*os.File, error) { - return doGetProcRoot() -}) - -var hasProcThreadSelf = sync_OnceValue(func() bool { - return unix.Access("/proc/thread-self/", unix.F_OK) == nil -}) - -var errUnsafeProcfs = errors.New("unsafe procfs detected") - -type procThreadSelfCloser func() - -// procThreadSelf returns a handle to /proc/thread-self/ (or an -// equivalent handle on older kernels where /proc/thread-self doesn't exist). -// Once finished with the handle, you must call the returned closer function -// (runtime.UnlockOSThread). You must not pass the returned *os.File to other -// Go threads or use the handle after calling the closer. -// -// This is similar to ProcThreadSelf from runc, but with extra hardening -// applied and using *os.File. -func procThreadSelf(procRoot *os.File, subpath string) (_ *os.File, _ procThreadSelfCloser, Err error) { - // We need to lock our thread until the caller is done with the handle - // because between getting the handle and using it we could get interrupted - // by the Go runtime and hit the case where the underlying thread is - // swapped out and the original thread is killed, resulting in - // pull-your-hair-out-hard-to-debug issues in the caller. - runtime.LockOSThread() - defer func() { - if Err != nil { - runtime.UnlockOSThread() - } - }() - - // Figure out what prefix we want to use. - threadSelf := "thread-self/" - if !hasProcThreadSelf() || hookForceProcSelfTask() { - /// Pre-3.17 kernels don't have /proc/thread-self, so do it manually. - threadSelf = "self/task/" + strconv.Itoa(unix.Gettid()) + "/" - if _, err := fstatatFile(procRoot, threadSelf, unix.AT_SYMLINK_NOFOLLOW); err != nil || hookForceProcSelf() { - // In this case, we running in a pid namespace that doesn't match - // the /proc mount we have. This can happen inside runc. - // - // Unfortunately, there is no nice way to get the correct TID to - // use here because of the age of the kernel, so we have to just - // use /proc/self and hope that it works. - threadSelf = "self/" - } - } - - // Grab the handle. - var ( - handle *os.File - err error - ) - if hasOpenat2() { - // We prefer being able to use RESOLVE_NO_XDEV if we can, to be - // absolutely sure we are operating on a clean /proc handle that - // doesn't have any cheeky overmounts that could trick us (including - // symlink mounts on top of /proc/thread-self). RESOLVE_BENEATH isn't - // strictly needed, but just use it since we have it. - // - // NOTE: /proc/self is technically a magic-link (the contents of the - // symlink are generated dynamically), but it doesn't use - // nd_jump_link() so RESOLVE_NO_MAGICLINKS allows it. - // - // NOTE: We MUST NOT use RESOLVE_IN_ROOT here, as openat2File uses - // procSelfFdReadlink to clean up the returned f.Name() if we use - // RESOLVE_IN_ROOT (which would lead to an infinite recursion). - handle, err = openat2File(procRoot, threadSelf+subpath, &unix.OpenHow{ - Flags: unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC, - Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_MAGICLINKS, - }) - if err != nil { - // TODO: Once we bump the minimum Go version to 1.20, we can use - // multiple %w verbs for this wrapping. For now we need to use a - // compatibility shim for older Go versions. - //err = fmt.Errorf("%w: %w", errUnsafeProcfs, err) - return nil, nil, wrapBaseError(err, errUnsafeProcfs) - } - } else { - handle, err = openatFile(procRoot, threadSelf+subpath, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) - if err != nil { - // TODO: Once we bump the minimum Go version to 1.20, we can use - // multiple %w verbs for this wrapping. For now we need to use a - // compatibility shim for older Go versions. - //err = fmt.Errorf("%w: %w", errUnsafeProcfs, err) - return nil, nil, wrapBaseError(err, errUnsafeProcfs) - } - defer func() { - if Err != nil { - _ = handle.Close() - } - }() - // We can't detect bind-mounts of different parts of procfs on top of - // /proc (a-la RESOLVE_NO_XDEV), but we can at least be sure that we - // aren't on the wrong filesystem here. - if statfs, err := fstatfs(handle); err != nil { - return nil, nil, err - } else if statfs.Type != procSuperMagic { - return nil, nil, fmt.Errorf("%w: incorrect /proc/self/fd filesystem type 0x%x", errUnsafeProcfs, statfs.Type) - } - } - return handle, runtime.UnlockOSThread, nil -} - -// STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to -// avoid bumping the requirement for a single constant we can just define it -// ourselves. -const STATX_MNT_ID_UNIQUE = 0x4000 - -var hasStatxMountId = sync_OnceValue(func() bool { - var ( - stx unix.Statx_t - // We don't care which mount ID we get. The kernel will give us the - // unique one if it is supported. - wantStxMask uint32 = STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID - ) - err := unix.Statx(-int(unix.EBADF), "/", 0, int(wantStxMask), &stx) - return err == nil && stx.Mask&wantStxMask != 0 -}) - -func getMountId(dir *os.File, path string) (uint64, error) { - // If we don't have statx(STATX_MNT_ID*) support, we can't do anything. - if !hasStatxMountId() { - return 0, nil - } - - var ( - stx unix.Statx_t - // We don't care which mount ID we get. The kernel will give us the - // unique one if it is supported. - wantStxMask uint32 = STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID - ) - - err := unix.Statx(int(dir.Fd()), path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, int(wantStxMask), &stx) - if stx.Mask&wantStxMask == 0 { - // It's not a kernel limitation, for some reason we couldn't get a - // mount ID. Assume it's some kind of attack. - err = fmt.Errorf("%w: could not get mount id", errUnsafeProcfs) - } - if err != nil { - return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: dir.Name() + "/" + path, Err: err} - } - return stx.Mnt_id, nil -} - -func checkSymlinkOvermount(procRoot *os.File, dir *os.File, path string) error { - // Get the mntId of our procfs handle. - expectedMountId, err := getMountId(procRoot, "") - if err != nil { - return err - } - // Get the mntId of the target magic-link. - gotMountId, err := getMountId(dir, path) - if err != nil { - return err - } - // As long as the directory mount is alive, even with wrapping mount IDs, - // we would expect to see a different mount ID here. (Of course, if we're - // using unsafeHostProcRoot() then an attaker could change this after we - // did this check.) - if expectedMountId != gotMountId { - return fmt.Errorf("%w: symlink %s/%s has an overmount obscuring the real link (mount ids do not match %d != %d)", errUnsafeProcfs, dir.Name(), path, expectedMountId, gotMountId) - } - return nil -} - -func doRawProcSelfFdReadlink(procRoot *os.File, fd int) (string, error) { - fdPath := fmt.Sprintf("fd/%d", fd) - procFdLink, closer, err := procThreadSelf(procRoot, fdPath) - if err != nil { - return "", fmt.Errorf("get safe /proc/thread-self/%s handle: %w", fdPath, err) - } - defer procFdLink.Close() - defer closer() - - // Try to detect if there is a mount on top of the magic-link. Since we use the handle directly - // provide to the closure. If the closure uses the handle directly, this - // should be safe in general (a mount on top of the path afterwards would - // not affect the handle itself) and will definitely be safe if we are - // using privateProcRoot() (at least since Linux 5.12[1], when anonymous - // mount namespaces were completely isolated from external mounts including - // mount propagation events). - // - // [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts - // onto targets that reside on shared mounts"). - if err := checkSymlinkOvermount(procRoot, procFdLink, ""); err != nil { - return "", fmt.Errorf("check safety of /proc/thread-self/fd/%d magiclink: %w", fd, err) - } - - // readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See Linux commit - // 65cfc6722361 ("readlinkat(), fchownat() and fstatat() with empty - // relative pathnames"). - return readlinkatFile(procFdLink, "") -} - -func rawProcSelfFdReadlink(fd int) (string, error) { - procRoot, err := getProcRoot() - if err != nil { - return "", err - } - return doRawProcSelfFdReadlink(procRoot, fd) -} - -func procSelfFdReadlink(f *os.File) (string, error) { - return rawProcSelfFdReadlink(int(f.Fd())) -} - -var ( - errPossibleBreakout = errors.New("possible breakout detected") - errInvalidDirectory = errors.New("wandered into deleted directory") - errDeletedInode = errors.New("cannot verify path of deleted inode") -) - -func isDeadInode(file *os.File) error { - // If the nlink of a file drops to 0, there is an attacker deleting - // directories during our walk, which could result in weird /proc values. - // It's better to error out in this case. - stat, err := fstat(file) - if err != nil { - return fmt.Errorf("check for dead inode: %w", err) - } - if stat.Nlink == 0 { - err := errDeletedInode - if stat.Mode&unix.S_IFMT == unix.S_IFDIR { - err = errInvalidDirectory - } - return fmt.Errorf("%w %q", err, file.Name()) - } - return nil -} - -func checkProcSelfFdPath(path string, file *os.File) error { - if err := isDeadInode(file); err != nil { - return err - } - actualPath, err := procSelfFdReadlink(file) - if err != nil { - return fmt.Errorf("get path of handle: %w", err) - } - if actualPath != path { - return fmt.Errorf("%w: handle path %q doesn't match expected path %q", errPossibleBreakout, actualPath, path) - } - return nil -} - -// Test hooks used in the procfs tests to verify that the fallback logic works. -// See testing_mocks_linux_test.go and procfs_linux_test.go for more details. -var ( - hookForcePrivateProcRootOpenTree = hookDummyFile - hookForcePrivateProcRootOpenTreeAtRecursive = hookDummyFile - hookForceGetProcRootUnsafe = hookDummy - - hookForceProcSelfTask = hookDummy - hookForceProcSelf = hookDummy -) - -func hookDummy() bool { return false } -func hookDummyFile(_ *os.File) bool { return false } diff --git a/vendor/github.com/cyphar/filepath-securejoin/vfs.go b/vendor/github.com/cyphar/filepath-securejoin/vfs.go deleted file mode 100644 index 36373f8c517..00000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/vfs.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2017-2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import "os" - -// In future this should be moved into a separate package, because now there -// are several projects (umoci and go-mtree) that are using this sort of -// interface. - -// VFS is the minimal interface necessary to use [SecureJoinVFS]. A nil VFS is -// equivalent to using the standard [os].* family of functions. This is mainly -// used for the purposes of mock testing, but also can be used to otherwise use -// [SecureJoinVFS] with VFS-like system. -type VFS interface { - // Lstat returns an [os.FileInfo] describing the named file. If the - // file is a symbolic link, the returned [os.FileInfo] describes the - // symbolic link. Lstat makes no attempt to follow the link. - // The semantics are identical to [os.Lstat]. - Lstat(name string) (os.FileInfo, error) - - // Readlink returns the destination of the named symbolic link. - // The semantics are identical to [os.Readlink]. - Readlink(name string) (string, error) -} - -// osVFS is the "nil" VFS, in that it just passes everything through to the os -// module. -type osVFS struct{} - -func (o osVFS) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) } - -func (o osVFS) Readlink(name string) (string, error) { return os.Readlink(name) } diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE deleted file mode 100644 index bc52e96f2b0..00000000000 --- a/vendor/github.com/davecgh/go-spew/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -ISC License - -Copyright (c) 2012-2016 Dave Collins - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go deleted file mode 100644 index 792994785e3..00000000000 --- a/vendor/github.com/davecgh/go-spew/spew/bypass.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) 2015-2016 Dave Collins -// -// Permission to use, copy, modify, and distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -// NOTE: Due to the following build constraints, this file will only be compiled -// when the code is not running on Google App Engine, compiled by GopherJS, and -// "-tags safe" is not added to the go build command line. The "disableunsafe" -// tag is deprecated and thus should not be used. -// Go versions prior to 1.4 are disabled because they use a different layout -// for interfaces which make the implementation of unsafeReflectValue more complex. -// +build !js,!appengine,!safe,!disableunsafe,go1.4 - -package spew - -import ( - "reflect" - "unsafe" -) - -const ( - // UnsafeDisabled is a build-time constant which specifies whether or - // not access to the unsafe package is available. - UnsafeDisabled = false - - // ptrSize is the size of a pointer on the current arch. - ptrSize = unsafe.Sizeof((*byte)(nil)) -) - -type flag uintptr - -var ( - // flagRO indicates whether the value field of a reflect.Value - // is read-only. - flagRO flag - - // flagAddr indicates whether the address of the reflect.Value's - // value may be taken. - flagAddr flag -) - -// flagKindMask holds the bits that make up the kind -// part of the flags field. In all the supported versions, -// it is in the lower 5 bits. -const flagKindMask = flag(0x1f) - -// Different versions of Go have used different -// bit layouts for the flags type. This table -// records the known combinations. -var okFlags = []struct { - ro, addr flag -}{{ - // From Go 1.4 to 1.5 - ro: 1 << 5, - addr: 1 << 7, -}, { - // Up to Go tip. - ro: 1<<5 | 1<<6, - addr: 1 << 8, -}} - -var flagValOffset = func() uintptr { - field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") - if !ok { - panic("reflect.Value has no flag field") - } - return field.Offset -}() - -// flagField returns a pointer to the flag field of a reflect.Value. -func flagField(v *reflect.Value) *flag { - return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset)) -} - -// unsafeReflectValue converts the passed reflect.Value into a one that bypasses -// the typical safety restrictions preventing access to unaddressable and -// unexported data. It works by digging the raw pointer to the underlying -// value out of the protected value and generating a new unprotected (unsafe) -// reflect.Value to it. -// -// This allows us to check for implementations of the Stringer and error -// interfaces to be used for pretty printing ordinarily unaddressable and -// inaccessible values such as unexported struct fields. -func unsafeReflectValue(v reflect.Value) reflect.Value { - if !v.IsValid() || (v.CanInterface() && v.CanAddr()) { - return v - } - flagFieldPtr := flagField(&v) - *flagFieldPtr &^= flagRO - *flagFieldPtr |= flagAddr - return v -} - -// Sanity checks against future reflect package changes -// to the type or semantics of the Value.flag field. -func init() { - field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") - if !ok { - panic("reflect.Value has no flag field") - } - if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() { - panic("reflect.Value flag field has changed kind") - } - type t0 int - var t struct { - A t0 - // t0 will have flagEmbedRO set. - t0 - // a will have flagStickyRO set - a t0 - } - vA := reflect.ValueOf(t).FieldByName("A") - va := reflect.ValueOf(t).FieldByName("a") - vt0 := reflect.ValueOf(t).FieldByName("t0") - - // Infer flagRO from the difference between the flags - // for the (otherwise identical) fields in t. - flagPublic := *flagField(&vA) - flagWithRO := *flagField(&va) | *flagField(&vt0) - flagRO = flagPublic ^ flagWithRO - - // Infer flagAddr from the difference between a value - // taken from a pointer and not. - vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A") - flagNoPtr := *flagField(&vA) - flagPtr := *flagField(&vPtrA) - flagAddr = flagNoPtr ^ flagPtr - - // Check that the inferred flags tally with one of the known versions. - for _, f := range okFlags { - if flagRO == f.ro && flagAddr == f.addr { - return - } - } - panic("reflect.Value read-only flag has changed semantics") -} diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go deleted file mode 100644 index 205c28d68c4..00000000000 --- a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2015-2016 Dave Collins -// -// Permission to use, copy, modify, and distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -// NOTE: Due to the following build constraints, this file will only be compiled -// when the code is running on Google App Engine, compiled by GopherJS, or -// "-tags safe" is added to the go build command line. The "disableunsafe" -// tag is deprecated and thus should not be used. -// +build js appengine safe disableunsafe !go1.4 - -package spew - -import "reflect" - -const ( - // UnsafeDisabled is a build-time constant which specifies whether or - // not access to the unsafe package is available. - UnsafeDisabled = true -) - -// unsafeReflectValue typically converts the passed reflect.Value into a one -// that bypasses the typical safety restrictions preventing access to -// unaddressable and unexported data. However, doing this relies on access to -// the unsafe package. This is a stub version which simply returns the passed -// reflect.Value when the unsafe package is not available. -func unsafeReflectValue(v reflect.Value) reflect.Value { - return v -} diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go deleted file mode 100644 index 1be8ce94576..00000000000 --- a/vendor/github.com/davecgh/go-spew/spew/common.go +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (c) 2013-2016 Dave Collins - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -package spew - -import ( - "bytes" - "fmt" - "io" - "reflect" - "sort" - "strconv" -) - -// Some constants in the form of bytes to avoid string overhead. This mirrors -// the technique used in the fmt package. -var ( - panicBytes = []byte("(PANIC=") - plusBytes = []byte("+") - iBytes = []byte("i") - trueBytes = []byte("true") - falseBytes = []byte("false") - interfaceBytes = []byte("(interface {})") - commaNewlineBytes = []byte(",\n") - newlineBytes = []byte("\n") - openBraceBytes = []byte("{") - openBraceNewlineBytes = []byte("{\n") - closeBraceBytes = []byte("}") - asteriskBytes = []byte("*") - colonBytes = []byte(":") - colonSpaceBytes = []byte(": ") - openParenBytes = []byte("(") - closeParenBytes = []byte(")") - spaceBytes = []byte(" ") - pointerChainBytes = []byte("->") - nilAngleBytes = []byte("") - maxNewlineBytes = []byte("\n") - maxShortBytes = []byte("") - circularBytes = []byte("") - circularShortBytes = []byte("") - invalidAngleBytes = []byte("") - openBracketBytes = []byte("[") - closeBracketBytes = []byte("]") - percentBytes = []byte("%") - precisionBytes = []byte(".") - openAngleBytes = []byte("<") - closeAngleBytes = []byte(">") - openMapBytes = []byte("map[") - closeMapBytes = []byte("]") - lenEqualsBytes = []byte("len=") - capEqualsBytes = []byte("cap=") -) - -// hexDigits is used to map a decimal value to a hex digit. -var hexDigits = "0123456789abcdef" - -// catchPanic handles any panics that might occur during the handleMethods -// calls. -func catchPanic(w io.Writer, v reflect.Value) { - if err := recover(); err != nil { - w.Write(panicBytes) - fmt.Fprintf(w, "%v", err) - w.Write(closeParenBytes) - } -} - -// handleMethods attempts to call the Error and String methods on the underlying -// type the passed reflect.Value represents and outputes the result to Writer w. -// -// It handles panics in any called methods by catching and displaying the error -// as the formatted value. -func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) { - // We need an interface to check if the type implements the error or - // Stringer interface. However, the reflect package won't give us an - // interface on certain things like unexported struct fields in order - // to enforce visibility rules. We use unsafe, when it's available, - // to bypass these restrictions since this package does not mutate the - // values. - if !v.CanInterface() { - if UnsafeDisabled { - return false - } - - v = unsafeReflectValue(v) - } - - // Choose whether or not to do error and Stringer interface lookups against - // the base type or a pointer to the base type depending on settings. - // Technically calling one of these methods with a pointer receiver can - // mutate the value, however, types which choose to satisify an error or - // Stringer interface with a pointer receiver should not be mutating their - // state inside these interface methods. - if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() { - v = unsafeReflectValue(v) - } - if v.CanAddr() { - v = v.Addr() - } - - // Is it an error or Stringer? - switch iface := v.Interface().(type) { - case error: - defer catchPanic(w, v) - if cs.ContinueOnMethod { - w.Write(openParenBytes) - w.Write([]byte(iface.Error())) - w.Write(closeParenBytes) - w.Write(spaceBytes) - return false - } - - w.Write([]byte(iface.Error())) - return true - - case fmt.Stringer: - defer catchPanic(w, v) - if cs.ContinueOnMethod { - w.Write(openParenBytes) - w.Write([]byte(iface.String())) - w.Write(closeParenBytes) - w.Write(spaceBytes) - return false - } - w.Write([]byte(iface.String())) - return true - } - return false -} - -// printBool outputs a boolean value as true or false to Writer w. -func printBool(w io.Writer, val bool) { - if val { - w.Write(trueBytes) - } else { - w.Write(falseBytes) - } -} - -// printInt outputs a signed integer value to Writer w. -func printInt(w io.Writer, val int64, base int) { - w.Write([]byte(strconv.FormatInt(val, base))) -} - -// printUint outputs an unsigned integer value to Writer w. -func printUint(w io.Writer, val uint64, base int) { - w.Write([]byte(strconv.FormatUint(val, base))) -} - -// printFloat outputs a floating point value using the specified precision, -// which is expected to be 32 or 64bit, to Writer w. -func printFloat(w io.Writer, val float64, precision int) { - w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision))) -} - -// printComplex outputs a complex value using the specified float precision -// for the real and imaginary parts to Writer w. -func printComplex(w io.Writer, c complex128, floatPrecision int) { - r := real(c) - w.Write(openParenBytes) - w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision))) - i := imag(c) - if i >= 0 { - w.Write(plusBytes) - } - w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision))) - w.Write(iBytes) - w.Write(closeParenBytes) -} - -// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x' -// prefix to Writer w. -func printHexPtr(w io.Writer, p uintptr) { - // Null pointer. - num := uint64(p) - if num == 0 { - w.Write(nilAngleBytes) - return - } - - // Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix - buf := make([]byte, 18) - - // It's simpler to construct the hex string right to left. - base := uint64(16) - i := len(buf) - 1 - for num >= base { - buf[i] = hexDigits[num%base] - num /= base - i-- - } - buf[i] = hexDigits[num] - - // Add '0x' prefix. - i-- - buf[i] = 'x' - i-- - buf[i] = '0' - - // Strip unused leading bytes. - buf = buf[i:] - w.Write(buf) -} - -// valuesSorter implements sort.Interface to allow a slice of reflect.Value -// elements to be sorted. -type valuesSorter struct { - values []reflect.Value - strings []string // either nil or same len and values - cs *ConfigState -} - -// newValuesSorter initializes a valuesSorter instance, which holds a set of -// surrogate keys on which the data should be sorted. It uses flags in -// ConfigState to decide if and how to populate those surrogate keys. -func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface { - vs := &valuesSorter{values: values, cs: cs} - if canSortSimply(vs.values[0].Kind()) { - return vs - } - if !cs.DisableMethods { - vs.strings = make([]string, len(values)) - for i := range vs.values { - b := bytes.Buffer{} - if !handleMethods(cs, &b, vs.values[i]) { - vs.strings = nil - break - } - vs.strings[i] = b.String() - } - } - if vs.strings == nil && cs.SpewKeys { - vs.strings = make([]string, len(values)) - for i := range vs.values { - vs.strings[i] = Sprintf("%#v", vs.values[i].Interface()) - } - } - return vs -} - -// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted -// directly, or whether it should be considered for sorting by surrogate keys -// (if the ConfigState allows it). -func canSortSimply(kind reflect.Kind) bool { - // This switch parallels valueSortLess, except for the default case. - switch kind { - case reflect.Bool: - return true - case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: - return true - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - return true - case reflect.Float32, reflect.Float64: - return true - case reflect.String: - return true - case reflect.Uintptr: - return true - case reflect.Array: - return true - } - return false -} - -// Len returns the number of values in the slice. It is part of the -// sort.Interface implementation. -func (s *valuesSorter) Len() int { - return len(s.values) -} - -// Swap swaps the values at the passed indices. It is part of the -// sort.Interface implementation. -func (s *valuesSorter) Swap(i, j int) { - s.values[i], s.values[j] = s.values[j], s.values[i] - if s.strings != nil { - s.strings[i], s.strings[j] = s.strings[j], s.strings[i] - } -} - -// valueSortLess returns whether the first value should sort before the second -// value. It is used by valueSorter.Less as part of the sort.Interface -// implementation. -func valueSortLess(a, b reflect.Value) bool { - switch a.Kind() { - case reflect.Bool: - return !a.Bool() && b.Bool() - case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: - return a.Int() < b.Int() - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - return a.Uint() < b.Uint() - case reflect.Float32, reflect.Float64: - return a.Float() < b.Float() - case reflect.String: - return a.String() < b.String() - case reflect.Uintptr: - return a.Uint() < b.Uint() - case reflect.Array: - // Compare the contents of both arrays. - l := a.Len() - for i := 0; i < l; i++ { - av := a.Index(i) - bv := b.Index(i) - if av.Interface() == bv.Interface() { - continue - } - return valueSortLess(av, bv) - } - } - return a.String() < b.String() -} - -// Less returns whether the value at index i should sort before the -// value at index j. It is part of the sort.Interface implementation. -func (s *valuesSorter) Less(i, j int) bool { - if s.strings == nil { - return valueSortLess(s.values[i], s.values[j]) - } - return s.strings[i] < s.strings[j] -} - -// sortValues is a sort function that handles both native types and any type that -// can be converted to error or Stringer. Other inputs are sorted according to -// their Value.String() value to ensure display stability. -func sortValues(values []reflect.Value, cs *ConfigState) { - if len(values) == 0 { - return - } - sort.Sort(newValuesSorter(values, cs)) -} diff --git a/vendor/github.com/davecgh/go-spew/spew/config.go b/vendor/github.com/davecgh/go-spew/spew/config.go deleted file mode 100644 index 2e3d22f3120..00000000000 --- a/vendor/github.com/davecgh/go-spew/spew/config.go +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (c) 2013-2016 Dave Collins - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -package spew - -import ( - "bytes" - "fmt" - "io" - "os" -) - -// ConfigState houses the configuration options used by spew to format and -// display values. There is a global instance, Config, that is used to control -// all top-level Formatter and Dump functionality. Each ConfigState instance -// provides methods equivalent to the top-level functions. -// -// The zero value for ConfigState provides no indentation. You would typically -// want to set it to a space or a tab. -// -// Alternatively, you can use NewDefaultConfig to get a ConfigState instance -// with default settings. See the documentation of NewDefaultConfig for default -// values. -type ConfigState struct { - // Indent specifies the string to use for each indentation level. The - // global config instance that all top-level functions use set this to a - // single space by default. If you would like more indentation, you might - // set this to a tab with "\t" or perhaps two spaces with " ". - Indent string - - // MaxDepth controls the maximum number of levels to descend into nested - // data structures. The default, 0, means there is no limit. - // - // NOTE: Circular data structures are properly detected, so it is not - // necessary to set this value unless you specifically want to limit deeply - // nested data structures. - MaxDepth int - - // DisableMethods specifies whether or not error and Stringer interfaces are - // invoked for types that implement them. - DisableMethods bool - - // DisablePointerMethods specifies whether or not to check for and invoke - // error and Stringer interfaces on types which only accept a pointer - // receiver when the current type is not a pointer. - // - // NOTE: This might be an unsafe action since calling one of these methods - // with a pointer receiver could technically mutate the value, however, - // in practice, types which choose to satisify an error or Stringer - // interface with a pointer receiver should not be mutating their state - // inside these interface methods. As a result, this option relies on - // access to the unsafe package, so it will not have any effect when - // running in environments without access to the unsafe package such as - // Google App Engine or with the "safe" build tag specified. - DisablePointerMethods bool - - // DisablePointerAddresses specifies whether to disable the printing of - // pointer addresses. This is useful when diffing data structures in tests. - DisablePointerAddresses bool - - // DisableCapacities specifies whether to disable the printing of capacities - // for arrays, slices, maps and channels. This is useful when diffing - // data structures in tests. - DisableCapacities bool - - // ContinueOnMethod specifies whether or not recursion should continue once - // a custom error or Stringer interface is invoked. The default, false, - // means it will print the results of invoking the custom error or Stringer - // interface and return immediately instead of continuing to recurse into - // the internals of the data type. - // - // NOTE: This flag does not have any effect if method invocation is disabled - // via the DisableMethods or DisablePointerMethods options. - ContinueOnMethod bool - - // SortKeys specifies map keys should be sorted before being printed. Use - // this to have a more deterministic, diffable output. Note that only - // native types (bool, int, uint, floats, uintptr and string) and types - // that support the error or Stringer interfaces (if methods are - // enabled) are supported, with other types sorted according to the - // reflect.Value.String() output which guarantees display stability. - SortKeys bool - - // SpewKeys specifies that, as a last resort attempt, map keys should - // be spewed to strings and sorted by those strings. This is only - // considered if SortKeys is true. - SpewKeys bool -} - -// Config is the active configuration of the top-level functions. -// The configuration can be changed by modifying the contents of spew.Config. -var Config = ConfigState{Indent: " "} - -// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were -// passed with a Formatter interface returned by c.NewFormatter. It returns -// the formatted string as a value that satisfies error. See NewFormatter -// for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b)) -func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) { - return fmt.Errorf(format, c.convertArgs(a)...) -} - -// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were -// passed with a Formatter interface returned by c.NewFormatter. It returns -// the number of bytes written and any write error encountered. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b)) -func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) { - return fmt.Fprint(w, c.convertArgs(a)...) -} - -// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were -// passed with a Formatter interface returned by c.NewFormatter. It returns -// the number of bytes written and any write error encountered. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b)) -func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { - return fmt.Fprintf(w, format, c.convertArgs(a)...) -} - -// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it -// passed with a Formatter interface returned by c.NewFormatter. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b)) -func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { - return fmt.Fprintln(w, c.convertArgs(a)...) -} - -// Print is a wrapper for fmt.Print that treats each argument as if it were -// passed with a Formatter interface returned by c.NewFormatter. It returns -// the number of bytes written and any write error encountered. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Print(c.NewFormatter(a), c.NewFormatter(b)) -func (c *ConfigState) Print(a ...interface{}) (n int, err error) { - return fmt.Print(c.convertArgs(a)...) -} - -// Printf is a wrapper for fmt.Printf that treats each argument as if it were -// passed with a Formatter interface returned by c.NewFormatter. It returns -// the number of bytes written and any write error encountered. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b)) -func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) { - return fmt.Printf(format, c.convertArgs(a)...) -} - -// Println is a wrapper for fmt.Println that treats each argument as if it were -// passed with a Formatter interface returned by c.NewFormatter. It returns -// the number of bytes written and any write error encountered. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Println(c.NewFormatter(a), c.NewFormatter(b)) -func (c *ConfigState) Println(a ...interface{}) (n int, err error) { - return fmt.Println(c.convertArgs(a)...) -} - -// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were -// passed with a Formatter interface returned by c.NewFormatter. It returns -// the resulting string. See NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b)) -func (c *ConfigState) Sprint(a ...interface{}) string { - return fmt.Sprint(c.convertArgs(a)...) -} - -// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were -// passed with a Formatter interface returned by c.NewFormatter. It returns -// the resulting string. See NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b)) -func (c *ConfigState) Sprintf(format string, a ...interface{}) string { - return fmt.Sprintf(format, c.convertArgs(a)...) -} - -// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it -// were passed with a Formatter interface returned by c.NewFormatter. It -// returns the resulting string. See NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b)) -func (c *ConfigState) Sprintln(a ...interface{}) string { - return fmt.Sprintln(c.convertArgs(a)...) -} - -/* -NewFormatter returns a custom formatter that satisfies the fmt.Formatter -interface. As a result, it integrates cleanly with standard fmt package -printing functions. The formatter is useful for inline printing of smaller data -types similar to the standard %v format specifier. - -The custom formatter only responds to the %v (most compact), %+v (adds pointer -addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb -combinations. Any other verbs such as %x and %q will be sent to the the -standard fmt package for formatting. In addition, the custom formatter ignores -the width and precision arguments (however they will still work on the format -specifiers not handled by the custom formatter). - -Typically this function shouldn't be called directly. It is much easier to make -use of the custom formatter by calling one of the convenience functions such as -c.Printf, c.Println, or c.Printf. -*/ -func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter { - return newFormatter(c, v) -} - -// Fdump formats and displays the passed arguments to io.Writer w. It formats -// exactly the same as Dump. -func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) { - fdump(c, w, a...) -} - -/* -Dump displays the passed parameters to standard out with newlines, customizable -indentation, and additional debug information such as complete types and all -pointer addresses used to indirect to the final value. It provides the -following features over the built-in printing facilities provided by the fmt -package: - - * Pointers are dereferenced and followed - * Circular data structures are detected and handled properly - * Custom Stringer/error interfaces are optionally invoked, including - on unexported types - * Custom types which only implement the Stringer/error interfaces via - a pointer receiver are optionally invoked when passing non-pointer - variables - * Byte arrays and slices are dumped like the hexdump -C command which - includes offsets, byte values in hex, and ASCII output - -The configuration options are controlled by modifying the public members -of c. See ConfigState for options documentation. - -See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to -get the formatted result as a string. -*/ -func (c *ConfigState) Dump(a ...interface{}) { - fdump(c, os.Stdout, a...) -} - -// Sdump returns a string with the passed arguments formatted exactly the same -// as Dump. -func (c *ConfigState) Sdump(a ...interface{}) string { - var buf bytes.Buffer - fdump(c, &buf, a...) - return buf.String() -} - -// convertArgs accepts a slice of arguments and returns a slice of the same -// length with each argument converted to a spew Formatter interface using -// the ConfigState associated with s. -func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) { - formatters = make([]interface{}, len(args)) - for index, arg := range args { - formatters[index] = newFormatter(c, arg) - } - return formatters -} - -// NewDefaultConfig returns a ConfigState with the following default settings. -// -// Indent: " " -// MaxDepth: 0 -// DisableMethods: false -// DisablePointerMethods: false -// ContinueOnMethod: false -// SortKeys: false -func NewDefaultConfig() *ConfigState { - return &ConfigState{Indent: " "} -} diff --git a/vendor/github.com/davecgh/go-spew/spew/doc.go b/vendor/github.com/davecgh/go-spew/spew/doc.go deleted file mode 100644 index aacaac6f1e1..00000000000 --- a/vendor/github.com/davecgh/go-spew/spew/doc.go +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (c) 2013-2016 Dave Collins - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -/* -Package spew implements a deep pretty printer for Go data structures to aid in -debugging. - -A quick overview of the additional features spew provides over the built-in -printing facilities for Go data types are as follows: - - * Pointers are dereferenced and followed - * Circular data structures are detected and handled properly - * Custom Stringer/error interfaces are optionally invoked, including - on unexported types - * Custom types which only implement the Stringer/error interfaces via - a pointer receiver are optionally invoked when passing non-pointer - variables - * Byte arrays and slices are dumped like the hexdump -C command which - includes offsets, byte values in hex, and ASCII output (only when using - Dump style) - -There are two different approaches spew allows for dumping Go data structures: - - * Dump style which prints with newlines, customizable indentation, - and additional debug information such as types and all pointer addresses - used to indirect to the final value - * A custom Formatter interface that integrates cleanly with the standard fmt - package and replaces %v, %+v, %#v, and %#+v to provide inline printing - similar to the default %v while providing the additional functionality - outlined above and passing unsupported format verbs such as %x and %q - along to fmt - -Quick Start - -This section demonstrates how to quickly get started with spew. See the -sections below for further details on formatting and configuration options. - -To dump a variable with full newlines, indentation, type, and pointer -information use Dump, Fdump, or Sdump: - spew.Dump(myVar1, myVar2, ...) - spew.Fdump(someWriter, myVar1, myVar2, ...) - str := spew.Sdump(myVar1, myVar2, ...) - -Alternatively, if you would prefer to use format strings with a compacted inline -printing style, use the convenience wrappers Printf, Fprintf, etc with -%v (most compact), %+v (adds pointer addresses), %#v (adds types), or -%#+v (adds types and pointer addresses): - spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) - spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) - spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) - spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) - -Configuration Options - -Configuration of spew is handled by fields in the ConfigState type. For -convenience, all of the top-level functions use a global state available -via the spew.Config global. - -It is also possible to create a ConfigState instance that provides methods -equivalent to the top-level functions. This allows concurrent configuration -options. See the ConfigState documentation for more details. - -The following configuration options are available: - * Indent - String to use for each indentation level for Dump functions. - It is a single space by default. A popular alternative is "\t". - - * MaxDepth - Maximum number of levels to descend into nested data structures. - There is no limit by default. - - * DisableMethods - Disables invocation of error and Stringer interface methods. - Method invocation is enabled by default. - - * DisablePointerMethods - Disables invocation of error and Stringer interface methods on types - which only accept pointer receivers from non-pointer variables. - Pointer method invocation is enabled by default. - - * DisablePointerAddresses - DisablePointerAddresses specifies whether to disable the printing of - pointer addresses. This is useful when diffing data structures in tests. - - * DisableCapacities - DisableCapacities specifies whether to disable the printing of - capacities for arrays, slices, maps and channels. This is useful when - diffing data structures in tests. - - * ContinueOnMethod - Enables recursion into types after invoking error and Stringer interface - methods. Recursion after method invocation is disabled by default. - - * SortKeys - Specifies map keys should be sorted before being printed. Use - this to have a more deterministic, diffable output. Note that - only native types (bool, int, uint, floats, uintptr and string) - and types which implement error or Stringer interfaces are - supported with other types sorted according to the - reflect.Value.String() output which guarantees display - stability. Natural map order is used by default. - - * SpewKeys - Specifies that, as a last resort attempt, map keys should be - spewed to strings and sorted by those strings. This is only - considered if SortKeys is true. - -Dump Usage - -Simply call spew.Dump with a list of variables you want to dump: - - spew.Dump(myVar1, myVar2, ...) - -You may also call spew.Fdump if you would prefer to output to an arbitrary -io.Writer. For example, to dump to standard error: - - spew.Fdump(os.Stderr, myVar1, myVar2, ...) - -A third option is to call spew.Sdump to get the formatted output as a string: - - str := spew.Sdump(myVar1, myVar2, ...) - -Sample Dump Output - -See the Dump example for details on the setup of the types and variables being -shown here. - - (main.Foo) { - unexportedField: (*main.Bar)(0xf84002e210)({ - flag: (main.Flag) flagTwo, - data: (uintptr) - }), - ExportedField: (map[interface {}]interface {}) (len=1) { - (string) (len=3) "one": (bool) true - } - } - -Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C -command as shown. - ([]uint8) (len=32 cap=32) { - 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... | - 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0| - 00000020 31 32 |12| - } - -Custom Formatter - -Spew provides a custom formatter that implements the fmt.Formatter interface -so that it integrates cleanly with standard fmt package printing functions. The -formatter is useful for inline printing of smaller data types similar to the -standard %v format specifier. - -The custom formatter only responds to the %v (most compact), %+v (adds pointer -addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb -combinations. Any other verbs such as %x and %q will be sent to the the -standard fmt package for formatting. In addition, the custom formatter ignores -the width and precision arguments (however they will still work on the format -specifiers not handled by the custom formatter). - -Custom Formatter Usage - -The simplest way to make use of the spew custom formatter is to call one of the -convenience functions such as spew.Printf, spew.Println, or spew.Printf. The -functions have syntax you are most likely already familiar with: - - spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) - spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) - spew.Println(myVar, myVar2) - spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) - spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) - -See the Index for the full list convenience functions. - -Sample Formatter Output - -Double pointer to a uint8: - %v: <**>5 - %+v: <**>(0xf8400420d0->0xf8400420c8)5 - %#v: (**uint8)5 - %#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5 - -Pointer to circular struct with a uint8 field and a pointer to itself: - %v: <*>{1 <*>} - %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)} - %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)} - %#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)} - -See the Printf example for details on the setup of variables being shown -here. - -Errors - -Since it is possible for custom Stringer/error interfaces to panic, spew -detects them and handles them internally by printing the panic information -inline with the output. Since spew is intended to provide deep pretty printing -capabilities on structures, it intentionally does not return any errors. -*/ -package spew diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go deleted file mode 100644 index f78d89fc1f6..00000000000 --- a/vendor/github.com/davecgh/go-spew/spew/dump.go +++ /dev/null @@ -1,509 +0,0 @@ -/* - * Copyright (c) 2013-2016 Dave Collins - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -package spew - -import ( - "bytes" - "encoding/hex" - "fmt" - "io" - "os" - "reflect" - "regexp" - "strconv" - "strings" -) - -var ( - // uint8Type is a reflect.Type representing a uint8. It is used to - // convert cgo types to uint8 slices for hexdumping. - uint8Type = reflect.TypeOf(uint8(0)) - - // cCharRE is a regular expression that matches a cgo char. - // It is used to detect character arrays to hexdump them. - cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`) - - // cUnsignedCharRE is a regular expression that matches a cgo unsigned - // char. It is used to detect unsigned character arrays to hexdump - // them. - cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`) - - // cUint8tCharRE is a regular expression that matches a cgo uint8_t. - // It is used to detect uint8_t arrays to hexdump them. - cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`) -) - -// dumpState contains information about the state of a dump operation. -type dumpState struct { - w io.Writer - depth int - pointers map[uintptr]int - ignoreNextType bool - ignoreNextIndent bool - cs *ConfigState -} - -// indent performs indentation according to the depth level and cs.Indent -// option. -func (d *dumpState) indent() { - if d.ignoreNextIndent { - d.ignoreNextIndent = false - return - } - d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth)) -} - -// unpackValue returns values inside of non-nil interfaces when possible. -// This is useful for data types like structs, arrays, slices, and maps which -// can contain varying types packed inside an interface. -func (d *dumpState) unpackValue(v reflect.Value) reflect.Value { - if v.Kind() == reflect.Interface && !v.IsNil() { - v = v.Elem() - } - return v -} - -// dumpPtr handles formatting of pointers by indirecting them as necessary. -func (d *dumpState) dumpPtr(v reflect.Value) { - // Remove pointers at or below the current depth from map used to detect - // circular refs. - for k, depth := range d.pointers { - if depth >= d.depth { - delete(d.pointers, k) - } - } - - // Keep list of all dereferenced pointers to show later. - pointerChain := make([]uintptr, 0) - - // Figure out how many levels of indirection there are by dereferencing - // pointers and unpacking interfaces down the chain while detecting circular - // references. - nilFound := false - cycleFound := false - indirects := 0 - ve := v - for ve.Kind() == reflect.Ptr { - if ve.IsNil() { - nilFound = true - break - } - indirects++ - addr := ve.Pointer() - pointerChain = append(pointerChain, addr) - if pd, ok := d.pointers[addr]; ok && pd < d.depth { - cycleFound = true - indirects-- - break - } - d.pointers[addr] = d.depth - - ve = ve.Elem() - if ve.Kind() == reflect.Interface { - if ve.IsNil() { - nilFound = true - break - } - ve = ve.Elem() - } - } - - // Display type information. - d.w.Write(openParenBytes) - d.w.Write(bytes.Repeat(asteriskBytes, indirects)) - d.w.Write([]byte(ve.Type().String())) - d.w.Write(closeParenBytes) - - // Display pointer information. - if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 { - d.w.Write(openParenBytes) - for i, addr := range pointerChain { - if i > 0 { - d.w.Write(pointerChainBytes) - } - printHexPtr(d.w, addr) - } - d.w.Write(closeParenBytes) - } - - // Display dereferenced value. - d.w.Write(openParenBytes) - switch { - case nilFound: - d.w.Write(nilAngleBytes) - - case cycleFound: - d.w.Write(circularBytes) - - default: - d.ignoreNextType = true - d.dump(ve) - } - d.w.Write(closeParenBytes) -} - -// dumpSlice handles formatting of arrays and slices. Byte (uint8 under -// reflection) arrays and slices are dumped in hexdump -C fashion. -func (d *dumpState) dumpSlice(v reflect.Value) { - // Determine whether this type should be hex dumped or not. Also, - // for types which should be hexdumped, try to use the underlying data - // first, then fall back to trying to convert them to a uint8 slice. - var buf []uint8 - doConvert := false - doHexDump := false - numEntries := v.Len() - if numEntries > 0 { - vt := v.Index(0).Type() - vts := vt.String() - switch { - // C types that need to be converted. - case cCharRE.MatchString(vts): - fallthrough - case cUnsignedCharRE.MatchString(vts): - fallthrough - case cUint8tCharRE.MatchString(vts): - doConvert = true - - // Try to use existing uint8 slices and fall back to converting - // and copying if that fails. - case vt.Kind() == reflect.Uint8: - // We need an addressable interface to convert the type - // to a byte slice. However, the reflect package won't - // give us an interface on certain things like - // unexported struct fields in order to enforce - // visibility rules. We use unsafe, when available, to - // bypass these restrictions since this package does not - // mutate the values. - vs := v - if !vs.CanInterface() || !vs.CanAddr() { - vs = unsafeReflectValue(vs) - } - if !UnsafeDisabled { - vs = vs.Slice(0, numEntries) - - // Use the existing uint8 slice if it can be - // type asserted. - iface := vs.Interface() - if slice, ok := iface.([]uint8); ok { - buf = slice - doHexDump = true - break - } - } - - // The underlying data needs to be converted if it can't - // be type asserted to a uint8 slice. - doConvert = true - } - - // Copy and convert the underlying type if needed. - if doConvert && vt.ConvertibleTo(uint8Type) { - // Convert and copy each element into a uint8 byte - // slice. - buf = make([]uint8, numEntries) - for i := 0; i < numEntries; i++ { - vv := v.Index(i) - buf[i] = uint8(vv.Convert(uint8Type).Uint()) - } - doHexDump = true - } - } - - // Hexdump the entire slice as needed. - if doHexDump { - indent := strings.Repeat(d.cs.Indent, d.depth) - str := indent + hex.Dump(buf) - str = strings.Replace(str, "\n", "\n"+indent, -1) - str = strings.TrimRight(str, d.cs.Indent) - d.w.Write([]byte(str)) - return - } - - // Recursively call dump for each item. - for i := 0; i < numEntries; i++ { - d.dump(d.unpackValue(v.Index(i))) - if i < (numEntries - 1) { - d.w.Write(commaNewlineBytes) - } else { - d.w.Write(newlineBytes) - } - } -} - -// dump is the main workhorse for dumping a value. It uses the passed reflect -// value to figure out what kind of object we are dealing with and formats it -// appropriately. It is a recursive function, however circular data structures -// are detected and handled properly. -func (d *dumpState) dump(v reflect.Value) { - // Handle invalid reflect values immediately. - kind := v.Kind() - if kind == reflect.Invalid { - d.w.Write(invalidAngleBytes) - return - } - - // Handle pointers specially. - if kind == reflect.Ptr { - d.indent() - d.dumpPtr(v) - return - } - - // Print type information unless already handled elsewhere. - if !d.ignoreNextType { - d.indent() - d.w.Write(openParenBytes) - d.w.Write([]byte(v.Type().String())) - d.w.Write(closeParenBytes) - d.w.Write(spaceBytes) - } - d.ignoreNextType = false - - // Display length and capacity if the built-in len and cap functions - // work with the value's kind and the len/cap itself is non-zero. - valueLen, valueCap := 0, 0 - switch v.Kind() { - case reflect.Array, reflect.Slice, reflect.Chan: - valueLen, valueCap = v.Len(), v.Cap() - case reflect.Map, reflect.String: - valueLen = v.Len() - } - if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 { - d.w.Write(openParenBytes) - if valueLen != 0 { - d.w.Write(lenEqualsBytes) - printInt(d.w, int64(valueLen), 10) - } - if !d.cs.DisableCapacities && valueCap != 0 { - if valueLen != 0 { - d.w.Write(spaceBytes) - } - d.w.Write(capEqualsBytes) - printInt(d.w, int64(valueCap), 10) - } - d.w.Write(closeParenBytes) - d.w.Write(spaceBytes) - } - - // Call Stringer/error interfaces if they exist and the handle methods flag - // is enabled - if !d.cs.DisableMethods { - if (kind != reflect.Invalid) && (kind != reflect.Interface) { - if handled := handleMethods(d.cs, d.w, v); handled { - return - } - } - } - - switch kind { - case reflect.Invalid: - // Do nothing. We should never get here since invalid has already - // been handled above. - - case reflect.Bool: - printBool(d.w, v.Bool()) - - case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: - printInt(d.w, v.Int(), 10) - - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - printUint(d.w, v.Uint(), 10) - - case reflect.Float32: - printFloat(d.w, v.Float(), 32) - - case reflect.Float64: - printFloat(d.w, v.Float(), 64) - - case reflect.Complex64: - printComplex(d.w, v.Complex(), 32) - - case reflect.Complex128: - printComplex(d.w, v.Complex(), 64) - - case reflect.Slice: - if v.IsNil() { - d.w.Write(nilAngleBytes) - break - } - fallthrough - - case reflect.Array: - d.w.Write(openBraceNewlineBytes) - d.depth++ - if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { - d.indent() - d.w.Write(maxNewlineBytes) - } else { - d.dumpSlice(v) - } - d.depth-- - d.indent() - d.w.Write(closeBraceBytes) - - case reflect.String: - d.w.Write([]byte(strconv.Quote(v.String()))) - - case reflect.Interface: - // The only time we should get here is for nil interfaces due to - // unpackValue calls. - if v.IsNil() { - d.w.Write(nilAngleBytes) - } - - case reflect.Ptr: - // Do nothing. We should never get here since pointers have already - // been handled above. - - case reflect.Map: - // nil maps should be indicated as different than empty maps - if v.IsNil() { - d.w.Write(nilAngleBytes) - break - } - - d.w.Write(openBraceNewlineBytes) - d.depth++ - if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { - d.indent() - d.w.Write(maxNewlineBytes) - } else { - numEntries := v.Len() - keys := v.MapKeys() - if d.cs.SortKeys { - sortValues(keys, d.cs) - } - for i, key := range keys { - d.dump(d.unpackValue(key)) - d.w.Write(colonSpaceBytes) - d.ignoreNextIndent = true - d.dump(d.unpackValue(v.MapIndex(key))) - if i < (numEntries - 1) { - d.w.Write(commaNewlineBytes) - } else { - d.w.Write(newlineBytes) - } - } - } - d.depth-- - d.indent() - d.w.Write(closeBraceBytes) - - case reflect.Struct: - d.w.Write(openBraceNewlineBytes) - d.depth++ - if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { - d.indent() - d.w.Write(maxNewlineBytes) - } else { - vt := v.Type() - numFields := v.NumField() - for i := 0; i < numFields; i++ { - d.indent() - vtf := vt.Field(i) - d.w.Write([]byte(vtf.Name)) - d.w.Write(colonSpaceBytes) - d.ignoreNextIndent = true - d.dump(d.unpackValue(v.Field(i))) - if i < (numFields - 1) { - d.w.Write(commaNewlineBytes) - } else { - d.w.Write(newlineBytes) - } - } - } - d.depth-- - d.indent() - d.w.Write(closeBraceBytes) - - case reflect.Uintptr: - printHexPtr(d.w, uintptr(v.Uint())) - - case reflect.UnsafePointer, reflect.Chan, reflect.Func: - printHexPtr(d.w, v.Pointer()) - - // There were not any other types at the time this code was written, but - // fall back to letting the default fmt package handle it in case any new - // types are added. - default: - if v.CanInterface() { - fmt.Fprintf(d.w, "%v", v.Interface()) - } else { - fmt.Fprintf(d.w, "%v", v.String()) - } - } -} - -// fdump is a helper function to consolidate the logic from the various public -// methods which take varying writers and config states. -func fdump(cs *ConfigState, w io.Writer, a ...interface{}) { - for _, arg := range a { - if arg == nil { - w.Write(interfaceBytes) - w.Write(spaceBytes) - w.Write(nilAngleBytes) - w.Write(newlineBytes) - continue - } - - d := dumpState{w: w, cs: cs} - d.pointers = make(map[uintptr]int) - d.dump(reflect.ValueOf(arg)) - d.w.Write(newlineBytes) - } -} - -// Fdump formats and displays the passed arguments to io.Writer w. It formats -// exactly the same as Dump. -func Fdump(w io.Writer, a ...interface{}) { - fdump(&Config, w, a...) -} - -// Sdump returns a string with the passed arguments formatted exactly the same -// as Dump. -func Sdump(a ...interface{}) string { - var buf bytes.Buffer - fdump(&Config, &buf, a...) - return buf.String() -} - -/* -Dump displays the passed parameters to standard out with newlines, customizable -indentation, and additional debug information such as complete types and all -pointer addresses used to indirect to the final value. It provides the -following features over the built-in printing facilities provided by the fmt -package: - - * Pointers are dereferenced and followed - * Circular data structures are detected and handled properly - * Custom Stringer/error interfaces are optionally invoked, including - on unexported types - * Custom types which only implement the Stringer/error interfaces via - a pointer receiver are optionally invoked when passing non-pointer - variables - * Byte arrays and slices are dumped like the hexdump -C command which - includes offsets, byte values in hex, and ASCII output - -The configuration options are controlled by an exported package global, -spew.Config. See ConfigState for options documentation. - -See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to -get the formatted result as a string. -*/ -func Dump(a ...interface{}) { - fdump(&Config, os.Stdout, a...) -} diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go deleted file mode 100644 index b04edb7d7ac..00000000000 --- a/vendor/github.com/davecgh/go-spew/spew/format.go +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright (c) 2013-2016 Dave Collins - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -package spew - -import ( - "bytes" - "fmt" - "reflect" - "strconv" - "strings" -) - -// supportedFlags is a list of all the character flags supported by fmt package. -const supportedFlags = "0-+# " - -// formatState implements the fmt.Formatter interface and contains information -// about the state of a formatting operation. The NewFormatter function can -// be used to get a new Formatter which can be used directly as arguments -// in standard fmt package printing calls. -type formatState struct { - value interface{} - fs fmt.State - depth int - pointers map[uintptr]int - ignoreNextType bool - cs *ConfigState -} - -// buildDefaultFormat recreates the original format string without precision -// and width information to pass in to fmt.Sprintf in the case of an -// unrecognized type. Unless new types are added to the language, this -// function won't ever be called. -func (f *formatState) buildDefaultFormat() (format string) { - buf := bytes.NewBuffer(percentBytes) - - for _, flag := range supportedFlags { - if f.fs.Flag(int(flag)) { - buf.WriteRune(flag) - } - } - - buf.WriteRune('v') - - format = buf.String() - return format -} - -// constructOrigFormat recreates the original format string including precision -// and width information to pass along to the standard fmt package. This allows -// automatic deferral of all format strings this package doesn't support. -func (f *formatState) constructOrigFormat(verb rune) (format string) { - buf := bytes.NewBuffer(percentBytes) - - for _, flag := range supportedFlags { - if f.fs.Flag(int(flag)) { - buf.WriteRune(flag) - } - } - - if width, ok := f.fs.Width(); ok { - buf.WriteString(strconv.Itoa(width)) - } - - if precision, ok := f.fs.Precision(); ok { - buf.Write(precisionBytes) - buf.WriteString(strconv.Itoa(precision)) - } - - buf.WriteRune(verb) - - format = buf.String() - return format -} - -// unpackValue returns values inside of non-nil interfaces when possible and -// ensures that types for values which have been unpacked from an interface -// are displayed when the show types flag is also set. -// This is useful for data types like structs, arrays, slices, and maps which -// can contain varying types packed inside an interface. -func (f *formatState) unpackValue(v reflect.Value) reflect.Value { - if v.Kind() == reflect.Interface { - f.ignoreNextType = false - if !v.IsNil() { - v = v.Elem() - } - } - return v -} - -// formatPtr handles formatting of pointers by indirecting them as necessary. -func (f *formatState) formatPtr(v reflect.Value) { - // Display nil if top level pointer is nil. - showTypes := f.fs.Flag('#') - if v.IsNil() && (!showTypes || f.ignoreNextType) { - f.fs.Write(nilAngleBytes) - return - } - - // Remove pointers at or below the current depth from map used to detect - // circular refs. - for k, depth := range f.pointers { - if depth >= f.depth { - delete(f.pointers, k) - } - } - - // Keep list of all dereferenced pointers to possibly show later. - pointerChain := make([]uintptr, 0) - - // Figure out how many levels of indirection there are by derferencing - // pointers and unpacking interfaces down the chain while detecting circular - // references. - nilFound := false - cycleFound := false - indirects := 0 - ve := v - for ve.Kind() == reflect.Ptr { - if ve.IsNil() { - nilFound = true - break - } - indirects++ - addr := ve.Pointer() - pointerChain = append(pointerChain, addr) - if pd, ok := f.pointers[addr]; ok && pd < f.depth { - cycleFound = true - indirects-- - break - } - f.pointers[addr] = f.depth - - ve = ve.Elem() - if ve.Kind() == reflect.Interface { - if ve.IsNil() { - nilFound = true - break - } - ve = ve.Elem() - } - } - - // Display type or indirection level depending on flags. - if showTypes && !f.ignoreNextType { - f.fs.Write(openParenBytes) - f.fs.Write(bytes.Repeat(asteriskBytes, indirects)) - f.fs.Write([]byte(ve.Type().String())) - f.fs.Write(closeParenBytes) - } else { - if nilFound || cycleFound { - indirects += strings.Count(ve.Type().String(), "*") - } - f.fs.Write(openAngleBytes) - f.fs.Write([]byte(strings.Repeat("*", indirects))) - f.fs.Write(closeAngleBytes) - } - - // Display pointer information depending on flags. - if f.fs.Flag('+') && (len(pointerChain) > 0) { - f.fs.Write(openParenBytes) - for i, addr := range pointerChain { - if i > 0 { - f.fs.Write(pointerChainBytes) - } - printHexPtr(f.fs, addr) - } - f.fs.Write(closeParenBytes) - } - - // Display dereferenced value. - switch { - case nilFound: - f.fs.Write(nilAngleBytes) - - case cycleFound: - f.fs.Write(circularShortBytes) - - default: - f.ignoreNextType = true - f.format(ve) - } -} - -// format is the main workhorse for providing the Formatter interface. It -// uses the passed reflect value to figure out what kind of object we are -// dealing with and formats it appropriately. It is a recursive function, -// however circular data structures are detected and handled properly. -func (f *formatState) format(v reflect.Value) { - // Handle invalid reflect values immediately. - kind := v.Kind() - if kind == reflect.Invalid { - f.fs.Write(invalidAngleBytes) - return - } - - // Handle pointers specially. - if kind == reflect.Ptr { - f.formatPtr(v) - return - } - - // Print type information unless already handled elsewhere. - if !f.ignoreNextType && f.fs.Flag('#') { - f.fs.Write(openParenBytes) - f.fs.Write([]byte(v.Type().String())) - f.fs.Write(closeParenBytes) - } - f.ignoreNextType = false - - // Call Stringer/error interfaces if they exist and the handle methods - // flag is enabled. - if !f.cs.DisableMethods { - if (kind != reflect.Invalid) && (kind != reflect.Interface) { - if handled := handleMethods(f.cs, f.fs, v); handled { - return - } - } - } - - switch kind { - case reflect.Invalid: - // Do nothing. We should never get here since invalid has already - // been handled above. - - case reflect.Bool: - printBool(f.fs, v.Bool()) - - case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: - printInt(f.fs, v.Int(), 10) - - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - printUint(f.fs, v.Uint(), 10) - - case reflect.Float32: - printFloat(f.fs, v.Float(), 32) - - case reflect.Float64: - printFloat(f.fs, v.Float(), 64) - - case reflect.Complex64: - printComplex(f.fs, v.Complex(), 32) - - case reflect.Complex128: - printComplex(f.fs, v.Complex(), 64) - - case reflect.Slice: - if v.IsNil() { - f.fs.Write(nilAngleBytes) - break - } - fallthrough - - case reflect.Array: - f.fs.Write(openBracketBytes) - f.depth++ - if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { - f.fs.Write(maxShortBytes) - } else { - numEntries := v.Len() - for i := 0; i < numEntries; i++ { - if i > 0 { - f.fs.Write(spaceBytes) - } - f.ignoreNextType = true - f.format(f.unpackValue(v.Index(i))) - } - } - f.depth-- - f.fs.Write(closeBracketBytes) - - case reflect.String: - f.fs.Write([]byte(v.String())) - - case reflect.Interface: - // The only time we should get here is for nil interfaces due to - // unpackValue calls. - if v.IsNil() { - f.fs.Write(nilAngleBytes) - } - - case reflect.Ptr: - // Do nothing. We should never get here since pointers have already - // been handled above. - - case reflect.Map: - // nil maps should be indicated as different than empty maps - if v.IsNil() { - f.fs.Write(nilAngleBytes) - break - } - - f.fs.Write(openMapBytes) - f.depth++ - if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { - f.fs.Write(maxShortBytes) - } else { - keys := v.MapKeys() - if f.cs.SortKeys { - sortValues(keys, f.cs) - } - for i, key := range keys { - if i > 0 { - f.fs.Write(spaceBytes) - } - f.ignoreNextType = true - f.format(f.unpackValue(key)) - f.fs.Write(colonBytes) - f.ignoreNextType = true - f.format(f.unpackValue(v.MapIndex(key))) - } - } - f.depth-- - f.fs.Write(closeMapBytes) - - case reflect.Struct: - numFields := v.NumField() - f.fs.Write(openBraceBytes) - f.depth++ - if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { - f.fs.Write(maxShortBytes) - } else { - vt := v.Type() - for i := 0; i < numFields; i++ { - if i > 0 { - f.fs.Write(spaceBytes) - } - vtf := vt.Field(i) - if f.fs.Flag('+') || f.fs.Flag('#') { - f.fs.Write([]byte(vtf.Name)) - f.fs.Write(colonBytes) - } - f.format(f.unpackValue(v.Field(i))) - } - } - f.depth-- - f.fs.Write(closeBraceBytes) - - case reflect.Uintptr: - printHexPtr(f.fs, uintptr(v.Uint())) - - case reflect.UnsafePointer, reflect.Chan, reflect.Func: - printHexPtr(f.fs, v.Pointer()) - - // There were not any other types at the time this code was written, but - // fall back to letting the default fmt package handle it if any get added. - default: - format := f.buildDefaultFormat() - if v.CanInterface() { - fmt.Fprintf(f.fs, format, v.Interface()) - } else { - fmt.Fprintf(f.fs, format, v.String()) - } - } -} - -// Format satisfies the fmt.Formatter interface. See NewFormatter for usage -// details. -func (f *formatState) Format(fs fmt.State, verb rune) { - f.fs = fs - - // Use standard formatting for verbs that are not v. - if verb != 'v' { - format := f.constructOrigFormat(verb) - fmt.Fprintf(fs, format, f.value) - return - } - - if f.value == nil { - if fs.Flag('#') { - fs.Write(interfaceBytes) - } - fs.Write(nilAngleBytes) - return - } - - f.format(reflect.ValueOf(f.value)) -} - -// newFormatter is a helper function to consolidate the logic from the various -// public methods which take varying config states. -func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter { - fs := &formatState{value: v, cs: cs} - fs.pointers = make(map[uintptr]int) - return fs -} - -/* -NewFormatter returns a custom formatter that satisfies the fmt.Formatter -interface. As a result, it integrates cleanly with standard fmt package -printing functions. The formatter is useful for inline printing of smaller data -types similar to the standard %v format specifier. - -The custom formatter only responds to the %v (most compact), %+v (adds pointer -addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb -combinations. Any other verbs such as %x and %q will be sent to the the -standard fmt package for formatting. In addition, the custom formatter ignores -the width and precision arguments (however they will still work on the format -specifiers not handled by the custom formatter). - -Typically this function shouldn't be called directly. It is much easier to make -use of the custom formatter by calling one of the convenience functions such as -Printf, Println, or Fprintf. -*/ -func NewFormatter(v interface{}) fmt.Formatter { - return newFormatter(&Config, v) -} diff --git a/vendor/github.com/davecgh/go-spew/spew/spew.go b/vendor/github.com/davecgh/go-spew/spew/spew.go deleted file mode 100644 index 32c0e338825..00000000000 --- a/vendor/github.com/davecgh/go-spew/spew/spew.go +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2013-2016 Dave Collins - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -package spew - -import ( - "fmt" - "io" -) - -// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were -// passed with a default Formatter interface returned by NewFormatter. It -// returns the formatted string as a value that satisfies error. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b)) -func Errorf(format string, a ...interface{}) (err error) { - return fmt.Errorf(format, convertArgs(a)...) -} - -// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were -// passed with a default Formatter interface returned by NewFormatter. It -// returns the number of bytes written and any write error encountered. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b)) -func Fprint(w io.Writer, a ...interface{}) (n int, err error) { - return fmt.Fprint(w, convertArgs(a)...) -} - -// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were -// passed with a default Formatter interface returned by NewFormatter. It -// returns the number of bytes written and any write error encountered. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b)) -func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { - return fmt.Fprintf(w, format, convertArgs(a)...) -} - -// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it -// passed with a default Formatter interface returned by NewFormatter. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b)) -func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { - return fmt.Fprintln(w, convertArgs(a)...) -} - -// Print is a wrapper for fmt.Print that treats each argument as if it were -// passed with a default Formatter interface returned by NewFormatter. It -// returns the number of bytes written and any write error encountered. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b)) -func Print(a ...interface{}) (n int, err error) { - return fmt.Print(convertArgs(a)...) -} - -// Printf is a wrapper for fmt.Printf that treats each argument as if it were -// passed with a default Formatter interface returned by NewFormatter. It -// returns the number of bytes written and any write error encountered. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b)) -func Printf(format string, a ...interface{}) (n int, err error) { - return fmt.Printf(format, convertArgs(a)...) -} - -// Println is a wrapper for fmt.Println that treats each argument as if it were -// passed with a default Formatter interface returned by NewFormatter. It -// returns the number of bytes written and any write error encountered. See -// NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b)) -func Println(a ...interface{}) (n int, err error) { - return fmt.Println(convertArgs(a)...) -} - -// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were -// passed with a default Formatter interface returned by NewFormatter. It -// returns the resulting string. See NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b)) -func Sprint(a ...interface{}) string { - return fmt.Sprint(convertArgs(a)...) -} - -// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were -// passed with a default Formatter interface returned by NewFormatter. It -// returns the resulting string. See NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b)) -func Sprintf(format string, a ...interface{}) string { - return fmt.Sprintf(format, convertArgs(a)...) -} - -// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it -// were passed with a default Formatter interface returned by NewFormatter. It -// returns the resulting string. See NewFormatter for formatting details. -// -// This function is shorthand for the following syntax: -// -// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b)) -func Sprintln(a ...interface{}) string { - return fmt.Sprintln(convertArgs(a)...) -} - -// convertArgs accepts a slice of arguments and returns a slice of the same -// length with each argument converted to a default spew Formatter interface. -func convertArgs(args []interface{}) (formatters []interface{}) { - formatters = make([]interface{}, len(args)) - for index, arg := range args { - formatters[index] = NewFormatter(arg) - } - return formatters -} diff --git a/vendor/github.com/emirpasic/gods/LICENSE b/vendor/github.com/emirpasic/gods/LICENSE deleted file mode 100644 index e5e449b6eca..00000000000 --- a/vendor/github.com/emirpasic/gods/LICENSE +++ /dev/null @@ -1,41 +0,0 @@ -Copyright (c) 2015, Emir Pasic -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -------------------------------------------------------------------------------- - -AVL Tree: - -Copyright (c) 2017 Benjamin Scher Purcell - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/emirpasic/gods/containers/containers.go b/vendor/github.com/emirpasic/gods/containers/containers.go deleted file mode 100644 index a512a3cbaa7..00000000000 --- a/vendor/github.com/emirpasic/gods/containers/containers.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package containers provides core interfaces and functions for data structures. -// -// Container is the base interface for all data structures to implement. -// -// Iterators provide stateful iterators. -// -// Enumerable provides Ruby inspired (each, select, map, find, any?, etc.) container functions. -// -// Serialization provides serializers (marshalers) and deserializers (unmarshalers). -package containers - -import "github.com/emirpasic/gods/utils" - -// Container is base interface that all data structures implement. -type Container interface { - Empty() bool - Size() int - Clear() - Values() []interface{} - String() string -} - -// GetSortedValues returns sorted container's elements with respect to the passed comparator. -// Does not affect the ordering of elements within the container. -func GetSortedValues(container Container, comparator utils.Comparator) []interface{} { - values := container.Values() - if len(values) < 2 { - return values - } - utils.Sort(values, comparator) - return values -} diff --git a/vendor/github.com/emirpasic/gods/containers/enumerable.go b/vendor/github.com/emirpasic/gods/containers/enumerable.go deleted file mode 100644 index 70660054ae5..00000000000 --- a/vendor/github.com/emirpasic/gods/containers/enumerable.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package containers - -// EnumerableWithIndex provides functions for ordered containers whose values can be fetched by an index. -type EnumerableWithIndex interface { - // Each calls the given function once for each element, passing that element's index and value. - Each(func(index int, value interface{})) - - // Map invokes the given function once for each element and returns a - // container containing the values returned by the given function. - // Map(func(index int, value interface{}) interface{}) Container - - // Select returns a new container containing all elements for which the given function returns a true value. - // Select(func(index int, value interface{}) bool) Container - - // Any passes each element of the container to the given function and - // returns true if the function ever returns true for any element. - Any(func(index int, value interface{}) bool) bool - - // All passes each element of the container to the given function and - // returns true if the function returns true for all elements. - All(func(index int, value interface{}) bool) bool - - // Find passes each element of the container to the given function and returns - // the first (index,value) for which the function is true or -1,nil otherwise - // if no element matches the criteria. - Find(func(index int, value interface{}) bool) (int, interface{}) -} - -// EnumerableWithKey provides functions for ordered containers whose values whose elements are key/value pairs. -type EnumerableWithKey interface { - // Each calls the given function once for each element, passing that element's key and value. - Each(func(key interface{}, value interface{})) - - // Map invokes the given function once for each element and returns a container - // containing the values returned by the given function as key/value pairs. - // Map(func(key interface{}, value interface{}) (interface{}, interface{})) Container - - // Select returns a new container containing all elements for which the given function returns a true value. - // Select(func(key interface{}, value interface{}) bool) Container - - // Any passes each element of the container to the given function and - // returns true if the function ever returns true for any element. - Any(func(key interface{}, value interface{}) bool) bool - - // All passes each element of the container to the given function and - // returns true if the function returns true for all elements. - All(func(key interface{}, value interface{}) bool) bool - - // Find passes each element of the container to the given function and returns - // the first (key,value) for which the function is true or nil,nil otherwise if no element - // matches the criteria. - Find(func(key interface{}, value interface{}) bool) (interface{}, interface{}) -} diff --git a/vendor/github.com/emirpasic/gods/containers/iterator.go b/vendor/github.com/emirpasic/gods/containers/iterator.go deleted file mode 100644 index 73994ec82a7..00000000000 --- a/vendor/github.com/emirpasic/gods/containers/iterator.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package containers - -// IteratorWithIndex is stateful iterator for ordered containers whose values can be fetched by an index. -type IteratorWithIndex interface { - // Next moves the iterator to the next element and returns true if there was a next element in the container. - // If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). - // If Next() was called for the first time, then it will point the iterator to the first element if it exists. - // Modifies the state of the iterator. - Next() bool - - // Value returns the current element's value. - // Does not modify the state of the iterator. - Value() interface{} - - // Index returns the current element's index. - // Does not modify the state of the iterator. - Index() int - - // Begin resets the iterator to its initial state (one-before-first) - // Call Next() to fetch the first element if any. - Begin() - - // First moves the iterator to the first element and returns true if there was a first element in the container. - // If First() returns true, then first element's index and value can be retrieved by Index() and Value(). - // Modifies the state of the iterator. - First() bool - - // NextTo moves the iterator to the next element from current position that satisfies the condition given by the - // passed function, and returns true if there was a next element in the container. - // If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). - // Modifies the state of the iterator. - NextTo(func(index int, value interface{}) bool) bool -} - -// IteratorWithKey is a stateful iterator for ordered containers whose elements are key value pairs. -type IteratorWithKey interface { - // Next moves the iterator to the next element and returns true if there was a next element in the container. - // If Next() returns true, then next element's key and value can be retrieved by Key() and Value(). - // If Next() was called for the first time, then it will point the iterator to the first element if it exists. - // Modifies the state of the iterator. - Next() bool - - // Value returns the current element's value. - // Does not modify the state of the iterator. - Value() interface{} - - // Key returns the current element's key. - // Does not modify the state of the iterator. - Key() interface{} - - // Begin resets the iterator to its initial state (one-before-first) - // Call Next() to fetch the first element if any. - Begin() - - // First moves the iterator to the first element and returns true if there was a first element in the container. - // If First() returns true, then first element's key and value can be retrieved by Key() and Value(). - // Modifies the state of the iterator. - First() bool - - // NextTo moves the iterator to the next element from current position that satisfies the condition given by the - // passed function, and returns true if there was a next element in the container. - // If NextTo() returns true, then next element's key and value can be retrieved by Key() and Value(). - // Modifies the state of the iterator. - NextTo(func(key interface{}, value interface{}) bool) bool -} - -// ReverseIteratorWithIndex is stateful iterator for ordered containers whose values can be fetched by an index. -// -// Essentially it is the same as IteratorWithIndex, but provides additional: -// -// Prev() function to enable traversal in reverse -// -// Last() function to move the iterator to the last element. -// -// End() function to move the iterator past the last element (one-past-the-end). -type ReverseIteratorWithIndex interface { - // Prev moves the iterator to the previous element and returns true if there was a previous element in the container. - // If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). - // Modifies the state of the iterator. - Prev() bool - - // End moves the iterator past the last element (one-past-the-end). - // Call Prev() to fetch the last element if any. - End() - - // Last moves the iterator to the last element and returns true if there was a last element in the container. - // If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). - // Modifies the state of the iterator. - Last() bool - - // PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the - // passed function, and returns true if there was a next element in the container. - // If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). - // Modifies the state of the iterator. - PrevTo(func(index int, value interface{}) bool) bool - - IteratorWithIndex -} - -// ReverseIteratorWithKey is a stateful iterator for ordered containers whose elements are key value pairs. -// -// Essentially it is the same as IteratorWithKey, but provides additional: -// -// Prev() function to enable traversal in reverse -// -// Last() function to move the iterator to the last element. -type ReverseIteratorWithKey interface { - // Prev moves the iterator to the previous element and returns true if there was a previous element in the container. - // If Prev() returns true, then previous element's key and value can be retrieved by Key() and Value(). - // Modifies the state of the iterator. - Prev() bool - - // End moves the iterator past the last element (one-past-the-end). - // Call Prev() to fetch the last element if any. - End() - - // Last moves the iterator to the last element and returns true if there was a last element in the container. - // If Last() returns true, then last element's key and value can be retrieved by Key() and Value(). - // Modifies the state of the iterator. - Last() bool - - // PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the - // passed function, and returns true if there was a next element in the container. - // If PrevTo() returns true, then next element's key and value can be retrieved by Key() and Value(). - // Modifies the state of the iterator. - PrevTo(func(key interface{}, value interface{}) bool) bool - - IteratorWithKey -} diff --git a/vendor/github.com/emirpasic/gods/containers/serialization.go b/vendor/github.com/emirpasic/gods/containers/serialization.go deleted file mode 100644 index fd9cbe23a3f..00000000000 --- a/vendor/github.com/emirpasic/gods/containers/serialization.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package containers - -// JSONSerializer provides JSON serialization -type JSONSerializer interface { - // ToJSON outputs the JSON representation of containers's elements. - ToJSON() ([]byte, error) - // MarshalJSON @implements json.Marshaler - MarshalJSON() ([]byte, error) -} - -// JSONDeserializer provides JSON deserialization -type JSONDeserializer interface { - // FromJSON populates containers's elements from the input JSON representation. - FromJSON([]byte) error - // UnmarshalJSON @implements json.Unmarshaler - UnmarshalJSON([]byte) error -} diff --git a/vendor/github.com/emirpasic/gods/lists/arraylist/arraylist.go b/vendor/github.com/emirpasic/gods/lists/arraylist/arraylist.go deleted file mode 100644 index 60ce4583203..00000000000 --- a/vendor/github.com/emirpasic/gods/lists/arraylist/arraylist.go +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package arraylist implements the array list. -// -// Structure is not thread safe. -// -// Reference: https://en.wikipedia.org/wiki/List_%28abstract_data_type%29 -package arraylist - -import ( - "fmt" - "strings" - - "github.com/emirpasic/gods/lists" - "github.com/emirpasic/gods/utils" -) - -// Assert List implementation -var _ lists.List = (*List)(nil) - -// List holds the elements in a slice -type List struct { - elements []interface{} - size int -} - -const ( - growthFactor = float32(2.0) // growth by 100% - shrinkFactor = float32(0.25) // shrink when size is 25% of capacity (0 means never shrink) -) - -// New instantiates a new list and adds the passed values, if any, to the list -func New(values ...interface{}) *List { - list := &List{} - if len(values) > 0 { - list.Add(values...) - } - return list -} - -// Add appends a value at the end of the list -func (list *List) Add(values ...interface{}) { - list.growBy(len(values)) - for _, value := range values { - list.elements[list.size] = value - list.size++ - } -} - -// Get returns the element at index. -// Second return parameter is true if index is within bounds of the array and array is not empty, otherwise false. -func (list *List) Get(index int) (interface{}, bool) { - - if !list.withinRange(index) { - return nil, false - } - - return list.elements[index], true -} - -// Remove removes the element at the given index from the list. -func (list *List) Remove(index int) { - - if !list.withinRange(index) { - return - } - - list.elements[index] = nil // cleanup reference - copy(list.elements[index:], list.elements[index+1:list.size]) // shift to the left by one (slow operation, need ways to optimize this) - list.size-- - - list.shrink() -} - -// Contains checks if elements (one or more) are present in the set. -// All elements have to be present in the set for the method to return true. -// Performance time complexity of n^2. -// Returns true if no arguments are passed at all, i.e. set is always super-set of empty set. -func (list *List) Contains(values ...interface{}) bool { - - for _, searchValue := range values { - found := false - for index := 0; index < list.size; index++ { - if list.elements[index] == searchValue { - found = true - break - } - } - if !found { - return false - } - } - return true -} - -// Values returns all elements in the list. -func (list *List) Values() []interface{} { - newElements := make([]interface{}, list.size, list.size) - copy(newElements, list.elements[:list.size]) - return newElements -} - -//IndexOf returns index of provided element -func (list *List) IndexOf(value interface{}) int { - if list.size == 0 { - return -1 - } - for index, element := range list.elements { - if element == value { - return index - } - } - return -1 -} - -// Empty returns true if list does not contain any elements. -func (list *List) Empty() bool { - return list.size == 0 -} - -// Size returns number of elements within the list. -func (list *List) Size() int { - return list.size -} - -// Clear removes all elements from the list. -func (list *List) Clear() { - list.size = 0 - list.elements = []interface{}{} -} - -// Sort sorts values (in-place) using. -func (list *List) Sort(comparator utils.Comparator) { - if len(list.elements) < 2 { - return - } - utils.Sort(list.elements[:list.size], comparator) -} - -// Swap swaps the two values at the specified positions. -func (list *List) Swap(i, j int) { - if list.withinRange(i) && list.withinRange(j) { - list.elements[i], list.elements[j] = list.elements[j], list.elements[i] - } -} - -// Insert inserts values at specified index position shifting the value at that position (if any) and any subsequent elements to the right. -// Does not do anything if position is negative or bigger than list's size -// Note: position equal to list's size is valid, i.e. append. -func (list *List) Insert(index int, values ...interface{}) { - - if !list.withinRange(index) { - // Append - if index == list.size { - list.Add(values...) - } - return - } - - l := len(values) - list.growBy(l) - list.size += l - copy(list.elements[index+l:], list.elements[index:list.size-l]) - copy(list.elements[index:], values) -} - -// Set the value at specified index -// Does not do anything if position is negative or bigger than list's size -// Note: position equal to list's size is valid, i.e. append. -func (list *List) Set(index int, value interface{}) { - - if !list.withinRange(index) { - // Append - if index == list.size { - list.Add(value) - } - return - } - - list.elements[index] = value -} - -// String returns a string representation of container -func (list *List) String() string { - str := "ArrayList\n" - values := []string{} - for _, value := range list.elements[:list.size] { - values = append(values, fmt.Sprintf("%v", value)) - } - str += strings.Join(values, ", ") - return str -} - -// Check that the index is within bounds of the list -func (list *List) withinRange(index int) bool { - return index >= 0 && index < list.size -} - -func (list *List) resize(cap int) { - newElements := make([]interface{}, cap, cap) - copy(newElements, list.elements) - list.elements = newElements -} - -// Expand the array if necessary, i.e. capacity will be reached if we add n elements -func (list *List) growBy(n int) { - // When capacity is reached, grow by a factor of growthFactor and add number of elements - currentCapacity := cap(list.elements) - if list.size+n >= currentCapacity { - newCapacity := int(growthFactor * float32(currentCapacity+n)) - list.resize(newCapacity) - } -} - -// Shrink the array if necessary, i.e. when size is shrinkFactor percent of current capacity -func (list *List) shrink() { - if shrinkFactor == 0.0 { - return - } - // Shrink when size is at shrinkFactor * capacity - currentCapacity := cap(list.elements) - if list.size <= int(float32(currentCapacity)*shrinkFactor) { - list.resize(list.size) - } -} diff --git a/vendor/github.com/emirpasic/gods/lists/arraylist/enumerable.go b/vendor/github.com/emirpasic/gods/lists/arraylist/enumerable.go deleted file mode 100644 index 8bd60b0a5cc..00000000000 --- a/vendor/github.com/emirpasic/gods/lists/arraylist/enumerable.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package arraylist - -import "github.com/emirpasic/gods/containers" - -// Assert Enumerable implementation -var _ containers.EnumerableWithIndex = (*List)(nil) - -// Each calls the given function once for each element, passing that element's index and value. -func (list *List) Each(f func(index int, value interface{})) { - iterator := list.Iterator() - for iterator.Next() { - f(iterator.Index(), iterator.Value()) - } -} - -// Map invokes the given function once for each element and returns a -// container containing the values returned by the given function. -func (list *List) Map(f func(index int, value interface{}) interface{}) *List { - newList := &List{} - iterator := list.Iterator() - for iterator.Next() { - newList.Add(f(iterator.Index(), iterator.Value())) - } - return newList -} - -// Select returns a new container containing all elements for which the given function returns a true value. -func (list *List) Select(f func(index int, value interface{}) bool) *List { - newList := &List{} - iterator := list.Iterator() - for iterator.Next() { - if f(iterator.Index(), iterator.Value()) { - newList.Add(iterator.Value()) - } - } - return newList -} - -// Any passes each element of the collection to the given function and -// returns true if the function ever returns true for any element. -func (list *List) Any(f func(index int, value interface{}) bool) bool { - iterator := list.Iterator() - for iterator.Next() { - if f(iterator.Index(), iterator.Value()) { - return true - } - } - return false -} - -// All passes each element of the collection to the given function and -// returns true if the function returns true for all elements. -func (list *List) All(f func(index int, value interface{}) bool) bool { - iterator := list.Iterator() - for iterator.Next() { - if !f(iterator.Index(), iterator.Value()) { - return false - } - } - return true -} - -// Find passes each element of the container to the given function and returns -// the first (index,value) for which the function is true or -1,nil otherwise -// if no element matches the criteria. -func (list *List) Find(f func(index int, value interface{}) bool) (int, interface{}) { - iterator := list.Iterator() - for iterator.Next() { - if f(iterator.Index(), iterator.Value()) { - return iterator.Index(), iterator.Value() - } - } - return -1, nil -} diff --git a/vendor/github.com/emirpasic/gods/lists/arraylist/iterator.go b/vendor/github.com/emirpasic/gods/lists/arraylist/iterator.go deleted file mode 100644 index f9efe20c541..00000000000 --- a/vendor/github.com/emirpasic/gods/lists/arraylist/iterator.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package arraylist - -import "github.com/emirpasic/gods/containers" - -// Assert Iterator implementation -var _ containers.ReverseIteratorWithIndex = (*Iterator)(nil) - -// Iterator holding the iterator's state -type Iterator struct { - list *List - index int -} - -// Iterator returns a stateful iterator whose values can be fetched by an index. -func (list *List) Iterator() Iterator { - return Iterator{list: list, index: -1} -} - -// Next moves the iterator to the next element and returns true if there was a next element in the container. -// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). -// If Next() was called for the first time, then it will point the iterator to the first element if it exists. -// Modifies the state of the iterator. -func (iterator *Iterator) Next() bool { - if iterator.index < iterator.list.size { - iterator.index++ - } - return iterator.list.withinRange(iterator.index) -} - -// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. -// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). -// Modifies the state of the iterator. -func (iterator *Iterator) Prev() bool { - if iterator.index >= 0 { - iterator.index-- - } - return iterator.list.withinRange(iterator.index) -} - -// Value returns the current element's value. -// Does not modify the state of the iterator. -func (iterator *Iterator) Value() interface{} { - return iterator.list.elements[iterator.index] -} - -// Index returns the current element's index. -// Does not modify the state of the iterator. -func (iterator *Iterator) Index() int { - return iterator.index -} - -// Begin resets the iterator to its initial state (one-before-first) -// Call Next() to fetch the first element if any. -func (iterator *Iterator) Begin() { - iterator.index = -1 -} - -// End moves the iterator past the last element (one-past-the-end). -// Call Prev() to fetch the last element if any. -func (iterator *Iterator) End() { - iterator.index = iterator.list.size -} - -// First moves the iterator to the first element and returns true if there was a first element in the container. -// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). -// Modifies the state of the iterator. -func (iterator *Iterator) First() bool { - iterator.Begin() - return iterator.Next() -} - -// Last moves the iterator to the last element and returns true if there was a last element in the container. -// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). -// Modifies the state of the iterator. -func (iterator *Iterator) Last() bool { - iterator.End() - return iterator.Prev() -} - -// NextTo moves the iterator to the next element from current position that satisfies the condition given by the -// passed function, and returns true if there was a next element in the container. -// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). -// Modifies the state of the iterator. -func (iterator *Iterator) NextTo(f func(index int, value interface{}) bool) bool { - for iterator.Next() { - index, value := iterator.Index(), iterator.Value() - if f(index, value) { - return true - } - } - return false -} - -// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the -// passed function, and returns true if there was a next element in the container. -// If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). -// Modifies the state of the iterator. -func (iterator *Iterator) PrevTo(f func(index int, value interface{}) bool) bool { - for iterator.Prev() { - index, value := iterator.Index(), iterator.Value() - if f(index, value) { - return true - } - } - return false -} diff --git a/vendor/github.com/emirpasic/gods/lists/arraylist/serialization.go b/vendor/github.com/emirpasic/gods/lists/arraylist/serialization.go deleted file mode 100644 index 5e86fe96f33..00000000000 --- a/vendor/github.com/emirpasic/gods/lists/arraylist/serialization.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package arraylist - -import ( - "encoding/json" - "github.com/emirpasic/gods/containers" -) - -// Assert Serialization implementation -var _ containers.JSONSerializer = (*List)(nil) -var _ containers.JSONDeserializer = (*List)(nil) - -// ToJSON outputs the JSON representation of list's elements. -func (list *List) ToJSON() ([]byte, error) { - return json.Marshal(list.elements[:list.size]) -} - -// FromJSON populates list's elements from the input JSON representation. -func (list *List) FromJSON(data []byte) error { - err := json.Unmarshal(data, &list.elements) - if err == nil { - list.size = len(list.elements) - } - return err -} - -// UnmarshalJSON @implements json.Unmarshaler -func (list *List) UnmarshalJSON(bytes []byte) error { - return list.FromJSON(bytes) -} - -// MarshalJSON @implements json.Marshaler -func (list *List) MarshalJSON() ([]byte, error) { - return list.ToJSON() -} diff --git a/vendor/github.com/emirpasic/gods/lists/lists.go b/vendor/github.com/emirpasic/gods/lists/lists.go deleted file mode 100644 index 55bd619e235..00000000000 --- a/vendor/github.com/emirpasic/gods/lists/lists.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package lists provides an abstract List interface. -// -// In computer science, a list or sequence is an abstract data type that represents an ordered sequence of values, where the same value may occur more than once. An instance of a list is a computer representation of the mathematical concept of a finite sequence; the (potentially) infinite analog of a list is a stream. Lists are a basic example of containers, as they contain other values. If the same value occurs multiple times, each occurrence is considered a distinct item. -// -// Reference: https://en.wikipedia.org/wiki/List_%28abstract_data_type%29 -package lists - -import ( - "github.com/emirpasic/gods/containers" - "github.com/emirpasic/gods/utils" -) - -// List interface that all lists implement -type List interface { - Get(index int) (interface{}, bool) - Remove(index int) - Add(values ...interface{}) - Contains(values ...interface{}) bool - Sort(comparator utils.Comparator) - Swap(index1, index2 int) - Insert(index int, values ...interface{}) - Set(index int, value interface{}) - - containers.Container - // Empty() bool - // Size() int - // Clear() - // Values() []interface{} - // String() string -} diff --git a/vendor/github.com/emirpasic/gods/trees/binaryheap/binaryheap.go b/vendor/github.com/emirpasic/gods/trees/binaryheap/binaryheap.go deleted file mode 100644 index e658f2577e3..00000000000 --- a/vendor/github.com/emirpasic/gods/trees/binaryheap/binaryheap.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package binaryheap implements a binary heap backed by array list. -// -// Comparator defines this heap as either min or max heap. -// -// Structure is not thread safe. -// -// References: http://en.wikipedia.org/wiki/Binary_heap -package binaryheap - -import ( - "fmt" - "github.com/emirpasic/gods/lists/arraylist" - "github.com/emirpasic/gods/trees" - "github.com/emirpasic/gods/utils" - "strings" -) - -// Assert Tree implementation -var _ trees.Tree = (*Heap)(nil) - -// Heap holds elements in an array-list -type Heap struct { - list *arraylist.List - Comparator utils.Comparator -} - -// NewWith instantiates a new empty heap tree with the custom comparator. -func NewWith(comparator utils.Comparator) *Heap { - return &Heap{list: arraylist.New(), Comparator: comparator} -} - -// NewWithIntComparator instantiates a new empty heap with the IntComparator, i.e. elements are of type int. -func NewWithIntComparator() *Heap { - return &Heap{list: arraylist.New(), Comparator: utils.IntComparator} -} - -// NewWithStringComparator instantiates a new empty heap with the StringComparator, i.e. elements are of type string. -func NewWithStringComparator() *Heap { - return &Heap{list: arraylist.New(), Comparator: utils.StringComparator} -} - -// Push adds a value onto the heap and bubbles it up accordingly. -func (heap *Heap) Push(values ...interface{}) { - if len(values) == 1 { - heap.list.Add(values[0]) - heap.bubbleUp() - } else { - // Reference: https://en.wikipedia.org/wiki/Binary_heap#Building_a_heap - for _, value := range values { - heap.list.Add(value) - } - size := heap.list.Size()/2 + 1 - for i := size; i >= 0; i-- { - heap.bubbleDownIndex(i) - } - } -} - -// Pop removes top element on heap and returns it, or nil if heap is empty. -// Second return parameter is true, unless the heap was empty and there was nothing to pop. -func (heap *Heap) Pop() (value interface{}, ok bool) { - value, ok = heap.list.Get(0) - if !ok { - return - } - lastIndex := heap.list.Size() - 1 - heap.list.Swap(0, lastIndex) - heap.list.Remove(lastIndex) - heap.bubbleDown() - return -} - -// Peek returns top element on the heap without removing it, or nil if heap is empty. -// Second return parameter is true, unless the heap was empty and there was nothing to peek. -func (heap *Heap) Peek() (value interface{}, ok bool) { - return heap.list.Get(0) -} - -// Empty returns true if heap does not contain any elements. -func (heap *Heap) Empty() bool { - return heap.list.Empty() -} - -// Size returns number of elements within the heap. -func (heap *Heap) Size() int { - return heap.list.Size() -} - -// Clear removes all elements from the heap. -func (heap *Heap) Clear() { - heap.list.Clear() -} - -// Values returns all elements in the heap. -func (heap *Heap) Values() []interface{} { - values := make([]interface{}, heap.list.Size(), heap.list.Size()) - for it := heap.Iterator(); it.Next(); { - values[it.Index()] = it.Value() - } - return values -} - -// String returns a string representation of container -func (heap *Heap) String() string { - str := "BinaryHeap\n" - values := []string{} - for it := heap.Iterator(); it.Next(); { - values = append(values, fmt.Sprintf("%v", it.Value())) - } - str += strings.Join(values, ", ") - return str -} - -// Performs the "bubble down" operation. This is to place the element that is at the root -// of the heap in its correct place so that the heap maintains the min/max-heap order property. -func (heap *Heap) bubbleDown() { - heap.bubbleDownIndex(0) -} - -// Performs the "bubble down" operation. This is to place the element that is at the index -// of the heap in its correct place so that the heap maintains the min/max-heap order property. -func (heap *Heap) bubbleDownIndex(index int) { - size := heap.list.Size() - for leftIndex := index<<1 + 1; leftIndex < size; leftIndex = index<<1 + 1 { - rightIndex := index<<1 + 2 - smallerIndex := leftIndex - leftValue, _ := heap.list.Get(leftIndex) - rightValue, _ := heap.list.Get(rightIndex) - if rightIndex < size && heap.Comparator(leftValue, rightValue) > 0 { - smallerIndex = rightIndex - } - indexValue, _ := heap.list.Get(index) - smallerValue, _ := heap.list.Get(smallerIndex) - if heap.Comparator(indexValue, smallerValue) > 0 { - heap.list.Swap(index, smallerIndex) - } else { - break - } - index = smallerIndex - } -} - -// Performs the "bubble up" operation. This is to place a newly inserted -// element (i.e. last element in the list) in its correct place so that -// the heap maintains the min/max-heap order property. -func (heap *Heap) bubbleUp() { - index := heap.list.Size() - 1 - for parentIndex := (index - 1) >> 1; index > 0; parentIndex = (index - 1) >> 1 { - indexValue, _ := heap.list.Get(index) - parentValue, _ := heap.list.Get(parentIndex) - if heap.Comparator(parentValue, indexValue) <= 0 { - break - } - heap.list.Swap(index, parentIndex) - index = parentIndex - } -} - -// Check that the index is within bounds of the list -func (heap *Heap) withinRange(index int) bool { - return index >= 0 && index < heap.list.Size() -} diff --git a/vendor/github.com/emirpasic/gods/trees/binaryheap/iterator.go b/vendor/github.com/emirpasic/gods/trees/binaryheap/iterator.go deleted file mode 100644 index f2179633b03..00000000000 --- a/vendor/github.com/emirpasic/gods/trees/binaryheap/iterator.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package binaryheap - -import ( - "github.com/emirpasic/gods/containers" -) - -// Assert Iterator implementation -var _ containers.ReverseIteratorWithIndex = (*Iterator)(nil) - -// Iterator returns a stateful iterator whose values can be fetched by an index. -type Iterator struct { - heap *Heap - index int -} - -// Iterator returns a stateful iterator whose values can be fetched by an index. -func (heap *Heap) Iterator() Iterator { - return Iterator{heap: heap, index: -1} -} - -// Next moves the iterator to the next element and returns true if there was a next element in the container. -// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). -// If Next() was called for the first time, then it will point the iterator to the first element if it exists. -// Modifies the state of the iterator. -func (iterator *Iterator) Next() bool { - if iterator.index < iterator.heap.Size() { - iterator.index++ - } - return iterator.heap.withinRange(iterator.index) -} - -// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. -// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). -// Modifies the state of the iterator. -func (iterator *Iterator) Prev() bool { - if iterator.index >= 0 { - iterator.index-- - } - return iterator.heap.withinRange(iterator.index) -} - -// Value returns the current element's value. -// Does not modify the state of the iterator. -func (iterator *Iterator) Value() interface{} { - start, end := evaluateRange(iterator.index) - if end > iterator.heap.Size() { - end = iterator.heap.Size() - } - tmpHeap := NewWith(iterator.heap.Comparator) - for n := start; n < end; n++ { - value, _ := iterator.heap.list.Get(n) - tmpHeap.Push(value) - } - for n := 0; n < iterator.index-start; n++ { - tmpHeap.Pop() - } - value, _ := tmpHeap.Pop() - return value -} - -// Index returns the current element's index. -// Does not modify the state of the iterator. -func (iterator *Iterator) Index() int { - return iterator.index -} - -// Begin resets the iterator to its initial state (one-before-first) -// Call Next() to fetch the first element if any. -func (iterator *Iterator) Begin() { - iterator.index = -1 -} - -// End moves the iterator past the last element (one-past-the-end). -// Call Prev() to fetch the last element if any. -func (iterator *Iterator) End() { - iterator.index = iterator.heap.Size() -} - -// First moves the iterator to the first element and returns true if there was a first element in the container. -// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). -// Modifies the state of the iterator. -func (iterator *Iterator) First() bool { - iterator.Begin() - return iterator.Next() -} - -// Last moves the iterator to the last element and returns true if there was a last element in the container. -// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). -// Modifies the state of the iterator. -func (iterator *Iterator) Last() bool { - iterator.End() - return iterator.Prev() -} - -// NextTo moves the iterator to the next element from current position that satisfies the condition given by the -// passed function, and returns true if there was a next element in the container. -// If NextTo() returns true, then next element's index and value can be retrieved by Index() and Value(). -// Modifies the state of the iterator. -func (iterator *Iterator) NextTo(f func(index int, value interface{}) bool) bool { - for iterator.Next() { - index, value := iterator.Index(), iterator.Value() - if f(index, value) { - return true - } - } - return false -} - -// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the -// passed function, and returns true if there was a next element in the container. -// If PrevTo() returns true, then next element's index and value can be retrieved by Index() and Value(). -// Modifies the state of the iterator. -func (iterator *Iterator) PrevTo(f func(index int, value interface{}) bool) bool { - for iterator.Prev() { - index, value := iterator.Index(), iterator.Value() - if f(index, value) { - return true - } - } - return false -} - -// numOfBits counts the number of bits of an int -func numOfBits(n int) uint { - var count uint - for n != 0 { - count++ - n >>= 1 - } - return count -} - -// evaluateRange evaluates the index range [start,end) of same level nodes in the heap as the index -func evaluateRange(index int) (start int, end int) { - bits := numOfBits(index+1) - 1 - start = 1< b -type Comparator func(a, b interface{}) int - -// StringComparator provides a fast comparison on strings -func StringComparator(a, b interface{}) int { - s1 := a.(string) - s2 := b.(string) - min := len(s2) - if len(s1) < len(s2) { - min = len(s1) - } - diff := 0 - for i := 0; i < min && diff == 0; i++ { - diff = int(s1[i]) - int(s2[i]) - } - if diff == 0 { - diff = len(s1) - len(s2) - } - if diff < 0 { - return -1 - } - if diff > 0 { - return 1 - } - return 0 -} - -// IntComparator provides a basic comparison on int -func IntComparator(a, b interface{}) int { - aAsserted := a.(int) - bAsserted := b.(int) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// Int8Comparator provides a basic comparison on int8 -func Int8Comparator(a, b interface{}) int { - aAsserted := a.(int8) - bAsserted := b.(int8) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// Int16Comparator provides a basic comparison on int16 -func Int16Comparator(a, b interface{}) int { - aAsserted := a.(int16) - bAsserted := b.(int16) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// Int32Comparator provides a basic comparison on int32 -func Int32Comparator(a, b interface{}) int { - aAsserted := a.(int32) - bAsserted := b.(int32) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// Int64Comparator provides a basic comparison on int64 -func Int64Comparator(a, b interface{}) int { - aAsserted := a.(int64) - bAsserted := b.(int64) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// UIntComparator provides a basic comparison on uint -func UIntComparator(a, b interface{}) int { - aAsserted := a.(uint) - bAsserted := b.(uint) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// UInt8Comparator provides a basic comparison on uint8 -func UInt8Comparator(a, b interface{}) int { - aAsserted := a.(uint8) - bAsserted := b.(uint8) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// UInt16Comparator provides a basic comparison on uint16 -func UInt16Comparator(a, b interface{}) int { - aAsserted := a.(uint16) - bAsserted := b.(uint16) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// UInt32Comparator provides a basic comparison on uint32 -func UInt32Comparator(a, b interface{}) int { - aAsserted := a.(uint32) - bAsserted := b.(uint32) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// UInt64Comparator provides a basic comparison on uint64 -func UInt64Comparator(a, b interface{}) int { - aAsserted := a.(uint64) - bAsserted := b.(uint64) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// Float32Comparator provides a basic comparison on float32 -func Float32Comparator(a, b interface{}) int { - aAsserted := a.(float32) - bAsserted := b.(float32) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// Float64Comparator provides a basic comparison on float64 -func Float64Comparator(a, b interface{}) int { - aAsserted := a.(float64) - bAsserted := b.(float64) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// ByteComparator provides a basic comparison on byte -func ByteComparator(a, b interface{}) int { - aAsserted := a.(byte) - bAsserted := b.(byte) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// RuneComparator provides a basic comparison on rune -func RuneComparator(a, b interface{}) int { - aAsserted := a.(rune) - bAsserted := b.(rune) - switch { - case aAsserted > bAsserted: - return 1 - case aAsserted < bAsserted: - return -1 - default: - return 0 - } -} - -// TimeComparator provides a basic comparison on time.Time -func TimeComparator(a, b interface{}) int { - aAsserted := a.(time.Time) - bAsserted := b.(time.Time) - - switch { - case aAsserted.After(bAsserted): - return 1 - case aAsserted.Before(bAsserted): - return -1 - default: - return 0 - } -} diff --git a/vendor/github.com/emirpasic/gods/utils/sort.go b/vendor/github.com/emirpasic/gods/utils/sort.go deleted file mode 100644 index 79ced1f5d26..00000000000 --- a/vendor/github.com/emirpasic/gods/utils/sort.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package utils - -import "sort" - -// Sort sorts values (in-place) with respect to the given comparator. -// -// Uses Go's sort (hybrid of quicksort for large and then insertion sort for smaller slices). -func Sort(values []interface{}, comparator Comparator) { - sort.Sort(sortable{values, comparator}) -} - -type sortable struct { - values []interface{} - comparator Comparator -} - -func (s sortable) Len() int { - return len(s.values) -} -func (s sortable) Swap(i, j int) { - s.values[i], s.values[j] = s.values[j], s.values[i] -} -func (s sortable) Less(i, j int) bool { - return s.comparator(s.values[i], s.values[j]) < 0 -} diff --git a/vendor/github.com/emirpasic/gods/utils/utils.go b/vendor/github.com/emirpasic/gods/utils/utils.go deleted file mode 100644 index 262c62576ae..00000000000 --- a/vendor/github.com/emirpasic/gods/utils/utils.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2015, Emir Pasic. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package utils provides common utility functions. -// -// Provided functionalities: -// - sorting -// - comparators -package utils - -import ( - "fmt" - "strconv" -) - -// ToString converts a value to string. -func ToString(value interface{}) string { - switch value := value.(type) { - case string: - return value - case int8: - return strconv.FormatInt(int64(value), 10) - case int16: - return strconv.FormatInt(int64(value), 10) - case int32: - return strconv.FormatInt(int64(value), 10) - case int64: - return strconv.FormatInt(value, 10) - case uint8: - return strconv.FormatUint(uint64(value), 10) - case uint16: - return strconv.FormatUint(uint64(value), 10) - case uint32: - return strconv.FormatUint(uint64(value), 10) - case uint64: - return strconv.FormatUint(value, 10) - case float32: - return strconv.FormatFloat(float64(value), 'g', -1, 64) - case float64: - return strconv.FormatFloat(value, 'g', -1, 64) - case bool: - return strconv.FormatBool(value) - default: - return fmt.Sprintf("%+v", value) - } -} diff --git a/vendor/github.com/fatih/color/LICENSE.md b/vendor/github.com/fatih/color/LICENSE.md deleted file mode 100644 index 25fdaf639df..00000000000 --- a/vendor/github.com/fatih/color/LICENSE.md +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 Fatih Arslan - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/fatih/color/README.md b/vendor/github.com/fatih/color/README.md deleted file mode 100644 index 42d9abc07ed..00000000000 --- a/vendor/github.com/fatih/color/README.md +++ /dev/null @@ -1,182 +0,0 @@ -# Archived project. No maintenance. - -This project is not maintained anymore and is archived. Feel free to fork and -make your own changes if needed. For more detail read my blog post: [Taking an indefinite sabbatical from my projects](https://arslan.io/2018/10/09/taking-an-indefinite-sabbatical-from-my-projects/) - -Thanks to everyone for their valuable feedback and contributions. - - -# Color [![GoDoc](https://godoc.org/github.com/fatih/color?status.svg)](https://godoc.org/github.com/fatih/color) - -Color lets you use colorized outputs in terms of [ANSI Escape -Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It -has support for Windows too! The API can be used in several ways, pick one that -suits you. - - -![Color](https://i.imgur.com/c1JI0lA.png) - - -## Install - -```bash -go get github.com/fatih/color -``` - -## Examples - -### Standard colors - -```go -// Print with default helper functions -color.Cyan("Prints text in cyan.") - -// A newline will be appended automatically -color.Blue("Prints %s in blue.", "text") - -// These are using the default foreground colors -color.Red("We have red") -color.Magenta("And many others ..") - -``` - -### Mix and reuse colors - -```go -// Create a new color object -c := color.New(color.FgCyan).Add(color.Underline) -c.Println("Prints cyan text with an underline.") - -// Or just add them to New() -d := color.New(color.FgCyan, color.Bold) -d.Printf("This prints bold cyan %s\n", "too!.") - -// Mix up foreground and background colors, create new mixes! -red := color.New(color.FgRed) - -boldRed := red.Add(color.Bold) -boldRed.Println("This will print text in bold red.") - -whiteBackground := red.Add(color.BgWhite) -whiteBackground.Println("Red text with white background.") -``` - -### Use your own output (io.Writer) - -```go -// Use your own io.Writer output -color.New(color.FgBlue).Fprintln(myWriter, "blue color!") - -blue := color.New(color.FgBlue) -blue.Fprint(writer, "This will print text in blue.") -``` - -### Custom print functions (PrintFunc) - -```go -// Create a custom print function for convenience -red := color.New(color.FgRed).PrintfFunc() -red("Warning") -red("Error: %s", err) - -// Mix up multiple attributes -notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() -notice("Don't forget this...") -``` - -### Custom fprint functions (FprintFunc) - -```go -blue := color.New(FgBlue).FprintfFunc() -blue(myWriter, "important notice: %s", stars) - -// Mix up with multiple attributes -success := color.New(color.Bold, color.FgGreen).FprintlnFunc() -success(myWriter, "Don't forget this...") -``` - -### Insert into noncolor strings (SprintFunc) - -```go -// Create SprintXxx functions to mix strings with other non-colorized strings: -yellow := color.New(color.FgYellow).SprintFunc() -red := color.New(color.FgRed).SprintFunc() -fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error")) - -info := color.New(color.FgWhite, color.BgGreen).SprintFunc() -fmt.Printf("This %s rocks!\n", info("package")) - -// Use helper functions -fmt.Println("This", color.RedString("warning"), "should be not neglected.") -fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.") - -// Windows supported too! Just don't forget to change the output to color.Output -fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) -``` - -### Plug into existing code - -```go -// Use handy standard colors -color.Set(color.FgYellow) - -fmt.Println("Existing text will now be in yellow") -fmt.Printf("This one %s\n", "too") - -color.Unset() // Don't forget to unset - -// You can mix up parameters -color.Set(color.FgMagenta, color.Bold) -defer color.Unset() // Use it in your function - -fmt.Println("All text will now be bold magenta.") -``` - -### Disable/Enable color - -There might be a case where you want to explicitly disable/enable color output. the -`go-isatty` package will automatically disable color output for non-tty output streams -(for example if the output were piped directly to `less`) - -`Color` has support to disable/enable colors both globally and for single color -definitions. For example suppose you have a CLI app and a `--no-color` bool flag. You -can easily disable the color output with: - -```go - -var flagNoColor = flag.Bool("no-color", false, "Disable color output") - -if *flagNoColor { - color.NoColor = true // disables colorized output -} -``` - -It also has support for single color definitions (local). You can -disable/enable color output on the fly: - -```go -c := color.New(color.FgCyan) -c.Println("Prints cyan text") - -c.DisableColor() -c.Println("This is printed without any color") - -c.EnableColor() -c.Println("This prints again cyan...") -``` - -## Todo - -* Save/Return previous values -* Evaluate fmt.Formatter interface - - -## Credits - - * [Fatih Arslan](https://github.com/fatih) - * Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable) - -## License - -The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details - diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go deleted file mode 100644 index 91c8e9f0620..00000000000 --- a/vendor/github.com/fatih/color/color.go +++ /dev/null @@ -1,603 +0,0 @@ -package color - -import ( - "fmt" - "io" - "os" - "strconv" - "strings" - "sync" - - "github.com/mattn/go-colorable" - "github.com/mattn/go-isatty" -) - -var ( - // NoColor defines if the output is colorized or not. It's dynamically set to - // false or true based on the stdout's file descriptor referring to a terminal - // or not. This is a global option and affects all colors. For more control - // over each color block use the methods DisableColor() individually. - NoColor = os.Getenv("TERM") == "dumb" || - (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) - - // Output defines the standard output of the print functions. By default - // os.Stdout is used. - Output = colorable.NewColorableStdout() - - // Error defines a color supporting writer for os.Stderr. - Error = colorable.NewColorableStderr() - - // colorsCache is used to reduce the count of created Color objects and - // allows to reuse already created objects with required Attribute. - colorsCache = make(map[Attribute]*Color) - colorsCacheMu sync.Mutex // protects colorsCache -) - -// Color defines a custom color object which is defined by SGR parameters. -type Color struct { - params []Attribute - noColor *bool -} - -// Attribute defines a single SGR Code -type Attribute int - -const escape = "\x1b" - -// Base attributes -const ( - Reset Attribute = iota - Bold - Faint - Italic - Underline - BlinkSlow - BlinkRapid - ReverseVideo - Concealed - CrossedOut -) - -// Foreground text colors -const ( - FgBlack Attribute = iota + 30 - FgRed - FgGreen - FgYellow - FgBlue - FgMagenta - FgCyan - FgWhite -) - -// Foreground Hi-Intensity text colors -const ( - FgHiBlack Attribute = iota + 90 - FgHiRed - FgHiGreen - FgHiYellow - FgHiBlue - FgHiMagenta - FgHiCyan - FgHiWhite -) - -// Background text colors -const ( - BgBlack Attribute = iota + 40 - BgRed - BgGreen - BgYellow - BgBlue - BgMagenta - BgCyan - BgWhite -) - -// Background Hi-Intensity text colors -const ( - BgHiBlack Attribute = iota + 100 - BgHiRed - BgHiGreen - BgHiYellow - BgHiBlue - BgHiMagenta - BgHiCyan - BgHiWhite -) - -// New returns a newly created color object. -func New(value ...Attribute) *Color { - c := &Color{params: make([]Attribute, 0)} - c.Add(value...) - return c -} - -// Set sets the given parameters immediately. It will change the color of -// output with the given SGR parameters until color.Unset() is called. -func Set(p ...Attribute) *Color { - c := New(p...) - c.Set() - return c -} - -// Unset resets all escape attributes and clears the output. Usually should -// be called after Set(). -func Unset() { - if NoColor { - return - } - - fmt.Fprintf(Output, "%s[%dm", escape, Reset) -} - -// Set sets the SGR sequence. -func (c *Color) Set() *Color { - if c.isNoColorSet() { - return c - } - - fmt.Fprintf(Output, c.format()) - return c -} - -func (c *Color) unset() { - if c.isNoColorSet() { - return - } - - Unset() -} - -func (c *Color) setWriter(w io.Writer) *Color { - if c.isNoColorSet() { - return c - } - - fmt.Fprintf(w, c.format()) - return c -} - -func (c *Color) unsetWriter(w io.Writer) { - if c.isNoColorSet() { - return - } - - if NoColor { - return - } - - fmt.Fprintf(w, "%s[%dm", escape, Reset) -} - -// Add is used to chain SGR parameters. Use as many as parameters to combine -// and create custom color objects. Example: Add(color.FgRed, color.Underline). -func (c *Color) Add(value ...Attribute) *Color { - c.params = append(c.params, value...) - return c -} - -func (c *Color) prepend(value Attribute) { - c.params = append(c.params, 0) - copy(c.params[1:], c.params[0:]) - c.params[0] = value -} - -// Fprint formats using the default formats for its operands and writes to w. -// Spaces are added between operands when neither is a string. -// It returns the number of bytes written and any write error encountered. -// On Windows, users should wrap w with colorable.NewColorable() if w is of -// type *os.File. -func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) { - c.setWriter(w) - defer c.unsetWriter(w) - - return fmt.Fprint(w, a...) -} - -// Print formats using the default formats for its operands and writes to -// standard output. Spaces are added between operands when neither is a -// string. It returns the number of bytes written and any write error -// encountered. This is the standard fmt.Print() method wrapped with the given -// color. -func (c *Color) Print(a ...interface{}) (n int, err error) { - c.Set() - defer c.unset() - - return fmt.Fprint(Output, a...) -} - -// Fprintf formats according to a format specifier and writes to w. -// It returns the number of bytes written and any write error encountered. -// On Windows, users should wrap w with colorable.NewColorable() if w is of -// type *os.File. -func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { - c.setWriter(w) - defer c.unsetWriter(w) - - return fmt.Fprintf(w, format, a...) -} - -// Printf formats according to a format specifier and writes to standard output. -// It returns the number of bytes written and any write error encountered. -// This is the standard fmt.Printf() method wrapped with the given color. -func (c *Color) Printf(format string, a ...interface{}) (n int, err error) { - c.Set() - defer c.unset() - - return fmt.Fprintf(Output, format, a...) -} - -// Fprintln formats using the default formats for its operands and writes to w. -// Spaces are always added between operands and a newline is appended. -// On Windows, users should wrap w with colorable.NewColorable() if w is of -// type *os.File. -func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { - c.setWriter(w) - defer c.unsetWriter(w) - - return fmt.Fprintln(w, a...) -} - -// Println formats using the default formats for its operands and writes to -// standard output. Spaces are always added between operands and a newline is -// appended. It returns the number of bytes written and any write error -// encountered. This is the standard fmt.Print() method wrapped with the given -// color. -func (c *Color) Println(a ...interface{}) (n int, err error) { - c.Set() - defer c.unset() - - return fmt.Fprintln(Output, a...) -} - -// Sprint is just like Print, but returns a string instead of printing it. -func (c *Color) Sprint(a ...interface{}) string { - return c.wrap(fmt.Sprint(a...)) -} - -// Sprintln is just like Println, but returns a string instead of printing it. -func (c *Color) Sprintln(a ...interface{}) string { - return c.wrap(fmt.Sprintln(a...)) -} - -// Sprintf is just like Printf, but returns a string instead of printing it. -func (c *Color) Sprintf(format string, a ...interface{}) string { - return c.wrap(fmt.Sprintf(format, a...)) -} - -// FprintFunc returns a new function that prints the passed arguments as -// colorized with color.Fprint(). -func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) { - return func(w io.Writer, a ...interface{}) { - c.Fprint(w, a...) - } -} - -// PrintFunc returns a new function that prints the passed arguments as -// colorized with color.Print(). -func (c *Color) PrintFunc() func(a ...interface{}) { - return func(a ...interface{}) { - c.Print(a...) - } -} - -// FprintfFunc returns a new function that prints the passed arguments as -// colorized with color.Fprintf(). -func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) { - return func(w io.Writer, format string, a ...interface{}) { - c.Fprintf(w, format, a...) - } -} - -// PrintfFunc returns a new function that prints the passed arguments as -// colorized with color.Printf(). -func (c *Color) PrintfFunc() func(format string, a ...interface{}) { - return func(format string, a ...interface{}) { - c.Printf(format, a...) - } -} - -// FprintlnFunc returns a new function that prints the passed arguments as -// colorized with color.Fprintln(). -func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) { - return func(w io.Writer, a ...interface{}) { - c.Fprintln(w, a...) - } -} - -// PrintlnFunc returns a new function that prints the passed arguments as -// colorized with color.Println(). -func (c *Color) PrintlnFunc() func(a ...interface{}) { - return func(a ...interface{}) { - c.Println(a...) - } -} - -// SprintFunc returns a new function that returns colorized strings for the -// given arguments with fmt.Sprint(). Useful to put into or mix into other -// string. Windows users should use this in conjunction with color.Output, example: -// -// put := New(FgYellow).SprintFunc() -// fmt.Fprintf(color.Output, "This is a %s", put("warning")) -func (c *Color) SprintFunc() func(a ...interface{}) string { - return func(a ...interface{}) string { - return c.wrap(fmt.Sprint(a...)) - } -} - -// SprintfFunc returns a new function that returns colorized strings for the -// given arguments with fmt.Sprintf(). Useful to put into or mix into other -// string. Windows users should use this in conjunction with color.Output. -func (c *Color) SprintfFunc() func(format string, a ...interface{}) string { - return func(format string, a ...interface{}) string { - return c.wrap(fmt.Sprintf(format, a...)) - } -} - -// SprintlnFunc returns a new function that returns colorized strings for the -// given arguments with fmt.Sprintln(). Useful to put into or mix into other -// string. Windows users should use this in conjunction with color.Output. -func (c *Color) SprintlnFunc() func(a ...interface{}) string { - return func(a ...interface{}) string { - return c.wrap(fmt.Sprintln(a...)) - } -} - -// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m" -// an example output might be: "1;36" -> bold cyan -func (c *Color) sequence() string { - format := make([]string, len(c.params)) - for i, v := range c.params { - format[i] = strconv.Itoa(int(v)) - } - - return strings.Join(format, ";") -} - -// wrap wraps the s string with the colors attributes. The string is ready to -// be printed. -func (c *Color) wrap(s string) string { - if c.isNoColorSet() { - return s - } - - return c.format() + s + c.unformat() -} - -func (c *Color) format() string { - return fmt.Sprintf("%s[%sm", escape, c.sequence()) -} - -func (c *Color) unformat() string { - return fmt.Sprintf("%s[%dm", escape, Reset) -} - -// DisableColor disables the color output. Useful to not change any existing -// code and still being able to output. Can be used for flags like -// "--no-color". To enable back use EnableColor() method. -func (c *Color) DisableColor() { - c.noColor = boolPtr(true) -} - -// EnableColor enables the color output. Use it in conjunction with -// DisableColor(). Otherwise this method has no side effects. -func (c *Color) EnableColor() { - c.noColor = boolPtr(false) -} - -func (c *Color) isNoColorSet() bool { - // check first if we have user setted action - if c.noColor != nil { - return *c.noColor - } - - // if not return the global option, which is disabled by default - return NoColor -} - -// Equals returns a boolean value indicating whether two colors are equal. -func (c *Color) Equals(c2 *Color) bool { - if len(c.params) != len(c2.params) { - return false - } - - for _, attr := range c.params { - if !c2.attrExists(attr) { - return false - } - } - - return true -} - -func (c *Color) attrExists(a Attribute) bool { - for _, attr := range c.params { - if attr == a { - return true - } - } - - return false -} - -func boolPtr(v bool) *bool { - return &v -} - -func getCachedColor(p Attribute) *Color { - colorsCacheMu.Lock() - defer colorsCacheMu.Unlock() - - c, ok := colorsCache[p] - if !ok { - c = New(p) - colorsCache[p] = c - } - - return c -} - -func colorPrint(format string, p Attribute, a ...interface{}) { - c := getCachedColor(p) - - if !strings.HasSuffix(format, "\n") { - format += "\n" - } - - if len(a) == 0 { - c.Print(format) - } else { - c.Printf(format, a...) - } -} - -func colorString(format string, p Attribute, a ...interface{}) string { - c := getCachedColor(p) - - if len(a) == 0 { - return c.SprintFunc()(format) - } - - return c.SprintfFunc()(format, a...) -} - -// Black is a convenient helper function to print with black foreground. A -// newline is appended to format by default. -func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) } - -// Red is a convenient helper function to print with red foreground. A -// newline is appended to format by default. -func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) } - -// Green is a convenient helper function to print with green foreground. A -// newline is appended to format by default. -func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) } - -// Yellow is a convenient helper function to print with yellow foreground. -// A newline is appended to format by default. -func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) } - -// Blue is a convenient helper function to print with blue foreground. A -// newline is appended to format by default. -func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) } - -// Magenta is a convenient helper function to print with magenta foreground. -// A newline is appended to format by default. -func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) } - -// Cyan is a convenient helper function to print with cyan foreground. A -// newline is appended to format by default. -func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) } - -// White is a convenient helper function to print with white foreground. A -// newline is appended to format by default. -func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) } - -// BlackString is a convenient helper function to return a string with black -// foreground. -func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) } - -// RedString is a convenient helper function to return a string with red -// foreground. -func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) } - -// GreenString is a convenient helper function to return a string with green -// foreground. -func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) } - -// YellowString is a convenient helper function to return a string with yellow -// foreground. -func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) } - -// BlueString is a convenient helper function to return a string with blue -// foreground. -func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) } - -// MagentaString is a convenient helper function to return a string with magenta -// foreground. -func MagentaString(format string, a ...interface{}) string { - return colorString(format, FgMagenta, a...) -} - -// CyanString is a convenient helper function to return a string with cyan -// foreground. -func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) } - -// WhiteString is a convenient helper function to return a string with white -// foreground. -func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) } - -// HiBlack is a convenient helper function to print with hi-intensity black foreground. A -// newline is appended to format by default. -func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) } - -// HiRed is a convenient helper function to print with hi-intensity red foreground. A -// newline is appended to format by default. -func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) } - -// HiGreen is a convenient helper function to print with hi-intensity green foreground. A -// newline is appended to format by default. -func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) } - -// HiYellow is a convenient helper function to print with hi-intensity yellow foreground. -// A newline is appended to format by default. -func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) } - -// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A -// newline is appended to format by default. -func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) } - -// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground. -// A newline is appended to format by default. -func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) } - -// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A -// newline is appended to format by default. -func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) } - -// HiWhite is a convenient helper function to print with hi-intensity white foreground. A -// newline is appended to format by default. -func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) } - -// HiBlackString is a convenient helper function to return a string with hi-intensity black -// foreground. -func HiBlackString(format string, a ...interface{}) string { - return colorString(format, FgHiBlack, a...) -} - -// HiRedString is a convenient helper function to return a string with hi-intensity red -// foreground. -func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) } - -// HiGreenString is a convenient helper function to return a string with hi-intensity green -// foreground. -func HiGreenString(format string, a ...interface{}) string { - return colorString(format, FgHiGreen, a...) -} - -// HiYellowString is a convenient helper function to return a string with hi-intensity yellow -// foreground. -func HiYellowString(format string, a ...interface{}) string { - return colorString(format, FgHiYellow, a...) -} - -// HiBlueString is a convenient helper function to return a string with hi-intensity blue -// foreground. -func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) } - -// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta -// foreground. -func HiMagentaString(format string, a ...interface{}) string { - return colorString(format, FgHiMagenta, a...) -} - -// HiCyanString is a convenient helper function to return a string with hi-intensity cyan -// foreground. -func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) } - -// HiWhiteString is a convenient helper function to return a string with hi-intensity white -// foreground. -func HiWhiteString(format string, a ...interface{}) string { - return colorString(format, FgHiWhite, a...) -} diff --git a/vendor/github.com/fatih/color/doc.go b/vendor/github.com/fatih/color/doc.go deleted file mode 100644 index cf1e96500f4..00000000000 --- a/vendor/github.com/fatih/color/doc.go +++ /dev/null @@ -1,133 +0,0 @@ -/* -Package color is an ANSI color package to output colorized or SGR defined -output to the standard output. The API can be used in several way, pick one -that suits you. - -Use simple and default helper functions with predefined foreground colors: - - color.Cyan("Prints text in cyan.") - - // a newline will be appended automatically - color.Blue("Prints %s in blue.", "text") - - // More default foreground colors.. - color.Red("We have red") - color.Yellow("Yellow color too!") - color.Magenta("And many others ..") - - // Hi-intensity colors - color.HiGreen("Bright green color.") - color.HiBlack("Bright black means gray..") - color.HiWhite("Shiny white color!") - -However there are times where custom color mixes are required. Below are some -examples to create custom color objects and use the print functions of each -separate color object. - - // Create a new color object - c := color.New(color.FgCyan).Add(color.Underline) - c.Println("Prints cyan text with an underline.") - - // Or just add them to New() - d := color.New(color.FgCyan, color.Bold) - d.Printf("This prints bold cyan %s\n", "too!.") - - - // Mix up foreground and background colors, create new mixes! - red := color.New(color.FgRed) - - boldRed := red.Add(color.Bold) - boldRed.Println("This will print text in bold red.") - - whiteBackground := red.Add(color.BgWhite) - whiteBackground.Println("Red text with White background.") - - // Use your own io.Writer output - color.New(color.FgBlue).Fprintln(myWriter, "blue color!") - - blue := color.New(color.FgBlue) - blue.Fprint(myWriter, "This will print text in blue.") - -You can create PrintXxx functions to simplify even more: - - // Create a custom print function for convenient - red := color.New(color.FgRed).PrintfFunc() - red("warning") - red("error: %s", err) - - // Mix up multiple attributes - notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() - notice("don't forget this...") - -You can also FprintXxx functions to pass your own io.Writer: - - blue := color.New(FgBlue).FprintfFunc() - blue(myWriter, "important notice: %s", stars) - - // Mix up with multiple attributes - success := color.New(color.Bold, color.FgGreen).FprintlnFunc() - success(myWriter, don't forget this...") - - -Or create SprintXxx functions to mix strings with other non-colorized strings: - - yellow := New(FgYellow).SprintFunc() - red := New(FgRed).SprintFunc() - - fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error")) - - info := New(FgWhite, BgGreen).SprintFunc() - fmt.Printf("this %s rocks!\n", info("package")) - -Windows support is enabled by default. All Print functions work as intended. -However only for color.SprintXXX functions, user should use fmt.FprintXXX and -set the output to color.Output: - - fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) - - info := New(FgWhite, BgGreen).SprintFunc() - fmt.Fprintf(color.Output, "this %s rocks!\n", info("package")) - -Using with existing code is possible. Just use the Set() method to set the -standard output to the given parameters. That way a rewrite of an existing -code is not required. - - // Use handy standard colors. - color.Set(color.FgYellow) - - fmt.Println("Existing text will be now in Yellow") - fmt.Printf("This one %s\n", "too") - - color.Unset() // don't forget to unset - - // You can mix up parameters - color.Set(color.FgMagenta, color.Bold) - defer color.Unset() // use it in your function - - fmt.Println("All text will be now bold magenta.") - -There might be a case where you want to disable color output (for example to -pipe the standard output of your app to somewhere else). `Color` has support to -disable colors both globally and for single color definition. For example -suppose you have a CLI app and a `--no-color` bool flag. You can easily disable -the color output with: - - var flagNoColor = flag.Bool("no-color", false, "Disable color output") - - if *flagNoColor { - color.NoColor = true // disables colorized output - } - -It also has support for single color definitions (local). You can -disable/enable color output on the fly: - - c := color.New(color.FgCyan) - c.Println("Prints cyan text") - - c.DisableColor() - c.Println("This is printed without any color") - - c.EnableColor() - c.Println("This prints again cyan...") -*/ -package color diff --git a/vendor/github.com/gdamore/encoding/.appveyor.yml b/vendor/github.com/gdamore/encoding/.appveyor.yml deleted file mode 100644 index 19a4c5ddf91..00000000000 --- a/vendor/github.com/gdamore/encoding/.appveyor.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: 1.0.{build} -clone_folder: c:\gopath\src\github.com\gdamore\encoding -environment: - GOPATH: c:\gopath -build_script: -- go version -- go env -- SET PATH=%LOCALAPPDATA%\atom\bin;%GOPATH%\bin;%PATH% -- go get -t ./... -- go build -- go install ./... -test_script: -- go test ./... diff --git a/vendor/github.com/gdamore/encoding/CODE_OF_CONDUCT.md b/vendor/github.com/gdamore/encoding/CODE_OF_CONDUCT.md deleted file mode 100644 index 65527da0853..00000000000 --- a/vendor/github.com/gdamore/encoding/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,73 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at garrett@damore.org. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org diff --git a/vendor/github.com/gdamore/encoding/LICENSE b/vendor/github.com/gdamore/encoding/LICENSE deleted file mode 100644 index d6456956733..00000000000 --- a/vendor/github.com/gdamore/encoding/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/gdamore/encoding/README.md b/vendor/github.com/gdamore/encoding/README.md deleted file mode 100644 index 2ce29a9f374..00000000000 --- a/vendor/github.com/gdamore/encoding/README.md +++ /dev/null @@ -1,20 +0,0 @@ -## encoding - - -[![Linux](https://img.shields.io/github/actions/workflow/status/gdamore/encoding/linux.yml?branch=main&logoColor=grey&logo=linux&label=)](https://github.com/gdamore/encoding/actions/workflows/linux.yml) -[![Windows](https://img.shields.io/github/actions/workflow/status/gdamore/encoding/windows.yml?branch=main&logoColor=grey&logo=windows&label=)](https://github.com/gdamore/encoding/actions/workflows/windows.yml) -[![Apache License](https://img.shields.io/github/license/gdamore/encoding.svg?logoColor=silver&logo=opensourceinitiative&color=blue&label=)](https://github.com/gdamore/encoding/blob/master/LICENSE) -[![Coverage](https://img.shields.io/codecov/c/github/gdamore/encoding?logoColor=grey&logo=codecov&label=)](https://codecov.io/gh/gdamore/encoding) -[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/gdamore/encoding) - -Package encoding provides a number of encodings that are missing from the -standard Go [encoding]("https://godoc.org/golang.org/x/text/encoding") package. - -We hope that we can contribute these to the standard Go library someday. It -turns out that some of these are useful for dealing with I/O streams coming -from non-UTF friendly sources. - -The UTF8 Encoder is also useful for situations where valid UTF-8 might be -carried in streams that contain non-valid UTF; in particular I use it for -helping me cope with terminals that embed escape sequences in otherwise -valid UTF-8. diff --git a/vendor/github.com/gdamore/encoding/SECURITY.md b/vendor/github.com/gdamore/encoding/SECURITY.md deleted file mode 100644 index b9f64966f51..00000000000 --- a/vendor/github.com/gdamore/encoding/SECURITY.md +++ /dev/null @@ -1,12 +0,0 @@ -# Security Policy - -We take security very seriously in mangos, since you may be using it in -Internet-facing applications. - -## Reporting a Vulnerability - -To report a vulnerability, please contact us on our discord. -You may also send an email to garrett@damore.org, or info@staysail.tech. - -We will keep the reporter updated on any status updates on a regular basis, -and will respond within two business days for any reported security issue. diff --git a/vendor/github.com/gdamore/encoding/ascii.go b/vendor/github.com/gdamore/encoding/ascii.go deleted file mode 100644 index b7321f43346..00000000000 --- a/vendor/github.com/gdamore/encoding/ascii.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2015 Garrett D'Amore -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package encoding - -import ( - "golang.org/x/text/encoding" -) - -// ASCII represents the 7-bit US-ASCII scheme. It decodes directly to -// UTF-8 without change, as all ASCII values are legal UTF-8. -// Unicode values less than 128 (i.e. 7 bits) map 1:1 with ASCII. -// It encodes runes outside of that to 0x1A, the ASCII substitution character. -var ASCII encoding.Encoding - -func init() { - amap := make(map[byte]rune) - for i := 128; i <= 255; i++ { - amap[byte(i)] = RuneError - } - - cm := &Charmap{Map: amap} - cm.Init() - ASCII = cm -} diff --git a/vendor/github.com/gdamore/encoding/charmap.go b/vendor/github.com/gdamore/encoding/charmap.go deleted file mode 100644 index e8089c453a4..00000000000 --- a/vendor/github.com/gdamore/encoding/charmap.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2024 Garrett D'Amore -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package encoding - -import ( - "sync" - "unicode/utf8" - - "golang.org/x/text/encoding" - "golang.org/x/text/transform" -) - -const ( - // RuneError is an alias for the UTF-8 replacement rune, '\uFFFD'. - RuneError = '\uFFFD' - - // RuneSelf is the rune below which UTF-8 and the Unicode values are - // identical. Its also the limit for ASCII. - RuneSelf = 0x80 - - // ASCIISub is the ASCII substitution character. - ASCIISub = '\x1a' -) - -// Charmap is a structure for setting up encodings for 8-bit character sets, -// for transforming between UTF8 and that other character set. It has some -// ideas borrowed from golang.org/x/text/encoding/charmap, but it uses a -// different implementation. This implementation uses maps, and supports -// user-defined maps. -// -// We do assume that a character map has a reasonable substitution character, -// and that valid encodings are stable (exactly a 1:1 map) and stateless -// (that is there is no shift character or anything like that.) Hence this -// approach will not work for many East Asian character sets. -// -// Measurement shows little or no measurable difference in the performance of -// the two approaches. The difference was down to a couple of nsec/op, and -// no consistent pattern as to which ran faster. With the conversion to -// UTF-8 the code takes about 25 nsec/op. The conversion in the reverse -// direction takes about 100 nsec/op. (The larger cost for conversion -// from UTF-8 is most likely due to the need to convert the UTF-8 byte stream -// to a rune before conversion. -type Charmap struct { - transform.NopResetter - bytes map[rune]byte - runes [256][]byte - once sync.Once - - // The map between bytes and runes. To indicate that a specific - // byte value is invalid for a charcter set, use the rune - // utf8.RuneError. Values that are absent from this map will - // be assumed to have the identity mapping -- that is the default - // is to assume ISO8859-1, where all 8-bit characters have the same - // numeric value as their Unicode runes. (Not to be confused with - // the UTF-8 values, which *will* be different for non-ASCII runes.) - // - // If no values less than RuneSelf are changed (or have non-identity - // mappings), then the character set is assumed to be an ASCII - // superset, and certain assumptions and optimizations become - // available for ASCII bytes. - Map map[byte]rune - - // The ReplacementChar is the byte value to use for substitution. - // It should normally be ASCIISub for ASCII encodings. This may be - // unset (left to zero) for mappings that are strictly ASCII supersets. - // In that case ASCIISub will be assumed instead. - ReplacementChar byte -} - -type cmapDecoder struct { - transform.NopResetter - runes [256][]byte -} - -type cmapEncoder struct { - transform.NopResetter - bytes map[rune]byte - replace byte -} - -// Init initializes internal values of a character map. This should -// be done early, to minimize the cost of allocation of transforms -// later. It is not strictly necessary however, as the allocation -// functions will arrange to call it if it has not already been done. -func (c *Charmap) Init() { - c.once.Do(c.initialize) -} - -func (c *Charmap) initialize() { - c.bytes = make(map[rune]byte) - ascii := true - - for i := 0; i < 256; i++ { - r, ok := c.Map[byte(i)] - if !ok { - r = rune(i) - } - if r < 128 && r != rune(i) { - ascii = false - } - if r != RuneError { - c.bytes[r] = byte(i) - } - utf := make([]byte, utf8.RuneLen(r)) - utf8.EncodeRune(utf, r) - c.runes[i] = utf - } - if ascii && c.ReplacementChar == '\x00' { - c.ReplacementChar = ASCIISub - } -} - -// NewDecoder returns a Decoder the converts from the 8-bit -// character set to UTF-8. Unknown mappings, if any, are mapped -// to '\uFFFD'. -func (c *Charmap) NewDecoder() *encoding.Decoder { - c.Init() - return &encoding.Decoder{Transformer: &cmapDecoder{runes: c.runes}} -} - -// NewEncoder returns a Transformer that converts from UTF8 to the -// 8-bit character set. Unknown mappings are mapped to 0x1A. -func (c *Charmap) NewEncoder() *encoding.Encoder { - c.Init() - return &encoding.Encoder{ - Transformer: &cmapEncoder{ - bytes: c.bytes, - replace: c.ReplacementChar, - }, - } -} - -func (d *cmapDecoder) Transform(dst, src []byte, atEOF bool) (int, int, error) { - var e error - var ndst, nsrc int - - for _, c := range src { - b := d.runes[c] - l := len(b) - - if ndst+l > len(dst) { - e = transform.ErrShortDst - break - } - for i := 0; i < l; i++ { - dst[ndst] = b[i] - ndst++ - } - nsrc++ - } - return ndst, nsrc, e -} - -func (d *cmapEncoder) Transform(dst, src []byte, atEOF bool) (int, int, error) { - var e error - var ndst, nsrc int - for nsrc < len(src) { - if ndst >= len(dst) { - e = transform.ErrShortDst - break - } - - r, sz := utf8.DecodeRune(src[nsrc:]) - if r == utf8.RuneError && sz == 1 { - // If its inconclusive due to insufficient data in - // in the source, report it - if atEOF && !utf8.FullRune(src[nsrc:]) { - e = transform.ErrShortSrc - break - } - } - - if c, ok := d.bytes[r]; ok { - dst[ndst] = c - } else { - dst[ndst] = d.replace - } - nsrc += sz - ndst++ - } - - return ndst, nsrc, e -} diff --git a/vendor/github.com/gdamore/encoding/doc.go b/vendor/github.com/gdamore/encoding/doc.go deleted file mode 100644 index 8a7b48d7ee0..00000000000 --- a/vendor/github.com/gdamore/encoding/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2015 Garrett D'Amore -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package encoding provides a few of the encoding structures that are -// missing from the Go x/text/encoding tree. -package encoding diff --git a/vendor/github.com/gdamore/encoding/ebcdic.go b/vendor/github.com/gdamore/encoding/ebcdic.go deleted file mode 100644 index 8e13f1a97fd..00000000000 --- a/vendor/github.com/gdamore/encoding/ebcdic.go +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright 2015 Garrett D'Amore -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package encoding - -import ( - "golang.org/x/text/encoding" -) - -// EBCDIC represents the 8-bit EBCDIC scheme, found in some mainframe -// environments. If you don't know what this is, consider yourself lucky. -var EBCDIC encoding.Encoding - -func init() { - cm := &Charmap{ - ReplacementChar: '\x3f', - Map: map[byte]rune{ - // 0x00-0x03 match - 0x04: RuneError, - 0x05: '\t', - 0x06: RuneError, - 0x07: '\x7f', - 0x08: RuneError, - 0x09: RuneError, - 0x0a: RuneError, - // 0x0b-0x13 match - 0x14: RuneError, - 0x15: '\x85', // Not in any ISO code - 0x16: '\x08', - 0x17: RuneError, - // 0x18-0x19 match - 0x1a: RuneError, - 0x1b: RuneError, - // 0x1c-0x1f match - 0x20: RuneError, - 0x21: RuneError, - 0x22: RuneError, - 0x23: RuneError, - 0x24: RuneError, - 0x25: '\n', - 0x26: '\x17', - 0x27: '\x1b', - 0x28: RuneError, - 0x29: RuneError, - 0x2a: RuneError, - 0x2b: RuneError, - 0x2c: RuneError, - 0x2d: '\x05', - 0x2e: '\x06', - 0x2f: '\x07', - 0x30: RuneError, - 0x31: RuneError, - 0x32: '\x16', - 0x33: RuneError, - 0x34: RuneError, - 0x35: RuneError, - 0x36: RuneError, - 0x37: '\x04', - 0x38: RuneError, - 0x39: RuneError, - 0x3a: RuneError, - 0x3b: RuneError, - 0x3c: '\x14', - 0x3d: '\x15', - 0x3e: RuneError, - 0x3f: '\x1a', // also replacement char - 0x40: ' ', - 0x41: '\xa0', - 0x42: RuneError, - 0x43: RuneError, - 0x44: RuneError, - 0x45: RuneError, - 0x46: RuneError, - 0x47: RuneError, - 0x48: RuneError, - 0x49: RuneError, - 0x4a: RuneError, - 0x4b: '.', - 0x4c: '<', - 0x4d: '(', - 0x4e: '+', - 0x4f: '|', - 0x50: '&', - 0x51: RuneError, - 0x52: RuneError, - 0x53: RuneError, - 0x54: RuneError, - 0x55: RuneError, - 0x56: RuneError, - 0x57: RuneError, - 0x58: RuneError, - 0x59: RuneError, - 0x5a: '!', - 0x5b: '$', - 0x5c: '*', - 0x5d: ')', - 0x5e: ';', - 0x5f: '¬', - 0x60: '-', - 0x61: '/', - 0x62: RuneError, - 0x63: RuneError, - 0x64: RuneError, - 0x65: RuneError, - 0x66: RuneError, - 0x67: RuneError, - 0x68: RuneError, - 0x69: RuneError, - 0x6a: '¦', - 0x6b: ',', - 0x6c: '%', - 0x6d: '_', - 0x6e: '>', - 0x6f: '?', - 0x70: RuneError, - 0x71: RuneError, - 0x72: RuneError, - 0x73: RuneError, - 0x74: RuneError, - 0x75: RuneError, - 0x76: RuneError, - 0x77: RuneError, - 0x78: RuneError, - 0x79: '`', - 0x7a: ':', - 0x7b: '#', - 0x7c: '@', - 0x7d: '\'', - 0x7e: '=', - 0x7f: '"', - 0x80: RuneError, - 0x81: 'a', - 0x82: 'b', - 0x83: 'c', - 0x84: 'd', - 0x85: 'e', - 0x86: 'f', - 0x87: 'g', - 0x88: 'h', - 0x89: 'i', - 0x8a: RuneError, - 0x8b: RuneError, - 0x8c: RuneError, - 0x8d: RuneError, - 0x8e: RuneError, - 0x8f: '±', - 0x90: RuneError, - 0x91: 'j', - 0x92: 'k', - 0x93: 'l', - 0x94: 'm', - 0x95: 'n', - 0x96: 'o', - 0x97: 'p', - 0x98: 'q', - 0x99: 'r', - 0x9a: RuneError, - 0x9b: RuneError, - 0x9c: RuneError, - 0x9d: RuneError, - 0x9e: RuneError, - 0x9f: RuneError, - 0xa0: RuneError, - 0xa1: '~', - 0xa2: 's', - 0xa3: 't', - 0xa4: 'u', - 0xa5: 'v', - 0xa6: 'w', - 0xa7: 'x', - 0xa8: 'y', - 0xa9: 'z', - 0xaa: RuneError, - 0xab: RuneError, - 0xac: RuneError, - 0xad: RuneError, - 0xae: RuneError, - 0xaf: RuneError, - 0xb0: '^', - 0xb1: RuneError, - 0xb2: RuneError, - 0xb3: RuneError, - 0xb4: RuneError, - 0xb5: RuneError, - 0xb6: RuneError, - 0xb7: RuneError, - 0xb8: RuneError, - 0xb9: RuneError, - 0xba: '[', - 0xbb: ']', - 0xbc: RuneError, - 0xbd: RuneError, - 0xbe: RuneError, - 0xbf: RuneError, - 0xc0: '{', - 0xc1: 'A', - 0xc2: 'B', - 0xc3: 'C', - 0xc4: 'D', - 0xc5: 'E', - 0xc6: 'F', - 0xc7: 'G', - 0xc8: 'H', - 0xc9: 'I', - 0xca: '\xad', // NB: soft hyphen - 0xcb: RuneError, - 0xcc: RuneError, - 0xcd: RuneError, - 0xce: RuneError, - 0xcf: RuneError, - 0xd0: '}', - 0xd1: 'J', - 0xd2: 'K', - 0xd3: 'L', - 0xd4: 'M', - 0xd5: 'N', - 0xd6: 'O', - 0xd7: 'P', - 0xd8: 'Q', - 0xd9: 'R', - 0xda: RuneError, - 0xdb: RuneError, - 0xdc: RuneError, - 0xdd: RuneError, - 0xde: RuneError, - 0xdf: RuneError, - 0xe0: '\\', - 0xe1: '\u2007', // Non-breaking space - 0xe2: 'S', - 0xe3: 'T', - 0xe4: 'U', - 0xe5: 'V', - 0xe6: 'W', - 0xe7: 'X', - 0xe8: 'Y', - 0xe9: 'Z', - 0xea: RuneError, - 0xeb: RuneError, - 0xec: RuneError, - 0xed: RuneError, - 0xee: RuneError, - 0xef: RuneError, - 0xf0: '0', - 0xf1: '1', - 0xf2: '2', - 0xf3: '3', - 0xf4: '4', - 0xf5: '5', - 0xf6: '6', - 0xf7: '7', - 0xf8: '8', - 0xf9: '9', - 0xfa: RuneError, - 0xfb: RuneError, - 0xfc: RuneError, - 0xfd: RuneError, - 0xfe: RuneError, - 0xff: RuneError, - }} - cm.Init() - EBCDIC = cm -} diff --git a/vendor/github.com/gdamore/encoding/latin1.go b/vendor/github.com/gdamore/encoding/latin1.go deleted file mode 100644 index 226bf01d0f8..00000000000 --- a/vendor/github.com/gdamore/encoding/latin1.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2015 Garrett D'Amore -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package encoding - -import ( - "golang.org/x/text/encoding" -) - -// ISO8859_1 represents the 8-bit ISO8859-1 scheme. It decodes directly to -// UTF-8 without change, as all ISO8859-1 values are legal UTF-8. -// Unicode values less than 256 (i.e. 8 bits) map 1:1 with 8859-1. -// It encodes runes outside of that to 0x1A, the ASCII substitution character. -var ISO8859_1 encoding.Encoding - -func init() { - cm := &Charmap{} - cm.Init() - - // 8859-1 is the 8-bit identity map for Unicode. - ISO8859_1 = cm -} diff --git a/vendor/github.com/gdamore/encoding/latin5.go b/vendor/github.com/gdamore/encoding/latin5.go deleted file mode 100644 index c75ecf27534..00000000000 --- a/vendor/github.com/gdamore/encoding/latin5.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2015 Garrett D'Amore -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package encoding - -import ( - "golang.org/x/text/encoding" -) - -// ISO8859_9 represents the 8-bit ISO8859-9 scheme. -var ISO8859_9 encoding.Encoding - -func init() { - cm := &Charmap{Map: map[byte]rune{ - 0xD0: 'Ğ', - 0xDD: 'İ', - 0xDE: 'Ş', - 0xF0: 'ğ', - 0xFD: 'ı', - 0xFE: 'ş', - }} - cm.Init() - ISO8859_9 = cm -} diff --git a/vendor/github.com/gdamore/encoding/utf8.go b/vendor/github.com/gdamore/encoding/utf8.go deleted file mode 100644 index 2d59f4b39dd..00000000000 --- a/vendor/github.com/gdamore/encoding/utf8.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2015 Garrett D'Amore -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package encoding - -import ( - "golang.org/x/text/encoding" -) - -type validUtf8 struct{} - -// UTF8 is an encoding for UTF-8. All it does is verify that the UTF-8 -// in is valid. The main reason for its existence is that it will detect -// and report ErrSrcShort or ErrDstShort, whereas the Nop encoding just -// passes every byte, blithely. -var UTF8 encoding.Encoding = validUtf8{} - -func (validUtf8) NewDecoder() *encoding.Decoder { - return &encoding.Decoder{Transformer: encoding.UTF8Validator} -} - -func (validUtf8) NewEncoder() *encoding.Encoder { - return &encoding.Encoder{Transformer: encoding.UTF8Validator} -} diff --git a/vendor/github.com/gdamore/tcell/v2/.gitignore b/vendor/github.com/gdamore/tcell/v2/.gitignore deleted file mode 100644 index c57100a595c..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/.gitignore +++ /dev/null @@ -1 +0,0 @@ -coverage.txt diff --git a/vendor/github.com/gdamore/tcell/v2/AUTHORS b/vendor/github.com/gdamore/tcell/v2/AUTHORS deleted file mode 100644 index 53f87ee6391..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/AUTHORS +++ /dev/null @@ -1,4 +0,0 @@ -Garrett D'Amore -Zachary Yedidia -Junegunn Choi -Staysail Systems, Inc. diff --git a/vendor/github.com/gdamore/tcell/v2/CHANGESv2.md b/vendor/github.com/gdamore/tcell/v2/CHANGESv2.md deleted file mode 100644 index ad97c11b5b4..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/CHANGESv2.md +++ /dev/null @@ -1,82 +0,0 @@ -## Breaking Changes in _Tcell_ v2 - -A number of changes were made to _Tcell_ for version two, and some of these are breaking. - -### Import Path - -The import path for tcell has changed to `github.com/gdamore/tcell/v2` to reflect a new major version. - -### Style Is Not Numeric - -The type `Style` has changed to a structure, to allow us to add additional data such as flags for color setting, -more attribute bits, and so forth. -Applications that relied on this being a number will need to be updated to use the accessor methods. - -### Mouse Event Changes - -The middle mouse button was reported as button 2 on Linux, but as button 3 on Windows, -and the right mouse button was reported the reverse way. -_Tcell_ now always reports the right mouse button as button 2, and the middle button as button 3. -To help make this clearer, new symbols `ButtonPrimary`, `ButtonSecondary`, and -`ButtonMiddle` are provided. -(Note that which button is right vs. left may be impacted by user preferences. -Usually the left button will be considered the Primary, and the right will be the Secondary.) -Applications may need to adjust their handling of mouse buttons 2 and 3 accordingly. - -### Terminals Removed - -A number of terminals have been removed. -These are mostly ancient definitions unlikely to be used by anyone, such as `adm3a`. - -### High Number Function Keys - -Historically terminfo reported function keys with modifiers set as a different -function key altogether. For example, Shift-F1 was reported as F13 on XTerm. -_Tcell_ now prefers to report these using the base key (such as F1) with modifiers added. -This works on XTerm and VTE based emulators, but some emulators may not support this. -The new behavior more closely aligns with behavior on Windows platforms. - -## New Features in _Tcell_ v2 - -These features are not breaking, but are introduced in version 2. - -### Improved Modifier Support - -For terminals that appear to behave like the venerable XTerm, _tcell_ -automatically adds modifier reporting for ALT, CTRL, SHIFT, and META keys -when the terminal reports them. - -### Better Support for Palettes (Themes) - -When using a color by its name or palette entry, _Tcell_ now tries to -use that palette entry as is; this should avoid some inconsistency and respect -terminal themes correctly. - -When true fidelity to RGB values is needed, the new `TrueColor()` API can be used -to create a direct color, which bypasses the palette altogether. - -### Automatic TrueColor Detection - -For some terminals, if the `Tc` or `RGB` properties are present in terminfo, -_Tcell_ will automatically assume the terminal supports 24-bit color. - -### ColorReset - -A new color value, `ColorReset` can be used on the foreground or background -to reset the color the default used by the terminal. - -### tmux Support - -_Tcell_ now has improved support for tmux, when the `$TERM` variable is set to "tmux". - -### Strikethrough Support - -_Tcell_ has support for strikethrough when the terminal supports it, using the new `StrikeThrough()` API. - -### Bracketed Paste Support - -_Tcell_ provides the long requested capability to discriminate paste event by using the -bracketed-paste capability present in some terminals. This is automatically available on -terminals that support XTerm style mouse handling, but applications must opt-in to this -by using the new `EnablePaste()` function. A new `EventPaste` type of event will be -delivered when starting and finishing a paste operation. \ No newline at end of file diff --git a/vendor/github.com/gdamore/tcell/v2/LICENSE b/vendor/github.com/gdamore/tcell/v2/LICENSE deleted file mode 100644 index d6456956733..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/gdamore/tcell/v2/README-wasm.md b/vendor/github.com/gdamore/tcell/v2/README-wasm.md deleted file mode 100644 index 278bacad3de..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/README-wasm.md +++ /dev/null @@ -1,61 +0,0 @@ -# WASM for _Tcell_ - -You can build _Tcell_ project into a webpage by compiling it slightly differently. This will result in a _Tcell_ project you can embed into another html page, or use as a standalone page. - -## Building your project - -WASM needs special build flags in order to work. You can build it by executing -```sh -GOOS=js GOARCH=wasm go build -o yourfile.wasm -``` - -## Additional files - -You also need 5 other files in the same directory as the wasm. Four (`tcell.html`, `tcell.js`, `termstyle.css`, and `beep.wav`) are provided in the `webfiles` directory. The last one, `wasm_exec.js`, can be copied from GOROOT into the current directory by executing -```sh -cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./ -``` - -In `tcell.js`, you also need to change the constant -```js -const wasmFilePath = "yourfile.wasm" -``` -to the file you outputted to when building. - -## Displaying your project - -### Standalone - -You can see the project (with an white background around the terminal) by serving the directory. You can do this using any framework, including another golang project: - -```golang -// server.go - -package main - -import ( - "log" - "net/http" -) - -func main() { - log.Fatal(http.ListenAndServe(":8080", - http.FileServer(http.Dir("/path/to/dir/to/serve")), - )) -} - -``` - -To see the webpage with this example, you can type in `localhost:8080/tcell.html` into your browser while `server.go` is running. - -### Embedding -It is recommended to use an iframe if you want to embed the app into a webpage: -```html - -``` - -## Other considerations - -### Accessing files - -`io.Open(filename)` and other related functions for reading file systems do not work; use `http.Get(filename)` instead. \ No newline at end of file diff --git a/vendor/github.com/gdamore/tcell/v2/README.md b/vendor/github.com/gdamore/tcell/v2/README.md deleted file mode 100644 index 93ceb845f73..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/README.md +++ /dev/null @@ -1,238 +0,0 @@ - - -# Tcell - -_Tcell_ is a _Go_ package that provides a cell based view for text terminals, like _XTerm_. -It was inspired by _termbox_, but includes many additional improvements. - -[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) -[![Linux](https://img.shields.io/github/actions/workflow/status/gdamore/tcell/linux.yml?branch=main&logoColor=grey&logo=linux&label=)](https://github.com/gdamore/tcell/actions/workflows/linux.yml) -[![Windows](https://img.shields.io/github/actions/workflow/status/gdamore/tcell/windows.yml?branch=main&logoColor=grey&label=Windows)](https://github.com/gdamore/tcell/actions/workflows/windows.yml) -[![Web Assembly](https://img.shields.io/github/actions/workflow/status/gdamore/tcell/webasm.yml?branch=main&logoColor=grey&logo=webassembly&label=)](https://github.com/gdamore/tcell/actions/workflows/webasm.yml) -[![Apache License](https://img.shields.io/github/license/gdamore/tcell.svg?logoColor=silver&logo=opensourceinitiative&color=blue&label=)](https://github.com/gdamore/tcell/blob/master/LICENSE) -[![Docs](https://img.shields.io/badge/godoc-reference-blue.svg?label=&logo=go)](https://pkg.go.dev/github.com/gdamore/tcell/v2) -[![Discord](https://img.shields.io/discord/639503822733180969?label=&logo=discord)](https://discord.gg/urTTxDN) -[![Coverage](https://img.shields.io/codecov/c/github/gdamore/tcell?logoColor=grey&logo=codecov&label=)](https://codecov.io/gh/gdamore/tcell) -[![Go Report Card](https://goreportcard.com/badge/github.com/gdamore/tcell/v2)](https://goreportcard.com/report/github.com/gdamore/tcell/v2) -[![Latest Release](https://img.shields.io/github/v/release/gdamore/tcell.svg?logo=github&label=)](https://github.com/gdamore/tcell/releases) - -Please see [here](UKRAINE.md) for an important message for the people of Russia. - -NOTE: This is version 2 of _Tcell_. There are breaking changes relative to version 1. -Version 1.x remains available using the import `github.com/gdamore/tcell`. - -## Tutorial - -A brief, and still somewhat rough, [tutorial](TUTORIAL.md) is available. - -## Examples - -A number of example are posted up on our [Gallery](https://github.com/gdamore/tcell/wikis/Gallery/). - -Let us know if you want to add your masterpiece to the list! - -## Pure Go Terminfo Database - -_Tcell_ includes a full parser and expander for terminfo capability strings, -so that it can avoid hard coding escape strings for formatting. It also favors -portability, and includes support for all POSIX systems. - -The database is also flexible & extensible, and can be modified by either running -a program to build the entire database, or an entry for just a single terminal. - -## More Portable - -_Tcell_ is portable to a wide variety of systems, and is pure Go, without -any need for CGO. -_Tcell_ is believed to work with mainstream systems officially supported by golang. - -Following the Go support policy, _Tcell_ officially only supports the current ("stable") version of go, -and the version immediately prior ("oldstable"). This policy is necessary to make sure that we can -update dependencies to pick up security fixes and new features, and it allows us to adopt changes -(such as library and language features) that are only supported in newer versions of Go. - -## No Async IO - -_Tcell_ is able to operate without requiring `SIGIO` signals (unlike _termbox_), -or asynchronous I/O, and can instead use standard Go file objects and Go routines. -This means it should be safe, especially for -use with programs that use exec, or otherwise need to manipulate the tty streams. -This model is also much closer to idiomatic Go, leading to fewer surprises. - -## Rich Unicode & non-Unicode support - -_Tcell_ includes enhanced support for Unicode, including wide characters and -combining characters, provided your terminal can support them. -Note that -Windows terminals generally don't support the full Unicode repertoire. - -It will also convert to and from Unicode locales, so that the program -can work with UTF-8 internally, and get reasonable output in other locales. -_Tcell_ tries hard to convert to native characters on both input and output. -On output _Tcell_ even makes use of the alternate character set to facilitate -drawing certain characters. - -## More Function Keys - -_Tcell_ also has richer support for a larger number of special keys that some -terminals can send. - -## Better Color Handling - -_Tcell_ will respect your terminal's color space as specified within your terminfo entries. -For example attempts to emit color sequences on VT100 terminals -won't result in unintended consequences. - -_Tcell_ maps 16 colors down to 8, for terminals that need it. -(The upper 8 colors are just brighter versions of the lower 8.) - -## Better Mouse Support - -_Tcell_ supports enhanced mouse tracking mode, so your application can receive -regular mouse motion events, and wheel events, if your terminal supports it. - -## _Termbox_ Compatibility - -A compatibility layer for _termbox_ is provided in the `compat` directory. -To use it, try importing `github.com/gdamore/tcell/termbox` instead. -Most _termbox-go_ programs will probably work without further modification. - -## Working With Unicode - -Internally _Tcell_ uses UTF-8, just like Go. -However, _Tcell_ understands how to -convert to and from other character sets, using the capabilities of -the `golang.org/x/text/encoding` packages. -Your application must supply -them, as the full set of the most common ones bloats the program by about 2 MB. -If you're lazy, and want them all anyway, see the `encoding` sub-directory. - -## Wide & Combining Characters - -The `SetContent()` API takes a primary rune, and an optional list of combining runes. -If any of the runes is a wide (East Asian) rune occupying two cells, -then the library will skip output from the following cell. Care must be -taken in the application to avoid explicitly attempting to set content in the -next cell, otherwise the results are undefined. (Normally the wide character -is displayed, and the other character is not; do not depend on that behavior.) - -## Colors - -_Tcell_ assumes the ANSI/XTerm color model, including the 256 color map that -XTerm uses when it supports 256 colors. The terminfo guidance will be -honored, with respect to the number of colors supported. Also, only -terminals which expose ANSI style `setaf` and `setab` will support color; -if you have a color terminal that only has `setf` and `setb`, please submit -a ticket. - -## 24-bit Color - -_Tcell_ _supports 24-bit color!_ (That is, if your terminal can support it.) - -NOTE: Technically the approach of using 24-bit RGB values for color is more -accurately described as "direct color", but most people use the term "true color". -We follow the (inaccurate) common convention. - -There are a few ways you can enable (or disable) true color. - -- For many terminals, we can detect it automatically if your terminal - includes the `RGB` or `Tc` capabilities (or rather it did when the database - was updated.) - -- You can force this one by setting the `COLORTERM` environment variable to - `24-bit`, `truecolor` or `24bit`. This is the same method used - by most other terminal applications that support 24-bit color. - -- If you set your `TERM` environment variable to a value with the suffix `-truecolor` - then 24-bit color compatible with XTerm and ECMA-48 will be assumed. - (This feature is deprecated. - It is recommended to use one of other methods listed above.) - -- You can disable 24-bit color by setting `TCELL_TRUECOLOR=disable` in your - environment. - -When using TrueColor, programs will display the colors that the programmer -intended, overriding any "`themes`" you may have set in your terminal -emulator. (For some cases, accurate color fidelity is more important -than respecting themes. For other cases, such as typical text apps that -only use a few colors, its more desirable to respect the themes that -the user has established.) - -## Performance - -Reasonable attempts have been made to minimize sending data to terminals, -avoiding repeated sequences or drawing the same cell on refresh updates. - -## Terminfo - -(Not relevant for Windows users.) - -The Terminfo implementation operates with a built-in database. -This should satisfy most users. However, it can also (on systems -with ncurses installed), dynamically parse the output from `infocmp` -for terminals it does not already know about. - -See the `terminfo/` directory for more information about generating -new entries for the built-in database. - -_Tcell_ requires that the terminal support the `cup` mode of cursor addressing. -Ancient terminals without the ability to position the cursor directly -are not supported. -This is unlikely to be a problem; such terminals have not been mass-produced -since the early 1970s. - -## Mouse Support - -Mouse support is detected via the `kmous` terminfo variable, however, -enablement/disablement and decoding mouse events is done using hard coded -sequences based on the XTerm X11 model. All popular -terminals with mouse tracking support this model. (Full terminfo support -is not possible as terminfo sequences are not defined.) - -On Windows, the mouse works normally. - -Mouse wheel buttons on various terminals are known to work, but the support -in terminal emulators, as well as support for various buttons and -live mouse tracking, varies widely. -Modern _xterm_, macOS _Terminal_, and _iTerm_ all work well. - -## Bracketed Paste - -Terminals that appear to support the XTerm mouse model also can support -bracketed paste, for applications that opt-in. See `EnablePaste()` for details. - -## Testability - -There is a `SimulationScreen`, that can be used to simulate a real screen -for automated testing. The supplied tests do this. The simulation contains -event delivery, screen resizing support, and capabilities to inject events -and examine "`physical`" screen contents. - -## Platforms - -### POSIX (Linux, FreeBSD, macOS, Solaris, etc.) - -Everything works using pure Go on mainstream platforms. Some more esoteric -platforms (e.g., AIX) may need to be added. Pull requests are welcome! - -### Windows - -Windows console mode applications are supported. - -Modern console applications like ConEmu and the Windows Terminal, -support all the good features (resize, mouse tracking, etc.) - -### WASM - -WASM is supported, but needs additional setup detailed in [README-wasm](README-wasm.md). - -### Plan9 and its variants - -Plan 9 is supported on a limited basis. The Plan 9 backend opens `/dev/cons` for I/O, enables raw mode by writing `rawon`/`rawoff` to `/dev/consctl`, watches `/dev/wctl` for resize notifications, and then constructs a **terminfo-backed** `Screen` (so `NewScreen` works as on other platforms). Typical usage is inside `vt(1)` with `TERM=vt100`. Expect **monochrome text** and **no mouse reporting** under stock `vt(1)` (it generally does not emit ANSI color or xterm mouse sequences). If a Plan 9 terminal supplies ANSI color escape sequences and xterm-style mouse reporting, color can be picked up via **terminfo** and mouse support could be added by wiring those sequences into the Plan 9 TTY path; contributions that improve terminal detection and broaden feature support are welcome. - -### Commercial Support - -_Tcell_ is absolutely free, but if you want to obtain commercial, professional support, there are options. - -- [TideLift](https://tidelift.com/) subscriptions include support for _Tcell_, as well as many other open source packages. -- [Staysail Systems Inc.](mailto:info@staysail.tech) offers direct support, and custom development around _Tcell_ on an hourly basis. diff --git a/vendor/github.com/gdamore/tcell/v2/SECURITY.md b/vendor/github.com/gdamore/tcell/v2/SECURITY.md deleted file mode 100644 index 5c0aa5ab4d2..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/SECURITY.md +++ /dev/null @@ -1,15 +0,0 @@ -# SECURITY - -It's somewhat unlikely that tcell is in a security sensitive path, -but we do take security seriously. - -## Vulnerabilityu Response - -If you report a vulnerability, we will respond within 2 business days. - -## Report a Vulnerability - -If you wish to report a vulnerability found in tcell, simply send a message -to garrett@damore.org. You may also reach us on our discord channel - -https://discord.gg/urTTxDN - a private message to `gdamore` on that channel -may be submitted instead of mail. diff --git a/vendor/github.com/gdamore/tcell/v2/TUTORIAL.md b/vendor/github.com/gdamore/tcell/v2/TUTORIAL.md deleted file mode 100644 index f52fcff0d40..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/TUTORIAL.md +++ /dev/null @@ -1,313 +0,0 @@ -# _Tcell_ Tutorial - -_Tcell_ provides a low-level, portable API for building terminal-based programs. -A [terminal emulator](https://en.wikipedia.org/wiki/Terminal_emulator) -(or a real terminal such as a DEC VT-220) is used to interact with such a program. - -_Tcell_'s interface is fairly low-level. -While it provides a reasonably portable way of dealing with all the usual terminal -features, it may be easier to utilize a higher level framework. -A number of such frameworks are listed on the _Tcell_ main [README](README.md). - -This tutorial provides the details of _Tcell_, and is appropriate for developers -wishing to create their own application frameworks or needing more direct access -to the terminal capabilities. - -## Resize events - -Applications receive an event of type `EventResize` when they are first initialized and each time the terminal is resized. -The new size is available as `Size`. - -```go -switch ev := ev.(type) { -case *tcell.EventResize: - w, h := ev.Size() - logMessage(fmt.Sprintf("Resized to %dx%d", w, h)) -} -``` - -## Key events - -When a key is pressed, applications receive an event of type `EventKey`. -This event describes the modifier keys pressed (if any) and the pressed key or rune. - -When a rune key is pressed, an event with its `Key` set to `KeyRune` is dispatched. - -When a non-rune key is pressed, it is available as the `Key` of the event. - -```go -switch ev := ev.(type) { -case *tcell.EventKey: - mod, key, ch := ev.Mod(), ev.Key(), ev.Rune() - logMessage(fmt.Sprintf("EventKey Modifiers: %d Key: %d Rune: %d", mod, key, ch)) -} -``` - -### Key event restrictions - -Terminal-based programs have less visibility into keyboard activity than graphical applications. - -When a key is pressed and held, additional key press events are sent by the terminal emulator. -The rate of these repeated events depends on the emulator's configuration. -Key release events are not available. - -It is not possible to distinguish runes typed while holding shift and runes typed using caps lock. -Capital letters are reported without the Shift modifier. - -## Mouse events - -Applications receive an event of type `EventMouse` when the mouse moves, or a mouse button is pressed or released. -Mouse events are only delivered if -`EnableMouse` has been called. - -The mouse buttons being pressed (if any) are available as `Buttons`, and the position of the mouse is available as `Position`. - -```go -switch ev := ev.(type) { -case *tcell.EventMouse: - mod := ev.Modifiers() - btns := ev.Buttons() - x, y := ev.Position() - logMessage(fmt.Sprintf("EventMouse Modifiers: %d Buttons: %d Position: %d,%d", mod, btns, x, y)) -} -``` - -### Mouse buttons - -Identifier | Alias | Description ------------|-----------------|----------- -Button1 | ButtonPrimary | Left button -Button2 | ButtonSecondary | Right button -Button3 | ButtonMiddle | Middle button -Button4 | | Side button (thumb/next) -Button5 | | Side button (thumb/prev) -WheelUp | | Scroll wheel up -WheelDown | | Scroll wheel down -WheelLeft | | Horizontal wheel left -WheelRight | | Horizontal wheel right - -## Usage - -To create a _Tcell_ application, first initialize a screen to hold it. - -```go -s, err := tcell.NewScreen() -if err != nil { - log.Fatalf("%+v", err) -} -if err := s.Init(); err != nil { - log.Fatalf("%+v", err) -} - -// Set default text style -defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset) -s.SetStyle(defStyle) - -// Clear screen -s.Clear() -``` - -Text may be drawn on the screen using `SetContent`. - -```go -s.SetContent(0, 0, 'H', nil, defStyle) -s.SetContent(1, 0, 'i', nil, defStyle) -s.SetContent(2, 0, '!', nil, defStyle) -``` - -To draw text more easily, define a render function. - -```go -func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) { - row := y1 - col := x1 - for _, r := range []rune(text) { - s.SetContent(col, row, r, nil, style) - col++ - if col >= x2 { - row++ - col = x1 - } - if row > y2 { - break - } - } -} -``` - -Lastly, define an event loop to handle user input and update application state. - -```go -quit := func() { - s.Fini() - os.Exit(0) -} -for { - // Update screen - s.Show() - - // Poll event - ev := s.PollEvent() - - // Process event - switch ev := ev.(type) { - case *tcell.EventResize: - s.Sync() - case *tcell.EventKey: - if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC { - quit() - } - } -} -``` - -## Demo application - -The following demonstrates how to initialize a screen, draw text/graphics and handle user input. - -```go -package main - -import ( - "fmt" - "log" - - "github.com/gdamore/tcell/v2" -) - -func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) { - row := y1 - col := x1 - for _, r := range []rune(text) { - s.SetContent(col, row, r, nil, style) - col++ - if col >= x2 { - row++ - col = x1 - } - if row > y2 { - break - } - } -} - -func drawBox(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) { - if y2 < y1 { - y1, y2 = y2, y1 - } - if x2 < x1 { - x1, x2 = x2, x1 - } - - // Fill background - for row := y1; row <= y2; row++ { - for col := x1; col <= x2; col++ { - s.SetContent(col, row, ' ', nil, style) - } - } - - // Draw borders - for col := x1; col <= x2; col++ { - s.SetContent(col, y1, tcell.RuneHLine, nil, style) - s.SetContent(col, y2, tcell.RuneHLine, nil, style) - } - for row := y1 + 1; row < y2; row++ { - s.SetContent(x1, row, tcell.RuneVLine, nil, style) - s.SetContent(x2, row, tcell.RuneVLine, nil, style) - } - - // Only draw corners if necessary - if y1 != y2 && x1 != x2 { - s.SetContent(x1, y1, tcell.RuneULCorner, nil, style) - s.SetContent(x2, y1, tcell.RuneURCorner, nil, style) - s.SetContent(x1, y2, tcell.RuneLLCorner, nil, style) - s.SetContent(x2, y2, tcell.RuneLRCorner, nil, style) - } - - drawText(s, x1+1, y1+1, x2-1, y2-1, style, text) -} - -func main() { - defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset) - boxStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorPurple) - - // Initialize screen - s, err := tcell.NewScreen() - if err != nil { - log.Fatalf("%+v", err) - } - if err := s.Init(); err != nil { - log.Fatalf("%+v", err) - } - s.SetStyle(defStyle) - s.EnableMouse() - s.EnablePaste() - s.Clear() - - // Draw initial boxes - drawBox(s, 1, 1, 42, 7, boxStyle, "Click and drag to draw a box") - drawBox(s, 5, 9, 32, 14, boxStyle, "Press C to reset") - - quit := func() { - // You have to catch panics in a defer, clean up, and - // re-raise them - otherwise your application can - // die without leaving any diagnostic trace. - maybePanic := recover() - s.Fini() - if maybePanic != nil { - panic(maybePanic) - } - } - defer quit() - - // Here's how to get the screen size when you need it. - // xmax, ymax := s.Size() - - // Here's an example of how to inject a keystroke where it will - // be picked up by the next PollEvent call. Note that the - // queue is LIFO, it has a limited length, and PostEvent() can - // return an error. - // s.PostEvent(tcell.NewEventKey(tcell.KeyRune, rune('a'), 0)) - - // Event loop - ox, oy := -1, -1 - for { - // Update screen - s.Show() - - // Poll event - ev := s.PollEvent() - - // Process event - switch ev := ev.(type) { - case *tcell.EventResize: - s.Sync() - case *tcell.EventKey: - if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC { - return - } else if ev.Key() == tcell.KeyCtrlL { - s.Sync() - } else if ev.Rune() == 'C' || ev.Rune() == 'c' { - s.Clear() - } - case *tcell.EventMouse: - x, y := ev.Position() - - switch ev.Buttons() { - case tcell.Button1, tcell.Button2: - if ox < 0 { - ox, oy = x, y // record location when click started - } - - case tcell.ButtonNone: - if ox >= 0 { - label := fmt.Sprintf("%d,%d to %d,%d", ox, oy, x, y) - drawBox(s, ox, oy, x, y, boxStyle, label) - ox, oy = -1, -1 - } - } - } - } -} -``` - diff --git a/vendor/github.com/gdamore/tcell/v2/UKRAINE.md b/vendor/github.com/gdamore/tcell/v2/UKRAINE.md deleted file mode 100644 index d86d3e126dd..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/UKRAINE.md +++ /dev/null @@ -1,77 +0,0 @@ -# Ukraine, Russia, and a World Tragedy - -## A message to those inside Russia - -### Written March 4, 2022. - -It is with a very heavy heart that I write this. I am normally opposed to the use of open source -projects to communicate political positions or advocate for things outside the immediate relevancy -to that project. - -However, the events occurring in Ukraine, and specifically the unprecedented invasion of Ukraine by -Russian forces operating under orders from Russian President Vladimir Putin compel me to speak out. - -Those who know me, know that I have family, friends, and colleagues in Russia, and Ukraine both. My closest friends -have historically been Russian friends my wife's hometown of Chelyabinsk. I myself have in the past -frequently traveled to Russia, and indeed operated a software development firm with offices in St. Petersburg. -I had a special kinship with Russia and its people. - -I say "had", because I fear that the actions of Putin, and the massive disinformation campaign that his regime -has waged inside Russia, mean that it's likely that I won't see those friends again. At present, I'm not sure -my wife will see her own mother again. We no longer feel it's safe for either of us to return Russia given -actions taken by the regime to crack down on those who express disagreement. - -Russian citizens are being led to believe it is acting purely defensively, and that only legitimate military -targets are being targeted, and that all the information we have received in the West are fakes. - -I am confident that nothing could be further from the truth. - -This has caused many in Russia, including people whom I respect and believe to be smarter than this, to -stand by Putin, and endorse his actions. The claim is that the entirety of NATO is operating at the behest -of the USA, and that the entirety of Europe was poised to attack Russia. While this is clearly absurd to those -of us with any understanding of western politics, Russian citizens are being fed this lie, and believing it. - -If you're reading this from inside Russia -- YOU are the person that I hope this message reaches. Your -government is LYING to you. Of course, all governments lie all the time. But consider this. Almost the -entire world has condemned the invasion of Ukraine as criminal, and has applied sanctions. Even countries -which have poor relations with the US sanctioning Russia, as well as nations which historically have remained -neutral. (Famously neutral -- even during World War II, Switzerland has acted to apply sanctions in -concert with the rest of the world.) - -Ask yourself, why does Putin fear a free press so much, if what he says is true? Why the crack-downs on -children expressing only a desire for peace with Ukraine? Why would the entire world unified against him, -if Putin was in the right? Why would the only countries that stood with Russia against -the UN resolution to condemn these acts as crimes be Belarus, North Korea, and Syria? Even countries normally -allied to Russia could not bring themselves to do more than abstain from the vote to condemn it. - -To be clear, I do not claim that the actions taken by the West or by the Ukrainian government were completely -blameless. On the contrary, I understand that Western media is biased, and the truth is rarely exactly -as reported. I believe that there is a kernel of truth in the claims of fascists and ultra-nationalist -militias operating in Ukraine and specifically Donbas. However, I am also equally certain that Putin's -response is out of proportion, and that concerns about such militias are principally just a pretext to justify -an invasion. - -Europe is at war, unlike we've seen in my lifetime. The world is more divided, and closer to nuclear holocaust -than it has been since the Cold War. And that is 100% the fault of Putin. - -While Putin remains in power, there cannot really be any way for Russian international relations to return -to normal. Putin has set your country on a path to return to the Cold War, likely because he fancies himself -to be a new Stalin. However, unlike the Soviet Union, the Russian economy does not have the wherewithal to -stand on its own, and the invasion of Ukraine has fully ensured that Russia will not find any friends anywhere -else in Europe, and probably few places in Asia. - -The *only* paths forward for Russia are either a Russia without Putin (and those who would support his agenda), -or a complete breakdown of Russian prosperity, likely followed by the increasing international conflict that will -be the natural escalation from a country that is isolated and impoverished. Those of us observing from the West are -gravely concerned, because we cannot see any end to this madness that does not result in nuclear conflict, -unless from within. - -In the meantime, the worst prices will be paid for by innocents in Ukraine, and by young Russian mean -forced to carry out the orders of Putin's corrupt regime. - -And *that* is why I write this -- to appeal to those within Russia to open your eyes, and think with -your minds. It is right and proper to be proud of your country and its rich heritage. But it is also -right and proper to look for ways to save it from the ruinous path that its current leadership has set it upon, -and to recognize when that leadership is no longer acting in interest of the country or its people. - - - Garrett D'Amore, March 4, 2022 \ No newline at end of file diff --git a/vendor/github.com/gdamore/tcell/v2/attr.go b/vendor/github.com/gdamore/tcell/v2/attr.go deleted file mode 100644 index 05af5e5d7a7..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/attr.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2024 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -// AttrMask represents a mask of text attributes, apart from color. -// Note that support for attributes may vary widely across terminals. -type AttrMask uint - -// Attributes are not colors, but affect the display of text. They can -// be combined, in some cases, but not others. (E.g. you can have Dim Italic, -// but only CurlyUnderline cannot be mixed with DottedUnderline.) -const ( - AttrBold AttrMask = 1 << iota - AttrBlink - AttrReverse - AttrUnderline // Deprecated: Use UnderlineStyle - AttrDim - AttrItalic - AttrStrikeThrough - AttrInvalid AttrMask = 1 << 31 // Mark the style or attributes invalid - AttrNone AttrMask = 0 // Just normal text. -) diff --git a/vendor/github.com/gdamore/tcell/v2/cell.go b/vendor/github.com/gdamore/tcell/v2/cell.go deleted file mode 100644 index 43faedb3234..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/cell.go +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2024 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "os" - "reflect" - - runewidth "github.com/mattn/go-runewidth" -) - -type cell struct { - currMain rune - currComb []rune - currStyle Style - lastMain rune - lastStyle Style - lastComb []rune - width int - lock bool -} - -// CellBuffer represents a two-dimensional array of character cells. -// This is primarily intended for use by Screen implementors; it -// contains much of the common code they need. To create one, just -// declare a variable of its type; no explicit initialization is necessary. -// -// CellBuffer is not thread safe. -type CellBuffer struct { - w int - h int - cells []cell -} - -// SetContent sets the contents (primary rune, combining runes, -// and style) for a cell at a given location. If the background or -// foreground of the style is set to ColorNone, then the respective -// color is left un changed. -func (cb *CellBuffer) SetContent(x int, y int, - mainc rune, combc []rune, style Style, -) { - if x >= 0 && y >= 0 && x < cb.w && y < cb.h { - c := &cb.cells[(y*cb.w)+x] - - // Wide characters: we want to mark the "wide" cells - // dirty as well as the base cell, to make sure we consider - // both cells as dirty together. We only need to do this - // if we're changing content - if (c.width > 0) && (mainc != c.currMain || len(combc) != len(c.currComb) || (len(combc) > 0 && !reflect.DeepEqual(combc, c.currComb))) { - for i := 0; i < c.width; i++ { - cb.SetDirty(x+i, y, true) - } - } - - c.currComb = append([]rune{}, combc...) - - if c.currMain != mainc { - c.width = runewidth.RuneWidth(mainc) - } - c.currMain = mainc - if style.fg == ColorNone { - style.fg = c.currStyle.fg - } - if style.bg == ColorNone { - style.bg = c.currStyle.bg - } - c.currStyle = style - } -} - -// GetContent returns the contents of a character cell, including the -// primary rune, any combining character runes (which will usually be -// nil), the style, and the display width in cells. (The width can be -// either 1, normally, or 2 for East Asian full-width characters.) -func (cb *CellBuffer) GetContent(x, y int) (rune, []rune, Style, int) { - var mainc rune - var combc []rune - var style Style - var width int - if x >= 0 && y >= 0 && x < cb.w && y < cb.h { - c := &cb.cells[(y*cb.w)+x] - mainc, combc, style = c.currMain, c.currComb, c.currStyle - if width = c.width; width == 0 || mainc < ' ' { - width = 1 - mainc = ' ' - } - } - return mainc, combc, style, width -} - -// Size returns the (width, height) in cells of the buffer. -func (cb *CellBuffer) Size() (int, int) { - return cb.w, cb.h -} - -// Invalidate marks all characters within the buffer as dirty. -func (cb *CellBuffer) Invalidate() { - for i := range cb.cells { - cb.cells[i].lastMain = rune(0) - } -} - -// Dirty checks if a character at the given location needs to be -// refreshed on the physical display. This returns true if the cell -// content is different since the last time it was marked clean. -func (cb *CellBuffer) Dirty(x, y int) bool { - if x >= 0 && y >= 0 && x < cb.w && y < cb.h { - c := &cb.cells[(y*cb.w)+x] - if c.lock { - return false - } - if c.lastMain == rune(0) { - return true - } - if c.lastMain != c.currMain { - return true - } - if c.lastStyle != c.currStyle { - return true - } - if len(c.lastComb) != len(c.currComb) { - return true - } - for i := range c.lastComb { - if c.lastComb[i] != c.currComb[i] { - return true - } - } - } - return false -} - -// SetDirty is normally used to indicate that a cell has -// been displayed (in which case dirty is false), or to manually -// force a cell to be marked dirty. -func (cb *CellBuffer) SetDirty(x, y int, dirty bool) { - if x >= 0 && y >= 0 && x < cb.w && y < cb.h { - c := &cb.cells[(y*cb.w)+x] - if dirty { - c.lastMain = rune(0) - } else { - if c.currMain == rune(0) { - c.currMain = ' ' - } - c.lastMain = c.currMain - c.lastComb = c.currComb - c.lastStyle = c.currStyle - } - } -} - -// LockCell locks a cell from being drawn, effectively marking it "clean" until -// the lock is removed. This can be used to prevent tcell from drawing a given -// cell, even if the underlying content has changed. For example, when drawing a -// sixel graphic directly to a TTY screen an implementer must lock the region -// underneath the graphic to prevent tcell from drawing on top of the graphic. -func (cb *CellBuffer) LockCell(x, y int) { - if x < 0 || y < 0 { - return - } - if x >= cb.w || y >= cb.h { - return - } - c := &cb.cells[(y*cb.w)+x] - c.lock = true -} - -// UnlockCell removes a lock from the cell and marks it as dirty -func (cb *CellBuffer) UnlockCell(x, y int) { - if x < 0 || y < 0 { - return - } - if x >= cb.w || y >= cb.h { - return - } - c := &cb.cells[(y*cb.w)+x] - c.lock = false - cb.SetDirty(x, y, true) -} - -// Resize is used to resize the cells array, with different dimensions, -// while preserving the original contents. The cells will be invalidated -// so that they can be redrawn. -func (cb *CellBuffer) Resize(w, h int) { - if cb.h == h && cb.w == w { - return - } - - newc := make([]cell, w*h) - for y := 0; y < h && y < cb.h; y++ { - for x := 0; x < w && x < cb.w; x++ { - oc := &cb.cells[(y*cb.w)+x] - nc := &newc[(y*w)+x] - nc.currMain = oc.currMain - nc.currComb = oc.currComb - nc.currStyle = oc.currStyle - nc.width = oc.width - nc.lastMain = rune(0) - } - } - cb.cells = newc - cb.h = h - cb.w = w -} - -// Fill fills the entire cell buffer array with the specified character -// and style. Normally choose ' ' to clear the screen. This API doesn't -// support combining characters, or characters with a width larger than one. -// If either the foreground or background are ColorNone, then the respective -// color is unchanged. -func (cb *CellBuffer) Fill(r rune, style Style) { - for i := range cb.cells { - c := &cb.cells[i] - c.currMain = r - c.currComb = nil - cs := style - if cs.fg == ColorNone { - cs.fg = c.currStyle.fg - } - if cs.bg == ColorNone { - cs.bg = c.currStyle.bg - } - c.currStyle = cs - c.width = 1 - } -} - -var runeConfig *runewidth.Condition - -func init() { - // The defaults for the runewidth package are poorly chosen for terminal - // applications. We however will honor the setting in the environment if - // it is set. - if os.Getenv("RUNEWIDTH_EASTASIAN") == "" { - runewidth.DefaultCondition.EastAsianWidth = false - } -} diff --git a/vendor/github.com/gdamore/tcell/v2/charset_plan9.go b/vendor/github.com/gdamore/tcell/v2/charset_plan9.go deleted file mode 100644 index 959d181e158..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/charset_plan9.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build plan9 -// +build plan9 - -// Copyright 2025 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -// Plan 9 uses UTF-8 system-wide, so we return "UTF-8" unconditionally. -func getCharset() string { - return "UTF-8" -} diff --git a/vendor/github.com/gdamore/tcell/v2/charset_stub.go b/vendor/github.com/gdamore/tcell/v2/charset_stub.go deleted file mode 100644 index 829be2c2616..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/charset_stub.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build nacl -// +build nacl - -// Copyright 2015 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -func getCharset() string { - return "" -} diff --git a/vendor/github.com/gdamore/tcell/v2/charset_unix.go b/vendor/github.com/gdamore/tcell/v2/charset_unix.go deleted file mode 100644 index 8bbf1f5ed30..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/charset_unix.go +++ /dev/null @@ -1,50 +0,0 @@ -//go:build !windows && !nacl && !plan9 -// +build !windows,!nacl,!plan9 - -// Copyright 2016 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "os" - "strings" -) - -func getCharset() string { - // Determine the character set. This can help us later. - // Per POSIX, we search for LC_ALL first, then LC_CTYPE, and - // finally LANG. First one set wins. - locale := "" - if locale = os.Getenv("LC_ALL"); locale == "" { - if locale = os.Getenv("LC_CTYPE"); locale == "" { - locale = os.Getenv("LANG") - } - } - if locale == "POSIX" || locale == "C" { - return "US-ASCII" - } - if i := strings.IndexRune(locale, '@'); i >= 0 { - locale = locale[:i] - } - if i := strings.IndexRune(locale, '.'); i >= 0 { - locale = locale[i+1:] - } else { - // Default assumption, and on Linux we can see LC_ALL - // without a character set, which we assume implies UTF-8. - return "UTF-8" - } - // XXX: add support for aliases - return locale -} diff --git a/vendor/github.com/gdamore/tcell/v2/charset_windows.go b/vendor/github.com/gdamore/tcell/v2/charset_windows.go deleted file mode 100644 index 08068a02b8f..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/charset_windows.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build windows -// +build windows - -// Copyright 2015 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -func getCharset() string { - return "UTF-16" -} diff --git a/vendor/github.com/gdamore/tcell/v2/color.go b/vendor/github.com/gdamore/tcell/v2/color.go deleted file mode 100644 index 904848eaa6f..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/color.go +++ /dev/null @@ -1,1128 +0,0 @@ -// Copyright 2023 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "fmt" - ic "image/color" - "strconv" -) - -// Color represents a color. The low numeric values are the same as used -// by ECMA-48, and beyond that XTerm. A 24-bit RGB value may be used by -// adding in the ColorIsRGB flag. For Color names we use the W3C approved -// color names. -// -// We use a 64-bit integer to allow future expansion if we want to add an -// 8-bit alpha, while still leaving us some room for extra options. -// -// Note that on various terminals colors may be approximated however, or -// not supported at all. If no suitable representation for a color is known, -// the library will simply not set any color, deferring to whatever default -// attributes the terminal uses. -type Color uint64 - -const ( - // ColorDefault is used to leave the Color unchanged from whatever - // system or terminal default may exist. It's also the zero value. - ColorDefault Color = 0 - - // ColorValid is used to indicate the color value is actually - // valid (initialized). This is useful to permit the zero value - // to be treated as the default. - ColorValid Color = 1 << 32 - - // ColorIsRGB is used to indicate that the numeric value is not - // a known color constant, but rather an RGB value. The lower - // order 3 bytes are RGB. - ColorIsRGB Color = 1 << 33 - - // ColorSpecial is a flag used to indicate that the values have - // special meaning, and live outside of the color space(s). - ColorSpecial Color = 1 << 34 -) - -// Note that the order of these options is important -- it follows the -// definitions used by ECMA and XTerm. Hence any further named colors -// must begin at a value not less than 256. -const ( - ColorBlack = ColorValid + iota - ColorMaroon - ColorGreen - ColorOlive - ColorNavy - ColorPurple - ColorTeal - ColorSilver - ColorGray - ColorRed - ColorLime - ColorYellow - ColorBlue - ColorFuchsia - ColorAqua - ColorWhite - Color16 - Color17 - Color18 - Color19 - Color20 - Color21 - Color22 - Color23 - Color24 - Color25 - Color26 - Color27 - Color28 - Color29 - Color30 - Color31 - Color32 - Color33 - Color34 - Color35 - Color36 - Color37 - Color38 - Color39 - Color40 - Color41 - Color42 - Color43 - Color44 - Color45 - Color46 - Color47 - Color48 - Color49 - Color50 - Color51 - Color52 - Color53 - Color54 - Color55 - Color56 - Color57 - Color58 - Color59 - Color60 - Color61 - Color62 - Color63 - Color64 - Color65 - Color66 - Color67 - Color68 - Color69 - Color70 - Color71 - Color72 - Color73 - Color74 - Color75 - Color76 - Color77 - Color78 - Color79 - Color80 - Color81 - Color82 - Color83 - Color84 - Color85 - Color86 - Color87 - Color88 - Color89 - Color90 - Color91 - Color92 - Color93 - Color94 - Color95 - Color96 - Color97 - Color98 - Color99 - Color100 - Color101 - Color102 - Color103 - Color104 - Color105 - Color106 - Color107 - Color108 - Color109 - Color110 - Color111 - Color112 - Color113 - Color114 - Color115 - Color116 - Color117 - Color118 - Color119 - Color120 - Color121 - Color122 - Color123 - Color124 - Color125 - Color126 - Color127 - Color128 - Color129 - Color130 - Color131 - Color132 - Color133 - Color134 - Color135 - Color136 - Color137 - Color138 - Color139 - Color140 - Color141 - Color142 - Color143 - Color144 - Color145 - Color146 - Color147 - Color148 - Color149 - Color150 - Color151 - Color152 - Color153 - Color154 - Color155 - Color156 - Color157 - Color158 - Color159 - Color160 - Color161 - Color162 - Color163 - Color164 - Color165 - Color166 - Color167 - Color168 - Color169 - Color170 - Color171 - Color172 - Color173 - Color174 - Color175 - Color176 - Color177 - Color178 - Color179 - Color180 - Color181 - Color182 - Color183 - Color184 - Color185 - Color186 - Color187 - Color188 - Color189 - Color190 - Color191 - Color192 - Color193 - Color194 - Color195 - Color196 - Color197 - Color198 - Color199 - Color200 - Color201 - Color202 - Color203 - Color204 - Color205 - Color206 - Color207 - Color208 - Color209 - Color210 - Color211 - Color212 - Color213 - Color214 - Color215 - Color216 - Color217 - Color218 - Color219 - Color220 - Color221 - Color222 - Color223 - Color224 - Color225 - Color226 - Color227 - Color228 - Color229 - Color230 - Color231 - Color232 - Color233 - Color234 - Color235 - Color236 - Color237 - Color238 - Color239 - Color240 - Color241 - Color242 - Color243 - Color244 - Color245 - Color246 - Color247 - Color248 - Color249 - Color250 - Color251 - Color252 - Color253 - Color254 - Color255 - ColorAliceBlue = ColorIsRGB | ColorValid | 0xF0F8FF - ColorAntiqueWhite = ColorIsRGB | ColorValid | 0xFAEBD7 - ColorAquaMarine = ColorIsRGB | ColorValid | 0x7FFFD4 - ColorAzure = ColorIsRGB | ColorValid | 0xF0FFFF - ColorBeige = ColorIsRGB | ColorValid | 0xF5F5DC - ColorBisque = ColorIsRGB | ColorValid | 0xFFE4C4 - ColorBlanchedAlmond = ColorIsRGB | ColorValid | 0xFFEBCD - ColorBlueViolet = ColorIsRGB | ColorValid | 0x8A2BE2 - ColorBrown = ColorIsRGB | ColorValid | 0xA52A2A - ColorBurlyWood = ColorIsRGB | ColorValid | 0xDEB887 - ColorCadetBlue = ColorIsRGB | ColorValid | 0x5F9EA0 - ColorChartreuse = ColorIsRGB | ColorValid | 0x7FFF00 - ColorChocolate = ColorIsRGB | ColorValid | 0xD2691E - ColorCoral = ColorIsRGB | ColorValid | 0xFF7F50 - ColorCornflowerBlue = ColorIsRGB | ColorValid | 0x6495ED - ColorCornsilk = ColorIsRGB | ColorValid | 0xFFF8DC - ColorCrimson = ColorIsRGB | ColorValid | 0xDC143C - ColorDarkBlue = ColorIsRGB | ColorValid | 0x00008B - ColorDarkCyan = ColorIsRGB | ColorValid | 0x008B8B - ColorDarkGoldenrod = ColorIsRGB | ColorValid | 0xB8860B - ColorDarkGray = ColorIsRGB | ColorValid | 0xA9A9A9 - ColorDarkGreen = ColorIsRGB | ColorValid | 0x006400 - ColorDarkKhaki = ColorIsRGB | ColorValid | 0xBDB76B - ColorDarkMagenta = ColorIsRGB | ColorValid | 0x8B008B - ColorDarkOliveGreen = ColorIsRGB | ColorValid | 0x556B2F - ColorDarkOrange = ColorIsRGB | ColorValid | 0xFF8C00 - ColorDarkOrchid = ColorIsRGB | ColorValid | 0x9932CC - ColorDarkRed = ColorIsRGB | ColorValid | 0x8B0000 - ColorDarkSalmon = ColorIsRGB | ColorValid | 0xE9967A - ColorDarkSeaGreen = ColorIsRGB | ColorValid | 0x8FBC8F - ColorDarkSlateBlue = ColorIsRGB | ColorValid | 0x483D8B - ColorDarkSlateGray = ColorIsRGB | ColorValid | 0x2F4F4F - ColorDarkTurquoise = ColorIsRGB | ColorValid | 0x00CED1 - ColorDarkViolet = ColorIsRGB | ColorValid | 0x9400D3 - ColorDeepPink = ColorIsRGB | ColorValid | 0xFF1493 - ColorDeepSkyBlue = ColorIsRGB | ColorValid | 0x00BFFF - ColorDimGray = ColorIsRGB | ColorValid | 0x696969 - ColorDodgerBlue = ColorIsRGB | ColorValid | 0x1E90FF - ColorFireBrick = ColorIsRGB | ColorValid | 0xB22222 - ColorFloralWhite = ColorIsRGB | ColorValid | 0xFFFAF0 - ColorForestGreen = ColorIsRGB | ColorValid | 0x228B22 - ColorGainsboro = ColorIsRGB | ColorValid | 0xDCDCDC - ColorGhostWhite = ColorIsRGB | ColorValid | 0xF8F8FF - ColorGold = ColorIsRGB | ColorValid | 0xFFD700 - ColorGoldenrod = ColorIsRGB | ColorValid | 0xDAA520 - ColorGreenYellow = ColorIsRGB | ColorValid | 0xADFF2F - ColorHoneydew = ColorIsRGB | ColorValid | 0xF0FFF0 - ColorHotPink = ColorIsRGB | ColorValid | 0xFF69B4 - ColorIndianRed = ColorIsRGB | ColorValid | 0xCD5C5C - ColorIndigo = ColorIsRGB | ColorValid | 0x4B0082 - ColorIvory = ColorIsRGB | ColorValid | 0xFFFFF0 - ColorKhaki = ColorIsRGB | ColorValid | 0xF0E68C - ColorLavender = ColorIsRGB | ColorValid | 0xE6E6FA - ColorLavenderBlush = ColorIsRGB | ColorValid | 0xFFF0F5 - ColorLawnGreen = ColorIsRGB | ColorValid | 0x7CFC00 - ColorLemonChiffon = ColorIsRGB | ColorValid | 0xFFFACD - ColorLightBlue = ColorIsRGB | ColorValid | 0xADD8E6 - ColorLightCoral = ColorIsRGB | ColorValid | 0xF08080 - ColorLightCyan = ColorIsRGB | ColorValid | 0xE0FFFF - ColorLightGoldenrodYellow = ColorIsRGB | ColorValid | 0xFAFAD2 - ColorLightGray = ColorIsRGB | ColorValid | 0xD3D3D3 - ColorLightGreen = ColorIsRGB | ColorValid | 0x90EE90 - ColorLightPink = ColorIsRGB | ColorValid | 0xFFB6C1 - ColorLightSalmon = ColorIsRGB | ColorValid | 0xFFA07A - ColorLightSeaGreen = ColorIsRGB | ColorValid | 0x20B2AA - ColorLightSkyBlue = ColorIsRGB | ColorValid | 0x87CEFA - ColorLightSlateGray = ColorIsRGB | ColorValid | 0x778899 - ColorLightSteelBlue = ColorIsRGB | ColorValid | 0xB0C4DE - ColorLightYellow = ColorIsRGB | ColorValid | 0xFFFFE0 - ColorLimeGreen = ColorIsRGB | ColorValid | 0x32CD32 - ColorLinen = ColorIsRGB | ColorValid | 0xFAF0E6 - ColorMediumAquamarine = ColorIsRGB | ColorValid | 0x66CDAA - ColorMediumBlue = ColorIsRGB | ColorValid | 0x0000CD - ColorMediumOrchid = ColorIsRGB | ColorValid | 0xBA55D3 - ColorMediumPurple = ColorIsRGB | ColorValid | 0x9370DB - ColorMediumSeaGreen = ColorIsRGB | ColorValid | 0x3CB371 - ColorMediumSlateBlue = ColorIsRGB | ColorValid | 0x7B68EE - ColorMediumSpringGreen = ColorIsRGB | ColorValid | 0x00FA9A - ColorMediumTurquoise = ColorIsRGB | ColorValid | 0x48D1CC - ColorMediumVioletRed = ColorIsRGB | ColorValid | 0xC71585 - ColorMidnightBlue = ColorIsRGB | ColorValid | 0x191970 - ColorMintCream = ColorIsRGB | ColorValid | 0xF5FFFA - ColorMistyRose = ColorIsRGB | ColorValid | 0xFFE4E1 - ColorMoccasin = ColorIsRGB | ColorValid | 0xFFE4B5 - ColorNavajoWhite = ColorIsRGB | ColorValid | 0xFFDEAD - ColorOldLace = ColorIsRGB | ColorValid | 0xFDF5E6 - ColorOliveDrab = ColorIsRGB | ColorValid | 0x6B8E23 - ColorOrange = ColorIsRGB | ColorValid | 0xFFA500 - ColorOrangeRed = ColorIsRGB | ColorValid | 0xFF4500 - ColorOrchid = ColorIsRGB | ColorValid | 0xDA70D6 - ColorPaleGoldenrod = ColorIsRGB | ColorValid | 0xEEE8AA - ColorPaleGreen = ColorIsRGB | ColorValid | 0x98FB98 - ColorPaleTurquoise = ColorIsRGB | ColorValid | 0xAFEEEE - ColorPaleVioletRed = ColorIsRGB | ColorValid | 0xDB7093 - ColorPapayaWhip = ColorIsRGB | ColorValid | 0xFFEFD5 - ColorPeachPuff = ColorIsRGB | ColorValid | 0xFFDAB9 - ColorPeru = ColorIsRGB | ColorValid | 0xCD853F - ColorPink = ColorIsRGB | ColorValid | 0xFFC0CB - ColorPlum = ColorIsRGB | ColorValid | 0xDDA0DD - ColorPowderBlue = ColorIsRGB | ColorValid | 0xB0E0E6 - ColorRebeccaPurple = ColorIsRGB | ColorValid | 0x663399 - ColorRosyBrown = ColorIsRGB | ColorValid | 0xBC8F8F - ColorRoyalBlue = ColorIsRGB | ColorValid | 0x4169E1 - ColorSaddleBrown = ColorIsRGB | ColorValid | 0x8B4513 - ColorSalmon = ColorIsRGB | ColorValid | 0xFA8072 - ColorSandyBrown = ColorIsRGB | ColorValid | 0xF4A460 - ColorSeaGreen = ColorIsRGB | ColorValid | 0x2E8B57 - ColorSeashell = ColorIsRGB | ColorValid | 0xFFF5EE - ColorSienna = ColorIsRGB | ColorValid | 0xA0522D - ColorSkyblue = ColorIsRGB | ColorValid | 0x87CEEB - ColorSlateBlue = ColorIsRGB | ColorValid | 0x6A5ACD - ColorSlateGray = ColorIsRGB | ColorValid | 0x708090 - ColorSnow = ColorIsRGB | ColorValid | 0xFFFAFA - ColorSpringGreen = ColorIsRGB | ColorValid | 0x00FF7F - ColorSteelBlue = ColorIsRGB | ColorValid | 0x4682B4 - ColorTan = ColorIsRGB | ColorValid | 0xD2B48C - ColorThistle = ColorIsRGB | ColorValid | 0xD8BFD8 - ColorTomato = ColorIsRGB | ColorValid | 0xFF6347 - ColorTurquoise = ColorIsRGB | ColorValid | 0x40E0D0 - ColorViolet = ColorIsRGB | ColorValid | 0xEE82EE - ColorWheat = ColorIsRGB | ColorValid | 0xF5DEB3 - ColorWhiteSmoke = ColorIsRGB | ColorValid | 0xF5F5F5 - ColorYellowGreen = ColorIsRGB | ColorValid | 0x9ACD32 -) - -// These are aliases for the color gray, because some of us spell -// it as grey. -const ( - ColorGrey = ColorGray - ColorDimGrey = ColorDimGray - ColorDarkGrey = ColorDarkGray - ColorDarkSlateGrey = ColorDarkSlateGray - ColorLightGrey = ColorLightGray - ColorLightSlateGrey = ColorLightSlateGray - ColorSlateGrey = ColorSlateGray -) - -// ColorValues maps color constants to their RGB values. -var ColorValues = map[Color]int32{ - ColorBlack: 0x000000, - ColorMaroon: 0x800000, - ColorGreen: 0x008000, - ColorOlive: 0x808000, - ColorNavy: 0x000080, - ColorPurple: 0x800080, - ColorTeal: 0x008080, - ColorSilver: 0xC0C0C0, - ColorGray: 0x808080, - ColorRed: 0xFF0000, - ColorLime: 0x00FF00, - ColorYellow: 0xFFFF00, - ColorBlue: 0x0000FF, - ColorFuchsia: 0xFF00FF, - ColorAqua: 0x00FFFF, - ColorWhite: 0xFFFFFF, - Color16: 0x000000, // black - Color17: 0x00005F, - Color18: 0x000087, - Color19: 0x0000AF, - Color20: 0x0000D7, - Color21: 0x0000FF, // blue - Color22: 0x005F00, - Color23: 0x005F5F, - Color24: 0x005F87, - Color25: 0x005FAF, - Color26: 0x005FD7, - Color27: 0x005FFF, - Color28: 0x008700, - Color29: 0x00875F, - Color30: 0x008787, - Color31: 0x0087Af, - Color32: 0x0087D7, - Color33: 0x0087FF, - Color34: 0x00AF00, - Color35: 0x00AF5F, - Color36: 0x00AF87, - Color37: 0x00AFAF, - Color38: 0x00AFD7, - Color39: 0x00AFFF, - Color40: 0x00D700, - Color41: 0x00D75F, - Color42: 0x00D787, - Color43: 0x00D7AF, - Color44: 0x00D7D7, - Color45: 0x00D7FF, - Color46: 0x00FF00, // lime - Color47: 0x00FF5F, - Color48: 0x00FF87, - Color49: 0x00FFAF, - Color50: 0x00FFd7, - Color51: 0x00FFFF, // aqua - Color52: 0x5F0000, - Color53: 0x5F005F, - Color54: 0x5F0087, - Color55: 0x5F00AF, - Color56: 0x5F00D7, - Color57: 0x5F00FF, - Color58: 0x5F5F00, - Color59: 0x5F5F5F, - Color60: 0x5F5F87, - Color61: 0x5F5FAF, - Color62: 0x5F5FD7, - Color63: 0x5F5FFF, - Color64: 0x5F8700, - Color65: 0x5F875F, - Color66: 0x5F8787, - Color67: 0x5F87AF, - Color68: 0x5F87D7, - Color69: 0x5F87FF, - Color70: 0x5FAF00, - Color71: 0x5FAF5F, - Color72: 0x5FAF87, - Color73: 0x5FAFAF, - Color74: 0x5FAFD7, - Color75: 0x5FAFFF, - Color76: 0x5FD700, - Color77: 0x5FD75F, - Color78: 0x5FD787, - Color79: 0x5FD7AF, - Color80: 0x5FD7D7, - Color81: 0x5FD7FF, - Color82: 0x5FFF00, - Color83: 0x5FFF5F, - Color84: 0x5FFF87, - Color85: 0x5FFFAF, - Color86: 0x5FFFD7, - Color87: 0x5FFFFF, - Color88: 0x870000, - Color89: 0x87005F, - Color90: 0x870087, - Color91: 0x8700AF, - Color92: 0x8700D7, - Color93: 0x8700FF, - Color94: 0x875F00, - Color95: 0x875F5F, - Color96: 0x875F87, - Color97: 0x875FAF, - Color98: 0x875FD7, - Color99: 0x875FFF, - Color100: 0x878700, - Color101: 0x87875F, - Color102: 0x878787, - Color103: 0x8787AF, - Color104: 0x8787D7, - Color105: 0x8787FF, - Color106: 0x87AF00, - Color107: 0x87AF5F, - Color108: 0x87AF87, - Color109: 0x87AFAF, - Color110: 0x87AFD7, - Color111: 0x87AFFF, - Color112: 0x87D700, - Color113: 0x87D75F, - Color114: 0x87D787, - Color115: 0x87D7AF, - Color116: 0x87D7D7, - Color117: 0x87D7FF, - Color118: 0x87FF00, - Color119: 0x87FF5F, - Color120: 0x87FF87, - Color121: 0x87FFAF, - Color122: 0x87FFD7, - Color123: 0x87FFFF, - Color124: 0xAF0000, - Color125: 0xAF005F, - Color126: 0xAF0087, - Color127: 0xAF00AF, - Color128: 0xAF00D7, - Color129: 0xAF00FF, - Color130: 0xAF5F00, - Color131: 0xAF5F5F, - Color132: 0xAF5F87, - Color133: 0xAF5FAF, - Color134: 0xAF5FD7, - Color135: 0xAF5FFF, - Color136: 0xAF8700, - Color137: 0xAF875F, - Color138: 0xAF8787, - Color139: 0xAF87AF, - Color140: 0xAF87D7, - Color141: 0xAF87FF, - Color142: 0xAFAF00, - Color143: 0xAFAF5F, - Color144: 0xAFAF87, - Color145: 0xAFAFAF, - Color146: 0xAFAFD7, - Color147: 0xAFAFFF, - Color148: 0xAFD700, - Color149: 0xAFD75F, - Color150: 0xAFD787, - Color151: 0xAFD7AF, - Color152: 0xAFD7D7, - Color153: 0xAFD7FF, - Color154: 0xAFFF00, - Color155: 0xAFFF5F, - Color156: 0xAFFF87, - Color157: 0xAFFFAF, - Color158: 0xAFFFD7, - Color159: 0xAFFFFF, - Color160: 0xD70000, - Color161: 0xD7005F, - Color162: 0xD70087, - Color163: 0xD700AF, - Color164: 0xD700D7, - Color165: 0xD700FF, - Color166: 0xD75F00, - Color167: 0xD75F5F, - Color168: 0xD75F87, - Color169: 0xD75FAF, - Color170: 0xD75FD7, - Color171: 0xD75FFF, - Color172: 0xD78700, - Color173: 0xD7875F, - Color174: 0xD78787, - Color175: 0xD787AF, - Color176: 0xD787D7, - Color177: 0xD787FF, - Color178: 0xD7AF00, - Color179: 0xD7AF5F, - Color180: 0xD7AF87, - Color181: 0xD7AFAF, - Color182: 0xD7AFD7, - Color183: 0xD7AFFF, - Color184: 0xD7D700, - Color185: 0xD7D75F, - Color186: 0xD7D787, - Color187: 0xD7D7AF, - Color188: 0xD7D7D7, - Color189: 0xD7D7FF, - Color190: 0xD7FF00, - Color191: 0xD7FF5F, - Color192: 0xD7FF87, - Color193: 0xD7FFAF, - Color194: 0xD7FFD7, - Color195: 0xD7FFFF, - Color196: 0xFF0000, // red - Color197: 0xFF005F, - Color198: 0xFF0087, - Color199: 0xFF00AF, - Color200: 0xFF00D7, - Color201: 0xFF00FF, // fuchsia - Color202: 0xFF5F00, - Color203: 0xFF5F5F, - Color204: 0xFF5F87, - Color205: 0xFF5FAF, - Color206: 0xFF5FD7, - Color207: 0xFF5FFF, - Color208: 0xFF8700, - Color209: 0xFF875F, - Color210: 0xFF8787, - Color211: 0xFF87AF, - Color212: 0xFF87D7, - Color213: 0xFF87FF, - Color214: 0xFFAF00, - Color215: 0xFFAF5F, - Color216: 0xFFAF87, - Color217: 0xFFAFAF, - Color218: 0xFFAFD7, - Color219: 0xFFAFFF, - Color220: 0xFFD700, - Color221: 0xFFD75F, - Color222: 0xFFD787, - Color223: 0xFFD7AF, - Color224: 0xFFD7D7, - Color225: 0xFFD7FF, - Color226: 0xFFFF00, // yellow - Color227: 0xFFFF5F, - Color228: 0xFFFF87, - Color229: 0xFFFFAF, - Color230: 0xFFFFD7, - Color231: 0xFFFFFF, // white - Color232: 0x080808, - Color233: 0x121212, - Color234: 0x1C1C1C, - Color235: 0x262626, - Color236: 0x303030, - Color237: 0x3A3A3A, - Color238: 0x444444, - Color239: 0x4E4E4E, - Color240: 0x585858, - Color241: 0x626262, - Color242: 0x6C6C6C, - Color243: 0x767676, - Color244: 0x808080, // grey - Color245: 0x8A8A8A, - Color246: 0x949494, - Color247: 0x9E9E9E, - Color248: 0xA8A8A8, - Color249: 0xB2B2B2, - Color250: 0xBCBCBC, - Color251: 0xC6C6C6, - Color252: 0xD0D0D0, - Color253: 0xDADADA, - Color254: 0xE4E4E4, - Color255: 0xEEEEEE, - ColorAliceBlue: 0xF0F8FF, - ColorAntiqueWhite: 0xFAEBD7, - ColorAquaMarine: 0x7FFFD4, - ColorAzure: 0xF0FFFF, - ColorBeige: 0xF5F5DC, - ColorBisque: 0xFFE4C4, - ColorBlanchedAlmond: 0xFFEBCD, - ColorBlueViolet: 0x8A2BE2, - ColorBrown: 0xA52A2A, - ColorBurlyWood: 0xDEB887, - ColorCadetBlue: 0x5F9EA0, - ColorChartreuse: 0x7FFF00, - ColorChocolate: 0xD2691E, - ColorCoral: 0xFF7F50, - ColorCornflowerBlue: 0x6495ED, - ColorCornsilk: 0xFFF8DC, - ColorCrimson: 0xDC143C, - ColorDarkBlue: 0x00008B, - ColorDarkCyan: 0x008B8B, - ColorDarkGoldenrod: 0xB8860B, - ColorDarkGray: 0xA9A9A9, - ColorDarkGreen: 0x006400, - ColorDarkKhaki: 0xBDB76B, - ColorDarkMagenta: 0x8B008B, - ColorDarkOliveGreen: 0x556B2F, - ColorDarkOrange: 0xFF8C00, - ColorDarkOrchid: 0x9932CC, - ColorDarkRed: 0x8B0000, - ColorDarkSalmon: 0xE9967A, - ColorDarkSeaGreen: 0x8FBC8F, - ColorDarkSlateBlue: 0x483D8B, - ColorDarkSlateGray: 0x2F4F4F, - ColorDarkTurquoise: 0x00CED1, - ColorDarkViolet: 0x9400D3, - ColorDeepPink: 0xFF1493, - ColorDeepSkyBlue: 0x00BFFF, - ColorDimGray: 0x696969, - ColorDodgerBlue: 0x1E90FF, - ColorFireBrick: 0xB22222, - ColorFloralWhite: 0xFFFAF0, - ColorForestGreen: 0x228B22, - ColorGainsboro: 0xDCDCDC, - ColorGhostWhite: 0xF8F8FF, - ColorGold: 0xFFD700, - ColorGoldenrod: 0xDAA520, - ColorGreenYellow: 0xADFF2F, - ColorHoneydew: 0xF0FFF0, - ColorHotPink: 0xFF69B4, - ColorIndianRed: 0xCD5C5C, - ColorIndigo: 0x4B0082, - ColorIvory: 0xFFFFF0, - ColorKhaki: 0xF0E68C, - ColorLavender: 0xE6E6FA, - ColorLavenderBlush: 0xFFF0F5, - ColorLawnGreen: 0x7CFC00, - ColorLemonChiffon: 0xFFFACD, - ColorLightBlue: 0xADD8E6, - ColorLightCoral: 0xF08080, - ColorLightCyan: 0xE0FFFF, - ColorLightGoldenrodYellow: 0xFAFAD2, - ColorLightGray: 0xD3D3D3, - ColorLightGreen: 0x90EE90, - ColorLightPink: 0xFFB6C1, - ColorLightSalmon: 0xFFA07A, - ColorLightSeaGreen: 0x20B2AA, - ColorLightSkyBlue: 0x87CEFA, - ColorLightSlateGray: 0x778899, - ColorLightSteelBlue: 0xB0C4DE, - ColorLightYellow: 0xFFFFE0, - ColorLimeGreen: 0x32CD32, - ColorLinen: 0xFAF0E6, - ColorMediumAquamarine: 0x66CDAA, - ColorMediumBlue: 0x0000CD, - ColorMediumOrchid: 0xBA55D3, - ColorMediumPurple: 0x9370DB, - ColorMediumSeaGreen: 0x3CB371, - ColorMediumSlateBlue: 0x7B68EE, - ColorMediumSpringGreen: 0x00FA9A, - ColorMediumTurquoise: 0x48D1CC, - ColorMediumVioletRed: 0xC71585, - ColorMidnightBlue: 0x191970, - ColorMintCream: 0xF5FFFA, - ColorMistyRose: 0xFFE4E1, - ColorMoccasin: 0xFFE4B5, - ColorNavajoWhite: 0xFFDEAD, - ColorOldLace: 0xFDF5E6, - ColorOliveDrab: 0x6B8E23, - ColorOrange: 0xFFA500, - ColorOrangeRed: 0xFF4500, - ColorOrchid: 0xDA70D6, - ColorPaleGoldenrod: 0xEEE8AA, - ColorPaleGreen: 0x98FB98, - ColorPaleTurquoise: 0xAFEEEE, - ColorPaleVioletRed: 0xDB7093, - ColorPapayaWhip: 0xFFEFD5, - ColorPeachPuff: 0xFFDAB9, - ColorPeru: 0xCD853F, - ColorPink: 0xFFC0CB, - ColorPlum: 0xDDA0DD, - ColorPowderBlue: 0xB0E0E6, - ColorRebeccaPurple: 0x663399, - ColorRosyBrown: 0xBC8F8F, - ColorRoyalBlue: 0x4169E1, - ColorSaddleBrown: 0x8B4513, - ColorSalmon: 0xFA8072, - ColorSandyBrown: 0xF4A460, - ColorSeaGreen: 0x2E8B57, - ColorSeashell: 0xFFF5EE, - ColorSienna: 0xA0522D, - ColorSkyblue: 0x87CEEB, - ColorSlateBlue: 0x6A5ACD, - ColorSlateGray: 0x708090, - ColorSnow: 0xFFFAFA, - ColorSpringGreen: 0x00FF7F, - ColorSteelBlue: 0x4682B4, - ColorTan: 0xD2B48C, - ColorThistle: 0xD8BFD8, - ColorTomato: 0xFF6347, - ColorTurquoise: 0x40E0D0, - ColorViolet: 0xEE82EE, - ColorWheat: 0xF5DEB3, - ColorWhiteSmoke: 0xF5F5F5, - ColorYellowGreen: 0x9ACD32, -} - -// Special colors. -const ( - // ColorReset is used to indicate that the color should use the - // vanilla terminal colors. (Basically go back to the defaults.) - ColorReset = ColorSpecial | iota - - // ColorNone indicates that we should not change the color from - // whatever is already displayed. This can only be used in limited - // circumstances. - ColorNone -) - -// ColorNames holds the written names of colors. Useful to present a list of -// recognized named colors. -var ColorNames = map[string]Color{ - "black": ColorBlack, - "maroon": ColorMaroon, - "green": ColorGreen, - "olive": ColorOlive, - "navy": ColorNavy, - "purple": ColorPurple, - "teal": ColorTeal, - "silver": ColorSilver, - "gray": ColorGray, - "red": ColorRed, - "lime": ColorLime, - "yellow": ColorYellow, - "blue": ColorBlue, - "fuchsia": ColorFuchsia, - "aqua": ColorAqua, - "white": ColorWhite, - "aliceblue": ColorAliceBlue, - "antiquewhite": ColorAntiqueWhite, - "aquamarine": ColorAquaMarine, - "azure": ColorAzure, - "beige": ColorBeige, - "bisque": ColorBisque, - "blanchedalmond": ColorBlanchedAlmond, - "blueviolet": ColorBlueViolet, - "brown": ColorBrown, - "burlywood": ColorBurlyWood, - "cadetblue": ColorCadetBlue, - "chartreuse": ColorChartreuse, - "chocolate": ColorChocolate, - "coral": ColorCoral, - "cornflowerblue": ColorCornflowerBlue, - "cornsilk": ColorCornsilk, - "crimson": ColorCrimson, - "darkblue": ColorDarkBlue, - "darkcyan": ColorDarkCyan, - "darkgoldenrod": ColorDarkGoldenrod, - "darkgray": ColorDarkGray, - "darkgreen": ColorDarkGreen, - "darkkhaki": ColorDarkKhaki, - "darkmagenta": ColorDarkMagenta, - "darkolivegreen": ColorDarkOliveGreen, - "darkorange": ColorDarkOrange, - "darkorchid": ColorDarkOrchid, - "darkred": ColorDarkRed, - "darksalmon": ColorDarkSalmon, - "darkseagreen": ColorDarkSeaGreen, - "darkslateblue": ColorDarkSlateBlue, - "darkslategray": ColorDarkSlateGray, - "darkturquoise": ColorDarkTurquoise, - "darkviolet": ColorDarkViolet, - "deeppink": ColorDeepPink, - "deepskyblue": ColorDeepSkyBlue, - "dimgray": ColorDimGray, - "dodgerblue": ColorDodgerBlue, - "firebrick": ColorFireBrick, - "floralwhite": ColorFloralWhite, - "forestgreen": ColorForestGreen, - "gainsboro": ColorGainsboro, - "ghostwhite": ColorGhostWhite, - "gold": ColorGold, - "goldenrod": ColorGoldenrod, - "greenyellow": ColorGreenYellow, - "honeydew": ColorHoneydew, - "hotpink": ColorHotPink, - "indianred": ColorIndianRed, - "indigo": ColorIndigo, - "ivory": ColorIvory, - "khaki": ColorKhaki, - "lavender": ColorLavender, - "lavenderblush": ColorLavenderBlush, - "lawngreen": ColorLawnGreen, - "lemonchiffon": ColorLemonChiffon, - "lightblue": ColorLightBlue, - "lightcoral": ColorLightCoral, - "lightcyan": ColorLightCyan, - "lightgoldenrodyellow": ColorLightGoldenrodYellow, - "lightgray": ColorLightGray, - "lightgreen": ColorLightGreen, - "lightpink": ColorLightPink, - "lightsalmon": ColorLightSalmon, - "lightseagreen": ColorLightSeaGreen, - "lightskyblue": ColorLightSkyBlue, - "lightslategray": ColorLightSlateGray, - "lightsteelblue": ColorLightSteelBlue, - "lightyellow": ColorLightYellow, - "limegreen": ColorLimeGreen, - "linen": ColorLinen, - "mediumaquamarine": ColorMediumAquamarine, - "mediumblue": ColorMediumBlue, - "mediumorchid": ColorMediumOrchid, - "mediumpurple": ColorMediumPurple, - "mediumseagreen": ColorMediumSeaGreen, - "mediumslateblue": ColorMediumSlateBlue, - "mediumspringgreen": ColorMediumSpringGreen, - "mediumturquoise": ColorMediumTurquoise, - "mediumvioletred": ColorMediumVioletRed, - "midnightblue": ColorMidnightBlue, - "mintcream": ColorMintCream, - "mistyrose": ColorMistyRose, - "moccasin": ColorMoccasin, - "navajowhite": ColorNavajoWhite, - "oldlace": ColorOldLace, - "olivedrab": ColorOliveDrab, - "orange": ColorOrange, - "orangered": ColorOrangeRed, - "orchid": ColorOrchid, - "palegoldenrod": ColorPaleGoldenrod, - "palegreen": ColorPaleGreen, - "paleturquoise": ColorPaleTurquoise, - "palevioletred": ColorPaleVioletRed, - "papayawhip": ColorPapayaWhip, - "peachpuff": ColorPeachPuff, - "peru": ColorPeru, - "pink": ColorPink, - "plum": ColorPlum, - "powderblue": ColorPowderBlue, - "rebeccapurple": ColorRebeccaPurple, - "rosybrown": ColorRosyBrown, - "royalblue": ColorRoyalBlue, - "saddlebrown": ColorSaddleBrown, - "salmon": ColorSalmon, - "sandybrown": ColorSandyBrown, - "seagreen": ColorSeaGreen, - "seashell": ColorSeashell, - "sienna": ColorSienna, - "skyblue": ColorSkyblue, - "slateblue": ColorSlateBlue, - "slategray": ColorSlateGray, - "snow": ColorSnow, - "springgreen": ColorSpringGreen, - "steelblue": ColorSteelBlue, - "tan": ColorTan, - "thistle": ColorThistle, - "tomato": ColorTomato, - "turquoise": ColorTurquoise, - "violet": ColorViolet, - "wheat": ColorWheat, - "whitesmoke": ColorWhiteSmoke, - "yellowgreen": ColorYellowGreen, - "grey": ColorGray, - "dimgrey": ColorDimGray, - "darkgrey": ColorDarkGray, - "darkslategrey": ColorDarkSlateGray, - "lightgrey": ColorLightGray, - "lightslategrey": ColorLightSlateGray, - "slategrey": ColorSlateGray, -} - -// Valid indicates the color is a valid value (has been set). -func (c Color) Valid() bool { - return c&ColorValid != 0 -} - -// IsRGB is true if the color is an RGB specific value. -func (c Color) IsRGB() bool { - return c&(ColorValid|ColorIsRGB) == (ColorValid | ColorIsRGB) -} - -// CSS returns the CSS hex string ( #ABCDEF ) if valid -// if not a valid color returns empty string -func (c Color) CSS() string { - if !c.Valid() { - return "" - } - return fmt.Sprintf("#%06X", c.Hex()) -} - -// String implements fmt.Stringer to return either the -// W3C name if it has one or the CSS hex string '#ABCDEF' -func (c Color) String() string { - if !c.Valid() { - switch c { - case ColorNone: - return "none" - case ColorDefault: - return "default" - case ColorReset: - return "reset" - } - return "" - } - return c.Name(true) -} - -// Name returns W3C name or an empty string if no arguments -// if passed true as an argument it will falls back to -// the CSS hex string if no W3C name found '#ABCDEF' -func (c Color) Name(css ...bool) string { - for name, hex := range ColorNames { - if c == hex { - return name - } - } - if len(css) > 0 && css[0] { - return c.CSS() - } - return "" -} - -// Hex returns the color's hexadecimal RGB 24-bit value with each component -// consisting of a single byte, R << 16 | G << 8 | B. If the color -// is unknown or unset, -1 is returned. -func (c Color) Hex() int32 { - if !c.Valid() { - return -1 - } - if c&ColorIsRGB != 0 { - return int32(c & 0xffffff) - } - if v, ok := ColorValues[c]; ok { - return v - } - return -1 -} - -// RGB returns the red, green, and blue components of the color, with -// each component represented as a value 0-255. In the event that the -// color cannot be broken up (not set usually), -1 is returned for each value. -func (c Color) RGB() (int32, int32, int32) { - v := c.Hex() - if v < 0 { - return -1, -1, -1 - } - return (v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff -} - -// TrueColor returns the true color (RGB) version of the provided color. -// This is useful for ensuring color accuracy when using named colors. -// This will override terminal theme colors. -func (c Color) TrueColor() Color { - if !c.Valid() { - return ColorDefault - } - if c&ColorIsRGB != 0 { - return c | ColorValid - } - return Color(c.Hex()) | ColorIsRGB | ColorValid -} - -// NewRGBColor returns a new color with the given red, green, and blue values. -// Each value must be represented in the range 0-255. -func NewRGBColor(r, g, b int32) Color { - return NewHexColor(((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)) -} - -// NewHexColor returns a color using the given 24-bit RGB value. -func NewHexColor(v int32) Color { - return ColorIsRGB | Color(v) | ColorValid -} - -// GetColor creates a Color from a color name (W3C name). A hex value may -// be supplied as a string in the format "#ffffff". -func GetColor(name string) Color { - if c, ok := ColorNames[name]; ok { - return c - } - if len(name) == 7 && name[0] == '#' { - if v, e := strconv.ParseInt(name[1:], 16, 32); e == nil { - return NewHexColor(int32(v)) - } - } - return ColorDefault -} - -// PaletteColor creates a color based on the palette index. -func PaletteColor(index int) Color { - return Color(index) | ColorValid -} - -// FromImageColor converts an image/color.Color into tcell.Color. -// The alpha value is dropped, so it should be tracked separately if it is -// needed. -func FromImageColor(imageColor ic.Color) Color { - r, g, b, _ := imageColor.RGBA() - // NOTE image/color.Color RGB values range is [0, 0xFFFF] as uint32 - return NewRGBColor(int32(r>>8), int32(g>>8), int32(b>>8)) -} diff --git a/vendor/github.com/gdamore/tcell/v2/colorfit.go b/vendor/github.com/gdamore/tcell/v2/colorfit.go deleted file mode 100644 index f690097f518..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/colorfit.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2016 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "math" - - "github.com/lucasb-eyer/go-colorful" -) - -// FindColor attempts to find a given color, or the best match possible for it, -// from the palette given. This is an expensive operation, so results should -// be cached by the caller. -func FindColor(c Color, palette []Color) Color { - match := ColorDefault - dist := float64(0) - r, g, b := c.RGB() - c1 := colorful.Color{ - R: float64(r) / 255.0, - G: float64(g) / 255.0, - B: float64(b) / 255.0, - } - for _, d := range palette { - r, g, b = d.RGB() - c2 := colorful.Color{ - R: float64(r) / 255.0, - G: float64(g) / 255.0, - B: float64(b) / 255.0, - } - // CIE94 is more accurate, but really really expensive. - nd := c1.DistanceCIE76(c2) - if math.IsNaN(nd) { - nd = math.Inf(1) - } - if match == ColorDefault || nd < dist { - match = d - dist = nd - } - } - return match -} diff --git a/vendor/github.com/gdamore/tcell/v2/console_stub.go b/vendor/github.com/gdamore/tcell/v2/console_stub.go deleted file mode 100644 index 6ff7e92a09e..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/console_stub.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build !windows -// +build !windows - -// Copyright 2015 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -// NewConsoleScreen returns a console based screen. This platform -// doesn't have support for any, so it returns nil and a suitable error. -func NewConsoleScreen() (Screen, error) { - return nil, ErrNoScreen -} diff --git a/vendor/github.com/gdamore/tcell/v2/console_win.go b/vendor/github.com/gdamore/tcell/v2/console_win.go deleted file mode 100644 index a46f3c31fd3..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/console_win.go +++ /dev/null @@ -1,1411 +0,0 @@ -//go:build windows -// +build windows - -// Copyright 2024 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "errors" - "fmt" - "os" - "strings" - "sync" - "syscall" - "unicode/utf16" - "unsafe" -) - -type cScreen struct { - in syscall.Handle - out syscall.Handle - cancelflag syscall.Handle - scandone chan struct{} - quit chan struct{} - curx int - cury int - style Style - fini bool - vten bool - truecolor bool - running bool - disableAlt bool // disable the alternate screen - title string - - w int - h int - - oscreen consoleInfo - ocursor cursorInfo - cursorStyle CursorStyle - cursorColor Color - oimode uint32 - oomode uint32 - cells CellBuffer - focusEnable bool - - mouseEnabled bool - wg sync.WaitGroup - eventQ chan Event - stopQ chan struct{} - finiOnce sync.Once - - sync.Mutex -} - -var winLock sync.Mutex - -var winPalette = []Color{ - ColorBlack, - ColorMaroon, - ColorGreen, - ColorNavy, - ColorOlive, - ColorPurple, - ColorTeal, - ColorSilver, - ColorGray, - ColorRed, - ColorLime, - ColorBlue, - ColorYellow, - ColorFuchsia, - ColorAqua, - ColorWhite, -} - -var winColors = map[Color]Color{ - ColorBlack: ColorBlack, - ColorMaroon: ColorMaroon, - ColorGreen: ColorGreen, - ColorNavy: ColorNavy, - ColorOlive: ColorOlive, - ColorPurple: ColorPurple, - ColorTeal: ColorTeal, - ColorSilver: ColorSilver, - ColorGray: ColorGray, - ColorRed: ColorRed, - ColorLime: ColorLime, - ColorBlue: ColorBlue, - ColorYellow: ColorYellow, - ColorFuchsia: ColorFuchsia, - ColorAqua: ColorAqua, - ColorWhite: ColorWhite, -} - -var ( - k32 = syscall.NewLazyDLL("kernel32.dll") - u32 = syscall.NewLazyDLL("user32.dll") -) - -// We have to bring in the kernel32 and user32 DLLs directly, so we can get -// access to some system calls that the core Go API lacks. -// -// Note that Windows appends some functions with W to indicate that wide -// characters (Unicode) are in use. The documentation refers to them -// without this suffix, as the resolution is made via preprocessor. -var ( - procReadConsoleInput = k32.NewProc("ReadConsoleInputW") - procWaitForMultipleObjects = k32.NewProc("WaitForMultipleObjects") - procCreateEvent = k32.NewProc("CreateEventW") - procSetEvent = k32.NewProc("SetEvent") - procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo") - procSetConsoleCursorInfo = k32.NewProc("SetConsoleCursorInfo") - procSetConsoleCursorPosition = k32.NewProc("SetConsoleCursorPosition") - procSetConsoleMode = k32.NewProc("SetConsoleMode") - procGetConsoleMode = k32.NewProc("GetConsoleMode") - procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo") - procFillConsoleOutputAttribute = k32.NewProc("FillConsoleOutputAttribute") - procFillConsoleOutputCharacter = k32.NewProc("FillConsoleOutputCharacterW") - procSetConsoleWindowInfo = k32.NewProc("SetConsoleWindowInfo") - procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize") - procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute") - procGetLargestConsoleWindowSize = k32.NewProc("GetLargestConsoleWindowSize") - procMessageBeep = u32.NewProc("MessageBeep") -) - -const ( - w32Infinite = ^uintptr(0) - w32WaitObject0 = uintptr(0) -) - -const ( - // VT100/XTerm escapes understood by the console - vtShowCursor = "\x1b[?25h" - vtHideCursor = "\x1b[?25l" - vtCursorPos = "\x1b[%d;%dH" // Note that it is Y then X - vtSgr0 = "\x1b[0m" - vtBold = "\x1b[1m" - vtUnderline = "\x1b[4m" - vtBlink = "\x1b[5m" // Not sure if this is processed - vtReverse = "\x1b[7m" - vtSetFg = "\x1b[38;5;%dm" - vtSetBg = "\x1b[48;5;%dm" - vtSetFgRGB = "\x1b[38;2;%d;%d;%dm" // RGB - vtSetBgRGB = "\x1b[48;2;%d;%d;%dm" // RGB - vtCursorDefault = "\x1b[0 q" - vtCursorBlinkingBlock = "\x1b[1 q" - vtCursorSteadyBlock = "\x1b[2 q" - vtCursorBlinkingUnderline = "\x1b[3 q" - vtCursorSteadyUnderline = "\x1b[4 q" - vtCursorBlinkingBar = "\x1b[5 q" - vtCursorSteadyBar = "\x1b[6 q" - vtDisableAm = "\x1b[?7l" - vtEnableAm = "\x1b[?7h" - vtEnterCA = "\x1b[?1049h\x1b[22;0;0t" - vtExitCA = "\x1b[?1049l\x1b[23;0;0t" - vtDoubleUnderline = "\x1b[4:2m" - vtCurlyUnderline = "\x1b[4:3m" - vtDottedUnderline = "\x1b[4:4m" - vtDashedUnderline = "\x1b[4:5m" - vtUnderColor = "\x1b[58:5:%dm" - vtUnderColorRGB = "\x1b[58:2::%d:%d:%dm" - vtUnderColorReset = "\x1b[59m" - vtEnterUrl = "\x1b]8;%s;%s\x1b\\" // NB arg 1 is id, arg 2 is url - vtExitUrl = "\x1b]8;;\x1b\\" - vtCursorColorRGB = "\x1b]12;#%02x%02x%02x\007" - vtCursorColorReset = "\x1b]112\007" - vtSaveTitle = "\x1b[22;2t" - vtRestoreTitle = "\x1b[23;2t" - vtSetTitle = "\x1b]2;%s\x1b\\" -) - -var vtCursorStyles = map[CursorStyle]string{ - CursorStyleDefault: vtCursorDefault, - CursorStyleBlinkingBlock: vtCursorBlinkingBlock, - CursorStyleSteadyBlock: vtCursorSteadyBlock, - CursorStyleBlinkingUnderline: vtCursorBlinkingUnderline, - CursorStyleSteadyUnderline: vtCursorSteadyUnderline, - CursorStyleBlinkingBar: vtCursorBlinkingBar, - CursorStyleSteadyBar: vtCursorSteadyBar, -} - -// NewConsoleScreen returns a Screen for the Windows console associated -// with the current process. The Screen makes use of the Windows Console -// API to display content and read events. -func NewConsoleScreen() (Screen, error) { - return &baseScreen{screenImpl: &cScreen{}}, nil -} - -func (s *cScreen) Init() error { - s.eventQ = make(chan Event, 10) - s.quit = make(chan struct{}) - s.scandone = make(chan struct{}) - in, e := syscall.Open("CONIN$", syscall.O_RDWR, 0) - if e != nil { - return e - } - s.in = in - out, e := syscall.Open("CONOUT$", syscall.O_RDWR, 0) - if e != nil { - _ = syscall.Close(s.in) - return e - } - s.out = out - - s.truecolor = true - - // ConEmu handling of colors and scrolling when in VT output mode is extremely poor. - // The color palette will scroll even though characters do not, when - // emitting stuff for the last character. In the future we might change this to - // look at specific versions of ConEmu if they fix the bug. - // We can also try disabling auto margin mode. - tryVt := true - if os.Getenv("ConEmuPID") != "" { - s.truecolor = false - tryVt = false - } - switch os.Getenv("TCELL_TRUECOLOR") { - case "disable": - s.truecolor = false - case "enable": - s.truecolor = true - tryVt = true - } - - s.Lock() - - s.curx = -1 - s.cury = -1 - s.style = StyleDefault - s.getCursorInfo(&s.ocursor) - s.getConsoleInfo(&s.oscreen) - s.getOutMode(&s.oomode) - s.getInMode(&s.oimode) - s.resize() - - s.fini = false - s.setInMode(modeResizeEn | modeExtendFlg) - - // If a user needs to force old style console, they may do so - // by setting TCELL_VTMODE to disable. This is an undocumented safety net for now. - // It may be removed in the future. (This mostly exists because of ConEmu.) - switch os.Getenv("TCELL_VTMODE") { - case "disable": - tryVt = false - case "enable": - tryVt = true - } - switch os.Getenv("TCELL_ALTSCREEN") { - case "enable": - s.disableAlt = false // also the default - case "disable": - s.disableAlt = true - } - if tryVt { - s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline) - var om uint32 - s.getOutMode(&om) - if om&modeVtOutput == modeVtOutput { - s.vten = true - } else { - s.truecolor = false - s.setOutMode(0) - } - } else { - s.setOutMode(0) - } - - s.Unlock() - - return s.engage() -} - -func (s *cScreen) CharacterSet() string { - // We are always UTF-16LE on Windows - return "UTF-16LE" -} - -func (s *cScreen) EnableMouse(...MouseFlags) { - s.Lock() - s.mouseEnabled = true - s.enableMouse(true) - s.Unlock() -} - -func (s *cScreen) DisableMouse() { - s.Lock() - s.mouseEnabled = false - s.enableMouse(false) - s.Unlock() -} - -func (s *cScreen) enableMouse(on bool) { - if on { - s.setInMode(modeResizeEn | modeMouseEn | modeExtendFlg) - } else { - s.setInMode(modeResizeEn | modeExtendFlg) - } -} - -// Windows lacks bracketed paste (for now) - -func (s *cScreen) EnablePaste() {} - -func (s *cScreen) DisablePaste() {} - -func (s *cScreen) EnableFocus() { - s.Lock() - s.focusEnable = true - s.Unlock() -} - -func (s *cScreen) DisableFocus() { - s.Lock() - s.focusEnable = false - s.Unlock() -} - -func (s *cScreen) Fini() { - s.finiOnce.Do(func() { - close(s.quit) - s.disengage() - }) -} - -func (s *cScreen) disengage() { - s.Lock() - if !s.running { - s.Unlock() - return - } - s.running = false - stopQ := s.stopQ - _, _, _ = procSetEvent.Call(uintptr(s.cancelflag)) - close(stopQ) - s.Unlock() - - s.wg.Wait() - - if s.vten { - s.emitVtString(vtCursorStyles[CursorStyleDefault]) - s.emitVtString(vtCursorColorReset) - s.emitVtString(vtEnableAm) - if !s.disableAlt { - s.emitVtString(vtRestoreTitle) - s.emitVtString(vtExitCA) - } - } else if !s.disableAlt { - s.clearScreen(StyleDefault, s.vten) - s.setCursorPos(0, 0, false) - } - s.setCursorInfo(&s.ocursor) - s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y)) - s.setInMode(s.oimode) - s.setOutMode(s.oomode) - _, _, _ = procSetConsoleTextAttribute.Call( - uintptr(s.out), - uintptr(s.mapStyle(StyleDefault))) -} - -func (s *cScreen) engage() error { - s.Lock() - defer s.Unlock() - if s.running { - return errors.New("already engaged") - } - s.stopQ = make(chan struct{}) - cf, _, e := procCreateEvent.Call( - uintptr(0), - uintptr(1), - uintptr(0), - uintptr(0)) - if cf == uintptr(0) { - return e - } - s.running = true - s.cancelflag = syscall.Handle(cf) - s.enableMouse(s.mouseEnabled) - - if s.vten { - s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline) - if !s.disableAlt { - s.emitVtString(vtSaveTitle) - s.emitVtString(vtEnterCA) - } - s.emitVtString(vtDisableAm) - if s.title != "" { - s.emitVtString(fmt.Sprintf(vtSetTitle, s.title)) - } - } else { - s.setOutMode(0) - } - - s.clearScreen(s.style, s.vten) - s.hideCursor() - - s.cells.Invalidate() - s.hideCursor() - s.resize() - s.draw() - s.doCursor() - - s.wg.Add(1) - go s.scanInput(s.stopQ) - return nil -} - -type cursorInfo struct { - size uint32 - visible uint32 -} - -type coord struct { - x int16 - y int16 -} - -func (c coord) uintptr() uintptr { - // little endian, put x first - return uintptr(c.x) | (uintptr(c.y) << 16) -} - -type rect struct { - left int16 - top int16 - right int16 - bottom int16 -} - -func (s *cScreen) emitVtString(vs string) { - esc := utf16.Encode([]rune(vs)) - _ = syscall.WriteConsole(s.out, &esc[0], uint32(len(esc)), nil, nil) -} - -func (s *cScreen) showCursor() { - if s.vten { - s.emitVtString(vtShowCursor) - s.emitVtString(vtCursorStyles[s.cursorStyle]) - if s.cursorColor == ColorReset { - s.emitVtString(vtCursorColorReset) - } else if s.cursorColor.Valid() { - r, g, b := s.cursorColor.RGB() - s.emitVtString(fmt.Sprintf(vtCursorColorRGB, r, g, b)) - } - } else { - s.setCursorInfo(&cursorInfo{size: 100, visible: 1}) - } -} - -func (s *cScreen) hideCursor() { - if s.vten { - s.emitVtString(vtHideCursor) - } else { - s.setCursorInfo(&cursorInfo{size: 1, visible: 0}) - } -} - -func (s *cScreen) ShowCursor(x, y int) { - s.Lock() - if !s.fini { - s.curx = x - s.cury = y - } - s.doCursor() - s.Unlock() -} - -func (s *cScreen) SetCursor(cs CursorStyle, cc Color) { - s.Lock() - if !s.fini { - if _, ok := vtCursorStyles[cs]; ok { - s.cursorStyle = cs - s.cursorColor = cc - s.doCursor() - } - } - s.Unlock() -} - -func (s *cScreen) doCursor() { - x, y := s.curx, s.cury - - if x < 0 || y < 0 || x >= s.w || y >= s.h { - s.hideCursor() - } else { - s.setCursorPos(x, y, s.vten) - s.showCursor() - } -} - -func (s *cScreen) HideCursor() { - s.ShowCursor(-1, -1) -} - -type inputRecord struct { - typ uint16 - _ uint16 - data [16]byte -} - -const ( - keyEvent uint16 = 1 - mouseEvent uint16 = 2 - resizeEvent uint16 = 4 - menuEvent uint16 = 8 // don't use - focusEvent uint16 = 16 -) - -type mouseRecord struct { - x int16 - y int16 - btns uint32 - mod uint32 - flags uint32 -} - -type focusRecord struct { - focused int32 // actually BOOL -} - -const ( - mouseHWheeled uint32 = 0x8 - mouseVWheeled uint32 = 0x4 - // mouseDoubleClick uint32 = 0x2 - // mouseMoved uint32 = 0x1 -) - -type resizeRecord struct { - x int16 - y int16 -} - -type keyRecord struct { - isdown int32 - repeat uint16 - kcode uint16 - scode uint16 - ch uint16 - mod uint32 -} - -const ( - // Constants per Microsoft. We don't put the modifiers - // here. - vkCancel = 0x03 - vkBack = 0x08 // Backspace - vkTab = 0x09 - vkClear = 0x0c - vkReturn = 0x0d - vkPause = 0x13 - vkEscape = 0x1b - vkSpace = 0x20 - vkPrior = 0x21 // PgUp - vkNext = 0x22 // PgDn - vkEnd = 0x23 - vkHome = 0x24 - vkLeft = 0x25 - vkUp = 0x26 - vkRight = 0x27 - vkDown = 0x28 - vkPrint = 0x2a - vkPrtScr = 0x2c - vkInsert = 0x2d - vkDelete = 0x2e - vkHelp = 0x2f - vkF1 = 0x70 - vkF2 = 0x71 - vkF3 = 0x72 - vkF4 = 0x73 - vkF5 = 0x74 - vkF6 = 0x75 - vkF7 = 0x76 - vkF8 = 0x77 - vkF9 = 0x78 - vkF10 = 0x79 - vkF11 = 0x7a - vkF12 = 0x7b - vkF13 = 0x7c - vkF14 = 0x7d - vkF15 = 0x7e - vkF16 = 0x7f - vkF17 = 0x80 - vkF18 = 0x81 - vkF19 = 0x82 - vkF20 = 0x83 - vkF21 = 0x84 - vkF22 = 0x85 - vkF23 = 0x86 - vkF24 = 0x87 -) - -var vkKeys = map[uint16]Key{ - vkCancel: KeyCancel, - vkBack: KeyBackspace, - vkTab: KeyTab, - vkClear: KeyClear, - vkPause: KeyPause, - vkPrint: KeyPrint, - vkPrtScr: KeyPrint, - vkPrior: KeyPgUp, - vkNext: KeyPgDn, - vkReturn: KeyEnter, - vkEnd: KeyEnd, - vkHome: KeyHome, - vkLeft: KeyLeft, - vkUp: KeyUp, - vkRight: KeyRight, - vkDown: KeyDown, - vkInsert: KeyInsert, - vkDelete: KeyDelete, - vkHelp: KeyHelp, - vkEscape: KeyEscape, - vkSpace: ' ', - vkF1: KeyF1, - vkF2: KeyF2, - vkF3: KeyF3, - vkF4: KeyF4, - vkF5: KeyF5, - vkF6: KeyF6, - vkF7: KeyF7, - vkF8: KeyF8, - vkF9: KeyF9, - vkF10: KeyF10, - vkF11: KeyF11, - vkF12: KeyF12, - vkF13: KeyF13, - vkF14: KeyF14, - vkF15: KeyF15, - vkF16: KeyF16, - vkF17: KeyF17, - vkF18: KeyF18, - vkF19: KeyF19, - vkF20: KeyF20, - vkF21: KeyF21, - vkF22: KeyF22, - vkF23: KeyF23, - vkF24: KeyF24, -} - -// NB: All Windows platforms are little endian. We assume this -// never, ever change. The following code is endian safe. and does -// not use unsafe pointers. -func getu32(v []byte) uint32 { - return uint32(v[0]) + (uint32(v[1]) << 8) + (uint32(v[2]) << 16) + (uint32(v[3]) << 24) -} - -func geti32(v []byte) int32 { - return int32(getu32(v)) -} - -func getu16(v []byte) uint16 { - return uint16(v[0]) + (uint16(v[1]) << 8) -} - -func geti16(v []byte) int16 { - return int16(getu16(v)) -} - -// Convert windows dwControlKeyState to modifier mask -func mod2mask(cks uint32, filter_ctrl_alt bool) ModMask { - mm := ModNone - // Left or right control - ctrl := (cks & (0x0008 | 0x0004)) != 0 - // Left or right alt - alt := (cks & (0x0002 | 0x0001)) != 0 - // Filter out ctrl+alt (it means AltGr) - if !filter_ctrl_alt || !(ctrl && alt) { - if ctrl { - mm |= ModCtrl - } - if alt { - mm |= ModAlt - } - } - // Any shift - if (cks & 0x0010) != 0 { - mm |= ModShift - } - return mm -} - -func mrec2btns(mbtns, flags uint32) ButtonMask { - btns := ButtonNone - if mbtns&0x1 != 0 { - btns |= Button1 - } - if mbtns&0x2 != 0 { - btns |= Button2 - } - if mbtns&0x4 != 0 { - btns |= Button3 - } - if mbtns&0x8 != 0 { - btns |= Button4 - } - if mbtns&0x10 != 0 { - btns |= Button5 - } - if mbtns&0x20 != 0 { - btns |= Button6 - } - if mbtns&0x40 != 0 { - btns |= Button7 - } - if mbtns&0x80 != 0 { - btns |= Button8 - } - - if flags&mouseVWheeled != 0 { - if mbtns&0x80000000 == 0 { - btns |= WheelUp - } else { - btns |= WheelDown - } - } - if flags&mouseHWheeled != 0 { - if mbtns&0x80000000 == 0 { - btns |= WheelRight - } else { - btns |= WheelLeft - } - } - return btns -} - -func (s *cScreen) postEvent(ev Event) { - select { - case s.eventQ <- ev: - case <-s.quit: - } -} - -func (s *cScreen) getConsoleInput() error { - // cancelFlag comes first as WaitForMultipleObjects returns the lowest index - // in the event that both events are signalled. - waitObjects := []syscall.Handle{s.cancelflag, s.in} - // As arrays are contiguous in memory, a pointer to the first object is the - // same as a pointer to the array itself. - pWaitObjects := unsafe.Pointer(&waitObjects[0]) - - rv, _, er := procWaitForMultipleObjects.Call( - uintptr(len(waitObjects)), - uintptr(pWaitObjects), - uintptr(0), - w32Infinite) - // WaitForMultipleObjects returns WAIT_OBJECT_0 + the index. - switch rv { - case w32WaitObject0: // s.cancelFlag - return errors.New("cancelled") - case w32WaitObject0 + 1: // s.in - rec := &inputRecord{} - var nrec int32 - rv, _, er := procReadConsoleInput.Call( - uintptr(s.in), - uintptr(unsafe.Pointer(rec)), - uintptr(1), - uintptr(unsafe.Pointer(&nrec))) - if rv == 0 { - return er - } - if nrec != 1 { - return nil - } - switch rec.typ { - case keyEvent: - krec := &keyRecord{} - krec.isdown = geti32(rec.data[0:]) - krec.repeat = getu16(rec.data[4:]) - krec.kcode = getu16(rec.data[6:]) - krec.scode = getu16(rec.data[8:]) - krec.ch = getu16(rec.data[10:]) - krec.mod = getu32(rec.data[12:]) - - if krec.isdown == 0 || krec.repeat < 1 { - // it's a key release event, ignore it - return nil - } - if krec.ch != 0 { - // synthesized key code - for krec.repeat > 0 { - // convert shift+tab to backtab - if mod2mask(krec.mod, false) == ModShift && krec.ch == vkTab { - s.postEvent(NewEventKey(KeyBacktab, 0, ModNone)) - } else { - s.postEvent(NewEventKey(KeyRune, rune(krec.ch), mod2mask(krec.mod, true))) - } - krec.repeat-- - } - return nil - } - key := KeyNUL // impossible on Windows - ok := false - if key, ok = vkKeys[krec.kcode]; !ok { - return nil - } - for krec.repeat > 0 { - s.postEvent(NewEventKey(key, rune(krec.ch), mod2mask(krec.mod, false))) - krec.repeat-- - } - - case mouseEvent: - var mrec mouseRecord - mrec.x = geti16(rec.data[0:]) - mrec.y = geti16(rec.data[2:]) - mrec.btns = getu32(rec.data[4:]) - mrec.mod = getu32(rec.data[8:]) - mrec.flags = getu32(rec.data[12:]) - btns := mrec2btns(mrec.btns, mrec.flags) - // we ignore double click, events are delivered normally - s.postEvent(NewEventMouse(int(mrec.x), int(mrec.y), btns, mod2mask(mrec.mod, false))) - - case resizeEvent: - var rrec resizeRecord - rrec.x = geti16(rec.data[0:]) - rrec.y = geti16(rec.data[2:]) - s.postEvent(NewEventResize(int(rrec.x), int(rrec.y))) - - case focusEvent: - var focus focusRecord - focus.focused = geti32(rec.data[0:]) - s.Lock() - enabled := s.focusEnable - s.Unlock() - if enabled { - s.postEvent(NewEventFocus(focus.focused != 0)) - } - - default: - } - default: - return er - } - - return nil -} - -func (s *cScreen) scanInput(stopQ chan struct{}) { - defer s.wg.Done() - for { - select { - case <-stopQ: - return - default: - } - if e := s.getConsoleInput(); e != nil { - return - } - } -} - -func (s *cScreen) Colors() int { - if s.vten { - return 1 << 24 - } - // Windows console can display 8 colors, in either low or high intensity - return 16 -} - -var vgaColors = map[Color]uint16{ - ColorBlack: 0, - ColorMaroon: 0x4, - ColorGreen: 0x2, - ColorNavy: 0x1, - ColorOlive: 0x6, - ColorPurple: 0x5, - ColorTeal: 0x3, - ColorSilver: 0x7, - ColorGrey: 0x8, - ColorRed: 0xc, - ColorLime: 0xa, - ColorBlue: 0x9, - ColorYellow: 0xe, - ColorFuchsia: 0xd, - ColorAqua: 0xb, - ColorWhite: 0xf, -} - -// Windows uses RGB signals -func mapColor2RGB(c Color) uint16 { - winLock.Lock() - if v, ok := winColors[c]; ok { - c = v - } else { - v = FindColor(c, winPalette) - winColors[c] = v - c = v - } - winLock.Unlock() - - if vc, ok := vgaColors[c]; ok { - return vc - } - return 0 -} - -// Map a tcell style to Windows attributes -func (s *cScreen) mapStyle(style Style) uint16 { - f, b, a := style.fg, style.bg, style.attrs - fa := s.oscreen.attrs & 0xf - ba := (s.oscreen.attrs) >> 4 & 0xf - if f != ColorDefault && f != ColorReset { - fa = mapColor2RGB(f) - } - if b != ColorDefault && b != ColorReset { - ba = mapColor2RGB(b) - } - var attr uint16 - // We simulate reverse by doing the color swap ourselves. - // Apparently windows cannot really do this except in DBCS - // views. - if a&AttrReverse != 0 { - attr = ba - attr |= fa << 4 - } else { - attr = fa - attr |= ba << 4 - } - if a&AttrBold != 0 { - attr |= 0x8 - } - if a&AttrDim != 0 { - attr &^= 0x8 - } - if a&AttrUnderline != 0 { - // Best effort -- doesn't seem to work though. - attr |= 0x8000 - } - // Blink is unsupported - return attr -} - -func (s *cScreen) makeVtStyle(style Style) string { - esc := &strings.Builder{} - - fg, bg, attrs := style.fg, style.bg, style.attrs - us, uc := style.ulStyle, style.ulColor - - esc.WriteString(vtSgr0) - if attrs&(AttrBold|AttrDim) == AttrBold { - esc.WriteString(vtBold) - } - if attrs&AttrBlink != 0 { - esc.WriteString(vtBlink) - } - if us != UnderlineStyleNone { - if uc == ColorReset { - esc.WriteString(vtUnderColorReset) - } else if uc.IsRGB() { - r, g, b := uc.RGB() - _, _ = fmt.Fprintf(esc, vtUnderColorRGB, int(r), int(g), int(b)) - } else if uc.Valid() { - _, _ = fmt.Fprintf(esc, vtUnderColor, uc&0xff) - } - - esc.WriteString(vtUnderline) - // legacy ConHost does not understand these but Terminal does - switch us { - case UnderlineStyleSolid: - case UnderlineStyleDouble: - esc.WriteString(vtDoubleUnderline) - case UnderlineStyleCurly: - esc.WriteString(vtCurlyUnderline) - case UnderlineStyleDotted: - esc.WriteString(vtDottedUnderline) - case UnderlineStyleDashed: - esc.WriteString(vtDashedUnderline) - } - } - - if attrs&AttrReverse != 0 { - esc.WriteString(vtReverse) - } - if fg.IsRGB() { - r, g, b := fg.RGB() - _, _ = fmt.Fprintf(esc, vtSetFgRGB, r, g, b) - } else if fg.Valid() { - _, _ = fmt.Fprintf(esc, vtSetFg, fg&0xff) - } - if bg.IsRGB() { - r, g, b := bg.RGB() - _, _ = fmt.Fprintf(esc, vtSetBgRGB, r, g, b) - } else if bg.Valid() { - _, _ = fmt.Fprintf(esc, vtSetBg, bg&0xff) - } - // URL string can be long, so don't send it unless we really need to - if style.url != "" { - _, _ = fmt.Fprintf(esc, vtEnterUrl, style.urlId, style.url) - } else { - esc.WriteString(vtExitUrl) - } - - return esc.String() -} - -func (s *cScreen) sendVtStyle(style Style) { - s.emitVtString(s.makeVtStyle(style)) -} - -func (s *cScreen) writeString(x, y int, style Style, vtBuf, ch []uint16) { - // we assume the caller has hidden the cursor - if len(ch) == 0 { - return - } - - if s.vten { - vtBuf = append(vtBuf, utf16.Encode([]rune(fmt.Sprintf(vtCursorPos, y+1, x+1)))...) - styleStr := s.makeVtStyle(style) - vtBuf = append(vtBuf, utf16.Encode([]rune(styleStr))...) - vtBuf = append(vtBuf, ch...) - _ = syscall.WriteConsole(s.out, &vtBuf[0], uint32(len(vtBuf)), nil, nil) - vtBuf = vtBuf[:0] - } else { - s.setCursorPos(x, y, s.vten) - _, _, _ = procSetConsoleTextAttribute.Call( - uintptr(s.out), - uintptr(s.mapStyle(style))) - _ = syscall.WriteConsole(s.out, &ch[0], uint32(len(ch)), nil, nil) - } -} - -func (s *cScreen) draw() { - // allocate a scratch line bit enough for no combining chars. - // if you have combining characters, you may pay for extra allocations. - buf := make([]uint16, 0, s.w) - var vtBuf []uint16 - wcs := buf[:] - lstyle := styleInvalid - - lx, ly := -1, -1 - ra := make([]rune, 1) - - for y := 0; y < s.h; y++ { - for x := 0; x < s.w; x++ { - mainc, combc, style, width := s.cells.GetContent(x, y) - dirty := s.cells.Dirty(x, y) - if style == StyleDefault { - style = s.style - } - - if !dirty || style != lstyle { - // write out any data queued thus far - // because we are going to skip over some - // cells, or because we need to change styles - s.writeString(lx, ly, lstyle, vtBuf, wcs) - wcs = buf[0:0] - lstyle = StyleDefault - if !dirty { - continue - } - } - if x > s.w-width { - mainc = ' ' - combc = nil - width = 1 - } - if len(wcs) == 0 { - lstyle = style - lx = x - ly = y - } - ra[0] = mainc - wcs = append(wcs, utf16.Encode(ra)...) - if len(combc) != 0 { - wcs = append(wcs, utf16.Encode(combc)...) - } - for dx := 0; dx < width; dx++ { - s.cells.SetDirty(x+dx, y, false) - } - x += width - 1 - } - s.writeString(lx, ly, lstyle, vtBuf, wcs) - wcs = buf[0:0] - lstyle = styleInvalid - } -} - -func (s *cScreen) Show() { - s.Lock() - if !s.fini { - s.hideCursor() - s.resize() - s.draw() - s.doCursor() - } - s.Unlock() -} - -func (s *cScreen) Sync() { - s.Lock() - if !s.fini { - s.cells.Invalidate() - s.hideCursor() - s.resize() - s.draw() - s.doCursor() - } - s.Unlock() -} - -type consoleInfo struct { - size coord - pos coord - attrs uint16 - win rect - maxsz coord -} - -func (s *cScreen) getConsoleInfo(info *consoleInfo) { - _, _, _ = procGetConsoleScreenBufferInfo.Call( - uintptr(s.out), - uintptr(unsafe.Pointer(info))) -} - -func (s *cScreen) getCursorInfo(info *cursorInfo) { - _, _, _ = procGetConsoleCursorInfo.Call( - uintptr(s.out), - uintptr(unsafe.Pointer(info))) -} - -func (s *cScreen) setCursorInfo(info *cursorInfo) { - _, _, _ = procSetConsoleCursorInfo.Call( - uintptr(s.out), - uintptr(unsafe.Pointer(info))) -} - -func (s *cScreen) setCursorPos(x, y int, vtEnable bool) { - if vtEnable { - // Note that the string is Y first. Origin is 1,1. - s.emitVtString(fmt.Sprintf(vtCursorPos, y+1, x+1)) - } else { - _, _, _ = procSetConsoleCursorPosition.Call( - uintptr(s.out), - coord{int16(x), int16(y)}.uintptr()) - } -} - -func (s *cScreen) setBufferSize(x, y int) { - _, _, _ = procSetConsoleScreenBufferSize.Call( - uintptr(s.out), - coord{int16(x), int16(y)}.uintptr()) -} - -func (s *cScreen) Size() (int, int) { - s.Lock() - w, h := s.w, s.h - s.Unlock() - - return w, h -} - -func (s *cScreen) SetSize(w, h int) { - xy, _, _ := procGetLargestConsoleWindowSize.Call(uintptr(s.out)) - - // xy is little endian packed - y := int(xy >> 16) - x := int(xy & 0xffff) - - if x == 0 || y == 0 { - return - } - - // This is a hacky workaround for Windows Terminal. - // Essentially Windows Terminal (Windows 11) does not support application - // initiated resizing. To detect this, we look for an extremely large size - // for the maximum width. If it is > 500, then this is almost certainly - // Windows Terminal, and won't support this. (Note that the legacy console - // does support application resizing.) - if x >= 500 { - return - } - - s.setBufferSize(x, y) - r := rect{0, 0, int16(w - 1), int16(h - 1)} - _, _, _ = procSetConsoleWindowInfo.Call( - uintptr(s.out), - uintptr(1), - uintptr(unsafe.Pointer(&r))) - - s.resize() -} - -func (s *cScreen) resize() { - info := consoleInfo{} - s.getConsoleInfo(&info) - - w := int((info.win.right - info.win.left) + 1) - h := int((info.win.bottom - info.win.top) + 1) - - if s.w == w && s.h == h { - return - } - - s.cells.Resize(w, h) - s.w = w - s.h = h - - s.setBufferSize(w, h) - - r := rect{0, 0, int16(w - 1), int16(h - 1)} - _, _, _ = procSetConsoleWindowInfo.Call( - uintptr(s.out), - uintptr(1), - uintptr(unsafe.Pointer(&r))) - select { - case s.eventQ <- NewEventResize(w, h): - default: - } -} - -func (s *cScreen) clearScreen(style Style, vtEnable bool) { - if vtEnable { - s.sendVtStyle(style) - row := strings.Repeat(" ", s.w) - for y := 0; y < s.h; y++ { - s.setCursorPos(0, y, vtEnable) - s.emitVtString(row) - } - s.setCursorPos(0, 0, vtEnable) - - } else { - pos := coord{0, 0} - attr := s.mapStyle(style) - x, y := s.w, s.h - scratch := uint32(0) - count := uint32(x * y) - - _, _, _ = procFillConsoleOutputAttribute.Call( - uintptr(s.out), - uintptr(attr), - uintptr(count), - pos.uintptr(), - uintptr(unsafe.Pointer(&scratch))) - _, _, _ = procFillConsoleOutputCharacter.Call( - uintptr(s.out), - uintptr(' '), - uintptr(count), - pos.uintptr(), - uintptr(unsafe.Pointer(&scratch))) - } -} - -const ( - // Input modes - modeExtendFlg uint32 = 0x0080 - modeMouseEn = 0x0010 - modeResizeEn = 0x0008 - // modeCooked = 0x0001 - // modeVtInput = 0x0200 - - // Output modes - modeCookedOut uint32 = 0x0001 - modeVtOutput = 0x0004 - modeNoAutoNL = 0x0008 - modeUnderline = 0x0010 // ENABLE_LVB_GRID_WORLDWIDE, needed for underlines - // modeWrapEOL = 0x0002 -) - -func (s *cScreen) setInMode(mode uint32) { - _, _, _ = procSetConsoleMode.Call( - uintptr(s.in), - uintptr(mode)) -} - -func (s *cScreen) setOutMode(mode uint32) { - _, _, _ = procSetConsoleMode.Call( - uintptr(s.out), - uintptr(mode)) -} - -func (s *cScreen) getInMode(v *uint32) { - _, _, _ = procGetConsoleMode.Call( - uintptr(s.in), - uintptr(unsafe.Pointer(v))) -} - -func (s *cScreen) getOutMode(v *uint32) { - _, _, _ = procGetConsoleMode.Call( - uintptr(s.out), - uintptr(unsafe.Pointer(v))) -} - -func (s *cScreen) SetStyle(style Style) { - s.Lock() - s.style = style - s.Unlock() -} - -func (s *cScreen) SetTitle(title string) { - s.Lock() - s.title = title - if s.vten { - s.emitVtString(fmt.Sprintf(vtSetTitle, title)) - } - s.Unlock() -} - -// No fallback rune support, since we have Unicode. Yay! - -func (s *cScreen) RegisterRuneFallback(_ rune, _ string) { -} - -func (s *cScreen) UnregisterRuneFallback(_ rune) { -} - -func (s *cScreen) CanDisplay(_ rune, _ bool) bool { - // We presume we can display anything -- we're Unicode. - // (Sadly this not precisely true. Combining characters are especially - // poorly supported under Windows.) - return true -} - -func (s *cScreen) HasMouse() bool { - return true -} - -func (s *cScreen) SetClipboard(_ []byte) { -} - -func (s *cScreen) GetClipboard() { -} - -func (s *cScreen) Resize(int, int, int, int) {} - -func (s *cScreen) HasKey(k Key) bool { - // Microsoft has codes for some keys, but they are unusual, - // so we don't include them. We include all the typical - // 101, 105 key layout keys. - valid := map[Key]bool{ - KeyBackspace: true, - KeyTab: true, - KeyEscape: true, - KeyPause: true, - KeyPrint: true, - KeyPgUp: true, - KeyPgDn: true, - KeyEnter: true, - KeyEnd: true, - KeyHome: true, - KeyLeft: true, - KeyUp: true, - KeyRight: true, - KeyDown: true, - KeyInsert: true, - KeyDelete: true, - KeyF1: true, - KeyF2: true, - KeyF3: true, - KeyF4: true, - KeyF5: true, - KeyF6: true, - KeyF7: true, - KeyF8: true, - KeyF9: true, - KeyF10: true, - KeyF11: true, - KeyF12: true, - KeyRune: true, - } - - return valid[k] -} - -func (s *cScreen) Beep() error { - // A simple beep. If the sound card is not available, the sound is generated - // using the speaker. - // - // Reference: - // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebeep - const simpleBeep = 0xffffffff - if rv, _, err := procMessageBeep.Call(simpleBeep); rv == 0 { - return err - } - return nil -} - -func (s *cScreen) Suspend() error { - s.disengage() - return nil -} - -func (s *cScreen) Resume() error { - return s.engage() -} - -func (s *cScreen) Tty() (Tty, bool) { - return nil, false -} - -func (s *cScreen) GetCells() *CellBuffer { - return &s.cells -} - -func (s *cScreen) EventQ() chan Event { - return s.eventQ -} - -func (s *cScreen) StopQ() <-chan struct{} { - return s.quit -} diff --git a/vendor/github.com/gdamore/tcell/v2/doc.go b/vendor/github.com/gdamore/tcell/v2/doc.go deleted file mode 100644 index 690dd27ad07..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/doc.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2018 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package tcell provides a lower-level, portable API for building -// programs that interact with terminals or consoles. It works with -// both common (and many uncommon!) terminals or terminal emulators, -// and Windows console implementations. -// -// It provides support for up to 256 colors, text attributes, and box drawing -// elements. A database of terminals built from a real terminfo database -// is provided, along with code to generate new database entries. -// -// Tcell offers very rich support for mice, dependent upon the terminal -// of course. (Windows, XTerm, and iTerm 2 are known to work very well.) -// -// If the environment is not Unicode by default, such as an ISO8859 based -// locale or GB18030, Tcell can convert input and output, so that your -// terminal can operate in whatever locale is most convenient, while the -// application program can just assume "everything is UTF-8". Reasonable -// defaults are used for updating characters to something suitable for -// display. Unicode box drawing characters will be converted to use the -// alternate character set of your terminal, if native conversions are -// not available. If no ACS is available, then some ASCII fallbacks will -// be used. -// -// Note that support for non-UTF-8 locales (other than C) must be enabled -// by the application using RegisterEncoding() -- we don't have them all -// enabled by default to avoid bloating the application unnecessarily. -// (These days UTF-8 is good enough for almost everyone, and nobody should -// be using legacy locales anymore.) Also, actual glyphs for various code -// point will only be displayed if your terminal or emulator (or the font -// the emulator is using) supports them. -// -// A rich set of key codes is supported, with support for up to 65 function -// keys, and various other special keys. -package tcell diff --git a/vendor/github.com/gdamore/tcell/v2/encoding.go b/vendor/github.com/gdamore/tcell/v2/encoding.go deleted file mode 100644 index b7644c27e85..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/encoding.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2022 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "strings" - "sync" - - "golang.org/x/text/encoding" - - gencoding "github.com/gdamore/encoding" -) - -var encodings map[string]encoding.Encoding -var encodingLk sync.Mutex -var encodingFallback EncodingFallback = EncodingFallbackFail - -// RegisterEncoding may be called by the application to register an encoding. -// The presence of additional encodings will facilitate application usage with -// terminal environments where the I/O subsystem does not support Unicode. -// -// Windows systems use Unicode natively, and do not need any of the encoding -// subsystem when using Windows Console screens. -// -// Please see the Go documentation for golang.org/x/text/encoding -- most of -// the common ones exist already as stock variables. For example, ISO8859-15 -// can be registered using the following code: -// -// import "golang.org/x/text/encoding/charmap" -// -// ... -// RegisterEncoding("ISO8859-15", charmap.ISO8859_15) -// -// Aliases can be registered as well, for example "8859-15" could be an alias -// for "ISO8859-15". -// -// For POSIX systems, this package will check the environment variables -// LC_ALL, LC_CTYPE, and LANG (in that order) to determine the character set. -// These are expected to have the following pattern: -// -// $language[.$codeset[@$variant] -// -// We extract only the $codeset part, which will usually be something like -// UTF-8 or ISO8859-15 or KOI8-R. Note that if the locale is either "POSIX" -// or "C", then we assume US-ASCII (the POSIX 'portable character set' -// and assume all other characters are somehow invalid.) -// -// Modern POSIX systems and terminal emulators may use UTF-8, and for those -// systems, this API is also unnecessary. For example, Darwin (MacOS X) and -// modern Linux running modern xterm generally will out of the box without -// any of this. Use of UTF-8 is recommended when possible, as it saves -// quite a lot processing overhead. -// -// Note that some encodings are quite large (for example GB18030 which is a -// superset of Unicode) and so the application size can be expected to -// increase quite a bit as each encoding is added. - -// The East Asian encodings have been seen to add 100-200K per encoding to the -// size of the resulting binary. -func RegisterEncoding(charset string, enc encoding.Encoding) { - encodingLk.Lock() - charset = strings.ToLower(charset) - encodings[charset] = enc - encodingLk.Unlock() -} - -// EncodingFallback describes how the system behaves when the locale -// requires a character set that we do not support. The system always -// supports UTF-8 and US-ASCII. On Windows consoles, UTF-16LE is also -// supported automatically. Other character sets must be added using the -// RegisterEncoding API. (A large group of nearly all of them can be -// added using the RegisterAll function in the encoding sub package.) -type EncodingFallback int - -const ( - // EncodingFallbackFail behavior causes GetEncoding to fail - // when it cannot find an encoding. - EncodingFallbackFail = iota - - // EncodingFallbackASCII behavior causes GetEncoding to fall back - // to a 7-bit ASCII encoding, if no other encoding can be found. - EncodingFallbackASCII - - // EncodingFallbackUTF8 behavior causes GetEncoding to assume - // UTF8 can pass unmodified upon failure. Note that this behavior - // is not recommended, unless you are sure your terminal can cope - // with real UTF8 sequences. - EncodingFallbackUTF8 -) - -// SetEncodingFallback changes the behavior of GetEncoding when a suitable -// encoding is not found. The default is EncodingFallbackFail, which -// causes GetEncoding to simply return nil. -func SetEncodingFallback(fb EncodingFallback) { - encodingLk.Lock() - encodingFallback = fb - encodingLk.Unlock() -} - -// GetEncoding is used by Screen implementors who want to locate an encoding -// for the given character set name. Note that this will return nil for -// either the Unicode (UTF-8) or ASCII encodings, since we don't use -// encodings for them but instead have our own native methods. -func GetEncoding(charset string) encoding.Encoding { - charset = strings.ToLower(charset) - encodingLk.Lock() - defer encodingLk.Unlock() - if enc, ok := encodings[charset]; ok { - return enc - } - switch encodingFallback { - case EncodingFallbackASCII: - return gencoding.ASCII - case EncodingFallbackUTF8: - return encoding.Nop - } - return nil -} - -func init() { - // We always support UTF-8 and ASCII. - encodings = make(map[string]encoding.Encoding) - encodings["utf-8"] = gencoding.UTF8 - encodings["utf8"] = gencoding.UTF8 - encodings["us-ascii"] = gencoding.ASCII - encodings["ascii"] = gencoding.ASCII - encodings["iso646"] = gencoding.ASCII -} diff --git a/vendor/github.com/gdamore/tcell/v2/errors.go b/vendor/github.com/gdamore/tcell/v2/errors.go deleted file mode 100644 index 201dff9f806..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/errors.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2015 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "errors" - "time" - - "github.com/gdamore/tcell/v2/terminfo" -) - -var ( - // ErrTermNotFound indicates that a suitable terminal entry could - // not be found. This can result from either not having TERM set, - // or from the TERM failing to support certain minimal functionality, - // in particular absolute cursor addressability (the cup capability) - // is required. For example, legacy "adm3" lacks this capability, - // whereas the slightly newer "adm3a" supports it. This failure - // occurs most often with "dumb". - ErrTermNotFound = terminfo.ErrTermNotFound - - // ErrNoScreen indicates that no suitable screen could be found. - // This may result from attempting to run on a platform where there - // is no support for either termios or console I/O (such as nacl), - // or from running in an environment where there is no access to - // a suitable console/terminal device. (For example, running on - // without a controlling TTY or with no /dev/tty on POSIX platforms.) - ErrNoScreen = errors.New("no suitable screen available") - - // ErrNoCharset indicates that the locale environment the - // program is not supported by the program, because no suitable - // encoding was found for it. This problem never occurs if - // the environment is UTF-8 or UTF-16. - ErrNoCharset = errors.New("character set not supported") - - // ErrEventQFull indicates that the event queue is full, and - // cannot accept more events. - ErrEventQFull = errors.New("event queue full") -) - -// An EventError is an event representing some sort of error, and carries -// an error payload. -type EventError struct { - t time.Time - err error -} - -// When returns the time when the event was created. -func (ev *EventError) When() time.Time { - return ev.t -} - -// Error implements the error. -func (ev *EventError) Error() string { - return ev.err.Error() -} - -// NewEventError creates an ErrorEvent with the given error payload. -func NewEventError(err error) *EventError { - return &EventError{t: time.Now(), err: err} -} diff --git a/vendor/github.com/gdamore/tcell/v2/event.go b/vendor/github.com/gdamore/tcell/v2/event.go deleted file mode 100644 index a3b770063be..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/event.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2015 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "time" -) - -// Event is a generic interface used for passing around Events. -// Concrete types follow. -type Event interface { - // When reports the time when the event was generated. - When() time.Time -} - -// EventTime is a simple base event class, suitable for easy reuse. -// It can be used to deliver actual timer events as well. -type EventTime struct { - when time.Time -} - -// When returns the time stamp when the event occurred. -func (e *EventTime) When() time.Time { - return e.when -} - -// SetEventTime sets the time of occurrence for the event. -func (e *EventTime) SetEventTime(t time.Time) { - e.when = t -} - -// SetEventNow sets the time of occurrence for the event to the current time. -func (e *EventTime) SetEventNow() { - e.SetEventTime(time.Now()) -} - -// EventHandler is anything that handles events. If the handler has -// consumed the event, it should return true. False otherwise. -type EventHandler interface { - HandleEvent(Event) bool -} diff --git a/vendor/github.com/gdamore/tcell/v2/focus.go b/vendor/github.com/gdamore/tcell/v2/focus.go deleted file mode 100644 index e9b93ef6dec..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/focus.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2023 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -// EventFocus is a focus event. It is sent when the terminal window (or tab) -// gets or loses focus. -type EventFocus struct { - *EventTime - - // True if the window received focus, false if it lost focus - Focused bool -} - -func NewEventFocus(focused bool) *EventFocus { - return &EventFocus{Focused: focused} -} diff --git a/vendor/github.com/gdamore/tcell/v2/interrupt.go b/vendor/github.com/gdamore/tcell/v2/interrupt.go deleted file mode 100644 index 70dddfce2f5..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/interrupt.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2015 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "time" -) - -// EventInterrupt is a generic wakeup event. Its can be used to -// to request a redraw. It can carry an arbitrary payload, as well. -type EventInterrupt struct { - t time.Time - v interface{} -} - -// When returns the time when this event was created. -func (ev *EventInterrupt) When() time.Time { - return ev.t -} - -// Data is used to obtain the opaque event payload. -func (ev *EventInterrupt) Data() interface{} { - return ev.v -} - -// NewEventInterrupt creates an EventInterrupt with the given payload. -func NewEventInterrupt(data interface{}) *EventInterrupt { - return &EventInterrupt{t: time.Now(), v: data} -} diff --git a/vendor/github.com/gdamore/tcell/v2/key.go b/vendor/github.com/gdamore/tcell/v2/key.go deleted file mode 100644 index 9741e699fee..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/key.go +++ /dev/null @@ -1,470 +0,0 @@ -// Copyright 2016 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "fmt" - "strings" - "time" -) - -// EventKey represents a key press. Usually this is a key press followed -// by a key release, but since terminal programs don't have a way to report -// key release events, we usually get just one event. If a key is held down -// then the terminal may synthesize repeated key presses at some predefined -// rate. We have no control over that, nor visibility into it. -// -// In some cases, we can have a modifier key, such as ModAlt, that can be -// generated with a key press. (This usually is represented by having the -// high bit set, or in some cases, by sending an ESC prior to the rune.) -// -// If the value of Key() is KeyRune, then the actual key value will be -// available with the Rune() method. This will be the case for most keys. -// In most situations, the modifiers will not be set. For example, if the -// rune is 'A', this will be reported without the ModShift bit set, since -// really can't tell if the Shift key was pressed (it might have been CAPSLOCK, -// or a terminal that only can send capitals, or keyboard with separate -// capital letters from lower case letters). -// -// Generally, terminal applications have far less visibility into keyboard -// activity than graphical applications. Hence, they should avoid depending -// overly much on availability of modifiers, or the availability of any -// specific keys. -type EventKey struct { - t time.Time - mod ModMask - key Key - ch rune -} - -// When returns the time when this Event was created, which should closely -// match the time when the key was pressed. -func (ev *EventKey) When() time.Time { - return ev.t -} - -// Rune returns the rune corresponding to the key press, if it makes sense. -// The result is only defined if the value of Key() is KeyRune. -func (ev *EventKey) Rune() rune { - return ev.ch -} - -// Key returns a virtual key code. We use this to identify specific key -// codes, such as KeyEnter, etc. Most control and function keys are reported -// with unique Key values. Normal alphanumeric and punctuation keys will -// generally return KeyRune here; the specific key can be further decoded -// using the Rune() function. -func (ev *EventKey) Key() Key { - return ev.key -} - -// Modifiers returns the modifiers that were present with the key press. Note -// that not all platforms and terminals support this equally well, and some -// cases we will not not know for sure. Hence, applications should avoid -// using this in most circumstances. -func (ev *EventKey) Modifiers() ModMask { - return ev.mod -} - -// KeyNames holds the written names of special keys. Useful to echo back a key -// name, or to look up a key from a string value. -var KeyNames = map[Key]string{ - KeyEnter: "Enter", - KeyBackspace: "Backspace", - KeyTab: "Tab", - KeyBacktab: "Backtab", - KeyEsc: "Esc", - KeyBackspace2: "Backspace2", - KeyDelete: "Delete", - KeyInsert: "Insert", - KeyUp: "Up", - KeyDown: "Down", - KeyLeft: "Left", - KeyRight: "Right", - KeyHome: "Home", - KeyEnd: "End", - KeyUpLeft: "UpLeft", - KeyUpRight: "UpRight", - KeyDownLeft: "DownLeft", - KeyDownRight: "DownRight", - KeyCenter: "Center", - KeyPgDn: "PgDn", - KeyPgUp: "PgUp", - KeyClear: "Clear", - KeyExit: "Exit", - KeyCancel: "Cancel", - KeyPause: "Pause", - KeyPrint: "Print", - KeyF1: "F1", - KeyF2: "F2", - KeyF3: "F3", - KeyF4: "F4", - KeyF5: "F5", - KeyF6: "F6", - KeyF7: "F7", - KeyF8: "F8", - KeyF9: "F9", - KeyF10: "F10", - KeyF11: "F11", - KeyF12: "F12", - KeyF13: "F13", - KeyF14: "F14", - KeyF15: "F15", - KeyF16: "F16", - KeyF17: "F17", - KeyF18: "F18", - KeyF19: "F19", - KeyF20: "F20", - KeyF21: "F21", - KeyF22: "F22", - KeyF23: "F23", - KeyF24: "F24", - KeyF25: "F25", - KeyF26: "F26", - KeyF27: "F27", - KeyF28: "F28", - KeyF29: "F29", - KeyF30: "F30", - KeyF31: "F31", - KeyF32: "F32", - KeyF33: "F33", - KeyF34: "F34", - KeyF35: "F35", - KeyF36: "F36", - KeyF37: "F37", - KeyF38: "F38", - KeyF39: "F39", - KeyF40: "F40", - KeyF41: "F41", - KeyF42: "F42", - KeyF43: "F43", - KeyF44: "F44", - KeyF45: "F45", - KeyF46: "F46", - KeyF47: "F47", - KeyF48: "F48", - KeyF49: "F49", - KeyF50: "F50", - KeyF51: "F51", - KeyF52: "F52", - KeyF53: "F53", - KeyF54: "F54", - KeyF55: "F55", - KeyF56: "F56", - KeyF57: "F57", - KeyF58: "F58", - KeyF59: "F59", - KeyF60: "F60", - KeyF61: "F61", - KeyF62: "F62", - KeyF63: "F63", - KeyF64: "F64", - KeyCtrlA: "Ctrl-A", - KeyCtrlB: "Ctrl-B", - KeyCtrlC: "Ctrl-C", - KeyCtrlD: "Ctrl-D", - KeyCtrlE: "Ctrl-E", - KeyCtrlF: "Ctrl-F", - KeyCtrlG: "Ctrl-G", - KeyCtrlJ: "Ctrl-J", - KeyCtrlK: "Ctrl-K", - KeyCtrlL: "Ctrl-L", - KeyCtrlN: "Ctrl-N", - KeyCtrlO: "Ctrl-O", - KeyCtrlP: "Ctrl-P", - KeyCtrlQ: "Ctrl-Q", - KeyCtrlR: "Ctrl-R", - KeyCtrlS: "Ctrl-S", - KeyCtrlT: "Ctrl-T", - KeyCtrlU: "Ctrl-U", - KeyCtrlV: "Ctrl-V", - KeyCtrlW: "Ctrl-W", - KeyCtrlX: "Ctrl-X", - KeyCtrlY: "Ctrl-Y", - KeyCtrlZ: "Ctrl-Z", - KeyCtrlSpace: "Ctrl-Space", - KeyCtrlUnderscore: "Ctrl-_", - KeyCtrlRightSq: "Ctrl-]", - KeyCtrlBackslash: "Ctrl-\\", - KeyCtrlCarat: "Ctrl-^", -} - -// Name returns a printable value or the key stroke. This can be used -// when printing the event, for example. -func (ev *EventKey) Name() string { - s := "" - m := []string{} - if ev.mod&ModShift != 0 { - m = append(m, "Shift") - } - if ev.mod&ModAlt != 0 { - m = append(m, "Alt") - } - if ev.mod&ModMeta != 0 { - m = append(m, "Meta") - } - if ev.mod&ModCtrl != 0 { - m = append(m, "Ctrl") - } - - ok := false - if s, ok = KeyNames[ev.key]; !ok { - if ev.key == KeyRune { - s = "Rune[" + string(ev.ch) + "]" - } else { - s = fmt.Sprintf("Key[%d,%d]", ev.key, int(ev.ch)) - } - } - if len(m) != 0 { - if ev.mod&ModCtrl != 0 && strings.HasPrefix(s, "Ctrl-") { - s = s[5:] - } - return fmt.Sprintf("%s+%s", strings.Join(m, "+"), s) - } - return s -} - -// NewEventKey attempts to create a suitable event. It parses the various -// ASCII control sequences if KeyRune is passed for Key, but if the caller -// has more precise information it should set that specifically. Callers -// that aren't sure about modifier state (most) should just pass ModNone. -func NewEventKey(k Key, ch rune, mod ModMask) *EventKey { - if k == KeyRune && (ch < ' ' || ch == 0x7f) { - // Turn specials into proper key codes. This is for - // control characters and the DEL. - k = Key(ch) - if mod == ModNone && ch < ' ' { - switch Key(ch) { - case KeyBackspace, KeyTab, KeyEsc, KeyEnter: - // these keys are directly typeable without CTRL - default: - // most likely entered with a CTRL keypress - mod = ModCtrl - } - } - } - return &EventKey{t: time.Now(), key: k, ch: ch, mod: mod} -} - -// ModMask is a mask of modifier keys. Note that it will not always be -// possible to report modifier keys. -type ModMask int16 - -// These are the modifiers keys that can be sent either with a key press, -// or a mouse event. Note that as of now, due to the confusion associated -// with Meta, and the lack of support for it on many/most platforms, the -// current implementations never use it. Instead, they use ModAlt, even for -// events that could possibly have been distinguished from ModAlt. -const ( - ModShift ModMask = 1 << iota - ModCtrl - ModAlt - ModMeta - ModNone ModMask = 0 -) - -// Key is a generic value for representing keys, and especially special -// keys (function keys, cursor movement keys, etc.) For normal keys, like -// ASCII letters, we use KeyRune, and then expect the application to -// inspect the Rune() member of the EventKey. -type Key int16 - -// This is the list of named keys. KeyRune is special however, in that it is -// a place holder key indicating that a printable character was sent. The -// actual value of the rune will be transported in the Rune of the associated -// EventKey. -const ( - KeyRune Key = iota + 256 - KeyUp - KeyDown - KeyRight - KeyLeft - KeyUpLeft - KeyUpRight - KeyDownLeft - KeyDownRight - KeyCenter - KeyPgUp - KeyPgDn - KeyHome - KeyEnd - KeyInsert - KeyDelete - KeyHelp - KeyExit - KeyClear - KeyCancel - KeyPrint - KeyPause - KeyBacktab - KeyF1 - KeyF2 - KeyF3 - KeyF4 - KeyF5 - KeyF6 - KeyF7 - KeyF8 - KeyF9 - KeyF10 - KeyF11 - KeyF12 - KeyF13 - KeyF14 - KeyF15 - KeyF16 - KeyF17 - KeyF18 - KeyF19 - KeyF20 - KeyF21 - KeyF22 - KeyF23 - KeyF24 - KeyF25 - KeyF26 - KeyF27 - KeyF28 - KeyF29 - KeyF30 - KeyF31 - KeyF32 - KeyF33 - KeyF34 - KeyF35 - KeyF36 - KeyF37 - KeyF38 - KeyF39 - KeyF40 - KeyF41 - KeyF42 - KeyF43 - KeyF44 - KeyF45 - KeyF46 - KeyF47 - KeyF48 - KeyF49 - KeyF50 - KeyF51 - KeyF52 - KeyF53 - KeyF54 - KeyF55 - KeyF56 - KeyF57 - KeyF58 - KeyF59 - KeyF60 - KeyF61 - KeyF62 - KeyF63 - KeyF64 -) - -const ( - // These key codes are used internally, and will never appear to applications. - keyPasteStart Key = iota + 16384 - keyPasteEnd -) - -// These are the control keys. Note that they overlap with other keys, -// perhaps. For example, KeyCtrlH is the same as KeyBackspace. -const ( - KeyCtrlSpace Key = iota - KeyCtrlA - KeyCtrlB - KeyCtrlC - KeyCtrlD - KeyCtrlE - KeyCtrlF - KeyCtrlG - KeyCtrlH - KeyCtrlI - KeyCtrlJ - KeyCtrlK - KeyCtrlL - KeyCtrlM - KeyCtrlN - KeyCtrlO - KeyCtrlP - KeyCtrlQ - KeyCtrlR - KeyCtrlS - KeyCtrlT - KeyCtrlU - KeyCtrlV - KeyCtrlW - KeyCtrlX - KeyCtrlY - KeyCtrlZ - KeyCtrlLeftSq // Escape - KeyCtrlBackslash - KeyCtrlRightSq - KeyCtrlCarat - KeyCtrlUnderscore -) - -// Special values - these are fixed in an attempt to make it more likely -// that aliases will encode the same way. - -// These are the defined ASCII values for key codes. They generally match -// with KeyCtrl values. -const ( - KeyNUL Key = iota - KeySOH - KeySTX - KeyETX - KeyEOT - KeyENQ - KeyACK - KeyBEL - KeyBS - KeyTAB - KeyLF - KeyVT - KeyFF - KeyCR - KeySO - KeySI - KeyDLE - KeyDC1 - KeyDC2 - KeyDC3 - KeyDC4 - KeyNAK - KeySYN - KeyETB - KeyCAN - KeyEM - KeySUB - KeyESC - KeyFS - KeyGS - KeyRS - KeyUS - KeyDEL Key = 0x7F -) - -// These keys are aliases for other names. -const ( - KeyBackspace = KeyBS - KeyTab = KeyTAB - KeyEsc = KeyESC - KeyEscape = KeyESC - KeyEnter = KeyCR - KeyBackspace2 = KeyDEL -) diff --git a/vendor/github.com/gdamore/tcell/v2/mouse.go b/vendor/github.com/gdamore/tcell/v2/mouse.go deleted file mode 100644 index 008c2e26c30..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/mouse.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2020 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "time" -) - -// EventMouse is a mouse event. It is sent on either mouse up or mouse down -// events. It is also sent on mouse motion events - if the terminal supports -// it. We make every effort to ensure that mouse release events are delivered. -// Hence, click drag can be identified by a motion event with the mouse down, -// without any intervening button release. On some terminals only the initiating -// press and terminating release event will be delivered. -// -// Mouse wheel events, when reported, may appear on their own as individual -// impulses; that is, there will normally not be a release event delivered -// for mouse wheel movements. -// -// Most terminals cannot report the state of more than one button at a time -- -// and some cannot report motion events unless a button is pressed. -// -// Applications can inspect the time between events to resolve double or -// triple clicks. -type EventMouse struct { - t time.Time - btn ButtonMask - mod ModMask - x int - y int -} - -// When returns the time when this EventMouse was created. -func (ev *EventMouse) When() time.Time { - return ev.t -} - -// Buttons returns the list of buttons that were pressed or wheel motions. -func (ev *EventMouse) Buttons() ButtonMask { - return ev.btn -} - -// Modifiers returns a list of keyboard modifiers that were pressed -// with the mouse button(s). -func (ev *EventMouse) Modifiers() ModMask { - return ev.mod -} - -// Position returns the mouse position in character cells. The origin -// 0, 0 is at the upper left corner. -func (ev *EventMouse) Position() (int, int) { - return ev.x, ev.y -} - -// NewEventMouse is used to create a new mouse event. Applications -// shouldn't need to use this; its mostly for screen implementors. -func NewEventMouse(x, y int, btn ButtonMask, mod ModMask) *EventMouse { - return &EventMouse{t: time.Now(), x: x, y: y, btn: btn, mod: mod} -} - -// ButtonMask is a mask of mouse buttons and wheel events. Mouse button presses -// are normally delivered as both press and release events. Mouse wheel events -// are normally just single impulse events. Windows supports up to eight -// separate buttons plus all four wheel directions, but XTerm can only support -// mouse buttons 1-3 and wheel up/down. Its not unheard of for terminals -// to support only one or two buttons (think Macs). Old terminals, and true -// emulations (such as vt100) won't support mice at all, of course. -type ButtonMask int16 - -// These are the actual button values. Note that tcell version 1.x reversed buttons -// two and three on *nix based terminals. We use button 1 as the primary, and -// button 2 as the secondary, and button 3 (which is often missing) as the middle. -const ( - Button1 ButtonMask = 1 << iota // Usually the left (primary) mouse button. - Button2 // Usually the right (secondary) mouse button. - Button3 // Usually the middle mouse button. - Button4 // Often a side button (thumb/next). - Button5 // Often a side button (thumb/prev). - Button6 - Button7 - Button8 - WheelUp // Wheel motion up/away from user. - WheelDown // Wheel motion down/towards user. - WheelLeft // Wheel motion to left. - WheelRight // Wheel motion to right. - ButtonNone ButtonMask = 0 // No button or wheel events. - - ButtonPrimary = Button1 - ButtonSecondary = Button2 - ButtonMiddle = Button3 -) diff --git a/vendor/github.com/gdamore/tcell/v2/nonblock_bsd.go b/vendor/github.com/gdamore/tcell/v2/nonblock_bsd.go deleted file mode 100644 index 622888e31ed..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/nonblock_bsd.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build darwin || dragonfly || freebsd || netbsd || openbsd -// +build darwin dragonfly freebsd netbsd openbsd - -package tcell - -import ( - "syscall" - - "golang.org/x/sys/unix" -) - -// BSD systems use TIOC style ioctls. - -// tcSetBufParams is used by the tty driver on UNIX systems to configure the -// buffering parameters (minimum character count and minimum wait time in msec.) -// This also waits for output to drain first. -func tcSetBufParams(fd int, vMin uint8, vTime uint8) error { - _ = syscall.SetNonblock(fd, true) - tio, err := unix.IoctlGetTermios(fd, unix.TIOCGETA) - if err != nil { - return err - } - tio.Cc[unix.VMIN] = vMin - tio.Cc[unix.VTIME] = vTime - if err = unix.IoctlSetTermios(fd, unix.TIOCSETAW, tio); err != nil { - return err - } - return nil -} diff --git a/vendor/github.com/gdamore/tcell/v2/nonblock_unix.go b/vendor/github.com/gdamore/tcell/v2/nonblock_unix.go deleted file mode 100644 index 160a6419d92..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/nonblock_unix.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2021 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build linux || aix || zos || solaris -// +build linux aix zos solaris - -package tcell - -import ( - "syscall" - - "golang.org/x/sys/unix" -) - -// tcSetBufParams is used by the tty driver on UNIX systems to configure the -// buffering parameters (minimum character count and minimum wait time in msec.) -// This also waits for output to drain first. -func tcSetBufParams(fd int, vMin uint8, vTime uint8) error { - _ = syscall.SetNonblock(fd, true) - tio, err := unix.IoctlGetTermios(fd, unix.TCGETS) - if err != nil { - return err - } - tio.Cc[unix.VMIN] = vMin - tio.Cc[unix.VTIME] = vTime - if err = unix.IoctlSetTermios(fd, unix.TCSETSW, tio); err != nil { - return err - } - return nil -} diff --git a/vendor/github.com/gdamore/tcell/v2/paste.go b/vendor/github.com/gdamore/tcell/v2/paste.go deleted file mode 100644 index f511f63cb59..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/paste.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2024 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "time" -) - -// EventPaste is used to mark the start and end of a bracketed paste. -// -// An event with .Start() true will be sent to mark the start of a bracketed paste, -// followed by a number of keys (string data) for the content, ending with the -// an event with .End() true. -type EventPaste struct { - start bool - t time.Time - data []byte -} - -// When returns the time when this EventPaste was created. -func (ev *EventPaste) When() time.Time { - return ev.t -} - -// Start returns true if this is the start of a paste. -func (ev *EventPaste) Start() bool { - return ev.start -} - -// End returns true if this is the end of a paste. -func (ev *EventPaste) End() bool { - return !ev.start -} - -// NewEventPaste returns a new EventPaste. -func NewEventPaste(start bool) *EventPaste { - return &EventPaste{t: time.Now(), start: start} -} - -// NewEventClipboard returns a new NewEventClipboard with a data payload -func NewEventClipboard(data []byte) *EventClipboard { - return &EventClipboard{t: time.Now(), data: data} -} - -// EventClipboard represents data from the clipboard, -// in response to a GetClipboard request. -type EventClipboard struct { - t time.Time - data []byte -} - -// Data returns the attached binary data. -func (ev *EventClipboard) Data() []byte { - return ev.data -} - -// When returns the time when this event was created. -func (ev *EventClipboard) When() time.Time { - return ev.t -} diff --git a/vendor/github.com/gdamore/tcell/v2/resize.go b/vendor/github.com/gdamore/tcell/v2/resize.go deleted file mode 100644 index f3e2b3a5fa1..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/resize.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2015 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "time" -) - -// EventResize is sent when the window size changes. -type EventResize struct { - t time.Time - ws WindowSize -} - -// NewEventResize creates an EventResize with the new updated window size, -// which is given in character cells. -func NewEventResize(width, height int) *EventResize { - ws := WindowSize{ - Width: width, - Height: height, - } - return &EventResize{t: time.Now(), ws: ws} -} - -// When returns the time when the Event was created. -func (ev *EventResize) When() time.Time { - return ev.t -} - -// Size returns the new window size as width, height in character cells. -func (ev *EventResize) Size() (int, int) { - return ev.ws.Width, ev.ws.Height -} - -// PixelSize returns the new window size as width, height in pixels. The size -// will be 0,0 if the screen doesn't support this feature -func (ev *EventResize) PixelSize() (int, int) { - return ev.ws.PixelWidth, ev.ws.PixelHeight -} - -type WindowSize struct { - Width int - Height int - PixelWidth int - PixelHeight int -} - -// CellDimensions returns the dimensions of a single cell, in pixels -func (ws WindowSize) CellDimensions() (int, int) { - if ws.PixelWidth == 0 || ws.PixelHeight == 0 { - return 0, 0 - } - return (ws.PixelWidth / ws.Width), (ws.PixelHeight / ws.Height) -} diff --git a/vendor/github.com/gdamore/tcell/v2/runes.go b/vendor/github.com/gdamore/tcell/v2/runes.go deleted file mode 100644 index ed9c63b5c1c..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/runes.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2015 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -// The names of these constants are chosen to match Terminfo names, -// modulo case, and changing the prefix from ACS_ to Rune. These are -// the runes we provide extra special handling for, with ASCII fallbacks -// for terminals that lack them. -const ( - RuneSterling = '£' - RuneDArrow = '↓' - RuneLArrow = '←' - RuneRArrow = '→' - RuneUArrow = '↑' - RuneBullet = '·' - RuneBoard = '░' - RuneCkBoard = '▒' - RuneDegree = '°' - RuneDiamond = '◆' - RuneGEqual = '≥' - RunePi = 'π' - RuneHLine = '─' - RuneLantern = '§' - RunePlus = '┼' - RuneLEqual = '≤' - RuneLLCorner = '└' - RuneLRCorner = '┘' - RuneNEqual = '≠' - RunePlMinus = '±' - RuneS1 = '⎺' - RuneS3 = '⎻' - RuneS7 = '⎼' - RuneS9 = '⎽' - RuneBlock = '█' - RuneTTee = '┬' - RuneRTee = '┤' - RuneLTee = '├' - RuneBTee = '┴' - RuneULCorner = '┌' - RuneURCorner = '┐' - RuneVLine = '│' -) - -// RuneFallbacks is the default map of fallback strings that will be -// used to replace a rune when no other more appropriate transformation -// is available, and the rune cannot be displayed directly. -// -// New entries may be added to this map over time, as it becomes clear -// that such is desirable. Characters that represent either letters or -// numbers should not be added to this list unless it is certain that -// the meaning will still convey unambiguously. -// -// As an example, it would be appropriate to add an ASCII mapping for -// the full width form of the letter 'A', but it would not be appropriate -// to do so a glyph representing the country China. -// -// Programs that desire richer fallbacks may register additional ones, -// or change or even remove these mappings with Screen.RegisterRuneFallback -// Screen.UnregisterRuneFallback methods. -// -// Note that Unicode is presumed to be able to display all glyphs. -// This is a pretty poor assumption, but there is no easy way to -// figure out which glyphs are supported in a given font. Hence, -// some care in selecting the characters you support in your application -// is still appropriate. -var RuneFallbacks = map[rune]string{ - RuneSterling: "f", - RuneDArrow: "v", - RuneLArrow: "<", - RuneRArrow: ">", - RuneUArrow: "^", - RuneBullet: "o", - RuneBoard: "#", - RuneCkBoard: ":", - RuneDegree: "\\", - RuneDiamond: "+", - RuneGEqual: ">", - RunePi: "*", - RuneHLine: "-", - RuneLantern: "#", - RunePlus: "+", - RuneLEqual: "<", - RuneLLCorner: "+", - RuneLRCorner: "+", - RuneNEqual: "!", - RunePlMinus: "#", - RuneS1: "~", - RuneS3: "-", - RuneS7: "-", - RuneS9: "_", - RuneBlock: "#", - RuneTTee: "+", - RuneRTee: "+", - RuneLTee: "+", - RuneBTee: "+", - RuneULCorner: "+", - RuneURCorner: "+", - RuneVLine: "|", -} diff --git a/vendor/github.com/gdamore/tcell/v2/screen.go b/vendor/github.com/gdamore/tcell/v2/screen.go deleted file mode 100644 index 18dc551912b..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/screen.go +++ /dev/null @@ -1,495 +0,0 @@ -// Copyright 2024 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import "sync" - -// Screen represents the physical (or emulated) screen. -// This can be a terminal window or a physical console. Platforms implement -// this differently. -type Screen interface { - // Init initializes the screen for use. - Init() error - - // Fini finalizes the screen also releasing resources. - Fini() - - // Clear logically erases the screen. - // This is effectively a short-cut for Fill(' ', StyleDefault). - Clear() - - // Fill fills the screen with the given character and style. - // The effect of filling the screen is not visible until Show - // is called (or Sync). - Fill(rune, Style) - - // SetCell is an older API, and will be removed. Please use - // SetContent instead; SetCell is implemented in terms of SetContent. - SetCell(x int, y int, style Style, ch ...rune) - - // GetContent returns the contents at the given location. If the - // coordinates are out of range, then the values will be 0, nil, - // StyleDefault. Note that the contents returned are logical contents - // and may not actually be what is displayed, but rather are what will - // be displayed if Show() or Sync() is called. The width is the width - // in screen cells; most often this will be 1, but some East Asian - // characters and emoji require two cells. - GetContent(x, y int) (primary rune, combining []rune, style Style, width int) - - // SetContent sets the contents of the given cell location. If - // the coordinates are out of range, then the operation is ignored. - // - // The first rune is the primary non-zero width rune. The array - // that follows is a possible list of combining characters to append, - // and will usually be nil (no combining characters.) - // - // The results are not displayed until Show() or Sync() is called. - // - // Note that wide (East Asian full width and emoji) runes occupy two cells, - // and attempts to place character at next cell to the right will have - // undefined effects. Wide runes that are printed in the - // last column will be replaced with a single width space on output. - SetContent(x int, y int, primary rune, combining []rune, style Style) - - // SetStyle sets the default style to use when clearing the screen - // or when StyleDefault is specified. If it is also StyleDefault, - // then whatever system/terminal default is relevant will be used. - SetStyle(style Style) - - // ShowCursor is used to display the cursor at a given location. - // If the coordinates -1, -1 are given or are otherwise outside the - // dimensions of the screen, the cursor will be hidden. - ShowCursor(x int, y int) - - // HideCursor is used to hide the cursor. It's an alias for - // ShowCursor(-1, -1).sim - HideCursor() - - // SetCursorStyle is used to set the cursor style. If the style - // is not supported (or cursor styles are not supported at all), - // then this will have no effect. Color will be changed if supplied, - // and the terminal supports doing so. - SetCursorStyle(CursorStyle, ...Color) - - // Size returns the screen size as width, height. This changes in - // response to a call to Clear or Flush. - Size() (width, height int) - - // ChannelEvents is an infinite loop that waits for an event and - // channels it into the user provided channel ch. Closing the - // quit channel and calling the Fini method are cancellation - // signals. When a cancellation signal is received the method - // returns after closing ch. - // - // This method should be used as a goroutine. - // - // NOTE: PollEvent should not be called while this method is running. - ChannelEvents(ch chan<- Event, quit <-chan struct{}) - - // PollEvent waits for events to arrive. Main application loops - // must spin on this to prevent the application from stalling. - // Furthermore, this will return nil if the Screen is finalized. - PollEvent() Event - - // HasPendingEvent returns true if PollEvent would return an event - // without blocking. If the screen is stopped and PollEvent would - // return nil, then the return value from this function is unspecified. - // The purpose of this function is to allow multiple events to be collected - // at once, to minimize screen redraws. - HasPendingEvent() bool - - // PostEvent tries to post an event into the event stream. This - // can fail if the event queue is full. In that case, the event - // is dropped, and ErrEventQFull is returned. - PostEvent(ev Event) error - - // Deprecated: PostEventWait is unsafe, and will be removed - // in the future. - // - // PostEventWait is like PostEvent, but if the queue is full, it - // blocks until there is space in the queue, making delivery - // reliable. However, it is VERY important that this function - // never be called from within whatever event loop is polling - // with PollEvent(), otherwise a deadlock may arise. - // - // For this reason, when using this function, the use of a - // Goroutine is recommended to ensure no deadlock can occur. - PostEventWait(ev Event) - - // EnableMouse enables the mouse. (If your terminal supports it.) - // If no flags are specified, then all events are reported, if the - // terminal supports them. - EnableMouse(...MouseFlags) - - // DisableMouse disables the mouse. - DisableMouse() - - // EnablePaste enables bracketed paste mode, if supported. - EnablePaste() - - // DisablePaste disables bracketed paste mode. - DisablePaste() - - // EnableFocus enables reporting of focus events, if your terminal supports it. - EnableFocus() - - // DisableFocus disables reporting of focus events. - DisableFocus() - - // HasMouse returns true if the terminal (apparently) supports a - // mouse. Note that the return value of true doesn't guarantee that - // a mouse/pointing device is present; a false return definitely - // indicates no mouse support is available. - HasMouse() bool - - // Colors returns the number of colors. All colors are assumed to - // use the ANSI color map. If a terminal is monochrome, it will - // return 0. - Colors() int - - // Show makes all the content changes made using SetContent() visible - // on the display. - // - // It does so in the most efficient and least visually disruptive - // manner possible. - Show() - - // Sync works like Show(), but it updates every visible cell on the - // physical display, assuming that it is not synchronized with any - // internal model. This may be both expensive and visually jarring, - // so it should only be used when believed to actually be necessary. - // - // Typically, this is called as a result of a user-requested redraw - // (e.g. to clear up on-screen corruption caused by some other program), - // or during a resize event. - Sync() - - // CharacterSet returns information about the character set. - // This isn't the full locale, but it does give us the input/output - // character set. Note that this is just for diagnostic purposes, - // we normally translate input/output to/from UTF-8, regardless of - // what the user's environment is. - CharacterSet() string - - // RegisterRuneFallback adds a fallback for runes that are not - // part of the character set -- for example one could register - // o as a fallback for ø. This should be done cautiously for - // characters that might be displayed ordinarily in language - // specific text -- characters that could change the meaning of - // written text would be dangerous. The intention here is to - // facilitate fallback characters in pseudo-graphical applications. - // - // If the terminal has fallbacks already in place via an alternate - // character set, those are used in preference. Also, standard - // fallbacks for graphical characters in the alternate character set - // terminfo string are registered implicitly. - // - // The display string should be the same width as original rune. - // This makes it possible to register two character replacements - // for full width East Asian characters, for example. - // - // It is recommended that replacement strings consist only of - // 7-bit ASCII, since other characters may not display everywhere. - RegisterRuneFallback(r rune, subst string) - - // UnregisterRuneFallback unmaps a replacement. It will unmap - // the implicit ASCII replacements for alternate characters as well. - // When an unmapped char needs to be displayed, but no suitable - // glyph is available, '?' is emitted instead. It is not possible - // to "disable" the use of alternate characters that are supported - // by your terminal except by changing the terminal database. - UnregisterRuneFallback(r rune) - - // CanDisplay returns true if the given rune can be displayed on - // this screen. Note that this is a best-guess effort -- whether - // your fonts support the character or not may be questionable. - // Mostly this is for folks who work outside of Unicode. - // - // If checkFallbacks is true, then if any (possibly imperfect) - // fallbacks are registered, this will return true. This will - // also return true if the terminal can replace the glyph with - // one that is visually indistinguishable from the one requested. - CanDisplay(r rune, checkFallbacks bool) bool - - // Resize does nothing, since it's generally not possible to - // ask a screen to resize, but it allows the Screen to implement - // the View interface. - Resize(int, int, int, int) - - // HasKey returns true if the keyboard is believed to have the - // key. In some cases a keyboard may have keys with this name - // but no support for them, while in others a key may be reported - // as supported but not actually be usable (such as some emulators - // that hijack certain keys). Its best not to depend to strictly - // on this function, but it can be used for hinting when building - // menus, displayed hot-keys, etc. Note that KeyRune (literal - // runes) is always true. - HasKey(Key) bool - - // Suspend pauses input and output processing. It also restores the - // terminal settings to what they were when the application started. - // This can be used to, for example, run a sub-shell. - Suspend() error - - // Resume resumes after Suspend(). - Resume() error - - // Beep attempts to sound an OS-dependent audible alert and returns an error - // when unsuccessful. - Beep() error - - // SetSize attempts to resize the window. It also invalidates the cells and - // calls the resize function. Note that if the window size is changed, it will - // not be restored upon application exit. - // - // Many terminals cannot support this. Perversely, the "modern" Windows Terminal - // does not support application-initiated resizing, whereas the legacy terminal does. - // Also, some emulators can support this but may have it disabled by default. - SetSize(int, int) - - // LockRegion sets or unsets a lock on a region of cells. A lock on a - // cell prevents the cell from being redrawn. - LockRegion(x, y, width, height int, lock bool) - - // Tty returns the underlying Tty. If the screen is not a terminal, the - // returned bool will be false - Tty() (Tty, bool) - - // SetTitle sets a window title on the screen. - // Terminals may be configured to ignore this, or unable to. - // Tcell may attempt to save and restore the window title on entry and exit, but - // the results may vary. Use of unicode characters may not be supported. - SetTitle(string) - - // SetClipboard is used to post arbitrary data to the system clipboard. - // This need not be UTF-8 string data. It's up to the recipient to decode the - // data meaningfully. Terminals may prevent this for security reasons. - SetClipboard([]byte) - - // GetClipboard is used to request the clipboard contents. It may be ignored. - // If the terminal is willing, it will be post the clipboard contents using an - // EventPaste with the clipboard content as the Data() field. Terminals may - // prevent this for security reasons. - GetClipboard() -} - -// NewScreen returns a default Screen suitable for the user's terminal -// environment. -func NewScreen() (Screen, error) { - // Windows is happier if we try for a console screen first. - if s, _ := NewConsoleScreen(); s != nil { - return s, nil - } else if s, e := NewTerminfoScreen(); s != nil { - return s, nil - } else { - return nil, e - } -} - -// MouseFlags are options to modify the handling of mouse events. -// Actual events can be ORed together. -type MouseFlags int - -const ( - MouseButtonEvents = MouseFlags(1) // Click events only - MouseDragEvents = MouseFlags(2) // Click-drag events (includes button events) - MouseMotionEvents = MouseFlags(4) // All mouse events (includes click and drag events) -) - -// CursorStyle represents a given cursor style, which can include the shape and -// whether the cursor blinks or is solid. Support for changing this is not universal. -type CursorStyle int - -const ( - CursorStyleDefault = CursorStyle(iota) // The default - CursorStyleBlinkingBlock - CursorStyleSteadyBlock - CursorStyleBlinkingUnderline - CursorStyleSteadyUnderline - CursorStyleBlinkingBar - CursorStyleSteadyBar -) - -// screenImpl is a subset of Screen that can be used with baseScreen to formulate -// a complete implementation of Screen. See Screen for doc comments about methods. -type screenImpl interface { - Init() error - Fini() - SetStyle(style Style) - ShowCursor(x int, y int) - HideCursor() - SetCursor(CursorStyle, Color) - Size() (width, height int) - EnableMouse(...MouseFlags) - DisableMouse() - EnablePaste() - DisablePaste() - EnableFocus() - DisableFocus() - HasMouse() bool - Colors() int - Show() - Sync() - CharacterSet() string - RegisterRuneFallback(r rune, subst string) - UnregisterRuneFallback(r rune) - CanDisplay(r rune, checkFallbacks bool) bool - Resize(int, int, int, int) - HasKey(Key) bool - Suspend() error - Resume() error - Beep() error - SetSize(int, int) - SetTitle(string) - Tty() (Tty, bool) - SetClipboard([]byte) - GetClipboard() - - // Following methods are not part of the Screen api, but are used for interaction with - // the common layer code. - - // Locker locks the underlying data structures so that we can access them - // in a thread-safe way. - sync.Locker - - // GetCells returns a pointer to the underlying CellBuffer that the implementation uses. - // Various methods will write to these for performance, but will use the lock to do so. - GetCells() *CellBuffer - - // StopQ is closed when the screen is shut down via Fini. It remains open if the screen - // is merely suspended. - StopQ() <-chan struct{} - - // EventQ delivers events. Events are posted to this by the screen in response to - // key presses, resizes, etc. Application code receives events from this via the - // Screen.PollEvent, Screen.ChannelEvents APIs. - EventQ() chan Event -} - -type baseScreen struct { - screenImpl -} - -func (b *baseScreen) SetCell(x int, y int, style Style, ch ...rune) { - if len(ch) > 0 { - b.SetContent(x, y, ch[0], ch[1:], style) - } else { - b.SetContent(x, y, ' ', nil, style) - } -} - -func (b *baseScreen) Clear() { - b.Fill(' ', StyleDefault) -} - -func (b *baseScreen) Fill(r rune, style Style) { - cb := b.GetCells() - b.Lock() - cb.Fill(r, style) - b.Unlock() -} - -func (b *baseScreen) SetContent(x, y int, mainc rune, combc []rune, st Style) { - - cells := b.GetCells() - b.Lock() - cells.SetContent(x, y, mainc, combc, st) - b.Unlock() -} - -func (b *baseScreen) GetContent(x, y int) (rune, []rune, Style, int) { - var primary rune - var combining []rune - var style Style - var width int - cells := b.GetCells() - b.Lock() - primary, combining, style, width = cells.GetContent(x, y) - b.Unlock() - return primary, combining, style, width -} - -func (b *baseScreen) LockRegion(x, y, width, height int, lock bool) { - cells := b.GetCells() - b.Lock() - for j := y; j < (y + height); j += 1 { - for i := x; i < (x + width); i += 1 { - switch lock { - case true: - cells.LockCell(i, j) - case false: - cells.UnlockCell(i, j) - } - } - } - b.Unlock() -} - -func (b *baseScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) { - defer close(ch) - for { - select { - case <-quit: - return - case <-b.StopQ(): - return - case ev := <-b.EventQ(): - select { - case <-quit: - return - case <-b.StopQ(): - return - case ch <- ev: - } - } - } -} - -func (b *baseScreen) PollEvent() Event { - select { - case <-b.StopQ(): - return nil - case ev := <-b.EventQ(): - return ev - } -} - -func (b *baseScreen) HasPendingEvent() bool { - return len(b.EventQ()) > 0 -} - -func (b *baseScreen) PostEventWait(ev Event) { - select { - case b.EventQ() <- ev: - case <-b.StopQ(): - } -} - -func (b *baseScreen) PostEvent(ev Event) error { - select { - case b.EventQ() <- ev: - return nil - default: - return ErrEventQFull - } -} - -func (b *baseScreen) SetCursorStyle(cs CursorStyle, ccs ...Color) { - if len(ccs) > 0 { - b.SetCursor(cs, ccs[0]) - } else { - b.SetCursor(cs, ColorNone) - } -} diff --git a/vendor/github.com/gdamore/tcell/v2/simulation.go b/vendor/github.com/gdamore/tcell/v2/simulation.go deleted file mode 100644 index 66efaa94e10..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/simulation.go +++ /dev/null @@ -1,528 +0,0 @@ -// Copyright 2024 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "sync" - "unicode/utf8" - - "golang.org/x/text/transform" -) - -// NewSimulationScreen returns a SimulationScreen. Note that -// SimulationScreen is also a Screen. -func NewSimulationScreen(charset string) SimulationScreen { - if charset == "" { - charset = "UTF-8" - } - ss := &simscreen{charset: charset} - ss.Screen = &baseScreen{screenImpl: ss} - return ss -} - -// SimulationScreen represents a screen simulation. This is intended to -// be a superset of normal Screens, but also adds some important interfaces -// for testing. -type SimulationScreen interface { - Screen - - // InjectKeyBytes injects a stream of bytes corresponding to - // the native encoding (see charset). It turns true if the entire - // set of bytes were processed and delivered as KeyEvents, false - // if any bytes were not fully understood. Any bytes that are not - // fully converted are discarded. - InjectKeyBytes(buf []byte) bool - - // InjectKey injects a key event. The rune is a UTF-8 rune, post - // any translation. - InjectKey(key Key, r rune, mod ModMask) - - // InjectMouse injects a mouse event. - InjectMouse(x, y int, buttons ButtonMask, mod ModMask) - - // GetContents returns screen contents as an array of - // cells, along with the physical width & height. Note that the - // physical contents will be used until the next time SetSize() - // is called. - GetContents() (cells []SimCell, width int, height int) - - // GetCursor returns the cursor details. - GetCursor() (x int, y int, visible bool) - - // GetTitle gets the previously set title. - GetTitle() string - - // GetClipboardData gets the actual data for the clipboard. - GetClipboardData() []byte -} - -// SimCell represents a simulated screen cell. The purpose of this -// is to track on screen content. -type SimCell struct { - // Bytes is the actual character bytes. Normally this is - // rune data, but it could be be data in another encoding system. - Bytes []byte - - // Style is the style used to display the data. - Style Style - - // Runes is the list of runes, unadulterated, in UTF-8. - Runes []rune -} - -type simscreen struct { - physw int - physh int - fini bool - style Style - evch chan Event - quit chan struct{} - - front []SimCell - back CellBuffer - clear bool - cursorx int - cursory int - cursorvis bool - mouse bool - paste bool - charset string - encoder transform.Transformer - decoder transform.Transformer - fillchar rune - fillstyle Style - fallback map[rune]string - title string - clipboard []byte - - Screen - sync.Mutex -} - -func (s *simscreen) Init() error { - s.evch = make(chan Event, 10) - s.quit = make(chan struct{}) - s.fillchar = 'X' - s.fillstyle = StyleDefault - s.mouse = false - s.physw = 80 - s.physh = 25 - s.cursorx = -1 - s.cursory = -1 - s.style = StyleDefault - - if enc := GetEncoding(s.charset); enc != nil { - s.encoder = enc.NewEncoder() - s.decoder = enc.NewDecoder() - } else { - return ErrNoCharset - } - - s.front = make([]SimCell, s.physw*s.physh) - s.back.Resize(80, 25) - - // default fallbacks - s.fallback = make(map[rune]string) - for k, v := range RuneFallbacks { - s.fallback[k] = v - } - return nil -} - -func (s *simscreen) Fini() { - s.Lock() - s.fini = true - s.back.Resize(0, 0) - s.Unlock() - if s.quit != nil { - close(s.quit) - } - s.physw = 0 - s.physh = 0 - s.front = nil -} - -func (s *simscreen) SetStyle(style Style) { - s.Lock() - s.style = style - s.Unlock() -} - -func (s *simscreen) drawCell(x, y int) int { - - mainc, combc, style, width := s.back.GetContent(x, y) - if !s.back.Dirty(x, y) { - return width - } - if x >= s.physw || y >= s.physh || x < 0 || y < 0 { - return width - } - simc := &s.front[(y*s.physw)+x] - - if style == StyleDefault { - style = s.style - } - simc.Style = style - simc.Runes = append([]rune{mainc}, combc...) - - // now emit runes - taking care to not overrun width with a - // wide character, and to ensure that we emit exactly one regular - // character followed up by any residual combing characters - - simc.Bytes = nil - - if x > s.physw-width { - simc.Runes = []rune{' '} - simc.Bytes = []byte{' '} - return width - } - - lbuf := make([]byte, 12) - ubuf := make([]byte, 12) - nout := 0 - - for _, r := range simc.Runes { - - l := utf8.EncodeRune(ubuf, r) - - nout, _, _ = s.encoder.Transform(lbuf, ubuf[:l], true) - - if nout == 0 || lbuf[0] == '\x1a' { - - // skip combining - - if subst, ok := s.fallback[r]; ok { - simc.Bytes = append(simc.Bytes, - []byte(subst)...) - - } else if r >= ' ' && r <= '~' { - simc.Bytes = append(simc.Bytes, byte(r)) - - } else if simc.Bytes == nil { - simc.Bytes = append(simc.Bytes, '?') - } - } else { - simc.Bytes = append(simc.Bytes, lbuf[:nout]...) - } - } - s.back.SetDirty(x, y, false) - return width -} - -func (s *simscreen) ShowCursor(x, y int) { - s.Lock() - s.cursorx, s.cursory = x, y - s.showCursor() - s.Unlock() -} - -func (s *simscreen) HideCursor() { - s.ShowCursor(-1, -1) -} - -func (s *simscreen) showCursor() { - - x, y := s.cursorx, s.cursory - if x < 0 || y < 0 || x >= s.physw || y >= s.physh { - s.cursorvis = false - } else { - s.cursorvis = true - } -} - -func (s *simscreen) hideCursor() { - // does not update cursor position - s.cursorvis = false -} - -func (s *simscreen) SetCursor(CursorStyle, Color) {} - -func (s *simscreen) Show() { - s.Lock() - s.resize() - s.draw() - s.Unlock() -} - -func (s *simscreen) clearScreen() { - // We emulate a hardware clear by filling with a specific pattern - for i := range s.front { - s.front[i].Style = s.fillstyle - s.front[i].Runes = []rune{s.fillchar} - s.front[i].Bytes = []byte{byte(s.fillchar)} - } - s.clear = false -} - -func (s *simscreen) draw() { - s.hideCursor() - if s.clear { - s.clearScreen() - } - - w, h := s.back.Size() - for y := 0; y < h; y++ { - for x := 0; x < w; x++ { - width := s.drawCell(x, y) - x += width - 1 - } - } - s.showCursor() -} - -func (s *simscreen) EnableMouse(...MouseFlags) { - s.mouse = true -} - -func (s *simscreen) DisableMouse() { - s.mouse = false -} - -func (s *simscreen) EnablePaste() { - s.paste = true -} - -func (s *simscreen) DisablePaste() { - s.paste = false -} - -func (s *simscreen) EnableFocus() { -} - -func (s *simscreen) DisableFocus() { -} - -func (s *simscreen) Size() (int, int) { - s.Lock() - w, h := s.back.Size() - s.Unlock() - return w, h -} - -func (s *simscreen) resize() { - w, h := s.physw, s.physh - ow, oh := s.back.Size() - if w != ow || h != oh { - s.back.Resize(w, h) - ev := NewEventResize(w, h) - s.postEvent(ev) - } -} - -func (s *simscreen) Colors() int { - return 256 -} - -func (s *simscreen) postEvent(ev Event) { - select { - case s.evch <- ev: - case <-s.quit: - } -} - -func (s *simscreen) InjectMouse(x, y int, buttons ButtonMask, mod ModMask) { - ev := NewEventMouse(x, y, buttons, mod) - s.postEvent(ev) -} - -func (s *simscreen) InjectKey(key Key, r rune, mod ModMask) { - ev := NewEventKey(key, r, mod) - s.postEvent(ev) -} - -func (s *simscreen) InjectKeyBytes(b []byte) bool { - failed := false - -outer: - for len(b) > 0 { - if b[0] >= ' ' && b[0] <= 0x7F { - // printable ASCII easy to deal with -- no encodings - ev := NewEventKey(KeyRune, rune(b[0]), ModNone) - s.postEvent(ev) - b = b[1:] - continue - } - - if b[0] < 0x80 { - mod := ModNone - // No encodings start with low numbered values - if Key(b[0]) >= KeyCtrlA && Key(b[0]) <= KeyCtrlZ { - mod = ModCtrl - } - ev := NewEventKey(Key(b[0]), 0, mod) - s.postEvent(ev) - b = b[1:] - continue - } - - utfb := make([]byte, len(b)*4) // worst case - for l := 1; l < len(b); l++ { - s.decoder.Reset() - nout, nin, _ := s.decoder.Transform(utfb, b[:l], true) - - if nout != 0 { - r, _ := utf8.DecodeRune(utfb[:nout]) - if r != utf8.RuneError { - ev := NewEventKey(KeyRune, r, ModNone) - s.postEvent(ev) - } - b = b[nin:] - continue outer - } - } - failed = true - b = b[1:] - continue - } - - return !failed -} - -func (s *simscreen) Sync() { - s.Lock() - s.clear = true - s.resize() - s.back.Invalidate() - s.draw() - s.Unlock() -} - -func (s *simscreen) CharacterSet() string { - return s.charset -} - -func (s *simscreen) SetSize(w, h int) { - s.Lock() - newc := make([]SimCell, w*h) - for row := 0; row < h && row < s.physh; row++ { - for col := 0; col < w && col < s.physw; col++ { - newc[(row*w)+col] = s.front[(row*s.physw)+col] - } - } - s.cursorx, s.cursory = -1, -1 - s.physw, s.physh = w, h - s.front = newc - s.back.Resize(w, h) - s.Unlock() -} - -func (s *simscreen) GetContents() ([]SimCell, int, int) { - s.Lock() - cells, w, h := s.front, s.physw, s.physh - s.Unlock() - return cells, w, h -} - -func (s *simscreen) GetCursor() (int, int, bool) { - s.Lock() - x, y, vis := s.cursorx, s.cursory, s.cursorvis - s.Unlock() - return x, y, vis -} - -func (s *simscreen) RegisterRuneFallback(r rune, subst string) { - s.Lock() - s.fallback[r] = subst - s.Unlock() -} - -func (s *simscreen) UnregisterRuneFallback(r rune) { - s.Lock() - delete(s.fallback, r) - s.Unlock() -} - -func (s *simscreen) CanDisplay(r rune, checkFallbacks bool) bool { - - if enc := s.encoder; enc != nil { - nb := make([]byte, 6) - ob := make([]byte, 6) - num := utf8.EncodeRune(ob, r) - - enc.Reset() - dst, _, err := enc.Transform(nb, ob[:num], true) - if dst != 0 && err == nil && nb[0] != '\x1A' { - return true - } - } - if !checkFallbacks { - return false - } - if _, ok := s.fallback[r]; ok { - return true - } - return false -} - -func (s *simscreen) HasMouse() bool { - return false -} - -func (s *simscreen) Resize(int, int, int, int) {} - -func (s *simscreen) HasKey(Key) bool { - return true -} - -func (s *simscreen) Beep() error { - return nil -} - -func (s *simscreen) Suspend() error { - return nil -} - -func (s *simscreen) Resume() error { - return nil -} - -func (s *simscreen) Tty() (Tty, bool) { - return nil, false -} - -func (s *simscreen) GetCells() *CellBuffer { - return &s.back -} - -func (s *simscreen) EventQ() chan Event { - return s.evch -} - -func (s *simscreen) StopQ() <-chan struct{} { - return s.quit -} - -func (s *simscreen) SetTitle(title string) { - s.title = title -} - -func (s *simscreen) GetTitle() string { - return s.title -} - -func (s *simscreen) SetClipboard(data []byte) { - s.clipboard = data -} - -func (s *simscreen) GetClipboard() { - if s.clipboard != nil { - ev := NewEventClipboard(s.clipboard) - s.postEvent(ev) - } -} - -func (s *simscreen) GetClipboardData() []byte { - return s.clipboard -} diff --git a/vendor/github.com/gdamore/tcell/v2/stdin_unix.go b/vendor/github.com/gdamore/tcell/v2/stdin_unix.go deleted file mode 100644 index b478b8918cd..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/stdin_unix.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2021 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos -// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos - -package tcell - -import ( - "errors" - "fmt" - "os" - "os/signal" - "strconv" - "sync" - "syscall" - "time" - - "golang.org/x/sys/unix" - "golang.org/x/term" -) - -// stdIoTty is an implementation of the Tty API based upon stdin/stdout. -type stdIoTty struct { - fd int - in *os.File - out *os.File - saved *term.State - sig chan os.Signal - cb func() - stopQ chan struct{} - dev string - wg sync.WaitGroup - l sync.Mutex -} - -func (tty *stdIoTty) Read(b []byte) (int, error) { - return tty.in.Read(b) -} - -func (tty *stdIoTty) Write(b []byte) (int, error) { - return tty.out.Write(b) -} - -func (tty *stdIoTty) Close() error { - return nil -} - -func (tty *stdIoTty) Start() error { - tty.l.Lock() - defer tty.l.Unlock() - - // We open another copy of /dev/tty. This is a workaround for unusual behavior - // observed in macOS, apparently caused when a sub-shell (for example) closes our - // own tty device (when it exits for example). Getting a fresh new one seems to - // resolve the problem. (We believe this is a bug in the macOS tty driver that - // fails to account for dup() references to the same file before applying close() - // related behaviors to the tty.) We're also holding the original copy we opened - // since closing that might have deleterious effects as well. The upshot is that - // we will have up to two separate file handles open on /dev/tty. (Note that when - // using stdin/stdout instead of /dev/tty this problem is not observed.) - var err error - tty.in = os.Stdin - tty.out = os.Stdout - tty.fd = int(tty.in.Fd()) - - if !term.IsTerminal(tty.fd) { - return errors.New("device is not a terminal") - } - - _ = tty.in.SetReadDeadline(time.Time{}) - saved, err := term.MakeRaw(tty.fd) // also sets vMin and vTime - if err != nil { - return err - } - tty.saved = saved - - tty.stopQ = make(chan struct{}) - tty.wg.Add(1) - go func(stopQ chan struct{}) { - defer tty.wg.Done() - for { - select { - case <-tty.sig: - tty.l.Lock() - cb := tty.cb - tty.l.Unlock() - if cb != nil { - cb() - } - case <-stopQ: - return - } - } - }(tty.stopQ) - - signal.Notify(tty.sig, syscall.SIGWINCH) - return nil -} - -func (tty *stdIoTty) Drain() error { - _ = tty.in.SetReadDeadline(time.Now()) - if err := tcSetBufParams(tty.fd, 0, 0); err != nil { - return err - } - return nil -} - -func (tty *stdIoTty) Stop() error { - tty.l.Lock() - if err := term.Restore(tty.fd, tty.saved); err != nil { - tty.l.Unlock() - return err - } - _ = tty.in.SetReadDeadline(time.Now()) - - signal.Stop(tty.sig) - close(tty.stopQ) - tty.l.Unlock() - - tty.wg.Wait() - - return nil -} - -func (tty *stdIoTty) WindowSize() (WindowSize, error) { - size := WindowSize{} - ws, err := unix.IoctlGetWinsize(tty.fd, unix.TIOCGWINSZ) - if err != nil { - return size, err - } - w := int(ws.Col) - h := int(ws.Row) - if w == 0 { - w, _ = strconv.Atoi(os.Getenv("COLUMNS")) - } - if w == 0 { - w = 80 // default - } - if h == 0 { - h, _ = strconv.Atoi(os.Getenv("LINES")) - } - if h == 0 { - h = 25 // default - } - size.Width = w - size.Height = h - size.PixelWidth = int(ws.Xpixel) - size.PixelHeight = int(ws.Ypixel) - return size, nil -} - -func (tty *stdIoTty) NotifyResize(cb func()) { - tty.l.Lock() - tty.cb = cb - tty.l.Unlock() -} - -// NewStdioTty opens a tty using standard input/output. -func NewStdIoTty() (Tty, error) { - tty := &stdIoTty{ - sig: make(chan os.Signal), - in: os.Stdin, - out: os.Stdout, - } - var err error - tty.fd = int(tty.in.Fd()) - if !term.IsTerminal(tty.fd) { - return nil, errors.New("not a terminal") - } - if tty.saved, err = term.GetState(tty.fd); err != nil { - return nil, fmt.Errorf("failed to get state: %w", err) - } - return tty, nil -} diff --git a/vendor/github.com/gdamore/tcell/v2/style.go b/vendor/github.com/gdamore/tcell/v2/style.go deleted file mode 100644 index 73995c0e4bb..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/style.go +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2024 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -// Style represents a complete text style, including both foreground color, -// background color, and additional attributes such as "bold" or "underline". -// -// Note that not all terminals can display all colors or attributes, and -// many might have specific incompatibilities between specific attributes -// and color combinations. -// -// To use Style, just declare a variable of its type. -type Style struct { - fg Color - bg Color - ulStyle UnderlineStyle - ulColor Color - attrs AttrMask - url string - urlId string -} - -// StyleDefault represents a default style, based upon the context. -// It is the zero value. -var StyleDefault Style - -// styleInvalid is just an arbitrary invalid style used internally. -var styleInvalid = Style{attrs: AttrInvalid} - -// Foreground returns a new style based on s, with the foreground color set -// as requested. ColorDefault can be used to select the global default. -func (s Style) Foreground(c Color) Style { - s2 := s - s2.fg = c - return s2 -} - -// Background returns a new style based on s, with the background color set -// as requested. ColorDefault can be used to select the global default. -func (s Style) Background(c Color) Style { - s2 := s - s2.bg = c - return s2 -} - -// Decompose breaks a style up, returning the foreground, background, -// and other attributes. The URL if set is not included. -// Deprecated: Applications should not attempt to decompose style, -// as this content is not sufficient to describe the actual style. -func (s Style) Decompose() (fg Color, bg Color, attr AttrMask) { - return s.fg, s.bg, s.attrs -} - -func (s Style) setAttrs(attrs AttrMask, on bool) Style { - s2 := s - if on { - s2.attrs |= attrs - } else { - s2.attrs &^= attrs - } - return s2 -} - -// Normal returns the style with all attributes disabled. -func (s Style) Normal() Style { - return Style{ - fg: s.fg, - bg: s.bg, - } -} - -// Bold returns a new style based on s, with the bold attribute set -// as requested. -func (s Style) Bold(on bool) Style { - return s.setAttrs(AttrBold, on) -} - -// Blink returns a new style based on s, with the blink attribute set -// as requested. -func (s Style) Blink(on bool) Style { - return s.setAttrs(AttrBlink, on) -} - -// Dim returns a new style based on s, with the dim attribute set -// as requested. -func (s Style) Dim(on bool) Style { - return s.setAttrs(AttrDim, on) -} - -// Italic returns a new style based on s, with the italic attribute set -// as requested. -func (s Style) Italic(on bool) Style { - return s.setAttrs(AttrItalic, on) -} - -// Reverse returns a new style based on s, with the reverse attribute set -// as requested. (Reverse usually changes the foreground and background -// colors.) -func (s Style) Reverse(on bool) Style { - return s.setAttrs(AttrReverse, on) -} - -// StrikeThrough sets strikethrough mode. -func (s Style) StrikeThrough(on bool) Style { - return s.setAttrs(AttrStrikeThrough, on) -} - -// Underline style. Modern terminals have the option of rendering the -// underline using different styles, and even different colors. -type UnderlineStyle int - -const ( - UnderlineStyleNone = UnderlineStyle(iota) - UnderlineStyleSolid - UnderlineStyleDouble - UnderlineStyleCurly - UnderlineStyleDotted - UnderlineStyleDashed -) - -// Underline returns a new style based on s, with the underline attribute set -// as requested. The parameters can be: -// -// bool: on / off - enables just a simple underline -// UnderlineStyle: sets a specific style (should not coexist with the bool) -// Color: the color to use -func (s Style) Underline(params ...interface{}) Style { - s2 := s - for _, param := range params { - switch v := param.(type) { - case bool: - if v { - s2.ulStyle = UnderlineStyleSolid - s2.attrs |= AttrUnderline - } else { - s2.ulStyle = UnderlineStyleNone - s2.attrs &^= AttrUnderline - } - case UnderlineStyle: - if v == UnderlineStyleNone { - s2.attrs &^= AttrUnderline - } else { - s2.attrs |= AttrUnderline - } - s2.ulStyle = v - case Color: - s2.ulColor = v - default: - panic("Bad type for underline") - } - } - return s2 -} - -// GetUnderlineStyle returns the underline style for the style. -func (s Style) GetUnderlineStyle() UnderlineStyle { - return s.ulStyle -} - -// GetUnderlineColor returns the underline color for the style. -func (s Style) GetUnderlineColor() Color { - return s.ulColor -} - -// Attributes returns a new style based on s, with its attributes set as -// specified. -func (s Style) Attributes(attrs AttrMask) Style { - s2 := s - s2.attrs = attrs - return s2 -} - -// Url returns a style with the Url set. If the provided Url is not empty, -// and the terminal supports it, text will typically be marked up as a clickable -// link to that Url. If the Url is empty, then this mode is turned off. -func (s Style) Url(url string) Style { - s2 := s - s2.url = url - return s2 -} - -// UrlId returns a style with the UrlId set. If the provided UrlId is not empty, -// any marked up Url with this style will be given the UrlId also. If the -// terminal supports it, any text with the same UrlId will be grouped as if it -// were one Url, even if it spans multiple lines. -func (s Style) UrlId(id string) Style { - s2 := s - s2.urlId = "id=" + id - return s2 -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/.gitignore b/vendor/github.com/gdamore/tcell/v2/terminfo/.gitignore deleted file mode 100644 index 74f3c04fd60..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/.gitignore +++ /dev/null @@ -1 +0,0 @@ -mkinfo diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/README.md b/vendor/github.com/gdamore/tcell/v2/terminfo/README.md deleted file mode 100644 index 20ae937f382..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/README.md +++ /dev/null @@ -1,25 +0,0 @@ -This package represents the parent for all terminals. - -In older versions of tcell we had (a couple of) different -external file formats for the terminal database. Those are -now removed. All terminal definitions are supplied by -one of two methods: - -1. Compiled Go code - -2. For systems with terminfo and infocmp, dynamically - generated at runtime. - -The Go code can be generated using the mkinfo utility in -this directory. The database entry should be generated -into a package in a directory named as the first character -of the package name. (This permits us to group them all -without having a huge directory of little packages.) - -It may be desirable to add new packages to the extended -package, or -- rarely -- the base package. - -Applications which want to have the large set of terminal -descriptions built into the binary can simply import the -extended package. Otherwise a smaller reasonable default -set (the base package) will be included instead. diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/TERMINALS.md b/vendor/github.com/gdamore/tcell/v2/terminfo/TERMINALS.md deleted file mode 100644 index 85c1e61c2ea..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/TERMINALS.md +++ /dev/null @@ -1,7 +0,0 @@ -TERMINALS -========= - -The best way to populate terminals on Debian is to install ncurses, -ncurses-term, screen, tmux, rxvt-unicode, and dvtm. This populates the -the terminfo database so that we can have a reasonable set of starting -terminals. diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/a/aixterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/aixterm/term.go deleted file mode 100644 index 503c9199edc..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/a/aixterm/term.go +++ /dev/null @@ -1,83 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package aixterm - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // IBM Aixterm Terminal Emulator - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "aixterm", - Columns: 80, - Lines: 25, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - AttrOff: "\x1b[0;10m\x1b(B", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Reverse: "\x1b[7m", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[32m\x1b[40m", - PadChar: "\x00", - AltChars: "jjkkllmmnnqqttuuvvwwxx", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[139q", - KeyDelete: "\x1b[P", - KeyBackspace: "\b", - KeyHome: "\x1b[H", - KeyEnd: "\x1b[146q", - KeyPgUp: "\x1b[150q", - KeyPgDn: "\x1b[154q", - KeyF1: "\x1b[001q", - KeyF2: "\x1b[002q", - KeyF3: "\x1b[003q", - KeyF4: "\x1b[004q", - KeyF5: "\x1b[005q", - KeyF6: "\x1b[006q", - KeyF7: "\x1b[007q", - KeyF8: "\x1b[008q", - KeyF9: "\x1b[009q", - KeyF10: "\x1b[010q", - KeyF11: "\x1b[011q", - KeyF12: "\x1b[012q", - KeyF13: "\x1b[013q", - KeyF14: "\x1b[014q", - KeyF15: "\x1b[015q", - KeyF16: "\x1b[016q", - KeyF17: "\x1b[017q", - KeyF18: "\x1b[018q", - KeyF19: "\x1b[019q", - KeyF20: "\x1b[020q", - KeyF21: "\x1b[021q", - KeyF22: "\x1b[022q", - KeyF23: "\x1b[023q", - KeyF24: "\x1b[024q", - KeyF25: "\x1b[025q", - KeyF26: "\x1b[026q", - KeyF27: "\x1b[027q", - KeyF28: "\x1b[028q", - KeyF29: "\x1b[029q", - KeyF30: "\x1b[030q", - KeyF31: "\x1b[031q", - KeyF32: "\x1b[032q", - KeyF33: "\x1b[033q", - KeyF34: "\x1b[034q", - KeyF35: "\x1b[035q", - KeyF36: "\x1b[036q", - KeyClear: "\x1b[144q", - KeyBacktab: "\x1b[Z", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/direct.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/direct.go deleted file mode 100644 index db6351af2b2..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/direct.go +++ /dev/null @@ -1,69 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package alacritty - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // alacritty with direct color indexing - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "alacritty-direct", - Columns: 80, - Lines: 24, - Colors: 16777216, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h\x1b[22;0;0t", - ExitCA: "\x1b[?1049l\x1b[23;0;0t", - ShowCursor: "\x1b[?12l\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b(B\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - TrueColor: true, - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/term.go deleted file mode 100644 index a82d6dbef62..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/term.go +++ /dev/null @@ -1,76 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package alacritty - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // alacritty terminal emulator - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "alacritty", - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h\x1b[22;0;0t", - ExitCA: "\x1b[?1049l\x1b[23;0;0t", - ShowCursor: "\x1b[?12l\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b(B\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[<", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - DoubleUnderline: "\x1b[4:2m", - CurlyUnderline: "\x1b[4:3m", - DottedUnderline: "\x1b[4:4m", - DashedUnderline: "\x1b[4:5m", - XTermLike: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/a/ansi/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/ansi/term.go deleted file mode 100644 index 5c572fd4961..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/a/ansi/term.go +++ /dev/null @@ -1,43 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package ansi - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // ansi/pc-term compatible with color - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "ansi", - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - AttrOff: "\x1b[0;10m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe", - EnterAcs: "\x1b[11m", - ExitAcs: "\x1b[10m", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\x1b[D", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[L", - KeyBackspace: "\b", - KeyHome: "\x1b[H", - KeyBacktab: "\x1b[Z", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/b/beterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/b/beterm/term.go deleted file mode 100644 index e6d88838c3f..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/b/beterm/term.go +++ /dev/null @@ -1,57 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package beterm - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // BeOS Terminal - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "beterm", - Columns: 80, - Lines: 25, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - AttrOff: "\x1b[0;10m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?4h", - ExitKeypad: "\x1b[?4l", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[m", - PadChar: "\x00", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\b", - KeyHome: "\x1b[1~", - KeyEnd: "\x1b[4~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1b[11~", - KeyF2: "\x1b[12~", - KeyF3: "\x1b[13~", - KeyF4: "\x1b[14~", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[16~", - KeyF7: "\x1b[17~", - KeyF8: "\x1b[18~", - KeyF9: "\x1b[19~", - KeyF10: "\x1b[20~", - KeyF11: "\x1b[21~", - KeyF12: "\x1b[22~", - AutoMargin: true, - InsertChar: "\x1b[@", - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/base/base.go b/vendor/github.com/gdamore/tcell/v2/terminfo/base/base.go deleted file mode 100644 index 75aeb15f20d..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/base/base.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2020 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This is just a "minimalist" set of the base terminal descriptions. -// It should be sufficient for most applications. - -// Package base contains the base terminal descriptions that are likely -// to be needed by any stock application. It is imported by default in the -// terminfo package, so terminal types listed here will be available to any -// tcell application. -package base - -import ( - // The following imports just register themselves -- - // these are the terminal types we aggregate in this package. - _ "github.com/gdamore/tcell/v2/terminfo/a/ansi" - _ "github.com/gdamore/tcell/v2/terminfo/t/tmux" - _ "github.com/gdamore/tcell/v2/terminfo/v/vt100" - _ "github.com/gdamore/tcell/v2/terminfo/v/vt102" - _ "github.com/gdamore/tcell/v2/terminfo/v/vt220" - _ "github.com/gdamore/tcell/v2/terminfo/x/xterm" -) diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/c/cygwin/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/c/cygwin/term.go deleted file mode 100644 index 46a0a4a3a25..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/c/cygwin/term.go +++ /dev/null @@ -1,66 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package cygwin - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // ANSI emulation for Cygwin - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "cygwin", - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - AttrOff: "\x1b[0;10m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Reverse: "\x1b[7m", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe", - EnterAcs: "\x1b[11m", - ExitAcs: "\x1b[10m", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\b", - KeyHome: "\x1b[1~", - KeyEnd: "\x1b[4~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1b[[A", - KeyF2: "\x1b[[B", - KeyF3: "\x1b[[C", - KeyF4: "\x1b[[D", - KeyF5: "\x1b[[E", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF15: "\x1b[28~", - KeyF16: "\x1b[29~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - AutoMargin: true, - InsertChar: "\x1b[@", - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/d/dtterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/d/dtterm/term.go deleted file mode 100644 index 90a5fedfc33..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/d/dtterm/term.go +++ /dev/null @@ -1,71 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package dtterm - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // CDE desktop terminal - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "dtterm", - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b(B\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\b", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1b[11~", - KeyF2: "\x1b[12~", - KeyF3: "\x1b[13~", - KeyF4: "\x1b[14~", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF15: "\x1b[28~", - KeyF16: "\x1b[29~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - KeyHelp: "\x1b[28~", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/dynamic/dynamic.go b/vendor/github.com/gdamore/tcell/v2/terminfo/dynamic/dynamic.go deleted file mode 100644 index 047ebded6b2..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/dynamic/dynamic.go +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright 2021 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// The dynamic package is used to generate a terminal description dynamically, -// using infocmp. This is really a method of last resort, as the performance -// will be slow, and it requires a working infocmp. But, the hope is that it -// will assist folks who have to deal with a terminal description that isn't -// already built in. This requires infocmp to be in the user's path, and to -// support reasonably the -1 option. - -package dynamic - -import ( - "bytes" - "errors" - "os/exec" - "regexp" - "strconv" - "strings" - - "github.com/gdamore/tcell/v2/terminfo" -) - -type termcap struct { - name string - desc string - aliases []string - bools map[string]bool - nums map[string]int - strs map[string]string -} - -func (tc *termcap) getnum(s string) int { - return (tc.nums[s]) -} - -func (tc *termcap) getflag(s string) bool { - return (tc.bools[s]) -} - -func (tc *termcap) getstr(s string) string { - return (tc.strs[s]) -} - -const ( - none = iota - control - escaped -) - -var errNotAddressable = errors.New("terminal not cursor addressable") - -func unescape(s string) string { - // Various escapes are in \x format. Control codes are - // encoded as ^M (carat followed by ASCII equivalent). - // escapes are: \e, \E - escape - // \0 NULL, \n \l \r \t \b \f \s for equivalent C escape. - buf := &bytes.Buffer{} - esc := none - - for i := 0; i < len(s); i++ { - c := s[i] - switch esc { - case none: - switch c { - case '\\': - esc = escaped - case '^': - esc = control - default: - buf.WriteByte(c) - } - case control: - buf.WriteByte(c ^ 1<<6) - esc = none - case escaped: - switch c { - case 'E', 'e': - buf.WriteByte(0x1b) - case '0', '1', '2', '3', '4', '5', '6', '7': - if i+2 < len(s) && s[i+1] >= '0' && s[i+1] <= '7' && s[i+2] >= '0' && s[i+2] <= '7' { - buf.WriteByte(((c - '0') * 64) + ((s[i+1] - '0') * 8) + (s[i+2] - '0')) - i = i + 2 - } else if c == '0' { - buf.WriteByte(0) - } - case 'n': - buf.WriteByte('\n') - case 'r': - buf.WriteByte('\r') - case 't': - buf.WriteByte('\t') - case 'b': - buf.WriteByte('\b') - case 'f': - buf.WriteByte('\f') - case 's': - buf.WriteByte(' ') - default: - buf.WriteByte(c) - } - esc = none - } - } - return (buf.String()) -} - -func (tc *termcap) setupterm(name string) error { - cmd := exec.Command("infocmp", "-1", name) - output := &bytes.Buffer{} - cmd.Stdout = output - - tc.strs = make(map[string]string) - tc.bools = make(map[string]bool) - tc.nums = make(map[string]int) - - if err := cmd.Run(); err != nil { - return err - } - - // Now parse the output. - // We get comment lines (starting with "#"), followed by - // a header line that looks like "||...|" - // then capabilities, one per line, starting with a tab and ending - // with a comma and newline. - lines := strings.Split(output.String(), "\n") - for len(lines) > 0 && strings.HasPrefix(lines[0], "#") { - lines = lines[1:] - } - - // Ditch trailing empty last line - if lines[len(lines)-1] == "" { - lines = lines[:len(lines)-1] - } - header := lines[0] - if strings.HasSuffix(header, ",") { - header = header[:len(header)-1] - } - names := strings.Split(header, "|") - tc.name = names[0] - names = names[1:] - if len(names) > 0 { - tc.desc = names[len(names)-1] - names = names[:len(names)-1] - } - tc.aliases = names - for _, val := range lines[1:] { - if (!strings.HasPrefix(val, "\t")) || - (!strings.HasSuffix(val, ",")) { - return (errors.New("malformed infocmp: " + val)) - } - - val = val[1:] - val = val[:len(val)-1] - - if k := strings.SplitN(val, "=", 2); len(k) == 2 { - tc.strs[k[0]] = unescape(k[1]) - } else if k := strings.SplitN(val, "#", 2); len(k) == 2 { - u, err := strconv.ParseUint(k[1], 0, 0) - if err != nil { - return (err) - } - tc.nums[k[0]] = int(u) - } else { - tc.bools[val] = true - } - } - return nil -} - -// LoadTerminfo creates a Terminfo by for named terminal by attempting to parse -// the output from infocmp. This returns the terminfo entry, a description of -// the terminal, and either nil or an error. -func LoadTerminfo(name string) (*terminfo.Terminfo, string, error) { - var tc termcap - if err := tc.setupterm(name); err != nil { - return nil, "", err - } - t := &terminfo.Terminfo{} - t.Name = tc.name - t.Aliases = tc.aliases - t.Colors = tc.getnum("colors") - t.Columns = tc.getnum("cols") - t.Lines = tc.getnum("lines") - t.Bell = tc.getstr("bel") - t.Clear = tc.getstr("clear") - t.EnterCA = tc.getstr("smcup") - t.ExitCA = tc.getstr("rmcup") - t.ShowCursor = tc.getstr("cnorm") - t.HideCursor = tc.getstr("civis") - t.AttrOff = tc.getstr("sgr0") - t.Underline = tc.getstr("smul") - t.Bold = tc.getstr("bold") - t.Blink = tc.getstr("blink") - t.Dim = tc.getstr("dim") - t.Italic = tc.getstr("sitm") - t.Reverse = tc.getstr("rev") - t.EnterKeypad = tc.getstr("smkx") - t.ExitKeypad = tc.getstr("rmkx") - t.SetFg = tc.getstr("setaf") - t.SetBg = tc.getstr("setab") - t.SetCursor = tc.getstr("cup") - t.CursorBack1 = tc.getstr("cub1") - t.CursorUp1 = tc.getstr("cuu1") - t.KeyF1 = tc.getstr("kf1") - t.KeyF2 = tc.getstr("kf2") - t.KeyF3 = tc.getstr("kf3") - t.KeyF4 = tc.getstr("kf4") - t.KeyF5 = tc.getstr("kf5") - t.KeyF6 = tc.getstr("kf6") - t.KeyF7 = tc.getstr("kf7") - t.KeyF8 = tc.getstr("kf8") - t.KeyF9 = tc.getstr("kf9") - t.KeyF10 = tc.getstr("kf10") - t.KeyF11 = tc.getstr("kf11") - t.KeyF12 = tc.getstr("kf12") - t.KeyF13 = tc.getstr("kf13") - t.KeyF14 = tc.getstr("kf14") - t.KeyF15 = tc.getstr("kf15") - t.KeyF16 = tc.getstr("kf16") - t.KeyF17 = tc.getstr("kf17") - t.KeyF18 = tc.getstr("kf18") - t.KeyF19 = tc.getstr("kf19") - t.KeyF20 = tc.getstr("kf20") - t.KeyF21 = tc.getstr("kf21") - t.KeyF22 = tc.getstr("kf22") - t.KeyF23 = tc.getstr("kf23") - t.KeyF24 = tc.getstr("kf24") - t.KeyF25 = tc.getstr("kf25") - t.KeyF26 = tc.getstr("kf26") - t.KeyF27 = tc.getstr("kf27") - t.KeyF28 = tc.getstr("kf28") - t.KeyF29 = tc.getstr("kf29") - t.KeyF30 = tc.getstr("kf30") - t.KeyF31 = tc.getstr("kf31") - t.KeyF32 = tc.getstr("kf32") - t.KeyF33 = tc.getstr("kf33") - t.KeyF34 = tc.getstr("kf34") - t.KeyF35 = tc.getstr("kf35") - t.KeyF36 = tc.getstr("kf36") - t.KeyF37 = tc.getstr("kf37") - t.KeyF38 = tc.getstr("kf38") - t.KeyF39 = tc.getstr("kf39") - t.KeyF40 = tc.getstr("kf40") - t.KeyF41 = tc.getstr("kf41") - t.KeyF42 = tc.getstr("kf42") - t.KeyF43 = tc.getstr("kf43") - t.KeyF44 = tc.getstr("kf44") - t.KeyF45 = tc.getstr("kf45") - t.KeyF46 = tc.getstr("kf46") - t.KeyF47 = tc.getstr("kf47") - t.KeyF48 = tc.getstr("kf48") - t.KeyF49 = tc.getstr("kf49") - t.KeyF50 = tc.getstr("kf50") - t.KeyF51 = tc.getstr("kf51") - t.KeyF52 = tc.getstr("kf52") - t.KeyF53 = tc.getstr("kf53") - t.KeyF54 = tc.getstr("kf54") - t.KeyF55 = tc.getstr("kf55") - t.KeyF56 = tc.getstr("kf56") - t.KeyF57 = tc.getstr("kf57") - t.KeyF58 = tc.getstr("kf58") - t.KeyF59 = tc.getstr("kf59") - t.KeyF60 = tc.getstr("kf60") - t.KeyF61 = tc.getstr("kf61") - t.KeyF62 = tc.getstr("kf62") - t.KeyF63 = tc.getstr("kf63") - t.KeyF64 = tc.getstr("kf64") - t.KeyInsert = tc.getstr("kich1") - t.KeyDelete = tc.getstr("kdch1") - t.KeyBackspace = tc.getstr("kbs") - t.KeyHome = tc.getstr("khome") - t.KeyEnd = tc.getstr("kend") - t.KeyUp = tc.getstr("kcuu1") - t.KeyDown = tc.getstr("kcud1") - t.KeyRight = tc.getstr("kcuf1") - t.KeyLeft = tc.getstr("kcub1") - t.KeyPgDn = tc.getstr("knp") - t.KeyPgUp = tc.getstr("kpp") - t.KeyBacktab = tc.getstr("kcbt") - t.KeyExit = tc.getstr("kext") - t.KeyCancel = tc.getstr("kcan") - t.KeyPrint = tc.getstr("kprt") - t.KeyHelp = tc.getstr("khlp") - t.KeyClear = tc.getstr("kclr") - t.AltChars = tc.getstr("acsc") - t.EnterAcs = tc.getstr("smacs") - t.ExitAcs = tc.getstr("rmacs") - t.EnableAcs = tc.getstr("enacs") - t.Mouse = tc.getstr("kmous") - t.KeyShfRight = tc.getstr("kRIT") - t.KeyShfLeft = tc.getstr("kLFT") - t.KeyShfHome = tc.getstr("kHOM") - t.KeyShfEnd = tc.getstr("kEND") - - // Terminfo lacks descriptions for a bunch of modified keys, - // but modern XTerm and emulators often have them. Let's add them, - // if the shifted right and left arrows are defined. - if t.KeyShfRight == "\x1b[1;2C" && t.KeyShfLeft == "\x1b[1;2D" { - t.Modifiers = terminfo.ModifiersXTerm - - t.KeyShfUp = "\x1b[1;2A" - t.KeyShfDown = "\x1b[1;2B" - t.KeyMetaUp = "\x1b[1;9A" - t.KeyMetaDown = "\x1b[1;9B" - t.KeyMetaRight = "\x1b[1;9C" - t.KeyMetaLeft = "\x1b[1;9D" - t.KeyAltUp = "\x1b[1;3A" - t.KeyAltDown = "\x1b[1;3B" - t.KeyAltRight = "\x1b[1;3C" - t.KeyAltLeft = "\x1b[1;3D" - t.KeyCtrlUp = "\x1b[1;5A" - t.KeyCtrlDown = "\x1b[1;5B" - t.KeyCtrlRight = "\x1b[1;5C" - t.KeyCtrlLeft = "\x1b[1;5D" - t.KeyAltShfUp = "\x1b[1;4A" - t.KeyAltShfDown = "\x1b[1;4B" - t.KeyAltShfRight = "\x1b[1;4C" - t.KeyAltShfLeft = "\x1b[1;4D" - - t.KeyMetaShfUp = "\x1b[1;10A" - t.KeyMetaShfDown = "\x1b[1;10B" - t.KeyMetaShfRight = "\x1b[1;10C" - t.KeyMetaShfLeft = "\x1b[1;10D" - - t.KeyCtrlShfUp = "\x1b[1;6A" - t.KeyCtrlShfDown = "\x1b[1;6B" - t.KeyCtrlShfRight = "\x1b[1;6C" - t.KeyCtrlShfLeft = "\x1b[1;6D" - - t.KeyShfPgUp = "\x1b[5;2~" - t.KeyShfPgDn = "\x1b[6;2~" - } - // And also for Home and End - if t.KeyShfHome == "\x1b[1;2H" && t.KeyShfEnd == "\x1b[1;2F" { - t.KeyCtrlHome = "\x1b[1;5H" - t.KeyCtrlEnd = "\x1b[1;5F" - t.KeyAltHome = "\x1b[1;9H" - t.KeyAltEnd = "\x1b[1;9F" - t.KeyCtrlShfHome = "\x1b[1;6H" - t.KeyCtrlShfEnd = "\x1b[1;6F" - t.KeyAltShfHome = "\x1b[1;4H" - t.KeyAltShfEnd = "\x1b[1;4F" - t.KeyMetaShfHome = "\x1b[1;10H" - t.KeyMetaShfEnd = "\x1b[1;10F" - } - - // And the same thing for rxvt and workalikes (Eterm, aterm, etc.) - // It seems that urxvt at least send escaped as ALT prefix for these, - // although some places seem to indicate a separate ALT key sesquence. - if t.KeyShfRight == "\x1b[c" && t.KeyShfLeft == "\x1b[d" { - t.KeyShfUp = "\x1b[a" - t.KeyShfDown = "\x1b[b" - t.KeyCtrlUp = "\x1b[Oa" - t.KeyCtrlDown = "\x1b[Ob" - t.KeyCtrlRight = "\x1b[Oc" - t.KeyCtrlLeft = "\x1b[Od" - } - if t.KeyShfHome == "\x1b[7$" && t.KeyShfEnd == "\x1b[8$" { - t.KeyCtrlHome = "\x1b[7^" - t.KeyCtrlEnd = "\x1b[8^" - } - - // Technically the RGB flag that is provided for xterm-direct is not - // quite right. The problem is that the -direct flag that was introduced - // with ncurses 6.1 requires a parsing for the parameters that we lack. - // For this case we'll just assume it's XTerm compatible. Someday this - // may be incorrect, but right now it is correct, and nobody uses it - // anyway. - if tc.getflag("Tc") { - // This presumes XTerm 24-bit true color. - t.TrueColor = true - } else if tc.getflag("RGB") { - // This is for xterm-direct, which uses a different scheme entirely. - // (ncurses went a very different direction from everyone else, and - // so it's unlikely anything is using this definition.) - t.TrueColor = true - t.SetBg = "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m" - t.SetFg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m" - } - - // We only support colors in ANSI 8 or 256 color mode. - if t.Colors < 8 || t.SetFg == "" { - t.Colors = 0 - } - if t.SetCursor == "" { - return nil, "", errNotAddressable - } - - // For padding, we lookup the pad char. If that isn't present, - // and npc is *not* set, then we assume a null byte. - t.PadChar = tc.getstr("pad") - if t.PadChar == "" { - if !tc.getflag("npc") { - t.PadChar = "\u0000" - } - } - - // For terminals that use "standard" SGR sequences, lets combine the - // foreground and background together. - if strings.HasPrefix(t.SetFg, "\x1b[") && - strings.HasPrefix(t.SetBg, "\x1b[") && - strings.HasSuffix(t.SetFg, "m") && - strings.HasSuffix(t.SetBg, "m") { - fg := t.SetFg[:len(t.SetFg)-1] - r := regexp.MustCompile("%p1") - bg := r.ReplaceAllString(t.SetBg[2:], "%p2") - t.SetFgBg = fg + ";" + bg - } - - return t, tc.desc, nil -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/e/emacs/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/e/emacs/term.go deleted file mode 100644 index f6d078d0845..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/e/emacs/term.go +++ /dev/null @@ -1,65 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package emacs - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // GNU Emacs term.el terminal emulation - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "eterm", - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - AttrOff: "\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Reverse: "\x1b[7m", - PadChar: "\x00", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - AutoMargin: true, - }) - - // Emacs term.el terminal emulator term-protocol-version 0.96 - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "eterm-color", - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - AttrOff: "\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - SetFg: "\x1b[%p1%{30}%+%dm", - SetBg: "\x1b[%p1%'('%+%dm", - SetFgBg: "\x1b[%p1%{30}%+%d;%p2%'('%+%dm", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[1~", - KeyEnd: "\x1b[4~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/extended/extended.go b/vendor/github.com/gdamore/tcell/v2/terminfo/extended/extended.go deleted file mode 100644 index 6e5c2e6c897..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/extended/extended.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2024 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package extended contains an extended set of terminal descriptions. -// Applications desiring to have a better chance of Just Working by -// default should include this package. This will significantly increase -// the size of the program. -package extended - -import ( - // The following imports just register themselves -- - // these are the terminal types we aggregate in this package. - _ "github.com/gdamore/tcell/v2/terminfo/a/aixterm" - _ "github.com/gdamore/tcell/v2/terminfo/a/alacritty" - _ "github.com/gdamore/tcell/v2/terminfo/a/ansi" - _ "github.com/gdamore/tcell/v2/terminfo/b/beterm" - _ "github.com/gdamore/tcell/v2/terminfo/c/cygwin" - _ "github.com/gdamore/tcell/v2/terminfo/d/dtterm" - _ "github.com/gdamore/tcell/v2/terminfo/e/emacs" - _ "github.com/gdamore/tcell/v2/terminfo/f/foot" - _ "github.com/gdamore/tcell/v2/terminfo/g/gnome" - _ "github.com/gdamore/tcell/v2/terminfo/h/hpterm" - _ "github.com/gdamore/tcell/v2/terminfo/k/konsole" - _ "github.com/gdamore/tcell/v2/terminfo/k/kterm" - _ "github.com/gdamore/tcell/v2/terminfo/l/linux" - _ "github.com/gdamore/tcell/v2/terminfo/p/pcansi" - _ "github.com/gdamore/tcell/v2/terminfo/r/rxvt" - _ "github.com/gdamore/tcell/v2/terminfo/s/screen" - _ "github.com/gdamore/tcell/v2/terminfo/s/simpleterm" - _ "github.com/gdamore/tcell/v2/terminfo/s/sun" - _ "github.com/gdamore/tcell/v2/terminfo/t/tmux" - _ "github.com/gdamore/tcell/v2/terminfo/v/vt100" - _ "github.com/gdamore/tcell/v2/terminfo/v/vt102" - _ "github.com/gdamore/tcell/v2/terminfo/v/vt220" - _ "github.com/gdamore/tcell/v2/terminfo/v/vt320" - _ "github.com/gdamore/tcell/v2/terminfo/v/vt400" - _ "github.com/gdamore/tcell/v2/terminfo/v/vt420" - _ "github.com/gdamore/tcell/v2/terminfo/v/vt52" - _ "github.com/gdamore/tcell/v2/terminfo/w/wy50" - _ "github.com/gdamore/tcell/v2/terminfo/w/wy60" - _ "github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi" - _ "github.com/gdamore/tcell/v2/terminfo/x/xfce" - _ "github.com/gdamore/tcell/v2/terminfo/x/xterm" - _ "github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty" - _ "github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty" -) diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/f/foot/foot.go b/vendor/github.com/gdamore/tcell/v2/terminfo/f/foot/foot.go deleted file mode 100644 index 5daa3c8acdf..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/f/foot/foot.go +++ /dev/null @@ -1,70 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package foot - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // foot terminal emulator - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "foot", - Aliases: []string{"foot-extra"}, - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h\x1b[22;0;0t", - ExitCA: "\x1b[?1049l\x1b[23;0;0t", - ShowCursor: "\x1b[?12l\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b(B\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38:5:%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48:5:%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38:5:%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48:5:%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\u007f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/g/gnome/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/g/gnome/term.go deleted file mode 100644 index 4a81122ac1a..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/g/gnome/term.go +++ /dev/null @@ -1,136 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package gnome - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // GNOME Terminal - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "gnome", - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - XTermLike: true, - }) - - // GNOME Terminal with xterm 256-colors - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "gnome-256color", - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - XTermLike: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/gen.sh b/vendor/github.com/gdamore/tcell/v2/terminfo/gen.sh deleted file mode 100644 index 851175a3ff8..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/gen.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -while read line -do - case "$line" in - *'|'*) - alias=${line#*|} - line=${line%|*} - ;; - *) - alias=${line%%,*} - ;; - esac - - alias=${alias//-/_} - direc=${alias:0:1} - - mkdir -p ${direc}/${alias} - go run mkinfo.go -P ${alias} -go ${direc}/${alias}/term.go ${line//,/ } -done < models.txt diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/h/hpterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/h/hpterm/term.go deleted file mode 100644 index 56a0fb730e0..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/h/hpterm/term.go +++ /dev/null @@ -1,51 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package hpterm - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // HP X11 terminal emulator (old) - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "hpterm", - Aliases: []string{"X-hpterm"}, - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b&a0y0C\x1bJ", - AttrOff: "\x1b&d@\x0f", - Underline: "\x1b&dD", - Bold: "\x1b&dB", - Dim: "\x1b&dH", - Reverse: "\x1b&dB", - EnterKeypad: "\x1b&s1A", - ExitKeypad: "\x1b&s0A", - PadChar: "\x00", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - SetCursor: "\x1b&a%p1%dy%p2%dC", - CursorBack1: "\b", - CursorUp1: "\x1bA", - KeyUp: "\x1bA", - KeyDown: "\x1bB", - KeyRight: "\x1bC", - KeyLeft: "\x1bD", - KeyInsert: "\x1bQ", - KeyDelete: "\x1bP", - KeyBackspace: "\b", - KeyHome: "\x1bh", - KeyPgUp: "\x1bV", - KeyPgDn: "\x1bU", - KeyF1: "\x1bp", - KeyF2: "\x1bq", - KeyF3: "\x1br", - KeyF4: "\x1bs", - KeyF5: "\x1bt", - KeyF6: "\x1bu", - KeyF7: "\x1bv", - KeyF8: "\x1bw", - KeyClear: "\x1bJ", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/k/konsole/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/k/konsole/term.go deleted file mode 100644 index 36c9423e8f5..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/k/konsole/term.go +++ /dev/null @@ -1,138 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package konsole - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // KDE console window - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "konsole", - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[<", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - XTermLike: true, - }) - - // KDE console window with xterm 256-colors - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "konsole-256color", - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[<", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - XTermLike: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/k/kterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/k/kterm/term.go deleted file mode 100644 index e1a0d8d129c..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/k/kterm/term.go +++ /dev/null @@ -1,71 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package kterm - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // kterm kanji terminal emulator (X window system) - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "kterm", - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - AttrOff: "\x1b[m\x1b(B", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "``aajjkkllmmnnooppqqrrssttuuvvwwxx~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1b[11~", - KeyF2: "\x1b[12~", - KeyF3: "\x1b[13~", - KeyF4: "\x1b[14~", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF15: "\x1b[28~", - KeyF16: "\x1b[29~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - AutoMargin: true, - XTermLike: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/l/linux/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/l/linux/term.go deleted file mode 100644 index 8975bb38323..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/l/linux/term.go +++ /dev/null @@ -1,73 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package linux - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // Linux console - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "linux", - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - ShowCursor: "\x1b[?25h\x1b[?0c", - HideCursor: "\x1b[?25l\x1b[?1c", - AttrOff: "\x1b[m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[1~", - KeyEnd: "\x1b[4~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1b[[A", - KeyF2: "\x1b[[B", - KeyF3: "\x1b[[C", - KeyF4: "\x1b[[D", - KeyF5: "\x1b[[E", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF15: "\x1b[28~", - KeyF16: "\x1b[29~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - KeyBacktab: "\x1b\t", - AutoMargin: true, - InsertChar: "\x1b[@", - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/models.txt b/vendor/github.com/gdamore/tcell/v2/terminfo/models.txt deleted file mode 100644 index 1c709f474ee..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/models.txt +++ /dev/null @@ -1,31 +0,0 @@ -aixterm -alacritty -ansi -beterm -cygwin -dtterm -eterm,eterm-color|emacs -gnome,gnome-256color -hpterm -konsole,konsole-256color -kterm -linux -pcansi -rxvt,rxvt-256color,rxvt-88color,rxvt-unicode,rxvt-unicode-256color -screen,screen-256color -st,st-256color|simpleterm -tmux,tmux-256color -vt52 -vt100 -vt102 -vt220 -vt320 -vt400 -vt420 -wy50 -wy60 -wy99-ansi,wy99a-ansi -xfce -xterm,xterm-88color,xterm-256color -xterm-ghostty -xterm-kitty diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/p/pcansi/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/p/pcansi/term.go deleted file mode 100644 index aadc8719310..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/p/pcansi/term.go +++ /dev/null @@ -1,41 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package pcansi - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // ibm-pc terminal programs claiming to be ANSI - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "pcansi", - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - AttrOff: "\x1b[0;10m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[37;40m", - PadChar: "\x00", - AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe", - EnterAcs: "\x1b[12m", - ExitAcs: "\x1b[10m", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\x1b[D", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyBackspace: "\b", - KeyHome: "\x1b[H", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/r/rxvt/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/r/rxvt/term.go deleted file mode 100644 index 979074aa3f1..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/r/rxvt/term.go +++ /dev/null @@ -1,493 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package rxvt - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // rxvt terminal emulator (X Window System) - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "rxvt", - Aliases: []string{"rxvt-color"}, - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b=", - ExitKeypad: "\x1b>", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b(B\x1b)0", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[7~", - KeyEnd: "\x1b[8~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1b[11~", - KeyF2: "\x1b[12~", - KeyF3: "\x1b[13~", - KeyF4: "\x1b[14~", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF15: "\x1b[28~", - KeyF16: "\x1b[29~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - KeyF21: "\x1b[23$", - KeyF22: "\x1b[24$", - KeyF23: "\x1b[11^", - KeyF24: "\x1b[12^", - KeyF25: "\x1b[13^", - KeyF26: "\x1b[14^", - KeyF27: "\x1b[15^", - KeyF28: "\x1b[17^", - KeyF29: "\x1b[18^", - KeyF30: "\x1b[19^", - KeyF31: "\x1b[20^", - KeyF32: "\x1b[21^", - KeyF33: "\x1b[23^", - KeyF34: "\x1b[24^", - KeyF35: "\x1b[25^", - KeyF36: "\x1b[26^", - KeyF37: "\x1b[28^", - KeyF38: "\x1b[29^", - KeyF39: "\x1b[31^", - KeyF40: "\x1b[32^", - KeyF41: "\x1b[33^", - KeyF42: "\x1b[34^", - KeyF43: "\x1b[23@", - KeyF44: "\x1b[24@", - KeyBacktab: "\x1b[Z", - KeyShfLeft: "\x1b[d", - KeyShfRight: "\x1b[c", - KeyShfUp: "\x1b[a", - KeyShfDown: "\x1b[b", - KeyShfHome: "\x1b[7$", - KeyShfEnd: "\x1b[8$", - KeyShfInsert: "\x1b[2$", - KeyShfDelete: "\x1b[3$", - KeyCtrlUp: "\x1b[Oa", - KeyCtrlDown: "\x1b[Ob", - KeyCtrlRight: "\x1b[Oc", - KeyCtrlLeft: "\x1b[Od", - KeyCtrlHome: "\x1b[7^", - KeyCtrlEnd: "\x1b[8^", - AutoMargin: true, - XTermLike: true, - }) - - // rxvt 2.7.9 with xterm 256-colors - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "rxvt-256color", - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b=", - ExitKeypad: "\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b(B\x1b)0", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[7~", - KeyEnd: "\x1b[8~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1b[11~", - KeyF2: "\x1b[12~", - KeyF3: "\x1b[13~", - KeyF4: "\x1b[14~", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF15: "\x1b[28~", - KeyF16: "\x1b[29~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - KeyF21: "\x1b[23$", - KeyF22: "\x1b[24$", - KeyF23: "\x1b[11^", - KeyF24: "\x1b[12^", - KeyF25: "\x1b[13^", - KeyF26: "\x1b[14^", - KeyF27: "\x1b[15^", - KeyF28: "\x1b[17^", - KeyF29: "\x1b[18^", - KeyF30: "\x1b[19^", - KeyF31: "\x1b[20^", - KeyF32: "\x1b[21^", - KeyF33: "\x1b[23^", - KeyF34: "\x1b[24^", - KeyF35: "\x1b[25^", - KeyF36: "\x1b[26^", - KeyF37: "\x1b[28^", - KeyF38: "\x1b[29^", - KeyF39: "\x1b[31^", - KeyF40: "\x1b[32^", - KeyF41: "\x1b[33^", - KeyF42: "\x1b[34^", - KeyF43: "\x1b[23@", - KeyF44: "\x1b[24@", - KeyBacktab: "\x1b[Z", - KeyShfLeft: "\x1b[d", - KeyShfRight: "\x1b[c", - KeyShfUp: "\x1b[a", - KeyShfDown: "\x1b[b", - KeyShfHome: "\x1b[7$", - KeyShfEnd: "\x1b[8$", - KeyShfInsert: "\x1b[2$", - KeyShfDelete: "\x1b[3$", - KeyCtrlUp: "\x1b[Oa", - KeyCtrlDown: "\x1b[Ob", - KeyCtrlRight: "\x1b[Oc", - KeyCtrlLeft: "\x1b[Od", - KeyCtrlHome: "\x1b[7^", - KeyCtrlEnd: "\x1b[8^", - AutoMargin: true, - XTermLike: true, - }) - - // rxvt 2.7.9 with xterm 88-colors - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "rxvt-88color", - Columns: 80, - Lines: 24, - Colors: 88, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b=", - ExitKeypad: "\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b(B\x1b)0", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[7~", - KeyEnd: "\x1b[8~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1b[11~", - KeyF2: "\x1b[12~", - KeyF3: "\x1b[13~", - KeyF4: "\x1b[14~", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF15: "\x1b[28~", - KeyF16: "\x1b[29~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - KeyF21: "\x1b[23$", - KeyF22: "\x1b[24$", - KeyF23: "\x1b[11^", - KeyF24: "\x1b[12^", - KeyF25: "\x1b[13^", - KeyF26: "\x1b[14^", - KeyF27: "\x1b[15^", - KeyF28: "\x1b[17^", - KeyF29: "\x1b[18^", - KeyF30: "\x1b[19^", - KeyF31: "\x1b[20^", - KeyF32: "\x1b[21^", - KeyF33: "\x1b[23^", - KeyF34: "\x1b[24^", - KeyF35: "\x1b[25^", - KeyF36: "\x1b[26^", - KeyF37: "\x1b[28^", - KeyF38: "\x1b[29^", - KeyF39: "\x1b[31^", - KeyF40: "\x1b[32^", - KeyF41: "\x1b[33^", - KeyF42: "\x1b[34^", - KeyF43: "\x1b[23@", - KeyF44: "\x1b[24@", - KeyBacktab: "\x1b[Z", - KeyShfLeft: "\x1b[d", - KeyShfRight: "\x1b[c", - KeyShfUp: "\x1b[a", - KeyShfDown: "\x1b[b", - KeyShfHome: "\x1b[7$", - KeyShfEnd: "\x1b[8$", - KeyShfInsert: "\x1b[2$", - KeyShfDelete: "\x1b[3$", - KeyCtrlUp: "\x1b[Oa", - KeyCtrlDown: "\x1b[Ob", - KeyCtrlRight: "\x1b[Oc", - KeyCtrlLeft: "\x1b[Od", - KeyCtrlHome: "\x1b[7^", - KeyCtrlEnd: "\x1b[8^", - AutoMargin: true, - XTermLike: true, - }) - - // rxvt-unicode terminal (X Window System) - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "rxvt-unicode", - Columns: 80, - Lines: 24, - Colors: 88, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h", - ExitCA: "\x1b[r\x1b[?1049l", - ShowCursor: "\x1b[?12l\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x1b(B", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b=", - ExitKeypad: "\x1b>", - SetFg: "\x1b[38;5;%p1%dm", - SetBg: "\x1b[48;5;%p1%dm", - SetFgBg: "\x1b[38;5;%p1%d;48;5;%p2%dm", - ResetFgBg: "\x1b[39;49m", - AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[7~", - KeyEnd: "\x1b[8~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1b[11~", - KeyF2: "\x1b[12~", - KeyF3: "\x1b[13~", - KeyF4: "\x1b[14~", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF15: "\x1b[28~", - KeyF16: "\x1b[29~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - KeyBacktab: "\x1b[Z", - KeyShfLeft: "\x1b[d", - KeyShfRight: "\x1b[c", - KeyShfUp: "\x1b[a", - KeyShfDown: "\x1b[b", - KeyShfHome: "\x1b[7$", - KeyShfEnd: "\x1b[8$", - KeyShfInsert: "\x1b[2$", - KeyShfDelete: "\x1b[3$", - KeyCtrlUp: "\x1b[Oa", - KeyCtrlDown: "\x1b[Ob", - KeyCtrlRight: "\x1b[Oc", - KeyCtrlLeft: "\x1b[Od", - KeyCtrlHome: "\x1b[7^", - KeyCtrlEnd: "\x1b[8^", - AutoMargin: true, - InsertChar: "\x1b[@", - }) - - // rxvt-unicode terminal with 256 colors (X Window System) - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "rxvt-unicode-256color", - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h", - ExitCA: "\x1b[r\x1b[?1049l", - ShowCursor: "\x1b[?12l\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x1b(B", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b=", - ExitKeypad: "\x1b>", - SetFg: "\x1b[38;5;%p1%dm", - SetBg: "\x1b[48;5;%p1%dm", - SetFgBg: "\x1b[38;5;%p1%d;48;5;%p2%dm", - ResetFgBg: "\x1b[39;49m", - AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[7~", - KeyEnd: "\x1b[8~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1b[11~", - KeyF2: "\x1b[12~", - KeyF3: "\x1b[13~", - KeyF4: "\x1b[14~", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF15: "\x1b[28~", - KeyF16: "\x1b[29~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - KeyBacktab: "\x1b[Z", - KeyShfLeft: "\x1b[d", - KeyShfRight: "\x1b[c", - KeyShfUp: "\x1b[a", - KeyShfDown: "\x1b[b", - KeyShfHome: "\x1b[7$", - KeyShfEnd: "\x1b[8$", - KeyShfInsert: "\x1b[2$", - KeyShfDelete: "\x1b[3$", - KeyCtrlUp: "\x1b[Oa", - KeyCtrlDown: "\x1b[Ob", - KeyCtrlRight: "\x1b[Oc", - KeyCtrlLeft: "\x1b[Od", - KeyCtrlHome: "\x1b[7^", - KeyCtrlEnd: "\x1b[8^", - AutoMargin: true, - InsertChar: "\x1b[@", - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/s/screen/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/s/screen/term.go deleted file mode 100644 index b8595295134..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/s/screen/term.go +++ /dev/null @@ -1,128 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package screen - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // VT 100/ANSI X3.64 virtual terminal - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "screen", - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - EnterCA: "\x1b[?1049h", - ExitCA: "\x1b[?1049l", - ShowCursor: "\x1b[34h\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b(B\x1b)0", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1bM", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[1~", - KeyEnd: "\x1b[4~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - AutoMargin: true, - }) - - // GNU Screen with 256 colors - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "screen-256color", - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - EnterCA: "\x1b[?1049h", - ExitCA: "\x1b[?1049l", - ShowCursor: "\x1b[34h\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b(B\x1b)0", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1bM", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[1~", - KeyEnd: "\x1b[4~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/s/simpleterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/s/simpleterm/term.go deleted file mode 100644 index 9257637cee6..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/s/simpleterm/term.go +++ /dev/null @@ -1,136 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package simpleterm - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // aka simpleterm - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "st", - Aliases: []string{"stterm"}, - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h", - ExitCA: "\x1b[?1049l", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAcs: "\x1b)0", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[1~", - KeyEnd: "\x1b[4~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyClear: "\x1b[3;5~", - Modifiers: 1, - AutoMargin: true, - XTermLike: true, - }) - - // simpleterm with 256 colors - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "st-256color", - Aliases: []string{"stterm-256color"}, - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h", - ExitCA: "\x1b[?1049l", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAcs: "\x1b)0", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[1~", - KeyEnd: "\x1b[4~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyClear: "\x1b[3;5~", - Modifiers: 1, - AutoMargin: true, - XTermLike: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/s/sun/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/s/sun/term.go deleted file mode 100644 index 16cb96c20a8..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/s/sun/term.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2021 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This terminal definition is hand-coded, as the default terminfo for -// this terminal is busted with respect to color. Unlike pretty much every -// other ANSI compliant terminal, this terminal cannot combine foreground and -// background escapes. The default terminfo also only provides escapes for -// 16-bit color. - -package sun - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // Sun Microsystems Inc. workstation console - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "sun", - Aliases: []string{"sun1", "sun2"}, - Columns: 80, - Lines: 34, - Bell: "\a", - Clear: "\f", - AttrOff: "\x1b[m", - Reverse: "\x1b[7m", - PadChar: "\x00", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[247z", - KeyDelete: "\u007f", - KeyBackspace: "\b", - KeyHome: "\x1b[214z", - KeyEnd: "\x1b[220z", - KeyPgUp: "\x1b[216z", - KeyPgDn: "\x1b[222z", - KeyF1: "\x1b[224z", - KeyF2: "\x1b[225z", - KeyF3: "\x1b[226z", - KeyF4: "\x1b[227z", - KeyF5: "\x1b[228z", - KeyF6: "\x1b[229z", - KeyF7: "\x1b[230z", - KeyF8: "\x1b[231z", - KeyF9: "\x1b[232z", - KeyF10: "\x1b[233z", - KeyF11: "\x1b[234z", - KeyF12: "\x1b[235z", - AutoMargin: true, - InsertChar: "\x1b[@", - }) - - // Sun Microsystems Workstation console with color support (IA systems) - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "sun-color", - Columns: 80, - Lines: 34, - Colors: 256, - Bell: "\a", - Clear: "\f", - AttrOff: "\x1b[m", - Bold: "\x1b[1m", - Reverse: "\x1b[7m", - SetFg: "\x1b[38;5;%p1%dm", - SetBg: "\x1b[48;5;%p1%dm", - ResetFgBg: "\x1b[0m", - PadChar: "\x00", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[247z", - KeyDelete: "\u007f", - KeyBackspace: "\b", - KeyHome: "\x1b[214z", - KeyEnd: "\x1b[220z", - KeyPgUp: "\x1b[216z", - KeyPgDn: "\x1b[222z", - KeyF1: "\x1b[224z", - KeyF2: "\x1b[225z", - KeyF3: "\x1b[226z", - KeyF4: "\x1b[227z", - KeyF5: "\x1b[228z", - KeyF6: "\x1b[229z", - KeyF7: "\x1b[230z", - KeyF8: "\x1b[231z", - KeyF9: "\x1b[232z", - KeyF10: "\x1b[233z", - KeyF11: "\x1b[234z", - KeyF12: "\x1b[235z", - AutoMargin: true, - InsertChar: "\x1b[@", - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/t/tmux/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/t/tmux/term.go deleted file mode 100644 index 8aa76a06bcd..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/t/tmux/term.go +++ /dev/null @@ -1,142 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package tmux - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // tmux terminal multiplexer - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "tmux", - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - EnterCA: "\x1b[?1049h", - ExitCA: "\x1b[?1049l", - ShowCursor: "\x1b[34h\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b(B\x1b)0", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1bM", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[1~", - KeyEnd: "\x1b[4~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - DoubleUnderline: "\x1b[4:2m", - CurlyUnderline: "\x1b[4:3m", - DottedUnderline: "\x1b[4:4m", - DashedUnderline: "\x1b[4:5m", - }) - - // tmux with 256 colors - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "tmux-256color", - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - EnterCA: "\x1b[?1049h", - ExitCA: "\x1b[?1049l", - ShowCursor: "\x1b[34h\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b(B\x1b)0", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1bM", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[1~", - KeyEnd: "\x1b[4~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - DoubleUnderline: "\x1b[4:2m", - CurlyUnderline: "\x1b[4:3m", - DottedUnderline: "\x1b[4:4m", - DashedUnderline: "\x1b[4:5m", - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/terminfo.go b/vendor/github.com/gdamore/tcell/v2/terminfo/terminfo.go deleted file mode 100644 index 44fefc51ddd..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/terminfo.go +++ /dev/null @@ -1,781 +0,0 @@ -// Copyright 2024 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package terminfo - -import ( - "bytes" - "errors" - "fmt" - "io" - "os" - "strconv" - "strings" - "sync" - "time" -) - -var ( - // ErrTermNotFound indicates that a suitable terminal entry could - // not be found. This can result from either not having TERM set, - // or from the TERM failing to support certain minimal functionality, - // in particular absolute cursor addressability (the cup capability) - // is required. For example, legacy "adm3" lacks this capability, - // whereas the slightly newer "adm3a" supports it. This failure - // occurs most often with "dumb". - ErrTermNotFound = errors.New("terminal entry not found") -) - -// Terminfo represents a terminfo entry. Note that we use friendly names -// in Go, but when we write out JSON, we use the same names as terminfo. -// The name, aliases and smous, rmous fields do not come from terminfo directly. -type Terminfo struct { - Name string - Aliases []string - Columns int // cols - Lines int // lines - Colors int // colors - Bell string // bell - Clear string // clear - EnterCA string // smcup - ExitCA string // rmcup - ShowCursor string // cnorm - HideCursor string // civis - AttrOff string // sgr0 - Underline string // smul - Bold string // bold - Blink string // blink - Reverse string // rev - Dim string // dim - Italic string // sitm - EnterKeypad string // smkx - ExitKeypad string // rmkx - SetFg string // setaf - SetBg string // setab - ResetFgBg string // op - SetCursor string // cup - CursorBack1 string // cub1 - CursorUp1 string // cuu1 - PadChar string // pad - KeyBackspace string // kbs - KeyF1 string // kf1 - KeyF2 string // kf2 - KeyF3 string // kf3 - KeyF4 string // kf4 - KeyF5 string // kf5 - KeyF6 string // kf6 - KeyF7 string // kf7 - KeyF8 string // kf8 - KeyF9 string // kf9 - KeyF10 string // kf10 - KeyF11 string // kf11 - KeyF12 string // kf12 - KeyF13 string // kf13 - KeyF14 string // kf14 - KeyF15 string // kf15 - KeyF16 string // kf16 - KeyF17 string // kf17 - KeyF18 string // kf18 - KeyF19 string // kf19 - KeyF20 string // kf20 - KeyF21 string // kf21 - KeyF22 string // kf22 - KeyF23 string // kf23 - KeyF24 string // kf24 - KeyF25 string // kf25 - KeyF26 string // kf26 - KeyF27 string // kf27 - KeyF28 string // kf28 - KeyF29 string // kf29 - KeyF30 string // kf30 - KeyF31 string // kf31 - KeyF32 string // kf32 - KeyF33 string // kf33 - KeyF34 string // kf34 - KeyF35 string // kf35 - KeyF36 string // kf36 - KeyF37 string // kf37 - KeyF38 string // kf38 - KeyF39 string // kf39 - KeyF40 string // kf40 - KeyF41 string // kf41 - KeyF42 string // kf42 - KeyF43 string // kf43 - KeyF44 string // kf44 - KeyF45 string // kf45 - KeyF46 string // kf46 - KeyF47 string // kf47 - KeyF48 string // kf48 - KeyF49 string // kf49 - KeyF50 string // kf50 - KeyF51 string // kf51 - KeyF52 string // kf52 - KeyF53 string // kf53 - KeyF54 string // kf54 - KeyF55 string // kf55 - KeyF56 string // kf56 - KeyF57 string // kf57 - KeyF58 string // kf58 - KeyF59 string // kf59 - KeyF60 string // kf60 - KeyF61 string // kf61 - KeyF62 string // kf62 - KeyF63 string // kf63 - KeyF64 string // kf64 - KeyInsert string // kich1 - KeyDelete string // kdch1 - KeyHome string // khome - KeyEnd string // kend - KeyHelp string // khlp - KeyPgUp string // kpp - KeyPgDn string // knp - KeyUp string // kcuu1 - KeyDown string // kcud1 - KeyLeft string // kcub1 - KeyRight string // kcuf1 - KeyBacktab string // kcbt - KeyExit string // kext - KeyClear string // kclr - KeyPrint string // kprt - KeyCancel string // kcan - Mouse string // kmous - AltChars string // acsc - EnterAcs string // smacs - ExitAcs string // rmacs - EnableAcs string // enacs - KeyShfRight string // kRIT - KeyShfLeft string // kLFT - KeyShfHome string // kHOM - KeyShfEnd string // kEND - KeyShfInsert string // kIC - KeyShfDelete string // kDC - - // These are non-standard extensions to terminfo. This includes - // true color support, and some additional keys. Its kind of bizarre - // that shifted variants of left and right exist, but not up and down. - // Terminal support for these are going to vary amongst XTerm - // emulations, so don't depend too much on them in your application. - - StrikeThrough string // smxx - SetFgBg string // setfgbg - SetFgBgRGB string // setfgbgrgb - SetFgRGB string // setfrgb - SetBgRGB string // setbrgb - KeyShfUp string // shift-up - KeyShfDown string // shift-down - KeyShfPgUp string // shift-kpp - KeyShfPgDn string // shift-knp - KeyCtrlUp string // ctrl-up - KeyCtrlDown string // ctrl-left - KeyCtrlRight string // ctrl-right - KeyCtrlLeft string // ctrl-left - KeyMetaUp string // meta-up - KeyMetaDown string // meta-left - KeyMetaRight string // meta-right - KeyMetaLeft string // meta-left - KeyAltUp string // alt-up - KeyAltDown string // alt-left - KeyAltRight string // alt-right - KeyAltLeft string // alt-left - KeyCtrlHome string - KeyCtrlEnd string - KeyMetaHome string - KeyMetaEnd string - KeyAltHome string - KeyAltEnd string - KeyAltShfUp string - KeyAltShfDown string - KeyAltShfLeft string - KeyAltShfRight string - KeyMetaShfUp string - KeyMetaShfDown string - KeyMetaShfLeft string - KeyMetaShfRight string - KeyCtrlShfUp string - KeyCtrlShfDown string - KeyCtrlShfLeft string - KeyCtrlShfRight string - KeyCtrlShfHome string - KeyCtrlShfEnd string - KeyAltShfHome string - KeyAltShfEnd string - KeyMetaShfHome string - KeyMetaShfEnd string - EnablePaste string // bracketed paste mode - DisablePaste string - PasteStart string - PasteEnd string - Modifiers int - InsertChar string // string to insert a character (ich1) - AutoMargin bool // true if writing to last cell in line advances - TrueColor bool // true if the terminal supports direct color - CursorDefault string - CursorBlinkingBlock string - CursorSteadyBlock string - CursorBlinkingUnderline string - CursorSteadyUnderline string - CursorBlinkingBar string - CursorSteadyBar string - CursorColor string // nothing uses it yet - CursorColorRGB string // Cs (but not really because Cs uses X11 color string) - CursorColorReset string // Cr - EnterUrl string - ExitUrl string - SetWindowSize string - SetWindowTitle string // no terminfo extension - EnableFocusReporting string - DisableFocusReporting string - DisableAutoMargin string // smam - EnableAutoMargin string // rmam - DoubleUnderline string // Smulx with param 2 - CurlyUnderline string // Smulx with param 3 - DottedUnderline string // Smulx with param 4 - DashedUnderline string // Smulx with param 5 - UnderlineColor string // Setuc1 - UnderlineColorRGB string // Setulc - UnderlineColorReset string // ol - XTermLike bool // (XT) has XTerm extensions -} - -const ( - ModifiersNone = 0 - ModifiersXTerm = 1 -) - -type stack []interface{} - -func (st stack) Push(v interface{}) stack { - if b, ok := v.(bool); ok { - if b { - return append(st, 1) - } else { - return append(st, 0) - } - } - return append(st, v) -} - -func (st stack) PopString() (string, stack) { - if len(st) > 0 { - e := st[len(st)-1] - var s string - switch v := e.(type) { - case int: - s = strconv.Itoa(v) - case string: - s = v - } - return s, st[:len(st)-1] - } - return "", st - -} -func (st stack) PopInt() (int, stack) { - if len(st) > 0 { - e := st[len(st)-1] - var i int - switch v := e.(type) { - case int: - i = v - case string: - i, _ = strconv.Atoi(v) - } - return i, st[:len(st)-1] - } - return 0, st -} - -// static vars -var svars [26]string - -type paramsBuffer struct { - out bytes.Buffer - buf bytes.Buffer -} - -// Start initializes the params buffer with the initial string data. -// It also locks the paramsBuffer. The caller must call End() when -// finished. -func (pb *paramsBuffer) Start(s string) { - pb.out.Reset() - pb.buf.Reset() - pb.buf.WriteString(s) -} - -// End returns the final output from TParam, but it also releases the lock. -func (pb *paramsBuffer) End() string { - s := pb.out.String() - return s -} - -// NextCh returns the next input character to the expander. -func (pb *paramsBuffer) NextCh() (byte, error) { - return pb.buf.ReadByte() -} - -// PutCh "emits" (rather schedules for output) a single byte character. -func (pb *paramsBuffer) PutCh(ch byte) { - pb.out.WriteByte(ch) -} - -// PutString schedules a string for output. -func (pb *paramsBuffer) PutString(s string) { - pb.out.WriteString(s) -} - -// TParm takes a terminfo parameterized string, such as setaf or cup, and -// evaluates the string, and returns the result with the parameter -// applied. -func (t *Terminfo) TParm(s string, p ...interface{}) string { - var stk stack - var a string - var ai, bi int - var dvars [26]string - var params [9]interface{} - var pb = ¶msBuffer{} - - pb.Start(s) - - // make sure we always have 9 parameters -- makes it easier - // later to skip checks - for i := 0; i < len(params) && i < len(p); i++ { - params[i] = p[i] - } - - const ( - emit = iota - toEnd - toElse - ) - - skip := emit - - for { - - ch, err := pb.NextCh() - if err != nil { - break - } - - if ch != '%' { - if skip == emit { - pb.PutCh(ch) - } - continue - } - - ch, err = pb.NextCh() - if err != nil { - // XXX Error - break - } - if skip == toEnd { - if ch == ';' { - skip = emit - } - continue - } else if skip == toElse { - if ch == 'e' || ch == ';' { - skip = emit - } - continue - } - - switch ch { - case '%': // quoted % - pb.PutCh(ch) - - case 'i': // increment both parameters (ANSI cup support) - if i, ok := params[0].(int); ok { - params[0] = i + 1 - } - if i, ok := params[1].(int); ok { - params[1] = i + 1 - } - - case 's': - // NB: 's', 'c', and 'd' below are special cased for - // efficiency. They could be handled by the richer - // format support below, less efficiently. - a, stk = stk.PopString() - pb.PutString(a) - - case 'c': - // Integer as special character. - ai, stk = stk.PopInt() - pb.PutCh(byte(ai)) - - case 'd': - ai, stk = stk.PopInt() - pb.PutString(strconv.Itoa(ai)) - - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'x', 'X', 'o', ':': - // This is pretty suboptimal, but this is rarely used. - // None of the mainstream terminals use any of this, - // and it would surprise me if this code is ever - // executed outside test cases. - f := "%" - if ch == ':' { - ch, _ = pb.NextCh() - } - f += string(ch) - for ch == '+' || ch == '-' || ch == '#' || ch == ' ' { - ch, _ = pb.NextCh() - f += string(ch) - } - for (ch >= '0' && ch <= '9') || ch == '.' { - ch, _ = pb.NextCh() - f += string(ch) - } - switch ch { - case 'd', 'x', 'X', 'o': - ai, stk = stk.PopInt() - pb.PutString(fmt.Sprintf(f, ai)) - case 's': - a, stk = stk.PopString() - pb.PutString(fmt.Sprintf(f, a)) - case 'c': - ai, stk = stk.PopInt() - pb.PutString(fmt.Sprintf(f, ai)) - } - - case 'p': // push parameter - ch, _ = pb.NextCh() - ai = int(ch - '1') - if ai >= 0 && ai < len(params) { - stk = stk.Push(params[ai]) - } else { - stk = stk.Push(0) - } - - case 'P': // pop & store variable - ch, _ = pb.NextCh() - if ch >= 'A' && ch <= 'Z' { - svars[int(ch-'A')], stk = stk.PopString() - } else if ch >= 'a' && ch <= 'z' { - dvars[int(ch-'a')], stk = stk.PopString() - } - - case 'g': // recall & push variable - ch, _ = pb.NextCh() - if ch >= 'A' && ch <= 'Z' { - stk = stk.Push(svars[int(ch-'A')]) - } else if ch >= 'a' && ch <= 'z' { - stk = stk.Push(dvars[int(ch-'a')]) - } - - case '\'': // push(char) - the integer value of it - ch, _ = pb.NextCh() - _, _ = pb.NextCh() // must be ' but we don't check - stk = stk.Push(int(ch)) - - case '{': // push(int) - ai = 0 - ch, _ = pb.NextCh() - for ch >= '0' && ch <= '9' { - ai *= 10 - ai += int(ch - '0') - ch, _ = pb.NextCh() - } - // ch must be '}' but no verification - stk = stk.Push(ai) - - case 'l': // push(strlen(pop)) - a, stk = stk.PopString() - stk = stk.Push(len(a)) - - case '+': - bi, stk = stk.PopInt() - ai, stk = stk.PopInt() - stk = stk.Push(ai + bi) - - case '-': - bi, stk = stk.PopInt() - ai, stk = stk.PopInt() - stk = stk.Push(ai - bi) - - case '*': - bi, stk = stk.PopInt() - ai, stk = stk.PopInt() - stk = stk.Push(ai * bi) - - case '/': - bi, stk = stk.PopInt() - ai, stk = stk.PopInt() - if bi != 0 { - stk = stk.Push(ai / bi) - } else { - stk = stk.Push(0) - } - - case 'm': // push(pop mod pop) - bi, stk = stk.PopInt() - ai, stk = stk.PopInt() - if bi != 0 { - stk = stk.Push(ai % bi) - } else { - stk = stk.Push(0) - } - - case '&': // AND - bi, stk = stk.PopInt() - ai, stk = stk.PopInt() - stk = stk.Push(ai & bi) - - case '|': // OR - bi, stk = stk.PopInt() - ai, stk = stk.PopInt() - stk = stk.Push(ai | bi) - - case '^': // XOR - bi, stk = stk.PopInt() - ai, stk = stk.PopInt() - stk = stk.Push(ai ^ bi) - - case '~': // bit complement - ai, stk = stk.PopInt() - stk = stk.Push(ai ^ -1) - - case '!': // logical NOT - ai, stk = stk.PopInt() - stk = stk.Push(ai == 0) - - case '=': // numeric compare - bi, stk = stk.PopInt() - ai, stk = stk.PopInt() - stk = stk.Push(ai == bi) - - case '>': // greater than, numeric - bi, stk = stk.PopInt() - ai, stk = stk.PopInt() - stk = stk.Push(ai > bi) - - case '<': // less than, numeric - bi, stk = stk.PopInt() - ai, stk = stk.PopInt() - stk = stk.Push(ai < bi) - - case '?': // start conditional - - case ';': - skip = emit - - case 't': - ai, stk = stk.PopInt() - if ai == 0 { - skip = toElse - } - - case 'e': - skip = toEnd - - default: - pb.PutString("%" + string(ch)) - } - } - - return pb.End() -} - -// TPuts emits the string to the writer, but expands inline padding -// indications (of the form $<[delay]> where [delay] is msec) to -// a suitable time (unless the terminfo string indicates this isn't needed -// by specifying npc - no padding). All Terminfo based strings should be -// emitted using this function. -func (t *Terminfo) TPuts(w io.Writer, s string) { - for { - beg := strings.Index(s, "$<") - if beg < 0 { - // Most strings don't need padding, which is good news! - _, _ = io.WriteString(w, s) - return - } - _, _ = io.WriteString(w, s[:beg]) - s = s[beg+2:] - end := strings.Index(s, ">") - if end < 0 { - // unterminated.. just emit bytes unadulterated - _, _ = io.WriteString(w, "$<"+s) - return - } - val := s[:end] - s = s[end+1:] - padus := 0 - unit := time.Millisecond - dot := false - loop: - for i := range val { - switch val[i] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - padus *= 10 - padus += int(val[i] - '0') - if dot { - unit /= 10 - } - case '.': - if !dot { - dot = true - } else { - break loop - } - default: - break loop - } - } - - // Curses historically uses padding to achieve "fine grained" - // delays. We have much better clocks these days, and so we - // do not rely on padding but simply sleep a bit. - if len(t.PadChar) > 0 { - time.Sleep(unit * time.Duration(padus)) - } - } -} - -// TGoto returns a string suitable for addressing the cursor at the given -// row and column. The origin 0, 0 is in the upper left corner of the screen. -func (t *Terminfo) TGoto(col, row int) string { - return t.TParm(t.SetCursor, row, col) -} - -// TColor returns a string corresponding to the given foreground and background -// colors. Either fg or bg can be set to -1 to elide. -func (t *Terminfo) TColor(fi, bi int) string { - rv := "" - // As a special case, we map bright colors to lower versions if the - // color table only holds 8. For the remaining 240 colors, the user - // is out of luck. Someday we could create a mapping table, but its - // not worth it. - if t.Colors == 8 { - if fi > 7 && fi < 16 { - fi -= 8 - } - if bi > 7 && bi < 16 { - bi -= 8 - } - } - if t.Colors > fi && fi >= 0 { - rv += t.TParm(t.SetFg, fi) - } - if t.Colors > bi && bi >= 0 { - rv += t.TParm(t.SetBg, bi) - } - return rv -} - -var ( - dblock sync.Mutex - terminfos = make(map[string]*Terminfo) -) - -// AddTerminfo can be called to register a new Terminfo entry. -func AddTerminfo(t *Terminfo) { - dblock.Lock() - terminfos[t.Name] = t - for _, x := range t.Aliases { - terminfos[x] = t - } - dblock.Unlock() -} - -// LookupTerminfo attempts to find a definition for the named $TERM. -func LookupTerminfo(name string) (*Terminfo, error) { - if name == "" { - // else on windows: index out of bounds - // on the name[0] reference below - return nil, ErrTermNotFound - } - - addtruecolor := false - add256color := false - switch os.Getenv("COLORTERM") { - case "truecolor", "24bit", "24-bit": - addtruecolor = true - } - dblock.Lock() - t := terminfos[name] - dblock.Unlock() - - // If the name ends in -truecolor, then fabricate an entry - // from the corresponding -256color, -color, or bare terminal. - if t != nil && t.TrueColor { - addtruecolor = true - } else if t == nil && strings.HasSuffix(name, "-truecolor") { - - suffixes := []string{ - "-256color", - "-88color", - "-color", - "", - } - base := name[:len(name)-len("-truecolor")] - for _, s := range suffixes { - if t, _ = LookupTerminfo(base + s); t != nil { - addtruecolor = true - break - } - } - } - - // If the name ends in -256color, maybe fabricate using the xterm 256 color sequences - if t == nil && strings.HasSuffix(name, "-256color") { - suffixes := []string{ - "-88color", - "-color", - } - base := name[:len(name)-len("-256color")] - for _, s := range suffixes { - if t, _ = LookupTerminfo(base + s); t != nil { - add256color = true - break - } - } - } - - if t == nil { - return nil, ErrTermNotFound - } - - switch os.Getenv("TCELL_TRUECOLOR") { - case "": - case "disable": - addtruecolor = false - default: - addtruecolor = true - } - - // If the user has requested 24-bit color with $COLORTERM, then - // amend the value (unless already present). This means we don't - // need to have a value present. - if addtruecolor && - t.SetFgBgRGB == "" && - t.SetFgRGB == "" && - t.SetBgRGB == "" { - - // Supply vanilla ISO 8613-6:1994 24-bit color sequences. - t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm" - t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm" - t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" + - "48;2;%p4%d;%p5%d;%p6%dm" - } - - if add256color { - t.Colors = 256 - t.SetFg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m" - t.SetBg = "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m" - t.SetFgBg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m" - t.ResetFgBg = "\x1b[39;49m" - } - return t, nil -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt100/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt100/term.go deleted file mode 100644 index 2bad42e97e0..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt100/term.go +++ /dev/null @@ -1,51 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package vt100 - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // DEC VT100 (w/advanced video) - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "vt100", - Aliases: []string{"vt100-am"}, - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b[H\x1b[J$<50>", - AttrOff: "\x1b[m\x0f$<2>", - Underline: "\x1b[4m$<2>", - Bold: "\x1b[1m$<2>", - Blink: "\x1b[5m$<2>", - Reverse: "\x1b[7m$<2>", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b(B\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - SetCursor: "\x1b[%i%p1%d;%p2%dH$<5>", - CursorBack1: "\b", - CursorUp1: "\x1b[A$<2>", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyBackspace: "\b", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1bOt", - KeyF6: "\x1bOu", - KeyF7: "\x1bOv", - KeyF8: "\x1bOl", - KeyF9: "\x1bOw", - KeyF10: "\x1bOx", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt102/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt102/term.go deleted file mode 100644 index 1269b5b7f35..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt102/term.go +++ /dev/null @@ -1,50 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package vt102 - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // DEC VT102 - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "vt102", - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b[H\x1b[J$<50>", - AttrOff: "\x1b[m\x0f$<2>", - Underline: "\x1b[4m$<2>", - Bold: "\x1b[1m$<2>", - Blink: "\x1b[5m$<2>", - Reverse: "\x1b[7m$<2>", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b(B\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - SetCursor: "\x1b[%i%p1%d;%p2%dH$<5>", - CursorBack1: "\b", - CursorUp1: "\x1b[A$<2>", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyBackspace: "\b", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1bOt", - KeyF6: "\x1bOu", - KeyF7: "\x1bOv", - KeyF8: "\x1bOl", - KeyF9: "\x1bOw", - KeyF10: "\x1bOx", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt220/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt220/term.go deleted file mode 100644 index a637677a37b..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt220/term.go +++ /dev/null @@ -1,63 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package vt220 - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // DEC VT220 - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "vt220", - Aliases: []string{"vt200"}, - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x1b(B", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0$<2>", - ExitAcs: "\x1b(B$<4>", - EnableAcs: "\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\b", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - KeyHelp: "\x1b[28~", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt320/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt320/term.go deleted file mode 100644 index e929ed45c6e..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt320/term.go +++ /dev/null @@ -1,66 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package vt320 - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // DEC VT320 7 bit terminal - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "vt320", - Aliases: []string{"vt300"}, - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x1b(B", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1b[1~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF15: "\x1b[28~", - KeyF16: "\x1b[29~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt400/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt400/term.go deleted file mode 100644 index 05406563d30..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt400/term.go +++ /dev/null @@ -1,50 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package vt400 - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // DEC VT400 24x80 column autowrap - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "vt400", - Aliases: []string{"vt400-24", "dec-vt400"}, - Columns: 80, - Lines: 24, - Clear: "\x1b[H\x1b[J$<10/>", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x1b(B", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyBackspace: "\b", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - AutoMargin: true, - InsertChar: "\x1b[@", - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt420/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt420/term.go deleted file mode 100644 index 4c56f1e528c..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt420/term.go +++ /dev/null @@ -1,56 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package vt420 - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // DEC VT420 - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "vt420", - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b[H\x1b[2J$<50>", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x1b(B$<2>", - Underline: "\x1b[4m", - Bold: "\x1b[1m$<2>", - Blink: "\x1b[5m$<2>", - Reverse: "\x1b[7m$<2>", - EnterKeypad: "\x1b=", - ExitKeypad: "\x1b>", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0$<2>", - ExitAcs: "\x1b(B$<4>", - EnableAcs: "\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - SetCursor: "\x1b[%i%p1%d;%p2%dH$<10>", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\b", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[17~", - KeyF6: "\x1b[18~", - KeyF7: "\x1b[19~", - KeyF8: "\x1b[20~", - KeyF9: "\x1b[21~", - KeyF10: "\x1b[29~", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt52/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt52/term.go deleted file mode 100644 index 5d193ed78aa..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt52/term.go +++ /dev/null @@ -1,39 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package vt52 - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // DEC VT52 - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "vt52", - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1bH\x1bJ", - EnterKeypad: "\x1b=", - ExitKeypad: "\x1b>", - PadChar: "\x00", - AltChars: "+h.k0affggolpnqprrss", - EnterAcs: "\x1bF", - ExitAcs: "\x1bG", - SetCursor: "\x1bY%p1%' '%+%c%p2%' '%+%c", - CursorBack1: "\x1bD", - CursorUp1: "\x1bA", - KeyUp: "\x1bA", - KeyDown: "\x1bB", - KeyRight: "\x1bC", - KeyLeft: "\x1bD", - KeyBackspace: "\b", - KeyF1: "\x1bP", - KeyF2: "\x1bQ", - KeyF3: "\x1bR", - KeyF5: "\x1b?t", - KeyF6: "\x1b?u", - KeyF7: "\x1b?v", - KeyF8: "\x1b?w", - KeyF9: "\x1b?x", - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy50/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy50/term.go deleted file mode 100644 index beced62d5c2..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy50/term.go +++ /dev/null @@ -1,60 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package wy50 - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // Wyse 50 - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "wy50", - Aliases: []string{"wyse50"}, - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b+$<20>", - ShowCursor: "\x1b`1", - HideCursor: "\x1b`0", - AttrOff: "\x1b(\x1bH\x03", - Dim: "\x1b`7\x1b)", - Reverse: "\x1b`6\x1b)", - PadChar: "\x00", - AltChars: "a;j5k3l2m1n8q:t4u9v=w0x6", - EnterAcs: "\x1bH\x02", - ExitAcs: "\x1bH\x03", - SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c", - CursorBack1: "\b", - CursorUp1: "\v", - KeyUp: "\v", - KeyDown: "\n", - KeyRight: "\f", - KeyLeft: "\b", - KeyInsert: "\x1bQ", - KeyDelete: "\x1bW", - KeyBackspace: "\b", - KeyHome: "\x1e", - KeyPgUp: "\x1bJ", - KeyPgDn: "\x1bK", - KeyF1: "\x01@\r", - KeyF2: "\x01A\r", - KeyF3: "\x01B\r", - KeyF4: "\x01C\r", - KeyF5: "\x01D\r", - KeyF6: "\x01E\r", - KeyF7: "\x01F\r", - KeyF8: "\x01G\r", - KeyF9: "\x01H\r", - KeyF10: "\x01I\r", - KeyF11: "\x01J\r", - KeyF12: "\x01K\r", - KeyF13: "\x01L\r", - KeyF14: "\x01M\r", - KeyF15: "\x01N\r", - KeyF16: "\x01O\r", - KeyPrint: "\x1bP", - KeyBacktab: "\x1bI", - KeyShfHome: "\x1b{", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy60/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy60/term.go deleted file mode 100644 index 27705f205bc..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy60/term.go +++ /dev/null @@ -1,66 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package wy60 - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // Wyse 60 - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "wy60", - Aliases: []string{"wyse60"}, - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b+$<100>", - EnterCA: "\x1bw0", - ExitCA: "\x1bw1", - ShowCursor: "\x1b`1", - HideCursor: "\x1b`0", - AttrOff: "\x1b(\x1bH\x03\x1bG0\x1bcD", - Underline: "\x1bG8", - Dim: "\x1bGp", - Blink: "\x1bG2", - Reverse: "\x1bG4", - PadChar: "\x00", - AltChars: "+/,.0[a2fxgqh1ihjYk?lZm@nEqDtCu4vAwBx3yszr{c~~", - EnterAcs: "\x1bcE", - ExitAcs: "\x1bcD", - EnableAutoMargin: "\x1bd/", - DisableAutoMargin: "\x1bd.", - SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c", - CursorBack1: "\b", - CursorUp1: "\v", - KeyUp: "\v", - KeyDown: "\n", - KeyRight: "\f", - KeyLeft: "\b", - KeyInsert: "\x1bQ", - KeyDelete: "\x1bW", - KeyBackspace: "\b", - KeyHome: "\x1e", - KeyPgUp: "\x1bJ", - KeyPgDn: "\x1bK", - KeyF1: "\x01@\r", - KeyF2: "\x01A\r", - KeyF3: "\x01B\r", - KeyF4: "\x01C\r", - KeyF5: "\x01D\r", - KeyF6: "\x01E\r", - KeyF7: "\x01F\r", - KeyF8: "\x01G\r", - KeyF9: "\x01H\r", - KeyF10: "\x01I\r", - KeyF11: "\x01J\r", - KeyF12: "\x01K\r", - KeyF13: "\x01L\r", - KeyF14: "\x01M\r", - KeyF15: "\x01N\r", - KeyF16: "\x01O\r", - KeyPrint: "\x1bP", - KeyBacktab: "\x1bI", - KeyShfHome: "\x1b{", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi/term.go deleted file mode 100644 index 9b5cd7e7e0e..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi/term.go +++ /dev/null @@ -1,120 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package wy99_ansi - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // Wyse WY-99GT in ANSI mode (int'l PC keyboard) - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "wy99-ansi", - Columns: 80, - Lines: 25, - Bell: "\a", - Clear: "\x1b[H\x1b[J$<200>", - ShowCursor: "\x1b[34h\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0f\x1b[\"q", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h", - ExitKeypad: "\x1b[?1l", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooqqssttuuvvwwxx{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b$<1>", - CursorUp1: "\x1bM", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyBackspace: "\b", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[M", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF17: "\x1b[K", - KeyF18: "\x1b[31~", - KeyF19: "\x1b[32~", - KeyF20: "\x1b[33~", - KeyF21: "\x1b[34~", - KeyF22: "\x1b[35~", - KeyF23: "\x1b[1~", - KeyF24: "\x1b[2~", - KeyBacktab: "\x1b[z", - AutoMargin: true, - }) - - // Wyse WY-99GT in ANSI mode (US PC keyboard) - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "wy99a-ansi", - Columns: 80, - Lines: 25, - Bell: "\a", - Clear: "\x1b[H\x1b[J$<200>", - ShowCursor: "\x1b[34h\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0f\x1b[\"q", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h", - ExitKeypad: "\x1b[?1l", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooqqssttuuvvwwxx{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b$<1>", - CursorUp1: "\x1bM", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyBackspace: "\b", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[M", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF17: "\x1b[K", - KeyF18: "\x1b[31~", - KeyF19: "\x1b[32~", - KeyF20: "\x1b[33~", - KeyF21: "\x1b[34~", - KeyF22: "\x1b[35~", - KeyF23: "\x1b[1~", - KeyF24: "\x1b[2~", - KeyBacktab: "\x1b[z", - AutoMargin: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xfce/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xfce/term.go deleted file mode 100644 index b9999a1c59d..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xfce/term.go +++ /dev/null @@ -1,70 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package xfce - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // Xfce Terminal - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "xfce", - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m\x0f", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - PadChar: "\x00", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b)0", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - XTermLike: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/direct.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/direct.go deleted file mode 100644 index 358ebae91cf..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/direct.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2021 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This terminal definition is derived from the xterm-256color definition, but -// makes use of the RGB property these terminals have to support direct color. -// The terminfo entry for this uses a new format for the color handling introduced -// by ncurses 6.1 (and used by nobody else), so this override ensures we get -// good handling even in the face of this. - -package xterm - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // derived from xterm-256color, but adds full RGB support - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "xterm-direct", - Aliases: []string{"xterm-truecolor"}, - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h\x1b[22;0;0t", - ExitCA: "\x1b[?1049l\x1b[23;0;0t", - ShowCursor: "\x1b[?12l\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b(B\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - SetFgRGB: "\x1b[38;2;%p1%d;%p2%d;%p3%dm", - SetBgRGB: "\x1b[48;2;%p1%d;%p2%d;%p3%dm", - SetFgBgRGB: "\x1b[38;2;%p1%d;%p2%d;%p3%d;48;2;%p4%d;%p5%d;%p6%dm", - ResetFgBg: "\x1b[39;49m", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\u007f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - TrueColor: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/term.go deleted file mode 100644 index faf7d8acbbe..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/term.go +++ /dev/null @@ -1,201 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package xterm - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // xterm terminal emulator (X Window System) - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "xterm", - Aliases: []string{"xterm-debian"}, - Columns: 80, - Lines: 24, - Colors: 8, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h\x1b[22;0;0t", - ExitCA: "\x1b[?1049l\x1b[23;0;0t", - ShowCursor: "\x1b[?12l\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b(B\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[3%p1%dm", - SetBg: "\x1b[4%p1%dm", - SetFgBg: "\x1b[3%p1%d;4%p2%dm", - ResetFgBg: "\x1b[39;49m", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[<", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - XTermLike: true, - }) - - // xterm with 88 colors - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "xterm-88color", - Columns: 80, - Lines: 24, - Colors: 88, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h\x1b[22;0;0t", - ExitCA: "\x1b[?1049l\x1b[23;0;0t", - ShowCursor: "\x1b[?12l\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b(B\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[<", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - XTermLike: true, - }) - - // xterm with 256 colors - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "xterm-256color", - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h\x1b[22;0;0t", - ExitCA: "\x1b[?1049l\x1b[23;0;0t", - ShowCursor: "\x1b[?12l\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b(B\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[<", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - AutoMargin: true, - XTermLike: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty/term.go deleted file mode 100644 index 54d88db92db..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty/term.go +++ /dev/null @@ -1,79 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package xterm_ghostty - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // Ghostty - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "xterm-ghostty", - Aliases: []string{"ghostty"}, - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h", - ExitCA: "\x1b[?1049l", - ShowCursor: "\x1b[?12l\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b(B\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h\x1b=", - ExitKeypad: "\x1b[?1l\x1b>", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[<", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - TrueColor: true, - AutoMargin: true, - InsertChar: "\x1b[@", - DoubleUnderline: "\x1b[4:2m", - CurlyUnderline: "\x1b[4:3m", - DottedUnderline: "\x1b[4:4m", - DashedUnderline: "\x1b[4:5m", - XTermLike: true, - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty/term.go deleted file mode 100644 index 8ee597760e4..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty/term.go +++ /dev/null @@ -1,75 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package xterm_kitty - -import "github.com/gdamore/tcell/v2/terminfo" - -func init() { - - // KovIdTTY - terminfo.AddTerminfo(&terminfo.Terminfo{ - Name: "xterm-kitty", - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b[?1049h", - ExitCA: "\x1b[?1049l", - ShowCursor: "\x1b[?12h\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b(B\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Dim: "\x1b[2m", - Italic: "\x1b[3m", - Reverse: "\x1b[7m", - EnterKeypad: "\x1b[?1h", - ExitKeypad: "\x1b[?1l", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - ResetFgBg: "\x1b[39;49m", - AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x1b(0", - ExitAcs: "\x1b(B", - EnableAutoMargin: "\x1b[?7h", - DisableAutoMargin: "\x1b[?7l", - StrikeThrough: "\x1b[9m", - Mouse: "\x1b[M", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1bOA", - KeyDown: "\x1bOB", - KeyRight: "\x1bOC", - KeyLeft: "\x1bOD", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\x7f", - KeyHome: "\x1bOH", - KeyEnd: "\x1bOF", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1bOP", - KeyF2: "\x1bOQ", - KeyF3: "\x1bOR", - KeyF4: "\x1bOS", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyBacktab: "\x1b[Z", - Modifiers: 1, - TrueColor: true, - AutoMargin: true, - DoubleUnderline: "\x1b[4:2m", - CurlyUnderline: "\x1b[4:3m", - DottedUnderline: "\x1b[4:4m", - DashedUnderline: "\x1b[4:5m", - }) -} diff --git a/vendor/github.com/gdamore/tcell/v2/terms_default.go b/vendor/github.com/gdamore/tcell/v2/terms_default.go deleted file mode 100644 index fefcf8938fb..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terms_default.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build !tcell_minimal -// +build !tcell_minimal - -// Copyright 2019 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - // This imports the default terminal entries. To disable, use the - // tcell_minimal build tag. - _ "github.com/gdamore/tcell/v2/terminfo/extended" -) diff --git a/vendor/github.com/gdamore/tcell/v2/terms_dynamic.go b/vendor/github.com/gdamore/tcell/v2/terms_dynamic.go deleted file mode 100644 index 9e549449886..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terms_dynamic.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build !tcell_minimal && !nacl && !js && !zos && !plan9 && !windows && !android -// +build !tcell_minimal,!nacl,!js,!zos,!plan9,!windows,!android - -// Copyright 2019 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - // This imports a dynamic version of the terminal database, which - // is built using infocmp. This relies on a working installation - // of infocmp (typically supplied with ncurses). We only do this - // for systems likely to have that -- i.e. UNIX based hosts. We - // also don't support Android here, because you really don't want - // to run external programs there. Generally the android terminals - // will be automatically included anyway. - "github.com/gdamore/tcell/v2/terminfo" - "github.com/gdamore/tcell/v2/terminfo/dynamic" - - "fmt" -) - -func loadDynamicTerminfo(term string) (*terminfo.Terminfo, error) { - if term == "" { - return nil, fmt.Errorf("%w: term not set", ErrTermNotFound) - } - ti, _, e := dynamic.LoadTerminfo(term) - if e != nil { - return nil, e - } - return ti, nil -} diff --git a/vendor/github.com/gdamore/tcell/v2/terms_static.go b/vendor/github.com/gdamore/tcell/v2/terms_static.go deleted file mode 100644 index 6d725cbccc5..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/terms_static.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build tcell_minimal || nacl || zos || plan9 || windows || android || js -// +build tcell_minimal nacl zos plan9 windows android js - -// Copyright 2019 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "errors" - - "github.com/gdamore/tcell/v2/terminfo" -) - -func loadDynamicTerminfo(_ string) (*terminfo.Terminfo, error) { - return nil, errors.New("terminal type unsupported") -} diff --git a/vendor/github.com/gdamore/tcell/v2/tscreen.go b/vendor/github.com/gdamore/tcell/v2/tscreen.go deleted file mode 100644 index 2c1d32d31c7..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/tscreen.go +++ /dev/null @@ -1,2153 +0,0 @@ -// Copyright 2024 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !(js && wasm) -// +build !js !wasm - -package tcell - -import ( - "bytes" - "encoding/base64" - "errors" - "io" - "os" - "strconv" - "strings" - "sync" - "time" - "unicode/utf8" - - "golang.org/x/term" - "golang.org/x/text/transform" - - "github.com/gdamore/tcell/v2/terminfo" -) - -// NewTerminfoScreen returns a Screen that uses the stock TTY interface -// and POSIX terminal control, combined with a terminfo description taken from -// the $TERM environment variable. It returns an error if the terminal -// is not supported for any reason. -// -// For terminals that do not support dynamic resize events, the $LINES -// $COLUMNS environment variables can be set to the actual window size, -// otherwise defaults taken from the terminal database are used. -func NewTerminfoScreen() (Screen, error) { - return NewTerminfoScreenFromTty(nil) -} - -// LookupTerminfo attempts to find a definition for the named $TERM falling -// back to attempting to parse the output from infocmp. -func LookupTerminfo(name string) (ti *terminfo.Terminfo, e error) { - ti, e = terminfo.LookupTerminfo(name) - if e != nil { - ti, e = loadDynamicTerminfo(name) - if e != nil { - return nil, e - } - terminfo.AddTerminfo(ti) - } - - return -} - -// NewTerminfoScreenFromTtyTerminfo returns a Screen using a custom Tty -// implementation and custom terminfo specification. -// If the passed in tty is nil, then a reasonable default (typically /dev/tty) -// is presumed, at least on UNIX hosts. (Windows hosts will typically fail this -// call altogether.) -// If passed terminfo is nil, then TERM environment variable is queried for -// terminal specification. -func NewTerminfoScreenFromTtyTerminfo(tty Tty, ti *terminfo.Terminfo) (s Screen, e error) { - if ti == nil { - ti, e = LookupTerminfo(os.Getenv("TERM")) - if e != nil { - return - } - } - - t := &tScreen{ti: ti, tty: tty} - - t.keyexist = make(map[Key]bool) - t.keycodes = make(map[string]*tKeyCode) - if len(ti.Mouse) > 0 { - t.mouse = []byte(ti.Mouse) - } - t.prepareKeys() - t.buildAcsMap() - t.resizeQ = make(chan bool, 1) - t.fallback = make(map[rune]string) - for k, v := range RuneFallbacks { - t.fallback[k] = v - } - - return &baseScreen{screenImpl: t}, nil -} - -// NewTerminfoScreenFromTty returns a Screen using a custom Tty implementation. -// If the passed in tty is nil, then a reasonable default (typically /dev/tty) -// is presumed, at least on UNIX hosts. (Windows hosts will typically fail this -// call altogether.) -func NewTerminfoScreenFromTty(tty Tty) (Screen, error) { - return NewTerminfoScreenFromTtyTerminfo(tty, nil) -} - -// tKeyCode represents a combination of a key code and modifiers. -type tKeyCode struct { - key Key - mod ModMask -} - -// tScreen represents a screen backed by a terminfo implementation. -type tScreen struct { - ti *terminfo.Terminfo - tty Tty - h int - w int - fini bool - cells CellBuffer - buffering bool // true if we are collecting writes to buf instead of sending directly to out - buf bytes.Buffer - curstyle Style - style Style - resizeQ chan bool - quit chan struct{} - keyexist map[Key]bool - keycodes map[string]*tKeyCode - keychan chan []byte - keytimer *time.Timer - keyexpire time.Time - cx int - cy int - mouse []byte - clear bool - cursorx int - cursory int - acs map[rune]string - charset string - encoder transform.Transformer - decoder transform.Transformer - fallback map[rune]string - colors map[Color]Color - palette []Color - truecolor bool - escaped bool - buttondn bool - finiOnce sync.Once - enablePaste string - disablePaste string - enterUrl string - exitUrl string - setWinSize string - enableFocus string - disableFocus string - doubleUnder string - curlyUnder string - dottedUnder string - dashedUnder string - underColor string - underRGB string - underFg string - cursorStyles map[CursorStyle]string - cursorStyle CursorStyle - cursorColor Color - cursorRGB string - cursorFg string - saved *term.State - stopQ chan struct{} - eventQ chan Event - running bool - wg sync.WaitGroup - mouseFlags MouseFlags - pasteEnabled bool - focusEnabled bool - setTitle string - saveTitle string - restoreTitle string - title string - setClipboard string - - sync.Mutex -} - -func (t *tScreen) Init() error { - if e := t.initialize(); e != nil { - return e - } - - t.keychan = make(chan []byte, 10) - t.keytimer = time.NewTimer(time.Millisecond * 50) - t.charset = "UTF-8" - - t.charset = getCharset() - if enc := GetEncoding(t.charset); enc != nil { - t.encoder = enc.NewEncoder() - t.decoder = enc.NewDecoder() - } else { - return ErrNoCharset - } - ti := t.ti - - // environment overrides - w := ti.Columns - h := ti.Lines - if i, _ := strconv.Atoi(os.Getenv("LINES")); i != 0 { - h = i - } - if i, _ := strconv.Atoi(os.Getenv("COLUMNS")); i != 0 { - w = i - } - if t.ti.SetFgBgRGB != "" || t.ti.SetFgRGB != "" || t.ti.SetBgRGB != "" { - t.truecolor = true - } - // A user who wants to have his themes honored can - // set this environment variable. - if os.Getenv("TCELL_TRUECOLOR") == "disable" { - t.truecolor = false - } - nColors := t.nColors() - if nColors > 256 { - nColors = 256 // clip to reasonable limits - } - t.colors = make(map[Color]Color, nColors) - t.palette = make([]Color, nColors) - for i := 0; i < nColors; i++ { - t.palette[i] = Color(i) | ColorValid - // identity map for our builtin colors - t.colors[Color(i)|ColorValid] = Color(i) | ColorValid - } - - t.quit = make(chan struct{}) - t.eventQ = make(chan Event, 10) - - t.Lock() - t.cx = -1 - t.cy = -1 - t.style = StyleDefault - t.cells.Resize(w, h) - t.cursorx = -1 - t.cursory = -1 - t.resize() - t.Unlock() - - if err := t.engage(); err != nil { - return err - } - - return nil -} - -func (t *tScreen) prepareKeyMod(key Key, mod ModMask, val string) { - if val != "" { - // Do not override codes that already exist - if _, exist := t.keycodes[val]; !exist { - t.keyexist[key] = true - t.keycodes[val] = &tKeyCode{key: key, mod: mod} - } - } -} - -func (t *tScreen) prepareKeyModReplace(key Key, replace Key, mod ModMask, val string) { - if val != "" { - // Do not override codes that already exist - if old, exist := t.keycodes[val]; !exist || old.key == replace { - t.keyexist[key] = true - t.keycodes[val] = &tKeyCode{key: key, mod: mod} - } - } -} - -func (t *tScreen) prepareKeyModXTerm(key Key, val string) { - - if strings.HasPrefix(val, "\x1b[") && strings.HasSuffix(val, "~") { - - // Drop the trailing ~ - val = val[:len(val)-1] - - // These suffixes are calculated assuming Xterm style modifier suffixes. - // Please see https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf for - // more information (specifically "PC-Style Function Keys"). - t.prepareKeyModReplace(key, key+12, ModShift, val+";2~") - t.prepareKeyModReplace(key, key+48, ModAlt, val+";3~") - t.prepareKeyModReplace(key, key+60, ModAlt|ModShift, val+";4~") - t.prepareKeyModReplace(key, key+24, ModCtrl, val+";5~") - t.prepareKeyModReplace(key, key+36, ModCtrl|ModShift, val+";6~") - t.prepareKeyMod(key, ModAlt|ModCtrl, val+";7~") - t.prepareKeyMod(key, ModShift|ModAlt|ModCtrl, val+";8~") - t.prepareKeyMod(key, ModMeta, val+";9~") - t.prepareKeyMod(key, ModMeta|ModShift, val+";10~") - t.prepareKeyMod(key, ModMeta|ModAlt, val+";11~") - t.prepareKeyMod(key, ModMeta|ModAlt|ModShift, val+";12~") - t.prepareKeyMod(key, ModMeta|ModCtrl, val+";13~") - t.prepareKeyMod(key, ModMeta|ModCtrl|ModShift, val+";14~") - t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt, val+";15~") - t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt|ModShift, val+";16~") - } else if strings.HasPrefix(val, "\x1bO") && len(val) == 3 { - val = val[2:] - t.prepareKeyModReplace(key, key+12, ModShift, "\x1b[1;2"+val) - t.prepareKeyModReplace(key, key+48, ModAlt, "\x1b[1;3"+val) - t.prepareKeyModReplace(key, key+24, ModCtrl, "\x1b[1;5"+val) - t.prepareKeyModReplace(key, key+36, ModCtrl|ModShift, "\x1b[1;6"+val) - t.prepareKeyModReplace(key, key+60, ModAlt|ModShift, "\x1b[1;4"+val) - t.prepareKeyMod(key, ModAlt|ModCtrl, "\x1b[1;7"+val) - t.prepareKeyMod(key, ModShift|ModAlt|ModCtrl, "\x1b[1;8"+val) - t.prepareKeyMod(key, ModMeta, "\x1b[1;9"+val) - t.prepareKeyMod(key, ModMeta|ModShift, "\x1b[1;10"+val) - t.prepareKeyMod(key, ModMeta|ModAlt, "\x1b[1;11"+val) - t.prepareKeyMod(key, ModMeta|ModAlt|ModShift, "\x1b[1;12"+val) - t.prepareKeyMod(key, ModMeta|ModCtrl, "\x1b[1;13"+val) - t.prepareKeyMod(key, ModMeta|ModCtrl|ModShift, "\x1b[1;14"+val) - t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt, "\x1b[1;15"+val) - t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt|ModShift, "\x1b[1;16"+val) - } -} - -func (t *tScreen) prepareXtermModifiers() { - if t.ti.Modifiers != terminfo.ModifiersXTerm { - return - } - t.prepareKeyModXTerm(KeyRight, t.ti.KeyRight) - t.prepareKeyModXTerm(KeyLeft, t.ti.KeyLeft) - t.prepareKeyModXTerm(KeyUp, t.ti.KeyUp) - t.prepareKeyModXTerm(KeyDown, t.ti.KeyDown) - t.prepareKeyModXTerm(KeyInsert, t.ti.KeyInsert) - t.prepareKeyModXTerm(KeyDelete, t.ti.KeyDelete) - t.prepareKeyModXTerm(KeyPgUp, t.ti.KeyPgUp) - t.prepareKeyModXTerm(KeyPgDn, t.ti.KeyPgDn) - t.prepareKeyModXTerm(KeyHome, t.ti.KeyHome) - t.prepareKeyModXTerm(KeyEnd, t.ti.KeyEnd) - t.prepareKeyModXTerm(KeyF1, t.ti.KeyF1) - t.prepareKeyModXTerm(KeyF2, t.ti.KeyF2) - t.prepareKeyModXTerm(KeyF3, t.ti.KeyF3) - t.prepareKeyModXTerm(KeyF4, t.ti.KeyF4) - t.prepareKeyModXTerm(KeyF5, t.ti.KeyF5) - t.prepareKeyModXTerm(KeyF6, t.ti.KeyF6) - t.prepareKeyModXTerm(KeyF7, t.ti.KeyF7) - t.prepareKeyModXTerm(KeyF8, t.ti.KeyF8) - t.prepareKeyModXTerm(KeyF9, t.ti.KeyF9) - t.prepareKeyModXTerm(KeyF10, t.ti.KeyF10) - t.prepareKeyModXTerm(KeyF11, t.ti.KeyF11) - t.prepareKeyModXTerm(KeyF12, t.ti.KeyF12) -} - -func (t *tScreen) prepareBracketedPaste() { - // Another workaround for lack of reporting in terminfo. - // We assume if the terminal has a mouse entry, that it - // offers bracketed paste. But we allow specific overrides - // via our terminal database. - if t.ti.EnablePaste != "" { - t.enablePaste = t.ti.EnablePaste - t.disablePaste = t.ti.DisablePaste - t.prepareKey(keyPasteStart, t.ti.PasteStart) - t.prepareKey(keyPasteEnd, t.ti.PasteEnd) - } else if t.ti.Mouse != "" || t.ti.XTermLike { - t.enablePaste = "\x1b[?2004h" - t.disablePaste = "\x1b[?2004l" - t.prepareKey(keyPasteStart, "\x1b[200~") - t.prepareKey(keyPasteEnd, "\x1b[201~") - } -} - -func (t *tScreen) prepareUnderlines() { - if t.ti.DoubleUnderline != "" { - t.doubleUnder = t.ti.DoubleUnderline - } else if t.ti.XTermLike { - t.doubleUnder = "\x1b[4:2m" - } - if t.ti.CurlyUnderline != "" { - t.curlyUnder = t.ti.CurlyUnderline - } else if t.ti.XTermLike { - t.curlyUnder = "\x1b[4:3m" - } - if t.ti.DottedUnderline != "" { - t.dottedUnder = t.ti.DottedUnderline - } else if t.ti.XTermLike { - t.dottedUnder = "\x1b[4:4m" - } - if t.ti.DashedUnderline != "" { - t.dashedUnder = t.ti.DashedUnderline - } else if t.ti.XTermLike { - t.dashedUnder = "\x1b[4:5m" - } - - // Underline colors. We're not going to rely upon terminfo for this - // Essentially all terminals that support the curly underlines are - // expected to also support coloring them too - which reflects actual - // practice since these were introduced at about the same time. - if t.ti.UnderlineColor != "" { - t.underColor = t.ti.UnderlineColor - } else if t.curlyUnder != "" { - t.underColor = "\x1b[58:5:%p1%dm" - } - if t.ti.UnderlineColorRGB != "" { - // An interesting wart here is that in order to facilitate - // using just a single parameter, the Setulc parameter takes - // the 24-bit color as an integer rather than separate bytes. - // This matches the "new" style direct color approach that - // ncurses took, even though everyone else went another way. - t.underRGB = t.ti.UnderlineColorRGB - } else if t.underColor != "" { - t.underRGB = "\x1b[58:2::%p1%d:%p2%d:%p3%dm" - } - if t.ti.UnderlineColorReset != "" { - t.underFg = t.ti.UnderlineColorReset - } else if t.curlyUnder != "" { - t.underFg = "\x1b[59m" - } -} - -func (t *tScreen) prepareExtendedOSC() { - // Linux is a special beast - because it has a mouse entry, but does - // not swallow these OSC commands properly. - if strings.Contains(t.ti.Name, "linux") { - return - } - // More stuff for limits in terminfo. This time we are applying - // the most common OSC (operating system commands). Generally - // terminals that don't understand these will ignore them. - // Again, we condition this based on mouse capabilities. - if t.ti.EnterUrl != "" { - t.enterUrl = t.ti.EnterUrl - t.exitUrl = t.ti.ExitUrl - } else if t.ti.Mouse != "" || t.ti.XTermLike { - t.enterUrl = "\x1b]8;%p2%s;%p1%s\x1b\\" - t.exitUrl = "\x1b]8;;\x1b\\" - } - - if t.ti.SetWindowSize != "" { - t.setWinSize = t.ti.SetWindowSize - } else if t.ti.Mouse != "" || t.ti.XTermLike { - t.setWinSize = "\x1b[8;%p1%p2%d;%dt" - } - - if t.ti.EnableFocusReporting != "" { - t.enableFocus = t.ti.EnableFocusReporting - } else if t.ti.Mouse != "" || t.ti.XTermLike { - t.enableFocus = "\x1b[?1004h" - } - if t.ti.DisableFocusReporting != "" { - t.disableFocus = t.ti.DisableFocusReporting - } else if t.ti.Mouse != "" || t.ti.XTermLike { - t.disableFocus = "\x1b[?1004l" - } - - if t.ti.SetWindowTitle != "" { - t.setTitle = t.ti.SetWindowTitle - } else if t.ti.XTermLike { - t.saveTitle = "\x1b[22;2t" - t.restoreTitle = "\x1b[23;2t" - // this also tries to request that UTF-8 is allowed in the title - t.setTitle = "\x1b[>2t\x1b]2;%p1%s\x1b\\" - } - - if t.setClipboard == "" && t.ti.XTermLike { - // this string takes a base64 string and sends it to the clipboard. - // it will also be able to retrieve the clipboard using "?" as the - // sent string, when we support that. - t.setClipboard = "\x1b]52;c;%p1%s\x1b\\" - } -} - -func (t *tScreen) prepareCursorStyles() { - // Another workaround for lack of reporting in terminfo. - // We assume if the terminal has a mouse entry, that it - // offers bracketed paste. But we allow specific overrides - // via our terminal database. - if t.ti.CursorDefault != "" { - t.cursorStyles = map[CursorStyle]string{ - CursorStyleDefault: t.ti.CursorDefault, - CursorStyleBlinkingBlock: t.ti.CursorBlinkingBlock, - CursorStyleSteadyBlock: t.ti.CursorSteadyBlock, - CursorStyleBlinkingUnderline: t.ti.CursorBlinkingUnderline, - CursorStyleSteadyUnderline: t.ti.CursorSteadyUnderline, - CursorStyleBlinkingBar: t.ti.CursorBlinkingBar, - CursorStyleSteadyBar: t.ti.CursorSteadyBar, - } - } else if t.ti.Mouse != "" || t.ti.XTermLike { - t.cursorStyles = map[CursorStyle]string{ - CursorStyleDefault: "\x1b[0 q", - CursorStyleBlinkingBlock: "\x1b[1 q", - CursorStyleSteadyBlock: "\x1b[2 q", - CursorStyleBlinkingUnderline: "\x1b[3 q", - CursorStyleSteadyUnderline: "\x1b[4 q", - CursorStyleBlinkingBar: "\x1b[5 q", - CursorStyleSteadyBar: "\x1b[6 q", - } - } - if t.ti.CursorColorRGB != "" { - // if it was X11 style with just a single %p1%s, then convert - t.cursorRGB = t.ti.CursorColorRGB - } - if t.ti.CursorColorReset != "" { - t.cursorFg = t.ti.CursorColorReset - } - if t.cursorRGB == "" { - t.cursorRGB = "\x1b]12;%p1%s\007" - t.cursorFg = "\x1b]112\007" - } - - // convert XTERM style color names to RGB color code. We have no way to do palette colors - t.cursorRGB = strings.Replace(t.cursorRGB, "%p1%s", "#%p1%02x%p2%02x%p3%02x", 1) -} - -func (t *tScreen) prepareKey(key Key, val string) { - t.prepareKeyMod(key, ModNone, val) -} - -func (t *tScreen) prepareKeys() { - ti := t.ti - if strings.HasPrefix(ti.Name, "xterm") { - // assume its some form of XTerm clone - t.ti.XTermLike = true - ti.XTermLike = true - } - t.prepareKey(KeyBackspace, ti.KeyBackspace) - t.prepareKey(KeyF1, ti.KeyF1) - t.prepareKey(KeyF2, ti.KeyF2) - t.prepareKey(KeyF3, ti.KeyF3) - t.prepareKey(KeyF4, ti.KeyF4) - t.prepareKey(KeyF5, ti.KeyF5) - t.prepareKey(KeyF6, ti.KeyF6) - t.prepareKey(KeyF7, ti.KeyF7) - t.prepareKey(KeyF8, ti.KeyF8) - t.prepareKey(KeyF9, ti.KeyF9) - t.prepareKey(KeyF10, ti.KeyF10) - t.prepareKey(KeyF11, ti.KeyF11) - t.prepareKey(KeyF12, ti.KeyF12) - t.prepareKey(KeyF13, ti.KeyF13) - t.prepareKey(KeyF14, ti.KeyF14) - t.prepareKey(KeyF15, ti.KeyF15) - t.prepareKey(KeyF16, ti.KeyF16) - t.prepareKey(KeyF17, ti.KeyF17) - t.prepareKey(KeyF18, ti.KeyF18) - t.prepareKey(KeyF19, ti.KeyF19) - t.prepareKey(KeyF20, ti.KeyF20) - t.prepareKey(KeyF21, ti.KeyF21) - t.prepareKey(KeyF22, ti.KeyF22) - t.prepareKey(KeyF23, ti.KeyF23) - t.prepareKey(KeyF24, ti.KeyF24) - t.prepareKey(KeyF25, ti.KeyF25) - t.prepareKey(KeyF26, ti.KeyF26) - t.prepareKey(KeyF27, ti.KeyF27) - t.prepareKey(KeyF28, ti.KeyF28) - t.prepareKey(KeyF29, ti.KeyF29) - t.prepareKey(KeyF30, ti.KeyF30) - t.prepareKey(KeyF31, ti.KeyF31) - t.prepareKey(KeyF32, ti.KeyF32) - t.prepareKey(KeyF33, ti.KeyF33) - t.prepareKey(KeyF34, ti.KeyF34) - t.prepareKey(KeyF35, ti.KeyF35) - t.prepareKey(KeyF36, ti.KeyF36) - t.prepareKey(KeyF37, ti.KeyF37) - t.prepareKey(KeyF38, ti.KeyF38) - t.prepareKey(KeyF39, ti.KeyF39) - t.prepareKey(KeyF40, ti.KeyF40) - t.prepareKey(KeyF41, ti.KeyF41) - t.prepareKey(KeyF42, ti.KeyF42) - t.prepareKey(KeyF43, ti.KeyF43) - t.prepareKey(KeyF44, ti.KeyF44) - t.prepareKey(KeyF45, ti.KeyF45) - t.prepareKey(KeyF46, ti.KeyF46) - t.prepareKey(KeyF47, ti.KeyF47) - t.prepareKey(KeyF48, ti.KeyF48) - t.prepareKey(KeyF49, ti.KeyF49) - t.prepareKey(KeyF50, ti.KeyF50) - t.prepareKey(KeyF51, ti.KeyF51) - t.prepareKey(KeyF52, ti.KeyF52) - t.prepareKey(KeyF53, ti.KeyF53) - t.prepareKey(KeyF54, ti.KeyF54) - t.prepareKey(KeyF55, ti.KeyF55) - t.prepareKey(KeyF56, ti.KeyF56) - t.prepareKey(KeyF57, ti.KeyF57) - t.prepareKey(KeyF58, ti.KeyF58) - t.prepareKey(KeyF59, ti.KeyF59) - t.prepareKey(KeyF60, ti.KeyF60) - t.prepareKey(KeyF61, ti.KeyF61) - t.prepareKey(KeyF62, ti.KeyF62) - t.prepareKey(KeyF63, ti.KeyF63) - t.prepareKey(KeyF64, ti.KeyF64) - t.prepareKey(KeyInsert, ti.KeyInsert) - t.prepareKey(KeyDelete, ti.KeyDelete) - t.prepareKey(KeyHome, ti.KeyHome) - t.prepareKey(KeyEnd, ti.KeyEnd) - t.prepareKey(KeyUp, ti.KeyUp) - t.prepareKey(KeyDown, ti.KeyDown) - t.prepareKey(KeyLeft, ti.KeyLeft) - t.prepareKey(KeyRight, ti.KeyRight) - t.prepareKey(KeyPgUp, ti.KeyPgUp) - t.prepareKey(KeyPgDn, ti.KeyPgDn) - t.prepareKey(KeyHelp, ti.KeyHelp) - t.prepareKey(KeyPrint, ti.KeyPrint) - t.prepareKey(KeyCancel, ti.KeyCancel) - t.prepareKey(KeyExit, ti.KeyExit) - t.prepareKey(KeyBacktab, ti.KeyBacktab) - - t.prepareKeyMod(KeyRight, ModShift, ti.KeyShfRight) - t.prepareKeyMod(KeyLeft, ModShift, ti.KeyShfLeft) - t.prepareKeyMod(KeyUp, ModShift, ti.KeyShfUp) - t.prepareKeyMod(KeyDown, ModShift, ti.KeyShfDown) - t.prepareKeyMod(KeyHome, ModShift, ti.KeyShfHome) - t.prepareKeyMod(KeyEnd, ModShift, ti.KeyShfEnd) - t.prepareKeyMod(KeyPgUp, ModShift, ti.KeyShfPgUp) - t.prepareKeyMod(KeyPgDn, ModShift, ti.KeyShfPgDn) - - t.prepareKeyMod(KeyRight, ModCtrl, ti.KeyCtrlRight) - t.prepareKeyMod(KeyLeft, ModCtrl, ti.KeyCtrlLeft) - t.prepareKeyMod(KeyUp, ModCtrl, ti.KeyCtrlUp) - t.prepareKeyMod(KeyDown, ModCtrl, ti.KeyCtrlDown) - t.prepareKeyMod(KeyHome, ModCtrl, ti.KeyCtrlHome) - t.prepareKeyMod(KeyEnd, ModCtrl, ti.KeyCtrlEnd) - - // Sadly, xterm handling of keycodes is somewhat erratic. In - // particular, different codes are sent depending on application - // mode is in use or not, and the entries for many of these are - // simply absent from terminfo on many systems. So we insert - // a number of escape sequences if they are not already used, in - // order to have the widest correct usage. Note that prepareKey - // will not inject codes if the escape sequence is already known. - // We also only do this for terminals that have the application - // mode present. - - // Cursor mode - if ti.EnterKeypad != "" { - t.prepareKey(KeyUp, "\x1b[A") - t.prepareKey(KeyDown, "\x1b[B") - t.prepareKey(KeyRight, "\x1b[C") - t.prepareKey(KeyLeft, "\x1b[D") - t.prepareKey(KeyEnd, "\x1b[F") - t.prepareKey(KeyHome, "\x1b[H") - t.prepareKey(KeyDelete, "\x1b[3~") - t.prepareKey(KeyHome, "\x1b[1~") - t.prepareKey(KeyEnd, "\x1b[4~") - t.prepareKey(KeyPgUp, "\x1b[5~") - t.prepareKey(KeyPgDn, "\x1b[6~") - - // Application mode - t.prepareKey(KeyUp, "\x1bOA") - t.prepareKey(KeyDown, "\x1bOB") - t.prepareKey(KeyRight, "\x1bOC") - t.prepareKey(KeyLeft, "\x1bOD") - t.prepareKey(KeyHome, "\x1bOH") - } - - t.prepareKey(keyPasteStart, ti.PasteStart) - t.prepareKey(keyPasteEnd, ti.PasteEnd) - t.prepareXtermModifiers() - t.prepareBracketedPaste() - t.prepareCursorStyles() - t.prepareUnderlines() - t.prepareExtendedOSC() - -outer: - // Add key mappings for control keys. - for i := 0; i < ' '; i++ { - // Do not insert direct key codes for ambiguous keys. - // For example, ESC is used for lots of other keys, so - // when parsing this we don't want to fast path handling - // of it, but instead wait a bit before parsing it as in - // isolation. - for esc := range t.keycodes { - if []byte(esc)[0] == byte(i) { - continue outer - } - } - - t.keyexist[Key(i)] = true - - mod := ModCtrl - switch Key(i) { - case KeyBS, KeyTAB, KeyESC, KeyCR: - // directly type-able- no control sequence - mod = ModNone - } - t.keycodes[string(rune(i))] = &tKeyCode{key: Key(i), mod: mod} - } -} - -func (t *tScreen) Fini() { - t.finiOnce.Do(t.finish) -} - -func (t *tScreen) finish() { - close(t.quit) - t.finalize() -} - -func (t *tScreen) SetStyle(style Style) { - t.Lock() - if !t.fini { - t.style = style - } - t.Unlock() -} - -func (t *tScreen) encodeRune(r rune, buf []byte) []byte { - - nb := make([]byte, 6) - ob := make([]byte, 6) - num := utf8.EncodeRune(ob, r) - ob = ob[:num] - dst := 0 - var err error - if enc := t.encoder; enc != nil { - enc.Reset() - dst, _, err = enc.Transform(nb, ob, true) - } - if err != nil || dst == 0 || nb[0] == '\x1a' { - // Combining characters are elided - if len(buf) == 0 { - if acs, ok := t.acs[r]; ok { - buf = append(buf, []byte(acs)...) - } else if fb, ok := t.fallback[r]; ok { - buf = append(buf, []byte(fb)...) - } else { - buf = append(buf, '?') - } - } - } else { - buf = append(buf, nb[:dst]...) - } - - return buf -} - -func (t *tScreen) sendFgBg(fg Color, bg Color, attr AttrMask) AttrMask { - ti := t.ti - if ti.Colors == 0 { - // foreground vs background, we calculate luminance - // and possibly do a reverse video - if !fg.Valid() { - return attr - } - v, ok := t.colors[fg] - if !ok { - v = FindColor(fg, []Color{ColorBlack, ColorWhite}) - t.colors[fg] = v - } - switch v { - case ColorWhite: - return attr - case ColorBlack: - return attr ^ AttrReverse - } - } - - if fg == ColorReset || bg == ColorReset { - t.TPuts(ti.ResetFgBg) - } - if t.truecolor { - if ti.SetFgBgRGB != "" && fg.IsRGB() && bg.IsRGB() { - r1, g1, b1 := fg.RGB() - r2, g2, b2 := bg.RGB() - t.TPuts(ti.TParm(ti.SetFgBgRGB, - int(r1), int(g1), int(b1), - int(r2), int(g2), int(b2))) - return attr - } - - if fg.IsRGB() && ti.SetFgRGB != "" { - r, g, b := fg.RGB() - t.TPuts(ti.TParm(ti.SetFgRGB, int(r), int(g), int(b))) - fg = ColorDefault - } - - if bg.IsRGB() && ti.SetBgRGB != "" { - r, g, b := bg.RGB() - t.TPuts(ti.TParm(ti.SetBgRGB, - int(r), int(g), int(b))) - bg = ColorDefault - } - } - - if fg.Valid() { - if v, ok := t.colors[fg]; ok { - fg = v - } else { - v = FindColor(fg, t.palette) - t.colors[fg] = v - fg = v - } - } - - if bg.Valid() { - if v, ok := t.colors[bg]; ok { - bg = v - } else { - v = FindColor(bg, t.palette) - t.colors[bg] = v - bg = v - } - } - - if fg.Valid() && bg.Valid() && ti.SetFgBg != "" { - t.TPuts(ti.TParm(ti.SetFgBg, int(fg&0xff), int(bg&0xff))) - } else { - if fg.Valid() && ti.SetFg != "" { - t.TPuts(ti.TParm(ti.SetFg, int(fg&0xff))) - } - if bg.Valid() && ti.SetBg != "" { - t.TPuts(ti.TParm(ti.SetBg, int(bg&0xff))) - } - } - return attr -} - -func (t *tScreen) drawCell(x, y int) int { - - ti := t.ti - - mainc, combc, style, width := t.cells.GetContent(x, y) - if !t.cells.Dirty(x, y) { - return width - } - - if y == t.h-1 && x == t.w-1 && t.ti.AutoMargin && ti.DisableAutoMargin == "" && ti.InsertChar != "" { - // our solution is somewhat goofy. - // we write to the second to the last cell what we want in the last cell, then we - // insert a character at that 2nd to last position to shift the last column into - // place, then we rewrite that 2nd to last cell. Old terminals suck. - t.TPuts(ti.TGoto(x-1, y)) - defer func() { - t.TPuts(ti.TGoto(x-1, y)) - t.TPuts(ti.InsertChar) - t.cy = y - t.cx = x - 1 - t.cells.SetDirty(x-1, y, true) - _ = t.drawCell(x-1, y) - t.TPuts(t.ti.TGoto(0, 0)) - t.cy = 0 - t.cx = 0 - }() - } else if t.cy != y || t.cx != x { - t.TPuts(ti.TGoto(x, y)) - t.cx = x - t.cy = y - } - - if style == StyleDefault { - style = t.style - } - if style != t.curstyle { - fg, bg, attrs := style.fg, style.bg, style.attrs - - t.TPuts(ti.AttrOff) - - attrs = t.sendFgBg(fg, bg, attrs) - if attrs&AttrBold != 0 { - t.TPuts(ti.Bold) - } - if us, uc := style.ulStyle, style.ulColor; us != UnderlineStyleNone { - if t.underColor != "" || t.underRGB != "" { - if uc == ColorReset { - t.TPuts(t.underFg) - } else if uc.IsRGB() { - if t.underRGB != "" { - r, g, b := uc.RGB() - t.TPuts(ti.TParm(t.underRGB, int(r), int(g), int(b))) - } else { - if v, ok := t.colors[uc]; ok { - uc = v - } else { - v = FindColor(uc, t.palette) - t.colors[uc] = v - uc = v - } - t.TPuts(ti.TParm(t.underColor, int(uc&0xff))) - } - } else if uc.Valid() { - t.TPuts(ti.TParm(t.underColor, int(uc&0xff))) - } - } - t.TPuts(ti.Underline) // to ensure everyone gets at least a basic underline - switch us { - case UnderlineStyleDouble: - t.TPuts(t.doubleUnder) - case UnderlineStyleCurly: - t.TPuts(t.curlyUnder) - case UnderlineStyleDotted: - t.TPuts(t.dottedUnder) - case UnderlineStyleDashed: - t.TPuts(t.dashedUnder) - } - } - if attrs&AttrReverse != 0 { - t.TPuts(ti.Reverse) - } - if attrs&AttrBlink != 0 { - t.TPuts(ti.Blink) - } - if attrs&AttrDim != 0 { - t.TPuts(ti.Dim) - } - if attrs&AttrItalic != 0 { - t.TPuts(ti.Italic) - } - if attrs&AttrStrikeThrough != 0 { - t.TPuts(ti.StrikeThrough) - } - - // URL string can be long, so don't send it unless we really need to - if t.enterUrl != "" && t.curstyle.url != style.url { - if style.url != "" { - t.TPuts(ti.TParm(t.enterUrl, style.url, style.urlId)) - } else { - t.TPuts(t.exitUrl) - } - } - - t.curstyle = style - } - - // now emit runes - taking care to not overrun width with a - // wide character, and to ensure that we emit exactly one regular - // character followed up by any residual combing characters - - if width < 1 { - width = 1 - } - - var str string - - buf := make([]byte, 0, 6) - - buf = t.encodeRune(mainc, buf) - for _, r := range combc { - buf = t.encodeRune(r, buf) - } - - str = string(buf) - if width > 1 && str == "?" { - // No FullWidth character support - str = "? " - t.cx = -1 - } - - if x > t.w-width { - // too wide to fit; emit a single space instead - width = 1 - str = " " - } - t.writeString(str) - t.cx += width - t.cells.SetDirty(x, y, false) - if width > 1 { - t.cx = -1 - } - - return width -} - -func (t *tScreen) ShowCursor(x, y int) { - t.Lock() - t.cursorx = x - t.cursory = y - t.Unlock() -} - -func (t *tScreen) SetCursor(cs CursorStyle, cc Color) { - t.Lock() - t.cursorStyle = cs - t.cursorColor = cc - t.Unlock() -} - -func (t *tScreen) HideCursor() { - t.ShowCursor(-1, -1) -} - -func (t *tScreen) showCursor() { - - x, y := t.cursorx, t.cursory - w, h := t.cells.Size() - if x < 0 || y < 0 || x >= w || y >= h { - t.hideCursor() - return - } - t.TPuts(t.ti.TGoto(x, y)) - t.TPuts(t.ti.ShowCursor) - if t.cursorStyles != nil { - if esc, ok := t.cursorStyles[t.cursorStyle]; ok { - t.TPuts(esc) - } - } - if t.cursorRGB != "" { - if t.cursorColor == ColorReset { - t.TPuts(t.cursorFg) - } else if t.cursorColor.Valid() { - r, g, b := t.cursorColor.RGB() - t.TPuts(t.ti.TParm(t.cursorRGB, int(r), int(g), int(b))) - } - } - t.cx = x - t.cy = y -} - -// writeString sends a string to the terminal. The string is sent as-is and -// this function does not expand inline padding indications (of the form -// $<[delay]> where [delay] is msec). In order to have these expanded, use -// TPuts. If the screen is "buffering", the string is collected in a buffer, -// with the intention that the entire buffer be sent to the terminal in one -// write operation at some point later. -func (t *tScreen) writeString(s string) { - if t.buffering { - _, _ = io.WriteString(&t.buf, s) - } else { - _, _ = io.WriteString(t.tty, s) - } -} - -func (t *tScreen) TPuts(s string) { - if t.buffering { - t.ti.TPuts(&t.buf, s) - } else { - t.ti.TPuts(t.tty, s) - } -} - -func (t *tScreen) Show() { - t.Lock() - if !t.fini { - t.resize() - t.draw() - } - t.Unlock() -} - -func (t *tScreen) clearScreen() { - t.TPuts(t.ti.AttrOff) - t.TPuts(t.exitUrl) - _ = t.sendFgBg(t.style.fg, t.style.bg, AttrNone) - t.TPuts(t.ti.Clear) - t.clear = false -} - -func (t *tScreen) hideCursor() { - // does not update cursor position - if t.ti.HideCursor != "" { - t.TPuts(t.ti.HideCursor) - } else { - // No way to hide cursor, stick it - // at bottom right of screen - t.cx, t.cy = t.cells.Size() - t.TPuts(t.ti.TGoto(t.cx, t.cy)) - } -} - -func (t *tScreen) draw() { - // clobber cursor position, because we're going to change it all - t.cx = -1 - t.cy = -1 - // make no style assumptions - t.curstyle = styleInvalid - - t.buf.Reset() - t.buffering = true - defer func() { - t.buffering = false - }() - - // hide the cursor while we move stuff around - t.hideCursor() - - if t.clear { - t.clearScreen() - } - - for y := 0; y < t.h; y++ { - for x := 0; x < t.w; x++ { - width := t.drawCell(x, y) - if width > 1 { - if x+1 < t.w { - // this is necessary so that if we ever - // go back to drawing that cell, we - // actually will *draw* it. - t.cells.SetDirty(x+1, y, true) - } - } - x += width - 1 - } - } - - // restore the cursor - t.showCursor() - - _, _ = t.buf.WriteTo(t.tty) -} - -func (t *tScreen) EnableMouse(flags ...MouseFlags) { - var f MouseFlags - flagsPresent := false - for _, flag := range flags { - f |= flag - flagsPresent = true - } - if !flagsPresent { - f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents - } - - t.Lock() - t.mouseFlags = f - t.enableMouse(f) - t.Unlock() -} - -func (t *tScreen) enableMouse(f MouseFlags) { - // Rather than using terminfo to find mouse escape sequences, we rely on the fact that - // pretty much *every* terminal that supports mouse tracking follows the - // XTerm standards (the modern ones). - if len(t.mouse) != 0 { - // start by disabling all tracking. - t.TPuts("\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l") - if f&MouseButtonEvents != 0 { - t.TPuts("\x1b[?1000h") - } - if f&MouseDragEvents != 0 { - t.TPuts("\x1b[?1002h") - } - if f&MouseMotionEvents != 0 { - t.TPuts("\x1b[?1003h") - } - if f&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) != 0 { - t.TPuts("\x1b[?1006h") - } - } - -} - -func (t *tScreen) DisableMouse() { - t.Lock() - t.mouseFlags = 0 - t.enableMouse(0) - t.Unlock() -} - -func (t *tScreen) EnablePaste() { - t.Lock() - t.pasteEnabled = true - t.enablePasting(true) - t.Unlock() -} - -func (t *tScreen) DisablePaste() { - t.Lock() - t.pasteEnabled = false - t.enablePasting(false) - t.Unlock() -} - -func (t *tScreen) enablePasting(on bool) { - var s string - if on { - s = t.enablePaste - } else { - s = t.disablePaste - } - if s != "" { - t.TPuts(s) - } -} - -func (t *tScreen) EnableFocus() { - t.Lock() - t.focusEnabled = true - t.enableFocusReporting() - t.Unlock() -} - -func (t *tScreen) DisableFocus() { - t.Lock() - t.focusEnabled = false - t.disableFocusReporting() - t.Unlock() -} - -func (t *tScreen) enableFocusReporting() { - if t.enableFocus != "" { - t.TPuts(t.enableFocus) - } -} - -func (t *tScreen) disableFocusReporting() { - if t.disableFocus != "" { - t.TPuts(t.disableFocus) - } -} - -func (t *tScreen) Size() (int, int) { - t.Lock() - w, h := t.w, t.h - t.Unlock() - return w, h -} - -func (t *tScreen) resize() { - ws, err := t.tty.WindowSize() - if err != nil { - return - } - if ws.Width == t.w && ws.Height == t.h { - return - } - t.cx = -1 - t.cy = -1 - - t.cells.Resize(ws.Width, ws.Height) - t.cells.Invalidate() - t.h = ws.Height - t.w = ws.Width - ev := &EventResize{t: time.Now(), ws: ws} - select { - case t.eventQ <- ev: - default: - } -} - -func (t *tScreen) Colors() int { - // this doesn't change, no need for lock - if t.truecolor { - return 1 << 24 - } - return t.ti.Colors -} - -// nColors returns the size of the built-in palette. -// This is distinct from Colors(), as it will generally -// always be a small number. (<= 256) -func (t *tScreen) nColors() int { - return t.ti.Colors -} - -// vtACSNames is a map of bytes defined by terminfo that are used in -// the terminals Alternate Character Set to represent other glyphs. -// For example, the upper left corner of the box drawing set can be -// displayed by printing "l" while in the alternate character set. -// It's not quite that simple, since the "l" is the terminfo name, -// and it may be necessary to use a different character based on -// the terminal implementation (or the terminal may lack support for -// this altogether). See buildAcsMap below for detail. -var vtACSNames = map[byte]rune{ - '+': RuneRArrow, - ',': RuneLArrow, - '-': RuneUArrow, - '.': RuneDArrow, - '0': RuneBlock, - '`': RuneDiamond, - 'a': RuneCkBoard, - 'b': '␉', // VT100, Not defined by terminfo - 'c': '␌', // VT100, Not defined by terminfo - 'd': '␋', // VT100, Not defined by terminfo - 'e': '␊', // VT100, Not defined by terminfo - 'f': RuneDegree, - 'g': RunePlMinus, - 'h': RuneBoard, - 'i': RuneLantern, - 'j': RuneLRCorner, - 'k': RuneURCorner, - 'l': RuneULCorner, - 'm': RuneLLCorner, - 'n': RunePlus, - 'o': RuneS1, - 'p': RuneS3, - 'q': RuneHLine, - 'r': RuneS7, - 's': RuneS9, - 't': RuneLTee, - 'u': RuneRTee, - 'v': RuneBTee, - 'w': RuneTTee, - 'x': RuneVLine, - 'y': RuneLEqual, - 'z': RuneGEqual, - '{': RunePi, - '|': RuneNEqual, - '}': RuneSterling, - '~': RuneBullet, -} - -// buildAcsMap builds a map of characters that we translate from Unicode to -// alternate character encodings. To do this, we use the standard VT100 ACS -// maps. This is only done if the terminal lacks support for Unicode; we -// always prefer to emit Unicode glyphs when we are able. -func (t *tScreen) buildAcsMap() { - acsstr := t.ti.AltChars - t.acs = make(map[rune]string) - for len(acsstr) > 2 { - srcv := acsstr[0] - dstv := string(acsstr[1]) - if r, ok := vtACSNames[srcv]; ok { - t.acs[r] = t.ti.EnterAcs + dstv + t.ti.ExitAcs - } - acsstr = acsstr[2:] - } -} - -func (t *tScreen) clip(x, y int) (int, int) { - w, h := t.cells.Size() - if x < 0 { - x = 0 - } - if y < 0 { - y = 0 - } - if x > w-1 { - x = w - 1 - } - if y > h-1 { - y = h - 1 - } - return x, y -} - -// buildMouseEvent returns an event based on the supplied coordinates and button -// state. Note that the screen's mouse button state is updated based on the -// input to this function (i.e. it mutates the receiver). -func (t *tScreen) buildMouseEvent(x, y, btn int) *EventMouse { - - // XTerm mouse events only report at most one button at a time, - // which may include a wheel button. Wheel motion events are - // reported as single impulses, while other button events are reported - // as separate press & release events. - - button := ButtonNone - mod := ModNone - - // Mouse wheel has bit 6 set, no release events. It should be noted - // that wheel events are sometimes misdelivered as mouse button events - // during a click-drag, so we debounce these, considering them to be - // button press events unless we see an intervening release event. - switch btn & 0x43 { - case 0: - button = Button1 - case 1: - button = Button3 // Note we prefer to treat right as button 2 - case 2: - button = Button2 // And the middle button as button 3 - case 3: - button = ButtonNone - case 0x40: - button = WheelUp - case 0x41: - button = WheelDown - case 0x42: - button = WheelLeft - case 0x43: - button = WheelRight - } - - if btn&0x4 != 0 { - mod |= ModShift - } - if btn&0x8 != 0 { - mod |= ModAlt - } - if btn&0x10 != 0 { - mod |= ModCtrl - } - - // Some terminals will report mouse coordinates outside the - // screen, especially with click-drag events. Clip the coordinates - // to the screen in that case. - x, y = t.clip(x, y) - - return NewEventMouse(x, y, button, mod) -} - -// parseSgrMouse attempts to locate an SGR mouse record at the start of the -// buffer. It returns true, true if it found one, and the associated bytes -// be removed from the buffer. It returns true, false if the buffer might -// contain such an event, but more bytes are necessary (partial match), and -// false, false if the content is definitely *not* an SGR mouse record. -func (t *tScreen) parseSgrMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) { - - b := buf.Bytes() - - var x, y, btn, state int - dig := false - neg := false - motion := false - scroll := false - i := 0 - val := 0 - - for i = range b { - switch b[i] { - case '\x1b': - if state != 0 { - return false, false - } - state = 1 - - case '\x9b': - if state != 0 { - return false, false - } - state = 2 - - case '[': - if state != 1 { - return false, false - } - state = 2 - - case '<': - if state != 2 { - return false, false - } - val = 0 - dig = false - neg = false - state = 3 - - case '-': - if state != 3 && state != 4 && state != 5 { - return false, false - } - if dig || neg { - return false, false - } - neg = true // stay in state - - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - if state != 3 && state != 4 && state != 5 { - return false, false - } - val *= 10 - val += int(b[i] - '0') - dig = true // stay in state - - case ';': - if neg { - val = -val - } - switch state { - case 3: - btn, val = val, 0 - neg, dig, state = false, false, 4 - case 4: - x, val = val-1, 0 - neg, dig, state = false, false, 5 - default: - return false, false - } - - case 'm', 'M': - if state != 5 { - return false, false - } - if neg { - val = -val - } - y = val - 1 - - motion = (btn & 32) != 0 - scroll = (btn & 0x42) == 0x40 - btn &^= 32 - if b[i] == 'm' { - // mouse release, clear all buttons - btn |= 3 - btn &^= 0x40 - t.buttondn = false - } else if motion { - /* - * Some broken terminals appear to send - * mouse button one motion events, instead of - * encoding 35 (no buttons) into these events. - * We resolve these by looking for a non-motion - * event first. - */ - if !t.buttondn { - btn |= 3 - btn &^= 0x40 - } - } else if !scroll { - t.buttondn = true - } - // consume the event bytes - for i >= 0 { - _, _ = buf.ReadByte() - i-- - } - *evs = append(*evs, t.buildMouseEvent(x, y, btn)) - return true, true - } - } - - // incomplete & inconclusive at this point - return true, false -} - -func (t *tScreen) parseFocus(buf *bytes.Buffer, evs *[]Event) (bool, bool) { - state := 0 - b := buf.Bytes() - for i := range b { - switch state { - case 0: - if b[i] != '\x1b' { - return false, false - } - state = 1 - case 1: - if b[i] != '[' { - return false, false - } - state = 2 - case 2: - if b[i] != 'I' && b[i] != 'O' { - return false, false - } - *evs = append(*evs, NewEventFocus(b[i] == 'I')) - _, _ = buf.ReadByte() - _, _ = buf.ReadByte() - _, _ = buf.ReadByte() - return true, true - } - } - return true, false -} - -func (t *tScreen) parseClipboard(buf *bytes.Buffer, evs *[]Event) (bool, bool) { - b := buf.Bytes() - state := 0 - prefix := []byte("\x1b]52;c;") - - if len(prefix) >= len(b) { - if bytes.HasPrefix(prefix, b) { - // inconclusive so far - return true, false - } - // definitely not a match - return false, false - } - b = b[len(prefix):] - - for _, c := range b { - // valid base64 digits - if state == 0 { - if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '+') || (c == '/') || (c == '=') { - continue - } - if c == '\x1b' { - state = 1 - continue - } - if c == '\a' { - // matched with BEL instead of ST - b = b[:len(b)-1] // drop the trailing BEL - decoded := make([]byte, base64.StdEncoding.DecodedLen(len(b))) - if num, err := base64.StdEncoding.Decode(decoded, b); err == nil { - *evs = append(*evs, NewEventClipboard(decoded[:num])) - } - _, _ = buf.ReadBytes('\a') - return true, true - } - return false, false - } - if state == 1 { - if c == '\\' { - b = b[:len(b)-2] // drop the trailing ST (\x1b\\) - // now decode the data - decoded := make([]byte, base64.StdEncoding.DecodedLen(len(b))) - if num, err := base64.StdEncoding.Decode(decoded, b); err == nil { - *evs = append(*evs, NewEventClipboard(decoded[:num])) - } - _, _ = buf.ReadBytes('\\') - return true, true - } - return false, false - } - } - // not enough data yet (not terminated) - return true, false -} - -// parseXtermMouse is like parseSgrMouse, but it parses a legacy -// X11 mouse record. -func (t *tScreen) parseXtermMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) { - - b := buf.Bytes() - - state := 0 - btn := 0 - x := 0 - y := 0 - - for i := range b { - switch state { - case 0: - switch b[i] { - case '\x1b': - state = 1 - case '\x9b': - state = 2 - default: - return false, false - } - case 1: - if b[i] != '[' { - return false, false - } - state = 2 - case 2: - if b[i] != 'M' { - return false, false - } - state++ - case 3: - btn = int(b[i]) - state++ - case 4: - x = int(b[i]) - 32 - 1 - state++ - case 5: - y = int(b[i]) - 32 - 1 - for i >= 0 { - _, _ = buf.ReadByte() - i-- - } - *evs = append(*evs, t.buildMouseEvent(x, y, btn)) - return true, true - } - } - return true, false -} - -func (t *tScreen) parseFunctionKey(buf *bytes.Buffer, evs *[]Event) (bool, bool) { - b := buf.Bytes() - partial := false - for e, k := range t.keycodes { - esc := []byte(e) - if (len(esc) == 1) && (esc[0] == '\x1b') { - continue - } - if bytes.HasPrefix(b, esc) { - // matched - var r rune - if len(esc) == 1 { - r = rune(b[0]) - } - mod := k.mod - if t.escaped { - mod |= ModAlt - t.escaped = false - } - switch k.key { - case keyPasteStart: - *evs = append(*evs, NewEventPaste(true)) - case keyPasteEnd: - *evs = append(*evs, NewEventPaste(false)) - default: - *evs = append(*evs, NewEventKey(k.key, r, mod)) - } - for i := 0; i < len(esc); i++ { - _, _ = buf.ReadByte() - } - return true, true - } - if bytes.HasPrefix(esc, b) { - partial = true - } - } - return partial, false -} - -func (t *tScreen) parseRune(buf *bytes.Buffer, evs *[]Event) (bool, bool) { - b := buf.Bytes() - if b[0] >= ' ' && b[0] <= 0x7F { - // printable ASCII easy to deal with -- no encodings - mod := ModNone - if t.escaped { - mod = ModAlt - t.escaped = false - } - *evs = append(*evs, NewEventKey(KeyRune, rune(b[0]), mod)) - _, _ = buf.ReadByte() - return true, true - } - - if b[0] < 0x80 { - // Low numbered values are control keys, not runes. - return false, false - } - - utf := make([]byte, 12) - for l := 1; l <= len(b); l++ { - t.decoder.Reset() - nOut, nIn, e := t.decoder.Transform(utf, b[:l], true) - if e == transform.ErrShortSrc { - continue - } - if nOut != 0 { - r, _ := utf8.DecodeRune(utf[:nOut]) - if r != utf8.RuneError { - mod := ModNone - if t.escaped { - mod = ModAlt - t.escaped = false - } - *evs = append(*evs, NewEventKey(KeyRune, r, mod)) - } - for nIn > 0 { - _, _ = buf.ReadByte() - nIn-- - } - return true, true - } - } - // Looks like potential escape - return true, false -} - -func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) { - evs := t.collectEventsFromInput(buf, expire) - - for _, ev := range evs { - select { - case t.eventQ <- ev: - case <-t.quit: - return - } - } -} - -// Return an array of Events extracted from the supplied buffer. This is done -// while holding the screen's lock - the events can then be queued for -// application processing with the lock released. -func (t *tScreen) collectEventsFromInput(buf *bytes.Buffer, expire bool) []Event { - - res := make([]Event, 0, 20) - - t.Lock() - defer t.Unlock() - - for { - b := buf.Bytes() - if len(b) == 0 { - buf.Reset() - return res - } - - partials := 0 - - if part, comp := t.parseRune(buf, &res); comp { - continue - } else if part { - partials++ - } - - if part, comp := t.parseFunctionKey(buf, &res); comp { - continue - } else if part { - partials++ - } - - if part, comp := t.parseFocus(buf, &res); comp { - continue - } else if part { - partials++ - } - - // Only parse mouse records if this term claims to have - // mouse support - - if t.ti.Mouse != "" { - if part, comp := t.parseXtermMouse(buf, &res); comp { - continue - } else if part { - partials++ - } - - if part, comp := t.parseSgrMouse(buf, &res); comp { - continue - } else if part { - partials++ - } - } - - if t.setClipboard != "" { - if part, comp := t.parseClipboard(buf, &res); comp { - continue - } else if part { - partials++ - } - } - - if partials == 0 || expire { - if b[0] == '\x1b' { - if len(b) == 1 { - res = append(res, NewEventKey(KeyEsc, 0, ModNone)) - t.escaped = false - } else { - t.escaped = true - } - _, _ = buf.ReadByte() - continue - } - // Nothing was going to match, or we timed-out - // waiting for more data -- just deliver the characters - // to the app & let them sort it out. Possibly we - // should only do this for control characters like ESC. - by, _ := buf.ReadByte() - mod := ModNone - if t.escaped { - t.escaped = false - mod = ModAlt - } - res = append(res, NewEventKey(KeyRune, rune(by), mod)) - continue - } - - // well we have some partial data, wait until we get - // some more - break - } - - return res -} - -func (t *tScreen) mainLoop(stopQ chan struct{}) { - defer t.wg.Done() - buf := &bytes.Buffer{} - for { - select { - case <-stopQ: - return - case <-t.quit: - return - case <-t.resizeQ: - t.Lock() - t.cx = -1 - t.cy = -1 - t.resize() - t.cells.Invalidate() - t.draw() - t.Unlock() - continue - case <-t.keytimer.C: - // If the timer fired, and the current time - // is after the expiration of the escape sequence, - // then we assume the escape sequence reached its - // conclusion, and process the chunk independently. - // This lets us detect conflicts such as a lone ESC. - if buf.Len() > 0 { - if time.Now().After(t.keyexpire) { - t.scanInput(buf, true) - } - } - if buf.Len() > 0 { - if !t.keytimer.Stop() { - select { - case <-t.keytimer.C: - default: - } - } - t.keytimer.Reset(time.Millisecond * 50) - } - case chunk := <-t.keychan: - buf.Write(chunk) - t.keyexpire = time.Now().Add(time.Millisecond * 50) - t.scanInput(buf, false) - if !t.keytimer.Stop() { - select { - case <-t.keytimer.C: - default: - } - } - if buf.Len() > 0 { - t.keytimer.Reset(time.Millisecond * 50) - } - } - } -} - -func (t *tScreen) inputLoop(stopQ chan struct{}) { - - defer t.wg.Done() - for { - select { - case <-stopQ: - return - default: - } - chunk := make([]byte, 128) - n, e := t.tty.Read(chunk) - switch e { - case nil: - default: - t.Lock() - running := t.running - t.Unlock() - if running { - select { - case t.eventQ <- NewEventError(e): - case <-t.quit: - } - } - return - } - if n > 0 { - t.keychan <- chunk[:n] - } - } -} - -func (t *tScreen) Sync() { - t.Lock() - t.cx = -1 - t.cy = -1 - if !t.fini { - t.resize() - t.clear = true - t.cells.Invalidate() - t.draw() - } - t.Unlock() -} - -func (t *tScreen) CharacterSet() string { - return t.charset -} - -func (t *tScreen) RegisterRuneFallback(orig rune, fallback string) { - t.Lock() - t.fallback[orig] = fallback - t.Unlock() -} - -func (t *tScreen) UnregisterRuneFallback(orig rune) { - t.Lock() - delete(t.fallback, orig) - t.Unlock() -} - -func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool { - - if enc := t.encoder; enc != nil { - nb := make([]byte, 6) - ob := make([]byte, 6) - num := utf8.EncodeRune(ob, r) - - enc.Reset() - dst, _, err := enc.Transform(nb, ob[:num], true) - if dst != 0 && err == nil && nb[0] != '\x1A' { - return true - } - } - // Terminal fallbacks always permitted, since we assume they are - // basically nearly perfect renditions. - if _, ok := t.acs[r]; ok { - return true - } - if !checkFallbacks { - return false - } - if _, ok := t.fallback[r]; ok { - return true - } - return false -} - -func (t *tScreen) HasMouse() bool { - return len(t.mouse) != 0 -} - -func (t *tScreen) HasKey(k Key) bool { - if k == KeyRune { - return true - } - return t.keyexist[k] -} - -func (t *tScreen) SetSize(w, h int) { - if t.setWinSize != "" { - t.TPuts(t.ti.TParm(t.setWinSize, w, h)) - } - t.cells.Invalidate() - t.resize() -} - -func (t *tScreen) Resize(int, int, int, int) {} - -func (t *tScreen) Suspend() error { - t.disengage() - return nil -} - -func (t *tScreen) Resume() error { - return t.engage() -} - -func (t *tScreen) Tty() (Tty, bool) { - return t.tty, true -} - -// engage is used to place the terminal in raw mode and establish screen size, etc. -// Think of this is as tcell "engaging" the clutch, as it's going to be driving the -// terminal interface. -func (t *tScreen) engage() error { - t.Lock() - defer t.Unlock() - if t.tty == nil { - return ErrNoScreen - } - t.tty.NotifyResize(func() { - select { - case t.resizeQ <- true: - default: - } - }) - if t.running { - return errors.New("already engaged") - } - if err := t.tty.Start(); err != nil { - return err - } - t.running = true - if ws, err := t.tty.WindowSize(); err == nil && ws.Width != 0 && ws.Height != 0 { - t.cells.Resize(ws.Width, ws.Height) - } - stopQ := make(chan struct{}) - t.stopQ = stopQ - t.enableMouse(t.mouseFlags) - t.enablePasting(t.pasteEnabled) - if t.focusEnabled { - t.enableFocusReporting() - } - - ti := t.ti - if os.Getenv("TCELL_ALTSCREEN") != "disable" { - // Technically this may not be right, but every terminal we know about - // (even Wyse 60) uses this to enter the alternate screen buffer, and - // possibly save and restore the window title and/or icon. - // (In theory there could be terminals that don't support X,Y cursor - // positions without a setup command, but we don't support them.) - t.TPuts(ti.EnterCA) - if t.saveTitle != "" { - t.TPuts(t.saveTitle) - } - } - t.TPuts(ti.EnterKeypad) - t.TPuts(ti.HideCursor) - t.TPuts(ti.EnableAcs) - t.TPuts(ti.DisableAutoMargin) - t.TPuts(ti.Clear) - if t.title != "" && t.setTitle != "" { - t.TPuts(t.ti.TParm(t.setTitle, t.title)) - } - - t.wg.Add(2) - go t.inputLoop(stopQ) - go t.mainLoop(stopQ) - return nil -} - -// disengage is used to release the terminal back to support from the caller. -// Think of this as tcell disengaging the clutch, so that another application -// can take over the terminal interface. This restores the TTY mode that was -// present when the application was first started. -func (t *tScreen) disengage() { - - t.Lock() - if !t.running { - t.Unlock() - return - } - t.running = false - stopQ := t.stopQ - close(stopQ) - _ = t.tty.Drain() - t.Unlock() - - t.tty.NotifyResize(nil) - // wait for everything to shut down - t.wg.Wait() - - // shutdown the screen and disable special modes (e.g. mouse and bracketed paste) - ti := t.ti - t.cells.Resize(0, 0) - t.TPuts(ti.ShowCursor) - if t.cursorStyles != nil && t.cursorStyle != CursorStyleDefault { - t.TPuts(t.cursorStyles[CursorStyleDefault]) - } - if t.cursorFg != "" && t.cursorColor.Valid() { - t.TPuts(t.cursorFg) - } - t.TPuts(ti.ResetFgBg) - t.TPuts(ti.AttrOff) - t.TPuts(ti.ExitKeypad) - t.TPuts(ti.EnableAutoMargin) - if os.Getenv("TCELL_ALTSCREEN") != "disable" { - if t.restoreTitle != "" { - t.TPuts(t.restoreTitle) - } - t.TPuts(ti.Clear) // only needed if ExitCA is empty - t.TPuts(ti.ExitCA) - } - t.enableMouse(0) - t.enablePasting(false) - t.disableFocusReporting() - - _ = t.tty.Stop() -} - -// Beep emits a beep to the terminal. -func (t *tScreen) Beep() error { - t.writeString(string(byte(7))) - return nil -} - -// finalize is used to at application shutdown, and restores the terminal -// to it's initial state. It should not be called more than once. -func (t *tScreen) finalize() { - t.disengage() - _ = t.tty.Close() -} - -func (t *tScreen) StopQ() <-chan struct{} { - return t.quit -} - -func (t *tScreen) EventQ() chan Event { - return t.eventQ -} - -func (t *tScreen) GetCells() *CellBuffer { - return &t.cells -} - -func (t *tScreen) SetTitle(title string) { - t.Lock() - t.title = title - if t.setTitle != "" && t.running { - t.TPuts(t.ti.TParm(t.setTitle, title)) - } - t.Unlock() -} - -func (t *tScreen) SetClipboard(data []byte) { - // Post binary data to the system clipboard. It might be UTF-8, it might not be. - t.Lock() - if t.setClipboard != "" { - encoded := base64.StdEncoding.EncodeToString(data) - t.TPuts(t.ti.TParm(t.setClipboard, encoded)) - } - t.Unlock() -} - -func (t *tScreen) GetClipboard() { - t.Lock() - if t.setClipboard != "" { - t.TPuts(t.ti.TParm(t.setClipboard, "?")) - } - t.Unlock() -} diff --git a/vendor/github.com/gdamore/tcell/v2/tscreen_plan9.go b/vendor/github.com/gdamore/tcell/v2/tscreen_plan9.go deleted file mode 100644 index fdd55b8c827..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/tscreen_plan9.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build plan9 -// +build plan9 - -// Copyright 2025 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import "os" - -// initialize on Plan 9: if no TTY was provided, use the Plan 9 TTY. -func (t *tScreen) initialize() error { - if os.Getenv("TERM") == "" { - // TERM should be "vt100" in a vt(1) window; color/mouse support will be limited. - _ = os.Setenv("TERM", "vt100") - } - if t.tty == nil { - tty, err := NewDevTty() - if err != nil { - return err - } - t.tty = tty - } - return nil -} diff --git a/vendor/github.com/gdamore/tcell/v2/tscreen_stub.go b/vendor/github.com/gdamore/tcell/v2/tscreen_stub.go deleted file mode 100644 index 5d0649dda73..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/tscreen_stub.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build windows -// +build windows - -// Copyright 2022 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -// NB: We might someday wish to move Windows to this model. However, -// that would probably mean sacrificing some of the richer key reporting -// that we can obtain with the console API present on Windows. - -func (t *tScreen) initialize() error { - if t.tty == nil { - return ErrNoScreen - } - // If a tty was supplied (custom), it should work. - // Custom screen implementations will need to provide a TTY - // implementation that we can use. - return nil -} diff --git a/vendor/github.com/gdamore/tcell/v2/tscreen_unix.go b/vendor/github.com/gdamore/tcell/v2/tscreen_unix.go deleted file mode 100644 index 27f4c813442..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/tscreen_unix.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2024 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos -// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos - -package tcell - -import ( - // import the stock terminals - _ "github.com/gdamore/tcell/v2/terminfo/base" -) - -// initialize is used at application startup, and sets up the initial values -// including file descriptors used for terminals and saving the initial state -// so that it can be restored when the application terminates. -func (t *tScreen) initialize() error { - var err error - if t.tty == nil { - t.tty, err = NewDevTty() - if err != nil { - return err - } - } - return nil -} diff --git a/vendor/github.com/gdamore/tcell/v2/tty.go b/vendor/github.com/gdamore/tcell/v2/tty.go deleted file mode 100644 index 8bb1ac5066c..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/tty.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2021 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import "io" - -// Tty is an abstraction of a tty (traditionally "teletype"). This allows applications to -// provide for alternate backends, as there are situations where the traditional /dev/tty -// does not work, or where more flexible handling is required. This interface is for use -// with the terminfo-style based API. It extends the io.ReadWriter API. It is reasonable -// that the implementation might choose to use different underlying files for the Reader -// and Writer sides of this API, as part of it's internal implementation. -type Tty interface { - // Start is used to activate the Tty for use. Upon return the terminal should be - // in raw mode, non-blocking, etc. The implementation should take care of saving - // any state that is required so that it may be restored when Stop is called. - Start() error - - // Stop is used to stop using this Tty instance. This may be a suspend, so that other - // terminal based applications can run in the foreground. Implementations should - // restore any state collected at Start(), and return to ordinary blocking mode, etc. - // Drain is called first to drain the input. Once this is called, no more Read - // or Write calls will be made until Start is called again. - Stop() error - - // Drain is called before Stop, and ensures that the reader will wake up appropriately - // if it was blocked. This workaround is required for /dev/tty on certain UNIX systems - // to ensure that Read() does not block forever. This typically arranges for the tty driver - // to send data immediately (e.g. VMIN and VTIME both set zero) and sets a deadline on input. - // Implementations may reasonably make this a no-op. There will still be control sequences - // emitted between the time this is called, and when Stop is called. - Drain() error - - // NotifyResize is used register a callback when the tty thinks the dimensions have - // changed. The standard UNIX implementation links this to a handler for SIGWINCH. - // If the supplied callback is nil, then any handler should be unregistered. - NotifyResize(cb func()) - - // WindowSize is called to determine the terminal dimensions. This might be determined - // by an ioctl or other means. - WindowSize() (WindowSize, error) - - io.ReadWriteCloser -} diff --git a/vendor/github.com/gdamore/tcell/v2/tty_plan9.go b/vendor/github.com/gdamore/tcell/v2/tty_plan9.go deleted file mode 100644 index 6c99ee6d334..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/tty_plan9.go +++ /dev/null @@ -1,270 +0,0 @@ -//go:build plan9 -// +build plan9 - -// Copyright 2025 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcell - -import ( - "bufio" - "errors" - "fmt" - "io" - "os" - "strconv" - "strings" - "sync" - "sync/atomic" -) - -// p9Tty implements tcell.Tty using Plan 9's /dev/cons and /dev/consctl. -// Raw mode is enabled by writing "rawon" to /dev/consctl while the fd stays open. -// Resize notifications are read from /dev/wctl: the first read returns geometry, -// subsequent reads block until the window changes (rio(4)). -// -// References: -// - kbdfs(8): cons/consctl rawon|rawoff semantics -// - rio(4): wctl geometry and blocking-on-change behavior -// - vt(1): VT100 emulator typically used for TUI programs on Plan 9 -// -// Limitations: -// - We assume VT100-level capabilities (often no colors, no mouse). -// - Window size is conservative: we return 80x24 unless overridden. -// Set LINES/COLUMNS (or TCELL_LINES/TCELL_COLS) to refine. -// - Mouse and bracketed paste are not wired; terminfo/xterm queries -// are not attempted because vt(1) may not support them. -type p9Tty struct { - cons *os.File // /dev/cons (read+write) - consctl *os.File // /dev/consctl (write "rawon"/"rawoff") - wctl *os.File // /dev/wctl (resize notifications) - - // protect close/stop; Read/Write are serialized by os.File - mu sync.Mutex - closed atomic.Bool - - // resize callback - onResize atomic.Value // func() - wg sync.WaitGroup - stopCh chan struct{} -} - -func NewDevTty() (Tty, error) { // tcell signature - return newPlan9TTY() -} - -func NewStdIoTty() (Tty, error) { // also required by tcell - // On Plan 9 there is no POSIX tty discipline on stdin/stdout; - // use /dev/cons explicitly for robustness. - return newPlan9TTY() -} - -func NewDevTtyFromDev(_ string) (Tty, error) { // required by tcell - // Plan 9 does not have multiple "ttys" in the POSIX sense; - // always bind to /dev/cons and /dev/consctl. - return newPlan9TTY() -} - -func newPlan9TTY() (Tty, error) { - cons, err := os.OpenFile("/dev/cons", os.O_RDWR, 0) - if err != nil { - return nil, fmt.Errorf("open /dev/cons: %w", err) - } - consctl, err := os.OpenFile("/dev/consctl", os.O_WRONLY, 0) - if err != nil { - _ = cons.Close() - return nil, fmt.Errorf("open /dev/consctl: %w", err) - } - // /dev/wctl may not exist (console without rio); best-effort. - wctl, _ := os.OpenFile("/dev/wctl", os.O_RDWR, 0) - - t := &p9Tty{ - cons: cons, - consctl: consctl, - wctl: wctl, - stopCh: make(chan struct{}), - } - return t, nil -} - -func (t *p9Tty) Start() error { - t.mu.Lock() - defer t.mu.Unlock() - - if t.closed.Load() { - return errors.New("tty closed") - } - - // Recreate stop channel if absent or closed (supports resume). - if t.stopCh == nil || isClosed(t.stopCh) { - t.stopCh = make(chan struct{}) - } - - // Put console into raw mode; remains active while consctl is open. - if _, err := t.consctl.Write([]byte("rawon")); err != nil { - return fmt.Errorf("enable raw mode: %w", err) - } - - // Reopen /dev/wctl on resume; best-effort (system console may lack it). - if t.wctl == nil { - if f, err := os.OpenFile("/dev/wctl", os.O_RDWR, 0); err == nil { - t.wctl = f - } - } - - if t.wctl != nil { - t.wg.Add(1) - go t.watchResize() - } - return nil -} - -func (t *p9Tty) Drain() error { - // Per tcell docs, this may reasonably be a no-op on non-POSIX ttys. - // Read deadlines are not available on plan9 os.File; we rely on Stop(). - return nil -} - -func (t *p9Tty) Stop() error { - t.mu.Lock() - defer t.mu.Unlock() - - // Signal watcher to stop (if not already). - if t.stopCh != nil && !isClosed(t.stopCh) { - close(t.stopCh) - } - - // Exit raw mode first. - _, _ = t.consctl.Write([]byte("rawoff")) - - // Closing wctl unblocks watchResize; nil it so Start() can reopen later. - if t.wctl != nil { - _ = t.wctl.Close() - t.wctl = nil - } - - // Ensure watcher goroutine has exited before returning. - t.wg.Wait() - return nil -} - -func (t *p9Tty) Close() error { - t.mu.Lock() - defer t.mu.Unlock() - - if t.closed.Swap(true) { - return nil - } - - if t.stopCh != nil && !isClosed(t.stopCh) { - close(t.stopCh) - } - _, _ = t.consctl.Write([]byte("rawoff")) - - _ = t.cons.Close() - _ = t.consctl.Close() - if t.wctl != nil { - _ = t.wctl.Close() - t.wctl = nil - } - - t.wg.Wait() - return nil -} - -func (t *p9Tty) Read(p []byte) (int, error) { - return t.cons.Read(p) -} - -func (t *p9Tty) Write(p []byte) (int, error) { - return t.cons.Write(p) -} - -func (t *p9Tty) NotifyResize(cb func()) { - if cb == nil { - t.onResize.Store((func())(nil)) - return - } - t.onResize.Store(cb) -} - -func (t *p9Tty) WindowSize() (WindowSize, error) { - // Strategy: - // 1) honor explicit overrides (TCELL_LINES/TCELL_COLS, LINES/COLUMNS), - // 2) otherwise return conservative 80x24. - // Reading /dev/wctl gives pixel geometry, but char cell metrics are - // not generally available to non-draw clients; vt(1) is fixed-cell. - lines, cols := envInt("TCELL_LINES"), envInt("TCELL_COLS") - if lines == 0 { - lines = envInt("LINES") - } - if cols == 0 { - cols = envInt("COLUMNS") - } - if lines <= 0 { - lines = 24 - } - if cols <= 0 { - cols = 80 - } - return WindowSize{Width: cols, Height: lines}, nil -} - -// watchResize blocks on /dev/wctl reads; each read returns when the window -// changes size/position/state, per rio(4). We ignore the parsed geometry and -// just notify tcell to re-query WindowSize(). -func (t *p9Tty) watchResize() { - defer t.wg.Done() - - r := bufio.NewReader(t.wctl) - for { - select { - case <-t.stopCh: - return - default: - } - // Each read delivers something like: - // " minx miny maxx maxy visible current\n" - // We don't need to parse here; just signal. - _, err := r.ReadString('\n') - if err != nil { - if errors.Is(err, io.EOF) { - return - } - // transient errors: continue - } - if cb, _ := t.onResize.Load().(func()); cb != nil { - cb() - } - } -} - -func envInt(name string) int { - if s := strings.TrimSpace(os.Getenv(name)); s != "" { - if v, err := strconv.Atoi(s); err == nil { - return v - } - } - return 0 -} - -// helper: safe check if a channel is closed -func isClosed(ch <-chan struct{}) bool { - select { - case <-ch: - return true - default: - return false - } -} diff --git a/vendor/github.com/gdamore/tcell/v2/tty_unix.go b/vendor/github.com/gdamore/tcell/v2/tty_unix.go deleted file mode 100644 index ca82d83d840..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/tty_unix.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2021 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos -// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos - -package tcell - -import ( - "errors" - "fmt" - "os" - "os/signal" - "strconv" - "sync" - "syscall" - "time" - - "golang.org/x/sys/unix" - "golang.org/x/term" -) - -// devTty is an implementation of the Tty API based upon /dev/tty. -type devTty struct { - fd int - f *os.File - of *os.File // the first open of /dev/tty - saved *term.State - sig chan os.Signal - cb func() - stopQ chan struct{} - dev string - wg sync.WaitGroup - l sync.Mutex -} - -func (tty *devTty) Read(b []byte) (int, error) { - return tty.f.Read(b) -} - -func (tty *devTty) Write(b []byte) (int, error) { - return tty.f.Write(b) -} - -func (tty *devTty) Close() error { - return tty.f.Close() -} - -func (tty *devTty) Start() error { - tty.l.Lock() - defer tty.l.Unlock() - - // We open another copy of /dev/tty. This is a workaround for unusual behavior - // observed in macOS, apparently caused when a subshell (for example) closes our - // own tty device (when it exits for example). Getting a fresh new one seems to - // resolve the problem. (We believe this is a bug in the macOS tty driver that - // fails to account for dup() references to the same file before applying close() - // related behaviors to the tty.) We're also holding the original copy we opened - // since closing that might have deleterious effects as well. The upshot is that - // we will have up to two separate file handles open on /dev/tty. (Note that when - // using stdin/stdout instead of /dev/tty this problem is not observed.) - var err error - if tty.f, err = os.OpenFile(tty.dev, os.O_RDWR, 0); err != nil { - return err - } - - if !term.IsTerminal(tty.fd) { - return errors.New("device is not a terminal") - } - - _ = tty.f.SetReadDeadline(time.Time{}) - saved, err := term.MakeRaw(tty.fd) // also sets vMin and vTime - if err != nil { - return err - } - tty.saved = saved - - tty.stopQ = make(chan struct{}) - tty.wg.Add(1) - go func(stopQ chan struct{}) { - defer tty.wg.Done() - for { - select { - case <-tty.sig: - tty.l.Lock() - cb := tty.cb - tty.l.Unlock() - if cb != nil { - cb() - } - case <-stopQ: - return - } - } - }(tty.stopQ) - - signal.Notify(tty.sig, syscall.SIGWINCH) - return nil -} - -func (tty *devTty) Drain() error { - _ = tty.f.SetReadDeadline(time.Now()) - if err := tcSetBufParams(tty.fd, 0, 0); err != nil { - return err - } - return nil -} - -func (tty *devTty) Stop() error { - tty.l.Lock() - if err := term.Restore(tty.fd, tty.saved); err != nil { - tty.l.Unlock() - return err - } - _ = tty.f.SetReadDeadline(time.Now()) - - signal.Stop(tty.sig) - close(tty.stopQ) - tty.l.Unlock() - - tty.wg.Wait() - - // close our tty device -- we'll get another one if we Start again later. - _ = tty.f.Close() - - return nil -} - -func (tty *devTty) WindowSize() (WindowSize, error) { - size := WindowSize{} - ws, err := unix.IoctlGetWinsize(tty.fd, unix.TIOCGWINSZ) - if err != nil { - return size, err - } - w := int(ws.Col) - h := int(ws.Row) - if w == 0 { - w, _ = strconv.Atoi(os.Getenv("COLUMNS")) - } - if w == 0 { - w = 80 // default - } - if h == 0 { - h, _ = strconv.Atoi(os.Getenv("LINES")) - } - if h == 0 { - h = 25 // default - } - size.Width = w - size.Height = h - size.PixelWidth = int(ws.Xpixel) - size.PixelHeight = int(ws.Ypixel) - return size, nil -} - -func (tty *devTty) NotifyResize(cb func()) { - tty.l.Lock() - tty.cb = cb - tty.l.Unlock() -} - -// NewDevTty opens a /dev/tty based Tty. -func NewDevTty() (Tty, error) { - return NewDevTtyFromDev("/dev/tty") -} - -// NewDevTtyFromDev opens a tty device given a path. This can be useful to bind to other nodes. -func NewDevTtyFromDev(dev string) (Tty, error) { - tty := &devTty{ - dev: dev, - sig: make(chan os.Signal), - } - var err error - if tty.of, err = os.OpenFile(dev, os.O_RDWR, 0); err != nil { - return nil, err - } - tty.fd = int(tty.of.Fd()) - if !term.IsTerminal(tty.fd) { - _ = tty.f.Close() - return nil, errors.New("not a terminal") - } - if tty.saved, err = term.GetState(tty.fd); err != nil { - _ = tty.f.Close() - return nil, fmt.Errorf("failed to get state: %w", err) - } - return tty, nil -} diff --git a/vendor/github.com/gdamore/tcell/v2/wscreen.go b/vendor/github.com/gdamore/tcell/v2/wscreen.go deleted file mode 100644 index 44734c27bae..00000000000 --- a/vendor/github.com/gdamore/tcell/v2/wscreen.go +++ /dev/null @@ -1,676 +0,0 @@ -// Copyright 2025 The TCell Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use file except in compliance with the License. -// You may obtain a copy of the license at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build js && wasm -// +build js,wasm - -package tcell - -import ( - "errors" - "fmt" - "strings" - "sync" - "syscall/js" - "unicode/utf8" - - "github.com/gdamore/tcell/v2/terminfo" -) - -func NewTerminfoScreen() (Screen, error) { - t := &wScreen{} - t.fallback = make(map[rune]string) - - return &baseScreen{screenImpl: t}, nil -} - -type wScreen struct { - w, h int - style Style - cells CellBuffer - - running bool - clear bool - flagsPresent bool - pasteEnabled bool - mouseFlags MouseFlags - - cursorStyle CursorStyle - - quit chan struct{} - evch chan Event - fallback map[rune]string - finiOnce sync.Once - - sync.Mutex -} - -func (t *wScreen) Init() error { - t.w, t.h = 80, 24 // default for html as of now - t.evch = make(chan Event, 10) - t.quit = make(chan struct{}) - - t.Lock() - t.running = true - t.style = StyleDefault - t.cells.Resize(t.w, t.h) - t.Unlock() - - js.Global().Set("onKeyEvent", js.FuncOf(t.onKeyEvent)) - js.Global().Set("onMouseClick", js.FuncOf(t.unset)) - js.Global().Set("onMouseMove", js.FuncOf(t.unset)) - js.Global().Set("onFocus", js.FuncOf(t.unset)) - - return nil -} - -func (t *wScreen) Fini() { - t.finiOnce.Do(func() { - close(t.quit) - }) -} - -func (t *wScreen) SetStyle(style Style) { - t.Lock() - t.style = style - t.Unlock() -} - -// paletteColor gives a more natural palette color actually matching -// typical XTerm. We might in the future want to permit styling these -// via CSS. - -var palette = map[Color]int32{ - ColorBlack: 0x000000, - ColorMaroon: 0xcd0000, - ColorGreen: 0x00cd00, - ColorOlive: 0xcdcd00, - ColorNavy: 0x0000ee, - ColorPurple: 0xcd00cd, - ColorTeal: 0x00cdcd, - ColorSilver: 0xe5e5e5, - ColorGray: 0x7f7f7f, - ColorRed: 0xff0000, - ColorLime: 0x00ff00, - ColorYellow: 0xffff00, - ColorBlue: 0x5c5cff, - ColorFuchsia: 0xff00ff, - ColorAqua: 0x00ffff, - ColorWhite: 0xffffff, -} - -func paletteColor(c Color) int32 { - if c.IsRGB() { - return int32(c & 0xffffff) - } - if c >= ColorBlack && c <= ColorWhite { - return palette[c] - } - return c.Hex() -} - -func (t *wScreen) drawCell(x, y int) int { - mainc, combc, style, width := t.cells.GetContent(x, y) - - if !t.cells.Dirty(x, y) { - return width - } - - if style == StyleDefault { - style = t.style - } - - fg, bg := paletteColor(style.fg), paletteColor(style.bg) - if fg == -1 { - fg = 0xe5e5e5 - } - if bg == -1 { - bg = 0x000000 - } - us, uc := style.ulStyle, paletteColor(style.ulColor) - if uc == -1 { - uc = 0x000000 - } - - s := "" - if len(combc) > 0 { - b := make([]rune, 0, 1+len(combc)) - b = append(b, mainc) - b = append(b, combc...) - s = string(b) - } else { - s = string(mainc) - } - - t.cells.SetDirty(x, y, false) - js.Global().Call("drawCell", x, y, s, fg, bg, int(style.attrs), int(us), int(uc)) - - return width -} - -func (t *wScreen) ShowCursor(x, y int) { - t.Lock() - js.Global().Call("showCursor", x, y) - t.Unlock() -} - -func (t *wScreen) SetCursor(cs CursorStyle, cc Color) { - if !cc.Valid() { - cc = ColorLightGray - } - t.Lock() - js.Global().Call("setCursorStyle", curStyleClasses[cs], fmt.Sprintf("#%06x", cc.Hex())) - t.Unlock() -} - -func (t *wScreen) HideCursor() { - t.ShowCursor(-1, -1) -} - -func (t *wScreen) Show() { - t.Lock() - t.resize() - t.draw() - t.Unlock() -} - -func (t *wScreen) clearScreen() { - js.Global().Call("clearScreen", t.style.fg.Hex(), t.style.bg.Hex()) - t.clear = false -} - -func (t *wScreen) draw() { - if t.clear { - t.clearScreen() - } - - for y := 0; y < t.h; y++ { - for x := 0; x < t.w; x++ { - width := t.drawCell(x, y) - x += width - 1 - } - } - - js.Global().Call("show") -} - -func (t *wScreen) EnableMouse(flags ...MouseFlags) { - var f MouseFlags - flagsPresent := false - for _, flag := range flags { - f |= flag - flagsPresent = true - } - if !flagsPresent { - f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents - } - - t.Lock() - t.mouseFlags = f - t.enableMouse(f) - t.Unlock() -} - -func (t *wScreen) enableMouse(f MouseFlags) { - if f&MouseButtonEvents != 0 { - js.Global().Set("onMouseClick", js.FuncOf(t.onMouseEvent)) - } else { - js.Global().Set("onMouseClick", js.FuncOf(t.unset)) - } - - if f&MouseDragEvents != 0 || f&MouseMotionEvents != 0 { - js.Global().Set("onMouseMove", js.FuncOf(t.onMouseEvent)) - } else { - js.Global().Set("onMouseMove", js.FuncOf(t.unset)) - } -} - -func (t *wScreen) DisableMouse() { - t.Lock() - t.mouseFlags = 0 - t.enableMouse(0) - t.Unlock() -} - -func (t *wScreen) EnablePaste() { - t.Lock() - t.pasteEnabled = true - t.enablePasting(true) - t.Unlock() -} - -func (t *wScreen) DisablePaste() { - t.Lock() - t.pasteEnabled = false - t.enablePasting(false) - t.Unlock() -} - -func (t *wScreen) enablePasting(on bool) { - if on { - js.Global().Set("onPaste", js.FuncOf(t.onPaste)) - } else { - js.Global().Set("onPaste", js.FuncOf(t.unset)) - } -} - -func (t *wScreen) EnableFocus() { - t.Lock() - js.Global().Set("onFocus", js.FuncOf(t.onFocus)) - t.Unlock() -} - -func (t *wScreen) DisableFocus() { - t.Lock() - js.Global().Set("onFocus", js.FuncOf(t.unset)) - t.Unlock() -} - -func (s *wScreen) GetClipboard() { -} - -func (s *wScreen) SetClipboard(_ []byte) { -} - -func (t *wScreen) Size() (int, int) { - t.Lock() - w, h := t.w, t.h - t.Unlock() - return w, h -} - -// resize does nothing, as asking the web window to resize -// without a specified width or height will cause no change. -func (t *wScreen) resize() {} - -func (t *wScreen) Colors() int { - return 16777216 // 256 ^ 3 -} - -func (t *wScreen) clip(x, y int) (int, int) { - w, h := t.cells.Size() - if x < 0 { - x = 0 - } - if y < 0 { - y = 0 - } - if x > w-1 { - x = w - 1 - } - if y > h-1 { - y = h - 1 - } - return x, y -} - -func (t *wScreen) postEvent(ev Event) { - select { - case t.evch <- ev: - case <-t.quit: - } -} - -func (t *wScreen) onMouseEvent(this js.Value, args []js.Value) interface{} { - mod := ModNone - button := ButtonNone - - switch args[2].Int() { - case 0: - if t.mouseFlags&MouseMotionEvents == 0 { - // don't want this event! is a mouse motion event, but user has asked not. - return nil - } - button = ButtonNone - case 1: - button = Button1 - case 2: - button = Button3 // Note we prefer to treat right as button 2 - case 3: - button = Button2 // And the middle button as button 3 - } - - if args[3].Bool() { // mod shift - mod |= ModShift - } - - if args[4].Bool() { // mod alt - mod |= ModAlt - } - - if args[5].Bool() { // mod ctrl - mod |= ModCtrl - } - - t.postEvent(NewEventMouse(args[0].Int(), args[1].Int(), button, mod)) - return nil -} - -func (t *wScreen) onKeyEvent(this js.Value, args []js.Value) interface{} { - key := args[0].String() - - // don't accept any modifier keys as their own - if key == "Control" || key == "Alt" || key == "Meta" || key == "Shift" { - return nil - } - - mod := ModNone - if args[1].Bool() { // mod shift - mod |= ModShift - } - - if args[2].Bool() { // mod alt - mod |= ModAlt - } - - if args[3].Bool() { // mod ctrl - mod |= ModCtrl - } - - if args[4].Bool() { // mod meta - mod |= ModMeta - } - - // check for special case of Ctrl + key - if mod == ModCtrl { - if k, ok := WebKeyNames["Ctrl-"+strings.ToLower(key)]; ok { - t.postEvent(NewEventKey(k, 0, mod)) - return nil - } - } - - // next try function keys - if k, ok := WebKeyNames[key]; ok { - t.postEvent(NewEventKey(k, 0, mod)) - return nil - } - - // finally try normal, printable chars - r, _ := utf8.DecodeRuneInString(key) - t.postEvent(NewEventKey(KeyRune, r, mod)) - return nil -} - -func (t *wScreen) onPaste(this js.Value, args []js.Value) interface{} { - t.postEvent(NewEventPaste(args[0].Bool())) - return nil -} - -func (t *wScreen) onFocus(this js.Value, args []js.Value) interface{} { - t.postEvent(NewEventFocus(args[0].Bool())) - return nil -} - -// unset is a dummy function for js when we want nothing to -// happen when javascript calls a function (for example, when -// mouse input is disabled, when onMouseEvent() is called from -// js, it redirects here and does nothing). -func (t *wScreen) unset(this js.Value, args []js.Value) interface{} { - return nil -} - -func (t *wScreen) Sync() { - t.Lock() - t.resize() - t.clear = true - t.cells.Invalidate() - t.draw() - t.Unlock() -} - -func (t *wScreen) CharacterSet() string { - return "UTF-8" -} - -func (t *wScreen) RegisterRuneFallback(orig rune, fallback string) { - t.Lock() - t.fallback[orig] = fallback - t.Unlock() -} - -func (t *wScreen) UnregisterRuneFallback(orig rune) { - t.Lock() - delete(t.fallback, orig) - t.Unlock() -} - -func (t *wScreen) CanDisplay(r rune, checkFallbacks bool) bool { - if utf8.ValidRune(r) { - return true - } - if !checkFallbacks { - return false - } - if _, ok := t.fallback[r]; ok { - return true - } - return false -} - -func (t *wScreen) HasMouse() bool { - return true -} - -func (t *wScreen) HasKey(k Key) bool { - return true -} - -func (t *wScreen) SetSize(w, h int) { - if w == t.w && h == t.h { - return - } - - t.cells.Invalidate() - t.cells.Resize(w, h) - js.Global().Call("resize", w, h) - t.w, t.h = w, h - t.postEvent(NewEventResize(w, h)) -} - -func (t *wScreen) Resize(int, int, int, int) {} - -// Suspend simply pauses all input and output, and clears the screen. -// There isn't a "default terminal" to go back to. -func (t *wScreen) Suspend() error { - t.Lock() - if !t.running { - t.Unlock() - return nil - } - t.running = false - t.clearScreen() - t.enableMouse(0) - t.enablePasting(false) - js.Global().Set("onKeyEvent", js.FuncOf(t.unset)) // stop keypresses - return nil -} - -func (t *wScreen) Resume() error { - t.Lock() - - if t.running { - return errors.New("already engaged") - } - t.running = true - - t.enableMouse(t.mouseFlags) - t.enablePasting(t.pasteEnabled) - - js.Global().Set("onKeyEvent", js.FuncOf(t.onKeyEvent)) - - t.Unlock() - return nil -} - -func (t *wScreen) Beep() error { - js.Global().Call("beep") - return nil -} - -func (t *wScreen) Tty() (Tty, bool) { - return nil, false -} - -func (t *wScreen) GetCells() *CellBuffer { - return &t.cells -} - -func (t *wScreen) EventQ() chan Event { - return t.evch -} - -func (t *wScreen) StopQ() <-chan struct{} { - return t.quit -} - -func (t *wScreen) SetTitle(title string) { - js.Global().Call("setTitle", title) -} - -// WebKeyNames maps string names reported from HTML -// (KeyboardEvent.key) to tcell accepted keys. -var WebKeyNames = map[string]Key{ - "Enter": KeyEnter, - "Backspace": KeyBackspace, - "Tab": KeyTab, - "Backtab": KeyBacktab, - "Escape": KeyEsc, - "Backspace2": KeyBackspace2, - "Delete": KeyDelete, - "Insert": KeyInsert, - "ArrowUp": KeyUp, - "ArrowDown": KeyDown, - "ArrowLeft": KeyLeft, - "ArrowRight": KeyRight, - "Home": KeyHome, - "End": KeyEnd, - "UpLeft": KeyUpLeft, // not supported by HTML - "UpRight": KeyUpRight, // not supported by HTML - "DownLeft": KeyDownLeft, // not supported by HTML - "DownRight": KeyDownRight, // not supported by HTML - "Center": KeyCenter, - "PgDn": KeyPgDn, - "PgUp": KeyPgUp, - "Clear": KeyClear, - "Exit": KeyExit, - "Cancel": KeyCancel, - "Pause": KeyPause, - "Print": KeyPrint, - "F1": KeyF1, - "F2": KeyF2, - "F3": KeyF3, - "F4": KeyF4, - "F5": KeyF5, - "F6": KeyF6, - "F7": KeyF7, - "F8": KeyF8, - "F9": KeyF9, - "F10": KeyF10, - "F11": KeyF11, - "F12": KeyF12, - "F13": KeyF13, - "F14": KeyF14, - "F15": KeyF15, - "F16": KeyF16, - "F17": KeyF17, - "F18": KeyF18, - "F19": KeyF19, - "F20": KeyF20, - "F21": KeyF21, - "F22": KeyF22, - "F23": KeyF23, - "F24": KeyF24, - "F25": KeyF25, - "F26": KeyF26, - "F27": KeyF27, - "F28": KeyF28, - "F29": KeyF29, - "F30": KeyF30, - "F31": KeyF31, - "F32": KeyF32, - "F33": KeyF33, - "F34": KeyF34, - "F35": KeyF35, - "F36": KeyF36, - "F37": KeyF37, - "F38": KeyF38, - "F39": KeyF39, - "F40": KeyF40, - "F41": KeyF41, - "F42": KeyF42, - "F43": KeyF43, - "F44": KeyF44, - "F45": KeyF45, - "F46": KeyF46, - "F47": KeyF47, - "F48": KeyF48, - "F49": KeyF49, - "F50": KeyF50, - "F51": KeyF51, - "F52": KeyF52, - "F53": KeyF53, - "F54": KeyF54, - "F55": KeyF55, - "F56": KeyF56, - "F57": KeyF57, - "F58": KeyF58, - "F59": KeyF59, - "F60": KeyF60, - "F61": KeyF61, - "F62": KeyF62, - "F63": KeyF63, - "F64": KeyF64, - "Ctrl-a": KeyCtrlA, // not reported by HTML- need to do special check - "Ctrl-b": KeyCtrlB, // not reported by HTML- need to do special check - "Ctrl-c": KeyCtrlC, // not reported by HTML- need to do special check - "Ctrl-d": KeyCtrlD, // not reported by HTML- need to do special check - "Ctrl-e": KeyCtrlE, // not reported by HTML- need to do special check - "Ctrl-f": KeyCtrlF, // not reported by HTML- need to do special check - "Ctrl-g": KeyCtrlG, // not reported by HTML- need to do special check - "Ctrl-j": KeyCtrlJ, // not reported by HTML- need to do special check - "Ctrl-k": KeyCtrlK, // not reported by HTML- need to do special check - "Ctrl-l": KeyCtrlL, // not reported by HTML- need to do special check - "Ctrl-n": KeyCtrlN, // not reported by HTML- need to do special check - "Ctrl-o": KeyCtrlO, // not reported by HTML- need to do special check - "Ctrl-p": KeyCtrlP, // not reported by HTML- need to do special check - "Ctrl-q": KeyCtrlQ, // not reported by HTML- need to do special check - "Ctrl-r": KeyCtrlR, // not reported by HTML- need to do special check - "Ctrl-s": KeyCtrlS, // not reported by HTML- need to do special check - "Ctrl-t": KeyCtrlT, // not reported by HTML- need to do special check - "Ctrl-u": KeyCtrlU, // not reported by HTML- need to do special check - "Ctrl-v": KeyCtrlV, // not reported by HTML- need to do special check - "Ctrl-w": KeyCtrlW, // not reported by HTML- need to do special check - "Ctrl-x": KeyCtrlX, // not reported by HTML- need to do special check - "Ctrl-y": KeyCtrlY, // not reported by HTML- need to do special check - "Ctrl-z": KeyCtrlZ, // not reported by HTML- need to do special check - "Ctrl- ": KeyCtrlSpace, // not reported by HTML- need to do special check - "Ctrl-_": KeyCtrlUnderscore, // not reported by HTML- need to do special check - "Ctrl-]": KeyCtrlRightSq, // not reported by HTML- need to do special check - "Ctrl-\\": KeyCtrlBackslash, // not reported by HTML- need to do special check - "Ctrl-^": KeyCtrlCarat, // not reported by HTML- need to do special check -} - -var curStyleClasses = map[CursorStyle]string{ - CursorStyleDefault: "cursor-blinking-block", - CursorStyleBlinkingBlock: "cursor-blinking-block", - CursorStyleSteadyBlock: "cursor-steady-block", - CursorStyleBlinkingUnderline: "cursor-blinking-underline", - CursorStyleSteadyUnderline: "cursor-steady-underline", - CursorStyleBlinkingBar: "cursor-blinking-bar", - CursorStyleSteadyBar: "cursor-steady-bar", -} - -func LookupTerminfo(name string) (ti *terminfo.Terminfo, e error) { - return nil, errors.New("LookupTermInfo not supported") -} diff --git a/vendor/github.com/go-errors/errors/.travis.yml b/vendor/github.com/go-errors/errors/.travis.yml deleted file mode 100644 index 1dc296026a7..00000000000 --- a/vendor/github.com/go-errors/errors/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: go - -go: - - "1.8.x" - - "1.11.x" - - "1.16.x" - - "1.21.x" diff --git a/vendor/github.com/go-errors/errors/LICENSE.MIT b/vendor/github.com/go-errors/errors/LICENSE.MIT deleted file mode 100644 index c9a5b2eeb75..00000000000 --- a/vendor/github.com/go-errors/errors/LICENSE.MIT +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2015 Conrad Irwin - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/go-errors/errors/README.md b/vendor/github.com/go-errors/errors/README.md deleted file mode 100644 index 558bc883ef9..00000000000 --- a/vendor/github.com/go-errors/errors/README.md +++ /dev/null @@ -1,84 +0,0 @@ -go-errors/errors -================ - -[![Build Status](https://travis-ci.org/go-errors/errors.svg?branch=master)](https://travis-ci.org/go-errors/errors) - -Package errors adds stacktrace support to errors in go. - -This is particularly useful when you want to understand the state of execution -when an error was returned unexpectedly. - -It provides the type \*Error which implements the standard golang error -interface, so you can use this library interchangeably with code that is -expecting a normal error return. - -Usage ------ - -Full documentation is available on -[godoc](https://godoc.org/github.com/go-errors/errors), but here's a simple -example: - -```go -package crashy - -import "github.com/go-errors/errors" - -var Crashed = errors.Errorf("oh dear") - -func Crash() error { - return errors.New(Crashed) -} -``` - -This can be called as follows: - -```go -package main - -import ( - "crashy" - "fmt" - "github.com/go-errors/errors" -) - -func main() { - err := crashy.Crash() - if err != nil { - if errors.Is(err, crashy.Crashed) { - fmt.Println(err.(*errors.Error).ErrorStack()) - } else { - panic(err) - } - } -} -``` - -Meta-fu -------- - -This package was original written to allow reporting to -[Bugsnag](https://bugsnag.com/) from -[bugsnag-go](https://github.com/bugsnag/bugsnag-go), but after I found similar -packages by Facebook and Dropbox, it was moved to one canonical location so -everyone can benefit. - -This package is licensed under the MIT license, see LICENSE.MIT for details. - - -## Changelog -* v1.1.0 updated to use go1.13's standard-library errors.Is method instead of == in errors.Is -* v1.2.0 added `errors.As` from the standard library. -* v1.3.0 *BREAKING* updated error methods to return `error` instead of `*Error`. -> Code that needs access to the underlying `*Error` can use the new errors.AsError(e) -> ``` -> // before -> errors.New(err).ErrorStack() -> // after ->. errors.AsError(errors.Wrap(err)).ErrorStack() -> ``` -* v1.4.0 *BREAKING* v1.4.0 reverted all changes from v1.3.0 and is identical to v1.2.0 -* v1.4.1 no code change, but now without an unnecessary cover.out file. -* v1.4.2 performance improvement to ErrorStack() to avoid unnecessary work https://github.com/go-errors/errors/pull/40 -* v1.5.0 add errors.Join() and errors.Unwrap() copying the stdlib https://github.com/go-errors/errors/pull/40 -* v1.5.1 fix build on go1.13..go1.19 (broken by adding Join and Unwrap with wrong build constraints) diff --git a/vendor/github.com/go-errors/errors/error.go b/vendor/github.com/go-errors/errors/error.go deleted file mode 100644 index ccbc2e4272d..00000000000 --- a/vendor/github.com/go-errors/errors/error.go +++ /dev/null @@ -1,209 +0,0 @@ -// Package errors provides errors that have stack-traces. -// -// This is particularly useful when you want to understand the -// state of execution when an error was returned unexpectedly. -// -// It provides the type *Error which implements the standard -// golang error interface, so you can use this library interchangably -// with code that is expecting a normal error return. -// -// For example: -// -// package crashy -// -// import "github.com/go-errors/errors" -// -// var Crashed = errors.Errorf("oh dear") -// -// func Crash() error { -// return errors.New(Crashed) -// } -// -// This can be called as follows: -// -// package main -// -// import ( -// "crashy" -// "fmt" -// "github.com/go-errors/errors" -// ) -// -// func main() { -// err := crashy.Crash() -// if err != nil { -// if errors.Is(err, crashy.Crashed) { -// fmt.Println(err.(*errors.Error).ErrorStack()) -// } else { -// panic(err) -// } -// } -// } -// -// This package was original written to allow reporting to Bugsnag, -// but after I found similar packages by Facebook and Dropbox, it -// was moved to one canonical location so everyone can benefit. -package errors - -import ( - "bytes" - "fmt" - "reflect" - "runtime" -) - -// The maximum number of stackframes on any error. -var MaxStackDepth = 50 - -// Error is an error with an attached stacktrace. It can be used -// wherever the builtin error interface is expected. -type Error struct { - Err error - stack []uintptr - frames []StackFrame - prefix string -} - -// New makes an Error from the given value. If that value is already an -// error then it will be used directly, if not, it will be passed to -// fmt.Errorf("%v"). The stacktrace will point to the line of code that -// called New. -func New(e interface{}) *Error { - var err error - - switch e := e.(type) { - case error: - err = e - default: - err = fmt.Errorf("%v", e) - } - - stack := make([]uintptr, MaxStackDepth) - length := runtime.Callers(2, stack[:]) - return &Error{ - Err: err, - stack: stack[:length], - } -} - -// Wrap makes an Error from the given value. If that value is already an -// error then it will be used directly, if not, it will be passed to -// fmt.Errorf("%v"). The skip parameter indicates how far up the stack -// to start the stacktrace. 0 is from the current call, 1 from its caller, etc. -func Wrap(e interface{}, skip int) *Error { - if e == nil { - return nil - } - - var err error - - switch e := e.(type) { - case *Error: - return e - case error: - err = e - default: - err = fmt.Errorf("%v", e) - } - - stack := make([]uintptr, MaxStackDepth) - length := runtime.Callers(2+skip, stack[:]) - return &Error{ - Err: err, - stack: stack[:length], - } -} - -// WrapPrefix makes an Error from the given value. If that value is already an -// error then it will be used directly, if not, it will be passed to -// fmt.Errorf("%v"). The prefix parameter is used to add a prefix to the -// error message when calling Error(). The skip parameter indicates how far -// up the stack to start the stacktrace. 0 is from the current call, -// 1 from its caller, etc. -func WrapPrefix(e interface{}, prefix string, skip int) *Error { - if e == nil { - return nil - } - - err := Wrap(e, 1+skip) - - if err.prefix != "" { - prefix = fmt.Sprintf("%s: %s", prefix, err.prefix) - } - - return &Error{ - Err: err.Err, - stack: err.stack, - prefix: prefix, - } - -} - -// Errorf creates a new error with the given message. You can use it -// as a drop-in replacement for fmt.Errorf() to provide descriptive -// errors in return values. -func Errorf(format string, a ...interface{}) *Error { - return Wrap(fmt.Errorf(format, a...), 1) -} - -// Error returns the underlying error's message. -func (err *Error) Error() string { - - msg := err.Err.Error() - if err.prefix != "" { - msg = fmt.Sprintf("%s: %s", err.prefix, msg) - } - - return msg -} - -// Stack returns the callstack formatted the same way that go does -// in runtime/debug.Stack() -func (err *Error) Stack() []byte { - buf := bytes.Buffer{} - - for _, frame := range err.StackFrames() { - buf.WriteString(frame.String()) - } - - return buf.Bytes() -} - -// Callers satisfies the bugsnag ErrorWithCallerS() interface -// so that the stack can be read out. -func (err *Error) Callers() []uintptr { - return err.stack -} - -// ErrorStack returns a string that contains both the -// error message and the callstack. -func (err *Error) ErrorStack() string { - return err.TypeName() + " " + err.Error() + "\n" + string(err.Stack()) -} - -// StackFrames returns an array of frames containing information about the -// stack. -func (err *Error) StackFrames() []StackFrame { - if err.frames == nil { - err.frames = make([]StackFrame, len(err.stack)) - - for i, pc := range err.stack { - err.frames[i] = NewStackFrame(pc) - } - } - - return err.frames -} - -// TypeName returns the type this error. e.g. *errors.stringError. -func (err *Error) TypeName() string { - if _, ok := err.Err.(uncaughtPanic); ok { - return "panic" - } - return reflect.TypeOf(err.Err).String() -} - -// Return the wrapped error (implements api for As function). -func (err *Error) Unwrap() error { - return err.Err -} diff --git a/vendor/github.com/go-errors/errors/error_1_13.go b/vendor/github.com/go-errors/errors/error_1_13.go deleted file mode 100644 index 34ab3e00eb5..00000000000 --- a/vendor/github.com/go-errors/errors/error_1_13.go +++ /dev/null @@ -1,35 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -package errors - -import ( - baseErrors "errors" -) - -// As finds the first error in err's tree that matches target, and if one is found, sets -// target to that error value and returns true. Otherwise, it returns false. -// -// For more information see stdlib errors.As. -func As(err error, target interface{}) bool { - return baseErrors.As(err, target) -} - -// Is detects whether the error is equal to a given error. Errors -// are considered equal by this function if they are matched by errors.Is -// or if their contained errors are matched through errors.Is. -func Is(e error, original error) bool { - if baseErrors.Is(e, original) { - return true - } - - if e, ok := e.(*Error); ok { - return Is(e.Err, original) - } - - if original, ok := original.(*Error); ok { - return Is(e, original.Err) - } - - return false -} diff --git a/vendor/github.com/go-errors/errors/error_backward.go b/vendor/github.com/go-errors/errors/error_backward.go deleted file mode 100644 index ff14c4bfa27..00000000000 --- a/vendor/github.com/go-errors/errors/error_backward.go +++ /dev/null @@ -1,125 +0,0 @@ -//go:build !go1.13 -// +build !go1.13 - -package errors - -import ( - "reflect" -) - -type unwrapper interface { - Unwrap() error -} - -// As assigns error or any wrapped error to the value target points -// to. If there is no value of the target type of target As returns -// false. -func As(err error, target interface{}) bool { - targetType := reflect.TypeOf(target) - - for { - errType := reflect.TypeOf(err) - - if errType == nil { - return false - } - - if reflect.PtrTo(errType) == targetType { - reflect.ValueOf(target).Elem().Set(reflect.ValueOf(err)) - return true - } - - wrapped, ok := err.(unwrapper) - if ok { - err = wrapped.Unwrap() - } else { - return false - } - } -} - -// Is detects whether the error is equal to a given error. Errors -// are considered equal by this function if they are the same object, -// or if they both contain the same error inside an errors.Error. -func Is(e error, original error) bool { - if e == original { - return true - } - - if e, ok := e.(*Error); ok { - return Is(e.Err, original) - } - - if original, ok := original.(*Error); ok { - return Is(e, original.Err) - } - - return false -} - -// Disclaimer: functions Join and Unwrap are copied from the stdlib errors -// package v1.21.0. - -// Join returns an error that wraps the given errors. -// Any nil error values are discarded. -// Join returns nil if every value in errs is nil. -// The error formats as the concatenation of the strings obtained -// by calling the Error method of each element of errs, with a newline -// between each string. -// -// A non-nil error returned by Join implements the Unwrap() []error method. -func Join(errs ...error) error { - n := 0 - for _, err := range errs { - if err != nil { - n++ - } - } - if n == 0 { - return nil - } - e := &joinError{ - errs: make([]error, 0, n), - } - for _, err := range errs { - if err != nil { - e.errs = append(e.errs, err) - } - } - return e -} - -type joinError struct { - errs []error -} - -func (e *joinError) Error() string { - var b []byte - for i, err := range e.errs { - if i > 0 { - b = append(b, '\n') - } - b = append(b, err.Error()...) - } - return string(b) -} - -func (e *joinError) Unwrap() []error { - return e.errs -} - -// Unwrap returns the result of calling the Unwrap method on err, if err's -// type contains an Unwrap method returning error. -// Otherwise, Unwrap returns nil. -// -// Unwrap only calls a method of the form "Unwrap() error". -// In particular Unwrap does not unwrap errors returned by [Join]. -func Unwrap(err error) error { - u, ok := err.(interface { - Unwrap() error - }) - if !ok { - return nil - } - return u.Unwrap() -} diff --git a/vendor/github.com/go-errors/errors/join_unwrap_1_20.go b/vendor/github.com/go-errors/errors/join_unwrap_1_20.go deleted file mode 100644 index 44df35ece92..00000000000 --- a/vendor/github.com/go-errors/errors/join_unwrap_1_20.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build go1.20 -// +build go1.20 - -package errors - -import baseErrors "errors" - -// Join returns an error that wraps the given errors. -// Any nil error values are discarded. -// Join returns nil if every value in errs is nil. -// The error formats as the concatenation of the strings obtained -// by calling the Error method of each element of errs, with a newline -// between each string. -// -// A non-nil error returned by Join implements the Unwrap() []error method. -// -// For more information see stdlib errors.Join. -func Join(errs ...error) error { - return baseErrors.Join(errs...) -} - -// Unwrap returns the result of calling the Unwrap method on err, if err's -// type contains an Unwrap method returning error. -// Otherwise, Unwrap returns nil. -// -// Unwrap only calls a method of the form "Unwrap() error". -// In particular Unwrap does not unwrap errors returned by [Join]. -// -// For more information see stdlib errors.Unwrap. -func Unwrap(err error) error { - return baseErrors.Unwrap(err) -} diff --git a/vendor/github.com/go-errors/errors/join_unwrap_backward.go b/vendor/github.com/go-errors/errors/join_unwrap_backward.go deleted file mode 100644 index 50c766976c8..00000000000 --- a/vendor/github.com/go-errors/errors/join_unwrap_backward.go +++ /dev/null @@ -1,71 +0,0 @@ -//go:build !go1.20 -// +build !go1.20 - -package errors - -// Disclaimer: functions Join and Unwrap are copied from the stdlib errors -// package v1.21.0. - -// Join returns an error that wraps the given errors. -// Any nil error values are discarded. -// Join returns nil if every value in errs is nil. -// The error formats as the concatenation of the strings obtained -// by calling the Error method of each element of errs, with a newline -// between each string. -// -// A non-nil error returned by Join implements the Unwrap() []error method. -func Join(errs ...error) error { - n := 0 - for _, err := range errs { - if err != nil { - n++ - } - } - if n == 0 { - return nil - } - e := &joinError{ - errs: make([]error, 0, n), - } - for _, err := range errs { - if err != nil { - e.errs = append(e.errs, err) - } - } - return e -} - -type joinError struct { - errs []error -} - -func (e *joinError) Error() string { - var b []byte - for i, err := range e.errs { - if i > 0 { - b = append(b, '\n') - } - b = append(b, err.Error()...) - } - return string(b) -} - -func (e *joinError) Unwrap() []error { - return e.errs -} - -// Unwrap returns the result of calling the Unwrap method on err, if err's -// type contains an Unwrap method returning error. -// Otherwise, Unwrap returns nil. -// -// Unwrap only calls a method of the form "Unwrap() error". -// In particular Unwrap does not unwrap errors returned by [Join]. -func Unwrap(err error) error { - u, ok := err.(interface { - Unwrap() error - }) - if !ok { - return nil - } - return u.Unwrap() -} diff --git a/vendor/github.com/go-errors/errors/parse_panic.go b/vendor/github.com/go-errors/errors/parse_panic.go deleted file mode 100644 index cc37052d786..00000000000 --- a/vendor/github.com/go-errors/errors/parse_panic.go +++ /dev/null @@ -1,127 +0,0 @@ -package errors - -import ( - "strconv" - "strings" -) - -type uncaughtPanic struct{ message string } - -func (p uncaughtPanic) Error() string { - return p.message -} - -// ParsePanic allows you to get an error object from the output of a go program -// that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap. -func ParsePanic(text string) (*Error, error) { - lines := strings.Split(text, "\n") - - state := "start" - - var message string - var stack []StackFrame - - for i := 0; i < len(lines); i++ { - line := lines[i] - - if state == "start" { - if strings.HasPrefix(line, "panic: ") { - message = strings.TrimPrefix(line, "panic: ") - state = "seek" - } else { - return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line) - } - - } else if state == "seek" { - if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") { - state = "parsing" - } - - } else if state == "parsing" { - if line == "" { - state = "done" - break - } - createdBy := false - if strings.HasPrefix(line, "created by ") { - line = strings.TrimPrefix(line, "created by ") - createdBy = true - } - - i++ - - if i >= len(lines) { - return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line) - } - - frame, err := parsePanicFrame(line, lines[i], createdBy) - if err != nil { - return nil, err - } - - stack = append(stack, *frame) - if createdBy { - state = "done" - break - } - } - } - - if state == "done" || state == "parsing" { - return &Error{Err: uncaughtPanic{message}, frames: stack}, nil - } - return nil, Errorf("could not parse panic: %v", text) -} - -// The lines we're passing look like this: -// -// main.(*foo).destruct(0xc208067e98) -// /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151 -func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) { - idx := strings.LastIndex(name, "(") - if idx == -1 && !createdBy { - return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name) - } - if idx != -1 { - name = name[:idx] - } - pkg := "" - - if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 { - pkg += name[:lastslash] + "/" - name = name[lastslash+1:] - } - if period := strings.Index(name, "."); period >= 0 { - pkg += name[:period] - name = name[period+1:] - } - - name = strings.Replace(name, "·", ".", -1) - - if !strings.HasPrefix(line, "\t") { - return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line) - } - - idx = strings.LastIndex(line, ":") - if idx == -1 { - return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line) - } - file := line[1:idx] - - number := line[idx+1:] - if idx = strings.Index(number, " +"); idx > -1 { - number = number[:idx] - } - - lno, err := strconv.ParseInt(number, 10, 32) - if err != nil { - return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line) - } - - return &StackFrame{ - File: file, - LineNumber: int(lno), - Package: pkg, - Name: name, - }, nil -} diff --git a/vendor/github.com/go-errors/errors/stackframe.go b/vendor/github.com/go-errors/errors/stackframe.go deleted file mode 100644 index ef4a8b3f3b9..00000000000 --- a/vendor/github.com/go-errors/errors/stackframe.go +++ /dev/null @@ -1,122 +0,0 @@ -package errors - -import ( - "bufio" - "bytes" - "fmt" - "os" - "runtime" - "strings" -) - -// A StackFrame contains all necessary information about to generate a line -// in a callstack. -type StackFrame struct { - // The path to the file containing this ProgramCounter - File string - // The LineNumber in that file - LineNumber int - // The Name of the function that contains this ProgramCounter - Name string - // The Package that contains this function - Package string - // The underlying ProgramCounter - ProgramCounter uintptr -} - -// NewStackFrame popoulates a stack frame object from the program counter. -func NewStackFrame(pc uintptr) (frame StackFrame) { - - frame = StackFrame{ProgramCounter: pc} - if frame.Func() == nil { - return - } - frame.Package, frame.Name = packageAndName(frame.Func()) - - // pc -1 because the program counters we use are usually return addresses, - // and we want to show the line that corresponds to the function call - frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1) - return - -} - -// Func returns the function that contained this frame. -func (frame *StackFrame) Func() *runtime.Func { - if frame.ProgramCounter == 0 { - return nil - } - return runtime.FuncForPC(frame.ProgramCounter) -} - -// String returns the stackframe formatted in the same way as go does -// in runtime/debug.Stack() -func (frame *StackFrame) String() string { - str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter) - - source, err := frame.sourceLine() - if err != nil { - return str - } - - return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source) -} - -// SourceLine gets the line of code (from File and Line) of the original source if possible. -func (frame *StackFrame) SourceLine() (string, error) { - source, err := frame.sourceLine() - if err != nil { - return source, New(err) - } - return source, err -} - -func (frame *StackFrame) sourceLine() (string, error) { - if frame.LineNumber <= 0 { - return "???", nil - } - - file, err := os.Open(frame.File) - if err != nil { - return "", err - } - defer file.Close() - - scanner := bufio.NewScanner(file) - currentLine := 1 - for scanner.Scan() { - if currentLine == frame.LineNumber { - return string(bytes.Trim(scanner.Bytes(), " \t")), nil - } - currentLine++ - } - if err := scanner.Err(); err != nil { - return "", err - } - - return "???", nil -} - -func packageAndName(fn *runtime.Func) (string, string) { - name := fn.Name() - pkg := "" - - // The name includes the path name to the package, which is unnecessary - // since the file name is already included. Plus, it has center dots. - // That is, we see - // runtime/debug.*T·ptrmethod - // and want - // *T.ptrmethod - // Since the package path might contains dots (e.g. code.google.com/...), - // we first remove the path prefix if there is one. - if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 { - pkg += name[:lastslash] + "/" - name = name[lastslash+1:] - } - if period := strings.Index(name, "."); period >= 0 { - pkg += name[:period] - name = name[period+1:] - } - - name = strings.Replace(name, "·", ".", -1) - return pkg, name -} diff --git a/vendor/github.com/go-git/gcfg/.gitignore b/vendor/github.com/go-git/gcfg/.gitignore deleted file mode 100644 index 2d830686d42..00000000000 --- a/vendor/github.com/go-git/gcfg/.gitignore +++ /dev/null @@ -1 +0,0 @@ -coverage.out diff --git a/vendor/github.com/go-git/gcfg/LICENSE b/vendor/github.com/go-git/gcfg/LICENSE deleted file mode 100644 index 87a5cede339..00000000000 --- a/vendor/github.com/go-git/gcfg/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2012 Péter Surányi. Portions Copyright (c) 2009 The Go -Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/go-git/gcfg/Makefile b/vendor/github.com/go-git/gcfg/Makefile deleted file mode 100644 index 73604da6b61..00000000000 --- a/vendor/github.com/go-git/gcfg/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -# General -WORKDIR = $(PWD) - -# Go parameters -GOCMD = go -GOTEST = $(GOCMD) test - -# Coverage -COVERAGE_REPORT = coverage.out -COVERAGE_MODE = count - -test: - $(GOTEST) ./... - -test-coverage: - echo "" > $(COVERAGE_REPORT); \ - $(GOTEST) -coverprofile=$(COVERAGE_REPORT) -coverpkg=./... -covermode=$(COVERAGE_MODE) ./... diff --git a/vendor/github.com/go-git/gcfg/README b/vendor/github.com/go-git/gcfg/README deleted file mode 100644 index 1ff233a529d..00000000000 --- a/vendor/github.com/go-git/gcfg/README +++ /dev/null @@ -1,4 +0,0 @@ -Gcfg reads INI-style configuration files into Go structs; -supports user-defined types and subsections. - -Package docs: https://godoc.org/gopkg.in/gcfg.v1 diff --git a/vendor/github.com/go-git/gcfg/doc.go b/vendor/github.com/go-git/gcfg/doc.go deleted file mode 100644 index 7bdefbf0203..00000000000 --- a/vendor/github.com/go-git/gcfg/doc.go +++ /dev/null @@ -1,145 +0,0 @@ -// Package gcfg reads "INI-style" text-based configuration files with -// "name=value" pairs grouped into sections (gcfg files). -// -// This package is still a work in progress; see the sections below for planned -// changes. -// -// Syntax -// -// The syntax is based on that used by git config: -// http://git-scm.com/docs/git-config#_syntax . -// There are some (planned) differences compared to the git config format: -// - improve data portability: -// - must be encoded in UTF-8 (for now) and must not contain the 0 byte -// - include and "path" type is not supported -// (path type may be implementable as a user-defined type) -// - internationalization -// - section and variable names can contain unicode letters, unicode digits -// (as defined in http://golang.org/ref/spec#Characters ) and hyphens -// (U+002D), starting with a unicode letter -// - disallow potentially ambiguous or misleading definitions: -// - `[sec.sub]` format is not allowed (deprecated in gitconfig) -// - `[sec ""]` is not allowed -// - use `[sec]` for section name "sec" and empty subsection name -// - (planned) within a single file, definitions must be contiguous for each: -// - section: '[secA]' -> '[secB]' -> '[secA]' is an error -// - subsection: '[sec "A"]' -> '[sec "B"]' -> '[sec "A"]' is an error -// - multivalued variable: 'multi=a' -> 'other=x' -> 'multi=b' is an error -// -// Data structure -// -// The functions in this package read values into a user-defined struct. -// Each section corresponds to a struct field in the config struct, and each -// variable in a section corresponds to a data field in the section struct. -// The mapping of each section or variable name to fields is done either based -// on the "gcfg" struct tag or by matching the name of the section or variable, -// ignoring case. In the latter case, hyphens '-' in section and variable names -// correspond to underscores '_' in field names. -// Fields must be exported; to use a section or variable name starting with a -// letter that is neither upper- or lower-case, prefix the field name with 'X'. -// (See https://code.google.com/p/go/issues/detail?id=5763#c4 .) -// -// For sections with subsections, the corresponding field in config must be a -// map, rather than a struct, with string keys and pointer-to-struct values. -// Values for subsection variables are stored in the map with the subsection -// name used as the map key. -// (Note that unlike section and variable names, subsection names are case -// sensitive.) -// When using a map, and there is a section with the same section name but -// without a subsection name, its values are stored with the empty string used -// as the key. -// It is possible to provide default values for subsections in the section -// "default-" (or by setting values in the corresponding struct -// field "Default_"). -// -// The functions in this package panic if config is not a pointer to a struct, -// or when a field is not of a suitable type (either a struct or a map with -// string keys and pointer-to-struct values). -// -// Parsing of values -// -// The section structs in the config struct may contain single-valued or -// multi-valued variables. Variables of unnamed slice type (that is, a type -// starting with `[]`) are treated as multi-value; all others (including named -// slice types) are treated as single-valued variables. -// -// Single-valued variables are handled based on the type as follows. -// Unnamed pointer types (that is, types starting with `*`) are dereferenced, -// and if necessary, a new instance is allocated. -// -// For types implementing the encoding.TextUnmarshaler interface, the -// UnmarshalText method is used to set the value. Implementing this method is -// the recommended way for parsing user-defined types. -// -// For fields of string kind, the value string is assigned to the field, after -// unquoting and unescaping as needed. -// For fields of bool kind, the field is set to true if the value is "true", -// "yes", "on" or "1", and set to false if the value is "false", "no", "off" or -// "0", ignoring case. In addition, single-valued bool fields can be specified -// with a "blank" value (variable name without equals sign and value); in such -// case the value is set to true. -// -// Predefined integer types [u]int(|8|16|32|64) and big.Int are parsed as -// decimal or hexadecimal (if having '0x' prefix). (This is to prevent -// unintuitively handling zero-padded numbers as octal.) Other types having -// [u]int* as the underlying type, such as os.FileMode and uintptr allow -// decimal, hexadecimal, or octal values. -// Parsing mode for integer types can be overridden using the struct tag option -// ",int=mode" where mode is a combination of the 'd', 'h', and 'o' characters -// (each standing for decimal, hexadecimal, and octal, respectively.) -// -// All other types are parsed using fmt.Sscanf with the "%v" verb. -// -// For multi-valued variables, each individual value is parsed as above and -// appended to the slice. If the first value is specified as a "blank" value -// (variable name without equals sign and value), a new slice is allocated; -// that is any values previously set in the slice will be ignored. -// -// The types subpackage for provides helpers for parsing "enum-like" and integer -// types. -// -// Error handling -// -// There are 3 types of errors: -// -// - programmer errors / panics: -// - invalid configuration structure -// - data errors: -// - fatal errors: -// - invalid configuration syntax -// - warnings: -// - data that doesn't belong to any part of the config structure -// -// Programmer errors trigger panics. These are should be fixed by the programmer -// before releasing code that uses gcfg. -// -// Data errors cause gcfg to return a non-nil error value. This includes the -// case when there are extra unknown key-value definitions in the configuration -// data (extra data). -// However, in some occasions it is desirable to be able to proceed in -// situations when the only data error is that of extra data. -// These errors are handled at a different (warning) priority and can be -// filtered out programmatically. To ignore extra data warnings, wrap the -// gcfg.Read*Into invocation into a call to gcfg.FatalOnly. -// -// TODO -// -// The following is a list of changes under consideration: -// - documentation -// - self-contained syntax documentation -// - more practical examples -// - move TODOs to issue tracker (eventually) -// - syntax -// - reconsider valid escape sequences -// (gitconfig doesn't support \r in value, \t in subsection name, etc.) -// - reading / parsing gcfg files -// - define internal representation structure -// - support multiple inputs (readers, strings, files) -// - support declaring encoding (?) -// - support varying fields sets for subsections (?) -// - writing gcfg files -// - error handling -// - make error context accessible programmatically? -// - limit input size? -// -package gcfg // import "github.com/go-git/gcfg" diff --git a/vendor/github.com/go-git/gcfg/errors.go b/vendor/github.com/go-git/gcfg/errors.go deleted file mode 100644 index 853c76021de..00000000000 --- a/vendor/github.com/go-git/gcfg/errors.go +++ /dev/null @@ -1,41 +0,0 @@ -package gcfg - -import ( - "gopkg.in/warnings.v0" -) - -// FatalOnly filters the results of a Read*Into invocation and returns only -// fatal errors. That is, errors (warnings) indicating data for unknown -// sections / variables is ignored. Example invocation: -// -// err := gcfg.FatalOnly(gcfg.ReadFileInto(&cfg, configFile)) -// if err != nil { -// ... -// -func FatalOnly(err error) error { - return warnings.FatalOnly(err) -} - -func isFatal(err error) bool { - _, ok := err.(extraData) - return !ok -} - -type extraData struct { - section string - subsection *string - variable *string -} - -func (e extraData) Error() string { - s := "can't store data at section \"" + e.section + "\"" - if e.subsection != nil { - s += ", subsection \"" + *e.subsection + "\"" - } - if e.variable != nil { - s += ", variable \"" + *e.variable + "\"" - } - return s -} - -var _ error = extraData{} diff --git a/vendor/github.com/go-git/gcfg/read.go b/vendor/github.com/go-git/gcfg/read.go deleted file mode 100644 index ea5d2edd060..00000000000 --- a/vendor/github.com/go-git/gcfg/read.go +++ /dev/null @@ -1,273 +0,0 @@ -package gcfg - -import ( - "fmt" - "io" - "os" - "strings" - - "gopkg.in/warnings.v0" - - "github.com/go-git/gcfg/scanner" - "github.com/go-git/gcfg/token" -) - -var unescape = map[rune]rune{'\\': '\\', '"': '"', 'n': '\n', 't': '\t', 'b': '\b', '\n': '\n'} - -// no error: invalid literals should be caught by scanner -func unquote(s string) string { - u, q, esc := make([]rune, 0, len(s)), false, false - for _, c := range s { - if esc { - uc, ok := unescape[c] - switch { - case ok: - u = append(u, uc) - fallthrough - case !q && c == '\n': - esc = false - continue - } - panic("invalid escape sequence") - } - switch c { - case '"': - q = !q - case '\\': - esc = true - default: - u = append(u, c) - } - } - if q { - panic("missing end quote") - } - if esc { - panic("invalid escape sequence") - } - return string(u) -} - -func read(c *warnings.Collector, callback func(string, string, string, string, bool) error, - fset *token.FileSet, file *token.File, src []byte) error { - // - var s scanner.Scanner - var errs scanner.ErrorList - s.Init(file, src, func(p token.Position, m string) { errs.Add(p, m) }, 0) - sect, sectsub := "", "" - pos, tok, lit := s.Scan() - errfn := func(msg string) error { - return fmt.Errorf("%s: %s", fset.Position(pos), msg) - } - for { - if errs.Len() > 0 { - if err := c.Collect(errs.Err()); err != nil { - return err - } - } - switch tok { - case token.EOF: - return nil - case token.EOL, token.COMMENT: - pos, tok, lit = s.Scan() - case token.LBRACK: - pos, tok, lit = s.Scan() - if errs.Len() > 0 { - if err := c.Collect(errs.Err()); err != nil { - return err - } - } - if tok != token.IDENT { - if err := c.Collect(errfn("expected section name")); err != nil { - return err - } - } - sect, sectsub = lit, "" - pos, tok, lit = s.Scan() - if errs.Len() > 0 { - if err := c.Collect(errs.Err()); err != nil { - return err - } - } - if tok == token.STRING { - sectsub = unquote(lit) - if sectsub == "" { - if err := c.Collect(errfn("empty subsection name")); err != nil { - return err - } - } - pos, tok, lit = s.Scan() - if errs.Len() > 0 { - if err := c.Collect(errs.Err()); err != nil { - return err - } - } - } - if tok != token.RBRACK { - if sectsub == "" { - if err := c.Collect(errfn("expected subsection name or right bracket")); err != nil { - return err - } - } - if err := c.Collect(errfn("expected right bracket")); err != nil { - return err - } - } - pos, tok, lit = s.Scan() - if tok != token.EOL && tok != token.EOF && tok != token.COMMENT { - if err := c.Collect(errfn("expected EOL, EOF, or comment")); err != nil { - return err - } - } - // If a section/subsection header was found, ensure a - // container object is created, even if there are no - // variables further down. - err := c.Collect(callback(sect, sectsub, "", "", true)) - if err != nil { - return err - } - case token.IDENT: - if sect == "" { - if err := c.Collect(errfn("expected section header")); err != nil { - return err - } - } - n := lit - pos, tok, lit = s.Scan() - if errs.Len() > 0 { - return errs.Err() - } - blank, v := tok == token.EOF || tok == token.EOL || tok == token.COMMENT, "" - if !blank { - if tok != token.ASSIGN { - if err := c.Collect(errfn("expected '='")); err != nil { - return err - } - } - pos, tok, lit = s.Scan() - if errs.Len() > 0 { - if err := c.Collect(errs.Err()); err != nil { - return err - } - } - if tok != token.STRING { - if err := c.Collect(errfn("expected value")); err != nil { - return err - } - } - v = unquote(lit) - pos, tok, lit = s.Scan() - if errs.Len() > 0 { - if err := c.Collect(errs.Err()); err != nil { - return err - } - } - if tok != token.EOL && tok != token.EOF && tok != token.COMMENT { - if err := c.Collect(errfn("expected EOL, EOF, or comment")); err != nil { - return err - } - } - } - err := c.Collect(callback(sect, sectsub, n, v, blank)) - if err != nil { - return err - } - default: - if sect == "" { - if err := c.Collect(errfn("expected section header")); err != nil { - return err - } - } - if err := c.Collect(errfn("expected section header or variable declaration")); err != nil { - return err - } - } - } - panic("never reached") -} - -func readInto(config interface{}, fset *token.FileSet, file *token.File, - src []byte) error { - // - c := warnings.NewCollector(isFatal) - firstPassCallback := func(s string, ss string, k string, v string, bv bool) error { - return set(c, config, s, ss, k, v, bv, false) - } - err := read(c, firstPassCallback, fset, file, src) - if err != nil { - return err - } - secondPassCallback := func(s string, ss string, k string, v string, bv bool) error { - return set(c, config, s, ss, k, v, bv, true) - } - err = read(c, secondPassCallback, fset, file, src) - if err != nil { - return err - } - return c.Done() -} - -// ReadWithCallback reads gcfg formatted data from reader and calls -// callback with each section and option found. -// -// Callback is called with section, subsection, option key, option value -// and blank value flag as arguments. -// -// When a section is found, callback is called with nil subsection, option key -// and option value. -// -// When a subsection is found, callback is called with nil option key and -// option value. -// -// If blank value flag is true, it means that the value was not set for an option -// (as opposed to set to empty string). -// -// If callback returns an error, ReadWithCallback terminates with an error too. -func ReadWithCallback(reader io.Reader, callback func(string, string, string, string, bool) error) error { - src, err := io.ReadAll(reader) - if err != nil { - return err - } - - fset := token.NewFileSet() - file := fset.AddFile("", fset.Base(), len(src)) - c := warnings.NewCollector(isFatal) - - return read(c, callback, fset, file, src) -} - -// ReadInto reads gcfg formatted data from reader and sets the values into the -// corresponding fields in config. -func ReadInto(config interface{}, reader io.Reader) error { - src, err := io.ReadAll(reader) - if err != nil { - return err - } - fset := token.NewFileSet() - file := fset.AddFile("", fset.Base(), len(src)) - return readInto(config, fset, file, src) -} - -// ReadStringInto reads gcfg formatted data from str and sets the values into -// the corresponding fields in config. -func ReadStringInto(config interface{}, str string) error { - r := strings.NewReader(str) - return ReadInto(config, r) -} - -// ReadFileInto reads gcfg formatted data from the file filename and sets the -// values into the corresponding fields in config. -func ReadFileInto(config interface{}, filename string) error { - f, err := os.Open(filename) - if err != nil { - return err - } - defer f.Close() - src, err := io.ReadAll(f) - if err != nil { - return err - } - fset := token.NewFileSet() - file := fset.AddFile(filename, fset.Base(), len(src)) - return readInto(config, fset, file, src) -} diff --git a/vendor/github.com/go-git/gcfg/scanner/errors.go b/vendor/github.com/go-git/gcfg/scanner/errors.go deleted file mode 100644 index a6e00f5c64e..00000000000 --- a/vendor/github.com/go-git/gcfg/scanner/errors.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package scanner - -import ( - "fmt" - "io" - "sort" -) - -import ( - "github.com/go-git/gcfg/token" -) - -// In an ErrorList, an error is represented by an *Error. -// The position Pos, if valid, points to the beginning of -// the offending token, and the error condition is described -// by Msg. -// -type Error struct { - Pos token.Position - Msg string -} - -// Error implements the error interface. -func (e Error) Error() string { - if e.Pos.Filename != "" || e.Pos.IsValid() { - // don't print "" - // TODO(gri) reconsider the semantics of Position.IsValid - return e.Pos.String() + ": " + e.Msg - } - return e.Msg -} - -// ErrorList is a list of *Errors. -// The zero value for an ErrorList is an empty ErrorList ready to use. -// -type ErrorList []*Error - -// Add adds an Error with given position and error message to an ErrorList. -func (p *ErrorList) Add(pos token.Position, msg string) { - *p = append(*p, &Error{pos, msg}) -} - -// Reset resets an ErrorList to no errors. -func (p *ErrorList) Reset() { *p = (*p)[0:0] } - -// ErrorList implements the sort Interface. -func (p ErrorList) Len() int { return len(p) } -func (p ErrorList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -func (p ErrorList) Less(i, j int) bool { - e := &p[i].Pos - f := &p[j].Pos - if e.Filename < f.Filename { - return true - } - if e.Filename == f.Filename { - return e.Offset < f.Offset - } - return false -} - -// Sort sorts an ErrorList. *Error entries are sorted by position, -// other errors are sorted by error message, and before any *Error -// entry. -// -func (p ErrorList) Sort() { - sort.Sort(p) -} - -// RemoveMultiples sorts an ErrorList and removes all but the first error per line. -func (p *ErrorList) RemoveMultiples() { - sort.Sort(p) - var last token.Position // initial last.Line is != any legal error line - i := 0 - for _, e := range *p { - if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line { - last = e.Pos - (*p)[i] = e - i++ - } - } - (*p) = (*p)[0:i] -} - -// An ErrorList implements the error interface. -func (p ErrorList) Error() string { - switch len(p) { - case 0: - return "no errors" - case 1: - return p[0].Error() - } - return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) -} - -// Err returns an error equivalent to this error list. -// If the list is empty, Err returns nil. -func (p ErrorList) Err() error { - if len(p) == 0 { - return nil - } - return p -} - -// PrintError is a utility function that prints a list of errors to w, -// one error per line, if the err parameter is an ErrorList. Otherwise -// it prints the err string. -// -func PrintError(w io.Writer, err error) { - if list, ok := err.(ErrorList); ok { - for _, e := range list { - fmt.Fprintf(w, "%s\n", e) - } - } else if err != nil { - fmt.Fprintf(w, "%s\n", err) - } -} diff --git a/vendor/github.com/go-git/gcfg/scanner/scanner.go b/vendor/github.com/go-git/gcfg/scanner/scanner.go deleted file mode 100644 index b3da03d0eb2..00000000000 --- a/vendor/github.com/go-git/gcfg/scanner/scanner.go +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package scanner implements a scanner for gcfg configuration text. -// It takes a []byte as source which can then be tokenized -// through repeated calls to the Scan method. -// -// Note that the API for the scanner package may change to accommodate new -// features or implementation changes in gcfg. -package scanner - -import ( - "fmt" - "path/filepath" - "unicode" - "unicode/utf8" - - "github.com/go-git/gcfg/token" -) - -// An ErrorHandler may be provided to Scanner.Init. If a syntax error is -// encountered and a handler was installed, the handler is called with a -// position and an error message. The position points to the beginning of -// the offending token. -type ErrorHandler func(pos token.Position, msg string) - -// A Scanner holds the scanner's internal state while processing -// a given text. It can be allocated as part of another data -// structure but must be initialized via Init before use. -type Scanner struct { - // immutable state - file *token.File // source file handle - dir string // directory portion of file.Name() - src []byte // source - err ErrorHandler // error reporting; or nil - mode Mode // scanning mode - - // scanning state - ch rune // current character - offset int // character offset - rdOffset int // reading offset (position after current character) - lineOffset int // current line offset - nextVal bool // next token is expected to be a value - - // public state - ok to modify - ErrorCount int // number of errors encountered -} - -// Read the next Unicode char into s.ch. -// s.ch < 0 means end-of-file. -func (s *Scanner) next() { - if s.rdOffset < len(s.src) { - s.offset = s.rdOffset - if s.ch == '\n' { - s.lineOffset = s.offset - s.file.AddLine(s.offset) - } - r, w := rune(s.src[s.rdOffset]), 1 - switch { - case r == 0: - s.error(s.offset, "illegal character NUL") - case r >= 0x80: - // not ASCII - r, w = utf8.DecodeRune(s.src[s.rdOffset:]) - if r == utf8.RuneError && w == 1 { - s.error(s.offset, "illegal UTF-8 encoding") - } - } - s.rdOffset += w - s.ch = r - } else { - s.offset = len(s.src) - if s.ch == '\n' { - s.lineOffset = s.offset - s.file.AddLine(s.offset) - } - s.ch = -1 // eof - } -} - -// A mode value is a set of flags (or 0). -// They control scanner behavior. -type Mode uint - -const ( - ScanComments Mode = 1 << iota // return comments as COMMENT tokens -) - -// Init prepares the scanner s to tokenize the text src by setting the -// scanner at the beginning of src. The scanner uses the file set file -// for position information and it adds line information for each line. -// It is ok to re-use the same file when re-scanning the same file as -// line information which is already present is ignored. Init causes a -// panic if the file size does not match the src size. -// -// Calls to Scan will invoke the error handler err if they encounter a -// syntax error and err is not nil. Also, for each error encountered, -// the Scanner field ErrorCount is incremented by one. The mode parameter -// determines how comments are handled. -// -// Note that Init may call err if there is an error in the first character -// of the file. -func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) { - // Explicitly initialize all fields since a scanner may be reused. - if file.Size() != len(src) { - panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src))) - } - s.file = file - s.dir, _ = filepath.Split(file.Name()) - s.src = src - s.err = err - s.mode = mode - - s.ch = ' ' - s.offset = 0 - s.rdOffset = 0 - s.lineOffset = 0 - s.ErrorCount = 0 - s.nextVal = false - - s.next() -} - -func (s *Scanner) error(offs int, msg string) { - if s.err != nil { - s.err(s.file.Position(s.file.Pos(offs)), msg) - } - s.ErrorCount++ -} - -func (s *Scanner) scanComment() string { - // initial [;#] already consumed - offs := s.offset - 1 // position of initial [;#] - - for s.ch != '\n' && s.ch >= 0 { - s.next() - } - return string(s.src[offs:s.offset]) -} - -func isLetter(ch rune) bool { - return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch >= 0x80 && unicode.IsLetter(ch) -} - -func isDigit(ch rune) bool { - return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) -} - -func (s *Scanner) scanIdentifier() string { - offs := s.offset - for isLetter(s.ch) || isDigit(s.ch) || s.ch == '-' { - s.next() - } - return string(s.src[offs:s.offset]) -} - -// val indicate if we are scanning a value (vs a header) -func (s *Scanner) scanEscape(val bool) { - offs := s.offset - ch := s.ch - s.next() // always make progress - switch ch { - case '\\', '"', '\n': - // ok - case 'n', 't', 'b': - if val { - break // ok - } - fallthrough - default: - s.error(offs, "unknown escape sequence") - } -} - -func (s *Scanner) scanString() string { - // '"' opening already consumed - offs := s.offset - 1 - - for s.ch != '"' { - ch := s.ch - s.next() - if ch == '\n' || ch < 0 { - s.error(offs, "string not terminated") - break - } - if ch == '\\' { - s.scanEscape(false) - } - } - - s.next() - - return string(s.src[offs:s.offset]) -} - -func stripCR(b []byte) []byte { - c := make([]byte, len(b)) - i := 0 - for _, ch := range b { - if ch != '\r' { - c[i] = ch - i++ - } - } - return c[:i] -} - -func (s *Scanner) scanValString() string { - offs := s.offset - - hasCR := false - end := offs - inQuote := false -loop: - for inQuote || s.ch >= 0 && s.ch != '\n' && s.ch != ';' && s.ch != '#' { - ch := s.ch - s.next() - switch { - case inQuote && ch == '\\': - s.scanEscape(true) - case !inQuote && ch == '\\': - if s.ch == '\r' { - hasCR = true - s.next() - } - if s.ch != '\n' { - s.scanEscape(true) - } else { - s.next() - } - case ch == '"': - inQuote = !inQuote - case ch == '\r': - hasCR = true - case ch < 0 || inQuote && ch == '\n': - s.error(offs, "string not terminated") - break loop - } - if inQuote || !isWhiteSpace(ch) { - end = s.offset - } - } - - lit := s.src[offs:end] - if hasCR { - lit = stripCR(lit) - } - - return string(lit) -} - -func isWhiteSpace(ch rune) bool { - return ch == ' ' || ch == '\t' || ch == '\r' -} - -func (s *Scanner) skipWhitespace() { - for isWhiteSpace(s.ch) { - s.next() - } -} - -// Scan scans the next token and returns the token position, the token, -// and its literal string if applicable. The source end is indicated by -// token.EOF. -// -// If the returned token is a literal (token.IDENT, token.STRING) or -// token.COMMENT, the literal string has the corresponding value. -// -// If the returned token is token.ILLEGAL, the literal string is the -// offending character. -// -// In all other cases, Scan returns an empty literal string. -// -// For more tolerant parsing, Scan will return a valid token if -// possible even if a syntax error was encountered. Thus, even -// if the resulting token sequence contains no illegal tokens, -// a client may not assume that no error occurred. Instead it -// must check the scanner's ErrorCount or the number of calls -// of the error handler, if there was one installed. -// -// Scan adds line information to the file added to the file -// set with Init. Token positions are relative to that file -// and thus relative to the file set. -func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) { -scanAgain: - s.skipWhitespace() - - // current token start - pos = s.file.Pos(s.offset) - - // determine token value - switch ch := s.ch; { - case s.nextVal: - lit = s.scanValString() - tok = token.STRING - s.nextVal = false - case isLetter(ch): - lit = s.scanIdentifier() - tok = token.IDENT - default: - s.next() // always make progress - switch ch { - case -1: - tok = token.EOF - case '\n': - tok = token.EOL - case '"': - tok = token.STRING - lit = s.scanString() - case '[': - tok = token.LBRACK - case ']': - tok = token.RBRACK - case ';', '#': - // comment - lit = s.scanComment() - if s.mode&ScanComments == 0 { - // skip comment - goto scanAgain - } - tok = token.COMMENT - case '=': - tok = token.ASSIGN - s.nextVal = true - default: - s.error(s.file.Offset(pos), fmt.Sprintf("illegal character %#U", ch)) - tok = token.ILLEGAL - lit = string(ch) - } - } - - return -} diff --git a/vendor/github.com/go-git/gcfg/set.go b/vendor/github.com/go-git/gcfg/set.go deleted file mode 100644 index dc9795dbdb2..00000000000 --- a/vendor/github.com/go-git/gcfg/set.go +++ /dev/null @@ -1,334 +0,0 @@ -package gcfg - -import ( - "bytes" - "encoding" - "encoding/gob" - "fmt" - "math/big" - "reflect" - "strings" - "unicode" - "unicode/utf8" - - "gopkg.in/warnings.v0" - - "github.com/go-git/gcfg/types" -) - -type tag struct { - ident string - intMode string -} - -func newTag(ts string) tag { - t := tag{} - s := strings.Split(ts, ",") - t.ident = s[0] - for _, tse := range s[1:] { - if strings.HasPrefix(tse, "int=") { - t.intMode = tse[len("int="):] - } - } - return t -} - -func fieldFold(v reflect.Value, name string) (reflect.Value, tag) { - var n string - r0, _ := utf8.DecodeRuneInString(name) - if unicode.IsLetter(r0) && !unicode.IsLower(r0) && !unicode.IsUpper(r0) { - n = "X" - } - n += strings.Replace(name, "-", "_", -1) - f, ok := v.Type().FieldByNameFunc(func(fieldName string) bool { - if !v.FieldByName(fieldName).CanSet() { - return false - } - f, _ := v.Type().FieldByName(fieldName) - t := newTag(f.Tag.Get("gcfg")) - if t.ident != "" { - return strings.EqualFold(t.ident, name) - } - return strings.EqualFold(n, fieldName) - }) - if !ok { - return reflect.Value{}, tag{} - } - return v.FieldByName(f.Name), newTag(f.Tag.Get("gcfg")) -} - -type setter func(destp interface{}, blank bool, val string, t tag) error - -var errUnsupportedType = fmt.Errorf("unsupported type") -var errBlankUnsupported = fmt.Errorf("blank value not supported for type") - -var setters = []setter{ - typeSetter, textUnmarshalerSetter, kindSetter, scanSetter, -} - -func textUnmarshalerSetter(d interface{}, blank bool, val string, t tag) error { - dtu, ok := d.(encoding.TextUnmarshaler) - if !ok { - return errUnsupportedType - } - if blank { - return errBlankUnsupported - } - return dtu.UnmarshalText([]byte(val)) -} - -func boolSetter(d interface{}, blank bool, val string, t tag) error { - if blank { - reflect.ValueOf(d).Elem().Set(reflect.ValueOf(true)) - return nil - } - b, err := types.ParseBool(val) - if err == nil { - reflect.ValueOf(d).Elem().Set(reflect.ValueOf(b)) - } - return err -} - -func intMode(mode string) types.IntMode { - var m types.IntMode - if strings.ContainsAny(mode, "dD") { - m |= types.Dec - } - if strings.ContainsAny(mode, "hH") { - m |= types.Hex - } - if strings.ContainsAny(mode, "oO") { - m |= types.Oct - } - return m -} - -var typeModes = map[reflect.Type]types.IntMode{ - reflect.TypeOf(int(0)): types.Dec | types.Hex, - reflect.TypeOf(int8(0)): types.Dec | types.Hex, - reflect.TypeOf(int16(0)): types.Dec | types.Hex, - reflect.TypeOf(int32(0)): types.Dec | types.Hex, - reflect.TypeOf(int64(0)): types.Dec | types.Hex, - reflect.TypeOf(uint(0)): types.Dec | types.Hex, - reflect.TypeOf(uint8(0)): types.Dec | types.Hex, - reflect.TypeOf(uint16(0)): types.Dec | types.Hex, - reflect.TypeOf(uint32(0)): types.Dec | types.Hex, - reflect.TypeOf(uint64(0)): types.Dec | types.Hex, - // use default mode (allow dec/hex/oct) for uintptr type - reflect.TypeOf(big.Int{}): types.Dec | types.Hex, -} - -func intModeDefault(t reflect.Type) types.IntMode { - m, ok := typeModes[t] - if !ok { - m = types.Dec | types.Hex | types.Oct - } - return m -} - -func intSetter(d interface{}, blank bool, val string, t tag) error { - if blank { - return errBlankUnsupported - } - mode := intMode(t.intMode) - if mode == 0 { - mode = intModeDefault(reflect.TypeOf(d).Elem()) - } - return types.ParseInt(d, val, mode) -} - -func stringSetter(d interface{}, blank bool, val string, t tag) error { - if blank { - return errBlankUnsupported - } - dsp, ok := d.(*string) - if !ok { - return errUnsupportedType - } - *dsp = val - return nil -} - -var kindSetters = map[reflect.Kind]setter{ - reflect.String: stringSetter, - reflect.Bool: boolSetter, - reflect.Int: intSetter, - reflect.Int8: intSetter, - reflect.Int16: intSetter, - reflect.Int32: intSetter, - reflect.Int64: intSetter, - reflect.Uint: intSetter, - reflect.Uint8: intSetter, - reflect.Uint16: intSetter, - reflect.Uint32: intSetter, - reflect.Uint64: intSetter, - reflect.Uintptr: intSetter, -} - -var typeSetters = map[reflect.Type]setter{ - reflect.TypeOf(big.Int{}): intSetter, -} - -func typeSetter(d interface{}, blank bool, val string, tt tag) error { - t := reflect.ValueOf(d).Type().Elem() - setter, ok := typeSetters[t] - if !ok { - return errUnsupportedType - } - return setter(d, blank, val, tt) -} - -func kindSetter(d interface{}, blank bool, val string, tt tag) error { - k := reflect.ValueOf(d).Type().Elem().Kind() - setter, ok := kindSetters[k] - if !ok { - return errUnsupportedType - } - return setter(d, blank, val, tt) -} - -func scanSetter(d interface{}, blank bool, val string, tt tag) error { - if blank { - return errBlankUnsupported - } - return types.ScanFully(d, val, 'v') -} - -func newValue(c *warnings.Collector, sect string, vCfg reflect.Value, - vType reflect.Type) (reflect.Value, error) { - // - pv := reflect.New(vType) - dfltName := "default-" + sect - dfltField, _ := fieldFold(vCfg, dfltName) - var err error - if dfltField.IsValid() { - b := bytes.NewBuffer(nil) - ge := gob.NewEncoder(b) - if err = c.Collect(ge.EncodeValue(dfltField)); err != nil { - return pv, err - } - gd := gob.NewDecoder(bytes.NewReader(b.Bytes())) - if err = c.Collect(gd.DecodeValue(pv.Elem())); err != nil { - return pv, err - } - } - return pv, nil -} - -func set(c *warnings.Collector, cfg interface{}, sect, sub, name string, - value string, blankValue bool, subsectPass bool) error { - // - vPCfg := reflect.ValueOf(cfg) - if vPCfg.Kind() != reflect.Ptr || vPCfg.Elem().Kind() != reflect.Struct { - panic(fmt.Errorf("config must be a pointer to a struct")) - } - vCfg := vPCfg.Elem() - vSect, _ := fieldFold(vCfg, sect) - if !vSect.IsValid() { - err := extraData{section: sect} - return c.Collect(err) - } - isSubsect := vSect.Kind() == reflect.Map - if subsectPass != isSubsect { - return nil - } - if isSubsect { - vst := vSect.Type() - if vst.Key().Kind() != reflect.String || - vst.Elem().Kind() != reflect.Ptr || - vst.Elem().Elem().Kind() != reflect.Struct { - panic(fmt.Errorf("map field for section must have string keys and "+ - " pointer-to-struct values: section %q", sect)) - } - if vSect.IsNil() { - vSect.Set(reflect.MakeMap(vst)) - } - k := reflect.ValueOf(sub) - pv := vSect.MapIndex(k) - if !pv.IsValid() { - vType := vSect.Type().Elem().Elem() - var err error - if pv, err = newValue(c, sect, vCfg, vType); err != nil { - return err - } - vSect.SetMapIndex(k, pv) - } - vSect = pv.Elem() - } else if vSect.Kind() != reflect.Struct { - panic(fmt.Errorf("field for section must be a map or a struct: "+ - "section %q", sect)) - } else if sub != "" { - err := extraData{section: sect, subsection: &sub} - return c.Collect(err) - } - // Empty name is a special value, meaning that only the - // section/subsection object is to be created, with no values set. - if name == "" { - return nil - } - vVar, t := fieldFold(vSect, name) - if !vVar.IsValid() { - var err error - if isSubsect { - err = extraData{section: sect, subsection: &sub, variable: &name} - } else { - err = extraData{section: sect, variable: &name} - } - return c.Collect(err) - } - // vVal is either single-valued var, or newly allocated value within multi-valued var - var vVal reflect.Value - // multi-value if unnamed slice type - isMulti := vVar.Type().Name() == "" && vVar.Kind() == reflect.Slice || - vVar.Type().Name() == "" && vVar.Kind() == reflect.Ptr && vVar.Type().Elem().Name() == "" && vVar.Type().Elem().Kind() == reflect.Slice - if isMulti && vVar.Kind() == reflect.Ptr { - if vVar.IsNil() { - vVar.Set(reflect.New(vVar.Type().Elem())) - } - vVar = vVar.Elem() - } - if isMulti && blankValue { - vVar.Set(reflect.Zero(vVar.Type())) - return nil - } - if isMulti { - vVal = reflect.New(vVar.Type().Elem()).Elem() - } else { - vVal = vVar - } - isDeref := vVal.Type().Name() == "" && vVal.Type().Kind() == reflect.Ptr - isNew := isDeref && vVal.IsNil() - // vAddr is address of value to set (dereferenced & allocated as needed) - var vAddr reflect.Value - switch { - case isNew: - vAddr = reflect.New(vVal.Type().Elem()) - case isDeref && !isNew: - vAddr = vVal - default: - vAddr = vVal.Addr() - } - vAddrI := vAddr.Interface() - err, ok := error(nil), false - for _, s := range setters { - err = s(vAddrI, blankValue, value, t) - if err == nil { - ok = true - break - } - if err != errUnsupportedType { - return err - } - } - if !ok { - // in case all setters returned errUnsupportedType - return err - } - if isNew { // set reference if it was dereferenced and newly allocated - vVal.Set(vAddr) - } - if isMulti { // append if multi-valued - vVar.Set(reflect.Append(vVar, vVal)) - } - return nil -} diff --git a/vendor/github.com/go-git/gcfg/token/position.go b/vendor/github.com/go-git/gcfg/token/position.go deleted file mode 100644 index fc45c1e7693..00000000000 --- a/vendor/github.com/go-git/gcfg/token/position.go +++ /dev/null @@ -1,435 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// TODO(gri) consider making this a separate package outside the go directory. - -package token - -import ( - "fmt" - "sort" - "sync" -) - -// ----------------------------------------------------------------------------- -// Positions - -// Position describes an arbitrary source position -// including the file, line, and column location. -// A Position is valid if the line number is > 0. -// -type Position struct { - Filename string // filename, if any - Offset int // offset, starting at 0 - Line int // line number, starting at 1 - Column int // column number, starting at 1 (character count) -} - -// IsValid returns true if the position is valid. -func (pos *Position) IsValid() bool { return pos.Line > 0 } - -// String returns a string in one of several forms: -// -// file:line:column valid position with file name -// line:column valid position without file name -// file invalid position with file name -// - invalid position without file name -// -func (pos Position) String() string { - s := pos.Filename - if pos.IsValid() { - if s != "" { - s += ":" - } - s += fmt.Sprintf("%d:%d", pos.Line, pos.Column) - } - if s == "" { - s = "-" - } - return s -} - -// Pos is a compact encoding of a source position within a file set. -// It can be converted into a Position for a more convenient, but much -// larger, representation. -// -// The Pos value for a given file is a number in the range [base, base+size], -// where base and size are specified when adding the file to the file set via -// AddFile. -// -// To create the Pos value for a specific source offset, first add -// the respective file to the current file set (via FileSet.AddFile) -// and then call File.Pos(offset) for that file. Given a Pos value p -// for a specific file set fset, the corresponding Position value is -// obtained by calling fset.Position(p). -// -// Pos values can be compared directly with the usual comparison operators: -// If two Pos values p and q are in the same file, comparing p and q is -// equivalent to comparing the respective source file offsets. If p and q -// are in different files, p < q is true if the file implied by p was added -// to the respective file set before the file implied by q. -// -type Pos int - -// The zero value for Pos is NoPos; there is no file and line information -// associated with it, and NoPos().IsValid() is false. NoPos is always -// smaller than any other Pos value. The corresponding Position value -// for NoPos is the zero value for Position. -// -const NoPos Pos = 0 - -// IsValid returns true if the position is valid. -func (p Pos) IsValid() bool { - return p != NoPos -} - -// ----------------------------------------------------------------------------- -// File - -// A File is a handle for a file belonging to a FileSet. -// A File has a name, size, and line offset table. -// -type File struct { - set *FileSet - name string // file name as provided to AddFile - base int // Pos value range for this file is [base...base+size] - size int // file size as provided to AddFile - - // lines and infos are protected by set.mutex - lines []int - infos []lineInfo -} - -// Name returns the file name of file f as registered with AddFile. -func (f *File) Name() string { - return f.name -} - -// Base returns the base offset of file f as registered with AddFile. -func (f *File) Base() int { - return f.base -} - -// Size returns the size of file f as registered with AddFile. -func (f *File) Size() int { - return f.size -} - -// LineCount returns the number of lines in file f. -func (f *File) LineCount() int { - f.set.mutex.RLock() - n := len(f.lines) - f.set.mutex.RUnlock() - return n -} - -// AddLine adds the line offset for a new line. -// The line offset must be larger than the offset for the previous line -// and smaller than the file size; otherwise the line offset is ignored. -// -func (f *File) AddLine(offset int) { - f.set.mutex.Lock() - if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size { - f.lines = append(f.lines, offset) - } - f.set.mutex.Unlock() -} - -// SetLines sets the line offsets for a file and returns true if successful. -// The line offsets are the offsets of the first character of each line; -// for instance for the content "ab\nc\n" the line offsets are {0, 3}. -// An empty file has an empty line offset table. -// Each line offset must be larger than the offset for the previous line -// and smaller than the file size; otherwise SetLines fails and returns -// false. -// -func (f *File) SetLines(lines []int) bool { - // verify validity of lines table - size := f.size - for i, offset := range lines { - if i > 0 && offset <= lines[i-1] || size <= offset { - return false - } - } - - // set lines table - f.set.mutex.Lock() - f.lines = lines - f.set.mutex.Unlock() - return true -} - -// SetLinesForContent sets the line offsets for the given file content. -func (f *File) SetLinesForContent(content []byte) { - var lines []int - line := 0 - for offset, b := range content { - if line >= 0 { - lines = append(lines, line) - } - line = -1 - if b == '\n' { - line = offset + 1 - } - } - - // set lines table - f.set.mutex.Lock() - f.lines = lines - f.set.mutex.Unlock() -} - -// A lineInfo object describes alternative file and line number -// information (such as provided via a //line comment in a .go -// file) for a given file offset. -type lineInfo struct { - // fields are exported to make them accessible to gob - Offset int - Filename string - Line int -} - -// AddLineInfo adds alternative file and line number information for -// a given file offset. The offset must be larger than the offset for -// the previously added alternative line info and smaller than the -// file size; otherwise the information is ignored. -// -// AddLineInfo is typically used to register alternative position -// information for //line filename:line comments in source files. -// -func (f *File) AddLineInfo(offset int, filename string, line int) { - f.set.mutex.Lock() - if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size { - f.infos = append(f.infos, lineInfo{offset, filename, line}) - } - f.set.mutex.Unlock() -} - -// Pos returns the Pos value for the given file offset; -// the offset must be <= f.Size(). -// f.Pos(f.Offset(p)) == p. -// -func (f *File) Pos(offset int) Pos { - if offset > f.size { - panic("illegal file offset") - } - return Pos(f.base + offset) -} - -// Offset returns the offset for the given file position p; -// p must be a valid Pos value in that file. -// f.Offset(f.Pos(offset)) == offset. -// -func (f *File) Offset(p Pos) int { - if int(p) < f.base || int(p) > f.base+f.size { - panic("illegal Pos value") - } - return int(p) - f.base -} - -// Line returns the line number for the given file position p; -// p must be a Pos value in that file or NoPos. -// -func (f *File) Line(p Pos) int { - // TODO(gri) this can be implemented much more efficiently - return f.Position(p).Line -} - -func searchLineInfos(a []lineInfo, x int) int { - return sort.Search(len(a), func(i int) bool { return a[i].Offset > x }) - 1 -} - -// info returns the file name, line, and column number for a file offset. -func (f *File) info(offset int) (filename string, line, column int) { - filename = f.name - if i := searchInts(f.lines, offset); i >= 0 { - line, column = i+1, offset-f.lines[i]+1 - } - if len(f.infos) > 0 { - // almost no files have extra line infos - if i := searchLineInfos(f.infos, offset); i >= 0 { - alt := &f.infos[i] - filename = alt.Filename - if i := searchInts(f.lines, alt.Offset); i >= 0 { - line += alt.Line - i - 1 - } - } - } - return -} - -func (f *File) position(p Pos) (pos Position) { - offset := int(p) - f.base - pos.Offset = offset - pos.Filename, pos.Line, pos.Column = f.info(offset) - return -} - -// Position returns the Position value for the given file position p; -// p must be a Pos value in that file or NoPos. -// -func (f *File) Position(p Pos) (pos Position) { - if p != NoPos { - if int(p) < f.base || int(p) > f.base+f.size { - panic("illegal Pos value") - } - pos = f.position(p) - } - return -} - -// ----------------------------------------------------------------------------- -// FileSet - -// A FileSet represents a set of source files. -// Methods of file sets are synchronized; multiple goroutines -// may invoke them concurrently. -// -type FileSet struct { - mutex sync.RWMutex // protects the file set - base int // base offset for the next file - files []*File // list of files in the order added to the set - last *File // cache of last file looked up -} - -// NewFileSet creates a new file set. -func NewFileSet() *FileSet { - s := new(FileSet) - s.base = 1 // 0 == NoPos - return s -} - -// Base returns the minimum base offset that must be provided to -// AddFile when adding the next file. -// -func (s *FileSet) Base() int { - s.mutex.RLock() - b := s.base - s.mutex.RUnlock() - return b - -} - -// AddFile adds a new file with a given filename, base offset, and file size -// to the file set s and returns the file. Multiple files may have the same -// name. The base offset must not be smaller than the FileSet's Base(), and -// size must not be negative. -// -// Adding the file will set the file set's Base() value to base + size + 1 -// as the minimum base value for the next file. The following relationship -// exists between a Pos value p for a given file offset offs: -// -// int(p) = base + offs -// -// with offs in the range [0, size] and thus p in the range [base, base+size]. -// For convenience, File.Pos may be used to create file-specific position -// values from a file offset. -// -func (s *FileSet) AddFile(filename string, base, size int) *File { - s.mutex.Lock() - defer s.mutex.Unlock() - if base < s.base || size < 0 { - panic("illegal base or size") - } - // base >= s.base && size >= 0 - f := &File{s, filename, base, size, []int{0}, nil} - base += size + 1 // +1 because EOF also has a position - if base < 0 { - panic("token.Pos offset overflow (> 2G of source code in file set)") - } - // add the file to the file set - s.base = base - s.files = append(s.files, f) - s.last = f - return f -} - -// Iterate calls f for the files in the file set in the order they were added -// until f returns false. -// -func (s *FileSet) Iterate(f func(*File) bool) { - for i := 0; ; i++ { - var file *File - s.mutex.RLock() - if i < len(s.files) { - file = s.files[i] - } - s.mutex.RUnlock() - if file == nil || !f(file) { - break - } - } -} - -func searchFiles(a []*File, x int) int { - return sort.Search(len(a), func(i int) bool { return a[i].base > x }) - 1 -} - -func (s *FileSet) file(p Pos) *File { - // common case: p is in last file - if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size { - return f - } - // p is not in last file - search all files - if i := searchFiles(s.files, int(p)); i >= 0 { - f := s.files[i] - // f.base <= int(p) by definition of searchFiles - if int(p) <= f.base+f.size { - s.last = f - return f - } - } - return nil -} - -// File returns the file that contains the position p. -// If no such file is found (for instance for p == NoPos), -// the result is nil. -// -func (s *FileSet) File(p Pos) (f *File) { - if p != NoPos { - s.mutex.RLock() - f = s.file(p) - s.mutex.RUnlock() - } - return -} - -// Position converts a Pos in the fileset into a general Position. -func (s *FileSet) Position(p Pos) (pos Position) { - if p != NoPos { - s.mutex.RLock() - if f := s.file(p); f != nil { - pos = f.position(p) - } - s.mutex.RUnlock() - } - return -} - -// ----------------------------------------------------------------------------- -// Helper functions - -func searchInts(a []int, x int) int { - // This function body is a manually inlined version of: - // - // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1 - // - // With better compiler optimizations, this may not be needed in the - // future, but at the moment this change improves the go/printer - // benchmark performance by ~30%. This has a direct impact on the - // speed of gofmt and thus seems worthwhile (2011-04-29). - // TODO(gri): Remove this when compilers have caught up. - i, j := 0, len(a) - for i < j { - h := i + (j-i)/2 // avoid overflow when computing h - // i ≤ h < j - if a[h] <= x { - i = h + 1 - } else { - j = h - } - } - return i - 1 -} diff --git a/vendor/github.com/go-git/gcfg/token/serialize.go b/vendor/github.com/go-git/gcfg/token/serialize.go deleted file mode 100644 index 4adc8f9e334..00000000000 --- a/vendor/github.com/go-git/gcfg/token/serialize.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package token - -type serializedFile struct { - // fields correspond 1:1 to fields with same (lower-case) name in File - Name string - Base int - Size int - Lines []int - Infos []lineInfo -} - -type serializedFileSet struct { - Base int - Files []serializedFile -} - -// Read calls decode to deserialize a file set into s; s must not be nil. -func (s *FileSet) Read(decode func(interface{}) error) error { - var ss serializedFileSet - if err := decode(&ss); err != nil { - return err - } - - s.mutex.Lock() - s.base = ss.Base - files := make([]*File, len(ss.Files)) - for i := 0; i < len(ss.Files); i++ { - f := &ss.Files[i] - files[i] = &File{s, f.Name, f.Base, f.Size, f.Lines, f.Infos} - } - s.files = files - s.last = nil - s.mutex.Unlock() - - return nil -} - -// Write calls encode to serialize the file set s. -func (s *FileSet) Write(encode func(interface{}) error) error { - var ss serializedFileSet - - s.mutex.Lock() - ss.Base = s.base - files := make([]serializedFile, len(s.files)) - for i, f := range s.files { - files[i] = serializedFile{f.name, f.base, f.size, f.lines, f.infos} - } - ss.Files = files - s.mutex.Unlock() - - return encode(ss) -} diff --git a/vendor/github.com/go-git/gcfg/token/token.go b/vendor/github.com/go-git/gcfg/token/token.go deleted file mode 100644 index b3c7c83fa9e..00000000000 --- a/vendor/github.com/go-git/gcfg/token/token.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package token defines constants representing the lexical tokens of the gcfg -// configuration syntax and basic operations on tokens (printing, predicates). -// -// Note that the API for the token package may change to accommodate new -// features or implementation changes in gcfg. -// -package token - -import "strconv" - -// Token is the set of lexical tokens of the gcfg configuration syntax. -type Token int - -// The list of tokens. -const ( - // Special tokens - ILLEGAL Token = iota - EOF - COMMENT - - literal_beg - // Identifiers and basic type literals - // (these tokens stand for classes of literals) - IDENT // section-name, variable-name - STRING // "subsection-name", variable value - literal_end - - operator_beg - // Operators and delimiters - ASSIGN // = - LBRACK // [ - RBRACK // ] - EOL // \n - operator_end -) - -var tokens = [...]string{ - ILLEGAL: "ILLEGAL", - - EOF: "EOF", - COMMENT: "COMMENT", - - IDENT: "IDENT", - STRING: "STRING", - - ASSIGN: "=", - LBRACK: "[", - RBRACK: "]", - EOL: "\n", -} - -// String returns the string corresponding to the token tok. -// For operators and delimiters, the string is the actual token character -// sequence (e.g., for the token ASSIGN, the string is "="). For all other -// tokens the string corresponds to the token constant name (e.g. for the -// token IDENT, the string is "IDENT"). -// -func (tok Token) String() string { - s := "" - if 0 <= tok && tok < Token(len(tokens)) { - s = tokens[tok] - } - if s == "" { - s = "token(" + strconv.Itoa(int(tok)) + ")" - } - return s -} - -// Predicates - -// IsLiteral returns true for tokens corresponding to identifiers -// and basic type literals; it returns false otherwise. -// -func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end } - -// IsOperator returns true for tokens corresponding to operators and -// delimiters; it returns false otherwise. -// -func (tok Token) IsOperator() bool { return operator_beg < tok && tok < operator_end } diff --git a/vendor/github.com/go-git/gcfg/types/bool.go b/vendor/github.com/go-git/gcfg/types/bool.go deleted file mode 100644 index 8dcae0d8cfd..00000000000 --- a/vendor/github.com/go-git/gcfg/types/bool.go +++ /dev/null @@ -1,23 +0,0 @@ -package types - -// BoolValues defines the name and value mappings for ParseBool. -var BoolValues = map[string]interface{}{ - "true": true, "yes": true, "on": true, "1": true, - "false": false, "no": false, "off": false, "0": false, -} - -var boolParser = func() *EnumParser { - ep := &EnumParser{} - ep.AddVals(BoolValues) - return ep -}() - -// ParseBool parses bool values according to the definitions in BoolValues. -// Parsing is case-insensitive. -func ParseBool(s string) (bool, error) { - v, err := boolParser.Parse(s) - if err != nil { - return false, err - } - return v.(bool), nil -} diff --git a/vendor/github.com/go-git/gcfg/types/doc.go b/vendor/github.com/go-git/gcfg/types/doc.go deleted file mode 100644 index 9f9c345f6ea..00000000000 --- a/vendor/github.com/go-git/gcfg/types/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Package types defines helpers for type conversions. -// -// The API for this package is not finalized yet. -package types diff --git a/vendor/github.com/go-git/gcfg/types/enum.go b/vendor/github.com/go-git/gcfg/types/enum.go deleted file mode 100644 index 1a0c7ef453d..00000000000 --- a/vendor/github.com/go-git/gcfg/types/enum.go +++ /dev/null @@ -1,44 +0,0 @@ -package types - -import ( - "fmt" - "reflect" - "strings" -) - -// EnumParser parses "enum" values; i.e. a predefined set of strings to -// predefined values. -type EnumParser struct { - Type string // type name; if not set, use type of first value added - CaseMatch bool // if true, matching of strings is case-sensitive - // PrefixMatch bool - vals map[string]interface{} -} - -// AddVals adds strings and values to an EnumParser. -func (ep *EnumParser) AddVals(vals map[string]interface{}) { - if ep.vals == nil { - ep.vals = make(map[string]interface{}) - } - for k, v := range vals { - if ep.Type == "" { - ep.Type = reflect.TypeOf(v).Name() - } - if !ep.CaseMatch { - k = strings.ToLower(k) - } - ep.vals[k] = v - } -} - -// Parse parses the string and returns the value or an error. -func (ep EnumParser) Parse(s string) (interface{}, error) { - if !ep.CaseMatch { - s = strings.ToLower(s) - } - v, ok := ep.vals[s] - if !ok { - return false, fmt.Errorf("failed to parse %s %#q", ep.Type, s) - } - return v, nil -} diff --git a/vendor/github.com/go-git/gcfg/types/int.go b/vendor/github.com/go-git/gcfg/types/int.go deleted file mode 100644 index af7e75c1250..00000000000 --- a/vendor/github.com/go-git/gcfg/types/int.go +++ /dev/null @@ -1,86 +0,0 @@ -package types - -import ( - "fmt" - "strings" -) - -// An IntMode is a mode for parsing integer values, representing a set of -// accepted bases. -type IntMode uint8 - -// IntMode values for ParseInt; can be combined using binary or. -const ( - Dec IntMode = 1 << iota - Hex - Oct -) - -// String returns a string representation of IntMode; e.g. `IntMode(Dec|Hex)`. -func (m IntMode) String() string { - var modes []string - if m&Dec != 0 { - modes = append(modes, "Dec") - } - if m&Hex != 0 { - modes = append(modes, "Hex") - } - if m&Oct != 0 { - modes = append(modes, "Oct") - } - return "IntMode(" + strings.Join(modes, "|") + ")" -} - -var errIntAmbig = fmt.Errorf("ambiguous integer value; must include '0' prefix") - -func prefix0(val string) bool { - return strings.HasPrefix(val, "0") || strings.HasPrefix(val, "-0") -} - -func prefix0x(val string) bool { - return strings.HasPrefix(val, "0x") || strings.HasPrefix(val, "-0x") -} - -// ParseInt parses val using mode into intptr, which must be a pointer to an -// integer kind type. Non-decimal value require prefix `0` or `0x` in the cases -// when mode permits ambiguity of base; otherwise the prefix can be omitted. -func ParseInt(intptr interface{}, val string, mode IntMode) error { - val = strings.TrimSpace(val) - verb := byte(0) - switch mode { - case Dec: - verb = 'd' - case Dec + Hex: - if prefix0x(val) { - verb = 'v' - } else { - verb = 'd' - } - case Dec + Oct: - if prefix0(val) && !prefix0x(val) { - verb = 'v' - } else { - verb = 'd' - } - case Dec + Hex + Oct: - verb = 'v' - case Hex: - if prefix0x(val) { - verb = 'v' - } else { - verb = 'x' - } - case Oct: - verb = 'o' - case Hex + Oct: - if prefix0(val) { - verb = 'v' - } else { - return errIntAmbig - } - } - if verb == 0 { - panic("unsupported mode") - } - return ScanFully(intptr, val, verb) -} diff --git a/vendor/github.com/go-git/gcfg/types/scan.go b/vendor/github.com/go-git/gcfg/types/scan.go deleted file mode 100644 index db2f6ed3caf..00000000000 --- a/vendor/github.com/go-git/gcfg/types/scan.go +++ /dev/null @@ -1,23 +0,0 @@ -package types - -import ( - "fmt" - "io" - "reflect" -) - -// ScanFully uses fmt.Sscanf with verb to fully scan val into ptr. -func ScanFully(ptr interface{}, val string, verb byte) error { - t := reflect.ValueOf(ptr).Elem().Type() - // attempt to read extra bytes to make sure the value is consumed - var b []byte - n, err := fmt.Sscanf(val, "%"+string(verb)+"%s", ptr, &b) - switch { - case n < 1 || n == 1 && err != io.EOF: - return fmt.Errorf("failed to parse %q as %v: %v", val, t, err) - case n > 1: - return fmt.Errorf("failed to parse %q as %v: extra characters %q", val, t, string(b)) - } - // n == 1 && err == io.EOF - return nil -} diff --git a/vendor/github.com/go-git/go-billy/v5/.gitignore b/vendor/github.com/go-git/go-billy/v5/.gitignore deleted file mode 100644 index 7aeb46699cd..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/coverage.txt -/vendor -Gopkg.lock -Gopkg.toml diff --git a/vendor/github.com/go-git/go-billy/v5/LICENSE b/vendor/github.com/go-git/go-billy/v5/LICENSE deleted file mode 100644 index 9d60756894a..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2017 Sourced Technologies S.L. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/go-git/go-billy/v5/Makefile b/vendor/github.com/go-git/go-billy/v5/Makefile deleted file mode 100644 index 3c95ddeaaca..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -# Go parameters -GOCMD = go -GOTEST = $(GOCMD) test -WASIRUN_WRAPPER := $(CURDIR)/scripts/wasirun-wrapper - -.PHONY: test -test: - $(GOTEST) -race ./... - -test-coverage: - echo "" > $(COVERAGE_REPORT); \ - $(GOTEST) -coverprofile=$(COVERAGE_REPORT) -coverpkg=./... -covermode=$(COVERAGE_MODE) ./... - -.PHONY: wasitest -wasitest: export GOARCH=wasm -wasitest: export GOOS=wasip1 -wasitest: - $(GOTEST) -exec $(WASIRUN_WRAPPER) ./... diff --git a/vendor/github.com/go-git/go-billy/v5/README.md b/vendor/github.com/go-git/go-billy/v5/README.md deleted file mode 100644 index da5c074782c..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# go-billy [![GoDoc](https://godoc.org/gopkg.in/go-git/go-billy.v5?status.svg)](https://pkg.go.dev/github.com/go-git/go-billy/v5) [![Test](https://github.com/go-git/go-billy/workflows/Test/badge.svg)](https://github.com/go-git/go-billy/actions?query=workflow%3ATest) - -The missing interface filesystem abstraction for Go. -Billy implements an interface based on the `os` standard library, allowing to develop applications without dependency on the underlying storage. Makes it virtually free to implement mocks and testing over filesystem operations. - -Billy was born as part of [go-git/go-git](https://github.com/go-git/go-git) project. - -## Installation - -```go -import "github.com/go-git/go-billy/v5" // with go modules enabled (GO111MODULE=on or outside GOPATH) -import "github.com/go-git/go-billy" // with go modules disabled -``` - -## Usage - -Billy exposes filesystems using the -[`Filesystem` interface](https://pkg.go.dev/github.com/go-git/go-billy/v5?tab=doc#Filesystem). -Each filesystem implementation gives you a `New` method, whose arguments depend on -the implementation itself, that returns a new `Filesystem`. - -The following example caches in memory all readable files in a directory from any -billy's filesystem implementation. - -```go -func LoadToMemory(origin billy.Filesystem, path string) (*memory.Memory, error) { - memory := memory.New() - - files, err := origin.ReadDir("/") - if err != nil { - return nil, err - } - - for _, file := range files { - if file.IsDir() { - continue - } - - src, err := origin.Open(file.Name()) - if err != nil { - return nil, err - } - - dst, err := memory.Create(file.Name()) - if err != nil { - return nil, err - } - - if _, err = io.Copy(dst, src); err != nil { - return nil, err - } - - if err := dst.Close(); err != nil { - return nil, err - } - - if err := src.Close(); err != nil { - return nil, err - } - } - - return memory, nil -} -``` - -## Why billy? - -The library billy deals with storage systems and Billy is the name of a well-known, IKEA -bookcase. That's it. - -## License - -Apache License Version 2.0, see [LICENSE](LICENSE) diff --git a/vendor/github.com/go-git/go-billy/v5/fs.go b/vendor/github.com/go-git/go-billy/v5/fs.go deleted file mode 100644 index d86f9d82369..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/fs.go +++ /dev/null @@ -1,204 +0,0 @@ -package billy - -import ( - "errors" - "io" - "os" - "time" -) - -var ( - ErrReadOnly = errors.New("read-only filesystem") - ErrNotSupported = errors.New("feature not supported") - ErrCrossedBoundary = errors.New("chroot boundary crossed") -) - -// Capability holds the supported features of a billy filesystem. This does -// not mean that the capability has to be supported by the underlying storage. -// For example, a billy filesystem may support WriteCapability but the -// storage be mounted in read only mode. -type Capability uint64 - -const ( - // WriteCapability means that the fs is writable. - WriteCapability Capability = 1 << iota - // ReadCapability means that the fs is readable. - ReadCapability - // ReadAndWriteCapability is the ability to open a file in read and write mode. - ReadAndWriteCapability - // SeekCapability means it is able to move position inside the file. - SeekCapability - // TruncateCapability means that a file can be truncated. - TruncateCapability - // LockCapability is the ability to lock a file. - LockCapability - - // DefaultCapabilities lists all capable features supported by filesystems - // without Capability interface. This list should not be changed until a - // major version is released. - DefaultCapabilities Capability = WriteCapability | ReadCapability | - ReadAndWriteCapability | SeekCapability | TruncateCapability | - LockCapability - - // AllCapabilities lists all capable features. - AllCapabilities Capability = WriteCapability | ReadCapability | - ReadAndWriteCapability | SeekCapability | TruncateCapability | - LockCapability -) - -// Filesystem abstract the operations in a storage-agnostic interface. -// Each method implementation mimics the behavior of the equivalent functions -// at the os package from the standard library. -type Filesystem interface { - Basic - TempFile - Dir - Symlink - Chroot -} - -// Basic abstract the basic operations in a storage-agnostic interface as -// an extension to the Basic interface. -type Basic interface { - // Create creates the named file with mode 0666 (before umask), truncating - // it if it already exists. If successful, methods on the returned File can - // be used for I/O; the associated file descriptor has mode O_RDWR. - Create(filename string) (File, error) - // Open opens the named file for reading. If successful, methods on the - // returned file can be used for reading; the associated file descriptor has - // mode O_RDONLY. - Open(filename string) (File, error) - // OpenFile is the generalized open call; most users will use Open or Create - // instead. It opens the named file with specified flag (O_RDONLY etc.) and - // perm, (0666 etc.) if applicable. If successful, methods on the returned - // File can be used for I/O. - OpenFile(filename string, flag int, perm os.FileMode) (File, error) - // Stat returns a FileInfo describing the named file. - Stat(filename string) (os.FileInfo, error) - // Rename renames (moves) oldpath to newpath. If newpath already exists and - // is not a directory, Rename replaces it. OS-specific restrictions may - // apply when oldpath and newpath are in different directories. - Rename(oldpath, newpath string) error - // Remove removes the named file or directory. - Remove(filename string) error - // Join joins any number of path elements into a single path, adding a - // Separator if necessary. Join calls filepath.Clean on the result; in - // particular, all empty strings are ignored. On Windows, the result is a - // UNC path if and only if the first path element is a UNC path. - Join(elem ...string) string -} - -type TempFile interface { - // TempFile creates a new temporary file in the directory dir with a name - // beginning with prefix, opens the file for reading and writing, and - // returns the resulting *os.File. If dir is the empty string, TempFile - // uses the default directory for temporary files (see os.TempDir). - // Multiple programs calling TempFile simultaneously will not choose the - // same file. The caller can use f.Name() to find the pathname of the file. - // It is the caller's responsibility to remove the file when no longer - // needed. - TempFile(dir, prefix string) (File, error) -} - -// Dir abstract the dir related operations in a storage-agnostic interface as -// an extension to the Basic interface. -type Dir interface { - // ReadDir reads the directory named by dirname and returns a list of - // directory entries sorted by filename. - ReadDir(path string) ([]os.FileInfo, error) - // MkdirAll creates a directory named path, along with any necessary - // parents, and returns nil, or else returns an error. The permission bits - // perm are used for all directories that MkdirAll creates. If path is/ - // already a directory, MkdirAll does nothing and returns nil. - MkdirAll(filename string, perm os.FileMode) error -} - -// Symlink abstract the symlink related operations in a storage-agnostic -// interface as an extension to the Basic interface. -type Symlink interface { - // Lstat returns a FileInfo describing the named file. If the file is a - // symbolic link, the returned FileInfo describes the symbolic link. Lstat - // makes no attempt to follow the link. - Lstat(filename string) (os.FileInfo, error) - // Symlink creates a symbolic-link from link to target. target may be an - // absolute or relative path, and need not refer to an existing node. - // Parent directories of link are created as necessary. - Symlink(target, link string) error - // Readlink returns the target path of link. - Readlink(link string) (string, error) -} - -// Change abstract the FileInfo change related operations in a storage-agnostic -// interface as an extension to the Basic interface -type Change interface { - // Chmod changes the mode of the named file to mode. If the file is a - // symbolic link, it changes the mode of the link's target. - Chmod(name string, mode os.FileMode) error - // Lchown changes the numeric uid and gid of the named file. If the file is - // a symbolic link, it changes the uid and gid of the link itself. - Lchown(name string, uid, gid int) error - // Chown changes the numeric uid and gid of the named file. If the file is a - // symbolic link, it changes the uid and gid of the link's target. - Chown(name string, uid, gid int) error - // Chtimes changes the access and modification times of the named file, - // similar to the Unix utime() or utimes() functions. - // - // The underlying filesystem may truncate or round the values to a less - // precise time unit. - Chtimes(name string, atime time.Time, mtime time.Time) error -} - -// Chroot abstract the chroot related operations in a storage-agnostic interface -// as an extension to the Basic interface. -type Chroot interface { - // Chroot returns a new filesystem from the same type where the new root is - // the given path. Files outside of the designated directory tree cannot be - // accessed. - Chroot(path string) (Filesystem, error) - // Root returns the root path of the filesystem. - Root() string -} - -// File represent a file, being a subset of the os.File -type File interface { - // Name returns the name of the file as presented to Open. - Name() string - io.Writer - // TODO: Add io.WriterAt for v6 - // io.WriterAt - io.Reader - io.ReaderAt - io.Seeker - io.Closer - // Lock locks the file like e.g. flock. It protects against access from - // other processes. - Lock() error - // Unlock unlocks the file. - Unlock() error - // Truncate the file. - Truncate(size int64) error -} - -// Capable interface can return the available features of a filesystem. -type Capable interface { - // Capabilities returns the capabilities of a filesystem in bit flags. - Capabilities() Capability -} - -// Capabilities returns the features supported by a filesystem. If the FS -// does not implement Capable interface it returns all features. -func Capabilities(fs Basic) Capability { - capable, ok := fs.(Capable) - if !ok { - return DefaultCapabilities - } - - return capable.Capabilities() -} - -// CapabilityCheck tests the filesystem for the provided capabilities and -// returns true in case it supports all of them. -func CapabilityCheck(fs Basic, capabilities Capability) bool { - fsCaps := Capabilities(fs) - return fsCaps&capabilities == capabilities -} diff --git a/vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go b/vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go deleted file mode 100644 index 8b44e784bd7..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go +++ /dev/null @@ -1,242 +0,0 @@ -package chroot - -import ( - "os" - "path/filepath" - "strings" - - "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/helper/polyfill" -) - -// ChrootHelper is a helper to implement billy.Chroot. -type ChrootHelper struct { - underlying billy.Filesystem - base string -} - -// New creates a new filesystem wrapping up the given 'fs'. -// The created filesystem has its base in the given ChrootHelperectory of the -// underlying filesystem. -func New(fs billy.Basic, base string) billy.Filesystem { - return &ChrootHelper{ - underlying: polyfill.New(fs), - base: base, - } -} - -func (fs *ChrootHelper) underlyingPath(filename string) (string, error) { - if isCrossBoundaries(filename) { - return "", billy.ErrCrossedBoundary - } - - return fs.Join(fs.Root(), filename), nil -} - -func isCrossBoundaries(path string) bool { - path = filepath.ToSlash(path) - path = filepath.Clean(path) - - return strings.HasPrefix(path, ".."+string(filepath.Separator)) -} - -func (fs *ChrootHelper) Create(filename string) (billy.File, error) { - fullpath, err := fs.underlyingPath(filename) - if err != nil { - return nil, err - } - - f, err := fs.underlying.Create(fullpath) - if err != nil { - return nil, err - } - - return newFile(fs, f, filename), nil -} - -func (fs *ChrootHelper) Open(filename string) (billy.File, error) { - fullpath, err := fs.underlyingPath(filename) - if err != nil { - return nil, err - } - - f, err := fs.underlying.Open(fullpath) - if err != nil { - return nil, err - } - - return newFile(fs, f, filename), nil -} - -func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) { - fullpath, err := fs.underlyingPath(filename) - if err != nil { - return nil, err - } - - f, err := fs.underlying.OpenFile(fullpath, flag, mode) - if err != nil { - return nil, err - } - - return newFile(fs, f, filename), nil -} - -func (fs *ChrootHelper) Stat(filename string) (os.FileInfo, error) { - fullpath, err := fs.underlyingPath(filename) - if err != nil { - return nil, err - } - - return fs.underlying.Stat(fullpath) -} - -func (fs *ChrootHelper) Rename(from, to string) error { - var err error - from, err = fs.underlyingPath(from) - if err != nil { - return err - } - - to, err = fs.underlyingPath(to) - if err != nil { - return err - } - - return fs.underlying.Rename(from, to) -} - -func (fs *ChrootHelper) Remove(path string) error { - fullpath, err := fs.underlyingPath(path) - if err != nil { - return err - } - - return fs.underlying.Remove(fullpath) -} - -func (fs *ChrootHelper) Join(elem ...string) string { - return fs.underlying.Join(elem...) -} - -func (fs *ChrootHelper) TempFile(dir, prefix string) (billy.File, error) { - fullpath, err := fs.underlyingPath(dir) - if err != nil { - return nil, err - } - - f, err := fs.underlying.(billy.TempFile).TempFile(fullpath, prefix) - if err != nil { - return nil, err - } - - return newFile(fs, f, fs.Join(dir, filepath.Base(f.Name()))), nil -} - -func (fs *ChrootHelper) ReadDir(path string) ([]os.FileInfo, error) { - fullpath, err := fs.underlyingPath(path) - if err != nil { - return nil, err - } - - return fs.underlying.(billy.Dir).ReadDir(fullpath) -} - -func (fs *ChrootHelper) MkdirAll(filename string, perm os.FileMode) error { - fullpath, err := fs.underlyingPath(filename) - if err != nil { - return err - } - - return fs.underlying.(billy.Dir).MkdirAll(fullpath, perm) -} - -func (fs *ChrootHelper) Lstat(filename string) (os.FileInfo, error) { - fullpath, err := fs.underlyingPath(filename) - if err != nil { - return nil, err - } - - return fs.underlying.(billy.Symlink).Lstat(fullpath) -} - -func (fs *ChrootHelper) Symlink(target, link string) error { - target = filepath.FromSlash(target) - - // only rewrite target if it's already absolute - if filepath.IsAbs(target) || strings.HasPrefix(target, string(filepath.Separator)) { - target = fs.Join(fs.Root(), target) - target = filepath.Clean(filepath.FromSlash(target)) - } - - link, err := fs.underlyingPath(link) - if err != nil { - return err - } - - return fs.underlying.(billy.Symlink).Symlink(target, link) -} - -func (fs *ChrootHelper) Readlink(link string) (string, error) { - fullpath, err := fs.underlyingPath(link) - if err != nil { - return "", err - } - - target, err := fs.underlying.(billy.Symlink).Readlink(fullpath) - if err != nil { - return "", err - } - - if !filepath.IsAbs(target) && !strings.HasPrefix(target, string(filepath.Separator)) { - return target, nil - } - - target, err = filepath.Rel(fs.base, target) - if err != nil { - return "", err - } - - return string(os.PathSeparator) + target, nil -} - -func (fs *ChrootHelper) Chroot(path string) (billy.Filesystem, error) { - fullpath, err := fs.underlyingPath(path) - if err != nil { - return nil, err - } - - return New(fs.underlying, fullpath), nil -} - -func (fs *ChrootHelper) Root() string { - return fs.base -} - -func (fs *ChrootHelper) Underlying() billy.Basic { - return fs.underlying -} - -// Capabilities implements the Capable interface. -func (fs *ChrootHelper) Capabilities() billy.Capability { - return billy.Capabilities(fs.underlying) -} - -type file struct { - billy.File - name string -} - -func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File { - filename = fs.Join(fs.Root(), filename) - filename, _ = filepath.Rel(fs.Root(), filename) - - return &file{ - File: f, - name: filename, - } -} - -func (f *file) Name() string { - return f.name -} diff --git a/vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go b/vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go deleted file mode 100644 index 1efce0e7b8f..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go +++ /dev/null @@ -1,105 +0,0 @@ -package polyfill - -import ( - "os" - "path/filepath" - - "github.com/go-git/go-billy/v5" -) - -// Polyfill is a helper that implements all missing method from billy.Filesystem. -type Polyfill struct { - billy.Basic - c capabilities -} - -type capabilities struct{ tempfile, dir, symlink, chroot bool } - -// New creates a new filesystem wrapping up 'fs' the intercepts all the calls -// made and errors if fs doesn't implement any of the billy interfaces. -func New(fs billy.Basic) billy.Filesystem { - if original, ok := fs.(billy.Filesystem); ok { - return original - } - - h := &Polyfill{Basic: fs} - - _, h.c.tempfile = h.Basic.(billy.TempFile) - _, h.c.dir = h.Basic.(billy.Dir) - _, h.c.symlink = h.Basic.(billy.Symlink) - _, h.c.chroot = h.Basic.(billy.Chroot) - return h -} - -func (h *Polyfill) TempFile(dir, prefix string) (billy.File, error) { - if !h.c.tempfile { - return nil, billy.ErrNotSupported - } - - return h.Basic.(billy.TempFile).TempFile(dir, prefix) -} - -func (h *Polyfill) ReadDir(path string) ([]os.FileInfo, error) { - if !h.c.dir { - return nil, billy.ErrNotSupported - } - - return h.Basic.(billy.Dir).ReadDir(path) -} - -func (h *Polyfill) MkdirAll(filename string, perm os.FileMode) error { - if !h.c.dir { - return billy.ErrNotSupported - } - - return h.Basic.(billy.Dir).MkdirAll(filename, perm) -} - -func (h *Polyfill) Symlink(target, link string) error { - if !h.c.symlink { - return billy.ErrNotSupported - } - - return h.Basic.(billy.Symlink).Symlink(target, link) -} - -func (h *Polyfill) Readlink(link string) (string, error) { - if !h.c.symlink { - return "", billy.ErrNotSupported - } - - return h.Basic.(billy.Symlink).Readlink(link) -} - -func (h *Polyfill) Lstat(path string) (os.FileInfo, error) { - if !h.c.symlink { - return nil, billy.ErrNotSupported - } - - return h.Basic.(billy.Symlink).Lstat(path) -} - -func (h *Polyfill) Chroot(path string) (billy.Filesystem, error) { - if !h.c.chroot { - return nil, billy.ErrNotSupported - } - - return h.Basic.(billy.Chroot).Chroot(path) -} - -func (h *Polyfill) Root() string { - if !h.c.chroot { - return string(filepath.Separator) - } - - return h.Basic.(billy.Chroot).Root() -} - -func (h *Polyfill) Underlying() billy.Basic { - return h.Basic -} - -// Capabilities implements the Capable interface. -func (h *Polyfill) Capabilities() billy.Capability { - return billy.Capabilities(h.Basic) -} diff --git a/vendor/github.com/go-git/go-billy/v5/memfs/memory.go b/vendor/github.com/go-git/go-billy/v5/memfs/memory.go deleted file mode 100644 index 6cbd7d08ca4..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/memfs/memory.go +++ /dev/null @@ -1,424 +0,0 @@ -// Package memfs provides a billy filesystem base on memory. -package memfs // import "github.com/go-git/go-billy/v5/memfs" - -import ( - "errors" - "fmt" - "io" - "os" - "path/filepath" - "sort" - "strings" - "syscall" - "time" - - "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/helper/chroot" - "github.com/go-git/go-billy/v5/util" -) - -const separator = filepath.Separator - -var errNotLink = errors.New("not a link") - -// Memory a very convenient filesystem based on memory files. -type Memory struct { - s *storage - - tempCount int -} - -// New returns a new Memory filesystem. -func New() billy.Filesystem { - fs := &Memory{s: newStorage()} - fs.s.New("/", 0755|os.ModeDir, 0) - return chroot.New(fs, string(separator)) -} - -func (fs *Memory) Create(filename string) (billy.File, error) { - return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) -} - -func (fs *Memory) Open(filename string) (billy.File, error) { - return fs.OpenFile(filename, os.O_RDONLY, 0) -} - -func (fs *Memory) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { - f, has := fs.s.Get(filename) - if !has { - if !isCreate(flag) { - return nil, os.ErrNotExist - } - - var err error - f, err = fs.s.New(filename, perm, flag) - if err != nil { - return nil, err - } - } else { - if isExclusive(flag) { - return nil, os.ErrExist - } - - if target, isLink := fs.resolveLink(filename, f); isLink { - if target != filename { - return fs.OpenFile(target, flag, perm) - } - } - } - - if f.mode.IsDir() { - return nil, fmt.Errorf("cannot open directory: %s", filename) - } - - return f.Duplicate(filename, perm, flag), nil -} - -func (fs *Memory) resolveLink(fullpath string, f *file) (target string, isLink bool) { - if !isSymlink(f.mode) { - return fullpath, false - } - - target = string(f.content.bytes) - if !isAbs(target) { - target = fs.Join(filepath.Dir(fullpath), target) - } - - return target, true -} - -// On Windows OS, IsAbs validates if a path is valid based on if stars with a -// unit (eg.: `C:\`) to assert that is absolute, but in this mem implementation -// any path starting by `separator` is also considered absolute. -func isAbs(path string) bool { - return filepath.IsAbs(path) || strings.HasPrefix(path, string(separator)) -} - -func (fs *Memory) Stat(filename string) (os.FileInfo, error) { - f, has := fs.s.Get(filename) - if !has { - return nil, os.ErrNotExist - } - - fi, _ := f.Stat() - - var err error - if target, isLink := fs.resolveLink(filename, f); isLink { - fi, err = fs.Stat(target) - if err != nil { - return nil, err - } - } - - // the name of the file should always the name of the stated file, so we - // overwrite the Stat returned from the storage with it, since the - // filename may belong to a link. - fi.(*fileInfo).name = filepath.Base(filename) - return fi, nil -} - -func (fs *Memory) Lstat(filename string) (os.FileInfo, error) { - f, has := fs.s.Get(filename) - if !has { - return nil, os.ErrNotExist - } - - return f.Stat() -} - -type ByName []os.FileInfo - -func (a ByName) Len() int { return len(a) } -func (a ByName) Less(i, j int) bool { return a[i].Name() < a[j].Name() } -func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -func (fs *Memory) ReadDir(path string) ([]os.FileInfo, error) { - if f, has := fs.s.Get(path); has { - if target, isLink := fs.resolveLink(path, f); isLink { - if target != path { - return fs.ReadDir(target) - } - } - } else { - return nil, &os.PathError{Op: "open", Path: path, Err: syscall.ENOENT} - } - - var entries []os.FileInfo - for _, f := range fs.s.Children(path) { - fi, _ := f.Stat() - entries = append(entries, fi) - } - - sort.Sort(ByName(entries)) - - return entries, nil -} - -func (fs *Memory) MkdirAll(path string, perm os.FileMode) error { - _, err := fs.s.New(path, perm|os.ModeDir, 0) - return err -} - -func (fs *Memory) TempFile(dir, prefix string) (billy.File, error) { - return util.TempFile(fs, dir, prefix) -} - -func (fs *Memory) getTempFilename(dir, prefix string) string { - fs.tempCount++ - filename := fmt.Sprintf("%s_%d_%d", prefix, fs.tempCount, time.Now().UnixNano()) - return fs.Join(dir, filename) -} - -func (fs *Memory) Rename(from, to string) error { - return fs.s.Rename(from, to) -} - -func (fs *Memory) Remove(filename string) error { - return fs.s.Remove(filename) -} - -// Falls back to Go's filepath.Join, which works differently depending on the -// OS where the code is being executed. -func (fs *Memory) Join(elem ...string) string { - return filepath.Join(elem...) -} - -func (fs *Memory) Symlink(target, link string) error { - _, err := fs.Lstat(link) - if err == nil { - return os.ErrExist - } - - if !errors.Is(err, os.ErrNotExist) { - return err - } - - return util.WriteFile(fs, link, []byte(target), 0777|os.ModeSymlink) -} - -func (fs *Memory) Readlink(link string) (string, error) { - f, has := fs.s.Get(link) - if !has { - return "", os.ErrNotExist - } - - if !isSymlink(f.mode) { - return "", &os.PathError{ - Op: "readlink", - Path: link, - Err: fmt.Errorf("not a symlink"), - } - } - - return string(f.content.bytes), nil -} - -// Capabilities implements the Capable interface. -func (fs *Memory) Capabilities() billy.Capability { - return billy.WriteCapability | - billy.ReadCapability | - billy.ReadAndWriteCapability | - billy.SeekCapability | - billy.TruncateCapability -} - -type file struct { - name string - content *content - position int64 - flag int - mode os.FileMode - - isClosed bool -} - -func (f *file) Name() string { - return f.name -} - -func (f *file) Read(b []byte) (int, error) { - n, err := f.ReadAt(b, f.position) - f.position += int64(n) - - if errors.Is(err, io.EOF) && n != 0 { - err = nil - } - - return n, err -} - -func (f *file) ReadAt(b []byte, off int64) (int, error) { - if f.isClosed { - return 0, os.ErrClosed - } - - if !isReadAndWrite(f.flag) && !isReadOnly(f.flag) { - return 0, errors.New("read not supported") - } - - n, err := f.content.ReadAt(b, off) - - return n, err -} - -func (f *file) Seek(offset int64, whence int) (int64, error) { - if f.isClosed { - return 0, os.ErrClosed - } - - switch whence { - case io.SeekCurrent: - f.position += offset - case io.SeekStart: - f.position = offset - case io.SeekEnd: - f.position = int64(f.content.Len()) + offset - } - - return f.position, nil -} - -func (f *file) Write(p []byte) (int, error) { - return f.WriteAt(p, f.position) -} - -func (f *file) WriteAt(p []byte, off int64) (int, error) { - if f.isClosed { - return 0, os.ErrClosed - } - - if !isReadAndWrite(f.flag) && !isWriteOnly(f.flag) { - return 0, errors.New("write not supported") - } - - n, err := f.content.WriteAt(p, off) - f.position = off + int64(n) - - return n, err -} - -func (f *file) Close() error { - if f.isClosed { - return os.ErrClosed - } - - f.isClosed = true - return nil -} - -func (f *file) Truncate(size int64) error { - if size < int64(len(f.content.bytes)) { - f.content.bytes = f.content.bytes[:size] - } else if more := int(size) - len(f.content.bytes); more > 0 { - f.content.bytes = append(f.content.bytes, make([]byte, more)...) - } - - return nil -} - -func (f *file) Duplicate(filename string, mode os.FileMode, flag int) billy.File { - new := &file{ - name: filename, - content: f.content, - mode: mode, - flag: flag, - } - - if isTruncate(flag) { - new.content.Truncate() - } - - if isAppend(flag) { - new.position = int64(new.content.Len()) - } - - return new -} - -func (f *file) Stat() (os.FileInfo, error) { - return &fileInfo{ - name: f.Name(), - mode: f.mode, - size: f.content.Len(), - }, nil -} - -// Lock is a no-op in memfs. -func (f *file) Lock() error { - return nil -} - -// Unlock is a no-op in memfs. -func (f *file) Unlock() error { - return nil -} - -type fileInfo struct { - name string - size int - mode os.FileMode -} - -func (fi *fileInfo) Name() string { - return fi.name -} - -func (fi *fileInfo) Size() int64 { - return int64(fi.size) -} - -func (fi *fileInfo) Mode() os.FileMode { - return fi.mode -} - -func (*fileInfo) ModTime() time.Time { - return time.Now() -} - -func (fi *fileInfo) IsDir() bool { - return fi.mode.IsDir() -} - -func (*fileInfo) Sys() interface{} { - return nil -} - -func (c *content) Truncate() { - c.bytes = make([]byte, 0) -} - -func (c *content) Len() int { - return len(c.bytes) -} - -func isCreate(flag int) bool { - return flag&os.O_CREATE != 0 -} - -func isExclusive(flag int) bool { - return flag&os.O_EXCL != 0 -} - -func isAppend(flag int) bool { - return flag&os.O_APPEND != 0 -} - -func isTruncate(flag int) bool { - return flag&os.O_TRUNC != 0 -} - -func isReadAndWrite(flag int) bool { - return flag&os.O_RDWR != 0 -} - -func isReadOnly(flag int) bool { - return flag == os.O_RDONLY -} - -func isWriteOnly(flag int) bool { - return flag&os.O_WRONLY != 0 -} - -func isSymlink(m os.FileMode) bool { - return m&os.ModeSymlink != 0 -} diff --git a/vendor/github.com/go-git/go-billy/v5/memfs/storage.go b/vendor/github.com/go-git/go-billy/v5/memfs/storage.go deleted file mode 100644 index 16b48ce0026..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/memfs/storage.go +++ /dev/null @@ -1,239 +0,0 @@ -package memfs - -import ( - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "sync" -) - -type storage struct { - files map[string]*file - children map[string]map[string]*file -} - -func newStorage() *storage { - return &storage{ - files: make(map[string]*file, 0), - children: make(map[string]map[string]*file, 0), - } -} - -func (s *storage) Has(path string) bool { - path = clean(path) - - _, ok := s.files[path] - return ok -} - -func (s *storage) New(path string, mode os.FileMode, flag int) (*file, error) { - path = clean(path) - if s.Has(path) { - if !s.MustGet(path).mode.IsDir() { - return nil, fmt.Errorf("file already exists %q", path) - } - - return nil, nil - } - - name := filepath.Base(path) - - f := &file{ - name: name, - content: &content{name: name}, - mode: mode, - flag: flag, - } - - s.files[path] = f - s.createParent(path, mode, f) - return f, nil -} - -func (s *storage) createParent(path string, mode os.FileMode, f *file) error { - base := filepath.Dir(path) - base = clean(base) - if f.Name() == string(separator) { - return nil - } - - if _, err := s.New(base, mode.Perm()|os.ModeDir, 0); err != nil { - return err - } - - if _, ok := s.children[base]; !ok { - s.children[base] = make(map[string]*file, 0) - } - - s.children[base][f.Name()] = f - return nil -} - -func (s *storage) Children(path string) []*file { - path = clean(path) - - l := make([]*file, 0) - for _, f := range s.children[path] { - l = append(l, f) - } - - return l -} - -func (s *storage) MustGet(path string) *file { - f, ok := s.Get(path) - if !ok { - panic(fmt.Errorf("couldn't find %q", path)) - } - - return f -} - -func (s *storage) Get(path string) (*file, bool) { - path = clean(path) - if !s.Has(path) { - return nil, false - } - - file, ok := s.files[path] - return file, ok -} - -func (s *storage) Rename(from, to string) error { - from = clean(from) - to = clean(to) - - if !s.Has(from) { - return os.ErrNotExist - } - - move := [][2]string{{from, to}} - - for pathFrom := range s.files { - if pathFrom == from || !strings.HasPrefix(pathFrom, from) { - continue - } - - rel, _ := filepath.Rel(from, pathFrom) - pathTo := filepath.Join(to, rel) - - move = append(move, [2]string{pathFrom, pathTo}) - } - - for _, ops := range move { - from := ops[0] - to := ops[1] - - if err := s.move(from, to); err != nil { - return err - } - } - - return nil -} - -func (s *storage) move(from, to string) error { - s.files[to] = s.files[from] - s.files[to].name = filepath.Base(to) - s.children[to] = s.children[from] - - defer func() { - delete(s.children, from) - delete(s.files, from) - delete(s.children[filepath.Dir(from)], filepath.Base(from)) - }() - - return s.createParent(to, 0644, s.files[to]) -} - -func (s *storage) Remove(path string) error { - path = clean(path) - - f, has := s.Get(path) - if !has { - return os.ErrNotExist - } - - if f.mode.IsDir() && len(s.children[path]) != 0 { - return fmt.Errorf("dir: %s contains files", path) - } - - base, file := filepath.Split(path) - base = filepath.Clean(base) - - delete(s.children[base], file) - delete(s.files, path) - return nil -} - -func clean(path string) string { - return filepath.Clean(filepath.FromSlash(path)) -} - -type content struct { - name string - bytes []byte - - m sync.RWMutex -} - -func (c *content) WriteAt(p []byte, off int64) (int, error) { - if off < 0 { - return 0, &os.PathError{ - Op: "writeat", - Path: c.name, - Err: errors.New("negative offset"), - } - } - - c.m.Lock() - prev := len(c.bytes) - - diff := int(off) - prev - if diff > 0 { - c.bytes = append(c.bytes, make([]byte, diff)...) - } - - c.bytes = append(c.bytes[:off], p...) - if len(c.bytes) < prev { - c.bytes = c.bytes[:prev] - } - c.m.Unlock() - - return len(p), nil -} - -func (c *content) ReadAt(b []byte, off int64) (n int, err error) { - if off < 0 { - return 0, &os.PathError{ - Op: "readat", - Path: c.name, - Err: errors.New("negative offset"), - } - } - - c.m.RLock() - size := int64(len(c.bytes)) - if off >= size { - c.m.RUnlock() - return 0, io.EOF - } - - l := int64(len(b)) - if off+l > size { - l = size - off - } - - btr := c.bytes[off : off+l] - n = copy(b, btr) - - if len(btr) < len(b) { - err = io.EOF - } - c.m.RUnlock() - - return -} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os.go b/vendor/github.com/go-git/go-billy/v5/osfs/os.go deleted file mode 100644 index a7fe79f2f6a..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/osfs/os.go +++ /dev/null @@ -1,127 +0,0 @@ -//go:build !js -// +build !js - -// Package osfs provides a billy filesystem for the OS. -package osfs - -import ( - "fmt" - "io/fs" - "os" - "sync" - - "github.com/go-git/go-billy/v5" -) - -const ( - defaultDirectoryMode = 0o755 - defaultCreateMode = 0o666 -) - -// Default Filesystem representing the root of the os filesystem. -var Default = &ChrootOS{} - -// New returns a new OS filesystem. -// By default paths are deduplicated, but still enforced -// under baseDir. For more info refer to WithDeduplicatePath. -func New(baseDir string, opts ...Option) billy.Filesystem { - o := &options{ - deduplicatePath: true, - } - for _, opt := range opts { - opt(o) - } - - if o.Type == BoundOSFS { - return newBoundOS(baseDir, o.deduplicatePath) - } - - return newChrootOS(baseDir) -} - -// WithBoundOS returns the option of using a Bound filesystem OS. -func WithBoundOS() Option { - return func(o *options) { - o.Type = BoundOSFS - } -} - -// WithChrootOS returns the option of using a Chroot filesystem OS. -func WithChrootOS() Option { - return func(o *options) { - o.Type = ChrootOSFS - } -} - -// WithDeduplicatePath toggles the deduplication of the base dir in the path. -// This occurs when absolute links are being used. -// Assuming base dir /base/dir and an absolute symlink /base/dir/target: -// -// With DeduplicatePath (default): /base/dir/target -// Without DeduplicatePath: /base/dir/base/dir/target -// -// This option is only used by the BoundOS OS type. -func WithDeduplicatePath(enabled bool) Option { - return func(o *options) { - o.deduplicatePath = enabled - } -} - -type options struct { - Type - deduplicatePath bool -} - -type Type int - -const ( - ChrootOSFS Type = iota - BoundOSFS -) - -func readDir(dir string) ([]os.FileInfo, error) { - entries, err := os.ReadDir(dir) - if err != nil { - return nil, err - } - infos := make([]fs.FileInfo, 0, len(entries)) - for _, entry := range entries { - fi, err := entry.Info() - if err != nil { - return nil, err - } - infos = append(infos, fi) - } - return infos, nil -} - -func tempFile(dir, prefix string) (billy.File, error) { - f, err := os.CreateTemp(dir, prefix) - if err != nil { - return nil, err - } - return &file{File: f}, nil -} - -func openFile(fn string, flag int, perm os.FileMode, createDir func(string) error) (billy.File, error) { - if flag&os.O_CREATE != 0 { - if createDir == nil { - return nil, fmt.Errorf("createDir func cannot be nil if file needs to be opened in create mode") - } - if err := createDir(fn); err != nil { - return nil, err - } - } - - f, err := os.OpenFile(fn, flag, perm) - if err != nil { - return nil, err - } - return &file{File: f}, err -} - -// file is a wrapper for an os.File which adds support for file locking. -type file struct { - *os.File - m sync.Mutex -} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go deleted file mode 100644 index c0a61099018..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go +++ /dev/null @@ -1,265 +0,0 @@ -//go:build !js -// +build !js - -/* - Copyright 2022 The Flux authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package osfs - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/go-git/go-billy/v5" -) - -// BoundOS is a fs implementation based on the OS filesystem which is bound to -// a base dir. -// Prefer this fs implementation over ChrootOS. -// -// Behaviours of note: -// 1. Read and write operations can only be directed to files which descends -// from the base dir. -// 2. Symlinks don't have their targets modified, and therefore can point -// to locations outside the base dir or to non-existent paths. -// 3. Readlink and Lstat ensures that the link file is located within the base -// dir, evaluating any symlinks that file or base dir may contain. -type BoundOS struct { - baseDir string - deduplicatePath bool -} - -func newBoundOS(d string, deduplicatePath bool) billy.Filesystem { - return &BoundOS{baseDir: d, deduplicatePath: deduplicatePath} -} - -func (fs *BoundOS) Create(filename string) (billy.File, error) { - return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode) -} - -func (fs *BoundOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { - fn, err := fs.abs(filename) - if err != nil { - return nil, err - } - return openFile(fn, flag, perm, fs.createDir) -} - -func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, error) { - dir, err := fs.abs(path) - if err != nil { - return nil, err - } - - return readDir(dir) -} - -func (fs *BoundOS) Rename(from, to string) error { - f, err := fs.abs(from) - if err != nil { - return err - } - t, err := fs.abs(to) - if err != nil { - return err - } - - // MkdirAll for target name. - if err := fs.createDir(t); err != nil { - return err - } - - return os.Rename(f, t) -} - -func (fs *BoundOS) MkdirAll(path string, perm os.FileMode) error { - dir, err := fs.abs(path) - if err != nil { - return err - } - return os.MkdirAll(dir, perm) -} - -func (fs *BoundOS) Open(filename string) (billy.File, error) { - return fs.OpenFile(filename, os.O_RDONLY, 0) -} - -func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) { - filename, err := fs.abs(filename) - if err != nil { - return nil, err - } - return os.Stat(filename) -} - -func (fs *BoundOS) Remove(filename string) error { - fn, err := fs.abs(filename) - if err != nil { - return err - } - return os.Remove(fn) -} - -// TempFile creates a temporary file. If dir is empty, the file -// will be created within the OS Temporary dir. If dir is provided -// it must descend from the current base dir. -func (fs *BoundOS) TempFile(dir, prefix string) (billy.File, error) { - if dir != "" { - var err error - dir, err = fs.abs(dir) - if err != nil { - return nil, err - } - } - - return tempFile(dir, prefix) -} - -func (fs *BoundOS) Join(elem ...string) string { - return filepath.Join(elem...) -} - -func (fs *BoundOS) RemoveAll(path string) error { - dir, err := fs.abs(path) - if err != nil { - return err - } - return os.RemoveAll(dir) -} - -func (fs *BoundOS) Symlink(target, link string) error { - ln, err := fs.abs(link) - if err != nil { - return err - } - // MkdirAll for containing dir. - if err := fs.createDir(ln); err != nil { - return err - } - return os.Symlink(target, ln) -} - -func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) { - filename = filepath.Clean(filename) - if !filepath.IsAbs(filename) { - filename = filepath.Join(fs.baseDir, filename) - } - if ok, err := fs.insideBaseDirEval(filename); !ok { - return nil, err - } - return os.Lstat(filename) -} - -func (fs *BoundOS) Readlink(link string) (string, error) { - if !filepath.IsAbs(link) { - link = filepath.Clean(filepath.Join(fs.baseDir, link)) - } - if ok, err := fs.insideBaseDirEval(link); !ok { - return "", err - } - return os.Readlink(link) -} - -// Chroot returns a new OS filesystem, with the base dir set to the -// result of joining the provided path with the underlying base dir. -func (fs *BoundOS) Chroot(path string) (billy.Filesystem, error) { - joined, err := securejoin.SecureJoin(fs.baseDir, path) - if err != nil { - return nil, err - } - return New(joined), nil -} - -// Root returns the current base dir of the billy.Filesystem. -// This is required in order for this implementation to be a drop-in -// replacement for other upstream implementations (e.g. memory and osfs). -func (fs *BoundOS) Root() string { - return fs.baseDir -} - -func (fs *BoundOS) createDir(fullpath string) error { - dir := filepath.Dir(fullpath) - if dir != "." { - if err := os.MkdirAll(dir, defaultDirectoryMode); err != nil { - return err - } - } - - return nil -} - -// abs transforms filename to an absolute path, taking into account the base dir. -// Relative paths won't be allowed to ascend the base dir, so `../file` will become -// `/working-dir/file`. -// -// Note that if filename is a symlink, the returned address will be the target of the -// symlink. -func (fs *BoundOS) abs(filename string) (string, error) { - if filename == fs.baseDir { - filename = string(filepath.Separator) - } - - path, err := securejoin.SecureJoin(fs.baseDir, filename) - if err != nil { - return "", nil - } - - if fs.deduplicatePath { - vol := filepath.VolumeName(fs.baseDir) - dup := filepath.Join(fs.baseDir, fs.baseDir[len(vol):]) - if strings.HasPrefix(path, dup+string(filepath.Separator)) { - return fs.abs(path[len(dup):]) - } - } - return path, nil -} - -// insideBaseDir checks whether filename is located within -// the fs.baseDir. -func (fs *BoundOS) insideBaseDir(filename string) (bool, error) { - if filename == fs.baseDir { - return true, nil - } - if !strings.HasPrefix(filename, fs.baseDir+string(filepath.Separator)) { - return false, fmt.Errorf("path outside base dir") - } - return true, nil -} - -// insideBaseDirEval checks whether filename is contained within -// a dir that is within the fs.baseDir, by first evaluating any symlinks -// that either filename or fs.baseDir may contain. -func (fs *BoundOS) insideBaseDirEval(filename string) (bool, error) { - // "/" contains all others. - if fs.baseDir == "/" { - return true, nil - } - dir, err := filepath.EvalSymlinks(filepath.Dir(filename)) - if dir == "" || os.IsNotExist(err) { - dir = filepath.Dir(filename) - } - wd, err := filepath.EvalSymlinks(fs.baseDir) - if wd == "" || os.IsNotExist(err) { - wd = fs.baseDir - } - if filename != wd && dir != wd && !strings.HasPrefix(dir, wd+string(filepath.Separator)) { - return false, fmt.Errorf("%q: path outside base dir %q: %w", filename, fs.baseDir, os.ErrNotExist) - } - return true, nil -} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go deleted file mode 100644 index fd65e773c4b..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go +++ /dev/null @@ -1,112 +0,0 @@ -//go:build !js -// +build !js - -package osfs - -import ( - "os" - "path/filepath" - - "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/helper/chroot" -) - -// ChrootOS is a legacy filesystem based on a "soft chroot" of the os filesystem. -// Although this is still the default os filesystem, consider using BoundOS instead. -// -// Behaviours of note: -// 1. A "soft chroot" translates the base dir to "/" for the purposes of the -// fs abstraction. -// 2. Symlinks targets may be modified to be kept within the chroot bounds. -// 3. Some file modes does not pass-through the fs abstraction. -// 4. The combination of 1 and 2 may cause go-git to think that a Git repository -// is dirty, when in fact it isn't. -type ChrootOS struct{} - -func newChrootOS(baseDir string) billy.Filesystem { - return chroot.New(&ChrootOS{}, baseDir) -} - -func (fs *ChrootOS) Create(filename string) (billy.File, error) { - return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode) -} - -func (fs *ChrootOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { - return openFile(filename, flag, perm, fs.createDir) -} - -func (fs *ChrootOS) createDir(fullpath string) error { - dir := filepath.Dir(fullpath) - if dir != "." { - if err := os.MkdirAll(dir, defaultDirectoryMode); err != nil { - return err - } - } - - return nil -} - -func (fs *ChrootOS) ReadDir(dir string) ([]os.FileInfo, error) { - return readDir(dir) -} - -func (fs *ChrootOS) Rename(from, to string) error { - if err := fs.createDir(to); err != nil { - return err - } - - return rename(from, to) -} - -func (fs *ChrootOS) MkdirAll(path string, perm os.FileMode) error { - return os.MkdirAll(path, defaultDirectoryMode) -} - -func (fs *ChrootOS) Open(filename string) (billy.File, error) { - return fs.OpenFile(filename, os.O_RDONLY, 0) -} - -func (fs *ChrootOS) Stat(filename string) (os.FileInfo, error) { - return os.Stat(filename) -} - -func (fs *ChrootOS) Remove(filename string) error { - return os.Remove(filename) -} - -func (fs *ChrootOS) TempFile(dir, prefix string) (billy.File, error) { - if err := fs.createDir(dir + string(os.PathSeparator)); err != nil { - return nil, err - } - - return tempFile(dir, prefix) -} - -func (fs *ChrootOS) Join(elem ...string) string { - return filepath.Join(elem...) -} - -func (fs *ChrootOS) RemoveAll(path string) error { - return os.RemoveAll(filepath.Clean(path)) -} - -func (fs *ChrootOS) Lstat(filename string) (os.FileInfo, error) { - return os.Lstat(filepath.Clean(filename)) -} - -func (fs *ChrootOS) Symlink(target, link string) error { - if err := fs.createDir(link); err != nil { - return err - } - - return os.Symlink(target, link) -} - -func (fs *ChrootOS) Readlink(link string) (string, error) { - return os.Readlink(link) -} - -// Capabilities implements the Capable interface. -func (fs *ChrootOS) Capabilities() billy.Capability { - return billy.DefaultCapabilities -} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_js.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_js.go deleted file mode 100644 index 2e58aa5c610..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/osfs/os_js.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build js -// +build js - -package osfs - -import ( - "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/helper/chroot" - "github.com/go-git/go-billy/v5/memfs" -) - -// globalMemFs is the global memory fs -var globalMemFs = memfs.New() - -// Default Filesystem representing the root of in-memory filesystem for a -// js/wasm environment. -var Default = memfs.New() - -// New returns a new OS filesystem. -func New(baseDir string, _ ...Option) billy.Filesystem { - return chroot.New(Default, Default.Join("/", baseDir)) -} - -type options struct { -} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_options.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_options.go deleted file mode 100644 index 2f235c6ddcd..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/osfs/os_options.go +++ /dev/null @@ -1,3 +0,0 @@ -package osfs - -type Option func(*options) diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_plan9.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_plan9.go deleted file mode 100644 index 84020b52f11..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/osfs/os_plan9.go +++ /dev/null @@ -1,91 +0,0 @@ -//go:build plan9 -// +build plan9 - -package osfs - -import ( - "io" - "os" - "path/filepath" - "syscall" -) - -func (f *file) Lock() error { - // Plan 9 uses a mode bit instead of explicit lock/unlock syscalls. - // - // Per http://man.cat-v.org/plan_9/5/stat: “Exclusive use files may be open - // for I/O by only one fid at a time across all clients of the server. If a - // second open is attempted, it draws an error.” - // - // There is no obvious way to implement this function using the exclusive use bit. - // See https://golang.org/src/cmd/go/internal/lockedfile/lockedfile_plan9.go - // for how file locking is done by the go tool on Plan 9. - return nil -} - -func (f *file) Unlock() error { - return nil -} - -func rename(from, to string) error { - // If from and to are in different directories, copy the file - // since Plan 9 does not support cross-directory rename. - if filepath.Dir(from) != filepath.Dir(to) { - fi, err := os.Stat(from) - if err != nil { - return &os.LinkError{"rename", from, to, err} - } - if fi.Mode().IsDir() { - return &os.LinkError{"rename", from, to, syscall.EISDIR} - } - fromFile, err := os.Open(from) - if err != nil { - return &os.LinkError{"rename", from, to, err} - } - toFile, err := os.OpenFile(to, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode()) - if err != nil { - return &os.LinkError{"rename", from, to, err} - } - _, err = io.Copy(toFile, fromFile) - if err != nil { - return &os.LinkError{"rename", from, to, err} - } - - // Copy mtime and mode from original file. - // We need only one syscall if we avoid os.Chmod and os.Chtimes. - dir := fi.Sys().(*syscall.Dir) - var d syscall.Dir - d.Null() - d.Mtime = dir.Mtime - d.Mode = dir.Mode - if err = dirwstat(to, &d); err != nil { - return &os.LinkError{"rename", from, to, err} - } - - // Remove original file. - err = os.Remove(from) - if err != nil { - return &os.LinkError{"rename", from, to, err} - } - return nil - } - return os.Rename(from, to) -} - -func dirwstat(name string, d *syscall.Dir) error { - var buf [syscall.STATFIXLEN]byte - - n, err := d.Marshal(buf[:]) - if err != nil { - return &os.PathError{"dirwstat", name, err} - } - if err = syscall.Wstat(name, buf[:n]); err != nil { - return &os.PathError{"dirwstat", name, err} - } - return nil -} - -func umask(new int) func() { - return func() { - } -} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_posix.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_posix.go deleted file mode 100644 index 6fb8273f17a..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/osfs/os_posix.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build !plan9 && !windows && !wasm -// +build !plan9,!windows,!wasm - -package osfs - -import ( - "os" - "syscall" - - "golang.org/x/sys/unix" -) - -func (f *file) Lock() error { - f.m.Lock() - defer f.m.Unlock() - - return unix.Flock(int(f.File.Fd()), unix.LOCK_EX) -} - -func (f *file) Unlock() error { - f.m.Lock() - defer f.m.Unlock() - - return unix.Flock(int(f.File.Fd()), unix.LOCK_UN) -} - -func rename(from, to string) error { - return os.Rename(from, to) -} - -// umask sets umask to a new value, and returns a func which allows the -// caller to reset it back to what it was originally. -func umask(new int) func() { - old := syscall.Umask(new) - return func() { - syscall.Umask(old) - } -} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_wasip1.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_wasip1.go deleted file mode 100644 index 79e6e331929..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/osfs/os_wasip1.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build wasip1 -// +build wasip1 - -package osfs - -import ( - "os" - "syscall" -) - -func (f *file) Lock() error { - f.m.Lock() - defer f.m.Unlock() - return nil -} - -func (f *file) Unlock() error { - f.m.Lock() - defer f.m.Unlock() - return nil -} - -func rename(from, to string) error { - return os.Rename(from, to) -} - -// umask sets umask to a new value, and returns a func which allows the -// caller to reset it back to what it was originally. -func umask(new int) func() { - old := syscall.Umask(new) - return func() { - syscall.Umask(old) - } -} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_windows.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_windows.go deleted file mode 100644 index e54df748e5f..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/osfs/os_windows.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build windows -// +build windows - -package osfs - -import ( - "os" - "runtime" - "unsafe" - - "golang.org/x/sys/windows" -) - -var ( - kernel32DLL = windows.NewLazySystemDLL("kernel32.dll") - lockFileExProc = kernel32DLL.NewProc("LockFileEx") - unlockFileProc = kernel32DLL.NewProc("UnlockFile") -) - -const ( - lockfileExclusiveLock = 0x2 -) - -func (f *file) Lock() error { - f.m.Lock() - defer f.m.Unlock() - - var overlapped windows.Overlapped - // err is always non-nil as per sys/windows semantics. - ret, _, err := lockFileExProc.Call(f.File.Fd(), lockfileExclusiveLock, 0, 0xFFFFFFFF, 0, - uintptr(unsafe.Pointer(&overlapped))) - runtime.KeepAlive(&overlapped) - if ret == 0 { - return err - } - return nil -} - -func (f *file) Unlock() error { - f.m.Lock() - defer f.m.Unlock() - - // err is always non-nil as per sys/windows semantics. - ret, _, err := unlockFileProc.Call(f.File.Fd(), 0, 0, 0xFFFFFFFF, 0) - if ret == 0 { - return err - } - return nil -} - -func rename(from, to string) error { - return os.Rename(from, to) -} - -func umask(new int) func() { - return func() { - } -} diff --git a/vendor/github.com/go-git/go-billy/v5/util/glob.go b/vendor/github.com/go-git/go-billy/v5/util/glob.go deleted file mode 100644 index f7cb1de8966..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/util/glob.go +++ /dev/null @@ -1,111 +0,0 @@ -package util - -import ( - "path/filepath" - "sort" - "strings" - - "github.com/go-git/go-billy/v5" -) - -// Glob returns the names of all files matching pattern or nil -// if there is no matching file. The syntax of patterns is the same -// as in Match. The pattern may describe hierarchical names such as -// /usr/*/bin/ed (assuming the Separator is '/'). -// -// Glob ignores file system errors such as I/O errors reading directories. -// The only possible returned error is ErrBadPattern, when pattern -// is malformed. -// -// Function originally from https://golang.org/src/path/filepath/match_test.go -func Glob(fs billy.Filesystem, pattern string) (matches []string, err error) { - if !hasMeta(pattern) { - if _, err = fs.Lstat(pattern); err != nil { - return nil, nil - } - return []string{pattern}, nil - } - - dir, file := filepath.Split(pattern) - // Prevent infinite recursion. See issue 15879. - if dir == pattern { - return nil, filepath.ErrBadPattern - } - - var m []string - m, err = Glob(fs, cleanGlobPath(dir)) - if err != nil { - return - } - for _, d := range m { - matches, err = glob(fs, d, file, matches) - if err != nil { - return - } - } - return -} - -// cleanGlobPath prepares path for glob matching. -func cleanGlobPath(path string) string { - switch path { - case "": - return "." - case string(filepath.Separator): - // do nothing to the path - return path - default: - return path[0 : len(path)-1] // chop off trailing separator - } -} - -// glob searches for files matching pattern in the directory dir -// and appends them to matches. If the directory cannot be -// opened, it returns the existing matches. New matches are -// added in lexicographical order. -func glob(fs billy.Filesystem, dir, pattern string, matches []string) (m []string, e error) { - m = matches - fi, err := fs.Stat(dir) - if err != nil { - return - } - - if !fi.IsDir() { - return - } - - names, _ := readdirnames(fs, dir) - sort.Strings(names) - - for _, n := range names { - matched, err := filepath.Match(pattern, n) - if err != nil { - return m, err - } - if matched { - m = append(m, filepath.Join(dir, n)) - } - } - return -} - -// hasMeta reports whether path contains any of the magic characters -// recognized by Match. -func hasMeta(path string) bool { - // TODO(niemeyer): Should other magic characters be added here? - return strings.ContainsAny(path, "*?[") -} - -func readdirnames(fs billy.Filesystem, dir string) ([]string, error) { - files, err := fs.ReadDir(dir) - if err != nil { - return nil, err - } - - var names []string - for _, file := range files { - names = append(names, file.Name()) - } - - return names, nil -} diff --git a/vendor/github.com/go-git/go-billy/v5/util/util.go b/vendor/github.com/go-git/go-billy/v5/util/util.go deleted file mode 100644 index 2cdd832c73f..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/util/util.go +++ /dev/null @@ -1,287 +0,0 @@ -package util - -import ( - "errors" - "io" - "os" - "path/filepath" - "strconv" - "sync" - "time" - - "github.com/go-git/go-billy/v5" -) - -// RemoveAll removes path and any children it contains. It removes everything it -// can but returns the first error it encounters. If the path does not exist, -// RemoveAll returns nil (no error). -func RemoveAll(fs billy.Basic, path string) error { - fs, path = getUnderlyingAndPath(fs, path) - - if r, ok := fs.(removerAll); ok { - return r.RemoveAll(path) - } - - return removeAll(fs, path) -} - -type removerAll interface { - RemoveAll(string) error -} - -func removeAll(fs billy.Basic, path string) error { - // This implementation is adapted from os.RemoveAll. - - // Simple case: if Remove works, we're done. - err := fs.Remove(path) - if err == nil || errors.Is(err, os.ErrNotExist) { - return nil - } - - // Otherwise, is this a directory we need to recurse into? - dir, serr := fs.Stat(path) - if serr != nil { - if errors.Is(serr, os.ErrNotExist) { - return nil - } - - return serr - } - - if !dir.IsDir() { - // Not a directory; return the error from Remove. - return err - } - - dirfs, ok := fs.(billy.Dir) - if !ok { - return billy.ErrNotSupported - } - - // Directory. - fis, err := dirfs.ReadDir(path) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - // Race. It was deleted between the Lstat and Open. - // Return nil per RemoveAll's docs. - return nil - } - - return err - } - - // Remove contents & return first error. - err = nil - for _, fi := range fis { - cpath := fs.Join(path, fi.Name()) - err1 := removeAll(fs, cpath) - if err == nil { - err = err1 - } - } - - // Remove directory. - err1 := fs.Remove(path) - if err1 == nil || errors.Is(err1, os.ErrNotExist) { - return nil - } - - if err == nil { - err = err1 - } - - return err - -} - -// WriteFile writes data to a file named by filename in the given filesystem. -// If the file does not exist, WriteFile creates it with permissions perm; -// otherwise WriteFile truncates it before writing. -func WriteFile(fs billy.Basic, filename string, data []byte, perm os.FileMode) (err error) { - f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) - if err != nil { - return err - } - defer func() { - if f != nil { - err1 := f.Close() - if err == nil { - err = err1 - } - } - }() - - n, err := f.Write(data) - if err == nil && n < len(data) { - err = io.ErrShortWrite - } - - return nil -} - -// Random number state. -// We generate random temporary file names so that there's a good -// chance the file doesn't exist yet - keeps the number of tries in -// TempFile to a minimum. -var rand uint32 -var randmu sync.Mutex - -func reseed() uint32 { - return uint32(time.Now().UnixNano() + int64(os.Getpid())) -} - -func nextSuffix() string { - randmu.Lock() - r := rand - if r == 0 { - r = reseed() - } - r = r*1664525 + 1013904223 // constants from Numerical Recipes - rand = r - randmu.Unlock() - return strconv.Itoa(int(1e9 + r%1e9))[1:] -} - -// TempFile creates a new temporary file in the directory dir with a name -// beginning with prefix, opens the file for reading and writing, and returns -// the resulting *os.File. If dir is the empty string, TempFile uses the default -// directory for temporary files (see os.TempDir). Multiple programs calling -// TempFile simultaneously will not choose the same file. The caller can use -// f.Name() to find the pathname of the file. It is the caller's responsibility -// to remove the file when no longer needed. -func TempFile(fs billy.Basic, dir, prefix string) (f billy.File, err error) { - // This implementation is based on stdlib ioutil.TempFile. - if dir == "" { - dir = getTempDir(fs) - } - - nconflict := 0 - for i := 0; i < 10000; i++ { - name := filepath.Join(dir, prefix+nextSuffix()) - f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) - if errors.Is(err, os.ErrExist) { - if nconflict++; nconflict > 10 { - randmu.Lock() - rand = reseed() - randmu.Unlock() - } - continue - } - break - } - return -} - -// TempDir creates a new temporary directory in the directory dir -// with a name beginning with prefix and returns the path of the -// new directory. If dir is the empty string, TempDir uses the -// default directory for temporary files (see os.TempDir). -// Multiple programs calling TempDir simultaneously -// will not choose the same directory. It is the caller's responsibility -// to remove the directory when no longer needed. -func TempDir(fs billy.Dir, dir, prefix string) (name string, err error) { - // This implementation is based on stdlib ioutil.TempDir - - if dir == "" { - dir = getTempDir(fs.(billy.Basic)) - } - - nconflict := 0 - for i := 0; i < 10000; i++ { - try := filepath.Join(dir, prefix+nextSuffix()) - err = fs.MkdirAll(try, 0700) - if errors.Is(err, os.ErrExist) { - if nconflict++; nconflict > 10 { - randmu.Lock() - rand = reseed() - randmu.Unlock() - } - continue - } - if errors.Is(err, os.ErrNotExist) { - if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { - return "", err - } - } - if err == nil { - name = try - } - break - } - return -} - -func getTempDir(fs billy.Basic) string { - ch, ok := fs.(billy.Chroot) - if !ok || ch.Root() == "" || ch.Root() == "/" || ch.Root() == string(filepath.Separator) { - return os.TempDir() - } - - return ".tmp" -} - -type underlying interface { - Underlying() billy.Basic -} - -func getUnderlyingAndPath(fs billy.Basic, path string) (billy.Basic, string) { - u, ok := fs.(underlying) - if !ok { - return fs, path - } - if ch, ok := fs.(billy.Chroot); ok { - path = fs.Join(ch.Root(), path) - } - - return u.Underlying(), path -} - -// ReadFile reads the named file and returns the contents from the given filesystem. -// A successful call returns err == nil, not err == EOF. -// Because ReadFile reads the whole file, it does not treat an EOF from Read -// as an error to be reported. -func ReadFile(fs billy.Basic, name string) ([]byte, error) { - f, err := fs.Open(name) - if err != nil { - return nil, err - } - - defer f.Close() - - var size int - if info, err := fs.Stat(name); err == nil { - size64 := info.Size() - if int64(int(size64)) == size64 { - size = int(size64) - } - } - - size++ // one byte for final read at EOF - // If a file claims a small size, read at least 512 bytes. - // In particular, files in Linux's /proc claim size 0 but - // then do not work right if read in small pieces, - // so an initial read of 1 byte would not work correctly. - - if size < 512 { - size = 512 - } - - data := make([]byte, 0, size) - for { - if len(data) >= cap(data) { - d := append(data[:cap(data)], 0) - data = d[:len(data)] - } - - n, err := f.Read(data[len(data):cap(data)]) - data = data[:len(data)+n] - - if err != nil { - if errors.Is(err, io.EOF) { - err = nil - } - - return data, err - } - } -} diff --git a/vendor/github.com/go-git/go-billy/v5/util/walk.go b/vendor/github.com/go-git/go-billy/v5/util/walk.go deleted file mode 100644 index 1531bcaaaeb..00000000000 --- a/vendor/github.com/go-git/go-billy/v5/util/walk.go +++ /dev/null @@ -1,72 +0,0 @@ -package util - -import ( - "os" - "path/filepath" - - "github.com/go-git/go-billy/v5" -) - -// walk recursively descends path, calling walkFn -// adapted from https://golang.org/src/path/filepath/path.go -func walk(fs billy.Filesystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error { - if !info.IsDir() { - return walkFn(path, info, nil) - } - - names, err := readdirnames(fs, path) - err1 := walkFn(path, info, err) - // If err != nil, walk can't walk into this directory. - // err1 != nil means walkFn want walk to skip this directory or stop walking. - // Therefore, if one of err and err1 isn't nil, walk will return. - if err != nil || err1 != nil { - // The caller's behavior is controlled by the return value, which is decided - // by walkFn. walkFn may ignore err and return nil. - // If walkFn returns SkipDir, it will be handled by the caller. - // So walk should return whatever walkFn returns. - return err1 - } - - for _, name := range names { - filename := filepath.Join(path, name) - fileInfo, err := fs.Lstat(filename) - if err != nil { - if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { - return err - } - } else { - err = walk(fs, filename, fileInfo, walkFn) - if err != nil { - if !fileInfo.IsDir() || err != filepath.SkipDir { - return err - } - } - } - } - return nil -} - -// Walk walks the file tree rooted at root, calling fn for each file or -// directory in the tree, including root. All errors that arise visiting files -// and directories are filtered by fn: see the WalkFunc documentation for -// details. -// -// The files are walked in lexical order, which makes the output deterministic -// but requires Walk to read an entire directory into memory before proceeding -// to walk that directory. Walk does not follow symbolic links. -// -// Function adapted from https://github.com/golang/go/blob/3b770f2ccb1fa6fecc22ea822a19447b10b70c5c/src/path/filepath/path.go#L500 -func Walk(fs billy.Filesystem, root string, walkFn filepath.WalkFunc) error { - info, err := fs.Lstat(root) - if err != nil { - err = walkFn(root, nil, err) - } else { - err = walk(fs, root, info, walkFn) - } - - if err == filepath.SkipDir { - return nil - } - - return err -} diff --git a/vendor/github.com/go-logfmt/logfmt/.gitignore b/vendor/github.com/go-logfmt/logfmt/.gitignore deleted file mode 100644 index 1d74e21965c..00000000000 --- a/vendor/github.com/go-logfmt/logfmt/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.vscode/ diff --git a/vendor/github.com/go-logfmt/logfmt/.travis.yml b/vendor/github.com/go-logfmt/logfmt/.travis.yml deleted file mode 100644 index 51393098987..00000000000 --- a/vendor/github.com/go-logfmt/logfmt/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: go -sudo: false -go: - - "1.7.x" - - "1.8.x" - - "1.9.x" - - "1.10.x" - - "1.11.x" - - "1.12.x" - - "1.13.x" - - "tip" - -before_install: - - go get github.com/mattn/goveralls - - go get golang.org/x/tools/cmd/cover - -script: - - goveralls -service=travis-ci diff --git a/vendor/github.com/go-logfmt/logfmt/CHANGELOG.md b/vendor/github.com/go-logfmt/logfmt/CHANGELOG.md deleted file mode 100644 index 1a9a27bcf6e..00000000000 --- a/vendor/github.com/go-logfmt/logfmt/CHANGELOG.md +++ /dev/null @@ -1,48 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [0.5.0] - 2020-01-03 - -### Changed -- Remove the dependency on github.com/kr/logfmt by [@ChrisHines] -- Move fuzz code to github.com/go-logfmt/fuzzlogfmt by [@ChrisHines] - -## [0.4.0] - 2018-11-21 - -### Added -- Go module support by [@ChrisHines] -- CHANGELOG by [@ChrisHines] - -### Changed -- Drop invalid runes from keys instead of returning ErrInvalidKey by [@ChrisHines] -- On panic while printing, attempt to print panic value by [@bboreham] - -## [0.3.0] - 2016-11-15 -### Added -- Pool buffers for quoted strings and byte slices by [@nussjustin] -### Fixed -- Fuzz fix, quote invalid UTF-8 values by [@judwhite] - -## [0.2.0] - 2016-05-08 -### Added -- Encoder.EncodeKeyvals by [@ChrisHines] - -## [0.1.0] - 2016-03-28 -### Added -- Encoder by [@ChrisHines] -- Decoder by [@ChrisHines] -- MarshalKeyvals by [@ChrisHines] - -[0.5.0]: https://github.com/go-logfmt/logfmt/compare/v0.4.0...v0.5.0 -[0.4.0]: https://github.com/go-logfmt/logfmt/compare/v0.3.0...v0.4.0 -[0.3.0]: https://github.com/go-logfmt/logfmt/compare/v0.2.0...v0.3.0 -[0.2.0]: https://github.com/go-logfmt/logfmt/compare/v0.1.0...v0.2.0 -[0.1.0]: https://github.com/go-logfmt/logfmt/commits/v0.1.0 - -[@ChrisHines]: https://github.com/ChrisHines -[@bboreham]: https://github.com/bboreham -[@judwhite]: https://github.com/judwhite -[@nussjustin]: https://github.com/nussjustin diff --git a/vendor/github.com/go-logfmt/logfmt/LICENSE b/vendor/github.com/go-logfmt/logfmt/LICENSE deleted file mode 100644 index c026508962b..00000000000 --- a/vendor/github.com/go-logfmt/logfmt/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 go-logfmt - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/vendor/github.com/go-logfmt/logfmt/README.md b/vendor/github.com/go-logfmt/logfmt/README.md deleted file mode 100644 index 3a8f10b76cb..00000000000 --- a/vendor/github.com/go-logfmt/logfmt/README.md +++ /dev/null @@ -1,33 +0,0 @@ -[![GoDoc](https://godoc.org/github.com/go-logfmt/logfmt?status.svg)](https://godoc.org/github.com/go-logfmt/logfmt) -[![Go Report Card](https://goreportcard.com/badge/go-logfmt/logfmt)](https://goreportcard.com/report/go-logfmt/logfmt) -[![TravisCI](https://travis-ci.org/go-logfmt/logfmt.svg?branch=master)](https://travis-ci.org/go-logfmt/logfmt) -[![Coverage Status](https://coveralls.io/repos/github/go-logfmt/logfmt/badge.svg?branch=master)](https://coveralls.io/github/go-logfmt/logfmt?branch=master) - -# logfmt - -Package logfmt implements utilities to marshal and unmarshal data in the [logfmt -format](https://brandur.org/logfmt). It provides an API similar to -[encoding/json](http://golang.org/pkg/encoding/json/) and -[encoding/xml](http://golang.org/pkg/encoding/xml/). - -The logfmt format was first documented by Brandur Leach in [this -article](https://brandur.org/logfmt). The format has not been formally -standardized. The most authoritative public specification to date has been the -documentation of a Go Language [package](http://godoc.org/github.com/kr/logfmt) -written by Blake Mizerany and Keith Rarick. - -## Goals - -This project attempts to conform as closely as possible to the prior art, while -also removing ambiguity where necessary to provide well behaved encoder and -decoder implementations. - -## Non-goals - -This project does not attempt to formally standardize the logfmt format. In the -event that logfmt is standardized this project would take conforming to the -standard as a goal. - -## Versioning - -Package logfmt publishes releases via [semver](http://semver.org/) compatible Git tags prefixed with a single 'v'. diff --git a/vendor/github.com/go-logfmt/logfmt/decode.go b/vendor/github.com/go-logfmt/logfmt/decode.go deleted file mode 100644 index 2013708e485..00000000000 --- a/vendor/github.com/go-logfmt/logfmt/decode.go +++ /dev/null @@ -1,237 +0,0 @@ -package logfmt - -import ( - "bufio" - "bytes" - "fmt" - "io" - "unicode/utf8" -) - -// A Decoder reads and decodes logfmt records from an input stream. -type Decoder struct { - pos int - key []byte - value []byte - lineNum int - s *bufio.Scanner - err error -} - -// NewDecoder returns a new decoder that reads from r. -// -// The decoder introduces its own buffering and may read data from r beyond -// the logfmt records requested. -func NewDecoder(r io.Reader) *Decoder { - dec := &Decoder{ - s: bufio.NewScanner(r), - } - return dec -} - -// ScanRecord advances the Decoder to the next record, which can then be -// parsed with the ScanKeyval method. It returns false when decoding stops, -// either by reaching the end of the input or an error. After ScanRecord -// returns false, the Err method will return any error that occurred during -// decoding, except that if it was io.EOF, Err will return nil. -func (dec *Decoder) ScanRecord() bool { - if dec.err != nil { - return false - } - if !dec.s.Scan() { - dec.err = dec.s.Err() - return false - } - dec.lineNum++ - dec.pos = 0 - return true -} - -// ScanKeyval advances the Decoder to the next key/value pair of the current -// record, which can then be retrieved with the Key and Value methods. It -// returns false when decoding stops, either by reaching the end of the -// current record or an error. -func (dec *Decoder) ScanKeyval() bool { - dec.key, dec.value = nil, nil - if dec.err != nil { - return false - } - - line := dec.s.Bytes() - - // garbage - for p, c := range line[dec.pos:] { - if c > ' ' { - dec.pos += p - goto key - } - } - dec.pos = len(line) - return false - -key: - const invalidKeyError = "invalid key" - - start, multibyte := dec.pos, false - for p, c := range line[dec.pos:] { - switch { - case c == '=': - dec.pos += p - if dec.pos > start { - dec.key = line[start:dec.pos] - if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) { - dec.syntaxError(invalidKeyError) - return false - } - } - if dec.key == nil { - dec.unexpectedByte(c) - return false - } - goto equal - case c == '"': - dec.pos += p - dec.unexpectedByte(c) - return false - case c <= ' ': - dec.pos += p - if dec.pos > start { - dec.key = line[start:dec.pos] - if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) { - dec.syntaxError(invalidKeyError) - return false - } - } - return true - case c >= utf8.RuneSelf: - multibyte = true - } - } - dec.pos = len(line) - if dec.pos > start { - dec.key = line[start:dec.pos] - if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) { - dec.syntaxError(invalidKeyError) - return false - } - } - return true - -equal: - dec.pos++ - if dec.pos >= len(line) { - return true - } - switch c := line[dec.pos]; { - case c <= ' ': - return true - case c == '"': - goto qvalue - } - - // value - start = dec.pos - for p, c := range line[dec.pos:] { - switch { - case c == '=' || c == '"': - dec.pos += p - dec.unexpectedByte(c) - return false - case c <= ' ': - dec.pos += p - if dec.pos > start { - dec.value = line[start:dec.pos] - } - return true - } - } - dec.pos = len(line) - if dec.pos > start { - dec.value = line[start:dec.pos] - } - return true - -qvalue: - const ( - untermQuote = "unterminated quoted value" - invalidQuote = "invalid quoted value" - ) - - hasEsc, esc := false, false - start = dec.pos - for p, c := range line[dec.pos+1:] { - switch { - case esc: - esc = false - case c == '\\': - hasEsc, esc = true, true - case c == '"': - dec.pos += p + 2 - if hasEsc { - v, ok := unquoteBytes(line[start:dec.pos]) - if !ok { - dec.syntaxError(invalidQuote) - return false - } - dec.value = v - } else { - start++ - end := dec.pos - 1 - if end > start { - dec.value = line[start:end] - } - } - return true - } - } - dec.pos = len(line) - dec.syntaxError(untermQuote) - return false -} - -// Key returns the most recent key found by a call to ScanKeyval. The returned -// slice may point to internal buffers and is only valid until the next call -// to ScanRecord. It does no allocation. -func (dec *Decoder) Key() []byte { - return dec.key -} - -// Value returns the most recent value found by a call to ScanKeyval. The -// returned slice may point to internal buffers and is only valid until the -// next call to ScanRecord. It does no allocation when the value has no -// escape sequences. -func (dec *Decoder) Value() []byte { - return dec.value -} - -// Err returns the first non-EOF error that was encountered by the Scanner. -func (dec *Decoder) Err() error { - return dec.err -} - -func (dec *Decoder) syntaxError(msg string) { - dec.err = &SyntaxError{ - Msg: msg, - Line: dec.lineNum, - Pos: dec.pos + 1, - } -} - -func (dec *Decoder) unexpectedByte(c byte) { - dec.err = &SyntaxError{ - Msg: fmt.Sprintf("unexpected %q", c), - Line: dec.lineNum, - Pos: dec.pos + 1, - } -} - -// A SyntaxError represents a syntax error in the logfmt input stream. -type SyntaxError struct { - Msg string - Line int - Pos int -} - -func (e *SyntaxError) Error() string { - return fmt.Sprintf("logfmt syntax error at pos %d on line %d: %s", e.Pos, e.Line, e.Msg) -} diff --git a/vendor/github.com/go-logfmt/logfmt/doc.go b/vendor/github.com/go-logfmt/logfmt/doc.go deleted file mode 100644 index 378e9ad126a..00000000000 --- a/vendor/github.com/go-logfmt/logfmt/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Package logfmt implements utilities to marshal and unmarshal data in the -// logfmt format. The logfmt format records key/value pairs in a way that -// balances readability for humans and simplicity of computer parsing. It is -// most commonly used as a more human friendly alternative to JSON for -// structured logging. -package logfmt diff --git a/vendor/github.com/go-logfmt/logfmt/encode.go b/vendor/github.com/go-logfmt/logfmt/encode.go deleted file mode 100644 index 4ea9d23998c..00000000000 --- a/vendor/github.com/go-logfmt/logfmt/encode.go +++ /dev/null @@ -1,322 +0,0 @@ -package logfmt - -import ( - "bytes" - "encoding" - "errors" - "fmt" - "io" - "reflect" - "strings" - "unicode/utf8" -) - -// MarshalKeyvals returns the logfmt encoding of keyvals, a variadic sequence -// of alternating keys and values. -func MarshalKeyvals(keyvals ...interface{}) ([]byte, error) { - buf := &bytes.Buffer{} - if err := NewEncoder(buf).EncodeKeyvals(keyvals...); err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// An Encoder writes logfmt data to an output stream. -type Encoder struct { - w io.Writer - scratch bytes.Buffer - needSep bool -} - -// NewEncoder returns a new encoder that writes to w. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{ - w: w, - } -} - -var ( - space = []byte(" ") - equals = []byte("=") - newline = []byte("\n") - null = []byte("null") -) - -// EncodeKeyval writes the logfmt encoding of key and value to the stream. A -// single space is written before the second and subsequent keys in a record. -// Nothing is written if a non-nil error is returned. -func (enc *Encoder) EncodeKeyval(key, value interface{}) error { - enc.scratch.Reset() - if enc.needSep { - if _, err := enc.scratch.Write(space); err != nil { - return err - } - } - if err := writeKey(&enc.scratch, key); err != nil { - return err - } - if _, err := enc.scratch.Write(equals); err != nil { - return err - } - if err := writeValue(&enc.scratch, value); err != nil { - return err - } - _, err := enc.w.Write(enc.scratch.Bytes()) - enc.needSep = true - return err -} - -// EncodeKeyvals writes the logfmt encoding of keyvals to the stream. Keyvals -// is a variadic sequence of alternating keys and values. Keys of unsupported -// type are skipped along with their corresponding value. Values of -// unsupported type or that cause a MarshalerError are replaced by their error -// but do not cause EncodeKeyvals to return an error. If a non-nil error is -// returned some key/value pairs may not have be written. -func (enc *Encoder) EncodeKeyvals(keyvals ...interface{}) error { - if len(keyvals) == 0 { - return nil - } - if len(keyvals)%2 == 1 { - keyvals = append(keyvals, nil) - } - for i := 0; i < len(keyvals); i += 2 { - k, v := keyvals[i], keyvals[i+1] - err := enc.EncodeKeyval(k, v) - if err == ErrUnsupportedKeyType { - continue - } - if _, ok := err.(*MarshalerError); ok || err == ErrUnsupportedValueType { - v = err - err = enc.EncodeKeyval(k, v) - } - if err != nil { - return err - } - } - return nil -} - -// MarshalerError represents an error encountered while marshaling a value. -type MarshalerError struct { - Type reflect.Type - Err error -} - -func (e *MarshalerError) Error() string { - return "error marshaling value of type " + e.Type.String() + ": " + e.Err.Error() -} - -// ErrNilKey is returned by Marshal functions and Encoder methods if a key is -// a nil interface or pointer value. -var ErrNilKey = errors.New("nil key") - -// ErrInvalidKey is returned by Marshal functions and Encoder methods if, after -// dropping invalid runes, a key is empty. -var ErrInvalidKey = errors.New("invalid key") - -// ErrUnsupportedKeyType is returned by Encoder methods if a key has an -// unsupported type. -var ErrUnsupportedKeyType = errors.New("unsupported key type") - -// ErrUnsupportedValueType is returned by Encoder methods if a value has an -// unsupported type. -var ErrUnsupportedValueType = errors.New("unsupported value type") - -func writeKey(w io.Writer, key interface{}) error { - if key == nil { - return ErrNilKey - } - - switch k := key.(type) { - case string: - return writeStringKey(w, k) - case []byte: - if k == nil { - return ErrNilKey - } - return writeBytesKey(w, k) - case encoding.TextMarshaler: - kb, err := safeMarshal(k) - if err != nil { - return err - } - if kb == nil { - return ErrNilKey - } - return writeBytesKey(w, kb) - case fmt.Stringer: - ks, ok := safeString(k) - if !ok { - return ErrNilKey - } - return writeStringKey(w, ks) - default: - rkey := reflect.ValueOf(key) - switch rkey.Kind() { - case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Struct: - return ErrUnsupportedKeyType - case reflect.Ptr: - if rkey.IsNil() { - return ErrNilKey - } - return writeKey(w, rkey.Elem().Interface()) - } - return writeStringKey(w, fmt.Sprint(k)) - } -} - -// keyRuneFilter returns r for all valid key runes, and -1 for all invalid key -// runes. When used as the mapping function for strings.Map and bytes.Map -// functions it causes them to remove invalid key runes from strings or byte -// slices respectively. -func keyRuneFilter(r rune) rune { - if r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError { - return -1 - } - return r -} - -func writeStringKey(w io.Writer, key string) error { - k := strings.Map(keyRuneFilter, key) - if k == "" { - return ErrInvalidKey - } - _, err := io.WriteString(w, k) - return err -} - -func writeBytesKey(w io.Writer, key []byte) error { - k := bytes.Map(keyRuneFilter, key) - if len(k) == 0 { - return ErrInvalidKey - } - _, err := w.Write(k) - return err -} - -func writeValue(w io.Writer, value interface{}) error { - switch v := value.(type) { - case nil: - return writeBytesValue(w, null) - case string: - return writeStringValue(w, v, true) - case []byte: - return writeBytesValue(w, v) - case encoding.TextMarshaler: - vb, err := safeMarshal(v) - if err != nil { - return err - } - if vb == nil { - vb = null - } - return writeBytesValue(w, vb) - case error: - se, ok := safeError(v) - return writeStringValue(w, se, ok) - case fmt.Stringer: - ss, ok := safeString(v) - return writeStringValue(w, ss, ok) - default: - rvalue := reflect.ValueOf(value) - switch rvalue.Kind() { - case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Struct: - return ErrUnsupportedValueType - case reflect.Ptr: - if rvalue.IsNil() { - return writeBytesValue(w, null) - } - return writeValue(w, rvalue.Elem().Interface()) - } - return writeStringValue(w, fmt.Sprint(v), true) - } -} - -func needsQuotedValueRune(r rune) bool { - return r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError -} - -func writeStringValue(w io.Writer, value string, ok bool) error { - var err error - if ok && value == "null" { - _, err = io.WriteString(w, `"null"`) - } else if strings.IndexFunc(value, needsQuotedValueRune) != -1 { - _, err = writeQuotedString(w, value) - } else { - _, err = io.WriteString(w, value) - } - return err -} - -func writeBytesValue(w io.Writer, value []byte) error { - var err error - if bytes.IndexFunc(value, needsQuotedValueRune) != -1 { - _, err = writeQuotedBytes(w, value) - } else { - _, err = w.Write(value) - } - return err -} - -// EndRecord writes a newline character to the stream and resets the encoder -// to the beginning of a new record. -func (enc *Encoder) EndRecord() error { - _, err := enc.w.Write(newline) - if err == nil { - enc.needSep = false - } - return err -} - -// Reset resets the encoder to the beginning of a new record. -func (enc *Encoder) Reset() { - enc.needSep = false -} - -func safeError(err error) (s string, ok bool) { - defer func() { - if panicVal := recover(); panicVal != nil { - if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { - s, ok = "null", false - } else { - s, ok = fmt.Sprintf("PANIC:%v", panicVal), false - } - } - }() - s, ok = err.Error(), true - return -} - -func safeString(str fmt.Stringer) (s string, ok bool) { - defer func() { - if panicVal := recover(); panicVal != nil { - if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { - s, ok = "null", false - } else { - s, ok = fmt.Sprintf("PANIC:%v", panicVal), true - } - } - }() - s, ok = str.String(), true - return -} - -func safeMarshal(tm encoding.TextMarshaler) (b []byte, err error) { - defer func() { - if panicVal := recover(); panicVal != nil { - if v := reflect.ValueOf(tm); v.Kind() == reflect.Ptr && v.IsNil() { - b, err = nil, nil - } else { - b, err = nil, fmt.Errorf("panic when marshalling: %s", panicVal) - } - } - }() - b, err = tm.MarshalText() - if err != nil { - return nil, &MarshalerError{ - Type: reflect.TypeOf(tm), - Err: err, - } - } - return -} diff --git a/vendor/github.com/go-logfmt/logfmt/jsonstring.go b/vendor/github.com/go-logfmt/logfmt/jsonstring.go deleted file mode 100644 index 030ac85fcc2..00000000000 --- a/vendor/github.com/go-logfmt/logfmt/jsonstring.go +++ /dev/null @@ -1,277 +0,0 @@ -package logfmt - -import ( - "bytes" - "io" - "strconv" - "sync" - "unicode" - "unicode/utf16" - "unicode/utf8" -) - -// Taken from Go's encoding/json and modified for use here. - -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -var hex = "0123456789abcdef" - -var bufferPool = sync.Pool{ - New: func() interface{} { - return &bytes.Buffer{} - }, -} - -func getBuffer() *bytes.Buffer { - return bufferPool.Get().(*bytes.Buffer) -} - -func poolBuffer(buf *bytes.Buffer) { - buf.Reset() - bufferPool.Put(buf) -} - -// NOTE: keep in sync with writeQuotedBytes below. -func writeQuotedString(w io.Writer, s string) (int, error) { - buf := getBuffer() - buf.WriteByte('"') - start := 0 - for i := 0; i < len(s); { - if b := s[i]; b < utf8.RuneSelf { - if 0x20 <= b && b != '\\' && b != '"' { - i++ - continue - } - if start < i { - buf.WriteString(s[start:i]) - } - switch b { - case '\\', '"': - buf.WriteByte('\\') - buf.WriteByte(b) - case '\n': - buf.WriteByte('\\') - buf.WriteByte('n') - case '\r': - buf.WriteByte('\\') - buf.WriteByte('r') - case '\t': - buf.WriteByte('\\') - buf.WriteByte('t') - default: - // This encodes bytes < 0x20 except for \n, \r, and \t. - buf.WriteString(`\u00`) - buf.WriteByte(hex[b>>4]) - buf.WriteByte(hex[b&0xF]) - } - i++ - start = i - continue - } - c, size := utf8.DecodeRuneInString(s[i:]) - if c == utf8.RuneError { - if start < i { - buf.WriteString(s[start:i]) - } - buf.WriteString(`\ufffd`) - i += size - start = i - continue - } - i += size - } - if start < len(s) { - buf.WriteString(s[start:]) - } - buf.WriteByte('"') - n, err := w.Write(buf.Bytes()) - poolBuffer(buf) - return n, err -} - -// NOTE: keep in sync with writeQuoteString above. -func writeQuotedBytes(w io.Writer, s []byte) (int, error) { - buf := getBuffer() - buf.WriteByte('"') - start := 0 - for i := 0; i < len(s); { - if b := s[i]; b < utf8.RuneSelf { - if 0x20 <= b && b != '\\' && b != '"' { - i++ - continue - } - if start < i { - buf.Write(s[start:i]) - } - switch b { - case '\\', '"': - buf.WriteByte('\\') - buf.WriteByte(b) - case '\n': - buf.WriteByte('\\') - buf.WriteByte('n') - case '\r': - buf.WriteByte('\\') - buf.WriteByte('r') - case '\t': - buf.WriteByte('\\') - buf.WriteByte('t') - default: - // This encodes bytes < 0x20 except for \n, \r, and \t. - buf.WriteString(`\u00`) - buf.WriteByte(hex[b>>4]) - buf.WriteByte(hex[b&0xF]) - } - i++ - start = i - continue - } - c, size := utf8.DecodeRune(s[i:]) - if c == utf8.RuneError { - if start < i { - buf.Write(s[start:i]) - } - buf.WriteString(`\ufffd`) - i += size - start = i - continue - } - i += size - } - if start < len(s) { - buf.Write(s[start:]) - } - buf.WriteByte('"') - n, err := w.Write(buf.Bytes()) - poolBuffer(buf) - return n, err -} - -// getu4 decodes \uXXXX from the beginning of s, returning the hex value, -// or it returns -1. -func getu4(s []byte) rune { - if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { - return -1 - } - r, err := strconv.ParseUint(string(s[2:6]), 16, 64) - if err != nil { - return -1 - } - return rune(r) -} - -func unquoteBytes(s []byte) (t []byte, ok bool) { - if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { - return - } - s = s[1 : len(s)-1] - - // Check for unusual characters. If there are none, - // then no unquoting is needed, so return a slice of the - // original bytes. - r := 0 - for r < len(s) { - c := s[r] - if c == '\\' || c == '"' || c < ' ' { - break - } - if c < utf8.RuneSelf { - r++ - continue - } - rr, size := utf8.DecodeRune(s[r:]) - if rr == utf8.RuneError { - break - } - r += size - } - if r == len(s) { - return s, true - } - - b := make([]byte, len(s)+2*utf8.UTFMax) - w := copy(b, s[0:r]) - for r < len(s) { - // Out of room? Can only happen if s is full of - // malformed UTF-8 and we're replacing each - // byte with RuneError. - if w >= len(b)-2*utf8.UTFMax { - nb := make([]byte, (len(b)+utf8.UTFMax)*2) - copy(nb, b[0:w]) - b = nb - } - switch c := s[r]; { - case c == '\\': - r++ - if r >= len(s) { - return - } - switch s[r] { - default: - return - case '"', '\\', '/', '\'': - b[w] = s[r] - r++ - w++ - case 'b': - b[w] = '\b' - r++ - w++ - case 'f': - b[w] = '\f' - r++ - w++ - case 'n': - b[w] = '\n' - r++ - w++ - case 'r': - b[w] = '\r' - r++ - w++ - case 't': - b[w] = '\t' - r++ - w++ - case 'u': - r-- - rr := getu4(s[r:]) - if rr < 0 { - return - } - r += 6 - if utf16.IsSurrogate(rr) { - rr1 := getu4(s[r:]) - if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { - // A valid pair; consume. - r += 6 - w += utf8.EncodeRune(b[w:], dec) - break - } - // Invalid surrogate; fall back to replacement rune. - rr = unicode.ReplacementChar - } - w += utf8.EncodeRune(b[w:], rr) - } - - // Quote, control characters are invalid. - case c == '"', c < ' ': - return - - // ASCII - case c < utf8.RuneSelf: - b[w] = c - r++ - w++ - - // Coerce to well-formed UTF-8. - default: - rr, size := utf8.DecodeRune(s[r:]) - r += size - w += utf8.EncodeRune(b[w:], rr) - } - } - return b[0:w], true -} diff --git a/vendor/github.com/gobwas/glob/.gitignore b/vendor/github.com/gobwas/glob/.gitignore deleted file mode 100644 index b4ae623be55..00000000000 --- a/vendor/github.com/gobwas/glob/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -glob.iml -.idea -*.cpu -*.mem -*.test -*.dot -*.png -*.svg diff --git a/vendor/github.com/gobwas/glob/.travis.yml b/vendor/github.com/gobwas/glob/.travis.yml deleted file mode 100644 index e8a276826cf..00000000000 --- a/vendor/github.com/gobwas/glob/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -sudo: false - -language: go - -go: - - 1.5.3 - -script: - - go test -v ./... diff --git a/vendor/github.com/gobwas/glob/LICENSE b/vendor/github.com/gobwas/glob/LICENSE deleted file mode 100644 index 9d4735cad9f..00000000000 --- a/vendor/github.com/gobwas/glob/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Sergey Kamardin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/gobwas/glob/bench.sh b/vendor/github.com/gobwas/glob/bench.sh deleted file mode 100644 index 804cf22e646..00000000000 --- a/vendor/github.com/gobwas/glob/bench.sh +++ /dev/null @@ -1,26 +0,0 @@ -#! /bin/bash - -bench() { - filename="/tmp/$1-$2.bench" - if test -e "${filename}"; - then - echo "Already exists ${filename}" - else - backup=`git rev-parse --abbrev-ref HEAD` - git checkout $1 - echo -n "Creating ${filename}... " - go test ./... -run=NONE -bench=$2 > "${filename}" -benchmem - echo "OK" - git checkout ${backup} - sleep 5 - fi -} - - -to=$1 -current=`git rev-parse --abbrev-ref HEAD` - -bench ${to} $2 -bench ${current} $2 - -benchcmp $3 "/tmp/${to}-$2.bench" "/tmp/${current}-$2.bench" diff --git a/vendor/github.com/gobwas/glob/compiler/compiler.go b/vendor/github.com/gobwas/glob/compiler/compiler.go deleted file mode 100644 index 02e7de80a0b..00000000000 --- a/vendor/github.com/gobwas/glob/compiler/compiler.go +++ /dev/null @@ -1,525 +0,0 @@ -package compiler - -// TODO use constructor with all matchers, and to their structs private -// TODO glue multiple Text nodes (like after QuoteMeta) - -import ( - "fmt" - "reflect" - - "github.com/gobwas/glob/match" - "github.com/gobwas/glob/syntax/ast" - "github.com/gobwas/glob/util/runes" -) - -func optimizeMatcher(matcher match.Matcher) match.Matcher { - switch m := matcher.(type) { - - case match.Any: - if len(m.Separators) == 0 { - return match.NewSuper() - } - - case match.AnyOf: - if len(m.Matchers) == 1 { - return m.Matchers[0] - } - - return m - - case match.List: - if m.Not == false && len(m.List) == 1 { - return match.NewText(string(m.List)) - } - - return m - - case match.BTree: - m.Left = optimizeMatcher(m.Left) - m.Right = optimizeMatcher(m.Right) - - r, ok := m.Value.(match.Text) - if !ok { - return m - } - - var ( - leftNil = m.Left == nil - rightNil = m.Right == nil - ) - if leftNil && rightNil { - return match.NewText(r.Str) - } - - _, leftSuper := m.Left.(match.Super) - lp, leftPrefix := m.Left.(match.Prefix) - la, leftAny := m.Left.(match.Any) - - _, rightSuper := m.Right.(match.Super) - rs, rightSuffix := m.Right.(match.Suffix) - ra, rightAny := m.Right.(match.Any) - - switch { - case leftSuper && rightSuper: - return match.NewContains(r.Str, false) - - case leftSuper && rightNil: - return match.NewSuffix(r.Str) - - case rightSuper && leftNil: - return match.NewPrefix(r.Str) - - case leftNil && rightSuffix: - return match.NewPrefixSuffix(r.Str, rs.Suffix) - - case rightNil && leftPrefix: - return match.NewPrefixSuffix(lp.Prefix, r.Str) - - case rightNil && leftAny: - return match.NewSuffixAny(r.Str, la.Separators) - - case leftNil && rightAny: - return match.NewPrefixAny(r.Str, ra.Separators) - } - - return m - } - - return matcher -} - -func compileMatchers(matchers []match.Matcher) (match.Matcher, error) { - if len(matchers) == 0 { - return nil, fmt.Errorf("compile error: need at least one matcher") - } - if len(matchers) == 1 { - return matchers[0], nil - } - if m := glueMatchers(matchers); m != nil { - return m, nil - } - - idx := -1 - maxLen := -1 - var val match.Matcher - for i, matcher := range matchers { - if l := matcher.Len(); l != -1 && l >= maxLen { - maxLen = l - idx = i - val = matcher - } - } - - if val == nil { // not found matcher with static length - r, err := compileMatchers(matchers[1:]) - if err != nil { - return nil, err - } - return match.NewBTree(matchers[0], nil, r), nil - } - - left := matchers[:idx] - var right []match.Matcher - if len(matchers) > idx+1 { - right = matchers[idx+1:] - } - - var l, r match.Matcher - var err error - if len(left) > 0 { - l, err = compileMatchers(left) - if err != nil { - return nil, err - } - } - - if len(right) > 0 { - r, err = compileMatchers(right) - if err != nil { - return nil, err - } - } - - return match.NewBTree(val, l, r), nil -} - -func glueMatchers(matchers []match.Matcher) match.Matcher { - if m := glueMatchersAsEvery(matchers); m != nil { - return m - } - if m := glueMatchersAsRow(matchers); m != nil { - return m - } - return nil -} - -func glueMatchersAsRow(matchers []match.Matcher) match.Matcher { - if len(matchers) <= 1 { - return nil - } - - var ( - c []match.Matcher - l int - ) - for _, matcher := range matchers { - if ml := matcher.Len(); ml == -1 { - return nil - } else { - c = append(c, matcher) - l += ml - } - } - return match.NewRow(l, c...) -} - -func glueMatchersAsEvery(matchers []match.Matcher) match.Matcher { - if len(matchers) <= 1 { - return nil - } - - var ( - hasAny bool - hasSuper bool - hasSingle bool - min int - separator []rune - ) - - for i, matcher := range matchers { - var sep []rune - - switch m := matcher.(type) { - case match.Super: - sep = []rune{} - hasSuper = true - - case match.Any: - sep = m.Separators - hasAny = true - - case match.Single: - sep = m.Separators - hasSingle = true - min++ - - case match.List: - if !m.Not { - return nil - } - sep = m.List - hasSingle = true - min++ - - default: - return nil - } - - // initialize - if i == 0 { - separator = sep - } - - if runes.Equal(sep, separator) { - continue - } - - return nil - } - - if hasSuper && !hasAny && !hasSingle { - return match.NewSuper() - } - - if hasAny && !hasSuper && !hasSingle { - return match.NewAny(separator) - } - - if (hasAny || hasSuper) && min > 0 && len(separator) == 0 { - return match.NewMin(min) - } - - every := match.NewEveryOf() - - if min > 0 { - every.Add(match.NewMin(min)) - - if !hasAny && !hasSuper { - every.Add(match.NewMax(min)) - } - } - - if len(separator) > 0 { - every.Add(match.NewContains(string(separator), true)) - } - - return every -} - -func minimizeMatchers(matchers []match.Matcher) []match.Matcher { - var done match.Matcher - var left, right, count int - - for l := 0; l < len(matchers); l++ { - for r := len(matchers); r > l; r-- { - if glued := glueMatchers(matchers[l:r]); glued != nil { - var swap bool - - if done == nil { - swap = true - } else { - cl, gl := done.Len(), glued.Len() - swap = cl > -1 && gl > -1 && gl > cl - swap = swap || count < r-l - } - - if swap { - done = glued - left = l - right = r - count = r - l - } - } - } - } - - if done == nil { - return matchers - } - - next := append(append([]match.Matcher{}, matchers[:left]...), done) - if right < len(matchers) { - next = append(next, matchers[right:]...) - } - - if len(next) == len(matchers) { - return next - } - - return minimizeMatchers(next) -} - -// minimizeAnyOf tries to apply some heuristics to minimize number of nodes in given tree -func minimizeTree(tree *ast.Node) *ast.Node { - switch tree.Kind { - case ast.KindAnyOf: - return minimizeTreeAnyOf(tree) - default: - return nil - } -} - -// minimizeAnyOf tries to find common children of given node of AnyOf pattern -// it searches for common children from left and from right -// if any common children are found – then it returns new optimized ast tree -// else it returns nil -func minimizeTreeAnyOf(tree *ast.Node) *ast.Node { - if !areOfSameKind(tree.Children, ast.KindPattern) { - return nil - } - - commonLeft, commonRight := commonChildren(tree.Children) - commonLeftCount, commonRightCount := len(commonLeft), len(commonRight) - if commonLeftCount == 0 && commonRightCount == 0 { // there are no common parts - return nil - } - - var result []*ast.Node - if commonLeftCount > 0 { - result = append(result, ast.NewNode(ast.KindPattern, nil, commonLeft...)) - } - - var anyOf []*ast.Node - for _, child := range tree.Children { - reuse := child.Children[commonLeftCount : len(child.Children)-commonRightCount] - var node *ast.Node - if len(reuse) == 0 { - // this pattern is completely reduced by commonLeft and commonRight patterns - // so it become nothing - node = ast.NewNode(ast.KindNothing, nil) - } else { - node = ast.NewNode(ast.KindPattern, nil, reuse...) - } - anyOf = appendIfUnique(anyOf, node) - } - switch { - case len(anyOf) == 1 && anyOf[0].Kind != ast.KindNothing: - result = append(result, anyOf[0]) - case len(anyOf) > 1: - result = append(result, ast.NewNode(ast.KindAnyOf, nil, anyOf...)) - } - - if commonRightCount > 0 { - result = append(result, ast.NewNode(ast.KindPattern, nil, commonRight...)) - } - - return ast.NewNode(ast.KindPattern, nil, result...) -} - -func commonChildren(nodes []*ast.Node) (commonLeft, commonRight []*ast.Node) { - if len(nodes) <= 1 { - return - } - - // find node that has least number of children - idx := leastChildren(nodes) - if idx == -1 { - return - } - tree := nodes[idx] - treeLength := len(tree.Children) - - // allocate max able size for rightCommon slice - // to get ability insert elements in reverse order (from end to start) - // without sorting - commonRight = make([]*ast.Node, treeLength) - lastRight := treeLength // will use this to get results as commonRight[lastRight:] - - var ( - breakLeft bool - breakRight bool - commonTotal int - ) - for i, j := 0, treeLength-1; commonTotal < treeLength && j >= 0 && !(breakLeft && breakRight); i, j = i+1, j-1 { - treeLeft := tree.Children[i] - treeRight := tree.Children[j] - - for k := 0; k < len(nodes) && !(breakLeft && breakRight); k++ { - // skip least children node - if k == idx { - continue - } - - restLeft := nodes[k].Children[i] - restRight := nodes[k].Children[j+len(nodes[k].Children)-treeLength] - - breakLeft = breakLeft || !treeLeft.Equal(restLeft) - - // disable searching for right common parts, if left part is already overlapping - breakRight = breakRight || (!breakLeft && j <= i) - breakRight = breakRight || !treeRight.Equal(restRight) - } - - if !breakLeft { - commonTotal++ - commonLeft = append(commonLeft, treeLeft) - } - if !breakRight { - commonTotal++ - lastRight = j - commonRight[j] = treeRight - } - } - - commonRight = commonRight[lastRight:] - - return -} - -func appendIfUnique(target []*ast.Node, val *ast.Node) []*ast.Node { - for _, n := range target { - if reflect.DeepEqual(n, val) { - return target - } - } - return append(target, val) -} - -func areOfSameKind(nodes []*ast.Node, kind ast.Kind) bool { - for _, n := range nodes { - if n.Kind != kind { - return false - } - } - return true -} - -func leastChildren(nodes []*ast.Node) int { - min := -1 - idx := -1 - for i, n := range nodes { - if idx == -1 || (len(n.Children) < min) { - min = len(n.Children) - idx = i - } - } - return idx -} - -func compileTreeChildren(tree *ast.Node, sep []rune) ([]match.Matcher, error) { - var matchers []match.Matcher - for _, desc := range tree.Children { - m, err := compile(desc, sep) - if err != nil { - return nil, err - } - matchers = append(matchers, optimizeMatcher(m)) - } - return matchers, nil -} - -func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) { - switch tree.Kind { - case ast.KindAnyOf: - // todo this could be faster on pattern_alternatives_combine_lite (see glob_test.go) - if n := minimizeTree(tree); n != nil { - return compile(n, sep) - } - matchers, err := compileTreeChildren(tree, sep) - if err != nil { - return nil, err - } - return match.NewAnyOf(matchers...), nil - - case ast.KindPattern: - if len(tree.Children) == 0 { - return match.NewNothing(), nil - } - matchers, err := compileTreeChildren(tree, sep) - if err != nil { - return nil, err - } - m, err = compileMatchers(minimizeMatchers(matchers)) - if err != nil { - return nil, err - } - - case ast.KindAny: - m = match.NewAny(sep) - - case ast.KindSuper: - m = match.NewSuper() - - case ast.KindSingle: - m = match.NewSingle(sep) - - case ast.KindNothing: - m = match.NewNothing() - - case ast.KindList: - l := tree.Value.(ast.List) - m = match.NewList([]rune(l.Chars), l.Not) - - case ast.KindRange: - r := tree.Value.(ast.Range) - m = match.NewRange(r.Lo, r.Hi, r.Not) - - case ast.KindText: - t := tree.Value.(ast.Text) - m = match.NewText(t.Text) - - default: - return nil, fmt.Errorf("could not compile tree: unknown node type") - } - - return optimizeMatcher(m), nil -} - -func Compile(tree *ast.Node, sep []rune) (match.Matcher, error) { - m, err := compile(tree, sep) - if err != nil { - return nil, err - } - - return m, nil -} diff --git a/vendor/github.com/gobwas/glob/glob.go b/vendor/github.com/gobwas/glob/glob.go deleted file mode 100644 index 2afde343af8..00000000000 --- a/vendor/github.com/gobwas/glob/glob.go +++ /dev/null @@ -1,80 +0,0 @@ -package glob - -import ( - "github.com/gobwas/glob/compiler" - "github.com/gobwas/glob/syntax" -) - -// Glob represents compiled glob pattern. -type Glob interface { - Match(string) bool -} - -// Compile creates Glob for given pattern and strings (if any present after pattern) as separators. -// The pattern syntax is: -// -// pattern: -// { term } -// -// term: -// `*` matches any sequence of non-separator characters -// `**` matches any sequence of characters -// `?` matches any single non-separator character -// `[` [ `!` ] { character-range } `]` -// character class (must be non-empty) -// `{` pattern-list `}` -// pattern alternatives -// c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`) -// `\` c matches character c -// -// character-range: -// c matches character c (c != `\\`, `-`, `]`) -// `\` c matches character c -// lo `-` hi matches character c for lo <= c <= hi -// -// pattern-list: -// pattern { `,` pattern } -// comma-separated (without spaces) patterns -// -func Compile(pattern string, separators ...rune) (Glob, error) { - ast, err := syntax.Parse(pattern) - if err != nil { - return nil, err - } - - matcher, err := compiler.Compile(ast, separators) - if err != nil { - return nil, err - } - - return matcher, nil -} - -// MustCompile is the same as Compile, except that if Compile returns error, this will panic -func MustCompile(pattern string, separators ...rune) Glob { - g, err := Compile(pattern, separators...) - if err != nil { - panic(err) - } - - return g -} - -// QuoteMeta returns a string that quotes all glob pattern meta characters -// inside the argument text; For example, QuoteMeta(`{foo*}`) returns `\[foo\*\]`. -func QuoteMeta(s string) string { - b := make([]byte, 2*len(s)) - - // a byte loop is correct because all meta characters are ASCII - j := 0 - for i := 0; i < len(s); i++ { - if syntax.Special(s[i]) { - b[j] = '\\' - j++ - } - b[j] = s[i] - j++ - } - - return string(b[0:j]) -} diff --git a/vendor/github.com/gobwas/glob/match/any.go b/vendor/github.com/gobwas/glob/match/any.go deleted file mode 100644 index 514a9a5c450..00000000000 --- a/vendor/github.com/gobwas/glob/match/any.go +++ /dev/null @@ -1,45 +0,0 @@ -package match - -import ( - "fmt" - "github.com/gobwas/glob/util/strings" -) - -type Any struct { - Separators []rune -} - -func NewAny(s []rune) Any { - return Any{s} -} - -func (self Any) Match(s string) bool { - return strings.IndexAnyRunes(s, self.Separators) == -1 -} - -func (self Any) Index(s string) (int, []int) { - found := strings.IndexAnyRunes(s, self.Separators) - switch found { - case -1: - case 0: - return 0, segments0 - default: - s = s[:found] - } - - segments := acquireSegments(len(s)) - for i := range s { - segments = append(segments, i) - } - segments = append(segments, len(s)) - - return 0, segments -} - -func (self Any) Len() int { - return lenNo -} - -func (self Any) String() string { - return fmt.Sprintf("", string(self.Separators)) -} diff --git a/vendor/github.com/gobwas/glob/match/any_of.go b/vendor/github.com/gobwas/glob/match/any_of.go deleted file mode 100644 index 8e65356cdc9..00000000000 --- a/vendor/github.com/gobwas/glob/match/any_of.go +++ /dev/null @@ -1,82 +0,0 @@ -package match - -import "fmt" - -type AnyOf struct { - Matchers Matchers -} - -func NewAnyOf(m ...Matcher) AnyOf { - return AnyOf{Matchers(m)} -} - -func (self *AnyOf) Add(m Matcher) error { - self.Matchers = append(self.Matchers, m) - return nil -} - -func (self AnyOf) Match(s string) bool { - for _, m := range self.Matchers { - if m.Match(s) { - return true - } - } - - return false -} - -func (self AnyOf) Index(s string) (int, []int) { - index := -1 - - segments := acquireSegments(len(s)) - for _, m := range self.Matchers { - idx, seg := m.Index(s) - if idx == -1 { - continue - } - - if index == -1 || idx < index { - index = idx - segments = append(segments[:0], seg...) - continue - } - - if idx > index { - continue - } - - // here idx == index - segments = appendMerge(segments, seg) - } - - if index == -1 { - releaseSegments(segments) - return -1, nil - } - - return index, segments -} - -func (self AnyOf) Len() (l int) { - l = -1 - for _, m := range self.Matchers { - ml := m.Len() - switch { - case l == -1: - l = ml - continue - - case ml == -1: - return -1 - - case l != ml: - return -1 - } - } - - return -} - -func (self AnyOf) String() string { - return fmt.Sprintf("", self.Matchers) -} diff --git a/vendor/github.com/gobwas/glob/match/btree.go b/vendor/github.com/gobwas/glob/match/btree.go deleted file mode 100644 index a8130e93eae..00000000000 --- a/vendor/github.com/gobwas/glob/match/btree.go +++ /dev/null @@ -1,146 +0,0 @@ -package match - -import ( - "fmt" - "unicode/utf8" -) - -type BTree struct { - Value Matcher - Left Matcher - Right Matcher - ValueLengthRunes int - LeftLengthRunes int - RightLengthRunes int - LengthRunes int -} - -func NewBTree(Value, Left, Right Matcher) (tree BTree) { - tree.Value = Value - tree.Left = Left - tree.Right = Right - - lenOk := true - if tree.ValueLengthRunes = Value.Len(); tree.ValueLengthRunes == -1 { - lenOk = false - } - - if Left != nil { - if tree.LeftLengthRunes = Left.Len(); tree.LeftLengthRunes == -1 { - lenOk = false - } - } - - if Right != nil { - if tree.RightLengthRunes = Right.Len(); tree.RightLengthRunes == -1 { - lenOk = false - } - } - - if lenOk { - tree.LengthRunes = tree.LeftLengthRunes + tree.ValueLengthRunes + tree.RightLengthRunes - } else { - tree.LengthRunes = -1 - } - - return tree -} - -func (self BTree) Len() int { - return self.LengthRunes -} - -// todo? -func (self BTree) Index(s string) (int, []int) { - return -1, nil -} - -func (self BTree) Match(s string) bool { - inputLen := len(s) - - // self.Length, self.RLen and self.LLen are values meaning the length of runes for each part - // here we manipulating byte length for better optimizations - // but these checks still works, cause minLen of 1-rune string is 1 byte. - if self.LengthRunes != -1 && self.LengthRunes > inputLen { - return false - } - - // try to cut unnecessary parts - // by knowledge of length of right and left part - var offset, limit int - if self.LeftLengthRunes >= 0 { - offset = self.LeftLengthRunes - } - if self.RightLengthRunes >= 0 { - limit = inputLen - self.RightLengthRunes - } else { - limit = inputLen - } - - for offset < limit { - // search for matching part in substring - index, segments := self.Value.Index(s[offset:limit]) - if index == -1 { - releaseSegments(segments) - return false - } - - l := s[:offset+index] - var left bool - if self.Left != nil { - left = self.Left.Match(l) - } else { - left = l == "" - } - - if left { - for i := len(segments) - 1; i >= 0; i-- { - length := segments[i] - - var right bool - var r string - // if there is no string for the right branch - if inputLen <= offset+index+length { - r = "" - } else { - r = s[offset+index+length:] - } - - if self.Right != nil { - right = self.Right.Match(r) - } else { - right = r == "" - } - - if right { - releaseSegments(segments) - return true - } - } - } - - _, step := utf8.DecodeRuneInString(s[offset+index:]) - offset += index + step - - releaseSegments(segments) - } - - return false -} - -func (self BTree) String() string { - const n string = "" - var l, r string - if self.Left == nil { - l = n - } else { - l = self.Left.String() - } - if self.Right == nil { - r = n - } else { - r = self.Right.String() - } - - return fmt.Sprintf("%s]>", l, self.Value, r) -} diff --git a/vendor/github.com/gobwas/glob/match/contains.go b/vendor/github.com/gobwas/glob/match/contains.go deleted file mode 100644 index 0998e95b0ea..00000000000 --- a/vendor/github.com/gobwas/glob/match/contains.go +++ /dev/null @@ -1,58 +0,0 @@ -package match - -import ( - "fmt" - "strings" -) - -type Contains struct { - Needle string - Not bool -} - -func NewContains(needle string, not bool) Contains { - return Contains{needle, not} -} - -func (self Contains) Match(s string) bool { - return strings.Contains(s, self.Needle) != self.Not -} - -func (self Contains) Index(s string) (int, []int) { - var offset int - - idx := strings.Index(s, self.Needle) - - if !self.Not { - if idx == -1 { - return -1, nil - } - - offset = idx + len(self.Needle) - if len(s) <= offset { - return 0, []int{offset} - } - s = s[offset:] - } else if idx != -1 { - s = s[:idx] - } - - segments := acquireSegments(len(s) + 1) - for i := range s { - segments = append(segments, offset+i) - } - - return 0, append(segments, offset+len(s)) -} - -func (self Contains) Len() int { - return lenNo -} - -func (self Contains) String() string { - var not string - if self.Not { - not = "!" - } - return fmt.Sprintf("", not, self.Needle) -} diff --git a/vendor/github.com/gobwas/glob/match/every_of.go b/vendor/github.com/gobwas/glob/match/every_of.go deleted file mode 100644 index 7c968ee368b..00000000000 --- a/vendor/github.com/gobwas/glob/match/every_of.go +++ /dev/null @@ -1,99 +0,0 @@ -package match - -import ( - "fmt" -) - -type EveryOf struct { - Matchers Matchers -} - -func NewEveryOf(m ...Matcher) EveryOf { - return EveryOf{Matchers(m)} -} - -func (self *EveryOf) Add(m Matcher) error { - self.Matchers = append(self.Matchers, m) - return nil -} - -func (self EveryOf) Len() (l int) { - for _, m := range self.Matchers { - if ml := m.Len(); l > 0 { - l += ml - } else { - return -1 - } - } - - return -} - -func (self EveryOf) Index(s string) (int, []int) { - var index int - var offset int - - // make `in` with cap as len(s), - // cause it is the maximum size of output segments values - next := acquireSegments(len(s)) - current := acquireSegments(len(s)) - - sub := s - for i, m := range self.Matchers { - idx, seg := m.Index(sub) - if idx == -1 { - releaseSegments(next) - releaseSegments(current) - return -1, nil - } - - if i == 0 { - // we use copy here instead of `current = seg` - // cause seg is a slice from reusable buffer `in` - // and it could be overwritten in next iteration - current = append(current, seg...) - } else { - // clear the next - next = next[:0] - - delta := index - (idx + offset) - for _, ex := range current { - for _, n := range seg { - if ex+delta == n { - next = append(next, n) - } - } - } - - if len(next) == 0 { - releaseSegments(next) - releaseSegments(current) - return -1, nil - } - - current = append(current[:0], next...) - } - - index = idx + offset - sub = s[index:] - offset += idx - } - - releaseSegments(next) - - return index, current -} - -func (self EveryOf) Match(s string) bool { - for _, m := range self.Matchers { - if !m.Match(s) { - return false - } - } - - return true -} - -func (self EveryOf) String() string { - return fmt.Sprintf("", self.Matchers) -} diff --git a/vendor/github.com/gobwas/glob/match/list.go b/vendor/github.com/gobwas/glob/match/list.go deleted file mode 100644 index 7fd763ecd8e..00000000000 --- a/vendor/github.com/gobwas/glob/match/list.go +++ /dev/null @@ -1,49 +0,0 @@ -package match - -import ( - "fmt" - "github.com/gobwas/glob/util/runes" - "unicode/utf8" -) - -type List struct { - List []rune - Not bool -} - -func NewList(list []rune, not bool) List { - return List{list, not} -} - -func (self List) Match(s string) bool { - r, w := utf8.DecodeRuneInString(s) - if len(s) > w { - return false - } - - inList := runes.IndexRune(self.List, r) != -1 - return inList == !self.Not -} - -func (self List) Len() int { - return lenOne -} - -func (self List) Index(s string) (int, []int) { - for i, r := range s { - if self.Not == (runes.IndexRune(self.List, r) == -1) { - return i, segmentsByRuneLength[utf8.RuneLen(r)] - } - } - - return -1, nil -} - -func (self List) String() string { - var not string - if self.Not { - not = "!" - } - - return fmt.Sprintf("", not, string(self.List)) -} diff --git a/vendor/github.com/gobwas/glob/match/match.go b/vendor/github.com/gobwas/glob/match/match.go deleted file mode 100644 index f80e007fb83..00000000000 --- a/vendor/github.com/gobwas/glob/match/match.go +++ /dev/null @@ -1,81 +0,0 @@ -package match - -// todo common table of rune's length - -import ( - "fmt" - "strings" -) - -const lenOne = 1 -const lenZero = 0 -const lenNo = -1 - -type Matcher interface { - Match(string) bool - Index(string) (int, []int) - Len() int - String() string -} - -type Matchers []Matcher - -func (m Matchers) String() string { - var s []string - for _, matcher := range m { - s = append(s, fmt.Sprint(matcher)) - } - - return fmt.Sprintf("%s", strings.Join(s, ",")) -} - -// appendMerge merges and sorts given already SORTED and UNIQUE segments. -func appendMerge(target, sub []int) []int { - lt, ls := len(target), len(sub) - out := make([]int, 0, lt+ls) - - for x, y := 0, 0; x < lt || y < ls; { - if x >= lt { - out = append(out, sub[y:]...) - break - } - - if y >= ls { - out = append(out, target[x:]...) - break - } - - xValue := target[x] - yValue := sub[y] - - switch { - - case xValue == yValue: - out = append(out, xValue) - x++ - y++ - - case xValue < yValue: - out = append(out, xValue) - x++ - - case yValue < xValue: - out = append(out, yValue) - y++ - - } - } - - target = append(target[:0], out...) - - return target -} - -func reverseSegments(input []int) { - l := len(input) - m := l / 2 - - for i := 0; i < m; i++ { - input[i], input[l-i-1] = input[l-i-1], input[i] - } -} diff --git a/vendor/github.com/gobwas/glob/match/max.go b/vendor/github.com/gobwas/glob/match/max.go deleted file mode 100644 index d72f69efff7..00000000000 --- a/vendor/github.com/gobwas/glob/match/max.go +++ /dev/null @@ -1,49 +0,0 @@ -package match - -import ( - "fmt" - "unicode/utf8" -) - -type Max struct { - Limit int -} - -func NewMax(l int) Max { - return Max{l} -} - -func (self Max) Match(s string) bool { - var l int - for range s { - l += 1 - if l > self.Limit { - return false - } - } - - return true -} - -func (self Max) Index(s string) (int, []int) { - segments := acquireSegments(self.Limit + 1) - segments = append(segments, 0) - var count int - for i, r := range s { - count++ - if count > self.Limit { - break - } - segments = append(segments, i+utf8.RuneLen(r)) - } - - return 0, segments -} - -func (self Max) Len() int { - return lenNo -} - -func (self Max) String() string { - return fmt.Sprintf("", self.Limit) -} diff --git a/vendor/github.com/gobwas/glob/match/min.go b/vendor/github.com/gobwas/glob/match/min.go deleted file mode 100644 index db57ac8eb49..00000000000 --- a/vendor/github.com/gobwas/glob/match/min.go +++ /dev/null @@ -1,57 +0,0 @@ -package match - -import ( - "fmt" - "unicode/utf8" -) - -type Min struct { - Limit int -} - -func NewMin(l int) Min { - return Min{l} -} - -func (self Min) Match(s string) bool { - var l int - for range s { - l += 1 - if l >= self.Limit { - return true - } - } - - return false -} - -func (self Min) Index(s string) (int, []int) { - var count int - - c := len(s) - self.Limit + 1 - if c <= 0 { - return -1, nil - } - - segments := acquireSegments(c) - for i, r := range s { - count++ - if count >= self.Limit { - segments = append(segments, i+utf8.RuneLen(r)) - } - } - - if len(segments) == 0 { - return -1, nil - } - - return 0, segments -} - -func (self Min) Len() int { - return lenNo -} - -func (self Min) String() string { - return fmt.Sprintf("", self.Limit) -} diff --git a/vendor/github.com/gobwas/glob/match/nothing.go b/vendor/github.com/gobwas/glob/match/nothing.go deleted file mode 100644 index 0d4ecd36b80..00000000000 --- a/vendor/github.com/gobwas/glob/match/nothing.go +++ /dev/null @@ -1,27 +0,0 @@ -package match - -import ( - "fmt" -) - -type Nothing struct{} - -func NewNothing() Nothing { - return Nothing{} -} - -func (self Nothing) Match(s string) bool { - return len(s) == 0 -} - -func (self Nothing) Index(s string) (int, []int) { - return 0, segments0 -} - -func (self Nothing) Len() int { - return lenZero -} - -func (self Nothing) String() string { - return fmt.Sprintf("") -} diff --git a/vendor/github.com/gobwas/glob/match/prefix.go b/vendor/github.com/gobwas/glob/match/prefix.go deleted file mode 100644 index a7347250e8d..00000000000 --- a/vendor/github.com/gobwas/glob/match/prefix.go +++ /dev/null @@ -1,50 +0,0 @@ -package match - -import ( - "fmt" - "strings" - "unicode/utf8" -) - -type Prefix struct { - Prefix string -} - -func NewPrefix(p string) Prefix { - return Prefix{p} -} - -func (self Prefix) Index(s string) (int, []int) { - idx := strings.Index(s, self.Prefix) - if idx == -1 { - return -1, nil - } - - length := len(self.Prefix) - var sub string - if len(s) > idx+length { - sub = s[idx+length:] - } else { - sub = "" - } - - segments := acquireSegments(len(sub) + 1) - segments = append(segments, length) - for i, r := range sub { - segments = append(segments, length+i+utf8.RuneLen(r)) - } - - return idx, segments -} - -func (self Prefix) Len() int { - return lenNo -} - -func (self Prefix) Match(s string) bool { - return strings.HasPrefix(s, self.Prefix) -} - -func (self Prefix) String() string { - return fmt.Sprintf("", self.Prefix) -} diff --git a/vendor/github.com/gobwas/glob/match/prefix_any.go b/vendor/github.com/gobwas/glob/match/prefix_any.go deleted file mode 100644 index 8ee58fe1b3c..00000000000 --- a/vendor/github.com/gobwas/glob/match/prefix_any.go +++ /dev/null @@ -1,55 +0,0 @@ -package match - -import ( - "fmt" - "strings" - "unicode/utf8" - - sutil "github.com/gobwas/glob/util/strings" -) - -type PrefixAny struct { - Prefix string - Separators []rune -} - -func NewPrefixAny(s string, sep []rune) PrefixAny { - return PrefixAny{s, sep} -} - -func (self PrefixAny) Index(s string) (int, []int) { - idx := strings.Index(s, self.Prefix) - if idx == -1 { - return -1, nil - } - - n := len(self.Prefix) - sub := s[idx+n:] - i := sutil.IndexAnyRunes(sub, self.Separators) - if i > -1 { - sub = sub[:i] - } - - seg := acquireSegments(len(sub) + 1) - seg = append(seg, n) - for i, r := range sub { - seg = append(seg, n+i+utf8.RuneLen(r)) - } - - return idx, seg -} - -func (self PrefixAny) Len() int { - return lenNo -} - -func (self PrefixAny) Match(s string) bool { - if !strings.HasPrefix(s, self.Prefix) { - return false - } - return sutil.IndexAnyRunes(s[len(self.Prefix):], self.Separators) == -1 -} - -func (self PrefixAny) String() string { - return fmt.Sprintf("", self.Prefix, string(self.Separators)) -} diff --git a/vendor/github.com/gobwas/glob/match/prefix_suffix.go b/vendor/github.com/gobwas/glob/match/prefix_suffix.go deleted file mode 100644 index 8208085a199..00000000000 --- a/vendor/github.com/gobwas/glob/match/prefix_suffix.go +++ /dev/null @@ -1,62 +0,0 @@ -package match - -import ( - "fmt" - "strings" -) - -type PrefixSuffix struct { - Prefix, Suffix string -} - -func NewPrefixSuffix(p, s string) PrefixSuffix { - return PrefixSuffix{p, s} -} - -func (self PrefixSuffix) Index(s string) (int, []int) { - prefixIdx := strings.Index(s, self.Prefix) - if prefixIdx == -1 { - return -1, nil - } - - suffixLen := len(self.Suffix) - if suffixLen <= 0 { - return prefixIdx, []int{len(s) - prefixIdx} - } - - if (len(s) - prefixIdx) <= 0 { - return -1, nil - } - - segments := acquireSegments(len(s) - prefixIdx) - for sub := s[prefixIdx:]; ; { - suffixIdx := strings.LastIndex(sub, self.Suffix) - if suffixIdx == -1 { - break - } - - segments = append(segments, suffixIdx+suffixLen) - sub = sub[:suffixIdx] - } - - if len(segments) == 0 { - releaseSegments(segments) - return -1, nil - } - - reverseSegments(segments) - - return prefixIdx, segments -} - -func (self PrefixSuffix) Len() int { - return lenNo -} - -func (self PrefixSuffix) Match(s string) bool { - return strings.HasPrefix(s, self.Prefix) && strings.HasSuffix(s, self.Suffix) -} - -func (self PrefixSuffix) String() string { - return fmt.Sprintf("", self.Prefix, self.Suffix) -} diff --git a/vendor/github.com/gobwas/glob/match/range.go b/vendor/github.com/gobwas/glob/match/range.go deleted file mode 100644 index ce30245a40b..00000000000 --- a/vendor/github.com/gobwas/glob/match/range.go +++ /dev/null @@ -1,48 +0,0 @@ -package match - -import ( - "fmt" - "unicode/utf8" -) - -type Range struct { - Lo, Hi rune - Not bool -} - -func NewRange(lo, hi rune, not bool) Range { - return Range{lo, hi, not} -} - -func (self Range) Len() int { - return lenOne -} - -func (self Range) Match(s string) bool { - r, w := utf8.DecodeRuneInString(s) - if len(s) > w { - return false - } - - inRange := r >= self.Lo && r <= self.Hi - - return inRange == !self.Not -} - -func (self Range) Index(s string) (int, []int) { - for i, r := range s { - if self.Not != (r >= self.Lo && r <= self.Hi) { - return i, segmentsByRuneLength[utf8.RuneLen(r)] - } - } - - return -1, nil -} - -func (self Range) String() string { - var not string - if self.Not { - not = "!" - } - return fmt.Sprintf("", not, string(self.Lo), string(self.Hi)) -} diff --git a/vendor/github.com/gobwas/glob/match/row.go b/vendor/github.com/gobwas/glob/match/row.go deleted file mode 100644 index 4379042e42f..00000000000 --- a/vendor/github.com/gobwas/glob/match/row.go +++ /dev/null @@ -1,77 +0,0 @@ -package match - -import ( - "fmt" -) - -type Row struct { - Matchers Matchers - RunesLength int - Segments []int -} - -func NewRow(len int, m ...Matcher) Row { - return Row{ - Matchers: Matchers(m), - RunesLength: len, - Segments: []int{len}, - } -} - -func (self Row) matchAll(s string) bool { - var idx int - for _, m := range self.Matchers { - length := m.Len() - - var next, i int - for next = range s[idx:] { - i++ - if i == length { - break - } - } - - if i < length || !m.Match(s[idx:idx+next+1]) { - return false - } - - idx += next + 1 - } - - return true -} - -func (self Row) lenOk(s string) bool { - var i int - for range s { - i++ - if i > self.RunesLength { - return false - } - } - return self.RunesLength == i -} - -func (self Row) Match(s string) bool { - return self.lenOk(s) && self.matchAll(s) -} - -func (self Row) Len() (l int) { - return self.RunesLength -} - -func (self Row) Index(s string) (int, []int) { - for i := range s { - if len(s[i:]) < self.RunesLength { - break - } - if self.matchAll(s[i:]) { - return i, self.Segments - } - } - return -1, nil -} - -func (self Row) String() string { - return fmt.Sprintf("", self.RunesLength, self.Matchers) -} diff --git a/vendor/github.com/gobwas/glob/match/segments.go b/vendor/github.com/gobwas/glob/match/segments.go deleted file mode 100644 index 9ea6f309439..00000000000 --- a/vendor/github.com/gobwas/glob/match/segments.go +++ /dev/null @@ -1,91 +0,0 @@ -package match - -import ( - "sync" -) - -type SomePool interface { - Get() []int - Put([]int) -} - -var segmentsPools [1024]sync.Pool - -func toPowerOfTwo(v int) int { - v-- - v |= v >> 1 - v |= v >> 2 - v |= v >> 4 - v |= v >> 8 - v |= v >> 16 - v++ - - return v -} - -const ( - cacheFrom = 16 - cacheToAndHigher = 1024 - cacheFromIndex = 15 - cacheToAndHigherIndex = 1023 -) - -var ( - segments0 = []int{0} - segments1 = []int{1} - segments2 = []int{2} - segments3 = []int{3} - segments4 = []int{4} -) - -var segmentsByRuneLength [5][]int = [5][]int{ - 0: segments0, - 1: segments1, - 2: segments2, - 3: segments3, - 4: segments4, -} - -func init() { - for i := cacheToAndHigher; i >= cacheFrom; i >>= 1 { - func(i int) { - segmentsPools[i-1] = sync.Pool{New: func() interface{} { - return make([]int, 0, i) - }} - }(i) - } -} - -func getTableIndex(c int) int { - p := toPowerOfTwo(c) - switch { - case p >= cacheToAndHigher: - return cacheToAndHigherIndex - case p <= cacheFrom: - return cacheFromIndex - default: - return p - 1 - } -} - -func acquireSegments(c int) []int { - // make []int with less capacity than cacheFrom - // is faster than acquiring it from pool - if c < cacheFrom { - return make([]int, 0, c) - } - - return segmentsPools[getTableIndex(c)].Get().([]int)[:0] -} - -func releaseSegments(s []int) { - c := cap(s) - - // make []int with less capacity than cacheFrom - // is faster than acquiring it from pool - if c < cacheFrom { - return - } - - segmentsPools[getTableIndex(c)].Put(s) -} diff --git a/vendor/github.com/gobwas/glob/match/single.go b/vendor/github.com/gobwas/glob/match/single.go deleted file mode 100644 index ee6e3954c1f..00000000000 --- a/vendor/github.com/gobwas/glob/match/single.go +++ /dev/null @@ -1,43 +0,0 @@ -package match - -import ( - "fmt" - "github.com/gobwas/glob/util/runes" - "unicode/utf8" -) - -// single represents ? -type Single struct { - Separators []rune -} - -func NewSingle(s []rune) Single { - return Single{s} -} - -func (self Single) Match(s string) bool { - r, w := utf8.DecodeRuneInString(s) - if len(s) > w { - return false - } - - return runes.IndexRune(self.Separators, r) == -1 -} - -func (self Single) Len() int { - return lenOne -} - -func (self Single) Index(s string) (int, []int) { - for i, r := range s { - if runes.IndexRune(self.Separators, r) == -1 { - return i, segmentsByRuneLength[utf8.RuneLen(r)] - } - } - - return -1, nil -} - -func (self Single) String() string { - return fmt.Sprintf("", string(self.Separators)) -} diff --git a/vendor/github.com/gobwas/glob/match/suffix.go b/vendor/github.com/gobwas/glob/match/suffix.go deleted file mode 100644 index 85bea8c68ec..00000000000 --- a/vendor/github.com/gobwas/glob/match/suffix.go +++ /dev/null @@ -1,35 +0,0 @@ -package match - -import ( - "fmt" - "strings" -) - -type Suffix struct { - Suffix string -} - -func NewSuffix(s string) Suffix { - return Suffix{s} -} - -func (self Suffix) Len() int { - return lenNo -} - -func (self Suffix) Match(s string) bool { - return strings.HasSuffix(s, self.Suffix) -} - -func (self Suffix) Index(s string) (int, []int) { - idx := strings.Index(s, self.Suffix) - if idx == -1 { - return -1, nil - } - - return 0, []int{idx + len(self.Suffix)} -} - -func (self Suffix) String() string { - return fmt.Sprintf("", self.Suffix) -} diff --git a/vendor/github.com/gobwas/glob/match/suffix_any.go b/vendor/github.com/gobwas/glob/match/suffix_any.go deleted file mode 100644 index c5106f8196c..00000000000 --- a/vendor/github.com/gobwas/glob/match/suffix_any.go +++ /dev/null @@ -1,43 +0,0 @@ -package match - -import ( - "fmt" - "strings" - - sutil "github.com/gobwas/glob/util/strings" -) - -type SuffixAny struct { - Suffix string - Separators []rune -} - -func NewSuffixAny(s string, sep []rune) SuffixAny { - return SuffixAny{s, sep} -} - -func (self SuffixAny) Index(s string) (int, []int) { - idx := strings.Index(s, self.Suffix) - if idx == -1 { - return -1, nil - } - - i := sutil.LastIndexAnyRunes(s[:idx], self.Separators) + 1 - - return i, []int{idx + len(self.Suffix) - i} -} - -func (self SuffixAny) Len() int { - return lenNo -} - -func (self SuffixAny) Match(s string) bool { - if !strings.HasSuffix(s, self.Suffix) { - return false - } - return sutil.IndexAnyRunes(s[:len(s)-len(self.Suffix)], self.Separators) == -1 -} - -func (self SuffixAny) String() string { - return fmt.Sprintf("", string(self.Separators), self.Suffix) -} diff --git a/vendor/github.com/gobwas/glob/match/super.go b/vendor/github.com/gobwas/glob/match/super.go deleted file mode 100644 index 3875950bb8c..00000000000 --- a/vendor/github.com/gobwas/glob/match/super.go +++ /dev/null @@ -1,33 +0,0 @@ -package match - -import ( - "fmt" -) - -type Super struct{} - -func NewSuper() Super { - return Super{} -} - -func (self Super) Match(s string) bool { - return true -} - -func (self Super) Len() int { - return lenNo -} - -func (self Super) Index(s string) (int, []int) { - segments := acquireSegments(len(s) + 1) - for i := range s { - segments = append(segments, i) - } - segments = append(segments, len(s)) - - return 0, segments -} - -func (self Super) String() string { - return fmt.Sprintf("") -} diff --git a/vendor/github.com/gobwas/glob/match/text.go b/vendor/github.com/gobwas/glob/match/text.go deleted file mode 100644 index 0a17616d3cb..00000000000 --- a/vendor/github.com/gobwas/glob/match/text.go +++ /dev/null @@ -1,45 +0,0 @@ -package match - -import ( - "fmt" - "strings" - "unicode/utf8" -) - -// raw represents raw string to match -type Text struct { - Str string - RunesLength int - BytesLength int - Segments []int -} - -func NewText(s string) Text { - return Text{ - Str: s, - RunesLength: utf8.RuneCountInString(s), - BytesLength: len(s), - Segments: []int{len(s)}, - } -} - -func (self Text) Match(s string) bool { - return self.Str == s -} - -func (self Text) Len() int { - return self.RunesLength -} - -func (self Text) Index(s string) (int, []int) { - index := strings.Index(s, self.Str) - if index == -1 { - return -1, nil - } - - return index, self.Segments -} - -func (self Text) String() string { - return fmt.Sprintf("", self.Str) -} diff --git a/vendor/github.com/gobwas/glob/readme.md b/vendor/github.com/gobwas/glob/readme.md deleted file mode 100644 index f58144e733e..00000000000 --- a/vendor/github.com/gobwas/glob/readme.md +++ /dev/null @@ -1,148 +0,0 @@ -# glob.[go](https://golang.org) - -[![GoDoc][godoc-image]][godoc-url] [![Build Status][travis-image]][travis-url] - -> Go Globbing Library. - -## Install - -```shell - go get github.com/gobwas/glob -``` - -## Example - -```go - -package main - -import "github.com/gobwas/glob" - -func main() { - var g glob.Glob - - // create simple glob - g = glob.MustCompile("*.github.com") - g.Match("api.github.com") // true - - // quote meta characters and then create simple glob - g = glob.MustCompile(glob.QuoteMeta("*.github.com")) - g.Match("*.github.com") // true - - // create new glob with set of delimiters as ["."] - g = glob.MustCompile("api.*.com", '.') - g.Match("api.github.com") // true - g.Match("api.gi.hub.com") // false - - // create new glob with set of delimiters as ["."] - // but now with super wildcard - g = glob.MustCompile("api.**.com", '.') - g.Match("api.github.com") // true - g.Match("api.gi.hub.com") // true - - // create glob with single symbol wildcard - g = glob.MustCompile("?at") - g.Match("cat") // true - g.Match("fat") // true - g.Match("at") // false - - // create glob with single symbol wildcard and delimiters ['f'] - g = glob.MustCompile("?at", 'f') - g.Match("cat") // true - g.Match("fat") // false - g.Match("at") // false - - // create glob with character-list matchers - g = glob.MustCompile("[abc]at") - g.Match("cat") // true - g.Match("bat") // true - g.Match("fat") // false - g.Match("at") // false - - // create glob with character-list matchers - g = glob.MustCompile("[!abc]at") - g.Match("cat") // false - g.Match("bat") // false - g.Match("fat") // true - g.Match("at") // false - - // create glob with character-range matchers - g = glob.MustCompile("[a-c]at") - g.Match("cat") // true - g.Match("bat") // true - g.Match("fat") // false - g.Match("at") // false - - // create glob with character-range matchers - g = glob.MustCompile("[!a-c]at") - g.Match("cat") // false - g.Match("bat") // false - g.Match("fat") // true - g.Match("at") // false - - // create glob with pattern-alternatives list - g = glob.MustCompile("{cat,bat,[fr]at}") - g.Match("cat") // true - g.Match("bat") // true - g.Match("fat") // true - g.Match("rat") // true - g.Match("at") // false - g.Match("zat") // false -} - -``` - -## Performance - -This library is created for compile-once patterns. This means, that compilation could take time, but -strings matching is done faster, than in case when always parsing template. - -If you will not use compiled `glob.Glob` object, and do `g := glob.MustCompile(pattern); g.Match(...)` every time, then your code will be much more slower. - -Run `go test -bench=.` from source root to see the benchmarks: - -Pattern | Fixture | Match | Speed (ns/op) ---------|---------|-------|-------------- -`[a-z][!a-x]*cat*[h][!b]*eyes*` | `my cat has very bright eyes` | `true` | 432 -`[a-z][!a-x]*cat*[h][!b]*eyes*` | `my dog has very bright eyes` | `false` | 199 -`https://*.google.*` | `https://account.google.com` | `true` | 96 -`https://*.google.*` | `https://google.com` | `false` | 66 -`{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://yahoo.com` | `true` | 163 -`{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://google.com` | `false` | 197 -`{https://*gobwas.com,http://exclude.gobwas.com}` | `https://safe.gobwas.com` | `true` | 22 -`{https://*gobwas.com,http://exclude.gobwas.com}` | `http://safe.gobwas.com` | `false` | 24 -`abc*` | `abcdef` | `true` | 8.15 -`abc*` | `af` | `false` | 5.68 -`*def` | `abcdef` | `true` | 8.84 -`*def` | `af` | `false` | 5.74 -`ab*ef` | `abcdef` | `true` | 15.2 -`ab*ef` | `af` | `false` | 10.4 - -The same things with `regexp` package: - -Pattern | Fixture | Match | Speed (ns/op) ---------|---------|-------|-------------- -`^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my cat has very bright eyes` | `true` | 2553 -`^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my dog has very bright eyes` | `false` | 1383 -`^https:\/\/.*\.google\..*$` | `https://account.google.com` | `true` | 1205 -`^https:\/\/.*\.google\..*$` | `https://google.com` | `false` | 767 -`^(https:\/\/.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$` | `http://yahoo.com` | `true` | 1435 -`^(https:\/\/.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$` | `http://google.com` | `false` | 1674 -`^(https:\/\/.*gobwas\.com|http://exclude.gobwas.com)$` | `https://safe.gobwas.com` | `true` | 1039 -`^(https:\/\/.*gobwas\.com|http://exclude.gobwas.com)$` | `http://safe.gobwas.com` | `false` | 272 -`^abc.*$` | `abcdef` | `true` | 237 -`^abc.*$` | `af` | `false` | 100 -`^.*def$` | `abcdef` | `true` | 464 -`^.*def$` | `af` | `false` | 265 -`^ab.*ef$` | `abcdef` | `true` | 375 -`^ab.*ef$` | `af` | `false` | 145 - -[godoc-image]: https://godoc.org/github.com/gobwas/glob?status.svg -[godoc-url]: https://godoc.org/github.com/gobwas/glob -[travis-image]: https://travis-ci.org/gobwas/glob.svg?branch=master -[travis-url]: https://travis-ci.org/gobwas/glob - -## Syntax - -Syntax is inspired by [standard wildcards](http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm), -except that `**` is aka super-asterisk, that do not sensitive for separators. \ No newline at end of file diff --git a/vendor/github.com/gobwas/glob/syntax/ast/ast.go b/vendor/github.com/gobwas/glob/syntax/ast/ast.go deleted file mode 100644 index 3220a694a9c..00000000000 --- a/vendor/github.com/gobwas/glob/syntax/ast/ast.go +++ /dev/null @@ -1,122 +0,0 @@ -package ast - -import ( - "bytes" - "fmt" -) - -type Node struct { - Parent *Node - Children []*Node - Value interface{} - Kind Kind -} - -func NewNode(k Kind, v interface{}, ch ...*Node) *Node { - n := &Node{ - Kind: k, - Value: v, - } - for _, c := range ch { - Insert(n, c) - } - return n -} - -func (a *Node) Equal(b *Node) bool { - if a.Kind != b.Kind { - return false - } - if a.Value != b.Value { - return false - } - if len(a.Children) != len(b.Children) { - return false - } - for i, c := range a.Children { - if !c.Equal(b.Children[i]) { - return false - } - } - return true -} - -func (a *Node) String() string { - var buf bytes.Buffer - buf.WriteString(a.Kind.String()) - if a.Value != nil { - buf.WriteString(" =") - buf.WriteString(fmt.Sprintf("%v", a.Value)) - } - if len(a.Children) > 0 { - buf.WriteString(" [") - for i, c := range a.Children { - if i > 0 { - buf.WriteString(", ") - } - buf.WriteString(c.String()) - } - buf.WriteString("]") - } - return buf.String() -} - -func Insert(parent *Node, children ...*Node) { - parent.Children = append(parent.Children, children...) - for _, ch := range children { - ch.Parent = parent - } -} - -type List struct { - Not bool - Chars string -} - -type Range struct { - Not bool - Lo, Hi rune -} - -type Text struct { - Text string -} - -type Kind int - -const ( - KindNothing Kind = iota - KindPattern - KindList - KindRange - KindText - KindAny - KindSuper - KindSingle - KindAnyOf -) - -func (k Kind) String() string { - switch k { - case KindNothing: - return "Nothing" - case KindPattern: - return "Pattern" - case KindList: - return "List" - case KindRange: - return "Range" - case KindText: - return "Text" - case KindAny: - return "Any" - case KindSuper: - return "Super" - case KindSingle: - return "Single" - case KindAnyOf: - return "AnyOf" - default: - return "" - } -} diff --git a/vendor/github.com/gobwas/glob/syntax/ast/parser.go b/vendor/github.com/gobwas/glob/syntax/ast/parser.go deleted file mode 100644 index 429b4094303..00000000000 --- a/vendor/github.com/gobwas/glob/syntax/ast/parser.go +++ /dev/null @@ -1,157 +0,0 @@ -package ast - -import ( - "errors" - "fmt" - "github.com/gobwas/glob/syntax/lexer" - "unicode/utf8" -) - -type Lexer interface { - Next() lexer.Token -} - -type parseFn func(*Node, Lexer) (parseFn, *Node, error) - -func Parse(lexer Lexer) (*Node, error) { - var parser parseFn - - root := NewNode(KindPattern, nil) - - var ( - tree *Node - err error - ) - for parser, tree = parserMain, root; parser != nil; { - parser, tree, err = parser(tree, lexer) - if err != nil { - return nil, err - } - } - - return root, nil -} - -func parserMain(tree *Node, lex Lexer) (parseFn, *Node, error) { - for { - token := lex.Next() - switch token.Type { - case lexer.EOF: - return nil, tree, nil - - case lexer.Error: - return nil, tree, errors.New(token.Raw) - - case lexer.Text: - Insert(tree, NewNode(KindText, Text{token.Raw})) - return parserMain, tree, nil - - case lexer.Any: - Insert(tree, NewNode(KindAny, nil)) - return parserMain, tree, nil - - case lexer.Super: - Insert(tree, NewNode(KindSuper, nil)) - return parserMain, tree, nil - - case lexer.Single: - Insert(tree, NewNode(KindSingle, nil)) - return parserMain, tree, nil - - case lexer.RangeOpen: - return parserRange, tree, nil - - case lexer.TermsOpen: - a := NewNode(KindAnyOf, nil) - Insert(tree, a) - - p := NewNode(KindPattern, nil) - Insert(a, p) - - return parserMain, p, nil - - case lexer.Separator: - p := NewNode(KindPattern, nil) - Insert(tree.Parent, p) - - return parserMain, p, nil - - case lexer.TermsClose: - return parserMain, tree.Parent.Parent, nil - - default: - return nil, tree, fmt.Errorf("unexpected token: %s", token) - } - } - return nil, tree, fmt.Errorf("unknown error") -} - -func parserRange(tree *Node, lex Lexer) (parseFn, *Node, error) { - var ( - not bool - lo rune - hi rune - chars string - ) - for { - token := lex.Next() - switch token.Type { - case lexer.EOF: - return nil, tree, errors.New("unexpected end") - - case lexer.Error: - return nil, tree, errors.New(token.Raw) - - case lexer.Not: - not = true - - case lexer.RangeLo: - r, w := utf8.DecodeRuneInString(token.Raw) - if len(token.Raw) > w { - return nil, tree, fmt.Errorf("unexpected length of lo character") - } - lo = r - - case lexer.RangeBetween: - // - - case lexer.RangeHi: - r, w := utf8.DecodeRuneInString(token.Raw) - if len(token.Raw) > w { - return nil, tree, fmt.Errorf("unexpected length of lo character") - } - - hi = r - - if hi < lo { - return nil, tree, fmt.Errorf("hi character '%s' should be greater than lo '%s'", string(hi), string(lo)) - } - - case lexer.Text: - chars = token.Raw - - case lexer.RangeClose: - isRange := lo != 0 && hi != 0 - isChars := chars != "" - - if isChars == isRange { - return nil, tree, fmt.Errorf("could not parse range") - } - - if isRange { - Insert(tree, NewNode(KindRange, Range{ - Lo: lo, - Hi: hi, - Not: not, - })) - } else { - Insert(tree, NewNode(KindList, List{ - Chars: chars, - Not: not, - })) - } - - return parserMain, tree, nil - } - } -} diff --git a/vendor/github.com/gobwas/glob/syntax/lexer/lexer.go b/vendor/github.com/gobwas/glob/syntax/lexer/lexer.go deleted file mode 100644 index a1c8d1962a0..00000000000 --- a/vendor/github.com/gobwas/glob/syntax/lexer/lexer.go +++ /dev/null @@ -1,273 +0,0 @@ -package lexer - -import ( - "bytes" - "fmt" - "github.com/gobwas/glob/util/runes" - "unicode/utf8" -) - -const ( - char_any = '*' - char_comma = ',' - char_single = '?' - char_escape = '\\' - char_range_open = '[' - char_range_close = ']' - char_terms_open = '{' - char_terms_close = '}' - char_range_not = '!' - char_range_between = '-' -) - -var specials = []byte{ - char_any, - char_single, - char_escape, - char_range_open, - char_range_close, - char_terms_open, - char_terms_close, -} - -func Special(c byte) bool { - return bytes.IndexByte(specials, c) != -1 -} - -type tokens []Token - -func (i *tokens) shift() (ret Token) { - ret = (*i)[0] - copy(*i, (*i)[1:]) - *i = (*i)[:len(*i)-1] - return -} - -func (i *tokens) push(v Token) { - *i = append(*i, v) -} - -func (i *tokens) empty() bool { - return len(*i) == 0 -} - -var eof rune = 0 - -type lexer struct { - data string - pos int - err error - - tokens tokens - termsLevel int - - lastRune rune - lastRuneSize int - hasRune bool -} - -func NewLexer(source string) *lexer { - l := &lexer{ - data: source, - tokens: tokens(make([]Token, 0, 4)), - } - return l -} - -func (l *lexer) Next() Token { - if l.err != nil { - return Token{Error, l.err.Error()} - } - if !l.tokens.empty() { - return l.tokens.shift() - } - - l.fetchItem() - return l.Next() -} - -func (l *lexer) peek() (r rune, w int) { - if l.pos == len(l.data) { - return eof, 0 - } - - r, w = utf8.DecodeRuneInString(l.data[l.pos:]) - if r == utf8.RuneError { - l.errorf("could not read rune") - r = eof - w = 0 - } - - return -} - -func (l *lexer) read() rune { - if l.hasRune { - l.hasRune = false - l.seek(l.lastRuneSize) - return l.lastRune - } - - r, s := l.peek() - l.seek(s) - - l.lastRune = r - l.lastRuneSize = s - - return r -} - -func (l *lexer) seek(w int) { - l.pos += w -} - -func (l *lexer) unread() { - if l.hasRune { - l.errorf("could not unread rune") - return - } - l.seek(-l.lastRuneSize) - l.hasRune = true -} - -func (l *lexer) errorf(f string, v ...interface{}) { - l.err = fmt.Errorf(f, v...) -} - -func (l *lexer) inTerms() bool { - return l.termsLevel > 0 -} - -func (l *lexer) termsEnter() { - l.termsLevel++ -} - -func (l *lexer) termsLeave() { - l.termsLevel-- -} - -var inTextBreakers = []rune{char_single, char_any, char_range_open, char_terms_open} -var inTermsBreakers = append(inTextBreakers, char_terms_close, char_comma) - -func (l *lexer) fetchItem() { - r := l.read() - switch { - case r == eof: - l.tokens.push(Token{EOF, ""}) - - case r == char_terms_open: - l.termsEnter() - l.tokens.push(Token{TermsOpen, string(r)}) - - case r == char_comma && l.inTerms(): - l.tokens.push(Token{Separator, string(r)}) - - case r == char_terms_close && l.inTerms(): - l.tokens.push(Token{TermsClose, string(r)}) - l.termsLeave() - - case r == char_range_open: - l.tokens.push(Token{RangeOpen, string(r)}) - l.fetchRange() - - case r == char_single: - l.tokens.push(Token{Single, string(r)}) - - case r == char_any: - if l.read() == char_any { - l.tokens.push(Token{Super, string(r) + string(r)}) - } else { - l.unread() - l.tokens.push(Token{Any, string(r)}) - } - - default: - l.unread() - - var breakers []rune - if l.inTerms() { - breakers = inTermsBreakers - } else { - breakers = inTextBreakers - } - l.fetchText(breakers) - } -} - -func (l *lexer) fetchRange() { - var wantHi bool - var wantClose bool - var seenNot bool - for { - r := l.read() - if r == eof { - l.errorf("unexpected end of input") - return - } - - if wantClose { - if r != char_range_close { - l.errorf("expected close range character") - } else { - l.tokens.push(Token{RangeClose, string(r)}) - } - return - } - - if wantHi { - l.tokens.push(Token{RangeHi, string(r)}) - wantClose = true - continue - } - - if !seenNot && r == char_range_not { - l.tokens.push(Token{Not, string(r)}) - seenNot = true - continue - } - - if n, w := l.peek(); n == char_range_between { - l.seek(w) - l.tokens.push(Token{RangeLo, string(r)}) - l.tokens.push(Token{RangeBetween, string(n)}) - wantHi = true - continue - } - - l.unread() // unread first peek and fetch as text - l.fetchText([]rune{char_range_close}) - wantClose = true - } -} - -func (l *lexer) fetchText(breakers []rune) { - var data []rune - var escaped bool - -reading: - for { - r := l.read() - if r == eof { - break - } - - if !escaped { - if r == char_escape { - escaped = true - continue - } - - if runes.IndexRune(breakers, r) != -1 { - l.unread() - break reading - } - } - - escaped = false - data = append(data, r) - } - - if len(data) > 0 { - l.tokens.push(Token{Text, string(data)}) - } -} diff --git a/vendor/github.com/gobwas/glob/syntax/lexer/token.go b/vendor/github.com/gobwas/glob/syntax/lexer/token.go deleted file mode 100644 index 2797c4e83a4..00000000000 --- a/vendor/github.com/gobwas/glob/syntax/lexer/token.go +++ /dev/null @@ -1,88 +0,0 @@ -package lexer - -import "fmt" - -type TokenType int - -const ( - EOF TokenType = iota - Error - Text - Char - Any - Super - Single - Not - Separator - RangeOpen - RangeClose - RangeLo - RangeHi - RangeBetween - TermsOpen - TermsClose -) - -func (tt TokenType) String() string { - switch tt { - case EOF: - return "eof" - - case Error: - return "error" - - case Text: - return "text" - - case Char: - return "char" - - case Any: - return "any" - - case Super: - return "super" - - case Single: - return "single" - - case Not: - return "not" - - case Separator: - return "separator" - - case RangeOpen: - return "range_open" - - case RangeClose: - return "range_close" - - case RangeLo: - return "range_lo" - - case RangeHi: - return "range_hi" - - case RangeBetween: - return "range_between" - - case TermsOpen: - return "terms_open" - - case TermsClose: - return "terms_close" - - default: - return "undef" - } -} - -type Token struct { - Type TokenType - Raw string -} - -func (t Token) String() string { - return fmt.Sprintf("%v<%q>", t.Type, t.Raw) -} diff --git a/vendor/github.com/gobwas/glob/syntax/syntax.go b/vendor/github.com/gobwas/glob/syntax/syntax.go deleted file mode 100644 index 1d168b14829..00000000000 --- a/vendor/github.com/gobwas/glob/syntax/syntax.go +++ /dev/null @@ -1,14 +0,0 @@ -package syntax - -import ( - "github.com/gobwas/glob/syntax/ast" - "github.com/gobwas/glob/syntax/lexer" -) - -func Parse(s string) (*ast.Node, error) { - return ast.Parse(lexer.NewLexer(s)) -} - -func Special(b byte) bool { - return lexer.Special(b) -} diff --git a/vendor/github.com/gobwas/glob/util/runes/runes.go b/vendor/github.com/gobwas/glob/util/runes/runes.go deleted file mode 100644 index a7235564107..00000000000 --- a/vendor/github.com/gobwas/glob/util/runes/runes.go +++ /dev/null @@ -1,154 +0,0 @@ -package runes - -func Index(s, needle []rune) int { - ls, ln := len(s), len(needle) - - switch { - case ln == 0: - return 0 - case ln == 1: - return IndexRune(s, needle[0]) - case ln == ls: - if Equal(s, needle) { - return 0 - } - return -1 - case ln > ls: - return -1 - } - -head: - for i := 0; i < ls && ls-i >= ln; i++ { - for y := 0; y < ln; y++ { - if s[i+y] != needle[y] { - continue head - } - } - - return i - } - - return -1 -} - -func LastIndex(s, needle []rune) int { - ls, ln := len(s), len(needle) - - switch { - case ln == 0: - if ls == 0 { - return 0 - } - return ls - case ln == 1: - return IndexLastRune(s, needle[0]) - case ln == ls: - if Equal(s, needle) { - return 0 - } - return -1 - case ln > ls: - return -1 - } - -head: - for i := ls - 1; i >= 0 && i >= ln; i-- { - for y := ln - 1; y >= 0; y-- { - if s[i-(ln-y-1)] != needle[y] { - continue head - } - } - - return i - ln + 1 - } - - return -1 -} - -// IndexAny returns the index of the first instance of any Unicode code point -// from chars in s, or -1 if no Unicode code point from chars is present in s. -func IndexAny(s, chars []rune) int { - if len(chars) > 0 { - for i, c := range s { - for _, m := range chars { - if c == m { - return i - } - } - } - } - return -1 -} - -func Contains(s, needle []rune) bool { - return Index(s, needle) >= 0 -} - -func Max(s []rune) (max rune) { - for _, r := range s { - if r > max { - max = r - } - } - - return -} - -func Min(s []rune) rune { - min := rune(-1) - for _, r := range s { - if min == -1 { - min = r - continue - } - - if r < min { - min = r - } - } - - return min -} - -func IndexRune(s []rune, r rune) int { - for i, c := range s { - if c == r { - return i - } - } - return -1 -} - -func IndexLastRune(s []rune, r rune) int { - for i := len(s) - 1; i >= 0; i-- { - if s[i] == r { - return i - } - } - - return -1 -} - -func Equal(a, b []rune) bool { - if len(a) == len(b) { - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - - return true - } - - return false -} - -// HasPrefix tests whether the string s begins with prefix. -func HasPrefix(s, prefix []rune) bool { - return len(s) >= len(prefix) && Equal(s[0:len(prefix)], prefix) -} - -// HasSuffix tests whether the string s ends with suffix. -func HasSuffix(s, suffix []rune) bool { - return len(s) >= len(suffix) && Equal(s[len(s)-len(suffix):], suffix) -} diff --git a/vendor/github.com/gobwas/glob/util/strings/strings.go b/vendor/github.com/gobwas/glob/util/strings/strings.go deleted file mode 100644 index e8ee1920b17..00000000000 --- a/vendor/github.com/gobwas/glob/util/strings/strings.go +++ /dev/null @@ -1,39 +0,0 @@ -package strings - -import ( - "strings" - "unicode/utf8" -) - -func IndexAnyRunes(s string, rs []rune) int { - for _, r := range rs { - if i := strings.IndexRune(s, r); i != -1 { - return i - } - } - - return -1 -} - -func LastIndexAnyRunes(s string, rs []rune) int { - for _, r := range rs { - i := -1 - if 0 <= r && r < utf8.RuneSelf { - i = strings.LastIndexByte(s, byte(r)) - } else { - sub := s - for len(sub) > 0 { - j := strings.IndexRune(s, r) - if j == -1 { - break - } - i = j - sub = sub[i+1:] - } - } - if i != -1 { - return i - } - } - return -1 -} diff --git a/vendor/github.com/golang/groupcache/LICENSE b/vendor/github.com/golang/groupcache/LICENSE deleted file mode 100644 index 37ec93a14fd..00000000000 --- a/vendor/github.com/golang/groupcache/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, "control" means (i) the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising -permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -"Object" form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -"submitted" means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -2. Grant of Copyright License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -3. Grant of Patent License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of -this License; and -You must cause any modified files to carry prominent notices stating that You -changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -5. Submission of Contributions. - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -6. Trademarks. - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -8. Limitation of Liability. - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work - -To apply the Apache License to your work, attach the following boilerplate -notice, with the fields enclosed by brackets "[]" replaced with your own -identifying information. (Don't include the brackets!) The text should be -enclosed in the appropriate comment syntax for the file format. We also -recommend that a file or class name and description of purpose be included on -the same "printed page" as the copyright notice for easier identification within -third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/golang/groupcache/lru/lru.go b/vendor/github.com/golang/groupcache/lru/lru.go deleted file mode 100644 index eac1c7664f9..00000000000 --- a/vendor/github.com/golang/groupcache/lru/lru.go +++ /dev/null @@ -1,133 +0,0 @@ -/* -Copyright 2013 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package lru implements an LRU cache. -package lru - -import "container/list" - -// Cache is an LRU cache. It is not safe for concurrent access. -type Cache struct { - // MaxEntries is the maximum number of cache entries before - // an item is evicted. Zero means no limit. - MaxEntries int - - // OnEvicted optionally specifies a callback function to be - // executed when an entry is purged from the cache. - OnEvicted func(key Key, value interface{}) - - ll *list.List - cache map[interface{}]*list.Element -} - -// A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators -type Key interface{} - -type entry struct { - key Key - value interface{} -} - -// New creates a new Cache. -// If maxEntries is zero, the cache has no limit and it's assumed -// that eviction is done by the caller. -func New(maxEntries int) *Cache { - return &Cache{ - MaxEntries: maxEntries, - ll: list.New(), - cache: make(map[interface{}]*list.Element), - } -} - -// Add adds a value to the cache. -func (c *Cache) Add(key Key, value interface{}) { - if c.cache == nil { - c.cache = make(map[interface{}]*list.Element) - c.ll = list.New() - } - if ee, ok := c.cache[key]; ok { - c.ll.MoveToFront(ee) - ee.Value.(*entry).value = value - return - } - ele := c.ll.PushFront(&entry{key, value}) - c.cache[key] = ele - if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries { - c.RemoveOldest() - } -} - -// Get looks up a key's value from the cache. -func (c *Cache) Get(key Key) (value interface{}, ok bool) { - if c.cache == nil { - return - } - if ele, hit := c.cache[key]; hit { - c.ll.MoveToFront(ele) - return ele.Value.(*entry).value, true - } - return -} - -// Remove removes the provided key from the cache. -func (c *Cache) Remove(key Key) { - if c.cache == nil { - return - } - if ele, hit := c.cache[key]; hit { - c.removeElement(ele) - } -} - -// RemoveOldest removes the oldest item from the cache. -func (c *Cache) RemoveOldest() { - if c.cache == nil { - return - } - ele := c.ll.Back() - if ele != nil { - c.removeElement(ele) - } -} - -func (c *Cache) removeElement(e *list.Element) { - c.ll.Remove(e) - kv := e.Value.(*entry) - delete(c.cache, kv.key) - if c.OnEvicted != nil { - c.OnEvicted(kv.key, kv.value) - } -} - -// Len returns the number of items in the cache. -func (c *Cache) Len() int { - if c.cache == nil { - return 0 - } - return c.ll.Len() -} - -// Clear purges all stored items from the cache. -func (c *Cache) Clear() { - if c.OnEvicted != nil { - for _, e := range c.cache { - kv := e.Value.(*entry) - c.OnEvicted(kv.key, kv.value) - } - } - c.ll = nil - c.cache = nil -} diff --git a/vendor/github.com/gookit/color/.gitignore b/vendor/github.com/gookit/color/.gitignore deleted file mode 100644 index 5efa5e3f0f1..00000000000 --- a/vendor/github.com/gookit/color/.gitignore +++ /dev/null @@ -1,20 +0,0 @@ -*.log -*.swp -.idea -*.patch -### Go template -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out -.DS_Store -app -demo diff --git a/vendor/github.com/gookit/color/LICENSE b/vendor/github.com/gookit/color/LICENSE deleted file mode 100644 index d839cdc1ad1..00000000000 --- a/vendor/github.com/gookit/color/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 inhere - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/gookit/color/README.md b/vendor/github.com/gookit/color/README.md deleted file mode 100644 index 134181dc6c0..00000000000 --- a/vendor/github.com/gookit/color/README.md +++ /dev/null @@ -1,468 +0,0 @@ -# CLI Color - -![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/color?style=flat-square) -[![Actions Status](https://github.com/gookit/color/workflows/action-tests/badge.svg)](https://github.com/gookit/color/actions) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/51b28c5f7ffe4cc2b0f12ecf25ed247f)](https://app.codacy.com/app/inhere/color) -[![GoDoc](https://godoc.org/github.com/gookit/color?status.svg)](https://pkg.go.dev/github.com/gookit/color?tab=overview) -[![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/color)](https://github.com/gookit/color) -[![Build Status](https://travis-ci.org/gookit/color.svg?branch=master)](https://travis-ci.org/gookit/color) -[![Coverage Status](https://coveralls.io/repos/github/gookit/color/badge.svg?branch=master)](https://coveralls.io/github/gookit/color?branch=master) -[![Go Report Card](https://goreportcard.com/badge/github.com/gookit/color)](https://goreportcard.com/report/github.com/gookit/color) - -A command-line color library with true color support, universal API methods and Windows support. - -> **[中文说明](README.zh-CN.md)** - -Basic color preview: - -![basic-color](_examples/images/basic-color2.png) - -Now, 256 colors and RGB colors have also been supported to work in Windows CMD and PowerShell: - -![color-on-cmd-pwsh](_examples/images/color-on-cmd-pwsh.jpg) - -## Features - - - Simple to use, zero dependencies - - Supports rich color output: 16-color (4-bit), 256-color (8-bit), true color (24-bit, RGB) - - 16-color output is the most commonly used and most widely supported, working on any Windows version - - Since `v1.2.4` **the 256-color (8-bit), true color (24-bit) support windows CMD and PowerShell** - - See [this gist](https://gist.github.com/XVilka/8346728) for information on true color support - - Generic API methods: `Print`, `Printf`, `Println`, `Sprint`, `Sprintf` - - Supports HTML tag-style color rendering, such as `message`. - - In addition to using built-in tags, it also supports custom color attributes - - Custom color attributes support the use of 16 color names, 256 color values, rgb color values and hex color values - - Support working on Windows `cmd` and `powerShell` terminal - - Basic colors: `Bold`, `Black`, `White`, `Gray`, `Red`, `Green`, `Yellow`, `Blue`, `Magenta`, `Cyan` - - Additional styles: `Info`, `Note`, `Light`, `Error`, `Danger`, `Notice`, `Success`, `Comment`, `Primary`, `Warning`, `Question`, `Secondary` - - Support by set `NO_COLOR` for disable color or use `FORCE_COLOR` for force open color render. - - Support Rgb, 256, 16 color conversion - -## GoDoc - - - [godoc for gopkg](https://pkg.go.dev/gopkg.in/gookit/color.v1) - - [godoc for github](https://pkg.go.dev/github.com/gookit/color) - -## Install - -```bash -go get github.com/gookit/color -``` - -## Quick start - -```go -package main - -import ( - "fmt" - - "github.com/gookit/color" -) - -func main() { - // quick use package func - color.Redp("Simple to use color") - color.Redln("Simple to use color") - color.Greenp("Simple to use color\n") - color.Cyanln("Simple to use color") - color.Yellowln("Simple to use color") - - // quick use like fmt.Print* - color.Red.Println("Simple to use color") - color.Green.Print("Simple to use color\n") - color.Cyan.Printf("Simple to use %s\n", "color") - color.Yellow.Printf("Simple to use %s\n", "color") - - // use like func - red := color.FgRed.Render - green := color.FgGreen.Render - fmt.Printf("%s line %s library\n", red("Command"), green("color")) - - // custom color - color.New(color.FgWhite, color.BgBlack).Println("custom color style") - - // can also: - color.Style{color.FgCyan, color.OpBold}.Println("custom color style") - - // internal theme/style: - color.Info.Tips("message") - color.Info.Prompt("message") - color.Info.Println("message") - color.Warn.Println("message") - color.Error.Println("message") - - // use style tag - color.Print("hello, welcome\n") - // Custom label attr: Supports the use of 16 color names, 256 color values, rgb color values and hex color values - color.Println("hello, welcome") - - // apply a style tag - color.Tag("info").Println("info style text") - - // prompt message - color.Info.Prompt("prompt style message") - color.Warn.Prompt("prompt style message") - - // tips message - color.Info.Tips("tips style message") - color.Warn.Tips("tips style message") -} -``` - -Run demo: `go run ./_examples/demo.go` - -![colored-out](_examples/images/color-demo.jpg) - -## Basic/16 color - -Supported on any Windows version. Provide generic API methods: `Print`, `Printf`, `Println`, `Sprint`, `Sprintf` - -```go -color.Bold.Println("bold message") -color.Black.Println("bold message") -color.White.Println("bold message") -color.Gray.Println("bold message") -color.Red.Println("yellow message") -color.Blue.Println("yellow message") -color.Cyan.Println("yellow message") -color.Yellow.Println("yellow message") -color.Magenta.Println("yellow message") - -// Only use foreground color -color.FgCyan.Printf("Simple to use %s\n", "color") -// Only use background color -color.BgRed.Printf("Simple to use %s\n", "color") -``` - -Run demo: `go run ./_examples/color_16.go` - -![basic-color](_examples/images/basic-color.png) - -### Custom build color - -```go -// Full custom: foreground, background, option -myStyle := color.New(color.FgWhite, color.BgBlack, color.OpBold) -myStyle.Println("custom color style") - -// can also: -color.Style{color.FgCyan, color.OpBold}.Println("custom color style") -``` - -custom set console settings: - -```go -// set console color -color.Set(color.FgCyan) - -// print message -fmt.Print("message") - -// reset console settings -color.Reset() -``` - -### Additional styles - -provide generic API methods: `Print`, `Printf`, `Println`, `Sprint`, `Sprintf` - -print message use defined style: - -```go -color.Info.Println("Info message") -color.Note.Println("Note message") -color.Notice.Println("Notice message") -color.Error.Println("Error message") -color.Danger.Println("Danger message") -color.Warn.Println("Warn message") -color.Debug.Println("Debug message") -color.Primary.Println("Primary message") -color.Question.Println("Question message") -color.Secondary.Println("Secondary message") -``` - -Run demo: `go run ./_examples/theme_basic.go` - -![theme-basic](_examples/images/theme-basic.png) - -**Tips style** - -```go -color.Info.Tips("Info tips message") -color.Note.Tips("Note tips message") -color.Notice.Tips("Notice tips message") -color.Error.Tips("Error tips message") -color.Danger.Tips("Danger tips message") -color.Warn.Tips("Warn tips message") -color.Debug.Tips("Debug tips message") -color.Primary.Tips("Primary tips message") -color.Question.Tips("Question tips message") -color.Secondary.Tips("Secondary tips message") -``` - -Run demo: `go run ./_examples/theme_tips.go` - -![theme-tips](_examples/images/theme-tips.png) - -**Prompt Style** - -```go -color.Info.Prompt("Info prompt message") -color.Note.Prompt("Note prompt message") -color.Notice.Prompt("Notice prompt message") -color.Error.Prompt("Error prompt message") -color.Danger.Prompt("Danger prompt message") -color.Warn.Prompt("Warn prompt message") -color.Debug.Prompt("Debug prompt message") -color.Primary.Prompt("Primary prompt message") -color.Question.Prompt("Question prompt message") -color.Secondary.Prompt("Secondary prompt message") -``` - -Run demo: `go run ./_examples/theme_prompt.go` - -![theme-prompt](_examples/images/theme-prompt.png) - -**Block Style** - -```go -color.Info.Block("Info block message") -color.Note.Block("Note block message") -color.Notice.Block("Notice block message") -color.Error.Block("Error block message") -color.Danger.Block("Danger block message") -color.Warn.Block("Warn block message") -color.Debug.Block("Debug block message") -color.Primary.Block("Primary block message") -color.Question.Block("Question block message") -color.Secondary.Block("Secondary block message") -``` - -Run demo: `go run ./_examples/theme_block.go` - -![theme-block](_examples/images/theme-block.png) - -## 256-color usage - -> 256 colors support Windows CMD, PowerShell environment after `v1.2.4` - -### Set the foreground or background color - -- `color.C256(val uint8, isBg ...bool) Color256` - -```go -c := color.C256(132) // fg color -c.Println("message") -c.Printf("format %s", "message") - -c := color.C256(132, true) // bg color -c.Println("message") -c.Printf("format %s", "message") -``` - -### 256-color style - -Can be used to set foreground and background colors at the same time. - -- `S256(fgAndBg ...uint8) *Style256` - -```go -s := color.S256(32, 203) -s.Println("message") -s.Printf("format %s", "message") -``` - -with options: - -```go -s := color.S256(32, 203) -s.SetOpts(color.Opts{color.OpBold}) - -s.Println("style with options") -s.Printf("style with %s\n", "options") -``` - -Run demo: `go run ./_examples/color_256.go` - -![color-tags](_examples/images/color-256.png) - -## RGB/True color - -> RGB colors support Windows `CMD`, `PowerShell` environment after `v1.2.4` - -**Preview:** - -> Run demo: `Run demo: go run ./_examples/color_rgb.go` - -![color-rgb](_examples/images/color-rgb.png) - -example: - -```go -color.RGB(30, 144, 255).Println("message. use RGB number") - -color.HEX("#1976D2").Println("blue-darken") -color.HEX("#D50000", true).Println("red-accent. use HEX style") - -color.RGBStyleFromString("213,0,0").Println("red-accent. use RGB number") -color.HEXStyle("eee", "D50000").Println("deep-purple color") -``` - -### Set the foreground or background color - -- `color.RGB(r, g, b uint8, isBg ...bool) RGBColor` - -```go -c := color.RGB(30,144,255) // fg color -c.Println("message") -c.Printf("format %s", "message") - -c := color.RGB(30,144,255, true) // bg color -c.Println("message") -c.Printf("format %s", "message") -``` - -Create a style from an hexadecimal color string: - -- `color.HEX(hex string, isBg ...bool) RGBColor` - -```go -c := color.HEX("ccc") // can also: "cccccc" "#cccccc" -c.Println("message") -c.Printf("format %s", "message") - -c = color.HEX("aabbcc", true) // as bg color -c.Println("message") -c.Printf("format %s", "message") -``` - -### RGB color style - -Can be used to set the foreground and background colors at the same time. - -- `color.NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle` - -```go -s := color.NewRGBStyle(RGB(20, 144, 234), RGB(234, 78, 23)) -s.Println("message") -s.Printf("format %s", "message") -``` - -Create a style from an hexadecimal color string: - -- `color.HEXStyle(fg string, bg ...string) *RGBStyle` - -```go -s := color.HEXStyle("11aa23", "eee") -s.Println("message") -s.Printf("format %s", "message") -``` - -with options: - -```go -s := color.HEXStyle("11aa23", "eee") -s.SetOpts(color.Opts{color.OpBold}) - -s.Println("style with options") -s.Printf("style with %s\n", "options") -``` - -## HTML-like tag usage - -**Supported** on Windows `cmd.exe` `PowerShell` . - -```go -// use style tag -color.Print("hello, welcome") -color.Println("hello") -color.Println("hello") -color.Println("hello") - -// custom color attributes -color.Print("hello, welcome\n") - -// Custom label attr: Supports the use of 16 color names, 256 color values, rgb color values and hex color values -color.Println("hello, welcome") -``` - -- `color.Tag` - -```go -// set a style tag -color.Tag("info").Print("info style text") -color.Tag("info").Printf("%s style text", "info") -color.Tag("info").Println("info style text") -``` - -Run demo: `go run ./_examples/color_tag.go` - -![color-tags](_examples/images/color-tags.png) - -## Color convert - -Supports conversion between Rgb, 256, 16 colors, `Rgb <=> 256 <=> 16` - -```go -basic := color.Red -basic.Println("basic color") - -c256 := color.Red.C256() -c256.Println("256 color") -c256.C16().Println("basic color") - -rgb := color.Red.RGB() -rgb.Println("rgb color") -rgb.C256().Println("256 color") -``` - -## Func refer - -There are some useful functions reference - -- `Disable()` disable color render -- `SetOutput(io.Writer)` custom set the colored text output writer -- `ForceOpenColor()` force open color render -- `Colors2code(colors ...Color) string` Convert colors to code. return like "32;45;3" -- `ClearCode(str string) string` Use for clear color codes -- `ClearTag(s string) string` clear all color html-tag for a string -- `IsConsole(w io.Writer)` Determine whether w is one of stderr, stdout, stdin -- `HexToRgb(hex string) (rgb []int)` Convert hex color string to RGB numbers -- `RgbToHex(rgb []int) string` Convert RGB to hex code -- More useful func please see https://pkg.go.dev/github.com/gookit/color - -## Project use - -Check out these projects, which use https://github.com/gookit/color : - -- https://github.com/Delta456/box-cli-maker Make Highly Customized Boxes for your CLI - -## Gookit packages - - - [gookit/ini](https://github.com/gookit/ini) Go config management, use INI files - - [gookit/rux](https://github.com/gookit/rux) Simple and fast request router for golang HTTP - - [gookit/gcli](https://github.com/gookit/gcli) build CLI application, tool library, running CLI commands - - [gookit/slog](https://github.com/gookit/slog) Concise and extensible go log library - - [gookit/event](https://github.com/gookit/event) Lightweight event manager and dispatcher implements by Go - - [gookit/cache](https://github.com/gookit/cache) Generic cache use and cache manager for golang. support File, Memory, Redis, Memcached. - - [gookit/config](https://github.com/gookit/config) Go config management. support JSON, YAML, TOML, INI, HCL, ENV and Flags - - [gookit/color](https://github.com/gookit/color) A command-line color library with true color support, universal API methods and Windows support - - [gookit/filter](https://github.com/gookit/filter) Provide filtering, sanitizing, and conversion of golang data - - [gookit/validate](https://github.com/gookit/validate) Use for data validation and filtering. support Map, Struct, Form data - - [gookit/goutil](https://github.com/gookit/goutil) Some utils for the Go: string, array/slice, map, format, cli, env, filesystem, test and more - - More, please see https://github.com/gookit - -## See also - - - [inhere/console](https://github.com/inhere/php-console) - - [xo/terminfo](https://github.com/xo/terminfo) - - [beego/bee](https://github.com/beego/bee) - - [issue9/term](https://github.com/issue9/term) - - [ANSI escape code](https://en.wikipedia.org/wiki/ANSI_escape_code) - - [Standard ANSI color map](https://conemu.github.io/en/AnsiEscapeCodes.html#Standard_ANSI_color_map) - - [Terminal Colors](https://gist.github.com/XVilka/8346728) - -## License - -[MIT](/LICENSE) diff --git a/vendor/github.com/gookit/color/README.zh-CN.md b/vendor/github.com/gookit/color/README.zh-CN.md deleted file mode 100644 index dee1458b00a..00000000000 --- a/vendor/github.com/gookit/color/README.zh-CN.md +++ /dev/null @@ -1,472 +0,0 @@ -# CLI Color - -![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/color?style=flat-square) -[![Actions Status](https://github.com/gookit/color/workflows/action-tests/badge.svg)](https://github.com/gookit/color/actions) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/51b28c5f7ffe4cc2b0f12ecf25ed247f)](https://app.codacy.com/app/inhere/color) -[![GoDoc](https://godoc.org/github.com/gookit/color?status.svg)](https://pkg.go.dev/github.com/gookit/color?tab=overview) -[![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/color)](https://github.com/gookit/color) -[![Build Status](https://travis-ci.org/gookit/color.svg?branch=master)](https://travis-ci.org/gookit/color) -[![Coverage Status](https://coveralls.io/repos/github/gookit/color/badge.svg?branch=master)](https://coveralls.io/github/gookit/color?branch=master) -[![Go Report Card](https://goreportcard.com/badge/github.com/gookit/color)](https://goreportcard.com/report/github.com/gookit/color) - -Golang下的命令行色彩使用库, 拥有丰富的色彩渲染输出,通用的API方法,兼容Windows系统 - -> **[EN README](README.md)** - -基本颜色预览: - -![basic-color](_examples/images/basic-color2.png) - -现在,256色和RGB色彩也已经支持windows CMD和PowerShell中工作: - -![color-on-cmd-pwsh](_examples/images/color-on-cmd-pwsh.jpg) - -## 功能特色 - - - 使用简单方便 - - 支持丰富的颜色输出, 16色(4bit),256色(8bit),RGB色彩(24bit, RGB) - - 16色(4bit)是最常用和支持最广的,支持Windows `cmd.exe` - - 自 `v1.2.4` 起 **256色(8bit),RGB色彩(24bit)均支持Windows CMD和PowerShell终端** - - 请查看 [this gist](https://gist.github.com/XVilka/8346728) 了解支持RGB色彩的终端 - - 提供通用的API方法:`Print` `Printf` `Println` `Sprint` `Sprintf` - - 同时支持html标签式的颜色渲染,除了使用内置标签,同时支持自定义颜色属性 - - 例如: `this an message` 标签内部的文本将会渲染为绿色字体 - - 自定义颜色属性: 支持使用16色彩名称,256色彩值,rgb色彩值以及hex色彩值 - - 基础色彩: `Bold` `Black` `White` `Gray` `Red` `Green` `Yellow` `Blue` `Magenta` `Cyan` - - 扩展风格: `Info` `Note` `Light` `Error` `Danger` `Notice` `Success` `Comment` `Primary` `Warning` `Question` `Secondary` - - 支持通过设置环境变量 `NO_COLOR` 来禁用色彩,或者使用 `FORCE_COLOR` 来强制使用色彩渲染. - - 支持 Rgb, 256, 16 色彩之间的互相转换 - - 支持Linux、Mac,同时兼容Windows系统环境 - -## GoDoc - - - [godoc for gopkg](https://pkg.go.dev/gopkg.in/gookit/color.v1) - - [godoc for github](https://pkg.go.dev/github.com/gookit/color) - -## 安装 - -```bash -go get github.com/gookit/color -``` - -## 快速开始 - -如下,引入当前包就可以快速的使用 - -```go -package main - -import ( - "fmt" - - "github.com/gookit/color" -) - -func main() { - // 简单快速的使用,跟 fmt.Print* 类似 - color.Redp("Simple to use color") - color.Redln("Simple to use color") - color.Greenp("Simple to use color\n") - color.Cyanln("Simple to use color") - color.Yellowln("Simple to use color") - - // 简单快速的使用,跟 fmt.Print* 类似 - color.Red.Println("Simple to use color") - color.Green.Print("Simple to use color\n") - color.Cyan.Printf("Simple to use %s\n", "color") - color.Yellow.Printf("Simple to use %s\n", "color") - - // use like func - red := color.FgRed.Render - green := color.FgGreen.Render - fmt.Printf("%s line %s library\n", red("Command"), green("color")) - - // 自定义颜色 - color.New(color.FgWhite, color.BgBlack).Println("custom color style") - - // 也可以: - color.Style{color.FgCyan, color.OpBold}.Println("custom color style") - - // internal style: - color.Info.Println("message") - color.Warn.Println("message") - color.Error.Println("message") - - // 使用内置颜色标签 - color.Print("hello, welcome\n") - // 自定义标签: 支持使用16色彩名称,256色彩值,rgb色彩值以及hex色彩值 - color.Println("hello, welcome") - - // apply a style tag - color.Tag("info").Println("info style text") - - // prompt message - color.Info.Prompt("prompt style message") - color.Warn.Prompt("prompt style message") - - // tips message - color.Info.Tips("tips style message") - color.Warn.Tips("tips style message") -} -``` - -> 运行 demo: `go run ./_examples/demo.go` - -![colored-out](_examples/images/color-demo.jpg) - -## 基础颜色(16-color) - -提供通用的API方法:`Print` `Printf` `Println` `Sprint` `Sprintf` - -> 支持在windows `cmd.exe` `powerShell` 等终端使用 - -```go -color.Bold.Println("bold message") -color.Black.Println("bold message") -color.White.Println("bold message") -color.Gray.Println("bold message") -color.Red.Println("yellow message") -color.Blue.Println("yellow message") -color.Cyan.Println("yellow message") -color.Yellow.Println("yellow message") -color.Magenta.Println("yellow message") - -// Only use foreground color -color.FgCyan.Printf("Simple to use %s\n", "color") -// Only use background color -color.BgRed.Printf("Simple to use %s\n", "color") -``` - -> 运行demo: `go run ./_examples/color_16.go` - -![basic-color](_examples/images/basic-color.png) - -### 构建风格 - -```go -// 仅设置前景色 -color.FgCyan.Printf("Simple to use %s\n", "color") -// 仅设置背景色 -color.BgRed.Printf("Simple to use %s\n", "color") - -// 完全自定义: 前景色 背景色 选项 -style := color.New(color.FgWhite, color.BgBlack, color.OpBold) -style.Println("custom color style") - -// 也可以: -color.Style{color.FgCyan, color.OpBold}.Println("custom color style") -``` - -直接设置控制台属性: - -```go -// 设置console颜色 -color.Set(color.FgCyan) - -// 输出信息 -fmt.Print("message") - -// 重置console颜色 -color.Reset() -``` - -> 当然,color已经内置丰富的色彩风格支持 - -### 扩展风格方法 - -提供通用的API方法:`Print` `Printf` `Println` `Sprint` `Sprintf` - -> 支持在windows `cmd.exe` `powerShell` 等终端使用 - -基础使用: - -```go -// print message -color.Info.Println("Info message") -color.Note.Println("Note message") -color.Notice.Println("Notice message") -color.Error.Println("Error message") -color.Danger.Println("Danger message") -color.Warn.Println("Warn message") -color.Debug.Println("Debug message") -color.Primary.Println("Primary message") -color.Question.Println("Question message") -color.Secondary.Println("Secondary message") -``` - -Run demo: `go run ./_examples/theme_basic.go` - -![theme-basic](_examples/images/theme-basic.png) - -**简约提示风格** - -```go -color.Info.Tips("Info tips message") -color.Note.Tips("Note tips message") -color.Notice.Tips("Notice tips message") -color.Error.Tips("Error tips message") -color.Danger.Tips("Danger tips message") -color.Warn.Tips("Warn tips message") -color.Debug.Tips("Debug tips message") -color.Primary.Tips("Primary tips message") -color.Question.Tips("Question tips message") -color.Secondary.Tips("Secondary tips message") -``` - -Run demo: `go run ./_examples/theme_tips.go` - -![theme-tips](_examples/images/theme-tips.png) - -**着重提示风格** - -```go -color.Info.Prompt("Info prompt message") -color.Note.Prompt("Note prompt message") -color.Notice.Prompt("Notice prompt message") -color.Error.Prompt("Error prompt message") -color.Danger.Prompt("Danger prompt message") -``` - -Run demo: `go run ./_examples/theme_prompt.go` - -![theme-prompt](_examples/images/theme-prompt.png) - -**强调提示风格** - -```go -color.Warn.Block("Warn block message") -color.Debug.Block("Debug block message") -color.Primary.Block("Primary block message") -color.Question.Block("Question block message") -color.Secondary.Block("Secondary block message") -``` - -Run demo: `go run ./_examples/theme_block.go` - -![theme-block](_examples/images/theme-block.png) - -## 256 色彩使用 - -> 256色彩在 `v1.2.4` 后支持Windows CMD,PowerShell 环境 - -### 使用前景或后景色 - - - `color.C256(val uint8, isBg ...bool) Color256` - -```go -c := color.C256(132) // fg color -c.Println("message") -c.Printf("format %s", "message") - -c := color.C256(132, true) // bg color -c.Println("message") -c.Printf("format %s", "message") -``` - -### 使用256 色彩风格 - -> 可同时设置前景和背景色 - -- `color.S256(fgAndBg ...uint8) *Style256` - -```go -s := color.S256(32, 203) -s.Println("message") -s.Printf("format %s", "message") -``` - -可以同时添加选项设置: - -```go -s := color.S256(32, 203) -s.SetOpts(color.Opts{color.OpBold}) - -s.Println("style with options") -s.Printf("style with %s\n", "options") -``` - -> 运行 demo: `go run ./_examples/color_256.go` - -![color-tags](_examples/images/color-256.png) - -## RGB/True色彩使用 - -> RGB色彩在 `v1.2.4` 后支持 Windows `CMD`, `PowerShell` 环境 - -**效果预览:** - -> 运行 demo: `Run demo: go run ./_examples/color_rgb.go` - -![color-rgb](_examples/images/color-rgb.png) - -代码示例: - -```go -color.RGB(30, 144, 255).Println("message. use RGB number") - -color.HEX("#1976D2").Println("blue-darken") -color.HEX("#D50000", true).Println("red-accent. use HEX style") - -color.RGBStyleFromString("213,0,0").Println("red-accent. use RGB number") -color.HEXStyle("eee", "D50000").Println("deep-purple color") -``` - -### 使用前景或后景色 - -- `color.RGB(r, g, b uint8, isBg ...bool) RGBColor` - -```go -c := color.RGB(30,144,255) // fg color -c.Println("message") -c.Printf("format %s", "message") - -c := color.RGB(30,144,255, true) // bg color -c.Println("message") -c.Printf("format %s", "message") -``` - -- `color.HEX(hex string, isBg ...bool) RGBColor` 从16进制颜色创建 - -```go -c := color.HEX("ccc") // 也可以写为: "cccccc" "#cccccc" -c.Println("message") -c.Printf("format %s", "message") - -c = color.HEX("aabbcc", true) // as bg color -c.Println("message") -c.Printf("format %s", "message") -``` - -### 使用RGB风格 - -> 可同时设置前景和背景色 - -- `color.NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle` - -```go -s := color.NewRGBStyle(RGB(20, 144, 234), RGB(234, 78, 23)) -s.Println("message") -s.Printf("format %s", "message") -``` - -- `color.HEXStyle(fg string, bg ...string) *RGBStyle` 从16进制颜色创建 - -```go -s := color.HEXStyle("11aa23", "eee") -s.Println("message") -s.Printf("format %s", "message") -``` - -- 可以同时添加选项设置: - -```go -s := color.HEXStyle("11aa23", "eee") -s.SetOpts(color.Opts{color.OpBold}) - -s.Println("style with options") -s.Printf("style with %s\n", "options") -``` - -## 使用颜色标签 - -> **支持** 在windows `cmd.exe` `PowerShell` 使用 - -使用内置的颜色标签,可以非常方便简单的构建自己需要的任何格式 - -> 同时支持自定义颜色属性: 支持使用16色彩名称,256色彩值,rgb色彩值以及hex色彩值 - -```go -// 使用内置的 color tag -color.Print("hello, welcome") -color.Println("hello") -color.Println("hello") -color.Println("hello") - -// 自定义颜色属性 -color.Print("hello, welcome\n") - -// 自定义颜色属性: 支持使用16色彩名称,256色彩值,rgb色彩值以及hex色彩值 -color.Println("hello, welcome") -``` - -- 使用 `color.Tag` - -给后面输出的文本信息加上给定的颜色风格标签 - -```go -// set a style tag -color.Tag("info").Print("info style text") -color.Tag("info").Printf("%s style text", "info") -color.Tag("info").Println("info style text") -``` - -> 运行 demo: `go run ./_examples/color_tag.go` - -![color-tags](_examples/images/color-tags.png) - -## 颜色转换 - -支持 Rgb, 256, 16 色彩之间的互相转换 `Rgb <=> 256 <=> 16` - -```go -basic := color.Red -basic.Println("basic color") - -c256 := color.Red.C256() -c256.Println("256 color") -c256.C16().Println("basic color") - -rgb := color.Red.RGB() -rgb.Println("rgb color") -rgb.C256().Println("256 color") -``` - -## 方法参考 - -一些有用的工具方法参考 - -- `Disable()` disable color render -- `SetOutput(io.Writer)` custom set the colored text output writer -- `ForceOpenColor()` force open color render -- `ClearCode(str string) string` Use for clear color codes -- `Colors2code(colors ...Color) string` Convert colors to code. return like "32;45;3" -- `ClearTag(s string) string` clear all color html-tag for a string -- `IsConsole(w io.Writer)` Determine whether w is one of stderr, stdout, stdin -- `HexToRgb(hex string) (rgb []int)` Convert hex color string to RGB numbers -- `RgbToHex(rgb []int) string` Convert RGB to hex code -- 更多请查看文档 https://pkg.go.dev/github.com/gookit/color - -## 使用color的项目 - -看看这些使用了 https://github.com/gookit/color 的项目: - -- https://github.com/Delta456/box-cli-maker Make Highly Customized Boxes for your CLI - -## Gookit 工具包 - - - [gookit/ini](https://github.com/gookit/ini) INI配置读取管理,支持多文件加载,数据覆盖合并, 解析ENV变量, 解析变量引用 - - [gookit/rux](https://github.com/gookit/rux) Simple and fast request router for golang HTTP - - [gookit/gcli](https://github.com/gookit/gcli) Go的命令行应用,工具库,运行CLI命令,支持命令行色彩,用户交互,进度显示,数据格式化显示 - - [gookit/slog](https://github.com/gookit/slog) 简洁易扩展的go日志库 - - [gookit/event](https://github.com/gookit/event) Go实现的轻量级的事件管理、调度程序库, 支持设置监听器的优先级, 支持对一组事件进行监听 - - [gookit/cache](https://github.com/gookit/cache) 通用的缓存使用包装库,通过包装各种常用的驱动,来提供统一的使用API - - [gookit/config](https://github.com/gookit/config) Go应用配置管理,支持多种格式(JSON, YAML, TOML, INI, HCL, ENV, Flags),多文件加载,远程文件加载,数据合并 - - [gookit/color](https://github.com/gookit/color) CLI 控制台颜色渲染工具库, 拥有简洁的使用API,支持16色,256色,RGB色彩渲染输出 - - [gookit/filter](https://github.com/gookit/filter) 提供对Golang数据的过滤,净化,转换 - - [gookit/validate](https://github.com/gookit/validate) Go通用的数据验证与过滤库,使用简单,内置大部分常用验证、过滤器 - - [gookit/goutil](https://github.com/gookit/goutil) Go 的一些工具函数,格式化,特殊处理,常用信息获取等 - - 更多请查看 https://github.com/gookit - -## 参考项目 - - - [inhere/console](https://github.com/inhere/php-console) - - [xo/terminfo](https://github.com/xo/terminfo) - - [beego/bee](https://github.com/beego/bee) - - [issue9/term](https://github.com/issue9/term) - - [ANSI转义序列](https://zh.wikipedia.org/wiki/ANSI转义序列) - - [Standard ANSI color map](https://conemu.github.io/en/AnsiEscapeCodes.html#Standard_ANSI_color_map) - - [Terminal Colors](https://gist.github.com/XVilka/8346728) - -## License - -MIT diff --git a/vendor/github.com/gookit/color/color.go b/vendor/github.com/gookit/color/color.go deleted file mode 100644 index edb2a5d7b0d..00000000000 --- a/vendor/github.com/gookit/color/color.go +++ /dev/null @@ -1,238 +0,0 @@ -/* -Package color is Command line color library. -Support rich color rendering output, universal API method, compatible with Windows system - -Source code and other details for the project are available at GitHub: - - https://github.com/gookit/color - -More usage please see README and tests. -*/ -package color - -import ( - "fmt" - "io" - "os" - "regexp" - - "github.com/xo/terminfo" -) - -// terminal color available level alias of the terminfo.ColorLevel* -const ( - LevelNo = terminfo.ColorLevelNone // not support color. - Level16 = terminfo.ColorLevelBasic // 3/4 bit color supported - Level256 = terminfo.ColorLevelHundreds // 8 bit color supported - LevelRgb = terminfo.ColorLevelMillions // (24 bit)true color supported -) - -// color render templates -// ESC 操作的表示: -// "\033"(Octal 8进制) = "\x1b"(Hexadecimal 16进制) = 27 (10进制) -const ( - SettingTpl = "\x1b[%sm" - FullColorTpl = "\x1b[%sm%s\x1b[0m" -) - -// ResetSet Close all properties. -const ResetSet = "\x1b[0m" - -// CodeExpr regex to clear color codes eg "\033[1;36mText\x1b[0m" -const CodeExpr = `\033\[[\d;?]+m` - -var ( - // Enable switch color render and display - // - // NOTICE: - // if ENV: NO_COLOR is not empty, will disable color render. - Enable = os.Getenv("NO_COLOR") == "" - // RenderTag render HTML tag on call color.Xprint, color.PrintX - RenderTag = true - // debug mode for development. - // - // set env: - // COLOR_DEBUG_MODE=on - // or: - // COLOR_DEBUG_MODE=on go run ./_examples/envcheck.go - debugMode = os.Getenv("COLOR_DEBUG_MODE") == "on" - // inner errors record on detect color level - innerErrs []error - // output the default io.Writer message print - output io.Writer = os.Stdout - // mark current env, It's like in `cmd.exe` - // if not in windows, it's always is False. - isLikeInCmd bool - // the color support level for current terminal - // needVTP - need enable VTP, only for windows OS - colorLevel, needVTP = detectTermColorLevel() - // match color codes - codeRegex = regexp.MustCompile(CodeExpr) - // mark current env is support color. - // Always: isLikeInCmd != supportColor - // supportColor = IsSupportColor() -) - -// TermColorLevel value on current ENV -func TermColorLevel() terminfo.ColorLevel { - return colorLevel -} - -// SupportColor on the current ENV -func SupportColor() bool { - return colorLevel > terminfo.ColorLevelNone -} - -// Support16Color on the current ENV -// func Support16Color() bool { -// return colorLevel > terminfo.ColorLevelNone -// } - -// Support256Color on the current ENV -func Support256Color() bool { - return colorLevel > terminfo.ColorLevelBasic -} - -// SupportTrueColor on the current ENV -func SupportTrueColor() bool { - return colorLevel > terminfo.ColorLevelHundreds -} - -/************************************************************* - * global settings - *************************************************************/ - -// Set set console color attributes -func Set(colors ...Color) (int, error) { - code := Colors2code(colors...) - err := SetTerminal(code) - return 0, err -} - -// Reset reset console color attributes -func Reset() (int, error) { - err := ResetTerminal() - return 0, err -} - -// Disable disable color output -func Disable() bool { - oldVal := Enable - Enable = false - return oldVal -} - -// NotRenderTag on call color.Xprint, color.PrintX -func NotRenderTag() { - RenderTag = false -} - -// SetOutput set default colored text output -func SetOutput(w io.Writer) { - output = w -} - -// ResetOutput reset output -func ResetOutput() { - output = os.Stdout -} - -// ResetOptions reset all package option setting -func ResetOptions() { - RenderTag = true - Enable = true - output = os.Stdout -} - -// ForceColor force open color render -func ForceSetColorLevel(level terminfo.ColorLevel) terminfo.ColorLevel { - oldLevelVal := colorLevel - colorLevel = level - return oldLevelVal -} - -// ForceColor force open color render -func ForceColor() terminfo.ColorLevel { - return ForceOpenColor() -} - -// ForceOpenColor force open color render -func ForceOpenColor() terminfo.ColorLevel { - // TODO should set level to ? - return ForceSetColorLevel(terminfo.ColorLevelMillions) -} - -// IsLikeInCmd check result -// Deprecated -func IsLikeInCmd() bool { - return isLikeInCmd -} - -// InnerErrs info -func InnerErrs() []error { - return innerErrs -} - -/************************************************************* - * render color code - *************************************************************/ - -// RenderCode render message by color code. -// Usage: -// msg := RenderCode("3;32;45", "some", "message") -func RenderCode(code string, args ...interface{}) string { - var message string - if ln := len(args); ln == 0 { - return "" - } - - message = fmt.Sprint(args...) - if len(code) == 0 { - return message - } - - // disabled OR not support color - if !Enable || !SupportColor() { - return ClearCode(message) - } - - return fmt.Sprintf(FullColorTpl, code, message) -} - -// RenderWithSpaces Render code with spaces. -// If the number of args is > 1, a space will be added between the args -func RenderWithSpaces(code string, args ...interface{}) string { - message := formatArgsForPrintln(args) - if len(code) == 0 { - return message - } - - // disabled OR not support color - if !Enable || !SupportColor() { - return ClearCode(message) - } - - return fmt.Sprintf(FullColorTpl, code, message) -} - -// RenderString render a string with color code. -// Usage: -// msg := RenderString("3;32;45", "a message") -func RenderString(code string, str string) string { - if len(code) == 0 || str == "" { - return str - } - - // disabled OR not support color - if !Enable || !SupportColor() { - return ClearCode(str) - } - - return fmt.Sprintf(FullColorTpl, code, str) -} - -// ClearCode clear color codes. -// eg: "\033[36;1mText\x1b[0m" -> "Text" -func ClearCode(str string) string { - return codeRegex.ReplaceAllString(str, "") -} diff --git a/vendor/github.com/gookit/color/color_16.go b/vendor/github.com/gookit/color/color_16.go deleted file mode 100644 index 28e1048e0ca..00000000000 --- a/vendor/github.com/gookit/color/color_16.go +++ /dev/null @@ -1,440 +0,0 @@ -package color - -import ( - "fmt" - "strconv" -) - -// Color Color16, 16 color value type -// 3(2^3=8) OR 4(2^4=16) bite color. -type Color uint8 -type Basic = Color // alias of Color - -// Opts basic color options. code: 0 - 9 -type Opts []Color - -// Add option value -func (o *Opts) Add(ops ...Color) { - for _, op := range ops { - if uint8(op) < 10 { - *o = append(*o, op) - } - } -} - -// IsValid options -func (o Opts) IsValid() bool { - return len(o) > 0 -} - -// IsEmpty options -func (o Opts) IsEmpty() bool { - return len(o) == 0 -} - -// String options to string. eg: "1;3" -func (o Opts) String() string { - return Colors2code(o...) -} - -/************************************************************* - * Basic 16 color definition - *************************************************************/ - -// Base value for foreground/background color -const ( - FgBase uint8 = 30 - BgBase uint8 = 40 - // hi color base code - HiFgBase uint8 = 90 - HiBgBase uint8 = 100 -) - -// Foreground colors. basic foreground colors 30 - 37 -const ( - FgBlack Color = iota + 30 - FgRed - FgGreen - FgYellow - FgBlue - FgMagenta // 品红 - FgCyan // 青色 - FgWhite - // FgDefault revert default FG - FgDefault Color = 39 -) - -// Extra foreground color 90 - 97(非标准) -const ( - FgDarkGray Color = iota + 90 // 亮黑(灰) - FgLightRed - FgLightGreen - FgLightYellow - FgLightBlue - FgLightMagenta - FgLightCyan - FgLightWhite - // FgGray is alias of FgDarkGray - FgGray Color = 90 // 亮黑(灰) -) - -// Background colors. basic background colors 40 - 47 -const ( - BgBlack Color = iota + 40 - BgRed - BgGreen - BgYellow // BgBrown like yellow - BgBlue - BgMagenta - BgCyan - BgWhite - // BgDefault revert default BG - BgDefault Color = 49 -) - -// Extra background color 100 - 107(非标准) -const ( - BgDarkGray Color = iota + 100 - BgLightRed - BgLightGreen - BgLightYellow - BgLightBlue - BgLightMagenta - BgLightCyan - BgLightWhite - // BgGray is alias of BgDarkGray - BgGray Color = 100 -) - -// Option settings -const ( - OpReset Color = iota // 0 重置所有设置 - OpBold // 1 加粗 - OpFuzzy // 2 模糊(不是所有的终端仿真器都支持) - OpItalic // 3 斜体(不是所有的终端仿真器都支持) - OpUnderscore // 4 下划线 - OpBlink // 5 闪烁 - OpFastBlink // 5 快速闪烁(未广泛支持) - OpReverse // 7 颠倒的 交换背景色与前景色 - OpConcealed // 8 隐匿的 - OpStrikethrough // 9 删除的,删除线(未广泛支持) -) - -// There are basic and light foreground color aliases -const ( - Red = FgRed - Cyan = FgCyan - Gray = FgDarkGray // is light Black - Blue = FgBlue - Black = FgBlack - Green = FgGreen - White = FgWhite - Yellow = FgYellow - Magenta = FgMagenta - - // special - - Bold = OpBold - Normal = FgDefault - - // extra light - - LightRed = FgLightRed - LightCyan = FgLightCyan - LightBlue = FgLightBlue - LightGreen = FgLightGreen - LightWhite = FgLightWhite - LightYellow = FgLightYellow - LightMagenta = FgLightMagenta - - HiRed = FgLightRed - HiCyan = FgLightCyan - HiBlue = FgLightBlue - HiGreen = FgLightGreen - HiWhite = FgLightWhite - HiYellow = FgLightYellow - HiMagenta = FgLightMagenta - - BgHiRed = BgLightRed - BgHiCyan = BgLightCyan - BgHiBlue = BgLightBlue - BgHiGreen = BgLightGreen - BgHiWhite = BgLightWhite - BgHiYellow = BgLightYellow - BgHiMagenta = BgLightMagenta -) - -// Bit4 an method for create Color -func Bit4(code uint8) Color { - return Color(code) -} - -/************************************************************* - * Color render methods - *************************************************************/ - -// Name get color code name. -func (c Color) Name() string { - name, ok := basic2nameMap[uint8(c)] - if ok { - return name - } - return "unknown" -} - -// Text render a text message -func (c Color) Text(message string) string { - return RenderString(c.String(), message) -} - -// Render messages by color setting -// Usage: -// green := color.FgGreen.Render -// fmt.Println(green("message")) -func (c Color) Render(a ...interface{}) string { - return RenderCode(c.String(), a...) -} - -// Renderln messages by color setting. -// like Println, will add spaces for each argument -// Usage: -// green := color.FgGreen.Renderln -// fmt.Println(green("message")) -func (c Color) Renderln(a ...interface{}) string { - return RenderWithSpaces(c.String(), a...) -} - -// Sprint render messages by color setting. is alias of the Render() -func (c Color) Sprint(a ...interface{}) string { - return RenderCode(c.String(), a...) -} - -// Sprintf format and render message. -// Usage: -// green := color.Green.Sprintf -// colored := green("message") -func (c Color) Sprintf(format string, args ...interface{}) string { - return RenderString(c.String(), fmt.Sprintf(format, args...)) -} - -// Print messages. -// Usage: -// color.Green.Print("message") -// OR: -// green := color.FgGreen.Print -// green("message") -func (c Color) Print(args ...interface{}) { - doPrintV2(c.Code(), fmt.Sprint(args...)) -} - -// Printf format and print messages. -// Usage: -// color.Cyan.Printf("string %s", "arg0") -func (c Color) Printf(format string, a ...interface{}) { - doPrintV2(c.Code(), fmt.Sprintf(format, a...)) -} - -// Println messages with new line -func (c Color) Println(a ...interface{}) { - doPrintlnV2(c.String(), a) -} - -// Light current color. eg: 36(FgCyan) -> 96(FgLightCyan). -// Usage: -// lightCyan := Cyan.Light() -// lightCyan.Print("message") -func (c Color) Light() Color { - val := int(c) - if val >= 30 && val <= 47 { - return Color(uint8(c) + 60) - } - - // don't change - return c -} - -// Darken current color. eg. 96(FgLightCyan) -> 36(FgCyan) -// Usage: -// cyan := LightCyan.Darken() -// cyan.Print("message") -func (c Color) Darken() Color { - val := int(c) - if val >= 90 && val <= 107 { - return Color(uint8(c) - 60) - } - - // don't change - return c -} - -// C256 convert 16 color to 256-color code. -func (c Color) C256() Color256 { - val := uint8(c) - if val < 10 { // is option code - return emptyC256 // empty - } - - var isBg uint8 - if val >= BgBase && val <= 47 { // is bg - isBg = AsBg - val = val - 10 // to fg code - } else if val >= HiBgBase && val <= 107 { // is hi bg - isBg = AsBg - val = val - 10 // to fg code - } - - if c256, ok := basicTo256Map[val]; ok { - return Color256{c256, isBg} - } - - // use raw value direct convert - return Color256{val} -} - -// RGB convert 16 color to 256-color code. -func (c Color) RGB() RGBColor { - val := uint8(c) - if val < 10 { // is option code - return emptyRGBColor - } - - return HEX(Basic2hex(val)) -} - -// Code convert to code string. eg "35" -func (c Color) Code() string { - // return fmt.Sprintf("%d", c) - return strconv.Itoa(int(c)) -} - -// String convert to code string. eg "35" -func (c Color) String() string { - // return fmt.Sprintf("%d", c) - return strconv.Itoa(int(c)) -} - -// IsValid color value -func (c Color) IsValid() bool { - return c < 107 -} - -/************************************************************* - * basic color maps - *************************************************************/ - -// FgColors foreground colors map -var FgColors = map[string]Color{ - "black": FgBlack, - "red": FgRed, - "green": FgGreen, - "yellow": FgYellow, - "blue": FgBlue, - "magenta": FgMagenta, - "cyan": FgCyan, - "white": FgWhite, - "default": FgDefault, -} - -// BgColors background colors map -var BgColors = map[string]Color{ - "black": BgBlack, - "red": BgRed, - "green": BgGreen, - "yellow": BgYellow, - "blue": BgBlue, - "magenta": BgMagenta, - "cyan": BgCyan, - "white": BgWhite, - "default": BgDefault, -} - -// ExFgColors extra foreground colors map -var ExFgColors = map[string]Color{ - "darkGray": FgDarkGray, - "lightRed": FgLightRed, - "lightGreen": FgLightGreen, - "lightYellow": FgLightYellow, - "lightBlue": FgLightBlue, - "lightMagenta": FgLightMagenta, - "lightCyan": FgLightCyan, - "lightWhite": FgLightWhite, -} - -// ExBgColors extra background colors map -var ExBgColors = map[string]Color{ - "darkGray": BgDarkGray, - "lightRed": BgLightRed, - "lightGreen": BgLightGreen, - "lightYellow": BgLightYellow, - "lightBlue": BgLightBlue, - "lightMagenta": BgLightMagenta, - "lightCyan": BgLightCyan, - "lightWhite": BgLightWhite, -} - -// Options color options map -// Deprecated -// NOTICE: please use AllOptions instead. -var Options = AllOptions - -// AllOptions color options map -var AllOptions = map[string]Color{ - "reset": OpReset, - "bold": OpBold, - "fuzzy": OpFuzzy, - "italic": OpItalic, - "underscore": OpUnderscore, - "blink": OpBlink, - "reverse": OpReverse, - "concealed": OpConcealed, -} - -var ( - // TODO basic name alias - // basicNameAlias = map[string]string{} - - // basic color name to code - name2basicMap = initName2basicMap() - // basic2nameMap basic color code to name - basic2nameMap = map[uint8]string{ - 30: "black", - 31: "red", - 32: "green", - 33: "yellow", - 34: "blue", - 35: "magenta", - 36: "cyan", - 37: "white", - // hi color code - 90: "lightBlack", - 91: "lightRed", - 92: "lightGreen", - 93: "lightYellow", - 94: "lightBlue", - 95: "lightMagenta", - 96: "lightCyan", - 97: "lightWhite", - // options - 0: "reset", - 1: "bold", - 2: "fuzzy", - 3: "italic", - 4: "underscore", - 5: "blink", - 7: "reverse", - 8: "concealed", - } -) - -// Basic2nameMap data -func Basic2nameMap() map[uint8]string { - return basic2nameMap -} - -func initName2basicMap() map[string]uint8 { - n2b := make(map[string]uint8, len(basic2nameMap)) - for u, s := range basic2nameMap { - n2b[s] = u - } - return n2b -} diff --git a/vendor/github.com/gookit/color/color_256.go b/vendor/github.com/gookit/color/color_256.go deleted file mode 100644 index efd6dca301c..00000000000 --- a/vendor/github.com/gookit/color/color_256.go +++ /dev/null @@ -1,308 +0,0 @@ -package color - -import ( - "fmt" - "strconv" - "strings" -) - -/* -from wikipedia, 256 color: - ESC[ … 38;5; … m选择前景色 - ESC[ … 48;5; … m选择背景色 - 0- 7:标准颜色(同 ESC[30–37m) - 8- 15:高强度颜色(同 ESC[90–97m) - 16-231:6 × 6 × 6 立方(216色): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) - 232-255:从黑到白的24阶灰度色 -*/ - -// tpl for 8 bit 256 color(`2^8`) -// -// format: -// ESC[ … 38;5; … m // 选择前景色 -// ESC[ … 48;5; … m // 选择背景色 -// -// example: -// fg "\x1b[38;5;242m" -// bg "\x1b[48;5;208m" -// both "\x1b[38;5;242;48;5;208m" -// -// links: -// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#8位 -const ( - TplFg256 = "38;5;%d" - TplBg256 = "48;5;%d" - Fg256Pfx = "38;5;" - Bg256Pfx = "48;5;" -) - -/************************************************************* - * 8bit(256) Color: Bit8Color Color256 - *************************************************************/ - -// Color256 256 color (8 bit), uint8 range at 0 - 255 -// -// 颜色值使用10进制和16进制都可 0x98 = 152 -// -// The color consists of two uint8: -// 0: color value -// 1: color type; Fg=0, Bg=1, >1: unset value -// -// example: -// fg color: [152, 0] -// bg color: [152, 1] -// -// NOTICE: now support 256 color on windows CMD, PowerShell -// lint warn - Name starts with package name -type Color256 [2]uint8 -type Bit8Color = Color256 // alias - -var emptyC256 = Color256{1: 99} - -// Bit8 create a color256 -func Bit8(val uint8, isBg ...bool) Color256 { - return C256(val, isBg...) -} - -// C256 create a color256 -func C256(val uint8, isBg ...bool) Color256 { - bc := Color256{val} - - // mark is bg color - if len(isBg) > 0 && isBg[0] { - bc[1] = AsBg - } - - return bc -} - -// Set terminal by 256 color code -func (c Color256) Set() error { - return SetTerminal(c.String()) -} - -// Reset terminal. alias of the ResetTerminal() -func (c Color256) Reset() error { - return ResetTerminal() -} - -// Print print message -func (c Color256) Print(a ...interface{}) { - doPrintV2(c.String(), fmt.Sprint(a...)) -} - -// Printf format and print message -func (c Color256) Printf(format string, a ...interface{}) { - doPrintV2(c.String(), fmt.Sprintf(format, a...)) -} - -// Println print message with newline -func (c Color256) Println(a ...interface{}) { - doPrintlnV2(c.String(), a) -} - -// Sprint returns rendered message -func (c Color256) Sprint(a ...interface{}) string { - return RenderCode(c.String(), a...) -} - -// Sprintf returns format and rendered message -func (c Color256) Sprintf(format string, a ...interface{}) string { - return RenderString(c.String(), fmt.Sprintf(format, a...)) -} - -// C16 convert color-256 to 16 color. -func (c Color256) C16() Color { - return c.Basic() -} - -// Basic convert color-256 to basic 16 color. -func (c Color256) Basic() Color { - return Color(c[0]) // TODO -} - -// RGB convert color-256 to RGB color. -func (c Color256) RGB() RGBColor { - return RGBFromSlice(C256ToRgb(c[0]), c[1] == AsBg) -} - -// RGBColor convert color-256 to RGB color. -func (c Color256) RGBColor() RGBColor { - return c.RGB() -} - -// Value return color value -func (c Color256) Value() uint8 { - return c[0] -} - -// Code convert to color code string. eg: "12" -func (c Color256) Code() string { - return strconv.Itoa(int(c[0])) -} - -// FullCode convert to color code string with prefix. eg: "38;5;12" -func (c Color256) FullCode() string { - return c.String() -} - -// String convert to color code string with prefix. eg: "38;5;12" -func (c Color256) String() string { - if c[1] == AsFg { // 0 is Fg - // return fmt.Sprintf(TplFg256, c[0]) - return Fg256Pfx + strconv.Itoa(int(c[0])) - } - - if c[1] == AsBg { // 1 is Bg - // return fmt.Sprintf(TplBg256, c[0]) - return Bg256Pfx + strconv.Itoa(int(c[0])) - } - - return "" // empty -} - -// IsFg color -func (c Color256) IsFg() bool { - return c[1] == AsFg -} - -// ToFg 256 color -func (c Color256) ToFg() Color256 { - c[1] = AsFg - return c -} - -// IsBg color -func (c Color256) IsBg() bool { - return c[1] == AsBg -} - -// ToBg 256 color -func (c Color256) ToBg() Color256 { - c[1] = AsBg - return c -} - -// IsEmpty value -func (c Color256) IsEmpty() bool { - return c[1] > 1 -} - -/************************************************************* - * 8bit(256) Style - *************************************************************/ - -// Style256 definition -// -// 前/背景色 -// 都是由两位uint8组成, 第一位是色彩值; -// 第二位与 Bit8Color 不一样的是,在这里表示是否设置了值 0 未设置 !=0 已设置 -type Style256 struct { - // p Printer - - // Name of the style - Name string - // color options of the style - opts Opts - // fg and bg color - fg, bg Color256 -} - -// S256 create a color256 style -// Usage: -// s := color.S256() -// s := color.S256(132) // fg -// s := color.S256(132, 203) // fg and bg -func S256(fgAndBg ...uint8) *Style256 { - s := &Style256{} - vl := len(fgAndBg) - if vl > 0 { // with fg - s.fg = Color256{fgAndBg[0], 1} - - if vl > 1 { // and with bg - s.bg = Color256{fgAndBg[1], 1} - } - } - - return s -} - -// Set fg and bg color value, can also with color options -func (s *Style256) Set(fgVal, bgVal uint8, opts ...Color) *Style256 { - s.fg = Color256{fgVal, 1} - s.bg = Color256{bgVal, 1} - s.opts.Add(opts...) - return s -} - -// SetBg set bg color value -func (s *Style256) SetBg(bgVal uint8) *Style256 { - s.bg = Color256{bgVal, 1} - return s -} - -// SetFg set fg color value -func (s *Style256) SetFg(fgVal uint8) *Style256 { - s.fg = Color256{fgVal, 1} - return s -} - -// SetOpts set options -func (s *Style256) SetOpts(opts Opts) *Style256 { - s.opts = opts - return s -} - -// AddOpts add options -func (s *Style256) AddOpts(opts ...Color) *Style256 { - s.opts.Add(opts...) - return s -} - -// Print message -func (s *Style256) Print(a ...interface{}) { - doPrintV2(s.String(), fmt.Sprint(a...)) -} - -// Printf format and print message -func (s *Style256) Printf(format string, a ...interface{}) { - doPrintV2(s.String(), fmt.Sprintf(format, a...)) -} - -// Println print message with newline -func (s *Style256) Println(a ...interface{}) { - doPrintlnV2(s.String(), a) -} - -// Sprint returns rendered message -func (s *Style256) Sprint(a ...interface{}) string { - return RenderCode(s.Code(), a...) -} - -// Sprintf returns format and rendered message -func (s *Style256) Sprintf(format string, a ...interface{}) string { - return RenderString(s.Code(), fmt.Sprintf(format, a...)) -} - -// Code convert to color code string -func (s *Style256) Code() string { - return s.String() -} - -// String convert to color code string -func (s *Style256) String() string { - var ss []string - if s.fg[1] > 0 { - ss = append(ss, fmt.Sprintf(TplFg256, s.fg[0])) - } - - if s.bg[1] > 0 { - ss = append(ss, fmt.Sprintf(TplBg256, s.bg[0])) - } - - if s.opts.IsValid() { - ss = append(ss, s.opts.String()) - } - - return strings.Join(ss, ";") -} diff --git a/vendor/github.com/gookit/color/color_rgb.go b/vendor/github.com/gookit/color/color_rgb.go deleted file mode 100644 index a7ede18537f..00000000000 --- a/vendor/github.com/gookit/color/color_rgb.go +++ /dev/null @@ -1,391 +0,0 @@ -package color - -import ( - "fmt" - "strconv" - "strings" -) - -// 24 bit RGB color -// RGB: -// R 0-255 G 0-255 B 0-255 -// R 00-FF G 00-FF B 00-FF (16进制) -// -// Format: -// ESC[ … 38;2;;; … m // Select RGB foreground color -// ESC[ … 48;2;;; … m // Choose RGB background color -// -// links: -// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#24位 -// -// example: -// fg: \x1b[38;2;30;144;255mMESSAGE\x1b[0m -// bg: \x1b[48;2;30;144;255mMESSAGE\x1b[0m -// both: \x1b[38;2;233;90;203;48;2;30;144;255mMESSAGE\x1b[0m -const ( - TplFgRGB = "38;2;%d;%d;%d" - TplBgRGB = "48;2;%d;%d;%d" - FgRGBPfx = "38;2;" - BgRGBPfx = "48;2;" -) - -// mark color is fg or bg. -const ( - AsFg uint8 = iota - AsBg -) - -// values from https://github.com/go-terminfo/terminfo -// var ( -// RgbaBlack = image_color.RGBA{0, 0, 0, 255} -// Red = color.RGBA{205, 0, 0, 255} -// Green = color.RGBA{0, 205, 0, 255} -// Orange = color.RGBA{205, 205, 0, 255} -// Blue = color.RGBA{0, 0, 238, 255} -// Magenta = color.RGBA{205, 0, 205, 255} -// Cyan = color.RGBA{0, 205, 205, 255} -// LightGrey = color.RGBA{229, 229, 229, 255} -// -// DarkGrey = color.RGBA{127, 127, 127, 255} -// LightRed = color.RGBA{255, 0, 0, 255} -// LightGreen = color.RGBA{0, 255, 0, 255} -// Yellow = color.RGBA{255, 255, 0, 255} -// LightBlue = color.RGBA{92, 92, 255, 255} -// LightMagenta = color.RGBA{255, 0, 255, 255} -// LightCyan = color.RGBA{0, 255, 255, 255} -// White = color.RGBA{255, 255, 255, 255} -// ) - -/************************************************************* - * RGB Color(Bit24Color, TrueColor) - *************************************************************/ - -// RGBColor definition. -// -// The first to third digits represent the color value. -// The last digit represents the foreground(0), background(1), >1 is unset value -// -// Usage: -// // 0, 1, 2 is R,G,B. -// // 3rd: Fg=0, Bg=1, >1: unset value -// RGBColor{30,144,255, 0} -// RGBColor{30,144,255, 1} -// -// NOTICE: now support RGB color on windows CMD, PowerShell -type RGBColor [4]uint8 - -// create a empty RGBColor -var emptyRGBColor = RGBColor{3: 99} - -// RGB color create. -// Usage: -// c := RGB(30,144,255) -// c := RGB(30,144,255, true) -// c.Print("message") -func RGB(r, g, b uint8, isBg ...bool) RGBColor { - rgb := RGBColor{r, g, b} - if len(isBg) > 0 && isBg[0] { - rgb[3] = AsBg - } - - return rgb -} - -// Rgb alias of the RGB() -func Rgb(r, g, b uint8, isBg ...bool) RGBColor { return RGB(r, g, b, isBg...) } - -// Bit24 alias of the RGB() -func Bit24(r, g, b uint8, isBg ...bool) RGBColor { return RGB(r, g, b, isBg...) } - -// RGBFromSlice quick RGBColor from slice -func RGBFromSlice(rgb []uint8, isBg ...bool) RGBColor { - return RGB(rgb[0], rgb[1], rgb[2], isBg...) -} - -// HEX create RGB color from a HEX color string. -// Usage: -// c := HEX("ccc") // rgb: [204 204 204] -// c := HEX("aabbcc") // rgb: [170 187 204] -// c := HEX("#aabbcc") -// c := HEX("0xaabbcc") -// c.Print("message") -func HEX(hex string, isBg ...bool) RGBColor { - if rgb := HexToRgb(hex); len(rgb) > 0 { - return RGB(uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]), isBg...) - } - - // mark is empty - return emptyRGBColor -} - -// Hex alias of the HEX() -func Hex(hex string, isBg ...bool) RGBColor { return HEX(hex, isBg...) } - -// RGBFromString create RGB color from a string. -// Usage: -// c := RGBFromString("170,187,204") -// c.Print("message") -func RGBFromString(rgb string, isBg ...bool) RGBColor { - ss := stringToArr(rgb, ",") - if len(ss) != 3 { - return emptyRGBColor - } - - var ar [3]int - for i, val := range ss { - iv, err := strconv.Atoi(val) - if err != nil { - return emptyRGBColor - } - - ar[i] = iv - } - - return RGB(uint8(ar[0]), uint8(ar[1]), uint8(ar[2]), isBg...) -} - -// Set terminal by rgb/true color code -func (c RGBColor) Set() error { - return SetTerminal(c.String()) -} - -// Reset terminal. alias of the ResetTerminal() -func (c RGBColor) Reset() error { - return ResetTerminal() -} - -// Print print message -func (c RGBColor) Print(a ...interface{}) { - doPrintV2(c.String(), fmt.Sprint(a...)) -} - -// Printf format and print message -func (c RGBColor) Printf(format string, a ...interface{}) { - doPrintV2(c.String(), fmt.Sprintf(format, a...)) -} - -// Println print message with newline -func (c RGBColor) Println(a ...interface{}) { - doPrintlnV2(c.String(), a) -} - -// Sprint returns rendered message -func (c RGBColor) Sprint(a ...interface{}) string { - return RenderCode(c.String(), a...) -} - -// Sprintf returns format and rendered message -func (c RGBColor) Sprintf(format string, a ...interface{}) string { - return RenderString(c.String(), fmt.Sprintf(format, a...)) -} - -// Values to RGB values -func (c RGBColor) Values() []int { - return []int{int(c[0]), int(c[1]), int(c[2])} -} - -// Code to color code string without prefix. eg: "204;123;56" -func (c RGBColor) Code() string { - return fmt.Sprintf("%d;%d;%d", c[0], c[1], c[2]) -} - -// Hex color rgb to hex string. as in "ff0080". -func (c RGBColor) Hex() string { - return fmt.Sprintf("%02x%02x%02x", c[0], c[1], c[2]) -} - -// FullCode to color code string with prefix -func (c RGBColor) FullCode() string { - return c.String() -} - -// String to color code string with prefix. eg: "38;2;204;123;56" -func (c RGBColor) String() string { - if c[3] == AsFg { - return fmt.Sprintf(TplFgRGB, c[0], c[1], c[2]) - } - - if c[3] == AsBg { - return fmt.Sprintf(TplBgRGB, c[0], c[1], c[2]) - } - - // c[3] > 1 is empty - return "" -} - -// IsEmpty value -func (c RGBColor) IsEmpty() bool { - return c[3] > AsBg -} - -// IsValid value -// func (c RGBColor) IsValid() bool { -// return c[3] <= AsBg -// } - -// C256 returns the closest approximate 256 (8 bit) color -func (c RGBColor) C256() Color256 { - return C256(RgbTo256(c[0], c[1], c[2]), c[3] == AsBg) -} - -// Basic returns the closest approximate 16 (4 bit) color -func (c RGBColor) Basic() Color { - // return Color(RgbToAnsi(c[0], c[1], c[2], c[3] == AsBg)) - return Color(Rgb2basic(c[0], c[1], c[2], c[3] == AsBg)) -} - -// Color returns the closest approximate 16 (4 bit) color -func (c RGBColor) Color() Color { return c.Basic() } - -// C16 returns the closest approximate 16 (4 bit) color -func (c RGBColor) C16() Color { return c.Basic() } - -/************************************************************* - * RGB Style - *************************************************************/ - -// RGBStyle definition. -// -// Foreground/Background color -// All are composed of 4 digits uint8, the first three digits are the color value; -// The last bit is different from RGBColor, here it indicates whether the value is set. -// - 1 Has been set -// - ^1 Not set -type RGBStyle struct { - // Name of the style - Name string - // color options of the style - opts Opts - // fg and bg color - fg, bg RGBColor -} - -// NewRGBStyle create a RGBStyle. -func NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle { - s := &RGBStyle{} - if len(bg) > 0 { - s.SetBg(bg[0]) - } - - return s.SetFg(fg) -} - -// HEXStyle create a RGBStyle from HEX color string. -// Usage: -// s := HEXStyle("aabbcc", "eee") -// s.Print("message") -func HEXStyle(fg string, bg ...string) *RGBStyle { - s := &RGBStyle{} - if len(bg) > 0 { - s.SetBg(HEX(bg[0])) - } - - if len(fg) > 0 { - s.SetFg(HEX(fg)) - } - - return s -} - -// RGBStyleFromString create a RGBStyle from color value string. -// Usage: -// s := RGBStyleFromString("170,187,204", "70,87,4") -// s.Print("message") -func RGBStyleFromString(fg string, bg ...string) *RGBStyle { - s := &RGBStyle{} - if len(bg) > 0 { - s.SetBg(RGBFromString(bg[0])) - } - - return s.SetFg(RGBFromString(fg)) -} - -// Set fg and bg color, can also with color options -func (s *RGBStyle) Set(fg, bg RGBColor, opts ...Color) *RGBStyle { - return s.SetFg(fg).SetBg(bg).SetOpts(opts) -} - -// SetFg set fg color -func (s *RGBStyle) SetFg(fg RGBColor) *RGBStyle { - fg[3] = 1 // add fixed value, mark is valid - s.fg = fg - return s -} - -// SetBg set bg color -func (s *RGBStyle) SetBg(bg RGBColor) *RGBStyle { - bg[3] = 1 // add fixed value, mark is valid - s.bg = bg - return s -} - -// SetOpts set color options -func (s *RGBStyle) SetOpts(opts Opts) *RGBStyle { - s.opts = opts - return s -} - -// AddOpts add options -func (s *RGBStyle) AddOpts(opts ...Color) *RGBStyle { - s.opts.Add(opts...) - return s -} - -// Print print message -func (s *RGBStyle) Print(a ...interface{}) { - doPrintV2(s.String(), fmt.Sprint(a...)) -} - -// Printf format and print message -func (s *RGBStyle) Printf(format string, a ...interface{}) { - doPrintV2(s.String(), fmt.Sprintf(format, a...)) -} - -// Println print message with newline -func (s *RGBStyle) Println(a ...interface{}) { - doPrintlnV2(s.String(), a) -} - -// Sprint returns rendered message -func (s *RGBStyle) Sprint(a ...interface{}) string { - return RenderCode(s.String(), a...) -} - -// Sprintf returns format and rendered message -func (s *RGBStyle) Sprintf(format string, a ...interface{}) string { - return RenderString(s.String(), fmt.Sprintf(format, a...)) -} - -// Code convert to color code string -func (s *RGBStyle) Code() string { - return s.String() -} - -// FullCode convert to color code string -func (s *RGBStyle) FullCode() string { - return s.String() -} - -// String convert to color code string -func (s *RGBStyle) String() string { - var ss []string - // last value ensure is enable. - if s.fg[3] == 1 { - ss = append(ss, fmt.Sprintf(TplFgRGB, s.fg[0], s.fg[1], s.fg[2])) - } - - if s.bg[3] == 1 { - ss = append(ss, fmt.Sprintf(TplBgRGB, s.bg[0], s.bg[1], s.bg[2])) - } - - if s.opts.IsValid() { - ss = append(ss, s.opts.String()) - } - - return strings.Join(ss, ";") -} - -// IsEmpty style -func (s *RGBStyle) IsEmpty() bool { - return s.fg[3] != 1 && s.bg[3] != 1 -} diff --git a/vendor/github.com/gookit/color/color_tag.go b/vendor/github.com/gookit/color/color_tag.go deleted file mode 100644 index 051ba84fed7..00000000000 --- a/vendor/github.com/gookit/color/color_tag.go +++ /dev/null @@ -1,427 +0,0 @@ -package color - -import ( - "fmt" - "regexp" - "strings" -) - -// output colored text like use html tag. (not support windows cmd) -const ( - // MatchExpr regex to match color tags - // - // Notice: golang 不支持反向引用. 即不支持使用 \1 引用第一个匹配 ([a-z=;]+) - // MatchExpr = `<([a-z=;]+)>(.*?)<\/\1>` - // 所以调整一下 统一使用 `` 来结束标签,例如 "some text" - // - // allow custom attrs, eg: "content" - // (?s:...) s - 让 "." 匹配换行 - MatchExpr = `<([0-9a-zA-Z_=,;]+)>(?s:(.*?))<\/>` - - // AttrExpr regex to match custom color attributes - // eg: "content" - AttrExpr = `(fg|bg|op)[\s]*=[\s]*([0-9a-zA-Z,]+);?` - - // StripExpr regex used for removing color tags - // StripExpr = `<[\/]?[a-zA-Z=;]+>` - // 随着上面的做一些调整 - StripExpr = `<[\/]?[0-9a-zA-Z_=,;]*>` -) - -var ( - attrRegex = regexp.MustCompile(AttrExpr) - matchRegex = regexp.MustCompile(MatchExpr) - stripRegex = regexp.MustCompile(StripExpr) -) - -/************************************************************* - * internal defined color tags - *************************************************************/ - -// There are internal defined color tags -// Usage: content text -// @notice 加 0 在前面是为了防止之前的影响到现在的设置 -var colorTags = map[string]string{ - // basic tags - "red": "0;31", - "red1": "1;31", // with bold - "redB": "1;31", - "red_b": "1;31", - "blue": "0;34", - "blue1": "1;34", // with bold - "blueB": "1;34", - "blue_b": "1;34", - "cyan": "0;36", - "cyan1": "1;36", // with bold - "cyanB": "1;36", - "cyan_b": "1;36", - "green": "0;32", - "green1": "1;32", // with bold - "greenB": "1;32", - "green_b": "1;32", - "black": "0;30", - "white": "1;37", - "default": "0;39", // no color - "normal": "0;39", // no color - "brown": "0;33", // #A52A2A - "yellow": "0;33", - "ylw0": "0;33", - "yellowB": "1;33", // with bold - "ylw1": "1;33", - "ylwB": "1;33", - "magenta": "0;35", - "mga": "0;35", // short name - "magentaB": "1;35", // with bold - "mgb": "1;35", - "mgaB": "1;35", - - // light/hi tags - - "gray": "0;90", - "darkGray": "0;90", - "dark_gray": "0;90", - "lightYellow": "0;93", - "light_yellow": "0;93", - "hiYellow": "0;93", - "hi_yellow": "0;93", - "hiYellowB": "1;93", // with bold - "hi_yellow_b": "1;93", - "lightMagenta": "0;95", - "light_magenta": "0;95", - "hiMagenta": "0;95", - "hi_magenta": "0;95", - "lightMagentaB": "1;95", // with bold - "hiMagentaB": "1;95", // with bold - "hi_magenta_b": "1;95", - "lightRed": "0;91", - "light_red": "0;91", - "hiRed": "0;91", - "hi_red": "0;91", - "lightRedB": "1;91", // with bold - "light_red_b": "1;91", - "hi_red_b": "1;91", - "lightGreen": "0;92", - "light_green": "0;92", - "hiGreen": "0;92", - "hi_green": "0;92", - "lightGreenB": "1;92", - "light_green_b": "1;92", - "hi_green_b": "1;92", - "lightBlue": "0;94", - "light_blue": "0;94", - "hiBlue": "0;94", - "hi_blue": "0;94", - "lightBlueB": "1;94", - "light_blue_b": "1;94", - "hi_blue_b": "1;94", - "lightCyan": "0;96", - "light_cyan": "0;96", - "hiCyan": "0;96", - "hi_cyan": "0;96", - "lightCyanB": "1;96", - "light_cyan_b": "1;96", - "hi_cyan_b": "1;96", - "lightWhite": "0;97;40", - "light_white": "0;97;40", - - // option - "bold": "1", - "b": "1", - "underscore": "4", - "us": "4", // short name for 'underscore' - "reverse": "7", - - // alert tags, like bootstrap's alert - "suc": "1;32", // same "green" and "bold" - "success": "1;32", - "info": "0;32", // same "green", - "comment": "0;33", // same "brown" - "note": "36;1", - "notice": "36;4", - "warn": "0;1;33", - "warning": "0;30;43", - "primary": "0;34", - "danger": "1;31", // same "red" but add bold - "err": "97;41", - "error": "97;41", // fg light white; bg red -} - -/************************************************************* - * parse color tags - *************************************************************/ - -var ( - tagParser = TagParser{} - rxNumStr = regexp.MustCompile("^[0-9]{1,3}$") - rxHexCode = regexp.MustCompile("^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$") -) - -// TagParser struct -type TagParser struct { - disable bool -} - -// NewTagParser create -func NewTagParser() *TagParser { - return &TagParser{} -} - -// func (tp *TagParser) Disable() *TagParser { -// tp.disable = true -// return tp -// } - -// ParseByEnv parse given string. will check package setting. -func (tp *TagParser) ParseByEnv(str string) string { - // disable handler TAG - if !RenderTag { - return str - } - - // disable OR not support color - if !Enable || !SupportColor() { - return ClearTag(str) - } - - return tp.Parse(str) -} - -// Parse parse given string, replace color tag and return rendered string -func (tp *TagParser) Parse(str string) string { - // not contains color tag - if !strings.Contains(str, "") { - return str - } - - // find color tags by regex. str eg: "content" - matched := matchRegex.FindAllStringSubmatch(str, -1) - - // item: 0 full text 1 tag name 2 tag content - for _, item := range matched { - full, tag, content := item[0], item[1], item[2] - - // use defined tag name: "content" -> tag: "info" - if !strings.ContainsRune(tag, '=') { - code := colorTags[tag] - if len(code) > 0 { - now := RenderString(code, content) - // old := WrapTag(content, tag) is equals to var 'full' - str = strings.Replace(str, full, now, 1) - } - continue - } - - // custom color in tag - // - basic: "fg=white;bg=blue;op=bold" - if code := ParseCodeFromAttr(tag); len(code) > 0 { - now := RenderString(code, content) - str = strings.Replace(str, full, now, 1) - } - } - - return str -} - -// func (tp *TagParser) ParseAttr(attr string) (code string) { -// return -// } - -// ReplaceTag parse string, replace color tag and return rendered string -func ReplaceTag(str string) string { - return tagParser.ParseByEnv(str) -} - -// ParseCodeFromAttr parse color attributes. -// -// attr format: -// // VALUE please see var: FgColors, BgColors, AllOptions -// "fg=VALUE;bg=VALUE;op=VALUE" -// 16 color: -// "fg=yellow" -// "bg=red" -// "op=bold,underscore" option is allow multi value -// "fg=white;bg=blue;op=bold" -// "fg=white;op=bold,underscore" -// 256 color: -// "fg=167" -// "fg=167;bg=23" -// "fg=167;bg=23;op=bold" -// true color: -// // hex -// "fg=fc1cac" -// "fg=fc1cac;bg=c2c3c4" -// // r,g,b -// "fg=23,45,214" -// "fg=23,45,214;bg=109,99,88" -func ParseCodeFromAttr(attr string) (code string) { - if !strings.ContainsRune(attr, '=') { - return - } - - attr = strings.Trim(attr, ";=,") - if len(attr) == 0 { - return - } - - var codes []string - matched := attrRegex.FindAllStringSubmatch(attr, -1) - - for _, item := range matched { - pos, val := item[1], item[2] - switch pos { - case "fg": - if c, ok := FgColors[val]; ok { // basic - codes = append(codes, c.String()) - } else if c, ok := ExFgColors[val]; ok { // extra - codes = append(codes, c.String()) - } else if code := rgbHex256toCode(val, false); code != "" { - codes = append(codes, code) - } - case "bg": - if c, ok := BgColors[val]; ok { // basic bg - codes = append(codes, c.String()) - } else if c, ok := ExBgColors[val]; ok { // extra bg - codes = append(codes, c.String()) - } else if code := rgbHex256toCode(val, true); code != "" { - codes = append(codes, code) - } - case "op": // options allow multi value - if strings.Contains(val, ",") { - ns := strings.Split(val, ",") - for _, n := range ns { - if c, ok := AllOptions[n]; ok { - codes = append(codes, c.String()) - } - } - } else if c, ok := AllOptions[val]; ok { - codes = append(codes, c.String()) - } - } - } - - return strings.Join(codes, ";") -} - -func rgbHex256toCode(val string, isBg bool) (code string) { - if len(val) == 6 && rxHexCode.MatchString(val) { // hex: "fc1cac" - code = HEX(val, isBg).String() - } else if strings.ContainsRune(val, ',') { // rgb: "231,178,161" - code = strings.Replace(val, ",", ";", -1) - if isBg { - code = BgRGBPfx + code - } else { - code = FgRGBPfx + code - } - } else if len(val) < 4 && rxNumStr.MatchString(val) { // 256 code - if isBg { - code = Bg256Pfx + val - } else { - code = Fg256Pfx + val - } - } - return -} - -// ClearTag clear all tag for a string -func ClearTag(s string) string { - if !strings.Contains(s, "") { - return s - } - - return stripRegex.ReplaceAllString(s, "") -} - -/************************************************************* - * helper methods - *************************************************************/ - -// GetTagCode get color code by tag name -func GetTagCode(name string) string { - return colorTags[name] -} - -// ApplyTag for messages -func ApplyTag(tag string, a ...interface{}) string { - return RenderCode(GetTagCode(tag), a...) -} - -// WrapTag wrap a tag for a string "content" -func WrapTag(s string, tag string) string { - if s == "" || tag == "" { - return s - } - - return fmt.Sprintf("<%s>%s", tag, s) -} - -// GetColorTags get all internal color tags -func GetColorTags() map[string]string { - return colorTags -} - -// IsDefinedTag is defined tag name -func IsDefinedTag(name string) bool { - _, ok := colorTags[name] - return ok -} - -/************************************************************* - * Tag extra - *************************************************************/ - -// Tag value is a defined style name -// Usage: -// Tag("info").Println("message") -type Tag string - -// Print messages -func (tg Tag) Print(a ...interface{}) { - name := string(tg) - str := fmt.Sprint(a...) - - if stl := GetStyle(name); !stl.IsEmpty() { - stl.Print(str) - } else { - doPrintV2(GetTagCode(name), str) - } -} - -// Printf format and print messages -func (tg Tag) Printf(format string, a ...interface{}) { - name := string(tg) - str := fmt.Sprintf(format, a...) - - if stl := GetStyle(name); !stl.IsEmpty() { - stl.Print(str) - } else { - doPrintV2(GetTagCode(name), str) - } -} - -// Println messages line -func (tg Tag) Println(a ...interface{}) { - name := string(tg) - if stl := GetStyle(name); !stl.IsEmpty() { - stl.Println(a...) - } else { - doPrintlnV2(GetTagCode(name), a) - } -} - -// Sprint render messages -func (tg Tag) Sprint(a ...interface{}) string { - name := string(tg) - // if stl := GetStyle(name); !stl.IsEmpty() { - // return stl.Render(args...) - // } - - return RenderCode(GetTagCode(name), a...) -} - -// Sprintf format and render messages -func (tg Tag) Sprintf(format string, a ...interface{}) string { - tag := string(tg) - str := fmt.Sprintf(format, a...) - - return RenderString(GetTagCode(tag), str) -} diff --git a/vendor/github.com/gookit/color/convert.go b/vendor/github.com/gookit/color/convert.go deleted file mode 100644 index d641fb7679c..00000000000 --- a/vendor/github.com/gookit/color/convert.go +++ /dev/null @@ -1,593 +0,0 @@ -package color - -import ( - "fmt" - "math" - "strconv" - "strings" -) - -var ( - // ---------- basic(16) <=> 256 color convert ---------- - basicTo256Map = map[uint8]uint8{ - 30: 0, // black 000000 - 31: 160, // red c51e14 - 32: 34, // green 1dc121 - 33: 184, // yellow c7c329 - 34: 20, // blue 0a2fc4 - 35: 170, // magenta c839c5 - 36: 44, // cyan 20c5c6 - 37: 188, // white c7c7c7 - 90: 59, // lightBlack 686868 - 91: 203, // lightRed fd6f6b - 92: 83, // lightGreen 67f86f - 93: 227, // lightYellow fffa72 - 94: 69, // lightBlue 6a76fb - 95: 213, // lightMagenta fd7cfc - 96: 87, // lightCyan 68fdfe - 97: 15, // lightWhite ffffff - } - - // ---------- basic(16) <=> RGB color convert ---------- - // refer from Hyper app - basic2hexMap = map[uint8]string{ - 30: "000000", // black - 31: "c51e14", // red - 32: "1dc121", // green - 33: "c7c329", // yellow - 34: "0a2fc4", // blue - 35: "c839c5", // magenta - 36: "20c5c6", // cyan - 37: "c7c7c7", // white - 90: "686868", // lightBlack/darkGray - 91: "fd6f6b", // lightRed - 92: "67f86f", // lightGreen - 93: "fffa72", // lightYellow - 94: "6a76fb", // lightBlue - 95: "fd7cfc", // lightMagenta - 96: "68fdfe", // lightCyan - 97: "ffffff", // lightWhite - } - // will convert data from basic2hexMap - hex2basicMap = initHex2basicMap() - - // ---------- 256 <=> RGB color convert ---------- - // adapted from https://gist.github.com/MicahElliott/719710 - - c256ToHexMap = init256ToHexMap() - - // rgb to 256 color look-up table - // RGB hex => 256 code - hexTo256Table = map[string]uint8{ - // Primary 3-bit (8 colors). Unique representation! - "000000": 0, - "800000": 1, - "008000": 2, - "808000": 3, - "000080": 4, - "800080": 5, - "008080": 6, - "c0c0c0": 7, - - // Equivalent "bright" versions of original 8 colors. - "808080": 8, - "ff0000": 9, - "00ff00": 10, - "ffff00": 11, - "0000ff": 12, - "ff00ff": 13, - "00ffff": 14, - "ffffff": 15, - - // values commented out below are duplicates from the prior sections - - // Strictly ascending. - // "000000": 16, - "000001": 16, // up: avoid key conflicts, value + 1 - "00005f": 17, - "000087": 18, - "0000af": 19, - "0000d7": 20, - // "0000ff": 21, - "0000fe": 21, // up: avoid key conflicts, value - 1 - "005f00": 22, - "005f5f": 23, - "005f87": 24, - "005faf": 25, - "005fd7": 26, - "005fff": 27, - "008700": 28, - "00875f": 29, - "008787": 30, - "0087af": 31, - "0087d7": 32, - "0087ff": 33, - "00af00": 34, - "00af5f": 35, - "00af87": 36, - "00afaf": 37, - "00afd7": 38, - "00afff": 39, - "00d700": 40, - "00d75f": 41, - "00d787": 42, - "00d7af": 43, - "00d7d7": 44, - "00d7ff": 45, - // "00ff00": 46, - "00ff01": 46, // up: avoid key conflicts, value + 1 - "00ff5f": 47, - "00ff87": 48, - "00ffaf": 49, - "00ffd7": 50, - // "00ffff": 51, - "00fffe": 51, // up: avoid key conflicts, value - 1 - "5f0000": 52, - "5f005f": 53, - "5f0087": 54, - "5f00af": 55, - "5f00d7": 56, - "5f00ff": 57, - "5f5f00": 58, - "5f5f5f": 59, - "5f5f87": 60, - "5f5faf": 61, - "5f5fd7": 62, - "5f5fff": 63, - "5f8700": 64, - "5f875f": 65, - "5f8787": 66, - "5f87af": 67, - "5f87d7": 68, - "5f87ff": 69, - "5faf00": 70, - "5faf5f": 71, - "5faf87": 72, - "5fafaf": 73, - "5fafd7": 74, - "5fafff": 75, - "5fd700": 76, - "5fd75f": 77, - "5fd787": 78, - "5fd7af": 79, - "5fd7d7": 80, - "5fd7ff": 81, - "5fff00": 82, - "5fff5f": 83, - "5fff87": 84, - "5fffaf": 85, - "5fffd7": 86, - "5fffff": 87, - "870000": 88, - "87005f": 89, - "870087": 90, - "8700af": 91, - "8700d7": 92, - "8700ff": 93, - "875f00": 94, - "875f5f": 95, - "875f87": 96, - "875faf": 97, - "875fd7": 98, - "875fff": 99, - "878700": 100, - "87875f": 101, - "878787": 102, - "8787af": 103, - "8787d7": 104, - "8787ff": 105, - "87af00": 106, - "87af5f": 107, - "87af87": 108, - "87afaf": 109, - "87afd7": 110, - "87afff": 111, - "87d700": 112, - "87d75f": 113, - "87d787": 114, - "87d7af": 115, - "87d7d7": 116, - "87d7ff": 117, - "87ff00": 118, - "87ff5f": 119, - "87ff87": 120, - "87ffaf": 121, - "87ffd7": 122, - "87ffff": 123, - "af0000": 124, - "af005f": 125, - "af0087": 126, - "af00af": 127, - "af00d7": 128, - "af00ff": 129, - "af5f00": 130, - "af5f5f": 131, - "af5f87": 132, - "af5faf": 133, - "af5fd7": 134, - "af5fff": 135, - "af8700": 136, - "af875f": 137, - "af8787": 138, - "af87af": 139, - "af87d7": 140, - "af87ff": 141, - "afaf00": 142, - "afaf5f": 143, - "afaf87": 144, - "afafaf": 145, - "afafd7": 146, - "afafff": 147, - "afd700": 148, - "afd75f": 149, - "afd787": 150, - "afd7af": 151, - "afd7d7": 152, - "afd7ff": 153, - "afff00": 154, - "afff5f": 155, - "afff87": 156, - "afffaf": 157, - "afffd7": 158, - "afffff": 159, - "d70000": 160, - "d7005f": 161, - "d70087": 162, - "d700af": 163, - "d700d7": 164, - "d700ff": 165, - "d75f00": 166, - "d75f5f": 167, - "d75f87": 168, - "d75faf": 169, - "d75fd7": 170, - "d75fff": 171, - "d78700": 172, - "d7875f": 173, - "d78787": 174, - "d787af": 175, - "d787d7": 176, - "d787ff": 177, - "d7af00": 178, - "d7af5f": 179, - "d7af87": 180, - "d7afaf": 181, - "d7afd7": 182, - "d7afff": 183, - "d7d700": 184, - "d7d75f": 185, - "d7d787": 186, - "d7d7af": 187, - "d7d7d7": 188, - "d7d7ff": 189, - "d7ff00": 190, - "d7ff5f": 191, - "d7ff87": 192, - "d7ffaf": 193, - "d7ffd7": 194, - "d7ffff": 195, - // "ff0000": 196, - "ff0001": 196, // up: avoid key conflicts, value + 1 - "ff005f": 197, - "ff0087": 198, - "ff00af": 199, - "ff00d7": 200, - // "ff00ff": 201, - "ff00fe": 201, // up: avoid key conflicts, value - 1 - "ff5f00": 202, - "ff5f5f": 203, - "ff5f87": 204, - "ff5faf": 205, - "ff5fd7": 206, - "ff5fff": 207, - "ff8700": 208, - "ff875f": 209, - "ff8787": 210, - "ff87af": 211, - "ff87d7": 212, - "ff87ff": 213, - "ffaf00": 214, - "ffaf5f": 215, - "ffaf87": 216, - "ffafaf": 217, - "ffafd7": 218, - "ffafff": 219, - "ffd700": 220, - "ffd75f": 221, - "ffd787": 222, - "ffd7af": 223, - "ffd7d7": 224, - "ffd7ff": 225, - // "ffff00": 226, - "ffff01": 226, // up: avoid key conflicts, value + 1 - "ffff5f": 227, - "ffff87": 228, - "ffffaf": 229, - "ffffd7": 230, - // "ffffff": 231, - "fffffe": 231, // up: avoid key conflicts, value - 1 - - // Gray-scale range. - "080808": 232, - "121212": 233, - "1c1c1c": 234, - "262626": 235, - "303030": 236, - "3a3a3a": 237, - "444444": 238, - "4e4e4e": 239, - "585858": 240, - "626262": 241, - "6c6c6c": 242, - "767676": 243, - // "808080": 244, - "808081": 244, // up: avoid key conflicts, value + 1 - "8a8a8a": 245, - "949494": 246, - "9e9e9e": 247, - "a8a8a8": 248, - "b2b2b2": 249, - "bcbcbc": 250, - "c6c6c6": 251, - "d0d0d0": 252, - "dadada": 253, - "e4e4e4": 254, - "eeeeee": 255, - } - - incs = []uint8{0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff} -) - -func initHex2basicMap() map[string]uint8 { - h2b := make(map[string]uint8, len(basic2hexMap)) - // ini data map - for u, s := range basic2hexMap { - h2b[s] = u - } - return h2b -} - -func init256ToHexMap() map[uint8]string { - c256toh := make(map[uint8]string, len(hexTo256Table)) - // ini data map - for hex, c256 := range hexTo256Table { - c256toh[c256] = hex - } - return c256toh -} - -// RgbTo256Table mapping data -func RgbTo256Table() map[string]uint8 { - return hexTo256Table -} - -// Colors2code convert colors to code. return like "32;45;3" -func Colors2code(colors ...Color) string { - if len(colors) == 0 { - return "" - } - - var codes []string - for _, color := range colors { - codes = append(codes, color.String()) - } - - return strings.Join(codes, ";") -} - -/************************************************************* - * HEX code <=> RGB/True color code - *************************************************************/ - -// Hex2rgb alias of the HexToRgb() -func Hex2rgb(hex string) []int { return HexToRgb(hex) } - -// HexToRGB alias of the HexToRgb() -func HexToRGB(hex string) []int { return HexToRgb(hex) } - -// HexToRgb convert hex color string to RGB numbers -// -// Usage: -// rgb := HexToRgb("ccc") // rgb: [204 204 204] -// rgb := HexToRgb("aabbcc") // rgb: [170 187 204] -// rgb := HexToRgb("#aabbcc") // rgb: [170 187 204] -// rgb := HexToRgb("0xad99c0") // rgb: [170 187 204] -func HexToRgb(hex string) (rgb []int) { - hex = strings.TrimSpace(hex) - if hex == "" { - return - } - - // like from css. eg "#ccc" "#ad99c0" - if hex[0] == '#' { - hex = hex[1:] - } - - hex = strings.ToLower(hex) - switch len(hex) { - case 3: // "ccc" - hex = string([]byte{hex[0], hex[0], hex[1], hex[1], hex[2], hex[2]}) - case 8: // "0xad99c0" - hex = strings.TrimPrefix(hex, "0x") - } - - // recheck - if len(hex) != 6 { - return - } - - // convert string to int64 - if i64, err := strconv.ParseInt(hex, 16, 32); err == nil { - color := int(i64) - // parse int - rgb = make([]int, 3) - rgb[0] = color >> 16 - rgb[1] = (color & 0x00FF00) >> 8 - rgb[2] = color & 0x0000FF - } - return -} - -// Rgb2hex alias of the RgbToHex() -func Rgb2hex(rgb []int) string { return RgbToHex(rgb) } - -// RgbToHex convert RGB-code to hex-code -// -// Usage: -// hex := RgbToHex([]int{170, 187, 204}) // hex: "aabbcc" -func RgbToHex(rgb []int) string { - hexNodes := make([]string, len(rgb)) - - for _, v := range rgb { - hexNodes = append(hexNodes, strconv.FormatInt(int64(v), 16)) - } - return strings.Join(hexNodes, "") -} - -/************************************************************* - * 4bit(16) color <=> RGB/True color - *************************************************************/ - -// Basic2hex convert basic color to hex string. -func Basic2hex(val uint8) string { - return basic2hexMap[val] -} - -// Hex2basic convert hex string to basic color code. -func Hex2basic(hex string) uint8 { - return hex2basicMap[hex] -} - -// Rgb2basic alias of the RgbToAnsi() -func Rgb2basic(r, g, b uint8, isBg bool) uint8 { - // is basic color, direct use static map data. - hex := RgbToHex([]int{int(r), int(g), int(b)}) - if val, ok := hex2basicMap[hex]; ok { - if isBg { - return val + 10 - } - return val - } - - return RgbToAnsi(r, g, b, isBg) -} - -// Rgb2ansi alias of the RgbToAnsi() -func Rgb2ansi(r, g, b uint8, isBg bool) uint8 { - return RgbToAnsi(r, g, b, isBg) -} - -// RgbToAnsi convert RGB-code to 16-code -// refer https://github.com/radareorg/radare2/blob/master/libr/cons/rgb.c#L249-L271 -func RgbToAnsi(r, g, b uint8, isBg bool) uint8 { - var bright, c, k uint8 - base := compareVal(isBg, BgBase, FgBase) - - // eco bright-specific - if r == 0x80 && g == 0x80 && b == 0x80 { // 0x80=128 - bright = 53 - } else if r == 0xff || g == 0xff || b == 0xff { // 0xff=255 - bright = 60 - } // else bright = 0 - - if r == g && g == b { - // 0x7f=127 - // r = (r > 0x7f) ? 1 : 0; - r = compareVal(r > 0x7f, 1, 0) - g = compareVal(g > 0x7f, 1, 0) - b = compareVal(b > 0x7f, 1, 0) - } else { - k = (r + g + b) / 3 - - // r = (r >= k) ? 1 : 0; - r = compareVal(r >= k, 1, 0) - g = compareVal(g >= k, 1, 0) - b = compareVal(b >= k, 1, 0) - } - - // c = (r ? 1 : 0) + (g ? (b ? 6 : 2) : (b ? 4 : 0)) - c = compareVal(r > 0, 1, 0) - - if g > 0 { - c += compareVal(b > 0, 6, 2) - } else { - c += compareVal(b > 0, 4, 0) - } - return base + bright + c -} - -/************************************************************* - * 8bit(256) color <=> RGB/True color - *************************************************************/ - -// Rgb2short convert RGB-code to 256-code -func Rgb2short(r, g, b uint8) uint8 { - return RgbTo256(r, g, b) -} - -// RgbTo256 convert RGB-code to 256-code -func RgbTo256(r, g, b uint8) uint8 { - res := make([]uint8, 3) - for partI, part := range [3]uint8{r, g, b} { - i := 0 - for i < len(incs)-1 { - s, b := incs[i], incs[i+1] // smaller, bigger - if s <= part && part <= b { - s1 := math.Abs(float64(s) - float64(part)) - b1 := math.Abs(float64(b) - float64(part)) - var closest uint8 - if s1 < b1 { - closest = s - } else { - closest = b - } - res[partI] = closest - break - } - i++ - } - } - hex := fmt.Sprintf("%02x%02x%02x", res[0], res[1], res[2]) - equiv := hexTo256Table[hex] - return equiv -} - -// C256ToRgb convert an 256 color code to RGB numbers -func C256ToRgb(val uint8) (rgb []uint8) { - hex := c256ToHexMap[val] - // convert to rgb code - rgbInts := Hex2rgb(hex) - - return []uint8{ - uint8(rgbInts[0]), - uint8(rgbInts[1]), - uint8(rgbInts[2]), - } -} - -// C256ToRgbV1 convert an 256 color code to RGB numbers -// refer https://github.com/torvalds/linux/commit/cec5b2a97a11ade56a701e83044d0a2a984c67b4 -func C256ToRgbV1(val uint8) (rgb []uint8) { - var r, g, b uint8 - if val < 8 { // Standard colours. - // r = val&1 ? 0xaa : 0x00; - r = compareVal(val&1 == 1, 0xaa, 0x00) - g = compareVal(val&2 == 2, 0xaa, 0x00) - b = compareVal(val&4 == 4, 0xaa, 0x00) - } else if val < 16 { - // r = val & 1 ? 0xff : 0x55; - r = compareVal(val&1 == 1, 0xff, 0x55) - g = compareVal(val&2 == 2, 0xff, 0x55) - b = compareVal(val&4 == 4, 0xff, 0x55) - } else if val < 232 { /* 6x6x6 colour cube. */ - r = (val - 16) / 36 * 85 / 2 - g = (val - 16) / 6 % 6 * 85 / 2 - b = (val - 16) % 6 * 85 / 2 - } else { /* Grayscale ramp. */ - nv := uint8(int(val)*10 - 2312) - // set value - r, g, b = nv, nv, nv - } - - return []uint8{r, g, b} -} diff --git a/vendor/github.com/gookit/color/detect_env.go b/vendor/github.com/gookit/color/detect_env.go deleted file mode 100644 index f5dde8fda3d..00000000000 --- a/vendor/github.com/gookit/color/detect_env.go +++ /dev/null @@ -1,281 +0,0 @@ -package color - -import ( - "io" - "io/ioutil" - "os" - "runtime" - "strconv" - "strings" - "syscall" - - "github.com/xo/terminfo" -) - -/************************************************************* - * helper methods for detect color supports - *************************************************************/ - -// DetectColorLevel for current env -// -// NOTICE: The method will detect terminal info each times, -// if only want get current color level, please direct call SupportColor() or TermColorLevel() -func DetectColorLevel() terminfo.ColorLevel { - level, _ := detectTermColorLevel() - return level -} - -// detect terminal color support level -// -// refer https://github.com/Delta456/box-cli-maker -func detectTermColorLevel() (level terminfo.ColorLevel, needVTP bool) { - // on windows WSL: - // - runtime.GOOS == "Linux" - // - support true-color - // env: - // WSL_DISTRO_NAME=Debian - if val := os.Getenv("WSL_DISTRO_NAME"); val != "" { - // detect WSL as it has True Color support - if detectWSL() { - debugf("True Color support on WSL environment") - return terminfo.ColorLevelMillions, false - } - } - - isWin := runtime.GOOS == "windows" - termVal := os.Getenv("TERM") - - // on TERM=screen: not support true-color - if termVal != "screen" { - // On JetBrains Terminal - // - support true-color - // env: - // TERMINAL_EMULATOR=JetBrains-JediTerm - val := os.Getenv("TERMINAL_EMULATOR") - if val == "JetBrains-JediTerm" { - debugf("True Color support on JetBrains-JediTerm, is win: %v", isWin) - return terminfo.ColorLevelMillions, isWin - } - } - - // level, err = terminfo.ColorLevelFromEnv() - level = detectColorLevelFromEnv(termVal, isWin) - debugf("color level by detectColorLevelFromEnv: %s", level.String()) - - // fallback: simple detect by TERM value string. - if level == terminfo.ColorLevelNone { - debugf("level none - fallback check special term color support") - // on Windows: enable VTP as it has True Color support - level, needVTP = detectSpecialTermColor(termVal) - } - return -} - -// detectColorFromEnv returns the color level COLORTERM, FORCE_COLOR, -// TERM_PROGRAM, or determined from the TERM environment variable. -// -// refer the terminfo.ColorLevelFromEnv() -// https://en.wikipedia.org/wiki/Terminfo -func detectColorLevelFromEnv(termVal string, isWin bool) terminfo.ColorLevel { - // check for overriding environment variables - colorTerm, termProg, forceColor := os.Getenv("COLORTERM"), os.Getenv("TERM_PROGRAM"), os.Getenv("FORCE_COLOR") - switch { - case strings.Contains(colorTerm, "truecolor") || strings.Contains(colorTerm, "24bit"): - if termVal == "screen" { // on TERM=screen: not support true-color - return terminfo.ColorLevelHundreds - } - return terminfo.ColorLevelMillions - case colorTerm != "" || forceColor != "": - return terminfo.ColorLevelBasic - case termProg == "Apple_Terminal": - return terminfo.ColorLevelHundreds - case termProg == "Terminus" || termProg == "Hyper": - if termVal == "screen" { // on TERM=screen: not support true-color - return terminfo.ColorLevelHundreds - } - return terminfo.ColorLevelMillions - case termProg == "iTerm.app": - if termVal == "screen" { // on TERM=screen: not support true-color - return terminfo.ColorLevelHundreds - } - - // check iTerm version - ver := os.Getenv("TERM_PROGRAM_VERSION") - if ver != "" { - i, err := strconv.Atoi(strings.Split(ver, ".")[0]) - if err != nil { - saveInternalError(terminfo.ErrInvalidTermProgramVersion) - // return terminfo.ColorLevelNone - return terminfo.ColorLevelHundreds - } - if i == 3 { - return terminfo.ColorLevelMillions - } - } - return terminfo.ColorLevelHundreds - } - - // otherwise determine from TERM's max_colors capability - if !isWin && termVal != "" { - debugf("TERM=%s - check color level by load terminfo file", termVal) - ti, err := terminfo.Load(termVal) - if err != nil { - saveInternalError(err) - return terminfo.ColorLevelNone - } - - debugf("the loaded term info file is: %s", ti.File) - v, ok := ti.Nums[terminfo.MaxColors] - switch { - case !ok || v <= 16: - return terminfo.ColorLevelNone - case ok && v >= 256: - return terminfo.ColorLevelHundreds - } - return terminfo.ColorLevelBasic - } - - // no TERM env value. default return none level - return terminfo.ColorLevelNone - // return terminfo.ColorLevelBasic -} - -var detectedWSL bool -var wslContents string - -// https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364 -func detectWSL() bool { - if !detectedWSL { - b := make([]byte, 1024) - // `cat /proc/version` - // on mac: - // !not the file! - // on linux(debian,ubuntu,alpine): - // Linux version 4.19.121-linuxkit (root@18b3f92ade35) (gcc version 9.2.0 (Alpine 9.2.0)) #1 SMP Thu Jan 21 15:36:34 UTC 2021 - // on win git bash, conEmu: - // MINGW64_NT-10.0-19042 version 3.1.7-340.x86_64 (@WIN-N0G619FD3UK) (gcc version 9.3.0 (GCC) ) 2020-10-23 13:08 UTC - // on WSL: - // Linux version 4.4.0-19041-Microsoft (Microsoft@Microsoft.com) (gcc version 5.4.0 (GCC) ) #488-Microsoft Mon Sep 01 13:43:00 PST 2020 - f, err := os.Open("/proc/version") - if err == nil { - _, _ = f.Read(b) // ignore error - if err = f.Close(); err != nil { - saveInternalError(err) - } - - wslContents = string(b) - } - detectedWSL = true - } - return strings.Contains(wslContents, "Microsoft") -} - -// refer -// https://github.com/Delta456/box-cli-maker/blob/7b5a1ad8a016ce181e7d8b05e24b54ff60b4b38a/detect_unix.go#L27-L45 -// detect WSL as it has True Color support -func isWSL() bool { - // on windows WSL: - // - runtime.GOOS == "Linux" - // - support true-color - // WSL_DISTRO_NAME=Debian - if val := os.Getenv("WSL_DISTRO_NAME"); val == "" { - return false - } - - // `cat /proc/sys/kernel/osrelease` - // on mac: - // !not the file! - // on linux: - // 4.19.121-linuxkit - // on WSL Output: - // 4.4.0-19041-Microsoft - wsl, err := ioutil.ReadFile("/proc/sys/kernel/osrelease") - if err != nil { - saveInternalError(err) - return false - } - - // it gives "Microsoft" for WSL and "microsoft" for WSL 2 - // it support True-color - content := strings.ToLower(string(wsl)) - return strings.Contains(content, "microsoft") -} - -/************************************************************* - * helper methods for check env - *************************************************************/ - -// IsWindows OS env -func IsWindows() bool { - return runtime.GOOS == "windows" -} - -// IsConsole Determine whether w is one of stderr, stdout, stdin -func IsConsole(w io.Writer) bool { - o, ok := w.(*os.File) - if !ok { - return false - } - - fd := o.Fd() - - // fix: cannot use 'o == os.Stdout' to compare - return fd == uintptr(syscall.Stdout) || fd == uintptr(syscall.Stdin) || fd == uintptr(syscall.Stderr) -} - -// IsMSys msys(MINGW64) environment, does not necessarily support color -func IsMSys() bool { - // like "MSYSTEM=MINGW64" - if len(os.Getenv("MSYSTEM")) > 0 { - return true - } - - return false -} - -// IsSupportColor check current console is support color. -// -// NOTICE: The method will detect terminal info each times, -// if only want get current color level, please direct call SupportColor() or TermColorLevel() -func IsSupportColor() bool { - return IsSupport16Color() -} - -// IsSupportColor check current console is support color. -// -// NOTICE: The method will detect terminal info each times, -// if only want get current color level, please direct call SupportColor() or TermColorLevel() -func IsSupport16Color() bool { - level, _ := detectTermColorLevel() - return level > terminfo.ColorLevelNone -} - -// IsSupport256Color render check -// -// NOTICE: The method will detect terminal info each times, -// if only want get current color level, please direct call SupportColor() or TermColorLevel() -func IsSupport256Color() bool { - level, _ := detectTermColorLevel() - return level > terminfo.ColorLevelBasic -} - -// IsSupportRGBColor check. alias of the IsSupportTrueColor() -// -// NOTICE: The method will detect terminal info each times, -// if only want get current color level, please direct call SupportColor() or TermColorLevel() -func IsSupportRGBColor() bool { - return IsSupportTrueColor() -} - -// IsSupportTrueColor render check. -// -// NOTICE: The method will detect terminal info each times, -// if only want get current color level, please direct call SupportColor() or TermColorLevel() -// -// ENV: -// "COLORTERM=truecolor" -// "COLORTERM=24bit" -func IsSupportTrueColor() bool { - level, _ := detectTermColorLevel() - return level > terminfo.ColorLevelHundreds -} diff --git a/vendor/github.com/gookit/color/detect_nonwin.go b/vendor/github.com/gookit/color/detect_nonwin.go deleted file mode 100644 index 75c7202f0bc..00000000000 --- a/vendor/github.com/gookit/color/detect_nonwin.go +++ /dev/null @@ -1,48 +0,0 @@ -// +build !windows - -// The method in the file has no effect -// Only for compatibility with non-Windows systems - -package color - -import ( - "strings" - "syscall" - - "github.com/xo/terminfo" -) - -// detect special term color support -func detectSpecialTermColor(termVal string) (terminfo.ColorLevel, bool) { - if termVal == "" { - return terminfo.ColorLevelNone, false - } - - debugf("terminfo check fail - fallback detect color by check TERM value") - - // on TERM=screen: - // - support 256, not support true-color. test on macOS - if termVal == "screen" { - return terminfo.ColorLevelHundreds, false - } - - if strings.Contains(termVal, "256color") { - return terminfo.ColorLevelHundreds, false - } - - if strings.Contains(termVal, "xterm") { - return terminfo.ColorLevelHundreds, false - // return terminfo.ColorLevelBasic, false - } - - // return terminfo.ColorLevelNone, nil - return terminfo.ColorLevelBasic, false -} - -// IsTerminal returns true if the given file descriptor is a terminal. -// -// Usage: -// IsTerminal(os.Stdout.Fd()) -func IsTerminal(fd uintptr) bool { - return fd == uintptr(syscall.Stdout) || fd == uintptr(syscall.Stdin) || fd == uintptr(syscall.Stderr) -} diff --git a/vendor/github.com/gookit/color/detect_windows.go b/vendor/github.com/gookit/color/detect_windows.go deleted file mode 100644 index 7707d9ca27f..00000000000 --- a/vendor/github.com/gookit/color/detect_windows.go +++ /dev/null @@ -1,243 +0,0 @@ -// +build windows - -// Display color on windows -// refer: -// golang.org/x/sys/windows -// golang.org/x/crypto/ssh/terminal -// https://docs.microsoft.com/en-us/windows/console -package color - -import ( - "os" - "syscall" - "unsafe" - - "github.com/xo/terminfo" - "golang.org/x/sys/windows" -) - -// related docs -// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences -// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples -var ( - // isMSys bool - kernel32 *syscall.LazyDLL - - procGetConsoleMode *syscall.LazyProc - procSetConsoleMode *syscall.LazyProc -) - -func init() { - if !SupportColor() { - isLikeInCmd = true - return - } - - // if disabled. - if !Enable { - return - } - - // if at windows's ConEmu, Cmder, putty ... terminals not need VTP - - // -------- try force enable colors on windows terminal ------- - tryEnableVTP(needVTP) - - // fetch console screen buffer info - // err := getConsoleScreenBufferInfo(uintptr(syscall.Stdout), &defScreenInfo) -} - -// try force enable colors on windows terminal -func tryEnableVTP(enable bool) bool { - if !enable { - return false - } - - debugf("True-Color by enable VirtualTerminalProcessing on windows") - - initKernel32Proc() - - // enable colors on windows terminal - if tryEnableOnCONOUT() { - return true - } - - return tryEnableOnStdout() -} - -func initKernel32Proc() { - if kernel32 != nil { - return - } - - // load related windows dll - // https://docs.microsoft.com/en-us/windows/console/setconsolemode - kernel32 = syscall.NewLazyDLL("kernel32.dll") - - procGetConsoleMode = kernel32.NewProc("GetConsoleMode") - procSetConsoleMode = kernel32.NewProc("SetConsoleMode") -} - -func tryEnableOnCONOUT() bool { - outHandle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0) - if err != nil { - saveInternalError(err) - return false - } - - err = EnableVirtualTerminalProcessing(outHandle, true) - if err != nil { - saveInternalError(err) - return false - } - - return true -} - -func tryEnableOnStdout() bool { - // try direct open syscall.Stdout - err := EnableVirtualTerminalProcessing(syscall.Stdout, true) - if err != nil { - saveInternalError(err) - return false - } - - return true -} - -// Get the Windows Version and Build Number -var ( - winVersion, _, buildNumber = windows.RtlGetNtVersionNumbers() -) - -// refer -// https://github.com/Delta456/box-cli-maker/blob/7b5a1ad8a016ce181e7d8b05e24b54ff60b4b38a/detect_windows.go#L30-L57 -// https://github.com/gookit/color/issues/25#issuecomment-738727917 -// detects the Color Level Supported on windows: cmd, powerShell -func detectSpecialTermColor(termVal string) (tl terminfo.ColorLevel, needVTP bool) { - if os.Getenv("ConEmuANSI") == "ON" { - debugf("support True Color by ConEmuANSI=ON") - // ConEmuANSI is "ON" for generic ANSI support - // but True Color option is enabled by default - // I am just assuming that people wouldn't have disabled it - // Even if it is not enabled then ConEmu will auto round off - // accordingly - return terminfo.ColorLevelMillions, false - } - - // Before Windows 10 Build Number 10586, console never supported ANSI Colors - if buildNumber < 10586 || winVersion < 10 { - // Detect if using ANSICON on older systems - if os.Getenv("ANSICON") != "" { - conVersion := os.Getenv("ANSICON_VER") - // 8 bit Colors were only supported after v1.81 release - if conVersion >= "181" { - return terminfo.ColorLevelHundreds, false - } - return terminfo.ColorLevelBasic, false - } - - return terminfo.ColorLevelNone, false - } - - // True Color is not available before build 14931 so fallback to 8 bit color. - if buildNumber < 14931 { - return terminfo.ColorLevelHundreds, true - } - - // Windows 10 build 14931 is the first release that supports 16m/TrueColor - debugf("support True Color on windows version is >= build 14931") - return terminfo.ColorLevelMillions, true -} - -/************************************************************* - * render full color code on windows(8,16,24bit color) - *************************************************************/ - -// docs https://docs.microsoft.com/zh-cn/windows/console/getconsolemode#parameters -const ( - // equals to docs page's ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 - EnableVirtualTerminalProcessingMode uint32 = 0x4 -) - -// EnableVirtualTerminalProcessing Enable virtual terminal processing -// -// ref from github.com/konsorten/go-windows-terminal-sequences -// doc https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples -// -// Usage: -// err := EnableVirtualTerminalProcessing(syscall.Stdout, true) -// // support print color text -// err = EnableVirtualTerminalProcessing(syscall.Stdout, false) -func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error { - var mode uint32 - // Check if it is currently in the terminal - // err := syscall.GetConsoleMode(syscall.Stdout, &mode) - err := syscall.GetConsoleMode(stream, &mode) - if err != nil { - // fmt.Println("EnableVirtualTerminalProcessing", err) - return err - } - - if enable { - mode |= EnableVirtualTerminalProcessingMode - } else { - mode &^= EnableVirtualTerminalProcessingMode - } - - ret, _, err := procSetConsoleMode.Call(uintptr(stream), uintptr(mode)) - if ret == 0 { - return err - } - - return nil -} - -// renderColorCodeOnCmd enable cmd color render. -// func renderColorCodeOnCmd(fn func()) { -// err := EnableVirtualTerminalProcessing(syscall.Stdout, true) -// // if is not in terminal, will clear color tag. -// if err != nil { -// // panic(err) -// fn() -// return -// } -// -// // force open color render -// old := ForceOpenColor() -// fn() -// // revert color setting -// supportColor = old -// -// err = EnableVirtualTerminalProcessing(syscall.Stdout, false) -// if err != nil { -// panic(err) -// } -// } - -/************************************************************* - * render simple color code on windows - *************************************************************/ - -// IsTty returns true if the given file descriptor is a terminal. -func IsTty(fd uintptr) bool { - initKernel32Proc() - - var st uint32 - r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) - return r != 0 && e == 0 -} - -// IsTerminal returns true if the given file descriptor is a terminal. -// -// Usage: -// fd := os.Stdout.Fd() -// fd := uintptr(syscall.Stdout) // for windows -// IsTerminal(fd) -func IsTerminal(fd uintptr) bool { - initKernel32Proc() - - var st uint32 - r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) - return r != 0 && e == 0 -} diff --git a/vendor/github.com/gookit/color/printer.go b/vendor/github.com/gookit/color/printer.go deleted file mode 100644 index 326aabc0b45..00000000000 --- a/vendor/github.com/gookit/color/printer.go +++ /dev/null @@ -1,122 +0,0 @@ -package color - -import "fmt" - -/************************************************************* - * colored message Printer - *************************************************************/ - -// PrinterFace interface -type PrinterFace interface { - fmt.Stringer - Sprint(a ...interface{}) string - Sprintf(format string, a ...interface{}) string - Print(a ...interface{}) - Printf(format string, a ...interface{}) - Println(a ...interface{}) -} - -// Printer a generic color message printer. -// -// Usage: -// p := &Printer{Code: "32;45;3"} -// p.Print("message") -type Printer struct { - // NoColor disable color. - NoColor bool - // Code color code string. eg "32;45;3" - Code string -} - -// NewPrinter instance -func NewPrinter(colorCode string) *Printer { - return &Printer{Code: colorCode} -} - -// String returns color code string. eg: "32;45;3" -func (p *Printer) String() string { - // panic("implement me") - return p.Code -} - -// Sprint returns rendering colored messages -func (p *Printer) Sprint(a ...interface{}) string { - return RenderCode(p.String(), a...) -} - -// Sprintf returns format and rendering colored messages -func (p *Printer) Sprintf(format string, a ...interface{}) string { - return RenderString(p.String(), fmt.Sprintf(format, a...)) -} - -// Print rendering colored messages -func (p *Printer) Print(a ...interface{}) { - doPrintV2(p.String(), fmt.Sprint(a...)) -} - -// Printf format and rendering colored messages -func (p *Printer) Printf(format string, a ...interface{}) { - doPrintV2(p.String(), fmt.Sprintf(format, a...)) -} - -// Println rendering colored messages with newline -func (p *Printer) Println(a ...interface{}) { - doPrintlnV2(p.Code, a) -} - -// IsEmpty color code -func (p *Printer) IsEmpty() bool { - return p.Code == "" -} - -/************************************************************* - * SimplePrinter struct - *************************************************************/ - -// SimplePrinter use for quick use color print on inject to struct -type SimplePrinter struct{} - -// Print message -func (s *SimplePrinter) Print(v ...interface{}) { - Print(v...) -} - -// Printf message -func (s *SimplePrinter) Printf(format string, v ...interface{}) { - Printf(format, v...) -} - -// Println message -func (s *SimplePrinter) Println(v ...interface{}) { - Println(v...) -} - -// Infof message -func (s *SimplePrinter) Infof(format string, a ...interface{}) { - Info.Printf(format, a...) -} - -// Infoln message -func (s *SimplePrinter) Infoln(a ...interface{}) { - Info.Println(a...) -} - -// Warnf message -func (s *SimplePrinter) Warnf(format string, a ...interface{}) { - Warn.Printf(format, a...) -} - -// Warnln message -func (s *SimplePrinter) Warnln(a ...interface{}) { - Warn.Println(a...) -} - -// Errorf message -func (s *SimplePrinter) Errorf(format string, a ...interface{}) { - Error.Printf(format, a...) -} - -// Errorln message -func (s *SimplePrinter) Errorln(a ...interface{}) { - Error.Println(a...) -} diff --git a/vendor/github.com/gookit/color/quickstart.go b/vendor/github.com/gookit/color/quickstart.go deleted file mode 100644 index 3cc3b77fa6a..00000000000 --- a/vendor/github.com/gookit/color/quickstart.go +++ /dev/null @@ -1,109 +0,0 @@ -package color - -/************************************************************* - * quick use color print message - *************************************************************/ - -// Redp print message with Red color -func Redp(a ...interface{}) { - Red.Print(a...) -} - -// Redln print message line with Red color -func Redln(a ...interface{}) { - Red.Println(a...) -} - -// Bluep print message with Blue color -func Bluep(a ...interface{}) { - Blue.Print(a...) -} - -// Blueln print message line with Blue color -func Blueln(a ...interface{}) { - Blue.Println(a...) -} - -// Cyanp print message with Cyan color -func Cyanp(a ...interface{}) { - Cyan.Print(a...) -} - -// Cyanln print message line with Cyan color -func Cyanln(a ...interface{}) { - Cyan.Println(a...) -} - -// Grayp print message with Gray color -func Grayp(a ...interface{}) { - Gray.Print(a...) -} - -// Grayln print message line with Gray color -func Grayln(a ...interface{}) { - Gray.Println(a...) -} - -// Greenp print message with Green color -func Greenp(a ...interface{}) { - Green.Print(a...) -} - -// Greenln print message line with Green color -func Greenln(a ...interface{}) { - Green.Println(a...) -} - -// Yellowp print message with Yellow color -func Yellowp(a ...interface{}) { - Yellow.Print(a...) -} - -// Yellowln print message line with Yellow color -func Yellowln(a ...interface{}) { - Yellow.Println(a...) -} - -// Magentap print message with Magenta color -func Magentap(a ...interface{}) { - Magenta.Print(a...) -} - -// Magentaln print message line with Magenta color -func Magentaln(a ...interface{}) { - Magenta.Println(a...) -} - -/************************************************************* - * quick use style print message - *************************************************************/ - -// Infof print message with Info style -func Infof(format string, a ...interface{}) { - Info.Printf(format, a...) -} - -// Infoln print message with Info style -func Infoln(a ...interface{}) { - Info.Println(a...) -} - -// Errorf print message with Error style -func Errorf(format string, a ...interface{}) { - Error.Printf(format, a...) -} - -// Errorln print message with Error style -func Errorln(a ...interface{}) { - Error.Println(a...) -} - -// Warnf print message with Warn style -func Warnf(format string, a ...interface{}) { - Warn.Printf(format, a...) -} - -// Warnln print message with Warn style -func Warnln(a ...interface{}) { - Warn.Println(a...) -} diff --git a/vendor/github.com/gookit/color/style.go b/vendor/github.com/gookit/color/style.go deleted file mode 100644 index fad76fb3371..00000000000 --- a/vendor/github.com/gookit/color/style.go +++ /dev/null @@ -1,315 +0,0 @@ -package color - -import ( - "fmt" - "strings" -) - -/************************************************************* - * 16 color Style - *************************************************************/ - -// Style a 16 color style. can add: fg color, bg color, color options -// -// Example: -// color.Style{color.FgGreen}.Print("message") -type Style []Color - -// New create a custom style -// -// Usage: -// color.New(color.FgGreen).Print("message") -// equals to: -// color.Style{color.FgGreen}.Print("message") -func New(colors ...Color) Style { - return colors -} - -// Save to global styles map -func (s Style) Save(name string) { - AddStyle(name, s) -} - -// Add to global styles map -func (s *Style) Add(cs ...Color) { - *s = append(*s, cs...) -} - -// Render render text -// Usage: -// color.New(color.FgGreen).Render("text") -// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text") -func (s Style) Render(a ...interface{}) string { - return RenderCode(s.String(), a...) -} - -// Renderln render text line. -// like Println, will add spaces for each argument -// Usage: -// color.New(color.FgGreen).Renderln("text", "more") -// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text", "more") -func (s Style) Renderln(a ...interface{}) string { - return RenderWithSpaces(s.String(), a...) -} - -// Sprint is alias of the 'Render' -func (s Style) Sprint(a ...interface{}) string { - return RenderCode(s.String(), a...) -} - -// Sprintf format and render message. -func (s Style) Sprintf(format string, a ...interface{}) string { - return RenderString(s.String(), fmt.Sprintf(format, a...)) -} - -// Print render and Print text -func (s Style) Print(a ...interface{}) { - doPrintV2(s.String(), fmt.Sprint(a...)) -} - -// Printf render and print text -func (s Style) Printf(format string, a ...interface{}) { - doPrintV2(s.Code(), fmt.Sprintf(format, a...)) -} - -// Println render and print text line -func (s Style) Println(a ...interface{}) { - doPrintlnV2(s.String(), a) -} - -// Code convert to code string. returns like "32;45;3" -func (s Style) Code() string { - return s.String() -} - -// String convert to code string. returns like "32;45;3" -func (s Style) String() string { - return Colors2code(s...) -} - -// IsEmpty style -func (s Style) IsEmpty() bool { - return len(s) == 0 -} - -/************************************************************* - * Theme(extended Style) - *************************************************************/ - -// Theme definition. extends from Style -type Theme struct { - // Name theme name - Name string - // Style for the theme - Style -} - -// NewTheme instance -func NewTheme(name string, style Style) *Theme { - return &Theme{name, style} -} - -// Save to themes map -func (t *Theme) Save() { - AddTheme(t.Name, t.Style) -} - -// Tips use name as title, only apply style for name -func (t *Theme) Tips(format string, a ...interface{}) { - // only apply style for name - t.Print(strings.ToUpper(t.Name) + ": ") - Printf(format+"\n", a...) -} - -// Prompt use name as title, and apply style for message -func (t *Theme) Prompt(format string, a ...interface{}) { - title := strings.ToUpper(t.Name) + ":" - t.Println(title, fmt.Sprintf(format, a...)) -} - -// Block like Prompt, but will wrap a empty line -func (t *Theme) Block(format string, a ...interface{}) { - title := strings.ToUpper(t.Name) + ":\n" - - t.Println(title, fmt.Sprintf(format, a...)) -} - -/************************************************************* - * Theme: internal themes - *************************************************************/ - -// internal themes(like bootstrap style) -// Usage: -// color.Info.Print("message") -// color.Info.Printf("a %s message", "test") -// color.Warn.Println("message") -// color.Error.Println("message") -var ( - // Info color style - Info = &Theme{"info", Style{OpReset, FgGreen}} - // Note color style - Note = &Theme{"note", Style{OpBold, FgLightCyan}} - // Warn color style - Warn = &Theme{"warning", Style{OpBold, FgYellow}} - // Light color style - Light = &Theme{"light", Style{FgLightWhite, BgBlack}} - // Error color style - Error = &Theme{"error", Style{FgLightWhite, BgRed}} - // Danger color style - Danger = &Theme{"danger", Style{OpBold, FgRed}} - // Debug color style - Debug = &Theme{"debug", Style{OpReset, FgCyan}} - // Notice color style - Notice = &Theme{"notice", Style{OpBold, FgCyan}} - // Comment color style - Comment = &Theme{"comment", Style{OpReset, FgYellow}} - // Success color style - Success = &Theme{"success", Style{OpBold, FgGreen}} - // Primary color style - Primary = &Theme{"primary", Style{OpReset, FgBlue}} - // Question color style - Question = &Theme{"question", Style{OpReset, FgMagenta}} - // Secondary color style - Secondary = &Theme{"secondary", Style{FgDarkGray}} -) - -// Themes internal defined themes. -// Usage: -// color.Themes["info"].Println("message") -var Themes = map[string]*Theme{ - "info": Info, - "note": Note, - "light": Light, - "error": Error, - - "debug": Debug, - "danger": Danger, - "notice": Notice, - "success": Success, - "comment": Comment, - "primary": Primary, - "warning": Warn, - - "question": Question, - "secondary": Secondary, -} - -// AddTheme add a theme and style -func AddTheme(name string, style Style) { - Themes[name] = NewTheme(name, style) - Styles[name] = style -} - -// GetTheme get defined theme by name -func GetTheme(name string) *Theme { - return Themes[name] -} - -/************************************************************* - * internal styles - *************************************************************/ - -// Styles internal defined styles, like bootstrap styles. -// Usage: -// color.Styles["info"].Println("message") -var Styles = map[string]Style{ - "info": {OpReset, FgGreen}, - "note": {OpBold, FgLightCyan}, - "light": {FgLightWhite, BgRed}, - "error": {FgLightWhite, BgRed}, - - "danger": {OpBold, FgRed}, - "notice": {OpBold, FgCyan}, - "success": {OpBold, FgGreen}, - "comment": {OpReset, FgMagenta}, - "primary": {OpReset, FgBlue}, - "warning": {OpBold, FgYellow}, - - "question": {OpReset, FgMagenta}, - "secondary": {FgDarkGray}, -} - -// some style name alias -var styleAliases = map[string]string{ - "err": "error", - "suc": "success", - "warn": "warning", -} - -// AddStyle add a style -func AddStyle(name string, s Style) { - Styles[name] = s -} - -// GetStyle get defined style by name -func GetStyle(name string) Style { - if s, ok := Styles[name]; ok { - return s - } - - if realName, ok := styleAliases[name]; ok { - return Styles[realName] - } - - // empty style - return New() -} - -/************************************************************* - * color scheme - *************************************************************/ - -// Scheme struct -type Scheme struct { - Name string - Styles map[string]Style -} - -// NewScheme create new Scheme -func NewScheme(name string, styles map[string]Style) *Scheme { - return &Scheme{Name: name, Styles: styles} -} - -// NewDefaultScheme create an defuault color Scheme -func NewDefaultScheme(name string) *Scheme { - return NewScheme(name, map[string]Style{ - "info": {OpReset, FgGreen}, - "warn": {OpBold, FgYellow}, - "error": {FgLightWhite, BgRed}, - }) -} - -// Style get by name -func (s *Scheme) Style(name string) Style { - return s.Styles[name] -} - -// Infof message print -func (s *Scheme) Infof(format string, a ...interface{}) { - s.Styles["info"].Printf(format, a...) -} - -// Infoln message print -func (s *Scheme) Infoln(v ...interface{}) { - s.Styles["info"].Println(v...) -} - -// Warnf message print -func (s *Scheme) Warnf(format string, a ...interface{}) { - s.Styles["warn"].Printf(format, a...) -} - -// Warnln message print -func (s *Scheme) Warnln(v ...interface{}) { - s.Styles["warn"].Println(v...) -} - -// Errorf message print -func (s *Scheme) Errorf(format string, a ...interface{}) { - s.Styles["error"].Printf(format, a...) -} - -// Errorln message print -func (s *Scheme) Errorln(v ...interface{}) { - s.Styles["error"].Println(v...) -} diff --git a/vendor/github.com/gookit/color/utils.go b/vendor/github.com/gookit/color/utils.go deleted file mode 100644 index dedf9f2df4d..00000000000 --- a/vendor/github.com/gookit/color/utils.go +++ /dev/null @@ -1,206 +0,0 @@ -package color - -import ( - "fmt" - "io" - "log" - "strings" -) - -// SetTerminal by given code. -func SetTerminal(code string) error { - if !Enable || !SupportColor() { - return nil - } - - _, err := fmt.Fprintf(output, SettingTpl, code) - return err -} - -// ResetTerminal terminal setting. -func ResetTerminal() error { - if !Enable || !SupportColor() { - return nil - } - - _, err := fmt.Fprint(output, ResetSet) - return err -} - -/************************************************************* - * print methods(will auto parse color tags) - *************************************************************/ - -// Print render color tag and print messages -func Print(a ...interface{}) { - Fprint(output, a...) -} - -// Printf format and print messages -func Printf(format string, a ...interface{}) { - Fprintf(output, format, a...) -} - -// Println messages with new line -func Println(a ...interface{}) { - Fprintln(output, a...) -} - -// Fprint print rendered messages to writer -// Notice: will ignore print error -func Fprint(w io.Writer, a ...interface{}) { - _, err := fmt.Fprint(w, Render(a...)) - saveInternalError(err) - - // if isLikeInCmd { - // renderColorCodeOnCmd(func() { - // _, _ = fmt.Fprint(w, Render(a...)) - // }) - // } else { - // _, _ = fmt.Fprint(w, Render(a...)) - // } -} - -// Fprintf print format and rendered messages to writer. -// Notice: will ignore print error -func Fprintf(w io.Writer, format string, a ...interface{}) { - str := fmt.Sprintf(format, a...) - _, err := fmt.Fprint(w, ReplaceTag(str)) - saveInternalError(err) -} - -// Fprintln print rendered messages line to writer -// Notice: will ignore print error -func Fprintln(w io.Writer, a ...interface{}) { - str := formatArgsForPrintln(a) - _, err := fmt.Fprintln(w, ReplaceTag(str)) - saveInternalError(err) -} - -// Lprint passes colored messages to a log.Logger for printing. -// Notice: should be goroutine safe -func Lprint(l *log.Logger, a ...interface{}) { - l.Print(Render(a...)) -} - -// Render parse color tags, return rendered string. -// Usage: -// text := Render("hello world!") -// fmt.Println(text) -func Render(a ...interface{}) string { - if len(a) == 0 { - return "" - } - - return ReplaceTag(fmt.Sprint(a...)) -} - -// Sprint parse color tags, return rendered string -func Sprint(a ...interface{}) string { - if len(a) == 0 { - return "" - } - - return ReplaceTag(fmt.Sprint(a...)) -} - -// Sprintf format and return rendered string -func Sprintf(format string, a ...interface{}) string { - return ReplaceTag(fmt.Sprintf(format, a...)) -} - -// String alias of the ReplaceTag -func String(s string) string { - return ReplaceTag(s) -} - -// Text alias of the ReplaceTag -func Text(s string) string { - return ReplaceTag(s) -} - -/************************************************************* - * helper methods for print - *************************************************************/ - -// new implementation, support render full color code on pwsh.exe, cmd.exe -func doPrintV2(code, str string) { - _, err := fmt.Fprint(output, RenderString(code, str)) - saveInternalError(err) - - // if isLikeInCmd { - // renderColorCodeOnCmd(func() { - // _, _ = fmt.Fprint(output, RenderString(code, str)) - // }) - // } else { - // _, _ = fmt.Fprint(output, RenderString(code, str)) - // } -} - -// new implementation, support render full color code on pwsh.exe, cmd.exe -func doPrintlnV2(code string, args []interface{}) { - str := formatArgsForPrintln(args) - _, err := fmt.Fprintln(output, RenderString(code, str)) - saveInternalError(err) -} - -// if use Println, will add spaces for each arg -func formatArgsForPrintln(args []interface{}) (message string) { - if ln := len(args); ln == 0 { - message = "" - } else if ln == 1 { - message = fmt.Sprint(args[0]) - } else { - message = fmt.Sprintln(args...) - // clear last "\n" - message = message[:len(message)-1] - } - return -} - -/************************************************************* - * helper methods - *************************************************************/ - -// is on debug mode -// func isDebugMode() bool { -// return debugMode == "on" -// } - -func debugf(f string, v ...interface{}) { - if debugMode { - fmt.Print("COLOR_DEBUG: ") - fmt.Printf(f, v...) - fmt.Println() - } -} - -// equals: return ok ? val1 : val2 -func compareVal(ok bool, val1, val2 uint8) uint8 { - if ok { - return val1 - } - return val2 -} - -func saveInternalError(err error) { - if err != nil { - debugf("inner error: %s", err.Error()) - innerErrs = append(innerErrs, err) - } -} - -func stringToArr(str, sep string) (arr []string) { - str = strings.TrimSpace(str) - if str == "" { - return - } - - ss := strings.Split(str, sep) - for _, val := range ss { - if val = strings.TrimSpace(val); val != "" { - arr = append(arr, val) - } - } - return -} diff --git a/vendor/github.com/integrii/flaggy/.gitignore b/vendor/github.com/integrii/flaggy/.gitignore deleted file mode 100644 index f46854f3924..00000000000 --- a/vendor/github.com/integrii/flaggy/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 -.glide/ - -.idea/ diff --git a/vendor/github.com/integrii/flaggy/.travis.yml b/vendor/github.com/integrii/flaggy/.travis.yml deleted file mode 100644 index 4e0d644fa74..00000000000 --- a/vendor/github.com/integrii/flaggy/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -{ - "language": "go", - "os": "linux", - "group": "stable", - "dist": "trusty", - "script": "go get -v && go test -v" -} diff --git a/vendor/github.com/integrii/flaggy/LICENSE b/vendor/github.com/integrii/flaggy/LICENSE deleted file mode 100644 index cf1ab25da03..00000000000 --- a/vendor/github.com/integrii/flaggy/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to diff --git a/vendor/github.com/integrii/flaggy/README.md b/vendor/github.com/integrii/flaggy/README.md deleted file mode 100644 index 740811fc7a5..00000000000 --- a/vendor/github.com/integrii/flaggy/README.md +++ /dev/null @@ -1,247 +0,0 @@ -

- - -
- - - - - - -

- -Sensible and _fast_ command-line flag parsing with excellent support for **subcommands** and **positional values**. Flags can be at any position. Flaggy has no required project or package layout like [Cobra requires](https://github.com/spf13/cobra/issues/641), and **no external dependencies**! - -Check out the [godoc](http://godoc.org/github.com/integrii/flaggy), [examples directory](https://github.com/integrii/flaggy/tree/master/examples), and [examples in this readme](https://github.com/integrii/flaggy#super-simple-example) to get started quickly. You can also read the Flaggy introduction post with helpful examples [on my weblog](https://ericgreer.info/post/a-better-flags-package-for-go/). - -# Installation - -`go get -u github.com/integrii/flaggy` - -# Key Features - -- Very easy to use ([see examples below](https://github.com/integrii/flaggy#super-simple-example)) -- 35 different flag types supported -- Any flag can be at any position -- Pretty and readable help output by default -- Positional subcommands -- Positional parameters -- Suggested subcommands when a subcommand is typo'd -- Nested subcommands -- Both global and subcommand specific flags -- Both global and subcommand specific positional parameters -- [Customizable help templates for both the global command and subcommands](https://github.com/integrii/flaggy/blob/master/examples/customTemplate/main.go) -- Customizable appended/prepended help messages for both the global command and subcommands -- Simple function that displays help followed by a custom message string -- Flags and subcommands may have both a short and long name -- Unlimited trailing arguments after a `--` -- Flags can use a single dash or double dash (`--flag`, `-flag`, `-f`, `--f`) -- Flags can have `=` assignment operators, or use a space (`--flag=value`, `--flag value`) -- Flags support single quote globs with spaces (`--flag 'this is all one value'`) -- Flags of slice types can be passed multiple times (`-f one -f two -f three`) -- Optional but default version output with `--version` -- Optional but default help output with `-h` or `--help` -- Optional but default help output when any invalid or unknown parameter is passed -- It's _fast_. All flag and subcommand parsing takes less than `1ms` in most programs. - -# Example Help Output - -``` -testCommand - Description goes here. Get more information at http://flaggy. -This is a prepend for help - - Usage: - testCommand [subcommandA|subcommandB|subcommandC] [testPositionalA] [testPositionalB] - - Positional Variables: - testPositionalA Test positional A does some things with a positional value. (Required) - testPositionalB Test positional B does some less than serious things with a positional value. - - Subcommands: - subcommandA (a) Subcommand A is a command that does stuff - subcommandB (b) Subcommand B is a command that does other stuff - subcommandC (c) Subcommand C is a command that does SERIOUS stuff - - Flags: - --version Displays the program version string. - -h --help Displays help with available flag, subcommand, and positional value parameters. - -s --stringFlag This is a test string flag that does some stringy string stuff. - -i --intFlg This is a test int flag that does some interesting int stuff. (default: 5) - -b --boolFlag This is a test bool flag that does some booly bool stuff. (default: true) - -d --durationFlag This is a test duration flag that does some untimely stuff. (default: 1h23s) - -This is an append for help -This is a help add-on message -``` - -# Super Simple Example - -`./yourApp -f test` - -```go -// Declare variables and their defaults -var stringFlag = "defaultValue" - -// Add a flag -flaggy.String(&stringFlag, "f", "flag", "A test string flag") - -// Parse the flag -flaggy.Parse() - -// Use the flag -print(stringFlag) -``` - - -# Example with Subcommand - -`./yourApp subcommandExample -f test` - -```go -// Declare variables and their defaults -var stringFlag = "defaultValue" - -// Create the subcommand -subcommand := flaggy.NewSubcommand("subcommandExample") - -// Add a flag to the subcommand -subcommand.String(&stringFlag, "f", "flag", "A test string flag") - -// Add the subcommand to the parser at position 1 -flaggy.AttachSubcommand(subcommand, 1) - -// Parse the subcommand and all flags -flaggy.Parse() - -// Use the flag -print(stringFlag) -``` - -# Example with Nested Subcommands, Various Flags and Trailing Arguments - -`./yourApp subcommandExample --flag=5 nestedSubcommand -t test -y -- trailingArg` - -```go -// Declare variables and their defaults -var stringFlagF = "defaultValueF" -var intFlagT = 3 -var boolFlagB bool - -// Create the subcommands -subcommandExample := flaggy.NewSubcommand("subcommandExample") -nestedSubcommand := flaggy.NewSubcommand("nestedSubcommand") - -// Add a flag to both subcommands -subcommandExample.String(&stringFlagF, "t", "testFlag", "A test string flag") -nestedSubcommand.Int(&intFlagT, "f", "flag", "A test int flag") - -// add a global bool flag for fun -flaggy.Bool(&boolFlagB, "y", "yes", "A sample boolean flag") - -// attach the nested subcommand to the parent subcommand at position 1 -subcommandExample.AttachSubcommand(nestedSubcommand, 1) -// attach the base subcommand to the parser at position 1 -flaggy.AttachSubcommand(subcommandExample, 1) - -// Parse everything, then use the flags and trailing arguments -flaggy.Parse() -print(stringFlagF) -print(intFlagT) -print(boolFlagB) -print(flaggy.TrailingArguments[0]) -``` - -# Supported Flag Types - -Flaggy has specific flag types for all basic types included in go as well as a slice of any of those types. This includes all of the following types: - -- string and []string -- bool and []bool -- all int types and all []int types -- all float types and all []float types -- all uint types and all []uint types - -Other more specific types can also be used as flag types. They will be automatically parsed using the standard parsing functions included with those types in those packages. This includes: - -- net.IP -- []net.IP -- net.HardwareAddr -- []net.HardwareAddr -- net.IPMask -- []net.IPMask -- time.Duration -- []time.Duration - -# An Example Program - -Best practice when using flaggy includes setting your program's name, description, and version (at build time) as shown in this example program. - -```go -package main - -import "github.com/integrii/flaggy" - -// Make a variable for the version which will be set at build time. -var version = "unknown" - -// Keep subcommands as globals so you can easily check if they were used later on. -var mySubcommand *flaggy.Subcommand - -// Setup the variables you want your incoming flags to set. -var testVar string - -// If you would like an environment variable as the default for a value, just populate the flag -// with the value of the environment by default. If the flag corresponding to this value is not -// used, then it will not be changed. -var myVar = os.Getenv("MY_VAR") - - -func init() { - // Set your program's name and description. These appear in help output. - flaggy.SetName("Test Program") - flaggy.SetDescription("A little example program") - - // You can disable various things by changing bools on the default parser - // (or your own parser if you have created one). - flaggy.DefaultParser.ShowHelpOnUnexpected = false - - // You can set a help prepend or append on the default parser. - flaggy.DefaultParser.AdditionalHelpPrepend = "http://github.com/integrii/flaggy" - - // Add a flag to the main program (this will be available in all subcommands as well). - flaggy.String(&testVar, "tv", "testVariable", "A variable just for testing things!") - - // Create any subcommands and set their parameters. - mySubcommand = flaggy.NewSubcommand("mySubcommand") - mySubcommand.Description = "My great subcommand!" - - // Add a flag to the subcommand. - mySubcommand.String(&myVar, "mv", "myVariable", "A variable just for me!") - - // Set the version and parse all inputs into variables. - flaggy.SetVersion(version) - flaggy.Parse() -} - -func main(){ - if mySubcommand.Used { - ... - } -} -``` - -Then, you can use the following build command to set the `version` variable in the above program at build time. - -```bash -# build your app and set the version string -$ go build -ldflags='-X main.version=1.0.3-a3db3' -$ ./yourApp version -Version: 1.0.3-a3db3 -$ ./yourApp --help -Test Program - A little example program -http://github.com/integrii/flaggy -``` - -# Contributions - -Please feel free to open an issue if you find any bugs or see any features that make sense. Pull requests will be reviewed and accepted if they make sense, but it is always wise to submit a proposal issue before any major changes. diff --git a/vendor/github.com/integrii/flaggy/argumentParser.go b/vendor/github.com/integrii/flaggy/argumentParser.go deleted file mode 100644 index 34d00f96076..00000000000 --- a/vendor/github.com/integrii/flaggy/argumentParser.go +++ /dev/null @@ -1,27 +0,0 @@ -package flaggy - -// setValueForParsers sets the value for a specified key in the -// specified parsers (which normally include a Parser and Subcommand). -// The return values represent the key being set, and any errors -// returned when setting the key, such as failures to convert the string -// into the appropriate flag value. We stop assigning values as soon -// as we find a any parser that accepts it. -func setValueForParsers(key string, value string, parsers ...ArgumentParser) (bool, error) { - - for _, p := range parsers { - valueWasSet, err := p.SetValueForKey(key, value) - if err != nil { - return valueWasSet, err - } - if valueWasSet { - return true, nil - } - } - - return false, nil -} - -// ArgumentParser represents a parser or subcommand -type ArgumentParser interface { - SetValueForKey(key string, value string) (bool, error) -} diff --git a/vendor/github.com/integrii/flaggy/flag.go b/vendor/github.com/integrii/flaggy/flag.go deleted file mode 100644 index 409ddf94d00..00000000000 --- a/vendor/github.com/integrii/flaggy/flag.go +++ /dev/null @@ -1,622 +0,0 @@ -package flaggy - -import ( - "errors" - "fmt" - "net" - "reflect" - "strconv" - "strings" - "time" -) - -// Flag holds the base methods for all flag types -type Flag struct { - ShortName string - LongName string - Description string - rawValue string // the value as a string before being parsed - Hidden bool // indicates this flag should be hidden from help and suggestions - AssignmentVar interface{} - defaultValue string // the value (as a string), that was set by default before any parsing and assignment - parsed bool // indicates that this flag has already been parsed -} - -// HasName indicates that this flag's short or long name matches the -// supplied name string -func (f *Flag) HasName(name string) bool { - name = strings.TrimSpace(name) - if f.ShortName == name || f.LongName == name { - return true - } - return false -} - -// identifyAndAssignValue identifies the type of the incoming value -// and assigns it to the AssignmentVar pointer's target value. If -// the value is a type that needs parsing, that is performed as well. -func (f *Flag) identifyAndAssignValue(value string) error { - - var err error - - // Only parse this flag default value once. This keeps us from - // overwriting the default value in help output - if !f.parsed { - f.parsed = true - // parse the default value as a string and remember it for help output - f.defaultValue, err = f.returnAssignmentVarValueAsString() - if err != nil { - return err - } - } - - debugPrint("attempting to assign value", value, "to flag", f.LongName) - f.rawValue = value // remember the raw value - - // depending on the type of the assignment variable, we convert the - // incoming string and assign it. We only use pointers to variables - // in flagy. No returning vars by value. - switch f.AssignmentVar.(type) { - case *string: - v, _ := (f.AssignmentVar).(*string) - *v = value - case *[]string: - v := f.AssignmentVar.(*[]string) - splitString := strings.Split(value, ",") - new := append(*v, splitString...) - *v = new - case *bool: - v, err := strconv.ParseBool(value) - if err != nil { - return err - } - a, _ := (f.AssignmentVar).(*bool) - *a = v - case *[]bool: - // parse the incoming bool - b, err := strconv.ParseBool(value) - if err != nil { - return err - } - // cast the assignment var - existing := f.AssignmentVar.(*[]bool) - // deref the assignment var and append to it - v := append(*existing, b) - // pointer the new value and assign it - a, _ := (f.AssignmentVar).(*[]bool) - *a = v - case *time.Duration: - v, err := time.ParseDuration(value) - if err != nil { - return err - } - a, _ := (f.AssignmentVar).(*time.Duration) - *a = v - case *[]time.Duration: - t, err := time.ParseDuration(value) - if err != nil { - return err - } - existing := f.AssignmentVar.(*[]time.Duration) - // deref the assignment var and append to it - v := append(*existing, t) - // pointer the new value and assign it - a, _ := (f.AssignmentVar).(*[]time.Duration) - *a = v - case *float32: - v, err := strconv.ParseFloat(value, 32) - if err != nil { - return err - } - float := float32(v) - a, _ := (f.AssignmentVar).(*float32) - *a = float - case *[]float32: - v, err := strconv.ParseFloat(value, 32) - if err != nil { - return err - } - float := float32(v) - existing := f.AssignmentVar.(*[]float32) - new := append(*existing, float) - *existing = new - case *float64: - v, err := strconv.ParseFloat(value, 64) - if err != nil { - return err - } - a, _ := (f.AssignmentVar).(*float64) - *a = v - case *[]float64: - v, err := strconv.ParseFloat(value, 64) - if err != nil { - return err - } - existing := f.AssignmentVar.(*[]float64) - new := append(*existing, v) - - *existing = new - case *int: - v, err := strconv.Atoi(value) - if err != nil { - return err - } - e := f.AssignmentVar.(*int) - *e = v - case *[]int: - v, err := strconv.Atoi(value) - if err != nil { - return err - } - existing := f.AssignmentVar.(*[]int) - new := append(*existing, v) - *existing = new - case *uint: - v, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return err - } - existing := f.AssignmentVar.(*uint) - *existing = uint(v) - case *[]uint: - v, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return err - } - existing := f.AssignmentVar.(*[]uint) - new := append(*existing, uint(v)) - *existing = new - case *uint64: - v, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return err - } - existing := f.AssignmentVar.(*uint64) - *existing = v - case *[]uint64: - v, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return err - } - existing := f.AssignmentVar.(*[]uint64) - new := append(*existing, v) - *existing = new - case *uint32: - v, err := strconv.ParseUint(value, 10, 32) - if err != nil { - return err - } - existing := f.AssignmentVar.(*uint32) - *existing = uint32(v) - case *[]uint32: - v, err := strconv.ParseUint(value, 10, 32) - if err != nil { - return err - } - existing := f.AssignmentVar.(*[]uint32) - new := append(*existing, uint32(v)) - *existing = new - case *uint16: - v, err := strconv.ParseUint(value, 10, 16) - if err != nil { - return err - } - val := uint16(v) - existing := f.AssignmentVar.(*uint16) - *existing = val - case *[]uint16: - v, err := strconv.ParseUint(value, 10, 16) - if err != nil { - return err - } - existing := f.AssignmentVar.(*[]uint16) - new := append(*existing, uint16(v)) - *existing = new - case *uint8: - v, err := strconv.ParseUint(value, 10, 8) - if err != nil { - return err - } - val := uint8(v) - existing := f.AssignmentVar.(*uint8) - *existing = val - case *[]uint8: - var newSlice []uint8 - - v, err := strconv.ParseUint(value, 10, 8) - if err != nil { - return err - } - newV := uint8(v) - existing := f.AssignmentVar.(*[]uint8) - newSlice = append(*existing, newV) - *existing = newSlice - case *int64: - v, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return err - } - existing := f.AssignmentVar.(*int64) - *existing = v - case *[]int64: - v, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return err - } - existingSlice := f.AssignmentVar.(*[]int64) - newSlice := append(*existingSlice, v) - *existingSlice = newSlice - case *int32: - v, err := strconv.ParseInt(value, 10, 32) - if err != nil { - return err - } - converted := int32(v) - existing := f.AssignmentVar.(*int32) - *existing = converted - case *[]int32: - v, err := strconv.ParseInt(value, 10, 32) - if err != nil { - return err - } - existingSlice := f.AssignmentVar.(*[]int32) - newSlice := append(*existingSlice, int32(v)) - *existingSlice = newSlice - case *int16: - v, err := strconv.ParseInt(value, 10, 16) - if err != nil { - return err - } - converted := int16(v) - existing := f.AssignmentVar.(*int16) - *existing = converted - case *[]int16: - v, err := strconv.ParseInt(value, 10, 16) - if err != nil { - return err - } - existingSlice := f.AssignmentVar.(*[]int16) - newSlice := append(*existingSlice, int16(v)) - *existingSlice = newSlice - case *int8: - v, err := strconv.ParseInt(value, 10, 8) - if err != nil { - return err - } - converted := int8(v) - existing := f.AssignmentVar.(*int8) - *existing = converted - case *[]int8: - v, err := strconv.ParseInt(value, 10, 8) - if err != nil { - return err - } - existingSlice := f.AssignmentVar.(*[]int8) - newSlice := append(*existingSlice, int8(v)) - *existingSlice = newSlice - case *net.IP: - v := net.ParseIP(value) - existing := f.AssignmentVar.(*net.IP) - *existing = v - case *[]net.IP: - v := net.ParseIP(value) - existing := f.AssignmentVar.(*[]net.IP) - new := append(*existing, v) - *existing = new - case *net.HardwareAddr: - v, err := net.ParseMAC(value) - if err != nil { - return err - } - existing := f.AssignmentVar.(*net.HardwareAddr) - *existing = v - case *[]net.HardwareAddr: - v, err := net.ParseMAC(value) - if err != nil { - return err - } - existing := f.AssignmentVar.(*[]net.HardwareAddr) - new := append(*existing, v) - *existing = new - case *net.IPMask: - v := net.IPMask(net.ParseIP(value).To4()) - existing := f.AssignmentVar.(*net.IPMask) - *existing = v - case *[]net.IPMask: - v := net.IPMask(net.ParseIP(value).To4()) - existing := f.AssignmentVar.(*[]net.IPMask) - new := append(*existing, v) - *existing = new - default: - return errors.New("Unknown flag assignmentVar supplied in flag " + f.LongName + " " + f.ShortName) - } - - return err -} - -const argIsPositional = "positional" // subcommand or positional value -const argIsFlagWithSpace = "flagWithSpace" // -f path or --file path -const argIsFlagWithValue = "flagWithValue" // -f=path or --file=path -const argIsFinal = "final" // the final argument only '--' - -// determineArgType determines if the specified arg is a flag with space -// separated value, a flag with a connected value, or neither (positional) -func determineArgType(arg string) string { - - // if the arg is --, then its the final arg - if arg == "--" { - return argIsFinal - } - - // if it has the prefix --, then its a long flag - if strings.HasPrefix(arg, "--") { - // if it contains an equals, it is a joined value - if strings.Contains(arg, "=") { - return argIsFlagWithValue - } - return argIsFlagWithSpace - } - - // if it has the prefix -, then its a short flag - if strings.HasPrefix(arg, "-") { - // if it contains an equals, it is a joined value - if strings.Contains(arg, "=") { - return argIsFlagWithValue - } - return argIsFlagWithSpace - } - - return argIsPositional -} - -// parseArgWithValue parses a key=value concatenated argument into a key and -// value -func parseArgWithValue(arg string) (key string, value string) { - - // remove up to two minuses from start of flag - arg = strings.TrimPrefix(arg, "-") - arg = strings.TrimPrefix(arg, "-") - - // debugPrint("parseArgWithValue parsing", arg) - - // break at the equals - args := strings.SplitN(arg, "=", 2) - - // if its a bool arg, with no explicit value, we return a blank - if len(args) == 1 { - return args[0], "" - } - - // if its a key and value pair, we return those - if len(args) == 2 { - // debugPrint("parseArgWithValue parsed", args[0], args[1]) - return args[0], args[1] - } - - fmt.Println("Warning: attempted to parseArgWithValue but did not have correct parameter count.", arg, "->", args) - return "", "" -} - -// parseFlagToName parses a flag with space value down to a key name: -// --path -> path -// -p -> p -func parseFlagToName(arg string) string { - // remove minus from start - arg = strings.TrimLeft(arg, "-") - arg = strings.TrimLeft(arg, "-") - return arg -} - -// flagIsBool determines if the flag is a bool within the specified parser -// and subcommand's context -func flagIsBool(sc *Subcommand, p *Parser, key string) bool { - for _, f := range append(sc.Flags, p.Flags...) { - if f.HasName(key) { - _, isBool := f.AssignmentVar.(*bool) - _, isBoolSlice := f.AssignmentVar.(*[]bool) - if isBool || isBoolSlice { - return true - } - } - } - - // by default, the answer is false - return false -} - -// returnAssignmentVarValueAsString returns the value of the flag's -// assignment variable as a string. This is used to display the -// default value of flags before they are assigned (like when help is output). -func (f *Flag) returnAssignmentVarValueAsString() (string, error) { - - debugPrint("returning current value of assignment var of flag", f.LongName) - - var err error - - // depending on the type of the assignment variable, we convert the - // incoming string and assign it. We only use pointers to variables - // in flagy. No returning vars by value. - switch f.AssignmentVar.(type) { - case *string: - v, _ := (f.AssignmentVar).(*string) - return *v, err - case *[]string: - v := f.AssignmentVar.(*[]string) - return strings.Join(*v, ","), err - case *bool: - a, _ := (f.AssignmentVar).(*bool) - return strconv.FormatBool(*a), err - case *[]bool: - value := f.AssignmentVar.(*[]bool) - var ss []string - for _, b := range *value { - ss = append(ss, strconv.FormatBool(b)) - } - return strings.Join(ss, ","), err - case *time.Duration: - a := f.AssignmentVar.(*time.Duration) - return (*a).String(), err - case *[]time.Duration: - tds := f.AssignmentVar.(*[]time.Duration) - var asSlice []string - for _, td := range *tds { - asSlice = append(asSlice, td.String()) - } - return strings.Join(asSlice, ","), err - case *float32: - a := f.AssignmentVar.(*float32) - return strconv.FormatFloat(float64(*a), 'f', 2, 32), err - case *[]float32: - v := f.AssignmentVar.(*[]float32) - var strSlice []string - for _, f := range *v { - formatted := strconv.FormatFloat(float64(f), 'f', 2, 32) - strSlice = append(strSlice, formatted) - } - return strings.Join(strSlice, ","), err - case *float64: - a := f.AssignmentVar.(*float64) - return strconv.FormatFloat(float64(*a), 'f', 2, 64), err - case *[]float64: - v := f.AssignmentVar.(*[]float64) - var strSlice []string - for _, f := range *v { - formatted := strconv.FormatFloat(float64(f), 'f', 2, 64) - strSlice = append(strSlice, formatted) - } - return strings.Join(strSlice, ","), err - case *int: - a := f.AssignmentVar.(*int) - return strconv.Itoa(*a), err - case *[]int: - val := f.AssignmentVar.(*[]int) - var strSlice []string - for _, i := range *val { - str := strconv.Itoa(i) - strSlice = append(strSlice, str) - } - return strings.Join(strSlice, ","), err - case *uint: - v := f.AssignmentVar.(*uint) - return strconv.FormatUint(uint64(*v), 10), err - case *[]uint: - values := f.AssignmentVar.(*[]uint) - var strVars []string - for _, i := range *values { - strVars = append(strVars, strconv.FormatUint(uint64(i), 10)) - } - return strings.Join(strVars, ","), err - case *uint64: - v := f.AssignmentVar.(*uint64) - return strconv.FormatUint(*v, 10), err - case *[]uint64: - values := f.AssignmentVar.(*[]uint64) - var strVars []string - for _, i := range *values { - strVars = append(strVars, strconv.FormatUint(i, 10)) - } - return strings.Join(strVars, ","), err - case *uint32: - v := f.AssignmentVar.(*uint32) - return strconv.FormatUint(uint64(*v), 10), err - case *[]uint32: - values := f.AssignmentVar.(*[]uint32) - var strVars []string - for _, i := range *values { - strVars = append(strVars, strconv.FormatUint(uint64(i), 10)) - } - return strings.Join(strVars, ","), err - case *uint16: - v := f.AssignmentVar.(*uint16) - return strconv.FormatUint(uint64(*v), 10), err - case *[]uint16: - values := f.AssignmentVar.(*[]uint16) - var strVars []string - for _, i := range *values { - strVars = append(strVars, strconv.FormatUint(uint64(i), 10)) - } - return strings.Join(strVars, ","), err - case *uint8: - v := f.AssignmentVar.(*uint8) - return strconv.FormatUint(uint64(*v), 10), err - case *[]uint8: - values := f.AssignmentVar.(*[]uint8) - var strVars []string - for _, i := range *values { - strVars = append(strVars, strconv.FormatUint(uint64(i), 10)) - } - return strings.Join(strVars, ","), err - case *int64: - v := f.AssignmentVar.(*int64) - return strconv.FormatInt(int64(*v), 10), err - case *[]int64: - values := f.AssignmentVar.(*[]int64) - var strVars []string - for _, i := range *values { - strVars = append(strVars, strconv.FormatInt(i, 10)) - } - return strings.Join(strVars, ","), err - case *int32: - v := f.AssignmentVar.(*int32) - return strconv.FormatInt(int64(*v), 10), err - case *[]int32: - values := f.AssignmentVar.(*[]int32) - var strVars []string - for _, i := range *values { - strVars = append(strVars, strconv.FormatInt(int64(i), 10)) - } - return strings.Join(strVars, ","), err - case *int16: - v := f.AssignmentVar.(*int16) - return strconv.FormatInt(int64(*v), 10), err - case *[]int16: - values := f.AssignmentVar.(*[]int16) - var strVars []string - for _, i := range *values { - strVars = append(strVars, strconv.FormatInt(int64(i), 10)) - } - return strings.Join(strVars, ","), err - case *int8: - v := f.AssignmentVar.(*int8) - return strconv.FormatInt(int64(*v), 10), err - case *[]int8: - values := f.AssignmentVar.(*[]int8) - var strVars []string - for _, i := range *values { - strVars = append(strVars, strconv.FormatInt(int64(i), 10)) - } - return strings.Join(strVars, ","), err - case *net.IP: - val := f.AssignmentVar.(*net.IP) - return val.String(), err - case *[]net.IP: - val := f.AssignmentVar.(*[]net.IP) - var strSlice []string - for _, ip := range *val { - strSlice = append(strSlice, ip.String()) - } - return strings.Join(strSlice, ","), err - case *net.HardwareAddr: - val := f.AssignmentVar.(*net.HardwareAddr) - return val.String(), err - case *[]net.HardwareAddr: - val := f.AssignmentVar.(*[]net.HardwareAddr) - var strSlice []string - for _, mac := range *val { - strSlice = append(strSlice, mac.String()) - } - return strings.Join(strSlice, ","), err - case *net.IPMask: - val := f.AssignmentVar.(*net.IPMask) - return val.String(), err - case *[]net.IPMask: - val := f.AssignmentVar.(*[]net.IPMask) - var strSlice []string - for _, m := range *val { - strSlice = append(strSlice, m.String()) - } - return strings.Join(strSlice, ","), err - default: - return "", errors.New("Unknown flag assignmentVar found in flag " + f.LongName + " " + f.ShortName + ". Type not supported: " + reflect.TypeOf(f.AssignmentVar).String()) - } -} diff --git a/vendor/github.com/integrii/flaggy/help.go b/vendor/github.com/integrii/flaggy/help.go deleted file mode 100644 index 353be7d4fbb..00000000000 --- a/vendor/github.com/integrii/flaggy/help.go +++ /dev/null @@ -1,23 +0,0 @@ -package flaggy - -// defaultHelpTemplate is the help template used by default -// {{if (or (or (gt (len .StringFlags) 0) (gt (len .IntFlags) 0)) (gt (len .BoolFlags) 0))}} -// {{if (or (gt (len .StringFlags) 0) (gt (len .BoolFlags) 0))}} -const defaultHelpTemplate = `{{.CommandName}}{{if .Description}} - {{.Description}}{{end}}{{if .PrependMessage}} -{{.PrependMessage}}{{end}} -{{if .UsageString}} - Usage: - {{.UsageString}}{{end}}{{if .Positionals}} - - Positional Variables: {{range .Positionals}} - {{.Name}} {{.Spacer}}{{if .Description}} {{.Description}}{{end}}{{if .DefaultValue}} (default: {{.DefaultValue}}){{else}}{{if .Required}} (Required){{end}}{{end}}{{end}}{{end}}{{if .Subcommands}} - - Subcommands: {{range .Subcommands}} - {{.LongName}}{{if .ShortName}} ({{.ShortName}}){{end}}{{if .Position}}{{if gt .Position 1}} (position {{.Position}}){{end}}{{end}}{{if .Description}} {{.Spacer}}{{.Description}}{{end}}{{end}} -{{end}}{{if (gt (len .Flags) 0)}} - Flags: {{if .Flags}}{{range .Flags}} - {{if .ShortName}}-{{.ShortName}} {{else}} {{end}}{{if .LongName}}--{{.LongName}}{{end}}{{if .Description}} {{.Spacer}}{{.Description}}{{if .DefaultValue}} (default: {{.DefaultValue}}){{end}}{{end}}{{end}}{{end}} -{{end}}{{if .AppendMessage}}{{.AppendMessage}} -{{end}}{{if .Message}} -{{.Message}}{{end}} -` diff --git a/vendor/github.com/integrii/flaggy/helpValues.go b/vendor/github.com/integrii/flaggy/helpValues.go deleted file mode 100644 index df2f679e95a..00000000000 --- a/vendor/github.com/integrii/flaggy/helpValues.go +++ /dev/null @@ -1,289 +0,0 @@ -package flaggy - -import ( - "log" - "reflect" - "strings" - "unicode/utf8" -) - -// Help represents the values needed to render a Help page -type Help struct { - Subcommands []HelpSubcommand - Positionals []HelpPositional - Flags []HelpFlag - UsageString string - CommandName string - PrependMessage string - AppendMessage string - Message string - Description string -} - -// HelpSubcommand is used to template subcommand Help output -type HelpSubcommand struct { - ShortName string - LongName string - Description string - Position int - Spacer string -} - -// HelpPositional is used to template positional Help output -type HelpPositional struct { - Name string - Description string - Required bool - Position int - DefaultValue string - Spacer string -} - -// HelpFlag is used to template string flag Help output -type HelpFlag struct { - ShortName string - LongName string - Description string - DefaultValue string - Spacer string -} - -// ExtractValues extracts Help template values from a subcommand and its parent -// parser. The parser is required in order to detect default flag settings -// for help and version output. -func (h *Help) ExtractValues(p *Parser, message string) { - - // accept message string for output - h.Message = message - - // extract Help values from the current subcommand in context - // prependMessage string - h.PrependMessage = p.subcommandContext.AdditionalHelpPrepend - // appendMessage string - h.AppendMessage = p.subcommandContext.AdditionalHelpAppend - // command name - h.CommandName = p.subcommandContext.Name - // description - h.Description = p.subcommandContext.Description - - maxLength := getLongestNameLength(p.subcommandContext.Subcommands, 0) - - // subcommands []HelpSubcommand - for _, cmd := range p.subcommandContext.Subcommands { - if cmd.Hidden { - continue - } - newHelpSubcommand := HelpSubcommand{ - ShortName: cmd.ShortName, - LongName: cmd.Name, - Description: cmd.Description, - Position: cmd.Position, - Spacer: makeSpacer(cmd.Name, maxLength), - } - h.Subcommands = append(h.Subcommands, newHelpSubcommand) - } - - maxLength = getLongestNameLength(p.subcommandContext.PositionalFlags, 0) - - // parse positional flags into help output structs - for _, pos := range p.subcommandContext.PositionalFlags { - if pos.Hidden { - continue - } - newHelpPositional := HelpPositional{ - Name: pos.Name, - Position: pos.Position, - Description: pos.Description, - Required: pos.Required, - DefaultValue: pos.defaultValue, - Spacer: makeSpacer(pos.Name, maxLength), - } - h.Positionals = append(h.Positionals, newHelpPositional) - } - - maxLength = len(versionFlagLongName) - if len(helpFlagLongName) > maxLength { - maxLength = len(helpFlagLongName) - } - maxLength = getLongestNameLength(p.subcommandContext.Flags, maxLength) - maxLength = getLongestNameLength(p.Flags, maxLength) - - // if the built-in version flag is enabled, then add it as a help flag - if p.ShowVersionWithVersionFlag { - defaultVersionFlag := HelpFlag{ - ShortName: "", - LongName: versionFlagLongName, - Description: "Displays the program version string.", - DefaultValue: "", - Spacer: makeSpacer(versionFlagLongName, maxLength), - } - h.Flags = append(h.Flags, defaultVersionFlag) - } - - // if the built-in help flag exists, then add it as a help flag - if p.ShowHelpWithHFlag { - defaultHelpFlag := HelpFlag{ - ShortName: helpFlagShortName, - LongName: helpFlagLongName, - Description: "Displays help with available flag, subcommand, and positional value parameters.", - DefaultValue: "", - Spacer: makeSpacer(helpFlagLongName, maxLength), - } - h.Flags = append(h.Flags, defaultHelpFlag) - } - - // go through every flag in the subcommand and add it to help output - h.parseFlagsToHelpFlags(p.subcommandContext.Flags, maxLength) - - // go through every flag in the parent parser and add it to help output - h.parseFlagsToHelpFlags(p.Flags, maxLength) - - // formulate the usage string - // first, we capture all the command and positional names by position - commandsByPosition := make(map[int]string) - for _, pos := range p.subcommandContext.PositionalFlags { - if pos.Hidden { - continue - } - if len(commandsByPosition[pos.Position]) > 0 { - commandsByPosition[pos.Position] = commandsByPosition[pos.Position] + "|" + pos.Name - } else { - commandsByPosition[pos.Position] = pos.Name - } - } - for _, cmd := range p.subcommandContext.Subcommands { - if cmd.Hidden { - continue - } - if len(commandsByPosition[cmd.Position]) > 0 { - commandsByPosition[cmd.Position] = commandsByPosition[cmd.Position] + "|" + cmd.Name - } else { - commandsByPosition[cmd.Position] = cmd.Name - } - } - - // find the highest position count in the map - var highestPosition int - for i := range commandsByPosition { - if i > highestPosition { - highestPosition = i - } - } - - // only have a usage string if there are positional items - var usageString string - if highestPosition > 0 { - // find each positional value and make our final string - usageString = p.subcommandContext.Name - for i := 1; i <= highestPosition; i++ { - if len(commandsByPosition[i]) > 0 { - usageString = usageString + " [" + commandsByPosition[i] + "]" - } else { - // dont keep listing after the first position without any properties - // it will be impossible to reach anything beyond here anyway - break - } - } - } - - h.UsageString = usageString - -} - -// parseFlagsToHelpFlags parses the specified slice of flags into -// help flags on the the calling help command -func (h *Help) parseFlagsToHelpFlags(flags []*Flag, maxLength int) { - - for _, f := range flags { - if f.Hidden { - continue - } - - // parse help values out if the flag hasn't been parsed yet - if !f.parsed { - f.parsed = true - // parse the default value as a string and remember it for help output - f.defaultValue, _ = f.returnAssignmentVarValueAsString() - } - - // determine the default value based on the assignment variable - defaultValue := f.defaultValue - - // dont show nils - if defaultValue == "" { - defaultValue = "" - } - - // for bools, dont show a default of false - _, isBool := f.AssignmentVar.(*bool) - if isBool { - b := f.AssignmentVar.(*bool) - if *b == false { - defaultValue = "" - } - } - - newHelpFlag := HelpFlag{ - ShortName: f.ShortName, - LongName: f.LongName, - Description: f.Description, - DefaultValue: defaultValue, - Spacer: makeSpacer(f.LongName, maxLength), - } - h.AddFlagToHelp(newHelpFlag) - } -} - -// AddFlagToHelp adds a flag to help output if it does not exist -func (h *Help) AddFlagToHelp(f HelpFlag) { - for _, existingFlag := range h.Flags { - if len(existingFlag.ShortName) > 0 && existingFlag.ShortName == f.ShortName { - return - } - if len(existingFlag.LongName) > 0 && existingFlag.LongName == f.LongName { - return - } - } - h.Flags = append(h.Flags, f) -} - -// getLongestNameLength takes a slice of any supported flag and returns the length of the longest of their names -func getLongestNameLength(slice interface{}, min int) int { - var maxLength = min - - s := reflect.ValueOf(slice) - if s.Kind() != reflect.Slice { - log.Panicf("Paremeter given to getLongestNameLength() is of type %s. Expected slice", s.Kind()) - } - - for i := 0; i < s.Len(); i++ { - option := s.Index(i).Interface() - var name string - switch t := option.(type) { - case *Subcommand: - name = t.Name - case *Flag: - name = t.LongName - case *PositionalValue: - name = t.Name - default: - log.Panicf("Unexpected type %T found in slice passed to getLongestNameLength(). Possible types: *Subcommand, *Flag, *PositionalValue", t) - } - length := len(name) - if length > maxLength { - maxLength = length - } - } - - return maxLength -} - -// makeSpacer creates a string of whitespaces, with a length of the given -// maxLength minus the length of the given name -func makeSpacer(name string, maxLength int) string { - length := maxLength - utf8.RuneCountInString(name) - if length < 0 { - length = 0 - } - return strings.Repeat(" ", length) -} diff --git a/vendor/github.com/integrii/flaggy/logo.png b/vendor/github.com/integrii/flaggy/logo.png deleted file mode 100644 index d5ebabfb76c..00000000000 Binary files a/vendor/github.com/integrii/flaggy/logo.png and /dev/null differ diff --git a/vendor/github.com/integrii/flaggy/main.go b/vendor/github.com/integrii/flaggy/main.go deleted file mode 100644 index b242cb61dfe..00000000000 --- a/vendor/github.com/integrii/flaggy/main.go +++ /dev/null @@ -1,353 +0,0 @@ -// Package flaggy is a input flag parsing package that supports recursive -// subcommands, positional values, and any-position flags without -// unnecessary complexeties. -// -// For a getting started tutorial and full feature list, check out the -// readme at https://github.com/integrii/flaggy. -package flaggy // import "github.com/integrii/flaggy" - -import ( - "fmt" - "log" - "net" - "os" - "strconv" - "strings" - "time" -) - -// strings used for builtin help and version flags both short and long -const versionFlagLongName = "version" -const helpFlagLongName = "help" -const helpFlagShortName = "h" - -// defaultVersion is applied to parsers when they are created -const defaultVersion = "0.0.0" - -// DebugMode indicates that debug output should be enabled -var DebugMode bool - -// DefaultHelpTemplate is the help template that will be used -// for newly created subcommands and commands -var DefaultHelpTemplate = defaultHelpTemplate - -// DefaultParser is the default parser that is used with the package-level public -// functions -var DefaultParser *Parser - -// TrailingArguments holds trailing arguments in the main parser after parsing -// has been run. -var TrailingArguments []string - -func init() { - - // set the default help template - // allow usage like flaggy.StringVar by enabling a default Parser - ResetParser() -} - -// ResetParser resets the default parser to a fresh instance. Uses the -// name of the binary executing as the program name by default. -func ResetParser() { - if len(os.Args) > 0 { - chunks := strings.Split(os.Args[0], "/") - DefaultParser = NewParser(chunks[len(chunks)-1]) - } else { - DefaultParser = NewParser("default") - } -} - -// Parse parses flags as requested in the default package parser -func Parse() { - err := DefaultParser.Parse() - TrailingArguments = DefaultParser.TrailingArguments - if err != nil { - log.Panicln("Error from argument parser:", err) - } -} - -// ParseArgs parses the passed args as if they were the arguments to the -// running binary. Targets the default main parser for the package. -func ParseArgs(args []string) { - err := DefaultParser.ParseArgs(args) - TrailingArguments = DefaultParser.TrailingArguments - if err != nil { - log.Panicln("Error from argument parser:", err) - } -} - -// String adds a new string flag -func String(assignmentVar *string, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// StringSlice adds a new slice of strings flag -// Specify the flag multiple times to fill the slice -func StringSlice(assignmentVar *[]string, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Bool adds a new bool flag -func Bool(assignmentVar *bool, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// BoolSlice adds a new slice of bools flag -// Specify the flag multiple times to fill the slice -func BoolSlice(assignmentVar *[]bool, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// ByteSlice adds a new slice of bytes flag -// Specify the flag multiple times to fill the slice. Takes hex as input. -func ByteSlice(assignmentVar *[]byte, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Duration adds a new time.Duration flag. -// Input format is described in time.ParseDuration(). -// Example values: 1h, 1h50m, 32s -func Duration(assignmentVar *time.Duration, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// DurationSlice adds a new time.Duration flag. -// Input format is described in time.ParseDuration(). -// Example values: 1h, 1h50m, 32s -// Specify the flag multiple times to fill the slice. -func DurationSlice(assignmentVar *[]time.Duration, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Float32 adds a new float32 flag. -func Float32(assignmentVar *float32, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Float32Slice adds a new float32 flag. -// Specify the flag multiple times to fill the slice. -func Float32Slice(assignmentVar *[]float32, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Float64 adds a new float64 flag. -func Float64(assignmentVar *float64, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Float64Slice adds a new float64 flag. -// Specify the flag multiple times to fill the slice. -func Float64Slice(assignmentVar *[]float64, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Int adds a new int flag -func Int(assignmentVar *int, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// IntSlice adds a new int slice flag. -// Specify the flag multiple times to fill the slice. -func IntSlice(assignmentVar *[]int, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// UInt adds a new uint flag -func UInt(assignmentVar *uint, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// UIntSlice adds a new uint slice flag. -// Specify the flag multiple times to fill the slice. -func UIntSlice(assignmentVar *[]uint, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// UInt64 adds a new uint64 flag -func UInt64(assignmentVar *uint64, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// UInt64Slice adds a new uint64 slice flag. -// Specify the flag multiple times to fill the slice. -func UInt64Slice(assignmentVar *[]uint64, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// UInt32 adds a new uint32 flag -func UInt32(assignmentVar *uint32, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// UInt32Slice adds a new uint32 slice flag. -// Specify the flag multiple times to fill the slice. -func UInt32Slice(assignmentVar *[]uint32, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// UInt16 adds a new uint16 flag -func UInt16(assignmentVar *uint16, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// UInt16Slice adds a new uint16 slice flag. -// Specify the flag multiple times to fill the slice. -func UInt16Slice(assignmentVar *[]uint16, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// UInt8 adds a new uint8 flag -func UInt8(assignmentVar *uint8, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// UInt8Slice adds a new uint8 slice flag. -// Specify the flag multiple times to fill the slice. -func UInt8Slice(assignmentVar *[]uint8, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Int64 adds a new int64 flag -func Int64(assignmentVar *int64, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Int64Slice adds a new int64 slice flag. -// Specify the flag multiple times to fill the slice. -func Int64Slice(assignmentVar *[]int64, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Int32 adds a new int32 flag -func Int32(assignmentVar *int32, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Int32Slice adds a new int32 slice flag. -// Specify the flag multiple times to fill the slice. -func Int32Slice(assignmentVar *[]int32, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Int16 adds a new int16 flag -func Int16(assignmentVar *int16, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Int16Slice adds a new int16 slice flag. -// Specify the flag multiple times to fill the slice. -func Int16Slice(assignmentVar *[]int16, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Int8 adds a new int8 flag -func Int8(assignmentVar *int8, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// Int8Slice adds a new int8 slice flag. -// Specify the flag multiple times to fill the slice. -func Int8Slice(assignmentVar *[]int8, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// IP adds a new net.IP flag. -func IP(assignmentVar *net.IP, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// IPSlice adds a new int8 slice flag. -// Specify the flag multiple times to fill the slice. -func IPSlice(assignmentVar *[]net.IP, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// HardwareAddr adds a new net.HardwareAddr flag. -func HardwareAddr(assignmentVar *net.HardwareAddr, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// HardwareAddrSlice adds a new net.HardwareAddr slice flag. -// Specify the flag multiple times to fill the slice. -func HardwareAddrSlice(assignmentVar *[]net.HardwareAddr, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// IPMask adds a new net.IPMask flag. IPv4 Only. -func IPMask(assignmentVar *net.IPMask, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// IPMaskSlice adds a new net.HardwareAddr slice flag. IPv4 only. -// Specify the flag multiple times to fill the slice. -func IPMaskSlice(assignmentVar *[]net.IPMask, shortName string, longName string, description string) { - DefaultParser.add(assignmentVar, shortName, longName, description) -} - -// AttachSubcommand adds a subcommand for parsing -func AttachSubcommand(subcommand *Subcommand, relativePosition int) { - DefaultParser.AttachSubcommand(subcommand, relativePosition) -} - -// ShowHelp shows parser help -func ShowHelp(message string) { - DefaultParser.ShowHelpWithMessage(message) -} - -// SetDescription sets the description of the default package command parser -func SetDescription(description string) { - DefaultParser.Description = description -} - -// SetVersion sets the version of the default package command parser -func SetVersion(version string) { - DefaultParser.Version = version -} - -// SetName sets the name of the default package command parser -func SetName(name string) { - DefaultParser.Name = name -} - -// ShowHelpAndExit shows parser help and exits with status code 2 -func ShowHelpAndExit(message string) { - ShowHelp(message) - exitOrPanic(2) -} - -// PanicInsteadOfExit is used when running tests -var PanicInsteadOfExit bool - -// exitOrPanic panics instead of calling os.Exit so that tests can catch -// more failures -func exitOrPanic(code int) { - if PanicInsteadOfExit { - panic("Panic instead of exit with code: " + strconv.Itoa(code)) - } - os.Exit(code) -} - -// ShowHelpOnUnexpectedEnable enables the ShowHelpOnUnexpected behavior on the -// default parser. This causes unknown inputs to error out. -func ShowHelpOnUnexpectedEnable() { - DefaultParser.ShowHelpOnUnexpected = true -} - -// ShowHelpOnUnexpectedDisable disables the ShowHelpOnUnexpected behavior on the -// default parser. This causes unknown inputs to error out. -func ShowHelpOnUnexpectedDisable() { - DefaultParser.ShowHelpOnUnexpected = false -} - -// AddPositionalValue adds a positional value to the main parser at the global -// context -func AddPositionalValue(assignmentVar *string, name string, relativePosition int, required bool, description string) { - DefaultParser.AddPositionalValue(assignmentVar, name, relativePosition, required, description) -} - -// debugPrint prints if debugging is enabled -func debugPrint(i ...interface{}) { - if DebugMode { - fmt.Println(i...) - } -} diff --git a/vendor/github.com/integrii/flaggy/parsedValue.go b/vendor/github.com/integrii/flaggy/parsedValue.go deleted file mode 100644 index 04ada324ad6..00000000000 --- a/vendor/github.com/integrii/flaggy/parsedValue.go +++ /dev/null @@ -1,23 +0,0 @@ -package flaggy - -// parsedValue represents a flag or subcommand that was parsed. Primairily used -// to account for all parsed values in order to determine if unknown values were -// passed to the root parser after all subcommands have been parsed. -type parsedValue struct { - Key string - Value string - IsPositional bool // indicates that this value was positional and not a key/value -} - -// newParsedValue creates and returns a new parsedValue struct with the -// supplied values set -func newParsedValue(key string, value string, isPositional bool) parsedValue { - if len(key) == 0 && len(value) == 0 { - panic("cant add parsed value with no key or value") - } - return parsedValue{ - Key: key, - Value: value, - IsPositional: isPositional, - } -} diff --git a/vendor/github.com/integrii/flaggy/parser.go b/vendor/github.com/integrii/flaggy/parser.go deleted file mode 100644 index 41ab76e60b5..00000000000 --- a/vendor/github.com/integrii/flaggy/parser.go +++ /dev/null @@ -1,195 +0,0 @@ -package flaggy - -import ( - "errors" - "fmt" - "os" - "strconv" - - "text/template" -) - -// Parser represents the set of flags and subcommands we are expecting -// from our input arguments. Parser is the top level struct responsible for -// parsing an entire set of subcommands and flags. -type Parser struct { - Subcommand - Version string // the optional version of the parser. - ShowHelpWithHFlag bool // display help when -h or --help passed - ShowVersionWithVersionFlag bool // display the version when --version passed - ShowHelpOnUnexpected bool // display help when an unexpected flag or subcommand is passed - TrailingArguments []string // everything after a -- is placed here - HelpTemplate *template.Template // template for Help output - trailingArgumentsExtracted bool // indicates that trailing args have been parsed and should not be appended again - parsed bool // indicates this parser has parsed - subcommandContext *Subcommand // points to the most specific subcommand being used -} - -// NewParser creates a new ArgumentParser ready to parse inputs -func NewParser(name string) *Parser { - // this can not be done inline because of struct embedding - p := &Parser{} - p.Name = name - p.Version = defaultVersion - p.ShowHelpOnUnexpected = true - p.ShowHelpWithHFlag = true - p.ShowVersionWithVersionFlag = true - p.SetHelpTemplate(DefaultHelpTemplate) - p.subcommandContext = &Subcommand{} - return p -} - -// ParseArgs parses as if the passed args were the os.Args, but without the -// binary at the 0 position in the array. An error is returned if there -// is a low level issue converting flags to their proper type. No error -// is returned for invalid arguments or missing require subcommands. -func (p *Parser) ParseArgs(args []string) error { - if p.parsed { - return errors.New("Parser.Parse() called twice on parser with name: " + " " + p.Name + " " + p.ShortName) - } - p.parsed = true - - debugPrint("Kicking off parsing with args:", args) - err := p.parse(p, args, 0) - if err != nil { - return err - } - - // if we are set to crash on unexpected args, look for those here TODO - if p.ShowHelpOnUnexpected { - parsedValues := p.findAllParsedValues() - debugPrint("parsedValues:", parsedValues) - argsNotParsed := findArgsNotInParsedValues(args, parsedValues) - if len(argsNotParsed) > 0 { - // flatten out unused args for our error message - var argsNotParsedFlat string - for _, a := range argsNotParsed { - argsNotParsedFlat = argsNotParsedFlat + " " + a - } - p.ShowHelpAndExit("Unknown arguments supplied: " + argsNotParsedFlat) - } - } - - return nil -} - -// findArgsNotInParsedValues finds arguments not used in parsed values. The -// incoming args should be in the order supplied by the user and should not -// include the invoked binary, which is normally the first thing in os.Args. -func findArgsNotInParsedValues(args []string, parsedValues []parsedValue) []string { - var argsNotUsed []string - var skipNext bool - for _, a := range args { - - // if the final argument (--) is seen, then we stop checking because all - // further values are trailing arguments. - if determineArgType(a) == argIsFinal { - return argsNotUsed - } - - // allow for skipping the next arg when needed - if skipNext { - skipNext = false - continue - } - - // strip flag slashes from incoming arguments so they match up with the - // keys from parsedValues. - arg := parseFlagToName(a) - - // indicates that we found this arg used in one of the parsed values. Used - // to indicate which values should be added to argsNotUsed. - var foundArgUsed bool - - // search all args for a corresponding parsed value - for _, pv := range parsedValues { - // this argumenet was a key - // debugPrint(pv.Key, "==", arg) - debugPrint(pv.Key + "==" + arg + " || (" + strconv.FormatBool(pv.IsPositional) + " && " + pv.Value + " == " + arg + ")") - if pv.Key == arg || (pv.IsPositional && pv.Value == arg) { - debugPrint("Found matching parsed arg for " + pv.Key) - foundArgUsed = true // the arg was used in this parsedValues set - // if the value is not a positional value and the parsed value had a - // value that was not blank, we skip the next value in the argument list - if !pv.IsPositional && len(pv.Value) > 0 { - skipNext = true - break - } - } - // this prevents excessive parsed values from being checked after we find - // the arg used for the first time - if foundArgUsed { - break - } - } - - // if the arg was not used in any parsed values, then we add it to the slice - // of arguments not used - if !foundArgUsed { - argsNotUsed = append(argsNotUsed, arg) - } - } - - return argsNotUsed -} - -// ShowVersionAndExit shows the version of this parser -func (p *Parser) ShowVersionAndExit() { - fmt.Println("Version:", p.Version) - exitOrPanic(0) -} - -// SetHelpTemplate sets the go template this parser will use when rendering -// Help. -func (p *Parser) SetHelpTemplate(tmpl string) error { - var err error - p.HelpTemplate = template.New(helpFlagLongName) - p.HelpTemplate, err = p.HelpTemplate.Parse(tmpl) - if err != nil { - return err - } - return nil -} - -// Parse calculates all flags and subcommands -func (p *Parser) Parse() error { - - err := p.ParseArgs(os.Args[1:]) - if err != nil { - return err - } - return nil - -} - -// ShowHelp shows Help without an error message -func (p *Parser) ShowHelp() { - debugPrint("showing help for", p.subcommandContext.Name) - p.ShowHelpWithMessage("") -} - -// ShowHelpAndExit shows parser help and exits with status code 2 -func (p *Parser) ShowHelpAndExit(message string) { - p.ShowHelpWithMessage(message) - exitOrPanic(2) -} - -// ShowHelpWithMessage shows the Help for this parser with an optional string error -// message as a header. The supplied subcommand will be the context of Help -// displayed to the user. -func (p *Parser) ShowHelpWithMessage(message string) { - - // create a new Help values template and extract values into it - help := Help{} - help.ExtractValues(p, message) - err := p.HelpTemplate.Execute(os.Stderr, help) - if err != nil { - fmt.Fprintln(os.Stderr, "Error rendering Help template:", err) - } -} - -// DisableShowVersionWithVersion disables the showing of version information -// with --version. It is enabled by default. -func (p *Parser) DisableShowVersionWithVersion() { - p.ShowVersionWithVersionFlag = false -} diff --git a/vendor/github.com/integrii/flaggy/positionalValue.go b/vendor/github.com/integrii/flaggy/positionalValue.go deleted file mode 100644 index bf649a9d9e0..00000000000 --- a/vendor/github.com/integrii/flaggy/positionalValue.go +++ /dev/null @@ -1,14 +0,0 @@ -package flaggy - -// PositionalValue represents a value which is determined by its position -// relative to where a subcommand was detected. -type PositionalValue struct { - Name string // used in documentation only - Description string - AssignmentVar *string // the var that will get this variable - Position int // the position, not including switches, of this variable - Required bool // this subcommand must always be specified - Found bool // was this positional found during parsing? - Hidden bool // indicates this positional value should be hidden from help - defaultValue string // used for help output -} diff --git a/vendor/github.com/integrii/flaggy/subCommand.go b/vendor/github.com/integrii/flaggy/subCommand.go deleted file mode 100644 index 7f99e3e8aa2..00000000000 --- a/vendor/github.com/integrii/flaggy/subCommand.go +++ /dev/null @@ -1,755 +0,0 @@ -package flaggy - -import ( - "fmt" - "log" - "net" - "os" - "strconv" - "strings" - "time" -) - -// Subcommand represents a subcommand which contains a set of child -// subcommands along with a set of flags relevant to it. Parsing -// runs until a subcommand is detected by matching its name and -// position. Once a matching subcommand is found, the next set -// of parsing occurs within that matched subcommand. -type Subcommand struct { - Name string - ShortName string - Description string - Position int // the position of this subcommand, not including flags - Subcommands []*Subcommand - Flags []*Flag - PositionalFlags []*PositionalValue - ParsedValues []parsedValue // a list of values and positionals parsed - AdditionalHelpPrepend string // additional prepended message when Help is displayed - AdditionalHelpAppend string // additional appended message when Help is displayed - Used bool // indicates this subcommand was found and parsed - Hidden bool // indicates this subcommand should be hidden from help -} - -// NewSubcommand creates a new subcommand that can have flags or PositionalFlags -// added to it. The position starts with 1, not 0 -func NewSubcommand(name string) *Subcommand { - if len(name) == 0 { - fmt.Fprintln(os.Stderr, "Error creating subcommand (NewSubcommand()). No subcommand name was specified.") - exitOrPanic(2) - } - newSC := &Subcommand{ - Name: name, - } - return newSC -} - -// parseAllFlagsFromArgs parses the non-positional flags such as -f or -v=value -// out of the supplied args and returns the resulting positional items in order, -// all the flag names found (without values), a bool to indicate if help was -// requested, and any errors found during parsing -func (sc *Subcommand) parseAllFlagsFromArgs(p *Parser, args []string) ([]string, bool, error) { - - var positionalOnlyArguments []string - var helpRequested bool // indicates the user has supplied -h and we - // should render help if we are the last subcommand - - // indicates we should skip the next argument, like when parsing a flag - // that separates key and value by space - var skipNext bool - - // endArgfound indicates that a -- was found and everything - // remaining should be added to the trailing arguments slices - var endArgFound bool - - // find all the normal flags (not positional) and parse them out - for i, a := range args { - - debugPrint("parsing arg:", a) - - // evaluate if there is a following arg to avoid panics - var nextArgExists bool - var nextArg string - if len(args)-1 >= i+1 { - nextArgExists = true - nextArg = args[i+1] - } - - // if end arg -- has been found, just add everything to TrailingArguments - if endArgFound { - if !p.trailingArgumentsExtracted { - p.TrailingArguments = append(p.TrailingArguments, a) - } - continue - } - - // skip this run if specified - if skipNext { - skipNext = false - debugPrint("skipping flag because it is an arg:", a) - continue - } - - // parse the flag into its name for consideration without dashes - flagName := parseFlagToName(a) - - // if the flag being passed is version or v and the option to display - // version with version flags, then display version - if p.ShowVersionWithVersionFlag { - if flagName == versionFlagLongName { - p.ShowVersionAndExit() - } - } - - // if the show Help on h flag option is set, then show Help when h or Help - // is passed as an option - if p.ShowHelpWithHFlag { - if flagName == helpFlagShortName || flagName == helpFlagLongName { - // Ensure this is the last subcommand passed so we give the correct - // help output - helpRequested = true - continue - } - } - - // determine what kind of flag this is - argType := determineArgType(a) - - // strip flags from arg - // debugPrint("Parsing flag named", a, "of type", argType) - - // depending on the flag type, parse the key and value out, then apply it - switch argType { - case argIsFinal: - // debugPrint("Arg", i, "is final:", a) - endArgFound = true - case argIsPositional: - // debugPrint("Arg is positional or subcommand:", a) - // this positional argument into a slice of their own, so that - // we can determine if its a subcommand or positional value later - positionalOnlyArguments = append(positionalOnlyArguments, a) - // track this as a parsed value with the subcommand - sc.addParsedPositionalValue(a) - case argIsFlagWithSpace: // a flag with a space. ex) -k v or --key value - a = parseFlagToName(a) - - // debugPrint("Arg", i, "is flag with space:", a) - // parse next arg as value to this flag and apply to subcommand flags - // if the flag is a bool flag, then we check for a following positional - // and skip it if necessary - if flagIsBool(sc, p, a) { - debugPrint(sc.Name, "bool flag", a, "next var is:", nextArg) - // set the value in this subcommand and its root parser - valueSet, err := setValueForParsers(a, "true", p, sc) - - // if an error occurs, just return it and quit parsing - if err != nil { - return []string{}, false, err - } - - // log all values parsed by this subcommand. We leave the value blank - // because the bool value had no explicit true or false supplied - if valueSet { - sc.addParsedFlag(a, "") - } - - // we've found and set a standalone bool flag, so we move on to the next - // argument in the list of arguments - continue - } - - skipNext = true - // debugPrint(sc.Name, "NOT bool flag", a) - - // if the next arg was not found, then show a Help message - if !nextArgExists { - p.ShowHelpWithMessage("Expected a following arg for flag " + a + ", but it did not exist.") - exitOrPanic(2) - } - valueSet, err := setValueForParsers(a, nextArg, p, sc) - if err != nil { - return []string{}, false, err - } - - // log all parsed values in the subcommand - if valueSet { - sc.addParsedFlag(a, nextArg) - } - case argIsFlagWithValue: // a flag with an equals sign. ex) -k=v or --key=value - // debugPrint("Arg", i, "is flag with value:", a) - a = parseFlagToName(a) - - // parse flag into key and value and apply to subcommand flags - key, val := parseArgWithValue(a) - - // set the value in this subcommand and its root parser - valueSet, err := setValueForParsers(key, val, p, sc) - if err != nil { - return []string{}, false, err - } - - // log all values parsed by the subcommand - if valueSet { - sc.addParsedFlag(a, val) - } - } - } - - return positionalOnlyArguments, helpRequested, nil -} - -// findAllParsedValues finds all values parsed by all subcommands and this -// subcommand and its child subcommands -func (sc *Subcommand) findAllParsedValues() []parsedValue { - parsedValues := sc.ParsedValues - for _, sc := range sc.Subcommands { - // skip unused subcommands - if !sc.Used { - continue - } - parsedValues = append(parsedValues, sc.findAllParsedValues()...) - } - return parsedValues -} - -// parse causes the argument parser to parse based on the supplied []string. -// depth specifies the non-flag subcommand positional depth. A slice of flags -// and subcommands parsed is returned so that the parser can ultimately decide -// if there were any unexpected values supplied by the user -func (sc *Subcommand) parse(p *Parser, args []string, depth int) error { - - debugPrint("- Parsing subcommand", sc.Name, "with depth of", depth, "and args", args) - - // if a command is parsed, its used - sc.Used = true - debugPrint("used subcommand", sc.Name, sc.ShortName) - if len(sc.Name) > 0 { - sc.addParsedPositionalValue(sc.Name) - } - if len(sc.ShortName) > 0 { - sc.addParsedPositionalValue(sc.ShortName) - } - - // as subcommands are used, they become the context of the parser. This helps - // us understand how to display help based on which subcommand is being used - p.subcommandContext = sc - - // ensure that help and version flags are not used if the parser has the - // built-in help and version flags enabled - if p.ShowHelpWithHFlag { - sc.ensureNoConflictWithBuiltinHelp() - } - if p.ShowVersionWithVersionFlag { - sc.ensureNoConflictWithBuiltinVersion() - } - - // Parse the normal flags out of the argument list and return the positionals - // (subcommands and positional values), along with the flags used. - // Then the flag values are applied to the parent parser and the current - // subcommand being parsed. - positionalOnlyArguments, helpRequested, err := sc.parseAllFlagsFromArgs(p, args) - if err != nil { - return err - } - - // indicate that trailing arguments have been extracted, so that they aren't - // appended a second time - p.trailingArgumentsExtracted = true - - // loop over positional values and look for their matching positional - // parameter, or their positional command. If neither are found, then - // we throw an error - var parsedArgCount int - for pos, v := range positionalOnlyArguments { - - // the first relative positional argument will be human natural at position 1 - // but offset for the depth of relative commands being parsed for currently. - relativeDepth := pos - depth + 1 - // debugPrint("Parsing positional only position", relativeDepth, "with value", v) - - if relativeDepth < 1 { - // debugPrint(sc.Name, "skipped value:", v) - continue - } - parsedArgCount++ - - // determine subcommands and parse them by positional value and name - for _, cmd := range sc.Subcommands { - // debugPrint("Subcommand being compared", relativeDepth, "==", cmd.Position, "and", v, "==", cmd.Name, "==", cmd.ShortName) - if relativeDepth == cmd.Position && (v == cmd.Name || v == cmd.ShortName) { - debugPrint("Decending into positional subcommand", cmd.Name, "at relativeDepth", relativeDepth, "and absolute depth", depth+1) - return cmd.parse(p, args, depth+parsedArgCount) // continue recursive positional parsing - } - } - - // determine positional args and parse them by positional value and name - var foundPositional bool - for _, val := range sc.PositionalFlags { - if relativeDepth == val.Position { - debugPrint("Found a positional value at relativePos:", relativeDepth, "value:", v) - - // set original value for help output - val.defaultValue = *val.AssignmentVar - - // defrerence the struct pointer, then set the pointer property within it - *val.AssignmentVar = v - // debugPrint("set positional to value", *val.AssignmentVar) - foundPositional = true - val.Found = true - break - } - } - - // if there aren't any positional flags but there are subcommands that - // were not used, display a useful message with subcommand options. - if !foundPositional && p.ShowHelpOnUnexpected { - debugPrint("No positional at position", relativeDepth) - var foundSubcommandAtDepth bool - for _, cmd := range sc.Subcommands { - if cmd.Position == relativeDepth { - foundSubcommandAtDepth = true - } - } - - // if there is a subcommand here but it was not specified, display them all - // as a suggestion to the user before exiting. - if foundSubcommandAtDepth { - // determine which name to use in upcoming help output - fmt.Fprintln(os.Stderr, sc.Name+":", "No subcommand or positional value found at position", strconv.Itoa(relativeDepth)+".") - var output string - for _, cmd := range sc.Subcommands { - if cmd.Hidden { - continue - } - output = output + " " + cmd.Name - } - // if there are available subcommands, let the user know - if len(output) > 0 { - output = strings.TrimLeft(output, " ") - fmt.Println("Available subcommands:", output) - } - exitOrPanic(2) - } - - // if there were not any flags or subcommands at this position at all, then - // throw an error (display Help if necessary) - p.ShowHelpWithMessage("Unexpected argument: " + v) - exitOrPanic(2) - } - } - - // if help was requested and we should show help when h is passed, - if helpRequested && p.ShowHelpWithHFlag { - p.ShowHelp() - exitOrPanic(0) - } - - // find any positionals that were not used on subcommands that were - // found and throw help (unknown argument) in the global parse or subcommand - for _, pv := range p.PositionalFlags { - if pv.Required && !pv.Found { - p.ShowHelpWithMessage("Required global positional variable " + pv.Name + " not found at position " + strconv.Itoa(pv.Position)) - exitOrPanic(2) - } - } - for _, pv := range sc.PositionalFlags { - if pv.Required && !pv.Found { - p.ShowHelpWithMessage("Required positional of subcommand " + sc.Name + " named " + pv.Name + " not found at position " + strconv.Itoa(pv.Position)) - exitOrPanic(2) - } - } - - return nil -} - -// addParsedFlag makes it easy to append flag values parsed by the subcommand -func (sc *Subcommand) addParsedFlag(key string, value string) { - sc.ParsedValues = append(sc.ParsedValues, newParsedValue(key, value, false)) -} - -// addParsedPositionalValue makes it easy to append positionals parsed by the -// subcommand -func (sc *Subcommand) addParsedPositionalValue(value string) { - sc.ParsedValues = append(sc.ParsedValues, newParsedValue("", value, true)) -} - -// FlagExists lets you know if the flag name exists as either a short or long -// name in the (sub)command -func (sc *Subcommand) FlagExists(name string) bool { - - for _, f := range sc.Flags { - if f.HasName(name) { - return true - } - } - - return false -} - -// AttachSubcommand adds a possible subcommand to the Parser. -func (sc *Subcommand) AttachSubcommand(newSC *Subcommand, relativePosition int) { - - // assign the depth of the subcommand when its attached - newSC.Position = relativePosition - - // ensure no subcommands at this depth with this name - for _, other := range sc.Subcommands { - if newSC.Position == other.Position { - if newSC.Name != "" { - if newSC.Name == other.Name { - log.Panicln("Unable to add subcommand because one already exists at position" + strconv.Itoa(newSC.Position) + " with name " + other.Name) - } - } - if newSC.ShortName != "" { - if newSC.ShortName == other.ShortName { - log.Panicln("Unable to add subcommand because one already exists at position" + strconv.Itoa(newSC.Position) + " with name " + other.ShortName) - } - } - } - } - - // ensure no positionals at this depth - for _, other := range sc.PositionalFlags { - if newSC.Position == other.Position { - log.Panicln("Unable to add subcommand because a positional value already exists at position " + strconv.Itoa(newSC.Position) + ": " + other.Name) - } - } - - sc.Subcommands = append(sc.Subcommands, newSC) -} - -// add is a "generic" to add flags of any type. Checks the supplied parent -// parser to ensure that the user isn't setting version or help flags that -// conflict with the built-in help and version flag behavior. -func (sc *Subcommand) add(assignmentVar interface{}, shortName string, longName string, description string) { - - // if the flag is already used, throw an error - for _, existingFlag := range sc.Flags { - if longName != "" && existingFlag.LongName == longName { - log.Panicln("Flag " + longName + " added to subcommand " + sc.Name + " but the name is already assigned.") - } - if shortName != "" && existingFlag.ShortName == shortName { - log.Panicln("Flag " + shortName + " added to subcommand " + sc.Name + " but the short name is already assigned.") - } - } - - newFlag := Flag{ - AssignmentVar: assignmentVar, - ShortName: shortName, - LongName: longName, - Description: description, - } - sc.Flags = append(sc.Flags, &newFlag) -} - -// String adds a new string flag -func (sc *Subcommand) String(assignmentVar *string, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// StringSlice adds a new slice of strings flag -// Specify the flag multiple times to fill the slice -func (sc *Subcommand) StringSlice(assignmentVar *[]string, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Bool adds a new bool flag -func (sc *Subcommand) Bool(assignmentVar *bool, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// BoolSlice adds a new slice of bools flag -// Specify the flag multiple times to fill the slice -func (sc *Subcommand) BoolSlice(assignmentVar *[]bool, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// ByteSlice adds a new slice of bytes flag -// Specify the flag multiple times to fill the slice. Takes hex as input. -func (sc *Subcommand) ByteSlice(assignmentVar *[]byte, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Duration adds a new time.Duration flag. -// Input format is described in time.ParseDuration(). -// Example values: 1h, 1h50m, 32s -func (sc *Subcommand) Duration(assignmentVar *time.Duration, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// DurationSlice adds a new time.Duration flag. -// Input format is described in time.ParseDuration(). -// Example values: 1h, 1h50m, 32s -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) DurationSlice(assignmentVar *[]time.Duration, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Float32 adds a new float32 flag. -func (sc *Subcommand) Float32(assignmentVar *float32, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Float32Slice adds a new float32 flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) Float32Slice(assignmentVar *[]float32, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Float64 adds a new float64 flag. -func (sc *Subcommand) Float64(assignmentVar *float64, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Float64Slice adds a new float64 flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) Float64Slice(assignmentVar *[]float64, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Int adds a new int flag -func (sc *Subcommand) Int(assignmentVar *int, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// IntSlice adds a new int slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) IntSlice(assignmentVar *[]int, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// UInt adds a new uint flag -func (sc *Subcommand) UInt(assignmentVar *uint, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// UIntSlice adds a new uint slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) UIntSlice(assignmentVar *[]uint, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// UInt64 adds a new uint64 flag -func (sc *Subcommand) UInt64(assignmentVar *uint64, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// UInt64Slice adds a new uint64 slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) UInt64Slice(assignmentVar *[]uint64, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// UInt32 adds a new uint32 flag -func (sc *Subcommand) UInt32(assignmentVar *uint32, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// UInt32Slice adds a new uint32 slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) UInt32Slice(assignmentVar *[]uint32, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// UInt16 adds a new uint16 flag -func (sc *Subcommand) UInt16(assignmentVar *uint16, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// UInt16Slice adds a new uint16 slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) UInt16Slice(assignmentVar *[]uint16, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// UInt8 adds a new uint8 flag -func (sc *Subcommand) UInt8(assignmentVar *uint8, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// UInt8Slice adds a new uint8 slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) UInt8Slice(assignmentVar *[]uint8, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Int64 adds a new int64 flag. -func (sc *Subcommand) Int64(assignmentVar *int64, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Int64Slice adds a new int64 slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) Int64Slice(assignmentVar *[]int64, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Int32 adds a new int32 flag -func (sc *Subcommand) Int32(assignmentVar *int32, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Int32Slice adds a new int32 slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) Int32Slice(assignmentVar *[]int32, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Int16 adds a new int16 flag -func (sc *Subcommand) Int16(assignmentVar *int16, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Int16Slice adds a new int16 slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) Int16Slice(assignmentVar *[]int16, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Int8 adds a new int8 flag -func (sc *Subcommand) Int8(assignmentVar *int8, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// Int8Slice adds a new int8 slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) Int8Slice(assignmentVar *[]int8, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// IP adds a new net.IP flag. -func (sc *Subcommand) IP(assignmentVar *net.IP, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// IPSlice adds a new int8 slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) IPSlice(assignmentVar *[]net.IP, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// HardwareAddr adds a new net.HardwareAddr flag. -func (sc *Subcommand) HardwareAddr(assignmentVar *net.HardwareAddr, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// HardwareAddrSlice adds a new net.HardwareAddr slice flag. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) HardwareAddrSlice(assignmentVar *[]net.HardwareAddr, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// IPMask adds a new net.IPMask flag. IPv4 Only. -func (sc *Subcommand) IPMask(assignmentVar *net.IPMask, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// IPMaskSlice adds a new net.HardwareAddr slice flag. IPv4 only. -// Specify the flag multiple times to fill the slice. -func (sc *Subcommand) IPMaskSlice(assignmentVar *[]net.IPMask, shortName string, longName string, description string) { - sc.add(assignmentVar, shortName, longName, description) -} - -// AddPositionalValue adds a positional value to the subcommand. the -// relativePosition starts at 1 and is relative to the subcommand it belongs to -func (sc *Subcommand) AddPositionalValue(assignmentVar *string, name string, relativePosition int, required bool, description string) { - - // ensure no other positionals are at this depth - for _, other := range sc.PositionalFlags { - if relativePosition == other.Position { - log.Panicln("Unable to add positional value because one already exists at position: " + strconv.Itoa(relativePosition)) - } - } - - // ensure no subcommands at this depth - for _, other := range sc.Subcommands { - if relativePosition == other.Position { - log.Panicln("Unable to add positional value a subcommand already exists at position: " + strconv.Itoa(relativePosition)) - } - } - - newPositionalValue := PositionalValue{ - Name: name, - Position: relativePosition, - AssignmentVar: assignmentVar, - Required: required, - Description: description, - defaultValue: *assignmentVar, - } - sc.PositionalFlags = append(sc.PositionalFlags, &newPositionalValue) -} - -// SetValueForKey sets the value for the specified key. If setting a bool -// value, then send "true" or "false" as strings. The returned bool indicates -// that a value was set. -func (sc *Subcommand) SetValueForKey(key string, value string) (bool, error) { - - // debugPrint("Looking to set key", key, "to value", value) - // check for and assign flags that match the key - for _, f := range sc.Flags { - // debugPrint("Evaluating string flag", f.ShortName, "==", key, "||", f.LongName, "==", key) - if f.ShortName == key || f.LongName == key { - // debugPrint("Setting string value for", key, "to", value) - f.identifyAndAssignValue(value) - return true, nil - } - } - - // debugPrint(sc.Name, "was unable to find a key named", key, "to set to value", value) - return false, nil -} - -// ensureNoConflictWithBuiltinHelp ensures that the flags on this subcommand do -// not conflict with the builtin help flags (-h or --help). Exits the program -// if a conflict is found. -func (sc *Subcommand) ensureNoConflictWithBuiltinHelp() { - for _, f := range sc.Flags { - if f.LongName == helpFlagLongName { - sc.exitBecauseOfHelpFlagConflict(f.LongName) - } - if f.LongName == helpFlagShortName { - sc.exitBecauseOfHelpFlagConflict(f.LongName) - } - if f.ShortName == helpFlagLongName { - sc.exitBecauseOfHelpFlagConflict(f.ShortName) - } - if f.ShortName == helpFlagShortName { - sc.exitBecauseOfHelpFlagConflict(f.ShortName) - } - } -} - -// ensureNoConflictWithBuiltinVersion ensures that the flags on this subcommand do -// not conflict with the builtin version flag (--version). Exits the program -// if a conflict is found. -func (sc *Subcommand) ensureNoConflictWithBuiltinVersion() { - for _, f := range sc.Flags { - if f.LongName == versionFlagLongName { - sc.exitBecauseOfVersionFlagConflict(f.LongName) - } - if f.ShortName == versionFlagLongName { - sc.exitBecauseOfVersionFlagConflict(f.ShortName) - } - } -} - -// exitBecauseOfVersionFlagConflict exits the program with a message about how to prevent -// flags being defined from conflicting with the builtin flags. -func (sc *Subcommand) exitBecauseOfVersionFlagConflict(flagName string) { - fmt.Println(`Flag with name '` + flagName + `' conflicts with the internal --version flag in flaggy. - -You must either change the flag's name, or disable flaggy's internal version -flag with 'flaggy.DefaultParser.ShowVersionWithVersionFlag = false'. If you are using -a custom parser, you must instead set '.ShowVersionWithVersionFlag = false' on it.`) - exitOrPanic(1) -} - -// exitBecauseOfHelpFlagConflict exits the program with a message about how to prevent -// flags being defined from conflicting with the builtin flags. -func (sc *Subcommand) exitBecauseOfHelpFlagConflict(flagName string) { - fmt.Println(`Flag with name '` + flagName + `' conflicts with the internal --help or -h flag in flaggy. - -You must either change the flag's name, or disable flaggy's internal help -flag with 'flaggy.DefaultParser.ShowHelpWithHFlag = false'. If you are using -a custom parser, you must instead set '.ShowHelpWithHFlag = false' on it.`) - exitOrPanic(1) -} diff --git a/vendor/github.com/jbenet/go-context/LICENSE b/vendor/github.com/jbenet/go-context/LICENSE deleted file mode 100644 index c7386b3c940..00000000000 --- a/vendor/github.com/jbenet/go-context/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Juan Batiz-Benet - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/github.com/jbenet/go-context/io/ctxio.go b/vendor/github.com/jbenet/go-context/io/ctxio.go deleted file mode 100644 index b4f2454235a..00000000000 --- a/vendor/github.com/jbenet/go-context/io/ctxio.go +++ /dev/null @@ -1,120 +0,0 @@ -// Package ctxio provides io.Reader and io.Writer wrappers that -// respect context.Contexts. Use these at the interface between -// your context code and your io. -// -// WARNING: read the code. see how writes and reads will continue -// until you cancel the io. Maybe this package should provide -// versions of io.ReadCloser and io.WriteCloser that automatically -// call .Close when the context expires. But for now -- since in my -// use cases I have long-lived connections with ephemeral io wrappers -// -- this has yet to be a need. -package ctxio - -import ( - "io" - - context "golang.org/x/net/context" -) - -type ioret struct { - n int - err error -} - -type Writer interface { - io.Writer -} - -type ctxWriter struct { - w io.Writer - ctx context.Context -} - -// NewWriter wraps a writer to make it respect given Context. -// If there is a blocking write, the returned Writer will return -// whenever the context is cancelled (the return values are n=0 -// and err=ctx.Err().) -// -// Note well: this wrapper DOES NOT ACTUALLY cancel the underlying -// write-- there is no way to do that with the standard go io -// interface. So the read and write _will_ happen or hang. So, use -// this sparingly, make sure to cancel the read or write as necesary -// (e.g. closing a connection whose context is up, etc.) -// -// Furthermore, in order to protect your memory from being read -// _after_ you've cancelled the context, this io.Writer will -// first make a **copy** of the buffer. -func NewWriter(ctx context.Context, w io.Writer) *ctxWriter { - if ctx == nil { - ctx = context.Background() - } - return &ctxWriter{ctx: ctx, w: w} -} - -func (w *ctxWriter) Write(buf []byte) (int, error) { - buf2 := make([]byte, len(buf)) - copy(buf2, buf) - - c := make(chan ioret, 1) - - go func() { - n, err := w.w.Write(buf2) - c <- ioret{n, err} - close(c) - }() - - select { - case r := <-c: - return r.n, r.err - case <-w.ctx.Done(): - return 0, w.ctx.Err() - } -} - -type Reader interface { - io.Reader -} - -type ctxReader struct { - r io.Reader - ctx context.Context -} - -// NewReader wraps a reader to make it respect given Context. -// If there is a blocking read, the returned Reader will return -// whenever the context is cancelled (the return values are n=0 -// and err=ctx.Err().) -// -// Note well: this wrapper DOES NOT ACTUALLY cancel the underlying -// write-- there is no way to do that with the standard go io -// interface. So the read and write _will_ happen or hang. So, use -// this sparingly, make sure to cancel the read or write as necesary -// (e.g. closing a connection whose context is up, etc.) -// -// Furthermore, in order to protect your memory from being read -// _before_ you've cancelled the context, this io.Reader will -// allocate a buffer of the same size, and **copy** into the client's -// if the read succeeds in time. -func NewReader(ctx context.Context, r io.Reader) *ctxReader { - return &ctxReader{ctx: ctx, r: r} -} - -func (r *ctxReader) Read(buf []byte) (int, error) { - buf2 := make([]byte, len(buf)) - - c := make(chan ioret, 1) - - go func() { - n, err := r.r.Read(buf2) - c <- ioret{n, err} - close(c) - }() - - select { - case ret := <-c: - copy(buf, buf2) - return ret.n, ret.err - case <-r.ctx.Done(): - return 0, r.ctx.Err() - } -} diff --git a/vendor/github.com/jesseduffield/generics/LICENSE b/vendor/github.com/jesseduffield/generics/LICENSE deleted file mode 100644 index 2a7175dcc30..00000000000 --- a/vendor/github.com/jesseduffield/generics/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Jesse Duffield - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/jesseduffield/generics/maps/maps.go b/vendor/github.com/jesseduffield/generics/maps/maps.go deleted file mode 100644 index 26d9c32c2b0..00000000000 --- a/vendor/github.com/jesseduffield/generics/maps/maps.go +++ /dev/null @@ -1,53 +0,0 @@ -package maps - -func Keys[Key comparable, Value any](m map[Key]Value) []Key { - keys := make([]Key, 0, len(m)) - for key := range m { - keys = append(keys, key) - } - return keys -} - -func Values[Key comparable, Value any](m map[Key]Value) []Value { - values := make([]Value, 0, len(m)) - for _, value := range m { - values = append(values, value) - } - return values -} - -func TransformValues[Key comparable, Value any, NewValue any]( - m map[Key]Value, fn func(Value) NewValue, -) map[Key]NewValue { - output := make(map[Key]NewValue, len(m)) - for key, value := range m { - output[key] = fn(value) - } - return output -} - -func TransformKeys[Key comparable, Value any, NewKey comparable](m map[Key]Value, fn func(Key) NewKey) map[NewKey]Value { - output := make(map[NewKey]Value, len(m)) - for key, value := range m { - output[fn(key)] = value - } - return output -} - -func MapToSlice[Key comparable, Value any, Mapped any](m map[Key]Value, f func(Key, Value) Mapped) []Mapped { - output := make([]Mapped, 0, len(m)) - for key, value := range m { - output = append(output, f(key, value)) - } - return output -} - -func Filter[Key comparable, Value any](m map[Key]Value, f func(Key, Value) bool) map[Key]Value { - output := map[Key]Value{} - for key, value := range m { - if f(key, value) { - output[key] = value - } - } - return output -} diff --git a/vendor/github.com/jesseduffield/generics/orderedset/orderedset.go b/vendor/github.com/jesseduffield/generics/orderedset/orderedset.go deleted file mode 100644 index ed5c95b54c9..00000000000 --- a/vendor/github.com/jesseduffield/generics/orderedset/orderedset.go +++ /dev/null @@ -1,65 +0,0 @@ -package orderedset - -import ( - orderedmap "github.com/wk8/go-ordered-map/v2" -) - -type OrderedSet[T comparable] struct { - om *orderedmap.OrderedMap[T, bool] -} - -func New[T comparable]() *OrderedSet[T] { - return &OrderedSet[T]{om: orderedmap.New[T, bool]()} -} - -func NewFromSlice[T comparable](slice []T) *OrderedSet[T] { - result := &OrderedSet[T]{om: orderedmap.New[T, bool](len(slice))} - result.Add(slice...) - return result -} - -func (os *OrderedSet[T]) Add(values ...T) { - for _, value := range values { - os.om.Set(value, true) - } -} - -func (os *OrderedSet[T]) Remove(value T) { - os.om.Delete(value) -} - -func (os *OrderedSet[T]) RemoveSlice(slice []T) { - for _, value := range slice { - os.Remove(value) - } -} - -func (os *OrderedSet[T]) Includes(value T) bool { - return os.om.Value(value) -} - -func (os *OrderedSet[T]) Len() int { - return os.om.Len() -} - -func (os *OrderedSet[T]) ToSliceFromOldest() []T { - // TODO: can be simplified to - // return os.om.KeysFromOldest() - // when we update to a newer version of go-ordered-map - result := make([]T, 0, os.Len()) - for pair := os.om.Oldest(); pair != nil; pair = pair.Next() { - result = append(result, pair.Key) - } - return result -} - -func (os *OrderedSet[T]) ToSliceFromNewest() []T { - // TODO: can be simplified to - // return os.om.KeysFromNewest() - // when we update to a newer version of go-ordered-map - result := make([]T, 0, os.Len()) - for pair := os.om.Newest(); pair != nil; pair = pair.Prev() { - result = append(result, pair.Key) - } - return result -} diff --git a/vendor/github.com/jesseduffield/generics/set/set.go b/vendor/github.com/jesseduffield/generics/set/set.go deleted file mode 100644 index 5b8f0090e4e..00000000000 --- a/vendor/github.com/jesseduffield/generics/set/set.go +++ /dev/null @@ -1,46 +0,0 @@ -package set - -import "github.com/jesseduffield/generics/maps" - -type Set[T comparable] struct { - hashMap map[T]bool -} - -func New[T comparable]() *Set[T] { - return &Set[T]{hashMap: make(map[T]bool)} -} - -func NewFromSlice[T comparable](slice []T) *Set[T] { - result := &Set[T]{hashMap: make(map[T]bool, len(slice))} - result.Add(slice...) - return result -} - -func (s *Set[T]) Add(values ...T) { - for _, value := range values { - s.hashMap[value] = true - } -} - -func (s *Set[T]) Remove(value T) { - delete(s.hashMap, value) -} - -func (s *Set[T]) RemoveSlice(slice []T) { - for _, value := range slice { - s.Remove(value) - } -} - -func (s *Set[T]) Includes(value T) bool { - return s.hashMap[value] -} - -func (s *Set[T]) Len() int { - return len(s.hashMap) -} - -// output slice is not necessarily in the same order that items were added -func (s *Set[T]) ToSlice() []T { - return maps.Keys(s.hashMap) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/.gitignore b/vendor/github.com/jesseduffield/go-git/v5/.gitignore deleted file mode 100644 index b7f2c5807ca..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -coverage.out -*~ -coverage.txt -profile.out -.tmp/ -.git-dist/ -.vscode diff --git a/vendor/github.com/jesseduffield/go-git/v5/CODE_OF_CONDUCT.md b/vendor/github.com/jesseduffield/go-git/v5/CODE_OF_CONDUCT.md deleted file mode 100644 index a689fa3c34a..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,74 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -education, socio-economic status, nationality, personal appearance, race, -religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at conduct@sourced.tech. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - diff --git a/vendor/github.com/jesseduffield/go-git/v5/COMPATIBILITY.md b/vendor/github.com/jesseduffield/go-git/v5/COMPATIBILITY.md deleted file mode 100644 index ba1fb90ac54..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/COMPATIBILITY.md +++ /dev/null @@ -1,234 +0,0 @@ -# Supported Features - -Here is a non-comprehensive table of git commands and features and their -compatibility status with go-git. - -## Getting and creating repositories - -| Feature | Sub-feature | Status | Notes | Examples | -| ------- | ------------------------------------------------------------------------------------------------------------------ | ------ | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `init` | | ✅ | | | -| `init` | `--bare` | ✅ | | | -| `init` | `--template`
`--separate-git-dir`
`--shared` | ❌ | | | -| `clone` | | ✅ | | - [PlainClone](_examples/clone/main.go) | -| `clone` | Authentication:
- none
- access token
- username + password
- ssh | ✅ | | - [clone ssh (private_key)](_examples/clone/auth/ssh/private_key/main.go)
- [clone ssh (ssh_agent)](_examples/clone/auth/ssh/ssh_agent/main.go)
- [clone access token](_examples/clone/auth/basic/access_token/main.go)
- [clone user + password](_examples/clone/auth/basic/username_password/main.go) | -| `clone` | `--progress`
`--single-branch`
`--depth`
`--origin`
`--recurse-submodules`
`--shared` | ✅ | | - [recurse submodules](_examples/clone/main.go)
- [progress](_examples/progress/main.go) | - -## Basic snapshotting - -| Feature | Sub-feature | Status | Notes | Examples | -| -------- | ----------- | ------ | -------------------------------------------------------- | ------------------------------------ | -| `add` | | ✅ | Plain add is supported. Any other flags aren't supported | | -| `status` | | ✅ | | | -| `commit` | | ✅ | | - [commit](_examples/commit/main.go) | -| `reset` | | ✅ | | | -| `rm` | | ✅ | | | -| `mv` | | ✅ | | | - -## Branching and merging - -| Feature | Sub-feature | Status | Notes | Examples | -| ----------- | ----------- | ------------ | --------------------------------------- | ----------------------------------------------------------------------------------------------- | -| `branch` | | ✅ | | - [branch](_examples/branch/main.go) | -| `checkout` | | ✅ | Basic usages of checkout are supported. | - [checkout](_examples/checkout/main.go) | -| `merge` | | ⚠️ (partial) | Fast-forward only | | -| `mergetool` | | ❌ | | | -| `stash` | | ❌ | | | -| `sparse-checkout` | | ✅ | | - [sparse-checkout](_examples/sparse-checkout/main.go) | -| `tag` | | ✅ | | - [tag](_examples/tag/main.go)
- [tag create and push](_examples/tag-create-push/main.go) | - -## Sharing and updating projects - -| Feature | Sub-feature | Status | Notes | Examples | -| ----------- | ----------- | ------ | ----------------------------------------------------------------------- | ------------------------------------------ | -| `fetch` | | ✅ | | | -| `pull` | | ✅ | Only supports merges where the merge can be resolved as a fast-forward. | - [pull](_examples/pull/main.go) | -| `push` | | ✅ | | - [push](_examples/push/main.go) | -| `remote` | | ✅ | | - [remotes](_examples/remotes/main.go) | -| `submodule` | | ✅ | | - [submodule](_examples/submodule/main.go) | -| `submodule` | deinit | ❌ | | | - -## Inspection and comparison - -| Feature | Sub-feature | Status | Notes | Examples | -| ---------- | ----------- | --------- | ----- | ------------------------------ | -| `show` | | ✅ | | | -| `log` | | ✅ | | - [log](_examples/log/main.go) | -| `shortlog` | | (see log) | | | -| `describe` | | ❌ | | | - -## Patching - -| Feature | Sub-feature | Status | Notes | Examples | -| ------------- | ----------- | ------ | ---------------------------------------------------- | -------- | -| `apply` | | ❌ | | | -| `cherry-pick` | | ❌ | | | -| `diff` | | ✅ | Patch object with UnifiedDiff output representation. | | -| `rebase` | | ❌ | | | -| `revert` | | ❌ | | | - -## Debugging - -| Feature | Sub-feature | Status | Notes | Examples | -| -------- | ----------- | ------ | ----- | ---------------------------------- | -| `bisect` | | ❌ | | | -| `blame` | | ✅ | | - [blame](_examples/blame/main.go) | -| `grep` | | ✅ | | | - -## Email - -| Feature | Sub-feature | Status | Notes | Examples | -| -------------- | ----------- | ------ | ----- | -------- | -| `am` | | ❌ | | | -| `apply` | | ❌ | | | -| `format-patch` | | ❌ | | | -| `send-email` | | ❌ | | | -| `request-pull` | | ❌ | | | - -## External systems - -| Feature | Sub-feature | Status | Notes | Examples | -| ------------- | ----------- | ------ | ----- | -------- | -| `svn` | | ❌ | | | -| `fast-import` | | ❌ | | | -| `lfs` | | ❌ | | | - -## Administration - -| Feature | Sub-feature | Status | Notes | Examples | -| --------------- | ----------- | ------ | ----- | -------- | -| `clean` | | ✅ | | | -| `gc` | | ❌ | | | -| `fsck` | | ❌ | | | -| `reflog` | | ❌ | | | -| `filter-branch` | | ❌ | | | -| `instaweb` | | ❌ | | | -| `archive` | | ❌ | | | -| `bundle` | | ❌ | | | -| `prune` | | ❌ | | | -| `repack` | | ❌ | | | - -## Server admin - -| Feature | Sub-feature | Status | Notes | Examples | -| -------------------- | ----------- | ------ | ----- | ----------------------------------------- | -| `daemon` | | ❌ | | | -| `update-server-info` | | ✅ | | [cli](./cli/go-git/update_server_info.go) | - -## Advanced - -| Feature | Sub-feature | Status | Notes | Examples | -| ---------- | ----------- | ----------- | ----- | -------- | -| `notes` | | ❌ | | | -| `replace` | | ❌ | | | -| `worktree` | | ❌ | | | -| `annotate` | | (see blame) | | | - -## GPG - -| Feature | Sub-feature | Status | Notes | Examples | -| ------------------- | ----------- | ------ | ----- | -------- | -| `git-verify-commit` | | ✅ | | | -| `git-verify-tag` | | ✅ | | | - -## Plumbing commands - -| Feature | Sub-feature | Status | Notes | Examples | -| --------------- | ------------------------------------- | ------------ | --------------------------------------------------- | -------------------------------------------- | -| `cat-file` | | ✅ | | | -| `check-ignore` | | ❌ | | | -| `commit-tree` | | ❌ | | | -| `count-objects` | | ❌ | | | -| `diff-index` | | ❌ | | | -| `for-each-ref` | | ✅ | | | -| `hash-object` | | ✅ | | | -| `ls-files` | | ✅ | | | -| `ls-remote` | | ✅ | | - [ls-remote](_examples/ls-remote/main.go) | -| `merge-base` | `--independent`
`--is-ancestor` | ⚠️ (partial) | Calculates the merge-base only between two commits. | - [merge-base](_examples/merge_base/main.go) | -| `merge-base` | `--fork-point`
`--octopus` | ❌ | | | -| `read-tree` | | ❌ | | | -| `rev-list` | | ✅ | | | -| `rev-parse` | | ❌ | | | -| `show-ref` | | ✅ | | | -| `symbolic-ref` | | ✅ | | | -| `update-index` | | ❌ | | | -| `update-ref` | | ❌ | | | -| `verify-pack` | | ❌ | | | -| `write-tree` | | ❌ | | | - -## Indexes and Git Protocols - -| Feature | Version | Status | Notes | -| -------------------- | ------------------------------------------------------------------------------- | ------ | ----- | -| index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | -| index | [v2](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ✅ | | -| index | [v3](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | -| pack-protocol | [v1](https://github.com/git/git/blob/master/Documentation/gitprotocol-pack.txt) | ✅ | | -| pack-protocol | [v2](https://github.com/git/git/blob/master/Documentation/gitprotocol-v2.txt) | ❌ | | -| multi-pack-index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | -| pack-\*.rev files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | -| pack-\*.mtimes files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | -| cruft packs | | ❌ | | - -## Capabilities - -| Feature | Status | Notes | -| ------------------------------ | ------------ | ----- | -| `multi_ack` | ❌ | | -| `multi_ack_detailed` | ❌ | | -| `no-done` | ❌ | | -| `thin-pack` | ❌ | | -| `side-band` | ⚠️ (partial) | | -| `side-band-64k` | ⚠️ (partial) | | -| `ofs-delta` | ✅ | | -| `agent` | ✅ | | -| `object-format` | ❌ | | -| `symref` | ✅ | | -| `shallow` | ✅ | | -| `deepen-since` | ✅ | | -| `deepen-not` | ❌ | | -| `deepen-relative` | ❌ | | -| `no-progress` | ✅ | | -| `include-tag` | ✅ | | -| `report-status` | ✅ | | -| `report-status-v2` | ❌ | | -| `delete-refs` | ✅ | | -| `quiet` | ❌ | | -| `atomic` | ✅ | | -| `push-options` | ✅ | | -| `allow-tip-sha1-in-want` | ✅ | | -| `allow-reachable-sha1-in-want` | ❌ | | -| `push-cert=` | ❌ | | -| `filter` | ❌ | | -| `session-id=` | ❌ | | - -## Transport Schemes - -| Scheme | Status | Notes | Examples | -| -------------------- | ------------ | ---------------------------------------------------------------------- | ---------------------------------------------- | -| `http(s)://` (dumb) | ❌ | | | -| `http(s)://` (smart) | ✅ | | | -| `git://` | ✅ | | | -| `ssh://` | ✅ | | | -| `file://` | ⚠️ (partial) | Warning: this is not pure Golang. This shells out to the `git` binary. | | -| Custom | ✅ | All existing schemes can be replaced by custom implementations. | - [custom_http](_examples/custom_http/main.go) | - -## SHA256 - -| Feature | Sub-feature | Status | Notes | Examples | -| -------- | ----------- | ------ | ---------------------------------- | ------------------------------------ | -| `init` | | ✅ | Requires building with tag sha256. | - [init](_examples/sha256/main.go) | -| `commit` | | ✅ | Requires building with tag sha256. | - [commit](_examples/sha256/main.go) | -| `pull` | | ❌ | | | -| `fetch` | | ❌ | | | -| `push` | | ❌ | | | - -## Other features - -| Feature | Sub-feature | Status | Notes | Examples | -| --------------- | --------------------------- | ------ | ---------------------------------------------- | -------- | -| `config` | `--local` | ✅ | Read and write per-repository (`.git/config`). | | -| `config` | `--global`
`--system` | ✅ | Read-only. | | -| `gitignore` | | ✅ | | | -| `gitattributes` | | ✅ | | | -| `git-worktree` | | ❌ | Multiple worktrees are not supported. | | diff --git a/vendor/github.com/jesseduffield/go-git/v5/CONTRIBUTING.md b/vendor/github.com/jesseduffield/go-git/v5/CONTRIBUTING.md deleted file mode 100644 index a5b01823bfc..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/CONTRIBUTING.md +++ /dev/null @@ -1,53 +0,0 @@ -# Contributing Guidelines - -source{d} go-git project is [Apache 2.0 licensed](LICENSE) and accepts -contributions via GitHub pull requests. This document outlines some of the -conventions on development workflow, commit message formatting, contact points, -and other resources to make it easier to get your contribution accepted. - -## Support Channels - -The official support channels, for both users and contributors, are: - -- [StackOverflow go-git tag](https://stackoverflow.com/questions/tagged/go-git) for user questions. -- GitHub [Issues](https://github.com/src-d/go-git/issues)* for bug reports and feature requests. - -*Before opening a new issue or submitting a new pull request, it's helpful to -search the project - it's likely that another user has already reported the -issue you're facing, or it's a known issue that we're already aware of. - - -## How to Contribute - -Pull Requests (PRs) are the main and exclusive way to contribute to the official go-git project. -In order for a PR to be accepted it needs to pass a list of requirements: - -- You should be able to run the same query using `git`. We don't accept features that are not implemented in the official git implementation. -- The expected behavior must match the [official git implementation](https://github.com/git/git). -- The actual behavior must be correctly explained with natural language and providing a minimum working example in Go that reproduces it. -- All PRs must be written in idiomatic Go, formatted according to [gofmt](https://golang.org/cmd/gofmt/), and without any warnings from [go lint](https://github.com/golang/lint) nor [go vet](https://golang.org/cmd/vet/). -- They should in general include tests, and those shall pass. -- If the PR is a bug fix, it has to include a suite of unit tests for the new functionality. -- If the PR is a new feature, it has to come with a suite of unit tests, that tests the new functionality. -- In any case, all the PRs have to pass the personal evaluation of at least one of the maintainers of go-git. - -### Branches - -The `master` branch is currently used for maintaining the `v5` major release only. The accepted changes would -be dependency bumps, bug fixes and small changes that aren't needed for `v6`. New development should target the -`v6-exp` branch, and if agreed with at least one go-git maintainer, it can be back ported to `v5` by creating -a new PR that targets `master`. - -### Format of the commit message - -Every commit message should describe what was changed, under which context and, if applicable, the GitHub issue it relates to: - -``` -plumbing: packp, Skip argument validations for unknown capabilities. Fixes #623 -``` - -The format can be described more formally as follows: - -``` -: , . [Fixes #] -``` diff --git a/vendor/github.com/jesseduffield/go-git/v5/EXTENDING.md b/vendor/github.com/jesseduffield/go-git/v5/EXTENDING.md deleted file mode 100644 index a2778e34abf..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/EXTENDING.md +++ /dev/null @@ -1,78 +0,0 @@ -# Extending go-git - -`go-git` was built in a highly extensible manner, which enables some of its functionalities to be changed or extended without the need of changing its codebase. Here are the key extensibility features: - -## Dot Git Storers - -Dot git storers are the components responsible for storing the Git internal files, including objects and references. - -The built-in storer implementations include [memory](storage/memory) and [filesystem](storage/filesystem). The `memory` storer stores all the data in memory, and its use look like this: - -```go - r, err := git.Init(memory.NewStorage(), nil) -``` - -The `filesystem` storer stores the data in the OS filesystem, and can be used as follows: - -```go - r, err := git.Init(filesystem.NewStorage(osfs.New("/tmp/foo")), nil) -``` - -New implementations can be created by implementing the [storage.Storer interface](storage/storer.go#L16). - -## Filesystem - -Git repository worktrees are managed using a filesystem abstraction based on [go-billy](https://github.com/go-git/go-billy). The Git operations will take place against the specific filesystem implementation. Initialising a repository in Memory can be done as follows: - -```go - fs := memfs.New() - r, err := git.Init(memory.NewStorage(), fs) -``` - -The same operation can be done against the OS filesystem: - -```go - fs := osfs.New("/tmp/foo") - r, err := git.Init(memory.NewStorage(), fs) -``` - -New filesystems (e.g. cloud based storage) could be created by implementing `go-billy`'s [Filesystem interface](https://github.com/go-git/go-billy/blob/326c59f064021b821a55371d57794fbfb86d4cb3/fs.go#L52). - -## Transport Schemes - -Git supports various transport schemes, including `http`, `https`, `ssh`, `git`, `file`. `go-git` defines the [transport.Transport interface](plumbing/transport/common.go#L48) to represent them. - -The built-in implementations can be replaced by calling `client.InstallProtocol`. - -An example of changing the built-in `https` implementation to skip TLS could look like this: - -```go - customClient := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, - } - - client.InstallProtocol("https", githttp.NewClient(customClient)) -``` - -Some internal implementations enables code reuse amongst the different transport implementations. Some of these may be made public in the future (e.g. `plumbing/transport/internal/common`). - -## Cache - -Several different operations across `go-git` lean on caching of objects in order to achieve optimal performance. The caching functionality is defined by the [cache.Object interface](plumbing/cache/common.go#L17). - -Two built-in implementations are `cache.ObjectLRU` and `cache.BufferLRU`. However, the caching functionality can be customized by implementing the interface `cache.Object` interface. - -## Hash - -`go-git` uses the `crypto.Hash` interface to represent hash functions. The built-in implementations are `github.com/pjbgf/sha1cd` for SHA1 and Go's `crypto/SHA256`. - -The default hash functions can be changed by calling `hash.RegisterHash`. -```go - func init() { - hash.RegisterHash(crypto.SHA1, sha1.New) - } -``` - -New `SHA1` or `SHA256` hash functions that implement the `hash.RegisterHash` interface can be registered by calling `RegisterHash`. diff --git a/vendor/github.com/jesseduffield/go-git/v5/LICENSE b/vendor/github.com/jesseduffield/go-git/v5/LICENSE deleted file mode 100644 index 8aa3d854cf7..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2018 Sourced Technologies, S.L. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/jesseduffield/go-git/v5/Makefile b/vendor/github.com/jesseduffield/go-git/v5/Makefile deleted file mode 100644 index 3d5b54f7e65..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/Makefile +++ /dev/null @@ -1,54 +0,0 @@ -# General -WORKDIR = $(PWD) - -# Go parameters -GOCMD = go -GOTEST = $(GOCMD) test - -# Git config -GIT_VERSION ?= -GIT_DIST_PATH ?= $(PWD)/.git-dist -GIT_REPOSITORY = http://github.com/git/git.git - -# Coverage -COVERAGE_REPORT = coverage.out -COVERAGE_MODE = count - -build-git: - @if [ -f $(GIT_DIST_PATH)/git ]; then \ - echo "nothing to do, using cache $(GIT_DIST_PATH)"; \ - else \ - git clone $(GIT_REPOSITORY) -b $(GIT_VERSION) --depth 1 --single-branch $(GIT_DIST_PATH); \ - cd $(GIT_DIST_PATH); \ - make configure; \ - ./configure; \ - make all; \ - fi - -test: - @echo "running against `git version`"; \ - $(GOTEST) -race ./... - $(GOTEST) -v _examples/common_test.go _examples/common.go --examples - -TEMP_REPO := $(shell mktemp) -test-sha256: - $(GOCMD) run -tags sha256 _examples/sha256/main.go $(TEMP_REPO) - cd $(TEMP_REPO) && git fsck - rm -rf $(TEMP_REPO) - -test-coverage: - @echo "running against `git version`"; \ - echo "" > $(COVERAGE_REPORT); \ - $(GOTEST) -coverprofile=$(COVERAGE_REPORT) -coverpkg=./... -covermode=$(COVERAGE_MODE) ./... - -clean: - rm -rf $(GIT_DIST_PATH) - -fuzz: - @go test -fuzz=FuzzParser $(PWD)/internal/revision - @go test -fuzz=FuzzDecoder $(PWD)/plumbing/format/config - @go test -fuzz=FuzzPatchDelta $(PWD)/plumbing/format/packfile - @go test -fuzz=FuzzParseSignedBytes $(PWD)/plumbing/object - @go test -fuzz=FuzzDecode $(PWD)/plumbing/object - @go test -fuzz=FuzzDecoder $(PWD)/plumbing/protocol/packp - @go test -fuzz=FuzzNewEndpoint $(PWD)/plumbing/transport diff --git a/vendor/github.com/jesseduffield/go-git/v5/README.md b/vendor/github.com/jesseduffield/go-git/v5/README.md deleted file mode 100644 index ff0c9b72bae..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/README.md +++ /dev/null @@ -1,131 +0,0 @@ -![go-git logo](https://cdn.rawgit.com/src-d/artwork/02036484/go-git/files/go-git-github-readme-header.png) -[![GoDoc](https://godoc.org/github.com/go-git/go-git/v5?status.svg)](https://pkg.go.dev/github.com/go-git/go-git/v5) [![Build Status](https://github.com/go-git/go-git/workflows/Test/badge.svg)](https://github.com/go-git/go-git/actions) [![Go Report Card](https://goreportcard.com/badge/github.com/go-git/go-git)](https://goreportcard.com/report/github.com/go-git/go-git) - -*go-git* is a highly extensible git implementation library written in **pure Go**. - -It can be used to manipulate git repositories at low level *(plumbing)* or high level *(porcelain)*, through an idiomatic Go API. It also supports several types of storage, such as in-memory filesystems, or custom implementations, thanks to the [`Storer`](https://pkg.go.dev/github.com/go-git/go-git/v5/plumbing/storer) interface. - -It's being actively developed since 2015 and is being used extensively by [Keybase](https://keybase.io/blog/encrypted-git-for-everyone), [Gitea](https://gitea.io/en-us/) or [Pulumi](https://github.com/search?q=org%3Apulumi+go-git&type=Code), and by many other libraries and tools. - -Project Status --------------- - -After the legal issues with the [`src-d`](https://github.com/src-d) organization, the lack of update for four months and the requirement to make a hard fork, the project is **now back to normality**. - -The project is currently actively maintained by individual contributors, including several of the original authors, but also backed by a new company, [gitsight](https://github.com/gitsight), where `go-git` is a critical component used at scale. - - -Comparison with git -------------------- - -*go-git* aims to be fully compatible with [git](https://github.com/git/git), all the *porcelain* operations are implemented to work exactly as *git* does. - -*git* is a humongous project with years of development by thousands of contributors, making it challenging for *go-git* to implement all the features. You can find a comparison of *go-git* vs *git* in the [compatibility documentation](COMPATIBILITY.md). - - -Installation ------------- - -The recommended way to install *go-git* is: - -```go -import "github.com/go-git/go-git/v5" // with go modules enabled (GO111MODULE=on or outside GOPATH) -import "github.com/go-git/go-git" // with go modules disabled -``` - - -Examples --------- - -> Please note that the `CheckIfError` and `Info` functions used in the examples are from the [examples package](https://github.com/go-git/go-git/blob/master/_examples/common.go#L19) just to be used in the examples. - - -### Basic example - -A basic example that mimics the standard `git clone` command - -```go -// Clone the given repository to the given directory -Info("git clone https://github.com/go-git/go-git") - -_, err := git.PlainClone("/tmp/foo", false, &git.CloneOptions{ - URL: "https://github.com/go-git/go-git", - Progress: os.Stdout, -}) - -CheckIfError(err) -``` - -Outputs: -``` -Counting objects: 4924, done. -Compressing objects: 100% (1333/1333), done. -Total 4924 (delta 530), reused 6 (delta 6), pack-reused 3533 -``` - -### In-memory example - -Cloning a repository into memory and printing the history of HEAD, just like `git log` does - - -```go -// Clones the given repository in memory, creating the remote, the local -// branches and fetching the objects, exactly as: -Info("git clone https://github.com/go-git/go-billy") - -r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{ - URL: "https://github.com/go-git/go-billy", -}) - -CheckIfError(err) - -// Gets the HEAD history from HEAD, just like this command: -Info("git log") - -// ... retrieves the branch pointed by HEAD -ref, err := r.Head() -CheckIfError(err) - - -// ... retrieves the commit history -cIter, err := r.Log(&git.LogOptions{From: ref.Hash()}) -CheckIfError(err) - -// ... just iterates over the commits, printing it -err = cIter.ForEach(func(c *object.Commit) error { - fmt.Println(c) - return nil -}) -CheckIfError(err) -``` - -Outputs: -``` -commit ded8054fd0c3994453e9c8aacaf48d118d42991e -Author: Santiago M. Mola -Date: Sat Nov 12 21:18:41 2016 +0100 - - index: ReadFrom/WriteTo returns IndexReadError/IndexWriteError. (#9) - -commit df707095626f384ce2dc1a83b30f9a21d69b9dfc -Author: Santiago M. Mola -Date: Fri Nov 11 13:23:22 2016 +0100 - - readwriter: fix bug when writing index. (#10) - - When using ReadWriter on an existing siva file, absolute offset for - index entries was not being calculated correctly. -... -``` - -You can find this [example](_examples/log/main.go) and many others in the [examples](_examples) folder. - -Contribute ----------- - -[Contributions](https://github.com/go-git/go-git/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) are more than welcome, if you are interested please take a look to -our [Contributing Guidelines](CONTRIBUTING.md). - -License -------- -Apache License Version 2.0, see [LICENSE](LICENSE) diff --git a/vendor/github.com/jesseduffield/go-git/v5/SECURITY.md b/vendor/github.com/jesseduffield/go-git/v5/SECURITY.md deleted file mode 100644 index 0d2f8d038f3..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/SECURITY.md +++ /dev/null @@ -1,38 +0,0 @@ -# go-git Security Policy - -The purpose of this security policy is to outline `go-git`'s process -for reporting, handling and disclosing security sensitive information. - -## Supported Versions - -The project follows a version support policy where only the latest minor -release is actively supported. Therefore, only issues that impact the latest -minor release will be fixed. Users are encouraged to upgrade to the latest -minor/patch release to benefit from the most up-to-date features, bug fixes, -and security enhancements.​ - -The supported versions policy applies to both the `go-git` library and its -associated repositories within the `go-git` org. - -## Reporting Security Issues - -Please report any security vulnerabilities or potential weaknesses in `go-git` -privately via go-git-security@googlegroups.com. Do not publicly disclose the -details of the vulnerability until a fix has been implemented and released. - -During the process the project maintainers will investigate the report, so please -provide detailed information, including steps to reproduce, affected versions, and any mitigations if known. - -The project maintainers will acknowledge the receipt of the report and work with -the reporter to validate and address the issue. - -Please note that `go-git` does not have any bounty programs, and therefore do -not provide financial compensation for disclosures. - -## Security Disclosure Process - -The project maintainers will make every effort to promptly address security issues. - -Once a security vulnerability is fixed, a security advisory will be published to notify users and provide appropriate mitigation measures. - -All `go-git` advisories can be found at https://github.com/go-git/go-git/security/advisories. diff --git a/vendor/github.com/jesseduffield/go-git/v5/blame.go b/vendor/github.com/jesseduffield/go-git/v5/blame.go deleted file mode 100644 index 2f1c910a8ea..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/blame.go +++ /dev/null @@ -1,587 +0,0 @@ -package git - -import ( - "bytes" - "container/heap" - "errors" - "fmt" - "io" - "strconv" - "time" - "unicode/utf8" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/object" - "github.com/jesseduffield/go-git/v5/utils/diff" - "github.com/sergi/go-diff/diffmatchpatch" -) - -// BlameResult represents the result of a Blame operation. -type BlameResult struct { - // Path is the path of the File that we're blaming. - Path string - // Rev (Revision) is the hash of the specified Commit used to generate this result. - Rev plumbing.Hash - // Lines contains every line with its authorship. - Lines []*Line -} - -// Blame returns a BlameResult with the information about the last author of -// each line from file `path` at commit `c`. -func Blame(c *object.Commit, path string) (*BlameResult, error) { - // The file to blame is identified by the input arguments: - // commit and path. commit is a Commit object obtained from a Repository. Path - // represents a path to a specific file contained in the repository. - // - // Blaming a file is done by walking the tree in reverse order trying to find where each line was last modified. - // - // When a diff is found it cannot immediately assume it came from that commit, as it may have come from 1 of its - // parents, so it will first try to resolve those diffs from its parents, if it couldn't find the change in its - // parents then it will assign the change to itself. - // - // When encountering 2 parents that have made the same change to a file it will choose the parent that was merged - // into the current branch first (this is determined by the order of the parents inside the commit). - // - // This currently works on a line by line basis, if performance becomes an issue it could be changed to work with - // hunks rather than lines. Then when encountering diff hunks it would need to split them where necessary. - - b := new(blame) - b.fRev = c - b.path = path - b.q = new(priorityQueue) - - file, err := b.fRev.File(path) - if err != nil { - return nil, err - } - finalLines, err := file.Lines() - if err != nil { - return nil, err - } - finalLength := len(finalLines) - - needsMap := make([]lineMap, finalLength) - for i := range needsMap { - needsMap[i] = lineMap{i, i, nil, -1} - } - contents, err := file.Contents() - if err != nil { - return nil, err - } - b.q.Push(&queueItem{ - nil, - nil, - c, - path, - contents, - needsMap, - 0, - false, - 0, - }) - items := make([]*queueItem, 0) - for { - items = items[:0] - for { - if b.q.Len() == 0 { - return nil, errors.New("invalid state: no items left on the blame queue") - } - item := b.q.Pop() - items = append(items, item) - next := b.q.Peek() - if next == nil || next.Hash != item.Commit.Hash { - break - } - } - finished, err := b.addBlames(items) - if err != nil { - return nil, err - } - if finished { - break - } - } - - b.lineToCommit = make([]*object.Commit, finalLength) - for i := range needsMap { - b.lineToCommit[i] = needsMap[i].Commit - } - - lines, err := newLines(finalLines, b.lineToCommit) - if err != nil { - return nil, err - } - - return &BlameResult{ - Path: path, - Rev: c.Hash, - Lines: lines, - }, nil -} - -// Line values represent the contents and author of a line in BlamedResult values. -type Line struct { - // Author is the email address of the last author that modified the line. - Author string - // AuthorName is the name of the last author that modified the line. - AuthorName string - // Text is the original text of the line. - Text string - // Date is when the original text of the line was introduced - Date time.Time - // Hash is the commit hash that introduced the original line - Hash plumbing.Hash -} - -func newLine(author, authorName, text string, date time.Time, hash plumbing.Hash) *Line { - return &Line{ - Author: author, - AuthorName: authorName, - Text: text, - Hash: hash, - Date: date, - } -} - -func newLines(contents []string, commits []*object.Commit) ([]*Line, error) { - result := make([]*Line, 0, len(contents)) - for i := range contents { - result = append(result, newLine( - commits[i].Author.Email, commits[i].Author.Name, contents[i], - commits[i].Author.When, commits[i].Hash, - )) - } - - return result, nil -} - -// this struct is internally used by the blame function to hold its -// inputs, outputs and state. -type blame struct { - // the path of the file to blame - path string - // the commit of the final revision of the file to blame - fRev *object.Commit - // resolved lines - lineToCommit []*object.Commit - // queue of commits that need resolving - q *priorityQueue -} - -type lineMap struct { - Orig, Cur int - Commit *object.Commit - FromParentNo int -} - -func (b *blame) addBlames(curItems []*queueItem) (bool, error) { - curItem := curItems[0] - - // Simple optimisation to merge paths, there is potential to go a bit further here and check for any duplicates - // not only if they are all the same. - if len(curItems) == 1 { - curItems = nil - } else if curItem.IdenticalToChild { - allSame := true - lenCurItems := len(curItems) - lowestParentNo := curItem.ParentNo - for i := 1; i < lenCurItems; i++ { - if !curItems[i].IdenticalToChild || curItem.Child != curItems[i].Child { - allSame = false - break - } - lowestParentNo = min(lowestParentNo, curItems[i].ParentNo) - } - if allSame { - curItem.Child.numParentsNeedResolving = curItem.Child.numParentsNeedResolving - lenCurItems + 1 - curItems = nil // free the memory - curItem.ParentNo = lowestParentNo - - // Now check if we can remove the parent completely - for curItem.Child.IdenticalToChild && curItem.Child.MergedChildren == nil && curItem.Child.numParentsNeedResolving == 1 { - oldChild := curItem.Child - curItem.Child = oldChild.Child - curItem.ParentNo = oldChild.ParentNo - } - } - } - - // if we have more than 1 item for this commit, create a single needsMap - if len(curItems) > 1 { - curItem.MergedChildren = make([]childToNeedsMap, len(curItems)) - for i, c := range curItems { - curItem.MergedChildren[i] = childToNeedsMap{c.Child, c.NeedsMap, c.IdenticalToChild, c.ParentNo} - } - newNeedsMap := make([]lineMap, 0, len(curItem.NeedsMap)) - newNeedsMap = append(newNeedsMap, curItems[0].NeedsMap...) - - for i := 1; i < len(curItems); i++ { - cur := curItems[i].NeedsMap - n := 0 // position in newNeedsMap - c := 0 // position in current list - for c < len(cur) { - if n == len(newNeedsMap) { - newNeedsMap = append(newNeedsMap, cur[c:]...) - break - } else if newNeedsMap[n].Cur == cur[c].Cur { - n++ - c++ - } else if newNeedsMap[n].Cur < cur[c].Cur { - n++ - } else { - newNeedsMap = append(newNeedsMap, cur[c]) - newPos := len(newNeedsMap) - 1 - for newPos > n { - newNeedsMap[newPos-1], newNeedsMap[newPos] = newNeedsMap[newPos], newNeedsMap[newPos-1] - newPos-- - } - } - } - } - curItem.NeedsMap = newNeedsMap - curItem.IdenticalToChild = false - curItem.Child = nil - curItems = nil // free the memory - } - - parents, err := parentsContainingPath(curItem.path, curItem.Commit) - if err != nil { - return false, err - } - - anyPushed := false - for parnetNo, prev := range parents { - currentHash, err := blobHash(curItem.path, curItem.Commit) - if err != nil { - return false, err - } - prevHash, err := blobHash(prev.Path, prev.Commit) - if err != nil { - return false, err - } - if currentHash == prevHash { - if len(parents) == 1 && curItem.MergedChildren == nil && curItem.IdenticalToChild { - // commit that has 1 parent and 1 child and is the same as both, bypass it completely - b.q.Push(&queueItem{ - Child: curItem.Child, - Commit: prev.Commit, - path: prev.Path, - Contents: curItem.Contents, - NeedsMap: curItem.NeedsMap, // reuse the NeedsMap as we are throwing away this item - IdenticalToChild: true, - ParentNo: curItem.ParentNo, - }) - } else { - b.q.Push(&queueItem{ - Child: curItem, - Commit: prev.Commit, - path: prev.Path, - Contents: curItem.Contents, - NeedsMap: append([]lineMap(nil), curItem.NeedsMap...), // create new slice and copy - IdenticalToChild: true, - ParentNo: parnetNo, - }) - curItem.numParentsNeedResolving++ - } - anyPushed = true - continue - } - - // get the contents of the file - file, err := prev.Commit.File(prev.Path) - if err != nil { - return false, err - } - prevContents, err := file.Contents() - if err != nil { - return false, err - } - - hunks := diff.Do(prevContents, curItem.Contents) - prevl := -1 - curl := -1 - need := 0 - getFromParent := make([]lineMap, 0) - out: - for h := range hunks { - hLines := countLines(hunks[h].Text) - for hl := 0; hl < hLines; hl++ { - switch hunks[h].Type { - case diffmatchpatch.DiffEqual: - prevl++ - curl++ - if curl == curItem.NeedsMap[need].Cur { - // add to needs - getFromParent = append(getFromParent, lineMap{curl, prevl, nil, -1}) - // move to next need - need++ - if need >= len(curItem.NeedsMap) { - break out - } - } - case diffmatchpatch.DiffInsert: - curl++ - if curl == curItem.NeedsMap[need].Cur { - // the line we want is added, it may have been added here (or by another parent), skip it for now - need++ - if need >= len(curItem.NeedsMap) { - break out - } - } - case diffmatchpatch.DiffDelete: - prevl += hLines - continue out - default: - return false, errors.New("invalid state: invalid hunk Type") - } - } - } - - if len(getFromParent) > 0 { - b.q.Push(&queueItem{ - curItem, - nil, - prev.Commit, - prev.Path, - prevContents, - getFromParent, - 0, - false, - parnetNo, - }) - curItem.numParentsNeedResolving++ - anyPushed = true - } - } - - curItem.Contents = "" // no longer need, free the memory - - if !anyPushed { - return finishNeeds(curItem) - } - - return false, nil -} - -func finishNeeds(curItem *queueItem) (bool, error) { - // any needs left in the needsMap must have come from this revision - for i := range curItem.NeedsMap { - if curItem.NeedsMap[i].Commit == nil { - curItem.NeedsMap[i].Commit = curItem.Commit - curItem.NeedsMap[i].FromParentNo = -1 - } - } - - if curItem.Child == nil && curItem.MergedChildren == nil { - return true, nil - } - - if curItem.MergedChildren == nil { - return applyNeeds(curItem.Child, curItem.NeedsMap, curItem.IdenticalToChild, curItem.ParentNo) - } - - for _, ctn := range curItem.MergedChildren { - m := 0 // position in merged needs map - p := 0 // position in parent needs map - for p < len(ctn.NeedsMap) { - if ctn.NeedsMap[p].Cur == curItem.NeedsMap[m].Cur { - ctn.NeedsMap[p].Commit = curItem.NeedsMap[m].Commit - m++ - p++ - } else if ctn.NeedsMap[p].Cur < curItem.NeedsMap[m].Cur { - p++ - } else { - m++ - } - } - finished, err := applyNeeds(ctn.Child, ctn.NeedsMap, ctn.IdenticalToChild, ctn.ParentNo) - if finished || err != nil { - return finished, err - } - } - - return false, nil -} - -func applyNeeds(child *queueItem, needsMap []lineMap, identicalToChild bool, parentNo int) (bool, error) { - if identicalToChild { - for i := range child.NeedsMap { - l := &child.NeedsMap[i] - if l.Cur != needsMap[i].Cur || l.Orig != needsMap[i].Orig { - return false, errors.New("needsMap isn't the same? Why not??") - } - if l.Commit == nil || parentNo < l.FromParentNo { - l.Commit = needsMap[i].Commit - l.FromParentNo = parentNo - } - } - } else { - i := 0 - out: - for j := range child.NeedsMap { - l := &child.NeedsMap[j] - for needsMap[i].Orig < l.Cur { - i++ - if i == len(needsMap) { - break out - } - } - if l.Cur == needsMap[i].Orig { - if l.Commit == nil || parentNo < l.FromParentNo { - l.Commit = needsMap[i].Commit - l.FromParentNo = parentNo - } - } - } - } - child.numParentsNeedResolving-- - if child.numParentsNeedResolving == 0 { - finished, err := finishNeeds(child) - if finished || err != nil { - return finished, err - } - } - - return false, nil -} - -// String prints the results of a Blame using git-blame's style. -func (b BlameResult) String() string { - var buf bytes.Buffer - - // max line number length - mlnl := len(strconv.Itoa(len(b.Lines))) - // max author length - mal := b.maxAuthorLength() - format := fmt.Sprintf("%%s (%%-%ds %%s %%%dd) %%s\n", mal, mlnl) - - for ln := range b.Lines { - _, _ = fmt.Fprintf(&buf, format, b.Lines[ln].Hash.String()[:8], - b.Lines[ln].AuthorName, b.Lines[ln].Date.Format("2006-01-02 15:04:05 -0700"), ln+1, b.Lines[ln].Text) - } - return buf.String() -} - -// utility function to calculate the number of runes needed -// to print the longest author name in the blame of a file. -func (b BlameResult) maxAuthorLength() int { - m := 0 - for ln := range b.Lines { - m = max(m, utf8.RuneCountInString(b.Lines[ln].AuthorName)) - } - return m -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -type childToNeedsMap struct { - Child *queueItem - NeedsMap []lineMap - IdenticalToChild bool - ParentNo int -} - -type queueItem struct { - Child *queueItem - MergedChildren []childToNeedsMap - Commit *object.Commit - path string - Contents string - NeedsMap []lineMap - numParentsNeedResolving int - IdenticalToChild bool - ParentNo int -} - -type priorityQueueImp []*queueItem - -func (pq *priorityQueueImp) Len() int { return len(*pq) } -func (pq *priorityQueueImp) Less(i, j int) bool { - return !(*pq)[i].Commit.Less((*pq)[j].Commit) -} -func (pq *priorityQueueImp) Swap(i, j int) { (*pq)[i], (*pq)[j] = (*pq)[j], (*pq)[i] } -func (pq *priorityQueueImp) Push(x any) { *pq = append(*pq, x.(*queueItem)) } -func (pq *priorityQueueImp) Pop() any { - n := len(*pq) - ret := (*pq)[n-1] - (*pq)[n-1] = nil // ovoid memory leak - *pq = (*pq)[0 : n-1] - - return ret -} -func (pq *priorityQueueImp) Peek() *object.Commit { - if len(*pq) == 0 { - return nil - } - return (*pq)[0].Commit -} - -type priorityQueue priorityQueueImp - -func (pq *priorityQueue) Init() { heap.Init((*priorityQueueImp)(pq)) } -func (pq *priorityQueue) Len() int { return (*priorityQueueImp)(pq).Len() } -func (pq *priorityQueue) Push(c *queueItem) { - heap.Push((*priorityQueueImp)(pq), c) -} -func (pq *priorityQueue) Pop() *queueItem { - return heap.Pop((*priorityQueueImp)(pq)).(*queueItem) -} -func (pq *priorityQueue) Peek() *object.Commit { return (*priorityQueueImp)(pq).Peek() } - -type parentCommit struct { - Commit *object.Commit - Path string -} - -func parentsContainingPath(path string, c *object.Commit) ([]parentCommit, error) { - // TODO: benchmark this method making git.object.Commit.parent public instead of using - // an iterator - var result []parentCommit - iter := c.Parents() - for { - parent, err := iter.Next() - if err == io.EOF { - return result, nil - } - if err != nil { - return nil, err - } - if _, err := parent.File(path); err == nil { - result = append(result, parentCommit{parent, path}) - } else { - // look for renames - patch, err := parent.Patch(c) - if err != nil { - return nil, err - } else if patch != nil { - for _, fp := range patch.FilePatches() { - from, to := fp.Files() - if from != nil && to != nil && to.Path() == path { - result = append(result, parentCommit{parent, from.Path()}) - break - } - } - } - } - } -} - -func blobHash(path string, commit *object.Commit) (plumbing.Hash, error) { - file, err := commit.File(path) - if err != nil { - return plumbing.ZeroHash, err - } - return file.Hash, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/common.go b/vendor/github.com/jesseduffield/go-git/v5/common.go deleted file mode 100644 index 6174339a815..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/common.go +++ /dev/null @@ -1,20 +0,0 @@ -package git - -import "strings" - -// countLines returns the number of lines in a string à la git, this is -// The newline character is assumed to be '\n'. The empty string -// contains 0 lines. If the last line of the string doesn't end with a -// newline, it will still be considered a line. -func countLines(s string) int { - if s == "" { - return 0 - } - - nEOL := strings.Count(s, "\n") - if strings.HasSuffix(s, "\n") { - return nEOL - } - - return nEOL + 1 -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/config/branch.go b/vendor/github.com/jesseduffield/go-git/v5/config/branch.go deleted file mode 100644 index f7095725003..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/config/branch.go +++ /dev/null @@ -1,123 +0,0 @@ -package config - -import ( - "errors" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing" - format "github.com/jesseduffield/go-git/v5/plumbing/format/config" -) - -var ( - errBranchEmptyName = errors.New("branch config: empty name") - errBranchInvalidMerge = errors.New("branch config: invalid merge") - errBranchInvalidRebase = errors.New("branch config: rebase must be one of 'true' or 'interactive'") -) - -// Branch contains information on the -// local branches and which remote to track -type Branch struct { - // Name of branch - Name string - // Remote name of remote to track - Remote string - // Merge is the local refspec for the branch - Merge plumbing.ReferenceName - // Rebase instead of merge when pulling. Valid values are - // "true" and "interactive". "false" is undocumented and - // typically represented by the non-existence of this field - Rebase string - // Description explains what the branch is for. - // Multi-line explanations may be used. - // - // Original git command to edit: - // git branch --edit-description - Description string - - raw *format.Subsection -} - -// Validate validates fields of branch -func (b *Branch) Validate() error { - if b.Name == "" { - return errBranchEmptyName - } - - if b.Merge != "" && !b.Merge.IsBranch() { - return errBranchInvalidMerge - } - - if b.Rebase != "" && - b.Rebase != "true" && - b.Rebase != "interactive" && - b.Rebase != "false" { - return errBranchInvalidRebase - } - - return plumbing.NewBranchReferenceName(b.Name).Validate() -} - -func (b *Branch) marshal() *format.Subsection { - if b.raw == nil { - b.raw = &format.Subsection{} - } - - b.raw.Name = b.Name - - if b.Remote == "" { - b.raw.RemoveOption(remoteSection) - } else { - b.raw.SetOption(remoteSection, b.Remote) - } - - if b.Merge == "" { - b.raw.RemoveOption(mergeKey) - } else { - b.raw.SetOption(mergeKey, string(b.Merge)) - } - - if b.Rebase == "" { - b.raw.RemoveOption(rebaseKey) - } else { - b.raw.SetOption(rebaseKey, b.Rebase) - } - - if b.Description == "" { - b.raw.RemoveOption(descriptionKey) - } else { - desc := quoteDescription(b.Description) - b.raw.SetOption(descriptionKey, desc) - } - - return b.raw -} - -// hack to trigger conditional quoting in the -// plumbing/format/config/Encoder.encodeOptions -// -// Current Encoder implementation uses Go %q format if value contains a backslash character, -// which is not consistent with reference git implementation. -// git just replaces newline characters with \n, while Encoder prints them directly. -// Until value quoting fix, we should escape description value by replacing newline characters with \n. -func quoteDescription(desc string) string { - return strings.ReplaceAll(desc, "\n", `\n`) -} - -func (b *Branch) unmarshal(s *format.Subsection) error { - b.raw = s - - b.Name = b.raw.Name - b.Remote = b.raw.Options.Get(remoteSection) - b.Merge = plumbing.ReferenceName(b.raw.Options.Get(mergeKey)) - b.Rebase = b.raw.Options.Get(rebaseKey) - b.Description = unquoteDescription(b.raw.Options.Get(descriptionKey)) - - return nil -} - -// hack to enable conditional quoting in the -// plumbing/format/config/Encoder.encodeOptions -// goto quoteDescription for details. -func unquoteDescription(desc string) string { - return strings.ReplaceAll(desc, `\n`, "\n") -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/config/config.go b/vendor/github.com/jesseduffield/go-git/v5/config/config.go deleted file mode 100644 index 083c0813e03..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/config/config.go +++ /dev/null @@ -1,698 +0,0 @@ -// Package config contains the abstraction of multiple config files -package config - -import ( - "bytes" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "sort" - "strconv" - - "github.com/go-git/go-billy/v5/osfs" - "github.com/jesseduffield/go-git/v5/internal/url" - "github.com/jesseduffield/go-git/v5/plumbing" - format "github.com/jesseduffield/go-git/v5/plumbing/format/config" -) - -const ( - // DefaultFetchRefSpec is the default refspec used for fetch. - DefaultFetchRefSpec = "+refs/heads/*:refs/remotes/%s/*" - // DefaultPushRefSpec is the default refspec used for push. - DefaultPushRefSpec = "refs/heads/*:refs/heads/*" -) - -// ConfigStorer generic storage of Config object -type ConfigStorer interface { - Config() (*Config, error) - SetConfig(*Config) error -} - -var ( - ErrInvalid = errors.New("config invalid key in remote or branch") - ErrRemoteConfigNotFound = errors.New("remote config not found") - ErrRemoteConfigEmptyURL = errors.New("remote config: empty URL") - ErrRemoteConfigEmptyName = errors.New("remote config: empty name") -) - -// Scope defines the scope of a config file, such as local, global or system. -type Scope int - -// Available ConfigScope's -const ( - LocalScope Scope = iota - GlobalScope - SystemScope -) - -// Config contains the repository configuration -// https://www.kernel.org/pub/software/scm/git/docs/git-config.html#FILES -type Config struct { - Core struct { - // IsBare if true this repository is assumed to be bare and has no - // working directory associated with it. - IsBare bool - // Worktree is the path to the root of the working tree. - Worktree string - // CommentChar is the character indicating the start of a - // comment for commands like commit and tag - CommentChar string - // RepositoryFormatVersion identifies the repository format and layout version. - RepositoryFormatVersion format.RepositoryFormatVersion - } - - User struct { - // Name is the personal name of the author and the committer of a commit. - Name string - // Email is the email of the author and the committer of a commit. - Email string - } - - Author struct { - // Name is the personal name of the author of a commit. - Name string - // Email is the email of the author of a commit. - Email string - } - - Committer struct { - // Name is the personal name of the committer of a commit. - Name string - // Email is the email of the committer of a commit. - Email string - } - - Pack struct { - // Window controls the size of the sliding window for delta - // compression. The default is 10. A value of 0 turns off - // delta compression entirely. - Window uint - } - - Init struct { - // DefaultBranch Allows overriding the default branch name - // e.g. when initializing a new repository or when cloning - // an empty repository. - DefaultBranch string - } - - Extensions struct { - // ObjectFormat specifies the hash algorithm to use. The - // acceptable values are sha1 and sha256. If not specified, - // sha1 is assumed. It is an error to specify this key unless - // core.repositoryFormatVersion is 1. - // - // This setting must not be changed after repository initialization - // (e.g. clone or init). - ObjectFormat format.ObjectFormat - } - - // Remotes list of repository remotes, the key of the map is the name - // of the remote, should equal to RemoteConfig.Name. - Remotes map[string]*RemoteConfig - // Submodules list of repository submodules, the key of the map is the name - // of the submodule, should equal to Submodule.Name. - Submodules map[string]*Submodule - // Branches list of branches, the key is the branch name and should - // equal Branch.Name - Branches map[string]*Branch - // URLs list of url rewrite rules, if repo url starts with URL.InsteadOf value, it will be replaced with the - // key instead. - URLs map[string]*URL - // Raw contains the raw information of a config file. The main goal is - // preserve the parsed information from the original format, to avoid - // dropping unsupported fields. - Raw *format.Config -} - -// NewConfig returns a new empty Config. -func NewConfig() *Config { - config := &Config{ - Remotes: make(map[string]*RemoteConfig), - Submodules: make(map[string]*Submodule), - Branches: make(map[string]*Branch), - URLs: make(map[string]*URL), - Raw: format.New(), - } - - config.Pack.Window = DefaultPackWindow - - return config -} - -// ReadConfig reads a config file from a io.Reader. -func ReadConfig(r io.Reader) (*Config, error) { - b, err := io.ReadAll(r) - if err != nil { - return nil, err - } - - cfg := NewConfig() - if err = cfg.Unmarshal(b); err != nil { - return nil, err - } - - return cfg, nil -} - -// LoadConfig loads a config file from a given scope. The returned Config, -// contains exclusively information from the given scope. If it couldn't find a -// config file to the given scope, an empty one is returned. -func LoadConfig(scope Scope) (*Config, error) { - if scope == LocalScope { - return nil, fmt.Errorf("LocalScope should be read from the a ConfigStorer") - } - - files, err := Paths(scope) - if err != nil { - return nil, err - } - - for _, file := range files { - f, err := osfs.Default.Open(file) - if err != nil { - if os.IsNotExist(err) { - continue - } - - return nil, err - } - - defer f.Close() - return ReadConfig(f) - } - - return NewConfig(), nil -} - -// Paths returns the config file location for a given scope. -func Paths(scope Scope) ([]string, error) { - var files []string - switch scope { - case GlobalScope: - xdg := os.Getenv("XDG_CONFIG_HOME") - if xdg != "" { - files = append(files, filepath.Join(xdg, "git/config")) - } - - home, err := os.UserHomeDir() - if err != nil { - return nil, err - } - - files = append(files, - filepath.Join(home, ".gitconfig"), - filepath.Join(home, ".config/git/config"), - ) - case SystemScope: - files = append(files, "/etc/gitconfig") - } - - return files, nil -} - -// Validate validates the fields and sets the default values. -func (c *Config) Validate() error { - for name, r := range c.Remotes { - if r.Name != name { - return ErrInvalid - } - - if err := r.Validate(); err != nil { - return err - } - } - - for name, b := range c.Branches { - if b.Name != name { - return ErrInvalid - } - - if err := b.Validate(); err != nil { - return err - } - } - - return nil -} - -const ( - remoteSection = "remote" - submoduleSection = "submodule" - branchSection = "branch" - coreSection = "core" - packSection = "pack" - userSection = "user" - authorSection = "author" - committerSection = "committer" - initSection = "init" - urlSection = "url" - extensionsSection = "extensions" - fetchKey = "fetch" - urlKey = "url" - pushurlKey = "pushurl" - bareKey = "bare" - worktreeKey = "worktree" - commentCharKey = "commentChar" - windowKey = "window" - mergeKey = "merge" - rebaseKey = "rebase" - nameKey = "name" - emailKey = "email" - descriptionKey = "description" - defaultBranchKey = "defaultBranch" - repositoryFormatVersionKey = "repositoryformatversion" - objectFormat = "objectformat" - mirrorKey = "mirror" - - // DefaultPackWindow holds the number of previous objects used to - // generate deltas. The value 10 is the same used by git command. - DefaultPackWindow = uint(10) -) - -// Unmarshal parses a git-config file and stores it. -func (c *Config) Unmarshal(b []byte) error { - r := bytes.NewBuffer(b) - d := format.NewDecoder(r) - - c.Raw = format.New() - if err := d.Decode(c.Raw); err != nil { - return err - } - - c.unmarshalCore() - c.unmarshalUser() - c.unmarshalInit() - if err := c.unmarshalPack(); err != nil { - return err - } - unmarshalSubmodules(c.Raw, c.Submodules) - - if err := c.unmarshalBranches(); err != nil { - return err - } - - if err := c.unmarshalURLs(); err != nil { - return err - } - - return c.unmarshalRemotes() -} - -func (c *Config) unmarshalCore() { - s := c.Raw.Section(coreSection) - if s.Options.Get(bareKey) == "true" { - c.Core.IsBare = true - } - - c.Core.Worktree = s.Options.Get(worktreeKey) - c.Core.CommentChar = s.Options.Get(commentCharKey) -} - -func (c *Config) unmarshalUser() { - s := c.Raw.Section(userSection) - c.User.Name = s.Options.Get(nameKey) - c.User.Email = s.Options.Get(emailKey) - - s = c.Raw.Section(authorSection) - c.Author.Name = s.Options.Get(nameKey) - c.Author.Email = s.Options.Get(emailKey) - - s = c.Raw.Section(committerSection) - c.Committer.Name = s.Options.Get(nameKey) - c.Committer.Email = s.Options.Get(emailKey) -} - -func (c *Config) unmarshalPack() error { - s := c.Raw.Section(packSection) - window := s.Options.Get(windowKey) - if window == "" { - c.Pack.Window = DefaultPackWindow - } else { - winUint, err := strconv.ParseUint(window, 10, 32) - if err != nil { - return err - } - c.Pack.Window = uint(winUint) - } - return nil -} - -func (c *Config) unmarshalRemotes() error { - s := c.Raw.Section(remoteSection) - for _, sub := range s.Subsections { - r := &RemoteConfig{} - if err := r.unmarshal(sub); err != nil { - return err - } - - c.Remotes[r.Name] = r - } - - // Apply insteadOf url rules - for _, r := range c.Remotes { - r.applyURLRules(c.URLs) - } - - return nil -} - -func (c *Config) unmarshalURLs() error { - s := c.Raw.Section(urlSection) - for _, sub := range s.Subsections { - r := &URL{} - if err := r.unmarshal(sub); err != nil { - return err - } - - c.URLs[r.Name] = r - } - - return nil -} - -func unmarshalSubmodules(fc *format.Config, submodules map[string]*Submodule) { - s := fc.Section(submoduleSection) - for _, sub := range s.Subsections { - m := &Submodule{} - m.unmarshal(sub) - - if m.Validate() == ErrModuleBadPath { - continue - } - - submodules[m.Name] = m - } -} - -func (c *Config) unmarshalBranches() error { - bs := c.Raw.Section(branchSection) - for _, sub := range bs.Subsections { - b := &Branch{} - - if err := b.unmarshal(sub); err != nil { - return err - } - - c.Branches[b.Name] = b - } - return nil -} - -func (c *Config) unmarshalInit() { - s := c.Raw.Section(initSection) - c.Init.DefaultBranch = s.Options.Get(defaultBranchKey) -} - -// Marshal returns Config encoded as a git-config file. -func (c *Config) Marshal() ([]byte, error) { - c.marshalCore() - c.marshalExtensions() - c.marshalUser() - c.marshalPack() - c.marshalRemotes() - c.marshalSubmodules() - c.marshalBranches() - c.marshalURLs() - c.marshalInit() - - buf := bytes.NewBuffer(nil) - if err := format.NewEncoder(buf).Encode(c.Raw); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func (c *Config) marshalCore() { - s := c.Raw.Section(coreSection) - s.SetOption(bareKey, fmt.Sprintf("%t", c.Core.IsBare)) - if string(c.Core.RepositoryFormatVersion) != "" { - s.SetOption(repositoryFormatVersionKey, string(c.Core.RepositoryFormatVersion)) - } - - if c.Core.Worktree != "" { - s.SetOption(worktreeKey, c.Core.Worktree) - } -} - -func (c *Config) marshalExtensions() { - // Extensions are only supported on Version 1, therefore - // ignore them otherwise. - if c.Core.RepositoryFormatVersion == format.Version_1 { - s := c.Raw.Section(extensionsSection) - s.SetOption(objectFormat, string(c.Extensions.ObjectFormat)) - } -} - -func (c *Config) marshalUser() { - s := c.Raw.Section(userSection) - if c.User.Name != "" { - s.SetOption(nameKey, c.User.Name) - } - - if c.User.Email != "" { - s.SetOption(emailKey, c.User.Email) - } - - s = c.Raw.Section(authorSection) - if c.Author.Name != "" { - s.SetOption(nameKey, c.Author.Name) - } - - if c.Author.Email != "" { - s.SetOption(emailKey, c.Author.Email) - } - - s = c.Raw.Section(committerSection) - if c.Committer.Name != "" { - s.SetOption(nameKey, c.Committer.Name) - } - - if c.Committer.Email != "" { - s.SetOption(emailKey, c.Committer.Email) - } -} - -func (c *Config) marshalPack() { - s := c.Raw.Section(packSection) - if c.Pack.Window != DefaultPackWindow { - s.SetOption(windowKey, fmt.Sprintf("%d", c.Pack.Window)) - } -} - -func (c *Config) marshalRemotes() { - s := c.Raw.Section(remoteSection) - newSubsections := make(format.Subsections, 0, len(c.Remotes)) - added := make(map[string]bool) - for _, subsection := range s.Subsections { - if remote, ok := c.Remotes[subsection.Name]; ok { - newSubsections = append(newSubsections, remote.marshal()) - added[subsection.Name] = true - } - } - - remoteNames := make([]string, 0, len(c.Remotes)) - for name := range c.Remotes { - remoteNames = append(remoteNames, name) - } - - sort.Strings(remoteNames) - - for _, name := range remoteNames { - if !added[name] { - newSubsections = append(newSubsections, c.Remotes[name].marshal()) - } - } - - s.Subsections = newSubsections -} - -func (c *Config) marshalSubmodules() { - s := c.Raw.Section(submoduleSection) - s.Subsections = make(format.Subsections, len(c.Submodules)) - - var i int - for _, r := range c.Submodules { - section := r.marshal() - // the submodule section at config is a subset of the .gitmodule file - // we should remove the non-valid options for the config file. - section.RemoveOption(pathKey) - s.Subsections[i] = section - i++ - } -} - -func (c *Config) marshalBranches() { - s := c.Raw.Section(branchSection) - newSubsections := make(format.Subsections, 0, len(c.Branches)) - added := make(map[string]bool) - for _, subsection := range s.Subsections { - if branch, ok := c.Branches[subsection.Name]; ok { - newSubsections = append(newSubsections, branch.marshal()) - added[subsection.Name] = true - } - } - - branchNames := make([]string, 0, len(c.Branches)) - for name := range c.Branches { - branchNames = append(branchNames, name) - } - - sort.Strings(branchNames) - - for _, name := range branchNames { - if !added[name] { - newSubsections = append(newSubsections, c.Branches[name].marshal()) - } - } - - s.Subsections = newSubsections -} - -func (c *Config) marshalURLs() { - s := c.Raw.Section(urlSection) - s.Subsections = make(format.Subsections, len(c.URLs)) - - var i int - for _, r := range c.URLs { - section := r.marshal() - // the submodule section at config is a subset of the .gitmodule file - // we should remove the non-valid options for the config file. - s.Subsections[i] = section - i++ - } -} - -func (c *Config) marshalInit() { - s := c.Raw.Section(initSection) - if c.Init.DefaultBranch != "" { - s.SetOption(defaultBranchKey, c.Init.DefaultBranch) - } -} - -// RemoteConfig contains the configuration for a given remote repository. -type RemoteConfig struct { - // Name of the remote - Name string - // URLs the URLs of a remote repository. It must be non-empty. Fetch will - // always use the first URL, while push will use all of them. - URLs []string - // Mirror indicates that the repository is a mirror of remote. - Mirror bool - - // insteadOfRulesApplied have urls been modified - insteadOfRulesApplied bool - // originalURLs are the urls before applying insteadOf rules - originalURLs []string - - // Fetch the default set of "refspec" for fetch operation - Fetch []RefSpec - - // raw representation of the subsection, filled by marshal or unmarshal are - // called - raw *format.Subsection -} - -// Validate validates the fields and sets the default values. -func (c *RemoteConfig) Validate() error { - if c.Name == "" { - return ErrRemoteConfigEmptyName - } - - if len(c.URLs) == 0 { - return ErrRemoteConfigEmptyURL - } - - for _, r := range c.Fetch { - if err := r.Validate(); err != nil { - return err - } - } - - if len(c.Fetch) == 0 { - c.Fetch = []RefSpec{RefSpec(fmt.Sprintf(DefaultFetchRefSpec, c.Name))} - } - - return plumbing.NewRemoteHEADReferenceName(c.Name).Validate() -} - -func (c *RemoteConfig) unmarshal(s *format.Subsection) error { - c.raw = s - - fetch := []RefSpec{} - for _, f := range c.raw.Options.GetAll(fetchKey) { - rs := RefSpec(f) - if err := rs.Validate(); err != nil { - return err - } - - fetch = append(fetch, rs) - } - - c.Name = c.raw.Name - c.URLs = append([]string(nil), c.raw.Options.GetAll(urlKey)...) - c.URLs = append(c.URLs, c.raw.Options.GetAll(pushurlKey)...) - c.Fetch = fetch - c.Mirror = c.raw.Options.Get(mirrorKey) == "true" - - return nil -} - -func (c *RemoteConfig) marshal() *format.Subsection { - if c.raw == nil { - c.raw = &format.Subsection{} - } - - c.raw.Name = c.Name - if len(c.URLs) == 0 { - c.raw.RemoveOption(urlKey) - } else { - urls := c.URLs - if c.insteadOfRulesApplied { - urls = c.originalURLs - } - - c.raw.SetOption(urlKey, urls...) - } - - if len(c.Fetch) == 0 { - c.raw.RemoveOption(fetchKey) - } else { - var values []string - for _, rs := range c.Fetch { - values = append(values, rs.String()) - } - - c.raw.SetOption(fetchKey, values...) - } - - if c.Mirror { - c.raw.SetOption(mirrorKey, strconv.FormatBool(c.Mirror)) - } - - return c.raw -} - -func (c *RemoteConfig) IsFirstURLLocal() bool { - return url.IsLocalEndpoint(c.URLs[0]) -} - -func (c *RemoteConfig) applyURLRules(urlRules map[string]*URL) { - // save original urls - originalURLs := make([]string, len(c.URLs)) - copy(originalURLs, c.URLs) - - for i, url := range c.URLs { - if matchingURLRule := findLongestInsteadOfMatch(url, urlRules); matchingURLRule != nil { - c.URLs[i] = matchingURLRule.ApplyInsteadOf(c.URLs[i]) - c.insteadOfRulesApplied = true - } - } - - if c.insteadOfRulesApplied { - c.originalURLs = originalURLs - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/config/modules.go b/vendor/github.com/jesseduffield/go-git/v5/config/modules.go deleted file mode 100644 index 898e2d9ec1b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/config/modules.go +++ /dev/null @@ -1,139 +0,0 @@ -package config - -import ( - "bytes" - "errors" - "regexp" - - format "github.com/jesseduffield/go-git/v5/plumbing/format/config" -) - -var ( - ErrModuleEmptyURL = errors.New("module config: empty URL") - ErrModuleEmptyPath = errors.New("module config: empty path") - ErrModuleBadPath = errors.New("submodule has an invalid path") -) - -var ( - // Matches module paths with dotdot ".." components. - dotdotPath = regexp.MustCompile(`(^|[/\\])\.\.([/\\]|$)`) -) - -// Modules defines the submodules properties, represents a .gitmodules file -// https://www.kernel.org/pub/software/scm/git/docs/gitmodules.html -type Modules struct { - // Submodules is a map of submodules being the key the name of the submodule. - Submodules map[string]*Submodule - - raw *format.Config -} - -// NewModules returns a new empty Modules -func NewModules() *Modules { - return &Modules{ - Submodules: make(map[string]*Submodule), - raw: format.New(), - } -} - -const ( - pathKey = "path" - branchKey = "branch" -) - -// Unmarshal parses a git-config file and stores it. -func (m *Modules) Unmarshal(b []byte) error { - r := bytes.NewBuffer(b) - d := format.NewDecoder(r) - - m.raw = format.New() - if err := d.Decode(m.raw); err != nil { - return err - } - - unmarshalSubmodules(m.raw, m.Submodules) - return nil -} - -// Marshal returns Modules encoded as a git-config file. -func (m *Modules) Marshal() ([]byte, error) { - s := m.raw.Section(submoduleSection) - s.Subsections = make(format.Subsections, len(m.Submodules)) - - var i int - for _, r := range m.Submodules { - s.Subsections[i] = r.marshal() - i++ - } - - buf := bytes.NewBuffer(nil) - if err := format.NewEncoder(buf).Encode(m.raw); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -// Submodule defines a submodule. -type Submodule struct { - // Name module name - Name string - // Path defines the path, relative to the top-level directory of the Git - // working tree. - Path string - // URL defines a URL from which the submodule repository can be cloned. - URL string - // Branch is a remote branch name for tracking updates in the upstream - // submodule. Optional value. - Branch string - - // raw representation of the subsection, filled by marshal or unmarshal are - // called. - raw *format.Subsection -} - -// Validate validates the fields and sets the default values. -func (m *Submodule) Validate() error { - if m.Path == "" { - return ErrModuleEmptyPath - } - - if m.URL == "" { - return ErrModuleEmptyURL - } - - if dotdotPath.MatchString(m.Path) { - return ErrModuleBadPath - } - - return nil -} - -func (m *Submodule) unmarshal(s *format.Subsection) { - m.raw = s - - m.Name = m.raw.Name - m.Path = m.raw.Option(pathKey) - m.URL = m.raw.Option(urlKey) - m.Branch = m.raw.Option(branchKey) -} - -func (m *Submodule) marshal() *format.Subsection { - if m.raw == nil { - m.raw = &format.Subsection{} - } - - m.raw.Name = m.Name - if m.raw.Name == "" { - m.raw.Name = m.Path - } - - m.raw.SetOption(pathKey, m.Path) - m.raw.SetOption(urlKey, m.URL) - - if m.Branch != "" { - m.raw.SetOption(branchKey, m.Branch) - } - - return m.raw -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/config/refspec.go b/vendor/github.com/jesseduffield/go-git/v5/config/refspec.go deleted file mode 100644 index 9df1b9fd07e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/config/refspec.go +++ /dev/null @@ -1,180 +0,0 @@ -package config - -import ( - "errors" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing" -) - -const ( - refSpecWildcard = "*" - refSpecForce = "+" - refSpecSeparator = ":" - refSpecNegative = "^" -) - -var ( - ErrRefSpecMalformedSeparator = errors.New("malformed refspec, separators are wrong") - ErrRefSpecMalformedWildcard = errors.New("malformed refspec, mismatched number of wildcards") - ErrRefSpecMalformedNegative = errors.New("malformed negative refspec, one ^ and no separators allowed") -) - -// RefSpec is a mapping from local branches to remote references. -// The format of the refspec is an optional +, followed by :, where -// is the pattern for references on the remote side and is where -// those references will be written locally. The + tells Git to update the -// reference even if it isn’t a fast-forward. -// eg.: "+refs/heads/*:refs/remotes/origin/*" -// -// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec -type RefSpec string - -// Validate validates the RefSpec -func (s RefSpec) Validate() error { - spec := string(s) - - if strings.Index(spec, refSpecNegative) == 0 { - // This is a negative refspec - if strings.Count(spec, refSpecNegative) != 1 { - return ErrRefSpecMalformedNegative - } - - if strings.Count(spec, refSpecSeparator) != 0 { - return ErrRefSpecMalformedNegative - } - - if strings.Count(spec, refSpecWildcard) > 1 { - return ErrRefSpecMalformedWildcard - } - - return nil - } - - if strings.Count(spec, refSpecSeparator) != 1 { - return ErrRefSpecMalformedSeparator - } - - sep := strings.Index(spec, refSpecSeparator) - if sep == len(spec)-1 { - return ErrRefSpecMalformedSeparator - } - - ws := strings.Count(spec[0:sep], refSpecWildcard) - wd := strings.Count(spec[sep+1:], refSpecWildcard) - if ws == wd && ws < 2 && wd < 2 { - return nil - } - - return ErrRefSpecMalformedWildcard -} - -// IsForceUpdate returns if update is allowed in non fast-forward merges. -func (s RefSpec) IsForceUpdate() bool { - return s[0] == refSpecForce[0] -} - -// IsDelete returns true if the refspec indicates a delete (empty src). -func (s RefSpec) IsDelete() bool { - return s[0] == refSpecSeparator[0] -} - -// IsExactSHA1 returns true if the source is a SHA1 hash. -func (s RefSpec) IsExactSHA1() bool { - return plumbing.IsHash(s.Src()) -} - -// IsNegative returns if the refspec is a negative one -func (s RefSpec) IsNegative() bool { - return s[0] == refSpecNegative[0] -} - -// Src returns the src side. -func (s RefSpec) Src() string { - spec := string(s) - - var start int - if s.IsForceUpdate() || s.IsNegative() { - start = 1 - } else { - start = 0 - } - - end := strings.Index(spec, refSpecSeparator) - return spec[start:end] -} - -// Match match the given plumbing.ReferenceName against the source. -func (s RefSpec) Match(n plumbing.ReferenceName) bool { - if !s.IsWildcard() { - return s.matchExact(n) - } - - return s.matchGlob(n) -} - -// IsWildcard returns true if the RefSpec contains a wildcard. -func (s RefSpec) IsWildcard() bool { - return strings.Contains(string(s), refSpecWildcard) -} - -func (s RefSpec) matchExact(n plumbing.ReferenceName) bool { - return s.Src() == n.String() -} - -func (s RefSpec) matchGlob(n plumbing.ReferenceName) bool { - src := s.Src() - name := n.String() - wildcard := strings.Index(src, refSpecWildcard) - - var prefix, suffix string - prefix = src[0:wildcard] - if len(src) > wildcard+1 { - suffix = src[wildcard+1:] - } - - return len(name) >= len(prefix)+len(suffix) && - strings.HasPrefix(name, prefix) && - strings.HasSuffix(name, suffix) -} - -// Dst returns the destination for the given remote reference. -func (s RefSpec) Dst(n plumbing.ReferenceName) plumbing.ReferenceName { - spec := string(s) - start := strings.Index(spec, refSpecSeparator) + 1 - dst := spec[start:] - src := s.Src() - - if !s.IsWildcard() { - return plumbing.ReferenceName(dst) - } - - name := n.String() - ws := strings.Index(src, refSpecWildcard) - wd := strings.Index(dst, refSpecWildcard) - match := name[ws : len(name)-(len(src)-(ws+1))] - - return plumbing.ReferenceName(dst[0:wd] + match + dst[wd+1:]) -} - -func (s RefSpec) Reverse() RefSpec { - spec := string(s) - separator := strings.Index(spec, refSpecSeparator) - - return RefSpec(spec[separator+1:] + refSpecSeparator + spec[:separator]) -} - -func (s RefSpec) String() string { - return string(s) -} - -// MatchAny returns true if any of the RefSpec match with the given ReferenceName. -func MatchAny(l []RefSpec, n plumbing.ReferenceName) bool { - for _, r := range l { - if r.Match(n) { - return true - } - } - - return false -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/config/url.go b/vendor/github.com/jesseduffield/go-git/v5/config/url.go deleted file mode 100644 index 3cefe2f27e0..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/config/url.go +++ /dev/null @@ -1,81 +0,0 @@ -package config - -import ( - "errors" - "strings" - - format "github.com/jesseduffield/go-git/v5/plumbing/format/config" -) - -var ( - errURLEmptyInsteadOf = errors.New("url config: empty insteadOf") -) - -// Url defines Url rewrite rules -type URL struct { - // Name new base url - Name string - // Any URL that starts with this value will be rewritten to start, instead, with . - // When more than one insteadOf strings match a given URL, the longest match is used. - InsteadOf string - - // raw representation of the subsection, filled by marshal or unmarshal are - // called. - raw *format.Subsection -} - -// Validate validates fields of branch -func (b *URL) Validate() error { - if b.InsteadOf == "" { - return errURLEmptyInsteadOf - } - - return nil -} - -const ( - insteadOfKey = "insteadOf" -) - -func (u *URL) unmarshal(s *format.Subsection) error { - u.raw = s - - u.Name = s.Name - u.InsteadOf = u.raw.Option(insteadOfKey) - return nil -} - -func (u *URL) marshal() *format.Subsection { - if u.raw == nil { - u.raw = &format.Subsection{} - } - - u.raw.Name = u.Name - u.raw.SetOption(insteadOfKey, u.InsteadOf) - - return u.raw -} - -func findLongestInsteadOfMatch(remoteURL string, urls map[string]*URL) *URL { - var longestMatch *URL - for _, u := range urls { - if !strings.HasPrefix(remoteURL, u.InsteadOf) { - continue - } - - // according to spec if there is more than one match, take the logest - if longestMatch == nil || len(longestMatch.InsteadOf) < len(u.InsteadOf) { - longestMatch = u - } - } - - return longestMatch -} - -func (u *URL) ApplyInsteadOf(url string) string { - if !strings.HasPrefix(url, u.InsteadOf) { - return url - } - - return u.Name + url[len(u.InsteadOf):] -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/doc.go b/vendor/github.com/jesseduffield/go-git/v5/doc.go deleted file mode 100644 index 3d817fe9c8c..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -// A highly extensible git implementation in pure Go. -// -// go-git aims to reach the completeness of libgit2 or jgit, nowadays covers the -// majority of the plumbing read operations and some of the main write -// operations, but lacks the main porcelain operations such as merges. -// -// It is highly extensible, we have been following the open/close principle in -// its design to facilitate extensions, mainly focusing the efforts on the -// persistence of the objects. -package git diff --git a/vendor/github.com/jesseduffield/go-git/v5/internal/path_util/path_util.go b/vendor/github.com/jesseduffield/go-git/v5/internal/path_util/path_util.go deleted file mode 100644 index 48e4a3d0ece..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/internal/path_util/path_util.go +++ /dev/null @@ -1,29 +0,0 @@ -package path_util - -import ( - "os" - "os/user" - "strings" -) - -func ReplaceTildeWithHome(path string) (string, error) { - if strings.HasPrefix(path, "~") { - firstSlash := strings.Index(path, "/") - if firstSlash == 1 { - home, err := os.UserHomeDir() - if err != nil { - return path, err - } - return strings.Replace(path, "~", home, 1), nil - } else if firstSlash > 1 { - username := path[1:firstSlash] - userAccount, err := user.Lookup(username) - if err != nil { - return path, err - } - return strings.Replace(path, path[:firstSlash], userAccount.HomeDir, 1), nil - } - } - - return path, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/internal/revision/parser.go b/vendor/github.com/jesseduffield/go-git/v5/internal/revision/parser.go deleted file mode 100644 index 8a2a7190e5c..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/internal/revision/parser.go +++ /dev/null @@ -1,626 +0,0 @@ -// Package revision extracts git revision from string -// More information about revision : https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html -package revision - -import ( - "bytes" - "fmt" - "io" - "regexp" - "strconv" - "time" -) - -// ErrInvalidRevision is emitted if string doesn't match valid revision -type ErrInvalidRevision struct { - s string -} - -func (e *ErrInvalidRevision) Error() string { - return "Revision invalid : " + e.s -} - -// Revisioner represents a revision component. -// A revision is made of multiple revision components -// obtained after parsing a revision string, -// for instance revision "master~" will be converted in -// two revision components Ref and TildePath -type Revisioner interface { -} - -// Ref represents a reference name : HEAD, master, -type Ref string - -// TildePath represents ~, ~{n} -type TildePath struct { - Depth int -} - -// CaretPath represents ^, ^{n} -type CaretPath struct { - Depth int -} - -// CaretReg represents ^{/foo bar} -type CaretReg struct { - Regexp *regexp.Regexp - Negate bool -} - -// CaretType represents ^{commit} -type CaretType struct { - ObjectType string -} - -// AtReflog represents @{n} -type AtReflog struct { - Depth int -} - -// AtCheckout represents @{-n} -type AtCheckout struct { - Depth int -} - -// AtUpstream represents @{upstream}, @{u} -type AtUpstream struct { - BranchName string -} - -// AtPush represents @{push} -type AtPush struct { - BranchName string -} - -// AtDate represents @{"2006-01-02T15:04:05Z"} -type AtDate struct { - Date time.Time -} - -// ColonReg represents :/foo bar -type ColonReg struct { - Regexp *regexp.Regexp - Negate bool -} - -// ColonPath represents :./ : -type ColonPath struct { - Path string -} - -// ColonStagePath represents ::/ -type ColonStagePath struct { - Path string - Stage int -} - -// Parser represents a parser -// use to tokenize and transform to revisioner chunks -// a given string -type Parser struct { - s *scanner - currentParsedChar struct { - tok token - lit string - } - unreadLastChar bool -} - -// NewParserFromString returns a new instance of parser from a string. -func NewParserFromString(s string) *Parser { - return NewParser(bytes.NewBufferString(s)) -} - -// NewParser returns a new instance of parser. -func NewParser(r io.Reader) *Parser { - return &Parser{s: newScanner(r)} -} - -// scan returns the next token from the underlying scanner -// or the last scanned token if an unscan was requested -func (p *Parser) scan() (token, string, error) { - if p.unreadLastChar { - p.unreadLastChar = false - return p.currentParsedChar.tok, p.currentParsedChar.lit, nil - } - - tok, lit, err := p.s.scan() - - p.currentParsedChar.tok, p.currentParsedChar.lit = tok, lit - - return tok, lit, err -} - -// unscan pushes the previously read token back onto the buffer. -func (p *Parser) unscan() { p.unreadLastChar = true } - -// Parse explode a revision string into revisioner chunks -func (p *Parser) Parse() ([]Revisioner, error) { - var rev Revisioner - var revs []Revisioner - var tok token - var err error - - for { - tok, _, err = p.scan() - - if err != nil { - return nil, err - } - - switch tok { - case at: - rev, err = p.parseAt() - case tilde: - rev, err = p.parseTilde() - case caret: - rev, err = p.parseCaret() - case colon: - rev, err = p.parseColon() - case eof: - err = p.validateFullRevision(&revs) - - if err != nil { - return []Revisioner{}, err - } - - return revs, nil - default: - p.unscan() - rev, err = p.parseRef() - } - - if err != nil { - return []Revisioner{}, err - } - - revs = append(revs, rev) - } -} - -// validateFullRevision ensures all revisioner chunks make a valid revision -func (p *Parser) validateFullRevision(chunks *[]Revisioner) error { - var hasReference bool - - for i, chunk := range *chunks { - switch chunk.(type) { - case Ref: - if i == 0 { - hasReference = true - } else { - return &ErrInvalidRevision{`reference must be defined once at the beginning`} - } - case AtDate: - if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { - return nil - } - - return &ErrInvalidRevision{`"@" statement is not valid, could be : @{}, @{}`} - case AtReflog: - if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { - return nil - } - - return &ErrInvalidRevision{`"@" statement is not valid, could be : @{}, @{}`} - case AtCheckout: - if len(*chunks) == 1 { - return nil - } - - return &ErrInvalidRevision{`"@" statement is not valid, could be : @{-}`} - case AtUpstream: - if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { - return nil - } - - return &ErrInvalidRevision{`"@" statement is not valid, could be : @{upstream}, @{upstream}, @{u}, @{u}`} - case AtPush: - if len(*chunks) == 1 || hasReference && len(*chunks) == 2 { - return nil - } - - return &ErrInvalidRevision{`"@" statement is not valid, could be : @{push}, @{push}`} - case TildePath, CaretPath, CaretReg: - if !hasReference { - return &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`} - } - case ColonReg: - if len(*chunks) == 1 { - return nil - } - - return &ErrInvalidRevision{`":" statement is not valid, could be : :/`} - case ColonPath: - if i == len(*chunks)-1 && hasReference || len(*chunks) == 1 { - return nil - } - - return &ErrInvalidRevision{`":" statement is not valid, could be : :`} - case ColonStagePath: - if len(*chunks) == 1 { - return nil - } - - return &ErrInvalidRevision{`":" statement is not valid, could be : ::`} - } - } - - return nil -} - -// parseAt extract @ statements -func (p *Parser) parseAt() (Revisioner, error) { - var tok, nextTok token - var lit, nextLit string - var err error - - tok, _, err = p.scan() - - if err != nil { - return nil, err - } - - if tok != obrace { - p.unscan() - - return Ref("HEAD"), nil - } - - tok, lit, err = p.scan() - - if err != nil { - return nil, err - } - - nextTok, nextLit, err = p.scan() - - if err != nil { - return nil, err - } - - switch { - case tok == word && (lit == "u" || lit == "upstream") && nextTok == cbrace: - return AtUpstream{}, nil - case tok == word && lit == "push" && nextTok == cbrace: - return AtPush{}, nil - case tok == number && nextTok == cbrace: - n, _ := strconv.Atoi(lit) - - return AtReflog{n}, nil - case tok == minus && nextTok == number: - n, _ := strconv.Atoi(nextLit) - - t, _, err := p.scan() - - if err != nil { - return nil, err - } - - if t != cbrace { - return nil, &ErrInvalidRevision{s: `missing "}" in @{-n} structure`} - } - - return AtCheckout{n}, nil - default: - p.unscan() - - date := lit - - for { - tok, lit, err = p.scan() - - if err != nil { - return nil, err - } - - switch { - case tok == cbrace: - t, err := time.Parse("2006-01-02T15:04:05Z", date) - - if err != nil { - return nil, &ErrInvalidRevision{fmt.Sprintf(`wrong date "%s" must fit ISO-8601 format : 2006-01-02T15:04:05Z`, date)} - } - - return AtDate{t}, nil - case tok == eof: - return nil, &ErrInvalidRevision{s: `missing "}" in @{} structure`} - default: - date += lit - } - } - } -} - -// parseTilde extract ~ statements -func (p *Parser) parseTilde() (Revisioner, error) { - var tok token - var lit string - var err error - - tok, lit, err = p.scan() - - if err != nil { - return nil, err - } - - switch { - case tok == number: - n, _ := strconv.Atoi(lit) - - return TildePath{n}, nil - default: - p.unscan() - return TildePath{1}, nil - } -} - -// parseCaret extract ^ statements -func (p *Parser) parseCaret() (Revisioner, error) { - var tok token - var lit string - var err error - - tok, lit, err = p.scan() - - if err != nil { - return nil, err - } - - switch { - case tok == obrace: - r, err := p.parseCaretBraces() - - if err != nil { - return nil, err - } - - return r, nil - case tok == number: - n, _ := strconv.Atoi(lit) - - if n > 2 { - return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" found must be 0, 1 or 2 after "^"`, lit)} - } - - return CaretPath{n}, nil - default: - p.unscan() - return CaretPath{1}, nil - } -} - -// parseCaretBraces extract ^{} statements -func (p *Parser) parseCaretBraces() (Revisioner, error) { - var tok, nextTok token - var lit, _ string - start := true - var re string - var negate bool - var err error - - for { - tok, lit, err = p.scan() - - if err != nil { - return nil, err - } - - nextTok, _, err = p.scan() - - if err != nil { - return nil, err - } - - switch { - case tok == word && nextTok == cbrace && (lit == "commit" || lit == "tree" || lit == "blob" || lit == "tag" || lit == "object"): - return CaretType{lit}, nil - case re == "" && tok == cbrace: - return CaretType{"tag"}, nil - case re == "" && tok == emark && nextTok == emark: - re += lit - case re == "" && tok == emark && nextTok == minus: - negate = true - case re == "" && tok == emark: - return nil, &ErrInvalidRevision{s: `revision suffix brace component sequences starting with "/!" others than those defined are reserved`} - case re == "" && tok == slash: - p.unscan() - case tok != slash && start: - return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} - case tok == eof: - return nil, &ErrInvalidRevision{s: `missing "}" in ^{} structure`} - case tok != cbrace: - p.unscan() - re += lit - case tok == cbrace: - p.unscan() - - reg, err := regexp.Compile(re) - - if err != nil { - return CaretReg{}, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component, %s`, err.Error())} - } - - return CaretReg{reg, negate}, nil - } - - start = false - } -} - -// parseColon extract : statements -func (p *Parser) parseColon() (Revisioner, error) { - var tok token - var err error - - tok, _, err = p.scan() - - if err != nil { - return nil, err - } - - switch tok { - case slash: - return p.parseColonSlash() - default: - p.unscan() - return p.parseColonDefault() - } -} - -// parseColonSlash extract :/ statements -func (p *Parser) parseColonSlash() (Revisioner, error) { - var tok, nextTok token - var lit string - var re string - var negate bool - var err error - - for { - tok, lit, err = p.scan() - - if err != nil { - return nil, err - } - - nextTok, _, err = p.scan() - - if err != nil { - return nil, err - } - - switch { - case tok == emark && nextTok == emark: - re += lit - case re == "" && tok == emark && nextTok == minus: - negate = true - case re == "" && tok == emark: - return nil, &ErrInvalidRevision{s: `revision suffix brace component sequences starting with "/!" others than those defined are reserved`} - case tok == eof: - p.unscan() - reg, err := regexp.Compile(re) - - if err != nil { - return ColonReg{}, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component, %s`, err.Error())} - } - - return ColonReg{reg, negate}, nil - default: - p.unscan() - re += lit - } - } -} - -// parseColonDefault extract : statements -func (p *Parser) parseColonDefault() (Revisioner, error) { - var tok token - var lit string - var path string - var stage int - var err error - var n = -1 - - tok, lit, err = p.scan() - - if err != nil { - return nil, err - } - - nextTok, _, err := p.scan() - - if err != nil { - return nil, err - } - - if tok == number && nextTok == colon { - n, _ = strconv.Atoi(lit) - } - - switch n { - case 0, 1, 2, 3: - stage = n - default: - path += lit - p.unscan() - } - - for { - tok, lit, err = p.scan() - - if err != nil { - return nil, err - } - - switch { - case tok == eof && n == -1: - return ColonPath{path}, nil - case tok == eof: - return ColonStagePath{path, stage}, nil - default: - path += lit - } - } -} - -// parseRef extract reference name -func (p *Parser) parseRef() (Revisioner, error) { - var tok, prevTok token - var lit, buf string - var endOfRef bool - var err error - - for { - tok, lit, err = p.scan() - - if err != nil { - return nil, err - } - - switch tok { - case eof, at, colon, tilde, caret: - endOfRef = true - } - - err := p.checkRefFormat(tok, lit, prevTok, buf, endOfRef) - - if err != nil { - return "", err - } - - if endOfRef { - p.unscan() - return Ref(buf), nil - } - - buf += lit - prevTok = tok - } -} - -// checkRefFormat ensure reference name follow rules defined here : -// https://git-scm.com/docs/git-check-ref-format -func (p *Parser) checkRefFormat(token token, literal string, previousToken token, buffer string, endOfRef bool) error { - switch token { - case aslash, space, control, qmark, asterisk, obracket: - return &ErrInvalidRevision{fmt.Sprintf(`must not contains "%s"`, literal)} - } - - switch { - case (token == dot || token == slash) && buffer == "": - return &ErrInvalidRevision{fmt.Sprintf(`must not start with "%s"`, literal)} - case previousToken == slash && endOfRef: - return &ErrInvalidRevision{`must not end with "/"`} - case previousToken == dot && endOfRef: - return &ErrInvalidRevision{`must not end with "."`} - case token == dot && previousToken == slash: - return &ErrInvalidRevision{`must not contains "/."`} - case previousToken == dot && token == dot: - return &ErrInvalidRevision{`must not contains ".."`} - case previousToken == slash && token == slash: - return &ErrInvalidRevision{`must not contains consecutively "/"`} - case (token == slash || endOfRef) && len(buffer) > 4 && buffer[len(buffer)-5:] == ".lock": - return &ErrInvalidRevision{"cannot end with .lock"} - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/internal/revision/scanner.go b/vendor/github.com/jesseduffield/go-git/v5/internal/revision/scanner.go deleted file mode 100644 index 2444f33ec2f..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/internal/revision/scanner.go +++ /dev/null @@ -1,122 +0,0 @@ -package revision - -import ( - "bufio" - "io" - "unicode" -) - -// runeCategoryValidator takes a rune as input and -// validates it belongs to a rune category -type runeCategoryValidator func(r rune) bool - -// tokenizeExpression aggregates a series of runes matching check predicate into a single -// string and provides given tokenType as token type -func tokenizeExpression(ch rune, tokenType token, check runeCategoryValidator, r *bufio.Reader) (token, string, error) { - var data []rune - data = append(data, ch) - - for { - c, _, err := r.ReadRune() - - if c == zeroRune { - break - } - - if err != nil { - return tokenError, "", err - } - - if check(c) { - data = append(data, c) - } else { - err := r.UnreadRune() - - if err != nil { - return tokenError, "", err - } - - return tokenType, string(data), nil - } - } - - return tokenType, string(data), nil -} - -// maxRevisionLength holds the maximum length that will be parsed for a -// revision. Git itself doesn't enforce a max length, but rather leans on -// the OS to enforce it via its ARG_MAX. -const maxRevisionLength = 128 * 1024 // 128kb - -var zeroRune = rune(0) - -// scanner represents a lexical scanner. -type scanner struct { - r *bufio.Reader -} - -// newScanner returns a new instance of scanner. -func newScanner(r io.Reader) *scanner { - return &scanner{r: bufio.NewReader(io.LimitReader(r, maxRevisionLength))} -} - -// Scan extracts tokens and their strings counterpart -// from the reader -func (s *scanner) scan() (token, string, error) { - ch, _, err := s.r.ReadRune() - - if err != nil && err != io.EOF { - return tokenError, "", err - } - - switch ch { - case zeroRune: - return eof, "", nil - case ':': - return colon, string(ch), nil - case '~': - return tilde, string(ch), nil - case '^': - return caret, string(ch), nil - case '.': - return dot, string(ch), nil - case '/': - return slash, string(ch), nil - case '{': - return obrace, string(ch), nil - case '}': - return cbrace, string(ch), nil - case '-': - return minus, string(ch), nil - case '@': - return at, string(ch), nil - case '\\': - return aslash, string(ch), nil - case '?': - return qmark, string(ch), nil - case '*': - return asterisk, string(ch), nil - case '[': - return obracket, string(ch), nil - case '!': - return emark, string(ch), nil - } - - if unicode.IsSpace(ch) { - return space, string(ch), nil - } - - if unicode.IsControl(ch) { - return control, string(ch), nil - } - - if unicode.IsLetter(ch) { - return tokenizeExpression(ch, word, unicode.IsLetter, s.r) - } - - if unicode.IsNumber(ch) { - return tokenizeExpression(ch, number, unicode.IsNumber, s.r) - } - - return tokenError, string(ch), nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/internal/revision/token.go b/vendor/github.com/jesseduffield/go-git/v5/internal/revision/token.go deleted file mode 100644 index abc40488693..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/internal/revision/token.go +++ /dev/null @@ -1,28 +0,0 @@ -package revision - -// token represents a entity extracted from string parsing -type token int - -const ( - eof token = iota - - aslash - asterisk - at - caret - cbrace - colon - control - dot - emark - minus - number - obrace - obracket - qmark - slash - space - tilde - tokenError - word -) diff --git a/vendor/github.com/jesseduffield/go-git/v5/internal/url/url.go b/vendor/github.com/jesseduffield/go-git/v5/internal/url/url.go deleted file mode 100644 index 26624486937..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/internal/url/url.go +++ /dev/null @@ -1,39 +0,0 @@ -package url - -import ( - "regexp" -) - -var ( - isSchemeRegExp = regexp.MustCompile(`^[^:]+://`) - - // Ref: https://github.com/git/git/blob/master/Documentation/urls.txt#L37 - scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P[^@]+)@)?(?P[^:\s]+):(?:(?P[0-9]{1,5}):)?(?P[^\\].*)$`) -) - -// MatchesScheme returns true if the given string matches a URL-like -// format scheme. -func MatchesScheme(url string) bool { - return isSchemeRegExp.MatchString(url) -} - -// MatchesScpLike returns true if the given string matches an SCP-like -// format scheme. -func MatchesScpLike(url string) bool { - return scpLikeUrlRegExp.MatchString(url) -} - -// FindScpLikeComponents returns the user, host, port and path of the -// given SCP-like URL. -func FindScpLikeComponents(url string) (user, host, port, path string) { - m := scpLikeUrlRegExp.FindStringSubmatch(url) - return m[1], m[2], m[3], m[4] -} - -// IsLocalEndpoint returns true if the given URL string specifies a -// local file endpoint. For example, on a Linux machine, -// `/home/user/src/go-git` would match as a local endpoint, but -// `https://github.com/src-d/go-git` would not. -func IsLocalEndpoint(url string) bool { - return !MatchesScheme(url) && !MatchesScpLike(url) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/object_walker.go b/vendor/github.com/jesseduffield/go-git/v5/object_walker.go deleted file mode 100644 index 2f390267afc..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/object_walker.go +++ /dev/null @@ -1,104 +0,0 @@ -package git - -import ( - "fmt" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" - "github.com/jesseduffield/go-git/v5/plumbing/object" - "github.com/jesseduffield/go-git/v5/storage" -) - -type objectWalker struct { - Storer storage.Storer - // seen is the set of objects seen in the repo. - // seen map can become huge if walking over large - // repos. Thus using struct{} as the value type. - seen map[plumbing.Hash]struct{} -} - -func newObjectWalker(s storage.Storer) *objectWalker { - return &objectWalker{s, map[plumbing.Hash]struct{}{}} -} - -// walkAllRefs walks all (hash) references from the repo. -func (p *objectWalker) walkAllRefs() error { - // Walk over all the references in the repo. - it, err := p.Storer.IterReferences() - if err != nil { - return err - } - defer it.Close() - err = it.ForEach(func(ref *plumbing.Reference) error { - // Exit this iteration early for non-hash references. - if ref.Type() != plumbing.HashReference { - return nil - } - return p.walkObjectTree(ref.Hash()) - }) - return err -} - -func (p *objectWalker) isSeen(hash plumbing.Hash) bool { - _, seen := p.seen[hash] - return seen -} - -func (p *objectWalker) add(hash plumbing.Hash) { - p.seen[hash] = struct{}{} -} - -// walkObjectTree walks over all objects and remembers references -// to them in the objectWalker. This is used instead of the revlist -// walks because memory usage is tight with huge repos. -func (p *objectWalker) walkObjectTree(hash plumbing.Hash) error { - // Check if we have already seen, and mark this object - if p.isSeen(hash) { - return nil - } - p.add(hash) - // Fetch the object. - obj, err := object.GetObject(p.Storer, hash) - if err != nil { - return fmt.Errorf("getting object %s failed: %v", hash, err) - } - // Walk all children depending on object type. - switch obj := obj.(type) { - case *object.Commit: - err = p.walkObjectTree(obj.TreeHash) - if err != nil { - return err - } - for _, h := range obj.ParentHashes { - err = p.walkObjectTree(h) - if err != nil { - return err - } - } - case *object.Tree: - for i := range obj.Entries { - // Shortcut for blob objects: - // 'or' the lower bits of a mode and check that it - // it matches a filemode.Executable. The type information - // is in the higher bits, but this is the cleanest way - // to handle plain files with different modes. - // Other non-tree objects are somewhat rare, so they - // are not special-cased. - if obj.Entries[i].Mode|0755 == filemode.Executable { - p.add(obj.Entries[i].Hash) - continue - } - // Normal walk for sub-trees (and symlinks etc). - err = p.walkObjectTree(obj.Entries[i].Hash) - if err != nil { - return err - } - } - case *object.Tag: - return p.walkObjectTree(obj.Target) - default: - // Error out on unhandled object types. - return fmt.Errorf("unknown object %X %s %T", obj.ID(), obj.Type(), obj) - } - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/options.go b/vendor/github.com/jesseduffield/go-git/v5/options.go deleted file mode 100644 index 101c1418a83..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/options.go +++ /dev/null @@ -1,818 +0,0 @@ -package git - -import ( - "errors" - "fmt" - "regexp" - "strings" - "time" - - "github.com/ProtonMail/go-crypto/openpgp" - "github.com/jesseduffield/go-git/v5/config" - "github.com/jesseduffield/go-git/v5/plumbing" - formatcfg "github.com/jesseduffield/go-git/v5/plumbing/format/config" - "github.com/jesseduffield/go-git/v5/plumbing/object" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband" - "github.com/jesseduffield/go-git/v5/plumbing/transport" -) - -// SubmoduleRescursivity defines how depth will affect any submodule recursive -// operation. -type SubmoduleRescursivity uint - -const ( - // DefaultRemoteName name of the default Remote, just like git command. - DefaultRemoteName = "origin" - - // NoRecurseSubmodules disables the recursion for a submodule operation. - NoRecurseSubmodules SubmoduleRescursivity = 0 - // DefaultSubmoduleRecursionDepth allow recursion in a submodule operation. - DefaultSubmoduleRecursionDepth SubmoduleRescursivity = 10 -) - -var ( - ErrMissingURL = errors.New("URL field is required") -) - -// CloneOptions describes how a clone should be performed. -type CloneOptions struct { - // The (possibly remote) repository URL to clone from. - URL string - // Auth credentials, if required, to use with the remote repository. - Auth transport.AuthMethod - // Name of the remote to be added, by default `origin`. - RemoteName string - // Remote branch to clone. - ReferenceName plumbing.ReferenceName - // Fetch only ReferenceName if true. - SingleBranch bool - // Mirror clones the repository as a mirror. - // - // Compared to a bare clone, mirror not only maps local branches of the - // source to local branches of the target, it maps all refs (including - // remote-tracking branches, notes etc.) and sets up a refspec configuration - // such that all these refs are overwritten by a git remote update in the - // target repository. - Mirror bool - // No checkout of HEAD after clone if true. - NoCheckout bool - // Limit fetching to the specified number of commits. - Depth int - // RecurseSubmodules after the clone is created, initialize all submodules - // within, using their default settings. This option is ignored if the - // cloned repository does not have a worktree. - RecurseSubmodules SubmoduleRescursivity - // ShallowSubmodules limit cloning submodules to the 1 level of depth. - // It matches the git command --shallow-submodules. - ShallowSubmodules bool - // Progress is where the human readable information sent by the server is - // stored, if nil nothing is stored and the capability (if supported) - // no-progress, is sent to the server to avoid send this information. - Progress sideband.Progress - // Tags describe how the tags will be fetched from the remote repository, - // by default is AllTags. - Tags TagMode - // InsecureSkipTLS skips ssl verify if protocol is https - InsecureSkipTLS bool - // CABundle specify additional ca bundle with system cert pool - CABundle []byte - // ProxyOptions provides info required for connecting to a proxy. - ProxyOptions transport.ProxyOptions - // When the repository to clone is on the local machine, instead of - // using hard links, automatically setup .git/objects/info/alternates - // to share the objects with the source repository. - // The resulting repository starts out without any object of its own. - // NOTE: this is a possibly dangerous operation; do not use it unless - // you understand what it does. - // - // [Reference]: https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---shared - Shared bool -} - -// MergeOptions describes how a merge should be performed. -type MergeOptions struct { - // Strategy defines the merge strategy to be used. - Strategy MergeStrategy -} - -// MergeStrategy represents the different types of merge strategies. -type MergeStrategy int8 - -const ( - // FastForwardMerge represents a Git merge strategy where the current - // branch can be simply updated to point to the HEAD of the branch being - // merged. This is only possible if the history of the branch being merged - // is a linear descendant of the current branch, with no conflicting commits. - // - // This is the default option. - FastForwardMerge MergeStrategy = iota -) - -// Validate validates the fields and sets the default values. -func (o *CloneOptions) Validate() error { - if o.URL == "" { - return ErrMissingURL - } - - if o.RemoteName == "" { - o.RemoteName = DefaultRemoteName - } - - if o.ReferenceName == "" { - o.ReferenceName = plumbing.HEAD - } - - if o.Tags == InvalidTagMode { - o.Tags = AllTags - } - - return nil -} - -// PullOptions describes how a pull should be performed. -type PullOptions struct { - // Name of the remote to be pulled. If empty, uses the default. - RemoteName string - // RemoteURL overrides the remote repo address with a custom URL - RemoteURL string - // Remote branch to clone. If empty, uses HEAD. - ReferenceName plumbing.ReferenceName - // Fetch only ReferenceName if true. - SingleBranch bool - // Limit fetching to the specified number of commits. - Depth int - // Auth credentials, if required, to use with the remote repository. - Auth transport.AuthMethod - // RecurseSubmodules controls if new commits of all populated submodules - // should be fetched too. - RecurseSubmodules SubmoduleRescursivity - // Progress is where the human readable information sent by the server is - // stored, if nil nothing is stored and the capability (if supported) - // no-progress, is sent to the server to avoid send this information. - Progress sideband.Progress - // Force allows the pull to update a local branch even when the remote - // branch does not descend from it. - Force bool - // InsecureSkipTLS skips ssl verify if protocol is https - InsecureSkipTLS bool - // CABundle specify additional ca bundle with system cert pool - CABundle []byte - // ProxyOptions provides info required for connecting to a proxy. - ProxyOptions transport.ProxyOptions -} - -// Validate validates the fields and sets the default values. -func (o *PullOptions) Validate() error { - if o.RemoteName == "" { - o.RemoteName = DefaultRemoteName - } - - if o.ReferenceName == "" { - o.ReferenceName = plumbing.HEAD - } - - return nil -} - -type TagMode int - -const ( - InvalidTagMode TagMode = iota - // TagFollowing any tag that points into the histories being fetched is also - // fetched. TagFollowing requires a server with `include-tag` capability - // in order to fetch the annotated tags objects. - TagFollowing - // AllTags fetch all tags from the remote (i.e., fetch remote tags - // refs/tags/* into local tags with the same name) - AllTags - // NoTags fetch no tags from the remote at all - NoTags -) - -// FetchOptions describes how a fetch should be performed -type FetchOptions struct { - // Name of the remote to fetch from. Defaults to origin. - RemoteName string - // RemoteURL overrides the remote repo address with a custom URL - RemoteURL string - RefSpecs []config.RefSpec - // Depth limit fetching to the specified number of commits from the tip of - // each remote branch history. - Depth int - // Auth credentials, if required, to use with the remote repository. - Auth transport.AuthMethod - // Progress is where the human readable information sent by the server is - // stored, if nil nothing is stored and the capability (if supported) - // no-progress, is sent to the server to avoid send this information. - Progress sideband.Progress - // Tags describe how the tags will be fetched from the remote repository, - // by default is TagFollowing. - Tags TagMode - // Force allows the fetch to update a local branch even when the remote - // branch does not descend from it. - Force bool - // InsecureSkipTLS skips ssl verify if protocol is https - InsecureSkipTLS bool - // CABundle specify additional ca bundle with system cert pool - CABundle []byte - // ProxyOptions provides info required for connecting to a proxy. - ProxyOptions transport.ProxyOptions - // Prune specify that local refs that match given RefSpecs and that do - // not exist remotely will be removed. - Prune bool -} - -// Validate validates the fields and sets the default values. -func (o *FetchOptions) Validate() error { - if o.RemoteName == "" { - o.RemoteName = DefaultRemoteName - } - - if o.Tags == InvalidTagMode { - o.Tags = TagFollowing - } - - for _, r := range o.RefSpecs { - if err := r.Validate(); err != nil { - return err - } - } - - return nil -} - -// PushOptions describes how a push should be performed. -type PushOptions struct { - // RemoteName is the name of the remote to be pushed to. - RemoteName string - // RemoteURL overrides the remote repo address with a custom URL - RemoteURL string - // RefSpecs specify what destination ref to update with what source object. - // - // The format of a parameter is an optional plus +, followed by - // the source object , followed by a colon :, followed by the destination ref . - // The is often the name of the branch you would want to push, but it can be a SHA-1. - // The tells which ref on the remote side is updated with this push. - // - // A refspec with empty src can be used to delete a reference. - RefSpecs []config.RefSpec - // Auth credentials, if required, to use with the remote repository. - Auth transport.AuthMethod - // Progress is where the human readable information sent by the server is - // stored, if nil nothing is stored. - Progress sideband.Progress - // Prune specify that remote refs that match given RefSpecs and that do - // not exist locally will be removed. - Prune bool - // Force allows the push to update a remote branch even when the local - // branch does not descend from it. - Force bool - // InsecureSkipTLS skips ssl verify if protocol is https - InsecureSkipTLS bool - // CABundle specify additional ca bundle with system cert pool - CABundle []byte - // RequireRemoteRefs only allows a remote ref to be updated if its current - // value is the one specified here. - RequireRemoteRefs []config.RefSpec - // FollowTags will send any annotated tags with a commit target reachable from - // the refs already being pushed - FollowTags bool - // ForceWithLease allows a force push as long as the remote ref adheres to a "lease" - ForceWithLease *ForceWithLease - // PushOptions sets options to be transferred to the server during push. - Options map[string]string - // Atomic sets option to be an atomic push - Atomic bool - // ProxyOptions provides info required for connecting to a proxy. - ProxyOptions transport.ProxyOptions -} - -// ForceWithLease sets fields on the lease -// If neither RefName nor Hash are set, ForceWithLease protects -// all refs in the refspec by ensuring the ref of the remote in the local repsitory -// matches the one in the ref advertisement. -type ForceWithLease struct { - // RefName, when set will protect the ref by ensuring it matches the - // hash in the ref advertisement. - RefName plumbing.ReferenceName - // Hash is the expected object id of RefName. The push will be rejected unless this - // matches the corresponding object id of RefName in the refs advertisement. - Hash plumbing.Hash -} - -// Validate validates the fields and sets the default values. -func (o *PushOptions) Validate() error { - if o.RemoteName == "" { - o.RemoteName = DefaultRemoteName - } - - if len(o.RefSpecs) == 0 { - o.RefSpecs = []config.RefSpec{ - config.RefSpec(config.DefaultPushRefSpec), - } - } - - for _, r := range o.RefSpecs { - if err := r.Validate(); err != nil { - return err - } - } - - return nil -} - -// SubmoduleUpdateOptions describes how a submodule update should be performed. -type SubmoduleUpdateOptions struct { - // Init, if true initializes the submodules recorded in the index. - Init bool - // NoFetch tell to the update command to not fetch new objects from the - // remote site. - NoFetch bool - // RecurseSubmodules the update is performed not only in the submodules of - // the current repository but also in any nested submodules inside those - // submodules (and so on). Until the SubmoduleRescursivity is reached. - RecurseSubmodules SubmoduleRescursivity - // Auth credentials, if required, to use with the remote repository. - Auth transport.AuthMethod - // Depth limit fetching to the specified number of commits from the tip of - // each remote branch history. - Depth int -} - -var ( - ErrBranchHashExclusive = errors.New("Branch and Hash are mutually exclusive") - ErrCreateRequiresBranch = errors.New("Branch is mandatory when Create is used") -) - -// CheckoutOptions describes how a checkout operation should be performed. -type CheckoutOptions struct { - // Hash is the hash of a commit or tag to be checked out. If used, HEAD - // will be in detached mode. If Create is not used, Branch and Hash are - // mutually exclusive. - Hash plumbing.Hash - // Branch to be checked out, if Branch and Hash are empty is set to `master`. - Branch plumbing.ReferenceName - // Create a new branch named Branch and start it at Hash. - Create bool - // Force, if true when switching branches, proceed even if the index or the - // working tree differs from HEAD. This is used to throw away local changes - Force bool - // Keep, if true when switching branches, local changes (the index or the - // working tree changes) will be kept so that they can be committed to the - // target branch. Force and Keep are mutually exclusive, should not be both - // set to true. - Keep bool - // SparseCheckoutDirectories - SparseCheckoutDirectories []string -} - -// Validate validates the fields and sets the default values. -func (o *CheckoutOptions) Validate() error { - if !o.Create && !o.Hash.IsZero() && o.Branch != "" { - return ErrBranchHashExclusive - } - - if o.Create && o.Branch == "" { - return ErrCreateRequiresBranch - } - - if o.Branch == "" { - o.Branch = plumbing.Master - } - - return nil -} - -// ResetMode defines the mode of a reset operation. -type ResetMode int8 - -const ( - // MixedReset resets the index but not the working tree (i.e., the changed - // files are preserved but not marked for commit) and reports what has not - // been updated. This is the default action. - MixedReset ResetMode = iota - // HardReset resets the index and working tree. Any changes to tracked files - // in the working tree are discarded. - HardReset - // MergeReset resets the index and updates the files in the working tree - // that are different between Commit and HEAD, but keeps those which are - // different between the index and working tree (i.e. which have changes - // which have not been added). - // - // If a file that is different between Commit and the index has unstaged - // changes, reset is aborted. - MergeReset - // SoftReset does not touch the index file or the working tree at all (but - // resets the head to , just like all modes do). This leaves all - // your changed files "Changes to be committed", as git status would put it. - SoftReset -) - -// ResetOptions describes how a reset operation should be performed. -type ResetOptions struct { - // Commit, if commit is present set the current branch head (HEAD) to it. - Commit plumbing.Hash - // Mode, form resets the current branch head to Commit and possibly updates - // the index (resetting it to the tree of Commit) and the working tree - // depending on Mode. If empty MixedReset is used. - Mode ResetMode - // Files, if not empty will constrain the reseting the index to only files - // specified in this list. - Files []string -} - -// Validate validates the fields and sets the default values. -func (o *ResetOptions) Validate(r *Repository) error { - if o.Commit == plumbing.ZeroHash { - ref, err := r.Head() - if err != nil { - return err - } - - o.Commit = ref.Hash() - } else { - _, err := r.CommitObject(o.Commit) - if err != nil { - return fmt.Errorf("invalid reset option: %w", err) - } - } - - return nil -} - -type LogOrder int8 - -const ( - LogOrderDefault LogOrder = iota - LogOrderDFS - LogOrderDFSPost - LogOrderBSF - LogOrderCommitterTime -) - -// LogOptions describes how a log action should be performed. -type LogOptions struct { - // When the From option is set the log will only contain commits - // reachable from it. If this option is not set, HEAD will be used as - // the default From. - From plumbing.Hash - - // The default traversal algorithm is Depth-first search - // set Order=LogOrderCommitterTime for ordering by committer time (more compatible with `git log`) - // set Order=LogOrderBSF for Breadth-first search - Order LogOrder - - // Show only those commits in which the specified file was inserted/updated. - // It is equivalent to running `git log -- `. - // this field is kept for compatibility, it can be replaced with PathFilter - FileName *string - - // Filter commits based on the path of files that are updated - // takes file path as argument and should return true if the file is desired - // It can be used to implement `git log -- ` - // either is a file path, or directory path, or a regexp of file/directory path - PathFilter func(string) bool - - // Pretend as if all the refs in refs/, along with HEAD, are listed on the command line as . - // It is equivalent to running `git log --all`. - // If set on true, the From option will be ignored. - All bool - - // Show commits more recent than a specific date. - // It is equivalent to running `git log --since ` or `git log --after `. - Since *time.Time - - // Show commits older than a specific date. - // It is equivalent to running `git log --until ` or `git log --before `. - Until *time.Time -} - -var ( - ErrMissingAuthor = errors.New("author field is required") -) - -// AddOptions describes how an `add` operation should be performed -type AddOptions struct { - // All equivalent to `git add -A`, update the index not only where the - // working tree has a file matching `Path` but also where the index already - // has an entry. This adds, modifies, and removes index entries to match the - // working tree. If no `Path` nor `Glob` is given when `All` option is - // used, all files in the entire working tree are updated. - All bool - // Path is the exact filepath to the file or directory to be added. - Path string - // Glob adds all paths, matching pattern, to the index. If pattern matches a - // directory path, all directory contents are added to the index recursively. - Glob string - // SkipStatus adds the path with no status check. This option is relevant only - // when the `Path` option is specified and does not apply when the `All` option is used. - // Notice that when passing an ignored path it will be added anyway. - // When true it can speed up adding files to the worktree in very large repositories. - SkipStatus bool -} - -// Validate validates the fields and sets the default values. -func (o *AddOptions) Validate(r *Repository) error { - if o.Path != "" && o.Glob != "" { - return fmt.Errorf("fields Path and Glob are mutual exclusive") - } - - return nil -} - -// CommitOptions describes how a commit operation should be performed. -type CommitOptions struct { - // All automatically stage files that have been modified and deleted, but - // new files you have not told Git about are not affected. - All bool - // AllowEmptyCommits enable empty commits to be created. An empty commit - // is when no changes to the tree were made, but a new commit message is - // provided. The default behavior is false, which results in ErrEmptyCommit. - AllowEmptyCommits bool - // Author is the author's signature of the commit. If Author is empty the - // Name and Email is read from the config, and time.Now it's used as When. - Author *object.Signature - // Committer is the committer's signature of the commit. If Committer is - // nil the Author signature is used. - Committer *object.Signature - // Parents are the parents commits for the new commit, by default when - // len(Parents) is zero, the hash of HEAD reference is used. - Parents []plumbing.Hash - // SignKey denotes a key to sign the commit with. A nil value here means the - // commit will not be signed. The private key must be present and already - // decrypted. - SignKey *openpgp.Entity - // Signer denotes a cryptographic signer to sign the commit with. - // A nil value here means the commit will not be signed. - // Takes precedence over SignKey. - Signer Signer - // Amend will create a new commit object and replace the commit that HEAD currently - // points to. Cannot be used with All nor Parents. - Amend bool -} - -// Validate validates the fields and sets the default values. -func (o *CommitOptions) Validate(r *Repository) error { - if o.All && o.Amend { - return errors.New("all and amend cannot be used together") - } - - if o.Amend && len(o.Parents) > 0 { - return errors.New("parents cannot be used with amend") - } - - if o.Author == nil { - if err := o.loadConfigAuthorAndCommitter(r); err != nil { - return err - } - } - - if o.Committer == nil { - o.Committer = o.Author - } - - if len(o.Parents) == 0 { - head, err := r.Head() - if err != nil && err != plumbing.ErrReferenceNotFound { - return err - } - - if head != nil { - o.Parents = []plumbing.Hash{head.Hash()} - } - } - - return nil -} - -func (o *CommitOptions) loadConfigAuthorAndCommitter(r *Repository) error { - cfg, err := r.ConfigScoped(config.SystemScope) - if err != nil { - return err - } - - if o.Author == nil && cfg.Author.Email != "" && cfg.Author.Name != "" { - o.Author = &object.Signature{ - Name: cfg.Author.Name, - Email: cfg.Author.Email, - When: time.Now(), - } - } - - if o.Committer == nil && cfg.Committer.Email != "" && cfg.Committer.Name != "" { - o.Committer = &object.Signature{ - Name: cfg.Committer.Name, - Email: cfg.Committer.Email, - When: time.Now(), - } - } - - if o.Author == nil && cfg.User.Email != "" && cfg.User.Name != "" { - o.Author = &object.Signature{ - Name: cfg.User.Name, - Email: cfg.User.Email, - When: time.Now(), - } - } - - if o.Author == nil { - return ErrMissingAuthor - } - - return nil -} - -var ( - ErrMissingName = errors.New("name field is required") - ErrMissingTagger = errors.New("tagger field is required") - ErrMissingMessage = errors.New("message field is required") -) - -// CreateTagOptions describes how a tag object should be created. -type CreateTagOptions struct { - // Tagger defines the signature of the tag creator. If Tagger is empty the - // Name and Email is read from the config, and time.Now it's used as When. - Tagger *object.Signature - // Message defines the annotation of the tag. It is canonicalized during - // validation into the format expected by git - no leading whitespace and - // ending in a newline. - Message string - // SignKey denotes a key to sign the tag with. A nil value here means the tag - // will not be signed. The private key must be present and already decrypted. - SignKey *openpgp.Entity -} - -// Validate validates the fields and sets the default values. -func (o *CreateTagOptions) Validate(r *Repository, hash plumbing.Hash) error { - if o.Tagger == nil { - if err := o.loadConfigTagger(r); err != nil { - return err - } - } - - if o.Message == "" { - return ErrMissingMessage - } - - // Canonicalize the message into the expected message format. - o.Message = strings.TrimSpace(o.Message) + "\n" - - return nil -} - -func (o *CreateTagOptions) loadConfigTagger(r *Repository) error { - cfg, err := r.ConfigScoped(config.SystemScope) - if err != nil { - return err - } - - if o.Tagger == nil && cfg.Author.Email != "" && cfg.Author.Name != "" { - o.Tagger = &object.Signature{ - Name: cfg.Author.Name, - Email: cfg.Author.Email, - When: time.Now(), - } - } - - if o.Tagger == nil && cfg.User.Email != "" && cfg.User.Name != "" { - o.Tagger = &object.Signature{ - Name: cfg.User.Name, - Email: cfg.User.Email, - When: time.Now(), - } - } - - if o.Tagger == nil { - return ErrMissingTagger - } - - return nil -} - -// ListOptions describes how a remote list should be performed. -type ListOptions struct { - // Auth credentials, if required, to use with the remote repository. - Auth transport.AuthMethod - // InsecureSkipTLS skips ssl verify if protocol is https - InsecureSkipTLS bool - // CABundle specify additional ca bundle with system cert pool - CABundle []byte - // PeelingOption defines how peeled objects are handled during a - // remote list. - PeelingOption PeelingOption - // ProxyOptions provides info required for connecting to a proxy. - ProxyOptions transport.ProxyOptions - // Timeout specifies the timeout in seconds for list operations - Timeout int -} - -// PeelingOption represents the different ways to handle peeled references. -// -// Peeled references represent the underlying object of an annotated -// (or signed) tag. Refer to upstream documentation for more info: -// https://github.com/git/git/blob/master/Documentation/technical/reftable.txt -type PeelingOption uint8 - -const ( - // IgnorePeeled ignores all peeled reference names. This is the default behavior. - IgnorePeeled PeelingOption = 0 - // OnlyPeeled returns only peeled reference names. - OnlyPeeled PeelingOption = 1 - // AppendPeeled appends peeled reference names to the reference list. - AppendPeeled PeelingOption = 2 -) - -// CleanOptions describes how a clean should be performed. -type CleanOptions struct { - Dir bool -} - -// GrepOptions describes how a grep should be performed. -type GrepOptions struct { - // Patterns are compiled Regexp objects to be matched. - Patterns []*regexp.Regexp - // InvertMatch selects non-matching lines. - InvertMatch bool - // CommitHash is the hash of the commit from which worktree should be derived. - CommitHash plumbing.Hash - // ReferenceName is the branch or tag name from which worktree should be derived. - ReferenceName plumbing.ReferenceName - // PathSpecs are compiled Regexp objects of pathspec to use in the matching. - PathSpecs []*regexp.Regexp -} - -var ( - ErrHashOrReference = errors.New("ambiguous options, only one of CommitHash or ReferenceName can be passed") -) - -// Validate validates the fields and sets the default values. -// -// TODO: deprecate in favor of Validate(r *Repository) in v6. -func (o *GrepOptions) Validate(w *Worktree) error { - return o.validate(w.r) -} - -func (o *GrepOptions) validate(r *Repository) error { - if !o.CommitHash.IsZero() && o.ReferenceName != "" { - return ErrHashOrReference - } - - // If none of CommitHash and ReferenceName are provided, set commit hash of - // the repository's head. - if o.CommitHash.IsZero() && o.ReferenceName == "" { - ref, err := r.Head() - if err != nil { - return err - } - o.CommitHash = ref.Hash() - } - - return nil -} - -// PlainOpenOptions describes how opening a plain repository should be -// performed. -type PlainOpenOptions struct { - // DetectDotGit defines whether parent directories should be - // walked until a .git directory or file is found. - DetectDotGit bool - // Enable .git/commondir support (see https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt). - // NOTE: This option will only work with the filesystem storage. - EnableDotGitCommonDir bool -} - -// Validate validates the fields and sets the default values. -func (o *PlainOpenOptions) Validate() error { return nil } - -type PlainInitOptions struct { - InitOptions - // Determines if the repository will have a worktree (non-bare) or not (bare). - Bare bool - ObjectFormat formatcfg.ObjectFormat -} - -// Validate validates the fields and sets the default values. -func (o *PlainInitOptions) Validate() error { return nil } - -var ( - ErrNoRestorePaths = errors.New("you must specify path(s) to restore") -) - -// RestoreOptions describes how a restore should be performed. -type RestoreOptions struct { - // Marks to restore the content in the index - Staged bool - // Marks to restore the content of the working tree - Worktree bool - // List of file paths that will be restored - Files []string -} - -// Validate validates the fields and sets the default values. -func (o *RestoreOptions) Validate() error { - if len(o.Files) == 0 { - return ErrNoRestorePaths - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/oss-fuzz.sh b/vendor/github.com/jesseduffield/go-git/v5/oss-fuzz.sh deleted file mode 100644 index 885548f401b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/oss-fuzz.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -eu -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ - - -go mod download -go get github.com/AdamKorcz/go-118-fuzz-build/testing - -if [ "$SANITIZER" != "coverage" ]; then - sed -i '/func (s \*DecoderSuite) TestDecode(/,/^}/ s/^/\/\//' plumbing/format/config/decoder_test.go - sed -n '35,$p' plumbing/format/packfile/common_test.go >> plumbing/format/packfile/delta_test.go - sed -n '20,53p' plumbing/object/object_test.go >> plumbing/object/tree_test.go - sed -i 's|func Test|// func Test|' plumbing/transport/common_test.go -fi - -compile_native_go_fuzzer $(pwd)/internal/revision FuzzParser fuzz_parser -compile_native_go_fuzzer $(pwd)/plumbing/format/config FuzzDecoder fuzz_decoder_config -compile_native_go_fuzzer $(pwd)/plumbing/format/packfile FuzzPatchDelta fuzz_patch_delta -compile_native_go_fuzzer $(pwd)/plumbing/object FuzzParseSignedBytes fuzz_parse_signed_bytes -compile_native_go_fuzzer $(pwd)/plumbing/object FuzzDecode fuzz_decode -compile_native_go_fuzzer $(pwd)/plumbing/protocol/packp FuzzDecoder fuzz_decoder_packp -compile_native_go_fuzzer $(pwd)/plumbing/transport FuzzNewEndpoint fuzz_new_endpoint diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/cache/buffer_lru.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/cache/buffer_lru.go deleted file mode 100644 index acaf1952033..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/cache/buffer_lru.go +++ /dev/null @@ -1,98 +0,0 @@ -package cache - -import ( - "container/list" - "sync" -) - -// BufferLRU implements an object cache with an LRU eviction policy and a -// maximum size (measured in object size). -type BufferLRU struct { - MaxSize FileSize - - actualSize FileSize - ll *list.List - cache map[int64]*list.Element - mut sync.Mutex -} - -// NewBufferLRU creates a new BufferLRU with the given maximum size. The maximum -// size will never be exceeded. -func NewBufferLRU(maxSize FileSize) *BufferLRU { - return &BufferLRU{MaxSize: maxSize} -} - -// NewBufferLRUDefault creates a new BufferLRU with the default cache size. -func NewBufferLRUDefault() *BufferLRU { - return &BufferLRU{MaxSize: DefaultMaxSize} -} - -type buffer struct { - Key int64 - Slice []byte -} - -// Put puts a buffer into the cache. If the buffer is already in the cache, it -// will be marked as used. Otherwise, it will be inserted. A buffers might -// be evicted to make room for the new one. -func (c *BufferLRU) Put(key int64, slice []byte) { - c.mut.Lock() - defer c.mut.Unlock() - - if c.cache == nil { - c.actualSize = 0 - c.cache = make(map[int64]*list.Element, 1000) - c.ll = list.New() - } - - bufSize := FileSize(len(slice)) - if ee, ok := c.cache[key]; ok { - oldBuf := ee.Value.(buffer) - // in this case bufSize is a delta: new size - old size - bufSize -= FileSize(len(oldBuf.Slice)) - c.ll.MoveToFront(ee) - ee.Value = buffer{key, slice} - } else { - if bufSize > c.MaxSize { - return - } - ee := c.ll.PushFront(buffer{key, slice}) - c.cache[key] = ee - } - - c.actualSize += bufSize - for c.actualSize > c.MaxSize { - last := c.ll.Back() - lastObj := last.Value.(buffer) - lastSize := FileSize(len(lastObj.Slice)) - - c.ll.Remove(last) - delete(c.cache, lastObj.Key) - c.actualSize -= lastSize - } -} - -// Get returns a buffer by its key. It marks the buffer as used. If the buffer -// is not in the cache, (nil, false) will be returned. -func (c *BufferLRU) Get(key int64) ([]byte, bool) { - c.mut.Lock() - defer c.mut.Unlock() - - ee, ok := c.cache[key] - if !ok { - return nil, false - } - - c.ll.MoveToFront(ee) - return ee.Value.(buffer).Slice, true -} - -// Clear the content of this buffer cache. -func (c *BufferLRU) Clear() { - c.mut.Lock() - defer c.mut.Unlock() - - c.ll = nil - c.cache = nil - c.actualSize = 0 -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/cache/common.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/cache/common.go deleted file mode 100644 index 7856df3d350..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/cache/common.go +++ /dev/null @@ -1,39 +0,0 @@ -package cache - -import "github.com/jesseduffield/go-git/v5/plumbing" - -const ( - Byte FileSize = 1 << (iota * 10) - KiByte - MiByte - GiByte -) - -type FileSize int64 - -const DefaultMaxSize FileSize = 96 * MiByte - -// Object is an interface to a object cache. -type Object interface { - // Put puts the given object into the cache. Whether this object will - // actually be put into the cache or not is implementation specific. - Put(o plumbing.EncodedObject) - // Get gets an object from the cache given its hash. The second return value - // is true if the object was returned, and false otherwise. - Get(k plumbing.Hash) (plumbing.EncodedObject, bool) - // Clear clears every object from the cache. - Clear() -} - -// Buffer is an interface to a buffer cache. -type Buffer interface { - // Put puts a buffer into the cache. If the buffer is already in the cache, - // it will be marked as used. Otherwise, it will be inserted. Buffer might - // be evicted to make room for the new one. - Put(key int64, slice []byte) - // Get returns a buffer by its key. It marks the buffer as used. If the - // buffer is not in the cache, (nil, false) will be returned. - Get(key int64) ([]byte, bool) - // Clear clears every object from the cache. - Clear() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/cache/object_lru.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/cache/object_lru.go deleted file mode 100644 index 75b2b72b083..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/cache/object_lru.go +++ /dev/null @@ -1,101 +0,0 @@ -package cache - -import ( - "container/list" - "sync" - - "github.com/jesseduffield/go-git/v5/plumbing" -) - -// ObjectLRU implements an object cache with an LRU eviction policy and a -// maximum size (measured in object size). -type ObjectLRU struct { - MaxSize FileSize - - actualSize FileSize - ll *list.List - cache map[interface{}]*list.Element - mut sync.Mutex -} - -// NewObjectLRU creates a new ObjectLRU with the given maximum size. The maximum -// size will never be exceeded. -func NewObjectLRU(maxSize FileSize) *ObjectLRU { - return &ObjectLRU{MaxSize: maxSize} -} - -// NewObjectLRUDefault creates a new ObjectLRU with the default cache size. -func NewObjectLRUDefault() *ObjectLRU { - return &ObjectLRU{MaxSize: DefaultMaxSize} -} - -// Put puts an object into the cache. If the object is already in the cache, it -// will be marked as used. Otherwise, it will be inserted. A single object might -// be evicted to make room for the new object. -func (c *ObjectLRU) Put(obj plumbing.EncodedObject) { - c.mut.Lock() - defer c.mut.Unlock() - - if c.cache == nil { - c.actualSize = 0 - c.cache = make(map[interface{}]*list.Element, 1000) - c.ll = list.New() - } - - objSize := FileSize(obj.Size()) - key := obj.Hash() - if ee, ok := c.cache[key]; ok { - oldObj := ee.Value.(plumbing.EncodedObject) - // in this case objSize is a delta: new size - old size - objSize -= FileSize(oldObj.Size()) - c.ll.MoveToFront(ee) - ee.Value = obj - } else { - if objSize > c.MaxSize { - return - } - ee := c.ll.PushFront(obj) - c.cache[key] = ee - } - - c.actualSize += objSize - for c.actualSize > c.MaxSize { - last := c.ll.Back() - if last == nil { - c.actualSize = 0 - break - } - - lastObj := last.Value.(plumbing.EncodedObject) - lastSize := FileSize(lastObj.Size()) - - c.ll.Remove(last) - delete(c.cache, lastObj.Hash()) - c.actualSize -= lastSize - } -} - -// Get returns an object by its hash. It marks the object as used. If the object -// is not in the cache, (nil, false) will be returned. -func (c *ObjectLRU) Get(k plumbing.Hash) (plumbing.EncodedObject, bool) { - c.mut.Lock() - defer c.mut.Unlock() - - ee, ok := c.cache[k] - if !ok { - return nil, false - } - - c.ll.MoveToFront(ee) - return ee.Value.(plumbing.EncodedObject), true -} - -// Clear the content of this object cache. -func (c *ObjectLRU) Clear() { - c.mut.Lock() - defer c.mut.Unlock() - - c.ll = nil - c.cache = nil - c.actualSize = 0 -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/color/color.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/color/color.go deleted file mode 100644 index 2cd74bdc1a8..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/color/color.go +++ /dev/null @@ -1,38 +0,0 @@ -package color - -// TODO read colors from a github.com/go-git/go-git/plumbing/format/config.Config struct -// TODO implement color parsing, see https://github.com/git/git/blob/v2.26.2/color.c - -// Colors. See https://github.com/git/git/blob/v2.26.2/color.h#L24-L53. -const ( - Normal = "" - Reset = "\033[m" - Bold = "\033[1m" - Red = "\033[31m" - Green = "\033[32m" - Yellow = "\033[33m" - Blue = "\033[34m" - Magenta = "\033[35m" - Cyan = "\033[36m" - BoldRed = "\033[1;31m" - BoldGreen = "\033[1;32m" - BoldYellow = "\033[1;33m" - BoldBlue = "\033[1;34m" - BoldMagenta = "\033[1;35m" - BoldCyan = "\033[1;36m" - FaintRed = "\033[2;31m" - FaintGreen = "\033[2;32m" - FaintYellow = "\033[2;33m" - FaintBlue = "\033[2;34m" - FaintMagenta = "\033[2;35m" - FaintCyan = "\033[2;36m" - BgRed = "\033[41m" - BgGreen = "\033[42m" - BgYellow = "\033[43m" - BgBlue = "\033[44m" - BgMagenta = "\033[45m" - BgCyan = "\033[46m" - Faint = "\033[2m" - FaintItalic = "\033[2;3m" - Reverse = "\033[7m" -) diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/error.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/error.go deleted file mode 100644 index a3ebed3f6c2..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/error.go +++ /dev/null @@ -1,35 +0,0 @@ -package plumbing - -import "fmt" - -type PermanentError struct { - Err error -} - -func NewPermanentError(err error) *PermanentError { - if err == nil { - return nil - } - - return &PermanentError{Err: err} -} - -func (e *PermanentError) Error() string { - return fmt.Sprintf("permanent client error: %s", e.Err.Error()) -} - -type UnexpectedError struct { - Err error -} - -func NewUnexpectedError(err error) *UnexpectedError { - if err == nil { - return nil - } - - return &UnexpectedError{Err: err} -} - -func (e *UnexpectedError) Error() string { - return fmt.Sprintf("unexpected client error: %s", e.Err.Error()) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/filemode/filemode.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/filemode/filemode.go deleted file mode 100644 index ea1a457558e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/filemode/filemode.go +++ /dev/null @@ -1,188 +0,0 @@ -package filemode - -import ( - "encoding/binary" - "fmt" - "os" - "strconv" -) - -// A FileMode represents the kind of tree entries used by git. It -// resembles regular file systems modes, although FileModes are -// considerably simpler (there are not so many), and there are some, -// like Submodule that has no file system equivalent. -type FileMode uint32 - -const ( - // Empty is used as the FileMode of tree elements when comparing - // trees in the following situations: - // - // - the mode of tree elements before their creation. - the mode of - // tree elements after their deletion. - the mode of unmerged - // elements when checking the index. - // - // Empty has no file system equivalent. As Empty is the zero value - // of FileMode, it is also returned by New and - // NewFromOsNewFromOSFileMode along with an error, when they fail. - Empty FileMode = 0 - // Dir represent a Directory. - Dir FileMode = 0040000 - // Regular represent non-executable files. Please note this is not - // the same as golang regular files, which include executable files. - Regular FileMode = 0100644 - // Deprecated represent non-executable files with the group writable - // bit set. This mode was supported by the first versions of git, - // but it has been deprecated nowadays. This library uses them - // internally, so you can read old packfiles, but will treat them as - // Regulars when interfacing with the outside world. This is the - // standard git behaviour. - Deprecated FileMode = 0100664 - // Executable represents executable files. - Executable FileMode = 0100755 - // Symlink represents symbolic links to files. - Symlink FileMode = 0120000 - // Submodule represents git submodules. This mode has no file system - // equivalent. - Submodule FileMode = 0160000 -) - -// New takes the octal string representation of a FileMode and returns -// the FileMode and a nil error. If the string can not be parsed to a -// 32 bit unsigned octal number, it returns Empty and the parsing error. -// -// Example: "40000" means Dir, "100644" means Regular. -// -// Please note this function does not check if the returned FileMode -// is valid in git or if it is malformed. For instance, "1" will -// return the malformed FileMode(1) and a nil error. -func New(s string) (FileMode, error) { - n, err := strconv.ParseUint(s, 8, 32) - if err != nil { - return Empty, err - } - - return FileMode(n), nil -} - -// NewFromOSFileMode returns the FileMode used by git to represent -// the provided file system modes and a nil error on success. If the -// file system mode cannot be mapped to any valid git mode (as with -// sockets or named pipes), it will return Empty and an error. -// -// Note that some git modes cannot be generated from os.FileModes, like -// Deprecated and Submodule; while Empty will be returned, along with an -// error, only when the method fails. -func NewFromOSFileMode(m os.FileMode) (FileMode, error) { - if m.IsRegular() { - if isSetTemporary(m) { - return Empty, fmt.Errorf("no equivalent git mode for %s", m) - } - if isSetCharDevice(m) { - return Empty, fmt.Errorf("no equivalent git mode for %s", m) - } - if isSetUserExecutable(m) { - return Executable, nil - } - return Regular, nil - } - - if m.IsDir() { - return Dir, nil - } - - if isSetSymLink(m) { - return Symlink, nil - } - - return Empty, fmt.Errorf("no equivalent git mode for %s", m) -} - -func isSetCharDevice(m os.FileMode) bool { - return m&os.ModeCharDevice != 0 -} - -func isSetTemporary(m os.FileMode) bool { - return m&os.ModeTemporary != 0 -} - -func isSetUserExecutable(m os.FileMode) bool { - return m&0100 != 0 -} - -func isSetSymLink(m os.FileMode) bool { - return m&os.ModeSymlink != 0 -} - -// Bytes return a slice of 4 bytes with the mode in little endian -// encoding. -func (m FileMode) Bytes() []byte { - ret := make([]byte, 4) - binary.LittleEndian.PutUint32(ret, uint32(m)) - return ret -} - -// IsMalformed returns if the FileMode should not appear in a git packfile, -// this is: Empty and any other mode not mentioned as a constant in this -// package. -func (m FileMode) IsMalformed() bool { - return m != Dir && - m != Regular && - m != Deprecated && - m != Executable && - m != Symlink && - m != Submodule -} - -// String returns the FileMode as a string in the standard git format, -// this is, an octal number padded with ceros to 7 digits. Malformed -// modes are printed in that same format, for easier debugging. -// -// Example: Regular is "0100644", Empty is "0000000". -func (m FileMode) String() string { - return fmt.Sprintf("%07o", uint32(m)) -} - -// IsRegular returns if the FileMode represents that of a regular file, -// this is, either Regular or Deprecated. Please note that Executable -// are not regular even though in the UNIX tradition, they usually are: -// See the IsFile method. -func (m FileMode) IsRegular() bool { - return m == Regular || - m == Deprecated -} - -// IsFile returns if the FileMode represents that of a file, this is, -// Regular, Deprecated, Executable or Link. -func (m FileMode) IsFile() bool { - return m == Regular || - m == Deprecated || - m == Executable || - m == Symlink -} - -// ToOSFileMode returns the os.FileMode to be used when creating file -// system elements with the given git mode and a nil error on success. -// -// When the provided mode cannot be mapped to a valid file system mode -// (e.g. Submodule) it returns os.FileMode(0) and an error. -// -// The returned file mode does not take into account the umask. -func (m FileMode) ToOSFileMode() (os.FileMode, error) { - switch m { - case Dir: - return os.ModePerm | os.ModeDir, nil - case Submodule: - return os.ModePerm | os.ModeDir, nil - case Regular: - return os.FileMode(0644), nil - // Deprecated is no longer allowed: treated as a Regular instead - case Deprecated: - return os.FileMode(0644), nil - case Executable: - return os.FileMode(0755), nil - case Symlink: - return os.ModePerm | os.ModeSymlink, nil - } - - return os.FileMode(0), fmt.Errorf("malformed mode (%s)", m) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/common.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/common.go deleted file mode 100644 index 6d689ea1e01..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/common.go +++ /dev/null @@ -1,109 +0,0 @@ -package config - -// New creates a new config instance. -func New() *Config { - return &Config{} -} - -// Config contains all the sections, comments and includes from a config file. -type Config struct { - Comment *Comment - Sections Sections - Includes Includes -} - -// Includes is a list of Includes in a config file. -type Includes []*Include - -// Include is a reference to an included config file. -type Include struct { - Path string - Config *Config -} - -// Comment string without the prefix '#' or ';'. -type Comment string - -const ( - // NoSubsection token is passed to Config.Section and Config.SetSection to - // represent the absence of a section. - NoSubsection = "" -) - -// Section returns a existing section with the given name or creates a new one. -func (c *Config) Section(name string) *Section { - for i := len(c.Sections) - 1; i >= 0; i-- { - s := c.Sections[i] - if s.IsName(name) { - return s - } - } - - s := &Section{Name: name} - c.Sections = append(c.Sections, s) - return s -} - -// HasSection checks if the Config has a section with the specified name. -func (c *Config) HasSection(name string) bool { - for _, s := range c.Sections { - if s.IsName(name) { - return true - } - } - return false -} - -// RemoveSection removes a section from a config file. -func (c *Config) RemoveSection(name string) *Config { - result := Sections{} - for _, s := range c.Sections { - if !s.IsName(name) { - result = append(result, s) - } - } - - c.Sections = result - return c -} - -// RemoveSubsection remove a subsection from a config file. -func (c *Config) RemoveSubsection(section string, subsection string) *Config { - for _, s := range c.Sections { - if s.IsName(section) { - result := Subsections{} - for _, ss := range s.Subsections { - if !ss.IsName(subsection) { - result = append(result, ss) - } - } - s.Subsections = result - } - } - - return c -} - -// AddOption adds an option to a given section and subsection. Use the -// NoSubsection constant for the subsection argument if no subsection is wanted. -func (c *Config) AddOption(section string, subsection string, key string, value string) *Config { - if subsection == "" { - c.Section(section).AddOption(key, value) - } else { - c.Section(section).Subsection(subsection).AddOption(key, value) - } - - return c -} - -// SetOption sets an option to a given section and subsection. Use the -// NoSubsection constant for the subsection argument if no subsection is wanted. -func (c *Config) SetOption(section string, subsection string, key string, value string) *Config { - if subsection == "" { - c.Section(section).SetOption(key, value) - } else { - c.Section(section).Subsection(subsection).SetOption(key, value) - } - - return c -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/decoder.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/decoder.go deleted file mode 100644 index 8e52d57f302..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/decoder.go +++ /dev/null @@ -1,37 +0,0 @@ -package config - -import ( - "io" - - "github.com/go-git/gcfg" -) - -// A Decoder reads and decodes config files from an input stream. -type Decoder struct { - io.Reader -} - -// NewDecoder returns a new decoder that reads from r. -func NewDecoder(r io.Reader) *Decoder { - return &Decoder{r} -} - -// Decode reads the whole config from its input and stores it in the -// value pointed to by config. -func (d *Decoder) Decode(config *Config) error { - cb := func(s string, ss string, k string, v string, bv bool) error { - if ss == "" && k == "" { - config.Section(s) - return nil - } - - if ss != "" && k == "" { - config.Section(s).Subsection(ss) - return nil - } - - config.AddOption(s, ss, k, v) - return nil - } - return gcfg.ReadWithCallback(d, cb) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/doc.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/doc.go deleted file mode 100644 index 3986c836581..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/doc.go +++ /dev/null @@ -1,122 +0,0 @@ -// Package config implements encoding and decoding of git config files. -// -// Configuration File -// ------------------ -// -// The Git configuration file contains a number of variables that affect -// the Git commands' behavior. The `.git/config` file in each repository -// is used to store the configuration for that repository, and -// `$HOME/.gitconfig` is used to store a per-user configuration as -// fallback values for the `.git/config` file. The file `/etc/gitconfig` -// can be used to store a system-wide default configuration. -// -// The configuration variables are used by both the Git plumbing -// and the porcelains. The variables are divided into sections, wherein -// the fully qualified variable name of the variable itself is the last -// dot-separated segment and the section name is everything before the last -// dot. The variable names are case-insensitive, allow only alphanumeric -// characters and `-`, and must start with an alphabetic character. Some -// variables may appear multiple times; we say then that the variable is -// multivalued. -// -// Syntax -// ~~~~~~ -// -// The syntax is fairly flexible and permissive; whitespaces are mostly -// ignored. The '#' and ';' characters begin comments to the end of line, -// blank lines are ignored. -// -// The file consists of sections and variables. A section begins with -// the name of the section in square brackets and continues until the next -// section begins. Section names are case-insensitive. Only alphanumeric -// characters, `-` and `.` are allowed in section names. Each variable -// must belong to some section, which means that there must be a section -// header before the first setting of a variable. -// -// Sections can be further divided into subsections. To begin a subsection -// put its name in double quotes, separated by space from the section name, -// in the section header, like in the example below: -// -// -------- -// [section "subsection"] -// -// -------- -// -// Subsection names are case sensitive and can contain any characters except -// newline (doublequote `"` and backslash can be included by escaping them -// as `\"` and `\\`, respectively). Section headers cannot span multiple -// lines. Variables may belong directly to a section or to a given subsection. -// You can have `[section]` if you have `[section "subsection"]`, but you -// don't need to. -// -// There is also a deprecated `[section.subsection]` syntax. With this -// syntax, the subsection name is converted to lower-case and is also -// compared case sensitively. These subsection names follow the same -// restrictions as section names. -// -// All the other lines (and the remainder of the line after the section -// header) are recognized as setting variables, in the form -// 'name = value' (or just 'name', which is a short-hand to say that -// the variable is the boolean "true"). -// The variable names are case-insensitive, allow only alphanumeric characters -// and `-`, and must start with an alphabetic character. -// -// A line that defines a value can be continued to the next line by -// ending it with a `\`; the backquote and the end-of-line are -// stripped. Leading whitespaces after 'name =', the remainder of the -// line after the first comment character '#' or ';', and trailing -// whitespaces of the line are discarded unless they are enclosed in -// double quotes. Internal whitespaces within the value are retained -// verbatim. -// -// Inside double quotes, double quote `"` and backslash `\` characters -// must be escaped: use `\"` for `"` and `\\` for `\`. -// -// The following escape sequences (beside `\"` and `\\`) are recognized: -// `\n` for newline character (NL), `\t` for horizontal tabulation (HT, TAB) -// and `\b` for backspace (BS). Other char escape sequences (including octal -// escape sequences) are invalid. -// -// Includes -// ~~~~~~~~ -// -// You can include one config file from another by setting the special -// `include.path` variable to the name of the file to be included. The -// variable takes a pathname as its value, and is subject to tilde -// expansion. -// -// The included file is expanded immediately, as if its contents had been -// found at the location of the include directive. If the value of the -// `include.path` variable is a relative path, the path is considered to be -// relative to the configuration file in which the include directive was -// found. See below for examples. -// -// -// Example -// ~~~~~~~ -// -// # Core variables -// [core] -// ; Don't trust file modes -// filemode = false -// -// # Our diff algorithm -// [diff] -// external = /usr/local/bin/diff-wrapper -// renames = true -// -// [branch "devel"] -// remote = origin -// merge = refs/heads/devel -// -// # Proxy settings -// [core] -// gitProxy="ssh" for "kernel.org" -// gitProxy=default-proxy ; for the rest -// -// [include] -// path = /path/to/foo.inc ; include by absolute path -// path = foo ; expand "foo" relative to the current file -// path = ~/foo ; expand "foo" in your `$HOME` directory -// -package config diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/encoder.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/encoder.go deleted file mode 100644 index de069aed5e7..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/encoder.go +++ /dev/null @@ -1,82 +0,0 @@ -package config - -import ( - "fmt" - "io" - "strings" -) - -// An Encoder writes config files to an output stream. -type Encoder struct { - w io.Writer -} - -var ( - subsectionReplacer = strings.NewReplacer(`"`, `\"`, `\`, `\\`) - valueReplacer = strings.NewReplacer(`"`, `\"`, `\`, `\\`, "\n", `\n`, "\t", `\t`, "\b", `\b`) -) -// NewEncoder returns a new encoder that writes to w. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{w} -} - -// Encode writes the config in git config format to the stream of the encoder. -func (e *Encoder) Encode(cfg *Config) error { - for _, s := range cfg.Sections { - if err := e.encodeSection(s); err != nil { - return err - } - } - - return nil -} - -func (e *Encoder) encodeSection(s *Section) error { - if len(s.Options) > 0 { - if err := e.printf("[%s]\n", s.Name); err != nil { - return err - } - - if err := e.encodeOptions(s.Options); err != nil { - return err - } - } - - for _, ss := range s.Subsections { - if err := e.encodeSubsection(s.Name, ss); err != nil { - return err - } - } - - return nil -} - -func (e *Encoder) encodeSubsection(sectionName string, s *Subsection) error { - if err := e.printf("[%s \"%s\"]\n", sectionName, subsectionReplacer.Replace(s.Name)); err != nil { - return err - } - - return e.encodeOptions(s.Options) -} - -func (e *Encoder) encodeOptions(opts Options) error { - for _, o := range opts { - var value string - if strings.ContainsAny(o.Value, "#;\"\t\n\\") || strings.HasPrefix(o.Value, " ") || strings.HasSuffix(o.Value, " ") { - value = `"`+valueReplacer.Replace(o.Value)+`"` - } else { - value = o.Value - } - - if err := e.printf("\t%s = %s\n", o.Key, value); err != nil { - return err - } - } - - return nil -} - -func (e *Encoder) printf(msg string, args ...interface{}) error { - _, err := fmt.Fprintf(e.w, msg, args...) - return err -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/format.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/format.go deleted file mode 100644 index 4873ea9258c..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/format.go +++ /dev/null @@ -1,53 +0,0 @@ -package config - -// RepositoryFormatVersion represents the repository format version, -// as per defined at: -// -// https://git-scm.com/docs/repository-version -type RepositoryFormatVersion string - -const ( - // Version_0 is the format defined by the initial version of git, - // including but not limited to the format of the repository - // directory, the repository configuration file, and the object - // and ref storage. - // - // Specifying the complete behavior of git is beyond the scope - // of this document. - Version_0 = "0" - - // Version_1 is identical to version 0, with the following exceptions: - // - // 1. When reading the core.repositoryformatversion variable, a git - // implementation which supports version 1 MUST also read any - // configuration keys found in the extensions section of the - // configuration file. - // - // 2. If a version-1 repository specifies any extensions.* keys that - // the running git has not implemented, the operation MUST NOT proceed. - // Similarly, if the value of any known key is not understood by the - // implementation, the operation MUST NOT proceed. - // - // Note that if no extensions are specified in the config file, then - // core.repositoryformatversion SHOULD be set to 0 (setting it to 1 provides - // no benefit, and makes the repository incompatible with older - // implementations of git). - Version_1 = "1" - - // DefaultRepositoryFormatVersion holds the default repository format version. - DefaultRepositoryFormatVersion = Version_0 -) - -// ObjectFormat defines the object format. -type ObjectFormat string - -const ( - // SHA1 represents the object format used for SHA1. - SHA1 ObjectFormat = "sha1" - - // SHA256 represents the object format used for SHA256. - SHA256 ObjectFormat = "sha256" - - // DefaultObjectFormat holds the default object format. - DefaultObjectFormat = SHA1 -) diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/option.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/option.go deleted file mode 100644 index cad394810a1..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/option.go +++ /dev/null @@ -1,127 +0,0 @@ -package config - -import ( - "fmt" - "strings" -) - -// Option defines a key/value entity in a config file. -type Option struct { - // Key preserving original caseness. - // Use IsKey instead to compare key regardless of caseness. - Key string - // Original value as string, could be not normalized. - Value string -} - -type Options []*Option - -// IsKey returns true if the given key matches -// this option's key in a case-insensitive comparison. -func (o *Option) IsKey(key string) bool { - return strings.EqualFold(o.Key, key) -} - -func (opts Options) GoString() string { - var strs []string - for _, opt := range opts { - strs = append(strs, fmt.Sprintf("%#v", opt)) - } - - return strings.Join(strs, ", ") -} - -// Get gets the value for the given key if set, -// otherwise it returns the empty string. -// -// Note that there is no difference -// -// This matches git behaviour since git v1.8.1-rc1, -// if there are multiple definitions of a key, the -// last one wins. -// -// See: http://article.gmane.org/gmane.linux.kernel/1407184 -// -// In order to get all possible values for the same key, -// use GetAll. -func (opts Options) Get(key string) string { - for i := len(opts) - 1; i >= 0; i-- { - o := opts[i] - if o.IsKey(key) { - return o.Value - } - } - return "" -} - -// Has checks if an Option exist with the given key. -func (opts Options) Has(key string) bool { - for _, o := range opts { - if o.IsKey(key) { - return true - } - } - return false -} - -// GetAll returns all possible values for the same key. -func (opts Options) GetAll(key string) []string { - result := []string{} - for _, o := range opts { - if o.IsKey(key) { - result = append(result, o.Value) - } - } - return result -} - -func (opts Options) withoutOption(key string) Options { - result := Options{} - for _, o := range opts { - if !o.IsKey(key) { - result = append(result, o) - } - } - return result -} - -func (opts Options) withAddedOption(key string, value string) Options { - return append(opts, &Option{key, value}) -} - -func (opts Options) withSettedOption(key string, values ...string) Options { - var result Options - var added []string - for _, o := range opts { - if !o.IsKey(key) { - result = append(result, o) - continue - } - - if contains(values, o.Value) { - added = append(added, o.Value) - result = append(result, o) - continue - } - } - - for _, value := range values { - if contains(added, value) { - continue - } - - result = result.withAddedOption(key, value) - } - - return result -} - -func contains(haystack []string, needle string) bool { - for _, s := range haystack { - if s == needle { - return true - } - } - - return false -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/section.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/section.go deleted file mode 100644 index 4625ac5837e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/config/section.go +++ /dev/null @@ -1,181 +0,0 @@ -package config - -import ( - "fmt" - "strings" -) - -// Section is the representation of a section inside git configuration files. -// Each Section contains Options that are used by both the Git plumbing -// and the porcelains. -// Sections can be further divided into subsections. To begin a subsection -// put its name in double quotes, separated by space from the section name, -// in the section header, like in the example below: -// -// [section "subsection"] -// -// All the other lines (and the remainder of the line after the section header) -// are recognized as option variables, in the form "name = value" (or just name, -// which is a short-hand to say that the variable is the boolean "true"). -// The variable names are case-insensitive, allow only alphanumeric characters -// and -, and must start with an alphabetic character: -// -// [section "subsection1"] -// option1 = value1 -// option2 -// [section "subsection2"] -// option3 = value2 -// -type Section struct { - Name string - Options Options - Subsections Subsections -} - -type Subsection struct { - Name string - Options Options -} - -type Sections []*Section - -func (s Sections) GoString() string { - var strs []string - for _, ss := range s { - strs = append(strs, fmt.Sprintf("%#v", ss)) - } - - return strings.Join(strs, ", ") -} - -type Subsections []*Subsection - -func (s Subsections) GoString() string { - var strs []string - for _, ss := range s { - strs = append(strs, fmt.Sprintf("%#v", ss)) - } - - return strings.Join(strs, ", ") -} - -// IsName checks if the name provided is equals to the Section name, case insensitive. -func (s *Section) IsName(name string) bool { - return strings.EqualFold(s.Name, name) -} - -// Subsection returns a Subsection from the specified Section. If the -// Subsection does not exists, new one is created and added to Section. -func (s *Section) Subsection(name string) *Subsection { - for i := len(s.Subsections) - 1; i >= 0; i-- { - ss := s.Subsections[i] - if ss.IsName(name) { - return ss - } - } - - ss := &Subsection{Name: name} - s.Subsections = append(s.Subsections, ss) - return ss -} - -// HasSubsection checks if the Section has a Subsection with the specified name. -func (s *Section) HasSubsection(name string) bool { - for _, ss := range s.Subsections { - if ss.IsName(name) { - return true - } - } - - return false -} - -// RemoveSubsection removes a subsection from a Section. -func (s *Section) RemoveSubsection(name string) *Section { - result := Subsections{} - for _, s := range s.Subsections { - if !s.IsName(name) { - result = append(result, s) - } - } - - s.Subsections = result - return s -} - -// Option returns the value for the specified key. Empty string is returned if -// key does not exists. -func (s *Section) Option(key string) string { - return s.Options.Get(key) -} - -// OptionAll returns all possible values for an option with the specified key. -// If the option does not exists, an empty slice will be returned. -func (s *Section) OptionAll(key string) []string { - return s.Options.GetAll(key) -} - -// HasOption checks if the Section has an Option with the given key. -func (s *Section) HasOption(key string) bool { - return s.Options.Has(key) -} - -// AddOption adds a new Option to the Section. The updated Section is returned. -func (s *Section) AddOption(key string, value string) *Section { - s.Options = s.Options.withAddedOption(key, value) - return s -} - -// SetOption adds a new Option to the Section. If the option already exists, is replaced. -// The updated Section is returned. -func (s *Section) SetOption(key string, value string) *Section { - s.Options = s.Options.withSettedOption(key, value) - return s -} - -// Remove an option with the specified key. The updated Section is returned. -func (s *Section) RemoveOption(key string) *Section { - s.Options = s.Options.withoutOption(key) - return s -} - -// IsName checks if the name of the subsection is exactly the specified name. -func (s *Subsection) IsName(name string) bool { - return s.Name == name -} - -// Option returns an option with the specified key. If the option does not exists, -// empty spring will be returned. -func (s *Subsection) Option(key string) string { - return s.Options.Get(key) -} - -// OptionAll returns all possible values for an option with the specified key. -// If the option does not exists, an empty slice will be returned. -func (s *Subsection) OptionAll(key string) []string { - return s.Options.GetAll(key) -} - -// HasOption checks if the Subsection has an Option with the given key. -func (s *Subsection) HasOption(key string) bool { - return s.Options.Has(key) -} - -// AddOption adds a new Option to the Subsection. The updated Subsection is returned. -func (s *Subsection) AddOption(key string, value string) *Subsection { - s.Options = s.Options.withAddedOption(key, value) - return s -} - -// SetOption adds a new Option to the Subsection. If the option already exists, is replaced. -// The updated Subsection is returned. -func (s *Subsection) SetOption(key string, value ...string) *Subsection { - s.Options = s.Options.withSettedOption(key, value...) - return s -} - -// RemoveOption removes the option with the specified key. The updated Subsection is returned. -func (s *Subsection) RemoveOption(key string) *Subsection { - s.Options = s.Options.withoutOption(key) - return s -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/diff/colorconfig.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/diff/colorconfig.go deleted file mode 100644 index 212401be7b0..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/diff/colorconfig.go +++ /dev/null @@ -1,97 +0,0 @@ -package diff - -import "github.com/jesseduffield/go-git/v5/plumbing/color" - -// A ColorKey is a key into a ColorConfig map and also equal to the key in the -// diff.color subsection of the config. See -// https://github.com/git/git/blob/v2.26.2/diff.c#L83-L106. -type ColorKey string - -// ColorKeys. -const ( - Context ColorKey = "context" - Meta ColorKey = "meta" - Frag ColorKey = "frag" - Old ColorKey = "old" - New ColorKey = "new" - Commit ColorKey = "commit" - Whitespace ColorKey = "whitespace" - Func ColorKey = "func" - OldMoved ColorKey = "oldMoved" - OldMovedAlternative ColorKey = "oldMovedAlternative" - OldMovedDimmed ColorKey = "oldMovedDimmed" - OldMovedAlternativeDimmed ColorKey = "oldMovedAlternativeDimmed" - NewMoved ColorKey = "newMoved" - NewMovedAlternative ColorKey = "newMovedAlternative" - NewMovedDimmed ColorKey = "newMovedDimmed" - NewMovedAlternativeDimmed ColorKey = "newMovedAlternativeDimmed" - ContextDimmed ColorKey = "contextDimmed" - OldDimmed ColorKey = "oldDimmed" - NewDimmed ColorKey = "newDimmed" - ContextBold ColorKey = "contextBold" - OldBold ColorKey = "oldBold" - NewBold ColorKey = "newBold" -) - -// A ColorConfig is a color configuration. A nil or empty ColorConfig -// corresponds to no color. -type ColorConfig map[ColorKey]string - -// A ColorConfigOption sets an option on a ColorConfig. -type ColorConfigOption func(ColorConfig) - -// WithColor sets the color for key. -func WithColor(key ColorKey, color string) ColorConfigOption { - return func(cc ColorConfig) { - cc[key] = color - } -} - -// defaultColorConfig is the default color configuration. See -// https://github.com/git/git/blob/v2.26.2/diff.c#L57-L81. -var defaultColorConfig = ColorConfig{ - Context: color.Normal, - Meta: color.Bold, - Frag: color.Cyan, - Old: color.Red, - New: color.Green, - Commit: color.Yellow, - Whitespace: color.BgRed, - Func: color.Normal, - OldMoved: color.BoldMagenta, - OldMovedAlternative: color.BoldBlue, - OldMovedDimmed: color.Faint, - OldMovedAlternativeDimmed: color.FaintItalic, - NewMoved: color.BoldCyan, - NewMovedAlternative: color.BoldYellow, - NewMovedDimmed: color.Faint, - NewMovedAlternativeDimmed: color.FaintItalic, - ContextDimmed: color.Faint, - OldDimmed: color.FaintRed, - NewDimmed: color.FaintGreen, - ContextBold: color.Bold, - OldBold: color.BoldRed, - NewBold: color.BoldGreen, -} - -// NewColorConfig returns a new ColorConfig. -func NewColorConfig(options ...ColorConfigOption) ColorConfig { - cc := make(ColorConfig) - for key, value := range defaultColorConfig { - cc[key] = value - } - for _, option := range options { - option(cc) - } - return cc -} - -// Reset returns the ANSI escape sequence to reset the color with key set from -// cc. If no color was set then no reset is needed so it returns the empty -// string. -func (cc ColorConfig) Reset(key ColorKey) string { - if cc[key] == "" { - return "" - } - return color.Reset -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/diff/patch.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/diff/patch.go deleted file mode 100644 index 330f5dc1f3b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/diff/patch.go +++ /dev/null @@ -1,58 +0,0 @@ -package diff - -import ( - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" -) - -// Operation defines the operation of a diff item. -type Operation int - -const ( - // Equal item represents an equals diff. - Equal Operation = iota - // Add item represents an insert diff. - Add - // Delete item represents a delete diff. - Delete -) - -// Patch represents a collection of steps to transform several files. -type Patch interface { - // FilePatches returns a slice of patches per file. - FilePatches() []FilePatch - // Message returns an optional message that can be at the top of the - // Patch representation. - Message() string -} - -// FilePatch represents the necessary steps to transform one file into another. -type FilePatch interface { - // IsBinary returns true if this patch is representing a binary file. - IsBinary() bool - // Files returns the from and to Files, with all the necessary metadata - // about them. If the patch creates a new file, "from" will be nil. - // If the patch deletes a file, "to" will be nil. - Files() (from, to File) - // Chunks returns a slice of ordered changes to transform "from" File into - // "to" File. If the file is a binary one, Chunks will be empty. - Chunks() []Chunk -} - -// File contains all the file metadata necessary to print some patch formats. -type File interface { - // Hash returns the File Hash. - Hash() plumbing.Hash - // Mode returns the FileMode. - Mode() filemode.FileMode - // Path returns the complete Path to the file, including the filename. - Path() string -} - -// Chunk represents a portion of a file transformation into another. -type Chunk interface { - // Content contains the portion of the file. - Content() string - // Type contains the Operation to do with this Chunk. - Type() Operation -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/diff/unified_encoder.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/diff/unified_encoder.go deleted file mode 100644 index 7c811c0786b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/diff/unified_encoder.go +++ /dev/null @@ -1,395 +0,0 @@ -package diff - -import ( - "fmt" - "io" - "regexp" - "strconv" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing" -) - -// DefaultContextLines is the default number of context lines. -const DefaultContextLines = 3 - -var ( - splitLinesRegexp = regexp.MustCompile(`[^\n]*(\n|$)`) - - operationChar = map[Operation]byte{ - Add: '+', - Delete: '-', - Equal: ' ', - } - - operationColorKey = map[Operation]ColorKey{ - Add: New, - Delete: Old, - Equal: Context, - } -) - -// UnifiedEncoder encodes an unified diff into the provided Writer. It does not -// support similarity index for renames or sorting hash representations. -type UnifiedEncoder struct { - io.Writer - - // contextLines is the count of unchanged lines that will appear surrounding - // a change. - contextLines int - - // srcPrefix and dstPrefix are prepended to file paths when encoding a diff. - srcPrefix string - dstPrefix string - - // colorConfig is the color configuration. The default is no color. - color ColorConfig -} - -// NewUnifiedEncoder returns a new UnifiedEncoder that writes to w. -func NewUnifiedEncoder(w io.Writer, contextLines int) *UnifiedEncoder { - return &UnifiedEncoder{ - Writer: w, - srcPrefix: "a/", - dstPrefix: "b/", - contextLines: contextLines, - } -} - -// SetColor sets e's color configuration and returns e. -func (e *UnifiedEncoder) SetColor(colorConfig ColorConfig) *UnifiedEncoder { - e.color = colorConfig - return e -} - -// SetSrcPrefix sets e's srcPrefix and returns e. -func (e *UnifiedEncoder) SetSrcPrefix(prefix string) *UnifiedEncoder { - e.srcPrefix = prefix - return e -} - -// SetDstPrefix sets e's dstPrefix and returns e. -func (e *UnifiedEncoder) SetDstPrefix(prefix string) *UnifiedEncoder { - e.dstPrefix = prefix - return e -} - -// Encode encodes patch. -func (e *UnifiedEncoder) Encode(patch Patch) error { - sb := &strings.Builder{} - - if message := patch.Message(); message != "" { - sb.WriteString(message) - if !strings.HasSuffix(message, "\n") { - sb.WriteByte('\n') - } - } - - for _, filePatch := range patch.FilePatches() { - e.writeFilePatchHeader(sb, filePatch) - g := newHunksGenerator(filePatch.Chunks(), e.contextLines) - for _, hunk := range g.Generate() { - hunk.writeTo(sb, e.color) - } - } - - _, err := e.Write([]byte(sb.String())) - return err -} - -func (e *UnifiedEncoder) writeFilePatchHeader(sb *strings.Builder, filePatch FilePatch) { - from, to := filePatch.Files() - if from == nil && to == nil { - return - } - isBinary := filePatch.IsBinary() - - var lines []string - switch { - case from != nil && to != nil: - hashEquals := from.Hash() == to.Hash() - lines = append(lines, - fmt.Sprintf("diff --git %s%s %s%s", - e.srcPrefix, from.Path(), e.dstPrefix, to.Path()), - ) - if from.Mode() != to.Mode() { - lines = append(lines, - fmt.Sprintf("old mode %o", from.Mode()), - fmt.Sprintf("new mode %o", to.Mode()), - ) - } - if from.Path() != to.Path() { - lines = append(lines, - fmt.Sprintf("rename from %s", from.Path()), - fmt.Sprintf("rename to %s", to.Path()), - ) - } - if from.Mode() != to.Mode() && !hashEquals { - lines = append(lines, - fmt.Sprintf("index %s..%s", from.Hash(), to.Hash()), - ) - } else if !hashEquals { - lines = append(lines, - fmt.Sprintf("index %s..%s %o", from.Hash(), to.Hash(), from.Mode()), - ) - } - if !hashEquals { - lines = e.appendPathLines(lines, e.srcPrefix+from.Path(), e.dstPrefix+to.Path(), isBinary) - } - case from == nil: - lines = append(lines, - fmt.Sprintf("diff --git %s %s", e.srcPrefix+to.Path(), e.dstPrefix+to.Path()), - fmt.Sprintf("new file mode %o", to.Mode()), - fmt.Sprintf("index %s..%s", plumbing.ZeroHash, to.Hash()), - ) - lines = e.appendPathLines(lines, "/dev/null", e.dstPrefix+to.Path(), isBinary) - case to == nil: - lines = append(lines, - fmt.Sprintf("diff --git %s %s", e.srcPrefix+from.Path(), e.dstPrefix+from.Path()), - fmt.Sprintf("deleted file mode %o", from.Mode()), - fmt.Sprintf("index %s..%s", from.Hash(), plumbing.ZeroHash), - ) - lines = e.appendPathLines(lines, e.srcPrefix+from.Path(), "/dev/null", isBinary) - } - - sb.WriteString(e.color[Meta]) - sb.WriteString(lines[0]) - for _, line := range lines[1:] { - sb.WriteByte('\n') - sb.WriteString(line) - } - sb.WriteString(e.color.Reset(Meta)) - sb.WriteByte('\n') -} - -func (e *UnifiedEncoder) appendPathLines(lines []string, fromPath, toPath string, isBinary bool) []string { - if isBinary { - return append(lines, - fmt.Sprintf("Binary files %s and %s differ", fromPath, toPath), - ) - } - return append(lines, - fmt.Sprintf("--- %s", fromPath), - fmt.Sprintf("+++ %s", toPath), - ) -} - -type hunksGenerator struct { - fromLine, toLine int - ctxLines int - chunks []Chunk - current *hunk - hunks []*hunk - beforeContext, afterContext []string -} - -func newHunksGenerator(chunks []Chunk, ctxLines int) *hunksGenerator { - return &hunksGenerator{ - chunks: chunks, - ctxLines: ctxLines, - } -} - -func (g *hunksGenerator) Generate() []*hunk { - for i, chunk := range g.chunks { - lines := splitLines(chunk.Content()) - nLines := len(lines) - - switch chunk.Type() { - case Equal: - g.fromLine += nLines - g.toLine += nLines - g.processEqualsLines(lines, i) - case Delete: - if nLines != 0 { - g.fromLine++ - } - - g.processHunk(i, chunk.Type()) - g.fromLine += nLines - 1 - g.current.AddOp(chunk.Type(), lines...) - case Add: - if nLines != 0 { - g.toLine++ - } - g.processHunk(i, chunk.Type()) - g.toLine += nLines - 1 - g.current.AddOp(chunk.Type(), lines...) - } - - if i == len(g.chunks)-1 && g.current != nil { - g.hunks = append(g.hunks, g.current) - } - } - - return g.hunks -} - -func (g *hunksGenerator) processHunk(i int, op Operation) { - if g.current != nil { - return - } - - var ctxPrefix string - linesBefore := len(g.beforeContext) - if linesBefore > g.ctxLines { - ctxPrefix = g.beforeContext[linesBefore-g.ctxLines-1] - g.beforeContext = g.beforeContext[linesBefore-g.ctxLines:] - linesBefore = g.ctxLines - } - - g.current = &hunk{ctxPrefix: strings.TrimSuffix(ctxPrefix, "\n")} - g.current.AddOp(Equal, g.beforeContext...) - - switch op { - case Delete: - g.current.fromLine, g.current.toLine = - g.addLineNumbers(g.fromLine, g.toLine, linesBefore, i, Add) - case Add: - g.current.toLine, g.current.fromLine = - g.addLineNumbers(g.toLine, g.fromLine, linesBefore, i, Delete) - } - - g.beforeContext = nil -} - -// addLineNumbers obtains the line numbers in a new chunk. -func (g *hunksGenerator) addLineNumbers(la, lb int, linesBefore int, i int, op Operation) (cla, clb int) { - cla = la - linesBefore - // we need to search for a reference for the next diff - switch { - case linesBefore != 0 && g.ctxLines != 0: - if lb > g.ctxLines { - clb = lb - g.ctxLines + 1 - } else { - clb = 1 - } - case g.ctxLines == 0: - clb = lb - case i != len(g.chunks)-1: - next := g.chunks[i+1] - if next.Type() == op || next.Type() == Equal { - // this diff will be into this chunk - clb = lb + 1 - } - } - - return -} - -func (g *hunksGenerator) processEqualsLines(ls []string, i int) { - if g.current == nil { - g.beforeContext = append(g.beforeContext, ls...) - return - } - - g.afterContext = append(g.afterContext, ls...) - if len(g.afterContext) <= g.ctxLines*2 && i != len(g.chunks)-1 { - g.current.AddOp(Equal, g.afterContext...) - g.afterContext = nil - } else { - ctxLines := g.ctxLines - if ctxLines > len(g.afterContext) { - ctxLines = len(g.afterContext) - } - g.current.AddOp(Equal, g.afterContext[:ctxLines]...) - g.hunks = append(g.hunks, g.current) - - g.current = nil - g.beforeContext = g.afterContext[ctxLines:] - g.afterContext = nil - } -} - -func splitLines(s string) []string { - out := splitLinesRegexp.FindAllString(s, -1) - if out[len(out)-1] == "" { - out = out[:len(out)-1] - } - return out -} - -type hunk struct { - fromLine int - toLine int - - fromCount int - toCount int - - ctxPrefix string - ops []*op -} - -func (h *hunk) writeTo(sb *strings.Builder, color ColorConfig) { - sb.WriteString(color[Frag]) - sb.WriteString("@@ -") - - if h.fromCount == 1 { - sb.WriteString(strconv.Itoa(h.fromLine)) - } else { - sb.WriteString(strconv.Itoa(h.fromLine)) - sb.WriteByte(',') - sb.WriteString(strconv.Itoa(h.fromCount)) - } - - sb.WriteString(" +") - - if h.toCount == 1 { - sb.WriteString(strconv.Itoa(h.toLine)) - } else { - sb.WriteString(strconv.Itoa(h.toLine)) - sb.WriteByte(',') - sb.WriteString(strconv.Itoa(h.toCount)) - } - - sb.WriteString(" @@") - sb.WriteString(color.Reset(Frag)) - - if h.ctxPrefix != "" { - sb.WriteByte(' ') - sb.WriteString(color[Func]) - sb.WriteString(h.ctxPrefix) - sb.WriteString(color.Reset(Func)) - } - - sb.WriteByte('\n') - - for _, op := range h.ops { - op.writeTo(sb, color) - } -} - -func (h *hunk) AddOp(t Operation, ss ...string) { - n := len(ss) - switch t { - case Add: - h.toCount += n - case Delete: - h.fromCount += n - case Equal: - h.toCount += n - h.fromCount += n - } - - for _, s := range ss { - h.ops = append(h.ops, &op{s, t}) - } -} - -type op struct { - text string - t Operation -} - -func (o *op) writeTo(sb *strings.Builder, color ColorConfig) { - colorKey := operationColorKey[o.t] - sb.WriteString(color[colorKey]) - sb.WriteByte(operationChar[o.t]) - if strings.HasSuffix(o.text, "\n") { - sb.WriteString(strings.TrimSuffix(o.text, "\n")) - } else { - sb.WriteString(o.text + "\n\\ No newline at end of file") - } - sb.WriteString(color.Reset(colorKey)) - sb.WriteByte('\n') -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/dir.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/dir.go deleted file mode 100644 index af511d12fe5..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/dir.go +++ /dev/null @@ -1,148 +0,0 @@ -package gitignore - -import ( - "bufio" - "bytes" - "io" - "os" - "strings" - - "github.com/go-git/go-billy/v5" - "github.com/jesseduffield/go-git/v5/internal/path_util" - "github.com/jesseduffield/go-git/v5/plumbing/format/config" - gioutil "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -const ( - commentPrefix = "#" - coreSection = "core" - excludesfile = "excludesfile" - gitDir = ".git" - gitignoreFile = ".gitignore" - gitconfigFile = ".gitconfig" - systemFile = "/etc/gitconfig" - infoExcludeFile = gitDir + "/info/exclude" -) - -// readIgnoreFile reads a specific git ignore file. -func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []Pattern, err error) { - - ignoreFile, _ = path_util.ReplaceTildeWithHome(ignoreFile) - - f, err := fs.Open(fs.Join(append(path, ignoreFile)...)) - if err == nil { - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - s := scanner.Text() - if !strings.HasPrefix(s, commentPrefix) && len(strings.TrimSpace(s)) > 0 { - ps = append(ps, ParsePattern(s, path)) - } - } - } else if !os.IsNotExist(err) { - return nil, err - } - - return -} - -// ReadPatterns reads the .git/info/exclude and then the gitignore patterns -// recursively traversing through the directory structure. The result is in -// the ascending order of priority (last higher). -func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error) { - ps, _ = readIgnoreFile(fs, path, infoExcludeFile) - - subps, _ := readIgnoreFile(fs, path, gitignoreFile) - ps = append(ps, subps...) - - var fis []os.FileInfo - fis, err = fs.ReadDir(fs.Join(path...)) - if err != nil { - return - } - - for _, fi := range fis { - if fi.IsDir() && fi.Name() != gitDir { - if NewMatcher(ps).Match(append(path, fi.Name()), true) { - continue - } - - var subps []Pattern - subps, err = ReadPatterns(fs, append(path, fi.Name())) - if err != nil { - return - } - - if len(subps) > 0 { - ps = append(ps, subps...) - } - } - } - - return -} - -func loadPatterns(fs billy.Filesystem, path string) (ps []Pattern, err error) { - f, err := fs.Open(path) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - return nil, err - } - - defer gioutil.CheckClose(f, &err) - - b, err := io.ReadAll(f) - if err != nil { - return - } - - d := config.NewDecoder(bytes.NewBuffer(b)) - - raw := config.New() - if err = d.Decode(raw); err != nil { - return - } - - s := raw.Section(coreSection) - efo := s.Options.Get(excludesfile) - if efo == "" { - return nil, nil - } - - ps, err = readIgnoreFile(fs, nil, efo) - if os.IsNotExist(err) { - return nil, nil - } - - return -} - -// LoadGlobalPatterns loads gitignore patterns from the gitignore file -// declared in a user's ~/.gitconfig file. If the ~/.gitconfig file does not -// exist the function will return nil. If the core.excludesfile property -// is not declared, the function will return nil. If the file pointed to by -// the core.excludesfile property does not exist, the function will return nil. -// -// The function assumes fs is rooted at the root filesystem. -func LoadGlobalPatterns(fs billy.Filesystem) (ps []Pattern, err error) { - home, err := os.UserHomeDir() - if err != nil { - return - } - - return loadPatterns(fs, fs.Join(home, gitconfigFile)) -} - -// LoadSystemPatterns loads gitignore patterns from the gitignore file -// declared in a system's /etc/gitconfig file. If the /etc/gitconfig file does -// not exist the function will return nil. If the core.excludesfile property -// is not declared, the function will return nil. If the file pointed to by -// the core.excludesfile property does not exist, the function will return nil. -// -// The function assumes fs is rooted at the root filesystem. -func LoadSystemPatterns(fs billy.Filesystem) (ps []Pattern, err error) { - return loadPatterns(fs, systemFile) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/doc.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/doc.go deleted file mode 100644 index eecd4baccb2..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/doc.go +++ /dev/null @@ -1,70 +0,0 @@ -// Package gitignore implements matching file system paths to gitignore patterns that -// can be automatically read from a git repository tree in the order of definition -// priorities. It support all pattern formats as specified in the original gitignore -// documentation, copied below: -// -// Pattern format -// ============== -// -// - A blank line matches no files, so it can serve as a separator for readability. -// -// - A line starting with # serves as a comment. Put a backslash ("\") in front of -// the first hash for patterns that begin with a hash. -// -// - Trailing spaces are ignored unless they are quoted with backslash ("\"). -// -// - An optional prefix "!" which negates the pattern; any matching file excluded -// by a previous pattern will become included again. It is not possible to -// re-include a file if a parent directory of that file is excluded. -// Git doesn’t list excluded directories for performance reasons, so -// any patterns on contained files have no effect, no matter where they are -// defined. Put a backslash ("\") in front of the first "!" for patterns -// that begin with a literal "!", for example, "\!important!.txt". -// -// - If the pattern ends with a slash, it is removed for the purpose of the -// following description, but it would only find a match with a directory. -// In other words, foo/ will match a directory foo and paths underneath it, -// but will not match a regular file or a symbolic link foo (this is consistent -// with the way how pathspec works in general in Git). -// -// - If the pattern does not contain a slash /, Git treats it as a shell glob -// pattern and checks for a match against the pathname relative to the location -// of the .gitignore file (relative to the toplevel of the work tree if not -// from a .gitignore file). -// -// - Otherwise, Git treats the pattern as a shell glob suitable for consumption -// by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will -// not match a / in the pathname. For example, "Documentation/*.html" matches -// "Documentation/git.html" but not "Documentation/ppc/ppc.html" or -// "tools/perf/Documentation/perf.html". -// -// - A leading slash matches the beginning of the pathname. For example, -// "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". -// -// Two consecutive asterisks ("**") in patterns matched against full pathname -// may have special meaning: -// -// - A leading "**" followed by a slash means match in all directories. -// For example, "**/foo" matches file or directory "foo" anywhere, the same as -// pattern "foo". "**/foo/bar" matches file or directory "bar" -// anywhere that is directly under directory "foo". -// -// - A trailing "/**" matches everything inside. For example, "abc/**" matches -// all files inside directory "abc", relative to the location of the -// .gitignore file, with infinite depth. -// -// - A slash followed by two consecutive asterisks then a slash matches -// zero or more directories. For example, "a/**/b" matches "a/b", "a/x/b", -// "a/x/y/b" and so on. -// -// - Other consecutive asterisks are considered invalid. -// -// Copyright and license -// ===================== -// -// Copyright (c) Oleg Sklyar, Silvertern and source{d} -// -// The package code was donated to source{d} to include, modify and develop -// further as a part of the `go-git` project, release it on the license of -// the whole project or delete it from the project. -package gitignore diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/matcher.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/matcher.go deleted file mode 100644 index bd1e9e2d4cf..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/matcher.go +++ /dev/null @@ -1,30 +0,0 @@ -package gitignore - -// Matcher defines a global multi-pattern matcher for gitignore patterns -type Matcher interface { - // Match matches patterns in the order of priorities. As soon as an inclusion or - // exclusion is found, not further matching is performed. - Match(path []string, isDir bool) bool -} - -// NewMatcher constructs a new global matcher. Patterns must be given in the order of -// increasing priority. That is most generic settings files first, then the content of -// the repo .gitignore, then content of .gitignore down the path or the repo and then -// the content command line arguments. -func NewMatcher(ps []Pattern) Matcher { - return &matcher{ps} -} - -type matcher struct { - patterns []Pattern -} - -func (m *matcher) Match(path []string, isDir bool) bool { - n := len(m.patterns) - for i := n - 1; i >= 0; i-- { - if match := m.patterns[i].Match(path, isDir); match > NoMatch { - return match == Exclude - } - } - return false -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/pattern.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/pattern.go deleted file mode 100644 index 450b3cdf72b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/gitignore/pattern.go +++ /dev/null @@ -1,155 +0,0 @@ -package gitignore - -import ( - "path/filepath" - "strings" -) - -// MatchResult defines outcomes of a match, no match, exclusion or inclusion. -type MatchResult int - -const ( - // NoMatch defines the no match outcome of a match check - NoMatch MatchResult = iota - // Exclude defines an exclusion of a file as a result of a match check - Exclude - // Include defines an explicit inclusion of a file as a result of a match check - Include -) - -const ( - inclusionPrefix = "!" - zeroToManyDirs = "**" - patternDirSep = "/" -) - -// Pattern defines a single gitignore pattern. -type Pattern interface { - // Match matches the given path to the pattern. - Match(path []string, isDir bool) MatchResult -} - -type pattern struct { - domain []string - pattern []string - inclusion bool - dirOnly bool - isGlob bool -} - -// ParsePattern parses a gitignore pattern string into the Pattern structure. -func ParsePattern(p string, domain []string) Pattern { - // storing domain, copy it to ensure it isn't changed externally - domain = append([]string(nil), domain...) - res := pattern{domain: domain} - - if strings.HasPrefix(p, inclusionPrefix) { - res.inclusion = true - p = p[1:] - } - - if !strings.HasSuffix(p, "\\ ") { - p = strings.TrimRight(p, " ") - } - - if strings.HasSuffix(p, patternDirSep) { - res.dirOnly = true - p = p[:len(p)-1] - } - - if strings.Contains(p, patternDirSep) { - res.isGlob = true - } - - res.pattern = strings.Split(p, patternDirSep) - return &res -} - -func (p *pattern) Match(path []string, isDir bool) MatchResult { - if len(path) <= len(p.domain) { - return NoMatch - } - for i, e := range p.domain { - if path[i] != e { - return NoMatch - } - } - - path = path[len(p.domain):] - if p.isGlob && !p.globMatch(path, isDir) { - return NoMatch - } else if !p.isGlob && !p.simpleNameMatch(path, isDir) { - return NoMatch - } - - if p.inclusion { - return Include - } else { - return Exclude - } -} - -func (p *pattern) simpleNameMatch(path []string, isDir bool) bool { - for i, name := range path { - if match, err := filepath.Match(p.pattern[0], name); err != nil { - return false - } else if !match { - continue - } - if p.dirOnly && !isDir && i == len(path)-1 { - return false - } - return true - } - return false -} - -func (p *pattern) globMatch(path []string, isDir bool) bool { - matched := false - canTraverse := false - for i, pattern := range p.pattern { - if pattern == "" { - canTraverse = false - continue - } - if pattern == zeroToManyDirs { - if i == len(p.pattern)-1 { - break - } - canTraverse = true - continue - } - if strings.Contains(pattern, zeroToManyDirs) { - return false - } - if len(path) == 0 { - return false - } - if canTraverse { - canTraverse = false - for len(path) > 0 { - e := path[0] - path = path[1:] - if match, err := filepath.Match(pattern, e); err != nil { - return false - } else if match { - matched = true - break - } else if len(path) == 0 { - // if nothing left then fail - matched = false - } - } - } else { - if match, err := filepath.Match(pattern, path[0]); err != nil || !match { - return false - } - matched = true - path = path[1:] - } - } - if matched && p.dirOnly && !isDir && len(path) == 0 { - matched = false - } - return matched -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/decoder.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/decoder.go deleted file mode 100644 index d38df328dca..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/decoder.go +++ /dev/null @@ -1,178 +0,0 @@ -package idxfile - -import ( - "bufio" - "bytes" - "errors" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing/hash" - "github.com/jesseduffield/go-git/v5/utils/binary" -) - -var ( - // ErrUnsupportedVersion is returned by Decode when the idx file version - // is not supported. - ErrUnsupportedVersion = errors.New("unsupported version") - // ErrMalformedIdxFile is returned by Decode when the idx file is corrupted. - ErrMalformedIdxFile = errors.New("malformed IDX file") -) - -const ( - fanout = 256 - objectIDLength = hash.Size -) - -// Decoder reads and decodes idx files from an input stream. -type Decoder struct { - *bufio.Reader -} - -// NewDecoder builds a new idx stream decoder, that reads from r. -func NewDecoder(r io.Reader) *Decoder { - return &Decoder{bufio.NewReader(r)} -} - -// Decode reads from the stream and decode the content into the MemoryIndex struct. -func (d *Decoder) Decode(idx *MemoryIndex) error { - if err := validateHeader(d); err != nil { - return err - } - - flow := []func(*MemoryIndex, io.Reader) error{ - readVersion, - readFanout, - readObjectNames, - readCRC32, - readOffsets, - readChecksums, - } - - for _, f := range flow { - if err := f(idx, d); err != nil { - return err - } - } - - return nil -} - -func validateHeader(r io.Reader) error { - var h = make([]byte, 4) - if _, err := io.ReadFull(r, h); err != nil { - return err - } - - if !bytes.Equal(h, idxHeader) { - return ErrMalformedIdxFile - } - - return nil -} - -func readVersion(idx *MemoryIndex, r io.Reader) error { - v, err := binary.ReadUint32(r) - if err != nil { - return err - } - - if v > VersionSupported { - return ErrUnsupportedVersion - } - - idx.Version = v - return nil -} - -func readFanout(idx *MemoryIndex, r io.Reader) error { - for k := 0; k < fanout; k++ { - n, err := binary.ReadUint32(r) - if err != nil { - return err - } - - idx.Fanout[k] = n - idx.FanoutMapping[k] = noMapping - } - - return nil -} - -func readObjectNames(idx *MemoryIndex, r io.Reader) error { - for k := 0; k < fanout; k++ { - var buckets uint32 - if k == 0 { - buckets = idx.Fanout[k] - } else { - buckets = idx.Fanout[k] - idx.Fanout[k-1] - } - - if buckets == 0 { - continue - } - - idx.FanoutMapping[k] = len(idx.Names) - - nameLen := int(buckets * objectIDLength) - bin := make([]byte, nameLen) - if _, err := io.ReadFull(r, bin); err != nil { - return err - } - - idx.Names = append(idx.Names, bin) - idx.Offset32 = append(idx.Offset32, make([]byte, buckets*4)) - idx.CRC32 = append(idx.CRC32, make([]byte, buckets*4)) - } - - return nil -} - -func readCRC32(idx *MemoryIndex, r io.Reader) error { - for k := 0; k < fanout; k++ { - if pos := idx.FanoutMapping[k]; pos != noMapping { - if _, err := io.ReadFull(r, idx.CRC32[pos]); err != nil { - return err - } - } - } - - return nil -} - -func readOffsets(idx *MemoryIndex, r io.Reader) error { - var o64cnt int - for k := 0; k < fanout; k++ { - if pos := idx.FanoutMapping[k]; pos != noMapping { - if _, err := io.ReadFull(r, idx.Offset32[pos]); err != nil { - return err - } - - for p := 0; p < len(idx.Offset32[pos]); p += 4 { - if idx.Offset32[pos][p]&(byte(1)<<7) > 0 { - o64cnt++ - } - } - } - } - - if o64cnt > 0 { - idx.Offset64 = make([]byte, o64cnt*8) - if _, err := io.ReadFull(r, idx.Offset64); err != nil { - return err - } - } - - return nil -} - -func readChecksums(idx *MemoryIndex, r io.Reader) error { - if _, err := io.ReadFull(r, idx.PackfileChecksum[:]); err != nil { - return err - } - - if _, err := io.ReadFull(r, idx.IdxChecksum[:]); err != nil { - return err - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/doc.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/doc.go deleted file mode 100644 index 1e628ab4a5e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/doc.go +++ /dev/null @@ -1,128 +0,0 @@ -// Package idxfile implements encoding and decoding of packfile idx files. -// -// == Original (version 1) pack-*.idx files have the following format: -// -// - The header consists of 256 4-byte network byte order -// integers. N-th entry of this table records the number of -// objects in the corresponding pack, the first byte of whose -// object name is less than or equal to N. This is called the -// 'first-level fan-out' table. -// -// - The header is followed by sorted 24-byte entries, one entry -// per object in the pack. Each entry is: -// -// 4-byte network byte order integer, recording where the -// object is stored in the packfile as the offset from the -// beginning. -// -// 20-byte object name. -// -// - The file is concluded with a trailer: -// -// A copy of the 20-byte SHA1 checksum at the end of -// corresponding packfile. -// -// 20-byte SHA1-checksum of all of the above. -// -// Pack Idx file: -// -// -- +--------------------------------+ -// fanout | fanout[0] = 2 (for example) |-. -// table +--------------------------------+ | -// | fanout[1] | | -// +--------------------------------+ | -// | fanout[2] | | -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | -// | fanout[255] = total objects |---. -// -- +--------------------------------+ | | -// main | offset | | | -// index | object name 00XXXXXXXXXXXXXXXX | | | -// tab +--------------------------------+ | | -// | offset | | | -// | object name 00XXXXXXXXXXXXXXXX | | | -// +--------------------------------+<+ | -// .-| offset | | -// | | object name 01XXXXXXXXXXXXXXXX | | -// | +--------------------------------+ | -// | | offset | | -// | | object name 01XXXXXXXXXXXXXXXX | | -// | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | -// | | offset | | -// | | object name FFXXXXXXXXXXXXXXXX | | -// --| +--------------------------------+<--+ -// trailer | | packfile checksum | -// | +--------------------------------+ -// | | idxfile checksum | -// | +--------------------------------+ -// .---------. -// | -// Pack file entry: <+ -// -// packed object header: -// 1-byte size extension bit (MSB) -// type (next 3 bit) -// size0 (lower 4-bit) -// n-byte sizeN (as long as MSB is set, each 7-bit) -// size0..sizeN form 4+7+7+..+7 bit integer, size0 -// is the least significant part, and sizeN is the -// most significant part. -// packed object data: -// If it is not DELTA, then deflated bytes (the size above -// is the size before compression). -// If it is REF_DELTA, then -// 20-byte base object name SHA1 (the size above is the -// size of the delta data that follows). -// delta data, deflated. -// If it is OFS_DELTA, then -// n-byte offset (see below) interpreted as a negative -// offset from the type-byte of the header of the -// ofs-delta entry (the size above is the size of -// the delta data that follows). -// delta data, deflated. -// -// offset encoding: -// n bytes with MSB set in all but the last one. -// The offset is then the number constructed by -// concatenating the lower 7 bit of each byte, and -// for n >= 2 adding 2^7 + 2^14 + ... + 2^(7*(n-1)) -// to the result. -// -// == Version 2 pack-*.idx files support packs larger than 4 GiB, and -// have some other reorganizations. They have the format: -// -// - A 4-byte magic number '\377tOc' which is an unreasonable -// fanout[0] value. -// -// - A 4-byte version number (= 2) -// -// - A 256-entry fan-out table just like v1. -// -// - A table of sorted 20-byte SHA1 object names. These are -// packed together without offset values to reduce the cache -// footprint of the binary search for a specific object name. -// -// - A table of 4-byte CRC32 values of the packed object data. -// This is new in v2 so compressed data can be copied directly -// from pack to pack during repacking without undetected -// data corruption. -// -// - A table of 4-byte offset values (in network byte order). -// These are usually 31-bit pack file offsets, but large -// offsets are encoded as an index into the next table with -// the msbit set. -// -// - A table of 8-byte offset entries (empty for pack files less -// than 2 GiB). Pack files are organized with heavily used -// objects toward the front, so most object references should -// not need to refer to this table. -// -// - The same trailer as a v1 pack file: -// -// A copy of the 20-byte SHA1 checksum at the end of -// corresponding packfile. -// -// 20-byte SHA1-checksum of all of the above. -// -// Source: -// https://www.kernel.org/pub/software/scm/git/docs/v1.7.5/technical/pack-format.txt -package idxfile diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/encoder.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/encoder.go deleted file mode 100644 index 9e293488e84..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/encoder.go +++ /dev/null @@ -1,141 +0,0 @@ -package idxfile - -import ( - "io" - - "github.com/jesseduffield/go-git/v5/plumbing/hash" - "github.com/jesseduffield/go-git/v5/utils/binary" -) - -// Encoder writes MemoryIndex structs to an output stream. -type Encoder struct { - io.Writer - hash hash.Hash -} - -// NewEncoder returns a new stream encoder that writes to w. -func NewEncoder(w io.Writer) *Encoder { - h := hash.New(hash.CryptoType) - mw := io.MultiWriter(w, h) - return &Encoder{mw, h} -} - -// Encode encodes an MemoryIndex to the encoder writer. -func (e *Encoder) Encode(idx *MemoryIndex) (int, error) { - flow := []func(*MemoryIndex) (int, error){ - e.encodeHeader, - e.encodeFanout, - e.encodeHashes, - e.encodeCRC32, - e.encodeOffsets, - e.encodeChecksums, - } - - sz := 0 - for _, f := range flow { - i, err := f(idx) - sz += i - - if err != nil { - return sz, err - } - } - - return sz, nil -} - -func (e *Encoder) encodeHeader(idx *MemoryIndex) (int, error) { - c, err := e.Write(idxHeader) - if err != nil { - return c, err - } - - return c + 4, binary.WriteUint32(e, idx.Version) -} - -func (e *Encoder) encodeFanout(idx *MemoryIndex) (int, error) { - for _, c := range idx.Fanout { - if err := binary.WriteUint32(e, c); err != nil { - return 0, err - } - } - - return fanout * 4, nil -} - -func (e *Encoder) encodeHashes(idx *MemoryIndex) (int, error) { - var size int - for k := 0; k < fanout; k++ { - pos := idx.FanoutMapping[k] - if pos == noMapping { - continue - } - - n, err := e.Write(idx.Names[pos]) - if err != nil { - return size, err - } - size += n - } - return size, nil -} - -func (e *Encoder) encodeCRC32(idx *MemoryIndex) (int, error) { - var size int - for k := 0; k < fanout; k++ { - pos := idx.FanoutMapping[k] - if pos == noMapping { - continue - } - - n, err := e.Write(idx.CRC32[pos]) - if err != nil { - return size, err - } - - size += n - } - - return size, nil -} - -func (e *Encoder) encodeOffsets(idx *MemoryIndex) (int, error) { - var size int - for k := 0; k < fanout; k++ { - pos := idx.FanoutMapping[k] - if pos == noMapping { - continue - } - - n, err := e.Write(idx.Offset32[pos]) - if err != nil { - return size, err - } - - size += n - } - - if len(idx.Offset64) > 0 { - n, err := e.Write(idx.Offset64) - if err != nil { - return size, err - } - - size += n - } - - return size, nil -} - -func (e *Encoder) encodeChecksums(idx *MemoryIndex) (int, error) { - if _, err := e.Write(idx.PackfileChecksum[:]); err != nil { - return 0, err - } - - copy(idx.IdxChecksum[:], e.hash.Sum(nil)[:hash.Size]) - if _, err := e.Write(idx.IdxChecksum[:]); err != nil { - return 0, err - } - - return hash.HexSize, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/idxfile.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/idxfile.go deleted file mode 100644 index 99ea8dd759d..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/idxfile.go +++ /dev/null @@ -1,347 +0,0 @@ -package idxfile - -import ( - "bytes" - "io" - "sort" - - encbin "encoding/binary" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/hash" -) - -const ( - // VersionSupported is the only idx version supported. - VersionSupported = 2 - - noMapping = -1 -) - -var ( - idxHeader = []byte{255, 't', 'O', 'c'} -) - -// Index represents an index of a packfile. -type Index interface { - // Contains checks whether the given hash is in the index. - Contains(h plumbing.Hash) (bool, error) - // FindOffset finds the offset in the packfile for the object with - // the given hash. - FindOffset(h plumbing.Hash) (int64, error) - // FindCRC32 finds the CRC32 of the object with the given hash. - FindCRC32(h plumbing.Hash) (uint32, error) - // FindHash finds the hash for the object with the given offset. - FindHash(o int64) (plumbing.Hash, error) - // Count returns the number of entries in the index. - Count() (int64, error) - // Entries returns an iterator to retrieve all index entries. - Entries() (EntryIter, error) - // EntriesByOffset returns an iterator to retrieve all index entries ordered - // by offset. - EntriesByOffset() (EntryIter, error) -} - -// MemoryIndex is the in memory representation of an idx file. -type MemoryIndex struct { - Version uint32 - Fanout [256]uint32 - // FanoutMapping maps the position in the fanout table to the position - // in the Names, Offset32 and CRC32 slices. This improves the memory - // usage by not needing an array with unnecessary empty slots. - FanoutMapping [256]int - Names [][]byte - Offset32 [][]byte - CRC32 [][]byte - Offset64 []byte - PackfileChecksum [hash.Size]byte - IdxChecksum [hash.Size]byte - - offsetHash map[int64]plumbing.Hash - offsetHashIsFull bool -} - -var _ Index = (*MemoryIndex)(nil) - -// NewMemoryIndex returns an instance of a new MemoryIndex. -func NewMemoryIndex() *MemoryIndex { - return &MemoryIndex{} -} - -func (idx *MemoryIndex) findHashIndex(h plumbing.Hash) (int, bool) { - k := idx.FanoutMapping[h[0]] - if k == noMapping { - return 0, false - } - - if len(idx.Names) <= k { - return 0, false - } - - data := idx.Names[k] - high := uint64(len(idx.Offset32[k])) >> 2 - if high == 0 { - return 0, false - } - - low := uint64(0) - for { - mid := (low + high) >> 1 - offset := mid * objectIDLength - - cmp := bytes.Compare(h[:], data[offset:offset+objectIDLength]) - if cmp < 0 { - high = mid - } else if cmp == 0 { - return int(mid), true - } else { - low = mid + 1 - } - - if low >= high { - break - } - } - - return 0, false -} - -// Contains implements the Index interface. -func (idx *MemoryIndex) Contains(h plumbing.Hash) (bool, error) { - _, ok := idx.findHashIndex(h) - return ok, nil -} - -// FindOffset implements the Index interface. -func (idx *MemoryIndex) FindOffset(h plumbing.Hash) (int64, error) { - if len(idx.FanoutMapping) <= int(h[0]) { - return 0, plumbing.ErrObjectNotFound - } - - k := idx.FanoutMapping[h[0]] - i, ok := idx.findHashIndex(h) - if !ok { - return 0, plumbing.ErrObjectNotFound - } - - offset := idx.getOffset(k, i) - - if !idx.offsetHashIsFull { - // Save the offset for reverse lookup - if idx.offsetHash == nil { - idx.offsetHash = make(map[int64]plumbing.Hash) - } - idx.offsetHash[int64(offset)] = h - } - - return int64(offset), nil -} - -const isO64Mask = uint64(1) << 31 - -func (idx *MemoryIndex) getOffset(firstLevel, secondLevel int) uint64 { - offset := secondLevel << 2 - ofs := encbin.BigEndian.Uint32(idx.Offset32[firstLevel][offset : offset+4]) - - if (uint64(ofs) & isO64Mask) != 0 { - offset := 8 * (uint64(ofs) & ^isO64Mask) - n := encbin.BigEndian.Uint64(idx.Offset64[offset : offset+8]) - return n - } - - return uint64(ofs) -} - -// FindCRC32 implements the Index interface. -func (idx *MemoryIndex) FindCRC32(h plumbing.Hash) (uint32, error) { - k := idx.FanoutMapping[h[0]] - i, ok := idx.findHashIndex(h) - if !ok { - return 0, plumbing.ErrObjectNotFound - } - - return idx.getCRC32(k, i), nil -} - -func (idx *MemoryIndex) getCRC32(firstLevel, secondLevel int) uint32 { - offset := secondLevel << 2 - return encbin.BigEndian.Uint32(idx.CRC32[firstLevel][offset : offset+4]) -} - -// FindHash implements the Index interface. -func (idx *MemoryIndex) FindHash(o int64) (plumbing.Hash, error) { - var hash plumbing.Hash - var ok bool - - if idx.offsetHash != nil { - if hash, ok = idx.offsetHash[o]; ok { - return hash, nil - } - } - - // Lazily generate the reverse offset/hash map if required. - if !idx.offsetHashIsFull || idx.offsetHash == nil { - if err := idx.genOffsetHash(); err != nil { - return plumbing.ZeroHash, err - } - - hash, ok = idx.offsetHash[o] - } - - if !ok { - return plumbing.ZeroHash, plumbing.ErrObjectNotFound - } - - return hash, nil -} - -// genOffsetHash generates the offset/hash mapping for reverse search. -func (idx *MemoryIndex) genOffsetHash() error { - count, err := idx.Count() - if err != nil { - return err - } - - idx.offsetHash = make(map[int64]plumbing.Hash, count) - idx.offsetHashIsFull = true - - var hash plumbing.Hash - i := uint32(0) - for firstLevel, fanoutValue := range idx.Fanout { - mappedFirstLevel := idx.FanoutMapping[firstLevel] - for secondLevel := uint32(0); i < fanoutValue; i++ { - copy(hash[:], idx.Names[mappedFirstLevel][secondLevel*objectIDLength:]) - offset := int64(idx.getOffset(mappedFirstLevel, int(secondLevel))) - idx.offsetHash[offset] = hash - secondLevel++ - } - } - - return nil -} - -// Count implements the Index interface. -func (idx *MemoryIndex) Count() (int64, error) { - return int64(idx.Fanout[fanout-1]), nil -} - -// Entries implements the Index interface. -func (idx *MemoryIndex) Entries() (EntryIter, error) { - return &idxfileEntryIter{idx, 0, 0, 0}, nil -} - -// EntriesByOffset implements the Index interface. -func (idx *MemoryIndex) EntriesByOffset() (EntryIter, error) { - count, err := idx.Count() - if err != nil { - return nil, err - } - - iter := &idxfileEntryOffsetIter{ - entries: make(entriesByOffset, count), - } - - entries, err := idx.Entries() - if err != nil { - return nil, err - } - - for pos := 0; int64(pos) < count; pos++ { - entry, err := entries.Next() - if err != nil { - return nil, err - } - - iter.entries[pos] = entry - } - - sort.Sort(iter.entries) - - return iter, nil -} - -// EntryIter is an iterator that will return the entries in a packfile index. -type EntryIter interface { - // Next returns the next entry in the packfile index. - Next() (*Entry, error) - // Close closes the iterator. - Close() error -} - -type idxfileEntryIter struct { - idx *MemoryIndex - total int - firstLevel, secondLevel int -} - -func (i *idxfileEntryIter) Next() (*Entry, error) { - for { - if i.firstLevel >= fanout { - return nil, io.EOF - } - - if i.total >= int(i.idx.Fanout[i.firstLevel]) { - i.firstLevel++ - i.secondLevel = 0 - continue - } - - mappedFirstLevel := i.idx.FanoutMapping[i.firstLevel] - entry := new(Entry) - copy(entry.Hash[:], i.idx.Names[mappedFirstLevel][i.secondLevel*objectIDLength:]) - entry.Offset = i.idx.getOffset(mappedFirstLevel, i.secondLevel) - entry.CRC32 = i.idx.getCRC32(mappedFirstLevel, i.secondLevel) - - i.secondLevel++ - i.total++ - - return entry, nil - } -} - -func (i *idxfileEntryIter) Close() error { - i.firstLevel = fanout - return nil -} - -// Entry is the in memory representation of an object entry in the idx file. -type Entry struct { - Hash plumbing.Hash - CRC32 uint32 - Offset uint64 -} - -type idxfileEntryOffsetIter struct { - entries entriesByOffset - pos int -} - -func (i *idxfileEntryOffsetIter) Next() (*Entry, error) { - if i.pos >= len(i.entries) { - return nil, io.EOF - } - - entry := i.entries[i.pos] - i.pos++ - - return entry, nil -} - -func (i *idxfileEntryOffsetIter) Close() error { - i.pos = len(i.entries) + 1 - return nil -} - -type entriesByOffset []*Entry - -func (o entriesByOffset) Len() int { - return len(o) -} - -func (o entriesByOffset) Less(i int, j int) bool { - return o[i].Offset < o[j].Offset -} - -func (o entriesByOffset) Swap(i int, j int) { - o[i], o[j] = o[j], o[i] -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/writer.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/writer.go deleted file mode 100644 index baa2ac37ad4..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/idxfile/writer.go +++ /dev/null @@ -1,193 +0,0 @@ -package idxfile - -import ( - "bytes" - "fmt" - "math" - "sort" - "sync" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/utils/binary" -) - -// objects implements sort.Interface and uses hash as sorting key. -type objects []Entry - -// Writer implements a packfile Observer interface and is used to generate -// indexes. -type Writer struct { - m sync.Mutex - - count uint32 - checksum plumbing.Hash - objects objects - offset64 uint32 - finished bool - index *MemoryIndex - added map[plumbing.Hash]struct{} -} - -// Index returns a previously created MemoryIndex or creates a new one if -// needed. -func (w *Writer) Index() (*MemoryIndex, error) { - w.m.Lock() - defer w.m.Unlock() - - if w.index == nil { - return w.createIndex() - } - - return w.index, nil -} - -// Add appends new object data. -func (w *Writer) Add(h plumbing.Hash, pos uint64, crc uint32) { - w.m.Lock() - defer w.m.Unlock() - - if w.added == nil { - w.added = make(map[plumbing.Hash]struct{}) - } - - if _, ok := w.added[h]; !ok { - w.added[h] = struct{}{} - w.objects = append(w.objects, Entry{h, crc, pos}) - } - -} - -func (w *Writer) Finished() bool { - return w.finished -} - -// OnHeader implements packfile.Observer interface. -func (w *Writer) OnHeader(count uint32) error { - w.count = count - w.objects = make(objects, 0, count) - return nil -} - -// OnInflatedObjectHeader implements packfile.Observer interface. -func (w *Writer) OnInflatedObjectHeader(t plumbing.ObjectType, objSize int64, pos int64) error { - return nil -} - -// OnInflatedObjectContent implements packfile.Observer interface. -func (w *Writer) OnInflatedObjectContent(h plumbing.Hash, pos int64, crc uint32, _ []byte) error { - w.Add(h, uint64(pos), crc) - return nil -} - -// OnFooter implements packfile.Observer interface. -func (w *Writer) OnFooter(h plumbing.Hash) error { - w.checksum = h - w.finished = true - _, err := w.createIndex() - - return err -} - -// creatIndex returns a filled MemoryIndex with the information filled by -// the observer callbacks. -func (w *Writer) createIndex() (*MemoryIndex, error) { - if !w.finished { - return nil, fmt.Errorf("the index still hasn't finished building") - } - - idx := new(MemoryIndex) - w.index = idx - - sort.Sort(w.objects) - - // unmap all fans by default - for i := range idx.FanoutMapping { - idx.FanoutMapping[i] = noMapping - } - - buf := new(bytes.Buffer) - - last := -1 - bucket := -1 - for i, o := range w.objects { - fan := o.Hash[0] - - // fill the gaps between fans - for j := last + 1; j < int(fan); j++ { - idx.Fanout[j] = uint32(i) - } - - // update the number of objects for this position - idx.Fanout[fan] = uint32(i + 1) - - // we move from one bucket to another, update counters and allocate - // memory - if last != int(fan) { - bucket++ - idx.FanoutMapping[fan] = bucket - last = int(fan) - - idx.Names = append(idx.Names, make([]byte, 0)) - idx.Offset32 = append(idx.Offset32, make([]byte, 0)) - idx.CRC32 = append(idx.CRC32, make([]byte, 0)) - } - - idx.Names[bucket] = append(idx.Names[bucket], o.Hash[:]...) - - offset := o.Offset - if offset > math.MaxInt32 { - var err error - offset, err = w.addOffset64(offset) - if err != nil { - return nil, err - } - } - - buf.Truncate(0) - if err := binary.WriteUint32(buf, uint32(offset)); err != nil { - return nil, err - } - idx.Offset32[bucket] = append(idx.Offset32[bucket], buf.Bytes()...) - - buf.Truncate(0) - if err := binary.WriteUint32(buf, o.CRC32); err != nil { - return nil, err - } - idx.CRC32[bucket] = append(idx.CRC32[bucket], buf.Bytes()...) - } - - for j := last + 1; j < 256; j++ { - idx.Fanout[j] = uint32(len(w.objects)) - } - - idx.Version = VersionSupported - idx.PackfileChecksum = w.checksum - - return idx, nil -} - -func (w *Writer) addOffset64(pos uint64) (uint64, error) { - buf := new(bytes.Buffer) - if err := binary.WriteUint64(buf, pos); err != nil { - return 0, err - } - - w.index.Offset64 = append(w.index.Offset64, buf.Bytes()...) - index := uint64(w.offset64 | (1 << 31)) - w.offset64++ - - return index, nil -} - -func (o objects) Len() int { - return len(o) -} - -func (o objects) Less(i int, j int) bool { - cmp := bytes.Compare(o[i].Hash[:], o[j].Hash[:]) - return cmp < 0 -} - -func (o objects) Swap(i int, j int) { - o[i], o[j] = o[j], o[i] -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/decoder.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/decoder.go deleted file mode 100644 index 6bd26206d88..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/decoder.go +++ /dev/null @@ -1,503 +0,0 @@ -package index - -import ( - "bufio" - "bytes" - "errors" - "io" - - "strconv" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/hash" - "github.com/jesseduffield/go-git/v5/utils/binary" -) - -var ( - // DecodeVersionSupported is the range of supported index versions - DecodeVersionSupported = struct{ Min, Max uint32 }{Min: 2, Max: 4} - - // ErrMalformedSignature is returned by Decode when the index header file is - // malformed - ErrMalformedSignature = errors.New("malformed index signature file") - // ErrInvalidChecksum is returned by Decode if the SHA1 hash mismatch with - // the read content - ErrInvalidChecksum = errors.New("invalid checksum") - // ErrUnknownExtension is returned when an index extension is encountered that is considered mandatory - ErrUnknownExtension = errors.New("unknown extension") -) - -const ( - entryHeaderLength = 62 - entryExtended = 0x4000 - entryValid = 0x8000 - nameMask = 0xfff - intentToAddMask = 1 << 13 - skipWorkTreeMask = 1 << 14 -) - -// A Decoder reads and decodes index files from an input stream. -type Decoder struct { - buf *bufio.Reader - r io.Reader - hash hash.Hash - lastEntry *Entry - - extReader *bufio.Reader -} - -// NewDecoder returns a new decoder that reads from r. -func NewDecoder(r io.Reader) *Decoder { - h := hash.New(hash.CryptoType) - buf := bufio.NewReader(r) - return &Decoder{ - buf: buf, - r: io.TeeReader(buf, h), - hash: h, - extReader: bufio.NewReader(nil), - } -} - -// Decode reads the whole index object from its input and stores it in the -// value pointed to by idx. -func (d *Decoder) Decode(idx *Index) error { - var err error - idx.Version, err = validateHeader(d.r) - if err != nil { - return err - } - - entryCount, err := binary.ReadUint32(d.r) - if err != nil { - return err - } - - if err := d.readEntries(idx, int(entryCount)); err != nil { - return err - } - - return d.readExtensions(idx) -} - -func (d *Decoder) readEntries(idx *Index, count int) error { - for i := 0; i < count; i++ { - e, err := d.readEntry(idx) - if err != nil { - return err - } - - d.lastEntry = e - idx.Entries = append(idx.Entries, e) - } - - return nil -} - -func (d *Decoder) readEntry(idx *Index) (*Entry, error) { - e := &Entry{} - - var msec, mnsec, sec, nsec uint32 - var flags uint16 - - flow := []interface{}{ - &sec, &nsec, - &msec, &mnsec, - &e.Dev, - &e.Inode, - &e.Mode, - &e.UID, - &e.GID, - &e.Size, - &e.Hash, - &flags, - } - - if err := binary.Read(d.r, flow...); err != nil { - return nil, err - } - - read := entryHeaderLength - - if sec != 0 || nsec != 0 { - e.CreatedAt = time.Unix(int64(sec), int64(nsec)) - } - - if msec != 0 || mnsec != 0 { - e.ModifiedAt = time.Unix(int64(msec), int64(mnsec)) - } - - e.Stage = Stage(flags>>12) & 0x3 - - if flags&entryExtended != 0 { - extended, err := binary.ReadUint16(d.r) - if err != nil { - return nil, err - } - - read += 2 - e.IntentToAdd = extended&intentToAddMask != 0 - e.SkipWorktree = extended&skipWorkTreeMask != 0 - } - - if err := d.readEntryName(idx, e, flags); err != nil { - return nil, err - } - - return e, d.padEntry(idx, e, read) -} - -func (d *Decoder) readEntryName(idx *Index, e *Entry, flags uint16) error { - var name string - var err error - - switch idx.Version { - case 2, 3: - len := flags & nameMask - name, err = d.doReadEntryName(len) - case 4: - name, err = d.doReadEntryNameV4() - default: - return ErrUnsupportedVersion - } - - if err != nil { - return err - } - - e.Name = name - return nil -} - -func (d *Decoder) doReadEntryNameV4() (string, error) { - l, err := binary.ReadVariableWidthInt(d.r) - if err != nil { - return "", err - } - - var base string - if d.lastEntry != nil { - base = d.lastEntry.Name[:len(d.lastEntry.Name)-int(l)] - } - - name, err := binary.ReadUntil(d.r, '\x00') - if err != nil { - return "", err - } - - return base + string(name), nil -} - -func (d *Decoder) doReadEntryName(len uint16) (string, error) { - name := make([]byte, len) - _, err := io.ReadFull(d.r, name) - - return string(name), err -} - -// Index entries are padded out to the next 8 byte alignment -// for historical reasons related to how C Git read the files. -func (d *Decoder) padEntry(idx *Index, e *Entry, read int) error { - if idx.Version == 4 { - return nil - } - - entrySize := read + len(e.Name) - padLen := 8 - entrySize%8 - _, err := io.CopyN(io.Discard, d.r, int64(padLen)) - return err -} - -func (d *Decoder) readExtensions(idx *Index) error { - // TODO: support 'Split index' and 'Untracked cache' extensions, take in - // count that they are not supported by jgit or libgit - - var expected []byte - var peeked []byte - var err error - - // we should always be able to peek for 4 bytes (header) + 4 bytes (extlen) + final hash - // if this fails, we know that we're at the end of the index - peekLen := 4 + 4 + d.hash.Size() - - for { - expected = d.hash.Sum(nil) - peeked, err = d.buf.Peek(peekLen) - if len(peeked) < peekLen { - // there can't be an extension at this point, so let's bail out - break - } - if err != nil { - return err - } - - err = d.readExtension(idx) - if err != nil { - return err - } - } - - return d.readChecksum(expected) -} - -func (d *Decoder) readExtension(idx *Index) error { - var header [4]byte - - if _, err := io.ReadFull(d.r, header[:]); err != nil { - return err - } - - r, err := d.getExtensionReader() - if err != nil { - return err - } - - switch { - case bytes.Equal(header[:], treeExtSignature): - idx.Cache = &Tree{} - d := &treeExtensionDecoder{r} - if err := d.Decode(idx.Cache); err != nil { - return err - } - case bytes.Equal(header[:], resolveUndoExtSignature): - idx.ResolveUndo = &ResolveUndo{} - d := &resolveUndoDecoder{r} - if err := d.Decode(idx.ResolveUndo); err != nil { - return err - } - case bytes.Equal(header[:], endOfIndexEntryExtSignature): - idx.EndOfIndexEntry = &EndOfIndexEntry{} - d := &endOfIndexEntryDecoder{r} - if err := d.Decode(idx.EndOfIndexEntry); err != nil { - return err - } - default: - // See https://git-scm.com/docs/index-format, which says: - // If the first byte is 'A'..'Z' the extension is optional and can be ignored. - if header[0] < 'A' || header[0] > 'Z' { - return ErrUnknownExtension - } - - d := &unknownExtensionDecoder{r} - if err := d.Decode(); err != nil { - return err - } - } - - return nil -} - -func (d *Decoder) getExtensionReader() (*bufio.Reader, error) { - len, err := binary.ReadUint32(d.r) - if err != nil { - return nil, err - } - - d.extReader.Reset(&io.LimitedReader{R: d.r, N: int64(len)}) - return d.extReader, nil -} - -func (d *Decoder) readChecksum(expected []byte) error { - var h plumbing.Hash - - if _, err := io.ReadFull(d.r, h[:]); err != nil { - return err - } - - if !bytes.Equal(h[:], expected) { - return ErrInvalidChecksum - } - - return nil -} - -func validateHeader(r io.Reader) (version uint32, err error) { - var s = make([]byte, 4) - if _, err := io.ReadFull(r, s); err != nil { - return 0, err - } - - if !bytes.Equal(s, indexSignature) { - return 0, ErrMalformedSignature - } - - version, err = binary.ReadUint32(r) - if err != nil { - return 0, err - } - - if version < DecodeVersionSupported.Min || version > DecodeVersionSupported.Max { - return 0, ErrUnsupportedVersion - } - - return -} - -type treeExtensionDecoder struct { - r *bufio.Reader -} - -func (d *treeExtensionDecoder) Decode(t *Tree) error { - for { - e, err := d.readEntry() - if err != nil { - if err == io.EOF { - return nil - } - - return err - } - - if e == nil { - continue - } - - t.Entries = append(t.Entries, *e) - } -} - -func (d *treeExtensionDecoder) readEntry() (*TreeEntry, error) { - e := &TreeEntry{} - - path, err := binary.ReadUntil(d.r, '\x00') - if err != nil { - return nil, err - } - - e.Path = string(path) - - count, err := binary.ReadUntil(d.r, ' ') - if err != nil { - return nil, err - } - - i, err := strconv.Atoi(string(count)) - if err != nil { - return nil, err - } - - // An entry can be in an invalidated state and is represented by having a - // negative number in the entry_count field. - if i == -1 { - return nil, nil - } - - e.Entries = i - trees, err := binary.ReadUntil(d.r, '\n') - if err != nil { - return nil, err - } - - i, err = strconv.Atoi(string(trees)) - if err != nil { - return nil, err - } - - e.Trees = i - _, err = io.ReadFull(d.r, e.Hash[:]) - if err != nil { - return nil, err - } - return e, nil -} - -type resolveUndoDecoder struct { - r *bufio.Reader -} - -func (d *resolveUndoDecoder) Decode(ru *ResolveUndo) error { - for { - e, err := d.readEntry() - if err != nil { - if err == io.EOF { - return nil - } - - return err - } - - ru.Entries = append(ru.Entries, *e) - } -} - -func (d *resolveUndoDecoder) readEntry() (*ResolveUndoEntry, error) { - e := &ResolveUndoEntry{ - Stages: make(map[Stage]plumbing.Hash), - } - - path, err := binary.ReadUntil(d.r, '\x00') - if err != nil { - return nil, err - } - - e.Path = string(path) - - for i := 0; i < 3; i++ { - if err := d.readStage(e, Stage(i+1)); err != nil { - return nil, err - } - } - - for s := range e.Stages { - var hash plumbing.Hash - if _, err := io.ReadFull(d.r, hash[:]); err != nil { - return nil, err - } - - e.Stages[s] = hash - } - - return e, nil -} - -func (d *resolveUndoDecoder) readStage(e *ResolveUndoEntry, s Stage) error { - ascii, err := binary.ReadUntil(d.r, '\x00') - if err != nil { - return err - } - - stage, err := strconv.ParseInt(string(ascii), 8, 64) - if err != nil { - return err - } - - if stage != 0 { - e.Stages[s] = plumbing.ZeroHash - } - - return nil -} - -type endOfIndexEntryDecoder struct { - r *bufio.Reader -} - -func (d *endOfIndexEntryDecoder) Decode(e *EndOfIndexEntry) error { - var err error - e.Offset, err = binary.ReadUint32(d.r) - if err != nil { - return err - } - - _, err = io.ReadFull(d.r, e.Hash[:]) - return err -} - -type unknownExtensionDecoder struct { - r *bufio.Reader -} - -func (d *unknownExtensionDecoder) Decode() error { - var buf [1024]byte - - for { - _, err := d.r.Read(buf[:]) - if err == io.EOF { - break - } - if err != nil { - return err - } - } - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/doc.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/doc.go deleted file mode 100644 index 39ae6ad5f91..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/doc.go +++ /dev/null @@ -1,360 +0,0 @@ -// Package index implements encoding and decoding of index format files. -// -// Git index format -// ================ -// -// == The Git index file has the following format -// -// All binary numbers are in network byte order. Version 2 is described -// here unless stated otherwise. -// -// - A 12-byte header consisting of -// -// 4-byte signature: -// The signature is { 'D', 'I', 'R', 'C' } (stands for "dircache") -// -// 4-byte version number: -// The current supported versions are 2, 3 and 4. -// -// 32-bit number of index entries. -// -// - A number of sorted index entries (see below). -// -// - Extensions -// -// Extensions are identified by signature. Optional extensions can -// be ignored if Git does not understand them. -// -// Git currently supports cached tree and resolve undo extensions. -// -// 4-byte extension signature. If the first byte is 'A'..'Z' the -// extension is optional and can be ignored. -// -// 32-bit size of the extension -// -// Extension data -// -// - 160-bit SHA-1 over the content of the index file before this -// checksum. -// -// == Index entry -// -// Index entries are sorted in ascending order on the name field, -// interpreted as a string of unsigned bytes (i.e. memcmp() order, no -// localization, no special casing of directory separator '/'). Entries -// with the same name are sorted by their stage field. -// -// 32-bit ctime seconds, the last time a file's metadata changed -// this is stat(2) data -// -// 32-bit ctime nanosecond fractions -// this is stat(2) data -// -// 32-bit mtime seconds, the last time a file's data changed -// this is stat(2) data -// -// 32-bit mtime nanosecond fractions -// this is stat(2) data -// -// 32-bit dev -// this is stat(2) data -// -// 32-bit ino -// this is stat(2) data -// -// 32-bit mode, split into (high to low bits) -// -// 4-bit object type -// valid values in binary are 1000 (regular file), 1010 (symbolic link) -// and 1110 (gitlink) -// -// 3-bit unused -// -// 9-bit unix permission. Only 0755 and 0644 are valid for regular files. -// Symbolic links and gitlinks have value 0 in this field. -// -// 32-bit uid -// this is stat(2) data -// -// 32-bit gid -// this is stat(2) data -// -// 32-bit file size -// This is the on-disk size from stat(2), truncated to 32-bit. -// -// 160-bit SHA-1 for the represented object -// -// A 16-bit 'flags' field split into (high to low bits) -// -// 1-bit assume-valid flag -// -// 1-bit extended flag (must be zero in version 2) -// -// 2-bit stage (during merge) -// -// 12-bit name length if the length is less than 0xFFF; otherwise 0xFFF -// is stored in this field. -// -// (Version 3 or later) A 16-bit field, only applicable if the -// "extended flag" above is 1, split into (high to low bits). -// -// 1-bit reserved for future -// -// 1-bit skip-worktree flag (used by sparse checkout) -// -// 1-bit intent-to-add flag (used by "git add -N") -// -// 13-bit unused, must be zero -// -// Entry path name (variable length) relative to top level directory -// (without leading slash). '/' is used as path separator. The special -// path components ".", ".." and ".git" (without quotes) are disallowed. -// Trailing slash is also disallowed. -// -// The exact encoding is undefined, but the '.' and '/' characters -// are encoded in 7-bit ASCII and the encoding cannot contain a NUL -// byte (iow, this is a UNIX pathname). -// -// (Version 4) In version 4, the entry path name is prefix-compressed -// relative to the path name for the previous entry (the very first -// entry is encoded as if the path name for the previous entry is an -// empty string). At the beginning of an entry, an integer N in the -// variable width encoding (the same encoding as the offset is encoded -// for OFS_DELTA pack entries; see pack-format.txt) is stored, followed -// by a NUL-terminated string S. Removing N bytes from the end of the -// path name for the previous entry, and replacing it with the string S -// yields the path name for this entry. -// -// 1-8 nul bytes as necessary to pad the entry to a multiple of eight bytes -// while keeping the name NUL-terminated. -// -// (Version 4) In version 4, the padding after the pathname does not -// exist. -// -// Interpretation of index entries in split index mode is completely -// different. See below for details. -// -// == Extensions -// -// === Cached tree -// -// Cached tree extension contains pre-computed hashes for trees that can -// be derived from the index. It helps speed up tree object generation -// from index for a new commit. -// -// When a path is updated in index, the path must be invalidated and -// removed from tree cache. -// -// The signature for this extension is { 'T', 'R', 'E', 'E' }. -// -// A series of entries fill the entire extension; each of which -// consists of: -// -// - NUL-terminated path component (relative to its parent directory); -// -// - ASCII decimal number of entries in the index that is covered by the -// tree this entry represents (entry_count); -// -// - A space (ASCII 32); -// -// - ASCII decimal number that represents the number of subtrees this -// tree has; -// -// - A newline (ASCII 10); and -// -// - 160-bit object name for the object that would result from writing -// this span of index as a tree. -// -// An entry can be in an invalidated state and is represented by having -// a negative number in the entry_count field. In this case, there is no -// object name and the next entry starts immediately after the newline. -// When writing an invalid entry, -1 should always be used as entry_count. -// -// The entries are written out in the top-down, depth-first order. The -// first entry represents the root level of the repository, followed by the -// first subtree--let's call this A--of the root level (with its name -// relative to the root level), followed by the first subtree of A (with -// its name relative to A), ... -// -// === Resolve undo -// -// A conflict is represented in the index as a set of higher stage entries. -// When a conflict is resolved (e.g. with "git add path"), these higher -// stage entries will be removed and a stage-0 entry with proper resolution -// is added. -// -// When these higher stage entries are removed, they are saved in the -// resolve undo extension, so that conflicts can be recreated (e.g. with -// "git checkout -m"), in case users want to redo a conflict resolution -// from scratch. -// -// The signature for this extension is { 'R', 'E', 'U', 'C' }. -// -// A series of entries fill the entire extension; each of which -// consists of: -// -// - NUL-terminated pathname the entry describes (relative to the root of -// the repository, i.e. full pathname); -// -// - Three NUL-terminated ASCII octal numbers, entry mode of entries in -// stage 1 to 3 (a missing stage is represented by "0" in this field); -// and -// -// - At most three 160-bit object names of the entry in stages from 1 to 3 -// (nothing is written for a missing stage). -// -// === Split index -// -// In split index mode, the majority of index entries could be stored -// in a separate file. This extension records the changes to be made on -// top of that to produce the final index. -// -// The signature for this extension is { 'l', 'i', 'n', 'k' }. -// -// The extension consists of: -// -// - 160-bit SHA-1 of the shared index file. The shared index file path -// is $GIT_DIR/sharedindex.. If all 160 bits are zero, the -// index does not require a shared index file. -// -// - An ewah-encoded delete bitmap, each bit represents an entry in the -// shared index. If a bit is set, its corresponding entry in the -// shared index will be removed from the final index. Note, because -// a delete operation changes index entry positions, but we do need -// original positions in replace phase, it's best to just mark -// entries for removal, then do a mass deletion after replacement. -// -// - An ewah-encoded replace bitmap, each bit represents an entry in -// the shared index. If a bit is set, its corresponding entry in the -// shared index will be replaced with an entry in this index -// file. All replaced entries are stored in sorted order in this -// index. The first "1" bit in the replace bitmap corresponds to the -// first index entry, the second "1" bit to the second entry and so -// on. Replaced entries may have empty path names to save space. -// -// The remaining index entries after replaced ones will be added to the -// final index. These added entries are also sorted by entry name then -// stage. -// -// == Untracked cache -// -// Untracked cache saves the untracked file list and necessary data to -// verify the cache. The signature for this extension is { 'U', 'N', -// 'T', 'R' }. -// -// The extension starts with -// -// - A sequence of NUL-terminated strings, preceded by the size of the -// sequence in variable width encoding. Each string describes the -// environment where the cache can be used. -// -// - Stat data of $GIT_DIR/info/exclude. See "Index entry" section from -// ctime field until "file size". -// -// - Stat data of plumbing.excludesfile -// -// - 32-bit dir_flags (see struct dir_struct) -// -// - 160-bit SHA-1 of $GIT_DIR/info/exclude. Null SHA-1 means the file -// does not exist. -// -// - 160-bit SHA-1 of plumbing.excludesfile. Null SHA-1 means the file does -// not exist. -// -// - NUL-terminated string of per-dir exclude file name. This usually -// is ".gitignore". -// -// - The number of following directory blocks, variable width -// encoding. If this number is zero, the extension ends here with a -// following NUL. -// -// - A number of directory blocks in depth-first-search order, each -// consists of -// -// - The number of untracked entries, variable width encoding. -// -// - The number of sub-directory blocks, variable width encoding. -// -// - The directory name terminated by NUL. -// -// - A number of untracked file/dir names terminated by NUL. -// -// The remaining data of each directory block is grouped by type: -// -// - An ewah bitmap, the n-th bit marks whether the n-th directory has -// valid untracked cache entries. -// -// - An ewah bitmap, the n-th bit records "check-only" bit of -// read_directory_recursive() for the n-th directory. -// -// - An ewah bitmap, the n-th bit indicates whether SHA-1 and stat data -// is valid for the n-th directory and exists in the next data. -// -// - An array of stat data. The n-th data corresponds with the n-th -// "one" bit in the previous ewah bitmap. -// -// - An array of SHA-1. The n-th SHA-1 corresponds with the n-th "one" bit -// in the previous ewah bitmap. -// -// - One NUL. -// -// == File System Monitor cache -// -// The file system monitor cache tracks files for which the core.fsmonitor -// hook has told us about changes. The signature for this extension is -// { 'F', 'S', 'M', 'N' }. -// -// The extension starts with -// -// - 32-bit version number: the current supported version is 1. -// -// - 64-bit time: the extension data reflects all changes through the given -// time which is stored as the nanoseconds elapsed since midnight, -// January 1, 1970. -// -// - 32-bit bitmap size: the size of the CE_FSMONITOR_VALID bitmap. -// -// - An ewah bitmap, the n-th bit indicates whether the n-th index entry -// is not CE_FSMONITOR_VALID. -// -// == End of Index Entry -// -// The End of Index Entry (EOIE) is used to locate the end of the variable -// length index entries and the beginning of the extensions. Code can take -// advantage of this to quickly locate the index extensions without having -// to parse through all of the index entries. -// -// Because it must be able to be loaded before the variable length cache -// entries and other index extensions, this extension must be written last. -// The signature for this extension is { 'E', 'O', 'I', 'E' }. -// -// The extension consists of: -// -// - 32-bit offset to the end of the index entries -// -// - 160-bit SHA-1 over the extension types and their sizes (but not -// their contents). E.g. if we have "TREE" extension that is N-bytes -// long, "REUC" extension that is M-bytes long, followed by "EOIE", -// then the hash would be: -// -// SHA-1("TREE" + + -// "REUC" + ) -// -// == Index Entry Offset Table -// -// The Index Entry Offset Table (IEOT) is used to help address the CPU -// cost of loading the index by enabling multi-threading the process of -// converting cache entries from the on-disk format to the in-memory format. -// The signature for this extension is { 'I', 'E', 'O', 'T' }. -// -// The extension consists of: -// -// - 32-bit version (currently 1) -// -// - A number of index offset entries each consisting of: -// -// - 32-bit offset from the beginning of the file to the first cache entry -// in this block of entries. -// -// - 32-bit count of cache entries in this blockpackage index -package index diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/encoder.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/encoder.go deleted file mode 100644 index 9543c32b237..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/encoder.go +++ /dev/null @@ -1,239 +0,0 @@ -package index - -import ( - "bytes" - "errors" - "fmt" - "io" - "path" - "sort" - "strings" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing/hash" - "github.com/jesseduffield/go-git/v5/utils/binary" -) - -var ( - // EncodeVersionSupported is the range of supported index versions - EncodeVersionSupported uint32 = 4 - - // ErrInvalidTimestamp is returned by Encode if a Index with a Entry with - // negative timestamp values - ErrInvalidTimestamp = errors.New("negative timestamps are not allowed") -) - -// An Encoder writes an Index to an output stream. -type Encoder struct { - w io.Writer - hash hash.Hash - lastEntry *Entry -} - -// NewEncoder returns a new encoder that writes to w. -func NewEncoder(w io.Writer) *Encoder { - h := hash.New(hash.CryptoType) - mw := io.MultiWriter(w, h) - return &Encoder{mw, h, nil} -} - -// Encode writes the Index to the stream of the encoder. -func (e *Encoder) Encode(idx *Index) error { - return e.encode(idx, true) -} - -func (e *Encoder) encode(idx *Index, footer bool) error { - - // TODO: support extensions - if idx.Version > EncodeVersionSupported { - return ErrUnsupportedVersion - } - - if err := e.encodeHeader(idx); err != nil { - return err - } - - if err := e.encodeEntries(idx); err != nil { - return err - } - - if footer { - return e.encodeFooter() - } - return nil -} - -func (e *Encoder) encodeHeader(idx *Index) error { - return binary.Write(e.w, - indexSignature, - idx.Version, - uint32(len(idx.Entries)), - ) -} - -func (e *Encoder) encodeEntries(idx *Index) error { - sort.Sort(byName(idx.Entries)) - - for _, entry := range idx.Entries { - if err := e.encodeEntry(idx, entry); err != nil { - return err - } - entryLength := entryHeaderLength - if entry.IntentToAdd || entry.SkipWorktree { - entryLength += 2 - } - - wrote := entryLength + len(entry.Name) - if err := e.padEntry(idx, wrote); err != nil { - return err - } - } - - return nil -} - -func (e *Encoder) encodeEntry(idx *Index, entry *Entry) error { - sec, nsec, err := e.timeToUint32(&entry.CreatedAt) - if err != nil { - return err - } - - msec, mnsec, err := e.timeToUint32(&entry.ModifiedAt) - if err != nil { - return err - } - - flags := uint16(entry.Stage&0x3) << 12 - if l := len(entry.Name); l < nameMask { - flags |= uint16(l) - } else { - flags |= nameMask - } - - flow := []interface{}{ - sec, nsec, - msec, mnsec, - entry.Dev, - entry.Inode, - entry.Mode, - entry.UID, - entry.GID, - entry.Size, - entry.Hash[:], - } - - flagsFlow := []interface{}{flags} - - if entry.IntentToAdd || entry.SkipWorktree { - var extendedFlags uint16 - - if entry.IntentToAdd { - extendedFlags |= intentToAddMask - } - if entry.SkipWorktree { - extendedFlags |= skipWorkTreeMask - } - - flagsFlow = []interface{}{flags | entryExtended, extendedFlags} - } - - flow = append(flow, flagsFlow...) - - if err := binary.Write(e.w, flow...); err != nil { - return err - } - - switch idx.Version { - case 2, 3: - err = e.encodeEntryName(entry) - case 4: - err = e.encodeEntryNameV4(entry) - default: - err = ErrUnsupportedVersion - } - - return err -} - -func (e *Encoder) encodeEntryName(entry *Entry) error { - return binary.Write(e.w, []byte(entry.Name)) -} - -func (e *Encoder) encodeEntryNameV4(entry *Entry) error { - name := entry.Name - l := 0 - if e.lastEntry != nil { - dir := path.Dir(e.lastEntry.Name) + "/" - if strings.HasPrefix(entry.Name, dir) { - l = len(e.lastEntry.Name) - len(dir) - name = strings.TrimPrefix(entry.Name, dir) - } else { - l = len(e.lastEntry.Name) - } - } - - e.lastEntry = entry - - err := binary.WriteVariableWidthInt(e.w, int64(l)) - if err != nil { - return err - } - - return binary.Write(e.w, []byte(name+string('\x00'))) -} - -func (e *Encoder) encodeRawExtension(signature string, data []byte) error { - if len(signature) != 4 { - return fmt.Errorf("invalid signature length") - } - - _, err := e.w.Write([]byte(signature)) - if err != nil { - return err - } - - err = binary.WriteUint32(e.w, uint32(len(data))) - if err != nil { - return err - } - - _, err = e.w.Write(data) - if err != nil { - return err - } - - return nil -} - -func (e *Encoder) timeToUint32(t *time.Time) (uint32, uint32, error) { - if t.IsZero() { - return 0, 0, nil - } - - if t.Unix() < 0 || t.UnixNano() < 0 { - return 0, 0, ErrInvalidTimestamp - } - - return uint32(t.Unix()), uint32(t.Nanosecond()), nil -} - -func (e *Encoder) padEntry(idx *Index, wrote int) error { - if idx.Version == 4 { - return nil - } - - padLen := 8 - wrote%8 - - _, err := e.w.Write(bytes.Repeat([]byte{'\x00'}, padLen)) - return err -} - -func (e *Encoder) encodeFooter() error { - return binary.Write(e.w, e.hash.Sum(nil)) -} - -type byName []*Entry - -func (l byName) Len() int { return len(l) } -func (l byName) Swap(i, j int) { l[i], l[j] = l[j], l[i] } -func (l byName) Less(i, j int) bool { return l[i].Name < l[j].Name } diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/index.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/index.go deleted file mode 100644 index 2f68ae97868..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/index.go +++ /dev/null @@ -1,231 +0,0 @@ -package index - -import ( - "bytes" - "errors" - "fmt" - "path/filepath" - "strings" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" -) - -var ( - // ErrUnsupportedVersion is returned by Decode when the index file version - // is not supported. - ErrUnsupportedVersion = errors.New("unsupported version") - // ErrEntryNotFound is returned by Index.Entry, if an entry is not found. - ErrEntryNotFound = errors.New("entry not found") - - indexSignature = []byte{'D', 'I', 'R', 'C'} - treeExtSignature = []byte{'T', 'R', 'E', 'E'} - resolveUndoExtSignature = []byte{'R', 'E', 'U', 'C'} - endOfIndexEntryExtSignature = []byte{'E', 'O', 'I', 'E'} -) - -// Stage during merge -type Stage int - -const ( - // Merged is the default stage, fully merged - Merged Stage = 1 - // AncestorMode is the base revision - AncestorMode Stage = 1 - // OurMode is the first tree revision, ours - OurMode Stage = 2 - // TheirMode is the second tree revision, theirs - TheirMode Stage = 3 -) - -// Index contains the information about which objects are currently checked out -// in the worktree, having information about the working files. Changes in -// worktree are detected using this Index. The Index is also used during merges -type Index struct { - // Version is index version - Version uint32 - // Entries collection of entries represented by this Index. The order of - // this collection is not guaranteed - Entries []*Entry - // Cache represents the 'Cached tree' extension - Cache *Tree - // ResolveUndo represents the 'Resolve undo' extension - ResolveUndo *ResolveUndo - // EndOfIndexEntry represents the 'End of Index Entry' extension - EndOfIndexEntry *EndOfIndexEntry -} - -// Add creates a new Entry and returns it. The caller should first check that -// another entry with the same path does not exist. -func (i *Index) Add(path string) *Entry { - e := &Entry{ - Name: filepath.ToSlash(path), - } - - i.Entries = append(i.Entries, e) - return e -} - -// Entry returns the entry that match the given path, if any. -func (i *Index) Entry(path string) (*Entry, error) { - path = filepath.ToSlash(path) - for _, e := range i.Entries { - if e.Name == path { - return e, nil - } - } - - return nil, ErrEntryNotFound -} - -// Remove remove the entry that match the give path and returns deleted entry. -func (i *Index) Remove(path string) (*Entry, error) { - path = filepath.ToSlash(path) - for index, e := range i.Entries { - if e.Name == path { - i.Entries = append(i.Entries[:index], i.Entries[index+1:]...) - return e, nil - } - } - - return nil, ErrEntryNotFound -} - -// Glob returns the all entries matching pattern or nil if there is no matching -// entry. The syntax of patterns is the same as in filepath.Glob. -func (i *Index) Glob(pattern string) (matches []*Entry, err error) { - pattern = filepath.ToSlash(pattern) - for _, e := range i.Entries { - m, err := match(pattern, e.Name) - if err != nil { - return nil, err - } - - if m { - matches = append(matches, e) - } - } - - return -} - -// String is equivalent to `git ls-files --stage --debug` -func (i *Index) String() string { - buf := bytes.NewBuffer(nil) - for _, e := range i.Entries { - buf.WriteString(e.String()) - } - - return buf.String() -} - -// Entry represents a single file (or stage of a file) in the cache. An entry -// represents exactly one stage of a file. If a file path is unmerged then -// multiple Entry instances may appear for the same path name. -type Entry struct { - // Hash is the SHA1 of the represented file - Hash plumbing.Hash - // Name is the Entry path name relative to top level directory - Name string - // CreatedAt time when the tracked path was created - CreatedAt time.Time - // ModifiedAt time when the tracked path was changed - ModifiedAt time.Time - // Dev and Inode of the tracked path - Dev, Inode uint32 - // Mode of the path - Mode filemode.FileMode - // UID and GID, userid and group id of the owner - UID, GID uint32 - // Size is the length in bytes for regular files - Size uint32 - // Stage on a merge is defines what stage is representing this entry - // https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging - Stage Stage - // SkipWorktree used in sparse checkouts - // https://git-scm.com/docs/git-read-tree#_sparse_checkout - SkipWorktree bool - // IntentToAdd record only the fact that the path will be added later - // https://git-scm.com/docs/git-add ("git add -N") - IntentToAdd bool -} - -func (e Entry) String() string { - buf := bytes.NewBuffer(nil) - - fmt.Fprintf(buf, "%06o %s %d\t%s\n", e.Mode, e.Hash, e.Stage, e.Name) - fmt.Fprintf(buf, " ctime: %d:%d\n", e.CreatedAt.Unix(), e.CreatedAt.Nanosecond()) - fmt.Fprintf(buf, " mtime: %d:%d\n", e.ModifiedAt.Unix(), e.ModifiedAt.Nanosecond()) - fmt.Fprintf(buf, " dev: %d\tino: %d\n", e.Dev, e.Inode) - fmt.Fprintf(buf, " uid: %d\tgid: %d\n", e.UID, e.GID) - fmt.Fprintf(buf, " size: %d\tflags: %x\n", e.Size, 0) - - return buf.String() -} - -// Tree contains pre-computed hashes for trees that can be derived from the -// index. It helps speed up tree object generation from index for a new commit. -type Tree struct { - Entries []TreeEntry -} - -// TreeEntry entry of a cached Tree -type TreeEntry struct { - // Path component (relative to its parent directory) - Path string - // Entries is the number of entries in the index that is covered by the tree - // this entry represents. - Entries int - // Trees is the number that represents the number of subtrees this tree has - Trees int - // Hash object name for the object that would result from writing this span - // of index as a tree. - Hash plumbing.Hash -} - -// ResolveUndo is used when a conflict is resolved (e.g. with "git add path"), -// these higher stage entries are removed and a stage-0 entry with proper -// resolution is added. When these higher stage entries are removed, they are -// saved in the resolve undo extension. -type ResolveUndo struct { - Entries []ResolveUndoEntry -} - -// ResolveUndoEntry contains the information about a conflict when is resolved -type ResolveUndoEntry struct { - Path string - Stages map[Stage]plumbing.Hash -} - -// EndOfIndexEntry is the End of Index Entry (EOIE) is used to locate the end of -// the variable length index entries and the beginning of the extensions. Code -// can take advantage of this to quickly locate the index extensions without -// having to parse through all of the index entries. -// -// Because it must be able to be loaded before the variable length cache -// entries and other index extensions, this extension must be written last. -type EndOfIndexEntry struct { - // Offset to the end of the index entries - Offset uint32 - // Hash is a SHA-1 over the extension types and their sizes (but not - // their contents). - Hash plumbing.Hash -} - -// SkipUnless applies patterns in the form of A, A/B, A/B/C -// to the index to prevent the files from being checked out -func (i *Index) SkipUnless(patterns []string) { - for _, e := range i.Entries { - var include bool - for _, pattern := range patterns { - if strings.HasPrefix(e.Name, pattern) { - include = true - break - } - } - if !include { - e.SkipWorktree = true - } - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/match.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/match.go deleted file mode 100644 index 2891d7d34cc..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/index/match.go +++ /dev/null @@ -1,186 +0,0 @@ -package index - -import ( - "path/filepath" - "runtime" - "unicode/utf8" -) - -// match is filepath.Match with support to match fullpath and not only filenames -// code from: -// https://github.com/golang/go/blob/39852bf4cce6927e01d0136c7843f65a801738cb/src/path/filepath/match.go#L44-L224 -func match(pattern, name string) (matched bool, err error) { -Pattern: - for len(pattern) > 0 { - var star bool - var chunk string - star, chunk, pattern = scanChunk(pattern) - - // Look for match at current position. - t, ok, err := matchChunk(chunk, name) - // if we're the last chunk, make sure we've exhausted the name - // otherwise we'll give a false result even if we could still match - // using the star - if ok && (len(t) == 0 || len(pattern) > 0) { - name = t - continue - } - if err != nil { - return false, err - } - if star { - // Look for match skipping i+1 bytes. - // Cannot skip /. - for i := 0; i < len(name); i++ { - t, ok, err := matchChunk(chunk, name[i+1:]) - if ok { - // if we're the last chunk, make sure we exhausted the name - if len(pattern) == 0 && len(t) > 0 { - continue - } - name = t - continue Pattern - } - if err != nil { - return false, err - } - } - } - return false, nil - } - return len(name) == 0, nil -} - -// scanChunk gets the next segment of pattern, which is a non-star string -// possibly preceded by a star. -func scanChunk(pattern string) (star bool, chunk, rest string) { - for len(pattern) > 0 && pattern[0] == '*' { - pattern = pattern[1:] - star = true - } - inrange := false - var i int -Scan: - for i = 0; i < len(pattern); i++ { - switch pattern[i] { - case '\\': - if runtime.GOOS != "windows" { - // error check handled in matchChunk: bad pattern. - if i+1 < len(pattern) { - i++ - } - } - case '[': - inrange = true - case ']': - inrange = false - case '*': - if !inrange { - break Scan - } - } - } - return star, pattern[0:i], pattern[i:] -} - -// matchChunk checks whether chunk matches the beginning of s. -// If so, it returns the remainder of s (after the match). -// Chunk is all single-character operators: literals, char classes, and ?. -func matchChunk(chunk, s string) (rest string, ok bool, err error) { - for len(chunk) > 0 { - if len(s) == 0 { - return - } - switch chunk[0] { - case '[': - // character class - r, n := utf8.DecodeRuneInString(s) - s = s[n:] - chunk = chunk[1:] - // We can't end right after '[', we're expecting at least - // a closing bracket and possibly a caret. - if len(chunk) == 0 { - err = filepath.ErrBadPattern - return - } - // possibly negated - negated := chunk[0] == '^' - if negated { - chunk = chunk[1:] - } - // parse all ranges - match := false - nrange := 0 - for { - if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 { - chunk = chunk[1:] - break - } - var lo, hi rune - if lo, chunk, err = getEsc(chunk); err != nil { - return - } - hi = lo - if chunk[0] == '-' { - if hi, chunk, err = getEsc(chunk[1:]); err != nil { - return - } - } - if lo <= r && r <= hi { - match = true - } - nrange++ - } - if match == negated { - return - } - - case '?': - _, n := utf8.DecodeRuneInString(s) - s = s[n:] - chunk = chunk[1:] - - case '\\': - if runtime.GOOS != "windows" { - chunk = chunk[1:] - if len(chunk) == 0 { - err = filepath.ErrBadPattern - return - } - } - fallthrough - - default: - if chunk[0] != s[0] { - return - } - s = s[1:] - chunk = chunk[1:] - } - } - return s, true, nil -} - -// getEsc gets a possibly-escaped character from chunk, for a character class. -func getEsc(chunk string) (r rune, nchunk string, err error) { - if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { - err = filepath.ErrBadPattern - return - } - if chunk[0] == '\\' && runtime.GOOS != "windows" { - chunk = chunk[1:] - if len(chunk) == 0 { - err = filepath.ErrBadPattern - return - } - } - r, n := utf8.DecodeRuneInString(chunk) - if r == utf8.RuneError && n == 1 { - err = filepath.ErrBadPattern - } - nchunk = chunk[n:] - if len(nchunk) == 0 { - err = filepath.ErrBadPattern - } - return -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/objfile/doc.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/objfile/doc.go deleted file mode 100644 index a7145160ae0..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/objfile/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package objfile implements encoding and decoding of object files. -package objfile diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/objfile/reader.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/objfile/reader.go deleted file mode 100644 index 4339428055a..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/objfile/reader.go +++ /dev/null @@ -1,117 +0,0 @@ -package objfile - -import ( - "errors" - "io" - "strconv" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/packfile" - "github.com/jesseduffield/go-git/v5/utils/sync" -) - -var ( - ErrClosed = errors.New("objfile: already closed") - ErrHeader = errors.New("objfile: invalid header") - ErrNegativeSize = errors.New("objfile: negative object size") -) - -// Reader reads and decodes compressed objfile data from a provided io.Reader. -// Reader implements io.ReadCloser. Close should be called when finished with -// the Reader. Close will not close the underlying io.Reader. -type Reader struct { - multi io.Reader - zlib io.Reader - zlibref sync.ZLibReader - hasher plumbing.Hasher -} - -// NewReader returns a new Reader reading from r. -func NewReader(r io.Reader) (*Reader, error) { - zlib, err := sync.GetZlibReader(r) - if err != nil { - return nil, packfile.ErrZLib.AddDetails(err.Error()) - } - - return &Reader{ - zlib: zlib.Reader, - zlibref: zlib, - }, nil -} - -// Header reads the type and the size of object, and prepares the reader for read -func (r *Reader) Header() (t plumbing.ObjectType, size int64, err error) { - var raw []byte - raw, err = r.readUntil(' ') - if err != nil { - return - } - - t, err = plumbing.ParseObjectType(string(raw)) - if err != nil { - return - } - - raw, err = r.readUntil(0) - if err != nil { - return - } - - size, err = strconv.ParseInt(string(raw), 10, 64) - if err != nil { - err = ErrHeader - return - } - - defer r.prepareForRead(t, size) - return -} - -// readSlice reads one byte at a time from r until it encounters delim or an -// error. -func (r *Reader) readUntil(delim byte) ([]byte, error) { - var buf [1]byte - value := make([]byte, 0, 16) - for { - if n, err := r.zlib.Read(buf[:]); err != nil && (err != io.EOF || n == 0) { - if err == io.EOF { - return nil, ErrHeader - } - return nil, err - } - - if buf[0] == delim { - return value, nil - } - - value = append(value, buf[0]) - } -} - -func (r *Reader) prepareForRead(t plumbing.ObjectType, size int64) { - r.hasher = plumbing.NewHasher(t, size) - r.multi = io.TeeReader(r.zlib, r.hasher) -} - -// Read reads len(p) bytes into p from the object data stream. It returns -// the number of bytes read (0 <= n <= len(p)) and any error encountered. Even -// if Read returns n < len(p), it may use all of p as scratch space during the -// call. -// -// If Read encounters the end of the data stream it will return err == io.EOF, -// either in the current call if n > 0 or in a subsequent call. -func (r *Reader) Read(p []byte) (n int, err error) { - return r.multi.Read(p) -} - -// Hash returns the hash of the object data stream that has been read so far. -func (r *Reader) Hash() plumbing.Hash { - return r.hasher.Sum() -} - -// Close releases any resources consumed by the Reader. Calling Close does not -// close the wrapped io.Reader originally passed to NewReader. -func (r *Reader) Close() error { - sync.PutZlibReader(r.zlibref) - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/objfile/writer.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/objfile/writer.go deleted file mode 100644 index 0d9fae321a2..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/objfile/writer.go +++ /dev/null @@ -1,112 +0,0 @@ -package objfile - -import ( - "compress/zlib" - "errors" - "io" - "strconv" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/utils/sync" -) - -var ( - ErrOverflow = errors.New("objfile: declared data length exceeded (overflow)") -) - -// Writer writes and encodes data in compressed objfile format to a provided -// io.Writer. Close should be called when finished with the Writer. Close will -// not close the underlying io.Writer. -type Writer struct { - raw io.Writer - hasher plumbing.Hasher - multi io.Writer - zlib *zlib.Writer - - closed bool - pending int64 // number of unwritten bytes -} - -// NewWriter returns a new Writer writing to w. -// -// The returned Writer implements io.WriteCloser. Close should be called when -// finished with the Writer. Close will not close the underlying io.Writer. -func NewWriter(w io.Writer) *Writer { - zlib := sync.GetZlibWriter(w) - return &Writer{ - raw: w, - zlib: zlib, - } -} - -// WriteHeader writes the type and the size and prepares to accept the object's -// contents. If an invalid t is provided, plumbing.ErrInvalidType is returned. If a -// negative size is provided, ErrNegativeSize is returned. -func (w *Writer) WriteHeader(t plumbing.ObjectType, size int64) error { - if !t.Valid() { - return plumbing.ErrInvalidType - } - if size < 0 { - return ErrNegativeSize - } - - b := t.Bytes() - b = append(b, ' ') - b = append(b, []byte(strconv.FormatInt(size, 10))...) - b = append(b, 0) - - defer w.prepareForWrite(t, size) - _, err := w.zlib.Write(b) - - return err -} - -func (w *Writer) prepareForWrite(t plumbing.ObjectType, size int64) { - w.pending = size - - w.hasher = plumbing.NewHasher(t, size) - w.multi = io.MultiWriter(w.zlib, w.hasher) -} - -// Write writes the object's contents. Write returns the error ErrOverflow if -// more than size bytes are written after WriteHeader. -func (w *Writer) Write(p []byte) (n int, err error) { - if w.closed { - return 0, ErrClosed - } - - overwrite := false - if int64(len(p)) > w.pending { - p = p[0:w.pending] - overwrite = true - } - - n, err = w.multi.Write(p) - w.pending -= int64(n) - if err == nil && overwrite { - err = ErrOverflow - return - } - - return -} - -// Hash returns the hash of the object data stream that has been written so far. -// It can be called before or after Close. -func (w *Writer) Hash() plumbing.Hash { - return w.hasher.Sum() // Not yet closed, return hash of data written so far -} - -// Close releases any resources consumed by the Writer. -// -// Calling Close does not close the wrapped io.Writer originally passed to -// NewWriter. -func (w *Writer) Close() error { - defer sync.PutZlibWriter(w.zlib) - if err := w.zlib.Close(); err != nil { - return err - } - - w.closed = true - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/common.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/common.go deleted file mode 100644 index 926ac2ebb0e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/common.go +++ /dev/null @@ -1,60 +0,0 @@ -package packfile - -import ( - "io" - - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -var signature = []byte{'P', 'A', 'C', 'K'} - -const ( - // VersionSupported is the packfile version supported by this package - VersionSupported uint32 = 2 - - firstLengthBits = uint8(4) // the first byte into object header has 4 bits to store the length - lengthBits = uint8(7) // subsequent bytes has 7 bits to store the length - maskFirstLength = 15 // 0000 1111 - maskContinue = 0x80 // 1000 0000 - maskLength = uint8(127) // 0111 1111 - maskType = uint8(112) // 0111 0000 -) - -// UpdateObjectStorage updates the storer with the objects in the given -// packfile. -func UpdateObjectStorage(s storer.Storer, packfile io.Reader) error { - if pw, ok := s.(storer.PackfileWriter); ok { - return WritePackfileToObjectStorage(pw, packfile) - } - - p, err := NewParserWithStorage(NewScanner(packfile), s) - if err != nil { - return err - } - - _, err = p.Parse() - return err -} - -// WritePackfileToObjectStorage writes all the packfile objects into the given -// object storage. -func WritePackfileToObjectStorage( - sw storer.PackfileWriter, - packfile io.Reader, -) (err error) { - w, err := sw.PackfileWriter() - if err != nil { - return err - } - - defer ioutil.CheckClose(w, &err) - - var n int64 - n, err = io.Copy(w, packfile) - if err == nil && n == 0 { - return ErrEmptyPackfile - } - - return err -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/delta_index.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/delta_index.go deleted file mode 100644 index a60ec0b24d6..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/delta_index.go +++ /dev/null @@ -1,295 +0,0 @@ -package packfile - -const blksz = 16 -const maxChainLength = 64 - -// deltaIndex is a modified version of JGit's DeltaIndex adapted to our current -// design. -type deltaIndex struct { - table []int - entries []int - mask int -} - -func (idx *deltaIndex) init(buf []byte) { - scanner := newDeltaIndexScanner(buf, len(buf)) - idx.mask = scanner.mask - idx.table = scanner.table - idx.entries = make([]int, countEntries(scanner)+1) - idx.copyEntries(scanner) -} - -// findMatch returns the offset of src where the block starting at tgtOffset -// is and the length of the match. A length of 0 means there was no match. A -// length of -1 means the src length is lower than the blksz and whatever -// other positive length is the length of the match in bytes. -func (idx *deltaIndex) findMatch(src, tgt []byte, tgtOffset int) (srcOffset, l int) { - if len(tgt) < tgtOffset+s { - return 0, len(tgt) - tgtOffset - } - - if len(src) < blksz { - return 0, -1 - } - - h := hashBlock(tgt, tgtOffset) - tIdx := h & idx.mask - eIdx := idx.table[tIdx] - if eIdx == 0 { - return - } - - srcOffset = idx.entries[eIdx] - - l = matchLength(src, tgt, tgtOffset, srcOffset) - - return -} - -func matchLength(src, tgt []byte, otgt, osrc int) (l int) { - lensrc := len(src) - lentgt := len(tgt) - for (osrc < lensrc && otgt < lentgt) && src[osrc] == tgt[otgt] { - l++ - osrc++ - otgt++ - } - return -} - -func countEntries(scan *deltaIndexScanner) (cnt int) { - // Figure out exactly how many entries we need. As we do the - // enumeration truncate any delta chains longer than what we - // are willing to scan during encode. This keeps the encode - // logic linear in the size of the input rather than quadratic. - for i := 0; i < len(scan.table); i++ { - h := scan.table[i] - if h == 0 { - continue - } - - size := 0 - for { - size++ - if size == maxChainLength { - scan.next[h] = 0 - break - } - h = scan.next[h] - - if h == 0 { - break - } - } - cnt += size - } - - return -} - -func (idx *deltaIndex) copyEntries(scanner *deltaIndexScanner) { - // Rebuild the entries list from the scanner, positioning all - // blocks in the same hash chain next to each other. We can - // then later discard the next list, along with the scanner. - // - next := 1 - for i := 0; i < len(idx.table); i++ { - h := idx.table[i] - if h == 0 { - continue - } - - idx.table[i] = next - for { - idx.entries[next] = scanner.entries[h] - next++ - h = scanner.next[h] - - if h == 0 { - break - } - } - } -} - -type deltaIndexScanner struct { - table []int - entries []int - next []int - mask int - count int -} - -func newDeltaIndexScanner(buf []byte, size int) *deltaIndexScanner { - size -= size % blksz - worstCaseBlockCnt := size / blksz - if worstCaseBlockCnt < 1 { - return new(deltaIndexScanner) - } - - tableSize := tableSize(worstCaseBlockCnt) - scanner := &deltaIndexScanner{ - table: make([]int, tableSize), - mask: tableSize - 1, - entries: make([]int, worstCaseBlockCnt+1), - next: make([]int, worstCaseBlockCnt+1), - } - - scanner.scan(buf, size) - return scanner -} - -// slightly modified version of JGit's DeltaIndexScanner. We store the offset on the entries -// instead of the entries and the key, so we avoid operations to retrieve the offset later, as -// we don't use the key. -// See: https://github.com/eclipse/jgit/blob/005e5feb4ecd08c4e4d141a38b9e7942accb3212/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java -func (s *deltaIndexScanner) scan(buf []byte, end int) { - lastHash := 0 - ptr := end - blksz - - for { - key := hashBlock(buf, ptr) - tIdx := key & s.mask - head := s.table[tIdx] - if head != 0 && lastHash == key { - s.entries[head] = ptr - } else { - s.count++ - eIdx := s.count - s.entries[eIdx] = ptr - s.next[eIdx] = head - s.table[tIdx] = eIdx - } - - lastHash = key - ptr -= blksz - - if 0 > ptr { - break - } - } -} - -func tableSize(worstCaseBlockCnt int) int { - shift := 32 - leadingZeros(uint32(worstCaseBlockCnt)) - sz := 1 << uint(shift-1) - if sz < worstCaseBlockCnt { - sz <<= 1 - } - return sz -} - -// use https://golang.org/pkg/math/bits/#LeadingZeros32 in the future -func leadingZeros(x uint32) (n int) { - if x >= 1<<16 { - x >>= 16 - n = 16 - } - if x >= 1<<8 { - x >>= 8 - n += 8 - } - n += int(len8tab[x]) - return 32 - n -} - -var len8tab = [256]uint8{ - 0x00, 0x01, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, - 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, -} - -func hashBlock(raw []byte, ptr int) int { - // The first 4 steps collapse out into a 4 byte big-endian decode, - // with a larger right shift as we combined shift lefts together. - // - hash := ((uint32(raw[ptr]) & 0xff) << 24) | - ((uint32(raw[ptr+1]) & 0xff) << 16) | - ((uint32(raw[ptr+2]) & 0xff) << 8) | - (uint32(raw[ptr+3]) & 0xff) - hash ^= T[hash>>31] - - hash = ((hash << 8) | (uint32(raw[ptr+4]) & 0xff)) ^ T[hash>>23] - hash = ((hash << 8) | (uint32(raw[ptr+5]) & 0xff)) ^ T[hash>>23] - hash = ((hash << 8) | (uint32(raw[ptr+6]) & 0xff)) ^ T[hash>>23] - hash = ((hash << 8) | (uint32(raw[ptr+7]) & 0xff)) ^ T[hash>>23] - - hash = ((hash << 8) | (uint32(raw[ptr+8]) & 0xff)) ^ T[hash>>23] - hash = ((hash << 8) | (uint32(raw[ptr+9]) & 0xff)) ^ T[hash>>23] - hash = ((hash << 8) | (uint32(raw[ptr+10]) & 0xff)) ^ T[hash>>23] - hash = ((hash << 8) | (uint32(raw[ptr+11]) & 0xff)) ^ T[hash>>23] - - hash = ((hash << 8) | (uint32(raw[ptr+12]) & 0xff)) ^ T[hash>>23] - hash = ((hash << 8) | (uint32(raw[ptr+13]) & 0xff)) ^ T[hash>>23] - hash = ((hash << 8) | (uint32(raw[ptr+14]) & 0xff)) ^ T[hash>>23] - hash = ((hash << 8) | (uint32(raw[ptr+15]) & 0xff)) ^ T[hash>>23] - - return int(hash) -} - -var T = []uint32{0x00000000, 0xd4c6b32d, 0x7d4bd577, - 0xa98d665a, 0x2e5119c3, 0xfa97aaee, 0x531accb4, 0x87dc7f99, - 0x5ca23386, 0x886480ab, 0x21e9e6f1, 0xf52f55dc, 0x72f32a45, - 0xa6359968, 0x0fb8ff32, 0xdb7e4c1f, 0x6d82d421, 0xb944670c, - 0x10c90156, 0xc40fb27b, 0x43d3cde2, 0x97157ecf, 0x3e981895, - 0xea5eabb8, 0x3120e7a7, 0xe5e6548a, 0x4c6b32d0, 0x98ad81fd, - 0x1f71fe64, 0xcbb74d49, 0x623a2b13, 0xb6fc983e, 0x0fc31b6f, - 0xdb05a842, 0x7288ce18, 0xa64e7d35, 0x219202ac, 0xf554b181, - 0x5cd9d7db, 0x881f64f6, 0x536128e9, 0x87a79bc4, 0x2e2afd9e, - 0xfaec4eb3, 0x7d30312a, 0xa9f68207, 0x007be45d, 0xd4bd5770, - 0x6241cf4e, 0xb6877c63, 0x1f0a1a39, 0xcbcca914, 0x4c10d68d, - 0x98d665a0, 0x315b03fa, 0xe59db0d7, 0x3ee3fcc8, 0xea254fe5, - 0x43a829bf, 0x976e9a92, 0x10b2e50b, 0xc4745626, 0x6df9307c, - 0xb93f8351, 0x1f8636de, 0xcb4085f3, 0x62cde3a9, 0xb60b5084, - 0x31d72f1d, 0xe5119c30, 0x4c9cfa6a, 0x985a4947, 0x43240558, - 0x97e2b675, 0x3e6fd02f, 0xeaa96302, 0x6d751c9b, 0xb9b3afb6, - 0x103ec9ec, 0xc4f87ac1, 0x7204e2ff, 0xa6c251d2, 0x0f4f3788, - 0xdb8984a5, 0x5c55fb3c, 0x88934811, 0x211e2e4b, 0xf5d89d66, - 0x2ea6d179, 0xfa606254, 0x53ed040e, 0x872bb723, 0x00f7c8ba, - 0xd4317b97, 0x7dbc1dcd, 0xa97aaee0, 0x10452db1, 0xc4839e9c, - 0x6d0ef8c6, 0xb9c84beb, 0x3e143472, 0xead2875f, 0x435fe105, - 0x97995228, 0x4ce71e37, 0x9821ad1a, 0x31accb40, 0xe56a786d, - 0x62b607f4, 0xb670b4d9, 0x1ffdd283, 0xcb3b61ae, 0x7dc7f990, - 0xa9014abd, 0x008c2ce7, 0xd44a9fca, 0x5396e053, 0x8750537e, - 0x2edd3524, 0xfa1b8609, 0x2165ca16, 0xf5a3793b, 0x5c2e1f61, - 0x88e8ac4c, 0x0f34d3d5, 0xdbf260f8, 0x727f06a2, 0xa6b9b58f, - 0x3f0c6dbc, 0xebcade91, 0x4247b8cb, 0x96810be6, 0x115d747f, - 0xc59bc752, 0x6c16a108, 0xb8d01225, 0x63ae5e3a, 0xb768ed17, - 0x1ee58b4d, 0xca233860, 0x4dff47f9, 0x9939f4d4, 0x30b4928e, - 0xe47221a3, 0x528eb99d, 0x86480ab0, 0x2fc56cea, 0xfb03dfc7, - 0x7cdfa05e, 0xa8191373, 0x01947529, 0xd552c604, 0x0e2c8a1b, - 0xdaea3936, 0x73675f6c, 0xa7a1ec41, 0x207d93d8, 0xf4bb20f5, - 0x5d3646af, 0x89f0f582, 0x30cf76d3, 0xe409c5fe, 0x4d84a3a4, - 0x99421089, 0x1e9e6f10, 0xca58dc3d, 0x63d5ba67, 0xb713094a, - 0x6c6d4555, 0xb8abf678, 0x11269022, 0xc5e0230f, 0x423c5c96, - 0x96faefbb, 0x3f7789e1, 0xebb13acc, 0x5d4da2f2, 0x898b11df, - 0x20067785, 0xf4c0c4a8, 0x731cbb31, 0xa7da081c, 0x0e576e46, - 0xda91dd6b, 0x01ef9174, 0xd5292259, 0x7ca44403, 0xa862f72e, - 0x2fbe88b7, 0xfb783b9a, 0x52f55dc0, 0x8633eeed, 0x208a5b62, - 0xf44ce84f, 0x5dc18e15, 0x89073d38, 0x0edb42a1, 0xda1df18c, - 0x739097d6, 0xa75624fb, 0x7c2868e4, 0xa8eedbc9, 0x0163bd93, - 0xd5a50ebe, 0x52797127, 0x86bfc20a, 0x2f32a450, 0xfbf4177d, - 0x4d088f43, 0x99ce3c6e, 0x30435a34, 0xe485e919, 0x63599680, - 0xb79f25ad, 0x1e1243f7, 0xcad4f0da, 0x11aabcc5, 0xc56c0fe8, - 0x6ce169b2, 0xb827da9f, 0x3ffba506, 0xeb3d162b, 0x42b07071, - 0x9676c35c, 0x2f49400d, 0xfb8ff320, 0x5202957a, 0x86c42657, - 0x011859ce, 0xd5deeae3, 0x7c538cb9, 0xa8953f94, 0x73eb738b, - 0xa72dc0a6, 0x0ea0a6fc, 0xda6615d1, 0x5dba6a48, 0x897cd965, - 0x20f1bf3f, 0xf4370c12, 0x42cb942c, 0x960d2701, 0x3f80415b, - 0xeb46f276, 0x6c9a8def, 0xb85c3ec2, 0x11d15898, 0xc517ebb5, - 0x1e69a7aa, 0xcaaf1487, 0x632272dd, 0xb7e4c1f0, 0x3038be69, - 0xe4fe0d44, 0x4d736b1e, 0x99b5d833, -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/delta_selector.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/delta_selector.go deleted file mode 100644 index 1741fbd2293..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/delta_selector.go +++ /dev/null @@ -1,369 +0,0 @@ -package packfile - -import ( - "sort" - "sync" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" -) - -const ( - // deltas based on deltas, how many steps we can do. - // 50 is the default value used in JGit - maxDepth = int64(50) -) - -// applyDelta is the set of object types that we should apply deltas -var applyDelta = map[plumbing.ObjectType]bool{ - plumbing.BlobObject: true, - plumbing.TreeObject: true, -} - -type deltaSelector struct { - storer storer.EncodedObjectStorer -} - -func newDeltaSelector(s storer.EncodedObjectStorer) *deltaSelector { - return &deltaSelector{s} -} - -// ObjectsToPack creates a list of ObjectToPack from the hashes -// provided, creating deltas if it's suitable, using an specific -// internal logic. `packWindow` specifies the size of the sliding -// window used to compare objects for delta compression; 0 turns off -// delta compression entirely. -func (dw *deltaSelector) ObjectsToPack( - hashes []plumbing.Hash, - packWindow uint, -) ([]*ObjectToPack, error) { - otp, err := dw.objectsToPack(hashes, packWindow) - if err != nil { - return nil, err - } - - if packWindow == 0 { - return otp, nil - } - - dw.sort(otp) - - var objectGroups [][]*ObjectToPack - var prev *ObjectToPack - i := -1 - for _, obj := range otp { - if prev == nil || prev.Type() != obj.Type() { - objectGroups = append(objectGroups, []*ObjectToPack{obj}) - i++ - prev = obj - } else { - objectGroups[i] = append(objectGroups[i], obj) - } - } - - var wg sync.WaitGroup - var once sync.Once - for _, objs := range objectGroups { - objs := objs - wg.Add(1) - go func() { - if walkErr := dw.walk(objs, packWindow); walkErr != nil { - once.Do(func() { - err = walkErr - }) - } - wg.Done() - }() - } - wg.Wait() - - if err != nil { - return nil, err - } - - return otp, nil -} - -func (dw *deltaSelector) objectsToPack( - hashes []plumbing.Hash, - packWindow uint, -) ([]*ObjectToPack, error) { - var objectsToPack []*ObjectToPack - for _, h := range hashes { - var o plumbing.EncodedObject - var err error - if packWindow == 0 { - o, err = dw.encodedObject(h) - } else { - o, err = dw.encodedDeltaObject(h) - } - if err != nil { - return nil, err - } - - otp := newObjectToPack(o) - if _, ok := o.(plumbing.DeltaObject); ok { - otp.CleanOriginal() - } - - objectsToPack = append(objectsToPack, otp) - } - - if packWindow == 0 { - return objectsToPack, nil - } - - if err := dw.fixAndBreakChains(objectsToPack); err != nil { - return nil, err - } - - return objectsToPack, nil -} - -func (dw *deltaSelector) encodedDeltaObject(h plumbing.Hash) (plumbing.EncodedObject, error) { - edos, ok := dw.storer.(storer.DeltaObjectStorer) - if !ok { - return dw.encodedObject(h) - } - - return edos.DeltaObject(plumbing.AnyObject, h) -} - -func (dw *deltaSelector) encodedObject(h plumbing.Hash) (plumbing.EncodedObject, error) { - return dw.storer.EncodedObject(plumbing.AnyObject, h) -} - -func (dw *deltaSelector) fixAndBreakChains(objectsToPack []*ObjectToPack) error { - m := make(map[plumbing.Hash]*ObjectToPack, len(objectsToPack)) - for _, otp := range objectsToPack { - m[otp.Hash()] = otp - } - - for _, otp := range objectsToPack { - if err := dw.fixAndBreakChainsOne(m, otp); err != nil { - return err - } - } - - return nil -} - -func (dw *deltaSelector) fixAndBreakChainsOne(objectsToPack map[plumbing.Hash]*ObjectToPack, otp *ObjectToPack) error { - if !otp.Object.Type().IsDelta() { - return nil - } - - // Initial ObjectToPack instances might have a delta assigned to Object - // but no actual base initially. Once Base is assigned to a delta, it means - // we already fixed it. - if otp.Base != nil { - return nil - } - - do, ok := otp.Object.(plumbing.DeltaObject) - if !ok { - // if this is not a DeltaObject, then we cannot retrieve its base, - // so we have to break the delta chain here. - return dw.undeltify(otp) - } - - base, ok := objectsToPack[do.BaseHash()] - if !ok { - // The base of the delta is not in our list of objects to pack, so - // we break the chain. - return dw.undeltify(otp) - } - - if err := dw.fixAndBreakChainsOne(objectsToPack, base); err != nil { - return err - } - - otp.SetDelta(base, otp.Object) - return nil -} - -func (dw *deltaSelector) restoreOriginal(otp *ObjectToPack) error { - if otp.Original != nil { - return nil - } - - if !otp.Object.Type().IsDelta() { - return nil - } - - obj, err := dw.encodedObject(otp.Hash()) - if err != nil { - return err - } - - otp.SetOriginal(obj) - - return nil -} - -// undeltify undeltifies an *ObjectToPack by retrieving the original object from -// the storer and resetting it. -func (dw *deltaSelector) undeltify(otp *ObjectToPack) error { - if err := dw.restoreOriginal(otp); err != nil { - return err - } - - otp.Object = otp.Original - otp.Depth = 0 - return nil -} - -func (dw *deltaSelector) sort(objectsToPack []*ObjectToPack) { - sort.Sort(byTypeAndSize(objectsToPack)) -} - -func (dw *deltaSelector) walk( - objectsToPack []*ObjectToPack, - packWindow uint, -) error { - indexMap := make(map[plumbing.Hash]*deltaIndex) - for i := 0; i < len(objectsToPack); i++ { - // Clean up the index map and reconstructed delta objects for anything - // outside our pack window, to save memory. - if i > int(packWindow) { - obj := objectsToPack[i-int(packWindow)] - - delete(indexMap, obj.Hash()) - - if obj.IsDelta() { - obj.SaveOriginalMetadata() - obj.CleanOriginal() - } - } - - target := objectsToPack[i] - - // If we already have a delta, we don't try to find a new one for this - // object. This happens when a delta is set to be reused from an existing - // packfile. - if target.IsDelta() { - continue - } - - // We only want to create deltas from specific types. - if !applyDelta[target.Type()] { - continue - } - - for j := i - 1; j >= 0 && i-j < int(packWindow); j-- { - base := objectsToPack[j] - // Objects must use only the same type as their delta base. - // Since objectsToPack is sorted by type and size, once we find - // a different type, we know we won't find more of them. - if base.Type() != target.Type() { - break - } - - if err := dw.tryToDeltify(indexMap, base, target); err != nil { - return err - } - } - } - - return nil -} - -func (dw *deltaSelector) tryToDeltify(indexMap map[plumbing.Hash]*deltaIndex, base, target *ObjectToPack) error { - // Original object might not be present if we're reusing a delta, so we - // ensure it is restored. - if err := dw.restoreOriginal(target); err != nil { - return err - } - - if err := dw.restoreOriginal(base); err != nil { - return err - } - - // If the sizes are radically different, this is a bad pairing. - if target.Size() < base.Size()>>4 { - return nil - } - - msz := dw.deltaSizeLimit( - target.Object.Size(), - base.Depth, - target.Depth, - target.IsDelta(), - ) - - // Nearly impossible to fit useful delta. - if msz <= 8 { - return nil - } - - // If we have to insert a lot to make this work, find another. - if base.Size()-target.Size() > msz { - return nil - } - - if _, ok := indexMap[base.Hash()]; !ok { - indexMap[base.Hash()] = new(deltaIndex) - } - - // Now we can generate the delta using originals - delta, err := getDelta(indexMap[base.Hash()], base.Original, target.Original) - if err != nil { - return err - } - - // if delta better than target - if delta.Size() < msz { - target.SetDelta(base, delta) - } - - return nil -} - -func (dw *deltaSelector) deltaSizeLimit(targetSize int64, baseDepth int, - targetDepth int, targetDelta bool) int64 { - if !targetDelta { - // Any delta should be no more than 50% of the original size - // (for text files deflate of whole form should shrink 50%). - n := targetSize >> 1 - - // Evenly distribute delta size limits over allowed depth. - // If src is non-delta (depth = 0), delta <= 50% of original. - // If src is almost at limit (9/10), delta <= 10% of original. - return n * (maxDepth - int64(baseDepth)) / maxDepth - } - - // With a delta base chosen any new delta must be "better". - // Retain the distribution described above. - d := int64(targetDepth) - n := targetSize - - // If target depth is bigger than maxDepth, this delta is not suitable to be used. - if d >= maxDepth { - return 0 - } - - // If src is whole (depth=0) and base is near limit (depth=9/10) - // any delta using src can be 10x larger and still be better. - // - // If src is near limit (depth=9/10) and base is whole (depth=0) - // a new delta dependent on src must be 1/10th the size. - return n * (maxDepth - int64(baseDepth)) / (maxDepth - d) -} - -type byTypeAndSize []*ObjectToPack - -func (a byTypeAndSize) Len() int { return len(a) } - -func (a byTypeAndSize) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -func (a byTypeAndSize) Less(i, j int) bool { - if a[i].Type() < a[j].Type() { - return false - } - - if a[i].Type() > a[j].Type() { - return true - } - - return a[i].Size() > a[j].Size() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/diff_delta.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/diff_delta.go deleted file mode 100644 index bbb36cf2691..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/diff_delta.go +++ /dev/null @@ -1,204 +0,0 @@ -package packfile - -import ( - "bytes" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/jesseduffield/go-git/v5/utils/sync" -) - -// See https://github.com/jelmer/dulwich/blob/master/dulwich/pack.py and -// https://github.com/tarruda/node-git-core/blob/master/src/js/delta.js -// for more info - -const ( - // Standard chunk size used to generate fingerprints - s = 16 - - // https://github.com/git/git/blob/f7466e94375b3be27f229c78873f0acf8301c0a5/diff-delta.c#L428 - // Max size of a copy operation (64KB). - maxCopySize = 64 * 1024 - - // Min size of a copy operation. - minCopySize = 4 -) - -// GetDelta returns an EncodedObject of type OFSDeltaObject. Base and Target object, -// will be loaded into memory to be able to create the delta object. -// To generate target again, you will need the obtained object and "base" one. -// Error will be returned if base or target object cannot be read. -func GetDelta(base, target plumbing.EncodedObject) (plumbing.EncodedObject, error) { - return getDelta(new(deltaIndex), base, target) -} - -func getDelta(index *deltaIndex, base, target plumbing.EncodedObject) (o plumbing.EncodedObject, err error) { - br, err := base.Reader() - if err != nil { - return nil, err - } - - defer ioutil.CheckClose(br, &err) - - tr, err := target.Reader() - if err != nil { - return nil, err - } - - defer ioutil.CheckClose(tr, &err) - - bb := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(bb) - - _, err = bb.ReadFrom(br) - if err != nil { - return nil, err - } - - tb := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(tb) - - _, err = tb.ReadFrom(tr) - if err != nil { - return nil, err - } - - db := diffDelta(index, bb.Bytes(), tb.Bytes()) - delta := &plumbing.MemoryObject{} - _, err = delta.Write(db) - if err != nil { - return nil, err - } - - delta.SetSize(int64(len(db))) - delta.SetType(plumbing.OFSDeltaObject) - - return delta, nil -} - -// DiffDelta returns the delta that transforms src into tgt. -func DiffDelta(src, tgt []byte) []byte { - return diffDelta(new(deltaIndex), src, tgt) -} - -func diffDelta(index *deltaIndex, src []byte, tgt []byte) []byte { - buf := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(buf) - buf.Write(deltaEncodeSize(len(src))) - buf.Write(deltaEncodeSize(len(tgt))) - - if len(index.entries) == 0 { - index.init(src) - } - - ibuf := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(ibuf) - for i := 0; i < len(tgt); i++ { - offset, l := index.findMatch(src, tgt, i) - - if l == 0 { - // couldn't find a match, just write the current byte and continue - ibuf.WriteByte(tgt[i]) - } else if l < 0 { - // src is less than blksz, copy the rest of the target to avoid - // calls to findMatch - for ; i < len(tgt); i++ { - ibuf.WriteByte(tgt[i]) - } - } else if l < s { - // remaining target is less than blksz, copy what's left of it - // and avoid calls to findMatch - for j := i; j < i+l; j++ { - ibuf.WriteByte(tgt[j]) - } - i += l - 1 - } else { - encodeInsertOperation(ibuf, buf) - - rl := l - aOffset := offset - for rl > 0 { - if rl < maxCopySize { - buf.Write(encodeCopyOperation(aOffset, rl)) - break - } - - buf.Write(encodeCopyOperation(aOffset, maxCopySize)) - rl -= maxCopySize - aOffset += maxCopySize - } - - i += l - 1 - } - } - - encodeInsertOperation(ibuf, buf) - - // buf.Bytes() is only valid until the next modifying operation on the buffer. Copy it. - return append([]byte{}, buf.Bytes()...) -} - -func encodeInsertOperation(ibuf, buf *bytes.Buffer) { - if ibuf.Len() == 0 { - return - } - - b := ibuf.Bytes() - s := ibuf.Len() - o := 0 - for { - if s <= 127 { - break - } - buf.WriteByte(byte(127)) - buf.Write(b[o : o+127]) - s -= 127 - o += 127 - } - buf.WriteByte(byte(s)) - buf.Write(b[o : o+s]) - - ibuf.Reset() -} - -func deltaEncodeSize(size int) []byte { - var ret []byte - c := size & 0x7f - size >>= 7 - for { - if size == 0 { - break - } - - ret = append(ret, byte(c|0x80)) - c = size & 0x7f - size >>= 7 - } - ret = append(ret, byte(c)) - - return ret -} - -func encodeCopyOperation(offset, length int) []byte { - code := 0x80 - var opcodes []byte - - var i uint - for i = 0; i < 4; i++ { - f := 0xff << (i * 8) - if offset&f != 0 { - opcodes = append(opcodes, byte(offset&f>>(i*8))) - code |= 0x01 << i - } - } - - for i = 0; i < 3; i++ { - f := 0xff << (i * 8) - if length&f != 0 { - opcodes = append(opcodes, byte(length&f>>(i*8))) - code |= 0x10 << i - } - } - - return append([]byte{byte(code)}, opcodes...) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/doc.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/doc.go deleted file mode 100644 index 2882a7f3782..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/doc.go +++ /dev/null @@ -1,39 +0,0 @@ -// Package packfile implements encoding and decoding of packfile format. -// -// == pack-*.pack files have the following format: -// -// - A header appears at the beginning and consists of the following: -// -// 4-byte signature: -// The signature is: {'P', 'A', 'C', 'K'} -// -// 4-byte version number (network byte order): -// GIT currently accepts version number 2 or 3 but -// generates version 2 only. -// -// 4-byte number of objects contained in the pack (network byte order) -// -// Observation: we cannot have more than 4G versions ;-) and -// more than 4G objects in a pack. -// -// - The header is followed by number of object entries, each of -// which looks like this: -// -// (undeltified representation) -// n-byte type and length (3-bit type, (n-1)*7+4-bit length) -// compressed data -// -// (deltified representation) -// n-byte type and length (3-bit type, (n-1)*7+4-bit length) -// 20-byte base object name -// compressed delta data -// -// Observation: length of each object is encoded in a variable -// length format and is not constrained to 32-bit or anything. -// -// - The trailer records 20-byte SHA1 checksum of all of the above. -// -// -// Source: -// https://www.kernel.org/pub/software/scm/git/docs/v1.7.5/technical/pack-protocol.txt -package packfile diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/encoder.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/encoder.go deleted file mode 100644 index 1d228b5c01e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/encoder.go +++ /dev/null @@ -1,221 +0,0 @@ -package packfile - -import ( - "compress/zlib" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/hash" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/utils/binary" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -// Encoder gets the data from the storage and write it into the writer in PACK -// format -type Encoder struct { - selector *deltaSelector - w *offsetWriter - zw *zlib.Writer - hasher plumbing.Hasher - - useRefDeltas bool -} - -// NewEncoder creates a new packfile encoder using a specific Writer and -// EncodedObjectStorer. By default deltas used to generate the packfile will be -// OFSDeltaObject. To use Reference deltas, set useRefDeltas to true. -func NewEncoder(w io.Writer, s storer.EncodedObjectStorer, useRefDeltas bool) *Encoder { - h := plumbing.Hasher{ - Hash: hash.New(hash.CryptoType), - } - mw := io.MultiWriter(w, h) - ow := newOffsetWriter(mw) - zw := zlib.NewWriter(mw) - return &Encoder{ - selector: newDeltaSelector(s), - w: ow, - zw: zw, - hasher: h, - useRefDeltas: useRefDeltas, - } -} - -// Encode creates a packfile containing all the objects referenced in -// hashes and writes it to the writer in the Encoder. `packWindow` -// specifies the size of the sliding window used to compare objects -// for delta compression; 0 turns off delta compression entirely. -func (e *Encoder) Encode( - hashes []plumbing.Hash, - packWindow uint, -) (plumbing.Hash, error) { - objects, err := e.selector.ObjectsToPack(hashes, packWindow) - if err != nil { - return plumbing.ZeroHash, err - } - - return e.encode(objects) -} - -func (e *Encoder) encode(objects []*ObjectToPack) (plumbing.Hash, error) { - if err := e.head(len(objects)); err != nil { - return plumbing.ZeroHash, err - } - - for _, o := range objects { - if err := e.entry(o); err != nil { - return plumbing.ZeroHash, err - } - } - - return e.footer() -} - -func (e *Encoder) head(numEntries int) error { - return binary.Write( - e.w, - signature, - int32(VersionSupported), - int32(numEntries), - ) -} - -func (e *Encoder) entry(o *ObjectToPack) (err error) { - if o.WantWrite() { - // A cycle exists in this delta chain. This should only occur if a - // selected object representation disappeared during writing - // (for example due to a concurrent repack) and a different base - // was chosen, forcing a cycle. Select something other than a - // delta, and write this object. - e.selector.restoreOriginal(o) - o.BackToOriginal() - } - - if o.IsWritten() { - return nil - } - - o.MarkWantWrite() - - if err := e.writeBaseIfDelta(o); err != nil { - return err - } - - // We need to check if we already write that object due a cyclic delta chain - if o.IsWritten() { - return nil - } - - o.Offset = e.w.Offset() - - if o.IsDelta() { - if err := e.writeDeltaHeader(o); err != nil { - return err - } - } else { - if err := e.entryHead(o.Type(), o.Size()); err != nil { - return err - } - } - - e.zw.Reset(e.w) - - defer ioutil.CheckClose(e.zw, &err) - - or, err := o.Object.Reader() - if err != nil { - return err - } - - defer ioutil.CheckClose(or, &err) - - _, err = io.Copy(e.zw, or) - return err -} - -func (e *Encoder) writeBaseIfDelta(o *ObjectToPack) error { - if o.IsDelta() && !o.Base.IsWritten() { - // We must write base first - return e.entry(o.Base) - } - - return nil -} - -func (e *Encoder) writeDeltaHeader(o *ObjectToPack) error { - // Write offset deltas by default - t := plumbing.OFSDeltaObject - if e.useRefDeltas { - t = plumbing.REFDeltaObject - } - - if err := e.entryHead(t, o.Object.Size()); err != nil { - return err - } - - if e.useRefDeltas { - return e.writeRefDeltaHeader(o.Base.Hash()) - } else { - return e.writeOfsDeltaHeader(o) - } -} - -func (e *Encoder) writeRefDeltaHeader(base plumbing.Hash) error { - return binary.Write(e.w, base) -} - -func (e *Encoder) writeOfsDeltaHeader(o *ObjectToPack) error { - // for OFS_DELTA, offset of the base is interpreted as negative offset - // relative to the type-byte of the header of the ofs-delta entry. - relativeOffset := o.Offset - o.Base.Offset - if relativeOffset <= 0 { - return fmt.Errorf("bad offset for OFS_DELTA entry: %d", relativeOffset) - } - - return binary.WriteVariableWidthInt(e.w, relativeOffset) -} - -func (e *Encoder) entryHead(typeNum plumbing.ObjectType, size int64) error { - t := int64(typeNum) - header := []byte{} - c := (t << firstLengthBits) | (size & maskFirstLength) - size >>= firstLengthBits - for { - if size == 0 { - break - } - header = append(header, byte(c|maskContinue)) - c = size & int64(maskLength) - size >>= lengthBits - } - - header = append(header, byte(c)) - _, err := e.w.Write(header) - - return err -} - -func (e *Encoder) footer() (plumbing.Hash, error) { - h := e.hasher.Sum() - return h, binary.Write(e.w, h) -} - -type offsetWriter struct { - w io.Writer - offset int64 -} - -func newOffsetWriter(w io.Writer) *offsetWriter { - return &offsetWriter{w: w} -} - -func (ow *offsetWriter) Write(p []byte) (n int, err error) { - n, err = ow.w.Write(p) - ow.offset += int64(n) - return n, err -} - -func (ow *offsetWriter) Offset() int64 { - return ow.offset -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/error.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/error.go deleted file mode 100644 index c0b91633131..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/error.go +++ /dev/null @@ -1,30 +0,0 @@ -package packfile - -import "fmt" - -// Error specifies errors returned during packfile parsing. -type Error struct { - reason, details string -} - -// NewError returns a new error. -func NewError(reason string) *Error { - return &Error{reason: reason} -} - -// Error returns a text representation of the error. -func (e *Error) Error() string { - if e.details == "" { - return e.reason - } - - return fmt.Sprintf("%s: %s", e.reason, e.details) -} - -// AddDetails adds details to an error, with additional text. -func (e *Error) AddDetails(format string, args ...interface{}) *Error { - return &Error{ - reason: e.reason, - details: fmt.Sprintf(format, args...), - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/fsobject.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/fsobject.go deleted file mode 100644 index 64db4aa5c16..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/fsobject.go +++ /dev/null @@ -1,119 +0,0 @@ -package packfile - -import ( - "io" - - billy "github.com/go-git/go-billy/v5" - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/cache" - "github.com/jesseduffield/go-git/v5/plumbing/format/idxfile" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -// FSObject is an object from the packfile on the filesystem. -type FSObject struct { - hash plumbing.Hash - offset int64 - size int64 - typ plumbing.ObjectType - index idxfile.Index - fs billy.Filesystem - path string - cache cache.Object - largeObjectThreshold int64 -} - -// NewFSObject creates a new filesystem object. -func NewFSObject( - hash plumbing.Hash, - finalType plumbing.ObjectType, - offset int64, - contentSize int64, - index idxfile.Index, - fs billy.Filesystem, - path string, - cache cache.Object, - largeObjectThreshold int64, -) *FSObject { - return &FSObject{ - hash: hash, - offset: offset, - size: contentSize, - typ: finalType, - index: index, - fs: fs, - path: path, - cache: cache, - largeObjectThreshold: largeObjectThreshold, - } -} - -// Reader implements the plumbing.EncodedObject interface. -func (o *FSObject) Reader() (io.ReadCloser, error) { - obj, ok := o.cache.Get(o.hash) - if ok && obj != o { - reader, err := obj.Reader() - if err != nil { - return nil, err - } - - return reader, nil - } - - f, err := o.fs.Open(o.path) - if err != nil { - return nil, err - } - - p := NewPackfileWithCache(o.index, nil, f, o.cache, o.largeObjectThreshold) - if o.largeObjectThreshold > 0 && o.size > o.largeObjectThreshold { - // We have a big object - h, err := p.objectHeaderAtOffset(o.offset) - if err != nil { - return nil, err - } - - r, err := p.getReaderDirect(h) - if err != nil { - _ = f.Close() - return nil, err - } - return ioutil.NewReadCloserWithCloser(r, f.Close), nil - } - r, err := p.getObjectContent(o.offset) - if err != nil { - _ = f.Close() - return nil, err - } - - if err := f.Close(); err != nil { - return nil, err - } - - return r, nil -} - -// SetSize implements the plumbing.EncodedObject interface. This method -// is a noop. -func (o *FSObject) SetSize(int64) {} - -// SetType implements the plumbing.EncodedObject interface. This method is -// a noop. -func (o *FSObject) SetType(plumbing.ObjectType) {} - -// Hash implements the plumbing.EncodedObject interface. -func (o *FSObject) Hash() plumbing.Hash { return o.hash } - -// Size implements the plumbing.EncodedObject interface. -func (o *FSObject) Size() int64 { return o.size } - -// Type implements the plumbing.EncodedObject interface. -func (o *FSObject) Type() plumbing.ObjectType { - return o.typ -} - -// Writer implements the plumbing.EncodedObject interface. This method always -// returns a nil writer. -func (o *FSObject) Writer() (io.WriteCloser, error) { - return nil, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/object_pack.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/object_pack.go deleted file mode 100644 index 484946dc3cd..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/object_pack.go +++ /dev/null @@ -1,164 +0,0 @@ -package packfile - -import ( - "github.com/jesseduffield/go-git/v5/plumbing" -) - -// ObjectToPack is a representation of an object that is going to be into a -// pack file. -type ObjectToPack struct { - // The main object to pack, it could be any object, including deltas - Object plumbing.EncodedObject - // Base is the object that a delta is based on (it could be also another delta). - // If the main object is not a delta, Base will be null - Base *ObjectToPack - // Original is the object that we can generate applying the delta to - // Base, or the same object as Object in the case of a non-delta - // object. - Original plumbing.EncodedObject - // Depth is the amount of deltas needed to resolve to obtain Original - // (delta based on delta based on ...) - Depth int - - // offset in pack when object has been already written, or 0 if it - // has not been written yet - Offset int64 - - // Information from the original object - resolvedOriginal bool - originalType plumbing.ObjectType - originalSize int64 - originalHash plumbing.Hash -} - -// newObjectToPack creates a correct ObjectToPack based on a non-delta object -func newObjectToPack(o plumbing.EncodedObject) *ObjectToPack { - return &ObjectToPack{ - Object: o, - Original: o, - } -} - -// newDeltaObjectToPack creates a correct ObjectToPack for a delta object, based on -// his base (could be another delta), the delta target (in this case called original), -// and the delta Object itself -func newDeltaObjectToPack(base *ObjectToPack, original, delta plumbing.EncodedObject) *ObjectToPack { - return &ObjectToPack{ - Object: delta, - Base: base, - Original: original, - Depth: base.Depth + 1, - } -} - -// BackToOriginal converts that ObjectToPack to a non-deltified object if it was one -func (o *ObjectToPack) BackToOriginal() { - if o.IsDelta() && o.Original != nil { - o.Object = o.Original - o.Base = nil - o.Depth = 0 - } -} - -// IsWritten returns if that ObjectToPack was -// already written into the packfile or not -func (o *ObjectToPack) IsWritten() bool { - return o.Offset > 1 -} - -// MarkWantWrite marks this ObjectToPack as WantWrite -// to avoid delta chain loops -func (o *ObjectToPack) MarkWantWrite() { - o.Offset = 1 -} - -// WantWrite checks if this ObjectToPack was marked as WantWrite before -func (o *ObjectToPack) WantWrite() bool { - return o.Offset == 1 -} - -// SetOriginal sets both Original and saves size, type and hash. If object -// is nil Original is set but previous resolved values are kept -func (o *ObjectToPack) SetOriginal(obj plumbing.EncodedObject) { - o.Original = obj - o.SaveOriginalMetadata() -} - -// SaveOriginalMetadata saves size, type and hash of Original object -func (o *ObjectToPack) SaveOriginalMetadata() { - if o.Original != nil { - o.originalSize = o.Original.Size() - o.originalType = o.Original.Type() - o.originalHash = o.Original.Hash() - o.resolvedOriginal = true - } -} - -// CleanOriginal sets Original to nil -func (o *ObjectToPack) CleanOriginal() { - o.Original = nil -} - -func (o *ObjectToPack) Type() plumbing.ObjectType { - if o.Original != nil { - return o.Original.Type() - } - - if o.resolvedOriginal { - return o.originalType - } - - if o.Base != nil { - return o.Base.Type() - } - - if o.Object != nil { - return o.Object.Type() - } - - panic("cannot get type") -} - -func (o *ObjectToPack) Hash() plumbing.Hash { - if o.Original != nil { - return o.Original.Hash() - } - - if o.resolvedOriginal { - return o.originalHash - } - - do, ok := o.Object.(plumbing.DeltaObject) - if ok { - return do.ActualHash() - } - - panic("cannot get hash") -} - -func (o *ObjectToPack) Size() int64 { - if o.Original != nil { - return o.Original.Size() - } - - if o.resolvedOriginal { - return o.originalSize - } - - do, ok := o.Object.(plumbing.DeltaObject) - if ok { - return do.ActualSize() - } - - panic("cannot get ObjectToPack size") -} - -func (o *ObjectToPack) IsDelta() bool { - return o.Base != nil -} - -func (o *ObjectToPack) SetDelta(base *ObjectToPack, delta plumbing.EncodedObject) { - o.Object = delta - o.Base = base - o.Depth = base.Depth + 1 -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/packfile.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/packfile.go deleted file mode 100644 index d7d6221172b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/packfile.go +++ /dev/null @@ -1,641 +0,0 @@ -package packfile - -import ( - "bytes" - "fmt" - "io" - "os" - - billy "github.com/go-git/go-billy/v5" - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/cache" - "github.com/jesseduffield/go-git/v5/plumbing/format/idxfile" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/jesseduffield/go-git/v5/utils/sync" -) - -var ( - // ErrInvalidObject is returned by Decode when an invalid object is - // found in the packfile. - ErrInvalidObject = NewError("invalid git object") - // ErrZLib is returned by Decode when there was an error unzipping - // the packfile contents. - ErrZLib = NewError("zlib reading error") -) - -// When reading small objects from packfile it is beneficial to do so at -// once to exploit the buffered I/O. In many cases the objects are so small -// that they were already loaded to memory when the object header was -// loaded from the packfile. Wrapping in FSObject would cause this buffered -// data to be thrown away and then re-read later, with the additional -// seeking causing reloads from disk. Objects smaller than this threshold -// are now always read into memory and stored in cache instead of being -// wrapped in FSObject. -const smallObjectThreshold = 16 * 1024 - -// Packfile allows retrieving information from inside a packfile. -type Packfile struct { - idxfile.Index - fs billy.Filesystem - file billy.File - s *Scanner - deltaBaseCache cache.Object - offsetToType map[int64]plumbing.ObjectType - largeObjectThreshold int64 -} - -// NewPackfileWithCache creates a new Packfile with the given object cache. -// If the filesystem is provided, the packfile will return FSObjects, otherwise -// it will return MemoryObjects. -func NewPackfileWithCache( - index idxfile.Index, - fs billy.Filesystem, - file billy.File, - cache cache.Object, - largeObjectThreshold int64, -) *Packfile { - s := NewScanner(file) - return &Packfile{ - index, - fs, - file, - s, - cache, - make(map[int64]plumbing.ObjectType), - largeObjectThreshold, - } -} - -// NewPackfile returns a packfile representation for the given packfile file -// and packfile idx. -// If the filesystem is provided, the packfile will return FSObjects, otherwise -// it will return MemoryObjects. -func NewPackfile(index idxfile.Index, fs billy.Filesystem, file billy.File, largeObjectThreshold int64) *Packfile { - return NewPackfileWithCache(index, fs, file, cache.NewObjectLRUDefault(), largeObjectThreshold) -} - -// Get retrieves the encoded object in the packfile with the given hash. -func (p *Packfile) Get(h plumbing.Hash) (plumbing.EncodedObject, error) { - offset, err := p.FindOffset(h) - if err != nil { - return nil, err - } - - return p.objectAtOffset(offset, h) -} - -// GetByOffset retrieves the encoded object from the packfile at the given -// offset. -func (p *Packfile) GetByOffset(o int64) (plumbing.EncodedObject, error) { - hash, err := p.FindHash(o) - if err != nil { - return nil, err - } - - return p.objectAtOffset(o, hash) -} - -// GetSizeByOffset retrieves the size of the encoded object from the -// packfile with the given offset. -func (p *Packfile) GetSizeByOffset(o int64) (size int64, err error) { - if _, err := p.s.SeekFromStart(o); err != nil { - if err == io.EOF || isInvalid(err) { - return 0, plumbing.ErrObjectNotFound - } - - return 0, err - } - - h, err := p.nextObjectHeader() - if err != nil { - return 0, err - } - return p.getObjectSize(h) -} - -func (p *Packfile) objectHeaderAtOffset(offset int64) (*ObjectHeader, error) { - h, err := p.s.SeekObjectHeader(offset) - p.s.pendingObject = nil - return h, err -} - -func (p *Packfile) nextObjectHeader() (*ObjectHeader, error) { - h, err := p.s.NextObjectHeader() - p.s.pendingObject = nil - return h, err -} - -func (p *Packfile) getDeltaObjectSize(buf *bytes.Buffer) int64 { - delta := buf.Bytes() - _, delta = decodeLEB128(delta) // skip src size - sz, _ := decodeLEB128(delta) - return int64(sz) -} - -func (p *Packfile) getObjectSize(h *ObjectHeader) (int64, error) { - switch h.Type { - case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject: - return h.Length, nil - case plumbing.REFDeltaObject, plumbing.OFSDeltaObject: - buf := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(buf) - - if _, _, err := p.s.NextObject(buf); err != nil { - return 0, err - } - - return p.getDeltaObjectSize(buf), nil - default: - return 0, ErrInvalidObject.AddDetails("type %q", h.Type) - } -} - -func (p *Packfile) getObjectType(h *ObjectHeader) (typ plumbing.ObjectType, err error) { - switch h.Type { - case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject: - return h.Type, nil - case plumbing.REFDeltaObject, plumbing.OFSDeltaObject: - var offset int64 - if h.Type == plumbing.REFDeltaObject { - offset, err = p.FindOffset(h.Reference) - if err != nil { - return - } - } else { - offset = h.OffsetReference - } - - if baseType, ok := p.offsetToType[offset]; ok { - typ = baseType - } else { - h, err = p.objectHeaderAtOffset(offset) - if err != nil { - return - } - - typ, err = p.getObjectType(h) - if err != nil { - return - } - } - default: - err = ErrInvalidObject.AddDetails("type %q", h.Type) - } - - p.offsetToType[h.Offset] = typ - - return -} - -func (p *Packfile) objectAtOffset(offset int64, hash plumbing.Hash) (plumbing.EncodedObject, error) { - if obj, ok := p.cacheGet(hash); ok { - return obj, nil - } - - h, err := p.objectHeaderAtOffset(offset) - if err != nil { - if err == io.EOF || isInvalid(err) { - return nil, plumbing.ErrObjectNotFound - } - return nil, err - } - - return p.getNextObject(h, hash) -} - -func (p *Packfile) getNextObject(h *ObjectHeader, hash plumbing.Hash) (plumbing.EncodedObject, error) { - var err error - - // If we have no filesystem, we will return a MemoryObject instead - // of an FSObject. - if p.fs == nil { - return p.getNextMemoryObject(h) - } - - // If the object is small enough then read it completely into memory now since - // it is already read from disk into buffer anyway. For delta objects we want - // to perform the optimization too, but we have to be careful about applying - // small deltas on big objects. - var size int64 - if h.Length <= smallObjectThreshold { - if h.Type != plumbing.OFSDeltaObject && h.Type != plumbing.REFDeltaObject { - return p.getNextMemoryObject(h) - } - - // For delta objects we read the delta data and apply the small object - // optimization only if the expanded version of the object still meets - // the small object threshold condition. - buf := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(buf) - - if _, _, err := p.s.NextObject(buf); err != nil { - return nil, err - } - - size = p.getDeltaObjectSize(buf) - if size <= smallObjectThreshold { - var obj = new(plumbing.MemoryObject) - obj.SetSize(size) - if h.Type == plumbing.REFDeltaObject { - err = p.fillREFDeltaObjectContentWithBuffer(obj, h.Reference, buf) - } else { - err = p.fillOFSDeltaObjectContentWithBuffer(obj, h.OffsetReference, buf) - } - return obj, err - } - } else { - size, err = p.getObjectSize(h) - if err != nil { - return nil, err - } - } - - typ, err := p.getObjectType(h) - if err != nil { - return nil, err - } - - p.offsetToType[h.Offset] = typ - - return NewFSObject( - hash, - typ, - h.Offset, - size, - p.Index, - p.fs, - p.file.Name(), - p.deltaBaseCache, - p.largeObjectThreshold, - ), nil -} - -func (p *Packfile) getObjectContent(offset int64) (io.ReadCloser, error) { - h, err := p.objectHeaderAtOffset(offset) - if err != nil { - return nil, err - } - - // getObjectContent is called from FSObject, so we have to explicitly - // get memory object here to avoid recursive cycle - obj, err := p.getNextMemoryObject(h) - if err != nil { - return nil, err - } - - return obj.Reader() -} - -func asyncReader(p *Packfile) (io.ReadCloser, error) { - reader := ioutil.NewReaderUsingReaderAt(p.file, p.s.r.offset) - zr, err := sync.GetZlibReader(reader) - if err != nil { - return nil, fmt.Errorf("zlib reset error: %s", err) - } - - return ioutil.NewReadCloserWithCloser(zr.Reader, func() error { - sync.PutZlibReader(zr) - return nil - }), nil - -} - -func (p *Packfile) getReaderDirect(h *ObjectHeader) (io.ReadCloser, error) { - switch h.Type { - case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject: - return asyncReader(p) - case plumbing.REFDeltaObject: - deltaRc, err := asyncReader(p) - if err != nil { - return nil, err - } - r, err := p.readREFDeltaObjectContent(h, deltaRc) - if err != nil { - return nil, err - } - return r, nil - case plumbing.OFSDeltaObject: - deltaRc, err := asyncReader(p) - if err != nil { - return nil, err - } - r, err := p.readOFSDeltaObjectContent(h, deltaRc) - if err != nil { - return nil, err - } - return r, nil - default: - return nil, ErrInvalidObject.AddDetails("type %q", h.Type) - } -} - -func (p *Packfile) getNextMemoryObject(h *ObjectHeader) (plumbing.EncodedObject, error) { - var obj = new(plumbing.MemoryObject) - obj.SetSize(h.Length) - obj.SetType(h.Type) - - var err error - switch h.Type { - case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject: - err = p.fillRegularObjectContent(obj) - case plumbing.REFDeltaObject: - err = p.fillREFDeltaObjectContent(obj, h.Reference) - case plumbing.OFSDeltaObject: - err = p.fillOFSDeltaObjectContent(obj, h.OffsetReference) - default: - err = ErrInvalidObject.AddDetails("type %q", h.Type) - } - - if err != nil { - return nil, err - } - - p.offsetToType[h.Offset] = obj.Type() - - return obj, nil -} - -func (p *Packfile) fillRegularObjectContent(obj plumbing.EncodedObject) (err error) { - w, err := obj.Writer() - if err != nil { - return err - } - - defer ioutil.CheckClose(w, &err) - - _, _, err = p.s.NextObject(w) - p.cachePut(obj) - - return err -} - -func (p *Packfile) fillREFDeltaObjectContent(obj plumbing.EncodedObject, ref plumbing.Hash) error { - buf := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(buf) - - _, _, err := p.s.NextObject(buf) - if err != nil { - return err - } - - return p.fillREFDeltaObjectContentWithBuffer(obj, ref, buf) -} - -func (p *Packfile) readREFDeltaObjectContent(h *ObjectHeader, deltaRC io.Reader) (io.ReadCloser, error) { - var err error - - base, ok := p.cacheGet(h.Reference) - if !ok { - base, err = p.Get(h.Reference) - if err != nil { - return nil, err - } - } - - return ReaderFromDelta(base, deltaRC) -} - -func (p *Packfile) fillREFDeltaObjectContentWithBuffer(obj plumbing.EncodedObject, ref plumbing.Hash, buf *bytes.Buffer) error { - var err error - - base, ok := p.cacheGet(ref) - if !ok { - base, err = p.Get(ref) - if err != nil { - return err - } - } - - obj.SetType(base.Type()) - err = ApplyDelta(obj, base, buf.Bytes()) - p.cachePut(obj) - - return err -} - -func (p *Packfile) fillOFSDeltaObjectContent(obj plumbing.EncodedObject, offset int64) error { - buf := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(buf) - - _, _, err := p.s.NextObject(buf) - if err != nil { - return err - } - - return p.fillOFSDeltaObjectContentWithBuffer(obj, offset, buf) -} - -func (p *Packfile) readOFSDeltaObjectContent(h *ObjectHeader, deltaRC io.Reader) (io.ReadCloser, error) { - hash, err := p.FindHash(h.OffsetReference) - if err != nil { - return nil, err - } - - base, err := p.objectAtOffset(h.OffsetReference, hash) - if err != nil { - return nil, err - } - - return ReaderFromDelta(base, deltaRC) -} - -func (p *Packfile) fillOFSDeltaObjectContentWithBuffer(obj plumbing.EncodedObject, offset int64, buf *bytes.Buffer) error { - hash, err := p.FindHash(offset) - if err != nil { - return err - } - - base, err := p.objectAtOffset(offset, hash) - if err != nil { - return err - } - - obj.SetType(base.Type()) - err = ApplyDelta(obj, base, buf.Bytes()) - p.cachePut(obj) - - return err -} - -func (p *Packfile) cacheGet(h plumbing.Hash) (plumbing.EncodedObject, bool) { - if p.deltaBaseCache == nil { - return nil, false - } - - return p.deltaBaseCache.Get(h) -} - -func (p *Packfile) cachePut(obj plumbing.EncodedObject) { - if p.deltaBaseCache == nil { - return - } - - p.deltaBaseCache.Put(obj) -} - -// GetAll returns an iterator with all encoded objects in the packfile. -// The iterator returned is not thread-safe, it should be used in the same -// thread as the Packfile instance. -func (p *Packfile) GetAll() (storer.EncodedObjectIter, error) { - return p.GetByType(plumbing.AnyObject) -} - -// GetByType returns all the objects of the given type. -func (p *Packfile) GetByType(typ plumbing.ObjectType) (storer.EncodedObjectIter, error) { - switch typ { - case plumbing.AnyObject, - plumbing.BlobObject, - plumbing.TreeObject, - plumbing.CommitObject, - plumbing.TagObject: - entries, err := p.EntriesByOffset() - if err != nil { - return nil, err - } - - return &objectIter{ - // Easiest way to provide an object decoder is just to pass a Packfile - // instance. To not mess with the seeks, it's a new instance with a - // different scanner but the same cache and offset to hash map for - // reusing as much cache as possible. - p: p, - iter: entries, - typ: typ, - }, nil - default: - return nil, plumbing.ErrInvalidType - } -} - -// ID returns the ID of the packfile, which is the checksum at the end of it. -func (p *Packfile) ID() (plumbing.Hash, error) { - prev, err := p.file.Seek(-20, io.SeekEnd) - if err != nil { - return plumbing.ZeroHash, err - } - - var hash plumbing.Hash - if _, err := io.ReadFull(p.file, hash[:]); err != nil { - return plumbing.ZeroHash, err - } - - if _, err := p.file.Seek(prev, io.SeekStart); err != nil { - return plumbing.ZeroHash, err - } - - return hash, nil -} - -// Scanner returns the packfile's Scanner -func (p *Packfile) Scanner() *Scanner { - return p.s -} - -// Close the packfile and its resources. -func (p *Packfile) Close() error { - closer, ok := p.file.(io.Closer) - if !ok { - return nil - } - - return closer.Close() -} - -type objectIter struct { - p *Packfile - typ plumbing.ObjectType - iter idxfile.EntryIter -} - -func (i *objectIter) Next() (plumbing.EncodedObject, error) { - for { - e, err := i.iter.Next() - if err != nil { - return nil, err - } - - if i.typ != plumbing.AnyObject { - if typ, ok := i.p.offsetToType[int64(e.Offset)]; ok { - if typ != i.typ { - continue - } - } else if obj, ok := i.p.cacheGet(e.Hash); ok { - if obj.Type() != i.typ { - i.p.offsetToType[int64(e.Offset)] = obj.Type() - continue - } - return obj, nil - } else { - h, err := i.p.objectHeaderAtOffset(int64(e.Offset)) - if err != nil { - return nil, err - } - - if h.Type == plumbing.REFDeltaObject || h.Type == plumbing.OFSDeltaObject { - typ, err := i.p.getObjectType(h) - if err != nil { - return nil, err - } - if typ != i.typ { - i.p.offsetToType[int64(e.Offset)] = typ - continue - } - // getObjectType will seek in the file so we cannot use getNextObject safely - return i.p.objectAtOffset(int64(e.Offset), e.Hash) - } else { - if h.Type != i.typ { - i.p.offsetToType[int64(e.Offset)] = h.Type - continue - } - return i.p.getNextObject(h, e.Hash) - } - } - } - - obj, err := i.p.objectAtOffset(int64(e.Offset), e.Hash) - if err != nil { - return nil, err - } - - return obj, nil - } -} - -func (i *objectIter) ForEach(f func(plumbing.EncodedObject) error) error { - for { - o, err := i.Next() - if err != nil { - if err == io.EOF { - return nil - } - return err - } - - if err := f(o); err != nil { - return err - } - } -} - -func (i *objectIter) Close() { - i.iter.Close() -} - -// isInvalid checks whether an error is an os.PathError with an os.ErrInvalid -// error inside. It also checks for the windows error, which is different from -// os.ErrInvalid. -func isInvalid(err error) bool { - pe, ok := err.(*os.PathError) - if !ok { - return false - } - - errstr := pe.Err.Error() - return errstr == errInvalidUnix || errstr == errInvalidWindows -} - -// errInvalidWindows is the Windows equivalent to os.ErrInvalid -const errInvalidWindows = "The parameter is incorrect." - -var errInvalidUnix = os.ErrInvalid.Error() diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/parser.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/parser.go deleted file mode 100644 index 3bdfbe197ae..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/parser.go +++ /dev/null @@ -1,611 +0,0 @@ -package packfile - -import ( - "bytes" - "errors" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/cache" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/jesseduffield/go-git/v5/utils/sync" -) - -var ( - // ErrReferenceDeltaNotFound is returned when the reference delta is not - // found. - ErrReferenceDeltaNotFound = errors.New("reference delta not found") - - // ErrNotSeekableSource is returned when the source for the parser is not - // seekable and a storage was not provided, so it can't be parsed. - ErrNotSeekableSource = errors.New("parser source is not seekable and storage was not provided") - - // ErrDeltaNotCached is returned when the delta could not be found in cache. - ErrDeltaNotCached = errors.New("delta could not be found in cache") -) - -// Observer interface is implemented by index encoders. -type Observer interface { - // OnHeader is called when a new packfile is opened. - OnHeader(count uint32) error - // OnInflatedObjectHeader is called for each object header read. - OnInflatedObjectHeader(t plumbing.ObjectType, objSize int64, pos int64) error - // OnInflatedObjectContent is called for each decoded object. - OnInflatedObjectContent(h plumbing.Hash, pos int64, crc uint32, content []byte) error - // OnFooter is called when decoding is done. - OnFooter(h plumbing.Hash) error -} - -// Parser decodes a packfile and calls any observer associated to it. Is used -// to generate indexes. -type Parser struct { - storage storer.EncodedObjectStorer - scanner *Scanner - count uint32 - oi []*objectInfo - oiByHash map[plumbing.Hash]*objectInfo - oiByOffset map[int64]*objectInfo - checksum plumbing.Hash - - cache *cache.BufferLRU - // delta content by offset, only used if source is not seekable - deltas map[int64][]byte - - ob []Observer -} - -// NewParser creates a new Parser. The Scanner source must be seekable. -// If it's not, NewParserWithStorage should be used instead. -func NewParser(scanner *Scanner, ob ...Observer) (*Parser, error) { - return NewParserWithStorage(scanner, nil, ob...) -} - -// NewParserWithStorage creates a new Parser. The scanner source must either -// be seekable or a storage must be provided. -func NewParserWithStorage( - scanner *Scanner, - storage storer.EncodedObjectStorer, - ob ...Observer, -) (*Parser, error) { - if !scanner.IsSeekable && storage == nil { - return nil, ErrNotSeekableSource - } - - var deltas map[int64][]byte - if !scanner.IsSeekable { - deltas = make(map[int64][]byte) - } - - return &Parser{ - storage: storage, - scanner: scanner, - ob: ob, - count: 0, - cache: cache.NewBufferLRUDefault(), - deltas: deltas, - }, nil -} - -func (p *Parser) forEachObserver(f func(o Observer) error) error { - for _, o := range p.ob { - if err := f(o); err != nil { - return err - } - } - return nil -} - -func (p *Parser) onHeader(count uint32) error { - return p.forEachObserver(func(o Observer) error { - return o.OnHeader(count) - }) -} - -func (p *Parser) onInflatedObjectHeader( - t plumbing.ObjectType, - objSize int64, - pos int64, -) error { - return p.forEachObserver(func(o Observer) error { - return o.OnInflatedObjectHeader(t, objSize, pos) - }) -} - -func (p *Parser) onInflatedObjectContent( - h plumbing.Hash, - pos int64, - crc uint32, - content []byte, -) error { - return p.forEachObserver(func(o Observer) error { - return o.OnInflatedObjectContent(h, pos, crc, content) - }) -} - -func (p *Parser) onFooter(h plumbing.Hash) error { - return p.forEachObserver(func(o Observer) error { - return o.OnFooter(h) - }) -} - -// Parse start decoding phase of the packfile. -func (p *Parser) Parse() (plumbing.Hash, error) { - if err := p.init(); err != nil { - return plumbing.ZeroHash, err - } - - if err := p.indexObjects(); err != nil { - return plumbing.ZeroHash, err - } - - var err error - p.checksum, err = p.scanner.Checksum() - if err != nil && err != io.EOF { - return plumbing.ZeroHash, err - } - - if err := p.resolveDeltas(); err != nil { - return plumbing.ZeroHash, err - } - - if err := p.onFooter(p.checksum); err != nil { - return plumbing.ZeroHash, err - } - - return p.checksum, nil -} - -func (p *Parser) init() error { - _, c, err := p.scanner.Header() - if err != nil { - return err - } - - if err := p.onHeader(c); err != nil { - return err - } - - p.count = c - p.oiByHash = make(map[plumbing.Hash]*objectInfo, p.count) - p.oiByOffset = make(map[int64]*objectInfo, p.count) - p.oi = make([]*objectInfo, p.count) - - return nil -} - -type objectHeaderWriter func(typ plumbing.ObjectType, sz int64) error - -type lazyObjectWriter interface { - // LazyWriter enables an object to be lazily written. - // It returns: - // - w: a writer to receive the object's content. - // - lwh: a func to write the object header. - // - err: any error from the initial writer creation process. - // - // Note that if the object header is not written BEFORE the writer - // is used, this will result in an invalid object. - LazyWriter() (w io.WriteCloser, lwh objectHeaderWriter, err error) -} - -func (p *Parser) indexObjects() error { - buf := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(buf) - - for i := uint32(0); i < p.count; i++ { - oh, err := p.scanner.NextObjectHeader() - if err != nil { - return err - } - - delta := false - var ota *objectInfo - switch t := oh.Type; t { - case plumbing.OFSDeltaObject: - delta = true - - parent, ok := p.oiByOffset[oh.OffsetReference] - if !ok { - return plumbing.ErrObjectNotFound - } - - ota = newDeltaObject(oh.Offset, oh.Length, t, parent) - parent.Children = append(parent.Children, ota) - case plumbing.REFDeltaObject: - delta = true - parent, ok := p.oiByHash[oh.Reference] - if !ok { - // can't find referenced object in this pack file - // this must be a "thin" pack. - parent = &objectInfo{ //Placeholder parent - SHA1: oh.Reference, - ExternalRef: true, // mark as an external reference that must be resolved - Type: plumbing.AnyObject, - DiskType: plumbing.AnyObject, - } - p.oiByHash[oh.Reference] = parent - } - ota = newDeltaObject(oh.Offset, oh.Length, t, parent) - parent.Children = append(parent.Children, ota) - - default: - ota = newBaseObject(oh.Offset, oh.Length, t) - } - - hasher := plumbing.NewHasher(oh.Type, oh.Length) - writers := []io.Writer{hasher} - var obj *plumbing.MemoryObject - - // Lazy writing is only available for non-delta objects. - if p.storage != nil && !delta { - // When a storage is set and supports lazy writing, - // use that instead of creating a memory object. - if low, ok := p.storage.(lazyObjectWriter); ok { - ow, lwh, err := low.LazyWriter() - if err != nil { - return err - } - - if err = lwh(oh.Type, oh.Length); err != nil { - return err - } - - defer ow.Close() - writers = append(writers, ow) - } else { - obj = new(plumbing.MemoryObject) - obj.SetSize(oh.Length) - obj.SetType(oh.Type) - - writers = append(writers, obj) - } - } - if delta && !p.scanner.IsSeekable { - buf.Reset() - buf.Grow(int(oh.Length)) - writers = append(writers, buf) - } - - mw := io.MultiWriter(writers...) - - _, crc, err := p.scanner.NextObject(mw) - if err != nil { - return err - } - - // Non delta objects needs to be added into the storage. This - // is only required when lazy writing is not supported. - if obj != nil { - if _, err := p.storage.SetEncodedObject(obj); err != nil { - return err - } - } - - ota.Crc32 = crc - ota.Length = oh.Length - - if !delta { - sha1 := hasher.Sum() - - // Move children of placeholder parent into actual parent, in case this - // was a non-external delta reference. - if placeholder, ok := p.oiByHash[sha1]; ok { - ota.Children = placeholder.Children - for _, c := range ota.Children { - c.Parent = ota - } - } - - ota.SHA1 = sha1 - p.oiByHash[ota.SHA1] = ota - } - - if delta && !p.scanner.IsSeekable { - data := buf.Bytes() - p.deltas[oh.Offset] = make([]byte, len(data)) - copy(p.deltas[oh.Offset], data) - } - - p.oiByOffset[oh.Offset] = ota - p.oi[i] = ota - } - - return nil -} - -func (p *Parser) resolveDeltas() error { - buf := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(buf) - - for _, obj := range p.oi { - buf.Reset() - buf.Grow(int(obj.Length)) - err := p.get(obj, buf) - if err != nil { - return err - } - - if err := p.onInflatedObjectHeader(obj.Type, obj.Length, obj.Offset); err != nil { - return err - } - - if err := p.onInflatedObjectContent(obj.SHA1, obj.Offset, obj.Crc32, nil); err != nil { - return err - } - - if !obj.IsDelta() && len(obj.Children) > 0 { - // Dealing with an io.ReaderAt object, means we can - // create it once and reuse across all children. - r := bytes.NewReader(buf.Bytes()) - for _, child := range obj.Children { - // Even though we are discarding the output, we still need to read it to - // so that the scanner can advance to the next object, and the SHA1 can be - // calculated. - if err := p.resolveObject(io.Discard, child, r); err != nil { - return err - } - p.resolveExternalRef(child) - } - - // Remove the delta from the cache. - if obj.DiskType.IsDelta() && !p.scanner.IsSeekable { - delete(p.deltas, obj.Offset) - } - } - } - - return nil -} - -func (p *Parser) resolveExternalRef(o *objectInfo) { - if ref, ok := p.oiByHash[o.SHA1]; ok && ref.ExternalRef { - p.oiByHash[o.SHA1] = o - o.Children = ref.Children - for _, c := range o.Children { - c.Parent = o - } - } -} - -func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) (err error) { - if !o.ExternalRef { // skip cache check for placeholder parents - b, ok := p.cache.Get(o.Offset) - if ok { - _, err := buf.Write(b) - return err - } - } - - // If it's not on the cache and is not a delta we can try to find it in the - // storage, if there's one. External refs must enter here. - if p.storage != nil && !o.Type.IsDelta() { - var e plumbing.EncodedObject - e, err = p.storage.EncodedObject(plumbing.AnyObject, o.SHA1) - if err != nil { - return err - } - o.Type = e.Type() - - var r io.ReadCloser - r, err = e.Reader() - if err != nil { - return err - } - - defer ioutil.CheckClose(r, &err) - - _, err = buf.ReadFrom(io.LimitReader(r, e.Size())) - return err - } - - if o.ExternalRef { - // we were not able to resolve a ref in a thin pack - return ErrReferenceDeltaNotFound - } - - if o.DiskType.IsDelta() { - b := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(b) - buf.Grow(int(o.Length)) - err := p.get(o.Parent, b) - if err != nil { - return err - } - - err = p.resolveObject(buf, o, bytes.NewReader(b.Bytes())) - if err != nil { - return err - } - } else { - err := p.readData(buf, o) - if err != nil { - return err - } - } - - // If the scanner is seekable, caching this data into - // memory by offset seems wasteful. - // There is a trade-off to be considered here in terms - // of execution time vs memory consumption. - // - // TODO: improve seekable execution time, so that we can - // skip this cache. - if len(o.Children) > 0 { - data := make([]byte, buf.Len()) - copy(data, buf.Bytes()) - p.cache.Put(o.Offset, data) - } - return nil -} - -// resolveObject resolves an object from base, using information -// provided by o. -// -// This call has the side-effect of changing field values -// from the object info o: -// - Type: OFSDeltaObject may become the target type (e.g. Blob). -// - Size: The size may be update with the target size. -// - Hash: Zero hashes will be calculated as part of the object -// resolution. Hence why this process can't be avoided even when w -// is an io.Discard. -// -// base must be an io.ReaderAt, which is a requirement from -// patchDeltaStream. The main reason being that reversing an -// delta object may lead to going backs and forths within base, -// which is not supported by io.Reader. -func (p *Parser) resolveObject( - w io.Writer, - o *objectInfo, - base io.ReaderAt, -) error { - if !o.DiskType.IsDelta() { - return nil - } - buf := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(buf) - err := p.readData(buf, o) - if err != nil { - return err - } - - writers := []io.Writer{w} - var obj *plumbing.MemoryObject - var lwh objectHeaderWriter - - if p.storage != nil { - if low, ok := p.storage.(lazyObjectWriter); ok { - ow, wh, err := low.LazyWriter() - if err != nil { - return err - } - lwh = wh - - defer ow.Close() - writers = append(writers, ow) - } else { - obj = new(plumbing.MemoryObject) - ow, err := obj.Writer() - if err != nil { - return err - } - - writers = append(writers, ow) - } - } - - mw := io.MultiWriter(writers...) - - err = applyPatchBase(o, base, buf, mw, lwh) - if err != nil { - return err - } - - if obj != nil { - obj.SetType(o.Type) - obj.SetSize(o.Size()) // Size here is correct as it was populated by applyPatchBase. - if _, err := p.storage.SetEncodedObject(obj); err != nil { - return err - } - } - return err -} - -func (p *Parser) readData(w io.Writer, o *objectInfo) error { - if !p.scanner.IsSeekable && o.DiskType.IsDelta() { - data, ok := p.deltas[o.Offset] - if !ok { - return ErrDeltaNotCached - } - _, err := w.Write(data) - return err - } - - if _, err := p.scanner.SeekObjectHeader(o.Offset); err != nil { - return err - } - - if _, _, err := p.scanner.NextObject(w); err != nil { - return err - } - return nil -} - -// applyPatchBase applies the patch to target. -// -// Note that ota will be updated based on the description in resolveObject. -func applyPatchBase(ota *objectInfo, base io.ReaderAt, delta io.Reader, target io.Writer, wh objectHeaderWriter) error { - if target == nil { - return fmt.Errorf("cannot apply patch against nil target") - } - - typ := ota.Type - if ota.SHA1 == plumbing.ZeroHash { - typ = ota.Parent.Type - } - - sz, h, err := patchDeltaWriter(target, base, delta, typ, wh) - if err != nil { - return err - } - - if ota.SHA1 == plumbing.ZeroHash { - ota.Type = typ - ota.Length = int64(sz) - ota.SHA1 = h - } - - return nil -} - -func getSHA1(t plumbing.ObjectType, data []byte) (plumbing.Hash, error) { - hasher := plumbing.NewHasher(t, int64(len(data))) - if _, err := hasher.Write(data); err != nil { - return plumbing.ZeroHash, err - } - - return hasher.Sum(), nil -} - -type objectInfo struct { - Offset int64 - Length int64 - Type plumbing.ObjectType - DiskType plumbing.ObjectType - ExternalRef bool // indicates this is an external reference in a thin pack file - - Crc32 uint32 - - Parent *objectInfo - Children []*objectInfo - SHA1 plumbing.Hash -} - -func newBaseObject(offset, length int64, t plumbing.ObjectType) *objectInfo { - return newDeltaObject(offset, length, t, nil) -} - -func newDeltaObject( - offset, length int64, - t plumbing.ObjectType, - parent *objectInfo, -) *objectInfo { - obj := &objectInfo{ - Offset: offset, - Length: length, - Type: t, - DiskType: t, - Crc32: 0, - Parent: parent, - } - - return obj -} - -func (o *objectInfo) IsDelta() bool { - return o.Type.IsDelta() -} - -func (o *objectInfo) Size() int64 { - return o.Length -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/patch_delta.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/patch_delta.go deleted file mode 100644 index 54d9b08b285..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/patch_delta.go +++ /dev/null @@ -1,543 +0,0 @@ -package packfile - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "math" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/jesseduffield/go-git/v5/utils/sync" -) - -// See https://github.com/git/git/blob/49fa3dc76179e04b0833542fa52d0f287a4955ac/delta.h -// https://github.com/git/git/blob/c2c5f6b1e479f2c38e0e01345350620944e3527f/patch-delta.c, -// and https://github.com/tarruda/node-git-core/blob/master/src/js/delta.js -// for details about the delta format. - -var ( - ErrInvalidDelta = errors.New("invalid delta") - ErrDeltaCmd = errors.New("wrong delta command") -) - -const ( - payload = 0x7f // 0111 1111 - continuation = 0x80 // 1000 0000 - - // maxPatchPreemptionSize defines what is the max size of bytes to be - // premptively made available for a patch operation. - maxPatchPreemptionSize uint = 65536 - - // minDeltaSize defines the smallest size for a delta. - minDeltaSize = 4 -) - -type offset struct { - mask byte - shift uint -} - -var offsets = []offset{ - {mask: 0x01, shift: 0}, - {mask: 0x02, shift: 8}, - {mask: 0x04, shift: 16}, - {mask: 0x08, shift: 24}, -} - -var sizes = []offset{ - {mask: 0x10, shift: 0}, - {mask: 0x20, shift: 8}, - {mask: 0x40, shift: 16}, -} - -// ApplyDelta writes to target the result of applying the modification deltas in delta to base. -func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) (err error) { - r, err := base.Reader() - if err != nil { - return err - } - - defer ioutil.CheckClose(r, &err) - - w, err := target.Writer() - if err != nil { - return err - } - - defer ioutil.CheckClose(w, &err) - - buf := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(buf) - _, err = buf.ReadFrom(r) - if err != nil { - return err - } - src := buf.Bytes() - - dst := sync.GetBytesBuffer() - defer sync.PutBytesBuffer(dst) - err = patchDelta(dst, src, delta) - if err != nil { - return err - } - - target.SetSize(int64(dst.Len())) - - b := sync.GetByteSlice() - _, err = io.CopyBuffer(w, dst, *b) - sync.PutByteSlice(b) - return err -} - -// PatchDelta returns the result of applying the modification deltas in delta to src. -// An error will be returned if delta is corrupted (ErrInvalidDelta) or an action command -// is not copy from source or copy from delta (ErrDeltaCmd). -func PatchDelta(src, delta []byte) ([]byte, error) { - if len(src) == 0 || len(delta) < minDeltaSize { - return nil, ErrInvalidDelta - } - - b := &bytes.Buffer{} - if err := patchDelta(b, src, delta); err != nil { - return nil, err - } - return b.Bytes(), nil -} - -func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadCloser, error) { - deltaBuf := bufio.NewReaderSize(deltaRC, 1024) - srcSz, err := decodeLEB128ByteReader(deltaBuf) - if err != nil { - if err == io.EOF { - return nil, ErrInvalidDelta - } - return nil, err - } - if srcSz != uint(base.Size()) { - return nil, ErrInvalidDelta - } - - targetSz, err := decodeLEB128ByteReader(deltaBuf) - if err != nil { - if err == io.EOF { - return nil, ErrInvalidDelta - } - return nil, err - } - remainingTargetSz := targetSz - - dstRd, dstWr := io.Pipe() - - go func() { - baseRd, err := base.Reader() - if err != nil { - _ = dstWr.CloseWithError(ErrInvalidDelta) - return - } - defer baseRd.Close() - - baseBuf := bufio.NewReader(baseRd) - basePos := uint(0) - - for { - cmd, err := deltaBuf.ReadByte() - if err == io.EOF { - _ = dstWr.CloseWithError(ErrInvalidDelta) - return - } - if err != nil { - _ = dstWr.CloseWithError(err) - return - } - - switch { - case isCopyFromSrc(cmd): - offset, err := decodeOffsetByteReader(cmd, deltaBuf) - if err != nil { - _ = dstWr.CloseWithError(err) - return - } - sz, err := decodeSizeByteReader(cmd, deltaBuf) - if err != nil { - _ = dstWr.CloseWithError(err) - return - } - - if invalidSize(sz, targetSz) || - invalidOffsetSize(offset, sz, srcSz) { - _ = dstWr.Close() - return - } - - discard := offset - basePos - if basePos > offset { - _ = baseRd.Close() - baseRd, err = base.Reader() - if err != nil { - _ = dstWr.CloseWithError(ErrInvalidDelta) - return - } - baseBuf.Reset(baseRd) - discard = offset - } - for discard > math.MaxInt32 { - n, err := baseBuf.Discard(math.MaxInt32) - if err != nil { - _ = dstWr.CloseWithError(err) - return - } - basePos += uint(n) - discard -= uint(n) - } - for discard > 0 { - n, err := baseBuf.Discard(int(discard)) - if err != nil { - _ = dstWr.CloseWithError(err) - return - } - basePos += uint(n) - discard -= uint(n) - } - if _, err := io.Copy(dstWr, io.LimitReader(baseBuf, int64(sz))); err != nil { - _ = dstWr.CloseWithError(err) - return - } - remainingTargetSz -= sz - basePos += sz - - case isCopyFromDelta(cmd): - sz := uint(cmd) // cmd is the size itself - if invalidSize(sz, targetSz) { - _ = dstWr.CloseWithError(ErrInvalidDelta) - return - } - if _, err := io.Copy(dstWr, io.LimitReader(deltaBuf, int64(sz))); err != nil { - _ = dstWr.CloseWithError(err) - return - } - - remainingTargetSz -= sz - - default: - _ = dstWr.CloseWithError(ErrDeltaCmd) - return - } - - if remainingTargetSz <= 0 { - _ = dstWr.Close() - return - } - } - }() - - return dstRd, nil -} - -func patchDelta(dst *bytes.Buffer, src, delta []byte) error { - if len(delta) < minCopySize { - return ErrInvalidDelta - } - - srcSz, delta := decodeLEB128(delta) - if srcSz != uint(len(src)) { - return ErrInvalidDelta - } - - targetSz, delta := decodeLEB128(delta) - remainingTargetSz := targetSz - - var cmd byte - - growSz := min(targetSz, maxPatchPreemptionSize) - dst.Grow(int(growSz)) - for { - if len(delta) == 0 { - return ErrInvalidDelta - } - - cmd = delta[0] - delta = delta[1:] - - switch { - case isCopyFromSrc(cmd): - var offset, sz uint - var err error - offset, delta, err = decodeOffset(cmd, delta) - if err != nil { - return err - } - - sz, delta, err = decodeSize(cmd, delta) - if err != nil { - return err - } - - if invalidSize(sz, targetSz) || - invalidOffsetSize(offset, sz, srcSz) { - break - } - dst.Write(src[offset : offset+sz]) - remainingTargetSz -= sz - - case isCopyFromDelta(cmd): - sz := uint(cmd) // cmd is the size itself - if invalidSize(sz, targetSz) { - return ErrInvalidDelta - } - - if uint(len(delta)) < sz { - return ErrInvalidDelta - } - - dst.Write(delta[0:sz]) - remainingTargetSz -= sz - delta = delta[sz:] - - default: - return ErrDeltaCmd - } - - if remainingTargetSz <= 0 { - break - } - } - - return nil -} - -func patchDeltaWriter(dst io.Writer, base io.ReaderAt, delta io.Reader, - typ plumbing.ObjectType, writeHeader objectHeaderWriter) (uint, plumbing.Hash, error) { - deltaBuf := bufio.NewReaderSize(delta, 1024) - srcSz, err := decodeLEB128ByteReader(deltaBuf) - if err != nil { - if err == io.EOF { - return 0, plumbing.ZeroHash, ErrInvalidDelta - } - return 0, plumbing.ZeroHash, err - } - - if r, ok := base.(*bytes.Reader); ok && srcSz != uint(r.Size()) { - return 0, plumbing.ZeroHash, ErrInvalidDelta - } - - targetSz, err := decodeLEB128ByteReader(deltaBuf) - if err != nil { - if err == io.EOF { - return 0, plumbing.ZeroHash, ErrInvalidDelta - } - return 0, plumbing.ZeroHash, err - } - - // If header still needs to be written, caller will provide - // a LazyObjectWriterHeader. This seems to be the case when - // dealing with thin-packs. - if writeHeader != nil { - err = writeHeader(typ, int64(targetSz)) - if err != nil { - return 0, plumbing.ZeroHash, fmt.Errorf("could not lazy write header: %w", err) - } - } - - remainingTargetSz := targetSz - - hasher := plumbing.NewHasher(typ, int64(targetSz)) - mw := io.MultiWriter(dst, hasher) - - bufp := sync.GetByteSlice() - defer sync.PutByteSlice(bufp) - - sr := io.NewSectionReader(base, int64(0), int64(srcSz)) - // Keep both the io.LimitedReader types, so we can reset N. - baselr := io.LimitReader(sr, 0).(*io.LimitedReader) - deltalr := io.LimitReader(deltaBuf, 0).(*io.LimitedReader) - - for { - buf := *bufp - cmd, err := deltaBuf.ReadByte() - if err == io.EOF { - return 0, plumbing.ZeroHash, ErrInvalidDelta - } - if err != nil { - return 0, plumbing.ZeroHash, err - } - - if isCopyFromSrc(cmd) { - offset, err := decodeOffsetByteReader(cmd, deltaBuf) - if err != nil { - return 0, plumbing.ZeroHash, err - } - sz, err := decodeSizeByteReader(cmd, deltaBuf) - if err != nil { - return 0, plumbing.ZeroHash, err - } - - if invalidSize(sz, targetSz) || - invalidOffsetSize(offset, sz, srcSz) { - return 0, plumbing.ZeroHash, err - } - - if _, err := sr.Seek(int64(offset), io.SeekStart); err != nil { - return 0, plumbing.ZeroHash, err - } - baselr.N = int64(sz) - if _, err := io.CopyBuffer(mw, baselr, buf); err != nil { - return 0, plumbing.ZeroHash, err - } - remainingTargetSz -= sz - } else if isCopyFromDelta(cmd) { - sz := uint(cmd) // cmd is the size itself - if invalidSize(sz, targetSz) { - return 0, plumbing.ZeroHash, ErrInvalidDelta - } - deltalr.N = int64(sz) - if _, err := io.CopyBuffer(mw, deltalr, buf); err != nil { - return 0, plumbing.ZeroHash, err - } - - remainingTargetSz -= sz - } else { - return 0, plumbing.ZeroHash, err - } - if remainingTargetSz <= 0 { - break - } - } - - return targetSz, hasher.Sum(), nil -} - -// Decodes a number encoded as an unsigned LEB128 at the start of some -// binary data and returns the decoded number and the rest of the -// stream. -// -// This must be called twice on the delta data buffer, first to get the -// expected source buffer size, and again to get the target buffer size. -func decodeLEB128(input []byte) (uint, []byte) { - if len(input) == 0 { - return 0, input - } - - var num, sz uint - var b byte - for { - b = input[sz] - num |= (uint(b) & payload) << (sz * 7) // concats 7 bits chunks - sz++ - - if uint(b)&continuation == 0 || sz == uint(len(input)) { - break - } - } - - return num, input[sz:] -} - -func decodeLEB128ByteReader(input io.ByteReader) (uint, error) { - var num, sz uint - for { - b, err := input.ReadByte() - if err != nil { - return 0, err - } - - num |= (uint(b) & payload) << (sz * 7) // concats 7 bits chunks - sz++ - - if uint(b)&continuation == 0 { - break - } - } - - return num, nil -} - -func isCopyFromSrc(cmd byte) bool { - return (cmd & continuation) != 0 -} - -func isCopyFromDelta(cmd byte) bool { - return (cmd&continuation) == 0 && cmd != 0 -} - -func decodeOffsetByteReader(cmd byte, delta io.ByteReader) (uint, error) { - var offset uint - for _, o := range offsets { - if (cmd & o.mask) != 0 { - next, err := delta.ReadByte() - if err != nil { - return 0, err - } - offset |= uint(next) << o.shift - } - } - - return offset, nil -} - -func decodeOffset(cmd byte, delta []byte) (uint, []byte, error) { - var offset uint - for _, o := range offsets { - if (cmd & o.mask) != 0 { - if len(delta) == 0 { - return 0, nil, ErrInvalidDelta - } - offset |= uint(delta[0]) << o.shift - delta = delta[1:] - } - } - - return offset, delta, nil -} - -func decodeSizeByteReader(cmd byte, delta io.ByteReader) (uint, error) { - var sz uint - for _, s := range sizes { - if (cmd & s.mask) != 0 { - next, err := delta.ReadByte() - if err != nil { - return 0, err - } - sz |= uint(next) << s.shift - } - } - - if sz == 0 { - sz = maxCopySize - } - - return sz, nil -} - -func decodeSize(cmd byte, delta []byte) (uint, []byte, error) { - var sz uint - for _, s := range sizes { - if (cmd & s.mask) != 0 { - if len(delta) == 0 { - return 0, nil, ErrInvalidDelta - } - sz |= uint(delta[0]) << s.shift - delta = delta[1:] - } - } - if sz == 0 { - sz = maxCopySize - } - - return sz, delta, nil -} - -func invalidSize(sz, targetSz uint) bool { - return sz > targetSz -} - -func invalidOffsetSize(offset, sz, srcSz uint) bool { - return sumOverflows(offset, sz) || - offset+sz > srcSz -} - -func sumOverflows(a, b uint) bool { - return a+b < a -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/scanner.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/scanner.go deleted file mode 100644 index 47b7df3e49a..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/packfile/scanner.go +++ /dev/null @@ -1,474 +0,0 @@ -package packfile - -import ( - "bufio" - "bytes" - "fmt" - "hash" - "hash/crc32" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/utils/binary" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/jesseduffield/go-git/v5/utils/sync" -) - -var ( - // ErrEmptyPackfile is returned by ReadHeader when no data is found in the packfile - ErrEmptyPackfile = NewError("empty packfile") - // ErrBadSignature is returned by ReadHeader when the signature in the packfile is incorrect. - ErrBadSignature = NewError("malformed pack file signature") - // ErrUnsupportedVersion is returned by ReadHeader when the packfile version is - // different than VersionSupported. - ErrUnsupportedVersion = NewError("unsupported packfile version") - // ErrSeekNotSupported returned if seek is not support - ErrSeekNotSupported = NewError("not seek support") -) - -// ObjectHeader contains the information related to the object, this information -// is collected from the previous bytes to the content of the object. -type ObjectHeader struct { - Type plumbing.ObjectType - Offset int64 - Length int64 - Reference plumbing.Hash - OffsetReference int64 -} - -type Scanner struct { - r *scannerReader - crc hash.Hash32 - - // pendingObject is used to detect if an object has been read, or still - // is waiting to be read - pendingObject *ObjectHeader - version, objects uint32 - - // lsSeekable says if this scanner can do Seek or not, to have a Scanner - // seekable a r implementing io.Seeker is required - IsSeekable bool -} - -// NewScanner returns a new Scanner based on a reader, if the given reader -// implements io.ReadSeeker the Scanner will be also Seekable -func NewScanner(r io.Reader) *Scanner { - _, ok := r.(io.ReadSeeker) - - crc := crc32.NewIEEE() - return &Scanner{ - r: newScannerReader(r, crc), - crc: crc, - IsSeekable: ok, - } -} - -func (s *Scanner) Reset(r io.Reader) { - _, ok := r.(io.ReadSeeker) - - s.r.Reset(r) - s.crc.Reset() - s.IsSeekable = ok - s.pendingObject = nil - s.version = 0 - s.objects = 0 -} - -// Header reads the whole packfile header (signature, version and object count). -// It returns the version and the object count and performs checks on the -// validity of the signature and the version fields. -func (s *Scanner) Header() (version, objects uint32, err error) { - if s.version != 0 { - return s.version, s.objects, nil - } - - sig, err := s.readSignature() - if err != nil { - if err == io.EOF { - err = ErrEmptyPackfile - } - - return - } - - if !s.isValidSignature(sig) { - err = ErrBadSignature - return - } - - version, err = s.readVersion() - s.version = version - if err != nil { - return - } - - if !s.isSupportedVersion(version) { - err = ErrUnsupportedVersion.AddDetails("%d", version) - return - } - - objects, err = s.readCount() - s.objects = objects - return -} - -// readSignature reads a returns the signature field in the packfile. -func (s *Scanner) readSignature() ([]byte, error) { - var sig = make([]byte, 4) - if _, err := io.ReadFull(s.r, sig); err != nil { - return []byte{}, err - } - - return sig, nil -} - -// isValidSignature returns if sig is a valid packfile signature. -func (s *Scanner) isValidSignature(sig []byte) bool { - return bytes.Equal(sig, signature) -} - -// readVersion reads and returns the version field of a packfile. -func (s *Scanner) readVersion() (uint32, error) { - return binary.ReadUint32(s.r) -} - -// isSupportedVersion returns whether version v is supported by the parser. -// The current supported version is VersionSupported, defined above. -func (s *Scanner) isSupportedVersion(v uint32) bool { - return v == VersionSupported -} - -// readCount reads and returns the count of objects field of a packfile. -func (s *Scanner) readCount() (uint32, error) { - return binary.ReadUint32(s.r) -} - -// SeekObjectHeader seeks to specified offset and returns the ObjectHeader -// for the next object in the reader -func (s *Scanner) SeekObjectHeader(offset int64) (*ObjectHeader, error) { - // if seeking we assume that you are not interested in the header - if s.version == 0 { - s.version = VersionSupported - } - - if _, err := s.r.Seek(offset, io.SeekStart); err != nil { - return nil, err - } - - h, err := s.nextObjectHeader() - if err != nil { - return nil, err - } - - h.Offset = offset - return h, nil -} - -// NextObjectHeader returns the ObjectHeader for the next object in the reader -func (s *Scanner) NextObjectHeader() (*ObjectHeader, error) { - if err := s.doPending(); err != nil { - return nil, err - } - - offset, err := s.r.Seek(0, io.SeekCurrent) - if err != nil { - return nil, err - } - - h, err := s.nextObjectHeader() - if err != nil { - return nil, err - } - - h.Offset = offset - return h, nil -} - -// nextObjectHeader returns the ObjectHeader for the next object in the reader -// without the Offset field -func (s *Scanner) nextObjectHeader() (*ObjectHeader, error) { - s.r.Flush() - s.crc.Reset() - - h := &ObjectHeader{} - s.pendingObject = h - - var err error - h.Offset, err = s.r.Seek(0, io.SeekCurrent) - if err != nil { - return nil, err - } - - h.Type, h.Length, err = s.readObjectTypeAndLength() - if err != nil { - return nil, err - } - - switch h.Type { - case plumbing.OFSDeltaObject: - no, err := binary.ReadVariableWidthInt(s.r) - if err != nil { - return nil, err - } - - h.OffsetReference = h.Offset - no - case plumbing.REFDeltaObject: - var err error - h.Reference, err = binary.ReadHash(s.r) - if err != nil { - return nil, err - } - } - - return h, nil -} - -func (s *Scanner) doPending() error { - if s.version == 0 { - var err error - s.version, s.objects, err = s.Header() - if err != nil { - return err - } - } - - return s.discardObjectIfNeeded() -} - -func (s *Scanner) discardObjectIfNeeded() error { - if s.pendingObject == nil { - return nil - } - - h := s.pendingObject - n, _, err := s.NextObject(io.Discard) - if err != nil { - return err - } - - if n != h.Length { - return fmt.Errorf( - "error discarding object, discarded %d, expected %d", - n, h.Length, - ) - } - - return nil -} - -// ReadObjectTypeAndLength reads and returns the object type and the -// length field from an object entry in a packfile. -func (s *Scanner) readObjectTypeAndLength() (plumbing.ObjectType, int64, error) { - t, c, err := s.readType() - if err != nil { - return t, 0, err - } - - l, err := s.readLength(c) - - return t, l, err -} - -func (s *Scanner) readType() (plumbing.ObjectType, byte, error) { - var c byte - var err error - if c, err = s.r.ReadByte(); err != nil { - return plumbing.ObjectType(0), 0, err - } - - typ := parseType(c) - - return typ, c, nil -} - -func parseType(b byte) plumbing.ObjectType { - return plumbing.ObjectType((b & maskType) >> firstLengthBits) -} - -// the length is codified in the last 4 bits of the first byte and in -// the last 7 bits of subsequent bytes. Last byte has a 0 MSB. -func (s *Scanner) readLength(first byte) (int64, error) { - length := int64(first & maskFirstLength) - - c := first - shift := firstLengthBits - var err error - for c&maskContinue > 0 { - if c, err = s.r.ReadByte(); err != nil { - return 0, err - } - - length += int64(c&maskLength) << shift - shift += lengthBits - } - - return length, nil -} - -// NextObject writes the content of the next object into the reader, returns -// the number of bytes written, the CRC32 of the content and an error, if any -func (s *Scanner) NextObject(w io.Writer) (written int64, crc32 uint32, err error) { - s.pendingObject = nil - written, err = s.copyObject(w) - - s.r.Flush() - crc32 = s.crc.Sum32() - s.crc.Reset() - - return -} - -// ReadObject returns a reader for the object content and an error -func (s *Scanner) ReadObject() (io.ReadCloser, error) { - s.pendingObject = nil - zr, err := sync.GetZlibReader(s.r) - - if err != nil { - return nil, fmt.Errorf("zlib reset error: %s", err) - } - - return ioutil.NewReadCloserWithCloser(zr.Reader, func() error { - sync.PutZlibReader(zr) - return nil - }), nil -} - -// ReadRegularObject reads and write a non-deltified object -// from it zlib stream in an object entry in the packfile. -func (s *Scanner) copyObject(w io.Writer) (n int64, err error) { - zr, err := sync.GetZlibReader(s.r) - defer sync.PutZlibReader(zr) - - if err != nil { - return 0, fmt.Errorf("zlib reset error: %s", err) - } - - defer ioutil.CheckClose(zr.Reader, &err) - buf := sync.GetByteSlice() - n, err = io.CopyBuffer(w, zr.Reader, *buf) - sync.PutByteSlice(buf) - return -} - -// SeekFromStart sets a new offset from start, returns the old position before -// the change. -func (s *Scanner) SeekFromStart(offset int64) (previous int64, err error) { - // if seeking we assume that you are not interested in the header - if s.version == 0 { - s.version = VersionSupported - } - - previous, err = s.r.Seek(0, io.SeekCurrent) - if err != nil { - return -1, err - } - - _, err = s.r.Seek(offset, io.SeekStart) - return previous, err -} - -// Checksum returns the checksum of the packfile -func (s *Scanner) Checksum() (plumbing.Hash, error) { - err := s.discardObjectIfNeeded() - if err != nil { - return plumbing.ZeroHash, err - } - - return binary.ReadHash(s.r) -} - -// Close reads the reader until io.EOF -func (s *Scanner) Close() error { - buf := sync.GetByteSlice() - _, err := io.CopyBuffer(io.Discard, s.r, *buf) - sync.PutByteSlice(buf) - - return err -} - -// Flush is a no-op (deprecated) -func (s *Scanner) Flush() error { - return nil -} - -// scannerReader has the following characteristics: -// - Provides an io.SeekReader impl for bufio.Reader, when the underlying -// reader supports it. -// - Keeps track of the current read position, for when the underlying reader -// isn't an io.SeekReader, but we still want to know the current offset. -// - Writes to the hash writer what it reads, with the aid of a smaller buffer. -// The buffer helps avoid a performance penalty for performing small writes -// to the crc32 hash writer. -type scannerReader struct { - reader io.Reader - crc io.Writer - rbuf *bufio.Reader - wbuf *bufio.Writer - offset int64 -} - -func newScannerReader(r io.Reader, h io.Writer) *scannerReader { - sr := &scannerReader{ - rbuf: bufio.NewReader(nil), - wbuf: bufio.NewWriterSize(nil, 64), - crc: h, - } - sr.Reset(r) - - return sr -} - -func (r *scannerReader) Reset(reader io.Reader) { - r.reader = reader - r.rbuf.Reset(r.reader) - r.wbuf.Reset(r.crc) - - r.offset = 0 - if seeker, ok := r.reader.(io.ReadSeeker); ok { - r.offset, _ = seeker.Seek(0, io.SeekCurrent) - } -} - -func (r *scannerReader) Read(p []byte) (n int, err error) { - n, err = r.rbuf.Read(p) - - r.offset += int64(n) - if _, err := r.wbuf.Write(p[:n]); err != nil { - return n, err - } - return -} - -func (r *scannerReader) ReadByte() (b byte, err error) { - b, err = r.rbuf.ReadByte() - if err == nil { - r.offset++ - return b, r.wbuf.WriteByte(b) - } - return -} - -func (r *scannerReader) Flush() error { - return r.wbuf.Flush() -} - -// Seek seeks to a location. If the underlying reader is not an io.ReadSeeker, -// then only whence=io.SeekCurrent is supported, any other operation fails. -func (r *scannerReader) Seek(offset int64, whence int) (int64, error) { - var err error - - if seeker, ok := r.reader.(io.ReadSeeker); !ok { - if whence != io.SeekCurrent || offset != 0 { - return -1, ErrSeekNotSupported - } - } else { - if whence == io.SeekCurrent && offset == 0 { - return r.offset, nil - } - - r.offset, err = seeker.Seek(offset, whence) - r.rbuf.Reset(r.reader) - } - - return r.offset, err -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/pktline/encoder.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/pktline/encoder.go deleted file mode 100644 index 59934ac0692..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/pktline/encoder.go +++ /dev/null @@ -1,126 +0,0 @@ -// Package pktline implements reading payloads form pkt-lines and encoding -// pkt-lines from payloads. -package pktline - -import ( - "bytes" - "errors" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/utils/trace" -) - -// An Encoder writes pkt-lines to an output stream. -type Encoder struct { - w io.Writer -} - -const ( - // MaxPayloadSize is the maximum payload size of a pkt-line in bytes. - MaxPayloadSize = 65516 - - // For compatibility with canonical Git implementation, accept longer pkt-lines - OversizePayloadMax = 65520 -) - -var ( - // FlushPkt are the contents of a flush-pkt pkt-line. - FlushPkt = []byte{'0', '0', '0', '0'} - // Flush is the payload to use with the Encode method to encode a flush-pkt. - Flush = []byte{} - // FlushString is the payload to use with the EncodeString method to encode a flush-pkt. - FlushString = "" - // ErrPayloadTooLong is returned by the Encode methods when any of the - // provided payloads is bigger than MaxPayloadSize. - ErrPayloadTooLong = errors.New("payload is too long") -) - -// NewEncoder returns a new encoder that writes to w. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{ - w: w, - } -} - -// Flush encodes a flush-pkt to the output stream. -func (e *Encoder) Flush() error { - defer trace.Packet.Print("packet: > 0000") - _, err := e.w.Write(FlushPkt) - return err -} - -// Encode encodes a pkt-line with the payload specified and write it to -// the output stream. If several payloads are specified, each of them -// will get streamed in their own pkt-lines. -func (e *Encoder) Encode(payloads ...[]byte) error { - for _, p := range payloads { - if err := e.encodeLine(p); err != nil { - return err - } - } - - return nil -} - -func (e *Encoder) encodeLine(p []byte) error { - if len(p) > MaxPayloadSize { - return ErrPayloadTooLong - } - - if bytes.Equal(p, Flush) { - return e.Flush() - } - - n := len(p) + 4 - defer trace.Packet.Printf("packet: > %04x %s", n, p) - if _, err := e.w.Write(asciiHex16(n)); err != nil { - return err - } - _, err := e.w.Write(p) - return err -} - -// Returns the hexadecimal ascii representation of the 16 less -// significant bits of n. The length of the returned slice will always -// be 4. Example: if n is 1234 (0x4d2), the return value will be -// []byte{'0', '4', 'd', '2'}. -func asciiHex16(n int) []byte { - var ret [4]byte - ret[0] = byteToASCIIHex(byte(n & 0xf000 >> 12)) - ret[1] = byteToASCIIHex(byte(n & 0x0f00 >> 8)) - ret[2] = byteToASCIIHex(byte(n & 0x00f0 >> 4)) - ret[3] = byteToASCIIHex(byte(n & 0x000f)) - - return ret[:] -} - -// turns a byte into its hexadecimal ascii representation. Example: -// from 11 (0xb) to 'b'. -func byteToASCIIHex(n byte) byte { - if n < 10 { - return '0' + n - } - - return 'a' - 10 + n -} - -// EncodeString works similarly as Encode but payloads are specified as strings. -func (e *Encoder) EncodeString(payloads ...string) error { - for _, p := range payloads { - if err := e.Encode([]byte(p)); err != nil { - return err - } - } - - return nil -} - -// Encodef encodes a single pkt-line with the payload formatted as -// the format specifier. The rest of the arguments will be used in -// the format string. -func (e *Encoder) Encodef(format string, a ...interface{}) error { - return e.EncodeString( - fmt.Sprintf(format, a...), - ) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/pktline/error.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/pktline/error.go deleted file mode 100644 index 2c0e5a72a9b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/pktline/error.go +++ /dev/null @@ -1,51 +0,0 @@ -package pktline - -import ( - "bytes" - "errors" - "io" - "strings" -) - -var ( - // ErrInvalidErrorLine is returned by Decode when the packet line is not an - // error line. - ErrInvalidErrorLine = errors.New("expected an error-line") - - errPrefix = []byte("ERR ") -) - -// ErrorLine is a packet line that contains an error message. -// Once this packet is sent by client or server, the data transfer process is -// terminated. -// See https://git-scm.com/docs/pack-protocol#_pkt_line_format -type ErrorLine struct { - Text string -} - -// Error implements the error interface. -func (e *ErrorLine) Error() string { - return e.Text -} - -// Encode encodes the ErrorLine into a packet line. -func (e *ErrorLine) Encode(w io.Writer) error { - p := NewEncoder(w) - return p.Encodef("%s%s\n", string(errPrefix), e.Text) -} - -// Decode decodes a packet line into an ErrorLine. -func (e *ErrorLine) Decode(r io.Reader) error { - s := NewScanner(r) - if !s.Scan() { - return s.Err() - } - - line := s.Bytes() - if !bytes.HasPrefix(line, errPrefix) { - return ErrInvalidErrorLine - } - - e.Text = strings.TrimSpace(string(line[4:])) - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/pktline/scanner.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/pktline/scanner.go deleted file mode 100644 index a88362b7b5d..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/format/pktline/scanner.go +++ /dev/null @@ -1,148 +0,0 @@ -package pktline - -import ( - "bytes" - "errors" - "io" - "strings" - - "github.com/jesseduffield/go-git/v5/utils/trace" -) - -const ( - lenSize = 4 -) - -// ErrInvalidPktLen is returned by Err() when an invalid pkt-len is found. -var ErrInvalidPktLen = errors.New("invalid pkt-len found") - -// Scanner provides a convenient interface for reading the payloads of a -// series of pkt-lines. It takes an io.Reader providing the source, -// which then can be tokenized through repeated calls to the Scan -// method. -// -// After each Scan call, the Bytes method will return the payload of the -// corresponding pkt-line on a shared buffer, which will be 65516 bytes -// or smaller. Flush pkt-lines are represented by empty byte slices. -// -// Scanning stops at EOF or the first I/O error. -type Scanner struct { - r io.Reader // The reader provided by the client - err error // Sticky error - payload []byte // Last pkt-payload - len [lenSize]byte // Last pkt-len -} - -// NewScanner returns a new Scanner to read from r. -func NewScanner(r io.Reader) *Scanner { - return &Scanner{ - r: r, - } -} - -// Err returns the first error encountered by the Scanner. -func (s *Scanner) Err() error { - return s.err -} - -// Scan advances the Scanner to the next pkt-line, whose payload will -// then be available through the Bytes method. Scanning stops at EOF -// or the first I/O error. After Scan returns false, the Err method -// will return any error that occurred during scanning, except that if -// it was io.EOF, Err will return nil. -func (s *Scanner) Scan() bool { - var l int - l, s.err = s.readPayloadLen() - if s.err == io.EOF { - s.err = nil - return false - } - if s.err != nil { - return false - } - - if cap(s.payload) < l { - s.payload = make([]byte, 0, l) - } - - if _, s.err = io.ReadFull(s.r, s.payload[:l]); s.err != nil { - return false - } - s.payload = s.payload[:l] - trace.Packet.Printf("packet: < %04x %s", l, s.payload) - - if bytes.HasPrefix(s.payload, errPrefix) { - s.err = &ErrorLine{ - Text: strings.TrimSpace(string(s.payload[4:])), - } - return false - } - - return true -} - -// Bytes returns the most recent payload generated by a call to Scan. -// The underlying array may point to data that will be overwritten by a -// subsequent call to Scan. It does no allocation. -func (s *Scanner) Bytes() []byte { - return s.payload -} - -// Method readPayloadLen returns the payload length by reading the -// pkt-len and subtracting the pkt-len size. -func (s *Scanner) readPayloadLen() (int, error) { - if _, err := io.ReadFull(s.r, s.len[:]); err != nil { - if err == io.ErrUnexpectedEOF { - return 0, ErrInvalidPktLen - } - - return 0, err - } - - n, err := hexDecode(s.len) - if err != nil { - return 0, err - } - - switch { - case n == 0: - return 0, nil - case n <= lenSize: - return 0, ErrInvalidPktLen - case n > OversizePayloadMax+lenSize: - return 0, ErrInvalidPktLen - default: - return n - lenSize, nil - } -} - -// Turns the hexadecimal representation of a number in a byte slice into -// a number. This function substitute strconv.ParseUint(string(buf), 16, -// 16) and/or hex.Decode, to avoid generating new strings, thus helping the -// GC. -func hexDecode(buf [lenSize]byte) (int, error) { - var ret int - for i := 0; i < lenSize; i++ { - n, err := asciiHexToByte(buf[i]) - if err != nil { - return 0, ErrInvalidPktLen - } - ret = 16*ret + int(n) - } - return ret, nil -} - -// turns the hexadecimal ascii representation of a byte into its -// numerical value. Example: from 'b' to 11 (0xb). -func asciiHexToByte(b byte) (byte, error) { - switch { - case b >= '0' && b <= '9': - return b - '0', nil - case b >= 'a' && b <= 'f': - return b - 'a' + 10, nil - case b >= 'A' && b <= 'F': - return b - 'A' + 10, nil - default: - return 0, ErrInvalidPktLen - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash.go deleted file mode 100644 index 0532cec93f7..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash.go +++ /dev/null @@ -1,84 +0,0 @@ -package plumbing - -import ( - "bytes" - "encoding/hex" - "sort" - "strconv" - - "github.com/jesseduffield/go-git/v5/plumbing/hash" -) - -// Hash SHA1 hashed content -type Hash [hash.Size]byte - -// ZeroHash is Hash with value zero -var ZeroHash Hash - -// ComputeHash compute the hash for a given ObjectType and content -func ComputeHash(t ObjectType, content []byte) Hash { - h := NewHasher(t, int64(len(content))) - h.Write(content) - return h.Sum() -} - -// NewHash return a new Hash from a hexadecimal hash representation -func NewHash(s string) Hash { - b, _ := hex.DecodeString(s) - - var h Hash - copy(h[:], b) - - return h -} - -func (h Hash) IsZero() bool { - var empty Hash - return h == empty -} - -func (h Hash) String() string { - return hex.EncodeToString(h[:]) -} - -type Hasher struct { - hash.Hash -} - -func NewHasher(t ObjectType, size int64) Hasher { - h := Hasher{hash.New(hash.CryptoType)} - h.Write(t.Bytes()) - h.Write([]byte(" ")) - h.Write([]byte(strconv.FormatInt(size, 10))) - h.Write([]byte{0}) - return h -} - -func (h Hasher) Sum() (hash Hash) { - copy(hash[:], h.Hash.Sum(nil)) - return -} - -// HashesSort sorts a slice of Hashes in increasing order. -func HashesSort(a []Hash) { - sort.Sort(HashSlice(a)) -} - -// HashSlice attaches the methods of sort.Interface to []Hash, sorting in -// increasing order. -type HashSlice []Hash - -func (p HashSlice) Len() int { return len(p) } -func (p HashSlice) Less(i, j int) bool { return bytes.Compare(p[i][:], p[j][:]) < 0 } -func (p HashSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -// IsHash returns true if the given string is a valid hash. -func IsHash(s string) bool { - switch len(s) { - case hash.HexSize: - _, err := hex.DecodeString(s) - return err == nil - default: - return false - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash/hash.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash/hash.go deleted file mode 100644 index 8609848f679..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash/hash.go +++ /dev/null @@ -1,60 +0,0 @@ -// package hash provides a way for managing the -// underlying hash implementations used across go-git. -package hash - -import ( - "crypto" - "fmt" - "hash" - - "github.com/pjbgf/sha1cd" -) - -// algos is a map of hash algorithms. -var algos = map[crypto.Hash]func() hash.Hash{} - -func init() { - reset() -} - -// reset resets the default algos value. Can be used after running tests -// that registers new algorithms to avoid side effects. -func reset() { - algos[crypto.SHA1] = sha1cd.New - algos[crypto.SHA256] = crypto.SHA256.New -} - -// RegisterHash allows for the hash algorithm used to be overridden. -// This ensures the hash selection for go-git must be explicit, when -// overriding the default value. -func RegisterHash(h crypto.Hash, f func() hash.Hash) error { - if f == nil { - return fmt.Errorf("cannot register hash: f is nil") - } - - switch h { - case crypto.SHA1: - algos[h] = f - case crypto.SHA256: - algos[h] = f - default: - return fmt.Errorf("unsupported hash function: %v", h) - } - return nil -} - -// Hash is the same as hash.Hash. This allows consumers -// to not having to import this package alongside "hash". -type Hash interface { - hash.Hash -} - -// New returns a new Hash for the given hash function. -// It panics if the hash function is not registered. -func New(h crypto.Hash) Hash { - hh, ok := algos[h] - if !ok { - panic(fmt.Sprintf("hash algorithm not registered: %v", h)) - } - return hh() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash/hash_sha1.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash/hash_sha1.go deleted file mode 100644 index e3cb60fec9e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash/hash_sha1.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !sha256 -// +build !sha256 - -package hash - -import "crypto" - -const ( - // CryptoType defines what hash algorithm is being used. - CryptoType = crypto.SHA1 - // Size defines the amount of bytes the hash yields. - Size = 20 - // HexSize defines the strings size of the hash when represented in hexadecimal. - HexSize = 40 -) diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash/hash_sha256.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash/hash_sha256.go deleted file mode 100644 index 1c52b897539..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/hash/hash_sha256.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build sha256 -// +build sha256 - -package hash - -import "crypto" - -const ( - // CryptoType defines what hash algorithm is being used. - CryptoType = crypto.SHA256 - // Size defines the amount of bytes the hash yields. - Size = 32 - // HexSize defines the strings size of the hash when represented in hexadecimal. - HexSize = 64 -) diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/memory.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/memory.go deleted file mode 100644 index 6d11271dd67..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/memory.go +++ /dev/null @@ -1,72 +0,0 @@ -package plumbing - -import ( - "bytes" - "io" -) - -// MemoryObject on memory Object implementation -type MemoryObject struct { - t ObjectType - h Hash - cont []byte - sz int64 -} - -// Hash returns the object Hash, the hash is calculated on-the-fly the first -// time it's called, in all subsequent calls the same Hash is returned even -// if the type or the content have changed. The Hash is only generated if the -// size of the content is exactly the object size. -func (o *MemoryObject) Hash() Hash { - if o.h == ZeroHash && int64(len(o.cont)) == o.sz { - o.h = ComputeHash(o.t, o.cont) - } - - return o.h -} - -// Type returns the ObjectType -func (o *MemoryObject) Type() ObjectType { return o.t } - -// SetType sets the ObjectType -func (o *MemoryObject) SetType(t ObjectType) { o.t = t } - -// Size returns the size of the object -func (o *MemoryObject) Size() int64 { return o.sz } - -// SetSize set the object size, a content of the given size should be written -// afterwards -func (o *MemoryObject) SetSize(s int64) { o.sz = s } - -// Reader returns an io.ReadCloser used to read the object's content. -// -// For a MemoryObject, this reader is seekable. -func (o *MemoryObject) Reader() (io.ReadCloser, error) { - return nopCloser{bytes.NewReader(o.cont)}, nil -} - -// Writer returns a ObjectWriter used to write the object's content. -func (o *MemoryObject) Writer() (io.WriteCloser, error) { - return o, nil -} - -func (o *MemoryObject) Write(p []byte) (n int, err error) { - o.cont = append(o.cont, p...) - o.sz = int64(len(o.cont)) - - return len(p), nil -} - -// Close releases any resources consumed by the object when it is acting as a -// ObjectWriter. -func (o *MemoryObject) Close() error { return nil } - -// nopCloser exposes the extra methods of bytes.Reader while nopping Close(). -// -// This allows clients to attempt seeking in a cached Blob's Reader. -type nopCloser struct { - *bytes.Reader -} - -// Close does nothing. -func (nc nopCloser) Close() error { return nil } diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object.go deleted file mode 100644 index 3ee9de9f3ec..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object.go +++ /dev/null @@ -1,111 +0,0 @@ -// package plumbing implement the core interfaces and structs used by go-git -package plumbing - -import ( - "errors" - "io" -) - -var ( - ErrObjectNotFound = errors.New("object not found") - // ErrInvalidType is returned when an invalid object type is provided. - ErrInvalidType = errors.New("invalid object type") -) - -// Object is a generic representation of any git object -type EncodedObject interface { - Hash() Hash - Type() ObjectType - SetType(ObjectType) - Size() int64 - SetSize(int64) - Reader() (io.ReadCloser, error) - Writer() (io.WriteCloser, error) -} - -// DeltaObject is an EncodedObject representing a delta. -type DeltaObject interface { - EncodedObject - // BaseHash returns the hash of the object used as base for this delta. - BaseHash() Hash - // ActualHash returns the hash of the object after applying the delta. - ActualHash() Hash - // Size returns the size of the object after applying the delta. - ActualSize() int64 -} - -// ObjectType internal object type -// Integer values from 0 to 7 map to those exposed by git. -// AnyObject is used to represent any from 0 to 7. -type ObjectType int8 - -const ( - InvalidObject ObjectType = 0 - CommitObject ObjectType = 1 - TreeObject ObjectType = 2 - BlobObject ObjectType = 3 - TagObject ObjectType = 4 - // 5 reserved for future expansion - OFSDeltaObject ObjectType = 6 - REFDeltaObject ObjectType = 7 - - AnyObject ObjectType = -127 -) - -func (t ObjectType) String() string { - switch t { - case CommitObject: - return "commit" - case TreeObject: - return "tree" - case BlobObject: - return "blob" - case TagObject: - return "tag" - case OFSDeltaObject: - return "ofs-delta" - case REFDeltaObject: - return "ref-delta" - case AnyObject: - return "any" - default: - return "unknown" - } -} - -func (t ObjectType) Bytes() []byte { - return []byte(t.String()) -} - -// Valid returns true if t is a valid ObjectType. -func (t ObjectType) Valid() bool { - return t >= CommitObject && t <= REFDeltaObject -} - -// IsDelta returns true for any ObjectType that represents a delta (i.e. -// REFDeltaObject or OFSDeltaObject). -func (t ObjectType) IsDelta() bool { - return t == REFDeltaObject || t == OFSDeltaObject -} - -// ParseObjectType parses a string representation of ObjectType. It returns an -// error on parse failure. -func ParseObjectType(value string) (typ ObjectType, err error) { - switch value { - case "commit": - typ = CommitObject - case "tree": - typ = TreeObject - case "blob": - typ = BlobObject - case "tag": - typ = TagObject - case "ofs-delta": - typ = OFSDeltaObject - case "ref-delta": - typ = REFDeltaObject - default: - err = ErrInvalidType - } - return -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/blob.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/blob.go deleted file mode 100644 index 7bce28e800b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/blob.go +++ /dev/null @@ -1,144 +0,0 @@ -package object - -import ( - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -// Blob is used to store arbitrary data - it is generally a file. -type Blob struct { - // Hash of the blob. - Hash plumbing.Hash - // Size of the (uncompressed) blob. - Size int64 - - obj plumbing.EncodedObject -} - -// GetBlob gets a blob from an object storer and decodes it. -func GetBlob(s storer.EncodedObjectStorer, h plumbing.Hash) (*Blob, error) { - o, err := s.EncodedObject(plumbing.BlobObject, h) - if err != nil { - return nil, err - } - - return DecodeBlob(o) -} - -// DecodeObject decodes an encoded object into a *Blob. -func DecodeBlob(o plumbing.EncodedObject) (*Blob, error) { - b := &Blob{} - if err := b.Decode(o); err != nil { - return nil, err - } - - return b, nil -} - -// ID returns the object ID of the blob. The returned value will always match -// the current value of Blob.Hash. -// -// ID is present to fulfill the Object interface. -func (b *Blob) ID() plumbing.Hash { - return b.Hash -} - -// Type returns the type of object. It always returns plumbing.BlobObject. -// -// Type is present to fulfill the Object interface. -func (b *Blob) Type() plumbing.ObjectType { - return plumbing.BlobObject -} - -// Decode transforms a plumbing.EncodedObject into a Blob struct. -func (b *Blob) Decode(o plumbing.EncodedObject) error { - if o.Type() != plumbing.BlobObject { - return ErrUnsupportedObject - } - - b.Hash = o.Hash() - b.Size = o.Size() - b.obj = o - - return nil -} - -// Encode transforms a Blob into a plumbing.EncodedObject. -func (b *Blob) Encode(o plumbing.EncodedObject) (err error) { - o.SetType(plumbing.BlobObject) - - w, err := o.Writer() - if err != nil { - return err - } - - defer ioutil.CheckClose(w, &err) - - r, err := b.Reader() - if err != nil { - return err - } - - defer ioutil.CheckClose(r, &err) - - _, err = io.Copy(w, r) - return err -} - -// Reader returns a reader allow the access to the content of the blob -func (b *Blob) Reader() (io.ReadCloser, error) { - return b.obj.Reader() -} - -// BlobIter provides an iterator for a set of blobs. -type BlobIter struct { - storer.EncodedObjectIter - s storer.EncodedObjectStorer -} - -// NewBlobIter takes a storer.EncodedObjectStorer and a -// storer.EncodedObjectIter and returns a *BlobIter that iterates over all -// blobs contained in the storer.EncodedObjectIter. -// -// Any non-blob object returned by the storer.EncodedObjectIter is skipped. -func NewBlobIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) *BlobIter { - return &BlobIter{iter, s} -} - -// Next moves the iterator to the next blob and returns a pointer to it. If -// there are no more blobs, it returns io.EOF. -func (iter *BlobIter) Next() (*Blob, error) { - for { - obj, err := iter.EncodedObjectIter.Next() - if err != nil { - return nil, err - } - - if obj.Type() != plumbing.BlobObject { - continue - } - - return DecodeBlob(obj) - } -} - -// ForEach call the cb function for each blob contained on this iter until -// an error happens or the end of the iter is reached. If ErrStop is sent -// the iteration is stop but no error is returned. The iterator is closed. -func (iter *BlobIter) ForEach(cb func(*Blob) error) error { - return iter.EncodedObjectIter.ForEach(func(obj plumbing.EncodedObject) error { - if obj.Type() != plumbing.BlobObject { - return nil - } - - b, err := DecodeBlob(obj) - if err != nil { - return err - } - - return cb(b) - }) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/change.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/change.go deleted file mode 100644 index 5d33eda1295..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/change.go +++ /dev/null @@ -1,159 +0,0 @@ -package object - -import ( - "bytes" - "context" - "fmt" - "strings" - - "github.com/jesseduffield/go-git/v5/utils/merkletrie" -) - -// Change values represent a detected change between two git trees. For -// modifications, From is the original status of the node and To is its -// final status. For insertions, From is the zero value and for -// deletions To is the zero value. -type Change struct { - From ChangeEntry - To ChangeEntry -} - -var empty ChangeEntry - -// Action returns the kind of action represented by the change, an -// insertion, a deletion or a modification. -func (c *Change) Action() (merkletrie.Action, error) { - if c.From == empty && c.To == empty { - return merkletrie.Action(0), - fmt.Errorf("malformed change: empty from and to") - } - - if c.From == empty { - return merkletrie.Insert, nil - } - - if c.To == empty { - return merkletrie.Delete, nil - } - - return merkletrie.Modify, nil -} - -// Files returns the files before and after a change. -// For insertions from will be nil. For deletions to will be nil. -func (c *Change) Files() (from, to *File, err error) { - action, err := c.Action() - if err != nil { - return - } - - if action == merkletrie.Insert || action == merkletrie.Modify { - to, err = c.To.Tree.TreeEntryFile(&c.To.TreeEntry) - if !c.To.TreeEntry.Mode.IsFile() { - return nil, nil, nil - } - - if err != nil { - return - } - } - - if action == merkletrie.Delete || action == merkletrie.Modify { - from, err = c.From.Tree.TreeEntryFile(&c.From.TreeEntry) - if !c.From.TreeEntry.Mode.IsFile() { - return nil, nil, nil - } - - if err != nil { - return - } - } - - return -} - -func (c *Change) String() string { - action, err := c.Action() - if err != nil { - return "malformed change" - } - - return fmt.Sprintf("", action, c.name()) -} - -// Patch returns a Patch with all the file changes in chunks. This -// representation can be used to create several diff outputs. -func (c *Change) Patch() (*Patch, error) { - return c.PatchContext(context.Background()) -} - -// Patch returns a Patch with all the file changes in chunks. This -// representation can be used to create several diff outputs. -// If context expires, an non-nil error will be returned -// Provided context must be non-nil -func (c *Change) PatchContext(ctx context.Context) (*Patch, error) { - return getPatchContext(ctx, "", c) -} - -func (c *Change) name() string { - if c.From != empty { - return c.From.Name - } - - return c.To.Name -} - -// ChangeEntry values represent a node that has suffered a change. -type ChangeEntry struct { - // Full path of the node using "/" as separator. - Name string - // Parent tree of the node that has changed. - Tree *Tree - // The entry of the node. - TreeEntry TreeEntry -} - -// Changes represents a collection of changes between two git trees. -// Implements sort.Interface lexicographically over the path of the -// changed files. -type Changes []*Change - -func (c Changes) Len() int { - return len(c) -} - -func (c Changes) Swap(i, j int) { - c[i], c[j] = c[j], c[i] -} - -func (c Changes) Less(i, j int) bool { - return strings.Compare(c[i].name(), c[j].name()) < 0 -} - -func (c Changes) String() string { - var buffer bytes.Buffer - buffer.WriteString("[") - comma := "" - for _, v := range c { - buffer.WriteString(comma) - buffer.WriteString(v.String()) - comma = ", " - } - buffer.WriteString("]") - - return buffer.String() -} - -// Patch returns a Patch with all the changes in chunks. This -// representation can be used to create several diff outputs. -func (c Changes) Patch() (*Patch, error) { - return c.PatchContext(context.Background()) -} - -// Patch returns a Patch with all the changes in chunks. This -// representation can be used to create several diff outputs. -// If context expires, an non-nil error will be returned -// Provided context must be non-nil -func (c Changes) PatchContext(ctx context.Context) (*Patch, error) { - return getPatchContext(ctx, "", c...) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/change_adaptor.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/change_adaptor.go deleted file mode 100644 index c4789499455..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/change_adaptor.go +++ /dev/null @@ -1,61 +0,0 @@ -package object - -import ( - "errors" - "fmt" - - "github.com/jesseduffield/go-git/v5/utils/merkletrie" - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" -) - -// The following functions transform changes types form the merkletrie -// package to changes types from this package. - -func newChange(c merkletrie.Change) (*Change, error) { - ret := &Change{} - - var err error - if ret.From, err = newChangeEntry(c.From); err != nil { - return nil, fmt.Errorf("from field: %s", err) - } - - if ret.To, err = newChangeEntry(c.To); err != nil { - return nil, fmt.Errorf("to field: %s", err) - } - - return ret, nil -} - -func newChangeEntry(p noder.Path) (ChangeEntry, error) { - if p == nil { - return empty, nil - } - - asTreeNoder, ok := p.Last().(*treeNoder) - if !ok { - return ChangeEntry{}, errors.New("cannot transform non-TreeNoders") - } - - return ChangeEntry{ - Name: p.String(), - Tree: asTreeNoder.parent, - TreeEntry: TreeEntry{ - Name: asTreeNoder.name, - Mode: asTreeNoder.mode, - Hash: asTreeNoder.hash, - }, - }, nil -} - -func newChanges(src merkletrie.Changes) (Changes, error) { - ret := make(Changes, len(src)) - var err error - for i, e := range src { - ret[i], err = newChange(e) - if err != nil { - return nil, fmt.Errorf("change #%d: %s", i, err) - } - } - - return ret, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit.go deleted file mode 100644 index f6392c99ac2..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit.go +++ /dev/null @@ -1,507 +0,0 @@ -package object - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "strings" - - "github.com/ProtonMail/go-crypto/openpgp" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/jesseduffield/go-git/v5/utils/sync" -) - -const ( - beginpgp string = "-----BEGIN PGP SIGNATURE-----" - endpgp string = "-----END PGP SIGNATURE-----" - headerpgp string = "gpgsig" - headerencoding string = "encoding" - - // https://github.com/git/git/blob/bcb6cae2966cc407ca1afc77413b3ef11103c175/Documentation/gitformat-signature.txt#L153 - // When a merge commit is created from a signed tag, the tag is embedded in - // the commit with the "mergetag" header. - headermergetag string = "mergetag" - - defaultUtf8CommitMessageEncoding MessageEncoding = "UTF-8" -) - -// Hash represents the hash of an object -type Hash plumbing.Hash - -// MessageEncoding represents the encoding of a commit -type MessageEncoding string - -// Commit points to a single tree, marking it as what the project looked like -// at a certain point in time. It contains meta-information about that point -// in time, such as a timestamp, the author of the changes since the last -// commit, a pointer to the previous commit(s), etc. -// http://shafiulazam.com/gitbook/1_the_git_object_model.html -type Commit struct { - // Hash of the commit object. - Hash plumbing.Hash - // Author is the original author of the commit. - Author Signature - // Committer is the one performing the commit, might be different from - // Author. - Committer Signature - // MergeTag is the embedded tag object when a merge commit is created by - // merging a signed tag. - MergeTag string - // PGPSignature is the PGP signature of the commit. - PGPSignature string - // Message is the commit message, contains arbitrary text. - Message string - // TreeHash is the hash of the root tree of the commit. - TreeHash plumbing.Hash - // ParentHashes are the hashes of the parent commits of the commit. - ParentHashes []plumbing.Hash - // Encoding is the encoding of the commit. - Encoding MessageEncoding - - s storer.EncodedObjectStorer -} - -// GetCommit gets a commit from an object storer and decodes it. -func GetCommit(s storer.EncodedObjectStorer, h plumbing.Hash) (*Commit, error) { - o, err := s.EncodedObject(plumbing.CommitObject, h) - if err != nil { - return nil, err - } - - return DecodeCommit(s, o) -} - -// DecodeCommit decodes an encoded object into a *Commit and associates it to -// the given object storer. -func DecodeCommit(s storer.EncodedObjectStorer, o plumbing.EncodedObject) (*Commit, error) { - c := &Commit{s: s} - if err := c.Decode(o); err != nil { - return nil, err - } - - return c, nil -} - -// Tree returns the Tree from the commit. -func (c *Commit) Tree() (*Tree, error) { - return GetTree(c.s, c.TreeHash) -} - -// PatchContext returns the Patch between the actual commit and the provided one. -// Error will be return if context expires. Provided context must be non-nil. -// -// NOTE: Since version 5.1.0 the renames are correctly handled, the settings -// used are the recommended options DefaultDiffTreeOptions. -func (c *Commit) PatchContext(ctx context.Context, to *Commit) (*Patch, error) { - fromTree, err := c.Tree() - if err != nil { - return nil, err - } - - var toTree *Tree - if to != nil { - toTree, err = to.Tree() - if err != nil { - return nil, err - } - } - - return fromTree.PatchContext(ctx, toTree) -} - -// Patch returns the Patch between the actual commit and the provided one. -// -// NOTE: Since version 5.1.0 the renames are correctly handled, the settings -// used are the recommended options DefaultDiffTreeOptions. -func (c *Commit) Patch(to *Commit) (*Patch, error) { - return c.PatchContext(context.Background(), to) -} - -// Parents return a CommitIter to the parent Commits. -func (c *Commit) Parents() CommitIter { - return NewCommitIter(c.s, - storer.NewEncodedObjectLookupIter(c.s, plumbing.CommitObject, c.ParentHashes), - ) -} - -// NumParents returns the number of parents in a commit. -func (c *Commit) NumParents() int { - return len(c.ParentHashes) -} - -var ErrParentNotFound = errors.New("commit parent not found") - -// Parent returns the ith parent of a commit. -func (c *Commit) Parent(i int) (*Commit, error) { - if len(c.ParentHashes) == 0 || i > len(c.ParentHashes)-1 { - return nil, ErrParentNotFound - } - - return GetCommit(c.s, c.ParentHashes[i]) -} - -// File returns the file with the specified "path" in the commit and a -// nil error if the file exists. If the file does not exist, it returns -// a nil file and the ErrFileNotFound error. -func (c *Commit) File(path string) (*File, error) { - tree, err := c.Tree() - if err != nil { - return nil, err - } - - return tree.File(path) -} - -// Files returns a FileIter allowing to iterate over the Tree -func (c *Commit) Files() (*FileIter, error) { - tree, err := c.Tree() - if err != nil { - return nil, err - } - - return tree.Files(), nil -} - -// ID returns the object ID of the commit. The returned value will always match -// the current value of Commit.Hash. -// -// ID is present to fulfill the Object interface. -func (c *Commit) ID() plumbing.Hash { - return c.Hash -} - -// Type returns the type of object. It always returns plumbing.CommitObject. -// -// Type is present to fulfill the Object interface. -func (c *Commit) Type() plumbing.ObjectType { - return plumbing.CommitObject -} - -// Decode transforms a plumbing.EncodedObject into a Commit struct. -func (c *Commit) Decode(o plumbing.EncodedObject) (err error) { - if o.Type() != plumbing.CommitObject { - return ErrUnsupportedObject - } - - c.Hash = o.Hash() - c.Encoding = defaultUtf8CommitMessageEncoding - - reader, err := o.Reader() - if err != nil { - return err - } - defer ioutil.CheckClose(reader, &err) - - r := sync.GetBufioReader(reader) - defer sync.PutBufioReader(r) - - var message bool - var mergetag bool - var pgpsig bool - var msgbuf bytes.Buffer - for { - line, err := r.ReadBytes('\n') - if err != nil && err != io.EOF { - return err - } - - if mergetag { - if len(line) > 0 && line[0] == ' ' { - line = bytes.TrimLeft(line, " ") - c.MergeTag += string(line) - continue - } else { - mergetag = false - } - } - - if pgpsig { - if len(line) > 0 && line[0] == ' ' { - line = bytes.TrimLeft(line, " ") - c.PGPSignature += string(line) - continue - } else { - pgpsig = false - } - } - - if !message { - line = bytes.TrimSpace(line) - if len(line) == 0 { - message = true - continue - } - - split := bytes.SplitN(line, []byte{' '}, 2) - - var data []byte - if len(split) == 2 { - data = split[1] - } - - switch string(split[0]) { - case "tree": - c.TreeHash = plumbing.NewHash(string(data)) - case "parent": - c.ParentHashes = append(c.ParentHashes, plumbing.NewHash(string(data))) - case "author": - c.Author.Decode(data) - case "committer": - c.Committer.Decode(data) - case headermergetag: - c.MergeTag += string(data) + "\n" - mergetag = true - case headerencoding: - c.Encoding = MessageEncoding(data) - case headerpgp: - c.PGPSignature += string(data) + "\n" - pgpsig = true - } - } else { - msgbuf.Write(line) - } - - if err == io.EOF { - break - } - } - c.Message = msgbuf.String() - return nil -} - -// Encode transforms a Commit into a plumbing.EncodedObject. -func (c *Commit) Encode(o plumbing.EncodedObject) error { - return c.encode(o, true) -} - -// EncodeWithoutSignature export a Commit into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature). -func (c *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error { - return c.encode(o, false) -} - -func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) { - o.SetType(plumbing.CommitObject) - w, err := o.Writer() - if err != nil { - return err - } - - defer ioutil.CheckClose(w, &err) - - if _, err = fmt.Fprintf(w, "tree %s\n", c.TreeHash.String()); err != nil { - return err - } - - for _, parent := range c.ParentHashes { - if _, err = fmt.Fprintf(w, "parent %s\n", parent.String()); err != nil { - return err - } - } - - if _, err = fmt.Fprint(w, "author "); err != nil { - return err - } - - if err = c.Author.Encode(w); err != nil { - return err - } - - if _, err = fmt.Fprint(w, "\ncommitter "); err != nil { - return err - } - - if err = c.Committer.Encode(w); err != nil { - return err - } - - if c.MergeTag != "" { - if _, err = fmt.Fprint(w, "\n"+headermergetag+" "); err != nil { - return err - } - - // Split tag information lines and re-write with a left padding and - // newline. Use join for this so it's clear that a newline should not be - // added after this section. The newline will be added either as part of - // the PGP signature or the commit message. - mergetag := strings.TrimSuffix(c.MergeTag, "\n") - lines := strings.Split(mergetag, "\n") - if _, err = fmt.Fprint(w, strings.Join(lines, "\n ")); err != nil { - return err - } - } - - if string(c.Encoding) != "" && c.Encoding != defaultUtf8CommitMessageEncoding { - if _, err = fmt.Fprintf(w, "\n%s %s", headerencoding, c.Encoding); err != nil { - return err - } - } - - if c.PGPSignature != "" && includeSig { - if _, err = fmt.Fprint(w, "\n"+headerpgp+" "); err != nil { - return err - } - - // Split all the signature lines and re-write with a left padding and - // newline. Use join for this so it's clear that a newline should not be - // added after this section, as it will be added when the message is - // printed. - signature := strings.TrimSuffix(c.PGPSignature, "\n") - lines := strings.Split(signature, "\n") - if _, err = fmt.Fprint(w, strings.Join(lines, "\n ")); err != nil { - return err - } - } - - if _, err = fmt.Fprintf(w, "\n\n%s", c.Message); err != nil { - return err - } - - return err -} - -// Stats returns the stats of a commit. -func (c *Commit) Stats() (FileStats, error) { - return c.StatsContext(context.Background()) -} - -// StatsContext returns the stats of a commit. Error will be return if context -// expires. Provided context must be non-nil. -func (c *Commit) StatsContext(ctx context.Context) (FileStats, error) { - fromTree, err := c.Tree() - if err != nil { - return nil, err - } - - toTree := &Tree{} - if c.NumParents() != 0 { - firstParent, err := c.Parents().Next() - if err != nil { - return nil, err - } - - toTree, err = firstParent.Tree() - if err != nil { - return nil, err - } - } - - patch, err := toTree.PatchContext(ctx, fromTree) - if err != nil { - return nil, err - } - - return getFileStatsFromFilePatches(patch.FilePatches()), nil -} - -func (c *Commit) String() string { - return fmt.Sprintf( - "%s %s\nAuthor: %s\nDate: %s\n\n%s\n", - plumbing.CommitObject, c.Hash, c.Author.String(), - c.Author.When.Format(DateFormat), indent(c.Message), - ) -} - -// Verify performs PGP verification of the commit with a provided armored -// keyring and returns openpgp.Entity associated with verifying key on success. -func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) { - keyRingReader := strings.NewReader(armoredKeyRing) - keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader) - if err != nil { - return nil, err - } - - // Extract signature. - signature := strings.NewReader(c.PGPSignature) - - encoded := &plumbing.MemoryObject{} - // Encode commit components, excluding signature and get a reader object. - if err := c.EncodeWithoutSignature(encoded); err != nil { - return nil, err - } - er, err := encoded.Reader() - if err != nil { - return nil, err - } - - return openpgp.CheckArmoredDetachedSignature(keyring, er, signature, nil) -} - -// Less defines a compare function to determine which commit is 'earlier' by: -// - First use Committer.When -// - If Committer.When are equal then use Author.When -// - If Author.When also equal then compare the string value of the hash -func (c *Commit) Less(rhs *Commit) bool { - return c.Committer.When.Before(rhs.Committer.When) || - (c.Committer.When.Equal(rhs.Committer.When) && - (c.Author.When.Before(rhs.Author.When) || - (c.Author.When.Equal(rhs.Author.When) && bytes.Compare(c.Hash[:], rhs.Hash[:]) < 0))) -} - -func indent(t string) string { - var output []string - for _, line := range strings.Split(t, "\n") { - if len(line) != 0 { - line = " " + line - } - - output = append(output, line) - } - - return strings.Join(output, "\n") -} - -// CommitIter is a generic closable interface for iterating over commits. -type CommitIter interface { - Next() (*Commit, error) - ForEach(func(*Commit) error) error - Close() -} - -// storerCommitIter provides an iterator from commits in an EncodedObjectStorer. -type storerCommitIter struct { - storer.EncodedObjectIter - s storer.EncodedObjectStorer -} - -// NewCommitIter takes a storer.EncodedObjectStorer and a -// storer.EncodedObjectIter and returns a CommitIter that iterates over all -// commits contained in the storer.EncodedObjectIter. -// -// Any non-commit object returned by the storer.EncodedObjectIter is skipped. -func NewCommitIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) CommitIter { - return &storerCommitIter{iter, s} -} - -// Next moves the iterator to the next commit and returns a pointer to it. If -// there are no more commits, it returns io.EOF. -func (iter *storerCommitIter) Next() (*Commit, error) { - obj, err := iter.EncodedObjectIter.Next() - if err != nil { - return nil, err - } - - return DecodeCommit(iter.s, obj) -} - -// ForEach call the cb function for each commit contained on this iter until -// an error appends or the end of the iter is reached. If ErrStop is sent -// the iteration is stopped but no error is returned. The iterator is closed. -func (iter *storerCommitIter) ForEach(cb func(*Commit) error) error { - return iter.EncodedObjectIter.ForEach(func(obj plumbing.EncodedObject) error { - c, err := DecodeCommit(iter.s, obj) - if err != nil { - return err - } - - return cb(c) - }) -} - -func (iter *storerCommitIter) Close() { - iter.EncodedObjectIter.Close() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker.go deleted file mode 100644 index 60da75cad87..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker.go +++ /dev/null @@ -1,327 +0,0 @@ -package object - -import ( - "container/list" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/storage" -) - -type commitPreIterator struct { - seenExternal map[plumbing.Hash]bool - seen map[plumbing.Hash]bool - stack []CommitIter - start *Commit -} - -// NewCommitPreorderIter returns a CommitIter that walks the commit history, -// starting at the given commit and visiting its parents in pre-order. -// The given callback will be called for each visited commit. Each commit will -// be visited only once. If the callback returns an error, walking will stop -// and will return the error. Other errors might be returned if the history -// cannot be traversed (e.g. missing objects). Ignore allows to skip some -// commits from being iterated. -func NewCommitPreorderIter( - c *Commit, - seenExternal map[plumbing.Hash]bool, - ignore []plumbing.Hash, -) CommitIter { - seen := make(map[plumbing.Hash]bool) - for _, h := range ignore { - seen[h] = true - } - - return &commitPreIterator{ - seenExternal: seenExternal, - seen: seen, - stack: make([]CommitIter, 0), - start: c, - } -} - -func (w *commitPreIterator) Next() (*Commit, error) { - var c *Commit - for { - if w.start != nil { - c = w.start - w.start = nil - } else { - current := len(w.stack) - 1 - if current < 0 { - return nil, io.EOF - } - - var err error - c, err = w.stack[current].Next() - if err == io.EOF { - w.stack = w.stack[:current] - continue - } - - if err != nil { - return nil, err - } - } - - if w.seen[c.Hash] || w.seenExternal[c.Hash] { - continue - } - - w.seen[c.Hash] = true - - if c.NumParents() > 0 { - w.stack = append(w.stack, filteredParentIter(c, w.seen)) - } - - return c, nil - } -} - -func filteredParentIter(c *Commit, seen map[plumbing.Hash]bool) CommitIter { - var hashes []plumbing.Hash - for _, h := range c.ParentHashes { - if !seen[h] { - hashes = append(hashes, h) - } - } - - return NewCommitIter(c.s, - storer.NewEncodedObjectLookupIter(c.s, plumbing.CommitObject, hashes), - ) -} - -func (w *commitPreIterator) ForEach(cb func(*Commit) error) error { - for { - c, err := w.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - err = cb(c) - if err == storer.ErrStop { - break - } - if err != nil { - return err - } - } - - return nil -} - -func (w *commitPreIterator) Close() {} - -type commitPostIterator struct { - stack []*Commit - seen map[plumbing.Hash]bool -} - -// NewCommitPostorderIter returns a CommitIter that walks the commit -// history like WalkCommitHistory but in post-order. This means that after -// walking a merge commit, the merged commit will be walked before the base -// it was merged on. This can be useful if you wish to see the history in -// chronological order. Ignore allows to skip some commits from being iterated. -func NewCommitPostorderIter(c *Commit, ignore []plumbing.Hash) CommitIter { - seen := make(map[plumbing.Hash]bool) - for _, h := range ignore { - seen[h] = true - } - - return &commitPostIterator{ - stack: []*Commit{c}, - seen: seen, - } -} - -func (w *commitPostIterator) Next() (*Commit, error) { - for { - if len(w.stack) == 0 { - return nil, io.EOF - } - - c := w.stack[len(w.stack)-1] - w.stack = w.stack[:len(w.stack)-1] - - if w.seen[c.Hash] { - continue - } - - w.seen[c.Hash] = true - - return c, c.Parents().ForEach(func(p *Commit) error { - w.stack = append(w.stack, p) - return nil - }) - } -} - -func (w *commitPostIterator) ForEach(cb func(*Commit) error) error { - for { - c, err := w.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - err = cb(c) - if err == storer.ErrStop { - break - } - if err != nil { - return err - } - } - - return nil -} - -func (w *commitPostIterator) Close() {} - -// commitAllIterator stands for commit iterator for all refs. -type commitAllIterator struct { - // currCommit points to the current commit. - currCommit *list.Element -} - -// NewCommitAllIter returns a new commit iterator for all refs. -// repoStorer is a repo Storer used to get commits and references. -// commitIterFunc is a commit iterator function, used to iterate through ref commits in chosen order -func NewCommitAllIter(repoStorer storage.Storer, commitIterFunc func(*Commit) CommitIter) (CommitIter, error) { - commitsPath := list.New() - commitsLookup := make(map[plumbing.Hash]*list.Element) - head, err := storer.ResolveReference(repoStorer, plumbing.HEAD) - if err == nil { - err = addReference(repoStorer, commitIterFunc, head, commitsPath, commitsLookup) - } - - if err != nil && err != plumbing.ErrReferenceNotFound { - return nil, err - } - - // add all references along with the HEAD - refIter, err := repoStorer.IterReferences() - if err != nil { - return nil, err - } - defer refIter.Close() - - for { - ref, err := refIter.Next() - if err == io.EOF { - break - } - - if err == plumbing.ErrReferenceNotFound { - continue - } - - if err != nil { - return nil, err - } - - if err = addReference(repoStorer, commitIterFunc, ref, commitsPath, commitsLookup); err != nil { - return nil, err - } - } - - return &commitAllIterator{commitsPath.Front()}, nil -} - -func addReference( - repoStorer storage.Storer, - commitIterFunc func(*Commit) CommitIter, - ref *plumbing.Reference, - commitsPath *list.List, - commitsLookup map[plumbing.Hash]*list.Element) error { - - _, exists := commitsLookup[ref.Hash()] - if exists { - // we already have it - skip the reference. - return nil - } - - refCommit, _ := GetCommit(repoStorer, ref.Hash()) - if refCommit == nil { - // if it's not a commit - skip it. - return nil - } - - var ( - refCommits []*Commit - parent *list.Element - ) - // collect all ref commits to add - commitIter := commitIterFunc(refCommit) - for c, e := commitIter.Next(); e == nil; { - parent, exists = commitsLookup[c.Hash] - if exists { - break - } - refCommits = append(refCommits, c) - c, e = commitIter.Next() - } - commitIter.Close() - - if parent == nil { - // common parent - not found - // add all commits to the path from this ref (maybe it's a HEAD and we don't have anything, yet) - for _, c := range refCommits { - parent = commitsPath.PushBack(c) - commitsLookup[c.Hash] = parent - } - } else { - // add ref's commits to the path in reverse order (from the latest) - for i := len(refCommits) - 1; i >= 0; i-- { - c := refCommits[i] - // insert before found common parent - parent = commitsPath.InsertBefore(c, parent) - commitsLookup[c.Hash] = parent - } - } - - return nil -} - -func (it *commitAllIterator) Next() (*Commit, error) { - if it.currCommit == nil { - return nil, io.EOF - } - - c := it.currCommit.Value.(*Commit) - it.currCommit = it.currCommit.Next() - - return c, nil -} - -func (it *commitAllIterator) ForEach(cb func(*Commit) error) error { - for { - c, err := it.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - err = cb(c) - if err == storer.ErrStop { - break - } - if err != nil { - return err - } - } - - return nil -} - -func (it *commitAllIterator) Close() { - it.currCommit = nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_bfs.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_bfs.go deleted file mode 100644 index c9c744d6c0b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_bfs.go +++ /dev/null @@ -1,100 +0,0 @@ -package object - -import ( - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" -) - -type bfsCommitIterator struct { - seenExternal map[plumbing.Hash]bool - seen map[plumbing.Hash]bool - queue []*Commit -} - -// NewCommitIterBSF returns a CommitIter that walks the commit history, -// starting at the given commit and visiting its parents in pre-order. -// The given callback will be called for each visited commit. Each commit will -// be visited only once. If the callback returns an error, walking will stop -// and will return the error. Other errors might be returned if the history -// cannot be traversed (e.g. missing objects). Ignore allows to skip some -// commits from being iterated. -func NewCommitIterBSF( - c *Commit, - seenExternal map[plumbing.Hash]bool, - ignore []plumbing.Hash, -) CommitIter { - seen := make(map[plumbing.Hash]bool) - for _, h := range ignore { - seen[h] = true - } - - return &bfsCommitIterator{ - seenExternal: seenExternal, - seen: seen, - queue: []*Commit{c}, - } -} - -func (w *bfsCommitIterator) appendHash(store storer.EncodedObjectStorer, h plumbing.Hash) error { - if w.seen[h] || w.seenExternal[h] { - return nil - } - c, err := GetCommit(store, h) - if err != nil { - return err - } - w.queue = append(w.queue, c) - return nil -} - -func (w *bfsCommitIterator) Next() (*Commit, error) { - var c *Commit - for { - if len(w.queue) == 0 { - return nil, io.EOF - } - c = w.queue[0] - w.queue = w.queue[1:] - - if w.seen[c.Hash] || w.seenExternal[c.Hash] { - continue - } - - w.seen[c.Hash] = true - - for _, h := range c.ParentHashes { - err := w.appendHash(c.s, h) - if err != nil { - return nil, err - } - } - - return c, nil - } -} - -func (w *bfsCommitIterator) ForEach(cb func(*Commit) error) error { - for { - c, err := w.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - err = cb(c) - if err == storer.ErrStop { - break - } - if err != nil { - return err - } - } - - return nil -} - -func (w *bfsCommitIterator) Close() {} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_bfs_filtered.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_bfs_filtered.go deleted file mode 100644 index 72343a64b3d..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_bfs_filtered.go +++ /dev/null @@ -1,175 +0,0 @@ -package object - -import ( - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" -) - -// NewFilterCommitIter returns a CommitIter that walks the commit history, -// starting at the passed commit and visiting its parents in Breadth-first order. -// The commits returned by the CommitIter will validate the passed CommitFilter. -// The history won't be transversed beyond a commit if isLimit is true for it. -// Each commit will be visited only once. -// If the commit history can not be traversed, or the Close() method is called, -// the CommitIter won't return more commits. -// If no isValid is passed, all ancestors of from commit will be valid. -// If no isLimit is limit, all ancestors of all commits will be visited. -func NewFilterCommitIter( - from *Commit, - isValid *CommitFilter, - isLimit *CommitFilter, -) CommitIter { - var validFilter CommitFilter - if isValid == nil { - validFilter = func(_ *Commit) bool { - return true - } - } else { - validFilter = *isValid - } - - var limitFilter CommitFilter - if isLimit == nil { - limitFilter = func(_ *Commit) bool { - return false - } - } else { - limitFilter = *isLimit - } - - return &filterCommitIter{ - isValid: validFilter, - isLimit: limitFilter, - visited: map[plumbing.Hash]struct{}{}, - queue: []*Commit{from}, - } -} - -// CommitFilter returns a boolean for the passed Commit -type CommitFilter func(*Commit) bool - -// filterCommitIter implements CommitIter -type filterCommitIter struct { - isValid CommitFilter - isLimit CommitFilter - visited map[plumbing.Hash]struct{} - queue []*Commit - lastErr error -} - -// Next returns the next commit of the CommitIter. -// It will return io.EOF if there are no more commits to visit, -// or an error if the history could not be traversed. -func (w *filterCommitIter) Next() (*Commit, error) { - var commit *Commit - var err error - for { - commit, err = w.popNewFromQueue() - if err != nil { - return nil, w.close(err) - } - - w.visited[commit.Hash] = struct{}{} - - if !w.isLimit(commit) { - err = w.addToQueue(commit.s, commit.ParentHashes...) - if err != nil { - return nil, w.close(err) - } - } - - if w.isValid(commit) { - return commit, nil - } - } -} - -// ForEach runs the passed callback over each Commit returned by the CommitIter -// until the callback returns an error or there is no more commits to traverse. -func (w *filterCommitIter) ForEach(cb func(*Commit) error) error { - for { - commit, err := w.Next() - if err == io.EOF { - break - } - - if err != nil { - return err - } - - if err := cb(commit); err == storer.ErrStop { - break - } else if err != nil { - return err - } - } - - return nil -} - -// Error returns the error that caused that the CommitIter is no longer returning commits -func (w *filterCommitIter) Error() error { - return w.lastErr -} - -// Close closes the CommitIter -func (w *filterCommitIter) Close() { - w.visited = map[plumbing.Hash]struct{}{} - w.queue = []*Commit{} - w.isLimit = nil - w.isValid = nil -} - -// close closes the CommitIter with an error -func (w *filterCommitIter) close(err error) error { - w.Close() - w.lastErr = err - return err -} - -// popNewFromQueue returns the first new commit from the internal fifo queue, -// or an io.EOF error if the queue is empty -func (w *filterCommitIter) popNewFromQueue() (*Commit, error) { - var first *Commit - for { - if len(w.queue) == 0 { - if w.lastErr != nil { - return nil, w.lastErr - } - - return nil, io.EOF - } - - first = w.queue[0] - w.queue = w.queue[1:] - if _, ok := w.visited[first.Hash]; ok { - continue - } - - return first, nil - } -} - -// addToQueue adds the passed commits to the internal fifo queue if they weren't seen -// or returns an error if the passed hashes could not be used to get valid commits -func (w *filterCommitIter) addToQueue( - store storer.EncodedObjectStorer, - hashes ...plumbing.Hash, -) error { - for _, hash := range hashes { - if _, ok := w.visited[hash]; ok { - continue - } - - commit, err := GetCommit(store, hash) - if err != nil { - return err - } - - w.queue = append(w.queue, commit) - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_ctime.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_ctime.go deleted file mode 100644 index 69ac2aa35ee..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_ctime.go +++ /dev/null @@ -1,103 +0,0 @@ -package object - -import ( - "io" - - "github.com/emirpasic/gods/trees/binaryheap" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" -) - -type commitIteratorByCTime struct { - seenExternal map[plumbing.Hash]bool - seen map[plumbing.Hash]bool - heap *binaryheap.Heap -} - -// NewCommitIterCTime returns a CommitIter that walks the commit history, -// starting at the given commit and visiting its parents while preserving Committer Time order. -// this appears to be the closest order to `git log` -// The given callback will be called for each visited commit. Each commit will -// be visited only once. If the callback returns an error, walking will stop -// and will return the error. Other errors might be returned if the history -// cannot be traversed (e.g. missing objects). Ignore allows to skip some -// commits from being iterated. -func NewCommitIterCTime( - c *Commit, - seenExternal map[plumbing.Hash]bool, - ignore []plumbing.Hash, -) CommitIter { - seen := make(map[plumbing.Hash]bool) - for _, h := range ignore { - seen[h] = true - } - - heap := binaryheap.NewWith(func(a, b interface{}) int { - if a.(*Commit).Committer.When.Before(b.(*Commit).Committer.When) { - return 1 - } - return -1 - }) - heap.Push(c) - - return &commitIteratorByCTime{ - seenExternal: seenExternal, - seen: seen, - heap: heap, - } -} - -func (w *commitIteratorByCTime) Next() (*Commit, error) { - var c *Commit - for { - cIn, ok := w.heap.Pop() - if !ok { - return nil, io.EOF - } - c = cIn.(*Commit) - - if w.seen[c.Hash] || w.seenExternal[c.Hash] { - continue - } - - w.seen[c.Hash] = true - - for _, h := range c.ParentHashes { - if w.seen[h] || w.seenExternal[h] { - continue - } - pc, err := GetCommit(c.s, h) - if err != nil { - return nil, err - } - w.heap.Push(pc) - } - - return c, nil - } -} - -func (w *commitIteratorByCTime) ForEach(cb func(*Commit) error) error { - for { - c, err := w.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - err = cb(c) - if err == storer.ErrStop { - break - } - if err != nil { - return err - } - } - - return nil -} - -func (w *commitIteratorByCTime) Close() {} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_limit.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_limit.go deleted file mode 100644 index 24677a87294..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_limit.go +++ /dev/null @@ -1,65 +0,0 @@ -package object - -import ( - "io" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing/storer" -) - -type commitLimitIter struct { - sourceIter CommitIter - limitOptions LogLimitOptions -} - -type LogLimitOptions struct { - Since *time.Time - Until *time.Time -} - -func NewCommitLimitIterFromIter(commitIter CommitIter, limitOptions LogLimitOptions) CommitIter { - iterator := new(commitLimitIter) - iterator.sourceIter = commitIter - iterator.limitOptions = limitOptions - return iterator -} - -func (c *commitLimitIter) Next() (*Commit, error) { - for { - commit, err := c.sourceIter.Next() - if err != nil { - return nil, err - } - - if c.limitOptions.Since != nil && commit.Committer.When.Before(*c.limitOptions.Since) { - continue - } - if c.limitOptions.Until != nil && commit.Committer.When.After(*c.limitOptions.Until) { - continue - } - return commit, nil - } -} - -func (c *commitLimitIter) ForEach(cb func(*Commit) error) error { - for { - commit, nextErr := c.Next() - if nextErr == io.EOF { - break - } - if nextErr != nil { - return nextErr - } - err := cb(commit) - if err == storer.ErrStop { - return nil - } else if err != nil { - return err - } - } - return nil -} - -func (c *commitLimitIter) Close() { - c.sourceIter.Close() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_path.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_path.go deleted file mode 100644 index b54b7e1d23a..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/commit_walker_path.go +++ /dev/null @@ -1,167 +0,0 @@ -package object - -import ( - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" -) - -type commitPathIter struct { - pathFilter func(string) bool - sourceIter CommitIter - currentCommit *Commit - checkParent bool -} - -// NewCommitPathIterFromIter returns a commit iterator which performs diffTree between -// successive trees returned from the commit iterator from the argument. The purpose of this is -// to find the commits that explain how the files that match the path came to be. -// If checkParent is true then the function double checks if potential parent (next commit in a path) -// is one of the parents in the tree (it's used by `git log --all`). -// pathFilter is a function that takes path of file as argument and returns true if we want it -func NewCommitPathIterFromIter(pathFilter func(string) bool, commitIter CommitIter, checkParent bool) CommitIter { - iterator := new(commitPathIter) - iterator.sourceIter = commitIter - iterator.pathFilter = pathFilter - iterator.checkParent = checkParent - return iterator -} - -// NewCommitFileIterFromIter is kept for compatibility, can be replaced with NewCommitPathIterFromIter -func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, checkParent bool) CommitIter { - return NewCommitPathIterFromIter( - func(path string) bool { - return path == fileName - }, - commitIter, - checkParent, - ) -} - -func (c *commitPathIter) Next() (*Commit, error) { - if c.currentCommit == nil { - var err error - c.currentCommit, err = c.sourceIter.Next() - if err != nil { - return nil, err - } - } - commit, commitErr := c.getNextFileCommit() - - // Setting current-commit to nil to prevent unwanted states when errors are raised - if commitErr != nil { - c.currentCommit = nil - } - return commit, commitErr -} - -func (c *commitPathIter) getNextFileCommit() (*Commit, error) { - var parentTree, currentTree *Tree - - for { - // Parent-commit can be nil if the current-commit is the initial commit - parentCommit, parentCommitErr := c.sourceIter.Next() - if parentCommitErr != nil { - // If the parent-commit is beyond the initial commit, keep it nil - if parentCommitErr != io.EOF { - return nil, parentCommitErr - } - parentCommit = nil - } - - if parentTree == nil { - var currTreeErr error - currentTree, currTreeErr = c.currentCommit.Tree() - if currTreeErr != nil { - return nil, currTreeErr - } - } else { - currentTree = parentTree - parentTree = nil - } - - if parentCommit != nil { - var parentTreeErr error - parentTree, parentTreeErr = parentCommit.Tree() - if parentTreeErr != nil { - return nil, parentTreeErr - } - } - - // Find diff between current and parent trees - changes, diffErr := DiffTree(currentTree, parentTree) - if diffErr != nil { - return nil, diffErr - } - - found := c.hasFileChange(changes, parentCommit) - - // Storing the current-commit in-case a change is found, and - // Updating the current-commit for the next-iteration - prevCommit := c.currentCommit - c.currentCommit = parentCommit - - if found { - return prevCommit, nil - } - - // If not matches found and if parent-commit is beyond the initial commit, then return with EOF - if parentCommit == nil { - return nil, io.EOF - } - } -} - -func (c *commitPathIter) hasFileChange(changes Changes, parent *Commit) bool { - for _, change := range changes { - if !c.pathFilter(change.name()) { - continue - } - - // filename matches, now check if source iterator contains all commits (from all refs) - if c.checkParent { - // Check if parent is beyond the initial commit - if parent == nil || isParentHash(parent.Hash, c.currentCommit) { - return true - } - continue - } - - return true - } - - return false -} - -func isParentHash(hash plumbing.Hash, commit *Commit) bool { - for _, h := range commit.ParentHashes { - if h == hash { - return true - } - } - return false -} - -func (c *commitPathIter) ForEach(cb func(*Commit) error) error { - for { - commit, nextErr := c.Next() - if nextErr == io.EOF { - break - } - if nextErr != nil { - return nextErr - } - err := cb(commit) - if err == storer.ErrStop { - return nil - } else if err != nil { - return err - } - } - return nil -} - -func (c *commitPathIter) Close() { - c.sourceIter.Close() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/difftree.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/difftree.go deleted file mode 100644 index a2dd582bee3..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/difftree.go +++ /dev/null @@ -1,98 +0,0 @@ -package object - -import ( - "bytes" - "context" - - "github.com/jesseduffield/go-git/v5/utils/merkletrie" - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" -) - -// DiffTree compares the content and mode of the blobs found via two -// tree objects. -// DiffTree does not perform rename detection, use DiffTreeWithOptions -// instead to detect renames. -func DiffTree(a, b *Tree) (Changes, error) { - return DiffTreeContext(context.Background(), a, b) -} - -// DiffTreeContext compares the content and mode of the blobs found via two -// tree objects. Provided context must be non-nil. -// An error will be returned if context expires. -func DiffTreeContext(ctx context.Context, a, b *Tree) (Changes, error) { - return DiffTreeWithOptions(ctx, a, b, nil) -} - -// DiffTreeOptions are the configurable options when performing a diff tree. -type DiffTreeOptions struct { - // DetectRenames is whether the diff tree will use rename detection. - DetectRenames bool - // RenameScore is the threshold to of similarity between files to consider - // that a pair of delete and insert are a rename. The number must be - // exactly between 0 and 100. - RenameScore uint - // RenameLimit is the maximum amount of files that can be compared when - // detecting renames. The number of comparisons that have to be performed - // is equal to the number of deleted files * the number of added files. - // That means, that if 100 files were deleted and 50 files were added, 5000 - // file comparisons may be needed. So, if the rename limit is 50, the number - // of both deleted and added needs to be equal or less than 50. - // A value of 0 means no limit. - RenameLimit uint - // OnlyExactRenames performs only detection of exact renames and will not perform - // any detection of renames based on file similarity. - OnlyExactRenames bool -} - -// DefaultDiffTreeOptions are the default and recommended options for the -// diff tree. -var DefaultDiffTreeOptions = &DiffTreeOptions{ - DetectRenames: true, - RenameScore: 60, - RenameLimit: 0, - OnlyExactRenames: false, -} - -// DiffTreeWithOptions compares the content and mode of the blobs found -// via two tree objects with the given options. The provided context -// must be non-nil. -// If no options are passed, no rename detection will be performed. The -// recommended options are DefaultDiffTreeOptions. -// An error will be returned if the context expires. -// This function will be deprecated and removed in v6 so the default -// behaviour of DiffTree is to detect renames. -func DiffTreeWithOptions( - ctx context.Context, - a, b *Tree, - opts *DiffTreeOptions, -) (Changes, error) { - from := NewTreeRootNode(a) - to := NewTreeRootNode(b) - - hashEqual := func(a, b noder.Hasher) bool { - return bytes.Equal(a.Hash(), b.Hash()) - } - - merkletrieChanges, err := merkletrie.DiffTreeContext(ctx, from, to, hashEqual) - if err != nil { - if err == merkletrie.ErrCanceled { - return nil, ErrCanceled - } - return nil, err - } - - changes, err := newChanges(merkletrieChanges) - if err != nil { - return nil, err - } - - if opts == nil { - opts = new(DiffTreeOptions) - } - - if opts.DetectRenames { - return DetectRenames(changes, opts) - } - - return changes, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/file.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/file.go deleted file mode 100644 index 755f8785983..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/file.go +++ /dev/null @@ -1,137 +0,0 @@ -package object - -import ( - "bytes" - "io" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing/filemode" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/utils/binary" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -// File represents git file objects. -type File struct { - // Name is the path of the file. It might be relative to a tree, - // depending of the function that generates it. - Name string - // Mode is the file mode. - Mode filemode.FileMode - // Blob with the contents of the file. - Blob -} - -// NewFile returns a File based on the given blob object -func NewFile(name string, m filemode.FileMode, b *Blob) *File { - return &File{Name: name, Mode: m, Blob: *b} -} - -// Contents returns the contents of a file as a string. -func (f *File) Contents() (content string, err error) { - reader, err := f.Reader() - if err != nil { - return "", err - } - defer ioutil.CheckClose(reader, &err) - - buf := new(bytes.Buffer) - if _, err := buf.ReadFrom(reader); err != nil { - return "", err - } - - return buf.String(), nil -} - -// IsBinary returns if the file is binary or not -func (f *File) IsBinary() (bin bool, err error) { - reader, err := f.Reader() - if err != nil { - return false, err - } - defer ioutil.CheckClose(reader, &err) - - return binary.IsBinary(reader) -} - -// Lines returns a slice of lines from the contents of a file, stripping -// all end of line characters. If the last line is empty (does not end -// in an end of line), it is also stripped. -func (f *File) Lines() ([]string, error) { - content, err := f.Contents() - if err != nil { - return nil, err - } - - splits := strings.Split(content, "\n") - // remove the last line if it is empty - if splits[len(splits)-1] == "" { - return splits[:len(splits)-1], nil - } - - return splits, nil -} - -// FileIter provides an iterator for the files in a tree. -type FileIter struct { - s storer.EncodedObjectStorer - w TreeWalker -} - -// NewFileIter takes a storer.EncodedObjectStorer and a Tree and returns a -// *FileIter that iterates over all files contained in the tree, recursively. -func NewFileIter(s storer.EncodedObjectStorer, t *Tree) *FileIter { - return &FileIter{s: s, w: *NewTreeWalker(t, true, nil)} -} - -// Next moves the iterator to the next file and returns a pointer to it. If -// there are no more files, it returns io.EOF. -func (iter *FileIter) Next() (*File, error) { - for { - name, entry, err := iter.w.Next() - if err != nil { - return nil, err - } - - if entry.Mode == filemode.Dir || entry.Mode == filemode.Submodule { - continue - } - - blob, err := GetBlob(iter.s, entry.Hash) - if err != nil { - return nil, err - } - - return NewFile(name, entry.Mode, blob), nil - } -} - -// ForEach call the cb function for each file contained in this iter until -// an error happens or the end of the iter is reached. If plumbing.ErrStop is sent -// the iteration is stop but no error is returned. The iterator is closed. -func (iter *FileIter) ForEach(cb func(*File) error) error { - defer iter.Close() - - for { - f, err := iter.Next() - if err != nil { - if err == io.EOF { - return nil - } - - return err - } - - if err := cb(f); err != nil { - if err == storer.ErrStop { - return nil - } - - return err - } - } -} - -func (iter *FileIter) Close() { - iter.w.Close() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/merge_base.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/merge_base.go deleted file mode 100644 index 33eb5d8b07e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/merge_base.go +++ /dev/null @@ -1,210 +0,0 @@ -package object - -import ( - "fmt" - "sort" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" -) - -// errIsReachable is thrown when first commit is an ancestor of the second -var errIsReachable = fmt.Errorf("first is reachable from second") - -// MergeBase mimics the behavior of `git merge-base actual other`, returning the -// best common ancestor between the actual and the passed one. -// The best common ancestors can not be reached from other common ancestors. -func (c *Commit) MergeBase(other *Commit) ([]*Commit, error) { - // use sortedByCommitDateDesc strategy - sorted := sortByCommitDateDesc(c, other) - newer := sorted[0] - older := sorted[1] - - newerHistory, err := ancestorsIndex(older, newer) - if err == errIsReachable { - return []*Commit{older}, nil - } - - if err != nil { - return nil, err - } - - var res []*Commit - inNewerHistory := isInIndexCommitFilter(newerHistory) - resIter := NewFilterCommitIter(older, &inNewerHistory, &inNewerHistory) - _ = resIter.ForEach(func(commit *Commit) error { - res = append(res, commit) - return nil - }) - - return Independents(res) -} - -// IsAncestor returns true if the actual commit is ancestor of the passed one. -// It returns an error if the history is not transversable -// It mimics the behavior of `git merge --is-ancestor actual other` -func (c *Commit) IsAncestor(other *Commit) (bool, error) { - found := false - iter := NewCommitPreorderIter(other, nil, nil) - err := iter.ForEach(func(comm *Commit) error { - if comm.Hash != c.Hash { - return nil - } - - found = true - return storer.ErrStop - }) - - return found, err -} - -// ancestorsIndex returns a map with the ancestors of the starting commit if the -// excluded one is not one of them. It returns errIsReachable if the excluded commit -// is ancestor of the starting, or another error if the history is not traversable. -func ancestorsIndex(excluded, starting *Commit) (map[plumbing.Hash]struct{}, error) { - if excluded.Hash.String() == starting.Hash.String() { - return nil, errIsReachable - } - - startingHistory := map[plumbing.Hash]struct{}{} - startingIter := NewCommitIterBSF(starting, nil, nil) - err := startingIter.ForEach(func(commit *Commit) error { - if commit.Hash == excluded.Hash { - return errIsReachable - } - - startingHistory[commit.Hash] = struct{}{} - return nil - }) - - if err != nil { - return nil, err - } - - return startingHistory, nil -} - -// Independents returns a subset of the passed commits, that are not reachable the others -// It mimics the behavior of `git merge-base --independent commit...`. -func Independents(commits []*Commit) ([]*Commit, error) { - // use sortedByCommitDateDesc strategy - candidates := sortByCommitDateDesc(commits...) - candidates = removeDuplicated(candidates) - - seen := map[plumbing.Hash]struct{}{} - var isLimit CommitFilter = func(commit *Commit) bool { - _, ok := seen[commit.Hash] - return ok - } - - if len(candidates) < 2 { - return candidates, nil - } - - pos := 0 - for { - from := candidates[pos] - others := remove(candidates, from) - fromHistoryIter := NewFilterCommitIter(from, nil, &isLimit) - err := fromHistoryIter.ForEach(func(fromAncestor *Commit) error { - for _, other := range others { - if fromAncestor.Hash == other.Hash { - candidates = remove(candidates, other) - others = remove(others, other) - } - } - - if len(candidates) == 1 { - return storer.ErrStop - } - - seen[fromAncestor.Hash] = struct{}{} - return nil - }) - - if err != nil { - return nil, err - } - - nextPos := indexOf(candidates, from) + 1 - if nextPos >= len(candidates) { - break - } - - pos = nextPos - } - - return candidates, nil -} - -// sortByCommitDateDesc returns the passed commits, sorted by `committer.When desc` -// -// Following this strategy, it is tried to reduce the time needed when walking -// the history from one commit to reach the others. It is assumed that ancestors -// use to be committed before its descendant; -// That way `Independents(A^, A)` will be processed as being `Independents(A, A^)`; -// so starting by `A` it will be reached `A^` way sooner than walking from `A^` -// to the initial commit, and then from `A` to `A^`. -func sortByCommitDateDesc(commits ...*Commit) []*Commit { - sorted := make([]*Commit, len(commits)) - copy(sorted, commits) - sort.Slice(sorted, func(i, j int) bool { - return sorted[i].Committer.When.After(sorted[j].Committer.When) - }) - - return sorted -} - -// indexOf returns the first position where target was found in the passed commits -func indexOf(commits []*Commit, target *Commit) int { - for i, commit := range commits { - if target.Hash == commit.Hash { - return i - } - } - - return -1 -} - -// remove returns the passed commits excluding the commit toDelete -func remove(commits []*Commit, toDelete *Commit) []*Commit { - res := make([]*Commit, len(commits)) - j := 0 - for _, commit := range commits { - if commit.Hash == toDelete.Hash { - continue - } - - res[j] = commit - j++ - } - - return res[:j] -} - -// removeDuplicated removes duplicated commits from the passed slice of commits -func removeDuplicated(commits []*Commit) []*Commit { - seen := make(map[plumbing.Hash]struct{}, len(commits)) - res := make([]*Commit, len(commits)) - j := 0 - for _, commit := range commits { - if _, ok := seen[commit.Hash]; ok { - continue - } - - seen[commit.Hash] = struct{}{} - res[j] = commit - j++ - } - - return res[:j] -} - -// isInIndexCommitFilter returns a commitFilter that returns true -// if the commit is in the passed index. -func isInIndexCommitFilter(index map[plumbing.Hash]struct{}) CommitFilter { - return func(c *Commit) bool { - _, ok := index[c.Hash] - return ok - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/object.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/object.go deleted file mode 100644 index d77b358e3a1..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/object.go +++ /dev/null @@ -1,239 +0,0 @@ -// Package object contains implementations of all Git objects and utility -// functions to work with them. -package object - -import ( - "bytes" - "errors" - "fmt" - "io" - "strconv" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" -) - -// ErrUnsupportedObject trigger when a non-supported object is being decoded. -var ErrUnsupportedObject = errors.New("unsupported object type") - -// Object is a generic representation of any git object. It is implemented by -// Commit, Tree, Blob, and Tag, and includes the functions that are common to -// them. -// -// Object is returned when an object can be of any type. It is frequently used -// with a type cast to acquire the specific type of object: -// -// func process(obj Object) { -// switch o := obj.(type) { -// case *Commit: -// // o is a Commit -// case *Tree: -// // o is a Tree -// case *Blob: -// // o is a Blob -// case *Tag: -// // o is a Tag -// } -// } -// -// This interface is intentionally different from plumbing.EncodedObject, which -// is a lower level interface used by storage implementations to read and write -// objects in its encoded form. -type Object interface { - ID() plumbing.Hash - Type() plumbing.ObjectType - Decode(plumbing.EncodedObject) error - Encode(plumbing.EncodedObject) error -} - -// GetObject gets an object from an object storer and decodes it. -func GetObject(s storer.EncodedObjectStorer, h plumbing.Hash) (Object, error) { - o, err := s.EncodedObject(plumbing.AnyObject, h) - if err != nil { - return nil, err - } - - return DecodeObject(s, o) -} - -// DecodeObject decodes an encoded object into an Object and associates it to -// the given object storer. -func DecodeObject(s storer.EncodedObjectStorer, o plumbing.EncodedObject) (Object, error) { - switch o.Type() { - case plumbing.CommitObject: - return DecodeCommit(s, o) - case plumbing.TreeObject: - return DecodeTree(s, o) - case plumbing.BlobObject: - return DecodeBlob(o) - case plumbing.TagObject: - return DecodeTag(s, o) - default: - return nil, plumbing.ErrInvalidType - } -} - -// DateFormat is the format being used in the original git implementation -const DateFormat = "Mon Jan 02 15:04:05 2006 -0700" - -// Signature is used to identify who and when created a commit or tag. -type Signature struct { - // Name represents a person name. It is an arbitrary string. - Name string - // Email is an email, but it cannot be assumed to be well-formed. - Email string - // When is the timestamp of the signature. - When time.Time -} - -// Decode decodes a byte slice into a signature -func (s *Signature) Decode(b []byte) { - open := bytes.LastIndexByte(b, '<') - close := bytes.LastIndexByte(b, '>') - if open == -1 || close == -1 { - return - } - - if close < open { - return - } - - s.Name = string(bytes.Trim(b[:open], " ")) - s.Email = string(b[open+1 : close]) - - hasTime := close+2 < len(b) - if hasTime { - s.decodeTimeAndTimeZone(b[close+2:]) - } -} - -// Encode encodes a Signature into a writer. -func (s *Signature) Encode(w io.Writer) error { - if _, err := fmt.Fprintf(w, "%s <%s> ", s.Name, s.Email); err != nil { - return err - } - if err := s.encodeTimeAndTimeZone(w); err != nil { - return err - } - return nil -} - -var timeZoneLength = 5 - -func (s *Signature) decodeTimeAndTimeZone(b []byte) { - space := bytes.IndexByte(b, ' ') - if space == -1 { - space = len(b) - } - - ts, err := strconv.ParseInt(string(b[:space]), 10, 64) - if err != nil { - return - } - - s.When = time.Unix(ts, 0).In(time.UTC) - var tzStart = space + 1 - if tzStart >= len(b) || tzStart+timeZoneLength > len(b) { - return - } - - timezone := string(b[tzStart : tzStart+timeZoneLength]) - tzhours, err1 := strconv.ParseInt(timezone[0:3], 10, 64) - tzmins, err2 := strconv.ParseInt(timezone[3:], 10, 64) - if err1 != nil || err2 != nil { - return - } - if tzhours < 0 { - tzmins *= -1 - } - - tz := time.FixedZone("", int(tzhours*60*60+tzmins*60)) - - s.When = s.When.In(tz) -} - -func (s *Signature) encodeTimeAndTimeZone(w io.Writer) error { - u := s.When.Unix() - if u < 0 { - u = 0 - } - _, err := fmt.Fprintf(w, "%d %s", u, s.When.Format("-0700")) - return err -} - -func (s *Signature) String() string { - return fmt.Sprintf("%s <%s>", s.Name, s.Email) -} - -// ObjectIter provides an iterator for a set of objects. -type ObjectIter struct { - storer.EncodedObjectIter - s storer.EncodedObjectStorer -} - -// NewObjectIter takes a storer.EncodedObjectStorer and a -// storer.EncodedObjectIter and returns an *ObjectIter that iterates over all -// objects contained in the storer.EncodedObjectIter. -func NewObjectIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) *ObjectIter { - return &ObjectIter{iter, s} -} - -// Next moves the iterator to the next object and returns a pointer to it. If -// there are no more objects, it returns io.EOF. -func (iter *ObjectIter) Next() (Object, error) { - for { - obj, err := iter.EncodedObjectIter.Next() - if err != nil { - return nil, err - } - - o, err := iter.toObject(obj) - if err == plumbing.ErrInvalidType { - continue - } - - if err != nil { - return nil, err - } - - return o, nil - } -} - -// ForEach call the cb function for each object contained on this iter until -// an error happens or the end of the iter is reached. If ErrStop is sent -// the iteration is stop but no error is returned. The iterator is closed. -func (iter *ObjectIter) ForEach(cb func(Object) error) error { - return iter.EncodedObjectIter.ForEach(func(obj plumbing.EncodedObject) error { - o, err := iter.toObject(obj) - if err == plumbing.ErrInvalidType { - return nil - } - - if err != nil { - return err - } - - return cb(o) - }) -} - -func (iter *ObjectIter) toObject(obj plumbing.EncodedObject) (Object, error) { - switch obj.Type() { - case plumbing.BlobObject: - blob := &Blob{} - return blob, blob.Decode(obj) - case plumbing.TreeObject: - tree := &Tree{s: iter.s} - return tree, tree.Decode(obj) - case plumbing.CommitObject: - commit := &Commit{} - return commit, commit.Decode(obj) - case plumbing.TagObject: - tag := &Tag{} - return tag, tag.Decode(obj) - default: - return nil, plumbing.ErrInvalidType - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/patch.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/patch.go deleted file mode 100644 index 7a35b07ecd5..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/patch.go +++ /dev/null @@ -1,337 +0,0 @@ -package object - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "strconv" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" - fdiff "github.com/jesseduffield/go-git/v5/plumbing/format/diff" - "github.com/jesseduffield/go-git/v5/utils/diff" - - dmp "github.com/sergi/go-diff/diffmatchpatch" -) - -var ( - ErrCanceled = errors.New("operation canceled") -) - -func getPatch(message string, changes ...*Change) (*Patch, error) { - ctx := context.Background() - return getPatchContext(ctx, message, changes...) -} - -func getPatchContext(ctx context.Context, message string, changes ...*Change) (*Patch, error) { - var filePatches []fdiff.FilePatch - for _, c := range changes { - select { - case <-ctx.Done(): - return nil, ErrCanceled - default: - } - - fp, err := filePatchWithContext(ctx, c) - if err != nil { - return nil, err - } - - filePatches = append(filePatches, fp) - } - - return &Patch{message, filePatches}, nil -} - -func filePatchWithContext(ctx context.Context, c *Change) (fdiff.FilePatch, error) { - from, to, err := c.Files() - if err != nil { - return nil, err - } - fromContent, fIsBinary, err := fileContent(from) - if err != nil { - return nil, err - } - - toContent, tIsBinary, err := fileContent(to) - if err != nil { - return nil, err - } - - if fIsBinary || tIsBinary { - return &textFilePatch{from: c.From, to: c.To}, nil - } - - diffs := diff.Do(fromContent, toContent) - - var chunks []fdiff.Chunk - for _, d := range diffs { - select { - case <-ctx.Done(): - return nil, ErrCanceled - default: - } - - var op fdiff.Operation - switch d.Type { - case dmp.DiffEqual: - op = fdiff.Equal - case dmp.DiffDelete: - op = fdiff.Delete - case dmp.DiffInsert: - op = fdiff.Add - } - - chunks = append(chunks, &textChunk{d.Text, op}) - } - - return &textFilePatch{ - chunks: chunks, - from: c.From, - to: c.To, - }, nil - -} - -func fileContent(f *File) (content string, isBinary bool, err error) { - if f == nil { - return - } - - isBinary, err = f.IsBinary() - if err != nil || isBinary { - return - } - - content, err = f.Contents() - - return -} - -// Patch is an implementation of fdiff.Patch interface -type Patch struct { - message string - filePatches []fdiff.FilePatch -} - -func (p *Patch) FilePatches() []fdiff.FilePatch { - return p.filePatches -} - -func (p *Patch) Message() string { - return p.message -} - -func (p *Patch) Encode(w io.Writer) error { - ue := fdiff.NewUnifiedEncoder(w, fdiff.DefaultContextLines) - - return ue.Encode(p) -} - -func (p *Patch) Stats() FileStats { - return getFileStatsFromFilePatches(p.FilePatches()) -} - -func (p *Patch) String() string { - buf := bytes.NewBuffer(nil) - err := p.Encode(buf) - if err != nil { - return fmt.Sprintf("malformed patch: %s", err.Error()) - } - - return buf.String() -} - -// changeEntryWrapper is an implementation of fdiff.File interface -type changeEntryWrapper struct { - ce ChangeEntry -} - -func (f *changeEntryWrapper) Hash() plumbing.Hash { - if !f.ce.TreeEntry.Mode.IsFile() { - return plumbing.ZeroHash - } - - return f.ce.TreeEntry.Hash -} - -func (f *changeEntryWrapper) Mode() filemode.FileMode { - return f.ce.TreeEntry.Mode -} -func (f *changeEntryWrapper) Path() string { - if !f.ce.TreeEntry.Mode.IsFile() { - return "" - } - - return f.ce.Name -} - -func (f *changeEntryWrapper) Empty() bool { - return !f.ce.TreeEntry.Mode.IsFile() -} - -// textFilePatch is an implementation of fdiff.FilePatch interface -type textFilePatch struct { - chunks []fdiff.Chunk - from, to ChangeEntry -} - -func (tf *textFilePatch) Files() (from fdiff.File, to fdiff.File) { - f := &changeEntryWrapper{tf.from} - t := &changeEntryWrapper{tf.to} - - if !f.Empty() { - from = f - } - - if !t.Empty() { - to = t - } - - return -} - -func (tf *textFilePatch) IsBinary() bool { - return len(tf.chunks) == 0 -} - -func (tf *textFilePatch) Chunks() []fdiff.Chunk { - return tf.chunks -} - -// textChunk is an implementation of fdiff.Chunk interface -type textChunk struct { - content string - op fdiff.Operation -} - -func (t *textChunk) Content() string { - return t.content -} - -func (t *textChunk) Type() fdiff.Operation { - return t.op -} - -// FileStat stores the status of changes in content of a file. -type FileStat struct { - Name string - Addition int - Deletion int -} - -func (fs FileStat) String() string { - return printStat([]FileStat{fs}) -} - -// FileStats is a collection of FileStat. -type FileStats []FileStat - -func (fileStats FileStats) String() string { - return printStat(fileStats) -} - -// printStat prints the stats of changes in content of files. -// Original implementation: https://github.com/git/git/blob/1a87c842ece327d03d08096395969aca5e0a6996/diff.c#L2615 -// Parts of the output: -// |<+++/---> -// example: " main.go | 10 +++++++--- " -func printStat(fileStats []FileStat) string { - maxGraphWidth := uint(53) - maxNameLen := 0 - maxChangeLen := 0 - - scaleLinear := func(it, width, max uint) uint { - if it == 0 || max == 0 { - return 0 - } - - return 1 + (it * (width - 1) / max) - } - - for _, fs := range fileStats { - if len(fs.Name) > maxNameLen { - maxNameLen = len(fs.Name) - } - - changes := strconv.Itoa(fs.Addition + fs.Deletion) - if len(changes) > maxChangeLen { - maxChangeLen = len(changes) - } - } - - result := "" - for _, fs := range fileStats { - add := uint(fs.Addition) - del := uint(fs.Deletion) - np := maxNameLen - len(fs.Name) - cp := maxChangeLen - len(strconv.Itoa(fs.Addition+fs.Deletion)) - - total := add + del - if total > maxGraphWidth { - add = scaleLinear(add, maxGraphWidth, total) - del = scaleLinear(del, maxGraphWidth, total) - } - - adds := strings.Repeat("+", int(add)) - dels := strings.Repeat("-", int(del)) - namePad := strings.Repeat(" ", np) - changePad := strings.Repeat(" ", cp) - - result += fmt.Sprintf(" %s%s | %s%d %s%s\n", fs.Name, namePad, changePad, total, adds, dels) - } - return result -} - -func getFileStatsFromFilePatches(filePatches []fdiff.FilePatch) FileStats { - var fileStats FileStats - - for _, fp := range filePatches { - // ignore empty patches (binary files, submodule refs updates) - if len(fp.Chunks()) == 0 { - continue - } - - cs := FileStat{} - from, to := fp.Files() - if from == nil { - // New File is created. - cs.Name = to.Path() - } else if to == nil { - // File is deleted. - cs.Name = from.Path() - } else if from.Path() != to.Path() { - // File is renamed. - cs.Name = fmt.Sprintf("%s => %s", from.Path(), to.Path()) - } else { - cs.Name = from.Path() - } - - for _, chunk := range fp.Chunks() { - s := chunk.Content() - if len(s) == 0 { - continue - } - - switch chunk.Type() { - case fdiff.Add: - cs.Addition += strings.Count(s, "\n") - if s[len(s)-1] != '\n' { - cs.Addition++ - } - case fdiff.Delete: - cs.Deletion += strings.Count(s, "\n") - if s[len(s)-1] != '\n' { - cs.Deletion++ - } - } - } - - fileStats = append(fileStats, cs) - } - - return fileStats -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/rename.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/rename.go deleted file mode 100644 index 9d27dd1c328..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/rename.go +++ /dev/null @@ -1,816 +0,0 @@ -package object - -import ( - "errors" - "io" - "sort" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/jesseduffield/go-git/v5/utils/merkletrie" -) - -// DetectRenames detects the renames in the given changes on two trees with -// the given options. It will return the given changes grouping additions and -// deletions into modifications when possible. -// If options is nil, the default diff tree options will be used. -func DetectRenames( - changes Changes, - opts *DiffTreeOptions, -) (Changes, error) { - if opts == nil { - opts = DefaultDiffTreeOptions - } - - detector := &renameDetector{ - renameScore: int(opts.RenameScore), - renameLimit: int(opts.RenameLimit), - onlyExact: opts.OnlyExactRenames, - } - - for _, c := range changes { - action, err := c.Action() - if err != nil { - return nil, err - } - - switch action { - case merkletrie.Insert: - detector.added = append(detector.added, c) - case merkletrie.Delete: - detector.deleted = append(detector.deleted, c) - default: - detector.modified = append(detector.modified, c) - } - } - - return detector.detect() -} - -// renameDetector will detect and resolve renames in a set of changes. -// see: https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java -type renameDetector struct { - added []*Change - deleted []*Change - modified []*Change - - renameScore int - renameLimit int - onlyExact bool -} - -// detectExactRenames detects matches files that were deleted with files that -// were added where the hash is the same on both. If there are multiple targets -// the one with the most similar path will be chosen as the rename and the -// rest as either deletions or additions. -func (d *renameDetector) detectExactRenames() { - added := groupChangesByHash(d.added) - deletes := groupChangesByHash(d.deleted) - var uniqueAdds []*Change - var nonUniqueAdds [][]*Change - var addedLeft []*Change - - for _, cs := range added { - if len(cs) == 1 { - uniqueAdds = append(uniqueAdds, cs[0]) - } else { - nonUniqueAdds = append(nonUniqueAdds, cs) - } - } - - for _, c := range uniqueAdds { - hash := changeHash(c) - deleted := deletes[hash] - - if len(deleted) == 1 { - if sameMode(c, deleted[0]) { - d.modified = append(d.modified, &Change{From: deleted[0].From, To: c.To}) - delete(deletes, hash) - } else { - addedLeft = append(addedLeft, c) - } - } else if len(deleted) > 1 { - bestMatch := bestNameMatch(c, deleted) - if bestMatch != nil && sameMode(c, bestMatch) { - d.modified = append(d.modified, &Change{From: bestMatch.From, To: c.To}) - delete(deletes, hash) - - var newDeletes = make([]*Change, 0, len(deleted)-1) - for _, d := range deleted { - if d != bestMatch { - newDeletes = append(newDeletes, d) - } - } - deletes[hash] = newDeletes - } - } else { - addedLeft = append(addedLeft, c) - } - } - - for _, added := range nonUniqueAdds { - hash := changeHash(added[0]) - deleted := deletes[hash] - - if len(deleted) == 1 { - deleted := deleted[0] - bestMatch := bestNameMatch(deleted, added) - if bestMatch != nil && sameMode(deleted, bestMatch) { - d.modified = append(d.modified, &Change{From: deleted.From, To: bestMatch.To}) - delete(deletes, hash) - - for _, c := range added { - if c != bestMatch { - addedLeft = append(addedLeft, c) - } - } - } else { - addedLeft = append(addedLeft, added...) - } - } else if len(deleted) > 1 { - maxSize := len(deleted) * len(added) - if d.renameLimit > 0 && d.renameLimit < maxSize { - maxSize = d.renameLimit - } - - matrix := make(similarityMatrix, 0, maxSize) - - for delIdx, del := range deleted { - deletedName := changeName(del) - - for addIdx, add := range added { - addedName := changeName(add) - - score := nameSimilarityScore(addedName, deletedName) - matrix = append(matrix, similarityPair{added: addIdx, deleted: delIdx, score: score}) - - if len(matrix) >= maxSize { - break - } - } - - if len(matrix) >= maxSize { - break - } - } - - sort.Stable(matrix) - - usedAdds := make(map[*Change]struct{}) - usedDeletes := make(map[*Change]struct{}) - for i := len(matrix) - 1; i >= 0; i-- { - del := deleted[matrix[i].deleted] - add := added[matrix[i].added] - - if add == nil || del == nil { - // it was already matched - continue - } - - usedAdds[add] = struct{}{} - usedDeletes[del] = struct{}{} - d.modified = append(d.modified, &Change{From: del.From, To: add.To}) - added[matrix[i].added] = nil - deleted[matrix[i].deleted] = nil - } - - for _, c := range added { - if _, ok := usedAdds[c]; !ok && c != nil { - addedLeft = append(addedLeft, c) - } - } - - var newDeletes = make([]*Change, 0, len(deleted)-len(usedDeletes)) - for _, c := range deleted { - if _, ok := usedDeletes[c]; !ok && c != nil { - newDeletes = append(newDeletes, c) - } - } - deletes[hash] = newDeletes - } else { - addedLeft = append(addedLeft, added...) - } - } - - d.added = addedLeft - d.deleted = nil - for _, dels := range deletes { - d.deleted = append(d.deleted, dels...) - } -} - -// detectContentRenames detects renames based on the similarity of the content -// in the files by building a matrix of pairs between sources and destinations -// and matching by the highest score. -// see: https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java -func (d *renameDetector) detectContentRenames() error { - cnt := max(len(d.added), len(d.deleted)) - if d.renameLimit > 0 && cnt > d.renameLimit { - return nil - } - - srcs, dsts := d.deleted, d.added - matrix, err := buildSimilarityMatrix(srcs, dsts, d.renameScore) - if err != nil { - return err - } - renames := make([]*Change, 0, min(len(matrix), len(dsts))) - - // Match rename pairs on a first come, first serve basis until - // we have looked at everything that is above the minimum score. - for i := len(matrix) - 1; i >= 0; i-- { - pair := matrix[i] - src := srcs[pair.deleted] - dst := dsts[pair.added] - - if dst == nil || src == nil { - // It was already matched before - continue - } - - renames = append(renames, &Change{From: src.From, To: dst.To}) - - // Claim destination and source as matched - dsts[pair.added] = nil - srcs[pair.deleted] = nil - } - - d.modified = append(d.modified, renames...) - d.added = compactChanges(dsts) - d.deleted = compactChanges(srcs) - - return nil -} - -func (d *renameDetector) detect() (Changes, error) { - if len(d.added) > 0 && len(d.deleted) > 0 { - d.detectExactRenames() - - if !d.onlyExact { - if err := d.detectContentRenames(); err != nil { - return nil, err - } - } - } - - result := make(Changes, 0, len(d.added)+len(d.deleted)+len(d.modified)) - result = append(result, d.added...) - result = append(result, d.deleted...) - result = append(result, d.modified...) - - sort.Stable(result) - - return result, nil -} - -func bestNameMatch(change *Change, changes []*Change) *Change { - var best *Change - var bestScore int - - cname := changeName(change) - - for _, c := range changes { - score := nameSimilarityScore(cname, changeName(c)) - if score > bestScore { - bestScore = score - best = c - } - } - - return best -} - -func nameSimilarityScore(a, b string) int { - aDirLen := strings.LastIndexByte(a, '/') + 1 - bDirLen := strings.LastIndexByte(b, '/') + 1 - - dirMin := min(aDirLen, bDirLen) - dirMax := max(aDirLen, bDirLen) - - var dirScoreLtr, dirScoreRtl int - if dirMax == 0 { - dirScoreLtr = 100 - dirScoreRtl = 100 - } else { - var dirSim int - - for ; dirSim < dirMin; dirSim++ { - if a[dirSim] != b[dirSim] { - break - } - } - - dirScoreLtr = dirSim * 100 / dirMax - - if dirScoreLtr == 100 { - dirScoreRtl = 100 - } else { - for dirSim = 0; dirSim < dirMin; dirSim++ { - if a[aDirLen-1-dirSim] != b[bDirLen-1-dirSim] { - break - } - } - dirScoreRtl = dirSim * 100 / dirMax - } - } - - fileMin := min(len(a)-aDirLen, len(b)-bDirLen) - fileMax := max(len(a)-aDirLen, len(b)-bDirLen) - - fileSim := 0 - for ; fileSim < fileMin; fileSim++ { - if a[len(a)-1-fileSim] != b[len(b)-1-fileSim] { - break - } - } - fileScore := fileSim * 100 / fileMax - - return (((dirScoreLtr + dirScoreRtl) * 25) + (fileScore * 50)) / 100 -} - -func changeName(c *Change) string { - if c.To != empty { - return c.To.Name - } - return c.From.Name -} - -func changeHash(c *Change) plumbing.Hash { - if c.To != empty { - return c.To.TreeEntry.Hash - } - - return c.From.TreeEntry.Hash -} - -func changeMode(c *Change) filemode.FileMode { - if c.To != empty { - return c.To.TreeEntry.Mode - } - - return c.From.TreeEntry.Mode -} - -func sameMode(a, b *Change) bool { - return changeMode(a) == changeMode(b) -} - -func groupChangesByHash(changes []*Change) map[plumbing.Hash][]*Change { - var result = make(map[plumbing.Hash][]*Change) - for _, c := range changes { - hash := changeHash(c) - result[hash] = append(result[hash], c) - } - return result -} - -type similarityMatrix []similarityPair - -func (m similarityMatrix) Len() int { return len(m) } -func (m similarityMatrix) Swap(i, j int) { m[i], m[j] = m[j], m[i] } -func (m similarityMatrix) Less(i, j int) bool { - if m[i].score == m[j].score { - if m[i].added == m[j].added { - return m[i].deleted < m[j].deleted - } - return m[i].added < m[j].added - } - return m[i].score < m[j].score -} - -type similarityPair struct { - // index of the added file - added int - // index of the deleted file - deleted int - // similarity score - score int -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -const maxMatrixSize = 10000 - -func buildSimilarityMatrix(srcs, dsts []*Change, renameScore int) (similarityMatrix, error) { - // Allocate for the worst-case scenario where every pair has a score - // that we need to consider. We might not need that many. - matrixSize := len(srcs) * len(dsts) - if matrixSize > maxMatrixSize { - matrixSize = maxMatrixSize - } - matrix := make(similarityMatrix, 0, matrixSize) - srcSizes := make([]int64, len(srcs)) - dstSizes := make([]int64, len(dsts)) - dstTooLarge := make(map[int]bool) - - // Consider each pair of files, if the score is above the minimum - // threshold we need to record that scoring in the matrix so we can - // later find the best matches. -outerLoop: - for srcIdx, src := range srcs { - if changeMode(src) != filemode.Regular { - continue - } - - // Declare the from file and the similarity index here to be able to - // reuse it inside the inner loop. The reason to not initialize them - // here is so we can skip the initialization in case they happen to - // not be needed later. They will be initialized inside the inner - // loop if and only if they're needed and reused in subsequent passes. - var from *File - var s *similarityIndex - var err error - for dstIdx, dst := range dsts { - if changeMode(dst) != filemode.Regular { - continue - } - - if dstTooLarge[dstIdx] { - continue - } - - var to *File - srcSize := srcSizes[srcIdx] - if srcSize == 0 { - from, _, err = src.Files() - if err != nil { - return nil, err - } - srcSize = from.Size + 1 - srcSizes[srcIdx] = srcSize - } - - dstSize := dstSizes[dstIdx] - if dstSize == 0 { - _, to, err = dst.Files() - if err != nil { - return nil, err - } - dstSize = to.Size + 1 - dstSizes[dstIdx] = dstSize - } - - min, max := srcSize, dstSize - if dstSize < srcSize { - min = dstSize - max = srcSize - } - - if int(min*100/max) < renameScore { - // File sizes are too different to be a match - continue - } - - if s == nil { - s, err = fileSimilarityIndex(from) - if err != nil { - if err == errIndexFull { - continue outerLoop - } - return nil, err - } - } - - if to == nil { - _, to, err = dst.Files() - if err != nil { - return nil, err - } - } - - di, err := fileSimilarityIndex(to) - if err != nil { - if err == errIndexFull { - dstTooLarge[dstIdx] = true - } - - return nil, err - } - - contentScore := s.score(di, 10000) - // The name score returns a value between 0 and 100, so we need to - // convert it to the same range as the content score. - nameScore := nameSimilarityScore(src.From.Name, dst.To.Name) * 100 - score := (contentScore*99 + nameScore*1) / 10000 - - if score < renameScore { - continue - } - - matrix = append(matrix, similarityPair{added: dstIdx, deleted: srcIdx, score: score}) - } - } - - sort.Stable(matrix) - - return matrix, nil -} - -func compactChanges(changes []*Change) []*Change { - var result []*Change - for _, c := range changes { - if c != nil { - result = append(result, c) - } - } - return result -} - -const ( - keyShift = 32 - maxCountValue = (1 << keyShift) - 1 -) - -var errIndexFull = errors.New("index is full") - -// similarityIndex is an index structure of lines/blocks in one file. -// This structure can be used to compute an approximation of the similarity -// between two files. -// To save space in memory, this index uses a space efficient encoding which -// will not exceed 1MiB per instance. The index starts out at a smaller size -// (closer to 2KiB), but may grow as more distinct blocks within the scanned -// file are discovered. -// see: https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java -type similarityIndex struct { - hashed uint64 - // number of non-zero entries in hashes - numHashes int - growAt int - hashes []keyCountPair - hashBits int -} - -func fileSimilarityIndex(f *File) (*similarityIndex, error) { - idx := newSimilarityIndex() - if err := idx.hash(f); err != nil { - return nil, err - } - - sort.Stable(keyCountPairs(idx.hashes)) - - return idx, nil -} - -func newSimilarityIndex() *similarityIndex { - return &similarityIndex{ - hashBits: 8, - hashes: make([]keyCountPair, 1<<8), - growAt: shouldGrowAt(8), - } -} - -func (i *similarityIndex) hash(f *File) error { - isBin, err := f.IsBinary() - if err != nil { - return err - } - - r, err := f.Reader() - if err != nil { - return err - } - - defer ioutil.CheckClose(r, &err) - - return i.hashContent(r, f.Size, isBin) -} - -func (i *similarityIndex) hashContent(r io.Reader, size int64, isBin bool) error { - var buf = make([]byte, 4096) - var ptr, cnt int - remaining := size - - for 0 < remaining { - hash := 5381 - var blockHashedCnt uint64 - - // Hash one line or block, whatever happens first - n := int64(0) - for { - if ptr == cnt { - ptr = 0 - var err error - cnt, err = io.ReadFull(r, buf) - if err != nil && err != io.ErrUnexpectedEOF { - return err - } - - if cnt == 0 { - return io.EOF - } - } - n++ - c := buf[ptr] & 0xff - ptr++ - - // Ignore CR in CRLF sequence if it's text - if !isBin && c == '\r' && ptr < cnt && buf[ptr] == '\n' { - continue - } - blockHashedCnt++ - - if c == '\n' { - break - } - - hash = (hash << 5) + hash + int(c) - - if n >= 64 || n >= remaining { - break - } - } - i.hashed += blockHashedCnt - if err := i.add(hash, blockHashedCnt); err != nil { - return err - } - remaining -= n - } - - return nil -} - -// score computes the similarity score between this index and another one. -// A region of a file is defined as a line in a text file or a fixed-size -// block in a binary file. To prepare an index, each region in the file is -// hashed; the values and counts of hashes are retained in a sorted table. -// Define the similarity fraction F as the count of matching regions between -// the two files divided between the maximum count of regions in either file. -// The similarity score is F multiplied by the maxScore constant, yielding a -// range [0, maxScore]. It is defined as maxScore for the degenerate case of -// two empty files. -// The similarity score is symmetrical; i.e. a.score(b) == b.score(a). -func (i *similarityIndex) score(other *similarityIndex, maxScore int) int { - var maxHashed = i.hashed - if maxHashed < other.hashed { - maxHashed = other.hashed - } - if maxHashed == 0 { - return maxScore - } - - return int(i.common(other) * uint64(maxScore) / maxHashed) -} - -func (i *similarityIndex) common(dst *similarityIndex) uint64 { - srcIdx, dstIdx := 0, 0 - if i.numHashes == 0 || dst.numHashes == 0 { - return 0 - } - - var common uint64 - srcKey, dstKey := i.hashes[srcIdx].key(), dst.hashes[dstIdx].key() - - for { - if srcKey == dstKey { - srcCnt, dstCnt := i.hashes[srcIdx].count(), dst.hashes[dstIdx].count() - if srcCnt < dstCnt { - common += srcCnt - } else { - common += dstCnt - } - - srcIdx++ - if srcIdx == len(i.hashes) { - break - } - srcKey = i.hashes[srcIdx].key() - - dstIdx++ - if dstIdx == len(dst.hashes) { - break - } - dstKey = dst.hashes[dstIdx].key() - } else if srcKey < dstKey { - // Region of src that is not in dst - srcIdx++ - if srcIdx == len(i.hashes) { - break - } - srcKey = i.hashes[srcIdx].key() - } else { - // Region of dst that is not in src - dstIdx++ - if dstIdx == len(dst.hashes) { - break - } - dstKey = dst.hashes[dstIdx].key() - } - } - - return common -} - -func (i *similarityIndex) add(key int, cnt uint64) error { - key = int(uint32(key) * 0x9e370001 >> 1) - - j := i.slot(key) - for { - v := i.hashes[j] - if v == 0 { - // It's an empty slot, so we can store it here. - if i.growAt <= i.numHashes { - if err := i.grow(); err != nil { - return err - } - j = i.slot(key) - continue - } - - var err error - i.hashes[j], err = newKeyCountPair(key, cnt) - if err != nil { - return err - } - i.numHashes++ - return nil - } else if v.key() == key { - // It's the same key, so increment the counter. - var err error - i.hashes[j], err = newKeyCountPair(key, v.count()+cnt) - return err - } else if j+1 >= len(i.hashes) { - j = 0 - } else { - j++ - } - } -} - -type keyCountPair uint64 - -func newKeyCountPair(key int, cnt uint64) (keyCountPair, error) { - if cnt > maxCountValue { - return 0, errIndexFull - } - - return keyCountPair((uint64(key) << keyShift) | cnt), nil -} - -func (p keyCountPair) key() int { - return int(p >> keyShift) -} - -func (p keyCountPair) count() uint64 { - return uint64(p) & maxCountValue -} - -func (i *similarityIndex) slot(key int) int { - // We use 31 - hashBits because the upper bit was already forced - // to be 0 and we want the remaining high bits to be used as the - // table slot. - return int(uint32(key) >> uint(31-i.hashBits)) -} - -func shouldGrowAt(hashBits int) int { - return (1 << uint(hashBits)) * (hashBits - 3) / hashBits -} - -func (i *similarityIndex) grow() error { - if i.hashBits == 30 { - return errIndexFull - } - - old := i.hashes - - i.hashBits++ - i.growAt = shouldGrowAt(i.hashBits) - - // TODO(erizocosmico): find a way to check if it will OOM and return - // errIndexFull instead. - i.hashes = make([]keyCountPair, 1<= len(i.hashes) { - j = 0 - } - } - i.hashes[j] = v - } - } - - return nil -} - -type keyCountPairs []keyCountPair - -func (p keyCountPairs) Len() int { return len(p) } -func (p keyCountPairs) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -func (p keyCountPairs) Less(i, j int) bool { return p[i] < p[j] } diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/signature.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/signature.go deleted file mode 100644 index f9c3d306bd9..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/signature.go +++ /dev/null @@ -1,102 +0,0 @@ -package object - -import "bytes" - -const ( - signatureTypeUnknown signatureType = iota - signatureTypeOpenPGP - signatureTypeX509 - signatureTypeSSH -) - -var ( - // openPGPSignatureFormat is the format of an OpenPGP signature. - openPGPSignatureFormat = signatureFormat{ - []byte("-----BEGIN PGP SIGNATURE-----"), - []byte("-----BEGIN PGP MESSAGE-----"), - } - // x509SignatureFormat is the format of an X509 signature, which is - // a PKCS#7 (S/MIME) signature. - x509SignatureFormat = signatureFormat{ - []byte("-----BEGIN CERTIFICATE-----"), - []byte("-----BEGIN SIGNED MESSAGE-----"), - } - - // sshSignatureFormat is the format of an SSH signature. - sshSignatureFormat = signatureFormat{ - []byte("-----BEGIN SSH SIGNATURE-----"), - } -) - -var ( - // knownSignatureFormats is a map of known signature formats, indexed by - // their signatureType. - knownSignatureFormats = map[signatureType]signatureFormat{ - signatureTypeOpenPGP: openPGPSignatureFormat, - signatureTypeX509: x509SignatureFormat, - signatureTypeSSH: sshSignatureFormat, - } -) - -// signatureType represents the type of the signature. -type signatureType int8 - -// signatureFormat represents the beginning of a signature. -type signatureFormat [][]byte - -// typeForSignature returns the type of the signature based on its format. -func typeForSignature(b []byte) signatureType { - for t, i := range knownSignatureFormats { - for _, begin := range i { - if bytes.HasPrefix(b, begin) { - return t - } - } - } - return signatureTypeUnknown -} - -// parseSignedBytes returns the position of the last signature block found in -// the given bytes. If no signature block is found, it returns -1. -// -// When multiple signature blocks are found, the position of the last one is -// returned. Any tailing bytes after this signature block start should be -// considered part of the signature. -// -// Given this, it would be safe to use the returned position to split the bytes -// into two parts: the first part containing the message, the second part -// containing the signature. -// -// Example: -// -// message := []byte(`Message with signature -// -// -----BEGIN SSH SIGNATURE----- -// ...`) -// -// var signature string -// if pos, _ := parseSignedBytes(message); pos != -1 { -// signature = string(message[pos:]) -// message = message[:pos] -// } -// -// This logic is on par with git's gpg-interface.c:parse_signed_buffer(). -// https://github.com/git/git/blob/7c2ef319c52c4997256f5807564523dfd4acdfc7/gpg-interface.c#L668 -func parseSignedBytes(b []byte) (int, signatureType) { - var n, match = 0, -1 - var t signatureType - for n < len(b) { - var i = b[n:] - if st := typeForSignature(i); st != signatureTypeUnknown { - match = n - t = st - } - if eol := bytes.IndexByte(i, '\n'); eol >= 0 { - n += eol + 1 - continue - } - // If we reach this point, we've reached the end. - break - } - return match, t -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/tag.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/tag.go deleted file mode 100644 index 6e303af4ca3..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/tag.go +++ /dev/null @@ -1,330 +0,0 @@ -package object - -import ( - "bytes" - "fmt" - "io" - "strings" - - "github.com/ProtonMail/go-crypto/openpgp" - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/jesseduffield/go-git/v5/utils/sync" -) - -// Tag represents an annotated tag object. It points to a single git object of -// any type, but tags typically are applied to commit or blob objects. It -// provides a reference that associates the target with a tag name. It also -// contains meta-information about the tag, including the tagger, tag date and -// message. -// -// Note that this is not used for lightweight tags. -// -// https://git-scm.com/book/en/v2/Git-Internals-Git-References#Tags -type Tag struct { - // Hash of the tag. - Hash plumbing.Hash - // Name of the tag. - Name string - // Tagger is the one who created the tag. - Tagger Signature - // Message is an arbitrary text message. - Message string - // PGPSignature is the PGP signature of the tag. - PGPSignature string - // TargetType is the object type of the target. - TargetType plumbing.ObjectType - // Target is the hash of the target object. - Target plumbing.Hash - - s storer.EncodedObjectStorer -} - -// GetTag gets a tag from an object storer and decodes it. -func GetTag(s storer.EncodedObjectStorer, h plumbing.Hash) (*Tag, error) { - o, err := s.EncodedObject(plumbing.TagObject, h) - if err != nil { - return nil, err - } - - return DecodeTag(s, o) -} - -// DecodeTag decodes an encoded object into a *Commit and associates it to the -// given object storer. -func DecodeTag(s storer.EncodedObjectStorer, o plumbing.EncodedObject) (*Tag, error) { - t := &Tag{s: s} - if err := t.Decode(o); err != nil { - return nil, err - } - - return t, nil -} - -// ID returns the object ID of the tag, not the object that the tag references. -// The returned value will always match the current value of Tag.Hash. -// -// ID is present to fulfill the Object interface. -func (t *Tag) ID() plumbing.Hash { - return t.Hash -} - -// Type returns the type of object. It always returns plumbing.TagObject. -// -// Type is present to fulfill the Object interface. -func (t *Tag) Type() plumbing.ObjectType { - return plumbing.TagObject -} - -// Decode transforms a plumbing.EncodedObject into a Tag struct. -func (t *Tag) Decode(o plumbing.EncodedObject) (err error) { - if o.Type() != plumbing.TagObject { - return ErrUnsupportedObject - } - - t.Hash = o.Hash() - - reader, err := o.Reader() - if err != nil { - return err - } - defer ioutil.CheckClose(reader, &err) - - r := sync.GetBufioReader(reader) - defer sync.PutBufioReader(r) - - for { - var line []byte - line, err = r.ReadBytes('\n') - if err != nil && err != io.EOF { - return err - } - - line = bytes.TrimSpace(line) - if len(line) == 0 { - break // Start of message - } - - split := bytes.SplitN(line, []byte{' '}, 2) - switch string(split[0]) { - case "object": - t.Target = plumbing.NewHash(string(split[1])) - case "type": - t.TargetType, err = plumbing.ParseObjectType(string(split[1])) - if err != nil { - return err - } - case "tag": - t.Name = string(split[1]) - case "tagger": - t.Tagger.Decode(split[1]) - } - - if err == io.EOF { - return nil - } - } - - data, err := io.ReadAll(r) - if err != nil { - return err - } - if sm, _ := parseSignedBytes(data); sm >= 0 { - t.PGPSignature = string(data[sm:]) - data = data[:sm] - } - t.Message = string(data) - - return nil -} - -// Encode transforms a Tag into a plumbing.EncodedObject. -func (t *Tag) Encode(o plumbing.EncodedObject) error { - return t.encode(o, true) -} - -// EncodeWithoutSignature export a Tag into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature). -func (t *Tag) EncodeWithoutSignature(o plumbing.EncodedObject) error { - return t.encode(o, false) -} - -func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) { - o.SetType(plumbing.TagObject) - w, err := o.Writer() - if err != nil { - return err - } - defer ioutil.CheckClose(w, &err) - - if _, err = fmt.Fprintf(w, - "object %s\ntype %s\ntag %s\ntagger ", - t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil { - return err - } - - if err = t.Tagger.Encode(w); err != nil { - return err - } - - if _, err = fmt.Fprint(w, "\n\n"); err != nil { - return err - } - - if _, err = fmt.Fprint(w, t.Message); err != nil { - return err - } - - // Note that this is highly sensitive to what it sent along in the message. - // Message *always* needs to end with a newline, or else the message and the - // signature will be concatenated into a corrupt object. Since this is a - // lower-level method, we assume you know what you are doing and have already - // done the needful on the message in the caller. - if includeSig { - if _, err = fmt.Fprint(w, t.PGPSignature); err != nil { - return err - } - } - - return err -} - -// Commit returns the commit pointed to by the tag. If the tag points to a -// different type of object ErrUnsupportedObject will be returned. -func (t *Tag) Commit() (*Commit, error) { - if t.TargetType != plumbing.CommitObject { - return nil, ErrUnsupportedObject - } - - o, err := t.s.EncodedObject(plumbing.CommitObject, t.Target) - if err != nil { - return nil, err - } - - return DecodeCommit(t.s, o) -} - -// Tree returns the tree pointed to by the tag. If the tag points to a commit -// object the tree of that commit will be returned. If the tag does not point -// to a commit or tree object ErrUnsupportedObject will be returned. -func (t *Tag) Tree() (*Tree, error) { - switch t.TargetType { - case plumbing.CommitObject: - c, err := t.Commit() - if err != nil { - return nil, err - } - - return c.Tree() - case plumbing.TreeObject: - return GetTree(t.s, t.Target) - default: - return nil, ErrUnsupportedObject - } -} - -// Blob returns the blob pointed to by the tag. If the tag points to a -// different type of object ErrUnsupportedObject will be returned. -func (t *Tag) Blob() (*Blob, error) { - if t.TargetType != plumbing.BlobObject { - return nil, ErrUnsupportedObject - } - - return GetBlob(t.s, t.Target) -} - -// Object returns the object pointed to by the tag. -func (t *Tag) Object() (Object, error) { - o, err := t.s.EncodedObject(t.TargetType, t.Target) - if err != nil { - return nil, err - } - - return DecodeObject(t.s, o) -} - -// String returns the meta information contained in the tag as a formatted -// string. -func (t *Tag) String() string { - obj, _ := t.Object() - - return fmt.Sprintf( - "%s %s\nTagger: %s\nDate: %s\n\n%s\n%s", - plumbing.TagObject, t.Name, t.Tagger.String(), t.Tagger.When.Format(DateFormat), - t.Message, objectAsString(obj), - ) -} - -// Verify performs PGP verification of the tag with a provided armored -// keyring and returns openpgp.Entity associated with verifying key on success. -func (t *Tag) Verify(armoredKeyRing string) (*openpgp.Entity, error) { - keyRingReader := strings.NewReader(armoredKeyRing) - keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader) - if err != nil { - return nil, err - } - - // Extract signature. - signature := strings.NewReader(t.PGPSignature) - - encoded := &plumbing.MemoryObject{} - // Encode tag components, excluding signature and get a reader object. - if err := t.EncodeWithoutSignature(encoded); err != nil { - return nil, err - } - er, err := encoded.Reader() - if err != nil { - return nil, err - } - - return openpgp.CheckArmoredDetachedSignature(keyring, er, signature, nil) -} - -// TagIter provides an iterator for a set of tags. -type TagIter struct { - storer.EncodedObjectIter - s storer.EncodedObjectStorer -} - -// NewTagIter takes a storer.EncodedObjectStorer and a -// storer.EncodedObjectIter and returns a *TagIter that iterates over all -// tags contained in the storer.EncodedObjectIter. -// -// Any non-tag object returned by the storer.EncodedObjectIter is skipped. -func NewTagIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) *TagIter { - return &TagIter{iter, s} -} - -// Next moves the iterator to the next tag and returns a pointer to it. If -// there are no more tags, it returns io.EOF. -func (iter *TagIter) Next() (*Tag, error) { - obj, err := iter.EncodedObjectIter.Next() - if err != nil { - return nil, err - } - - return DecodeTag(iter.s, obj) -} - -// ForEach call the cb function for each tag contained on this iter until -// an error happens or the end of the iter is reached. If ErrStop is sent -// the iteration is stop but no error is returned. The iterator is closed. -func (iter *TagIter) ForEach(cb func(*Tag) error) error { - return iter.EncodedObjectIter.ForEach(func(obj plumbing.EncodedObject) error { - t, err := DecodeTag(iter.s, obj) - if err != nil { - return err - } - - return cb(t) - }) -} - -func objectAsString(obj Object) string { - switch o := obj.(type) { - case *Commit: - return o.String() - default: - return "" - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/tree.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/tree.go deleted file mode 100644 index 35a30958abd..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/tree.go +++ /dev/null @@ -1,558 +0,0 @@ -package object - -import ( - "context" - "errors" - "fmt" - "io" - "path" - "path/filepath" - "sort" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/jesseduffield/go-git/v5/utils/sync" -) - -const ( - maxTreeDepth = 1024 - startingStackSize = 8 -) - -// New errors defined by this package. -var ( - ErrMaxTreeDepth = errors.New("maximum tree depth exceeded") - ErrFileNotFound = errors.New("file not found") - ErrDirectoryNotFound = errors.New("directory not found") - ErrEntryNotFound = errors.New("entry not found") - ErrEntriesNotSorted = errors.New("entries in tree are not sorted") -) - -// Tree is basically like a directory - it references a bunch of other trees -// and/or blobs (i.e. files and sub-directories) -type Tree struct { - Entries []TreeEntry - Hash plumbing.Hash - - s storer.EncodedObjectStorer - m map[string]*TreeEntry - t map[string]*Tree // tree path cache -} - -// GetTree gets a tree from an object storer and decodes it. -func GetTree(s storer.EncodedObjectStorer, h plumbing.Hash) (*Tree, error) { - o, err := s.EncodedObject(plumbing.TreeObject, h) - if err != nil { - return nil, err - } - - return DecodeTree(s, o) -} - -// DecodeTree decodes an encoded object into a *Tree and associates it to the -// given object storer. -func DecodeTree(s storer.EncodedObjectStorer, o plumbing.EncodedObject) (*Tree, error) { - t := &Tree{s: s} - if err := t.Decode(o); err != nil { - return nil, err - } - - return t, nil -} - -// TreeEntry represents a file -type TreeEntry struct { - Name string - Mode filemode.FileMode - Hash plumbing.Hash -} - -// File returns the hash of the file identified by the `path` argument. -// The path is interpreted as relative to the tree receiver. -func (t *Tree) File(path string) (*File, error) { - e, err := t.FindEntry(path) - if err != nil { - return nil, ErrFileNotFound - } - - blob, err := GetBlob(t.s, e.Hash) - if err != nil { - if err == plumbing.ErrObjectNotFound { - return nil, ErrFileNotFound - } - return nil, err - } - - return NewFile(path, e.Mode, blob), nil -} - -// Size returns the plaintext size of an object, without reading it -// into memory. -func (t *Tree) Size(path string) (int64, error) { - e, err := t.FindEntry(path) - if err != nil { - return 0, ErrEntryNotFound - } - - return t.s.EncodedObjectSize(e.Hash) -} - -// Tree returns the tree identified by the `path` argument. -// The path is interpreted as relative to the tree receiver. -func (t *Tree) Tree(path string) (*Tree, error) { - e, err := t.FindEntry(path) - if err != nil { - return nil, ErrDirectoryNotFound - } - - tree, err := GetTree(t.s, e.Hash) - if err == plumbing.ErrObjectNotFound { - return nil, ErrDirectoryNotFound - } - - return tree, err -} - -// TreeEntryFile returns the *File for a given *TreeEntry. -func (t *Tree) TreeEntryFile(e *TreeEntry) (*File, error) { - blob, err := GetBlob(t.s, e.Hash) - if err != nil { - return nil, err - } - - return NewFile(e.Name, e.Mode, blob), nil -} - -// FindEntry search a TreeEntry in this tree or any subtree. -func (t *Tree) FindEntry(path string) (*TreeEntry, error) { - if t.t == nil { - t.t = make(map[string]*Tree) - } - - pathParts := strings.Split(path, "/") - startingTree := t - pathCurrent := "" - - // search for the longest path in the tree path cache - for i := len(pathParts) - 1; i > 1; i-- { - path := filepath.Join(pathParts[:i]...) - - tree, ok := t.t[path] - if ok { - startingTree = tree - pathParts = pathParts[i:] - pathCurrent = path - - break - } - } - - var tree *Tree - var err error - for tree = startingTree; len(pathParts) > 1; pathParts = pathParts[1:] { - if tree, err = tree.dir(pathParts[0]); err != nil { - return nil, err - } - - pathCurrent = filepath.Join(pathCurrent, pathParts[0]) - t.t[pathCurrent] = tree - } - - return tree.entry(pathParts[0]) -} - -func (t *Tree) dir(baseName string) (*Tree, error) { - entry, err := t.entry(baseName) - if err != nil { - return nil, ErrDirectoryNotFound - } - - obj, err := t.s.EncodedObject(plumbing.TreeObject, entry.Hash) - if err != nil { - return nil, err - } - - tree := &Tree{s: t.s} - err = tree.Decode(obj) - - return tree, err -} - -func (t *Tree) entry(baseName string) (*TreeEntry, error) { - if t.m == nil { - t.buildMap() - } - - entry, ok := t.m[baseName] - if !ok { - return nil, ErrEntryNotFound - } - - return entry, nil -} - -// Files returns a FileIter allowing to iterate over the Tree -func (t *Tree) Files() *FileIter { - return NewFileIter(t.s, t) -} - -// ID returns the object ID of the tree. The returned value will always match -// the current value of Tree.Hash. -// -// ID is present to fulfill the Object interface. -func (t *Tree) ID() plumbing.Hash { - return t.Hash -} - -// Type returns the type of object. It always returns plumbing.TreeObject. -func (t *Tree) Type() plumbing.ObjectType { - return plumbing.TreeObject -} - -// Decode transform an plumbing.EncodedObject into a Tree struct -func (t *Tree) Decode(o plumbing.EncodedObject) (err error) { - if o.Type() != plumbing.TreeObject { - return ErrUnsupportedObject - } - - t.Hash = o.Hash() - if o.Size() == 0 { - return nil - } - - t.Entries = nil - t.m = nil - - reader, err := o.Reader() - if err != nil { - return err - } - defer ioutil.CheckClose(reader, &err) - - r := sync.GetBufioReader(reader) - defer sync.PutBufioReader(r) - - for { - str, err := r.ReadString(' ') - if err != nil { - if err == io.EOF { - break - } - - return err - } - str = str[:len(str)-1] // strip last byte (' ') - - mode, err := filemode.New(str) - if err != nil { - return err - } - - name, err := r.ReadString(0) - if err != nil && err != io.EOF { - return err - } - - var hash plumbing.Hash - if _, err = io.ReadFull(r, hash[:]); err != nil { - return err - } - - baseName := name[:len(name)-1] - t.Entries = append(t.Entries, TreeEntry{ - Hash: hash, - Mode: mode, - Name: baseName, - }) - } - - return nil -} - -type TreeEntrySorter []TreeEntry - -func (s TreeEntrySorter) Len() int { - return len(s) -} - -func (s TreeEntrySorter) Less(i, j int) bool { - name1 := s[i].Name - name2 := s[j].Name - if s[i].Mode == filemode.Dir { - name1 += "/" - } - if s[j].Mode == filemode.Dir { - name2 += "/" - } - return name1 < name2 -} - -func (s TreeEntrySorter) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -// Encode transforms a Tree into a plumbing.EncodedObject. -// The tree entries must be sorted by name. -func (t *Tree) Encode(o plumbing.EncodedObject) (err error) { - o.SetType(plumbing.TreeObject) - w, err := o.Writer() - if err != nil { - return err - } - - defer ioutil.CheckClose(w, &err) - - if !sort.IsSorted(TreeEntrySorter(t.Entries)) { - return ErrEntriesNotSorted - } - - for _, entry := range t.Entries { - if strings.IndexByte(entry.Name, 0) != -1 { - return fmt.Errorf("malformed filename %q", entry.Name) - } - if _, err = fmt.Fprintf(w, "%o %s", entry.Mode, entry.Name); err != nil { - return err - } - - if _, err = w.Write([]byte{0x00}); err != nil { - return err - } - - if _, err = w.Write(entry.Hash[:]); err != nil { - return err - } - } - - return err -} - -func (t *Tree) buildMap() { - t.m = make(map[string]*TreeEntry) - for i := 0; i < len(t.Entries); i++ { - t.m[t.Entries[i].Name] = &t.Entries[i] - } -} - -// Diff returns a list of changes between this tree and the provided one -func (t *Tree) Diff(to *Tree) (Changes, error) { - return t.DiffContext(context.Background(), to) -} - -// DiffContext returns a list of changes between this tree and the provided one -// Error will be returned if context expires. Provided context must be non nil. -// -// NOTE: Since version 5.1.0 the renames are correctly handled, the settings -// used are the recommended options DefaultDiffTreeOptions. -func (t *Tree) DiffContext(ctx context.Context, to *Tree) (Changes, error) { - return DiffTreeWithOptions(ctx, t, to, DefaultDiffTreeOptions) -} - -// Patch returns a slice of Patch objects with all the changes between trees -// in chunks. This representation can be used to create several diff outputs. -func (t *Tree) Patch(to *Tree) (*Patch, error) { - return t.PatchContext(context.Background(), to) -} - -// PatchContext returns a slice of Patch objects with all the changes between -// trees in chunks. This representation can be used to create several diff -// outputs. If context expires, an error will be returned. Provided context must -// be non-nil. -// -// NOTE: Since version 5.1.0 the renames are correctly handled, the settings -// used are the recommended options DefaultDiffTreeOptions. -func (t *Tree) PatchContext(ctx context.Context, to *Tree) (*Patch, error) { - changes, err := t.DiffContext(ctx, to) - if err != nil { - return nil, err - } - - return changes.PatchContext(ctx) -} - -// treeEntryIter facilitates iterating through the TreeEntry objects in a Tree. -type treeEntryIter struct { - t *Tree - pos int -} - -func (iter *treeEntryIter) Next() (TreeEntry, error) { - if iter.pos >= len(iter.t.Entries) { - return TreeEntry{}, io.EOF - } - iter.pos++ - return iter.t.Entries[iter.pos-1], nil -} - -// TreeWalker provides a means of walking through all of the entries in a Tree. -type TreeWalker struct { - stack []*treeEntryIter - base string - recursive bool - seen map[plumbing.Hash]bool - - s storer.EncodedObjectStorer - t *Tree -} - -// NewTreeWalker returns a new TreeWalker for the given tree. -// -// It is the caller's responsibility to call Close() when finished with the -// tree walker. -func NewTreeWalker(t *Tree, recursive bool, seen map[plumbing.Hash]bool) *TreeWalker { - stack := make([]*treeEntryIter, 0, startingStackSize) - stack = append(stack, &treeEntryIter{t, 0}) - - return &TreeWalker{ - stack: stack, - recursive: recursive, - seen: seen, - - s: t.s, - t: t, - } -} - -// Next returns the next object from the tree. Objects are returned in order -// and subtrees are included. After the last object has been returned further -// calls to Next() will return io.EOF. -// -// In the current implementation any objects which cannot be found in the -// underlying repository will be skipped automatically. It is possible that this -// may change in future versions. -func (w *TreeWalker) Next() (name string, entry TreeEntry, err error) { - var obj *Tree - for { - current := len(w.stack) - 1 - if current < 0 { - // Nothing left on the stack so we're finished - err = io.EOF - return - } - - if current > maxTreeDepth { - // We're probably following bad data or some self-referencing tree - err = ErrMaxTreeDepth - return - } - - entry, err = w.stack[current].Next() - if err == io.EOF { - // Finished with the current tree, move back up to the parent - w.stack = w.stack[:current] - w.base, _ = path.Split(w.base) - w.base = strings.TrimSuffix(w.base, "/") - continue - } - - if err != nil { - return - } - - if w.seen[entry.Hash] { - continue - } - - if entry.Mode == filemode.Dir { - obj, err = GetTree(w.s, entry.Hash) - } - - name = simpleJoin(w.base, entry.Name) - - if err != nil { - err = io.EOF - return - } - - break - } - - if !w.recursive { - return - } - - if obj != nil { - w.stack = append(w.stack, &treeEntryIter{obj, 0}) - w.base = simpleJoin(w.base, entry.Name) - } - - return -} - -// Tree returns the tree that the tree walker most recently operated on. -func (w *TreeWalker) Tree() *Tree { - current := len(w.stack) - 1 - if w.stack[current].pos == 0 { - current-- - } - - if current < 0 { - return nil - } - - return w.stack[current].t -} - -// Close releases any resources used by the TreeWalker. -func (w *TreeWalker) Close() { - w.stack = nil -} - -// TreeIter provides an iterator for a set of trees. -type TreeIter struct { - storer.EncodedObjectIter - s storer.EncodedObjectStorer -} - -// NewTreeIter takes a storer.EncodedObjectStorer and a -// storer.EncodedObjectIter and returns a *TreeIter that iterates over all -// tree contained in the storer.EncodedObjectIter. -// -// Any non-tree object returned by the storer.EncodedObjectIter is skipped. -func NewTreeIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) *TreeIter { - return &TreeIter{iter, s} -} - -// Next moves the iterator to the next tree and returns a pointer to it. If -// there are no more trees, it returns io.EOF. -func (iter *TreeIter) Next() (*Tree, error) { - for { - obj, err := iter.EncodedObjectIter.Next() - if err != nil { - return nil, err - } - - if obj.Type() != plumbing.TreeObject { - continue - } - - return DecodeTree(iter.s, obj) - } -} - -// ForEach call the cb function for each tree contained on this iter until -// an error happens or the end of the iter is reached. If ErrStop is sent -// the iteration is stop but no error is returned. The iterator is closed. -func (iter *TreeIter) ForEach(cb func(*Tree) error) error { - return iter.EncodedObjectIter.ForEach(func(obj plumbing.EncodedObject) error { - if obj.Type() != plumbing.TreeObject { - return nil - } - - t, err := DecodeTree(iter.s, obj) - if err != nil { - return err - } - - return cb(t) - }) -} - -func simpleJoin(parent, child string) string { - if len(parent) > 0 { - return parent + "/" + child - } - return child -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/treenoder.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/treenoder.go deleted file mode 100644 index ae281b0f07a..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/object/treenoder.go +++ /dev/null @@ -1,142 +0,0 @@ -package object - -import ( - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" -) - -// A treenoder is a helper type that wraps git trees into merkletrie -// noders. -// -// As a merkletrie noder doesn't understand the concept of modes (e.g. -// file permissions), the treenoder includes the mode of the git tree in -// the hash, so changes in the modes will be detected as modifications -// to the file contents by the merkletrie difftree algorithm. This is -// consistent with how the "git diff-tree" command works. -type treeNoder struct { - parent *Tree // the root node is its own parent - name string // empty string for the root node - mode filemode.FileMode - hash plumbing.Hash - children []noder.Noder // memoized -} - -// NewTreeRootNode returns the root node of a Tree -func NewTreeRootNode(t *Tree) noder.Noder { - if t == nil { - return &treeNoder{} - } - - return &treeNoder{ - parent: t, - name: "", - mode: filemode.Dir, - hash: t.Hash, - } -} - -func (t *treeNoder) Skip() bool { - return false -} - -func (t *treeNoder) isRoot() bool { - return t.name == "" -} - -func (t *treeNoder) String() string { - return "treeNoder <" + t.name + ">" -} - -func (t *treeNoder) Hash() []byte { - if t.mode == filemode.Deprecated { - return append(t.hash[:], filemode.Regular.Bytes()...) - } - return append(t.hash[:], t.mode.Bytes()...) -} - -func (t *treeNoder) Name() string { - return t.name -} - -func (t *treeNoder) IsDir() bool { - return t.mode == filemode.Dir -} - -// Children will return the children of a treenoder as treenoders, -// building them from the children of the wrapped git tree. -func (t *treeNoder) Children() ([]noder.Noder, error) { - if t.mode != filemode.Dir { - return noder.NoChildren, nil - } - - // children are memoized for efficiency - if t.children != nil { - return t.children, nil - } - - // the parent of the returned children will be ourself as a tree if - // we are a not the root treenoder. The root is special as it - // is is own parent. - parent := t.parent - if !t.isRoot() { - var err error - if parent, err = t.parent.Tree(t.name); err != nil { - return nil, err - } - } - - var err error - t.children, err = transformChildren(parent) - return t.children, err -} - -// Returns the children of a tree as treenoders. -// Efficiency is key here. -func transformChildren(t *Tree) ([]noder.Noder, error) { - var err error - var e TreeEntry - - // there will be more tree entries than children in the tree, - // due to submodules and empty directories, but I think it is still - // worth it to pre-allocate the whole array now, even if sometimes - // is bigger than needed. - ret := make([]noder.Noder, 0, len(t.Entries)) - - walker := NewTreeWalker(t, false, nil) // don't recurse - // don't defer walker.Close() for efficiency reasons. - for { - _, e, err = walker.Next() - if err == io.EOF { - break - } - if err != nil { - walker.Close() - return nil, err - } - - ret = append(ret, &treeNoder{ - parent: t, - name: e.Name, - mode: e.Mode, - hash: e.Hash, - }) - } - walker.Close() - - return ret, nil -} - -// len(t.tree.Entries) != the number of elements walked by treewalker -// for some reason because of empty directories, submodules, etc, so we -// have to walk here. -func (t *treeNoder) NumChildren() (int, error) { - children, err := t.Children() - if err != nil { - return 0, err - } - - return len(children), nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/advrefs.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/advrefs.go deleted file mode 100644 index 7bc053dc54e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/advrefs.go +++ /dev/null @@ -1,211 +0,0 @@ -package packp - -import ( - "fmt" - "sort" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/storage/memory" -) - -// AdvRefs values represent the information transmitted on an -// advertised-refs message. Values from this type are not zero-value -// safe, use the New function instead. -type AdvRefs struct { - // Prefix stores prefix payloads. - // - // When using this message over (smart) HTTP, you have to add a pktline - // before the whole thing with the following payload: - // - // '# service=$servicename" LF - // - // Moreover, some (all) git HTTP smart servers will send a flush-pkt - // just after the first pkt-line. - // - // To accommodate both situations, the Prefix field allow you to store - // any data you want to send before the actual pktlines. It will also - // be filled up with whatever is found on the line. - Prefix [][]byte - // Head stores the resolved HEAD reference if present. - // This can be present with git-upload-pack, not with git-receive-pack. - Head *plumbing.Hash - // Capabilities are the capabilities. - Capabilities *capability.List - // References are the hash references. - References map[string]plumbing.Hash - // Peeled are the peeled hash references. - Peeled map[string]plumbing.Hash - // Shallows are the shallow object ids. - Shallows []plumbing.Hash -} - -// NewAdvRefs returns a pointer to a new AdvRefs value, ready to be used. -func NewAdvRefs() *AdvRefs { - return &AdvRefs{ - Prefix: [][]byte{}, - Capabilities: capability.NewList(), - References: make(map[string]plumbing.Hash), - Peeled: make(map[string]plumbing.Hash), - Shallows: []plumbing.Hash{}, - } -} - -func (a *AdvRefs) AddReference(r *plumbing.Reference) error { - switch r.Type() { - case plumbing.SymbolicReference: - v := fmt.Sprintf("%s:%s", r.Name().String(), r.Target().String()) - return a.Capabilities.Add(capability.SymRef, v) - case plumbing.HashReference: - a.References[r.Name().String()] = r.Hash() - default: - return plumbing.ErrInvalidType - } - - return nil -} - -func (a *AdvRefs) AllReferences() (memory.ReferenceStorage, error) { - s := memory.ReferenceStorage{} - if err := a.addRefs(s); err != nil { - return s, plumbing.NewUnexpectedError(err) - } - - return s, nil -} - -func (a *AdvRefs) addRefs(s storer.ReferenceStorer) error { - for name, hash := range a.References { - ref := plumbing.NewReferenceFromStrings(name, hash.String()) - if err := s.SetReference(ref); err != nil { - return err - } - } - - if a.supportSymrefs() { - return a.addSymbolicRefs(s) - } - - return a.resolveHead(s) -} - -// If the server does not support symrefs capability, -// we need to guess the reference where HEAD is pointing to. -// -// Git versions prior to 1.8.4.3 has an special procedure to get -// the reference where is pointing to HEAD: -// - Check if a reference called master exists. If exists and it -// has the same hash as HEAD hash, we can say that HEAD is pointing to master -// - If master does not exists or does not have the same hash as HEAD, -// order references and check in that order if that reference has the same -// hash than HEAD. If yes, set HEAD pointing to that branch hash -// - If no reference is found, throw an error -func (a *AdvRefs) resolveHead(s storer.ReferenceStorer) error { - if a.Head == nil { - return nil - } - - ref, err := s.Reference(plumbing.Master) - - // check first if HEAD is pointing to master - if err == nil { - ok, err := a.createHeadIfCorrectReference(ref, s) - if err != nil { - return err - } - - if ok { - return nil - } - } - - if err != nil && err != plumbing.ErrReferenceNotFound { - return err - } - - // From here we are trying to guess the branch that HEAD is pointing - refIter, err := s.IterReferences() - if err != nil { - return err - } - - var refNames []string - err = refIter.ForEach(func(r *plumbing.Reference) error { - refNames = append(refNames, string(r.Name())) - return nil - }) - if err != nil { - return err - } - - sort.Strings(refNames) - - var headSet bool - for _, refName := range refNames { - ref, err := s.Reference(plumbing.ReferenceName(refName)) - if err != nil { - return err - } - ok, err := a.createHeadIfCorrectReference(ref, s) - if err != nil { - return err - } - if ok { - headSet = true - break - } - } - - if !headSet { - return plumbing.ErrReferenceNotFound - } - - return nil -} - -func (a *AdvRefs) createHeadIfCorrectReference( - reference *plumbing.Reference, - s storer.ReferenceStorer) (bool, error) { - if reference.Hash() == *a.Head { - headRef := plumbing.NewSymbolicReference(plumbing.HEAD, reference.Name()) - if err := s.SetReference(headRef); err != nil { - return false, err - } - - return true, nil - } - - return false, nil -} - -func (a *AdvRefs) addSymbolicRefs(s storer.ReferenceStorer) error { - for _, symref := range a.Capabilities.Get(capability.SymRef) { - chunks := strings.Split(symref, ":") - if len(chunks) != 2 { - err := fmt.Errorf("bad number of `:` in symref value (%q)", symref) - return plumbing.NewUnexpectedError(err) - } - name := plumbing.ReferenceName(chunks[0]) - target := plumbing.ReferenceName(chunks[1]) - ref := plumbing.NewSymbolicReference(name, target) - if err := s.SetReference(ref); err != nil { - return nil - } - } - - return nil -} - -func (a *AdvRefs) supportSymrefs() bool { - return a.Capabilities.Supports(capability.SymRef) -} - -// IsEmpty returns true if doesn't contain any reference. -func (a *AdvRefs) IsEmpty() bool { - return a.Head == nil && - len(a.References) == 0 && - len(a.Peeled) == 0 && - len(a.Shallows) == 0 -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/advrefs_decode.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/advrefs_decode.go deleted file mode 100644 index d596547f515..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/advrefs_decode.go +++ /dev/null @@ -1,289 +0,0 @@ -package packp - -import ( - "bytes" - "encoding/hex" - "errors" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" -) - -// Decode reads the next advertised-refs message form its input and -// stores it in the AdvRefs. -func (a *AdvRefs) Decode(r io.Reader) error { - d := newAdvRefsDecoder(r) - return d.Decode(a) -} - -type advRefsDecoder struct { - s *pktline.Scanner // a pkt-line scanner from the input stream - line []byte // current pkt-line contents, use parser.nextLine() to make it advance - nLine int // current pkt-line number for debugging, begins at 1 - hash plumbing.Hash // last hash read - err error // sticky error, use the parser.error() method to fill this out - data *AdvRefs // parsed data is stored here -} - -var ( - // ErrEmptyAdvRefs is returned by Decode if it gets an empty advertised - // references message. - ErrEmptyAdvRefs = errors.New("empty advertised-ref message") - // ErrEmptyInput is returned by Decode if the input is empty. - ErrEmptyInput = errors.New("empty input") -) - -func newAdvRefsDecoder(r io.Reader) *advRefsDecoder { - return &advRefsDecoder{ - s: pktline.NewScanner(r), - } -} - -func (d *advRefsDecoder) Decode(v *AdvRefs) error { - d.data = v - - for state := decodePrefix; state != nil; { - state = state(d) - } - - return d.err -} - -type decoderStateFn func(*advRefsDecoder) decoderStateFn - -// fills out the parser sticky error -func (d *advRefsDecoder) error(format string, a ...interface{}) { - msg := fmt.Sprintf( - "pkt-line %d: %s", d.nLine, - fmt.Sprintf(format, a...), - ) - - d.err = NewErrUnexpectedData(msg, d.line) -} - -// Reads a new pkt-line from the scanner, makes its payload available as -// p.line and increments p.nLine. A successful invocation returns true, -// otherwise, false is returned and the sticky error is filled out -// accordingly. Trims eols at the end of the payloads. -func (d *advRefsDecoder) nextLine() bool { - d.nLine++ - - if !d.s.Scan() { - if d.err = d.s.Err(); d.err != nil { - return false - } - - if d.nLine == 1 { - d.err = ErrEmptyInput - return false - } - - d.error("EOF") - return false - } - - d.line = d.s.Bytes() - d.line = bytes.TrimSuffix(d.line, eol) - - return true -} - -// The HTTP smart prefix is often followed by a flush-pkt. -func decodePrefix(d *advRefsDecoder) decoderStateFn { - if ok := d.nextLine(); !ok { - return nil - } - - if !isPrefix(d.line) { - return decodeFirstHash - } - - tmp := make([]byte, len(d.line)) - copy(tmp, d.line) - d.data.Prefix = append(d.data.Prefix, tmp) - if ok := d.nextLine(); !ok { - return nil - } - - if !isFlush(d.line) { - return decodeFirstHash - } - - d.data.Prefix = append(d.data.Prefix, pktline.Flush) - if ok := d.nextLine(); !ok { - return nil - } - - return decodeFirstHash -} - -func isPrefix(payload []byte) bool { - return len(payload) > 0 && payload[0] == '#' -} - -// If the first hash is zero, then a no-refs is coming. Otherwise, a -// list-of-refs is coming, and the hash will be followed by the first -// advertised ref. -func decodeFirstHash(p *advRefsDecoder) decoderStateFn { - // If the repository is empty, we receive a flush here (HTTP). - if isFlush(p.line) { - p.err = ErrEmptyAdvRefs - return nil - } - - // TODO: Use object-format (when available) for hash size. Git 2.41+ - if len(p.line) < hashSize { - p.error("cannot read hash, pkt-line too short") - return nil - } - - if _, err := hex.Decode(p.hash[:], p.line[:hashSize]); err != nil { - p.error("invalid hash text: %s", err) - return nil - } - - p.line = p.line[hashSize:] - - if p.hash.IsZero() { - return decodeSkipNoRefs - } - - return decodeFirstRef -} - -// Skips SP "capabilities^{}" NUL -func decodeSkipNoRefs(p *advRefsDecoder) decoderStateFn { - if len(p.line) < len(noHeadMark) { - p.error("too short zero-id ref") - return nil - } - - if !bytes.HasPrefix(p.line, noHeadMark) { - p.error("malformed zero-id ref") - return nil - } - - p.line = p.line[len(noHeadMark):] - - return decodeCaps -} - -// decode the refname, expects SP refname NULL -func decodeFirstRef(l *advRefsDecoder) decoderStateFn { - if len(l.line) < 3 { - l.error("line too short after hash") - return nil - } - - if !bytes.HasPrefix(l.line, sp) { - l.error("no space after hash") - return nil - } - l.line = l.line[1:] - - chunks := bytes.SplitN(l.line, null, 2) - if len(chunks) < 2 { - l.error("NULL not found") - return nil - } - ref := chunks[0] - l.line = chunks[1] - - if bytes.Equal(ref, []byte(head)) { - l.data.Head = &l.hash - } else { - l.data.References[string(ref)] = l.hash - } - - return decodeCaps -} - -func decodeCaps(p *advRefsDecoder) decoderStateFn { - if err := p.data.Capabilities.Decode(p.line); err != nil { - p.error("invalid capabilities: %s", err) - return nil - } - - return decodeOtherRefs -} - -// The refs are either tips (obj-id SP refname) or a peeled (obj-id SP refname^{}). -// If there are no refs, then there might be a shallow or flush-ptk. -func decodeOtherRefs(p *advRefsDecoder) decoderStateFn { - if ok := p.nextLine(); !ok { - return nil - } - - if bytes.HasPrefix(p.line, shallow) { - return decodeShallow - } - - if len(p.line) == 0 { - return nil - } - - saveTo := p.data.References - if bytes.HasSuffix(p.line, peeled) { - p.line = bytes.TrimSuffix(p.line, peeled) - saveTo = p.data.Peeled - } - - ref, hash, err := readRef(p.line) - if err != nil { - p.error("%s", err) - return nil - } - saveTo[ref] = hash - - return decodeOtherRefs -} - -// Reads a ref-name -func readRef(data []byte) (string, plumbing.Hash, error) { - chunks := bytes.Split(data, sp) - switch { - case len(chunks) == 1: - return "", plumbing.ZeroHash, fmt.Errorf("malformed ref data: no space was found") - case len(chunks) > 2: - return "", plumbing.ZeroHash, fmt.Errorf("malformed ref data: more than one space found") - default: - return string(chunks[1]), plumbing.NewHash(string(chunks[0])), nil - } -} - -// Keeps reading shallows until a flush-pkt is found -func decodeShallow(p *advRefsDecoder) decoderStateFn { - if !bytes.HasPrefix(p.line, shallow) { - p.error("malformed shallow prefix, found %q... instead", p.line[:len(shallow)]) - return nil - } - p.line = bytes.TrimPrefix(p.line, shallow) - - if len(p.line) != hashSize { - p.error(fmt.Sprintf( - "malformed shallow hash: wrong length, expected 40 bytes, read %d bytes", - len(p.line))) - return nil - } - - text := p.line[:hashSize] - var h plumbing.Hash - if _, err := hex.Decode(h[:], text); err != nil { - p.error("invalid hash text: %s", err) - return nil - } - - p.data.Shallows = append(p.data.Shallows, h) - - if ok := p.nextLine(); !ok { - return nil - } - - if len(p.line) == 0 { - return nil // successful parse of the advertised-refs message - } - - return decodeShallow -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/advrefs_encode.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/advrefs_encode.go deleted file mode 100644 index 9de6f8e0541..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/advrefs_encode.go +++ /dev/null @@ -1,176 +0,0 @@ -package packp - -import ( - "bytes" - "fmt" - "io" - "sort" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" -) - -// Encode writes the AdvRefs encoding to a writer. -// -// All the payloads will end with a newline character. Capabilities, -// references and shallows are written in alphabetical order, except for -// peeled references that always follow their corresponding references. -func (a *AdvRefs) Encode(w io.Writer) error { - e := newAdvRefsEncoder(w) - return e.Encode(a) -} - -type advRefsEncoder struct { - data *AdvRefs // data to encode - pe *pktline.Encoder // where to write the encoded data - firstRefName string // reference name to encode in the first pkt-line (HEAD if present) - firstRefHash plumbing.Hash // hash referenced to encode in the first pkt-line (HEAD if present) - sortedRefs []string // hash references to encode ordered by increasing order - err error // sticky error - -} - -func newAdvRefsEncoder(w io.Writer) *advRefsEncoder { - return &advRefsEncoder{ - pe: pktline.NewEncoder(w), - } -} - -func (e *advRefsEncoder) Encode(v *AdvRefs) error { - e.data = v - e.sortRefs() - e.setFirstRef() - - for state := encodePrefix; state != nil; { - state = state(e) - } - - return e.err -} - -func (e *advRefsEncoder) sortRefs() { - if len(e.data.References) > 0 { - refs := make([]string, 0, len(e.data.References)) - for refName := range e.data.References { - refs = append(refs, refName) - } - - sort.Strings(refs) - e.sortedRefs = refs - } -} - -func (e *advRefsEncoder) setFirstRef() { - if e.data.Head != nil { - e.firstRefName = head - e.firstRefHash = *e.data.Head - return - } - - if len(e.sortedRefs) > 0 { - refName := e.sortedRefs[0] - e.firstRefName = refName - e.firstRefHash = e.data.References[refName] - } -} - -type encoderStateFn func(*advRefsEncoder) encoderStateFn - -func encodePrefix(e *advRefsEncoder) encoderStateFn { - for _, p := range e.data.Prefix { - if bytes.Equal(p, pktline.Flush) { - if e.err = e.pe.Flush(); e.err != nil { - return nil - } - continue - } - if e.err = e.pe.Encodef("%s\n", string(p)); e.err != nil { - return nil - } - } - - return encodeFirstLine -} - -// Adds the first pkt-line payload: head hash, head ref and capabilities. -// If HEAD ref is not found, the first reference ordered in increasing order will be used. -// If there aren't HEAD neither refs, the first line will be "PKT-LINE(zero-id SP "capabilities^{}" NUL capability-list)". -// See: https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt -// See: https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt -func encodeFirstLine(e *advRefsEncoder) encoderStateFn { - const formatFirstLine = "%s %s\x00%s\n" - var firstLine string - capabilities := formatCaps(e.data.Capabilities) - - if e.firstRefName == "" { - firstLine = fmt.Sprintf(formatFirstLine, plumbing.ZeroHash.String(), "capabilities^{}", capabilities) - } else { - firstLine = fmt.Sprintf(formatFirstLine, e.firstRefHash.String(), e.firstRefName, capabilities) - - } - - if e.err = e.pe.EncodeString(firstLine); e.err != nil { - return nil - } - - return encodeRefs -} - -func formatCaps(c *capability.List) string { - if c == nil { - return "" - } - - return c.String() -} - -// Adds the (sorted) refs: hash SP refname EOL -// and their peeled refs if any. -func encodeRefs(e *advRefsEncoder) encoderStateFn { - for _, r := range e.sortedRefs { - if r == e.firstRefName { - continue - } - - hash := e.data.References[r] - if e.err = e.pe.Encodef("%s %s\n", hash.String(), r); e.err != nil { - return nil - } - - if hash, ok := e.data.Peeled[r]; ok { - if e.err = e.pe.Encodef("%s %s^{}\n", hash.String(), r); e.err != nil { - return nil - } - } - } - - return encodeShallow -} - -// Adds the (sorted) shallows: "shallow" SP hash EOL -func encodeShallow(e *advRefsEncoder) encoderStateFn { - sorted := sortShallows(e.data.Shallows) - for _, hash := range sorted { - if e.err = e.pe.Encodef("shallow %s\n", hash); e.err != nil { - return nil - } - } - - return encodeFlush -} - -func sortShallows(c []plumbing.Hash) []string { - ret := []string{} - for _, h := range c { - ret = append(ret, h.String()) - } - sort.Strings(ret) - - return ret -} - -func encodeFlush(e *advRefsEncoder) encoderStateFn { - e.err = e.pe.Flush() - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability/capability.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability/capability.go deleted file mode 100644 index b52e8a49d51..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability/capability.go +++ /dev/null @@ -1,272 +0,0 @@ -// Package capability defines the server and client capabilities. -package capability - -import ( - "fmt" - "os" -) - -// Capability describes a server or client capability. -type Capability string - -func (n Capability) String() string { - return string(n) -} - -const ( - // MultiACK capability allows the server to return "ACK obj-id continue" as - // soon as it finds a commit that it can use as a common base, between the - // client's wants and the client's have set. - // - // By sending this early, the server can potentially head off the client - // from walking any further down that particular branch of the client's - // repository history. The client may still need to walk down other - // branches, sending have lines for those, until the server has a - // complete cut across the DAG, or the client has said "done". - // - // Without multi_ack, a client sends have lines in --date-order until - // the server has found a common base. That means the client will send - // have lines that are already known by the server to be common, because - // they overlap in time with another branch that the server hasn't found - // a common base on yet. - // - // For example suppose the client has commits in caps that the server - // doesn't and the server has commits in lower case that the client - // doesn't, as in the following diagram: - // - // +---- u ---------------------- x - // / +----- y - // / / - // a -- b -- c -- d -- E -- F - // \ - // +--- Q -- R -- S - // - // If the client wants x,y and starts out by saying have F,S, the server - // doesn't know what F,S is. Eventually the client says "have d" and - // the server sends "ACK d continue" to let the client know to stop - // walking down that line (so don't send c-b-a), but it's not done yet, - // it needs a base for x. The client keeps going with S-R-Q, until a - // gets reached, at which point the server has a clear base and it all - // ends. - // - // Without multi_ack the client would have sent that c-b-a chain anyway, - // interleaved with S-R-Q. - MultiACK Capability = "multi_ack" - // MultiACKDetailed is an extension of multi_ack that permits client to - // better understand the server's in-memory state. - MultiACKDetailed Capability = "multi_ack_detailed" - // NoDone should only be used with the smart HTTP protocol. If - // multi_ack_detailed and no-done are both present, then the sender is - // free to immediately send a pack following its first "ACK obj-id ready" - // message. - // - // Without no-done in the smart HTTP protocol, the server session would - // end and the client has to make another trip to send "done" before - // the server can send the pack. no-done removes the last round and - // thus slightly reduces latency. - NoDone Capability = "no-done" - // ThinPack is one with deltas which reference base objects not - // contained within the pack (but are known to exist at the receiving - // end). This can reduce the network traffic significantly, but it - // requires the receiving end to know how to "thicken" these packs by - // adding the missing bases to the pack. - // - // The upload-pack server advertises 'thin-pack' when it can generate - // and send a thin pack. A client requests the 'thin-pack' capability - // when it understands how to "thicken" it, notifying the server that - // it can receive such a pack. A client MUST NOT request the - // 'thin-pack' capability if it cannot turn a thin pack into a - // self-contained pack. - // - // Receive-pack, on the other hand, is assumed by default to be able to - // handle thin packs, but can ask the client not to use the feature by - // advertising the 'no-thin' capability. A client MUST NOT send a thin - // pack if the server advertises the 'no-thin' capability. - // - // The reasons for this asymmetry are historical. The receive-pack - // program did not exist until after the invention of thin packs, so - // historically the reference implementation of receive-pack always - // understood thin packs. Adding 'no-thin' later allowed receive-pack - // to disable the feature in a backwards-compatible manner. - ThinPack Capability = "thin-pack" - // Sideband means that server can send, and client understand multiplexed - // progress reports and error info interleaved with the packfile itself. - // - // These two options are mutually exclusive. A modern client always - // favors Sideband64k. - // - // Either mode indicates that the packfile data will be streamed broken - // up into packets of up to either 1000 bytes in the case of 'side_band', - // or 65520 bytes in the case of 'side_band_64k'. Each packet is made up - // of a leading 4-byte pkt-line length of how much data is in the packet, - // followed by a 1-byte stream code, followed by the actual data. - // - // The stream code can be one of: - // - // 1 - pack data - // 2 - progress messages - // 3 - fatal error message just before stream aborts - // - // The "side-band-64k" capability came about as a way for newer clients - // that can handle much larger packets to request packets that are - // actually crammed nearly full, while maintaining backward compatibility - // for the older clients. - // - // Further, with side-band and its up to 1000-byte messages, it's actually - // 999 bytes of payload and 1 byte for the stream code. With side-band-64k, - // same deal, you have up to 65519 bytes of data and 1 byte for the stream - // code. - // - // The client MUST send only maximum of one of "side-band" and "side- - // band-64k". Server MUST diagnose it as an error if client requests - // both. - Sideband Capability = "side-band" - Sideband64k Capability = "side-band-64k" - // OFSDelta server can send, and client understand PACKv2 with delta - // referring to its base by position in pack rather than by an obj-id. That - // is, they can send/read OBJ_OFS_DELTA (aka type 6) in a packfile. - OFSDelta Capability = "ofs-delta" - // Agent the server may optionally send this capability to notify the client - // that the server is running version `X`. The client may optionally return - // its own agent string by responding with an `agent=Y` capability (but it - // MUST NOT do so if the server did not mention the agent capability). The - // `X` and `Y` strings may contain any printable ASCII characters except - // space (i.e., the byte range 32 < x < 127), and are typically of the form - // "package/version" (e.g., "git/1.8.3.1"). The agent strings are purely - // informative for statistics and debugging purposes, and MUST NOT be used - // to programmatically assume the presence or absence of particular features. - Agent Capability = "agent" - // Shallow capability adds "deepen", "shallow" and "unshallow" commands to - // the fetch-pack/upload-pack protocol so clients can request shallow - // clones. - Shallow Capability = "shallow" - // DeepenSince adds "deepen-since" command to fetch-pack/upload-pack - // protocol so the client can request shallow clones that are cut at a - // specific time, instead of depth. Internally it's equivalent of doing - // "rev-list --max-age=" on the server side. "deepen-since" - // cannot be used with "deepen". - DeepenSince Capability = "deepen-since" - // DeepenNot adds "deepen-not" command to fetch-pack/upload-pack - // protocol so the client can request shallow clones that are cut at a - // specific revision, instead of depth. Internally it's equivalent of - // doing "rev-list --not " on the server side. "deepen-not" - // cannot be used with "deepen", but can be used with "deepen-since". - DeepenNot Capability = "deepen-not" - // DeepenRelative if this capability is requested by the client, the - // semantics of "deepen" command is changed. The "depth" argument is the - // depth from the current shallow boundary, instead of the depth from - // remote refs. - DeepenRelative Capability = "deepen-relative" - // NoProgress the client was started with "git clone -q" or something, and - // doesn't want that side band 2. Basically the client just says "I do not - // wish to receive stream 2 on sideband, so do not send it to me, and if - // you did, I will drop it on the floor anyway". However, the sideband - // channel 3 is still used for error responses. - NoProgress Capability = "no-progress" - // IncludeTag capability is about sending annotated tags if we are - // sending objects they point to. If we pack an object to the client, and - // a tag object points exactly at that object, we pack the tag object too. - // In general this allows a client to get all new annotated tags when it - // fetches a branch, in a single network connection. - // - // Clients MAY always send include-tag, hardcoding it into a request when - // the server advertises this capability. The decision for a client to - // request include-tag only has to do with the client's desires for tag - // data, whether or not a server had advertised objects in the - // refs/tags/* namespace. - // - // Servers MUST pack the tags if their referrant is packed and the client - // has requested include-tags. - // - // Clients MUST be prepared for the case where a server has ignored - // include-tag and has not actually sent tags in the pack. In such - // cases the client SHOULD issue a subsequent fetch to acquire the tags - // that include-tag would have otherwise given the client. - // - // The server SHOULD send include-tag, if it supports it, regardless - // of whether or not there are tags available. - IncludeTag Capability = "include-tag" - // ReportStatus the receive-pack process can receive a 'report-status' - // capability, which tells it that the client wants a report of what - // happened after a packfile upload and reference update. If the pushing - // client requests this capability, after unpacking and updating references - // the server will respond with whether the packfile unpacked successfully - // and if each reference was updated successfully. If any of those were not - // successful, it will send back an error message. See pack-protocol.txt - // for example messages. - ReportStatus Capability = "report-status" - // DeleteRefs If the server sends back this capability, it means that - // it is capable of accepting a zero-id value as the target - // value of a reference update. It is not sent back by the client, it - // simply informs the client that it can be sent zero-id values - // to delete references - DeleteRefs Capability = "delete-refs" - // Quiet If the receive-pack server advertises this capability, it is - // capable of silencing human-readable progress output which otherwise may - // be shown when processing the received pack. A send-pack client should - // respond with the 'quiet' capability to suppress server-side progress - // reporting if the local progress reporting is also being suppressed - // (e.g., via `push -q`, or if stderr does not go to a tty). - Quiet Capability = "quiet" - // Atomic If the server sends this capability it is capable of accepting - // atomic pushes. If the pushing client requests this capability, the server - // will update the refs in one atomic transaction. Either all refs are - // updated or none. - Atomic Capability = "atomic" - // PushOptions If the server sends this capability it is able to accept - // push options after the update commands have been sent, but before the - // packfile is streamed. If the pushing client requests this capability, - // the server will pass the options to the pre- and post- receive hooks - // that process this push request. - PushOptions Capability = "push-options" - // AllowTipSHA1InWant if the upload-pack server advertises this capability, - // fetch-pack may send "want" lines with SHA-1s that exist at the server but - // are not advertised by upload-pack. - AllowTipSHA1InWant Capability = "allow-tip-sha1-in-want" - // AllowReachableSHA1InWant if the upload-pack server advertises this - // capability, fetch-pack may send "want" lines with SHA-1s that exist at - // the server but are not advertised by upload-pack. - AllowReachableSHA1InWant Capability = "allow-reachable-sha1-in-want" - // PushCert the receive-pack server that advertises this capability is - // willing to accept a signed push certificate, and asks the to be - // included in the push certificate. A send-pack client MUST NOT - // send a push-cert packet unless the receive-pack server advertises - // this capability. - PushCert Capability = "push-cert" - // SymRef symbolic reference support for better negotiation. - SymRef Capability = "symref" - // ObjectFormat takes a hash algorithm as an argument, indicates that the - // server supports the given hash algorithms. - ObjectFormat Capability = "object-format" - // Filter if present, fetch-pack may send "filter" commands to request a - // partial clone or partial fetch and request that the server omit various objects from the packfile - Filter Capability = "filter" -) - -const userAgent = "go-git/5.x" - -// DefaultAgent provides the user agent string. -func DefaultAgent() string { - if envUserAgent, ok := os.LookupEnv("GO_GIT_USER_AGENT_EXTRA"); ok { - return fmt.Sprintf("%s %s", userAgent, envUserAgent) - } - return userAgent -} - -var known = map[Capability]bool{ - MultiACK: true, MultiACKDetailed: true, NoDone: true, ThinPack: true, - Sideband: true, Sideband64k: true, OFSDelta: true, Agent: true, - Shallow: true, DeepenSince: true, DeepenNot: true, DeepenRelative: true, - NoProgress: true, IncludeTag: true, ReportStatus: true, DeleteRefs: true, - Quiet: true, Atomic: true, PushOptions: true, AllowTipSHA1InWant: true, - AllowReachableSHA1InWant: true, PushCert: true, SymRef: true, - ObjectFormat: true, Filter: true, -} - -var requiresArgument = map[Capability]bool{ - Agent: true, PushCert: true, SymRef: true, ObjectFormat: true, -} - -var multipleArgument = map[Capability]bool{ - SymRef: true, -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability/list.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability/list.go deleted file mode 100644 index 553d81cbe4d..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability/list.go +++ /dev/null @@ -1,195 +0,0 @@ -package capability - -import ( - "bytes" - "errors" - "fmt" - "strings" -) - -var ( - // ErrArgumentsRequired is returned if no arguments are giving with a - // capability that requires arguments - ErrArgumentsRequired = errors.New("arguments required") - // ErrArguments is returned if arguments are given with a capabilities that - // not supports arguments - ErrArguments = errors.New("arguments not allowed") - // ErrEmptyArgument is returned when an empty value is given - ErrEmptyArgument = errors.New("empty argument") - // ErrMultipleArguments multiple argument given to a capabilities that not - // support it - ErrMultipleArguments = errors.New("multiple arguments not allowed") -) - -// List represents a list of capabilities -type List struct { - m map[Capability]*entry - sort []string -} - -type entry struct { - Name Capability - Values []string -} - -// NewList returns a new List of capabilities -func NewList() *List { - return &List{ - m: make(map[Capability]*entry), - } -} - -// IsEmpty returns true if the List is empty -func (l *List) IsEmpty() bool { - return len(l.sort) == 0 -} - -// Decode decodes list of capabilities from raw into the list -func (l *List) Decode(raw []byte) error { - // git 1.x receive pack used to send a leading space on its - // git-receive-pack capabilities announcement. We just trim space to be - // tolerant to space changes in different versions. - raw = bytes.TrimSpace(raw) - - if len(raw) == 0 { - return nil - } - - for _, data := range bytes.Split(raw, []byte{' '}) { - pair := bytes.SplitN(data, []byte{'='}, 2) - - c := Capability(pair[0]) - if len(pair) == 1 { - if err := l.Add(c); err != nil { - return err - } - - continue - } - - if err := l.Add(c, string(pair[1])); err != nil { - return err - } - } - - return nil -} - -// Get returns the values for a capability -func (l *List) Get(capability Capability) []string { - if _, ok := l.m[capability]; !ok { - return nil - } - - return l.m[capability].Values -} - -// Set sets a capability removing the previous values -func (l *List) Set(capability Capability, values ...string) error { - if _, ok := l.m[capability]; ok { - l.m[capability].Values = l.m[capability].Values[:0] - } - return l.Add(capability, values...) -} - -// Add adds a capability, values are optional -func (l *List) Add(c Capability, values ...string) error { - if err := l.validate(c, values); err != nil { - return err - } - - if !l.Supports(c) { - l.m[c] = &entry{Name: c} - l.sort = append(l.sort, c.String()) - } - - if len(values) == 0 { - return nil - } - - if known[c] && !multipleArgument[c] && len(l.m[c].Values) > 0 { - return ErrMultipleArguments - } - - l.m[c].Values = append(l.m[c].Values, values...) - return nil -} - -func (l *List) validateNoEmptyArgs(values []string) error { - for _, v := range values { - if v == "" { - return ErrEmptyArgument - } - } - return nil -} - -func (l *List) validate(c Capability, values []string) error { - if !known[c] { - return l.validateNoEmptyArgs(values) - } - if requiresArgument[c] && len(values) == 0 { - return ErrArgumentsRequired - } - - if !requiresArgument[c] && len(values) != 0 { - return ErrArguments - } - - if !multipleArgument[c] && len(values) > 1 { - return ErrMultipleArguments - } - return l.validateNoEmptyArgs(values) -} - -// Supports returns true if capability is present -func (l *List) Supports(capability Capability) bool { - _, ok := l.m[capability] - return ok -} - -// Delete deletes a capability from the List -func (l *List) Delete(capability Capability) { - if !l.Supports(capability) { - return - } - - delete(l.m, capability) - for i, c := range l.sort { - if c != string(capability) { - continue - } - - l.sort = append(l.sort[:i], l.sort[i+1:]...) - return - } -} - -// All returns a slice with all defined capabilities. -func (l *List) All() []Capability { - var cs []Capability - for _, key := range l.sort { - cs = append(cs, Capability(key)) - } - - return cs -} - -// String generates the capabilities strings, the capabilities are sorted in -// insertion order -func (l *List) String() string { - var o []string - for _, key := range l.sort { - cap := l.m[Capability(key)] - if len(cap.Values) == 0 { - o = append(o, key) - continue - } - - for _, value := range cap.Values { - o = append(o, fmt.Sprintf("%s=%s", key, value)) - } - } - - return strings.Join(o, " ") -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/common.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/common.go deleted file mode 100644 index a858323e79b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/common.go +++ /dev/null @@ -1,74 +0,0 @@ -package packp - -import ( - "fmt" -) - -type stateFn func() stateFn - -const ( - // common - hashSize = 40 - - // advrefs - head = "HEAD" - noHead = "capabilities^{}" -) - -var ( - // common - sp = []byte(" ") - eol = []byte("\n") - - // advertised-refs - null = []byte("\x00") - peeled = []byte("^{}") - noHeadMark = []byte(" capabilities^{}\x00") - - // upload-request - want = []byte("want ") - shallow = []byte("shallow ") - deepen = []byte("deepen") - deepenCommits = []byte("deepen ") - deepenSince = []byte("deepen-since ") - deepenReference = []byte("deepen-not ") - - // shallow-update - unshallow = []byte("unshallow ") - - // server-response - ack = []byte("ACK") - nak = []byte("NAK") - - // updreq - shallowNoSp = []byte("shallow") -) - -func isFlush(payload []byte) bool { - return len(payload) == 0 -} - -var ( - // ErrNilWriter is returned when a nil writer is passed to the encoder. - ErrNilWriter = fmt.Errorf("nil writer") -) - -// ErrUnexpectedData represents an unexpected data decoding a message -type ErrUnexpectedData struct { - Msg string - Data []byte -} - -// NewErrUnexpectedData returns a new ErrUnexpectedData containing the data and -// the message given -func NewErrUnexpectedData(msg string, data []byte) error { - return &ErrUnexpectedData{Msg: msg, Data: data} -} - -func (err *ErrUnexpectedData) Error() string { - if len(err.Data) == 0 { - return err.Msg - } - - return fmt.Sprintf("%s (%s)", err.Msg, err.Data) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/doc.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/doc.go deleted file mode 100644 index 4950d1d6625..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/doc.go +++ /dev/null @@ -1,724 +0,0 @@ -package packp - -/* - -A nice way to trace the real data transmitted and received by git, use: - -GIT_TRACE_PACKET=true git ls-remote http://github.com/src-d/go-git -GIT_TRACE_PACKET=true git clone http://github.com/src-d/go-git - -Here follows a copy of the current protocol specification at the time of -this writing. - -(Please notice that most http git servers will add a flush-pkt after the -first pkt-line when using HTTP smart.) - - -Documentation Common to Pack and Http Protocols -=============================================== - -ABNF Notation -------------- - -ABNF notation as described by RFC 5234 is used within the protocol documents, -except the following replacement core rules are used: ----- - HEXDIG = DIGIT / "a" / "b" / "c" / "d" / "e" / "f" ----- - -We also define the following common rules: ----- - NUL = %x00 - zero-id = 40*"0" - obj-id = 40*(HEXDIGIT) - - refname = "HEAD" - refname /= "refs/" ----- - -A refname is a hierarchical octet string beginning with "refs/" and -not violating the 'git-check-ref-format' command's validation rules. -More specifically, they: - -. They can include slash `/` for hierarchical (directory) - grouping, but no slash-separated component can begin with a - dot `.`. - -. They must contain at least one `/`. This enforces the presence of a - category like `heads/`, `tags/` etc. but the actual names are not - restricted. - -. They cannot have two consecutive dots `..` anywhere. - -. They cannot have ASCII control characters (i.e. bytes whose - values are lower than \040, or \177 `DEL`), space, tilde `~`, - caret `^`, colon `:`, question-mark `?`, asterisk `*`, - or open bracket `[` anywhere. - -. They cannot end with a slash `/` or a dot `.`. - -. They cannot end with the sequence `.lock`. - -. They cannot contain a sequence `@{`. - -. They cannot contain a `\\`. - - -pkt-line Format ---------------- - -Much (but not all) of the payload is described around pkt-lines. - -A pkt-line is a variable length binary string. The first four bytes -of the line, the pkt-len, indicates the total length of the line, -in hexadecimal. The pkt-len includes the 4 bytes used to contain -the length's hexadecimal representation. - -A pkt-line MAY contain binary data, so implementors MUST ensure -pkt-line parsing/formatting routines are 8-bit clean. - -A non-binary line SHOULD BE terminated by an LF, which if present -MUST be included in the total length. Receivers MUST treat pkt-lines -with non-binary data the same whether or not they contain the trailing -LF (stripping the LF if present, and not complaining when it is -missing). - -The maximum length of a pkt-line's data component is 65516 bytes. -Implementations MUST NOT send pkt-line whose length exceeds 65520 -(65516 bytes of payload + 4 bytes of length data). - -Implementations SHOULD NOT send an empty pkt-line ("0004"). - -A pkt-line with a length field of 0 ("0000"), called a flush-pkt, -is a special case and MUST be handled differently than an empty -pkt-line ("0004"). - ----- - pkt-line = data-pkt / flush-pkt - - data-pkt = pkt-len pkt-payload - pkt-len = 4*(HEXDIG) - pkt-payload = (pkt-len - 4)*(OCTET) - - flush-pkt = "0000" ----- - -Examples (as C-style strings): - ----- - pkt-line actual value - --------------------------------- - "0006a\n" "a\n" - "0005a" "a" - "000bfoobar\n" "foobar\n" - "0004" "" ----- - -Packfile transfer protocols -=========================== - -Git supports transferring data in packfiles over the ssh://, git://, http:// and -file:// transports. There exist two sets of protocols, one for pushing -data from a client to a server and another for fetching data from a -server to a client. The three transports (ssh, git, file) use the same -protocol to transfer data. http is documented in http-protocol.txt. - -The processes invoked in the canonical Git implementation are 'upload-pack' -on the server side and 'fetch-pack' on the client side for fetching data; -then 'receive-pack' on the server and 'send-pack' on the client for pushing -data. The protocol functions to have a server tell a client what is -currently on the server, then for the two to negotiate the smallest amount -of data to send in order to fully update one or the other. - -pkt-line Format ---------------- - -The descriptions below build on the pkt-line format described in -protocol-common.txt. When the grammar indicate `PKT-LINE(...)`, unless -otherwise noted the usual pkt-line LF rules apply: the sender SHOULD -include a LF, but the receiver MUST NOT complain if it is not present. - -Transports ----------- -There are three transports over which the packfile protocol is -initiated. The Git transport is a simple, unauthenticated server that -takes the command (almost always 'upload-pack', though Git -servers can be configured to be globally writable, in which 'receive- -pack' initiation is also allowed) with which the client wishes to -communicate and executes it and connects it to the requesting -process. - -In the SSH transport, the client just runs the 'upload-pack' -or 'receive-pack' process on the server over the SSH protocol and then -communicates with that invoked process over the SSH connection. - -The file:// transport runs the 'upload-pack' or 'receive-pack' -process locally and communicates with it over a pipe. - -Git Transport -------------- - -The Git transport starts off by sending the command and repository -on the wire using the pkt-line format, followed by a NUL byte and a -hostname parameter, terminated by a NUL byte. - - 0032git-upload-pack /project.git\0host=myserver.com\0 - --- - git-proto-request = request-command SP pathname NUL [ host-parameter NUL ] - request-command = "git-upload-pack" / "git-receive-pack" / - "git-upload-archive" ; case sensitive - pathname = *( %x01-ff ) ; exclude NUL - host-parameter = "host=" hostname [ ":" port ] --- - -Only host-parameter is allowed in the git-proto-request. Clients -MUST NOT attempt to send additional parameters. It is used for the -git-daemon name based virtual hosting. See --interpolated-path -option to git daemon, with the %H/%CH format characters. - -Basically what the Git client is doing to connect to an 'upload-pack' -process on the server side over the Git protocol is this: - - $ echo -e -n \ - "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" | - nc -v example.com 9418 - -If the server refuses the request for some reasons, it could abort -gracefully with an error message. - ----- - error-line = PKT-LINE("ERR" SP explanation-text) ----- - - -SSH Transport -------------- - -Initiating the upload-pack or receive-pack processes over SSH is -executing the binary on the server via SSH remote execution. -It is basically equivalent to running this: - - $ ssh git.example.com "git-upload-pack '/project.git'" - -For a server to support Git pushing and pulling for a given user over -SSH, that user needs to be able to execute one or both of those -commands via the SSH shell that they are provided on login. On some -systems, that shell access is limited to only being able to run those -two commands, or even just one of them. - -In an ssh:// format URI, it's absolute in the URI, so the '/' after -the host name (or port number) is sent as an argument, which is then -read by the remote git-upload-pack exactly as is, so it's effectively -an absolute path in the remote filesystem. - - git clone ssh://user@example.com/project.git - | - v - ssh user@example.com "git-upload-pack '/project.git'" - -In a "user@host:path" format URI, its relative to the user's home -directory, because the Git client will run: - - git clone user@example.com:project.git - | - v - ssh user@example.com "git-upload-pack 'project.git'" - -The exception is if a '~' is used, in which case -we execute it without the leading '/'. - - ssh://user@example.com/~alice/project.git, - | - v - ssh user@example.com "git-upload-pack '~alice/project.git'" - -A few things to remember here: - -- The "command name" is spelled with dash (e.g. git-upload-pack), but - this can be overridden by the client; - -- The repository path is always quoted with single quotes. - -Fetching Data From a Server ---------------------------- - -When one Git repository wants to get data that a second repository -has, the first can 'fetch' from the second. This operation determines -what data the server has that the client does not then streams that -data down to the client in packfile format. - - -Reference Discovery -------------------- - -When the client initially connects the server will immediately respond -with a listing of each reference it has (all branches and tags) along -with the object name that each reference currently points to. - - $ echo -e -n "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" | - nc -v example.com 9418 - 00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack - side-band side-band-64k ofs-delta shallow no-progress include-tag - 00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration - 003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 refs/heads/master - 003cb88d2441cac0977faf98efc80305012112238d9d refs/tags/v0.9 - 003c525128480b96c89e6418b1e40909bf6c5b2d580f refs/tags/v1.0 - 003fe92df48743b7bc7d26bcaabfddde0a1e20cae47c refs/tags/v1.0^{} - 0000 - -The returned response is a pkt-line stream describing each ref and -its current value. The stream MUST be sorted by name according to -the C locale ordering. - -If HEAD is a valid ref, HEAD MUST appear as the first advertised -ref. If HEAD is not a valid ref, HEAD MUST NOT appear in the -advertisement list at all, but other refs may still appear. - -The stream MUST include capability declarations behind a NUL on the -first ref. The peeled value of a ref (that is "ref^{}") MUST be -immediately after the ref itself, if presented. A conforming server -MUST peel the ref if it's an annotated tag. - ----- - advertised-refs = (no-refs / list-of-refs) - *shallow - flush-pkt - - no-refs = PKT-LINE(zero-id SP "capabilities^{}" - NUL capability-list) - - list-of-refs = first-ref *other-ref - first-ref = PKT-LINE(obj-id SP refname - NUL capability-list) - - other-ref = PKT-LINE(other-tip / other-peeled) - other-tip = obj-id SP refname - other-peeled = obj-id SP refname "^{}" - - shallow = PKT-LINE("shallow" SP obj-id) - - capability-list = capability *(SP capability) - capability = 1*(LC_ALPHA / DIGIT / "-" / "_") - LC_ALPHA = %x61-7A ----- - -Server and client MUST use lowercase for obj-id, both MUST treat obj-id -as case-insensitive. - -See protocol-capabilities.txt for a list of allowed server capabilities -and descriptions. - -Packfile Negotiation --------------------- -After reference and capabilities discovery, the client can decide to -terminate the connection by sending a flush-pkt, telling the server it can -now gracefully terminate, and disconnect, when it does not need any pack -data. This can happen with the ls-remote command, and also can happen when -the client already is up-to-date. - -Otherwise, it enters the negotiation phase, where the client and -server determine what the minimal packfile necessary for transport is, -by telling the server what objects it wants, its shallow objects -(if any), and the maximum commit depth it wants (if any). The client -will also send a list of the capabilities it wants to be in effect, -out of what the server said it could do with the first 'want' line. - ----- - upload-request = want-list - *shallow-line - *1depth-request - flush-pkt - - want-list = first-want - *additional-want - - shallow-line = PKT-LINE("shallow" SP obj-id) - - depth-request = PKT-LINE("deepen" SP depth) / - PKT-LINE("deepen-since" SP timestamp) / - PKT-LINE("deepen-not" SP ref) - - first-want = PKT-LINE("want" SP obj-id SP capability-list) - additional-want = PKT-LINE("want" SP obj-id) - - depth = 1*DIGIT ----- - -Clients MUST send all the obj-ids it wants from the reference -discovery phase as 'want' lines. Clients MUST send at least one -'want' command in the request body. Clients MUST NOT mention an -obj-id in a 'want' command which did not appear in the response -obtained through ref discovery. - -The client MUST write all obj-ids which it only has shallow copies -of (meaning that it does not have the parents of a commit) as -'shallow' lines so that the server is aware of the limitations of -the client's history. - -The client now sends the maximum commit history depth it wants for -this transaction, which is the number of commits it wants from the -tip of the history, if any, as a 'deepen' line. A depth of 0 is the -same as not making a depth request. The client does not want to receive -any commits beyond this depth, nor does it want objects needed only to -complete those commits. Commits whose parents are not received as a -result are defined as shallow and marked as such in the server. This -information is sent back to the client in the next step. - -Once all the 'want's and 'shallow's (and optional 'deepen') are -transferred, clients MUST send a flush-pkt, to tell the server side -that it is done sending the list. - -Otherwise, if the client sent a positive depth request, the server -will determine which commits will and will not be shallow and -send this information to the client. If the client did not request -a positive depth, this step is skipped. - ----- - shallow-update = *shallow-line - *unshallow-line - flush-pkt - - shallow-line = PKT-LINE("shallow" SP obj-id) - - unshallow-line = PKT-LINE("unshallow" SP obj-id) ----- - -If the client has requested a positive depth, the server will compute -the set of commits which are no deeper than the desired depth. The set -of commits start at the client's wants. - -The server writes 'shallow' lines for each -commit whose parents will not be sent as a result. The server writes -an 'unshallow' line for each commit which the client has indicated is -shallow, but is no longer shallow at the currently requested depth -(that is, its parents will now be sent). The server MUST NOT mark -as unshallow anything which the client has not indicated was shallow. - -Now the client will send a list of the obj-ids it has using 'have' -lines, so the server can make a packfile that only contains the objects -that the client needs. In multi_ack mode, the canonical implementation -will send up to 32 of these at a time, then will send a flush-pkt. The -canonical implementation will skip ahead and send the next 32 immediately, -so that there is always a block of 32 "in-flight on the wire" at a time. - ----- - upload-haves = have-list - compute-end - - have-list = *have-line - have-line = PKT-LINE("have" SP obj-id) - compute-end = flush-pkt / PKT-LINE("done") ----- - -If the server reads 'have' lines, it then will respond by ACKing any -of the obj-ids the client said it had that the server also has. The -server will ACK obj-ids differently depending on which ack mode is -chosen by the client. - -In multi_ack mode: - - * the server will respond with 'ACK obj-id continue' for any common - commits. - - * once the server has found an acceptable common base commit and is - ready to make a packfile, it will blindly ACK all 'have' obj-ids - back to the client. - - * the server will then send a 'NAK' and then wait for another response - from the client - either a 'done' or another list of 'have' lines. - -In multi_ack_detailed mode: - - * the server will differentiate the ACKs where it is signaling - that it is ready to send data with 'ACK obj-id ready' lines, and - signals the identified common commits with 'ACK obj-id common' lines. - -Without either multi_ack or multi_ack_detailed: - - * upload-pack sends "ACK obj-id" on the first common object it finds. - After that it says nothing until the client gives it a "done". - - * upload-pack sends "NAK" on a flush-pkt if no common object - has been found yet. If one has been found, and thus an ACK - was already sent, it's silent on the flush-pkt. - -After the client has gotten enough ACK responses that it can determine -that the server has enough information to send an efficient packfile -(in the canonical implementation, this is determined when it has received -enough ACKs that it can color everything left in the --date-order queue -as common with the server, or the --date-order queue is empty), or the -client determines that it wants to give up (in the canonical implementation, -this is determined when the client sends 256 'have' lines without getting -any of them ACKed by the server - meaning there is nothing in common and -the server should just send all of its objects), then the client will send -a 'done' command. The 'done' command signals to the server that the client -is ready to receive its packfile data. - -However, the 256 limit *only* turns on in the canonical client -implementation if we have received at least one "ACK %s continue" -during a prior round. This helps to ensure that at least one common -ancestor is found before we give up entirely. - -Once the 'done' line is read from the client, the server will either -send a final 'ACK obj-id' or it will send a 'NAK'. 'obj-id' is the object -name of the last commit determined to be common. The server only sends -ACK after 'done' if there is at least one common base and multi_ack or -multi_ack_detailed is enabled. The server always sends NAK after 'done' -if there is no common base found. - -Then the server will start sending its packfile data. - ----- - server-response = *ack_multi ack / nak - ack_multi = PKT-LINE("ACK" SP obj-id ack_status) - ack_status = "continue" / "common" / "ready" - ack = PKT-LINE("ACK" SP obj-id) - nak = PKT-LINE("NAK") ----- - -A simple clone may look like this (with no 'have' lines): - ----- - C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d multi_ack \ - side-band-64k ofs-delta\n - C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n - C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n - C: 0032want 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n - C: 0032want 74730d410fcb6603ace96f1dc55ea6196122532d\n - C: 0000 - C: 0009done\n - - S: 0008NAK\n - S: [PACKFILE] ----- - -An incremental update (fetch) response might look like this: - ----- - C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d multi_ack \ - side-band-64k ofs-delta\n - C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n - C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n - C: 0000 - C: 0032have 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n - C: [30 more have lines] - C: 0032have 74730d410fcb6603ace96f1dc55ea6196122532d\n - C: 0000 - - S: 003aACK 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01 continue\n - S: 003aACK 74730d410fcb6603ace96f1dc55ea6196122532d continue\n - S: 0008NAK\n - - C: 0009done\n - - S: 0031ACK 74730d410fcb6603ace96f1dc55ea6196122532d\n - S: [PACKFILE] ----- - - -Packfile Data -------------- - -Now that the client and server have finished negotiation about what -the minimal amount of data that needs to be sent to the client is, the server -will construct and send the required data in packfile format. - -See pack-format.txt for what the packfile itself actually looks like. - -If 'side-band' or 'side-band-64k' capabilities have been specified by -the client, the server will send the packfile data multiplexed. - -Each packet starting with the packet-line length of the amount of data -that follows, followed by a single byte specifying the sideband the -following data is coming in on. - -In 'side-band' mode, it will send up to 999 data bytes plus 1 control -code, for a total of up to 1000 bytes in a pkt-line. In 'side-band-64k' -mode it will send up to 65519 data bytes plus 1 control code, for a -total of up to 65520 bytes in a pkt-line. - -The sideband byte will be a '1', '2' or a '3'. Sideband '1' will contain -packfile data, sideband '2' will be used for progress information that the -client will generally print to stderr and sideband '3' is used for error -information. - -If no 'side-band' capability was specified, the server will stream the -entire packfile without multiplexing. - - -Pushing Data To a Server ------------------------- - -Pushing data to a server will invoke the 'receive-pack' process on the -server, which will allow the client to tell it which references it should -update and then send all the data the server will need for those new -references to be complete. Once all the data is received and validated, -the server will then update its references to what the client specified. - -Authentication --------------- - -The protocol itself contains no authentication mechanisms. That is to be -handled by the transport, such as SSH, before the 'receive-pack' process is -invoked. If 'receive-pack' is configured over the Git transport, those -repositories will be writable by anyone who can access that port (9418) as -that transport is unauthenticated. - -Reference Discovery -------------------- - -The reference discovery phase is done nearly the same way as it is in the -fetching protocol. Each reference obj-id and name on the server is sent -in packet-line format to the client, followed by a flush-pkt. The only -real difference is that the capability listing is different - the only -possible values are 'report-status', 'delete-refs', 'ofs-delta' and -'push-options'. - -Reference Update Request and Packfile Transfer ----------------------------------------------- - -Once the client knows what references the server is at, it can send a -list of reference update requests. For each reference on the server -that it wants to update, it sends a line listing the obj-id currently on -the server, the obj-id the client would like to update it to and the name -of the reference. - -This list is followed by a flush-pkt. Then the push options are transmitted -one per packet followed by another flush-pkt. After that the packfile that -should contain all the objects that the server will need to complete the new -references will be sent. - ----- - update-request = *shallow ( command-list | push-cert ) [packfile] - - shallow = PKT-LINE("shallow" SP obj-id) - - command-list = PKT-LINE(command NUL capability-list) - *PKT-LINE(command) - flush-pkt - - command = create / delete / update - create = zero-id SP new-id SP name - delete = old-id SP zero-id SP name - update = old-id SP new-id SP name - - old-id = obj-id - new-id = obj-id - - push-cert = PKT-LINE("push-cert" NUL capability-list LF) - PKT-LINE("certificate version 0.1" LF) - PKT-LINE("pusher" SP ident LF) - PKT-LINE("pushee" SP url LF) - PKT-LINE("nonce" SP nonce LF) - PKT-LINE(LF) - *PKT-LINE(command LF) - *PKT-LINE(gpg-signature-lines LF) - PKT-LINE("push-cert-end" LF) - - packfile = "PACK" 28*(OCTET) ----- - -If the receiving end does not support delete-refs, the sending end MUST -NOT ask for delete command. - -If the receiving end does not support push-cert, the sending end -MUST NOT send a push-cert command. When a push-cert command is -sent, command-list MUST NOT be sent; the commands recorded in the -push certificate is used instead. - -The packfile MUST NOT be sent if the only command used is 'delete'. - -A packfile MUST be sent if either create or update command is used, -even if the server already has all the necessary objects. In this -case the client MUST send an empty packfile. The only time this -is likely to happen is if the client is creating -a new branch or a tag that points to an existing obj-id. - -The server will receive the packfile, unpack it, then validate each -reference that is being updated that it hasn't changed while the request -was being processed (the obj-id is still the same as the old-id), and -it will run any update hooks to make sure that the update is acceptable. -If all of that is fine, the server will then update the references. - -Push Certificate ----------------- - -A push certificate begins with a set of header lines. After the -header and an empty line, the protocol commands follow, one per -line. Note that the trailing LF in push-cert PKT-LINEs is _not_ -optional; it must be present. - -Currently, the following header fields are defined: - -`pusher` ident:: - Identify the GPG key in "Human Readable Name " - format. - -`pushee` url:: - The repository URL (anonymized, if the URL contains - authentication material) the user who ran `git push` - intended to push into. - -`nonce` nonce:: - The 'nonce' string the receiving repository asked the - pushing user to include in the certificate, to prevent - replay attacks. - -The GPG signature lines are a detached signature for the contents -recorded in the push certificate before the signature block begins. -The detached signature is used to certify that the commands were -given by the pusher, who must be the signer. - -Report Status -------------- - -After receiving the pack data from the sender, the receiver sends a -report if 'report-status' capability is in effect. -It is a short listing of what happened in that update. It will first -list the status of the packfile unpacking as either 'unpack ok' or -'unpack [error]'. Then it will list the status for each of the references -that it tried to update. Each line is either 'ok [refname]' if the -update was successful, or 'ng [refname] [error]' if the update was not. - ----- - report-status = unpack-status - 1*(command-status) - flush-pkt - - unpack-status = PKT-LINE("unpack" SP unpack-result) - unpack-result = "ok" / error-msg - - command-status = command-ok / command-fail - command-ok = PKT-LINE("ok" SP refname) - command-fail = PKT-LINE("ng" SP refname SP error-msg) - - error-msg = 1*(OCTECT) ; where not "ok" ----- - -Updates can be unsuccessful for a number of reasons. The reference can have -changed since the reference discovery phase was originally sent, meaning -someone pushed in the meantime. The reference being pushed could be a -non-fast-forward reference and the update hooks or configuration could be -set to not allow that, etc. Also, some references can be updated while others -can be rejected. - -An example client/server communication might look like this: - ----- - S: 007c74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/local\0report-status delete-refs ofs-delta\n - S: 003e7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug\n - S: 003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/master\n - S: 003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/team\n - S: 0000 - - C: 003e7d1665144a3a975c05f1f43902ddaf084e784dbe 74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/debug\n - C: 003e74730d410fcb6603ace96f1dc55ea6196122532d 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a refs/heads/master\n - C: 0000 - C: [PACKDATA] - - S: 000eunpack ok\n - S: 0018ok refs/heads/debug\n - S: 002ang refs/heads/master non-fast-forward\n ----- -*/ diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/filter.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/filter.go deleted file mode 100644 index 08932af1104..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/filter.go +++ /dev/null @@ -1,76 +0,0 @@ -package packp - -import ( - "errors" - "fmt" - "github.com/jesseduffield/go-git/v5/plumbing" - "net/url" - "strings" -) - -var ErrUnsupportedObjectFilterType = errors.New("unsupported object filter type") - -// Filter values enable the partial clone capability which causes -// the server to omit objects that match the filter. -// -// See [Git's documentation] for more details. -// -// [Git's documentation]: https://github.com/git/git/blob/e02ecfcc534e2021aae29077a958dd11c3897e4c/Documentation/rev-list-options.txt#L948 -type Filter string - -type BlobLimitPrefix string - -const ( - BlobLimitPrefixNone BlobLimitPrefix = "" - BlobLimitPrefixKibi BlobLimitPrefix = "k" - BlobLimitPrefixMebi BlobLimitPrefix = "m" - BlobLimitPrefixGibi BlobLimitPrefix = "g" -) - -// FilterBlobNone omits all blobs. -func FilterBlobNone() Filter { - return "blob:none" -} - -// FilterBlobLimit omits blobs of size at least n bytes (when prefix is -// BlobLimitPrefixNone), n kibibytes (when prefix is BlobLimitPrefixKibi), -// n mebibytes (when prefix is BlobLimitPrefixMebi) or n gibibytes (when -// prefix is BlobLimitPrefixGibi). n can be zero, in which case all blobs -// will be omitted. -func FilterBlobLimit(n uint64, prefix BlobLimitPrefix) Filter { - return Filter(fmt.Sprintf("blob:limit=%d%s", n, prefix)) -} - -// FilterTreeDepth omits all blobs and trees whose depth from the root tree -// is larger or equal to depth. -func FilterTreeDepth(depth uint64) Filter { - return Filter(fmt.Sprintf("tree:%d", depth)) -} - -// FilterObjectType omits all objects which are not of the requested type t. -// Supported types are TagObject, CommitObject, TreeObject and BlobObject. -func FilterObjectType(t plumbing.ObjectType) (Filter, error) { - switch t { - case plumbing.TagObject: - fallthrough - case plumbing.CommitObject: - fallthrough - case plumbing.TreeObject: - fallthrough - case plumbing.BlobObject: - return Filter(fmt.Sprintf("object:type=%s", t.String())), nil - default: - return "", fmt.Errorf("%w: %s", ErrUnsupportedObjectFilterType, t.String()) - } -} - -// FilterCombine combines multiple Filter values together. -func FilterCombine(filters ...Filter) Filter { - var escapedFilters []string - - for _, filter := range filters { - escapedFilters = append(escapedFilters, url.QueryEscape(string(filter))) - } - - return Filter(fmt.Sprintf("combine:%s", strings.Join(escapedFilters, "+"))) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/gitproto.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/gitproto.go deleted file mode 100644 index cbb05a1d1df..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/gitproto.go +++ /dev/null @@ -1,120 +0,0 @@ -package packp - -import ( - "fmt" - "io" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" -) - -var ( - // ErrInvalidGitProtoRequest is returned by Decode if the input is not a - // valid git protocol request. - ErrInvalidGitProtoRequest = fmt.Errorf("invalid git protocol request") -) - -// GitProtoRequest is a command request for the git protocol. -// It is used to send the command, endpoint, and extra parameters to the -// remote. -// See https://git-scm.com/docs/pack-protocol#_git_transport -type GitProtoRequest struct { - RequestCommand string - Pathname string - - // Optional - Host string - - // Optional - ExtraParams []string -} - -// validate validates the request. -func (g *GitProtoRequest) validate() error { - if g.RequestCommand == "" { - return fmt.Errorf("%w: empty request command", ErrInvalidGitProtoRequest) - } - - if g.Pathname == "" { - return fmt.Errorf("%w: empty pathname", ErrInvalidGitProtoRequest) - } - - return nil -} - -// Encode encodes the request into the writer. -func (g *GitProtoRequest) Encode(w io.Writer) error { - if w == nil { - return ErrNilWriter - } - - if err := g.validate(); err != nil { - return err - } - - p := pktline.NewEncoder(w) - req := fmt.Sprintf("%s %s\x00", g.RequestCommand, g.Pathname) - if host := g.Host; host != "" { - req += fmt.Sprintf("host=%s\x00", host) - } - - if len(g.ExtraParams) > 0 { - req += "\x00" - for _, param := range g.ExtraParams { - req += param + "\x00" - } - } - - if err := p.Encode([]byte(req)); err != nil { - return err - } - - return nil -} - -// Decode decodes the request from the reader. -func (g *GitProtoRequest) Decode(r io.Reader) error { - s := pktline.NewScanner(r) - if !s.Scan() { - err := s.Err() - if err == nil { - return ErrInvalidGitProtoRequest - } - return err - } - - line := string(s.Bytes()) - if len(line) == 0 { - return io.EOF - } - - if line[len(line)-1] != 0 { - return fmt.Errorf("%w: missing null terminator", ErrInvalidGitProtoRequest) - } - - parts := strings.SplitN(line, " ", 2) - if len(parts) != 2 { - return fmt.Errorf("%w: short request", ErrInvalidGitProtoRequest) - } - - g.RequestCommand = parts[0] - params := strings.Split(parts[1], string(null)) - if len(params) < 1 { - return fmt.Errorf("%w: missing pathname", ErrInvalidGitProtoRequest) - } - - g.Pathname = params[0] - if len(params) > 1 { - g.Host = strings.TrimPrefix(params[1], "host=") - } - - if len(params) > 2 { - for _, param := range params[2:] { - if param != "" { - g.ExtraParams = append(g.ExtraParams, param) - } - } - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/report_status.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/report_status.go deleted file mode 100644 index a96658ad153..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/report_status.go +++ /dev/null @@ -1,165 +0,0 @@ -package packp - -import ( - "bytes" - "fmt" - "io" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" -) - -const ( - ok = "ok" -) - -// ReportStatus is a report status message, as used in the git-receive-pack -// process whenever the 'report-status' capability is negotiated. -type ReportStatus struct { - UnpackStatus string - CommandStatuses []*CommandStatus -} - -// NewReportStatus creates a new ReportStatus message. -func NewReportStatus() *ReportStatus { - return &ReportStatus{} -} - -// Error returns the first error if any. -func (s *ReportStatus) Error() error { - if s.UnpackStatus != ok { - return fmt.Errorf("unpack error: %s", s.UnpackStatus) - } - - for _, s := range s.CommandStatuses { - if err := s.Error(); err != nil { - return err - } - } - - return nil -} - -// Encode writes the report status to a writer. -func (s *ReportStatus) Encode(w io.Writer) error { - e := pktline.NewEncoder(w) - if err := e.Encodef("unpack %s\n", s.UnpackStatus); err != nil { - return err - } - - for _, cs := range s.CommandStatuses { - if err := cs.encode(w); err != nil { - return err - } - } - - return e.Flush() -} - -// Decode reads from the given reader and decodes a report-status message. It -// does not read more input than what is needed to fill the report status. -func (s *ReportStatus) Decode(r io.Reader) error { - scan := pktline.NewScanner(r) - if err := s.scanFirstLine(scan); err != nil { - return err - } - - if err := s.decodeReportStatus(scan.Bytes()); err != nil { - return err - } - - flushed := false - for scan.Scan() { - b := scan.Bytes() - if isFlush(b) { - flushed = true - break - } - - if err := s.decodeCommandStatus(b); err != nil { - return err - } - } - - if !flushed { - return fmt.Errorf("missing flush") - } - - return scan.Err() -} - -func (s *ReportStatus) scanFirstLine(scan *pktline.Scanner) error { - if scan.Scan() { - return nil - } - - if scan.Err() != nil { - return scan.Err() - } - - return io.ErrUnexpectedEOF -} - -func (s *ReportStatus) decodeReportStatus(b []byte) error { - if isFlush(b) { - return fmt.Errorf("premature flush") - } - - b = bytes.TrimSuffix(b, eol) - - line := string(b) - fields := strings.SplitN(line, " ", 2) - if len(fields) != 2 || fields[0] != "unpack" { - return fmt.Errorf("malformed unpack status: %s", line) - } - - s.UnpackStatus = fields[1] - return nil -} - -func (s *ReportStatus) decodeCommandStatus(b []byte) error { - b = bytes.TrimSuffix(b, eol) - - line := string(b) - fields := strings.SplitN(line, " ", 3) - status := ok - if len(fields) == 3 && fields[0] == "ng" { - status = fields[2] - } else if len(fields) != 2 || fields[0] != "ok" { - return fmt.Errorf("malformed command status: %s", line) - } - - cs := &CommandStatus{ - ReferenceName: plumbing.ReferenceName(fields[1]), - Status: status, - } - s.CommandStatuses = append(s.CommandStatuses, cs) - return nil -} - -// CommandStatus is the status of a reference in a report status. -// See ReportStatus struct. -type CommandStatus struct { - ReferenceName plumbing.ReferenceName - Status string -} - -// Error returns the error, if any. -func (s *CommandStatus) Error() error { - if s.Status == ok { - return nil - } - - return fmt.Errorf("command error on %s: %s", - s.ReferenceName.String(), s.Status) -} - -func (s *CommandStatus) encode(w io.Writer) error { - e := pktline.NewEncoder(w) - if s.Error() == nil { - return e.Encodef("ok %s\n", s.ReferenceName.String()) - } - - return e.Encodef("ng %s %s\n", s.ReferenceName.String(), s.Status) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/shallowupd.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/shallowupd.go deleted file mode 100644 index af6ba69c74e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/shallowupd.go +++ /dev/null @@ -1,92 +0,0 @@ -package packp - -import ( - "bytes" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" -) - -const ( - shallowLineLen = 48 - unshallowLineLen = 50 -) - -type ShallowUpdate struct { - Shallows []plumbing.Hash - Unshallows []plumbing.Hash -} - -func (r *ShallowUpdate) Decode(reader io.Reader) error { - s := pktline.NewScanner(reader) - - for s.Scan() { - line := s.Bytes() - line = bytes.TrimSpace(line) - - var err error - switch { - case bytes.HasPrefix(line, shallow): - err = r.decodeShallowLine(line) - case bytes.HasPrefix(line, unshallow): - err = r.decodeUnshallowLine(line) - case bytes.Equal(line, pktline.Flush): - return nil - } - - if err != nil { - return err - } - } - - return s.Err() -} - -func (r *ShallowUpdate) decodeShallowLine(line []byte) error { - hash, err := r.decodeLine(line, shallow, shallowLineLen) - if err != nil { - return err - } - - r.Shallows = append(r.Shallows, hash) - return nil -} - -func (r *ShallowUpdate) decodeUnshallowLine(line []byte) error { - hash, err := r.decodeLine(line, unshallow, unshallowLineLen) - if err != nil { - return err - } - - r.Unshallows = append(r.Unshallows, hash) - return nil -} - -func (r *ShallowUpdate) decodeLine(line, prefix []byte, expLen int) (plumbing.Hash, error) { - if len(line) != expLen { - return plumbing.ZeroHash, fmt.Errorf("malformed %s%q", prefix, line) - } - - raw := string(line[expLen-40 : expLen]) - return plumbing.NewHash(raw), nil -} - -func (r *ShallowUpdate) Encode(w io.Writer) error { - e := pktline.NewEncoder(w) - - for _, h := range r.Shallows { - if err := e.Encodef("%s%s\n", shallow, h.String()); err != nil { - return err - } - } - - for _, h := range r.Unshallows { - if err := e.Encodef("%s%s\n", unshallow, h.String()); err != nil { - return err - } - } - - return e.Flush() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/common.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/common.go deleted file mode 100644 index de5001281fd..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/common.go +++ /dev/null @@ -1,33 +0,0 @@ -package sideband - -// Type sideband type "side-band" or "side-band-64k" -type Type int8 - -const ( - // Sideband legacy sideband type up to 1000-byte messages - Sideband Type = iota - // Sideband64k sideband type up to 65519-byte messages - Sideband64k Type = iota - - // MaxPackedSize for Sideband type - MaxPackedSize = 1000 - // MaxPackedSize64k for Sideband64k type - MaxPackedSize64k = 65520 -) - -// Channel sideband channel -type Channel byte - -// WithPayload encode the payload as a message -func (ch Channel) WithPayload(payload []byte) []byte { - return append([]byte{byte(ch)}, payload...) -} - -const ( - // PackData packfile content - PackData Channel = 1 - // ProgressMessage progress messages - ProgressMessage Channel = 2 - // ErrorMessage fatal error message just before stream aborts - ErrorMessage Channel = 3 -) diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/demux.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/demux.go deleted file mode 100644 index a8e3f7378ea..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/demux.go +++ /dev/null @@ -1,148 +0,0 @@ -package sideband - -import ( - "errors" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" -) - -// ErrMaxPackedExceeded returned by Read, if the maximum packed size is exceeded -var ErrMaxPackedExceeded = errors.New("max. packed size exceeded") - -// Progress where the progress information is stored -type Progress interface { - io.Writer -} - -// Demuxer demultiplexes the progress reports and error info interleaved with the -// packfile itself. -// -// A sideband has three different channels the main one, called PackData, contains -// the packfile data; the ErrorMessage channel, that contains server errors; and -// the last one, ProgressMessage channel, containing information about the ongoing -// task happening in the server (optional, can be suppressed sending NoProgress -// or Quiet capabilities to the server) -// -// In order to demultiplex the data stream, method `Read` should be called to -// retrieve the PackData channel, the incoming data from the ProgressMessage is -// written at `Progress` (if any), if any message is retrieved from the -// ErrorMessage channel an error is returned and we can assume that the -// connection has been closed. -type Demuxer struct { - t Type - r io.Reader - s *pktline.Scanner - - max int - pending []byte - - // Progress is where the progress messages are stored - Progress Progress -} - -// NewDemuxer returns a new Demuxer for the given t and read from r -func NewDemuxer(t Type, r io.Reader) *Demuxer { - max := MaxPackedSize64k - if t == Sideband { - max = MaxPackedSize - } - - return &Demuxer{ - t: t, - r: r, - max: max, - s: pktline.NewScanner(r), - } -} - -// Read reads up to len(p) bytes from the PackData channel into p, an error can -// be return if an error happens when reading or if a message is sent in the -// ErrorMessage channel. -// -// When a ProgressMessage is read, is not copy to b, instead of this is written -// to the Progress -func (d *Demuxer) Read(b []byte) (n int, err error) { - var read, req int - - req = len(b) - for read < req { - n, err := d.doRead(b[read:req]) - read += n - - if err != nil { - return read, err - } - } - - return read, nil -} - -func (d *Demuxer) doRead(b []byte) (int, error) { - read, err := d.nextPackData() - size := len(read) - wanted := len(b) - - if size > wanted { - d.pending = read[wanted:] - } - - if wanted > size { - wanted = size - } - - size = copy(b, read[:wanted]) - return size, err -} - -func (d *Demuxer) nextPackData() ([]byte, error) { - content := d.getPending() - if len(content) != 0 { - return content, nil - } - - if !d.s.Scan() { - if err := d.s.Err(); err != nil { - return nil, err - } - - return nil, io.EOF - } - - content = d.s.Bytes() - - size := len(content) - if size == 0 { - return nil, io.EOF - } else if size > d.max { - return nil, ErrMaxPackedExceeded - } - - switch Channel(content[0]) { - case PackData: - return content[1:], nil - case ProgressMessage: - if d.Progress != nil { - _, err := d.Progress.Write(content[1:]) - return nil, err - } - case ErrorMessage: - return nil, fmt.Errorf("unexpected error: %s", content[1:]) - default: - return nil, fmt.Errorf("unknown channel %s", content) - } - - return nil, nil -} - -func (d *Demuxer) getPending() (b []byte) { - if len(d.pending) == 0 { - return nil - } - - content := d.pending - d.pending = nil - - return content -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/doc.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/doc.go deleted file mode 100644 index c5d24295291..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/doc.go +++ /dev/null @@ -1,31 +0,0 @@ -// Package sideband implements a sideband mutiplex/demultiplexer -package sideband - -// If 'side-band' or 'side-band-64k' capabilities have been specified by -// the client, the server will send the packfile data multiplexed. -// -// Either mode indicates that the packfile data will be streamed broken -// up into packets of up to either 1000 bytes in the case of 'side_band', -// or 65520 bytes in the case of 'side_band_64k'. Each packet is made up -// of a leading 4-byte pkt-line length of how much data is in the packet, -// followed by a 1-byte stream code, followed by the actual data. -// -// The stream code can be one of: -// -// 1 - pack data -// 2 - progress messages -// 3 - fatal error message just before stream aborts -// -// The "side-band-64k" capability came about as a way for newer clients -// that can handle much larger packets to request packets that are -// actually crammed nearly full, while maintaining backward compatibility -// for the older clients. -// -// Further, with side-band and its up to 1000-byte messages, it's actually -// 999 bytes of payload and 1 byte for the stream code. With side-band-64k, -// same deal, you have up to 65519 bytes of data and 1 byte for the stream -// code. -// -// The client MUST send only maximum of one of "side-band" and "side- -// band-64k". Server MUST diagnose it as an error if client requests -// both. diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/muxer.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/muxer.go deleted file mode 100644 index 5c9f851b068..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband/muxer.go +++ /dev/null @@ -1,65 +0,0 @@ -package sideband - -import ( - "io" - - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" -) - -// Muxer multiplex the packfile along with the progress messages and the error -// information. The multiplex is perform using pktline format. -type Muxer struct { - max int - e *pktline.Encoder -} - -const chLen = 1 - -// NewMuxer returns a new Muxer for the given t that writes on w. -// -// If t is equal to `Sideband` the max pack size is set to MaxPackedSize, in any -// other value is given, max pack is set to MaxPackedSize64k, that is the -// maximum length of a line in pktline format. -func NewMuxer(t Type, w io.Writer) *Muxer { - max := MaxPackedSize64k - if t == Sideband { - max = MaxPackedSize - } - - return &Muxer{ - max: max - chLen, - e: pktline.NewEncoder(w), - } -} - -// Write writes p in the PackData channel -func (m *Muxer) Write(p []byte) (int, error) { - return m.WriteChannel(PackData, p) -} - -// WriteChannel writes p in the given channel. This method can be used with any -// channel, but is recommend use it only for the ProgressMessage and -// ErrorMessage channels and use Write for the PackData channel -func (m *Muxer) WriteChannel(t Channel, p []byte) (int, error) { - wrote := 0 - size := len(p) - for wrote < size { - n, err := m.doWrite(t, p[wrote:]) - wrote += n - - if err != nil { - return wrote, err - } - } - - return wrote, nil -} - -func (m *Muxer) doWrite(ch Channel, p []byte) (int, error) { - sz := len(p) - if sz > m.max { - sz = m.max - } - - return sz, m.e.Encode(ch.WithPayload(p[:sz])) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/srvresp.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/srvresp.go deleted file mode 100644 index e02f7c74025..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/srvresp.go +++ /dev/null @@ -1,144 +0,0 @@ -package packp - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" -) - -const ackLineLen = 44 - -// ServerResponse object acknowledgement from upload-pack service -type ServerResponse struct { - ACKs []plumbing.Hash -} - -// Decode decodes the response into the struct, isMultiACK should be true, if -// the request was done with multi_ack or multi_ack_detailed capabilities. -func (r *ServerResponse) Decode(reader *bufio.Reader, isMultiACK bool) error { - s := pktline.NewScanner(reader) - - for s.Scan() { - line := s.Bytes() - - if err := r.decodeLine(line); err != nil { - return err - } - - // we need to detect when the end of a response header and the beginning - // of a packfile header happened, some requests to the git daemon - // produces a duplicate ACK header even when multi_ack is not supported. - stop, err := r.stopReading(reader) - if err != nil { - return err - } - - if stop { - break - } - } - - // isMultiACK is true when the remote server advertises the related - // capabilities when they are not in transport.UnsupportedCapabilities. - // - // Users may decide to remove multi_ack and multi_ack_detailed from the - // unsupported capabilities list, which allows them to do initial clones - // from Azure DevOps. - // - // Follow-up fetches may error, therefore errors are wrapped with additional - // information highlighting that this capabilities are not supported by go-git. - // - // TODO: Implement support for multi_ack or multi_ack_detailed responses. - err := s.Err() - if err != nil && isMultiACK { - return fmt.Errorf("multi_ack and multi_ack_detailed are not supported: %w", err) - } - - return err -} - -// stopReading detects when a valid command such as ACK or NAK is found to be -// read in the buffer without moving the read pointer. -func (r *ServerResponse) stopReading(reader *bufio.Reader) (bool, error) { - ahead, err := reader.Peek(7) - if err == io.EOF { - return true, nil - } - - if err != nil { - return false, err - } - - if len(ahead) > 4 && r.isValidCommand(ahead[0:3]) { - return false, nil - } - - if len(ahead) == 7 && r.isValidCommand(ahead[4:]) { - return false, nil - } - - return true, nil -} - -func (r *ServerResponse) isValidCommand(b []byte) bool { - commands := [][]byte{ack, nak} - for _, c := range commands { - if bytes.Equal(b, c) { - return true - } - } - - return false -} - -func (r *ServerResponse) decodeLine(line []byte) error { - if len(line) == 0 { - return fmt.Errorf("unexpected flush") - } - - if len(line) >= 3 { - if bytes.Equal(line[0:3], ack) { - return r.decodeACKLine(line) - } - - if bytes.Equal(line[0:3], nak) { - return nil - } - } - - return fmt.Errorf("unexpected content %q", string(line)) -} - -func (r *ServerResponse) decodeACKLine(line []byte) error { - if len(line) < ackLineLen { - return fmt.Errorf("malformed ACK %q", line) - } - - sp := bytes.Index(line, []byte(" ")) - if sp+41 > len(line) { - return fmt.Errorf("malformed ACK %q", line) - } - h := plumbing.NewHash(string(line[sp+1 : sp+41])) - r.ACKs = append(r.ACKs, h) - return nil -} - -// Encode encodes the ServerResponse into a writer. -func (r *ServerResponse) Encode(w io.Writer, isMultiACK bool) error { - if len(r.ACKs) > 1 && !isMultiACK { - // For further information, refer to comments in the Decode func above. - return errors.New("multi_ack and multi_ack_detailed are not supported") - } - - e := pktline.NewEncoder(w) - if len(r.ACKs) == 0 { - return e.Encodef("%s\n", nak) - } - - return e.Encodef("%s %s\n", ack, r.ACKs[0].String()) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/ulreq.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/ulreq.go deleted file mode 100644 index 74297f76993..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/ulreq.go +++ /dev/null @@ -1,169 +0,0 @@ -package packp - -import ( - "fmt" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" -) - -// UploadRequest values represent the information transmitted on a -// upload-request message. Values from this type are not zero-value -// safe, use the New function instead. -// This is a low level type, use UploadPackRequest instead. -type UploadRequest struct { - Capabilities *capability.List - Wants []plumbing.Hash - Shallows []plumbing.Hash - Depth Depth - Filter Filter -} - -// Depth values stores the desired depth of the requested packfile: see -// DepthCommit, DepthSince and DepthReference. -type Depth interface { - isDepth() - IsZero() bool -} - -// DepthCommits values stores the maximum number of requested commits in -// the packfile. Zero means infinite. A negative value will have -// undefined consequences. -type DepthCommits int - -func (d DepthCommits) isDepth() {} - -func (d DepthCommits) IsZero() bool { - return d == 0 -} - -// DepthSince values requests only commits newer than the specified time. -type DepthSince time.Time - -func (d DepthSince) isDepth() {} - -func (d DepthSince) IsZero() bool { - return time.Time(d).IsZero() -} - -// DepthReference requests only commits not to found in the specified reference. -type DepthReference string - -func (d DepthReference) isDepth() {} - -func (d DepthReference) IsZero() bool { - return string(d) == "" -} - -// NewUploadRequest returns a pointer to a new UploadRequest value, ready to be -// used. It has no capabilities, wants or shallows and an infinite depth. Please -// note that to encode an upload-request it has to have at least one wanted hash. -func NewUploadRequest() *UploadRequest { - return &UploadRequest{ - Capabilities: capability.NewList(), - Wants: []plumbing.Hash{}, - Shallows: []plumbing.Hash{}, - Depth: DepthCommits(0), - } -} - -// NewUploadRequestFromCapabilities returns a pointer to a new UploadRequest -// value, the request capabilities are filled with the most optimal ones, based -// on the adv value (advertised capabilities), the UploadRequest generated it -// has no wants or shallows and an infinite depth. -func NewUploadRequestFromCapabilities(adv *capability.List) *UploadRequest { - r := NewUploadRequest() - - if adv.Supports(capability.MultiACKDetailed) { - r.Capabilities.Set(capability.MultiACKDetailed) - } else if adv.Supports(capability.MultiACK) { - r.Capabilities.Set(capability.MultiACK) - } - - if adv.Supports(capability.Sideband64k) { - r.Capabilities.Set(capability.Sideband64k) - } else if adv.Supports(capability.Sideband) { - r.Capabilities.Set(capability.Sideband) - } - - if adv.Supports(capability.ThinPack) { - r.Capabilities.Set(capability.ThinPack) - } - - if adv.Supports(capability.OFSDelta) { - r.Capabilities.Set(capability.OFSDelta) - } - - if adv.Supports(capability.Agent) { - r.Capabilities.Set(capability.Agent, capability.DefaultAgent()) - } - - return r -} - -// Validate validates the content of UploadRequest, following the next rules: -// - Wants MUST have at least one reference -// - capability.Shallow MUST be present if Shallows is not empty -// - is a non-zero DepthCommits is given capability.Shallow MUST be present -// - is a DepthSince is given capability.Shallow MUST be present -// - is a DepthReference is given capability.DeepenNot MUST be present -// - MUST contain only maximum of one of capability.Sideband and capability.Sideband64k -// - MUST contain only maximum of one of capability.MultiACK and capability.MultiACKDetailed -func (req *UploadRequest) Validate() error { - if len(req.Wants) == 0 { - return fmt.Errorf("want can't be empty") - } - - if err := req.validateRequiredCapabilities(); err != nil { - return err - } - - if err := req.validateConflictCapabilities(); err != nil { - return err - } - - return nil -} - -func (req *UploadRequest) validateRequiredCapabilities() error { - msg := "missing capability %s" - - if len(req.Shallows) != 0 && !req.Capabilities.Supports(capability.Shallow) { - return fmt.Errorf(msg, capability.Shallow) - } - - switch req.Depth.(type) { - case DepthCommits: - if req.Depth != DepthCommits(0) { - if !req.Capabilities.Supports(capability.Shallow) { - return fmt.Errorf(msg, capability.Shallow) - } - } - case DepthSince: - if !req.Capabilities.Supports(capability.DeepenSince) { - return fmt.Errorf(msg, capability.DeepenSince) - } - case DepthReference: - if !req.Capabilities.Supports(capability.DeepenNot) { - return fmt.Errorf(msg, capability.DeepenNot) - } - } - - return nil -} - -func (req *UploadRequest) validateConflictCapabilities() error { - msg := "capabilities %s and %s are mutually exclusive" - if req.Capabilities.Supports(capability.Sideband) && - req.Capabilities.Supports(capability.Sideband64k) { - return fmt.Errorf(msg, capability.Sideband, capability.Sideband64k) - } - - if req.Capabilities.Supports(capability.MultiACK) && - req.Capabilities.Supports(capability.MultiACKDetailed) { - return fmt.Errorf(msg, capability.MultiACK, capability.MultiACKDetailed) - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/ulreq_decode.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/ulreq_decode.go deleted file mode 100644 index edadcaa609c..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/ulreq_decode.go +++ /dev/null @@ -1,257 +0,0 @@ -package packp - -import ( - "bytes" - "encoding/hex" - "fmt" - "io" - "strconv" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" -) - -// Decode reads the next upload-request form its input and -// stores it in the UploadRequest. -func (req *UploadRequest) Decode(r io.Reader) error { - d := newUlReqDecoder(r) - return d.Decode(req) -} - -type ulReqDecoder struct { - s *pktline.Scanner // a pkt-line scanner from the input stream - line []byte // current pkt-line contents, use parser.nextLine() to make it advance - nLine int // current pkt-line number for debugging, begins at 1 - err error // sticky error, use the parser.error() method to fill this out - data *UploadRequest // parsed data is stored here -} - -func newUlReqDecoder(r io.Reader) *ulReqDecoder { - return &ulReqDecoder{ - s: pktline.NewScanner(r), - } -} - -func (d *ulReqDecoder) Decode(v *UploadRequest) error { - d.data = v - - for state := d.decodeFirstWant; state != nil; { - state = state() - } - - return d.err -} - -// fills out the parser sticky error -func (d *ulReqDecoder) error(format string, a ...interface{}) { - msg := fmt.Sprintf( - "pkt-line %d: %s", d.nLine, - fmt.Sprintf(format, a...), - ) - - d.err = NewErrUnexpectedData(msg, d.line) -} - -// Reads a new pkt-line from the scanner, makes its payload available as -// p.line and increments p.nLine. A successful invocation returns true, -// otherwise, false is returned and the sticky error is filled out -// accordingly. Trims eols at the end of the payloads. -func (d *ulReqDecoder) nextLine() bool { - d.nLine++ - - if !d.s.Scan() { - if d.err = d.s.Err(); d.err != nil { - return false - } - - d.error("EOF") - return false - } - - d.line = d.s.Bytes() - d.line = bytes.TrimSuffix(d.line, eol) - - return true -} - -// Expected format: want [ capabilities] -func (d *ulReqDecoder) decodeFirstWant() stateFn { - if ok := d.nextLine(); !ok { - return nil - } - - if !bytes.HasPrefix(d.line, want) { - d.error("missing 'want ' prefix") - return nil - } - d.line = bytes.TrimPrefix(d.line, want) - - hash, ok := d.readHash() - if !ok { - return nil - } - d.data.Wants = append(d.data.Wants, hash) - - return d.decodeCaps -} - -func (d *ulReqDecoder) readHash() (plumbing.Hash, bool) { - if len(d.line) < hashSize { - d.err = fmt.Errorf("malformed hash: %v", d.line) - return plumbing.ZeroHash, false - } - - var hash plumbing.Hash - if _, err := hex.Decode(hash[:], d.line[:hashSize]); err != nil { - d.error("invalid hash text: %s", err) - return plumbing.ZeroHash, false - } - d.line = d.line[hashSize:] - - return hash, true -} - -// Expected format: sp cap1 sp cap2 sp cap3... -func (d *ulReqDecoder) decodeCaps() stateFn { - d.line = bytes.TrimPrefix(d.line, sp) - if err := d.data.Capabilities.Decode(d.line); err != nil { - d.error("invalid capabilities: %s", err) - } - - return d.decodeOtherWants -} - -// Expected format: want -func (d *ulReqDecoder) decodeOtherWants() stateFn { - if ok := d.nextLine(); !ok { - return nil - } - - if bytes.HasPrefix(d.line, shallow) { - return d.decodeShallow - } - - if bytes.HasPrefix(d.line, deepen) { - return d.decodeDeepen - } - - if len(d.line) == 0 { - return nil - } - - if !bytes.HasPrefix(d.line, want) { - d.error("unexpected payload while expecting a want: %q", d.line) - return nil - } - d.line = bytes.TrimPrefix(d.line, want) - - hash, ok := d.readHash() - if !ok { - return nil - } - d.data.Wants = append(d.data.Wants, hash) - - return d.decodeOtherWants -} - -// Expected format: shallow -func (d *ulReqDecoder) decodeShallow() stateFn { - if bytes.HasPrefix(d.line, deepen) { - return d.decodeDeepen - } - - if len(d.line) == 0 { - return nil - } - - if !bytes.HasPrefix(d.line, shallow) { - d.error("unexpected payload while expecting a shallow: %q", d.line) - return nil - } - d.line = bytes.TrimPrefix(d.line, shallow) - - hash, ok := d.readHash() - if !ok { - return nil - } - d.data.Shallows = append(d.data.Shallows, hash) - - if ok := d.nextLine(); !ok { - return nil - } - - return d.decodeShallow -} - -// Expected format: deepen / deepen-since
    / deepen-not -func (d *ulReqDecoder) decodeDeepen() stateFn { - if bytes.HasPrefix(d.line, deepenCommits) { - return d.decodeDeepenCommits - } - - if bytes.HasPrefix(d.line, deepenSince) { - return d.decodeDeepenSince - } - - if bytes.HasPrefix(d.line, deepenReference) { - return d.decodeDeepenReference - } - - if len(d.line) == 0 { - return nil - } - - d.error("unexpected deepen specification: %q", d.line) - return nil -} - -func (d *ulReqDecoder) decodeDeepenCommits() stateFn { - d.line = bytes.TrimPrefix(d.line, deepenCommits) - - var n int - if n, d.err = strconv.Atoi(string(d.line)); d.err != nil { - return nil - } - if n < 0 { - d.err = fmt.Errorf("negative depth") - return nil - } - d.data.Depth = DepthCommits(n) - - return d.decodeFlush -} - -func (d *ulReqDecoder) decodeDeepenSince() stateFn { - d.line = bytes.TrimPrefix(d.line, deepenSince) - - var secs int64 - secs, d.err = strconv.ParseInt(string(d.line), 10, 64) - if d.err != nil { - return nil - } - t := time.Unix(secs, 0).UTC() - d.data.Depth = DepthSince(t) - - return d.decodeFlush -} - -func (d *ulReqDecoder) decodeDeepenReference() stateFn { - d.line = bytes.TrimPrefix(d.line, deepenReference) - - d.data.Depth = DepthReference(string(d.line)) - - return d.decodeFlush -} - -func (d *ulReqDecoder) decodeFlush() stateFn { - if ok := d.nextLine(); !ok { - return nil - } - - if len(d.line) != 0 { - d.err = fmt.Errorf("unexpected payload while expecting a flush-pkt: %q", d.line) - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/ulreq_encode.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/ulreq_encode.go deleted file mode 100644 index 3507a23cd9e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/ulreq_encode.go +++ /dev/null @@ -1,156 +0,0 @@ -package packp - -import ( - "bytes" - "fmt" - "io" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" -) - -// Encode writes the UlReq encoding of u to the stream. -// -// All the payloads will end with a newline character. Wants and -// shallows are sorted alphabetically. A depth of 0 means no depth -// request is sent. -func (req *UploadRequest) Encode(w io.Writer) error { - e := newUlReqEncoder(w) - return e.Encode(req) -} - -type ulReqEncoder struct { - pe *pktline.Encoder // where to write the encoded data - data *UploadRequest // the data to encode - err error // sticky error -} - -func newUlReqEncoder(w io.Writer) *ulReqEncoder { - return &ulReqEncoder{ - pe: pktline.NewEncoder(w), - } -} - -func (e *ulReqEncoder) Encode(v *UploadRequest) error { - e.data = v - - if len(v.Wants) == 0 { - return fmt.Errorf("empty wants provided") - } - - plumbing.HashesSort(e.data.Wants) - for state := e.encodeFirstWant; state != nil; { - state = state() - } - - return e.err -} - -func (e *ulReqEncoder) encodeFirstWant() stateFn { - var err error - if e.data.Capabilities.IsEmpty() { - err = e.pe.Encodef("want %s\n", e.data.Wants[0]) - } else { - err = e.pe.Encodef( - "want %s %s\n", - e.data.Wants[0], - e.data.Capabilities.String(), - ) - } - - if err != nil { - e.err = fmt.Errorf("encoding first want line: %s", err) - return nil - } - - return e.encodeAdditionalWants -} - -func (e *ulReqEncoder) encodeAdditionalWants() stateFn { - last := e.data.Wants[0] - for _, w := range e.data.Wants[1:] { - if bytes.Equal(last[:], w[:]) { - continue - } - - if err := e.pe.Encodef("want %s\n", w); err != nil { - e.err = fmt.Errorf("encoding want %q: %s", w, err) - return nil - } - - last = w - } - - return e.encodeShallows -} - -func (e *ulReqEncoder) encodeShallows() stateFn { - plumbing.HashesSort(e.data.Shallows) - - var last plumbing.Hash - for _, s := range e.data.Shallows { - if bytes.Equal(last[:], s[:]) { - continue - } - - if err := e.pe.Encodef("shallow %s\n", s); err != nil { - e.err = fmt.Errorf("encoding shallow %q: %s", s, err) - return nil - } - - last = s - } - - return e.encodeDepth -} - -func (e *ulReqEncoder) encodeDepth() stateFn { - switch depth := e.data.Depth.(type) { - case DepthCommits: - if depth != 0 { - commits := int(depth) - if err := e.pe.Encodef("deepen %d\n", commits); err != nil { - e.err = fmt.Errorf("encoding depth %d: %s", depth, err) - return nil - } - } - case DepthSince: - when := time.Time(depth).UTC() - if err := e.pe.Encodef("deepen-since %d\n", when.Unix()); err != nil { - e.err = fmt.Errorf("encoding depth %s: %s", when, err) - return nil - } - case DepthReference: - reference := string(depth) - if err := e.pe.Encodef("deepen-not %s\n", reference); err != nil { - e.err = fmt.Errorf("encoding depth %s: %s", reference, err) - return nil - } - default: - e.err = fmt.Errorf("unsupported depth type") - return nil - } - - return e.encodeFilter -} - -func (e *ulReqEncoder) encodeFilter() stateFn { - if filter := e.data.Filter; filter != "" { - if err := e.pe.Encodef("filter %s\n", filter); err != nil { - e.err = fmt.Errorf("encoding filter %s: %s", filter, err) - return nil - } - } - - return e.encodeFlush -} - -func (e *ulReqEncoder) encodeFlush() stateFn { - if err := e.pe.Flush(); err != nil { - e.err = fmt.Errorf("encoding flush-pkt: %s", err) - return nil - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/updreq.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/updreq.go deleted file mode 100644 index cca6fcf0ee1..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/updreq.go +++ /dev/null @@ -1,128 +0,0 @@ -package packp - -import ( - "errors" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband" -) - -var ( - ErrEmptyCommands = errors.New("commands cannot be empty") - ErrMalformedCommand = errors.New("malformed command") -) - -// ReferenceUpdateRequest values represent reference upload requests. -// Values from this type are not zero-value safe, use the New function instead. -type ReferenceUpdateRequest struct { - Capabilities *capability.List - Commands []*Command - Options []*Option - Shallow *plumbing.Hash - // Packfile contains an optional packfile reader. - Packfile io.ReadCloser - - // Progress receives sideband progress messages from the server - Progress sideband.Progress -} - -// New returns a pointer to a new ReferenceUpdateRequest value. -func NewReferenceUpdateRequest() *ReferenceUpdateRequest { - return &ReferenceUpdateRequest{ - // TODO: Add support for push-cert - Capabilities: capability.NewList(), - Commands: nil, - } -} - -// NewReferenceUpdateRequestFromCapabilities returns a pointer to a new -// ReferenceUpdateRequest value, the request capabilities are filled with the -// most optimal ones, based on the adv value (advertised capabilities), the -// ReferenceUpdateRequest contains no commands -// -// It does set the following capabilities: -// - agent -// - report-status -// - ofs-delta -// - ref-delta -// - delete-refs -// It leaves up to the user to add the following capabilities later: -// - atomic -// - ofs-delta -// - side-band -// - side-band-64k -// - quiet -// - push-cert -func NewReferenceUpdateRequestFromCapabilities(adv *capability.List) *ReferenceUpdateRequest { - r := NewReferenceUpdateRequest() - - if adv.Supports(capability.Agent) { - r.Capabilities.Set(capability.Agent, capability.DefaultAgent()) - } - - if adv.Supports(capability.ReportStatus) { - r.Capabilities.Set(capability.ReportStatus) - } - - return r -} - -func (req *ReferenceUpdateRequest) validate() error { - if len(req.Commands) == 0 { - return ErrEmptyCommands - } - - for _, c := range req.Commands { - if err := c.validate(); err != nil { - return err - } - } - - return nil -} - -type Action string - -const ( - Create Action = "create" - Update Action = "update" - Delete Action = "delete" - Invalid Action = "invalid" -) - -type Command struct { - Name plumbing.ReferenceName - Old plumbing.Hash - New plumbing.Hash -} - -func (c *Command) Action() Action { - if c.Old == plumbing.ZeroHash && c.New == plumbing.ZeroHash { - return Invalid - } - - if c.Old == plumbing.ZeroHash { - return Create - } - - if c.New == plumbing.ZeroHash { - return Delete - } - - return Update -} - -func (c *Command) validate() error { - if c.Action() == Invalid { - return ErrMalformedCommand - } - - return nil -} - -type Option struct { - Key string - Value string -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/updreq_decode.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/updreq_decode.go deleted file mode 100644 index ceff5298b07..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/updreq_decode.go +++ /dev/null @@ -1,249 +0,0 @@ -package packp - -import ( - "bytes" - "encoding/hex" - "errors" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" -) - -var ( - shallowLineLength = len(shallow) + hashSize - minCommandLength = hashSize*2 + 2 + 1 - minCommandAndCapsLength = minCommandLength + 1 -) - -var ( - ErrEmpty = errors.New("empty update-request message") - errNoCommands = errors.New("unexpected EOF before any command") - errMissingCapabilitiesDelimiter = errors.New("capabilities delimiter not found") -) - -func errMalformedRequest(reason string) error { - return fmt.Errorf("malformed request: %s", reason) -} - -func errInvalidHashSize(got int) error { - return fmt.Errorf("invalid hash size: expected %d, got %d", - hashSize, got) -} - -func errInvalidHash(err error) error { - return fmt.Errorf("invalid hash: %s", err.Error()) -} - -func errInvalidShallowLineLength(got int) error { - return errMalformedRequest(fmt.Sprintf( - "invalid shallow line length: expected %d, got %d", - shallowLineLength, got)) -} - -func errInvalidCommandCapabilitiesLineLength(got int) error { - return errMalformedRequest(fmt.Sprintf( - "invalid command and capabilities line length: expected at least %d, got %d", - minCommandAndCapsLength, got)) -} - -func errInvalidCommandLineLength(got int) error { - return errMalformedRequest(fmt.Sprintf( - "invalid command line length: expected at least %d, got %d", - minCommandLength, got)) -} - -func errInvalidShallowObjId(err error) error { - return errMalformedRequest( - fmt.Sprintf("invalid shallow object id: %s", err.Error())) -} - -func errInvalidOldObjId(err error) error { - return errMalformedRequest( - fmt.Sprintf("invalid old object id: %s", err.Error())) -} - -func errInvalidNewObjId(err error) error { - return errMalformedRequest( - fmt.Sprintf("invalid new object id: %s", err.Error())) -} - -func errMalformedCommand(err error) error { - return errMalformedRequest(fmt.Sprintf( - "malformed command: %s", err.Error())) -} - -// Decode reads the next update-request message form the reader and wr -func (req *ReferenceUpdateRequest) Decode(r io.Reader) error { - var rc io.ReadCloser - var ok bool - rc, ok = r.(io.ReadCloser) - if !ok { - rc = io.NopCloser(r) - } - - d := &updReqDecoder{r: rc, s: pktline.NewScanner(r)} - return d.Decode(req) -} - -type updReqDecoder struct { - r io.ReadCloser - s *pktline.Scanner - req *ReferenceUpdateRequest -} - -func (d *updReqDecoder) Decode(req *ReferenceUpdateRequest) error { - d.req = req - funcs := []func() error{ - d.scanLine, - d.decodeShallow, - d.decodeCommandAndCapabilities, - d.decodeCommands, - d.setPackfile, - req.validate, - } - - for _, f := range funcs { - if err := f(); err != nil { - return err - } - } - - return nil -} - -func (d *updReqDecoder) scanLine() error { - if ok := d.s.Scan(); !ok { - return d.scanErrorOr(ErrEmpty) - } - - return nil -} - -func (d *updReqDecoder) decodeShallow() error { - b := d.s.Bytes() - - if !bytes.HasPrefix(b, shallowNoSp) { - return nil - } - - if len(b) != shallowLineLength { - return errInvalidShallowLineLength(len(b)) - } - - h, err := parseHash(string(b[len(shallow):])) - if err != nil { - return errInvalidShallowObjId(err) - } - - if ok := d.s.Scan(); !ok { - return d.scanErrorOr(errNoCommands) - } - - d.req.Shallow = &h - - return nil -} - -func (d *updReqDecoder) decodeCommands() error { - for { - b := d.s.Bytes() - if bytes.Equal(b, pktline.Flush) { - return nil - } - - c, err := parseCommand(b) - if err != nil { - return err - } - - d.req.Commands = append(d.req.Commands, c) - - if ok := d.s.Scan(); !ok { - return d.s.Err() - } - } -} - -func (d *updReqDecoder) decodeCommandAndCapabilities() error { - b := d.s.Bytes() - i := bytes.IndexByte(b, 0) - if i == -1 { - return errMissingCapabilitiesDelimiter - } - - if len(b) < minCommandAndCapsLength { - return errInvalidCommandCapabilitiesLineLength(len(b)) - } - - cmd, err := parseCommand(b[:i]) - if err != nil { - return err - } - - d.req.Commands = append(d.req.Commands, cmd) - - if err := d.req.Capabilities.Decode(b[i+1:]); err != nil { - return err - } - - if err := d.scanLine(); err != nil { - return err - } - - return nil -} - -func (d *updReqDecoder) setPackfile() error { - d.req.Packfile = d.r - - return nil -} - -func parseCommand(b []byte) (*Command, error) { - if len(b) < minCommandLength { - return nil, errInvalidCommandLineLength(len(b)) - } - - var ( - os, ns string - n plumbing.ReferenceName - ) - if _, err := fmt.Sscanf(string(b), "%s %s %s", &os, &ns, &n); err != nil { - return nil, errMalformedCommand(err) - } - - oh, err := parseHash(os) - if err != nil { - return nil, errInvalidOldObjId(err) - } - - nh, err := parseHash(ns) - if err != nil { - return nil, errInvalidNewObjId(err) - } - - return &Command{Old: oh, New: nh, Name: n}, nil -} - -func parseHash(s string) (plumbing.Hash, error) { - if len(s) != hashSize { - return plumbing.ZeroHash, errInvalidHashSize(len(s)) - } - - if _, err := hex.DecodeString(s); err != nil { - return plumbing.ZeroHash, errInvalidHash(err) - } - - h := plumbing.NewHash(s) - return h, nil -} - -func (d *updReqDecoder) scanErrorOr(origErr error) error { - if err := d.s.Err(); err != nil { - return err - } - - return origErr -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/updreq_encode.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/updreq_encode.go deleted file mode 100644 index a6d527a03d2..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/updreq_encode.go +++ /dev/null @@ -1,89 +0,0 @@ -package packp - -import ( - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" -) - -// Encode writes the ReferenceUpdateRequest encoding to the stream. -func (req *ReferenceUpdateRequest) Encode(w io.Writer) error { - if err := req.validate(); err != nil { - return err - } - - e := pktline.NewEncoder(w) - - if err := req.encodeShallow(e, req.Shallow); err != nil { - return err - } - - if err := req.encodeCommands(e, req.Commands, req.Capabilities); err != nil { - return err - } - - if req.Capabilities.Supports(capability.PushOptions) { - if err := req.encodeOptions(e, req.Options); err != nil { - return err - } - } - - if req.Packfile != nil { - if _, err := io.Copy(w, req.Packfile); err != nil { - return err - } - - return req.Packfile.Close() - } - - return nil -} - -func (req *ReferenceUpdateRequest) encodeShallow(e *pktline.Encoder, - h *plumbing.Hash) error { - - if h == nil { - return nil - } - - objId := []byte(h.String()) - return e.Encodef("%s%s", shallow, objId) -} - -func (req *ReferenceUpdateRequest) encodeCommands(e *pktline.Encoder, - cmds []*Command, cap *capability.List) error { - - if err := e.Encodef("%s\x00%s", - formatCommand(cmds[0]), cap.String()); err != nil { - return err - } - - for _, cmd := range cmds[1:] { - if err := e.Encodef(formatCommand(cmd)); err != nil { - return err - } - } - - return e.Flush() -} - -func formatCommand(cmd *Command) string { - o := cmd.Old.String() - n := cmd.New.String() - return fmt.Sprintf("%s %s %s", o, n, cmd.Name) -} - -func (req *ReferenceUpdateRequest) encodeOptions(e *pktline.Encoder, - opts []*Option) error { - - for _, opt := range opts { - if err := e.Encodef("%s=%s", opt.Key, opt.Value); err != nil { - return err - } - } - - return e.Flush() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/uppackreq.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/uppackreq.go deleted file mode 100644 index fca1fe9a86a..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/uppackreq.go +++ /dev/null @@ -1,98 +0,0 @@ -package packp - -import ( - "bytes" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" -) - -// UploadPackRequest represents a upload-pack request. -// Zero-value is not safe, use NewUploadPackRequest instead. -type UploadPackRequest struct { - UploadRequest - UploadHaves -} - -// NewUploadPackRequest creates a new UploadPackRequest and returns a pointer. -func NewUploadPackRequest() *UploadPackRequest { - ur := NewUploadRequest() - return &UploadPackRequest{ - UploadHaves: UploadHaves{}, - UploadRequest: *ur, - } -} - -// NewUploadPackRequestFromCapabilities creates a new UploadPackRequest and -// returns a pointer. The request capabilities are filled with the most optimal -// ones, based on the adv value (advertised capabilities), the UploadPackRequest -// it has no wants, haves or shallows and an infinite depth -func NewUploadPackRequestFromCapabilities(adv *capability.List) *UploadPackRequest { - ur := NewUploadRequestFromCapabilities(adv) - return &UploadPackRequest{ - UploadHaves: UploadHaves{}, - UploadRequest: *ur, - } -} - -// IsEmpty returns whether a request is empty - it is empty if Haves are contained -// in the Wants, or if Wants length is zero, and we don't have any shallows -func (r *UploadPackRequest) IsEmpty() bool { - return isSubset(r.Wants, r.Haves) && len(r.Shallows) == 0 -} - -func isSubset(needle []plumbing.Hash, haystack []plumbing.Hash) bool { - for _, h := range needle { - found := false - for _, oh := range haystack { - if h == oh { - found = true - break - } - } - - if !found { - return false - } - } - - return true -} - -// UploadHaves is a message to signal the references that a client has in a -// upload-pack. Do not use this directly. Use UploadPackRequest request instead. -type UploadHaves struct { - Haves []plumbing.Hash -} - -// Encode encodes the UploadHaves into the Writer. If flush is true, a flush -// command will be encoded at the end of the writer content. -func (u *UploadHaves) Encode(w io.Writer, flush bool) error { - e := pktline.NewEncoder(w) - - plumbing.HashesSort(u.Haves) - - var last plumbing.Hash - for _, have := range u.Haves { - if bytes.Equal(last[:], have[:]) { - continue - } - - if err := e.Encodef("have %s\n", have); err != nil { - return fmt.Errorf("sending haves for %q: %s", have, err) - } - - last = have - } - - if flush && len(u.Haves) != 0 { - if err := e.Flush(); err != nil { - return fmt.Errorf("sending flush-pkt after haves: %s", err) - } - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/uppackresp.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/uppackresp.go deleted file mode 100644 index 4682e555f9a..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/uppackresp.go +++ /dev/null @@ -1,108 +0,0 @@ -package packp - -import ( - "errors" - "io" - - "bufio" - - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -// ErrUploadPackResponseNotDecoded is returned if Read is called without -// decoding first -var ErrUploadPackResponseNotDecoded = errors.New("upload-pack-response should be decoded") - -// UploadPackResponse contains all the information responded by the upload-pack -// service, the response implements io.ReadCloser that allows to read the -// packfile directly from it. -type UploadPackResponse struct { - ShallowUpdate - ServerResponse - - r io.ReadCloser - isShallow bool - isMultiACK bool -} - -// NewUploadPackResponse create a new UploadPackResponse instance, the request -// being responded by the response is required. -func NewUploadPackResponse(req *UploadPackRequest) *UploadPackResponse { - isShallow := !req.Depth.IsZero() - isMultiACK := req.Capabilities.Supports(capability.MultiACK) || - req.Capabilities.Supports(capability.MultiACKDetailed) - - return &UploadPackResponse{ - isShallow: isShallow, - isMultiACK: isMultiACK, - } -} - -// NewUploadPackResponseWithPackfile creates a new UploadPackResponse instance, -// and sets its packfile reader. -func NewUploadPackResponseWithPackfile(req *UploadPackRequest, - pf io.ReadCloser) *UploadPackResponse { - - r := NewUploadPackResponse(req) - r.r = pf - return r -} - -// Decode decodes all the responses sent by upload-pack service into the struct -// and prepares it to read the packfile using the Read method -func (r *UploadPackResponse) Decode(reader io.ReadCloser) error { - buf := bufio.NewReader(reader) - - if r.isShallow { - if err := r.ShallowUpdate.Decode(buf); err != nil { - return err - } - } - - if err := r.ServerResponse.Decode(buf, r.isMultiACK); err != nil { - return err - } - - // now the reader is ready to read the packfile content - r.r = ioutil.NewReadCloser(buf, reader) - - return nil -} - -// Encode encodes an UploadPackResponse. -func (r *UploadPackResponse) Encode(w io.Writer) (err error) { - if r.isShallow { - if err := r.ShallowUpdate.Encode(w); err != nil { - return err - } - } - - if err := r.ServerResponse.Encode(w, r.isMultiACK); err != nil { - return err - } - - defer ioutil.CheckClose(r.r, &err) - _, err = io.Copy(w, r.r) - return err -} - -// Read reads the packfile data, if the request was done with any Sideband -// capability the content read should be demultiplexed. If the methods wasn't -// called before the ErrUploadPackResponseNotDecoded will be return -func (r *UploadPackResponse) Read(p []byte) (int, error) { - if r.r == nil { - return 0, ErrUploadPackResponseNotDecoded - } - - return r.r.Read(p) -} - -// Close the underlying reader, if any -func (r *UploadPackResponse) Close() error { - if r.r == nil { - return nil - } - - return r.r.Close() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/reference.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/reference.go deleted file mode 100644 index 4daa3416496..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/reference.go +++ /dev/null @@ -1,315 +0,0 @@ -package plumbing - -import ( - "errors" - "fmt" - "regexp" - "strings" -) - -const ( - refPrefix = "refs/" - refHeadPrefix = refPrefix + "heads/" - refTagPrefix = refPrefix + "tags/" - refRemotePrefix = refPrefix + "remotes/" - refNotePrefix = refPrefix + "notes/" - symrefPrefix = "ref: " -) - -// RefRevParseRules are a set of rules to parse references into short names, or expand into a full reference. -// These are the same rules as used by git in shorten_unambiguous_ref and expand_ref. -// See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417 -var RefRevParseRules = []string{ - "%s", - "refs/%s", - "refs/tags/%s", - "refs/heads/%s", - "refs/remotes/%s", - "refs/remotes/%s/HEAD", -} - -var ( - ErrReferenceNotFound = errors.New("reference not found") - - // ErrInvalidReferenceName is returned when a reference name is invalid. - ErrInvalidReferenceName = errors.New("invalid reference name") -) - -// ReferenceType reference type's -type ReferenceType int8 - -const ( - InvalidReference ReferenceType = 0 - HashReference ReferenceType = 1 - SymbolicReference ReferenceType = 2 -) - -func (r ReferenceType) String() string { - switch r { - case InvalidReference: - return "invalid-reference" - case HashReference: - return "hash-reference" - case SymbolicReference: - return "symbolic-reference" - } - - return "" -} - -// ReferenceName reference name's -type ReferenceName string - -// NewBranchReferenceName returns a reference name describing a branch based on -// his short name. -func NewBranchReferenceName(name string) ReferenceName { - return ReferenceName(refHeadPrefix + name) -} - -// NewNoteReferenceName returns a reference name describing a note based on his -// short name. -func NewNoteReferenceName(name string) ReferenceName { - return ReferenceName(refNotePrefix + name) -} - -// NewRemoteReferenceName returns a reference name describing a remote branch -// based on his short name and the remote name. -func NewRemoteReferenceName(remote, name string) ReferenceName { - return ReferenceName(refRemotePrefix + fmt.Sprintf("%s/%s", remote, name)) -} - -// NewRemoteHEADReferenceName returns a reference name describing a the HEAD -// branch of a remote. -func NewRemoteHEADReferenceName(remote string) ReferenceName { - return ReferenceName(refRemotePrefix + fmt.Sprintf("%s/%s", remote, HEAD)) -} - -// NewTagReferenceName returns a reference name describing a tag based on short -// his name. -func NewTagReferenceName(name string) ReferenceName { - return ReferenceName(refTagPrefix + name) -} - -// IsBranch check if a reference is a branch -func (r ReferenceName) IsBranch() bool { - return strings.HasPrefix(string(r), refHeadPrefix) -} - -// IsNote check if a reference is a note -func (r ReferenceName) IsNote() bool { - return strings.HasPrefix(string(r), refNotePrefix) -} - -// IsRemote check if a reference is a remote -func (r ReferenceName) IsRemote() bool { - return strings.HasPrefix(string(r), refRemotePrefix) -} - -// IsTag check if a reference is a tag -func (r ReferenceName) IsTag() bool { - return strings.HasPrefix(string(r), refTagPrefix) -} - -func (r ReferenceName) String() string { - return string(r) -} - -// Short returns the short name of a ReferenceName -func (r ReferenceName) Short() string { - s := string(r) - res := s - for _, format := range RefRevParseRules[1:] { - _, err := fmt.Sscanf(s, format, &res) - if err == nil { - continue - } - } - - return res -} - -var ( - ctrlSeqs = regexp.MustCompile(`[\000-\037\177]`) -) - -// Validate validates a reference name. -// This follows the git-check-ref-format rules. -// See https://git-scm.com/docs/git-check-ref-format -// -// It is important to note that this function does not check if the reference -// exists in the repository. -// It only checks if the reference name is valid. -// This functions does not support the --refspec-pattern, --normalize, and -// --allow-onelevel options. -// -// Git imposes the following rules on how references are named: -// -// 1. They can include slash / for hierarchical (directory) grouping, but no -// slash-separated component can begin with a dot . or end with the -// sequence .lock. -// 2. They must contain at least one /. This enforces the presence of a -// category like heads/, tags/ etc. but the actual names are not -// restricted. If the --allow-onelevel option is used, this rule is -// waived. -// 3. They cannot have two consecutive dots .. anywhere. -// 4. They cannot have ASCII control characters (i.e. bytes whose values are -// lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : -// anywhere. -// 5. They cannot have question-mark ?, asterisk *, or open bracket [ -// anywhere. See the --refspec-pattern option below for an exception to this -// rule. -// 6. They cannot begin or end with a slash / or contain multiple consecutive -// slashes (see the --normalize option below for an exception to this rule). -// 7. They cannot end with a dot .. -// 8. They cannot contain a sequence @{. -// 9. They cannot be the single character @. -// 10. They cannot contain a \. -func (r ReferenceName) Validate() error { - s := string(r) - if len(s) == 0 { - return ErrInvalidReferenceName - } - - // HEAD is a special case - if r == HEAD { - return nil - } - - // rule 7 - if strings.HasSuffix(s, ".") { - return ErrInvalidReferenceName - } - - // rule 2 - parts := strings.Split(s, "/") - if len(parts) < 2 { - return ErrInvalidReferenceName - } - - isBranch := r.IsBranch() - isTag := r.IsTag() - for i, part := range parts { - // rule 6 - if len(part) == 0 { - return ErrInvalidReferenceName - } - - if strings.HasPrefix(part, ".") || // rule 1 - strings.Contains(part, "..") || // rule 3 - ctrlSeqs.MatchString(part) || // rule 4 - strings.ContainsAny(part, "~^:?*[ \t\n") || // rule 4 & 5 - strings.Contains(part, "@{") || // rule 8 - part == "@" || // rule 9 - strings.Contains(part, "\\") || // rule 10 - strings.HasSuffix(part, ".lock") { // rule 1 - return ErrInvalidReferenceName - } - - if (isBranch || isTag) && strings.HasPrefix(part, "-") && (i == 2) { // branches & tags can't start with - - return ErrInvalidReferenceName - } - } - - return nil -} - -const ( - HEAD ReferenceName = "HEAD" - Master ReferenceName = "refs/heads/master" - Main ReferenceName = "refs/heads/main" -) - -// Reference is a representation of git reference -type Reference struct { - t ReferenceType - n ReferenceName - h Hash - target ReferenceName -} - -// NewReferenceFromStrings creates a reference from name and target as string, -// the resulting reference can be a SymbolicReference or a HashReference base -// on the target provided -func NewReferenceFromStrings(name, target string) *Reference { - n := ReferenceName(name) - - if strings.HasPrefix(target, symrefPrefix) { - target := ReferenceName(target[len(symrefPrefix):]) - return NewSymbolicReference(n, target) - } - - return NewHashReference(n, NewHash(target)) -} - -// NewSymbolicReference creates a new SymbolicReference reference -func NewSymbolicReference(n, target ReferenceName) *Reference { - return &Reference{ - t: SymbolicReference, - n: n, - target: target, - } -} - -// NewHashReference creates a new HashReference reference -func NewHashReference(n ReferenceName, h Hash) *Reference { - return &Reference{ - t: HashReference, - n: n, - h: h, - } -} - -// Type returns the type of a reference -func (r *Reference) Type() ReferenceType { - return r.t -} - -// Name returns the name of a reference -func (r *Reference) Name() ReferenceName { - return r.n -} - -// Hash returns the hash of a hash reference -func (r *Reference) Hash() Hash { - return r.h -} - -// Target returns the target of a symbolic reference -func (r *Reference) Target() ReferenceName { - return r.target -} - -// Strings dump a reference as a [2]string -func (r *Reference) Strings() [2]string { - var o [2]string - o[0] = r.Name().String() - - switch r.Type() { - case HashReference: - o[1] = r.Hash().String() - case SymbolicReference: - o[1] = symrefPrefix + r.Target().String() - } - - return o -} - -func (r *Reference) String() string { - ref := "" - switch r.Type() { - case HashReference: - ref = r.Hash().String() - case SymbolicReference: - ref = symrefPrefix + r.Target().String() - default: - return "" - } - - name := r.Name().String() - var v strings.Builder - v.Grow(len(ref) + len(name) + 1) - v.WriteString(ref) - v.WriteString(" ") - v.WriteString(name) - return v.String() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/revision.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/revision.go deleted file mode 100644 index 5f053b200c0..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/revision.go +++ /dev/null @@ -1,11 +0,0 @@ -package plumbing - -// Revision represents a git revision -// to get more details about git revisions -// please check git manual page : -// https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html -type Revision string - -func (r Revision) String() string { - return string(r) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/revlist/revlist.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/revlist/revlist.go deleted file mode 100644 index 99600f5399a..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/revlist/revlist.go +++ /dev/null @@ -1,230 +0,0 @@ -// Package revlist provides support to access the ancestors of commits, in a -// similar way as the git-rev-list command. -package revlist - -import ( - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" - "github.com/jesseduffield/go-git/v5/plumbing/object" - "github.com/jesseduffield/go-git/v5/plumbing/storer" -) - -// Objects applies a complementary set. It gets all the hashes from all -// the reachable objects from the given objects. Ignore param are object hashes -// that we want to ignore on the result. All that objects must be accessible -// from the object storer. -func Objects( - s storer.EncodedObjectStorer, - objs, - ignore []plumbing.Hash, -) ([]plumbing.Hash, error) { - return ObjectsWithStorageForIgnores(s, s, objs, ignore) -} - -// ObjectsWithStorageForIgnores is the same as Objects, but a -// secondary storage layer can be provided, to be used to finding the -// full set of objects to be ignored while finding the reachable -// objects. This is useful when the main `s` storage layer is slow -// and/or remote, while the ignore list is available somewhere local. -func ObjectsWithStorageForIgnores( - s, ignoreStore storer.EncodedObjectStorer, - objs, - ignore []plumbing.Hash, -) ([]plumbing.Hash, error) { - ignore, err := objects(ignoreStore, ignore, nil, true) - if err != nil { - return nil, err - } - - return objects(s, objs, ignore, false) -} - -func objects( - s storer.EncodedObjectStorer, - objects, - ignore []plumbing.Hash, - allowMissingObjects bool, -) ([]plumbing.Hash, error) { - seen := hashListToSet(ignore) - result := make(map[plumbing.Hash]bool) - visited := make(map[plumbing.Hash]bool) - - walkerFunc := func(h plumbing.Hash) { - if !seen[h] { - result[h] = true - seen[h] = true - } - } - - for _, h := range objects { - if err := processObject(s, h, seen, visited, ignore, walkerFunc); err != nil { - if allowMissingObjects && err == plumbing.ErrObjectNotFound { - continue - } - - return nil, err - } - } - - return hashSetToList(result), nil -} - -// processObject obtains the object using the hash an process it depending of its type -func processObject( - s storer.EncodedObjectStorer, - h plumbing.Hash, - seen map[plumbing.Hash]bool, - visited map[plumbing.Hash]bool, - ignore []plumbing.Hash, - walkerFunc func(h plumbing.Hash), -) error { - if seen[h] { - return nil - } - - o, err := s.EncodedObject(plumbing.AnyObject, h) - if err != nil { - return err - } - - do, err := object.DecodeObject(s, o) - if err != nil { - return err - } - - switch do := do.(type) { - case *object.Commit: - return reachableObjects(do, seen, visited, ignore, walkerFunc) - case *object.Tree: - return iterateCommitTrees(seen, do, walkerFunc) - case *object.Tag: - walkerFunc(do.Hash) - return processObject(s, do.Target, seen, visited, ignore, walkerFunc) - case *object.Blob: - walkerFunc(do.Hash) - default: - return fmt.Errorf("object type not valid: %s. "+ - "Object reference: %s", o.Type(), o.Hash()) - } - - return nil -} - -// reachableObjects returns, using the callback function, all the reachable -// objects from the specified commit. To avoid to iterate over seen commits, -// if a commit hash is into the 'seen' set, we will not iterate all his trees -// and blobs objects. -func reachableObjects( - commit *object.Commit, - seen map[plumbing.Hash]bool, - visited map[plumbing.Hash]bool, - ignore []plumbing.Hash, - cb func(h plumbing.Hash), -) error { - i := object.NewCommitPreorderIter(commit, seen, ignore) - pending := make(map[plumbing.Hash]bool) - addPendingParents(pending, visited, commit) - for { - commit, err := i.Next() - if err == io.EOF { - break - } - - if err != nil { - return err - } - - if pending[commit.Hash] { - delete(pending, commit.Hash) - } - - addPendingParents(pending, visited, commit) - - if visited[commit.Hash] && len(pending) == 0 { - break - } - - if seen[commit.Hash] { - continue - } - - cb(commit.Hash) - - tree, err := commit.Tree() - if err != nil { - return err - } - - if err := iterateCommitTrees(seen, tree, cb); err != nil { - return err - } - } - - return nil -} - -func addPendingParents(pending, visited map[plumbing.Hash]bool, commit *object.Commit) { - for _, p := range commit.ParentHashes { - if !visited[p] { - pending[p] = true - } - } -} - -// iterateCommitTrees iterate all reachable trees from the given commit -func iterateCommitTrees( - seen map[plumbing.Hash]bool, - tree *object.Tree, - cb func(h plumbing.Hash), -) error { - if seen[tree.Hash] { - return nil - } - - cb(tree.Hash) - - treeWalker := object.NewTreeWalker(tree, true, seen) - - for { - _, e, err := treeWalker.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - if e.Mode == filemode.Submodule { - continue - } - - if seen[e.Hash] { - continue - } - - cb(e.Hash) - } - - return nil -} - -func hashSetToList(hashes map[plumbing.Hash]bool) []plumbing.Hash { - var result []plumbing.Hash - for key := range hashes { - result = append(result, key) - } - - return result -} - -func hashListToSet(hashes []plumbing.Hash) map[plumbing.Hash]bool { - result := make(map[plumbing.Hash]bool) - for _, h := range hashes { - result[h] = true - } - - return result -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/doc.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/doc.go deleted file mode 100644 index 4d4f179c618..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package storer defines the interfaces to store objects, references, etc. -package storer diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/index.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/index.go deleted file mode 100644 index 0cb5287d6a6..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/index.go +++ /dev/null @@ -1,9 +0,0 @@ -package storer - -import "github.com/jesseduffield/go-git/v5/plumbing/format/index" - -// IndexStorer generic storage of index.Index -type IndexStorer interface { - SetIndex(*index.Index) error - Index() (*index.Index, error) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/object.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/object.go deleted file mode 100644 index 876a73d4a0e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/object.go +++ /dev/null @@ -1,289 +0,0 @@ -package storer - -import ( - "errors" - "io" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing" -) - -var ( - //ErrStop is used to stop a ForEach function in an Iter - ErrStop = errors.New("stop iter") -) - -// EncodedObjectStorer generic storage of objects -type EncodedObjectStorer interface { - // NewEncodedObject returns a new plumbing.EncodedObject, the real type - // of the object can be a custom implementation or the default one, - // plumbing.MemoryObject. - NewEncodedObject() plumbing.EncodedObject - // SetEncodedObject saves an object into the storage, the object should - // be create with the NewEncodedObject, method, and file if the type is - // not supported. - SetEncodedObject(plumbing.EncodedObject) (plumbing.Hash, error) - // EncodedObject gets an object by hash with the given - // plumbing.ObjectType. Implementors should return - // (nil, plumbing.ErrObjectNotFound) if an object doesn't exist with - // both the given hash and object type. - // - // Valid plumbing.ObjectType values are CommitObject, BlobObject, TagObject, - // TreeObject and AnyObject. If plumbing.AnyObject is given, the object must - // be looked up regardless of its type. - EncodedObject(plumbing.ObjectType, plumbing.Hash) (plumbing.EncodedObject, error) - // IterObjects returns a custom EncodedObjectStorer over all the object - // on the storage. - // - // Valid plumbing.ObjectType values are CommitObject, BlobObject, TagObject, - IterEncodedObjects(plumbing.ObjectType) (EncodedObjectIter, error) - // HasEncodedObject returns ErrObjNotFound if the object doesn't - // exist. If the object does exist, it returns nil. - HasEncodedObject(plumbing.Hash) error - // EncodedObjectSize returns the plaintext size of the encoded object. - EncodedObjectSize(plumbing.Hash) (int64, error) - AddAlternate(remote string) error -} - -// DeltaObjectStorer is an EncodedObjectStorer that can return delta -// objects. -type DeltaObjectStorer interface { - // DeltaObject is the same as EncodedObject but without resolving deltas. - // Deltas will be returned as plumbing.DeltaObject instances. - DeltaObject(plumbing.ObjectType, plumbing.Hash) (plumbing.EncodedObject, error) -} - -// Transactioner is a optional method for ObjectStorer, it enables transactional read and write -// operations. -type Transactioner interface { - // Begin starts a transaction. - Begin() Transaction -} - -// LooseObjectStorer is an optional interface for managing "loose" -// objects, i.e. those not in packfiles. -type LooseObjectStorer interface { - // ForEachObjectHash iterates over all the (loose) object hashes - // in the repository without necessarily having to read those objects. - // Objects only inside pack files may be omitted. - // If ErrStop is sent the iteration is stop but no error is returned. - ForEachObjectHash(func(plumbing.Hash) error) error - // LooseObjectTime looks up the (m)time associated with the - // loose object (that is not in a pack file). Some - // implementations (e.g. without loose objects) - // always return an error. - LooseObjectTime(plumbing.Hash) (time.Time, error) - // DeleteLooseObject deletes a loose object if it exists. - DeleteLooseObject(plumbing.Hash) error -} - -// PackedObjectStorer is an optional interface for managing objects in -// packfiles. -type PackedObjectStorer interface { - // ObjectPacks returns hashes of object packs if the underlying - // implementation has pack files. - ObjectPacks() ([]plumbing.Hash, error) - // DeleteOldObjectPackAndIndex deletes an object pack and the corresponding index file if they exist. - // Deletion is only performed if the pack is older than the supplied time (or the time is zero). - DeleteOldObjectPackAndIndex(plumbing.Hash, time.Time) error -} - -// PackfileWriter is an optional method for ObjectStorer, it enables directly writing -// a packfile to storage. -type PackfileWriter interface { - // PackfileWriter returns a writer for writing a packfile to the storage - // - // If the Storer not implements PackfileWriter the objects should be written - // using the Set method. - PackfileWriter() (io.WriteCloser, error) -} - -// EncodedObjectIter is a generic closable interface for iterating over objects. -type EncodedObjectIter interface { - Next() (plumbing.EncodedObject, error) - ForEach(func(plumbing.EncodedObject) error) error - Close() -} - -// Transaction is an in-progress storage transaction. A transaction must end -// with a call to Commit or Rollback. -type Transaction interface { - SetEncodedObject(plumbing.EncodedObject) (plumbing.Hash, error) - EncodedObject(plumbing.ObjectType, plumbing.Hash) (plumbing.EncodedObject, error) - Commit() error - Rollback() error -} - -// EncodedObjectLookupIter implements EncodedObjectIter. It iterates over a -// series of object hashes and yields their associated objects by retrieving -// each one from object storage. The retrievals are lazy and only occur when the -// iterator moves forward with a call to Next(). -// -// The EncodedObjectLookupIter must be closed with a call to Close() when it is -// no longer needed. -type EncodedObjectLookupIter struct { - storage EncodedObjectStorer - series []plumbing.Hash - t plumbing.ObjectType - pos int -} - -// NewEncodedObjectLookupIter returns an object iterator given an object storage -// and a slice of object hashes. -func NewEncodedObjectLookupIter( - storage EncodedObjectStorer, t plumbing.ObjectType, series []plumbing.Hash) *EncodedObjectLookupIter { - return &EncodedObjectLookupIter{ - storage: storage, - series: series, - t: t, - } -} - -// Next returns the next object from the iterator. If the iterator has reached -// the end it will return io.EOF as an error. If the object can't be found in -// the object storage, it will return plumbing.ErrObjectNotFound as an error. -// If the object is retrieved successfully error will be nil. -func (iter *EncodedObjectLookupIter) Next() (plumbing.EncodedObject, error) { - if iter.pos >= len(iter.series) { - return nil, io.EOF - } - - hash := iter.series[iter.pos] - obj, err := iter.storage.EncodedObject(iter.t, hash) - if err == nil { - iter.pos++ - } - - return obj, err -} - -// ForEach call the cb function for each object contained on this iter until -// an error happens or the end of the iter is reached. If ErrStop is sent -// the iteration is stop but no error is returned. The iterator is closed. -func (iter *EncodedObjectLookupIter) ForEach(cb func(plumbing.EncodedObject) error) error { - return ForEachIterator(iter, cb) -} - -// Close releases any resources used by the iterator. -func (iter *EncodedObjectLookupIter) Close() { - iter.pos = len(iter.series) -} - -// EncodedObjectSliceIter implements EncodedObjectIter. It iterates over a -// series of objects stored in a slice and yields each one in turn when Next() -// is called. -// -// The EncodedObjectSliceIter must be closed with a call to Close() when it is -// no longer needed. -type EncodedObjectSliceIter struct { - series []plumbing.EncodedObject -} - -// NewEncodedObjectSliceIter returns an object iterator for the given slice of -// objects. -func NewEncodedObjectSliceIter(series []plumbing.EncodedObject) *EncodedObjectSliceIter { - return &EncodedObjectSliceIter{ - series: series, - } -} - -// Next returns the next object from the iterator. If the iterator has reached -// the end it will return io.EOF as an error. If the object is retrieved -// successfully error will be nil. -func (iter *EncodedObjectSliceIter) Next() (plumbing.EncodedObject, error) { - if len(iter.series) == 0 { - return nil, io.EOF - } - - obj := iter.series[0] - iter.series = iter.series[1:] - - return obj, nil -} - -// ForEach call the cb function for each object contained on this iter until -// an error happens or the end of the iter is reached. If ErrStop is sent -// the iteration is stop but no error is returned. The iterator is closed. -func (iter *EncodedObjectSliceIter) ForEach(cb func(plumbing.EncodedObject) error) error { - return ForEachIterator(iter, cb) -} - -// Close releases any resources used by the iterator. -func (iter *EncodedObjectSliceIter) Close() { - iter.series = []plumbing.EncodedObject{} -} - -// MultiEncodedObjectIter implements EncodedObjectIter. It iterates over several -// EncodedObjectIter, -// -// The MultiObjectIter must be closed with a call to Close() when it is no -// longer needed. -type MultiEncodedObjectIter struct { - iters []EncodedObjectIter -} - -// NewMultiEncodedObjectIter returns an object iterator for the given slice of -// EncodedObjectIters. -func NewMultiEncodedObjectIter(iters []EncodedObjectIter) EncodedObjectIter { - return &MultiEncodedObjectIter{iters: iters} -} - -// Next returns the next object from the iterator, if one iterator reach io.EOF -// is removed and the next one is used. -func (iter *MultiEncodedObjectIter) Next() (plumbing.EncodedObject, error) { - if len(iter.iters) == 0 { - return nil, io.EOF - } - - obj, err := iter.iters[0].Next() - if err == io.EOF { - iter.iters[0].Close() - iter.iters = iter.iters[1:] - return iter.Next() - } - - return obj, err -} - -// ForEach call the cb function for each object contained on this iter until -// an error happens or the end of the iter is reached. If ErrStop is sent -// the iteration is stop but no error is returned. The iterator is closed. -func (iter *MultiEncodedObjectIter) ForEach(cb func(plumbing.EncodedObject) error) error { - return ForEachIterator(iter, cb) -} - -// Close releases any resources used by the iterator. -func (iter *MultiEncodedObjectIter) Close() { - for _, i := range iter.iters { - i.Close() - } -} - -type bareIterator interface { - Next() (plumbing.EncodedObject, error) - Close() -} - -// ForEachIterator is a helper function to build iterators without need to -// rewrite the same ForEach function each time. -func ForEachIterator(iter bareIterator, cb func(plumbing.EncodedObject) error) error { - defer iter.Close() - for { - obj, err := iter.Next() - if err != nil { - if err == io.EOF { - return nil - } - - return err - } - - if err := cb(obj); err != nil { - if err == ErrStop { - return nil - } - - return err - } - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/reference.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/reference.go deleted file mode 100644 index 3d0699d77e9..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/reference.go +++ /dev/null @@ -1,240 +0,0 @@ -package storer - -import ( - "errors" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" -) - -const MaxResolveRecursion = 1024 - -// ErrMaxResolveRecursion is returned by ResolveReference is MaxResolveRecursion -// is exceeded -var ErrMaxResolveRecursion = errors.New("max. recursion level reached") - -// ReferenceStorer is a generic storage of references. -type ReferenceStorer interface { - SetReference(*plumbing.Reference) error - // CheckAndSetReference sets the reference `new`, but if `old` is - // not `nil`, it first checks that the current stored value for - // `old.Name()` matches the given reference value in `old`. If - // not, it returns an error and doesn't update `new`. - CheckAndSetReference(new, old *plumbing.Reference) error - Reference(plumbing.ReferenceName) (*plumbing.Reference, error) - IterReferences() (ReferenceIter, error) - RemoveReference(plumbing.ReferenceName) error - CountLooseRefs() (int, error) - PackRefs() error -} - -// ReferenceIter is a generic closable interface for iterating over references. -type ReferenceIter interface { - Next() (*plumbing.Reference, error) - ForEach(func(*plumbing.Reference) error) error - Close() -} - -type referenceFilteredIter struct { - ff func(r *plumbing.Reference) bool - iter ReferenceIter -} - -// NewReferenceFilteredIter returns a reference iterator for the given reference -// Iterator. This iterator will iterate only references that accomplish the -// provided function. -func NewReferenceFilteredIter( - ff func(r *plumbing.Reference) bool, iter ReferenceIter) ReferenceIter { - return &referenceFilteredIter{ff, iter} -} - -// Next returns the next reference from the iterator. If the iterator has reached -// the end it will return io.EOF as an error. -func (iter *referenceFilteredIter) Next() (*plumbing.Reference, error) { - for { - r, err := iter.iter.Next() - if err != nil { - return nil, err - } - - if iter.ff(r) { - return r, nil - } - - continue - } -} - -// ForEach call the cb function for each reference contained on this iter until -// an error happens or the end of the iter is reached. If ErrStop is sent -// the iteration is stopped but no error is returned. The iterator is closed. -func (iter *referenceFilteredIter) ForEach(cb func(*plumbing.Reference) error) error { - defer iter.Close() - for { - r, err := iter.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - if err := cb(r); err != nil { - if err == ErrStop { - break - } - - return err - } - } - - return nil -} - -// Close releases any resources used by the iterator. -func (iter *referenceFilteredIter) Close() { - iter.iter.Close() -} - -// ReferenceSliceIter implements ReferenceIter. It iterates over a series of -// references stored in a slice and yields each one in turn when Next() is -// called. -// -// The ReferenceSliceIter must be closed with a call to Close() when it is no -// longer needed. -type ReferenceSliceIter struct { - series []*plumbing.Reference - pos int -} - -// NewReferenceSliceIter returns a reference iterator for the given slice of -// objects. -func NewReferenceSliceIter(series []*plumbing.Reference) ReferenceIter { - return &ReferenceSliceIter{ - series: series, - } -} - -// Next returns the next reference from the iterator. If the iterator has -// reached the end it will return io.EOF as an error. -func (iter *ReferenceSliceIter) Next() (*plumbing.Reference, error) { - if iter.pos >= len(iter.series) { - return nil, io.EOF - } - - obj := iter.series[iter.pos] - iter.pos++ - return obj, nil -} - -// ForEach call the cb function for each reference contained on this iter until -// an error happens or the end of the iter is reached. If ErrStop is sent -// the iteration is stop but no error is returned. The iterator is closed. -func (iter *ReferenceSliceIter) ForEach(cb func(*plumbing.Reference) error) error { - return forEachReferenceIter(iter, cb) -} - -type bareReferenceIterator interface { - Next() (*plumbing.Reference, error) - Close() -} - -func forEachReferenceIter(iter bareReferenceIterator, cb func(*plumbing.Reference) error) error { - defer iter.Close() - for { - obj, err := iter.Next() - if err != nil { - if err == io.EOF { - return nil - } - - return err - } - - if err := cb(obj); err != nil { - if err == ErrStop { - return nil - } - - return err - } - } -} - -// Close releases any resources used by the iterator. -func (iter *ReferenceSliceIter) Close() { - iter.pos = len(iter.series) -} - -// MultiReferenceIter implements ReferenceIter. It iterates over several -// ReferenceIter, -// -// The MultiReferenceIter must be closed with a call to Close() when it is no -// longer needed. -type MultiReferenceIter struct { - iters []ReferenceIter -} - -// NewMultiReferenceIter returns an reference iterator for the given slice of -// EncodedObjectIters. -func NewMultiReferenceIter(iters []ReferenceIter) ReferenceIter { - return &MultiReferenceIter{iters: iters} -} - -// Next returns the next reference from the iterator, if one iterator reach -// io.EOF is removed and the next one is used. -func (iter *MultiReferenceIter) Next() (*plumbing.Reference, error) { - if len(iter.iters) == 0 { - return nil, io.EOF - } - - obj, err := iter.iters[0].Next() - if err == io.EOF { - iter.iters[0].Close() - iter.iters = iter.iters[1:] - return iter.Next() - } - - return obj, err -} - -// ForEach call the cb function for each reference contained on this iter until -// an error happens or the end of the iter is reached. If ErrStop is sent -// the iteration is stop but no error is returned. The iterator is closed. -func (iter *MultiReferenceIter) ForEach(cb func(*plumbing.Reference) error) error { - return forEachReferenceIter(iter, cb) -} - -// Close releases any resources used by the iterator. -func (iter *MultiReferenceIter) Close() { - for _, i := range iter.iters { - i.Close() - } -} - -// ResolveReference resolves a SymbolicReference to a HashReference. -func ResolveReference(s ReferenceStorer, n plumbing.ReferenceName) (*plumbing.Reference, error) { - r, err := s.Reference(n) - if err != nil || r == nil { - return r, err - } - return resolveReference(s, r, 0) -} - -func resolveReference(s ReferenceStorer, r *plumbing.Reference, recursion int) (*plumbing.Reference, error) { - if r.Type() != plumbing.SymbolicReference { - return r, nil - } - - if recursion > MaxResolveRecursion { - return nil, ErrMaxResolveRecursion - } - - t, err := s.Reference(r.Target()) - if err != nil { - return nil, err - } - - recursion++ - return resolveReference(s, t, recursion) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/shallow.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/shallow.go deleted file mode 100644 index 409ae4d62bd..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/shallow.go +++ /dev/null @@ -1,10 +0,0 @@ -package storer - -import "github.com/jesseduffield/go-git/v5/plumbing" - -// ShallowStorer is a storage of references to shallow commits by hash, -// meaning that these commits have missing parents because of a shallow fetch. -type ShallowStorer interface { - SetShallow([]plumbing.Hash) error - Shallow() ([]plumbing.Hash, error) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/storer.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/storer.go deleted file mode 100644 index c7bc65a0c49..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/storer/storer.go +++ /dev/null @@ -1,15 +0,0 @@ -package storer - -// Storer is a basic storer for encoded objects and references. -type Storer interface { - EncodedObjectStorer - ReferenceStorer -} - -// Initializer should be implemented by storers that require to perform any -// operation when creating a new repository (i.e. git init). -type Initializer interface { - // Init performs initialization of the storer and returns the error, if - // any. - Init() error -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/client/client.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/client/client.go deleted file mode 100644 index 12065d8b629..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/client/client.go +++ /dev/null @@ -1,51 +0,0 @@ -// Package client contains helper function to deal with the different client -// protocols. -package client - -import ( - "fmt" - - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/plumbing/transport/file" - "github.com/jesseduffield/go-git/v5/plumbing/transport/git" - "github.com/jesseduffield/go-git/v5/plumbing/transport/http" - "github.com/jesseduffield/go-git/v5/plumbing/transport/ssh" -) - -// Protocols are the protocols supported by default. -var Protocols = map[string]transport.Transport{ - "http": http.DefaultClient, - "https": http.DefaultClient, - "ssh": ssh.DefaultClient, - "git": git.DefaultClient, - "file": file.DefaultClient, -} - -// InstallProtocol adds or modifies an existing protocol. -func InstallProtocol(scheme string, c transport.Transport) { - if c == nil { - delete(Protocols, scheme) - return - } - - Protocols[scheme] = c -} - -// NewClient returns the appropriate client among of the set of known protocols: -// http://, https://, ssh:// and file://. -// See `InstallProtocol` to add or modify protocols. -func NewClient(endpoint *transport.Endpoint) (transport.Transport, error) { - return getTransport(endpoint) -} - -func getTransport(endpoint *transport.Endpoint) (transport.Transport, error) { - f, ok := Protocols[endpoint.Protocol] - if !ok { - return nil, fmt.Errorf("unsupported scheme %q", endpoint.Protocol) - } - - if f == nil { - return nil, fmt.Errorf("malformed client for scheme %q, client is defined as nil", endpoint.Protocol) - } - return f, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/common.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/common.go deleted file mode 100644 index 900434f41bb..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/common.go +++ /dev/null @@ -1,325 +0,0 @@ -// Package transport includes the implementation for different transport -// protocols. -// -// `Client` can be used to fetch and send packfiles to a git server. -// The `client` package provides higher level functions to instantiate the -// appropriate `Client` based on the repository URL. -// -// go-git supports HTTP and SSH (see `Protocols`), but you can also install -// your own protocols (see the `client` package). -// -// Each protocol has its own implementation of `Client`, but you should -// generally not use them directly, use `client.NewClient` instead. -package transport - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "net/url" - "path/filepath" - "strconv" - "strings" - - giturl "github.com/jesseduffield/go-git/v5/internal/url" - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" -) - -var ( - ErrRepositoryNotFound = errors.New("repository not found") - ErrEmptyRemoteRepository = errors.New("remote repository is empty") - ErrAuthenticationRequired = errors.New("authentication required") - ErrAuthorizationFailed = errors.New("authorization failed") - ErrEmptyUploadPackRequest = errors.New("empty git-upload-pack given") - ErrInvalidAuthMethod = errors.New("invalid auth method") - ErrAlreadyConnected = errors.New("session already established") -) - -const ( - UploadPackServiceName = "git-upload-pack" - ReceivePackServiceName = "git-receive-pack" -) - -// Transport can initiate git-upload-pack and git-receive-pack processes. -// It is implemented both by the client and the server, making this a RPC. -type Transport interface { - // NewUploadPackSession starts a git-upload-pack session for an endpoint. - NewUploadPackSession(*Endpoint, AuthMethod) (UploadPackSession, error) - // NewReceivePackSession starts a git-receive-pack session for an endpoint. - NewReceivePackSession(*Endpoint, AuthMethod) (ReceivePackSession, error) -} - -type Session interface { - // AdvertisedReferences retrieves the advertised references for a - // repository. - // If the repository does not exist, returns ErrRepositoryNotFound. - // If the repository exists, but is empty, returns ErrEmptyRemoteRepository. - AdvertisedReferences() (*packp.AdvRefs, error) - // AdvertisedReferencesContext retrieves the advertised references for a - // repository. - // If the repository does not exist, returns ErrRepositoryNotFound. - // If the repository exists, but is empty, returns ErrEmptyRemoteRepository. - AdvertisedReferencesContext(context.Context) (*packp.AdvRefs, error) - io.Closer -} - -type AuthMethod interface { - fmt.Stringer - Name() string -} - -// UploadPackSession represents a git-upload-pack session. -// A git-upload-pack session has two steps: reference discovery -// (AdvertisedReferences) and uploading pack (UploadPack). -type UploadPackSession interface { - Session - // UploadPack takes a git-upload-pack request and returns a response, - // including a packfile. Don't be confused by terminology, the client - // side of a git-upload-pack is called git-fetch-pack, although here - // the same interface is used to make it RPC-like. - UploadPack(context.Context, *packp.UploadPackRequest) (*packp.UploadPackResponse, error) -} - -// ReceivePackSession represents a git-receive-pack session. -// A git-receive-pack session has two steps: reference discovery -// (AdvertisedReferences) and receiving pack (ReceivePack). -// In that order. -type ReceivePackSession interface { - Session - // ReceivePack sends an update references request and a packfile - // reader and returns a ReportStatus and error. Don't be confused by - // terminology, the client side of a git-receive-pack is called - // git-send-pack, although here the same interface is used to make it - // RPC-like. - ReceivePack(context.Context, *packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) -} - -// Endpoint represents a Git URL in any supported protocol. -type Endpoint struct { - // Protocol is the protocol of the endpoint (e.g. git, https, file). - Protocol string - // User is the user. - User string - // Password is the password. - Password string - // Host is the host. - Host string - // Port is the port to connect, if 0 the default port for the given protocol - // will be used. - Port int - // Path is the repository path. - Path string - // InsecureSkipTLS skips ssl verify if protocol is https - InsecureSkipTLS bool - // CaBundle specify additional ca bundle with system cert pool - CaBundle []byte - // Proxy provides info required for connecting to a proxy. - Proxy ProxyOptions -} - -type ProxyOptions struct { - URL string - Username string - Password string -} - -func (o *ProxyOptions) Validate() error { - if o.URL != "" { - _, err := url.Parse(o.URL) - return err - } - return nil -} - -func (o *ProxyOptions) FullURL() (*url.URL, error) { - proxyURL, err := url.Parse(o.URL) - if err != nil { - return nil, err - } - if o.Username != "" { - if o.Password != "" { - proxyURL.User = url.UserPassword(o.Username, o.Password) - } else { - proxyURL.User = url.User(o.Username) - } - } - return proxyURL, nil -} - -var defaultPorts = map[string]int{ - "http": 80, - "https": 443, - "git": 9418, - "ssh": 22, -} - -// String returns a string representation of the Git URL. -func (u *Endpoint) String() string { - var buf bytes.Buffer - if u.Protocol != "" { - buf.WriteString(u.Protocol) - buf.WriteByte(':') - } - - if u.Protocol != "" || u.Host != "" || u.User != "" || u.Password != "" { - buf.WriteString("//") - - if u.User != "" || u.Password != "" { - buf.WriteString(url.PathEscape(u.User)) - if u.Password != "" { - buf.WriteByte(':') - buf.WriteString(url.PathEscape(u.Password)) - } - - buf.WriteByte('@') - } - - if u.Host != "" { - buf.WriteString(u.Host) - - if u.Port != 0 { - port, ok := defaultPorts[strings.ToLower(u.Protocol)] - if !ok || ok && port != u.Port { - fmt.Fprintf(&buf, ":%d", u.Port) - } - } - } - } - - if u.Path != "" && u.Path[0] != '/' && u.Host != "" { - buf.WriteByte('/') - } - - buf.WriteString(u.Path) - return buf.String() -} - -func NewEndpoint(endpoint string) (*Endpoint, error) { - if e, ok := parseSCPLike(endpoint); ok { - return e, nil - } - - if e, ok := parseFile(endpoint); ok { - return e, nil - } - - return parseURL(endpoint) -} - -func parseURL(endpoint string) (*Endpoint, error) { - u, err := url.Parse(endpoint) - if err != nil { - return nil, err - } - - if !u.IsAbs() { - return nil, plumbing.NewPermanentError(fmt.Errorf( - "invalid endpoint: %s", endpoint, - )) - } - - var user, pass string - if u.User != nil { - user = u.User.Username() - pass, _ = u.User.Password() - } - - host := u.Hostname() - if strings.Contains(host, ":") { - // IPv6 address - host = "[" + host + "]" - } - - return &Endpoint{ - Protocol: u.Scheme, - User: user, - Password: pass, - Host: host, - Port: getPort(u), - Path: getPath(u), - }, nil -} - -func getPort(u *url.URL) int { - p := u.Port() - if p == "" { - return 0 - } - - i, err := strconv.Atoi(p) - if err != nil { - return 0 - } - - return i -} - -func getPath(u *url.URL) string { - var res string = u.Path - if u.RawQuery != "" { - res += "?" + u.RawQuery - } - - if u.Fragment != "" { - res += "#" + u.Fragment - } - - return res -} - -func parseSCPLike(endpoint string) (*Endpoint, bool) { - if giturl.MatchesScheme(endpoint) || !giturl.MatchesScpLike(endpoint) { - return nil, false - } - - user, host, portStr, path := giturl.FindScpLikeComponents(endpoint) - port, err := strconv.Atoi(portStr) - if err != nil { - port = 22 - } - - return &Endpoint{ - Protocol: "ssh", - User: user, - Host: host, - Port: port, - Path: path, - }, true -} - -func parseFile(endpoint string) (*Endpoint, bool) { - if giturl.MatchesScheme(endpoint) { - return nil, false - } - - path, err := filepath.Abs(endpoint) - if err != nil { - return nil, false - } - - return &Endpoint{ - Protocol: "file", - Path: path, - }, true -} - -// UnsupportedCapabilities are the capabilities not supported by any client -// implementation -var UnsupportedCapabilities = []capability.Capability{ - capability.MultiACK, - capability.MultiACKDetailed, - capability.ThinPack, -} - -// FilterUnsupportedCapabilities it filter out all the UnsupportedCapabilities -// from a capability.List, the intended usage is on the client implementation -// to filter the capabilities from an AdvRefs message. -func FilterUnsupportedCapabilities(list *capability.List) { - for _, c := range UnsupportedCapabilities { - list.Delete(c) - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/file/client.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/file/client.go deleted file mode 100644 index 1379132a634..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/file/client.go +++ /dev/null @@ -1,173 +0,0 @@ -// Package file implements the file transport protocol. -package file - -import ( - "bufio" - "errors" - "io" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common" - "golang.org/x/sys/execabs" -) - -// DefaultClient is the default local client. -var DefaultClient = NewClient( - transport.UploadPackServiceName, - transport.ReceivePackServiceName, -) - -type runner struct { - UploadPackBin string - ReceivePackBin string -} - -// NewClient returns a new local client using the given git-upload-pack and -// git-receive-pack binaries. -func NewClient(uploadPackBin, receivePackBin string) transport.Transport { - return common.NewClient(&runner{ - UploadPackBin: uploadPackBin, - ReceivePackBin: receivePackBin, - }) -} - -func prefixExecPath(cmd string) (string, error) { - // Use `git --exec-path` to find the exec path. - execCmd := execabs.Command("git", "--exec-path") - - stdout, err := execCmd.StdoutPipe() - if err != nil { - return "", err - } - stdoutBuf := bufio.NewReader(stdout) - - err = execCmd.Start() - if err != nil { - return "", err - } - - execPathBytes, isPrefix, err := stdoutBuf.ReadLine() - if err != nil { - return "", err - } - if isPrefix { - return "", errors.New("couldn't read exec-path line all at once") - } - - err = execCmd.Wait() - if err != nil { - return "", err - } - execPath := string(execPathBytes) - execPath = strings.TrimSpace(execPath) - cmd = filepath.Join(execPath, cmd) - - // Make sure it actually exists. - _, err = execabs.LookPath(cmd) - if err != nil { - return "", err - } - return cmd, nil -} - -func (r *runner) Command(cmd string, ep *transport.Endpoint, auth transport.AuthMethod, -) (common.Command, error) { - - switch cmd { - case transport.UploadPackServiceName: - cmd = r.UploadPackBin - case transport.ReceivePackServiceName: - cmd = r.ReceivePackBin - } - - _, err := execabs.LookPath(cmd) - if err != nil { - if e, ok := err.(*execabs.Error); ok && e.Err == execabs.ErrNotFound { - cmd, err = prefixExecPath(cmd) - if err != nil { - return nil, err - } - } else { - return nil, err - } - } - - return &command{cmd: execabs.Command(cmd, adjustPathForWindows(ep.Path))}, nil -} - -func isDriveLetter(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') -} - -// On Windows, the path that results from a file: URL has a leading slash. This -// has to be removed if there's a drive letter -func adjustPathForWindows(p string) string { - if runtime.GOOS != "windows" { - return p - } - if len(p) >= 3 && p[0] == '/' && isDriveLetter(p[1]) && p[2] == ':' { - return p[1:] - } - return p -} - -type command struct { - cmd *execabs.Cmd - stderrCloser io.Closer - closed bool -} - -func (c *command) Start() error { - return c.cmd.Start() -} - -func (c *command) StderrPipe() (io.Reader, error) { - // Pipe returned by Command.StderrPipe has a race with Read + Command.Wait. - // We use an io.Pipe and close it after the command finishes. - r, w := io.Pipe() - c.cmd.Stderr = w - c.stderrCloser = r - return r, nil -} - -func (c *command) StdinPipe() (io.WriteCloser, error) { - return c.cmd.StdinPipe() -} - -func (c *command) StdoutPipe() (io.Reader, error) { - return c.cmd.StdoutPipe() -} - -func (c *command) Kill() error { - c.cmd.Process.Kill() - return c.Close() -} - -// Close waits for the command to exit. -func (c *command) Close() error { - if c.closed { - return nil - } - - defer func() { - c.closed = true - _ = c.stderrCloser.Close() - - }() - - err := c.cmd.Wait() - if _, ok := err.(*os.PathError); ok { - return nil - } - - // When a repository does not exist, the command exits with code 128. - if _, ok := err.(*execabs.ExitError); ok { - return nil - } - - return err -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/file/server.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/file/server.go deleted file mode 100644 index 79ea016fb32..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/file/server.go +++ /dev/null @@ -1,53 +0,0 @@ -package file - -import ( - "fmt" - "os" - - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common" - "github.com/jesseduffield/go-git/v5/plumbing/transport/server" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -// ServeUploadPack serves a git-upload-pack request using standard output, input -// and error. This is meant to be used when implementing a git-upload-pack -// command. -func ServeUploadPack(path string) error { - ep, err := transport.NewEndpoint(path) - if err != nil { - return err - } - - // TODO: define and implement a server-side AuthMethod - s, err := server.DefaultServer.NewUploadPackSession(ep, nil) - if err != nil { - return fmt.Errorf("error creating session: %s", err) - } - - return common.ServeUploadPack(srvCmd, s) -} - -// ServeReceivePack serves a git-receive-pack request using standard output, -// input and error. This is meant to be used when implementing a -// git-receive-pack command. -func ServeReceivePack(path string) error { - ep, err := transport.NewEndpoint(path) - if err != nil { - return err - } - - // TODO: define and implement a server-side AuthMethod - s, err := server.DefaultServer.NewReceivePackSession(ep, nil) - if err != nil { - return fmt.Errorf("error creating session: %s", err) - } - - return common.ServeReceivePack(srvCmd, s) -} - -var srvCmd = common.ServerCommand{ - Stdin: os.Stdin, - Stdout: ioutil.WriteNopCloser(os.Stdout), - Stderr: os.Stderr, -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/git/common.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/git/common.go deleted file mode 100644 index 8368f1f1ded..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/git/common.go +++ /dev/null @@ -1,108 +0,0 @@ -// Package git implements the git transport protocol. -package git - -import ( - "io" - "net" - "strconv" - - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp" - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -// DefaultClient is the default git client. -var DefaultClient = common.NewClient(&runner{}) - -const DefaultPort = 9418 - -type runner struct{} - -// Command returns a new Command for the given cmd in the given Endpoint -func (r *runner) Command(cmd string, ep *transport.Endpoint, auth transport.AuthMethod) (common.Command, error) { - // auth not allowed since git protocol doesn't support authentication - if auth != nil { - return nil, transport.ErrInvalidAuthMethod - } - c := &command{command: cmd, endpoint: ep} - if err := c.connect(); err != nil { - return nil, err - } - return c, nil -} - -type command struct { - conn net.Conn - connected bool - command string - endpoint *transport.Endpoint -} - -// Start executes the command sending the required message to the TCP connection -func (c *command) Start() error { - req := packp.GitProtoRequest{ - RequestCommand: c.command, - Pathname: c.endpoint.Path, - } - host := c.endpoint.Host - if c.endpoint.Port != DefaultPort { - host = net.JoinHostPort(c.endpoint.Host, strconv.Itoa(c.endpoint.Port)) - } - - req.Host = host - - return req.Encode(c.conn) -} - -func (c *command) connect() error { - if c.connected { - return transport.ErrAlreadyConnected - } - - var err error - c.conn, err = net.Dial("tcp", c.getHostWithPort()) - if err != nil { - return err - } - - c.connected = true - return nil -} - -func (c *command) getHostWithPort() string { - host := c.endpoint.Host - port := c.endpoint.Port - if port <= 0 { - port = DefaultPort - } - - return net.JoinHostPort(host, strconv.Itoa(port)) -} - -// StderrPipe git protocol doesn't have any dedicated error channel -func (c *command) StderrPipe() (io.Reader, error) { - return nil, nil -} - -// StdinPipe returns the underlying connection as WriteCloser, wrapped to prevent -// call to the Close function from the connection, a command execution in git -// protocol can't be closed or killed -func (c *command) StdinPipe() (io.WriteCloser, error) { - return ioutil.WriteNopCloser(c.conn), nil -} - -// StdoutPipe returns the underlying connection as Reader -func (c *command) StdoutPipe() (io.Reader, error) { - return c.conn, nil -} - -// Close closes the TCP connection and connection. -func (c *command) Close() error { - if !c.connected { - return nil - } - - c.connected = false - return c.conn.Close() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/common.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/common.go deleted file mode 100644 index 9495d176c83..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/common.go +++ /dev/null @@ -1,453 +0,0 @@ -// Package http implements the HTTP transport protocol. -package http - -import ( - "bytes" - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "net" - "net/http" - "net/url" - "reflect" - "strconv" - "strings" - "sync" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/golang/groupcache/lru" -) - -// it requires a bytes.Buffer, because we need to know the length -func applyHeadersToRequest(req *http.Request, content *bytes.Buffer, host string, requestType string) { - req.Header.Add("User-Agent", capability.DefaultAgent()) - req.Header.Add("Host", host) // host:port - - if content == nil { - req.Header.Add("Accept", "*/*") - return - } - - req.Header.Add("Accept", fmt.Sprintf("application/x-%s-result", requestType)) - req.Header.Add("Content-Type", fmt.Sprintf("application/x-%s-request", requestType)) - req.Header.Add("Content-Length", strconv.Itoa(content.Len())) -} - -const infoRefsPath = "/info/refs" - -func advertisedReferences(ctx context.Context, s *session, serviceName string) (ref *packp.AdvRefs, err error) { - url := fmt.Sprintf( - "%s%s?service=%s", - s.endpoint.String(), infoRefsPath, serviceName, - ) - - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return nil, err - } - - s.ApplyAuthToRequest(req) - applyHeadersToRequest(req, nil, s.endpoint.Host, serviceName) - res, err := s.client.Do(req.WithContext(ctx)) - if err != nil { - return nil, err - } - - s.ModifyEndpointIfRedirect(res) - defer ioutil.CheckClose(res.Body, &err) - - if err = NewErr(res); err != nil { - return nil, err - } - - ar := packp.NewAdvRefs() - if err = ar.Decode(res.Body); err != nil { - if err == packp.ErrEmptyAdvRefs { - err = transport.ErrEmptyRemoteRepository - } - - return nil, err - } - - // Git 2.41+ returns a zero-id plus capabilities when an empty - // repository is being cloned. This skips the existing logic within - // advrefs_decode.decodeFirstHash, which expects a flush-pkt instead. - // - // This logic aligns with plumbing/transport/internal/common/common.go. - if ar.IsEmpty() && - // Empty repositories are valid for git-receive-pack. - transport.ReceivePackServiceName != serviceName { - return nil, transport.ErrEmptyRemoteRepository - } - - transport.FilterUnsupportedCapabilities(ar.Capabilities) - s.advRefs = ar - - return ar, nil -} - -type client struct { - client *http.Client - transports *lru.Cache - mutex sync.RWMutex -} - -// ClientOptions holds user configurable options for the client. -type ClientOptions struct { - // CacheMaxEntries is the max no. of entries that the transport objects - // cache will hold at any given point of time. It must be a positive integer. - // Calling `client.addTransport()` after the cache has reached the specified - // size, will result in the least recently used transport getting deleted - // before the provided transport is added to the cache. - CacheMaxEntries int -} - -var ( - // defaultTransportCacheSize is the default capacity of the transport objects cache. - // Its value is 0 because transport caching is turned off by default and is an - // opt-in feature. - defaultTransportCacheSize = 0 - - // DefaultClient is the default HTTP client, which uses a net/http client configured - // with http.DefaultTransport. - DefaultClient = NewClient(nil) -) - -// NewClient creates a new client with a custom net/http client. -// See `InstallProtocol` to install and override default http client. -// If the net/http client is nil or empty, it will use a net/http client configured -// with http.DefaultTransport. -// -// Note that for HTTP client cannot distinguish between private repositories and -// unexistent repositories on GitHub. So it returns `ErrAuthorizationRequired` -// for both. -func NewClient(c *http.Client) transport.Transport { - if c == nil { - c = &http.Client{ - Transport: http.DefaultTransport, - } - } - return NewClientWithOptions(c, &ClientOptions{ - CacheMaxEntries: defaultTransportCacheSize, - }) -} - -// NewClientWithOptions returns a new client configured with the provided net/http client -// and other custom options specific to the client. -// If the net/http client is nil or empty, it will use a net/http client configured -// with http.DefaultTransport. -func NewClientWithOptions(c *http.Client, opts *ClientOptions) transport.Transport { - if c == nil { - c = &http.Client{ - Transport: http.DefaultTransport, - } - } - cl := &client{ - client: c, - } - - if opts != nil { - if opts.CacheMaxEntries > 0 { - cl.transports = lru.New(opts.CacheMaxEntries) - } - } - return cl -} - -func (c *client) NewUploadPackSession(ep *transport.Endpoint, auth transport.AuthMethod) ( - transport.UploadPackSession, error) { - - return newUploadPackSession(c, ep, auth) -} - -func (c *client) NewReceivePackSession(ep *transport.Endpoint, auth transport.AuthMethod) ( - transport.ReceivePackSession, error) { - - return newReceivePackSession(c, ep, auth) -} - -type session struct { - auth AuthMethod - client *http.Client - endpoint *transport.Endpoint - advRefs *packp.AdvRefs -} - -func transportWithInsecureTLS(transport *http.Transport) { - if transport.TLSClientConfig == nil { - transport.TLSClientConfig = &tls.Config{} - } - transport.TLSClientConfig.InsecureSkipVerify = true -} - -func transportWithCABundle(transport *http.Transport, caBundle []byte) error { - rootCAs, err := x509.SystemCertPool() - if err != nil { - return err - } - if rootCAs == nil { - rootCAs = x509.NewCertPool() - } - rootCAs.AppendCertsFromPEM(caBundle) - if transport.TLSClientConfig == nil { - transport.TLSClientConfig = &tls.Config{} - } - transport.TLSClientConfig.RootCAs = rootCAs - return nil -} - -func transportWithProxy(transport *http.Transport, proxyURL *url.URL) { - transport.Proxy = http.ProxyURL(proxyURL) -} - -func configureTransport(transport *http.Transport, ep *transport.Endpoint) error { - if len(ep.CaBundle) > 0 { - if err := transportWithCABundle(transport, ep.CaBundle); err != nil { - return err - } - } - if ep.InsecureSkipTLS { - transportWithInsecureTLS(transport) - } - - if ep.Proxy.URL != "" { - proxyURL, err := ep.Proxy.FullURL() - if err != nil { - return err - } - transportWithProxy(transport, proxyURL) - } - return nil -} - -func newSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (*session, error) { - var httpClient *http.Client - - // We need to configure the http transport if there are transport specific - // options present in the endpoint. - if len(ep.CaBundle) > 0 || ep.InsecureSkipTLS || ep.Proxy.URL != "" { - var transport *http.Transport - // if the client wasn't configured to have a cache for transports then just configure - // the transport and use it directly, otherwise try to use the cache. - if c.transports == nil { - tr, ok := c.client.Transport.(*http.Transport) - if !ok { - return nil, fmt.Errorf("expected underlying client transport to be of type: %s; got: %s", - reflect.TypeOf(transport), reflect.TypeOf(c.client.Transport)) - } - - transport = tr.Clone() - configureTransport(transport, ep) - } else { - transportOpts := transportOptions{ - caBundle: string(ep.CaBundle), - insecureSkipTLS: ep.InsecureSkipTLS, - } - if ep.Proxy.URL != "" { - proxyURL, err := ep.Proxy.FullURL() - if err != nil { - return nil, err - } - transportOpts.proxyURL = *proxyURL - } - var found bool - transport, found = c.fetchTransport(transportOpts) - - if !found { - transport = c.client.Transport.(*http.Transport).Clone() - configureTransport(transport, ep) - c.addTransport(transportOpts, transport) - } - } - - httpClient = &http.Client{ - Transport: transport, - CheckRedirect: c.client.CheckRedirect, - Jar: c.client.Jar, - Timeout: c.client.Timeout, - } - } else { - httpClient = c.client - } - - s := &session{ - auth: basicAuthFromEndpoint(ep), - client: httpClient, - endpoint: ep, - } - if auth != nil { - a, ok := auth.(AuthMethod) - if !ok { - return nil, transport.ErrInvalidAuthMethod - } - - s.auth = a - } - - return s, nil -} - -func (s *session) ApplyAuthToRequest(req *http.Request) { - if s.auth == nil { - return - } - - s.auth.SetAuth(req) -} - -func (s *session) ModifyEndpointIfRedirect(res *http.Response) { - if res.Request == nil { - return - } - - r := res.Request - if !strings.HasSuffix(r.URL.Path, infoRefsPath) { - return - } - - h, p, err := net.SplitHostPort(r.URL.Host) - if err != nil { - h = r.URL.Host - } - if p != "" { - port, err := strconv.Atoi(p) - if err == nil { - s.endpoint.Port = port - } - } - s.endpoint.Host = h - - s.endpoint.Protocol = r.URL.Scheme - s.endpoint.Path = r.URL.Path[:len(r.URL.Path)-len(infoRefsPath)] -} - -func (*session) Close() error { - return nil -} - -// AuthMethod is concrete implementation of common.AuthMethod for HTTP services -type AuthMethod interface { - transport.AuthMethod - SetAuth(r *http.Request) -} - -func basicAuthFromEndpoint(ep *transport.Endpoint) *BasicAuth { - u := ep.User - if u == "" { - return nil - } - - return &BasicAuth{u, ep.Password} -} - -// BasicAuth represent a HTTP basic auth -type BasicAuth struct { - Username, Password string -} - -func (a *BasicAuth) SetAuth(r *http.Request) { - if a == nil { - return - } - - r.SetBasicAuth(a.Username, a.Password) -} - -// Name is name of the auth -func (a *BasicAuth) Name() string { - return "http-basic-auth" -} - -func (a *BasicAuth) String() string { - masked := "*******" - if a.Password == "" { - masked = "" - } - - return fmt.Sprintf("%s - %s:%s", a.Name(), a.Username, masked) -} - -// TokenAuth implements an http.AuthMethod that can be used with http transport -// to authenticate with HTTP token authentication (also known as bearer -// authentication). -// -// IMPORTANT: If you are looking to use OAuth tokens with popular servers (e.g. -// GitHub, Bitbucket, GitLab) you should use BasicAuth instead. These servers -// use basic HTTP authentication, with the OAuth token as user or password. -// Check the documentation of your git server for details. -type TokenAuth struct { - Token string -} - -func (a *TokenAuth) SetAuth(r *http.Request) { - if a == nil { - return - } - r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", a.Token)) -} - -// Name is name of the auth -func (a *TokenAuth) Name() string { - return "http-token-auth" -} - -func (a *TokenAuth) String() string { - masked := "*******" - if a.Token == "" { - masked = "" - } - return fmt.Sprintf("%s - %s", a.Name(), masked) -} - -// Err is a dedicated error to return errors based on status code -type Err struct { - Response *http.Response - Reason string -} - -// NewErr returns a new Err based on a http response and closes response body -// if needed -func NewErr(r *http.Response) error { - if r.StatusCode >= http.StatusOK && r.StatusCode < http.StatusMultipleChoices { - return nil - } - - var reason string - - // If a response message is present, add it to error - var messageBuffer bytes.Buffer - if r.Body != nil { - messageLength, _ := messageBuffer.ReadFrom(r.Body) - if messageLength > 0 { - reason = messageBuffer.String() - } - _ = r.Body.Close() - } - - switch r.StatusCode { - case http.StatusUnauthorized: - return fmt.Errorf("%w: %s", transport.ErrAuthenticationRequired, reason) - case http.StatusForbidden: - return fmt.Errorf("%w: %s", transport.ErrAuthorizationFailed, reason) - case http.StatusNotFound: - return fmt.Errorf("%w: %s", transport.ErrRepositoryNotFound, reason) - } - - return plumbing.NewUnexpectedError(&Err{r, reason}) -} - -// StatusCode returns the status code of the response -func (e *Err) StatusCode() int { - return e.Response.StatusCode -} - -func (e *Err) Error() string { - return fmt.Sprintf("unexpected requesting %q status code: %d", - e.Response.Request.URL, e.Response.StatusCode, - ) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/receive_pack.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/receive_pack.go deleted file mode 100644 index 5a7211cd7ad..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/receive_pack.go +++ /dev/null @@ -1,109 +0,0 @@ -package http - -import ( - "bytes" - "context" - "fmt" - "io" - "net/http" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband" - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -type rpSession struct { - *session -} - -func newReceivePackSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (transport.ReceivePackSession, error) { - s, err := newSession(c, ep, auth) - return &rpSession{s}, err -} - -func (s *rpSession) AdvertisedReferences() (*packp.AdvRefs, error) { - return advertisedReferences(context.TODO(), s.session, transport.ReceivePackServiceName) -} - -func (s *rpSession) AdvertisedReferencesContext(ctx context.Context) (*packp.AdvRefs, error) { - return advertisedReferences(ctx, s.session, transport.ReceivePackServiceName) -} - -func (s *rpSession) ReceivePack(ctx context.Context, req *packp.ReferenceUpdateRequest) ( - *packp.ReportStatus, error) { - url := fmt.Sprintf( - "%s/%s", - s.endpoint.String(), transport.ReceivePackServiceName, - ) - - buf := bytes.NewBuffer(nil) - if err := req.Encode(buf); err != nil { - return nil, err - } - - res, err := s.doRequest(ctx, http.MethodPost, url, buf) - if err != nil { - return nil, err - } - - r, err := ioutil.NonEmptyReader(res.Body) - if err == ioutil.ErrEmptyReader { - return nil, nil - } - - if err != nil { - return nil, err - } - - var d *sideband.Demuxer - if req.Capabilities.Supports(capability.Sideband64k) { - d = sideband.NewDemuxer(sideband.Sideband64k, r) - } else if req.Capabilities.Supports(capability.Sideband) { - d = sideband.NewDemuxer(sideband.Sideband, r) - } - if d != nil { - d.Progress = req.Progress - r = d - } - - rc := ioutil.NewReadCloser(r, res.Body) - - report := packp.NewReportStatus() - if err := report.Decode(rc); err != nil { - return nil, err - } - - return report, report.Error() -} - -func (s *rpSession) doRequest( - ctx context.Context, method, url string, content *bytes.Buffer, -) (*http.Response, error) { - - var body io.Reader - if content != nil { - body = content - } - - req, err := http.NewRequest(method, url, body) - if err != nil { - return nil, plumbing.NewPermanentError(err) - } - - applyHeadersToRequest(req, content, s.endpoint.Host, transport.ReceivePackServiceName) - s.ApplyAuthToRequest(req) - - res, err := s.client.Do(req.WithContext(ctx)) - if err != nil { - return nil, plumbing.NewUnexpectedError(err) - } - - if err := NewErr(res); err != nil { - return nil, err - } - - return res, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/transport.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/transport.go deleted file mode 100644 index c8db389204a..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/transport.go +++ /dev/null @@ -1,40 +0,0 @@ -package http - -import ( - "net/http" - "net/url" -) - -// transportOptions contains transport specific configuration. -type transportOptions struct { - insecureSkipTLS bool - // []byte is not comparable. - caBundle string - proxyURL url.URL -} - -func (c *client) addTransport(opts transportOptions, transport *http.Transport) { - c.mutex.Lock() - c.transports.Add(opts, transport) - c.mutex.Unlock() -} - -func (c *client) removeTransport(opts transportOptions) { - c.mutex.Lock() - c.transports.Remove(opts) - c.mutex.Unlock() -} - -func (c *client) fetchTransport(opts transportOptions) (*http.Transport, bool) { - c.mutex.RLock() - t, ok := c.transports.Get(opts) - c.mutex.RUnlock() - if !ok { - return nil, false - } - transport, ok := t.(*http.Transport) - if !ok { - return nil, false - } - return transport, true -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/upload_pack.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/upload_pack.go deleted file mode 100644 index 954dc51870b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/http/upload_pack.go +++ /dev/null @@ -1,126 +0,0 @@ -package http - -import ( - "bytes" - "context" - "fmt" - "io" - "net/http" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp" - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -type upSession struct { - *session -} - -func newUploadPackSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (transport.UploadPackSession, error) { - s, err := newSession(c, ep, auth) - return &upSession{s}, err -} - -func (s *upSession) AdvertisedReferences() (*packp.AdvRefs, error) { - return advertisedReferences(context.TODO(), s.session, transport.UploadPackServiceName) -} - -func (s *upSession) AdvertisedReferencesContext(ctx context.Context) (*packp.AdvRefs, error) { - return advertisedReferences(ctx, s.session, transport.UploadPackServiceName) -} - -func (s *upSession) UploadPack( - ctx context.Context, req *packp.UploadPackRequest, -) (*packp.UploadPackResponse, error) { - - if req.IsEmpty() { - return nil, transport.ErrEmptyUploadPackRequest - } - - if err := req.Validate(); err != nil { - return nil, err - } - - url := fmt.Sprintf( - "%s/%s", - s.endpoint.String(), transport.UploadPackServiceName, - ) - - content, err := uploadPackRequestToReader(req) - if err != nil { - return nil, err - } - - res, err := s.doRequest(ctx, http.MethodPost, url, content) - if err != nil { - return nil, err - } - - r, err := ioutil.NonEmptyReader(res.Body) - if err != nil { - if err == ioutil.ErrEmptyReader || err == io.ErrUnexpectedEOF { - return nil, transport.ErrEmptyUploadPackRequest - } - - return nil, err - } - - rc := ioutil.NewReadCloser(r, res.Body) - return common.DecodeUploadPackResponse(rc, req) -} - -// Close does nothing. -func (s *upSession) Close() error { - return nil -} - -func (s *upSession) doRequest( - ctx context.Context, method, url string, content *bytes.Buffer, -) (*http.Response, error) { - - var body io.Reader - if content != nil { - body = content - } - - req, err := http.NewRequest(method, url, body) - if err != nil { - return nil, plumbing.NewPermanentError(err) - } - - applyHeadersToRequest(req, content, s.endpoint.Host, transport.UploadPackServiceName) - s.ApplyAuthToRequest(req) - - res, err := s.client.Do(req.WithContext(ctx)) - if err != nil { - return nil, plumbing.NewUnexpectedError(err) - } - - if err := NewErr(res); err != nil { - return nil, err - } - - return res, nil -} - -func uploadPackRequestToReader(req *packp.UploadPackRequest) (*bytes.Buffer, error) { - buf := bytes.NewBuffer(nil) - e := pktline.NewEncoder(buf) - - if err := req.UploadRequest.Encode(buf); err != nil { - return nil, fmt.Errorf("sending upload-req message: %s", err) - } - - if err := req.UploadHaves.Encode(buf, false); err != nil { - return nil, fmt.Errorf("sending haves message: %s", err) - } - - if err := e.EncodeString("done\n"); err != nil { - return nil, err - } - - return buf, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common/common.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common/common.go deleted file mode 100644 index 6c770693a0d..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common/common.go +++ /dev/null @@ -1,492 +0,0 @@ -// Package common implements the git pack protocol with a pluggable transport. -// This is a low-level package to implement new transports. Use a concrete -// implementation instead (e.g. http, file, ssh). -// -// A simple example of usage can be found in the file package. -package common - -import ( - "bufio" - "context" - "errors" - "fmt" - "io" - "regexp" - "strings" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing/format/pktline" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband" - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -const ( - readErrorSecondsTimeout = 10 -) - -var ( - ErrTimeoutExceeded = errors.New("timeout exceeded") - // stdErrSkipPattern is used for skipping lines from a command's stderr output. - // Any line matching this pattern will be skipped from further - // processing and not be returned to calling code. - stdErrSkipPattern = regexp.MustCompile("^remote:( =*){0,1}$") -) - -// Commander creates Command instances. This is the main entry point for -// transport implementations. -type Commander interface { - // Command creates a new Command for the given git command and - // endpoint. cmd can be git-upload-pack or git-receive-pack. An - // error should be returned if the endpoint is not supported or the - // command cannot be created (e.g. binary does not exist, connection - // cannot be established). - Command(cmd string, ep *transport.Endpoint, auth transport.AuthMethod) (Command, error) -} - -// Command is used for a single command execution. -// This interface is modeled after exec.Cmd and ssh.Session in the standard -// library. -type Command interface { - // StderrPipe returns a pipe that will be connected to the command's - // standard error when the command starts. It should not be called after - // Start. - StderrPipe() (io.Reader, error) - // StdinPipe returns a pipe that will be connected to the command's - // standard input when the command starts. It should not be called after - // Start. The pipe should be closed when no more input is expected. - StdinPipe() (io.WriteCloser, error) - // StdoutPipe returns a pipe that will be connected to the command's - // standard output when the command starts. It should not be called after - // Start. - StdoutPipe() (io.Reader, error) - // Start starts the specified command. It does not wait for it to - // complete. - Start() error - // Close closes the command and releases any resources used by it. It - // will block until the command exits. - Close() error -} - -// CommandKiller expands the Command interface, enabling it for being killed. -type CommandKiller interface { - // Kill and close the session whatever the state it is. It will block until - // the command is terminated. - Kill() error -} - -type client struct { - cmdr Commander -} - -// NewClient creates a new client using the given Commander. -func NewClient(runner Commander) transport.Transport { - return &client{runner} -} - -// NewUploadPackSession creates a new UploadPackSession. -func (c *client) NewUploadPackSession(ep *transport.Endpoint, auth transport.AuthMethod) ( - transport.UploadPackSession, error) { - - return c.newSession(transport.UploadPackServiceName, ep, auth) -} - -// NewReceivePackSession creates a new ReceivePackSession. -func (c *client) NewReceivePackSession(ep *transport.Endpoint, auth transport.AuthMethod) ( - transport.ReceivePackSession, error) { - - return c.newSession(transport.ReceivePackServiceName, ep, auth) -} - -type session struct { - Stdin io.WriteCloser - Stdout io.Reader - Command Command - - isReceivePack bool - advRefs *packp.AdvRefs - packRun bool - finished bool - firstErrLine chan string -} - -func (c *client) newSession(s string, ep *transport.Endpoint, auth transport.AuthMethod) (*session, error) { - cmd, err := c.cmdr.Command(s, ep, auth) - if err != nil { - return nil, err - } - - stdin, err := cmd.StdinPipe() - if err != nil { - return nil, err - } - - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - stderr, err := cmd.StderrPipe() - if err != nil { - return nil, err - } - - if err := cmd.Start(); err != nil { - return nil, err - } - - return &session{ - Stdin: stdin, - Stdout: stdout, - Command: cmd, - firstErrLine: c.listenFirstError(stderr), - isReceivePack: s == transport.ReceivePackServiceName, - }, nil -} - -func (c *client) listenFirstError(r io.Reader) chan string { - if r == nil { - return nil - } - - errLine := make(chan string, 1) - go func() { - s := bufio.NewScanner(r) - for { - if s.Scan() { - line := s.Text() - if !stdErrSkipPattern.MatchString(line) { - errLine <- line - break - } - } else { - close(errLine) - break - } - } - - _, _ = io.Copy(io.Discard, r) - }() - - return errLine -} - -func (s *session) AdvertisedReferences() (*packp.AdvRefs, error) { - return s.AdvertisedReferencesContext(context.TODO()) -} - -// AdvertisedReferences retrieves the advertised references from the server. -func (s *session) AdvertisedReferencesContext(ctx context.Context) (*packp.AdvRefs, error) { - if s.advRefs != nil { - return s.advRefs, nil - } - - ar := packp.NewAdvRefs() - if err := ar.Decode(s.StdoutContext(ctx)); err != nil { - if err := s.handleAdvRefDecodeError(err); err != nil { - return nil, err - } - } - - // Some servers like jGit, announce capabilities instead of returning an - // packp message with a flush. This verifies that we received a empty - // adv-refs, even it contains capabilities. - if !s.isReceivePack && ar.IsEmpty() { - return nil, transport.ErrEmptyRemoteRepository - } - - transport.FilterUnsupportedCapabilities(ar.Capabilities) - s.advRefs = ar - return ar, nil -} - -func (s *session) handleAdvRefDecodeError(err error) error { - var errLine *pktline.ErrorLine - if errors.As(err, &errLine) { - if isRepoNotFoundError(errLine.Text) { - return transport.ErrRepositoryNotFound - } - - return errLine - } - - // If repository is not found, we get empty stdout and server writes an - // error to stderr. - if errors.Is(err, packp.ErrEmptyInput) { - // TODO:(v6): handle this error in a better way. - // Instead of checking the stderr output for a specific error message, - // define an ExitError and embed the stderr output and exit (if one - // exists) in the error struct. Just like exec.ExitError. - s.finished = true - if err := s.checkNotFoundError(); err != nil { - return err - } - - return io.ErrUnexpectedEOF - } - - // For empty (but existing) repositories, we get empty advertised-references - // message. But valid. That is, it includes at least a flush. - if err == packp.ErrEmptyAdvRefs { - // Empty repositories are valid for git-receive-pack. - if s.isReceivePack { - return nil - } - - if err := s.finish(); err != nil { - return err - } - - return transport.ErrEmptyRemoteRepository - } - - // Some server sends the errors as normal content (git protocol), so when - // we try to decode it fails, we need to check the content of it, to detect - // not found errors - if uerr, ok := err.(*packp.ErrUnexpectedData); ok { - if isRepoNotFoundError(string(uerr.Data)) { - return transport.ErrRepositoryNotFound - } - } - - return err -} - -// UploadPack performs a request to the server to fetch a packfile. A reader is -// returned with the packfile content. The reader must be closed after reading. -func (s *session) UploadPack(ctx context.Context, req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) { - if req.IsEmpty() { - // XXX: IsEmpty means haves are a subset of wants, in that case we have - // everything we asked for. Close the connection and return nil. - if err := s.finish(); err != nil { - return nil, err - } - // TODO:(v6) return nil here - return nil, transport.ErrEmptyUploadPackRequest - } - - if err := req.Validate(); err != nil { - return nil, err - } - - if _, err := s.AdvertisedReferencesContext(ctx); err != nil { - return nil, err - } - - s.packRun = true - - in := s.StdinContext(ctx) - out := s.StdoutContext(ctx) - - if err := uploadPack(in, out, req); err != nil { - return nil, err - } - - r, err := ioutil.NonEmptyReader(out) - if err == ioutil.ErrEmptyReader { - if c, ok := s.Stdout.(io.Closer); ok { - _ = c.Close() - } - - return nil, transport.ErrEmptyUploadPackRequest - } - - if err != nil { - return nil, err - } - - rc := ioutil.NewReadCloser(r, s) - return DecodeUploadPackResponse(rc, req) -} - -func (s *session) StdinContext(ctx context.Context) io.WriteCloser { - return ioutil.NewWriteCloserOnError( - ioutil.NewContextWriteCloser(ctx, s.Stdin), - s.onError, - ) -} - -func (s *session) StdoutContext(ctx context.Context) io.Reader { - return ioutil.NewReaderOnError( - ioutil.NewContextReader(ctx, s.Stdout), - s.onError, - ) -} - -func (s *session) onError(err error) { - if k, ok := s.Command.(CommandKiller); ok { - _ = k.Kill() - } - - _ = s.Close() -} - -func (s *session) ReceivePack(ctx context.Context, req *packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) { - if _, err := s.AdvertisedReferences(); err != nil { - return nil, err - } - - s.packRun = true - - w := s.StdinContext(ctx) - if err := req.Encode(w); err != nil { - return nil, err - } - - if err := w.Close(); err != nil { - return nil, err - } - - if !req.Capabilities.Supports(capability.ReportStatus) { - // If we don't have report-status, we can only - // check return value error. - return nil, s.Command.Close() - } - - r := s.StdoutContext(ctx) - - var d *sideband.Demuxer - if req.Capabilities.Supports(capability.Sideband64k) { - d = sideband.NewDemuxer(sideband.Sideband64k, r) - } else if req.Capabilities.Supports(capability.Sideband) { - d = sideband.NewDemuxer(sideband.Sideband, r) - } - if d != nil { - d.Progress = req.Progress - r = d - } - - report := packp.NewReportStatus() - if err := report.Decode(r); err != nil { - return nil, err - } - - if err := report.Error(); err != nil { - defer s.Close() - return report, err - } - - return report, s.Command.Close() -} - -func (s *session) finish() error { - if s.finished { - return nil - } - - s.finished = true - - // If we did not run a upload/receive-pack, we close the connection - // gracefully by sending a flush packet to the server. If the server - // operates correctly, it will exit with status 0. - if !s.packRun { - _, err := s.Stdin.Write(pktline.FlushPkt) - return err - } - - return nil -} - -func (s *session) Close() (err error) { - err = s.finish() - - defer ioutil.CheckClose(s.Command, &err) - return -} - -func (s *session) checkNotFoundError() error { - t := time.NewTicker(time.Second * readErrorSecondsTimeout) - defer t.Stop() - - select { - case <-t.C: - return ErrTimeoutExceeded - case line, ok := <-s.firstErrLine: - if !ok || len(line) == 0 { - return nil - } - - if isRepoNotFoundError(line) { - return transport.ErrRepositoryNotFound - } - - // TODO:(v6): return server error just as it is without a prefix - return fmt.Errorf("unknown error: %s", line) - } -} - -const ( - githubRepoNotFoundErr = "Repository not found." - bitbucketRepoNotFoundErr = "repository does not exist." - localRepoNotFoundErr = "does not appear to be a git repository" - gitProtocolNotFoundErr = "Repository not found." - gitProtocolNoSuchErr = "no such repository" - gitProtocolAccessDeniedErr = "access denied" - gogsAccessDeniedErr = "Repository does not exist or you do not have access" - gitlabRepoNotFoundErr = "The project you were looking for could not be found" -) - -func isRepoNotFoundError(s string) bool { - for _, err := range []string{ - githubRepoNotFoundErr, - bitbucketRepoNotFoundErr, - localRepoNotFoundErr, - gitProtocolNotFoundErr, - gitProtocolNoSuchErr, - gitProtocolAccessDeniedErr, - gogsAccessDeniedErr, - gitlabRepoNotFoundErr, - } { - if strings.Contains(s, err) { - return true - } - } - - return false -} - -// uploadPack implements the git-upload-pack protocol. -func uploadPack(w io.WriteCloser, _ io.Reader, req *packp.UploadPackRequest) error { - // TODO support multi_ack mode - // TODO support multi_ack_detailed mode - // TODO support acks for common objects - // TODO build a proper state machine for all these processing options - - if err := req.UploadRequest.Encode(w); err != nil { - return fmt.Errorf("sending upload-req message: %s", err) - } - - if err := req.UploadHaves.Encode(w, true); err != nil { - return fmt.Errorf("sending haves message: %s", err) - } - - if err := sendDone(w); err != nil { - return fmt.Errorf("sending done message: %s", err) - } - - if err := w.Close(); err != nil { - return fmt.Errorf("closing input: %s", err) - } - - return nil -} - -func sendDone(w io.Writer) error { - e := pktline.NewEncoder(w) - - return e.Encodef("done\n") -} - -// DecodeUploadPackResponse decodes r into a new packp.UploadPackResponse -func DecodeUploadPackResponse(r io.ReadCloser, req *packp.UploadPackRequest) ( - *packp.UploadPackResponse, error, -) { - res := packp.NewUploadPackResponse(req) - if err := res.Decode(r); err != nil { - return nil, fmt.Errorf("error decoding upload-pack response: %s", err) - } - - return res, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common/mocks.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common/mocks.go deleted file mode 100644 index 32a1415e1ad..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common/mocks.go +++ /dev/null @@ -1,46 +0,0 @@ -package common - -import ( - "bytes" - "io" - - gogitioutil "github.com/jesseduffield/go-git/v5/utils/ioutil" - - "github.com/jesseduffield/go-git/v5/plumbing/transport" -) - -type MockCommand struct { - stdin bytes.Buffer - stdout bytes.Buffer - stderr bytes.Buffer -} - -func (c MockCommand) StderrPipe() (io.Reader, error) { - return &c.stderr, nil -} - -func (c MockCommand) StdinPipe() (io.WriteCloser, error) { - return gogitioutil.WriteNopCloser(&c.stdin), nil -} - -func (c MockCommand) StdoutPipe() (io.Reader, error) { - return &c.stdout, nil -} - -func (c MockCommand) Start() error { - return nil -} - -func (c MockCommand) Close() error { - panic("not implemented") -} - -type MockCommander struct { - stderr string -} - -func (c MockCommander) Command(cmd string, ep *transport.Endpoint, auth transport.AuthMethod) (Command, error) { - return &MockCommand{ - stderr: *bytes.NewBufferString(c.stderr), - }, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common/server.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common/server.go deleted file mode 100644 index 1f8dd2404ef..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common/server.go +++ /dev/null @@ -1,73 +0,0 @@ -package common - -import ( - "context" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp" - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -// ServerCommand is used for a single server command execution. -type ServerCommand struct { - Stderr io.Writer - Stdout io.WriteCloser - Stdin io.Reader -} - -func ServeUploadPack(cmd ServerCommand, s transport.UploadPackSession) (err error) { - ioutil.CheckClose(cmd.Stdout, &err) - - ar, err := s.AdvertisedReferences() - if err != nil { - return err - } - - if err := ar.Encode(cmd.Stdout); err != nil { - return err - } - - req := packp.NewUploadPackRequest() - if err := req.Decode(cmd.Stdin); err != nil { - return err - } - - var resp *packp.UploadPackResponse - resp, err = s.UploadPack(context.TODO(), req) - if err != nil { - return err - } - - return resp.Encode(cmd.Stdout) -} - -func ServeReceivePack(cmd ServerCommand, s transport.ReceivePackSession) error { - ar, err := s.AdvertisedReferences() - if err != nil { - return fmt.Errorf("internal error in advertised references: %s", err) - } - - if err := ar.Encode(cmd.Stdout); err != nil { - return fmt.Errorf("error in advertised references encoding: %s", err) - } - - req := packp.NewReferenceUpdateRequest() - if err := req.Decode(cmd.Stdin); err != nil { - return fmt.Errorf("error decoding: %s", err) - } - - rs, err := s.ReceivePack(context.TODO(), req) - if rs != nil { - if err := rs.Encode(cmd.Stdout); err != nil { - return fmt.Errorf("error in encoding report status %s", err) - } - } - - if err != nil { - return fmt.Errorf("error in receive pack: %s", err) - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/server/loader.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/server/loader.go deleted file mode 100644 index ded1cf1aea3..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/server/loader.go +++ /dev/null @@ -1,72 +0,0 @@ -package server - -import ( - "github.com/jesseduffield/go-git/v5/plumbing/cache" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/storage/filesystem" - - "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/osfs" -) - -// DefaultLoader is a filesystem loader ignoring host and resolving paths to /. -var DefaultLoader = NewFilesystemLoader(osfs.New("")) - -// Loader loads repository's storer.Storer based on an optional host and a path. -type Loader interface { - // Load loads a storer.Storer given a transport.Endpoint. - // Returns transport.ErrRepositoryNotFound if the repository does not - // exist. - Load(ep *transport.Endpoint) (storer.Storer, error) -} - -type fsLoader struct { - base billy.Filesystem -} - -// NewFilesystemLoader creates a Loader that ignores host and resolves paths -// with a given base filesystem. -func NewFilesystemLoader(base billy.Filesystem) Loader { - return &fsLoader{base} -} - -// Load looks up the endpoint's path in the base file system and returns a -// storer for it. Returns transport.ErrRepositoryNotFound if a repository does -// not exist in the given path. -func (l *fsLoader) Load(ep *transport.Endpoint) (storer.Storer, error) { - fs, err := l.base.Chroot(ep.Path) - if err != nil { - return nil, err - } - - var bare bool - if _, err := fs.Stat("config"); err == nil { - bare = true - } - - if !bare { - // do not use git.GitDirName due to import cycle - if _, err := fs.Stat(".git"); err != nil { - return nil, transport.ErrRepositoryNotFound - } - } - - return filesystem.NewStorage(fs, cache.NewObjectLRUDefault()), nil -} - -// MapLoader is a Loader that uses a lookup map of storer.Storer by -// transport.Endpoint. -type MapLoader map[string]storer.Storer - -// Load returns a storer.Storer for given a transport.Endpoint by looking it up -// in the map. Returns transport.ErrRepositoryNotFound if the endpoint does not -// exist. -func (l MapLoader) Load(ep *transport.Endpoint) (storer.Storer, error) { - s, ok := l[ep.String()] - if !ok { - return nil, transport.ErrRepositoryNotFound - } - - return s, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/server/server.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/server/server.go deleted file mode 100644 index 2d730ad5743..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/server/server.go +++ /dev/null @@ -1,432 +0,0 @@ -// Package server implements the git server protocol. For most use cases, the -// transport-specific implementations should be used. -package server - -import ( - "context" - "errors" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/packfile" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" - "github.com/jesseduffield/go-git/v5/plumbing/revlist" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -var DefaultServer = NewServer(DefaultLoader) - -type server struct { - loader Loader - handler *handler -} - -// NewServer returns a transport.Transport implementing a git server, -// independent of transport. Each transport must wrap this. -func NewServer(loader Loader) transport.Transport { - return &server{ - loader, - &handler{asClient: false}, - } -} - -// NewClient returns a transport.Transport implementing a client with an -// embedded server. -func NewClient(loader Loader) transport.Transport { - return &server{ - loader, - &handler{asClient: true}, - } -} - -func (s *server) NewUploadPackSession(ep *transport.Endpoint, auth transport.AuthMethod) (transport.UploadPackSession, error) { - sto, err := s.loader.Load(ep) - if err != nil { - return nil, err - } - - return s.handler.NewUploadPackSession(sto) -} - -func (s *server) NewReceivePackSession(ep *transport.Endpoint, auth transport.AuthMethod) (transport.ReceivePackSession, error) { - sto, err := s.loader.Load(ep) - if err != nil { - return nil, err - } - - return s.handler.NewReceivePackSession(sto) -} - -type handler struct { - asClient bool -} - -func (h *handler) NewUploadPackSession(s storer.Storer) (transport.UploadPackSession, error) { - return &upSession{ - session: session{storer: s, asClient: h.asClient}, - }, nil -} - -func (h *handler) NewReceivePackSession(s storer.Storer) (transport.ReceivePackSession, error) { - return &rpSession{ - session: session{storer: s, asClient: h.asClient}, - cmdStatus: map[plumbing.ReferenceName]error{}, - }, nil -} - -type session struct { - storer storer.Storer - caps *capability.List - asClient bool -} - -func (s *session) Close() error { - return nil -} - -func (s *session) SetAuth(transport.AuthMethod) error { - //TODO: deprecate - return nil -} - -func (s *session) checkSupportedCapabilities(cl *capability.List) error { - for _, c := range cl.All() { - if !s.caps.Supports(c) { - return fmt.Errorf("unsupported capability: %s", c) - } - } - - return nil -} - -type upSession struct { - session -} - -func (s *upSession) AdvertisedReferences() (*packp.AdvRefs, error) { - return s.AdvertisedReferencesContext(context.TODO()) -} - -func (s *upSession) AdvertisedReferencesContext(ctx context.Context) (*packp.AdvRefs, error) { - ar := packp.NewAdvRefs() - - if err := s.setSupportedCapabilities(ar.Capabilities); err != nil { - return nil, err - } - - s.caps = ar.Capabilities - - if err := setReferences(s.storer, ar); err != nil { - return nil, err - } - - if err := setHEAD(s.storer, ar); err != nil { - return nil, err - } - - if s.asClient && len(ar.References) == 0 { - return nil, transport.ErrEmptyRemoteRepository - } - - return ar, nil -} - -func (s *upSession) UploadPack(ctx context.Context, req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) { - if req.IsEmpty() { - return nil, transport.ErrEmptyUploadPackRequest - } - - if err := req.Validate(); err != nil { - return nil, err - } - - if s.caps == nil { - s.caps = capability.NewList() - if err := s.setSupportedCapabilities(s.caps); err != nil { - return nil, err - } - } - - if err := s.checkSupportedCapabilities(req.Capabilities); err != nil { - return nil, err - } - - s.caps = req.Capabilities - - if len(req.Shallows) > 0 { - return nil, fmt.Errorf("shallow not supported") - } - - objs, err := s.objectsToUpload(req) - if err != nil { - return nil, err - } - - pr, pw := io.Pipe() - e := packfile.NewEncoder(pw, s.storer, false) - go func() { - // TODO: plumb through a pack window. - _, err := e.Encode(objs, 10) - pw.CloseWithError(err) - }() - - return packp.NewUploadPackResponseWithPackfile(req, - ioutil.NewContextReadCloser(ctx, pr), - ), nil -} - -func (s *upSession) objectsToUpload(req *packp.UploadPackRequest) ([]plumbing.Hash, error) { - haves, err := revlist.Objects(s.storer, req.Haves, nil) - if err != nil { - return nil, err - } - - return revlist.Objects(s.storer, req.Wants, haves) -} - -func (*upSession) setSupportedCapabilities(c *capability.List) error { - if err := c.Set(capability.Agent, capability.DefaultAgent()); err != nil { - return err - } - - if err := c.Set(capability.OFSDelta); err != nil { - return err - } - - return nil -} - -type rpSession struct { - session - cmdStatus map[plumbing.ReferenceName]error - firstErr error - unpackErr error -} - -func (s *rpSession) AdvertisedReferences() (*packp.AdvRefs, error) { - return s.AdvertisedReferencesContext(context.TODO()) -} - -func (s *rpSession) AdvertisedReferencesContext(ctx context.Context) (*packp.AdvRefs, error) { - ar := packp.NewAdvRefs() - - if err := s.setSupportedCapabilities(ar.Capabilities); err != nil { - return nil, err - } - - s.caps = ar.Capabilities - - if err := setReferences(s.storer, ar); err != nil { - return nil, err - } - - if err := setHEAD(s.storer, ar); err != nil { - return nil, err - } - - return ar, nil -} - -var ( - ErrUpdateReference = errors.New("failed to update ref") -) - -func (s *rpSession) ReceivePack(ctx context.Context, req *packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) { - if s.caps == nil { - s.caps = capability.NewList() - if err := s.setSupportedCapabilities(s.caps); err != nil { - return nil, err - } - } - - if err := s.checkSupportedCapabilities(req.Capabilities); err != nil { - return nil, err - } - - s.caps = req.Capabilities - - //TODO: Implement 'atomic' update of references. - - if req.Packfile != nil { - r := ioutil.NewContextReadCloser(ctx, req.Packfile) - if err := s.writePackfile(r); err != nil { - s.unpackErr = err - s.firstErr = err - return s.reportStatus(), err - } - } - - s.updateReferences(req) - return s.reportStatus(), s.firstErr -} - -func (s *rpSession) updateReferences(req *packp.ReferenceUpdateRequest) { - for _, cmd := range req.Commands { - exists, err := referenceExists(s.storer, cmd.Name) - if err != nil { - s.setStatus(cmd.Name, err) - continue - } - - switch cmd.Action() { - case packp.Create: - if exists { - s.setStatus(cmd.Name, ErrUpdateReference) - continue - } - - ref := plumbing.NewHashReference(cmd.Name, cmd.New) - err := s.storer.SetReference(ref) - s.setStatus(cmd.Name, err) - case packp.Delete: - if !exists { - s.setStatus(cmd.Name, ErrUpdateReference) - continue - } - - err := s.storer.RemoveReference(cmd.Name) - s.setStatus(cmd.Name, err) - case packp.Update: - if !exists { - s.setStatus(cmd.Name, ErrUpdateReference) - continue - } - - ref := plumbing.NewHashReference(cmd.Name, cmd.New) - err := s.storer.SetReference(ref) - s.setStatus(cmd.Name, err) - } - } -} - -func (s *rpSession) writePackfile(r io.ReadCloser) error { - if r == nil { - return nil - } - - if err := packfile.UpdateObjectStorage(s.storer, r); err != nil { - _ = r.Close() - return err - } - - return r.Close() -} - -func (s *rpSession) setStatus(ref plumbing.ReferenceName, err error) { - s.cmdStatus[ref] = err - if s.firstErr == nil && err != nil { - s.firstErr = err - } -} - -func (s *rpSession) reportStatus() *packp.ReportStatus { - if !s.caps.Supports(capability.ReportStatus) { - return nil - } - - rs := packp.NewReportStatus() - rs.UnpackStatus = "ok" - - if s.unpackErr != nil { - rs.UnpackStatus = s.unpackErr.Error() - } - - if s.cmdStatus == nil { - return rs - } - - for ref, err := range s.cmdStatus { - msg := "ok" - if err != nil { - msg = err.Error() - } - status := &packp.CommandStatus{ - ReferenceName: ref, - Status: msg, - } - rs.CommandStatuses = append(rs.CommandStatuses, status) - } - - return rs -} - -func (*rpSession) setSupportedCapabilities(c *capability.List) error { - if err := c.Set(capability.Agent, capability.DefaultAgent()); err != nil { - return err - } - - if err := c.Set(capability.OFSDelta); err != nil { - return err - } - - if err := c.Set(capability.DeleteRefs); err != nil { - return err - } - - return c.Set(capability.ReportStatus) -} - -func setHEAD(s storer.Storer, ar *packp.AdvRefs) error { - ref, err := s.Reference(plumbing.HEAD) - if err == plumbing.ErrReferenceNotFound { - return nil - } - - if err != nil { - return err - } - - if ref.Type() == plumbing.SymbolicReference { - if err := ar.AddReference(ref); err != nil { - return nil - } - - ref, err = storer.ResolveReference(s, ref.Target()) - if err == plumbing.ErrReferenceNotFound { - return nil - } - - if err != nil { - return err - } - } - - if ref.Type() != plumbing.HashReference { - return plumbing.ErrInvalidType - } - - h := ref.Hash() - ar.Head = &h - - return nil -} - -func setReferences(s storer.Storer, ar *packp.AdvRefs) error { - //TODO: add peeled references. - iter, err := s.IterReferences() - if err != nil { - return err - } - - return iter.ForEach(func(ref *plumbing.Reference) error { - if ref.Type() != plumbing.HashReference { - return nil - } - - ar.References[ref.Name().String()] = ref.Hash() - return nil - }) -} - -func referenceExists(s storer.ReferenceStorer, n plumbing.ReferenceName) (bool, error) { - _, err := s.Reference(n) - if err == plumbing.ErrReferenceNotFound { - return false, nil - } - - return err == nil, err -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/ssh/auth_method.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/ssh/auth_method.go deleted file mode 100644 index 1fbe028b262..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/ssh/auth_method.go +++ /dev/null @@ -1,313 +0,0 @@ -package ssh - -import ( - "errors" - "fmt" - "os" - "os/user" - "path/filepath" - - "github.com/jesseduffield/go-git/v5/plumbing/transport" - - "github.com/skeema/knownhosts" - sshagent "github.com/xanzy/ssh-agent" - "golang.org/x/crypto/ssh" -) - -const DefaultUsername = "git" - -// AuthMethod is the interface all auth methods for the ssh client -// must implement. The clientConfig method returns the ssh client -// configuration needed to establish an ssh connection. -type AuthMethod interface { - transport.AuthMethod - // ClientConfig should return a valid ssh.ClientConfig to be used to create - // a connection to the SSH server. - ClientConfig() (*ssh.ClientConfig, error) -} - -// The names of the AuthMethod implementations. To be returned by the -// Name() method. Most git servers only allow PublicKeysName and -// PublicKeysCallbackName. -const ( - KeyboardInteractiveName = "ssh-keyboard-interactive" - PasswordName = "ssh-password" - PasswordCallbackName = "ssh-password-callback" - PublicKeysName = "ssh-public-keys" - PublicKeysCallbackName = "ssh-public-key-callback" -) - -// KeyboardInteractive implements AuthMethod by using a -// prompt/response sequence controlled by the server. -type KeyboardInteractive struct { - User string - Challenge ssh.KeyboardInteractiveChallenge - HostKeyCallbackHelper -} - -func (a *KeyboardInteractive) Name() string { - return KeyboardInteractiveName -} - -func (a *KeyboardInteractive) String() string { - return fmt.Sprintf("user: %s, name: %s", a.User, a.Name()) -} - -func (a *KeyboardInteractive) ClientConfig() (*ssh.ClientConfig, error) { - return a.SetHostKeyCallback(&ssh.ClientConfig{ - User: a.User, - Auth: []ssh.AuthMethod{ - a.Challenge, - }, - }) -} - -// Password implements AuthMethod by using the given password. -type Password struct { - User string - Password string - HostKeyCallbackHelper -} - -func (a *Password) Name() string { - return PasswordName -} - -func (a *Password) String() string { - return fmt.Sprintf("user: %s, name: %s", a.User, a.Name()) -} - -func (a *Password) ClientConfig() (*ssh.ClientConfig, error) { - return a.SetHostKeyCallback(&ssh.ClientConfig{ - User: a.User, - Auth: []ssh.AuthMethod{ssh.Password(a.Password)}, - }) -} - -// PasswordCallback implements AuthMethod by using a callback -// to fetch the password. -type PasswordCallback struct { - User string - Callback func() (pass string, err error) - HostKeyCallbackHelper -} - -func (a *PasswordCallback) Name() string { - return PasswordCallbackName -} - -func (a *PasswordCallback) String() string { - return fmt.Sprintf("user: %s, name: %s", a.User, a.Name()) -} - -func (a *PasswordCallback) ClientConfig() (*ssh.ClientConfig, error) { - return a.SetHostKeyCallback(&ssh.ClientConfig{ - User: a.User, - Auth: []ssh.AuthMethod{ssh.PasswordCallback(a.Callback)}, - }) -} - -// PublicKeys implements AuthMethod by using the given key pairs. -type PublicKeys struct { - User string - Signer ssh.Signer - HostKeyCallbackHelper -} - -// NewPublicKeys returns a PublicKeys from a PEM encoded private key. An -// encryption password should be given if the pemBytes contains a password -// encrypted PEM block otherwise password should be empty. It supports RSA -// (PKCS#1), PKCS#8, DSA (OpenSSL), and ECDSA private keys. -func NewPublicKeys(user string, pemBytes []byte, password string) (*PublicKeys, error) { - signer, err := ssh.ParsePrivateKey(pemBytes) - if _, ok := err.(*ssh.PassphraseMissingError); ok { - signer, err = ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(password)) - } - if err != nil { - return nil, err - } - return &PublicKeys{User: user, Signer: signer}, nil -} - -// NewPublicKeysFromFile returns a PublicKeys from a file containing a PEM -// encoded private key. An encryption password should be given if the pemBytes -// contains a password encrypted PEM block otherwise password should be empty. -func NewPublicKeysFromFile(user, pemFile, password string) (*PublicKeys, error) { - bytes, err := os.ReadFile(pemFile) - if err != nil { - return nil, err - } - - return NewPublicKeys(user, bytes, password) -} - -func (a *PublicKeys) Name() string { - return PublicKeysName -} - -func (a *PublicKeys) String() string { - return fmt.Sprintf("user: %s, name: %s", a.User, a.Name()) -} - -func (a *PublicKeys) ClientConfig() (*ssh.ClientConfig, error) { - return a.SetHostKeyCallback(&ssh.ClientConfig{ - User: a.User, - Auth: []ssh.AuthMethod{ssh.PublicKeys(a.Signer)}, - }) -} - -func username() (string, error) { - var username string - if user, err := user.Current(); err == nil { - username = user.Username - } else { - username = os.Getenv("USER") - } - - if username == "" { - return "", errors.New("failed to get username") - } - - return username, nil -} - -// PublicKeysCallback implements AuthMethod by asking a -// ssh.agent.Agent to act as a signer. -type PublicKeysCallback struct { - User string - Callback func() (signers []ssh.Signer, err error) - HostKeyCallbackHelper -} - -// NewSSHAgentAuth returns a PublicKeysCallback based on a SSH agent, it opens -// a pipe with the SSH agent and uses the pipe as the implementer of the public -// key callback function. -func NewSSHAgentAuth(u string) (*PublicKeysCallback, error) { - var err error - if u == "" { - u, err = username() - if err != nil { - return nil, err - } - } - - a, _, err := sshagent.New() - if err != nil { - return nil, fmt.Errorf("error creating SSH agent: %q", err) - } - - return &PublicKeysCallback{ - User: u, - Callback: a.Signers, - }, nil -} - -func (a *PublicKeysCallback) Name() string { - return PublicKeysCallbackName -} - -func (a *PublicKeysCallback) String() string { - return fmt.Sprintf("user: %s, name: %s", a.User, a.Name()) -} - -func (a *PublicKeysCallback) ClientConfig() (*ssh.ClientConfig, error) { - return a.SetHostKeyCallback(&ssh.ClientConfig{ - User: a.User, - Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Callback)}, - }) -} - -// NewKnownHostsCallback returns ssh.HostKeyCallback based on a file based on a -// known_hosts file. http://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT -// -// If list of files is empty, then it will be read from the SSH_KNOWN_HOSTS -// environment variable, example: -// -// /home/foo/custom_known_hosts_file:/etc/custom_known/hosts_file -// -// If SSH_KNOWN_HOSTS is not set the following file locations will be used: -// -// ~/.ssh/known_hosts -// /etc/ssh/ssh_known_hosts -func NewKnownHostsCallback(files ...string) (ssh.HostKeyCallback, error) { - kh, err := newKnownHosts(files...) - return ssh.HostKeyCallback(kh), err -} - -func newKnownHosts(files ...string) (knownhosts.HostKeyCallback, error) { - var err error - - if len(files) == 0 { - if files, err = getDefaultKnownHostsFiles(); err != nil { - return nil, err - } - } - - if files, err = filterKnownHostsFiles(files...); err != nil { - return nil, err - } - - return knownhosts.New(files...) -} - -func getDefaultKnownHostsFiles() ([]string, error) { - files := filepath.SplitList(os.Getenv("SSH_KNOWN_HOSTS")) - if len(files) != 0 { - return files, nil - } - - homeDirPath, err := os.UserHomeDir() - if err != nil { - return nil, err - } - - return []string{ - filepath.Join(homeDirPath, "/.ssh/known_hosts"), - "/etc/ssh/ssh_known_hosts", - }, nil -} - -func filterKnownHostsFiles(files ...string) ([]string, error) { - var out []string - for _, file := range files { - _, err := os.Stat(file) - if err == nil { - out = append(out, file) - continue - } - - if !os.IsNotExist(err) { - return nil, err - } - } - - if len(out) == 0 { - return nil, fmt.Errorf("unable to find any valid known_hosts file, set SSH_KNOWN_HOSTS env variable") - } - - return out, nil -} - -// HostKeyCallbackHelper is a helper that provides common functionality to -// configure HostKeyCallback into a ssh.ClientConfig. -type HostKeyCallbackHelper struct { - // HostKeyCallback is the function type used for verifying server keys. - // If nil default callback will be create using NewKnownHostsCallback - // without argument. - HostKeyCallback ssh.HostKeyCallback -} - -// SetHostKeyCallback sets the field HostKeyCallback in the given cfg. If -// HostKeyCallback is empty a default callback is created using -// NewKnownHostsCallback. -func (m *HostKeyCallbackHelper) SetHostKeyCallback(cfg *ssh.ClientConfig) (*ssh.ClientConfig, error) { - var err error - if m.HostKeyCallback == nil { - if m.HostKeyCallback, err = NewKnownHostsCallback(); err != nil { - return cfg, err - } - } - - cfg.HostKeyCallback = m.HostKeyCallback - return cfg, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/ssh/common.go b/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/ssh/common.go deleted file mode 100644 index d668c9aee42..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/plumbing/transport/ssh/common.go +++ /dev/null @@ -1,276 +0,0 @@ -// Package ssh implements the SSH transport protocol. -package ssh - -import ( - "context" - "fmt" - "net" - "reflect" - "strconv" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/plumbing/transport/internal/common" - "github.com/skeema/knownhosts" - - "github.com/kevinburke/ssh_config" - "golang.org/x/crypto/ssh" - "golang.org/x/net/proxy" -) - -// DefaultClient is the default SSH client. -var DefaultClient = NewClient(nil) - -// DefaultSSHConfig is the reader used to access parameters stored in the -// system's ssh_config files. If nil all the ssh_config are ignored. -var DefaultSSHConfig sshConfig = ssh_config.DefaultUserSettings - -type sshConfig interface { - Get(alias, key string) string -} - -// NewClient creates a new SSH client with an optional *ssh.ClientConfig. -func NewClient(config *ssh.ClientConfig) transport.Transport { - return common.NewClient(&runner{config: config}) -} - -// DefaultAuthBuilder is the function used to create a default AuthMethod, when -// the user doesn't provide any. -var DefaultAuthBuilder = func(user string) (AuthMethod, error) { - return NewSSHAgentAuth(user) -} - -const DefaultPort = 22 - -type runner struct { - config *ssh.ClientConfig -} - -func (r *runner) Command(cmd string, ep *transport.Endpoint, auth transport.AuthMethod) (common.Command, error) { - c := &command{command: cmd, endpoint: ep, config: r.config} - if auth != nil { - if err := c.setAuth(auth); err != nil { - return nil, err - } - } - - if err := c.connect(); err != nil { - return nil, err - } - return c, nil -} - -type command struct { - *ssh.Session - connected bool - command string - endpoint *transport.Endpoint - client *ssh.Client - auth AuthMethod - config *ssh.ClientConfig -} - -func (c *command) setAuth(auth transport.AuthMethod) error { - a, ok := auth.(AuthMethod) - if !ok { - return transport.ErrInvalidAuthMethod - } - - c.auth = a - return nil -} - -func (c *command) Start() error { - return c.Session.Start(endpointToCommand(c.command, c.endpoint)) -} - -// Close closes the SSH session and connection. -func (c *command) Close() error { - if !c.connected { - return nil - } - - c.connected = false - - //XXX: If did read the full packfile, then the session might be already - // closed. - _ = c.Session.Close() - err := c.client.Close() - - //XXX: in go1.16+ we can use errors.Is(err, net.ErrClosed) - if err != nil && strings.HasSuffix(err.Error(), "use of closed network connection") { - return nil - } - - return err -} - -// connect connects to the SSH server, unless a AuthMethod was set with -// SetAuth method, by default uses an auth method based on PublicKeysCallback, -// it connects to a SSH agent, using the address stored in the SSH_AUTH_SOCK -// environment var. -func (c *command) connect() error { - if c.connected { - return transport.ErrAlreadyConnected - } - - if c.auth == nil { - if err := c.setAuthFromEndpoint(); err != nil { - return err - } - } - - var err error - config, err := c.auth.ClientConfig() - if err != nil { - return err - } - hostWithPort := c.getHostWithPort() - if config.HostKeyCallback == nil { - kh, err := newKnownHosts() - if err != nil { - return err - } - config.HostKeyCallback = kh.HostKeyCallback() - config.HostKeyAlgorithms = kh.HostKeyAlgorithms(hostWithPort) - } else if len(config.HostKeyAlgorithms) == 0 { - // Set the HostKeyAlgorithms based on HostKeyCallback. - // For background see https://github.com/go-git/go-git/issues/411 as well as - // https://github.com/golang/go/issues/29286 for root cause. - config.HostKeyAlgorithms = knownhosts.HostKeyAlgorithms(config.HostKeyCallback, hostWithPort) - } - - overrideConfig(c.config, config) - - c.client, err = dial("tcp", hostWithPort, c.endpoint.Proxy, config) - if err != nil { - return err - } - - c.Session, err = c.client.NewSession() - if err != nil { - _ = c.client.Close() - return err - } - - c.connected = true - return nil -} - -func dial(network, addr string, proxyOpts transport.ProxyOptions, config *ssh.ClientConfig) (*ssh.Client, error) { - var ( - ctx = context.Background() - cancel context.CancelFunc - ) - if config.Timeout > 0 { - ctx, cancel = context.WithTimeout(ctx, config.Timeout) - } else { - ctx, cancel = context.WithCancel(ctx) - } - defer cancel() - - var conn net.Conn - var dialErr error - - if proxyOpts.URL != "" { - proxyUrl, err := proxyOpts.FullURL() - if err != nil { - return nil, err - } - dialer, err := proxy.FromURL(proxyUrl, proxy.Direct) - if err != nil { - return nil, err - } - - // Try to use a ContextDialer, but fall back to a Dialer if that goes south. - ctxDialer, ok := dialer.(proxy.ContextDialer) - if !ok { - return nil, fmt.Errorf("expected ssh proxy dialer to be of type %s; got %s", - reflect.TypeOf(ctxDialer), reflect.TypeOf(dialer)) - } - conn, dialErr = ctxDialer.DialContext(ctx, "tcp", addr) - } else { - conn, dialErr = proxy.Dial(ctx, network, addr) - } - if dialErr != nil { - return nil, dialErr - } - - c, chans, reqs, err := ssh.NewClientConn(conn, addr, config) - if err != nil { - return nil, err - } - return ssh.NewClient(c, chans, reqs), nil -} - -func (c *command) getHostWithPort() string { - if addr, found := c.doGetHostWithPortFromSSHConfig(); found { - return addr - } - - host := c.endpoint.Host - port := c.endpoint.Port - if port <= 0 { - port = DefaultPort - } - - return net.JoinHostPort(host, strconv.Itoa(port)) -} - -func (c *command) doGetHostWithPortFromSSHConfig() (addr string, found bool) { - if DefaultSSHConfig == nil { - return - } - - host := c.endpoint.Host - port := c.endpoint.Port - - configHost := DefaultSSHConfig.Get(c.endpoint.Host, "Hostname") - if configHost != "" { - host = configHost - found = true - } - - if !found { - return - } - - configPort := DefaultSSHConfig.Get(c.endpoint.Host, "Port") - if configPort != "" { - if i, err := strconv.Atoi(configPort); err == nil { - port = i - } - } - - addr = net.JoinHostPort(host, strconv.Itoa(port)) - return -} - -func (c *command) setAuthFromEndpoint() error { - var err error - c.auth, err = DefaultAuthBuilder(c.endpoint.User) - return err -} - -func endpointToCommand(cmd string, ep *transport.Endpoint) string { - return fmt.Sprintf("%s '%s'", cmd, ep.Path) -} - -func overrideConfig(overrides *ssh.ClientConfig, c *ssh.ClientConfig) { - if overrides == nil { - return - } - - t := reflect.TypeOf(*c) - vc := reflect.ValueOf(c).Elem() - vo := reflect.ValueOf(overrides).Elem() - - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - vcf := vc.FieldByName(f.Name) - vof := vo.FieldByName(f.Name) - vcf.Set(vof) - } - - *c = vc.Interface().(ssh.ClientConfig) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/prune.go b/vendor/github.com/jesseduffield/go-git/v5/prune.go deleted file mode 100644 index d8772de6f68..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/prune.go +++ /dev/null @@ -1,66 +0,0 @@ -package git - -import ( - "errors" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" -) - -type PruneHandler func(unreferencedObjectHash plumbing.Hash) error -type PruneOptions struct { - // OnlyObjectsOlderThan if set to non-zero value - // selects only objects older than the time provided. - OnlyObjectsOlderThan time.Time - // Handler is called on matching objects - Handler PruneHandler -} - -var ErrLooseObjectsNotSupported = errors.New("loose objects not supported") - -// DeleteObject deletes an object from a repository. -// The type conveniently matches PruneHandler. -func (r *Repository) DeleteObject(hash plumbing.Hash) error { - los, ok := r.Storer.(storer.LooseObjectStorer) - if !ok { - return ErrLooseObjectsNotSupported - } - - return los.DeleteLooseObject(hash) -} - -func (r *Repository) Prune(opt PruneOptions) error { - los, ok := r.Storer.(storer.LooseObjectStorer) - if !ok { - return ErrLooseObjectsNotSupported - } - - pw := newObjectWalker(r.Storer) - err := pw.walkAllRefs() - if err != nil { - return err - } - // Now walk all (loose) objects in storage. - return los.ForEachObjectHash(func(hash plumbing.Hash) error { - // Get out if we have seen this object. - if pw.isSeen(hash) { - return nil - } - // Otherwise it is a candidate for pruning. - // Check out for too new objects next. - if !opt.OnlyObjectsOlderThan.IsZero() { - // Errors here are non-fatal. The object may be e.g. packed. - // Or concurrently deleted. Skip such objects. - t, err := los.LooseObjectTime(hash) - if err != nil { - return nil - } - // Skip too new objects. - if !t.Before(opt.OnlyObjectsOlderThan) { - return nil - } - } - return opt.Handler(hash) - }) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/remote.go b/vendor/github.com/jesseduffield/go-git/v5/remote.go deleted file mode 100644 index 5761089f4ae..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/remote.go +++ /dev/null @@ -1,1535 +0,0 @@ -package git - -import ( - "context" - "errors" - "fmt" - "io" - "strings" - "time" - - "github.com/go-git/go-billy/v5/osfs" - - "github.com/jesseduffield/go-git/v5/config" - "github.com/jesseduffield/go-git/v5/internal/url" - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/cache" - "github.com/jesseduffield/go-git/v5/plumbing/format/packfile" - "github.com/jesseduffield/go-git/v5/plumbing/object" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/capability" - "github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband" - "github.com/jesseduffield/go-git/v5/plumbing/revlist" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/plumbing/transport" - "github.com/jesseduffield/go-git/v5/plumbing/transport/client" - "github.com/jesseduffield/go-git/v5/storage" - "github.com/jesseduffield/go-git/v5/storage/filesystem" - "github.com/jesseduffield/go-git/v5/storage/memory" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -var ( - NoErrAlreadyUpToDate = errors.New("already up-to-date") - ErrDeleteRefNotSupported = errors.New("server does not support delete-refs") - ErrForceNeeded = errors.New("some refs were not updated") - ErrExactSHA1NotSupported = errors.New("server does not support exact SHA1 refspec") - ErrEmptyUrls = errors.New("URLs cannot be empty") -) - -type NoMatchingRefSpecError struct { - refSpec config.RefSpec -} - -func (e NoMatchingRefSpecError) Error() string { - return fmt.Sprintf("couldn't find remote ref %q", e.refSpec.Src()) -} - -func (e NoMatchingRefSpecError) Is(target error) bool { - _, ok := target.(NoMatchingRefSpecError) - return ok -} - -const ( - // This describes the maximum number of commits to walk when - // computing the haves to send to a server, for each ref in the - // repo containing this remote, when not using the multi-ack - // protocol. Setting this to 0 means there is no limit. - maxHavesToVisitPerRef = 100 - - // peeledSuffix is the suffix used to build peeled reference names. - peeledSuffix = "^{}" -) - -// Remote represents a connection to a remote repository. -type Remote struct { - c *config.RemoteConfig - s storage.Storer -} - -// NewRemote creates a new Remote. -// The intended purpose is to use the Remote for tasks such as listing remote references (like using git ls-remote). -// Otherwise Remotes should be created via the use of a Repository. -func NewRemote(s storage.Storer, c *config.RemoteConfig) *Remote { - return &Remote{s: s, c: c} -} - -// Config returns the RemoteConfig object used to instantiate this Remote. -func (r *Remote) Config() *config.RemoteConfig { - return r.c -} - -func (r *Remote) String() string { - var fetch, push string - if len(r.c.URLs) > 0 { - fetch = r.c.URLs[0] - push = r.c.URLs[len(r.c.URLs)-1] - } - - return fmt.Sprintf("%s\t%s (fetch)\n%[1]s\t%[3]s (push)", r.c.Name, fetch, push) -} - -// Push performs a push to the remote. Returns NoErrAlreadyUpToDate if the -// remote was already up-to-date. -func (r *Remote) Push(o *PushOptions) error { - return r.PushContext(context.Background(), o) -} - -// PushContext performs a push to the remote. Returns NoErrAlreadyUpToDate if -// the remote was already up-to-date. -// -// The provided Context must be non-nil. If the context expires before the -// operation is complete, an error is returned. The context only affects the -// transport operations. -func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) { - if err := o.Validate(); err != nil { - return err - } - - if o.RemoteName != r.c.Name { - return fmt.Errorf("remote names don't match: %s != %s", o.RemoteName, r.c.Name) - } - - if o.RemoteURL == "" && len(r.c.URLs) > 0 { - o.RemoteURL = r.c.URLs[len(r.c.URLs)-1] - } - - s, err := newSendPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.CABundle, o.ProxyOptions) - if err != nil { - return err - } - - defer ioutil.CheckClose(s, &err) - - ar, err := s.AdvertisedReferencesContext(ctx) - if err != nil { - return err - } - - remoteRefs, err := ar.AllReferences() - if err != nil { - return err - } - - if err := r.checkRequireRemoteRefs(o.RequireRemoteRefs, remoteRefs); err != nil { - return err - } - - isDelete := false - allDelete := true - for _, rs := range o.RefSpecs { - if rs.IsDelete() { - isDelete = true - } else { - allDelete = false - } - if isDelete && !allDelete { - break - } - } - - if isDelete && !ar.Capabilities.Supports(capability.DeleteRefs) { - return ErrDeleteRefNotSupported - } - - if o.Force { - for i := 0; i < len(o.RefSpecs); i++ { - rs := &o.RefSpecs[i] - if !rs.IsForceUpdate() && !rs.IsDelete() { - o.RefSpecs[i] = config.RefSpec("+" + rs.String()) - } - } - } - - localRefs, err := r.references() - if err != nil { - return err - } - - req, err := r.newReferenceUpdateRequest(o, localRefs, remoteRefs, ar) - if err != nil { - return err - } - - if len(req.Commands) == 0 { - return NoErrAlreadyUpToDate - } - - objects := objectsToPush(req.Commands) - - haves, err := referencesToHashes(remoteRefs) - if err != nil { - return err - } - - stop, err := r.s.Shallow() - if err != nil { - return err - } - - // if we have shallow we should include this as part of the objects that - // we are aware. - haves = append(haves, stop...) - - var hashesToPush []plumbing.Hash - // Avoid the expensive revlist operation if we're only doing deletes. - if !allDelete { - if url.IsLocalEndpoint(o.RemoteURL) { - // If we're are pushing to a local repo, it might be much - // faster to use a local storage layer to get the commits - // to ignore, when calculating the object revlist. - localStorer := filesystem.NewStorage( - osfs.New(o.RemoteURL), cache.NewObjectLRUDefault()) - hashesToPush, err = revlist.ObjectsWithStorageForIgnores( - r.s, localStorer, objects, haves) - } else { - hashesToPush, err = revlist.Objects(r.s, objects, haves) - } - if err != nil { - return err - } - } - - if len(hashesToPush) == 0 { - allDelete = true - for _, command := range req.Commands { - if command.Action() != packp.Delete { - allDelete = false - break - } - } - } - - rs, err := pushHashes(ctx, s, r.s, req, hashesToPush, r.useRefDeltas(ar), allDelete) - if err != nil { - return err - } - - if rs != nil { - if err = rs.Error(); err != nil { - return err - } - } - - return r.updateRemoteReferenceStorage(req) -} - -func (r *Remote) useRefDeltas(ar *packp.AdvRefs) bool { - return !ar.Capabilities.Supports(capability.OFSDelta) -} - -func (r *Remote) addReachableTags(localRefs []*plumbing.Reference, remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest) error { - tags := make(map[plumbing.Reference]struct{}) - // get a list of all tags locally - for _, ref := range localRefs { - if strings.HasPrefix(string(ref.Name()), "refs/tags") { - tags[*ref] = struct{}{} - } - } - - remoteRefIter, err := remoteRefs.IterReferences() - if err != nil { - return err - } - - // remove any that are already on the remote - if err := remoteRefIter.ForEach(func(reference *plumbing.Reference) error { - delete(tags, *reference) - return nil - }); err != nil { - return err - } - - for tag := range tags { - tagObject, err := object.GetObject(r.s, tag.Hash()) - var tagCommit *object.Commit - if err != nil { - return fmt.Errorf("get tag object: %w", err) - } - - if tagObject.Type() != plumbing.TagObject { - continue - } - - annotatedTag, ok := tagObject.(*object.Tag) - if !ok { - return errors.New("could not get annotated tag object") - } - - tagCommit, err = object.GetCommit(r.s, annotatedTag.Target) - if err != nil { - return fmt.Errorf("get annotated tag commit: %w", err) - } - - // only include tags that are reachable from one of the refs - // already being pushed - for _, cmd := range req.Commands { - if tag.Name() == cmd.Name { - continue - } - - if strings.HasPrefix(cmd.Name.String(), "refs/tags") { - continue - } - - c, err := object.GetCommit(r.s, cmd.New) - if err != nil { - return fmt.Errorf("get commit %v: %w", cmd.Name, err) - } - - if isAncestor, err := tagCommit.IsAncestor(c); err == nil && isAncestor { - req.Commands = append(req.Commands, &packp.Command{Name: tag.Name(), New: tag.Hash()}) - } - } - } - - return nil -} - -func (r *Remote) newReferenceUpdateRequest( - o *PushOptions, - localRefs []*plumbing.Reference, - remoteRefs storer.ReferenceStorer, - ar *packp.AdvRefs, -) (*packp.ReferenceUpdateRequest, error) { - req := packp.NewReferenceUpdateRequestFromCapabilities(ar.Capabilities) - - if o.Progress != nil { - req.Progress = o.Progress - if ar.Capabilities.Supports(capability.Sideband64k) { - _ = req.Capabilities.Set(capability.Sideband64k) - } else if ar.Capabilities.Supports(capability.Sideband) { - _ = req.Capabilities.Set(capability.Sideband) - } - } - - if ar.Capabilities.Supports(capability.PushOptions) { - _ = req.Capabilities.Set(capability.PushOptions) - for k, v := range o.Options { - req.Options = append(req.Options, &packp.Option{Key: k, Value: v}) - } - } - - if o.Atomic && ar.Capabilities.Supports(capability.Atomic) { - _ = req.Capabilities.Set(capability.Atomic) - } - - if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune, o.ForceWithLease); err != nil { - - return nil, err - } - - if o.FollowTags { - if err := r.addReachableTags(localRefs, remoteRefs, req); err != nil { - return nil, err - } - } - - return req, nil -} - -func (r *Remote) updateRemoteReferenceStorage( - req *packp.ReferenceUpdateRequest, -) error { - - for _, spec := range r.c.Fetch { - for _, c := range req.Commands { - if !spec.Match(c.Name) { - continue - } - - local := spec.Dst(c.Name) - ref := plumbing.NewHashReference(local, c.New) - switch c.Action() { - case packp.Create, packp.Update: - if err := r.s.SetReference(ref); err != nil { - return err - } - case packp.Delete: - if err := r.s.RemoveReference(local); err != nil { - return err - } - } - } - } - - return nil -} - -// FetchContext fetches references along with the objects necessary to complete -// their histories. -// -// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are -// no changes to be fetched, or an error. -// -// The provided Context must be non-nil. If the context expires before the -// operation is complete, an error is returned. The context only affects the -// transport operations. -func (r *Remote) FetchContext(ctx context.Context, o *FetchOptions) error { - _, err := r.fetch(ctx, o) - return err -} - -// Fetch fetches references along with the objects necessary to complete their -// histories. -// -// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are -// no changes to be fetched, or an error. -func (r *Remote) Fetch(o *FetchOptions) error { - return r.FetchContext(context.Background(), o) -} - -func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.ReferenceStorer, err error) { - if o.RemoteName == "" { - o.RemoteName = r.c.Name - } - - if err = o.Validate(); err != nil { - return nil, err - } - - if len(o.RefSpecs) == 0 { - o.RefSpecs = r.c.Fetch - } - - if o.RemoteURL == "" { - o.RemoteURL = r.c.URLs[0] - } - - s, err := newUploadPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.CABundle, o.ProxyOptions) - if err != nil { - return nil, err - } - - defer ioutil.CheckClose(s, &err) - - ar, err := s.AdvertisedReferencesContext(ctx) - if err != nil { - return nil, err - } - - req, err := r.newUploadPackRequest(o, ar) - if err != nil { - return nil, err - } - - if err := r.isSupportedRefSpec(o.RefSpecs, ar); err != nil { - return nil, err - } - - remoteRefs, err := ar.AllReferences() - if err != nil { - return nil, err - } - - localRefs, err := r.references() - if err != nil { - return nil, err - } - - refs, specToRefs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags) - if err != nil { - return nil, err - } - - if !req.Depth.IsZero() { - req.Shallows, err = r.s.Shallow() - if err != nil { - return nil, fmt.Errorf("existing checkout is not shallow") - } - } - - req.Wants, err = getWants(r.s, refs, o.Depth) - if len(req.Wants) > 0 { - req.Haves, err = getHaves(localRefs, remoteRefs, r.s, o.Depth) - if err != nil { - return nil, err - } - - if err = r.fetchPack(ctx, o, s, req); err != nil { - return nil, err - } - } - - var updatedPrune bool - if o.Prune { - updatedPrune, err = r.pruneRemotes(o.RefSpecs, localRefs, remoteRefs) - if err != nil { - return nil, err - } - } - - updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, specToRefs, o.Tags, o.Force) - if err != nil { - return nil, err - } - - if !updated { - updated, err = depthChanged(req.Shallows, r.s) - if err != nil { - return nil, fmt.Errorf("error checking depth change: %v", err) - } - } - - if !updated && !updatedPrune { - // No references updated, but may have fetched new objects, check if we now have any of our wants - for _, hash := range req.Wants { - exists, _ := objectExists(r.s, hash) - if exists { - updated = true - break - } - } - - if !updated { - return remoteRefs, NoErrAlreadyUpToDate - } - } - - return remoteRefs, nil -} - -func depthChanged(before []plumbing.Hash, s storage.Storer) (bool, error) { - after, err := s.Shallow() - if err != nil { - return false, err - } - - if len(before) != len(after) { - return true, nil - } - - bm := make(map[plumbing.Hash]bool, len(before)) - for _, b := range before { - bm[b] = true - } - for _, a := range after { - if _, ok := bm[a]; !ok { - return true, nil - } - } - - return false, nil -} - -func newUploadPackSession(url string, auth transport.AuthMethod, insecure bool, cabundle []byte, proxyOpts transport.ProxyOptions) (transport.UploadPackSession, error) { - c, ep, err := newClient(url, insecure, cabundle, proxyOpts) - if err != nil { - return nil, err - } - - return c.NewUploadPackSession(ep, auth) -} - -func newSendPackSession(url string, auth transport.AuthMethod, insecure bool, cabundle []byte, proxyOpts transport.ProxyOptions) (transport.ReceivePackSession, error) { - c, ep, err := newClient(url, insecure, cabundle, proxyOpts) - if err != nil { - return nil, err - } - - return c.NewReceivePackSession(ep, auth) -} - -func newClient(url string, insecure bool, cabundle []byte, proxyOpts transport.ProxyOptions) (transport.Transport, *transport.Endpoint, error) { - ep, err := transport.NewEndpoint(url) - if err != nil { - return nil, nil, err - } - ep.InsecureSkipTLS = insecure - ep.CaBundle = cabundle - ep.Proxy = proxyOpts - - c, err := client.NewClient(ep) - if err != nil { - return nil, nil, err - } - - return c, ep, err -} - -func (r *Remote) fetchPack(ctx context.Context, o *FetchOptions, s transport.UploadPackSession, - req *packp.UploadPackRequest) (err error) { - - reader, err := s.UploadPack(ctx, req) - if err != nil { - if errors.Is(err, transport.ErrEmptyUploadPackRequest) { - // XXX: no packfile provided, everything is up-to-date. - return nil - } - return err - } - - defer ioutil.CheckClose(reader, &err) - - if err = r.updateShallow(o, reader); err != nil { - return err - } - - if err = packfile.UpdateObjectStorage(r.s, - buildSidebandIfSupported(req.Capabilities, reader, o.Progress), - ); err != nil { - return err - } - - return err -} - -func (r *Remote) pruneRemotes(specs []config.RefSpec, localRefs []*plumbing.Reference, remoteRefs memory.ReferenceStorage) (bool, error) { - var updatedPrune bool - for _, spec := range specs { - rev := spec.Reverse() - for _, ref := range localRefs { - if !rev.Match(ref.Name()) { - continue - } - _, err := remoteRefs.Reference(rev.Dst(ref.Name())) - if errors.Is(err, plumbing.ErrReferenceNotFound) { - updatedPrune = true - err := r.s.RemoveReference(ref.Name()) - if err != nil { - return false, err - } - } - } - } - return updatedPrune, nil -} - -func (r *Remote) addReferencesToUpdate( - refspecs []config.RefSpec, - localRefs []*plumbing.Reference, - remoteRefs storer.ReferenceStorer, - req *packp.ReferenceUpdateRequest, - prune bool, - forceWithLease *ForceWithLease, -) error { - // This references dictionary will be used to search references by name. - refsDict := make(map[string]*plumbing.Reference) - for _, ref := range localRefs { - refsDict[ref.Name().String()] = ref - } - - for _, rs := range refspecs { - if rs.IsDelete() { - if err := r.deleteReferences(rs, remoteRefs, refsDict, req, false); err != nil { - return err - } - } else { - err := r.addOrUpdateReferences(rs, localRefs, refsDict, remoteRefs, req, forceWithLease) - if err != nil { - return err - } - - if prune { - if err := r.deleteReferences(rs, remoteRefs, refsDict, req, true); err != nil { - return err - } - } - } - } - - return nil -} - -func (r *Remote) addOrUpdateReferences( - rs config.RefSpec, - localRefs []*plumbing.Reference, - refsDict map[string]*plumbing.Reference, - remoteRefs storer.ReferenceStorer, - req *packp.ReferenceUpdateRequest, - forceWithLease *ForceWithLease, -) error { - // If it is not a wildcard refspec we can directly search for the reference - // in the references dictionary. - if !rs.IsWildcard() { - ref, ok := refsDict[rs.Src()] - if !ok { - commit, err := object.GetCommit(r.s, plumbing.NewHash(rs.Src())) - if err == nil { - return r.addCommit(rs, remoteRefs, commit.Hash, req) - } - return nil - } - - return r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease) - } - - for _, ref := range localRefs { - err := r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease) - if err != nil { - return err - } - } - - return nil -} - -func (r *Remote) deleteReferences(rs config.RefSpec, - remoteRefs storer.ReferenceStorer, - refsDict map[string]*plumbing.Reference, - req *packp.ReferenceUpdateRequest, - prune bool) error { - iter, err := remoteRefs.IterReferences() - if err != nil { - return err - } - - return iter.ForEach(func(ref *plumbing.Reference) error { - if ref.Type() != plumbing.HashReference { - return nil - } - - if prune { - rs := rs.Reverse() - if !rs.Match(ref.Name()) { - return nil - } - - if _, ok := refsDict[rs.Dst(ref.Name()).String()]; ok { - return nil - } - } else if rs.Dst("") != ref.Name() { - return nil - } - - cmd := &packp.Command{ - Name: ref.Name(), - Old: ref.Hash(), - New: plumbing.ZeroHash, - } - req.Commands = append(req.Commands, cmd) - return nil - }) -} - -func (r *Remote) addCommit(rs config.RefSpec, - remoteRefs storer.ReferenceStorer, localCommit plumbing.Hash, - req *packp.ReferenceUpdateRequest) error { - - if rs.IsWildcard() { - return errors.New("can't use wildcard together with hash refspecs") - } - - cmd := &packp.Command{ - Name: rs.Dst(""), - Old: plumbing.ZeroHash, - New: localCommit, - } - remoteRef, err := remoteRefs.Reference(cmd.Name) - if err == nil { - if remoteRef.Type() != plumbing.HashReference { - // TODO: check actual git behavior here - return nil - } - - cmd.Old = remoteRef.Hash() - } else if err != plumbing.ErrReferenceNotFound { - return err - } - if cmd.Old == cmd.New { - return nil - } - if !rs.IsForceUpdate() { - if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil { - return err - } - } - - req.Commands = append(req.Commands, cmd) - return nil -} - -func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec, - remoteRefs storer.ReferenceStorer, localRef *plumbing.Reference, - req *packp.ReferenceUpdateRequest, forceWithLease *ForceWithLease) error { - - if localRef.Type() != plumbing.HashReference { - return nil - } - - if !rs.Match(localRef.Name()) { - return nil - } - - cmd := &packp.Command{ - Name: rs.Dst(localRef.Name()), - Old: plumbing.ZeroHash, - New: localRef.Hash(), - } - - remoteRef, err := remoteRefs.Reference(cmd.Name) - if err == nil { - if remoteRef.Type() != plumbing.HashReference { - // TODO: check actual git behavior here - return nil - } - - cmd.Old = remoteRef.Hash() - } else if err != plumbing.ErrReferenceNotFound { - return err - } - - if cmd.Old == cmd.New { - return nil - } - - if forceWithLease != nil { - if err = r.checkForceWithLease(localRef, cmd, forceWithLease); err != nil { - return err - } - } else if !rs.IsForceUpdate() { - if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil { - return err - } - } - - req.Commands = append(req.Commands, cmd) - return nil -} - -func (r *Remote) checkForceWithLease(localRef *plumbing.Reference, cmd *packp.Command, forceWithLease *ForceWithLease) error { - remotePrefix := fmt.Sprintf("refs/remotes/%s/", r.Config().Name) - - ref, err := storer.ResolveReference( - r.s, - plumbing.ReferenceName(remotePrefix+strings.Replace(localRef.Name().String(), "refs/heads/", "", -1))) - if err != nil { - return err - } - - if forceWithLease.RefName.String() == "" || (forceWithLease.RefName == cmd.Name) { - expectedOID := ref.Hash() - - if !forceWithLease.Hash.IsZero() { - expectedOID = forceWithLease.Hash - } - - if cmd.Old != expectedOID { - return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String()) - } - } - - return nil -} - -func (r *Remote) references() ([]*plumbing.Reference, error) { - var localRefs []*plumbing.Reference - - iter, err := r.s.IterReferences() - if err != nil { - return nil, err - } - - for { - ref, err := iter.Next() - if err == io.EOF { - break - } - - if err != nil { - return nil, err - } - - localRefs = append(localRefs, ref) - } - - return localRefs, nil -} - -func getRemoteRefsFromStorer(remoteRefStorer storer.ReferenceStorer) ( - map[plumbing.Hash]bool, error) { - remoteRefs := map[plumbing.Hash]bool{} - iter, err := remoteRefStorer.IterReferences() - if err != nil { - return nil, err - } - err = iter.ForEach(func(ref *plumbing.Reference) error { - if ref.Type() != plumbing.HashReference { - return nil - } - remoteRefs[ref.Hash()] = true - return nil - }) - if err != nil { - return nil, err - } - return remoteRefs, nil -} - -// getHavesFromRef populates the given `haves` map with the given -// reference, and up to `maxHavesToVisitPerRef` ancestor commits. -func getHavesFromRef( - ref *plumbing.Reference, - remoteRefs map[plumbing.Hash]bool, - s storage.Storer, - haves map[plumbing.Hash]bool, - depth int, -) error { - h := ref.Hash() - if haves[h] { - return nil - } - - commit, err := object.GetCommit(s, h) - if err != nil { - if !errors.Is(err, plumbing.ErrObjectNotFound) { - // Ignore the error if this isn't a commit. - haves[ref.Hash()] = true - } - return nil - } - - // Until go-git supports proper commit negotiation during an - // upload pack request, include up to `maxHavesToVisitPerRef` - // commits from the history of each ref. - walker := object.NewCommitPreorderIter(commit, haves, nil) - toVisit := maxHavesToVisitPerRef - // But only need up to the requested depth - if depth > 0 && depth < maxHavesToVisitPerRef { - toVisit = depth - } - // It is safe to ignore any error here as we are just trying to find the references that we already have - // An example of a legitimate failure is we have a shallow clone and don't have the previous commit(s) - _ = walker.ForEach(func(c *object.Commit) error { - haves[c.Hash] = true - toVisit-- - // If toVisit starts out at 0 (indicating there is no - // max), then it will be negative here and we won't stop - // early. - if toVisit == 0 || remoteRefs[c.Hash] { - return storer.ErrStop - } - return nil - }) - - return nil -} - -func getHaves( - localRefs []*plumbing.Reference, - remoteRefStorer storer.ReferenceStorer, - s storage.Storer, - depth int, -) ([]plumbing.Hash, error) { - haves := map[plumbing.Hash]bool{} - - // Build a map of all the remote references, to avoid loading too - // many parent commits for references we know don't need to be - // transferred. - remoteRefs, err := getRemoteRefsFromStorer(remoteRefStorer) - if err != nil { - return nil, err - } - - for _, ref := range localRefs { - if haves[ref.Hash()] { - continue - } - - if ref.Type() != plumbing.HashReference { - continue - } - - err = getHavesFromRef(ref, remoteRefs, s, haves, depth) - if err != nil { - return nil, err - } - } - - var result []plumbing.Hash - for h := range haves { - result = append(result, h) - } - - return result, nil -} - -const refspecAllTags = "+refs/tags/*:refs/tags/*" - -func calculateRefs( - spec []config.RefSpec, - remoteRefs storer.ReferenceStorer, - tagMode TagMode, -) (memory.ReferenceStorage, [][]*plumbing.Reference, error) { - if tagMode == AllTags { - spec = append(spec, refspecAllTags) - } - - refs := make(memory.ReferenceStorage) - // list of references matched for each spec - specToRefs := make([][]*plumbing.Reference, len(spec)) - for i := range spec { - var err error - specToRefs[i], err = doCalculateRefs(spec[i], remoteRefs, refs) - if err != nil { - return nil, nil, err - } - } - - return refs, specToRefs, nil -} - -func doCalculateRefs( - s config.RefSpec, - remoteRefs storer.ReferenceStorer, - refs memory.ReferenceStorage, -) ([]*plumbing.Reference, error) { - var refList []*plumbing.Reference - - if s.IsExactSHA1() { - ref := plumbing.NewHashReference(s.Dst(""), plumbing.NewHash(s.Src())) - - refList = append(refList, ref) - return refList, refs.SetReference(ref) - } - - var matched bool - onMatched := func(ref *plumbing.Reference) error { - if ref.Type() == plumbing.SymbolicReference { - target, err := storer.ResolveReference(remoteRefs, ref.Name()) - if err != nil { - return err - } - - ref = plumbing.NewHashReference(ref.Name(), target.Hash()) - } - - if ref.Type() != plumbing.HashReference { - return nil - } - - matched = true - refList = append(refList, ref) - return refs.SetReference(ref) - } - - var ret error - if s.IsWildcard() { - iter, err := remoteRefs.IterReferences() - if err != nil { - return nil, err - } - ret = iter.ForEach(func(ref *plumbing.Reference) error { - if !s.Match(ref.Name()) { - return nil - } - - return onMatched(ref) - }) - } else { - var resolvedRef *plumbing.Reference - src := s.Src() - resolvedRef, ret = expand_ref(remoteRefs, plumbing.ReferenceName(src)) - if ret == nil { - ret = onMatched(resolvedRef) - } - } - - if !matched && !s.IsWildcard() { - return nil, NoMatchingRefSpecError{refSpec: s} - } - - return refList, ret -} - -func getWants(localStorer storage.Storer, refs memory.ReferenceStorage, depth int) ([]plumbing.Hash, error) { - // If depth is anything other than 1 and the repo has shallow commits then just because we have the commit - // at the reference doesn't mean that we don't still need to fetch the parents - shallow := false - if depth != 1 { - if s, _ := localStorer.Shallow(); len(s) > 0 { - shallow = true - } - } - - wants := map[plumbing.Hash]bool{} - for _, ref := range refs { - hash := ref.Hash() - exists, err := objectExists(localStorer, ref.Hash()) - if err != nil { - return nil, err - } - - if !exists || shallow { - wants[hash] = true - } - } - - var result []plumbing.Hash - for h := range wants { - result = append(result, h) - } - - return result, nil -} - -func objectExists(s storer.EncodedObjectStorer, h plumbing.Hash) (bool, error) { - _, err := s.EncodedObject(plumbing.AnyObject, h) - if err == plumbing.ErrObjectNotFound { - return false, nil - } - - return true, err -} - -func checkFastForwardUpdate(s storer.EncodedObjectStorer, remoteRefs storer.ReferenceStorer, cmd *packp.Command) error { - if cmd.Old == plumbing.ZeroHash { - _, err := remoteRefs.Reference(cmd.Name) - if err == plumbing.ErrReferenceNotFound { - return nil - } - - if err != nil { - return err - } - - return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String()) - } - - ff, err := isFastForward(s, cmd.Old, cmd.New, nil) - if err != nil { - return err - } - - if !ff { - return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String()) - } - - return nil -} - -func isFastForward(s storer.EncodedObjectStorer, old, new plumbing.Hash, earliestShallow *plumbing.Hash) (bool, error) { - c, err := object.GetCommit(s, new) - if err != nil { - return false, err - } - - parentsToIgnore := []plumbing.Hash{} - if earliestShallow != nil { - earliestCommit, err := object.GetCommit(s, *earliestShallow) - if err != nil { - return false, err - } - - parentsToIgnore = earliestCommit.ParentHashes - } - - found := false - // stop iterating at the earliest shallow commit, ignoring its parents - // note: when pull depth is smaller than the number of new changes on the remote, this fails due to missing parents. - // as far as i can tell, without the commits in-between the shallow pull and the earliest shallow, there's no - // real way of telling whether it will be a fast-forward merge. - iter := object.NewCommitPreorderIter(c, nil, parentsToIgnore) - err = iter.ForEach(func(c *object.Commit) error { - if c.Hash != old { - return nil - } - - found = true - return storer.ErrStop - }) - return found, err -} - -func (r *Remote) newUploadPackRequest(o *FetchOptions, - ar *packp.AdvRefs) (*packp.UploadPackRequest, error) { - - req := packp.NewUploadPackRequestFromCapabilities(ar.Capabilities) - - if o.Depth != 0 { - req.Depth = packp.DepthCommits(o.Depth) - if err := req.Capabilities.Set(capability.Shallow); err != nil { - return nil, err - } - } - - if o.Progress == nil && ar.Capabilities.Supports(capability.NoProgress) { - if err := req.Capabilities.Set(capability.NoProgress); err != nil { - return nil, err - } - } - - isWildcard := true - for _, s := range o.RefSpecs { - if !s.IsWildcard() { - isWildcard = false - break - } - } - - if isWildcard && o.Tags == TagFollowing && ar.Capabilities.Supports(capability.IncludeTag) { - if err := req.Capabilities.Set(capability.IncludeTag); err != nil { - return nil, err - } - } - - return req, nil -} - -func (r *Remote) isSupportedRefSpec(refs []config.RefSpec, ar *packp.AdvRefs) error { - var containsIsExact bool - for _, ref := range refs { - if ref.IsExactSHA1() { - containsIsExact = true - } - } - - if !containsIsExact { - return nil - } - - if ar.Capabilities.Supports(capability.AllowReachableSHA1InWant) || - ar.Capabilities.Supports(capability.AllowTipSHA1InWant) { - return nil - } - - return ErrExactSHA1NotSupported -} - -func buildSidebandIfSupported(l *capability.List, reader io.Reader, p sideband.Progress) io.Reader { - var t sideband.Type - - switch { - case l.Supports(capability.Sideband): - t = sideband.Sideband - case l.Supports(capability.Sideband64k): - t = sideband.Sideband64k - default: - return reader - } - - d := sideband.NewDemuxer(t, reader) - d.Progress = p - - return d -} - -func (r *Remote) updateLocalReferenceStorage( - specs []config.RefSpec, - fetchedRefs, remoteRefs memory.ReferenceStorage, - specToRefs [][]*plumbing.Reference, - tagMode TagMode, - force bool, -) (updated bool, err error) { - isWildcard := true - forceNeeded := false - - for i, spec := range specs { - if !spec.IsWildcard() { - isWildcard = false - } - - for _, ref := range specToRefs[i] { - if ref.Type() != plumbing.HashReference { - continue - } - - localName := spec.Dst(ref.Name()) - // If localName doesn't start with "refs/" then treat as a branch. - if !strings.HasPrefix(localName.String(), "refs/") { - localName = plumbing.NewBranchReferenceName(localName.String()) - } - old, _ := storer.ResolveReference(r.s, localName) - new := plumbing.NewHashReference(localName, ref.Hash()) - - // If the ref exists locally as a non-tag and force is not - // specified, only update if the new ref is an ancestor of the old - if old != nil && !old.Name().IsTag() && !force && !spec.IsForceUpdate() { - ff, err := isFastForward(r.s, old.Hash(), new.Hash(), nil) - if err != nil { - return updated, err - } - - if !ff { - forceNeeded = true - continue - } - } - - refUpdated, err := checkAndUpdateReferenceStorerIfNeeded(r.s, new, old) - if err != nil { - return updated, err - } - - if refUpdated { - updated = true - } - } - } - - if tagMode == NoTags { - return updated, nil - } - - tags := fetchedRefs - if isWildcard { - tags = remoteRefs - } - tagUpdated, err := r.buildFetchedTags(tags) - if err != nil { - return updated, err - } - - if tagUpdated { - updated = true - } - - if forceNeeded { - err = ErrForceNeeded - } - - return -} - -func (r *Remote) buildFetchedTags(refs memory.ReferenceStorage) (updated bool, err error) { - for _, ref := range refs { - if !ref.Name().IsTag() { - continue - } - - _, err := r.s.EncodedObject(plumbing.AnyObject, ref.Hash()) - if err == plumbing.ErrObjectNotFound { - continue - } - - if err != nil { - return false, err - } - - refUpdated, err := updateReferenceStorerIfNeeded(r.s, ref) - if err != nil { - return updated, err - } - - if refUpdated { - updated = true - } - } - - return -} - -// List the references on the remote repository. -// The provided Context must be non-nil. If the context expires before the -// operation is complete, an error is returned. The context only affects to the -// transport operations. -func (r *Remote) ListContext(ctx context.Context, o *ListOptions) (rfs []*plumbing.Reference, err error) { - return r.list(ctx, o) -} - -func (r *Remote) List(o *ListOptions) (rfs []*plumbing.Reference, err error) { - timeout := o.Timeout - // Default to the old hardcoded 10s value if a timeout is not explicitly set. - if timeout == 0 { - timeout = 10 - } - if timeout < 0 { - return nil, fmt.Errorf("invalid timeout: %d", timeout) - } - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) - defer cancel() - return r.ListContext(ctx, o) -} - -func (r *Remote) list(ctx context.Context, o *ListOptions) (rfs []*plumbing.Reference, err error) { - if r.c == nil || len(r.c.URLs) == 0 { - return nil, ErrEmptyUrls - } - - s, err := newUploadPackSession(r.c.URLs[0], o.Auth, o.InsecureSkipTLS, o.CABundle, o.ProxyOptions) - if err != nil { - return nil, err - } - - defer ioutil.CheckClose(s, &err) - - ar, err := s.AdvertisedReferencesContext(ctx) - if err != nil { - return nil, err - } - - allRefs, err := ar.AllReferences() - if err != nil { - return nil, err - } - - refs, err := allRefs.IterReferences() - if err != nil { - return nil, err - } - - var resultRefs []*plumbing.Reference - if o.PeelingOption == AppendPeeled || o.PeelingOption == IgnorePeeled { - err = refs.ForEach(func(ref *plumbing.Reference) error { - resultRefs = append(resultRefs, ref) - return nil - }) - if err != nil { - return nil, err - } - } - - if o.PeelingOption == AppendPeeled || o.PeelingOption == OnlyPeeled { - for k, v := range ar.Peeled { - resultRefs = append(resultRefs, plumbing.NewReferenceFromStrings(k+"^{}", v.String())) - } - } - - return resultRefs, nil -} - -func objectsToPush(commands []*packp.Command) []plumbing.Hash { - objects := make([]plumbing.Hash, 0, len(commands)) - for _, cmd := range commands { - if cmd.New == plumbing.ZeroHash { - continue - } - objects = append(objects, cmd.New) - } - return objects -} - -func referencesToHashes(refs storer.ReferenceStorer) ([]plumbing.Hash, error) { - iter, err := refs.IterReferences() - if err != nil { - return nil, err - } - - var hs []plumbing.Hash - err = iter.ForEach(func(ref *plumbing.Reference) error { - if ref.Type() != plumbing.HashReference { - return nil - } - - hs = append(hs, ref.Hash()) - return nil - }) - if err != nil { - return nil, err - } - - return hs, nil -} - -func pushHashes( - ctx context.Context, - sess transport.ReceivePackSession, - s storage.Storer, - req *packp.ReferenceUpdateRequest, - hs []plumbing.Hash, - useRefDeltas bool, - allDelete bool, -) (*packp.ReportStatus, error) { - rd, wr := io.Pipe() - - config, err := s.Config() - if err != nil { - return nil, err - } - - // Set buffer size to 1 so the error message can be written when - // ReceivePack fails. Otherwise the goroutine will be blocked writing - // to the channel. - done := make(chan error, 1) - - if !allDelete { - req.Packfile = rd - go func() { - e := packfile.NewEncoder(wr, s, useRefDeltas) - if _, err := e.Encode(hs, config.Pack.Window); err != nil { - done <- wr.CloseWithError(err) - return - } - - done <- wr.Close() - }() - } else { - close(done) - } - - rs, err := sess.ReceivePack(ctx, req) - if err != nil { - // close the pipe to unlock encode write - _ = rd.Close() - return nil, err - } - - if err := <-done; err != nil { - return nil, err - } - - return rs, nil -} - -func (r *Remote) updateShallow(o *FetchOptions, resp *packp.UploadPackResponse) error { - if o.Depth == 0 || len(resp.Shallows) == 0 { - return nil - } - - shallows, err := r.s.Shallow() - if err != nil { - return err - } - -outer: - for _, s := range resp.Shallows { - for _, oldS := range shallows { - if s == oldS { - continue outer - } - } - shallows = append(shallows, s) - } - - return r.s.SetShallow(shallows) -} - -func (r *Remote) checkRequireRemoteRefs(requires []config.RefSpec, remoteRefs storer.ReferenceStorer) error { - for _, require := range requires { - if require.IsWildcard() { - return fmt.Errorf("wildcards not supported in RequireRemoteRefs, got %s", require.String()) - } - - name := require.Dst("") - remote, err := remoteRefs.Reference(name) - if err != nil { - return fmt.Errorf("remote ref %s required to be %s but is absent", name.String(), require.Src()) - } - - var requireHash string - if require.IsExactSHA1() { - requireHash = require.Src() - } else { - target, err := storer.ResolveReference(remoteRefs, plumbing.ReferenceName(require.Src())) - if err != nil { - return fmt.Errorf("could not resolve ref %s in RequireRemoteRefs", require.Src()) - } - requireHash = target.Hash().String() - } - - if remote.Hash().String() != requireHash { - return fmt.Errorf("remote ref %s required to be %s but is %s", name.String(), requireHash, remote.Hash().String()) - } - } - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/repository.go b/vendor/github.com/jesseduffield/go-git/v5/repository.go deleted file mode 100644 index 92447ee70b7..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/repository.go +++ /dev/null @@ -1,1886 +0,0 @@ -package git - -import ( - "bytes" - "context" - "crypto" - "encoding/hex" - "errors" - "fmt" - "io" - "os" - "path" - "path/filepath" - "strings" - "time" - - "dario.cat/mergo" - "github.com/ProtonMail/go-crypto/openpgp" - "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/osfs" - "github.com/go-git/go-billy/v5/util" - "github.com/jesseduffield/go-git/v5/config" - "github.com/jesseduffield/go-git/v5/internal/path_util" - "github.com/jesseduffield/go-git/v5/internal/revision" - "github.com/jesseduffield/go-git/v5/internal/url" - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/cache" - formatcfg "github.com/jesseduffield/go-git/v5/plumbing/format/config" - "github.com/jesseduffield/go-git/v5/plumbing/format/packfile" - "github.com/jesseduffield/go-git/v5/plumbing/hash" - "github.com/jesseduffield/go-git/v5/plumbing/object" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/storage" - "github.com/jesseduffield/go-git/v5/storage/filesystem" - "github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -// GitDirName this is a special folder where all the git stuff is. -const GitDirName = ".git" - -var ( - // ErrBranchExists an error stating the specified branch already exists - ErrBranchExists = errors.New("branch already exists") - // ErrBranchNotFound an error stating the specified branch does not exist - ErrBranchNotFound = errors.New("branch not found") - // ErrTagExists an error stating the specified tag already exists - ErrTagExists = errors.New("tag already exists") - // ErrTagNotFound an error stating the specified tag does not exist - ErrTagNotFound = errors.New("tag not found") - // ErrFetching is returned when the packfile could not be downloaded - ErrFetching = errors.New("unable to fetch packfile") - - ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch") - ErrRepositoryNotExists = errors.New("repository does not exist") - ErrRepositoryIncomplete = errors.New("repository's commondir path does not exist") - ErrRepositoryAlreadyExists = errors.New("repository already exists") - ErrRemoteNotFound = errors.New("remote not found") - ErrRemoteExists = errors.New("remote already exists") - ErrAnonymousRemoteName = errors.New("anonymous remote name must be 'anonymous'") - ErrWorktreeNotProvided = errors.New("worktree should be provided") - ErrIsBareRepository = errors.New("worktree not available in a bare repository") - ErrUnableToResolveCommit = errors.New("unable to resolve commit") - ErrPackedObjectsNotSupported = errors.New("packed objects not supported") - ErrSHA256NotSupported = errors.New("go-git was not compiled with SHA256 support") - ErrAlternatePathNotSupported = errors.New("alternate path must use the file scheme") - ErrUnsupportedMergeStrategy = errors.New("unsupported merge strategy") - ErrFastForwardMergeNotPossible = errors.New("not possible to fast-forward merge changes") -) - -// Repository represents a git repository -type Repository struct { - Storer storage.Storer - - r map[string]*Remote - wt billy.Filesystem -} - -type InitOptions struct { - // The default branch (e.g. "refs/heads/master") - DefaultBranch plumbing.ReferenceName -} - -// Init creates an empty git repository, based on the given Storer and worktree. -// The worktree Filesystem is optional, if nil a bare repository is created. If -// the given storer is not empty ErrRepositoryAlreadyExists is returned -func Init(s storage.Storer, worktree billy.Filesystem) (*Repository, error) { - options := InitOptions{ - DefaultBranch: plumbing.Master, - } - return InitWithOptions(s, worktree, options) -} - -func InitWithOptions(s storage.Storer, worktree billy.Filesystem, options InitOptions) (*Repository, error) { - if err := initStorer(s); err != nil { - return nil, err - } - - if options.DefaultBranch == "" { - options.DefaultBranch = plumbing.Master - } - - if err := options.DefaultBranch.Validate(); err != nil { - return nil, err - } - - r := newRepository(s, worktree) - _, err := r.Reference(plumbing.HEAD, false) - switch err { - case plumbing.ErrReferenceNotFound: - case nil: - return nil, ErrRepositoryAlreadyExists - default: - return nil, err - } - - h := plumbing.NewSymbolicReference(plumbing.HEAD, options.DefaultBranch) - if err := s.SetReference(h); err != nil { - return nil, err - } - - if worktree == nil { - _ = r.setIsBare(true) - return r, nil - } - - return r, setWorktreeAndStoragePaths(r, worktree) -} - -func initStorer(s storer.Storer) error { - i, ok := s.(storer.Initializer) - if !ok { - return nil - } - - return i.Init() -} - -func setWorktreeAndStoragePaths(r *Repository, worktree billy.Filesystem) error { - type fsBased interface { - Filesystem() billy.Filesystem - } - - // .git file is only created if the storage is file based and the file - // system is osfs.OS - fs, isFSBased := r.Storer.(fsBased) - if !isFSBased { - return nil - } - - if err := createDotGitFile(worktree, fs.Filesystem()); err != nil { - return err - } - - return setConfigWorktree(r, worktree, fs.Filesystem()) -} - -func createDotGitFile(worktree, storage billy.Filesystem) error { - path, err := filepath.Rel(worktree.Root(), storage.Root()) - if err != nil { - path = storage.Root() - } - - if path == GitDirName { - // not needed, since the folder is the default place - return nil - } - - f, err := worktree.Create(GitDirName) - if err != nil { - return err - } - - defer f.Close() - _, err = fmt.Fprintf(f, "gitdir: %s\n", path) - return err -} - -func setConfigWorktree(r *Repository, worktree, storage billy.Filesystem) error { - path, err := filepath.Rel(storage.Root(), worktree.Root()) - if err != nil { - path = worktree.Root() - } - - if path == ".." { - // not needed, since the folder is the default place - return nil - } - - cfg, err := r.Config() - if err != nil { - return err - } - - cfg.Core.Worktree = path - return r.Storer.SetConfig(cfg) -} - -// Open opens a git repository using the given Storer and worktree filesystem, -// if the given storer is complete empty ErrRepositoryNotExists is returned. -// The worktree can be nil when the repository being opened is bare, if the -// repository is a normal one (not bare) and worktree is nil the err -// ErrWorktreeNotProvided is returned -func Open(s storage.Storer, worktree billy.Filesystem) (*Repository, error) { - _, err := s.Reference(plumbing.HEAD) - if err == plumbing.ErrReferenceNotFound { - return nil, ErrRepositoryNotExists - } - - if err != nil { - return nil, err - } - - return newRepository(s, worktree), nil -} - -// Clone a repository into the given Storer and worktree Filesystem with the -// given options, if worktree is nil a bare repository is created. If the given -// storer is not empty ErrRepositoryAlreadyExists is returned. -func Clone(s storage.Storer, worktree billy.Filesystem, o *CloneOptions) (*Repository, error) { - return CloneContext(context.Background(), s, worktree, o) -} - -// CloneContext a repository into the given Storer and worktree Filesystem with -// the given options, if worktree is nil a bare repository is created. If the -// given storer is not empty ErrRepositoryAlreadyExists is returned. -// -// The provided Context must be non-nil. If the context expires before the -// operation is complete, an error is returned. The context only affects the -// transport operations. -func CloneContext( - ctx context.Context, s storage.Storer, worktree billy.Filesystem, o *CloneOptions, -) (*Repository, error) { - r, err := Init(s, worktree) - if err != nil { - return nil, err - } - - return r, r.clone(ctx, o) -} - -// PlainInit create an empty git repository at the given path. isBare defines -// if the repository will have worktree (non-bare) or not (bare), if the path -// is not empty ErrRepositoryAlreadyExists is returned. -func PlainInit(path string, isBare bool) (*Repository, error) { - return PlainInitWithOptions(path, &PlainInitOptions{ - Bare: isBare, - }) -} - -func PlainInitWithOptions(path string, opts *PlainInitOptions) (*Repository, error) { - if opts == nil { - opts = &PlainInitOptions{} - } - - var wt, dot billy.Filesystem - - if opts.Bare { - dot = osfs.New(path) - } else { - wt = osfs.New(path) - dot, _ = wt.Chroot(GitDirName) - } - - s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) - - r, err := InitWithOptions(s, wt, opts.InitOptions) - if err != nil { - return nil, err - } - - cfg, err := r.Config() - if err != nil { - return nil, err - } - - if opts.ObjectFormat != "" { - if opts.ObjectFormat == formatcfg.SHA256 && hash.CryptoType != crypto.SHA256 { - return nil, ErrSHA256NotSupported - } - - cfg.Core.RepositoryFormatVersion = formatcfg.Version_1 - cfg.Extensions.ObjectFormat = opts.ObjectFormat - } - - err = r.Storer.SetConfig(cfg) - if err != nil { - return nil, err - } - - return r, err -} - -// PlainOpen opens a git repository from the given path. It detects if the -// repository is bare or a normal one. If the path doesn't contain a valid -// repository ErrRepositoryNotExists is returned -func PlainOpen(path string) (*Repository, error) { - return PlainOpenWithOptions(path, &PlainOpenOptions{}) -} - -// PlainOpenWithOptions opens a git repository from the given path with specific -// options. See PlainOpen for more info. -func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error) { - dot, wt, err := dotGitToOSFilesystems(path, o.DetectDotGit) - if err != nil { - return nil, err - } - - if _, err := dot.Stat(""); err != nil { - if os.IsNotExist(err) { - return nil, ErrRepositoryNotExists - } - - return nil, err - } - - var repositoryFs billy.Filesystem - - if o.EnableDotGitCommonDir { - dotGitCommon, err := dotGitCommonDirectory(dot) - if err != nil { - return nil, err - } - repositoryFs = dotgit.NewRepositoryFilesystem(dot, dotGitCommon) - } else { - repositoryFs = dot - } - - s := filesystem.NewStorage(repositoryFs, cache.NewObjectLRUDefault()) - - return Open(s, wt) -} - -func dotGitToOSFilesystems(path string, detect bool) (dot, wt billy.Filesystem, err error) { - path, err = path_util.ReplaceTildeWithHome(path) - if err != nil { - return nil, nil, err - } - - if path, err = filepath.Abs(path); err != nil { - return nil, nil, err - } - - var fs billy.Filesystem - var fi os.FileInfo - for { - fs = osfs.New(path) - - pathinfo, err := fs.Stat("/") - if !os.IsNotExist(err) { - if pathinfo == nil { - return nil, nil, err - } - if !pathinfo.IsDir() && detect { - fs = osfs.New(filepath.Dir(path)) - } - } - - fi, err = fs.Stat(GitDirName) - if err == nil { - // no error; stop - break - } - if !os.IsNotExist(err) { - // unknown error; stop - return nil, nil, err - } - if detect { - // try its parent as long as we haven't reached - // the root dir - if dir := filepath.Dir(path); dir != path { - path = dir - continue - } - } - // not detecting via parent dirs and the dir does not exist; - // stop - return fs, nil, nil - } - - if fi.IsDir() { - dot, err = fs.Chroot(GitDirName) - return dot, fs, err - } - - dot, err = dotGitFileToOSFilesystem(path, fs) - if err != nil { - return nil, nil, err - } - - return dot, fs, nil -} - -func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (bfs billy.Filesystem, err error) { - f, err := fs.Open(GitDirName) - if err != nil { - return nil, err - } - defer ioutil.CheckClose(f, &err) - - b, err := io.ReadAll(f) - if err != nil { - return nil, err - } - - line := string(b) - const prefix = "gitdir: " - if !strings.HasPrefix(line, prefix) { - return nil, fmt.Errorf(".git file has no %s prefix", prefix) - } - - gitdir := strings.Split(line[len(prefix):], "\n")[0] - gitdir = strings.TrimSpace(gitdir) - if filepath.IsAbs(gitdir) { - return osfs.New(gitdir), nil - } - - return osfs.New(fs.Join(path, gitdir)), nil -} - -func dotGitCommonDirectory(fs billy.Filesystem) (commonDir billy.Filesystem, err error) { - f, err := fs.Open("commondir") - if os.IsNotExist(err) { - return nil, nil - } - if err != nil { - return nil, err - } - - b, err := io.ReadAll(f) - if err != nil { - return nil, err - } - if len(b) > 0 { - path := strings.TrimSpace(string(b)) - if filepath.IsAbs(path) { - commonDir = osfs.New(path) - } else { - commonDir = osfs.New(filepath.Join(fs.Root(), path)) - } - if _, err := commonDir.Stat(""); err != nil { - if os.IsNotExist(err) { - return nil, ErrRepositoryIncomplete - } - - return nil, err - } - } - - return commonDir, nil -} - -// PlainClone a repository into the path with the given options, isBare defines -// if the new repository will be bare or normal. If the path is not empty -// ErrRepositoryAlreadyExists is returned. -// -// TODO(mcuadros): move isBare to CloneOptions in v5 -func PlainClone(path string, isBare bool, o *CloneOptions) (*Repository, error) { - return PlainCloneContext(context.Background(), path, isBare, o) -} - -// PlainCloneContext a repository into the path with the given options, isBare -// defines if the new repository will be bare or normal. If the path is not empty -// ErrRepositoryAlreadyExists is returned. -// -// The provided Context must be non-nil. If the context expires before the -// operation is complete, an error is returned. The context only affects the -// transport operations. -// -// TODO(mcuadros): move isBare to CloneOptions in v5 -// TODO(smola): refuse upfront to clone on a non-empty directory in v5, see #1027 -func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOptions) (*Repository, error) { - cleanup, cleanupParent, err := checkIfCleanupIsNeeded(path) - if err != nil { - return nil, err - } - - if o.Mirror { - isBare = true - } - r, err := PlainInit(path, isBare) - if err != nil { - return nil, err - } - - err = r.clone(ctx, o) - if err != nil && err != ErrRepositoryAlreadyExists { - if cleanup { - _ = cleanUpDir(path, cleanupParent) - } - } - - return r, err -} - -func newRepository(s storage.Storer, worktree billy.Filesystem) *Repository { - return &Repository{ - Storer: s, - wt: worktree, - r: make(map[string]*Remote), - } -} - -func checkIfCleanupIsNeeded(path string) (cleanup bool, cleanParent bool, err error) { - fi, err := osfs.Default.Stat(path) - if err != nil { - if os.IsNotExist(err) { - return true, true, nil - } - - return false, false, err - } - - if !fi.IsDir() { - return false, false, fmt.Errorf("path is not a directory: %s", path) - } - - files, err := osfs.Default.ReadDir(path) - if err != nil { - return false, false, err - } - - if len(files) == 0 { - return true, false, nil - } - - return false, false, nil -} - -func cleanUpDir(path string, all bool) error { - if all { - return util.RemoveAll(osfs.Default, path) - } - - files, err := osfs.Default.ReadDir(path) - if err != nil { - return err - } - - for _, fi := range files { - if err := util.RemoveAll(osfs.Default, osfs.Default.Join(path, fi.Name())); err != nil { - return err - } - } - - return err -} - -// Config return the repository config. In a filesystem backed repository this -// means read the `.git/config`. -func (r *Repository) Config() (*config.Config, error) { - return r.Storer.Config() -} - -// SetConfig marshall and writes the repository config. In a filesystem backed -// repository this means write the `.git/config`. This function should be called -// with the result of `Repository.Config` and never with the output of -// `Repository.ConfigScoped`. -func (r *Repository) SetConfig(cfg *config.Config) error { - return r.Storer.SetConfig(cfg) -} - -// ConfigScoped returns the repository config, merged with requested scope and -// lower. For example if, config.GlobalScope is given the local and global config -// are returned merged in one config value. -func (r *Repository) ConfigScoped(scope config.Scope) (*config.Config, error) { - // TODO(mcuadros): v6, add this as ConfigOptions.Scoped - - var err error - system := config.NewConfig() - if scope >= config.SystemScope { - system, err = config.LoadConfig(config.SystemScope) - if err != nil { - return nil, err - } - } - - global := config.NewConfig() - if scope >= config.GlobalScope { - global, err = config.LoadConfig(config.GlobalScope) - if err != nil { - return nil, err - } - } - - local, err := r.Storer.Config() - if err != nil { - return nil, err - } - - _ = mergo.Merge(global, system) - _ = mergo.Merge(local, global) - return local, nil -} - -// Remote return a remote if exists -func (r *Repository) Remote(name string) (*Remote, error) { - cfg, err := r.Config() - if err != nil { - return nil, err - } - - c, ok := cfg.Remotes[name] - if !ok { - return nil, ErrRemoteNotFound - } - - return NewRemote(r.Storer, c), nil -} - -// Remotes returns a list with all the remotes -func (r *Repository) Remotes() ([]*Remote, error) { - cfg, err := r.Config() - if err != nil { - return nil, err - } - - remotes := make([]*Remote, len(cfg.Remotes)) - - var i int - for _, c := range cfg.Remotes { - remotes[i] = NewRemote(r.Storer, c) - i++ - } - - return remotes, nil -} - -// CreateRemote creates a new remote -func (r *Repository) CreateRemote(c *config.RemoteConfig) (*Remote, error) { - if err := c.Validate(); err != nil { - return nil, err - } - - remote := NewRemote(r.Storer, c) - - cfg, err := r.Config() - if err != nil { - return nil, err - } - - if _, ok := cfg.Remotes[c.Name]; ok { - return nil, ErrRemoteExists - } - - cfg.Remotes[c.Name] = c - return remote, r.Storer.SetConfig(cfg) -} - -// CreateRemoteAnonymous creates a new anonymous remote. c.Name must be "anonymous". -// It's used like 'git fetch git@github.com:src-d/go-git.git master:master'. -func (r *Repository) CreateRemoteAnonymous(c *config.RemoteConfig) (*Remote, error) { - if err := c.Validate(); err != nil { - return nil, err - } - - if c.Name != "anonymous" { - return nil, ErrAnonymousRemoteName - } - - remote := NewRemote(r.Storer, c) - - return remote, nil -} - -// DeleteRemote delete a remote from the repository and delete the config -func (r *Repository) DeleteRemote(name string) error { - cfg, err := r.Config() - if err != nil { - return err - } - - if _, ok := cfg.Remotes[name]; !ok { - return ErrRemoteNotFound - } - - delete(cfg.Remotes, name) - return r.Storer.SetConfig(cfg) -} - -// Branch return a Branch if exists -func (r *Repository) Branch(name string) (*config.Branch, error) { - cfg, err := r.Config() - if err != nil { - return nil, err - } - - b, ok := cfg.Branches[name] - if !ok { - return nil, ErrBranchNotFound - } - - return b, nil -} - -// CreateBranch creates a new Branch -func (r *Repository) CreateBranch(c *config.Branch) error { - if err := c.Validate(); err != nil { - return err - } - - cfg, err := r.Config() - if err != nil { - return err - } - - if _, ok := cfg.Branches[c.Name]; ok { - return ErrBranchExists - } - - cfg.Branches[c.Name] = c - return r.Storer.SetConfig(cfg) -} - -// DeleteBranch delete a Branch from the repository and delete the config -func (r *Repository) DeleteBranch(name string) error { - cfg, err := r.Config() - if err != nil { - return err - } - - if _, ok := cfg.Branches[name]; !ok { - return ErrBranchNotFound - } - - delete(cfg.Branches, name) - return r.Storer.SetConfig(cfg) -} - -// CreateTag creates a tag. If opts is included, the tag is an annotated tag, -// otherwise a lightweight tag is created. -func (r *Repository) CreateTag(name string, hash plumbing.Hash, opts *CreateTagOptions) (*plumbing.Reference, error) { - rname := plumbing.NewTagReferenceName(name) - if err := rname.Validate(); err != nil { - return nil, err - } - - _, err := r.Storer.Reference(rname) - switch err { - case nil: - // Tag exists, this is an error - return nil, ErrTagExists - case plumbing.ErrReferenceNotFound: - // Tag missing, available for creation, pass this - default: - // Some other error - return nil, err - } - - var target plumbing.Hash - if opts != nil { - target, err = r.createTagObject(name, hash, opts) - if err != nil { - return nil, err - } - } else { - target = hash - } - - ref := plumbing.NewHashReference(rname, target) - if err = r.Storer.SetReference(ref); err != nil { - return nil, err - } - - return ref, nil -} - -func (r *Repository) createTagObject(name string, hash plumbing.Hash, opts *CreateTagOptions) (plumbing.Hash, error) { - if err := opts.Validate(r, hash); err != nil { - return plumbing.ZeroHash, err - } - - rawobj, err := object.GetObject(r.Storer, hash) - if err != nil { - return plumbing.ZeroHash, err - } - - tag := &object.Tag{ - Name: name, - Tagger: *opts.Tagger, - Message: opts.Message, - TargetType: rawobj.Type(), - Target: hash, - } - - if opts.SignKey != nil { - sig, err := r.buildTagSignature(tag, opts.SignKey) - if err != nil { - return plumbing.ZeroHash, err - } - - tag.PGPSignature = sig - } - - obj := r.Storer.NewEncodedObject() - if err := tag.Encode(obj); err != nil { - return plumbing.ZeroHash, err - } - - return r.Storer.SetEncodedObject(obj) -} - -func (r *Repository) buildTagSignature(tag *object.Tag, signKey *openpgp.Entity) (string, error) { - encoded := &plumbing.MemoryObject{} - if err := tag.Encode(encoded); err != nil { - return "", err - } - - rdr, err := encoded.Reader() - if err != nil { - return "", err - } - - var b bytes.Buffer - if err := openpgp.ArmoredDetachSign(&b, signKey, rdr, nil); err != nil { - return "", err - } - - return b.String(), nil -} - -// Tag returns a tag from the repository. -// -// If you want to check to see if the tag is an annotated tag, you can call -// TagObject on the hash of the reference in ForEach: -// -// ref, err := r.Tag("v0.1.0") -// if err != nil { -// // Handle error -// } -// -// obj, err := r.TagObject(ref.Hash()) -// switch err { -// case nil: -// // Tag object present -// case plumbing.ErrObjectNotFound: -// // Not a tag object -// default: -// // Some other error -// } -func (r *Repository) Tag(name string) (*plumbing.Reference, error) { - ref, err := r.Reference(plumbing.ReferenceName(path.Join("refs", "tags", name)), false) - if err != nil { - if err == plumbing.ErrReferenceNotFound { - // Return a friendly error for this one, versus just ReferenceNotFound. - return nil, ErrTagNotFound - } - - return nil, err - } - - return ref, nil -} - -// DeleteTag deletes a tag from the repository. -func (r *Repository) DeleteTag(name string) error { - _, err := r.Tag(name) - if err != nil { - return err - } - - return r.Storer.RemoveReference(plumbing.ReferenceName(path.Join("refs", "tags", name))) -} - -func (r *Repository) resolveToCommitHash(h plumbing.Hash) (plumbing.Hash, error) { - obj, err := r.Storer.EncodedObject(plumbing.AnyObject, h) - if err != nil { - return plumbing.ZeroHash, err - } - switch obj.Type() { - case plumbing.TagObject: - t, err := object.DecodeTag(r.Storer, obj) - if err != nil { - return plumbing.ZeroHash, err - } - return r.resolveToCommitHash(t.Target) - case plumbing.CommitObject: - return h, nil - default: - return plumbing.ZeroHash, ErrUnableToResolveCommit - } -} - -// Clone clones a remote repository -func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { - if err := o.Validate(); err != nil { - return err - } - - c := &config.RemoteConfig{ - Name: o.RemoteName, - URLs: []string{o.URL}, - Fetch: r.cloneRefSpec(o), - Mirror: o.Mirror, - } - - if _, err := r.CreateRemote(c); err != nil { - return err - } - - // When the repository to clone is on the local machine, - // instead of using hard links, automatically setup .git/objects/info/alternates - // to share the objects with the source repository - if o.Shared { - if !url.IsLocalEndpoint(o.URL) { - return ErrAlternatePathNotSupported - } - altpath := o.URL - remoteRepo, err := PlainOpen(o.URL) - if err != nil { - return fmt.Errorf("failed to open remote repository: %w", err) - } - conf, err := remoteRepo.Config() - if err != nil { - return fmt.Errorf("failed to read remote repository configuration: %w", err) - } - if !conf.Core.IsBare { - altpath = path.Join(altpath, GitDirName) - } - if err := r.Storer.AddAlternate(altpath); err != nil { - return fmt.Errorf("failed to add alternate file to git objects dir: %w", err) - } - } - - ref, err := r.fetchAndUpdateReferences(ctx, &FetchOptions{ - RefSpecs: c.Fetch, - Depth: o.Depth, - Auth: o.Auth, - Progress: o.Progress, - Tags: o.Tags, - RemoteName: o.RemoteName, - InsecureSkipTLS: o.InsecureSkipTLS, - CABundle: o.CABundle, - ProxyOptions: o.ProxyOptions, - }, o.ReferenceName) - if err != nil { - return err - } - - if r.wt != nil && !o.NoCheckout { - w, err := r.Worktree() - if err != nil { - return err - } - - head, err := r.Head() - if err != nil { - return err - } - - if err := w.Reset(&ResetOptions{ - Mode: MergeReset, - Commit: head.Hash(), - }); err != nil { - return err - } - - if o.RecurseSubmodules != NoRecurseSubmodules { - if err := w.updateSubmodules(ctx, &SubmoduleUpdateOptions{ - RecurseSubmodules: o.RecurseSubmodules, - Depth: func() int { - if o.ShallowSubmodules { - return 1 - } - return 0 - }(), - Auth: o.Auth, - }); err != nil { - return err - } - } - } - - if err := r.updateRemoteConfigIfNeeded(o, c, ref); err != nil { - return err - } - - if !o.Mirror && ref.Name().IsBranch() { - branchRef := ref.Name() - branchName := strings.Split(string(branchRef), "refs/heads/")[1] - - b := &config.Branch{ - Name: branchName, - Merge: branchRef, - } - - if o.RemoteName == "" { - b.Remote = "origin" - } else { - b.Remote = o.RemoteName - } - - if err := r.CreateBranch(b); err != nil { - return err - } - } - - return nil -} - -const ( - refspecTag = "+refs/tags/%s:refs/tags/%[1]s" - refspecSingleBranch = "+refs/heads/%s:refs/remotes/%s/%[1]s" - refspecSingleBranchHEAD = "+HEAD:refs/remotes/%s/HEAD" -) - -func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec { - switch { - case o.Mirror: - return []config.RefSpec{"+refs/*:refs/*"} - case o.ReferenceName.IsTag(): - return []config.RefSpec{ - config.RefSpec(fmt.Sprintf(refspecTag, o.ReferenceName.Short())), - } - case o.SingleBranch && o.ReferenceName == plumbing.HEAD: - return []config.RefSpec{ - config.RefSpec(fmt.Sprintf(refspecSingleBranchHEAD, o.RemoteName)), - } - case o.SingleBranch: - return []config.RefSpec{ - config.RefSpec(fmt.Sprintf(refspecSingleBranch, o.ReferenceName.Short(), o.RemoteName)), - } - default: - return []config.RefSpec{ - config.RefSpec(fmt.Sprintf(config.DefaultFetchRefSpec, o.RemoteName)), - } - } -} - -func (r *Repository) setIsBare(isBare bool) error { - cfg, err := r.Config() - if err != nil { - return err - } - - cfg.Core.IsBare = isBare - return r.Storer.SetConfig(cfg) -} - -func (r *Repository) updateRemoteConfigIfNeeded(o *CloneOptions, c *config.RemoteConfig, _ *plumbing.Reference) error { - if !o.SingleBranch { - return nil - } - - c.Fetch = r.cloneRefSpec(o) - - cfg, err := r.Config() - if err != nil { - return err - } - - cfg.Remotes[c.Name] = c - return r.Storer.SetConfig(cfg) -} - -func (r *Repository) fetchAndUpdateReferences( - ctx context.Context, o *FetchOptions, ref plumbing.ReferenceName, -) (*plumbing.Reference, error) { - - if err := o.Validate(); err != nil { - return nil, err - } - - remote, err := r.Remote(o.RemoteName) - if err != nil { - return nil, err - } - - objsUpdated := true - remoteRefs, err := remote.fetch(ctx, o) - if err == NoErrAlreadyUpToDate { - objsUpdated = false - } else if err == packfile.ErrEmptyPackfile { - return nil, ErrFetching - } else if err != nil { - return nil, err - } - - resolvedRef, err := expand_ref(remoteRefs, ref) - if err != nil { - return nil, err - } - - refsUpdated, err := r.updateReferences(remote.c.Fetch, resolvedRef) - if err != nil { - return nil, err - } - - if !objsUpdated && !refsUpdated { - return nil, NoErrAlreadyUpToDate - } - - return resolvedRef, nil -} - -func (r *Repository) updateReferences(spec []config.RefSpec, - resolvedRef *plumbing.Reference) (updated bool, err error) { - - if !resolvedRef.Name().IsBranch() { - // Detached HEAD mode - h, err := r.resolveToCommitHash(resolvedRef.Hash()) - if err != nil { - return false, err - } - head := plumbing.NewHashReference(plumbing.HEAD, h) - return updateReferenceStorerIfNeeded(r.Storer, head) - } - - refs := []*plumbing.Reference{ - // Create local reference for the resolved ref - resolvedRef, - // Create local symbolic HEAD - plumbing.NewSymbolicReference(plumbing.HEAD, resolvedRef.Name()), - } - - refs = append(refs, r.calculateRemoteHeadReference(spec, resolvedRef)...) - - for _, ref := range refs { - u, err := updateReferenceStorerIfNeeded(r.Storer, ref) - if err != nil { - return updated, err - } - - if u { - updated = true - } - } - - return -} - -func (r *Repository) calculateRemoteHeadReference(spec []config.RefSpec, - resolvedHead *plumbing.Reference) []*plumbing.Reference { - - var refs []*plumbing.Reference - - // Create resolved HEAD reference with remote prefix if it does not - // exist. This is needed when using single branch and HEAD. - for _, rs := range spec { - name := resolvedHead.Name() - if !rs.Match(name) { - continue - } - - name = rs.Dst(name) - _, err := r.Storer.Reference(name) - if err == plumbing.ErrReferenceNotFound { - refs = append(refs, plumbing.NewHashReference(name, resolvedHead.Hash())) - } - } - - return refs -} - -func checkAndUpdateReferenceStorerIfNeeded( - s storer.ReferenceStorer, r, old *plumbing.Reference) ( - updated bool, err error) { - p, err := s.Reference(r.Name()) - if err != nil && err != plumbing.ErrReferenceNotFound { - return false, err - } - - // we use the string method to compare references, is the easiest way - if err == plumbing.ErrReferenceNotFound || r.String() != p.String() { - if err := s.CheckAndSetReference(r, old); err != nil { - return false, err - } - - return true, nil - } - - return false, nil -} - -func updateReferenceStorerIfNeeded( - s storer.ReferenceStorer, r *plumbing.Reference) (updated bool, err error) { - return checkAndUpdateReferenceStorerIfNeeded(s, r, nil) -} - -// Fetch fetches references along with the objects necessary to complete -// their histories, from the remote named as FetchOptions.RemoteName. -// -// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are -// no changes to be fetched, or an error. -func (r *Repository) Fetch(o *FetchOptions) error { - return r.FetchContext(context.Background(), o) -} - -// FetchContext fetches references along with the objects necessary to complete -// their histories, from the remote named as FetchOptions.RemoteName. -// -// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are -// no changes to be fetched, or an error. -// -// The provided Context must be non-nil. If the context expires before the -// operation is complete, an error is returned. The context only affects the -// transport operations. -func (r *Repository) FetchContext(ctx context.Context, o *FetchOptions) error { - if err := o.Validate(); err != nil { - return err - } - - remote, err := r.Remote(o.RemoteName) - if err != nil { - return err - } - - return remote.FetchContext(ctx, o) -} - -// Push performs a push to the remote. Returns NoErrAlreadyUpToDate if -// the remote was already up-to-date, from the remote named as -// FetchOptions.RemoteName. -func (r *Repository) Push(o *PushOptions) error { - return r.PushContext(context.Background(), o) -} - -// PushContext performs a push to the remote. Returns NoErrAlreadyUpToDate if -// the remote was already up-to-date, from the remote named as -// FetchOptions.RemoteName. -// -// The provided Context must be non-nil. If the context expires before the -// operation is complete, an error is returned. The context only affects the -// transport operations. -func (r *Repository) PushContext(ctx context.Context, o *PushOptions) error { - if err := o.Validate(); err != nil { - return err - } - - remote, err := r.Remote(o.RemoteName) - if err != nil { - return err - } - - return remote.PushContext(ctx, o) -} - -// Log returns the commit history from the given LogOptions. -func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) { - fn := commitIterFunc(o.Order) - if fn == nil { - return nil, fmt.Errorf("invalid Order=%v", o.Order) - } - - var ( - it object.CommitIter - err error - ) - if o.All { - it, err = r.logAll(fn) - } else { - it, err = r.log(o.From, fn) - } - - if err != nil { - return nil, err - } - - if o.FileName != nil { - // for `git log --all` also check parent (if the next commit comes from the real parent) - it = r.logWithFile(*o.FileName, it, o.All) - } - if o.PathFilter != nil { - it = r.logWithPathFilter(o.PathFilter, it, o.All) - } - - if o.Since != nil || o.Until != nil { - limitOptions := object.LogLimitOptions{Since: o.Since, Until: o.Until} - it = r.logWithLimit(it, limitOptions) - } - - return it, nil -} - -func (r *Repository) log(from plumbing.Hash, commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) { - h := from - if from == plumbing.ZeroHash { - head, err := r.Head() - if err != nil { - return nil, err - } - - h = head.Hash() - } - - commit, err := r.CommitObject(h) - if err != nil { - return nil, err - } - return commitIterFunc(commit), nil -} - -func (r *Repository) logAll(commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) { - return object.NewCommitAllIter(r.Storer, commitIterFunc) -} - -func (*Repository) logWithFile(fileName string, commitIter object.CommitIter, checkParent bool) object.CommitIter { - return object.NewCommitPathIterFromIter( - func(path string) bool { - return path == fileName - }, - commitIter, - checkParent, - ) -} - -func (*Repository) logWithPathFilter(pathFilter func(string) bool, commitIter object.CommitIter, checkParent bool) object.CommitIter { - return object.NewCommitPathIterFromIter( - pathFilter, - commitIter, - checkParent, - ) -} - -func (*Repository) logWithLimit(commitIter object.CommitIter, limitOptions object.LogLimitOptions) object.CommitIter { - return object.NewCommitLimitIterFromIter(commitIter, limitOptions) -} - -func commitIterFunc(order LogOrder) func(c *object.Commit) object.CommitIter { - switch order { - case LogOrderDefault: - return func(c *object.Commit) object.CommitIter { - return object.NewCommitPreorderIter(c, nil, nil) - } - case LogOrderDFS: - return func(c *object.Commit) object.CommitIter { - return object.NewCommitPreorderIter(c, nil, nil) - } - case LogOrderDFSPost: - return func(c *object.Commit) object.CommitIter { - return object.NewCommitPostorderIter(c, nil) - } - case LogOrderBSF: - return func(c *object.Commit) object.CommitIter { - return object.NewCommitIterBSF(c, nil, nil) - } - case LogOrderCommitterTime: - return func(c *object.Commit) object.CommitIter { - return object.NewCommitIterCTime(c, nil, nil) - } - } - return nil -} - -// Tags returns all the tag References in a repository. -// -// If you want to check to see if the tag is an annotated tag, you can call -// TagObject on the hash Reference passed in through ForEach: -// -// iter, err := r.Tags() -// if err != nil { -// // Handle error -// } -// -// if err := iter.ForEach(func (ref *plumbing.Reference) error { -// obj, err := r.TagObject(ref.Hash()) -// switch err { -// case nil: -// // Tag object present -// case plumbing.ErrObjectNotFound: -// // Not a tag object -// default: -// // Some other error -// return err -// } -// }); err != nil { -// // Handle outer iterator error -// } -func (r *Repository) Tags() (storer.ReferenceIter, error) { - refIter, err := r.Storer.IterReferences() - if err != nil { - return nil, err - } - - return storer.NewReferenceFilteredIter( - func(r *plumbing.Reference) bool { - return r.Name().IsTag() - }, refIter), nil -} - -// Branches returns all the References that are Branches. -func (r *Repository) Branches() (storer.ReferenceIter, error) { - refIter, err := r.Storer.IterReferences() - if err != nil { - return nil, err - } - - return storer.NewReferenceFilteredIter( - func(r *plumbing.Reference) bool { - return r.Name().IsBranch() - }, refIter), nil -} - -// Notes returns all the References that are notes. For more information: -// https://git-scm.com/docs/git-notes -func (r *Repository) Notes() (storer.ReferenceIter, error) { - refIter, err := r.Storer.IterReferences() - if err != nil { - return nil, err - } - - return storer.NewReferenceFilteredIter( - func(r *plumbing.Reference) bool { - return r.Name().IsNote() - }, refIter), nil -} - -// TreeObject return a Tree with the given hash. If not found -// plumbing.ErrObjectNotFound is returned -func (r *Repository) TreeObject(h plumbing.Hash) (*object.Tree, error) { - return object.GetTree(r.Storer, h) -} - -// TreeObjects returns an unsorted TreeIter with all the trees in the repository -func (r *Repository) TreeObjects() (*object.TreeIter, error) { - iter, err := r.Storer.IterEncodedObjects(plumbing.TreeObject) - if err != nil { - return nil, err - } - - return object.NewTreeIter(r.Storer, iter), nil -} - -// CommitObject return a Commit with the given hash. If not found -// plumbing.ErrObjectNotFound is returned. -func (r *Repository) CommitObject(h plumbing.Hash) (*object.Commit, error) { - return object.GetCommit(r.Storer, h) -} - -// CommitObjects returns an unsorted CommitIter with all the commits in the repository. -func (r *Repository) CommitObjects() (object.CommitIter, error) { - iter, err := r.Storer.IterEncodedObjects(plumbing.CommitObject) - if err != nil { - return nil, err - } - - return object.NewCommitIter(r.Storer, iter), nil -} - -// BlobObject returns a Blob with the given hash. If not found -// plumbing.ErrObjectNotFound is returned. -func (r *Repository) BlobObject(h plumbing.Hash) (*object.Blob, error) { - return object.GetBlob(r.Storer, h) -} - -// BlobObjects returns an unsorted BlobIter with all the blobs in the repository. -func (r *Repository) BlobObjects() (*object.BlobIter, error) { - iter, err := r.Storer.IterEncodedObjects(plumbing.BlobObject) - if err != nil { - return nil, err - } - - return object.NewBlobIter(r.Storer, iter), nil -} - -// TagObject returns a Tag with the given hash. If not found -// plumbing.ErrObjectNotFound is returned. This method only returns -// annotated Tags, no lightweight Tags. -func (r *Repository) TagObject(h plumbing.Hash) (*object.Tag, error) { - return object.GetTag(r.Storer, h) -} - -// TagObjects returns a unsorted TagIter that can step through all of the annotated -// tags in the repository. -func (r *Repository) TagObjects() (*object.TagIter, error) { - iter, err := r.Storer.IterEncodedObjects(plumbing.TagObject) - if err != nil { - return nil, err - } - - return object.NewTagIter(r.Storer, iter), nil -} - -// Object returns an Object with the given hash. If not found -// plumbing.ErrObjectNotFound is returned. -func (r *Repository) Object(t plumbing.ObjectType, h plumbing.Hash) (object.Object, error) { - obj, err := r.Storer.EncodedObject(t, h) - if err != nil { - return nil, err - } - - return object.DecodeObject(r.Storer, obj) -} - -// Objects returns an unsorted ObjectIter with all the objects in the repository. -func (r *Repository) Objects() (*object.ObjectIter, error) { - iter, err := r.Storer.IterEncodedObjects(plumbing.AnyObject) - if err != nil { - return nil, err - } - - return object.NewObjectIter(r.Storer, iter), nil -} - -// Head returns the reference where HEAD is pointing to. -func (r *Repository) Head() (*plumbing.Reference, error) { - return storer.ResolveReference(r.Storer, plumbing.HEAD) -} - -// Reference returns the reference for a given reference name. If resolved is -// true, any symbolic reference will be resolved. -func (r *Repository) Reference(name plumbing.ReferenceName, resolved bool) ( - *plumbing.Reference, error) { - - if resolved { - return storer.ResolveReference(r.Storer, name) - } - - return r.Storer.Reference(name) -} - -// References returns an unsorted ReferenceIter for all references. -func (r *Repository) References() (storer.ReferenceIter, error) { - return r.Storer.IterReferences() -} - -// Worktree returns a worktree based on the given fs, if nil the default -// worktree will be used. -func (r *Repository) Worktree() (*Worktree, error) { - if r.wt == nil { - return nil, ErrIsBareRepository - } - - return &Worktree{r: r, Filesystem: r.wt}, nil -} - -func expand_ref(s storer.ReferenceStorer, ref plumbing.ReferenceName) (*plumbing.Reference, error) { - // For improving troubleshooting, this preserves the error for the provided `ref`, - // and returns the error for that specific ref in case all parse rules fails. - var ret error - for _, rule := range plumbing.RefRevParseRules { - resolvedRef, err := storer.ResolveReference(s, plumbing.ReferenceName(fmt.Sprintf(rule, ref))) - - if err == nil { - return resolvedRef, nil - } else if ret == nil { - ret = err - } - } - - return nil, ret -} - -// ResolveRevision resolves revision to corresponding hash. It will always -// resolve to a commit hash, not a tree or annotated tag. -// -// Implemented resolvers : HEAD, branch, tag, heads/branch, refs/heads/branch, -// refs/tags/tag, refs/remotes/origin/branch, refs/remotes/origin/HEAD, tilde and caret (HEAD~1, master~^, tag~2, ref/heads/master~1, ...), selection by text (HEAD^{/fix nasty bug}), hash (prefix and full) -func (r *Repository) ResolveRevision(in plumbing.Revision) (*plumbing.Hash, error) { - rev := in.String() - if rev == "" { - return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound - } - - p := revision.NewParserFromString(rev) - items, err := p.Parse() - - if err != nil { - return nil, err - } - - var commit *object.Commit - - for _, item := range items { - switch item := item.(type) { - case revision.Ref: - revisionRef := item - - var tryHashes []plumbing.Hash - - tryHashes = append(tryHashes, r.resolveHashPrefix(string(revisionRef))...) - - ref, err := expand_ref(r.Storer, plumbing.ReferenceName(revisionRef)) - if err == nil { - tryHashes = append(tryHashes, ref.Hash()) - } - - // in ambiguous cases, `git rev-parse` will emit a warning, but - // will always return the oid in preference to a ref; we don't have - // the ability to emit a warning here, so (for speed purposes) - // don't bother to detect the ambiguity either, just return in the - // priority that git would. - gotOne := false - for _, hash := range tryHashes { - commitObj, err := r.CommitObject(hash) - if err == nil { - commit = commitObj - gotOne = true - break - } - - tagObj, err := r.TagObject(hash) - if err == nil { - // If the tag target lookup fails here, this most likely - // represents some sort of repo corruption, so let the - // error bubble up. - tagCommit, err := tagObj.Commit() - if err != nil { - return &plumbing.ZeroHash, err - } - commit = tagCommit - gotOne = true - break - } - } - - if !gotOne { - return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound - } - - case revision.CaretPath: - depth := item.Depth - - if depth == 0 { - break - } - - iter := commit.Parents() - - c, err := iter.Next() - - if err != nil { - return &plumbing.ZeroHash, err - } - - if depth == 1 { - commit = c - - break - } - - c, err = iter.Next() - - if err != nil { - return &plumbing.ZeroHash, err - } - - commit = c - case revision.TildePath: - for i := 0; i < item.Depth; i++ { - c, err := commit.Parents().Next() - - if err != nil { - return &plumbing.ZeroHash, err - } - - commit = c - } - case revision.CaretReg: - history := object.NewCommitPreorderIter(commit, nil, nil) - - re := item.Regexp - negate := item.Negate - - var c *object.Commit - - err := history.ForEach(func(hc *object.Commit) error { - if !negate && re.MatchString(hc.Message) { - c = hc - return storer.ErrStop - } - - if negate && !re.MatchString(hc.Message) { - c = hc - return storer.ErrStop - } - - return nil - }) - if err != nil { - return &plumbing.ZeroHash, err - } - - if c == nil { - return &plumbing.ZeroHash, fmt.Errorf("no commit message match regexp: %q", re.String()) - } - - commit = c - } - } - - if commit == nil { - return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound - } - - return &commit.Hash, nil -} - -// resolveHashPrefix returns a list of potential hashes that the given string -// is a prefix of. It quietly swallows errors, returning nil. -func (r *Repository) resolveHashPrefix(hashStr string) []plumbing.Hash { - // Handle complete and partial hashes. - // plumbing.NewHash forces args into a full 20 byte hash, which isn't suitable - // for partial hashes since they will become zero-filled. - - if hashStr == "" { - return nil - } - if len(hashStr) == len(plumbing.ZeroHash)*2 { - // Only a full hash is possible. - hexb, err := hex.DecodeString(hashStr) - if err != nil { - return nil - } - var h plumbing.Hash - copy(h[:], hexb) - return []plumbing.Hash{h} - } - - // Partial hash. - // hex.DecodeString only decodes to complete bytes, so only works with pairs of hex digits. - evenHex := hashStr[:len(hashStr)&^1] - hexb, err := hex.DecodeString(evenHex) - if err != nil { - return nil - } - candidates := expandPartialHash(r.Storer, hexb) - if len(evenHex) == len(hashStr) { - // The prefix was an exact number of bytes. - return candidates - } - // Do another prefix check to ensure the dangling nybble is correct. - var hashes []plumbing.Hash - for _, h := range candidates { - if strings.HasPrefix(h.String(), hashStr) { - hashes = append(hashes, h) - } - } - return hashes -} - -type RepackConfig struct { - // UseRefDeltas configures whether packfile encoder will use reference deltas. - // By default OFSDeltaObject is used. - UseRefDeltas bool - // OnlyDeletePacksOlderThan if set to non-zero value - // selects only objects older than the time provided. - OnlyDeletePacksOlderThan time.Time -} - -func (r *Repository) RepackObjects(cfg *RepackConfig) (err error) { - pos, ok := r.Storer.(storer.PackedObjectStorer) - if !ok { - return ErrPackedObjectsNotSupported - } - - // Get the existing object packs. - hs, err := pos.ObjectPacks() - if err != nil { - return err - } - - // Create a new pack. - nh, err := r.createNewObjectPack(cfg) - if err != nil { - return err - } - - // Delete old packs. - for _, h := range hs { - // Skip if new hash is the same as an old one. - if h == nh { - continue - } - err = pos.DeleteOldObjectPackAndIndex(h, cfg.OnlyDeletePacksOlderThan) - if err != nil { - return err - } - } - - return nil -} - -// Merge merges the reference branch into the current branch. -// -// If the merge is not possible (or supported) returns an error without changing -// the HEAD for the current branch. Possible errors include: -// - The merge strategy is not supported. -// - The specific strategy cannot be used (e.g. using FastForwardMerge when one is not possible). -func (r *Repository) Merge(ref plumbing.Reference, opts MergeOptions) error { - if opts.Strategy != FastForwardMerge { - return ErrUnsupportedMergeStrategy - } - - // Ignore error as not having a shallow list is optional here. - shallowList, _ := r.Storer.Shallow() - var earliestShallow *plumbing.Hash - if len(shallowList) > 0 { - earliestShallow = &shallowList[0] - } - - head, err := r.Head() - if err != nil { - return err - } - - ff, err := isFastForward(r.Storer, head.Hash(), ref.Hash(), earliestShallow) - if err != nil { - return err - } - - if !ff { - return ErrFastForwardMergeNotPossible - } - - return r.Storer.SetReference(plumbing.NewHashReference(head.Name(), ref.Hash())) -} - -// createNewObjectPack is a helper for RepackObjects taking care -// of creating a new pack. It is used so the PackfileWriter -// deferred close has the right scope. -func (r *Repository) createNewObjectPack(cfg *RepackConfig) (h plumbing.Hash, err error) { - ow := newObjectWalker(r.Storer) - err = ow.walkAllRefs() - if err != nil { - return h, err - } - objs := make([]plumbing.Hash, 0, len(ow.seen)) - for h := range ow.seen { - objs = append(objs, h) - } - pfw, ok := r.Storer.(storer.PackfileWriter) - if !ok { - return h, fmt.Errorf("Repository storer is not a storer.PackfileWriter") - } - wc, err := pfw.PackfileWriter() - if err != nil { - return h, err - } - defer ioutil.CheckClose(wc, &err) - scfg, err := r.Config() - if err != nil { - return h, err - } - enc := packfile.NewEncoder(wc, r.Storer, cfg.UseRefDeltas) - h, err = enc.Encode(objs, scfg.Pack.Window) - if err != nil { - return h, err - } - - // Delete the packed, loose objects. - if los, ok := r.Storer.(storer.LooseObjectStorer); ok { - err = los.ForEachObjectHash(func(hash plumbing.Hash) error { - if ow.isSeen(hash) { - err = los.DeleteLooseObject(hash) - if err != nil { - return err - } - } - return nil - }) - if err != nil { - return h, err - } - } - - return h, err -} - -func expandPartialHash(st storer.EncodedObjectStorer, prefix []byte) (hashes []plumbing.Hash) { - // The fast version is implemented by storage/filesystem.ObjectStorage. - type fastIter interface { - HashesWithPrefix(prefix []byte) ([]plumbing.Hash, error) - } - if fi, ok := st.(fastIter); ok { - h, err := fi.HashesWithPrefix(prefix) - if err != nil { - return nil - } - return h - } - - // Slow path. - iter, err := st.IterEncodedObjects(plumbing.AnyObject) - if err != nil { - return nil - } - iter.ForEach(func(obj plumbing.EncodedObject) error { - h := obj.Hash() - if bytes.HasPrefix(h[:], prefix) { - hashes = append(hashes, h) - } - return nil - }) - return -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/signer.go b/vendor/github.com/jesseduffield/go-git/v5/signer.go deleted file mode 100644 index ccc4c6092fb..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/signer.go +++ /dev/null @@ -1,33 +0,0 @@ -package git - -import ( - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" -) - -// signableObject is an object which can be signed. -type signableObject interface { - EncodeWithoutSignature(o plumbing.EncodedObject) error -} - -// Signer is an interface for signing git objects. -// message is a reader containing the encoded object to be signed. -// Implementors should return the encoded signature and an error if any. -// See https://git-scm.com/docs/gitformat-signature for more information. -type Signer interface { - Sign(message io.Reader) ([]byte, error) -} - -func signObject(signer Signer, obj signableObject) ([]byte, error) { - encoded := &plumbing.MemoryObject{} - if err := obj.EncodeWithoutSignature(encoded); err != nil { - return nil, err - } - r, err := encoded.Reader() - if err != nil { - return nil, err - } - - return signer.Sign(r) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/status.go b/vendor/github.com/jesseduffield/go-git/v5/status.go deleted file mode 100644 index 537d8214803..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/status.go +++ /dev/null @@ -1,148 +0,0 @@ -package git - -import ( - "bytes" - "fmt" - "path/filepath" - - mindex "github.com/jesseduffield/go-git/v5/utils/merkletrie/index" - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" -) - -// Status represents the current status of a Worktree. -// The key of the map is the path of the file. -type Status map[string]*FileStatus - -// File returns the FileStatus for a given path, if the FileStatus doesn't -// exists a new FileStatus is added to the map using the path as key. -func (s Status) File(path string) *FileStatus { - if _, ok := (s)[path]; !ok { - s[path] = &FileStatus{Worktree: Untracked, Staging: Untracked} - } - - return s[path] -} - -// IsUntracked checks if file for given path is 'Untracked' -func (s Status) IsUntracked(path string) bool { - stat, ok := (s)[filepath.ToSlash(path)] - return ok && stat.Worktree == Untracked -} - -// IsClean returns true if all the files are in Unmodified status. -func (s Status) IsClean() bool { - for _, status := range s { - if status.Worktree != Unmodified || status.Staging != Unmodified { - return false - } - } - - return true -} - -func (s Status) String() string { - buf := bytes.NewBuffer(nil) - for path, status := range s { - if status.Staging == Unmodified && status.Worktree == Unmodified { - continue - } - - if status.Staging == Renamed { - path = fmt.Sprintf("%s -> %s", path, status.Extra) - } - - fmt.Fprintf(buf, "%c%c %s\n", status.Staging, status.Worktree, path) - } - - return buf.String() -} - -// FileStatus contains the status of a file in the worktree -type FileStatus struct { - // Staging is the status of a file in the staging area - Staging StatusCode - // Worktree is the status of a file in the worktree - Worktree StatusCode - // Extra contains extra information, such as the previous name in a rename - Extra string -} - -// StatusCode status code of a file in the Worktree -type StatusCode byte - -const ( - Unmodified StatusCode = ' ' - Untracked StatusCode = '?' - Modified StatusCode = 'M' - Added StatusCode = 'A' - Deleted StatusCode = 'D' - Renamed StatusCode = 'R' - Copied StatusCode = 'C' - UpdatedButUnmerged StatusCode = 'U' -) - -// StatusStrategy defines the different types of strategies when processing -// the worktree status. -type StatusStrategy int - -const ( - // TODO: (V6) Review the default status strategy. - // TODO: (V6) Review the type used to represent Status, to enable lazy - // processing of statuses going direct to the backing filesystem. - defaultStatusStrategy = Empty - - // Empty starts its status map from empty. Missing entries for a given - // path means that the file is untracked. This causes a known issue (#119) - // whereby unmodified files can be incorrectly reported as untracked. - // - // This can be used when returning the changed state within a modified Worktree. - // For example, to check whether the current worktree is clean. - Empty StatusStrategy = 0 - // Preload goes through all existing nodes from the index and add them to the - // status map as unmodified. This is currently the most reliable strategy - // although it comes at a performance cost in large repositories. - // - // This method is recommended when fetching the status of unmodified files. - // For example, to confirm the status of a specific file that is either - // untracked or unmodified. - Preload StatusStrategy = 1 -) - -func (s StatusStrategy) new(w *Worktree) (Status, error) { - switch s { - case Preload: - return preloadStatus(w) - case Empty: - return make(Status), nil - } - return nil, fmt.Errorf("%w: %+v", ErrUnsupportedStatusStrategy, s) -} - -func preloadStatus(w *Worktree) (Status, error) { - idx, err := w.r.Storer.Index() - if err != nil { - return nil, err - } - - idxRoot := mindex.NewRootNode(idx) - nodes := []noder.Noder{idxRoot} - - status := make(Status) - for len(nodes) > 0 { - var node noder.Noder - node, nodes = nodes[0], nodes[1:] - if node.IsDir() { - children, err := node.Children() - if err != nil { - return nil, err - } - nodes = append(nodes, children...) - continue - } - fs := status.File(node.Name()) - fs.Worktree = Unmodified - fs.Staging = Unmodified - } - - return status, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/config.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/config.go deleted file mode 100644 index fa28d5af844..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/config.go +++ /dev/null @@ -1,48 +0,0 @@ -package filesystem - -import ( - "os" - - "github.com/jesseduffield/go-git/v5/config" - "github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -type ConfigStorage struct { - dir *dotgit.DotGit -} - -func (c *ConfigStorage) Config() (conf *config.Config, err error) { - f, err := c.dir.Config() - if err != nil { - if os.IsNotExist(err) { - return config.NewConfig(), nil - } - - return nil, err - } - - defer ioutil.CheckClose(f, &err) - return config.ReadConfig(f) -} - -func (c *ConfigStorage) SetConfig(cfg *config.Config) (err error) { - if err = cfg.Validate(); err != nil { - return err - } - - f, err := c.dir.ConfigWriter() - if err != nil { - return err - } - - defer ioutil.CheckClose(f, &err) - - b, err := cfg.Marshal() - if err != nil { - return err - } - - _, err = f.Write(b) - return err -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/deltaobject.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/deltaobject.go deleted file mode 100644 index 65bf0d5e7fb..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/deltaobject.go +++ /dev/null @@ -1,37 +0,0 @@ -package filesystem - -import ( - "github.com/jesseduffield/go-git/v5/plumbing" -) - -type deltaObject struct { - plumbing.EncodedObject - base plumbing.Hash - hash plumbing.Hash - size int64 -} - -func newDeltaObject( - obj plumbing.EncodedObject, - hash plumbing.Hash, - base plumbing.Hash, - size int64) plumbing.DeltaObject { - return &deltaObject{ - EncodedObject: obj, - hash: hash, - base: base, - size: size, - } -} - -func (o *deltaObject) BaseHash() plumbing.Hash { - return o.base -} - -func (o *deltaObject) ActualSize() int64 { - return o.size -} - -func (o *deltaObject) ActualHash() plumbing.Hash { - return o.hash -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/dotgit.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/dotgit.go deleted file mode 100644 index 236dec6ed3b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/dotgit.go +++ /dev/null @@ -1,1274 +0,0 @@ -// https://github.com/git/git/blob/master/Documentation/gitrepository-layout.txt -package dotgit - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "os" - "path" - "path/filepath" - "reflect" - "runtime" - "sort" - "strings" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/hash" - "github.com/jesseduffield/go-git/v5/storage" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - - "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/helper/chroot" -) - -const ( - suffix = ".git" - packedRefsPath = "packed-refs" - configPath = "config" - indexPath = "index" - shallowPath = "shallow" - modulePath = "modules" - objectsPath = "objects" - packPath = "pack" - refsPath = "refs" - branchesPath = "branches" - hooksPath = "hooks" - infoPath = "info" - remotesPath = "remotes" - logsPath = "logs" - worktreesPath = "worktrees" - alternatesPath = "alternates" - - tmpPackedRefsPrefix = "._packed-refs" - - packPrefix = "pack-" - packExt = ".pack" - idxExt = ".idx" -) - -var ( - // ErrNotFound is returned by New when the path is not found. - ErrNotFound = errors.New("path not found") - // ErrIdxNotFound is returned by Idxfile when the idx file is not found - ErrIdxNotFound = errors.New("idx file not found") - // ErrPackfileNotFound is returned by Packfile when the packfile is not found - ErrPackfileNotFound = errors.New("packfile not found") - // ErrConfigNotFound is returned by Config when the config is not found - ErrConfigNotFound = errors.New("config file not found") - // ErrPackedRefsDuplicatedRef is returned when a duplicated reference is - // found in the packed-ref file. This is usually the case for corrupted git - // repositories. - ErrPackedRefsDuplicatedRef = errors.New("duplicated ref found in packed-ref file") - // ErrPackedRefsBadFormat is returned when the packed-ref file corrupt. - ErrPackedRefsBadFormat = errors.New("malformed packed-ref") - // ErrSymRefTargetNotFound is returned when a symbolic reference is - // targeting a non-existing object. This usually means the repository - // is corrupt. - ErrSymRefTargetNotFound = errors.New("symbolic reference target not found") - // ErrIsDir is returned when a reference file is attempting to be read, - // but the path specified is a directory. - ErrIsDir = errors.New("reference path is a directory") - // ErrEmptyRefFile is returned when a reference file is attempted to be read, - // but the file is empty - ErrEmptyRefFile = errors.New("ref file is empty") -) - -// Options holds configuration for the storage. -type Options struct { - // ExclusiveAccess means that the filesystem is not modified externally - // while the repo is open. - ExclusiveAccess bool - // KeepDescriptors makes the file descriptors to be reused but they will - // need to be manually closed calling Close(). - KeepDescriptors bool - // AlternatesFS provides the billy filesystem to be used for Git Alternates. - // If none is provided, it falls back to using the underlying instance used for - // DotGit. - AlternatesFS billy.Filesystem -} - -// The DotGit type represents a local git repository on disk. This -// type is not zero-value-safe, use the New function to initialize it. -type DotGit struct { - options Options - fs billy.Filesystem - - // incoming object directory information - incomingChecked bool - incomingDirName string - - objectList []plumbing.Hash // sorted - objectMap map[plumbing.Hash]struct{} - packList []plumbing.Hash - packMap map[plumbing.Hash]struct{} - - files map[plumbing.Hash]billy.File -} - -// New returns a DotGit value ready to be used. The path argument must -// be the absolute path of a git repository directory (e.g. -// "/foo/bar/.git"). -func New(fs billy.Filesystem) *DotGit { - return NewWithOptions(fs, Options{}) -} - -// NewWithOptions sets non default configuration options. -// See New for complete help. -func NewWithOptions(fs billy.Filesystem, o Options) *DotGit { - return &DotGit{ - options: o, - fs: fs, - } -} - -// Initialize creates all the folder scaffolding. -func (d *DotGit) Initialize() error { - mustExists := []string{ - d.fs.Join("objects", "info"), - d.fs.Join("objects", "pack"), - d.fs.Join("refs", "heads"), - d.fs.Join("refs", "tags"), - } - - for _, path := range mustExists { - _, err := d.fs.Stat(path) - if err == nil { - continue - } - - if !os.IsNotExist(err) { - return err - } - - if err := d.fs.MkdirAll(path, os.ModeDir|os.ModePerm); err != nil { - return err - } - } - - return nil -} - -// Close closes all opened files. -func (d *DotGit) Close() error { - var firstError error - if d.files != nil { - for _, f := range d.files { - err := f.Close() - if err != nil && firstError == nil { - firstError = err - continue - } - } - - d.files = nil - } - - if firstError != nil { - return firstError - } - - return nil -} - -// ConfigWriter returns a file pointer for write to the config file -func (d *DotGit) ConfigWriter() (billy.File, error) { - return d.fs.Create(configPath) -} - -// Config returns a file pointer for read to the config file -func (d *DotGit) Config() (billy.File, error) { - return d.fs.Open(configPath) -} - -// IndexWriter returns a file pointer for write to the index file -func (d *DotGit) IndexWriter() (billy.File, error) { - return d.fs.Create(indexPath) -} - -// Index returns a file pointer for read to the index file -func (d *DotGit) Index() (billy.File, error) { - return d.fs.Open(indexPath) -} - -// ShallowWriter returns a file pointer for write to the shallow file -func (d *DotGit) ShallowWriter() (billy.File, error) { - return d.fs.Create(shallowPath) -} - -// Shallow returns a file pointer for read to the shallow file -func (d *DotGit) Shallow() (billy.File, error) { - f, err := d.fs.Open(shallowPath) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - - return nil, err - } - - return f, nil -} - -// NewObjectPack return a writer for a new packfile, it saves the packfile to -// disk and also generates and save the index for the given packfile. -func (d *DotGit) NewObjectPack() (*PackWriter, error) { - d.cleanPackList() - return newPackWrite(d.fs) -} - -// ObjectPacks returns the list of availables packfiles -func (d *DotGit) ObjectPacks() ([]plumbing.Hash, error) { - if !d.options.ExclusiveAccess { - return d.objectPacks() - } - - err := d.genPackList() - if err != nil { - return nil, err - } - - return d.packList, nil -} - -func (d *DotGit) objectPacks() ([]plumbing.Hash, error) { - packDir := d.fs.Join(objectsPath, packPath) - files, err := d.fs.ReadDir(packDir) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - - return nil, err - } - - var packs []plumbing.Hash - for _, f := range files { - n := f.Name() - if !strings.HasSuffix(n, packExt) || !strings.HasPrefix(n, packPrefix) { - continue - } - - h := plumbing.NewHash(n[5 : len(n)-5]) // pack-(hash).pack - if h.IsZero() { - // Ignore files with badly-formatted names. - continue - } - packs = append(packs, h) - } - - return packs, nil -} - -func (d *DotGit) objectPackPath(hash plumbing.Hash, extension string) string { - return d.fs.Join(objectsPath, packPath, fmt.Sprintf("pack-%s.%s", hash.String(), extension)) -} - -func (d *DotGit) objectPackOpen(hash plumbing.Hash, extension string) (billy.File, error) { - if d.options.KeepDescriptors && extension == "pack" { - if d.files == nil { - d.files = make(map[plumbing.Hash]billy.File) - } - - f, ok := d.files[hash] - if ok { - return f, nil - } - } - - err := d.hasPack(hash) - if err != nil { - return nil, err - } - - path := d.objectPackPath(hash, extension) - pack, err := d.fs.Open(path) - if err != nil { - if os.IsNotExist(err) { - return nil, ErrPackfileNotFound - } - - return nil, err - } - - if d.options.KeepDescriptors && extension == "pack" { - d.files[hash] = pack - } - - return pack, nil -} - -// ObjectPack returns a fs.File of the given packfile -func (d *DotGit) ObjectPack(hash plumbing.Hash) (billy.File, error) { - err := d.hasPack(hash) - if err != nil { - return nil, err - } - - return d.objectPackOpen(hash, `pack`) -} - -// ObjectPackIdx returns a fs.File of the index file for a given packfile -func (d *DotGit) ObjectPackIdx(hash plumbing.Hash) (billy.File, error) { - err := d.hasPack(hash) - if err != nil { - return nil, err - } - - return d.objectPackOpen(hash, `idx`) -} - -func (d *DotGit) DeleteOldObjectPackAndIndex(hash plumbing.Hash, t time.Time) error { - d.cleanPackList() - - path := d.objectPackPath(hash, `pack`) - if !t.IsZero() { - fi, err := d.fs.Stat(path) - if err != nil { - return err - } - // too new, skip deletion. - if !fi.ModTime().Before(t) { - return nil - } - } - err := d.fs.Remove(path) - if err != nil { - return err - } - return d.fs.Remove(d.objectPackPath(hash, `idx`)) -} - -// NewObject return a writer for a new object file. -func (d *DotGit) NewObject() (*ObjectWriter, error) { - d.cleanObjectList() - - return newObjectWriter(d.fs) -} - -// ObjectsWithPrefix returns the hashes of objects that have the given prefix. -func (d *DotGit) ObjectsWithPrefix(prefix []byte) ([]plumbing.Hash, error) { - // Handle edge cases. - if len(prefix) < 1 { - return d.Objects() - } else if len(prefix) > len(plumbing.ZeroHash) { - return nil, nil - } - - if d.options.ExclusiveAccess { - err := d.genObjectList() - if err != nil { - return nil, err - } - - // Rely on d.objectList being sorted. - // Figure out the half-open interval defined by the prefix. - first := sort.Search(len(d.objectList), func(i int) bool { - // Same as plumbing.HashSlice.Less. - return bytes.Compare(d.objectList[i][:], prefix) >= 0 - }) - lim := len(d.objectList) - if limPrefix, overflow := incBytes(prefix); !overflow { - lim = sort.Search(len(d.objectList), func(i int) bool { - // Same as plumbing.HashSlice.Less. - return bytes.Compare(d.objectList[i][:], limPrefix) >= 0 - }) - } - return d.objectList[first:lim], nil - } - - // This is the slow path. - var objects []plumbing.Hash - var n int - err := d.ForEachObjectHash(func(hash plumbing.Hash) error { - n++ - if bytes.HasPrefix(hash[:], prefix) { - objects = append(objects, hash) - } - return nil - }) - if err != nil { - return nil, err - } - return objects, nil -} - -// Objects returns a slice with the hashes of objects found under the -// .git/objects/ directory. -func (d *DotGit) Objects() ([]plumbing.Hash, error) { - if d.options.ExclusiveAccess { - err := d.genObjectList() - if err != nil { - return nil, err - } - - return d.objectList, nil - } - - var objects []plumbing.Hash - err := d.ForEachObjectHash(func(hash plumbing.Hash) error { - objects = append(objects, hash) - return nil - }) - if err != nil { - return nil, err - } - return objects, nil -} - -// ForEachObjectHash iterates over the hashes of objects found under the -// .git/objects/ directory and executes the provided function. -func (d *DotGit) ForEachObjectHash(fun func(plumbing.Hash) error) error { - if !d.options.ExclusiveAccess { - return d.forEachObjectHash(fun) - } - - err := d.genObjectList() - if err != nil { - return err - } - - for _, h := range d.objectList { - err := fun(h) - if err != nil { - return err - } - } - - return nil -} - -func (d *DotGit) forEachObjectHash(fun func(plumbing.Hash) error) error { - files, err := d.fs.ReadDir(objectsPath) - if err != nil { - if os.IsNotExist(err) { - return nil - } - - return err - } - - for _, f := range files { - if f.IsDir() && len(f.Name()) == 2 && isHex(f.Name()) { - base := f.Name() - d, err := d.fs.ReadDir(d.fs.Join(objectsPath, base)) - if err != nil { - return err - } - - for _, o := range d { - h := plumbing.NewHash(base + o.Name()) - if h.IsZero() { - // Ignore files with badly-formatted names. - continue - } - err = fun(h) - if err != nil { - return err - } - } - } - } - - return nil -} - -func (d *DotGit) cleanObjectList() { - d.objectMap = nil - d.objectList = nil -} - -func (d *DotGit) genObjectList() error { - if d.objectMap != nil { - return nil - } - - d.objectMap = make(map[plumbing.Hash]struct{}) - populate := func(h plumbing.Hash) error { - d.objectList = append(d.objectList, h) - d.objectMap[h] = struct{}{} - - return nil - } - if err := d.forEachObjectHash(populate); err != nil { - return err - } - plumbing.HashesSort(d.objectList) - return nil -} - -func (d *DotGit) hasObject(h plumbing.Hash) error { - if !d.options.ExclusiveAccess { - return nil - } - - err := d.genObjectList() - if err != nil { - return err - } - - _, ok := d.objectMap[h] - if !ok { - return plumbing.ErrObjectNotFound - } - - return nil -} - -func (d *DotGit) cleanPackList() { - d.packMap = nil - d.packList = nil -} - -func (d *DotGit) genPackList() error { - if d.packMap != nil { - return nil - } - - op, err := d.objectPacks() - if err != nil { - return err - } - - d.packMap = make(map[plumbing.Hash]struct{}) - d.packList = nil - - for _, h := range op { - d.packList = append(d.packList, h) - d.packMap[h] = struct{}{} - } - - return nil -} - -func (d *DotGit) hasPack(h plumbing.Hash) error { - if !d.options.ExclusiveAccess { - return nil - } - - err := d.genPackList() - if err != nil { - return err - } - - _, ok := d.packMap[h] - if !ok { - return ErrPackfileNotFound - } - - return nil -} - -func (d *DotGit) objectPath(h plumbing.Hash) string { - hex := h.String() - return d.fs.Join(objectsPath, hex[0:2], hex[2:hash.HexSize]) -} - -// incomingObjectPath is intended to add support for a git pre-receive hook -// to be written it adds support for go-git to find objects in an "incoming" -// directory, so that the library can be used to write a pre-receive hook -// that deals with the incoming objects. -// -// More on git hooks found here : https://git-scm.com/docs/githooks -// More on 'quarantine'/incoming directory here: -// -// https://git-scm.com/docs/git-receive-pack -func (d *DotGit) incomingObjectPath(h plumbing.Hash) string { - hString := h.String() - - if d.incomingDirName == "" { - return d.fs.Join(objectsPath, hString[0:2], hString[2:hash.HexSize]) - } - - return d.fs.Join(objectsPath, d.incomingDirName, hString[0:2], hString[2:hash.HexSize]) -} - -// hasIncomingObjects searches for an incoming directory and keeps its name -// so it doesn't have to be found each time an object is accessed. -func (d *DotGit) hasIncomingObjects() bool { - if !d.incomingChecked { - directoryContents, err := d.fs.ReadDir(objectsPath) - if err == nil { - for _, file := range directoryContents { - if file.IsDir() && (strings.HasPrefix(file.Name(), "tmp_objdir-incoming-") || - // Before Git 2.35 incoming commits directory had another prefix - strings.HasPrefix(file.Name(), "incoming-")) { - d.incomingDirName = file.Name() - } - } - } - - d.incomingChecked = true - } - - return d.incomingDirName != "" -} - -// Object returns a fs.File pointing the object file, if exists -func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) { - err := d.hasObject(h) - if err != nil { - return nil, err - } - - obj1, err1 := d.fs.Open(d.objectPath(h)) - if os.IsNotExist(err1) && d.hasIncomingObjects() { - obj2, err2 := d.fs.Open(d.incomingObjectPath(h)) - if err2 != nil { - return obj1, err1 - } - return obj2, err2 - } - return obj1, err1 -} - -// ObjectStat returns a os.FileInfo pointing the object file, if exists -func (d *DotGit) ObjectStat(h plumbing.Hash) (os.FileInfo, error) { - err := d.hasObject(h) - if err != nil { - return nil, err - } - - obj1, err1 := d.fs.Stat(d.objectPath(h)) - if os.IsNotExist(err1) && d.hasIncomingObjects() { - obj2, err2 := d.fs.Stat(d.incomingObjectPath(h)) - if err2 != nil { - return obj1, err1 - } - return obj2, err2 - } - return obj1, err1 -} - -// ObjectDelete removes the object file, if exists -func (d *DotGit) ObjectDelete(h plumbing.Hash) error { - d.cleanObjectList() - - err1 := d.fs.Remove(d.objectPath(h)) - if os.IsNotExist(err1) && d.hasIncomingObjects() { - err2 := d.fs.Remove(d.incomingObjectPath(h)) - if err2 != nil { - return err1 - } - return err2 - } - return err1 -} - -func (d *DotGit) readReferenceFrom(rd io.Reader, name string) (ref *plumbing.Reference, err error) { - b, err := io.ReadAll(rd) - if err != nil { - return nil, err - } - - if len(b) == 0 { - return nil, ErrEmptyRefFile - } - - line := strings.TrimSpace(string(b)) - return plumbing.NewReferenceFromStrings(name, line), nil -} - -// checkReferenceAndTruncate reads the reference from the given file, or the `pack-refs` file if -// the file was empty. Then it checks that the old reference matches the stored reference and -// truncates the file. -func (d *DotGit) checkReferenceAndTruncate(f billy.File, old *plumbing.Reference) error { - if old == nil { - return nil - } - - ref, err := d.readReferenceFrom(f, old.Name().String()) - if errors.Is(err, ErrEmptyRefFile) { - // This may happen if the reference is being read from a newly created file. - // In that case, try getting the reference from the packed refs file. - ref, err = d.packedRef(old.Name()) - } - - if err != nil { - return err - } - - if ref.Hash() != old.Hash() { - return storage.ErrReferenceHasChanged - } - _, err = f.Seek(0, io.SeekStart) - if err != nil { - return err - } - return f.Truncate(0) -} - -func (d *DotGit) SetRef(r, old *plumbing.Reference) error { - var content string - switch r.Type() { - case plumbing.SymbolicReference: - content = fmt.Sprintf("ref: %s\n", r.Target()) - case plumbing.HashReference: - content = fmt.Sprintln(r.Hash().String()) - } - - fileName := r.Name().String() - - return d.setRef(fileName, content, old) -} - -// Refs scans the git directory collecting references, which it returns. -// Symbolic references are resolved and included in the output. -func (d *DotGit) Refs() ([]*plumbing.Reference, error) { - var refs []*plumbing.Reference - seen := make(map[plumbing.ReferenceName]bool) - if err := d.addRefFromHEAD(&refs); err != nil { - return nil, err - } - - if err := d.addRefsFromRefDir(&refs, seen); err != nil { - return nil, err - } - - if err := d.addRefsFromPackedRefs(&refs, seen); err != nil { - return nil, err - } - - return refs, nil -} - -// Ref returns the reference for a given reference name. -func (d *DotGit) Ref(name plumbing.ReferenceName) (*plumbing.Reference, error) { - ref, err := d.readReferenceFile(".", name.String()) - if err == nil { - return ref, nil - } - - return d.packedRef(name) -} - -func (d *DotGit) findPackedRefsInFile(f billy.File, recv refsRecv) error { - s := bufio.NewScanner(f) - for s.Scan() { - ref, err := d.processLine(s.Text()) - if err != nil { - return err - } - - if !recv(ref) { - // skip parse - return nil - } - } - if err := s.Err(); err != nil { - return err - } - return nil -} - -// refsRecv: returning true means that the reference continues to be resolved, otherwise it is stopped, which will speed up the lookup of a single reference. -type refsRecv func(*plumbing.Reference) bool - -func (d *DotGit) findPackedRefs(recv refsRecv) error { - f, err := d.fs.Open(packedRefsPath) - if err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - - defer ioutil.CheckClose(f, &err) - return d.findPackedRefsInFile(f, recv) -} - -func (d *DotGit) packedRef(name plumbing.ReferenceName) (*plumbing.Reference, error) { - var ref *plumbing.Reference - if err := d.findPackedRefs(func(r *plumbing.Reference) bool { - if r != nil && r.Name() == name { - ref = r - // ref found - return false - } - return true - }); err != nil { - return nil, err - } - if ref != nil { - return ref, nil - } - return nil, plumbing.ErrReferenceNotFound -} - -// RemoveRef removes a reference by name. -func (d *DotGit) RemoveRef(name plumbing.ReferenceName) error { - path := d.fs.Join(".", name.String()) - _, err := d.fs.Stat(path) - if err == nil { - err = d.fs.Remove(path) - // Drop down to remove it from the packed refs file, too. - } - - if err != nil && !os.IsNotExist(err) { - return err - } - - return d.rewritePackedRefsWithoutRef(name) -} - -func refsRecvFunc(refs *[]*plumbing.Reference, seen map[plumbing.ReferenceName]bool) refsRecv { - return func(r *plumbing.Reference) bool { - if r != nil && !seen[r.Name()] { - *refs = append(*refs, r) - seen[r.Name()] = true - } - return true - } -} - -func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference, seen map[plumbing.ReferenceName]bool) (err error) { - return d.findPackedRefs(refsRecvFunc(refs, seen)) -} - -func (d *DotGit) addRefsFromPackedRefsFile(refs *[]*plumbing.Reference, f billy.File, seen map[plumbing.ReferenceName]bool) (err error) { - return d.findPackedRefsInFile(f, refsRecvFunc(refs, seen)) -} - -func (d *DotGit) openAndLockPackedRefs(doCreate bool) ( - pr billy.File, err error, -) { - var f billy.File - defer func() { - if err != nil && f != nil { - ioutil.CheckClose(f, &err) - } - }() - - // File mode is retrieved from a constant defined in the target specific - // files (dotgit_rewrite_packed_refs_*). Some modes are not available - // in all filesystems. - openFlags := d.openAndLockPackedRefsMode() - if doCreate { - openFlags |= os.O_CREATE - } - - // Keep trying to open and lock the file until we're sure the file - // didn't change between the open and the lock. - for { - f, err = d.fs.OpenFile(packedRefsPath, openFlags, 0600) - if err != nil { - if os.IsNotExist(err) && !doCreate { - return nil, nil - } - - return nil, err - } - fi, err := d.fs.Stat(packedRefsPath) - if err != nil { - return nil, err - } - mtime := fi.ModTime() - - err = f.Lock() - if err != nil { - return nil, err - } - - fi, err = d.fs.Stat(packedRefsPath) - if err != nil { - return nil, err - } - if mtime.Equal(fi.ModTime()) { - break - } - // The file has changed since we opened it. Close and retry. - err = f.Close() - if err != nil { - return nil, err - } - } - return f, nil -} - -func (d *DotGit) rewritePackedRefsWithoutRef(name plumbing.ReferenceName) (err error) { - pr, err := d.openAndLockPackedRefs(false) - if err != nil { - return err - } - if pr == nil { - return nil - } - defer ioutil.CheckClose(pr, &err) - - // Creating the temp file in the same directory as the target file - // improves our chances for rename operation to be atomic. - tmp, err := d.fs.TempFile("", tmpPackedRefsPrefix) - if err != nil { - return err - } - tmpName := tmp.Name() - defer func() { - ioutil.CheckClose(tmp, &err) - _ = d.fs.Remove(tmpName) // don't check err, we might have renamed it - }() - - s := bufio.NewScanner(pr) - found := false - for s.Scan() { - line := s.Text() - ref, err := d.processLine(line) - if err != nil { - return err - } - - if ref != nil && ref.Name() == name { - found = true - continue - } - - if _, err := fmt.Fprintln(tmp, line); err != nil { - return err - } - } - - if err := s.Err(); err != nil { - return err - } - - if !found { - return nil - } - - return d.rewritePackedRefsWhileLocked(tmp, pr) -} - -// process lines from a packed-refs file -func (d *DotGit) processLine(line string) (*plumbing.Reference, error) { - if len(line) == 0 { - return nil, nil - } - - switch line[0] { - case '#': // comment - ignore - return nil, nil - case '^': // annotated tag commit of the previous line - ignore - return nil, nil - default: - ws := strings.Split(line, " ") // hash then ref - if len(ws) != 2 { - return nil, ErrPackedRefsBadFormat - } - - return plumbing.NewReferenceFromStrings(ws[1], ws[0]), nil - } -} - -func (d *DotGit) addRefsFromRefDir(refs *[]*plumbing.Reference, seen map[plumbing.ReferenceName]bool) error { - return d.walkReferencesTree(refs, []string{refsPath}, seen) -} - -func (d *DotGit) walkReferencesTree(refs *[]*plumbing.Reference, relPath []string, seen map[plumbing.ReferenceName]bool) error { - files, err := d.fs.ReadDir(d.fs.Join(relPath...)) - if err != nil { - if os.IsNotExist(err) { - // a race happened, and our directory is gone now - return nil - } - - return err - } - - for _, f := range files { - newRelPath := append(append([]string(nil), relPath...), f.Name()) - if f.IsDir() { - if err = d.walkReferencesTree(refs, newRelPath, seen); err != nil { - return err - } - - continue - } - - ref, err := d.readReferenceFile(".", strings.Join(newRelPath, "/")) - if os.IsNotExist(err) { - // a race happened, and our file is gone now - continue - } - if err != nil { - return err - } - - if ref != nil && !seen[ref.Name()] { - *refs = append(*refs, ref) - seen[ref.Name()] = true - } - } - - return nil -} - -func (d *DotGit) addRefFromHEAD(refs *[]*plumbing.Reference) error { - ref, err := d.readReferenceFile(".", "HEAD") - if err != nil { - if os.IsNotExist(err) { - return nil - } - - return err - } - - *refs = append(*refs, ref) - return nil -} - -func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference, err error) { - path = d.fs.Join(path, d.fs.Join(strings.Split(name, "/")...)) - st, err := d.fs.Stat(path) - if err != nil { - return nil, err - } - if st.IsDir() { - return nil, ErrIsDir - } - - f, err := d.fs.Open(path) - if err != nil { - return nil, err - } - defer ioutil.CheckClose(f, &err) - - return d.readReferenceFrom(f, name) -} - -func (d *DotGit) CountLooseRefs() (int, error) { - var refs []*plumbing.Reference - seen := make(map[plumbing.ReferenceName]bool) - if err := d.addRefsFromRefDir(&refs, seen); err != nil { - return 0, err - } - - return len(refs), nil -} - -// PackRefs packs all loose refs into the packed-refs file. -// -// This implementation only works under the assumption that the view -// of the file system won't be updated during this operation. This -// strategy would not work on a general file system though, without -// locking each loose reference and checking it again before deleting -// the file, because otherwise an updated reference could sneak in and -// then be deleted by the packed-refs process. Alternatively, every -// ref update could also lock packed-refs, so only one lock is -// required during ref-packing. But that would worsen performance in -// the common case. -// -// TODO: add an "all" boolean like the `git pack-refs --all` flag. -// When `all` is false, it would only pack refs that have already been -// packed, plus all tags. -func (d *DotGit) PackRefs() (err error) { - // Lock packed-refs, and create it if it doesn't exist yet. - f, err := d.openAndLockPackedRefs(true) - if err != nil { - return err - } - defer ioutil.CheckClose(f, &err) - - // Gather all refs using addRefsFromRefDir and addRefsFromPackedRefs. - var refs []*plumbing.Reference - seen := make(map[plumbing.ReferenceName]bool) - if err = d.addRefsFromRefDir(&refs, seen); err != nil { - return err - } - if len(refs) == 0 { - // Nothing to do! - return nil - } - numLooseRefs := len(refs) - if err = d.addRefsFromPackedRefsFile(&refs, f, seen); err != nil { - return err - } - - // Write them all to a new temp packed-refs file. - tmp, err := d.fs.TempFile("", tmpPackedRefsPrefix) - if err != nil { - return err - } - tmpName := tmp.Name() - defer func() { - ioutil.CheckClose(tmp, &err) - _ = d.fs.Remove(tmpName) // don't check err, we might have renamed it - }() - - w := bufio.NewWriter(tmp) - for _, ref := range refs { - _, err = w.WriteString(ref.String() + "\n") - if err != nil { - return err - } - } - err = w.Flush() - if err != nil { - return err - } - - // Rename the temp packed-refs file. - err = d.rewritePackedRefsWhileLocked(tmp, f) - if err != nil { - return err - } - - // Delete all the loose refs, while still holding the packed-refs - // lock. - for _, ref := range refs[:numLooseRefs] { - path := d.fs.Join(".", ref.Name().String()) - err = d.fs.Remove(path) - if err != nil && !os.IsNotExist(err) { - return err - } - } - - return nil -} - -// Module return a billy.Filesystem pointing to the module folder -func (d *DotGit) Module(name string) (billy.Filesystem, error) { - return d.fs.Chroot(d.fs.Join(modulePath, name)) -} - -func (d *DotGit) AddAlternate(remote string) error { - altpath := d.fs.Join(objectsPath, infoPath, alternatesPath) - - f, err := d.fs.OpenFile(altpath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640) - if err != nil { - return fmt.Errorf("cannot open file: %w", err) - } - defer f.Close() - - // locking in windows throws an error, based on comments - // https://github.com/go-git/go-git/pull/860#issuecomment-1751823044 - // do not lock on windows platform. - if runtime.GOOS != "windows" { - if err = f.Lock(); err != nil { - return fmt.Errorf("cannot lock file: %w", err) - } - defer f.Unlock() - } - - line := path.Join(remote, objectsPath) + "\n" - _, err = io.WriteString(f, line) - if err != nil { - return fmt.Errorf("error writing 'alternates' file: %w", err) - } - - return nil -} - -// Alternates returns DotGit(s) based off paths in objects/info/alternates if -// available. This can be used to checks if it's a shared repository. -func (d *DotGit) Alternates() ([]*DotGit, error) { - altpath := d.fs.Join(objectsPath, infoPath, alternatesPath) - f, err := d.fs.Open(altpath) - if err != nil { - return nil, err - } - defer f.Close() - - fs := d.options.AlternatesFS - if fs == nil { - fs = d.fs - } - - var alternates []*DotGit - seen := make(map[string]struct{}) - - // Read alternate paths line-by-line and create DotGit objects. - scanner := bufio.NewScanner(f) - for scanner.Scan() { - path := scanner.Text() - - // Avoid creating multiple dotgits for the same alternative path. - if _, ok := seen[path]; ok { - continue - } - - seen[path] = struct{}{} - - if filepath.IsAbs(path) { - // Handling absolute paths should be straight-forward. However, the default osfs (Chroot) - // tries to concatenate an abs path with the root path in some operations (e.g. Stat), - // which leads to unexpected errors. Therefore, make the path relative to the current FS instead. - if reflect.TypeOf(fs) == reflect.TypeOf(&chroot.ChrootHelper{}) { - path, err = filepath.Rel(fs.Root(), path) - if err != nil { - return nil, fmt.Errorf("cannot make path %q relative: %w", path, err) - } - } - } else { - // By Git conventions, relative paths should be based on the object database (.git/objects/info) - // location as per: https://www.kernel.org/pub/software/scm/git/docs/gitrepository-layout.html - // However, due to the nature of go-git and its filesystem handling via Billy, paths cannot - // cross its "chroot boundaries". Therefore, ignore any "../" and treat the path from the - // fs root. If this is not correct based on the dotgit fs, set a different one via AlternatesFS. - abs := filepath.Join(string(filepath.Separator), filepath.ToSlash(path)) - path = filepath.FromSlash(abs) - } - - // Aligns with upstream behavior: exit if target path is not a valid directory. - if fi, err := fs.Stat(path); err != nil || !fi.IsDir() { - return nil, fmt.Errorf("invalid object directory %q: %w", path, err) - } - afs, err := fs.Chroot(filepath.Dir(path)) - if err != nil { - return nil, fmt.Errorf("cannot chroot %q: %w", path, err) - } - alternates = append(alternates, New(afs)) - } - - if err = scanner.Err(); err != nil { - return nil, err - } - - return alternates, nil -} - -// Fs returns the underlying filesystem of the DotGit folder. -func (d *DotGit) Fs() billy.Filesystem { - return d.fs -} - -func isHex(s string) bool { - for _, b := range []byte(s) { - if isNum(b) { - continue - } - if isHexAlpha(b) { - continue - } - - return false - } - - return true -} - -func isNum(b byte) bool { - return b >= '0' && b <= '9' -} - -func isHexAlpha(b byte) bool { - return b >= 'a' && b <= 'f' || b >= 'A' && b <= 'F' -} - -// incBytes increments a byte slice, which involves incrementing the -// right-most byte, and following carry leftward. -// It makes a copy so that the provided slice's underlying array is not modified. -// If the overall operation overflows (e.g. incBytes(0xff, 0xff)), the second return parameter indicates that. -func incBytes(in []byte) (out []byte, overflow bool) { - out = make([]byte, len(in)) - copy(out, in) - for i := len(out) - 1; i >= 0; i-- { - out[i]++ - if out[i] != 0 { - return // Didn't overflow. - } - } - overflow = true - return -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/dotgit_rewrite_packed_refs.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/dotgit_rewrite_packed_refs.go deleted file mode 100644 index d0ee2f3d90c..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/dotgit_rewrite_packed_refs.go +++ /dev/null @@ -1,81 +0,0 @@ -package dotgit - -import ( - "io" - "os" - "runtime" - - "github.com/go-git/go-billy/v5" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -func (d *DotGit) openAndLockPackedRefsMode() int { - if billy.CapabilityCheck(d.fs, billy.ReadAndWriteCapability) { - return os.O_RDWR - } - - return os.O_RDONLY -} - -func (d *DotGit) rewritePackedRefsWhileLocked( - tmp billy.File, pr billy.File) error { - // Try plain rename. If we aren't using the bare Windows filesystem as the - // storage layer, we might be able to get away with a rename over a locked - // file. - err := d.fs.Rename(tmp.Name(), pr.Name()) - if err == nil { - return nil - } - - // If we are in a filesystem that does not support rename (e.g. sivafs) - // a full copy is done. - if err == billy.ErrNotSupported { - return d.copyNewFile(tmp, pr) - } - - if runtime.GOOS != "windows" { - return err - } - - // Otherwise, Windows doesn't let us rename over a locked file, so - // we have to do a straight copy. Unfortunately this could result - // in a partially-written file if the process fails before the - // copy completes. - return d.copyToExistingFile(tmp, pr) -} - -func (d *DotGit) copyToExistingFile(tmp, pr billy.File) error { - _, err := pr.Seek(0, io.SeekStart) - if err != nil { - return err - } - err = pr.Truncate(0) - if err != nil { - return err - } - _, err = tmp.Seek(0, io.SeekStart) - if err != nil { - return err - } - _, err = io.Copy(pr, tmp) - - return err -} - -func (d *DotGit) copyNewFile(tmp billy.File, pr billy.File) (err error) { - prWrite, err := d.fs.Create(pr.Name()) - if err != nil { - return err - } - - defer ioutil.CheckClose(prWrite, &err) - - _, err = tmp.Seek(0, io.SeekStart) - if err != nil { - return err - } - - _, err = io.Copy(prWrite, tmp) - - return err -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/dotgit_setref.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/dotgit_setref.go deleted file mode 100644 index 31a81dddb9c..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/dotgit_setref.go +++ /dev/null @@ -1,90 +0,0 @@ -package dotgit - -import ( - "fmt" - "os" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - - "github.com/go-git/go-billy/v5" -) - -func (d *DotGit) setRef(fileName, content string, old *plumbing.Reference) (err error) { - if billy.CapabilityCheck(d.fs, billy.ReadAndWriteCapability) { - return d.setRefRwfs(fileName, content, old) - } - - return d.setRefNorwfs(fileName, content, old) -} - -func (d *DotGit) setRefRwfs(fileName, content string, old *plumbing.Reference) (err error) { - // If we are not checking an old ref, just truncate the file. - mode := os.O_RDWR | os.O_CREATE - if old == nil { - mode |= os.O_TRUNC - } - - f, err := d.fs.OpenFile(fileName, mode, 0666) - if err != nil { - return err - } - - defer ioutil.CheckClose(f, &err) - - // Lock is unlocked by the deferred Close above. This is because Unlock - // does not imply a fsync and thus there would be a race between - // Unlock+Close and other concurrent writers. Adding Sync to go-billy - // could work, but this is better (and avoids superfluous syncs). - err = f.Lock() - if err != nil { - return err - } - - // this is a no-op to call even when old is nil. - err = d.checkReferenceAndTruncate(f, old) - if err != nil { - return err - } - - _, err = f.Write([]byte(content)) - return err -} - -// There are some filesystems that don't support opening files in RDWD mode. -// In these filesystems the standard SetRef function can not be used as it -// reads the reference file to check that it's not modified before updating it. -// -// This version of the function writes the reference without extra checks -// making it compatible with these simple filesystems. This is usually not -// a problem as they should be accessed by only one process at a time. -func (d *DotGit) setRefNorwfs(fileName, content string, old *plumbing.Reference) error { - _, err := d.fs.Stat(fileName) - if err == nil && old != nil { - fRead, err := d.fs.Open(fileName) - if err != nil { - return err - } - - ref, err := d.readReferenceFrom(fRead, old.Name().String()) - fRead.Close() - - if err != nil { - return err - } - - if ref.Hash() != old.Hash() { - return fmt.Errorf("reference has changed concurrently") - } - } - - f, err := d.fs.Create(fileName) - if err != nil { - return err - } - - defer f.Close() - - _, err = f.Write([]byte(content)) - return err -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/reader.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/reader.go deleted file mode 100644 index 28f3f1cf785..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/reader.go +++ /dev/null @@ -1,79 +0,0 @@ -package dotgit - -import ( - "fmt" - "io" - "os" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/objfile" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -var _ (plumbing.EncodedObject) = &EncodedObject{} - -type EncodedObject struct { - dir *DotGit - h plumbing.Hash - t plumbing.ObjectType - sz int64 -} - -func (e *EncodedObject) Hash() plumbing.Hash { - return e.h -} - -func (e *EncodedObject) Reader() (io.ReadCloser, error) { - f, err := e.dir.Object(e.h) - if err != nil { - if os.IsNotExist(err) { - return nil, plumbing.ErrObjectNotFound - } - - return nil, err - } - r, err := objfile.NewReader(f) - if err != nil { - return nil, err - } - - t, size, err := r.Header() - if err != nil { - _ = r.Close() - return nil, err - } - if t != e.t { - _ = r.Close() - return nil, objfile.ErrHeader - } - if size != e.sz { - _ = r.Close() - return nil, objfile.ErrHeader - } - return ioutil.NewReadCloserWithCloser(r, f.Close), nil -} - -func (e *EncodedObject) SetType(plumbing.ObjectType) {} - -func (e *EncodedObject) Type() plumbing.ObjectType { - return e.t -} - -func (e *EncodedObject) Size() int64 { - return e.sz -} - -func (e *EncodedObject) SetSize(int64) {} - -func (e *EncodedObject) Writer() (io.WriteCloser, error) { - return nil, fmt.Errorf("not supported") -} - -func NewEncodedObject(dir *DotGit, h plumbing.Hash, t plumbing.ObjectType, size int64) *EncodedObject { - return &EncodedObject{ - dir: dir, - h: h, - t: t, - sz: size, - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/repository_filesystem.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/repository_filesystem.go deleted file mode 100644 index 8d243efea1f..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/repository_filesystem.go +++ /dev/null @@ -1,111 +0,0 @@ -package dotgit - -import ( - "os" - "path/filepath" - "strings" - - "github.com/go-git/go-billy/v5" -) - -// RepositoryFilesystem is a billy.Filesystem compatible object wrapper -// which handles dot-git filesystem operations and supports commondir according to git scm layout: -// https://github.com/git/git/blob/master/Documentation/gitrepository-layout.txt -type RepositoryFilesystem struct { - dotGitFs billy.Filesystem - commonDotGitFs billy.Filesystem -} - -func NewRepositoryFilesystem(dotGitFs, commonDotGitFs billy.Filesystem) *RepositoryFilesystem { - return &RepositoryFilesystem{ - dotGitFs: dotGitFs, - commonDotGitFs: commonDotGitFs, - } -} - -func (fs *RepositoryFilesystem) mapToRepositoryFsByPath(path string) billy.Filesystem { - // Nothing to decide if commondir not defined - if fs.commonDotGitFs == nil { - return fs.dotGitFs - } - - cleanPath := filepath.Clean(path) - - // Check exceptions for commondir (https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt) - switch cleanPath { - case fs.dotGitFs.Join(logsPath, "HEAD"): - return fs.dotGitFs - case fs.dotGitFs.Join(refsPath, "bisect"), fs.dotGitFs.Join(refsPath, "rewritten"), fs.dotGitFs.Join(refsPath, "worktree"): - return fs.dotGitFs - } - - // Determine dot-git root by first path element. - // There are some elements which should always use commondir when commondir defined. - // Usual dot-git root will be used for the rest of files. - switch strings.Split(cleanPath, string(filepath.Separator))[0] { - case objectsPath, refsPath, packedRefsPath, configPath, branchesPath, hooksPath, infoPath, remotesPath, logsPath, shallowPath, worktreesPath: - return fs.commonDotGitFs - default: - return fs.dotGitFs - } -} - -func (fs *RepositoryFilesystem) Create(filename string) (billy.File, error) { - return fs.mapToRepositoryFsByPath(filename).Create(filename) -} - -func (fs *RepositoryFilesystem) Open(filename string) (billy.File, error) { - return fs.mapToRepositoryFsByPath(filename).Open(filename) -} - -func (fs *RepositoryFilesystem) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { - return fs.mapToRepositoryFsByPath(filename).OpenFile(filename, flag, perm) -} - -func (fs *RepositoryFilesystem) Stat(filename string) (os.FileInfo, error) { - return fs.mapToRepositoryFsByPath(filename).Stat(filename) -} - -func (fs *RepositoryFilesystem) Rename(oldpath, newpath string) error { - return fs.mapToRepositoryFsByPath(oldpath).Rename(oldpath, newpath) -} - -func (fs *RepositoryFilesystem) Remove(filename string) error { - return fs.mapToRepositoryFsByPath(filename).Remove(filename) -} - -func (fs *RepositoryFilesystem) Join(elem ...string) string { - return fs.dotGitFs.Join(elem...) -} - -func (fs *RepositoryFilesystem) TempFile(dir, prefix string) (billy.File, error) { - return fs.mapToRepositoryFsByPath(dir).TempFile(dir, prefix) -} - -func (fs *RepositoryFilesystem) ReadDir(path string) ([]os.FileInfo, error) { - return fs.mapToRepositoryFsByPath(path).ReadDir(path) -} - -func (fs *RepositoryFilesystem) MkdirAll(filename string, perm os.FileMode) error { - return fs.mapToRepositoryFsByPath(filename).MkdirAll(filename, perm) -} - -func (fs *RepositoryFilesystem) Lstat(filename string) (os.FileInfo, error) { - return fs.mapToRepositoryFsByPath(filename).Lstat(filename) -} - -func (fs *RepositoryFilesystem) Symlink(target, link string) error { - return fs.mapToRepositoryFsByPath(target).Symlink(target, link) -} - -func (fs *RepositoryFilesystem) Readlink(link string) (string, error) { - return fs.mapToRepositoryFsByPath(link).Readlink(link) -} - -func (fs *RepositoryFilesystem) Chroot(path string) (billy.Filesystem, error) { - return fs.mapToRepositoryFsByPath(path).Chroot(path) -} - -func (fs *RepositoryFilesystem) Root() string { - return fs.dotGitFs.Root() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/writers.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/writers.go deleted file mode 100644 index 6ef097d9526..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit/writers.go +++ /dev/null @@ -1,285 +0,0 @@ -package dotgit - -import ( - "fmt" - "io" - "sync/atomic" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/idxfile" - "github.com/jesseduffield/go-git/v5/plumbing/format/objfile" - "github.com/jesseduffield/go-git/v5/plumbing/format/packfile" - "github.com/jesseduffield/go-git/v5/plumbing/hash" - - "github.com/go-git/go-billy/v5" -) - -// PackWriter is a io.Writer that generates the packfile index simultaneously, -// a packfile.Decoder is used with a file reader to read the file being written -// this operation is synchronized with the write operations. -// The packfile is written in a temp file, when Close is called this file -// is renamed/moved (depends on the Filesystem implementation) to the final -// location, if the PackWriter is not used, nothing is written -type PackWriter struct { - Notify func(plumbing.Hash, *idxfile.Writer) - - fs billy.Filesystem - fr, fw billy.File - synced *syncedReader - checksum plumbing.Hash - parser *packfile.Parser - writer *idxfile.Writer - result chan error -} - -func newPackWrite(fs billy.Filesystem) (*PackWriter, error) { - fw, err := fs.TempFile(fs.Join(objectsPath, packPath), "tmp_pack_") - if err != nil { - return nil, err - } - - fr, err := fs.Open(fw.Name()) - if err != nil { - return nil, err - } - - writer := &PackWriter{ - fs: fs, - fw: fw, - fr: fr, - synced: newSyncedReader(fw, fr), - result: make(chan error), - } - - go writer.buildIndex() - return writer, nil -} - -func (w *PackWriter) buildIndex() { - s := packfile.NewScanner(w.synced) - w.writer = new(idxfile.Writer) - var err error - w.parser, err = packfile.NewParser(s, w.writer) - if err != nil { - w.result <- err - return - } - - checksum, err := w.parser.Parse() - if err != nil { - w.result <- err - return - } - - w.checksum = checksum - w.result <- err -} - -// waitBuildIndex waits until buildIndex function finishes, this can terminate -// with a packfile.ErrEmptyPackfile, this means that nothing was written so we -// ignore the error -func (w *PackWriter) waitBuildIndex() error { - err := <-w.result - if err == packfile.ErrEmptyPackfile { - return nil - } - - return err -} - -func (w *PackWriter) Write(p []byte) (int, error) { - return w.synced.Write(p) -} - -// Close closes all the file descriptors and save the final packfile, if nothing -// was written, the tempfiles are deleted without writing a packfile. -func (w *PackWriter) Close() error { - defer func() { - if w.Notify != nil && w.writer != nil && w.writer.Finished() { - w.Notify(w.checksum, w.writer) - } - - close(w.result) - }() - - if err := w.synced.Close(); err != nil { - return err - } - - if err := w.waitBuildIndex(); err != nil { - return err - } - - if err := w.fr.Close(); err != nil { - return err - } - - if err := w.fw.Close(); err != nil { - return err - } - - if w.writer == nil || !w.writer.Finished() { - return w.clean() - } - - return w.save() -} - -func (w *PackWriter) clean() error { - return w.fs.Remove(w.fw.Name()) -} - -func (w *PackWriter) save() error { - base := w.fs.Join(objectsPath, packPath, fmt.Sprintf("pack-%s", w.checksum)) - idx, err := w.fs.Create(fmt.Sprintf("%s.idx", base)) - if err != nil { - return err - } - - if err := w.encodeIdx(idx); err != nil { - return err - } - - if err := idx.Close(); err != nil { - return err - } - - return w.fs.Rename(w.fw.Name(), fmt.Sprintf("%s.pack", base)) -} - -func (w *PackWriter) encodeIdx(writer io.Writer) error { - idx, err := w.writer.Index() - if err != nil { - return err - } - - e := idxfile.NewEncoder(writer) - _, err = e.Encode(idx) - return err -} - -type syncedReader struct { - w io.Writer - r io.ReadSeeker - - blocked, done uint32 - written, read uint64 - news chan bool -} - -func newSyncedReader(w io.Writer, r io.ReadSeeker) *syncedReader { - return &syncedReader{ - w: w, - r: r, - news: make(chan bool), - } -} - -func (s *syncedReader) Write(p []byte) (n int, err error) { - defer func() { - written := atomic.AddUint64(&s.written, uint64(n)) - read := atomic.LoadUint64(&s.read) - if written > read { - s.wake() - } - }() - - n, err = s.w.Write(p) - return -} - -func (s *syncedReader) Read(p []byte) (n int, err error) { - defer func() { atomic.AddUint64(&s.read, uint64(n)) }() - - for { - s.sleep() - n, err = s.r.Read(p) - if err == io.EOF && !s.isDone() && n == 0 { - continue - } - - break - } - - return -} - -func (s *syncedReader) isDone() bool { - return atomic.LoadUint32(&s.done) == 1 -} - -func (s *syncedReader) isBlocked() bool { - return atomic.LoadUint32(&s.blocked) == 1 -} - -func (s *syncedReader) wake() { - if s.isBlocked() { - atomic.StoreUint32(&s.blocked, 0) - s.news <- true - } -} - -func (s *syncedReader) sleep() { - read := atomic.LoadUint64(&s.read) - written := atomic.LoadUint64(&s.written) - if read >= written { - atomic.StoreUint32(&s.blocked, 1) - <-s.news - } - -} - -func (s *syncedReader) Seek(offset int64, whence int) (int64, error) { - if whence == io.SeekCurrent { - return s.r.Seek(offset, whence) - } - - p, err := s.r.Seek(offset, whence) - atomic.StoreUint64(&s.read, uint64(p)) - - return p, err -} - -func (s *syncedReader) Close() error { - atomic.StoreUint32(&s.done, 1) - close(s.news) - return nil -} - -type ObjectWriter struct { - objfile.Writer - fs billy.Filesystem - f billy.File -} - -func newObjectWriter(fs billy.Filesystem) (*ObjectWriter, error) { - f, err := fs.TempFile(fs.Join(objectsPath, packPath), "tmp_obj_") - if err != nil { - return nil, err - } - - return &ObjectWriter{ - Writer: (*objfile.NewWriter(f)), - fs: fs, - f: f, - }, nil -} - -func (w *ObjectWriter) Close() error { - if err := w.Writer.Close(); err != nil { - return err - } - - if err := w.f.Close(); err != nil { - return err - } - - return w.save() -} - -func (w *ObjectWriter) save() error { - hex := w.Hash().String() - file := w.fs.Join(objectsPath, hex[0:2], hex[2:hash.HexSize]) - - return w.fs.Rename(w.f.Name(), file) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/index.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/index.go deleted file mode 100644 index 8c10e4788b7..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/index.go +++ /dev/null @@ -1,54 +0,0 @@ -package filesystem - -import ( - "bufio" - "os" - - "github.com/jesseduffield/go-git/v5/plumbing/format/index" - "github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -type IndexStorage struct { - dir *dotgit.DotGit -} - -func (s *IndexStorage) SetIndex(idx *index.Index) (err error) { - f, err := s.dir.IndexWriter() - if err != nil { - return err - } - - defer ioutil.CheckClose(f, &err) - bw := bufio.NewWriter(f) - defer func() { - if e := bw.Flush(); err == nil && e != nil { - err = e - } - }() - - e := index.NewEncoder(bw) - err = e.Encode(idx) - return err -} - -func (s *IndexStorage) Index() (i *index.Index, err error) { - idx := &index.Index{ - Version: 2, - } - - f, err := s.dir.Index() - if err != nil { - if os.IsNotExist(err) { - return idx, nil - } - - return nil, err - } - - defer ioutil.CheckClose(f, &err) - - d := index.NewDecoder(f) - err = d.Decode(idx) - return idx, err -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/module.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/module.go deleted file mode 100644 index 77de7dbabf5..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/module.go +++ /dev/null @@ -1,20 +0,0 @@ -package filesystem - -import ( - "github.com/jesseduffield/go-git/v5/plumbing/cache" - "github.com/jesseduffield/go-git/v5/storage" - "github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit" -) - -type ModuleStorage struct { - dir *dotgit.DotGit -} - -func (s *ModuleStorage) Module(name string) (storage.Storer, error) { - fs, err := s.dir.Module(name) - if err != nil { - return nil, err - } - - return NewStorage(fs, cache.NewObjectLRUDefault()), nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/object.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/object.go deleted file mode 100644 index 64e24496a28..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/object.go +++ /dev/null @@ -1,892 +0,0 @@ -package filesystem - -import ( - "bytes" - "io" - "os" - "sync" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/cache" - "github.com/jesseduffield/go-git/v5/plumbing/format/idxfile" - "github.com/jesseduffield/go-git/v5/plumbing/format/objfile" - "github.com/jesseduffield/go-git/v5/plumbing/format/packfile" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - - "github.com/go-git/go-billy/v5" -) - -type ObjectStorage struct { - options Options - - // objectCache is an object cache uses to cache delta's bases and also recently - // loaded loose objects - objectCache cache.Object - - dir *dotgit.DotGit - index map[plumbing.Hash]idxfile.Index - - packList []plumbing.Hash - packListIdx int - packfiles map[plumbing.Hash]*packfile.Packfile -} - -// NewObjectStorage creates a new ObjectStorage with the given .git directory and cache. -func NewObjectStorage(dir *dotgit.DotGit, objectCache cache.Object) *ObjectStorage { - return NewObjectStorageWithOptions(dir, objectCache, Options{}) -} - -// NewObjectStorageWithOptions creates a new ObjectStorage with the given .git directory, cache and extra options -func NewObjectStorageWithOptions(dir *dotgit.DotGit, objectCache cache.Object, ops Options) *ObjectStorage { - return &ObjectStorage{ - options: ops, - objectCache: objectCache, - dir: dir, - } -} - -func (s *ObjectStorage) requireIndex() error { - if s.index != nil { - return nil - } - - s.index = make(map[plumbing.Hash]idxfile.Index) - packs, err := s.dir.ObjectPacks() - if err != nil { - return err - } - - for _, h := range packs { - if err := s.loadIdxFile(h); err != nil { - return err - } - } - - return nil -} - -// Reindex indexes again all packfiles. Useful if git changed packfiles externally -func (s *ObjectStorage) Reindex() { - s.index = nil -} - -func (s *ObjectStorage) loadIdxFile(h plumbing.Hash) (err error) { - f, err := s.dir.ObjectPackIdx(h) - if err != nil { - return err - } - - defer ioutil.CheckClose(f, &err) - - idxf := idxfile.NewMemoryIndex() - d := idxfile.NewDecoder(f) - if err = d.Decode(idxf); err != nil { - return err - } - - s.index[h] = idxf - return err -} - -func (s *ObjectStorage) NewEncodedObject() plumbing.EncodedObject { - return &plumbing.MemoryObject{} -} - -func (s *ObjectStorage) PackfileWriter() (io.WriteCloser, error) { - if err := s.requireIndex(); err != nil { - return nil, err - } - - w, err := s.dir.NewObjectPack() - if err != nil { - return nil, err - } - - w.Notify = func(h plumbing.Hash, writer *idxfile.Writer) { - index, err := writer.Index() - if err == nil { - s.index[h] = index - } - } - - return w, nil -} - -// SetEncodedObject adds a new object to the storage. -func (s *ObjectStorage) SetEncodedObject(o plumbing.EncodedObject) (h plumbing.Hash, err error) { - if o.Type() == plumbing.OFSDeltaObject || o.Type() == plumbing.REFDeltaObject { - return plumbing.ZeroHash, plumbing.ErrInvalidType - } - - ow, err := s.dir.NewObject() - if err != nil { - return plumbing.ZeroHash, err - } - - defer ioutil.CheckClose(ow, &err) - - or, err := o.Reader() - if err != nil { - return plumbing.ZeroHash, err - } - - defer ioutil.CheckClose(or, &err) - - if err = ow.WriteHeader(o.Type(), o.Size()); err != nil { - return plumbing.ZeroHash, err - } - - if _, err = io.Copy(ow, or); err != nil { - return plumbing.ZeroHash, err - } - - return o.Hash(), err -} - -// LazyWriter returns a lazy ObjectWriter that is bound to a DotGit file. -// It first write the header passing on the object type and size, so -// that the object contents can be written later, without the need to -// create a MemoryObject and buffering its entire contents into memory. -func (s *ObjectStorage) LazyWriter() (w io.WriteCloser, wh func(typ plumbing.ObjectType, sz int64) error, err error) { - ow, err := s.dir.NewObject() - if err != nil { - return nil, nil, err - } - - return ow, ow.WriteHeader, nil -} - -// HasEncodedObject returns nil if the object exists, without actually -// reading the object data from storage. -func (s *ObjectStorage) HasEncodedObject(h plumbing.Hash) (err error) { - // Check unpacked objects - f, err := s.dir.Object(h) - if err != nil { - if !os.IsNotExist(err) { - return err - } - // Fall through to check packed objects. - } else { - defer ioutil.CheckClose(f, &err) - return nil - } - - // Check packed objects. - if err := s.requireIndex(); err != nil { - return err - } - _, _, offset := s.findObjectInPackfile(h) - if offset == -1 { - return plumbing.ErrObjectNotFound - } - return nil -} - -func (s *ObjectStorage) encodedObjectSizeFromUnpacked(h plumbing.Hash) ( - size int64, err error) { - f, err := s.dir.Object(h) - if err != nil { - if os.IsNotExist(err) { - return 0, plumbing.ErrObjectNotFound - } - - return 0, err - } - - r, err := objfile.NewReader(f) - if err != nil { - return 0, err - } - defer ioutil.CheckClose(r, &err) - - _, size, err = r.Header() - return size, err -} - -func (s *ObjectStorage) packfile(idx idxfile.Index, pack plumbing.Hash) (*packfile.Packfile, error) { - if p := s.packfileFromCache(pack); p != nil { - return p, nil - } - - f, err := s.dir.ObjectPack(pack) - if err != nil { - return nil, err - } - - var p *packfile.Packfile - if s.objectCache != nil { - p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.objectCache, s.options.LargeObjectThreshold) - } else { - p = packfile.NewPackfile(idx, s.dir.Fs(), f, s.options.LargeObjectThreshold) - } - - return p, s.storePackfileInCache(pack, p) -} - -func (s *ObjectStorage) packfileFromCache(hash plumbing.Hash) *packfile.Packfile { - if s.packfiles == nil { - if s.options.KeepDescriptors { - s.packfiles = make(map[plumbing.Hash]*packfile.Packfile) - } else if s.options.MaxOpenDescriptors > 0 { - s.packList = make([]plumbing.Hash, s.options.MaxOpenDescriptors) - s.packfiles = make(map[plumbing.Hash]*packfile.Packfile, s.options.MaxOpenDescriptors) - } - } - - return s.packfiles[hash] -} - -func (s *ObjectStorage) storePackfileInCache(hash plumbing.Hash, p *packfile.Packfile) error { - if s.options.KeepDescriptors { - s.packfiles[hash] = p - return nil - } - - if s.options.MaxOpenDescriptors <= 0 { - return nil - } - - // start over as the limit of packList is hit - if s.packListIdx >= len(s.packList) { - s.packListIdx = 0 - } - - // close the existing packfile if open - if next := s.packList[s.packListIdx]; !next.IsZero() { - open := s.packfiles[next] - delete(s.packfiles, next) - if open != nil { - if err := open.Close(); err != nil { - return err - } - } - } - - // cache newly open packfile - s.packList[s.packListIdx] = hash - s.packfiles[hash] = p - s.packListIdx++ - - return nil -} - -func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) ( - size int64, err error) { - if err := s.requireIndex(); err != nil { - return 0, err - } - - pack, _, offset := s.findObjectInPackfile(h) - if offset == -1 { - return 0, plumbing.ErrObjectNotFound - } - - idx := s.index[pack] - hash, err := idx.FindHash(offset) - if err == nil { - obj, ok := s.objectCache.Get(hash) - if ok { - return obj.Size(), nil - } - } else if err != nil && err != plumbing.ErrObjectNotFound { - return 0, err - } - - p, err := s.packfile(idx, pack) - if err != nil { - return 0, err - } - - if !s.options.KeepDescriptors && s.options.MaxOpenDescriptors == 0 { - defer ioutil.CheckClose(p, &err) - } - - return p.GetSizeByOffset(offset) -} - -// EncodedObjectSize returns the plaintext size of the given object, -// without actually reading the full object data from storage. -func (s *ObjectStorage) EncodedObjectSize(h plumbing.Hash) ( - size int64, err error) { - size, err = s.encodedObjectSizeFromUnpacked(h) - if err != nil && err != plumbing.ErrObjectNotFound { - return 0, err - } else if err == nil { - return size, nil - } - - return s.encodedObjectSizeFromPackfile(h) -} - -// EncodedObject returns the object with the given hash, by searching for it in -// the packfile and the git object directories. -func (s *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) { - var obj plumbing.EncodedObject - var err error - - if s.index != nil { - obj, err = s.getFromPackfile(h, false) - if err == plumbing.ErrObjectNotFound { - obj, err = s.getFromUnpacked(h) - } - } else { - obj, err = s.getFromUnpacked(h) - if err == plumbing.ErrObjectNotFound { - obj, err = s.getFromPackfile(h, false) - } - } - - // If the error is still object not found, check if it's a shared object - // repository. - if err == plumbing.ErrObjectNotFound { - dotgits, e := s.dir.Alternates() - if e == nil { - // Create a new object storage with the DotGit(s) and check for the - // required hash object. Skip when not found. - for _, dg := range dotgits { - o := NewObjectStorage(dg, s.objectCache) - enobj, enerr := o.EncodedObject(t, h) - if enerr != nil { - continue - } - return enobj, nil - } - } - } - - if err != nil { - return nil, err - } - - if plumbing.AnyObject != t && obj.Type() != t { - return nil, plumbing.ErrObjectNotFound - } - - return obj, nil -} - -// DeltaObject returns the object with the given hash, by searching for -// it in the packfile and the git object directories. -func (s *ObjectStorage) DeltaObject(t plumbing.ObjectType, - h plumbing.Hash) (plumbing.EncodedObject, error) { - obj, err := s.getFromUnpacked(h) - if err == plumbing.ErrObjectNotFound { - obj, err = s.getFromPackfile(h, true) - } - - if err != nil { - return nil, err - } - - if plumbing.AnyObject != t && obj.Type() != t { - return nil, plumbing.ErrObjectNotFound - } - - return obj, nil -} - -func (s *ObjectStorage) getFromUnpacked(h plumbing.Hash) (obj plumbing.EncodedObject, err error) { - f, err := s.dir.Object(h) - if err != nil { - if os.IsNotExist(err) { - return nil, plumbing.ErrObjectNotFound - } - - return nil, err - } - defer ioutil.CheckClose(f, &err) - - if cacheObj, found := s.objectCache.Get(h); found { - return cacheObj, nil - } - - r, err := objfile.NewReader(f) - if err != nil { - return nil, err - } - - defer ioutil.CheckClose(r, &err) - - t, size, err := r.Header() - if err != nil { - return nil, err - } - - if s.options.LargeObjectThreshold > 0 && size > s.options.LargeObjectThreshold { - obj = dotgit.NewEncodedObject(s.dir, h, t, size) - return obj, nil - } - - obj = s.NewEncodedObject() - - obj.SetType(t) - obj.SetSize(size) - w, err := obj.Writer() - if err != nil { - return nil, err - } - - defer ioutil.CheckClose(w, &err) - - bufp := copyBufferPool.Get().(*[]byte) - buf := *bufp - _, err = io.CopyBuffer(w, r, buf) - copyBufferPool.Put(bufp) - - s.objectCache.Put(obj) - - return obj, err -} - -var copyBufferPool = sync.Pool{ - New: func() interface{} { - b := make([]byte, 32*1024) - return &b - }, -} - -// Get returns the object with the given hash, by searching for it in -// the packfile. -func (s *ObjectStorage) getFromPackfile(h plumbing.Hash, canBeDelta bool) ( - plumbing.EncodedObject, error) { - - if err := s.requireIndex(); err != nil { - return nil, err - } - - pack, hash, offset := s.findObjectInPackfile(h) - if offset == -1 { - return nil, plumbing.ErrObjectNotFound - } - - idx := s.index[pack] - p, err := s.packfile(idx, pack) - if err != nil { - return nil, err - } - - if !s.options.KeepDescriptors && s.options.MaxOpenDescriptors == 0 { - defer ioutil.CheckClose(p, &err) - } - - if canBeDelta { - return s.decodeDeltaObjectAt(p, offset, hash) - } - - return s.decodeObjectAt(p, offset) -} - -func (s *ObjectStorage) decodeObjectAt( - p *packfile.Packfile, - offset int64, -) (plumbing.EncodedObject, error) { - hash, err := p.FindHash(offset) - if err == nil { - obj, ok := s.objectCache.Get(hash) - if ok { - return obj, nil - } - } - - if err != nil && err != plumbing.ErrObjectNotFound { - return nil, err - } - - return p.GetByOffset(offset) -} - -func (s *ObjectStorage) decodeDeltaObjectAt( - p *packfile.Packfile, - offset int64, - hash plumbing.Hash, -) (plumbing.EncodedObject, error) { - scan := p.Scanner() - header, err := scan.SeekObjectHeader(offset) - if err != nil { - return nil, err - } - - var ( - base plumbing.Hash - ) - - switch header.Type { - case plumbing.REFDeltaObject: - base = header.Reference - case plumbing.OFSDeltaObject: - base, err = p.FindHash(header.OffsetReference) - if err != nil { - return nil, err - } - default: - return s.decodeObjectAt(p, offset) - } - - obj := &plumbing.MemoryObject{} - obj.SetType(header.Type) - w, err := obj.Writer() - if err != nil { - return nil, err - } - - if _, _, err := scan.NextObject(w); err != nil { - return nil, err - } - - return newDeltaObject(obj, hash, base, header.Length), nil -} - -func (s *ObjectStorage) findObjectInPackfile(h plumbing.Hash) (plumbing.Hash, plumbing.Hash, int64) { - for packfile, index := range s.index { - offset, err := index.FindOffset(h) - if err == nil { - return packfile, h, offset - } - } - - return plumbing.ZeroHash, plumbing.ZeroHash, -1 -} - -// HashesWithPrefix returns all objects with a hash that starts with a prefix by searching for -// them in the packfile and the git object directories. -func (s *ObjectStorage) HashesWithPrefix(prefix []byte) ([]plumbing.Hash, error) { - hashes, err := s.dir.ObjectsWithPrefix(prefix) - if err != nil { - return nil, err - } - - seen := hashListAsMap(hashes) - - // TODO: This could be faster with some idxfile changes, - // or diving into the packfile. - if err := s.requireIndex(); err != nil { - return nil, err - } - for _, index := range s.index { - ei, err := index.Entries() - if err != nil { - return nil, err - } - for { - e, err := ei.Next() - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - if bytes.HasPrefix(e.Hash[:], prefix) { - if _, ok := seen[e.Hash]; ok { - continue - } - hashes = append(hashes, e.Hash) - } - } - ei.Close() - } - - return hashes, nil -} - -// IterEncodedObjects returns an iterator for all the objects in the packfile -// with the given type. -func (s *ObjectStorage) IterEncodedObjects(t plumbing.ObjectType) (storer.EncodedObjectIter, error) { - objects, err := s.dir.Objects() - if err != nil { - return nil, err - } - - seen := make(map[plumbing.Hash]struct{}) - var iters []storer.EncodedObjectIter - if len(objects) != 0 { - iters = append(iters, &objectsIter{s: s, t: t, h: objects}) - seen = hashListAsMap(objects) - } - - packi, err := s.buildPackfileIters(t, seen) - if err != nil { - return nil, err - } - - iters = append(iters, packi) - return storer.NewMultiEncodedObjectIter(iters), nil -} - -func (s *ObjectStorage) buildPackfileIters( - t plumbing.ObjectType, - seen map[plumbing.Hash]struct{}, -) (storer.EncodedObjectIter, error) { - if err := s.requireIndex(); err != nil { - return nil, err - } - - packs, err := s.dir.ObjectPacks() - if err != nil { - return nil, err - } - return &lazyPackfilesIter{ - hashes: packs, - open: func(h plumbing.Hash) (storer.EncodedObjectIter, error) { - pack, err := s.dir.ObjectPack(h) - if err != nil { - return nil, err - } - return newPackfileIter( - s.dir.Fs(), pack, t, seen, s.index[h], - s.objectCache, s.options.KeepDescriptors, - s.options.LargeObjectThreshold, - ) - }, - }, nil -} - -// Close closes all opened files. -func (s *ObjectStorage) Close() error { - var firstError error - if s.options.KeepDescriptors || s.options.MaxOpenDescriptors > 0 { - for _, packfile := range s.packfiles { - err := packfile.Close() - if firstError == nil && err != nil { - firstError = err - } - } - } - - s.packfiles = nil - s.dir.Close() - - return firstError -} - -type lazyPackfilesIter struct { - hashes []plumbing.Hash - open func(h plumbing.Hash) (storer.EncodedObjectIter, error) - cur storer.EncodedObjectIter -} - -func (it *lazyPackfilesIter) Next() (plumbing.EncodedObject, error) { - for { - if it.cur == nil { - if len(it.hashes) == 0 { - return nil, io.EOF - } - h := it.hashes[0] - it.hashes = it.hashes[1:] - - sub, err := it.open(h) - if err == io.EOF { - continue - } else if err != nil { - return nil, err - } - it.cur = sub - } - ob, err := it.cur.Next() - if err == io.EOF { - it.cur.Close() - it.cur = nil - continue - } else if err != nil { - return nil, err - } - return ob, nil - } -} - -func (it *lazyPackfilesIter) ForEach(cb func(plumbing.EncodedObject) error) error { - return storer.ForEachIterator(it, cb) -} - -func (it *lazyPackfilesIter) Close() { - if it.cur != nil { - it.cur.Close() - it.cur = nil - } - it.hashes = nil -} - -type packfileIter struct { - pack billy.File - iter storer.EncodedObjectIter - seen map[plumbing.Hash]struct{} - - // tells whether the pack file should be left open after iteration or not - keepPack bool -} - -// NewPackfileIter returns a new EncodedObjectIter for the provided packfile -// and object type. Packfile and index file will be closed after they're -// used. If keepPack is true the packfile won't be closed after the iteration -// finished. -func NewPackfileIter( - fs billy.Filesystem, - f billy.File, - idxFile billy.File, - t plumbing.ObjectType, - keepPack bool, - largeObjectThreshold int64, -) (storer.EncodedObjectIter, error) { - idx := idxfile.NewMemoryIndex() - if err := idxfile.NewDecoder(idxFile).Decode(idx); err != nil { - return nil, err - } - - if err := idxFile.Close(); err != nil { - return nil, err - } - - seen := make(map[plumbing.Hash]struct{}) - return newPackfileIter(fs, f, t, seen, idx, nil, keepPack, largeObjectThreshold) -} - -func newPackfileIter( - fs billy.Filesystem, - f billy.File, - t plumbing.ObjectType, - seen map[plumbing.Hash]struct{}, - index idxfile.Index, - cache cache.Object, - keepPack bool, - largeObjectThreshold int64, -) (storer.EncodedObjectIter, error) { - var p *packfile.Packfile - if cache != nil { - p = packfile.NewPackfileWithCache(index, fs, f, cache, largeObjectThreshold) - } else { - p = packfile.NewPackfile(index, fs, f, largeObjectThreshold) - } - - iter, err := p.GetByType(t) - if err != nil { - return nil, err - } - - return &packfileIter{ - pack: f, - iter: iter, - seen: seen, - keepPack: keepPack, - }, nil -} - -func (iter *packfileIter) Next() (plumbing.EncodedObject, error) { - for { - obj, err := iter.iter.Next() - if err != nil { - return nil, err - } - - if _, ok := iter.seen[obj.Hash()]; ok { - continue - } - - return obj, nil - } -} - -func (iter *packfileIter) ForEach(cb func(plumbing.EncodedObject) error) error { - for { - o, err := iter.Next() - if err != nil { - if err == io.EOF { - iter.Close() - return nil - } - return err - } - - if err := cb(o); err != nil { - return err - } - } -} - -func (iter *packfileIter) Close() { - iter.iter.Close() - if !iter.keepPack { - _ = iter.pack.Close() - } -} - -type objectsIter struct { - s *ObjectStorage - t plumbing.ObjectType - h []plumbing.Hash -} - -func (iter *objectsIter) Next() (plumbing.EncodedObject, error) { - if len(iter.h) == 0 { - return nil, io.EOF - } - - obj, err := iter.s.getFromUnpacked(iter.h[0]) - iter.h = iter.h[1:] - - if err != nil { - return nil, err - } - - if iter.t != plumbing.AnyObject && iter.t != obj.Type() { - return iter.Next() - } - - return obj, err -} - -func (iter *objectsIter) ForEach(cb func(plumbing.EncodedObject) error) error { - for { - o, err := iter.Next() - if err != nil { - if err == io.EOF { - return nil - } - return err - } - - if err := cb(o); err != nil { - return err - } - } -} - -func (iter *objectsIter) Close() { - iter.h = []plumbing.Hash{} -} - -func hashListAsMap(l []plumbing.Hash) map[plumbing.Hash]struct{} { - m := make(map[plumbing.Hash]struct{}, len(l)) - for _, h := range l { - m[h] = struct{}{} - } - return m -} - -func (s *ObjectStorage) ForEachObjectHash(fun func(plumbing.Hash) error) error { - err := s.dir.ForEachObjectHash(fun) - if err == storer.ErrStop { - return nil - } - return err -} - -func (s *ObjectStorage) LooseObjectTime(hash plumbing.Hash) (time.Time, error) { - fi, err := s.dir.ObjectStat(hash) - if err != nil { - return time.Time{}, err - } - return fi.ModTime(), nil -} - -func (s *ObjectStorage) DeleteLooseObject(hash plumbing.Hash) error { - return s.dir.ObjectDelete(hash) -} - -func (s *ObjectStorage) ObjectPacks() ([]plumbing.Hash, error) { - return s.dir.ObjectPacks() -} - -func (s *ObjectStorage) DeleteOldObjectPackAndIndex(h plumbing.Hash, t time.Time) error { - return s.dir.DeleteOldObjectPackAndIndex(h, t) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/reference.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/reference.go deleted file mode 100644 index d6a79fce58e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/reference.go +++ /dev/null @@ -1,44 +0,0 @@ -package filesystem - -import ( - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit" -) - -type ReferenceStorage struct { - dir *dotgit.DotGit -} - -func (r *ReferenceStorage) SetReference(ref *plumbing.Reference) error { - return r.dir.SetRef(ref, nil) -} - -func (r *ReferenceStorage) CheckAndSetReference(ref, old *plumbing.Reference) error { - return r.dir.SetRef(ref, old) -} - -func (r *ReferenceStorage) Reference(n plumbing.ReferenceName) (*plumbing.Reference, error) { - return r.dir.Ref(n) -} - -func (r *ReferenceStorage) IterReferences() (storer.ReferenceIter, error) { - refs, err := r.dir.Refs() - if err != nil { - return nil, err - } - - return storer.NewReferenceSliceIter(refs), nil -} - -func (r *ReferenceStorage) RemoveReference(n plumbing.ReferenceName) error { - return r.dir.RemoveRef(n) -} - -func (r *ReferenceStorage) CountLooseRefs() (int, error) { - return r.dir.CountLooseRefs() -} - -func (r *ReferenceStorage) PackRefs() error { - return r.dir.PackRefs() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/shallow.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/shallow.go deleted file mode 100644 index 5f898fc1c70..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/shallow.go +++ /dev/null @@ -1,54 +0,0 @@ -package filesystem - -import ( - "bufio" - "fmt" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit" - "github.com/jesseduffield/go-git/v5/utils/ioutil" -) - -// ShallowStorage where the shallow commits are stored, an internal to -// manipulate the shallow file -type ShallowStorage struct { - dir *dotgit.DotGit -} - -// SetShallow save the shallows in the shallow file in the .git folder as one -// commit per line represented by 40-byte hexadecimal object terminated by a -// newline. -func (s *ShallowStorage) SetShallow(commits []plumbing.Hash) error { - f, err := s.dir.ShallowWriter() - if err != nil { - return err - } - - defer ioutil.CheckClose(f, &err) - for _, h := range commits { - if _, err := fmt.Fprintf(f, "%s\n", h); err != nil { - return err - } - } - - return err -} - -// Shallow returns the shallow commits reading from shallo file from .git -func (s *ShallowStorage) Shallow() ([]plumbing.Hash, error) { - f, err := s.dir.Shallow() - if f == nil || err != nil { - return nil, err - } - - defer ioutil.CheckClose(f, &err) - - var hash []plumbing.Hash - - scn := bufio.NewScanner(f) - for scn.Scan() { - hash = append(hash, plumbing.NewHash(scn.Text())) - } - - return hash, scn.Err() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/storage.go b/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/storage.go deleted file mode 100644 index 39633a67547..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/filesystem/storage.go +++ /dev/null @@ -1,85 +0,0 @@ -// Package filesystem is a storage backend base on filesystems -package filesystem - -import ( - "github.com/jesseduffield/go-git/v5/plumbing/cache" - "github.com/jesseduffield/go-git/v5/storage/filesystem/dotgit" - - "github.com/go-git/go-billy/v5" -) - -// Storage is an implementation of git.Storer that stores data on disk in the -// standard git format (this is, the .git directory). Zero values of this type -// are not safe to use, see the NewStorage function below. -type Storage struct { - fs billy.Filesystem - dir *dotgit.DotGit - - ObjectStorage - ReferenceStorage - IndexStorage - ShallowStorage - ConfigStorage - ModuleStorage -} - -// Options holds configuration for the storage. -type Options struct { - // ExclusiveAccess means that the filesystem is not modified externally - // while the repo is open. - ExclusiveAccess bool - // KeepDescriptors makes the file descriptors to be reused but they will - // need to be manually closed calling Close(). - KeepDescriptors bool - // MaxOpenDescriptors is the max number of file descriptors to keep - // open. If KeepDescriptors is true, all file descriptors will remain open. - MaxOpenDescriptors int - // LargeObjectThreshold maximum object size (in bytes) that will be read in to memory. - // If left unset or set to 0 there is no limit - LargeObjectThreshold int64 - // AlternatesFS provides the billy filesystem to be used for Git Alternates. - // If none is provided, it falls back to using the underlying instance used for - // DotGit. - AlternatesFS billy.Filesystem -} - -// NewStorage returns a new Storage backed by a given `fs.Filesystem` and cache. -func NewStorage(fs billy.Filesystem, cache cache.Object) *Storage { - return NewStorageWithOptions(fs, cache, Options{}) -} - -// NewStorageWithOptions returns a new Storage with extra options, -// backed by a given `fs.Filesystem` and cache. -func NewStorageWithOptions(fs billy.Filesystem, cache cache.Object, ops Options) *Storage { - dirOps := dotgit.Options{ - ExclusiveAccess: ops.ExclusiveAccess, - AlternatesFS: ops.AlternatesFS, - } - dir := dotgit.NewWithOptions(fs, dirOps) - - return &Storage{ - fs: fs, - dir: dir, - - ObjectStorage: *NewObjectStorageWithOptions(dir, cache, ops), - ReferenceStorage: ReferenceStorage{dir: dir}, - IndexStorage: IndexStorage{dir: dir}, - ShallowStorage: ShallowStorage{dir: dir}, - ConfigStorage: ConfigStorage{dir: dir}, - ModuleStorage: ModuleStorage{dir: dir}, - } -} - -// Filesystem returns the underlying filesystem -func (s *Storage) Filesystem() billy.Filesystem { - return s.fs -} - -// Init initializes .git directory -func (s *Storage) Init() error { - return s.dir.Initialize() -} - -func (s *Storage) AddAlternate(remote string) error { - return s.dir.AddAlternate(remote) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/memory/storage.go b/vendor/github.com/jesseduffield/go-git/v5/storage/memory/storage.go deleted file mode 100644 index db30af59727..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/memory/storage.go +++ /dev/null @@ -1,324 +0,0 @@ -// Package memory is a storage backend base on memory -package memory - -import ( - "fmt" - "time" - - "github.com/jesseduffield/go-git/v5/config" - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/index" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/storage" -) - -var ErrUnsupportedObjectType = fmt.Errorf("unsupported object type") - -// Storage is an implementation of git.Storer that stores data on memory, being -// ephemeral. The use of this storage should be done in controlled environments, -// since the representation in memory of some repository can fill the machine -// memory. in the other hand this storage has the best performance. -type Storage struct { - ConfigStorage - ObjectStorage - ShallowStorage - IndexStorage - ReferenceStorage - ModuleStorage -} - -// NewStorage returns a new Storage base on memory -func NewStorage() *Storage { - return &Storage{ - ReferenceStorage: make(ReferenceStorage), - ConfigStorage: ConfigStorage{}, - ShallowStorage: ShallowStorage{}, - ObjectStorage: ObjectStorage{ - Objects: make(map[plumbing.Hash]plumbing.EncodedObject), - Commits: make(map[plumbing.Hash]plumbing.EncodedObject), - Trees: make(map[plumbing.Hash]plumbing.EncodedObject), - Blobs: make(map[plumbing.Hash]plumbing.EncodedObject), - Tags: make(map[plumbing.Hash]plumbing.EncodedObject), - }, - ModuleStorage: make(ModuleStorage), - } -} - -type ConfigStorage struct { - config *config.Config -} - -func (c *ConfigStorage) SetConfig(cfg *config.Config) error { - if err := cfg.Validate(); err != nil { - return err - } - - c.config = cfg - return nil -} - -func (c *ConfigStorage) Config() (*config.Config, error) { - if c.config == nil { - c.config = config.NewConfig() - } - - return c.config, nil -} - -type IndexStorage struct { - index *index.Index -} - -func (c *IndexStorage) SetIndex(idx *index.Index) error { - c.index = idx - return nil -} - -func (c *IndexStorage) Index() (*index.Index, error) { - if c.index == nil { - c.index = &index.Index{Version: 2} - } - - return c.index, nil -} - -type ObjectStorage struct { - Objects map[plumbing.Hash]plumbing.EncodedObject - Commits map[plumbing.Hash]plumbing.EncodedObject - Trees map[plumbing.Hash]plumbing.EncodedObject - Blobs map[plumbing.Hash]plumbing.EncodedObject - Tags map[plumbing.Hash]plumbing.EncodedObject -} - -func (o *ObjectStorage) NewEncodedObject() plumbing.EncodedObject { - return &plumbing.MemoryObject{} -} - -func (o *ObjectStorage) SetEncodedObject(obj plumbing.EncodedObject) (plumbing.Hash, error) { - h := obj.Hash() - o.Objects[h] = obj - - switch obj.Type() { - case plumbing.CommitObject: - o.Commits[h] = o.Objects[h] - case plumbing.TreeObject: - o.Trees[h] = o.Objects[h] - case plumbing.BlobObject: - o.Blobs[h] = o.Objects[h] - case plumbing.TagObject: - o.Tags[h] = o.Objects[h] - default: - return h, ErrUnsupportedObjectType - } - - return h, nil -} - -func (o *ObjectStorage) HasEncodedObject(h plumbing.Hash) (err error) { - if _, ok := o.Objects[h]; !ok { - return plumbing.ErrObjectNotFound - } - return nil -} - -func (o *ObjectStorage) EncodedObjectSize(h plumbing.Hash) ( - size int64, err error) { - obj, ok := o.Objects[h] - if !ok { - return 0, plumbing.ErrObjectNotFound - } - - return obj.Size(), nil -} - -func (o *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) { - obj, ok := o.Objects[h] - if !ok || (plumbing.AnyObject != t && obj.Type() != t) { - return nil, plumbing.ErrObjectNotFound - } - - return obj, nil -} - -func (o *ObjectStorage) IterEncodedObjects(t plumbing.ObjectType) (storer.EncodedObjectIter, error) { - var series []plumbing.EncodedObject - switch t { - case plumbing.AnyObject: - series = flattenObjectMap(o.Objects) - case plumbing.CommitObject: - series = flattenObjectMap(o.Commits) - case plumbing.TreeObject: - series = flattenObjectMap(o.Trees) - case plumbing.BlobObject: - series = flattenObjectMap(o.Blobs) - case plumbing.TagObject: - series = flattenObjectMap(o.Tags) - } - - return storer.NewEncodedObjectSliceIter(series), nil -} - -func flattenObjectMap(m map[plumbing.Hash]plumbing.EncodedObject) []plumbing.EncodedObject { - objects := make([]plumbing.EncodedObject, 0, len(m)) - for _, obj := range m { - objects = append(objects, obj) - } - return objects -} - -func (o *ObjectStorage) Begin() storer.Transaction { - return &TxObjectStorage{ - Storage: o, - Objects: make(map[plumbing.Hash]plumbing.EncodedObject), - } -} - -func (o *ObjectStorage) ForEachObjectHash(fun func(plumbing.Hash) error) error { - for h := range o.Objects { - err := fun(h) - if err != nil { - if err == storer.ErrStop { - return nil - } - return err - } - } - return nil -} - -func (o *ObjectStorage) ObjectPacks() ([]plumbing.Hash, error) { - return nil, nil -} -func (o *ObjectStorage) DeleteOldObjectPackAndIndex(plumbing.Hash, time.Time) error { - return nil -} - -var errNotSupported = fmt.Errorf("not supported") - -func (o *ObjectStorage) LooseObjectTime(hash plumbing.Hash) (time.Time, error) { - return time.Time{}, errNotSupported -} -func (o *ObjectStorage) DeleteLooseObject(plumbing.Hash) error { - return errNotSupported -} - -func (o *ObjectStorage) AddAlternate(remote string) error { - return errNotSupported -} - -type TxObjectStorage struct { - Storage *ObjectStorage - Objects map[plumbing.Hash]plumbing.EncodedObject -} - -func (tx *TxObjectStorage) SetEncodedObject(obj plumbing.EncodedObject) (plumbing.Hash, error) { - h := obj.Hash() - tx.Objects[h] = obj - - return h, nil -} - -func (tx *TxObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) { - obj, ok := tx.Objects[h] - if !ok || (plumbing.AnyObject != t && obj.Type() != t) { - return nil, plumbing.ErrObjectNotFound - } - - return obj, nil -} - -func (tx *TxObjectStorage) Commit() error { - for h, obj := range tx.Objects { - delete(tx.Objects, h) - if _, err := tx.Storage.SetEncodedObject(obj); err != nil { - return err - } - } - - return nil -} - -func (tx *TxObjectStorage) Rollback() error { - tx.Objects = make(map[plumbing.Hash]plumbing.EncodedObject) - return nil -} - -type ReferenceStorage map[plumbing.ReferenceName]*plumbing.Reference - -func (r ReferenceStorage) SetReference(ref *plumbing.Reference) error { - if ref != nil { - r[ref.Name()] = ref - } - - return nil -} - -func (r ReferenceStorage) CheckAndSetReference(ref, old *plumbing.Reference) error { - if ref == nil { - return nil - } - - if old != nil { - tmp := r[ref.Name()] - if tmp != nil && tmp.Hash() != old.Hash() { - return storage.ErrReferenceHasChanged - } - } - r[ref.Name()] = ref - return nil -} - -func (r ReferenceStorage) Reference(n plumbing.ReferenceName) (*plumbing.Reference, error) { - ref, ok := r[n] - if !ok { - return nil, plumbing.ErrReferenceNotFound - } - - return ref, nil -} - -func (r ReferenceStorage) IterReferences() (storer.ReferenceIter, error) { - var refs []*plumbing.Reference - for _, ref := range r { - refs = append(refs, ref) - } - - return storer.NewReferenceSliceIter(refs), nil -} - -func (r ReferenceStorage) CountLooseRefs() (int, error) { - return len(r), nil -} - -func (r ReferenceStorage) PackRefs() error { - return nil -} - -func (r ReferenceStorage) RemoveReference(n plumbing.ReferenceName) error { - delete(r, n) - return nil -} - -type ShallowStorage []plumbing.Hash - -func (s *ShallowStorage) SetShallow(commits []plumbing.Hash) error { - *s = commits - return nil -} - -func (s ShallowStorage) Shallow() ([]plumbing.Hash, error) { - return s, nil -} - -type ModuleStorage map[string]*Storage - -func (s ModuleStorage) Module(name string) (storage.Storer, error) { - if m, ok := s[name]; ok { - return m, nil - } - - m := NewStorage() - s[name] = m - - return m, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/storage/storer.go b/vendor/github.com/jesseduffield/go-git/v5/storage/storer.go deleted file mode 100644 index 643592e0c4d..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/storage/storer.go +++ /dev/null @@ -1,30 +0,0 @@ -package storage - -import ( - "errors" - - "github.com/jesseduffield/go-git/v5/config" - "github.com/jesseduffield/go-git/v5/plumbing/storer" -) - -var ErrReferenceHasChanged = errors.New("reference has changed concurrently") - -// Storer is a generic storage of objects, references and any information -// related to a particular repository. The package github.com/jesseduffield/go-git/v5/storage -// contains two implementation a filesystem base implementation (such as `.git`) -// and a memory implementations being ephemeral -type Storer interface { - storer.EncodedObjectStorer - storer.ReferenceStorer - storer.ShallowStorer - storer.IndexStorer - config.ConfigStorer - ModuleStorer -} - -// ModuleStorer allows interact with the modules' Storers -type ModuleStorer interface { - // Module returns a Storer representing a submodule, if not exists returns a - // new empty Storer is returned - Module(name string) (Storer, error) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/submodule.go b/vendor/github.com/jesseduffield/go-git/v5/submodule.go deleted file mode 100644 index 8f16fef3c40..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/submodule.go +++ /dev/null @@ -1,398 +0,0 @@ -package git - -import ( - "bytes" - "context" - "errors" - "fmt" - "path" - - "github.com/go-git/go-billy/v5" - "github.com/jesseduffield/go-git/v5/config" - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/format/index" - "github.com/jesseduffield/go-git/v5/plumbing/transport" -) - -var ( - ErrSubmoduleAlreadyInitialized = errors.New("submodule already initialized") - ErrSubmoduleNotInitialized = errors.New("submodule not initialized") -) - -// Submodule a submodule allows you to keep another Git repository in a -// subdirectory of your repository. -type Submodule struct { - // initialized defines if a submodule was already initialized. - initialized bool - - c *config.Submodule - w *Worktree -} - -// Config returns the submodule config -func (s *Submodule) Config() *config.Submodule { - return s.c -} - -// Init initialize the submodule reading the recorded Entry in the index for -// the given submodule -func (s *Submodule) Init() error { - cfg, err := s.w.r.Config() - if err != nil { - return err - } - - _, ok := cfg.Submodules[s.c.Name] - if ok { - return ErrSubmoduleAlreadyInitialized - } - - s.initialized = true - - cfg.Submodules[s.c.Name] = s.c - return s.w.r.Storer.SetConfig(cfg) -} - -// Status returns the status of the submodule. -func (s *Submodule) Status() (*SubmoduleStatus, error) { - idx, err := s.w.r.Storer.Index() - if err != nil { - return nil, err - } - - return s.status(idx) -} - -func (s *Submodule) status(idx *index.Index) (*SubmoduleStatus, error) { - status := &SubmoduleStatus{ - Path: s.c.Path, - } - - e, err := idx.Entry(s.c.Path) - if err != nil && err != index.ErrEntryNotFound { - return nil, err - } - - if e != nil { - status.Expected = e.Hash - } - - if !s.initialized { - return status, nil - } - - r, err := s.Repository() - if err != nil { - return nil, err - } - - head, err := r.Head() - if err == nil { - status.Current = head.Hash() - } - - if err != nil && err == plumbing.ErrReferenceNotFound { - err = nil - } - - return status, err -} - -// Repository returns the Repository represented by this submodule -func (s *Submodule) Repository() (*Repository, error) { - if !s.initialized { - return nil, ErrSubmoduleNotInitialized - } - - storer, err := s.w.r.Storer.Module(s.c.Name) - if err != nil { - return nil, err - } - - _, err = storer.Reference(plumbing.HEAD) - if err != nil && err != plumbing.ErrReferenceNotFound { - return nil, err - } - - var exists bool - if err == nil { - exists = true - } - - var worktree billy.Filesystem - if worktree, err = s.w.Filesystem.Chroot(s.c.Path); err != nil { - return nil, err - } - - if exists { - return Open(storer, worktree) - } - - r, err := Init(storer, worktree) - if err != nil { - return nil, err - } - - moduleEndpoint, err := transport.NewEndpoint(s.c.URL) - if err != nil { - return nil, err - } - - if !path.IsAbs(moduleEndpoint.Path) && moduleEndpoint.Protocol == "file" { - remotes, err := s.w.r.Remotes() - if err != nil { - return nil, err - } - - rootEndpoint, err := transport.NewEndpoint(remotes[0].c.URLs[0]) - if err != nil { - return nil, err - } - - rootEndpoint.Path = path.Join(rootEndpoint.Path, moduleEndpoint.Path) - *moduleEndpoint = *rootEndpoint - } - - _, err = r.CreateRemote(&config.RemoteConfig{ - Name: DefaultRemoteName, - URLs: []string{moduleEndpoint.String()}, - }) - - return r, err -} - -// Update the registered submodule to match what the superproject expects, the -// submodule should be initialized first calling the Init method or setting in -// the options SubmoduleUpdateOptions.Init equals true -func (s *Submodule) Update(o *SubmoduleUpdateOptions) error { - return s.UpdateContext(context.Background(), o) -} - -// UpdateContext the registered submodule to match what the superproject -// expects, the submodule should be initialized first calling the Init method or -// setting in the options SubmoduleUpdateOptions.Init equals true. -// -// The provided Context must be non-nil. If the context expires before the -// operation is complete, an error is returned. The context only affects the -// transport operations. -func (s *Submodule) UpdateContext(ctx context.Context, o *SubmoduleUpdateOptions) error { - return s.update(ctx, o, plumbing.ZeroHash) -} - -func (s *Submodule) update(ctx context.Context, o *SubmoduleUpdateOptions, forceHash plumbing.Hash) error { - if !s.initialized && !o.Init { - return ErrSubmoduleNotInitialized - } - - if !s.initialized && o.Init { - if err := s.Init(); err != nil { - return err - } - } - - idx, err := s.w.r.Storer.Index() - if err != nil { - return err - } - - hash := forceHash - if hash.IsZero() { - e, err := idx.Entry(s.c.Path) - if err != nil { - return err - } - - hash = e.Hash - } - - r, err := s.Repository() - if err != nil { - return err - } - - if err := s.fetchAndCheckout(ctx, r, o, hash); err != nil { - return err - } - - return s.doRecursiveUpdate(ctx, r, o) -} - -func (s *Submodule) doRecursiveUpdate(ctx context.Context, r *Repository, o *SubmoduleUpdateOptions) error { - if o.RecurseSubmodules == NoRecurseSubmodules { - return nil - } - - w, err := r.Worktree() - if err != nil { - return err - } - - l, err := w.Submodules() - if err != nil { - return err - } - - new := &SubmoduleUpdateOptions{} - *new = *o - - new.RecurseSubmodules-- - return l.UpdateContext(ctx, new) -} - -func (s *Submodule) fetchAndCheckout( - ctx context.Context, r *Repository, o *SubmoduleUpdateOptions, hash plumbing.Hash, -) error { - if !o.NoFetch { - err := r.FetchContext(ctx, &FetchOptions{Auth: o.Auth, Depth: o.Depth}) - if err != nil && err != NoErrAlreadyUpToDate { - return err - } - } - - w, err := r.Worktree() - if err != nil { - return err - } - - // Handle a case when submodule refers to an orphaned commit that's still reachable - // through Git server using a special protocol capability[1]. - // - // [1]: https://git-scm.com/docs/protocol-capabilities#_allow_reachable_sha1_in_want - if !o.NoFetch { - if _, err := w.r.Object(plumbing.AnyObject, hash); err != nil { - refSpec := config.RefSpec("+" + hash.String() + ":" + hash.String()) - - err := r.FetchContext(ctx, &FetchOptions{ - Auth: o.Auth, - RefSpecs: []config.RefSpec{refSpec}, - Depth: o.Depth, - }) - if err != nil && err != NoErrAlreadyUpToDate && err != ErrExactSHA1NotSupported { - return err - } - } - } - - if err := w.Checkout(&CheckoutOptions{Hash: hash}); err != nil { - return err - } - - head := plumbing.NewHashReference(plumbing.HEAD, hash) - return r.Storer.SetReference(head) -} - -// Submodules list of several submodules from the same repository. -type Submodules []*Submodule - -// Init initializes the submodules in this list. -func (s Submodules) Init() error { - for _, sub := range s { - if err := sub.Init(); err != nil { - return err - } - } - - return nil -} - -// Update updates all the submodules in this list. -func (s Submodules) Update(o *SubmoduleUpdateOptions) error { - return s.UpdateContext(context.Background(), o) -} - -// UpdateContext updates all the submodules in this list. -// -// The provided Context must be non-nil. If the context expires before the -// operation is complete, an error is returned. The context only affects the -// transport operations. -func (s Submodules) UpdateContext(ctx context.Context, o *SubmoduleUpdateOptions) error { - for _, sub := range s { - if err := sub.UpdateContext(ctx, o); err != nil { - return err - } - } - - return nil -} - -// Status returns the status of the submodules. -func (s Submodules) Status() (SubmodulesStatus, error) { - var list SubmodulesStatus - - var r *Repository - for _, sub := range s { - if r == nil { - r = sub.w.r - } - - idx, err := r.Storer.Index() - if err != nil { - return nil, err - } - - status, err := sub.status(idx) - if err != nil { - return nil, err - } - - list = append(list, status) - } - - return list, nil -} - -// SubmodulesStatus contains the status for all submodiles in the worktree -type SubmodulesStatus []*SubmoduleStatus - -// String is equivalent to `git submodule status` -func (s SubmodulesStatus) String() string { - buf := bytes.NewBuffer(nil) - for _, sub := range s { - fmt.Fprintln(buf, sub) - } - - return buf.String() -} - -// SubmoduleStatus contains the status for a submodule in the worktree -type SubmoduleStatus struct { - Path string - Current plumbing.Hash - Expected plumbing.Hash - Branch plumbing.ReferenceName -} - -// IsClean is the HEAD of the submodule is equals to the expected commit -func (s *SubmoduleStatus) IsClean() bool { - return s.Current == s.Expected -} - -// String is equivalent to `git submodule status ` -// -// This will print the SHA-1 of the currently checked out commit for a -// submodule, along with the submodule path and the output of git describe fo -// the SHA-1. Each SHA-1 will be prefixed with - if the submodule is not -// initialized, + if the currently checked out submodule commit does not match -// the SHA-1 found in the index of the containing repository. -func (s *SubmoduleStatus) String() string { - var extra string - var status = ' ' - - if s.Current.IsZero() { - status = '-' - } else if !s.IsClean() { - status = '+' - } - - if len(s.Branch) != 0 { - extra = string(s.Branch[5:]) - } else if !s.Current.IsZero() { - extra = s.Current.String()[:7] - } - - if extra != "" { - extra = fmt.Sprintf(" (%s)", extra) - } - - return fmt.Sprintf("%c%s %s%s", status, s.Expected, s.Path, extra) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/binary/read.go b/vendor/github.com/jesseduffield/go-git/v5/utils/binary/read.go deleted file mode 100644 index 66970df399c..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/binary/read.go +++ /dev/null @@ -1,180 +0,0 @@ -// Package binary implements syntax-sugar functions on top of the standard -// library binary package -package binary - -import ( - "bufio" - "encoding/binary" - "io" - - "github.com/jesseduffield/go-git/v5/plumbing" -) - -// Read reads structured binary data from r into data. Bytes are read and -// decoded in BigEndian order -// https://golang.org/pkg/encoding/binary/#Read -func Read(r io.Reader, data ...interface{}) error { - for _, v := range data { - if err := binary.Read(r, binary.BigEndian, v); err != nil { - return err - } - } - - return nil -} - -// ReadUntil reads from r untin delim is found -func ReadUntil(r io.Reader, delim byte) ([]byte, error) { - if bufr, ok := r.(*bufio.Reader); ok { - return ReadUntilFromBufioReader(bufr, delim) - } - - var buf [1]byte - value := make([]byte, 0, 16) - for { - if _, err := io.ReadFull(r, buf[:]); err != nil { - if err == io.EOF { - return nil, err - } - - return nil, err - } - - if buf[0] == delim { - return value, nil - } - - value = append(value, buf[0]) - } -} - -// ReadUntilFromBufioReader is like bufio.ReadBytes but drops the delimiter -// from the result. -func ReadUntilFromBufioReader(r *bufio.Reader, delim byte) ([]byte, error) { - value, err := r.ReadBytes(delim) - if err != nil || len(value) == 0 { - return nil, err - } - - return value[:len(value)-1], nil -} - -// ReadVariableWidthInt reads and returns an int in Git VLQ special format: -// -// Ordinary VLQ has some redundancies, example: the number 358 can be -// encoded as the 2-octet VLQ 0x8166 or the 3-octet VLQ 0x808166 or the -// 4-octet VLQ 0x80808166 and so forth. -// -// To avoid these redundancies, the VLQ format used in Git removes this -// prepending redundancy and extends the representable range of shorter -// VLQs by adding an offset to VLQs of 2 or more octets in such a way -// that the lowest possible value for such an (N+1)-octet VLQ becomes -// exactly one more than the maximum possible value for an N-octet VLQ. -// In particular, since a 1-octet VLQ can store a maximum value of 127, -// the minimum 2-octet VLQ (0x8000) is assigned the value 128 instead of -// 0. Conversely, the maximum value of such a 2-octet VLQ (0xff7f) is -// 16511 instead of just 16383. Similarly, the minimum 3-octet VLQ -// (0x808000) has a value of 16512 instead of zero, which means -// that the maximum 3-octet VLQ (0xffff7f) is 2113663 instead of -// just 2097151. And so forth. -// -// This is how the offset is saved in C: -// -// dheader[pos] = ofs & 127; -// while (ofs >>= 7) -// dheader[--pos] = 128 | (--ofs & 127); -// -func ReadVariableWidthInt(r io.Reader) (int64, error) { - var c byte - if err := Read(r, &c); err != nil { - return 0, err - } - - var v = int64(c & maskLength) - for c&maskContinue > 0 { - v++ - if err := Read(r, &c); err != nil { - return 0, err - } - - v = (v << lengthBits) + int64(c&maskLength) - } - - return v, nil -} - -const ( - maskContinue = uint8(128) // 1000 000 - maskLength = uint8(127) // 0111 1111 - lengthBits = uint8(7) // subsequent bytes has 7 bits to store the length -) - -// ReadUint64 reads 8 bytes and returns them as a BigEndian uint32 -func ReadUint64(r io.Reader) (uint64, error) { - var v uint64 - if err := binary.Read(r, binary.BigEndian, &v); err != nil { - return 0, err - } - - return v, nil -} - -// ReadUint32 reads 4 bytes and returns them as a BigEndian uint32 -func ReadUint32(r io.Reader) (uint32, error) { - var v uint32 - if err := binary.Read(r, binary.BigEndian, &v); err != nil { - return 0, err - } - - return v, nil -} - -// ReadUint16 reads 2 bytes and returns them as a BigEndian uint16 -func ReadUint16(r io.Reader) (uint16, error) { - var v uint16 - if err := binary.Read(r, binary.BigEndian, &v); err != nil { - return 0, err - } - - return v, nil -} - -// ReadHash reads a plumbing.Hash from r -func ReadHash(r io.Reader) (plumbing.Hash, error) { - var h plumbing.Hash - if err := binary.Read(r, binary.BigEndian, h[:]); err != nil { - return plumbing.ZeroHash, err - } - - return h, nil -} - -const sniffLen = 8000 - -// IsBinary detects if data is a binary value based on: -// http://git.kernel.org/cgit/git/git.git/tree/xdiff-interface.c?id=HEAD#n198 -func IsBinary(r io.Reader) (bool, error) { - reader := bufio.NewReader(r) - c := 0 - for { - if c == sniffLen { - break - } - - b, err := reader.ReadByte() - if err == io.EOF { - break - } - if err != nil { - return false, err - } - - if b == byte(0) { - return true, nil - } - - c++ - } - - return false, nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/binary/write.go b/vendor/github.com/jesseduffield/go-git/v5/utils/binary/write.go deleted file mode 100644 index c08c73a06b2..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/binary/write.go +++ /dev/null @@ -1,50 +0,0 @@ -package binary - -import ( - "encoding/binary" - "io" -) - -// Write writes the binary representation of data into w, using BigEndian order -// https://golang.org/pkg/encoding/binary/#Write -func Write(w io.Writer, data ...interface{}) error { - for _, v := range data { - if err := binary.Write(w, binary.BigEndian, v); err != nil { - return err - } - } - - return nil -} - -func WriteVariableWidthInt(w io.Writer, n int64) error { - buf := []byte{byte(n & 0x7f)} - n >>= 7 - for n != 0 { - n-- - buf = append([]byte{0x80 | (byte(n & 0x7f))}, buf...) - n >>= 7 - } - - _, err := w.Write(buf) - - return err -} - -// WriteUint64 writes the binary representation of a uint64 into w, in BigEndian -// order -func WriteUint64(w io.Writer, value uint64) error { - return binary.Write(w, binary.BigEndian, value) -} - -// WriteUint32 writes the binary representation of a uint32 into w, in BigEndian -// order -func WriteUint32(w io.Writer, value uint32) error { - return binary.Write(w, binary.BigEndian, value) -} - -// WriteUint16 writes the binary representation of a uint16 into w, in BigEndian -// order -func WriteUint16(w io.Writer, value uint16) error { - return binary.Write(w, binary.BigEndian, value) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/diff/diff.go b/vendor/github.com/jesseduffield/go-git/v5/utils/diff/diff.go deleted file mode 100644 index 70054949fd9..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/diff/diff.go +++ /dev/null @@ -1,61 +0,0 @@ -// Package diff implements line oriented diffs, similar to the ancient -// Unix diff command. -// -// The current implementation is just a wrapper around Sergi's -// go-diff/diffmatchpatch library, which is a go port of Neil -// Fraser's google-diff-match-patch code -package diff - -import ( - "bytes" - "time" - - "github.com/sergi/go-diff/diffmatchpatch" -) - -// Do computes the (line oriented) modifications needed to turn the src -// string into the dst string. The underlying algorithm is Meyers, -// its complexity is O(N*d) where N is min(lines(src), lines(dst)) and d -// is the size of the diff. -func Do(src, dst string) (diffs []diffmatchpatch.Diff) { - // the default timeout is time.Second which may be too small under heavy load - return DoWithTimeout(src, dst, time.Hour) -} - -// DoWithTimeout computes the (line oriented) modifications needed to turn the src -// string into the dst string. The `timeout` argument specifies the maximum -// amount of time it is allowed to spend in this function. If the timeout -// is exceeded, the parts of the strings which were not considered are turned into -// a bulk delete+insert and the half-baked suboptimal result is returned at once. -// The underlying algorithm is Meyers, its complexity is O(N*d) where N is -// min(lines(src), lines(dst)) and d is the size of the diff. -func DoWithTimeout(src, dst string, timeout time.Duration) (diffs []diffmatchpatch.Diff) { - dmp := diffmatchpatch.New() - dmp.DiffTimeout = timeout - wSrc, wDst, warray := dmp.DiffLinesToRunes(src, dst) - diffs = dmp.DiffMainRunes(wSrc, wDst, false) - diffs = dmp.DiffCharsToLines(diffs, warray) - return diffs -} - -// Dst computes and returns the destination text. -func Dst(diffs []diffmatchpatch.Diff) string { - var text bytes.Buffer - for _, d := range diffs { - if d.Type != diffmatchpatch.DiffDelete { - text.WriteString(d.Text) - } - } - return text.String() -} - -// Src computes and returns the source text -func Src(diffs []diffmatchpatch.Diff) string { - var text bytes.Buffer - for _, d := range diffs { - if d.Type != diffmatchpatch.DiffInsert { - text.WriteString(d.Text) - } - } - return text.String() -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/ioutil/common.go b/vendor/github.com/jesseduffield/go-git/v5/utils/ioutil/common.go deleted file mode 100644 index 235af717bcb..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/ioutil/common.go +++ /dev/null @@ -1,210 +0,0 @@ -// Package ioutil implements some I/O utility functions. -package ioutil - -import ( - "bufio" - "context" - "errors" - "io" - - ctxio "github.com/jbenet/go-context/io" -) - -type readPeeker interface { - io.Reader - Peek(int) ([]byte, error) -} - -var ( - ErrEmptyReader = errors.New("reader is empty") -) - -// NonEmptyReader takes a reader and returns it if it is not empty, or -// `ErrEmptyReader` if it is empty. If there is an error when reading the first -// byte of the given reader, it will be propagated. -func NonEmptyReader(r io.Reader) (io.Reader, error) { - pr, ok := r.(readPeeker) - if !ok { - pr = bufio.NewReader(r) - } - - _, err := pr.Peek(1) - if err == io.EOF { - return nil, ErrEmptyReader - } - - if err != nil { - return nil, err - } - - return pr, nil -} - -type readCloser struct { - io.Reader - closer io.Closer -} - -func (r *readCloser) Close() error { - return r.closer.Close() -} - -// NewReadCloser creates an `io.ReadCloser` with the given `io.Reader` and -// `io.Closer`. -func NewReadCloser(r io.Reader, c io.Closer) io.ReadCloser { - return &readCloser{Reader: r, closer: c} -} - -type readCloserCloser struct { - io.ReadCloser - closer func() error -} - -func (r *readCloserCloser) Close() (err error) { - defer func() { - if err == nil { - err = r.closer() - return - } - _ = r.closer() - }() - return r.ReadCloser.Close() -} - -// NewReadCloserWithCloser creates an `io.ReadCloser` with the given `io.ReaderCloser` and -// `io.Closer` that ensures that the closer is closed on close -func NewReadCloserWithCloser(r io.ReadCloser, c func() error) io.ReadCloser { - return &readCloserCloser{ReadCloser: r, closer: c} -} - -type writeCloser struct { - io.Writer - closer io.Closer -} - -func (r *writeCloser) Close() error { - return r.closer.Close() -} - -// NewWriteCloser creates an `io.WriteCloser` with the given `io.Writer` and -// `io.Closer`. -func NewWriteCloser(w io.Writer, c io.Closer) io.WriteCloser { - return &writeCloser{Writer: w, closer: c} -} - -type writeNopCloser struct { - io.Writer -} - -func (writeNopCloser) Close() error { return nil } - -// WriteNopCloser returns a WriteCloser with a no-op Close method wrapping -// the provided Writer w. -func WriteNopCloser(w io.Writer) io.WriteCloser { - return writeNopCloser{w} -} - -type readerAtAsReader struct { - io.ReaderAt - offset int64 -} - -func (r *readerAtAsReader) Read(bs []byte) (int, error) { - n, err := r.ReaderAt.ReadAt(bs, r.offset) - r.offset += int64(n) - return n, err -} - -func NewReaderUsingReaderAt(r io.ReaderAt, offset int64) io.Reader { - return &readerAtAsReader{ - ReaderAt: r, - offset: offset, - } -} - -// CheckClose calls Close on the given io.Closer. If the given *error points to -// nil, it will be assigned the error returned by Close. Otherwise, any error -// returned by Close will be ignored. CheckClose is usually called with defer. -func CheckClose(c io.Closer, err *error) { - if cerr := c.Close(); cerr != nil && *err == nil { - *err = cerr - } -} - -// NewContextWriter wraps a writer to make it respect given Context. -// If there is a blocking write, the returned Writer will return whenever the -// context is cancelled (the return values are n=0 and err=ctx.Err()). -func NewContextWriter(ctx context.Context, w io.Writer) io.Writer { - return ctxio.NewWriter(ctx, w) -} - -// NewContextReader wraps a reader to make it respect given Context. -// If there is a blocking read, the returned Reader will return whenever the -// context is cancelled (the return values are n=0 and err=ctx.Err()). -func NewContextReader(ctx context.Context, r io.Reader) io.Reader { - return ctxio.NewReader(ctx, r) -} - -// NewContextWriteCloser as NewContextWriter but with io.Closer interface. -func NewContextWriteCloser(ctx context.Context, w io.WriteCloser) io.WriteCloser { - ctxw := ctxio.NewWriter(ctx, w) - return NewWriteCloser(ctxw, w) -} - -// NewContextReadCloser as NewContextReader but with io.Closer interface. -func NewContextReadCloser(ctx context.Context, r io.ReadCloser) io.ReadCloser { - ctxr := ctxio.NewReader(ctx, r) - return NewReadCloser(ctxr, r) -} - -type readerOnError struct { - io.Reader - notify func(error) -} - -// NewReaderOnError returns a io.Reader that call the notify function when an -// unexpected (!io.EOF) error happens, after call Read function. -func NewReaderOnError(r io.Reader, notify func(error)) io.Reader { - return &readerOnError{r, notify} -} - -// NewReadCloserOnError returns a io.ReadCloser that call the notify function -// when an unexpected (!io.EOF) error happens, after call Read function. -func NewReadCloserOnError(r io.ReadCloser, notify func(error)) io.ReadCloser { - return NewReadCloser(NewReaderOnError(r, notify), r) -} - -func (r *readerOnError) Read(buf []byte) (n int, err error) { - n, err = r.Reader.Read(buf) - if err != nil && err != io.EOF { - r.notify(err) - } - - return -} - -type writerOnError struct { - io.Writer - notify func(error) -} - -// NewWriterOnError returns a io.Writer that call the notify function when an -// unexpected (!io.EOF) error happens, after call Write function. -func NewWriterOnError(w io.Writer, notify func(error)) io.Writer { - return &writerOnError{w, notify} -} - -// NewWriteCloserOnError returns a io.WriteCloser that call the notify function -// when an unexpected (!io.EOF) error happens, after call Write function. -func NewWriteCloserOnError(w io.WriteCloser, notify func(error)) io.WriteCloser { - return NewWriteCloser(NewWriterOnError(w, notify), w) -} - -func (r *writerOnError) Write(p []byte) (n int, err error) { - n, err = r.Writer.Write(p) - if err != nil && err != io.EOF { - r.notify(err) - } - - return -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/change.go b/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/change.go deleted file mode 100644 index f408261fc0a..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/change.go +++ /dev/null @@ -1,158 +0,0 @@ -package merkletrie - -import ( - "errors" - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" -) - -var ( - ErrEmptyFileName = errors.New("empty filename in tree entry") -) - -// Action values represent the kind of things a Change can represent: -// insertion, deletions or modifications of files. -type Action int - -// The set of possible actions in a change. -const ( - _ Action = iota - Insert - Delete - Modify -) - -// String returns the action as a human readable text. -func (a Action) String() string { - switch a { - case Insert: - return "Insert" - case Delete: - return "Delete" - case Modify: - return "Modify" - default: - panic(fmt.Sprintf("unsupported action: %d", a)) - } -} - -// A Change value represent how a noder has change between to merkletries. -type Change struct { - // The noder before the change or nil if it was inserted. - From noder.Path - // The noder after the change or nil if it was deleted. - To noder.Path -} - -// Action is convenience method that returns what Action c represents. -func (c *Change) Action() (Action, error) { - if c.From == nil && c.To == nil { - return Action(0), fmt.Errorf("malformed change: nil from and to") - } - if c.From == nil { - return Insert, nil - } - if c.To == nil { - return Delete, nil - } - - return Modify, nil -} - -// NewInsert returns a new Change representing the insertion of n. -func NewInsert(n noder.Path) Change { return Change{To: n} } - -// NewDelete returns a new Change representing the deletion of n. -func NewDelete(n noder.Path) Change { return Change{From: n} } - -// NewModify returns a new Change representing that a has been modified and -// it is now b. -func NewModify(a, b noder.Path) Change { - return Change{ - From: a, - To: b, - } -} - -// String returns a single change in human readable form, using the -// format: '<' + action + space + path + '>'. The contents of the file -// before or after the change are not included in this format. -// -// Example: inserting a file at the path a/b/c.txt will return "". -func (c Change) String() string { - action, err := c.Action() - if err != nil { - panic(err) - } - - var path string - if action == Delete { - path = c.From.String() - } else { - path = c.To.String() - } - - return fmt.Sprintf("<%s %s>", action, path) -} - -// Changes is a list of changes between to merkletries. -type Changes []Change - -// NewChanges returns an empty list of changes. -func NewChanges() Changes { - return Changes{} -} - -// Add adds the change c to the list of changes. -func (l *Changes) Add(c Change) { - *l = append(*l, c) -} - -// AddRecursiveInsert adds the required changes to insert all the -// file-like noders found in root, recursively. -func (l *Changes) AddRecursiveInsert(root noder.Path) error { - return l.addRecursive(root, NewInsert) -} - -// AddRecursiveDelete adds the required changes to delete all the -// file-like noders found in root, recursively. -func (l *Changes) AddRecursiveDelete(root noder.Path) error { - return l.addRecursive(root, NewDelete) -} - -type noderToChangeFn func(noder.Path) Change // NewInsert or NewDelete - -func (l *Changes) addRecursive(root noder.Path, ctor noderToChangeFn) error { - if root.String() == "" { - return ErrEmptyFileName - } - - if !root.IsDir() { - l.Add(ctor(root)) - return nil - } - - i, err := NewIterFromPath(root) - if err != nil { - return err - } - - var current noder.Path - for { - if current, err = i.Step(); err != nil { - if err == io.EOF { - break - } - return err - } - if current.IsDir() { - continue - } - l.Add(ctor(current)) - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/difftree.go b/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/difftree.go deleted file mode 100644 index bd5805e4a94..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/difftree.go +++ /dev/null @@ -1,453 +0,0 @@ -package merkletrie - -// The focus of this difftree implementation is to save time by -// skipping whole directories if their hash is the same in both -// trees. -// -// The diff algorithm implemented here is based on the doubleiter -// type defined in this same package; we will iterate over both -// trees at the same time, while comparing the current noders in -// each iterator. Depending on how they differ we will output the -// corresponding changes and move the iterators further over both -// trees. -// -// The table below shows all the possible comparison results, along -// with what changes should we produce and how to advance the -// iterators. -// -// The table is implemented by the switches in this function, -// diffTwoNodes, diffTwoNodesSameName and diffTwoDirs. -// -// Many Bothans died to bring us this information, make sure you -// understand the table before modifying this code. - -// # Cases -// -// When comparing noders in both trees you will find yourself in -// one of 169 possible cases, but if we ignore moves, we can -// simplify a lot the search space into the following table: -// -// - "-": nothing, no file or directory -// - a<>: an empty file named "a". -// - a<1>: a file named "a", with "1" as its contents. -// - a<2>: a file named "a", with "2" as its contents. -// - a(): an empty dir named "a". -// - a(...): a dir named "a", with some files and/or dirs inside (possibly -// empty). -// - a(;;;): a dir named "a", with some other files and/or dirs inside -// (possibly empty), which different from the ones in "a(...)". -// -// \ to - a<> a<1> a<2> a() a(...) a(;;;) -// from \ -// - 00 01 02 03 04 05 06 -// a<> 10 11 12 13 14 15 16 -// a<1> 20 21 22 23 24 25 26 -// a<2> 30 31 32 33 34 35 36 -// a() 40 41 42 43 44 45 46 -// a(...) 50 51 52 53 54 55 56 -// a(;;;) 60 61 62 63 64 65 66 -// -// Every (from, to) combination in the table is a special case, but -// some of them can be merged into some more general cases, for -// instance 11 and 22 can be merged into the general case: both -// noders are equal. -// -// Here is a full list of all the cases that are similar and how to -// merge them together into more general cases. Each general case -// is labeled with an uppercase letter for further reference, and it -// is followed by the pseudocode of the checks you have to perform -// on both noders to see if you are in such a case, the actions to -// perform (i.e. what changes to output) and how to advance the -// iterators of each tree to continue the comparison process. -// -// ## A. Impossible: 00 -// -// ## B. Same thing on both sides: 11, 22, 33, 44, 55, 66 -// - check: `SameName() && SameHash()` -// - action: do nothing. -// - advance: `FromNext(); ToNext()` -// -// ### C. To was created: 01, 02, 03, 04, 05, 06 -// - check: `DifferentName() && ToBeforeFrom()` -// - action: insertRecursively(to) -// - advance: `ToNext()` -// -// ### D. From was deleted: 10, 20, 30, 40, 50, 60 -// - check: `DifferentName() && FromBeforeTo()` -// - action: `DeleteRecursively(from)` -// - advance: `FromNext()` -// -// ### E. Empty file to file with contents: 12, 13 -// - check: `SameName() && DifferentHash() && FromIsFile() && -// ToIsFile() && FromIsEmpty()` -// - action: `modifyFile(from, to)` -// - advance: `FromNext()` or `FromStep()` -// -// ### E'. file with contents to empty file: 21, 31 -// - check: `SameName() && DifferentHash() && FromIsFile() && -// ToIsFile() && ToIsEmpty()` -// - action: `modifyFile(from, to)` -// - advance: `FromNext()` or `FromStep()` -// -// ### F. empty file to empty dir with the same name: 14 -// - check: `SameName() && FromIsFile() && FromIsEmpty() && -// ToIsDir() && ToIsEmpty()` -// - action: `DeleteFile(from); InsertEmptyDir(to)` -// - advance: `FromNext(); ToNext()` -// -// ### F'. empty dir to empty file of the same name: 41 -// - check: `SameName() && FromIsDir() && FromIsEmpty && -// ToIsFile() && ToIsEmpty()` -// - action: `DeleteEmptyDir(from); InsertFile(to)` -// - advance: `FromNext(); ToNext()` or step for any of them. -// -// ### G. empty file to non-empty dir of the same name: 15, 16 -// - check: `SameName() && FromIsFile() && ToIsDir() && -// FromIsEmpty() && ToIsNotEmpty()` -// - action: `DeleteFile(from); InsertDirRecursively(to)` -// - advance: `FromNext(); ToNext()` -// -// ### G'. non-empty dir to empty file of the same name: 51, 61 -// - check: `SameName() && FromIsDir() && FromIsNotEmpty() && -// ToIsFile() && FromIsEmpty()` -// - action: `DeleteDirRecursively(from); InsertFile(to)` -// - advance: `FromNext(); ToNext()` -// -// ### H. modify file contents: 23, 32 -// - check: `SameName() && FromIsFile() && ToIsFile() && -// FromIsNotEmpty() && ToIsNotEmpty()` -// - action: `ModifyFile(from, to)` -// - advance: `FromNext(); ToNext()` -// -// ### I. file with contents to empty dir: 24, 34 -// - check: `SameName() && DifferentHash() && FromIsFile() && -// FromIsNotEmpty() && ToIsDir() && ToIsEmpty()` -// - action: `DeleteFile(from); InsertEmptyDir(to)` -// - advance: `FromNext(); ToNext()` -// -// ### I'. empty dir to file with contents: 42, 43 -// - check: `SameName() && DifferentHash() && FromIsDir() && -// FromIsEmpty() && ToIsFile() && ToIsEmpty()` -// - action: `DeleteDir(from); InsertFile(to)` -// - advance: `FromNext(); ToNext()` -// -// ### J. file with contents to dir with contents: 25, 26, 35, 36 -// - check: `SameName() && DifferentHash() && FromIsFile() && -// FromIsNotEmpty() && ToIsDir() && ToIsNotEmpty()` -// - action: `DeleteFile(from); InsertDirRecursively(to)` -// - advance: `FromNext(); ToNext()` -// -// ### J'. dir with contents to file with contents: 52, 62, 53, 63 -// - check: `SameName() && DifferentHash() && FromIsDir() && -// FromIsNotEmpty() && ToIsFile() && ToIsNotEmpty()` -// - action: `DeleteDirRecursively(from); InsertFile(to)` -// - advance: `FromNext(); ToNext()` -// -// ### K. empty dir to dir with contents: 45, 46 -// - check: `SameName() && DifferentHash() && FromIsDir() && -// FromIsEmpty() && ToIsDir() && ToIsNotEmpty()` -// - action: `InsertChildrenRecursively(to)` -// - advance: `FromNext(); ToNext()` -// -// ### K'. dir with contents to empty dir: 54, 64 -// - check: `SameName() && DifferentHash() && FromIsDir() && -// FromIsEmpty() && ToIsDir() && ToIsNotEmpty()` -// - action: `DeleteChildrenRecursively(from)` -// - advance: `FromNext(); ToNext()` -// -// ### L. dir with contents to dir with different contents: 56, 65 -// - check: `SameName() && DifferentHash() && FromIsDir() && -// FromIsNotEmpty() && ToIsDir() && ToIsNotEmpty()` -// - action: nothing -// - advance: `FromStep(); ToStep()` -// -// - -// All these cases can be further simplified by a truth table -// reduction process, in which we gather similar checks together to -// make the final code easier to read and understand. -// -// The first 6 columns are the outputs of the checks to perform on -// both noders. I have labeled them 1 to 6, this is what they mean: -// -// 1: SameName() -// 2: SameHash() -// 3: FromIsDir() -// 4: ToIsDir() -// 5: FromIsEmpty() -// 6: ToIsEmpty() -// -// The from and to columns are a fsnoder example of the elements -// that you will find on each tree under the specified comparison -// results (columns 1 to 6). -// -// The type column identifies the case we are into, from the list above. -// -// The type' column identifies the new set of reduced cases, using -// lowercase letters, and they are explained after the table. -// -// The last column is the set of actions and advances for each case. -// -// "---" means impossible except in case of hash collision. -// -// advance meaning: -// - NN: from.Next(); to.Next() -// - SS: from.Step(); to.Step() -// -// 1 2 3 4 5 6 | from | to |type|type'|action ; advance -// ------------+--------+--------+----+------------------------------------ -// 0 0 0 0 0 0 | | | | | if !SameName() { -// . | | | | | if FromBeforeTo() { -// . | | | D | d | delete(from); from.Next() -// . | | | | | } else { -// . | | | C | c | insert(to); to.Next() -// . | | | | | } -// 0 1 1 1 1 1 | | | | | } -// 1 0 0 0 0 0 | a<1> | a<2> | H | e | modify(from, to); NN -// 1 0 0 0 0 1 | a<1> | a<> | E' | e | modify(from, to); NN -// 1 0 0 0 1 0 | a<> | a<1> | E | e | modify(from, to); NN -// 1 0 0 0 1 1 | ---- | ---- | | e | -// 1 0 0 1 0 0 | a<1> | a(...) | J | f | delete(from); insert(to); NN -// 1 0 0 1 0 1 | a<1> | a() | I | f | delete(from); insert(to); NN -// 1 0 0 1 1 0 | a<> | a(...) | G | f | delete(from); insert(to); NN -// 1 0 0 1 1 1 | a<> | a() | F | f | delete(from); insert(to); NN -// 1 0 1 0 0 0 | a(...) | a<1> | J' | f | delete(from); insert(to); NN -// 1 0 1 0 0 1 | a(...) | a<> | G' | f | delete(from); insert(to); NN -// 1 0 1 0 1 0 | a() | a<1> | I' | f | delete(from); insert(to); NN -// 1 0 1 0 1 1 | a() | a<> | F' | f | delete(from); insert(to); NN -// 1 0 1 1 0 0 | a(...) | a(;;;) | L | g | nothing; SS -// 1 0 1 1 0 1 | a(...) | a() | K' | h | deleteChildren(from); NN -// 1 0 1 1 1 0 | a() | a(...) | K | i | insertChildren(to); NN -// 1 0 1 1 1 1 | ---- | ---- | | | -// 1 1 0 0 0 0 | a<1> | a<1> | B | b | nothing; NN -// 1 1 0 0 0 1 | ---- | ---- | | b | -// 1 1 0 0 1 0 | ---- | ---- | | b | -// 1 1 0 0 1 1 | a<> | a<> | B | b | nothing; NN -// 1 1 0 1 0 0 | ---- | ---- | | b | -// 1 1 0 1 0 1 | ---- | ---- | | b | -// 1 1 0 1 1 0 | ---- | ---- | | b | -// 1 1 0 1 1 1 | ---- | ---- | | b | -// 1 1 1 0 0 0 | ---- | ---- | | b | -// 1 1 1 0 0 1 | ---- | ---- | | b | -// 1 1 1 0 1 0 | ---- | ---- | | b | -// 1 1 1 0 1 1 | ---- | ---- | | b | -// 1 1 1 1 0 0 | a(...) | a(...) | B | b | nothing; NN -// 1 1 1 1 0 1 | ---- | ---- | | b | -// 1 1 1 1 1 0 | ---- | ---- | | b | -// 1 1 1 1 1 1 | a() | a() | B | b | nothing; NN -// -// c and d: -// if !SameName() -// d if FromBeforeTo() -// c else -// b: SameName) && sameHash() -// e: SameName() && !sameHash() && BothAreFiles() -// f: SameName() && !sameHash() && FileAndDir() -// g: SameName() && !sameHash() && BothAreDirs() && NoneIsEmpty -// i: SameName() && !sameHash() && BothAreDirs() && FromIsEmpty -// h: else of i - -import ( - "context" - "errors" - "fmt" - - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" -) - -var ( - // ErrCanceled is returned whenever the operation is canceled. - ErrCanceled = errors.New("operation canceled") -) - -// DiffTree calculates the list of changes between two merkletries. It -// uses the provided hashEqual callback to compare noders. -func DiffTree( - fromTree, - toTree noder.Noder, - hashEqual noder.Equal, -) (Changes, error) { - return DiffTreeContext(context.Background(), fromTree, toTree, hashEqual) -} - -// DiffTreeContext calculates the list of changes between two merkletries. It -// uses the provided hashEqual callback to compare noders. -// Error will be returned if context expires -// Provided context must be non nil -func DiffTreeContext(ctx context.Context, fromTree, toTree noder.Noder, - hashEqual noder.Equal) (Changes, error) { - ret := NewChanges() - - ii, err := newDoubleIter(fromTree, toTree, hashEqual) - if err != nil { - return nil, err - } - - for { - select { - case <-ctx.Done(): - return nil, ErrCanceled - default: - } - - from := ii.from.current - to := ii.to.current - - switch r := ii.remaining(); r { - case noMoreNoders: - return ret, nil - case onlyFromRemains: - if err = ret.AddRecursiveDelete(from); err != nil { - return nil, err - } - if err = ii.nextFrom(); err != nil { - return nil, err - } - case onlyToRemains: - if to.Skip() { - if err = ret.AddRecursiveDelete(to); err != nil { - return nil, err - } - } else { - if err = ret.AddRecursiveInsert(to); err != nil { - return nil, err - } - } - if err = ii.nextTo(); err != nil { - return nil, err - } - case bothHaveNodes: - if from.Skip() { - if err = ret.AddRecursiveDelete(from); err != nil { - return nil, err - } - if err := ii.nextBoth(); err != nil { - return nil, err - } - break - } - if to.Skip() { - if err = ret.AddRecursiveDelete(to); err != nil { - return nil, err - } - if err := ii.nextBoth(); err != nil { - return nil, err - } - break - } - - if err = diffNodes(&ret, ii); err != nil { - return nil, err - } - default: - panic(fmt.Sprintf("unknown remaining value: %d", r)) - } - } -} - -func diffNodes(changes *Changes, ii *doubleIter) error { - from := ii.from.current - to := ii.to.current - var err error - - // compare their full paths as strings - switch from.Compare(to) { - case -1: - if err = changes.AddRecursiveDelete(from); err != nil { - return err - } - if err = ii.nextFrom(); err != nil { - return err - } - case 1: - if err = changes.AddRecursiveInsert(to); err != nil { - return err - } - if err = ii.nextTo(); err != nil { - return err - } - default: - if err := diffNodesSameName(changes, ii); err != nil { - return err - } - } - - return nil -} - -func diffNodesSameName(changes *Changes, ii *doubleIter) error { - from := ii.from.current - to := ii.to.current - - status, err := ii.compare() - if err != nil { - return err - } - - switch { - case status.sameHash: - // do nothing - if err = ii.nextBoth(); err != nil { - return err - } - case status.bothAreFiles: - changes.Add(NewModify(from, to)) - if err = ii.nextBoth(); err != nil { - return err - } - case status.fileAndDir: - if err = changes.AddRecursiveDelete(from); err != nil { - return err - } - if err = changes.AddRecursiveInsert(to); err != nil { - return err - } - if err = ii.nextBoth(); err != nil { - return err - } - case status.bothAreDirs: - if err = diffDirs(changes, ii); err != nil { - return err - } - default: - return fmt.Errorf("bad status from double iterator") - } - - return nil -} - -func diffDirs(changes *Changes, ii *doubleIter) error { - from := ii.from.current - to := ii.to.current - - status, err := ii.compare() - if err != nil { - return err - } - - switch { - case status.fromIsEmptyDir: - if err = changes.AddRecursiveInsert(to); err != nil { - return err - } - if err = ii.nextBoth(); err != nil { - return err - } - case status.toIsEmptyDir: - if err = changes.AddRecursiveDelete(from); err != nil { - return err - } - if err = ii.nextBoth(); err != nil { - return err - } - case !status.fromIsEmptyDir && !status.toIsEmptyDir: - // do nothing - if err = ii.stepBoth(); err != nil { - return err - } - default: - return fmt.Errorf("both dirs are empty but has different hash") - } - - return nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/doc.go b/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/doc.go deleted file mode 100644 index 5204024ad4f..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/doc.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -Package merkletrie provides support for n-ary trees that are at the same -time Merkle trees and Radix trees (tries). - -Git trees are Radix n-ary trees in virtue of the names of their -tree entries. At the same time, git trees are Merkle trees thanks to -their hashes. - -This package defines Merkle tries as nodes that should have: - -- a hash: the Merkle part of the Merkle trie - -- a key: the Radix part of the Merkle trie - -The Merkle hash condition is not enforced by this package though. This -means that the hash of a node doesn't have to take into account the hashes of -their children, which is good for testing purposes. - -Nodes in the Merkle trie are abstracted by the Noder interface. The -intended use is that git trees implements this interface, either -directly or using a simple wrapper. - -This package provides an iterator for merkletries that can skip whole -directory-like noders and an efficient merkletrie comparison algorithm. - -When comparing git trees, the simple approach of alphabetically sorting -their elements and comparing the resulting lists is too slow as it -depends linearly on the number of files in the trees: When a directory -has lots of files but none of them has been modified, this approach is -very expensive. We can do better by prunning whole directories that -have not change, just by looking at their hashes. This package provides -the tools to do exactly that. -*/ -package merkletrie diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/doubleiter.go b/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/doubleiter.go deleted file mode 100644 index 2a6e6843d71..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/doubleiter.go +++ /dev/null @@ -1,187 +0,0 @@ -package merkletrie - -import ( - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" -) - -// A doubleIter is a convenience type to keep track of the current -// noders in two merkletries that are going to be iterated in parallel. -// It has methods for: -// -// - iterating over the merkletries, both at the same time or -// individually: nextFrom, nextTo, nextBoth, stepBoth -// -// - checking if there are noders left in one or both of them with the -// remaining method and its associated returned type. -// -// - comparing the current noders of both merkletries in several ways, -// with the compare method and its associated returned type. -type doubleIter struct { - from struct { - iter *Iter - current noder.Path // nil if no more nodes - } - to struct { - iter *Iter - current noder.Path // nil if no more nodes - } - hashEqual noder.Equal -} - -// NewdoubleIter returns a new doubleIter for the merkletries "from" and -// "to". The hashEqual callback function will be used by the doubleIter -// to compare the hash of the noders in the merkletries. The doubleIter -// will be initialized to the first elements in each merkletrie if any. -func newDoubleIter(from, to noder.Noder, hashEqual noder.Equal) ( - *doubleIter, error) { - var ii doubleIter - var err error - - if ii.from.iter, err = NewIter(from); err != nil { - return nil, fmt.Errorf("from: %s", err) - } - if ii.from.current, err = ii.from.iter.Next(); turnEOFIntoNil(err) != nil { - return nil, fmt.Errorf("from: %s", err) - } - - if ii.to.iter, err = NewIter(to); err != nil { - return nil, fmt.Errorf("to: %s", err) - } - if ii.to.current, err = ii.to.iter.Next(); turnEOFIntoNil(err) != nil { - return nil, fmt.Errorf("to: %s", err) - } - - ii.hashEqual = hashEqual - - return &ii, nil -} - -func turnEOFIntoNil(e error) error { - if e != nil && e != io.EOF { - return e - } - return nil -} - -// NextBoth makes d advance to the next noder in both merkletries. If -// any of them is a directory, it skips its contents. -func (d *doubleIter) nextBoth() error { - if err := d.nextFrom(); err != nil { - return err - } - if err := d.nextTo(); err != nil { - return err - } - - return nil -} - -// NextFrom makes d advance to the next noder in the "from" merkletrie, -// skipping its contents if it is a directory. -func (d *doubleIter) nextFrom() (err error) { - d.from.current, err = d.from.iter.Next() - return turnEOFIntoNil(err) -} - -// NextTo makes d advance to the next noder in the "to" merkletrie, -// skipping its contents if it is a directory. -func (d *doubleIter) nextTo() (err error) { - d.to.current, err = d.to.iter.Next() - return turnEOFIntoNil(err) -} - -// StepBoth makes d advance to the next noder in both merkletries, -// getting deeper into directories if that is the case. -func (d *doubleIter) stepBoth() (err error) { - if d.from.current, err = d.from.iter.Step(); turnEOFIntoNil(err) != nil { - return err - } - if d.to.current, err = d.to.iter.Step(); turnEOFIntoNil(err) != nil { - return err - } - return nil -} - -// Remaining returns if there are no more noders in the tree, if both -// have noders or if one of them doesn't. -func (d *doubleIter) remaining() remaining { - if d.from.current == nil && d.to.current == nil { - return noMoreNoders - } - - if d.from.current == nil && d.to.current != nil { - return onlyToRemains - } - - if d.from.current != nil && d.to.current == nil { - return onlyFromRemains - } - - return bothHaveNodes -} - -// Remaining values tells you whether both trees still have noders, or -// only one of them or none of them. -type remaining int - -const ( - noMoreNoders remaining = iota - onlyToRemains - onlyFromRemains - bothHaveNodes -) - -// Compare returns the comparison between the current elements in the -// merkletries. -func (d *doubleIter) compare() (s comparison, err error) { - s.sameHash = d.hashEqual(d.from.current, d.to.current) - - fromIsDir := d.from.current.IsDir() - toIsDir := d.to.current.IsDir() - - s.bothAreDirs = fromIsDir && toIsDir - s.bothAreFiles = !fromIsDir && !toIsDir - s.fileAndDir = !s.bothAreDirs && !s.bothAreFiles - - fromNumChildren, err := d.from.current.NumChildren() - if err != nil { - return comparison{}, fmt.Errorf("from: %s", err) - } - - toNumChildren, err := d.to.current.NumChildren() - if err != nil { - return comparison{}, fmt.Errorf("to: %s", err) - } - - s.fromIsEmptyDir = fromIsDir && fromNumChildren == 0 - s.toIsEmptyDir = toIsDir && toNumChildren == 0 - - return -} - -// Answers to a lot of questions you can ask about how to noders are -// equal or different. -type comparison struct { - // the following are only valid if both nodes have the same name - // (i.e. nameComparison == 0) - - // Do both nodes have the same hash? - sameHash bool - // Are both nodes files? - bothAreFiles bool - - // the following are only valid if any of the noders are dirs, - // this is, if !bothAreFiles - - // Is one a file and the other a dir? - fileAndDir bool - // Are both nodes dirs? - bothAreDirs bool - // Is the from node an empty dir? - fromIsEmptyDir bool - // Is the to Node an empty dir? - toIsEmptyDir bool -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem/node.go b/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem/node.go deleted file mode 100644 index a96f1e8f290..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem/node.go +++ /dev/null @@ -1,205 +0,0 @@ -package filesystem - -import ( - "io" - "os" - "path" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" - - "github.com/go-git/go-billy/v5" -) - -var ignore = map[string]bool{ - ".git": true, -} - -// The node represents a file or a directory in a billy.Filesystem. It -// implements the interface noder.Noder of merkletrie package. -// -// This implementation implements a "standard" hash method being able to be -// compared with any other noder.Noder implementation inside of go-git. -type node struct { - fs billy.Filesystem - submodules map[string]plumbing.Hash - - path string - hash []byte - children []noder.Noder - isDir bool - mode os.FileMode - size int64 -} - -// NewRootNode returns the root node based on a given billy.Filesystem. -// -// In order to provide the submodule hash status, a map[string]plumbing.Hash -// should be provided where the key is the path of the submodule and the commit -// of the submodule HEAD -func NewRootNode( - fs billy.Filesystem, - submodules map[string]plumbing.Hash, -) noder.Noder { - return &node{fs: fs, submodules: submodules, isDir: true} -} - -// Hash the hash of a filesystem is the result of concatenating the computed -// plumbing.Hash of the file as a Blob and its plumbing.FileMode; that way the -// difftree algorithm will detect changes in the contents of files and also in -// their mode. -// -// Please note that the hash is calculated on first invocation of Hash(), -// meaning that it will not update when the underlying file changes -// between invocations. -// -// The hash of a directory is always a 24-bytes slice of zero values -func (n *node) Hash() []byte { - if n.hash == nil { - n.calculateHash() - } - return n.hash -} - -func (n *node) Name() string { - return path.Base(n.path) -} - -func (n *node) IsDir() bool { - return n.isDir -} - -func (n *node) Skip() bool { - return false -} - -func (n *node) Children() ([]noder.Noder, error) { - if err := n.calculateChildren(); err != nil { - return nil, err - } - - return n.children, nil -} - -func (n *node) NumChildren() (int, error) { - if err := n.calculateChildren(); err != nil { - return -1, err - } - - return len(n.children), nil -} - -func (n *node) calculateChildren() error { - if !n.IsDir() { - return nil - } - - if len(n.children) != 0 { - return nil - } - - files, err := n.fs.ReadDir(n.path) - if err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - - for _, file := range files { - if _, ok := ignore[file.Name()]; ok { - continue - } - - if file.Mode()&os.ModeSocket != 0 { - continue - } - - c, err := n.newChildNode(file) - if err != nil { - return err - } - - n.children = append(n.children, c) - } - - return nil -} - -func (n *node) newChildNode(file os.FileInfo) (*node, error) { - path := path.Join(n.path, file.Name()) - - node := &node{ - fs: n.fs, - submodules: n.submodules, - - path: path, - isDir: file.IsDir(), - size: file.Size(), - mode: file.Mode(), - } - - if _, isSubmodule := n.submodules[path]; isSubmodule { - node.isDir = false - } - - return node, nil -} - -func (n *node) calculateHash() { - if n.isDir { - n.hash = make([]byte, 24) - return - } - mode, err := filemode.NewFromOSFileMode(n.mode) - if err != nil { - n.hash = plumbing.ZeroHash[:] - return - } - if submoduleHash, isSubmodule := n.submodules[n.path]; isSubmodule { - n.hash = append(submoduleHash[:], filemode.Submodule.Bytes()...) - return - } - var hash plumbing.Hash - if n.mode&os.ModeSymlink != 0 { - hash = n.doCalculateHashForSymlink() - } else { - hash = n.doCalculateHashForRegular() - } - n.hash = append(hash[:], mode.Bytes()...) -} - -func (n *node) doCalculateHashForRegular() plumbing.Hash { - f, err := n.fs.Open(n.path) - if err != nil { - return plumbing.ZeroHash - } - - defer f.Close() - - h := plumbing.NewHasher(plumbing.BlobObject, n.size) - if _, err := io.Copy(h, f); err != nil { - return plumbing.ZeroHash - } - - return h.Sum() -} - -func (n *node) doCalculateHashForSymlink() plumbing.Hash { - target, err := n.fs.Readlink(n.path) - if err != nil { - return plumbing.ZeroHash - } - - h := plumbing.NewHasher(plumbing.BlobObject, n.size) - if _, err := h.Write([]byte(target)); err != nil { - return plumbing.ZeroHash - } - - return h.Sum() -} - -func (n *node) String() string { - return n.path -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/index/node.go b/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/index/node.go deleted file mode 100644 index 59cd17f8439..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/index/node.go +++ /dev/null @@ -1,95 +0,0 @@ -package index - -import ( - "path" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing/format/index" - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" -) - -// The node represents a index.Entry or a directory inferred from the path -// of all entries. It implements the interface noder.Noder of merkletrie -// package. -// -// This implementation implements a "standard" hash method being able to be -// compared with any other noder.Noder implementation inside of go-git -type node struct { - path string - entry *index.Entry - children []noder.Noder - isDir bool - skip bool -} - -// NewRootNode returns the root node of a computed tree from a index.Index, -func NewRootNode(idx *index.Index) noder.Noder { - const rootNode = "" - - m := map[string]*node{rootNode: {isDir: true}} - - for _, e := range idx.Entries { - parts := strings.Split(e.Name, string("/")) - - var fullpath string - for _, part := range parts { - parent := fullpath - fullpath = path.Join(fullpath, part) - - if _, ok := m[fullpath]; ok { - continue - } - - n := &node{path: fullpath, skip: e.SkipWorktree} - if fullpath == e.Name { - n.entry = e - } else { - n.isDir = true - } - - m[n.path] = n - m[parent].children = append(m[parent].children, n) - } - } - - return m[rootNode] -} - -func (n *node) String() string { - return n.path -} - -func (n *node) Skip() bool { - return n.skip -} - -// Hash the hash of a filesystem is a 24-byte slice, is the result of -// concatenating the computed plumbing.Hash of the file as a Blob and its -// plumbing.FileMode; that way the difftree algorithm will detect changes in the -// contents of files and also in their mode. -// -// If the node is computed and not based on a index.Entry the hash is equals -// to a 24-bytes slices of zero values. -func (n *node) Hash() []byte { - if n.entry == nil { - return make([]byte, 24) - } - - return append(n.entry.Hash[:], n.entry.Mode.Bytes()...) -} - -func (n *node) Name() string { - return path.Base(n.path) -} - -func (n *node) IsDir() bool { - return n.isDir -} - -func (n *node) Children() ([]noder.Noder, error) { - return n.children, nil -} - -func (n *node) NumChildren() (int, error) { - return len(n.children), nil -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame/frame.go b/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame/frame.go deleted file mode 100644 index b24f97a55e3..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame/frame.go +++ /dev/null @@ -1,91 +0,0 @@ -package frame - -import ( - "bytes" - "fmt" - "sort" - "strings" - - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" -) - -// A Frame is a collection of siblings in a trie, sorted alphabetically -// by name. -type Frame struct { - // siblings, sorted in reverse alphabetical order by name - stack []noder.Noder -} - -type byName []noder.Noder - -func (a byName) Len() int { return len(a) } -func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byName) Less(i, j int) bool { - return strings.Compare(a[i].Name(), a[j].Name()) < 0 -} - -// New returns a frame with the children of the provided node. -func New(n noder.Noder) (*Frame, error) { - children, err := n.Children() - if err != nil { - return nil, err - } - - sort.Sort(sort.Reverse(byName(children))) - return &Frame{ - stack: children, - }, nil -} - -// String returns the quoted names of the noders in the frame sorted in -// alphabetical order by name, surrounded by square brackets and -// separated by comas. -// -// Examples: -// [] -// ["a", "b"] -func (f *Frame) String() string { - var buf bytes.Buffer - _ = buf.WriteByte('[') - - sep := "" - for i := f.Len() - 1; i >= 0; i-- { - _, _ = buf.WriteString(sep) - sep = ", " - _, _ = buf.WriteString(fmt.Sprintf("%q", f.stack[i].Name())) - } - - _ = buf.WriteByte(']') - - return buf.String() -} - -// First returns, but dont extract, the noder with the alphabetically -// smaller name in the frame and true if the frame was not empty. -// Otherwise it returns nil and false. -func (f *Frame) First() (noder.Noder, bool) { - if f.Len() == 0 { - return nil, false - } - - top := f.Len() - 1 - - return f.stack[top], true -} - -// Drop extracts the noder with the alphabetically smaller name in the -// frame or does nothing if the frame was empty. -func (f *Frame) Drop() { - if f.Len() == 0 { - return - } - - top := f.Len() - 1 - f.stack[top] = nil - f.stack = f.stack[:top] -} - -// Len returns the number of noders in the frame. -func (f *Frame) Len() int { - return len(f.stack) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/iter.go b/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/iter.go deleted file mode 100644 index d8a4fbf39a7..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/iter.go +++ /dev/null @@ -1,216 +0,0 @@ -package merkletrie - -import ( - "fmt" - "io" - - "github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame" - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" -) - -// Iter is an iterator for merkletries (only the trie part of the -// merkletrie is relevant here, it does not use the Hasher interface). -// -// The iteration is performed in depth-first pre-order. Entries at each -// depth are traversed in (case-sensitive) alphabetical order. -// -// This is the kind of traversal you will expect when listing ordinary -// files and directories recursively, for example: -// -// Trie Traversal order -// ---- --------------- -// . -// / | \ c -// / | \ d/ -// d c z ===> d/a -// / \ d/b -// b a z -// -// -// This iterator is somewhat especial as you can chose to skip whole -// "directories" when iterating: -// -// - The Step method will iterate normally. -// -// - the Next method will not descend deeper into the tree. -// -// For example, if the iterator is at `d/`, the Step method will return -// `d/a` while the Next would have returned `z` instead (skipping `d/` -// and its descendants). The name of the these two methods are based on -// the well known "next" and "step" operations, quite common in -// debuggers, like gdb. -// -// The paths returned by the iterator will be relative, if the iterator -// was created from a single node, or absolute, if the iterator was -// created from the path to the node (the path will be prefixed to all -// returned paths). -type Iter struct { - // Tells if the iteration has started. - hasStarted bool - // The top of this stack has the current node and its siblings. The - // rest of the stack keeps the ancestors of the current node and - // their corresponding siblings. The current element is always the - // top element of the top frame. - // - // When "step"ping into a node, its children are pushed as a new - // frame. - // - // When "next"ing pass a node, the current element is dropped by - // popping the top frame. - frameStack []*frame.Frame - // The base path used to turn the relative paths used internally by - // the iterator into absolute paths used by external applications. - // For relative iterator this will be nil. - base noder.Path -} - -// NewIter returns a new relative iterator using the provider noder as -// its unnamed root. When iterating, all returned paths will be -// relative to node. -func NewIter(n noder.Noder) (*Iter, error) { - return newIter(n, nil) -} - -// NewIterFromPath returns a new absolute iterator from the noder at the -// end of the path p. When iterating, all returned paths will be -// absolute, using the root of the path p as their root. -func NewIterFromPath(p noder.Path) (*Iter, error) { - return newIter(p, p) // Path implements Noder -} - -func newIter(root noder.Noder, base noder.Path) (*Iter, error) { - ret := &Iter{ - base: base, - } - - if root == nil { - return ret, nil - } - - frame, err := frame.New(root) - if err != nil { - return nil, err - } - ret.push(frame) - - return ret, nil -} - -func (iter *Iter) top() (*frame.Frame, bool) { - if len(iter.frameStack) == 0 { - return nil, false - } - top := len(iter.frameStack) - 1 - - return iter.frameStack[top], true -} - -func (iter *Iter) push(f *frame.Frame) { - iter.frameStack = append(iter.frameStack, f) -} - -const ( - doDescend = true - dontDescend = false -) - -// Next returns the path of the next node without descending deeper into -// the trie and nil. If there are no more entries in the trie it -// returns nil and io.EOF. In case of error, it will return nil and the -// error. -func (iter *Iter) Next() (noder.Path, error) { - return iter.advance(dontDescend) -} - -// Step returns the path to the next node in the trie, descending deeper -// into it if needed, and nil. If there are no more nodes in the trie, -// it returns nil and io.EOF. In case of error, it will return nil and -// the error. -func (iter *Iter) Step() (noder.Path, error) { - return iter.advance(doDescend) -} - -// Advances the iterator in the desired direction: descend or -// dontDescend. -// -// Returns the new current element and a nil error on success. If there -// are no more elements in the trie below the base, it returns nil, and -// io.EOF. Returns nil and an error in case of errors. -func (iter *Iter) advance(wantDescend bool) (noder.Path, error) { - current, err := iter.current() - if err != nil { - return nil, err - } - - // The first time we just return the current node. - if !iter.hasStarted { - iter.hasStarted = true - return current, nil - } - - // Advances means getting a next current node, either its first child or - // its next sibling, depending if we must descend or not. - numChildren, err := current.NumChildren() - if err != nil { - return nil, err - } - - mustDescend := numChildren != 0 && wantDescend - if mustDescend { - // descend: add a new frame with the current's children. - frame, err := frame.New(current) - if err != nil { - return nil, err - } - iter.push(frame) - } else { - // don't descend: just drop the current node - iter.drop() - } - - return iter.current() -} - -// Returns the path to the current node, adding the base if there was -// one, and a nil error. If there were no noders left, it returns nil -// and io.EOF. If an error occurred, it returns nil and the error. -func (iter *Iter) current() (noder.Path, error) { - if topFrame, ok := iter.top(); !ok { - return nil, io.EOF - } else if _, ok := topFrame.First(); !ok { - return nil, io.EOF - } - - ret := make(noder.Path, 0, len(iter.base)+len(iter.frameStack)) - - // concat the base... - ret = append(ret, iter.base...) - // ... and the current node and all its ancestors - for i, f := range iter.frameStack { - t, ok := f.First() - if !ok { - panic(fmt.Sprintf("frame %d is empty", i)) - } - ret = append(ret, t) - } - - return ret, nil -} - -// removes the current node if any, and all the frames that become empty as a -// consequence of this action. -func (iter *Iter) drop() { - frame, ok := iter.top() - if !ok { - return - } - - frame.Drop() - // if the frame is empty, remove it and its parent, recursively - if frame.Len() == 0 { - top := len(iter.frameStack) - 1 - iter.frameStack[top] = nil - iter.frameStack = iter.frameStack[:top] - iter.drop() - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/noder/noder.go b/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/noder/noder.go deleted file mode 100644 index 6d22b8c14ec..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/noder/noder.go +++ /dev/null @@ -1,60 +0,0 @@ -// Package noder provide an interface for defining nodes in a -// merkletrie, their hashes and their paths (a noders and its -// ancestors). -// -// The hasher interface is easy to implement naively by elements that -// already have a hash, like git blobs and trees. More sophisticated -// implementations can implement the Equal function in exotic ways -// though: for instance, comparing the modification time of directories -// in a filesystem. -package noder - -import "fmt" - -// Hasher interface is implemented by types that can tell you -// their hash. -type Hasher interface { - Hash() []byte -} - -// Equal functions take two hashers and return if they are equal. -// -// These functions are expected to be faster than reflect.Equal or -// reflect.DeepEqual because they can compare just the hash of the -// objects, instead of their contents, so they are expected to be O(1). -type Equal func(a, b Hasher) bool - -// The Noder interface is implemented by the elements of a Merkle Trie. -// -// There are two types of elements in a Merkle Trie: -// -// - file-like nodes: they cannot have children. -// -// - directory-like nodes: they can have 0 or more children and their -// hash is calculated by combining their children hashes. -type Noder interface { - Hasher - fmt.Stringer // for testing purposes - // Name returns the name of an element (relative, not its full - // path). - Name() string - // IsDir returns true if the element is a directory-like node or - // false if it is a file-like node. - IsDir() bool - // Children returns the children of the element. Note that empty - // directory-like noders and file-like noders will both return - // NoChildren. - Children() ([]Noder, error) - // NumChildren returns the number of children this element has. - // - // This method is an optimization: the number of children is easily - // calculated as the length of the value returned by the Children - // method (above); yet, some implementations will be able to - // implement NumChildren in O(1) while Children is usually more - // complex. - NumChildren() (int, error) - Skip() bool -} - -// NoChildren represents the children of a noder without children. -var NoChildren = []Noder{} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/noder/path.go b/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/noder/path.go deleted file mode 100644 index 6c1d363320b..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/merkletrie/noder/path.go +++ /dev/null @@ -1,98 +0,0 @@ -package noder - -import ( - "bytes" - "strings" -) - -// Path values represent a noder and its ancestors. The root goes first -// and the actual final noder the path is referring to will be the last. -// -// A path implements the Noder interface, redirecting all the interface -// calls to its final noder. -// -// Paths build from an empty Noder slice are not valid paths and should -// not be used. -type Path []Noder - -func (p Path) Skip() bool { - if len(p) > 0 { - return p.Last().Skip() - } - - return false -} - -// String returns the full path of the final noder as a string, using -// "/" as the separator. -func (p Path) String() string { - var buf bytes.Buffer - sep := "" - for _, e := range p { - _, _ = buf.WriteString(sep) - sep = "/" - _, _ = buf.WriteString(e.Name()) - } - - return buf.String() -} - -// Last returns the final noder in the path. -func (p Path) Last() Noder { - return p[len(p)-1] -} - -// Hash returns the hash of the final noder of the path. -func (p Path) Hash() []byte { - return p.Last().Hash() -} - -// Name returns the name of the final noder of the path. -func (p Path) Name() string { - return p.Last().Name() -} - -// IsDir returns if the final noder of the path is a directory-like -// noder. -func (p Path) IsDir() bool { - return p.Last().IsDir() -} - -// Children returns the children of the final noder in the path. -func (p Path) Children() ([]Noder, error) { - return p.Last().Children() -} - -// NumChildren returns the number of children the final noder of the -// path has. -func (p Path) NumChildren() (int, error) { - return p.Last().NumChildren() -} - -// Compare returns -1, 0 or 1 if the path p is smaller, equal or bigger -// than other, in "directory order"; for example: -// -// "a" < "b" -// "a/b/c/d/z" < "b" -// "a/b/a" > "a/b" -func (p Path) Compare(other Path) int { - i := 0 - for { - switch { - case len(other) == len(p) && i == len(p): - return 0 - case i == len(other): - return 1 - case i == len(p): - return -1 - default: - // We do *not* normalize Unicode here. CGit doesn't. - // https://github.com/src-d/go-git/issues/1057 - cmp := strings.Compare(p[i].Name(), other[i].Name()) - if cmp != 0 { - return cmp - } - } - i++ - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/sync/bufio.go b/vendor/github.com/jesseduffield/go-git/v5/utils/sync/bufio.go deleted file mode 100644 index 42f60f7ea16..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/sync/bufio.go +++ /dev/null @@ -1,29 +0,0 @@ -package sync - -import ( - "bufio" - "io" - "sync" -) - -var bufioReader = sync.Pool{ - New: func() interface{} { - return bufio.NewReader(nil) - }, -} - -// GetBufioReader returns a *bufio.Reader that is managed by a sync.Pool. -// Returns a bufio.Reader that is reset with reader and ready for use. -// -// After use, the *bufio.Reader should be put back into the sync.Pool -// by calling PutBufioReader. -func GetBufioReader(reader io.Reader) *bufio.Reader { - r := bufioReader.Get().(*bufio.Reader) - r.Reset(reader) - return r -} - -// PutBufioReader puts reader back into its sync.Pool. -func PutBufioReader(reader *bufio.Reader) { - bufioReader.Put(reader) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/sync/bytes.go b/vendor/github.com/jesseduffield/go-git/v5/utils/sync/bytes.go deleted file mode 100644 index c67b9783754..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/sync/bytes.go +++ /dev/null @@ -1,51 +0,0 @@ -package sync - -import ( - "bytes" - "sync" -) - -var ( - byteSlice = sync.Pool{ - New: func() interface{} { - b := make([]byte, 16*1024) - return &b - }, - } - bytesBuffer = sync.Pool{ - New: func() interface{} { - return bytes.NewBuffer(nil) - }, - } -) - -// GetByteSlice returns a *[]byte that is managed by a sync.Pool. -// The initial slice length will be 16384 (16kb). -// -// After use, the *[]byte should be put back into the sync.Pool -// by calling PutByteSlice. -func GetByteSlice() *[]byte { - buf := byteSlice.Get().(*[]byte) - return buf -} - -// PutByteSlice puts buf back into its sync.Pool. -func PutByteSlice(buf *[]byte) { - byteSlice.Put(buf) -} - -// GetBytesBuffer returns a *bytes.Buffer that is managed by a sync.Pool. -// Returns a buffer that is reset and ready for use. -// -// After use, the *bytes.Buffer should be put back into the sync.Pool -// by calling PutBytesBuffer. -func GetBytesBuffer() *bytes.Buffer { - buf := bytesBuffer.Get().(*bytes.Buffer) - buf.Reset() - return buf -} - -// PutBytesBuffer puts buf back into its sync.Pool. -func PutBytesBuffer(buf *bytes.Buffer) { - bytesBuffer.Put(buf) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/sync/zlib.go b/vendor/github.com/jesseduffield/go-git/v5/utils/sync/zlib.go deleted file mode 100644 index edf674d8521..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/sync/zlib.go +++ /dev/null @@ -1,74 +0,0 @@ -package sync - -import ( - "bytes" - "compress/zlib" - "io" - "sync" -) - -var ( - zlibInitBytes = []byte{0x78, 0x9c, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01} - zlibReader = sync.Pool{ - New: func() interface{} { - r, _ := zlib.NewReader(bytes.NewReader(zlibInitBytes)) - return ZLibReader{ - Reader: r.(zlibReadCloser), - } - }, - } - zlibWriter = sync.Pool{ - New: func() interface{} { - return zlib.NewWriter(nil) - }, - } -) - -type zlibReadCloser interface { - io.ReadCloser - zlib.Resetter -} - -type ZLibReader struct { - dict *[]byte - Reader zlibReadCloser -} - -// GetZlibReader returns a ZLibReader that is managed by a sync.Pool. -// Returns a ZLibReader that is reset using a dictionary that is -// also managed by a sync.Pool. -// -// After use, the ZLibReader should be put back into the sync.Pool -// by calling PutZlibReader. -func GetZlibReader(r io.Reader) (ZLibReader, error) { - z := zlibReader.Get().(ZLibReader) - z.dict = GetByteSlice() - - err := z.Reader.Reset(r, *z.dict) - - return z, err -} - -// PutZlibReader puts z back into its sync.Pool, first closing the reader. -// The Byte slice dictionary is also put back into its sync.Pool. -func PutZlibReader(z ZLibReader) { - z.Reader.Close() - PutByteSlice(z.dict) - zlibReader.Put(z) -} - -// GetZlibWriter returns a *zlib.Writer that is managed by a sync.Pool. -// Returns a writer that is reset with w and ready for use. -// -// After use, the *zlib.Writer should be put back into the sync.Pool -// by calling PutZlibWriter. -func GetZlibWriter(w io.Writer) *zlib.Writer { - z := zlibWriter.Get().(*zlib.Writer) - z.Reset(w) - return z -} - -// PutZlibWriter puts w back into its sync.Pool. -func PutZlibWriter(w *zlib.Writer) { - zlibWriter.Put(w) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/utils/trace/trace.go b/vendor/github.com/jesseduffield/go-git/v5/utils/trace/trace.go deleted file mode 100644 index 3e15c5b9f90..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/utils/trace/trace.go +++ /dev/null @@ -1,55 +0,0 @@ -package trace - -import ( - "fmt" - "log" - "os" - "sync/atomic" -) - -var ( - // logger is the logger to use for tracing. - logger = newLogger() - - // current is the targets that are enabled for tracing. - current atomic.Int32 -) - -func newLogger() *log.Logger { - return log.New(os.Stderr, "", log.Ltime|log.Lmicroseconds|log.Lshortfile) -} - -// Target is a tracing target. -type Target int32 - -const ( - // General traces general operations. - General Target = 1 << iota - - // Packet traces git packets. - Packet -) - -// SetTarget sets the tracing targets. -func SetTarget(target Target) { - current.Store(int32(target)) -} - -// SetLogger sets the logger to use for tracing. -func SetLogger(l *log.Logger) { - logger = l -} - -// Print prints the given message only if the target is enabled. -func (t Target) Print(args ...interface{}) { - if int32(t)¤t.Load() != 0 { - logger.Output(2, fmt.Sprint(args...)) // nolint: errcheck - } -} - -// Printf prints the given message only if the target is enabled. -func (t Target) Printf(format string, args ...interface{}) { - if int32(t)¤t.Load() != 0 { - logger.Output(2, fmt.Sprintf(format, args...)) // nolint: errcheck - } -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/worktree.go b/vendor/github.com/jesseduffield/go-git/v5/worktree.go deleted file mode 100644 index 304e90c9896..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/worktree.go +++ /dev/null @@ -1,1196 +0,0 @@ -package git - -import ( - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/util" - "github.com/jesseduffield/go-git/v5/config" - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" - "github.com/jesseduffield/go-git/v5/plumbing/format/gitignore" - "github.com/jesseduffield/go-git/v5/plumbing/format/index" - "github.com/jesseduffield/go-git/v5/plumbing/object" - "github.com/jesseduffield/go-git/v5/plumbing/storer" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/jesseduffield/go-git/v5/utils/merkletrie" - "github.com/jesseduffield/go-git/v5/utils/sync" -) - -var ( - ErrWorktreeNotClean = errors.New("worktree is not clean") - ErrSubmoduleNotFound = errors.New("submodule not found") - ErrUnstagedChanges = errors.New("worktree contains unstaged changes") - ErrGitModulesSymlink = errors.New(gitmodulesFile + " is a symlink") - ErrNonFastForwardUpdate = errors.New("non-fast-forward update") - ErrRestoreWorktreeOnlyNotSupported = errors.New("worktree only is not supported") -) - -// Worktree represents a git worktree. -type Worktree struct { - // Filesystem underlying filesystem. - Filesystem billy.Filesystem - // External excludes not found in the repository .gitignore - Excludes []gitignore.Pattern - - r *Repository -} - -// Pull incorporates changes from a remote repository into the current branch. -// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are -// no changes to be fetched, or an error. -// -// Pull only supports merges where the can be resolved as a fast-forward. -func (w *Worktree) Pull(o *PullOptions) error { - return w.PullContext(context.Background(), o) -} - -// PullContext incorporates changes from a remote repository into the current -// branch. Returns nil if the operation is successful, NoErrAlreadyUpToDate if -// there are no changes to be fetched, or an error. -// -// Pull only supports merges where the can be resolved as a fast-forward. -// -// The provided Context must be non-nil. If the context expires before the -// operation is complete, an error is returned. The context only affects the -// transport operations. -func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) error { - if err := o.Validate(); err != nil { - return err - } - - remote, err := w.r.Remote(o.RemoteName) - if err != nil { - return err - } - - fetchHead, err := remote.fetch(ctx, &FetchOptions{ - RemoteName: o.RemoteName, - RemoteURL: o.RemoteURL, - Depth: o.Depth, - Auth: o.Auth, - Progress: o.Progress, - Force: o.Force, - InsecureSkipTLS: o.InsecureSkipTLS, - CABundle: o.CABundle, - ProxyOptions: o.ProxyOptions, - }) - - updated := true - if err == NoErrAlreadyUpToDate { - updated = false - } else if err != nil { - return err - } - - ref, err := storer.ResolveReference(fetchHead, o.ReferenceName) - if err != nil { - return err - } - - head, err := w.r.Head() - if err == nil { - // if we don't have a shallows list, just ignore it - shallowList, _ := w.r.Storer.Shallow() - - var earliestShallow *plumbing.Hash - if len(shallowList) > 0 { - earliestShallow = &shallowList[0] - } - - headAheadOfRef, err := isFastForward(w.r.Storer, ref.Hash(), head.Hash(), earliestShallow) - if err != nil { - return err - } - - if !updated && headAheadOfRef { - return NoErrAlreadyUpToDate - } - - ff, err := isFastForward(w.r.Storer, head.Hash(), ref.Hash(), earliestShallow) - if err != nil { - return err - } - - if !ff { - return ErrNonFastForwardUpdate - } - } - - if err != nil && err != plumbing.ErrReferenceNotFound { - return err - } - - if err := w.updateHEAD(ref.Hash()); err != nil { - return err - } - - if err := w.Reset(&ResetOptions{ - Mode: MergeReset, - Commit: ref.Hash(), - }); err != nil { - return err - } - - if o.RecurseSubmodules != NoRecurseSubmodules { - return w.updateSubmodules(ctx, &SubmoduleUpdateOptions{ - RecurseSubmodules: o.RecurseSubmodules, - Auth: o.Auth, - }) - } - - return nil -} - -func (w *Worktree) updateSubmodules(ctx context.Context, o *SubmoduleUpdateOptions) error { - s, err := w.Submodules() - if err != nil { - return err - } - o.Init = true - return s.UpdateContext(ctx, o) -} - -// Checkout switch branches or restore working tree files. -func (w *Worktree) Checkout(opts *CheckoutOptions) error { - if err := opts.Validate(); err != nil { - return err - } - - if opts.Create { - if err := w.createBranch(opts); err != nil { - return err - } - } - - c, err := w.getCommitFromCheckoutOptions(opts) - if err != nil { - return err - } - - ro := &ResetOptions{Commit: c, Mode: MergeReset} - if opts.Force { - ro.Mode = HardReset - } else if opts.Keep { - ro.Mode = SoftReset - } - - if !opts.Hash.IsZero() && !opts.Create { - err = w.setHEADToCommit(opts.Hash) - } else { - err = w.setHEADToBranch(opts.Branch, c) - } - - if err != nil { - return err - } - - if len(opts.SparseCheckoutDirectories) > 0 { - return w.ResetSparsely(ro, opts.SparseCheckoutDirectories) - } - - return w.Reset(ro) -} - -func (w *Worktree) createBranch(opts *CheckoutOptions) error { - if err := opts.Branch.Validate(); err != nil { - return err - } - - _, err := w.r.Storer.Reference(opts.Branch) - if err == nil { - return fmt.Errorf("a branch named %q already exists", opts.Branch) - } - - if err != plumbing.ErrReferenceNotFound { - return err - } - - if opts.Hash.IsZero() { - ref, err := w.r.Head() - if err != nil { - return err - } - - opts.Hash = ref.Hash() - } - - return w.r.Storer.SetReference( - plumbing.NewHashReference(opts.Branch, opts.Hash), - ) -} - -func (w *Worktree) getCommitFromCheckoutOptions(opts *CheckoutOptions) (plumbing.Hash, error) { - hash := opts.Hash - if hash.IsZero() { - b, err := w.r.Reference(opts.Branch, true) - if err != nil { - return plumbing.ZeroHash, err - } - - hash = b.Hash() - } - - o, err := w.r.Object(plumbing.AnyObject, hash) - if err != nil { - return plumbing.ZeroHash, err - } - - switch o := o.(type) { - case *object.Tag: - if o.TargetType != plumbing.CommitObject { - return plumbing.ZeroHash, fmt.Errorf("%w: tag target %q", object.ErrUnsupportedObject, o.TargetType) - } - - return o.Target, nil - case *object.Commit: - return o.Hash, nil - } - - return plumbing.ZeroHash, fmt.Errorf("%w: %q", object.ErrUnsupportedObject, o.Type()) -} - -func (w *Worktree) setHEADToCommit(commit plumbing.Hash) error { - head := plumbing.NewHashReference(plumbing.HEAD, commit) - return w.r.Storer.SetReference(head) -} - -func (w *Worktree) setHEADToBranch(branch plumbing.ReferenceName, commit plumbing.Hash) error { - target, err := w.r.Storer.Reference(branch) - if err != nil { - return err - } - - var head *plumbing.Reference - if target.Name().IsBranch() { - head = plumbing.NewSymbolicReference(plumbing.HEAD, target.Name()) - } else { - head = plumbing.NewHashReference(plumbing.HEAD, commit) - } - - return w.r.Storer.SetReference(head) -} - -func (w *Worktree) ResetSparsely(opts *ResetOptions, dirs []string) error { - if err := opts.Validate(w.r); err != nil { - return err - } - - if opts.Mode == MergeReset { - unstaged, err := w.containsUnstagedChanges() - if err != nil { - return err - } - - if unstaged { - return ErrUnstagedChanges - } - } - - if err := w.setHEADCommit(opts.Commit); err != nil { - return err - } - - if opts.Mode == SoftReset { - return nil - } - - t, err := w.r.getTreeFromCommitHash(opts.Commit) - if err != nil { - return err - } - - if opts.Mode == MixedReset || opts.Mode == MergeReset || opts.Mode == HardReset { - if err := w.resetIndex(t, dirs, opts.Files); err != nil { - return err - } - } - - if opts.Mode == MergeReset || opts.Mode == HardReset { - if err := w.resetWorktree(t, opts.Files); err != nil { - return err - } - } - - return nil -} - -// Restore restores specified files in the working tree or stage with contents from -// a restore source. If a path is tracked but does not exist in the restore, -// source, it will be removed to match the source. -// -// If Staged and Worktree are true, then the restore source will be the index. -// If only Staged is true, then the restore source will be HEAD. -// If only Worktree is true or neither Staged nor Worktree are true, will -// result in ErrRestoreWorktreeOnlyNotSupported because restoring the working -// tree while leaving the stage untouched is not currently supported. -// -// Restore with no files specified will return ErrNoRestorePaths. -func (w *Worktree) Restore(o *RestoreOptions) error { - if err := o.Validate(); err != nil { - return err - } - - if o.Staged { - opts := &ResetOptions{ - Files: o.Files, - } - - if o.Worktree { - // If we are doing both Worktree and Staging then it is a hard reset - opts.Mode = HardReset - } else { - // If we are doing just staging then it is a mixed reset - opts.Mode = MixedReset - } - - return w.Reset(opts) - } - - return ErrRestoreWorktreeOnlyNotSupported -} - -// Reset the worktree to a specified state. -func (w *Worktree) Reset(opts *ResetOptions) error { - return w.ResetSparsely(opts, nil) -} - -func (w *Worktree) resetIndex(t *object.Tree, dirs []string, files []string) error { - idx, err := w.r.Storer.Index() - if err != nil { - return err - } - - b := newIndexBuilder(idx) - - changes, err := w.diffTreeWithStaging(t, true) - if err != nil { - return err - } - - for _, ch := range changes { - a, err := ch.Action() - if err != nil { - return err - } - - var name string - var e *object.TreeEntry - - switch a { - case merkletrie.Modify, merkletrie.Insert: - name = ch.To.String() - e, err = t.FindEntry(name) - if err != nil { - return err - } - case merkletrie.Delete: - name = ch.From.String() - } - - if len(files) > 0 { - contains := inFiles(files, name) - if !contains { - continue - } - } - - b.Remove(name) - if e == nil { - continue - } - - b.Add(&index.Entry{ - Name: name, - Hash: e.Hash, - Mode: e.Mode, - }) - - } - - b.Write(idx) - - if len(dirs) > 0 { - idx.SkipUnless(dirs) - } - - return w.r.Storer.SetIndex(idx) -} - -func inFiles(files []string, v string) bool { - v = filepath.Clean(v) - for _, s := range files { - if filepath.Clean(s) == v { - return true - } - } - - return false -} - -func (w *Worktree) resetWorktree(t *object.Tree, files []string) error { - changes, err := w.diffStagingWithWorktree(true, false) - if err != nil { - return err - } - - idx, err := w.r.Storer.Index() - if err != nil { - return err - } - b := newIndexBuilder(idx) - - for _, ch := range changes { - if err := w.validChange(ch); err != nil { - return err - } - - if len(files) > 0 { - file := "" - if ch.From != nil { - file = ch.From.String() - } else if ch.To != nil { - file = ch.To.String() - } - - if file == "" { - continue - } - - contains := inFiles(files, file) - if !contains { - continue - } - } - - if err := w.checkoutChange(ch, t, b); err != nil { - return err - } - } - - b.Write(idx) - return w.r.Storer.SetIndex(idx) -} - -// worktreeDeny is a list of paths that are not allowed -// to be used when resetting the worktree. -var worktreeDeny = map[string]struct{}{ - // .git - GitDirName: {}, - - // For other historical reasons, file names that do not conform to the 8.3 - // format (up to eight characters for the basename, three for the file - // extension, certain characters not allowed such as `+`, etc) are associated - // with a so-called "short name", at least on the `C:` drive by default. - // Which means that `git~1/` is a valid way to refer to `.git/`. - "git~1": {}, -} - -// validPath checks whether paths are valid. -// The rules around invalid paths could differ from upstream based on how -// filesystems are managed within go-git, but they are largely the same. -// -// For upstream rules: -// https://github.com/git/git/blob/564d0252ca632e0264ed670534a51d18a689ef5d/read-cache.c#L946 -// https://github.com/git/git/blob/564d0252ca632e0264ed670534a51d18a689ef5d/path.c#L1383 -func validPath(paths ...string) error { - for _, p := range paths { - parts := strings.FieldsFunc(p, func(r rune) bool { return (r == '\\' || r == '/') }) - if len(parts) == 0 { - return fmt.Errorf("invalid path: %q", p) - } - - if _, denied := worktreeDeny[strings.ToLower(parts[0])]; denied { - return fmt.Errorf("invalid path prefix: %q", p) - } - - if runtime.GOOS == "windows" { - // Volume names are not supported, in both formats: \\ and :. - if vol := filepath.VolumeName(p); vol != "" { - return fmt.Errorf("invalid path: %q", p) - } - - if !windowsValidPath(parts[0]) { - return fmt.Errorf("invalid path: %q", p) - } - } - - for _, part := range parts { - if part == ".." { - return fmt.Errorf("invalid path %q: cannot use '..'", p) - } - } - } - return nil -} - -// windowsPathReplacer defines the chars that need to be replaced -// as part of windowsValidPath. -var windowsPathReplacer *strings.Replacer - -func init() { - windowsPathReplacer = strings.NewReplacer(" ", "", ".", "") -} - -func windowsValidPath(part string) bool { - if len(part) > 3 && strings.EqualFold(part[:4], GitDirName) { - // For historical reasons, file names that end in spaces or periods are - // automatically trimmed. Therefore, `.git . . ./` is a valid way to refer - // to `.git/`. - if windowsPathReplacer.Replace(part[4:]) == "" { - return false - } - - // For yet other historical reasons, NTFS supports so-called "Alternate Data - // Streams", i.e. metadata associated with a given file, referred to via - // `::`. There exists a default stream - // type for directories, allowing `.git/` to be accessed via - // `.git::$INDEX_ALLOCATION/`. - // - // For performance reasons, _all_ Alternate Data Streams of `.git/` are - // forbidden, not just `::$INDEX_ALLOCATION`. - if len(part) > 4 && part[4:5] == ":" { - return false - } - } - return true -} - -func (w *Worktree) validChange(ch merkletrie.Change) error { - action, err := ch.Action() - if err != nil { - return nil - } - - switch action { - case merkletrie.Delete: - return validPath(ch.From.String()) - case merkletrie.Insert: - return validPath(ch.To.String()) - case merkletrie.Modify: - return validPath(ch.From.String(), ch.To.String()) - } - - return nil -} - -func (w *Worktree) checkoutChange(ch merkletrie.Change, t *object.Tree, idx *indexBuilder) error { - a, err := ch.Action() - if err != nil { - return err - } - - var e *object.TreeEntry - var name string - var isSubmodule bool - - switch a { - case merkletrie.Modify, merkletrie.Insert: - name = ch.To.String() - e, err = t.FindEntry(name) - if err != nil { - return err - } - - isSubmodule = e.Mode == filemode.Submodule - case merkletrie.Delete: - return rmFileAndDirsIfEmpty(w.Filesystem, ch.From.String()) - } - - if isSubmodule { - return w.checkoutChangeSubmodule(name, a, e, idx) - } - - return w.checkoutChangeRegularFile(name, a, t, e, idx) -} - -func (w *Worktree) containsUnstagedChanges() (bool, error) { - ch, err := w.diffStagingWithWorktree(false, true) - if err != nil { - return false, err - } - - for _, c := range ch { - a, err := c.Action() - if err != nil { - return false, err - } - - if a == merkletrie.Insert { - continue - } - - return true, nil - } - - return false, nil -} - -func (w *Worktree) setHEADCommit(commit plumbing.Hash) error { - head, err := w.r.Reference(plumbing.HEAD, false) - if err != nil { - return err - } - - if head.Type() == plumbing.HashReference { - head = plumbing.NewHashReference(plumbing.HEAD, commit) - return w.r.Storer.SetReference(head) - } - - branch, err := w.r.Reference(head.Target(), false) - if err != nil { - return err - } - - if !branch.Name().IsBranch() { - return fmt.Errorf("invalid HEAD target should be a branch, found %s", branch.Type()) - } - - branch = plumbing.NewHashReference(branch.Name(), commit) - return w.r.Storer.SetReference(branch) -} - -func (w *Worktree) checkoutChangeSubmodule(name string, - a merkletrie.Action, - e *object.TreeEntry, - idx *indexBuilder, -) error { - switch a { - case merkletrie.Modify: - sub, err := w.Submodule(name) - if err != nil { - return err - } - - if !sub.initialized { - return nil - } - - return w.addIndexFromTreeEntry(name, e, idx) - case merkletrie.Insert: - mode, err := e.Mode.ToOSFileMode() - if err != nil { - return err - } - - if err := w.Filesystem.MkdirAll(name, mode); err != nil { - return err - } - - return w.addIndexFromTreeEntry(name, e, idx) - } - - return nil -} - -func (w *Worktree) checkoutChangeRegularFile(name string, - a merkletrie.Action, - t *object.Tree, - e *object.TreeEntry, - idx *indexBuilder, -) error { - switch a { - case merkletrie.Modify: - idx.Remove(name) - - // to apply perm changes the file is deleted, billy doesn't implement - // chmod - if err := w.Filesystem.Remove(name); err != nil { - return err - } - - fallthrough - case merkletrie.Insert: - f, err := t.File(name) - if err != nil { - return err - } - - if err := w.checkoutFile(f); err != nil { - return err - } - - return w.addIndexFromFile(name, e.Hash, f.Mode, idx) - } - - return nil -} - -func (w *Worktree) checkoutFile(f *object.File) (err error) { - mode, err := f.Mode.ToOSFileMode() - if err != nil { - return - } - - if mode&os.ModeSymlink != 0 { - return w.checkoutFileSymlink(f) - } - - from, err := f.Reader() - if err != nil { - return - } - - defer ioutil.CheckClose(from, &err) - - to, err := w.Filesystem.OpenFile(f.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode.Perm()) - if err != nil { - return - } - - defer ioutil.CheckClose(to, &err) - buf := sync.GetByteSlice() - _, err = io.CopyBuffer(to, from, *buf) - sync.PutByteSlice(buf) - return -} - -func (w *Worktree) checkoutFileSymlink(f *object.File) (err error) { - // https://github.com/git/git/commit/10ecfa76491e4923988337b2e2243b05376b40de - if strings.EqualFold(f.Name, gitmodulesFile) { - return ErrGitModulesSymlink - } - - from, err := f.Reader() - if err != nil { - return - } - - defer ioutil.CheckClose(from, &err) - - bytes, err := io.ReadAll(from) - if err != nil { - return - } - - err = w.Filesystem.Symlink(string(bytes), f.Name) - - // On windows, this might fail. - // Follow Git on Windows behavior by writing the link as it is. - if err != nil && isSymlinkWindowsNonAdmin(err) { - mode, _ := f.Mode.ToOSFileMode() - - to, err := w.Filesystem.OpenFile(f.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode.Perm()) - if err != nil { - return err - } - - defer ioutil.CheckClose(to, &err) - - _, err = to.Write(bytes) - return err - } - return -} - -func (w *Worktree) addIndexFromTreeEntry(name string, f *object.TreeEntry, idx *indexBuilder) error { - idx.Remove(name) - idx.Add(&index.Entry{ - Hash: f.Hash, - Name: name, - Mode: filemode.Submodule, - }) - return nil -} - -func (w *Worktree) addIndexFromFile(name string, h plumbing.Hash, mode filemode.FileMode, idx *indexBuilder) error { - idx.Remove(name) - fi, err := w.Filesystem.Lstat(name) - if err != nil { - return err - } - - e := &index.Entry{ - Hash: h, - Name: name, - Mode: mode, - ModifiedAt: fi.ModTime(), - Size: uint32(fi.Size()), - } - - // if the FileInfo.Sys() comes from os the ctime, dev, inode, uid and gid - // can be retrieved, otherwise this doesn't apply - if fillSystemInfo != nil { - fillSystemInfo(e, fi.Sys()) - } - idx.Add(e) - return nil -} - -func (r *Repository) getTreeFromCommitHash(commit plumbing.Hash) (*object.Tree, error) { - c, err := r.CommitObject(commit) - if err != nil { - return nil, err - } - - return c.Tree() -} - -var fillSystemInfo func(e *index.Entry, sys interface{}) - -const gitmodulesFile = ".gitmodules" - -// Submodule returns the submodule with the given name -func (w *Worktree) Submodule(name string) (*Submodule, error) { - l, err := w.Submodules() - if err != nil { - return nil, err - } - - for _, m := range l { - if m.Config().Name == name { - return m, nil - } - } - - return nil, ErrSubmoduleNotFound -} - -// Submodules returns all the available submodules -func (w *Worktree) Submodules() (Submodules, error) { - l := make(Submodules, 0) - m, err := w.readGitmodulesFile() - if err != nil || m == nil { - return l, err - } - - c, err := w.r.Config() - if err != nil { - return nil, err - } - - for _, s := range m.Submodules { - l = append(l, w.newSubmodule(s, c.Submodules[s.Name])) - } - - return l, nil -} - -func (w *Worktree) newSubmodule(fromModules, fromConfig *config.Submodule) *Submodule { - m := &Submodule{w: w} - m.initialized = fromConfig != nil - - if !m.initialized { - m.c = fromModules - return m - } - - m.c = fromConfig - m.c.Path = fromModules.Path - return m -} - -func (w *Worktree) isSymlink(path string) bool { - if s, err := w.Filesystem.Lstat(path); err == nil { - return s.Mode()&os.ModeSymlink != 0 - } - return false -} - -func (w *Worktree) readGitmodulesFile() (*config.Modules, error) { - if w.isSymlink(gitmodulesFile) { - return nil, ErrGitModulesSymlink - } - - f, err := w.Filesystem.Open(gitmodulesFile) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - - return nil, err - } - - defer f.Close() - input, err := io.ReadAll(f) - if err != nil { - return nil, err - } - - m := config.NewModules() - if err := m.Unmarshal(input); err != nil { - return m, err - } - - return m, nil -} - -// Clean the worktree by removing untracked files. -// An empty dir could be removed - this is what `git clean -f -d .` does. -func (w *Worktree) Clean(opts *CleanOptions) error { - s, err := w.Status() - if err != nil { - return err - } - - root := "" - files, err := w.Filesystem.ReadDir(root) - if err != nil { - return err - } - return w.doClean(s, opts, root, files) -} - -func (w *Worktree) doClean(status Status, opts *CleanOptions, dir string, files []os.FileInfo) error { - for _, fi := range files { - if fi.Name() == GitDirName { - continue - } - - // relative path under the root - path := filepath.Join(dir, fi.Name()) - if fi.IsDir() { - if !opts.Dir { - continue - } - - subfiles, err := w.Filesystem.ReadDir(path) - if err != nil { - return err - } - err = w.doClean(status, opts, path, subfiles) - if err != nil { - return err - } - } else { - if status.IsUntracked(path) { - if err := w.Filesystem.Remove(path); err != nil { - return err - } - } - } - } - - if opts.Dir && dir != "" { - _, err := removeDirIfEmpty(w.Filesystem, dir) - return err - } - - return nil -} - -// GrepResult is structure of a grep result. -type GrepResult struct { - // FileName is the name of file which contains match. - FileName string - // LineNumber is the line number of a file at which a match was found. - LineNumber int - // Content is the content of the file at the matching line. - Content string - // TreeName is the name of the tree (reference name/commit hash) at - // which the match was performed. - TreeName string -} - -func (gr GrepResult) String() string { - return fmt.Sprintf("%s:%s:%d:%s", gr.TreeName, gr.FileName, gr.LineNumber, gr.Content) -} - -// Grep performs grep on a repository. -func (r *Repository) Grep(opts *GrepOptions) ([]GrepResult, error) { - if err := opts.validate(r); err != nil { - return nil, err - } - - // Obtain commit hash from options (CommitHash or ReferenceName). - var commitHash plumbing.Hash - // treeName contains the value of TreeName in GrepResult. - var treeName string - - if opts.ReferenceName != "" { - ref, err := r.Reference(opts.ReferenceName, true) - if err != nil { - return nil, err - } - commitHash = ref.Hash() - treeName = opts.ReferenceName.String() - } else if !opts.CommitHash.IsZero() { - commitHash = opts.CommitHash - treeName = opts.CommitHash.String() - } - - // Obtain a tree from the commit hash and get a tracked files iterator from - // the tree. - tree, err := r.getTreeFromCommitHash(commitHash) - if err != nil { - return nil, err - } - fileiter := tree.Files() - - return findMatchInFiles(fileiter, treeName, opts) -} - -// Grep performs grep on a worktree. -func (w *Worktree) Grep(opts *GrepOptions) ([]GrepResult, error) { - return w.r.Grep(opts) -} - -// findMatchInFiles takes a FileIter, worktree name and GrepOptions, and -// returns a slice of GrepResult containing the result of regex pattern matching -// in content of all the files. -func findMatchInFiles(fileiter *object.FileIter, treeName string, opts *GrepOptions) ([]GrepResult, error) { - var results []GrepResult - - err := fileiter.ForEach(func(file *object.File) error { - var fileInPathSpec bool - - // When no pathspecs are provided, search all the files. - if len(opts.PathSpecs) == 0 { - fileInPathSpec = true - } - - // Check if the file name matches with the pathspec. Break out of the - // loop once a match is found. - for _, pathSpec := range opts.PathSpecs { - if pathSpec != nil && pathSpec.MatchString(file.Name) { - fileInPathSpec = true - break - } - } - - // If the file does not match with any of the pathspec, skip it. - if !fileInPathSpec { - return nil - } - - grepResults, err := findMatchInFile(file, treeName, opts) - if err != nil { - return err - } - results = append(results, grepResults...) - - return nil - }) - - return results, err -} - -// findMatchInFile takes a single File, worktree name and GrepOptions, -// and returns a slice of GrepResult containing the result of regex pattern -// matching in the given file. -func findMatchInFile(file *object.File, treeName string, opts *GrepOptions) ([]GrepResult, error) { - var grepResults []GrepResult - - content, err := file.Contents() - if err != nil { - return grepResults, err - } - - // Split the file content and parse line-by-line. - contentByLine := strings.Split(content, "\n") - for lineNum, cnt := range contentByLine { - addToResult := false - - // Match the patterns and content. Break out of the loop once a - // match is found. - for _, pattern := range opts.Patterns { - if pattern != nil && pattern.MatchString(cnt) { - // Add to result only if invert match is not enabled. - if !opts.InvertMatch { - addToResult = true - break - } - } else if opts.InvertMatch { - // If matching fails, and invert match is enabled, add to - // results. - addToResult = true - break - } - } - - if addToResult { - grepResults = append(grepResults, GrepResult{ - FileName: file.Name, - LineNumber: lineNum + 1, - Content: cnt, - TreeName: treeName, - }) - } - } - - return grepResults, nil -} - -// will walk up the directory tree removing all encountered empty -// directories, not just the one containing this file -func rmFileAndDirsIfEmpty(fs billy.Filesystem, name string) error { - if err := util.RemoveAll(fs, name); err != nil { - return err - } - - dir := filepath.Dir(name) - for { - removed, err := removeDirIfEmpty(fs, dir) - if err != nil && !os.IsNotExist(err) { - return err - } - - if !removed { - // directory was not empty and not removed, - // stop checking parents - break - } - - // move to parent directory - dir = filepath.Dir(dir) - } - - return nil -} - -// removeDirIfEmpty will remove the supplied directory `dir` if -// `dir` is empty -// returns true if the directory was removed -func removeDirIfEmpty(fs billy.Filesystem, dir string) (bool, error) { - files, err := fs.ReadDir(dir) - if err != nil { - return false, err - } - - if len(files) > 0 { - return false, nil - } - - err = fs.Remove(dir) - if err != nil { - return false, err - } - - return true, nil -} - -type indexBuilder struct { - entries map[string]*index.Entry -} - -func newIndexBuilder(idx *index.Index) *indexBuilder { - entries := make(map[string]*index.Entry, len(idx.Entries)) - for _, e := range idx.Entries { - entries[e.Name] = e - } - return &indexBuilder{ - entries: entries, - } -} - -func (b *indexBuilder) Write(idx *index.Index) { - idx.Entries = idx.Entries[:0] - for _, e := range b.entries { - idx.Entries = append(idx.Entries, e) - } -} - -func (b *indexBuilder) Add(e *index.Entry) { - b.entries[e.Name] = e -} - -func (b *indexBuilder) Remove(name string) { - delete(b.entries, filepath.ToSlash(name)) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/worktree_bsd.go b/vendor/github.com/jesseduffield/go-git/v5/worktree_bsd.go deleted file mode 100644 index 562007874d6..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/worktree_bsd.go +++ /dev/null @@ -1,26 +0,0 @@ -// +build darwin freebsd netbsd - -package git - -import ( - "syscall" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing/format/index" -) - -func init() { - fillSystemInfo = func(e *index.Entry, sys interface{}) { - if os, ok := sys.(*syscall.Stat_t); ok { - e.CreatedAt = time.Unix(os.Atimespec.Unix()) - e.Dev = uint32(os.Dev) - e.Inode = uint32(os.Ino) - e.GID = os.Gid - e.UID = os.Uid - } - } -} - -func isSymlinkWindowsNonAdmin(err error) bool { - return false -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/worktree_commit.go b/vendor/github.com/jesseduffield/go-git/v5/worktree_commit.go deleted file mode 100644 index 0be85d03576..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/worktree_commit.go +++ /dev/null @@ -1,296 +0,0 @@ -package git - -import ( - "bytes" - "errors" - "io" - "path" - "regexp" - "sort" - "strings" - - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" - "github.com/jesseduffield/go-git/v5/plumbing/format/index" - "github.com/jesseduffield/go-git/v5/plumbing/object" - "github.com/jesseduffield/go-git/v5/storage" - - "github.com/ProtonMail/go-crypto/openpgp" - "github.com/ProtonMail/go-crypto/openpgp/packet" - "github.com/go-git/go-billy/v5" -) - -var ( - // ErrEmptyCommit occurs when a commit is attempted using a clean - // working tree, with no changes to be committed. - ErrEmptyCommit = errors.New("cannot create empty commit: clean working tree") - - // characters to be removed from user name and/or email before using them to build a commit object - // See https://git-scm.com/docs/git-commit#_commit_information - invalidCharactersRe = regexp.MustCompile(`[<>\n]`) -) - -// Commit stores the current contents of the index in a new commit along with -// a log message from the user describing the changes. -func (w *Worktree) Commit(msg string, opts *CommitOptions) (plumbing.Hash, error) { - if err := opts.Validate(w.r); err != nil { - return plumbing.ZeroHash, err - } - - if opts.All { - if err := w.autoAddModifiedAndDeleted(); err != nil { - return plumbing.ZeroHash, err - } - } - - if opts.Amend { - head, err := w.r.Head() - if err != nil { - return plumbing.ZeroHash, err - } - headCommit, err := w.r.CommitObject(head.Hash()) - if err != nil { - return plumbing.ZeroHash, err - } - - opts.Parents = nil - if len(headCommit.ParentHashes) != 0 { - opts.Parents = []plumbing.Hash{headCommit.ParentHashes[0]} - } - } - - idx, err := w.r.Storer.Index() - if err != nil { - return plumbing.ZeroHash, err - } - - // First handle the case of the first commit in the repository being empty. - if len(opts.Parents) == 0 && len(idx.Entries) == 0 && !opts.AllowEmptyCommits { - return plumbing.ZeroHash, ErrEmptyCommit - } - - h := &buildTreeHelper{ - fs: w.Filesystem, - s: w.r.Storer, - } - - treeHash, err := h.BuildTree(idx, opts) - if err != nil { - return plumbing.ZeroHash, err - } - - previousTree := plumbing.ZeroHash - if len(opts.Parents) > 0 { - parentCommit, err := w.r.CommitObject(opts.Parents[0]) - if err != nil { - return plumbing.ZeroHash, err - } - previousTree = parentCommit.TreeHash - } - - if treeHash == previousTree && !opts.AllowEmptyCommits { - return plumbing.ZeroHash, ErrEmptyCommit - } - - commit, err := w.buildCommitObject(msg, opts, treeHash) - if err != nil { - return plumbing.ZeroHash, err - } - - return commit, w.updateHEAD(commit) -} - -func (w *Worktree) autoAddModifiedAndDeleted() error { - s, err := w.Status() - if err != nil { - return err - } - - idx, err := w.r.Storer.Index() - if err != nil { - return err - } - - for path, fs := range s { - if fs.Worktree != Modified && fs.Worktree != Deleted { - continue - } - - if _, _, err := w.doAddFile(idx, s, path, nil); err != nil { - return err - } - - } - - return w.r.Storer.SetIndex(idx) -} - -func (w *Worktree) updateHEAD(commit plumbing.Hash) error { - head, err := w.r.Storer.Reference(plumbing.HEAD) - if err != nil { - return err - } - - name := plumbing.HEAD - if head.Type() != plumbing.HashReference { - name = head.Target() - } - - ref := plumbing.NewHashReference(name, commit) - return w.r.Storer.SetReference(ref) -} - -func (w *Worktree) buildCommitObject(msg string, opts *CommitOptions, tree plumbing.Hash) (plumbing.Hash, error) { - commit := &object.Commit{ - Author: w.sanitize(*opts.Author), - Committer: w.sanitize(*opts.Committer), - Message: msg, - TreeHash: tree, - ParentHashes: opts.Parents, - } - - // Convert SignKey into a Signer if set. Existing Signer should take priority. - signer := opts.Signer - if signer == nil && opts.SignKey != nil { - signer = &gpgSigner{key: opts.SignKey} - } - if signer != nil { - sig, err := signObject(signer, commit) - if err != nil { - return plumbing.ZeroHash, err - } - commit.PGPSignature = string(sig) - } - - obj := w.r.Storer.NewEncodedObject() - if err := commit.Encode(obj); err != nil { - return plumbing.ZeroHash, err - } - return w.r.Storer.SetEncodedObject(obj) -} - -func (w *Worktree) sanitize(signature object.Signature) object.Signature { - return object.Signature{ - Name: invalidCharactersRe.ReplaceAllString(signature.Name, ""), - Email: invalidCharactersRe.ReplaceAllString(signature.Email, ""), - When: signature.When, - } -} - -type gpgSigner struct { - key *openpgp.Entity - cfg *packet.Config -} - -func (s *gpgSigner) Sign(message io.Reader) ([]byte, error) { - var b bytes.Buffer - if err := openpgp.ArmoredDetachSign(&b, s.key, message, s.cfg); err != nil { - return nil, err - } - return b.Bytes(), nil -} - -// buildTreeHelper converts a given index.Index file into multiple git objects -// reading the blobs from the given filesystem and creating the trees from the -// index structure. The created objects are pushed to a given Storer. -type buildTreeHelper struct { - fs billy.Filesystem - s storage.Storer - - trees map[string]*object.Tree - entries map[string]*object.TreeEntry -} - -// BuildTree builds the tree objects and push its to the storer, the hash -// of the root tree is returned. -func (h *buildTreeHelper) BuildTree(idx *index.Index, opts *CommitOptions) (plumbing.Hash, error) { - const rootNode = "" - h.trees = map[string]*object.Tree{rootNode: {}} - h.entries = map[string]*object.TreeEntry{} - - for _, e := range idx.Entries { - if err := h.commitIndexEntry(e); err != nil { - return plumbing.ZeroHash, err - } - } - - return h.copyTreeToStorageRecursive(rootNode, h.trees[rootNode]) -} - -func (h *buildTreeHelper) commitIndexEntry(e *index.Entry) error { - parts := strings.Split(e.Name, "/") - - var fullpath string - for _, part := range parts { - parent := fullpath - fullpath = path.Join(fullpath, part) - - h.doBuildTree(e, parent, fullpath) - } - - return nil -} - -func (h *buildTreeHelper) doBuildTree(e *index.Entry, parent, fullpath string) { - if _, ok := h.trees[fullpath]; ok { - return - } - - if _, ok := h.entries[fullpath]; ok { - return - } - - te := object.TreeEntry{Name: path.Base(fullpath)} - - if fullpath == e.Name { - te.Mode = e.Mode - te.Hash = e.Hash - } else { - te.Mode = filemode.Dir - h.trees[fullpath] = &object.Tree{} - } - - h.trees[parent].Entries = append(h.trees[parent].Entries, te) -} - -type sortableEntries []object.TreeEntry - -func (sortableEntries) sortName(te object.TreeEntry) string { - if te.Mode == filemode.Dir { - return te.Name + "/" - } - return te.Name -} -func (se sortableEntries) Len() int { return len(se) } -func (se sortableEntries) Less(i int, j int) bool { return se.sortName(se[i]) < se.sortName(se[j]) } -func (se sortableEntries) Swap(i int, j int) { se[i], se[j] = se[j], se[i] } - -func (h *buildTreeHelper) copyTreeToStorageRecursive(parent string, t *object.Tree) (plumbing.Hash, error) { - sort.Sort(sortableEntries(t.Entries)) - for i, e := range t.Entries { - if e.Mode != filemode.Dir && !e.Hash.IsZero() { - continue - } - - path := path.Join(parent, e.Name) - - var err error - e.Hash, err = h.copyTreeToStorageRecursive(path, h.trees[path]) - if err != nil { - return plumbing.ZeroHash, err - } - - t.Entries[i] = e - } - - o := h.s.NewEncodedObject() - if err := t.Encode(o); err != nil { - return plumbing.ZeroHash, err - } - - hash := o.Hash() - if h.s.HasEncodedObject(hash) == nil { - return hash, nil - } - return h.s.SetEncodedObject(o) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/worktree_js.go b/vendor/github.com/jesseduffield/go-git/v5/worktree_js.go deleted file mode 100644 index 7c4f6c325e1..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/worktree_js.go +++ /dev/null @@ -1,26 +0,0 @@ -// +build js - -package git - -import ( - "syscall" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing/format/index" -) - -func init() { - fillSystemInfo = func(e *index.Entry, sys interface{}) { - if os, ok := sys.(*syscall.Stat_t); ok { - e.CreatedAt = time.Unix(int64(os.Ctime), int64(os.CtimeNsec)) - e.Dev = uint32(os.Dev) - e.Inode = uint32(os.Ino) - e.GID = os.Gid - e.UID = os.Uid - } - } -} - -func isSymlinkWindowsNonAdmin(err error) bool { - return false -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/worktree_linux.go b/vendor/github.com/jesseduffield/go-git/v5/worktree_linux.go deleted file mode 100644 index ee090a7b28e..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/worktree_linux.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build linux -// +build linux - -package git - -import ( - "syscall" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing/format/index" -) - -func init() { - fillSystemInfo = func(e *index.Entry, sys interface{}) { - if os, ok := sys.(*syscall.Stat_t); ok { - e.CreatedAt = time.Unix(os.Ctim.Unix()) - e.Dev = uint32(os.Dev) - e.Inode = uint32(os.Ino) - e.GID = os.Gid - e.UID = os.Uid - } - } -} - -func isSymlinkWindowsNonAdmin(_ error) bool { - return false -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/worktree_plan9.go b/vendor/github.com/jesseduffield/go-git/v5/worktree_plan9.go deleted file mode 100644 index 7952a68e593..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/worktree_plan9.go +++ /dev/null @@ -1,31 +0,0 @@ -package git - -import ( - "syscall" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing/format/index" -) - -func init() { - fillSystemInfo = func(e *index.Entry, sys interface{}) { - if os, ok := sys.(*syscall.Dir); ok { - // Plan 9 doesn't have a CreatedAt field. - e.CreatedAt = time.Unix(int64(os.Mtime), 0) - - e.Dev = uint32(os.Dev) - - // Plan 9 has no Inode. - // ext2srv(4) appears to store Inode in Qid.Path. - e.Inode = uint32(os.Qid.Path) - - // Plan 9 has string UID/GID - e.GID = 0 - e.UID = 0 - } - } -} - -func isSymlinkWindowsNonAdmin(err error) bool { - return true -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/worktree_status.go b/vendor/github.com/jesseduffield/go-git/v5/worktree_status.go deleted file mode 100644 index 21c74c59ebe..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/worktree_status.go +++ /dev/null @@ -1,733 +0,0 @@ -package git - -import ( - "bytes" - "errors" - "io" - "os" - "path" - "path/filepath" - "strings" - - "github.com/go-git/go-billy/v5/util" - "github.com/jesseduffield/go-git/v5/plumbing" - "github.com/jesseduffield/go-git/v5/plumbing/filemode" - "github.com/jesseduffield/go-git/v5/plumbing/format/gitignore" - "github.com/jesseduffield/go-git/v5/plumbing/format/index" - "github.com/jesseduffield/go-git/v5/plumbing/object" - "github.com/jesseduffield/go-git/v5/utils/ioutil" - "github.com/jesseduffield/go-git/v5/utils/merkletrie" - "github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem" - mindex "github.com/jesseduffield/go-git/v5/utils/merkletrie/index" - "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder" -) - -var ( - // ErrDestinationExists in an Move operation means that the target exists on - // the worktree. - ErrDestinationExists = errors.New("destination exists") - // ErrGlobNoMatches in an AddGlob if the glob pattern does not match any - // files in the worktree. - ErrGlobNoMatches = errors.New("glob pattern did not match any files") - // ErrUnsupportedStatusStrategy occurs when an invalid StatusStrategy is used - // when processing the Worktree status. - ErrUnsupportedStatusStrategy = errors.New("unsupported status strategy") -) - -// Status returns the working tree status. -func (w *Worktree) Status() (Status, error) { - return w.StatusWithOptions(StatusOptions{Strategy: defaultStatusStrategy}) -} - -// StatusOptions defines the options for Worktree.StatusWithOptions(). -type StatusOptions struct { - Strategy StatusStrategy -} - -// StatusWithOptions returns the working tree status. -func (w *Worktree) StatusWithOptions(o StatusOptions) (Status, error) { - var hash plumbing.Hash - - ref, err := w.r.Head() - if err != nil && err != plumbing.ErrReferenceNotFound { - return nil, err - } - - if err == nil { - hash = ref.Hash() - } - - return w.status(o.Strategy, hash) -} - -func (w *Worktree) status(ss StatusStrategy, commit plumbing.Hash) (Status, error) { - s, err := ss.new(w) - if err != nil { - return nil, err - } - - left, err := w.diffCommitWithStaging(commit, false) - if err != nil { - return nil, err - } - - for _, ch := range left { - a, err := ch.Action() - if err != nil { - return nil, err - } - - fs := s.File(nameFromAction(&ch)) - fs.Worktree = Unmodified - - switch a { - case merkletrie.Delete: - s.File(ch.From.String()).Staging = Deleted - case merkletrie.Insert: - s.File(ch.To.String()).Staging = Added - case merkletrie.Modify: - s.File(ch.To.String()).Staging = Modified - } - } - - right, err := w.diffStagingWithWorktree(false, true) - if err != nil { - return nil, err - } - - for _, ch := range right { - a, err := ch.Action() - if err != nil { - return nil, err - } - - fs := s.File(nameFromAction(&ch)) - if fs.Staging == Untracked { - fs.Staging = Unmodified - } - - switch a { - case merkletrie.Delete: - fs.Worktree = Deleted - case merkletrie.Insert: - fs.Worktree = Untracked - fs.Staging = Untracked - case merkletrie.Modify: - fs.Worktree = Modified - } - } - - return s, nil -} - -func nameFromAction(ch *merkletrie.Change) string { - name := ch.To.String() - if name == "" { - return ch.From.String() - } - - return name -} - -func (w *Worktree) diffStagingWithWorktree(reverse, excludeIgnoredChanges bool) (merkletrie.Changes, error) { - idx, err := w.r.Storer.Index() - if err != nil { - return nil, err - } - - from := mindex.NewRootNode(idx) - submodules, err := w.getSubmodulesStatus() - if err != nil { - return nil, err - } - - to := filesystem.NewRootNode(w.Filesystem, submodules) - - var c merkletrie.Changes - if reverse { - c, err = merkletrie.DiffTree(to, from, diffTreeIsEquals) - } else { - c, err = merkletrie.DiffTree(from, to, diffTreeIsEquals) - } - - if err != nil { - return nil, err - } - - if excludeIgnoredChanges { - return w.excludeIgnoredChanges(c), nil - } - return c, nil -} - -func (w *Worktree) excludeIgnoredChanges(changes merkletrie.Changes) merkletrie.Changes { - patterns, err := gitignore.ReadPatterns(w.Filesystem, nil) - if err != nil { - return changes - } - - patterns = append(patterns, w.Excludes...) - - if len(patterns) == 0 { - return changes - } - - m := gitignore.NewMatcher(patterns) - - var res merkletrie.Changes - for _, ch := range changes { - var path []string - for _, n := range ch.To { - path = append(path, n.Name()) - } - if len(path) == 0 { - for _, n := range ch.From { - path = append(path, n.Name()) - } - } - if len(path) != 0 { - isDir := (len(ch.To) > 0 && ch.To.IsDir()) || (len(ch.From) > 0 && ch.From.IsDir()) - if m.Match(path, isDir) { - if len(ch.From) == 0 { - continue - } - } - } - res = append(res, ch) - } - return res -} - -func (w *Worktree) getSubmodulesStatus() (map[string]plumbing.Hash, error) { - o := map[string]plumbing.Hash{} - - sub, err := w.Submodules() - if err != nil { - return nil, err - } - - status, err := sub.Status() - if err != nil { - return nil, err - } - - for _, s := range status { - if s.Current.IsZero() { - o[s.Path] = s.Expected - continue - } - - o[s.Path] = s.Current - } - - return o, nil -} - -func (w *Worktree) diffCommitWithStaging(commit plumbing.Hash, reverse bool) (merkletrie.Changes, error) { - var t *object.Tree - if !commit.IsZero() { - c, err := w.r.CommitObject(commit) - if err != nil { - return nil, err - } - - t, err = c.Tree() - if err != nil { - return nil, err - } - } - - return w.diffTreeWithStaging(t, reverse) -} - -func (w *Worktree) diffTreeWithStaging(t *object.Tree, reverse bool) (merkletrie.Changes, error) { - var from noder.Noder - if t != nil { - from = object.NewTreeRootNode(t) - } - - idx, err := w.r.Storer.Index() - if err != nil { - return nil, err - } - - to := mindex.NewRootNode(idx) - - if reverse { - return merkletrie.DiffTree(to, from, diffTreeIsEquals) - } - - return merkletrie.DiffTree(from, to, diffTreeIsEquals) -} - -var emptyNoderHash = make([]byte, 24) - -// diffTreeIsEquals is a implementation of noder.Equals, used to compare -// noder.Noder, it compare the content and the length of the hashes. -// -// Since some of the noder.Noder implementations doesn't compute a hash for -// some directories, if any of the hashes is a 24-byte slice of zero values -// the comparison is not done and the hashes are take as different. -func diffTreeIsEquals(a, b noder.Hasher) bool { - hashA := a.Hash() - hashB := b.Hash() - - if bytes.Equal(hashA, emptyNoderHash) || bytes.Equal(hashB, emptyNoderHash) { - return false - } - - return bytes.Equal(hashA, hashB) -} - -// Add adds the file contents of a file in the worktree to the index. if the -// file is already staged in the index no error is returned. If a file deleted -// from the Workspace is given, the file is removed from the index. If a -// directory given, adds the files and all his sub-directories recursively in -// the worktree to the index. If any of the files is already staged in the index -// no error is returned. When path is a file, the blob.Hash is returned. -func (w *Worktree) Add(path string) (plumbing.Hash, error) { - // TODO(mcuadros): deprecate in favor of AddWithOption in v6. - return w.doAdd(path, make([]gitignore.Pattern, 0), false) -} - -func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string, ignorePattern []gitignore.Pattern) (added bool, err error) { - if len(ignorePattern) > 0 { - m := gitignore.NewMatcher(ignorePattern) - matchPath := strings.Split(directory, string(os.PathSeparator)) - if m.Match(matchPath, true) { - // ignore - return false, nil - } - } - - directory = filepath.ToSlash(filepath.Clean(directory)) - - for name := range s { - if !isPathInDirectory(name, directory) { - continue - } - - var a bool - a, _, err = w.doAddFile(idx, s, name, ignorePattern) - if err != nil { - return - } - - added = added || a - } - - return -} - -func isPathInDirectory(path, directory string) bool { - return directory == "." || strings.HasPrefix(path, directory+"/") -} - -// AddWithOptions file contents to the index, updates the index using the -// current content found in the working tree, to prepare the content staged for -// the next commit. -// -// It typically adds the current content of existing paths as a whole, but with -// some options it can also be used to add content with only part of the changes -// made to the working tree files applied, or remove paths that do not exist in -// the working tree anymore. -func (w *Worktree) AddWithOptions(opts *AddOptions) error { - if err := opts.Validate(w.r); err != nil { - return err - } - - if opts.All { - _, err := w.doAdd(".", w.Excludes, false) - return err - } - - if opts.Glob != "" { - return w.AddGlob(opts.Glob) - } - - _, err := w.doAdd(opts.Path, make([]gitignore.Pattern, 0), opts.SkipStatus) - return err -} - -func (w *Worktree) doAdd(path string, ignorePattern []gitignore.Pattern, skipStatus bool) (plumbing.Hash, error) { - idx, err := w.r.Storer.Index() - if err != nil { - return plumbing.ZeroHash, err - } - - var h plumbing.Hash - var added bool - - fi, err := w.Filesystem.Lstat(path) - - // status is required for doAddDirectory - var s Status - var err2 error - if !skipStatus || fi == nil || fi.IsDir() { - s, err2 = w.Status() - if err2 != nil { - return plumbing.ZeroHash, err2 - } - } - - path = filepath.Clean(path) - - if err != nil || !fi.IsDir() { - added, h, err = w.doAddFile(idx, s, path, ignorePattern) - } else { - added, err = w.doAddDirectory(idx, s, path, ignorePattern) - } - - if err != nil { - return h, err - } - - if !added { - return h, nil - } - - return h, w.r.Storer.SetIndex(idx) -} - -// AddGlob adds all paths, matching pattern, to the index. If pattern matches a -// directory path, all directory contents are added to the index recursively. No -// error is returned if all matching paths are already staged in index. -func (w *Worktree) AddGlob(pattern string) error { - // TODO(mcuadros): deprecate in favor of AddWithOption in v6. - files, err := util.Glob(w.Filesystem, pattern) - if err != nil { - return err - } - - if len(files) == 0 { - return ErrGlobNoMatches - } - - s, err := w.Status() - if err != nil { - return err - } - - idx, err := w.r.Storer.Index() - if err != nil { - return err - } - - var saveIndex bool - for _, file := range files { - fi, err := w.Filesystem.Lstat(file) - if err != nil { - return err - } - - var added bool - if fi.IsDir() { - added, err = w.doAddDirectory(idx, s, file, make([]gitignore.Pattern, 0)) - } else { - added, _, err = w.doAddFile(idx, s, file, make([]gitignore.Pattern, 0)) - } - - if err != nil { - return err - } - - if !saveIndex && added { - saveIndex = true - } - } - - if saveIndex { - return w.r.Storer.SetIndex(idx) - } - - return nil -} - -// doAddFile create a new blob from path and update the index, added is true if -// the file added is different from the index. -// if s status is nil will skip the status check and update the index anyway -func (w *Worktree) doAddFile(idx *index.Index, s Status, path string, ignorePattern []gitignore.Pattern) (added bool, h plumbing.Hash, err error) { - if s != nil && s.File(path).Worktree == Unmodified { - return false, h, nil - } - if len(ignorePattern) > 0 { - m := gitignore.NewMatcher(ignorePattern) - matchPath := strings.Split(path, string(os.PathSeparator)) - if m.Match(matchPath, true) { - // ignore - return false, h, nil - } - } - - h, err = w.copyFileToStorage(path) - if err != nil { - if os.IsNotExist(err) { - added = true - h, err = w.deleteFromIndex(idx, path) - } - - return - } - - if err := w.addOrUpdateFileToIndex(idx, path, h); err != nil { - return false, h, err - } - - return true, h, err -} - -func (w *Worktree) copyFileToStorage(path string) (hash plumbing.Hash, err error) { - fi, err := w.Filesystem.Lstat(path) - if err != nil { - return plumbing.ZeroHash, err - } - - obj := w.r.Storer.NewEncodedObject() - obj.SetType(plumbing.BlobObject) - obj.SetSize(fi.Size()) - - writer, err := obj.Writer() - if err != nil { - return plumbing.ZeroHash, err - } - - defer ioutil.CheckClose(writer, &err) - - if fi.Mode()&os.ModeSymlink != 0 { - err = w.fillEncodedObjectFromSymlink(writer, path, fi) - } else { - err = w.fillEncodedObjectFromFile(writer, path, fi) - } - - if err != nil { - return plumbing.ZeroHash, err - } - - return w.r.Storer.SetEncodedObject(obj) -} - -func (w *Worktree) fillEncodedObjectFromFile(dst io.Writer, path string, _ os.FileInfo) (err error) { - src, err := w.Filesystem.Open(path) - if err != nil { - return err - } - - defer ioutil.CheckClose(src, &err) - - if _, err := io.Copy(dst, src); err != nil { - return err - } - - return err -} - -func (w *Worktree) fillEncodedObjectFromSymlink(dst io.Writer, path string, _ os.FileInfo) error { - target, err := w.Filesystem.Readlink(path) - if err != nil { - return err - } - - _, err = dst.Write([]byte(target)) - return err -} - -func (w *Worktree) addOrUpdateFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error { - e, err := idx.Entry(filename) - if err != nil && err != index.ErrEntryNotFound { - return err - } - - if err == index.ErrEntryNotFound { - return w.doAddFileToIndex(idx, filename, h) - } - - return w.doUpdateFileToIndex(e, filename, h) -} - -func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error { - return w.doUpdateFileToIndex(idx.Add(filename), filename, h) -} - -func (w *Worktree) doUpdateFileToIndex(e *index.Entry, filename string, h plumbing.Hash) error { - info, err := w.Filesystem.Lstat(filename) - if err != nil { - return err - } - - e.Hash = h - e.ModifiedAt = info.ModTime() - e.Mode, err = filemode.NewFromOSFileMode(info.Mode()) - if err != nil { - return err - } - - // The entry size must always reflect the current state, otherwise - // it will cause go-git's Worktree.Status() to divert from "git status". - // The size of a symlink is the length of the path to the target. - // The size of Regular and Executable files is the size of the files. - e.Size = uint32(info.Size()) - - fillSystemInfo(e, info.Sys()) - return nil -} - -// Remove removes files from the working tree and from the index. -func (w *Worktree) Remove(path string) (plumbing.Hash, error) { - // TODO(mcuadros): remove plumbing.Hash from signature at v5. - idx, err := w.r.Storer.Index() - if err != nil { - return plumbing.ZeroHash, err - } - - var h plumbing.Hash - - fi, err := w.Filesystem.Lstat(path) - if err != nil || !fi.IsDir() { - h, err = w.doRemoveFile(idx, path) - } else { - _, err = w.doRemoveDirectory(idx, path) - } - if err != nil { - return h, err - } - - return h, w.r.Storer.SetIndex(idx) -} - -func (w *Worktree) doRemoveDirectory(idx *index.Index, directory string) (removed bool, err error) { - files, err := w.Filesystem.ReadDir(directory) - if err != nil { - return false, err - } - - for _, file := range files { - name := path.Join(directory, file.Name()) - - var r bool - if file.IsDir() { - r, err = w.doRemoveDirectory(idx, name) - } else { - _, err = w.doRemoveFile(idx, name) - if err == index.ErrEntryNotFound { - err = nil - } - } - - if err != nil { - return - } - - if !removed && r { - removed = true - } - } - - err = w.removeEmptyDirectory(directory) - return -} - -func (w *Worktree) removeEmptyDirectory(path string) error { - files, err := w.Filesystem.ReadDir(path) - if err != nil { - return err - } - - if len(files) != 0 { - return nil - } - - return w.Filesystem.Remove(path) -} - -func (w *Worktree) doRemoveFile(idx *index.Index, path string) (plumbing.Hash, error) { - hash, err := w.deleteFromIndex(idx, path) - if err != nil { - return plumbing.ZeroHash, err - } - - return hash, w.deleteFromFilesystem(path) -} - -func (w *Worktree) deleteFromIndex(idx *index.Index, path string) (plumbing.Hash, error) { - e, err := idx.Remove(path) - if err != nil { - return plumbing.ZeroHash, err - } - - return e.Hash, nil -} - -func (w *Worktree) deleteFromFilesystem(path string) error { - err := w.Filesystem.Remove(path) - if os.IsNotExist(err) { - return nil - } - - return err -} - -// RemoveGlob removes all paths, matching pattern, from the index. If pattern -// matches a directory path, all directory contents are removed from the index -// recursively. -func (w *Worktree) RemoveGlob(pattern string) error { - idx, err := w.r.Storer.Index() - if err != nil { - return err - } - - entries, err := idx.Glob(pattern) - if err != nil { - return err - } - - for _, e := range entries { - file := filepath.FromSlash(e.Name) - if _, err := w.Filesystem.Lstat(file); err != nil && !os.IsNotExist(err) { - return err - } - - if _, err := w.doRemoveFile(idx, file); err != nil { - return err - } - - dir, _ := filepath.Split(file) - if err := w.removeEmptyDirectory(dir); err != nil { - return err - } - } - - return w.r.Storer.SetIndex(idx) -} - -// Move moves or rename a file in the worktree and the index, directories are -// not supported. -func (w *Worktree) Move(from, to string) (plumbing.Hash, error) { - // TODO(mcuadros): support directories and/or implement support for glob - if _, err := w.Filesystem.Lstat(from); err != nil { - return plumbing.ZeroHash, err - } - - if _, err := w.Filesystem.Lstat(to); err == nil { - return plumbing.ZeroHash, ErrDestinationExists - } - - idx, err := w.r.Storer.Index() - if err != nil { - return plumbing.ZeroHash, err - } - - hash, err := w.deleteFromIndex(idx, from) - if err != nil { - return plumbing.ZeroHash, err - } - - if err := w.Filesystem.Rename(from, to); err != nil { - return hash, err - } - - if err := w.addOrUpdateFileToIndex(idx, to, hash); err != nil { - return hash, err - } - - return hash, w.r.Storer.SetIndex(idx) -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/worktree_unix_other.go b/vendor/github.com/jesseduffield/go-git/v5/worktree_unix_other.go deleted file mode 100644 index cc89ef8d815..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/worktree_unix_other.go +++ /dev/null @@ -1,26 +0,0 @@ -// +build openbsd dragonfly solaris - -package git - -import ( - "syscall" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing/format/index" -) - -func init() { - fillSystemInfo = func(e *index.Entry, sys interface{}) { - if os, ok := sys.(*syscall.Stat_t); ok { - e.CreatedAt = time.Unix(os.Atim.Unix()) - e.Dev = uint32(os.Dev) - e.Inode = uint32(os.Ino) - e.GID = os.Gid - e.UID = os.Uid - } - } -} - -func isSymlinkWindowsNonAdmin(err error) bool { - return false -} diff --git a/vendor/github.com/jesseduffield/go-git/v5/worktree_windows.go b/vendor/github.com/jesseduffield/go-git/v5/worktree_windows.go deleted file mode 100644 index e98f0773ee8..00000000000 --- a/vendor/github.com/jesseduffield/go-git/v5/worktree_windows.go +++ /dev/null @@ -1,35 +0,0 @@ -// +build windows - -package git - -import ( - "os" - "syscall" - "time" - - "github.com/jesseduffield/go-git/v5/plumbing/format/index" -) - -func init() { - fillSystemInfo = func(e *index.Entry, sys interface{}) { - if os, ok := sys.(*syscall.Win32FileAttributeData); ok { - seconds := os.CreationTime.Nanoseconds() / 1000000000 - nanoseconds := os.CreationTime.Nanoseconds() - seconds*1000000000 - e.CreatedAt = time.Unix(seconds, nanoseconds) - } - } -} - -func isSymlinkWindowsNonAdmin(err error) bool { - const ERROR_PRIVILEGE_NOT_HELD syscall.Errno = 1314 - - if err != nil { - if errLink, ok := err.(*os.LinkError); ok { - if errNo, ok := errLink.Err.(syscall.Errno); ok { - return errNo == ERROR_PRIVILEGE_NOT_HELD - } - } - } - - return false -} diff --git a/vendor/github.com/jesseduffield/gocui/.gitignore b/vendor/github.com/jesseduffield/gocui/.gitignore deleted file mode 100644 index 1377554ebea..00000000000 --- a/vendor/github.com/jesseduffield/gocui/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.swp diff --git a/vendor/github.com/jesseduffield/gocui/AUTHORS b/vendor/github.com/jesseduffield/gocui/AUTHORS deleted file mode 100644 index 43ec4cecf7c..00000000000 --- a/vendor/github.com/jesseduffield/gocui/AUTHORS +++ /dev/null @@ -1,30 +0,0 @@ -# This is the official list of gocui authors for copyright purposes. - -# Names should be added to this file as -# Name or Organization contribution -# Contribution -# The email address is not required for organizations. - -Roi Martin - Main developer - -Ryan Sullivan - Toggleable view frames - -Matthieu Rakotojaona - Wrapped views - -Harry Lawrence - Basic mouse support - -Danny Tylman - Masked views - -Frederik Deweerdt - Colored fonts - -Henri Koski - Custom current view color - -Dustin Willis Webber - 256-colors output mode support diff --git a/vendor/github.com/jesseduffield/gocui/CHANGES_tcell.md b/vendor/github.com/jesseduffield/gocui/CHANGES_tcell.md deleted file mode 100644 index 7aa552342fd..00000000000 --- a/vendor/github.com/jesseduffield/gocui/CHANGES_tcell.md +++ /dev/null @@ -1,56 +0,0 @@ -# Change from termbox to tcell - -Original GOCUI was written on top of [termbox](https://github.com/nsf/termbox-go) package. This document describes changes which were done to be able to use to [tcell/v2](https://github.com/gdamore/tcell) package. - -## Attribute color - -Attribute type represents a terminal attribute like color and font effects. Color and font effects can be combined using bitwise OR (`|`). - -In `termbox` colors were represented by range 1 to 256. `0` was default color which uses the terminal default setting. - -In `tcell` colors can be represented in 24bit, and all of them starts from 0. Valid colors have special flag which gives them real value starting from 4294967296. `0` is a default similart to `termbox`. -The change to support all these colors was made in a way, that original colors from 1 to 256 are backward compatible and if user has color specified as -`Attribute(ansicolor+1)` without the valid color flag, it will be translated to `tcell` color by subtracting 1 and making the color valid by adding the flag. This should ensure backward compatibility. - -All the color constants are the same with different underlying values. From user perspective, this should be fine unless some arithmetic is done with it. For example `ColorBlack` was `1` in original version but is `4294967296` in new version. - -GOCUI provides a few helper functions which could be used to get the real color value or to create a color attribute. - -- `(a Attribute).Hex()` - returns `int32` value of the color represented as `Red << 16 | Green << 8 | Blue` -- `(a Attribute).RGB()` - returns 3 `int32` values for red, green and blue color. -- `GetColor(string)` - creates `Attribute` from color passed as a string. This can be hex value or color name (W3C name). -- `Get256Color(int32)` - creates `Attribute` from color number (ANSI colors). -- `GetRGBColor(int32)` - creates `Attribute` from color number created the same way as `Hex()` function returns. -- `NewRGBColor(int32, int32, int32)` - creates `Attribute` from color numbers for red, green and blue values. - -## Attribute font effect - -There were 3 attributes for font effect, `AttrBold`, `AttrUnderline` and `AttrReverse`. - -`tcell` supports more attributes, so they were added. All of these attributes have different values from before. However they can be used in the same way as before. - -All the font effect attributes: -- `AttrBold` -- `AttrBlink` -- `AttrReverse` -- `AttrUnderline` -- `AttrDim` -- `AttrItalic` -- `AttrStrikeThrough` - -## OutputMode - -`OutputMode` in `termbox` was used to translate colors into the correct range. So for example in `OutputGrayscale` you had colors from 1 - 24 all representing gray colors in range 232 - 255, and white and black color. - -`tcell` colors are 24bit and they are translated by the library into the color which can be read by terminal. - -The original translation from `termbox` was included in GOCUI to be backward compatible. This is enabled in all the original modes: `OutputNormal`, `Output216`, `OutputGrayscale` and `Output256`. - -`OutputTrue` is a new mode. It is recomended, because in this mode GOCUI doesn't do any kind of translation of the colors and pass them directly to `tcell`. If user wants to use true color in terminal and this mode doesn't work, it might be because of the terminal setup. `tcell` has a documentation what needs to be done, but in short `COLORTERM=truecolor` environment variable should help (see [_examples/colorstrue.go](./_examples/colorstrue.go)). Other way would be to have `TERM` environment variable having value with suffix `-truecolor`. To disable true color set `TCELL_TRUECOLOR=disable`. - -## Keybinding - -`termbox` had different way of handling input from terminal than `tcell`. This leads to some adjustement on how the keys are represented. -In general, all the keys in GOCUI should be presented from before, but the underlying values might be different. This could lead to some problems if a user uses different parser to create the `Key` for the keybinding. If using GOCUI parser, everything should be ok. - -Mouse is handled differently in `tcell`, but translation was done to keep it in the same way as it was before. However this was harder to test due to different behaviour across the platforms, so if anything is missing or not working, please report. diff --git a/vendor/github.com/jesseduffield/gocui/CODE_OF_CONDUCT.md b/vendor/github.com/jesseduffield/gocui/CODE_OF_CONDUCT.md deleted file mode 100644 index 1bdac055eb2..00000000000 --- a/vendor/github.com/jesseduffield/gocui/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at mkopenga@gmail.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/vendor/github.com/jesseduffield/gocui/CONTRIBUTING.md b/vendor/github.com/jesseduffield/gocui/CONTRIBUTING.md deleted file mode 100644 index b93e45b25a2..00000000000 --- a/vendor/github.com/jesseduffield/gocui/CONTRIBUTING.md +++ /dev/null @@ -1,33 +0,0 @@ -# Contributing - -Everyone is welcome to help make gocui better! - -When contributing to this repository, please first discuss the change you wish -to make via issue, email, or any other method with the owners of this repository -before making a change. - -## So all code changes happen through Pull Requests -Pull requests are the best way to propose changes to the codebase. We actively -welcome your pull requests: - -1. Fork the repo and create your branch from `master` with a name like `feature/contributors-guide`. -2. If you've added code that should be tested, add tests. -3. If you've added code that need documentation, update the documentation. -4. Make sure your code follows the [effective go](https://golang.org/doc/effective_go.html) guidelines as much as possible. -5. Be sure to test your modifications. -6. Make sure your branch is up to date with the master branch. -7. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). -8. Create that pull request! - -## Code of conduct -Please note by participating in this project, you agree to abide by the [code of conduct]. - -[code of conduct]: https://github.com/awesome-gocui/gocui/blob/master/CODE-OF-CONDUCT.md - -## Any contributions you make will be under the license indicated in the [license](LICENSE.md) -In short, when you submit code changes, your submissions are understood to be -under the same license as the rest of project. Feel free to contact the maintainers if that's a concern. - -## Report bugs using Github's [issues](https://github.com/awesome-gocui/gocui/issues) -We use GitHub issues to track public bugs. Report a bug by [opening a new -issue](https://github.com/awesome-gocui/gocui/issues/new); it's that easy! \ No newline at end of file diff --git a/vendor/github.com/jesseduffield/gocui/LICENSE b/vendor/github.com/jesseduffield/gocui/LICENSE deleted file mode 100644 index 8cb28215e45..00000000000 --- a/vendor/github.com/jesseduffield/gocui/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2014 The gocui Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the gocui Authors nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/jesseduffield/gocui/README.md b/vendor/github.com/jesseduffield/gocui/README.md deleted file mode 100644 index 3e86480bddd..00000000000 --- a/vendor/github.com/jesseduffield/gocui/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# GOCUI - Go Console User Interface -[![CircleCI](https://circleci.com/gh/awesome-gocui/gocui/tree/master.svg?style=svg)](https://circleci.com/gh/awesome-gocui/gocui/tree/master) -[![CodeCov](https://codecov.io/gh/awesome-gocui/gocui/branch/master/graph/badge.svg)](https://codecov.io/gh/awesome-gocui/gocui) -[![Go Report Card](https://goreportcard.com/badge/github.com/awesome-gocui/gocui)](https://goreportcard.com/report/github.com/awesome-gocui/gocui) -[![GolangCI](https://golangci.com/badges/github.com/awesome-gocui/gocui.svg)](https://golangci.com/badges/github.com/awesome-gocui/gocui.svg) -[![GoDoc](https://godoc.org/github.com/awesome-gocui/gocui?status.svg)](https://godoc.org/github.com/awesome-gocui/gocui) -![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/awesome-gocui/gocui.svg) - -Minimalist Go package aimed at creating Console User Interfaces. -A community fork based on the amazing work of [jroimartin](https://github.com/jroimartin/gocui) - -## Features - -* Minimalist API. -* Views (the "windows" in the GUI) implement the interface io.ReadWriter. -* Support for overlapping views. -* The GUI can be modified at runtime (concurrent-safe). -* Global and view-level keybindings. -* Mouse support. -* Colored text. -* Customizable editing mode. -* Easy to build reusable widgets, complex layouts... - -## About fork - -This fork has many improvements over the original work from [jroimartin](https://github.com/jroimartin/gocui). - -* Written ontop of TCell -* Better wide character support -* Support for 1 Line height views -* Better support for running in docker container -* Customize frame colors -* Improved code comments and quality -* Many small improvements -* Change Visibility of views - -For information about this org see: [awesome-gocui/about](https://github.com/awesome-gocui/about). - -## Installation - -Execute: - -``` -$ go get github.com/awesome-gocui/gocui -``` - -## Documentation - -Execute: - -``` -$ go doc github.com/awesome-gocui/gocui -``` - -Or visit [godoc.org](https://godoc.org/github.com/awesome-gocui/gocui) to read it -online. - -## Example -See the [_example](./_example/) folder for more examples - -```go -package main - -import ( - "fmt" - "log" - - "github.com/awesome-gocui/gocui" -) - -func main() { - g, err := gocui.NewGui(gocui.OutputNormal, true) - if err != nil { - log.Panicln(err) - } - defer g.Close() - - g.SetManagerFunc(layout) - - if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { - log.Panicln(err) - } - - if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { - log.Panicln(err) - } -} - -func layout(g *gocui.Gui) error { - maxX, maxY := g.Size() - if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0); err != nil { - if !gocui.IsUnknownView(err) { - return err - } - - if _, err := g.SetCurrentView("hello"); err != nil { - return err - } - - fmt.Fprintln(v, "Hello world!") - } - - return nil -} - -func quit(g *gocui.Gui, v *gocui.View) error { - return gocui.ErrQuit -} -``` - -## Screenshots - -![r2cui](https://cloud.githubusercontent.com/assets/1223476/19418932/63645052-93ce-11e6-867c-da5e97e37237.png) - -![_examples/demo.go](https://cloud.githubusercontent.com/assets/1223476/5992750/720b84f0-aa36-11e4-88ec-296fa3247b52.png) - -![_examples/dynamic.go](https://cloud.githubusercontent.com/assets/1223476/5992751/76ad5cc2-aa36-11e4-8204-6a90269db827.png) - -## Projects using gocui - -* [komanda-cli](https://github.com/mephux/komanda-cli): IRC Client For Developers. -* [vuls](https://github.com/future-architect/vuls): Agentless vulnerability scanner for Linux/FreeBSD. -* [wuzz](https://github.com/asciimoo/wuzz): Interactive cli tool for HTTP inspection. -* [httplab](https://github.com/gchaincl/httplab): Interactive web server. -* [domainr](https://github.com/MichaelThessel/domainr): Tool that checks the availability of domains based on keywords. -* [gotime](https://github.com/nanohard/gotime): Time tracker for projects and tasks. -* [claws](https://github.com/thehowl/claws): Interactive command line client for testing websockets. -* [terminews](http://github.com/antavelos/terminews): Terminal based RSS reader. -* [diagram](https://github.com/esimov/diagram): Tool to convert ascii arts into hand drawn diagrams. -* [pody](https://github.com/JulienBreux/pody): CLI app to manage Pods in a Kubernetes cluster. -* [kubexp](https://github.com/alitari/kubexp): Kubernetes client. -* [kcli](https://github.com/cswank/kcli): Tool for inspecting kafka topics/partitions/messages. -* [fac](https://github.com/mkchoi212/fac): git merge conflict resolver -* [jsonui](https://github.com/gulyasm/jsonui): Interactive JSON explorer for your terminal. -* [cointop](https://github.com/miguelmota/cointop): Interactive terminal based UI application for tracking cryptocurrencies. -* [lazygit](https://github.com/jesseduffield/lazygit): simple terminal UI for git commands. -* [lazydocker](https://github.com/jesseduffield/lazydocker): The lazier way to manage everything docker. - -Note: if your project is not listed here, let us know! :) diff --git a/vendor/github.com/jesseduffield/gocui/attribute.go b/vendor/github.com/jesseduffield/gocui/attribute.go deleted file mode 100644 index b6cbf39d08b..00000000000 --- a/vendor/github.com/jesseduffield/gocui/attribute.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2020 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gocui - -import "github.com/gdamore/tcell/v2" - -// Attribute affects the presentation of characters, such as color, boldness, etc. -type Attribute uint64 - -const ( - // ColorDefault is used to leave the Color unchanged from whatever system or teminal default may exist. - ColorDefault = Attribute(tcell.ColorDefault) - - // AttrIsValidColor is used to indicate the color value is actually - // valid (initialized). This is useful to permit the zero value - // to be treated as the default. - AttrIsValidColor = Attribute(tcell.ColorValid) - - // AttrIsRGBColor is used to indicate that the Attribute value is RGB value of color. - // The lower order 3 bytes are RGB. - // (It's not a color in basic ANSI range 256). - AttrIsRGBColor = Attribute(tcell.ColorIsRGB) - - // AttrColorBits is a mask where color is located in Attribute - AttrColorBits = 0xffffffffff // roughly 5 bytes, tcell uses 4 bytes and half-byte as a special flags for color (rest is reserved for future) - - // AttrStyleBits is a mask where character attributes (e.g.: bold, italic, underline) are located in Attribute - AttrStyleBits = 0xffffff0000000000 // remaining 3 bytes in the 8 bytes Attribute (tcell is not using it, so we should be fine) -) - -// Color attributes. These colors are compatible with tcell.Color type and can be expanded like: -// -// g.FgColor := gocui.Attribute(tcell.ColorLime) -const ( - ColorBlack Attribute = AttrIsValidColor + iota - ColorRed - ColorGreen - ColorYellow - ColorBlue - ColorMagenta - ColorCyan - ColorWhite -) - -// grayscale indexes (for backward compatibility with termbox-go original grayscale) -var grayscale = []tcell.Color{ - 16, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, - 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 231, -} - -// Attributes are not colors, but effects (e.g.: bold, dim) which affect the display of text. -// They can be combined. -const ( - AttrBold Attribute = 1 << (40 + iota) - AttrBlink - AttrReverse - AttrUnderline - AttrDim - AttrItalic - AttrStrikeThrough - AttrNone Attribute = 0 // Just normal text. -) - -// AttrAll represents all the text effect attributes turned on -const AttrAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim | AttrItalic - -// IsValidColor indicates if the Attribute is a valid color value (has been set). -func (a Attribute) IsValidColor() bool { - return a&AttrIsValidColor != 0 -} - -// Hex returns the color's hexadecimal RGB 24-bit value with each component -// consisting of a single byte, ala R << 16 | G << 8 | B. If the color -// is unknown or unset, -1 is returned. -// -// This function produce the same output as `tcell.Hex()` with additional -// support for `termbox-go` colors (to 256). -func (a Attribute) Hex() int32 { - if !a.IsValidColor() { - return -1 - } - tc := getTcellColor(a, OutputTrue) - return tc.Hex() -} - -// RGB returns the red, green, and blue components of the color, with -// each component represented as a value 0-255. If the color -// is unknown or unset, -1 is returned for each component. -// -// This function produce the same output as `tcell.RGB()` with additional -// support for `termbox-go` colors (to 256). -func (a Attribute) RGB() (int32, int32, int32) { - v := a.Hex() - if v < 0 { - return -1, -1, -1 - } - return (v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff -} - -// GetColor creates a Color from a color name (W3C name). A hex value may -// be supplied as a string in the format "#ffffff". -func GetColor(color string) Attribute { - return Attribute(tcell.GetColor(color)) -} - -// Get256Color creates Attribute which stores ANSI color (0-255) -func Get256Color(color int32) Attribute { - return Attribute(color) | AttrIsValidColor -} - -// GetRGBColor creates Attribute which stores RGB color. -// Color is passed as 24bit RGB value, where R << 16 | G << 8 | B -func GetRGBColor(color int32) Attribute { - return Attribute(color) | AttrIsValidColor | AttrIsRGBColor -} - -// NewRGBColor creates Attribute which stores RGB color. -func NewRGBColor(r, g, b int32) Attribute { - return Attribute(tcell.NewRGBColor(r, g, b)) -} - -// getTcellColor transform Attribute into tcell.Color -func getTcellColor(c Attribute, omode OutputMode) tcell.Color { - c = c & AttrColorBits - // Default color is 0 in tcell/v2 and was 0 in termbox-go, so we are good here - if c == ColorDefault { - return tcell.ColorDefault - } - - tc := tcell.ColorDefault - // Check if we have valid color - if c.IsValidColor() { - tc = tcell.Color(c) - } else if c > 0 && c <= 256 { - // It's not valid color, but it has value in range 1-256 - // This is old Attribute style of color from termbox-go (black=1, etc.) - // convert to tcell color (black=0|ColorValid) - tc = tcell.Color(c-1) | tcell.ColorValid - } - - switch omode { - case OutputTrue: - return tc - case OutputNormal: - tc &= tcell.Color(0xf) | tcell.ColorValid - case Output256: - tc &= tcell.Color(0xff) | tcell.ColorValid - case Output216: - tc &= tcell.Color(0xff) - if tc > 215 { - return tcell.ColorDefault - } - tc += tcell.Color(16) | tcell.ColorValid - case OutputGrayscale: - tc &= tcell.Color(0x1f) - if tc > 26 { - return tcell.ColorDefault - } - tc = grayscale[tc] | tcell.ColorValid - default: - return tcell.ColorDefault - } - return tc -} diff --git a/vendor/github.com/jesseduffield/gocui/doc.go b/vendor/github.com/jesseduffield/gocui/doc.go deleted file mode 100644 index b2f8250b162..00000000000 --- a/vendor/github.com/jesseduffield/gocui/doc.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2014 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package gocui allows to create console user interfaces. - -Create a new GUI: - - g, err := gocui.NewGui(gocui.OutputNormal, false) - if err != nil { - // handle error - } - defer g.Close() - - // Set GUI managers and key bindings - // ... - - if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { - // handle error - } - -Set GUI managers: - - g.SetManager(mgr1, mgr2) - -Managers are in charge of GUI's layout and can be used to build widgets. On -each iteration of the GUI's main loop, the Layout function of each configured -manager is executed. Managers are used to set-up and update the application's -main views, being possible to freely change them during execution. Also, it is -important to mention that a main loop iteration is executed on each reported -event (key-press, mouse event, window resize, etc). - -GUIs are composed by Views, you can think of it as buffers. Views implement the -io.ReadWriter interface, so you can just write to them if you want to modify -their content. The same is valid for reading. - -Create and initialize a view with absolute coordinates: - - if v, err := g.SetView("viewname", 2, 2, 22, 7, 0); err != nil { - if !gocui.IsUnknownView(err) { - // handle error - } - fmt.Fprintln(v, "This is a new view") - // ... - } - -Views can also be created using relative coordinates: - - maxX, maxY := g.Size() - if v, err := g.SetView("viewname", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2, 0); err != nil { - // ... - } - -Configure keybindings: - - if err := g.SetKeybinding("viewname", gocui.KeyEnter, gocui.ModNone, fcn); err != nil { - // handle error - } - -gocui implements full mouse support that can be enabled with: - - g.Mouse = true - -Mouse events are handled like any other keybinding: - - if err := g.SetKeybinding("viewname", gocui.MouseLeft, gocui.ModNone, fcn); err != nil { - // handle error - } - -IMPORTANT: Views can only be created, destroyed or updated in three ways: from -the Layout function within managers, from keybinding callbacks or via -*Gui.Update(). The reason for this is that it allows gocui to be -concurrent-safe. So, if you want to update your GUI from a goroutine, you must -use *Gui.Update(). For example: - - g.Update(func(g *gocui.Gui) error { - v, err := g.View("viewname") - if err != nil { - // handle error - } - v.Clear() - fmt.Fprintln(v, "Writing from different goroutines") - return nil - }) - -By default, gocui provides a basic editing mode. This mode can be extended -and customized creating a new Editor and assigning it to *View.Editor: - - type Editor interface { - Edit(v *View, key Key, ch rune, mod Modifier) - } - -DefaultEditor can be taken as example to create your own custom Editor: - - var DefaultEditor Editor = EditorFunc(simpleEditor) - - func simpleEditor(v *View, key Key, ch rune, mod Modifier) { - switch { - case ch != 0 && mod == 0: - v.EditWrite(ch) - case key == KeySpace: - v.EditWrite(' ') - case key == KeyBackspace || key == KeyBackspace2: - v.EditDelete(true) - // ... - } - } - -Colored text: - -Views allow to add colored text using ANSI colors. For example: - - fmt.Fprintln(v, "\x1b[0;31mHello world") - -For more information, see the examples in folder "_examples/". -*/ -package gocui diff --git a/vendor/github.com/jesseduffield/gocui/edit.go b/vendor/github.com/jesseduffield/gocui/edit.go deleted file mode 100644 index 4ced2ad0a7b..00000000000 --- a/vendor/github.com/jesseduffield/gocui/edit.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2014 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gocui - -import ( - "unicode" -) - -// Editor interface must be satisfied by gocui editors. -type Editor interface { - Edit(v *View, key Key, ch rune, mod Modifier) bool -} - -// The EditorFunc type is an adapter to allow the use of ordinary functions as -// Editors. If f is a function with the appropriate signature, EditorFunc(f) -// is an Editor object that calls f. -type EditorFunc func(v *View, key Key, ch rune, mod Modifier) bool - -// Edit calls f(v, key, ch, mod) -func (f EditorFunc) Edit(v *View, key Key, ch rune, mod Modifier) bool { - return f(v, key, ch, mod) -} - -// DefaultEditor is the default editor. -var DefaultEditor Editor = EditorFunc(SimpleEditor) - -// SimpleEditor is used as the default gocui editor. -func SimpleEditor(v *View, key Key, ch rune, mod Modifier) bool { - switch { - case key == KeyBackspace || key == KeyBackspace2: - v.TextArea.BackSpaceChar() - case key == KeyCtrlD || key == KeyDelete: - v.TextArea.DeleteChar() - case key == KeyArrowDown: - v.TextArea.MoveCursorDown() - case key == KeyArrowUp: - v.TextArea.MoveCursorUp() - case key == KeyArrowLeft && (mod&ModAlt) != 0: - v.TextArea.MoveLeftWord() - case key == KeyArrowLeft: - v.TextArea.MoveCursorLeft() - case key == KeyArrowRight && (mod&ModAlt) != 0: - v.TextArea.MoveRightWord() - case key == KeyArrowRight: - v.TextArea.MoveCursorRight() - case key == KeyEnter: - v.TextArea.TypeRune('\n') - case key == KeySpace: - v.TextArea.TypeRune(' ') - case key == KeyInsert: - v.TextArea.ToggleOverwrite() - case key == KeyCtrlU: - v.TextArea.DeleteToStartOfLine() - case key == KeyCtrlK: - v.TextArea.DeleteToEndOfLine() - case key == KeyCtrlA || key == KeyHome: - v.TextArea.GoToStartOfLine() - case key == KeyCtrlE || key == KeyEnd: - v.TextArea.GoToEndOfLine() - case key == KeyCtrlW: - v.TextArea.BackSpaceWord() - case key == KeyCtrlY: - v.TextArea.Yank() - case unicode.IsPrint(ch): - v.TextArea.TypeRune(ch) - default: - return false - } - - v.RenderTextArea() - - return true -} diff --git a/vendor/github.com/jesseduffield/gocui/escape.go b/vendor/github.com/jesseduffield/gocui/escape.go deleted file mode 100644 index f559bbb2589..00000000000 --- a/vendor/github.com/jesseduffield/gocui/escape.go +++ /dev/null @@ -1,372 +0,0 @@ -// Copyright 2014 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gocui - -import ( - "strconv" - - "github.com/go-errors/errors" -) - -type escapeInterpreter struct { - state escapeState - curch rune - csiParam []string - curFgColor, curBgColor Attribute - mode OutputMode - instruction instruction - hyperlink string -} - -type ( - escapeState int - fontEffect int -) - -type instruction interface{ isInstruction() } - -type eraseInLineFromCursor struct{} - -func (self eraseInLineFromCursor) isInstruction() {} - -type noInstruction struct{} - -func (self noInstruction) isInstruction() {} - -const ( - stateNone escapeState = iota - stateEscape - stateCSI - stateParams - stateOSC - stateOSCWaitForParams - stateOSCParams - stateOSCHyperlink - stateOSCEndEscape - stateOSCSkipUnknown - - bold fontEffect = 1 - faint fontEffect = 2 - italic fontEffect = 3 - underline fontEffect = 4 - blink fontEffect = 5 - reverse fontEffect = 7 - strike fontEffect = 9 - - setForegroundColor int = 38 - defaultForegroundColor int = 39 - setBackgroundColor int = 48 - defaultBackgroundColor int = 49 -) - -var ( - errNotCSI = errors.New("Not a CSI escape sequence") - errCSIParseError = errors.New("CSI escape sequence parsing error") - errCSITooLong = errors.New("CSI escape sequence is too long") - errOSCParseError = errors.New("OSC escape sequence parsing error") -) - -// runes in case of error will output the non-parsed runes as a string. -func (ei *escapeInterpreter) runes() []rune { - switch ei.state { - case stateNone: - return []rune{0x1b} - case stateEscape: - return []rune{0x1b, ei.curch} - case stateCSI: - return []rune{0x1b, '[', ei.curch} - case stateParams: - ret := []rune{0x1b, '['} - for _, s := range ei.csiParam { - ret = append(ret, []rune(s)...) - ret = append(ret, ';') - } - return append(ret, ei.curch) - default: - } - return nil -} - -// newEscapeInterpreter returns an escapeInterpreter that will be able to parse -// terminal escape sequences. -func newEscapeInterpreter(mode OutputMode) *escapeInterpreter { - ei := &escapeInterpreter{ - state: stateNone, - curFgColor: ColorDefault, - curBgColor: ColorDefault, - mode: mode, - instruction: noInstruction{}, - } - return ei -} - -// reset sets the escapeInterpreter in initial state. -func (ei *escapeInterpreter) reset() { - ei.state = stateNone - ei.curFgColor = ColorDefault - ei.curBgColor = ColorDefault - ei.csiParam = nil -} - -func (ei *escapeInterpreter) instructionRead() { - ei.instruction = noInstruction{} -} - -// parseOne parses a rune. If isEscape is true, it means that the rune is part -// of an escape sequence, and as such should not be printed verbatim. Otherwise, -// it's not an escape sequence. -func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) { - // Sanity checks - if len(ei.csiParam) > 20 { - return false, errCSITooLong - } - if len(ei.csiParam) > 0 && len(ei.csiParam[len(ei.csiParam)-1]) > 255 { - return false, errCSITooLong - } - - ei.curch = ch - - switch ei.state { - case stateNone: - if ch == 0x1b { - ei.state = stateEscape - return true, nil - } - return false, nil - case stateEscape: - switch ch { - case '[': - ei.state = stateCSI - return true, nil - case ']': - ei.state = stateOSC - return true, nil - default: - return false, errNotCSI - } - case stateCSI: - switch { - case ch >= '0' && ch <= '9': - ei.csiParam = append(ei.csiParam, "") - case ch == 'm': - ei.csiParam = append(ei.csiParam, "0") - case ch == 'K': - // fall through - default: - return false, errCSIParseError - } - ei.state = stateParams - fallthrough - case stateParams: - switch { - case ch >= '0' && ch <= '9': - ei.csiParam[len(ei.csiParam)-1] += string(ch) - return true, nil - case ch == ';': - ei.csiParam = append(ei.csiParam, "") - return true, nil - case ch == 'm': - if err := ei.outputCSI(); err != nil { - return false, errCSIParseError - } - - ei.state = stateNone - ei.csiParam = nil - return true, nil - case ch == 'K': - p := 0 - if len(ei.csiParam) != 0 && ei.csiParam[0] != "" { - p, err = strconv.Atoi(ei.csiParam[0]) - if err != nil { - return false, errCSIParseError - } - } - - if p == 0 { - ei.instruction = eraseInLineFromCursor{} - } else { - // non-zero values of P not supported - ei.instruction = noInstruction{} - } - - ei.state = stateNone - ei.csiParam = nil - return true, nil - default: - return false, errCSIParseError - } - case stateOSC: - if ch == '8' { - ei.state = stateOSCWaitForParams - ei.hyperlink = "" - return true, nil - } - - ei.state = stateOSCSkipUnknown - return true, nil - case stateOSCWaitForParams: - if ch != ';' { - return true, errOSCParseError - } - - ei.state = stateOSCParams - return true, nil - case stateOSCParams: - if ch == ';' { - ei.state = stateOSCHyperlink - } - return true, nil - case stateOSCHyperlink: - switch ch { - case 0x07: - ei.state = stateNone - case 0x1b: - ei.state = stateOSCEndEscape - default: - ei.hyperlink += string(ch) - } - return true, nil - case stateOSCEndEscape: - ei.state = stateNone - return true, nil - case stateOSCSkipUnknown: - switch ch { - case 0x07: - ei.state = stateNone - case 0x1b: - ei.state = stateOSCEndEscape - } - return true, nil - } - return false, nil -} - -func (ei *escapeInterpreter) outputCSI() error { - n := len(ei.csiParam) - for i := 0; i < n; { - p, err := strconv.Atoi(ei.csiParam[i]) - if err != nil { - return errCSIParseError - } - - skip := 1 - switch { - case p == 0: // reset style and color - ei.curFgColor = ColorDefault - ei.curBgColor = ColorDefault - case p >= 1 && p <= 9: // set style - ei.curFgColor |= getFontEffect(p) - case p >= 21 && p <= 29: // reset style - ei.curFgColor &= ^getFontEffect(p - 20) - case p >= 30 && p <= 37: // set foreground color - ei.curFgColor &= AttrStyleBits - ei.curFgColor |= Get256Color(int32(p) - 30) - case p == setForegroundColor: // set foreground color (256-color or true color) - var color Attribute - var err error - color, skip, err = ei.csiColor(ei.csiParam[i:]) - if err != nil { - return err - } - ei.curFgColor &= AttrStyleBits - ei.curFgColor |= color - case p == defaultForegroundColor: // reset foreground color - ei.curFgColor &= AttrStyleBits - ei.curFgColor |= ColorDefault - case p >= 40 && p <= 47: // set background color - ei.curBgColor &= AttrStyleBits - ei.curBgColor |= Get256Color(int32(p) - 40) - case p == setBackgroundColor: // set background color (256-color or true color) - var color Attribute - var err error - color, skip, err = ei.csiColor(ei.csiParam[i:]) - if err != nil { - return err - } - ei.curBgColor &= AttrStyleBits - ei.curBgColor |= color - case p == defaultBackgroundColor: // reset background color - ei.curBgColor &= AttrStyleBits - ei.curBgColor |= ColorDefault - case p >= 90 && p <= 97: // set bright foreground color - ei.curFgColor &= AttrStyleBits - ei.curFgColor |= Get256Color(int32(p) - 90 + 8) - case p >= 100 && p <= 107: // set bright background color - ei.curBgColor &= AttrStyleBits - ei.curBgColor |= Get256Color(int32(p) - 100 + 8) - default: - } - i += skip - } - - return nil -} - -func (ei *escapeInterpreter) csiColor(param []string) (color Attribute, skip int, err error) { - if len(param) < 2 { - return 0, 0, errCSIParseError - } - - switch param[1] { - case "2": - // 24-bit color - if ei.mode < OutputTrue { - return 0, 0, errCSIParseError - } - if len(param) < 5 { - return 0, 0, errCSIParseError - } - var red, green, blue int - red, err = strconv.Atoi(param[2]) - if err != nil { - return 0, 0, errCSIParseError - } - green, err = strconv.Atoi(param[3]) - if err != nil { - return 0, 0, errCSIParseError - } - blue, err = strconv.Atoi(param[4]) - if err != nil { - return 0, 0, errCSIParseError - } - return NewRGBColor(int32(red), int32(green), int32(blue)), 5, nil - case "5": - // 8-bit color - if ei.mode < Output256 { - return 0, 0, errCSIParseError - } - if len(param) < 3 { - return 0, 0, errCSIParseError - } - var hex int - hex, err = strconv.Atoi(param[2]) - if err != nil { - return 0, 0, errCSIParseError - } - return Get256Color(int32(hex)), 3, nil - default: - return 0, 0, errCSIParseError - } -} - -func getFontEffect(f int) Attribute { - switch fontEffect(f) { - case bold: - return AttrBold - case faint: - return AttrDim - case italic: - return AttrItalic - case underline: - return AttrUnderline - case blink: - return AttrBlink - case reverse: - return AttrReverse - case strike: - return AttrStrikeThrough - } - return AttrNone -} diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go deleted file mode 100644 index fac31d83683..00000000000 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ /dev/null @@ -1,1717 +0,0 @@ -// Copyright 2014 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gocui - -import ( - "context" - standardErrors "errors" - "runtime" - "strings" - "sync" - "time" - - "github.com/gdamore/tcell/v2" - "github.com/go-errors/errors" - "github.com/mattn/go-runewidth" -) - -// OutputMode represents an output mode, which determines how colors -// are used. -type OutputMode int - -const DOUBLE_CLICK_THRESHOLD = 500 * time.Millisecond - -var ( - // ErrAlreadyBlacklisted is returned when the keybinding is already blacklisted. - ErrAlreadyBlacklisted = standardErrors.New("keybind already blacklisted") - - // ErrBlacklisted is returned when the keybinding being parsed / used is blacklisted. - ErrBlacklisted = standardErrors.New("keybind blacklisted") - - // ErrNotBlacklisted is returned when a keybinding being whitelisted is not blacklisted. - ErrNotBlacklisted = standardErrors.New("keybind not blacklisted") - - // ErrNoSuchKeybind is returned when the keybinding being parsed does not exist. - ErrNoSuchKeybind = standardErrors.New("no such keybind") - - // ErrUnknownView allows to assert if a View must be initialized. - ErrUnknownView = standardErrors.New("unknown view") - - // ErrQuit is used to decide if the MainLoop finished successfully. - ErrQuit = standardErrors.New("quit") - - // ErrKeybindingNotHandled is returned when a keybinding is not handled, so that the key can be dispatched further - ErrKeybindingNotHandled = standardErrors.New("keybinding not handled") -) - -const ( - // OutputNormal provides 8-colors terminal mode. - OutputNormal OutputMode = iota - - // Output256 provides 256-colors terminal mode. - Output256 - - // Output216 provides 216 ansi color terminal mode. - Output216 - - // OutputGrayscale provides greyscale terminal mode. - OutputGrayscale - - // OutputTrue provides 24bit color terminal mode. - // This mode is recommended even if your terminal doesn't support - // such mode. The colors are represented exactly as you - // write them (no clamping or truncating). `tcell` should take care - // of what your terminal can do. - OutputTrue -) - -type tabClickHandler func(int) error - -type tabClickBinding struct { - viewName string - handler tabClickHandler -} - -// TODO: would be good to define inbound and outbound click handlers e.g. -// clicking on a file is an inbound thing where we don't care what context you're -// in when it happens, whereas clicking on the main view from the files view is an -// outbound click with a specific handler. But this requires more thinking about -// where handlers should live. -type ViewMouseBinding struct { - // the view that is clicked - ViewName string - - // the view that has focus when the click occurs. - FocusedView string - - Handler func(ViewMouseBindingOpts) error - - Modifier Modifier - - // must be a mouse key - Key Key -} - -type ViewMouseBindingOpts struct { - X int // i.e. origin x + cursor x - Y int // i.e. origin y + cursor y - - Key Key // which button was clicked (will be one of the Mouse* constants) - - IsDoubleClick bool // true if this is a double click -} - -type GuiMutexes struct { - // tickingMutex ensures we don't have two loops ticking. The point of 'ticking' - // is to refresh the gui rapidly so that loader characters can be animated. - tickingMutex sync.Mutex - - ViewsMutex sync.Mutex -} - -type replayedEvents struct { - Keys chan *TcellKeyEventWrapper - Resizes chan *TcellResizeEventWrapper - MouseEvents chan *TcellMouseEventWrapper -} - -type RecordingConfig struct { - Speed float64 - Leeway int -} - -type clickInfo struct { - x int - y int - key Key - viewName string - time time.Time -} - -// Gui represents the whole User Interface, including the views, layouts -// and keybindings. -type Gui struct { - RecordingConfig - // ReplayedEvents is for passing pre-recorded input events, for the purposes of testing - ReplayedEvents replayedEvents - playRecording bool - - tabClickBindings []*tabClickBinding - viewMouseBindings []*ViewMouseBinding - lastClick *clickInfo - gEvents chan GocuiEvent - userEvents chan userEvent - views []*View - currentView *View - managers []Manager - keybindings []*keybinding - focusHandler func(bool) error - openHyperlink func(string, string) error - maxX, maxY int - outputMode OutputMode - stop chan struct{} - blacklist []Key - - // BgColor and FgColor allow to configure the background and foreground - // colors of the GUI. - BgColor, FgColor, FrameColor Attribute - - // SelBgColor and SelFgColor allow to configure the background and - // foreground colors of the frame of the current view. - SelBgColor, SelFgColor, SelFrameColor Attribute - - // If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the - // frame of the current view. - Highlight bool - - // If ShowListFooter is true then show list footer (i.e. the part that says we're at item 5 out of 10) - ShowListFooter bool - - // If Cursor is true then the cursor is enabled. - Cursor bool - - // If Mouse is true then mouse events will be enabled. - Mouse bool - - IsPasting bool - - // If InputEsc is true, when ESC sequence is in the buffer and it doesn't - // match any known sequence, ESC means KeyEsc. - InputEsc bool - - // SupportOverlaps is true when we allow for view edges to overlap with other - // view edges - SupportOverlaps bool - - Mutexes GuiMutexes - - OnSearchEscape func() error - // these keys must either be of type Key of rune - SearchEscapeKey interface{} - NextSearchMatchKey interface{} - PrevSearchMatchKey interface{} - - ErrorHandler func(error) error - - screen tcell.Screen - suspendedMutex sync.Mutex - suspended bool - - taskManager *TaskManager - - lastHoverView *View -} - -type NewGuiOpts struct { - OutputMode OutputMode - SupportOverlaps bool - PlayRecording bool - Headless bool - // only applicable when Headless is true - Width int - // only applicable when Headless is true - Height int - - RuneReplacements map[rune]string -} - -// NewGui returns a new Gui object with a given output mode. -func NewGui(opts NewGuiOpts) (*Gui, error) { - g := &Gui{} - - var err error - if opts.Headless { - err = g.tcellInitSimulation(opts.Width, opts.Height) - } else { - err = g.tcellInit(runeReplacements) - } - if err != nil { - return nil, err - } - - if opts.Headless || runtime.GOOS == "windows" { - g.maxX, g.maxY = g.screen.Size() - } else { - // TODO: find out if we actually need this bespoke logic for linux - g.maxX, g.maxY, err = g.getTermWindowSize() - if err != nil { - return nil, err - } - } - - g.outputMode = opts.OutputMode - - g.stop = make(chan struct{}) - - g.gEvents = make(chan GocuiEvent, 20) - g.userEvents = make(chan userEvent, 20) - g.taskManager = newTaskManager() - - if opts.PlayRecording { - g.ReplayedEvents = replayedEvents{ - Keys: make(chan *TcellKeyEventWrapper), - Resizes: make(chan *TcellResizeEventWrapper), - MouseEvents: make(chan *TcellMouseEventWrapper), - } - } - - g.BgColor, g.FgColor, g.FrameColor = ColorDefault, ColorDefault, ColorDefault - g.SelBgColor, g.SelFgColor, g.SelFrameColor = ColorDefault, ColorDefault, ColorDefault - - // SupportOverlaps is true when we allow for view edges to overlap with other - // view edges - g.SupportOverlaps = opts.SupportOverlaps - - // default keys for when searching strings in a view - g.SearchEscapeKey = KeyEsc - g.NextSearchMatchKey = 'n' - g.PrevSearchMatchKey = 'N' - - g.playRecording = opts.PlayRecording - - return g, nil -} - -func (g *Gui) NewTask() *TaskImpl { - return g.taskManager.NewTask() -} - -// An idle listener listens for when the program is idle. This is useful for -// integration tests which can wait for the program to be idle before taking -// the next step in the test. -func (g *Gui) AddIdleListener(c chan struct{}) { - g.taskManager.addIdleListener(c) -} - -// Close finalizes the library. It should be called after a successful -// initialization and when gocui is not needed anymore. -func (g *Gui) Close() { - close(g.stop) - Screen.Fini() -} - -// Size returns the terminal's size. -func (g *Gui) Size() (x, y int) { - return g.maxX, g.maxY -} - -// SetRune writes a rune at the given point, relative to the top-left -// corner of the terminal. It checks if the position is valid and applies -// the given colors. -func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error { - if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { - // swallowing error because it's not that big of a deal - return nil - } - tcellSetCell(x, y, ch, fgColor, bgColor, g.outputMode) - return nil -} - -// Rune returns the rune contained in the cell at the given position. -// It checks if the position is valid. -func (g *Gui) Rune(x, y int) (rune, error) { - if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { - return ' ', errors.New("invalid point") - } - c, _, _, _ := Screen.GetContent(x, y) - return c, nil -} - -// SetView creates a new view with its top-left corner at (x0, y0) -// and the bottom-right one at (x1, y1). If a view with the same name -// already exists, its dimensions are updated; otherwise, the error -// ErrUnknownView is returned, which allows to assert if the View must -// be initialized. It checks if the position is valid. -func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, error) { - if name == "" { - return nil, errors.New("invalid name") - } - - if v, err := g.View(name); err == nil { - sizeChanged := v.x0 != x0 || v.x1 != x1 || v.y0 != y0 || v.y1 != y1 - - v.x0 = x0 - v.y0 = y0 - v.x1 = x1 - v.y1 = y1 - - if sizeChanged { - v.clearViewLines() - - if v.Editable { - cursorX, cursorY := v.TextArea.GetCursorXY() - newViewCursorX, newOriginX := updatedCursorAndOrigin(0, v.InnerWidth(), cursorX) - newViewCursorY, newOriginY := updatedCursorAndOrigin(0, v.InnerHeight(), cursorY) - - v.SetCursor(newViewCursorX, newViewCursorY) - v.SetOrigin(newOriginX, newOriginY) - } - } - - return v, nil - } - - g.Mutexes.ViewsMutex.Lock() - - v := NewView(name, x0, y0, x1, y1, g.outputMode) - v.BgColor, v.FgColor = g.BgColor, g.FgColor - v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor - v.Overlaps = overlaps - g.views = append(g.views, v) - - g.Mutexes.ViewsMutex.Unlock() - - return v, errors.Wrap(ErrUnknownView, 0) -} - -// SetViewBeneath sets a view stacked beneath another view -func (g *Gui) SetViewBeneath(name string, aboveViewName string, height int) (*View, error) { - aboveView, err := g.View(aboveViewName) - if err != nil { - return nil, err - } - - viewTop := aboveView.y1 + 1 - return g.SetView(name, aboveView.x0, viewTop, aboveView.x1, viewTop+height-1, 0) -} - -// SetViewOnTop sets the given view on top of the existing ones. -func (g *Gui) SetViewOnTop(name string) (*View, error) { - g.Mutexes.ViewsMutex.Lock() - defer g.Mutexes.ViewsMutex.Unlock() - - for i, v := range g.views { - if v.name == name { - s := append(g.views[:i], g.views[i+1:]...) - g.views = append(s, v) - return v, nil - } - } - return nil, errors.Wrap(ErrUnknownView, 0) -} - -// SetViewOnBottom sets the given view on bottom of the existing ones. -func (g *Gui) SetViewOnBottom(name string) (*View, error) { - g.Mutexes.ViewsMutex.Lock() - defer g.Mutexes.ViewsMutex.Unlock() - - for i, v := range g.views { - if v.name == name { - s := append(g.views[:i], g.views[i+1:]...) - g.views = append([]*View{v}, s...) - return v, nil - } - } - return nil, errors.Wrap(ErrUnknownView, 0) -} - -func (g *Gui) SetViewOnTopOf(toMove string, other string) error { - g.Mutexes.ViewsMutex.Lock() - defer g.Mutexes.ViewsMutex.Unlock() - - if toMove == other { - return nil - } - - // need to find the two current positions and then move toMove before other in the list. - toMoveIndex := -1 - otherIndex := -1 - - for i, v := range g.views { - if v.name == toMove { - toMoveIndex = i - } - - if v.name == other { - otherIndex = i - } - } - - if toMoveIndex == -1 || otherIndex == -1 { - return errors.Wrap(ErrUnknownView, 0) - } - - // already on top - if toMoveIndex > otherIndex { - return nil - } - - // need to actually do it the other way around. Last is highest - viewToMove := g.views[toMoveIndex] - - g.views = append(g.views[:toMoveIndex], g.views[toMoveIndex+1:]...) - g.views = append(g.views[:otherIndex], append([]*View{viewToMove}, g.views[otherIndex:]...)...) - return nil -} - -// replaces the content in toView with the content in fromView -func (g *Gui) CopyContent(fromView *View, toView *View) { - g.Mutexes.ViewsMutex.Lock() - defer g.Mutexes.ViewsMutex.Unlock() - - toView.CopyContent(fromView) -} - -// Views returns all the views in the GUI. -func (g *Gui) Views() []*View { - return g.views -} - -// View returns a pointer to the view with the given name, or error -// ErrUnknownView if a view with that name does not exist. -func (g *Gui) View(name string) (*View, error) { - g.Mutexes.ViewsMutex.Lock() - defer g.Mutexes.ViewsMutex.Unlock() - - for _, v := range g.views { - if v.name == name { - return v, nil - } - } - return nil, errors.Wrap(ErrUnknownView, 0) -} - -// VisibleViewByPosition returns a pointer to a view matching the given position, or -// error ErrUnknownView if a view in that position does not exist. -func (g *Gui) VisibleViewByPosition(x, y int) (*View, error) { - g.Mutexes.ViewsMutex.Lock() - defer g.Mutexes.ViewsMutex.Unlock() - - // traverse views in reverse order checking top views first - for i := len(g.views); i > 0; i-- { - v := g.views[i-1] - - if !v.Visible { - continue - } - - frameOffset := 0 - if v.Frame { - frameOffset = 1 - } - if x > v.x0-frameOffset && x < v.x1+frameOffset && y > v.y0-frameOffset && y < v.y1+frameOffset { - return v, nil - } - } - return nil, errors.Wrap(ErrUnknownView, 0) -} - -// ViewPosition returns the coordinates of the view with the given name, or -// error ErrUnknownView if a view with that name does not exist. -func (g *Gui) ViewPosition(name string) (x0, y0, x1, y1 int, err error) { - g.Mutexes.ViewsMutex.Lock() - defer g.Mutexes.ViewsMutex.Unlock() - - for _, v := range g.views { - if v.name == name { - return v.x0, v.y0, v.x1, v.y1, nil - } - } - return 0, 0, 0, 0, errors.Wrap(ErrUnknownView, 0) -} - -// DeleteView deletes a view by name. -func (g *Gui) DeleteView(name string) error { - g.Mutexes.ViewsMutex.Lock() - defer g.Mutexes.ViewsMutex.Unlock() - - for i, v := range g.views { - if v.name == name { - g.views = append(g.views[:i], g.views[i+1:]...) - return nil - } - } - return errors.Wrap(ErrUnknownView, 0) -} - -// SetCurrentView gives the focus to a given view. -func (g *Gui) SetCurrentView(name string) (*View, error) { - g.Mutexes.ViewsMutex.Lock() - defer g.Mutexes.ViewsMutex.Unlock() - - for _, v := range g.views { - if v.name == name { - g.currentView = v - return v, nil - } - } - return nil, errors.Wrap(ErrUnknownView, 0) -} - -// CurrentView returns the currently focused view, or nil if no view -// owns the focus. -func (g *Gui) CurrentView() *View { - return g.currentView -} - -// SetKeybinding creates a new keybinding. If viewname equals to "" -// (empty string) then the keybinding will apply to all views. key must -// be a rune or a Key. -// -// When mouse keys are used (MouseLeft, MouseRight, ...), modifier might not work correctly. -// It behaves differently on different platforms. Somewhere it doesn't register Alt key press, -// on others it might report Ctrl as Alt. It's not consistent and therefore it's not recommended -// to use with mouse keys. -func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error { - var kb *keybinding - - k, ch, err := getKey(key) - if err != nil { - return err - } - - if g.isBlacklisted(k) { - return ErrBlacklisted - } - - kb = newKeybinding(viewname, k, ch, mod, handler) - g.keybindings = append(g.keybindings, kb) - return nil -} - -// DeleteKeybinding deletes a keybinding. -func (g *Gui) DeleteKeybinding(viewname string, key interface{}, mod Modifier) error { - k, ch, err := getKey(key) - if err != nil { - return err - } - - for i, kb := range g.keybindings { - if kb.viewName == viewname && kb.ch == ch && kb.key == k && kb.mod == mod { - g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...) - return nil - } - } - return errors.New("keybinding not found") -} - -// DeleteKeybindings deletes all keybindings of view. -func (g *Gui) DeleteAllKeybindings() { - g.keybindings = []*keybinding{} - g.tabClickBindings = []*tabClickBinding{} - g.viewMouseBindings = []*ViewMouseBinding{} -} - -// DeleteKeybindings deletes all keybindings of view. -func (g *Gui) DeleteViewKeybindings(viewname string) { - var s []*keybinding - for _, kb := range g.keybindings { - if kb.viewName != viewname { - s = append(s, kb) - } - } - g.keybindings = s -} - -// SetTabClickBinding sets a binding for a tab click event -func (g *Gui) SetTabClickBinding(viewName string, handler tabClickHandler) error { - g.tabClickBindings = append(g.tabClickBindings, &tabClickBinding{ - viewName: viewName, - handler: handler, - }) - - return nil -} - -func (g *Gui) SetViewClickBinding(binding *ViewMouseBinding) error { - g.viewMouseBindings = append(g.viewMouseBindings, binding) - - return nil -} - -// BlackListKeybinding adds a keybinding to the blacklist -func (g *Gui) BlacklistKeybinding(k Key) error { - for _, j := range g.blacklist { - if j == k { - return ErrAlreadyBlacklisted - } - } - g.blacklist = append(g.blacklist, k) - return nil -} - -// WhiteListKeybinding removes a keybinding from the blacklist -func (g *Gui) WhitelistKeybinding(k Key) error { - for i, j := range g.blacklist { - if j == k { - g.blacklist = append(g.blacklist[:i], g.blacklist[i+1:]...) - return nil - } - } - return ErrNotBlacklisted -} - -func (g *Gui) SetFocusHandler(handler func(bool) error) { - g.focusHandler = handler -} - -func (g *Gui) SetOpenHyperlinkFunc(openHyperlinkFunc func(string, string) error) { - g.openHyperlink = openHyperlinkFunc -} - -// getKey takes an empty interface with a key and returns the corresponding -// typed Key or rune. -func getKey(key interface{}) (Key, rune, error) { - switch t := key.(type) { - case nil: // Ignore keybinding if `nil` - return 0, 0, nil - case Key: - return t, 0, nil - case rune: - return 0, t, nil - default: - return 0, 0, errors.New("unknown type") - } -} - -// userEvent represents an event triggered by the user. -type userEvent struct { - f func(*Gui) error - task Task -} - -// Update executes the passed function. This method can be called safely from a -// goroutine in order to update the GUI. It is important to note that the -// passed function won't be executed immediately, instead it will be added to -// the user events queue. Given that Update spawns a goroutine, the order in -// which the user events will be handled is not guaranteed. -func (g *Gui) Update(f func(*Gui) error) { - task := g.NewTask() - - go g.updateAsyncAux(f, task) -} - -// UpdateAsync is a version of Update that does not spawn a go routine, it can -// be a bit more efficient in cases where Update is called many times like when -// tailing a file. In general you should use Update() -func (g *Gui) UpdateAsync(f func(*Gui) error) { - task := g.NewTask() - - g.updateAsyncAux(f, task) -} - -func (g *Gui) updateAsyncAux(f func(*Gui) error, task Task) { - g.userEvents <- userEvent{f: f, task: task} -} - -// Calls a function in a goroutine. Handles panics gracefully and tracks -// number of background tasks. -// Always use this when you want to spawn a goroutine and you want lazygit to -// consider itself 'busy` as it runs the code. Don't use for long-running -// background goroutines where you wouldn't want lazygit to be considered busy -// (i.e. when you wouldn't want a loader to be shown to the user) -func (g *Gui) OnWorker(f func(Task) error) { - task := g.NewTask() - go func() { - g.onWorkerAux(f, task) - task.Done() - }() -} - -func (g *Gui) onWorkerAux(f func(Task) error, task Task) { - panicking := true - defer func() { - if panicking && Screen != nil { - Screen.Fini() - } - }() - - err := f(task) - - panicking = false - - if err != nil { - g.Update(func(g *Gui) error { - return err - }) - } -} - -// A Manager is in charge of GUI's layout and can be used to build widgets. -type Manager interface { - // Layout is called every time the GUI is redrawn, it must contain the - // base views and its initializations. - Layout(*Gui) error -} - -// The ManagerFunc type is an adapter to allow the use of ordinary functions as -// Managers. If f is a function with the appropriate signature, ManagerFunc(f) -// is an Manager object that calls f. -type ManagerFunc func(*Gui) error - -// Layout calls f(g) -func (f ManagerFunc) Layout(g *Gui) error { - return f(g) -} - -// SetManager sets the given GUI managers. It deletes all views and -// keybindings. -func (g *Gui) SetManager(managers ...Manager) { - g.managers = managers - g.currentView = nil - g.views = nil - g.keybindings = nil - g.tabClickBindings = nil - - go func() { g.gEvents <- GocuiEvent{Type: eventResize} }() -} - -// SetManagerFunc sets the given manager function. It deletes all views and -// keybindings. -func (g *Gui) SetManagerFunc(manager func(*Gui) error) { - g.SetManager(ManagerFunc(manager)) -} - -// MainLoop runs the main loop until an error is returned. A successful -// finish should return ErrQuit. -func (g *Gui) MainLoop() error { - go func() { - for { - select { - case <-g.stop: - return - default: - g.gEvents <- g.pollEvent() - } - } - }() - - Screen.EnableFocus() - Screen.EnablePaste() - - previousEnableMouse := false - for { - if g.Mouse != previousEnableMouse { - if g.Mouse { - Screen.EnableMouse() - } else { - Screen.DisableMouse() - } - - previousEnableMouse = g.Mouse - } - - err := g.processEvent() - if err != nil { - return err - } - } -} - -func (g *Gui) handleError(err error) error { - if err != nil && !standardErrors.Is(err, ErrQuit) && g.ErrorHandler != nil { - return g.ErrorHandler(err) - } - - return err -} - -func (g *Gui) processEvent() error { - select { - case ev := <-g.gEvents: - task := g.NewTask() - defer func() { task.Done() }() - - if err := g.handleError(g.handleEvent(&ev)); err != nil { - return err - } - case ev := <-g.userEvents: - defer func() { ev.task.Done() }() - - if err := g.handleError(ev.f(g)); err != nil { - return err - } - } - - if err := g.processRemainingEvents(); err != nil { - return err - } - if err := g.flush(); err != nil { - return err - } - - return nil -} - -// processRemainingEvents handles the remaining events in the events pool. -func (g *Gui) processRemainingEvents() error { - for { - select { - case ev := <-g.gEvents: - if err := g.handleError(g.handleEvent(&ev)); err != nil { - return err - } - case ev := <-g.userEvents: - err := g.handleError(ev.f(g)) - ev.task.Done() - if err != nil { - return err - } - default: - return nil - } - } -} - -// handleEvent handles an event, based on its type (key-press, error, -// etc.) -func (g *Gui) handleEvent(ev *GocuiEvent) error { - switch ev.Type { - case eventKey, eventMouse, eventMouseMove: - return g.onKey(ev) - case eventError: - return ev.Err - case eventResize: - g.onResize() - return nil - case eventFocus: - return g.onFocus(ev) - case eventPaste: - g.IsPasting = ev.Start - return nil - default: - return nil - } -} - -func (g *Gui) onResize() { - // not sure if we actually need this - // g.screen.Sync() -} - -// drawFrameEdges draws the horizontal and vertical edges of a view. -func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error { - runeH, runeV := '─', '│' - if len(v.FrameRunes) >= 2 { - runeH, runeV = v.FrameRunes[0], v.FrameRunes[1] - } - - for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ { - if x < 0 { - continue - } - if v.y0 > -1 && v.y0 < g.maxY { - if err := g.SetRune(x, v.y0, runeH, fgColor, bgColor); err != nil { - return err - } - } - if v.y1 > -1 && v.y1 < g.maxY { - if err := g.SetRune(x, v.y1, runeH, fgColor, bgColor); err != nil { - return err - } - } - } - - showScrollbar, realScrollbarStart, realScrollbarEnd := calcRealScrollbarStartEnd(v) - for y := v.y0 + 1; y < v.y1 && y < g.maxY; y++ { - if y < 0 { - continue - } - if v.x0 > -1 && v.x0 < g.maxX { - if err := g.SetRune(v.x0, y, runeV, fgColor, bgColor); err != nil { - return err - } - } - if v.x1 > -1 && v.x1 < g.maxX { - runeToPrint := calcScrollbarRune(showScrollbar, realScrollbarStart, realScrollbarEnd, y, runeV) - - if err := g.SetRune(v.x1, y, runeToPrint, fgColor, bgColor); err != nil { - return err - } - } - } - return nil -} - -func calcScrollbarRune( - showScrollbar bool, scrollbarStart int, scrollbarEnd int, position int, runeV rune, -) rune { - if showScrollbar && (position >= scrollbarStart && position <= scrollbarEnd) { - return '▐' - } else { - return runeV - } -} - -func calcRealScrollbarStartEnd(v *View) (bool, int, int) { - height := v.InnerHeight() - fullHeight := v.ViewLinesHeight() - v.scrollMargin() - - if v.CanScrollPastBottom { - fullHeight += height - } - - if height < 2 || height >= fullHeight { - return false, 0, 0 - } - - originY := v.OriginY() - scrollbarStart, scrollbarHeight := calcScrollbar(fullHeight, height, originY, height-1) - top := v.y0 + 1 - realScrollbarStart := top + scrollbarStart - realScrollbarEnd := realScrollbarStart + scrollbarHeight - - return true, realScrollbarStart, realScrollbarEnd -} - -func cornerRune(index byte) rune { - return []rune{' ', '│', '│', '│', '─', '┘', '┐', '┤', '─', '└', '┌', '├', '├', '┴', '┬', '┼'}[index] -} - -// cornerCustomRune returns rune from `v.FrameRunes` slice. If the length of slice is less than 11 -// all the missing runes will be translated to the default `cornerRune()` -func cornerCustomRune(v *View, index byte) rune { - // Translate `cornerRune()` index - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - // ' ', '│', '│', '│', '─', '┘', '┐', '┤', '─', '└', '┌', '├', '├', '┴', '┬', '┼' - // into `FrameRunes` index - // 0 1 2 3 4 5 6 7 8 9 10 - // '─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼' - switch index { - case 1, 2, 3: - return v.FrameRunes[1] - case 4, 8: - return v.FrameRunes[0] - case 5: - return v.FrameRunes[5] - case 6: - return v.FrameRunes[3] - case 7: - if len(v.FrameRunes) < 8 { - break - } - return v.FrameRunes[7] - case 9: - return v.FrameRunes[4] - case 10: - return v.FrameRunes[2] - case 11, 12: - if len(v.FrameRunes) < 7 { - break - } - return v.FrameRunes[6] - case 13: - if len(v.FrameRunes) < 10 { - break - } - return v.FrameRunes[9] - case 14: - if len(v.FrameRunes) < 9 { - break - } - return v.FrameRunes[8] - case 15: - if len(v.FrameRunes) < 11 { - break - } - return v.FrameRunes[10] - default: - return ' ' // cornerRune(0) - } - return cornerRune(index) -} - -func corner(v *View, directions byte) rune { - index := v.Overlaps | directions - if len(v.FrameRunes) >= 6 { - return cornerCustomRune(v, index) - } - return cornerRune(index) -} - -// drawFrameCorners draws the corners of the view. -func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error { - if v.y0 == v.y1 { - if !g.SupportOverlaps && v.x0 >= 0 && v.x1 >= 0 && v.y0 >= 0 && v.x0 < g.maxX && v.x1 < g.maxX && v.y0 < g.maxY { - if err := g.SetRune(v.x0, v.y0, '╶', fgColor, bgColor); err != nil { - return err - } - if err := g.SetRune(v.x1, v.y0, '╴', fgColor, bgColor); err != nil { - return err - } - } - return nil - } - - runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘' - if len(v.FrameRunes) >= 6 { - runeTL, runeTR, runeBL, runeBR = v.FrameRunes[2], v.FrameRunes[3], v.FrameRunes[4], v.FrameRunes[5] - } - if g.SupportOverlaps { - runeTL = corner(v, BOTTOM|RIGHT) - runeTR = corner(v, BOTTOM|LEFT) - runeBL = corner(v, TOP|RIGHT) - runeBR = corner(v, TOP|LEFT) - } - - corners := []struct { - x, y int - ch rune - }{{v.x0, v.y0, runeTL}, {v.x1, v.y0, runeTR}, {v.x0, v.y1, runeBL}, {v.x1, v.y1, runeBR}} - - for _, c := range corners { - if c.x >= 0 && c.y >= 0 && c.x < g.maxX && c.y < g.maxY { - if err := g.SetRune(c.x, c.y, c.ch, fgColor, bgColor); err != nil { - return err - } - } - } - return nil -} - -// drawTitle draws the title of the view. -func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error { - if v.y0 < 0 || v.y0 >= g.maxY { - return nil - } - - tabs := v.Tabs - prefix := v.TitlePrefix - if prefix != "" { - if len(v.FrameRunes) > 0 { - prefix += string(v.FrameRunes[0]) - } else { - prefix += "─" - } - } - separator := " - " - charIndex := 0 - currentTabStart := -1 - currentTabEnd := -1 - if len(tabs) == 0 { - tabs = []string{v.Title} - } else { - for i, tab := range tabs { - if i == v.TabIndex { - currentTabStart = charIndex - currentTabEnd = charIndex + len(tab) - break - } - charIndex += len(tab) - if i < len(tabs)-1 { - charIndex += len(separator) - } - } - } - - str := strings.Join(tabs, separator) - - x := v.x0 + 2 - for _, ch := range prefix { - if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil { - return err - } - x += runewidth.RuneWidth(ch) - } - for i, ch := range str { - if x < 0 { - continue - } else if x > v.x1-2 || x >= g.maxX { - break - } - currentFgColor := fgColor - currentBgColor := bgColor - // if you are the current view and you have multiple tabs, de-highlight the non-selected tabs - if v == g.currentView && len(v.Tabs) > 0 { - currentFgColor = v.FgColor - currentBgColor = v.BgColor - } - - if i >= currentTabStart && i <= currentTabEnd { - currentFgColor = v.SelFgColor - if v != g.currentView { - currentFgColor &= ^AttrBold - } - } - if err := g.SetRune(x, v.y0, ch, currentFgColor, currentBgColor); err != nil { - return err - } - x += runewidth.RuneWidth(ch) - } - return nil -} - -// drawSubtitle draws the subtitle of the view. -func (g *Gui) drawSubtitle(v *View, fgColor, bgColor Attribute) error { - if v.y0 < 0 || v.y0 >= g.maxY { - return nil - } - - start := v.x1 - 5 - runewidth.StringWidth(v.Subtitle) - if start < v.x0 { - return nil - } - x := start - for _, ch := range v.Subtitle { - if x >= v.x1 { - break - } - if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil { - return err - } - x += runewidth.RuneWidth(ch) - } - return nil -} - -// drawListFooter draws the footer of a list view, showing something like '1 of 10' -func (g *Gui) drawListFooter(v *View, fgColor, bgColor Attribute) error { - if len(v.lines) == 0 { - return nil - } - - message := v.Footer - - if v.y1 < 0 || v.y1 >= g.maxY { - return nil - } - - start := v.x1 - 1 - runewidth.StringWidth(message) - if start < v.x0 { - return nil - } - x := start - for _, ch := range message { - if x >= v.x1 { - break - } - if err := g.SetRune(x, v.y1, ch, fgColor, bgColor); err != nil { - return err - } - x += runewidth.RuneWidth(ch) - } - return nil -} - -// flush updates the gui, re-drawing frames and buffers. -func (g *Gui) flush() error { - // pretty sure we don't need this, but keeping it here in case we get weird visual artifacts - // g.clear(g.FgColor, g.BgColor) - - maxX, maxY := Screen.Size() - // if GUI's size has changed, we need to redraw all views - if maxX != g.maxX || maxY != g.maxY { - for _, v := range g.views { - v.clearViewLines() - } - } - g.maxX, g.maxY = maxX, maxY - - for _, m := range g.managers { - if err := m.Layout(g); err != nil { - return err - } - } - for _, v := range g.views { - if err := g.draw(v); err != nil { - return err - } - } - - Screen.Show() - return nil -} - -func (g *Gui) ForceLayoutAndRedraw() error { - return g.flush() -} - -// force redrawing one or more views outside of the normal main loop. Useful during longer -// operations that block the main thread, to update a spinner in a status view. -func (g *Gui) ForceRedrawViews(views ...*View) error { - for _, m := range g.managers { - if err := m.Layout(g); err != nil { - return err - } - } - - for _, v := range views { - v.draw() - } - - Screen.Show() - return nil -} - -// draw manages the cursor and calls the draw function of a view. -func (g *Gui) draw(v *View) error { - if g.suspended { - return nil - } - - if !v.Visible || v.y1 < v.y0 || v.x1 < v.x0 { - return nil - } - - if g.Cursor { - if curview := g.currentView; curview != nil { - vMaxX, vMaxY := curview.InnerSize() - if curview.cx >= 0 && curview.cx < vMaxX && curview.cy >= 0 && curview.cy < vMaxY { - cx, cy := curview.x0+curview.cx+1, curview.y0+curview.cy+1 - Screen.ShowCursor(cx, cy) - } else { - Screen.HideCursor() - } - } - } else { - Screen.HideCursor() - } - - v.draw() - - if v.Frame { - var fgColor, bgColor, frameColor Attribute - if g.Highlight && v == g.currentView { - fgColor = g.SelFgColor - bgColor = g.SelBgColor - frameColor = g.SelFrameColor - } else { - bgColor = g.BgColor - if v.TitleColor != ColorDefault { - fgColor = v.TitleColor - } else { - fgColor = g.FgColor - } - if v.FrameColor != ColorDefault { - frameColor = v.FrameColor - } else { - frameColor = g.FrameColor - } - } - - if err := g.drawFrameEdges(v, frameColor, bgColor); err != nil { - return err - } - if err := g.drawFrameCorners(v, frameColor, bgColor); err != nil { - return err - } - if v.Title != "" || len(v.Tabs) > 0 { - if err := g.drawTitle(v, fgColor, bgColor); err != nil { - return err - } - } - if v.Subtitle != "" { - if err := g.drawSubtitle(v, fgColor, bgColor); err != nil { - return err - } - } - if v.Footer != "" && g.ShowListFooter { - if err := g.drawListFooter(v, fgColor, bgColor); err != nil { - return err - } - } - } - - return nil -} - -// onKey manages key-press events. A keybinding handler is called when -// a key-press or mouse event satisfies a configured keybinding. Furthermore, -// currentView's internal buffer is modified if currentView.Editable is true. -func (g *Gui) onKey(ev *GocuiEvent) error { - switch ev.Type { - case eventKey: - - // When pasting text in Ghostty, it sends us '\r' instead of '\n' for - // newlines. I actually don't quite understand why, because from reading - // Ghostty's source code (e.g. - // https://github.com/ghostty-org/ghostty/commit/010338354a0) it does - // this conversion only for non-bracketed paste mode, but I'm seeing it - // in bracketed paste mode. Whatever I'm missing here, converting '\r' - // back to '\n' fixes pasting multi-line text from Ghostty, and doesn't - // seem harmful for other terminal emulators. - // - // KeyCtrlJ (int value 10) is '\r', and KeyCtrlM (int value 13) is '\n'. - if g.IsPasting && ev.Key == KeyCtrlJ { - ev.Key = KeyCtrlM - } - - err := g.execKeybindings(g.currentView, ev) - if err != nil { - return err - } - - case eventMouse: - mx, my := ev.MouseX, ev.MouseY - v, err := g.VisibleViewByPosition(mx, my) - if err != nil { - break - } - if v.Frame && my == v.y0 { - if len(v.Tabs) > 0 { - tabIndex := v.GetClickedTabIndex(mx - v.x0) - - if tabIndex >= 0 { - for _, binding := range g.tabClickBindings { - if binding.viewName == v.Name() { - return binding.handler(tabIndex) - } - } - } - } - } - - // newCx and newCy are relative to the view port, i.e. to the visible area of the view - newCx := mx - v.x0 - 1 - newCy := my - v.y0 - 1 - // newX and newY are relative to the view's content, independent of its scroll position - newX := newCx + v.ox - newY := newCy + v.oy - // if view is editable don't go further than the furthest character for that line - if v.Editable { - if newY < 0 { - newY = 0 - newCy = -v.oy - } else if newY >= len(v.lines) { - newY = len(v.lines) - 1 - newCy = newY - v.oy - } - - lastCharForLine := len(v.lines[newY]) - for lastCharForLine > 0 && v.lines[newY][lastCharForLine-1].chr == 0 { - lastCharForLine-- - } - if lastCharForLine < newX { - newX = lastCharForLine - newCx = lastCharForLine - v.ox - } - } - if !IsMouseScrollKey(ev.Key) { - v.SetCursor(newCx, newCy) - if v.Editable { - v.TextArea.SetCursor2D(newX, newY) - - // SetCursor2D might have adjusted the text area's cursor to the - // left to move left from a soft line break, so we need to - // update the view's cursor to match the text area's cursor. - cX, _ := v.TextArea.GetCursorXY() - v.SetCursorX(cX) - } - } - - if ev.Key == MouseLeft && !v.Editable && g.openHyperlink != nil { - if newY >= 0 && newY <= len(v.viewLines)-1 && newX >= 0 && newX <= len(v.viewLines[newY].line)-1 { - if link := v.viewLines[newY].line[newX].hyperlink; link != "" { - return g.openHyperlink(link, v.name) - } - } - } - - if IsMouseKey(ev.Key) { - isDoubleClick := g.recordClickInfo(newX, newY, ev.Key, v) - opts := ViewMouseBindingOpts{X: newX, Y: newY, Key: ev.Key, IsDoubleClick: isDoubleClick} - matched, err := g.execMouseKeybindings(v, ev, opts) - if err != nil { - return err - } - if matched { - return nil - } - } - - if err := g.execKeybindings(v, ev); err != nil { - return err - } - - case eventMouseMove: - mx, my := ev.MouseX, ev.MouseY - v, err := g.VisibleViewByPosition(mx, my) - if err != nil { - break - } - if g.lastHoverView != nil && g.lastHoverView != v { - g.lastHoverView.lastHoverPosition = nil - g.lastHoverView.hoveredHyperlink = nil - } - g.lastHoverView = v - v.onMouseMove(mx, my) - - default: - } - - return nil -} - -// remember the information for this click, and return true if it was a double click -func (g *Gui) recordClickInfo(x, y int, key Key, v *View) bool { - if IsMouseScrollKey(key) { - g.lastClick = nil - return false - } - - clickInfo := &clickInfo{ - x: x, - y: y, - key: key, - viewName: v.Name(), - time: time.Now(), - } - - isDoubleClick := g.lastClick != nil && - clickInfo.x == g.lastClick.x && - clickInfo.y == g.lastClick.y && - clickInfo.key == g.lastClick.key && - clickInfo.viewName == g.lastClick.viewName && - clickInfo.time.Before(g.lastClick.time.Add(DOUBLE_CLICK_THRESHOLD)) - - g.lastClick = clickInfo - return isDoubleClick -} - -func (g *Gui) execMouseKeybindings(view *View, ev *GocuiEvent, opts ViewMouseBindingOpts) (bool, error) { - isMatch := func(binding *ViewMouseBinding) bool { - return binding.ViewName == view.Name() && - ev.Key == binding.Key && - ev.Mod == binding.Modifier - } - - // first pass looks for ones that match the focused view - for _, binding := range g.viewMouseBindings { - if isMatch(binding) && binding.FocusedView != "" && binding.FocusedView == g.currentView.Name() { - if err := binding.Handler(opts); !errors.Is(err, ErrKeybindingNotHandled) { - return true, err - } - } - } - - for _, binding := range g.viewMouseBindings { - if isMatch(binding) && binding.FocusedView == "" { - return true, binding.Handler(opts) - } - } - - return false, nil -} - -func IsMouseKey(key interface{}) bool { - switch key { - case - MouseLeft, - MouseRight, - MouseMiddle, - MouseRelease, - MouseWheelUp, - MouseWheelDown, - MouseWheelLeft, - MouseWheelRight: - return true - default: - return false - } -} - -func IsMouseScrollKey(key interface{}) bool { - switch key { - case - MouseWheelUp, - MouseWheelDown, - MouseWheelLeft, - MouseWheelRight: - return true - default: - return false - } -} - -// execKeybindings executes the keybinding handlers that match the passed view -// and event. -func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error { - var globalKb *keybinding - var matchingParentViewKb *keybinding - - if g.IsPasting && v != nil && !v.Editable { - return nil - } - - // if we're searching, and we've hit n/N/Esc, we ignore the default keybinding - if v != nil && v.IsSearching() && ev.Mod == ModNone { - if eventMatchesKey(ev, g.NextSearchMatchKey) { - return v.gotoNextMatch() - } else if eventMatchesKey(ev, g.PrevSearchMatchKey) { - return v.gotoPreviousMatch() - } else if eventMatchesKey(ev, g.SearchEscapeKey) { - v.searcher.clearSearch() - if g.OnSearchEscape != nil { - if err := g.OnSearchEscape(); err != nil { - return err - } - } - return nil - } - } - - var err error - - for _, kb := range g.keybindings { - if kb.handler == nil { - continue - } - if !kb.matchKeypress(ev.Key, ev.Ch, ev.Mod) { - continue - } - if g.matchView(v, kb) { - err = g.execKeybinding(v, kb) - if !errors.Is(err, ErrKeybindingNotHandled) { - return err - } - - matchingParentViewKb = nil - break - } - if v != nil && g.matchView(v.ParentView, kb) { - matchingParentViewKb = kb - } - if globalKb == nil && kb.viewName == "" && ((v != nil && !v.Editable) || (kb.ch == 0 && kb.key != KeyCtrlU && kb.key != KeyCtrlA && kb.key != KeyCtrlE)) { - globalKb = kb - } - } - if matchingParentViewKb != nil { - err = g.execKeybinding(v.ParentView, matchingParentViewKb) - if !errors.Is(err, ErrKeybindingNotHandled) { - return err - } - } - - if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil { - matched := g.currentView.Editor.Edit(g.currentView, ev.Key, ev.Ch, ev.Mod) - if matched { - return nil - } - } - - if globalKb != nil { - err = g.execKeybinding(v, globalKb) - } - return err -} - -// execKeybinding executes a given keybinding -func (g *Gui) execKeybinding(v *View, kb *keybinding) error { - if g.isBlacklisted(kb.key) { - return nil - } - - if err := kb.handler(g, v); err != nil { - return err - } - return nil -} - -func (g *Gui) onFocus(ev *GocuiEvent) error { - if g.focusHandler != nil { - return g.focusHandler(ev.Focused) - } - - return nil -} - -func (g *Gui) StartTicking(ctx context.Context) { - go func() { - g.Mutexes.tickingMutex.Lock() - defer g.Mutexes.tickingMutex.Unlock() - ticker := time.NewTicker(time.Millisecond * 50) - defer ticker.Stop() - outer: - for { - select { - case <-ticker.C: - // I'm okay with having a data race here: there's no harm in letting one of these updates through - if g.suspended { - continue outer - } - - for _, view := range g.Views() { - if view.HasLoader { - g.UpdateAsync(func(g *Gui) error { return nil }) - continue outer - } - } - return - case <-ctx.Done(): - return - case <-g.stop: - return - } - } - }() -} - -// isBlacklisted reports whether the key is blacklisted -func (g *Gui) isBlacklisted(k Key) bool { - for _, j := range g.blacklist { - if j == k { - return true - } - } - return false -} - -func (g *Gui) Suspend() error { - g.suspendedMutex.Lock() - defer g.suspendedMutex.Unlock() - - if g.suspended { - return errors.New("Already suspended") - } - - g.suspended = true - - return g.screen.Suspend() -} - -func (g *Gui) Resume() error { - g.suspendedMutex.Lock() - defer g.suspendedMutex.Unlock() - - if !g.suspended { - return errors.New("Cannot resume because we are not suspended") - } - - g.suspended = false - - return g.screen.Resume() -} - -// matchView returns if the keybinding matches the current view (and the view's context) -func (g *Gui) matchView(v *View, kb *keybinding) bool { - // if the user is typing in a field, ignore char keys - if v == nil { - return false - } - if v.Editable && kb.ch != 0 { - return false - } - if kb.viewName != v.name { - return false - } - return true -} - -// returns a string representation of the current state of the gui, character-for-character -func (g *Gui) Snapshot() string { - if g.screen == nil { - return "" - } - - width, height := g.screen.Size() - - builder := &strings.Builder{} - - for y := 0; y < height; y++ { - for x := 0; x < width; x++ { - char, _, _, charWidth := g.screen.GetContent(x, y) - if charWidth == 0 { - continue - } - builder.WriteRune(char) - if charWidth > 1 { - x += charWidth - 1 - } - } - builder.WriteRune('\n') - } - - return builder.String() -} diff --git a/vendor/github.com/jesseduffield/gocui/gui_others.go b/vendor/github.com/jesseduffield/gocui/gui_others.go deleted file mode 100644 index f0de7822a57..00000000000 --- a/vendor/github.com/jesseduffield/gocui/gui_others.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2014 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !windows -// +build !windows - -package gocui - -import ( - "os" - "os/signal" - "syscall" - "unsafe" - - "github.com/go-errors/errors" -) - -// getTermWindowSize is get terminal window size on linux or unix. -// When gocui run inside the docker contaienr need to check and get the window size. -func (g *Gui) getTermWindowSize() (int, int, error) { - var sz struct { - rows uint16 - cols uint16 - _ [2]uint16 // to match underlying syscall; see https://github.com/awesome-gocui/gocui/issues/33 - } - - var termw, termh int - - out, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) - if err != nil { - return 0, 0, err - } - defer out.Close() - - signalCh := make(chan os.Signal, 1) - signal.Notify(signalCh, syscall.SIGWINCH, syscall.SIGINT) - - for { - _, _, _ = syscall.Syscall(syscall.SYS_IOCTL, - out.Fd(), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz))) - - // check terminal window size - termw, termh = int(sz.cols), int(sz.rows) - if termw > 0 && termh > 0 { - return termw, termh, nil - } - - signal := <-signalCh - switch signal { - // when the terminal window size is changed - case syscall.SIGWINCH: - continue - // ctrl + c to cancel - case syscall.SIGINT: - return 0, 0, errors.New("stop to get term window size") - } - } -} diff --git a/vendor/github.com/jesseduffield/gocui/gui_windows.go b/vendor/github.com/jesseduffield/gocui/gui_windows.go deleted file mode 100644 index 56c54570caf..00000000000 --- a/vendor/github.com/jesseduffield/gocui/gui_windows.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2014 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build windows -// +build windows - -package gocui - -import ( - "os" - "syscall" - "unsafe" -) - -type ( - wchar uint16 - short int16 - dword uint32 - word uint16 -) - -type coord struct { - x short - y short -} - -type smallRect struct { - left short - top short - right short - bottom short -} - -type consoleScreenBufferInfo struct { - size coord - cursorPosition coord - attributes word - window smallRect - maximumWindowSize coord -} - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") -) - -// getTermWindowSize is get terminal window size on windows. -func (g *Gui) getTermWindowSize() (int, int, error) { - var csbi consoleScreenBufferInfo - r1, _, err := procGetConsoleScreenBufferInfo.Call(os.Stdout.Fd(), uintptr(unsafe.Pointer(&csbi))) - if r1 == 0 { - return 0, 0, err - } - return int(csbi.window.right - csbi.window.left + 1), int(csbi.window.bottom - csbi.window.top + 1), nil -} diff --git a/vendor/github.com/jesseduffield/gocui/keybinding.go b/vendor/github.com/jesseduffield/gocui/keybinding.go deleted file mode 100644 index 0d2cecc68c8..00000000000 --- a/vendor/github.com/jesseduffield/gocui/keybinding.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2014 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gocui - -import ( - "strings" - - "github.com/gdamore/tcell/v2" -) - -// Key represents special keys or keys combinations. -type Key tcell.Key - -// Modifier allows to define special keys combinations. They can be used -// in combination with Keys or Runes when a new keybinding is defined. -type Modifier tcell.ModMask - -// Keybidings are used to link a given key-press event with a handler. -type keybinding struct { - viewName string - key Key - ch rune - mod Modifier - handler func(*Gui, *View) error -} - -// Parse takes the input string and extracts the keybinding. -// Returns a Key / rune, a Modifier and an error. -func Parse(input string) (interface{}, Modifier, error) { - if len(input) == 1 { - _, r, err := getKey(rune(input[0])) - if err != nil { - return nil, ModNone, err - } - return r, ModNone, nil - } - - var modifier Modifier - cleaned := make([]string, 0) - - tokens := strings.Split(input, "+") - for _, t := range tokens { - normalized := strings.Title(strings.ToLower(t)) - if t == "Alt" { - modifier = ModAlt - continue - } - cleaned = append(cleaned, normalized) - } - - key, exist := translate[strings.Join(cleaned, "")] - if !exist { - return nil, ModNone, ErrNoSuchKeybind - } - - return key, modifier, nil -} - -// ParseAll takes an array of strings and returns a map of all keybindings. -func ParseAll(input []string) (map[interface{}]Modifier, error) { - ret := make(map[interface{}]Modifier) - for _, i := range input { - k, m, err := Parse(i) - if err != nil { - return ret, err - } - ret[k] = m - } - return ret, nil -} - -// MustParse takes the input string and returns a Key / rune and a Modifier. -// It will panic if any error occured. -func MustParse(input string) (interface{}, Modifier) { - k, m, err := Parse(input) - if err != nil { - panic(err) - } - return k, m -} - -// MustParseAll takes an array of strings and returns a map of all keybindings. -// It will panic if any error occured. -func MustParseAll(input []string) map[interface{}]Modifier { - result, err := ParseAll(input) - if err != nil { - panic(err) - } - return result -} - -// newKeybinding returns a new Keybinding object. -func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) { - kb = &keybinding{ - viewName: viewname, - key: key, - ch: ch, - mod: mod, - handler: handler, - } - return kb -} - -func eventMatchesKey(ev *GocuiEvent, key interface{}) bool { - // assuming ModNone for now - if ev.Mod != ModNone { - return false - } - - k, ch, err := getKey(key) - if err != nil { - return false - } - - return k == ev.Key && ch == ev.Ch -} - -// matchKeypress returns if the keybinding matches the keypress. -func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool { - return kb.key == key && kb.ch == ch && kb.mod == mod -} - -// translations for strings to keys -var translate = map[string]Key{ - "F1": KeyF1, - "F2": KeyF2, - "F3": KeyF3, - "F4": KeyF4, - "F5": KeyF5, - "F6": KeyF6, - "F7": KeyF7, - "F8": KeyF8, - "F9": KeyF9, - "F10": KeyF10, - "F11": KeyF11, - "F12": KeyF12, - "Insert": KeyInsert, - "Delete": KeyDelete, - "Home": KeyHome, - "End": KeyEnd, - "Pgup": KeyPgup, - "Pgdn": KeyPgdn, - "ArrowUp": KeyArrowUp, - "ShiftArrowUp": KeyShiftArrowUp, - "ArrowDown": KeyArrowDown, - "ShiftArrowDown": KeyShiftArrowDown, - "ArrowLeft": KeyArrowLeft, - "ArrowRight": KeyArrowRight, - "CtrlTilde": KeyCtrlTilde, - "Ctrl2": KeyCtrl2, - "CtrlSpace": KeyCtrlSpace, - "CtrlA": KeyCtrlA, - "CtrlB": KeyCtrlB, - "CtrlC": KeyCtrlC, - "CtrlD": KeyCtrlD, - "CtrlE": KeyCtrlE, - "CtrlF": KeyCtrlF, - "CtrlG": KeyCtrlG, - "Backspace": KeyBackspace, - "CtrlH": KeyCtrlH, - "Tab": KeyTab, - "BackTab": KeyBacktab, - "CtrlI": KeyCtrlI, - "CtrlJ": KeyCtrlJ, - "CtrlK": KeyCtrlK, - "CtrlL": KeyCtrlL, - "Enter": KeyEnter, - "CtrlM": KeyCtrlM, - "CtrlN": KeyCtrlN, - "CtrlO": KeyCtrlO, - "CtrlP": KeyCtrlP, - "CtrlQ": KeyCtrlQ, - "CtrlR": KeyCtrlR, - "CtrlS": KeyCtrlS, - "CtrlT": KeyCtrlT, - "CtrlU": KeyCtrlU, - "CtrlV": KeyCtrlV, - "CtrlW": KeyCtrlW, - "CtrlX": KeyCtrlX, - "CtrlY": KeyCtrlY, - "CtrlZ": KeyCtrlZ, - "Esc": KeyEsc, - "CtrlLsqBracket": KeyCtrlLsqBracket, - "Ctrl3": KeyCtrl3, - "Ctrl4": KeyCtrl4, - "CtrlBackslash": KeyCtrlBackslash, - "Ctrl5": KeyCtrl5, - "CtrlRsqBracket": KeyCtrlRsqBracket, - "Ctrl6": KeyCtrl6, - "Ctrl7": KeyCtrl7, - "CtrlSlash": KeyCtrlSlash, - "CtrlUnderscore": KeyCtrlUnderscore, - "Space": KeySpace, - "Backspace2": KeyBackspace2, - "Ctrl8": KeyCtrl8, - "Mouseleft": MouseLeft, - "Mousemiddle": MouseMiddle, - "Mouseright": MouseRight, - "Mouserelease": MouseRelease, - "MousewheelUp": MouseWheelUp, - "MousewheelDown": MouseWheelDown, -} - -// Special keys. -const ( - KeyF1 Key = Key(tcell.KeyF1) - KeyF2 = Key(tcell.KeyF2) - KeyF3 = Key(tcell.KeyF3) - KeyF4 = Key(tcell.KeyF4) - KeyF5 = Key(tcell.KeyF5) - KeyF6 = Key(tcell.KeyF6) - KeyF7 = Key(tcell.KeyF7) - KeyF8 = Key(tcell.KeyF8) - KeyF9 = Key(tcell.KeyF9) - KeyF10 = Key(tcell.KeyF10) - KeyF11 = Key(tcell.KeyF11) - KeyF12 = Key(tcell.KeyF12) - KeyInsert = Key(tcell.KeyInsert) - KeyDelete = Key(tcell.KeyDelete) - KeyHome = Key(tcell.KeyHome) - KeyEnd = Key(tcell.KeyEnd) - KeyPgdn = Key(tcell.KeyPgDn) - KeyPgup = Key(tcell.KeyPgUp) - KeyArrowUp = Key(tcell.KeyUp) - KeyShiftArrowUp = Key(tcell.KeyF62) - KeyArrowDown = Key(tcell.KeyDown) - KeyShiftArrowDown = Key(tcell.KeyF63) - KeyArrowLeft = Key(tcell.KeyLeft) - KeyArrowRight = Key(tcell.KeyRight) -) - -// Keys combinations. -const ( - KeyCtrlTilde = Key(tcell.KeyF64) // arbitrary assignment - KeyCtrlSpace = Key(tcell.KeyCtrlSpace) - KeyCtrlA = Key(tcell.KeyCtrlA) - KeyCtrlB = Key(tcell.KeyCtrlB) - KeyCtrlC = Key(tcell.KeyCtrlC) - KeyCtrlD = Key(tcell.KeyCtrlD) - KeyCtrlE = Key(tcell.KeyCtrlE) - KeyCtrlF = Key(tcell.KeyCtrlF) - KeyCtrlG = Key(tcell.KeyCtrlG) - KeyBackspace = Key(tcell.KeyBackspace) - KeyCtrlH = Key(tcell.KeyCtrlH) - KeyTab = Key(tcell.KeyTab) - KeyBacktab = Key(tcell.KeyBacktab) - KeyCtrlI = Key(tcell.KeyCtrlI) - KeyCtrlJ = Key(tcell.KeyCtrlJ) - KeyCtrlK = Key(tcell.KeyCtrlK) - KeyCtrlL = Key(tcell.KeyCtrlL) - KeyEnter = Key(tcell.KeyEnter) - KeyCtrlM = Key(tcell.KeyCtrlM) - KeyCtrlN = Key(tcell.KeyCtrlN) - KeyCtrlO = Key(tcell.KeyCtrlO) - KeyCtrlP = Key(tcell.KeyCtrlP) - KeyCtrlQ = Key(tcell.KeyCtrlQ) - KeyCtrlR = Key(tcell.KeyCtrlR) - KeyCtrlS = Key(tcell.KeyCtrlS) - KeyCtrlT = Key(tcell.KeyCtrlT) - KeyCtrlU = Key(tcell.KeyCtrlU) - KeyCtrlV = Key(tcell.KeyCtrlV) - KeyCtrlW = Key(tcell.KeyCtrlW) - KeyCtrlX = Key(tcell.KeyCtrlX) - KeyCtrlY = Key(tcell.KeyCtrlY) - KeyCtrlZ = Key(tcell.KeyCtrlZ) - KeyEsc = Key(tcell.KeyEscape) - KeyCtrlUnderscore = Key(tcell.KeyCtrlUnderscore) - KeySpace = Key(32) - KeyBackspace2 = Key(tcell.KeyBackspace2) - KeyCtrl8 = Key(tcell.KeyBackspace2) // same key as in termbox-go - - // The following assignments were used in termbox implementation. - // In tcell, these are not keys per se. But in gocui we have them - // mapped to the keys so we have to use placeholder keys. - - KeyAltEnter = Key(tcell.KeyF64) // arbitrary assignments - MouseLeft = Key(tcell.KeyF63) - MouseRight = Key(tcell.KeyF62) - MouseMiddle = Key(tcell.KeyF61) - MouseRelease = Key(tcell.KeyF60) - MouseWheelUp = Key(tcell.KeyF59) - MouseWheelDown = Key(tcell.KeyF58) - MouseWheelLeft = Key(tcell.KeyF57) - MouseWheelRight = Key(tcell.KeyF56) - KeyCtrl2 = Key(tcell.KeyNUL) // termbox defines theses - KeyCtrl3 = Key(tcell.KeyEscape) - KeyCtrl4 = Key(tcell.KeyCtrlBackslash) - KeyCtrl5 = Key(tcell.KeyCtrlRightSq) - KeyCtrl6 = Key(tcell.KeyCtrlCarat) - KeyCtrl7 = Key(tcell.KeyCtrlUnderscore) - KeyCtrlSlash = Key(tcell.KeyCtrlUnderscore) - KeyCtrlRsqBracket = Key(tcell.KeyCtrlRightSq) - KeyCtrlBackslash = Key(tcell.KeyCtrlBackslash) - KeyCtrlLsqBracket = Key(tcell.KeyCtrlLeftSq) -) - -// Modifiers. -const ( - ModNone Modifier = Modifier(0) - ModAlt = Modifier(tcell.ModAlt) - ModMotion = Modifier(2) // just picking an arbitrary number here that doesn't clash with tcell.ModAlt - // ModCtrl doesn't work with keyboard keys. Use CtrlKey in Key and ModNone. This is was for mouse clicks only (tcell.v1) - // ModCtrl = Modifier(tcell.ModCtrl) -) diff --git a/vendor/github.com/jesseduffield/gocui/loader.go b/vendor/github.com/jesseduffield/gocui/loader.go deleted file mode 100644 index 0aad5ae85a6..00000000000 --- a/vendor/github.com/jesseduffield/gocui/loader.go +++ /dev/null @@ -1,33 +0,0 @@ -package gocui - -import "time" - -func (v *View) loaderLines() [][]cell { - duplicate := make([][]cell, len(v.lines)) - for i := range v.lines { - if i < len(v.lines)-1 { - duplicate[i] = make([]cell, len(v.lines[i])) - copy(duplicate[i], v.lines[i]) - } else { - duplicate[i] = make([]cell, len(v.lines[i])+2) - copy(duplicate[i], v.lines[i]) - duplicate[i][len(duplicate[i])-2] = cell{chr: ' '} - duplicate[i][len(duplicate[i])-1] = Loader() - } - } - - return duplicate -} - -// Loader can show a loading animation -func Loader() cell { - characters := "|/-\\" - now := time.Now() - nanos := now.UnixNano() - index := nanos / 50000000 % int64(len(characters)) - str := characters[index : index+1] - chr := []rune(str)[0] - return cell{ - chr: chr, - } -} diff --git a/vendor/github.com/jesseduffield/gocui/scrollbar.go b/vendor/github.com/jesseduffield/gocui/scrollbar.go deleted file mode 100644 index 5fe7cc2dc0b..00000000000 --- a/vendor/github.com/jesseduffield/gocui/scrollbar.go +++ /dev/null @@ -1,28 +0,0 @@ -package gocui - -import "math" - -// returns start and height of scrollbar -// `max` is the maximum possible value of `position` -func calcScrollbar(listSize int, pageSize int, position int, scrollAreaSize int) (int, int) { - height := calcScrollbarHeight(listSize, pageSize, scrollAreaSize) - // assume we can't scroll past the last item - maxPosition := listSize - pageSize - if maxPosition <= 0 { - return 0, height - } - if position == maxPosition { - return scrollAreaSize - height, height - } - // we only want to show the scrollbar at the top or bottom positions if we're at the end. Hence the .Ceil (for moving the scrollbar once we scroll down) and the -1 (for pretending there's a smaller range than we actually have, with the above condition ensuring we snap to the bottom once we're at the end of the list) - start := int(math.Ceil(((float64(position) / float64(maxPosition)) * float64(scrollAreaSize-height-1)))) - return start, height -} - -func calcScrollbarHeight(listSize int, pageSize int, scrollAreaSize int) int { - if pageSize >= listSize { - return scrollAreaSize - } - - return int((float64(pageSize) / float64(listSize)) * float64(scrollAreaSize)) -} diff --git a/vendor/github.com/jesseduffield/gocui/task.go b/vendor/github.com/jesseduffield/gocui/task.go deleted file mode 100644 index ace72f4a88a..00000000000 --- a/vendor/github.com/jesseduffield/gocui/task.go +++ /dev/null @@ -1,94 +0,0 @@ -package gocui - -// A task represents the fact that the program is busy doing something, which -// is useful for integration tests which only want to proceed when the program -// is idle. - -type Task interface { - Done() - Pause() - Continue() - // not exporting because we don't need to - isBusy() bool -} - -type TaskImpl struct { - id int - busy bool - onDone func() - withMutex func(func()) -} - -func (self *TaskImpl) Done() { - self.onDone() -} - -func (self *TaskImpl) Pause() { - self.withMutex(func() { - self.busy = false - }) -} - -func (self *TaskImpl) Continue() { - self.withMutex(func() { - self.busy = true - }) -} - -func (self *TaskImpl) isBusy() bool { - return self.busy -} - -type TaskStatus int - -const ( - TaskStatusBusy TaskStatus = iota - TaskStatusPaused - TaskStatusDone -) - -type FakeTask struct { - status TaskStatus -} - -func NewFakeTask() *FakeTask { - return &FakeTask{ - status: TaskStatusBusy, - } -} - -func (self *FakeTask) Done() { - self.status = TaskStatusDone -} - -func (self *FakeTask) Pause() { - self.status = TaskStatusPaused -} - -func (self *FakeTask) Continue() { - self.status = TaskStatusBusy -} - -func (self *FakeTask) isBusy() bool { - return self.status == TaskStatusBusy -} - -func (self *FakeTask) Status() TaskStatus { - return self.status -} - -func (self *FakeTask) FormatStatus() string { - return formatTaskStatus(self.status) -} - -func formatTaskStatus(status TaskStatus) string { - switch status { - case TaskStatusBusy: - return "busy" - case TaskStatusPaused: - return "paused" - case TaskStatusDone: - return "done" - } - return "unknown" -} diff --git a/vendor/github.com/jesseduffield/gocui/task_manager.go b/vendor/github.com/jesseduffield/gocui/task_manager.go deleted file mode 100644 index e3c82b4d4c4..00000000000 --- a/vendor/github.com/jesseduffield/gocui/task_manager.go +++ /dev/null @@ -1,67 +0,0 @@ -package gocui - -import "sync" - -// Tracks whether the program is busy (i.e. either something is happening on -// the main goroutine or a worker goroutine). Used by integration tests -// to wait until the program is idle before progressing. -type TaskManager struct { - // each of these listeners will be notified when the program goes from busy to idle - idleListeners []chan struct{} - tasks map[int]Task - // auto-incrementing id for new tasks - nextId int - - mutex sync.Mutex -} - -func newTaskManager() *TaskManager { - return &TaskManager{ - tasks: make(map[int]Task), - idleListeners: []chan struct{}{}, - } -} - -func (self *TaskManager) NewTask() *TaskImpl { - self.mutex.Lock() - defer self.mutex.Unlock() - - self.nextId++ - taskId := self.nextId - - onDone := func() { self.delete(taskId) } - task := &TaskImpl{id: taskId, busy: true, onDone: onDone, withMutex: self.withMutex} - self.tasks[taskId] = task - - return task -} - -func (self *TaskManager) addIdleListener(c chan struct{}) { - self.idleListeners = append(self.idleListeners, c) -} - -func (self *TaskManager) withMutex(f func()) { - self.mutex.Lock() - defer self.mutex.Unlock() - - f() - - // Check if all tasks are done - for _, task := range self.tasks { - if task.isBusy() { - return - } - } - - // If we get here, all tasks are done, so - // notify listeners that the program is idle - for _, listener := range self.idleListeners { - listener <- struct{}{} - } -} - -func (self *TaskManager) delete(taskId int) { - self.withMutex(func() { - delete(self.tasks, taskId) - }) -} diff --git a/vendor/github.com/jesseduffield/gocui/tcell_driver.go b/vendor/github.com/jesseduffield/gocui/tcell_driver.go deleted file mode 100644 index 4199f7abb03..00000000000 --- a/vendor/github.com/jesseduffield/gocui/tcell_driver.go +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright 2020 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gocui - -import ( - "github.com/gdamore/tcell/v2" - "github.com/mattn/go-runewidth" -) - -// We probably don't want this being a global variable for YOLO for now -var Screen tcell.Screen - -// oldStyle is a representation of how a cell would be styled when we were using termbox -type oldStyle struct { - fg Attribute - bg Attribute - outputMode OutputMode -} - -var runeReplacements = map[rune]string{ - '┌': "+", - '┐': "+", - '└': "+", - '┘': "+", - '╭': "+", - '╮': "+", - '╰': "+", - '╯': "+", - '─': "-", - '═': "-", - '║': "|", - '╔': "+", - '╗': "+", - '╚': "+", - '╝': "+", - - // using a hyphen here actually looks weird. - // We see these characters when in portrait mode - '╶': " ", - '╴': " ", - - '┴': "+", - '┬': "+", - '╷': "|", - '├': "+", - '│': "|", - '▼': "v", - '►': ">", - '▲': "^", - '◄': "<", -} - -// tcellInit initializes tcell screen for use. -func (g *Gui) tcellInit(runeReplacements map[rune]string) error { - runewidth.DefaultCondition.EastAsianWidth = false - tcell.SetEncodingFallback(tcell.EncodingFallbackASCII) - - if s, e := tcell.NewScreen(); e != nil { - return e - } else if e = s.Init(); e != nil { - return e - } else { - registerRuneFallbacks(s, runeReplacements) - - g.screen = s - Screen = s - return nil - } -} - -func registerRuneFallbacks(s tcell.Screen, additional map[rune]string) { - for before, after := range runeReplacements { - s.RegisterRuneFallback(before, after) - } - - for before, after := range additional { - s.RegisterRuneFallback(before, after) - } -} - -// tcellInitSimulation initializes tcell screen for use. -func (g *Gui) tcellInitSimulation(width int, height int) error { - s := tcell.NewSimulationScreen("") - if e := s.Init(); e != nil { - return e - } else { - g.screen = s - Screen = s - // setting to a larger value than the typical terminal size - // so that during a test we're more likely to see an item to select in a view. - s.SetSize(width, height) - s.Sync() - return nil - } -} - -// tcellSetCell sets the character cell at a given location to the given -// content (rune) and attributes using provided OutputMode -func tcellSetCell(x, y int, ch rune, fg, bg Attribute, outputMode OutputMode) { - st := getTcellStyle(oldStyle{fg: fg, bg: bg, outputMode: outputMode}) - Screen.SetContent(x, y, ch, nil, st) -} - -// getTcellStyle creates tcell.Style from Attributes -func getTcellStyle(input oldStyle) tcell.Style { - st := tcell.StyleDefault - - // extract colors and attributes - if input.fg != ColorDefault { - st = st.Foreground(getTcellColor(input.fg, input.outputMode)) - st = setTcellFontEffectStyle(st, input.fg) - } - if input.bg != ColorDefault { - st = st.Background(getTcellColor(input.bg, input.outputMode)) - st = setTcellFontEffectStyle(st, input.bg) - } - - return st -} - -// setTcellFontEffectStyle add additional attributes to tcell.Style -func setTcellFontEffectStyle(st tcell.Style, attr Attribute) tcell.Style { - if attr&AttrBold != 0 { - st = st.Bold(true) - } - if attr&AttrUnderline != 0 { - st = st.Underline(true) - } - if attr&AttrReverse != 0 { - st = st.Reverse(true) - } - if attr&AttrBlink != 0 { - st = st.Blink(true) - } - if attr&AttrDim != 0 { - st = st.Dim(true) - } - if attr&AttrItalic != 0 { - st = st.Italic(true) - } - if attr&AttrStrikeThrough != 0 { - st = st.StrikeThrough(true) - } - return st -} - -// gocuiEventType represents the type of event. -type gocuiEventType uint8 - -// GocuiEvent represents events like a keys, mouse actions, or window resize. -// -// The 'Mod', 'Key' and 'Ch' fields are valid if 'Type' is 'eventKey'. -// The 'MouseX' and 'MouseY' fields are valid if 'Type' is 'eventMouse'. -// The 'Width' and 'Height' fields are valid if 'Type' is 'eventResize'. -// The 'Focused' field is valid if 'Type' is 'eventFocus'. -// The 'Start' field is valid if 'Type' is 'eventPaste'. It is true for the -// beginning of a paste operation, false for the end. -// The 'Err' field is valid if 'Type' is 'eventError'. -type GocuiEvent struct { - Type gocuiEventType - Mod Modifier - Key Key - Ch rune - Width int - Height int - Err error - MouseX int - MouseY int - Focused bool - Start bool - N int -} - -// Event types. -const ( - eventNone gocuiEventType = iota - eventKey - eventResize - eventMouse - eventMouseMove // only used when no button is down, otherwise it's eventMouse - eventFocus - eventPaste - eventInterrupt - eventError - eventRaw -) - -const ( - NOT_DRAGGING int = iota - MAYBE_DRAGGING - DRAGGING -) - -var ( - lastMouseKey tcell.ButtonMask = tcell.ButtonNone - lastMouseMod tcell.ModMask = tcell.ModNone - dragState int = NOT_DRAGGING - lastX int = 0 - lastY int = 0 -) - -// this wrapper struct has public keys so we can easily serialize/deserialize to JSON -type TcellKeyEventWrapper struct { - Timestamp int64 - Mod tcell.ModMask - Key tcell.Key - Ch rune -} - -func NewTcellKeyEventWrapper(event *tcell.EventKey, timestamp int64) *TcellKeyEventWrapper { - return &TcellKeyEventWrapper{ - Timestamp: timestamp, - Mod: event.Modifiers(), - Key: event.Key(), - Ch: event.Rune(), - } -} - -func (wrapper TcellKeyEventWrapper) toTcellEvent() tcell.Event { - return tcell.NewEventKey(wrapper.Key, wrapper.Ch, wrapper.Mod) -} - -type TcellMouseEventWrapper struct { - Timestamp int64 - X int - Y int - ButtonMask tcell.ButtonMask - ModMask tcell.ModMask -} - -func NewTcellMouseEventWrapper(event *tcell.EventMouse, timestamp int64) *TcellMouseEventWrapper { - x, y := event.Position() - return &TcellMouseEventWrapper{ - Timestamp: timestamp, - X: x, - Y: y, - ButtonMask: event.Buttons(), - ModMask: event.Modifiers(), - } -} - -func (wrapper TcellMouseEventWrapper) toTcellEvent() tcell.Event { - return tcell.NewEventMouse(wrapper.X, wrapper.Y, wrapper.ButtonMask, wrapper.ModMask) -} - -type TcellResizeEventWrapper struct { - Timestamp int64 - Width int - Height int -} - -func NewTcellResizeEventWrapper(event *tcell.EventResize, timestamp int64) *TcellResizeEventWrapper { - w, h := event.Size() - - return &TcellResizeEventWrapper{ - Timestamp: timestamp, - Width: w, - Height: h, - } -} - -func (wrapper TcellResizeEventWrapper) toTcellEvent() tcell.Event { - return tcell.NewEventResize(wrapper.Width, wrapper.Height) -} - -// pollEvent get tcell.Event and transform it into gocuiEvent -func (g *Gui) pollEvent() GocuiEvent { - var tev tcell.Event - if g.playRecording { - select { - case ev := <-g.ReplayedEvents.Keys: - tev = (ev).toTcellEvent() - case ev := <-g.ReplayedEvents.Resizes: - tev = (ev).toTcellEvent() - case ev := <-g.ReplayedEvents.MouseEvents: - tev = (ev).toTcellEvent() - } - } else { - tev = Screen.PollEvent() - } - - switch tev := tev.(type) { - case *tcell.EventInterrupt: - return GocuiEvent{Type: eventInterrupt} - case *tcell.EventResize: - w, h := tev.Size() - return GocuiEvent{Type: eventResize, Width: w, Height: h} - case *tcell.EventKey: - k := tev.Key() - ch := rune(0) - if k == tcell.KeyRune { - k = 0 // if rune remove key (so it can match rune instead of key) - ch = tev.Rune() - if ch == ' ' { - // special handling for spacebar - k = 32 // tcell keys ends at 31 or starts at 256 - ch = rune(0) - } - } - mod := tev.Modifiers() - // remove control modifier and setup special handling of ctrl+spacebar, etc. - if mod == tcell.ModCtrl && k == 32 { - mod = 0 - ch = rune(0) - k = tcell.KeyCtrlSpace - } else if mod == tcell.ModShift && k == tcell.KeyUp { - mod = 0 - ch = rune(0) - k = tcell.KeyF62 - } else if mod == tcell.ModShift && k == tcell.KeyDown { - mod = 0 - ch = rune(0) - k = tcell.KeyF63 - } else if mod == tcell.ModCtrl || mod == tcell.ModShift { - // remove Ctrl or Shift if specified - // - shift - will be translated to the final code of rune - // - ctrl - is translated in the key - mod = 0 - } else if mod == tcell.ModAlt && k == tcell.KeyEnter { - // for the sake of convenience I'm having a KeyAltEnter key. I will likely - // regret this laziness in the future. We're arbitrarily mapping that to tcell's - // KeyF64. - mod = 0 - k = tcell.KeyF64 - } - - return GocuiEvent{ - Type: eventKey, - Key: Key(k), - Ch: ch, - Mod: Modifier(mod), - } - case *tcell.EventMouse: - x, y := tev.Position() - button := tev.Buttons() - mouseKey := MouseRelease - mouseMod := ModNone - // process mouse wheel - if button&tcell.WheelUp != 0 { - mouseKey = MouseWheelUp - } - if button&tcell.WheelDown != 0 { - mouseKey = MouseWheelDown - } - if button&tcell.WheelLeft != 0 { - mouseKey = MouseWheelLeft - } - if button&tcell.WheelRight != 0 { - mouseKey = MouseWheelRight - } - - wheeling := mouseKey == MouseWheelUp || mouseKey == MouseWheelDown || mouseKey == MouseWheelLeft || mouseKey == MouseWheelRight - - // process button events (not wheel events) - button &= tcell.ButtonMask(0xff) - if button != tcell.ButtonNone && lastMouseKey == tcell.ButtonNone { - lastMouseKey = button - lastMouseMod = tev.Modifiers() - switch button { - case tcell.ButtonPrimary: - mouseKey = MouseLeft - dragState = MAYBE_DRAGGING - lastX = x - lastY = y - case tcell.ButtonSecondary: - mouseKey = MouseRight - case tcell.ButtonMiddle: - mouseKey = MouseMiddle - default: - } - } - - switch tev.Buttons() { - case tcell.ButtonNone: - if lastMouseKey != tcell.ButtonNone { - switch lastMouseKey { - case tcell.ButtonPrimary: - dragState = NOT_DRAGGING - case tcell.ButtonSecondary: - case tcell.ButtonMiddle: - default: - } - mouseMod = Modifier(lastMouseMod) - lastMouseMod = tcell.ModNone - lastMouseKey = tcell.ButtonNone - } - default: - } - - if !wheeling { - switch dragState { - case NOT_DRAGGING: - return GocuiEvent{ - Type: eventMouseMove, - MouseX: x, - MouseY: y, - } - // if we haven't released the left mouse button and we've moved the cursor then we're dragging - case MAYBE_DRAGGING: - if x != lastX || y != lastY { - dragState = DRAGGING - } - case DRAGGING: - mouseMod = ModMotion - mouseKey = MouseLeft - } - } - - return GocuiEvent{ - Type: eventMouse, - MouseX: x, - MouseY: y, - Key: mouseKey, - Ch: 0, - Mod: mouseMod, - } - case *tcell.EventFocus: - return GocuiEvent{ - Type: eventFocus, - Focused: tev.Focused, - } - case *tcell.EventPaste: - return GocuiEvent{ - Type: eventPaste, - Start: tev.Start(), - } - default: - return GocuiEvent{Type: eventNone} - } -} diff --git a/vendor/github.com/jesseduffield/gocui/text_area.go b/vendor/github.com/jesseduffield/gocui/text_area.go deleted file mode 100644 index 1194b27277e..00000000000 --- a/vendor/github.com/jesseduffield/gocui/text_area.go +++ /dev/null @@ -1,516 +0,0 @@ -package gocui - -import ( - "regexp" - "strings" - - "github.com/mattn/go-runewidth" -) - -const ( - WHITESPACES = " \t" - WORD_SEPARATORS = "*?_+-.[]~=/&;!#$%^(){}<>" -) - -type CursorMapping struct { - Orig int - Wrapped int -} - -type TextArea struct { - content []rune - wrappedContent []rune - cursorMapping []CursorMapping - cursor int - overwrite bool - clipboard string - AutoWrap bool - AutoWrapWidth int -} - -func AutoWrapContent(content []rune, autoWrapWidth int) ([]rune, []CursorMapping) { - estimatedNumberOfSoftLineBreaks := len(content) / autoWrapWidth - cursorMapping := make([]CursorMapping, 0, estimatedNumberOfSoftLineBreaks) - wrappedContent := make([]rune, 0, len(content)+estimatedNumberOfSoftLineBreaks) - startOfLine := 0 - indexOfLastWhitespace := -1 - var footNoteMatcher footNoteMatcher - - for currentPos, r := range content { - if r == '\n' { - wrappedContent = append(wrappedContent, content[startOfLine:currentPos+1]...) - startOfLine = currentPos + 1 - indexOfLastWhitespace = -1 - footNoteMatcher.reset() - } else { - if r == ' ' && !footNoteMatcher.isFootNote() { - indexOfLastWhitespace = currentPos + 1 - } else if currentPos-startOfLine >= autoWrapWidth && indexOfLastWhitespace >= 0 { - wrapAt := indexOfLastWhitespace - wrappedContent = append(wrappedContent, content[startOfLine:wrapAt]...) - wrappedContent = append(wrappedContent, '\n') - cursorMapping = append(cursorMapping, CursorMapping{wrapAt, len(wrappedContent)}) - startOfLine = wrapAt - indexOfLastWhitespace = -1 - footNoteMatcher.reset() - } - footNoteMatcher.addRune(r) - } - } - - wrappedContent = append(wrappedContent, content[startOfLine:]...) - - return wrappedContent, cursorMapping -} - -var footNoteRe = regexp.MustCompile(`^\[\d+\]:\s*$`) - -type footNoteMatcher struct { - lineStr strings.Builder - didFailToMatch bool -} - -func (self *footNoteMatcher) addRune(r rune) { - if self.didFailToMatch { - // don't bother tracking the rune if we know it can't possibly match any more - return - } - - if self.lineStr.Len() == 0 && r != '[' { - // fail early if the first rune of a line isn't a '['; this is mainly to avoid a (possibly - // expensive) regex match - self.didFailToMatch = true - return - } - - self.lineStr.WriteRune(r) -} - -func (self *footNoteMatcher) isFootNote() bool { - if self.didFailToMatch { - return false - } - - if footNoteRe.MatchString(self.lineStr.String()) { - // it's a footnote, so treat spaces as non-breaking. It's important not to reset the matcher - // here, because there could be multiple spaces after a footnote. - return true - } - - // no need to check again for this line - self.didFailToMatch = true - return false -} - -func (self *footNoteMatcher) reset() { - self.lineStr.Reset() - self.didFailToMatch = false -} - -func (self *TextArea) autoWrapContent() { - if self.AutoWrap { - self.wrappedContent, self.cursorMapping = AutoWrapContent(self.content, self.AutoWrapWidth) - } else { - self.wrappedContent, self.cursorMapping = self.content, []CursorMapping{} - } -} - -func (self *TextArea) TypeRune(r rune) { - if self.overwrite && !self.atEnd() { - self.content[self.cursor] = r - } else { - self.content = append( - self.content[:self.cursor], - append([]rune{r}, self.content[self.cursor:]...)..., - ) - } - self.autoWrapContent() - - self.cursor++ -} - -func (self *TextArea) BackSpaceChar() { - if self.cursor == 0 { - return - } - - self.content = append(self.content[:self.cursor-1], self.content[self.cursor:]...) - self.autoWrapContent() - self.cursor-- -} - -func (self *TextArea) DeleteChar() { - if self.atEnd() { - return - } - - self.content = append(self.content[:self.cursor], self.content[self.cursor+1:]...) - self.autoWrapContent() -} - -func (self *TextArea) MoveCursorLeft() { - if self.cursor == 0 { - return - } - - self.cursor-- -} - -func (self *TextArea) MoveCursorRight() { - if self.cursor == len(self.content) { - return - } - - self.cursor++ -} - -func (self *TextArea) MoveLeftWord() { - if self.cursor == 0 { - return - } - if self.atLineStart() { - self.cursor-- - return - } - - for !self.atLineStart() && strings.ContainsRune(WHITESPACES, self.content[self.cursor-1]) { - self.cursor-- - } - separators := false - for !self.atLineStart() && strings.ContainsRune(WORD_SEPARATORS, self.content[self.cursor-1]) { - self.cursor-- - separators = true - } - if !separators { - for !self.atLineStart() && !strings.ContainsRune(WHITESPACES+WORD_SEPARATORS, self.content[self.cursor-1]) { - self.cursor-- - } - } -} - -func (self *TextArea) MoveRightWord() { - if self.atEnd() { - return - } - if self.atLineEnd() { - self.cursor++ - return - } - - for !self.atLineEnd() && strings.ContainsRune(WHITESPACES, self.content[self.cursor]) { - self.cursor++ - } - separators := false - for !self.atLineEnd() && strings.ContainsRune(WORD_SEPARATORS, self.content[self.cursor]) { - self.cursor++ - separators = true - } - if !separators { - for !self.atLineEnd() && !strings.ContainsRune(WHITESPACES+WORD_SEPARATORS, self.content[self.cursor]) { - self.cursor++ - } - } -} - -func (self *TextArea) MoveCursorUp() { - x, y := self.GetCursorXY() - self.SetCursor2D(x, y-1) -} - -func (self *TextArea) MoveCursorDown() { - x, y := self.GetCursorXY() - self.SetCursor2D(x, y+1) -} - -func (self *TextArea) GetContent() string { - return string(self.wrappedContent) -} - -func (self *TextArea) GetUnwrappedContent() string { - return string(self.content) -} - -func (self *TextArea) ToggleOverwrite() { - self.overwrite = !self.overwrite -} - -func (self *TextArea) atEnd() bool { - return self.cursor == len(self.content) -} - -func (self *TextArea) DeleteToStartOfLine() { - // copying vim's logic: if you're at the start of the line, you delete the newline - // character and go to the end of the previous line - if self.atLineStart() { - if self.cursor == 0 { - return - } - - self.content = append(self.content[:self.cursor-1], self.content[self.cursor:]...) - self.cursor-- - self.autoWrapContent() - return - } - - // otherwise, if we're at a soft line start, skip left past the soft line - // break, so we'll end up deleting the previous line. This seems like the - // only reasonable behavior in this case, as you can't delete just the soft - // line break. - if self.atSoftLineStart() { - self.cursor-- - } - - // otherwise, you delete everything up to the start of the current line, without - // deleting the newline character - newlineIndex := self.closestNewlineOnLeft() - self.clipboard = string(self.content[newlineIndex+1 : self.cursor]) - self.content = append(self.content[:newlineIndex+1], self.content[self.cursor:]...) - self.autoWrapContent() - self.cursor = newlineIndex + 1 -} - -func (self *TextArea) DeleteToEndOfLine() { - if self.atEnd() { - return - } - - // if we're at the end of the line, delete just the newline character - if self.atLineEnd() { - self.content = append(self.content[:self.cursor], self.content[self.cursor+1:]...) - self.autoWrapContent() - return - } - - // otherwise, if we're at a soft line end, skip right past the soft line - // break, so we'll end up deleting the next line. This seems like the - // only reasonable behavior in this case, as you can't delete just the soft - // line break. - if self.atSoftLineEnd() { - self.cursor++ - } - - lineEndIndex := self.closestNewlineOnRight() - self.clipboard = string(self.content[self.cursor:lineEndIndex]) - self.content = append(self.content[:self.cursor], self.content[lineEndIndex:]...) - self.autoWrapContent() -} - -func (self *TextArea) GoToStartOfLine() { - if self.atSoftLineStart() { - return - } - - // otherwise, you delete everything up to the start of the current line, without - // deleting the newline character - newlineIndex := self.closestNewlineOnLeft() - self.cursor = newlineIndex + 1 -} - -func (self *TextArea) closestNewlineOnLeft() int { - wrappedCursor := self.origCursorToWrappedCursor(self.cursor) - - newlineIndex := -1 - - for i, r := range self.wrappedContent[0:wrappedCursor] { - if r == '\n' { - newlineIndex = i - } - } - - unwrappedNewlineIndex := self.wrappedCursorToOrigCursor(newlineIndex) - if unwrappedNewlineIndex >= 0 && self.content[unwrappedNewlineIndex] != '\n' { - unwrappedNewlineIndex-- - } - return unwrappedNewlineIndex -} - -func (self *TextArea) GoToEndOfLine() { - if self.atEnd() { - return - } - - self.cursor = self.closestNewlineOnRight() - - self.moveLeftFromSoftLineBreak() -} - -func (self *TextArea) closestNewlineOnRight() int { - wrappedCursor := self.origCursorToWrappedCursor(self.cursor) - - for i, r := range self.wrappedContent[wrappedCursor:] { - if r == '\n' { - return self.wrappedCursorToOrigCursor(wrappedCursor + i) - } - } - - return len(self.content) -} - -func (self *TextArea) moveLeftFromSoftLineBreak() { - // If the end of line is a soft line break, we need to move left by one so - // that we end up at the last whitespace before the line break. Otherwise - // we'd be at the start of the next line, since the newline character - // doesn't really exist in the real content. - if self.cursor < len(self.content) && self.content[self.cursor] != '\n' { - self.cursor-- - } -} - -func (self *TextArea) atLineStart() bool { - return self.cursor == 0 || - (len(self.content) > self.cursor-1 && self.content[self.cursor-1] == '\n') -} - -func (self *TextArea) atSoftLineStart() bool { - wrappedCursor := self.origCursorToWrappedCursor(self.cursor) - return wrappedCursor == 0 || - (len(self.wrappedContent) > wrappedCursor-1 && self.wrappedContent[wrappedCursor-1] == '\n') -} - -func (self *TextArea) atLineEnd() bool { - return self.atEnd() || - (len(self.content) > self.cursor && self.content[self.cursor] == '\n') -} - -func (self *TextArea) atSoftLineEnd() bool { - wrappedCursor := self.origCursorToWrappedCursor(self.cursor) - return wrappedCursor == len(self.wrappedContent) || - (len(self.wrappedContent) > wrappedCursor+1 && self.wrappedContent[wrappedCursor+1] == '\n') -} - -func (self *TextArea) BackSpaceWord() { - if self.cursor == 0 { - return - } - if self.atLineStart() { - self.BackSpaceChar() - return - } - - right := self.cursor - for !self.atLineStart() && strings.ContainsRune(WHITESPACES, self.content[self.cursor-1]) { - self.cursor-- - } - separators := false - for !self.atLineStart() && strings.ContainsRune(WORD_SEPARATORS, self.content[self.cursor-1]) { - self.cursor-- - separators = true - } - if !separators { - for !self.atLineStart() && !strings.ContainsRune(WHITESPACES+WORD_SEPARATORS, self.content[self.cursor-1]) { - self.cursor-- - } - } - - self.clipboard = string(self.content[self.cursor:right]) - self.content = append(self.content[:self.cursor], self.content[right:]...) - self.autoWrapContent() -} - -func (self *TextArea) Yank() { - self.TypeString(self.clipboard) -} - -func origCursorToWrappedCursor(origCursor int, cursorMapping []CursorMapping) int { - prevMapping := CursorMapping{0, 0} - for _, mapping := range cursorMapping { - if origCursor < mapping.Orig { - break - } - prevMapping = mapping - } - - return origCursor + prevMapping.Wrapped - prevMapping.Orig -} - -func (self *TextArea) origCursorToWrappedCursor(origCursor int) int { - return origCursorToWrappedCursor(origCursor, self.cursorMapping) -} - -func wrappedCursorToOrigCursor(wrappedCursor int, cursorMapping []CursorMapping) int { - prevMapping := CursorMapping{0, 0} - for _, mapping := range cursorMapping { - if wrappedCursor < mapping.Wrapped { - break - } - prevMapping = mapping - } - - return wrappedCursor + prevMapping.Orig - prevMapping.Wrapped -} - -func (self *TextArea) wrappedCursorToOrigCursor(wrappedCursor int) int { - return wrappedCursorToOrigCursor(wrappedCursor, self.cursorMapping) -} - -func (self *TextArea) GetCursorXY() (int, int) { - cursorX := 0 - cursorY := 0 - wrappedCursor := self.origCursorToWrappedCursor(self.cursor) - for _, r := range self.wrappedContent[0:wrappedCursor] { - if r == '\n' { - cursorY++ - cursorX = 0 - } else { - chWidth := runewidth.RuneWidth(r) - cursorX += chWidth - } - } - - return cursorX, cursorY -} - -// takes an x,y position and maps it to a 1D cursor position -func (self *TextArea) SetCursor2D(x int, y int) { - if y < 0 { - y = 0 - } - if x < 0 { - x = 0 - } - - newCursor := 0 - for _, r := range self.wrappedContent { - if x <= 0 && y == 0 { - self.cursor = self.wrappedCursorToOrigCursor(newCursor) - if self.wrappedContent[newCursor] == '\n' { - self.moveLeftFromSoftLineBreak() - } - return - } - - if r == '\n' { - if y == 0 { - self.cursor = self.wrappedCursorToOrigCursor(newCursor) - self.moveLeftFromSoftLineBreak() - return - } - y-- - } else if y == 0 { - chWidth := runewidth.RuneWidth(r) - x -= chWidth - } - - newCursor++ - } - - // if we weren't able to run-down our arg, the user is trying to move out of - // bounds so we'll just return - if y > 0 { - return - } - - self.cursor = self.wrappedCursorToOrigCursor(newCursor) -} - -func (self *TextArea) Clear() { - self.content = []rune{} - self.wrappedContent = []rune{} - self.cursor = 0 -} - -func (self *TextArea) TypeString(str string) { - for _, r := range str { - self.TypeRune(r) - } -} diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go deleted file mode 100644 index 70cbd9735e4..00000000000 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ /dev/null @@ -1,1909 +0,0 @@ -// Copyright 2014 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gocui - -import ( - "bytes" - "fmt" - "io" - "strings" - "sync" - "unicode" - "unicode/utf8" - - "github.com/gdamore/tcell/v2" - "github.com/mattn/go-runewidth" -) - -// Constants for overlapping edges -const ( - TOP = 1 // view is overlapping at top edge - BOTTOM = 2 // view is overlapping at bottom edge - LEFT = 4 // view is overlapping at left edge - RIGHT = 8 // view is overlapping at right edge -) - -// A View is a window. It maintains its own internal buffer and cursor -// position. -type View struct { - name string - x0, y0, x1, y1 int // left top right bottom - ox, oy int // view offsets - cx, cy int // cursor position - rx, ry int // Read() offsets - wx, wy int // Write() offsets - lines [][]cell // All the data - outMode OutputMode - // The y position of the first line of a range selection. - // This is not relative to the view's origin: it is relative to the first line - // of the view's content, so you can scroll the view and this value will remain - // the same, unlike the view's cy value. - // A value of -1 means that there is no range selection. - // This value can be greater than the selected line index, in the event that - // a user starts a range select and then moves the cursor up. - rangeSelectStartY int - - // readBuffer is used for storing unread bytes - readBuffer []byte - - // tained is true if the viewLines must be updated - tainted bool - - // the last position that the mouse was hovering over; nil if the mouse is outside of - // this view, or not hovering over a cell - lastHoverPosition *pos - - // the location of the hyperlink that the mouse is currently hovering over; nil if none - hoveredHyperlink *SearchPosition - - // internal representation of the view's buffer. We will keep viewLines around - // from a previous render until we explicitly set them to nil, allowing us to - // render the same content twice without flicker. Wherever we want to render - // something without any chance of old content appearing (e.g. when actually - // rendering new content or if the view is resized) we should set tainted to - // true and viewLines to nil - viewLines []viewLine - - // If the last character written was a newline, we don't write it but - // instead set pendingNewline to true. If more text is written, we write the - // newline then. This is to avoid having an extra blank at the end of the view. - pendingNewline bool - - // writeMutex protects locks the write process - writeMutex sync.Mutex - - // ei is used to decode ESC sequences on Write - ei *escapeInterpreter - - // Visible specifies whether the view is visible. - Visible bool - - // BgColor and FgColor allow to configure the background and foreground - // colors of the View. - BgColor, FgColor Attribute - - // SelBgColor and SelFgColor are used to configure the background and - // foreground colors of the selected line, when it is highlighted. - SelBgColor, SelFgColor Attribute - - // InactiveViewSelBgColor is used to configure the background color of the - // selected line, when it is highlighted but the view doesn't have the - // focus. - InactiveViewSelBgColor Attribute - - // If Editable is true, keystrokes will be added to the view's internal - // buffer at the cursor position. - Editable bool - - // Editor allows to define the editor that manages the editing mode, - // including keybindings or cursor behaviour. DefaultEditor is used by - // default. - Editor Editor - - // Overwrite enables or disables the overwrite mode of the view. - Overwrite bool - - // If Highlight is true, Sel{Bg,Fg}Colors will be used - // for the line under the cursor position. - Highlight bool - // If HighlightInactive is true, InavtiveViewSel{Bg,Fg}Colors will be used - // instead of Sel{Bg,Fg}Colors for highlighting selected lines. - HighlightInactive bool - - // If Frame is true, a border will be drawn around the view. - Frame bool - - // FrameColor allow to configure the color of the Frame when it is not highlighted. - FrameColor Attribute - - // FrameRunes allows to define custom runes for the frame edges. - // The rune slice can be defined with 3 different lengths. - // If slice doesn't match these lengths, default runes will be used instead of missing one. - // - // 2 runes with only horizontal and vertical edges. - // []rune{'─', '│'} - // []rune{'═','║'} - // 6 runes with horizontal, vertical edges and top-left, top-right, bottom-left, bottom-right cornes. - // []rune{'─', '│', '┌', '┐', '└', '┘'} - // []rune{'═','║','╔','╗','╚','╝'} - // 11 runes which can be used with `gocui.Gui.SupportOverlaps` property. - // []rune{'─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼'} - // []rune{'═','║','╔','╗','╚','╝','╠','╣','╦','╩','╬'} - FrameRunes []rune - - // If Wrap is true, the content that is written to this View is - // automatically wrapped when it is longer than its width. If true the - // view's x-origin will be ignored. - Wrap bool - - // If Autoscroll is true, the View will automatically scroll down when the - // text overflows. If true the view's y-origin will be ignored. - Autoscroll bool - - // If Frame is true, Title allows to configure a title for the view. - Title string - - // If non-empty, TitlePrefix is prepended to the title of a view regardless on - // the the currently selected tab (if any.) - TitlePrefix string - - Tabs []string - TabIndex int - - // TitleColor allow to configure the color of title and subtitle for the view. - TitleColor Attribute - - // If Frame is true, Subtitle allows to configure a subtitle for the view. - Subtitle string - - // If Mask is true, the View will display the mask instead of the real - // content - Mask rune - - // Overlaps describes which edges are overlapping with another view's edges - Overlaps byte - - // If HasLoader is true, the message will be appended with a spinning loader animation - HasLoader bool - - // IgnoreCarriageReturns tells us whether to ignore '\r' characters - IgnoreCarriageReturns bool - - // ParentView is the view which catches events bubbled up from the given view if there's no matching handler - ParentView *View - - searcher *searcher - - // KeybindOnEdit should be set to true when you want to execute keybindings even when the view is editable - // (this is usually not the case) - KeybindOnEdit bool - - TextArea *TextArea - - // something like '1 of 20' for a list view - Footer string - - // if true, the user can scroll all the way past the last item until it appears at the top of the view - CanScrollPastBottom bool - - // if true, the view will automatically recognize https: URLs in the content written to it and render - // them as hyperlinks - AutoRenderHyperLinks bool - - // if true, the view will underline hyperlinks only when the cursor is on - // them; otherwise, they will always be underlined - UnderlineHyperLinksOnlyOnHover bool - - // number of spaces per \t character, defaults to 4 - TabWidth int -} - -type pos struct { - x, y int -} - -// call this in the event of a view resize, or if you want to render new content -// without the chance of old content still appearing, or if you want to remove -// a line from the existing content -func (v *View) clearViewLines() { - v.tainted = true - v.viewLines = nil - v.clearHover() -} - -type searcher struct { - searchString string - searchPositions []SearchPosition - modelSearchResults []SearchPosition - currentSearchIndex int - onSelectItem func(int, int, int) error -} - -func (v *View) SetOnSelectItem(onSelectItem func(int, int, int) error) { - v.searcher.onSelectItem = onSelectItem -} - -func (v *View) gotoNextMatch() error { - if len(v.searcher.searchPositions) == 0 { - return nil - } - if v.searcher.currentSearchIndex >= len(v.searcher.searchPositions)-1 { - v.searcher.currentSearchIndex = 0 - } else { - v.searcher.currentSearchIndex++ - } - return v.SelectSearchResult(v.searcher.currentSearchIndex) -} - -func (v *View) gotoPreviousMatch() error { - if len(v.searcher.searchPositions) == 0 { - return nil - } - if v.searcher.currentSearchIndex == 0 { - if len(v.searcher.searchPositions) > 0 { - v.searcher.currentSearchIndex = len(v.searcher.searchPositions) - 1 - } - } else { - v.searcher.currentSearchIndex-- - } - return v.SelectSearchResult(v.searcher.currentSearchIndex) -} - -func (v *View) SelectSearchResult(index int) error { - itemCount := len(v.searcher.searchPositions) - if itemCount == 0 { - return nil - } - if index > itemCount-1 { - index = itemCount - 1 - } - - y := v.searcher.searchPositions[index].Y - - v.FocusPoint(v.ox, y) - if v.searcher.onSelectItem != nil { - return v.searcher.onSelectItem(y, index, itemCount) - } - return nil -} - -// Returns , -func (v *View) GetSearchStatus() (int, int) { - return v.searcher.currentSearchIndex, len(v.searcher.searchPositions) -} - -// modelSearchResults is optional; pass nil to search the view. If non-nil, -// these positions will be used for highlighting search results. Even in this -// case the view will still be searched on a per-line basis, so that the caller -// doesn't have to make assumptions where in the rendered line the search result -// is. The XStart and XEnd values in the modelSearchResults are only used in -// case the search string is not found in the given line, which can happen if -// the view renders an abbreviated version of some of the model data. -// -// Mind the difference between nil and empty slice: nil means we're not -// searching the model, empty slice means we *are* searching the model but we -// didn't find any matches. -func (v *View) UpdateSearchResults(str string, modelSearchResults []SearchPosition) { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.searcher.search(str, modelSearchResults) - v.updateSearchPositions() - - if len(v.searcher.searchPositions) > 0 { - // get the first result past the current cursor - currentIndex := 0 - adjustedY := v.oy + v.cy - adjustedX := v.ox + v.cx - for i, pos := range v.searcher.searchPositions { - if pos.Y > adjustedY || (pos.Y == adjustedY && pos.XStart > adjustedX) { - currentIndex = i - break - } - } - v.searcher.currentSearchIndex = currentIndex - } -} - -func (v *View) Search(str string, modelSearchResults []SearchPosition) error { - v.UpdateSearchResults(str, modelSearchResults) - - if len(v.searcher.searchPositions) > 0 { - return v.SelectSearchResult(v.searcher.currentSearchIndex) - } - - return v.searcher.onSelectItem(-1, -1, 0) -} - -func (v *View) ClearSearch() { - v.searcher.clearSearch() -} - -func (v *View) IsSearching() bool { - return v.searcher.searchString != "" -} - -func (v *View) FocusPoint(cx int, cy int) { - lineCount := len(v.lines) - if cy < 0 || cy > lineCount { - return - } - height := v.InnerHeight() - - v.oy = calculateNewOrigin(cy, v.oy, lineCount, height) - v.cx = cx - v.cy = cy - v.oy -} - -func (v *View) SetRangeSelectStart(rangeSelectStartY int) { - v.rangeSelectStartY = rangeSelectStartY -} - -func (v *View) CancelRangeSelect() { - v.rangeSelectStartY = -1 -} - -func calculateNewOrigin(selectedLine int, oldOrigin int, lineCount int, viewHeight int) int { - if viewHeight >= lineCount { - return 0 - } else if selectedLine < oldOrigin || selectedLine >= oldOrigin+viewHeight { - // If the selected line is outside the visible area, scroll the view so - // that the selected line is in the middle. - newOrigin := selectedLine - viewHeight/2 - - // However, take care not to overflow if the total line count is less - // than the view height. - maxOrigin := lineCount - viewHeight - if newOrigin > maxOrigin { - newOrigin = maxOrigin - } - if newOrigin < 0 { - newOrigin = 0 - } - - return newOrigin - } - - return oldOrigin -} - -func (s *searcher) search(str string, modelSearchResults []SearchPosition) { - s.searchString = str - s.searchPositions = []SearchPosition{} - s.modelSearchResults = modelSearchResults - s.currentSearchIndex = 0 -} - -func (s *searcher) clearSearch() { - s.searchString = "" - s.searchPositions = []SearchPosition{} - s.currentSearchIndex = 0 -} - -type SearchPosition struct { - XStart int - XEnd int - Y int -} - -type viewLine struct { - linesX, linesY int // coordinates relative to v.lines - line []cell -} - -type cell struct { - chr rune - bgColor, fgColor Attribute - hyperlink string -} - -type lineType []cell - -// String returns a string from a given cell slice. -func (l lineType) String() string { - str := "" - for _, c := range l { - str += string(c.chr) - } - return str -} - -// NewView returns a new View object. -func NewView(name string, x0, y0, x1, y1 int, mode OutputMode) *View { - v := &View{ - name: name, - x0: x0, - y0: y0, - x1: x1, - y1: y1, - Visible: true, - Frame: true, - Editor: DefaultEditor, - tainted: true, - outMode: mode, - ei: newEscapeInterpreter(mode), - searcher: &searcher{}, - TextArea: &TextArea{}, - rangeSelectStartY: -1, - TabWidth: 4, - } - - v.FgColor, v.BgColor = ColorDefault, ColorDefault - v.SelFgColor, v.SelBgColor = ColorDefault, ColorDefault - v.InactiveViewSelBgColor = ColorDefault - v.TitleColor, v.FrameColor = ColorDefault, ColorDefault - return v -} - -// Dimensions returns the dimensions of the View -func (v *View) Dimensions() (int, int, int, int) { - return v.x0, v.y0, v.x1, v.y1 -} - -// Size returns the number of visible columns and rows in the View, including -// the frame if any -func (v *View) Size() (x, y int) { - return v.Width(), v.Height() -} - -// InnerSize returns the number of usable columns and rows in the View, excluding -// the frame if any -func (v *View) InnerSize() (x, y int) { - return v.InnerWidth(), v.InnerHeight() -} - -func (v *View) Width() int { - return v.x1 - v.x0 + 1 -} - -func (v *View) Height() int { - return v.y1 - v.y0 + 1 -} - -// The writeable area of the view is always two less then the view's size, -// because if it has a frame, we need to subtract that, but if it doesn't, the -// view is made 1 larger on all sides. I'd like to clean this up at some point, -// but for now we live with this weirdness. -func (v *View) InnerWidth() int { - innerWidth := v.Width() - 2 - if innerWidth < 0 { - return 0 - } - - return innerWidth -} - -func (v *View) InnerHeight() int { - innerHeight := v.Height() - 2 - if innerHeight < 0 { - return 0 - } - - return innerHeight -} - -// Name returns the name of the view. -func (v *View) Name() string { - return v.name -} - -// setRune sets a rune at the given point relative to the view. It applies the -// specified colors, taking into account if the cell must be highlighted. Also, -// it checks if the position is valid. -func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) { - maxX, maxY := v.Size() - if x < 0 || x >= maxX || y < 0 || y >= maxY { - return - } - - if v.Mask != 0 { - fgColor = v.FgColor - bgColor = v.BgColor - ch = v.Mask - } else if v.Highlight { - rangeSelectStart := v.cy - rangeSelectEnd := v.cy - if v.rangeSelectStartY != -1 { - relativeRangeSelectStart := v.rangeSelectStartY - v.oy - rangeSelectStart = min(relativeRangeSelectStart, v.cy) - rangeSelectEnd = max(relativeRangeSelectStart, v.cy) - } - - if y >= rangeSelectStart && y <= rangeSelectEnd { - // this ensures we use the bright variant of a colour upon highlight - fgColorComponent := fgColor & ^AttrAll - if fgColorComponent >= AttrIsValidColor && fgColorComponent < AttrIsValidColor+8 { - fgColor += 8 - } - fgColor = fgColor | AttrBold - if v.HighlightInactive { - bgColor = (bgColor & AttrStyleBits) | v.InactiveViewSelBgColor - } else { - bgColor = (bgColor & AttrStyleBits) | v.SelBgColor - } - } - } - - if matched, selected := v.isPatternMatchedRune(x, y); matched { - fgColor = ColorBlack - if selected { - bgColor = ColorCyan - } else { - bgColor = ColorYellow - } - } - - if v.isHoveredHyperlink(x, y) { - fgColor |= AttrUnderline - } - - // Don't display NUL characters - if ch == 0 { - ch = ' ' - } - - tcellSetCell(v.x0+x+1, v.y0+y+1, ch, fgColor, bgColor, v.outMode) -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -// SetCursor sets the cursor position of the view at the given point, -// relative to the view. It is allowed to set the position to a point outside -// the visible portion of the view, or even outside the content of the view. -// Clients are responsible for clamping to valid positions. -func (v *View) SetCursor(x, y int) { - v.cx = x - v.cy = y -} - -func (v *View) SetCursorX(x int) { - v.cx = x -} - -func (v *View) SetCursorY(y int) { - v.cy = y -} - -// Cursor returns the cursor position of the view. -func (v *View) Cursor() (x, y int) { - return v.cx, v.cy -} - -func (v *View) CursorX() int { - return v.cx -} - -func (v *View) CursorY() int { - return v.cy -} - -// SetOrigin sets the origin position of the view's internal buffer, -// so the buffer starts to be printed from this point, which means that -// it is linked with the origin point of view. It can be used to -// implement Horizontal and Vertical scrolling with just incrementing -// or decrementing ox and oy. -func (v *View) SetOrigin(x, y int) { - if x < 0 { - x = 0 - } - if y < 0 { - y = 0 - } - - v.ox = x - v.oy = y -} - -func (v *View) SetOriginX(x int) { - if x < 0 { - x = 0 - } - v.ox = x -} - -func (v *View) SetOriginY(y int) { - if y < 0 { - y = 0 - } - v.oy = y -} - -// Origin returns the origin position of the view. -func (v *View) Origin() (x, y int) { - return v.OriginX(), v.OriginY() -} - -func (v *View) OriginX() int { - return v.ox -} - -func (v *View) OriginY() int { - return v.oy -} - -// SetWritePos sets the write position of the view's internal buffer. -// So the next Write call would write directly to the specified position. -func (v *View) SetWritePos(x, y int) { - if x < 0 { - x = 0 - } - if y < 0 { - y = 0 - } - - v.wx = x - v.wy = y - - // Changing the write position makes a pending newline obsolete - v.pendingNewline = false -} - -// WritePos returns the current write position of the view's internal buffer. -func (v *View) WritePos() (x, y int) { - return v.wx, v.wy -} - -// SetReadPos sets the read position of the view's internal buffer. -// So the next Read call would read from the specified position. -func (v *View) SetReadPos(x, y int) { - if x < 0 { - x = 0 - } - if y < 0 { - y = 0 - } - - v.readBuffer = nil - v.rx = x - v.ry = y -} - -// ReadPos returns the current read position of the view's internal buffer. -func (v *View) ReadPos() (x, y int) { - return v.rx, v.ry -} - -// makeWriteable creates empty cells if required to make position (x, y) writeable. -func (v *View) makeWriteable(x, y int) { - // TODO: make this more efficient - - // line `y` must be index-able (that's why `<=`) - for len(v.lines) <= y { - if cap(v.lines) > len(v.lines) { - newLen := cap(v.lines) - if newLen > y { - newLen = y + 1 - } - v.lines = v.lines[:newLen] - } else { - v.lines = append(v.lines, nil) - } - } - // cell `x` need not be index-able (that's why `<`) - // append should be used by `lines[y]` user if he wants to write beyond `x` - for len(v.lines[y]) < x { - if cap(v.lines[y]) > len(v.lines[y]) { - newLen := cap(v.lines[y]) - if newLen > x { - newLen = x - } - v.lines[y] = v.lines[y][:newLen] - } else { - v.lines[y] = append(v.lines[y], cell{}) - } - } -} - -// writeCells copies []cell to specified location (x, y) -// !!! caller MUST ensure that specified location (x, y) is writeable by calling makeWriteable -func (v *View) writeCells(x, y int, cells []cell) { - var newLen int - // use maximum len available - line := v.lines[y][:cap(v.lines[y])] - maxCopy := len(line) - x - if maxCopy < len(cells) { - copy(line[x:], cells[:maxCopy]) - line = append(line, cells[maxCopy:]...) - newLen = len(line) - } else { // maxCopy >= len(cells) - copy(line[x:], cells) - newLen = x + len(cells) - if newLen < len(v.lines[y]) { - newLen = len(v.lines[y]) - } - } - v.lines[y] = line[:newLen] -} - -// Write appends a byte slice into the view's internal buffer. Because -// View implements the io.Writer interface, it can be passed as parameter -// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must -// be called to clear the view's buffer. -func (v *View) Write(p []byte) (n int, err error) { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.writeRunes(bytes.Runes(p)) - - return len(p), nil -} - -func (v *View) WriteRunes(p []rune) { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.writeRunes(p) -} - -// writeRunes copies slice of runes into internal lines buffer. -func (v *View) writeRunes(p []rune) { - v.tainted = true - v.clearHover() - - // Fill with empty cells, if writing outside current view buffer - v.makeWriteable(v.wx, v.wy) - - finishLine := func() { - v.autoRenderHyperlinksInCurrentLine() - if v.wx >= len(v.lines[v.wy]) { - v.writeCells(v.wx, v.wy, []cell{{ - chr: 0, - fgColor: 0, - bgColor: 0, - }}) - } - } - - advanceToNextLine := func() { - v.wx = 0 - v.wy++ - if v.wy >= len(v.lines) { - v.lines = append(v.lines, nil) - } - } - - if v.pendingNewline { - advanceToNextLine() - v.pendingNewline = false - } - - until := len(p) - if !v.Editable && until > 0 && p[until-1] == '\n' { - v.pendingNewline = true - until-- - } - - for _, r := range p[:until] { - switch r { - case '\n': - finishLine() - advanceToNextLine() - case '\r': - finishLine() - v.wx = 0 - default: - truncateLine, cells := v.parseInput(r, v.wx, v.wy) - if cells == nil { - continue - } - v.writeCells(v.wx, v.wy, cells) - if truncateLine { - length := v.wx + len(cells) - v.lines[v.wy] = v.lines[v.wy][:length] - } else { - v.wx += len(cells) - } - } - } - - if v.pendingNewline { - finishLine() - } else { - v.autoRenderHyperlinksInCurrentLine() - } - - v.updateSearchPositions() -} - -// exported functions use the mutex. Non-exported functions are for internal use -// and a calling function should use a mutex -func (v *View) WriteString(s string) { - v.WriteRunes([]rune(s)) -} - -func (v *View) writeString(s string) { - v.writeRunes([]rune(s)) -} - -func findSubstring(line []cell, substringToFind []rune) int { - for i := 0; i < len(line)-len(substringToFind); i++ { - for j := 0; j < len(substringToFind); j++ { - if line[i+j].chr != substringToFind[j] { - break - } - if j == len(substringToFind)-1 { - return i - } - } - } - return -1 -} - -func (v *View) autoRenderHyperlinksInCurrentLine() { - if !v.AutoRenderHyperLinks { - return - } - - // We need a heuristic to find the end of a hyperlink. Searching for the - // first character that is not a valid URI character is not quite good - // enough, because in markdown it's common to have a hyperlink followed by a - // ')', so we want to stop there. Hopefully URLs containing ')' are uncommon - // enough that this is not a problem. - lineEndCharacters := map[rune]bool{ - '\000': true, - ' ': true, - '\n': true, - '>': true, - '"': true, - ')': true, - } - line := v.lines[v.wy] - start := 0 - for { - linkStart := findSubstring(line[start:], []rune("https://")) - if linkStart == -1 { - break - } - linkStart += start - link := "" - linkEnd := linkStart - for ; linkEnd < len(line); linkEnd++ { - if _, ok := lineEndCharacters[line[linkEnd].chr]; ok { - break - } - link += string(line[linkEnd].chr) - } - for i := linkStart; i < linkEnd; i++ { - v.lines[v.wy][i].hyperlink = link - } - start = linkEnd - } -} - -// parseInput parses char by char the input written to the View. It returns nil -// while processing ESC sequences. Otherwise, it returns a cell slice that -// contains the processed data. -func (v *View) parseInput(ch rune, x int, _ int) (bool, []cell) { - cells := []cell{} - truncateLine := false - - isEscape, err := v.ei.parseOne(ch) - if err != nil { - for _, r := range v.ei.runes() { - c := cell{ - fgColor: v.FgColor, - bgColor: v.BgColor, - chr: r, - } - cells = append(cells, c) - } - v.ei.reset() - } else { - repeatCount := 1 - if _, ok := v.ei.instruction.(eraseInLineFromCursor); ok { - // fill rest of line - v.ei.instructionRead() - cx := 0 - for _, cell := range v.lines[v.wy][0:v.wx] { - cx += runewidth.RuneWidth(cell.chr) - } - repeatCount = v.InnerWidth() - cx - ch = ' ' - truncateLine = true - } else if isEscape { - // do not output anything - return truncateLine, nil - } else if ch == '\t' { - // fill tab-sized space - tabWidth := v.TabWidth - if tabWidth < 1 { - tabWidth = 4 - } - ch = ' ' - repeatCount = tabWidth - (x % tabWidth) - } - c := cell{ - fgColor: v.ei.curFgColor, - bgColor: v.ei.curBgColor, - hyperlink: v.ei.hyperlink, - chr: ch, - } - for i := 0; i < repeatCount; i++ { - cells = append(cells, c) - } - } - - return truncateLine, cells -} - -// Read reads data into p from the current reading position set by SetReadPos. -// It returns the number of bytes read into p. -// At EOF, err will be io.EOF. -func (v *View) Read(p []byte) (n int, err error) { - buffer := make([]byte, utf8.UTFMax) - offset := 0 - if v.readBuffer != nil { - copy(p, v.readBuffer) - if len(v.readBuffer) >= len(p) { - if len(v.readBuffer) > len(p) { - v.readBuffer = v.readBuffer[len(p):] - } - return len(p), nil - } - v.readBuffer = nil - } - for v.ry < len(v.lines) { - for v.rx < len(v.lines[v.ry]) { - count := utf8.EncodeRune(buffer, v.lines[v.ry][v.rx].chr) - copy(p[offset:], buffer[:count]) - v.rx++ - newOffset := offset + count - if newOffset >= len(p) { - if newOffset > len(p) { - v.readBuffer = buffer[newOffset-len(p):] - } - return len(p), nil - } - offset += count - } - v.rx = 0 - v.ry++ - } - return offset, io.EOF -} - -// only use this if the calling function has a lock on writeMutex -func (v *View) clear() { - v.rewind() - v.lines = nil - v.clearViewLines() -} - -// Clear empties the view's internal buffer. -// And resets reading and writing offsets. -func (v *View) Clear() { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.clear() -} - -func (v *View) SetContent(str string) { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.clear() - v.writeString(str) -} - -func (v *View) CopyContent(from *View) { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.clear() - - v.lines = from.lines - v.viewLines = from.viewLines - v.ox = from.ox - v.oy = from.oy - v.cx = from.cx - v.cy = from.cy -} - -// Rewind sets read and write pos to (0, 0). -func (v *View) Rewind() { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.rewind() -} - -// similar to Rewind but clears lines. Also similar to Clear but doesn't reset -// viewLines -func (v *View) Reset() { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.rewind() - v.lines = nil -} - -// This is for when we've done a restart for the sake of avoiding a flicker and -// we've reached the end of the new content to display: we need to clear the remaining -// content from the previous round. We do this by setting v.viewLines to nil so that -// we just render the new content from v.lines directly -func (v *View) FlushStaleCells() { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.clearViewLines() -} - -func (v *View) rewind() { - v.ei.reset() - - v.SetReadPos(0, 0) - v.SetWritePos(0, 0) -} - -func containsUpcaseChar(str string) bool { - for _, ch := range str { - if unicode.IsUpper(ch) { - return true - } - } - - return false -} - -func (v *View) updateSearchPositions() { - if v.searcher.searchString != "" { - var normalizeRune func(r rune) rune - var normalizedSearchStr string - // if we have any uppercase characters we'll do a case-sensitive search - if containsUpcaseChar(v.searcher.searchString) { - normalizeRune = func(r rune) rune { return r } - normalizedSearchStr = v.searcher.searchString - } else { - normalizeRune = unicode.ToLower - normalizedSearchStr = strings.ToLower(v.searcher.searchString) - } - - v.searcher.searchPositions = []SearchPosition{} - - searchPositionsForLine := func(line []cell, y int) []SearchPosition { - var result []SearchPosition - searchStringWidth := runewidth.StringWidth(v.searcher.searchString) - x := 0 - for startIdx, c := range line { - found := true - offset := 0 - for _, c := range normalizedSearchStr { - if len(line)-1 < startIdx+offset { - found = false - break - } - if normalizeRune(line[startIdx+offset].chr) != c { - found = false - break - } - offset += 1 - } - if found { - result = append(result, SearchPosition{XStart: x, XEnd: x + searchStringWidth, Y: y}) - } - x += runewidth.RuneWidth(c.chr) - } - return result - } - - if v.searcher.modelSearchResults != nil { - for _, result := range v.searcher.modelSearchResults { - // This code only works when v.Wrap is false. - - if result.Y >= len(v.lines) { - break - } - - // If a view line exists for this line index: - if v.lines[result.Y] != nil { - // search this view line for the search string - positions := searchPositionsForLine(v.lines[result.Y], result.Y) - if len(positions) > 0 { - // If we found any occurrences, add them - v.searcher.searchPositions = append(v.searcher.searchPositions, positions...) - } else { - // Otherwise, the search string was found in the model - // but not in the view line; this can happen if the view - // renders only truncated versions of the model strings. - // In this case, add one search position with what the - // model search function returned. - v.searcher.searchPositions = append(v.searcher.searchPositions, result) - } - } else { - // We don't have a view line for this line index. Add a - // searchPosition anyway, just for the sake of being able to - // show the "n of m" search status. The X positions don't - // matter in this case. - v.searcher.searchPositions = append(v.searcher.searchPositions, SearchPosition{XStart: -1, XEnd: -1, Y: result.Y}) - } - } - } else { - v.refreshViewLinesIfNeeded() - for y, line := range v.viewLines { - v.searcher.searchPositions = append(v.searcher.searchPositions, searchPositionsForLine(line.line, y)...) - } - } - } -} - -// IsTainted tells us if the view is tainted -func (v *View) IsTainted() bool { - return v.tainted -} - -// draw re-draws the view's contents. -func (v *View) draw() { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - if !v.Visible { - return - } - - v.clearRunes() - - maxX, maxY := v.InnerSize() - - if v.Wrap { - if maxX == 0 { - return - } - v.ox = 0 - } - - v.refreshViewLinesIfNeeded() - - visibleViewLinesHeight := v.viewLineLengthIgnoringTrailingBlankLines() - if v.Autoscroll && visibleViewLinesHeight > maxY { - v.oy = visibleViewLinesHeight - maxY - } - - if len(v.viewLines) == 0 { - return - } - - start := v.oy - if start > len(v.viewLines)-1 { - start = len(v.viewLines) - 1 - } - - emptyCell := cell{chr: ' ', fgColor: ColorDefault, bgColor: ColorDefault} - var prevFgColor Attribute - - for y, vline := range v.viewLines[start:] { - if y >= maxY { - break - } - - // x tracks the current x position in the view, and cellIdx tracks the - // index of the cell. If we print a double-sized rune, we increment cellIdx - // by one but x by two. - x := -v.ox - cellIdx := 0 - - var c cell - for { - if x >= maxX { - break - } - - if x < 0 { - if cellIdx < len(vline.line) { - x += runewidth.RuneWidth(vline.line[cellIdx].chr) - cellIdx++ - continue - } else { - // no more characters to write so we're only going to be printing empty cells - // past this point - x = 0 - } - } - - // if we're out of cells to write, we'll just print empty cells. - if cellIdx > len(vline.line)-1 { - c = emptyCell - c.fgColor = prevFgColor - } else { - c = vline.line[cellIdx] - // capturing previous foreground colour so that if we're using the reverse - // attribute we honour the final character's colour and don't awkwardly switch - // to a new background colour for the remainder of the line - prevFgColor = c.fgColor - } - - fgColor := c.fgColor - if fgColor == ColorDefault { - fgColor = v.FgColor - } - bgColor := c.bgColor - if bgColor == ColorDefault { - bgColor = v.BgColor - } - if c.hyperlink != "" && !v.UnderlineHyperLinksOnlyOnHover { - fgColor |= AttrUnderline - } - - v.setRune(x, y, c.chr, fgColor, bgColor) - - // Not sure why the previous code was here but it caused problems - // when typing wide characters in an editor - x += runewidth.RuneWidth(c.chr) - cellIdx++ - } - } -} - -func (v *View) refreshViewLinesIfNeeded() { - if v.tainted { - maxX := v.InnerWidth() - lineIdx := 0 - lines := v.lines - if v.HasLoader { - lines = v.loaderLines() - } - for i, line := range lines { - wrap := 0 - if v.Wrap { - wrap = maxX - } - - ls := lineWrap(line, wrap) - for j := range ls { - vline := viewLine{linesX: j, linesY: i, line: ls[j]} - - if lineIdx > len(v.viewLines)-1 { - v.viewLines = append(v.viewLines, vline) - } else { - v.viewLines[lineIdx] = vline - } - lineIdx++ - } - } - if !v.HasLoader { - v.tainted = false - } - } -} - -// if autoscroll is enabled but we only have a single row of cells shown to the -// user, we don't want to scroll to the final line if it contains no text. So -// this tells us the view lines height when we ignore any trailing blank lines -func (v *View) viewLineLengthIgnoringTrailingBlankLines() int { - for i := len(v.viewLines) - 1; i >= 0; i-- { - if len(v.viewLines[i].line) > 0 { - return i + 1 - } - } - return 0 -} - -func (v *View) isPatternMatchedRune(x, y int) (bool, bool) { - for i, pos := range v.searcher.searchPositions { - adjustedY := y + v.oy - adjustedX := x + v.ox - if adjustedY == pos.Y && adjustedX >= pos.XStart && adjustedX < pos.XEnd { - return true, i == v.searcher.currentSearchIndex - } - } - return false, false -} - -func (v *View) isHoveredHyperlink(x, y int) bool { - if v.UnderlineHyperLinksOnlyOnHover && v.hoveredHyperlink != nil { - adjustedY := y + v.oy - adjustedX := x + v.ox - return adjustedY == v.hoveredHyperlink.Y && adjustedX >= v.hoveredHyperlink.XStart && adjustedX < v.hoveredHyperlink.XEnd - } - return false -} - -// realPosition returns the position in the internal buffer corresponding to the -// point (x, y) of the view. -func (v *View) realPosition(vx, vy int) (x, y int, ok bool) { - vx = v.ox + vx - vy = v.oy + vy - - if vx < 0 || vy < 0 { - return 0, 0, false - } - - if len(v.viewLines) == 0 { - return vx, vy, true - } - - if vy < len(v.viewLines) { - vline := v.viewLines[vy] - x = vline.linesX + vx - y = vline.linesY - } else { - vline := v.viewLines[len(v.viewLines)-1] - x = vx - y = vline.linesY + vy - len(v.viewLines) + 1 - } - - return x, y, true -} - -// clearRunes erases all the cells in the view. -func (v *View) clearRunes() { - maxX, maxY := v.InnerSize() - for x := 0; x < maxX; x++ { - for y := 0; y < maxY; y++ { - tcellSetCell(v.x0+x+1, v.y0+y+1, ' ', v.FgColor, v.BgColor, v.outMode) - } - } -} - -// BufferLines returns the lines in the view's internal -// buffer. -func (v *View) BufferLines() []string { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - lines := make([]string, len(v.lines)) - for i, l := range v.lines { - str := lineType(l).String() - str = strings.Replace(str, "\x00", "", -1) - lines[i] = str - } - return lines -} - -// Buffer returns a string with the contents of the view's internal -// buffer. -func (v *View) Buffer() string { - return linesToString(v.lines) -} - -// ViewBufferLines returns the lines in the view's internal -// buffer that is shown to the user. -func (v *View) ViewBufferLines() []string { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.refreshViewLinesIfNeeded() - - lines := make([]string, len(v.viewLines)) - for i, l := range v.viewLines { - str := lineType(l.line).String() - str = strings.Replace(str, "\x00", "", -1) - lines[i] = str - } - return lines -} - -// LinesHeight is the count of view lines (i.e. lines excluding wrapping) -func (v *View) LinesHeight() int { - return len(v.lines) -} - -// ViewLinesHeight is the count of view lines (i.e. lines including wrapping) -func (v *View) ViewLinesHeight() int { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.refreshViewLinesIfNeeded() - return len(v.viewLines) -} - -// ViewBuffer returns a string with the contents of the view's buffer that is -// shown to the user. -func (v *View) ViewBuffer() string { - lines := make([][]cell, len(v.viewLines)) - for i := range v.viewLines { - lines[i] = v.viewLines[i].line - } - - return linesToString(lines) -} - -// Line returns a string with the line of the view's internal buffer -// at the position corresponding to the point (x, y). -func (v *View) Line(y int) (string, bool) { - _, y, ok := v.realPosition(0, y) - if !ok { - return "", false - } - - if y < 0 || y >= len(v.lines) { - return "", false - } - - return lineType(v.lines[y]).String(), true -} - -// Word returns a string with the word of the view's internal buffer -// at the position corresponding to the point (x, y). -func (v *View) Word(x, y int) (string, bool) { - x, y, ok := v.realPosition(x, y) - if !ok { - return "", false - } - - if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) { - return "", false - } - - str := lineType(v.lines[y]).String() - - nl := strings.LastIndexFunc(str[:x], indexFunc) - if nl == -1 { - nl = 0 - } else { - nl = nl + 1 - } - nr := strings.IndexFunc(str[x:], indexFunc) - if nr == -1 { - nr = len(str) - } else { - nr = nr + x - } - return str[nl:nr], true -} - -// indexFunc allows to split lines by words taking into account spaces -// and 0. -func indexFunc(r rune) bool { - return r == ' ' || r == 0 -} - -// SetHighlight toggles highlighting of separate lines, for custom lists -// or multiple selection in views. -func (v *View) SetHighlight(y int, on bool) { - if y < 0 || y >= len(v.lines) { - return - } - - line := v.lines[y] - cells := make([]cell, 0) - for _, c := range line { - if on { - c.bgColor = v.SelBgColor - c.fgColor = v.SelFgColor - } else { - c.bgColor = v.BgColor - c.fgColor = v.FgColor - } - cells = append(cells, c) - } - v.tainted = true - v.lines[y] = cells - v.clearHover() -} - -func lineWrap(line []cell, columns int) [][]cell { - if columns == 0 { - return [][]cell{line} - } - - var n int - var offset int - lastWhitespaceIndex := -1 - lines := make([][]cell, 0, 1) - for i := range line { - currChr := line[i].chr - rw := runewidth.RuneWidth(currChr) - n += rw - // if currChr == 'g' { - // panic(n) - // } - if n > columns { - // This code is convoluted but we've got comprehensive tests so feel free to do whatever you want - // to the code to simplify it so long as our tests still pass. - if currChr == ' ' { - // if the line ends in a space, we'll omit it. This means there'll be no - // way to distinguish between a clean break and a mid-word break, but - // I think it's worth it. - lines = append(lines, line[offset:i]) - offset = i + 1 - n = 0 - } else if currChr == '-' { - // if the last character is hyphen and the width of line is equal to the columns - lines = append(lines, line[offset:i]) - offset = i - n = rw - } else if lastWhitespaceIndex != -1 { - // if there is a space in the line and the line is not breaking at a space/hyphen - if line[lastWhitespaceIndex].chr == '-' { - // if break occurs at hyphen, we'll retain the hyphen - lines = append(lines, line[offset:lastWhitespaceIndex+1]) - } else { - // if break occurs at space, we'll omit the space - lines = append(lines, line[offset:lastWhitespaceIndex]) - } - // Either way, continue *after* the break - offset = lastWhitespaceIndex + 1 - n = 0 - for _, c := range line[offset : i+1] { - n += runewidth.RuneWidth(c.chr) - } - } else { - // in this case we're breaking mid-word - lines = append(lines, line[offset:i]) - offset = i - n = rw - } - lastWhitespaceIndex = -1 - } else if line[i].chr == ' ' || line[i].chr == '-' { - lastWhitespaceIndex = i - } - } - - lines = append(lines, line[offset:]) - return lines -} - -func linesToString(lines [][]cell) string { - str := make([]string, len(lines)) - for i := range lines { - rns := make([]rune, 0, len(lines[i])) - line := lineType(lines[i]).String() - for _, c := range line { - if c != '\x00' { - rns = append(rns, c) - } - } - str[i] = string(rns) - } - - return strings.Join(str, "\n") -} - -// GetClickedTabIndex tells us which tab was clicked -func (v *View) GetClickedTabIndex(x int) int { - if len(v.Tabs) <= 1 { - return 0 - } - - charX := len(v.TitlePrefix) + 1 - if v.TitlePrefix != "" { - charX += 1 - } - if x <= charX { - return -1 - } - for i, tab := range v.Tabs { - charX += runewidth.StringWidth(tab) - if x <= charX { - return i - } - charX += runewidth.StringWidth(" - ") - if x <= charX { - return -1 - } - } - - return -1 -} - -func (v *View) SelectedLineIdx() int { - _, seletedLineIdx := v.SelectedPoint() - return seletedLineIdx -} - -// expected to only be used in tests -func (v *View) SelectedLine() string { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - if len(v.lines) == 0 { - return "" - } - - return v.lineContentAtIdx(v.SelectedLineIdx()) -} - -// expected to only be used in tests -func (v *View) SelectedLines() []string { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - if len(v.lines) == 0 { - return nil - } - - startIdx, endIdx := v.SelectedLineRange() - - lines := make([]string, 0, endIdx-startIdx+1) - for i := startIdx; i <= endIdx; i++ { - lines = append(lines, v.lineContentAtIdx(i)) - } - - return lines -} - -func (v *View) lineContentAtIdx(idx int) string { - line := v.lines[idx] - str := lineType(line).String() - return strings.Replace(str, "\x00", "", -1) -} - -func (v *View) SelectedPoint() (int, int) { - cx, cy := v.Cursor() - ox, oy := v.Origin() - return cx + ox, cy + oy -} - -func (v *View) SelectedLineRange() (int, int) { - _, cy := v.Cursor() - _, oy := v.Origin() - - start := cy + oy - - if v.rangeSelectStartY == -1 { - return start, start - } - - end := v.rangeSelectStartY - - if start > end { - return end, start - } else { - return start, end - } -} - -func (v *View) RenderTextArea() { - v.Clear() - fmt.Fprint(v, v.TextArea.GetContent()) - cursorX, cursorY := v.TextArea.GetCursorXY() - prevOriginX, prevOriginY := v.Origin() - width, height := v.InnerWidth(), v.InnerHeight() - - newViewCursorX, newOriginX := updatedCursorAndOrigin(prevOriginX, width, cursorX) - newViewCursorY, newOriginY := updatedCursorAndOrigin(prevOriginY, height, cursorY) - - v.SetCursor(newViewCursorX, newViewCursorY) - v.SetOrigin(newOriginX, newOriginY) -} - -func updatedCursorAndOrigin(prevOrigin int, size int, cursor int) (int, int) { - var newViewCursor int - newOrigin := prevOrigin - usableSize := size - 1 - - if cursor > prevOrigin+usableSize { - newOrigin = cursor - usableSize - newViewCursor = usableSize - } else if cursor < prevOrigin { - newOrigin = cursor - newViewCursor = 0 - } else { - newViewCursor = cursor - prevOrigin - } - - return newViewCursor, newOrigin -} - -func (v *View) ClearTextArea() { - v.Clear() - - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.TextArea.Clear() - v.SetOrigin(0, 0) - v.SetCursor(0, 0) -} - -func (v *View) overwriteLines(y int, content string) { - // break by newline, then for each line, write it, then add that erase command - v.wx = 0 - v.wy = y - v.clearViewLines() - - lines := strings.Replace(content, "\n", "\x1b[K\n", -1) - // If the last line doesn't end with a linefeed, add the erase command at - // the end too - if !strings.HasSuffix(lines, "\n") { - lines += "\x1b[K" - } - v.writeString(lines) -} - -// only call this function if you don't care where v.wx and v.wy end up -func (v *View) OverwriteLines(y int, content string) { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.overwriteLines(y, content) -} - -// only call this function if you don't care where v.wx and v.wy end up -func (v *View) OverwriteLinesAndClearEverythingElse(y int, content string) { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - - v.overwriteLines(y, content) - - for i := 0; i < y; i += 1 { - v.lines[i] = nil - } - - for i := v.wy + 1; i < len(v.lines); i += 1 { - v.lines[i] = nil - } -} - -func (v *View) SetContentLineCount(lineCount int) { - if lineCount > 0 { - v.makeWriteable(0, lineCount-1) - } - v.lines = v.lines[:lineCount] -} - -func (v *View) ScrollUp(amount int) { - if amount > v.oy { - amount = v.oy - } - - if amount != 0 { - v.oy -= amount - v.cy += amount - - v.clearHover() - } -} - -// ensures we don't scroll past the end of the view's content -func (v *View) ScrollDown(amount int) { - adjustedAmount := v.adjustDownwardScrollAmount(amount) - if adjustedAmount > 0 { - v.oy += adjustedAmount - v.cy -= adjustedAmount - - v.clearHover() - } -} - -func (v *View) ScrollLeft(amount int) { - newOx := v.ox - amount - if newOx < 0 { - newOx = 0 - } - if newOx != v.ox { - v.ox = newOx - - v.clearHover() - } -} - -// not applying any limits to this -func (v *View) ScrollRight(amount int) { - v.ox += amount - - v.clearHover() -} - -func (v *View) adjustDownwardScrollAmount(scrollHeight int) int { - _, oy := v.Origin() - y := oy - if !v.CanScrollPastBottom { - sy := v.InnerHeight() - y += sy - } - scrollableLines := v.ViewLinesHeight() - y - if scrollableLines < 0 { - return 0 - } - - margin := v.scrollMargin() - if scrollableLines-margin < scrollHeight { - scrollHeight = scrollableLines - margin - } - if oy+scrollHeight < 0 { - return 0 - } else { - return scrollHeight - } -} - -// scrollMargin is about how many lines must still appear if you scroll -// all the way down. We'll subtract this from the total amount of scrollable lines -func (v *View) scrollMargin() int { - if v.CanScrollPastBottom { - // Setting to 2 because of the newline at the end of the file that we're likely showing. - // If we want to scroll past bottom outside the context of reading a file's contents, - // we should make this into a field on the view to be configured by the client. - // For now we're hardcoding it. - return 2 - } else { - return 0 - } -} - -// Returns true if the view contains a line containing the given text with the given -// foreground color -func (v *View) ContainsColoredText(fgColor string, text string) bool { - for _, line := range v.lines { - if containsColoredTextInLine(fgColor, text, line) { - return true - } - } - - return false -} - -func containsColoredTextInLine(fgColorStr string, text string, line []cell) bool { - fgColor := tcell.GetColor(fgColorStr) - - currentMatch := "" - for i := 0; i < len(line); i++ { - cell := line[i] - - // stripping attributes by converting to and from hex - cellColor := tcell.NewHexColor(cell.fgColor.Hex()) - - if cellColor == fgColor { - currentMatch += string(cell.chr) - } else if currentMatch != "" { - if strings.Contains(currentMatch, text) { - return true - } - currentMatch = "" - } - } - - return strings.Contains(currentMatch, text) -} - -func (v *View) onMouseMove(x int, y int) { - if v.Editable || !v.UnderlineHyperLinksOnlyOnHover { - return - } - - // newCx and newCy are relative to the view port, i.e. to the visible area of the view - newCx := x - v.x0 - 1 - newCy := y - v.y0 - 1 - // newX and newY are relative to the view's content, independent of its scroll position - newX := newCx + v.ox - newY := newCy + v.oy - - if newY >= 0 && newY <= len(v.viewLines)-1 && newX >= 0 && newX <= len(v.viewLines[newY].line)-1 { - if v.lastHoverPosition == nil || v.lastHoverPosition.x != newX || v.lastHoverPosition.y != newY { - v.hoveredHyperlink = v.findHyperlinkAt(newX, newY) - } - v.lastHoverPosition = &pos{x: newX, y: newY} - } else { - v.lastHoverPosition = nil - v.hoveredHyperlink = nil - } -} - -func (v *View) findHyperlinkAt(x, y int) *SearchPosition { - linkStr := v.viewLines[y].line[x].hyperlink - if linkStr == "" { - return nil - } - - xStart := x - for xStart > 0 && v.viewLines[y].line[xStart-1].hyperlink == linkStr { - xStart-- - } - xEnd := x + 1 - for xEnd < len(v.viewLines[y].line) && v.viewLines[y].line[xEnd].hyperlink == linkStr { - xEnd++ - } - - return &SearchPosition{XStart: xStart, XEnd: xEnd, Y: y} -} - -func (v *View) clearHover() { - v.hoveredHyperlink = nil - v.lastHoverPosition = nil -} diff --git a/vendor/github.com/jesseduffield/lazycore/LICENSE b/vendor/github.com/jesseduffield/lazycore/LICENSE deleted file mode 100644 index 2a7175dcc30..00000000000 --- a/vendor/github.com/jesseduffield/lazycore/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Jesse Duffield - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/jesseduffield/lazycore/pkg/boxlayout/boxlayout.go b/vendor/github.com/jesseduffield/lazycore/pkg/boxlayout/boxlayout.go deleted file mode 100644 index c516cbba751..00000000000 --- a/vendor/github.com/jesseduffield/lazycore/pkg/boxlayout/boxlayout.go +++ /dev/null @@ -1,211 +0,0 @@ -package boxlayout - -import ( - "github.com/jesseduffield/lazycore/pkg/utils" - "github.com/samber/lo" -) - -type Dimensions struct { - X0 int - X1 int - Y0 int - Y1 int -} - -type Direction int - -const ( - ROW Direction = iota - COLUMN -) - -// to give a high-level explanation of what's going on here. We layout our windows by arranging a bunch of boxes in the available space. -// If a box has children, it needs to specify how it wants to arrange those children: ROW or COLUMN. -// If a box represents a window, you can put the window name in the Window field. -// When determining how to divvy-up the available height (for row children) or width (for column children), we first -// give the boxes with a static `size` the space that they want. Then we apportion -// the remaining space based on the weights of the dynamic boxes (you can't define -// both size and weight at the same time: you gotta pick one). If there are two -// boxes, one with weight 1 and the other with weight 2, the first one gets 33% -// of the available space and the second one gets the remaining 66% - -type Box struct { - // Direction decides how the children boxes are laid out. ROW means the children will each form a row i.e. that they will be stacked on top of eachother. - Direction Direction - - // function which takes the width and height assigned to the box and decides which orientation it will have - ConditionalDirection func(width int, height int) Direction - - Children []*Box - - // function which takes the width and height assigned to the box and decides the layout of the children. - ConditionalChildren func(width int, height int) []*Box - - // Window refers to the name of the window this box represents, if there is one - Window string - - // static Size. If parent box's direction is ROW this refers to height, otherwise width - Size int - - // dynamic size. Once all statically sized children have been considered, Weight decides how much of the remaining space will be taken up by the box - // TODO: consider making there be one int and a type enum so we can't have size and Weight simultaneously defined - Weight int -} - -func ArrangeWindows(root *Box, x0, y0, width, height int) map[string]Dimensions { - children := root.getChildren(width, height) - if len(children) == 0 { - // leaf node - if root.Window != "" { - dimensionsForWindow := Dimensions{X0: x0, Y0: y0, X1: x0 + width - 1, Y1: y0 + height - 1} - return map[string]Dimensions{root.Window: dimensionsForWindow} - } - return map[string]Dimensions{} - } - - direction := root.getDirection(width, height) - - var availableSize int - if direction == COLUMN { - availableSize = width - } else { - availableSize = height - } - - sizes := calcSizes(children, availableSize) - - result := map[string]Dimensions{} - offset := 0 - for i, child := range children { - boxSize := sizes[i] - - var resultForChild map[string]Dimensions - if direction == COLUMN { - resultForChild = ArrangeWindows(child, x0+offset, y0, boxSize, height) - } else { - resultForChild = ArrangeWindows(child, x0, y0+offset, width, boxSize) - } - - result = mergeDimensionMaps(result, resultForChild) - offset += boxSize - } - - return result -} - -func calcSizes(boxes []*Box, availableSpace int) []int { - normalizedWeights := normalizeWeights(lo.Map(boxes, func(box *Box, _ int) int { return box.Weight })) - - totalWeight := 0 - reservedSpace := 0 - for i, box := range boxes { - if box.isStatic() { - reservedSpace += box.Size - } else { - totalWeight += normalizedWeights[i] - } - } - - dynamicSpace := utils.Max(0, availableSpace-reservedSpace) - - unitSize := 0 - extraSpace := 0 - if totalWeight > 0 { - unitSize = dynamicSpace / totalWeight - extraSpace = dynamicSpace % totalWeight - } - - result := make([]int, len(boxes)) - for i, box := range boxes { - if box.isStatic() { - // assuming that only one static child can have a size greater than the - // available space. In that case we just crop the size to what's available - result[i] = utils.Min(availableSpace, box.Size) - } else { - result[i] = unitSize * normalizedWeights[i] - } - } - - // distribute the remainder across dynamic boxes. - for extraSpace > 0 { - for i, weight := range normalizedWeights { - if weight > 0 { - result[i]++ - extraSpace-- - normalizedWeights[i]-- - - if extraSpace == 0 { - break - } - } - } - } - - return result -} - -// removes common multiple from weights e.g. if we get 2, 4, 4 we return 1, 2, 2. -func normalizeWeights(weights []int) []int { - if len(weights) == 0 { - return []int{} - } - - // to spare us some computation we'll exit early if any of our weights is 1 - if lo.SomeBy(weights, func(weight int) bool { return weight == 1 }) { - return weights - } - - // map weights to factorSlices and find the lowest common factor - positiveWeights := lo.Filter(weights, func(weight int, _ int) bool { return weight > 0 }) - factorSlices := lo.Map(positiveWeights, func(weight int, _ int) []int { return calcFactors(weight) }) - commonFactors := factorSlices[0] - for _, factors := range factorSlices { - commonFactors = lo.Intersect(commonFactors, factors) - } - - if len(commonFactors) == 0 { - return weights - } - - newWeights := lo.Map(weights, func(weight int, _ int) int { return weight / commonFactors[0] }) - - return normalizeWeights(newWeights) -} - -func calcFactors(n int) []int { - factors := []int{} - for i := 2; i <= n; i++ { - if n%i == 0 { - factors = append(factors, i) - } - } - return factors -} - -func (b *Box) isStatic() bool { - return b.Size > 0 -} - -func (b *Box) getDirection(width int, height int) Direction { - if b.ConditionalDirection != nil { - return b.ConditionalDirection(width, height) - } - return b.Direction -} - -func (b *Box) getChildren(width int, height int) []*Box { - if b.ConditionalChildren != nil { - return b.ConditionalChildren(width, height) - } - return b.Children -} - -func mergeDimensionMaps(a map[string]Dimensions, b map[string]Dimensions) map[string]Dimensions { - result := map[string]Dimensions{} - for _, dimensionMap := range []map[string]Dimensions{a, b} { - for k, v := range dimensionMap { - result[k] = v - } - } - return result -} diff --git a/vendor/github.com/jesseduffield/lazycore/pkg/utils/utils.go b/vendor/github.com/jesseduffield/lazycore/pkg/utils/utils.go deleted file mode 100644 index 06d828e42bf..00000000000 --- a/vendor/github.com/jesseduffield/lazycore/pkg/utils/utils.go +++ /dev/null @@ -1,61 +0,0 @@ -package utils - -import ( - "log" - "os" - "path/filepath" -) - -// Min returns the minimum of two integers -func Min(x, y int) int { - if x < y { - return x - } - return y -} - -// Max returns the maximum of two integers -func Max(x, y int) int { - if x > y { - return x - } - return y -} - -// Clamp returns a value x restricted between min and max -func Clamp(x int, min int, max int) int { - if x < min { - return min - } else if x > max { - return max - } - return x -} - -// GetLazyRootDirectory finds a lazy project root directory. -// -// It's used for cheatsheet scripts and integration tests. Not to be confused with finding the -// root directory of _any_ random repo. -func GetLazyRootDirectory() string { - path, err := os.Getwd() - if err != nil { - panic(err) - } - - for { - _, err := os.Stat(filepath.Join(path, ".git")) - if err == nil { - return path - } - - if !os.IsNotExist(err) { - panic(err) - } - - path = filepath.Dir(path) - - if path == "/" { - log.Fatal("must run in lazy project folder or child folder") - } - } -} diff --git a/vendor/github.com/jesseduffield/minimal/gitignore/LICENSE b/vendor/github.com/jesseduffield/minimal/gitignore/LICENSE deleted file mode 100644 index 56f5e365e59..00000000000 --- a/vendor/github.com/jesseduffield/minimal/gitignore/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2018, iriri -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/jesseduffield/minimal/gitignore/gitignore.go b/vendor/github.com/jesseduffield/minimal/gitignore/gitignore.go deleted file mode 100644 index 740f77d5166..00000000000 --- a/vendor/github.com/jesseduffield/minimal/gitignore/gitignore.go +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright 2018 iriri. All rights reserved. Use of this source code is -// governed by a BSD-style license which can be found in the LICENSE file. - -// Package gitignore can be used to parse .gitignore-style files into lists of -// globs that can be used to test against paths or selectively walk a file -// tree. Gobwas's glob package is used for matching because it is faster than -// using regexp, which is overkill, and supports globstars (**), unlike -// filepath.Match. -package gitignore - -import ( - "bufio" - "errors" - "os" - "os/user" - "path/filepath" - "strings" - - "github.com/gobwas/glob" -) - -type ignoreFile struct { - globs []glob.Glob - abspath []string -} - -type IgnoreList struct { - files []ignoreFile - cwd []string -} - -func toSplit(path string) []string { - return strings.Split(filepath.ToSlash(path), "/") -} - -func fromSplit(path []string) string { - return filepath.FromSlash(strings.Join(path, "/")) -} - -// New creates a new ignore list. -func New() (IgnoreList, error) { - cwd, err := filepath.Abs(".") - if err != nil { - return IgnoreList{}, err - } - files := make([]ignoreFile, 1, 4) - files[0].globs = make([]glob.Glob, 0, 16) - return IgnoreList{ - files, - toSplit(cwd), - }, nil -} - -// From creates a new ignore list and populates the first entry with the -// contents of the specified file. -func From(path string) (IgnoreList, error) { - ign, err := New() - if err == nil { - err = ign.append(path, nil) - } - return ign, err -} - -// FromGit finds the root directory of the current git repository and creates a -// new ignore list with the contents of all .gitignore files in that git -// repository. -func FromGit() (IgnoreList, error) { - ign, err := New() - if err == nil { - err = ign.AppendGit() - } - return ign, err -} - -func clean(s string) string { - i := len(s) - 1 - for ; i >= 0; i-- { - if s[i] != ' ' || i > 0 && s[i-1] == '\\' { - return s[:i+1] - } - } - return "" -} - -// AppendGlob appends a single glob as a new entry in the ignore list. The root -// (relevant for matching patterns that begin with "/") is assumed to be the -// current working directory. -func (ign *IgnoreList) AppendGlob(s string) error { - g, err := glob.Compile(clean(s), '/') - if err == nil { - ign.files[0].globs = append(ign.files[0].globs, g) - } - return err -} - -func toRelpath(s string, dir, cwd []string) string { - if s != "" { - if s[0] != '/' { - return s - } - if dir == nil || cwd == nil { - return s[1:] - } - dir = append(dir, toSplit(s[1:])...) - } - - i := 0 - min := len(cwd) - if len(dir) < min { - min = len(dir) - } - for ; i < min; i++ { - if dir[i] != cwd[i] { - break - } - } - if i == min && len(cwd) == len(dir) { - return "." - } - - ss := make([]string, (len(cwd)-i)+(len(dir)-i)) - j := 0 - for ; j < len(cwd)-i; j++ { - ss[j] = ".." - } - for k := 0; j < len(ss); j, k = j+1, k+1 { - ss[j] = dir[i+k] - } - return fromSplit(ss) -} - -func (ign *IgnoreList) append(path string, dir []string) error { - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - - var ignf *ignoreFile - if dir != nil { - ignf = &ign.files[0] - } else { - d, err := filepath.Abs(filepath.Dir(path)) - if err != nil { - return err - } - if d != fromSplit(ign.cwd) { - dir = toSplit(d) - ignf = &ignoreFile{ - make([]glob.Glob, 0, 16), - dir, - } - } else { - ignf = &ign.files[0] - } - } - scn := bufio.NewScanner(bufio.NewReader(f)) - for scn.Scan() { - s := scn.Text() - if s == "" || s[0] == '#' { - continue - } - g, err := glob.Compile(toRelpath(clean(s), dir, ign.cwd), '/') - if err != nil { - return err - } - ignf.globs = append(ignf.globs, g) - } - ign.files = append(ign.files, *ignf) - return nil -} - -// Append appends the globs in the specified file to the ignore list. Files are -// expected to have the same format as .gitignore files. -func (ign *IgnoreList) Append(path string) error { - return ign.append(path, nil) -} - -func exists(path string) bool { - _, err := os.Stat(path) - return !os.IsNotExist(err) -} - -func findGitRoot(cwd []string) (string, error) { - p := fromSplit(cwd) - for !exists(p + "/.git") { - if len(cwd) == 1 { - return "", errors.New("not in a git repository") - } - cwd = cwd[:len(cwd)-1] - p = fromSplit(cwd) - } - return p, nil -} - -func (ign *IgnoreList) appendAll(fname, root string) error { - return filepath.Walk( - root, - func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if filepath.Base(path) == fname { - ign.append(path, nil) - } - return nil - }) -} - -// AppendGit finds the root directory of the current git repository and appends -// the contents of all .gitignore files in that git repository to the ignore -// list. -func (ign *IgnoreList) AppendGit() error { - gitRoot, err := findGitRoot(ign.cwd) - if err != nil { - return err - } - if err = ign.AppendGlob(".git"); err != nil { - return err - } - usr, err := user.Current() - if err != nil { - return err - } - if gg := filepath.Join(usr.HomeDir, ".gitignore_global"); exists(gg) { - if err = ign.append(gg, toSplit(gitRoot)); err != nil { - return err - } - } - return ign.appendAll(".gitignore", gitRoot) -} - -func isPrefix(abspath, dir []string) bool { - if len(abspath) > len(dir) { - return false - } - for i := range abspath { - if abspath[i] != dir[i] { - return false - } - } - return true -} - -func (ign *IgnoreList) match(path string, info os.FileInfo) bool { - if path == "." { - return false - } - ss := make([]string, 0, 4) - base := filepath.Base(path) - ss = append(ss, path) - if base != path { - ss = append(ss, base) - } else { - ss = append(ss, "./"+path) - } - if info != nil && info.IsDir() { - ss = append(ss, path+"/") - if base != path { - ss = append(ss, base+"/") - } else { - ss = append(ss, "./"+path+"/") - } - } - - d, err := filepath.Abs(filepath.Dir(path)) - if err != nil { - return false - } - dir := toSplit(d) - for _, f := range ign.files { - if isPrefix(f.abspath, dir) || len(f.abspath) == 0 { - for _, g := range f.globs { - for _, s := range ss { - if g.Match(s) { - return true - } - } - } - } - } - return false -} - -// Match returns whether any of the globs in the ignore list match the -// specified path. Uses the same matching rules as .gitignore files. -func (ign *IgnoreList) Match(path string) bool { - return ign.match(path, nil) -} - -// Walk walks the file tree with the specified root and calls fn on each file -// or directory. Files and directories that match any of the globs in the -// ignore list are skipped. -func (ign *IgnoreList) Walk(root string, fn filepath.WalkFunc) error { - abs, err := filepath.Abs(root) - if err != nil { - return err - } - return filepath.Walk( - toRelpath("", toSplit(abs), ign.cwd), - func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if ign.match(path, info) { - if info.IsDir() { - return filepath.SkipDir - } - return err - } - return fn(path, info, err) - }) -} diff --git a/vendor/github.com/jesseduffield/minimal/gitignore/testgitignore b/vendor/github.com/jesseduffield/minimal/gitignore/testgitignore deleted file mode 100644 index b01e8daa4c7..00000000000 --- a/vendor/github.com/jesseduffield/minimal/gitignore/testgitignore +++ /dev/null @@ -1,10 +0,0 @@ -testgitignore -*.o - -*.out* -#*.ou -aa -bbb -ccc/ -**/Makefile -/testfs/ignoredfile* diff --git a/vendor/github.com/kardianos/osext/LICENSE b/vendor/github.com/kardianos/osext/LICENSE deleted file mode 100644 index 74487567632..00000000000 --- a/vendor/github.com/kardianos/osext/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/kardianos/osext/README.md b/vendor/github.com/kardianos/osext/README.md deleted file mode 100644 index 15cbc3d953e..00000000000 --- a/vendor/github.com/kardianos/osext/README.md +++ /dev/null @@ -1,21 +0,0 @@ -### Extensions to the "os" package. - -[![GoDoc](https://godoc.org/github.com/kardianos/osext?status.svg)](https://godoc.org/github.com/kardianos/osext) - -## Find the current Executable and ExecutableFolder. - -As of go1.8 the Executable function may be found in `os`. The Executable function -in the std lib `os` package is used if available. - -There is sometimes utility in finding the current executable file -that is running. This can be used for upgrading the current executable -or finding resources located relative to the executable file. Both -working directory and the os.Args[0] value are arbitrary and cannot -be relied on; os.Args[0] can be "faked". - -Multi-platform and supports: - * Linux - * OS X - * Windows - * Plan 9 - * BSDs. diff --git a/vendor/github.com/kardianos/osext/osext.go b/vendor/github.com/kardianos/osext/osext.go deleted file mode 100644 index 17f380f0e8d..00000000000 --- a/vendor/github.com/kardianos/osext/osext.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Extensions to the standard "os" package. -package osext // import "github.com/kardianos/osext" - -import "path/filepath" - -var cx, ce = executableClean() - -func executableClean() (string, error) { - p, err := executable() - return filepath.Clean(p), err -} - -// Executable returns an absolute path that can be used to -// re-invoke the current program. -// It may not be valid after the current program exits. -func Executable() (string, error) { - return cx, ce -} - -// Returns same path as Executable, returns just the folder -// path. Excludes the executable name and any trailing slash. -func ExecutableFolder() (string, error) { - p, err := Executable() - if err != nil { - return "", err - } - - return filepath.Dir(p), nil -} diff --git a/vendor/github.com/kardianos/osext/osext_go18.go b/vendor/github.com/kardianos/osext/osext_go18.go deleted file mode 100644 index 009d8a9262b..00000000000 --- a/vendor/github.com/kardianos/osext/osext_go18.go +++ /dev/null @@ -1,9 +0,0 @@ -//+build go1.8,!openbsd - -package osext - -import "os" - -func executable() (string, error) { - return os.Executable() -} diff --git a/vendor/github.com/kardianos/osext/osext_plan9.go b/vendor/github.com/kardianos/osext/osext_plan9.go deleted file mode 100644 index 95e237137ac..00000000000 --- a/vendor/github.com/kardianos/osext/osext_plan9.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//+build !go1.8 - -package osext - -import ( - "os" - "strconv" - "syscall" -) - -func executable() (string, error) { - f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text") - if err != nil { - return "", err - } - defer f.Close() - return syscall.Fd2path(int(f.Fd())) -} diff --git a/vendor/github.com/kardianos/osext/osext_procfs.go b/vendor/github.com/kardianos/osext/osext_procfs.go deleted file mode 100644 index e1f16f88511..00000000000 --- a/vendor/github.com/kardianos/osext/osext_procfs.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !go1.8,android !go1.8,linux !go1.8,netbsd !go1.8,solaris !go1.8,dragonfly - -package osext - -import ( - "errors" - "fmt" - "os" - "runtime" - "strings" -) - -func executable() (string, error) { - switch runtime.GOOS { - case "linux", "android": - const deletedTag = " (deleted)" - execpath, err := os.Readlink("/proc/self/exe") - if err != nil { - return execpath, err - } - execpath = strings.TrimSuffix(execpath, deletedTag) - execpath = strings.TrimPrefix(execpath, deletedTag) - return execpath, nil - case "netbsd": - return os.Readlink("/proc/curproc/exe") - case "dragonfly": - return os.Readlink("/proc/curproc/file") - case "solaris": - return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid())) - } - return "", errors.New("ExecPath not implemented for " + runtime.GOOS) -} diff --git a/vendor/github.com/kardianos/osext/osext_sysctl.go b/vendor/github.com/kardianos/osext/osext_sysctl.go deleted file mode 100644 index 33cee2522be..00000000000 --- a/vendor/github.com/kardianos/osext/osext_sysctl.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !go1.8,darwin !go1.8,freebsd openbsd - -package osext - -import ( - "os" - "os/exec" - "path/filepath" - "runtime" - "syscall" - "unsafe" -) - -var initCwd, initCwdErr = os.Getwd() - -func executable() (string, error) { - var mib [4]int32 - switch runtime.GOOS { - case "freebsd": - mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} - case "darwin": - mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} - case "openbsd": - mib = [4]int32{1 /* CTL_KERN */, 55 /* KERN_PROC_ARGS */, int32(os.Getpid()), 1 /* KERN_PROC_ARGV */} - } - - n := uintptr(0) - // Get length. - _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) - if errNum != 0 { - return "", errNum - } - if n == 0 { // This shouldn't happen. - return "", nil - } - buf := make([]byte, n) - _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) - if errNum != 0 { - return "", errNum - } - if n == 0 { // This shouldn't happen. - return "", nil - } - - var execPath string - switch runtime.GOOS { - case "openbsd": - // buf now contains **argv, with pointers to each of the C-style - // NULL terminated arguments. - var args []string - argv := uintptr(unsafe.Pointer(&buf[0])) - Loop: - for { - argp := *(**[1 << 20]byte)(unsafe.Pointer(argv)) - if argp == nil { - break - } - for i := 0; uintptr(i) < n; i++ { - // we don't want the full arguments list - if string(argp[i]) == " " { - break Loop - } - if argp[i] != 0 { - continue - } - args = append(args, string(argp[:i])) - n -= uintptr(i) - break - } - if n < unsafe.Sizeof(argv) { - break - } - argv += unsafe.Sizeof(argv) - n -= unsafe.Sizeof(argv) - } - execPath = args[0] - // There is no canonical way to get an executable path on - // OpenBSD, so check PATH in case we are called directly - if execPath[0] != '/' && execPath[0] != '.' { - execIsInPath, err := exec.LookPath(execPath) - if err == nil { - execPath = execIsInPath - } - } - default: - for i, v := range buf { - if v == 0 { - buf = buf[:i] - break - } - } - execPath = string(buf) - } - - var err error - // execPath will not be empty due to above checks. - // Try to get the absolute path if the execPath is not rooted. - if execPath[0] != '/' { - execPath, err = getAbs(execPath) - if err != nil { - return execPath, err - } - } - // For darwin KERN_PROCARGS may return the path to a symlink rather than the - // actual executable. - if runtime.GOOS == "darwin" { - if execPath, err = filepath.EvalSymlinks(execPath); err != nil { - return execPath, err - } - } - return execPath, nil -} - -func getAbs(execPath string) (string, error) { - if initCwdErr != nil { - return execPath, initCwdErr - } - // The execPath may begin with a "../" or a "./" so clean it first. - // Join the two paths, trailing and starting slashes undetermined, so use - // the generic Join function. - return filepath.Join(initCwd, filepath.Clean(execPath)), nil -} diff --git a/vendor/github.com/kardianos/osext/osext_windows.go b/vendor/github.com/kardianos/osext/osext_windows.go deleted file mode 100644 index 074b3b385ca..00000000000 --- a/vendor/github.com/kardianos/osext/osext_windows.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//+build !go1.8 - -package osext - -import ( - "syscall" - "unicode/utf16" - "unsafe" -) - -var ( - kernel = syscall.MustLoadDLL("kernel32.dll") - getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") -) - -// GetModuleFileName() with hModule = NULL -func executable() (exePath string, err error) { - return getModuleFileName() -} - -func getModuleFileName() (string, error) { - var n uint32 - b := make([]uint16, syscall.MAX_PATH) - size := uint32(len(b)) - - r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) - n = uint32(r0) - if n == 0 { - return "", e1 - } - return string(utf16.Decode(b[0:n])), nil -} diff --git a/vendor/github.com/karimkhaleel/jsonschema/.gitignore b/vendor/github.com/karimkhaleel/jsonschema/.gitignore deleted file mode 100644 index 8ef0e14fc72..00000000000 --- a/vendor/github.com/karimkhaleel/jsonschema/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -vendor/ -.idea/ diff --git a/vendor/github.com/karimkhaleel/jsonschema/.golangci.yml b/vendor/github.com/karimkhaleel/jsonschema/.golangci.yml deleted file mode 100644 index 3dac8a37df3..00000000000 --- a/vendor/github.com/karimkhaleel/jsonschema/.golangci.yml +++ /dev/null @@ -1,70 +0,0 @@ -run: - tests: true - max-same-issues: 50 - skip-dirs: - - resources - - old - skip-files: - - cmd/protopkg/main.go - -output: - print-issued-lines: false - -linters: - enable: - - gocyclo - - gocritic - - goconst - - dupl - - unconvert - - goimports - - unused - - vetshadow - - nakedret - - errcheck - - revive - - ineffassign - - goconst - - vet - - unparam - - gofmt - -linters-settings: - vet: - check-shadowing: true - use-installed-packages: true - dupl: - threshold: 100 - goconst: - min-len: 8 - min-occurrences: 3 - gocyclo: - min-complexity: 20 - gocritic: - disabled-checks: - - ifElseChain - gofmt: - rewrite-rules: - - pattern: 'interface{}' - replacement: 'any' - - pattern: 'a[b:len(a)]' - replacement: 'a[b:]' - -issues: - max-per-linter: 0 - max-same: 0 - exclude-use-default: false - exclude: - # Captured by errcheck. - - "^(G104|G204):" - # Very commonly not checked. - - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*Print(f|ln|)|os\.(Un)?Setenv). is not checked' - # Weird error only seen on Kochiku... - - "internal error: no range for" - - 'exported method `.*\.(MarshalJSON|UnmarshalJSON|URN|Payload|GoString|Close|Provides|Requires|ExcludeFromHash|MarshalText|UnmarshalText|Description|Check|Poll|Severity)` should have comment or be unexported' - - "composite literal uses unkeyed fields" - - 'declaration of "err" shadows declaration' - - "by other packages, and that stutters" - - "Potential file inclusion via variable" - - "at least one file in a package should have a package comment" - - "bad syntax for struct tag pair" diff --git a/vendor/github.com/karimkhaleel/jsonschema/COPYING b/vendor/github.com/karimkhaleel/jsonschema/COPYING deleted file mode 100644 index 2993ec085d3..00000000000 --- a/vendor/github.com/karimkhaleel/jsonschema/COPYING +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (C) 2014 Alec Thomas - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/karimkhaleel/jsonschema/README.md b/vendor/github.com/karimkhaleel/jsonschema/README.md deleted file mode 100644 index 65ed8aea2af..00000000000 --- a/vendor/github.com/karimkhaleel/jsonschema/README.md +++ /dev/null @@ -1,368 +0,0 @@ -# Go JSON Schema Reflection - -[![Lint](https://github.com/invopop/jsonschema/actions/workflows/lint.yaml/badge.svg)](https://github.com/invopop/jsonschema/actions/workflows/lint.yaml) -[![Test Go](https://github.com/invopop/jsonschema/actions/workflows/test.yaml/badge.svg)](https://github.com/invopop/jsonschema/actions/workflows/test.yaml) -[![Go Report Card](https://goreportcard.com/badge/github.com/invopop/jsonschema)](https://goreportcard.com/report/github.com/invopop/jsonschema) -[![GoDoc](https://godoc.org/github.com/invopop/jsonschema?status.svg)](https://godoc.org/github.com/invopop/jsonschema) -![Latest Tag](https://img.shields.io/github/v/tag/invopop/jsonschema) - -This package can be used to generate [JSON Schemas](http://json-schema.org/latest/json-schema-validation.html) from Go types through reflection. - -- Supports arbitrarily complex types, including `interface{}`, maps, slices, etc. -- Supports json-schema features such as minLength, maxLength, pattern, format, etc. -- Supports simple string and numeric enums. -- Supports custom property fields via the `jsonschema_extras` struct tag. - -This repository is a fork of the original [jsonschema](https://github.com/alecthomas/jsonschema) by [@alecthomas](https://github.com/alecthomas). At [Invopop](https://invopop.com) we use jsonschema as a cornerstone in our [GOBL library](https://github.com/invopop/gobl), and wanted to be able to continue building and adding features without taking up Alec's time. There have been a few significant changes that probably mean this version is a not compatible with with Alec's: - -- The original was stuck on the draft-04 version of JSON Schema, we've now moved to the latest JSON Schema Draft 2020-12. -- Schema IDs are added automatically from the current Go package's URL in order to be unique, and can be disabled with the `Anonymous` option. -- Support for the `FullyQualifyTypeName` option has been removed. If you have conflicts, you should use multiple schema files with different IDs, set the `DoNotReference` option to true to hide definitions completely, or add your own naming strategy using the `Namer` property. -- Support for `yaml` tags and related options has been dropped for the sake of simplification. There were a [few inconsistencies](https://github.com/invopop/jsonschema/pull/21) around this that have now been fixed. - -## Versions - -This project is still under v0 scheme, as per Go convention, breaking changes are likely. Please pin go modules to version tags or branches, and reach out if you think something can be improved. - -Go version >= 1.18 is required as generics are now being used. - -## Example - -The following Go type: - -```go -type TestUser struct { - ID int `json:"id"` - Name string `json:"name" jsonschema:"title=the name,description=The name of a friend,example=joe,example=lucy,default=alex"` - Friends []int `json:"friends,omitempty" jsonschema_description:"The list of IDs, omitted when empty"` - Tags map[string]interface{} `json:"tags,omitempty" jsonschema_extras:"a=b,foo=bar,foo=bar1"` - BirthDate time.Time `json:"birth_date,omitempty" jsonschema:"oneof_required=date"` - YearOfBirth string `json:"year_of_birth,omitempty" jsonschema:"oneof_required=year"` - Metadata interface{} `json:"metadata,omitempty" jsonschema:"oneof_type=string;array"` - FavColor string `json:"fav_color,omitempty" jsonschema:"enum=red,enum=green,enum=blue"` -} -``` - -Results in following JSON Schema: - -```go -jsonschema.Reflect(&TestUser{}) -``` - -```json -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/invopop/jsonschema_test/sample-user", - "$ref": "#/$defs/SampleUser", - "$defs": { - "SampleUser": { - "oneOf": [ - { - "required": ["birth_date"], - "title": "date" - }, - { - "required": ["year_of_birth"], - "title": "year" - } - ], - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string", - "title": "the name", - "description": "The name of a friend", - "default": "alex", - "examples": ["joe", "lucy"] - }, - "friends": { - "items": { - "type": "integer" - }, - "type": "array", - "description": "The list of IDs, omitted when empty" - }, - "tags": { - "type": "object", - "a": "b", - "foo": ["bar", "bar1"] - }, - "birth_date": { - "type": "string", - "format": "date-time" - }, - "year_of_birth": { - "type": "string" - }, - "metadata": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array" - } - ] - }, - "fav_color": { - "type": "string", - "enum": ["red", "green", "blue"] - } - }, - "additionalProperties": false, - "type": "object", - "required": ["id", "name"] - } - } -} -``` - -## YAML - -Support for `yaml` tags has now been removed. If you feel very strongly about this, we've opened a discussion to hear your comments: https://github.com/invopop/jsonschema/discussions/28 - -The recommended approach if you need to deal with YAML data is to first convert to JSON. The [invopop/yaml](https://github.com/invopop/yaml) library will make this trivial. - -## Configurable behaviour - -The behaviour of the schema generator can be altered with parameters when a `jsonschema.Reflector` -instance is created. - -### ExpandedStruct - -If set to `true`, makes the top level struct not to reference itself in the definitions. But type passed should be a struct type. - -eg. - -```go -type GrandfatherType struct { - FamilyName string `json:"family_name" jsonschema:"required"` -} - -type SomeBaseType struct { - SomeBaseProperty int `json:"some_base_property"` - // The jsonschema required tag is nonsensical for private and ignored properties. - // Their presence here tests that the fields *will not* be required in the output - // schema, even if they are tagged required. - somePrivateBaseProperty string `json:"i_am_private" jsonschema:"required"` - SomeIgnoredBaseProperty string `json:"-" jsonschema:"required"` - SomeSchemaIgnoredProperty string `jsonschema:"-,required"` - SomeUntaggedBaseProperty bool `jsonschema:"required"` - someUnexportedUntaggedBaseProperty bool - Grandfather GrandfatherType `json:"grand"` -} -``` - -will output: - -```json -{ - "$schema": "http://json-schema.org/draft/2020-12/schema", - "required": ["some_base_property", "grand", "SomeUntaggedBaseProperty"], - "properties": { - "SomeUntaggedBaseProperty": { - "type": "boolean" - }, - "grand": { - "$schema": "http://json-schema.org/draft/2020-12/schema", - "$ref": "#/definitions/GrandfatherType" - }, - "some_base_property": { - "type": "integer" - } - }, - "type": "object", - "$defs": { - "GrandfatherType": { - "required": ["family_name"], - "properties": { - "family_name": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - } - } -} -``` - -### Using Go Comments - -Writing a good schema with descriptions inside tags can become cumbersome and tedious, especially if you already have some Go comments around your types and field definitions. If you'd like to take advantage of these existing comments, you can use the `AddGoComments(base, path string)` method that forms part of the reflector to parse your go files and automatically generate a dictionary of Go import paths, types, and fields, to individual comments. These will then be used automatically as description fields, and can be overridden with a manual definition if needed. - -Take a simplified example of a User struct which for the sake of simplicity we assume is defined inside this package: - -```go -package main - -// User is used as a base to provide tests for comments. -type User struct { - // Unique sequential identifier. - ID int `json:"id" jsonschema:"required"` - // Name of the user - Name string `json:"name"` -} -``` - -To get the comments provided into your JSON schema, use a regular `Reflector` and add the go code using an import module URL and path. Fully qualified go module paths cannot be determined reliably by the `go/parser` library, so we need to introduce this manually: - -```go -r := new(Reflector) -if err := r.AddGoComments("github.com/invopop/jsonschema", "./"); err != nil { - // deal with error -} -s := r.Reflect(&User{}) -// output -``` - -Expect the results to be similar to: - -```json -{ - "$schema": "http://json-schema.org/draft/2020-12/schema", - "$ref": "#/$defs/User", - "$defs": { - "User": { - "required": ["id"], - "properties": { - "id": { - "type": "integer", - "description": "Unique sequential identifier." - }, - "name": { - "type": "string", - "description": "Name of the user" - } - }, - "additionalProperties": false, - "type": "object", - "description": "User is used as a base to provide tests for comments." - } - } -} -``` - -### Custom Key Naming - -In some situations, the keys actually used to write files are different from Go structs'. - -This is often the case when writing a configuration file to YAML or JSON from a Go struct, or when returning a JSON response for a Web API: APIs typically use snake_case, while Go uses PascalCase. - -You can pass a `func(string) string` function to `Reflector`'s `KeyNamer` option to map Go field names to JSON key names and reflect the aforementioned transformations, without having to specify `json:"..."` on every struct field. - -For example, consider the following struct - -```go -type User struct { - GivenName string - PasswordSalted []byte `json:"salted_password"` -} -``` - -We can transform field names to snake_case in the generated JSON schema: - -```go -r := new(jsonschema.Reflector) -r.KeyNamer = strcase.SnakeCase // from package github.com/stoewer/go-strcase - -r.Reflect(&User{}) -``` - -Will yield - -```diff - { - "$schema": "http://json-schema.org/draft/2020-12/schema", - "$ref": "#/$defs/User", - "$defs": { - "User": { - "properties": { -- "GivenName": { -+ "given_name": { - "type": "string" - }, - "salted_password": { - "type": "string", - "contentEncoding": "base64" - } - }, - "additionalProperties": false, - "type": "object", -- "required": ["GivenName", "salted_password"] -+ "required": ["given_name", "salted_password"] - } - } - } -``` - -As you can see, if a field name has a `json:""` tag set, the `key` argument to `KeyNamer` will have the value of that tag. - -### Custom Type Definitions - -Sometimes it can be useful to have custom JSON Marshal and Unmarshal methods in your structs that automatically convert for example a string into an object. - -To override auto-generating an object type for your type, implement the `JSONSchema() *Schema` method and whatever is defined will be provided in the schema definitions. - -You also have the option of defining a `JSONSchemaExtend(schema *jsonschema.Schema)` method for your types that will be called _after_ the schema has been generated, allowing you to add or manipulate the fields easily. - -Take the following simplified example of a `CompactDate` that only includes the Year and Month: - -```go -type CompactDate struct { - Year int - Month int -} - -func (d *CompactDate) UnmarshalJSON(data []byte) error { - if len(data) != 9 { - return errors.New("invalid compact date length") - } - var err error - d.Year, err = strconv.Atoi(string(data[1:5])) - if err != nil { - return err - } - d.Month, err = strconv.Atoi(string(data[7:8])) - if err != nil { - return err - } - return nil -} - -func (d *CompactDate) MarshalJSON() ([]byte, error) { - buf := new(bytes.Buffer) - buf.WriteByte('"') - buf.WriteString(fmt.Sprintf("%d-%02d", d.Year, d.Month)) - buf.WriteByte('"') - return buf.Bytes(), nil -} - -func (CompactDate) JSONSchema() *Schema { - return &Schema{ - Type: "string", - Title: "Compact Date", - Description: "Short date that only includes year and month", - Pattern: "^[0-9]{4}-[0-1][0-9]$", - } -} -``` - -The resulting schema generated for this struct would look like: - -```json -{ - "$schema": "http://json-schema.org/draft/2020-12/schema", - "$ref": "#/$defs/CompactDate", - "$defs": { - "CompactDate": { - "pattern": "^[0-9]{4}-[0-1][0-9]$", - "type": "string", - "title": "Compact Date", - "description": "Short date that only includes year and month" - } - } -} -``` diff --git a/vendor/github.com/karimkhaleel/jsonschema/comment_extractor.go b/vendor/github.com/karimkhaleel/jsonschema/comment_extractor.go deleted file mode 100644 index e157837af55..00000000000 --- a/vendor/github.com/karimkhaleel/jsonschema/comment_extractor.go +++ /dev/null @@ -1,93 +0,0 @@ -package jsonschema - -import ( - "fmt" - "io/fs" - gopath "path" - "path/filepath" - "strings" - - "go/ast" - "go/doc" - "go/parser" - "go/token" -) - -// ExtractGoComments will read all the go files contained in the provided path, -// including sub-directories, in order to generate a dictionary of comments -// associated with Types and Fields. The results will be added to the `commentsMap` -// provided in the parameters and expected to be used for Schema "description" fields. -// -// The `go/parser` library is used to extract all the comments and unfortunately doesn't -// have a built-in way to determine the fully qualified name of a package. The `base` paremeter, -// the URL used to import that package, is thus required to be able to match reflected types. -// -// When parsing type comments, we use the `go/doc`'s Synopsis method to extract the first phrase -// only. Field comments, which tend to be much shorter, will include everything. -func ExtractGoComments(base, path string, commentMap map[string]string) error { - fset := token.NewFileSet() - dict := make(map[string][]*ast.Package) - err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - d, err := parser.ParseDir(fset, path, nil, parser.ParseComments) - if err != nil { - return err - } - for _, v := range d { - // paths may have multiple packages, like for tests - k := gopath.Join(base, path) - dict[k] = append(dict[k], v) - } - } - return nil - }) - if err != nil { - return err - } - - for pkg, p := range dict { - for _, f := range p { - gtxt := "" - typ := "" - ast.Inspect(f, func(n ast.Node) bool { - switch x := n.(type) { - case *ast.TypeSpec: - typ = x.Name.String() - if !ast.IsExported(typ) { - typ = "" - } else { - txt := x.Doc.Text() - if txt == "" && gtxt != "" { - txt = gtxt - gtxt = "" - } - txt = doc.Synopsis(txt) - commentMap[fmt.Sprintf("%s.%s", pkg, typ)] = strings.TrimSpace(txt) - } - case *ast.Field: - txt := x.Doc.Text() - if txt == "" { - txt = x.Comment.Text() - } - if typ != "" && txt != "" { - for _, n := range x.Names { - if ast.IsExported(n.String()) { - k := fmt.Sprintf("%s.%s.%s", pkg, typ, n) - commentMap[k] = strings.TrimSpace(txt) - } - } - } - case *ast.GenDecl: - // remember for the next type - gtxt = x.Doc.Text() - } - return true - }) - } - } - - return nil -} diff --git a/vendor/github.com/karimkhaleel/jsonschema/id.go b/vendor/github.com/karimkhaleel/jsonschema/id.go deleted file mode 100644 index 73fafb38d0c..00000000000 --- a/vendor/github.com/karimkhaleel/jsonschema/id.go +++ /dev/null @@ -1,76 +0,0 @@ -package jsonschema - -import ( - "errors" - "fmt" - "net/url" - "strings" -) - -// ID represents a Schema ID type which should always be a URI. -// See draft-bhutton-json-schema-00 section 8.2.1 -type ID string - -// EmptyID is used to explicitly define an ID with no value. -const EmptyID ID = "" - -// Validate is used to check if the ID looks like a proper schema. -// This is done by parsing the ID as a URL and checking it has all the -// relevant parts. -func (id ID) Validate() error { - u, err := url.Parse(id.String()) - if err != nil { - return fmt.Errorf("invalid URL: %w", err) - } - if u.Hostname() == "" { - return errors.New("missing hostname") - } - if !strings.Contains(u.Hostname(), ".") { - return errors.New("hostname does not look valid") - } - if u.Path == "" { - return errors.New("path is expected") - } - if u.Scheme != "https" && u.Scheme != "http" { - return errors.New("unexpected schema") - } - return nil -} - -// Anchor sets the anchor part of the schema URI. -func (id ID) Anchor(name string) ID { - b := id.Base() - return ID(b.String() + "#" + name) -} - -// Def adds or replaces a definition identifier. -func (id ID) Def(name string) ID { - b := id.Base() - return ID(b.String() + "#/$defs/" + name) -} - -// Add appends the provided path to the id, and removes any -// anchor data that might be there. -func (id ID) Add(path string) ID { - b := id.Base() - if !strings.HasPrefix(path, "/") { - path = "/" + path - } - return ID(b.String() + path) -} - -// Base removes any anchor information from the schema -func (id ID) Base() ID { - s := id.String() - i := strings.LastIndex(s, "#") - if i != -1 { - s = s[0:i] - } - s = strings.TrimRight(s, "/") - return ID(s) -} - -// String provides string version of ID -func (id ID) String() string { - return string(id) -} diff --git a/vendor/github.com/karimkhaleel/jsonschema/reflect.go b/vendor/github.com/karimkhaleel/jsonschema/reflect.go deleted file mode 100644 index 4c8ff1903bb..00000000000 --- a/vendor/github.com/karimkhaleel/jsonschema/reflect.go +++ /dev/null @@ -1,1196 +0,0 @@ -// Package jsonschema uses reflection to generate JSON Schemas from Go types [1]. -// -// If json tags are present on struct fields, they will be used to infer -// property names and if a property is required (omitempty is present). -// -// [1] http://json-schema.org/latest/json-schema-validation.html -package jsonschema - -import ( - "bytes" - "encoding/json" - "net" - "net/url" - "reflect" - "strconv" - "strings" - "time" - - orderedmap "github.com/wk8/go-ordered-map/v2" -) - -// Version is the JSON Schema version. -var Version = "https://json-schema.org/draft/2020-12/schema" - -// Schema represents a JSON Schema object type. -// RFC draft-bhutton-json-schema-00 section 4.3 -type Schema struct { - // RFC draft-bhutton-json-schema-00 - Version string `json:"$schema,omitempty"` // section 8.1.1 - ID ID `json:"$id,omitempty"` // section 8.2.1 - Anchor string `json:"$anchor,omitempty"` // section 8.2.2 - Ref string `json:"$ref,omitempty"` // section 8.2.3.1 - DynamicRef string `json:"$dynamicRef,omitempty"` // section 8.2.3.2 - Definitions Definitions `json:"$defs,omitempty"` // section 8.2.4 - Comments string `json:"$comment,omitempty"` // section 8.3 - // RFC draft-bhutton-json-schema-00 section 10.2.1 (Sub-schemas with logic) - AllOf []*Schema `json:"allOf,omitempty"` // section 10.2.1.1 - AnyOf []*Schema `json:"anyOf,omitempty"` // section 10.2.1.2 - OneOf []*Schema `json:"oneOf,omitempty"` // section 10.2.1.3 - Not *Schema `json:"not,omitempty"` // section 10.2.1.4 - // RFC draft-bhutton-json-schema-00 section 10.2.2 (Apply sub-schemas conditionally) - If *Schema `json:"if,omitempty"` // section 10.2.2.1 - Then *Schema `json:"then,omitempty"` // section 10.2.2.2 - Else *Schema `json:"else,omitempty"` // section 10.2.2.3 - DependentSchemas map[string]*Schema `json:"dependentSchemas,omitempty"` // section 10.2.2.4 - // RFC draft-bhutton-json-schema-00 section 10.3.1 (arrays) - PrefixItems []*Schema `json:"prefixItems,omitempty"` // section 10.3.1.1 - Items *Schema `json:"items,omitempty"` // section 10.3.1.2 (replaces additionalItems) - Contains *Schema `json:"contains,omitempty"` // section 10.3.1.3 - // RFC draft-bhutton-json-schema-00 section 10.3.2 (sub-schemas) - Properties *orderedmap.OrderedMap[string, *Schema] `json:"properties,omitempty"` // section 10.3.2.1 - OriginalPropertiesMapping map[string]string `json:"-"` - PatternProperties map[string]*Schema `json:"patternProperties,omitempty"` // section 10.3.2.2 - AdditionalProperties *Schema `json:"additionalProperties,omitempty"` // section 10.3.2.3 - PropertyNames *Schema `json:"propertyNames,omitempty"` // section 10.3.2.4 - // RFC draft-bhutton-json-schema-validation-00, section 6 - Type string `json:"type,omitempty"` // section 6.1.1 - Enum []any `json:"enum,omitempty"` // section 6.1.2 - Const any `json:"const,omitempty"` // section 6.1.3 - MultipleOf json.Number `json:"multipleOf,omitempty"` // section 6.2.1 - Maximum json.Number `json:"maximum,omitempty"` // section 6.2.2 - ExclusiveMaximum json.Number `json:"exclusiveMaximum,omitempty"` // section 6.2.3 - Minimum json.Number `json:"minimum,omitempty"` // section 6.2.4 - ExclusiveMinimum json.Number `json:"exclusiveMinimum,omitempty"` // section 6.2.5 - MaxLength int `json:"maxLength,omitempty"` // section 6.3.1 - MinLength int `json:"minLength,omitempty"` // section 6.3.2 - Pattern string `json:"pattern,omitempty"` // section 6.3.3 - MaxItems int `json:"maxItems,omitempty"` // section 6.4.1 - MinItems int `json:"minItems,omitempty"` // section 6.4.2 - UniqueItems bool `json:"uniqueItems,omitempty"` // section 6.4.3 - MaxContains uint `json:"maxContains,omitempty"` // section 6.4.4 - MinContains uint `json:"minContains,omitempty"` // section 6.4.5 - MaxProperties int `json:"maxProperties,omitempty"` // section 6.5.1 - MinProperties int `json:"minProperties,omitempty"` // section 6.5.2 - Required []string `json:"required,omitempty"` // section 6.5.3 - DependentRequired map[string][]string `json:"dependentRequired,omitempty"` // section 6.5.4 - // RFC draft-bhutton-json-schema-validation-00, section 7 - Format string `json:"format,omitempty"` - // RFC draft-bhutton-json-schema-validation-00, section 8 - ContentEncoding string `json:"contentEncoding,omitempty"` // section 8.3 - ContentMediaType string `json:"contentMediaType,omitempty"` // section 8.4 - ContentSchema *Schema `json:"contentSchema,omitempty"` // section 8.5 - // RFC draft-bhutton-json-schema-validation-00, section 9 - Title string `json:"title,omitempty"` // section 9.1 - Description string `json:"description,omitempty"` // section 9.1 - Default any `json:"default,omitempty"` // section 9.2 - Deprecated bool `json:"deprecated,omitempty"` // section 9.3 - ReadOnly bool `json:"readOnly,omitempty"` // section 9.4 - WriteOnly bool `json:"writeOnly,omitempty"` // section 9.4 - Examples []any `json:"examples,omitempty"` // section 9.5 - - Extras map[string]any `json:"-"` - - // Special boolean representation of the Schema - section 4.3.2 - boolean *bool -} - -var ( - // TrueSchema defines a schema with a true value - TrueSchema = &Schema{boolean: &[]bool{true}[0]} - // FalseSchema defines a schema with a false value - FalseSchema = &Schema{boolean: &[]bool{false}[0]} -) - -// customSchemaImpl is used to detect if the type provides it's own -// custom Schema Type definition to use instead. Very useful for situations -// where there are custom JSON Marshal and Unmarshal methods. -type customSchemaImpl interface { - JSONSchema() *Schema -} - -// Function to be run after the schema has been generated. -// this will let you modify a schema afterwards -type extendSchemaImpl interface { - JSONSchemaExtend(*Schema) -} - -var customType = reflect.TypeOf((*customSchemaImpl)(nil)).Elem() -var extendType = reflect.TypeOf((*extendSchemaImpl)(nil)).Elem() - -// customSchemaGetFieldDocString -type customSchemaGetFieldDocString interface { - GetFieldDocString(fieldName string) string -} - -type customGetFieldDocString func(fieldName string) string - -var customStructGetFieldDocString = reflect.TypeOf((*customSchemaGetFieldDocString)(nil)).Elem() - -// Reflect reflects to Schema from a value using the default Reflector -func Reflect(v any) *Schema { - return ReflectFromType(reflect.TypeOf(v)) -} - -// ReflectFromType generates root schema using the default Reflector -func ReflectFromType(t reflect.Type) *Schema { - r := &Reflector{} - return r.ReflectFromType(t) -} - -// A Reflector reflects values into a Schema. -type Reflector struct { - // BaseSchemaID defines the URI that will be used as a base to determine Schema - // IDs for models. For example, a base Schema ID of `https://invopop.com/schemas` - // when defined with a struct called `User{}`, will result in a schema with an - // ID set to `https://invopop.com/schemas/user`. - // - // If no `BaseSchemaID` is provided, we'll take the type's complete package path - // and use that as a base instead. Set `Anonymous` to try if you do not want to - // include a schema ID. - BaseSchemaID ID - - // Anonymous when true will hide the auto-generated Schema ID and provide what is - // known as an "anonymous schema". As a rule, this is not recommended. - Anonymous bool - - // AssignAnchor when true will use the original struct's name as an anchor inside - // every definition, including the root schema. These can be useful for having a - // reference to the original struct's name in CamelCase instead of the snake-case used - // by default for URI compatibility. - // - // Anchors do not appear to be widely used out in the wild, so at this time the - // anchors themselves will not be used inside generated schema. - AssignAnchor bool - - // AllowAdditionalProperties will cause the Reflector to generate a schema - // without additionalProperties set to 'false' for all struct types. This means - // the presence of additional keys in JSON objects will not cause validation - // to fail. Note said additional keys will simply be dropped when the - // validated JSON is unmarshaled. - AllowAdditionalProperties bool - - // RequiredFromJSONSchemaTags will cause the Reflector to generate a schema - // that requires any key tagged with `jsonschema:required`, overriding the - // default of requiring any key *not* tagged with `json:,omitempty`. - RequiredFromJSONSchemaTags bool - - // Do not reference definitions. This will remove the top-level $defs map and - // instead cause the entire structure of types to be output in one tree. The - // list of type definitions (`$defs`) will not be included. - DoNotReference bool - - // ExpandedStruct when true will include the reflected type's definition in the - // root as opposed to a definition with a reference. - ExpandedStruct bool - - // FieldNameTag will change the tag used to get field names. json tags are used by default. - FieldNameTag string - - // IgnoredTypes defines a slice of types that should be ignored in the schema, - // switching to just allowing additional properties instead. - IgnoredTypes []any - - // Lookup allows a function to be defined that will provide a custom mapping of - // types to Schema IDs. This allows existing schema documents to be referenced - // by their ID instead of being embedded into the current schema definitions. - // Reflected types will never be pointers, only underlying elements. - Lookup func(reflect.Type) ID - - // Mapper is a function that can be used to map custom Go types to jsonschema schemas. - Mapper func(reflect.Type) *Schema - - // Namer allows customizing of type names. The default is to use the type's name - // provided by the reflect package. - Namer func(reflect.Type) string - - // KeyNamer allows customizing of key names. - // The default is to use the key's name as is, or the json tag if present. - // If a json tag is present, KeyNamer will receive the tag's name as an argument, not the original key name. - KeyNamer func(string) string - - // AdditionalFields allows adding structfields for a given type - AdditionalFields func(reflect.Type) []reflect.StructField - - // CommentMap is a dictionary of fully qualified go types and fields to comment - // strings that will be used if a description has not already been provided in - // the tags. Types and fields are added to the package path using "." as a - // separator. - // - // Type descriptions should be defined like: - // - // map[string]string{"github.com/invopop/jsonschema.Reflector": "A Reflector reflects values into a Schema."} - // - // And Fields defined as: - // - // map[string]string{"github.com/invopop/jsonschema.Reflector.DoNotReference": "Do not reference definitions."} - // - // See also: AddGoComments - CommentMap map[string]string -} - -// Reflect reflects to Schema from a value. -func (r *Reflector) Reflect(v any) *Schema { - return r.ReflectFromType(reflect.TypeOf(v)) -} - -// ReflectFromType generates root schema -func (r *Reflector) ReflectFromType(t reflect.Type) *Schema { - if t.Kind() == reflect.Ptr { - t = t.Elem() // re-assign from pointer - } - - name := r.typeName(t) - - s := new(Schema) - definitions := Definitions{} - s.Definitions = definitions - bs := r.reflectTypeToSchemaWithID(definitions, t) - if r.ExpandedStruct { - *s = *definitions[name] - delete(definitions, name) - } else { - *s = *bs - } - - // Attempt to set the schema ID - if !r.Anonymous && s.ID == EmptyID { - baseSchemaID := r.BaseSchemaID - if baseSchemaID == EmptyID { - id := ID("https://" + t.PkgPath()) - if err := id.Validate(); err == nil { - // it's okay to silently ignore URL errors - baseSchemaID = id - } - } - if baseSchemaID != EmptyID { - s.ID = baseSchemaID.Add(ToSnakeCase(name)) - } - } - - s.Version = Version - if !r.DoNotReference { - s.Definitions = definitions - } - - return s -} - -// Definitions hold schema definitions. -// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.26 -// RFC draft-wright-json-schema-validation-00, section 5.26 -type Definitions map[string]*Schema - -// Available Go defined types for JSON Schema Validation. -// RFC draft-wright-json-schema-validation-00, section 7.3 -var ( - timeType = reflect.TypeOf(time.Time{}) // date-time RFC section 7.3.1 - ipType = reflect.TypeOf(net.IP{}) // ipv4 and ipv6 RFC section 7.3.4, 7.3.5 - uriType = reflect.TypeOf(url.URL{}) // uri RFC section 7.3.6 -) - -// Byte slices will be encoded as base64 -var byteSliceType = reflect.TypeOf([]byte(nil)) - -// Except for json.RawMessage -var rawMessageType = reflect.TypeOf(json.RawMessage{}) - -// Go code generated from protobuf enum types should fulfil this interface. -type protoEnum interface { - EnumDescriptor() ([]byte, []int) -} - -var protoEnumType = reflect.TypeOf((*protoEnum)(nil)).Elem() - -// SetBaseSchemaID is a helper use to be able to set the reflectors base -// schema ID from a string as opposed to then ID instance. -func (r *Reflector) SetBaseSchemaID(id string) { - r.BaseSchemaID = ID(id) -} - -func (r *Reflector) refOrReflectTypeToSchema(definitions Definitions, t reflect.Type) *Schema { - id := r.lookupID(t) - if id != EmptyID { - return &Schema{ - Ref: id.String(), - } - } - - // Already added to definitions? - if def := r.refDefinition(definitions, t); def != nil { - return def - } - - return r.reflectTypeToSchemaWithID(definitions, t) -} - -func (r *Reflector) reflectTypeToSchemaWithID(defs Definitions, t reflect.Type) *Schema { - s := r.reflectTypeToSchema(defs, t) - if s != nil { - if r.Lookup != nil { - id := r.Lookup(t) - if id != EmptyID { - s.ID = id - } - } - } - return s -} - -func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type) *Schema { - // only try to reflect non-pointers - if t.Kind() == reflect.Ptr { - return r.refOrReflectTypeToSchema(definitions, t.Elem()) - } - - // Do any pre-definitions exist? - if r.Mapper != nil { - if t := r.Mapper(t); t != nil { - return t - } - } - if rt := r.reflectCustomSchema(definitions, t); rt != nil { - return rt - } - - // Prepare a base to which details can be added - st := new(Schema) - - // jsonpb will marshal protobuf enum options as either strings or integers. - // It will unmarshal either. - if t.Implements(protoEnumType) { - st.OneOf = []*Schema{ - {Type: "string"}, - {Type: "integer"}, - } - return st - } - - // Defined format types for JSON Schema Validation - // RFC draft-wright-json-schema-validation-00, section 7.3 - // TODO email RFC section 7.3.2, hostname RFC section 7.3.3, uriref RFC section 7.3.7 - if t == ipType { - // TODO differentiate ipv4 and ipv6 RFC section 7.3.4, 7.3.5 - st.Type = "string" - st.Format = "ipv4" - return st - } - - switch t.Kind() { - case reflect.Struct: - r.reflectStruct(definitions, t, st) - - case reflect.Slice, reflect.Array: - r.reflectSliceOrArray(definitions, t, st) - - case reflect.Map: - r.reflectMap(definitions, t, st) - - case reflect.Interface: - // empty - - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - st.Type = "integer" - - case reflect.Float32, reflect.Float64: - st.Type = "number" - - case reflect.Bool: - st.Type = "boolean" - - case reflect.String: - st.Type = "string" - - default: - panic("unsupported type " + t.String()) - } - - r.reflectSchemaExtend(definitions, t, st) - - // Always try to reference the definition which may have just been created - if def := r.refDefinition(definitions, t); def != nil { - return def - } - - return st -} - -func (r *Reflector) reflectCustomSchema(definitions Definitions, t reflect.Type) *Schema { - if t.Kind() == reflect.Ptr { - return r.reflectCustomSchema(definitions, t.Elem()) - } - - if t.Implements(customType) { - v := reflect.New(t) - o := v.Interface().(customSchemaImpl) - st := o.JSONSchema() - r.addDefinition(definitions, t, st) - if ref := r.refDefinition(definitions, t); ref != nil { - return ref - } - return st - } - - return nil -} - -func (r *Reflector) reflectSchemaExtend(definitions Definitions, t reflect.Type, s *Schema) *Schema { - if t.Implements(extendType) { - v := reflect.New(t) - o := v.Interface().(extendSchemaImpl) - o.JSONSchemaExtend(s) - if ref := r.refDefinition(definitions, t); ref != nil { - return ref - } - } - - return s -} - -func (r *Reflector) reflectSliceOrArray(definitions Definitions, t reflect.Type, st *Schema) { - if t == rawMessageType { - return - } - - r.addDefinition(definitions, t, st) - - if st.Description == "" { - st.Description = r.lookupComment(t, "") - } - - if t.Kind() == reflect.Array { - st.MinItems = t.Len() - st.MaxItems = st.MinItems - } - if t.Kind() == reflect.Slice && t.Elem() == byteSliceType.Elem() { - st.Type = "string" - // NOTE: ContentMediaType is not set here - st.ContentEncoding = "base64" - } else { - st.Type = "array" - st.Items = r.refOrReflectTypeToSchema(definitions, t.Elem()) - } -} - -func (r *Reflector) reflectMap(definitions Definitions, t reflect.Type, st *Schema) { - r.addDefinition(definitions, t, st) - - st.Type = "object" - if st.Description == "" { - st.Description = r.lookupComment(t, "") - } - - switch t.Key().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - st.PatternProperties = map[string]*Schema{ - "^[0-9]+$": r.refOrReflectTypeToSchema(definitions, t.Elem()), - } - st.AdditionalProperties = FalseSchema - return - } - if t.Elem().Kind() != reflect.Interface { - st.AdditionalProperties = r.refOrReflectTypeToSchema(definitions, t.Elem()) - } -} - -// Reflects a struct to a JSON Schema type. -func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type, s *Schema) { - // Handle special types - switch t { - case timeType: // date-time RFC section 7.3.1 - s.Type = "string" - s.Format = "date-time" - return - case uriType: // uri RFC section 7.3.6 - s.Type = "string" - s.Format = "uri" - return - } - - r.addDefinition(definitions, t, s) - s.Type = "object" - s.Properties = NewProperties() - s.OriginalPropertiesMapping = make(map[string]string) - s.Description = r.lookupComment(t, "") - if r.AssignAnchor { - s.Anchor = t.Name() - } - if !r.AllowAdditionalProperties && s.AdditionalProperties == nil { - s.AdditionalProperties = FalseSchema - } - - ignored := false - for _, it := range r.IgnoredTypes { - if reflect.TypeOf(it) == t { - ignored = true - break - } - } - if !ignored { - r.reflectStructFields(s, definitions, t) - } -} - -func (r *Reflector) reflectStructFields(st *Schema, definitions Definitions, t reflect.Type) { - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - if t.Kind() != reflect.Struct { - return - } - - var getFieldDocString customGetFieldDocString - if t.Implements(customStructGetFieldDocString) { - v := reflect.New(t) - o := v.Interface().(customSchemaGetFieldDocString) - getFieldDocString = o.GetFieldDocString - } - - handleField := func(f reflect.StructField) { - name, originalName, shouldEmbed, required, nullable := r.reflectFieldName(f) - // if anonymous and exported type should be processed recursively - // current type should inherit properties of anonymous one - if name == "" { - if shouldEmbed { - r.reflectStructFields(st, definitions, f.Type) - } - return - } - - property := r.refOrReflectTypeToSchema(definitions, f.Type) - property.structKeywordsFromTags(f, st, name) - if property.Description == "" { - property.Description = r.lookupComment(t, f.Name) - } - if getFieldDocString != nil { - property.Description = getFieldDocString(f.Name) - } - - if nullable { - property = &Schema{ - OneOf: []*Schema{ - property, - { - Type: "null", - }, - }, - } - } - - st.Properties.Set(name, property) - st.OriginalPropertiesMapping[originalName] = name - if required { - st.Required = appendUniqueString(st.Required, name) - } - } - - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - handleField(f) - } - if r.AdditionalFields != nil { - if af := r.AdditionalFields(t); af != nil { - for _, sf := range af { - handleField(sf) - } - } - } -} - -func appendUniqueString(base []string, value string) []string { - for _, v := range base { - if v == value { - return base - } - } - return append(base, value) -} - -func (r *Reflector) lookupComment(t reflect.Type, name string) string { - if r.CommentMap == nil { - return "" - } - - n := fullyQualifiedTypeName(t) - if name != "" { - n = n + "." + name - } - - return r.CommentMap[n] -} - -// addDefinition will append the provided schema. If needed, an ID and anchor will also be added. -func (r *Reflector) addDefinition(definitions Definitions, t reflect.Type, s *Schema) { - name := r.typeName(t) - if name == "" { - return - } - definitions[name] = s -} - -// refDefinition will provide a schema with a reference to an existing definition. -func (r *Reflector) refDefinition(definitions Definitions, t reflect.Type) *Schema { - if r.DoNotReference { - return nil - } - name := r.typeName(t) - if name == "" { - return nil - } - if _, ok := definitions[name]; !ok { - return nil - } - return &Schema{ - Ref: "#/$defs/" + name, - } -} - -func (r *Reflector) lookupID(t reflect.Type) ID { - if r.Lookup != nil { - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - return r.Lookup(t) - - } - return EmptyID -} - -func (t *Schema) structKeywordsFromTags(f reflect.StructField, parent *Schema, propertyName string) { - t.Description = f.Tag.Get("jsonschema_description") - - tags := splitOnUnescapedCommas(f.Tag.Get("jsonschema")) - tags = t.genericKeywords(tags, parent, propertyName) - - switch t.Type { - case "string": - t.stringKeywords(tags) - case "number": - t.numericalKeywords(tags) - case "integer": - t.numericalKeywords(tags) - case "array": - t.arrayKeywords(tags) - case "boolean": - t.booleanKeywords(tags) - } - extras := strings.Split(f.Tag.Get("jsonschema_extras"), ",") - t.extraKeywords(extras) -} - -// read struct tags for generic keywords -func (t *Schema) genericKeywords(tags []string, parent *Schema, propertyName string) []string { //nolint:gocyclo - unprocessed := make([]string, 0, len(tags)) - for _, tag := range tags { - nameValue := strings.Split(tag, "=") - if len(nameValue) == 2 { - name, val := nameValue[0], nameValue[1] - switch name { - case "title": - t.Title = val - case "description": - t.Description = val - case "type": - t.Type = val - case "anchor": - t.Anchor = val - case "oneof_required": - var typeFound *Schema - for i := range parent.OneOf { - if parent.OneOf[i].Title == nameValue[1] { - typeFound = parent.OneOf[i] - } - } - if typeFound == nil { - typeFound = &Schema{ - Title: nameValue[1], - Required: []string{}, - } - parent.OneOf = append(parent.OneOf, typeFound) - } - typeFound.Required = append(typeFound.Required, propertyName) - case "anyof_required": - var typeFound *Schema - for i := range parent.AnyOf { - if parent.AnyOf[i].Title == nameValue[1] { - typeFound = parent.AnyOf[i] - } - } - if typeFound == nil { - typeFound = &Schema{ - Title: nameValue[1], - Required: []string{}, - } - parent.AnyOf = append(parent.AnyOf, typeFound) - } - typeFound.Required = append(typeFound.Required, propertyName) - case "oneof_ref": - subSchema := t - if t.Items != nil { - subSchema = t.Items - } - if subSchema.OneOf == nil { - subSchema.OneOf = make([]*Schema, 0, 1) - } - subSchema.Ref = "" - refs := strings.Split(nameValue[1], ";") - for _, r := range refs { - subSchema.OneOf = append(subSchema.OneOf, &Schema{ - Ref: r, - }) - } - case "oneof_type": - if t.OneOf == nil { - t.OneOf = make([]*Schema, 0, 1) - } - t.Type = "" - types := strings.Split(nameValue[1], ";") - for _, ty := range types { - t.OneOf = append(t.OneOf, &Schema{ - Type: ty, - }) - } - case "anyof_ref": - subSchema := t - if t.Items != nil { - subSchema = t.Items - } - if subSchema.AnyOf == nil { - subSchema.AnyOf = make([]*Schema, 0, 1) - } - subSchema.Ref = "" - refs := strings.Split(nameValue[1], ";") - for _, r := range refs { - subSchema.AnyOf = append(subSchema.AnyOf, &Schema{ - Ref: r, - }) - } - case "anyof_type": - if t.AnyOf == nil { - t.AnyOf = make([]*Schema, 0, 1) - } - t.Type = "" - types := strings.Split(nameValue[1], ";") - for _, ty := range types { - t.AnyOf = append(t.AnyOf, &Schema{ - Type: ty, - }) - } - default: - unprocessed = append(unprocessed, tag) - } - } - } - return unprocessed -} - -// read struct tags for boolean type keywords -func (t *Schema) booleanKeywords(tags []string) { - for _, tag := range tags { - nameValue := strings.Split(tag, "=") - if len(nameValue) != 2 { - continue - } - name, val := nameValue[0], nameValue[1] - if name == "default" { - if val == "true" { - t.Default = true - } else if val == "false" { - t.Default = false - } - } - } -} - -// read struct tags for string type keywords -func (t *Schema) stringKeywords(tags []string) { - for _, tag := range tags { - nameValue := strings.Split(tag, "=") - if len(nameValue) == 2 { - name, val := nameValue[0], nameValue[1] - switch name { - case "minLength": - i, _ := strconv.Atoi(val) - t.MinLength = i - case "maxLength": - i, _ := strconv.Atoi(val) - t.MaxLength = i - case "pattern": - t.Pattern = val - case "format": - switch val { - case "date-time", "email", "hostname", "ipv4", "ipv6", "uri", "uuid": - t.Format = val - } - case "readOnly": - i, _ := strconv.ParseBool(val) - t.ReadOnly = i - case "writeOnly": - i, _ := strconv.ParseBool(val) - t.WriteOnly = i - case "default": - t.Default = val - case "example": - t.Examples = append(t.Examples, val) - case "enum": - t.Enum = append(t.Enum, val) - } - } - } -} - -// read struct tags for numerical type keywords -func (t *Schema) numericalKeywords(tags []string) { - for _, tag := range tags { - nameValue := strings.Split(tag, "=") - if len(nameValue) == 2 { - name, val := nameValue[0], nameValue[1] - switch name { - case "multipleOf": - t.MultipleOf, _ = toJSONNumber(val) - case "minimum": - t.Minimum, _ = toJSONNumber(val) - case "maximum": - t.Maximum, _ = toJSONNumber(val) - case "exclusiveMaximum": - t.ExclusiveMaximum, _ = toJSONNumber(val) - case "exclusiveMinimum": - t.ExclusiveMinimum, _ = toJSONNumber(val) - case "default": - if num, ok := toJSONNumber(val); ok { - t.Default = num - } - case "example": - if num, ok := toJSONNumber(val); ok { - t.Examples = append(t.Examples, num) - } - case "enum": - if num, ok := toJSONNumber(val); ok { - t.Enum = append(t.Enum, num) - } - } - } - } -} - -// read struct tags for object type keywords -// func (t *Type) objectKeywords(tags []string) { -// for _, tag := range tags{ -// nameValue := strings.Split(tag, "=") -// name, val := nameValue[0], nameValue[1] -// switch name{ -// case "dependencies": -// t.Dependencies = val -// break; -// case "patternProperties": -// t.PatternProperties = val -// break; -// } -// } -// } - -// read struct tags for array type keywords -func (t *Schema) arrayKeywords(tags []string) { - var defaultValues []any - - unprocessed := make([]string, 0, len(tags)) - for _, tag := range tags { - nameValue := strings.Split(tag, "=") - if len(nameValue) == 2 { - name, val := nameValue[0], nameValue[1] - switch name { - case "minItems": - i, _ := strconv.Atoi(val) - t.MinItems = i - case "maxItems": - i, _ := strconv.Atoi(val) - t.MaxItems = i - case "uniqueItems": - t.UniqueItems = true - case "default": - defaultValues = append(defaultValues, val) - case "format": - t.Items.Format = val - case "pattern": - t.Items.Pattern = val - default: - unprocessed = append(unprocessed, tag) // left for further processing by underlying type - } - } - } - if len(defaultValues) > 0 { - t.Default = defaultValues - } - - if len(unprocessed) == 0 { - // we don't have anything else to process - return - } - - switch t.Items.Type { - case "string": - t.Items.stringKeywords(unprocessed) - case "number": - t.Items.numericalKeywords(unprocessed) - case "integer": - t.Items.numericalKeywords(unprocessed) - case "array": - // explicitly don't support traversal for the [][]..., as it's unclear where the array tags belong - case "boolean": - t.Items.booleanKeywords(unprocessed) - } -} - -func (t *Schema) extraKeywords(tags []string) { - for _, tag := range tags { - nameValue := strings.SplitN(tag, "=", 2) - if len(nameValue) == 2 { - t.setExtra(nameValue[0], nameValue[1]) - } - } -} - -func (t *Schema) setExtra(key, val string) { - if t.Extras == nil { - t.Extras = map[string]any{} - } - if existingVal, ok := t.Extras[key]; ok { - switch existingVal := existingVal.(type) { - case string: - t.Extras[key] = []string{existingVal, val} - case []string: - t.Extras[key] = append(existingVal, val) - case int: - t.Extras[key], _ = strconv.Atoi(val) - case bool: - t.Extras[key] = (val == "true" || val == "t") - } - } else { - switch key { - case "minimum": - t.Extras[key], _ = strconv.Atoi(val) - default: - var x any - if val == "true" { - x = true - } else if val == "false" { - x = false - } else { - x = val - } - t.Extras[key] = x - } - } -} - -func requiredFromJSONTags(tags []string, val *bool) { - if ignoredByJSONTags(tags) { - return - } - - for _, tag := range tags[1:] { - if tag == "omitempty" { - *val = false - return - } - } - *val = true -} - -func requiredFromJSONSchemaTags(tags []string, val *bool) { - if ignoredByJSONSchemaTags(tags) { - return - } - for _, tag := range tags { - if tag == "required" { - *val = true - } - } -} - -func nullableFromJSONSchemaTags(tags []string) bool { - if ignoredByJSONSchemaTags(tags) { - return false - } - for _, tag := range tags { - if tag == "nullable" { - return true - } - } - return false -} - -func ignoredByJSONTags(tags []string) bool { - return tags[0] == "-" -} - -func ignoredByJSONSchemaTags(tags []string) bool { - return tags[0] == "-" -} - -// toJSONNumber converts string to *json.Number. -// It'll aso return whether the number is valid. -func toJSONNumber(s string) (json.Number, bool) { - num := json.Number(s) - if _, err := num.Int64(); err == nil { - return num, true - } - if _, err := num.Float64(); err == nil { - return num, true - } - return json.Number(""), false -} - -func (r *Reflector) fieldNameTag() string { - if r.FieldNameTag != "" { - return r.FieldNameTag - } - return "json" -} - -func (r *Reflector) reflectFieldName(f reflect.StructField) (string, string, bool, bool, bool) { - jsonTagString := f.Tag.Get(r.fieldNameTag()) - jsonTags := strings.Split(jsonTagString, ",") - - if ignoredByJSONTags(jsonTags) { - return "", "", false, false, false - } - - schemaTags := strings.Split(f.Tag.Get("jsonschema"), ",") - if ignoredByJSONSchemaTags(schemaTags) { - return "", "", false, false, false - } - - var required bool - if !r.RequiredFromJSONSchemaTags { - requiredFromJSONTags(jsonTags, &required) - } - requiredFromJSONSchemaTags(schemaTags, &required) - - nullable := nullableFromJSONSchemaTags(schemaTags) - - if f.Anonymous && jsonTags[0] == "" { - // As per JSON Marshal rules, anonymous structs are inherited - if f.Type.Kind() == reflect.Struct { - return "", "", true, false, false - } - - // As per JSON Marshal rules, anonymous pointer to structs are inherited - if f.Type.Kind() == reflect.Ptr && f.Type.Elem().Kind() == reflect.Struct { - return "", "", true, false, false - } - } - - // Try to determine the name from the different combos - name := f.Name - originalName := f.Name - if jsonTags[0] != "" { - name = jsonTags[0] - } - if !f.Anonymous && f.PkgPath != "" { - // field not anonymous and not export has no export name - name = "" - } else if r.KeyNamer != nil { - name = r.KeyNamer(name) - } - - return name, originalName, false, required, nullable -} - -// UnmarshalJSON is used to parse a schema object or boolean. -func (t *Schema) UnmarshalJSON(data []byte) error { - if bytes.Equal(data, []byte("true")) { - *t = *TrueSchema - return nil - } else if bytes.Equal(data, []byte("false")) { - *t = *FalseSchema - return nil - } - type SchemaAlt Schema - aux := &struct { - *SchemaAlt - }{ - SchemaAlt: (*SchemaAlt)(t), - } - return json.Unmarshal(data, aux) -} - -// MarshalJSON is used to serialize a schema object or boolean. -func (t *Schema) MarshalJSON() ([]byte, error) { - if t.boolean != nil { - if *t.boolean { - return []byte("true"), nil - } - return []byte("false"), nil - } - if reflect.DeepEqual(&Schema{}, t) { - // Don't bother returning empty schemas - return []byte("true"), nil - } - type SchemaAlt Schema - b, err := json.Marshal((*SchemaAlt)(t)) - if err != nil { - return nil, err - } - if t.Extras == nil || len(t.Extras) == 0 { - return b, nil - } - m, err := json.Marshal(t.Extras) - if err != nil { - return nil, err - } - if len(b) == 2 { - return m, nil - } - b[len(b)-1] = ',' - return append(b, m[1:]...), nil -} - -func (r *Reflector) typeName(t reflect.Type) string { - if r.Namer != nil { - if name := r.Namer(t); name != "" { - return name - } - } - return t.Name() -} - -// Split on commas that are not preceded by `\`. -// This way, we prevent splitting regexes -func splitOnUnescapedCommas(tagString string) []string { - ret := make([]string, 0) - separated := strings.Split(tagString, ",") - ret = append(ret, separated[0]) - i := 0 - for _, nextTag := range separated[1:] { - if len(ret[i]) == 0 { - ret = append(ret, nextTag) - i++ - continue - } - - if ret[i][len(ret[i])-1] == '\\' { - ret[i] = ret[i][:len(ret[i])-1] + "," + nextTag - } else { - ret = append(ret, nextTag) - i++ - } - } - - return ret -} - -func fullyQualifiedTypeName(t reflect.Type) string { - return t.PkgPath() + "." + t.Name() -} - -// AddGoComments will update the reflectors comment map with all the comments -// found in the provided source directories. See the #ExtractGoComments method -// for more details. -func (r *Reflector) AddGoComments(base, path string) error { - if r.CommentMap == nil { - r.CommentMap = make(map[string]string) - } - return ExtractGoComments(base, path, r.CommentMap) -} diff --git a/vendor/github.com/karimkhaleel/jsonschema/utils.go b/vendor/github.com/karimkhaleel/jsonschema/utils.go deleted file mode 100644 index ed8edf7411c..00000000000 --- a/vendor/github.com/karimkhaleel/jsonschema/utils.go +++ /dev/null @@ -1,26 +0,0 @@ -package jsonschema - -import ( - "regexp" - "strings" - - orderedmap "github.com/wk8/go-ordered-map/v2" -) - -var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") -var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") - -// ToSnakeCase converts the provided string into snake case using dashes. -// This is useful for Schema IDs and definitions to be coherent with -// common JSON Schema examples. -func ToSnakeCase(str string) string { - snake := matchFirstCap.ReplaceAllString(str, "${1}-${2}") - snake = matchAllCap.ReplaceAllString(snake, "${1}-${2}") - return strings.ToLower(snake) -} - -// NewProperties is a helper method to instantiate a new properties ordered -// map. -func NewProperties() *orderedmap.OrderedMap[string, *Schema] { - return orderedmap.New[string, *Schema]() -} diff --git a/vendor/github.com/kevinburke/ssh_config/.gitattributes b/vendor/github.com/kevinburke/ssh_config/.gitattributes deleted file mode 100644 index 44db5818894..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -testdata/dos-lines eol=crlf diff --git a/vendor/github.com/kevinburke/ssh_config/.gitignore b/vendor/github.com/kevinburke/ssh_config/.gitignore deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/vendor/github.com/kevinburke/ssh_config/.mailmap b/vendor/github.com/kevinburke/ssh_config/.mailmap deleted file mode 100644 index 253406b1cc6..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/.mailmap +++ /dev/null @@ -1 +0,0 @@ -Kevin Burke Kevin Burke diff --git a/vendor/github.com/kevinburke/ssh_config/AUTHORS.txt b/vendor/github.com/kevinburke/ssh_config/AUTHORS.txt deleted file mode 100644 index 311aeb1b4ff..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/AUTHORS.txt +++ /dev/null @@ -1,9 +0,0 @@ -Carlos A Becker -Dustin Spicuzza -Eugene Terentev -Kevin Burke -Mark Nevill -Scott Lessans -Sergey Lukjanov -Wayne Ashley Berry -santosh653 <70637961+santosh653@users.noreply.github.com> diff --git a/vendor/github.com/kevinburke/ssh_config/CHANGELOG.md b/vendor/github.com/kevinburke/ssh_config/CHANGELOG.md deleted file mode 100644 index d32a3f5106c..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/CHANGELOG.md +++ /dev/null @@ -1,19 +0,0 @@ -# Changes - -## Version 1.2 - -Previously, if a Host declaration or a value had trailing whitespace, that -whitespace would have been included as part of the value. This led to unexpected -consequences. For example: - -``` -Host example # A comment - HostName example.com # Another comment -``` - -Prior to version 1.2, the value for Host would have been "example " and the -value for HostName would have been "example.com ". Both of these are -unintuitive. - -Instead, we strip the trailing whitespace in the configuration, which leads to -more intuitive behavior. diff --git a/vendor/github.com/kevinburke/ssh_config/LICENSE b/vendor/github.com/kevinburke/ssh_config/LICENSE deleted file mode 100644 index b9a770ac2a9..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/LICENSE +++ /dev/null @@ -1,49 +0,0 @@ -Copyright (c) 2017 Kevin Burke. - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -=================== - -The lexer and parser borrow heavily from github.com/pelletier/go-toml. The -license for that project is copied below. - -The MIT License (MIT) - -Copyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/kevinburke/ssh_config/Makefile b/vendor/github.com/kevinburke/ssh_config/Makefile deleted file mode 100644 index df7ee728be6..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -BUMP_VERSION := $(GOPATH)/bin/bump_version -STATICCHECK := $(GOPATH)/bin/staticcheck -WRITE_MAILMAP := $(GOPATH)/bin/write_mailmap - -$(STATICCHECK): - go get honnef.co/go/tools/cmd/staticcheck - -lint: $(STATICCHECK) - go vet ./... - $(STATICCHECK) - -test: lint - @# the timeout helps guard against infinite recursion - go test -timeout=250ms ./... - -race-test: lint - go test -timeout=500ms -race ./... - -$(BUMP_VERSION): - go get -u github.com/kevinburke/bump_version - -$(WRITE_MAILMAP): - go get -u github.com/kevinburke/write_mailmap - -release: test | $(BUMP_VERSION) - $(BUMP_VERSION) --tag-prefix=v minor config.go - -force: ; - -AUTHORS.txt: force | $(WRITE_MAILMAP) - $(WRITE_MAILMAP) > AUTHORS.txt - -authors: AUTHORS.txt diff --git a/vendor/github.com/kevinburke/ssh_config/README.md b/vendor/github.com/kevinburke/ssh_config/README.md deleted file mode 100644 index f14b2168f75..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# ssh_config - -This is a Go parser for `ssh_config` files. Importantly, this parser attempts -to preserve comments in a given file, so you can manipulate a `ssh_config` file -from a program, if your heart desires. - -It's designed to be used with the excellent -[x/crypto/ssh](https://golang.org/x/crypto/ssh) package, which handles SSH -negotiation but isn't very easy to configure. - -The `ssh_config` `Get()` and `GetStrict()` functions will attempt to read values -from `$HOME/.ssh/config` and fall back to `/etc/ssh/ssh_config`. The first -argument is the host name to match on, and the second argument is the key you -want to retrieve. - -```go -port := ssh_config.Get("myhost", "Port") -``` - -Certain directives can occur multiple times for a host (such as `IdentityFile`), -so you should use the `GetAll` or `GetAllStrict` directive to retrieve those -instead. - -```go -files := ssh_config.GetAll("myhost", "IdentityFile") -``` - -You can also load a config file and read values from it. - -```go -var config = ` -Host *.test - Compression yes -` - -cfg, err := ssh_config.Decode(strings.NewReader(config)) -fmt.Println(cfg.Get("example.test", "Port")) -``` - -Some SSH arguments have default values - for example, the default value for -`KeyboardAuthentication` is `"yes"`. If you call Get(), and no value for the -given Host/keyword pair exists in the config, we'll return a default for the -keyword if one exists. - -### Manipulating SSH config files - -Here's how you can manipulate an SSH config file, and then write it back to -disk. - -```go -f, _ := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "config")) -cfg, _ := ssh_config.Decode(f) -for _, host := range cfg.Hosts { - fmt.Println("patterns:", host.Patterns) - for _, node := range host.Nodes { - // Manipulate the nodes as you see fit, or use a type switch to - // distinguish between Empty, KV, and Include nodes. - fmt.Println(node.String()) - } -} - -// Print the config to stdout: -fmt.Println(cfg.String()) -``` - -## Spec compliance - -Wherever possible we try to implement the specification as documented in -the `ssh_config` manpage. Unimplemented features should be present in the -[issues][issues] list. - -Notably, the `Match` directive is currently unsupported. - -[issues]: https://github.com/kevinburke/ssh_config/issues - -## Errata - -This is the second [comment-preserving configuration parser][blog] I've written, after -[an /etc/hosts parser][hostsfile]. Eventually, I will write one for every Linux -file format. - -[blog]: https://kev.inburke.com/kevin/more-comment-preserving-configuration-parsers/ -[hostsfile]: https://github.com/kevinburke/hostsfile - -## Donating - -I don't get paid to maintain this project. Donations free up time to make -improvements to the library, and respond to bug reports. You can send donations -via Paypal's "Send Money" feature to kev@inburke.com. Donations are not tax -deductible in the USA. - -You can also reach out about a consulting engagement: https://burke.services diff --git a/vendor/github.com/kevinburke/ssh_config/config.go b/vendor/github.com/kevinburke/ssh_config/config.go deleted file mode 100644 index 00d815c1a92..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/config.go +++ /dev/null @@ -1,803 +0,0 @@ -// Package ssh_config provides tools for manipulating SSH config files. -// -// Importantly, this parser attempts to preserve comments in a given file, so -// you can manipulate a `ssh_config` file from a program, if your heart desires. -// -// The Get() and GetStrict() functions will attempt to read values from -// $HOME/.ssh/config, falling back to /etc/ssh/ssh_config. The first argument is -// the host name to match on ("example.com"), and the second argument is the key -// you want to retrieve ("Port"). The keywords are case insensitive. -// -// port := ssh_config.Get("myhost", "Port") -// -// You can also manipulate an SSH config file and then print it or write it back -// to disk. -// -// f, _ := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "config")) -// cfg, _ := ssh_config.Decode(f) -// for _, host := range cfg.Hosts { -// fmt.Println("patterns:", host.Patterns) -// for _, node := range host.Nodes { -// fmt.Println(node.String()) -// } -// } -// -// // Write the cfg back to disk: -// fmt.Println(cfg.String()) -// -// BUG: the Match directive is currently unsupported; parsing a config with -// a Match directive will trigger an error. -package ssh_config - -import ( - "bytes" - "errors" - "fmt" - "io" - "os" - osuser "os/user" - "path/filepath" - "regexp" - "runtime" - "strings" - "sync" -) - -const version = "1.2" - -var _ = version - -type configFinder func() string - -// UserSettings checks ~/.ssh and /etc/ssh for configuration files. The config -// files are parsed and cached the first time Get() or GetStrict() is called. -type UserSettings struct { - IgnoreErrors bool - systemConfig *Config - systemConfigFinder configFinder - userConfig *Config - userConfigFinder configFinder - loadConfigs sync.Once - onceErr error -} - -func homedir() string { - user, err := osuser.Current() - if err == nil { - return user.HomeDir - } else { - return os.Getenv("HOME") - } -} - -func userConfigFinder() string { - return filepath.Join(homedir(), ".ssh", "config") -} - -// DefaultUserSettings is the default UserSettings and is used by Get and -// GetStrict. It checks both $HOME/.ssh/config and /etc/ssh/ssh_config for keys, -// and it will return parse errors (if any) instead of swallowing them. -var DefaultUserSettings = &UserSettings{ - IgnoreErrors: false, - systemConfigFinder: systemConfigFinder, - userConfigFinder: userConfigFinder, -} - -func systemConfigFinder() string { - return filepath.Join("/", "etc", "ssh", "ssh_config") -} - -func findVal(c *Config, alias, key string) (string, error) { - if c == nil { - return "", nil - } - val, err := c.Get(alias, key) - if err != nil || val == "" { - return "", err - } - if err := validate(key, val); err != nil { - return "", err - } - return val, nil -} - -func findAll(c *Config, alias, key string) ([]string, error) { - if c == nil { - return nil, nil - } - return c.GetAll(alias, key) -} - -// Get finds the first value for key within a declaration that matches the -// alias. Get returns the empty string if no value was found, or if IgnoreErrors -// is false and we could not parse the configuration file. Use GetStrict to -// disambiguate the latter cases. -// -// The match for key is case insensitive. -// -// Get is a wrapper around DefaultUserSettings.Get. -func Get(alias, key string) string { - return DefaultUserSettings.Get(alias, key) -} - -// GetAll retrieves zero or more directives for key for the given alias. GetAll -// returns nil if no value was found, or if IgnoreErrors is false and we could -// not parse the configuration file. Use GetAllStrict to disambiguate the -// latter cases. -// -// In most cases you want to use Get or GetStrict, which returns a single value. -// However, a subset of ssh configuration values (IdentityFile, for example) -// allow you to specify multiple directives. -// -// The match for key is case insensitive. -// -// GetAll is a wrapper around DefaultUserSettings.GetAll. -func GetAll(alias, key string) []string { - return DefaultUserSettings.GetAll(alias, key) -} - -// GetStrict finds the first value for key within a declaration that matches the -// alias. If key has a default value and no matching configuration is found, the -// default will be returned. For more information on default values and the way -// patterns are matched, see the manpage for ssh_config. -// -// The returned error will be non-nil if and only if a user's configuration file -// or the system configuration file could not be parsed, and u.IgnoreErrors is -// false. -// -// GetStrict is a wrapper around DefaultUserSettings.GetStrict. -func GetStrict(alias, key string) (string, error) { - return DefaultUserSettings.GetStrict(alias, key) -} - -// GetAllStrict retrieves zero or more directives for key for the given alias. -// -// In most cases you want to use Get or GetStrict, which returns a single value. -// However, a subset of ssh configuration values (IdentityFile, for example) -// allow you to specify multiple directives. -// -// The returned error will be non-nil if and only if a user's configuration file -// or the system configuration file could not be parsed, and u.IgnoreErrors is -// false. -// -// GetAllStrict is a wrapper around DefaultUserSettings.GetAllStrict. -func GetAllStrict(alias, key string) ([]string, error) { - return DefaultUserSettings.GetAllStrict(alias, key) -} - -// Get finds the first value for key within a declaration that matches the -// alias. Get returns the empty string if no value was found, or if IgnoreErrors -// is false and we could not parse the configuration file. Use GetStrict to -// disambiguate the latter cases. -// -// The match for key is case insensitive. -func (u *UserSettings) Get(alias, key string) string { - val, err := u.GetStrict(alias, key) - if err != nil { - return "" - } - return val -} - -// GetAll retrieves zero or more directives for key for the given alias. GetAll -// returns nil if no value was found, or if IgnoreErrors is false and we could -// not parse the configuration file. Use GetStrict to disambiguate the latter -// cases. -// -// The match for key is case insensitive. -func (u *UserSettings) GetAll(alias, key string) []string { - val, _ := u.GetAllStrict(alias, key) - return val -} - -// GetStrict finds the first value for key within a declaration that matches the -// alias. If key has a default value and no matching configuration is found, the -// default will be returned. For more information on default values and the way -// patterns are matched, see the manpage for ssh_config. -// -// error will be non-nil if and only if a user's configuration file or the -// system configuration file could not be parsed, and u.IgnoreErrors is false. -func (u *UserSettings) GetStrict(alias, key string) (string, error) { - u.doLoadConfigs() - //lint:ignore S1002 I prefer it this way - if u.onceErr != nil && u.IgnoreErrors == false { - return "", u.onceErr - } - val, err := findVal(u.userConfig, alias, key) - if err != nil || val != "" { - return val, err - } - val2, err2 := findVal(u.systemConfig, alias, key) - if err2 != nil || val2 != "" { - return val2, err2 - } - return Default(key), nil -} - -// GetAllStrict retrieves zero or more directives for key for the given alias. -// If key has a default value and no matching configuration is found, the -// default will be returned. For more information on default values and the way -// patterns are matched, see the manpage for ssh_config. -// -// The returned error will be non-nil if and only if a user's configuration file -// or the system configuration file could not be parsed, and u.IgnoreErrors is -// false. -func (u *UserSettings) GetAllStrict(alias, key string) ([]string, error) { - u.doLoadConfigs() - //lint:ignore S1002 I prefer it this way - if u.onceErr != nil && u.IgnoreErrors == false { - return nil, u.onceErr - } - val, err := findAll(u.userConfig, alias, key) - if err != nil || val != nil { - return val, err - } - val2, err2 := findAll(u.systemConfig, alias, key) - if err2 != nil || val2 != nil { - return val2, err2 - } - // TODO: IdentityFile has multiple default values that we should return. - if def := Default(key); def != "" { - return []string{def}, nil - } - return []string{}, nil -} - -func (u *UserSettings) doLoadConfigs() { - u.loadConfigs.Do(func() { - // can't parse user file, that's ok. - var filename string - if u.userConfigFinder == nil { - filename = userConfigFinder() - } else { - filename = u.userConfigFinder() - } - var err error - u.userConfig, err = parseFile(filename) - //lint:ignore S1002 I prefer it this way - if err != nil && os.IsNotExist(err) == false { - u.onceErr = err - return - } - if u.systemConfigFinder == nil { - filename = systemConfigFinder() - } else { - filename = u.systemConfigFinder() - } - u.systemConfig, err = parseFile(filename) - //lint:ignore S1002 I prefer it this way - if err != nil && os.IsNotExist(err) == false { - u.onceErr = err - return - } - }) -} - -func parseFile(filename string) (*Config, error) { - return parseWithDepth(filename, 0) -} - -func parseWithDepth(filename string, depth uint8) (*Config, error) { - b, err := os.ReadFile(filename) - if err != nil { - return nil, err - } - return decodeBytes(b, isSystem(filename), depth) -} - -func isSystem(filename string) bool { - // TODO: not sure this is the best way to detect a system repo - return strings.HasPrefix(filepath.Clean(filename), "/etc/ssh") -} - -// Decode reads r into a Config, or returns an error if r could not be parsed as -// an SSH config file. -func Decode(r io.Reader) (*Config, error) { - b, err := io.ReadAll(r) - if err != nil { - return nil, err - } - return decodeBytes(b, false, 0) -} - -// DecodeBytes reads b into a Config, or returns an error if r could not be -// parsed as an SSH config file. -func DecodeBytes(b []byte) (*Config, error) { - return decodeBytes(b, false, 0) -} - -func decodeBytes(b []byte, system bool, depth uint8) (c *Config, err error) { - defer func() { - if r := recover(); r != nil { - if _, ok := r.(runtime.Error); ok { - panic(r) - } - if e, ok := r.(error); ok && e == ErrDepthExceeded { - err = e - return - } - err = errors.New(r.(string)) - } - }() - - c = parseSSH(lexSSH(b), system, depth) - return c, err -} - -// Config represents an SSH config file. -type Config struct { - // A list of hosts to match against. The file begins with an implicit - // "Host *" declaration matching all hosts. - Hosts []*Host - depth uint8 - position Position -} - -// Get finds the first value in the configuration that matches the alias and -// contains key. Get returns the empty string if no value was found, or if the -// Config contains an invalid conditional Include value. -// -// The match for key is case insensitive. -func (c *Config) Get(alias, key string) (string, error) { - lowerKey := strings.ToLower(key) - for _, host := range c.Hosts { - if !host.Matches(alias) { - continue - } - for _, node := range host.Nodes { - switch t := node.(type) { - case *Empty: - continue - case *KV: - // "keys are case insensitive" per the spec - lkey := strings.ToLower(t.Key) - if lkey == "match" { - panic("can't handle Match directives") - } - if lkey == lowerKey { - return t.Value, nil - } - case *Include: - val := t.Get(alias, key) - if val != "" { - return val, nil - } - default: - return "", fmt.Errorf("unknown Node type %v", t) - } - } - } - return "", nil -} - -// GetAll returns all values in the configuration that match the alias and -// contains key, or nil if none are present. -func (c *Config) GetAll(alias, key string) ([]string, error) { - lowerKey := strings.ToLower(key) - all := []string(nil) - for _, host := range c.Hosts { - if !host.Matches(alias) { - continue - } - for _, node := range host.Nodes { - switch t := node.(type) { - case *Empty: - continue - case *KV: - // "keys are case insensitive" per the spec - lkey := strings.ToLower(t.Key) - if lkey == "match" { - panic("can't handle Match directives") - } - if lkey == lowerKey { - all = append(all, t.Value) - } - case *Include: - val, _ := t.GetAll(alias, key) - if len(val) > 0 { - all = append(all, val...) - } - default: - return nil, fmt.Errorf("unknown Node type %v", t) - } - } - } - - return all, nil -} - -// String returns a string representation of the Config file. -func (c Config) String() string { - return marshal(c).String() -} - -func (c Config) MarshalText() ([]byte, error) { - return marshal(c).Bytes(), nil -} - -func marshal(c Config) *bytes.Buffer { - var buf bytes.Buffer - for i := range c.Hosts { - buf.WriteString(c.Hosts[i].String()) - } - return &buf -} - -// Pattern is a pattern in a Host declaration. Patterns are read-only values; -// create a new one with NewPattern(). -type Pattern struct { - str string // Its appearance in the file, not the value that gets compiled. - regex *regexp.Regexp - not bool // True if this is a negated match -} - -// String prints the string representation of the pattern. -func (p Pattern) String() string { - return p.str -} - -// Copied from regexp.go with * and ? removed. -var specialBytes = []byte(`\.+()|[]{}^$`) - -func special(b byte) bool { - return bytes.IndexByte(specialBytes, b) >= 0 -} - -// NewPattern creates a new Pattern for matching hosts. NewPattern("*") creates -// a Pattern that matches all hosts. -// -// From the manpage, a pattern consists of zero or more non-whitespace -// characters, `*' (a wildcard that matches zero or more characters), or `?' (a -// wildcard that matches exactly one character). For example, to specify a set -// of declarations for any host in the ".co.uk" set of domains, the following -// pattern could be used: -// -// Host *.co.uk -// -// The following pattern would match any host in the 192.168.0.[0-9] network range: -// -// Host 192.168.0.? -func NewPattern(s string) (*Pattern, error) { - if s == "" { - return nil, errors.New("ssh_config: empty pattern") - } - negated := false - if s[0] == '!' { - negated = true - s = s[1:] - } - var buf bytes.Buffer - buf.WriteByte('^') - for i := 0; i < len(s); i++ { - // A byte loop is correct because all metacharacters are ASCII. - switch b := s[i]; b { - case '*': - buf.WriteString(".*") - case '?': - buf.WriteString(".?") - default: - // borrowing from QuoteMeta here. - if special(b) { - buf.WriteByte('\\') - } - buf.WriteByte(b) - } - } - buf.WriteByte('$') - r, err := regexp.Compile(buf.String()) - if err != nil { - return nil, err - } - return &Pattern{str: s, regex: r, not: negated}, nil -} - -// Host describes a Host directive and the keywords that follow it. -type Host struct { - // A list of host patterns that should match this host. - Patterns []*Pattern - // A Node is either a key/value pair or a comment line. - Nodes []Node - // EOLComment is the comment (if any) terminating the Host line. - EOLComment string - // Whitespace if any between the Host declaration and a trailing comment. - spaceBeforeComment string - - hasEquals bool - leadingSpace int // TODO: handle spaces vs tabs here. - // The file starts with an implicit "Host *" declaration. - implicit bool -} - -// Matches returns true if the Host matches for the given alias. For -// a description of the rules that provide a match, see the manpage for -// ssh_config. -func (h *Host) Matches(alias string) bool { - found := false - for i := range h.Patterns { - if h.Patterns[i].regex.MatchString(alias) { - if h.Patterns[i].not { - // Negated match. "A pattern entry may be negated by prefixing - // it with an exclamation mark (`!'). If a negated entry is - // matched, then the Host entry is ignored, regardless of - // whether any other patterns on the line match. Negated matches - // are therefore useful to provide exceptions for wildcard - // matches." - return false - } - found = true - } - } - return found -} - -// String prints h as it would appear in a config file. Minor tweaks may be -// present in the whitespace in the printed file. -func (h *Host) String() string { - var buf strings.Builder - //lint:ignore S1002 I prefer to write it this way - if h.implicit == false { - buf.WriteString(strings.Repeat(" ", int(h.leadingSpace))) - buf.WriteString("Host") - if h.hasEquals { - buf.WriteString(" = ") - } else { - buf.WriteString(" ") - } - for i, pat := range h.Patterns { - buf.WriteString(pat.String()) - if i < len(h.Patterns)-1 { - buf.WriteString(" ") - } - } - buf.WriteString(h.spaceBeforeComment) - if h.EOLComment != "" { - buf.WriteByte('#') - buf.WriteString(h.EOLComment) - } - buf.WriteByte('\n') - } - for i := range h.Nodes { - buf.WriteString(h.Nodes[i].String()) - buf.WriteByte('\n') - } - return buf.String() -} - -// Node represents a line in a Config. -type Node interface { - Pos() Position - String() string -} - -// KV is a line in the config file that contains a key, a value, and possibly -// a comment. -type KV struct { - Key string - Value string - // Whitespace after the value but before any comment - spaceAfterValue string - Comment string - hasEquals bool - leadingSpace int // Space before the key. TODO handle spaces vs tabs. - position Position -} - -// Pos returns k's Position. -func (k *KV) Pos() Position { - return k.position -} - -// String prints k as it was parsed in the config file. -func (k *KV) String() string { - if k == nil { - return "" - } - equals := " " - if k.hasEquals { - equals = " = " - } - line := strings.Repeat(" ", int(k.leadingSpace)) + k.Key + equals + k.Value + k.spaceAfterValue - if k.Comment != "" { - line += "#" + k.Comment - } - return line -} - -// Empty is a line in the config file that contains only whitespace or comments. -type Empty struct { - Comment string - leadingSpace int // TODO handle spaces vs tabs. - position Position -} - -// Pos returns e's Position. -func (e *Empty) Pos() Position { - return e.position -} - -// String prints e as it was parsed in the config file. -func (e *Empty) String() string { - if e == nil { - return "" - } - if e.Comment == "" { - return "" - } - return fmt.Sprintf("%s#%s", strings.Repeat(" ", int(e.leadingSpace)), e.Comment) -} - -// Include holds the result of an Include directive, including the config files -// that have been parsed as part of that directive. At most 5 levels of Include -// statements will be parsed. -type Include struct { - // Comment is the contents of any comment at the end of the Include - // statement. - Comment string - // an include directive can include several different files, and wildcards - directives []string - - mu sync.Mutex - // 1:1 mapping between matches and keys in files array; matches preserves - // ordering - matches []string - // actual filenames are listed here - files map[string]*Config - leadingSpace int - position Position - depth uint8 - hasEquals bool -} - -const maxRecurseDepth = 5 - -// ErrDepthExceeded is returned if too many Include directives are parsed. -// Usually this indicates a recursive loop (an Include directive pointing to the -// file it contains). -var ErrDepthExceeded = errors.New("ssh_config: max recurse depth exceeded") - -func removeDups(arr []string) []string { - // Use map to record duplicates as we find them. - encountered := make(map[string]bool, len(arr)) - result := make([]string, 0) - - for v := range arr { - //lint:ignore S1002 I prefer it this way - if encountered[arr[v]] == false { - encountered[arr[v]] = true - result = append(result, arr[v]) - } - } - return result -} - -// NewInclude creates a new Include with a list of file globs to include. -// Configuration files are parsed greedily (e.g. as soon as this function runs). -// Any error encountered while parsing nested configuration files will be -// returned. -func NewInclude(directives []string, hasEquals bool, pos Position, comment string, system bool, depth uint8) (*Include, error) { - if depth > maxRecurseDepth { - return nil, ErrDepthExceeded - } - inc := &Include{ - Comment: comment, - directives: directives, - files: make(map[string]*Config), - position: pos, - leadingSpace: pos.Col - 1, - depth: depth, - hasEquals: hasEquals, - } - // no need for inc.mu.Lock() since nothing else can access this inc - matches := make([]string, 0) - for i := range directives { - var path string - if filepath.IsAbs(directives[i]) { - path = directives[i] - } else if system { - path = filepath.Join("/etc/ssh", directives[i]) - } else { - path = filepath.Join(homedir(), ".ssh", directives[i]) - } - theseMatches, err := filepath.Glob(path) - if err != nil { - return nil, err - } - matches = append(matches, theseMatches...) - } - matches = removeDups(matches) - inc.matches = matches - for i := range matches { - config, err := parseWithDepth(matches[i], depth) - if err != nil { - return nil, err - } - inc.files[matches[i]] = config - } - return inc, nil -} - -// Pos returns the position of the Include directive in the larger file. -func (i *Include) Pos() Position { - return i.position -} - -// Get finds the first value in the Include statement matching the alias and the -// given key. -func (inc *Include) Get(alias, key string) string { - inc.mu.Lock() - defer inc.mu.Unlock() - // TODO: we search files in any order which is not correct - for i := range inc.matches { - cfg := inc.files[inc.matches[i]] - if cfg == nil { - panic("nil cfg") - } - val, err := cfg.Get(alias, key) - if err == nil && val != "" { - return val - } - } - return "" -} - -// GetAll finds all values in the Include statement matching the alias and the -// given key. -func (inc *Include) GetAll(alias, key string) ([]string, error) { - inc.mu.Lock() - defer inc.mu.Unlock() - var vals []string - - // TODO: we search files in any order which is not correct - for i := range inc.matches { - cfg := inc.files[inc.matches[i]] - if cfg == nil { - panic("nil cfg") - } - val, err := cfg.GetAll(alias, key) - if err == nil && len(val) != 0 { - // In theory if SupportsMultiple was false for this key we could - // stop looking here. But the caller has asked us to find all - // instances of the keyword (and could use Get() if they wanted) so - // let's keep looking. - vals = append(vals, val...) - } - } - return vals, nil -} - -// String prints out a string representation of this Include directive. Note -// included Config files are not printed as part of this representation. -func (inc *Include) String() string { - equals := " " - if inc.hasEquals { - equals = " = " - } - line := fmt.Sprintf("%sInclude%s%s", strings.Repeat(" ", int(inc.leadingSpace)), equals, strings.Join(inc.directives, " ")) - if inc.Comment != "" { - line += " #" + inc.Comment - } - return line -} - -var matchAll *Pattern - -func init() { - var err error - matchAll, err = NewPattern("*") - if err != nil { - panic(err) - } -} - -func newConfig() *Config { - return &Config{ - Hosts: []*Host{ - &Host{ - implicit: true, - Patterns: []*Pattern{matchAll}, - Nodes: make([]Node, 0), - }, - }, - depth: 0, - } -} diff --git a/vendor/github.com/kevinburke/ssh_config/lexer.go b/vendor/github.com/kevinburke/ssh_config/lexer.go deleted file mode 100644 index 11680b4c74d..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/lexer.go +++ /dev/null @@ -1,240 +0,0 @@ -package ssh_config - -import ( - "bytes" -) - -// Define state functions -type sshLexStateFn func() sshLexStateFn - -type sshLexer struct { - inputIdx int - input []rune // Textual source - - buffer []rune // Runes composing the current token - tokens chan token - line int - col int - endbufferLine int - endbufferCol int -} - -func (s *sshLexer) lexComment(previousState sshLexStateFn) sshLexStateFn { - return func() sshLexStateFn { - growingString := "" - for next := s.peek(); next != '\n' && next != eof; next = s.peek() { - if next == '\r' && s.follow("\r\n") { - break - } - growingString += string(next) - s.next() - } - s.emitWithValue(tokenComment, growingString) - s.skip() - return previousState - } -} - -// lex the space after an equals sign in a function -func (s *sshLexer) lexRspace() sshLexStateFn { - for { - next := s.peek() - if !isSpace(next) { - break - } - s.skip() - } - return s.lexRvalue -} - -func (s *sshLexer) lexEquals() sshLexStateFn { - for { - next := s.peek() - if next == '=' { - s.emit(tokenEquals) - s.skip() - return s.lexRspace - } - // TODO error handling here; newline eof etc. - if !isSpace(next) { - break - } - s.skip() - } - return s.lexRvalue -} - -func (s *sshLexer) lexKey() sshLexStateFn { - growingString := "" - - for r := s.peek(); isKeyChar(r); r = s.peek() { - // simplified a lot here - if isSpace(r) || r == '=' { - s.emitWithValue(tokenKey, growingString) - s.skip() - return s.lexEquals - } - growingString += string(r) - s.next() - } - s.emitWithValue(tokenKey, growingString) - return s.lexEquals -} - -func (s *sshLexer) lexRvalue() sshLexStateFn { - growingString := "" - for { - next := s.peek() - switch next { - case '\r': - if s.follow("\r\n") { - s.emitWithValue(tokenString, growingString) - s.skip() - return s.lexVoid - } - case '\n': - s.emitWithValue(tokenString, growingString) - s.skip() - return s.lexVoid - case '#': - s.emitWithValue(tokenString, growingString) - s.skip() - return s.lexComment(s.lexVoid) - case eof: - s.next() - } - if next == eof { - break - } - growingString += string(next) - s.next() - } - s.emit(tokenEOF) - return nil -} - -func (s *sshLexer) read() rune { - r := s.peek() - if r == '\n' { - s.endbufferLine++ - s.endbufferCol = 1 - } else { - s.endbufferCol++ - } - s.inputIdx++ - return r -} - -func (s *sshLexer) next() rune { - r := s.read() - - if r != eof { - s.buffer = append(s.buffer, r) - } - return r -} - -func (s *sshLexer) lexVoid() sshLexStateFn { - for { - next := s.peek() - switch next { - case '#': - s.skip() - return s.lexComment(s.lexVoid) - case '\r': - fallthrough - case '\n': - s.emit(tokenEmptyLine) - s.skip() - continue - } - - if isSpace(next) { - s.skip() - } - - if isKeyStartChar(next) { - return s.lexKey - } - - // removed IsKeyStartChar and lexKey. probably will need to readd - - if next == eof { - s.next() - break - } - } - - s.emit(tokenEOF) - return nil -} - -func (s *sshLexer) ignore() { - s.buffer = make([]rune, 0) - s.line = s.endbufferLine - s.col = s.endbufferCol -} - -func (s *sshLexer) skip() { - s.next() - s.ignore() -} - -func (s *sshLexer) emit(t tokenType) { - s.emitWithValue(t, string(s.buffer)) -} - -func (s *sshLexer) emitWithValue(t tokenType, value string) { - tok := token{ - Position: Position{s.line, s.col}, - typ: t, - val: value, - } - s.tokens <- tok - s.ignore() -} - -func (s *sshLexer) peek() rune { - if s.inputIdx >= len(s.input) { - return eof - } - - r := s.input[s.inputIdx] - return r -} - -func (s *sshLexer) follow(next string) bool { - inputIdx := s.inputIdx - for _, expectedRune := range next { - if inputIdx >= len(s.input) { - return false - } - r := s.input[inputIdx] - inputIdx++ - if expectedRune != r { - return false - } - } - return true -} - -func (s *sshLexer) run() { - for state := s.lexVoid; state != nil; { - state = state() - } - close(s.tokens) -} - -func lexSSH(input []byte) chan token { - runes := bytes.Runes(input) - l := &sshLexer{ - input: runes, - tokens: make(chan token), - line: 1, - col: 1, - endbufferLine: 1, - endbufferCol: 1, - } - go l.run() - return l.tokens -} diff --git a/vendor/github.com/kevinburke/ssh_config/parser.go b/vendor/github.com/kevinburke/ssh_config/parser.go deleted file mode 100644 index 2b1e718cb3b..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/parser.go +++ /dev/null @@ -1,200 +0,0 @@ -package ssh_config - -import ( - "fmt" - "strings" - "unicode" -) - -type sshParser struct { - flow chan token - config *Config - tokensBuffer []token - currentTable []string - seenTableKeys []string - // /etc/ssh parser or local parser - used to find the default for relative - // filepaths in the Include directive - system bool - depth uint8 -} - -type sshParserStateFn func() sshParserStateFn - -// Formats and panics an error message based on a token -func (p *sshParser) raiseErrorf(tok *token, msg string, args ...interface{}) { - // TODO this format is ugly - panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...)) -} - -func (p *sshParser) raiseError(tok *token, err error) { - if err == ErrDepthExceeded { - panic(err) - } - // TODO this format is ugly - panic(tok.Position.String() + ": " + err.Error()) -} - -func (p *sshParser) run() { - for state := p.parseStart; state != nil; { - state = state() - } -} - -func (p *sshParser) peek() *token { - if len(p.tokensBuffer) != 0 { - return &(p.tokensBuffer[0]) - } - - tok, ok := <-p.flow - if !ok { - return nil - } - p.tokensBuffer = append(p.tokensBuffer, tok) - return &tok -} - -func (p *sshParser) getToken() *token { - if len(p.tokensBuffer) != 0 { - tok := p.tokensBuffer[0] - p.tokensBuffer = p.tokensBuffer[1:] - return &tok - } - tok, ok := <-p.flow - if !ok { - return nil - } - return &tok -} - -func (p *sshParser) parseStart() sshParserStateFn { - tok := p.peek() - - // end of stream, parsing is finished - if tok == nil { - return nil - } - - switch tok.typ { - case tokenComment, tokenEmptyLine: - return p.parseComment - case tokenKey: - return p.parseKV - case tokenEOF: - return nil - default: - p.raiseErrorf(tok, fmt.Sprintf("unexpected token %q\n", tok)) - } - return nil -} - -func (p *sshParser) parseKV() sshParserStateFn { - key := p.getToken() - hasEquals := false - val := p.getToken() - if val.typ == tokenEquals { - hasEquals = true - val = p.getToken() - } - comment := "" - tok := p.peek() - if tok == nil { - tok = &token{typ: tokenEOF} - } - if tok.typ == tokenComment && tok.Position.Line == val.Position.Line { - tok = p.getToken() - comment = tok.val - } - if strings.ToLower(key.val) == "match" { - // https://github.com/kevinburke/ssh_config/issues/6 - p.raiseErrorf(val, "ssh_config: Match directive parsing is unsupported") - return nil - } - if strings.ToLower(key.val) == "host" { - strPatterns := strings.Split(val.val, " ") - patterns := make([]*Pattern, 0) - for i := range strPatterns { - if strPatterns[i] == "" { - continue - } - pat, err := NewPattern(strPatterns[i]) - if err != nil { - p.raiseErrorf(val, "Invalid host pattern: %v", err) - return nil - } - patterns = append(patterns, pat) - } - // val.val at this point could be e.g. "example.com " - hostval := strings.TrimRightFunc(val.val, unicode.IsSpace) - spaceBeforeComment := val.val[len(hostval):] - val.val = hostval - p.config.Hosts = append(p.config.Hosts, &Host{ - Patterns: patterns, - Nodes: make([]Node, 0), - EOLComment: comment, - spaceBeforeComment: spaceBeforeComment, - hasEquals: hasEquals, - }) - return p.parseStart - } - lastHost := p.config.Hosts[len(p.config.Hosts)-1] - if strings.ToLower(key.val) == "include" { - inc, err := NewInclude(strings.Split(val.val, " "), hasEquals, key.Position, comment, p.system, p.depth+1) - if err == ErrDepthExceeded { - p.raiseError(val, err) - return nil - } - if err != nil { - p.raiseErrorf(val, "Error parsing Include directive: %v", err) - return nil - } - lastHost.Nodes = append(lastHost.Nodes, inc) - return p.parseStart - } - shortval := strings.TrimRightFunc(val.val, unicode.IsSpace) - spaceAfterValue := val.val[len(shortval):] - kv := &KV{ - Key: key.val, - Value: shortval, - spaceAfterValue: spaceAfterValue, - Comment: comment, - hasEquals: hasEquals, - leadingSpace: key.Position.Col - 1, - position: key.Position, - } - lastHost.Nodes = append(lastHost.Nodes, kv) - return p.parseStart -} - -func (p *sshParser) parseComment() sshParserStateFn { - comment := p.getToken() - lastHost := p.config.Hosts[len(p.config.Hosts)-1] - lastHost.Nodes = append(lastHost.Nodes, &Empty{ - Comment: comment.val, - // account for the "#" as well - leadingSpace: comment.Position.Col - 2, - position: comment.Position, - }) - return p.parseStart -} - -func parseSSH(flow chan token, system bool, depth uint8) *Config { - // Ensure we consume tokens to completion even if parser exits early - defer func() { - for range flow { - } - }() - - result := newConfig() - result.position = Position{1, 1} - parser := &sshParser{ - flow: flow, - config: result, - tokensBuffer: make([]token, 0), - currentTable: make([]string, 0), - seenTableKeys: make([]string, 0), - system: system, - depth: depth, - } - parser.run() - return result -} diff --git a/vendor/github.com/kevinburke/ssh_config/position.go b/vendor/github.com/kevinburke/ssh_config/position.go deleted file mode 100644 index e0b5e3fb33c..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/position.go +++ /dev/null @@ -1,25 +0,0 @@ -package ssh_config - -import "fmt" - -// Position of a document element within a SSH document. -// -// Line and Col are both 1-indexed positions for the element's line number and -// column number, respectively. Values of zero or less will cause Invalid(), -// to return true. -type Position struct { - Line int // line within the document - Col int // column within the line -} - -// String representation of the position. -// Displays 1-indexed line and column numbers. -func (p Position) String() string { - return fmt.Sprintf("(%d, %d)", p.Line, p.Col) -} - -// Invalid returns whether or not the position is valid (i.e. with negative or -// null values) -func (p Position) Invalid() bool { - return p.Line <= 0 || p.Col <= 0 -} diff --git a/vendor/github.com/kevinburke/ssh_config/token.go b/vendor/github.com/kevinburke/ssh_config/token.go deleted file mode 100644 index a0ecbb2bb7d..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/token.go +++ /dev/null @@ -1,49 +0,0 @@ -package ssh_config - -import "fmt" - -type token struct { - Position - typ tokenType - val string -} - -func (t token) String() string { - switch t.typ { - case tokenEOF: - return "EOF" - } - return fmt.Sprintf("%q", t.val) -} - -type tokenType int - -const ( - eof = -(iota + 1) -) - -const ( - tokenError tokenType = iota - tokenEOF - tokenEmptyLine - tokenComment - tokenKey - tokenEquals - tokenString -) - -func isSpace(r rune) bool { - return r == ' ' || r == '\t' -} - -func isKeyStartChar(r rune) bool { - return !(isSpace(r) || r == '\r' || r == '\n' || r == eof) -} - -// I'm not sure that this is correct -func isKeyChar(r rune) bool { - // Keys start with the first character that isn't whitespace or [ and end - // with the last non-whitespace character before the equals sign. Keys - // cannot contain a # character." - return !(r == '\r' || r == '\n' || r == eof || r == '=') -} diff --git a/vendor/github.com/kevinburke/ssh_config/validators.go b/vendor/github.com/kevinburke/ssh_config/validators.go deleted file mode 100644 index 5977f90960f..00000000000 --- a/vendor/github.com/kevinburke/ssh_config/validators.go +++ /dev/null @@ -1,186 +0,0 @@ -package ssh_config - -import ( - "fmt" - "strconv" - "strings" -) - -// Default returns the default value for the given keyword, for example "22" if -// the keyword is "Port". Default returns the empty string if the keyword has no -// default, or if the keyword is unknown. Keyword matching is case-insensitive. -// -// Default values are provided by OpenSSH_7.4p1 on a Mac. -func Default(keyword string) string { - return defaults[strings.ToLower(keyword)] -} - -// Arguments where the value must be "yes" or "no" and *only* yes or no. -var yesnos = map[string]bool{ - strings.ToLower("BatchMode"): true, - strings.ToLower("CanonicalizeFallbackLocal"): true, - strings.ToLower("ChallengeResponseAuthentication"): true, - strings.ToLower("CheckHostIP"): true, - strings.ToLower("ClearAllForwardings"): true, - strings.ToLower("Compression"): true, - strings.ToLower("EnableSSHKeysign"): true, - strings.ToLower("ExitOnForwardFailure"): true, - strings.ToLower("ForwardAgent"): true, - strings.ToLower("ForwardX11"): true, - strings.ToLower("ForwardX11Trusted"): true, - strings.ToLower("GatewayPorts"): true, - strings.ToLower("GSSAPIAuthentication"): true, - strings.ToLower("GSSAPIDelegateCredentials"): true, - strings.ToLower("HostbasedAuthentication"): true, - strings.ToLower("IdentitiesOnly"): true, - strings.ToLower("KbdInteractiveAuthentication"): true, - strings.ToLower("NoHostAuthenticationForLocalhost"): true, - strings.ToLower("PasswordAuthentication"): true, - strings.ToLower("PermitLocalCommand"): true, - strings.ToLower("PubkeyAuthentication"): true, - strings.ToLower("RhostsRSAAuthentication"): true, - strings.ToLower("RSAAuthentication"): true, - strings.ToLower("StreamLocalBindUnlink"): true, - strings.ToLower("TCPKeepAlive"): true, - strings.ToLower("UseKeychain"): true, - strings.ToLower("UsePrivilegedPort"): true, - strings.ToLower("VisualHostKey"): true, -} - -var uints = map[string]bool{ - strings.ToLower("CanonicalizeMaxDots"): true, - strings.ToLower("CompressionLevel"): true, // 1 to 9 - strings.ToLower("ConnectionAttempts"): true, - strings.ToLower("ConnectTimeout"): true, - strings.ToLower("NumberOfPasswordPrompts"): true, - strings.ToLower("Port"): true, - strings.ToLower("ServerAliveCountMax"): true, - strings.ToLower("ServerAliveInterval"): true, -} - -func mustBeYesOrNo(lkey string) bool { - return yesnos[lkey] -} - -func mustBeUint(lkey string) bool { - return uints[lkey] -} - -func validate(key, val string) error { - lkey := strings.ToLower(key) - if mustBeYesOrNo(lkey) && (val != "yes" && val != "no") { - return fmt.Errorf("ssh_config: value for key %q must be 'yes' or 'no', got %q", key, val) - } - if mustBeUint(lkey) { - _, err := strconv.ParseUint(val, 10, 64) - if err != nil { - return fmt.Errorf("ssh_config: %v", err) - } - } - return nil -} - -var defaults = map[string]string{ - strings.ToLower("AddKeysToAgent"): "no", - strings.ToLower("AddressFamily"): "any", - strings.ToLower("BatchMode"): "no", - strings.ToLower("CanonicalizeFallbackLocal"): "yes", - strings.ToLower("CanonicalizeHostname"): "no", - strings.ToLower("CanonicalizeMaxDots"): "1", - strings.ToLower("ChallengeResponseAuthentication"): "yes", - strings.ToLower("CheckHostIP"): "yes", - // TODO is this still the correct cipher - strings.ToLower("Cipher"): "3des", - strings.ToLower("Ciphers"): "chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,aes128-cbc,aes192-cbc,aes256-cbc", - strings.ToLower("ClearAllForwardings"): "no", - strings.ToLower("Compression"): "no", - strings.ToLower("CompressionLevel"): "6", - strings.ToLower("ConnectionAttempts"): "1", - strings.ToLower("ControlMaster"): "no", - strings.ToLower("EnableSSHKeysign"): "no", - strings.ToLower("EscapeChar"): "~", - strings.ToLower("ExitOnForwardFailure"): "no", - strings.ToLower("FingerprintHash"): "sha256", - strings.ToLower("ForwardAgent"): "no", - strings.ToLower("ForwardX11"): "no", - strings.ToLower("ForwardX11Timeout"): "20m", - strings.ToLower("ForwardX11Trusted"): "no", - strings.ToLower("GatewayPorts"): "no", - strings.ToLower("GlobalKnownHostsFile"): "/etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2", - strings.ToLower("GSSAPIAuthentication"): "no", - strings.ToLower("GSSAPIDelegateCredentials"): "no", - strings.ToLower("HashKnownHosts"): "no", - strings.ToLower("HostbasedAuthentication"): "no", - - strings.ToLower("HostbasedKeyTypes"): "ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,ssh-rsa", - strings.ToLower("HostKeyAlgorithms"): "ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,ssh-rsa", - // HostName has a dynamic default (the value passed at the command line). - - strings.ToLower("IdentitiesOnly"): "no", - strings.ToLower("IdentityFile"): "~/.ssh/identity", - - // IPQoS has a dynamic default based on interactive or non-interactive - // sessions. - - strings.ToLower("KbdInteractiveAuthentication"): "yes", - - strings.ToLower("KexAlgorithms"): "curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1", - strings.ToLower("LogLevel"): "INFO", - strings.ToLower("MACs"): "umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1", - - strings.ToLower("NoHostAuthenticationForLocalhost"): "no", - strings.ToLower("NumberOfPasswordPrompts"): "3", - strings.ToLower("PasswordAuthentication"): "yes", - strings.ToLower("PermitLocalCommand"): "no", - strings.ToLower("Port"): "22", - - strings.ToLower("PreferredAuthentications"): "gssapi-with-mic,hostbased,publickey,keyboard-interactive,password", - strings.ToLower("Protocol"): "2", - strings.ToLower("ProxyUseFdpass"): "no", - strings.ToLower("PubkeyAcceptedKeyTypes"): "ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,ssh-rsa", - strings.ToLower("PubkeyAuthentication"): "yes", - strings.ToLower("RekeyLimit"): "default none", - strings.ToLower("RhostsRSAAuthentication"): "no", - strings.ToLower("RSAAuthentication"): "yes", - - strings.ToLower("ServerAliveCountMax"): "3", - strings.ToLower("ServerAliveInterval"): "0", - strings.ToLower("StreamLocalBindMask"): "0177", - strings.ToLower("StreamLocalBindUnlink"): "no", - strings.ToLower("StrictHostKeyChecking"): "ask", - strings.ToLower("TCPKeepAlive"): "yes", - strings.ToLower("Tunnel"): "no", - strings.ToLower("TunnelDevice"): "any:any", - strings.ToLower("UpdateHostKeys"): "no", - strings.ToLower("UseKeychain"): "no", - strings.ToLower("UsePrivilegedPort"): "no", - - strings.ToLower("UserKnownHostsFile"): "~/.ssh/known_hosts ~/.ssh/known_hosts2", - strings.ToLower("VerifyHostKeyDNS"): "no", - strings.ToLower("VisualHostKey"): "no", - strings.ToLower("XAuthLocation"): "/usr/X11R6/bin/xauth", -} - -// these identities are used for SSH protocol 2 -var defaultProtocol2Identities = []string{ - "~/.ssh/id_dsa", - "~/.ssh/id_ecdsa", - "~/.ssh/id_ed25519", - "~/.ssh/id_rsa", -} - -// these directives support multiple items that can be collected -// across multiple files -var pluralDirectives = map[string]bool{ - "CertificateFile": true, - "IdentityFile": true, - "DynamicForward": true, - "RemoteForward": true, - "SendEnv": true, - "SetEnv": true, -} - -// SupportsMultiple reports whether a directive can be specified multiple times. -func SupportsMultiple(key string) bool { - return pluralDirectives[strings.ToLower(key)] -} diff --git a/vendor/github.com/kr/logfmt/.gitignore b/vendor/github.com/kr/logfmt/.gitignore deleted file mode 100644 index 8e524f68a72..00000000000 --- a/vendor/github.com/kr/logfmt/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.test -*.swp -*.prof diff --git a/vendor/github.com/kr/logfmt/Readme b/vendor/github.com/kr/logfmt/Readme deleted file mode 100644 index 1865a11f7c1..00000000000 --- a/vendor/github.com/kr/logfmt/Readme +++ /dev/null @@ -1,12 +0,0 @@ -Go package for parsing (and, eventually, generating) -log lines in the logfmt style. - -See http://godoc.org/github.com/kr/logfmt for format, and other documentation and examples. - -Copyright (C) 2013 Keith Rarick, Blake Mizerany - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/kr/logfmt/decode.go b/vendor/github.com/kr/logfmt/decode.go deleted file mode 100644 index 1397fb74675..00000000000 --- a/vendor/github.com/kr/logfmt/decode.go +++ /dev/null @@ -1,184 +0,0 @@ -// Package implements the decoding of logfmt key-value pairs. -// -// Example logfmt message: -// -// foo=bar a=14 baz="hello kitty" cool%story=bro f %^asdf -// -// Example result in JSON: -// -// { "foo": "bar", "a": 14, "baz": "hello kitty", "cool%story": "bro", "f": true, "%^asdf": true } -// -// EBNFish: -// -// ident_byte = any byte greater than ' ', excluding '=' and '"' -// string_byte = any byte excluding '"' and '\' -// garbage = !ident_byte -// ident = ident_byte, { ident byte } -// key = ident -// value = ident | '"', { string_byte | '\', '"' }, '"' -// pair = key, '=', value | key, '=' | key -// message = { garbage, pair }, garbage -package logfmt - -import ( - "reflect" - "strconv" - "strings" - "time" -) - -// Handler is the interface implemented by objects that accept logfmt -// key-value pairs. HandleLogfmt must copy the logfmt data if it -// wishes to retain the data after returning. -type Handler interface { - HandleLogfmt(key, val []byte) error -} - -// The HandlerFunc type is an adapter to allow the use of ordinary functions as -// logfmt handlers. If f is a function with the appropriate signature, -// HandlerFunc(f) is a Handler object that calls f. -type HandlerFunc func(key, val []byte) error - -func (f HandlerFunc) HandleLogfmt(key, val []byte) error { - return f(key, val) -} - -// Unmarshal parses the logfmt encoding data and stores the result in the value -// pointed to by v. If v is an Handler, HandleLogfmt will be called for each -// key-value pair. -// -// If v is not a Handler, it will pass v to NewStructHandler and use the -// returned StructHandler for decoding. -func Unmarshal(data []byte, v interface{}) (err error) { - h, ok := v.(Handler) - if !ok { - h, err = NewStructHandler(v) - if err != nil { - return err - } - } - return gotoScanner(data, h) -} - -// StructHandler unmarshals logfmt into a struct. It matches incoming keys to -// the the struct's fields (either the struct field name or its tag, preferring -// an exact match but also accepting a case-insensitive match. -// -// Field types supported by StructHandler are: -// -// all numeric types (e.g. float32, int, etc.) -// []byte -// string -// bool - true if key is present, false otherwise (the value is ignored). -// time.Duration - uses time.ParseDuration -// -// If a field is a pointer to an above type, and a matching key is not present -// in the logfmt data, the pointer will be untouched. -// -// If v is not a pointer to an Handler or struct, Unmarshal will return an -// error. -type StructHandler struct { - rv reflect.Value -} - -func NewStructHandler(v interface{}) (Handler, error) { - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr || rv.IsNil() { - return nil, &InvalidUnmarshalError{reflect.TypeOf(v)} - } - return &StructHandler{rv: rv}, nil -} - -func (h *StructHandler) HandleLogfmt(key, val []byte) error { - el := h.rv.Elem() - skey := string(key) - for i := 0; i < el.NumField(); i++ { - fv := el.Field(i) - ft := el.Type().Field(i) - switch { - case ft.Name == skey: - case ft.Tag.Get("logfmt") == skey: - case strings.EqualFold(ft.Name, skey): - default: - continue - } - if fv.Kind() == reflect.Ptr { - if fv.IsNil() { - t := fv.Type().Elem() - v := reflect.New(t) - fv.Set(v) - fv = v - } - fv = fv.Elem() - } - switch fv.Interface().(type) { - case time.Duration: - d, err := time.ParseDuration(string(val)) - if err != nil { - return &UnmarshalTypeError{string(val), fv.Type()} - } - fv.Set(reflect.ValueOf(d)) - case string: - fv.SetString(string(val)) - case []byte: - b := make([]byte, len(val)) - copy(b, val) - fv.SetBytes(b) - case bool: - fv.SetBool(true) - default: - switch { - case reflect.Int <= fv.Kind() && fv.Kind() <= reflect.Int64: - v, err := strconv.ParseInt(string(val), 10, 64) - if err != nil { - return err - } - fv.SetInt(v) - case reflect.Uint32 <= fv.Kind() && fv.Kind() <= reflect.Uint64: - v, err := strconv.ParseUint(string(val), 10, 64) - if err != nil { - return err - } - fv.SetUint(v) - case reflect.Float32 <= fv.Kind() && fv.Kind() <= reflect.Float64: - v, err := strconv.ParseFloat(string(val), 10) - if err != nil { - return err - } - fv.SetFloat(v) - default: - return &UnmarshalTypeError{string(val), fv.Type()} - } - } - - } - return nil -} - -// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. -// (The argument to Unmarshal must be a non-nil pointer.) -type InvalidUnmarshalError struct { - Type reflect.Type -} - -func (e *InvalidUnmarshalError) Error() string { - if e.Type == nil { - return "logfmt: Unmarshal(nil)" - } - - if e.Type.Kind() != reflect.Ptr { - return "logfmt: Unmarshal(non-pointer " + e.Type.String() + ")" - } - return "logfmt: Unmarshal(nil " + e.Type.String() + ")" -} - -// An UnmarshalTypeError describes a logfmt value that was -// not appropriate for a value of a specific Go type. -type UnmarshalTypeError struct { - Value string // the logfmt value - Type reflect.Type // type of Go value it could not be assigned to -} - -func (e *UnmarshalTypeError) Error() string { - return "logfmt: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() -} diff --git a/vendor/github.com/kr/logfmt/scanner.go b/vendor/github.com/kr/logfmt/scanner.go deleted file mode 100644 index 095221ff24b..00000000000 --- a/vendor/github.com/kr/logfmt/scanner.go +++ /dev/null @@ -1,149 +0,0 @@ -package logfmt - -import ( - "errors" - "fmt" -) - -var ErrUnterminatedString = errors.New("logfmt: unterminated string") - -func gotoScanner(data []byte, h Handler) (err error) { - saveError := func(e error) { - if err == nil { - err = e - } - } - - var c byte - var i int - var m int - var key []byte - var val []byte - var ok bool - var esc bool - -garbage: - if i == len(data) { - return - } - - c = data[i] - switch { - case c > ' ' && c != '"' && c != '=': - key, val = nil, nil - m = i - i++ - goto key - default: - i++ - goto garbage - } - -key: - if i >= len(data) { - if m >= 0 { - key = data[m:i] - saveError(h.HandleLogfmt(key, nil)) - } - return - } - - c = data[i] - switch { - case c > ' ' && c != '"' && c != '=': - i++ - goto key - case c == '=': - key = data[m:i] - i++ - goto equal - default: - key = data[m:i] - i++ - saveError(h.HandleLogfmt(key, nil)) - goto garbage - } - -equal: - if i >= len(data) { - if m >= 0 { - i-- - key = data[m:i] - saveError(h.HandleLogfmt(key, nil)) - } - return - } - - c = data[i] - switch { - case c > ' ' && c != '"' && c != '=': - m = i - i++ - goto ivalue - case c == '"': - m = i - i++ - esc = false - goto qvalue - default: - if key != nil { - saveError(h.HandleLogfmt(key, val)) - } - i++ - goto garbage - } - -ivalue: - if i >= len(data) { - if m >= 0 { - val = data[m:i] - saveError(h.HandleLogfmt(key, val)) - } - return - } - - c = data[i] - switch { - case c > ' ' && c != '"' && c != '=': - i++ - goto ivalue - default: - val = data[m:i] - saveError(h.HandleLogfmt(key, val)) - i++ - goto garbage - } - -qvalue: - if i >= len(data) { - if m >= 0 { - saveError(ErrUnterminatedString) - } - return - } - - c = data[i] - switch c { - case '\\': - i += 2 - esc = true - goto qvalue - case '"': - i++ - val = data[m:i] - if esc { - val, ok = unquoteBytes(val) - if !ok { - saveError(fmt.Errorf("logfmt: error unquoting bytes %q", string(val))) - goto garbage - } - } else { - val = val[1 : len(val)-1] - } - saveError(h.HandleLogfmt(key, val)) - goto garbage - default: - i++ - goto qvalue - } -} diff --git a/vendor/github.com/kr/logfmt/unquote.go b/vendor/github.com/kr/logfmt/unquote.go deleted file mode 100644 index fb088a476e1..00000000000 --- a/vendor/github.com/kr/logfmt/unquote.go +++ /dev/null @@ -1,149 +0,0 @@ -package logfmt - -import ( - "strconv" - "unicode" - "unicode/utf16" - "unicode/utf8" -) - -// Taken from Go's encoding/json - -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// getu4 decodes \uXXXX from the beginning of s, returning the hex value, -// or it returns -1. -func getu4(s []byte) rune { - if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { - return -1 - } - r, err := strconv.ParseUint(string(s[2:6]), 16, 64) - if err != nil { - return -1 - } - return rune(r) -} - -// unquote converts a quoted JSON string literal s into an actual string t. -// The rules are different than for Go, so cannot use strconv.Unquote. -func unquote(s []byte) (t string, ok bool) { - s, ok = unquoteBytes(s) - t = string(s) - return -} - -func unquoteBytes(s []byte) (t []byte, ok bool) { - if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { - return - } - s = s[1 : len(s)-1] - - // Check for unusual characters. If there are none, - // then no unquoting is needed, so return a slice of the - // original bytes. - r := 0 - for r < len(s) { - c := s[r] - if c == '\\' || c == '"' || c < ' ' { - break - } - if c < utf8.RuneSelf { - r++ - continue - } - rr, size := utf8.DecodeRune(s[r:]) - if rr == utf8.RuneError && size == 1 { - break - } - r += size - } - if r == len(s) { - return s, true - } - - b := make([]byte, len(s)+2*utf8.UTFMax) - w := copy(b, s[0:r]) - for r < len(s) { - // Out of room? Can only happen if s is full of - // malformed UTF-8 and we're replacing each - // byte with RuneError. - if w >= len(b)-2*utf8.UTFMax { - nb := make([]byte, (len(b)+utf8.UTFMax)*2) - copy(nb, b[0:w]) - b = nb - } - switch c := s[r]; { - case c == '\\': - r++ - if r >= len(s) { - return - } - switch s[r] { - default: - return - case '"', '\\', '/', '\'': - b[w] = s[r] - r++ - w++ - case 'b': - b[w] = '\b' - r++ - w++ - case 'f': - b[w] = '\f' - r++ - w++ - case 'n': - b[w] = '\n' - r++ - w++ - case 'r': - b[w] = '\r' - r++ - w++ - case 't': - b[w] = '\t' - r++ - w++ - case 'u': - r-- - rr := getu4(s[r:]) - if rr < 0 { - return - } - r += 6 - if utf16.IsSurrogate(rr) { - rr1 := getu4(s[r:]) - if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { - // A valid pair; consume. - r += 6 - w += utf8.EncodeRune(b[w:], dec) - break - } - // Invalid surrogate; fall back to replacement rune. - rr = unicode.ReplacementChar - } - w += utf8.EncodeRune(b[w:], rr) - } - - // Quote, control characters are invalid. - case c == '"', c < ' ': - return - - // ASCII - case c < utf8.RuneSelf: - b[w] = c - r++ - w++ - - // Coerce to well-formed UTF-8. - default: - rr, size := utf8.DecodeRune(s[r:]) - r += size - w += utf8.EncodeRune(b[w:], rr) - } - } - return b[0:w], true -} diff --git a/vendor/github.com/kyokomi/emoji/v2/.gitignore b/vendor/github.com/kyokomi/emoji/v2/.gitignore deleted file mode 100644 index 8cd9b91689a..00000000000 --- a/vendor/github.com/kyokomi/emoji/v2/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.idea -emoji.iml diff --git a/vendor/github.com/kyokomi/emoji/v2/LICENSE b/vendor/github.com/kyokomi/emoji/v2/LICENSE deleted file mode 100644 index 239874e0cb7..00000000000 --- a/vendor/github.com/kyokomi/emoji/v2/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 kyokomi - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/kyokomi/emoji/v2/README.md b/vendor/github.com/kyokomi/emoji/v2/README.md deleted file mode 100644 index e604598591c..00000000000 --- a/vendor/github.com/kyokomi/emoji/v2/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Emoji -Emoji is a simple golang package. - -[![wercker status](https://app.wercker.com/status/7bef60de2c6d3e0e6c13d56b2393c5d8/s/master "wercker status")](https://app.wercker.com/project/byKey/7bef60de2c6d3e0e6c13d56b2393c5d8) -[![Coverage Status](https://coveralls.io/repos/kyokomi/emoji/badge.png?branch=master)](https://coveralls.io/r/kyokomi/emoji?branch=master) -[![GoDoc](https://pkg.go.dev/badge/github.com/kyokomi/emoji.svg)](https://pkg.go.dev/github.com/kyokomi/emoji/v2) - -Get it: - -``` -go get github.com/kyokomi/emoji/v2 -``` - -Import it: - -``` -import ( - "github.com/kyokomi/emoji/v2" -) -``` - -## Usage - -```go -package main - -import ( - "fmt" - - "github.com/kyokomi/emoji/v2" -) - -func main() { - fmt.Println("Hello World Emoji!") - - emoji.Println(":beer: Beer!!!") - - pizzaMessage := emoji.Sprint("I like a :pizza: and :sushi:!!") - fmt.Println(pizzaMessage) -} -``` - -## Demo - -![demo](screen/image.png) - -## Reference - -- [unicode Emoji Charts](http://www.unicode.org/emoji/charts/emoji-list.html) - -## License - -[MIT](https://github.com/kyokomi/emoji/blob/master/LICENSE) diff --git a/vendor/github.com/kyokomi/emoji/v2/emoji.go b/vendor/github.com/kyokomi/emoji/v2/emoji.go deleted file mode 100644 index 6913a2ea4a2..00000000000 --- a/vendor/github.com/kyokomi/emoji/v2/emoji.go +++ /dev/null @@ -1,157 +0,0 @@ -// Package emoji terminal output. -package emoji - -import ( - "bytes" - "errors" - "fmt" - "io" - "regexp" - "unicode" -) - -//go:generate generateEmojiCodeMap -pkg emoji -o emoji_codemap.go - -// Replace Padding character for emoji. -var ( - ReplacePadding = " " -) - -// CodeMap gets the underlying map of emoji. -func CodeMap() map[string]string { - return emojiCode() -} - -// RevCodeMap gets the underlying map of emoji. -func RevCodeMap() map[string][]string { - return emojiRevCode() -} - -func AliasList(shortCode string) []string { - return emojiRevCode()[emojiCode()[shortCode]] -} - -// HasAlias flags if the given `shortCode` has multiple aliases with other -// codes. -func HasAlias(shortCode string) bool { - return len(AliasList(shortCode)) > 1 -} - -// NormalizeShortCode normalizes a given `shortCode` to a deterministic alias. -func NormalizeShortCode(shortCode string) string { - shortLists := AliasList(shortCode) - if len(shortLists) == 0 { - return shortCode - } - return shortLists[0] -} - -// regular expression that matches :flag-[countrycode]: -var flagRegexp = regexp.MustCompile(":flag-([a-z]{2}):") - -func emojize(x string) string { - str, ok := emojiCode()[x] - if ok { - return str + ReplacePadding - } - if match := flagRegexp.FindStringSubmatch(x); len(match) == 2 { - return regionalIndicator(match[1][0]) + regionalIndicator(match[1][1]) - } - return x -} - -// regionalIndicator maps a lowercase letter to a unicode regional indicator -func regionalIndicator(i byte) string { - return string('\U0001F1E6' + rune(i) - 'a') -} - -func replaseEmoji(input *bytes.Buffer) string { - emoji := bytes.NewBufferString(":") - for { - i, _, err := input.ReadRune() - if err != nil { - // not replase - return emoji.String() - } - - if i == ':' && emoji.Len() == 1 { - return emoji.String() + replaseEmoji(input) - } - - emoji.WriteRune(i) - switch { - case unicode.IsSpace(i): - return emoji.String() - case i == ':': - return emojize(emoji.String()) - } - } -} - -func compile(x string) string { - if x == "" { - return "" - } - - input := bytes.NewBufferString(x) - output := bytes.NewBufferString("") - - for { - i, _, err := input.ReadRune() - if err != nil { - break - } - switch i { - default: - output.WriteRune(i) - case ':': - output.WriteString(replaseEmoji(input)) - } - } - return output.String() -} - -// Print is fmt.Print which supports emoji -func Print(a ...interface{}) (int, error) { - return fmt.Print(compile(fmt.Sprint(a...))) -} - -// Println is fmt.Println which supports emoji -func Println(a ...interface{}) (int, error) { - return fmt.Println(compile(fmt.Sprint(a...))) -} - -// Printf is fmt.Printf which supports emoji -func Printf(format string, a ...interface{}) (int, error) { - return fmt.Print(compile(fmt.Sprintf(format, a...))) -} - -// Fprint is fmt.Fprint which supports emoji -func Fprint(w io.Writer, a ...interface{}) (int, error) { - return fmt.Fprint(w, compile(fmt.Sprint(a...))) -} - -// Fprintln is fmt.Fprintln which supports emoji -func Fprintln(w io.Writer, a ...interface{}) (int, error) { - return fmt.Fprintln(w, compile(fmt.Sprint(a...))) -} - -// Fprintf is fmt.Fprintf which supports emoji -func Fprintf(w io.Writer, format string, a ...interface{}) (int, error) { - return fmt.Fprint(w, compile(fmt.Sprintf(format, a...))) -} - -// Sprint is fmt.Sprint which supports emoji -func Sprint(a ...interface{}) string { - return compile(fmt.Sprint(a...)) -} - -// Sprintf is fmt.Sprintf which supports emoji -func Sprintf(format string, a ...interface{}) string { - return compile(fmt.Sprintf(format, a...)) -} - -// Errorf is fmt.Errorf which supports emoji -func Errorf(format string, a ...interface{}) error { - return errors.New(compile(Sprintf(format, a...))) -} diff --git a/vendor/github.com/kyokomi/emoji/v2/emoji_codemap.go b/vendor/github.com/kyokomi/emoji/v2/emoji_codemap.go deleted file mode 100644 index 9a9d73b05e1..00000000000 --- a/vendor/github.com/kyokomi/emoji/v2/emoji_codemap.go +++ /dev/null @@ -1,7715 +0,0 @@ -package emoji - -import ( - "sync" -) - -// NOTE: THIS FILE WAS PRODUCED BY THE -// EMOJICODEMAP CODE GENERATION TOOL (github.com/kyokomi/emoji/cmd/generateEmojiCodeMap) -// DO NOT EDIT - -var emojiCodeMap map[string]string -var emojiCodeMapInitOnce = sync.Once{} - -func emojiCode() map[string]string { - emojiCodeMapInitOnce.Do(func() { - emojiCodeMap = map[string]string{ - ":+1:": "\U0001f44d", - ":-1:": "\U0001f44e", - ":100:": "\U0001f4af", - ":1234:": "\U0001f522", - ":1st_place_medal:": "\U0001f947", - ":2nd_place_medal:": "\U0001f948", - ":3rd_place_medal:": "\U0001f949", - ":8ball:": "\U0001f3b1", - ":AB_button_(blood_type):": "\U0001f18e", - ":ATM_sign:": "\U0001f3e7", - ":A_button_(blood_type):": "\U0001f170", - ":Aquarius:": "\u2652", - ":Aries:": "\u2648", - ":BACK_arrow:": "\U0001f519", - ":B_button_(blood_type):": "\U0001f171", - ":CL_button:": "\U0001f191", - ":COOL_button:": "\U0001f192", - ":Cancer:": "\u264b", - ":Capricorn:": "\u2651", - ":Christmas_tree:": "\U0001f384", - ":END_arrow:": "\U0001f51a", - ":FREE_button:": "\U0001f193", - ":Gemini:": "\u264a", - ":ID_button:": "\U0001f194", - ":Japanese_acceptable_button:": "\U0001f251", - ":Japanese_application_button:": "\U0001f238", - ":Japanese_bargain_button:": "\U0001f250", - ":Japanese_castle:": "\U0001f3ef", - ":Japanese_congratulations_button:": "\u3297", - ":Japanese_discount_button:": "\U0001f239", - ":Japanese_dolls:": "\U0001f38e", - ":Japanese_free_of_charge_button:": "\U0001f21a", - ":Japanese_here_button:": "\U0001f201", - ":Japanese_monthly_amount_button:": "\U0001f237", - ":Japanese_no_vacancy_button:": "\U0001f235", - ":Japanese_not_free_of_charge_button:": "\U0001f236", - ":Japanese_open_for_business_button:": "\U0001f23a", - ":Japanese_passing_grade_button:": "\U0001f234", - ":Japanese_post_office:": "\U0001f3e3", - ":Japanese_prohibited_button:": "\U0001f232", - ":Japanese_reserved_button:": "\U0001f22f", - ":Japanese_secret_button:": "\u3299", - ":Japanese_service_charge_button:": "\U0001f202", - ":Japanese_symbol_for_beginner:": "\U0001f530", - ":Japanese_vacancy_button:": "\U0001f233", - ":Leo:": "\u264c", - ":Libra:": "\u264e", - ":Mrs._Claus:": "\U0001f936", - ":NEW_button:": "\U0001f195", - ":NG_button:": "\U0001f196", - ":OK_button:": "\U0001f197", - ":OK_hand:": "\U0001f44c", - ":ON!_arrow:": "\U0001f51b", - ":O_button_(blood_type):": "\U0001f17e", - ":Ophiuchus:": "\u26ce", - ":P_button:": "\U0001f17f", - ":Pisces:": "\u2653", - ":SOON_arrow:": "\U0001f51c", - ":SOS_button:": "\U0001f198", - ":Sagittarius:": "\u2650", - ":Santa_Claus:": "\U0001f385", - ":Scorpio:": "\u264f", - ":Statue_of_Liberty:": "\U0001f5fd", - ":T-Rex:": "\U0001f996", - ":TOP_arrow:": "\U0001f51d", - ":Taurus:": "\u2649", - ":Tokyo_tower:": "\U0001f5fc", - ":UP!_button:": "\U0001f199", - ":VS_button:": "\U0001f19a", - ":Virgo:": "\u264d", - ":a:": "\U0001f170\ufe0f", - ":ab:": "\U0001f18e", - ":abacus:": "\U0001f9ee", - ":abc:": "\U0001f524", - ":abcd:": "\U0001f521", - ":accept:": "\U0001f251", - ":accordion:": "\U0001fa97", - ":adhesive_bandage:": "\U0001fa79", - ":admission_tickets:": "\U0001f39f\ufe0f", - ":adult:": "\U0001f9d1", - ":adult_tone1:": "\U0001f9d1\U0001f3fb", - ":adult_tone2:": "\U0001f9d1\U0001f3fc", - ":adult_tone3:": "\U0001f9d1\U0001f3fd", - ":adult_tone4:": "\U0001f9d1\U0001f3fe", - ":adult_tone5:": "\U0001f9d1\U0001f3ff", - ":aerial_tramway:": "\U0001f6a1", - ":afghanistan:": "\U0001f1e6\U0001f1eb", - ":airplane:": "\u2708\ufe0f", - ":airplane_arrival:": "\U0001f6ec", - ":airplane_arriving:": "\U0001f6ec", - ":airplane_departure:": "\U0001f6eb", - ":airplane_small:": "\U0001f6e9", - ":aland_islands:": "\U0001f1e6\U0001f1fd", - ":alarm_clock:": "\u23f0", - ":albania:": "\U0001f1e6\U0001f1f1", - ":alembic:": "\u2697\ufe0f", - ":algeria:": "\U0001f1e9\U0001f1ff", - ":alien:": "\U0001f47d", - ":alien_monster:": "\U0001f47e", - ":ambulance:": "\U0001f691", - ":american_football:": "\U0001f3c8", - ":american_samoa:": "\U0001f1e6\U0001f1f8", - ":amphora:": "\U0001f3fa", - ":anatomical_heart:": "\U0001fac0", - ":anchor:": "\u2693", - ":andorra:": "\U0001f1e6\U0001f1e9", - ":angel:": "\U0001f47c", - ":angel_tone1:": "\U0001f47c\U0001f3fb", - ":angel_tone2:": "\U0001f47c\U0001f3fc", - ":angel_tone3:": "\U0001f47c\U0001f3fd", - ":angel_tone4:": "\U0001f47c\U0001f3fe", - ":angel_tone5:": "\U0001f47c\U0001f3ff", - ":anger:": "\U0001f4a2", - ":anger_right:": "\U0001f5ef", - ":anger_symbol:": "\U0001f4a2", - ":angola:": "\U0001f1e6\U0001f1f4", - ":angry:": "\U0001f620", - ":angry_face:": "\U0001f620", - ":angry_face_with_horns:": "\U0001f47f", - ":anguilla:": "\U0001f1e6\U0001f1ee", - ":anguished:": "\U0001f627", - ":anguished_face:": "\U0001f627", - ":ant:": "\U0001f41c", - ":antarctica:": "\U0001f1e6\U0001f1f6", - ":antenna_bars:": "\U0001f4f6", - ":antigua_barbuda:": "\U0001f1e6\U0001f1ec", - ":anxious_face_with_sweat:": "\U0001f630", - ":apple:": "\U0001f34e", - ":aquarius:": "\u2652", - ":argentina:": "\U0001f1e6\U0001f1f7", - ":aries:": "\u2648", - ":armenia:": "\U0001f1e6\U0001f1f2", - ":arrow_backward:": "\u25c0\ufe0f", - ":arrow_double_down:": "\u23ec", - ":arrow_double_up:": "\u23eb", - ":arrow_down:": "\u2b07\ufe0f", - ":arrow_down_small:": "\U0001f53d", - ":arrow_forward:": "\u25b6\ufe0f", - ":arrow_heading_down:": "\u2935\ufe0f", - ":arrow_heading_up:": "\u2934\ufe0f", - ":arrow_left:": "\u2b05\ufe0f", - ":arrow_lower_left:": "\u2199\ufe0f", - ":arrow_lower_right:": "\u2198\ufe0f", - ":arrow_right:": "\u27a1\ufe0f", - ":arrow_right_hook:": "\u21aa\ufe0f", - ":arrow_up:": "\u2b06\ufe0f", - ":arrow_up_down:": "\u2195\ufe0f", - ":arrow_up_small:": "\U0001f53c", - ":arrow_upper_left:": "\u2196\ufe0f", - ":arrow_upper_right:": "\u2197\ufe0f", - ":arrows_clockwise:": "\U0001f503", - ":arrows_counterclockwise:": "\U0001f504", - ":art:": "\U0001f3a8", - ":articulated_lorry:": "\U0001f69b", - ":artificial_satellite:": "\U0001f6f0\ufe0f", - ":artist:": "\U0001f9d1\u200d\U0001f3a8", - ":artist_palette:": "\U0001f3a8", - ":aruba:": "\U0001f1e6\U0001f1fc", - ":ascension_island:": "\U0001f1e6\U0001f1e8", - ":asterisk:": "*\ufe0f\u20e3", - ":astonished:": "\U0001f632", - ":astonished_face:": "\U0001f632", - ":astronaut:": "\U0001f9d1\u200d\U0001f680", - ":athletic_shoe:": "\U0001f45f", - ":atm:": "\U0001f3e7", - ":atom:": "\u269b", - ":atom_symbol:": "\u269b\ufe0f", - ":australia:": "\U0001f1e6\U0001f1fa", - ":austria:": "\U0001f1e6\U0001f1f9", - ":auto_rickshaw:": "\U0001f6fa", - ":automobile:": "\U0001f697", - ":avocado:": "\U0001f951", - ":axe:": "\U0001fa93", - ":azerbaijan:": "\U0001f1e6\U0001f1ff", - ":b:": "\U0001f171\ufe0f", - ":baby:": "\U0001f476", - ":baby_angel:": "\U0001f47c", - ":baby_bottle:": "\U0001f37c", - ":baby_chick:": "\U0001f424", - ":baby_symbol:": "\U0001f6bc", - ":baby_tone1:": "\U0001f476\U0001f3fb", - ":baby_tone2:": "\U0001f476\U0001f3fc", - ":baby_tone3:": "\U0001f476\U0001f3fd", - ":baby_tone4:": "\U0001f476\U0001f3fe", - ":baby_tone5:": "\U0001f476\U0001f3ff", - ":back:": "\U0001f519", - ":backhand_index_pointing_down:": "\U0001f447", - ":backhand_index_pointing_left:": "\U0001f448", - ":backhand_index_pointing_right:": "\U0001f449", - ":backhand_index_pointing_up:": "\U0001f446", - ":backpack:": "\U0001f392", - ":bacon:": "\U0001f953", - ":badger:": "\U0001f9a1", - ":badminton:": "\U0001f3f8", - ":badminton_racquet_and_shuttlecock:": "\U0001f3f8", - ":bagel:": "\U0001f96f", - ":baggage_claim:": "\U0001f6c4", - ":baguette_bread:": "\U0001f956", - ":bahamas:": "\U0001f1e7\U0001f1f8", - ":bahrain:": "\U0001f1e7\U0001f1ed", - ":balance_scale:": "\u2696", - ":bald:": "\U0001f9b2", - ":bald_man:": "\U0001f468\u200d\U0001f9b2", - ":bald_person:": "\U0001f9d1\u200d\U0001f9b2", - ":bald_woman:": "\U0001f469\u200d\U0001f9b2", - ":ballet_shoes:": "\U0001fa70", - ":balloon:": "\U0001f388", - ":ballot_box:": "\U0001f5f3", - ":ballot_box_with_ballot:": "\U0001f5f3\ufe0f", - ":ballot_box_with_check:": "\u2611\ufe0f", - ":bamboo:": "\U0001f38d", - ":banana:": "\U0001f34c", - ":bangbang:": "\u203c\ufe0f", - ":bangladesh:": "\U0001f1e7\U0001f1e9", - ":banjo:": "\U0001fa95", - ":bank:": "\U0001f3e6", - ":bar_chart:": "\U0001f4ca", - ":barbados:": "\U0001f1e7\U0001f1e7", - ":barber:": "\U0001f488", - ":barber_pole:": "\U0001f488", - ":barely_sunny:": "\U0001f325\ufe0f", - ":baseball:": "\u26be", - ":basket:": "\U0001f9fa", - ":basketball:": "\U0001f3c0", - ":basketball_man:": "\u26f9\ufe0f\u200d\u2642\ufe0f", - ":basketball_woman:": "\u26f9\ufe0f\u200d\u2640\ufe0f", - ":bat:": "\U0001f987", - ":bath:": "\U0001f6c0", - ":bath_tone1:": "\U0001f6c0\U0001f3fb", - ":bath_tone2:": "\U0001f6c0\U0001f3fc", - ":bath_tone3:": "\U0001f6c0\U0001f3fd", - ":bath_tone4:": "\U0001f6c0\U0001f3fe", - ":bath_tone5:": "\U0001f6c0\U0001f3ff", - ":bathtub:": "\U0001f6c1", - ":battery:": "\U0001f50b", - ":beach:": "\U0001f3d6", - ":beach_umbrella:": "\u26f1", - ":beach_with_umbrella:": "\U0001f3d6\ufe0f", - ":beaming_face_with_smiling_eyes:": "\U0001f601", - ":bear:": "\U0001f43b", - ":bearded_person:": "\U0001f9d4", - ":bearded_person_tone1:": "\U0001f9d4\U0001f3fb", - ":bearded_person_tone2:": "\U0001f9d4\U0001f3fc", - ":bearded_person_tone3:": "\U0001f9d4\U0001f3fd", - ":bearded_person_tone4:": "\U0001f9d4\U0001f3fe", - ":bearded_person_tone5:": "\U0001f9d4\U0001f3ff", - ":beating_heart:": "\U0001f493", - ":beaver:": "\U0001f9ab", - ":bed:": "\U0001f6cf\ufe0f", - ":bee:": "\U0001f41d", - ":beer:": "\U0001f37a", - ":beer_mug:": "\U0001f37a", - ":beers:": "\U0001f37b", - ":beetle:": "\U0001fab2", - ":beginner:": "\U0001f530", - ":belarus:": "\U0001f1e7\U0001f1fe", - ":belgium:": "\U0001f1e7\U0001f1ea", - ":belize:": "\U0001f1e7\U0001f1ff", - ":bell:": "\U0001f514", - ":bell_pepper:": "\U0001fad1", - ":bell_with_slash:": "\U0001f515", - ":bellhop:": "\U0001f6ce", - ":bellhop_bell:": "\U0001f6ce\ufe0f", - ":benin:": "\U0001f1e7\U0001f1ef", - ":bento:": "\U0001f371", - ":bento_box:": "\U0001f371", - ":bermuda:": "\U0001f1e7\U0001f1f2", - ":beverage_box:": "\U0001f9c3", - ":bhutan:": "\U0001f1e7\U0001f1f9", - ":bicycle:": "\U0001f6b2", - ":bicyclist:": "\U0001f6b4\u200d\u2642\ufe0f", - ":bike:": "\U0001f6b2", - ":biking_man:": "\U0001f6b4\u200d\u2642\ufe0f", - ":biking_woman:": "\U0001f6b4\u200d\u2640\ufe0f", - ":bikini:": "\U0001f459", - ":billed_cap:": "\U0001f9e2", - ":biohazard:": "\u2623", - ":biohazard_sign:": "\u2623\ufe0f", - ":bird:": "\U0001f426", - ":birthday:": "\U0001f382", - ":birthday_cake:": "\U0001f382", - ":bison:": "\U0001f9ac", - ":black_cat:": "\U0001f408\u200d\u2b1b", - ":black_circle:": "\u26ab", - ":black_circle_for_record:": "\u23fa\ufe0f", - ":black_flag:": "\U0001f3f4", - ":black_heart:": "\U0001f5a4", - ":black_joker:": "\U0001f0cf", - ":black_large_square:": "\u2b1b", - ":black_left_pointing_double_triangle_with_vertical_bar:": "\u23ee\ufe0f", - ":black_medium-small_square:": "\u25fe", - ":black_medium_small_square:": "\u25fe", - ":black_medium_square:": "\u25fc\ufe0f", - ":black_nib:": "\u2712\ufe0f", - ":black_right_pointing_double_triangle_with_vertical_bar:": "\u23ed\ufe0f", - ":black_right_pointing_triangle_with_double_vertical_bar:": "\u23ef\ufe0f", - ":black_small_square:": "\u25aa\ufe0f", - ":black_square_button:": "\U0001f532", - ":black_square_for_stop:": "\u23f9\ufe0f", - ":blond-haired-man:": "\U0001f471\u200d\u2642\ufe0f", - ":blond-haired-woman:": "\U0001f471\u200d\u2640\ufe0f", - ":blond-haired_man:": "\U0001f471\u200d\u2642\ufe0f", - ":blond-haired_man_tone1:": "\U0001f471\U0001f3fb\u200d\u2642\ufe0f", - ":blond-haired_man_tone2:": "\U0001f471\U0001f3fc\u200d\u2642\ufe0f", - ":blond-haired_man_tone3:": "\U0001f471\U0001f3fd\u200d\u2642\ufe0f", - ":blond-haired_man_tone4:": "\U0001f471\U0001f3fe\u200d\u2642\ufe0f", - ":blond-haired_man_tone5:": "\U0001f471\U0001f3ff\u200d\u2642\ufe0f", - ":blond-haired_woman:": "\U0001f471\u200d\u2640\ufe0f", - ":blond-haired_woman_tone1:": "\U0001f471\U0001f3fb\u200d\u2640\ufe0f", - ":blond-haired_woman_tone2:": "\U0001f471\U0001f3fc\u200d\u2640\ufe0f", - ":blond-haired_woman_tone3:": "\U0001f471\U0001f3fd\u200d\u2640\ufe0f", - ":blond-haired_woman_tone4:": "\U0001f471\U0001f3fe\u200d\u2640\ufe0f", - ":blond-haired_woman_tone5:": "\U0001f471\U0001f3ff\u200d\u2640\ufe0f", - ":blond_haired_man:": "\U0001f471\u200d\u2642\ufe0f", - ":blond_haired_person:": "\U0001f471", - ":blond_haired_person_tone1:": "\U0001f471\U0001f3fb", - ":blond_haired_person_tone2:": "\U0001f471\U0001f3fc", - ":blond_haired_person_tone3:": "\U0001f471\U0001f3fd", - ":blond_haired_person_tone4:": "\U0001f471\U0001f3fe", - ":blond_haired_person_tone5:": "\U0001f471\U0001f3ff", - ":blond_haired_woman:": "\U0001f471\u200d\u2640\ufe0f", - ":blonde_woman:": "\U0001f471\u200d\u2640\ufe0f", - ":blossom:": "\U0001f33c", - ":blowfish:": "\U0001f421", - ":blue_book:": "\U0001f4d8", - ":blue_car:": "\U0001f699", - ":blue_circle:": "\U0001f535", - ":blue_heart:": "\U0001f499", - ":blue_square:": "\U0001f7e6", - ":blueberries:": "\U0001fad0", - ":blush:": "\U0001f60a", - ":boar:": "\U0001f417", - ":boat:": "\u26f5", - ":bolivia:": "\U0001f1e7\U0001f1f4", - ":bomb:": "\U0001f4a3", - ":bone:": "\U0001f9b4", - ":book:": "\U0001f4d6", - ":bookmark:": "\U0001f516", - ":bookmark_tabs:": "\U0001f4d1", - ":books:": "\U0001f4da", - ":boom:": "\U0001f4a5", - ":boomerang:": "\U0001fa83", - ":boot:": "\U0001f462", - ":bosnia_herzegovina:": "\U0001f1e7\U0001f1e6", - ":botswana:": "\U0001f1e7\U0001f1fc", - ":bottle_with_popping_cork:": "\U0001f37e", - ":bouncing_ball_man:": "\u26f9\ufe0f\u200d\u2642\ufe0f", - ":bouncing_ball_person:": "\u26f9\ufe0f", - ":bouncing_ball_woman:": "\u26f9\ufe0f\u200d\u2640\ufe0f", - ":bouquet:": "\U0001f490", - ":bouvet_island:": "\U0001f1e7\U0001f1fb", - ":bow:": "\U0001f647\u200d\u2642\ufe0f", - ":bow_and_arrow:": "\U0001f3f9", - ":bowing_man:": "\U0001f647\u200d\u2642\ufe0f", - ":bowing_woman:": "\U0001f647\u200d\u2640\ufe0f", - ":bowl_with_spoon:": "\U0001f963", - ":bowling:": "\U0001f3b3", - ":boxing_glove:": "\U0001f94a", - ":boy:": "\U0001f466", - ":boy_tone1:": "\U0001f466\U0001f3fb", - ":boy_tone2:": "\U0001f466\U0001f3fc", - ":boy_tone3:": "\U0001f466\U0001f3fd", - ":boy_tone4:": "\U0001f466\U0001f3fe", - ":boy_tone5:": "\U0001f466\U0001f3ff", - ":brain:": "\U0001f9e0", - ":brazil:": "\U0001f1e7\U0001f1f7", - ":bread:": "\U0001f35e", - ":breast-feeding:": "\U0001f931", - ":breast_feeding:": "\U0001f931", - ":breast_feeding_tone1:": "\U0001f931\U0001f3fb", - ":breast_feeding_tone2:": "\U0001f931\U0001f3fc", - ":breast_feeding_tone3:": "\U0001f931\U0001f3fd", - ":breast_feeding_tone4:": "\U0001f931\U0001f3fe", - ":breast_feeding_tone5:": "\U0001f931\U0001f3ff", - ":brick:": "\U0001f9f1", - ":bricks:": "\U0001f9f1", - ":bride_with_veil:": "\U0001f470", - ":bride_with_veil_tone1:": "\U0001f470\U0001f3fb", - ":bride_with_veil_tone2:": "\U0001f470\U0001f3fc", - ":bride_with_veil_tone3:": "\U0001f470\U0001f3fd", - ":bride_with_veil_tone4:": "\U0001f470\U0001f3fe", - ":bride_with_veil_tone5:": "\U0001f470\U0001f3ff", - ":bridge_at_night:": "\U0001f309", - ":briefcase:": "\U0001f4bc", - ":briefs:": "\U0001fa72", - ":bright_button:": "\U0001f506", - ":british_indian_ocean_territory:": "\U0001f1ee\U0001f1f4", - ":british_virgin_islands:": "\U0001f1fb\U0001f1ec", - ":broccoli:": "\U0001f966", - ":broken_heart:": "\U0001f494", - ":broom:": "\U0001f9f9", - ":brown_circle:": "\U0001f7e4", - ":brown_heart:": "\U0001f90e", - ":brown_square:": "\U0001f7eb", - ":brunei:": "\U0001f1e7\U0001f1f3", - ":bubble_tea:": "\U0001f9cb", - ":bucket:": "\U0001faa3", - ":bug:": "\U0001f41b", - ":building_construction:": "\U0001f3d7\ufe0f", - ":bulb:": "\U0001f4a1", - ":bulgaria:": "\U0001f1e7\U0001f1ec", - ":bullet_train:": "\U0001f685", - ":bullettrain_front:": "\U0001f685", - ":bullettrain_side:": "\U0001f684", - ":bullseye:": "\U0001f3af", - ":burkina_faso:": "\U0001f1e7\U0001f1eb", - ":burrito:": "\U0001f32f", - ":burundi:": "\U0001f1e7\U0001f1ee", - ":bus:": "\U0001f68c", - ":bus_stop:": "\U0001f68f", - ":business_suit_levitating:": "\U0001f574\ufe0f", - ":busstop:": "\U0001f68f", - ":bust_in_silhouette:": "\U0001f464", - ":busts_in_silhouette:": "\U0001f465", - ":butter:": "\U0001f9c8", - ":butterfly:": "\U0001f98b", - ":cactus:": "\U0001f335", - ":cake:": "\U0001f370", - ":calendar:": "\U0001f4c6", - ":calendar_spiral:": "\U0001f5d3", - ":call_me:": "\U0001f919", - ":call_me_hand:": "\U0001f919", - ":call_me_tone1:": "\U0001f919\U0001f3fb", - ":call_me_tone2:": "\U0001f919\U0001f3fc", - ":call_me_tone3:": "\U0001f919\U0001f3fd", - ":call_me_tone4:": "\U0001f919\U0001f3fe", - ":call_me_tone5:": "\U0001f919\U0001f3ff", - ":calling:": "\U0001f4f2", - ":cambodia:": "\U0001f1f0\U0001f1ed", - ":camel:": "\U0001f42b", - ":camera:": "\U0001f4f7", - ":camera_flash:": "\U0001f4f8", - ":camera_with_flash:": "\U0001f4f8", - ":cameroon:": "\U0001f1e8\U0001f1f2", - ":camping:": "\U0001f3d5\ufe0f", - ":canada:": "\U0001f1e8\U0001f1e6", - ":canary_islands:": "\U0001f1ee\U0001f1e8", - ":cancer:": "\u264b", - ":candle:": "\U0001f56f\ufe0f", - ":candy:": "\U0001f36c", - ":canned_food:": "\U0001f96b", - ":canoe:": "\U0001f6f6", - ":cape_verde:": "\U0001f1e8\U0001f1fb", - ":capital_abcd:": "\U0001f520", - ":capricorn:": "\u2651", - ":car:": "\U0001f697", - ":card_box:": "\U0001f5c3", - ":card_file_box:": "\U0001f5c3\ufe0f", - ":card_index:": "\U0001f4c7", - ":card_index_dividers:": "\U0001f5c2\ufe0f", - ":caribbean_netherlands:": "\U0001f1e7\U0001f1f6", - ":carousel_horse:": "\U0001f3a0", - ":carp_streamer:": "\U0001f38f", - ":carpentry_saw:": "\U0001fa9a", - ":carrot:": "\U0001f955", - ":cartwheeling:": "\U0001f938", - ":castle:": "\U0001f3f0", - ":cat:": "\U0001f431", - ":cat2:": "\U0001f408", - ":cat_face:": "\U0001f431", - ":cat_with_tears_of_joy:": "\U0001f639", - ":cat_with_wry_smile:": "\U0001f63c", - ":cayman_islands:": "\U0001f1f0\U0001f1fe", - ":cd:": "\U0001f4bf", - ":central_african_republic:": "\U0001f1e8\U0001f1eb", - ":ceuta_melilla:": "\U0001f1ea\U0001f1e6", - ":chad:": "\U0001f1f9\U0001f1e9", - ":chains:": "\u26d3\ufe0f", - ":chair:": "\U0001fa91", - ":champagne:": "\U0001f37e", - ":champagne_glass:": "\U0001f942", - ":chart:": "\U0001f4b9", - ":chart_decreasing:": "\U0001f4c9", - ":chart_increasing:": "\U0001f4c8", - ":chart_increasing_with_yen:": "\U0001f4b9", - ":chart_with_downwards_trend:": "\U0001f4c9", - ":chart_with_upwards_trend:": "\U0001f4c8", - ":check_box_with_check:": "\u2611", - ":check_mark:": "\u2714", - ":check_mark_button:": "\u2705", - ":checkered_flag:": "\U0001f3c1", - ":cheese:": "\U0001f9c0", - ":cheese_wedge:": "\U0001f9c0", - ":chequered_flag:": "\U0001f3c1", - ":cherries:": "\U0001f352", - ":cherry_blossom:": "\U0001f338", - ":chess_pawn:": "\u265f\ufe0f", - ":chestnut:": "\U0001f330", - ":chicken:": "\U0001f414", - ":child:": "\U0001f9d2", - ":child_tone1:": "\U0001f9d2\U0001f3fb", - ":child_tone2:": "\U0001f9d2\U0001f3fc", - ":child_tone3:": "\U0001f9d2\U0001f3fd", - ":child_tone4:": "\U0001f9d2\U0001f3fe", - ":child_tone5:": "\U0001f9d2\U0001f3ff", - ":children_crossing:": "\U0001f6b8", - ":chile:": "\U0001f1e8\U0001f1f1", - ":chipmunk:": "\U0001f43f\ufe0f", - ":chocolate_bar:": "\U0001f36b", - ":chopsticks:": "\U0001f962", - ":christmas_island:": "\U0001f1e8\U0001f1fd", - ":christmas_tree:": "\U0001f384", - ":church:": "\u26ea", - ":cigarette:": "\U0001f6ac", - ":cinema:": "\U0001f3a6", - ":circled_M:": "\u24c2", - ":circus_tent:": "\U0001f3aa", - ":city_dusk:": "\U0001f306", - ":city_sunrise:": "\U0001f307", - ":city_sunset:": "\U0001f306", - ":cityscape:": "\U0001f3d9\ufe0f", - ":cityscape_at_dusk:": "\U0001f306", - ":cl:": "\U0001f191", - ":clamp:": "\U0001f5dc", - ":clap:": "\U0001f44f", - ":clap_tone1:": "\U0001f44f\U0001f3fb", - ":clap_tone2:": "\U0001f44f\U0001f3fc", - ":clap_tone3:": "\U0001f44f\U0001f3fd", - ":clap_tone4:": "\U0001f44f\U0001f3fe", - ":clap_tone5:": "\U0001f44f\U0001f3ff", - ":clapper:": "\U0001f3ac", - ":clapper_board:": "\U0001f3ac", - ":clapping_hands:": "\U0001f44f", - ":classical_building:": "\U0001f3db\ufe0f", - ":climbing:": "\U0001f9d7", - ":climbing_man:": "\U0001f9d7\u200d\u2642\ufe0f", - ":climbing_woman:": "\U0001f9d7\u200d\u2640\ufe0f", - ":clinking_beer_mugs:": "\U0001f37b", - ":clinking_glasses:": "\U0001f942", - ":clipboard:": "\U0001f4cb", - ":clipperton_island:": "\U0001f1e8\U0001f1f5", - ":clock:": "\U0001f570", - ":clock1:": "\U0001f550", - ":clock10:": "\U0001f559", - ":clock1030:": "\U0001f565", - ":clock11:": "\U0001f55a", - ":clock1130:": "\U0001f566", - ":clock12:": "\U0001f55b", - ":clock1230:": "\U0001f567", - ":clock130:": "\U0001f55c", - ":clock2:": "\U0001f551", - ":clock230:": "\U0001f55d", - ":clock3:": "\U0001f552", - ":clock330:": "\U0001f55e", - ":clock4:": "\U0001f553", - ":clock430:": "\U0001f55f", - ":clock5:": "\U0001f554", - ":clock530:": "\U0001f560", - ":clock6:": "\U0001f555", - ":clock630:": "\U0001f561", - ":clock7:": "\U0001f556", - ":clock730:": "\U0001f562", - ":clock8:": "\U0001f557", - ":clock830:": "\U0001f563", - ":clock9:": "\U0001f558", - ":clock930:": "\U0001f564", - ":clockwise_vertical_arrows:": "\U0001f503", - ":closed_book:": "\U0001f4d5", - ":closed_lock_with_key:": "\U0001f510", - ":closed_mailbox_with_lowered_flag:": "\U0001f4ea", - ":closed_mailbox_with_raised_flag:": "\U0001f4eb", - ":closed_umbrella:": "\U0001f302", - ":cloud:": "\u2601\ufe0f", - ":cloud_lightning:": "\U0001f329", - ":cloud_rain:": "\U0001f327", - ":cloud_snow:": "\U0001f328", - ":cloud_tornado:": "\U0001f32a", - ":cloud_with_lightning:": "\U0001f329", - ":cloud_with_lightning_and_rain:": "\u26c8", - ":cloud_with_rain:": "\U0001f327", - ":cloud_with_snow:": "\U0001f328", - ":clown:": "\U0001f921", - ":clown_face:": "\U0001f921", - ":club_suit:": "\u2663", - ":clubs:": "\u2663\ufe0f", - ":clutch_bag:": "\U0001f45d", - ":cn:": "\U0001f1e8\U0001f1f3", - ":coat:": "\U0001f9e5", - ":cockroach:": "\U0001fab3", - ":cocktail:": "\U0001f378", - ":cocktail_glass:": "\U0001f378", - ":coconut:": "\U0001f965", - ":cocos_islands:": "\U0001f1e8\U0001f1e8", - ":coffee:": "\u2615", - ":coffin:": "\u26b0\ufe0f", - ":coin:": "\U0001fa99", - ":cold_face:": "\U0001f976", - ":cold_sweat:": "\U0001f630", - ":collision:": "\U0001f4a5", - ":colombia:": "\U0001f1e8\U0001f1f4", - ":comet:": "\u2604\ufe0f", - ":comoros:": "\U0001f1f0\U0001f1f2", - ":compass:": "\U0001f9ed", - ":compression:": "\U0001f5dc\ufe0f", - ":computer:": "\U0001f4bb", - ":computer_disk:": "\U0001f4bd", - ":computer_mouse:": "\U0001f5b1", - ":confetti_ball:": "\U0001f38a", - ":confounded:": "\U0001f616", - ":confounded_face:": "\U0001f616", - ":confused:": "\U0001f615", - ":confused_face:": "\U0001f615", - ":congo_brazzaville:": "\U0001f1e8\U0001f1ec", - ":congo_kinshasa:": "\U0001f1e8\U0001f1e9", - ":congratulations:": "\u3297\ufe0f", - ":construction:": "\U0001f6a7", - ":construction_site:": "\U0001f3d7", - ":construction_worker:": "\U0001f477\u200d\u2642\ufe0f", - ":construction_worker_man:": "\U0001f477\u200d\u2642\ufe0f", - ":construction_worker_tone1:": "\U0001f477\U0001f3fb", - ":construction_worker_tone2:": "\U0001f477\U0001f3fc", - ":construction_worker_tone3:": "\U0001f477\U0001f3fd", - ":construction_worker_tone4:": "\U0001f477\U0001f3fe", - ":construction_worker_tone5:": "\U0001f477\U0001f3ff", - ":construction_worker_woman:": "\U0001f477\u200d\u2640\ufe0f", - ":control_knobs:": "\U0001f39b\ufe0f", - ":convenience_store:": "\U0001f3ea", - ":cook:": "\U0001f9d1\u200d\U0001f373", - ":cook_islands:": "\U0001f1e8\U0001f1f0", - ":cooked_rice:": "\U0001f35a", - ":cookie:": "\U0001f36a", - ":cooking:": "\U0001f373", - ":cool:": "\U0001f192", - ":cop:": "\U0001f46e\u200d\u2642\ufe0f", - ":copyright:": "\u00a9\ufe0f", - ":corn:": "\U0001f33d", - ":costa_rica:": "\U0001f1e8\U0001f1f7", - ":cote_divoire:": "\U0001f1e8\U0001f1ee", - ":couch:": "\U0001f6cb", - ":couch_and_lamp:": "\U0001f6cb\ufe0f", - ":counterclockwise_arrows_button:": "\U0001f504", - ":couple:": "\U0001f46b", - ":couple_mm:": "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468", - ":couple_with_heart:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468", - ":couple_with_heart_man_man:": "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468", - ":couple_with_heart_woman_man:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468", - ":couple_with_heart_woman_woman:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469", - ":couple_ww:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469", - ":couplekiss:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", - ":couplekiss_man_man:": "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", - ":couplekiss_man_woman:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", - ":couplekiss_woman_woman:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469", - ":cow:": "\U0001f42e", - ":cow2:": "\U0001f404", - ":cow_face:": "\U0001f42e", - ":cowboy:": "\U0001f920", - ":cowboy_hat_face:": "\U0001f920", - ":crab:": "\U0001f980", - ":crayon:": "\U0001f58d", - ":crazy_face:": "\U0001f92a", - ":credit_card:": "\U0001f4b3", - ":crescent_moon:": "\U0001f319", - ":cricket:": "\U0001f997", - ":cricket_bat_and_ball:": "\U0001f3cf", - ":cricket_game:": "\U0001f3cf", - ":croatia:": "\U0001f1ed\U0001f1f7", - ":crocodile:": "\U0001f40a", - ":croissant:": "\U0001f950", - ":cross:": "\u271d", - ":cross_mark:": "\u274c", - ":cross_mark_button:": "\u274e", - ":crossed_fingers:": "\U0001f91e", - ":crossed_flags:": "\U0001f38c", - ":crossed_swords:": "\u2694\ufe0f", - ":crown:": "\U0001f451", - ":cruise_ship:": "\U0001f6f3", - ":cry:": "\U0001f622", - ":crying_cat:": "\U0001f63f", - ":crying_cat_face:": "\U0001f63f", - ":crying_face:": "\U0001f622", - ":crystal_ball:": "\U0001f52e", - ":cuba:": "\U0001f1e8\U0001f1fa", - ":cucumber:": "\U0001f952", - ":cup_with_straw:": "\U0001f964", - ":cupcake:": "\U0001f9c1", - ":cupid:": "\U0001f498", - ":curacao:": "\U0001f1e8\U0001f1fc", - ":curling_stone:": "\U0001f94c", - ":curly_hair:": "\U0001f9b1", - ":curly_haired_man:": "\U0001f468\u200d\U0001f9b1", - ":curly_haired_person:": "\U0001f9d1\u200d\U0001f9b1", - ":curly_haired_woman:": "\U0001f469\u200d\U0001f9b1", - ":curly_loop:": "\u27b0", - ":currency_exchange:": "\U0001f4b1", - ":curry:": "\U0001f35b", - ":curry_rice:": "\U0001f35b", - ":cursing_face:": "\U0001f92c", - ":custard:": "\U0001f36e", - ":customs:": "\U0001f6c3", - ":cut_of_meat:": "\U0001f969", - ":cyclone:": "\U0001f300", - ":cyprus:": "\U0001f1e8\U0001f1fe", - ":czech_republic:": "\U0001f1e8\U0001f1ff", - ":dagger:": "\U0001f5e1", - ":dagger_knife:": "\U0001f5e1\ufe0f", - ":dancer:": "\U0001f483", - ":dancer_tone1:": "\U0001f483\U0001f3fb", - ":dancer_tone2:": "\U0001f483\U0001f3fc", - ":dancer_tone3:": "\U0001f483\U0001f3fd", - ":dancer_tone4:": "\U0001f483\U0001f3fe", - ":dancer_tone5:": "\U0001f483\U0001f3ff", - ":dancers:": "\U0001f46f\u200d\u2640\ufe0f", - ":dancing_men:": "\U0001f46f\u200d\u2642\ufe0f", - ":dancing_women:": "\U0001f46f\u200d\u2640\ufe0f", - ":dango:": "\U0001f361", - ":dark_sunglasses:": "\U0001f576\ufe0f", - ":dart:": "\U0001f3af", - ":dash:": "\U0001f4a8", - ":dashing_away:": "\U0001f4a8", - ":date:": "\U0001f4c5", - ":de:": "\U0001f1e9\U0001f1ea", - ":deaf_man:": "\U0001f9cf\u200d\u2642\ufe0f", - ":deaf_person:": "\U0001f9cf", - ":deaf_woman:": "\U0001f9cf\u200d\u2640\ufe0f", - ":deciduous_tree:": "\U0001f333", - ":deer:": "\U0001f98c", - ":delivery_truck:": "\U0001f69a", - ":denmark:": "\U0001f1e9\U0001f1f0", - ":department_store:": "\U0001f3ec", - ":derelict_house:": "\U0001f3da", - ":derelict_house_building:": "\U0001f3da\ufe0f", - ":desert:": "\U0001f3dc\ufe0f", - ":desert_island:": "\U0001f3dd\ufe0f", - ":desktop:": "\U0001f5a5", - ":desktop_computer:": "\U0001f5a5\ufe0f", - ":detective:": "\U0001f575", - ":detective_tone1:": "\U0001f575\U0001f3fb", - ":detective_tone2:": "\U0001f575\U0001f3fc", - ":detective_tone3:": "\U0001f575\U0001f3fd", - ":detective_tone4:": "\U0001f575\U0001f3fe", - ":detective_tone5:": "\U0001f575\U0001f3ff", - ":diamond_shape_with_a_dot_inside:": "\U0001f4a0", - ":diamond_suit:": "\u2666", - ":diamond_with_a_dot:": "\U0001f4a0", - ":diamonds:": "\u2666\ufe0f", - ":diego_garcia:": "\U0001f1e9\U0001f1ec", - ":dim_button:": "\U0001f505", - ":disappointed:": "\U0001f61e", - ":disappointed_face:": "\U0001f61e", - ":disappointed_relieved:": "\U0001f625", - ":disguised_face:": "\U0001f978", - ":divide:": "\u2797", - ":dividers:": "\U0001f5c2", - ":diving_mask:": "\U0001f93f", - ":diya_lamp:": "\U0001fa94", - ":dizzy:": "\U0001f4ab", - ":dizzy_face:": "\U0001f635", - ":djibouti:": "\U0001f1e9\U0001f1ef", - ":dna:": "\U0001f9ec", - ":do_not_litter:": "\U0001f6af", - ":dodo:": "\U0001f9a4", - ":dog:": "\U0001f436", - ":dog2:": "\U0001f415", - ":dog_face:": "\U0001f436", - ":dollar:": "\U0001f4b5", - ":dollar_banknote:": "\U0001f4b5", - ":dolls:": "\U0001f38e", - ":dolphin:": "\U0001f42c", - ":dominica:": "\U0001f1e9\U0001f1f2", - ":dominican_republic:": "\U0001f1e9\U0001f1f4", - ":door:": "\U0001f6aa", - ":dotted_six-pointed_star:": "\U0001f52f", - ":double_curly_loop:": "\u27bf", - ":double_exclamation_mark:": "\u203c", - ":double_vertical_bar:": "\u23f8\ufe0f", - ":doughnut:": "\U0001f369", - ":dove:": "\U0001f54a", - ":dove_of_peace:": "\U0001f54a\ufe0f", - ":down-left_arrow:": "\u2199", - ":down-right_arrow:": "\u2198", - ":down_arrow:": "\u2b07", - ":downcast_face_with_sweat:": "\U0001f613", - ":downwards_button:": "\U0001f53d", - ":dragon:": "\U0001f409", - ":dragon_face:": "\U0001f432", - ":dress:": "\U0001f457", - ":dromedary_camel:": "\U0001f42a", - ":drooling_face:": "\U0001f924", - ":drop_of_blood:": "\U0001fa78", - ":droplet:": "\U0001f4a7", - ":drum:": "\U0001f941", - ":drum_with_drumsticks:": "\U0001f941", - ":duck:": "\U0001f986", - ":dumpling:": "\U0001f95f", - ":dvd:": "\U0001f4c0", - ":e-mail:": "\U0001f4e7", - ":eagle:": "\U0001f985", - ":ear:": "\U0001f442", - ":ear_of_corn:": "\U0001f33d", - ":ear_of_rice:": "\U0001f33e", - ":ear_tone1:": "\U0001f442\U0001f3fb", - ":ear_tone2:": "\U0001f442\U0001f3fc", - ":ear_tone3:": "\U0001f442\U0001f3fd", - ":ear_tone4:": "\U0001f442\U0001f3fe", - ":ear_tone5:": "\U0001f442\U0001f3ff", - ":ear_with_hearing_aid:": "\U0001f9bb", - ":earth_africa:": "\U0001f30d", - ":earth_americas:": "\U0001f30e", - ":earth_asia:": "\U0001f30f", - ":ecuador:": "\U0001f1ea\U0001f1e8", - ":egg:": "\U0001f95a", - ":eggplant:": "\U0001f346", - ":egypt:": "\U0001f1ea\U0001f1ec", - ":eight:": "8\ufe0f\u20e3", - ":eight-pointed_star:": "\u2734", - ":eight-spoked_asterisk:": "\u2733", - ":eight-thirty:": "\U0001f563", - ":eight_o’clock:": "\U0001f557", - ":eight_pointed_black_star:": "\u2734\ufe0f", - ":eight_spoked_asterisk:": "\u2733\ufe0f", - ":eject:": "\u23cf\ufe0f", - ":eject_button:": "\u23cf", - ":el_salvador:": "\U0001f1f8\U0001f1fb", - ":electric_plug:": "\U0001f50c", - ":elephant:": "\U0001f418", - ":elevator:": "\U0001f6d7", - ":eleven-thirty:": "\U0001f566", - ":eleven_o’clock:": "\U0001f55a", - ":elf:": "\U0001f9dd\u200d\u2642\ufe0f", - ":elf_man:": "\U0001f9dd\u200d\u2642\ufe0f", - ":elf_tone1:": "\U0001f9dd\U0001f3fb", - ":elf_tone2:": "\U0001f9dd\U0001f3fc", - ":elf_tone3:": "\U0001f9dd\U0001f3fd", - ":elf_tone4:": "\U0001f9dd\U0001f3fe", - ":elf_tone5:": "\U0001f9dd\U0001f3ff", - ":elf_woman:": "\U0001f9dd\u200d\u2640\ufe0f", - ":email:": "\u2709\ufe0f", - ":end:": "\U0001f51a", - ":england:": "\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f", - ":envelope:": "\u2709", - ":envelope_with_arrow:": "\U0001f4e9", - ":equatorial_guinea:": "\U0001f1ec\U0001f1f6", - ":eritrea:": "\U0001f1ea\U0001f1f7", - ":es:": "\U0001f1ea\U0001f1f8", - ":estonia:": "\U0001f1ea\U0001f1ea", - ":ethiopia:": "\U0001f1ea\U0001f1f9", - ":eu:": "\U0001f1ea\U0001f1fa", - ":euro:": "\U0001f4b6", - ":euro_banknote:": "\U0001f4b6", - ":european_castle:": "\U0001f3f0", - ":european_post_office:": "\U0001f3e4", - ":european_union:": "\U0001f1ea\U0001f1fa", - ":evergreen_tree:": "\U0001f332", - ":ewe:": "\U0001f411", - ":exclamation:": "\u2757", - ":exclamation_question_mark:": "\u2049", - ":exploding_head:": "\U0001f92f", - ":expressionless:": "\U0001f611", - ":expressionless_face:": "\U0001f611", - ":eye:": "\U0001f441\ufe0f", - ":eye-in-speech-bubble:": "\U0001f441\ufe0f\u200d\U0001f5e8\ufe0f", - ":eye_in_speech_bubble:": "\U0001f441\ufe0f\u200d\U0001f5e8\ufe0f", - ":eye_speech_bubble:": "\U0001f441\ufe0f\u200d\U0001f5e8\ufe0f", - ":eyeglasses:": "\U0001f453", - ":eyes:": "\U0001f440", - ":face_blowing_a_kiss:": "\U0001f618", - ":face_exhaling:": "\U0001f62e\u200d\U0001f4a8", - ":face_in_clouds:": "\U0001f636\u200d\U0001f32b\ufe0f", - ":face_palm:": "\U0001f926", - ":face_savoring_food:": "\U0001f60b", - ":face_screaming_in_fear:": "\U0001f631", - ":face_vomiting:": "\U0001f92e", - ":face_with_cowboy_hat:": "\U0001f920", - ":face_with_hand_over_mouth:": "\U0001f92d", - ":face_with_head-bandage:": "\U0001f915", - ":face_with_head_bandage:": "\U0001f915", - ":face_with_medical_mask:": "\U0001f637", - ":face_with_monocle:": "\U0001f9d0", - ":face_with_open_mouth:": "\U0001f62e", - ":face_with_raised_eyebrow:": "\U0001f928", - ":face_with_rolling_eyes:": "\U0001f644", - ":face_with_spiral_eyes:": "\U0001f635\u200d\U0001f4ab", - ":face_with_steam_from_nose:": "\U0001f624", - ":face_with_symbols_on_mouth:": "\U0001f92c", - ":face_with_symbols_over_mouth:": "\U0001f92c", - ":face_with_tears_of_joy:": "\U0001f602", - ":face_with_thermometer:": "\U0001f912", - ":face_with_tongue:": "\U0001f61b", - ":face_without_mouth:": "\U0001f636", - ":facepalm:": "\U0001f926", - ":facepunch:": "\U0001f44a", - ":factory:": "\U0001f3ed", - ":factory_worker:": "\U0001f9d1\u200d\U0001f3ed", - ":fairy:": "\U0001f9da\u200d\u2640\ufe0f", - ":fairy_man:": "\U0001f9da\u200d\u2642\ufe0f", - ":fairy_tone1:": "\U0001f9da\U0001f3fb", - ":fairy_tone2:": "\U0001f9da\U0001f3fc", - ":fairy_tone3:": "\U0001f9da\U0001f3fd", - ":fairy_tone4:": "\U0001f9da\U0001f3fe", - ":fairy_tone5:": "\U0001f9da\U0001f3ff", - ":fairy_woman:": "\U0001f9da\u200d\u2640\ufe0f", - ":falafel:": "\U0001f9c6", - ":falkland_islands:": "\U0001f1eb\U0001f1f0", - ":fallen_leaf:": "\U0001f342", - ":family:": "\U0001f468\u200d\U0001f469\u200d\U0001f466", - ":family_man_boy:": "\U0001f468\u200d\U0001f466", - ":family_man_boy_boy:": "\U0001f468\u200d\U0001f466\u200d\U0001f466", - ":family_man_girl:": "\U0001f468\u200d\U0001f467", - ":family_man_girl_boy:": "\U0001f468\u200d\U0001f467\u200d\U0001f466", - ":family_man_girl_girl:": "\U0001f468\u200d\U0001f467\u200d\U0001f467", - ":family_man_man_boy:": "\U0001f468\u200d\U0001f468\u200d\U0001f466", - ":family_man_man_boy_boy:": "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466", - ":family_man_man_girl:": "\U0001f468\u200d\U0001f468\u200d\U0001f467", - ":family_man_man_girl_boy:": "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466", - ":family_man_man_girl_girl:": "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467", - ":family_man_woman_boy:": "\U0001f468\u200d\U0001f469\u200d\U0001f466", - ":family_man_woman_boy_boy:": "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466", - ":family_man_woman_girl:": "\U0001f468\u200d\U0001f469\u200d\U0001f467", - ":family_man_woman_girl_boy:": "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466", - ":family_man_woman_girl_girl:": "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467", - ":family_mmb:": "\U0001f468\u200d\U0001f468\u200d\U0001f466", - ":family_mmbb:": "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466", - ":family_mmg:": "\U0001f468\u200d\U0001f468\u200d\U0001f467", - ":family_mmgb:": "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466", - ":family_mmgg:": "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467", - ":family_mwbb:": "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466", - ":family_mwg:": "\U0001f468\u200d\U0001f469\u200d\U0001f467", - ":family_mwgb:": "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466", - ":family_mwgg:": "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467", - ":family_woman_boy:": "\U0001f469\u200d\U0001f466", - ":family_woman_boy_boy:": "\U0001f469\u200d\U0001f466\u200d\U0001f466", - ":family_woman_girl:": "\U0001f469\u200d\U0001f467", - ":family_woman_girl_boy:": "\U0001f469\u200d\U0001f467\u200d\U0001f466", - ":family_woman_girl_girl:": "\U0001f469\u200d\U0001f467\u200d\U0001f467", - ":family_woman_woman_boy:": "\U0001f469\u200d\U0001f469\u200d\U0001f466", - ":family_woman_woman_boy_boy:": "\U0001f469\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466", - ":family_woman_woman_girl:": "\U0001f469\u200d\U0001f469\u200d\U0001f467", - ":family_woman_woman_girl_boy:": "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466", - ":family_woman_woman_girl_girl:": "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467", - ":family_wwb:": "\U0001f469\u200d\U0001f469\u200d\U0001f466", - ":family_wwbb:": "\U0001f469\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466", - ":family_wwg:": "\U0001f469\u200d\U0001f469\u200d\U0001f467", - ":family_wwgb:": "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466", - ":family_wwgg:": "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467", - ":farmer:": "\U0001f9d1\u200d\U0001f33e", - ":faroe_islands:": "\U0001f1eb\U0001f1f4", - ":fast-forward_button:": "\u23e9", - ":fast_down_button:": "\u23ec", - ":fast_forward:": "\u23e9", - ":fast_reverse_button:": "\u23ea", - ":fast_up_button:": "\u23eb", - ":fax:": "\U0001f4e0", - ":fax_machine:": "\U0001f4e0", - ":fearful:": "\U0001f628", - ":fearful_face:": "\U0001f628", - ":feather:": "\U0001fab6", - ":feet:": "\U0001f43e", - ":female-artist:": "\U0001f469\u200d\U0001f3a8", - ":female-astronaut:": "\U0001f469\u200d\U0001f680", - ":female-construction-worker:": "\U0001f477\u200d\u2640\ufe0f", - ":female-cook:": "\U0001f469\u200d\U0001f373", - ":female-detective:": "\U0001f575\ufe0f\u200d\u2640\ufe0f", - ":female-doctor:": "\U0001f469\u200d\u2695\ufe0f", - ":female-factory-worker:": "\U0001f469\u200d\U0001f3ed", - ":female-farmer:": "\U0001f469\u200d\U0001f33e", - ":female-firefighter:": "\U0001f469\u200d\U0001f692", - ":female-guard:": "\U0001f482\u200d\u2640\ufe0f", - ":female-judge:": "\U0001f469\u200d\u2696\ufe0f", - ":female-mechanic:": "\U0001f469\u200d\U0001f527", - ":female-office-worker:": "\U0001f469\u200d\U0001f4bc", - ":female-pilot:": "\U0001f469\u200d\u2708\ufe0f", - ":female-police-officer:": "\U0001f46e\u200d\u2640\ufe0f", - ":female-scientist:": "\U0001f469\u200d\U0001f52c", - ":female-singer:": "\U0001f469\u200d\U0001f3a4", - ":female-student:": "\U0001f469\u200d\U0001f393", - ":female-teacher:": "\U0001f469\u200d\U0001f3eb", - ":female-technologist:": "\U0001f469\u200d\U0001f4bb", - ":female_detective:": "\U0001f575\ufe0f\u200d\u2640\ufe0f", - ":female_elf:": "\U0001f9dd\u200d\u2640\ufe0f", - ":female_fairy:": "\U0001f9da\u200d\u2640\ufe0f", - ":female_genie:": "\U0001f9de\u200d\u2640\ufe0f", - ":female_mage:": "\U0001f9d9\u200d\u2640\ufe0f", - ":female_sign:": "\u2640\ufe0f", - ":female_superhero:": "\U0001f9b8\u200d\u2640\ufe0f", - ":female_supervillain:": "\U0001f9b9\u200d\u2640\ufe0f", - ":female_vampire:": "\U0001f9db\u200d\u2640\ufe0f", - ":female_zombie:": "\U0001f9df\u200d\u2640\ufe0f", - ":fencer:": "\U0001f93a", - ":ferris_wheel:": "\U0001f3a1", - ":ferry:": "\u26f4\ufe0f", - ":field_hockey:": "\U0001f3d1", - ":field_hockey_stick_and_ball:": "\U0001f3d1", - ":fiji:": "\U0001f1eb\U0001f1ef", - ":file_cabinet:": "\U0001f5c4\ufe0f", - ":file_folder:": "\U0001f4c1", - ":film_frames:": "\U0001f39e\ufe0f", - ":film_projector:": "\U0001f4fd\ufe0f", - ":film_strip:": "\U0001f39e\ufe0f", - ":fingers_crossed:": "\U0001f91e", - ":fingers_crossed_tone1:": "\U0001f91e\U0001f3fb", - ":fingers_crossed_tone2:": "\U0001f91e\U0001f3fc", - ":fingers_crossed_tone3:": "\U0001f91e\U0001f3fd", - ":fingers_crossed_tone4:": "\U0001f91e\U0001f3fe", - ":fingers_crossed_tone5:": "\U0001f91e\U0001f3ff", - ":finland:": "\U0001f1eb\U0001f1ee", - ":fire:": "\U0001f525", - ":fire_engine:": "\U0001f692", - ":fire_extinguisher:": "\U0001f9ef", - ":firecracker:": "\U0001f9e8", - ":firefighter:": "\U0001f9d1\u200d\U0001f692", - ":fireworks:": "\U0001f386", - ":first_place:": "\U0001f947", - ":first_place_medal:": "\U0001f947", - ":first_quarter_moon:": "\U0001f313", - ":first_quarter_moon_face:": "\U0001f31b", - ":first_quarter_moon_with_face:": "\U0001f31b", - ":fish:": "\U0001f41f", - ":fish_cake:": "\U0001f365", - ":fish_cake_with_swirl:": "\U0001f365", - ":fishing_pole:": "\U0001f3a3", - ":fishing_pole_and_fish:": "\U0001f3a3", - ":fist:": "\u270a", - ":fist_left:": "\U0001f91b", - ":fist_oncoming:": "\U0001f44a", - ":fist_raised:": "\u270a", - ":fist_right:": "\U0001f91c", - ":fist_tone1:": "\u270a\U0001f3fb", - ":fist_tone2:": "\u270a\U0001f3fc", - ":fist_tone3:": "\u270a\U0001f3fd", - ":fist_tone4:": "\u270a\U0001f3fe", - ":fist_tone5:": "\u270a\U0001f3ff", - ":five:": "5\ufe0f\u20e3", - ":five-thirty:": "\U0001f560", - ":five_o’clock:": "\U0001f554", - ":flag-ac:": "\U0001f1e6\U0001f1e8", - ":flag-ad:": "\U0001f1e6\U0001f1e9", - ":flag-ae:": "\U0001f1e6\U0001f1ea", - ":flag-af:": "\U0001f1e6\U0001f1eb", - ":flag-ag:": "\U0001f1e6\U0001f1ec", - ":flag-ai:": "\U0001f1e6\U0001f1ee", - ":flag-al:": "\U0001f1e6\U0001f1f1", - ":flag-am:": "\U0001f1e6\U0001f1f2", - ":flag-ao:": "\U0001f1e6\U0001f1f4", - ":flag-aq:": "\U0001f1e6\U0001f1f6", - ":flag-ar:": "\U0001f1e6\U0001f1f7", - ":flag-as:": "\U0001f1e6\U0001f1f8", - ":flag-at:": "\U0001f1e6\U0001f1f9", - ":flag-au:": "\U0001f1e6\U0001f1fa", - ":flag-aw:": "\U0001f1e6\U0001f1fc", - ":flag-ax:": "\U0001f1e6\U0001f1fd", - ":flag-az:": "\U0001f1e6\U0001f1ff", - ":flag-ba:": "\U0001f1e7\U0001f1e6", - ":flag-bb:": "\U0001f1e7\U0001f1e7", - ":flag-bd:": "\U0001f1e7\U0001f1e9", - ":flag-be:": "\U0001f1e7\U0001f1ea", - ":flag-bf:": "\U0001f1e7\U0001f1eb", - ":flag-bg:": "\U0001f1e7\U0001f1ec", - ":flag-bh:": "\U0001f1e7\U0001f1ed", - ":flag-bi:": "\U0001f1e7\U0001f1ee", - ":flag-bj:": "\U0001f1e7\U0001f1ef", - ":flag-bl:": "\U0001f1e7\U0001f1f1", - ":flag-bm:": "\U0001f1e7\U0001f1f2", - ":flag-bn:": "\U0001f1e7\U0001f1f3", - ":flag-bo:": "\U0001f1e7\U0001f1f4", - ":flag-bq:": "\U0001f1e7\U0001f1f6", - ":flag-br:": "\U0001f1e7\U0001f1f7", - ":flag-bs:": "\U0001f1e7\U0001f1f8", - ":flag-bt:": "\U0001f1e7\U0001f1f9", - ":flag-bv:": "\U0001f1e7\U0001f1fb", - ":flag-bw:": "\U0001f1e7\U0001f1fc", - ":flag-by:": "\U0001f1e7\U0001f1fe", - ":flag-bz:": "\U0001f1e7\U0001f1ff", - ":flag-ca:": "\U0001f1e8\U0001f1e6", - ":flag-cc:": "\U0001f1e8\U0001f1e8", - ":flag-cd:": "\U0001f1e8\U0001f1e9", - ":flag-cf:": "\U0001f1e8\U0001f1eb", - ":flag-cg:": "\U0001f1e8\U0001f1ec", - ":flag-ch:": "\U0001f1e8\U0001f1ed", - ":flag-ci:": "\U0001f1e8\U0001f1ee", - ":flag-ck:": "\U0001f1e8\U0001f1f0", - ":flag-cl:": "\U0001f1e8\U0001f1f1", - ":flag-cm:": "\U0001f1e8\U0001f1f2", - ":flag-co:": "\U0001f1e8\U0001f1f4", - ":flag-cp:": "\U0001f1e8\U0001f1f5", - ":flag-cr:": "\U0001f1e8\U0001f1f7", - ":flag-cu:": "\U0001f1e8\U0001f1fa", - ":flag-cv:": "\U0001f1e8\U0001f1fb", - ":flag-cw:": "\U0001f1e8\U0001f1fc", - ":flag-cx:": "\U0001f1e8\U0001f1fd", - ":flag-cy:": "\U0001f1e8\U0001f1fe", - ":flag-cz:": "\U0001f1e8\U0001f1ff", - ":flag-dg:": "\U0001f1e9\U0001f1ec", - ":flag-dj:": "\U0001f1e9\U0001f1ef", - ":flag-dk:": "\U0001f1e9\U0001f1f0", - ":flag-dm:": "\U0001f1e9\U0001f1f2", - ":flag-do:": "\U0001f1e9\U0001f1f4", - ":flag-dz:": "\U0001f1e9\U0001f1ff", - ":flag-ea:": "\U0001f1ea\U0001f1e6", - ":flag-ec:": "\U0001f1ea\U0001f1e8", - ":flag-ee:": "\U0001f1ea\U0001f1ea", - ":flag-eg:": "\U0001f1ea\U0001f1ec", - ":flag-eh:": "\U0001f1ea\U0001f1ed", - ":flag-england:": "\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f", - ":flag-er:": "\U0001f1ea\U0001f1f7", - ":flag-et:": "\U0001f1ea\U0001f1f9", - ":flag-eu:": "\U0001f1ea\U0001f1fa", - ":flag-fi:": "\U0001f1eb\U0001f1ee", - ":flag-fj:": "\U0001f1eb\U0001f1ef", - ":flag-fk:": "\U0001f1eb\U0001f1f0", - ":flag-fm:": "\U0001f1eb\U0001f1f2", - ":flag-fo:": "\U0001f1eb\U0001f1f4", - ":flag-ga:": "\U0001f1ec\U0001f1e6", - ":flag-gd:": "\U0001f1ec\U0001f1e9", - ":flag-ge:": "\U0001f1ec\U0001f1ea", - ":flag-gf:": "\U0001f1ec\U0001f1eb", - ":flag-gg:": "\U0001f1ec\U0001f1ec", - ":flag-gh:": "\U0001f1ec\U0001f1ed", - ":flag-gi:": "\U0001f1ec\U0001f1ee", - ":flag-gl:": "\U0001f1ec\U0001f1f1", - ":flag-gm:": "\U0001f1ec\U0001f1f2", - ":flag-gn:": "\U0001f1ec\U0001f1f3", - ":flag-gp:": "\U0001f1ec\U0001f1f5", - ":flag-gq:": "\U0001f1ec\U0001f1f6", - ":flag-gr:": "\U0001f1ec\U0001f1f7", - ":flag-gs:": "\U0001f1ec\U0001f1f8", - ":flag-gt:": "\U0001f1ec\U0001f1f9", - ":flag-gu:": "\U0001f1ec\U0001f1fa", - ":flag-gw:": "\U0001f1ec\U0001f1fc", - ":flag-gy:": "\U0001f1ec\U0001f1fe", - ":flag-hk:": "\U0001f1ed\U0001f1f0", - ":flag-hm:": "\U0001f1ed\U0001f1f2", - ":flag-hn:": "\U0001f1ed\U0001f1f3", - ":flag-hr:": "\U0001f1ed\U0001f1f7", - ":flag-ht:": "\U0001f1ed\U0001f1f9", - ":flag-hu:": "\U0001f1ed\U0001f1fa", - ":flag-ic:": "\U0001f1ee\U0001f1e8", - ":flag-id:": "\U0001f1ee\U0001f1e9", - ":flag-ie:": "\U0001f1ee\U0001f1ea", - ":flag-il:": "\U0001f1ee\U0001f1f1", - ":flag-im:": "\U0001f1ee\U0001f1f2", - ":flag-in:": "\U0001f1ee\U0001f1f3", - ":flag-io:": "\U0001f1ee\U0001f1f4", - ":flag-iq:": "\U0001f1ee\U0001f1f6", - ":flag-ir:": "\U0001f1ee\U0001f1f7", - ":flag-is:": "\U0001f1ee\U0001f1f8", - ":flag-je:": "\U0001f1ef\U0001f1ea", - ":flag-jm:": "\U0001f1ef\U0001f1f2", - ":flag-jo:": "\U0001f1ef\U0001f1f4", - ":flag-ke:": "\U0001f1f0\U0001f1ea", - ":flag-kg:": "\U0001f1f0\U0001f1ec", - ":flag-kh:": "\U0001f1f0\U0001f1ed", - ":flag-ki:": "\U0001f1f0\U0001f1ee", - ":flag-km:": "\U0001f1f0\U0001f1f2", - ":flag-kn:": "\U0001f1f0\U0001f1f3", - ":flag-kp:": "\U0001f1f0\U0001f1f5", - ":flag-kw:": "\U0001f1f0\U0001f1fc", - ":flag-ky:": "\U0001f1f0\U0001f1fe", - ":flag-kz:": "\U0001f1f0\U0001f1ff", - ":flag-la:": "\U0001f1f1\U0001f1e6", - ":flag-lb:": "\U0001f1f1\U0001f1e7", - ":flag-lc:": "\U0001f1f1\U0001f1e8", - ":flag-li:": "\U0001f1f1\U0001f1ee", - ":flag-lk:": "\U0001f1f1\U0001f1f0", - ":flag-lr:": "\U0001f1f1\U0001f1f7", - ":flag-ls:": "\U0001f1f1\U0001f1f8", - ":flag-lt:": "\U0001f1f1\U0001f1f9", - ":flag-lu:": "\U0001f1f1\U0001f1fa", - ":flag-lv:": "\U0001f1f1\U0001f1fb", - ":flag-ly:": "\U0001f1f1\U0001f1fe", - ":flag-ma:": "\U0001f1f2\U0001f1e6", - ":flag-mc:": "\U0001f1f2\U0001f1e8", - ":flag-md:": "\U0001f1f2\U0001f1e9", - ":flag-me:": "\U0001f1f2\U0001f1ea", - ":flag-mf:": "\U0001f1f2\U0001f1eb", - ":flag-mg:": "\U0001f1f2\U0001f1ec", - ":flag-mh:": "\U0001f1f2\U0001f1ed", - ":flag-mk:": "\U0001f1f2\U0001f1f0", - ":flag-ml:": "\U0001f1f2\U0001f1f1", - ":flag-mm:": "\U0001f1f2\U0001f1f2", - ":flag-mn:": "\U0001f1f2\U0001f1f3", - ":flag-mo:": "\U0001f1f2\U0001f1f4", - ":flag-mp:": "\U0001f1f2\U0001f1f5", - ":flag-mq:": "\U0001f1f2\U0001f1f6", - ":flag-mr:": "\U0001f1f2\U0001f1f7", - ":flag-ms:": "\U0001f1f2\U0001f1f8", - ":flag-mt:": "\U0001f1f2\U0001f1f9", - ":flag-mu:": "\U0001f1f2\U0001f1fa", - ":flag-mv:": "\U0001f1f2\U0001f1fb", - ":flag-mw:": "\U0001f1f2\U0001f1fc", - ":flag-mx:": "\U0001f1f2\U0001f1fd", - ":flag-my:": "\U0001f1f2\U0001f1fe", - ":flag-mz:": "\U0001f1f2\U0001f1ff", - ":flag-na:": "\U0001f1f3\U0001f1e6", - ":flag-nc:": "\U0001f1f3\U0001f1e8", - ":flag-ne:": "\U0001f1f3\U0001f1ea", - ":flag-nf:": "\U0001f1f3\U0001f1eb", - ":flag-ng:": "\U0001f1f3\U0001f1ec", - ":flag-ni:": "\U0001f1f3\U0001f1ee", - ":flag-nl:": "\U0001f1f3\U0001f1f1", - ":flag-no:": "\U0001f1f3\U0001f1f4", - ":flag-np:": "\U0001f1f3\U0001f1f5", - ":flag-nr:": "\U0001f1f3\U0001f1f7", - ":flag-nu:": "\U0001f1f3\U0001f1fa", - ":flag-nz:": "\U0001f1f3\U0001f1ff", - ":flag-om:": "\U0001f1f4\U0001f1f2", - ":flag-pa:": "\U0001f1f5\U0001f1e6", - ":flag-pe:": "\U0001f1f5\U0001f1ea", - ":flag-pf:": "\U0001f1f5\U0001f1eb", - ":flag-pg:": "\U0001f1f5\U0001f1ec", - ":flag-ph:": "\U0001f1f5\U0001f1ed", - ":flag-pk:": "\U0001f1f5\U0001f1f0", - ":flag-pl:": "\U0001f1f5\U0001f1f1", - ":flag-pm:": "\U0001f1f5\U0001f1f2", - ":flag-pn:": "\U0001f1f5\U0001f1f3", - ":flag-pr:": "\U0001f1f5\U0001f1f7", - ":flag-ps:": "\U0001f1f5\U0001f1f8", - ":flag-pt:": "\U0001f1f5\U0001f1f9", - ":flag-pw:": "\U0001f1f5\U0001f1fc", - ":flag-py:": "\U0001f1f5\U0001f1fe", - ":flag-qa:": "\U0001f1f6\U0001f1e6", - ":flag-re:": "\U0001f1f7\U0001f1ea", - ":flag-ro:": "\U0001f1f7\U0001f1f4", - ":flag-rs:": "\U0001f1f7\U0001f1f8", - ":flag-rw:": "\U0001f1f7\U0001f1fc", - ":flag-sa:": "\U0001f1f8\U0001f1e6", - ":flag-sb:": "\U0001f1f8\U0001f1e7", - ":flag-sc:": "\U0001f1f8\U0001f1e8", - ":flag-scotland:": "\U0001f3f4\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f", - ":flag-sd:": "\U0001f1f8\U0001f1e9", - ":flag-se:": "\U0001f1f8\U0001f1ea", - ":flag-sg:": "\U0001f1f8\U0001f1ec", - ":flag-sh:": "\U0001f1f8\U0001f1ed", - ":flag-si:": "\U0001f1f8\U0001f1ee", - ":flag-sj:": "\U0001f1f8\U0001f1ef", - ":flag-sk:": "\U0001f1f8\U0001f1f0", - ":flag-sl:": "\U0001f1f8\U0001f1f1", - ":flag-sm:": "\U0001f1f8\U0001f1f2", - ":flag-sn:": "\U0001f1f8\U0001f1f3", - ":flag-so:": "\U0001f1f8\U0001f1f4", - ":flag-sr:": "\U0001f1f8\U0001f1f7", - ":flag-ss:": "\U0001f1f8\U0001f1f8", - ":flag-st:": "\U0001f1f8\U0001f1f9", - ":flag-sv:": "\U0001f1f8\U0001f1fb", - ":flag-sx:": "\U0001f1f8\U0001f1fd", - ":flag-sy:": "\U0001f1f8\U0001f1fe", - ":flag-sz:": "\U0001f1f8\U0001f1ff", - ":flag-ta:": "\U0001f1f9\U0001f1e6", - ":flag-tc:": "\U0001f1f9\U0001f1e8", - ":flag-td:": "\U0001f1f9\U0001f1e9", - ":flag-tf:": "\U0001f1f9\U0001f1eb", - ":flag-tg:": "\U0001f1f9\U0001f1ec", - ":flag-th:": "\U0001f1f9\U0001f1ed", - ":flag-tj:": "\U0001f1f9\U0001f1ef", - ":flag-tk:": "\U0001f1f9\U0001f1f0", - ":flag-tl:": "\U0001f1f9\U0001f1f1", - ":flag-tm:": "\U0001f1f9\U0001f1f2", - ":flag-tn:": "\U0001f1f9\U0001f1f3", - ":flag-to:": "\U0001f1f9\U0001f1f4", - ":flag-tr:": "\U0001f1f9\U0001f1f7", - ":flag-tt:": "\U0001f1f9\U0001f1f9", - ":flag-tv:": "\U0001f1f9\U0001f1fb", - ":flag-tw:": "\U0001f1f9\U0001f1fc", - ":flag-tz:": "\U0001f1f9\U0001f1ff", - ":flag-ua:": "\U0001f1fa\U0001f1e6", - ":flag-ug:": "\U0001f1fa\U0001f1ec", - ":flag-um:": "\U0001f1fa\U0001f1f2", - ":flag-un:": "\U0001f1fa\U0001f1f3", - ":flag-uy:": "\U0001f1fa\U0001f1fe", - ":flag-uz:": "\U0001f1fa\U0001f1ff", - ":flag-va:": "\U0001f1fb\U0001f1e6", - ":flag-vc:": "\U0001f1fb\U0001f1e8", - ":flag-ve:": "\U0001f1fb\U0001f1ea", - ":flag-vg:": "\U0001f1fb\U0001f1ec", - ":flag-vi:": "\U0001f1fb\U0001f1ee", - ":flag-vn:": "\U0001f1fb\U0001f1f3", - ":flag-vu:": "\U0001f1fb\U0001f1fa", - ":flag-wales:": "\U0001f3f4\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f", - ":flag-wf:": "\U0001f1fc\U0001f1eb", - ":flag-ws:": "\U0001f1fc\U0001f1f8", - ":flag-xk:": "\U0001f1fd\U0001f1f0", - ":flag-ye:": "\U0001f1fe\U0001f1ea", - ":flag-yt:": "\U0001f1fe\U0001f1f9", - ":flag-za:": "\U0001f1ff\U0001f1e6", - ":flag-zm:": "\U0001f1ff\U0001f1f2", - ":flag-zw:": "\U0001f1ff\U0001f1fc", - ":flag_Afghanistan:": "\U0001f1e6\U0001f1eb", - ":flag_Albania:": "\U0001f1e6\U0001f1f1", - ":flag_Algeria:": "\U0001f1e9\U0001f1ff", - ":flag_American_Samoa:": "\U0001f1e6\U0001f1f8", - ":flag_Andorra:": "\U0001f1e6\U0001f1e9", - ":flag_Angola:": "\U0001f1e6\U0001f1f4", - ":flag_Anguilla:": "\U0001f1e6\U0001f1ee", - ":flag_Antarctica:": "\U0001f1e6\U0001f1f6", - ":flag_Antigua_&_Barbuda:": "\U0001f1e6\U0001f1ec", - ":flag_Argentina:": "\U0001f1e6\U0001f1f7", - ":flag_Armenia:": "\U0001f1e6\U0001f1f2", - ":flag_Aruba:": "\U0001f1e6\U0001f1fc", - ":flag_Ascension_Island:": "\U0001f1e6\U0001f1e8", - ":flag_Australia:": "\U0001f1e6\U0001f1fa", - ":flag_Austria:": "\U0001f1e6\U0001f1f9", - ":flag_Azerbaijan:": "\U0001f1e6\U0001f1ff", - ":flag_Bahamas:": "\U0001f1e7\U0001f1f8", - ":flag_Bahrain:": "\U0001f1e7\U0001f1ed", - ":flag_Bangladesh:": "\U0001f1e7\U0001f1e9", - ":flag_Barbados:": "\U0001f1e7\U0001f1e7", - ":flag_Belarus:": "\U0001f1e7\U0001f1fe", - ":flag_Belgium:": "\U0001f1e7\U0001f1ea", - ":flag_Belize:": "\U0001f1e7\U0001f1ff", - ":flag_Benin:": "\U0001f1e7\U0001f1ef", - ":flag_Bermuda:": "\U0001f1e7\U0001f1f2", - ":flag_Bhutan:": "\U0001f1e7\U0001f1f9", - ":flag_Bolivia:": "\U0001f1e7\U0001f1f4", - ":flag_Bosnia_&_Herzegovina:": "\U0001f1e7\U0001f1e6", - ":flag_Botswana:": "\U0001f1e7\U0001f1fc", - ":flag_Bouvet_Island:": "\U0001f1e7\U0001f1fb", - ":flag_Brazil:": "\U0001f1e7\U0001f1f7", - ":flag_British_Indian_Ocean_Territory:": "\U0001f1ee\U0001f1f4", - ":flag_British_Virgin_Islands:": "\U0001f1fb\U0001f1ec", - ":flag_Brunei:": "\U0001f1e7\U0001f1f3", - ":flag_Bulgaria:": "\U0001f1e7\U0001f1ec", - ":flag_Burkina_Faso:": "\U0001f1e7\U0001f1eb", - ":flag_Burundi:": "\U0001f1e7\U0001f1ee", - ":flag_Cambodia:": "\U0001f1f0\U0001f1ed", - ":flag_Cameroon:": "\U0001f1e8\U0001f1f2", - ":flag_Canada:": "\U0001f1e8\U0001f1e6", - ":flag_Canary_Islands:": "\U0001f1ee\U0001f1e8", - ":flag_Cape_Verde:": "\U0001f1e8\U0001f1fb", - ":flag_Caribbean_Netherlands:": "\U0001f1e7\U0001f1f6", - ":flag_Cayman_Islands:": "\U0001f1f0\U0001f1fe", - ":flag_Central_African_Republic:": "\U0001f1e8\U0001f1eb", - ":flag_Ceuta_&_Melilla:": "\U0001f1ea\U0001f1e6", - ":flag_Chad:": "\U0001f1f9\U0001f1e9", - ":flag_Chile:": "\U0001f1e8\U0001f1f1", - ":flag_China:": "\U0001f1e8\U0001f1f3", - ":flag_Christmas_Island:": "\U0001f1e8\U0001f1fd", - ":flag_Clipperton_Island:": "\U0001f1e8\U0001f1f5", - ":flag_Cocos_(Keeling)_Islands:": "\U0001f1e8\U0001f1e8", - ":flag_Colombia:": "\U0001f1e8\U0001f1f4", - ":flag_Comoros:": "\U0001f1f0\U0001f1f2", - ":flag_Congo_-_Brazzaville:": "\U0001f1e8\U0001f1ec", - ":flag_Congo_-_Kinshasa:": "\U0001f1e8\U0001f1e9", - ":flag_Cook_Islands:": "\U0001f1e8\U0001f1f0", - ":flag_Costa_Rica:": "\U0001f1e8\U0001f1f7", - ":flag_Croatia:": "\U0001f1ed\U0001f1f7", - ":flag_Cuba:": "\U0001f1e8\U0001f1fa", - ":flag_Curaçao:": "\U0001f1e8\U0001f1fc", - ":flag_Cyprus:": "\U0001f1e8\U0001f1fe", - ":flag_Czechia:": "\U0001f1e8\U0001f1ff", - ":flag_Côte_d’Ivoire:": "\U0001f1e8\U0001f1ee", - ":flag_Denmark:": "\U0001f1e9\U0001f1f0", - ":flag_Diego_Garcia:": "\U0001f1e9\U0001f1ec", - ":flag_Djibouti:": "\U0001f1e9\U0001f1ef", - ":flag_Dominica:": "\U0001f1e9\U0001f1f2", - ":flag_Dominican_Republic:": "\U0001f1e9\U0001f1f4", - ":flag_Ecuador:": "\U0001f1ea\U0001f1e8", - ":flag_Egypt:": "\U0001f1ea\U0001f1ec", - ":flag_El_Salvador:": "\U0001f1f8\U0001f1fb", - ":flag_England:": "\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f", - ":flag_Equatorial_Guinea:": "\U0001f1ec\U0001f1f6", - ":flag_Eritrea:": "\U0001f1ea\U0001f1f7", - ":flag_Estonia:": "\U0001f1ea\U0001f1ea", - ":flag_Eswatini:": "\U0001f1f8\U0001f1ff", - ":flag_Ethiopia:": "\U0001f1ea\U0001f1f9", - ":flag_European_Union:": "\U0001f1ea\U0001f1fa", - ":flag_Falkland_Islands:": "\U0001f1eb\U0001f1f0", - ":flag_Faroe_Islands:": "\U0001f1eb\U0001f1f4", - ":flag_Fiji:": "\U0001f1eb\U0001f1ef", - ":flag_Finland:": "\U0001f1eb\U0001f1ee", - ":flag_France:": "\U0001f1eb\U0001f1f7", - ":flag_French_Guiana:": "\U0001f1ec\U0001f1eb", - ":flag_French_Polynesia:": "\U0001f1f5\U0001f1eb", - ":flag_French_Southern_Territories:": "\U0001f1f9\U0001f1eb", - ":flag_Gabon:": "\U0001f1ec\U0001f1e6", - ":flag_Gambia:": "\U0001f1ec\U0001f1f2", - ":flag_Georgia:": "\U0001f1ec\U0001f1ea", - ":flag_Germany:": "\U0001f1e9\U0001f1ea", - ":flag_Ghana:": "\U0001f1ec\U0001f1ed", - ":flag_Gibraltar:": "\U0001f1ec\U0001f1ee", - ":flag_Greece:": "\U0001f1ec\U0001f1f7", - ":flag_Greenland:": "\U0001f1ec\U0001f1f1", - ":flag_Grenada:": "\U0001f1ec\U0001f1e9", - ":flag_Guadeloupe:": "\U0001f1ec\U0001f1f5", - ":flag_Guam:": "\U0001f1ec\U0001f1fa", - ":flag_Guatemala:": "\U0001f1ec\U0001f1f9", - ":flag_Guernsey:": "\U0001f1ec\U0001f1ec", - ":flag_Guinea:": "\U0001f1ec\U0001f1f3", - ":flag_Guinea-Bissau:": "\U0001f1ec\U0001f1fc", - ":flag_Guyana:": "\U0001f1ec\U0001f1fe", - ":flag_Haiti:": "\U0001f1ed\U0001f1f9", - ":flag_Heard_&_McDonald_Islands:": "\U0001f1ed\U0001f1f2", - ":flag_Honduras:": "\U0001f1ed\U0001f1f3", - ":flag_Hong_Kong_SAR_China:": "\U0001f1ed\U0001f1f0", - ":flag_Hungary:": "\U0001f1ed\U0001f1fa", - ":flag_Iceland:": "\U0001f1ee\U0001f1f8", - ":flag_India:": "\U0001f1ee\U0001f1f3", - ":flag_Indonesia:": "\U0001f1ee\U0001f1e9", - ":flag_Iran:": "\U0001f1ee\U0001f1f7", - ":flag_Iraq:": "\U0001f1ee\U0001f1f6", - ":flag_Ireland:": "\U0001f1ee\U0001f1ea", - ":flag_Isle_of_Man:": "\U0001f1ee\U0001f1f2", - ":flag_Israel:": "\U0001f1ee\U0001f1f1", - ":flag_Italy:": "\U0001f1ee\U0001f1f9", - ":flag_Jamaica:": "\U0001f1ef\U0001f1f2", - ":flag_Japan:": "\U0001f1ef\U0001f1f5", - ":flag_Jersey:": "\U0001f1ef\U0001f1ea", - ":flag_Jordan:": "\U0001f1ef\U0001f1f4", - ":flag_Kazakhstan:": "\U0001f1f0\U0001f1ff", - ":flag_Kenya:": "\U0001f1f0\U0001f1ea", - ":flag_Kiribati:": "\U0001f1f0\U0001f1ee", - ":flag_Kosovo:": "\U0001f1fd\U0001f1f0", - ":flag_Kuwait:": "\U0001f1f0\U0001f1fc", - ":flag_Kyrgyzstan:": "\U0001f1f0\U0001f1ec", - ":flag_Laos:": "\U0001f1f1\U0001f1e6", - ":flag_Latvia:": "\U0001f1f1\U0001f1fb", - ":flag_Lebanon:": "\U0001f1f1\U0001f1e7", - ":flag_Lesotho:": "\U0001f1f1\U0001f1f8", - ":flag_Liberia:": "\U0001f1f1\U0001f1f7", - ":flag_Libya:": "\U0001f1f1\U0001f1fe", - ":flag_Liechtenstein:": "\U0001f1f1\U0001f1ee", - ":flag_Lithuania:": "\U0001f1f1\U0001f1f9", - ":flag_Luxembourg:": "\U0001f1f1\U0001f1fa", - ":flag_Macao_SAR_China:": "\U0001f1f2\U0001f1f4", - ":flag_Madagascar:": "\U0001f1f2\U0001f1ec", - ":flag_Malawi:": "\U0001f1f2\U0001f1fc", - ":flag_Malaysia:": "\U0001f1f2\U0001f1fe", - ":flag_Maldives:": "\U0001f1f2\U0001f1fb", - ":flag_Mali:": "\U0001f1f2\U0001f1f1", - ":flag_Malta:": "\U0001f1f2\U0001f1f9", - ":flag_Marshall_Islands:": "\U0001f1f2\U0001f1ed", - ":flag_Martinique:": "\U0001f1f2\U0001f1f6", - ":flag_Mauritania:": "\U0001f1f2\U0001f1f7", - ":flag_Mauritius:": "\U0001f1f2\U0001f1fa", - ":flag_Mayotte:": "\U0001f1fe\U0001f1f9", - ":flag_Mexico:": "\U0001f1f2\U0001f1fd", - ":flag_Micronesia:": "\U0001f1eb\U0001f1f2", - ":flag_Moldova:": "\U0001f1f2\U0001f1e9", - ":flag_Monaco:": "\U0001f1f2\U0001f1e8", - ":flag_Mongolia:": "\U0001f1f2\U0001f1f3", - ":flag_Montenegro:": "\U0001f1f2\U0001f1ea", - ":flag_Montserrat:": "\U0001f1f2\U0001f1f8", - ":flag_Morocco:": "\U0001f1f2\U0001f1e6", - ":flag_Mozambique:": "\U0001f1f2\U0001f1ff", - ":flag_Myanmar_(Burma):": "\U0001f1f2\U0001f1f2", - ":flag_Namibia:": "\U0001f1f3\U0001f1e6", - ":flag_Nauru:": "\U0001f1f3\U0001f1f7", - ":flag_Nepal:": "\U0001f1f3\U0001f1f5", - ":flag_Netherlands:": "\U0001f1f3\U0001f1f1", - ":flag_New_Caledonia:": "\U0001f1f3\U0001f1e8", - ":flag_New_Zealand:": "\U0001f1f3\U0001f1ff", - ":flag_Nicaragua:": "\U0001f1f3\U0001f1ee", - ":flag_Niger:": "\U0001f1f3\U0001f1ea", - ":flag_Nigeria:": "\U0001f1f3\U0001f1ec", - ":flag_Niue:": "\U0001f1f3\U0001f1fa", - ":flag_Norfolk_Island:": "\U0001f1f3\U0001f1eb", - ":flag_North_Korea:": "\U0001f1f0\U0001f1f5", - ":flag_North_Macedonia:": "\U0001f1f2\U0001f1f0", - ":flag_Northern_Mariana_Islands:": "\U0001f1f2\U0001f1f5", - ":flag_Norway:": "\U0001f1f3\U0001f1f4", - ":flag_Oman:": "\U0001f1f4\U0001f1f2", - ":flag_Pakistan:": "\U0001f1f5\U0001f1f0", - ":flag_Palau:": "\U0001f1f5\U0001f1fc", - ":flag_Palestinian_Territories:": "\U0001f1f5\U0001f1f8", - ":flag_Panama:": "\U0001f1f5\U0001f1e6", - ":flag_Papua_New_Guinea:": "\U0001f1f5\U0001f1ec", - ":flag_Paraguay:": "\U0001f1f5\U0001f1fe", - ":flag_Peru:": "\U0001f1f5\U0001f1ea", - ":flag_Philippines:": "\U0001f1f5\U0001f1ed", - ":flag_Pitcairn_Islands:": "\U0001f1f5\U0001f1f3", - ":flag_Poland:": "\U0001f1f5\U0001f1f1", - ":flag_Portugal:": "\U0001f1f5\U0001f1f9", - ":flag_Puerto_Rico:": "\U0001f1f5\U0001f1f7", - ":flag_Qatar:": "\U0001f1f6\U0001f1e6", - ":flag_Romania:": "\U0001f1f7\U0001f1f4", - ":flag_Russia:": "\U0001f1f7\U0001f1fa", - ":flag_Rwanda:": "\U0001f1f7\U0001f1fc", - ":flag_Réunion:": "\U0001f1f7\U0001f1ea", - ":flag_Samoa:": "\U0001f1fc\U0001f1f8", - ":flag_San_Marino:": "\U0001f1f8\U0001f1f2", - ":flag_Saudi_Arabia:": "\U0001f1f8\U0001f1e6", - ":flag_Scotland:": "\U0001f3f4\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f", - ":flag_Senegal:": "\U0001f1f8\U0001f1f3", - ":flag_Serbia:": "\U0001f1f7\U0001f1f8", - ":flag_Seychelles:": "\U0001f1f8\U0001f1e8", - ":flag_Sierra_Leone:": "\U0001f1f8\U0001f1f1", - ":flag_Singapore:": "\U0001f1f8\U0001f1ec", - ":flag_Sint_Maarten:": "\U0001f1f8\U0001f1fd", - ":flag_Slovakia:": "\U0001f1f8\U0001f1f0", - ":flag_Slovenia:": "\U0001f1f8\U0001f1ee", - ":flag_Solomon_Islands:": "\U0001f1f8\U0001f1e7", - ":flag_Somalia:": "\U0001f1f8\U0001f1f4", - ":flag_South_Africa:": "\U0001f1ff\U0001f1e6", - ":flag_South_Georgia_&_South_Sandwich_Islands:": "\U0001f1ec\U0001f1f8", - ":flag_South_Korea:": "\U0001f1f0\U0001f1f7", - ":flag_South_Sudan:": "\U0001f1f8\U0001f1f8", - ":flag_Spain:": "\U0001f1ea\U0001f1f8", - ":flag_Sri_Lanka:": "\U0001f1f1\U0001f1f0", - ":flag_St._Barthélemy:": "\U0001f1e7\U0001f1f1", - ":flag_St._Helena:": "\U0001f1f8\U0001f1ed", - ":flag_St._Kitts_&_Nevis:": "\U0001f1f0\U0001f1f3", - ":flag_St._Lucia:": "\U0001f1f1\U0001f1e8", - ":flag_St._Martin:": "\U0001f1f2\U0001f1eb", - ":flag_St._Pierre_&_Miquelon:": "\U0001f1f5\U0001f1f2", - ":flag_St._Vincent_&_Grenadines:": "\U0001f1fb\U0001f1e8", - ":flag_Sudan:": "\U0001f1f8\U0001f1e9", - ":flag_Suriname:": "\U0001f1f8\U0001f1f7", - ":flag_Svalbard_&_Jan_Mayen:": "\U0001f1f8\U0001f1ef", - ":flag_Sweden:": "\U0001f1f8\U0001f1ea", - ":flag_Switzerland:": "\U0001f1e8\U0001f1ed", - ":flag_Syria:": "\U0001f1f8\U0001f1fe", - ":flag_São_Tomé_&_Príncipe:": "\U0001f1f8\U0001f1f9", - ":flag_Taiwan:": "\U0001f1f9\U0001f1fc", - ":flag_Tajikistan:": "\U0001f1f9\U0001f1ef", - ":flag_Tanzania:": "\U0001f1f9\U0001f1ff", - ":flag_Thailand:": "\U0001f1f9\U0001f1ed", - ":flag_Timor-Leste:": "\U0001f1f9\U0001f1f1", - ":flag_Togo:": "\U0001f1f9\U0001f1ec", - ":flag_Tokelau:": "\U0001f1f9\U0001f1f0", - ":flag_Tonga:": "\U0001f1f9\U0001f1f4", - ":flag_Trinidad_&_Tobago:": "\U0001f1f9\U0001f1f9", - ":flag_Tristan_da_Cunha:": "\U0001f1f9\U0001f1e6", - ":flag_Tunisia:": "\U0001f1f9\U0001f1f3", - ":flag_Turkey:": "\U0001f1f9\U0001f1f7", - ":flag_Turkmenistan:": "\U0001f1f9\U0001f1f2", - ":flag_Turks_&_Caicos_Islands:": "\U0001f1f9\U0001f1e8", - ":flag_Tuvalu:": "\U0001f1f9\U0001f1fb", - ":flag_U.S._Outlying_Islands:": "\U0001f1fa\U0001f1f2", - ":flag_U.S._Virgin_Islands:": "\U0001f1fb\U0001f1ee", - ":flag_Uganda:": "\U0001f1fa\U0001f1ec", - ":flag_Ukraine:": "\U0001f1fa\U0001f1e6", - ":flag_United_Arab_Emirates:": "\U0001f1e6\U0001f1ea", - ":flag_United_Kingdom:": "\U0001f1ec\U0001f1e7", - ":flag_United_Nations:": "\U0001f1fa\U0001f1f3", - ":flag_United_States:": "\U0001f1fa\U0001f1f8", - ":flag_Uruguay:": "\U0001f1fa\U0001f1fe", - ":flag_Uzbekistan:": "\U0001f1fa\U0001f1ff", - ":flag_Vanuatu:": "\U0001f1fb\U0001f1fa", - ":flag_Vatican_City:": "\U0001f1fb\U0001f1e6", - ":flag_Venezuela:": "\U0001f1fb\U0001f1ea", - ":flag_Vietnam:": "\U0001f1fb\U0001f1f3", - ":flag_Wales:": "\U0001f3f4\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f", - ":flag_Wallis_&_Futuna:": "\U0001f1fc\U0001f1eb", - ":flag_Western_Sahara:": "\U0001f1ea\U0001f1ed", - ":flag_Yemen:": "\U0001f1fe\U0001f1ea", - ":flag_Zambia:": "\U0001f1ff\U0001f1f2", - ":flag_Zimbabwe:": "\U0001f1ff\U0001f1fc", - ":flag_ac:": "\U0001f1e6\U0001f1e8", - ":flag_ad:": "\U0001f1e6\U0001f1e9", - ":flag_ae:": "\U0001f1e6\U0001f1ea", - ":flag_af:": "\U0001f1e6\U0001f1eb", - ":flag_ag:": "\U0001f1e6\U0001f1ec", - ":flag_ai:": "\U0001f1e6\U0001f1ee", - ":flag_al:": "\U0001f1e6\U0001f1f1", - ":flag_am:": "\U0001f1e6\U0001f1f2", - ":flag_ao:": "\U0001f1e6\U0001f1f4", - ":flag_aq:": "\U0001f1e6\U0001f1f6", - ":flag_ar:": "\U0001f1e6\U0001f1f7", - ":flag_as:": "\U0001f1e6\U0001f1f8", - ":flag_at:": "\U0001f1e6\U0001f1f9", - ":flag_au:": "\U0001f1e6\U0001f1fa", - ":flag_aw:": "\U0001f1e6\U0001f1fc", - ":flag_ax:": "\U0001f1e6\U0001f1fd", - ":flag_az:": "\U0001f1e6\U0001f1ff", - ":flag_ba:": "\U0001f1e7\U0001f1e6", - ":flag_bb:": "\U0001f1e7\U0001f1e7", - ":flag_bd:": "\U0001f1e7\U0001f1e9", - ":flag_be:": "\U0001f1e7\U0001f1ea", - ":flag_bf:": "\U0001f1e7\U0001f1eb", - ":flag_bg:": "\U0001f1e7\U0001f1ec", - ":flag_bh:": "\U0001f1e7\U0001f1ed", - ":flag_bi:": "\U0001f1e7\U0001f1ee", - ":flag_bj:": "\U0001f1e7\U0001f1ef", - ":flag_bl:": "\U0001f1e7\U0001f1f1", - ":flag_black:": "\U0001f3f4", - ":flag_bm:": "\U0001f1e7\U0001f1f2", - ":flag_bn:": "\U0001f1e7\U0001f1f3", - ":flag_bo:": "\U0001f1e7\U0001f1f4", - ":flag_bq:": "\U0001f1e7\U0001f1f6", - ":flag_br:": "\U0001f1e7\U0001f1f7", - ":flag_bs:": "\U0001f1e7\U0001f1f8", - ":flag_bt:": "\U0001f1e7\U0001f1f9", - ":flag_bv:": "\U0001f1e7\U0001f1fb", - ":flag_bw:": "\U0001f1e7\U0001f1fc", - ":flag_by:": "\U0001f1e7\U0001f1fe", - ":flag_bz:": "\U0001f1e7\U0001f1ff", - ":flag_ca:": "\U0001f1e8\U0001f1e6", - ":flag_cc:": "\U0001f1e8\U0001f1e8", - ":flag_cd:": "\U0001f1e8\U0001f1e9", - ":flag_cf:": "\U0001f1e8\U0001f1eb", - ":flag_cg:": "\U0001f1e8\U0001f1ec", - ":flag_ch:": "\U0001f1e8\U0001f1ed", - ":flag_ci:": "\U0001f1e8\U0001f1ee", - ":flag_ck:": "\U0001f1e8\U0001f1f0", - ":flag_cl:": "\U0001f1e8\U0001f1f1", - ":flag_cm:": "\U0001f1e8\U0001f1f2", - ":flag_cn:": "\U0001f1e8\U0001f1f3", - ":flag_co:": "\U0001f1e8\U0001f1f4", - ":flag_cp:": "\U0001f1e8\U0001f1f5", - ":flag_cr:": "\U0001f1e8\U0001f1f7", - ":flag_cu:": "\U0001f1e8\U0001f1fa", - ":flag_cv:": "\U0001f1e8\U0001f1fb", - ":flag_cw:": "\U0001f1e8\U0001f1fc", - ":flag_cx:": "\U0001f1e8\U0001f1fd", - ":flag_cy:": "\U0001f1e8\U0001f1fe", - ":flag_cz:": "\U0001f1e8\U0001f1ff", - ":flag_de:": "\U0001f1e9\U0001f1ea", - ":flag_dg:": "\U0001f1e9\U0001f1ec", - ":flag_dj:": "\U0001f1e9\U0001f1ef", - ":flag_dk:": "\U0001f1e9\U0001f1f0", - ":flag_dm:": "\U0001f1e9\U0001f1f2", - ":flag_do:": "\U0001f1e9\U0001f1f4", - ":flag_dz:": "\U0001f1e9\U0001f1ff", - ":flag_ea:": "\U0001f1ea\U0001f1e6", - ":flag_ec:": "\U0001f1ea\U0001f1e8", - ":flag_ee:": "\U0001f1ea\U0001f1ea", - ":flag_eg:": "\U0001f1ea\U0001f1ec", - ":flag_eh:": "\U0001f1ea\U0001f1ed", - ":flag_er:": "\U0001f1ea\U0001f1f7", - ":flag_es:": "\U0001f1ea\U0001f1f8", - ":flag_et:": "\U0001f1ea\U0001f1f9", - ":flag_eu:": "\U0001f1ea\U0001f1fa", - ":flag_fi:": "\U0001f1eb\U0001f1ee", - ":flag_fj:": "\U0001f1eb\U0001f1ef", - ":flag_fk:": "\U0001f1eb\U0001f1f0", - ":flag_fm:": "\U0001f1eb\U0001f1f2", - ":flag_fo:": "\U0001f1eb\U0001f1f4", - ":flag_fr:": "\U0001f1eb\U0001f1f7", - ":flag_ga:": "\U0001f1ec\U0001f1e6", - ":flag_gb:": "\U0001f1ec\U0001f1e7", - ":flag_gd:": "\U0001f1ec\U0001f1e9", - ":flag_ge:": "\U0001f1ec\U0001f1ea", - ":flag_gf:": "\U0001f1ec\U0001f1eb", - ":flag_gg:": "\U0001f1ec\U0001f1ec", - ":flag_gh:": "\U0001f1ec\U0001f1ed", - ":flag_gi:": "\U0001f1ec\U0001f1ee", - ":flag_gl:": "\U0001f1ec\U0001f1f1", - ":flag_gm:": "\U0001f1ec\U0001f1f2", - ":flag_gn:": "\U0001f1ec\U0001f1f3", - ":flag_gp:": "\U0001f1ec\U0001f1f5", - ":flag_gq:": "\U0001f1ec\U0001f1f6", - ":flag_gr:": "\U0001f1ec\U0001f1f7", - ":flag_gs:": "\U0001f1ec\U0001f1f8", - ":flag_gt:": "\U0001f1ec\U0001f1f9", - ":flag_gu:": "\U0001f1ec\U0001f1fa", - ":flag_gw:": "\U0001f1ec\U0001f1fc", - ":flag_gy:": "\U0001f1ec\U0001f1fe", - ":flag_hk:": "\U0001f1ed\U0001f1f0", - ":flag_hm:": "\U0001f1ed\U0001f1f2", - ":flag_hn:": "\U0001f1ed\U0001f1f3", - ":flag_hr:": "\U0001f1ed\U0001f1f7", - ":flag_ht:": "\U0001f1ed\U0001f1f9", - ":flag_hu:": "\U0001f1ed\U0001f1fa", - ":flag_ic:": "\U0001f1ee\U0001f1e8", - ":flag_id:": "\U0001f1ee\U0001f1e9", - ":flag_ie:": "\U0001f1ee\U0001f1ea", - ":flag_il:": "\U0001f1ee\U0001f1f1", - ":flag_im:": "\U0001f1ee\U0001f1f2", - ":flag_in:": "\U0001f1ee\U0001f1f3", - ":flag_in_hole:": "\u26f3", - ":flag_io:": "\U0001f1ee\U0001f1f4", - ":flag_iq:": "\U0001f1ee\U0001f1f6", - ":flag_ir:": "\U0001f1ee\U0001f1f7", - ":flag_is:": "\U0001f1ee\U0001f1f8", - ":flag_it:": "\U0001f1ee\U0001f1f9", - ":flag_je:": "\U0001f1ef\U0001f1ea", - ":flag_jm:": "\U0001f1ef\U0001f1f2", - ":flag_jo:": "\U0001f1ef\U0001f1f4", - ":flag_jp:": "\U0001f1ef\U0001f1f5", - ":flag_ke:": "\U0001f1f0\U0001f1ea", - ":flag_kg:": "\U0001f1f0\U0001f1ec", - ":flag_kh:": "\U0001f1f0\U0001f1ed", - ":flag_ki:": "\U0001f1f0\U0001f1ee", - ":flag_km:": "\U0001f1f0\U0001f1f2", - ":flag_kn:": "\U0001f1f0\U0001f1f3", - ":flag_kp:": "\U0001f1f0\U0001f1f5", - ":flag_kr:": "\U0001f1f0\U0001f1f7", - ":flag_kw:": "\U0001f1f0\U0001f1fc", - ":flag_ky:": "\U0001f1f0\U0001f1fe", - ":flag_kz:": "\U0001f1f0\U0001f1ff", - ":flag_la:": "\U0001f1f1\U0001f1e6", - ":flag_lb:": "\U0001f1f1\U0001f1e7", - ":flag_lc:": "\U0001f1f1\U0001f1e8", - ":flag_li:": "\U0001f1f1\U0001f1ee", - ":flag_lk:": "\U0001f1f1\U0001f1f0", - ":flag_lr:": "\U0001f1f1\U0001f1f7", - ":flag_ls:": "\U0001f1f1\U0001f1f8", - ":flag_lt:": "\U0001f1f1\U0001f1f9", - ":flag_lu:": "\U0001f1f1\U0001f1fa", - ":flag_lv:": "\U0001f1f1\U0001f1fb", - ":flag_ly:": "\U0001f1f1\U0001f1fe", - ":flag_ma:": "\U0001f1f2\U0001f1e6", - ":flag_mc:": "\U0001f1f2\U0001f1e8", - ":flag_md:": "\U0001f1f2\U0001f1e9", - ":flag_me:": "\U0001f1f2\U0001f1ea", - ":flag_mf:": "\U0001f1f2\U0001f1eb", - ":flag_mg:": "\U0001f1f2\U0001f1ec", - ":flag_mh:": "\U0001f1f2\U0001f1ed", - ":flag_mk:": "\U0001f1f2\U0001f1f0", - ":flag_ml:": "\U0001f1f2\U0001f1f1", - ":flag_mm:": "\U0001f1f2\U0001f1f2", - ":flag_mn:": "\U0001f1f2\U0001f1f3", - ":flag_mo:": "\U0001f1f2\U0001f1f4", - ":flag_mp:": "\U0001f1f2\U0001f1f5", - ":flag_mq:": "\U0001f1f2\U0001f1f6", - ":flag_mr:": "\U0001f1f2\U0001f1f7", - ":flag_ms:": "\U0001f1f2\U0001f1f8", - ":flag_mt:": "\U0001f1f2\U0001f1f9", - ":flag_mu:": "\U0001f1f2\U0001f1fa", - ":flag_mv:": "\U0001f1f2\U0001f1fb", - ":flag_mw:": "\U0001f1f2\U0001f1fc", - ":flag_mx:": "\U0001f1f2\U0001f1fd", - ":flag_my:": "\U0001f1f2\U0001f1fe", - ":flag_mz:": "\U0001f1f2\U0001f1ff", - ":flag_na:": "\U0001f1f3\U0001f1e6", - ":flag_nc:": "\U0001f1f3\U0001f1e8", - ":flag_ne:": "\U0001f1f3\U0001f1ea", - ":flag_nf:": "\U0001f1f3\U0001f1eb", - ":flag_ng:": "\U0001f1f3\U0001f1ec", - ":flag_ni:": "\U0001f1f3\U0001f1ee", - ":flag_nl:": "\U0001f1f3\U0001f1f1", - ":flag_no:": "\U0001f1f3\U0001f1f4", - ":flag_np:": "\U0001f1f3\U0001f1f5", - ":flag_nr:": "\U0001f1f3\U0001f1f7", - ":flag_nu:": "\U0001f1f3\U0001f1fa", - ":flag_nz:": "\U0001f1f3\U0001f1ff", - ":flag_om:": "\U0001f1f4\U0001f1f2", - ":flag_pa:": "\U0001f1f5\U0001f1e6", - ":flag_pe:": "\U0001f1f5\U0001f1ea", - ":flag_pf:": "\U0001f1f5\U0001f1eb", - ":flag_pg:": "\U0001f1f5\U0001f1ec", - ":flag_ph:": "\U0001f1f5\U0001f1ed", - ":flag_pk:": "\U0001f1f5\U0001f1f0", - ":flag_pl:": "\U0001f1f5\U0001f1f1", - ":flag_pm:": "\U0001f1f5\U0001f1f2", - ":flag_pn:": "\U0001f1f5\U0001f1f3", - ":flag_pr:": "\U0001f1f5\U0001f1f7", - ":flag_ps:": "\U0001f1f5\U0001f1f8", - ":flag_pt:": "\U0001f1f5\U0001f1f9", - ":flag_pw:": "\U0001f1f5\U0001f1fc", - ":flag_py:": "\U0001f1f5\U0001f1fe", - ":flag_qa:": "\U0001f1f6\U0001f1e6", - ":flag_re:": "\U0001f1f7\U0001f1ea", - ":flag_ro:": "\U0001f1f7\U0001f1f4", - ":flag_rs:": "\U0001f1f7\U0001f1f8", - ":flag_ru:": "\U0001f1f7\U0001f1fa", - ":flag_rw:": "\U0001f1f7\U0001f1fc", - ":flag_sa:": "\U0001f1f8\U0001f1e6", - ":flag_sb:": "\U0001f1f8\U0001f1e7", - ":flag_sc:": "\U0001f1f8\U0001f1e8", - ":flag_sd:": "\U0001f1f8\U0001f1e9", - ":flag_se:": "\U0001f1f8\U0001f1ea", - ":flag_sg:": "\U0001f1f8\U0001f1ec", - ":flag_sh:": "\U0001f1f8\U0001f1ed", - ":flag_si:": "\U0001f1f8\U0001f1ee", - ":flag_sj:": "\U0001f1f8\U0001f1ef", - ":flag_sk:": "\U0001f1f8\U0001f1f0", - ":flag_sl:": "\U0001f1f8\U0001f1f1", - ":flag_sm:": "\U0001f1f8\U0001f1f2", - ":flag_sn:": "\U0001f1f8\U0001f1f3", - ":flag_so:": "\U0001f1f8\U0001f1f4", - ":flag_sr:": "\U0001f1f8\U0001f1f7", - ":flag_ss:": "\U0001f1f8\U0001f1f8", - ":flag_st:": "\U0001f1f8\U0001f1f9", - ":flag_sv:": "\U0001f1f8\U0001f1fb", - ":flag_sx:": "\U0001f1f8\U0001f1fd", - ":flag_sy:": "\U0001f1f8\U0001f1fe", - ":flag_sz:": "\U0001f1f8\U0001f1ff", - ":flag_ta:": "\U0001f1f9\U0001f1e6", - ":flag_tc:": "\U0001f1f9\U0001f1e8", - ":flag_td:": "\U0001f1f9\U0001f1e9", - ":flag_tf:": "\U0001f1f9\U0001f1eb", - ":flag_tg:": "\U0001f1f9\U0001f1ec", - ":flag_th:": "\U0001f1f9\U0001f1ed", - ":flag_tj:": "\U0001f1f9\U0001f1ef", - ":flag_tk:": "\U0001f1f9\U0001f1f0", - ":flag_tl:": "\U0001f1f9\U0001f1f1", - ":flag_tm:": "\U0001f1f9\U0001f1f2", - ":flag_tn:": "\U0001f1f9\U0001f1f3", - ":flag_to:": "\U0001f1f9\U0001f1f4", - ":flag_tr:": "\U0001f1f9\U0001f1f7", - ":flag_tt:": "\U0001f1f9\U0001f1f9", - ":flag_tv:": "\U0001f1f9\U0001f1fb", - ":flag_tw:": "\U0001f1f9\U0001f1fc", - ":flag_tz:": "\U0001f1f9\U0001f1ff", - ":flag_ua:": "\U0001f1fa\U0001f1e6", - ":flag_ug:": "\U0001f1fa\U0001f1ec", - ":flag_um:": "\U0001f1fa\U0001f1f2", - ":flag_us:": "\U0001f1fa\U0001f1f8", - ":flag_uy:": "\U0001f1fa\U0001f1fe", - ":flag_uz:": "\U0001f1fa\U0001f1ff", - ":flag_va:": "\U0001f1fb\U0001f1e6", - ":flag_vc:": "\U0001f1fb\U0001f1e8", - ":flag_ve:": "\U0001f1fb\U0001f1ea", - ":flag_vg:": "\U0001f1fb\U0001f1ec", - ":flag_vi:": "\U0001f1fb\U0001f1ee", - ":flag_vn:": "\U0001f1fb\U0001f1f3", - ":flag_vu:": "\U0001f1fb\U0001f1fa", - ":flag_wf:": "\U0001f1fc\U0001f1eb", - ":flag_white:": "\U0001f3f3", - ":flag_ws:": "\U0001f1fc\U0001f1f8", - ":flag_xk:": "\U0001f1fd\U0001f1f0", - ":flag_ye:": "\U0001f1fe\U0001f1ea", - ":flag_yt:": "\U0001f1fe\U0001f1f9", - ":flag_za:": "\U0001f1ff\U0001f1e6", - ":flag_zm:": "\U0001f1ff\U0001f1f2", - ":flag_zw:": "\U0001f1ff\U0001f1fc", - ":flag_Åland_Islands:": "\U0001f1e6\U0001f1fd", - ":flags:": "\U0001f38f", - ":flamingo:": "\U0001f9a9", - ":flashlight:": "\U0001f526", - ":flat_shoe:": "\U0001f97f", - ":flatbread:": "\U0001fad3", - ":fleur-de-lis:": "\u269c", - ":fleur_de_lis:": "\u269c\ufe0f", - ":flexed_biceps:": "\U0001f4aa", - ":flight_arrival:": "\U0001f6ec", - ":flight_departure:": "\U0001f6eb", - ":flipper:": "\U0001f42c", - ":floppy_disk:": "\U0001f4be", - ":flower_playing_cards:": "\U0001f3b4", - ":flushed:": "\U0001f633", - ":flushed_face:": "\U0001f633", - ":fly:": "\U0001fab0", - ":flying_disc:": "\U0001f94f", - ":flying_saucer:": "\U0001f6f8", - ":fog:": "\U0001f32b\ufe0f", - ":foggy:": "\U0001f301", - ":folded_hands:": "\U0001f64f", - ":fondue:": "\U0001fad5", - ":foot:": "\U0001f9b6", - ":football:": "\U0001f3c8", - ":footprints:": "\U0001f463", - ":fork_and_knife:": "\U0001f374", - ":fork_and_knife_with_plate:": "\U0001f37d", - ":fork_knife_plate:": "\U0001f37d", - ":fortune_cookie:": "\U0001f960", - ":fountain:": "\u26f2", - ":fountain_pen:": "\U0001f58b", - ":four:": "4\ufe0f\u20e3", - ":four-thirty:": "\U0001f55f", - ":four_leaf_clover:": "\U0001f340", - ":four_o’clock:": "\U0001f553", - ":fox:": "\U0001f98a", - ":fox_face:": "\U0001f98a", - ":fr:": "\U0001f1eb\U0001f1f7", - ":frame_photo:": "\U0001f5bc", - ":frame_with_picture:": "\U0001f5bc\ufe0f", - ":framed_picture:": "\U0001f5bc", - ":free:": "\U0001f193", - ":french_bread:": "\U0001f956", - ":french_fries:": "\U0001f35f", - ":french_guiana:": "\U0001f1ec\U0001f1eb", - ":french_polynesia:": "\U0001f1f5\U0001f1eb", - ":french_southern_territories:": "\U0001f1f9\U0001f1eb", - ":fried_egg:": "\U0001f373", - ":fried_shrimp:": "\U0001f364", - ":fries:": "\U0001f35f", - ":frog:": "\U0001f438", - ":front-facing_baby_chick:": "\U0001f425", - ":frowning:": "\U0001f626", - ":frowning2:": "\u2639", - ":frowning_face:": "\u2639", - ":frowning_face_with_open_mouth:": "\U0001f626", - ":frowning_man:": "\U0001f64d\u200d\u2642\ufe0f", - ":frowning_person:": "\U0001f64d", - ":frowning_woman:": "\U0001f64d\u200d\u2640\ufe0f", - ":fu:": "\U0001f595", - ":fuel_pump:": "\u26fd", - ":fuelpump:": "\u26fd", - ":full_moon:": "\U0001f315", - ":full_moon_face:": "\U0001f31d", - ":full_moon_with_face:": "\U0001f31d", - ":funeral_urn:": "\u26b1\ufe0f", - ":gabon:": "\U0001f1ec\U0001f1e6", - ":gambia:": "\U0001f1ec\U0001f1f2", - ":game_die:": "\U0001f3b2", - ":garlic:": "\U0001f9c4", - ":gb:": "\U0001f1ec\U0001f1e7", - ":gear:": "\u2699\ufe0f", - ":gem:": "\U0001f48e", - ":gem_stone:": "\U0001f48e", - ":gemini:": "\u264a", - ":genie:": "\U0001f9de\u200d\u2642\ufe0f", - ":genie_man:": "\U0001f9de\u200d\u2642\ufe0f", - ":genie_woman:": "\U0001f9de\u200d\u2640\ufe0f", - ":georgia:": "\U0001f1ec\U0001f1ea", - ":ghana:": "\U0001f1ec\U0001f1ed", - ":ghost:": "\U0001f47b", - ":gibraltar:": "\U0001f1ec\U0001f1ee", - ":gift:": "\U0001f381", - ":gift_heart:": "\U0001f49d", - ":giraffe:": "\U0001f992", - ":giraffe_face:": "\U0001f992", - ":girl:": "\U0001f467", - ":girl_tone1:": "\U0001f467\U0001f3fb", - ":girl_tone2:": "\U0001f467\U0001f3fc", - ":girl_tone3:": "\U0001f467\U0001f3fd", - ":girl_tone4:": "\U0001f467\U0001f3fe", - ":girl_tone5:": "\U0001f467\U0001f3ff", - ":glass_of_milk:": "\U0001f95b", - ":glasses:": "\U0001f453", - ":globe_showing_Americas:": "\U0001f30e", - ":globe_showing_Asia-Australia:": "\U0001f30f", - ":globe_showing_Europe-Africa:": "\U0001f30d", - ":globe_with_meridians:": "\U0001f310", - ":gloves:": "\U0001f9e4", - ":glowing_star:": "\U0001f31f", - ":goal:": "\U0001f945", - ":goal_net:": "\U0001f945", - ":goat:": "\U0001f410", - ":goblin:": "\U0001f47a", - ":goggles:": "\U0001f97d", - ":golf:": "\u26f3", - ":golfer:": "\U0001f3cc\ufe0f\u200d\u2642\ufe0f", - ":golfing:": "\U0001f3cc\ufe0f", - ":golfing_man:": "\U0001f3cc\ufe0f\u200d\u2642\ufe0f", - ":golfing_woman:": "\U0001f3cc\ufe0f\u200d\u2640\ufe0f", - ":gorilla:": "\U0001f98d", - ":graduation_cap:": "\U0001f393", - ":grapes:": "\U0001f347", - ":greece:": "\U0001f1ec\U0001f1f7", - ":green_apple:": "\U0001f34f", - ":green_book:": "\U0001f4d7", - ":green_circle:": "\U0001f7e2", - ":green_heart:": "\U0001f49a", - ":green_salad:": "\U0001f957", - ":green_square:": "\U0001f7e9", - ":greenland:": "\U0001f1ec\U0001f1f1", - ":grenada:": "\U0001f1ec\U0001f1e9", - ":grey_exclamation:": "\u2755", - ":grey_question:": "\u2754", - ":grimacing:": "\U0001f62c", - ":grimacing_face:": "\U0001f62c", - ":grin:": "\U0001f601", - ":grinning:": "\U0001f600", - ":grinning_cat:": "\U0001f63a", - ":grinning_cat_with_smiling_eyes:": "\U0001f638", - ":grinning_face:": "\U0001f600", - ":grinning_face_with_big_eyes:": "\U0001f603", - ":grinning_face_with_smiling_eyes:": "\U0001f604", - ":grinning_face_with_sweat:": "\U0001f605", - ":grinning_squinting_face:": "\U0001f606", - ":growing_heart:": "\U0001f497", - ":guadeloupe:": "\U0001f1ec\U0001f1f5", - ":guam:": "\U0001f1ec\U0001f1fa", - ":guard:": "\U0001f482", - ":guard_tone1:": "\U0001f482\U0001f3fb", - ":guard_tone2:": "\U0001f482\U0001f3fc", - ":guard_tone3:": "\U0001f482\U0001f3fd", - ":guard_tone4:": "\U0001f482\U0001f3fe", - ":guard_tone5:": "\U0001f482\U0001f3ff", - ":guardsman:": "\U0001f482\u200d\u2642\ufe0f", - ":guardswoman:": "\U0001f482\u200d\u2640\ufe0f", - ":guatemala:": "\U0001f1ec\U0001f1f9", - ":guernsey:": "\U0001f1ec\U0001f1ec", - ":guide_dog:": "\U0001f9ae", - ":guinea:": "\U0001f1ec\U0001f1f3", - ":guinea_bissau:": "\U0001f1ec\U0001f1fc", - ":guitar:": "\U0001f3b8", - ":gun:": "\U0001f52b", - ":guyana:": "\U0001f1ec\U0001f1fe", - ":haircut:": "\U0001f487\u200d\u2640\ufe0f", - ":haircut_man:": "\U0001f487\u200d\u2642\ufe0f", - ":haircut_woman:": "\U0001f487\u200d\u2640\ufe0f", - ":haiti:": "\U0001f1ed\U0001f1f9", - ":hamburger:": "\U0001f354", - ":hammer:": "\U0001f528", - ":hammer_and_pick:": "\u2692\ufe0f", - ":hammer_and_wrench:": "\U0001f6e0\ufe0f", - ":hammer_pick:": "\u2692", - ":hamster:": "\U0001f439", - ":hand:": "\u270b", - ":hand_over_mouth:": "\U0001f92d", - ":hand_splayed_tone1:": "\U0001f590\U0001f3fb", - ":hand_splayed_tone2:": "\U0001f590\U0001f3fc", - ":hand_splayed_tone3:": "\U0001f590\U0001f3fd", - ":hand_splayed_tone4:": "\U0001f590\U0001f3fe", - ":hand_splayed_tone5:": "\U0001f590\U0001f3ff", - ":hand_with_fingers_splayed:": "\U0001f590", - ":handbag:": "\U0001f45c", - ":handball:": "\U0001f93e", - ":handball_person:": "\U0001f93e", - ":handshake:": "\U0001f91d", - ":hankey:": "\U0001f4a9", - ":hash:": "#\ufe0f\u20e3", - ":hatched_chick:": "\U0001f425", - ":hatching_chick:": "\U0001f423", - ":head_bandage:": "\U0001f915", - ":headphone:": "\U0001f3a7", - ":headphones:": "\U0001f3a7", - ":headstone:": "\U0001faa6", - ":health_worker:": "\U0001f9d1\u200d\u2695\ufe0f", - ":hear-no-evil_monkey:": "\U0001f649", - ":hear_no_evil:": "\U0001f649", - ":heard_mcdonald_islands:": "\U0001f1ed\U0001f1f2", - ":heart:": "\u2764\ufe0f", - ":heart_decoration:": "\U0001f49f", - ":heart_exclamation:": "\u2763", - ":heart_eyes:": "\U0001f60d", - ":heart_eyes_cat:": "\U0001f63b", - ":heart_on_fire:": "\u2764\ufe0f\u200d\U0001f525", - ":heart_suit:": "\u2665", - ":heart_with_arrow:": "\U0001f498", - ":heart_with_ribbon:": "\U0001f49d", - ":heartbeat:": "\U0001f493", - ":heartpulse:": "\U0001f497", - ":hearts:": "\u2665\ufe0f", - ":heavy_check_mark:": "\u2714\ufe0f", - ":heavy_division_sign:": "\u2797", - ":heavy_dollar_sign:": "\U0001f4b2", - ":heavy_exclamation_mark:": "\u2757", - ":heavy_heart_exclamation:": "\u2763\ufe0f", - ":heavy_heart_exclamation_mark_ornament:": "\u2763\ufe0f", - ":heavy_minus_sign:": "\u2796", - ":heavy_multiplication_x:": "\u2716\ufe0f", - ":heavy_plus_sign:": "\u2795", - ":hedgehog:": "\U0001f994", - ":helicopter:": "\U0001f681", - ":helmet_with_cross:": "\u26d1", - ":helmet_with_white_cross:": "\u26d1\ufe0f", - ":herb:": "\U0001f33f", - ":hibiscus:": "\U0001f33a", - ":high-heeled_shoe:": "\U0001f460", - ":high-speed_train:": "\U0001f684", - ":high_brightness:": "\U0001f506", - ":high_heel:": "\U0001f460", - ":high_voltage:": "\u26a1", - ":hiking_boot:": "\U0001f97e", - ":hindu_temple:": "\U0001f6d5", - ":hippopotamus:": "\U0001f99b", - ":hocho:": "\U0001f52a", - ":hockey:": "\U0001f3d2", - ":hole:": "\U0001f573\ufe0f", - ":hollow_red_circle:": "\u2b55", - ":homes:": "\U0001f3d8", - ":honduras:": "\U0001f1ed\U0001f1f3", - ":honey_pot:": "\U0001f36f", - ":honeybee:": "\U0001f41d", - ":hong_kong:": "\U0001f1ed\U0001f1f0", - ":hook:": "\U0001fa9d", - ":horizontal_traffic_light:": "\U0001f6a5", - ":horse:": "\U0001f434", - ":horse_face:": "\U0001f434", - ":horse_racing:": "\U0001f3c7", - ":horse_racing_tone1:": "\U0001f3c7\U0001f3fb", - ":horse_racing_tone2:": "\U0001f3c7\U0001f3fc", - ":horse_racing_tone3:": "\U0001f3c7\U0001f3fd", - ":horse_racing_tone4:": "\U0001f3c7\U0001f3fe", - ":horse_racing_tone5:": "\U0001f3c7\U0001f3ff", - ":hospital:": "\U0001f3e5", - ":hot_beverage:": "\u2615", - ":hot_dog:": "\U0001f32d", - ":hot_face:": "\U0001f975", - ":hot_pepper:": "\U0001f336\ufe0f", - ":hot_springs:": "\u2668", - ":hotdog:": "\U0001f32d", - ":hotel:": "\U0001f3e8", - ":hotsprings:": "\u2668\ufe0f", - ":hourglass:": "\u231b", - ":hourglass_done:": "\u231b", - ":hourglass_flowing_sand:": "\u23f3", - ":hourglass_not_done:": "\u23f3", - ":house:": "\U0001f3e0", - ":house_abandoned:": "\U0001f3da", - ":house_buildings:": "\U0001f3d8\ufe0f", - ":house_with_garden:": "\U0001f3e1", - ":houses:": "\U0001f3d8", - ":hugging:": "\U0001f917", - ":hugging_face:": "\U0001f917", - ":hugs:": "\U0001f917", - ":hundred_points:": "\U0001f4af", - ":hungary:": "\U0001f1ed\U0001f1fa", - ":hushed:": "\U0001f62f", - ":hushed_face:": "\U0001f62f", - ":hut:": "\U0001f6d6", - ":i_love_you_hand_sign:": "\U0001f91f", - ":ice:": "\U0001f9ca", - ":ice_cream:": "\U0001f368", - ":ice_cube:": "\U0001f9ca", - ":ice_hockey:": "\U0001f3d2", - ":ice_hockey_stick_and_puck:": "\U0001f3d2", - ":ice_skate:": "\u26f8\ufe0f", - ":icecream:": "\U0001f366", - ":iceland:": "\U0001f1ee\U0001f1f8", - ":id:": "\U0001f194", - ":ideograph_advantage:": "\U0001f250", - ":imp:": "\U0001f47f", - ":inbox_tray:": "\U0001f4e5", - ":incoming_envelope:": "\U0001f4e8", - ":index_pointing_up:": "\u261d", - ":india:": "\U0001f1ee\U0001f1f3", - ":indonesia:": "\U0001f1ee\U0001f1e9", - ":infinity:": "\u267e\ufe0f", - ":information:": "\u2139", - ":information_desk_person:": "\U0001f481\u200d\u2640\ufe0f", - ":information_source:": "\u2139\ufe0f", - ":innocent:": "\U0001f607", - ":input_latin_letters:": "\U0001f524", - ":input_latin_lowercase:": "\U0001f521", - ":input_latin_uppercase:": "\U0001f520", - ":input_numbers:": "\U0001f522", - ":input_symbols:": "\U0001f523", - ":interrobang:": "\u2049\ufe0f", - ":iphone:": "\U0001f4f1", - ":iran:": "\U0001f1ee\U0001f1f7", - ":iraq:": "\U0001f1ee\U0001f1f6", - ":ireland:": "\U0001f1ee\U0001f1ea", - ":island:": "\U0001f3dd", - ":isle_of_man:": "\U0001f1ee\U0001f1f2", - ":israel:": "\U0001f1ee\U0001f1f1", - ":it:": "\U0001f1ee\U0001f1f9", - ":izakaya_lantern:": "\U0001f3ee", - ":jack-o-lantern:": "\U0001f383", - ":jack_o_lantern:": "\U0001f383", - ":jamaica:": "\U0001f1ef\U0001f1f2", - ":japan:": "\U0001f5fe", - ":japanese_castle:": "\U0001f3ef", - ":japanese_goblin:": "\U0001f47a", - ":japanese_ogre:": "\U0001f479", - ":jeans:": "\U0001f456", - ":jersey:": "\U0001f1ef\U0001f1ea", - ":jigsaw:": "\U0001f9e9", - ":joker:": "\U0001f0cf", - ":jordan:": "\U0001f1ef\U0001f1f4", - ":joy:": "\U0001f602", - ":joy_cat:": "\U0001f639", - ":joystick:": "\U0001f579\ufe0f", - ":jp:": "\U0001f1ef\U0001f1f5", - ":judge:": "\U0001f9d1\u200d\u2696\ufe0f", - ":juggling:": "\U0001f939", - ":juggling_person:": "\U0001f939", - ":kaaba:": "\U0001f54b", - ":kangaroo:": "\U0001f998", - ":kazakhstan:": "\U0001f1f0\U0001f1ff", - ":kenya:": "\U0001f1f0\U0001f1ea", - ":key:": "\U0001f511", - ":key2:": "\U0001f5dd", - ":keyboard:": "\u2328\ufe0f", - ":keycap_#:": "#\ufe0f\u20e3", - ":keycap_*:": "*\ufe0f\u20e3", - ":keycap_0:": "0\ufe0f\u20e3", - ":keycap_1:": "1\ufe0f\u20e3", - ":keycap_10:": "\U0001f51f", - ":keycap_2:": "2\ufe0f\u20e3", - ":keycap_3:": "3\ufe0f\u20e3", - ":keycap_4:": "4\ufe0f\u20e3", - ":keycap_5:": "5\ufe0f\u20e3", - ":keycap_6:": "6\ufe0f\u20e3", - ":keycap_7:": "7\ufe0f\u20e3", - ":keycap_8:": "8\ufe0f\u20e3", - ":keycap_9:": "9\ufe0f\u20e3", - ":keycap_star:": "*\ufe0f\u20e3", - ":keycap_ten:": "\U0001f51f", - ":kick_scooter:": "\U0001f6f4", - ":kimono:": "\U0001f458", - ":kiribati:": "\U0001f1f0\U0001f1ee", - ":kiss:": "\U0001f48b", - ":kiss_man_man:": "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", - ":kiss_mark:": "\U0001f48b", - ":kiss_mm:": "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", - ":kiss_woman_man:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", - ":kiss_woman_woman:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469", - ":kiss_ww:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469", - ":kissing:": "\U0001f617", - ":kissing_cat:": "\U0001f63d", - ":kissing_closed_eyes:": "\U0001f61a", - ":kissing_face:": "\U0001f617", - ":kissing_face_with_closed_eyes:": "\U0001f61a", - ":kissing_face_with_smiling_eyes:": "\U0001f619", - ":kissing_heart:": "\U0001f618", - ":kissing_smiling_eyes:": "\U0001f619", - ":kitchen_knife:": "\U0001f52a", - ":kite:": "\U0001fa81", - ":kiwi:": "\U0001f95d", - ":kiwi_fruit:": "\U0001f95d", - ":kiwifruit:": "\U0001f95d", - ":kneeling_man:": "\U0001f9ce\u200d\u2642\ufe0f", - ":kneeling_person:": "\U0001f9ce", - ":kneeling_woman:": "\U0001f9ce\u200d\u2640\ufe0f", - ":knife:": "\U0001f52a", - ":knife_fork_plate:": "\U0001f37d\ufe0f", - ":knocked-out_face:": "\U0001f635", - ":knot:": "\U0001faa2", - ":koala:": "\U0001f428", - ":koko:": "\U0001f201", - ":kosovo:": "\U0001f1fd\U0001f1f0", - ":kr:": "\U0001f1f0\U0001f1f7", - ":kuwait:": "\U0001f1f0\U0001f1fc", - ":kyrgyzstan:": "\U0001f1f0\U0001f1ec", - ":lab_coat:": "\U0001f97c", - ":label:": "\U0001f3f7\ufe0f", - ":lacrosse:": "\U0001f94d", - ":ladder:": "\U0001fa9c", - ":lady_beetle:": "\U0001f41e", - ":ladybug:": "\U0001f41e", - ":lantern:": "\U0001f3ee", - ":laos:": "\U0001f1f1\U0001f1e6", - ":laptop:": "\U0001f4bb", - ":large_blue_circle:": "\U0001f535", - ":large_blue_diamond:": "\U0001f537", - ":large_blue_square:": "\U0001f7e6", - ":large_brown_circle:": "\U0001f7e4", - ":large_brown_square:": "\U0001f7eb", - ":large_green_circle:": "\U0001f7e2", - ":large_green_square:": "\U0001f7e9", - ":large_orange_circle:": "\U0001f7e0", - ":large_orange_diamond:": "\U0001f536", - ":large_orange_square:": "\U0001f7e7", - ":large_purple_circle:": "\U0001f7e3", - ":large_purple_square:": "\U0001f7ea", - ":large_red_square:": "\U0001f7e5", - ":large_yellow_circle:": "\U0001f7e1", - ":large_yellow_square:": "\U0001f7e8", - ":last_quarter_moon:": "\U0001f317", - ":last_quarter_moon_face:": "\U0001f31c", - ":last_quarter_moon_with_face:": "\U0001f31c", - ":last_track_button:": "\u23ee", - ":latin_cross:": "\u271d\ufe0f", - ":latvia:": "\U0001f1f1\U0001f1fb", - ":laughing:": "\U0001f606", - ":leaf_fluttering_in_wind:": "\U0001f343", - ":leafy_green:": "\U0001f96c", - ":leaves:": "\U0001f343", - ":lebanon:": "\U0001f1f1\U0001f1e7", - ":ledger:": "\U0001f4d2", - ":left-facing_fist:": "\U0001f91b", - ":left-right_arrow:": "\u2194", - ":left_arrow:": "\u2b05", - ":left_arrow_curving_right:": "\u21aa", - ":left_facing_fist:": "\U0001f91b", - ":left_facing_fist_tone1:": "\U0001f91b\U0001f3fb", - ":left_facing_fist_tone2:": "\U0001f91b\U0001f3fc", - ":left_facing_fist_tone3:": "\U0001f91b\U0001f3fd", - ":left_facing_fist_tone4:": "\U0001f91b\U0001f3fe", - ":left_facing_fist_tone5:": "\U0001f91b\U0001f3ff", - ":left_luggage:": "\U0001f6c5", - ":left_right_arrow:": "\u2194\ufe0f", - ":left_speech_bubble:": "\U0001f5e8\ufe0f", - ":leftwards_arrow_with_hook:": "\u21a9\ufe0f", - ":leg:": "\U0001f9b5", - ":lemon:": "\U0001f34b", - ":leo:": "\u264c", - ":leopard:": "\U0001f406", - ":lesotho:": "\U0001f1f1\U0001f1f8", - ":level_slider:": "\U0001f39a\ufe0f", - ":liberia:": "\U0001f1f1\U0001f1f7", - ":libra:": "\u264e", - ":libya:": "\U0001f1f1\U0001f1fe", - ":liechtenstein:": "\U0001f1f1\U0001f1ee", - ":light_bulb:": "\U0001f4a1", - ":light_rail:": "\U0001f688", - ":lightning:": "\U0001f329\ufe0f", - ":link:": "\U0001f517", - ":linked_paperclips:": "\U0001f587\ufe0f", - ":lion:": "\U0001f981", - ":lion_face:": "\U0001f981", - ":lips:": "\U0001f444", - ":lipstick:": "\U0001f484", - ":lithuania:": "\U0001f1f1\U0001f1f9", - ":litter_in_bin_sign:": "\U0001f6ae", - ":lizard:": "\U0001f98e", - ":llama:": "\U0001f999", - ":lobster:": "\U0001f99e", - ":lock:": "\U0001f512", - ":lock_with_ink_pen:": "\U0001f50f", - ":locked:": "\U0001f512", - ":locked_with_key:": "\U0001f510", - ":locked_with_pen:": "\U0001f50f", - ":locomotive:": "\U0001f682", - ":lollipop:": "\U0001f36d", - ":long_drum:": "\U0001fa98", - ":loop:": "\u27bf", - ":lotion_bottle:": "\U0001f9f4", - ":lotus_position:": "\U0001f9d8", - ":lotus_position_man:": "\U0001f9d8\u200d\u2642\ufe0f", - ":lotus_position_woman:": "\U0001f9d8\u200d\u2640\ufe0f", - ":loud_sound:": "\U0001f50a", - ":loudly_crying_face:": "\U0001f62d", - ":loudspeaker:": "\U0001f4e2", - ":love-you_gesture:": "\U0001f91f", - ":love_hotel:": "\U0001f3e9", - ":love_letter:": "\U0001f48c", - ":love_you_gesture:": "\U0001f91f", - ":love_you_gesture_tone1:": "\U0001f91f\U0001f3fb", - ":love_you_gesture_tone2:": "\U0001f91f\U0001f3fc", - ":love_you_gesture_tone3:": "\U0001f91f\U0001f3fd", - ":love_you_gesture_tone4:": "\U0001f91f\U0001f3fe", - ":love_you_gesture_tone5:": "\U0001f91f\U0001f3ff", - ":low_brightness:": "\U0001f505", - ":lower_left_ballpoint_pen:": "\U0001f58a\ufe0f", - ":lower_left_crayon:": "\U0001f58d\ufe0f", - ":lower_left_fountain_pen:": "\U0001f58b\ufe0f", - ":lower_left_paintbrush:": "\U0001f58c\ufe0f", - ":luggage:": "\U0001f9f3", - ":lungs:": "\U0001fac1", - ":luxembourg:": "\U0001f1f1\U0001f1fa", - ":lying_face:": "\U0001f925", - ":m:": "\u24dc\ufe0f", - ":macau:": "\U0001f1f2\U0001f1f4", - ":macedonia:": "\U0001f1f2\U0001f1f0", - ":madagascar:": "\U0001f1f2\U0001f1ec", - ":mag:": "\U0001f50d", - ":mag_right:": "\U0001f50e", - ":mage:": "\U0001f9d9\u200d\u2640\ufe0f", - ":mage_man:": "\U0001f9d9\u200d\u2642\ufe0f", - ":mage_tone1:": "\U0001f9d9\U0001f3fb", - ":mage_tone2:": "\U0001f9d9\U0001f3fc", - ":mage_tone3:": "\U0001f9d9\U0001f3fd", - ":mage_tone4:": "\U0001f9d9\U0001f3fe", - ":mage_tone5:": "\U0001f9d9\U0001f3ff", - ":mage_woman:": "\U0001f9d9\u200d\u2640\ufe0f", - ":magic_wand:": "\U0001fa84", - ":magnet:": "\U0001f9f2", - ":magnifying_glass_tilted_left:": "\U0001f50d", - ":magnifying_glass_tilted_right:": "\U0001f50e", - ":mahjong:": "\U0001f004", - ":mahjong_red_dragon:": "\U0001f004", - ":mailbox:": "\U0001f4eb", - ":mailbox_closed:": "\U0001f4ea", - ":mailbox_with_mail:": "\U0001f4ec", - ":mailbox_with_no_mail:": "\U0001f4ed", - ":malawi:": "\U0001f1f2\U0001f1fc", - ":malaysia:": "\U0001f1f2\U0001f1fe", - ":maldives:": "\U0001f1f2\U0001f1fb", - ":male-artist:": "\U0001f468\u200d\U0001f3a8", - ":male-astronaut:": "\U0001f468\u200d\U0001f680", - ":male-construction-worker:": "\U0001f477\u200d\u2642\ufe0f", - ":male-cook:": "\U0001f468\u200d\U0001f373", - ":male-detective:": "\U0001f575\ufe0f\u200d\u2642\ufe0f", - ":male-doctor:": "\U0001f468\u200d\u2695\ufe0f", - ":male-factory-worker:": "\U0001f468\u200d\U0001f3ed", - ":male-farmer:": "\U0001f468\u200d\U0001f33e", - ":male-firefighter:": "\U0001f468\u200d\U0001f692", - ":male-guard:": "\U0001f482\u200d\u2642\ufe0f", - ":male-judge:": "\U0001f468\u200d\u2696\ufe0f", - ":male-mechanic:": "\U0001f468\u200d\U0001f527", - ":male-office-worker:": "\U0001f468\u200d\U0001f4bc", - ":male-pilot:": "\U0001f468\u200d\u2708\ufe0f", - ":male-police-officer:": "\U0001f46e\u200d\u2642\ufe0f", - ":male-scientist:": "\U0001f468\u200d\U0001f52c", - ":male-singer:": "\U0001f468\u200d\U0001f3a4", - ":male-student:": "\U0001f468\u200d\U0001f393", - ":male-teacher:": "\U0001f468\u200d\U0001f3eb", - ":male-technologist:": "\U0001f468\u200d\U0001f4bb", - ":male_detective:": "\U0001f575\ufe0f\u200d\u2642\ufe0f", - ":male_elf:": "\U0001f9dd\u200d\u2642\ufe0f", - ":male_fairy:": "\U0001f9da\u200d\u2642\ufe0f", - ":male_genie:": "\U0001f9de\u200d\u2642\ufe0f", - ":male_mage:": "\U0001f9d9\u200d\u2642\ufe0f", - ":male_sign:": "\u2642\ufe0f", - ":male_superhero:": "\U0001f9b8\u200d\u2642\ufe0f", - ":male_supervillain:": "\U0001f9b9\u200d\u2642\ufe0f", - ":male_vampire:": "\U0001f9db\u200d\u2642\ufe0f", - ":male_zombie:": "\U0001f9df\u200d\u2642\ufe0f", - ":mali:": "\U0001f1f2\U0001f1f1", - ":malta:": "\U0001f1f2\U0001f1f9", - ":mammoth:": "\U0001f9a3", - ":man:": "\U0001f468", - ":man-biking:": "\U0001f6b4\u200d\u2642\ufe0f", - ":man-bouncing-ball:": "\u26f9\ufe0f\u200d\u2642\ufe0f", - ":man-bowing:": "\U0001f647\u200d\u2642\ufe0f", - ":man-boy:": "\U0001f468\u200d\U0001f466", - ":man-boy-boy:": "\U0001f468\u200d\U0001f466\u200d\U0001f466", - ":man-cartwheeling:": "\U0001f938\u200d\u2642\ufe0f", - ":man-facepalming:": "\U0001f926\u200d\u2642\ufe0f", - ":man-frowning:": "\U0001f64d\u200d\u2642\ufe0f", - ":man-gesturing-no:": "\U0001f645\u200d\u2642\ufe0f", - ":man-gesturing-ok:": "\U0001f646\u200d\u2642\ufe0f", - ":man-getting-haircut:": "\U0001f487\u200d\u2642\ufe0f", - ":man-getting-massage:": "\U0001f486\u200d\u2642\ufe0f", - ":man-girl:": "\U0001f468\u200d\U0001f467", - ":man-girl-boy:": "\U0001f468\u200d\U0001f467\u200d\U0001f466", - ":man-girl-girl:": "\U0001f468\u200d\U0001f467\u200d\U0001f467", - ":man-golfing:": "\U0001f3cc\ufe0f\u200d\u2642\ufe0f", - ":man-heart-man:": "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468", - ":man-juggling:": "\U0001f939\u200d\u2642\ufe0f", - ":man-kiss-man:": "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", - ":man-lifting-weights:": "\U0001f3cb\ufe0f\u200d\u2642\ufe0f", - ":man-man-boy:": "\U0001f468\u200d\U0001f468\u200d\U0001f466", - ":man-man-boy-boy:": "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466", - ":man-man-girl:": "\U0001f468\u200d\U0001f468\u200d\U0001f467", - ":man-man-girl-boy:": "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466", - ":man-man-girl-girl:": "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467", - ":man-mountain-biking:": "\U0001f6b5\u200d\u2642\ufe0f", - ":man-playing-handball:": "\U0001f93e\u200d\u2642\ufe0f", - ":man-playing-water-polo:": "\U0001f93d\u200d\u2642\ufe0f", - ":man-pouting:": "\U0001f64e\u200d\u2642\ufe0f", - ":man-raising-hand:": "\U0001f64b\u200d\u2642\ufe0f", - ":man-rowing-boat:": "\U0001f6a3\u200d\u2642\ufe0f", - ":man-running:": "\U0001f3c3\u200d\u2642\ufe0f", - ":man-shrugging:": "\U0001f937\u200d\u2642\ufe0f", - ":man-surfing:": "\U0001f3c4\u200d\u2642\ufe0f", - ":man-swimming:": "\U0001f3ca\u200d\u2642\ufe0f", - ":man-tipping-hand:": "\U0001f481\u200d\u2642\ufe0f", - ":man-walking:": "\U0001f6b6\u200d\u2642\ufe0f", - ":man-wearing-turban:": "\U0001f473\u200d\u2642\ufe0f", - ":man-with-bunny-ears-partying:": "\U0001f46f\u200d\u2642\ufe0f", - ":man-woman-boy:": "\U0001f468\u200d\U0001f469\u200d\U0001f466", - ":man-woman-boy-boy:": "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466", - ":man-woman-girl:": "\U0001f468\u200d\U0001f469\u200d\U0001f467", - ":man-woman-girl-boy:": "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466", - ":man-woman-girl-girl:": "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467", - ":man-wrestling:": "\U0001f93c\u200d\u2642\ufe0f", - ":man_and_woman_holding_hands:": "\U0001f46b", - ":man_artist:": "\U0001f468\u200d\U0001f3a8", - ":man_artist_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f3a8", - ":man_artist_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f3a8", - ":man_artist_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f3a8", - ":man_artist_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f3a8", - ":man_artist_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f3a8", - ":man_astronaut:": "\U0001f468\u200d\U0001f680", - ":man_astronaut_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f680", - ":man_astronaut_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f680", - ":man_astronaut_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f680", - ":man_astronaut_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f680", - ":man_astronaut_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f680", - ":man_bald:": "\U0001f468\u200d\U0001f9b2", - ":man_beard:": "\U0001f9d4\u200d\u2642\ufe0f", - ":man_biking:": "\U0001f6b4\u200d\u2642\ufe0f", - ":man_biking_tone1:": "\U0001f6b4\U0001f3fb\u200d\u2642\ufe0f", - ":man_biking_tone2:": "\U0001f6b4\U0001f3fc\u200d\u2642\ufe0f", - ":man_biking_tone3:": "\U0001f6b4\U0001f3fd\u200d\u2642\ufe0f", - ":man_biking_tone4:": "\U0001f6b4\U0001f3fe\u200d\u2642\ufe0f", - ":man_biking_tone5:": "\U0001f6b4\U0001f3ff\u200d\u2642\ufe0f", - ":man_blond_hair:": "\U0001f471\u200d\u2642\ufe0f", - ":man_bouncing_ball:": "\u26f9\ufe0f\u200d\u2642\ufe0f", - ":man_bouncing_ball_tone1:": "\u26f9\U0001f3fb\u200d\u2642\ufe0f", - ":man_bouncing_ball_tone2:": "\u26f9\U0001f3fc\u200d\u2642\ufe0f", - ":man_bouncing_ball_tone3:": "\u26f9\U0001f3fd\u200d\u2642\ufe0f", - ":man_bouncing_ball_tone4:": "\u26f9\U0001f3fe\u200d\u2642\ufe0f", - ":man_bouncing_ball_tone5:": "\u26f9\U0001f3ff\u200d\u2642\ufe0f", - ":man_bowing:": "\U0001f647\u200d\u2642\ufe0f", - ":man_bowing_tone1:": "\U0001f647\U0001f3fb\u200d\u2642\ufe0f", - ":man_bowing_tone2:": "\U0001f647\U0001f3fc\u200d\u2642\ufe0f", - ":man_bowing_tone3:": "\U0001f647\U0001f3fd\u200d\u2642\ufe0f", - ":man_bowing_tone4:": "\U0001f647\U0001f3fe\u200d\u2642\ufe0f", - ":man_bowing_tone5:": "\U0001f647\U0001f3ff\u200d\u2642\ufe0f", - ":man_cartwheeling:": "\U0001f938\u200d\u2642\ufe0f", - ":man_cartwheeling_tone1:": "\U0001f938\U0001f3fb\u200d\u2642\ufe0f", - ":man_cartwheeling_tone2:": "\U0001f938\U0001f3fc\u200d\u2642\ufe0f", - ":man_cartwheeling_tone3:": "\U0001f938\U0001f3fd\u200d\u2642\ufe0f", - ":man_cartwheeling_tone4:": "\U0001f938\U0001f3fe\u200d\u2642\ufe0f", - ":man_cartwheeling_tone5:": "\U0001f938\U0001f3ff\u200d\u2642\ufe0f", - ":man_climbing:": "\U0001f9d7\u200d\u2642\ufe0f", - ":man_climbing_tone1:": "\U0001f9d7\U0001f3fb\u200d\u2642\ufe0f", - ":man_climbing_tone2:": "\U0001f9d7\U0001f3fc\u200d\u2642\ufe0f", - ":man_climbing_tone3:": "\U0001f9d7\U0001f3fd\u200d\u2642\ufe0f", - ":man_climbing_tone4:": "\U0001f9d7\U0001f3fe\u200d\u2642\ufe0f", - ":man_climbing_tone5:": "\U0001f9d7\U0001f3ff\u200d\u2642\ufe0f", - ":man_construction_worker:": "\U0001f477\u200d\u2642\ufe0f", - ":man_construction_worker_tone1:": "\U0001f477\U0001f3fb\u200d\u2642\ufe0f", - ":man_construction_worker_tone2:": "\U0001f477\U0001f3fc\u200d\u2642\ufe0f", - ":man_construction_worker_tone3:": "\U0001f477\U0001f3fd\u200d\u2642\ufe0f", - ":man_construction_worker_tone4:": "\U0001f477\U0001f3fe\u200d\u2642\ufe0f", - ":man_construction_worker_tone5:": "\U0001f477\U0001f3ff\u200d\u2642\ufe0f", - ":man_cook:": "\U0001f468\u200d\U0001f373", - ":man_cook_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f373", - ":man_cook_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f373", - ":man_cook_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f373", - ":man_cook_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f373", - ":man_cook_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f373", - ":man_curly_hair:": "\U0001f468\u200d\U0001f9b1", - ":man_dancing:": "\U0001f57a", - ":man_dancing_tone1:": "\U0001f57a\U0001f3fb", - ":man_dancing_tone2:": "\U0001f57a\U0001f3fc", - ":man_dancing_tone3:": "\U0001f57a\U0001f3fd", - ":man_dancing_tone4:": "\U0001f57a\U0001f3fe", - ":man_dancing_tone5:": "\U0001f57a\U0001f3ff", - ":man_detective:": "\U0001f575\ufe0f\u200d\u2642\ufe0f", - ":man_detective_tone1:": "\U0001f575\U0001f3fb\u200d\u2642\ufe0f", - ":man_detective_tone2:": "\U0001f575\U0001f3fc\u200d\u2642\ufe0f", - ":man_detective_tone3:": "\U0001f575\U0001f3fd\u200d\u2642\ufe0f", - ":man_detective_tone4:": "\U0001f575\U0001f3fe\u200d\u2642\ufe0f", - ":man_detective_tone5:": "\U0001f575\U0001f3ff\u200d\u2642\ufe0f", - ":man_elf:": "\U0001f9dd\u200d\u2642\ufe0f", - ":man_elf_tone1:": "\U0001f9dd\U0001f3fb\u200d\u2642\ufe0f", - ":man_elf_tone2:": "\U0001f9dd\U0001f3fc\u200d\u2642\ufe0f", - ":man_elf_tone3:": "\U0001f9dd\U0001f3fd\u200d\u2642\ufe0f", - ":man_elf_tone4:": "\U0001f9dd\U0001f3fe\u200d\u2642\ufe0f", - ":man_elf_tone5:": "\U0001f9dd\U0001f3ff\u200d\u2642\ufe0f", - ":man_facepalming:": "\U0001f926\u200d\u2642\ufe0f", - ":man_facepalming_tone1:": "\U0001f926\U0001f3fb\u200d\u2642\ufe0f", - ":man_facepalming_tone2:": "\U0001f926\U0001f3fc\u200d\u2642\ufe0f", - ":man_facepalming_tone3:": "\U0001f926\U0001f3fd\u200d\u2642\ufe0f", - ":man_facepalming_tone4:": "\U0001f926\U0001f3fe\u200d\u2642\ufe0f", - ":man_facepalming_tone5:": "\U0001f926\U0001f3ff\u200d\u2642\ufe0f", - ":man_factory_worker:": "\U0001f468\u200d\U0001f3ed", - ":man_factory_worker_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f3ed", - ":man_factory_worker_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f3ed", - ":man_factory_worker_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f3ed", - ":man_factory_worker_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f3ed", - ":man_factory_worker_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f3ed", - ":man_fairy:": "\U0001f9da\u200d\u2642\ufe0f", - ":man_fairy_tone1:": "\U0001f9da\U0001f3fb\u200d\u2642\ufe0f", - ":man_fairy_tone2:": "\U0001f9da\U0001f3fc\u200d\u2642\ufe0f", - ":man_fairy_tone3:": "\U0001f9da\U0001f3fd\u200d\u2642\ufe0f", - ":man_fairy_tone4:": "\U0001f9da\U0001f3fe\u200d\u2642\ufe0f", - ":man_fairy_tone5:": "\U0001f9da\U0001f3ff\u200d\u2642\ufe0f", - ":man_farmer:": "\U0001f468\u200d\U0001f33e", - ":man_farmer_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f33e", - ":man_farmer_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f33e", - ":man_farmer_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f33e", - ":man_farmer_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f33e", - ":man_farmer_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f33e", - ":man_feeding_baby:": "\U0001f468\u200d\U0001f37c", - ":man_firefighter:": "\U0001f468\u200d\U0001f692", - ":man_firefighter_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f692", - ":man_firefighter_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f692", - ":man_firefighter_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f692", - ":man_firefighter_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f692", - ":man_firefighter_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f692", - ":man_frowning:": "\U0001f64d\u200d\u2642\ufe0f", - ":man_frowning_tone1:": "\U0001f64d\U0001f3fb\u200d\u2642\ufe0f", - ":man_frowning_tone2:": "\U0001f64d\U0001f3fc\u200d\u2642\ufe0f", - ":man_frowning_tone3:": "\U0001f64d\U0001f3fd\u200d\u2642\ufe0f", - ":man_frowning_tone4:": "\U0001f64d\U0001f3fe\u200d\u2642\ufe0f", - ":man_frowning_tone5:": "\U0001f64d\U0001f3ff\u200d\u2642\ufe0f", - ":man_genie:": "\U0001f9de\u200d\u2642\ufe0f", - ":man_gesturing_NO:": "\U0001f645\u200d\u2642\ufe0f", - ":man_gesturing_OK:": "\U0001f646\u200d\u2642\ufe0f", - ":man_gesturing_no:": "\U0001f645\u200d\u2642\ufe0f", - ":man_gesturing_no_tone1:": "\U0001f645\U0001f3fb\u200d\u2642\ufe0f", - ":man_gesturing_no_tone2:": "\U0001f645\U0001f3fc\u200d\u2642\ufe0f", - ":man_gesturing_no_tone3:": "\U0001f645\U0001f3fd\u200d\u2642\ufe0f", - ":man_gesturing_no_tone4:": "\U0001f645\U0001f3fe\u200d\u2642\ufe0f", - ":man_gesturing_no_tone5:": "\U0001f645\U0001f3ff\u200d\u2642\ufe0f", - ":man_gesturing_ok:": "\U0001f646\u200d\u2642\ufe0f", - ":man_gesturing_ok_tone1:": "\U0001f646\U0001f3fb\u200d\u2642\ufe0f", - ":man_gesturing_ok_tone2:": "\U0001f646\U0001f3fc\u200d\u2642\ufe0f", - ":man_gesturing_ok_tone3:": "\U0001f646\U0001f3fd\u200d\u2642\ufe0f", - ":man_gesturing_ok_tone4:": "\U0001f646\U0001f3fe\u200d\u2642\ufe0f", - ":man_gesturing_ok_tone5:": "\U0001f646\U0001f3ff\u200d\u2642\ufe0f", - ":man_getting_face_massage:": "\U0001f486\u200d\u2642\ufe0f", - ":man_getting_face_massage_tone1:": "\U0001f486\U0001f3fb\u200d\u2642\ufe0f", - ":man_getting_face_massage_tone2:": "\U0001f486\U0001f3fc\u200d\u2642\ufe0f", - ":man_getting_face_massage_tone3:": "\U0001f486\U0001f3fd\u200d\u2642\ufe0f", - ":man_getting_face_massage_tone4:": "\U0001f486\U0001f3fe\u200d\u2642\ufe0f", - ":man_getting_face_massage_tone5:": "\U0001f486\U0001f3ff\u200d\u2642\ufe0f", - ":man_getting_haircut:": "\U0001f487\u200d\u2642\ufe0f", - ":man_getting_haircut_tone1:": "\U0001f487\U0001f3fb\u200d\u2642\ufe0f", - ":man_getting_haircut_tone2:": "\U0001f487\U0001f3fc\u200d\u2642\ufe0f", - ":man_getting_haircut_tone3:": "\U0001f487\U0001f3fd\u200d\u2642\ufe0f", - ":man_getting_haircut_tone4:": "\U0001f487\U0001f3fe\u200d\u2642\ufe0f", - ":man_getting_haircut_tone5:": "\U0001f487\U0001f3ff\u200d\u2642\ufe0f", - ":man_getting_massage:": "\U0001f486\u200d\u2642\ufe0f", - ":man_golfing:": "\U0001f3cc\ufe0f\u200d\u2642\ufe0f", - ":man_golfing_tone1:": "\U0001f3cc\U0001f3fb\u200d\u2642\ufe0f", - ":man_golfing_tone2:": "\U0001f3cc\U0001f3fc\u200d\u2642\ufe0f", - ":man_golfing_tone3:": "\U0001f3cc\U0001f3fd\u200d\u2642\ufe0f", - ":man_golfing_tone4:": "\U0001f3cc\U0001f3fe\u200d\u2642\ufe0f", - ":man_golfing_tone5:": "\U0001f3cc\U0001f3ff\u200d\u2642\ufe0f", - ":man_guard:": "\U0001f482\u200d\u2642\ufe0f", - ":man_guard_tone1:": "\U0001f482\U0001f3fb\u200d\u2642\ufe0f", - ":man_guard_tone2:": "\U0001f482\U0001f3fc\u200d\u2642\ufe0f", - ":man_guard_tone3:": "\U0001f482\U0001f3fd\u200d\u2642\ufe0f", - ":man_guard_tone4:": "\U0001f482\U0001f3fe\u200d\u2642\ufe0f", - ":man_guard_tone5:": "\U0001f482\U0001f3ff\u200d\u2642\ufe0f", - ":man_health_worker:": "\U0001f468\u200d\u2695\ufe0f", - ":man_health_worker_tone1:": "\U0001f468\U0001f3fb\u200d\u2695\ufe0f", - ":man_health_worker_tone2:": "\U0001f468\U0001f3fc\u200d\u2695\ufe0f", - ":man_health_worker_tone3:": "\U0001f468\U0001f3fd\u200d\u2695\ufe0f", - ":man_health_worker_tone4:": "\U0001f468\U0001f3fe\u200d\u2695\ufe0f", - ":man_health_worker_tone5:": "\U0001f468\U0001f3ff\u200d\u2695\ufe0f", - ":man_in_business_suit_levitating:": "\U0001f574\ufe0f", - ":man_in_business_suit_levitating_tone1:": "\U0001f574\U0001f3fb", - ":man_in_business_suit_levitating_tone2:": "\U0001f574\U0001f3fc", - ":man_in_business_suit_levitating_tone3:": "\U0001f574\U0001f3fd", - ":man_in_business_suit_levitating_tone4:": "\U0001f574\U0001f3fe", - ":man_in_business_suit_levitating_tone5:": "\U0001f574\U0001f3ff", - ":man_in_lotus_position:": "\U0001f9d8\u200d\u2642\ufe0f", - ":man_in_lotus_position_tone1:": "\U0001f9d8\U0001f3fb\u200d\u2642\ufe0f", - ":man_in_lotus_position_tone2:": "\U0001f9d8\U0001f3fc\u200d\u2642\ufe0f", - ":man_in_lotus_position_tone3:": "\U0001f9d8\U0001f3fd\u200d\u2642\ufe0f", - ":man_in_lotus_position_tone4:": "\U0001f9d8\U0001f3fe\u200d\u2642\ufe0f", - ":man_in_lotus_position_tone5:": "\U0001f9d8\U0001f3ff\u200d\u2642\ufe0f", - ":man_in_manual_wheelchair:": "\U0001f468\u200d\U0001f9bd", - ":man_in_motorized_wheelchair:": "\U0001f468\u200d\U0001f9bc", - ":man_in_steamy_room:": "\U0001f9d6\u200d\u2642\ufe0f", - ":man_in_steamy_room_tone1:": "\U0001f9d6\U0001f3fb\u200d\u2642\ufe0f", - ":man_in_steamy_room_tone2:": "\U0001f9d6\U0001f3fc\u200d\u2642\ufe0f", - ":man_in_steamy_room_tone3:": "\U0001f9d6\U0001f3fd\u200d\u2642\ufe0f", - ":man_in_steamy_room_tone4:": "\U0001f9d6\U0001f3fe\u200d\u2642\ufe0f", - ":man_in_steamy_room_tone5:": "\U0001f9d6\U0001f3ff\u200d\u2642\ufe0f", - ":man_in_tuxedo:": "\U0001f935\u200d\u2642\ufe0f", - ":man_in_tuxedo_tone1:": "\U0001f935\U0001f3fb", - ":man_in_tuxedo_tone2:": "\U0001f935\U0001f3fc", - ":man_in_tuxedo_tone3:": "\U0001f935\U0001f3fd", - ":man_in_tuxedo_tone4:": "\U0001f935\U0001f3fe", - ":man_in_tuxedo_tone5:": "\U0001f935\U0001f3ff", - ":man_judge:": "\U0001f468\u200d\u2696\ufe0f", - ":man_judge_tone1:": "\U0001f468\U0001f3fb\u200d\u2696\ufe0f", - ":man_judge_tone2:": "\U0001f468\U0001f3fc\u200d\u2696\ufe0f", - ":man_judge_tone3:": "\U0001f468\U0001f3fd\u200d\u2696\ufe0f", - ":man_judge_tone4:": "\U0001f468\U0001f3fe\u200d\u2696\ufe0f", - ":man_judge_tone5:": "\U0001f468\U0001f3ff\u200d\u2696\ufe0f", - ":man_juggling:": "\U0001f939\u200d\u2642\ufe0f", - ":man_juggling_tone1:": "\U0001f939\U0001f3fb\u200d\u2642\ufe0f", - ":man_juggling_tone2:": "\U0001f939\U0001f3fc\u200d\u2642\ufe0f", - ":man_juggling_tone3:": "\U0001f939\U0001f3fd\u200d\u2642\ufe0f", - ":man_juggling_tone4:": "\U0001f939\U0001f3fe\u200d\u2642\ufe0f", - ":man_juggling_tone5:": "\U0001f939\U0001f3ff\u200d\u2642\ufe0f", - ":man_kneeling:": "\U0001f9ce\u200d\u2642\ufe0f", - ":man_lifting_weights:": "\U0001f3cb\ufe0f\u200d\u2642\ufe0f", - ":man_lifting_weights_tone1:": "\U0001f3cb\U0001f3fb\u200d\u2642\ufe0f", - ":man_lifting_weights_tone2:": "\U0001f3cb\U0001f3fc\u200d\u2642\ufe0f", - ":man_lifting_weights_tone3:": "\U0001f3cb\U0001f3fd\u200d\u2642\ufe0f", - ":man_lifting_weights_tone4:": "\U0001f3cb\U0001f3fe\u200d\u2642\ufe0f", - ":man_lifting_weights_tone5:": "\U0001f3cb\U0001f3ff\u200d\u2642\ufe0f", - ":man_mage:": "\U0001f9d9\u200d\u2642\ufe0f", - ":man_mage_tone1:": "\U0001f9d9\U0001f3fb\u200d\u2642\ufe0f", - ":man_mage_tone2:": "\U0001f9d9\U0001f3fc\u200d\u2642\ufe0f", - ":man_mage_tone3:": "\U0001f9d9\U0001f3fd\u200d\u2642\ufe0f", - ":man_mage_tone4:": "\U0001f9d9\U0001f3fe\u200d\u2642\ufe0f", - ":man_mage_tone5:": "\U0001f9d9\U0001f3ff\u200d\u2642\ufe0f", - ":man_mechanic:": "\U0001f468\u200d\U0001f527", - ":man_mechanic_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f527", - ":man_mechanic_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f527", - ":man_mechanic_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f527", - ":man_mechanic_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f527", - ":man_mechanic_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f527", - ":man_mountain_biking:": "\U0001f6b5\u200d\u2642\ufe0f", - ":man_mountain_biking_tone1:": "\U0001f6b5\U0001f3fb\u200d\u2642\ufe0f", - ":man_mountain_biking_tone2:": "\U0001f6b5\U0001f3fc\u200d\u2642\ufe0f", - ":man_mountain_biking_tone3:": "\U0001f6b5\U0001f3fd\u200d\u2642\ufe0f", - ":man_mountain_biking_tone4:": "\U0001f6b5\U0001f3fe\u200d\u2642\ufe0f", - ":man_mountain_biking_tone5:": "\U0001f6b5\U0001f3ff\u200d\u2642\ufe0f", - ":man_office_worker:": "\U0001f468\u200d\U0001f4bc", - ":man_office_worker_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f4bc", - ":man_office_worker_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f4bc", - ":man_office_worker_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f4bc", - ":man_office_worker_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f4bc", - ":man_office_worker_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f4bc", - ":man_pilot:": "\U0001f468\u200d\u2708\ufe0f", - ":man_pilot_tone1:": "\U0001f468\U0001f3fb\u200d\u2708\ufe0f", - ":man_pilot_tone2:": "\U0001f468\U0001f3fc\u200d\u2708\ufe0f", - ":man_pilot_tone3:": "\U0001f468\U0001f3fd\u200d\u2708\ufe0f", - ":man_pilot_tone4:": "\U0001f468\U0001f3fe\u200d\u2708\ufe0f", - ":man_pilot_tone5:": "\U0001f468\U0001f3ff\u200d\u2708\ufe0f", - ":man_playing_handball:": "\U0001f93e\u200d\u2642\ufe0f", - ":man_playing_handball_tone1:": "\U0001f93e\U0001f3fb\u200d\u2642\ufe0f", - ":man_playing_handball_tone2:": "\U0001f93e\U0001f3fc\u200d\u2642\ufe0f", - ":man_playing_handball_tone3:": "\U0001f93e\U0001f3fd\u200d\u2642\ufe0f", - ":man_playing_handball_tone4:": "\U0001f93e\U0001f3fe\u200d\u2642\ufe0f", - ":man_playing_handball_tone5:": "\U0001f93e\U0001f3ff\u200d\u2642\ufe0f", - ":man_playing_water_polo:": "\U0001f93d\u200d\u2642\ufe0f", - ":man_playing_water_polo_tone1:": "\U0001f93d\U0001f3fb\u200d\u2642\ufe0f", - ":man_playing_water_polo_tone2:": "\U0001f93d\U0001f3fc\u200d\u2642\ufe0f", - ":man_playing_water_polo_tone3:": "\U0001f93d\U0001f3fd\u200d\u2642\ufe0f", - ":man_playing_water_polo_tone4:": "\U0001f93d\U0001f3fe\u200d\u2642\ufe0f", - ":man_playing_water_polo_tone5:": "\U0001f93d\U0001f3ff\u200d\u2642\ufe0f", - ":man_police_officer:": "\U0001f46e\u200d\u2642\ufe0f", - ":man_police_officer_tone1:": "\U0001f46e\U0001f3fb\u200d\u2642\ufe0f", - ":man_police_officer_tone2:": "\U0001f46e\U0001f3fc\u200d\u2642\ufe0f", - ":man_police_officer_tone3:": "\U0001f46e\U0001f3fd\u200d\u2642\ufe0f", - ":man_police_officer_tone4:": "\U0001f46e\U0001f3fe\u200d\u2642\ufe0f", - ":man_police_officer_tone5:": "\U0001f46e\U0001f3ff\u200d\u2642\ufe0f", - ":man_pouting:": "\U0001f64e\u200d\u2642\ufe0f", - ":man_pouting_tone1:": "\U0001f64e\U0001f3fb\u200d\u2642\ufe0f", - ":man_pouting_tone2:": "\U0001f64e\U0001f3fc\u200d\u2642\ufe0f", - ":man_pouting_tone3:": "\U0001f64e\U0001f3fd\u200d\u2642\ufe0f", - ":man_pouting_tone4:": "\U0001f64e\U0001f3fe\u200d\u2642\ufe0f", - ":man_pouting_tone5:": "\U0001f64e\U0001f3ff\u200d\u2642\ufe0f", - ":man_raising_hand:": "\U0001f64b\u200d\u2642\ufe0f", - ":man_raising_hand_tone1:": "\U0001f64b\U0001f3fb\u200d\u2642\ufe0f", - ":man_raising_hand_tone2:": "\U0001f64b\U0001f3fc\u200d\u2642\ufe0f", - ":man_raising_hand_tone3:": "\U0001f64b\U0001f3fd\u200d\u2642\ufe0f", - ":man_raising_hand_tone4:": "\U0001f64b\U0001f3fe\u200d\u2642\ufe0f", - ":man_raising_hand_tone5:": "\U0001f64b\U0001f3ff\u200d\u2642\ufe0f", - ":man_red_hair:": "\U0001f468\u200d\U0001f9b0", - ":man_rowing_boat:": "\U0001f6a3\u200d\u2642\ufe0f", - ":man_rowing_boat_tone1:": "\U0001f6a3\U0001f3fb\u200d\u2642\ufe0f", - ":man_rowing_boat_tone2:": "\U0001f6a3\U0001f3fc\u200d\u2642\ufe0f", - ":man_rowing_boat_tone3:": "\U0001f6a3\U0001f3fd\u200d\u2642\ufe0f", - ":man_rowing_boat_tone4:": "\U0001f6a3\U0001f3fe\u200d\u2642\ufe0f", - ":man_rowing_boat_tone5:": "\U0001f6a3\U0001f3ff\u200d\u2642\ufe0f", - ":man_running:": "\U0001f3c3\u200d\u2642\ufe0f", - ":man_running_tone1:": "\U0001f3c3\U0001f3fb\u200d\u2642\ufe0f", - ":man_running_tone2:": "\U0001f3c3\U0001f3fc\u200d\u2642\ufe0f", - ":man_running_tone3:": "\U0001f3c3\U0001f3fd\u200d\u2642\ufe0f", - ":man_running_tone4:": "\U0001f3c3\U0001f3fe\u200d\u2642\ufe0f", - ":man_running_tone5:": "\U0001f3c3\U0001f3ff\u200d\u2642\ufe0f", - ":man_scientist:": "\U0001f468\u200d\U0001f52c", - ":man_scientist_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f52c", - ":man_scientist_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f52c", - ":man_scientist_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f52c", - ":man_scientist_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f52c", - ":man_scientist_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f52c", - ":man_shrugging:": "\U0001f937\u200d\u2642\ufe0f", - ":man_shrugging_tone1:": "\U0001f937\U0001f3fb\u200d\u2642\ufe0f", - ":man_shrugging_tone2:": "\U0001f937\U0001f3fc\u200d\u2642\ufe0f", - ":man_shrugging_tone3:": "\U0001f937\U0001f3fd\u200d\u2642\ufe0f", - ":man_shrugging_tone4:": "\U0001f937\U0001f3fe\u200d\u2642\ufe0f", - ":man_shrugging_tone5:": "\U0001f937\U0001f3ff\u200d\u2642\ufe0f", - ":man_singer:": "\U0001f468\u200d\U0001f3a4", - ":man_singer_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f3a4", - ":man_singer_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f3a4", - ":man_singer_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f3a4", - ":man_singer_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f3a4", - ":man_singer_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f3a4", - ":man_standing:": "\U0001f9cd\u200d\u2642\ufe0f", - ":man_student:": "\U0001f468\u200d\U0001f393", - ":man_student_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f393", - ":man_student_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f393", - ":man_student_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f393", - ":man_student_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f393", - ":man_student_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f393", - ":man_superhero:": "\U0001f9b8\u200d\u2642\ufe0f", - ":man_supervillain:": "\U0001f9b9\u200d\u2642\ufe0f", - ":man_surfing:": "\U0001f3c4\u200d\u2642\ufe0f", - ":man_surfing_tone1:": "\U0001f3c4\U0001f3fb\u200d\u2642\ufe0f", - ":man_surfing_tone2:": "\U0001f3c4\U0001f3fc\u200d\u2642\ufe0f", - ":man_surfing_tone3:": "\U0001f3c4\U0001f3fd\u200d\u2642\ufe0f", - ":man_surfing_tone4:": "\U0001f3c4\U0001f3fe\u200d\u2642\ufe0f", - ":man_surfing_tone5:": "\U0001f3c4\U0001f3ff\u200d\u2642\ufe0f", - ":man_swimming:": "\U0001f3ca\u200d\u2642\ufe0f", - ":man_swimming_tone1:": "\U0001f3ca\U0001f3fb\u200d\u2642\ufe0f", - ":man_swimming_tone2:": "\U0001f3ca\U0001f3fc\u200d\u2642\ufe0f", - ":man_swimming_tone3:": "\U0001f3ca\U0001f3fd\u200d\u2642\ufe0f", - ":man_swimming_tone4:": "\U0001f3ca\U0001f3fe\u200d\u2642\ufe0f", - ":man_swimming_tone5:": "\U0001f3ca\U0001f3ff\u200d\u2642\ufe0f", - ":man_teacher:": "\U0001f468\u200d\U0001f3eb", - ":man_teacher_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f3eb", - ":man_teacher_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f3eb", - ":man_teacher_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f3eb", - ":man_teacher_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f3eb", - ":man_teacher_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f3eb", - ":man_technologist:": "\U0001f468\u200d\U0001f4bb", - ":man_technologist_tone1:": "\U0001f468\U0001f3fb\u200d\U0001f4bb", - ":man_technologist_tone2:": "\U0001f468\U0001f3fc\u200d\U0001f4bb", - ":man_technologist_tone3:": "\U0001f468\U0001f3fd\u200d\U0001f4bb", - ":man_technologist_tone4:": "\U0001f468\U0001f3fe\u200d\U0001f4bb", - ":man_technologist_tone5:": "\U0001f468\U0001f3ff\u200d\U0001f4bb", - ":man_tipping_hand:": "\U0001f481\u200d\u2642\ufe0f", - ":man_tipping_hand_tone1:": "\U0001f481\U0001f3fb\u200d\u2642\ufe0f", - ":man_tipping_hand_tone2:": "\U0001f481\U0001f3fc\u200d\u2642\ufe0f", - ":man_tipping_hand_tone3:": "\U0001f481\U0001f3fd\u200d\u2642\ufe0f", - ":man_tipping_hand_tone4:": "\U0001f481\U0001f3fe\u200d\u2642\ufe0f", - ":man_tipping_hand_tone5:": "\U0001f481\U0001f3ff\u200d\u2642\ufe0f", - ":man_tone1:": "\U0001f468\U0001f3fb", - ":man_tone2:": "\U0001f468\U0001f3fc", - ":man_tone3:": "\U0001f468\U0001f3fd", - ":man_tone4:": "\U0001f468\U0001f3fe", - ":man_tone5:": "\U0001f468\U0001f3ff", - ":man_vampire:": "\U0001f9db\u200d\u2642\ufe0f", - ":man_vampire_tone1:": "\U0001f9db\U0001f3fb\u200d\u2642\ufe0f", - ":man_vampire_tone2:": "\U0001f9db\U0001f3fc\u200d\u2642\ufe0f", - ":man_vampire_tone3:": "\U0001f9db\U0001f3fd\u200d\u2642\ufe0f", - ":man_vampire_tone4:": "\U0001f9db\U0001f3fe\u200d\u2642\ufe0f", - ":man_vampire_tone5:": "\U0001f9db\U0001f3ff\u200d\u2642\ufe0f", - ":man_walking:": "\U0001f6b6\u200d\u2642\ufe0f", - ":man_walking_tone1:": "\U0001f6b6\U0001f3fb\u200d\u2642\ufe0f", - ":man_walking_tone2:": "\U0001f6b6\U0001f3fc\u200d\u2642\ufe0f", - ":man_walking_tone3:": "\U0001f6b6\U0001f3fd\u200d\u2642\ufe0f", - ":man_walking_tone4:": "\U0001f6b6\U0001f3fe\u200d\u2642\ufe0f", - ":man_walking_tone5:": "\U0001f6b6\U0001f3ff\u200d\u2642\ufe0f", - ":man_wearing_turban:": "\U0001f473\u200d\u2642\ufe0f", - ":man_wearing_turban_tone1:": "\U0001f473\U0001f3fb\u200d\u2642\ufe0f", - ":man_wearing_turban_tone2:": "\U0001f473\U0001f3fc\u200d\u2642\ufe0f", - ":man_wearing_turban_tone3:": "\U0001f473\U0001f3fd\u200d\u2642\ufe0f", - ":man_wearing_turban_tone4:": "\U0001f473\U0001f3fe\u200d\u2642\ufe0f", - ":man_wearing_turban_tone5:": "\U0001f473\U0001f3ff\u200d\u2642\ufe0f", - ":man_white_hair:": "\U0001f468\u200d\U0001f9b3", - ":man_with_chinese_cap:": "\U0001f472", - ":man_with_chinese_cap_tone1:": "\U0001f472\U0001f3fb", - ":man_with_chinese_cap_tone2:": "\U0001f472\U0001f3fc", - ":man_with_chinese_cap_tone3:": "\U0001f472\U0001f3fd", - ":man_with_chinese_cap_tone4:": "\U0001f472\U0001f3fe", - ":man_with_chinese_cap_tone5:": "\U0001f472\U0001f3ff", - ":man_with_gua_pi_mao:": "\U0001f472", - ":man_with_probing_cane:": "\U0001f468\u200d\U0001f9af", - ":man_with_turban:": "\U0001f473\u200d\u2642\ufe0f", - ":man_with_veil:": "\U0001f470\u200d\u2642\ufe0f", - ":man_with_white_cane:": "\U0001f468\u200d\U0001f9af", - ":man_zombie:": "\U0001f9df\u200d\u2642\ufe0f", - ":mandarin:": "\U0001f34a", - ":mango:": "\U0001f96d", - ":mans_shoe:": "\U0001f45e", - ":mantelpiece_clock:": "\U0001f570\ufe0f", - ":manual_wheelchair:": "\U0001f9bd", - ":man’s_shoe:": "\U0001f45e", - ":map:": "\U0001f5fa", - ":map_of_Japan:": "\U0001f5fe", - ":maple_leaf:": "\U0001f341", - ":marshall_islands:": "\U0001f1f2\U0001f1ed", - ":martial_arts_uniform:": "\U0001f94b", - ":martinique:": "\U0001f1f2\U0001f1f6", - ":mask:": "\U0001f637", - ":massage:": "\U0001f486\u200d\u2640\ufe0f", - ":massage_man:": "\U0001f486\u200d\u2642\ufe0f", - ":massage_woman:": "\U0001f486\u200d\u2640\ufe0f", - ":mate:": "\U0001f9c9", - ":mate_drink:": "\U0001f9c9", - ":mauritania:": "\U0001f1f2\U0001f1f7", - ":mauritius:": "\U0001f1f2\U0001f1fa", - ":mayotte:": "\U0001f1fe\U0001f1f9", - ":meat_on_bone:": "\U0001f356", - ":mechanic:": "\U0001f9d1\u200d\U0001f527", - ":mechanical_arm:": "\U0001f9be", - ":mechanical_leg:": "\U0001f9bf", - ":medal:": "\U0001f396\ufe0f", - ":medal_military:": "\U0001f396\ufe0f", - ":medal_sports:": "\U0001f3c5", - ":medical_symbol:": "\u2695\ufe0f", - ":mega:": "\U0001f4e3", - ":megaphone:": "\U0001f4e3", - ":melon:": "\U0001f348", - ":memo:": "\U0001f4dd", - ":men_holding_hands:": "\U0001f46c", - ":men_with_bunny_ears:": "\U0001f46f\u200d\u2642\ufe0f", - ":men_with_bunny_ears_partying:": "\U0001f46f\u200d\u2642\ufe0f", - ":men_wrestling:": "\U0001f93c\u200d\u2642\ufe0f", - ":mending_heart:": "\u2764\ufe0f\u200d\U0001fa79", - ":menorah:": "\U0001f54e", - ":menorah_with_nine_branches:": "\U0001f54e", - ":mens:": "\U0001f6b9", - ":men’s_room:": "\U0001f6b9", - ":mermaid:": "\U0001f9dc\u200d\u2640\ufe0f", - ":mermaid_tone1:": "\U0001f9dc\U0001f3fb\u200d\u2640\ufe0f", - ":mermaid_tone2:": "\U0001f9dc\U0001f3fc\u200d\u2640\ufe0f", - ":mermaid_tone3:": "\U0001f9dc\U0001f3fd\u200d\u2640\ufe0f", - ":mermaid_tone4:": "\U0001f9dc\U0001f3fe\u200d\u2640\ufe0f", - ":mermaid_tone5:": "\U0001f9dc\U0001f3ff\u200d\u2640\ufe0f", - ":merman:": "\U0001f9dc\u200d\u2642\ufe0f", - ":merman_tone1:": "\U0001f9dc\U0001f3fb\u200d\u2642\ufe0f", - ":merman_tone2:": "\U0001f9dc\U0001f3fc\u200d\u2642\ufe0f", - ":merman_tone3:": "\U0001f9dc\U0001f3fd\u200d\u2642\ufe0f", - ":merman_tone4:": "\U0001f9dc\U0001f3fe\u200d\u2642\ufe0f", - ":merman_tone5:": "\U0001f9dc\U0001f3ff\u200d\u2642\ufe0f", - ":merperson:": "\U0001f9dc\u200d\u2642\ufe0f", - ":merperson_tone1:": "\U0001f9dc\U0001f3fb", - ":merperson_tone2:": "\U0001f9dc\U0001f3fc", - ":merperson_tone3:": "\U0001f9dc\U0001f3fd", - ":merperson_tone4:": "\U0001f9dc\U0001f3fe", - ":merperson_tone5:": "\U0001f9dc\U0001f3ff", - ":metal:": "\U0001f918", - ":metal_tone1:": "\U0001f918\U0001f3fb", - ":metal_tone2:": "\U0001f918\U0001f3fc", - ":metal_tone3:": "\U0001f918\U0001f3fd", - ":metal_tone4:": "\U0001f918\U0001f3fe", - ":metal_tone5:": "\U0001f918\U0001f3ff", - ":metro:": "\U0001f687", - ":mexico:": "\U0001f1f2\U0001f1fd", - ":microbe:": "\U0001f9a0", - ":micronesia:": "\U0001f1eb\U0001f1f2", - ":microphone:": "\U0001f3a4", - ":microphone2:": "\U0001f399", - ":microscope:": "\U0001f52c", - ":middle_finger:": "\U0001f595", - ":middle_finger_tone1:": "\U0001f595\U0001f3fb", - ":middle_finger_tone2:": "\U0001f595\U0001f3fc", - ":middle_finger_tone3:": "\U0001f595\U0001f3fd", - ":middle_finger_tone4:": "\U0001f595\U0001f3fe", - ":middle_finger_tone5:": "\U0001f595\U0001f3ff", - ":military_helmet:": "\U0001fa96", - ":military_medal:": "\U0001f396", - ":milk:": "\U0001f95b", - ":milk_glass:": "\U0001f95b", - ":milky_way:": "\U0001f30c", - ":minibus:": "\U0001f690", - ":minidisc:": "\U0001f4bd", - ":minus:": "\u2796", - ":mirror:": "\U0001fa9e", - ":moai:": "\U0001f5ff", - ":mobile_phone:": "\U0001f4f1", - ":mobile_phone_off:": "\U0001f4f4", - ":mobile_phone_with_arrow:": "\U0001f4f2", - ":moldova:": "\U0001f1f2\U0001f1e9", - ":monaco:": "\U0001f1f2\U0001f1e8", - ":money-mouth_face:": "\U0001f911", - ":money_bag:": "\U0001f4b0", - ":money_mouth:": "\U0001f911", - ":money_mouth_face:": "\U0001f911", - ":money_with_wings:": "\U0001f4b8", - ":moneybag:": "\U0001f4b0", - ":mongolia:": "\U0001f1f2\U0001f1f3", - ":monkey:": "\U0001f412", - ":monkey_face:": "\U0001f435", - ":monocle_face:": "\U0001f9d0", - ":monorail:": "\U0001f69d", - ":montenegro:": "\U0001f1f2\U0001f1ea", - ":montserrat:": "\U0001f1f2\U0001f1f8", - ":moon:": "\U0001f314", - ":moon_cake:": "\U0001f96e", - ":moon_viewing_ceremony:": "\U0001f391", - ":morocco:": "\U0001f1f2\U0001f1e6", - ":mortar_board:": "\U0001f393", - ":mosque:": "\U0001f54c", - ":mosquito:": "\U0001f99f", - ":mostly_sunny:": "\U0001f324\ufe0f", - ":motor_boat:": "\U0001f6e5\ufe0f", - ":motor_scooter:": "\U0001f6f5", - ":motorboat:": "\U0001f6e5", - ":motorcycle:": "\U0001f3cd", - ":motorized_wheelchair:": "\U0001f9bc", - ":motorway:": "\U0001f6e3\ufe0f", - ":mount_fuji:": "\U0001f5fb", - ":mountain:": "\u26f0\ufe0f", - ":mountain_bicyclist:": "\U0001f6b5\u200d\u2642\ufe0f", - ":mountain_biking_man:": "\U0001f6b5\u200d\u2642\ufe0f", - ":mountain_biking_woman:": "\U0001f6b5\u200d\u2640\ufe0f", - ":mountain_cableway:": "\U0001f6a0", - ":mountain_railway:": "\U0001f69e", - ":mountain_snow:": "\U0001f3d4", - ":mouse:": "\U0001f42d", - ":mouse2:": "\U0001f401", - ":mouse_face:": "\U0001f42d", - ":mouse_three_button:": "\U0001f5b1", - ":mouse_trap:": "\U0001faa4", - ":mouth:": "\U0001f444", - ":movie_camera:": "\U0001f3a5", - ":moyai:": "\U0001f5ff", - ":mozambique:": "\U0001f1f2\U0001f1ff", - ":mrs_claus:": "\U0001f936", - ":mrs_claus_tone1:": "\U0001f936\U0001f3fb", - ":mrs_claus_tone2:": "\U0001f936\U0001f3fc", - ":mrs_claus_tone3:": "\U0001f936\U0001f3fd", - ":mrs_claus_tone4:": "\U0001f936\U0001f3fe", - ":mrs_claus_tone5:": "\U0001f936\U0001f3ff", - ":multiply:": "\u2716", - ":muscle:": "\U0001f4aa", - ":muscle_tone1:": "\U0001f4aa\U0001f3fb", - ":muscle_tone2:": "\U0001f4aa\U0001f3fc", - ":muscle_tone3:": "\U0001f4aa\U0001f3fd", - ":muscle_tone4:": "\U0001f4aa\U0001f3fe", - ":muscle_tone5:": "\U0001f4aa\U0001f3ff", - ":mushroom:": "\U0001f344", - ":musical_keyboard:": "\U0001f3b9", - ":musical_note:": "\U0001f3b5", - ":musical_notes:": "\U0001f3b6", - ":musical_score:": "\U0001f3bc", - ":mute:": "\U0001f507", - ":muted_speaker:": "\U0001f507", - ":mx_claus:": "\U0001f9d1\u200d\U0001f384", - ":myanmar:": "\U0001f1f2\U0001f1f2", - ":nail_care:": "\U0001f485", - ":nail_care_tone1:": "\U0001f485\U0001f3fb", - ":nail_care_tone2:": "\U0001f485\U0001f3fc", - ":nail_care_tone3:": "\U0001f485\U0001f3fd", - ":nail_care_tone4:": "\U0001f485\U0001f3fe", - ":nail_care_tone5:": "\U0001f485\U0001f3ff", - ":nail_polish:": "\U0001f485", - ":name_badge:": "\U0001f4db", - ":namibia:": "\U0001f1f3\U0001f1e6", - ":national_park:": "\U0001f3de\ufe0f", - ":nauru:": "\U0001f1f3\U0001f1f7", - ":nauseated_face:": "\U0001f922", - ":nazar_amulet:": "\U0001f9ff", - ":necktie:": "\U0001f454", - ":negative_squared_cross_mark:": "\u274e", - ":nepal:": "\U0001f1f3\U0001f1f5", - ":nerd:": "\U0001f913", - ":nerd_face:": "\U0001f913", - ":nesting_dolls:": "\U0001fa86", - ":netherlands:": "\U0001f1f3\U0001f1f1", - ":neutral_face:": "\U0001f610", - ":new:": "\U0001f195", - ":new_caledonia:": "\U0001f1f3\U0001f1e8", - ":new_moon:": "\U0001f311", - ":new_moon_face:": "\U0001f31a", - ":new_moon_with_face:": "\U0001f31a", - ":new_zealand:": "\U0001f1f3\U0001f1ff", - ":newspaper:": "\U0001f4f0", - ":newspaper2:": "\U0001f5de", - ":newspaper_roll:": "\U0001f5de\ufe0f", - ":next_track_button:": "\u23ed", - ":ng:": "\U0001f196", - ":ng_man:": "\U0001f645\u200d\u2642\ufe0f", - ":ng_woman:": "\U0001f645\u200d\u2640\ufe0f", - ":nicaragua:": "\U0001f1f3\U0001f1ee", - ":niger:": "\U0001f1f3\U0001f1ea", - ":nigeria:": "\U0001f1f3\U0001f1ec", - ":night_with_stars:": "\U0001f303", - ":nine:": "9\ufe0f\u20e3", - ":nine-thirty:": "\U0001f564", - ":nine_o’clock:": "\U0001f558", - ":ninja:": "\U0001f977", - ":niue:": "\U0001f1f3\U0001f1fa", - ":no_bell:": "\U0001f515", - ":no_bicycles:": "\U0001f6b3", - ":no_entry:": "\u26d4", - ":no_entry_sign:": "\U0001f6ab", - ":no_good:": "\U0001f645\u200d\u2640\ufe0f", - ":no_good_man:": "\U0001f645\u200d\u2642\ufe0f", - ":no_good_woman:": "\U0001f645\u200d\u2640\ufe0f", - ":no_littering:": "\U0001f6af", - ":no_mobile_phones:": "\U0001f4f5", - ":no_mouth:": "\U0001f636", - ":no_one_under_eighteen:": "\U0001f51e", - ":no_pedestrians:": "\U0001f6b7", - ":no_smoking:": "\U0001f6ad", - ":non-potable_water:": "\U0001f6b1", - ":norfolk_island:": "\U0001f1f3\U0001f1eb", - ":north_korea:": "\U0001f1f0\U0001f1f5", - ":northern_mariana_islands:": "\U0001f1f2\U0001f1f5", - ":norway:": "\U0001f1f3\U0001f1f4", - ":nose:": "\U0001f443", - ":nose_tone1:": "\U0001f443\U0001f3fb", - ":nose_tone2:": "\U0001f443\U0001f3fc", - ":nose_tone3:": "\U0001f443\U0001f3fd", - ":nose_tone4:": "\U0001f443\U0001f3fe", - ":nose_tone5:": "\U0001f443\U0001f3ff", - ":notebook:": "\U0001f4d3", - ":notebook_with_decorative_cover:": "\U0001f4d4", - ":notepad_spiral:": "\U0001f5d2", - ":notes:": "\U0001f3b6", - ":nut_and_bolt:": "\U0001f529", - ":o:": "\u2b55", - ":o2:": "\U0001f17e\ufe0f", - ":ocean:": "\U0001f30a", - ":octagonal_sign:": "\U0001f6d1", - ":octopus:": "\U0001f419", - ":oden:": "\U0001f362", - ":office:": "\U0001f3e2", - ":office_building:": "\U0001f3e2", - ":office_worker:": "\U0001f9d1\u200d\U0001f4bc", - ":ogre:": "\U0001f479", - ":oil:": "\U0001f6e2", - ":oil_drum:": "\U0001f6e2\ufe0f", - ":ok:": "\U0001f197", - ":ok_hand:": "\U0001f44c", - ":ok_hand_tone1:": "\U0001f44c\U0001f3fb", - ":ok_hand_tone2:": "\U0001f44c\U0001f3fc", - ":ok_hand_tone3:": "\U0001f44c\U0001f3fd", - ":ok_hand_tone4:": "\U0001f44c\U0001f3fe", - ":ok_hand_tone5:": "\U0001f44c\U0001f3ff", - ":ok_man:": "\U0001f646\u200d\u2642\ufe0f", - ":ok_person:": "\U0001f646", - ":ok_woman:": "\U0001f646\u200d\u2640\ufe0f", - ":old_key:": "\U0001f5dd\ufe0f", - ":old_man:": "\U0001f474", - ":old_woman:": "\U0001f475", - ":older_adult:": "\U0001f9d3", - ":older_adult_tone1:": "\U0001f9d3\U0001f3fb", - ":older_adult_tone2:": "\U0001f9d3\U0001f3fc", - ":older_adult_tone3:": "\U0001f9d3\U0001f3fd", - ":older_adult_tone4:": "\U0001f9d3\U0001f3fe", - ":older_adult_tone5:": "\U0001f9d3\U0001f3ff", - ":older_man:": "\U0001f474", - ":older_man_tone1:": "\U0001f474\U0001f3fb", - ":older_man_tone2:": "\U0001f474\U0001f3fc", - ":older_man_tone3:": "\U0001f474\U0001f3fd", - ":older_man_tone4:": "\U0001f474\U0001f3fe", - ":older_man_tone5:": "\U0001f474\U0001f3ff", - ":older_person:": "\U0001f9d3", - ":older_woman:": "\U0001f475", - ":older_woman_tone1:": "\U0001f475\U0001f3fb", - ":older_woman_tone2:": "\U0001f475\U0001f3fc", - ":older_woman_tone3:": "\U0001f475\U0001f3fd", - ":older_woman_tone4:": "\U0001f475\U0001f3fe", - ":older_woman_tone5:": "\U0001f475\U0001f3ff", - ":olive:": "\U0001fad2", - ":om:": "\U0001f549", - ":om_symbol:": "\U0001f549\ufe0f", - ":oman:": "\U0001f1f4\U0001f1f2", - ":on:": "\U0001f51b", - ":oncoming_automobile:": "\U0001f698", - ":oncoming_bus:": "\U0001f68d", - ":oncoming_fist:": "\U0001f44a", - ":oncoming_police_car:": "\U0001f694", - ":oncoming_taxi:": "\U0001f696", - ":one:": "1\ufe0f\u20e3", - ":one-piece_swimsuit:": "\U0001fa71", - ":one-thirty:": "\U0001f55c", - ":one_o’clock:": "\U0001f550", - ":one_piece_swimsuit:": "\U0001fa71", - ":onion:": "\U0001f9c5", - ":open_book:": "\U0001f4d6", - ":open_file_folder:": "\U0001f4c2", - ":open_hands:": "\U0001f450", - ":open_hands_tone1:": "\U0001f450\U0001f3fb", - ":open_hands_tone2:": "\U0001f450\U0001f3fc", - ":open_hands_tone3:": "\U0001f450\U0001f3fd", - ":open_hands_tone4:": "\U0001f450\U0001f3fe", - ":open_hands_tone5:": "\U0001f450\U0001f3ff", - ":open_mailbox_with_lowered_flag:": "\U0001f4ed", - ":open_mailbox_with_raised_flag:": "\U0001f4ec", - ":open_mouth:": "\U0001f62e", - ":open_umbrella:": "\u2602\ufe0f", - ":ophiuchus:": "\u26ce", - ":optical_disk:": "\U0001f4bf", - ":orange:": "\U0001f34a", - ":orange_book:": "\U0001f4d9", - ":orange_circle:": "\U0001f7e0", - ":orange_heart:": "\U0001f9e1", - ":orange_square:": "\U0001f7e7", - ":orangutan:": "\U0001f9a7", - ":orthodox_cross:": "\u2626\ufe0f", - ":otter:": "\U0001f9a6", - ":outbox_tray:": "\U0001f4e4", - ":owl:": "\U0001f989", - ":ox:": "\U0001f402", - ":oyster:": "\U0001f9aa", - ":package:": "\U0001f4e6", - ":page_facing_up:": "\U0001f4c4", - ":page_with_curl:": "\U0001f4c3", - ":pager:": "\U0001f4df", - ":paintbrush:": "\U0001f58c", - ":pakistan:": "\U0001f1f5\U0001f1f0", - ":palau:": "\U0001f1f5\U0001f1fc", - ":palestinian_territories:": "\U0001f1f5\U0001f1f8", - ":palm_tree:": "\U0001f334", - ":palms_up_together:": "\U0001f932", - ":palms_up_together_tone1:": "\U0001f932\U0001f3fb", - ":palms_up_together_tone2:": "\U0001f932\U0001f3fc", - ":palms_up_together_tone3:": "\U0001f932\U0001f3fd", - ":palms_up_together_tone4:": "\U0001f932\U0001f3fe", - ":palms_up_together_tone5:": "\U0001f932\U0001f3ff", - ":panama:": "\U0001f1f5\U0001f1e6", - ":pancakes:": "\U0001f95e", - ":panda:": "\U0001f43c", - ":panda_face:": "\U0001f43c", - ":paperclip:": "\U0001f4ce", - ":paperclips:": "\U0001f587", - ":papua_new_guinea:": "\U0001f1f5\U0001f1ec", - ":parachute:": "\U0001fa82", - ":paraguay:": "\U0001f1f5\U0001f1fe", - ":parasol_on_ground:": "\u26f1\ufe0f", - ":park:": "\U0001f3de", - ":parking:": "\U0001f17f\ufe0f", - ":parrot:": "\U0001f99c", - ":part_alternation_mark:": "\u303d\ufe0f", - ":partly_sunny:": "\u26c5", - ":partly_sunny_rain:": "\U0001f326\ufe0f", - ":party_popper:": "\U0001f389", - ":partying_face:": "\U0001f973", - ":passenger_ship:": "\U0001f6f3\ufe0f", - ":passport_control:": "\U0001f6c2", - ":pause_button:": "\u23f8", - ":paw_prints:": "\U0001f43e", - ":peace:": "\u262e", - ":peace_symbol:": "\u262e\ufe0f", - ":peach:": "\U0001f351", - ":peacock:": "\U0001f99a", - ":peanuts:": "\U0001f95c", - ":pear:": "\U0001f350", - ":pen:": "\U0001f58a", - ":pen_ballpoint:": "\U0001f58a", - ":pen_fountain:": "\U0001f58b", - ":pencil:": "\u270f", - ":pencil2:": "\u270f\ufe0f", - ":penguin:": "\U0001f427", - ":pensive:": "\U0001f614", - ":pensive_face:": "\U0001f614", - ":people_holding_hands:": "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1", - ":people_hugging:": "\U0001fac2", - ":people_with_bunny_ears:": "\U0001f46f", - ":people_with_bunny_ears_partying:": "\U0001f46f", - ":people_wrestling:": "\U0001f93c", - ":performing_arts:": "\U0001f3ad", - ":persevere:": "\U0001f623", - ":persevering_face:": "\U0001f623", - ":person:": "\U0001f9d1", - ":person_bald:": "\U0001f9d1\u200d\U0001f9b2", - ":person_beard:": "\U0001f9d4", - ":person_biking:": "\U0001f6b4", - ":person_biking_tone1:": "\U0001f6b4\U0001f3fb", - ":person_biking_tone2:": "\U0001f6b4\U0001f3fc", - ":person_biking_tone3:": "\U0001f6b4\U0001f3fd", - ":person_biking_tone4:": "\U0001f6b4\U0001f3fe", - ":person_biking_tone5:": "\U0001f6b4\U0001f3ff", - ":person_blond_hair:": "\U0001f471", - ":person_bouncing_ball:": "\u26f9", - ":person_bouncing_ball_tone1:": "\u26f9\U0001f3fb", - ":person_bouncing_ball_tone2:": "\u26f9\U0001f3fc", - ":person_bouncing_ball_tone3:": "\u26f9\U0001f3fd", - ":person_bouncing_ball_tone4:": "\u26f9\U0001f3fe", - ":person_bouncing_ball_tone5:": "\u26f9\U0001f3ff", - ":person_bowing:": "\U0001f647", - ":person_bowing_tone1:": "\U0001f647\U0001f3fb", - ":person_bowing_tone2:": "\U0001f647\U0001f3fc", - ":person_bowing_tone3:": "\U0001f647\U0001f3fd", - ":person_bowing_tone4:": "\U0001f647\U0001f3fe", - ":person_bowing_tone5:": "\U0001f647\U0001f3ff", - ":person_cartwheeling:": "\U0001f938", - ":person_climbing:": "\U0001f9d7\u200d\u2640\ufe0f", - ":person_climbing_tone1:": "\U0001f9d7\U0001f3fb", - ":person_climbing_tone2:": "\U0001f9d7\U0001f3fc", - ":person_climbing_tone3:": "\U0001f9d7\U0001f3fd", - ":person_climbing_tone4:": "\U0001f9d7\U0001f3fe", - ":person_climbing_tone5:": "\U0001f9d7\U0001f3ff", - ":person_curly_hair:": "\U0001f9d1\u200d\U0001f9b1", - ":person_doing_cartwheel:": "\U0001f938", - ":person_doing_cartwheel_tone1:": "\U0001f938\U0001f3fb", - ":person_doing_cartwheel_tone2:": "\U0001f938\U0001f3fc", - ":person_doing_cartwheel_tone3:": "\U0001f938\U0001f3fd", - ":person_doing_cartwheel_tone4:": "\U0001f938\U0001f3fe", - ":person_doing_cartwheel_tone5:": "\U0001f938\U0001f3ff", - ":person_facepalming:": "\U0001f926", - ":person_facepalming_tone1:": "\U0001f926\U0001f3fb", - ":person_facepalming_tone2:": "\U0001f926\U0001f3fc", - ":person_facepalming_tone3:": "\U0001f926\U0001f3fd", - ":person_facepalming_tone4:": "\U0001f926\U0001f3fe", - ":person_facepalming_tone5:": "\U0001f926\U0001f3ff", - ":person_feeding_baby:": "\U0001f9d1\u200d\U0001f37c", - ":person_fencing:": "\U0001f93a", - ":person_frowning:": "\U0001f64d\u200d\u2640\ufe0f", - ":person_frowning_tone1:": "\U0001f64d\U0001f3fb", - ":person_frowning_tone2:": "\U0001f64d\U0001f3fc", - ":person_frowning_tone3:": "\U0001f64d\U0001f3fd", - ":person_frowning_tone4:": "\U0001f64d\U0001f3fe", - ":person_frowning_tone5:": "\U0001f64d\U0001f3ff", - ":person_gesturing_NO:": "\U0001f645", - ":person_gesturing_OK:": "\U0001f646", - ":person_gesturing_no:": "\U0001f645", - ":person_gesturing_no_tone1:": "\U0001f645\U0001f3fb", - ":person_gesturing_no_tone2:": "\U0001f645\U0001f3fc", - ":person_gesturing_no_tone3:": "\U0001f645\U0001f3fd", - ":person_gesturing_no_tone4:": "\U0001f645\U0001f3fe", - ":person_gesturing_no_tone5:": "\U0001f645\U0001f3ff", - ":person_gesturing_ok:": "\U0001f646", - ":person_gesturing_ok_tone1:": "\U0001f646\U0001f3fb", - ":person_gesturing_ok_tone2:": "\U0001f646\U0001f3fc", - ":person_gesturing_ok_tone3:": "\U0001f646\U0001f3fd", - ":person_gesturing_ok_tone4:": "\U0001f646\U0001f3fe", - ":person_gesturing_ok_tone5:": "\U0001f646\U0001f3ff", - ":person_getting_haircut:": "\U0001f487", - ":person_getting_haircut_tone1:": "\U0001f487\U0001f3fb", - ":person_getting_haircut_tone2:": "\U0001f487\U0001f3fc", - ":person_getting_haircut_tone3:": "\U0001f487\U0001f3fd", - ":person_getting_haircut_tone4:": "\U0001f487\U0001f3fe", - ":person_getting_haircut_tone5:": "\U0001f487\U0001f3ff", - ":person_getting_massage:": "\U0001f486", - ":person_getting_massage_tone1:": "\U0001f486\U0001f3fb", - ":person_getting_massage_tone2:": "\U0001f486\U0001f3fc", - ":person_getting_massage_tone3:": "\U0001f486\U0001f3fd", - ":person_getting_massage_tone4:": "\U0001f486\U0001f3fe", - ":person_getting_massage_tone5:": "\U0001f486\U0001f3ff", - ":person_golfing:": "\U0001f3cc", - ":person_golfing_tone1:": "\U0001f3cc\U0001f3fb", - ":person_golfing_tone2:": "\U0001f3cc\U0001f3fc", - ":person_golfing_tone3:": "\U0001f3cc\U0001f3fd", - ":person_golfing_tone4:": "\U0001f3cc\U0001f3fe", - ":person_golfing_tone5:": "\U0001f3cc\U0001f3ff", - ":person_in_bed:": "\U0001f6cc", - ":person_in_bed_tone1:": "\U0001f6cc\U0001f3fb", - ":person_in_bed_tone2:": "\U0001f6cc\U0001f3fc", - ":person_in_bed_tone3:": "\U0001f6cc\U0001f3fd", - ":person_in_bed_tone4:": "\U0001f6cc\U0001f3fe", - ":person_in_bed_tone5:": "\U0001f6cc\U0001f3ff", - ":person_in_lotus_position:": "\U0001f9d8\u200d\u2640\ufe0f", - ":person_in_lotus_position_tone1:": "\U0001f9d8\U0001f3fb", - ":person_in_lotus_position_tone2:": "\U0001f9d8\U0001f3fc", - ":person_in_lotus_position_tone3:": "\U0001f9d8\U0001f3fd", - ":person_in_lotus_position_tone4:": "\U0001f9d8\U0001f3fe", - ":person_in_lotus_position_tone5:": "\U0001f9d8\U0001f3ff", - ":person_in_manual_wheelchair:": "\U0001f9d1\u200d\U0001f9bd", - ":person_in_motorized_wheelchair:": "\U0001f9d1\u200d\U0001f9bc", - ":person_in_steamy_room:": "\U0001f9d6\u200d\u2642\ufe0f", - ":person_in_steamy_room_tone1:": "\U0001f9d6\U0001f3fb", - ":person_in_steamy_room_tone2:": "\U0001f9d6\U0001f3fc", - ":person_in_steamy_room_tone3:": "\U0001f9d6\U0001f3fd", - ":person_in_steamy_room_tone4:": "\U0001f9d6\U0001f3fe", - ":person_in_steamy_room_tone5:": "\U0001f9d6\U0001f3ff", - ":person_in_suit_levitating:": "\U0001f574", - ":person_in_tuxedo:": "\U0001f935", - ":person_juggling:": "\U0001f939", - ":person_juggling_tone1:": "\U0001f939\U0001f3fb", - ":person_juggling_tone2:": "\U0001f939\U0001f3fc", - ":person_juggling_tone3:": "\U0001f939\U0001f3fd", - ":person_juggling_tone4:": "\U0001f939\U0001f3fe", - ":person_juggling_tone5:": "\U0001f939\U0001f3ff", - ":person_kneeling:": "\U0001f9ce", - ":person_lifting_weights:": "\U0001f3cb", - ":person_lifting_weights_tone1:": "\U0001f3cb\U0001f3fb", - ":person_lifting_weights_tone2:": "\U0001f3cb\U0001f3fc", - ":person_lifting_weights_tone3:": "\U0001f3cb\U0001f3fd", - ":person_lifting_weights_tone4:": "\U0001f3cb\U0001f3fe", - ":person_lifting_weights_tone5:": "\U0001f3cb\U0001f3ff", - ":person_mountain_biking:": "\U0001f6b5", - ":person_mountain_biking_tone1:": "\U0001f6b5\U0001f3fb", - ":person_mountain_biking_tone2:": "\U0001f6b5\U0001f3fc", - ":person_mountain_biking_tone3:": "\U0001f6b5\U0001f3fd", - ":person_mountain_biking_tone4:": "\U0001f6b5\U0001f3fe", - ":person_mountain_biking_tone5:": "\U0001f6b5\U0001f3ff", - ":person_playing_handball:": "\U0001f93e", - ":person_playing_handball_tone1:": "\U0001f93e\U0001f3fb", - ":person_playing_handball_tone2:": "\U0001f93e\U0001f3fc", - ":person_playing_handball_tone3:": "\U0001f93e\U0001f3fd", - ":person_playing_handball_tone4:": "\U0001f93e\U0001f3fe", - ":person_playing_handball_tone5:": "\U0001f93e\U0001f3ff", - ":person_playing_water_polo:": "\U0001f93d", - ":person_playing_water_polo_tone1:": "\U0001f93d\U0001f3fb", - ":person_playing_water_polo_tone2:": "\U0001f93d\U0001f3fc", - ":person_playing_water_polo_tone3:": "\U0001f93d\U0001f3fd", - ":person_playing_water_polo_tone4:": "\U0001f93d\U0001f3fe", - ":person_playing_water_polo_tone5:": "\U0001f93d\U0001f3ff", - ":person_pouting:": "\U0001f64e", - ":person_pouting_tone1:": "\U0001f64e\U0001f3fb", - ":person_pouting_tone2:": "\U0001f64e\U0001f3fc", - ":person_pouting_tone3:": "\U0001f64e\U0001f3fd", - ":person_pouting_tone4:": "\U0001f64e\U0001f3fe", - ":person_pouting_tone5:": "\U0001f64e\U0001f3ff", - ":person_raising_hand:": "\U0001f64b", - ":person_raising_hand_tone1:": "\U0001f64b\U0001f3fb", - ":person_raising_hand_tone2:": "\U0001f64b\U0001f3fc", - ":person_raising_hand_tone3:": "\U0001f64b\U0001f3fd", - ":person_raising_hand_tone4:": "\U0001f64b\U0001f3fe", - ":person_raising_hand_tone5:": "\U0001f64b\U0001f3ff", - ":person_red_hair:": "\U0001f9d1\u200d\U0001f9b0", - ":person_rowing_boat:": "\U0001f6a3", - ":person_rowing_boat_tone1:": "\U0001f6a3\U0001f3fb", - ":person_rowing_boat_tone2:": "\U0001f6a3\U0001f3fc", - ":person_rowing_boat_tone3:": "\U0001f6a3\U0001f3fd", - ":person_rowing_boat_tone4:": "\U0001f6a3\U0001f3fe", - ":person_rowing_boat_tone5:": "\U0001f6a3\U0001f3ff", - ":person_running:": "\U0001f3c3", - ":person_running_tone1:": "\U0001f3c3\U0001f3fb", - ":person_running_tone2:": "\U0001f3c3\U0001f3fc", - ":person_running_tone3:": "\U0001f3c3\U0001f3fd", - ":person_running_tone4:": "\U0001f3c3\U0001f3fe", - ":person_running_tone5:": "\U0001f3c3\U0001f3ff", - ":person_shrugging:": "\U0001f937", - ":person_shrugging_tone1:": "\U0001f937\U0001f3fb", - ":person_shrugging_tone2:": "\U0001f937\U0001f3fc", - ":person_shrugging_tone3:": "\U0001f937\U0001f3fd", - ":person_shrugging_tone4:": "\U0001f937\U0001f3fe", - ":person_shrugging_tone5:": "\U0001f937\U0001f3ff", - ":person_standing:": "\U0001f9cd", - ":person_surfing:": "\U0001f3c4", - ":person_surfing_tone1:": "\U0001f3c4\U0001f3fb", - ":person_surfing_tone2:": "\U0001f3c4\U0001f3fc", - ":person_surfing_tone3:": "\U0001f3c4\U0001f3fd", - ":person_surfing_tone4:": "\U0001f3c4\U0001f3fe", - ":person_surfing_tone5:": "\U0001f3c4\U0001f3ff", - ":person_swimming:": "\U0001f3ca", - ":person_swimming_tone1:": "\U0001f3ca\U0001f3fb", - ":person_swimming_tone2:": "\U0001f3ca\U0001f3fc", - ":person_swimming_tone3:": "\U0001f3ca\U0001f3fd", - ":person_swimming_tone4:": "\U0001f3ca\U0001f3fe", - ":person_swimming_tone5:": "\U0001f3ca\U0001f3ff", - ":person_taking_bath:": "\U0001f6c0", - ":person_tipping_hand:": "\U0001f481", - ":person_tipping_hand_tone1:": "\U0001f481\U0001f3fb", - ":person_tipping_hand_tone2:": "\U0001f481\U0001f3fc", - ":person_tipping_hand_tone3:": "\U0001f481\U0001f3fd", - ":person_tipping_hand_tone4:": "\U0001f481\U0001f3fe", - ":person_tipping_hand_tone5:": "\U0001f481\U0001f3ff", - ":person_walking:": "\U0001f6b6", - ":person_walking_tone1:": "\U0001f6b6\U0001f3fb", - ":person_walking_tone2:": "\U0001f6b6\U0001f3fc", - ":person_walking_tone3:": "\U0001f6b6\U0001f3fd", - ":person_walking_tone4:": "\U0001f6b6\U0001f3fe", - ":person_walking_tone5:": "\U0001f6b6\U0001f3ff", - ":person_wearing_turban:": "\U0001f473", - ":person_wearing_turban_tone1:": "\U0001f473\U0001f3fb", - ":person_wearing_turban_tone2:": "\U0001f473\U0001f3fc", - ":person_wearing_turban_tone3:": "\U0001f473\U0001f3fd", - ":person_wearing_turban_tone4:": "\U0001f473\U0001f3fe", - ":person_wearing_turban_tone5:": "\U0001f473\U0001f3ff", - ":person_white_hair:": "\U0001f9d1\u200d\U0001f9b3", - ":person_with_ball:": "\u26f9\ufe0f\u200d\u2642\ufe0f", - ":person_with_blond_hair:": "\U0001f471\u200d\u2642\ufe0f", - ":person_with_headscarf:": "\U0001f9d5", - ":person_with_pouting_face:": "\U0001f64e\u200d\u2640\ufe0f", - ":person_with_probing_cane:": "\U0001f9d1\u200d\U0001f9af", - ":person_with_skullcap:": "\U0001f472", - ":person_with_turban:": "\U0001f473", - ":person_with_veil:": "\U0001f470", - ":person_with_white_cane:": "\U0001f9d1\u200d\U0001f9af", - ":peru:": "\U0001f1f5\U0001f1ea", - ":petri_dish:": "\U0001f9eb", - ":philippines:": "\U0001f1f5\U0001f1ed", - ":phone:": "\u260e\ufe0f", - ":pick:": "\u26cf\ufe0f", - ":pickup_truck:": "\U0001f6fb", - ":pie:": "\U0001f967", - ":pig:": "\U0001f437", - ":pig2:": "\U0001f416", - ":pig_face:": "\U0001f437", - ":pig_nose:": "\U0001f43d", - ":pile_of_poo:": "\U0001f4a9", - ":pill:": "\U0001f48a", - ":pilot:": "\U0001f9d1\u200d\u2708\ufe0f", - ":pinata:": "\U0001fa85", - ":pinched_fingers:": "\U0001f90c", - ":pinching_hand:": "\U0001f90f", - ":pine_decoration:": "\U0001f38d", - ":pineapple:": "\U0001f34d", - ":ping_pong:": "\U0001f3d3", - ":pirate_flag:": "\U0001f3f4\u200d\u2620\ufe0f", - ":pisces:": "\u2653", - ":pitcairn_islands:": "\U0001f1f5\U0001f1f3", - ":pizza:": "\U0001f355", - ":piñata:": "\U0001fa85", - ":placard:": "\U0001faa7", - ":place_of_worship:": "\U0001f6d0", - ":plate_with_cutlery:": "\U0001f37d\ufe0f", - ":play_button:": "\u25b6", - ":play_or_pause_button:": "\u23ef", - ":play_pause:": "\u23ef", - ":pleading_face:": "\U0001f97a", - ":plunger:": "\U0001faa0", - ":plus:": "\u2795", - ":point_down:": "\U0001f447", - ":point_down_tone1:": "\U0001f447\U0001f3fb", - ":point_down_tone2:": "\U0001f447\U0001f3fc", - ":point_down_tone3:": "\U0001f447\U0001f3fd", - ":point_down_tone4:": "\U0001f447\U0001f3fe", - ":point_down_tone5:": "\U0001f447\U0001f3ff", - ":point_left:": "\U0001f448", - ":point_left_tone1:": "\U0001f448\U0001f3fb", - ":point_left_tone2:": "\U0001f448\U0001f3fc", - ":point_left_tone3:": "\U0001f448\U0001f3fd", - ":point_left_tone4:": "\U0001f448\U0001f3fe", - ":point_left_tone5:": "\U0001f448\U0001f3ff", - ":point_right:": "\U0001f449", - ":point_right_tone1:": "\U0001f449\U0001f3fb", - ":point_right_tone2:": "\U0001f449\U0001f3fc", - ":point_right_tone3:": "\U0001f449\U0001f3fd", - ":point_right_tone4:": "\U0001f449\U0001f3fe", - ":point_right_tone5:": "\U0001f449\U0001f3ff", - ":point_up:": "\u261d\ufe0f", - ":point_up_2:": "\U0001f446", - ":point_up_2_tone1:": "\U0001f446\U0001f3fb", - ":point_up_2_tone2:": "\U0001f446\U0001f3fc", - ":point_up_2_tone3:": "\U0001f446\U0001f3fd", - ":point_up_2_tone4:": "\U0001f446\U0001f3fe", - ":point_up_2_tone5:": "\U0001f446\U0001f3ff", - ":point_up_tone1:": "\u261d\U0001f3fb", - ":point_up_tone2:": "\u261d\U0001f3fc", - ":point_up_tone3:": "\u261d\U0001f3fd", - ":point_up_tone4:": "\u261d\U0001f3fe", - ":point_up_tone5:": "\u261d\U0001f3ff", - ":poland:": "\U0001f1f5\U0001f1f1", - ":polar_bear:": "\U0001f43b\u200d\u2744\ufe0f", - ":police_car:": "\U0001f693", - ":police_car_light:": "\U0001f6a8", - ":police_officer:": "\U0001f46e", - ":police_officer_tone1:": "\U0001f46e\U0001f3fb", - ":police_officer_tone2:": "\U0001f46e\U0001f3fc", - ":police_officer_tone3:": "\U0001f46e\U0001f3fd", - ":police_officer_tone4:": "\U0001f46e\U0001f3fe", - ":police_officer_tone5:": "\U0001f46e\U0001f3ff", - ":policeman:": "\U0001f46e\u200d\u2642\ufe0f", - ":policewoman:": "\U0001f46e\u200d\u2640\ufe0f", - ":poodle:": "\U0001f429", - ":pool_8_ball:": "\U0001f3b1", - ":poop:": "\U0001f4a9", - ":popcorn:": "\U0001f37f", - ":portugal:": "\U0001f1f5\U0001f1f9", - ":post_office:": "\U0001f3e3", - ":postal_horn:": "\U0001f4ef", - ":postbox:": "\U0001f4ee", - ":pot_of_food:": "\U0001f372", - ":potable_water:": "\U0001f6b0", - ":potato:": "\U0001f954", - ":potted_plant:": "\U0001fab4", - ":pouch:": "\U0001f45d", - ":poultry_leg:": "\U0001f357", - ":pound:": "\U0001f4b7", - ":pound_banknote:": "\U0001f4b7", - ":pout:": "\U0001f621", - ":pouting_cat:": "\U0001f63e", - ":pouting_face:": "\U0001f621", - ":pouting_man:": "\U0001f64e\u200d\u2642\ufe0f", - ":pouting_woman:": "\U0001f64e\u200d\u2640\ufe0f", - ":pray:": "\U0001f64f", - ":pray_tone1:": "\U0001f64f\U0001f3fb", - ":pray_tone2:": "\U0001f64f\U0001f3fc", - ":pray_tone3:": "\U0001f64f\U0001f3fd", - ":pray_tone4:": "\U0001f64f\U0001f3fe", - ":pray_tone5:": "\U0001f64f\U0001f3ff", - ":prayer_beads:": "\U0001f4ff", - ":pregnant_woman:": "\U0001f930", - ":pregnant_woman_tone1:": "\U0001f930\U0001f3fb", - ":pregnant_woman_tone2:": "\U0001f930\U0001f3fc", - ":pregnant_woman_tone3:": "\U0001f930\U0001f3fd", - ":pregnant_woman_tone4:": "\U0001f930\U0001f3fe", - ":pregnant_woman_tone5:": "\U0001f930\U0001f3ff", - ":pretzel:": "\U0001f968", - ":previous_track_button:": "\u23ee\ufe0f", - ":prince:": "\U0001f934", - ":prince_tone1:": "\U0001f934\U0001f3fb", - ":prince_tone2:": "\U0001f934\U0001f3fc", - ":prince_tone3:": "\U0001f934\U0001f3fd", - ":prince_tone4:": "\U0001f934\U0001f3fe", - ":prince_tone5:": "\U0001f934\U0001f3ff", - ":princess:": "\U0001f478", - ":princess_tone1:": "\U0001f478\U0001f3fb", - ":princess_tone2:": "\U0001f478\U0001f3fc", - ":princess_tone3:": "\U0001f478\U0001f3fd", - ":princess_tone4:": "\U0001f478\U0001f3fe", - ":princess_tone5:": "\U0001f478\U0001f3ff", - ":printer:": "\U0001f5a8\ufe0f", - ":probing_cane:": "\U0001f9af", - ":prohibited:": "\U0001f6ab", - ":projector:": "\U0001f4fd", - ":puerto_rico:": "\U0001f1f5\U0001f1f7", - ":punch:": "\U0001f44a", - ":punch_tone1:": "\U0001f44a\U0001f3fb", - ":punch_tone2:": "\U0001f44a\U0001f3fc", - ":punch_tone3:": "\U0001f44a\U0001f3fd", - ":punch_tone4:": "\U0001f44a\U0001f3fe", - ":punch_tone5:": "\U0001f44a\U0001f3ff", - ":purple_circle:": "\U0001f7e3", - ":purple_heart:": "\U0001f49c", - ":purple_square:": "\U0001f7ea", - ":purse:": "\U0001f45b", - ":pushpin:": "\U0001f4cc", - ":put_litter_in_its_place:": "\U0001f6ae", - ":puzzle_piece:": "\U0001f9e9", - ":qatar:": "\U0001f1f6\U0001f1e6", - ":question:": "\u2753", - ":rabbit:": "\U0001f430", - ":rabbit2:": "\U0001f407", - ":rabbit_face:": "\U0001f430", - ":raccoon:": "\U0001f99d", - ":race_car:": "\U0001f3ce", - ":racehorse:": "\U0001f40e", - ":racing_car:": "\U0001f3ce\ufe0f", - ":racing_motorcycle:": "\U0001f3cd\ufe0f", - ":radio:": "\U0001f4fb", - ":radio_button:": "\U0001f518", - ":radioactive:": "\u2622", - ":radioactive_sign:": "\u2622\ufe0f", - ":rage:": "\U0001f621", - ":railway_car:": "\U0001f683", - ":railway_track:": "\U0001f6e4\ufe0f", - ":rain_cloud:": "\U0001f327\ufe0f", - ":rainbow:": "\U0001f308", - ":rainbow-flag:": "\U0001f3f3\ufe0f\u200d\U0001f308", - ":rainbow_flag:": "\U0001f3f3\ufe0f\u200d\U0001f308", - ":raised_back_of_hand:": "\U0001f91a", - ":raised_back_of_hand_tone1:": "\U0001f91a\U0001f3fb", - ":raised_back_of_hand_tone2:": "\U0001f91a\U0001f3fc", - ":raised_back_of_hand_tone3:": "\U0001f91a\U0001f3fd", - ":raised_back_of_hand_tone4:": "\U0001f91a\U0001f3fe", - ":raised_back_of_hand_tone5:": "\U0001f91a\U0001f3ff", - ":raised_eyebrow:": "\U0001f928", - ":raised_fist:": "\u270a", - ":raised_hand:": "\u270b", - ":raised_hand_tone1:": "\u270b\U0001f3fb", - ":raised_hand_tone2:": "\u270b\U0001f3fc", - ":raised_hand_tone3:": "\u270b\U0001f3fd", - ":raised_hand_tone4:": "\u270b\U0001f3fe", - ":raised_hand_tone5:": "\u270b\U0001f3ff", - ":raised_hand_with_fingers_splayed:": "\U0001f590\ufe0f", - ":raised_hands:": "\U0001f64c", - ":raised_hands_tone1:": "\U0001f64c\U0001f3fb", - ":raised_hands_tone2:": "\U0001f64c\U0001f3fc", - ":raised_hands_tone3:": "\U0001f64c\U0001f3fd", - ":raised_hands_tone4:": "\U0001f64c\U0001f3fe", - ":raised_hands_tone5:": "\U0001f64c\U0001f3ff", - ":raising_hand:": "\U0001f64b\u200d\u2640\ufe0f", - ":raising_hand_man:": "\U0001f64b\u200d\u2642\ufe0f", - ":raising_hand_woman:": "\U0001f64b\u200d\u2640\ufe0f", - ":raising_hands:": "\U0001f64c", - ":ram:": "\U0001f40f", - ":ramen:": "\U0001f35c", - ":rat:": "\U0001f400", - ":razor:": "\U0001fa92", - ":receipt:": "\U0001f9fe", - ":record_button:": "\u23fa", - ":recycle:": "\u267b\ufe0f", - ":recycling_symbol:": "\u267b", - ":red_apple:": "\U0001f34e", - ":red_car:": "\U0001f697", - ":red_circle:": "\U0001f534", - ":red_envelope:": "\U0001f9e7", - ":red_exclamation_mark:": "\u2757", - ":red_hair:": "\U0001f9b0", - ":red_haired_man:": "\U0001f468\u200d\U0001f9b0", - ":red_haired_person:": "\U0001f9d1\u200d\U0001f9b0", - ":red_haired_woman:": "\U0001f469\u200d\U0001f9b0", - ":red_heart:": "\u2764", - ":red_paper_lantern:": "\U0001f3ee", - ":red_question_mark:": "\u2753", - ":red_square:": "\U0001f7e5", - ":red_triangle_pointed_down:": "\U0001f53b", - ":red_triangle_pointed_up:": "\U0001f53a", - ":registered:": "\u00ae\ufe0f", - ":relaxed:": "\u263a\ufe0f", - ":relieved:": "\U0001f60c", - ":relieved_face:": "\U0001f60c", - ":reminder_ribbon:": "\U0001f397\ufe0f", - ":repeat:": "\U0001f501", - ":repeat_button:": "\U0001f501", - ":repeat_one:": "\U0001f502", - ":repeat_single_button:": "\U0001f502", - ":rescue_worker_helmet:": "\u26d1\ufe0f", - ":rescue_worker’s_helmet:": "\u26d1", - ":restroom:": "\U0001f6bb", - ":reunion:": "\U0001f1f7\U0001f1ea", - ":reverse_button:": "\u25c0", - ":revolving_hearts:": "\U0001f49e", - ":rewind:": "\u23ea", - ":rhino:": "\U0001f98f", - ":rhinoceros:": "\U0001f98f", - ":ribbon:": "\U0001f380", - ":rice:": "\U0001f35a", - ":rice_ball:": "\U0001f359", - ":rice_cracker:": "\U0001f358", - ":rice_scene:": "\U0001f391", - ":right-facing_fist:": "\U0001f91c", - ":right_anger_bubble:": "\U0001f5ef\ufe0f", - ":right_arrow:": "\u27a1", - ":right_arrow_curving_down:": "\u2935", - ":right_arrow_curving_left:": "\u21a9", - ":right_arrow_curving_up:": "\u2934", - ":right_facing_fist:": "\U0001f91c", - ":right_facing_fist_tone1:": "\U0001f91c\U0001f3fb", - ":right_facing_fist_tone2:": "\U0001f91c\U0001f3fc", - ":right_facing_fist_tone3:": "\U0001f91c\U0001f3fd", - ":right_facing_fist_tone4:": "\U0001f91c\U0001f3fe", - ":right_facing_fist_tone5:": "\U0001f91c\U0001f3ff", - ":ring:": "\U0001f48d", - ":ringed_planet:": "\U0001fa90", - ":roasted_sweet_potato:": "\U0001f360", - ":robot:": "\U0001f916", - ":robot_face:": "\U0001f916", - ":rock:": "\U0001faa8", - ":rocket:": "\U0001f680", - ":rofl:": "\U0001f923", - ":roll_eyes:": "\U0001f644", - ":roll_of_paper:": "\U0001f9fb", - ":rolled-up_newspaper:": "\U0001f5de", - ":rolled_up_newspaper:": "\U0001f5de\ufe0f", - ":roller_coaster:": "\U0001f3a2", - ":roller_skate:": "\U0001f6fc", - ":rolling_eyes:": "\U0001f644", - ":rolling_on_the_floor_laughing:": "\U0001f923", - ":romania:": "\U0001f1f7\U0001f1f4", - ":rooster:": "\U0001f413", - ":rose:": "\U0001f339", - ":rosette:": "\U0001f3f5\ufe0f", - ":rotating_light:": "\U0001f6a8", - ":round_pushpin:": "\U0001f4cd", - ":rowboat:": "\U0001f6a3\u200d\u2642\ufe0f", - ":rowing_man:": "\U0001f6a3\u200d\u2642\ufe0f", - ":rowing_woman:": "\U0001f6a3\u200d\u2640\ufe0f", - ":ru:": "\U0001f1f7\U0001f1fa", - ":rugby_football:": "\U0001f3c9", - ":runner:": "\U0001f3c3\u200d\u2642\ufe0f", - ":running:": "\U0001f3c3", - ":running_man:": "\U0001f3c3\u200d\u2642\ufe0f", - ":running_shirt:": "\U0001f3bd", - ":running_shirt_with_sash:": "\U0001f3bd", - ":running_shoe:": "\U0001f45f", - ":running_woman:": "\U0001f3c3\u200d\u2640\ufe0f", - ":rwanda:": "\U0001f1f7\U0001f1fc", - ":sa:": "\U0001f202\ufe0f", - ":sad_but_relieved_face:": "\U0001f625", - ":safety_pin:": "\U0001f9f7", - ":safety_vest:": "\U0001f9ba", - ":sagittarius:": "\u2650", - ":sailboat:": "\u26f5", - ":sake:": "\U0001f376", - ":salad:": "\U0001f957", - ":salt:": "\U0001f9c2", - ":samoa:": "\U0001f1fc\U0001f1f8", - ":san_marino:": "\U0001f1f8\U0001f1f2", - ":sandal:": "\U0001f461", - ":sandwich:": "\U0001f96a", - ":santa:": "\U0001f385", - ":santa_tone1:": "\U0001f385\U0001f3fb", - ":santa_tone2:": "\U0001f385\U0001f3fc", - ":santa_tone3:": "\U0001f385\U0001f3fd", - ":santa_tone4:": "\U0001f385\U0001f3fe", - ":santa_tone5:": "\U0001f385\U0001f3ff", - ":sao_tome_principe:": "\U0001f1f8\U0001f1f9", - ":sari:": "\U0001f97b", - ":sassy_man:": "\U0001f481\u200d\u2642\ufe0f", - ":sassy_woman:": "\U0001f481\u200d\u2640\ufe0f", - ":satellite:": "\U0001f6f0\ufe0f", - ":satellite_antenna:": "\U0001f4e1", - ":satellite_orbital:": "\U0001f6f0", - ":satisfied:": "\U0001f606", - ":saudi_arabia:": "\U0001f1f8\U0001f1e6", - ":sauna_man:": "\U0001f9d6\u200d\u2642\ufe0f", - ":sauna_person:": "\U0001f9d6", - ":sauna_woman:": "\U0001f9d6\u200d\u2640\ufe0f", - ":sauropod:": "\U0001f995", - ":saxophone:": "\U0001f3b7", - ":scales:": "\u2696\ufe0f", - ":scarf:": "\U0001f9e3", - ":school:": "\U0001f3eb", - ":school_satchel:": "\U0001f392", - ":scientist:": "\U0001f9d1\u200d\U0001f52c", - ":scissors:": "\u2702\ufe0f", - ":scooter:": "\U0001f6f4", - ":scorpion:": "\U0001f982", - ":scorpius:": "\u264f", - ":scotland:": "\U0001f3f4\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f", - ":scream:": "\U0001f631", - ":scream_cat:": "\U0001f640", - ":screwdriver:": "\U0001fa9b", - ":scroll:": "\U0001f4dc", - ":seal:": "\U0001f9ad", - ":seat:": "\U0001f4ba", - ":second_place:": "\U0001f948", - ":second_place_medal:": "\U0001f948", - ":secret:": "\u3299\ufe0f", - ":see-no-evil_monkey:": "\U0001f648", - ":see_no_evil:": "\U0001f648", - ":seedling:": "\U0001f331", - ":selfie:": "\U0001f933", - ":selfie_tone1:": "\U0001f933\U0001f3fb", - ":selfie_tone2:": "\U0001f933\U0001f3fc", - ":selfie_tone3:": "\U0001f933\U0001f3fd", - ":selfie_tone4:": "\U0001f933\U0001f3fe", - ":selfie_tone5:": "\U0001f933\U0001f3ff", - ":senegal:": "\U0001f1f8\U0001f1f3", - ":serbia:": "\U0001f1f7\U0001f1f8", - ":service_dog:": "\U0001f415\u200d\U0001f9ba", - ":seven:": "7\ufe0f\u20e3", - ":seven-thirty:": "\U0001f562", - ":seven_o’clock:": "\U0001f556", - ":sewing_needle:": "\U0001faa1", - ":seychelles:": "\U0001f1f8\U0001f1e8", - ":shallow_pan_of_food:": "\U0001f958", - ":shamrock:": "\u2618\ufe0f", - ":shark:": "\U0001f988", - ":shaved_ice:": "\U0001f367", - ":sheaf_of_rice:": "\U0001f33e", - ":sheep:": "\U0001f411", - ":shell:": "\U0001f41a", - ":shield:": "\U0001f6e1\ufe0f", - ":shinto_shrine:": "\u26e9\ufe0f", - ":ship:": "\U0001f6a2", - ":shirt:": "\U0001f455", - ":shit:": "\U0001f4a9", - ":shoe:": "\U0001f45e", - ":shooting_star:": "\U0001f320", - ":shopping:": "\U0001f6cd\ufe0f", - ":shopping_bags:": "\U0001f6cd\ufe0f", - ":shopping_cart:": "\U0001f6d2", - ":shopping_trolley:": "\U0001f6d2", - ":shortcake:": "\U0001f370", - ":shorts:": "\U0001fa73", - ":shower:": "\U0001f6bf", - ":shrimp:": "\U0001f990", - ":shrug:": "\U0001f937", - ":shuffle_tracks_button:": "\U0001f500", - ":shushing_face:": "\U0001f92b", - ":sierra_leone:": "\U0001f1f8\U0001f1f1", - ":sign_of_the_horns:": "\U0001f918", - ":signal_strength:": "\U0001f4f6", - ":singapore:": "\U0001f1f8\U0001f1ec", - ":singer:": "\U0001f9d1\u200d\U0001f3a4", - ":sint_maarten:": "\U0001f1f8\U0001f1fd", - ":six:": "6\ufe0f\u20e3", - ":six-thirty:": "\U0001f561", - ":six_o’clock:": "\U0001f555", - ":six_pointed_star:": "\U0001f52f", - ":skateboard:": "\U0001f6f9", - ":ski:": "\U0001f3bf", - ":skier:": "\u26f7\ufe0f", - ":skin-tone-2:": "\U0001f3fb", - ":skin-tone-3:": "\U0001f3fc", - ":skin-tone-4:": "\U0001f3fd", - ":skin-tone-5:": "\U0001f3fe", - ":skin-tone-6:": "\U0001f3ff", - ":skis:": "\U0001f3bf", - ":skull:": "\U0001f480", - ":skull_and_crossbones:": "\u2620\ufe0f", - ":skull_crossbones:": "\u2620", - ":skunk:": "\U0001f9a8", - ":sled:": "\U0001f6f7", - ":sleeping:": "\U0001f634", - ":sleeping_accommodation:": "\U0001f6cc", - ":sleeping_bed:": "\U0001f6cc", - ":sleeping_face:": "\U0001f634", - ":sleepy:": "\U0001f62a", - ":sleepy_face:": "\U0001f62a", - ":sleuth_or_spy:": "\U0001f575\ufe0f\u200d\u2642\ufe0f", - ":slight_frown:": "\U0001f641", - ":slight_smile:": "\U0001f642", - ":slightly_frowning_face:": "\U0001f641", - ":slightly_smiling_face:": "\U0001f642", - ":slot_machine:": "\U0001f3b0", - ":sloth:": "\U0001f9a5", - ":slovakia:": "\U0001f1f8\U0001f1f0", - ":slovenia:": "\U0001f1f8\U0001f1ee", - ":small_airplane:": "\U0001f6e9\ufe0f", - ":small_blue_diamond:": "\U0001f539", - ":small_orange_diamond:": "\U0001f538", - ":small_red_triangle:": "\U0001f53a", - ":small_red_triangle_down:": "\U0001f53b", - ":smile:": "\U0001f604", - ":smile_cat:": "\U0001f638", - ":smiley:": "\U0001f603", - ":smiley_cat:": "\U0001f63a", - ":smiling_cat_with_heart-eyes:": "\U0001f63b", - ":smiling_face:": "\u263a", - ":smiling_face_with_3_hearts:": "\U0001f970", - ":smiling_face_with_halo:": "\U0001f607", - ":smiling_face_with_heart-eyes:": "\U0001f60d", - ":smiling_face_with_hearts:": "\U0001f970", - ":smiling_face_with_horns:": "\U0001f608", - ":smiling_face_with_smiling_eyes:": "\U0001f60a", - ":smiling_face_with_sunglasses:": "\U0001f60e", - ":smiling_face_with_tear:": "\U0001f972", - ":smiling_face_with_three_hearts:": "\U0001f970", - ":smiling_imp:": "\U0001f608", - ":smirk:": "\U0001f60f", - ":smirk_cat:": "\U0001f63c", - ":smirking_face:": "\U0001f60f", - ":smoking:": "\U0001f6ac", - ":snail:": "\U0001f40c", - ":snake:": "\U0001f40d", - ":sneezing_face:": "\U0001f927", - ":snow-capped_mountain:": "\U0001f3d4", - ":snow_capped_mountain:": "\U0001f3d4\ufe0f", - ":snow_cloud:": "\U0001f328\ufe0f", - ":snowboarder:": "\U0001f3c2", - ":snowboarder_tone1:": "\U0001f3c2\U0001f3fb", - ":snowboarder_tone2:": "\U0001f3c2\U0001f3fc", - ":snowboarder_tone3:": "\U0001f3c2\U0001f3fd", - ":snowboarder_tone4:": "\U0001f3c2\U0001f3fe", - ":snowboarder_tone5:": "\U0001f3c2\U0001f3ff", - ":snowflake:": "\u2744\ufe0f", - ":snowman:": "\u2603\ufe0f", - ":snowman2:": "\u2603", - ":snowman_with_snow:": "\u2603\ufe0f", - ":snowman_without_snow:": "\u26c4", - ":soap:": "\U0001f9fc", - ":sob:": "\U0001f62d", - ":soccer:": "\u26bd", - ":soccer_ball:": "\u26bd", - ":socks:": "\U0001f9e6", - ":soft_ice_cream:": "\U0001f366", - ":softball:": "\U0001f94e", - ":solomon_islands:": "\U0001f1f8\U0001f1e7", - ":somalia:": "\U0001f1f8\U0001f1f4", - ":soon:": "\U0001f51c", - ":sos:": "\U0001f198", - ":sound:": "\U0001f509", - ":south_africa:": "\U0001f1ff\U0001f1e6", - ":south_georgia_south_sandwich_islands:": "\U0001f1ec\U0001f1f8", - ":south_sudan:": "\U0001f1f8\U0001f1f8", - ":space_invader:": "\U0001f47e", - ":spade_suit:": "\u2660", - ":spades:": "\u2660\ufe0f", - ":spaghetti:": "\U0001f35d", - ":sparkle:": "\u2747\ufe0f", - ":sparkler:": "\U0001f387", - ":sparkles:": "\u2728", - ":sparkling_heart:": "\U0001f496", - ":speak-no-evil_monkey:": "\U0001f64a", - ":speak_no_evil:": "\U0001f64a", - ":speaker:": "\U0001f508", - ":speaker_high_volume:": "\U0001f50a", - ":speaker_low_volume:": "\U0001f508", - ":speaker_medium_volume:": "\U0001f509", - ":speaking_head:": "\U0001f5e3", - ":speaking_head_in_silhouette:": "\U0001f5e3\ufe0f", - ":speech_balloon:": "\U0001f4ac", - ":speech_left:": "\U0001f5e8", - ":speedboat:": "\U0001f6a4", - ":spider:": "\U0001f577\ufe0f", - ":spider_web:": "\U0001f578\ufe0f", - ":spiral_calendar:": "\U0001f5d3", - ":spiral_calendar_pad:": "\U0001f5d3\ufe0f", - ":spiral_note_pad:": "\U0001f5d2\ufe0f", - ":spiral_notepad:": "\U0001f5d2", - ":spiral_shell:": "\U0001f41a", - ":spock-hand:": "\U0001f596", - ":sponge:": "\U0001f9fd", - ":spoon:": "\U0001f944", - ":sport_utility_vehicle:": "\U0001f699", - ":sports_medal:": "\U0001f3c5", - ":spouting_whale:": "\U0001f433", - ":squid:": "\U0001f991", - ":squinting_face_with_tongue:": "\U0001f61d", - ":sri_lanka:": "\U0001f1f1\U0001f1f0", - ":st_barthelemy:": "\U0001f1e7\U0001f1f1", - ":st_helena:": "\U0001f1f8\U0001f1ed", - ":st_kitts_nevis:": "\U0001f1f0\U0001f1f3", - ":st_lucia:": "\U0001f1f1\U0001f1e8", - ":st_martin:": "\U0001f1f2\U0001f1eb", - ":st_pierre_miquelon:": "\U0001f1f5\U0001f1f2", - ":st_vincent_grenadines:": "\U0001f1fb\U0001f1e8", - ":stadium:": "\U0001f3df\ufe0f", - ":standing_man:": "\U0001f9cd\u200d\u2642\ufe0f", - ":standing_person:": "\U0001f9cd", - ":standing_woman:": "\U0001f9cd\u200d\u2640\ufe0f", - ":star:": "\u2b50", - ":star-struck:": "\U0001f929", - ":star2:": "\U0001f31f", - ":star_and_crescent:": "\u262a\ufe0f", - ":star_of_David:": "\u2721", - ":star_of_david:": "\u2721\ufe0f", - ":star_struck:": "\U0001f929", - ":stars:": "\U0001f320", - ":station:": "\U0001f689", - ":statue_of_liberty:": "\U0001f5fd", - ":steam_locomotive:": "\U0001f682", - ":steaming_bowl:": "\U0001f35c", - ":stethoscope:": "\U0001fa7a", - ":stew:": "\U0001f372", - ":stop_button:": "\u23f9", - ":stop_sign:": "\U0001f6d1", - ":stopwatch:": "\u23f1\ufe0f", - ":straight_ruler:": "\U0001f4cf", - ":strawberry:": "\U0001f353", - ":stuck_out_tongue:": "\U0001f61b", - ":stuck_out_tongue_closed_eyes:": "\U0001f61d", - ":stuck_out_tongue_winking_eye:": "\U0001f61c", - ":student:": "\U0001f9d1\u200d\U0001f393", - ":studio_microphone:": "\U0001f399\ufe0f", - ":stuffed_flatbread:": "\U0001f959", - ":sudan:": "\U0001f1f8\U0001f1e9", - ":sun:": "\u2600", - ":sun_behind_cloud:": "\u26c5", - ":sun_behind_large_cloud:": "\U0001f325", - ":sun_behind_rain_cloud:": "\U0001f326", - ":sun_behind_small_cloud:": "\U0001f324", - ":sun_with_face:": "\U0001f31e", - ":sunflower:": "\U0001f33b", - ":sunglasses:": "\U0001f60e", - ":sunny:": "\u2600\ufe0f", - ":sunrise:": "\U0001f305", - ":sunrise_over_mountains:": "\U0001f304", - ":sunset:": "\U0001f307", - ":superhero:": "\U0001f9b8", - ":superhero_man:": "\U0001f9b8\u200d\u2642\ufe0f", - ":superhero_woman:": "\U0001f9b8\u200d\u2640\ufe0f", - ":supervillain:": "\U0001f9b9", - ":supervillain_man:": "\U0001f9b9\u200d\u2642\ufe0f", - ":supervillain_woman:": "\U0001f9b9\u200d\u2640\ufe0f", - ":surfer:": "\U0001f3c4\u200d\u2642\ufe0f", - ":surfing_man:": "\U0001f3c4\u200d\u2642\ufe0f", - ":surfing_woman:": "\U0001f3c4\u200d\u2640\ufe0f", - ":suriname:": "\U0001f1f8\U0001f1f7", - ":sushi:": "\U0001f363", - ":suspension_railway:": "\U0001f69f", - ":svalbard_jan_mayen:": "\U0001f1f8\U0001f1ef", - ":swan:": "\U0001f9a2", - ":swaziland:": "\U0001f1f8\U0001f1ff", - ":sweat:": "\U0001f613", - ":sweat_droplets:": "\U0001f4a6", - ":sweat_drops:": "\U0001f4a6", - ":sweat_smile:": "\U0001f605", - ":sweden:": "\U0001f1f8\U0001f1ea", - ":sweet_potato:": "\U0001f360", - ":swim_brief:": "\U0001fa72", - ":swimmer:": "\U0001f3ca\u200d\u2642\ufe0f", - ":swimming_man:": "\U0001f3ca\u200d\u2642\ufe0f", - ":swimming_woman:": "\U0001f3ca\u200d\u2640\ufe0f", - ":switzerland:": "\U0001f1e8\U0001f1ed", - ":symbols:": "\U0001f523", - ":synagogue:": "\U0001f54d", - ":syria:": "\U0001f1f8\U0001f1fe", - ":syringe:": "\U0001f489", - ":t-rex:": "\U0001f996", - ":t-shirt:": "\U0001f455", - ":t_rex:": "\U0001f996", - ":table_tennis_paddle_and_ball:": "\U0001f3d3", - ":taco:": "\U0001f32e", - ":tada:": "\U0001f389", - ":taiwan:": "\U0001f1f9\U0001f1fc", - ":tajikistan:": "\U0001f1f9\U0001f1ef", - ":takeout_box:": "\U0001f961", - ":tamale:": "\U0001fad4", - ":tanabata_tree:": "\U0001f38b", - ":tangerine:": "\U0001f34a", - ":tanzania:": "\U0001f1f9\U0001f1ff", - ":taurus:": "\u2649", - ":taxi:": "\U0001f695", - ":tea:": "\U0001f375", - ":teacher:": "\U0001f9d1\u200d\U0001f3eb", - ":teacup_without_handle:": "\U0001f375", - ":teapot:": "\U0001fad6", - ":tear-off_calendar:": "\U0001f4c6", - ":technologist:": "\U0001f9d1\u200d\U0001f4bb", - ":teddy_bear:": "\U0001f9f8", - ":telephone:": "\u260e", - ":telephone_receiver:": "\U0001f4de", - ":telescope:": "\U0001f52d", - ":television:": "\U0001f4fa", - ":ten-thirty:": "\U0001f565", - ":ten_o’clock:": "\U0001f559", - ":tennis:": "\U0001f3be", - ":tent:": "\u26fa", - ":test_tube:": "\U0001f9ea", - ":thailand:": "\U0001f1f9\U0001f1ed", - ":the_horns:": "\U0001f918", - ":thermometer:": "\U0001f321\ufe0f", - ":thermometer_face:": "\U0001f912", - ":thinking:": "\U0001f914", - ":thinking_face:": "\U0001f914", - ":third_place:": "\U0001f949", - ":third_place_medal:": "\U0001f949", - ":thong_sandal:": "\U0001fa74", - ":thought_balloon:": "\U0001f4ad", - ":thread:": "\U0001f9f5", - ":three:": "3\ufe0f\u20e3", - ":three-thirty:": "\U0001f55e", - ":three_button_mouse:": "\U0001f5b1\ufe0f", - ":three_o’clock:": "\U0001f552", - ":thumbs_down:": "\U0001f44e", - ":thumbs_up:": "\U0001f44d", - ":thumbsdown:": "\U0001f44e", - ":thumbsdown_tone1:": "\U0001f44e\U0001f3fb", - ":thumbsdown_tone2:": "\U0001f44e\U0001f3fc", - ":thumbsdown_tone3:": "\U0001f44e\U0001f3fd", - ":thumbsdown_tone4:": "\U0001f44e\U0001f3fe", - ":thumbsdown_tone5:": "\U0001f44e\U0001f3ff", - ":thumbsup:": "\U0001f44d", - ":thumbsup_tone1:": "\U0001f44d\U0001f3fb", - ":thumbsup_tone2:": "\U0001f44d\U0001f3fc", - ":thumbsup_tone3:": "\U0001f44d\U0001f3fd", - ":thumbsup_tone4:": "\U0001f44d\U0001f3fe", - ":thumbsup_tone5:": "\U0001f44d\U0001f3ff", - ":thunder_cloud_and_rain:": "\u26c8\ufe0f", - ":thunder_cloud_rain:": "\u26c8", - ":ticket:": "\U0001f3ab", - ":tickets:": "\U0001f39f", - ":tiger:": "\U0001f42f", - ":tiger2:": "\U0001f405", - ":tiger_face:": "\U0001f42f", - ":timer:": "\u23f2", - ":timer_clock:": "\u23f2\ufe0f", - ":timor_leste:": "\U0001f1f9\U0001f1f1", - ":tipping_hand_man:": "\U0001f481\u200d\u2642\ufe0f", - ":tipping_hand_person:": "\U0001f481", - ":tipping_hand_woman:": "\U0001f481\u200d\u2640\ufe0f", - ":tired_face:": "\U0001f62b", - ":tm:": "\u2122\ufe0f", - ":togo:": "\U0001f1f9\U0001f1ec", - ":toilet:": "\U0001f6bd", - ":tokelau:": "\U0001f1f9\U0001f1f0", - ":tokyo_tower:": "\U0001f5fc", - ":tomato:": "\U0001f345", - ":tonga:": "\U0001f1f9\U0001f1f4", - ":tongue:": "\U0001f445", - ":toolbox:": "\U0001f9f0", - ":tools:": "\U0001f6e0", - ":tooth:": "\U0001f9b7", - ":toothbrush:": "\U0001faa5", - ":top:": "\U0001f51d", - ":top_hat:": "\U0001f3a9", - ":tophat:": "\U0001f3a9", - ":tornado:": "\U0001f32a\ufe0f", - ":tr:": "\U0001f1f9\U0001f1f7", - ":track_next:": "\u23ed", - ":track_previous:": "\u23ee", - ":trackball:": "\U0001f5b2\ufe0f", - ":tractor:": "\U0001f69c", - ":trade_mark:": "\u2122", - ":traffic_light:": "\U0001f6a5", - ":train:": "\U0001f68b", - ":train2:": "\U0001f686", - ":tram:": "\U0001f68a", - ":tram_car:": "\U0001f68b", - ":transgender_flag:": "\U0001f3f3\ufe0f\u200d\u26a7\ufe0f", - ":transgender_symbol:": "\u26a7\ufe0f", - ":triangular_flag:": "\U0001f6a9", - ":triangular_flag_on_post:": "\U0001f6a9", - ":triangular_ruler:": "\U0001f4d0", - ":trident:": "\U0001f531", - ":trident_emblem:": "\U0001f531", - ":trinidad_tobago:": "\U0001f1f9\U0001f1f9", - ":tristan_da_cunha:": "\U0001f1f9\U0001f1e6", - ":triumph:": "\U0001f624", - ":trolleybus:": "\U0001f68e", - ":trophy:": "\U0001f3c6", - ":tropical_drink:": "\U0001f379", - ":tropical_fish:": "\U0001f420", - ":truck:": "\U0001f69a", - ":trumpet:": "\U0001f3ba", - ":tshirt:": "\U0001f455", - ":tulip:": "\U0001f337", - ":tumbler_glass:": "\U0001f943", - ":tunisia:": "\U0001f1f9\U0001f1f3", - ":turkey:": "\U0001f983", - ":turkmenistan:": "\U0001f1f9\U0001f1f2", - ":turks_caicos_islands:": "\U0001f1f9\U0001f1e8", - ":turtle:": "\U0001f422", - ":tuvalu:": "\U0001f1f9\U0001f1fb", - ":tv:": "\U0001f4fa", - ":twelve-thirty:": "\U0001f567", - ":twelve_o’clock:": "\U0001f55b", - ":twisted_rightwards_arrows:": "\U0001f500", - ":two:": "2\ufe0f\u20e3", - ":two-hump_camel:": "\U0001f42b", - ":two-thirty:": "\U0001f55d", - ":two_hearts:": "\U0001f495", - ":two_men_holding_hands:": "\U0001f46c", - ":two_o’clock:": "\U0001f551", - ":two_women_holding_hands:": "\U0001f46d", - ":u5272:": "\U0001f239", - ":u5408:": "\U0001f234", - ":u55b6:": "\U0001f23a", - ":u6307:": "\U0001f22f", - ":u6708:": "\U0001f237\ufe0f", - ":u6709:": "\U0001f236", - ":u6e80:": "\U0001f235", - ":u7121:": "\U0001f21a", - ":u7533:": "\U0001f238", - ":u7981:": "\U0001f232", - ":u7a7a:": "\U0001f233", - ":uganda:": "\U0001f1fa\U0001f1ec", - ":uk:": "\U0001f1ec\U0001f1e7", - ":ukraine:": "\U0001f1fa\U0001f1e6", - ":umbrella:": "\u2602\ufe0f", - ":umbrella2:": "\u2602", - ":umbrella_on_ground:": "\u26f1\ufe0f", - ":umbrella_with_rain_drops:": "\u2614", - ":unamused:": "\U0001f612", - ":unamused_face:": "\U0001f612", - ":underage:": "\U0001f51e", - ":unicorn:": "\U0001f984", - ":unicorn_face:": "\U0001f984", - ":united_arab_emirates:": "\U0001f1e6\U0001f1ea", - ":united_nations:": "\U0001f1fa\U0001f1f3", - ":unlock:": "\U0001f513", - ":unlocked:": "\U0001f513", - ":up:": "\U0001f199", - ":up-down_arrow:": "\u2195", - ":up-left_arrow:": "\u2196", - ":up-right_arrow:": "\u2197", - ":up_arrow:": "\u2b06", - ":upside-down_face:": "\U0001f643", - ":upside_down:": "\U0001f643", - ":upside_down_face:": "\U0001f643", - ":upwards_button:": "\U0001f53c", - ":urn:": "\u26b1", - ":uruguay:": "\U0001f1fa\U0001f1fe", - ":us:": "\U0001f1fa\U0001f1f8", - ":us_outlying_islands:": "\U0001f1fa\U0001f1f2", - ":us_virgin_islands:": "\U0001f1fb\U0001f1ee", - ":uzbekistan:": "\U0001f1fa\U0001f1ff", - ":v:": "\u270c\ufe0f", - ":v_tone1:": "\u270c\U0001f3fb", - ":v_tone2:": "\u270c\U0001f3fc", - ":v_tone3:": "\u270c\U0001f3fd", - ":v_tone4:": "\u270c\U0001f3fe", - ":v_tone5:": "\u270c\U0001f3ff", - ":vampire:": "\U0001f9db\u200d\u2640\ufe0f", - ":vampire_man:": "\U0001f9db\u200d\u2642\ufe0f", - ":vampire_tone1:": "\U0001f9db\U0001f3fb", - ":vampire_tone2:": "\U0001f9db\U0001f3fc", - ":vampire_tone3:": "\U0001f9db\U0001f3fd", - ":vampire_tone4:": "\U0001f9db\U0001f3fe", - ":vampire_tone5:": "\U0001f9db\U0001f3ff", - ":vampire_woman:": "\U0001f9db\u200d\u2640\ufe0f", - ":vanuatu:": "\U0001f1fb\U0001f1fa", - ":vatican_city:": "\U0001f1fb\U0001f1e6", - ":venezuela:": "\U0001f1fb\U0001f1ea", - ":vertical_traffic_light:": "\U0001f6a6", - ":vhs:": "\U0001f4fc", - ":vibration_mode:": "\U0001f4f3", - ":victory_hand:": "\u270c", - ":video_camera:": "\U0001f4f9", - ":video_game:": "\U0001f3ae", - ":videocassette:": "\U0001f4fc", - ":vietnam:": "\U0001f1fb\U0001f1f3", - ":violin:": "\U0001f3bb", - ":virgo:": "\u264d", - ":volcano:": "\U0001f30b", - ":volleyball:": "\U0001f3d0", - ":vomiting_face:": "\U0001f92e", - ":vs:": "\U0001f19a", - ":vulcan:": "\U0001f596", - ":vulcan_salute:": "\U0001f596", - ":vulcan_tone1:": "\U0001f596\U0001f3fb", - ":vulcan_tone2:": "\U0001f596\U0001f3fc", - ":vulcan_tone3:": "\U0001f596\U0001f3fd", - ":vulcan_tone4:": "\U0001f596\U0001f3fe", - ":vulcan_tone5:": "\U0001f596\U0001f3ff", - ":waffle:": "\U0001f9c7", - ":wales:": "\U0001f3f4\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f", - ":walking:": "\U0001f6b6\u200d\u2642\ufe0f", - ":walking_man:": "\U0001f6b6\u200d\u2642\ufe0f", - ":walking_woman:": "\U0001f6b6\u200d\u2640\ufe0f", - ":wallis_futuna:": "\U0001f1fc\U0001f1eb", - ":waning_crescent_moon:": "\U0001f318", - ":waning_gibbous_moon:": "\U0001f316", - ":warning:": "\u26a0\ufe0f", - ":wastebasket:": "\U0001f5d1\ufe0f", - ":watch:": "\u231a", - ":water_buffalo:": "\U0001f403", - ":water_closet:": "\U0001f6be", - ":water_pistol:": "\U0001f52b", - ":water_polo:": "\U0001f93d", - ":water_wave:": "\U0001f30a", - ":watermelon:": "\U0001f349", - ":wave:": "\U0001f44b", - ":wave_tone1:": "\U0001f44b\U0001f3fb", - ":wave_tone2:": "\U0001f44b\U0001f3fc", - ":wave_tone3:": "\U0001f44b\U0001f3fd", - ":wave_tone4:": "\U0001f44b\U0001f3fe", - ":wave_tone5:": "\U0001f44b\U0001f3ff", - ":waving_black_flag:": "\U0001f3f4", - ":waving_hand:": "\U0001f44b", - ":waving_white_flag:": "\U0001f3f3\ufe0f", - ":wavy_dash:": "\u3030\ufe0f", - ":waxing_crescent_moon:": "\U0001f312", - ":waxing_gibbous_moon:": "\U0001f314", - ":wc:": "\U0001f6be", - ":weary:": "\U0001f629", - ":weary_cat:": "\U0001f640", - ":weary_face:": "\U0001f629", - ":wedding:": "\U0001f492", - ":weight_lifter:": "\U0001f3cb\ufe0f\u200d\u2642\ufe0f", - ":weight_lifting:": "\U0001f3cb\ufe0f", - ":weight_lifting_man:": "\U0001f3cb\ufe0f\u200d\u2642\ufe0f", - ":weight_lifting_woman:": "\U0001f3cb\ufe0f\u200d\u2640\ufe0f", - ":western_sahara:": "\U0001f1ea\U0001f1ed", - ":whale:": "\U0001f433", - ":whale2:": "\U0001f40b", - ":wheel_of_dharma:": "\u2638\ufe0f", - ":wheelchair:": "\u267f", - ":wheelchair_symbol:": "\u267f", - ":white_cane:": "\U0001f9af", - ":white_check_mark:": "\u2705", - ":white_circle:": "\u26aa", - ":white_exclamation_mark:": "\u2755", - ":white_flag:": "\U0001f3f3", - ":white_flower:": "\U0001f4ae", - ":white_frowning_face:": "\u2639\ufe0f", - ":white_hair:": "\U0001f9b3", - ":white_haired_man:": "\U0001f468\u200d\U0001f9b3", - ":white_haired_person:": "\U0001f9d1\u200d\U0001f9b3", - ":white_haired_woman:": "\U0001f469\u200d\U0001f9b3", - ":white_heart:": "\U0001f90d", - ":white_large_square:": "\u2b1c", - ":white_medium-small_square:": "\u25fd", - ":white_medium_small_square:": "\u25fd", - ":white_medium_square:": "\u25fb\ufe0f", - ":white_question_mark:": "\u2754", - ":white_small_square:": "\u25ab\ufe0f", - ":white_square_button:": "\U0001f533", - ":white_sun_cloud:": "\U0001f325", - ":white_sun_rain_cloud:": "\U0001f326", - ":white_sun_small_cloud:": "\U0001f324", - ":wilted_flower:": "\U0001f940", - ":wilted_rose:": "\U0001f940", - ":wind_blowing_face:": "\U0001f32c\ufe0f", - ":wind_chime:": "\U0001f390", - ":wind_face:": "\U0001f32c", - ":window:": "\U0001fa9f", - ":wine_glass:": "\U0001f377", - ":wink:": "\U0001f609", - ":winking_face:": "\U0001f609", - ":winking_face_with_tongue:": "\U0001f61c", - ":wolf:": "\U0001f43a", - ":woman:": "\U0001f469", - ":woman-biking:": "\U0001f6b4\u200d\u2640\ufe0f", - ":woman-bouncing-ball:": "\u26f9\ufe0f\u200d\u2640\ufe0f", - ":woman-bowing:": "\U0001f647\u200d\u2640\ufe0f", - ":woman-boy:": "\U0001f469\u200d\U0001f466", - ":woman-boy-boy:": "\U0001f469\u200d\U0001f466\u200d\U0001f466", - ":woman-cartwheeling:": "\U0001f938\u200d\u2640\ufe0f", - ":woman-facepalming:": "\U0001f926\u200d\u2640\ufe0f", - ":woman-frowning:": "\U0001f64d\u200d\u2640\ufe0f", - ":woman-gesturing-no:": "\U0001f645\u200d\u2640\ufe0f", - ":woman-gesturing-ok:": "\U0001f646\u200d\u2640\ufe0f", - ":woman-getting-haircut:": "\U0001f487\u200d\u2640\ufe0f", - ":woman-getting-massage:": "\U0001f486\u200d\u2640\ufe0f", - ":woman-girl:": "\U0001f469\u200d\U0001f467", - ":woman-girl-boy:": "\U0001f469\u200d\U0001f467\u200d\U0001f466", - ":woman-girl-girl:": "\U0001f469\u200d\U0001f467\u200d\U0001f467", - ":woman-golfing:": "\U0001f3cc\ufe0f\u200d\u2640\ufe0f", - ":woman-heart-man:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468", - ":woman-heart-woman:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469", - ":woman-juggling:": "\U0001f939\u200d\u2640\ufe0f", - ":woman-kiss-man:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", - ":woman-kiss-woman:": "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469", - ":woman-lifting-weights:": "\U0001f3cb\ufe0f\u200d\u2640\ufe0f", - ":woman-mountain-biking:": "\U0001f6b5\u200d\u2640\ufe0f", - ":woman-playing-handball:": "\U0001f93e\u200d\u2640\ufe0f", - ":woman-playing-water-polo:": "\U0001f93d\u200d\u2640\ufe0f", - ":woman-pouting:": "\U0001f64e\u200d\u2640\ufe0f", - ":woman-raising-hand:": "\U0001f64b\u200d\u2640\ufe0f", - ":woman-rowing-boat:": "\U0001f6a3\u200d\u2640\ufe0f", - ":woman-running:": "\U0001f3c3\u200d\u2640\ufe0f", - ":woman-shrugging:": "\U0001f937\u200d\u2640\ufe0f", - ":woman-surfing:": "\U0001f3c4\u200d\u2640\ufe0f", - ":woman-swimming:": "\U0001f3ca\u200d\u2640\ufe0f", - ":woman-tipping-hand:": "\U0001f481\u200d\u2640\ufe0f", - ":woman-walking:": "\U0001f6b6\u200d\u2640\ufe0f", - ":woman-wearing-turban:": "\U0001f473\u200d\u2640\ufe0f", - ":woman-with-bunny-ears-partying:": "\U0001f46f\u200d\u2640\ufe0f", - ":woman-woman-boy:": "\U0001f469\u200d\U0001f469\u200d\U0001f466", - ":woman-woman-boy-boy:": "\U0001f469\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466", - ":woman-woman-girl:": "\U0001f469\u200d\U0001f469\u200d\U0001f467", - ":woman-woman-girl-boy:": "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466", - ":woman-woman-girl-girl:": "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467", - ":woman-wrestling:": "\U0001f93c\u200d\u2640\ufe0f", - ":woman_and_man_holding_hands:": "\U0001f46b", - ":woman_artist:": "\U0001f469\u200d\U0001f3a8", - ":woman_artist_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f3a8", - ":woman_artist_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f3a8", - ":woman_artist_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f3a8", - ":woman_artist_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f3a8", - ":woman_artist_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f3a8", - ":woman_astronaut:": "\U0001f469\u200d\U0001f680", - ":woman_astronaut_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f680", - ":woman_astronaut_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f680", - ":woman_astronaut_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f680", - ":woman_astronaut_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f680", - ":woman_astronaut_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f680", - ":woman_bald:": "\U0001f469\u200d\U0001f9b2", - ":woman_beard:": "\U0001f9d4\u200d\u2640\ufe0f", - ":woman_biking:": "\U0001f6b4\u200d\u2640\ufe0f", - ":woman_biking_tone1:": "\U0001f6b4\U0001f3fb\u200d\u2640\ufe0f", - ":woman_biking_tone2:": "\U0001f6b4\U0001f3fc\u200d\u2640\ufe0f", - ":woman_biking_tone3:": "\U0001f6b4\U0001f3fd\u200d\u2640\ufe0f", - ":woman_biking_tone4:": "\U0001f6b4\U0001f3fe\u200d\u2640\ufe0f", - ":woman_biking_tone5:": "\U0001f6b4\U0001f3ff\u200d\u2640\ufe0f", - ":woman_blond_hair:": "\U0001f471\u200d\u2640\ufe0f", - ":woman_bouncing_ball:": "\u26f9\ufe0f\u200d\u2640\ufe0f", - ":woman_bouncing_ball_tone1:": "\u26f9\U0001f3fb\u200d\u2640\ufe0f", - ":woman_bouncing_ball_tone2:": "\u26f9\U0001f3fc\u200d\u2640\ufe0f", - ":woman_bouncing_ball_tone3:": "\u26f9\U0001f3fd\u200d\u2640\ufe0f", - ":woman_bouncing_ball_tone4:": "\u26f9\U0001f3fe\u200d\u2640\ufe0f", - ":woman_bouncing_ball_tone5:": "\u26f9\U0001f3ff\u200d\u2640\ufe0f", - ":woman_bowing:": "\U0001f647\u200d\u2640\ufe0f", - ":woman_bowing_tone1:": "\U0001f647\U0001f3fb\u200d\u2640\ufe0f", - ":woman_bowing_tone2:": "\U0001f647\U0001f3fc\u200d\u2640\ufe0f", - ":woman_bowing_tone3:": "\U0001f647\U0001f3fd\u200d\u2640\ufe0f", - ":woman_bowing_tone4:": "\U0001f647\U0001f3fe\u200d\u2640\ufe0f", - ":woman_bowing_tone5:": "\U0001f647\U0001f3ff\u200d\u2640\ufe0f", - ":woman_cartwheeling:": "\U0001f938\u200d\u2640\ufe0f", - ":woman_cartwheeling_tone1:": "\U0001f938\U0001f3fb\u200d\u2640\ufe0f", - ":woman_cartwheeling_tone2:": "\U0001f938\U0001f3fc\u200d\u2640\ufe0f", - ":woman_cartwheeling_tone3:": "\U0001f938\U0001f3fd\u200d\u2640\ufe0f", - ":woman_cartwheeling_tone4:": "\U0001f938\U0001f3fe\u200d\u2640\ufe0f", - ":woman_cartwheeling_tone5:": "\U0001f938\U0001f3ff\u200d\u2640\ufe0f", - ":woman_climbing:": "\U0001f9d7\u200d\u2640\ufe0f", - ":woman_climbing_tone1:": "\U0001f9d7\U0001f3fb\u200d\u2640\ufe0f", - ":woman_climbing_tone2:": "\U0001f9d7\U0001f3fc\u200d\u2640\ufe0f", - ":woman_climbing_tone3:": "\U0001f9d7\U0001f3fd\u200d\u2640\ufe0f", - ":woman_climbing_tone4:": "\U0001f9d7\U0001f3fe\u200d\u2640\ufe0f", - ":woman_climbing_tone5:": "\U0001f9d7\U0001f3ff\u200d\u2640\ufe0f", - ":woman_construction_worker:": "\U0001f477\u200d\u2640\ufe0f", - ":woman_construction_worker_tone1:": "\U0001f477\U0001f3fb\u200d\u2640\ufe0f", - ":woman_construction_worker_tone2:": "\U0001f477\U0001f3fc\u200d\u2640\ufe0f", - ":woman_construction_worker_tone3:": "\U0001f477\U0001f3fd\u200d\u2640\ufe0f", - ":woman_construction_worker_tone4:": "\U0001f477\U0001f3fe\u200d\u2640\ufe0f", - ":woman_construction_worker_tone5:": "\U0001f477\U0001f3ff\u200d\u2640\ufe0f", - ":woman_cook:": "\U0001f469\u200d\U0001f373", - ":woman_cook_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f373", - ":woman_cook_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f373", - ":woman_cook_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f373", - ":woman_cook_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f373", - ":woman_cook_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f373", - ":woman_curly_hair:": "\U0001f469\u200d\U0001f9b1", - ":woman_dancing:": "\U0001f483", - ":woman_detective:": "\U0001f575\ufe0f\u200d\u2640\ufe0f", - ":woman_detective_tone1:": "\U0001f575\U0001f3fb\u200d\u2640\ufe0f", - ":woman_detective_tone2:": "\U0001f575\U0001f3fc\u200d\u2640\ufe0f", - ":woman_detective_tone3:": "\U0001f575\U0001f3fd\u200d\u2640\ufe0f", - ":woman_detective_tone4:": "\U0001f575\U0001f3fe\u200d\u2640\ufe0f", - ":woman_detective_tone5:": "\U0001f575\U0001f3ff\u200d\u2640\ufe0f", - ":woman_elf:": "\U0001f9dd\u200d\u2640\ufe0f", - ":woman_elf_tone1:": "\U0001f9dd\U0001f3fb\u200d\u2640\ufe0f", - ":woman_elf_tone2:": "\U0001f9dd\U0001f3fc\u200d\u2640\ufe0f", - ":woman_elf_tone3:": "\U0001f9dd\U0001f3fd\u200d\u2640\ufe0f", - ":woman_elf_tone4:": "\U0001f9dd\U0001f3fe\u200d\u2640\ufe0f", - ":woman_elf_tone5:": "\U0001f9dd\U0001f3ff\u200d\u2640\ufe0f", - ":woman_facepalming:": "\U0001f926\u200d\u2640\ufe0f", - ":woman_facepalming_tone1:": "\U0001f926\U0001f3fb\u200d\u2640\ufe0f", - ":woman_facepalming_tone2:": "\U0001f926\U0001f3fc\u200d\u2640\ufe0f", - ":woman_facepalming_tone3:": "\U0001f926\U0001f3fd\u200d\u2640\ufe0f", - ":woman_facepalming_tone4:": "\U0001f926\U0001f3fe\u200d\u2640\ufe0f", - ":woman_facepalming_tone5:": "\U0001f926\U0001f3ff\u200d\u2640\ufe0f", - ":woman_factory_worker:": "\U0001f469\u200d\U0001f3ed", - ":woman_factory_worker_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f3ed", - ":woman_factory_worker_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f3ed", - ":woman_factory_worker_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f3ed", - ":woman_factory_worker_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f3ed", - ":woman_factory_worker_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f3ed", - ":woman_fairy:": "\U0001f9da\u200d\u2640\ufe0f", - ":woman_fairy_tone1:": "\U0001f9da\U0001f3fb\u200d\u2640\ufe0f", - ":woman_fairy_tone2:": "\U0001f9da\U0001f3fc\u200d\u2640\ufe0f", - ":woman_fairy_tone3:": "\U0001f9da\U0001f3fd\u200d\u2640\ufe0f", - ":woman_fairy_tone4:": "\U0001f9da\U0001f3fe\u200d\u2640\ufe0f", - ":woman_fairy_tone5:": "\U0001f9da\U0001f3ff\u200d\u2640\ufe0f", - ":woman_farmer:": "\U0001f469\u200d\U0001f33e", - ":woman_farmer_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f33e", - ":woman_farmer_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f33e", - ":woman_farmer_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f33e", - ":woman_farmer_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f33e", - ":woman_farmer_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f33e", - ":woman_feeding_baby:": "\U0001f469\u200d\U0001f37c", - ":woman_firefighter:": "\U0001f469\u200d\U0001f692", - ":woman_firefighter_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f692", - ":woman_firefighter_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f692", - ":woman_firefighter_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f692", - ":woman_firefighter_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f692", - ":woman_firefighter_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f692", - ":woman_frowning:": "\U0001f64d\u200d\u2640\ufe0f", - ":woman_frowning_tone1:": "\U0001f64d\U0001f3fb\u200d\u2640\ufe0f", - ":woman_frowning_tone2:": "\U0001f64d\U0001f3fc\u200d\u2640\ufe0f", - ":woman_frowning_tone3:": "\U0001f64d\U0001f3fd\u200d\u2640\ufe0f", - ":woman_frowning_tone4:": "\U0001f64d\U0001f3fe\u200d\u2640\ufe0f", - ":woman_frowning_tone5:": "\U0001f64d\U0001f3ff\u200d\u2640\ufe0f", - ":woman_genie:": "\U0001f9de\u200d\u2640\ufe0f", - ":woman_gesturing_NO:": "\U0001f645\u200d\u2640\ufe0f", - ":woman_gesturing_OK:": "\U0001f646\u200d\u2640\ufe0f", - ":woman_gesturing_no:": "\U0001f645\u200d\u2640\ufe0f", - ":woman_gesturing_no_tone1:": "\U0001f645\U0001f3fb\u200d\u2640\ufe0f", - ":woman_gesturing_no_tone2:": "\U0001f645\U0001f3fc\u200d\u2640\ufe0f", - ":woman_gesturing_no_tone3:": "\U0001f645\U0001f3fd\u200d\u2640\ufe0f", - ":woman_gesturing_no_tone4:": "\U0001f645\U0001f3fe\u200d\u2640\ufe0f", - ":woman_gesturing_no_tone5:": "\U0001f645\U0001f3ff\u200d\u2640\ufe0f", - ":woman_gesturing_ok:": "\U0001f646\u200d\u2640\ufe0f", - ":woman_gesturing_ok_tone1:": "\U0001f646\U0001f3fb\u200d\u2640\ufe0f", - ":woman_gesturing_ok_tone2:": "\U0001f646\U0001f3fc\u200d\u2640\ufe0f", - ":woman_gesturing_ok_tone3:": "\U0001f646\U0001f3fd\u200d\u2640\ufe0f", - ":woman_gesturing_ok_tone4:": "\U0001f646\U0001f3fe\u200d\u2640\ufe0f", - ":woman_gesturing_ok_tone5:": "\U0001f646\U0001f3ff\u200d\u2640\ufe0f", - ":woman_getting_face_massage:": "\U0001f486\u200d\u2640\ufe0f", - ":woman_getting_face_massage_tone1:": "\U0001f486\U0001f3fb\u200d\u2640\ufe0f", - ":woman_getting_face_massage_tone2:": "\U0001f486\U0001f3fc\u200d\u2640\ufe0f", - ":woman_getting_face_massage_tone3:": "\U0001f486\U0001f3fd\u200d\u2640\ufe0f", - ":woman_getting_face_massage_tone4:": "\U0001f486\U0001f3fe\u200d\u2640\ufe0f", - ":woman_getting_face_massage_tone5:": "\U0001f486\U0001f3ff\u200d\u2640\ufe0f", - ":woman_getting_haircut:": "\U0001f487\u200d\u2640\ufe0f", - ":woman_getting_haircut_tone1:": "\U0001f487\U0001f3fb\u200d\u2640\ufe0f", - ":woman_getting_haircut_tone2:": "\U0001f487\U0001f3fc\u200d\u2640\ufe0f", - ":woman_getting_haircut_tone3:": "\U0001f487\U0001f3fd\u200d\u2640\ufe0f", - ":woman_getting_haircut_tone4:": "\U0001f487\U0001f3fe\u200d\u2640\ufe0f", - ":woman_getting_haircut_tone5:": "\U0001f487\U0001f3ff\u200d\u2640\ufe0f", - ":woman_getting_massage:": "\U0001f486\u200d\u2640\ufe0f", - ":woman_golfing:": "\U0001f3cc\ufe0f\u200d\u2640\ufe0f", - ":woman_golfing_tone1:": "\U0001f3cc\U0001f3fb\u200d\u2640\ufe0f", - ":woman_golfing_tone2:": "\U0001f3cc\U0001f3fc\u200d\u2640\ufe0f", - ":woman_golfing_tone3:": "\U0001f3cc\U0001f3fd\u200d\u2640\ufe0f", - ":woman_golfing_tone4:": "\U0001f3cc\U0001f3fe\u200d\u2640\ufe0f", - ":woman_golfing_tone5:": "\U0001f3cc\U0001f3ff\u200d\u2640\ufe0f", - ":woman_guard:": "\U0001f482\u200d\u2640\ufe0f", - ":woman_guard_tone1:": "\U0001f482\U0001f3fb\u200d\u2640\ufe0f", - ":woman_guard_tone2:": "\U0001f482\U0001f3fc\u200d\u2640\ufe0f", - ":woman_guard_tone3:": "\U0001f482\U0001f3fd\u200d\u2640\ufe0f", - ":woman_guard_tone4:": "\U0001f482\U0001f3fe\u200d\u2640\ufe0f", - ":woman_guard_tone5:": "\U0001f482\U0001f3ff\u200d\u2640\ufe0f", - ":woman_health_worker:": "\U0001f469\u200d\u2695\ufe0f", - ":woman_health_worker_tone1:": "\U0001f469\U0001f3fb\u200d\u2695\ufe0f", - ":woman_health_worker_tone2:": "\U0001f469\U0001f3fc\u200d\u2695\ufe0f", - ":woman_health_worker_tone3:": "\U0001f469\U0001f3fd\u200d\u2695\ufe0f", - ":woman_health_worker_tone4:": "\U0001f469\U0001f3fe\u200d\u2695\ufe0f", - ":woman_health_worker_tone5:": "\U0001f469\U0001f3ff\u200d\u2695\ufe0f", - ":woman_in_lotus_position:": "\U0001f9d8\u200d\u2640\ufe0f", - ":woman_in_lotus_position_tone1:": "\U0001f9d8\U0001f3fb\u200d\u2640\ufe0f", - ":woman_in_lotus_position_tone2:": "\U0001f9d8\U0001f3fc\u200d\u2640\ufe0f", - ":woman_in_lotus_position_tone3:": "\U0001f9d8\U0001f3fd\u200d\u2640\ufe0f", - ":woman_in_lotus_position_tone4:": "\U0001f9d8\U0001f3fe\u200d\u2640\ufe0f", - ":woman_in_lotus_position_tone5:": "\U0001f9d8\U0001f3ff\u200d\u2640\ufe0f", - ":woman_in_manual_wheelchair:": "\U0001f469\u200d\U0001f9bd", - ":woman_in_motorized_wheelchair:": "\U0001f469\u200d\U0001f9bc", - ":woman_in_steamy_room:": "\U0001f9d6\u200d\u2640\ufe0f", - ":woman_in_steamy_room_tone1:": "\U0001f9d6\U0001f3fb\u200d\u2640\ufe0f", - ":woman_in_steamy_room_tone2:": "\U0001f9d6\U0001f3fc\u200d\u2640\ufe0f", - ":woman_in_steamy_room_tone3:": "\U0001f9d6\U0001f3fd\u200d\u2640\ufe0f", - ":woman_in_steamy_room_tone4:": "\U0001f9d6\U0001f3fe\u200d\u2640\ufe0f", - ":woman_in_steamy_room_tone5:": "\U0001f9d6\U0001f3ff\u200d\u2640\ufe0f", - ":woman_in_tuxedo:": "\U0001f935\u200d\u2640\ufe0f", - ":woman_judge:": "\U0001f469\u200d\u2696\ufe0f", - ":woman_judge_tone1:": "\U0001f469\U0001f3fb\u200d\u2696\ufe0f", - ":woman_judge_tone2:": "\U0001f469\U0001f3fc\u200d\u2696\ufe0f", - ":woman_judge_tone3:": "\U0001f469\U0001f3fd\u200d\u2696\ufe0f", - ":woman_judge_tone4:": "\U0001f469\U0001f3fe\u200d\u2696\ufe0f", - ":woman_judge_tone5:": "\U0001f469\U0001f3ff\u200d\u2696\ufe0f", - ":woman_juggling:": "\U0001f939\u200d\u2640\ufe0f", - ":woman_juggling_tone1:": "\U0001f939\U0001f3fb\u200d\u2640\ufe0f", - ":woman_juggling_tone2:": "\U0001f939\U0001f3fc\u200d\u2640\ufe0f", - ":woman_juggling_tone3:": "\U0001f939\U0001f3fd\u200d\u2640\ufe0f", - ":woman_juggling_tone4:": "\U0001f939\U0001f3fe\u200d\u2640\ufe0f", - ":woman_juggling_tone5:": "\U0001f939\U0001f3ff\u200d\u2640\ufe0f", - ":woman_kneeling:": "\U0001f9ce\u200d\u2640\ufe0f", - ":woman_lifting_weights:": "\U0001f3cb\ufe0f\u200d\u2640\ufe0f", - ":woman_lifting_weights_tone1:": "\U0001f3cb\U0001f3fb\u200d\u2640\ufe0f", - ":woman_lifting_weights_tone2:": "\U0001f3cb\U0001f3fc\u200d\u2640\ufe0f", - ":woman_lifting_weights_tone3:": "\U0001f3cb\U0001f3fd\u200d\u2640\ufe0f", - ":woman_lifting_weights_tone4:": "\U0001f3cb\U0001f3fe\u200d\u2640\ufe0f", - ":woman_lifting_weights_tone5:": "\U0001f3cb\U0001f3ff\u200d\u2640\ufe0f", - ":woman_mage:": "\U0001f9d9\u200d\u2640\ufe0f", - ":woman_mage_tone1:": "\U0001f9d9\U0001f3fb\u200d\u2640\ufe0f", - ":woman_mage_tone2:": "\U0001f9d9\U0001f3fc\u200d\u2640\ufe0f", - ":woman_mage_tone3:": "\U0001f9d9\U0001f3fd\u200d\u2640\ufe0f", - ":woman_mage_tone4:": "\U0001f9d9\U0001f3fe\u200d\u2640\ufe0f", - ":woman_mage_tone5:": "\U0001f9d9\U0001f3ff\u200d\u2640\ufe0f", - ":woman_mechanic:": "\U0001f469\u200d\U0001f527", - ":woman_mechanic_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f527", - ":woman_mechanic_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f527", - ":woman_mechanic_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f527", - ":woman_mechanic_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f527", - ":woman_mechanic_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f527", - ":woman_mountain_biking:": "\U0001f6b5\u200d\u2640\ufe0f", - ":woman_mountain_biking_tone1:": "\U0001f6b5\U0001f3fb\u200d\u2640\ufe0f", - ":woman_mountain_biking_tone2:": "\U0001f6b5\U0001f3fc\u200d\u2640\ufe0f", - ":woman_mountain_biking_tone3:": "\U0001f6b5\U0001f3fd\u200d\u2640\ufe0f", - ":woman_mountain_biking_tone4:": "\U0001f6b5\U0001f3fe\u200d\u2640\ufe0f", - ":woman_mountain_biking_tone5:": "\U0001f6b5\U0001f3ff\u200d\u2640\ufe0f", - ":woman_office_worker:": "\U0001f469\u200d\U0001f4bc", - ":woman_office_worker_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f4bc", - ":woman_office_worker_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f4bc", - ":woman_office_worker_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f4bc", - ":woman_office_worker_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f4bc", - ":woman_office_worker_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f4bc", - ":woman_pilot:": "\U0001f469\u200d\u2708\ufe0f", - ":woman_pilot_tone1:": "\U0001f469\U0001f3fb\u200d\u2708\ufe0f", - ":woman_pilot_tone2:": "\U0001f469\U0001f3fc\u200d\u2708\ufe0f", - ":woman_pilot_tone3:": "\U0001f469\U0001f3fd\u200d\u2708\ufe0f", - ":woman_pilot_tone4:": "\U0001f469\U0001f3fe\u200d\u2708\ufe0f", - ":woman_pilot_tone5:": "\U0001f469\U0001f3ff\u200d\u2708\ufe0f", - ":woman_playing_handball:": "\U0001f93e\u200d\u2640\ufe0f", - ":woman_playing_handball_tone1:": "\U0001f93e\U0001f3fb\u200d\u2640\ufe0f", - ":woman_playing_handball_tone2:": "\U0001f93e\U0001f3fc\u200d\u2640\ufe0f", - ":woman_playing_handball_tone3:": "\U0001f93e\U0001f3fd\u200d\u2640\ufe0f", - ":woman_playing_handball_tone4:": "\U0001f93e\U0001f3fe\u200d\u2640\ufe0f", - ":woman_playing_handball_tone5:": "\U0001f93e\U0001f3ff\u200d\u2640\ufe0f", - ":woman_playing_water_polo:": "\U0001f93d\u200d\u2640\ufe0f", - ":woman_playing_water_polo_tone1:": "\U0001f93d\U0001f3fb\u200d\u2640\ufe0f", - ":woman_playing_water_polo_tone2:": "\U0001f93d\U0001f3fc\u200d\u2640\ufe0f", - ":woman_playing_water_polo_tone3:": "\U0001f93d\U0001f3fd\u200d\u2640\ufe0f", - ":woman_playing_water_polo_tone4:": "\U0001f93d\U0001f3fe\u200d\u2640\ufe0f", - ":woman_playing_water_polo_tone5:": "\U0001f93d\U0001f3ff\u200d\u2640\ufe0f", - ":woman_police_officer:": "\U0001f46e\u200d\u2640\ufe0f", - ":woman_police_officer_tone1:": "\U0001f46e\U0001f3fb\u200d\u2640\ufe0f", - ":woman_police_officer_tone2:": "\U0001f46e\U0001f3fc\u200d\u2640\ufe0f", - ":woman_police_officer_tone3:": "\U0001f46e\U0001f3fd\u200d\u2640\ufe0f", - ":woman_police_officer_tone4:": "\U0001f46e\U0001f3fe\u200d\u2640\ufe0f", - ":woman_police_officer_tone5:": "\U0001f46e\U0001f3ff\u200d\u2640\ufe0f", - ":woman_pouting:": "\U0001f64e\u200d\u2640\ufe0f", - ":woman_pouting_tone1:": "\U0001f64e\U0001f3fb\u200d\u2640\ufe0f", - ":woman_pouting_tone2:": "\U0001f64e\U0001f3fc\u200d\u2640\ufe0f", - ":woman_pouting_tone3:": "\U0001f64e\U0001f3fd\u200d\u2640\ufe0f", - ":woman_pouting_tone4:": "\U0001f64e\U0001f3fe\u200d\u2640\ufe0f", - ":woman_pouting_tone5:": "\U0001f64e\U0001f3ff\u200d\u2640\ufe0f", - ":woman_raising_hand:": "\U0001f64b\u200d\u2640\ufe0f", - ":woman_raising_hand_tone1:": "\U0001f64b\U0001f3fb\u200d\u2640\ufe0f", - ":woman_raising_hand_tone2:": "\U0001f64b\U0001f3fc\u200d\u2640\ufe0f", - ":woman_raising_hand_tone3:": "\U0001f64b\U0001f3fd\u200d\u2640\ufe0f", - ":woman_raising_hand_tone4:": "\U0001f64b\U0001f3fe\u200d\u2640\ufe0f", - ":woman_raising_hand_tone5:": "\U0001f64b\U0001f3ff\u200d\u2640\ufe0f", - ":woman_red_hair:": "\U0001f469\u200d\U0001f9b0", - ":woman_rowing_boat:": "\U0001f6a3\u200d\u2640\ufe0f", - ":woman_rowing_boat_tone1:": "\U0001f6a3\U0001f3fb\u200d\u2640\ufe0f", - ":woman_rowing_boat_tone2:": "\U0001f6a3\U0001f3fc\u200d\u2640\ufe0f", - ":woman_rowing_boat_tone3:": "\U0001f6a3\U0001f3fd\u200d\u2640\ufe0f", - ":woman_rowing_boat_tone4:": "\U0001f6a3\U0001f3fe\u200d\u2640\ufe0f", - ":woman_rowing_boat_tone5:": "\U0001f6a3\U0001f3ff\u200d\u2640\ufe0f", - ":woman_running:": "\U0001f3c3\u200d\u2640\ufe0f", - ":woman_running_tone1:": "\U0001f3c3\U0001f3fb\u200d\u2640\ufe0f", - ":woman_running_tone2:": "\U0001f3c3\U0001f3fc\u200d\u2640\ufe0f", - ":woman_running_tone3:": "\U0001f3c3\U0001f3fd\u200d\u2640\ufe0f", - ":woman_running_tone4:": "\U0001f3c3\U0001f3fe\u200d\u2640\ufe0f", - ":woman_running_tone5:": "\U0001f3c3\U0001f3ff\u200d\u2640\ufe0f", - ":woman_scientist:": "\U0001f469\u200d\U0001f52c", - ":woman_scientist_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f52c", - ":woman_scientist_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f52c", - ":woman_scientist_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f52c", - ":woman_scientist_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f52c", - ":woman_scientist_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f52c", - ":woman_shrugging:": "\U0001f937\u200d\u2640\ufe0f", - ":woman_shrugging_tone1:": "\U0001f937\U0001f3fb\u200d\u2640\ufe0f", - ":woman_shrugging_tone2:": "\U0001f937\U0001f3fc\u200d\u2640\ufe0f", - ":woman_shrugging_tone3:": "\U0001f937\U0001f3fd\u200d\u2640\ufe0f", - ":woman_shrugging_tone4:": "\U0001f937\U0001f3fe\u200d\u2640\ufe0f", - ":woman_shrugging_tone5:": "\U0001f937\U0001f3ff\u200d\u2640\ufe0f", - ":woman_singer:": "\U0001f469\u200d\U0001f3a4", - ":woman_singer_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f3a4", - ":woman_singer_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f3a4", - ":woman_singer_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f3a4", - ":woman_singer_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f3a4", - ":woman_singer_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f3a4", - ":woman_standing:": "\U0001f9cd\u200d\u2640\ufe0f", - ":woman_student:": "\U0001f469\u200d\U0001f393", - ":woman_student_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f393", - ":woman_student_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f393", - ":woman_student_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f393", - ":woman_student_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f393", - ":woman_student_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f393", - ":woman_superhero:": "\U0001f9b8\u200d\u2640\ufe0f", - ":woman_supervillain:": "\U0001f9b9\u200d\u2640\ufe0f", - ":woman_surfing:": "\U0001f3c4\u200d\u2640\ufe0f", - ":woman_surfing_tone1:": "\U0001f3c4\U0001f3fb\u200d\u2640\ufe0f", - ":woman_surfing_tone2:": "\U0001f3c4\U0001f3fc\u200d\u2640\ufe0f", - ":woman_surfing_tone3:": "\U0001f3c4\U0001f3fd\u200d\u2640\ufe0f", - ":woman_surfing_tone4:": "\U0001f3c4\U0001f3fe\u200d\u2640\ufe0f", - ":woman_surfing_tone5:": "\U0001f3c4\U0001f3ff\u200d\u2640\ufe0f", - ":woman_swimming:": "\U0001f3ca\u200d\u2640\ufe0f", - ":woman_swimming_tone1:": "\U0001f3ca\U0001f3fb\u200d\u2640\ufe0f", - ":woman_swimming_tone2:": "\U0001f3ca\U0001f3fc\u200d\u2640\ufe0f", - ":woman_swimming_tone3:": "\U0001f3ca\U0001f3fd\u200d\u2640\ufe0f", - ":woman_swimming_tone4:": "\U0001f3ca\U0001f3fe\u200d\u2640\ufe0f", - ":woman_swimming_tone5:": "\U0001f3ca\U0001f3ff\u200d\u2640\ufe0f", - ":woman_teacher:": "\U0001f469\u200d\U0001f3eb", - ":woman_teacher_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f3eb", - ":woman_teacher_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f3eb", - ":woman_teacher_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f3eb", - ":woman_teacher_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f3eb", - ":woman_teacher_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f3eb", - ":woman_technologist:": "\U0001f469\u200d\U0001f4bb", - ":woman_technologist_tone1:": "\U0001f469\U0001f3fb\u200d\U0001f4bb", - ":woman_technologist_tone2:": "\U0001f469\U0001f3fc\u200d\U0001f4bb", - ":woman_technologist_tone3:": "\U0001f469\U0001f3fd\u200d\U0001f4bb", - ":woman_technologist_tone4:": "\U0001f469\U0001f3fe\u200d\U0001f4bb", - ":woman_technologist_tone5:": "\U0001f469\U0001f3ff\u200d\U0001f4bb", - ":woman_tipping_hand:": "\U0001f481\u200d\u2640\ufe0f", - ":woman_tipping_hand_tone1:": "\U0001f481\U0001f3fb\u200d\u2640\ufe0f", - ":woman_tipping_hand_tone2:": "\U0001f481\U0001f3fc\u200d\u2640\ufe0f", - ":woman_tipping_hand_tone3:": "\U0001f481\U0001f3fd\u200d\u2640\ufe0f", - ":woman_tipping_hand_tone4:": "\U0001f481\U0001f3fe\u200d\u2640\ufe0f", - ":woman_tipping_hand_tone5:": "\U0001f481\U0001f3ff\u200d\u2640\ufe0f", - ":woman_tone1:": "\U0001f469\U0001f3fb", - ":woman_tone2:": "\U0001f469\U0001f3fc", - ":woman_tone3:": "\U0001f469\U0001f3fd", - ":woman_tone4:": "\U0001f469\U0001f3fe", - ":woman_tone5:": "\U0001f469\U0001f3ff", - ":woman_vampire:": "\U0001f9db\u200d\u2640\ufe0f", - ":woman_vampire_tone1:": "\U0001f9db\U0001f3fb\u200d\u2640\ufe0f", - ":woman_vampire_tone2:": "\U0001f9db\U0001f3fc\u200d\u2640\ufe0f", - ":woman_vampire_tone3:": "\U0001f9db\U0001f3fd\u200d\u2640\ufe0f", - ":woman_vampire_tone4:": "\U0001f9db\U0001f3fe\u200d\u2640\ufe0f", - ":woman_vampire_tone5:": "\U0001f9db\U0001f3ff\u200d\u2640\ufe0f", - ":woman_walking:": "\U0001f6b6\u200d\u2640\ufe0f", - ":woman_walking_tone1:": "\U0001f6b6\U0001f3fb\u200d\u2640\ufe0f", - ":woman_walking_tone2:": "\U0001f6b6\U0001f3fc\u200d\u2640\ufe0f", - ":woman_walking_tone3:": "\U0001f6b6\U0001f3fd\u200d\u2640\ufe0f", - ":woman_walking_tone4:": "\U0001f6b6\U0001f3fe\u200d\u2640\ufe0f", - ":woman_walking_tone5:": "\U0001f6b6\U0001f3ff\u200d\u2640\ufe0f", - ":woman_wearing_turban:": "\U0001f473\u200d\u2640\ufe0f", - ":woman_wearing_turban_tone1:": "\U0001f473\U0001f3fb\u200d\u2640\ufe0f", - ":woman_wearing_turban_tone2:": "\U0001f473\U0001f3fc\u200d\u2640\ufe0f", - ":woman_wearing_turban_tone3:": "\U0001f473\U0001f3fd\u200d\u2640\ufe0f", - ":woman_wearing_turban_tone4:": "\U0001f473\U0001f3fe\u200d\u2640\ufe0f", - ":woman_wearing_turban_tone5:": "\U0001f473\U0001f3ff\u200d\u2640\ufe0f", - ":woman_white_hair:": "\U0001f469\u200d\U0001f9b3", - ":woman_with_headscarf:": "\U0001f9d5", - ":woman_with_headscarf_tone1:": "\U0001f9d5\U0001f3fb", - ":woman_with_headscarf_tone2:": "\U0001f9d5\U0001f3fc", - ":woman_with_headscarf_tone3:": "\U0001f9d5\U0001f3fd", - ":woman_with_headscarf_tone4:": "\U0001f9d5\U0001f3fe", - ":woman_with_headscarf_tone5:": "\U0001f9d5\U0001f3ff", - ":woman_with_probing_cane:": "\U0001f469\u200d\U0001f9af", - ":woman_with_turban:": "\U0001f473\u200d\u2640\ufe0f", - ":woman_with_veil:": "\U0001f470\u200d\u2640\ufe0f", - ":woman_with_white_cane:": "\U0001f469\u200d\U0001f9af", - ":woman_zombie:": "\U0001f9df\u200d\u2640\ufe0f", - ":womans_clothes:": "\U0001f45a", - ":womans_flat_shoe:": "\U0001f97f", - ":womans_hat:": "\U0001f452", - ":woman’s_boot:": "\U0001f462", - ":woman’s_clothes:": "\U0001f45a", - ":woman’s_hat:": "\U0001f452", - ":woman’s_sandal:": "\U0001f461", - ":women_holding_hands:": "\U0001f46d", - ":women_with_bunny_ears:": "\U0001f46f\u200d\u2640\ufe0f", - ":women_with_bunny_ears_partying:": "\U0001f46f\u200d\u2640\ufe0f", - ":women_wrestling:": "\U0001f93c\u200d\u2640\ufe0f", - ":womens:": "\U0001f6ba", - ":women’s_room:": "\U0001f6ba", - ":wood:": "\U0001fab5", - ":woozy_face:": "\U0001f974", - ":world_map:": "\U0001f5fa\ufe0f", - ":worm:": "\U0001fab1", - ":worried:": "\U0001f61f", - ":worried_face:": "\U0001f61f", - ":wrapped_gift:": "\U0001f381", - ":wrench:": "\U0001f527", - ":wrestlers:": "\U0001f93c", - ":wrestling:": "\U0001f93c", - ":writing_hand:": "\u270d\ufe0f", - ":writing_hand_tone1:": "\u270d\U0001f3fb", - ":writing_hand_tone2:": "\u270d\U0001f3fc", - ":writing_hand_tone3:": "\u270d\U0001f3fd", - ":writing_hand_tone4:": "\u270d\U0001f3fe", - ":writing_hand_tone5:": "\u270d\U0001f3ff", - ":x:": "\u274c", - ":yarn:": "\U0001f9f6", - ":yawning_face:": "\U0001f971", - ":yellow_circle:": "\U0001f7e1", - ":yellow_heart:": "\U0001f49b", - ":yellow_square:": "\U0001f7e8", - ":yemen:": "\U0001f1fe\U0001f1ea", - ":yen:": "\U0001f4b4", - ":yen_banknote:": "\U0001f4b4", - ":yin_yang:": "\u262f\ufe0f", - ":yo-yo:": "\U0001fa80", - ":yo_yo:": "\U0001fa80", - ":yum:": "\U0001f60b", - ":zambia:": "\U0001f1ff\U0001f1f2", - ":zany_face:": "\U0001f92a", - ":zap:": "\u26a1", - ":zebra:": "\U0001f993", - ":zebra_face:": "\U0001f993", - ":zero:": "0\ufe0f\u20e3", - ":zimbabwe:": "\U0001f1ff\U0001f1fc", - ":zipper-mouth_face:": "\U0001f910", - ":zipper_mouth:": "\U0001f910", - ":zipper_mouth_face:": "\U0001f910", - ":zombie:": "\U0001f9df\u200d\u2642\ufe0f", - ":zombie_man:": "\U0001f9df\u200d\u2642\ufe0f", - ":zombie_woman:": "\U0001f9df\u200d\u2640\ufe0f", - ":zzz:": "\U0001f4a4", - } - }) - return emojiCodeMap -} - -var emojiRevCodeMap map[string][]string -var emojiRevCodeMapInitOnce = sync.Once{} - -func emojiRevCode() map[string][]string { - emojiRevCodeMapInitOnce.Do(func() { - emojiRevCodeMap = map[string][]string{ - "#\ufe0f\u20e3": {":hash:", ":keycap_#:"}, - "*\ufe0f\u20e3": {":asterisk:", ":keycap_*:", ":keycap_star:"}, - "0\ufe0f\u20e3": {":zero:", ":keycap_0:"}, - "1\ufe0f\u20e3": {":one:", ":keycap_1:"}, - "2\ufe0f\u20e3": {":two:", ":keycap_2:"}, - "3\ufe0f\u20e3": {":three:", ":keycap_3:"}, - "4\ufe0f\u20e3": {":four:", ":keycap_4:"}, - "5\ufe0f\u20e3": {":five:", ":keycap_5:"}, - "6\ufe0f\u20e3": {":six:", ":keycap_6:"}, - "7\ufe0f\u20e3": {":seven:", ":keycap_7:"}, - "8\ufe0f\u20e3": {":eight:", ":keycap_8:"}, - "9\ufe0f\u20e3": {":nine:", ":keycap_9:"}, - "\U0001f004": {":mahjong:", ":mahjong_red_dragon:"}, - "\U0001f0cf": {":joker:", ":black_joker:"}, - "\U0001f170": {":A_button_(blood_type):"}, - "\U0001f170\ufe0f": {":a:"}, - "\U0001f171": {":B_button_(blood_type):"}, - "\U0001f171\ufe0f": {":b:"}, - "\U0001f17e": {":O_button_(blood_type):"}, - "\U0001f17e\ufe0f": {":o2:"}, - "\U0001f17f": {":P_button:"}, - "\U0001f17f\ufe0f": {":parking:"}, - "\U0001f18e": {":ab:", ":AB_button_(blood_type):"}, - "\U0001f191": {":cl:", ":CL_button:"}, - "\U0001f192": {":cool:", ":COOL_button:"}, - "\U0001f193": {":free:", ":FREE_button:"}, - "\U0001f194": {":id:", ":ID_button:"}, - "\U0001f195": {":new:", ":NEW_button:"}, - "\U0001f196": {":ng:", ":NG_button:"}, - "\U0001f197": {":ok:", ":OK_button:"}, - "\U0001f198": {":sos:", ":SOS_button:"}, - "\U0001f199": {":up:", ":UP!_button:"}, - "\U0001f19a": {":vs:", ":VS_button:"}, - "\U0001f1e6\U0001f1e8": {":flag-ac:", ":flag_ac:", ":ascension_island:", ":flag_Ascension_Island:"}, - "\U0001f1e6\U0001f1e9": {":andorra:", ":flag-ad:", ":flag_ad:", ":flag_Andorra:"}, - "\U0001f1e6\U0001f1ea": {":flag-ae:", ":flag_ae:", ":united_arab_emirates:", ":flag_United_Arab_Emirates:"}, - "\U0001f1e6\U0001f1eb": {":flag-af:", ":flag_af:", ":afghanistan:", ":flag_Afghanistan:"}, - "\U0001f1e6\U0001f1ec": {":flag-ag:", ":flag_ag:", ":antigua_barbuda:", ":flag_Antigua_&_Barbuda:"}, - "\U0001f1e6\U0001f1ee": {":flag-ai:", ":flag_ai:", ":anguilla:", ":flag_Anguilla:"}, - "\U0001f1e6\U0001f1f1": {":albania:", ":flag-al:", ":flag_al:", ":flag_Albania:"}, - "\U0001f1e6\U0001f1f2": {":armenia:", ":flag-am:", ":flag_am:", ":flag_Armenia:"}, - "\U0001f1e6\U0001f1f4": {":angola:", ":flag-ao:", ":flag_ao:", ":flag_Angola:"}, - "\U0001f1e6\U0001f1f6": {":flag-aq:", ":flag_aq:", ":antarctica:", ":flag_Antarctica:"}, - "\U0001f1e6\U0001f1f7": {":flag-ar:", ":flag_ar:", ":argentina:", ":flag_Argentina:"}, - "\U0001f1e6\U0001f1f8": {":flag-as:", ":flag_as:", ":american_samoa:", ":flag_American_Samoa:"}, - "\U0001f1e6\U0001f1f9": {":austria:", ":flag-at:", ":flag_at:", ":flag_Austria:"}, - "\U0001f1e6\U0001f1fa": {":flag-au:", ":flag_au:", ":australia:", ":flag_Australia:"}, - "\U0001f1e6\U0001f1fc": {":aruba:", ":flag-aw:", ":flag_aw:", ":flag_Aruba:"}, - "\U0001f1e6\U0001f1fd": {":flag-ax:", ":flag_ax:", ":aland_islands:", ":flag_Åland_Islands:"}, - "\U0001f1e6\U0001f1ff": {":flag-az:", ":flag_az:", ":azerbaijan:", ":flag_Azerbaijan:"}, - "\U0001f1e7\U0001f1e6": {":flag-ba:", ":flag_ba:", ":bosnia_herzegovina:", ":flag_Bosnia_&_Herzegovina:"}, - "\U0001f1e7\U0001f1e7": {":flag-bb:", ":flag_bb:", ":barbados:", ":flag_Barbados:"}, - "\U0001f1e7\U0001f1e9": {":flag-bd:", ":flag_bd:", ":bangladesh:", ":flag_Bangladesh:"}, - "\U0001f1e7\U0001f1ea": {":belgium:", ":flag-be:", ":flag_be:", ":flag_Belgium:"}, - "\U0001f1e7\U0001f1eb": {":flag-bf:", ":flag_bf:", ":burkina_faso:", ":flag_Burkina_Faso:"}, - "\U0001f1e7\U0001f1ec": {":flag-bg:", ":flag_bg:", ":bulgaria:", ":flag_Bulgaria:"}, - "\U0001f1e7\U0001f1ed": {":bahrain:", ":flag-bh:", ":flag_bh:", ":flag_Bahrain:"}, - "\U0001f1e7\U0001f1ee": {":burundi:", ":flag-bi:", ":flag_bi:", ":flag_Burundi:"}, - "\U0001f1e7\U0001f1ef": {":benin:", ":flag-bj:", ":flag_bj:", ":flag_Benin:"}, - "\U0001f1e7\U0001f1f1": {":flag-bl:", ":flag_bl:", ":st_barthelemy:", ":flag_St._Barthélemy:"}, - "\U0001f1e7\U0001f1f2": {":bermuda:", ":flag-bm:", ":flag_bm:", ":flag_Bermuda:"}, - "\U0001f1e7\U0001f1f3": {":brunei:", ":flag-bn:", ":flag_bn:", ":flag_Brunei:"}, - "\U0001f1e7\U0001f1f4": {":bolivia:", ":flag-bo:", ":flag_bo:", ":flag_Bolivia:"}, - "\U0001f1e7\U0001f1f6": {":flag-bq:", ":flag_bq:", ":caribbean_netherlands:", ":flag_Caribbean_Netherlands:"}, - "\U0001f1e7\U0001f1f7": {":brazil:", ":flag-br:", ":flag_br:", ":flag_Brazil:"}, - "\U0001f1e7\U0001f1f8": {":bahamas:", ":flag-bs:", ":flag_bs:", ":flag_Bahamas:"}, - "\U0001f1e7\U0001f1f9": {":bhutan:", ":flag-bt:", ":flag_bt:", ":flag_Bhutan:"}, - "\U0001f1e7\U0001f1fb": {":flag-bv:", ":flag_bv:", ":bouvet_island:", ":flag_Bouvet_Island:"}, - "\U0001f1e7\U0001f1fc": {":flag-bw:", ":flag_bw:", ":botswana:", ":flag_Botswana:"}, - "\U0001f1e7\U0001f1fe": {":belarus:", ":flag-by:", ":flag_by:", ":flag_Belarus:"}, - "\U0001f1e7\U0001f1ff": {":belize:", ":flag-bz:", ":flag_bz:", ":flag_Belize:"}, - "\U0001f1e8\U0001f1e6": {":canada:", ":flag-ca:", ":flag_ca:", ":flag_Canada:"}, - "\U0001f1e8\U0001f1e8": {":flag-cc:", ":flag_cc:", ":cocos_islands:", ":flag_Cocos_(Keeling)_Islands:"}, - "\U0001f1e8\U0001f1e9": {":flag-cd:", ":flag_cd:", ":congo_kinshasa:", ":flag_Congo_-_Kinshasa:"}, - "\U0001f1e8\U0001f1eb": {":flag-cf:", ":flag_cf:", ":central_african_republic:", ":flag_Central_African_Republic:"}, - "\U0001f1e8\U0001f1ec": {":flag-cg:", ":flag_cg:", ":congo_brazzaville:", ":flag_Congo_-_Brazzaville:"}, - "\U0001f1e8\U0001f1ed": {":flag-ch:", ":flag_ch:", ":switzerland:", ":flag_Switzerland:"}, - "\U0001f1e8\U0001f1ee": {":flag-ci:", ":flag_ci:", ":cote_divoire:", ":flag_Côte_d’Ivoire:"}, - "\U0001f1e8\U0001f1f0": {":flag-ck:", ":flag_ck:", ":cook_islands:", ":flag_Cook_Islands:"}, - "\U0001f1e8\U0001f1f1": {":chile:", ":flag-cl:", ":flag_cl:", ":flag_Chile:"}, - "\U0001f1e8\U0001f1f2": {":flag-cm:", ":flag_cm:", ":cameroon:", ":flag_Cameroon:"}, - "\U0001f1e8\U0001f1f3": {":cn:", ":flag_cn:", ":flag_China:"}, - "\U0001f1e8\U0001f1f4": {":flag-co:", ":flag_co:", ":colombia:", ":flag_Colombia:"}, - "\U0001f1e8\U0001f1f5": {":flag-cp:", ":flag_cp:", ":clipperton_island:", ":flag_Clipperton_Island:"}, - "\U0001f1e8\U0001f1f7": {":flag-cr:", ":flag_cr:", ":costa_rica:", ":flag_Costa_Rica:"}, - "\U0001f1e8\U0001f1fa": {":cuba:", ":flag-cu:", ":flag_cu:", ":flag_Cuba:"}, - "\U0001f1e8\U0001f1fb": {":flag-cv:", ":flag_cv:", ":cape_verde:", ":flag_Cape_Verde:"}, - "\U0001f1e8\U0001f1fc": {":curacao:", ":flag-cw:", ":flag_cw:", ":flag_Curaçao:"}, - "\U0001f1e8\U0001f1fd": {":flag-cx:", ":flag_cx:", ":christmas_island:", ":flag_Christmas_Island:"}, - "\U0001f1e8\U0001f1fe": {":cyprus:", ":flag-cy:", ":flag_cy:", ":flag_Cyprus:"}, - "\U0001f1e8\U0001f1ff": {":flag-cz:", ":flag_cz:", ":flag_Czechia:", ":czech_republic:"}, - "\U0001f1e9\U0001f1ea": {":de:", ":flag_de:", ":flag_Germany:"}, - "\U0001f1e9\U0001f1ec": {":flag-dg:", ":flag_dg:", ":diego_garcia:", ":flag_Diego_Garcia:"}, - "\U0001f1e9\U0001f1ef": {":flag-dj:", ":flag_dj:", ":djibouti:", ":flag_Djibouti:"}, - "\U0001f1e9\U0001f1f0": {":denmark:", ":flag-dk:", ":flag_dk:", ":flag_Denmark:"}, - "\U0001f1e9\U0001f1f2": {":flag-dm:", ":flag_dm:", ":dominica:", ":flag_Dominica:"}, - "\U0001f1e9\U0001f1f4": {":flag-do:", ":flag_do:", ":dominican_republic:", ":flag_Dominican_Republic:"}, - "\U0001f1e9\U0001f1ff": {":algeria:", ":flag-dz:", ":flag_dz:", ":flag_Algeria:"}, - "\U0001f1ea\U0001f1e6": {":flag-ea:", ":flag_ea:", ":ceuta_melilla:", ":flag_Ceuta_&_Melilla:"}, - "\U0001f1ea\U0001f1e8": {":ecuador:", ":flag-ec:", ":flag_ec:", ":flag_Ecuador:"}, - "\U0001f1ea\U0001f1ea": {":estonia:", ":flag-ee:", ":flag_ee:", ":flag_Estonia:"}, - "\U0001f1ea\U0001f1ec": {":egypt:", ":flag-eg:", ":flag_eg:", ":flag_Egypt:"}, - "\U0001f1ea\U0001f1ed": {":flag-eh:", ":flag_eh:", ":western_sahara:", ":flag_Western_Sahara:"}, - "\U0001f1ea\U0001f1f7": {":eritrea:", ":flag-er:", ":flag_er:", ":flag_Eritrea:"}, - "\U0001f1ea\U0001f1f8": {":es:", ":flag_es:", ":flag_Spain:"}, - "\U0001f1ea\U0001f1f9": {":flag-et:", ":flag_et:", ":ethiopia:", ":flag_Ethiopia:"}, - "\U0001f1ea\U0001f1fa": {":eu:", ":flag-eu:", ":flag_eu:", ":european_union:", ":flag_European_Union:"}, - "\U0001f1eb\U0001f1ee": {":finland:", ":flag-fi:", ":flag_fi:", ":flag_Finland:"}, - "\U0001f1eb\U0001f1ef": {":fiji:", ":flag-fj:", ":flag_fj:", ":flag_Fiji:"}, - "\U0001f1eb\U0001f1f0": {":flag-fk:", ":flag_fk:", ":falkland_islands:", ":flag_Falkland_Islands:"}, - "\U0001f1eb\U0001f1f2": {":flag-fm:", ":flag_fm:", ":micronesia:", ":flag_Micronesia:"}, - "\U0001f1eb\U0001f1f4": {":flag-fo:", ":flag_fo:", ":faroe_islands:", ":flag_Faroe_Islands:"}, - "\U0001f1eb\U0001f1f7": {":fr:", ":flag_fr:", ":flag_France:"}, - "\U0001f1ec\U0001f1e6": {":gabon:", ":flag-ga:", ":flag_ga:", ":flag_Gabon:"}, - "\U0001f1ec\U0001f1e7": {":gb:", ":uk:", ":flag_gb:", ":flag_United_Kingdom:"}, - "\U0001f1ec\U0001f1e9": {":flag-gd:", ":flag_gd:", ":grenada:", ":flag_Grenada:"}, - "\U0001f1ec\U0001f1ea": {":flag-ge:", ":flag_ge:", ":georgia:", ":flag_Georgia:"}, - "\U0001f1ec\U0001f1eb": {":flag-gf:", ":flag_gf:", ":french_guiana:", ":flag_French_Guiana:"}, - "\U0001f1ec\U0001f1ec": {":flag-gg:", ":flag_gg:", ":guernsey:", ":flag_Guernsey:"}, - "\U0001f1ec\U0001f1ed": {":ghana:", ":flag-gh:", ":flag_gh:", ":flag_Ghana:"}, - "\U0001f1ec\U0001f1ee": {":flag-gi:", ":flag_gi:", ":gibraltar:", ":flag_Gibraltar:"}, - "\U0001f1ec\U0001f1f1": {":flag-gl:", ":flag_gl:", ":greenland:", ":flag_Greenland:"}, - "\U0001f1ec\U0001f1f2": {":gambia:", ":flag-gm:", ":flag_gm:", ":flag_Gambia:"}, - "\U0001f1ec\U0001f1f3": {":guinea:", ":flag-gn:", ":flag_gn:", ":flag_Guinea:"}, - "\U0001f1ec\U0001f1f5": {":flag-gp:", ":flag_gp:", ":guadeloupe:", ":flag_Guadeloupe:"}, - "\U0001f1ec\U0001f1f6": {":flag-gq:", ":flag_gq:", ":equatorial_guinea:", ":flag_Equatorial_Guinea:"}, - "\U0001f1ec\U0001f1f7": {":greece:", ":flag-gr:", ":flag_gr:", ":flag_Greece:"}, - "\U0001f1ec\U0001f1f8": {":flag-gs:", ":flag_gs:", ":south_georgia_south_sandwich_islands:", ":flag_South_Georgia_&_South_Sandwich_Islands:"}, - "\U0001f1ec\U0001f1f9": {":flag-gt:", ":flag_gt:", ":guatemala:", ":flag_Guatemala:"}, - "\U0001f1ec\U0001f1fa": {":guam:", ":flag-gu:", ":flag_gu:", ":flag_Guam:"}, - "\U0001f1ec\U0001f1fc": {":flag-gw:", ":flag_gw:", ":guinea_bissau:", ":flag_Guinea-Bissau:"}, - "\U0001f1ec\U0001f1fe": {":guyana:", ":flag-gy:", ":flag_gy:", ":flag_Guyana:"}, - "\U0001f1ed\U0001f1f0": {":flag-hk:", ":flag_hk:", ":hong_kong:", ":flag_Hong_Kong_SAR_China:"}, - "\U0001f1ed\U0001f1f2": {":flag-hm:", ":flag_hm:", ":heard_mcdonald_islands:", ":flag_Heard_&_McDonald_Islands:"}, - "\U0001f1ed\U0001f1f3": {":flag-hn:", ":flag_hn:", ":honduras:", ":flag_Honduras:"}, - "\U0001f1ed\U0001f1f7": {":croatia:", ":flag-hr:", ":flag_hr:", ":flag_Croatia:"}, - "\U0001f1ed\U0001f1f9": {":haiti:", ":flag-ht:", ":flag_ht:", ":flag_Haiti:"}, - "\U0001f1ed\U0001f1fa": {":flag-hu:", ":flag_hu:", ":hungary:", ":flag_Hungary:"}, - "\U0001f1ee\U0001f1e8": {":flag-ic:", ":flag_ic:", ":canary_islands:", ":flag_Canary_Islands:"}, - "\U0001f1ee\U0001f1e9": {":flag-id:", ":flag_id:", ":indonesia:", ":flag_Indonesia:"}, - "\U0001f1ee\U0001f1ea": {":flag-ie:", ":flag_ie:", ":ireland:", ":flag_Ireland:"}, - "\U0001f1ee\U0001f1f1": {":israel:", ":flag-il:", ":flag_il:", ":flag_Israel:"}, - "\U0001f1ee\U0001f1f2": {":flag-im:", ":flag_im:", ":isle_of_man:", ":flag_Isle_of_Man:"}, - "\U0001f1ee\U0001f1f3": {":india:", ":flag-in:", ":flag_in:", ":flag_India:"}, - "\U0001f1ee\U0001f1f4": {":flag-io:", ":flag_io:", ":british_indian_ocean_territory:", ":flag_British_Indian_Ocean_Territory:"}, - "\U0001f1ee\U0001f1f6": {":iraq:", ":flag-iq:", ":flag_iq:", ":flag_Iraq:"}, - "\U0001f1ee\U0001f1f7": {":iran:", ":flag-ir:", ":flag_ir:", ":flag_Iran:"}, - "\U0001f1ee\U0001f1f8": {":flag-is:", ":flag_is:", ":iceland:", ":flag_Iceland:"}, - "\U0001f1ee\U0001f1f9": {":it:", ":flag_it:", ":flag_Italy:"}, - "\U0001f1ef\U0001f1ea": {":jersey:", ":flag-je:", ":flag_je:", ":flag_Jersey:"}, - "\U0001f1ef\U0001f1f2": {":flag-jm:", ":flag_jm:", ":jamaica:", ":flag_Jamaica:"}, - "\U0001f1ef\U0001f1f4": {":jordan:", ":flag-jo:", ":flag_jo:", ":flag_Jordan:"}, - "\U0001f1ef\U0001f1f5": {":jp:", ":flag_jp:", ":flag_Japan:"}, - "\U0001f1f0\U0001f1ea": {":kenya:", ":flag-ke:", ":flag_ke:", ":flag_Kenya:"}, - "\U0001f1f0\U0001f1ec": {":flag-kg:", ":flag_kg:", ":kyrgyzstan:", ":flag_Kyrgyzstan:"}, - "\U0001f1f0\U0001f1ed": {":flag-kh:", ":flag_kh:", ":cambodia:", ":flag_Cambodia:"}, - "\U0001f1f0\U0001f1ee": {":flag-ki:", ":flag_ki:", ":kiribati:", ":flag_Kiribati:"}, - "\U0001f1f0\U0001f1f2": {":comoros:", ":flag-km:", ":flag_km:", ":flag_Comoros:"}, - "\U0001f1f0\U0001f1f3": {":flag-kn:", ":flag_kn:", ":st_kitts_nevis:", ":flag_St._Kitts_&_Nevis:"}, - "\U0001f1f0\U0001f1f5": {":flag-kp:", ":flag_kp:", ":north_korea:", ":flag_North_Korea:"}, - "\U0001f1f0\U0001f1f7": {":kr:", ":flag_kr:", ":flag_South_Korea:"}, - "\U0001f1f0\U0001f1fc": {":kuwait:", ":flag-kw:", ":flag_kw:", ":flag_Kuwait:"}, - "\U0001f1f0\U0001f1fe": {":flag-ky:", ":flag_ky:", ":cayman_islands:", ":flag_Cayman_Islands:"}, - "\U0001f1f0\U0001f1ff": {":flag-kz:", ":flag_kz:", ":kazakhstan:", ":flag_Kazakhstan:"}, - "\U0001f1f1\U0001f1e6": {":laos:", ":flag-la:", ":flag_la:", ":flag_Laos:"}, - "\U0001f1f1\U0001f1e7": {":flag-lb:", ":flag_lb:", ":lebanon:", ":flag_Lebanon:"}, - "\U0001f1f1\U0001f1e8": {":flag-lc:", ":flag_lc:", ":st_lucia:", ":flag_St._Lucia:"}, - "\U0001f1f1\U0001f1ee": {":flag-li:", ":flag_li:", ":liechtenstein:", ":flag_Liechtenstein:"}, - "\U0001f1f1\U0001f1f0": {":flag-lk:", ":flag_lk:", ":sri_lanka:", ":flag_Sri_Lanka:"}, - "\U0001f1f1\U0001f1f7": {":flag-lr:", ":flag_lr:", ":liberia:", ":flag_Liberia:"}, - "\U0001f1f1\U0001f1f8": {":flag-ls:", ":flag_ls:", ":lesotho:", ":flag_Lesotho:"}, - "\U0001f1f1\U0001f1f9": {":flag-lt:", ":flag_lt:", ":lithuania:", ":flag_Lithuania:"}, - "\U0001f1f1\U0001f1fa": {":flag-lu:", ":flag_lu:", ":luxembourg:", ":flag_Luxembourg:"}, - "\U0001f1f1\U0001f1fb": {":latvia:", ":flag-lv:", ":flag_lv:", ":flag_Latvia:"}, - "\U0001f1f1\U0001f1fe": {":libya:", ":flag-ly:", ":flag_ly:", ":flag_Libya:"}, - "\U0001f1f2\U0001f1e6": {":flag-ma:", ":flag_ma:", ":morocco:", ":flag_Morocco:"}, - "\U0001f1f2\U0001f1e8": {":monaco:", ":flag-mc:", ":flag_mc:", ":flag_Monaco:"}, - "\U0001f1f2\U0001f1e9": {":flag-md:", ":flag_md:", ":moldova:", ":flag_Moldova:"}, - "\U0001f1f2\U0001f1ea": {":flag-me:", ":flag_me:", ":montenegro:", ":flag_Montenegro:"}, - "\U0001f1f2\U0001f1eb": {":flag-mf:", ":flag_mf:", ":st_martin:", ":flag_St._Martin:"}, - "\U0001f1f2\U0001f1ec": {":flag-mg:", ":flag_mg:", ":madagascar:", ":flag_Madagascar:"}, - "\U0001f1f2\U0001f1ed": {":flag-mh:", ":flag_mh:", ":marshall_islands:", ":flag_Marshall_Islands:"}, - "\U0001f1f2\U0001f1f0": {":flag-mk:", ":flag_mk:", ":macedonia:", ":flag_North_Macedonia:"}, - "\U0001f1f2\U0001f1f1": {":mali:", ":flag-ml:", ":flag_ml:", ":flag_Mali:"}, - "\U0001f1f2\U0001f1f2": {":flag-mm:", ":flag_mm:", ":myanmar:", ":flag_Myanmar_(Burma):"}, - "\U0001f1f2\U0001f1f3": {":flag-mn:", ":flag_mn:", ":mongolia:", ":flag_Mongolia:"}, - "\U0001f1f2\U0001f1f4": {":macau:", ":flag-mo:", ":flag_mo:", ":flag_Macao_SAR_China:"}, - "\U0001f1f2\U0001f1f5": {":flag-mp:", ":flag_mp:", ":northern_mariana_islands:", ":flag_Northern_Mariana_Islands:"}, - "\U0001f1f2\U0001f1f6": {":flag-mq:", ":flag_mq:", ":martinique:", ":flag_Martinique:"}, - "\U0001f1f2\U0001f1f7": {":flag-mr:", ":flag_mr:", ":mauritania:", ":flag_Mauritania:"}, - "\U0001f1f2\U0001f1f8": {":flag-ms:", ":flag_ms:", ":montserrat:", ":flag_Montserrat:"}, - "\U0001f1f2\U0001f1f9": {":malta:", ":flag-mt:", ":flag_mt:", ":flag_Malta:"}, - "\U0001f1f2\U0001f1fa": {":flag-mu:", ":flag_mu:", ":mauritius:", ":flag_Mauritius:"}, - "\U0001f1f2\U0001f1fb": {":flag-mv:", ":flag_mv:", ":maldives:", ":flag_Maldives:"}, - "\U0001f1f2\U0001f1fc": {":malawi:", ":flag-mw:", ":flag_mw:", ":flag_Malawi:"}, - "\U0001f1f2\U0001f1fd": {":mexico:", ":flag-mx:", ":flag_mx:", ":flag_Mexico:"}, - "\U0001f1f2\U0001f1fe": {":flag-my:", ":flag_my:", ":malaysia:", ":flag_Malaysia:"}, - "\U0001f1f2\U0001f1ff": {":flag-mz:", ":flag_mz:", ":mozambique:", ":flag_Mozambique:"}, - "\U0001f1f3\U0001f1e6": {":flag-na:", ":flag_na:", ":namibia:", ":flag_Namibia:"}, - "\U0001f1f3\U0001f1e8": {":flag-nc:", ":flag_nc:", ":new_caledonia:", ":flag_New_Caledonia:"}, - "\U0001f1f3\U0001f1ea": {":niger:", ":flag-ne:", ":flag_ne:", ":flag_Niger:"}, - "\U0001f1f3\U0001f1eb": {":flag-nf:", ":flag_nf:", ":norfolk_island:", ":flag_Norfolk_Island:"}, - "\U0001f1f3\U0001f1ec": {":flag-ng:", ":flag_ng:", ":nigeria:", ":flag_Nigeria:"}, - "\U0001f1f3\U0001f1ee": {":flag-ni:", ":flag_ni:", ":nicaragua:", ":flag_Nicaragua:"}, - "\U0001f1f3\U0001f1f1": {":flag-nl:", ":flag_nl:", ":netherlands:", ":flag_Netherlands:"}, - "\U0001f1f3\U0001f1f4": {":norway:", ":flag-no:", ":flag_no:", ":flag_Norway:"}, - "\U0001f1f3\U0001f1f5": {":nepal:", ":flag-np:", ":flag_np:", ":flag_Nepal:"}, - "\U0001f1f3\U0001f1f7": {":nauru:", ":flag-nr:", ":flag_nr:", ":flag_Nauru:"}, - "\U0001f1f3\U0001f1fa": {":niue:", ":flag-nu:", ":flag_nu:", ":flag_Niue:"}, - "\U0001f1f3\U0001f1ff": {":flag-nz:", ":flag_nz:", ":new_zealand:", ":flag_New_Zealand:"}, - "\U0001f1f4\U0001f1f2": {":oman:", ":flag-om:", ":flag_om:", ":flag_Oman:"}, - "\U0001f1f5\U0001f1e6": {":panama:", ":flag-pa:", ":flag_pa:", ":flag_Panama:"}, - "\U0001f1f5\U0001f1ea": {":peru:", ":flag-pe:", ":flag_pe:", ":flag_Peru:"}, - "\U0001f1f5\U0001f1eb": {":flag-pf:", ":flag_pf:", ":french_polynesia:", ":flag_French_Polynesia:"}, - "\U0001f1f5\U0001f1ec": {":flag-pg:", ":flag_pg:", ":papua_new_guinea:", ":flag_Papua_New_Guinea:"}, - "\U0001f1f5\U0001f1ed": {":flag-ph:", ":flag_ph:", ":philippines:", ":flag_Philippines:"}, - "\U0001f1f5\U0001f1f0": {":flag-pk:", ":flag_pk:", ":pakistan:", ":flag_Pakistan:"}, - "\U0001f1f5\U0001f1f1": {":poland:", ":flag-pl:", ":flag_pl:", ":flag_Poland:"}, - "\U0001f1f5\U0001f1f2": {":flag-pm:", ":flag_pm:", ":st_pierre_miquelon:", ":flag_St._Pierre_&_Miquelon:"}, - "\U0001f1f5\U0001f1f3": {":flag-pn:", ":flag_pn:", ":pitcairn_islands:", ":flag_Pitcairn_Islands:"}, - "\U0001f1f5\U0001f1f7": {":flag-pr:", ":flag_pr:", ":puerto_rico:", ":flag_Puerto_Rico:"}, - "\U0001f1f5\U0001f1f8": {":flag-ps:", ":flag_ps:", ":palestinian_territories:", ":flag_Palestinian_Territories:"}, - "\U0001f1f5\U0001f1f9": {":flag-pt:", ":flag_pt:", ":portugal:", ":flag_Portugal:"}, - "\U0001f1f5\U0001f1fc": {":palau:", ":flag-pw:", ":flag_pw:", ":flag_Palau:"}, - "\U0001f1f5\U0001f1fe": {":flag-py:", ":flag_py:", ":paraguay:", ":flag_Paraguay:"}, - "\U0001f1f6\U0001f1e6": {":qatar:", ":flag-qa:", ":flag_qa:", ":flag_Qatar:"}, - "\U0001f1f7\U0001f1ea": {":flag-re:", ":flag_re:", ":reunion:", ":flag_Réunion:"}, - "\U0001f1f7\U0001f1f4": {":flag-ro:", ":flag_ro:", ":romania:", ":flag_Romania:"}, - "\U0001f1f7\U0001f1f8": {":serbia:", ":flag-rs:", ":flag_rs:", ":flag_Serbia:"}, - "\U0001f1f7\U0001f1fa": {":ru:", ":flag_ru:", ":flag_Russia:"}, - "\U0001f1f7\U0001f1fc": {":rwanda:", ":flag-rw:", ":flag_rw:", ":flag_Rwanda:"}, - "\U0001f1f8\U0001f1e6": {":flag-sa:", ":flag_sa:", ":saudi_arabia:", ":flag_Saudi_Arabia:"}, - "\U0001f1f8\U0001f1e7": {":flag-sb:", ":flag_sb:", ":solomon_islands:", ":flag_Solomon_Islands:"}, - "\U0001f1f8\U0001f1e8": {":flag-sc:", ":flag_sc:", ":seychelles:", ":flag_Seychelles:"}, - "\U0001f1f8\U0001f1e9": {":sudan:", ":flag-sd:", ":flag_sd:", ":flag_Sudan:"}, - "\U0001f1f8\U0001f1ea": {":sweden:", ":flag-se:", ":flag_se:", ":flag_Sweden:"}, - "\U0001f1f8\U0001f1ec": {":flag-sg:", ":flag_sg:", ":singapore:", ":flag_Singapore:"}, - "\U0001f1f8\U0001f1ed": {":flag-sh:", ":flag_sh:", ":st_helena:", ":flag_St._Helena:"}, - "\U0001f1f8\U0001f1ee": {":flag-si:", ":flag_si:", ":slovenia:", ":flag_Slovenia:"}, - "\U0001f1f8\U0001f1ef": {":flag-sj:", ":flag_sj:", ":svalbard_jan_mayen:", ":flag_Svalbard_&_Jan_Mayen:"}, - "\U0001f1f8\U0001f1f0": {":flag-sk:", ":flag_sk:", ":slovakia:", ":flag_Slovakia:"}, - "\U0001f1f8\U0001f1f1": {":flag-sl:", ":flag_sl:", ":sierra_leone:", ":flag_Sierra_Leone:"}, - "\U0001f1f8\U0001f1f2": {":flag-sm:", ":flag_sm:", ":san_marino:", ":flag_San_Marino:"}, - "\U0001f1f8\U0001f1f3": {":flag-sn:", ":flag_sn:", ":senegal:", ":flag_Senegal:"}, - "\U0001f1f8\U0001f1f4": {":flag-so:", ":flag_so:", ":somalia:", ":flag_Somalia:"}, - "\U0001f1f8\U0001f1f7": {":flag-sr:", ":flag_sr:", ":suriname:", ":flag_Suriname:"}, - "\U0001f1f8\U0001f1f8": {":flag-ss:", ":flag_ss:", ":south_sudan:", ":flag_South_Sudan:"}, - "\U0001f1f8\U0001f1f9": {":flag-st:", ":flag_st:", ":sao_tome_principe:", ":flag_São_Tomé_&_Príncipe:"}, - "\U0001f1f8\U0001f1fb": {":flag-sv:", ":flag_sv:", ":el_salvador:", ":flag_El_Salvador:"}, - "\U0001f1f8\U0001f1fd": {":flag-sx:", ":flag_sx:", ":sint_maarten:", ":flag_Sint_Maarten:"}, - "\U0001f1f8\U0001f1fe": {":syria:", ":flag-sy:", ":flag_sy:", ":flag_Syria:"}, - "\U0001f1f8\U0001f1ff": {":flag-sz:", ":flag_sz:", ":swaziland:", ":flag_Eswatini:"}, - "\U0001f1f9\U0001f1e6": {":flag-ta:", ":flag_ta:", ":tristan_da_cunha:", ":flag_Tristan_da_Cunha:"}, - "\U0001f1f9\U0001f1e8": {":flag-tc:", ":flag_tc:", ":turks_caicos_islands:", ":flag_Turks_&_Caicos_Islands:"}, - "\U0001f1f9\U0001f1e9": {":chad:", ":flag-td:", ":flag_td:", ":flag_Chad:"}, - "\U0001f1f9\U0001f1eb": {":flag-tf:", ":flag_tf:", ":french_southern_territories:", ":flag_French_Southern_Territories:"}, - "\U0001f1f9\U0001f1ec": {":togo:", ":flag-tg:", ":flag_tg:", ":flag_Togo:"}, - "\U0001f1f9\U0001f1ed": {":flag-th:", ":flag_th:", ":thailand:", ":flag_Thailand:"}, - "\U0001f1f9\U0001f1ef": {":flag-tj:", ":flag_tj:", ":tajikistan:", ":flag_Tajikistan:"}, - "\U0001f1f9\U0001f1f0": {":flag-tk:", ":flag_tk:", ":tokelau:", ":flag_Tokelau:"}, - "\U0001f1f9\U0001f1f1": {":flag-tl:", ":flag_tl:", ":timor_leste:", ":flag_Timor-Leste:"}, - "\U0001f1f9\U0001f1f2": {":flag-tm:", ":flag_tm:", ":turkmenistan:", ":flag_Turkmenistan:"}, - "\U0001f1f9\U0001f1f3": {":flag-tn:", ":flag_tn:", ":tunisia:", ":flag_Tunisia:"}, - "\U0001f1f9\U0001f1f4": {":tonga:", ":flag-to:", ":flag_to:", ":flag_Tonga:"}, - "\U0001f1f9\U0001f1f7": {":tr:", ":flag-tr:", ":flag_tr:", ":flag_Turkey:"}, - "\U0001f1f9\U0001f1f9": {":flag-tt:", ":flag_tt:", ":trinidad_tobago:", ":flag_Trinidad_&_Tobago:"}, - "\U0001f1f9\U0001f1fb": {":tuvalu:", ":flag-tv:", ":flag_tv:", ":flag_Tuvalu:"}, - "\U0001f1f9\U0001f1fc": {":taiwan:", ":flag-tw:", ":flag_tw:", ":flag_Taiwan:"}, - "\U0001f1f9\U0001f1ff": {":flag-tz:", ":flag_tz:", ":tanzania:", ":flag_Tanzania:"}, - "\U0001f1fa\U0001f1e6": {":flag-ua:", ":flag_ua:", ":ukraine:", ":flag_Ukraine:"}, - "\U0001f1fa\U0001f1ec": {":uganda:", ":flag-ug:", ":flag_ug:", ":flag_Uganda:"}, - "\U0001f1fa\U0001f1f2": {":flag-um:", ":flag_um:", ":us_outlying_islands:", ":flag_U.S._Outlying_Islands:"}, - "\U0001f1fa\U0001f1f3": {":flag-un:", ":united_nations:", ":flag_United_Nations:"}, - "\U0001f1fa\U0001f1f8": {":us:", ":flag_us:", ":flag_United_States:"}, - "\U0001f1fa\U0001f1fe": {":flag-uy:", ":flag_uy:", ":uruguay:", ":flag_Uruguay:"}, - "\U0001f1fa\U0001f1ff": {":flag-uz:", ":flag_uz:", ":uzbekistan:", ":flag_Uzbekistan:"}, - "\U0001f1fb\U0001f1e6": {":flag-va:", ":flag_va:", ":vatican_city:", ":flag_Vatican_City:"}, - "\U0001f1fb\U0001f1e8": {":flag-vc:", ":flag_vc:", ":st_vincent_grenadines:", ":flag_St._Vincent_&_Grenadines:"}, - "\U0001f1fb\U0001f1ea": {":flag-ve:", ":flag_ve:", ":venezuela:", ":flag_Venezuela:"}, - "\U0001f1fb\U0001f1ec": {":flag-vg:", ":flag_vg:", ":british_virgin_islands:", ":flag_British_Virgin_Islands:"}, - "\U0001f1fb\U0001f1ee": {":flag-vi:", ":flag_vi:", ":us_virgin_islands:", ":flag_U.S._Virgin_Islands:"}, - "\U0001f1fb\U0001f1f3": {":flag-vn:", ":flag_vn:", ":vietnam:", ":flag_Vietnam:"}, - "\U0001f1fb\U0001f1fa": {":flag-vu:", ":flag_vu:", ":vanuatu:", ":flag_Vanuatu:"}, - "\U0001f1fc\U0001f1eb": {":flag-wf:", ":flag_wf:", ":wallis_futuna:", ":flag_Wallis_&_Futuna:"}, - "\U0001f1fc\U0001f1f8": {":samoa:", ":flag-ws:", ":flag_ws:", ":flag_Samoa:"}, - "\U0001f1fd\U0001f1f0": {":kosovo:", ":flag-xk:", ":flag_xk:", ":flag_Kosovo:"}, - "\U0001f1fe\U0001f1ea": {":yemen:", ":flag-ye:", ":flag_ye:", ":flag_Yemen:"}, - "\U0001f1fe\U0001f1f9": {":flag-yt:", ":flag_yt:", ":mayotte:", ":flag_Mayotte:"}, - "\U0001f1ff\U0001f1e6": {":flag-za:", ":flag_za:", ":south_africa:", ":flag_South_Africa:"}, - "\U0001f1ff\U0001f1f2": {":zambia:", ":flag-zm:", ":flag_zm:", ":flag_Zambia:"}, - "\U0001f1ff\U0001f1fc": {":flag-zw:", ":flag_zw:", ":zimbabwe:", ":flag_Zimbabwe:"}, - "\U0001f201": {":koko:", ":Japanese_here_button:"}, - "\U0001f202": {":Japanese_service_charge_button:"}, - "\U0001f202\ufe0f": {":sa:"}, - "\U0001f21a": {":u7121:", ":Japanese_free_of_charge_button:"}, - "\U0001f22f": {":u6307:", ":Japanese_reserved_button:"}, - "\U0001f232": {":u7981:", ":Japanese_prohibited_button:"}, - "\U0001f233": {":u7a7a:", ":Japanese_vacancy_button:"}, - "\U0001f234": {":u5408:", ":Japanese_passing_grade_button:"}, - "\U0001f235": {":u6e80:", ":Japanese_no_vacancy_button:"}, - "\U0001f236": {":u6709:", ":Japanese_not_free_of_charge_button:"}, - "\U0001f237": {":Japanese_monthly_amount_button:"}, - "\U0001f237\ufe0f": {":u6708:"}, - "\U0001f238": {":u7533:", ":Japanese_application_button:"}, - "\U0001f239": {":u5272:", ":Japanese_discount_button:"}, - "\U0001f23a": {":u55b6:", ":Japanese_open_for_business_button:"}, - "\U0001f250": {":ideograph_advantage:", ":Japanese_bargain_button:"}, - "\U0001f251": {":accept:", ":Japanese_acceptable_button:"}, - "\U0001f300": {":cyclone:"}, - "\U0001f301": {":foggy:"}, - "\U0001f302": {":closed_umbrella:"}, - "\U0001f303": {":night_with_stars:"}, - "\U0001f304": {":sunrise_over_mountains:"}, - "\U0001f305": {":sunrise:"}, - "\U0001f306": {":city_dusk:", ":city_sunset:", ":cityscape_at_dusk:"}, - "\U0001f307": {":sunset:", ":city_sunrise:"}, - "\U0001f308": {":rainbow:"}, - "\U0001f309": {":bridge_at_night:"}, - "\U0001f30a": {":ocean:", ":water_wave:"}, - "\U0001f30b": {":volcano:"}, - "\U0001f30c": {":milky_way:"}, - "\U0001f30d": {":earth_africa:", ":globe_showing_Europe-Africa:"}, - "\U0001f30e": {":earth_americas:", ":globe_showing_Americas:"}, - "\U0001f30f": {":earth_asia:", ":globe_showing_Asia-Australia:"}, - "\U0001f310": {":globe_with_meridians:"}, - "\U0001f311": {":new_moon:"}, - "\U0001f312": {":waxing_crescent_moon:"}, - "\U0001f313": {":first_quarter_moon:"}, - "\U0001f314": {":moon:", ":waxing_gibbous_moon:"}, - "\U0001f315": {":full_moon:"}, - "\U0001f316": {":waning_gibbous_moon:"}, - "\U0001f317": {":last_quarter_moon:"}, - "\U0001f318": {":waning_crescent_moon:"}, - "\U0001f319": {":crescent_moon:"}, - "\U0001f31a": {":new_moon_face:", ":new_moon_with_face:"}, - "\U0001f31b": {":first_quarter_moon_face:", ":first_quarter_moon_with_face:"}, - "\U0001f31c": {":last_quarter_moon_face:", ":last_quarter_moon_with_face:"}, - "\U0001f31d": {":full_moon_face:", ":full_moon_with_face:"}, - "\U0001f31e": {":sun_with_face:"}, - "\U0001f31f": {":star2:", ":glowing_star:"}, - "\U0001f320": {":stars:", ":shooting_star:"}, - "\U0001f321\ufe0f": {":thermometer:"}, - "\U0001f324": {":white_sun_small_cloud:", ":sun_behind_small_cloud:"}, - "\U0001f324\ufe0f": {":mostly_sunny:"}, - "\U0001f325": {":white_sun_cloud:", ":sun_behind_large_cloud:"}, - "\U0001f325\ufe0f": {":barely_sunny:"}, - "\U0001f326": {":white_sun_rain_cloud:", ":sun_behind_rain_cloud:"}, - "\U0001f326\ufe0f": {":partly_sunny_rain:"}, - "\U0001f327": {":cloud_rain:", ":cloud_with_rain:"}, - "\U0001f327\ufe0f": {":rain_cloud:"}, - "\U0001f328": {":cloud_snow:", ":cloud_with_snow:"}, - "\U0001f328\ufe0f": {":snow_cloud:"}, - "\U0001f329": {":cloud_lightning:", ":cloud_with_lightning:"}, - "\U0001f329\ufe0f": {":lightning:"}, - "\U0001f32a": {":cloud_tornado:"}, - "\U0001f32a\ufe0f": {":tornado:"}, - "\U0001f32b\ufe0f": {":fog:"}, - "\U0001f32c": {":wind_face:"}, - "\U0001f32c\ufe0f": {":wind_blowing_face:"}, - "\U0001f32d": {":hotdog:", ":hot_dog:"}, - "\U0001f32e": {":taco:"}, - "\U0001f32f": {":burrito:"}, - "\U0001f330": {":chestnut:"}, - "\U0001f331": {":seedling:"}, - "\U0001f332": {":evergreen_tree:"}, - "\U0001f333": {":deciduous_tree:"}, - "\U0001f334": {":palm_tree:"}, - "\U0001f335": {":cactus:"}, - "\U0001f336\ufe0f": {":hot_pepper:"}, - "\U0001f337": {":tulip:"}, - "\U0001f338": {":cherry_blossom:"}, - "\U0001f339": {":rose:"}, - "\U0001f33a": {":hibiscus:"}, - "\U0001f33b": {":sunflower:"}, - "\U0001f33c": {":blossom:"}, - "\U0001f33d": {":corn:", ":ear_of_corn:"}, - "\U0001f33e": {":ear_of_rice:", ":sheaf_of_rice:"}, - "\U0001f33f": {":herb:"}, - "\U0001f340": {":four_leaf_clover:"}, - "\U0001f341": {":maple_leaf:"}, - "\U0001f342": {":fallen_leaf:"}, - "\U0001f343": {":leaves:", ":leaf_fluttering_in_wind:"}, - "\U0001f344": {":mushroom:"}, - "\U0001f345": {":tomato:"}, - "\U0001f346": {":eggplant:"}, - "\U0001f347": {":grapes:"}, - "\U0001f348": {":melon:"}, - "\U0001f349": {":watermelon:"}, - "\U0001f34a": {":orange:", ":mandarin:", ":tangerine:"}, - "\U0001f34b": {":lemon:"}, - "\U0001f34c": {":banana:"}, - "\U0001f34d": {":pineapple:"}, - "\U0001f34e": {":apple:", ":red_apple:"}, - "\U0001f34f": {":green_apple:"}, - "\U0001f350": {":pear:"}, - "\U0001f351": {":peach:"}, - "\U0001f352": {":cherries:"}, - "\U0001f353": {":strawberry:"}, - "\U0001f354": {":hamburger:"}, - "\U0001f355": {":pizza:"}, - "\U0001f356": {":meat_on_bone:"}, - "\U0001f357": {":poultry_leg:"}, - "\U0001f358": {":rice_cracker:"}, - "\U0001f359": {":rice_ball:"}, - "\U0001f35a": {":rice:", ":cooked_rice:"}, - "\U0001f35b": {":curry:", ":curry_rice:"}, - "\U0001f35c": {":ramen:", ":steaming_bowl:"}, - "\U0001f35d": {":spaghetti:"}, - "\U0001f35e": {":bread:"}, - "\U0001f35f": {":fries:", ":french_fries:"}, - "\U0001f360": {":sweet_potato:", ":roasted_sweet_potato:"}, - "\U0001f361": {":dango:"}, - "\U0001f362": {":oden:"}, - "\U0001f363": {":sushi:"}, - "\U0001f364": {":fried_shrimp:"}, - "\U0001f365": {":fish_cake:", ":fish_cake_with_swirl:"}, - "\U0001f366": {":icecream:", ":soft_ice_cream:"}, - "\U0001f367": {":shaved_ice:"}, - "\U0001f368": {":ice_cream:"}, - "\U0001f369": {":doughnut:"}, - "\U0001f36a": {":cookie:"}, - "\U0001f36b": {":chocolate_bar:"}, - "\U0001f36c": {":candy:"}, - "\U0001f36d": {":lollipop:"}, - "\U0001f36e": {":custard:"}, - "\U0001f36f": {":honey_pot:"}, - "\U0001f370": {":cake:", ":shortcake:"}, - "\U0001f371": {":bento:", ":bento_box:"}, - "\U0001f372": {":stew:", ":pot_of_food:"}, - "\U0001f373": {":cooking:", ":fried_egg:"}, - "\U0001f374": {":fork_and_knife:"}, - "\U0001f375": {":tea:", ":teacup_without_handle:"}, - "\U0001f376": {":sake:"}, - "\U0001f377": {":wine_glass:"}, - "\U0001f378": {":cocktail:", ":cocktail_glass:"}, - "\U0001f379": {":tropical_drink:"}, - "\U0001f37a": {":beer:", ":beer_mug:"}, - "\U0001f37b": {":beers:", ":clinking_beer_mugs:"}, - "\U0001f37c": {":baby_bottle:"}, - "\U0001f37d": {":fork_knife_plate:", ":fork_and_knife_with_plate:"}, - "\U0001f37d\ufe0f": {":knife_fork_plate:", ":plate_with_cutlery:"}, - "\U0001f37e": {":champagne:", ":bottle_with_popping_cork:"}, - "\U0001f37f": {":popcorn:"}, - "\U0001f380": {":ribbon:"}, - "\U0001f381": {":gift:", ":wrapped_gift:"}, - "\U0001f382": {":birthday:", ":birthday_cake:"}, - "\U0001f383": {":jack-o-lantern:", ":jack_o_lantern:"}, - "\U0001f384": {":Christmas_tree:", ":christmas_tree:"}, - "\U0001f385": {":santa:", ":Santa_Claus:"}, - "\U0001f385\U0001f3fb": {":santa_tone1:"}, - "\U0001f385\U0001f3fc": {":santa_tone2:"}, - "\U0001f385\U0001f3fd": {":santa_tone3:"}, - "\U0001f385\U0001f3fe": {":santa_tone4:"}, - "\U0001f385\U0001f3ff": {":santa_tone5:"}, - "\U0001f386": {":fireworks:"}, - "\U0001f387": {":sparkler:"}, - "\U0001f388": {":balloon:"}, - "\U0001f389": {":tada:", ":party_popper:"}, - "\U0001f38a": {":confetti_ball:"}, - "\U0001f38b": {":tanabata_tree:"}, - "\U0001f38c": {":crossed_flags:"}, - "\U0001f38d": {":bamboo:", ":pine_decoration:"}, - "\U0001f38e": {":dolls:", ":Japanese_dolls:"}, - "\U0001f38f": {":flags:", ":carp_streamer:"}, - "\U0001f390": {":wind_chime:"}, - "\U0001f391": {":rice_scene:", ":moon_viewing_ceremony:"}, - "\U0001f392": {":backpack:", ":school_satchel:"}, - "\U0001f393": {":mortar_board:", ":graduation_cap:"}, - "\U0001f396": {":military_medal:"}, - "\U0001f396\ufe0f": {":medal:", ":medal_military:"}, - "\U0001f397\ufe0f": {":reminder_ribbon:"}, - "\U0001f399": {":microphone2:"}, - "\U0001f399\ufe0f": {":studio_microphone:"}, - "\U0001f39a\ufe0f": {":level_slider:"}, - "\U0001f39b\ufe0f": {":control_knobs:"}, - "\U0001f39e\ufe0f": {":film_strip:", ":film_frames:"}, - "\U0001f39f": {":tickets:"}, - "\U0001f39f\ufe0f": {":admission_tickets:"}, - "\U0001f3a0": {":carousel_horse:"}, - "\U0001f3a1": {":ferris_wheel:"}, - "\U0001f3a2": {":roller_coaster:"}, - "\U0001f3a3": {":fishing_pole:", ":fishing_pole_and_fish:"}, - "\U0001f3a4": {":microphone:"}, - "\U0001f3a5": {":movie_camera:"}, - "\U0001f3a6": {":cinema:"}, - "\U0001f3a7": {":headphone:", ":headphones:"}, - "\U0001f3a8": {":art:", ":artist_palette:"}, - "\U0001f3a9": {":tophat:", ":top_hat:"}, - "\U0001f3aa": {":circus_tent:"}, - "\U0001f3ab": {":ticket:"}, - "\U0001f3ac": {":clapper:", ":clapper_board:"}, - "\U0001f3ad": {":performing_arts:"}, - "\U0001f3ae": {":video_game:"}, - "\U0001f3af": {":dart:", ":bullseye:"}, - "\U0001f3b0": {":slot_machine:"}, - "\U0001f3b1": {":8ball:", ":pool_8_ball:"}, - "\U0001f3b2": {":game_die:"}, - "\U0001f3b3": {":bowling:"}, - "\U0001f3b4": {":flower_playing_cards:"}, - "\U0001f3b5": {":musical_note:"}, - "\U0001f3b6": {":notes:", ":musical_notes:"}, - "\U0001f3b7": {":saxophone:"}, - "\U0001f3b8": {":guitar:"}, - "\U0001f3b9": {":musical_keyboard:"}, - "\U0001f3ba": {":trumpet:"}, - "\U0001f3bb": {":violin:"}, - "\U0001f3bc": {":musical_score:"}, - "\U0001f3bd": {":running_shirt:", ":running_shirt_with_sash:"}, - "\U0001f3be": {":tennis:"}, - "\U0001f3bf": {":ski:", ":skis:"}, - "\U0001f3c0": {":basketball:"}, - "\U0001f3c1": {":checkered_flag:", ":chequered_flag:"}, - "\U0001f3c2": {":snowboarder:"}, - "\U0001f3c2\U0001f3fb": {":snowboarder_tone1:"}, - "\U0001f3c2\U0001f3fc": {":snowboarder_tone2:"}, - "\U0001f3c2\U0001f3fd": {":snowboarder_tone3:"}, - "\U0001f3c2\U0001f3fe": {":snowboarder_tone4:"}, - "\U0001f3c2\U0001f3ff": {":snowboarder_tone5:"}, - "\U0001f3c3": {":running:", ":person_running:"}, - "\U0001f3c3\U0001f3fb": {":person_running_tone1:"}, - "\U0001f3c3\U0001f3fb\u200d\u2640\ufe0f": {":woman_running_tone1:"}, - "\U0001f3c3\U0001f3fb\u200d\u2642\ufe0f": {":man_running_tone1:"}, - "\U0001f3c3\U0001f3fc": {":person_running_tone2:"}, - "\U0001f3c3\U0001f3fc\u200d\u2640\ufe0f": {":woman_running_tone2:"}, - "\U0001f3c3\U0001f3fc\u200d\u2642\ufe0f": {":man_running_tone2:"}, - "\U0001f3c3\U0001f3fd": {":person_running_tone3:"}, - "\U0001f3c3\U0001f3fd\u200d\u2640\ufe0f": {":woman_running_tone3:"}, - "\U0001f3c3\U0001f3fd\u200d\u2642\ufe0f": {":man_running_tone3:"}, - "\U0001f3c3\U0001f3fe": {":person_running_tone4:"}, - "\U0001f3c3\U0001f3fe\u200d\u2640\ufe0f": {":woman_running_tone4:"}, - "\U0001f3c3\U0001f3fe\u200d\u2642\ufe0f": {":man_running_tone4:"}, - "\U0001f3c3\U0001f3ff": {":person_running_tone5:"}, - "\U0001f3c3\U0001f3ff\u200d\u2640\ufe0f": {":woman_running_tone5:"}, - "\U0001f3c3\U0001f3ff\u200d\u2642\ufe0f": {":man_running_tone5:"}, - "\U0001f3c3\u200d\u2640\ufe0f": {":running_woman:", ":woman-running:", ":woman_running:"}, - "\U0001f3c3\u200d\u2642\ufe0f": {":runner:", ":man-running:", ":man_running:", ":running_man:"}, - "\U0001f3c4": {":person_surfing:"}, - "\U0001f3c4\U0001f3fb": {":person_surfing_tone1:"}, - "\U0001f3c4\U0001f3fb\u200d\u2640\ufe0f": {":woman_surfing_tone1:"}, - "\U0001f3c4\U0001f3fb\u200d\u2642\ufe0f": {":man_surfing_tone1:"}, - "\U0001f3c4\U0001f3fc": {":person_surfing_tone2:"}, - "\U0001f3c4\U0001f3fc\u200d\u2640\ufe0f": {":woman_surfing_tone2:"}, - "\U0001f3c4\U0001f3fc\u200d\u2642\ufe0f": {":man_surfing_tone2:"}, - "\U0001f3c4\U0001f3fd": {":person_surfing_tone3:"}, - "\U0001f3c4\U0001f3fd\u200d\u2640\ufe0f": {":woman_surfing_tone3:"}, - "\U0001f3c4\U0001f3fd\u200d\u2642\ufe0f": {":man_surfing_tone3:"}, - "\U0001f3c4\U0001f3fe": {":person_surfing_tone4:"}, - "\U0001f3c4\U0001f3fe\u200d\u2640\ufe0f": {":woman_surfing_tone4:"}, - "\U0001f3c4\U0001f3fe\u200d\u2642\ufe0f": {":man_surfing_tone4:"}, - "\U0001f3c4\U0001f3ff": {":person_surfing_tone5:"}, - "\U0001f3c4\U0001f3ff\u200d\u2640\ufe0f": {":woman_surfing_tone5:"}, - "\U0001f3c4\U0001f3ff\u200d\u2642\ufe0f": {":man_surfing_tone5:"}, - "\U0001f3c4\u200d\u2640\ufe0f": {":surfing_woman:", ":woman-surfing:", ":woman_surfing:"}, - "\U0001f3c4\u200d\u2642\ufe0f": {":surfer:", ":man-surfing:", ":man_surfing:", ":surfing_man:"}, - "\U0001f3c5": {":medal_sports:", ":sports_medal:"}, - "\U0001f3c6": {":trophy:"}, - "\U0001f3c7": {":horse_racing:"}, - "\U0001f3c7\U0001f3fb": {":horse_racing_tone1:"}, - "\U0001f3c7\U0001f3fc": {":horse_racing_tone2:"}, - "\U0001f3c7\U0001f3fd": {":horse_racing_tone3:"}, - "\U0001f3c7\U0001f3fe": {":horse_racing_tone4:"}, - "\U0001f3c7\U0001f3ff": {":horse_racing_tone5:"}, - "\U0001f3c8": {":football:", ":american_football:"}, - "\U0001f3c9": {":rugby_football:"}, - "\U0001f3ca": {":person_swimming:"}, - "\U0001f3ca\U0001f3fb": {":person_swimming_tone1:"}, - "\U0001f3ca\U0001f3fb\u200d\u2640\ufe0f": {":woman_swimming_tone1:"}, - "\U0001f3ca\U0001f3fb\u200d\u2642\ufe0f": {":man_swimming_tone1:"}, - "\U0001f3ca\U0001f3fc": {":person_swimming_tone2:"}, - "\U0001f3ca\U0001f3fc\u200d\u2640\ufe0f": {":woman_swimming_tone2:"}, - "\U0001f3ca\U0001f3fc\u200d\u2642\ufe0f": {":man_swimming_tone2:"}, - "\U0001f3ca\U0001f3fd": {":person_swimming_tone3:"}, - "\U0001f3ca\U0001f3fd\u200d\u2640\ufe0f": {":woman_swimming_tone3:"}, - "\U0001f3ca\U0001f3fd\u200d\u2642\ufe0f": {":man_swimming_tone3:"}, - "\U0001f3ca\U0001f3fe": {":person_swimming_tone4:"}, - "\U0001f3ca\U0001f3fe\u200d\u2640\ufe0f": {":woman_swimming_tone4:"}, - "\U0001f3ca\U0001f3fe\u200d\u2642\ufe0f": {":man_swimming_tone4:"}, - "\U0001f3ca\U0001f3ff": {":person_swimming_tone5:"}, - "\U0001f3ca\U0001f3ff\u200d\u2640\ufe0f": {":woman_swimming_tone5:"}, - "\U0001f3ca\U0001f3ff\u200d\u2642\ufe0f": {":man_swimming_tone5:"}, - "\U0001f3ca\u200d\u2640\ufe0f": {":swimming_woman:", ":woman-swimming:", ":woman_swimming:"}, - "\U0001f3ca\u200d\u2642\ufe0f": {":swimmer:", ":man-swimming:", ":man_swimming:", ":swimming_man:"}, - "\U0001f3cb": {":person_lifting_weights:"}, - "\U0001f3cb\U0001f3fb": {":person_lifting_weights_tone1:"}, - "\U0001f3cb\U0001f3fb\u200d\u2640\ufe0f": {":woman_lifting_weights_tone1:"}, - "\U0001f3cb\U0001f3fb\u200d\u2642\ufe0f": {":man_lifting_weights_tone1:"}, - "\U0001f3cb\U0001f3fc": {":person_lifting_weights_tone2:"}, - "\U0001f3cb\U0001f3fc\u200d\u2640\ufe0f": {":woman_lifting_weights_tone2:"}, - "\U0001f3cb\U0001f3fc\u200d\u2642\ufe0f": {":man_lifting_weights_tone2:"}, - "\U0001f3cb\U0001f3fd": {":person_lifting_weights_tone3:"}, - "\U0001f3cb\U0001f3fd\u200d\u2640\ufe0f": {":woman_lifting_weights_tone3:"}, - "\U0001f3cb\U0001f3fd\u200d\u2642\ufe0f": {":man_lifting_weights_tone3:"}, - "\U0001f3cb\U0001f3fe": {":person_lifting_weights_tone4:"}, - "\U0001f3cb\U0001f3fe\u200d\u2640\ufe0f": {":woman_lifting_weights_tone4:"}, - "\U0001f3cb\U0001f3fe\u200d\u2642\ufe0f": {":man_lifting_weights_tone4:"}, - "\U0001f3cb\U0001f3ff": {":person_lifting_weights_tone5:"}, - "\U0001f3cb\U0001f3ff\u200d\u2640\ufe0f": {":woman_lifting_weights_tone5:"}, - "\U0001f3cb\U0001f3ff\u200d\u2642\ufe0f": {":man_lifting_weights_tone5:"}, - "\U0001f3cb\ufe0f": {":weight_lifting:"}, - "\U0001f3cb\ufe0f\u200d\u2640\ufe0f": {":weight_lifting_woman:", ":woman-lifting-weights:", ":woman_lifting_weights:"}, - "\U0001f3cb\ufe0f\u200d\u2642\ufe0f": {":weight_lifter:", ":weight_lifting_man:", ":man-lifting-weights:", ":man_lifting_weights:"}, - "\U0001f3cc": {":person_golfing:"}, - "\U0001f3cc\U0001f3fb": {":person_golfing_tone1:"}, - "\U0001f3cc\U0001f3fb\u200d\u2640\ufe0f": {":woman_golfing_tone1:"}, - "\U0001f3cc\U0001f3fb\u200d\u2642\ufe0f": {":man_golfing_tone1:"}, - "\U0001f3cc\U0001f3fc": {":person_golfing_tone2:"}, - "\U0001f3cc\U0001f3fc\u200d\u2640\ufe0f": {":woman_golfing_tone2:"}, - "\U0001f3cc\U0001f3fc\u200d\u2642\ufe0f": {":man_golfing_tone2:"}, - "\U0001f3cc\U0001f3fd": {":person_golfing_tone3:"}, - "\U0001f3cc\U0001f3fd\u200d\u2640\ufe0f": {":woman_golfing_tone3:"}, - "\U0001f3cc\U0001f3fd\u200d\u2642\ufe0f": {":man_golfing_tone3:"}, - "\U0001f3cc\U0001f3fe": {":person_golfing_tone4:"}, - "\U0001f3cc\U0001f3fe\u200d\u2640\ufe0f": {":woman_golfing_tone4:"}, - "\U0001f3cc\U0001f3fe\u200d\u2642\ufe0f": {":man_golfing_tone4:"}, - "\U0001f3cc\U0001f3ff": {":person_golfing_tone5:"}, - "\U0001f3cc\U0001f3ff\u200d\u2640\ufe0f": {":woman_golfing_tone5:"}, - "\U0001f3cc\U0001f3ff\u200d\u2642\ufe0f": {":man_golfing_tone5:"}, - "\U0001f3cc\ufe0f": {":golfing:"}, - "\U0001f3cc\ufe0f\u200d\u2640\ufe0f": {":golfing_woman:", ":woman-golfing:", ":woman_golfing:"}, - "\U0001f3cc\ufe0f\u200d\u2642\ufe0f": {":golfer:", ":golfing_man:", ":man-golfing:", ":man_golfing:"}, - "\U0001f3cd": {":motorcycle:"}, - "\U0001f3cd\ufe0f": {":racing_motorcycle:"}, - "\U0001f3ce": {":race_car:"}, - "\U0001f3ce\ufe0f": {":racing_car:"}, - "\U0001f3cf": {":cricket_game:", ":cricket_bat_and_ball:"}, - "\U0001f3d0": {":volleyball:"}, - "\U0001f3d1": {":field_hockey:", ":field_hockey_stick_and_ball:"}, - "\U0001f3d2": {":hockey:", ":ice_hockey:", ":ice_hockey_stick_and_puck:"}, - "\U0001f3d3": {":ping_pong:", ":table_tennis_paddle_and_ball:"}, - "\U0001f3d4": {":mountain_snow:", ":snow-capped_mountain:"}, - "\U0001f3d4\ufe0f": {":snow_capped_mountain:"}, - "\U0001f3d5\ufe0f": {":camping:"}, - "\U0001f3d6": {":beach:"}, - "\U0001f3d6\ufe0f": {":beach_with_umbrella:"}, - "\U0001f3d7": {":construction_site:"}, - "\U0001f3d7\ufe0f": {":building_construction:"}, - "\U0001f3d8": {":homes:", ":houses:"}, - "\U0001f3d8\ufe0f": {":house_buildings:"}, - "\U0001f3d9\ufe0f": {":cityscape:"}, - "\U0001f3da": {":derelict_house:", ":house_abandoned:"}, - "\U0001f3da\ufe0f": {":derelict_house_building:"}, - "\U0001f3db\ufe0f": {":classical_building:"}, - "\U0001f3dc\ufe0f": {":desert:"}, - "\U0001f3dd": {":island:"}, - "\U0001f3dd\ufe0f": {":desert_island:"}, - "\U0001f3de": {":park:"}, - "\U0001f3de\ufe0f": {":national_park:"}, - "\U0001f3df\ufe0f": {":stadium:"}, - "\U0001f3e0": {":house:"}, - "\U0001f3e1": {":house_with_garden:"}, - "\U0001f3e2": {":office:", ":office_building:"}, - "\U0001f3e3": {":post_office:", ":Japanese_post_office:"}, - "\U0001f3e4": {":european_post_office:"}, - "\U0001f3e5": {":hospital:"}, - "\U0001f3e6": {":bank:"}, - "\U0001f3e7": {":atm:", ":ATM_sign:"}, - "\U0001f3e8": {":hotel:"}, - "\U0001f3e9": {":love_hotel:"}, - "\U0001f3ea": {":convenience_store:"}, - "\U0001f3eb": {":school:"}, - "\U0001f3ec": {":department_store:"}, - "\U0001f3ed": {":factory:"}, - "\U0001f3ee": {":lantern:", ":izakaya_lantern:", ":red_paper_lantern:"}, - "\U0001f3ef": {":Japanese_castle:", ":japanese_castle:"}, - "\U0001f3f0": {":castle:", ":european_castle:"}, - "\U0001f3f3": {":flag_white:", ":white_flag:"}, - "\U0001f3f3\ufe0f": {":waving_white_flag:"}, - "\U0001f3f3\ufe0f\u200d\U0001f308": {":rainbow-flag:", ":rainbow_flag:"}, - "\U0001f3f3\ufe0f\u200d\u26a7\ufe0f": {":transgender_flag:"}, - "\U0001f3f4": {":black_flag:", ":flag_black:", ":waving_black_flag:"}, - "\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f": {":england:", ":flag-england:", ":flag_England:"}, - "\U0001f3f4\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f": {":scotland:", ":flag-scotland:", ":flag_Scotland:"}, - "\U0001f3f4\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f": {":wales:", ":flag-wales:", ":flag_Wales:"}, - "\U0001f3f4\u200d\u2620\ufe0f": {":pirate_flag:"}, - "\U0001f3f5\ufe0f": {":rosette:"}, - "\U0001f3f7\ufe0f": {":label:"}, - "\U0001f3f8": {":badminton:", ":badminton_racquet_and_shuttlecock:"}, - "\U0001f3f9": {":bow_and_arrow:"}, - "\U0001f3fa": {":amphora:"}, - "\U0001f3fb": {":skin-tone-2:"}, - "\U0001f3fc": {":skin-tone-3:"}, - "\U0001f3fd": {":skin-tone-4:"}, - "\U0001f3fe": {":skin-tone-5:"}, - "\U0001f3ff": {":skin-tone-6:"}, - "\U0001f400": {":rat:"}, - "\U0001f401": {":mouse2:"}, - "\U0001f402": {":ox:"}, - "\U0001f403": {":water_buffalo:"}, - "\U0001f404": {":cow2:"}, - "\U0001f405": {":tiger2:"}, - "\U0001f406": {":leopard:"}, - "\U0001f407": {":rabbit2:"}, - "\U0001f408": {":cat2:"}, - "\U0001f408\u200d\u2b1b": {":black_cat:"}, - "\U0001f409": {":dragon:"}, - "\U0001f40a": {":crocodile:"}, - "\U0001f40b": {":whale2:"}, - "\U0001f40c": {":snail:"}, - "\U0001f40d": {":snake:"}, - "\U0001f40e": {":racehorse:"}, - "\U0001f40f": {":ram:"}, - "\U0001f410": {":goat:"}, - "\U0001f411": {":ewe:", ":sheep:"}, - "\U0001f412": {":monkey:"}, - "\U0001f413": {":rooster:"}, - "\U0001f414": {":chicken:"}, - "\U0001f415": {":dog2:"}, - "\U0001f415\u200d\U0001f9ba": {":service_dog:"}, - "\U0001f416": {":pig2:"}, - "\U0001f417": {":boar:"}, - "\U0001f418": {":elephant:"}, - "\U0001f419": {":octopus:"}, - "\U0001f41a": {":shell:", ":spiral_shell:"}, - "\U0001f41b": {":bug:"}, - "\U0001f41c": {":ant:"}, - "\U0001f41d": {":bee:", ":honeybee:"}, - "\U0001f41e": {":ladybug:", ":lady_beetle:"}, - "\U0001f41f": {":fish:"}, - "\U0001f420": {":tropical_fish:"}, - "\U0001f421": {":blowfish:"}, - "\U0001f422": {":turtle:"}, - "\U0001f423": {":hatching_chick:"}, - "\U0001f424": {":baby_chick:"}, - "\U0001f425": {":hatched_chick:", ":front-facing_baby_chick:"}, - "\U0001f426": {":bird:"}, - "\U0001f427": {":penguin:"}, - "\U0001f428": {":koala:"}, - "\U0001f429": {":poodle:"}, - "\U0001f42a": {":dromedary_camel:"}, - "\U0001f42b": {":camel:", ":two-hump_camel:"}, - "\U0001f42c": {":dolphin:", ":flipper:"}, - "\U0001f42d": {":mouse:", ":mouse_face:"}, - "\U0001f42e": {":cow:", ":cow_face:"}, - "\U0001f42f": {":tiger:", ":tiger_face:"}, - "\U0001f430": {":rabbit:", ":rabbit_face:"}, - "\U0001f431": {":cat:", ":cat_face:"}, - "\U0001f432": {":dragon_face:"}, - "\U0001f433": {":whale:", ":spouting_whale:"}, - "\U0001f434": {":horse:", ":horse_face:"}, - "\U0001f435": {":monkey_face:"}, - "\U0001f436": {":dog:", ":dog_face:"}, - "\U0001f437": {":pig:", ":pig_face:"}, - "\U0001f438": {":frog:"}, - "\U0001f439": {":hamster:"}, - "\U0001f43a": {":wolf:"}, - "\U0001f43b": {":bear:"}, - "\U0001f43b\u200d\u2744\ufe0f": {":polar_bear:"}, - "\U0001f43c": {":panda:", ":panda_face:"}, - "\U0001f43d": {":pig_nose:"}, - "\U0001f43e": {":feet:", ":paw_prints:"}, - "\U0001f43f\ufe0f": {":chipmunk:"}, - "\U0001f440": {":eyes:"}, - "\U0001f441\ufe0f": {":eye:"}, - "\U0001f441\ufe0f\u200d\U0001f5e8\ufe0f": {":eye_speech_bubble:", ":eye-in-speech-bubble:", ":eye_in_speech_bubble:"}, - "\U0001f442": {":ear:"}, - "\U0001f442\U0001f3fb": {":ear_tone1:"}, - "\U0001f442\U0001f3fc": {":ear_tone2:"}, - "\U0001f442\U0001f3fd": {":ear_tone3:"}, - "\U0001f442\U0001f3fe": {":ear_tone4:"}, - "\U0001f442\U0001f3ff": {":ear_tone5:"}, - "\U0001f443": {":nose:"}, - "\U0001f443\U0001f3fb": {":nose_tone1:"}, - "\U0001f443\U0001f3fc": {":nose_tone2:"}, - "\U0001f443\U0001f3fd": {":nose_tone3:"}, - "\U0001f443\U0001f3fe": {":nose_tone4:"}, - "\U0001f443\U0001f3ff": {":nose_tone5:"}, - "\U0001f444": {":lips:", ":mouth:"}, - "\U0001f445": {":tongue:"}, - "\U0001f446": {":point_up_2:", ":backhand_index_pointing_up:"}, - "\U0001f446\U0001f3fb": {":point_up_2_tone1:"}, - "\U0001f446\U0001f3fc": {":point_up_2_tone2:"}, - "\U0001f446\U0001f3fd": {":point_up_2_tone3:"}, - "\U0001f446\U0001f3fe": {":point_up_2_tone4:"}, - "\U0001f446\U0001f3ff": {":point_up_2_tone5:"}, - "\U0001f447": {":point_down:", ":backhand_index_pointing_down:"}, - "\U0001f447\U0001f3fb": {":point_down_tone1:"}, - "\U0001f447\U0001f3fc": {":point_down_tone2:"}, - "\U0001f447\U0001f3fd": {":point_down_tone3:"}, - "\U0001f447\U0001f3fe": {":point_down_tone4:"}, - "\U0001f447\U0001f3ff": {":point_down_tone5:"}, - "\U0001f448": {":point_left:", ":backhand_index_pointing_left:"}, - "\U0001f448\U0001f3fb": {":point_left_tone1:"}, - "\U0001f448\U0001f3fc": {":point_left_tone2:"}, - "\U0001f448\U0001f3fd": {":point_left_tone3:"}, - "\U0001f448\U0001f3fe": {":point_left_tone4:"}, - "\U0001f448\U0001f3ff": {":point_left_tone5:"}, - "\U0001f449": {":point_right:", ":backhand_index_pointing_right:"}, - "\U0001f449\U0001f3fb": {":point_right_tone1:"}, - "\U0001f449\U0001f3fc": {":point_right_tone2:"}, - "\U0001f449\U0001f3fd": {":point_right_tone3:"}, - "\U0001f449\U0001f3fe": {":point_right_tone4:"}, - "\U0001f449\U0001f3ff": {":point_right_tone5:"}, - "\U0001f44a": {":punch:", ":facepunch:", ":fist_oncoming:", ":oncoming_fist:"}, - "\U0001f44a\U0001f3fb": {":punch_tone1:"}, - "\U0001f44a\U0001f3fc": {":punch_tone2:"}, - "\U0001f44a\U0001f3fd": {":punch_tone3:"}, - "\U0001f44a\U0001f3fe": {":punch_tone4:"}, - "\U0001f44a\U0001f3ff": {":punch_tone5:"}, - "\U0001f44b": {":wave:", ":waving_hand:"}, - "\U0001f44b\U0001f3fb": {":wave_tone1:"}, - "\U0001f44b\U0001f3fc": {":wave_tone2:"}, - "\U0001f44b\U0001f3fd": {":wave_tone3:"}, - "\U0001f44b\U0001f3fe": {":wave_tone4:"}, - "\U0001f44b\U0001f3ff": {":wave_tone5:"}, - "\U0001f44c": {":OK_hand:", ":ok_hand:"}, - "\U0001f44c\U0001f3fb": {":ok_hand_tone1:"}, - "\U0001f44c\U0001f3fc": {":ok_hand_tone2:"}, - "\U0001f44c\U0001f3fd": {":ok_hand_tone3:"}, - "\U0001f44c\U0001f3fe": {":ok_hand_tone4:"}, - "\U0001f44c\U0001f3ff": {":ok_hand_tone5:"}, - "\U0001f44d": {":+1:", ":thumbsup:", ":thumbs_up:"}, - "\U0001f44d\U0001f3fb": {":thumbsup_tone1:"}, - "\U0001f44d\U0001f3fc": {":thumbsup_tone2:"}, - "\U0001f44d\U0001f3fd": {":thumbsup_tone3:"}, - "\U0001f44d\U0001f3fe": {":thumbsup_tone4:"}, - "\U0001f44d\U0001f3ff": {":thumbsup_tone5:"}, - "\U0001f44e": {":-1:", ":thumbsdown:", ":thumbs_down:"}, - "\U0001f44e\U0001f3fb": {":thumbsdown_tone1:"}, - "\U0001f44e\U0001f3fc": {":thumbsdown_tone2:"}, - "\U0001f44e\U0001f3fd": {":thumbsdown_tone3:"}, - "\U0001f44e\U0001f3fe": {":thumbsdown_tone4:"}, - "\U0001f44e\U0001f3ff": {":thumbsdown_tone5:"}, - "\U0001f44f": {":clap:", ":clapping_hands:"}, - "\U0001f44f\U0001f3fb": {":clap_tone1:"}, - "\U0001f44f\U0001f3fc": {":clap_tone2:"}, - "\U0001f44f\U0001f3fd": {":clap_tone3:"}, - "\U0001f44f\U0001f3fe": {":clap_tone4:"}, - "\U0001f44f\U0001f3ff": {":clap_tone5:"}, - "\U0001f450": {":open_hands:"}, - "\U0001f450\U0001f3fb": {":open_hands_tone1:"}, - "\U0001f450\U0001f3fc": {":open_hands_tone2:"}, - "\U0001f450\U0001f3fd": {":open_hands_tone3:"}, - "\U0001f450\U0001f3fe": {":open_hands_tone4:"}, - "\U0001f450\U0001f3ff": {":open_hands_tone5:"}, - "\U0001f451": {":crown:"}, - "\U0001f452": {":womans_hat:", ":woman’s_hat:"}, - "\U0001f453": {":glasses:", ":eyeglasses:"}, - "\U0001f454": {":necktie:"}, - "\U0001f455": {":shirt:", ":tshirt:", ":t-shirt:"}, - "\U0001f456": {":jeans:"}, - "\U0001f457": {":dress:"}, - "\U0001f458": {":kimono:"}, - "\U0001f459": {":bikini:"}, - "\U0001f45a": {":womans_clothes:", ":woman’s_clothes:"}, - "\U0001f45b": {":purse:"}, - "\U0001f45c": {":handbag:"}, - "\U0001f45d": {":pouch:", ":clutch_bag:"}, - "\U0001f45e": {":shoe:", ":mans_shoe:", ":man’s_shoe:"}, - "\U0001f45f": {":running_shoe:", ":athletic_shoe:"}, - "\U0001f460": {":high_heel:", ":high-heeled_shoe:"}, - "\U0001f461": {":sandal:", ":woman’s_sandal:"}, - "\U0001f462": {":boot:", ":woman’s_boot:"}, - "\U0001f463": {":footprints:"}, - "\U0001f464": {":bust_in_silhouette:"}, - "\U0001f465": {":busts_in_silhouette:"}, - "\U0001f466": {":boy:"}, - "\U0001f466\U0001f3fb": {":boy_tone1:"}, - "\U0001f466\U0001f3fc": {":boy_tone2:"}, - "\U0001f466\U0001f3fd": {":boy_tone3:"}, - "\U0001f466\U0001f3fe": {":boy_tone4:"}, - "\U0001f466\U0001f3ff": {":boy_tone5:"}, - "\U0001f467": {":girl:"}, - "\U0001f467\U0001f3fb": {":girl_tone1:"}, - "\U0001f467\U0001f3fc": {":girl_tone2:"}, - "\U0001f467\U0001f3fd": {":girl_tone3:"}, - "\U0001f467\U0001f3fe": {":girl_tone4:"}, - "\U0001f467\U0001f3ff": {":girl_tone5:"}, - "\U0001f468": {":man:"}, - "\U0001f468\U0001f3fb": {":man_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f33e": {":man_farmer_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f373": {":man_cook_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f393": {":man_student_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f3a4": {":man_singer_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f3a8": {":man_artist_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f3eb": {":man_teacher_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f3ed": {":man_factory_worker_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f4bb": {":man_technologist_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f4bc": {":man_office_worker_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f527": {":man_mechanic_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f52c": {":man_scientist_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f680": {":man_astronaut_tone1:"}, - "\U0001f468\U0001f3fb\u200d\U0001f692": {":man_firefighter_tone1:"}, - "\U0001f468\U0001f3fb\u200d\u2695\ufe0f": {":man_health_worker_tone1:"}, - "\U0001f468\U0001f3fb\u200d\u2696\ufe0f": {":man_judge_tone1:"}, - "\U0001f468\U0001f3fb\u200d\u2708\ufe0f": {":man_pilot_tone1:"}, - "\U0001f468\U0001f3fc": {":man_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f33e": {":man_farmer_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f373": {":man_cook_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f393": {":man_student_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f3a4": {":man_singer_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f3a8": {":man_artist_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f3eb": {":man_teacher_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f3ed": {":man_factory_worker_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f4bb": {":man_technologist_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f4bc": {":man_office_worker_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f527": {":man_mechanic_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f52c": {":man_scientist_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f680": {":man_astronaut_tone2:"}, - "\U0001f468\U0001f3fc\u200d\U0001f692": {":man_firefighter_tone2:"}, - "\U0001f468\U0001f3fc\u200d\u2695\ufe0f": {":man_health_worker_tone2:"}, - "\U0001f468\U0001f3fc\u200d\u2696\ufe0f": {":man_judge_tone2:"}, - "\U0001f468\U0001f3fc\u200d\u2708\ufe0f": {":man_pilot_tone2:"}, - "\U0001f468\U0001f3fd": {":man_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f33e": {":man_farmer_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f373": {":man_cook_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f393": {":man_student_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f3a4": {":man_singer_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f3a8": {":man_artist_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f3eb": {":man_teacher_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f3ed": {":man_factory_worker_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f4bb": {":man_technologist_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f4bc": {":man_office_worker_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f527": {":man_mechanic_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f52c": {":man_scientist_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f680": {":man_astronaut_tone3:"}, - "\U0001f468\U0001f3fd\u200d\U0001f692": {":man_firefighter_tone3:"}, - "\U0001f468\U0001f3fd\u200d\u2695\ufe0f": {":man_health_worker_tone3:"}, - "\U0001f468\U0001f3fd\u200d\u2696\ufe0f": {":man_judge_tone3:"}, - "\U0001f468\U0001f3fd\u200d\u2708\ufe0f": {":man_pilot_tone3:"}, - "\U0001f468\U0001f3fe": {":man_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f33e": {":man_farmer_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f373": {":man_cook_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f393": {":man_student_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f3a4": {":man_singer_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f3a8": {":man_artist_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f3eb": {":man_teacher_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f3ed": {":man_factory_worker_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f4bb": {":man_technologist_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f4bc": {":man_office_worker_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f527": {":man_mechanic_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f52c": {":man_scientist_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f680": {":man_astronaut_tone4:"}, - "\U0001f468\U0001f3fe\u200d\U0001f692": {":man_firefighter_tone4:"}, - "\U0001f468\U0001f3fe\u200d\u2695\ufe0f": {":man_health_worker_tone4:"}, - "\U0001f468\U0001f3fe\u200d\u2696\ufe0f": {":man_judge_tone4:"}, - "\U0001f468\U0001f3fe\u200d\u2708\ufe0f": {":man_pilot_tone4:"}, - "\U0001f468\U0001f3ff": {":man_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f33e": {":man_farmer_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f373": {":man_cook_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f393": {":man_student_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f3a4": {":man_singer_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f3a8": {":man_artist_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f3eb": {":man_teacher_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f3ed": {":man_factory_worker_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f4bb": {":man_technologist_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f4bc": {":man_office_worker_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f527": {":man_mechanic_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f52c": {":man_scientist_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f680": {":man_astronaut_tone5:"}, - "\U0001f468\U0001f3ff\u200d\U0001f692": {":man_firefighter_tone5:"}, - "\U0001f468\U0001f3ff\u200d\u2695\ufe0f": {":man_health_worker_tone5:"}, - "\U0001f468\U0001f3ff\u200d\u2696\ufe0f": {":man_judge_tone5:"}, - "\U0001f468\U0001f3ff\u200d\u2708\ufe0f": {":man_pilot_tone5:"}, - "\U0001f468\u200d\U0001f33e": {":man_farmer:", ":male-farmer:"}, - "\U0001f468\u200d\U0001f373": {":man_cook:", ":male-cook:"}, - "\U0001f468\u200d\U0001f37c": {":man_feeding_baby:"}, - "\U0001f468\u200d\U0001f393": {":man_student:", ":male-student:"}, - "\U0001f468\u200d\U0001f3a4": {":man_singer:", ":male-singer:"}, - "\U0001f468\u200d\U0001f3a8": {":man_artist:", ":male-artist:"}, - "\U0001f468\u200d\U0001f3eb": {":man_teacher:", ":male-teacher:"}, - "\U0001f468\u200d\U0001f3ed": {":man_factory_worker:", ":male-factory-worker:"}, - "\U0001f468\u200d\U0001f466": {":man-boy:", ":family_man_boy:"}, - "\U0001f468\u200d\U0001f466\u200d\U0001f466": {":man-boy-boy:", ":family_man_boy_boy:"}, - "\U0001f468\u200d\U0001f467": {":man-girl:", ":family_man_girl:"}, - "\U0001f468\u200d\U0001f467\u200d\U0001f466": {":man-girl-boy:", ":family_man_girl_boy:"}, - "\U0001f468\u200d\U0001f467\u200d\U0001f467": {":man-girl-girl:", ":family_man_girl_girl:"}, - "\U0001f468\u200d\U0001f468\u200d\U0001f466": {":family_mmb:", ":man-man-boy:", ":family_man_man_boy:"}, - "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466": {":family_mmbb:", ":man-man-boy-boy:", ":family_man_man_boy_boy:"}, - "\U0001f468\u200d\U0001f468\u200d\U0001f467": {":family_mmg:", ":man-man-girl:", ":family_man_man_girl:"}, - "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466": {":family_mmgb:", ":man-man-girl-boy:", ":family_man_man_girl_boy:"}, - "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467": {":family_mmgg:", ":man-man-girl-girl:", ":family_man_man_girl_girl:"}, - "\U0001f468\u200d\U0001f469\u200d\U0001f466": {":family:", ":man-woman-boy:", ":family_man_woman_boy:"}, - "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466": {":family_mwbb:", ":man-woman-boy-boy:", ":family_man_woman_boy_boy:"}, - "\U0001f468\u200d\U0001f469\u200d\U0001f467": {":family_mwg:", ":man-woman-girl:", ":family_man_woman_girl:"}, - "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466": {":family_mwgb:", ":man-woman-girl-boy:", ":family_man_woman_girl_boy:"}, - "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467": {":family_mwgg:", ":man-woman-girl-girl:", ":family_man_woman_girl_girl:"}, - "\U0001f468\u200d\U0001f4bb": {":man_technologist:", ":male-technologist:"}, - "\U0001f468\u200d\U0001f4bc": {":man_office_worker:", ":male-office-worker:"}, - "\U0001f468\u200d\U0001f527": {":man_mechanic:", ":male-mechanic:"}, - "\U0001f468\u200d\U0001f52c": {":man_scientist:", ":male-scientist:"}, - "\U0001f468\u200d\U0001f680": {":man_astronaut:", ":male-astronaut:"}, - "\U0001f468\u200d\U0001f692": {":man_firefighter:", ":male-firefighter:"}, - "\U0001f468\u200d\U0001f9af": {":man_with_white_cane:", ":man_with_probing_cane:"}, - "\U0001f468\u200d\U0001f9b0": {":man_red_hair:", ":red_haired_man:"}, - "\U0001f468\u200d\U0001f9b1": {":man_curly_hair:", ":curly_haired_man:"}, - "\U0001f468\u200d\U0001f9b2": {":bald_man:", ":man_bald:"}, - "\U0001f468\u200d\U0001f9b3": {":man_white_hair:", ":white_haired_man:"}, - "\U0001f468\u200d\U0001f9bc": {":man_in_motorized_wheelchair:"}, - "\U0001f468\u200d\U0001f9bd": {":man_in_manual_wheelchair:"}, - "\U0001f468\u200d\u2695\ufe0f": {":male-doctor:", ":man_health_worker:"}, - "\U0001f468\u200d\u2696\ufe0f": {":man_judge:", ":male-judge:"}, - "\U0001f468\u200d\u2708\ufe0f": {":man_pilot:", ":male-pilot:"}, - "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468": {":couple_mm:", ":man-heart-man:", ":couple_with_heart_man_man:"}, - "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468": {":kiss_mm:", ":kiss_man_man:", ":man-kiss-man:", ":couplekiss_man_man:"}, - "\U0001f469": {":woman:"}, - "\U0001f469\U0001f3fb": {":woman_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f33e": {":woman_farmer_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f373": {":woman_cook_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f393": {":woman_student_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f3a4": {":woman_singer_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f3a8": {":woman_artist_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f3eb": {":woman_teacher_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f3ed": {":woman_factory_worker_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f4bb": {":woman_technologist_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f4bc": {":woman_office_worker_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f527": {":woman_mechanic_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f52c": {":woman_scientist_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f680": {":woman_astronaut_tone1:"}, - "\U0001f469\U0001f3fb\u200d\U0001f692": {":woman_firefighter_tone1:"}, - "\U0001f469\U0001f3fb\u200d\u2695\ufe0f": {":woman_health_worker_tone1:"}, - "\U0001f469\U0001f3fb\u200d\u2696\ufe0f": {":woman_judge_tone1:"}, - "\U0001f469\U0001f3fb\u200d\u2708\ufe0f": {":woman_pilot_tone1:"}, - "\U0001f469\U0001f3fc": {":woman_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f33e": {":woman_farmer_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f373": {":woman_cook_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f393": {":woman_student_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f3a4": {":woman_singer_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f3a8": {":woman_artist_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f3eb": {":woman_teacher_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f3ed": {":woman_factory_worker_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f4bb": {":woman_technologist_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f4bc": {":woman_office_worker_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f527": {":woman_mechanic_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f52c": {":woman_scientist_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f680": {":woman_astronaut_tone2:"}, - "\U0001f469\U0001f3fc\u200d\U0001f692": {":woman_firefighter_tone2:"}, - "\U0001f469\U0001f3fc\u200d\u2695\ufe0f": {":woman_health_worker_tone2:"}, - "\U0001f469\U0001f3fc\u200d\u2696\ufe0f": {":woman_judge_tone2:"}, - "\U0001f469\U0001f3fc\u200d\u2708\ufe0f": {":woman_pilot_tone2:"}, - "\U0001f469\U0001f3fd": {":woman_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f33e": {":woman_farmer_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f373": {":woman_cook_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f393": {":woman_student_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f3a4": {":woman_singer_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f3a8": {":woman_artist_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f3eb": {":woman_teacher_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f3ed": {":woman_factory_worker_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f4bb": {":woman_technologist_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f4bc": {":woman_office_worker_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f527": {":woman_mechanic_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f52c": {":woman_scientist_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f680": {":woman_astronaut_tone3:"}, - "\U0001f469\U0001f3fd\u200d\U0001f692": {":woman_firefighter_tone3:"}, - "\U0001f469\U0001f3fd\u200d\u2695\ufe0f": {":woman_health_worker_tone3:"}, - "\U0001f469\U0001f3fd\u200d\u2696\ufe0f": {":woman_judge_tone3:"}, - "\U0001f469\U0001f3fd\u200d\u2708\ufe0f": {":woman_pilot_tone3:"}, - "\U0001f469\U0001f3fe": {":woman_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f33e": {":woman_farmer_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f373": {":woman_cook_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f393": {":woman_student_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f3a4": {":woman_singer_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f3a8": {":woman_artist_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f3eb": {":woman_teacher_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f3ed": {":woman_factory_worker_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f4bb": {":woman_technologist_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f4bc": {":woman_office_worker_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f527": {":woman_mechanic_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f52c": {":woman_scientist_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f680": {":woman_astronaut_tone4:"}, - "\U0001f469\U0001f3fe\u200d\U0001f692": {":woman_firefighter_tone4:"}, - "\U0001f469\U0001f3fe\u200d\u2695\ufe0f": {":woman_health_worker_tone4:"}, - "\U0001f469\U0001f3fe\u200d\u2696\ufe0f": {":woman_judge_tone4:"}, - "\U0001f469\U0001f3fe\u200d\u2708\ufe0f": {":woman_pilot_tone4:"}, - "\U0001f469\U0001f3ff": {":woman_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f33e": {":woman_farmer_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f373": {":woman_cook_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f393": {":woman_student_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f3a4": {":woman_singer_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f3a8": {":woman_artist_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f3eb": {":woman_teacher_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f3ed": {":woman_factory_worker_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f4bb": {":woman_technologist_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f4bc": {":woman_office_worker_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f527": {":woman_mechanic_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f52c": {":woman_scientist_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f680": {":woman_astronaut_tone5:"}, - "\U0001f469\U0001f3ff\u200d\U0001f692": {":woman_firefighter_tone5:"}, - "\U0001f469\U0001f3ff\u200d\u2695\ufe0f": {":woman_health_worker_tone5:"}, - "\U0001f469\U0001f3ff\u200d\u2696\ufe0f": {":woman_judge_tone5:"}, - "\U0001f469\U0001f3ff\u200d\u2708\ufe0f": {":woman_pilot_tone5:"}, - "\U0001f469\u200d\U0001f33e": {":woman_farmer:", ":female-farmer:"}, - "\U0001f469\u200d\U0001f373": {":woman_cook:", ":female-cook:"}, - "\U0001f469\u200d\U0001f37c": {":woman_feeding_baby:"}, - "\U0001f469\u200d\U0001f393": {":woman_student:", ":female-student:"}, - "\U0001f469\u200d\U0001f3a4": {":woman_singer:", ":female-singer:"}, - "\U0001f469\u200d\U0001f3a8": {":woman_artist:", ":female-artist:"}, - "\U0001f469\u200d\U0001f3eb": {":woman_teacher:", ":female-teacher:"}, - "\U0001f469\u200d\U0001f3ed": {":woman_factory_worker:", ":female-factory-worker:"}, - "\U0001f469\u200d\U0001f466": {":woman-boy:", ":family_woman_boy:"}, - "\U0001f469\u200d\U0001f466\u200d\U0001f466": {":woman-boy-boy:", ":family_woman_boy_boy:"}, - "\U0001f469\u200d\U0001f467": {":woman-girl:", ":family_woman_girl:"}, - "\U0001f469\u200d\U0001f467\u200d\U0001f466": {":woman-girl-boy:", ":family_woman_girl_boy:"}, - "\U0001f469\u200d\U0001f467\u200d\U0001f467": {":woman-girl-girl:", ":family_woman_girl_girl:"}, - "\U0001f469\u200d\U0001f469\u200d\U0001f466": {":family_wwb:", ":woman-woman-boy:", ":family_woman_woman_boy:"}, - "\U0001f469\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466": {":family_wwbb:", ":woman-woman-boy-boy:", ":family_woman_woman_boy_boy:"}, - "\U0001f469\u200d\U0001f469\u200d\U0001f467": {":family_wwg:", ":woman-woman-girl:", ":family_woman_woman_girl:"}, - "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466": {":family_wwgb:", ":woman-woman-girl-boy:", ":family_woman_woman_girl_boy:"}, - "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467": {":family_wwgg:", ":woman-woman-girl-girl:", ":family_woman_woman_girl_girl:"}, - "\U0001f469\u200d\U0001f4bb": {":woman_technologist:", ":female-technologist:"}, - "\U0001f469\u200d\U0001f4bc": {":woman_office_worker:", ":female-office-worker:"}, - "\U0001f469\u200d\U0001f527": {":woman_mechanic:", ":female-mechanic:"}, - "\U0001f469\u200d\U0001f52c": {":woman_scientist:", ":female-scientist:"}, - "\U0001f469\u200d\U0001f680": {":woman_astronaut:", ":female-astronaut:"}, - "\U0001f469\u200d\U0001f692": {":woman_firefighter:", ":female-firefighter:"}, - "\U0001f469\u200d\U0001f9af": {":woman_with_white_cane:", ":woman_with_probing_cane:"}, - "\U0001f469\u200d\U0001f9b0": {":woman_red_hair:", ":red_haired_woman:"}, - "\U0001f469\u200d\U0001f9b1": {":woman_curly_hair:", ":curly_haired_woman:"}, - "\U0001f469\u200d\U0001f9b2": {":bald_woman:", ":woman_bald:"}, - "\U0001f469\u200d\U0001f9b3": {":woman_white_hair:", ":white_haired_woman:"}, - "\U0001f469\u200d\U0001f9bc": {":woman_in_motorized_wheelchair:"}, - "\U0001f469\u200d\U0001f9bd": {":woman_in_manual_wheelchair:"}, - "\U0001f469\u200d\u2695\ufe0f": {":female-doctor:", ":woman_health_worker:"}, - "\U0001f469\u200d\u2696\ufe0f": {":woman_judge:", ":female-judge:"}, - "\U0001f469\u200d\u2708\ufe0f": {":woman_pilot:", ":female-pilot:"}, - "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468": {":woman-heart-man:", ":couple_with_heart:", ":couple_with_heart_woman_man:"}, - "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469": {":couple_ww:", ":woman-heart-woman:", ":couple_with_heart_woman_woman:"}, - "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468": {":couplekiss:", ":kiss_woman_man:", ":woman-kiss-man:", ":couplekiss_man_woman:"}, - "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469": {":kiss_ww:", ":kiss_woman_woman:", ":woman-kiss-woman:", ":couplekiss_woman_woman:"}, - "\U0001f46b": {":couple:", ":man_and_woman_holding_hands:", ":woman_and_man_holding_hands:"}, - "\U0001f46c": {":men_holding_hands:", ":two_men_holding_hands:"}, - "\U0001f46d": {":women_holding_hands:", ":two_women_holding_hands:"}, - "\U0001f46e": {":police_officer:"}, - "\U0001f46e\U0001f3fb": {":police_officer_tone1:"}, - "\U0001f46e\U0001f3fb\u200d\u2640\ufe0f": {":woman_police_officer_tone1:"}, - "\U0001f46e\U0001f3fb\u200d\u2642\ufe0f": {":man_police_officer_tone1:"}, - "\U0001f46e\U0001f3fc": {":police_officer_tone2:"}, - "\U0001f46e\U0001f3fc\u200d\u2640\ufe0f": {":woman_police_officer_tone2:"}, - "\U0001f46e\U0001f3fc\u200d\u2642\ufe0f": {":man_police_officer_tone2:"}, - "\U0001f46e\U0001f3fd": {":police_officer_tone3:"}, - "\U0001f46e\U0001f3fd\u200d\u2640\ufe0f": {":woman_police_officer_tone3:"}, - "\U0001f46e\U0001f3fd\u200d\u2642\ufe0f": {":man_police_officer_tone3:"}, - "\U0001f46e\U0001f3fe": {":police_officer_tone4:"}, - "\U0001f46e\U0001f3fe\u200d\u2640\ufe0f": {":woman_police_officer_tone4:"}, - "\U0001f46e\U0001f3fe\u200d\u2642\ufe0f": {":man_police_officer_tone4:"}, - "\U0001f46e\U0001f3ff": {":police_officer_tone5:"}, - "\U0001f46e\U0001f3ff\u200d\u2640\ufe0f": {":woman_police_officer_tone5:"}, - "\U0001f46e\U0001f3ff\u200d\u2642\ufe0f": {":man_police_officer_tone5:"}, - "\U0001f46e\u200d\u2640\ufe0f": {":policewoman:", ":woman_police_officer:", ":female-police-officer:"}, - "\U0001f46e\u200d\u2642\ufe0f": {":cop:", ":policeman:", ":man_police_officer:", ":male-police-officer:"}, - "\U0001f46f": {":people_with_bunny_ears:", ":people_with_bunny_ears_partying:"}, - "\U0001f46f\u200d\u2640\ufe0f": {":dancers:", ":dancing_women:", ":women_with_bunny_ears:", ":woman-with-bunny-ears-partying:", ":women_with_bunny_ears_partying:"}, - "\U0001f46f\u200d\u2642\ufe0f": {":dancing_men:", ":men_with_bunny_ears:", ":man-with-bunny-ears-partying:", ":men_with_bunny_ears_partying:"}, - "\U0001f470": {":bride_with_veil:", ":person_with_veil:"}, - "\U0001f470\U0001f3fb": {":bride_with_veil_tone1:"}, - "\U0001f470\U0001f3fc": {":bride_with_veil_tone2:"}, - "\U0001f470\U0001f3fd": {":bride_with_veil_tone3:"}, - "\U0001f470\U0001f3fe": {":bride_with_veil_tone4:"}, - "\U0001f470\U0001f3ff": {":bride_with_veil_tone5:"}, - "\U0001f470\u200d\u2640\ufe0f": {":woman_with_veil:"}, - "\U0001f470\u200d\u2642\ufe0f": {":man_with_veil:"}, - "\U0001f471": {":person_blond_hair:", ":blond_haired_person:"}, - "\U0001f471\U0001f3fb": {":blond_haired_person_tone1:"}, - "\U0001f471\U0001f3fb\u200d\u2640\ufe0f": {":blond-haired_woman_tone1:"}, - "\U0001f471\U0001f3fb\u200d\u2642\ufe0f": {":blond-haired_man_tone1:"}, - "\U0001f471\U0001f3fc": {":blond_haired_person_tone2:"}, - "\U0001f471\U0001f3fc\u200d\u2640\ufe0f": {":blond-haired_woman_tone2:"}, - "\U0001f471\U0001f3fc\u200d\u2642\ufe0f": {":blond-haired_man_tone2:"}, - "\U0001f471\U0001f3fd": {":blond_haired_person_tone3:"}, - "\U0001f471\U0001f3fd\u200d\u2640\ufe0f": {":blond-haired_woman_tone3:"}, - "\U0001f471\U0001f3fd\u200d\u2642\ufe0f": {":blond-haired_man_tone3:"}, - "\U0001f471\U0001f3fe": {":blond_haired_person_tone4:"}, - "\U0001f471\U0001f3fe\u200d\u2640\ufe0f": {":blond-haired_woman_tone4:"}, - "\U0001f471\U0001f3fe\u200d\u2642\ufe0f": {":blond-haired_man_tone4:"}, - "\U0001f471\U0001f3ff": {":blond_haired_person_tone5:"}, - "\U0001f471\U0001f3ff\u200d\u2640\ufe0f": {":blond-haired_woman_tone5:"}, - "\U0001f471\U0001f3ff\u200d\u2642\ufe0f": {":blond-haired_man_tone5:"}, - "\U0001f471\u200d\u2640\ufe0f": {":blonde_woman:", ":woman_blond_hair:", ":blond-haired-woman:", ":blond-haired_woman:", ":blond_haired_woman:"}, - "\U0001f471\u200d\u2642\ufe0f": {":man_blond_hair:", ":blond-haired-man:", ":blond-haired_man:", ":blond_haired_man:", ":person_with_blond_hair:"}, - "\U0001f472": {":man_with_gua_pi_mao:", ":man_with_chinese_cap:", ":person_with_skullcap:"}, - "\U0001f472\U0001f3fb": {":man_with_chinese_cap_tone1:"}, - "\U0001f472\U0001f3fc": {":man_with_chinese_cap_tone2:"}, - "\U0001f472\U0001f3fd": {":man_with_chinese_cap_tone3:"}, - "\U0001f472\U0001f3fe": {":man_with_chinese_cap_tone4:"}, - "\U0001f472\U0001f3ff": {":man_with_chinese_cap_tone5:"}, - "\U0001f473": {":person_with_turban:", ":person_wearing_turban:"}, - "\U0001f473\U0001f3fb": {":person_wearing_turban_tone1:"}, - "\U0001f473\U0001f3fb\u200d\u2640\ufe0f": {":woman_wearing_turban_tone1:"}, - "\U0001f473\U0001f3fb\u200d\u2642\ufe0f": {":man_wearing_turban_tone1:"}, - "\U0001f473\U0001f3fc": {":person_wearing_turban_tone2:"}, - "\U0001f473\U0001f3fc\u200d\u2640\ufe0f": {":woman_wearing_turban_tone2:"}, - "\U0001f473\U0001f3fc\u200d\u2642\ufe0f": {":man_wearing_turban_tone2:"}, - "\U0001f473\U0001f3fd": {":person_wearing_turban_tone3:"}, - "\U0001f473\U0001f3fd\u200d\u2640\ufe0f": {":woman_wearing_turban_tone3:"}, - "\U0001f473\U0001f3fd\u200d\u2642\ufe0f": {":man_wearing_turban_tone3:"}, - "\U0001f473\U0001f3fe": {":person_wearing_turban_tone4:"}, - "\U0001f473\U0001f3fe\u200d\u2640\ufe0f": {":woman_wearing_turban_tone4:"}, - "\U0001f473\U0001f3fe\u200d\u2642\ufe0f": {":man_wearing_turban_tone4:"}, - "\U0001f473\U0001f3ff": {":person_wearing_turban_tone5:"}, - "\U0001f473\U0001f3ff\u200d\u2640\ufe0f": {":woman_wearing_turban_tone5:"}, - "\U0001f473\U0001f3ff\u200d\u2642\ufe0f": {":man_wearing_turban_tone5:"}, - "\U0001f473\u200d\u2640\ufe0f": {":woman_with_turban:", ":woman-wearing-turban:", ":woman_wearing_turban:"}, - "\U0001f473\u200d\u2642\ufe0f": {":man_with_turban:", ":man-wearing-turban:", ":man_wearing_turban:"}, - "\U0001f474": {":old_man:", ":older_man:"}, - "\U0001f474\U0001f3fb": {":older_man_tone1:"}, - "\U0001f474\U0001f3fc": {":older_man_tone2:"}, - "\U0001f474\U0001f3fd": {":older_man_tone3:"}, - "\U0001f474\U0001f3fe": {":older_man_tone4:"}, - "\U0001f474\U0001f3ff": {":older_man_tone5:"}, - "\U0001f475": {":old_woman:", ":older_woman:"}, - "\U0001f475\U0001f3fb": {":older_woman_tone1:"}, - "\U0001f475\U0001f3fc": {":older_woman_tone2:"}, - "\U0001f475\U0001f3fd": {":older_woman_tone3:"}, - "\U0001f475\U0001f3fe": {":older_woman_tone4:"}, - "\U0001f475\U0001f3ff": {":older_woman_tone5:"}, - "\U0001f476": {":baby:"}, - "\U0001f476\U0001f3fb": {":baby_tone1:"}, - "\U0001f476\U0001f3fc": {":baby_tone2:"}, - "\U0001f476\U0001f3fd": {":baby_tone3:"}, - "\U0001f476\U0001f3fe": {":baby_tone4:"}, - "\U0001f476\U0001f3ff": {":baby_tone5:"}, - "\U0001f477\U0001f3fb": {":construction_worker_tone1:"}, - "\U0001f477\U0001f3fb\u200d\u2640\ufe0f": {":woman_construction_worker_tone1:"}, - "\U0001f477\U0001f3fb\u200d\u2642\ufe0f": {":man_construction_worker_tone1:"}, - "\U0001f477\U0001f3fc": {":construction_worker_tone2:"}, - "\U0001f477\U0001f3fc\u200d\u2640\ufe0f": {":woman_construction_worker_tone2:"}, - "\U0001f477\U0001f3fc\u200d\u2642\ufe0f": {":man_construction_worker_tone2:"}, - "\U0001f477\U0001f3fd": {":construction_worker_tone3:"}, - "\U0001f477\U0001f3fd\u200d\u2640\ufe0f": {":woman_construction_worker_tone3:"}, - "\U0001f477\U0001f3fd\u200d\u2642\ufe0f": {":man_construction_worker_tone3:"}, - "\U0001f477\U0001f3fe": {":construction_worker_tone4:"}, - "\U0001f477\U0001f3fe\u200d\u2640\ufe0f": {":woman_construction_worker_tone4:"}, - "\U0001f477\U0001f3fe\u200d\u2642\ufe0f": {":man_construction_worker_tone4:"}, - "\U0001f477\U0001f3ff": {":construction_worker_tone5:"}, - "\U0001f477\U0001f3ff\u200d\u2640\ufe0f": {":woman_construction_worker_tone5:"}, - "\U0001f477\U0001f3ff\u200d\u2642\ufe0f": {":man_construction_worker_tone5:"}, - "\U0001f477\u200d\u2640\ufe0f": {":construction_worker_woman:", ":woman_construction_worker:", ":female-construction-worker:"}, - "\U0001f477\u200d\u2642\ufe0f": {":construction_worker:", ":construction_worker_man:", ":man_construction_worker:", ":male-construction-worker:"}, - "\U0001f478": {":princess:"}, - "\U0001f478\U0001f3fb": {":princess_tone1:"}, - "\U0001f478\U0001f3fc": {":princess_tone2:"}, - "\U0001f478\U0001f3fd": {":princess_tone3:"}, - "\U0001f478\U0001f3fe": {":princess_tone4:"}, - "\U0001f478\U0001f3ff": {":princess_tone5:"}, - "\U0001f479": {":ogre:", ":japanese_ogre:"}, - "\U0001f47a": {":goblin:", ":japanese_goblin:"}, - "\U0001f47b": {":ghost:"}, - "\U0001f47c": {":angel:", ":baby_angel:"}, - "\U0001f47c\U0001f3fb": {":angel_tone1:"}, - "\U0001f47c\U0001f3fc": {":angel_tone2:"}, - "\U0001f47c\U0001f3fd": {":angel_tone3:"}, - "\U0001f47c\U0001f3fe": {":angel_tone4:"}, - "\U0001f47c\U0001f3ff": {":angel_tone5:"}, - "\U0001f47d": {":alien:"}, - "\U0001f47e": {":alien_monster:", ":space_invader:"}, - "\U0001f47f": {":imp:", ":angry_face_with_horns:"}, - "\U0001f480": {":skull:"}, - "\U0001f481": {":person_tipping_hand:", ":tipping_hand_person:"}, - "\U0001f481\U0001f3fb": {":person_tipping_hand_tone1:"}, - "\U0001f481\U0001f3fb\u200d\u2640\ufe0f": {":woman_tipping_hand_tone1:"}, - "\U0001f481\U0001f3fb\u200d\u2642\ufe0f": {":man_tipping_hand_tone1:"}, - "\U0001f481\U0001f3fc": {":person_tipping_hand_tone2:"}, - "\U0001f481\U0001f3fc\u200d\u2640\ufe0f": {":woman_tipping_hand_tone2:"}, - "\U0001f481\U0001f3fc\u200d\u2642\ufe0f": {":man_tipping_hand_tone2:"}, - "\U0001f481\U0001f3fd": {":person_tipping_hand_tone3:"}, - "\U0001f481\U0001f3fd\u200d\u2640\ufe0f": {":woman_tipping_hand_tone3:"}, - "\U0001f481\U0001f3fd\u200d\u2642\ufe0f": {":man_tipping_hand_tone3:"}, - "\U0001f481\U0001f3fe": {":person_tipping_hand_tone4:"}, - "\U0001f481\U0001f3fe\u200d\u2640\ufe0f": {":woman_tipping_hand_tone4:"}, - "\U0001f481\U0001f3fe\u200d\u2642\ufe0f": {":man_tipping_hand_tone4:"}, - "\U0001f481\U0001f3ff": {":person_tipping_hand_tone5:"}, - "\U0001f481\U0001f3ff\u200d\u2640\ufe0f": {":woman_tipping_hand_tone5:"}, - "\U0001f481\U0001f3ff\u200d\u2642\ufe0f": {":man_tipping_hand_tone5:"}, - "\U0001f481\u200d\u2640\ufe0f": {":sassy_woman:", ":tipping_hand_woman:", ":woman-tipping-hand:", ":woman_tipping_hand:", ":information_desk_person:"}, - "\U0001f481\u200d\u2642\ufe0f": {":sassy_man:", ":man-tipping-hand:", ":man_tipping_hand:", ":tipping_hand_man:"}, - "\U0001f482": {":guard:"}, - "\U0001f482\U0001f3fb": {":guard_tone1:"}, - "\U0001f482\U0001f3fb\u200d\u2640\ufe0f": {":woman_guard_tone1:"}, - "\U0001f482\U0001f3fb\u200d\u2642\ufe0f": {":man_guard_tone1:"}, - "\U0001f482\U0001f3fc": {":guard_tone2:"}, - "\U0001f482\U0001f3fc\u200d\u2640\ufe0f": {":woman_guard_tone2:"}, - "\U0001f482\U0001f3fc\u200d\u2642\ufe0f": {":man_guard_tone2:"}, - "\U0001f482\U0001f3fd": {":guard_tone3:"}, - "\U0001f482\U0001f3fd\u200d\u2640\ufe0f": {":woman_guard_tone3:"}, - "\U0001f482\U0001f3fd\u200d\u2642\ufe0f": {":man_guard_tone3:"}, - "\U0001f482\U0001f3fe": {":guard_tone4:"}, - "\U0001f482\U0001f3fe\u200d\u2640\ufe0f": {":woman_guard_tone4:"}, - "\U0001f482\U0001f3fe\u200d\u2642\ufe0f": {":man_guard_tone4:"}, - "\U0001f482\U0001f3ff": {":guard_tone5:"}, - "\U0001f482\U0001f3ff\u200d\u2640\ufe0f": {":woman_guard_tone5:"}, - "\U0001f482\U0001f3ff\u200d\u2642\ufe0f": {":man_guard_tone5:"}, - "\U0001f482\u200d\u2640\ufe0f": {":guardswoman:", ":woman_guard:", ":female-guard:"}, - "\U0001f482\u200d\u2642\ufe0f": {":guardsman:", ":man_guard:", ":male-guard:"}, - "\U0001f483": {":dancer:", ":woman_dancing:"}, - "\U0001f483\U0001f3fb": {":dancer_tone1:"}, - "\U0001f483\U0001f3fc": {":dancer_tone2:"}, - "\U0001f483\U0001f3fd": {":dancer_tone3:"}, - "\U0001f483\U0001f3fe": {":dancer_tone4:"}, - "\U0001f483\U0001f3ff": {":dancer_tone5:"}, - "\U0001f484": {":lipstick:"}, - "\U0001f485": {":nail_care:", ":nail_polish:"}, - "\U0001f485\U0001f3fb": {":nail_care_tone1:"}, - "\U0001f485\U0001f3fc": {":nail_care_tone2:"}, - "\U0001f485\U0001f3fd": {":nail_care_tone3:"}, - "\U0001f485\U0001f3fe": {":nail_care_tone4:"}, - "\U0001f485\U0001f3ff": {":nail_care_tone5:"}, - "\U0001f486": {":person_getting_massage:"}, - "\U0001f486\U0001f3fb": {":person_getting_massage_tone1:"}, - "\U0001f486\U0001f3fb\u200d\u2640\ufe0f": {":woman_getting_face_massage_tone1:"}, - "\U0001f486\U0001f3fb\u200d\u2642\ufe0f": {":man_getting_face_massage_tone1:"}, - "\U0001f486\U0001f3fc": {":person_getting_massage_tone2:"}, - "\U0001f486\U0001f3fc\u200d\u2640\ufe0f": {":woman_getting_face_massage_tone2:"}, - "\U0001f486\U0001f3fc\u200d\u2642\ufe0f": {":man_getting_face_massage_tone2:"}, - "\U0001f486\U0001f3fd": {":person_getting_massage_tone3:"}, - "\U0001f486\U0001f3fd\u200d\u2640\ufe0f": {":woman_getting_face_massage_tone3:"}, - "\U0001f486\U0001f3fd\u200d\u2642\ufe0f": {":man_getting_face_massage_tone3:"}, - "\U0001f486\U0001f3fe": {":person_getting_massage_tone4:"}, - "\U0001f486\U0001f3fe\u200d\u2640\ufe0f": {":woman_getting_face_massage_tone4:"}, - "\U0001f486\U0001f3fe\u200d\u2642\ufe0f": {":man_getting_face_massage_tone4:"}, - "\U0001f486\U0001f3ff": {":person_getting_massage_tone5:"}, - "\U0001f486\U0001f3ff\u200d\u2640\ufe0f": {":woman_getting_face_massage_tone5:"}, - "\U0001f486\U0001f3ff\u200d\u2642\ufe0f": {":man_getting_face_massage_tone5:"}, - "\U0001f486\u200d\u2640\ufe0f": {":massage:", ":massage_woman:", ":woman-getting-massage:", ":woman_getting_massage:", ":woman_getting_face_massage:"}, - "\U0001f486\u200d\u2642\ufe0f": {":massage_man:", ":man-getting-massage:", ":man_getting_massage:", ":man_getting_face_massage:"}, - "\U0001f487": {":person_getting_haircut:"}, - "\U0001f487\U0001f3fb": {":person_getting_haircut_tone1:"}, - "\U0001f487\U0001f3fb\u200d\u2640\ufe0f": {":woman_getting_haircut_tone1:"}, - "\U0001f487\U0001f3fb\u200d\u2642\ufe0f": {":man_getting_haircut_tone1:"}, - "\U0001f487\U0001f3fc": {":person_getting_haircut_tone2:"}, - "\U0001f487\U0001f3fc\u200d\u2640\ufe0f": {":woman_getting_haircut_tone2:"}, - "\U0001f487\U0001f3fc\u200d\u2642\ufe0f": {":man_getting_haircut_tone2:"}, - "\U0001f487\U0001f3fd": {":person_getting_haircut_tone3:"}, - "\U0001f487\U0001f3fd\u200d\u2640\ufe0f": {":woman_getting_haircut_tone3:"}, - "\U0001f487\U0001f3fd\u200d\u2642\ufe0f": {":man_getting_haircut_tone3:"}, - "\U0001f487\U0001f3fe": {":person_getting_haircut_tone4:"}, - "\U0001f487\U0001f3fe\u200d\u2640\ufe0f": {":woman_getting_haircut_tone4:"}, - "\U0001f487\U0001f3fe\u200d\u2642\ufe0f": {":man_getting_haircut_tone4:"}, - "\U0001f487\U0001f3ff": {":person_getting_haircut_tone5:"}, - "\U0001f487\U0001f3ff\u200d\u2640\ufe0f": {":woman_getting_haircut_tone5:"}, - "\U0001f487\U0001f3ff\u200d\u2642\ufe0f": {":man_getting_haircut_tone5:"}, - "\U0001f487\u200d\u2640\ufe0f": {":haircut:", ":haircut_woman:", ":woman-getting-haircut:", ":woman_getting_haircut:"}, - "\U0001f487\u200d\u2642\ufe0f": {":haircut_man:", ":man-getting-haircut:", ":man_getting_haircut:"}, - "\U0001f488": {":barber:", ":barber_pole:"}, - "\U0001f489": {":syringe:"}, - "\U0001f48a": {":pill:"}, - "\U0001f48b": {":kiss:", ":kiss_mark:"}, - "\U0001f48c": {":love_letter:"}, - "\U0001f48d": {":ring:"}, - "\U0001f48e": {":gem:", ":gem_stone:"}, - "\U0001f490": {":bouquet:"}, - "\U0001f492": {":wedding:"}, - "\U0001f493": {":heartbeat:", ":beating_heart:"}, - "\U0001f494": {":broken_heart:"}, - "\U0001f495": {":two_hearts:"}, - "\U0001f496": {":sparkling_heart:"}, - "\U0001f497": {":heartpulse:", ":growing_heart:"}, - "\U0001f498": {":cupid:", ":heart_with_arrow:"}, - "\U0001f499": {":blue_heart:"}, - "\U0001f49a": {":green_heart:"}, - "\U0001f49b": {":yellow_heart:"}, - "\U0001f49c": {":purple_heart:"}, - "\U0001f49d": {":gift_heart:", ":heart_with_ribbon:"}, - "\U0001f49e": {":revolving_hearts:"}, - "\U0001f49f": {":heart_decoration:"}, - "\U0001f4a0": {":diamond_with_a_dot:", ":diamond_shape_with_a_dot_inside:"}, - "\U0001f4a1": {":bulb:", ":light_bulb:"}, - "\U0001f4a2": {":anger:", ":anger_symbol:"}, - "\U0001f4a3": {":bomb:"}, - "\U0001f4a4": {":zzz:"}, - "\U0001f4a5": {":boom:", ":collision:"}, - "\U0001f4a6": {":sweat_drops:", ":sweat_droplets:"}, - "\U0001f4a7": {":droplet:"}, - "\U0001f4a8": {":dash:", ":dashing_away:"}, - "\U0001f4a9": {":poop:", ":shit:", ":hankey:", ":pile_of_poo:"}, - "\U0001f4aa": {":muscle:", ":flexed_biceps:"}, - "\U0001f4aa\U0001f3fb": {":muscle_tone1:"}, - "\U0001f4aa\U0001f3fc": {":muscle_tone2:"}, - "\U0001f4aa\U0001f3fd": {":muscle_tone3:"}, - "\U0001f4aa\U0001f3fe": {":muscle_tone4:"}, - "\U0001f4aa\U0001f3ff": {":muscle_tone5:"}, - "\U0001f4ab": {":dizzy:"}, - "\U0001f4ac": {":speech_balloon:"}, - "\U0001f4ad": {":thought_balloon:"}, - "\U0001f4ae": {":white_flower:"}, - "\U0001f4af": {":100:", ":hundred_points:"}, - "\U0001f4b0": {":moneybag:", ":money_bag:"}, - "\U0001f4b1": {":currency_exchange:"}, - "\U0001f4b2": {":heavy_dollar_sign:"}, - "\U0001f4b3": {":credit_card:"}, - "\U0001f4b4": {":yen:", ":yen_banknote:"}, - "\U0001f4b5": {":dollar:", ":dollar_banknote:"}, - "\U0001f4b6": {":euro:", ":euro_banknote:"}, - "\U0001f4b7": {":pound:", ":pound_banknote:"}, - "\U0001f4b8": {":money_with_wings:"}, - "\U0001f4b9": {":chart:", ":chart_increasing_with_yen:"}, - "\U0001f4ba": {":seat:"}, - "\U0001f4bb": {":laptop:", ":computer:"}, - "\U0001f4bc": {":briefcase:"}, - "\U0001f4bd": {":minidisc:", ":computer_disk:"}, - "\U0001f4be": {":floppy_disk:"}, - "\U0001f4bf": {":cd:", ":optical_disk:"}, - "\U0001f4c0": {":dvd:"}, - "\U0001f4c1": {":file_folder:"}, - "\U0001f4c2": {":open_file_folder:"}, - "\U0001f4c3": {":page_with_curl:"}, - "\U0001f4c4": {":page_facing_up:"}, - "\U0001f4c5": {":date:"}, - "\U0001f4c6": {":calendar:", ":tear-off_calendar:"}, - "\U0001f4c7": {":card_index:"}, - "\U0001f4c8": {":chart_increasing:", ":chart_with_upwards_trend:"}, - "\U0001f4c9": {":chart_decreasing:", ":chart_with_downwards_trend:"}, - "\U0001f4ca": {":bar_chart:"}, - "\U0001f4cb": {":clipboard:"}, - "\U0001f4cc": {":pushpin:"}, - "\U0001f4cd": {":round_pushpin:"}, - "\U0001f4ce": {":paperclip:"}, - "\U0001f4cf": {":straight_ruler:"}, - "\U0001f4d0": {":triangular_ruler:"}, - "\U0001f4d1": {":bookmark_tabs:"}, - "\U0001f4d2": {":ledger:"}, - "\U0001f4d3": {":notebook:"}, - "\U0001f4d4": {":notebook_with_decorative_cover:"}, - "\U0001f4d5": {":closed_book:"}, - "\U0001f4d6": {":book:", ":open_book:"}, - "\U0001f4d7": {":green_book:"}, - "\U0001f4d8": {":blue_book:"}, - "\U0001f4d9": {":orange_book:"}, - "\U0001f4da": {":books:"}, - "\U0001f4db": {":name_badge:"}, - "\U0001f4dc": {":scroll:"}, - "\U0001f4dd": {":memo:"}, - "\U0001f4de": {":telephone_receiver:"}, - "\U0001f4df": {":pager:"}, - "\U0001f4e0": {":fax:", ":fax_machine:"}, - "\U0001f4e1": {":satellite_antenna:"}, - "\U0001f4e2": {":loudspeaker:"}, - "\U0001f4e3": {":mega:", ":megaphone:"}, - "\U0001f4e4": {":outbox_tray:"}, - "\U0001f4e5": {":inbox_tray:"}, - "\U0001f4e6": {":package:"}, - "\U0001f4e7": {":e-mail:"}, - "\U0001f4e8": {":incoming_envelope:"}, - "\U0001f4e9": {":envelope_with_arrow:"}, - "\U0001f4ea": {":mailbox_closed:", ":closed_mailbox_with_lowered_flag:"}, - "\U0001f4eb": {":mailbox:", ":closed_mailbox_with_raised_flag:"}, - "\U0001f4ec": {":mailbox_with_mail:", ":open_mailbox_with_raised_flag:"}, - "\U0001f4ed": {":mailbox_with_no_mail:", ":open_mailbox_with_lowered_flag:"}, - "\U0001f4ee": {":postbox:"}, - "\U0001f4ef": {":postal_horn:"}, - "\U0001f4f0": {":newspaper:"}, - "\U0001f4f1": {":iphone:", ":mobile_phone:"}, - "\U0001f4f2": {":calling:", ":mobile_phone_with_arrow:"}, - "\U0001f4f3": {":vibration_mode:"}, - "\U0001f4f4": {":mobile_phone_off:"}, - "\U0001f4f5": {":no_mobile_phones:"}, - "\U0001f4f6": {":antenna_bars:", ":signal_strength:"}, - "\U0001f4f7": {":camera:"}, - "\U0001f4f8": {":camera_flash:", ":camera_with_flash:"}, - "\U0001f4f9": {":video_camera:"}, - "\U0001f4fa": {":tv:", ":television:"}, - "\U0001f4fb": {":radio:"}, - "\U0001f4fc": {":vhs:", ":videocassette:"}, - "\U0001f4fd": {":projector:"}, - "\U0001f4fd\ufe0f": {":film_projector:"}, - "\U0001f4ff": {":prayer_beads:"}, - "\U0001f500": {":shuffle_tracks_button:", ":twisted_rightwards_arrows:"}, - "\U0001f501": {":repeat:", ":repeat_button:"}, - "\U0001f502": {":repeat_one:", ":repeat_single_button:"}, - "\U0001f503": {":arrows_clockwise:", ":clockwise_vertical_arrows:"}, - "\U0001f504": {":arrows_counterclockwise:", ":counterclockwise_arrows_button:"}, - "\U0001f505": {":dim_button:", ":low_brightness:"}, - "\U0001f506": {":bright_button:", ":high_brightness:"}, - "\U0001f507": {":mute:", ":muted_speaker:"}, - "\U0001f508": {":speaker:", ":speaker_low_volume:"}, - "\U0001f509": {":sound:", ":speaker_medium_volume:"}, - "\U0001f50a": {":loud_sound:", ":speaker_high_volume:"}, - "\U0001f50b": {":battery:"}, - "\U0001f50c": {":electric_plug:"}, - "\U0001f50d": {":mag:", ":magnifying_glass_tilted_left:"}, - "\U0001f50e": {":mag_right:", ":magnifying_glass_tilted_right:"}, - "\U0001f50f": {":locked_with_pen:", ":lock_with_ink_pen:"}, - "\U0001f510": {":locked_with_key:", ":closed_lock_with_key:"}, - "\U0001f511": {":key:"}, - "\U0001f512": {":lock:", ":locked:"}, - "\U0001f513": {":unlock:", ":unlocked:"}, - "\U0001f514": {":bell:"}, - "\U0001f515": {":no_bell:", ":bell_with_slash:"}, - "\U0001f516": {":bookmark:"}, - "\U0001f517": {":link:"}, - "\U0001f518": {":radio_button:"}, - "\U0001f519": {":back:", ":BACK_arrow:"}, - "\U0001f51a": {":end:", ":END_arrow:"}, - "\U0001f51b": {":on:", ":ON!_arrow:"}, - "\U0001f51c": {":soon:", ":SOON_arrow:"}, - "\U0001f51d": {":top:", ":TOP_arrow:"}, - "\U0001f51e": {":underage:", ":no_one_under_eighteen:"}, - "\U0001f51f": {":keycap_10:", ":keycap_ten:"}, - "\U0001f520": {":capital_abcd:", ":input_latin_uppercase:"}, - "\U0001f521": {":abcd:", ":input_latin_lowercase:"}, - "\U0001f522": {":1234:", ":input_numbers:"}, - "\U0001f523": {":symbols:", ":input_symbols:"}, - "\U0001f524": {":abc:", ":input_latin_letters:"}, - "\U0001f525": {":fire:"}, - "\U0001f526": {":flashlight:"}, - "\U0001f527": {":wrench:"}, - "\U0001f528": {":hammer:"}, - "\U0001f529": {":nut_and_bolt:"}, - "\U0001f52a": {":hocho:", ":knife:", ":kitchen_knife:"}, - "\U0001f52b": {":gun:", ":water_pistol:"}, - "\U0001f52c": {":microscope:"}, - "\U0001f52d": {":telescope:"}, - "\U0001f52e": {":crystal_ball:"}, - "\U0001f52f": {":six_pointed_star:", ":dotted_six-pointed_star:"}, - "\U0001f530": {":beginner:", ":Japanese_symbol_for_beginner:"}, - "\U0001f531": {":trident:", ":trident_emblem:"}, - "\U0001f532": {":black_square_button:"}, - "\U0001f533": {":white_square_button:"}, - "\U0001f534": {":red_circle:"}, - "\U0001f535": {":blue_circle:", ":large_blue_circle:"}, - "\U0001f536": {":large_orange_diamond:"}, - "\U0001f537": {":large_blue_diamond:"}, - "\U0001f538": {":small_orange_diamond:"}, - "\U0001f539": {":small_blue_diamond:"}, - "\U0001f53a": {":small_red_triangle:", ":red_triangle_pointed_up:"}, - "\U0001f53b": {":small_red_triangle_down:", ":red_triangle_pointed_down:"}, - "\U0001f53c": {":arrow_up_small:", ":upwards_button:"}, - "\U0001f53d": {":arrow_down_small:", ":downwards_button:"}, - "\U0001f549": {":om:"}, - "\U0001f549\ufe0f": {":om_symbol:"}, - "\U0001f54a": {":dove:"}, - "\U0001f54a\ufe0f": {":dove_of_peace:"}, - "\U0001f54b": {":kaaba:"}, - "\U0001f54c": {":mosque:"}, - "\U0001f54d": {":synagogue:"}, - "\U0001f54e": {":menorah:", ":menorah_with_nine_branches:"}, - "\U0001f550": {":clock1:", ":one_o’clock:"}, - "\U0001f551": {":clock2:", ":two_o’clock:"}, - "\U0001f552": {":clock3:", ":three_o’clock:"}, - "\U0001f553": {":clock4:", ":four_o’clock:"}, - "\U0001f554": {":clock5:", ":five_o’clock:"}, - "\U0001f555": {":clock6:", ":six_o’clock:"}, - "\U0001f556": {":clock7:", ":seven_o’clock:"}, - "\U0001f557": {":clock8:", ":eight_o’clock:"}, - "\U0001f558": {":clock9:", ":nine_o’clock:"}, - "\U0001f559": {":clock10:", ":ten_o’clock:"}, - "\U0001f55a": {":clock11:", ":eleven_o’clock:"}, - "\U0001f55b": {":clock12:", ":twelve_o’clock:"}, - "\U0001f55c": {":clock130:", ":one-thirty:"}, - "\U0001f55d": {":clock230:", ":two-thirty:"}, - "\U0001f55e": {":clock330:", ":three-thirty:"}, - "\U0001f55f": {":clock430:", ":four-thirty:"}, - "\U0001f560": {":clock530:", ":five-thirty:"}, - "\U0001f561": {":clock630:", ":six-thirty:"}, - "\U0001f562": {":clock730:", ":seven-thirty:"}, - "\U0001f563": {":clock830:", ":eight-thirty:"}, - "\U0001f564": {":clock930:", ":nine-thirty:"}, - "\U0001f565": {":clock1030:", ":ten-thirty:"}, - "\U0001f566": {":clock1130:", ":eleven-thirty:"}, - "\U0001f567": {":clock1230:", ":twelve-thirty:"}, - "\U0001f56f\ufe0f": {":candle:"}, - "\U0001f570": {":clock:"}, - "\U0001f570\ufe0f": {":mantelpiece_clock:"}, - "\U0001f573\ufe0f": {":hole:"}, - "\U0001f574": {":person_in_suit_levitating:"}, - "\U0001f574\U0001f3fb": {":man_in_business_suit_levitating_tone1:"}, - "\U0001f574\U0001f3fc": {":man_in_business_suit_levitating_tone2:"}, - "\U0001f574\U0001f3fd": {":man_in_business_suit_levitating_tone3:"}, - "\U0001f574\U0001f3fe": {":man_in_business_suit_levitating_tone4:"}, - "\U0001f574\U0001f3ff": {":man_in_business_suit_levitating_tone5:"}, - "\U0001f574\ufe0f": {":business_suit_levitating:", ":man_in_business_suit_levitating:"}, - "\U0001f575": {":detective:"}, - "\U0001f575\U0001f3fb": {":detective_tone1:"}, - "\U0001f575\U0001f3fb\u200d\u2640\ufe0f": {":woman_detective_tone1:"}, - "\U0001f575\U0001f3fb\u200d\u2642\ufe0f": {":man_detective_tone1:"}, - "\U0001f575\U0001f3fc": {":detective_tone2:"}, - "\U0001f575\U0001f3fc\u200d\u2640\ufe0f": {":woman_detective_tone2:"}, - "\U0001f575\U0001f3fc\u200d\u2642\ufe0f": {":man_detective_tone2:"}, - "\U0001f575\U0001f3fd": {":detective_tone3:"}, - "\U0001f575\U0001f3fd\u200d\u2640\ufe0f": {":woman_detective_tone3:"}, - "\U0001f575\U0001f3fd\u200d\u2642\ufe0f": {":man_detective_tone3:"}, - "\U0001f575\U0001f3fe": {":detective_tone4:"}, - "\U0001f575\U0001f3fe\u200d\u2640\ufe0f": {":woman_detective_tone4:"}, - "\U0001f575\U0001f3fe\u200d\u2642\ufe0f": {":man_detective_tone4:"}, - "\U0001f575\U0001f3ff": {":detective_tone5:"}, - "\U0001f575\U0001f3ff\u200d\u2640\ufe0f": {":woman_detective_tone5:"}, - "\U0001f575\U0001f3ff\u200d\u2642\ufe0f": {":man_detective_tone5:"}, - "\U0001f575\ufe0f\u200d\u2640\ufe0f": {":woman_detective:", ":female-detective:", ":female_detective:"}, - "\U0001f575\ufe0f\u200d\u2642\ufe0f": {":man_detective:", ":sleuth_or_spy:", ":male-detective:", ":male_detective:"}, - "\U0001f576\ufe0f": {":dark_sunglasses:"}, - "\U0001f577\ufe0f": {":spider:"}, - "\U0001f578\ufe0f": {":spider_web:"}, - "\U0001f579\ufe0f": {":joystick:"}, - "\U0001f57a": {":man_dancing:"}, - "\U0001f57a\U0001f3fb": {":man_dancing_tone1:"}, - "\U0001f57a\U0001f3fc": {":man_dancing_tone2:"}, - "\U0001f57a\U0001f3fd": {":man_dancing_tone3:"}, - "\U0001f57a\U0001f3fe": {":man_dancing_tone4:"}, - "\U0001f57a\U0001f3ff": {":man_dancing_tone5:"}, - "\U0001f587": {":paperclips:"}, - "\U0001f587\ufe0f": {":linked_paperclips:"}, - "\U0001f58a": {":pen:", ":pen_ballpoint:"}, - "\U0001f58a\ufe0f": {":lower_left_ballpoint_pen:"}, - "\U0001f58b": {":fountain_pen:", ":pen_fountain:"}, - "\U0001f58b\ufe0f": {":lower_left_fountain_pen:"}, - "\U0001f58c": {":paintbrush:"}, - "\U0001f58c\ufe0f": {":lower_left_paintbrush:"}, - "\U0001f58d": {":crayon:"}, - "\U0001f58d\ufe0f": {":lower_left_crayon:"}, - "\U0001f590": {":hand_with_fingers_splayed:"}, - "\U0001f590\U0001f3fb": {":hand_splayed_tone1:"}, - "\U0001f590\U0001f3fc": {":hand_splayed_tone2:"}, - "\U0001f590\U0001f3fd": {":hand_splayed_tone3:"}, - "\U0001f590\U0001f3fe": {":hand_splayed_tone4:"}, - "\U0001f590\U0001f3ff": {":hand_splayed_tone5:"}, - "\U0001f590\ufe0f": {":raised_hand_with_fingers_splayed:"}, - "\U0001f595": {":fu:", ":middle_finger:"}, - "\U0001f595\U0001f3fb": {":middle_finger_tone1:"}, - "\U0001f595\U0001f3fc": {":middle_finger_tone2:"}, - "\U0001f595\U0001f3fd": {":middle_finger_tone3:"}, - "\U0001f595\U0001f3fe": {":middle_finger_tone4:"}, - "\U0001f595\U0001f3ff": {":middle_finger_tone5:"}, - "\U0001f596": {":vulcan:", ":spock-hand:", ":vulcan_salute:"}, - "\U0001f596\U0001f3fb": {":vulcan_tone1:"}, - "\U0001f596\U0001f3fc": {":vulcan_tone2:"}, - "\U0001f596\U0001f3fd": {":vulcan_tone3:"}, - "\U0001f596\U0001f3fe": {":vulcan_tone4:"}, - "\U0001f596\U0001f3ff": {":vulcan_tone5:"}, - "\U0001f5a4": {":black_heart:"}, - "\U0001f5a5": {":desktop:"}, - "\U0001f5a5\ufe0f": {":desktop_computer:"}, - "\U0001f5a8\ufe0f": {":printer:"}, - "\U0001f5b1": {":computer_mouse:", ":mouse_three_button:"}, - "\U0001f5b1\ufe0f": {":three_button_mouse:"}, - "\U0001f5b2\ufe0f": {":trackball:"}, - "\U0001f5bc": {":frame_photo:", ":framed_picture:"}, - "\U0001f5bc\ufe0f": {":frame_with_picture:"}, - "\U0001f5c2": {":dividers:"}, - "\U0001f5c2\ufe0f": {":card_index_dividers:"}, - "\U0001f5c3": {":card_box:"}, - "\U0001f5c3\ufe0f": {":card_file_box:"}, - "\U0001f5c4\ufe0f": {":file_cabinet:"}, - "\U0001f5d1\ufe0f": {":wastebasket:"}, - "\U0001f5d2": {":notepad_spiral:", ":spiral_notepad:"}, - "\U0001f5d2\ufe0f": {":spiral_note_pad:"}, - "\U0001f5d3": {":calendar_spiral:", ":spiral_calendar:"}, - "\U0001f5d3\ufe0f": {":spiral_calendar_pad:"}, - "\U0001f5dc": {":clamp:"}, - "\U0001f5dc\ufe0f": {":compression:"}, - "\U0001f5dd": {":key2:"}, - "\U0001f5dd\ufe0f": {":old_key:"}, - "\U0001f5de": {":newspaper2:", ":rolled-up_newspaper:"}, - "\U0001f5de\ufe0f": {":newspaper_roll:", ":rolled_up_newspaper:"}, - "\U0001f5e1": {":dagger:"}, - "\U0001f5e1\ufe0f": {":dagger_knife:"}, - "\U0001f5e3": {":speaking_head:"}, - "\U0001f5e3\ufe0f": {":speaking_head_in_silhouette:"}, - "\U0001f5e8": {":speech_left:"}, - "\U0001f5e8\ufe0f": {":left_speech_bubble:"}, - "\U0001f5ef": {":anger_right:"}, - "\U0001f5ef\ufe0f": {":right_anger_bubble:"}, - "\U0001f5f3": {":ballot_box:"}, - "\U0001f5f3\ufe0f": {":ballot_box_with_ballot:"}, - "\U0001f5fa": {":map:"}, - "\U0001f5fa\ufe0f": {":world_map:"}, - "\U0001f5fb": {":mount_fuji:"}, - "\U0001f5fc": {":Tokyo_tower:", ":tokyo_tower:"}, - "\U0001f5fd": {":Statue_of_Liberty:", ":statue_of_liberty:"}, - "\U0001f5fe": {":japan:", ":map_of_Japan:"}, - "\U0001f5ff": {":moai:", ":moyai:"}, - "\U0001f600": {":grinning:", ":grinning_face:"}, - "\U0001f601": {":grin:", ":beaming_face_with_smiling_eyes:"}, - "\U0001f602": {":joy:", ":face_with_tears_of_joy:"}, - "\U0001f603": {":smiley:", ":grinning_face_with_big_eyes:"}, - "\U0001f604": {":smile:", ":grinning_face_with_smiling_eyes:"}, - "\U0001f605": {":sweat_smile:", ":grinning_face_with_sweat:"}, - "\U0001f606": {":laughing:", ":satisfied:", ":grinning_squinting_face:"}, - "\U0001f607": {":innocent:", ":smiling_face_with_halo:"}, - "\U0001f608": {":smiling_imp:", ":smiling_face_with_horns:"}, - "\U0001f609": {":wink:", ":winking_face:"}, - "\U0001f60a": {":blush:", ":smiling_face_with_smiling_eyes:"}, - "\U0001f60b": {":yum:", ":face_savoring_food:"}, - "\U0001f60c": {":relieved:", ":relieved_face:"}, - "\U0001f60d": {":heart_eyes:", ":smiling_face_with_heart-eyes:"}, - "\U0001f60e": {":sunglasses:", ":smiling_face_with_sunglasses:"}, - "\U0001f60f": {":smirk:", ":smirking_face:"}, - "\U0001f610": {":neutral_face:"}, - "\U0001f611": {":expressionless:", ":expressionless_face:"}, - "\U0001f612": {":unamused:", ":unamused_face:"}, - "\U0001f613": {":sweat:", ":downcast_face_with_sweat:"}, - "\U0001f614": {":pensive:", ":pensive_face:"}, - "\U0001f615": {":confused:", ":confused_face:"}, - "\U0001f616": {":confounded:", ":confounded_face:"}, - "\U0001f617": {":kissing:", ":kissing_face:"}, - "\U0001f618": {":kissing_heart:", ":face_blowing_a_kiss:"}, - "\U0001f619": {":kissing_smiling_eyes:", ":kissing_face_with_smiling_eyes:"}, - "\U0001f61a": {":kissing_closed_eyes:", ":kissing_face_with_closed_eyes:"}, - "\U0001f61b": {":face_with_tongue:", ":stuck_out_tongue:"}, - "\U0001f61c": {":winking_face_with_tongue:", ":stuck_out_tongue_winking_eye:"}, - "\U0001f61d": {":squinting_face_with_tongue:", ":stuck_out_tongue_closed_eyes:"}, - "\U0001f61e": {":disappointed:", ":disappointed_face:"}, - "\U0001f61f": {":worried:", ":worried_face:"}, - "\U0001f620": {":angry:", ":angry_face:"}, - "\U0001f621": {":pout:", ":rage:", ":pouting_face:"}, - "\U0001f622": {":cry:", ":crying_face:"}, - "\U0001f623": {":persevere:", ":persevering_face:"}, - "\U0001f624": {":triumph:", ":face_with_steam_from_nose:"}, - "\U0001f625": {":disappointed_relieved:", ":sad_but_relieved_face:"}, - "\U0001f626": {":frowning:", ":frowning_face_with_open_mouth:"}, - "\U0001f627": {":anguished:", ":anguished_face:"}, - "\U0001f628": {":fearful:", ":fearful_face:"}, - "\U0001f629": {":weary:", ":weary_face:"}, - "\U0001f62a": {":sleepy:", ":sleepy_face:"}, - "\U0001f62b": {":tired_face:"}, - "\U0001f62c": {":grimacing:", ":grimacing_face:"}, - "\U0001f62d": {":sob:", ":loudly_crying_face:"}, - "\U0001f62e": {":open_mouth:", ":face_with_open_mouth:"}, - "\U0001f62e\u200d\U0001f4a8": {":face_exhaling:"}, - "\U0001f62f": {":hushed:", ":hushed_face:"}, - "\U0001f630": {":cold_sweat:", ":anxious_face_with_sweat:"}, - "\U0001f631": {":scream:", ":face_screaming_in_fear:"}, - "\U0001f632": {":astonished:", ":astonished_face:"}, - "\U0001f633": {":flushed:", ":flushed_face:"}, - "\U0001f634": {":sleeping:", ":sleeping_face:"}, - "\U0001f635": {":dizzy_face:", ":knocked-out_face:"}, - "\U0001f635\u200d\U0001f4ab": {":face_with_spiral_eyes:"}, - "\U0001f636": {":no_mouth:", ":face_without_mouth:"}, - "\U0001f636\u200d\U0001f32b\ufe0f": {":face_in_clouds:"}, - "\U0001f637": {":mask:", ":face_with_medical_mask:"}, - "\U0001f638": {":smile_cat:", ":grinning_cat_with_smiling_eyes:"}, - "\U0001f639": {":joy_cat:", ":cat_with_tears_of_joy:"}, - "\U0001f63a": {":smiley_cat:", ":grinning_cat:"}, - "\U0001f63b": {":heart_eyes_cat:", ":smiling_cat_with_heart-eyes:"}, - "\U0001f63c": {":smirk_cat:", ":cat_with_wry_smile:"}, - "\U0001f63d": {":kissing_cat:"}, - "\U0001f63e": {":pouting_cat:"}, - "\U0001f63f": {":crying_cat:", ":crying_cat_face:"}, - "\U0001f640": {":weary_cat:", ":scream_cat:"}, - "\U0001f641": {":slight_frown:", ":slightly_frowning_face:"}, - "\U0001f642": {":slight_smile:", ":slightly_smiling_face:"}, - "\U0001f643": {":upside_down:", ":upside-down_face:", ":upside_down_face:"}, - "\U0001f644": {":roll_eyes:", ":rolling_eyes:", ":face_with_rolling_eyes:"}, - "\U0001f645": {":person_gesturing_NO:", ":person_gesturing_no:"}, - "\U0001f645\U0001f3fb": {":person_gesturing_no_tone1:"}, - "\U0001f645\U0001f3fb\u200d\u2640\ufe0f": {":woman_gesturing_no_tone1:"}, - "\U0001f645\U0001f3fb\u200d\u2642\ufe0f": {":man_gesturing_no_tone1:"}, - "\U0001f645\U0001f3fc": {":person_gesturing_no_tone2:"}, - "\U0001f645\U0001f3fc\u200d\u2640\ufe0f": {":woman_gesturing_no_tone2:"}, - "\U0001f645\U0001f3fc\u200d\u2642\ufe0f": {":man_gesturing_no_tone2:"}, - "\U0001f645\U0001f3fd": {":person_gesturing_no_tone3:"}, - "\U0001f645\U0001f3fd\u200d\u2640\ufe0f": {":woman_gesturing_no_tone3:"}, - "\U0001f645\U0001f3fd\u200d\u2642\ufe0f": {":man_gesturing_no_tone3:"}, - "\U0001f645\U0001f3fe": {":person_gesturing_no_tone4:"}, - "\U0001f645\U0001f3fe\u200d\u2640\ufe0f": {":woman_gesturing_no_tone4:"}, - "\U0001f645\U0001f3fe\u200d\u2642\ufe0f": {":man_gesturing_no_tone4:"}, - "\U0001f645\U0001f3ff": {":person_gesturing_no_tone5:"}, - "\U0001f645\U0001f3ff\u200d\u2640\ufe0f": {":woman_gesturing_no_tone5:"}, - "\U0001f645\U0001f3ff\u200d\u2642\ufe0f": {":man_gesturing_no_tone5:"}, - "\U0001f645\u200d\u2640\ufe0f": {":no_good:", ":ng_woman:", ":no_good_woman:", ":woman-gesturing-no:", ":woman_gesturing_NO:", ":woman_gesturing_no:"}, - "\U0001f645\u200d\u2642\ufe0f": {":ng_man:", ":no_good_man:", ":man-gesturing-no:", ":man_gesturing_NO:", ":man_gesturing_no:"}, - "\U0001f646": {":ok_person:", ":person_gesturing_OK:", ":person_gesturing_ok:"}, - "\U0001f646\U0001f3fb": {":person_gesturing_ok_tone1:"}, - "\U0001f646\U0001f3fb\u200d\u2640\ufe0f": {":woman_gesturing_ok_tone1:"}, - "\U0001f646\U0001f3fb\u200d\u2642\ufe0f": {":man_gesturing_ok_tone1:"}, - "\U0001f646\U0001f3fc": {":person_gesturing_ok_tone2:"}, - "\U0001f646\U0001f3fc\u200d\u2640\ufe0f": {":woman_gesturing_ok_tone2:"}, - "\U0001f646\U0001f3fc\u200d\u2642\ufe0f": {":man_gesturing_ok_tone2:"}, - "\U0001f646\U0001f3fd": {":person_gesturing_ok_tone3:"}, - "\U0001f646\U0001f3fd\u200d\u2640\ufe0f": {":woman_gesturing_ok_tone3:"}, - "\U0001f646\U0001f3fd\u200d\u2642\ufe0f": {":man_gesturing_ok_tone3:"}, - "\U0001f646\U0001f3fe": {":person_gesturing_ok_tone4:"}, - "\U0001f646\U0001f3fe\u200d\u2640\ufe0f": {":woman_gesturing_ok_tone4:"}, - "\U0001f646\U0001f3fe\u200d\u2642\ufe0f": {":man_gesturing_ok_tone4:"}, - "\U0001f646\U0001f3ff": {":person_gesturing_ok_tone5:"}, - "\U0001f646\U0001f3ff\u200d\u2640\ufe0f": {":woman_gesturing_ok_tone5:"}, - "\U0001f646\U0001f3ff\u200d\u2642\ufe0f": {":man_gesturing_ok_tone5:"}, - "\U0001f646\u200d\u2640\ufe0f": {":ok_woman:", ":woman-gesturing-ok:", ":woman_gesturing_OK:", ":woman_gesturing_ok:"}, - "\U0001f646\u200d\u2642\ufe0f": {":ok_man:", ":man-gesturing-ok:", ":man_gesturing_OK:", ":man_gesturing_ok:"}, - "\U0001f647": {":person_bowing:"}, - "\U0001f647\U0001f3fb": {":person_bowing_tone1:"}, - "\U0001f647\U0001f3fb\u200d\u2640\ufe0f": {":woman_bowing_tone1:"}, - "\U0001f647\U0001f3fb\u200d\u2642\ufe0f": {":man_bowing_tone1:"}, - "\U0001f647\U0001f3fc": {":person_bowing_tone2:"}, - "\U0001f647\U0001f3fc\u200d\u2640\ufe0f": {":woman_bowing_tone2:"}, - "\U0001f647\U0001f3fc\u200d\u2642\ufe0f": {":man_bowing_tone2:"}, - "\U0001f647\U0001f3fd": {":person_bowing_tone3:"}, - "\U0001f647\U0001f3fd\u200d\u2640\ufe0f": {":woman_bowing_tone3:"}, - "\U0001f647\U0001f3fd\u200d\u2642\ufe0f": {":man_bowing_tone3:"}, - "\U0001f647\U0001f3fe": {":person_bowing_tone4:"}, - "\U0001f647\U0001f3fe\u200d\u2640\ufe0f": {":woman_bowing_tone4:"}, - "\U0001f647\U0001f3fe\u200d\u2642\ufe0f": {":man_bowing_tone4:"}, - "\U0001f647\U0001f3ff": {":person_bowing_tone5:"}, - "\U0001f647\U0001f3ff\u200d\u2640\ufe0f": {":woman_bowing_tone5:"}, - "\U0001f647\U0001f3ff\u200d\u2642\ufe0f": {":man_bowing_tone5:"}, - "\U0001f647\u200d\u2640\ufe0f": {":bowing_woman:", ":woman-bowing:", ":woman_bowing:"}, - "\U0001f647\u200d\u2642\ufe0f": {":bow:", ":bowing_man:", ":man-bowing:", ":man_bowing:"}, - "\U0001f648": {":see_no_evil:", ":see-no-evil_monkey:"}, - "\U0001f649": {":hear_no_evil:", ":hear-no-evil_monkey:"}, - "\U0001f64a": {":speak_no_evil:", ":speak-no-evil_monkey:"}, - "\U0001f64b": {":person_raising_hand:"}, - "\U0001f64b\U0001f3fb": {":person_raising_hand_tone1:"}, - "\U0001f64b\U0001f3fb\u200d\u2640\ufe0f": {":woman_raising_hand_tone1:"}, - "\U0001f64b\U0001f3fb\u200d\u2642\ufe0f": {":man_raising_hand_tone1:"}, - "\U0001f64b\U0001f3fc": {":person_raising_hand_tone2:"}, - "\U0001f64b\U0001f3fc\u200d\u2640\ufe0f": {":woman_raising_hand_tone2:"}, - "\U0001f64b\U0001f3fc\u200d\u2642\ufe0f": {":man_raising_hand_tone2:"}, - "\U0001f64b\U0001f3fd": {":person_raising_hand_tone3:"}, - "\U0001f64b\U0001f3fd\u200d\u2640\ufe0f": {":woman_raising_hand_tone3:"}, - "\U0001f64b\U0001f3fd\u200d\u2642\ufe0f": {":man_raising_hand_tone3:"}, - "\U0001f64b\U0001f3fe": {":person_raising_hand_tone4:"}, - "\U0001f64b\U0001f3fe\u200d\u2640\ufe0f": {":woman_raising_hand_tone4:"}, - "\U0001f64b\U0001f3fe\u200d\u2642\ufe0f": {":man_raising_hand_tone4:"}, - "\U0001f64b\U0001f3ff": {":person_raising_hand_tone5:"}, - "\U0001f64b\U0001f3ff\u200d\u2640\ufe0f": {":woman_raising_hand_tone5:"}, - "\U0001f64b\U0001f3ff\u200d\u2642\ufe0f": {":man_raising_hand_tone5:"}, - "\U0001f64b\u200d\u2640\ufe0f": {":raising_hand:", ":raising_hand_woman:", ":woman-raising-hand:", ":woman_raising_hand:"}, - "\U0001f64b\u200d\u2642\ufe0f": {":man-raising-hand:", ":man_raising_hand:", ":raising_hand_man:"}, - "\U0001f64c": {":raised_hands:", ":raising_hands:"}, - "\U0001f64c\U0001f3fb": {":raised_hands_tone1:"}, - "\U0001f64c\U0001f3fc": {":raised_hands_tone2:"}, - "\U0001f64c\U0001f3fd": {":raised_hands_tone3:"}, - "\U0001f64c\U0001f3fe": {":raised_hands_tone4:"}, - "\U0001f64c\U0001f3ff": {":raised_hands_tone5:"}, - "\U0001f64d": {":frowning_person:"}, - "\U0001f64d\U0001f3fb": {":person_frowning_tone1:"}, - "\U0001f64d\U0001f3fb\u200d\u2640\ufe0f": {":woman_frowning_tone1:"}, - "\U0001f64d\U0001f3fb\u200d\u2642\ufe0f": {":man_frowning_tone1:"}, - "\U0001f64d\U0001f3fc": {":person_frowning_tone2:"}, - "\U0001f64d\U0001f3fc\u200d\u2640\ufe0f": {":woman_frowning_tone2:"}, - "\U0001f64d\U0001f3fc\u200d\u2642\ufe0f": {":man_frowning_tone2:"}, - "\U0001f64d\U0001f3fd": {":person_frowning_tone3:"}, - "\U0001f64d\U0001f3fd\u200d\u2640\ufe0f": {":woman_frowning_tone3:"}, - "\U0001f64d\U0001f3fd\u200d\u2642\ufe0f": {":man_frowning_tone3:"}, - "\U0001f64d\U0001f3fe": {":person_frowning_tone4:"}, - "\U0001f64d\U0001f3fe\u200d\u2640\ufe0f": {":woman_frowning_tone4:"}, - "\U0001f64d\U0001f3fe\u200d\u2642\ufe0f": {":man_frowning_tone4:"}, - "\U0001f64d\U0001f3ff": {":person_frowning_tone5:"}, - "\U0001f64d\U0001f3ff\u200d\u2640\ufe0f": {":woman_frowning_tone5:"}, - "\U0001f64d\U0001f3ff\u200d\u2642\ufe0f": {":man_frowning_tone5:"}, - "\U0001f64d\u200d\u2640\ufe0f": {":frowning_woman:", ":woman-frowning:", ":woman_frowning:", ":person_frowning:"}, - "\U0001f64d\u200d\u2642\ufe0f": {":frowning_man:", ":man-frowning:", ":man_frowning:"}, - "\U0001f64e": {":person_pouting:"}, - "\U0001f64e\U0001f3fb": {":person_pouting_tone1:"}, - "\U0001f64e\U0001f3fb\u200d\u2640\ufe0f": {":woman_pouting_tone1:"}, - "\U0001f64e\U0001f3fb\u200d\u2642\ufe0f": {":man_pouting_tone1:"}, - "\U0001f64e\U0001f3fc": {":person_pouting_tone2:"}, - "\U0001f64e\U0001f3fc\u200d\u2640\ufe0f": {":woman_pouting_tone2:"}, - "\U0001f64e\U0001f3fc\u200d\u2642\ufe0f": {":man_pouting_tone2:"}, - "\U0001f64e\U0001f3fd": {":person_pouting_tone3:"}, - "\U0001f64e\U0001f3fd\u200d\u2640\ufe0f": {":woman_pouting_tone3:"}, - "\U0001f64e\U0001f3fd\u200d\u2642\ufe0f": {":man_pouting_tone3:"}, - "\U0001f64e\U0001f3fe": {":person_pouting_tone4:"}, - "\U0001f64e\U0001f3fe\u200d\u2640\ufe0f": {":woman_pouting_tone4:"}, - "\U0001f64e\U0001f3fe\u200d\u2642\ufe0f": {":man_pouting_tone4:"}, - "\U0001f64e\U0001f3ff": {":person_pouting_tone5:"}, - "\U0001f64e\U0001f3ff\u200d\u2640\ufe0f": {":woman_pouting_tone5:"}, - "\U0001f64e\U0001f3ff\u200d\u2642\ufe0f": {":man_pouting_tone5:"}, - "\U0001f64e\u200d\u2640\ufe0f": {":pouting_woman:", ":woman-pouting:", ":woman_pouting:", ":person_with_pouting_face:"}, - "\U0001f64e\u200d\u2642\ufe0f": {":man-pouting:", ":man_pouting:", ":pouting_man:"}, - "\U0001f64f": {":pray:", ":folded_hands:"}, - "\U0001f64f\U0001f3fb": {":pray_tone1:"}, - "\U0001f64f\U0001f3fc": {":pray_tone2:"}, - "\U0001f64f\U0001f3fd": {":pray_tone3:"}, - "\U0001f64f\U0001f3fe": {":pray_tone4:"}, - "\U0001f64f\U0001f3ff": {":pray_tone5:"}, - "\U0001f680": {":rocket:"}, - "\U0001f681": {":helicopter:"}, - "\U0001f682": {":locomotive:", ":steam_locomotive:"}, - "\U0001f683": {":railway_car:"}, - "\U0001f684": {":bullettrain_side:", ":high-speed_train:"}, - "\U0001f685": {":bullet_train:", ":bullettrain_front:"}, - "\U0001f686": {":train2:"}, - "\U0001f687": {":metro:"}, - "\U0001f688": {":light_rail:"}, - "\U0001f689": {":station:"}, - "\U0001f68a": {":tram:"}, - "\U0001f68b": {":train:", ":tram_car:"}, - "\U0001f68c": {":bus:"}, - "\U0001f68d": {":oncoming_bus:"}, - "\U0001f68e": {":trolleybus:"}, - "\U0001f68f": {":busstop:", ":bus_stop:"}, - "\U0001f690": {":minibus:"}, - "\U0001f691": {":ambulance:"}, - "\U0001f692": {":fire_engine:"}, - "\U0001f693": {":police_car:"}, - "\U0001f694": {":oncoming_police_car:"}, - "\U0001f695": {":taxi:"}, - "\U0001f696": {":oncoming_taxi:"}, - "\U0001f697": {":car:", ":red_car:", ":automobile:"}, - "\U0001f698": {":oncoming_automobile:"}, - "\U0001f699": {":blue_car:", ":sport_utility_vehicle:"}, - "\U0001f69a": {":truck:", ":delivery_truck:"}, - "\U0001f69b": {":articulated_lorry:"}, - "\U0001f69c": {":tractor:"}, - "\U0001f69d": {":monorail:"}, - "\U0001f69e": {":mountain_railway:"}, - "\U0001f69f": {":suspension_railway:"}, - "\U0001f6a0": {":mountain_cableway:"}, - "\U0001f6a1": {":aerial_tramway:"}, - "\U0001f6a2": {":ship:"}, - "\U0001f6a3": {":person_rowing_boat:"}, - "\U0001f6a3\U0001f3fb": {":person_rowing_boat_tone1:"}, - "\U0001f6a3\U0001f3fb\u200d\u2640\ufe0f": {":woman_rowing_boat_tone1:"}, - "\U0001f6a3\U0001f3fb\u200d\u2642\ufe0f": {":man_rowing_boat_tone1:"}, - "\U0001f6a3\U0001f3fc": {":person_rowing_boat_tone2:"}, - "\U0001f6a3\U0001f3fc\u200d\u2640\ufe0f": {":woman_rowing_boat_tone2:"}, - "\U0001f6a3\U0001f3fc\u200d\u2642\ufe0f": {":man_rowing_boat_tone2:"}, - "\U0001f6a3\U0001f3fd": {":person_rowing_boat_tone3:"}, - "\U0001f6a3\U0001f3fd\u200d\u2640\ufe0f": {":woman_rowing_boat_tone3:"}, - "\U0001f6a3\U0001f3fd\u200d\u2642\ufe0f": {":man_rowing_boat_tone3:"}, - "\U0001f6a3\U0001f3fe": {":person_rowing_boat_tone4:"}, - "\U0001f6a3\U0001f3fe\u200d\u2640\ufe0f": {":woman_rowing_boat_tone4:"}, - "\U0001f6a3\U0001f3fe\u200d\u2642\ufe0f": {":man_rowing_boat_tone4:"}, - "\U0001f6a3\U0001f3ff": {":person_rowing_boat_tone5:"}, - "\U0001f6a3\U0001f3ff\u200d\u2640\ufe0f": {":woman_rowing_boat_tone5:"}, - "\U0001f6a3\U0001f3ff\u200d\u2642\ufe0f": {":man_rowing_boat_tone5:"}, - "\U0001f6a3\u200d\u2640\ufe0f": {":rowing_woman:", ":woman-rowing-boat:", ":woman_rowing_boat:"}, - "\U0001f6a3\u200d\u2642\ufe0f": {":rowboat:", ":rowing_man:", ":man-rowing-boat:", ":man_rowing_boat:"}, - "\U0001f6a4": {":speedboat:"}, - "\U0001f6a5": {":traffic_light:", ":horizontal_traffic_light:"}, - "\U0001f6a6": {":vertical_traffic_light:"}, - "\U0001f6a7": {":construction:"}, - "\U0001f6a8": {":rotating_light:", ":police_car_light:"}, - "\U0001f6a9": {":triangular_flag:", ":triangular_flag_on_post:"}, - "\U0001f6aa": {":door:"}, - "\U0001f6ab": {":prohibited:", ":no_entry_sign:"}, - "\U0001f6ac": {":smoking:", ":cigarette:"}, - "\U0001f6ad": {":no_smoking:"}, - "\U0001f6ae": {":litter_in_bin_sign:", ":put_litter_in_its_place:"}, - "\U0001f6af": {":no_littering:", ":do_not_litter:"}, - "\U0001f6b0": {":potable_water:"}, - "\U0001f6b1": {":non-potable_water:"}, - "\U0001f6b2": {":bike:", ":bicycle:"}, - "\U0001f6b3": {":no_bicycles:"}, - "\U0001f6b4": {":person_biking:"}, - "\U0001f6b4\U0001f3fb": {":person_biking_tone1:"}, - "\U0001f6b4\U0001f3fb\u200d\u2640\ufe0f": {":woman_biking_tone1:"}, - "\U0001f6b4\U0001f3fb\u200d\u2642\ufe0f": {":man_biking_tone1:"}, - "\U0001f6b4\U0001f3fc": {":person_biking_tone2:"}, - "\U0001f6b4\U0001f3fc\u200d\u2640\ufe0f": {":woman_biking_tone2:"}, - "\U0001f6b4\U0001f3fc\u200d\u2642\ufe0f": {":man_biking_tone2:"}, - "\U0001f6b4\U0001f3fd": {":person_biking_tone3:"}, - "\U0001f6b4\U0001f3fd\u200d\u2640\ufe0f": {":woman_biking_tone3:"}, - "\U0001f6b4\U0001f3fd\u200d\u2642\ufe0f": {":man_biking_tone3:"}, - "\U0001f6b4\U0001f3fe": {":person_biking_tone4:"}, - "\U0001f6b4\U0001f3fe\u200d\u2640\ufe0f": {":woman_biking_tone4:"}, - "\U0001f6b4\U0001f3fe\u200d\u2642\ufe0f": {":man_biking_tone4:"}, - "\U0001f6b4\U0001f3ff": {":person_biking_tone5:"}, - "\U0001f6b4\U0001f3ff\u200d\u2640\ufe0f": {":woman_biking_tone5:"}, - "\U0001f6b4\U0001f3ff\u200d\u2642\ufe0f": {":man_biking_tone5:"}, - "\U0001f6b4\u200d\u2640\ufe0f": {":biking_woman:", ":woman-biking:", ":woman_biking:"}, - "\U0001f6b4\u200d\u2642\ufe0f": {":bicyclist:", ":biking_man:", ":man-biking:", ":man_biking:"}, - "\U0001f6b5": {":person_mountain_biking:"}, - "\U0001f6b5\U0001f3fb": {":person_mountain_biking_tone1:"}, - "\U0001f6b5\U0001f3fb\u200d\u2640\ufe0f": {":woman_mountain_biking_tone1:"}, - "\U0001f6b5\U0001f3fb\u200d\u2642\ufe0f": {":man_mountain_biking_tone1:"}, - "\U0001f6b5\U0001f3fc": {":person_mountain_biking_tone2:"}, - "\U0001f6b5\U0001f3fc\u200d\u2640\ufe0f": {":woman_mountain_biking_tone2:"}, - "\U0001f6b5\U0001f3fc\u200d\u2642\ufe0f": {":man_mountain_biking_tone2:"}, - "\U0001f6b5\U0001f3fd": {":person_mountain_biking_tone3:"}, - "\U0001f6b5\U0001f3fd\u200d\u2640\ufe0f": {":woman_mountain_biking_tone3:"}, - "\U0001f6b5\U0001f3fd\u200d\u2642\ufe0f": {":man_mountain_biking_tone3:"}, - "\U0001f6b5\U0001f3fe": {":person_mountain_biking_tone4:"}, - "\U0001f6b5\U0001f3fe\u200d\u2640\ufe0f": {":woman_mountain_biking_tone4:"}, - "\U0001f6b5\U0001f3fe\u200d\u2642\ufe0f": {":man_mountain_biking_tone4:"}, - "\U0001f6b5\U0001f3ff": {":person_mountain_biking_tone5:"}, - "\U0001f6b5\U0001f3ff\u200d\u2640\ufe0f": {":woman_mountain_biking_tone5:"}, - "\U0001f6b5\U0001f3ff\u200d\u2642\ufe0f": {":man_mountain_biking_tone5:"}, - "\U0001f6b5\u200d\u2640\ufe0f": {":mountain_biking_woman:", ":woman-mountain-biking:", ":woman_mountain_biking:"}, - "\U0001f6b5\u200d\u2642\ufe0f": {":mountain_bicyclist:", ":man-mountain-biking:", ":man_mountain_biking:", ":mountain_biking_man:"}, - "\U0001f6b6": {":person_walking:"}, - "\U0001f6b6\U0001f3fb": {":person_walking_tone1:"}, - "\U0001f6b6\U0001f3fb\u200d\u2640\ufe0f": {":woman_walking_tone1:"}, - "\U0001f6b6\U0001f3fb\u200d\u2642\ufe0f": {":man_walking_tone1:"}, - "\U0001f6b6\U0001f3fc": {":person_walking_tone2:"}, - "\U0001f6b6\U0001f3fc\u200d\u2640\ufe0f": {":woman_walking_tone2:"}, - "\U0001f6b6\U0001f3fc\u200d\u2642\ufe0f": {":man_walking_tone2:"}, - "\U0001f6b6\U0001f3fd": {":person_walking_tone3:"}, - "\U0001f6b6\U0001f3fd\u200d\u2640\ufe0f": {":woman_walking_tone3:"}, - "\U0001f6b6\U0001f3fd\u200d\u2642\ufe0f": {":man_walking_tone3:"}, - "\U0001f6b6\U0001f3fe": {":person_walking_tone4:"}, - "\U0001f6b6\U0001f3fe\u200d\u2640\ufe0f": {":woman_walking_tone4:"}, - "\U0001f6b6\U0001f3fe\u200d\u2642\ufe0f": {":man_walking_tone4:"}, - "\U0001f6b6\U0001f3ff": {":person_walking_tone5:"}, - "\U0001f6b6\U0001f3ff\u200d\u2640\ufe0f": {":woman_walking_tone5:"}, - "\U0001f6b6\U0001f3ff\u200d\u2642\ufe0f": {":man_walking_tone5:"}, - "\U0001f6b6\u200d\u2640\ufe0f": {":walking_woman:", ":woman-walking:", ":woman_walking:"}, - "\U0001f6b6\u200d\u2642\ufe0f": {":walking:", ":man-walking:", ":man_walking:", ":walking_man:"}, - "\U0001f6b7": {":no_pedestrians:"}, - "\U0001f6b8": {":children_crossing:"}, - "\U0001f6b9": {":mens:", ":men’s_room:"}, - "\U0001f6ba": {":womens:", ":women’s_room:"}, - "\U0001f6bb": {":restroom:"}, - "\U0001f6bc": {":baby_symbol:"}, - "\U0001f6bd": {":toilet:"}, - "\U0001f6be": {":wc:", ":water_closet:"}, - "\U0001f6bf": {":shower:"}, - "\U0001f6c0": {":bath:", ":person_taking_bath:"}, - "\U0001f6c0\U0001f3fb": {":bath_tone1:"}, - "\U0001f6c0\U0001f3fc": {":bath_tone2:"}, - "\U0001f6c0\U0001f3fd": {":bath_tone3:"}, - "\U0001f6c0\U0001f3fe": {":bath_tone4:"}, - "\U0001f6c0\U0001f3ff": {":bath_tone5:"}, - "\U0001f6c1": {":bathtub:"}, - "\U0001f6c2": {":passport_control:"}, - "\U0001f6c3": {":customs:"}, - "\U0001f6c4": {":baggage_claim:"}, - "\U0001f6c5": {":left_luggage:"}, - "\U0001f6cb": {":couch:"}, - "\U0001f6cb\ufe0f": {":couch_and_lamp:"}, - "\U0001f6cc": {":sleeping_bed:", ":person_in_bed:", ":sleeping_accommodation:"}, - "\U0001f6cc\U0001f3fb": {":person_in_bed_tone1:"}, - "\U0001f6cc\U0001f3fc": {":person_in_bed_tone2:"}, - "\U0001f6cc\U0001f3fd": {":person_in_bed_tone3:"}, - "\U0001f6cc\U0001f3fe": {":person_in_bed_tone4:"}, - "\U0001f6cc\U0001f3ff": {":person_in_bed_tone5:"}, - "\U0001f6cd\ufe0f": {":shopping:", ":shopping_bags:"}, - "\U0001f6ce": {":bellhop:"}, - "\U0001f6ce\ufe0f": {":bellhop_bell:"}, - "\U0001f6cf\ufe0f": {":bed:"}, - "\U0001f6d0": {":place_of_worship:"}, - "\U0001f6d1": {":stop_sign:", ":octagonal_sign:"}, - "\U0001f6d2": {":shopping_cart:", ":shopping_trolley:"}, - "\U0001f6d5": {":hindu_temple:"}, - "\U0001f6d6": {":hut:"}, - "\U0001f6d7": {":elevator:"}, - "\U0001f6e0": {":tools:"}, - "\U0001f6e0\ufe0f": {":hammer_and_wrench:"}, - "\U0001f6e1\ufe0f": {":shield:"}, - "\U0001f6e2": {":oil:"}, - "\U0001f6e2\ufe0f": {":oil_drum:"}, - "\U0001f6e3\ufe0f": {":motorway:"}, - "\U0001f6e4\ufe0f": {":railway_track:"}, - "\U0001f6e5": {":motorboat:"}, - "\U0001f6e5\ufe0f": {":motor_boat:"}, - "\U0001f6e9": {":airplane_small:"}, - "\U0001f6e9\ufe0f": {":small_airplane:"}, - "\U0001f6eb": {":flight_departure:", ":airplane_departure:"}, - "\U0001f6ec": {":flight_arrival:", ":airplane_arrival:", ":airplane_arriving:"}, - "\U0001f6f0": {":satellite_orbital:"}, - "\U0001f6f0\ufe0f": {":satellite:", ":artificial_satellite:"}, - "\U0001f6f3": {":cruise_ship:"}, - "\U0001f6f3\ufe0f": {":passenger_ship:"}, - "\U0001f6f4": {":scooter:", ":kick_scooter:"}, - "\U0001f6f5": {":motor_scooter:"}, - "\U0001f6f6": {":canoe:"}, - "\U0001f6f7": {":sled:"}, - "\U0001f6f8": {":flying_saucer:"}, - "\U0001f6f9": {":skateboard:"}, - "\U0001f6fa": {":auto_rickshaw:"}, - "\U0001f6fb": {":pickup_truck:"}, - "\U0001f6fc": {":roller_skate:"}, - "\U0001f7e0": {":orange_circle:", ":large_orange_circle:"}, - "\U0001f7e1": {":yellow_circle:", ":large_yellow_circle:"}, - "\U0001f7e2": {":green_circle:", ":large_green_circle:"}, - "\U0001f7e3": {":purple_circle:", ":large_purple_circle:"}, - "\U0001f7e4": {":brown_circle:", ":large_brown_circle:"}, - "\U0001f7e5": {":red_square:", ":large_red_square:"}, - "\U0001f7e6": {":blue_square:", ":large_blue_square:"}, - "\U0001f7e7": {":orange_square:", ":large_orange_square:"}, - "\U0001f7e8": {":yellow_square:", ":large_yellow_square:"}, - "\U0001f7e9": {":green_square:", ":large_green_square:"}, - "\U0001f7ea": {":purple_square:", ":large_purple_square:"}, - "\U0001f7eb": {":brown_square:", ":large_brown_square:"}, - "\U0001f90c": {":pinched_fingers:"}, - "\U0001f90d": {":white_heart:"}, - "\U0001f90e": {":brown_heart:"}, - "\U0001f90f": {":pinching_hand:"}, - "\U0001f910": {":zipper_mouth:", ":zipper-mouth_face:", ":zipper_mouth_face:"}, - "\U0001f911": {":money_mouth:", ":money-mouth_face:", ":money_mouth_face:"}, - "\U0001f912": {":thermometer_face:", ":face_with_thermometer:"}, - "\U0001f913": {":nerd:", ":nerd_face:"}, - "\U0001f914": {":thinking:", ":thinking_face:"}, - "\U0001f915": {":head_bandage:", ":face_with_head-bandage:", ":face_with_head_bandage:"}, - "\U0001f916": {":robot:", ":robot_face:"}, - "\U0001f917": {":hugs:", ":hugging:", ":hugging_face:"}, - "\U0001f918": {":metal:", ":the_horns:", ":sign_of_the_horns:"}, - "\U0001f918\U0001f3fb": {":metal_tone1:"}, - "\U0001f918\U0001f3fc": {":metal_tone2:"}, - "\U0001f918\U0001f3fd": {":metal_tone3:"}, - "\U0001f918\U0001f3fe": {":metal_tone4:"}, - "\U0001f918\U0001f3ff": {":metal_tone5:"}, - "\U0001f919": {":call_me:", ":call_me_hand:"}, - "\U0001f919\U0001f3fb": {":call_me_tone1:"}, - "\U0001f919\U0001f3fc": {":call_me_tone2:"}, - "\U0001f919\U0001f3fd": {":call_me_tone3:"}, - "\U0001f919\U0001f3fe": {":call_me_tone4:"}, - "\U0001f919\U0001f3ff": {":call_me_tone5:"}, - "\U0001f91a": {":raised_back_of_hand:"}, - "\U0001f91a\U0001f3fb": {":raised_back_of_hand_tone1:"}, - "\U0001f91a\U0001f3fc": {":raised_back_of_hand_tone2:"}, - "\U0001f91a\U0001f3fd": {":raised_back_of_hand_tone3:"}, - "\U0001f91a\U0001f3fe": {":raised_back_of_hand_tone4:"}, - "\U0001f91a\U0001f3ff": {":raised_back_of_hand_tone5:"}, - "\U0001f91b": {":fist_left:", ":left-facing_fist:", ":left_facing_fist:"}, - "\U0001f91b\U0001f3fb": {":left_facing_fist_tone1:"}, - "\U0001f91b\U0001f3fc": {":left_facing_fist_tone2:"}, - "\U0001f91b\U0001f3fd": {":left_facing_fist_tone3:"}, - "\U0001f91b\U0001f3fe": {":left_facing_fist_tone4:"}, - "\U0001f91b\U0001f3ff": {":left_facing_fist_tone5:"}, - "\U0001f91c": {":fist_right:", ":right-facing_fist:", ":right_facing_fist:"}, - "\U0001f91c\U0001f3fb": {":right_facing_fist_tone1:"}, - "\U0001f91c\U0001f3fc": {":right_facing_fist_tone2:"}, - "\U0001f91c\U0001f3fd": {":right_facing_fist_tone3:"}, - "\U0001f91c\U0001f3fe": {":right_facing_fist_tone4:"}, - "\U0001f91c\U0001f3ff": {":right_facing_fist_tone5:"}, - "\U0001f91d": {":handshake:"}, - "\U0001f91e": {":crossed_fingers:", ":fingers_crossed:"}, - "\U0001f91e\U0001f3fb": {":fingers_crossed_tone1:"}, - "\U0001f91e\U0001f3fc": {":fingers_crossed_tone2:"}, - "\U0001f91e\U0001f3fd": {":fingers_crossed_tone3:"}, - "\U0001f91e\U0001f3fe": {":fingers_crossed_tone4:"}, - "\U0001f91e\U0001f3ff": {":fingers_crossed_tone5:"}, - "\U0001f91f": {":love-you_gesture:", ":love_you_gesture:", ":i_love_you_hand_sign:"}, - "\U0001f91f\U0001f3fb": {":love_you_gesture_tone1:"}, - "\U0001f91f\U0001f3fc": {":love_you_gesture_tone2:"}, - "\U0001f91f\U0001f3fd": {":love_you_gesture_tone3:"}, - "\U0001f91f\U0001f3fe": {":love_you_gesture_tone4:"}, - "\U0001f91f\U0001f3ff": {":love_you_gesture_tone5:"}, - "\U0001f920": {":cowboy:", ":cowboy_hat_face:", ":face_with_cowboy_hat:"}, - "\U0001f921": {":clown:", ":clown_face:"}, - "\U0001f922": {":nauseated_face:"}, - "\U0001f923": {":rofl:", ":rolling_on_the_floor_laughing:"}, - "\U0001f924": {":drooling_face:"}, - "\U0001f925": {":lying_face:"}, - "\U0001f926": {":facepalm:", ":face_palm:", ":person_facepalming:"}, - "\U0001f926\U0001f3fb": {":person_facepalming_tone1:"}, - "\U0001f926\U0001f3fb\u200d\u2640\ufe0f": {":woman_facepalming_tone1:"}, - "\U0001f926\U0001f3fb\u200d\u2642\ufe0f": {":man_facepalming_tone1:"}, - "\U0001f926\U0001f3fc": {":person_facepalming_tone2:"}, - "\U0001f926\U0001f3fc\u200d\u2640\ufe0f": {":woman_facepalming_tone2:"}, - "\U0001f926\U0001f3fc\u200d\u2642\ufe0f": {":man_facepalming_tone2:"}, - "\U0001f926\U0001f3fd": {":person_facepalming_tone3:"}, - "\U0001f926\U0001f3fd\u200d\u2640\ufe0f": {":woman_facepalming_tone3:"}, - "\U0001f926\U0001f3fd\u200d\u2642\ufe0f": {":man_facepalming_tone3:"}, - "\U0001f926\U0001f3fe": {":person_facepalming_tone4:"}, - "\U0001f926\U0001f3fe\u200d\u2640\ufe0f": {":woman_facepalming_tone4:"}, - "\U0001f926\U0001f3fe\u200d\u2642\ufe0f": {":man_facepalming_tone4:"}, - "\U0001f926\U0001f3ff": {":person_facepalming_tone5:"}, - "\U0001f926\U0001f3ff\u200d\u2640\ufe0f": {":woman_facepalming_tone5:"}, - "\U0001f926\U0001f3ff\u200d\u2642\ufe0f": {":man_facepalming_tone5:"}, - "\U0001f926\u200d\u2640\ufe0f": {":woman-facepalming:", ":woman_facepalming:"}, - "\U0001f926\u200d\u2642\ufe0f": {":man-facepalming:", ":man_facepalming:"}, - "\U0001f927": {":sneezing_face:"}, - "\U0001f928": {":raised_eyebrow:", ":face_with_raised_eyebrow:"}, - "\U0001f929": {":star-struck:", ":star_struck:"}, - "\U0001f92a": {":zany_face:", ":crazy_face:"}, - "\U0001f92b": {":shushing_face:"}, - "\U0001f92c": {":cursing_face:", ":face_with_symbols_on_mouth:", ":face_with_symbols_over_mouth:"}, - "\U0001f92d": {":hand_over_mouth:", ":face_with_hand_over_mouth:"}, - "\U0001f92e": {":face_vomiting:", ":vomiting_face:"}, - "\U0001f92f": {":exploding_head:"}, - "\U0001f930": {":pregnant_woman:"}, - "\U0001f930\U0001f3fb": {":pregnant_woman_tone1:"}, - "\U0001f930\U0001f3fc": {":pregnant_woman_tone2:"}, - "\U0001f930\U0001f3fd": {":pregnant_woman_tone3:"}, - "\U0001f930\U0001f3fe": {":pregnant_woman_tone4:"}, - "\U0001f930\U0001f3ff": {":pregnant_woman_tone5:"}, - "\U0001f931": {":breast-feeding:", ":breast_feeding:"}, - "\U0001f931\U0001f3fb": {":breast_feeding_tone1:"}, - "\U0001f931\U0001f3fc": {":breast_feeding_tone2:"}, - "\U0001f931\U0001f3fd": {":breast_feeding_tone3:"}, - "\U0001f931\U0001f3fe": {":breast_feeding_tone4:"}, - "\U0001f931\U0001f3ff": {":breast_feeding_tone5:"}, - "\U0001f932": {":palms_up_together:"}, - "\U0001f932\U0001f3fb": {":palms_up_together_tone1:"}, - "\U0001f932\U0001f3fc": {":palms_up_together_tone2:"}, - "\U0001f932\U0001f3fd": {":palms_up_together_tone3:"}, - "\U0001f932\U0001f3fe": {":palms_up_together_tone4:"}, - "\U0001f932\U0001f3ff": {":palms_up_together_tone5:"}, - "\U0001f933": {":selfie:"}, - "\U0001f933\U0001f3fb": {":selfie_tone1:"}, - "\U0001f933\U0001f3fc": {":selfie_tone2:"}, - "\U0001f933\U0001f3fd": {":selfie_tone3:"}, - "\U0001f933\U0001f3fe": {":selfie_tone4:"}, - "\U0001f933\U0001f3ff": {":selfie_tone5:"}, - "\U0001f934": {":prince:"}, - "\U0001f934\U0001f3fb": {":prince_tone1:"}, - "\U0001f934\U0001f3fc": {":prince_tone2:"}, - "\U0001f934\U0001f3fd": {":prince_tone3:"}, - "\U0001f934\U0001f3fe": {":prince_tone4:"}, - "\U0001f934\U0001f3ff": {":prince_tone5:"}, - "\U0001f935": {":person_in_tuxedo:"}, - "\U0001f935\U0001f3fb": {":man_in_tuxedo_tone1:"}, - "\U0001f935\U0001f3fc": {":man_in_tuxedo_tone2:"}, - "\U0001f935\U0001f3fd": {":man_in_tuxedo_tone3:"}, - "\U0001f935\U0001f3fe": {":man_in_tuxedo_tone4:"}, - "\U0001f935\U0001f3ff": {":man_in_tuxedo_tone5:"}, - "\U0001f935\u200d\u2640\ufe0f": {":woman_in_tuxedo:"}, - "\U0001f935\u200d\u2642\ufe0f": {":man_in_tuxedo:"}, - "\U0001f936": {":mrs_claus:", ":Mrs._Claus:"}, - "\U0001f936\U0001f3fb": {":mrs_claus_tone1:"}, - "\U0001f936\U0001f3fc": {":mrs_claus_tone2:"}, - "\U0001f936\U0001f3fd": {":mrs_claus_tone3:"}, - "\U0001f936\U0001f3fe": {":mrs_claus_tone4:"}, - "\U0001f936\U0001f3ff": {":mrs_claus_tone5:"}, - "\U0001f937": {":shrug:", ":person_shrugging:"}, - "\U0001f937\U0001f3fb": {":person_shrugging_tone1:"}, - "\U0001f937\U0001f3fb\u200d\u2640\ufe0f": {":woman_shrugging_tone1:"}, - "\U0001f937\U0001f3fb\u200d\u2642\ufe0f": {":man_shrugging_tone1:"}, - "\U0001f937\U0001f3fc": {":person_shrugging_tone2:"}, - "\U0001f937\U0001f3fc\u200d\u2640\ufe0f": {":woman_shrugging_tone2:"}, - "\U0001f937\U0001f3fc\u200d\u2642\ufe0f": {":man_shrugging_tone2:"}, - "\U0001f937\U0001f3fd": {":person_shrugging_tone3:"}, - "\U0001f937\U0001f3fd\u200d\u2640\ufe0f": {":woman_shrugging_tone3:"}, - "\U0001f937\U0001f3fd\u200d\u2642\ufe0f": {":man_shrugging_tone3:"}, - "\U0001f937\U0001f3fe": {":person_shrugging_tone4:"}, - "\U0001f937\U0001f3fe\u200d\u2640\ufe0f": {":woman_shrugging_tone4:"}, - "\U0001f937\U0001f3fe\u200d\u2642\ufe0f": {":man_shrugging_tone4:"}, - "\U0001f937\U0001f3ff": {":person_shrugging_tone5:"}, - "\U0001f937\U0001f3ff\u200d\u2640\ufe0f": {":woman_shrugging_tone5:"}, - "\U0001f937\U0001f3ff\u200d\u2642\ufe0f": {":man_shrugging_tone5:"}, - "\U0001f937\u200d\u2640\ufe0f": {":woman-shrugging:", ":woman_shrugging:"}, - "\U0001f937\u200d\u2642\ufe0f": {":man-shrugging:", ":man_shrugging:"}, - "\U0001f938": {":cartwheeling:", ":person_cartwheeling:", ":person_doing_cartwheel:"}, - "\U0001f938\U0001f3fb": {":person_doing_cartwheel_tone1:"}, - "\U0001f938\U0001f3fb\u200d\u2640\ufe0f": {":woman_cartwheeling_tone1:"}, - "\U0001f938\U0001f3fb\u200d\u2642\ufe0f": {":man_cartwheeling_tone1:"}, - "\U0001f938\U0001f3fc": {":person_doing_cartwheel_tone2:"}, - "\U0001f938\U0001f3fc\u200d\u2640\ufe0f": {":woman_cartwheeling_tone2:"}, - "\U0001f938\U0001f3fc\u200d\u2642\ufe0f": {":man_cartwheeling_tone2:"}, - "\U0001f938\U0001f3fd": {":person_doing_cartwheel_tone3:"}, - "\U0001f938\U0001f3fd\u200d\u2640\ufe0f": {":woman_cartwheeling_tone3:"}, - "\U0001f938\U0001f3fd\u200d\u2642\ufe0f": {":man_cartwheeling_tone3:"}, - "\U0001f938\U0001f3fe": {":person_doing_cartwheel_tone4:"}, - "\U0001f938\U0001f3fe\u200d\u2640\ufe0f": {":woman_cartwheeling_tone4:"}, - "\U0001f938\U0001f3fe\u200d\u2642\ufe0f": {":man_cartwheeling_tone4:"}, - "\U0001f938\U0001f3ff": {":person_doing_cartwheel_tone5:"}, - "\U0001f938\U0001f3ff\u200d\u2640\ufe0f": {":woman_cartwheeling_tone5:"}, - "\U0001f938\U0001f3ff\u200d\u2642\ufe0f": {":man_cartwheeling_tone5:"}, - "\U0001f938\u200d\u2640\ufe0f": {":woman-cartwheeling:", ":woman_cartwheeling:"}, - "\U0001f938\u200d\u2642\ufe0f": {":man-cartwheeling:", ":man_cartwheeling:"}, - "\U0001f939": {":juggling:", ":juggling_person:", ":person_juggling:"}, - "\U0001f939\U0001f3fb": {":person_juggling_tone1:"}, - "\U0001f939\U0001f3fb\u200d\u2640\ufe0f": {":woman_juggling_tone1:"}, - "\U0001f939\U0001f3fb\u200d\u2642\ufe0f": {":man_juggling_tone1:"}, - "\U0001f939\U0001f3fc": {":person_juggling_tone2:"}, - "\U0001f939\U0001f3fc\u200d\u2640\ufe0f": {":woman_juggling_tone2:"}, - "\U0001f939\U0001f3fc\u200d\u2642\ufe0f": {":man_juggling_tone2:"}, - "\U0001f939\U0001f3fd": {":person_juggling_tone3:"}, - "\U0001f939\U0001f3fd\u200d\u2640\ufe0f": {":woman_juggling_tone3:"}, - "\U0001f939\U0001f3fd\u200d\u2642\ufe0f": {":man_juggling_tone3:"}, - "\U0001f939\U0001f3fe": {":person_juggling_tone4:"}, - "\U0001f939\U0001f3fe\u200d\u2640\ufe0f": {":woman_juggling_tone4:"}, - "\U0001f939\U0001f3fe\u200d\u2642\ufe0f": {":man_juggling_tone4:"}, - "\U0001f939\U0001f3ff": {":person_juggling_tone5:"}, - "\U0001f939\U0001f3ff\u200d\u2640\ufe0f": {":woman_juggling_tone5:"}, - "\U0001f939\U0001f3ff\u200d\u2642\ufe0f": {":man_juggling_tone5:"}, - "\U0001f939\u200d\u2640\ufe0f": {":woman-juggling:", ":woman_juggling:"}, - "\U0001f939\u200d\u2642\ufe0f": {":man-juggling:", ":man_juggling:"}, - "\U0001f93a": {":fencer:", ":person_fencing:"}, - "\U0001f93c": {":wrestlers:", ":wrestling:", ":people_wrestling:"}, - "\U0001f93c\u200d\u2640\ufe0f": {":woman-wrestling:", ":women_wrestling:"}, - "\U0001f93c\u200d\u2642\ufe0f": {":man-wrestling:", ":men_wrestling:"}, - "\U0001f93d": {":water_polo:", ":person_playing_water_polo:"}, - "\U0001f93d\U0001f3fb": {":person_playing_water_polo_tone1:"}, - "\U0001f93d\U0001f3fb\u200d\u2640\ufe0f": {":woman_playing_water_polo_tone1:"}, - "\U0001f93d\U0001f3fb\u200d\u2642\ufe0f": {":man_playing_water_polo_tone1:"}, - "\U0001f93d\U0001f3fc": {":person_playing_water_polo_tone2:"}, - "\U0001f93d\U0001f3fc\u200d\u2640\ufe0f": {":woman_playing_water_polo_tone2:"}, - "\U0001f93d\U0001f3fc\u200d\u2642\ufe0f": {":man_playing_water_polo_tone2:"}, - "\U0001f93d\U0001f3fd": {":person_playing_water_polo_tone3:"}, - "\U0001f93d\U0001f3fd\u200d\u2640\ufe0f": {":woman_playing_water_polo_tone3:"}, - "\U0001f93d\U0001f3fd\u200d\u2642\ufe0f": {":man_playing_water_polo_tone3:"}, - "\U0001f93d\U0001f3fe": {":person_playing_water_polo_tone4:"}, - "\U0001f93d\U0001f3fe\u200d\u2640\ufe0f": {":woman_playing_water_polo_tone4:"}, - "\U0001f93d\U0001f3fe\u200d\u2642\ufe0f": {":man_playing_water_polo_tone4:"}, - "\U0001f93d\U0001f3ff": {":person_playing_water_polo_tone5:"}, - "\U0001f93d\U0001f3ff\u200d\u2640\ufe0f": {":woman_playing_water_polo_tone5:"}, - "\U0001f93d\U0001f3ff\u200d\u2642\ufe0f": {":man_playing_water_polo_tone5:"}, - "\U0001f93d\u200d\u2640\ufe0f": {":woman-playing-water-polo:", ":woman_playing_water_polo:"}, - "\U0001f93d\u200d\u2642\ufe0f": {":man-playing-water-polo:", ":man_playing_water_polo:"}, - "\U0001f93e": {":handball:", ":handball_person:", ":person_playing_handball:"}, - "\U0001f93e\U0001f3fb": {":person_playing_handball_tone1:"}, - "\U0001f93e\U0001f3fb\u200d\u2640\ufe0f": {":woman_playing_handball_tone1:"}, - "\U0001f93e\U0001f3fb\u200d\u2642\ufe0f": {":man_playing_handball_tone1:"}, - "\U0001f93e\U0001f3fc": {":person_playing_handball_tone2:"}, - "\U0001f93e\U0001f3fc\u200d\u2640\ufe0f": {":woman_playing_handball_tone2:"}, - "\U0001f93e\U0001f3fc\u200d\u2642\ufe0f": {":man_playing_handball_tone2:"}, - "\U0001f93e\U0001f3fd": {":person_playing_handball_tone3:"}, - "\U0001f93e\U0001f3fd\u200d\u2640\ufe0f": {":woman_playing_handball_tone3:"}, - "\U0001f93e\U0001f3fd\u200d\u2642\ufe0f": {":man_playing_handball_tone3:"}, - "\U0001f93e\U0001f3fe": {":person_playing_handball_tone4:"}, - "\U0001f93e\U0001f3fe\u200d\u2640\ufe0f": {":woman_playing_handball_tone4:"}, - "\U0001f93e\U0001f3fe\u200d\u2642\ufe0f": {":man_playing_handball_tone4:"}, - "\U0001f93e\U0001f3ff": {":person_playing_handball_tone5:"}, - "\U0001f93e\U0001f3ff\u200d\u2640\ufe0f": {":woman_playing_handball_tone5:"}, - "\U0001f93e\U0001f3ff\u200d\u2642\ufe0f": {":man_playing_handball_tone5:"}, - "\U0001f93e\u200d\u2640\ufe0f": {":woman-playing-handball:", ":woman_playing_handball:"}, - "\U0001f93e\u200d\u2642\ufe0f": {":man-playing-handball:", ":man_playing_handball:"}, - "\U0001f93f": {":diving_mask:"}, - "\U0001f940": {":wilted_rose:", ":wilted_flower:"}, - "\U0001f941": {":drum:", ":drum_with_drumsticks:"}, - "\U0001f942": {":champagne_glass:", ":clinking_glasses:"}, - "\U0001f943": {":tumbler_glass:"}, - "\U0001f944": {":spoon:"}, - "\U0001f945": {":goal:", ":goal_net:"}, - "\U0001f947": {":first_place:", ":1st_place_medal:", ":first_place_medal:"}, - "\U0001f948": {":second_place:", ":2nd_place_medal:", ":second_place_medal:"}, - "\U0001f949": {":third_place:", ":3rd_place_medal:", ":third_place_medal:"}, - "\U0001f94a": {":boxing_glove:"}, - "\U0001f94b": {":martial_arts_uniform:"}, - "\U0001f94c": {":curling_stone:"}, - "\U0001f94d": {":lacrosse:"}, - "\U0001f94e": {":softball:"}, - "\U0001f94f": {":flying_disc:"}, - "\U0001f950": {":croissant:"}, - "\U0001f951": {":avocado:"}, - "\U0001f952": {":cucumber:"}, - "\U0001f953": {":bacon:"}, - "\U0001f954": {":potato:"}, - "\U0001f955": {":carrot:"}, - "\U0001f956": {":french_bread:", ":baguette_bread:"}, - "\U0001f957": {":salad:", ":green_salad:"}, - "\U0001f958": {":shallow_pan_of_food:"}, - "\U0001f959": {":stuffed_flatbread:"}, - "\U0001f95a": {":egg:"}, - "\U0001f95b": {":milk:", ":milk_glass:", ":glass_of_milk:"}, - "\U0001f95c": {":peanuts:"}, - "\U0001f95d": {":kiwi:", ":kiwifruit:", ":kiwi_fruit:"}, - "\U0001f95e": {":pancakes:"}, - "\U0001f95f": {":dumpling:"}, - "\U0001f960": {":fortune_cookie:"}, - "\U0001f961": {":takeout_box:"}, - "\U0001f962": {":chopsticks:"}, - "\U0001f963": {":bowl_with_spoon:"}, - "\U0001f964": {":cup_with_straw:"}, - "\U0001f965": {":coconut:"}, - "\U0001f966": {":broccoli:"}, - "\U0001f967": {":pie:"}, - "\U0001f968": {":pretzel:"}, - "\U0001f969": {":cut_of_meat:"}, - "\U0001f96a": {":sandwich:"}, - "\U0001f96b": {":canned_food:"}, - "\U0001f96c": {":leafy_green:"}, - "\U0001f96d": {":mango:"}, - "\U0001f96e": {":moon_cake:"}, - "\U0001f96f": {":bagel:"}, - "\U0001f970": {":smiling_face_with_hearts:", ":smiling_face_with_3_hearts:", ":smiling_face_with_three_hearts:"}, - "\U0001f971": {":yawning_face:"}, - "\U0001f972": {":smiling_face_with_tear:"}, - "\U0001f973": {":partying_face:"}, - "\U0001f974": {":woozy_face:"}, - "\U0001f975": {":hot_face:"}, - "\U0001f976": {":cold_face:"}, - "\U0001f977": {":ninja:"}, - "\U0001f978": {":disguised_face:"}, - "\U0001f97a": {":pleading_face:"}, - "\U0001f97b": {":sari:"}, - "\U0001f97c": {":lab_coat:"}, - "\U0001f97d": {":goggles:"}, - "\U0001f97e": {":hiking_boot:"}, - "\U0001f97f": {":flat_shoe:", ":womans_flat_shoe:"}, - "\U0001f980": {":crab:"}, - "\U0001f981": {":lion:", ":lion_face:"}, - "\U0001f982": {":scorpion:"}, - "\U0001f983": {":turkey:"}, - "\U0001f984": {":unicorn:", ":unicorn_face:"}, - "\U0001f985": {":eagle:"}, - "\U0001f986": {":duck:"}, - "\U0001f987": {":bat:"}, - "\U0001f988": {":shark:"}, - "\U0001f989": {":owl:"}, - "\U0001f98a": {":fox:", ":fox_face:"}, - "\U0001f98b": {":butterfly:"}, - "\U0001f98c": {":deer:"}, - "\U0001f98d": {":gorilla:"}, - "\U0001f98e": {":lizard:"}, - "\U0001f98f": {":rhino:", ":rhinoceros:"}, - "\U0001f990": {":shrimp:"}, - "\U0001f991": {":squid:"}, - "\U0001f992": {":giraffe:", ":giraffe_face:"}, - "\U0001f993": {":zebra:", ":zebra_face:"}, - "\U0001f994": {":hedgehog:"}, - "\U0001f995": {":sauropod:"}, - "\U0001f996": {":T-Rex:", ":t-rex:", ":t_rex:"}, - "\U0001f997": {":cricket:"}, - "\U0001f998": {":kangaroo:"}, - "\U0001f999": {":llama:"}, - "\U0001f99a": {":peacock:"}, - "\U0001f99b": {":hippopotamus:"}, - "\U0001f99c": {":parrot:"}, - "\U0001f99d": {":raccoon:"}, - "\U0001f99e": {":lobster:"}, - "\U0001f99f": {":mosquito:"}, - "\U0001f9a0": {":microbe:"}, - "\U0001f9a1": {":badger:"}, - "\U0001f9a2": {":swan:"}, - "\U0001f9a3": {":mammoth:"}, - "\U0001f9a4": {":dodo:"}, - "\U0001f9a5": {":sloth:"}, - "\U0001f9a6": {":otter:"}, - "\U0001f9a7": {":orangutan:"}, - "\U0001f9a8": {":skunk:"}, - "\U0001f9a9": {":flamingo:"}, - "\U0001f9aa": {":oyster:"}, - "\U0001f9ab": {":beaver:"}, - "\U0001f9ac": {":bison:"}, - "\U0001f9ad": {":seal:"}, - "\U0001f9ae": {":guide_dog:"}, - "\U0001f9af": {":white_cane:", ":probing_cane:"}, - "\U0001f9b0": {":red_hair:"}, - "\U0001f9b1": {":curly_hair:"}, - "\U0001f9b2": {":bald:"}, - "\U0001f9b3": {":white_hair:"}, - "\U0001f9b4": {":bone:"}, - "\U0001f9b5": {":leg:"}, - "\U0001f9b6": {":foot:"}, - "\U0001f9b7": {":tooth:"}, - "\U0001f9b8": {":superhero:"}, - "\U0001f9b8\u200d\u2640\ufe0f": {":superhero_woman:", ":woman_superhero:", ":female_superhero:"}, - "\U0001f9b8\u200d\u2642\ufe0f": {":man_superhero:", ":superhero_man:", ":male_superhero:"}, - "\U0001f9b9": {":supervillain:"}, - "\U0001f9b9\u200d\u2640\ufe0f": {":supervillain_woman:", ":woman_supervillain:", ":female_supervillain:"}, - "\U0001f9b9\u200d\u2642\ufe0f": {":man_supervillain:", ":supervillain_man:", ":male_supervillain:"}, - "\U0001f9ba": {":safety_vest:"}, - "\U0001f9bb": {":ear_with_hearing_aid:"}, - "\U0001f9bc": {":motorized_wheelchair:"}, - "\U0001f9bd": {":manual_wheelchair:"}, - "\U0001f9be": {":mechanical_arm:"}, - "\U0001f9bf": {":mechanical_leg:"}, - "\U0001f9c0": {":cheese:", ":cheese_wedge:"}, - "\U0001f9c1": {":cupcake:"}, - "\U0001f9c2": {":salt:"}, - "\U0001f9c3": {":beverage_box:"}, - "\U0001f9c4": {":garlic:"}, - "\U0001f9c5": {":onion:"}, - "\U0001f9c6": {":falafel:"}, - "\U0001f9c7": {":waffle:"}, - "\U0001f9c8": {":butter:"}, - "\U0001f9c9": {":mate:", ":mate_drink:"}, - "\U0001f9ca": {":ice:", ":ice_cube:"}, - "\U0001f9cb": {":bubble_tea:"}, - "\U0001f9cd": {":person_standing:", ":standing_person:"}, - "\U0001f9cd\u200d\u2640\ufe0f": {":standing_woman:", ":woman_standing:"}, - "\U0001f9cd\u200d\u2642\ufe0f": {":man_standing:", ":standing_man:"}, - "\U0001f9ce": {":kneeling_person:", ":person_kneeling:"}, - "\U0001f9ce\u200d\u2640\ufe0f": {":kneeling_woman:", ":woman_kneeling:"}, - "\U0001f9ce\u200d\u2642\ufe0f": {":kneeling_man:", ":man_kneeling:"}, - "\U0001f9cf": {":deaf_person:"}, - "\U0001f9cf\u200d\u2640\ufe0f": {":deaf_woman:"}, - "\U0001f9cf\u200d\u2642\ufe0f": {":deaf_man:"}, - "\U0001f9d0": {":monocle_face:", ":face_with_monocle:"}, - "\U0001f9d1": {":adult:", ":person:"}, - "\U0001f9d1\U0001f3fb": {":adult_tone1:"}, - "\U0001f9d1\U0001f3fc": {":adult_tone2:"}, - "\U0001f9d1\U0001f3fd": {":adult_tone3:"}, - "\U0001f9d1\U0001f3fe": {":adult_tone4:"}, - "\U0001f9d1\U0001f3ff": {":adult_tone5:"}, - "\U0001f9d1\u200d\U0001f33e": {":farmer:"}, - "\U0001f9d1\u200d\U0001f373": {":cook:"}, - "\U0001f9d1\u200d\U0001f37c": {":person_feeding_baby:"}, - "\U0001f9d1\u200d\U0001f384": {":mx_claus:"}, - "\U0001f9d1\u200d\U0001f393": {":student:"}, - "\U0001f9d1\u200d\U0001f3a4": {":singer:"}, - "\U0001f9d1\u200d\U0001f3a8": {":artist:"}, - "\U0001f9d1\u200d\U0001f3eb": {":teacher:"}, - "\U0001f9d1\u200d\U0001f3ed": {":factory_worker:"}, - "\U0001f9d1\u200d\U0001f4bb": {":technologist:"}, - "\U0001f9d1\u200d\U0001f4bc": {":office_worker:"}, - "\U0001f9d1\u200d\U0001f527": {":mechanic:"}, - "\U0001f9d1\u200d\U0001f52c": {":scientist:"}, - "\U0001f9d1\u200d\U0001f680": {":astronaut:"}, - "\U0001f9d1\u200d\U0001f692": {":firefighter:"}, - "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1": {":people_holding_hands:"}, - "\U0001f9d1\u200d\U0001f9af": {":person_with_white_cane:", ":person_with_probing_cane:"}, - "\U0001f9d1\u200d\U0001f9b0": {":person_red_hair:", ":red_haired_person:"}, - "\U0001f9d1\u200d\U0001f9b1": {":person_curly_hair:", ":curly_haired_person:"}, - "\U0001f9d1\u200d\U0001f9b2": {":bald_person:", ":person_bald:"}, - "\U0001f9d1\u200d\U0001f9b3": {":person_white_hair:", ":white_haired_person:"}, - "\U0001f9d1\u200d\U0001f9bc": {":person_in_motorized_wheelchair:"}, - "\U0001f9d1\u200d\U0001f9bd": {":person_in_manual_wheelchair:"}, - "\U0001f9d1\u200d\u2695\ufe0f": {":health_worker:"}, - "\U0001f9d1\u200d\u2696\ufe0f": {":judge:"}, - "\U0001f9d1\u200d\u2708\ufe0f": {":pilot:"}, - "\U0001f9d2": {":child:"}, - "\U0001f9d2\U0001f3fb": {":child_tone1:"}, - "\U0001f9d2\U0001f3fc": {":child_tone2:"}, - "\U0001f9d2\U0001f3fd": {":child_tone3:"}, - "\U0001f9d2\U0001f3fe": {":child_tone4:"}, - "\U0001f9d2\U0001f3ff": {":child_tone5:"}, - "\U0001f9d3": {":older_adult:", ":older_person:"}, - "\U0001f9d3\U0001f3fb": {":older_adult_tone1:"}, - "\U0001f9d3\U0001f3fc": {":older_adult_tone2:"}, - "\U0001f9d3\U0001f3fd": {":older_adult_tone3:"}, - "\U0001f9d3\U0001f3fe": {":older_adult_tone4:"}, - "\U0001f9d3\U0001f3ff": {":older_adult_tone5:"}, - "\U0001f9d4": {":person_beard:", ":bearded_person:"}, - "\U0001f9d4\U0001f3fb": {":bearded_person_tone1:"}, - "\U0001f9d4\U0001f3fc": {":bearded_person_tone2:"}, - "\U0001f9d4\U0001f3fd": {":bearded_person_tone3:"}, - "\U0001f9d4\U0001f3fe": {":bearded_person_tone4:"}, - "\U0001f9d4\U0001f3ff": {":bearded_person_tone5:"}, - "\U0001f9d4\u200d\u2640\ufe0f": {":woman_beard:"}, - "\U0001f9d4\u200d\u2642\ufe0f": {":man_beard:"}, - "\U0001f9d5": {":woman_with_headscarf:", ":person_with_headscarf:"}, - "\U0001f9d5\U0001f3fb": {":woman_with_headscarf_tone1:"}, - "\U0001f9d5\U0001f3fc": {":woman_with_headscarf_tone2:"}, - "\U0001f9d5\U0001f3fd": {":woman_with_headscarf_tone3:"}, - "\U0001f9d5\U0001f3fe": {":woman_with_headscarf_tone4:"}, - "\U0001f9d5\U0001f3ff": {":woman_with_headscarf_tone5:"}, - "\U0001f9d6": {":sauna_person:"}, - "\U0001f9d6\U0001f3fb": {":person_in_steamy_room_tone1:"}, - "\U0001f9d6\U0001f3fb\u200d\u2640\ufe0f": {":woman_in_steamy_room_tone1:"}, - "\U0001f9d6\U0001f3fb\u200d\u2642\ufe0f": {":man_in_steamy_room_tone1:"}, - "\U0001f9d6\U0001f3fc": {":person_in_steamy_room_tone2:"}, - "\U0001f9d6\U0001f3fc\u200d\u2640\ufe0f": {":woman_in_steamy_room_tone2:"}, - "\U0001f9d6\U0001f3fc\u200d\u2642\ufe0f": {":man_in_steamy_room_tone2:"}, - "\U0001f9d6\U0001f3fd": {":person_in_steamy_room_tone3:"}, - "\U0001f9d6\U0001f3fd\u200d\u2640\ufe0f": {":woman_in_steamy_room_tone3:"}, - "\U0001f9d6\U0001f3fd\u200d\u2642\ufe0f": {":man_in_steamy_room_tone3:"}, - "\U0001f9d6\U0001f3fe": {":person_in_steamy_room_tone4:"}, - "\U0001f9d6\U0001f3fe\u200d\u2640\ufe0f": {":woman_in_steamy_room_tone4:"}, - "\U0001f9d6\U0001f3fe\u200d\u2642\ufe0f": {":man_in_steamy_room_tone4:"}, - "\U0001f9d6\U0001f3ff": {":person_in_steamy_room_tone5:"}, - "\U0001f9d6\U0001f3ff\u200d\u2640\ufe0f": {":woman_in_steamy_room_tone5:"}, - "\U0001f9d6\U0001f3ff\u200d\u2642\ufe0f": {":man_in_steamy_room_tone5:"}, - "\U0001f9d6\u200d\u2640\ufe0f": {":sauna_woman:", ":woman_in_steamy_room:"}, - "\U0001f9d6\u200d\u2642\ufe0f": {":sauna_man:", ":man_in_steamy_room:", ":person_in_steamy_room:"}, - "\U0001f9d7": {":climbing:"}, - "\U0001f9d7\U0001f3fb": {":person_climbing_tone1:"}, - "\U0001f9d7\U0001f3fb\u200d\u2640\ufe0f": {":woman_climbing_tone1:"}, - "\U0001f9d7\U0001f3fb\u200d\u2642\ufe0f": {":man_climbing_tone1:"}, - "\U0001f9d7\U0001f3fc": {":person_climbing_tone2:"}, - "\U0001f9d7\U0001f3fc\u200d\u2640\ufe0f": {":woman_climbing_tone2:"}, - "\U0001f9d7\U0001f3fc\u200d\u2642\ufe0f": {":man_climbing_tone2:"}, - "\U0001f9d7\U0001f3fd": {":person_climbing_tone3:"}, - "\U0001f9d7\U0001f3fd\u200d\u2640\ufe0f": {":woman_climbing_tone3:"}, - "\U0001f9d7\U0001f3fd\u200d\u2642\ufe0f": {":man_climbing_tone3:"}, - "\U0001f9d7\U0001f3fe": {":person_climbing_tone4:"}, - "\U0001f9d7\U0001f3fe\u200d\u2640\ufe0f": {":woman_climbing_tone4:"}, - "\U0001f9d7\U0001f3fe\u200d\u2642\ufe0f": {":man_climbing_tone4:"}, - "\U0001f9d7\U0001f3ff": {":person_climbing_tone5:"}, - "\U0001f9d7\U0001f3ff\u200d\u2640\ufe0f": {":woman_climbing_tone5:"}, - "\U0001f9d7\U0001f3ff\u200d\u2642\ufe0f": {":man_climbing_tone5:"}, - "\U0001f9d7\u200d\u2640\ufe0f": {":climbing_woman:", ":woman_climbing:", ":person_climbing:"}, - "\U0001f9d7\u200d\u2642\ufe0f": {":climbing_man:", ":man_climbing:"}, - "\U0001f9d8": {":lotus_position:"}, - "\U0001f9d8\U0001f3fb": {":person_in_lotus_position_tone1:"}, - "\U0001f9d8\U0001f3fb\u200d\u2640\ufe0f": {":woman_in_lotus_position_tone1:"}, - "\U0001f9d8\U0001f3fb\u200d\u2642\ufe0f": {":man_in_lotus_position_tone1:"}, - "\U0001f9d8\U0001f3fc": {":person_in_lotus_position_tone2:"}, - "\U0001f9d8\U0001f3fc\u200d\u2640\ufe0f": {":woman_in_lotus_position_tone2:"}, - "\U0001f9d8\U0001f3fc\u200d\u2642\ufe0f": {":man_in_lotus_position_tone2:"}, - "\U0001f9d8\U0001f3fd": {":person_in_lotus_position_tone3:"}, - "\U0001f9d8\U0001f3fd\u200d\u2640\ufe0f": {":woman_in_lotus_position_tone3:"}, - "\U0001f9d8\U0001f3fd\u200d\u2642\ufe0f": {":man_in_lotus_position_tone3:"}, - "\U0001f9d8\U0001f3fe": {":person_in_lotus_position_tone4:"}, - "\U0001f9d8\U0001f3fe\u200d\u2640\ufe0f": {":woman_in_lotus_position_tone4:"}, - "\U0001f9d8\U0001f3fe\u200d\u2642\ufe0f": {":man_in_lotus_position_tone4:"}, - "\U0001f9d8\U0001f3ff": {":person_in_lotus_position_tone5:"}, - "\U0001f9d8\U0001f3ff\u200d\u2640\ufe0f": {":woman_in_lotus_position_tone5:"}, - "\U0001f9d8\U0001f3ff\u200d\u2642\ufe0f": {":man_in_lotus_position_tone5:"}, - "\U0001f9d8\u200d\u2640\ufe0f": {":lotus_position_woman:", ":woman_in_lotus_position:", ":person_in_lotus_position:"}, - "\U0001f9d8\u200d\u2642\ufe0f": {":lotus_position_man:", ":man_in_lotus_position:"}, - "\U0001f9d9\U0001f3fb": {":mage_tone1:"}, - "\U0001f9d9\U0001f3fb\u200d\u2640\ufe0f": {":woman_mage_tone1:"}, - "\U0001f9d9\U0001f3fb\u200d\u2642\ufe0f": {":man_mage_tone1:"}, - "\U0001f9d9\U0001f3fc": {":mage_tone2:"}, - "\U0001f9d9\U0001f3fc\u200d\u2640\ufe0f": {":woman_mage_tone2:"}, - "\U0001f9d9\U0001f3fc\u200d\u2642\ufe0f": {":man_mage_tone2:"}, - "\U0001f9d9\U0001f3fd": {":mage_tone3:"}, - "\U0001f9d9\U0001f3fd\u200d\u2640\ufe0f": {":woman_mage_tone3:"}, - "\U0001f9d9\U0001f3fd\u200d\u2642\ufe0f": {":man_mage_tone3:"}, - "\U0001f9d9\U0001f3fe": {":mage_tone4:"}, - "\U0001f9d9\U0001f3fe\u200d\u2640\ufe0f": {":woman_mage_tone4:"}, - "\U0001f9d9\U0001f3fe\u200d\u2642\ufe0f": {":man_mage_tone4:"}, - "\U0001f9d9\U0001f3ff": {":mage_tone5:"}, - "\U0001f9d9\U0001f3ff\u200d\u2640\ufe0f": {":woman_mage_tone5:"}, - "\U0001f9d9\U0001f3ff\u200d\u2642\ufe0f": {":man_mage_tone5:"}, - "\U0001f9d9\u200d\u2640\ufe0f": {":mage:", ":mage_woman:", ":woman_mage:", ":female_mage:"}, - "\U0001f9d9\u200d\u2642\ufe0f": {":mage_man:", ":man_mage:", ":male_mage:"}, - "\U0001f9da\U0001f3fb": {":fairy_tone1:"}, - "\U0001f9da\U0001f3fb\u200d\u2640\ufe0f": {":woman_fairy_tone1:"}, - "\U0001f9da\U0001f3fb\u200d\u2642\ufe0f": {":man_fairy_tone1:"}, - "\U0001f9da\U0001f3fc": {":fairy_tone2:"}, - "\U0001f9da\U0001f3fc\u200d\u2640\ufe0f": {":woman_fairy_tone2:"}, - "\U0001f9da\U0001f3fc\u200d\u2642\ufe0f": {":man_fairy_tone2:"}, - "\U0001f9da\U0001f3fd": {":fairy_tone3:"}, - "\U0001f9da\U0001f3fd\u200d\u2640\ufe0f": {":woman_fairy_tone3:"}, - "\U0001f9da\U0001f3fd\u200d\u2642\ufe0f": {":man_fairy_tone3:"}, - "\U0001f9da\U0001f3fe": {":fairy_tone4:"}, - "\U0001f9da\U0001f3fe\u200d\u2640\ufe0f": {":woman_fairy_tone4:"}, - "\U0001f9da\U0001f3fe\u200d\u2642\ufe0f": {":man_fairy_tone4:"}, - "\U0001f9da\U0001f3ff": {":fairy_tone5:"}, - "\U0001f9da\U0001f3ff\u200d\u2640\ufe0f": {":woman_fairy_tone5:"}, - "\U0001f9da\U0001f3ff\u200d\u2642\ufe0f": {":man_fairy_tone5:"}, - "\U0001f9da\u200d\u2640\ufe0f": {":fairy:", ":fairy_woman:", ":woman_fairy:", ":female_fairy:"}, - "\U0001f9da\u200d\u2642\ufe0f": {":fairy_man:", ":man_fairy:", ":male_fairy:"}, - "\U0001f9db\U0001f3fb": {":vampire_tone1:"}, - "\U0001f9db\U0001f3fb\u200d\u2640\ufe0f": {":woman_vampire_tone1:"}, - "\U0001f9db\U0001f3fb\u200d\u2642\ufe0f": {":man_vampire_tone1:"}, - "\U0001f9db\U0001f3fc": {":vampire_tone2:"}, - "\U0001f9db\U0001f3fc\u200d\u2640\ufe0f": {":woman_vampire_tone2:"}, - "\U0001f9db\U0001f3fc\u200d\u2642\ufe0f": {":man_vampire_tone2:"}, - "\U0001f9db\U0001f3fd": {":vampire_tone3:"}, - "\U0001f9db\U0001f3fd\u200d\u2640\ufe0f": {":woman_vampire_tone3:"}, - "\U0001f9db\U0001f3fd\u200d\u2642\ufe0f": {":man_vampire_tone3:"}, - "\U0001f9db\U0001f3fe": {":vampire_tone4:"}, - "\U0001f9db\U0001f3fe\u200d\u2640\ufe0f": {":woman_vampire_tone4:"}, - "\U0001f9db\U0001f3fe\u200d\u2642\ufe0f": {":man_vampire_tone4:"}, - "\U0001f9db\U0001f3ff": {":vampire_tone5:"}, - "\U0001f9db\U0001f3ff\u200d\u2640\ufe0f": {":woman_vampire_tone5:"}, - "\U0001f9db\U0001f3ff\u200d\u2642\ufe0f": {":man_vampire_tone5:"}, - "\U0001f9db\u200d\u2640\ufe0f": {":vampire:", ":vampire_woman:", ":woman_vampire:", ":female_vampire:"}, - "\U0001f9db\u200d\u2642\ufe0f": {":man_vampire:", ":vampire_man:", ":male_vampire:"}, - "\U0001f9dc\U0001f3fb": {":merperson_tone1:"}, - "\U0001f9dc\U0001f3fb\u200d\u2640\ufe0f": {":mermaid_tone1:"}, - "\U0001f9dc\U0001f3fb\u200d\u2642\ufe0f": {":merman_tone1:"}, - "\U0001f9dc\U0001f3fc": {":merperson_tone2:"}, - "\U0001f9dc\U0001f3fc\u200d\u2640\ufe0f": {":mermaid_tone2:"}, - "\U0001f9dc\U0001f3fc\u200d\u2642\ufe0f": {":merman_tone2:"}, - "\U0001f9dc\U0001f3fd": {":merperson_tone3:"}, - "\U0001f9dc\U0001f3fd\u200d\u2640\ufe0f": {":mermaid_tone3:"}, - "\U0001f9dc\U0001f3fd\u200d\u2642\ufe0f": {":merman_tone3:"}, - "\U0001f9dc\U0001f3fe": {":merperson_tone4:"}, - "\U0001f9dc\U0001f3fe\u200d\u2640\ufe0f": {":mermaid_tone4:"}, - "\U0001f9dc\U0001f3fe\u200d\u2642\ufe0f": {":merman_tone4:"}, - "\U0001f9dc\U0001f3ff": {":merperson_tone5:"}, - "\U0001f9dc\U0001f3ff\u200d\u2640\ufe0f": {":mermaid_tone5:"}, - "\U0001f9dc\U0001f3ff\u200d\u2642\ufe0f": {":merman_tone5:"}, - "\U0001f9dc\u200d\u2640\ufe0f": {":mermaid:"}, - "\U0001f9dc\u200d\u2642\ufe0f": {":merman:", ":merperson:"}, - "\U0001f9dd\U0001f3fb": {":elf_tone1:"}, - "\U0001f9dd\U0001f3fb\u200d\u2640\ufe0f": {":woman_elf_tone1:"}, - "\U0001f9dd\U0001f3fb\u200d\u2642\ufe0f": {":man_elf_tone1:"}, - "\U0001f9dd\U0001f3fc": {":elf_tone2:"}, - "\U0001f9dd\U0001f3fc\u200d\u2640\ufe0f": {":woman_elf_tone2:"}, - "\U0001f9dd\U0001f3fc\u200d\u2642\ufe0f": {":man_elf_tone2:"}, - "\U0001f9dd\U0001f3fd": {":elf_tone3:"}, - "\U0001f9dd\U0001f3fd\u200d\u2640\ufe0f": {":woman_elf_tone3:"}, - "\U0001f9dd\U0001f3fd\u200d\u2642\ufe0f": {":man_elf_tone3:"}, - "\U0001f9dd\U0001f3fe": {":elf_tone4:"}, - "\U0001f9dd\U0001f3fe\u200d\u2640\ufe0f": {":woman_elf_tone4:"}, - "\U0001f9dd\U0001f3fe\u200d\u2642\ufe0f": {":man_elf_tone4:"}, - "\U0001f9dd\U0001f3ff": {":elf_tone5:"}, - "\U0001f9dd\U0001f3ff\u200d\u2640\ufe0f": {":woman_elf_tone5:"}, - "\U0001f9dd\U0001f3ff\u200d\u2642\ufe0f": {":man_elf_tone5:"}, - "\U0001f9dd\u200d\u2640\ufe0f": {":elf_woman:", ":woman_elf:", ":female_elf:"}, - "\U0001f9dd\u200d\u2642\ufe0f": {":elf:", ":elf_man:", ":man_elf:", ":male_elf:"}, - "\U0001f9de\u200d\u2640\ufe0f": {":genie_woman:", ":woman_genie:", ":female_genie:"}, - "\U0001f9de\u200d\u2642\ufe0f": {":genie:", ":genie_man:", ":man_genie:", ":male_genie:"}, - "\U0001f9df\u200d\u2640\ufe0f": {":woman_zombie:", ":zombie_woman:", ":female_zombie:"}, - "\U0001f9df\u200d\u2642\ufe0f": {":zombie:", ":man_zombie:", ":zombie_man:", ":male_zombie:"}, - "\U0001f9e0": {":brain:"}, - "\U0001f9e1": {":orange_heart:"}, - "\U0001f9e2": {":billed_cap:"}, - "\U0001f9e3": {":scarf:"}, - "\U0001f9e4": {":gloves:"}, - "\U0001f9e5": {":coat:"}, - "\U0001f9e6": {":socks:"}, - "\U0001f9e7": {":red_envelope:"}, - "\U0001f9e8": {":firecracker:"}, - "\U0001f9e9": {":jigsaw:", ":puzzle_piece:"}, - "\U0001f9ea": {":test_tube:"}, - "\U0001f9eb": {":petri_dish:"}, - "\U0001f9ec": {":dna:"}, - "\U0001f9ed": {":compass:"}, - "\U0001f9ee": {":abacus:"}, - "\U0001f9ef": {":fire_extinguisher:"}, - "\U0001f9f0": {":toolbox:"}, - "\U0001f9f1": {":brick:", ":bricks:"}, - "\U0001f9f2": {":magnet:"}, - "\U0001f9f3": {":luggage:"}, - "\U0001f9f4": {":lotion_bottle:"}, - "\U0001f9f5": {":thread:"}, - "\U0001f9f6": {":yarn:"}, - "\U0001f9f7": {":safety_pin:"}, - "\U0001f9f8": {":teddy_bear:"}, - "\U0001f9f9": {":broom:"}, - "\U0001f9fa": {":basket:"}, - "\U0001f9fb": {":roll_of_paper:"}, - "\U0001f9fc": {":soap:"}, - "\U0001f9fd": {":sponge:"}, - "\U0001f9fe": {":receipt:"}, - "\U0001f9ff": {":nazar_amulet:"}, - "\U0001fa70": {":ballet_shoes:"}, - "\U0001fa71": {":one-piece_swimsuit:", ":one_piece_swimsuit:"}, - "\U0001fa72": {":briefs:", ":swim_brief:"}, - "\U0001fa73": {":shorts:"}, - "\U0001fa74": {":thong_sandal:"}, - "\U0001fa78": {":drop_of_blood:"}, - "\U0001fa79": {":adhesive_bandage:"}, - "\U0001fa7a": {":stethoscope:"}, - "\U0001fa80": {":yo-yo:", ":yo_yo:"}, - "\U0001fa81": {":kite:"}, - "\U0001fa82": {":parachute:"}, - "\U0001fa83": {":boomerang:"}, - "\U0001fa84": {":magic_wand:"}, - "\U0001fa85": {":pinata:", ":piñata:"}, - "\U0001fa86": {":nesting_dolls:"}, - "\U0001fa90": {":ringed_planet:"}, - "\U0001fa91": {":chair:"}, - "\U0001fa92": {":razor:"}, - "\U0001fa93": {":axe:"}, - "\U0001fa94": {":diya_lamp:"}, - "\U0001fa95": {":banjo:"}, - "\U0001fa96": {":military_helmet:"}, - "\U0001fa97": {":accordion:"}, - "\U0001fa98": {":long_drum:"}, - "\U0001fa99": {":coin:"}, - "\U0001fa9a": {":carpentry_saw:"}, - "\U0001fa9b": {":screwdriver:"}, - "\U0001fa9c": {":ladder:"}, - "\U0001fa9d": {":hook:"}, - "\U0001fa9e": {":mirror:"}, - "\U0001fa9f": {":window:"}, - "\U0001faa0": {":plunger:"}, - "\U0001faa1": {":sewing_needle:"}, - "\U0001faa2": {":knot:"}, - "\U0001faa3": {":bucket:"}, - "\U0001faa4": {":mouse_trap:"}, - "\U0001faa5": {":toothbrush:"}, - "\U0001faa6": {":headstone:"}, - "\U0001faa7": {":placard:"}, - "\U0001faa8": {":rock:"}, - "\U0001fab0": {":fly:"}, - "\U0001fab1": {":worm:"}, - "\U0001fab2": {":beetle:"}, - "\U0001fab3": {":cockroach:"}, - "\U0001fab4": {":potted_plant:"}, - "\U0001fab5": {":wood:"}, - "\U0001fab6": {":feather:"}, - "\U0001fac0": {":anatomical_heart:"}, - "\U0001fac1": {":lungs:"}, - "\U0001fac2": {":people_hugging:"}, - "\U0001fad0": {":blueberries:"}, - "\U0001fad1": {":bell_pepper:"}, - "\U0001fad2": {":olive:"}, - "\U0001fad3": {":flatbread:"}, - "\U0001fad4": {":tamale:"}, - "\U0001fad5": {":fondue:"}, - "\U0001fad6": {":teapot:"}, - "\u00a9\ufe0f": {":copyright:"}, - "\u00ae\ufe0f": {":registered:"}, - "\u203c": {":double_exclamation_mark:"}, - "\u203c\ufe0f": {":bangbang:"}, - "\u2049": {":exclamation_question_mark:"}, - "\u2049\ufe0f": {":interrobang:"}, - "\u2122": {":trade_mark:"}, - "\u2122\ufe0f": {":tm:"}, - "\u2139": {":information:"}, - "\u2139\ufe0f": {":information_source:"}, - "\u2194": {":left-right_arrow:"}, - "\u2194\ufe0f": {":left_right_arrow:"}, - "\u2195": {":up-down_arrow:"}, - "\u2195\ufe0f": {":arrow_up_down:"}, - "\u2196": {":up-left_arrow:"}, - "\u2196\ufe0f": {":arrow_upper_left:"}, - "\u2197": {":up-right_arrow:"}, - "\u2197\ufe0f": {":arrow_upper_right:"}, - "\u2198": {":down-right_arrow:"}, - "\u2198\ufe0f": {":arrow_lower_right:"}, - "\u2199": {":down-left_arrow:"}, - "\u2199\ufe0f": {":arrow_lower_left:"}, - "\u21a9": {":right_arrow_curving_left:"}, - "\u21a9\ufe0f": {":leftwards_arrow_with_hook:"}, - "\u21aa": {":left_arrow_curving_right:"}, - "\u21aa\ufe0f": {":arrow_right_hook:"}, - "\u231a": {":watch:"}, - "\u231b": {":hourglass:", ":hourglass_done:"}, - "\u2328\ufe0f": {":keyboard:"}, - "\u23cf": {":eject_button:"}, - "\u23cf\ufe0f": {":eject:"}, - "\u23e9": {":fast_forward:", ":fast-forward_button:"}, - "\u23ea": {":rewind:", ":fast_reverse_button:"}, - "\u23eb": {":fast_up_button:", ":arrow_double_up:"}, - "\u23ec": {":fast_down_button:", ":arrow_double_down:"}, - "\u23ed": {":track_next:", ":next_track_button:"}, - "\u23ed\ufe0f": {":black_right_pointing_double_triangle_with_vertical_bar:"}, - "\u23ee": {":track_previous:", ":last_track_button:"}, - "\u23ee\ufe0f": {":previous_track_button:", ":black_left_pointing_double_triangle_with_vertical_bar:"}, - "\u23ef": {":play_pause:", ":play_or_pause_button:"}, - "\u23ef\ufe0f": {":black_right_pointing_triangle_with_double_vertical_bar:"}, - "\u23f0": {":alarm_clock:"}, - "\u23f1\ufe0f": {":stopwatch:"}, - "\u23f2": {":timer:"}, - "\u23f2\ufe0f": {":timer_clock:"}, - "\u23f3": {":hourglass_not_done:", ":hourglass_flowing_sand:"}, - "\u23f8": {":pause_button:"}, - "\u23f8\ufe0f": {":double_vertical_bar:"}, - "\u23f9": {":stop_button:"}, - "\u23f9\ufe0f": {":black_square_for_stop:"}, - "\u23fa": {":record_button:"}, - "\u23fa\ufe0f": {":black_circle_for_record:"}, - "\u24c2": {":circled_M:"}, - "\u24dc\ufe0f": {":m:"}, - "\u25aa\ufe0f": {":black_small_square:"}, - "\u25ab\ufe0f": {":white_small_square:"}, - "\u25b6": {":play_button:"}, - "\u25b6\ufe0f": {":arrow_forward:"}, - "\u25c0": {":reverse_button:"}, - "\u25c0\ufe0f": {":arrow_backward:"}, - "\u25fb\ufe0f": {":white_medium_square:"}, - "\u25fc\ufe0f": {":black_medium_square:"}, - "\u25fd": {":white_medium-small_square:", ":white_medium_small_square:"}, - "\u25fe": {":black_medium-small_square:", ":black_medium_small_square:"}, - "\u2600": {":sun:"}, - "\u2600\ufe0f": {":sunny:"}, - "\u2601\ufe0f": {":cloud:"}, - "\u2602": {":umbrella2:"}, - "\u2602\ufe0f": {":umbrella:", ":open_umbrella:"}, - "\u2603": {":snowman2:"}, - "\u2603\ufe0f": {":snowman:", ":snowman_with_snow:"}, - "\u2604\ufe0f": {":comet:"}, - "\u260e": {":telephone:"}, - "\u260e\ufe0f": {":phone:"}, - "\u2611": {":check_box_with_check:"}, - "\u2611\ufe0f": {":ballot_box_with_check:"}, - "\u2614": {":umbrella_with_rain_drops:"}, - "\u2615": {":coffee:", ":hot_beverage:"}, - "\u2618\ufe0f": {":shamrock:"}, - "\u261d": {":index_pointing_up:"}, - "\u261d\U0001f3fb": {":point_up_tone1:"}, - "\u261d\U0001f3fc": {":point_up_tone2:"}, - "\u261d\U0001f3fd": {":point_up_tone3:"}, - "\u261d\U0001f3fe": {":point_up_tone4:"}, - "\u261d\U0001f3ff": {":point_up_tone5:"}, - "\u261d\ufe0f": {":point_up:"}, - "\u2620": {":skull_crossbones:"}, - "\u2620\ufe0f": {":skull_and_crossbones:"}, - "\u2622": {":radioactive:"}, - "\u2622\ufe0f": {":radioactive_sign:"}, - "\u2623": {":biohazard:"}, - "\u2623\ufe0f": {":biohazard_sign:"}, - "\u2626\ufe0f": {":orthodox_cross:"}, - "\u262a\ufe0f": {":star_and_crescent:"}, - "\u262e": {":peace:"}, - "\u262e\ufe0f": {":peace_symbol:"}, - "\u262f\ufe0f": {":yin_yang:"}, - "\u2638\ufe0f": {":wheel_of_dharma:"}, - "\u2639": {":frowning2:", ":frowning_face:"}, - "\u2639\ufe0f": {":white_frowning_face:"}, - "\u263a": {":smiling_face:"}, - "\u263a\ufe0f": {":relaxed:"}, - "\u2640\ufe0f": {":female_sign:"}, - "\u2642\ufe0f": {":male_sign:"}, - "\u2648": {":Aries:", ":aries:"}, - "\u2649": {":Taurus:", ":taurus:"}, - "\u264a": {":Gemini:", ":gemini:"}, - "\u264b": {":Cancer:", ":cancer:"}, - "\u264c": {":Leo:", ":leo:"}, - "\u264d": {":Virgo:", ":virgo:"}, - "\u264e": {":Libra:", ":libra:"}, - "\u264f": {":Scorpio:", ":scorpius:"}, - "\u2650": {":Sagittarius:", ":sagittarius:"}, - "\u2651": {":Capricorn:", ":capricorn:"}, - "\u2652": {":Aquarius:", ":aquarius:"}, - "\u2653": {":Pisces:", ":pisces:"}, - "\u265f\ufe0f": {":chess_pawn:"}, - "\u2660": {":spade_suit:"}, - "\u2660\ufe0f": {":spades:"}, - "\u2663": {":club_suit:"}, - "\u2663\ufe0f": {":clubs:"}, - "\u2665": {":heart_suit:"}, - "\u2665\ufe0f": {":hearts:"}, - "\u2666": {":diamond_suit:"}, - "\u2666\ufe0f": {":diamonds:"}, - "\u2668": {":hot_springs:"}, - "\u2668\ufe0f": {":hotsprings:"}, - "\u267b": {":recycling_symbol:"}, - "\u267b\ufe0f": {":recycle:"}, - "\u267e\ufe0f": {":infinity:"}, - "\u267f": {":wheelchair:", ":wheelchair_symbol:"}, - "\u2692": {":hammer_pick:"}, - "\u2692\ufe0f": {":hammer_and_pick:"}, - "\u2693": {":anchor:"}, - "\u2694\ufe0f": {":crossed_swords:"}, - "\u2695\ufe0f": {":medical_symbol:"}, - "\u2696": {":balance_scale:"}, - "\u2696\ufe0f": {":scales:"}, - "\u2697\ufe0f": {":alembic:"}, - "\u2699\ufe0f": {":gear:"}, - "\u269b": {":atom:"}, - "\u269b\ufe0f": {":atom_symbol:"}, - "\u269c": {":fleur-de-lis:"}, - "\u269c\ufe0f": {":fleur_de_lis:"}, - "\u26a0\ufe0f": {":warning:"}, - "\u26a1": {":zap:", ":high_voltage:"}, - "\u26a7\ufe0f": {":transgender_symbol:"}, - "\u26aa": {":white_circle:"}, - "\u26ab": {":black_circle:"}, - "\u26b0\ufe0f": {":coffin:"}, - "\u26b1": {":urn:"}, - "\u26b1\ufe0f": {":funeral_urn:"}, - "\u26bd": {":soccer:", ":soccer_ball:"}, - "\u26be": {":baseball:"}, - "\u26c4": {":snowman_without_snow:"}, - "\u26c5": {":partly_sunny:", ":sun_behind_cloud:"}, - "\u26c8": {":thunder_cloud_rain:", ":cloud_with_lightning_and_rain:"}, - "\u26c8\ufe0f": {":thunder_cloud_and_rain:"}, - "\u26ce": {":Ophiuchus:", ":ophiuchus:"}, - "\u26cf\ufe0f": {":pick:"}, - "\u26d1": {":helmet_with_cross:", ":rescue_worker’s_helmet:"}, - "\u26d1\ufe0f": {":rescue_worker_helmet:", ":helmet_with_white_cross:"}, - "\u26d3\ufe0f": {":chains:"}, - "\u26d4": {":no_entry:"}, - "\u26e9\ufe0f": {":shinto_shrine:"}, - "\u26ea": {":church:"}, - "\u26f0\ufe0f": {":mountain:"}, - "\u26f1": {":beach_umbrella:"}, - "\u26f1\ufe0f": {":parasol_on_ground:", ":umbrella_on_ground:"}, - "\u26f2": {":fountain:"}, - "\u26f3": {":golf:", ":flag_in_hole:"}, - "\u26f4\ufe0f": {":ferry:"}, - "\u26f5": {":boat:", ":sailboat:"}, - "\u26f7\ufe0f": {":skier:"}, - "\u26f8\ufe0f": {":ice_skate:"}, - "\u26f9": {":person_bouncing_ball:"}, - "\u26f9\U0001f3fb": {":person_bouncing_ball_tone1:"}, - "\u26f9\U0001f3fb\u200d\u2640\ufe0f": {":woman_bouncing_ball_tone1:"}, - "\u26f9\U0001f3fb\u200d\u2642\ufe0f": {":man_bouncing_ball_tone1:"}, - "\u26f9\U0001f3fc": {":person_bouncing_ball_tone2:"}, - "\u26f9\U0001f3fc\u200d\u2640\ufe0f": {":woman_bouncing_ball_tone2:"}, - "\u26f9\U0001f3fc\u200d\u2642\ufe0f": {":man_bouncing_ball_tone2:"}, - "\u26f9\U0001f3fd": {":person_bouncing_ball_tone3:"}, - "\u26f9\U0001f3fd\u200d\u2640\ufe0f": {":woman_bouncing_ball_tone3:"}, - "\u26f9\U0001f3fd\u200d\u2642\ufe0f": {":man_bouncing_ball_tone3:"}, - "\u26f9\U0001f3fe": {":person_bouncing_ball_tone4:"}, - "\u26f9\U0001f3fe\u200d\u2640\ufe0f": {":woman_bouncing_ball_tone4:"}, - "\u26f9\U0001f3fe\u200d\u2642\ufe0f": {":man_bouncing_ball_tone4:"}, - "\u26f9\U0001f3ff": {":person_bouncing_ball_tone5:"}, - "\u26f9\U0001f3ff\u200d\u2640\ufe0f": {":woman_bouncing_ball_tone5:"}, - "\u26f9\U0001f3ff\u200d\u2642\ufe0f": {":man_bouncing_ball_tone5:"}, - "\u26f9\ufe0f": {":bouncing_ball_person:"}, - "\u26f9\ufe0f\u200d\u2640\ufe0f": {":basketball_woman:", ":bouncing_ball_woman:", ":woman-bouncing-ball:", ":woman_bouncing_ball:"}, - "\u26f9\ufe0f\u200d\u2642\ufe0f": {":basketball_man:", ":person_with_ball:", ":bouncing_ball_man:", ":man-bouncing-ball:", ":man_bouncing_ball:"}, - "\u26fa": {":tent:"}, - "\u26fd": {":fuelpump:", ":fuel_pump:"}, - "\u2702\ufe0f": {":scissors:"}, - "\u2705": {":white_check_mark:", ":check_mark_button:"}, - "\u2708\ufe0f": {":airplane:"}, - "\u2709": {":envelope:"}, - "\u2709\ufe0f": {":email:"}, - "\u270a": {":fist:", ":fist_raised:", ":raised_fist:"}, - "\u270a\U0001f3fb": {":fist_tone1:"}, - "\u270a\U0001f3fc": {":fist_tone2:"}, - "\u270a\U0001f3fd": {":fist_tone3:"}, - "\u270a\U0001f3fe": {":fist_tone4:"}, - "\u270a\U0001f3ff": {":fist_tone5:"}, - "\u270b": {":hand:", ":raised_hand:"}, - "\u270b\U0001f3fb": {":raised_hand_tone1:"}, - "\u270b\U0001f3fc": {":raised_hand_tone2:"}, - "\u270b\U0001f3fd": {":raised_hand_tone3:"}, - "\u270b\U0001f3fe": {":raised_hand_tone4:"}, - "\u270b\U0001f3ff": {":raised_hand_tone5:"}, - "\u270c": {":victory_hand:"}, - "\u270c\U0001f3fb": {":v_tone1:"}, - "\u270c\U0001f3fc": {":v_tone2:"}, - "\u270c\U0001f3fd": {":v_tone3:"}, - "\u270c\U0001f3fe": {":v_tone4:"}, - "\u270c\U0001f3ff": {":v_tone5:"}, - "\u270c\ufe0f": {":v:"}, - "\u270d\U0001f3fb": {":writing_hand_tone1:"}, - "\u270d\U0001f3fc": {":writing_hand_tone2:"}, - "\u270d\U0001f3fd": {":writing_hand_tone3:"}, - "\u270d\U0001f3fe": {":writing_hand_tone4:"}, - "\u270d\U0001f3ff": {":writing_hand_tone5:"}, - "\u270d\ufe0f": {":writing_hand:"}, - "\u270f": {":pencil:"}, - "\u270f\ufe0f": {":pencil2:"}, - "\u2712\ufe0f": {":black_nib:"}, - "\u2714": {":check_mark:"}, - "\u2714\ufe0f": {":heavy_check_mark:"}, - "\u2716": {":multiply:"}, - "\u2716\ufe0f": {":heavy_multiplication_x:"}, - "\u271d": {":cross:"}, - "\u271d\ufe0f": {":latin_cross:"}, - "\u2721": {":star_of_David:"}, - "\u2721\ufe0f": {":star_of_david:"}, - "\u2728": {":sparkles:"}, - "\u2733": {":eight-spoked_asterisk:"}, - "\u2733\ufe0f": {":eight_spoked_asterisk:"}, - "\u2734": {":eight-pointed_star:"}, - "\u2734\ufe0f": {":eight_pointed_black_star:"}, - "\u2744\ufe0f": {":snowflake:"}, - "\u2747\ufe0f": {":sparkle:"}, - "\u274c": {":x:", ":cross_mark:"}, - "\u274e": {":cross_mark_button:", ":negative_squared_cross_mark:"}, - "\u2753": {":question:", ":red_question_mark:"}, - "\u2754": {":grey_question:", ":white_question_mark:"}, - "\u2755": {":grey_exclamation:", ":white_exclamation_mark:"}, - "\u2757": {":exclamation:", ":red_exclamation_mark:", ":heavy_exclamation_mark:"}, - "\u2763": {":heart_exclamation:"}, - "\u2763\ufe0f": {":heavy_heart_exclamation:", ":heavy_heart_exclamation_mark_ornament:"}, - "\u2764": {":red_heart:"}, - "\u2764\ufe0f": {":heart:"}, - "\u2764\ufe0f\u200d\U0001f525": {":heart_on_fire:"}, - "\u2764\ufe0f\u200d\U0001fa79": {":mending_heart:"}, - "\u2795": {":plus:", ":heavy_plus_sign:"}, - "\u2796": {":minus:", ":heavy_minus_sign:"}, - "\u2797": {":divide:", ":heavy_division_sign:"}, - "\u27a1": {":right_arrow:"}, - "\u27a1\ufe0f": {":arrow_right:"}, - "\u27b0": {":curly_loop:"}, - "\u27bf": {":loop:", ":double_curly_loop:"}, - "\u2934": {":right_arrow_curving_up:"}, - "\u2934\ufe0f": {":arrow_heading_up:"}, - "\u2935": {":right_arrow_curving_down:"}, - "\u2935\ufe0f": {":arrow_heading_down:"}, - "\u2b05": {":left_arrow:"}, - "\u2b05\ufe0f": {":arrow_left:"}, - "\u2b06": {":up_arrow:"}, - "\u2b06\ufe0f": {":arrow_up:"}, - "\u2b07": {":down_arrow:"}, - "\u2b07\ufe0f": {":arrow_down:"}, - "\u2b1b": {":black_large_square:"}, - "\u2b1c": {":white_large_square:"}, - "\u2b50": {":star:"}, - "\u2b55": {":o:", ":hollow_red_circle:"}, - "\u3030\ufe0f": {":wavy_dash:"}, - "\u303d\ufe0f": {":part_alternation_mark:"}, - "\u3297": {":Japanese_congratulations_button:"}, - "\u3297\ufe0f": {":congratulations:"}, - "\u3299": {":Japanese_secret_button:"}, - "\u3299\ufe0f": {":secret:"}, - } - }) - return emojiRevCodeMap -} diff --git a/vendor/github.com/kyokomi/emoji/v2/wercker.yml b/vendor/github.com/kyokomi/emoji/v2/wercker.yml deleted file mode 100644 index 2c4a6930afd..00000000000 --- a/vendor/github.com/kyokomi/emoji/v2/wercker.yml +++ /dev/null @@ -1,33 +0,0 @@ -box: golang -build: - steps: - - setup-go-workspace - - script: - name: go version - code: go version - - script: - name: install tools - code: | - go get github.com/mattn/goveralls - GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint - - script: - name: go get - code: | - go get ./... - - script: - name: go build - code: | - go build ./... - - script: - name: golangci-lint - code: | - golangci-lint run - - script: - name: go test - code: | - go test ./... - - script: - name: coveralls - code: | - goveralls -v -service wercker.com -repotoken $COVERALLS_TOKEN - diff --git a/vendor/github.com/lucasb-eyer/go-colorful/.gitignore b/vendor/github.com/lucasb-eyer/go-colorful/.gitignore deleted file mode 100644 index 0aa2c922818..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/.gitignore +++ /dev/null @@ -1,101 +0,0 @@ -# Created by https://www.toptal.com/developers/gitignore/api/code,go,linux,macos,windows -# Edit at https://www.toptal.com/developers/gitignore?templates=code,go,linux,macos,windows - -### Code ### -.vscode/* -!.vscode/tasks.json -!.vscode/launch.json -*.code-workspace - -### Go ### -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -### Go Patch ### -/vendor/ -/Godeps/ - -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# End of https://www.toptal.com/developers/gitignore/api/code,go,linux,macos,windows diff --git a/vendor/github.com/lucasb-eyer/go-colorful/CHANGELOG.md b/vendor/github.com/lucasb-eyer/go-colorful/CHANGELOG.md deleted file mode 100644 index a10d3fc8dce..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/CHANGELOG.md +++ /dev/null @@ -1,66 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -The format of this file is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -but only releases after v1.0.3 properly adhere to it. - -## [Unreleased] - -## [1.3.0] - 2025-09-08 -### Added -- `BlendLinearRgb` (#50) -- `DistanceRiemersma` (#52) -- Introduce a function for sorting colors (#57) -- YAML marshal/unmarshal support (#63) -- Add support for OkLab and OkLch (#66) -- Functions that use randomness now support specifying a custom source (#73) -- Functions BlendOkLab and BlendOkLch (#70) - -## Changed -- `Hex()` parsing is much faster (#78) - -### Fixed -- Fix bug when doing HSV/HCL blending between a gray color and non-gray color (#60) -- Docs for HSV/HSL were updated to note that hue 360 is not allowed (#71) - -### Deprecated -- `DistanceLinearRGB` is deprecated for the name `DistanceLinearRgb` which is more in-line with the rest of the library - - -## [1.2.0] - 2021-01-27 -This is the same as the v1.1.0 tag. - -### Added -- HSLuv and HPLuv color spaces (#41, #51) -- CIE LCh(uv) color space, called `LuvLCh` in code (#51) -- JSON and envconfig serialization support for `HexColor` (#42) -- `DistanceLinearRGB` (#53) - -### Fixed -- RGB to/from XYZ conversion is more accurate (#51) -- A bug in `XYZToLuvWhiteRef` that only applied to very small values was fixed (#51) -- `BlendHCL` output is clamped so that it's not invalid (#46) -- Properly documented `DistanceCIE76` (#40) -- Some small godoc fixes - - -## [1.0.3] - 2019-11-11 -- Remove SQLMock dependency - - -## [1.0.2] - 2019-04-07 -- Fixes SQLMock dependency - - -## [1.0.1] - 2019-03-24 -- Adds support for Go Modules - - -## [1.0.0] - 2018-05-26 -- API Breaking change in `MakeColor`: instead of `panic`ing when alpha is zero, it now returns a secondary, boolean return value indicating success. See [the color.Color interface](#the-colorcolor-interface) section and [this FAQ entry](#q-why-would-makecolor-ever-fail) for details. - - -## [0.9.0] - 2018-05-26 -- Initial version number after having ignored versioning for a long time :) diff --git a/vendor/github.com/lucasb-eyer/go-colorful/LICENSE b/vendor/github.com/lucasb-eyer/go-colorful/LICENSE deleted file mode 100644 index 4e402a00e52..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2013 Lucas Beyer - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/lucasb-eyer/go-colorful/README.md b/vendor/github.com/lucasb-eyer/go-colorful/README.md deleted file mode 100644 index b3bb545cf64..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/README.md +++ /dev/null @@ -1,483 +0,0 @@ -go-colorful -=========== - -[![Go Reference](https://pkg.go.dev/badge/github.com/lucasb-eyer/go-colorful.svg)](https://pkg.go.dev/github.com/lucasb-eyer/go-colorful) -[![go reportcard](https://goreportcard.com/badge/github.com/lucasb-eyer/go-colorful)](https://goreportcard.com/report/github.com/lucasb-eyer/go-colorful) - -A library for playing with colors in Go. Supports Go 1.13 onwards. - -Why? -==== -I love games. I make games. I love detail and I get lost in detail. -One such detail popped up during the development of [Memory Which Does Not Suck](https://github.com/lucasb-eyer/mwdns/), -when we wanted the server to assign the players random colors. Sometimes -two players got very similar colors, which bugged me. The very same evening, -[I want hue](http://tools.medialab.sciences-po.fr/iwanthue/) was the top post -on HackerNews' frontpage and showed me how to Do It Right™. Last but not -least, there was no library for handling color spaces available in go. Colorful -does just that and implements Go's `color.Color` interface. - -What? -===== -Go-Colorful stores colors in RGB and provides methods from converting these to various color-spaces. Currently supported colorspaces are: - -- **RGB:** All three of Red, Green and Blue in [0..1]. -- **HSL:** Hue in [0..360], Saturation and Luminance in [0..1]. For legacy reasons; please forget that it exists. -- **HSV:** Hue in [0..360], Saturation and Value in [0..1]. You're better off using HCL, see below. -- **Hex RGB:** The "internet" color format, as in #FF00FF. -- **Linear RGB:** See [gamma correct rendering](http://www.sjbrown.co.uk/2004/05/14/gamma-correct-rendering/). -- **CIE-XYZ:** CIE's standard color space, almost in [0..1]. -- **CIE-xyY:** encodes chromacity in x and y and luminance in Y, all in [0..1] -- **CIE-L\*a\*b\*:** A *perceptually uniform* color space, i.e. distances are meaningful. L\* in [0..1] and a\*, b\* almost in [-1..1]. -- **CIE-L\*u\*v\*:** Very similar to CIE-L\*a\*b\*, there is [no consensus](http://en.wikipedia.org/wiki/CIELUV#Historical_background) on which one is "better". -- **CIE-L\*C\*h° (HCL):** This is generally the [most useful](http://vis4.net/blog/posts/avoid-equidistant-hsv-colors/) one; CIE-L\*a\*b\* space in polar coordinates, i.e. a *better* HSV. H° is in [0..360], C\* almost in [0..1] and L\* as in CIE-L\*a\*b\*. -- **CIE LCh(uv):** Called `LuvLCh` in code, this is a cylindrical transformation of the CIE-L\*u\*v\* color space. Like HCL above: H° is in [0..360], C\* almost in [0..1] and L\* as in CIE-L\*u\*v\*. -- **HSLuv:** The better alternative to HSL, see [here](https://www.hsluv.org/) and [here](https://www.kuon.ch/post/2020-03-08-hsluv/). Hue in [0..360], Saturation and Luminance in [0..1]. -- **HPLuv:** A variant of HSLuv. The color space is smoother, but only pastel colors can be included. Because the valid colors are limited, it's easy to get invalid Saturation values way above 1.0, indicating the color can't be represented in HPLuv because it's not pastel. - -For the colorspaces where it makes sense (XYZ, Lab, Luv, HCl), the -[D65](http://en.wikipedia.org/wiki/Illuminant_D65) is used as reference white -by default but methods for using your own reference white are provided. - -A coordinate being *almost in* a range means that generally it is, but for very -bright colors and depending on the reference white, it might overflow this -range slightly. For example, C\* of #0000ff is 1.338. - -Unit-tests are provided. - -Nice, but what's it useful for? -------------------------------- - -- Converting color spaces. Some people like to do that. -- Blending (interpolating) between colors in a "natural" look by using the right colorspace. -- Generating random colors under some constraints (e.g. colors of the same shade, or shades of one color.) -- Generating gorgeous random palettes with distinct colors of a same temperature. - -So which colorspace should I use? -================================= -It depends on what you want to do. I think the folks from *I want hue* are -on-spot when they say that RGB fits to how *screens produce* color, CIE L\*a\*b\* -fits how *humans perceive* color and HCL fits how *humans think* colors. - -Whenever you'd use HSV, rather go for CIE-L\*C\*h°. for fixed lightness L\* and -chroma C\* values, the hue angle h° rotates through colors of the same -perceived brightness and intensity. - -How? -==== - -### Installing -Installing the library is as easy as - -```bash -$ go get github.com/lucasb-eyer/go-colorful -``` - -The package can then be used through an - -```go -import "github.com/lucasb-eyer/go-colorful" -``` - -### Basic usage - -Create a beautiful blue color using different source space: - -```go -// Any of the following should be the same -c := colorful.Color{0.313725, 0.478431, 0.721569} -c, err := colorful.Hex("#517AB8") -if err != nil { - log.Fatal(err) -} -c = colorful.Hsv(216.0, 0.56, 0.722) -c = colorful.Xyz(0.189165, 0.190837, 0.480248) -c = colorful.Xyy(0.219895, 0.221839, 0.190837) -c = colorful.Lab(0.507850, 0.040585,-0.370945) -c = colorful.Luv(0.507849,-0.194172,-0.567924) -c = colorful.Hcl(276.2440, 0.373160, 0.507849) -fmt.Printf("RGB values: %v, %v, %v", c.R, c.G, c.B) -``` - -And then converting this color back into various color spaces: - -```go -hex := c.Hex() -h, s, v := c.Hsv() -x, y, z := c.Xyz() -x, y, Y := c.Xyy() -l, a, b := c.Lab() -l, u, v := c.Luv() -h, c, l := c.Hcl() -``` - -Note that, because of Go's unfortunate choice of requiring an initial uppercase, -the name of the functions relating to the xyY space are just off. If you have -any good suggestion, please open an issue. (I don't consider XyY good.) - -### The `color.Color` interface -Because a `colorful.Color` implements Go's `color.Color` interface (found in the -`image/color` package), it can be used anywhere that expects a `color.Color`. - -Furthermore, you can convert anything that implements the `color.Color` interface -into a `colorful.Color` using the `MakeColor` function: - -```go -c, ok := colorful.MakeColor(color.Gray16{12345}) -``` - -**Caveat:** Be aware that this latter conversion (using `MakeColor`) hits a -corner-case when alpha is exactly zero. Because `color.Color` uses pre-multiplied -alpha colors, this means the RGB values are lost (set to 0) and it's impossible -to recover them. In such a case `MakeColor` will return `false` as its second value. - -### Comparing colors -In the RGB color space, the Euclidean distance between colors *doesn't* correspond -to visual/perceptual distance. This means that two pairs of colors which have the -same distance in RGB space can look much further apart. This is fixed by the -CIE-L\*a\*b\*, CIE-L\*u\*v\* and CIE-L\*C\*h° color spaces. -Thus you should only compare colors in any of these space. -(Note that the distance in CIE-L\*a\*b\* and CIE-L\*C\*h° are the same, since it's the same space but in cylindrical coordinates) - -![Color distance comparison](doc/colordist/colordist.png) - -The two colors shown on the top look much more different than the two shown on -the bottom. Still, in RGB space, their distance is the same. -Here is a little example program which shows the distances between the top two -and bottom two colors in RGB, CIE-L\*a\*b\* and CIE-L\*u\*v\* space. You can find it in `doc/colordist/colordist.go`. - -```go -package main - -import "fmt" -import "github.com/lucasb-eyer/go-colorful" - -func main() { - c1a := colorful.Color{150.0 / 255.0, 10.0 / 255.0, 150.0 / 255.0} - c1b := colorful.Color{53.0 / 255.0, 10.0 / 255.0, 150.0 / 255.0} - c2a := colorful.Color{10.0 / 255.0, 150.0 / 255.0, 50.0 / 255.0} - c2b := colorful.Color{99.9 / 255.0, 150.0 / 255.0, 10.0 / 255.0} - - fmt.Printf("DistanceRgb: c1: %v\tand c2: %v\n", c1a.DistanceRgb(c1b), c2a.DistanceRgb(c2b)) - fmt.Printf("DistanceLab: c1: %v\tand c2: %v\n", c1a.DistanceLab(c1b), c2a.DistanceLab(c2b)) - fmt.Printf("DistanceLuv: c1: %v\tand c2: %v\n", c1a.DistanceLuv(c1b), c2a.DistanceLuv(c2b)) - fmt.Printf("DistanceCIE76: c1: %v\tand c2: %v\n", c1a.DistanceCIE76(c1b), c2a.DistanceCIE76(c2b)) - fmt.Printf("DistanceCIE94: c1: %v\tand c2: %v\n", c1a.DistanceCIE94(c1b), c2a.DistanceCIE94(c2b)) - fmt.Printf("DistanceCIEDE2000: c1: %v\tand c2: %v\n", c1a.DistanceCIEDE2000(c1b), c2a.DistanceCIEDE2000(c2b)) -} -``` - -Running the above program shows that you should always prefer any of the CIE distances: - -```bash -$ go run colordist.go -DistanceRgb: c1: 0.3803921568627451 and c2: 0.3858713931171159 -DistanceLab: c1: 0.32048458312798056 and c2: 0.24397151758565272 -DistanceLuv: c1: 0.5134369614199698 and c2: 0.2568692839860636 -DistanceCIE76: c1: 0.32048458312798056 and c2: 0.24397151758565272 -DistanceCIE94: c1: 0.19799168128511324 and c2: 0.12207136371167401 -DistanceCIEDE2000: c1: 0.17274551120971166 and c2: 0.10665210031428465 -``` - -It also shows that `DistanceLab` is more formally known as `DistanceCIE76` and -has been superseded by the slightly more accurate, but much more expensive -`DistanceCIE94` and `DistanceCIEDE2000`. - -Note that `AlmostEqualRgb` is provided mainly for (unit-)testing purposes. Use -it only if you really know what you're doing. It will eat your cat. - -### Blending colors -Blending is highly connected to distance, since it basically "walks through" the -colorspace thus, if the colorspace maps distances well, the walk is "smooth". - -Colorful comes with blending functions in RGB, HSV and any of the LAB spaces. -Of course, you'd rather want to use the blending functions of the LAB spaces since -these spaces map distances well but, just in case, here is an example showing -you how the blendings (`#fdffcc` to `#242a42`) are done in the various spaces: - -![Blending colors in different spaces.](doc/colorblend/colorblend.png) - -What you see is that HSV is really bad: it adds some green, which is not present -in the original colors at all! RGB is much better, but it stays light a little -too long. LUV and LAB both hit the right lightness but LAB has a little more -color. HCL works in the same vein as HSV (both cylindrical interpolations) but -it does it right in that there is no green appearing and the lightness changes -in a linear manner. - -While this seems all good, you need to know one thing: When interpolating in any -of the CIE color spaces, you might get invalid RGB colors! This is important if -the starting and ending colors are user-input or random. An example of where this -happens is when blending between `#eeef61` and `#1e3140`: - -![Invalid RGB colors may crop up when blending in CIE spaces.](doc/colorblend/invalid.png) - -You can test whether a color is a valid RGB color by calling the `IsValid` method -and indeed, calling IsValid will return false for the redish colors on the bottom. -One way to "fix" this is to get a valid color close to the invalid one by calling -`Clamped`, which always returns a nearby valid color. Doing this, we get the -following result, which is satisfactory: - -![Fixing invalid RGB colors by clamping them to the valid range.](doc/colorblend/clamped.png) - -The following is the code creating the above three images; it can be found in `doc/colorblend/colorblend.go` - -```go -package main - -import "fmt" -import "github.com/lucasb-eyer/go-colorful" -import "image" -import "image/draw" -import "image/png" -import "os" - -func main() { - blocks := 10 - blockw := 40 - img := image.NewRGBA(image.Rect(0,0,blocks*blockw,200)) - - c1, _ := colorful.Hex("#fdffcc") - c2, _ := colorful.Hex("#242a42") - - // Use these colors to get invalid RGB in the gradient. - //c1, _ := colorful.Hex("#EEEF61") - //c2, _ := colorful.Hex("#1E3140") - - for i := 0 ; i < blocks ; i++ { - draw.Draw(img, image.Rect(i*blockw, 0,(i+1)*blockw, 40), &image.Uniform{c1.BlendHsv(c2, float64(i)/float64(blocks-1))}, image.Point{}, draw.Src) - draw.Draw(img, image.Rect(i*blockw, 40,(i+1)*blockw, 80), &image.Uniform{c1.BlendLuv(c2, float64(i)/float64(blocks-1))}, image.Point{}, draw.Src) - draw.Draw(img, image.Rect(i*blockw, 80,(i+1)*blockw,120), &image.Uniform{c1.BlendRgb(c2, float64(i)/float64(blocks-1))}, image.Point{}, draw.Src) - draw.Draw(img, image.Rect(i*blockw,120,(i+1)*blockw,160), &image.Uniform{c1.BlendLab(c2, float64(i)/float64(blocks-1))}, image.Point{}, draw.Src) - draw.Draw(img, image.Rect(i*blockw,160,(i+1)*blockw,200), &image.Uniform{c1.BlendHcl(c2, float64(i)/float64(blocks-1))}, image.Point{}, draw.Src) - - // This can be used to "fix" invalid colors in the gradient. - //draw.Draw(img, image.Rect(i*blockw,160,(i+1)*blockw,200), &image.Uniform{c1.BlendHcl(c2, float64(i)/float64(blocks-1)).Clamped()}, image.Point{}, draw.Src) - } - - toimg, err := os.Create("colorblend.png") - if err != nil { - fmt.Printf("Error: %v", err) - return - } - defer toimg.Close() - - png.Encode(toimg, img) -} -``` - -#### Generating color gradients -A very common reason to blend colors is creating gradients. There is an example -program in [doc/gradientgen.go](doc/gradientgen/gradientgen.go); it doesn't use any API -which hasn't been used in the previous example code, so I won't bother pasting -the code in here. Just look at that gorgeous gradient it generated in HCL space: - -!["Spectral" colorbrewer gradient in HCL space.](doc/gradientgen/gradientgen.png) - -### Getting random colors -It is sometimes necessary to generate random colors. You could simply do this -on your own by generating colors with random values. By restricting the random -values to a range smaller than [0..1] and using a space such as CIE-H\*C\*l° or -HSV, you can generate both random shades of a color or random colors of a -lightness: - -```go -random_blue := colorful.Hcl(180.0+rand.Float64()*50.0, 0.2+rand.Float64()*0.8, 0.3+rand.Float64()*0.7) -random_dark := colorful.Hcl(rand.Float64()*360.0, rand.Float64(), rand.Float64()*0.4) -random_light := colorful.Hcl(rand.Float64()*360.0, rand.Float64(), 0.6+rand.Float64()*0.4) -``` - -Since getting random "warm" and "happy" colors is quite a common task, there -are some helper functions: - -```go -colorful.WarmColor() -colorful.HappyColor() -colorful.FastWarmColor() -colorful.FastHappyColor() -``` - -The ones prefixed by `Fast` are faster but less coherent since they use the HSV -space as opposed to the regular ones which use CIE-L\*C\*h° space. The -following picture shows the warm colors in the top two rows and happy colors -in the bottom two rows. Within these, the first is the regular one and the -second is the fast one. - -![Warm, fast warm, happy and fast happy random colors, respectively.](doc/colorgens/colorgens.png) - -Don't forget to initialize the random seed! You can see the code used for -generating this picture in `doc/colorgens/colorgens.go`. - -### Getting random palettes -As soon as you need to generate more than one random color, you probably want -them to be distinguishable. Playing against an opponent which has almost the -same blue as I do is not fun. This is where random palettes can help. - -These palettes are generated using an algorithm which ensures that all colors -on the palette are as distinguishable as possible. Again, there is a `Fast` -method which works in HSV and is less perceptually uniform and a non-`Fast` -method which works in CIE spaces. For more theory on `SoftPalette`, check out -[I want hue](http://tools.medialab.sciences-po.fr/iwanthue/theory.php). Yet -again, there is a `Happy` and a `Warm` version, which do what you expect, but -now there is an additional `Soft` version, which is more configurable: you can -give a constraint on the color space in order to get colors within a certain *feel*. - -Let's start with the simple methods first, all they take is the amount of -colors to generate, which could, for example, be the player count. They return -an array of `colorful.Color` objects: - -```go -pal1, err1 := colorful.WarmPalette(10) -pal2 := colorful.FastWarmPalette(10) -pal3, err3 := colorful.HappyPalette(10) -pal4 := colorful.FastHappyPalette(10) -pal5, err5 := colorful.SoftPalette(10) -``` - -Note that the non-fast methods *may* fail if you ask for way too many colors. -Let's move on to the advanced one, namely `SoftPaletteEx`. Besides the color -count, this function takes a `SoftPaletteSettings` object as argument. The -interesting part here is its `CheckColor` member, which is a boolean function -taking three floating points as arguments: `l`, `a` and `b`. This function -should return `true` for colors which lie within the region you want and `false` -otherwise. The other members are `Iteration`, which should be within [5..100] -where higher means slower but more exact palette, and `ManySamples` which you -should set to `true` in case your `CheckColor` constraint rejects a large part -of the color space. - -For example, to create a palette of 10 brownish colors, you'd call it like this: - -```go -func isbrowny(l, a, b float64) bool { - h, c, L := colorful.LabToHcl(l, a, b) - return 10.0 < h && h < 50.0 && 0.1 < c && c < 0.5 && L < 0.5 -} -// Since the above function is pretty restrictive, we set ManySamples to true. -brownies := colorful.SoftPaletteEx(10, colorful.SoftPaletteSettings{isbrowny, 50, true}) -``` - -The following picture shows the palettes generated by all of these methods -(sourcecode in `doc/palettegens/palettegens.go`), in the order they were presented, i.e. -from top to bottom: `Warm`, `FastWarm`, `Happy`, `FastHappy`, `Soft`, -`SoftEx(isbrowny)`. All of them contain some randomness, so YMMV. - -![All example palettes](doc/palettegens/palettegens.png) - -Again, the code used for generating the above image is available as [doc/palettegens/palettegens.go](https://github.com/lucasb-eyer/go-colorful/blob/master/doc/palettegens/palettegens.go). - -### Sorting colors - -Sorting colors is not a well-defined operation. For example, {dark blue, dark red, light blue, light red} is already sorted if darker colors should precede lighter colors but would need to be re-sorted as {dark red, light red, dark blue, light blue} if longer-wavelength colors should precede shorter-wavelength colors. - -Go-Colorful's `Sorted` function orders a list of colors so as to minimize the average distance between adjacent colors, including between the last and the first. (`Sorted` does not necessarily find the true minimum, only a reasonably close approximation.) The following picture, drawn by [doc/colorsort/colorsort.go](https://github.com/lucasb-eyer/go-colorful/blob/master/doc/colorsort/colorsort.go), illustrates `Sorted`'s behavior: - -![Sorting colors](doc/colorsort/colorsort.png) - -The first row represents the input: a slice of 512 randomly chosen colors. The second row shows the colors sorted in CIE-L\*C\*h° space, ordered first by lightness (L), then by hue angle (h), and finally by chroma (C). Note that distracting pinstripes permeate the colors. Sorting using *any* color space and *any* ordering of the channels yields a similar pinstriped pattern. The third row of the image was sorted using Go-Colorful's `Sorted` function. Although the colors do not appear to be in any particular order, the sequence at least appears smoother than the one sorted by channel. - - -### Using linear RGB for computations -There are two methods for transforming RGB⟷Linear RGB: a fast and almost precise one, -and a slow and precise one. - -```go -r, g, b := colorful.Hex("#FF0000").FastLinearRgb() -``` - -TODO: describe some more. - -### Want to use some other reference point? - -```go -c := colorful.LabWhiteRef(0.507850, 0.040585,-0.370945, colorful.D50) -l, a, b := c.LabWhiteRef(colorful.D50) -``` - -### Reading and writing colors from databases - -The type `HexColor` makes it easy to store colors as strings in a database. It -implements the [https://godoc.org/database/sql#Scanner](database/sql.Scanner) -and [database/sql/driver.Value](https://godoc.org/database/sql/driver.Value) -interfaces which provide automatic type conversion. - -Example: - -```go -var hc HexColor -_, err := db.QueryRow("SELECT '#ff0000';").Scan(&hc) -// hc == HexColor{R: 1, G: 0, B: 0}; err == nil -``` - -FAQ -=== - -### Q: I get all f!@#ed up values! Your library sucks! -A: You probably provided values in the wrong range. For example, RGB values are -expected to reside between 0 and 1, *not* between 0 and 255. Normalize your colors. - -### Q: Lab/Luv/HCl seem broken! Your library sucks! -They look like this: - - - -A: You're likely trying to generate and display colors that can't be represented by RGB, -and thus monitors. When you're trying to convert, say, `HCL(190.0, 1.0, 1.0).RGB255()`, -you're asking for RGB values of `(-2105.254 300.680 286.185)`, which clearly don't exist, -and the `RGB255` function just casts these numbers to `uint8`, creating wrap-around and -what looks like a completely broken gradient. What you want to do, is either use more -reasonable values of colors which actually exist in RGB, or just `Clamp()` the resulting -color to its nearest existing one, living with the consequences: -`HCL(190.0, 1.0, 1.0).Clamp().RGB255()`. It will look something like this: - - - -[Here's an issue going in-depth about this](https://github.com/lucasb-eyer/go-colorful/issues/14), -as well as [my answer](https://github.com/lucasb-eyer/go-colorful/issues/14#issuecomment-324205385), -both with code and pretty pictures. Also note that this was somewhat covered above in the -["Blending colors" section](https://github.com/lucasb-eyer/go-colorful#blending-colors). - -### Q: In a tight loop, conversion to Lab/Luv/HCl/... are slooooow! -A: Yes, they are. -This library aims for correctness, readability, and modularity; it wasn't written with speed in mind. -A large part of the slowness comes from these conversions going through `LinearRgb` which uses powers. -I implemented a fast approximation to `LinearRgb` called `FastLinearRgb` by using Taylor approximations. -The approximation is roughly 5x faster and precise up to roughly 0.5%, -the major caveat being that if the input values are outside the range 0-1, accuracy drops dramatically. -You can use these in your conversions as follows: - -```go -col := // Get your color somehow -l, a, b := XyzToLab(LinearRgbToXyz(col.LinearRgb())) -``` - -If you need faster versions of `Distance*` and `Blend*` that make use of this fast approximation, -feel free to implement them and open a pull-request, I'll happily accept. - -The derivation of these functions can be followed in [this Jupyter notebook](doc/LinearRGB Approximations.ipynb). -Here's the main figure showing the approximation quality: - -![approximation quality](doc/approx-quality.png) - -More speed could be gained by using SIMD instructions in many places. -You can also get more speed for specific conversions by approximating the full conversion function, -but that is outside the scope of this library. -Thanks to [@ZirconiumX](https://github.com/ZirconiumX) for starting this investigation, -see [issue #18](https://github.com/lucasb-eyer/go-colorful/issues/18) for details. - -### Q: Why would `MakeColor` ever fail!? -A: `MakeColor` fails when the alpha channel is zero. In that case, the -conversion is undefined. See [issue 21](https://github.com/lucasb-eyer/go-colorful/issues/21) -as well as the short caveat note in the ["The `color.Color` interface"](README.md#the-colorcolor-interface) -section above. - -Who? -==== - -This library was developed by Lucas Beyer with contributions from -Bastien Dejean (@baskerville), Phil Kulak (@pkulak), Christian Muehlhaeuser (@muesli), and Scott Pakin (@spakin). - -It is now maintained by makeworld (@makew0rld). - - -## License - -This repo is under the MIT license, see [LICENSE](LICENSE) for details. diff --git a/vendor/github.com/lucasb-eyer/go-colorful/colorgens.go b/vendor/github.com/lucasb-eyer/go-colorful/colorgens.go deleted file mode 100644 index ac697d65582..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/colorgens.go +++ /dev/null @@ -1,67 +0,0 @@ -// Various ways to generate single random colors - -package colorful - -// Creates a random dark, "warm" color through a restricted HSV space. -func FastWarmColorWithRand(rand RandInterface) Color { - return Hsv( - rand.Float64()*360.0, - 0.5+rand.Float64()*0.3, - 0.3+rand.Float64()*0.3) -} - -func FastWarmColor() Color { - return FastWarmColorWithRand(getDefaultGlobalRand()) -} - -// Creates a random dark, "warm" color through restricted HCL space. -// This is slower than FastWarmColor but will likely give you colors which have -// the same "warmness" if you run it many times. -func WarmColorWithRand(rand RandInterface) (c Color) { - for c = randomWarmWithRand(rand); !c.IsValid(); c = randomWarmWithRand(rand) { - } - return -} - -func WarmColor() (c Color) { - return WarmColorWithRand(getDefaultGlobalRand()) -} - -func randomWarmWithRand(rand RandInterface) Color { - return Hcl( - rand.Float64()*360.0, - 0.1+rand.Float64()*0.3, - 0.2+rand.Float64()*0.3) -} - -// Creates a random bright, "pimpy" color through a restricted HSV space. -func FastHappyColorWithRand(rand RandInterface) Color { - return Hsv( - rand.Float64()*360.0, - 0.7+rand.Float64()*0.3, - 0.6+rand.Float64()*0.3) -} - -func FastHappyColor() Color { - return FastHappyColorWithRand(getDefaultGlobalRand()) -} - -// Creates a random bright, "pimpy" color through restricted HCL space. -// This is slower than FastHappyColor but will likely give you colors which -// have the same "brightness" if you run it many times. -func HappyColorWithRand(rand RandInterface) (c Color) { - for c = randomPimpWithRand(rand); !c.IsValid(); c = randomPimpWithRand(rand) { - } - return -} - -func HappyColor() (c Color) { - return HappyColorWithRand(getDefaultGlobalRand()) -} - -func randomPimpWithRand(rand RandInterface) Color { - return Hcl( - rand.Float64()*360.0, - 0.5+rand.Float64()*0.3, - 0.5+rand.Float64()*0.3) -} diff --git a/vendor/github.com/lucasb-eyer/go-colorful/colors.go b/vendor/github.com/lucasb-eyer/go-colorful/colors.go deleted file mode 100644 index 17441a8c613..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/colors.go +++ /dev/null @@ -1,1156 +0,0 @@ -// The colorful package provides all kinds of functions for working with colors. -package colorful - -import ( - "fmt" - "image/color" - "math" - "strconv" -) - -// A color is stored internally using sRGB (standard RGB) values in the range 0-1 -type Color struct { - R, G, B float64 -} - -// Implement the Go color.Color interface. -func (col Color) RGBA() (r, g, b, a uint32) { - r = uint32(col.R*65535.0 + 0.5) - g = uint32(col.G*65535.0 + 0.5) - b = uint32(col.B*65535.0 + 0.5) - a = 0xFFFF - return -} - -// Constructs a colorful.Color from something implementing color.Color -func MakeColor(col color.Color) (Color, bool) { - r, g, b, a := col.RGBA() - if a == 0 { - return Color{0, 0, 0}, false - } - - // Since color.Color is alpha pre-multiplied, we need to divide the - // RGB values by alpha again in order to get back the original RGB. - r *= 0xffff - r /= a - g *= 0xffff - g /= a - b *= 0xffff - b /= a - - return Color{float64(r) / 65535.0, float64(g) / 65535.0, float64(b) / 65535.0}, true -} - -// Might come in handy sometimes to reduce boilerplate code. -func (col Color) RGB255() (r, g, b uint8) { - r = uint8(col.R*255.0 + 0.5) - g = uint8(col.G*255.0 + 0.5) - b = uint8(col.B*255.0 + 0.5) - return -} - -// Used to simplify HSLuv testing. -func (col Color) values() (float64, float64, float64) { - return col.R, col.G, col.B -} - -// This is the tolerance used when comparing colors using AlmostEqualRgb. -const Delta = 1.0 / 255.0 - -// This is the default reference white point. -var D65 = [3]float64{0.95047, 1.00000, 1.08883} - -// And another one. -var D50 = [3]float64{0.96422, 1.00000, 0.82521} - -// Checks whether the color exists in RGB space, i.e. all values are in [0..1] -func (c Color) IsValid() bool { - return 0.0 <= c.R && c.R <= 1.0 && - 0.0 <= c.G && c.G <= 1.0 && - 0.0 <= c.B && c.B <= 1.0 -} - -// clamp01 clamps from 0 to 1. -func clamp01(v float64) float64 { - return math.Max(0.0, math.Min(v, 1.0)) -} - -// Returns Clamps the color into valid range, clamping each value to [0..1] -// If the color is valid already, this is a no-op. -func (c Color) Clamped() Color { - return Color{clamp01(c.R), clamp01(c.G), clamp01(c.B)} -} - -func sq(v float64) float64 { - return v * v -} - -func cub(v float64) float64 { - return v * v * v -} - -// DistanceRgb computes the distance between two colors in RGB space. -// This is not a good measure! Rather do it in Lab space. -func (c1 Color) DistanceRgb(c2 Color) float64 { - return math.Sqrt(sq(c1.R-c2.R) + sq(c1.G-c2.G) + sq(c1.B-c2.B)) -} - -// DistanceLinearRgb computes the distance between two colors in linear RGB -// space. This is not useful for measuring how humans perceive color, but -// might be useful for other things, like dithering. -func (c1 Color) DistanceLinearRgb(c2 Color) float64 { - r1, g1, b1 := c1.LinearRgb() - r2, g2, b2 := c2.LinearRgb() - return math.Sqrt(sq(r1-r2) + sq(g1-g2) + sq(b1-b2)) -} - -// DistanceLinearRGB is deprecated in favour of DistanceLinearRgb. -// They do the exact same thing. -func (c1 Color) DistanceLinearRGB(c2 Color) float64 { - return c1.DistanceLinearRgb(c2) -} - -// DistanceRiemersma is a color distance algorithm developed by Thiadmer Riemersma. -// It uses RGB coordinates, but he claims it has similar results to CIELUV. -// This makes it both fast and accurate. -// -// Sources: -// -// https://www.compuphase.com/cmetric.htm -// https://github.com/lucasb-eyer/go-colorful/issues/52 -func (c1 Color) DistanceRiemersma(c2 Color) float64 { - rAvg := (c1.R + c2.R) / 2.0 - // Deltas - dR := c1.R - c2.R - dG := c1.G - c2.G - dB := c1.B - c2.B - - return math.Sqrt((2+rAvg)*dR*dR + 4*dG*dG + (2+(1-rAvg))*dB*dB) -} - -// Check for equality between colors within the tolerance Delta (1/255). -func (c1 Color) AlmostEqualRgb(c2 Color) bool { - return math.Abs(c1.R-c2.R)+ - math.Abs(c1.G-c2.G)+ - math.Abs(c1.B-c2.B) < 3.0*Delta -} - -// You don't really want to use this, do you? Go for BlendLab, BlendLuv or BlendHcl. -func (c1 Color) BlendRgb(c2 Color, t float64) Color { - return Color{ - c1.R + t*(c2.R-c1.R), - c1.G + t*(c2.G-c1.G), - c1.B + t*(c2.B-c1.B), - } -} - -// Utility used by Hxx color-spaces for interpolating between two angles in [0,360]. -func interp_angle(a0, a1, t float64) float64 { - // Based on the answer here: http://stackoverflow.com/a/14498790/2366315 - // With potential proof that it works here: http://math.stackexchange.com/a/2144499 - delta := math.Mod(math.Mod(a1-a0, 360.0)+540, 360.0) - 180.0 - return math.Mod(a0+t*delta+360.0, 360.0) -} - -/// HSV /// -/////////// -// From http://en.wikipedia.org/wiki/HSL_and_HSV -// Note that h is in [0..359] and s,v in [0..1] - -// Hsv returns the Hue [0..359], Saturation and Value [0..1] of the color. -func (col Color) Hsv() (h, s, v float64) { - min := math.Min(math.Min(col.R, col.G), col.B) - v = math.Max(math.Max(col.R, col.G), col.B) - C := v - min - - s = 0.0 - if v != 0.0 { - s = C / v - } - - h = 0.0 // We use 0 instead of undefined as in wp. - if min != v { - if v == col.R { - h = math.Mod((col.G-col.B)/C, 6.0) - } - if v == col.G { - h = (col.B-col.R)/C + 2.0 - } - if v == col.B { - h = (col.R-col.G)/C + 4.0 - } - h *= 60.0 - if h < 0.0 { - h += 360.0 - } - } - return -} - -// Hsv creates a new Color given a Hue in [0..359], a Saturation and a Value in [0..1] -func Hsv(H, S, V float64) Color { - Hp := H / 60.0 - C := V * S - X := C * (1.0 - math.Abs(math.Mod(Hp, 2.0)-1.0)) - - m := V - C - r, g, b := 0.0, 0.0, 0.0 - - switch { - case 0.0 <= Hp && Hp < 1.0: - r = C - g = X - case 1.0 <= Hp && Hp < 2.0: - r = X - g = C - case 2.0 <= Hp && Hp < 3.0: - g = C - b = X - case 3.0 <= Hp && Hp < 4.0: - g = X - b = C - case 4.0 <= Hp && Hp < 5.0: - r = X - b = C - case 5.0 <= Hp && Hp < 6.0: - r = C - b = X - } - - return Color{m + r, m + g, m + b} -} - -// You don't really want to use this, do you? Go for BlendLab, BlendLuv or BlendHcl. -func (c1 Color) BlendHsv(c2 Color, t float64) Color { - h1, s1, v1 := c1.Hsv() - h2, s2, v2 := c2.Hsv() - - // https://github.com/lucasb-eyer/go-colorful/pull/60 - if s1 == 0 && s2 != 0 { - h1 = h2 - } else if s2 == 0 && s1 != 0 { - h2 = h1 - } - - // We know that h are both in [0..360] - return Hsv(interp_angle(h1, h2, t), s1+t*(s2-s1), v1+t*(v2-v1)) -} - -/// HSL /// -/////////// - -// Hsl returns the Hue [0..359], Saturation [0..1], and Luminance (lightness) [0..1] of the color. -func (col Color) Hsl() (h, s, l float64) { - min := math.Min(math.Min(col.R, col.G), col.B) - max := math.Max(math.Max(col.R, col.G), col.B) - - l = (max + min) / 2 - - if min == max { - s = 0 - h = 0 - } else { - if l < 0.5 { - s = (max - min) / (max + min) - } else { - s = (max - min) / (2.0 - max - min) - } - - if max == col.R { - h = (col.G - col.B) / (max - min) - } else if max == col.G { - h = 2.0 + (col.B-col.R)/(max-min) - } else { - h = 4.0 + (col.R-col.G)/(max-min) - } - - h *= 60 - - if h < 0 { - h += 360 - } - } - - return -} - -// Hsl creates a new Color given a Hue in [0..359], a Saturation [0..1], and a Luminance (lightness) in [0..1] -func Hsl(h, s, l float64) Color { - if s == 0 { - return Color{l, l, l} - } - - var r, g, b float64 - var t1 float64 - var t2 float64 - var tr float64 - var tg float64 - var tb float64 - - if l < 0.5 { - t1 = l * (1.0 + s) - } else { - t1 = l + s - l*s - } - - t2 = 2*l - t1 - h /= 360 - tr = h + 1.0/3.0 - tg = h - tb = h - 1.0/3.0 - - if tr < 0 { - tr++ - } - if tr > 1 { - tr-- - } - if tg < 0 { - tg++ - } - if tg > 1 { - tg-- - } - if tb < 0 { - tb++ - } - if tb > 1 { - tb-- - } - - // Red - if 6*tr < 1 { - r = t2 + (t1-t2)*6*tr - } else if 2*tr < 1 { - r = t1 - } else if 3*tr < 2 { - r = t2 + (t1-t2)*(2.0/3.0-tr)*6 - } else { - r = t2 - } - - // Green - if 6*tg < 1 { - g = t2 + (t1-t2)*6*tg - } else if 2*tg < 1 { - g = t1 - } else if 3*tg < 2 { - g = t2 + (t1-t2)*(2.0/3.0-tg)*6 - } else { - g = t2 - } - - // Blue - if 6*tb < 1 { - b = t2 + (t1-t2)*6*tb - } else if 2*tb < 1 { - b = t1 - } else if 3*tb < 2 { - b = t2 + (t1-t2)*(2.0/3.0-tb)*6 - } else { - b = t2 - } - - return Color{r, g, b} -} - -/// Hex /// -/////////// - -// Hex returns the hex "html" representation of the color, as in #ff0080. -func (col Color) Hex() string { - // Add 0.5 for rounding - return fmt.Sprintf("#%02x%02x%02x", uint8(col.R*255.0+0.5), uint8(col.G*255.0+0.5), uint8(col.B*255.0+0.5)) -} - -// Hex parses a "html" hex color-string, either in the 3 "#f0c" or 6 "#ff1034" digits form. -func Hex(scol string) (Color, error) { - if scol == "" || scol[0] != '#' { - return Color{}, fmt.Errorf("color: %v is not a hex-color", scol) - } - var c Color - var err error - switch len(scol) { - case 4: - c, err = parseHexColor(scol[1:2], scol[2:3], scol[3:4], 4, 1.0/15.0) - case 7: - c, err = parseHexColor(scol[1:3], scol[3:5], scol[5:7], 8, 1.0/255.0) - default: - return Color{}, fmt.Errorf("color: %v is not a hex-color", scol) - } - if err != nil { - return Color{}, fmt.Errorf("color: %v is not a hex-color: %w", scol, err) - } - return c, nil -} - -func parseHexColor(r, g, b string, bits int, factor float64) (Color, error) { - var c Color - var v uint64 - var err error - - if v, err = strconv.ParseUint(r, 16, bits); err != nil { - return Color{}, err - } - c.R = float64(v) * factor - - if v, err = strconv.ParseUint(g, 16, bits); err != nil { - return Color{}, err - } - c.G = float64(v) * factor - - if v, err = strconv.ParseUint(b, 16, bits); err != nil { - return Color{}, err - } - c.B = float64(v) * factor - - return c, err -} - -/// Linear /// -////////////// -// http://www.sjbrown.co.uk/2004/05/14/gamma-correct-rendering/ -// http://www.brucelindbloom.com/Eqn_RGB_to_XYZ.html - -func linearize(v float64) float64 { - if v <= 0.04045 { - return v / 12.92 - } - return math.Pow((v+0.055)/1.055, 2.4) -} - -// LinearRgb converts the color into the linear RGB space (see http://www.sjbrown.co.uk/2004/05/14/gamma-correct-rendering/). -func (col Color) LinearRgb() (r, g, b float64) { - r = linearize(col.R) - g = linearize(col.G) - b = linearize(col.B) - return -} - -// A much faster and still quite precise linearization using a 6th-order Taylor approximation. -// See the accompanying Jupyter notebook for derivation of the constants. -func linearize_fast(v float64) float64 { - v1 := v - 0.5 - v2 := v1 * v1 - v3 := v2 * v1 - v4 := v2 * v2 - // v5 := v3*v2 - return -0.248750514614486 + 0.925583310193438*v + 1.16740237321695*v2 + 0.280457026598666*v3 - 0.0757991963780179*v4 //+ 0.0437040411548932*v5 -} - -// FastLinearRgb is much faster than and almost as accurate as LinearRgb. -// BUT it is important to NOTE that they only produce good results for valid colors r,g,b in [0,1]. -func (col Color) FastLinearRgb() (r, g, b float64) { - r = linearize_fast(col.R) - g = linearize_fast(col.G) - b = linearize_fast(col.B) - return -} - -func delinearize(v float64) float64 { - if v <= 0.0031308 { - return 12.92 * v - } - return 1.055*math.Pow(v, 1.0/2.4) - 0.055 -} - -// LinearRgb creates an sRGB color out of the given linear RGB color (see http://www.sjbrown.co.uk/2004/05/14/gamma-correct-rendering/). -func LinearRgb(r, g, b float64) Color { - return Color{delinearize(r), delinearize(g), delinearize(b)} -} - -func delinearize_fast(v float64) float64 { - // This function (fractional root) is much harder to linearize, so we need to split. - if v > 0.2 { - v1 := v - 0.6 - v2 := v1 * v1 - v3 := v2 * v1 - v4 := v2 * v2 - v5 := v3 * v2 - return 0.442430344268235 + 0.592178981271708*v - 0.287864782562636*v2 + 0.253214392068985*v3 - 0.272557158129811*v4 + 0.325554383321718*v5 - } else if v > 0.03 { - v1 := v - 0.115 - v2 := v1 * v1 - v3 := v2 * v1 - v4 := v2 * v2 - v5 := v3 * v2 - return 0.194915592891669 + 1.55227076330229*v - 3.93691860257828*v2 + 18.0679839248761*v3 - 101.468750302746*v4 + 632.341487393927*v5 - } else { - v1 := v - 0.015 - v2 := v1 * v1 - v3 := v2 * v1 - v4 := v2 * v2 - v5 := v3 * v2 - // You can clearly see from the involved constants that the low-end is highly nonlinear. - return 0.0519565234928877 + 5.09316778537561*v - 99.0338180489702*v2 + 3484.52322764895*v3 - 150028.083412663*v4 + 7168008.42971613*v5 - } -} - -// FastLinearRgb is much faster than and almost as accurate as LinearRgb. -// BUT it is important to NOTE that they only produce good results for valid inputs r,g,b in [0,1]. -func FastLinearRgb(r, g, b float64) Color { - return Color{delinearize_fast(r), delinearize_fast(g), delinearize_fast(b)} -} - -// XyzToLinearRgb converts from CIE XYZ-space to Linear RGB space. -func XyzToLinearRgb(x, y, z float64) (r, g, b float64) { - r = 3.2409699419045214*x - 1.5373831775700935*y - 0.49861076029300328*z - g = -0.96924363628087983*x + 1.8759675015077207*y + 0.041555057407175613*z - b = 0.055630079696993609*x - 0.20397695888897657*y + 1.0569715142428786*z - return -} - -func LinearRgbToXyz(r, g, b float64) (x, y, z float64) { - x = 0.41239079926595948*r + 0.35758433938387796*g + 0.18048078840183429*b - y = 0.21263900587151036*r + 0.71516867876775593*g + 0.072192315360733715*b - z = 0.019330818715591851*r + 0.11919477979462599*g + 0.95053215224966058*b - return -} - -// BlendLinearRgb blends two colors in the Linear RGB color-space. -// Unlike BlendRgb, this will not produce dark color around the center. -// t == 0 results in c1, t == 1 results in c2 -func (c1 Color) BlendLinearRgb(c2 Color, t float64) Color { - r1, g1, b1 := c1.LinearRgb() - r2, g2, b2 := c2.LinearRgb() - return LinearRgb( - r1+t*(r2-r1), - g1+t*(g2-g1), - b1+t*(b2-b1), - ) -} - -/// XYZ /// -/////////// -// http://www.sjbrown.co.uk/2004/05/14/gamma-correct-rendering/ - -func (col Color) Xyz() (x, y, z float64) { - return LinearRgbToXyz(col.LinearRgb()) -} - -func Xyz(x, y, z float64) Color { - return LinearRgb(XyzToLinearRgb(x, y, z)) -} - -/// xyY /// -/////////// -// http://www.brucelindbloom.com/Eqn_XYZ_to_xyY.html - -// Well, the name is bad, since it's xyY but Golang needs me to start with a -// capital letter to make the method public. -func XyzToXyy(X, Y, Z float64) (x, y, Yout float64) { - return XyzToXyyWhiteRef(X, Y, Z, D65) -} - -func XyzToXyyWhiteRef(X, Y, Z float64, wref [3]float64) (x, y, Yout float64) { - Yout = Y - N := X + Y + Z - if math.Abs(N) < 1e-14 { - // When we have black, Bruce Lindbloom recommends to use - // the reference white's chromacity for x and y. - x = wref[0] / (wref[0] + wref[1] + wref[2]) - y = wref[1] / (wref[0] + wref[1] + wref[2]) - } else { - x = X / N - y = Y / N - } - return -} - -func XyyToXyz(x, y, Y float64) (X, Yout, Z float64) { - Yout = Y - - if -1e-14 < y && y < 1e-14 { - X = 0.0 - Z = 0.0 - } else { - X = Y / y * x - Z = Y / y * (1.0 - x - y) - } - - return -} - -// Converts the given color to CIE xyY space using D65 as reference white. -// (Note that the reference white is only used for black input.) -// x, y and Y are in [0..1] -func (col Color) Xyy() (x, y, Y float64) { - return XyzToXyy(col.Xyz()) -} - -// Converts the given color to CIE xyY space, taking into account -// a given reference white. (i.e. the monitor's white) -// (Note that the reference white is only used for black input.) -// x, y and Y are in [0..1] -func (col Color) XyyWhiteRef(wref [3]float64) (x, y, Y float64) { - X, Y2, Z := col.Xyz() - return XyzToXyyWhiteRef(X, Y2, Z, wref) -} - -// Generates a color by using data given in CIE xyY space. -// x, y and Y are in [0..1] -func Xyy(x, y, Y float64) Color { - return Xyz(XyyToXyz(x, y, Y)) -} - -/// L*a*b* /// -////////////// -// http://en.wikipedia.org/wiki/Lab_color_space#CIELAB-CIEXYZ_conversions -// For L*a*b*, we need to L*a*b*<->XYZ->RGB and the first one is device dependent. - -func lab_f(t float64) float64 { - if t > 6.0/29.0*6.0/29.0*6.0/29.0 { - return math.Cbrt(t) - } - return t/3.0*29.0/6.0*29.0/6.0 + 4.0/29.0 -} - -func XyzToLab(x, y, z float64) (l, a, b float64) { - // Use D65 white as reference point by default. - // http://www.fredmiranda.com/forum/topic/1035332 - // http://en.wikipedia.org/wiki/Standard_illuminant - return XyzToLabWhiteRef(x, y, z, D65) -} - -func XyzToLabWhiteRef(x, y, z float64, wref [3]float64) (l, a, b float64) { - fy := lab_f(y / wref[1]) - l = 1.16*fy - 0.16 - a = 5.0 * (lab_f(x/wref[0]) - fy) - b = 2.0 * (fy - lab_f(z/wref[2])) - return -} - -func lab_finv(t float64) float64 { - if t > 6.0/29.0 { - return t * t * t - } - return 3.0 * 6.0 / 29.0 * 6.0 / 29.0 * (t - 4.0/29.0) -} - -func LabToXyz(l, a, b float64) (x, y, z float64) { - // D65 white (see above). - return LabToXyzWhiteRef(l, a, b, D65) -} - -func LabToXyzWhiteRef(l, a, b float64, wref [3]float64) (x, y, z float64) { - l2 := (l + 0.16) / 1.16 - x = wref[0] * lab_finv(l2+a/5.0) - y = wref[1] * lab_finv(l2) - z = wref[2] * lab_finv(l2-b/2.0) - return -} - -// Converts the given color to CIE L*a*b* space using D65 as reference white. -func (col Color) Lab() (l, a, b float64) { - return XyzToLab(col.Xyz()) -} - -// Converts the given color to CIE L*a*b* space, taking into account -// a given reference white. (i.e. the monitor's white) -func (col Color) LabWhiteRef(wref [3]float64) (l, a, b float64) { - x, y, z := col.Xyz() - return XyzToLabWhiteRef(x, y, z, wref) -} - -// Generates a color by using data given in CIE L*a*b* space using D65 as reference white. -// WARNING: many combinations of `l`, `a`, and `b` values do not have corresponding -// valid RGB values, check the FAQ in the README if you're unsure. -func Lab(l, a, b float64) Color { - return Xyz(LabToXyz(l, a, b)) -} - -// Generates a color by using data given in CIE L*a*b* space, taking -// into account a given reference white. (i.e. the monitor's white) -func LabWhiteRef(l, a, b float64, wref [3]float64) Color { - return Xyz(LabToXyzWhiteRef(l, a, b, wref)) -} - -// DistanceLab is a good measure of visual similarity between two colors! -// A result of 0 would mean identical colors, while a result of 1 or higher -// means the colors differ a lot. -func (c1 Color) DistanceLab(c2 Color) float64 { - l1, a1, b1 := c1.Lab() - l2, a2, b2 := c2.Lab() - return math.Sqrt(sq(l1-l2) + sq(a1-a2) + sq(b1-b2)) -} - -// DistanceCIE76 is the same as DistanceLab. -func (c1 Color) DistanceCIE76(c2 Color) float64 { - return c1.DistanceLab(c2) -} - -// Uses the CIE94 formula to calculate color distance. More accurate than -// DistanceLab, but also more work. -func (cl Color) DistanceCIE94(cr Color) float64 { - l1, a1, b1 := cl.Lab() - l2, a2, b2 := cr.Lab() - - // NOTE: Since all those formulas expect L,a,b values 100x larger than we - // have them in this library, we either need to adjust all constants - // in the formula, or convert the ranges of L,a,b before, and then - // scale the distances down again. The latter is less error-prone. - l1, a1, b1 = l1*100.0, a1*100.0, b1*100.0 - l2, a2, b2 = l2*100.0, a2*100.0, b2*100.0 - - kl := 1.0 // 2.0 for textiles - kc := 1.0 - kh := 1.0 - k1 := 0.045 // 0.048 for textiles - k2 := 0.015 // 0.014 for textiles. - - deltaL := l1 - l2 - c1 := math.Sqrt(sq(a1) + sq(b1)) - c2 := math.Sqrt(sq(a2) + sq(b2)) - deltaCab := c1 - c2 - - // Not taking Sqrt here for stability, and it's unnecessary. - deltaHab2 := sq(a1-a2) + sq(b1-b2) - sq(deltaCab) - sl := 1.0 - sc := 1.0 + k1*c1 - sh := 1.0 + k2*c1 - - vL2 := sq(deltaL / (kl * sl)) - vC2 := sq(deltaCab / (kc * sc)) - vH2 := deltaHab2 / sq(kh*sh) - - return math.Sqrt(vL2+vC2+vH2) * 0.01 // See above. -} - -// DistanceCIEDE2000 uses the Delta E 2000 formula to calculate color -// distance. It is more expensive but more accurate than both DistanceLab -// and DistanceCIE94. -func (cl Color) DistanceCIEDE2000(cr Color) float64 { - return cl.DistanceCIEDE2000klch(cr, 1.0, 1.0, 1.0) -} - -// DistanceCIEDE2000klch uses the Delta E 2000 formula with custom values -// for the weighting factors kL, kC, and kH. -func (cl Color) DistanceCIEDE2000klch(cr Color, kl, kc, kh float64) float64 { - l1, a1, b1 := cl.Lab() - l2, a2, b2 := cr.Lab() - - // As with CIE94, we scale up the ranges of L,a,b beforehand and scale - // them down again afterwards. - l1, a1, b1 = l1*100.0, a1*100.0, b1*100.0 - l2, a2, b2 = l2*100.0, a2*100.0, b2*100.0 - - cab1 := math.Sqrt(sq(a1) + sq(b1)) - cab2 := math.Sqrt(sq(a2) + sq(b2)) - cabmean := (cab1 + cab2) / 2 - - g := 0.5 * (1 - math.Sqrt(math.Pow(cabmean, 7)/(math.Pow(cabmean, 7)+math.Pow(25, 7)))) - ap1 := (1 + g) * a1 - ap2 := (1 + g) * a2 - cp1 := math.Sqrt(sq(ap1) + sq(b1)) - cp2 := math.Sqrt(sq(ap2) + sq(b2)) - - hp1 := 0.0 - if b1 != ap1 || ap1 != 0 { - hp1 = math.Atan2(b1, ap1) - if hp1 < 0 { - hp1 += math.Pi * 2 - } - hp1 *= 180 / math.Pi - } - hp2 := 0.0 - if b2 != ap2 || ap2 != 0 { - hp2 = math.Atan2(b2, ap2) - if hp2 < 0 { - hp2 += math.Pi * 2 - } - hp2 *= 180 / math.Pi - } - - deltaLp := l2 - l1 - deltaCp := cp2 - cp1 - dhp := 0.0 - cpProduct := cp1 * cp2 - if cpProduct != 0 { - dhp = hp2 - hp1 - if dhp > 180 { - dhp -= 360 - } else if dhp < -180 { - dhp += 360 - } - } - deltaHp := 2 * math.Sqrt(cpProduct) * math.Sin(dhp/2*math.Pi/180) - - lpmean := (l1 + l2) / 2 - cpmean := (cp1 + cp2) / 2 - hpmean := hp1 + hp2 - if cpProduct != 0 { - hpmean /= 2 - if math.Abs(hp1-hp2) > 180 { - if hp1+hp2 < 360 { - hpmean += 180 - } else { - hpmean -= 180 - } - } - } - - t := 1 - 0.17*math.Cos((hpmean-30)*math.Pi/180) + 0.24*math.Cos(2*hpmean*math.Pi/180) + 0.32*math.Cos((3*hpmean+6)*math.Pi/180) - 0.2*math.Cos((4*hpmean-63)*math.Pi/180) - deltaTheta := 30 * math.Exp(-sq((hpmean-275)/25)) - rc := 2 * math.Sqrt(math.Pow(cpmean, 7)/(math.Pow(cpmean, 7)+math.Pow(25, 7))) - sl := 1 + (0.015*sq(lpmean-50))/math.Sqrt(20+sq(lpmean-50)) - sc := 1 + 0.045*cpmean - sh := 1 + 0.015*cpmean*t - rt := -math.Sin(2*deltaTheta*math.Pi/180) * rc - - return math.Sqrt(sq(deltaLp/(kl*sl))+sq(deltaCp/(kc*sc))+sq(deltaHp/(kh*sh))+rt*(deltaCp/(kc*sc))*(deltaHp/(kh*sh))) * 0.01 -} - -// BlendLab blends two colors in the L*a*b* color-space, which should result in a smoother blend. -// t == 0 results in c1, t == 1 results in c2 -func (c1 Color) BlendLab(c2 Color, t float64) Color { - l1, a1, b1 := c1.Lab() - l2, a2, b2 := c2.Lab() - return Lab(l1+t*(l2-l1), - a1+t*(a2-a1), - b1+t*(b2-b1)) -} - -/// L*u*v* /// -////////////// -// http://en.wikipedia.org/wiki/CIELUV#XYZ_.E2.86.92_CIELUV_and_CIELUV_.E2.86.92_XYZ_conversions -// For L*u*v*, we need to L*u*v*<->XYZ<->RGB and the first one is device dependent. - -func XyzToLuv(x, y, z float64) (l, a, b float64) { - // Use D65 white as reference point by default. - // http://www.fredmiranda.com/forum/topic/1035332 - // http://en.wikipedia.org/wiki/Standard_illuminant - return XyzToLuvWhiteRef(x, y, z, D65) -} - -func XyzToLuvWhiteRef(x, y, z float64, wref [3]float64) (l, u, v float64) { - if y/wref[1] <= 6.0/29.0*6.0/29.0*6.0/29.0 { - l = y / wref[1] * (29.0 / 3.0 * 29.0 / 3.0 * 29.0 / 3.0) / 100.0 - } else { - l = 1.16*math.Cbrt(y/wref[1]) - 0.16 - } - ubis, vbis := xyz_to_uv(x, y, z) - un, vn := xyz_to_uv(wref[0], wref[1], wref[2]) - u = 13.0 * l * (ubis - un) - v = 13.0 * l * (vbis - vn) - return -} - -// For this part, we do as R's graphics.hcl does, not as wikipedia does. -// Or is it the same? -func xyz_to_uv(x, y, z float64) (u, v float64) { - denom := x + 15.0*y + 3.0*z - if denom == 0.0 { - u, v = 0.0, 0.0 - } else { - u = 4.0 * x / denom - v = 9.0 * y / denom - } - return -} - -func LuvToXyz(l, u, v float64) (x, y, z float64) { - // D65 white (see above). - return LuvToXyzWhiteRef(l, u, v, D65) -} - -func LuvToXyzWhiteRef(l, u, v float64, wref [3]float64) (x, y, z float64) { - // y = wref[1] * lab_finv((l + 0.16) / 1.16) - if l <= 0.08 { - y = wref[1] * l * 100.0 * 3.0 / 29.0 * 3.0 / 29.0 * 3.0 / 29.0 - } else { - y = wref[1] * cub((l+0.16)/1.16) - } - un, vn := xyz_to_uv(wref[0], wref[1], wref[2]) - if l != 0.0 { - ubis := u/(13.0*l) + un - vbis := v/(13.0*l) + vn - x = y * 9.0 * ubis / (4.0 * vbis) - z = y * (12.0 - 3.0*ubis - 20.0*vbis) / (4.0 * vbis) - } else { - x, y = 0.0, 0.0 - } - return -} - -// Converts the given color to CIE L*u*v* space using D65 as reference white. -// L* is in [0..1] and both u* and v* are in about [-1..1] -func (col Color) Luv() (l, u, v float64) { - return XyzToLuv(col.Xyz()) -} - -// Converts the given color to CIE L*u*v* space, taking into account -// a given reference white. (i.e. the monitor's white) -// L* is in [0..1] and both u* and v* are in about [-1..1] -func (col Color) LuvWhiteRef(wref [3]float64) (l, u, v float64) { - x, y, z := col.Xyz() - return XyzToLuvWhiteRef(x, y, z, wref) -} - -// Generates a color by using data given in CIE L*u*v* space using D65 as reference white. -// L* is in [0..1] and both u* and v* are in about [-1..1] -// WARNING: many combinations of `l`, `u`, and `v` values do not have corresponding -// valid RGB values, check the FAQ in the README if you're unsure. -func Luv(l, u, v float64) Color { - return Xyz(LuvToXyz(l, u, v)) -} - -// Generates a color by using data given in CIE L*u*v* space, taking -// into account a given reference white. (i.e. the monitor's white) -// L* is in [0..1] and both u* and v* are in about [-1..1] -func LuvWhiteRef(l, u, v float64, wref [3]float64) Color { - return Xyz(LuvToXyzWhiteRef(l, u, v, wref)) -} - -// DistanceLuv is a good measure of visual similarity between two colors! -// A result of 0 would mean identical colors, while a result of 1 or higher -// means the colors differ a lot. -func (c1 Color) DistanceLuv(c2 Color) float64 { - l1, u1, v1 := c1.Luv() - l2, u2, v2 := c2.Luv() - return math.Sqrt(sq(l1-l2) + sq(u1-u2) + sq(v1-v2)) -} - -// BlendLuv blends two colors in the CIE-L*u*v* color-space, which should result in a smoother blend. -// t == 0 results in c1, t == 1 results in c2 -func (c1 Color) BlendLuv(c2 Color, t float64) Color { - l1, u1, v1 := c1.Luv() - l2, u2, v2 := c2.Luv() - return Luv(l1+t*(l2-l1), - u1+t*(u2-u1), - v1+t*(v2-v1)) -} - -/// HCL /// -/////////// -// HCL is nothing else than L*a*b* in cylindrical coordinates! -// (this was wrong on English wikipedia, I fixed it, let's hope the fix stays.) -// But it is widely popular since it is a "correct HSV" -// http://www.hunterlab.com/appnotes/an09_96a.pdf - -// Converts the given color to HCL space using D65 as reference white. -// H values are in [0..360], C and L values are in [0..1] although C can overshoot 1.0 -func (col Color) Hcl() (h, c, l float64) { - return col.HclWhiteRef(D65) -} - -func LabToHcl(L, a, b float64) (h, c, l float64) { - // Oops, floating point workaround necessary if a ~= b and both are very small (i.e. almost zero). - if math.Abs(b-a) > 1e-4 && math.Abs(a) > 1e-4 { - h = math.Mod(57.29577951308232087721*math.Atan2(b, a)+360.0, 360.0) // Rad2Deg - } else { - h = 0.0 - } - c = math.Sqrt(sq(a) + sq(b)) - l = L - return -} - -// Converts the given color to HCL space, taking into account -// a given reference white. (i.e. the monitor's white) -// H values are in [0..360], C and L values are in [0..1] -func (col Color) HclWhiteRef(wref [3]float64) (h, c, l float64) { - L, a, b := col.LabWhiteRef(wref) - return LabToHcl(L, a, b) -} - -// Generates a color by using data given in HCL space using D65 as reference white. -// H values are in [0..360], C and L values are in [0..1] -// WARNING: many combinations of `h`, `c`, and `l` values do not have corresponding -// valid RGB values, check the FAQ in the README if you're unsure. -func Hcl(h, c, l float64) Color { - return HclWhiteRef(h, c, l, D65) -} - -func HclToLab(h, c, l float64) (L, a, b float64) { - H := 0.01745329251994329576 * h // Deg2Rad - a = c * math.Cos(H) - b = c * math.Sin(H) - L = l - return -} - -// Generates a color by using data given in HCL space, taking -// into account a given reference white. (i.e. the monitor's white) -// H values are in [0..360], C and L values are in [0..1] -func HclWhiteRef(h, c, l float64, wref [3]float64) Color { - L, a, b := HclToLab(h, c, l) - return LabWhiteRef(L, a, b, wref) -} - -// BlendHcl blends two colors in the CIE-L*C*h° color-space, which should result in a smoother blend. -// t == 0 results in c1, t == 1 results in c2 -func (col1 Color) BlendHcl(col2 Color, t float64) Color { - h1, c1, l1 := col1.Hcl() - h2, c2, l2 := col2.Hcl() - - // https://github.com/lucasb-eyer/go-colorful/pull/60 - if c1 <= 0.00015 && c2 >= 0.00015 { - h1 = h2 - } else if c2 <= 0.00015 && c1 >= 0.00015 { - h2 = h1 - } - - // We know that h are both in [0..360] - return Hcl(interp_angle(h1, h2, t), c1+t*(c2-c1), l1+t*(l2-l1)).Clamped() -} - -// LuvLch - -// Converts the given color to LuvLCh space using D65 as reference white. -// h values are in [0..360], C and L values are in [0..1] although C can overshoot 1.0 -func (col Color) LuvLCh() (l, c, h float64) { - return col.LuvLChWhiteRef(D65) -} - -func LuvToLuvLCh(L, u, v float64) (l, c, h float64) { - // Oops, floating point workaround necessary if u ~= v and both are very small (i.e. almost zero). - if math.Abs(v-u) > 1e-4 && math.Abs(u) > 1e-4 { - h = math.Mod(57.29577951308232087721*math.Atan2(v, u)+360.0, 360.0) // Rad2Deg - } else { - h = 0.0 - } - l = L - c = math.Sqrt(sq(u) + sq(v)) - return -} - -// Converts the given color to LuvLCh space, taking into account -// a given reference white. (i.e. the monitor's white) -// h values are in [0..360], c and l values are in [0..1] -func (col Color) LuvLChWhiteRef(wref [3]float64) (l, c, h float64) { - return LuvToLuvLCh(col.LuvWhiteRef(wref)) -} - -// Generates a color by using data given in LuvLCh space using D65 as reference white. -// h values are in [0..360], C and L values are in [0..1] -// WARNING: many combinations of `l`, `c`, and `h` values do not have corresponding -// valid RGB values, check the FAQ in the README if you're unsure. -func LuvLCh(l, c, h float64) Color { - return LuvLChWhiteRef(l, c, h, D65) -} - -func LuvLChToLuv(l, c, h float64) (L, u, v float64) { - H := 0.01745329251994329576 * h // Deg2Rad - u = c * math.Cos(H) - v = c * math.Sin(H) - L = l - return -} - -// Generates a color by using data given in LuvLCh space, taking -// into account a given reference white. (i.e. the monitor's white) -// h values are in [0..360], C and L values are in [0..1] -func LuvLChWhiteRef(l, c, h float64, wref [3]float64) Color { - L, u, v := LuvLChToLuv(l, c, h) - return LuvWhiteRef(L, u, v, wref) -} - -// BlendLuvLCh blends two colors in the cylindrical CIELUV color space. -// t == 0 results in c1, t == 1 results in c2 -func (col1 Color) BlendLuvLCh(col2 Color, t float64) Color { - l1, c1, h1 := col1.LuvLCh() - l2, c2, h2 := col2.LuvLCh() - - // We know that h are both in [0..360] - return LuvLCh(l1+t*(l2-l1), c1+t*(c2-c1), interp_angle(h1, h2, t)) -} - -/// OkLab /// -/////////// - -func (col Color) OkLab() (l, a, b float64) { - return XyzToOkLab(col.Xyz()) -} - -func OkLab(l, a, b float64) Color { - return Xyz(OkLabToXyz(l, a, b)) -} - -func XyzToOkLab(x, y, z float64) (l, a, b float64) { - l_ := math.Cbrt(0.8189330101*x + 0.3618667424*y - 0.1288597137*z) - m_ := math.Cbrt(0.0329845436*x + 0.9293118715*y + 0.0361456387*z) - s_ := math.Cbrt(0.0482003018*x + 0.2643662691*y + 0.6338517070*z) - l = 0.2104542553*l_ + 0.7936177850*m_ - 0.0040720468*s_ - a = 1.9779984951*l_ - 2.4285922050*m_ + 0.4505937099*s_ - b = 0.0259040371*l_ + 0.7827717662*m_ - 0.8086757660*s_ - return -} - -func OkLabToXyz(l, a, b float64) (x, y, z float64) { - l_ := 0.9999999984505196*l + 0.39633779217376774*a + 0.2158037580607588*b - m_ := 1.0000000088817607*l - 0.10556134232365633*a - 0.0638541747717059*b - s_ := 1.0000000546724108*l - 0.08948418209496574*a - 1.2914855378640917*b - - ll := math.Pow(l_, 3) - m := math.Pow(m_, 3) - s := math.Pow(s_, 3) - - x = 1.2268798733741557*ll - 0.5578149965554813*m + 0.28139105017721594*s - y = -0.04057576262431372*ll + 1.1122868293970594*m - 0.07171106666151696*s - z = -0.07637294974672142*ll - 0.4214933239627916*m + 1.5869240244272422*s - - return -} - -// BlendOkLab blends two colors in the OkLab color-space, which should result in a better blend (even compared to BlendLab). -func (c1 Color) BlendOkLab(c2 Color, t float64) Color { - l1, a1, b1 := c1.OkLab() - l2, a2, b2 := c2.OkLab() - return OkLab(l1+t*(l2-l1), - a1+t*(a2-a1), - b1+t*(b2-b1)) -} - -/// OkLch /// -/////////// - -func (col Color) OkLch() (l, c, h float64) { - return OkLabToOkLch(col.OkLab()) -} - -func OkLch(l, c, h float64) Color { - return Xyz(OkLchToXyz(l, c, h)) -} - -func XyzToOkLch(x, y, z float64) (float64, float64, float64) { - l, c, h := OkLabToOkLch(XyzToOkLab(x, y, z)) - return l, c, h -} - -func OkLchToXyz(l, c, h float64) (float64, float64, float64) { - x, y, z := OkLabToXyz(OkLchToOkLab(l, c, h)) - return x, y, z -} - -func OkLabToOkLch(l, a, b float64) (float64, float64, float64) { - c := math.Sqrt((a * a) + (b * b)) - h := math.Atan2(b, a) - if h < 0 { - h += 2 * math.Pi - } - - return l, c, h * 180 / math.Pi -} - -func OkLchToOkLab(l, c, h float64) (float64, float64, float64) { - h *= math.Pi / 180 - a := c * math.Cos(h) - b := c * math.Sin(h) - return l, a, b -} - -// BlendOkLch blends two colors in the OkLch color-space, which should result in a better blend (even compared to BlendHcl). -func (col1 Color) BlendOkLch(col2 Color, t float64) Color { - l1, c1, h1 := col1.OkLch() - l2, c2, h2 := col2.OkLch() - - // https://github.com/lucasb-eyer/go-colorful/pull/60 - if c1 <= 0.00015 && c2 >= 0.00015 { - h1 = h2 - } else if c2 <= 0.00015 && c1 >= 0.00015 { - h2 = h1 - } - - // We know that h are both in [0..360] - return OkLch(l1+t*(l2-l1), c1+t*(c2-c1), interp_angle(h1, h2, t)).Clamped() -} diff --git a/vendor/github.com/lucasb-eyer/go-colorful/happy_palettegen.go b/vendor/github.com/lucasb-eyer/go-colorful/happy_palettegen.go deleted file mode 100644 index 0cb9286cb14..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/happy_palettegen.go +++ /dev/null @@ -1,29 +0,0 @@ -package colorful - -// Uses the HSV color space to generate colors with similar S,V but distributed -// evenly along their Hue. This is fast but not always pretty. -// If you've got time to spare, use Lab (the non-fast below). -func FastHappyPaletteWithRand(colorsCount int, rand RandInterface) (colors []Color) { - colors = make([]Color, colorsCount) - - for i := 0; i < colorsCount; i++ { - colors[i] = Hsv(float64(i)*(360.0/float64(colorsCount)), 0.8+rand.Float64()*0.2, 0.65+rand.Float64()*0.2) - } - return -} - -func FastHappyPalette(colorsCount int) (colors []Color) { - return FastHappyPaletteWithRand(colorsCount, getDefaultGlobalRand()) -} - -func HappyPaletteWithRand(colorsCount int, rand RandInterface) ([]Color, error) { - pimpy := func(l, a, b float64) bool { - _, c, _ := LabToHcl(l, a, b) - return 0.3 <= c && 0.4 <= l && l <= 0.8 - } - return SoftPaletteExWithRand(colorsCount, SoftPaletteSettings{pimpy, 50, true}, rand) -} - -func HappyPalette(colorsCount int) ([]Color, error) { - return HappyPaletteWithRand(colorsCount, getDefaultGlobalRand()) -} diff --git a/vendor/github.com/lucasb-eyer/go-colorful/hexcolor.go b/vendor/github.com/lucasb-eyer/go-colorful/hexcolor.go deleted file mode 100644 index ad8b06cc942..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/hexcolor.go +++ /dev/null @@ -1,87 +0,0 @@ -package colorful - -import ( - "database/sql/driver" - "encoding/json" - "fmt" - "reflect" -) - -// A HexColor is a Color stored as a hex string "#rrggbb". It implements the -// database/sql.Scanner, database/sql/driver.Value, -// encoding/json.Unmarshaler and encoding/json.Marshaler interfaces. -type HexColor Color - -type errUnsupportedType struct { - got interface{} - want reflect.Type -} - -func (hc *HexColor) Scan(value interface{}) error { - s, ok := value.(string) - if !ok { - return errUnsupportedType{got: reflect.TypeOf(value), want: reflect.TypeOf("")} - } - c, err := Hex(s) - if err != nil { - return err - } - *hc = HexColor(c) - return nil -} - -func (hc *HexColor) Value() (driver.Value, error) { - return Color(*hc).Hex(), nil -} - -func (e errUnsupportedType) Error() string { - return fmt.Sprintf("unsupported type: got %v, want a %s", e.got, e.want) -} - -func (hc *HexColor) UnmarshalJSON(data []byte) error { - var hexCode string - if err := json.Unmarshal(data, &hexCode); err != nil { - return err - } - - var col, err = Hex(hexCode) - if err != nil { - return err - } - *hc = HexColor(col) - return nil -} - -func (hc HexColor) MarshalJSON() ([]byte, error) { - return json.Marshal(Color(hc).Hex()) -} - -// Decode - deserialize function for https://github.com/kelseyhightower/envconfig -func (hc *HexColor) Decode(hexCode string) error { - var col, err = Hex(hexCode) - if err != nil { - return err - } - *hc = HexColor(col) - return nil -} - -func (hc HexColor) MarshalYAML() (interface{}, error) { - return Color(hc).Hex(), nil -} - -func (hc *HexColor) UnmarshalYAML(unmarshal func(interface{}) error) error { - var hexCode string - if err := unmarshal(&hexCode); err != nil { - return err - } - - var col, err = Hex(hexCode) - if err != nil { - return err - } - - *hc = HexColor(col) - - return nil -} diff --git a/vendor/github.com/lucasb-eyer/go-colorful/hsluv-snapshot-rev4.json b/vendor/github.com/lucasb-eyer/go-colorful/hsluv-snapshot-rev4.json deleted file mode 100644 index 16354abf510..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/hsluv-snapshot-rev4.json +++ /dev/null @@ -1 +0,0 @@ -{"#11ee00":{"lch":[82.5213119008325577,127.202882727266427,127.478988192005161],"luv":[82.5213119008325577,-77.3991947082883627,100.945222931227221],"rgb":[0.0666666666666666657,0.933333333333333348,0],"xyz":[0.308043578886299796,0.612655858810891907,0.102019012460713238],"hpluv":[127.478988192005161,308.195222762673438,82.5213119008325577],"hsluv":[127.478988192005161,100.000000000002416,82.5213119008325577]},"#11ee11":{"lch":[82.5429986110943759,126.352581314528209,127.715012949240403],"luv":[82.5429986110943759,-77.2942129186682,99.9528861720763473],"rgb":[0.0666666666666666657,0.933333333333333348,0.0666666666666666657],"xyz":[0.3090552443859369,0.613060525010746815,0.107347117425468874],"hpluv":[127.715012949240403,306.573296560288782,82.5429986110943759],"hsluv":[127.715012949240403,98.9038130800949205,82.5429986110943759]},"#11ee22":{"lch":[82.5831747617793184,124.791738379333623,128.158354445562821],"luv":[82.5831747617793184,-77.1009570540098,98.1245147202868253],"rgb":[0.0666666666666666657,0.933333333333333348,0.133333333333333331],"xyz":[0.310930602524413957,0.613810668266137616,0.117224003621448067],"hpluv":[128.158354445562821,303.59085997924285,82.5831747617793184],"hsluv":[128.158354445562821,98.9085620232469864,82.5831747617793184]},"#11ee33":{"lch":[82.6492529720821381,122.265269823008623,128.905098358231896],"luv":[82.6492529720821381,-76.7865393115689301,95.1452762119380537],"rgb":[0.0666666666666666657,0.933333333333333348,0.2],"xyz":[0.314018353256871663,0.615045768559120742,0.133486157479059203],"hpluv":[128.905098358231896,298.749143147736106,82.6492529720821381],"hsluv":[128.905098358231896,98.916292078887,82.6492529720821381]},"#11ee44":{"lch":[82.7444986901015511,118.712635154498344,130.021230388522838],"luv":[82.7444986901015511,-76.3407023620842,90.9108734321077],"rgb":[0.0666666666666666657,0.933333333333333348,0.266666666666666663],"xyz":[0.318476348501090578,0.616828966656808308,0.156964932431945842],"hpluv":[130.021230388522838,291.911386756693616,82.7444986901015511],"hsluv":[130.021230388522838,98.9272612770947148,82.7444986901015511]},"#11ee55":{"lch":[82.8716000285422894,114.135934527262179,131.587310643629934],"luv":[82.8716000285422894,-75.758934185545,85.3674144008224],"rgb":[0.0666666666666666657,0.933333333333333348,0.333333333333333315],"xyz":[0.324438762540452563,0.619213932272553058,0.188366979705919757],"hpluv":[131.587310643629934,283.052591495130912,82.8716000285422894],"hsluv":[131.587310643629934,98.941589727101146,82.8716000285422894]},"#11ee66":{"lch":[83.0328193013522622,108.602333046050703,133.707640253052432],"luv":[83.0328193013522622,-75.0419109433949103,78.5059128028513697],"rgb":[0.0666666666666666657,0.933333333333333348,0.4],"xyz":[0.332023758313960748,0.622247930581956377,0.228314624113063719],"hpluv":[133.707640253052432,272.269449526145593,83.0328193013522622],"hsluv":[133.707640253052432,98.9592735060659,83.0328193013522622]},"#11ee77":{"lch":[83.2300736177455747,102.250357200027821,136.520544097163679],"luv":[83.2300736177455747,-74.1950209885278866,70.3579022430685228],"rgb":[0.0666666666666666657,0.933333333333333348,0.466666666666666674],"xyz":[0.341337771334162654,0.625973535790037228,0.277368426019461656],"hpluv":[136.520544097163679,259.803949175129901,83.2300736177455747],"hsluv":[136.520544097163679,98.9801962733070155,83.2300736177455747]},"#11ee88":{"lch":[83.4649827070576151,95.3003261118453651,140.209511574476238],"luv":[83.4649827070576151,-73.2277962837693082,60.990507527375577],"rgb":[0.0666666666666666657,0.933333333333333348,0.533333333333333326],"xyz":[0.352478188436106454,0.63042970263081477,0.336041289423033795],"hpluv":[140.209511574476238,246.084644167270028,83.4649827070576151],"hsluv":[140.209511574476238,99.0041428894333109,83.4649827070576151]},"#11ee99":{"lch":[83.7388997377875626,88.07037792773761,145.011549795441141],"luv":[83.7388997377875626,-72.1532115864445416,50.5005497603372433],"rgb":[0.0666666666666666657,0.933333333333333348,0.6],"xyz":[0.365535152545179209,0.635652488274444,0.404807967064151675],"hpluv":[145.011549795441141,231.793725377578141,83.7388997377875626],"hsluv":[145.011549795441141,99.0308160530368582,83.7388997377875626]},"#11eeaa":{"lch":[84.0529327571252907,80.9984265129003802,151.210882439188083],"luv":[84.0529327571252907,-70.9868724309634729,39.0078074241021824],"rgb":[0.0666666666666666657,0.933333333333333348,0.66666666666666663],"xyz":[0.38059284551043171,0.641675565460545,0.484111816681150331],"hpluv":[151.210882439188083,217.967504021816438,84.0529327571252907],"hsluv":[151.210882439188083,99.0598554997167895,84.0529327571252907]},"#11eebb":{"lch":[84.4079608499599914,74.6634505604909435,159.089705667287262],"luv":[84.4079608499599914,-69.7461428935043273,26.6478216947980826],"rgb":[0.0666666666666666657,0.933333333333333348,0.733333333333333282],"xyz":[0.397730437617768384,0.648530602303479808,0.574369801779792],"hpluv":[159.089705667287262,206.122134265545043,84.4079608499599914],"hsluv":[159.089705667287262,99.0908585861444209,84.4079608499599914]},"#11eecc":{"lch":[84.8046473826435,69.7804076798411,168.790807110150524],"luv":[84.8046473826435,-68.449275126866155,13.5647348138992196],"rgb":[0.0666666666666666657,0.933333333333333348,0.8],"xyz":[0.417022813061490139,0.656247552480968666,0.675976312450062178],"hpluv":[168.790807110150524,198.342538571842852,84.8046473826435],"hsluv":[168.790807110150524,99.123400814408285,84.8046473826435]},"#11eedd":{"lch":[85.2434517572140749,67.1146678094459,180.081412911690762],"luv":[85.2434517572140749,-67.114600056421537,-0.0953647673755886743],"rgb":[0.0666666666666666657,0.933333333333333348,0.866666666666666696],"xyz":[0.438541138612123627,0.664854882701222172,0.789306160350068176],"hpluv":[180.081412911690762,197.173954094180345,85.2434517572140749],"hsluv":[180.081412911690762,99.1570549081779546,85.2434517572140749]},"#11eeee":{"lch":[85.7246405502341275,67.2734484234975,192.17705063006116],"luv":[85.7246405502341275,-65.759826803247222,-14.1902093570146306],"rgb":[0.0666666666666666657,0.933333333333333348,0.933333333333333348],"xyz":[0.462353318878298392,0.674379754807692189,0.914716976418591399],"hpluv":[192.17705063006116,205.138082793863816,85.7246405502341275],"hsluv":[192.17705063006116,99.1914073274009098,85.7246405502341275]},"#11eeff":{"lch":[86.2482985645723517,70.4606934075819282,203.935071880927921],"luv":[86.2482985645723517,-64.401481656171029,-28.585983907627277],"rgb":[0.0666666666666666657,0.933333333333333348,1],"xyz":[0.488524367288129757,0.684848174171624913,1.05255116471037335],"hpluv":[203.935071880927921,224.026806300523,86.2482985645723517],"hsluv":[203.935071880927921,99.9999999999942304,86.2482985645723517]},"#11ff00":{"lch":[87.7931168603164,135.408535196841626,127.513270797457935],"luv":[87.7931168603164,-82.4563732780469,107.407718111808407],"rgb":[0.0666666666666666657,1,0],"xyz":[0.359895951315973628,0.716360603670241,0.119303136603937349],"hpluv":[127.513270797457935,491.310985978769054,87.7931168603164],"hsluv":[127.513270797457935,100.000000000002373,87.7931168603164]},"#11ff11":{"lch":[87.8126571401035108,134.634318462908169,127.715012949240432],"luv":[87.8126571401035108,-82.3604359259359313,106.50426424355777],"rgb":[0.0666666666666666657,1,0.0666666666666666657],"xyz":[0.360907616815610732,0.716765269870095922,0.124631241568692985],"hpluv":[127.715012949240432,489.364334505449051,87.8126571401035108],"hsluv":[127.715012949240432,99.999999999991914,87.8126571401035108]},"#11ff22":{"lch":[87.848860165327963,133.211117719966126,128.093229681784152],"luv":[87.848860165327963,-82.1836510367646,104.838205757586152],"rgb":[0.0666666666666666657,1,0.133333333333333331],"xyz":[0.362782974954087789,0.717515413125486723,0.134508127764672164],"hpluv":[128.093229681784152,485.7796458877379,87.848860165327963],"hsluv":[128.093229681784152,99.9999999999918572,87.848860165327963]},"#11ff33":{"lch":[87.9084130007832698,130.901693692038833,128.728166832562891],"luv":[87.9084130007832698,-81.8955348861488659,102.119414300885666],"rgb":[0.0666666666666666657,1,0.2],"xyz":[0.365870725686545495,0.71875051341846985,0.150770281622283314],"hpluv":[128.728166832562891,479.945632467831388,87.9084130007832698],"hsluv":[128.728166832562891,99.9999999999919567,87.9084130007832698]},"#11ff44":{"lch":[87.9942732352876,127.641489512823171,129.672386074694657],"luv":[87.9942732352876,-81.4859348177847806,98.2465891108891185],"rgb":[0.0666666666666666657,1,0.266666666666666663],"xyz":[0.37032872093076441,0.720533711516157416,0.174249056575169953],"hpluv":[129.672386074694657,471.674193406224788,87.9942732352876],"hsluv":[129.672386074694657,99.9999999999918856,87.9942732352876]},"#11ff55":{"lch":[88.1088871723243727,123.41738800901625,130.987994113812931],"luv":[88.1088871723243727,-80.9495722978130772,93.1612494966077662],"rgb":[0.0666666666666666657,1,0.333333333333333315],"xyz":[0.376291134970126395,0.722918677131902165,0.205651103849143868],"hpluv":[130.987994113812931,460.897243009671797,88.1088871723243727],"hsluv":[130.987994113812931,99.9999999999917,88.1088871723243727]},"#11ff66":{"lch":[88.2543278429396,118.268592142924746,132.753132104158254],"luv":[88.2543278429396,-80.2855559529031382,86.8429006471038],"rgb":[0.0666666666666666657,1,0.4],"xyz":[0.38387613074363458,0.725952675441305484,0.24559874825628783],"hpluv":[132.753132104158254,447.675525940365219,88.2543278429396],"hsluv":[132.753132104158254,99.9999999999916724,88.2543278429396]},"#11ff77":{"lch":[88.4323687925046613,112.290518027227137,135.069051083024959],"luv":[88.4323687925046613,-79.4970211443964558,79.3056370505300521],"rgb":[0.0666666666666666657,1,0.466666666666666674],"xyz":[0.393190143763836486,0.729678280649386335,0.294652550162685767],"hpluv":[135.069051083024959,432.222948715922314,88.4323687925046613],"hsluv":[135.069051083024959,99.999999999991644,88.4323687925046613]},"#11ff88":{"lch":[88.6445280109338825,105.641380676940045,138.068036362648229],"luv":[88.6445280109338825,-78.5907289650887577,70.5946076698930369],"rgb":[0.0666666666666666657,1,0.533333333333333326],"xyz":[0.404330560865780286,0.734134447490163877,0.353325413566257907],"hpluv":[138.068036362648229,414.95023459347243,88.6445280109338825],"hsluv":[138.068036362648229,99.9999999999915,88.6445280109338825]},"#11ff99":{"lch":[88.8920961876840465,98.552301258979881,141.921030988541872],"luv":[88.8920961876840465,-77.5765731609304225,60.7818342932124338],"rgb":[0.0666666666666666657,1,0.6],"xyz":[0.417387524974853,0.73935723313379309,0.422092091207375786],"hpluv":[141.921030988541872,396.537381185702543,88.8920961876840465],"hsluv":[141.921030988541872,99.9999999999913456,88.8920961876840465]},"#11ffaa":{"lch":[89.1761561490339147,91.3418654410923523,146.840553381528281],"luv":[89.1761561490339147,-76.4669946123218,49.9613362233014158],"rgb":[0.0666666666666666657,1,0.66666666666666663],"xyz":[0.432445217940105542,0.745380310319894157,0.501395940824374442],"hpluv":[146.840553381528281,378.048392077141443,89.1761561490339147],"hsluv":[146.840553381528281,99.9999999999913,89.1761561490339147]},"#11ffbb":{"lch":[89.4975971674113,84.4340589142561413,153.067388238784645],"luv":[89.4975971674113,-75.2763296710648859,38.2437510711127],"rgb":[0.0666666666666666657,1,0.733333333333333282],"xyz":[0.449582810047442216,0.752235347162828916,0.591653925923016133],"hpluv":[153.067388238784645,361.099415032935838,89.4975971674113],"hsluv":[153.067388238784645,99.9999999999909335,89.4975971674113]},"#11ffcc":{"lch":[89.8571262823018628,78.3714319324892159,160.817799258328876],"luv":[89.8571262823018628,-74.0201316086282901,25.7507564896672143],"rgb":[0.0666666666666666657,1,0.8],"xyz":[0.468875185491163915,0.759952297340317773,0.693260436593286289],"hpluv":[160.817799258328876,348.067615225706845,89.8571262823018628],"hsluv":[160.817799258328876,99.999999999991,89.8571262823018628]},"#11ffdd":{"lch":[90.2552779380141317,73.7997451229305,170.162013498752287],"luv":[90.2552779380141317,-72.7145076797264238,12.6096293801408788],"rgb":[0.0666666666666666657,1,0.866666666666666696],"xyz":[0.490393511041797514,0.768559627560571279,0.806590284493292287],"hpluv":[170.162013498752287,342.256686666565315,90.2552779380141317],"hsluv":[170.162013498752287,99.999999999990834,90.2552779380141317]},"#11ffee":{"lch":[90.6924227584195819,71.3832589696730793,180.844217403257659],"luv":[90.6924227584195819,-71.3755103944163665,-1.05174952720347981],"rgb":[0.0666666666666666657,1,0.933333333333333348],"xyz":[0.514205691307972224,0.778084499667041296,0.93200110056181551],"hpluv":[180.844217403257659,347.82122947809512,90.6924227584195819],"hsluv":[180.844217403257659,99.9999999999901803,90.6924227584195819]},"#11ffff":{"lch":[91.1687759776689859,71.6302608322469467,192.17705063006116],"luv":[91.1687759776689859,-70.0186129384549361,-15.1092061032524665],"rgb":[0.0666666666666666657,1,1],"xyz":[0.540376739717803645,0.788552919030974,1.06983528885359735],"hpluv":[192.17705063006116,369.258709956275879,91.1687759776689859],"hsluv":[192.17705063006116,99.9999999999898108,91.1687759776689859]},"#00aa00":{"lch":[60.5587499434736287,93.727653253516209,127.71501294924046],"luv":[60.5587499434736287,-57.3364240886418415,74.1445038903004559],"rgb":[0,0.66666666666666663,0],"xyz":[0.143740958848290495,0.287481917696585,0.0479136529494288144],"hpluv":[127.71501294924046,196.394882900214554,60.5587499434736287],"hsluv":[127.71501294924046,100.000000000002359,60.5587499434736287]},"#00aa11":{"lch":[60.5946550577951939,92.4075267438518182,128.220974416403209],"luv":[60.5946550577951939,-57.1721703645967665,72.5981675713458543],"rgb":[0,0.66666666666666663,0.0666666666666666657],"xyz":[0.144752624347927628,0.28788658389643984,0.0532417579141844441],"hpluv":[128.220974416403209,193.513984665985475,60.5946550577951939],"hsluv":[128.220974416403209,99.9999999999907772,60.5946550577951939]},"#00aa22":{"lch":[60.661124672570665,90.0113827545795715,129.185497299711983],"luv":[60.661124672570665,-56.8721735728637725,69.7682226983709199],"rgb":[0,0.66666666666666663,0.133333333333333331],"xyz":[0.146627982486404629,0.288636727151830641,0.0631186441101636436],"hpluv":[129.185497299711983,188.289586599726533,60.661124672570665],"hsluv":[129.185497299711983,99.9999999999908624,60.661124672570665]},"#00aa33":{"lch":[60.7703154938824355,86.2098857925288513,130.852037745481823],"luv":[60.7703154938824355,-56.3905639077229353,65.2092685937350751],"rgb":[0,0.66666666666666663,0.2],"xyz":[0.14971573321886239,0.289871827444813768,0.0793807979677747799],"hpluv":[130.852037745481823,180.013429236819462,60.7703154938824355],"hsluv":[130.852037745481823,99.9999999999908,60.7703154938824355]},"#00aa44":{"lch":[60.9274158721733841,81.0355822964375108,133.441426631804489],"luv":[60.9274158721733841,-55.7210928860807044,58.8381288426430444],"rgb":[0,0.66666666666666663,0.266666666666666663],"xyz":[0.15417372846308125,0.291655025542501334,0.102859572920661418],"hpluv":[133.441426631804489,168.77274926729055,60.9274158721733841],"hsluv":[133.441426631804489,99.9999999999908908,60.9274158721733841]},"#00aa55":{"lch":[61.1365343944832915,74.6960845180523592,137.272019015051796],"luv":[61.1365343944832915,-54.8704978500827636,50.6826746335676717],"rgb":[0,0.66666666666666663,0.333333333333333315],"xyz":[0.160136142502443235,0.294039991158246194,0.134261620194635334],"hpluv":[137.272019015051796,155.037353806827582,61.1365343944832915],"hsluv":[137.272019015051796,99.9999999999910614,61.1365343944832915]},"#00aa66":{"lch":[61.4009335299549264,67.6053275851037512,142.80970662058607],"luv":[61.4009335299549264,-53.8565898531593703,40.8649978254956139],"rgb":[0,0.66666666666666663,0.4],"xyz":[0.16772113827595142,0.297073989467649513,0.174209264601779296],"hpluv":[142.80970662058607,139.715720243970395,61.4009335299549264],"hsluv":[142.80970662058607,99.9999999999911893,61.4009335299549264]},"#00aa77":{"lch":[61.7231520087844814,60.4394033477847188,150.696962972825474],"luv":[61.7231520087844814,-52.7057786154446859,29.5807771631501382],"rgb":[0,0.66666666666666663,0.466666666666666674],"xyz":[0.177035151296153326,0.300799594675730309,0.223263066508177205],"hpluv":[150.696962972825474,124.254291935777843,61.7231520087844814],"hsluv":[150.696962972825474,99.9999999999911893,61.7231520087844814]},"#00aa88":{"lch":[62.1050795642419615,54.2095218153359397,161.640221068188367],"luv":[62.1050795642419615,-51.4501140589855126,17.0750700954568337],"rgb":[0,0.66666666666666663,0.533333333333333326],"xyz":[0.188175568398097182,0.305255761516507906,0.281935929911749372],"hpluv":[161.640221068188367,110.761232665855573,62.1050795642419615],"hsluv":[161.640221068188367,99.9999999999911466,62.1050795642419615]},"#00aa99":{"lch":[62.5480102999456307,50.2545412813378576,175.872445658321794],"luv":[62.5480102999456307,-50.1241952468173793,3.61717711159146882],"rgb":[0,0.66666666666666663,0.6],"xyz":[0.201232532507169881,0.310478547160137064,0.350702607552867307],"hpluv":[175.872445658321794,101.95326553071466,62.5480102999456307],"hsluv":[175.872445658321794,99.9999999999913314,62.5480102999456307]},"#00aaaa":{"lch":[63.0526871437625829,49.8847230087107931,192.17705063006116],"luv":[63.0526871437625829,-48.762339705407328,-10.5223484123201398],"rgb":[0,0.66666666666666663,0.66666666666666663],"xyz":[0.216290225472422437,0.316501624346238186,0.430006457169865852],"hpluv":[192.17705063006116,100.392967527320806,63.0526871437625829],"hsluv":[192.17705063006116,99.9999999999914451,63.0526871437625829]},"#00aabb":{"lch":[63.6193436646561565,53.6276681768737,207.895374658889665],"luv":[63.6193436646561565,-47.3963155750249143,-25.0901587081772526],"rgb":[0,0.66666666666666663,0.733333333333333282],"xyz":[0.233427817579759056,0.323356661189172945,0.520264442268507654],"hpluv":[207.895374658889665,106.964349821245364,63.6193436646561565],"hsluv":[207.895374658889665,99.9999999999916,63.6193436646561565]},"#00aacc":{"lch":[64.2477463386430259,60.9097449106327886,220.878520684721707],"luv":[64.2477463386430259,-46.0537892020538,-39.8628338833449],"rgb":[0,0.66666666666666663,0.8],"xyz":[0.25272019302348081,0.331073611366661746,0.62187095293877781],"hpluv":[220.878520684721707,120.300715116377788,64.2477463386430259],"hsluv":[220.878520684721707,99.9999999999916298,64.2477463386430259]},"#00aadd":{"lch":[64.9372385342214926,70.6418801813473465,230.685034316882962],"luv":[64.9372385342214926,-44.7574928469198525,-54.6538385624811625],"rgb":[0,0.66666666666666663,0.866666666666666696],"xyz":[0.274238518574114354,0.339680941586915253,0.735200800838783808],"hpluv":[230.685034316882962,138.04089297290011,64.9372385342214926],"hsluv":[230.685034316882962,99.9999999999918145,64.9372385342214926]},"#00aaee":{"lch":[65.6867863979168618,81.8478503674051,237.87423205753521],"luv":[65.6867863979168618,-43.5250094774703129,-69.3155405356638283],"rgb":[0,0.66666666666666663,0.933333333333333348],"xyz":[0.298050698840289119,0.34920581369338527,0.860611616907307],"hpluv":[237.87423205753521,158.11336767521891,65.6867863979168618],"hsluv":[237.87423205753521,99.999999999991843,65.6867863979168618]},"#00aaff":{"lch":[66.4950261675888,93.8462134827344,243.161780722675303],"luv":[66.4950261675888,-42.369016683119284,-83.7375555551541],"rgb":[0,0.66666666666666663,1],"xyz":[0.324221747250120484,0.359674233057318,0.998445805199088876],"hpluv":[243.161780722675303,179.088178632175044,66.4950261675888],"hsluv":[243.161780722675303,99.9999999999982805,66.4950261675888]},"#00bb00":{"lch":[66.1662429166961772,102.406451239047826,127.71501294924046],"luv":[66.1662429166961772,-62.6455428450044352,81.0099822060849135],"rgb":[0,0.733333333333333282,0],"xyz":[0.177695456756889275,0.355390913513783546,0.0592318189189614333],"hpluv":[127.71501294924046,196.39488290021464,66.1662429166961772],"hsluv":[127.71501294924046,100.000000000002373,66.1662429166961772]},"#00bb11":{"lch":[66.1974173108447559,101.237205455569821,128.123527834983577],"luv":[66.1974173108447559,-62.4996967519340956,79.6414444518024425],"rgb":[0,0.733333333333333282,0.0666666666666666657],"xyz":[0.178707122256526407,0.355795579713638399,0.064559923883717063],"hpluv":[128.123527834983577,194.061073356438868,66.1974173108447559],"hsluv":[128.123527834983577,99.9999999999909335,66.1974173108447559]},"#00bb22":{"lch":[66.2551438620851911,99.1062916374383747,128.898124119483072],"luv":[66.2551438620851911,-62.232564676438038,77.1308299962987718],"rgb":[0,0.733333333333333282,0.133333333333333331],"xyz":[0.180582480395003409,0.3565457229690292,0.0744368100796962556],"hpluv":[128.898124119483072,189.810813804630897,66.2551438620851911],"hsluv":[128.898124119483072,99.9999999999908624,66.2551438620851911]},"#00bb33":{"lch":[66.3500136661217255,95.7008075372637137,130.224174268563928],"luv":[66.3500136661217255,-61.8016571104716235,73.0698278476423155],"rgb":[0,0.733333333333333282,0.2],"xyz":[0.18367023112746117,0.357780823262012326,0.0906989639373074],"hpluv":[130.224174268563928,183.02647365261987,66.3500136661217255],"hsluv":[130.224174268563928,99.9999999999909335,66.3500136661217255]},"#00bb44":{"lch":[66.4865992404304,91.0092453899789859,132.255785626190885],"luv":[66.4865992404304,-61.1983980271068,67.3605138443080875],"rgb":[0,0.733333333333333282,0.266666666666666663],"xyz":[0.18812822637168003,0.359564021359699892,0.114177738890194044],"hpluv":[132.255785626190885,173.696361176634838,66.4865992404304],"hsluv":[132.255785626190885,99.9999999999909619,66.4865992404304]},"#00bb55":{"lch":[66.6685736373934219,85.1496193371524,135.204737263674588],"luv":[66.6685736373934219,-60.4246386982598,59.9943389949978751],"rgb":[0,0.733333333333333282,0.333333333333333315],"xyz":[0.194090640411042015,0.361948986975444753,0.145579786164167946],"hpluv":[135.204737263674588,162.069343805127659,66.6685736373934219],"hsluv":[135.204737263674588,99.9999999999909477,66.6685736373934219]},"#00bb66":{"lch":[66.8989180170192412,78.3861452968700689,139.371990675590268],"luv":[66.8989180170192412,-59.4914065933894,51.0407711152765629],"rgb":[0,0.733333333333333282,0.4],"xyz":[0.2016756361845502,0.364982985284848072,0.185527430571311908],"hpluv":[139.371990675590268,148.682392510907704,66.8989180170192412],"hsluv":[139.371990675590268,99.9999999999911182,66.8989180170192412]},"#00bb77":{"lch":[67.1800303821267448,71.1598447269708316,145.178146497089472],"luv":[67.1800303821267448,-58.4173559625633061,40.6341731047866617],"rgb":[0,0.733333333333333282,0.466666666666666674],"xyz":[0.210989649204752105,0.368708590492928867,0.234581232477709817],"hpluv":[145.178146497089472,134.410786503463328,67.1800303821267448],"hsluv":[145.178146497089472,99.9999999999910898,67.1800303821267448]},"#00bb88":{"lch":[67.5137905946342,64.1363411600919,153.159702568813543],"luv":[67.5137905946342,-57.2268359754185525,28.9578918715820954],"rgb":[0,0.733333333333333282,0.533333333333333326],"xyz":[0.222130066306695961,0.373164757333706465,0.293254095881282],"hpluv":[153.159702568813543,120.545503395456095,67.5137905946342],"hsluv":[153.159702568813543,99.9999999999911608,67.5137905946342]},"#00bb99":{"lch":[67.9016044714860811,58.2533417764790187,163.826150797364875],"luv":[67.9016044714860811,-55.9477282230567141,16.2266304205851419],"rgb":[0,0.733333333333333282,0.6],"xyz":[0.235187030415768661,0.378387542977335622,0.362020773522399919],"hpluv":[163.826150797364875,108.862958898475256,67.9016044714860811],"hsluv":[163.826150797364875,99.9999999999912461,67.9016044714860811]},"#00bbaa":{"lch":[68.3444379186728384,54.6744668749029543,177.202021912208522],"luv":[68.3444379186728384,-54.6092872876890922,2.66890801368250408],"rgb":[0,0.733333333333333282,0.66666666666666663],"xyz":[0.250244723381021217,0.384410620163436745,0.44132462313939852],"hpluv":[177.202021912208522,101.512776720033713,68.3444379186728384],"hsluv":[177.202021912208522,99.9999999999913598,68.3444379186728384]},"#00bbbb":{"lch":[68.8428468315880338,54.4656619866929645,192.177050630061132],"luv":[68.8428468315880338,-53.2402096652165113,-11.4886209116881091],"rgb":[0,0.733333333333333282,0.733333333333333282],"xyz":[0.267382315488357836,0.391265657006371503,0.531582608238040266],"hpluv":[192.177050630061132,100.392967527320806,68.8428468315880338],"hsluv":[192.177050630061132,99.9999999999914451,68.8428468315880338]},"#00bbcc":{"lch":[69.3970058395379397,58.0340346662075675,206.653495587531239],"luv":[69.3970058395379397,-51.8670935918889384,-26.0337047299998297],"rgb":[0,0.733333333333333282,0.8],"xyz":[0.28667469093207959,0.398982607183860305,0.633189118908310422],"hpluv":[206.653495587531239,106.116119046155191,69.3970058395379397],"hsluv":[206.653495587531239,99.9999999999915161,69.3970058395379397]},"#00bbdd":{"lch":[70.0067374807312461,64.9183055759271923,218.91244904401708],"luv":[70.0067374807312461,-50.5133677649133119,-40.7772740125682844],"rgb":[0,0.733333333333333282,0.866666666666666696],"xyz":[0.308193016482713134,0.407589937404113811,0.74651896680831642],"hpluv":[218.91244904401708,117.670246608059514,70.0067374807312461],"hsluv":[218.91244904401708,99.999999999991644,70.0067374807312461]},"#00bbee":{"lch":[70.6715424904064236,74.2108860535778,228.474155043258463],"luv":[70.6715424904064236,-49.1986871961444336,-55.5584807840624819],"rgb":[0,0.733333333333333282,0.933333333333333348],"xyz":[0.332005196748887843,0.417114809510583828,0.871929782876839643],"hpluv":[228.474155043258463,133.248513578578667,70.6715424904064236],"hsluv":[228.474155043258463,99.9999999999918288,70.6715424904064236]},"#00bbff":{"lch":[71.3906313155650167,85.0452269855302,235.688960914523477],"luv":[71.3906313155650167,-47.9387359102869155,-70.246481992653],"rgb":[0,0.733333333333333282,1],"xyz":[0.358176245158719264,0.427583228874516552,1.00976397116862149],"hpluv":[235.688960914523477,151.163886263776277,71.3906313155650167],"hsluv":[235.688960914523477,99.9999999999978,71.3906313155650167]},"#00cc00":{"lch":[71.6795694698327139,110.939506494120423,127.71501294924046],"luv":[71.6795694698327139,-67.8655057683618566,87.7601688009055181],"rgb":[0,0.8,0],"xyz":[0.215919200066506195,0.431838400133018441,0.0719730666888333814],"hpluv":[127.71501294924046,196.394882900214611,71.6795694698327139],"hsluv":[127.71501294924046,100.000000000002359,71.6795694698327139]},"#00cc11":{"lch":[71.7069484470386698,109.895339051400697,128.05073784188761],"luv":[71.7069484470386698,-67.7349868616780668,86.5387606802328548],"rgb":[0,0.8,0.0666666666666666657],"xyz":[0.216930865566143327,0.432243066332873294,0.0773011716535890181],"hpluv":[128.05073784188761,194.472124503698296,71.7069484470386698],"hsluv":[128.05073784188761,99.9999999999908766,71.7069484470386698]},"#00cc22":{"lch":[71.7576566073484,107.986601617430239,128.68476606632143],"luv":[71.7576566073484,-67.4954197535952289,84.2939763041676287],"rgb":[0,0.8,0.133333333333333331],"xyz":[0.218806223704620328,0.432993209588264094,0.0871780578495682107],"hpluv":[128.68476606632143,190.959361108477,71.7576566073484],"hsluv":[128.68476606632143,99.9999999999909193,71.7576566073484]},"#00cc33":{"lch":[71.8410194320707,104.91966800737103,129.762682813168567],"luv":[71.8410194320707,-67.1075822658835364,80.6517770244687853],"rgb":[0,0.8,0.2],"xyz":[0.22189397443707809,0.434228309881247221,0.103440211707179347],"hpluv":[129.762682813168567,185.320621425294917,71.8410194320707],"hsluv":[129.762682813168567,99.9999999999909761,71.8410194320707]},"#00cc44":{"lch":[71.9610975929873717,100.65733905537941,131.396818004218431],"luv":[71.9610975929873717,-66.561699323308261,75.5078809721416491],"rgb":[0,0.8,0.266666666666666663],"xyz":[0.226351969681296949,0.436011507978934787,0.126918986660065986],"hpluv":[131.396818004218431,177.495355343216744,71.9610975929873717],"hsluv":[131.396818004218431,99.9999999999909903,71.9610975929873717]},"#00cc55":{"lch":[72.1211872877728837,95.2615727691762828,133.734892870047815],"luv":[72.1211872877728837,-65.8564749898269639,68.8308938513177],"rgb":[0,0.8,0.333333333333333315],"xyz":[0.232314383720658935,0.438396473594679648,0.158321033934039901],"hpluv":[133.734892870047815,167.607792551030144,72.1211872877728837],"hsluv":[133.734892870047815,99.999999999991033,72.1211872877728837]},"#00cc66":{"lch":[72.3240060759138,88.9026050634798821,136.980115521422647],"luv":[72.3240060759138,-64.9982032665013207,60.6539921126355495],"rgb":[0,0.8,0.4],"xyz":[0.23989937949416712,0.441430471904082966,0.198268678341183863],"hpluv":[136.980115521422647,155.980870440536961,72.3240060759138],"hsluv":[136.980115521422647,99.9999999999910614,72.3240060759138]},"#00cc77":{"lch":[72.5717906268391459,81.8763194870781206,141.413175407098493],"luv":[72.5717906268391459,-63.9997644777707464,51.066249515114805],"rgb":[0,0.8,0.466666666666666674],"xyz":[0.249213392514369025,0.445156077112163762,0.247322480247581772],"hpluv":[141.413175407098493,143.162673336571032,72.5717906268391459],"hsluv":[141.413175407098493,99.9999999999910756,72.5717906268391459]},"#00cc88":{"lch":[72.8663546950801,74.6325704710961162,147.40707881161012],"luv":[72.8663546950801,-62.8793552775828672,40.2020802322297683],"rgb":[0,0.8,0.533333333333333326],"xyz":[0.260353809616312881,0.449612243952941359,0.305995343651153939],"hpluv":[147.40707881161012,129.969270924532168,72.8663546950801],"hsluv":[147.40707881161012,99.9999999999911608,72.8663546950801]},"#00cc99":{"lch":[73.2091273059676695,67.813783770663278,155.40051707617576],"luv":[73.2091273059676695,-61.6589956558222809,28.2290191825639418],"rgb":[0,0.8,0.6],"xyz":[0.273410773725385581,0.454835029596570517,0.374762021292271874],"hpluv":[155.40051707617576,117.541728748843539,73.2091273059676695],"hsluv":[155.40051707617576,99.9999999999911893,73.2091273059676695]},"#00ccaa":{"lch":[73.6011808048110368,62.2803364521242102,165.745935171574274],"luv":[73.6011808048110368,-60.3629393869259374,15.3347923742089414],"rgb":[0,0.8,0.66666666666666663],"xyz":[0.288468466690638137,0.460858106782671639,0.454065870909270419],"hpluv":[165.745935171574274,107.375573062224,73.6011808048110368],"hsluv":[165.745935171574274,99.9999999999912887,73.6011808048110368]},"#00ccbb":{"lch":[74.043253901593,59.041045922693165,178.335616576813749],"luv":[74.043253901593,-59.0161369965186395,1.7148404163965667],"rgb":[0,0.8,0.733333333333333282],"xyz":[0.305606058797974756,0.467713143625606398,0.544323856007912221],"hpluv":[178.335616576813749,101.183074845522739,74.043253901593],"hsluv":[178.335616576813749,99.9999999999913882,74.043253901593]},"#00cccc":{"lch":[74.5357725840108714,58.9696734274942429,192.177050630061132],"luv":[74.5357725840108714,-57.64288292201784,-12.4386668330598962],"rgb":[0,0.8,0.8],"xyz":[0.32489843424169651,0.4754300938030952,0.645930366678182377],"hpluv":[192.177050630061132,100.392967527320835,74.5357725840108714],"hsluv":[192.177050630061132,99.9999999999914877,74.5357725840108714]},"#00ccdd":{"lch":[75.0788705190671,62.3850861111967063,205.58971515357635],"luv":[75.0788705190671,-56.2657375800620656,-26.9456071312750254],"rgb":[0,0.8,0.866666666666666696],"xyz":[0.346416759792330053,0.484037424023348706,0.759260214578188375],"hpluv":[205.58971515357635,105.439266222061761,75.0788705190671],"hsluv":[205.58971515357635,99.9999999999915588,75.0788705190671]},"#00ccee":{"lch":[75.672409810316779,68.9113069897593,217.179575991302841],"luv":[75.672409810316779,-54.9047659259632326,-41.6441461630807765],"rgb":[0,0.8,0.933333333333333348],"xyz":[0.370228940058504818,0.493562296129818723,0.884671030646711598],"hpluv":[217.179575991302841,115.555933163518176,75.672409810316779],"hsluv":[217.179575991302841,99.9999999999916,75.672409810316779]},"#00ccff":{"lch":[76.3160024985922263,77.7871508482342193,226.46755023570978],"luv":[76.3160024985922263,-53.5770891110031471,-56.3944710009551713],"rgb":[0,0.8,1],"xyz":[0.396399988468336184,0.504030715493751447,1.02250521893849333],"hpluv":[226.46755023570978,131.600547876461974,76.3160024985922263],"hsluv":[226.46755023570978,99.9999999999969731,76.3160024985922263]},"#00dd00":{"lch":[77.1074905447145369,119.34037845513086,127.715012949240503],"luv":[77.1074905447145369,-73.004607631587163,94.4057900468603],"rgb":[0,0.866666666666666696,0],"xyz":[0.258553190613681372,0.51710638122737,0.0861843968712247],"hpluv":[127.715012949240503,210.385995725156505,77.1074905447145369],"hsluv":[127.715012949240503,100.000000000002203,77.1074905447145369]},"#00dd11":{"lch":[77.1317715771024268,118.40111864948102,127.995077421524911],"luv":[77.1317715771024268,-72.8869911141770359,93.3076171797906255],"rgb":[0,0.866666666666666696,0.0666666666666666657],"xyz":[0.259564856113318476,0.517511047427224868,0.0915125018359803366],"hpluv":[127.995077421524911,208.997725019578468,77.1317715771024268],"hsluv":[127.995077421524911,99.9999999999909193,77.1317715771024268]},"#00dd22":{"lch":[77.1767486793617081,116.680170458435171,128.522366120948305],"luv":[77.1767486793617081,-72.6707542705971434,91.2864921658838568],"rgb":[0,0.866666666666666696,0.133333333333333331],"xyz":[0.261440214251795533,0.518261190682615669,0.101389388031959529],"hpluv":[128.522366120948305,206.449864525990506,77.1767486793617081],"hsluv":[128.522366120948305,99.9999999999909335,77.1767486793617081]},"#00dd33":{"lch":[77.2507083817471312,113.903613467858165,129.414072915332611],"luv":[77.2507083817471312,-72.3197155087496668,87.9993858488157485],"rgb":[0,0.866666666666666696,0.2],"xyz":[0.264527964984253239,0.519496290975598796,0.117651541889570666],"hpluv":[129.414072915332611,202.327676795977681,77.2507083817471312],"hsluv":[129.414072915332611,99.9999999999909335,77.2507083817471312]},"#00dd44":{"lch":[77.3572825066044,110.019432359123073,130.755032484191332],"luv":[77.3572825066044,-71.8235777962907633,83.3405613681826907],"rgb":[0,0.866666666666666696,0.266666666666666663],"xyz":[0.268985960228472154,0.521279489073286362,0.141130316842457304],"hpluv":[130.755032484191332,196.537344059934071,77.3572825066044],"hsluv":[130.755032484191332,99.9999999999909193,77.3572825066044]},"#00dd55":{"lch":[77.499442461574418,105.05363654061,132.652443872197409],"luv":[77.499442461574418,-71.1790335676869717,77.2645567564888],"rgb":[0,0.866666666666666696,0.333333333333333315],"xyz":[0.274948374267834139,0.523664454689031111,0.172532364116431219],"hpluv":[132.652443872197409,189.094972829508237,77.499442461574418],"hsluv":[132.652443872197409,99.9999999999909477,77.499442461574418]},"#00dd66":{"lch":[77.6796666807438,99.1151742217995,135.249123061333165],"luv":[77.6796666807438,-70.3890792332532413,69.7796194150732276],"rgb":[0,0.866666666666666696,0.4],"xyz":[0.282533370041342324,0.52669845299843443,0.212480008523575181],"hpluv":[135.249123061333165,180.139247328423863,77.6796666807438],"hsluv":[135.249123061333165,99.9999999999909903,77.6796666807438]},"#00dd77":{"lch":[77.9000291762011301,92.4061998396230138,138.73841210181584],"luv":[77.9000291762011301,-69.4623356452591878,60.9416909472136155],"rgb":[0,0.866666666666666696,0.466666666666666674],"xyz":[0.29184738306154423,0.530424058206515281,0.26153381042997309],"hpluv":[138.73841210181584,169.957910917592017,77.9000291762011301],"hsluv":[138.73841210181584,99.9999999999910187,77.9000291762011301]},"#00dd88":{"lch":[78.1622519856154,85.2389230174627386,143.3784757437721],"luv":[78.1622519856154,-68.4122000424903,50.8472701580255091],"rgb":[0,0.866666666666666696,0.533333333333333326],"xyz":[0.30298780016348803,0.534880225047292823,0.320206673833545286],"hpluv":[143.3784757437721,159.033158409305884,78.1622519856154],"hsluv":[143.3784757437721,99.9999999999911608,78.1622519856154]},"#00dd99":{"lch":[78.4677391993035798,78.0607504013048583,149.494791226300919],"luv":[78.4677391993035798,-67.2558167964087801,39.6249398770864545],"rgb":[0,0.866666666666666696,0.6],"xyz":[0.316044764272560785,0.540103010690922,0.388973351474663165],"hpluv":[149.494791226300919,148.113090063328627,78.4677391993035798],"hsluv":[149.494791226300919,99.9999999999911466,78.4677391993035798]},"#00ddaa":{"lch":[78.8176011215583401,71.4835041270533225,157.438879868811341],"luv":[78.8176011215583401,-66.0129273361177,27.4259874352573583],"rgb":[0,0.866666666666666696,0.66666666666666663],"xyz":[0.331102457237813286,0.546126087877023103,0.468277201091661766],"hpluv":[157.438879868811341,138.307036304413771,78.8176011215583401],"hsluv":[157.438879868811341,99.9999999999912319,78.8176011215583401]},"#00ddbb":{"lch":[79.21267314937,66.2909050184163675,167.440816272526462],"luv":[79.21267314937,-64.7046905962200896,14.4148223370296193],"rgb":[0,0.866666666666666696,0.733333333333333282],"xyz":[0.34824004934514996,0.552981124719957862,0.558535186190303512],"hpluv":[167.440816272526462,131.160951364069831,79.21267314937],"hsluv":[167.440816272526462,99.9999999999912319,79.21267314937]},"#00ddcc":{"lch":[79.6535319864315738,63.3571261830985,179.312753048293331],"luv":[79.6535319864315738,-63.3525685364998381,0.759932897798095253],"rgb":[0,0.866666666666666696,0.8],"xyz":[0.367532424788871714,0.560698074897446719,0.660141696860573668],"hpluv":[179.312753048293331,128.577362979680402,79.6535319864315738],"hsluv":[179.312753048293331,99.9999999999913314,79.6535319864315738]},"#00dddd":{"lch":[80.1405107346531338,63.4039144225475795,192.177050630061245],"luv":[80.1405107346531338,-61.9773555359817649,-13.3739958452306631],"rgb":[0,0.866666666666666696,0.866666666666666696],"xyz":[0.389050750339505202,0.569305405117700225,0.773471544760579666],"hpluv":[192.177050630061245,132.399857962191078,80.1405107346531338],"hsluv":[192.177050630061245,99.9999999999915,80.1405107346531338]},"#00ddee":{"lch":[80.6737137665329,66.6843941199945078,204.668960845135786],"luv":[80.6737137665329,-60.59840405426975,-27.8323884211582282],"rgb":[0,0.866666666666666696,0.933333333333333348],"xyz":[0.412862930605679967,0.578830277224170242,0.898882360829102889],"hpluv":[204.668960845135786,143.769811077134563,80.6737137665329],"hsluv":[204.668960845135786,99.9999999999914735,80.6737137665329]},"#00ddff":{"lch":[81.2530318771427,72.8883394631876627,215.643856178856652],"luv":[81.2530318771427,-59.2330695533496296,-42.475328144570291],"rgb":[0,0.866666666666666696,1],"xyz":[0.439033979015511333,0.589298696588103,1.03671654912088473],"hpluv":[215.643856178856652,162.831862460855405,81.2530318771427],"hsluv":[215.643856178856652,99.9999999999960636,81.2530318771427]},"#00ee00":{"lch":[82.4573791946470749,127.620478503329409,127.715012949240503],"luv":[82.4573791946470749,-78.0698291684561241,100.955873068518613],"rgb":[0,0.933333333333333348,0],"xyz":[0.305731966954196188,0.611463933908400925,0.101910655651395884],"hpluv":[127.715012949240503,307.908475174189959,82.4573791946470749],"hsluv":[127.715012949240503,100.000000000002217,82.4573791946470749]},"#00ee11":{"lch":[82.4790940690076582,126.770138643430457,127.951660682688043],"luv":[82.4790940690076582,-77.963182339567652,99.9620440525398],"rgb":[0,0.933333333333333348,0.0666666666666666657],"xyz":[0.306743632453833293,0.611868600108255833,0.107238760616151521],"hpluv":[127.951660682688043,306.293921948395678,82.4790940690076582],"hsluv":[127.951660682688043,99.9999999999909193,82.4790940690076582]},"#00ee22":{"lch":[82.5193223464761729,125.209295581045268,128.396138884075839],"luv":[82.5193223464761729,-77.7668632323815814,98.1309466116455],"rgb":[0,0.933333333333333348,0.133333333333333331],"xyz":[0.308618990592310349,0.612618743363646634,0.117115646812130714],"hpluv":[128.396138884075839,303.325246698320768,82.5193223464761729],"hsluv":[128.396138884075839,99.9999999999907914,82.5193223464761729]},"#00ee33":{"lch":[82.5854861516441616,122.683025615083068,129.144698003447559],"luv":[82.5854861516441616,-77.4474668310457304,95.1473313105795881],"rgb":[0,0.933333333333333348,0.2],"xyz":[0.311706741324768055,0.613853843656629761,0.133377800669741864],"hpluv":[129.144698003447559,298.506449004286878,82.5854861516441616],"hsluv":[129.144698003447559,99.9999999999910187,82.5854861516441616]},"#00ee44":{"lch":[82.680854944152216,119.131104912681948,130.263308305441626],"luv":[82.680854944152216,-76.994580956063885,90.9068460629701889],"rgb":[0,0.933333333333333348,0.266666666666666663],"xyz":[0.31616473656898697,0.615637041754317327,0.156856575622628502],"hpluv":[130.263308305441626,291.702339981024693,82.680854944152216],"hsluv":[130.263308305441626,99.9999999999908624,82.680854944152216]},"#00ee55":{"lch":[82.8081199656530913,114.556122924925475,131.832385242542614],"luv":[82.8081199656530913,-76.4036333062175572,85.3556683366704192],"rgb":[0,0.933333333333333348,0.333333333333333315],"xyz":[0.322127150608348956,0.618022007370062076,0.18825862289660239],"hpluv":[131.832385242542614,282.889526663711365,82.8081199656530913],"hsluv":[131.832385242542614,99.9999999999908908,82.8081199656530913]},"#00ee66":{"lch":[82.9695459516691756,109.025909834785097,133.955863991345211],"luv":[82.9695459516691756,-75.6753248662734137,78.4849936082476347],"rgb":[0,0.933333333333333348,0.4],"xyz":[0.329712146381857141,0.621056005679465395,0.22820626730374638],"hpluv":[133.955863991345211,272.166364406401044,82.9695459516691756],"hsluv":[133.955863991345211,99.9999999999909193,82.9695459516691756]},"#00ee77":{"lch":[83.167051813506589,102.679799146220446,136.771308753659213],"luv":[83.167051813506589,-74.8151450958190338,70.3266323450776127],"rgb":[0,0.933333333333333348,0.466666666666666674],"xyz":[0.339026159402059046,0.624781610887546246,0.277260069210144289],"hpluv":[136.771308753659213,259.776444306911685,83.167051813506589],"hsluv":[136.771308753659213,99.9999999999910898,83.167051813506589]},"#00ee88":{"lch":[83.4022585136551839,95.7389522528198427,140.46074817536558],"luv":[83.4022585136551839,-73.8327925713246742,60.9480575538504],"rgb":[0,0.933333333333333348,0.533333333333333326],"xyz":[0.350166576504002847,0.629237777728323788,0.335932932613716428],"hpluv":[140.46074817536558,246.149488794882956,83.4022585136551839],"hsluv":[140.46074817536558,99.999999999991033,83.4022585136551839]},"#00ee99":{"lch":[83.6765199188301096,88.5221307628359142,145.258543938418427],"luv":[83.6765199188301096,-72.7414610621927,50.4464813176313],"rgb":[0,0.933333333333333348,0.6],"xyz":[0.363223540613075602,0.634460563371953,0.404699610254834363],"hpluv":[145.258543938418427,231.96752956627526,83.6765199188301096],"hsluv":[145.258543938418427,99.9999999999911,83.6765199188301096]},"#00eeaa":{"lch":[83.9909442670452364,81.4671227341597159,151.444498676017645],"luv":[83.9909442670452364,-71.557012704351564,38.9420854527837363],"rgb":[0,0.933333333333333348,0.66666666666666663],"xyz":[0.378281233578328102,0.640483640558054068,0.484003459871832964],"hpluv":[151.444498676017645,218.263507316576721,83.9909442670452364],"hsluv":[151.444498676017645,99.9999999999911893,83.9909442670452364]},"#00eebb":{"lch":[84.3464103530465366,75.1511025294100392,159.294479220170871],"luv":[84.3464103530465366,-70.2970903476626319,26.5707978811035801],"rgb":[0,0.933333333333333348,0.733333333333333282],"xyz":[0.395418825685664777,0.647338677400988827,0.57426144497047471],"hpluv":[159.294479220170871,206.543608310772072,84.3464103530465366],"hsluv":[159.294479220170871,99.9999999999912,84.3464103530465366]},"#00eecc":{"lch":[84.743580800257746,70.2844566194431195,168.945018717488722],"luv":[84.743580800257746,-68.9802322625990456,13.4771064879770393],"rgb":[0,0.933333333333333348,0.8],"xyz":[0.414711201129386531,0.655055627578477684,0.675867955640744866],"hpluv":[168.945018717488722,198.871854707918374,84.743580800257746],"hsluv":[168.945018717488722,99.9999999999913,84.743580800257746]},"#00eedd":{"lch":[85.1829138464002114,67.6253239558150625,180.163192871920216],"luv":[85.1829138464002114,-67.6250496492668418,-0.192613766721418916],"rgb":[0,0.933333333333333348,0.866666666666666696],"xyz":[0.43622952668002,0.66366295779873119,0.789197803540750864],"hpluv":[180.163192871920216,197.760624486431198,85.1829138464002114],"hsluv":[180.163192871920216,99.9999999999913882,85.1829138464002114]},"#00eeee":{"lch":[85.6646745174910507,67.7744082531008303,192.177050630061217],"luv":[85.6646745174910507,-66.2495152673009358,-14.2958784586901881],"rgb":[0,0.933333333333333348,0.933333333333333348],"xyz":[0.460041706946194784,0.673187829905201207,0.914608619609274087],"hpluv":[192.177050630061217,205.696714727687493,85.6646745174910507],"hsluv":[192.177050630061217,99.9999999999914309,85.6646745174910507]},"#00eeff":{"lch":[86.1889457184888,70.9350767712842867,203.864647638418489],"luv":[86.1889457184888,-64.8703943995767247,-28.6987290135183635],"rgb":[0,0.933333333333333348,1],"xyz":[0.486212755356026149,0.683656249269133931,1.05244280790105593],"hpluv":[203.864647638418489,224.453619733699583,86.1889457184888],"hsluv":[203.864647638418489,99.9999999999939888,86.1889457184888]},"#00ff00":{"lch":[87.7355191096597338,135.789531996666284,127.715012949240474],"luv":[87.7355191096597338,-83.0671197143942663,107.418111239344327],"rgb":[0,1,0],"xyz":[0.35758433938387,0.71516867876775,0.11919477979462],"hpluv":[127.715012949240474,490.145375063702204,87.7355191096597338],"hsluv":[127.715012949240474,100.000000000002217,87.7355191096597338]},"#00ff11":{"lch":[87.7550810882892165,135.01527678270574,127.917210072153054],"luv":[87.7550810882892165,-82.9698837721702915,106.513489059100834],"rgb":[0,1,0.0666666666666666657],"xyz":[0.358596004883507125,0.715573344967604941,0.124522884759375632],"hpluv":[127.917210072153054,488.208403570135204,87.7550810882892165],"hsluv":[127.917210072153054,99.9999999999917719,87.7550810882892165]},"#00ff22":{"lch":[87.7913242833811864,133.592052176160422,128.296258949772664],"luv":[87.7913242833811864,-82.7907071985999892,104.845291769319132],"rgb":[0,1,0.133333333333333331],"xyz":[0.360471363021984181,0.716323488222995741,0.134399770955354825],"hpluv":[128.296258949772664,484.641757887342919,87.7913242833811864],"hsluv":[128.296258949772664,99.9999999999919,87.7913242833811864]},"#00ff33":{"lch":[87.850943105558116,131.282721750620482,128.932531697131338],"luv":[87.850943105558116,-82.4986966230128616,102.123053644879462],"rgb":[0,1,0.2],"xyz":[0.363559113754441887,0.717558588515978868,0.150661924812965975],"hpluv":[128.932531697131338,478.837727878060548,87.850943105558116],"hsluv":[128.932531697131338,99.999999999991843,87.850943105558116]},"#00ff44":{"lch":[87.9368982766027756,128.022939247233296,129.878593634905172],"luv":[87.9368982766027756,-82.0835673214571528,98.245411848516369],"rgb":[0,1,0.266666666666666663],"xyz":[0.368017108998660802,0.719341786613666434,0.174140699765852613],"hpluv":[129.878593634905172,470.610169071279643,87.9368982766027756],"hsluv":[129.878593634905172,99.9999999999916724,87.9368982766027756]},"#00ff55":{"lch":[88.0516385770734189,123.799916713223595,131.196479790431113],"luv":[88.0516385770734189,-81.5399771501718362,93.1539129857171133],"rgb":[0,1,0.333333333333333315],"xyz":[0.373979523038022788,0.721726752229411184,0.205542747039826501],"hpluv":[131.196479790431113,459.892953467552729,88.0516385770734189],"hsluv":[131.196479790431113,99.9999999999917719,88.0516385770734189]},"#00ff66":{"lch":[88.197238997611,118.653311588493224,132.964137709394919],"luv":[88.197238997611,-80.8670327043222557,86.8281715373192498],"rgb":[0,1,0.4],"xyz":[0.381564518811531,0.724760750538814502,0.245490391446970491],"hpluv":[132.964137709394919,446.748834194207859,88.197238997611],"hsluv":[132.964137709394919,99.9999999999917719,88.197238997611]},"#00ff77":{"lch":[88.3754745956423164,112.679107800887323,135.2824164931273],"luv":[88.3754745956423164,-80.0679233099649537,79.2824633297525452],"rgb":[0,1,0.466666666666666674],"xyz":[0.390878531831732878,0.728486355746895353,0.2945441933533684],"hpluv":[135.2824164931273,431.3936933951166,88.3754745956423164],"hsluv":[135.2824164931273,99.9999999999915445,88.3754745956423164]},"#00ff88":{"lch":[88.587864465470858,106.036155512425779,138.2828406903445],"luv":[88.587864465470858,-79.1495135423216425,70.5621767086956311],"rgb":[0,1,0.533333333333333326],"xyz":[0.402018948933676679,0.732942522587672896,0.353217056756940539],"hpluv":[138.2828406903445,414.239888157084465,88.587864465470858],"hsluv":[138.2828406903445,99.9999999999914,88.587864465470858]},"#00ff99":{"lch":[88.8357000190422,98.9561663203651278,142.1349886621461],"luv":[88.8357000190422,-78.1218422027535695,60.739613298668921],"rgb":[0,1,0.6],"xyz":[0.415075913042749378,0.738165308231302109,0.421983734398058474],"hpluv":[142.1349886621461,395.967958147281365,88.8357000190422],"hsluv":[142.1349886621461,99.9999999999915303,88.8357000190422]},"#00ffaa":{"lch":[89.1200644426462674,91.7580340339716258,147.049061977519528],"luv":[89.1200644426462674,-76.997527549554917,49.9090929694682828],"rgb":[0,1,0.66666666666666663],"xyz":[0.430133606008001934,0.744188385417403175,0.501287584015057],"hpluv":[147.049061977519528,377.639750156066668,89.1200644426462674],"hsluv":[147.049061977519528,99.9999999999913,89.1200644426462674]},"#00ffbb":{"lch":[89.4418470234824241,84.8653215767476468,153.262243037154207],"luv":[89.4418470234824241,-75.791105608661141,38.1815546689964407],"rgb":[0,1,0.733333333333333282],"xyz":[0.447271198115338608,0.751043422260337934,0.591545569113698821],"hpluv":[153.262243037154207,360.863433446149145,89.4418470234824241],"hsluv":[153.262243037154207,99.9999999999912,89.4418470234824241]},"#00ffcc":{"lch":[89.801754487955634,78.8187300060100569,160.986090443114392],"luv":[89.801754487955634,-74.5183415032407197,25.6789598575702236],"rgb":[0,1,0.8],"xyz":[0.466563573559060307,0.758760372437826791,0.693152079783969],"hpluv":[160.986090443114392,347.997153451554084,89.801754487955634],"hsluv":[160.986090443114392,99.9999999999912461,89.801754487955634]},"#00ffdd":{"lch":[90.2003206582774339,74.260092310928,170.286849800478649],"luv":[90.2003206582774339,-73.1955569867131572,12.5288366352336915],"rgb":[0,1,0.866666666666666696],"xyz":[0.488081899109693906,0.767367702658080297,0.806481927683975],"hpluv":[170.286849800478649,342.308208972166483,90.2003206582774339],"hsluv":[170.286849800478649,99.9999999999913314,90.2003206582774339]},"#00ffee":{"lch":[90.6379152481429458,71.8480695265374294,180.909719109957],"luv":[90.6379152481429458,-71.8390133400929898,-1.14072652818200426],"rgb":[0,1,0.933333333333333348],"xyz":[0.511894079375868616,0.776892574764550314,0.931892743752498198],"hpluv":[180.909719109957,347.895283980605143,90.6379152481429458],"hsluv":[180.909719109957,99.999999999991374,90.6379152481429458]},"#00ffff":{"lch":[91.114752316705065,72.0862882649682,192.17705063006116],"luv":[91.114752316705065,-70.4643799638718207,-15.205397466925735],"rgb":[0,1,1],"xyz":[0.5380651277857,0.787360994128483,1.06972693204428],"hpluv":[192.17705063006116,369.190533917051368,91.114752316705065],"hsluv":[192.17705063006116,99.9999999999914877,91.114752316705065]},"#ff0000":{"lch":[53.23711559542933,179.038096923620287,12.1770506300617765],"luv":[53.23711559542933,175.009822162883836,37.7650936255616],"rgb":[1,0,0],"xyz":[0.41239079926595,0.21263900587151,0.019330818715591],"hpluv":[12.1770506300617765,426.746789183125202,53.23711559542933],"hsluv":[12.1770506300617765,100.000000000002203,53.23711559542933]},"#ff0011":{"lch":[53.2810087118185294,177.689248384364731,11.7592124156573554],"luv":[53.2810087118185294,173.960033822228979,36.2129206771479346],"rgb":[1,0,0.0666666666666666657],"xyz":[0.413402464765587119,0.213043672071364848,0.0246589236803466325],"hpluv":[11.7592124156573554,423.182830024727082,53.2810087118185294],"hsluv":[11.7592124156573554,99.9999999999986073,53.2810087118185294]},"#ff0022":{"lch":[53.362228057366309,175.255817292919801,10.9800713678561319],"luv":[53.362228057366309,172.047495148921342,33.3805468497921751],"rgb":[1,0,0.133333333333333331],"xyz":[0.415277822904064176,0.213793815326755676,0.0345358098763258251],"hpluv":[10.9800713678561319,416.75211680728853,53.362228057366309],"hsluv":[10.9800713678561319,99.9999999999986215,53.362228057366309]},"#ff0033":{"lch":[53.4955416476677499,171.43316235878109,9.68478250033725],"luv":[53.4955416476677499,168.989928530586468,28.8397852204116347],"rgb":[1,0,0.2],"xyz":[0.418365573636521881,0.215028915619738775,0.0507979637339369683],"hpluv":[9.68478250033725,406.646064741178918,53.4955416476677499],"hsluv":[9.68478250033725,99.9999999999986215,53.4955416476677499]},"#ff0044":{"lch":[53.6871179383659722,166.29954793496961,7.78930386328567259],"luv":[53.6871179383659722,164.765128442936401,22.5386799204815809],"rgb":[1,0,0.266666666666666663],"xyz":[0.422823568880740797,0.216812113717426369,0.0742767386868236],"hpluv":[7.78930386328567259,393.061316669856922,53.6871179383659722],"hsluv":[7.78930386328567259,99.9999999999987637,53.6871179383659722]},"#ff0055":{"lch":[53.9417095924386558,160.100368719231,5.2128969892355661],"luv":[53.9417095924386558,159.438189183864722,14.5461986032050437],"rgb":[1,0,0.333333333333333315],"xyz":[0.428785982920102782,0.219197079333171202,0.105678785960797522],"hpluv":[5.2128969892355661,376.623098544524225,53.9417095924386558],"hsluv":[5.2128969892355661,99.9999999999988,53.9417095924386558]},"#ff0066":{"lch":[54.2629295430466669,153.227313284557312,1.88082466234467849],"luv":[54.2629295430466669,153.144763004983872,5.02902580538230204],"rgb":[1,0,0.4],"xyz":[0.436370978693610967,0.222231077642574493,0.145626430367941484],"hpluv":[1.88082466234467849,358.321012364802243,54.2629295430466669],"hsluv":[1.88082466234467849,99.99999999999892,54.2629295430466669]},"#ff0077":{"lch":[54.6533978532017244,146.184101175375929,357.735148851436577],"luv":[54.6533978532017244,146.06990602550718,-5.77702260269427281],"rgb":[1,0,0.466666666666666674],"xyz":[0.445684991713812872,0.225956682850655316,0.194680232274339393],"hpluv":[357.735148851436577,339.408176675868503,54.6533978532017244],"hsluv":[357.735148851436577,99.9999999999990479,54.6533978532017244]},"#ff0088":{"lch":[55.1148373309560782,139.538803635294983,352.754628092234327],"luv":[55.1148373309560782,138.424605854630585,-17.5984719211521572],"rgb":[1,0,0.533333333333333326],"xyz":[0.456825408815756673,0.230412849691432914,0.253353095677911533],"hpluv":[352.754628092234327,321.26675874055752,55.1148373309560782],"hsluv":[352.754628092234327,99.9999999999991616,55.1148373309560782]},"#ff0099":{"lch":[55.6481496721619493,133.863929319774144,346.981903482220218],"luv":[55.6481496721619493,130.423488026195145,-30.1540269949209758],"rgb":[1,0,0.6],"xyz":[0.469882372924829372,0.235635635335062071,0.322119773319029468],"hpluv":[346.981903482220218,305.247535929832054,55.6481496721619493],"hsluv":[346.981903482220218,99.9999999999993463,55.6481496721619493]},"#ff00aa":{"lch":[56.2534865150640258,129.667114810270476,340.549180922221581],"luv":[56.2534865150640258,122.266710865918853,-43.1788383036143273],"rgb":[1,0,0.66666666666666663],"xyz":[0.484940065890081928,0.241658712521163166,0.401423622936028068],"hpluv":[340.549180922221581,292.495864077812769,56.2534865150640258],"hsluv":[340.549180922221581,99.9999999999994742,56.2534865150640258]},"#ff00bb":{"lch":[56.9303217870161689,127.321325924901956,333.685619315648239],"luv":[56.9303217870161689,114.127678427447606,-56.4410582115202928],"rgb":[1,0,0.733333333333333282],"xyz":[0.502077657997418547,0.248513749364097924,0.491681608034669815],"hpluv":[333.685619315648239,283.789838362024682,56.9303217870161689],"hsluv":[333.685619315648239,99.9999999999995879,56.9303217870161689]},"#ff00cc":{"lch":[57.6775275187384153,127.012826563172382,326.690520651062286],"luv":[57.6775275187384153,106.146716951325558,-69.7505024499586312],"rgb":[1,0,0.8],"xyz":[0.521370033441140301,0.256230699541586726,0.593288118704939915],"hpluv":[326.690520651062286,279.434659423159303,57.6775275187384153],"hsluv":[326.690520651062286,99.9999999999997726,57.6775275187384153]},"#ff00dd":{"lch":[58.4934529509690151,128.727977043064641,319.874434183361473],"luv":[58.4934529509690151,98.4297766537384149,-82.9606602040687],"rgb":[1,0,0.866666666666666696],"xyz":[0.542888358991773901,0.264838029761840288,0.706617966604945913],"hpluv":[319.874434183361473,279.257606739571429,58.4934529509690151],"hsluv":[319.874434183361473,99.9999999999999716,58.4934529509690151]},"#ff00ee":{"lch":[59.3760054748790367,132.286429048213932,313.494468670954461],"luv":[59.3760054748790367,91.0507046157626,-95.9659757377649214],"rgb":[1,0,0.933333333333333348],"xyz":[0.56670053925794861,0.274362901868310305,0.832028782673469136],"hpluv":[313.494468670954461,282.711609251625362,59.3760054748790367],"hsluv":[313.494468670954461,100.000000000000156,59.3760054748790367]},"#ff00ff":{"lch":[60.3227313545512942,137.405400537897037,307.715012949243601],"luv":[60.3227313545512942,84.0556019897527875,-108.696365491768773],"rgb":[1,0,1],"xyz":[0.59287158766778,0.284831321232243,0.969862970965251],"hpluv":[307.715012949243601,289.042783730483336,60.3227313545512942],"hsluv":[307.715012949243601,100.000000000000384,60.3227313545512942]},"#ff1100":{"lch":[53.6695097624616864,176.771562285449363,12.5954542867932275],"luv":[53.6695097624616864,172.517389506501019,38.5478345786208934],"rgb":[1,0.0666666666666666657,0],"xyz":[0.414395199526878422,0.216647806393366865,0.019998952135900451],"hpluv":[12.5954542867932275,417.949777534481484,53.6695097624616864],"hsluv":[12.5954542867932275,100.000000000002245,53.6695097624616864]},"#ff1111":{"lch":[53.7128602445647658,175.445128796306847,12.1770506300617765],"luv":[53.7128602445647658,171.497694164414924,37.0072170615611569],"rgb":[1,0.0666666666666666657,0.0666666666666666657],"xyz":[0.415406865026515526,0.217052472593221718,0.0253270571006560807],"hpluv":[12.1770506300617765,414.478837946685644,53.7128602445647658],"hsluv":[12.1770506300617765,99.9999999999986215,53.7128602445647658]},"#ff1122":{"lch":[53.7930781791116743,173.051572118951754,11.3967197916969329],"luv":[53.7930781791116743,169.639425839354459,34.1952016185739538],"rgb":[1,0.0666666666666666657,0.133333333333333331],"xyz":[0.417282223164992583,0.217802615848612546,0.0352039432966352803],"hpluv":[11.3967197916969329,408.214548988049671,53.7930781791116743],"hsluv":[11.3967197916969329,99.9999999999987,53.7930781791116743]},"#ff1133":{"lch":[53.9247555399676912,169.290109899416,10.0990648343251674],"luv":[53.9247555399676912,166.667136068812482,29.6851320424264138],"rgb":[1,0.0666666666666666657,0.2],"xyz":[0.420369973897450289,0.219037716141595645,0.0514660971542464235],"hpluv":[10.0990648343251674,398.366425235699353,53.9247555399676912],"hsluv":[10.0990648343251674,99.9999999999987,53.9247555399676912]},"#ff1144":{"lch":[54.1139966850166445,164.235972949617775,8.19925898659400154],"luv":[54.1139966850166445,162.55716522781,23.4226993279181244],"rgb":[1,0.0666666666666666657,0.266666666666666663],"xyz":[0.424827969141669204,0.220820914239283239,0.074944872107133062],"hpluv":[8.19925898659400154,385.121711929848118,54.1139966850166445],"hsluv":[8.19925898659400154,99.9999999999988489,54.1139966850166445]},"#ff1155":{"lch":[54.365514290002,158.12888296709221,5.61535385404219856],"luv":[54.365514290002,157.370056367003059,15.4728467796533362],"rgb":[1,0.0666666666666666657,0.333333333333333315],"xyz":[0.430790383181031189,0.223205879855028072,0.106346919381106964],"hpluv":[5.61535385404219856,369.085538340858477,54.365514290002],"hsluv":[5.61535385404219856,99.9999999999988916,54.365514290002]},"#ff1166":{"lch":[54.6829025612910442,151.353597545298243,2.27091305216541839],"luv":[54.6829025612910442,151.234730451097789,5.99731567352495443],"rgb":[1,0.0666666666666666657,0.4],"xyz":[0.438375378954539374,0.226239878164431363,0.146294563788250925],"hpluv":[2.27091305216541839,351.221033033747858,54.6829025612910442],"hsluv":[2.27091305216541839,99.999999999999,54.6829025612910442]},"#ff1177":{"lch":[55.0687823252034292,144.407362773795285,358.105880212246802],"luv":[55.0687823252034292,144.328460520523464,-4.77303960367160496],"rgb":[1,0.0666666666666666657,0.466666666666666674],"xyz":[0.44768939197474128,0.229965483372512186,0.195348365694648834],"hpluv":[358.105880212246802,332.753927221166919,55.0687823252034292],"hsluv":[358.105880212246802,99.9999999999990905,55.0687823252034292]},"#ff1188":{"lch":[55.5248949860500716,137.85386672349216,353.096828842063303],"luv":[55.5248949860500716,136.85452147125136,-16.5689023019976744],"rgb":[1,0.0666666666666666657,0.533333333333333326],"xyz":[0.45882980907668508,0.234421650213289784,0.254021229098221],"hpluv":[353.096828842063303,315.043506786171235,55.5248949860500716],"hsluv":[353.096828842063303,99.9999999999992,55.5248949860500716]},"#ff1199":{"lch":[56.0521767726019249,132.264360312052816,347.28491936957397],"luv":[56.0521767726019249,129.020794472918823,-29.1117777254046715],"rgb":[1,0.0666666666666666657,0.6],"xyz":[0.47188677318575778,0.239644435856918941,0.322787906739338937],"hpluv":[347.28491936957397,299.426117704125659,56.0521767726019249],"hsluv":[347.28491936957397,99.9999999999993605,56.0521767726019249]},"#ff11aa":{"lch":[56.6508275614924912,128.148315107741439,340.802676353967],"luv":[56.6508275614924912,121.022209124101792,-42.1380536294115586],"rgb":[1,0.0666666666666666657,0.66666666666666663],"xyz":[0.486944466151010336,0.245667513043020036,0.402091756356337537],"hpluv":[340.802676353967,287.042344439162662,56.6508275614924912],"hsluv":[340.802676353967,99.9999999999995168,56.6508275614924912]},"#ff11bb":{"lch":[57.3203806938084455,125.882364893771992,333.882217516525884],"luv":[57.3203806938084455,113.02864145467457,-55.4156656746030762],"rgb":[1,0.0666666666666666657,0.733333333333333282],"xyz":[0.504082058258347,0.252522549885954795,0.492349741454979284],"hpluv":[333.882217516525884,278.673167271969135,57.3203806938084455],"hsluv":[333.882217516525884,99.9999999999996163,57.3203806938084455]},"#ff11cc":{"lch":[58.0597760671947754,125.656277294901628,326.828156543045054],"luv":[58.0597760671947754,105.178488239830273,-68.7530772780178694],"rgb":[1,0.0666666666666666657,0.8],"xyz":[0.523374433702068709,0.260239500063443596,0.593956252125249384],"hpluv":[326.828156543045054,274.630115267561905,58.0597760671947754],"hsluv":[326.828156543045054,99.9999999999997868,58.0597760671947754]},"#ff11dd":{"lch":[58.8674364673636177,127.458097444326981,319.957026901825429],"luv":[58.8674364673636177,97.5770916499376568,-82.0016938195011846],"rgb":[1,0.0666666666666666657,0.866666666666666696],"xyz":[0.544892759252702308,0.268846830283697158,0.707286100025255382],"hpluv":[319.957026901825429,274.746161939823423,58.8674364673636177],"hsluv":[319.957026901825429,100.000000000000028,58.8674364673636177]},"#ff11ee":{"lch":[59.7413458233107519,131.106916258937218,313.5305052972667],"luv":[59.7413458233107519,90.2986667085849319,-95.0535337669245877],"rgb":[1,0.0666666666666666657,0.933333333333333348],"xyz":[0.568704939518877,0.278371702390167175,0.832696916093778605],"hpluv":[313.5305052972667,278.477381794919836,59.7413458233107519],"hsluv":[313.5305052972667,100.000000000000199,59.7413458233107519]},"#ff11ff":{"lch":[60.6791274610807534,136.317870534400242,307.715012949243601],"luv":[60.6791274610807534,83.3903225409976,-107.83606045076894],"rgb":[1,0.0666666666666666657,1],"xyz":[0.594875987928708438,0.288840121754099899,0.97053110438556045],"hpluv":[307.715012949243601,285.070838096226908,60.6791274610807534],"hsluv":[307.715012949243601,100.000000000000398,60.6791274610807534]},"#ff2200":{"lch":[54.4571507543770679,172.725520469573979,13.3786813235288875],"luv":[54.4571507543770679,168.038102184023103,39.9662562154253393],"rgb":[1,0.133333333333333331,0],"xyz":[0.418110823261646336,0.224079053862902833,0.0212374933808230602],"hpluv":[13.3786813235288875,402.476865089738737,54.4571507543770679],"hsluv":[13.3786813235288875,100.00000000000216,54.4571507543770679]},"#ff2211":{"lch":[54.4995382972682876,171.437527349711331,12.9593558016228254],"luv":[54.4995382972682876,167.070909686171433,38.446546274251638],"rgb":[1,0.133333333333333331,0.0666666666666666657],"xyz":[0.41912248876128344,0.224483720062757686,0.0265655983455786934],"hpluv":[12.9593558016228254,399.164948195999784,54.4995382972682876],"hsluv":[12.9593558016228254,99.999999999998721,54.4995382972682876]},"#ff2222":{"lch":[54.5779789595956117,169.112342257331477,12.1770506300617924],"luv":[54.5779789595956117,165.307392407273255,35.6714216042562171],"rgb":[1,0.133333333333333331,0.133333333333333331],"xyz":[0.420997846899760497,0.225233863318148514,0.036442484541557886],"hpluv":[12.1770506300617924,393.185217729465933,54.5779789595956117],"hsluv":[12.1770506300617924,99.9999999999987494,54.5779789595956117]},"#ff2233":{"lch":[54.7067518227456,165.455769736233549,10.8753803895539445],"luv":[54.7067518227456,162.484163442906947,31.2171166072114978],"rgb":[1,0.133333333333333331,0.2],"xyz":[0.424085597632218203,0.226468963611131613,0.0527046383991690293],"hpluv":[10.8753803895539445,383.778210348001721,54.7067518227456],"hsluv":[10.8753803895539445,99.9999999999987779,54.7067518227456]},"#ff2244":{"lch":[54.8918465894738148,160.537768894747074,8.96806115251763103],"luv":[54.8918465894738148,158.575257224785304,25.0252480066910863],"rgb":[1,0.133333333333333331,0.266666666666666663],"xyz":[0.428543592876437118,0.228252161708819207,0.0761834133520556678],"hpluv":[8.96806115251763103,371.115171122776133,54.8918465894738148],"hsluv":[8.96806115251763103,99.9999999999988347,54.8918465894738148]},"#ff2255":{"lch":[55.1379036013317432,154.588213330392733,6.3708707633682522],"luv":[55.1379036013317432,153.633547891150499,17.1536778289841081],"rgb":[1,0.133333333333333331,0.333333333333333315],"xyz":[0.434506006915799103,0.23063712732456404,0.107585460626029583],"hpluv":[6.3708707633682522,355.76683037739258,55.1379036013317432],"hsluv":[6.3708707633682522,99.9999999999989342,55.1379036013317432]},"#ff2266":{"lch":[55.4484819892530254,147.979820726080618,3.00414546296194196],"luv":[55.4484819892530254,147.776458796099433,7.75535736170059753],"rgb":[1,0.133333333333333331,0.4],"xyz":[0.442091002689307289,0.23367112563396733,0.147533105033173545],"hpluv":[3.00414546296194196,338.650844053811227,55.4484819892530254],"hsluv":[3.00414546296194196,99.9999999999990763,55.4484819892530254]},"#ff2277":{"lch":[55.8262016697843961,141.198613687408425,358.803757025958646],"luv":[55.8262016697843961,141.167840095073672,-2.94778393674187145],"rgb":[1,0.133333333333333331,0.466666666666666674],"xyz":[0.451405015709509194,0.237396730842048154,0.196586906939571454],"hpluv":[358.803757025958646,320.945787006908631,55.8262016697843961],"hsluv":[358.803757025958646,99.9999999999991616,55.8262016697843961]},"#ff2288":{"lch":[56.2728344602164299,134.800794021339357,353.742009538390391],"luv":[56.2728344602164299,133.997535770156077,-14.694028593592142],"rgb":[1,0.133333333333333331,0.533333333333333326],"xyz":[0.462545432811453,0.241852897682825752,0.255259770343143622],"hpluv":[353.742009538390391,303.971583410200083,56.2728344602164299],"hsluv":[353.742009538390391,99.9999999999992752,56.2728344602164299]},"#ff2299":{"lch":[56.7893750973531866,129.355771045476672,347.857065824102108],"luv":[56.7893750973531866,126.461550291410035,-27.2101415039127161],"rgb":[1,0.133333333333333331,0.6],"xyz":[0.47560239692052575,0.247075683326454909,0.324026447984261501],"hpluv":[347.857065824102108,289.040064659782502,56.7893750973531866],"hsluv":[347.857065824102108,99.9999999999994316,56.7893750973531866]},"#ff22aa":{"lch":[57.3761062638205743,125.376808270539939,341.281866819384959],"luv":[57.3761062638205743,118.745473604842076,-40.2350164715945766],"rgb":[1,0.133333333333333331,0.66666666666666663],"xyz":[0.49066008988577825,0.253098760512556031,0.403330297601260102],"hpluv":[341.281866819384959,277.284417349873706,57.3761062638205743],"hsluv":[341.281866819384959,99.999999999999531,57.3761062638205743]},"#ff22bb":{"lch":[58.0326640845464112,123.247659375493754,334.254064951888324],"luv":[58.0326640845464112,111.01274832153284,-53.5364852379920606],"rgb":[1,0.133333333333333331,0.733333333333333282],"xyz":[0.507797681993114924,0.25995379735549079,0.493588282699901848],"hpluv":[334.254064951888324,269.491764983662165,58.0326640845464112],"hsluv":[334.254064951888324,99.9999999999997726,58.0326640845464112]},"#ff22cc":{"lch":[58.7581065478829316,123.164795236141373,327.088444575119183],"luv":[58.7581065478829316,103.398114188518221,-66.9208246199849128],"rgb":[1,0.133333333333333331,0.8],"xyz":[0.527090057436836679,0.267670747532979592,0.595194793370172],"hpluv":[327.088444575119183,265.985598747154427,58.7581065478829316],"hsluv":[327.088444575119183,99.9999999999998721,58.7581065478829316]},"#ff22dd":{"lch":[59.550985046801415,125.119413407905796,320.113090366201448],"luv":[59.550985046801415,96.0055877759378,-80.2358693312108358],"rgb":[1,0.133333333333333331,0.866666666666666696],"xyz":[0.548608382987470167,0.276278077753233098,0.708524641270178],"hpluv":[320.113090366201448,266.609166102550091,59.550985046801415],"hsluv":[320.113090366201448,100.000000000000028,59.550985046801415]},"#ff22ee":{"lch":[60.4094179672163705,128.929416765847606,313.598505937960113],"luv":[60.4094179672163705,88.9098108664153273,-93.3693742041783281],"rgb":[1,0.133333333333333331,0.933333333333333348],"xyz":[0.572420563253645,0.285802949859703115,0.833935457338701225],"hpluv":[313.598505937960113,270.823716236275,60.4094179672163705],"hsluv":[313.598505937960113,100.000000000000227,60.4094179672163705]},"#ff22ff":{"lch":[61.3311646171935223,134.305840538380238,307.715012949243601],"luv":[61.3311646171935223,82.1594946996257249,-106.244417422389745],"rgb":[1,0.133333333333333331,1],"xyz":[0.598591611663476297,0.296271369223635839,0.97176964563048307],"hpluv":[307.715012949243601,277.877263991976,61.3311646171935223],"hsluv":[307.715012949243601,100.000000000000398,61.3311646171935223]},"#ff3300":{"lch":[55.7168894472394811,166.476173059961667,14.689559134518138],"luv":[55.7168894472394811,161.034729269155179,42.2153072463082495],"rgb":[1,0.2,0],"xyz":[0.424228545350657182,0.236314498040924637,0.0232767340771599419],"hpluv":[14.689559134518138,379.144314271077917,55.7168894472394811],"hsluv":[14.689559134518138,100.000000000002203,55.7168894472394811]},"#ff3311":{"lch":[55.7578022303213,165.243627812887922,14.2690908575150317],"luv":[55.7578022303213,160.145669888681539,40.7286256663500339],"rgb":[1,0.2,0.0666666666666666657],"xyz":[0.425240210850294287,0.236719164240779489,0.0286048390419155751],"hpluv":[14.2690908575150317,376.06108995847427,55.7578022303213],"hsluv":[14.2690908575150317,99.9999999999988,55.7578022303213]},"#ff3322":{"lch":[55.8335204651182835,163.01701714894287,13.4842232594842422],"luv":[55.8335204651182835,158.523316810386802,38.0119179675594552],"rgb":[1,0.2,0.133333333333333331],"xyz":[0.427115568988771344,0.237469307496170318,0.0384817252378947677],"hpluv":[13.4842232594842422,370.490653647292163,55.8335204651182835],"hsluv":[13.4842232594842422,99.9999999999987494,55.8335204651182835]},"#ff3333":{"lch":[55.9578428172660267,159.511521097175432,12.1770506300617853],"luv":[55.9578428172660267,155.922585303490365,33.6462888742638455],"rgb":[1,0.2,0.2],"xyz":[0.430203319721229049,0.238704407789153417,0.0547438790955059179],"hpluv":[12.1770506300617853,361.718248261175631,55.9578428172660267],"hsluv":[12.1770506300617853,99.9999999999988773,55.9578428172660267]},"#ff3344":{"lch":[56.1365811585215368,154.789240798906889,10.2588910791084782],"luv":[56.1365811585215368,152.31463646984821,27.5673826135157078],"rgb":[1,0.2,0.266666666666666663],"xyz":[0.434661314965447965,0.240487605886841,0.0782226540483925564],"hpluv":[10.2588910791084782,349.892100101075414,56.1365811585215368],"hsluv":[10.2588910791084782,99.9999999999989626,56.1365811585215368]},"#ff3355":{"lch":[56.3742616664660403,149.065442517766684,7.64169944339336649],"luv":[56.3742616664660403,147.741595680550319,19.8223878173745902],"rgb":[1,0.2,0.333333333333333315],"xyz":[0.44062372900480995,0.242872571502585843,0.109624701322366458],"hpluv":[7.64169944339336649,335.533149366899124,56.3742616664660403],"hsluv":[7.64169944339336649,99.9999999999990195,56.3742616664660403]},"#ff3366":{"lch":[56.674385203130754,142.694983818340035,4.24028319431916056],"luv":[56.674385203130754,142.304390380845462,10.550776523662277],"rgb":[1,0.2,0.4],"xyz":[0.448208724778318135,0.245906569811989134,0.14957234572951042],"hpluv":[4.24028319431916056,319.492902958598108,56.674385203130754],"hsluv":[4.24028319431916056,99.9999999999991758,56.674385203130754]},"#ff3377":{"lch":[57.0395646827704468,136.14730874514737,359.983392279567909],"luv":[57.0395646827704468,136.147303025702882,-0.0394635770517579934],"rgb":[1,0.2,0.466666666666666674],"xyz":[0.457522737798520041,0.249632175020069957,0.198626147635908329],"hpluv":[359.983392279567909,302.881107814185,57.0395646827704468],"hsluv":[359.983392279567909,99.9999999999992895,57.0395646827704468]},"#ff3388":{"lch":[57.4716120619286954,129.967879448766553,354.83565117969431],"luv":[57.4716120619286954,129.440287861866665,-11.6987848363070146],"rgb":[1,0.2,0.533333333333333326],"xyz":[0.468663154900463841,0.254088341860847555,0.257299011039480496],"hpluv":[354.83565117969431,286.960407533356261,57.4716120619286954],"hsluv":[354.83565117969431,99.9999999999993889,57.4716120619286954]},"#ff3399":{"lch":[57.9716047421228353,124.724336507791776,348.82951213288959],"luv":[57.9716047421228353,122.361442957788952,-24.1627273832372573],"rgb":[1,0.2,0.6],"xyz":[0.481720119009536596,0.259311127504476713,0.326065688680598431],"hpluv":[348.82951213288959,273.007894976207297,57.9716047421228353],"hsluv":[348.82951213288959,99.9999999999995737,57.9716047421228353]},"#ff33aa":{"lch":[58.5399451724763935,120.937271340322638,342.098036856126953],"luv":[58.5399451724763935,115.081956962752116,-37.174813797329108],"rgb":[1,0.2,0.66666666666666663],"xyz":[0.496777811974789096,0.265334204690577835,0.405369538297597032],"hpluv":[342.098036856126953,262.148381504719794,58.5399451724763935],"hsluv":[342.098036856126953,99.9999999999996732,58.5399451724763935]},"#ff33bb":{"lch":[59.1764201449825862,119.003132790944747,334.888094830460091],"luv":[59.1764201449825862,107.755032536641394,-50.5034511403585498],"rgb":[1,0.2,0.733333333333333282],"xyz":[0.513915404082125771,0.272189241533512594,0.495627523396238778],"hpluv":[334.888094830460091,255.181409444549388,59.1764201449825862],"hsluv":[334.888094830460091,99.9999999999998295,59.1764201449825862]},"#ff33cc":{"lch":[59.8802624584280494,119.13012657470172,327.532171012183937],"luv":[59.8802624584280494,100.509254029343069,-63.9521454852723039],"rgb":[1,0.2,0.8],"xyz":[0.533207779525847525,0.279906191711001395,0.597234034066508879],"hpluv":[327.532171012183937,252.451080902073329,59.8802624584280494],"hsluv":[327.532171012183937,100.000000000000043,59.8802624584280494]},"#ff33dd":{"lch":[60.650215463767978,121.314823858854638,320.378757122173454],"luv":[60.650215463767978,93.4460015542912146,-77.3636302238999747],"rgb":[1,0.2,0.866666666666666696],"xyz":[0.554726105076481,0.288513521931254902,0.710563881966514876],"hpluv":[320.378757122173454,253.817084039055629,60.650215463767978],"hsluv":[320.378757122173454,100.000000000000171,60.650215463767978]},"#ff33ee":{"lch":[61.484599762034378,125.372817174433621,313.71398784253438],"luv":[61.484599762034378,86.6400000332951805,-90.6192787462169775],"rgb":[1,0.2,0.933333333333333348],"xyz":[0.578538285342655723,0.298038394037724919,0.8359746980350381],"hpluv":[313.71398784253438,258.747618308410438,61.484599762034378],"hsluv":[313.71398784253438,100.000000000000242,61.484599762034378]},"#ff33ff":{"lch":[62.3813806681475,131.007738376122177,307.715012949243658],"luv":[62.3813806681475,80.141932350642,-103.635409940481253],"rgb":[1,0.2,1],"xyz":[0.604709333752487144,0.308506813401657642,0.97380888632682],"hpluv":[307.715012949243658,266.490230971107223,62.3813806681475],"hsluv":[307.715012949243658,100.000000000000597,62.3813806681475]},"#ff4400":{"lch":[57.461133143380664,158.273971604467,16.6278363926044079],"luv":[57.461133143380664,151.655533944896689,45.2907177172089845],"rgb":[1,0.266666666666666663,0],"xyz":[0.433061115833623222,0.253979639006856939,0.0262209242381485352],"hpluv":[16.6278363926044079,349.522099776260404,57.461133143380664],"hsluv":[16.6278363926044079,100.000000000002203,57.461133143380664]},"#ff4411":{"lch":[57.500127691013958,157.107055615985729,16.2066010587584444],"luv":[57.500127691013958,150.863862649088418,43.84885256105823],"rgb":[1,0.266666666666666663,0.0666666666666666657],"xyz":[0.434072781333260327,0.254384305206711792,0.031549029202904165],"hpluv":[16.2066010587584444,346.709871357654038,57.500127691013958],"hsluv":[16.2066010587584444,99.9999999999990195,57.500127691013958]},"#ff4422":{"lch":[57.5723039440668174,154.996970095022306,15.4196600073807488],"luv":[57.5723039440668174,149.417734299680745,41.2116660108183908],"rgb":[1,0.266666666666666663,0.133333333333333331],"xyz":[0.435948139471737384,0.255134448462102592,0.0414259153988833645],"hpluv":[15.4196600073807488,341.624434338608523,57.5723039440668174],"hsluv":[15.4196600073807488,99.9999999999990195,57.5723039440668174]},"#ff4433":{"lch":[57.6908335218327437,151.669616752661852,14.1071803519879388],"luv":[57.6908335218327437,147.095485225188412,36.9674298845038223],"rgb":[1,0.266666666666666663,0.2],"xyz":[0.439035890204195089,0.256369548755085719,0.0576880692564945077],"hpluv":[14.1071803519879388,333.603886972203838,57.6908335218327437],"hsluv":[14.1071803519879388,99.9999999999991189,57.6908335218327437]},"#ff4444":{"lch":[57.8612930010941682,147.177084719743902,12.177050630061812],"luv":[57.8612930010941682,143.865668066403089,31.0445457111261],"rgb":[1,0.266666666666666663,0.266666666666666663],"xyz":[0.443493885448414,0.258152746852773285,0.0811668442093811393],"hpluv":[12.177050630061812,322.76868159643891,57.8612930010941682],"hsluv":[12.177050630061812,99.9999999999991616,57.8612930010941682]},"#ff4455":{"lch":[58.088054010202,141.716285969530816,9.53556562214303405],"luv":[58.088054010202,139.758186425649882,23.4766913344092778],"rgb":[1,0.266666666666666663,0.333333333333333315],"xyz":[0.449456299487776,0.260537712468518146,0.112568891483355055],"hpluv":[9.53556562214303405,309.579547415252762,58.088054010202],"hsluv":[9.53556562214303405,99.9999999999992184,58.088054010202]},"#ff4466":{"lch":[58.3745334436288772,135.619673907166316,6.08910281061040859],"luv":[58.3745334436288772,134.854526247228307,14.3858507333331573],"rgb":[1,0.266666666666666663,0.4],"xyz":[0.457041295261284175,0.263571710777921464,0.152516535890499016],"hpluv":[6.08910281061040859,294.807548797669426,58.3745334436288772],"hsluv":[6.08910281061040859,99.9999999999992468,58.3745334436288772]},"#ff4477":{"lch":[58.7233249761193292,129.336052383116169,1.75519751784143763],"luv":[58.7233249761193292,129.275370036155351,3.96145782045869943],"rgb":[1,0.266666666666666663,0.466666666666666674],"xyz":[0.466355308281486081,0.26729731598600226,0.201570337796896926],"hpluv":[1.75519751784143763,279.478426792191101,58.7233249761193292],"hsluv":[1.75519751784143763,99.99999999999946,58.7233249761193292]},"#ff4488":{"lch":[59.1362810655005831,123.398173767481396,356.485857706034096],"luv":[59.1362810655005831,123.16614811103635,-7.56367957014252212],"rgb":[1,0.266666666666666663,0.533333333333333326],"xyz":[0.477495725383429881,0.271753482826779857,0.260243201200469065],"hpluv":[356.485857706034096,264.785408966002421,59.1362810655005831],"hsluv":[356.485857706034096,99.9999999999995595,59.1362810655005831]},"#ff4499":{"lch":[59.6145739069951901,118.37348532064955,350.303370213710309],"luv":[59.6145739069951901,116.682328099077466,-19.9378117238896806],"rgb":[1,0.266666666666666663,0.6],"xyz":[0.490552689492502636,0.276976268470409,0.329009878841587],"hpluv":[350.303370213710309,251.965637795338,59.6145739069951901],"hsluv":[350.303370213710309,99.9999999999997158,59.6145739069951901]},"#ff44aa":{"lch":[60.1587486598557177,114.795436800176844,343.339450530546515],"luv":[60.1587486598557177,109.976338996192666,-32.9119609129858475],"rgb":[1,0.266666666666666663,0.66666666666666663],"xyz":[0.505610382457755136,0.282999345656510137,0.408313728458585601],"hpluv":[343.339450530546515,242.139230170638513,60.1587486598557177],"hsluv":[343.339450530546515,99.9999999999997726,60.1587486598557177]},"#ff44bb":{"lch":[60.768775409955694,113.081121999454581,335.854341209703],"luv":[60.768775409955694,103.187483930843442,-46.2567110015126133],"rgb":[1,0.266666666666666663,0.733333333333333282],"xyz":[0.522747974565091811,0.289854382499444896,0.498571713557227347],"hpluv":[335.854341209703,236.128794952899398,60.768775409955694],"hsluv":[335.854341209703,99.9999999999999716,60.768775409955694]},"#ff44cc":{"lch":[61.4441027606342232,113.457557937670586,328.208302422827],"luv":[61.4441027606342232,96.4354118722023088,-59.7731443895883743],"rgb":[1,0.266666666666666663,0.8],"xyz":[0.542040350008813565,0.297571332676933697,0.600178224227497559],"hpluv":[328.208302422827,234.310931883055929,61.4441027606342232],"hsluv":[328.208302422827,100.000000000000128,61.4441027606342232]},"#ff44dd":{"lch":[62.1837139115479403,115.929787300919912,320.782684481283354],"luv":[62.1837139115479403,89.8170022013310358,-73.2981698216444357],"rgb":[1,0.266666666666666663,0.866666666666666696],"xyz":[0.563558675559447,0.306178662897187204,0.713508072127503556],"hpluv":[320.782684481283354,236.568931830040128,62.1837139115479403],"hsluv":[320.782684481283354,100.000000000000384,62.1837139115479403]},"#ff44ee":{"lch":[62.986184892514558,120.309477517885213,313.888915695758442],"luv":[62.986184892514558,83.4060394378513195,-86.7052649261745643],"rgb":[1,0.266666666666666663,0.933333333333333348],"xyz":[0.587370855825621874,0.315703535003657221,0.83891888819602678],"hpluv":[313.888915695758442,242.378371703623515,62.986184892514558],"hsluv":[313.888915695758442,100.000000000000512,62.986184892514558]},"#ff44ff":{"lch":[63.8497439492436,126.288239910703226,307.715012949243771],"luv":[63.8497439492436,77.2548530724802447,-99.9019880507531468],"rgb":[1,0.266666666666666663,1],"xyz":[0.613541904235453184,0.326171954367589945,0.976753076487808514],"hpluv":[307.715012949243771,250.982289693600563,63.8497439492436],"hsluv":[307.715012949243771,100.000000000000711,63.8497439492436]},"#ff5500":{"lch":[59.6718499915998279,148.630700843778015,19.3008598736449528],"luv":[59.6718499915998279,140.27705963161867,49.1266910591374923],"rgb":[1,0.333333333333333315,0],"xyz":[0.444874372547969188,0.277606152435549203,0.030158676476263746],"hpluv":[19.3008598736449528,316.066414507984518,59.6718499915998279],"hsluv":[19.3008598736449528,100.00000000000226,59.6718499915998279]},"#ff5511":{"lch":[59.7086010657385486,147.530698996531413,18.8803784611224046],"luv":[59.7086010657385486,139.59299153240957,47.7399608445355526],"rgb":[1,0.333333333333333315,0.0666666666666666657],"xyz":[0.445886038047606292,0.278010818635404056,0.0354867814410193758],"hpluv":[18.8803784611224046,313.53413530658878,59.7086010657385486],"hsluv":[18.8803784611224046,99.9999999999992184,59.7086010657385486]},"#ff5522":{"lch":[59.7766335415963255,145.539064184811622,18.0939597274483681],"luv":[59.7766335415963255,138.341936048844929,45.2009727113036],"rgb":[1,0.333333333333333315,0.133333333333333331],"xyz":[0.447761396186083349,0.278760961890794856,0.0453636676369985753],"hpluv":[18.0939597274483681,308.949467849715688,59.7766335415963255],"hsluv":[18.0939597274483681,99.9999999999993,59.7766335415963255]},"#ff5533":{"lch":[59.8883826376776085,142.391759670631302,16.7797766500676033],"luv":[59.8883826376776085,136.328925827546385,41.1076295206398825],"rgb":[1,0.333333333333333315,0.2],"xyz":[0.450849146918541055,0.279996062183778,0.0616258214946097185],"hpluv":[16.7797766500676033,301.704368813615531,59.8883826376776085],"hsluv":[16.7797766500676033,99.9999999999992752,59.8883826376776085]},"#ff5544":{"lch":[60.0491441299879654,138.129067713899872,14.841281480974498],"luv":[60.0491441299879654,133.520956258941027,35.3806951204903228],"rgb":[1,0.333333333333333315,0.266666666666666663],"xyz":[0.45530714216275997,0.281779260281465549,0.0851045964474963501],"hpluv":[14.841281480974498,291.888903616465711,60.0491441299879654],"hsluv":[14.841281480974498,99.9999999999994458,60.0491441299879654]},"#ff5555":{"lch":[60.2631003442631936,132.926854505406169,12.1770506300618191],"luv":[60.2631003442631936,129.936061471805857,28.0386978637829927],"rgb":[1,0.333333333333333315,0.333333333333333315],"xyz":[0.461269556202121955,0.28416422589721041,0.116506643721470265],"hpluv":[12.1770506300618191,279.898508055628838,60.2631003442631936],"hsluv":[12.1770506300618191,99.99999999999946,60.2631003442631936]},"#ff5566":{"lch":[60.5335583680784168,127.091978224389294,8.68145952340772098],"luv":[60.5335583680784168,125.635857711143842,19.1833830742614],"rgb":[1,0.333333333333333315,0.4],"xyz":[0.468854551975630141,0.287198224206613728,0.156454288128614227],"hpluv":[8.68145952340772098,266.416588145649541,60.5335583680784168],"hsluv":[8.68145952340772098,99.9999999999995737,60.5335583680784168]},"#ff5577":{"lch":[60.8630749033481351,121.049870295691591,4.25532383082281918],"luv":[60.8630749033481351,120.716171483201308,8.98203991541407],"rgb":[1,0.333333333333333315,0.466666666666666674],"xyz":[0.478168564995832046,0.290923829414694524,0.205508090035012136],"hpluv":[4.25532383082281918,252.376995060411161,60.8630749033481351],"hsluv":[4.25532383082281918,99.9999999999997158,60.8630749033481351]},"#ff5588":{"lch":[61.2535329118914404,115.319895978664789,358.830706871579594],"luv":[61.2535329118914404,115.295882188952703,-2.35328680810625102],"rgb":[1,0.333333333333333315,0.533333333333333326],"xyz":[0.489308982097775846,0.295379996255472121,0.264180953438584276],"hpluv":[358.830706871579594,238.897951612134108,61.2535329118914404],"hsluv":[358.830706871579594,99.9999999999997868,61.2535329118914404]},"#ff5599":{"lch":[61.7061969251912075,110.472562823261683,352.412124726619879],"luv":[61.7061969251912075,109.505210454219565,-14.5875296097928366],"rgb":[1,0.333333333333333315,0.6],"xyz":[0.502365946206848601,0.300602781899101279,0.332947631079702211],"hpluv":[352.412124726619879,227.177321581811952,61.7061969251912075],"hsluv":[352.412124726619879,99.9999999999998721,61.7061969251912075]},"#ff55aa":{"lch":[62.2217597266614177,107.062368992166355,345.125792918237266],"luv":[62.2217597266614177,103.474894783269619,-27.4826673342684735],"rgb":[1,0.333333333333333315,0.66666666666666663],"xyz":[0.517423639172101102,0.306625859085202401,0.412251480696700812],"hpluv":[345.125792918237266,218.340291577764589,62.2217597266614177],"hsluv":[345.125792918237266,100.000000000000071,62.2217597266614177]},"#ff55bb":{"lch":[62.8003867495987862,105.538663220643826,337.249357740418191],"luv":[62.8003867495987862,97.3274002701012,-40.8140489422941855],"rgb":[1,0.333333333333333315,0.733333333333333282],"xyz":[0.534561231279437776,0.31348089592813716,0.502509465795342614],"hpluv":[337.249357740418191,213.249782655969199,62.8003867495987862],"hsluv":[337.249357740418191,100.000000000000284,62.8003867495987862]},"#ff55cc":{"lch":[63.441761241476712,106.157882562261193,329.184616986090759],"luv":[63.441761241476712,91.1707664760994447,-54.3818661895983837],"rgb":[1,0.333333333333333315,0.8],"xyz":[0.553853606723159531,0.321197846105625961,0.60411597646561277],"hpluv":[329.184616986090759,212.332436268611161,63.441761241476712],"hsluv":[329.184616986090759,100.000000000000441,63.441761241476712]},"#ff55dd":{"lch":[64.1451313698934769,108.938462409011748,321.364198961949114],"luv":[64.1451313698934769,85.0951546537234549,-68.0176686346904518],"rgb":[1,0.333333333333333315,0.866666666666666696],"xyz":[0.575371932273793,0.329805176325879468,0.717445824365618767],"hpluv":[321.364198961949114,215.504760823814451,64.1451313698934769],"hsluv":[321.364198961949114,100.000000000000597,64.1451313698934769]},"#ff55ee":{"lch":[64.9093593252901258,113.686114680552976,314.13939983200612],"luv":[64.9093593252901258,79.1717442713053288,-81.5865649491316418],"rgb":[1,0.333333333333333315,0.933333333333333348],"xyz":[0.599184112539967728,0.339330048432349485,0.842856640434142],"hpluv":[314.13939983200612,222.248801840624651,64.9093593252901258],"hsluv":[314.13939983200612,100.000000000000753,64.9093593252901258]},"#ff55ff":{"lch":[65.7329718140353378,120.074032289562709,307.715012949243885],"luv":[65.7329718140353378,73.4534088756767147,-94.9861566483116633],"rgb":[1,0.333333333333333315,1],"xyz":[0.625355160949799149,0.349798467796282209,0.980690828725923724],"hpluv":[307.715012949243885,231.795582155087629,65.7329718140353378],"hsluv":[307.715012949243885,100.000000000000981,65.7329718140353378]},"#ff6600":{"lch":[62.3097916023938438,138.227046243322206,22.8239093069931798],"luv":[62.3097916023938438,127.404056867086908,53.6183047751569717],"rgb":[1,0.4,0],"xyz":[0.459902430253815608,0.307662267847242543,0.03516802904487909],"hpluv":[22.8239093069931798,281.498480884542573,62.3097916023938438],"hsluv":[22.8239093069931798,100.000000000002359,62.3097916023938438]},"#ff6611":{"lch":[62.344110015411573,137.186959502953613,22.4076195476895244],"luv":[62.344110015411573,126.828705913080029,52.2947532174930245],"rgb":[1,0.4,0.0666666666666666657],"xyz":[0.460914095753452713,0.308066934047097396,0.0404961340096347197],"hpluv":[22.4076195476895244,279.226561167599414,62.344110015411573],"hsluv":[22.4076195476895244,99.9999999999995737,62.344110015411573]},"#ff6622":{"lch":[62.4076477973658257,135.300699513710725,21.6278909170268392],"luv":[62.4076477973658257,125.775148313603225,49.8687412673566115],"rgb":[1,0.4,0.133333333333333331],"xyz":[0.46278945389192977,0.308817077302488197,0.0503730202056139192],"hpluv":[21.6278909170268392,275.106945224361368,62.4076477973658257],"hsluv":[21.6278909170268392,99.9999999999996163,62.4076477973658257]},"#ff6633":{"lch":[62.5120380635233346,132.311574345484274,20.3215228987586443],"luv":[62.5120380635233346,124.076309265494103,45.9502141979128851],"rgb":[1,0.4,0.2],"xyz":[0.465877204624387475,0.310052177595471323,0.0666351740632250555],"hpluv":[20.3215228987586443,268.579898420339646,62.5120380635233346],"hsluv":[20.3215228987586443,99.9999999999995879,62.5120380635233346]},"#ff6644":{"lch":[62.6622654373265675,128.246261163642686,18.3868048135947362],"luv":[62.6622654373265675,121.699120282046835,40.4527826611364603],"rgb":[1,0.4,0.266666666666666663],"xyz":[0.470335199868606391,0.311835375693158889,0.0901139490161117],"hpluv":[18.3868048135947362,259.703586528718,62.6622654373265675],"hsluv":[18.3868048135947362,99.9999999999997726,62.6622654373265675]},"#ff6655":{"lch":[62.8622967709428764,123.257362768531593,15.7125644918265355],"luv":[62.8622967709428764,118.651528823693667,33.3795174388964284],"rgb":[1,0.4,0.333333333333333315],"xyz":[0.476297613907968376,0.31422034130890375,0.121515996290085609],"hpluv":[15.7125644918265355,248.806632458920831,62.8622967709428764],"hsluv":[15.7125644918265355,99.9999999999997158,62.8622967709428764]},"#ff6666":{"lch":[63.1153061541487119,117.623502253606588,12.1770506300618742],"luv":[63.1153061541487119,114.97702760078576,24.8107115273291683],"rgb":[1,0.4,0.4],"xyz":[0.483882609681476561,0.317254339618307069,0.161463640697229571],"hpluv":[12.1770506300618742,236.482353971627703,63.1153061541487119],"hsluv":[12.1770506300618742,99.9999999999999,63.1153061541487119]},"#ff6677":{"lch":[63.4237926928396121,111.744324598031497,7.65713975886231157],"luv":[63.4237926928396121,110.747917341816802,14.8893547314965815],"rgb":[1,0.4,0.466666666666666674],"xyz":[0.493196622701678467,0.320979944826387864,0.21051744260362748],"hpluv":[7.65713975886231157,223.569519019308729,63.4237926928396121],"hsluv":[7.65713975886231157,100.000000000000071,63.4237926928396121]},"#ff6688":{"lch":[63.7896518301749751,106.125321016318935,2.05404070639815961],"luv":[63.7896518301749751,106.057131857198982,3.80375380925921824],"rgb":[1,0.4,0.533333333333333326],"xyz":[0.504337039803622322,0.325436111667165462,0.26919030600719962],"hpluv":[2.05404070639815961,211.109662635719985,63.7896518301749751],"hsluv":[2.05404070639815961,100.000000000000128,63.7896518301749751]},"#ff6699":{"lch":[64.2142253202301276,101.344202045456129,355.341285926877504],"luv":[64.2142253202301276,101.009378125853075,-8.23121004826552394],"rgb":[1,0.4,0.6],"xyz":[0.517394003912695,0.330658897310794619,0.337956983648317555],"hpluv":[355.341285926877504,200.265890662959123,64.2142253202301276],"hsluv":[355.341285926877504,100.000000000000199,64.2142253202301276]},"#ff66aa":{"lch":[64.6983418323177233,97.9876087444390436,347.629516841099075],"luv":[64.6983418323177233,95.7126081968365838,-20.9920961224008664],"rgb":[1,0.4,0.66666666666666663],"xyz":[0.532451696877947578,0.336681974496895742,0.417260833265316156],"hpluv":[347.629516841099075,192.184047560801417,64.6983418323177233],"hsluv":[347.629516841099075,100.000000000000441,64.6983418323177233]},"#ff66bb":{"lch":[65.2423543089962408,96.5541832870936787,339.215698562051898],"luv":[65.2423543089962408,90.270720679111,-34.2623306024503123],"rgb":[1,0.4,0.733333333333333282],"xyz":[0.549589288985284141,0.3435370113398305,0.507518818363957847],"hpluv":[339.215698562051898,187.793604034801348,65.2423543089962408],"hsluv":[339.215698562051898,100.000000000000597,65.2423543089962408]},"#ff66cc":{"lch":[65.8461771980182533,97.3465701370285,330.562118792095362],"luv":[65.8461771980182533,84.7780628468697159,-47.8438583036069218],"rgb":[1,0.4,0.8],"xyz":[0.568881664429005895,0.351253961517319302,0.609125329034228],"hpluv":[330.562118792095362,187.598522894675455,65.8461771980182533],"hsluv":[330.562118792095362,100.000000000000711,65.8461771980182533]},"#ff66dd":{"lch":[66.5093249736543157,100.405273498350255,322.181562409870594],"luv":[66.5093249736543157,79.3159229397089831,-61.5646271368605298],"rgb":[1,0.4,0.866666666666666696],"xyz":[0.590399989979639495,0.359861291737572808,0.722455176934234],"hpluv":[322.181562409870594,191.563741116159406,66.5093249736543157],"hsluv":[322.181562409870594,100.000000000000952,66.5093249736543157]},"#ff66ee":{"lch":[67.2309523334132706,105.527911758853008,314.488878023448478],"luv":[67.2309523334132706,73.9508789773533408,-75.2821868615750702],"rgb":[1,0.4,0.933333333333333348],"xyz":[0.614212170245814204,0.369386163844042825,0.847865993002757223],"hpluv":[314.488878023448478,199.176184031939982,67.2309523334132706],"hsluv":[314.488878023448478,100.000000000001066,67.2309523334132706]},"#ff66ff":{"lch":[68.0098958254125137,112.360313920932768,307.715012949244056],"luv":[68.0098958254125137,68.7346624616611592,-88.8841173702707437],"rgb":[1,0.4,1],"xyz":[0.640383218655645625,0.379854583207975549,0.985700181294539179],"hpluv":[307.715012949244056,209.642901019847784,68.0098958254125137],"hsluv":[307.715012949244056,100.000000000001421,68.0098958254125137]},"#ff7700":{"lch":[65.3236824647912755,127.817378582796977,27.3102887077963814],"luv":[65.3236824647912755,113.570196302134065,58.6437786953806466],"rgb":[1,0.466666666666666674,0],"xyz":[0.478356168307233265,0.344569743954078356,0.0413192750626848],"hpluv":[27.3102887077963814,248.289625700463205,65.3236824647912755],"hsluv":[27.3102887077963814,100.00000000000226,65.3236824647912755]},"#ff7711":{"lch":[65.3555057958206476,126.824695098806032,26.9045059733925385],"luv":[65.3555057958206476,113.097436843789765,57.3887886809793],"rgb":[1,0.466666666666666674,0.0666666666666666657],"xyz":[0.479367833806870369,0.344974410153933209,0.0466473800274404271],"hpluv":[26.9045059733925385,246.24134425049084,65.3555057958206476],"hsluv":[26.9045059733925385,99.9999999999999716,65.3555057958206476]},"#ff7722":{"lch":[65.4144320044565291,125.020679344179442,26.1430666348463],"luv":[65.4144320044565291,112.230643754756542,55.0858681158159058],"rgb":[1,0.466666666666666674,0.133333333333333331],"xyz":[0.481243191945347426,0.345724553409324,0.0565242662234196266],"hpluv":[26.1430666348463,242.520026060215031,65.4144320044565291],"hsluv":[26.1430666348463,100.000000000000156,65.4144320044565291]},"#ff7733":{"lch":[65.511267747206432,122.151716277204869,24.8632302030062533],"luv":[65.511267747206432,110.829965834679456,51.3591322215488688],"rgb":[1,0.466666666666666674,0.2],"xyz":[0.484330942677805132,0.346959653702307136,0.0727864200810307699],"hpluv":[24.8632302030062533,236.604443239770575,65.511267747206432],"hsluv":[24.8632302030062533,100.000000000000128,65.511267747206432]},"#ff7744":{"lch":[65.6506715027637853,118.22870540382597,22.9581907744090898],"luv":[65.6506715027637853,108.863777518532288,46.1162089276663139],"rgb":[1,0.466666666666666674,0.266666666666666663],"xyz":[0.488788937922024047,0.348742851799994702,0.0962651950339174084],"hpluv":[22.9581907744090898,228.519406804146513,65.6506715027637853],"hsluv":[22.9581907744090898,100.000000000000199,65.6506715027637853]},"#ff7755":{"lch":[65.8363783536997857,113.378413750733145,20.3056908730066645],"luv":[65.8363783536997857,106.332452226692425,39.3455754576116092],"rgb":[1,0.466666666666666674,0.333333333333333315],"xyz":[0.494751351961386032,0.351127817415739563,0.12766724230789131],"hpluv":[20.3056908730066645,218.526329281612362,65.8363783536997857],"hsluv":[20.3056908730066645,100.000000000000171,65.8363783536997857]},"#ff7766":{"lch":[66.0714111968285351,107.847817312906827,16.7638759706376135],"luv":[66.0714111968285351,103.264450834403533,31.1063481146080676],"rgb":[1,0.466666666666666674,0.4],"xyz":[0.502336347734894217,0.354161815725142881,0.167614886715035272],"hpluv":[16.7638759706376135,207.127185394700234,66.0714111968285351],"hsluv":[16.7638759706376135,100.000000000000426,66.0714111968285351]},"#ff7777":{"lch":[66.3581913431115851,102.006782949974053,12.1770506300619488],"luv":[66.3581913431115851,99.7116772923406671,21.5166256497442419],"rgb":[1,0.466666666666666674,0.466666666666666674],"xyz":[0.511650360755096067,0.357887420933223677,0.216668688621433181],"hpluv":[12.1770506300619488,195.062523033846361,66.3581913431115851],"hsluv":[12.1770506300619488,100.000000000000355,66.3581913431115851]},"#ff7788":{"lch":[66.6986047917809,96.3441833198397291,6.39999172914420456],"luv":[66.6986047917809,95.7437601312525,10.7393694179903871],"rgb":[1,0.466666666666666674,0.533333333333333326],"xyz":[0.52279077785704,0.362343587774001274,0.275341552025005376],"hpluv":[6.39999172914420456,183.293927388427107,66.6986047917809],"hsluv":[6.39999172914420456,100.000000000000639,66.6986047917809]},"#ff7799":{"lch":[67.0940474565320244,91.4474963932601,359.352586865695173],"luv":[67.0940474565320244,91.4416585161261679,-1.0332881570442134],"rgb":[1,0.466666666666666674,0.6],"xyz":[0.535847741966112623,0.367566373417630432,0.344108229666123255],"hpluv":[359.352586865695173,172.952623850798517,67.0940474565320244],"hsluv":[359.352586865695173,100.000000000000782,67.0940474565320244]},"#ff77aa":{"lch":[67.5454605183692,87.9484746627524,351.107126776790835],"luv":[67.5454605183692,86.8912550098467449,-13.5957345634056814],"rgb":[1,0.466666666666666674,0.66666666666666663],"xyz":[0.550905434931365234,0.373589450603731554,0.423412079283121856],"hpluv":[351.107126776790835,165.223368139110704,67.5454605183692],"hsluv":[351.107126776790835,100.000000000000938,67.5454605183692]},"#ff77bb":{"lch":[68.0533617234635244,86.4195813509952,341.973592157308417],"luv":[68.0533617234635244,82.1775887588569,-26.7429980866295693],"rgb":[1,0.466666666666666674,0.733333333333333282],"xyz":[0.568043027038701798,0.380444487446666313,0.513670064381763658],"hpluv":[341.973592157308417,161.139458954487083,68.0533617234635244],"hsluv":[341.973592157308417,100.000000000001037,68.0533617234635244]},"#ff77cc":{"lch":[68.6178757233526682,87.2373067072756214,332.49967924393593],"luv":[68.6178757233526682,77.3802105983984347,-40.2821385887937],"rgb":[1,0.466666666666666674,0.8],"xyz":[0.587335402482423552,0.388161437624155115,0.615276575052033814],"hpluv":[332.49967924393593,161.325977991170333,68.6178757233526682],"hsluv":[332.49967924393593,100.000000000001265,68.6178757233526682]},"#ff77dd":{"lch":[69.238765020261809,90.480647802514838,323.326201907778],"luv":[69.238765020261809,72.5699004301545756,-54.0403291840472],"rgb":[1,0.466666666666666674,0.866666666666666696],"xyz":[0.608853728033057151,0.396768767844408621,0.728606422952039812],"hpluv":[323.326201907778,165.823361543811586,69.238765020261809],"hsluv":[323.326201907778,100.00000000000145,69.238765020261809]},"#ff77ee":{"lch":[69.9154621504300593,95.9376886025569604,314.973456368277198],"luv":[69.9154621504300593,67.8067552495039791,-67.8696105553513149],"rgb":[1,0.466666666666666674,0.933333333333333348],"xyz":[0.632665908299231861,0.406293639950878638,0.854017239020563],"hpluv":[314.973456368277198,174.122680701596721,69.9154621504300593],"hsluv":[314.973456368277198,100.000000000001535,69.9154621504300593]},"#ff77ff":{"lch":[70.6471031550122,103.213892868752552,307.715012949244283],"luv":[70.6471031550122,63.1394826173239494,-81.6487196221654],"rgb":[1,0.466666666666666674,1],"xyz":[0.658836956709063282,0.416762059314811362,0.99185142731234488],"hpluv":[307.715012949244283,185.388643374650655,70.6471031550122],"hsluv":[307.715012949244283,100.000000000001975,70.6471031550122]},"#ff8800":{"lch":[68.6580440198892603,118.150361410828182,32.8458067740872153],"luv":[68.6580440198892603,99.2620471866307383,64.0823992202883375],"rgb":[1,0.533333333333333326,0],"xyz":[0.500428538032203774,0.388714483404019873,0.0486767316376747472],"hpluv":[32.8458067740872153,218.364961888913399,68.6580440198892603],"hsluv":[32.8458067740872153,100.000000000002245,68.6580440198892603]},"#ff8811":{"lch":[68.6874112197728408,117.19102013872596,32.4606037779481582],"luv":[68.6874112197728408,98.8811760474647485,62.89871401408422],"rgb":[1,0.533333333333333326,0.0666666666666666657],"xyz":[0.501440203531840933,0.389119149603874726,0.0540048366024303769],"hpluv":[32.4606037779481582,216.499308154785638,68.6874112197728408],"hsluv":[32.4606037779481582,100.000000000000739,68.6874112197728408]},"#ff8822":{"lch":[68.7417963707939492,115.443262249268372,31.7362513605757321],"luv":[68.7417963707939492,98.1820090833864754,60.724294076614548],"rgb":[1,0.533333333333333326,0.133333333333333331],"xyz":[0.503315561670317879,0.389869292859265526,0.0638817227984095765],"hpluv":[31.7362513605757321,213.101761826290613,68.7417963707939492],"hsluv":[31.7362513605757321,100.000000000000668,68.7417963707939492]},"#ff8833":{"lch":[68.8311889682804292,112.651741292777714,30.5141745142023382],"luv":[68.8311889682804292,97.0498776480180823,57.1990914683060439],"rgb":[1,0.533333333333333326,0.2],"xyz":[0.506403312402775696,0.391104393152248653,0.0801438766560207128],"hpluv":[30.5141745142023382,207.678703624478942,68.8311889682804292],"hsluv":[30.5141745142023382,100.000000000000824,68.8311889682804292]},"#ff8844":{"lch":[68.9599197258043688,108.808998086617962,28.6842020071901302],"luv":[68.9599197258043688,95.4557990826346554,52.2263198599069725],"rgb":[1,0.533333333333333326,0.266666666666666663],"xyz":[0.510861307646994556,0.392887591249936219,0.103622651608907351],"hpluv":[28.6842020071901302,200.2199693629492,68.9599197258043688],"hsluv":[28.6842020071901302,100.000000000000838,68.9599197258043688]},"#ff8855":{"lch":[69.1314852187197602,104.012526361958052,26.1137258191789492],"luv":[69.1314852187197602,93.395152113439849,45.7815596272609682],"rgb":[1,0.533333333333333326,0.333333333333333315],"xyz":[0.516823721686356485,0.39527255686568108,0.135024698882881267],"hpluv":[26.1137258191789492,190.918970683811096,69.1314852187197602],"hsluv":[26.1137258191789492,100.000000000000867,69.1314852187197602]},"#ff8866":{"lch":[69.3487452092138881,98.4723154092605171,22.6389332988698087],"luv":[69.3487452092138881,90.8849122177262529,37.9042165627661802],"rgb":[1,0.533333333333333326,0.4],"xyz":[0.524408717459864726,0.398306555175084398,0.174972343290025228],"hpluv":[22.6389332988698087,180.183437843423292,69.3487452092138881],"hsluv":[22.6389332988698087,100.000000000001066,69.3487452092138881]},"#ff8877":{"lch":[69.6140261744794344,92.5206854303452246,18.0637242730473773],"luv":[69.6140261744794344,87.9605480867009248,28.6883114314535526],"rgb":[1,0.533333333333333326,0.466666666666666674],"xyz":[0.533722730480066576,0.402032160383165194,0.224026145196423138],"hpluv":[18.0637242730473773,168.648085666048672,69.6140261744794344],"hsluv":[18.0637242730473773,100.000000000001108,69.6140261744794344]},"#ff8888":{"lch":[69.9291829132988596,86.6211090413054,12.1770506300619186],"luv":[69.9291829132988596,84.672173963834382,18.2712749359175248],"rgb":[1,0.533333333333333326,0.533333333333333326],"xyz":[0.544863147582010487,0.406488327223942791,0.282699008599995305],"hpluv":[12.1770506300619186,157.182652238587849,69.9291829132988596],"hsluv":[12.1770506300619186,100.000000000001251,69.9291829132988596]},"#ff8899":{"lch":[70.29563969089034,81.3665448969888274,4.80888772903122597],"luv":[70.29563969089034,81.0801238487794791,6.82115423812421362],"rgb":[1,0.533333333333333326,0.6],"xyz":[0.557920111691083132,0.411711112867571949,0.351465686241113184],"hpluv":[4.80888772903122597,146.878021536398364,70.29563969089034],"hsluv":[4.80888772903122597,100.00000000000135,70.29563969089034]},"#ff88aa":{"lch":[70.7144212664750427,77.4442353273614827,355.944797831634332],"luv":[70.7144212664750427,77.2503443699940533,-5.47666688388778589],"rgb":[1,0.533333333333333326,0.66666666666666663],"xyz":[0.572977804656335743,0.417734190053673071,0.430769535858111841],"hpluv":[355.944797831634332,138.969799542374091,70.7144212664750427],"hsluv":[355.944797831634332,100.000000000001648,70.7144212664750427]},"#ff88bb":{"lch":[71.1861792611668847,75.5334875912487718,345.875835641341098],"luv":[71.1861792611668847,73.2500487000805,-18.431986141845023],"rgb":[1,0.533333333333333326,0.733333333333333282],"xyz":[0.590115396763672306,0.42458922689660783,0.521027520956753532],"hpluv":[345.875835641341098,134.642814203514433,71.1861792611668847],"hsluv":[345.875835641341098,100.000000000001776,71.1861792611668847]},"#ff88cc":{"lch":[71.711216864189268,76.1313589407537563,335.260476444218114],"luv":[71.711216864189268,69.1440007334156377,-31.8604924121286039],"rgb":[1,0.533333333333333326,0.8],"xyz":[0.609407772207394061,0.432306177074096631,0.622634031627023687],"hpluv":[335.260476444218114,134.714956877670915,71.711216864189268],"hsluv":[335.260476444218114,100.000000000001933,71.711216864189268]},"#ff88dd":{"lch":[72.2895135005839649,79.3885839073469413,324.950129439258774],"luv":[72.2895135005839649,64.9916618916399784,-45.5920074067441803],"rgb":[1,0.533333333333333326,0.866666666666666696],"xyz":[0.63092609775802766,0.440913507294350138,0.735963879527029685],"hpluv":[324.950129439258774,139.354847315526115,72.2895135005839649],"hsluv":[324.950129439258774,100.000000000002245,72.2895135005839649]},"#ff88ee":{"lch":[72.9207502525545124,85.0855243828499,315.651995307064169],"luv":[72.9207502525545124,60.845281511842515,-59.4760302748021203],"rgb":[1,0.533333333333333326,0.933333333333333348],"xyz":[0.654738278024202369,0.450438379400820155,0.861374695595552908],"hpluv":[315.651995307064169,148.062090862911901,72.9207502525545124],"hsluv":[315.651995307064169,100.000000000002444,72.9207502525545124]},"#ff88ff":{"lch":[73.6043362991539709,92.7672005781522842,307.715012949244624],"luv":[73.6043362991539709,56.7488822053271349,-73.3847250560567375],"rgb":[1,0.533333333333333326,1],"xyz":[0.68090932643403379,0.460906798764752879,0.999208883887334753],"hpluv":[307.715012949244624,159.930161835956909,73.6043362991539709],"hsluv":[307.715012949244624,100.000000000002771,73.6043362991539709]},"#ff9900":{"lch":[72.2588108283115389,109.907462524380705,39.4434130396340095],"luv":[72.2588108283115389,84.8763034831777077,69.8259509464759418],"rgb":[1,0.6,0],"xyz":[0.526298138484671219,0.440453684308955595,0.057299931788497],"hpluv":[39.4434130396340095,193.008172097547572,72.2588108283115389],"hsluv":[39.4434130396340095,100.000000000002288,72.2588108283115389]},"#ff9911":{"lch":[72.2858317740783889,108.970035258541955,39.0927051304156805],"luv":[72.2858317740783889,84.5745536570817791,68.7139975401902348],"rgb":[1,0.6,0.0666666666666666657],"xyz":[0.527309803984308378,0.440858350508810448,0.0626280367532526389],"hpluv":[39.0927051304156805,191.2904264008464,72.2858317740783889],"hsluv":[39.0927051304156805,100.000000000001506,72.2858317740783889]},"#ff9922":{"lch":[72.3358777005795304,107.257428554778556,38.4317580680427469],"luv":[72.3358777005795304,84.0200042697796,66.6692947517044274],"rgb":[1,0.6,0.133333333333333331],"xyz":[0.529185162122785324,0.441608493764201249,0.0725049229492318315],"hpluv":[38.4317580680427469,188.15378179700545,72.3358777005795304],"hsluv":[38.4317580680427469,100.00000000000145,72.3358777005795304]},"#ff9933":{"lch":[72.418154282067718,104.508625212907305,37.3122251519614778],"luv":[72.418154282067718,83.1203251002183237,63.3487513620113845],"rgb":[1,0.6,0.2],"xyz":[0.532272912855243141,0.442843594057184375,0.0887670768068429816],"hpluv":[37.3122251519614778,183.123470124205028,72.418154282067718],"hsluv":[37.3122251519614778,100.00000000000162,72.418154282067718]},"#ff9944":{"lch":[72.5366731246789556,100.695423976150749,35.6250099256014607],"luv":[72.5366731246789556,81.8499313718691326,58.6528528219818952],"rgb":[1,0.6,0.266666666666666663],"xyz":[0.536730908099462,0.444626792154871942,0.11224585175972962],"hpluv":[35.6250099256014607,176.153561585765658,72.5366731246789556],"hsluv":[35.6250099256014607,100.000000000001748,72.5366731246789556]},"#ff9955":{"lch":[72.6946936633514582,95.8821930466240673,33.2320443565807508],"luv":[72.6946936633514582,80.201421842029859,52.5464259293328269],"rgb":[1,0.6,0.333333333333333315],"xyz":[0.54269332213882393,0.447011757770616802,0.143647899033703508],"hpluv":[33.2320443565807508,167.368827825995851,72.6946936633514582],"hsluv":[33.2320443565807508,100.000000000001705,72.6946936633514582]},"#ff9966":{"lch":[72.8949069034106,90.2347392462793749,29.9516480142673025],"luv":[72.8949069034106,78.1836232693519406,45.0514064077924345],"rgb":[1,0.6,0.4],"xyz":[0.550278317912332171,0.450045756080020121,0.183595543440847497],"hpluv":[29.9516480142673025,157.078197331028889,72.8949069034106],"hsluv":[29.9516480142673025,100.000000000001819,72.8949069034106]},"#ff9977":{"lch":[73.1395321193821,84.0351966301436,25.5464816978182121],"luv":[73.1395321193821,75.8195571045914,36.2396058633438045],"rgb":[1,0.6,0.466666666666666674],"xyz":[0.559592330932534,0.453771361288100916,0.232649345347245406],"hpluv":[25.5464816978182121,145.796926659053128,73.1395321193821],"hsluv":[25.5464816978182121,100.000000000002018,73.1395321193821]},"#ff9988":{"lch":[73.430374185650777,77.702838567593929,19.7240568661095921],"luv":[73.430374185650777,73.1439295084068419,26.2239718107454713],"rgb":[1,0.6,0.533333333333333326],"xyz":[0.570732748034477932,0.458227528128878514,0.291322208750817546],"hpluv":[19.7240568661095921,134.276641294628575,73.430374185650777],"hsluv":[19.7240568661095921,100.000000000002203,73.430374185650777]},"#ff9999":{"lch":[73.76886125649402,71.8160022700114098,12.1770506300620251],"luv":[73.76886125649402,70.2001752793754,15.1483851545719261],"rgb":[1,0.6,0.6],"xyz":[0.583789712143550577,0.463450313772507672,0.360088886391935481],"hpluv":[12.1770506300620251,123.534275619879125,73.76886125649402],"hsluv":[12.1770506300620251,100.000000000002331,73.76886125649402]},"#ff99aa":{"lch":[74.1560723225582592,67.1124973440613,2.7130535693684088],"luv":[74.1560723225582592,67.0372720786985923,3.17670458229478481],"rgb":[1,0.6,0.66666666666666663],"xyz":[0.598847405108803188,0.469473390958608794,0.439392736008934082],"hpluv":[2.7130535693684088,114.840746523486033,74.1560723225582592],"hsluv":[2.7130535693684088,100.00000000000253,74.1560723225582592]},"#ff99bb":{"lch":[74.5927597146433925,64.4136927281220864,351.502648062184],"luv":[74.5927597146433925,63.7066038913843187,-9.51800564715024322],"rgb":[1,0.6,0.733333333333333282],"xyz":[0.615984997216139751,0.476328427801543552,0.529650721107575828],"hpluv":[351.502648062184,109.577363966618833,74.5927597146433925],"hsluv":[351.502648062184,100.000000000002615,74.5927597146433925]},"#ff99cc":{"lch":[75.0793694015197,64.4152606478183145,339.305696269483292],"luv":[75.0793694015197,60.259134450288677,-22.7631834247409977],"rgb":[1,0.6,0.8],"xyz":[0.635277372659861506,0.484045377979032354,0.631257231777846],"hpluv":[339.305696269483292,108.869813431806975,75.0793694015197],"hsluv":[339.305696269483292,100.000000000002871,75.0793694015197]},"#ff99dd":{"lch":[75.6160606971696296,67.4118390527965232,327.324068761847229],"luv":[75.6160606971696296,56.7430830794555,-36.3947601601954887],"rgb":[1,0.6,0.866666666666666696],"xyz":[0.656795698210495105,0.49265270819928586,0.744587079677852],"hpluv":[327.324068761847229,113.125745227459021,75.6160606971696296],"hsluv":[327.324068761847229,100.00000000000324,75.6160606971696296]},"#ff99ee":{"lch":[76.202726253448489,73.1905233351813393,316.627151984536795],"luv":[76.202726253448489,53.2022056350908201,-50.2630880631028845],"rgb":[1,0.6,0.933333333333333348],"xyz":[0.680607878476669814,0.502177580305755877,0.869997895746375205],"hpluv":[316.627151984536795,123.107716827744753,76.202726253448489],"hsluv":[316.627151984536795,100.000000000003524,76.202726253448489]},"#ff99ff":{"lch":[76.8390127436129,81.2030526869262275,307.715012949245],"luv":[76.8390127436129,49.6746958291708154,-64.2367524082209229],"rgb":[1,0.6,1],"xyz":[0.706778926886501235,0.512645999669688601,1.00783208403815694],"hpluv":[307.715012949245,141.150312559224801,76.8390127436129],"hsluv":[307.715012949245,100.000000000003752,76.8390127436129]},"#ee0000":{"lch":[49.7142799595632,167.190689697178925,12.1770506300617765],"luv":[49.7142799595632,163.428976145092918,35.2660811203203934],"rgb":[0.933333333333333348,0,0],"xyz":[0.352591085030832,0.181804778219026603,0.0165277071108199],"hpluv":[12.1770506300617765,426.746789183125202,49.7142799595632],"hsluv":[12.1770506300617765,100.000000000002217,49.7142799595632]},"#ee0011":{"lch":[49.7630000621001756,165.722449822455,11.6881730851639158],"luv":[49.7630000621001756,162.286136628676445,33.5729092170260728],"rgb":[0.933333333333333348,0,0.0666666666666666657],"xyz":[0.353602750530469079,0.182209444418881455,0.0218558120755755342],"hpluv":[11.6881730851639158,422.585038037937124,49.7630000621001756],"hsluv":[11.6881730851639158,99.9999999999963762,49.7630000621001756]},"#ee0022":{"lch":[49.8531236873270558,163.0858535413212,10.7756858750078184],"luv":[49.8531236873270558,160.210108449799208,30.4912573667411486],"rgb":[0.933333333333333348,0,0.133333333333333331],"xyz":[0.355478108668946136,0.182959587674272284,0.0317326982715547268],"hpluv":[10.7756858750078184,415.110044299310516,49.8531236873270558],"hsluv":[10.7756858750078184,99.9999999999964473,49.8531236873270558]},"#ee0033":{"lch":[50.000975779064234,158.977402767524836,9.25647316775448559],"luv":[50.000975779064234,156.907230803998146,25.5721628363475],"rgb":[0.933333333333333348,0,0.2],"xyz":[0.358565859401403841,0.184194687967255383,0.047994852129165877],"hpluv":[9.25647316775448559,403.456061197389261,50.000975779064234],"hsluv":[9.25647316775448559,99.9999999999965183,50.000975779064234]},"#ee0044":{"lch":[50.2132784041556164,153.529579514286212,7.02933300215353],"luv":[50.2132784041556164,152.375594335021788,18.7885613308314312],"rgb":[0.933333333333333348,0,0.266666666666666663],"xyz":[0.363023854645622757,0.185977886064942977,0.0714736270820525155],"hpluv":[7.02933300215353,387.983100931209492,50.2132784041556164],"hsluv":[7.02933300215353,99.9999999999966462,50.2132784041556164]},"#ee0055":{"lch":[50.4951150037793326,147.071833727726926,3.99754465361350508],"luv":[50.4951150037793326,146.714013644902735,10.2529252527975352],"rgb":[0.933333333333333348,0,0.333333333333333315],"xyz":[0.368986268684984742,0.188362851680687809,0.102875674356026417],"hpluv":[3.99754465361350508,369.589367027053072,50.4951150037793326],"hsluv":[3.99754465361350508,99.999999999996831,50.4951150037793326]},"#ee0066":{"lch":[50.8502318550204109,140.098840030056522,0.0757634158231174915],"luv":[50.8502318550204109,140.0987175463531,0.185255592479522613],"rgb":[0.933333333333333348,0,0.4],"xyz":[0.376571264458492927,0.1913968499900911,0.142823318763170393],"hpluv":[0.0757634158231174915,349.60765112382461,50.8502318550204109],"hsluv":[0.0757634158231174915,99.9999999999970584,50.8502318550204109]},"#ee0077":{"lch":[51.2812017254514956,133.219993530026585,355.209470699020642],"luv":[51.2812017254514956,132.754613083251769,-11.1256182415379214],"rgb":[0.933333333333333348,0,0.466666666666666674],"xyz":[0.385885277478694833,0.195122455198171924,0.191877120669568302],"hpluv":[355.209470699020642,329.648072606093592,51.2812017254514956],"hsluv":[355.209470699020642,99.9999999999973284,51.2812017254514956]},"#ee0088":{"lch":[51.7895361854883163,127.090944021268115,349.407446028193533],"luv":[51.7895361854883163,124.925218603260163,-23.3623160055840167],"rgb":[0.933333333333333348,0,0.533333333333333326],"xyz":[0.397025694580638633,0.199578622038949521,0.250549984073140442],"hpluv":[349.407446028193533,311.395197619459395,51.7895361854883163],"hsluv":[349.407446028193533,99.9999999999974705,51.7895361854883163]},"#ee0099":{"lch":[52.3757812732210652,122.329563392952366,342.780178840499048],"luv":[52.3757812732210652,116.846263825073436,-36.2142611415956637],"rgb":[0.933333333333333348,0,0.6],"xyz":[0.410082658689711388,0.204801407682578679,0.319316661714258376],"hpluv":[342.780178840499048,296.374093031221,52.3757812732210652],"hsluv":[342.780178840499048,99.9999999999978,52.3757812732210652]},"#ee00aa":{"lch":[53.0396114453995722,119.424239873739239,335.563712743666827],"luv":[53.0396114453995722,108.726436909364494,-49.403552366347],"rgb":[0.933333333333333348,0,0.66666666666666663],"xyz":[0.425140351654963888,0.210824484868679773,0.398620511331257],"hpluv":[335.563712743666827,285.713971708863653,53.0396114453995722],"hsluv":[335.563712743666827,99.999999999998,53.0396114453995722]},"#ee00bb":{"lch":[53.779927529436435,118.655378520732356,328.101249142938343],"luv":[53.779927529436435,100.736423933687789,-62.6998544252744381],"rgb":[0.933333333333333348,0,0.733333333333333282],"xyz":[0.442277943762300563,0.217679521711614532,0.488878496429898723],"hpluv":[328.101249142938343,279.966806180862307,53.779927529436435],"hsluv":[328.101249142938343,99.9999999999982094,53.779927529436435]},"#ee00cc":{"lch":[54.5949595671901,120.061129768120921,320.773339602207614],"luv":[54.5949595671901,93.0053918954961,-75.9254368414351575],"rgb":[0.933333333333333348,0,0.8],"xyz":[0.461570319206022317,0.225396471889103334,0.590485007100168824],"hpluv":[320.773339602207614,279.054611328209262,54.5949595671901],"hsluv":[320.773339602207614,99.9999999999984,54.5949595671901]},"#ee00dd":{"lch":[55.4823728661035744,123.466264594666441,313.907226483092529],"luv":[55.4823728661035744,85.6229535549924634,-88.9529556421808252],"rgb":[0.933333333333333348,0,0.866666666666666696],"xyz":[0.483088644756655805,0.234003802109356868,0.703814855000174822],"hpluv":[313.907226483092529,282.379138449157608,55.4823728661035744],"hsluv":[313.907226483092529,99.9999999999986215,55.4823728661035744]},"#ee00ee":{"lch":[56.4393743497109597,128.559742977308588,307.715012949243601],"luv":[56.4393743497109597,78.644409501394918,-101.698890694877051],"rgb":[0.933333333333333348,0,0.933333333333333348],"xyz":[0.506900825022830626,0.243528674215826912,0.829225671068698],"hpluv":[307.715012949243601,289.042783730483393,56.4393743497109597],"hsluv":[307.715012949243601,99.9999999999988489,56.4393743497109597]},"#ee00ff":{"lch":[57.4628159598150745,134.982567880189606,302.284502363601803],"luv":[57.4628159598150745,72.0973885084188879,-114.115118199983058],"rgb":[0.933333333333333348,0,1],"xyz":[0.533071873432661936,0.253997093579759636,0.96705985936047989],"hpluv":[302.284502363601803,298.078126285043766,57.4628159598150745],"hsluv":[302.284502363601803,99.9999999999989484,57.4628159598150745]},"#ee1100":{"lch":[50.1937733395544683,164.746074066243921,12.6667024036514828],"luv":[50.1937733395544683,160.736507742479517,36.1253927174799117],"rgb":[0.933333333333333348,0.0666666666666666657,0],"xyz":[0.354595485291760382,0.185813578740883473,0.0171958405311293527],"hpluv":[12.6667024036514828,416.489977947977081,50.1937733395544683],"hsluv":[12.6667024036514828,100.000000000002245,50.1937733395544683]},"#ee1111":{"lch":[50.2417909300708345,163.305921695383518,12.1770506300617907],"luv":[50.2417909300708345,159.631613634988071,34.4466542507197317],"rgb":[0.933333333333333348,0.0666666666666666657,0.0666666666666666657],"xyz":[0.355607150791397486,0.186218244940738326,0.0225239454958849825],"hpluv":[12.1770506300617907,412.454596338970589,50.2417909300708345],"hsluv":[12.1770506300617907,96.6508962208003197,50.2417909300708345]},"#ee1122":{"lch":[50.3306190654122219,160.718934991358793,11.2629010575952293],"luv":[50.3306190654122219,157.623701713525833,31.390201064696047],"rgb":[0.933333333333333348,0.0666666666666666657,0.133333333333333331],"xyz":[0.357482508929874543,0.186968388196129154,0.032400831691864182],"hpluv":[11.2629010575952293,405.204351077732667,50.3306190654122219],"hsluv":[11.2629010575952293,96.6948337079543592,50.3306190654122219]},"#ee1133":{"lch":[50.4763571232054318,156.685700791802191,9.74029685215880647],"luv":[50.4763571232054318,154.427033260746356,26.5084935615454427],"rgb":[0.933333333333333348,0.0666666666666666657,0.2],"xyz":[0.360570259662332249,0.188203488489112253,0.0486629855494753252],"hpluv":[9.74029685215880647,393.895198182016713,50.4763571232054318],"hsluv":[9.74029685215880647,96.7647175585846,50.4763571232054318]},"#ee1144":{"lch":[50.6856484752898382,151.333831494518165,7.50679337730589413],"luv":[50.6856484752898382,150.036806479185287,19.7708183022028514],"rgb":[0.933333333333333348,0.0666666666666666657,0.266666666666666663],"xyz":[0.365028254906551164,0.189986686586799847,0.0721417605023619568],"hpluv":[7.50679337730589413,378.870112575267399,50.6856484752898382],"hsluv":[7.50679337730589413,96.8605546889772455,50.6856484752898382]},"#ee1155":{"lch":[50.9635312364098496,144.984673036864649,4.46374659640210858],"luv":[50.9635312364098496,144.544902405536135,11.283909082431693],"rgb":[0.933333333333333348,0.0666666666666666657,0.333333333333333315],"xyz":[0.370990668945913149,0.19237165220254468,0.103543807776335872],"hpluv":[4.46374659640210858,360.995599227985224,50.9635312364098496],"hsluv":[4.46374659640210858,96.9801964566503,50.9635312364098496]},"#ee1166":{"lch":[51.3137360134299598,138.123816003378664,0.523151936541392],"luv":[51.3137360134299598,138.118058344046318,1.26115288756691357],"rgb":[0.933333333333333348,0.0666666666666666657,0.4],"xyz":[0.378575664719421334,0.19540565051194797,0.143491452183479834],"hpluv":[0.523151936541392,341.565705345826359,51.3137360134299598],"hsluv":[0.523151936541392,97.1198273857598764,51.3137360134299598]},"#ee1177":{"lch":[51.7388469835676119,131.353183798903302,355.627348241097309],"luv":[51.7388469835676119,130.970848560864681,-10.014775152519368],"rgb":[0.933333333333333348,0.0666666666666666657,0.466666666666666674],"xyz":[0.38788967773962324,0.199131255720028794,0.192545254089877743],"hpluv":[355.627348241097309,322.153744971780554,51.7388469835676119],"hsluv":[355.627348241097309,97.2745732157476,51.7388469835676119]},"#ee1188":{"lch":[52.2404115410600411,125.324707533591734,349.782339698165117],"luv":[52.2404115410600411,123.337180279157977,-22.2311106147846438],"rgb":[0.933333333333333348,0.0666666666666666657,0.533333333333333326],"xyz":[0.39903009484156704,0.203587422560806391,0.251218117493449911],"hpluv":[349.782339698165117,304.417375015566734,52.2404115410600411],"hsluv":[349.782339698165117,97.4391430985313605,52.2404115410600411]},"#ee1199":{"lch":[52.819032808459994,120.657103381490217,343.097768544337384],"luv":[52.819032808459994,115.44498939014953,-35.079780802050081],"rgb":[0.933333333333333348,0.0666666666666666657,0.6],"xyz":[0.412087058950639795,0.208810208204435549,0.31998479513456779],"hpluv":[343.097768544337384,289.869003225703352,52.819032808459994],"hsluv":[343.097768544337384,97.6083995478766298,52.819032808459994]},"#ee11aa":{"lch":[53.4744599034404615,117.843047501566133,335.812437212199143],"luv":[53.4744599034404615,107.497497568615216,-48.2832461723726496],"rgb":[0.933333333333333348,0.0666666666666666657,0.66666666666666663],"xyz":[0.427144751915892296,0.214833285390536644,0.399288644751566446],"hpluv":[335.812437212199143,279.638449078323276,53.4744599034404615],"hsluv":[335.812437212199143,97.7777799659692,53.4744599034404615]},"#ee11bb":{"lch":[54.205681814132376,117.167943285093472,328.276238054291071],"luv":[54.205681814132376,99.6622453247977376,-61.6097702517929662],"rgb":[0.933333333333333348,0.0666666666666666657,0.733333333333333282],"xyz":[0.44428234402322897,0.221688322233471402,0.489546629850208137],"hpluv":[328.276238054291071,274.285798241860448,54.205681814132376],"hsluv":[328.276238054291071,97.9435422525978652,54.205681814132376]},"#ee11cc":{"lch":[55.0110259993956703,118.672848043699901,320.878255441103249],"luv":[55.0110259993956703,92.0672263164348266,-74.8790404666182781],"rgb":[0.933333333333333348,0.0666666666666666657,0.8],"xyz":[0.463574719466950724,0.229405272410960204,0.591153140520478293],"hpluv":[320.878255441103249,273.741691636115945,55.0110259993956703],"hsluv":[320.878255441103249,98.102849778989,55.0110259993956703]},"#ee11dd":{"lch":[55.8882602794840864,122.182226882396691,313.952719233652829],"luv":[55.8882602794840864,84.8023500190691522,-87.9605479586430334],"rgb":[0.933333333333333348,0.0666666666666666657,0.866666666666666696],"xyz":[0.485093045017584212,0.238012602631213738,0.704482988420484291],"hpluv":[313.952719233652829,277.412976370396279,55.8882602794840864],"hsluv":[313.952719233652829,98.2537358693348608,55.8882602794840864]},"#ee11ee":{"lch":[56.83469533821048,127.382376320214306,307.715012949243601],"luv":[56.83469533821048,77.9241738866570302,-100.767519176899128],"rgb":[0.933333333333333348,0.0666666666666666657,0.933333333333333348],"xyz":[0.508905225283758922,0.247537474737683783,0.829893804489007514],"hpluv":[307.715012949243601,284.403630900032795,56.83469533821048],"hsluv":[307.715012949243601,98.3949944120453495,56.83469533821048]},"#ee11ff":{"lch":[57.8472847680859275,133.910906422249354,302.2526850652647],"luv":[57.8472847680859275,71.4621107907268254,-113.248830369952643],"rgb":[0.933333333333333348,0.0666666666666666657,1],"xyz":[0.535076273693590343,0.258005894101616451,0.967727992780789359],"hpluv":[302.2526850652647,293.746227206253536,57.8472847680859275],"hsluv":[302.2526850652647,99.99999999999892,57.8472847680859275]},"#ee2200":{"lch":[51.0646940471157222,160.407609402057773,13.5847947923325787],"luv":[51.0646940471157222,155.919944837816502,37.677207378671163],"rgb":[0.933333333333333348,0.133333333333333331,0],"xyz":[0.358311109026528296,0.19324482621041944,0.018434381776051962],"hpluv":[13.5847947923325787,398.605749597291435,51.0646940471157222],"hsluv":[13.5847947923325787,100.000000000002203,51.0646940471157222]},"#ee2211":{"lch":[51.1114738997186322,159.015005648229618,13.0939108674416342],"luv":[51.1114738997186322,154.880623331235768,36.0244991337056817],"rgb":[0.933333333333333348,0.133333333333333331,0.0666666666666666657],"xyz":[0.3593227745261654,0.193649492410274293,0.0237624867408075952],"hpluv":[13.0939108674416342,394.783534202752207,51.1114738997186322],"hsluv":[13.0939108674416342,96.7702863870018462,51.1114738997186322]},"#ee2222":{"lch":[51.1980191888258105,156.511980987808,12.1770506300618031],"luv":[51.1980191888258105,152.990533465747774,33.0135860305098348],"rgb":[0.933333333333333348,0.133333333333333331,0.133333333333333331],"xyz":[0.361198132664642457,0.194399635665665121,0.0336393729367867877],"hpluv":[12.1770506300618031,387.912483642854795,51.1980191888258105],"hsluv":[12.1770506300618031,90.899918517349,51.1980191888258105]},"#ee2233":{"lch":[51.340031013958,152.605977320930094,10.6487510890373542],"luv":[51.340031013958,149.977869701108148,28.1996970549978769],"rgb":[0.933333333333333348,0.133333333333333331,0.2],"xyz":[0.364285883397100163,0.19563473595864822,0.0499015267943979379],"hpluv":[10.6487510890373542,377.185287566809563,51.340031013958],"hsluv":[10.6487510890373542,91.0856949770771,51.340031013958]},"#ee2244":{"lch":[51.5440125284501391,147.416232714385046,8.4042516634418849],"luv":[51.5440125284501391,145.833202035739902,21.5458314229178391],"rgb":[0.933333333333333348,0.133333333333333331,0.266666666666666663],"xyz":[0.368743878641319078,0.197417934056335814,0.0733803017472845764],"hpluv":[8.4042516634418849,362.916246958463432,51.5440125284501391],"hsluv":[8.4042516634418849,91.3409150161676848,51.5440125284501391]},"#ee2255":{"lch":[51.8149196409757,141.25016266814734,5.34127242035781613],"luv":[51.8149196409757,140.636840571385164,13.1486701942394504],"rgb":[0.933333333333333348,0.133333333333333331,0.333333333333333315],"xyz":[0.374706292680681063,0.199802899672080647,0.104782349021258478],"hpluv":[5.34127242035781613,345.918233291596209,51.8149196409757],"hsluv":[5.34127242035781613,91.6602615743055082,51.8149196409757]},"#ee2266":{"lch":[52.1564522427987924,134.577740656965489,1.36671179444129165],"luv":[52.1564522427987924,134.539455426625494,3.20986196595936],"rgb":[0.933333333333333348,0.133333333333333331,0.4],"xyz":[0.382291288454189249,0.202836897981483938,0.144729993428402454],"hpluv":[1.36671179444129165,327.419481975304109,52.1564522427987924],"hsluv":[1.36671179444129165,92.0339967122243365,52.1564522427987924]},"#ee2277":{"lch":[52.5712108639856694,127.988129961564283,356.416786702014292],"luv":[52.5712108639856694,127.737923683219179,-7.99901644943552803],"rgb":[0.933333333333333348,0.133333333333333331,0.466666666666666674],"xyz":[0.391605301474391154,0.206562503189564761,0.193783795334800363],"hpluv":[356.416786702014292,308.930679724572485,52.5712108639856694],"hsluv":[356.416786702014292,92.4494947830095,52.5712108639856694]},"#ee2288":{"lch":[53.0608018273771194,122.127216672078461,350.491948161024197],"luv":[53.0608018273771194,120.449481512132621,-20.1737318195523763],"rgb":[0.933333333333333348,0.133333333333333331,0.533333333333333326],"xyz":[0.402745718576334955,0.211018670030342359,0.25245665873837253],"hpluv":[350.491948161024197,292.063965437042782,53.0608018273771194],"hsluv":[350.491948161024197,92.892885362452958,53.0608018273771194]},"#ee2299":{"lch":[53.6259244704506557,117.615935516390621,343.699890485995525],"luv":[53.6259244704506557,112.888334232708232,-33.0110933105844282],"rgb":[0.933333333333333348,0.133333333333333331,0.6],"xyz":[0.415802682685407654,0.216241455673971517,0.32122333637949041],"hpluv":[343.699890485995525,278.311211390643507,53.6259244704506557],"hsluv":[343.699890485995525,93.3505397228777412,53.6259244704506557]},"#ee22aa":{"lch":[54.266455218013121,114.955451111725907,336.284451026203612],"luv":[54.266455218013121,105.247863162980167,-46.2346519390706305],"rgb":[0.933333333333333348,0.133333333333333331,0.66666666666666663],"xyz":[0.43086037565066021,0.222264532860072611,0.400527185996489],"hpluv":[336.284451026203612,268.805062052359688,54.266455218013121],"hsluv":[336.284451026203612,93.8102001508292,54.266455218013121]},"#ee22bb":{"lch":[54.981534566577821,114.440827463928798,328.608334651454868],"luv":[54.981534566577821,97.6897319326175,-59.6105633722921],"rgb":[0.933333333333333348,0.133333333333333331,0.733333333333333282],"xyz":[0.447997967757996884,0.22911956970300737,0.490785171095130757],"hpluv":[328.608334651454868,264.121319791368,54.981534566577821],"hsluv":[328.608334651454868,94.2616688954678636,54.981534566577821]},"#ee22cc":{"lch":[55.7696584616915629,116.118636638570607,321.077185717181408],"luv":[55.7696584616915629,90.3394913251054419,-72.9541916679335856],"rgb":[0.933333333333333348,0.133333333333333331,0.8],"xyz":[0.467290343201718583,0.236836519880496171,0.592391681765400913],"hpluv":[321.077185717181408,264.206361207665168,55.7696584616915629],"hsluv":[321.077185717181408,94.6970823725699518,55.7696584616915629]},"#ee22dd":{"lch":[56.6287730491083749,119.812596042269817,314.038835862099],"luv":[56.6287730491083749,83.2872216309056,-86.1295354880806201],"rgb":[0.933333333333333348,0.133333333333333331,0.866666666666666696],"xyz":[0.488808668752352182,0.245443850100749705,0.70572152966540691],"hpluv":[314.038835862099,268.475495638829329,56.6287730491083749],"hsluv":[314.038835862099,95.1108639535381,56.6287730491083749]},"#ee22ee":{"lch":[57.5563705104872128,125.203701850491953,307.715012949243658],"luv":[57.5563705104872128,76.5914038981754,-99.0440498261932163],"rgb":[0.933333333333333348,0.133333333333333331,0.933333333333333348],"xyz":[0.512620849018526892,0.254968722207219722,0.831132345733930133],"hpluv":[307.715012949243658,276.03432908057755,57.5563705104872128],"hsluv":[307.715012949243658,95.4994708803944263,57.5563705104872128]},"#ee22ff":{"lch":[58.5495832280214046,131.922896299071255,302.192710378625122],"luv":[58.5495832280214046,70.2843784028787582,-111.641196341030223],"rgb":[0.933333333333333348,0.133333333333333331,1],"xyz":[0.538791897428358313,0.265437141571152446,0.968966534025712],"hpluv":[302.192710378625122,285.914180736870946,58.5495832280214046],"hsluv":[302.192710378625122,99.9999999999989,58.5495832280214046]},"#ee3300":{"lch":[52.4512471844783761,153.77210005382733,15.1254552240259841],"luv":[52.4512471844783761,148.444942331914945,40.1242800687903269],"rgb":[0.933333333333333348,0.2,0],"xyz":[0.364428831115539142,0.205480270388441244,0.0204736224723888437],"hpluv":[15.1254552240259841,372.015515114283232,52.4512471844783761],"hsluv":[15.1254552240259841,100.000000000002174,52.4512471844783761]},"#ee3311":{"lch":[52.4961529429458693,152.446596739109111,14.6331501802662043],"luv":[52.4961529429458693,147.501711829562538,38.5124637576621893],"rgb":[0.933333333333333348,0.2,0.0666666666666666657],"xyz":[0.365440496615176247,0.205884936588296097,0.0258017274371444769],"hpluv":[14.6331501802662043,368.493287978140756,52.4961529429458693],"hsluv":[14.6331501802662043,96.9493433827183395,52.4961529429458693]},"#ee3322":{"lch":[52.5792408568970302,150.061966488521733,13.7129404445972121],"luv":[52.5792408568970302,145.784540850223095,35.5733247742160685],"rgb":[0.933333333333333348,0.2,0.133333333333333331],"xyz":[0.367315854753653304,0.206635079843686925,0.0356786136331236695],"hpluv":[13.7129404445972121,362.155969729293304,52.5792408568970302],"hsluv":[13.7129404445972121,91.3983957113456,52.5792408568970302]},"#ee3333":{"lch":[52.7156069212027916,146.335083442311,12.177050630061796],"luv":[52.7156069212027916,143.042611430097821,30.8669396171076365],"rgb":[0.933333333333333348,0.2,0.2],"xyz":[0.370403605486111,0.207870180136670024,0.0519407674907348127],"hpluv":[12.177050630061796,352.248031751653059,52.7156069212027916],"hsluv":[12.177050630061796,82.5426319963487316,52.7156069212027916]},"#ee3344":{"lch":[52.9115382124740705,141.372894204534333,9.91688783885485314],"luv":[52.9115382124740705,139.260586308771792,24.3471623953096028],"rgb":[0.933333333333333348,0.2,0.266666666666666663],"xyz":[0.374861600730329925,0.209653378234357618,0.0754195424436214512],"hpluv":[9.91688783885485314,339.043238938553714,52.9115382124740705],"hsluv":[9.91688783885485314,83.0163224279527867,52.9115382124740705]},"#ee3355":{"lch":[53.1718605143623222,135.462446214194244,6.82400118051175664],"luv":[53.1718605143623222,134.502806079917804,16.0956357737583851],"rgb":[0.933333333333333348,0.2,0.333333333333333315],"xyz":[0.38082401476969191,0.212038343850102451,0.106821589717595367],"hpluv":[6.82400118051175664,323.278173463789,53.1718605143623222],"hsluv":[6.82400118051175664,83.6110915378108,53.1718605143623222]},"#ee3366":{"lch":[53.5002196972096158,129.050901787066266,2.79642975700151464],"luv":[53.5002196972096158,128.89722530875369,6.29607494868134765],"rgb":[0.933333333333333348,0.2,0.4],"xyz":[0.388409010543200095,0.215072342159505742,0.146769234124739328],"hpluv":[2.79642975700151464,306.086943567777439,53.5002196972096158],"hsluv":[2.79642975700151464,84.310080928774866,53.5002196972096158]},"#ee3377":{"lch":[53.8992319384372252,122.709031404530748,357.759441587930837],"luv":[53.8992319384372252,122.615219389586755,-4.79732866099687261],"rgb":[0.933333333333333348,0.2,0.466666666666666674],"xyz":[0.397723023563402,0.218797947367586565,0.195823036031137238],"hpluv":[357.759441587930837,288.89051161323988,53.8992319384372252],"hsluv":[357.759441587930837,85.0909052409050872,53.8992319384372252]},"#ee3388":{"lch":[54.3705825415329,117.074862235921088,351.703100554939169],"luv":[54.3705825415329,115.849509832295894,-16.8942131860788436],"rgb":[0.933333333333333348,0.2,0.533333333333333326],"xyz":[0.408863440665345801,0.223254114208364163,0.254495899434709405],"hpluv":[351.703100554939169,273.2366774905733,54.3705825415329],"hsluv":[351.703100554939169,85.9285066952877,54.3705825415329]},"#ee3399":{"lch":[54.9151057717267292,112.774715889061866,344.730687431692274],"luv":[54.9151057717267292,108.793611621096844,-29.6999430015709507],"rgb":[0.933333333333333348,0.2,0.6],"xyz":[0.4219204047744185,0.22847689985199332,0.323262577075827284],"hpluv":[344.730687431692274,260.590898768244074,54.9151057717267292],"hsluv":[344.730687431692274,86.7978129198036896,54.9151057717267292]},"#ee33aa":{"lch":[55.5328602544255,110.325325240584178,337.093995035693695],"luv":[55.5328602544255,101.625579555338021,-42.9408776049389047],"rgb":[0.933333333333333348,0.2,0.66666666666666663],"xyz":[0.436978097739671056,0.234499977038094415,0.402566426692825885],"hpluv":[337.093995035693695,252.095155701888757,55.5328602544255],"hsluv":[337.093995035693695,87.6758366884225779,55.5328602544255]},"#ee33bb":{"lch":[56.2232062298057826,110.03895679054483,329.178007243031175],"luv":[56.2232062298057826,94.4974163460242664,-56.3809392922602726],"rgb":[0.933333333333333348,0.2,0.733333333333333282],"xyz":[0.454115689847007731,0.241355013881029173,0.492824411791467631],"hpluv":[329.178007243031175,248.353441541401367,56.2232062298057826],"hsluv":[329.178007243031175,88.5430404155392665,56.2232062298057826]},"#ee33cc":{"lch":[56.9848866198670123,111.971807156825847,321.417898872172259],"luv":[56.9848866198670123,87.5300782585605788,-69.8295854062993726],"rgb":[0.933333333333333348,0.2,0.8],"xyz":[0.47340806529072943,0.249071964058517975,0.594430922461737787],"hpluv":[321.417898872172259,249.337916143678171,56.9848866198670123],"hsluv":[321.417898872172259,89.3839737673679764,56.9848866198670123]},"#ee33dd":{"lch":[57.8161114567543848,115.945977330727956,314.185904182223908],"luv":[57.8161114567543848,80.813036999634221,-83.1427850752753557],"rgb":[0.933333333333333348,0.2,0.866666666666666696],"xyz":[0.494926390841363029,0.257679294278771509,0.707760770361743785],"hpluv":[314.185904182223908,254.475591939392586,57.8161114567543848],"hsluv":[314.185904182223908,90.1873197259471482,57.8161114567543848]},"#ee33ee":{"lch":[58.7146439354817886,121.632779311923699,307.715012949243715],"luv":[58.7146439354817886,74.4069479563921448,-96.2192241652253415],"rgb":[0.933333333333333348,0.2,0.933333333333333348],"xyz":[0.518738571107537738,0.267204166385241526,0.833171586430267],"hpluv":[307.715012949243715,262.87151341613469,58.7146439354817886],"hsluv":[307.715012949243715,90.9455375510239747,58.7146439354817886]},"#ee33ff":{"lch":[59.6778857977730581,128.651158016084139,302.091050100274117],"luv":[59.6778857977730581,68.3480180420837229,-108.993893813362092],"rgb":[0.933333333333333348,0.2,1],"xyz":[0.544909619517369159,0.27767258574917425,0.971005774722048853],"hpluv":[302.091050100274117,273.551812848380507,59.6778857977730581],"hsluv":[302.091050100274117,99.9999999999986784,59.6778857977730581]},"#ee4400":{"lch":[54.3591594970822598,145.188828472067655,17.4116852889838647],"luv":[54.3591594970822598,138.536177662057611,43.4456372018905412],"rgb":[0.933333333333333348,0.266666666666666663,0],"xyz":[0.373261401598505183,0.223145411354373546,0.023417812633377437],"hpluv":[17.4116852889838647,338.922026437804789,54.3591594970822598],"hsluv":[17.4116852889838647,100.000000000002217,54.3591594970822598]},"#ee4411":{"lch":[54.4016650840252112,143.940172045268554,16.9187727396215735],"luv":[54.4016650840252112,137.710194578710485,41.8888462184768713],"rgb":[0.933333333333333348,0.266666666666666663,0.0666666666666666657],"xyz":[0.374273067098142287,0.223550077554228399,0.0287459175981330667],"hpluv":[16.9187727396215735,335.744689025208913,54.4016650840252112],"hsluv":[16.9187727396215735,97.1754310281257574,54.4016650840252112]},"#ee4422":{"lch":[54.4803236312215944,141.690847159829417,15.9963876830432117],"luv":[54.4803236312215944,136.204446081142294,39.0467032744039173],"rgb":[0.933333333333333348,0.266666666666666663,0.133333333333333331],"xyz":[0.376148425236619344,0.224300220809619227,0.0386228037941122662],"hpluv":[15.9963876830432117,330.020900264600868,54.4803236312215944],"hsluv":[15.9963876830432117,92.0288025918740118,54.4803236312215944]},"#ee4433":{"lch":[54.6094526105793534,138.167821982121467,14.4538486850626899],"luv":[54.6094526105793534,133.794673004457735,34.4867004352900608],"rgb":[0.933333333333333348,0.266666666666666663,0.2],"xyz":[0.37923617596907705,0.225535321102602326,0.0548849576517234095],"hpluv":[14.4538486850626899,321.054243711338643,54.6094526105793534],"hsluv":[14.4538486850626899,83.7991355104008591,54.6094526105793534]},"#ee4444":{"lch":[54.7950558424119549,133.462657054844783,12.1770506300618084],"luv":[54.7950558424119549,130.459808710538397,28.151716454753565],"rgb":[0.933333333333333348,0.266666666666666663,0.266666666666666663],"xyz":[0.383694171213295965,0.22731851920028992,0.078363732604610048],"hpluv":[12.1770506300618084,309.070617226475065,54.7950558424119549],"hsluv":[12.1770506300618084,79.6495466444067546,54.7950558424119549]},"#ee4455":{"lch":[55.04178262974213,127.837203216659944,9.0482956458548145],"luv":[55.04178262974213,126.246413461005446,20.1045670057943724],"rgb":[0.933333333333333348,0.266666666666666663,0.333333333333333315],"xyz":[0.38965658525265795,0.229703484816034753,0.109765779878583963],"hpluv":[9.0482956458548145,294.716259365516066,55.04178262974213],"hsluv":[9.0482956458548145,80.0457187830871106,55.04178262974213]},"#ee4466":{"lch":[55.3531965298607105,121.710491561886229,4.95183922571805102],"luv":[55.3531965298607105,121.256220070521849,10.5058483924504795],"rgb":[0.933333333333333348,0.266666666666666663,0.4],"xyz":[0.397241581026166135,0.232737483125438044,0.149713424285727925],"hpluv":[4.95183922571805102,279.013127650855779,55.3531965298607105],"hsluv":[4.95183922571805102,80.511500113091131,55.3531965298607105]},"#ee4477":{"lch":[55.7319177265462855,115.631099927359429,359.795147057523252],"luv":[55.7319177265462855,115.63036086114974,-0.41342173536332294],"rgb":[0.933333333333333348,0.266666666666666663,0.466666666666666674],"xyz":[0.406555594046368041,0.236463088333518867,0.198767226192125834],"hpluv":[359.795147057523252,263.275227085929203,55.7319177265462855],"hsluv":[359.795147057523252,81.0316034214938412,55.7319177265462855]},"#ee4488":{"lch":[56.1797144871475069,110.229334232011354,353.550243523911263],"luv":[56.1797144871475069,109.531664472369371,-12.3822697089333467],"rgb":[0.933333333333333348,0.266666666666666663,0.533333333333333326],"xyz":[0.417696011148311841,0.240919255174296465,0.257440089595698],"hpluv":[353.550243523911263,248.975712077739217,56.1797144871475069],"hsluv":[353.550243523911263,81.5885451367485217,56.1797144871475069]},"#ee4499":{"lch":[56.6975745677487,106.142668326601637,346.310852745323245],"luv":[56.6975745677487,103.127575819694869,-25.1190992084518889],"rgb":[0.933333333333333348,0.266666666666666663,0.6],"xyz":[0.430752975257384541,0.246142040817925623,0.326206767236815909],"hpluv":[346.310852745323245,237.55536706581762,56.6975745677487],"hsluv":[346.310852745323245,82.1643886194057,56.6975745677487]},"#ee44aa":{"lch":[57.2857706939250164,103.913945498461985,338.339047623856459],"luv":[57.2857706939250164,96.5759938315173798,-38.356035828954532],"rgb":[0.933333333333333348,0.266666666666666663,0.66666666666666663],"xyz":[0.445810668222637096,0.252165118004026745,0.405510616853814509],"hpluv":[338.339047623856459,230.179372132151769,57.2857706939250164],"hsluv":[338.339047623856459,82.742186475039972,57.2857706939250164]},"#ee44bb":{"lch":[57.9439265752057224,103.883653730246948,330.054621671216069],"luv":[57.9439265752057224,90.015359877018625,-51.8560362788815183],"rgb":[0.933333333333333348,0.266666666666666663,0.733333333333333282],"xyz":[0.462948260329973771,0.259020154846961503,0.495768601952456256],"hpluv":[330.054621671216069,227.498543532010189,57.9439265752057224],"hsluv":[330.054621671216069,83.3069826860278,57.9439265752057224]},"#ee44cc":{"lch":[58.6710858878032866,106.123661593235155,321.940977409416575],"luv":[58.6710858878032866,83.5592367986741493,-65.4223623509468837],"rgb":[0.933333333333333348,0.266666666666666663,0.8],"xyz":[0.48224063577369547,0.266737105024450305,0.597375112622726356],"hpluv":[321.940977409416575,229.523641905846944,58.6710858878032866],"hsluv":[321.940977409416575,83.8463488142865288,58.6710858878032866]},"#ee44dd":{"lch":[59.4657843936948041,110.45324904546132,314.41066654104867],"luv":[59.4657843936948041,77.2947793232282407,-78.9014405069524116],"rgb":[0.933333333333333348,0.266666666666666663,0.866666666666666696],"xyz":[0.503758961324329069,0.275344435244703811,0.710704960522732354],"hpluv":[314.41066654104867,235.695163072106425,59.4657843936948041],"hsluv":[314.41066654104867,84.3505154479208699,59.4657843936948041]},"#ee44ee":{"lch":[60.3261240941145189,116.527805305600168,307.715012949243771],"luv":[60.3261240941145189,71.28406005268711,-92.180866733529669],"rgb":[0.933333333333333348,0.266666666666666663,0.933333333333333348],"xyz":[0.527571141590503778,0.284869307351173828,0.836115776591255577],"hpluv":[307.715012949243771,245.111377339321677,60.3261240941145189],"hsluv":[307.715012949243771,84.8122051950840898,60.3261240941145189]},"#ee44ff":{"lch":[61.2498476847862321,123.946828366557639,301.937515996566106],"luv":[61.2498476847862321,65.5671420475233617,-105.184438705774298],"rgb":[0.933333333333333348,0.266666666666666663,1],"xyz":[0.553742190000335199,0.295337726715106552,0.973949964883037422],"hpluv":[301.937515996566106,256.785047727470896,61.2498476847862321],"hsluv":[301.937515996566106,99.9999999999986073,61.2498476847862321]},"#ee5500":{"lch":[56.7595334156469136,135.29504726150742,20.5772435658132551],"luv":[56.7595334156469136,126.663115619922038,47.5521288161507414],"rgb":[0.933333333333333348,0.333333333333333315,0],"xyz":[0.385074658312851148,0.24677192478306581,0.0273555648714926478],"hpluv":[20.5772435658132551,302.470071141489655,56.7595334156469136],"hsluv":[20.5772435658132551,100.000000000002331,56.7595334156469136]},"#ee5511":{"lch":[56.7992830001534799,134.121232245619609,20.0864579205919],"luv":[56.7992830001534799,125.963368804879124,46.0622910677426],"rgb":[0.933333333333333348,0.333333333333333315,0.0666666666666666657],"xyz":[0.386086323812488252,0.247176590982920663,0.0326836698362482775],"hpluv":[20.0864579205919,299.636011805134103,56.7992830001534799],"hsluv":[20.0864579205919,97.4301566790671245,56.7992830001534799]},"#ee5522":{"lch":[56.8728535321199331,132.003018379302972,19.1666168944474329],"luv":[56.8728535321199331,124.685803630375247,43.3387498007741385],"rgb":[0.933333333333333348,0.333333333333333315,0.133333333333333331],"xyz":[0.387961681950965309,0.247926734238311491,0.042560556032227477],"hpluv":[19.1666168944474329,294.522290528054612,56.8728535321199331],"hsluv":[19.1666168944474329,92.7404035063857748,56.8728535321199331]},"#ee5533":{"lch":[56.9936637318031813,128.675597773649343,17.6241311186411558],"luv":[56.9936637318031813,122.635981466153211,38.9592801812266671],"rgb":[0.933333333333333348,0.333333333333333315,0.2],"xyz":[0.391049432683423,0.24916183453129459,0.0588227098898386203],"hpluv":[17.6241311186411558,286.489655583397507,56.9936637318031813],"hsluv":[17.6241311186411558,85.2217597168545353,56.9936637318031813]},"#ee5544":{"lch":[57.1673833238913431,124.212647444540593,15.3377586553938237],"luv":[57.1673833238913431,119.788604494467549,32.8553194848226298],"rgb":[0.933333333333333348,0.333333333333333315,0.266666666666666663],"xyz":[0.39550742792764193,0.250945032628982156,0.0823014848427252588],"hpluv":[15.3377586553938237,275.712737821839653,57.1673833238913431],"hsluv":[15.3377586553938237,78.8138286806830308,57.1673833238913431]},"#ee5555":{"lch":[57.3984455800741813,118.847398490007407,12.1770506300618084],"luv":[57.3984455800741813,116.173386735287238,25.068871978930396],"rgb":[0.933333333333333348,0.333333333333333315,0.333333333333333315],"xyz":[0.401469841967003915,0.253329998244727,0.113703532116699174],"hpluv":[12.1770506300618084,262.741620924066638,57.3984455800741813],"hsluv":[12.1770506300618084,79.1862648733910817,57.3984455800741813]},"#ee5566":{"lch":[57.6903015433249777,112.967028718059339,8.00617638558467881],"luv":[57.6903015433249777,111.865945908872092,15.7340307391382019],"rgb":[0.933333333333333348,0.333333333333333315,0.4],"xyz":[0.409054837740512101,0.256363996554130336,0.153651176523843136],"hpluv":[8.00617638558467881,248.478160638648092,57.6903015433249777],"hsluv":[8.00617638558467881,79.626682914648967,57.6903015433249777]},"#ee5577":{"lch":[58.0455538260385,107.095574323644513,2.70497781295448236],"luv":[58.0455538260385,106.976246145051803,5.05418642558356],"rgb":[0.933333333333333348,0.333333333333333315,0.466666666666666674],"xyz":[0.418368850760714,0.260089601762211131,0.202704978430241045],"hpluv":[2.70497781295448236,234.121819944652458,58.0455538260385],"hsluv":[2.70497781295448236,80.121743738144815,58.0455538260385]},"#ee5588":{"lch":[58.4660405277881523,101.857434077675435,356.214905006905212],"luv":[58.4660405277881523,101.63524992227579,-6.72405012805051783],"rgb":[0.933333333333333348,0.333333333333333315,0.533333333333333326],"xyz":[0.429509267862657806,0.264545768602988729,0.261377841833813185],"hpluv":[356.214905006905212,221.069268787819283,58.4660405277881523],"hsluv":[356.214905006905212,80.6557447307456385,58.4660405277881523]},"#ee5599":{"lch":[58.9528982622070714,97.9100075268454475,348.609359498013816],"luv":[58.9528982622070714,95.9815181358717808,-19.3369529719717974],"rgb":[0.933333333333333348,0.333333333333333315,0.6],"xyz":[0.442566231971730506,0.269768554246617887,0.33014451947493112],"hpluv":[348.609359498013816,210.746926462762332,58.9528982622070714],"hsluv":[348.609359498013816,81.2121283261168685,58.9528982622070714]},"#ee55aa":{"lch":[59.5066178042993812,95.8379147411847327,340.160257686713578],"luv":[59.5066178042993812,90.1495111294489817,-32.5264745255299346],"rgb":[0.933333333333333348,0.333333333333333315,0.66666666666666663],"xyz":[0.457623924936983062,0.275791631432719,0.40944836909192972],"hpluv":[340.160257686713578,204.36730380296666,59.5066178042993812],"hsluv":[340.160257686713578,81.7747968800248515,59.5066178042993812]},"#ee55bb":{"lch":[60.1270988419473156,96.02691270708236,331.338718337462637],"luv":[60.1270988419473156,84.2607812264238305,-46.0574501157669],"rgb":[0.933333333333333348,0.333333333333333315,0.733333333333333282],"xyz":[0.474761517044319736,0.282646668275653767,0.499706354190571467],"hpluv":[331.338718337462637,202.657202566236862,60.1270988419473156],"hsluv":[331.338718337462637,82.3290961128101202,60.1270988419473156]},"#ee55cc":{"lch":[60.8137066481247359,98.5745380895967855,322.704854800823],"luv":[60.8137066481247359,78.4184921400445774,-59.728382282288],"rgb":[0.933333333333333348,0.333333333333333315,0.8],"xyz":[0.494053892488041435,0.290363618453142569,0.601312864860841567],"hpluv":[322.704854800823,205.68499115665557,60.8137066481247359],"hsluv":[322.704854800823,82.862416210457269,60.8137066481247359]},"#ee55dd":{"lch":[61.5653314057239669,103.296154527471415,314.73674606959],"luv":[61.5653314057239669,72.7050419742243577,-73.3762387404091925],"rgb":[0.933333333333333348,0.333333333333333315,0.866666666666666696],"xyz":[0.515572218038675,0.298970948673396075,0.714642712760847565],"hpluv":[314.73674606959,212.905685416828703,61.5653314057239669],"hsluv":[314.73674606959,83.3644351442966496,61.5653314057239669]},"#ee55ee":{"lch":[62.3804497031794796,109.822432229930158,307.715012949243942],"luv":[62.3804497031794796,67.1821530808003473,-86.8764923804219364],"rgb":[0.933333333333333348,0.333333333333333315,0.933333333333333348],"xyz":[0.539384398304849744,0.308495820779866092,0.840053528829370788],"hpluv":[307.715012949243942,223.399338603574023,62.3804497031794796],"hsluv":[307.715012949243942,83.8270760150894318,62.3804497031794796]},"#ee55ff":{"lch":[63.2571870514493355,117.722992850638121,301.718618818209791],"luv":[63.2571870514493355,61.8926401270512301,-100.139922827085911],"rgb":[0.933333333333333348,0.333333333333333315,1],"xyz":[0.565555446714681165,0.318964240143798816,0.977887717121152633],"hpluv":[301.718618818209791,236.15152010236153,63.2571870514493355],"hsluv":[301.718618818209791,99.9999999999986358,63.2571870514493355]},"#ee6600":{"lch":[59.6010827175637274,124.896403377083828,24.7633991985742],"luv":[59.6010827175637274,113.411584725929117,52.315619335764687],"rgb":[0.933333333333333348,0.4,0],"xyz":[0.400102716018697624,0.276828040194759151,0.032364917440108],"hpluv":[24.7633991985742,265.910269095548301,59.6010827175637274],"hsluv":[24.7633991985742,100.000000000002458,59.6010827175637274]},"#ee6611":{"lch":[59.6379025762155521,123.785593795891074,24.2806773941880323],"luv":[59.6379025762155521,112.835768380983566,50.9014990474188],"rgb":[0.933333333333333348,0.4,0.0666666666666666657],"xyz":[0.401114381518334728,0.277232706394614,0.0376930224048636284],"hpluv":[24.2806773941880323,263.38259338209491,59.6379025762155521],"hsluv":[24.2806773941880323,97.6946368166697425,59.6379025762155521]},"#ee6622":{"lch":[59.7060621192549235,121.776511986799889,23.3741045361832462],"luv":[59.7060621192549235,111.782804083827386,48.312768320888857],"rgb":[0.933333333333333348,0.4,0.133333333333333331],"xyz":[0.402989739656811785,0.277982849650004804,0.0475699086008428279],"hpluv":[23.3741045361832462,258.812011755725052,59.7060621192549235],"hsluv":[23.3741045361832462,93.4807634058905847,59.7060621192549235]},"#ee6633":{"lch":[59.818019190990654,118.60827278102343,21.848413141726418],"luv":[59.818019190990654,110.088842330137865,44.1403349161252621],"rgb":[0.933333333333333348,0.4,0.2],"xyz":[0.406077490389269491,0.279217949942987931,0.0638320624584539642],"hpluv":[21.848413141726418,251.606745577849637,59.818019190990654],"hsluv":[21.848413141726418,86.7067277856042722,59.818019190990654]},"#ee6644":{"lch":[59.9790782653121,114.334087778781978,19.5741908506499591],"luv":[59.9790782653121,107.726545105393242,38.3050271878496318],"rgb":[0.933333333333333348,0.4,0.266666666666666663],"xyz":[0.410535485633488406,0.281001148040675497,0.0873108374113406],"hpluv":[19.5741908506499591,241.888527179759762,59.9790782653121],"hsluv":[19.5741908506499591,77.7164494297015551,59.9790782653121]},"#ee6655":{"lch":[60.1934276072459227,109.155383321287928,16.4048569251700904],"luv":[60.1934276072459227,104.711671726378157,30.827966398783424],"rgb":[0.933333333333333348,0.4,0.333333333333333315],"xyz":[0.416497899672850391,0.283386113656420358,0.118712884685314518],"hpluv":[16.4048569251700904,230.109957101660228,60.1934276072459227],"hsluv":[16.4048569251700904,78.0627041699660822,60.1934276072459227]},"#ee6666":{"lch":[60.4643778553048179,103.423697151150392,12.1770506300619203],"luv":[60.4643778553048179,101.096711576265875,21.8155000143976636],"rgb":[0.933333333333333348,0.4,0.4],"xyz":[0.424082895446358576,0.286420111965823676,0.15866052909245848],"hpluv":[12.1770506300619203,217.050003231938149,60.4643778553048179],"hsluv":[12.1770506300619203,78.4746058088251601,60.4643778553048179]},"#ee6677":{"lch":[60.7944870758990845,97.6355083856116,6.72933164538236728],"luv":[60.7944870758990845,96.9628770314166388,11.4408468002682859],"rgb":[0.933333333333333348,0.4,0.466666666666666674],"xyz":[0.433396908466560482,0.290145717173904472,0.207714330998856389],"hpluv":[6.72933164538236728,203.79002370037793,60.7944870758990845],"hsluv":[6.72933164538236728,78.9407958828298177,60.7944870758990845]},"#ee6688":{"lch":[61.1856375663111294,92.4106294683140419,359.951978350089689],"luv":[61.1856375663111294,92.4105970103857,-0.0774526573243247418],"rgb":[0.933333333333333348,0.4,0.533333333333333326],"xyz":[0.444537325568504282,0.294601884014682069,0.266387194402428529],"hpluv":[359.951978350089689,191.6512981090967,61.1856375663111294],"hsluv":[359.951978350089689,79.4474583444281706,61.1856375663111294]},"#ee6699":{"lch":[61.6390913266860281,88.437141466109523,351.875732288608958],"luv":[61.6390913266860281,87.5495765855601746,-12.4979850530316181],"rgb":[0.933333333333333348,0.4,0.6],"xyz":[0.457594289677577,0.299824669658311227,0.335153872043546464],"hpluv":[351.875732288608958,182.061365307506776,61.6390913266860281],"hsluv":[351.875732288608958,79.9795786975914353,61.6390913266860281]},"#ee66aa":{"lch":[62.1555369290736337,86.3639450462453624,342.77320214224],"luv":[62.1555369290736337,82.489654306882187,-25.5770978862333536],"rgb":[0.933333333333333348,0.4,0.66666666666666663],"xyz":[0.472651982642829538,0.305847746844412349,0.414457721660545064],"hpluv":[342.77320214224,176.316102232879075,62.1555369290736337],"hsluv":[342.77320214224,80.5221008496243,62.1555369290736337]},"#ee66bb":{"lch":[62.735134131647655,86.6495273870251168,333.187217955259598],"luv":[62.735134131647655,77.3334216921159765,-39.0855790002422907],"rgb":[0.933333333333333348,0.4,0.733333333333333282],"xyz":[0.489789574750166212,0.312702783687347108,0.504715706759186755],"hpluv":[333.187217955259598,175.264796900814019,62.735134131647655],"hsluv":[333.187217955259598,81.0608551813628679,62.735134131647655]},"#ee66cc":{"lch":[63.3775592853136516,89.4351699392481549,323.800511847500275],"luv":[63.3775592853136516,72.1711045195964545,-52.8202735176909144],"rgb":[0.933333333333333348,0.4,0.8],"xyz":[0.509081950193887911,0.320419733864835909,0.606322217429456911],"hpluv":[323.800511847500275,179.065596131410075,63.3775592853136516],"hsluv":[323.800511847500275,81.5831918811758072,63.3775592853136516]},"#ee66dd":{"lch":[64.0820526997291751,94.5324861988586918,315.200217206616742],"luv":[64.0820526997291751,67.0777383779463179,-66.6105694393704795],"rgb":[0.933333333333333348,0.4,0.866666666666666696],"xyz":[0.53060027574452151,0.329027064085089416,0.719652065329462909],"hpluv":[315.200217206616742,187.190580763285084,64.0820526997291751],"hsluv":[315.200217206616742,82.0783141076240241,64.0820526997291751]},"#ee66ee":{"lch":[64.8474680131467,101.534802649490857,307.715012949244169],"luv":[64.8474680131467,62.1123254704966499,-80.32045302236628],"rgb":[0.933333333333333348,0.4,0.933333333333333348],"xyz":[0.55441245601069622,0.338551936191559433,0.845062881397986132],"hpluv":[307.715012949244169,198.683239249207219,64.8474680131467],"hsluv":[307.715012949244169,82.5373501246235,64.8474680131467]},"#ee66ff":{"lch":[65.6723229483953759,109.966867844968618,301.415067453827589],"luv":[65.6723229483953759,57.3184789471434897,-93.8472375449520797],"rgb":[0.933333333333333348,0.4,1],"xyz":[0.580583504420527641,0.349020355555492157,0.982897069689768088],"hpluv":[301.415067453827589,212.480364902930489,65.6723229483953759],"hsluv":[301.415067453827589,99.9999999999984794,65.6723229483953759]},"#ee7700":{"lch":[62.8217158048736763,114.851740825540901,30.0981414692213356],"luv":[62.8217158048736763,99.3660150566978,57.5961580525066097],"rgb":[0.933333333333333348,0.466666666666666674,0],"xyz":[0.418556454072115225,0.313735516301594908,0.0385161634579137],"hpluv":[30.0981414692213356,231.988851559171735,62.8217158048736763],"hsluv":[30.0981414692213356,100.000000000002203,62.8217158048736763]},"#ee7711":{"lch":[62.8555901763931075,113.786950077776382,29.6341042910547],"luv":[62.8555901763931075,98.9037041004629458,56.2630191441096059],"rgb":[0.933333333333333348,0.466666666666666674,0.0666666666666666657],"xyz":[0.419568119571752329,0.314140182501449761,0.0438442684226693288],"hpluv":[29.6341042910547,229.71421718860654,62.8555901763931075],"hsluv":[29.6341042910547,97.9532933827149463,62.8555901763931075]},"#ee7722":{"lch":[62.9183073649527955,111.855609649988921,28.7604537228304977],"luv":[62.9183073649527955,98.0569879769147406,53.8191835600085398],"rgb":[0.933333333333333348,0.466666666666666674,0.133333333333333331],"xyz":[0.421443477710229386,0.314890325756840561,0.0537211546186485284],"hpluv":[28.7604537228304977,225.59011469442973,62.9183073649527955],"hsluv":[28.7604537228304977,94.206312745702121,62.9183073649527955]},"#ee7733":{"lch":[63.0213536795682501,108.794922632811733,27.2836719807386174],"luv":[63.0213536795682501,96.6912591797812411,49.8711899688373208],"rgb":[0.933333333333333348,0.466666666666666674,0.2],"xyz":[0.424531228442687092,0.316125426049823688,0.0699833084762596647],"hpluv":[27.2836719807386174,219.058559128587405,63.0213536795682501],"hsluv":[27.2836719807386174,88.1668112455654,63.0213536795682501]},"#ee7744":{"lch":[63.1696562136619235,104.634411539935968,25.0668522383795889],"luv":[63.1696562136619235,94.7793214210065571,44.3310309972846497],"rgb":[0.933333333333333348,0.466666666666666674,0.266666666666666663],"xyz":[0.428989223686906,0.317908624147511254,0.0934620834291463],"hpluv":[25.0668522383795889,210.186756956079222,63.1696562136619235],"hsluv":[25.0668522383795889,79.7210233436604199,63.1696562136619235]},"#ee7755":{"lch":[63.3671413614491286,99.5393983003294096,21.9455950678528],"luv":[63.3671413614491286,92.3266881357997278,37.2004633286522051],"rgb":[0.933333333333333348,0.466666666666666674,0.333333333333333315],"xyz":[0.434951637726268,0.320293589763256115,0.124864130703120219],"hpluv":[21.9455950678528,199.328877993420377,63.3671413614491286],"hsluv":[21.9455950678528,76.6090179557391,63.3671413614491286]},"#ee7766":{"lch":[63.6169573916324822,93.8195852400781263,17.7221756586824775],"luv":[63.6169573916324822,89.367258373090749,28.5588463614608763],"rgb":[0.933333333333333348,0.466666666666666674,0.4],"xyz":[0.442536633499776177,0.323327588072659433,0.16481177511026418],"hpluv":[17.7221756586824775,187.13711975631665,63.6169573916324822],"hsluv":[17.7221756586824775,76.9903860669667,63.6169573916324822]},"#ee7777":{"lch":[63.9215909451051232,87.936547917610838,12.1770506300618937],"luv":[63.9215909451051232,85.9580160709841863,18.5487447771187846],"rgb":[0.933333333333333348,0.466666666666666674,0.466666666666666674],"xyz":[0.451850646519978083,0.327053193280740229,0.21386557701666209],"hpluv":[12.1770506300618937,174.56660414904394,63.9215909451051232],"hsluv":[12.1770506300618937,77.4248320836617268,63.9215909451051232]},"#ee7788":{"lch":[64.2829374304473,82.5014209284754543,5.11726519711922112],"luv":[64.2829374304473,82.1725895095950847,7.35866757674724514],"rgb":[0.933333333333333348,0.466666666666666674,0.533333333333333326],"xyz":[0.462991063621921883,0.331509360121517827,0.272538440420234229],"hpluv":[5.11726519711922112,162.85647909200884,64.2829374304473],"hsluv":[5.11726519711922112,77.9003779592442669,64.2829374304473]},"#ee7799":{"lch":[64.7023501026032477,78.2413537817778177,356.48599323172067],"luv":[64.7023501026032477,78.0942478632934893,-4.79561177242263259],"rgb":[0.933333333333333348,0.466666666666666674,0.6],"xyz":[0.476048027730994638,0.336732145765147,0.341305118061352164],"hpluv":[356.48599323172067,153.446019679421255,64.7023501026032477],"hsluv":[356.48599323172067,78.4035888028378167,64.7023501026032477]},"#ee77aa":{"lch":[65.1806796634753596,75.8992199041959,346.522692233148291],"luv":[65.1806796634753596,73.8091300431969302,-17.6890899803221124],"rgb":[0.933333333333333348,0.466666666666666674,0.66666666666666663],"xyz":[0.491105720696247139,0.342755222951248106,0.420608967678350765],"hpluv":[346.522692233148291,147.76029654451807,65.1806796634753596],"hsluv":[346.522692233148291,78.9205551316135256,65.1806796634753596]},"#ee77bb":{"lch":[65.7183104585581646,76.0514193956389875,335.85959217762661],"luv":[65.7183104585581646,69.4004166910179094,-31.1030634376174824],"rgb":[0.933333333333333348,0.466666666666666674,0.733333333333333282],"xyz":[0.508243312803583813,0.349610259794182865,0.510866952776992456],"hpluv":[335.85959217762661,146.845370988132231,65.7183104585581646],"hsluv":[335.85959217762661,79.4377328719524627,65.7183104585581646]},"#ee77cc":{"lch":[66.3151963922866,78.9180750862202416,325.378996221060731],"luv":[66.3151963922866,64.9439055461397459,-44.8369457894756067],"rgb":[0.933333333333333348,0.466666666666666674,0.8],"xyz":[0.527535688247305568,0.357327209971671667,0.612473463447262612],"hpluv":[325.378996221060731,151.008971652655617,66.3151963922866],"hsluv":[325.378996221060731,79.9425706591808307,66.3151963922866]},"#ee77dd":{"lch":[66.9708980107196652,84.3115421117289543,315.859798591258766],"luv":[66.9708980107196652,60.5051526262217152,-58.7159487612646842],"rgb":[0.933333333333333348,0.466666666666666674,0.866666666666666696],"xyz":[0.549054013797939056,0.365934540191925173,0.725803311347268609],"hpluv":[315.859798591258766,159.749768323538632,66.9708980107196652],"hsluv":[315.859798591258766,80.4238985377879345,66.9708980107196652]},"#ee77ee":{"lch":[67.6846211881785251,91.7687338274624409,307.715012949244453],"luv":[67.6846211881785251,56.1380858067324056,-72.5948746830766112],"rgb":[0.933333333333333348,0.466666666666666674,0.933333333333333348],"xyz":[0.572866194064113765,0.37545941229839519,0.851214127415791832],"hpluv":[307.715012949244453,172.045795420537047,67.6846211881785251],"hsluv":[307.715012949244453,80.8720902094370757,67.6846211881785251]},"#ee77ff":{"lch":[68.4552572311626761,100.746525491660947,300.997699928034137],"luv":[68.4552572311626761,51.8848298156272918,-86.3587102361150585],"rgb":[0.933333333333333348,0.466666666666666674,1],"xyz":[0.599037242473945186,0.385927831662327914,0.989048315707573789],"hpluv":[300.997699928034137,186.750854251257437,68.4552572311626761],"hsluv":[300.997699928034137,99.9999999999982,68.4552572311626761]},"#ee8800":{"lch":[66.3576417146455,105.981377873447272,36.6492300119340797],"luv":[66.3576417146455,85.0293774107247771,63.2618165491550428],"rgb":[0.933333333333333348,0.533333333333333326,0],"xyz":[0.440628823797085678,0.35788025575153648,0.045873620032903642],"hpluv":[36.6492300119340797,202.664622836431278,66.3576417146455],"hsluv":[36.6492300119340797,100.000000000002288,66.3576417146455]},"#ee8811":{"lch":[66.3886714607036907,104.946342152180421,36.2201184819273792],"luv":[66.3886714607036907,84.6657638625926552,62.0116373004794923],"rgb":[0.933333333333333348,0.533333333333333326,0.0666666666666666657],"xyz":[0.441640489296722782,0.358284921951391333,0.0512017249976592717],"hpluv":[36.2201184819273792,200.591559481147556,66.3886714607036907],"hsluv":[36.2201184819273792,98.1954604930108701,66.3886714607036907]},"#ee8822":{"lch":[66.4461305943750773,103.062674429887437,35.4099902294173745],"luv":[66.4461305943750773,83.9988395226221201,59.7169140151578],"rgb":[0.933333333333333348,0.533333333333333326,0.133333333333333331],"xyz":[0.443515847435199839,0.359035065206782134,0.0610786111936384712],"hpluv":[35.4099902294173745,196.820821024497235,66.4461305943750773],"hsluv":[35.4099902294173745,94.8869488142181581,66.4461305943750773]},"#ee8833":{"lch":[66.5405621290638578,100.059958052790449,34.0337853874148877],"luv":[66.5405621290638578,82.920456825626232,56.0017235927220369],"rgb":[0.933333333333333348,0.533333333333333326,0.2],"xyz":[0.446603598167657545,0.36027016549976526,0.0773407650512496214],"hpluv":[34.0337853874148877,190.815292572549708,66.5405621290638578],"hsluv":[34.0337853874148877,89.5408718212573689,66.5405621290638578]},"#ee8844":{"lch":[66.6765193585480347,95.9403990837829639,31.9512880443390657],"luv":[66.6765193585480347,81.4052672986078107,50.7714745934935223],"rgb":[0.933333333333333348,0.533333333333333326,0.266666666666666663],"xyz":[0.45106159341187646,0.362053363597452826,0.10081954000413626],"hpluv":[31.9512880443390657,182.586190035925256,66.6765193585480347],"hsluv":[31.9512880443390657,82.0371002457568608,66.6765193585480347]},"#ee8855":{"lch":[66.8576614114874559,90.8274017958005,28.983496619036984],"luv":[66.8576614114874559,79.4521157428502391,44.0111147434430805],"rgb":[0.933333333333333348,0.533333333333333326,0.333333333333333315],"xyz":[0.457024007451238445,0.364438329213197687,0.132221587278110175],"hpluv":[28.983496619036984,172.387208051834335,66.8576614114874559],"hsluv":[28.983496619036984,74.7174368883009663,66.8576614114874559]},"#ee8866":{"lch":[67.0869600103699213,84.978663004295683,24.8971939400565283],"luv":[67.0869600103699213,77.081139807049837,35.7752854921339249],"rgb":[0.933333333333333348,0.533333333333333326,0.4],"xyz":[0.464609003224746631,0.367472327522601,0.172169231685254109],"hpluv":[24.8971939400565283,160.735241770302764,67.0869600103699213],"hsluv":[24.8971939400565283,75.0669061044520447,67.0869600103699213]},"#ee8877":{"lch":[67.3668077908477727,78.8051510957513841,19.4009345351312952],"luv":[67.3668077908477727,74.3303771171996743,26.1772205713113095],"rgb":[0.933333333333333348,0.533333333333333326,0.466666666666666674],"xyz":[0.473923016244948536,0.371197932730681801,0.221223033591652019],"hpluv":[19.4009345351312952,148.438980876884,67.3668077908477727],"hsluv":[19.4009345351312952,75.4672397967126329,67.3668077908477727]},"#ee8888":{"lch":[67.6990830402889117,72.8916076032019191,12.177050630062066],"luv":[67.6990830402889117,71.2515799877231757,15.3752661190709663],"rgb":[0.933333333333333348,0.533333333333333326,0.533333333333333326],"xyz":[0.485063433346892336,0.375654099571459399,0.279895896995224214],"hpluv":[12.177050630062066,136.626224949154164,67.6990830402889117],"hsluv":[12.177050630062066,75.9081099773692927,67.6990830402889117]},"#ee8899":{"lch":[68.0851935471165319,67.9986383575237312,2.99916583787236446],"luv":[68.0851935471165319,67.9055003914663615,3.55778513430191889],"rgb":[0.933333333333333348,0.533333333333333326,0.6],"xyz":[0.498120397455965036,0.380876885215088556,0.348662574636342093],"hpluv":[2.99916583787236446,126.732168582193651,68.0851935471165319],"hsluv":[2.99916583787236446,76.3775584855855385,68.0851935471165319]},"#ee88aa":{"lch":[68.5261104708773274,64.9933943358063573,351.976176804910949],"luv":[68.5261104708773274,64.3571165389368,-9.07209226602857832],"rgb":[0.933333333333333348,0.533333333333333326,0.66666666666666663],"xyz":[0.513178090421217648,0.386899962401189679,0.427966424253340694],"hpluv":[351.976176804910949,120.351764916629207,68.5261104708773274],"hsluv":[351.976176804910949,76.8628030471707859,68.5261104708773274]},"#ee88bb":{"lch":[69.0223979406526098,64.6433047463018,339.810246341231903],"luv":[69.0223979406526098,60.6712812589502875,-22.3103670727442456],"rgb":[0.933333333333333348,0.533333333333333326,0.733333333333333282],"xyz":[0.530315682528554211,0.393754999244124437,0.51822440935198244],"hpluv":[339.810246341231903,118.842788638920595,69.0223979406526098],"hsluv":[339.810246341231903,77.3509666785266887,69.0223979406526098]},"#ee88cc":{"lch":[69.5742414545850778,67.3203562589766307,327.709288072293873],"luv":[69.5742414545850778,56.9091584539883399,-35.9635656031820687],"rgb":[0.933333333333333348,0.533333333333333326,0.8],"xyz":[0.549608057972276,0.401471949421613239,0.619830920022252596],"hpluv":[327.709288072293873,122.782720563937247,69.5742414545850778],"hsluv":[327.709288072293873,77.8296635442389686,69.5742414545850778]},"#ee88dd":{"lch":[70.1814766713242,72.856404222049747,316.817937357318669],"luv":[70.1814766713242,53.1256441200966663,-49.8570112721525547],"rgb":[0.933333333333333348,0.533333333333333326,0.866666666666666696],"xyz":[0.571126383522909564,0.410079279641866745,0.733160767922258594],"hpluv":[316.817937357318669,131.729959343498166,70.1814766713242],"hsluv":[316.817937357318669,78.2874043120714163,70.1814766713242]},"#ee88ee":{"lch":[70.8436192863675558,80.7013698438951224,307.715012949244851],"luv":[70.8436192863675558,49.367799206375345,-63.839889537812887],"rgb":[0.933333333333333348,0.533333333333333326,0.933333333333333348],"xyz":[0.594938563789084274,0.419604151748336762,0.858571583990781817],"hpluv":[307.715012949244851,144.550464850223619,70.8436192863675558],"hsluv":[307.715012949244851,78.7138135635212848,70.8436192863675558]},"#ee88ff":{"lch":[71.5598961203093182,90.2054153167292583,300.42003582834775],"luv":[71.5598961203093182,45.674190228859068,-77.7874366424399426],"rgb":[0.933333333333333348,0.533333333333333326,1],"xyz":[0.621109612198915695,0.430072571112269486,0.996405772282563662],"hpluv":[300.42003582834775,159.956626210428567,71.5598961203093182],"hsluv":[300.42003582834775,99.99999999999784,71.5598961203093182]},"#ee9900":{"lch":[70.1492527845175715,98.9919938823364731,44.3502140795235036],"luv":[70.1492527845175715,70.7872313214223112,69.1995862317686772],"rgb":[0.933333333333333348,0.6,0],"xyz":[0.466498424249553179,0.409619456656472147,0.0544968201837259],"hpluv":[44.3502140795235036,179.06732625175573,70.1492527845175715],"hsluv":[44.3502140795235036,100.000000000002217,70.1492527845175715]},"#ee9911":{"lch":[70.1776126165771785,97.9766822185852533,43.9766782844564119],"luv":[70.1776126165771785,70.5062245085518526,68.0316291449155273],"rgb":[0.933333333333333348,0.6,0.0666666666666666657],"xyz":[0.467510089749190283,0.410024122856327,0.0598249251484815267],"hpluv":[43.9766782844564119,177.15910010767405,70.1776126165771785],"hsluv":[43.9766782844564119,98.4152296143538337,70.1776126165771785]},"#ee9922":{"lch":[70.2301348691785,96.1222676294158447,43.2696050504519505],"luv":[70.2301348691785,69.9901292501651824,65.8852953379296622],"rgb":[0.933333333333333348,0.6,0.133333333333333331],"xyz":[0.46938544788766734,0.410774266111717801,0.0697018113444607262],"hpluv":[43.2696050504519505,173.676009463775955,70.2301348691785],"hsluv":[43.2696050504519505,95.5057584668238,70.2301348691785]},"#ee9933":{"lch":[70.3164728806357573,93.1473438817036339,42.0626701373461103],"luv":[70.3164728806357573,69.1537511222041417,62.4034163964169508],"rgb":[0.933333333333333348,0.6,0.2],"xyz":[0.472473198620125046,0.412009366404700927,0.0859639652020718625],"hpluv":[42.0626701373461103,168.094198140054317,70.3164728806357573],"hsluv":[42.0626701373461103,90.7937976503380213,70.3164728806357573]},"#ee9944":{"lch":[70.4408210614760719,89.024296887742608,40.221678197216157],"luv":[70.4408210614760719,67.9746586813449483,57.4871395488729533],"rgb":[0.933333333333333348,0.6,0.266666666666666663],"xyz":[0.476931193864343961,0.413792564502388494,0.109442740154958501],"hpluv":[40.221678197216157,160.370125602871781,70.4408210614760719],"hsluv":[40.221678197216157,84.1577311163605657,70.4408210614760719]},"#ee9955":{"lch":[70.6065752665828654,83.8291063606938138,37.5652459120346904],"luv":[70.6065752665828654,66.4479455240540631,51.1076276974862154],"rgb":[0.933333333333333348,0.6,0.333333333333333315],"xyz":[0.482893607903705946,0.416177530118133354,0.140844787428932416],"hpluv":[37.5652459120346904,150.656896294971034,70.6065752665828654],"hsluv":[37.5652459120346904,75.5772228053980797,70.6065752665828654]},"#ee9966":{"lch":[70.8165243284349373,77.7550236522342,33.8383101580563],"luv":[70.8165243284349373,64.5842808158336652,43.297971946282189],"rgb":[0.933333333333333348,0.6,0.4],"xyz":[0.490478603677214131,0.419211528427536673,0.180792431836076378],"hpluv":[33.8383101580563,139.326323276972687,70.8165243284349373],"hsluv":[33.8383101580563,72.525293376848623,70.8165243284349373]},"#ee9977":{"lch":[71.0729506656700778,71.1378337295946,28.6840524218341386],"luv":[71.0729506656700778,62.4077841236731601,34.1446901949989154],"rgb":[0.933333333333333348,0.6,0.466666666666666674],"xyz":[0.499792616697416037,0.422937133635617468,0.229846233742474287],"hpluv":[28.6840524218341386,127.009327518651787,71.0729506656700778],"hsluv":[28.6840524218341386,72.8885787597460677,71.0729506656700778]},"#ee9988":{"lch":[71.3776900371935312,64.4963091800695878,21.6331741754282376],"luv":[71.3776900371935312,59.9533946760230378,23.7773918811996943],"rgb":[0.933333333333333348,0.6,0.533333333333333326],"xyz":[0.510933033799359837,0.427393300476395066,0.288519097146046455],"hpluv":[21.6331741754282376,114.659937381049531,71.3776900371935312],"hsluv":[21.6331741754282376,73.2902809537896189,71.3776900371935312]},"#ee9999":{"lch":[71.732171153908709,58.5818834203282179,12.1770506300621602],"luv":[71.732171153908709,57.263818011494017,12.3568690136061505],"rgb":[0.933333333333333348,0.6,0.6],"xyz":[0.523989997908432592,0.432616086120024224,0.35728577478716439],"hpluv":[12.1770506300621602,103.630759412975706,71.732171153908709],"hsluv":[12.1770506300621602,73.7196701825771186,71.732171153908709]},"#ee99aa":{"lch":[72.1374451439022408,54.3863410009709725,0.0659165211073427237],"luv":[72.1374451439022408,54.3863050092105,0.0625693137293968082],"rgb":[0.933333333333333348,0.6,0.66666666666666663],"xyz":[0.539047690873685093,0.438639163306125346,0.436589624404162935],"hpluv":[0.0659165211073427237,95.6683780017161,72.1374451439022408],"hsluv":[0.0659165211073427237,74.1649147609968082,72.1374451439022408]},"#ee99bb":{"lch":[72.5942101669252366,52.9692652640659247,345.882936464906891],"luv":[72.5942101669252366,51.3695627363271683,-12.9194073739288822],"rgb":[0.933333333333333348,0.6,0.733333333333333282],"xyz":[0.556185282981021767,0.445494200149060104,0.526847609502804737],"hpluv":[345.882936464906891,92.5894045166522091,72.5942101669252366],"hsluv":[345.882936464906891,74.6136876066909878,72.5942101669252366]},"#ee99cc":{"lch":[73.1028341171650737,55.013164482536844,331.314039518972208],"luv":[73.1028341171650737,48.2610582375421515,-26.4067893575727517],"rgb":[0.933333333333333348,0.6,0.8],"xyz":[0.575477658424743521,0.453211150326548906,0.628454120173074893],"hpluv":[331.314039518972208,95.4930444317639342,73.1028341171650737],"hsluv":[331.314039518972208,75.0536815693659491,73.1028341171650737]},"#ee99dd":{"lch":[73.6633770412179274,60.4388711558699896,318.269971219550712],"luv":[73.6633770412179274,45.1048908137830935,-40.2294167403973049],"rgb":[0.933333333333333348,0.6,0.866666666666666696],"xyz":[0.596995983975377,0.461818480546802412,0.74178396807308089],"hpluv":[318.269971219550712,104.112780668711437,73.6633770412179274],"hsluv":[318.269971219550712,75.472992612571872,73.6633770412179274]},"#ee99ee":{"lch":[74.2756141069900337,68.5596754700476083,307.71501294924542],"luv":[74.2756141069900337,41.9403078139404499,-54.2350410807456953],"rgb":[0.933333333333333348,0.6,0.933333333333333348],"xyz":[0.620808164241551719,0.471343352653272429,0.867194784141604114],"hpluv":[307.71501294924542,117.128296895720368,74.2756141069900337],"hsluv":[307.71501294924542,75.8603506282489235,74.2756141069900337]},"#ee99ff":{"lch":[74.9390594560707,78.5455210447045857,299.603294913962486],"luv":[74.9390594560707,38.8008486558083519,-68.2927010724659],"rgb":[0.933333333333333348,0.6,1],"xyz":[0.64697921265138314,0.481811772017205153,1.00502897243338585],"hpluv":[299.603294913962486,133.000267199001968,74.9390594560707],"hsluv":[299.603294913962486,99.9999999999973284,74.9390594560707]},"#dd0000":{"lch":[46.1435564305616239,155.182233977468201,12.1770506300617765],"luv":[46.1435564305616239,151.69070515099267,32.7330981276182555],"rgb":[0.866666666666666696,0,0],"xyz":[0.298181282529475455,0.153749723804264049,0.0139772476185688679],"hpluv":[12.1770506300617765,426.746789183125316,46.1435564305616239],"hsluv":[12.1770506300617765,100.000000000002217,46.1435564305616239]},"#dd0011":{"lch":[46.1980288678146636,153.577384001942391,11.5987087531524224],"luv":[46.1980288678146636,150.441300178721519,30.8776306962803169],"rgb":[0.866666666666666696,0,0.0666666666666666657],"xyz":[0.29919294802911256,0.154154390004118902,0.0193053525833244977],"hpluv":[11.5987087531524224,421.835520233675084,46.1980288678146636],"hsluv":[11.5987087531524224,99.9999999999964473,46.1980288678146636]},"#dd0022":{"lch":[46.2987546285526292,150.712226421231577,10.5179424282654246],"luv":[46.2987546285526292,148.17992816752087,27.5115263319381462],"rgb":[0.866666666666666696,0,0.133333333333333331],"xyz":[0.301068306167589617,0.15490453325950973,0.0291822387793036972],"hpluv":[10.5179424282654246,413.065099977246746,46.2987546285526292],"hsluv":[10.5179424282654246,99.9999999999964615,46.2987546285526292]},"#dd0033":{"lch":[46.4638920568500637,146.293058552209629,8.71533624525386585],"luv":[46.4638920568500637,144.603865814948932,22.1671146505918131],"rgb":[0.866666666666666696,0,0.2],"xyz":[0.304156056900047322,0.156139633552492829,0.0454443926369148404],"hpluv":[8.71533624525386585,399.528220173505417,46.4638920568500637],"hsluv":[8.71533624525386585,99.9999999999966,46.4638920568500637]},"#dd0044":{"lch":[46.7007828741672242,140.527307525302433,6.06736355557067153],"luv":[46.7007828741672242,139.740117429456177,14.853408400522655],"rgb":[0.866666666666666696,0,0.266666666666666663],"xyz":[0.308614052144266238,0.157922831650180423,0.0689231675898014789],"hpluv":[6.06736355557067153,381.835137536210595,46.7007828741672242],"hsluv":[6.06736355557067153,99.9999999999967315,46.7007828741672242]},"#dd0055":{"lch":[47.0148448700731194,133.854751810486647,2.4577968894866693],"luv":[47.0148448700731194,133.731616129679537,5.74015936982693],"rgb":[0.866666666666666696,0,0.333333333333333315],"xyz":[0.314576466183628223,0.160307797265925256,0.10032521486377538],"hpluv":[2.4577968894866693,361.275168455412427,47.0148448700731194],"hsluv":[2.4577968894866693,99.9999999999969873,47.0148448700731194]},"#dd0066":{"lch":[47.4099042919878073,126.898325188331157,357.792852491951692],"luv":[47.4099042919878073,126.80418183984473,-4.88716722969802841],"rgb":[0.866666666666666696,0,0.4],"xyz":[0.322161461957136408,0.163341795575328547,0.140272859270919342],"hpluv":[357.792852491951692,339.645713706877359,47.4099042919878073],"hsluv":[357.792852491951692,99.9999999999972857,47.4099042919878073]},"#dd0077":{"lch":[47.8883827301537,120.388643903007392,352.036106438093952],"luv":[47.8883827301537,119.227564738695264,-16.6797298325047478],"rgb":[0.866666666666666696,0,0.466666666666666674],"xyz":[0.331475474977338314,0.16706740078340937,0.189326661177317251],"hpluv":[352.036106438093952,319.002934776287759,47.8883827301537],"hsluv":[352.036106438093952,99.9999999999974136,47.8883827301537]},"#dd0088":{"lch":[48.4514347566520058,115.064311489444805,345.260130057314882],"luv":[48.4514347566520058,111.277652945923506,-29.2759241252361342],"rgb":[0.866666666666666696,0,0.533333333333333326],"xyz":[0.342615892079282114,0.171523567624186968,0.247999524580889419],"hpluv":[345.260130057314882,301.351479235409442,48.4514347566520058],"hsluv":[345.260130057314882,99.9999999999976836,48.4514347566520058]},"#dd0099":{"lch":[49.0990738312553816,111.554442433955828,337.69359677942],"luv":[49.0990738312553816,103.206522936464793,-42.3415546492536166],"rgb":[0.866666666666666696,0,0.6],"xyz":[0.355672856188354869,0.176746353267816125,0.316766202222007354],"hpluv":[337.69359677942,288.305479360883112,49.0990738312553816],"hsluv":[337.69359677942,99.9999999999979252,49.0990738312553816]},"#dd00aa":{"lch":[49.8303011832281442,110.265023964610052,329.72204926251294],"luv":[49.8303011832281442,95.2237329190616606,-55.5951094870335041],"rgb":[0.866666666666666696,0,0.66666666666666663],"xyz":[0.370730549153607369,0.18276943045391722,0.396070051839005954],"hpluv":[329.72204926251294,280.791263168904948,49.8303011832281442],"hsluv":[329.72204926251294,99.9999999999981526,49.8303011832281442]},"#dd00bb":{"lch":[50.6432416523731064,111.311454300018838,321.811503537589374],"luv":[50.6432416523731064,87.4886923962170613,-68.8183737179635244],"rgb":[0.866666666666666696,0,0.733333333333333282],"xyz":[0.387868141260944044,0.189624467296851978,0.486328036937647701],"hpluv":[321.811503537589374,278.905890401213071,50.6432416523731064],"hsluv":[321.811503537589374,99.9999999999984,50.6432416523731064]},"#dd00cc":{"lch":[51.5352850119508901,114.534817141075266,314.3830496716472],"luv":[51.5352850119508901,80.1116001600253753,-81.8557014345352201],"rgb":[0.866666666666666696,0,0.8],"xyz":[0.407160516704665798,0.19734141747434078,0.587934547607917857],"hpluv":[314.3830496716472,282.014975724645751,51.5352850119508901],"hsluv":[314.3830496716472,99.9999999999986215,51.5352850119508901]},"#dd00dd":{"lch":[52.5032286812834883,119.593841400887641,307.715012949243601],"luv":[52.5032286812834883,73.1596596193909647,-94.6062952735996419],"rgb":[0.866666666666666696,0,0.866666666666666696],"xyz":[0.428678842255299286,0.205948747694594314,0.701264395507923854],"hpluv":[307.715012949243601,289.042783730483222,52.5032286812834883],"hsluv":[307.715012949243601,99.9999999999987779,52.5032286812834883]},"#dd00ee":{"lch":[53.5434168792756111,126.080010820296707,301.921476351261958],"luv":[53.5434168792756111,66.6656277920787659,-107.01337860068783],"rgb":[0.866666666666666696,0,0.933333333333333348],"xyz":[0.452491022521474051,0.215473619801064359,0.826675211576447078],"hpluv":[301.921476351261958,298.799235277631283,53.5434168792756111],"hsluv":[301.921476351261958,99.999999999998991,53.5434168792756111]},"#dd00ff":{"lch":[54.6518715304170399,133.605457484958208,296.990855958497434],"luv":[54.6518715304170399,60.6366090811706258,-119.053013019000375],"rgb":[0.866666666666666696,0,1],"xyz":[0.478662070931305417,0.225942039164997055,0.964509399868228923],"hpluv":[296.990855958497434,310.211923209940835,54.6518715304170399],"hsluv":[296.990855958497434,99.99999999999919,54.6518715304170399]},"#dd1100":{"lch":[46.6790301132195,152.538998994032681,12.7564763340959253],"luv":[46.6790301132195,148.773935032481603,33.6817824506429915],"rgb":[0.866666666666666696,0.0666666666666666657,0],"xyz":[0.300185682790403863,0.157758524326120919,0.0146453810388783197],"hpluv":[12.7564763340959253,414.665968881342394,46.6790301132195],"hsluv":[12.7564763340959253,100.000000000002373,46.6790301132195]},"#dd1111":{"lch":[46.7325769897078942,150.969760125239,12.1770506300617818],"luv":[46.7325769897078942,147.573010021229663,31.8445471870185592],"rgb":[0.866666666666666696,0.0666666666666666657,0.0666666666666666657],"xyz":[0.301197348290040967,0.158163190525975772,0.0199734860036339529],"hpluv":[12.1770506300617818,409.92986676092562,46.7325769897078942],"hsluv":[12.1770506300617818,96.0592738250283,46.7325769897078942]},"#dd1122":{"lch":[46.8315975390355774,148.166934443607602,11.093898425687982],"luv":[46.8315975390355774,145.398162733087418,28.509905932130728],"rgb":[0.866666666666666696,0.0666666666666666657,0.133333333333333331],"xyz":[0.303072706428518,0.1589133337813666,0.0298503721996131455],"hpluv":[11.093898425687982,401.468660625611221,46.8315975390355774],"hsluv":[11.093898425687982,96.1199649520447821,46.8315975390355774]},"#dd1133":{"lch":[46.9939567691892393,143.840838530693645,9.28627571582045697],"luv":[46.9939567691892393,141.955717705878527,23.2112265902085468],"rgb":[0.866666666666666696,0.0666666666666666657,0.2],"xyz":[0.30616045716097573,0.160148434074349699,0.0461125260572242957],"hpluv":[9.28627571582045697,388.400266865181436,46.9939567691892393],"hsluv":[9.28627571582045697,96.2159198798013477,46.9939567691892393]},"#dd1144":{"lch":[47.2268997120704555,138.191174032002039,6.62861883301083665],"luv":[47.2268997120704555,137.267398010546174,15.951865839373701],"rgb":[0.866666666666666696,0.0666666666666666657,0.266666666666666663],"xyz":[0.310618452405194645,0.161931632172037293,0.0695913010101109342],"hpluv":[6.62861883301083665,371.304486157060069,47.2268997120704555],"hsluv":[6.62861883301083665,96.346372634165391,47.2268997120704555]},"#dd1155":{"lch":[47.5357948285950442,131.646215003298352,3.00154982487266553],"luv":[47.5357948285950442,131.465612026054913,6.89338663571585908],"rgb":[0.866666666666666696,0.0666666666666666657,0.333333333333333315],"xyz":[0.31658086644455663,0.164316597787782126,0.100993348284084836],"hpluv":[3.00154982487266553,351.420379681935685,47.5357948285950442],"hsluv":[3.00154982487266553,96.5074087268808114,47.5357948285950442]},"#dd1166":{"lch":[47.9244613368761776,124.817323026056556,358.307054390798669],"luv":[47.9244613368761776,124.762840903711279,-3.68750010524401839],"rgb":[0.866666666666666696,0.0666666666666666657,0.4],"xyz":[0.324165862218064815,0.167350596097185417,0.140940992691228811],"hpluv":[358.307054390798669,330.488955339688346,47.9244613368761776],"hsluv":[358.307054390798669,96.6928417837132912,47.9244613368761776]},"#dd1177":{"lch":[48.3953520744879313,118.427317384197096,352.504166614065639],"luv":[48.3953520744879313,117.415279069986227,-15.4493282615978131],"rgb":[0.866666666666666696,0.0666666666666666657,0.466666666666666674],"xyz":[0.333479875238266721,0.17107620130526624,0.189994794597626721],"hpluv":[352.504166614065639,310.51856077863863,48.3953520744879313],"hsluv":[352.504166614065639,96.8952584834877229,48.3953520744879313]},"#dd1188":{"lch":[48.949686611979061,113.213254375126112,345.662599129740954],"luv":[48.949686611979061,109.687147393784173,-28.0351683216148473],"rgb":[0.866666666666666696,0.0666666666666666657,0.533333333333333326],"xyz":[0.344620292340210521,0.175532368146043838,0.248667658001198888],"hpluv":[345.662599129740954,293.48552443008623,48.949686611979061],"hsluv":[345.662599129740954,97.1070447043031209,48.949686611979061]},"#dd1199":{"lch":[49.5875717372425,109.808639676001164,338.012756373247385],"luv":[49.5875717372425,101.821953610845213,-41.1123717433662961],"rgb":[0.866666666666666696,0.0666666666666666657,0.6],"xyz":[0.357677256449283276,0.180755153789673,0.317434335642316767],"hpluv":[338.012756373247385,280.997849503034615,49.5875717372425],"hsluv":[338.012756373247385,97.321211179253126,49.5875717372425]},"#dd11aa":{"lch":[50.3081241313593779,108.626384639058173,329.948207544292131],"luv":[50.3081241313593779,94.0240738412992556,-54.3982074892042462],"rgb":[0.866666666666666696,0.0666666666666666657,0.66666666666666663],"xyz":[0.372734949414535777,0.18677823097577409,0.396738185259315368],"hpluv":[329.948207544292131,273.991145484396441,50.3081241313593779],"hsluv":[329.948207544292131,97.5319211216277751,50.3081241313593779]},"#dd11bb":{"lch":[51.1095995740137,109.78676639377484,321.947120969557943],"luv":[51.1095995740137,86.4507346157054855,-67.6712979010019495],"rgb":[0.866666666666666696,0.0666666666666666657,0.733333333333333282],"xyz":[0.389872541521872451,0.193633267818708849,0.486996170357957114],"hpluv":[321.947120969557943,272.57551535007633,51.1095995740137],"hsluv":[321.947120969557943,97.7347175386083791,51.1095995740137]},"#dd11cc":{"lch":[51.9895276454598303,113.13117809908816,314.441471026924035],"luv":[51.9895276454598303,79.2122218564644527,-80.7718228508547469],"rgb":[0.866666666666666696,0.0666666666666666657,0.8],"xyz":[0.409164916965594205,0.20135021799619765,0.588602681028227326],"hpluv":[314.441471026924035,276.12502270002949,51.9895276454598303],"hsluv":[314.441471026924035,97.9265130550616,51.9895276454598303]},"#dd11dd":{"lch":[52.9448482611329325,118.314931067086022,307.715012949243601],"luv":[52.9448482611329325,72.3773062506166553,-93.594596282658145],"rgb":[0.866666666666666696,0.0666666666666666657,0.866666666666666696],"xyz":[0.430683242516227693,0.209957548216451184,0.701932528928233324],"hpluv":[307.715012949243601,283.566663729067216,52.9448482611329325],"hsluv":[307.715012949243601,98.1054292618058525,52.9448482611329325]},"#dd11ee":{"lch":[53.9720454332022257,124.924845967379298,301.881652150577509],"luv":[53.9720454332022257,65.9811112999591103,-106.078791902980541],"rgb":[0.866666666666666696,0.0666666666666666657,0.933333333333333348],"xyz":[0.454495422782402458,0.219482420322921229,0.827343344996756547],"hpluv":[301.881652150577509,293.71036424495378,53.9720454332022257],"hsluv":[301.881652150577509,98.2705657762439841,53.9720454332022257]},"#dd11ff":{"lch":[55.067273793018515,132.56906123155207,296.926443611211937],"luv":[55.067273793018515,60.0334023394080774,-118.197066796810802],"rgb":[0.866666666666666696,0.0666666666666666657,1],"xyz":[0.480666471192233824,0.229950839686853925,0.965177533288538392],"hpluv":[296.926443611211937,305.483621811531123,55.067273793018515],"hsluv":[296.926443611211937,99.9999999999990763,55.067273793018515]},"#dd2200":{"lch":[47.6481385708110494,147.881667770992,13.8451074484812633],"luv":[47.6481385708110494,143.585141583480663,35.3877772568702937],"rgb":[0.866666666666666696,0.133333333333333331,0],"xyz":[0.303901306525171777,0.165189771795656887,0.0158839222838009289],"hpluv":[13.8451074484812633,393.829031299888356,47.6481385708110494],"hsluv":[13.8451074484812633,100.000000000002302,47.6481385708110494]},"#dd2211":{"lch":[47.7000692420668,146.371743885583,13.2640103652051735],"luv":[47.7000692420668,142.467011758872331,33.583298953557744],"rgb":[0.866666666666666696,0.133333333333333331,0.0666666666666666657],"xyz":[0.304912972024808882,0.16559443799551174,0.0212120272485565586],"hpluv":[13.2640103652051735,389.383517616197651,47.7000692420668],"hsluv":[13.2640103652051735,96.2235359913314596,47.7000692420668]},"#dd2222":{"lch":[47.796111526211412,143.672697420673273,12.1770506300617871],"luv":[47.796111526211412,140.440127868319678,30.3053537920670948],"rgb":[0.866666666666666696,0.133333333333333331,0.133333333333333331],"xyz":[0.306788330163285938,0.166344581250902568,0.0310889134445357582],"hpluv":[12.1770506300617871,381.435408792809369,47.796111526211412],"hsluv":[12.1770506300617871,89.3821391188339618,47.796111526211412]},"#dd2233":{"lch":[47.9536166692339805,139.501418135148583,10.3611027729364178],"luv":[47.9536166692339805,137.226679529052944,25.0895214611231978],"rgb":[0.866666666666666696,0.133333333333333331,0.2],"xyz":[0.309876080895743644,0.167579681543885667,0.0473510673021469],"hpluv":[10.3611027729364178,369.144652707036585,47.9536166692339805],"hsluv":[10.3611027729364178,89.634195646670733,47.9536166692339805]},"#dd2244":{"lch":[48.1796580724099073,134.044487106872765,7.68678657448498281],"luv":[48.1796580724099073,132.839973125453383,17.9294747210675],"rgb":[0.866666666666666696,0.133333333333333331,0.266666666666666663],"xyz":[0.314334076139962559,0.169362879641573261,0.0708298422550335399],"hpluv":[7.68678657448498281,353.040533161258963,48.1796580724099073],"hsluv":[7.68678657448498281,89.9776949827144819,48.1796580724099073]},"#dd2255":{"lch":[48.4795139291676236,127.710640494506933,4.02871777991100277],"luv":[48.4795139291676236,127.395062601571667,8.9724979943620351],"rgb":[0.866666666666666696,0.133333333333333331,0.333333333333333315],"xyz":[0.320296490179324544,0.171747845257318094,0.102231889529007441],"hpluv":[4.02871777991100277,334.278275235680212,48.4795139291676236],"hsluv":[4.02871777991100277,90.4030378986951746,48.4795139291676236]},"#dd2266":{"lch":[48.8569858046774499,121.091623353111601,359.280669781128381],"luv":[48.8569858046774499,121.082080247128843,-1.52022673298942879],"rgb":[0.866666666666666696,0.133333333333333331,0.4],"xyz":[0.32788148595283273,0.174781843566721384,0.142179533936151403],"hpluv":[359.280669781128381,314.504423311283745,48.8569858046774499],"hsluv":[359.280669781128381,90.8946273178398627,48.8569858046774499]},"#dd2277":{"lch":[49.3145747506863046,114.897314975199777,353.392641366134796],"luv":[49.3145747506863046,114.134166521370943,-13.2206286152450421],"rgb":[0.866666666666666696,0.133333333333333331,0.466666666666666674],"xyz":[0.337195498973034635,0.178507448774802208,0.191233335842549312],"hpluv":[353.392641366134796,295.64729776044,49.3145747506863046],"hsluv":[353.392641366134796,91.4334620569677128,49.3145747506863046]},"#dd2288":{"lch":[49.8536069462695934,109.863045744465836,346.42832123602642],"luv":[49.8536069462695934,106.795352255968297,-25.780643063628979],"rgb":[0.866666666666666696,0.133333333333333331,0.533333333333333326],"xyz":[0.348335916074978436,0.182963615615579805,0.24990619924612148],"hpluv":[346.42832123602642,279.636833718457694,49.8536069462695934],"hsluv":[346.42832123602642,91.999736258284841,49.8536069462695934]},"#dd2299":{"lch":[50.4743452384724947,106.631383564155598,338.620922954440232],"luv":[50.4743452384724947,99.2939711585852507,-38.8710593162073792],"rgb":[0.866666666666666696,0.133333333333333331,0.6],"xyz":[0.361392880184051135,0.188186401259208963,0.318672876887239387],"hpluv":[338.620922954440232,268.07337181170567,50.4743452384724947],"hsluv":[338.620922954440232,92.5749897333771088,50.4743452384724947]},"#dd22aa":{"lch":[51.176101525576982,105.62881092572934,330.379319008457628],"luv":[51.176101525576982,91.8248770713425699,-52.2076397514415902],"rgb":[0.866666666666666696,0.133333333333333331,0.66666666666666663],"xyz":[0.376450573149303691,0.194209478445310058,0.397976726504238],"hpluv":[330.379319008457628,261.911470041672374,51.176101525576982],"hsluv":[330.379319008457628,93.1435427232804898,51.176101525576982]},"#dd22bb":{"lch":[51.9573548685870321,106.984993341056935,322.205396597718504],"luv":[51.9573548685870321,84.5409044706425306,-65.5638945721781425],"rgb":[0.866666666666666696,0.133333333333333331,0.733333333333333282],"xyz":[0.393588165256640365,0.201064515288244816,0.488234711602879734],"hpluv":[322.205396597718504,261.285408571692301,51.9573548685870321],"hsluv":[322.205396597718504,93.6931794305449301,51.9573548685870321]},"#dd22cc":{"lch":[52.8158750154556174,110.541708291411879,314.55250800555325],"luv":[52.8158750154556174,77.5519304210182838,-78.7728846745956],"rgb":[0.866666666666666696,0.133333333333333331,0.8],"xyz":[0.412880540700362064,0.208781465465733618,0.58984122227315],"hpluv":[314.55250800555325,265.583456637160452,52.8158750154556174],"hsluv":[314.55250800555325,94.2152138812719073,52.8158750154556174]},"#dd22dd":{"lch":[53.7488483860564088,115.947384169062644,307.715012949243658],"luv":[53.7488483860564088,70.9289965118926204,-91.721716891171],"rgb":[0.866666666666666696,0.133333333333333331,0.866666666666666696],"xyz":[0.434398866250995663,0.217388795685987152,0.703171070173155943],"hpluv":[307.715012949243658,273.735496610715643,53.7488483860564088],"hsluv":[307.715012949243658,94.704144859729837,53.7488483860564088]},"#dd22ee":{"lch":[54.7530025006588374,122.779599443276055,301.806367069585178],"luv":[54.7530025006588374,64.7110170709106,-104.342293961267828],"rgb":[0.866666666666666696,0.133333333333333331,0.933333333333333348],"xyz":[0.458211046517170373,0.226913667792457197,0.828581886241679166],"hpluv":[301.806367069585178,284.549350776984397,54.7530025006588374],"hsluv":[301.806367069585178,95.1571011878196629,54.7530025006588374]},"#dd22ff":{"lch":[55.8247247862810525,130.638613434108407,296.804995701950531],"luv":[55.8247247862810525,58.9121835814083425,-116.601037498200881],"rgb":[0.866666666666666696,0.133333333333333331,1],"xyz":[0.484382094927001794,0.237382087156389893,0.966416074533461],"hpluv":[296.804995701950531,296.950662194199822,55.8247247862810525],"hsluv":[296.804995701950531,99.9999999999989768,55.8247247862810525]},"#dd3300":{"lch":[49.1823134049741526,140.84390252957769,15.6779143459349193],"luv":[49.1823134049741526,135.603943521894649,38.06015476941716],"rgb":[0.866666666666666696,0.2,0],"xyz":[0.310019028614182623,0.17742521597367869,0.0179231629801378106],"hpluv":[15.6779143459349193,363.386194305474646,49.1823134049741526],"hsluv":[15.6779143459349193,100.000000000002331,49.1823134049741526]},"#dd3311":{"lch":[49.2318310772226226,139.415415721210906,15.0950854994101622],"luv":[49.2318310772226226,134.60488288385605,36.3067988748864323],"rgb":[0.866666666666666696,0.2,0.0666666666666666657],"xyz":[0.311030694113819728,0.177829882173533543,0.0232512679448934403],"hpluv":[15.0950854994101622,359.338818773581409,49.2318310772226226],"hsluv":[15.0950854994101622,96.4660724045404834,49.2318310772226226]},"#dd3322":{"lch":[49.3234253076193,136.858666305461554,14.0037238091571297],"luv":[49.3234253076193,132.791226888227897,33.117738516222083],"rgb":[0.866666666666666696,0.2,0.133333333333333331],"xyz":[0.312906052252296785,0.178580025428924372,0.0331281541408726399],"hpluv":[14.0037238091571297,352.093818956611813,49.3234253076193],"hsluv":[14.0037238091571297,90.0546185843159321,49.3234253076193]},"#dd3333":{"lch":[49.4736766963079901,132.899088309008249,12.1770506300618315],"luv":[49.4736766963079901,129.908920002044738,28.0328410488111714],"rgb":[0.866666666666666696,0.2,0.2],"xyz":[0.31599380298475449,0.179815125721907471,0.0493903079984837831],"hpluv":[12.1770506300618315,340.868713502871344,49.4736766963079901],"hsluv":[12.1770506300618315,79.876105021286179,49.4736766963079901]},"#dd3344":{"lch":[49.6893958667399289,127.704341644457742,9.47932118493828391],"luv":[49.6893958667399289,125.960552293014786,21.031836364974712],"rgb":[0.866666666666666696,0.2,0.266666666666666663],"xyz":[0.320451798228973406,0.181598323819595064,0.0728690829513704286],"hpluv":[9.47932118493828391,326.122882655454703,49.6893958667399289],"hsluv":[9.47932118493828391,80.502956859434363,49.6893958667399289]},"#dd3355":{"lch":[49.9757165444531495,121.655077778432485,5.77481987360355742],"luv":[49.9757165444531495,121.037681249067731,12.2408197080725127],"rgb":[0.866666666666666696,0.2,0.333333333333333315],"xyz":[0.326414212268335391,0.183983289435339897,0.10427113022534433],"hpluv":[5.77481987360355742,308.89475746512926,49.9757165444531495],"hsluv":[5.77481987360355742,81.2827465905028674,49.9757165444531495]},"#dd3366":{"lch":[50.3364012453720164,115.314978813651479,0.942739432835561],"luv":[50.3364012453720164,115.29936949267524,1.8972963354308543],"rgb":[0.866666666666666696,0.2,0.4],"xyz":[0.333999208041843576,0.187017287744743188,0.144218774632488278],"hpluv":[0.942739432835561,290.698564526315124,50.3364012453720164],"hsluv":[0.942739432835561,82.1889615211792375,50.3364012453720164]},"#dd3377":{"lch":[50.77400791740952,109.376176789803836,354.916348657894],"luv":[50.77400791740952,108.945933542464203,-9.69183231981436],"rgb":[0.866666666666666696,0.2,0.466666666666666674],"xyz":[0.343313221062045482,0.190742892952824,0.193272576538886187],"hpluv":[354.916348657894,273.35096965093777,50.77400791740952],"hsluv":[354.916348657894,83.1884512053650269,50.77400791740952]},"#dd3388":{"lch":[51.2900053848270545,104.574014460431073,347.747121452304157],"luv":[51.2900053848270545,102.191864987712989,-22.1934051173651383],"rgb":[0.866666666666666696,0.2,0.533333333333333326],"xyz":[0.354453638163989282,0.195199059793601609,0.251945439942458382],"hpluv":[347.747121452304157,258.720214242298141,51.2900053848270545],"hsluv":[347.747121452304157,84.245872474104587,51.2900053848270545]},"#dd3399":{"lch":[51.8848727297214509,101.568582070491487,339.671486537323062],"luv":[51.8848727297214509,95.2425012264059347,-35.2851643605103433],"rgb":[0.866666666666666696,0.2,0.6],"xyz":[0.367510602273062,0.200421845437230767,0.320712117583576262],"hpluv":[339.671486537323062,248.403642764366595,51.8848727297214509],"hsluv":[339.671486537323062,85.3275173092541763,51.8848727297214509]},"#dd33aa":{"lch":[52.5581975758694284,100.811065681036794,331.124654782349864],"luv":[52.5581975758694284,88.2774684157494,-48.6822301651504219],"rgb":[0.866666666666666696,0.2,0.66666666666666663],"xyz":[0.382568295238314537,0.206444922623331861,0.400015967200574862],"hpluv":[331.124654782349864,243.392431352391867,52.5581975758694284],"hsluv":[331.124654782349864,86.4040244757801,52.5581975758694284]},"#dd33bb":{"lch":[53.3087788146031301,102.447919096954379,322.651217662355],"luv":[53.3087788146031301,81.4417156804123579,-62.1516136100020091],"rgb":[0.866666666666666696,0.2,0.733333333333333282],"xyz":[0.399705887345651212,0.21329995946626662,0.490273952299216609],"hpluv":[322.651217662355,243.861776984359892,53.3087788146031301],"hsluv":[322.651217662355,87.4518397844355633,53.3087788146031301]},"#dd33cc":{"lch":[54.1347343907921612,106.321268361530926,314.743497623169446],"luv":[54.1347343907921612,74.8431687457043751,-75.5163041872859253],"rgb":[0.866666666666666696,0.2,0.8],"xyz":[0.418998262789372911,0.221016909643755421,0.59188046296948682],"hpluv":[314.743497623169446,249.220329072313831,54.1347343907921612],"hsluv":[314.743497623169446,88.4535853271675734,54.1347343907921612]},"#dd33dd":{"lch":[55.033612168624586,112.066789743934578,307.715012949243715],"luv":[55.033612168624586,68.5551036430149,-88.6519211749340741],"rgb":[0.866666666666666696,0.2,0.866666666666666696],"xyz":[0.44051658834000651,0.229624239864008955,0.705210310869492818],"hpluv":[307.715012949243715,258.397459164480438,55.033612168624586],"hsluv":[307.715012949243715,89.3976510430439077,55.033612168624586]},"#dd33ee":{"lch":[56.0025007026426351,119.245240694744666,301.678101579798295],"luv":[56.0025007026426351,62.6212128139869506,-101.479116738632214],"rgb":[0.866666666666666696,0.2,0.933333333333333348],"xyz":[0.464328768606181219,0.239149111970479,0.830621126938016],"hpluv":[301.678101579798295,270.192295422478139,56.0025007026426351],"hsluv":[301.678101579798295,90.277343199481848,56.0025007026426351]},"#dd33ff":{"lch":[57.0381364623091116,127.442655532056975,296.59904001960814],"luv":[57.0381364623091116,57.0616979163907772,-113.954346472440875],"rgb":[0.866666666666666696,0.2,1],"xyz":[0.49049981701601264,0.249617531334411696,0.968455315229797886],"hpluv":[296.59904001960814,283.523336448078851,57.0381364623091116],"hsluv":[296.59904001960814,99.9999999999989626,57.0381364623091116]},"#dd4400":{"lch":[51.2775121999195278,131.902040393952689,18.409420821930695],"luv":[51.2775121999195278,125.151834557466444,41.6553305951168156],"rgb":[0.866666666666666696,0.266666666666666663,0],"xyz":[0.318851599097148664,0.19509035693961102,0.0208673531411264039],"hpluv":[18.409420821930695,326.410329295551833,51.2775121999195278],"hsluv":[18.409420821930695,100.000000000002245,51.2775121999195278]},"#dd4411":{"lch":[51.3239968707682124,130.563017922041524,17.8265447991253865],"luv":[51.3239968707682124,124.294382399750674,39.9700907276414128],"rgb":[0.866666666666666696,0.266666666666666663,0.0666666666666666657],"xyz":[0.319863264596785768,0.195495023139465873,0.0261954581058820371],"hpluv":[17.8265447991253865,322.804096021626094,51.3239968707682124],"hsluv":[17.8265447991253865,96.7659447415066154,51.3239968707682124]},"#dd4422":{"lch":[51.4099976690743148,128.162025810787327,16.7333496749677373],"luv":[51.4099976690743148,122.735013936090269,36.9001519513490237],"rgb":[0.866666666666666696,0.266666666666666663,0.133333333333333331],"xyz":[0.321738622735262825,0.196245166394856702,0.0360723443018612297],"hpluv":[16.7333496749677373,316.337811591652041,51.4099976690743148],"hsluv":[16.7333496749677373,90.8878404873512551,51.4099976690743148]},"#dd4433":{"lch":[51.551120550377874,124.432504768689228,14.8985084842763058],"luv":[51.551120550377874,120.249428964759446,31.9925472049216744],"rgb":[0.866666666666666696,0.266666666666666663,0.2],"xyz":[0.324826373467720531,0.197480266687839801,0.0523344981594723729],"hpluv":[14.8985084842763058,306.291581325991444,51.551120550377874],"hsluv":[14.8985084842763058,81.5276169642245634,51.551120550377874]},"#dd4444":{"lch":[51.7538349343952575,119.518854000271219,12.1770506300618191],"luv":[51.7538349343952575,116.829734805674121,25.2105042943215345],"rgb":[0.866666666666666696,0.266666666666666663,0.266666666666666663],"xyz":[0.329284368711939446,0.199263464785527394,0.0758132731123590115],"hpluv":[12.1770506300618191,293.044254198223086,51.7538349343952575],"hsluv":[12.1770506300618191,68.6693518559736,51.7538349343952575]},"#dd4455":{"lch":[52.0230766847265045,113.767549619287195,8.41751308754220773],"luv":[52.0230766847265045,112.542004033713866,16.6539086839248256],"rgb":[0.866666666666666696,0.266666666666666663,0.333333333333333315],"xyz":[0.335246782751301431,0.201648430401272227,0.107215320386332927],"hpluv":[8.41751308754220773,277.499175779034886,52.0230766847265045],"hsluv":[8.41751308754220773,69.8262296658756867,52.0230766847265045]},"#dd4466":{"lch":[52.3625377834239316,107.708396397149357,3.47572472865958737],"luv":[52.3625377834239316,107.51027478189765,6.52989056311981209],"rgb":[0.866666666666666696,0.266666666666666663,0.4],"xyz":[0.342831778524809616,0.204682428710675518,0.147162964793476875],"hpluv":[3.47572472865958737,261.016642983210886,52.3625377834239316],"hsluv":[3.47572472865958737,71.1800009153795088,52.3625377834239316]},"#dd4477":{"lch":[52.7748219535637304,102.013560717275851,357.256373562230806],"luv":[52.7748219535637304,101.896624345108023,-4.88308481282748108],"rgb":[0.866666666666666696,0.266666666666666663,0.466666666666666674],"xyz":[0.352145791545011522,0.208408033918756341,0.196216766699874784],"hpluv":[357.256373562230806,245.284698321799027,52.7748219535637304],"hsluv":[357.256373562230806,72.6848757644217898,52.7748219535637304]},"#dd4488":{"lch":[53.2615487789460502,97.4233940898595137,349.787328406912707],"luv":[53.2615487789460502,95.8798587184095368,-17.273401753155067],"rgb":[0.866666666666666696,0.266666666666666663,0.533333333333333326],"xyz":[0.363286208646955322,0.212864200759533939,0.254889630103446951],"hpluv":[349.787328406912707,232.107295472344475,53.2615487789460502],"hsluv":[349.787328406912707,74.290572198007979,53.2615487789460502]},"#dd4499":{"lch":[53.8234397136373133,94.6288173232078123,341.30539151945959],"luv":[53.8234397136373133,89.63624285692255,-30.330793502376995],"rgb":[0.866666666666666696,0.266666666666666663,0.6],"xyz":[0.376343172756028,0.218086986403163097,0.323656307744564886],"hpluv":[341.30539151945959,223.095746339835301,53.8234397136373133],"hsluv":[341.30539151945959,75.9477058190526577,53.8234397136373133]},"#dd44aa":{"lch":[54.4604007326765327,94.1226543521493539,332.285935566136516],"luv":[54.4604007326765327,83.3248558176969425,-43.7726223255380802],"rgb":[0.866666666666666696,0.266666666666666663,0.66666666666666663],"xyz":[0.391400865721280578,0.224110063589264191,0.402960157361563487],"hpluv":[332.285935566136516,219.307083848265933,54.4604007326765327],"hsluv":[332.285935566136516,77.6118806813578175,54.4604007326765327]},"#dd44bb":{"lch":[55.1716077278512387,96.0796222427451596,323.344264550451],"luv":[55.1716077278512387,77.0786382371216,-57.3600674495728526],"rgb":[0.866666666666666696,0.266666666666666663,0.733333333333333282],"xyz":[0.408538457828617252,0.23096510043219895,0.493218142460205233],"hpluv":[323.344264550451,220.981019921853772,55.1716077278512387],"hsluv":[323.344264550451,79.2461809113807334,55.1716077278512387]},"#dd44cc":{"lch":[55.9555962107488511,100.342448880687911,315.038748994660807],"luv":[55.9555962107488511,71.0007950362134,-70.9048246002984],"rgb":[0.866666666666666696,0.266666666666666663,0.8],"xyz":[0.427830833272338951,0.238682050609687751,0.594824653130475389],"hpluv":[315.038748994660807,227.551915064947707,55.9555962107488511],"hsluv":[315.038748994660807,80.8221578956601547,55.9555962107488511]},"#dd44dd":{"lch":[56.8103543983327484,106.525561993860563,307.715012949243828],"luv":[56.8103543983327484,65.1653443433699664,-84.2684594300729515],"rgb":[0.866666666666666696,0.266666666666666663,0.866666666666666696],"xyz":[0.44934915882297255,0.247289380829941285,0.708154501030481387],"hpluv":[307.715012949243828,237.939016458104504,56.8103543983327484],"hsluv":[307.715012949243828,82.3196529548951474,56.8103543983327484]},"#dd44ee":{"lch":[57.7334174818232384,114.162073161526394,301.482814132357476],"luv":[57.7334174818232384,59.6203198077272134,-97.3570563162323452],"rgb":[0.866666666666666696,0.266666666666666663,0.933333333333333348],"xyz":[0.473161339089147259,0.256814252936411302,0.83356531709900461],"hpluv":[301.482814132357476,250.91920763959763,57.7334174818232384],"hsluv":[301.482814132357476,85.8927976857303577,57.7334174818232384]},"#dd44ff":{"lch":[58.721960397178492,122.814959128704743,296.287773174859751],"luv":[58.721960397178492,54.3922733794591196,-110.113554035820684],"rgb":[0.866666666666666696,0.266666666666666663,1],"xyz":[0.49933238749897868,0.267282672300344,0.971399505390786455],"hpluv":[296.287773174859751,265.393357493052918,58.721960397178492],"hsluv":[296.287773174859751,99.9999999999987779,58.721960397178492]},"#dd5500":{"lch":[53.8905970004369834,121.845910621274882,22.2085433527856502],"luv":[53.8905970004369834,112.806678507237621,46.055175814369008],"rgb":[0.866666666666666696,0.333333333333333315,0],"xyz":[0.330664855811494629,0.218716870368303284,0.0248051053792416147],"hpluv":[22.2085433527856502,286.904453583707834,53.8905970004369834],"hsluv":[22.2085433527856502,100.000000000002217,53.8905970004369834]},"#dd5511":{"lch":[53.9336739056601573,120.58904296901602,21.6306448037720749],"luv":[53.9336739056601573,112.097097503939963,44.4517492948856656],"rgb":[0.866666666666666696,0.333333333333333315,0.0666666666666666657],"xyz":[0.331676521311131733,0.219121536568158137,0.0301332103439972479],"hpluv":[21.6306448037720749,283.71818310283129,53.9336739056601573],"hsluv":[21.6306448037720749,97.0955711707227493,53.9336739056601573]},"#dd5522":{"lch":[54.0133869328819856,118.329807147086328,20.5443811064534074],"luv":[54.0133869328819856,110.804107011743554,41.5258128011568957],"rgb":[0.866666666666666696,0.333333333333333315,0.133333333333333331],"xyz":[0.33355187944960879,0.219871679823548966,0.0400100965399764405],"hpluv":[20.5443811064534074,277.99185559962018,54.0133869328819856],"hsluv":[20.5443811064534074,91.805999212684128,54.0133869328819856]},"#dd5533":{"lch":[54.1442392255445526,114.805940194650788,18.7140712474621722],"luv":[54.1442392255445526,108.736323489647134,36.8349814433582736],"rgb":[0.866666666666666696,0.333333333333333315,0.2],"xyz":[0.336639630182066496,0.221106780116532065,0.0562722503975875837],"hpluv":[18.7140712474621722,269.061420184977408,54.1442392255445526],"hsluv":[18.7140712474621722,83.3546452149230674,54.1442392255445526]},"#dd5544":{"lch":[54.3323026853354207,110.135081135525823,15.9827981501284579],"luv":[54.3323026853354207,105.877744255237971,30.3255563535325479],"rgb":[0.866666666666666696,0.333333333333333315,0.266666666666666663],"xyz":[0.341097625426285411,0.222889978214219658,0.0797510253504742223],"hpluv":[15.9827981501284579,257.221277658214035,54.3323026853354207],"hsluv":[15.9827981501284579,71.6878656400709104,54.3323026853354207]},"#dd5555":{"lch":[54.5822696158357132,104.625049281135261,12.1770506300618937],"luv":[54.5822696158357132,102.271033836367579,22.0689051636128752],"rgb":[0.866666666666666696,0.333333333333333315,0.333333333333333315],"xyz":[0.347060039465647396,0.225274943829964491,0.111153072624448138],"hpluv":[12.1770506300618937,243.233512665758,54.5822696158357132],"hsluv":[12.1770506300618937,61.8784513389384,54.5822696158357132]},"#dd5566":{"lch":[54.8977244977922254,98.7674373059184205,7.11744080010036573],"luv":[54.8977244977922254,98.0063618739055897,12.2376347477602359],"rgb":[0.866666666666666696,0.333333333333333315,0.4],"xyz":[0.354645035239155582,0.228308942139367782,0.151100717031592086],"hpluv":[7.11744080010036573,228.296245111676484,54.8977244977922254],"hsluv":[7.11744080010036573,62.7979151590930655,54.8977244977922254]},"#dd5577":{"lch":[55.2812881935381597,93.2135187108595886,0.661514811515945267],"luv":[55.2812881935381597,93.2073060454778783,1.07618316489082022],"rgb":[0.866666666666666696,0.333333333333333315,0.466666666666666674],"xyz":[0.363959048259357487,0.232034547347448605,0.20015451893799],"hpluv":[0.661514811515945267,213.963687644495565,55.2812881935381597],"hsluv":[0.661514811515945267,63.8225359413096456,55.2812881935381597]},"#dd5588":{"lch":[55.7347110848163538,88.7163187329837,352.791835078768599],"luv":[55.7347110848163538,88.0151786426913532,-11.1316457915027787],"rgb":[0.866666666666666696,0.333333333333333315,0.533333333333333326],"xyz":[0.375099465361301287,0.236490714188226203,0.258827382341562162],"hpluv":[352.791835078768599,201.984054112510194,55.7347110848163538],"hsluv":[352.791835078768599,64.9173177009572,55.7347110848163538]},"#dd5599":{"lch":[56.2589463845586408,86.0173277442407169,343.733753082454825],"luv":[56.2589463845586408,82.5740942724548717,-24.0935598727929836],"rgb":[0.866666666666666696,0.333333333333333315,0.6],"xyz":[0.388156429470374,0.241713499831855361,0.327594059982680097],"hpluv":[343.733753082454825,194.014271994181229,56.2589463845586408],"hsluv":[343.733753082454825,66.0466553819121,56.2589463845586408]},"#dd55aa":{"lch":[56.8542178605490278,85.677853149792881,334.018383993549605],"luv":[56.8542178605490278,77.0187914763158545,-37.5339883290498477],"rgb":[0.866666666666666696,0.333333333333333315,0.66666666666666663],"xyz":[0.403214122435626543,0.247736577017956455,0.406897909599678698],"hpluv":[334.018383993549605,191.225239227061849,56.8542178605490278],"hsluv":[334.018383993549605,67.3075554564469485,56.8542178605490278]},"#dd55bb":{"lch":[57.5200884026389332,87.9206422548043633,324.375348896731964],"luv":[57.5200884026389332,71.4663144117056817,-51.2113780219251638],"rgb":[0.866666666666666696,0.333333333333333315,0.733333333333333282],"xyz":[0.420351714542963217,0.254591613860891242,0.497155894698320444],"hpluv":[324.375348896731964,193.959311397505388,57.5200884026389332],"hsluv":[324.375348896731964,69.5366313504716,57.5200884026389332]},"#dd55cc":{"lch":[58.2555317670128829,92.5910381182408315,315.474469199465034],"luv":[58.2555317670128829,66.0116746837007895,-64.9273374262858596],"rgb":[0.866666666666666696,0.333333333333333315,0.8],"xyz":[0.439644089986684916,0.262308564038380043,0.5987624053685906],"hpluv":[315.474469199465034,201.683843426221756,58.2555317670128829],"hsluv":[315.474469199465034,71.7082017587958802,58.2555317670128829]},"#dd55dd":{"lch":[59.0590075291469532,99.2700295618383848,307.715012949244056],"luv":[59.0590075291469532,60.7268860008133231,-78.5288742174016363],"rgb":[0.866666666666666696,0.333333333333333315,0.866666666666666696],"xyz":[0.461162415537318515,0.270915894258633549,0.712092253268596598],"hpluv":[307.715012949244056,213.290412049590259,59.0590075291469532],"hsluv":[307.715012949244056,73.7919865345850923,59.0590075291469532]},"#dd55ee":{"lch":[59.9285380001613674,107.447476486145689,301.201070052482692],"luv":[59.9285380001613674,55.6624113436061592,-91.905691698915],"rgb":[0.866666666666666696,0.333333333333333315,0.933333333333333348],"xyz":[0.484974595803493225,0.280440766365103566,0.837503069337119821],"hpluv":[301.201070052482692,227.510719406260364,59.9285380001613674],"hsluv":[301.201070052482692,84.9240358585231405,59.9285380001613674]},"#dd55ff":{"lch":[60.8617852443614,116.651127585902827,295.843561463814751],"luv":[60.8617852443614,50.8500320114402413,-104.984569397117028],"rgb":[0.866666666666666696,0.333333333333333315,1],"xyz":[0.511145644213324646,0.29090918572903629,0.975337257628901666],"hpluv":[295.843561463814751,243.211205533984923,60.8617852443614],"hsluv":[295.843561463814751,99.9999999999987,60.8617852443614]},"#dd6600":{"lch":[56.9556719941368783,111.624872970007345,27.247071009398578],"luv":[56.9556719941368783,99.2390381331236568,51.1050445257873349],"rgb":[0.866666666666666696,0.4,0],"xyz":[0.345692913517341105,0.248772985779996625,0.0298144579478569621],"hpluv":[27.247071009398578,248.69286407076,56.9556719941368783],"hsluv":[27.247071009398578,100.000000000002402,56.9556719941368783]},"#dd6611":{"lch":[56.9952083090352204,110.431802069127656,26.6846840374429064],"luv":[56.9952083090352204,98.6698726856829325,49.5927326573774181],"rgb":[0.866666666666666696,0.4,0.0666666666666666657],"xyz":[0.346704579016978209,0.249177651979851478,0.0351425629126125919],"hpluv":[26.6846840374429064,245.864111809588593,56.9952083090352204],"hsluv":[26.6846840374429064,97.4289366186781,56.9952083090352204]},"#dd6622":{"lch":[57.0683850250612181,108.280332786330632,25.6245441521289443],"luv":[57.0683850250612181,97.630674939023308,46.8282156319165637],"rgb":[0.866666666666666696,0.4,0.133333333333333331],"xyz":[0.348579937155455266,0.249927795235242306,0.0450194491085917914],"hpluv":[25.6245441521289443,240.764984433182946,57.0683850250612181],"hsluv":[25.6245441521289443,92.7369917538900239,57.0683850250612181]},"#dd6633":{"lch":[57.1885511035567617,104.905985735707603,23.8291565941278485],"luv":[57.1885511035567617,95.9631904105633566,42.3831561992078036],"rgb":[0.866666666666666696,0.4,0.2],"xyz":[0.351667687887912972,0.251162895528225405,0.0612816029662029346],"hpluv":[23.8291565941278485,232.771873337598265,57.1885511035567617],"hsluv":[23.8291565941278485,85.2149281001260306,57.1885511035567617]},"#dd6644":{"lch":[57.3613500282636153,100.395707721520722,21.1283100845564071],"luv":[57.3613500282636153,93.6466609873464222,36.1884099516133162],"rgb":[0.866666666666666696,0.4,0.266666666666666663],"xyz":[0.356125683132131887,0.252946093625912971,0.0847603779190895801],"hpluv":[21.1283100845564071,222.093121414046323,57.3613500282636153],"hsluv":[21.1283100845564071,74.7790089775498785,57.3613500282636153]},"#dd6655":{"lch":[57.5911977652478555,95.0134939889914136,17.3206113628181804],"luv":[57.5911977652478555,90.7049894048208927,28.2872575034667619],"rgb":[0.866666666666666696,0.4,0.333333333333333315],"xyz":[0.362088097171493872,0.255331059241657832,0.116162425193063482],"hpluv":[17.3206113628181804,209.347849744778358,57.5911977652478555],"hsluv":[17.3206113628181804,61.5516693045169205,57.5911977652478555]},"#dd6666":{"lch":[57.8815358558834703,89.2064417623026742,12.1770506300619559],"luv":[57.8815358558834703,87.1993378887650579,18.8166076552624659],"rgb":[0.866666666666666696,0.4,0.4],"xyz":[0.369673092945002058,0.25836505755106115,0.156110069600207457],"hpluv":[12.1770506300619559,195.566965385494854,57.8815358558834703],"hsluv":[12.1770506300619559,60.6635523422702,57.8815358558834703]},"#dd6677":{"lch":[58.2349645673757834,83.6008411979688901,5.48003957367995387],"luv":[58.2349645673757834,83.2187459143753188,7.98379467713524704],"rgb":[0.866666666666666696,0.4,0.466666666666666674],"xyz":[0.378987105965203963,0.262090662759141946,0.205163871506605366],"hpluv":[5.48003957367995387,182.165511808695555,58.2349645673757834],"hsluv":[5.48003957367995387,61.6238417996400329,58.2349645673757834]},"#dd6688":{"lch":[58.6533262634944208,78.9686504698842,357.125632416080862],"luv":[58.6533262634944208,78.8692993006444,-3.95997283577995],"rgb":[0.866666666666666696,0.4,0.533333333333333326],"xyz":[0.390127523067147763,0.266546829599919544,0.263836734910177506],"hpluv":[357.125632416080862,170.8446551494321,58.6533262634944208],"hsluv":[357.125632416080862,62.6596910240402778,58.6533262634944208]},"#dd6699":{"lch":[59.1377678367746853,76.1279623836736192,347.292482429378936],"luv":[59.1377678367746853,74.2632604984211184,-16.746187531306056],"rgb":[0.866666666666666696,0.4,0.6],"xyz":[0.403184487176220463,0.271769615243548701,0.332603412551295441],"hpluv":[347.292482429378936,163.349798950832565,59.1377678367746853],"hsluv":[347.292482429378936,63.7390009960191435,59.1377678367746853]},"#dd66aa":{"lch":[59.6887956605557406,75.751758533498986,336.577260120739709],"luv":[59.6887956605557406,69.5095811529117,-30.1122408476074845],"rgb":[0.866666666666666696,0.4,0.66666666666666663],"xyz":[0.418242180141473,0.277792692429649823,0.411907262168294042],"hpluv":[336.577260120739709,161.042027442088397,59.6887956605557406],"hsluv":[336.577260120739709,64.8305690383666473,59.6887956605557406]},"#dd66bb":{"lch":[60.3063295400458372,78.1477005551706,325.894443158435308],"luv":[60.3063295400458372,64.706761589723726,-43.8189240697585518],"rgb":[0.866666666666666696,0.4,0.733333333333333282],"xyz":[0.435379772248809693,0.284647729272584582,0.502165247266935788],"hpluv":[325.894443158435308,164.434383230317081,60.3063295400458372],"hsluv":[325.894443158435308,65.9059953936301213,60.3063295400458372]},"#dd66cc":{"lch":[60.9897585015337427,83.1712006992562891,316.109248272524042],"luv":[60.9897585015337427,59.9384091909213694,-57.6613885491638314],"rgb":[0.866666666666666696,0.4,0.8],"xyz":[0.454672147692531392,0.292364679450073384,0.603771757937205944],"hpluv":[316.109248272524042,173.043537218511,60.9897585015337427],"hsluv":[316.109248272524042,66.9408479310096709,60.9897585015337427]},"#dd66dd":{"lch":[61.7379991889007158,90.3518241723814555,307.715012949244283],"luv":[61.7379991889007158,55.2713135142553753,-71.4740094977595675],"rgb":[0.866666666666666696,0.4,0.866666666666666696],"xyz":[0.476190473243165,0.30097200967032689,0.717101605837211942],"hpluv":[307.715012949244283,185.705044478150711,61.7379991889007158],"hsluv":[307.715012949244283,67.9151328937623759,61.7379991889007158]},"#dd66ee":{"lch":[62.5495564285741779,99.1126076890086125,300.803780654240427],"luv":[62.5495564285741779,50.7555210146825431,-85.1304063742412183],"rgb":[0.866666666666666696,0.4,0.933333333333333348],"xyz":[0.500002653509339701,0.310496881776796907,0.842512421905735165],"hpluv":[300.803780654240427,201.068480083703747,62.5495564285741779],"hsluv":[300.803780654240427,83.670703518064812,62.5495564285741779]},"#dd66ff":{"lch":[63.4225848554444696,108.928953372808309,295.226788235463459],"luv":[63.4225848554444696,46.4257694276883512,-98.5401685402585628],"rgb":[0.866666666666666696,0.4,1],"xyz":[0.526173701919171122,0.320965301140729631,0.980346610197517],"hpluv":[295.226788235463459,217.940889521273107,63.4225848554444696],"hsluv":[295.226788235463459,99.9999999999986073,63.4225848554444696]},"#dd7700":{"lch":[60.3985006876916088,102.209421710697811,33.6568691403047779],"luv":[60.3985006876916088,85.0762157619203,56.6463008330327753],"rgb":[0.866666666666666696,0.466666666666666674,0],"xyz":[0.364146651570758706,0.285680461886832382,0.0359657039656626626],"hpluv":[33.6568691403047779,214.735624532269611,60.3985006876916088],"hsluv":[33.6568691403047779,100.000000000002245,60.3985006876916088]},"#dd7711":{"lch":[60.4345564785723894,101.058408313912437,33.1281817520222],"luv":[60.4345564785723894,84.6313647704988,55.2298287886556736],"rgb":[0.866666666666666696,0.466666666666666674,0.0666666666666666657],"xyz":[0.36515831707039581,0.286085128086687235,0.0412938089304182923],"hpluv":[33.1281817520222,212.190746676882327,60.4345564785723894],"hsluv":[33.1281817520222,97.7465438968969238,60.4345564785723894]},"#dd7722":{"lch":[60.5013044729937803,98.9745052833742136,32.1281810047412648],"luv":[60.5013044729937803,83.8175940663154364,52.6361436754539582],"rgb":[0.866666666666666696,0.466666666666666674,0.133333333333333331],"xyz":[0.367033675208872867,0.286835271342078035,0.0511706951263974918],"hpluv":[32.1281810047412648,207.585936448863947,60.5013044729937803],"hsluv":[32.1281810047412648,93.6262478826975126,60.5013044729937803]},"#dd7733":{"lch":[60.6109510167574115,95.6833141687104813,30.4242910043150516],"luv":[60.6109510167574115,82.5076313520097813,48.4539717565960331],"rgb":[0.866666666666666696,0.466666666666666674,0.2],"xyz":[0.370121425941330573,0.288070371635061162,0.067432848984008642],"hpluv":[30.4242910043150516,200.320058173129354,60.6109510167574115],"hsluv":[30.4242910043150516,86.9991150080846722,60.6109510167574115]},"#dd7744":{"lch":[60.7687036553482756,91.2360291121886888,27.8356422373186483],"luv":[60.7687036553482756,80.6791699404734857,42.6014617809804932],"rgb":[0.866666666666666696,0.466666666666666674,0.266666666666666663],"xyz":[0.374579421185549488,0.289853569732748728,0.0909116239368952805],"hpluv":[27.8356422373186483,190.513488615549676,60.7687036553482756],"hsluv":[27.8356422373186483,77.760618312285672,60.7687036553482756]},"#dd7755":{"lch":[60.9786842032445122,85.8448802225428125,24.131655223886618],"luv":[60.9786842032445122,78.3427622472179479,35.096368243717265],"rgb":[0.866666666666666696,0.466666666666666674,0.333333333333333315],"xyz":[0.380541835224911473,0.292238535348493589,0.122313671210869182],"hpluv":[24.131655223886618,178.63875223296418,60.9786842032445122],"hsluv":[24.131655223886618,65.9765814384596894,60.9786842032445122]},"#dd7766":{"lch":[61.2441632046235895,79.8999585154359693,19.0216316495787474],"luv":[61.2441632046235895,75.5370685872685073,26.0414024201977448],"rgb":[0.866666666666666696,0.466666666666666674,0.4],"xyz":[0.388126830998419659,0.295272533657896907,0.162261315618013158],"hpluv":[19.0216316495787474,165.546946717194828,61.2441632046235895],"hsluv":[19.0216316495787474,57.9572057581871576,61.2441632046235895]},"#dd7777":{"lch":[61.5676827516498122,73.9875996712566,12.1770506300619097],"luv":[61.5676827516498122,72.3229127387857318,15.6064473244910626],"rgb":[0.866666666666666696,0.466666666666666674,0.466666666666666674],"xyz":[0.397440844018621564,0.298998138865977703,0.211315117524411067],"hpluv":[12.1770506300619097,152.491436777287873,61.5676827516498122],"hsluv":[12.1770506300619097,58.8480912007490744,61.5676827516498122]},"#dd7788":{"lch":[61.9511315612573,68.8928540500868252,3.33484257213660307],"luv":[61.9511315612573,68.7761927043778769,4.00757485979294081],"rgb":[0.866666666666666696,0.466666666666666674,0.533333333333333326],"xyz":[0.408581261120565364,0.303454305706755301,0.269987980927983207],"hpluv":[3.33484257213660307,141.112101622994089,61.9511315612573],"hsluv":[3.33484257213660307,59.8177792812577849,61.9511315612573]},"#dd7799":{"lch":[62.395798681375723,65.5359342889949659,352.534540584191575],"luv":[62.395798681375723,64.9804103256140309,-8.5149842657693231],"rgb":[0.866666666666666696,0.466666666666666674,0.6],"xyz":[0.421638225229638119,0.308677091350384458,0.338754658569101141],"hpluv":[352.534540584191575,133.27953643418,62.395798681375723],"hsluv":[352.534540584191575,60.8378233331756135,62.395798681375723]},"#dd77aa":{"lch":[62.9024183325334576,64.7691926058513587,340.409048511657261],"luv":[62.9024183325334576,61.0197310981447743,-21.7172909803219412],"rgb":[0.866666666666666696,0.466666666666666674,0.66666666666666663],"xyz":[0.43669591819489062,0.314700168536485581,0.418058508186099742],"hpluv":[340.409048511657261,130.659342151792146,62.9024183325334576],"hsluv":[340.409048511657261,61.8795186032410598,62.9024183325334576]},"#dd77bb":{"lch":[63.4712121738611472,67.0588140204747,328.168414880738283],"luv":[63.4712121738611472,56.973307333119962,-35.3684434115847779],"rgb":[0.866666666666666696,0.466666666666666674,0.733333333333333282],"xyz":[0.453833510302227294,0.321555205379420339,0.508316493284741489],"hpluv":[328.168414880738283,134.065922948238125,63.4712121738611472],"hsluv":[328.168414880738283,62.9156692244191049,63.4712121738611472]},"#dd77cc":{"lch":[64.1019320742502856,72.2937871392105649,317.045265551381931],"luv":[64.1019320742502856,52.9112642996390861,-49.2624580095555586],"rgb":[0.866666666666666696,0.466666666666666674,0.8],"xyz":[0.473125885745949049,0.329272155556909141,0.609923003955011644],"hpluv":[317.045265551381931,143.109736775843601,64.1019320742502856],"hsluv":[317.045265551381931,63.9218174738772262,64.1019320742502856]},"#dd77dd":{"lch":[64.7939046430230547,79.9242419089019904,307.715012949244624],"luv":[64.7939046430230547,48.8924033620837761,-63.225132172198613],"rgb":[0.866666666666666696,0.466666666666666674,0.866666666666666696],"xyz":[0.494644211296582537,0.337879485777162647,0.723252851855017642],"hpluv":[307.715012949244624,156.524995415635317,64.7939046430230547],"hsluv":[307.715012949244624,64.8769154463944,64.7939046430230547]},"#dd77ee":{"lch":[65.5460776792256,89.2673698488479772,300.244676230176196],"luv":[65.5460776792256,44.9634126652007424,-77.116501743983946],"rgb":[0.866666666666666696,0.466666666666666674,0.933333333333333348],"xyz":[0.518456391562757357,0.347404357883632664,0.848663667923540865],"hpluv":[300.244676230176196,172.816560464305326,65.5460776792256],"hsluv":[300.244676230176196,82.07388698168063,65.5460776792256]},"#dd77ff":{"lch":[66.3570680439545,99.7204006423648366,294.377473048092611],"luv":[66.3570680439545,41.1592308753033436,-90.8299290874281837],"rgb":[0.866666666666666696,0.466666666666666674,1],"xyz":[0.544627439972588667,0.357872777247565388,0.98649785621532271],"hpluv":[294.377473048092611,190.693615319177383,66.3570680439545],"hsluv":[294.377473048092611,99.9999999999983373,66.3570680439545]},"#dd8800":{"lch":[64.1467534130096766,94.4821411478558701,41.4445641191571781],"luv":[64.1467534130096766,70.8234804869986192,62.5372657508392],"rgb":[0.866666666666666696,0.533333333333333326,0],"xyz":[0.386219021295729159,0.329825201336773954,0.0433231605406526124],"hpluv":[41.4445641191571781,186.902182331454583,64.1467534130096766],"hsluv":[41.4445641191571781,100.000000000002416,64.1467534130096766]},"#dd8811":{"lch":[64.1795176641247593,93.3565504581455,40.9755438084832377],"luv":[64.1795176641247593,70.4832194968749235,61.2173282886440333],"rgb":[0.866666666666666696,0.533333333333333326,0.0666666666666666657],"xyz":[0.387230686795366263,0.330229867536628807,0.0486512655054082421],"hpluv":[40.9755438084832377,184.581288668542754,64.1795176641247593],"hsluv":[40.9755438084832377,98.0366401848460072,64.1795176641247593]},"#dd8822":{"lch":[64.2401831232140665,91.3094921720035444,40.0852847486271529],"luv":[64.2401831232140665,69.8596876954725303,58.7966614341343359],"rgb":[0.866666666666666696,0.533333333333333326,0.133333333333333331],"xyz":[0.38910604493384332,0.330980010792019608,0.0585281517013874417],"hpluv":[40.0852847486271529,180.363429125237786,64.2401831232140665],"hsluv":[40.0852847486271529,94.440421143342,64.2401831232140665]},"#dd8833":{"lch":[64.3398685532121704,88.0506151089842319,38.5586382769189342],"luv":[64.3398685532121704,68.8529964065635,54.8832916916272],"rgb":[0.866666666666666696,0.533333333333333326,0.2],"xyz":[0.392193795666301,0.332215111085002734,0.0747903055589985849],"hpluv":[38.5586382769189342,173.656703033806934,64.3398685532121704],"hsluv":[38.5586382769189342,88.6389494712849171,64.3398685532121704]},"#dd8844":{"lch":[64.4833562447751376,83.590391105324656,36.2144134416389392],"luv":[64.4833562447751376,67.4417066096926305,49.3859260793323571],"rgb":[0.866666666666666696,0.533333333333333326,0.266666666666666663],"xyz":[0.396651790910519941,0.333998309182690301,0.0982690805118852234],"hpluv":[36.2144134416389392,164.493238171948974,64.4833562447751376],"hsluv":[36.2144134416389392,80.515719620269337,64.4833562447751376]},"#dd8855":{"lch":[64.6744699451661234,78.0792033713498768,32.8038905377638059],"luv":[64.6744699451661234,65.6278986079903746,42.3006019272060527],"rgb":[0.866666666666666696,0.533333333333333326,0.333333333333333315],"xyz":[0.402614204949881926,0.336383274798435161,0.129671127785859125],"hpluv":[32.8038905377638059,153.194023310958244,64.6744699451661234],"hsluv":[32.8038905377638059,70.0933757373770874,64.6744699451661234]},"#dd8866":{"lch":[64.9162913059566762,71.8298791231791114,27.9793586637294176],"luv":[64.9162913059566762,63.4341634455056251,33.69923504798588],"rgb":[0.866666666666666696,0.533333333333333326,0.4],"xyz":[0.410199200723390112,0.33941727310783848,0.169618772193003087],"hpluv":[27.9793586637294176,140.40764710249897,64.9162913059566762],"hsluv":[27.9793586637294176,57.516131894534638,64.9162913059566762]},"#dd8877":{"lch":[65.211273186305,65.3549274640793243,21.2774014292581],"luv":[65.211273186305,60.8999714810578325,23.7162395299384023],"rgb":[0.866666666666666696,0.533333333333333326,0.466666666666666674],"xyz":[0.419513213743592,0.343142878315919275,0.218672574099401],"hpluv":[21.2774014292581,127.173019810527066,65.211273186305],"hsluv":[21.2774014292581,55.3192807564291797,65.211273186305]},"#dd8888":{"lch":[65.5613077882642585,59.4140252364257222,12.17705063006205],"luv":[65.5613077882642585,58.0772370198051675,12.5323954190731097],"rgb":[0.866666666666666696,0.533333333333333326,0.533333333333333326],"xyz":[0.430653630845535818,0.347599045156696873,0.277345437502973191],"hpluv":[12.17705063006205,114.995459986814794,65.5613077882642585],"hsluv":[12.17705063006205,56.217054427929,65.5613077882642585]},"#dd8899":{"lch":[65.9677735951440809,55.0265961591163304,0.371909902430330563],"luv":[65.9677735951440809,55.0254369244830599,0.357178006339119947],"rgb":[0.866666666666666696,0.533333333333333326,0.6],"xyz":[0.443710594954608517,0.352821830800326031,0.34611211514409107],"hpluv":[0.371909902430330563,105.847388780281833,65.9677735951440809],"hsluv":[0.371909902430330563,57.1692646853200088,65.9677735951440809]},"#dd88aa":{"lch":[66.4315725960282,53.3143351650078756,346.342357286170909],"luv":[66.4315725960282,51.8068159744011751,-12.5885723051236678],"rgb":[0.866666666666666696,0.533333333333333326,0.66666666666666663],"xyz":[0.458768287919861073,0.358844907986427153,0.425415964761089671],"hpluv":[346.342357286170909,101.837748961685463,66.4315725960282],"hsluv":[346.342357286170909,58.1497775423036174,66.4315725960282]},"#dd88bb":{"lch":[66.9531637379342754,55.0554122119122695,331.715662007953938],"luv":[66.9531637379342754,48.482176665295448,-26.0878699709014299],"rgb":[0.866666666666666696,0.533333333333333326,0.733333333333333282],"xyz":[0.475905880027197747,0.365699944829361911,0.515673949859731473],"hpluv":[331.715662007953938,104.344182679937973,66.9531637379342754],"hsluv":[331.715662007953938,59.1328526037280824,66.9531637379342754]},"#dd88cc":{"lch":[67.5325957217288391,60.2482932018326949,318.477460357427788],"luv":[67.5325957217288391,45.1075955230381,-39.9394749572899741],"rgb":[0.866666666666666696,0.533333333333333326,0.8],"xyz":[0.495198255470919446,0.373416895006850713,0.617280460530001629],"hpluv":[318.477460357427788,113.206309259525241,67.5325957217288391],"hsluv":[318.477460357427788,60.0943355072680134,67.5325957217288391]},"#dd88dd":{"lch":[68.1695406599531566,68.219499196860184,307.715012949245079],"luv":[68.1695406599531566,41.7322103060295,-53.965940125636024],"rgb":[0.866666666666666696,0.533333333333333326,0.866666666666666696],"xyz":[0.516716581021553,0.382024225227104219,0.730610308430007627],"hpluv":[307.715012949245079,126.986480244117786,68.1695406599531566],"hsluv":[307.715012949245079,61.0124281772922785,68.1695406599531566]},"#dd88ee":{"lch":[68.8633291469121,78.1074456653267504,299.445313850889818],"luv":[68.8633291469121,38.3970440138518896,-68.0179393936650456],"rgb":[0.866666666666666696,0.533333333333333326,0.933333333333333348],"xyz":[0.540528761287727755,0.391549097333574236,0.85602112449853085],"hpluv":[299.445313850889818,143.92748858218269,68.8633291469121],"hsluv":[299.445313850889818,80.0342386911036385,68.8633291469121]},"#dd88ff":{"lch":[69.6129866887261244,89.1875622693819281,293.199992160066699],"luv":[69.6129866887261244,35.1347073752451706,-81.975445111391565],"rgb":[0.866666666666666696,0.533333333333333326,1],"xyz":[0.566699809697559176,0.40201751669750696,0.993855312790312695],"hpluv":[293.199992160066699,162.574846632166981,69.6129866887261244],"hsluv":[293.199992160066699,99.9999999999981242,69.6129866887261244]},"#dd9900":{"lch":[68.1357569139589287,89.1370219700488775,50.3810095729648921],"luv":[68.1357569139589287,56.8408371488252229,68.6624199829113735],"rgb":[0.866666666666666696,0.6,0],"xyz":[0.41208862174819666,0.381564402241709621,0.0519463606914748674],"hpluv":[50.3810095729648921,166.005456049637957,68.1357569139589287],"hsluv":[50.3810095729648921,100.000000000002245,68.1357569139589287]},"#dd9911":{"lch":[68.1654896561650077,88.0318570495471278,50.0002977758109424],"luv":[68.1654896561650077,56.5854364905009248,67.4366089951992507],"rgb":[0.866666666666666696,0.6,0.0666666666666666657],"xyz":[0.413100287247833764,0.381969068441564474,0.0572744656562305],"hpluv":[50.0002977758109424,163.875726960716179,68.1654896561650077],"hsluv":[50.0002977758109424,98.2940371374324826,68.1654896561650077]},"#dd9922":{"lch":[68.2205507365204,86.012937831153792,49.27558445879027],"luv":[68.2205507365204,56.1166821435168472,65.1854543556261774],"rgb":[0.866666666666666696,0.6,0.133333333333333331],"xyz":[0.414975645386310821,0.382719211696955275,0.0671513518522096897],"hpluv":[49.27558445879027,159.988175949224541,68.2205507365204],"hsluv":[49.27558445879027,95.1643810214000325,68.2205507365204]},"#dd9933":{"lch":[68.3110514885433133,82.7730333151944677,48.0261582829398179],"luv":[68.3110514885433133,55.3578808500206918,61.5376313485756867],"rgb":[0.866666666666666696,0.6,0.2],"xyz":[0.418063396118768527,0.383954311989938402,0.0834135057098208399],"hpluv":[48.0261582829398179,153.757824966526499,68.3110514885433133],"hsluv":[48.0261582829398179,90.1021642959546512,68.3110514885433133]},"#dd9944":{"lch":[68.4413718194248,78.2806718730308688,46.0898611008159875],"luv":[68.4413718194248,54.2899414547091936,56.3956190296495805],"rgb":[0.866666666666666696,0.6,0.266666666666666663],"xyz":[0.422521391362987442,0.385737510087625968,0.106892280662707478],"hpluv":[46.0898611008159875,145.136005207448051,68.4413718194248],"hsluv":[46.0898611008159875,82.9860784920926449,68.4413718194248]},"#dd9955":{"lch":[68.615044439797245,72.617894762441864,43.2298844404699452],"luv":[68.615044439797245,52.9102318436571935,49.7379734808277689],"rgb":[0.866666666666666696,0.6,0.333333333333333315],"xyz":[0.428483805402349427,0.388122475703370828,0.138294327936681394],"hpluv":[43.2298844404699452,134.296171447129637,68.615044439797245],"hsluv":[43.2298844404699452,73.8078183466747362,68.615044439797245]},"#dd9966":{"lch":[68.8349542760461333,65.9999398631214262,39.0839575312566296],"luv":[68.8349542760461333,51.2306688139135815,41.6102227200810404],"rgb":[0.866666666666666696,0.6,0.4],"xyz":[0.436068801175857612,0.391156474012774147,0.178241972343825356],"hpluv":[39.0839575312566296,121.667290932950721,68.8349542760461333],"hsluv":[39.0839575312566296,62.6592399312796076,68.8349542760461333]},"#dd9977":{"lch":[69.1034430542988929,58.8170492635600723,33.0939178586399834],"luv":[69.1034430542988929,49.2755524050587468,32.1148753578207291],"rgb":[0.866666666666666696,0.6,0.466666666666666674],"xyz":[0.445382814196059518,0.394882079220854942,0.227295774250223265],"hpluv":[33.0939178586399834,108.004754244465147,69.1034430542988929],"hsluv":[33.0939178586399834,50.7393036295826789,69.1034430542988929]},"#dd9988":{"lch":[69.4223715869125328,51.7141196395305656,24.4437159362198635],"luv":[69.4223715869125328,47.0788901370553745,21.3992587150758773],"rgb":[0.866666666666666696,0.6,0.533333333333333326],"xyz":[0.456523231298003318,0.39933824606163254,0.285968637653795432],"hpluv":[24.4437159362198635,94.5255072345235305,69.4223715869125328],"hsluv":[24.4437159362198635,51.5589665528693786,69.4223715869125328]},"#dd9999":{"lch":[69.7931614924381591,45.7097919546320597,12.1770506300622632],"luv":[69.7931614924381591,44.6813426781189875,9.64171649740066705],"rgb":[0.866666666666666696,0.6,0.6],"xyz":[0.469580195407076073,0.404561031705261698,0.354735315294913311],"hpluv":[12.1770506300622632,83.1066353202273689,69.7931614924381591],"hsluv":[12.1770506300622632,52.4335711570844438,69.7931614924381591]},"#dd99aa":{"lch":[70.2168268189972196,42.2311183685245624,355.977290330288042],"luv":[70.2168268189972196,42.1270745474506256,-2.96259155636920823],"rgb":[0.866666666666666696,0.6,0.66666666666666663],"xyz":[0.484637888372328574,0.41058410889136282,0.434039164911911912],"hpluv":[355.977290330288042,76.3186553239153369,70.2168268189972196],"hsluv":[355.977290330288042,53.3392978779897788,70.2168268189972196]},"#dd99bb":{"lch":[70.694001085559151,42.6623948649138427,337.661645838825223],"luv":[70.694001085559151,39.4608168046039651,-16.2149274658665163],"rgb":[0.866666666666666696,0.6,0.733333333333333282],"xyz":[0.501775480479665248,0.417439145734297579,0.524297150010553659],"hpluv":[337.661645838825223,76.5776430619999502,70.694001085559151],"hsluv":[337.661645838825223,54.2517942883204398,70.694001085559151]},"#dd99cc":{"lch":[71.2249627580945912,47.3730793907595,320.82657583403028],"luv":[71.2249627580945912,36.7253906259007294,-29.9241430008982121],"rgb":[0.866666666666666696,0.6,0.8],"xyz":[0.521067855923387,0.42515609591178638,0.625903660680823815],"hpluv":[320.82657583403028,84.3992741769230719,71.2249627580945912],"hsluv":[320.82657583403028,55.1472527266246075,71.2249627580945912]},"#dd99dd":{"lch":[71.8096607795551876,55.5140341348433424,307.715012949246],"luv":[71.8096607795551876,33.9598410238411077,-43.9151133843462844],"rgb":[0.866666666666666696,0.6,0.866666666666666696],"xyz":[0.54258618147402049,0.433763426132039887,0.739233508580829812],"hpluv":[307.715012949246,98.0977936102115677,71.8096607795551876],"hsluv":[307.715012949246,56.0031828352742878,71.8096607795551876]},"#dd99ee":{"lch":[72.447740927007942,65.8885644567307907,298.26171852853804],"luv":[72.447740927007942,31.1982236173053273,-58.0342465212858656],"rgb":[0.866666666666666696,0.6,0.933333333333333348],"xyz":[0.566398361740195311,0.443288298238509904,0.864644324649353],"hpluv":[298.26171852853804,115.404973126410823,72.447740927007942],"hsluv":[298.26171852853804,77.3800261839098908,72.447740927007942]},"#dd99ff":{"lch":[73.1385732331520302,77.5656763031263,291.532620718417377],"luv":[73.1385732331520302,28.4689992262389602,-72.1522710898123592],"rgb":[0.866666666666666696,0.6,1],"xyz":[0.592569410150026621,0.453756717602442627,1.00247851294113488],"hpluv":[291.532620718417377,134.574391894016571,73.1385732331520302],"hsluv":[291.532620718417377,99.9999999999977689,73.1385732331520302]},"#cc0000":{"lch":[42.5207510295766156,142.998625281495549,12.1770506300617818],"luv":[42.5207510295766156,139.781222041964895,30.163169542547891],"rgb":[0.8,0,0],"xyz":[0.249012838889184379,0.128397245052238429,0.0116724768229302719],"hpluv":[12.1770506300617818,426.746789183124861,42.5207510295766156],"hsluv":[12.1770506300617818,100.000000000002174,42.5207510295766156]},"#cc0011":{"lch":[42.5821659889152784,141.236718626044905,11.4841194603559],"luv":[42.5821659889152784,138.409148973409117,28.119711390930437],"rgb":[0.8,0,0.0666666666666666657],"xyz":[0.250024504388821511,0.128801911252093282,0.0170005817876859033],"hpluv":[11.4841194603559,420.880880123779207,42.5821659889152784],"hsluv":[11.4841194603559,99.9999999999964331,42.5821659889152784]},"#cc0022":{"lch":[42.6956735686566518,138.114600243667155,10.1872609469282853],"luv":[42.6956735686566518,135.937217546775798,24.4277646564013864],"rgb":[0.8,0,0.133333333333333331],"xyz":[0.251899862527298513,0.12955205450748411,0.0268774679836651],"hpluv":[10.1872609469282853,410.482879191578036,42.6956735686566518],"hsluv":[10.1872609469282853,99.9999999999964615,42.6956735686566518]},"#cc0033":{"lch":[42.881611378965772,133.362165770655935,8.01952044887972626],"luv":[42.881611378965772,132.057963211529,18.6054188736068511],"rgb":[0.8,0,0.2],"xyz":[0.254987613259756274,0.130787154800467209,0.0431396218412762461],"hpluv":[8.01952044887972626,394.639788400466045,42.881611378965772],"hsluv":[8.01952044887972626,99.9999999999966604,42.881611378965772]},"#cc0044":{"lch":[43.1480085091585153,127.29097956278504,4.82801781999359658],"luv":[43.1480085091585153,126.839328429887985,10.7134607624411853],"rgb":[0.8,0,0.266666666666666663],"xyz":[0.259445608503975134,0.132570352898154803,0.0666183967941628846],"hpluv":[4.82801781999359658,374.34858804079829,43.1480085091585153],"hsluv":[4.82801781999359658,99.9999999999967741,43.1480085091585153]},"#cc0055":{"lch":[43.5005971125795,120.485699890795146,0.473888563816867114],"luv":[43.5005971125795,120.481578818580687,0.996515708296922487],"rgb":[0.8,0,0.333333333333333315],"xyz":[0.265408022543337119,0.134955318513899636,0.0980204440681367861],"hpluv":[0.473888563816867114,351.463000970195878,43.5005971125795],"hsluv":[0.473888563816867114,99.999999999997,43.5005971125795]},"#cc0066":{"lch":[43.9431844272177372,113.726547538665841,354.863826263116096],"luv":[43.9431844272177372,113.269906269789104,-10.1811565500985228],"rgb":[0.8,0,0.4],"xyz":[0.272993018316845304,0.137989316823302927,0.137968088475280748],"hpluv":[354.863826263116096,328.404920869645196,43.9431844272177372],"hsluv":[354.863826263116096,99.9999999999972857,43.9431844272177372]},"#cc0077":{"lch":[44.4778741065655,107.874648109024193,348.012259047653401],"luv":[44.4778741065655,105.522124609829902,-22.4058234053856609],"rgb":[0.8,0,0.466666666666666674],"xyz":[0.28230703133704721,0.14171492203138375,0.187021890381678657],"hpluv":[348.012259047653401,307.761788629886667,44.4778741065655],"hsluv":[348.012259047653401,99.9999999999975273,44.4778741065655]},"#cc0088":{"lch":[45.1052440924579,103.725434836726933,340.1176986346278],"luv":[45.1052440924579,97.5426962017022703,-35.2758876538989838],"rgb":[0.8,0,0.533333333333333326],"xyz":[0.293447448438991065,0.146171088872161348,0.245694753785250825],"hpluv":[340.1176986346278,291.808241377507443,45.1052440924579],"hsluv":[340.1176986346278,99.9999999999978,45.1052440924579]},"#cc0099":{"lch":[45.8245205562958589,101.850048541314862,331.598662995615],"luv":[45.8245205562958589,89.5911194129305102,-48.4444394147173441],"rgb":[0.8,0,0.6],"xyz":[0.306504412548063765,0.151393874515790505,0.314461431426368732],"hpluv":[331.598662995615,282.034759885138044,45.8245205562958589],"hsluv":[331.598662995615,99.9999999999981,45.8245205562958589]},"#cc00aa":{"lch":[46.633760692471931,102.477609530343315,323.022725489580409],"luv":[46.633760692471931,81.8667129915779,-61.640098629123905],"rgb":[0.8,0,0.66666666666666663],"xyz":[0.321562105513316321,0.1574169517018916,0.393765281043367332],"hpluv":[323.022725489580409,278.848217687739293,46.633760692471931],"hsluv":[323.022725489580409,99.9999999999983658,46.633760692471931]},"#cc00bb":{"lch":[47.5300446684938933,105.484027274260768,314.937463984289479],"luv":[47.5300446684938933,74.5070162947061903,-74.6698368342758414],"rgb":[0.8,0,0.733333333333333282],"xyz":[0.33869969762065294,0.164271988544826358,0.484023266142009079],"hpluv":[314.937463984289479,281.616311803476265,47.5300446684938933],"hsluv":[314.937463984289479,99.9999999999986,47.5300446684938933]},"#cc00cc":{"lch":[48.5096711653281147,110.497164945278598,307.715012949243601],"luv":[48.5096711653281147,67.5949102529980621,-87.4102486487325],"rgb":[0.8,0,0.8],"xyz":[0.357992073064374694,0.17198893872231516,0.585629776812279235],"hpluv":[307.715012949243601,289.042783730483393,48.5096711653281147],"hsluv":[307.715012949243601,99.9999999999988,48.5096711653281147]},"#cc00dd":{"lch":[49.5683488162236614,117.049051317219835,301.506761454082039],"luv":[49.5683488162236614,61.1697383356450075,-99.7935044289451],"rgb":[0.8,0,0.866666666666666696],"xyz":[0.379510398615008238,0.180596268942568694,0.698959624712285232],"hpluv":[301.506761454082039,299.64205877637869,49.5683488162236614],"hsluv":[301.506761454082039,99.9999999999990337,49.5683488162236614]},"#cc00ee":{"lch":[50.7013760136427862,124.695255359169607,296.294949026353493],"luv":[50.7013760136427862,55.2390203059142522,-111.792474454818787],"rgb":[0.8,0,0.933333333333333348],"xyz":[0.403322578881182947,0.190121141049038739,0.824370440780808456],"hpluv":[296.294949026353493,312.082566880879938,50.7013760136427862],"hsluv":[296.294949026353493,99.99999999999919,50.7013760136427862]},"#cc00ff":{"lch":[51.9038030272213,133.072735088441448,291.971633700566258],"luv":[51.9038030272213,49.7888328026579075,-123.407556300526],"rgb":[0.8,0,1],"xyz":[0.429493627291014368,0.200589560412971435,0.962204629072590301],"hpluv":[291.971633700566258,325.333832743425603,51.9038030272213],"hsluv":[291.971633700566258,99.9999999999993321,51.9038030272213]},"#cc1100":{"lch":[43.1235624482234172,140.134259476931788,12.8715382160273855],"luv":[43.1235624482234172,136.61296213687416,31.2171307992428524],"rgb":[0.8,0.0666666666666666657,0],"xyz":[0.251017239150112814,0.132406045574095299,0.0123406102432397219],"hpluv":[12.8715382160273855,412.352867097941,43.1235624482234172],"hsluv":[12.8715382160273855,100.000000000002245,43.1235624482234172]},"#cc1111":{"lch":[43.1837333530957892,138.41807101963343,12.1770506300617676],"luv":[43.1837333530957892,135.303728142340162,29.196978192614182],"rgb":[0.8,0.0666666666666666657,0.0666666666666666657],"xyz":[0.252028904649749919,0.132810711773950152,0.0176687152079953516],"hpluv":[12.1770506300617676,406.735363437937394,43.1837333530957892],"hsluv":[12.1770506300617676,95.3107026807431197,43.1837333530957892]},"#cc1122":{"lch":[43.294951674171287,135.375047793376126,10.8766574447476163],"luv":[43.294951674171287,132.943125696930394,25.5446451333550044],"rgb":[0.8,0.0666666666666666657,0.133333333333333331],"xyz":[0.253904262788227,0.13356085502934098,0.0275456014039745511],"hpluv":[10.8766574447476163,396.771701832449367,43.294951674171287],"hsluv":[10.8766574447476163,95.396390587568618,43.294951674171287]},"#cc1133":{"lch":[43.4771672841157724,130.73841758888139,8.7012157385065958],"luv":[43.4771672841157724,129.233706917218683,19.7783424502448213],"rgb":[0.8,0.0666666666666666657,0.2],"xyz":[0.256992013520684681,0.134795955322324079,0.0438077552615856944],"hpluv":[8.7012157385065958,381.576227833431062,43.4771672841157724],"hsluv":[8.7012157385065958,95.5308510527687389,43.4771672841157724]},"#cc1144":{"lch":[43.7382910834512586,124.807582872189826,5.49437317543092796],"luv":[43.7382910834512586,124.234167693991893,11.9500761411646046],"rgb":[0.8,0.0666666666666666657,0.266666666666666663],"xyz":[0.261450008764903596,0.136579153420011673,0.0672865302144723398],"hpluv":[5.49437317543092796,362.091632024479338,43.7382910834512586],"hsluv":[5.49437317543092796,95.7116850897169229,43.7382910834512586]},"#cc1155":{"lch":[44.0840061747103107,118.151091154502552,1.11194247693657511],"luv":[44.0840061747103107,118.128842001262925,2.29282106589917234],"rgb":[0.8,0.0666666666666666657,0.333333333333333315],"xyz":[0.267412422804265582,0.138964119035756506,0.0986885774884462413],"hpluv":[1.11194247693657511,340.091681007194666,44.0840061747103107],"hsluv":[1.11194247693657511,95.9318436200579754,44.0840061747103107]},"#cc1166":{"lch":[44.5181325219627837,111.53522478210337,355.453854482233226],"luv":[44.5181325219627837,111.184314904818763,-8.84050260677650712],"rgb":[0.8,0.0666666666666666657,0.4],"xyz":[0.274997418577773767,0.141998117345159797,0.138636221895590217],"hpluv":[355.453854482233226,317.917500588946211,44.5181325219627837],"hsluv":[355.453854482233226,96.1812476973727115,44.5181325219627837]},"#cc1177":{"lch":[45.0428415016287857,105.814757555455103,348.528515458334311],"luv":[45.0428415016287857,103.700981473984271,-21.0444614531261323],"rgb":[0.8,0.0666666666666666657,0.466666666666666674],"xyz":[0.284311431597975672,0.14572372255324062,0.187690023801988126],"hpluv":[348.528515458334311,298.098498005115459,45.0428415016287857],"hsluv":[348.528515458334311,96.4486017572014589,45.0428415016287857]},"#cc1188":{"lch":[45.6588256994622341,101.788242110562607,340.533613155211185],"luv":[45.6588256994622341,95.9697371205835594,-33.9213176183443323],"rgb":[0.8,0.0666666666666666657,0.533333333333333326],"xyz":[0.295451848699919473,0.150179889394018218,0.246362887205560266],"hpluv":[340.533613155211185,282.886487347344485,45.6588256994622341],"hsluv":[340.533613155211185,96.7230145871901215,45.6588256994622341]},"#cc1199":{"lch":[46.3654632546324876,100.035938879036408,331.896400713626349],"luv":[46.3654632546324876,88.2414291467186,-47.1236591273953138],"rgb":[0.8,0.0666666666666666657,0.6],"xyz":[0.308508812808992228,0.155402675037647375,0.315129564846678201],"hpluv":[331.896400713626349,273.779405248492822,46.3654632546324876],"hsluv":[331.896400713626349,96.9951405388504355,46.3654632546324876]},"#cc11aa":{"lch":[47.1609900317596882,100.794975336059878,323.201580807901109],"luv":[47.1609900317596882,80.7113646178594735,-60.3763420100941488],"rgb":[0.8,0.0666666666666666657,0.66666666666666663],"xyz":[0.323566505774244728,0.16142575222374847,0.394433414463676801],"hpluv":[323.201580807901109,271.203503720358924,47.1609900317596882],"hsluv":[323.201580807901109,97.2577546113476075,47.1609900317596882]},"#cc11bb":{"lch":[48.0426807208370548,103.942897448445919,315.013990059648165],"luv":[48.0426807208370548,73.5166718264647727,-73.4807790754699681],"rgb":[0.8,0.0666666666666666657,0.733333333333333282],"xyz":[0.340704097881581403,0.168280789066683228,0.484691399562318548],"hpluv":[315.013990059648165,274.540811344802705,48.0426807208370548],"hsluv":[315.013990059648165,97.5058443216483,48.0426807208370548]},"#cc11cc":{"lch":[49.0070341259591515,109.103198367120783,307.715012949243601],"luv":[49.0070341259591515,66.7421730285370387,-86.3075328888743769],"rgb":[0.8,0.0666666666666666657,0.8],"xyz":[0.359996473325303157,0.17599773924417203,0.586297910232588704],"hpluv":[307.715012949243601,282.499958642668389,49.0070341259591515],"hsluv":[307.715012949243601,97.7363817897909541,49.0070341259591515]},"#cc11dd":{"lch":[50.0499556366759037,115.801352096543823,301.456118533327128],"luv":[50.0499556366759037,60.4304023076134555,-98.7831950502093292],"rgb":[0.8,0.0666666666666666657,0.866666666666666696],"xyz":[0.381514798875936645,0.184605069464425564,0.699627758132594701],"hpluv":[301.456118533327128,293.595408819402792,50.0499556366759037],"hsluv":[301.456118533327128,97.9479403979501342,50.0499556366759037]},"#cc11ee":{"lch":[51.1669298024285837,123.587345912593733,296.214453457233276],"luv":[51.1669298024285837,54.5925079172689109,-110.876012505059137],"rgb":[0.8,0.0666666666666666657,0.933333333333333348],"xyz":[0.40532697914211141,0.194129941570895609,0.825038574201117925],"hpluv":[296.214453457233276,306.495409047480564,51.1669298024285837],"hsluv":[296.214453457233276,98.1402788193950215,51.1669298024285837]},"#cc11ff":{"lch":[52.3531771468210678,132.094610043027387,291.87590029388349],"luv":[52.3531771468210678,49.2181193296501576,-122.582881072651375],"rgb":[0.8,0.0666666666666666657,1],"xyz":[0.431498027551942775,0.204598360934828305,0.96287276249289977],"hpluv":[291.87590029388349,320.170549145207644,52.3531771468210678],"hsluv":[291.87590029388349,99.9999999999991189,52.3531771468210678]},"#cc2200":{"lch":[44.2095884480383674,135.132222138307952,14.1797238149512133],"luv":[44.2095884480383674,131.015027118873576,33.102569825888537],"rgb":[0.8,0.133333333333333331,0],"xyz":[0.254732862884880729,0.139837293043631267,0.0135791514881623328],"hpluv":[14.1797238149512133,387.866054960954045,44.2095884480383674],"hsluv":[14.1797238149512133,100.00000000000226,44.2095884480383674]},"#cc2211":{"lch":[44.2676114068871129,133.490625771231663,13.482935392010976],"luv":[44.2676114068871129,129.81154471696064,31.1241068464871375],"rgb":[0.8,0.133333333333333331,0.0666666666666666657],"xyz":[0.255744528384517833,0.14024195924348612,0.0189072564529179643],"hpluv":[13.482935392010976,382.652016671578622,44.2676114068871129],"hsluv":[13.482935392010976,95.5414705532830197,44.2676114068871129]},"#cc2222":{"lch":[44.3748759613401162,130.576558981694717,12.1770506300617747],"luv":[44.3748759613401162,127.638646515421286,27.5429423121666019],"rgb":[0.8,0.133333333333333331,0.133333333333333331],"xyz":[0.25761988652299489,0.140992102498876948,0.0287841426488971604],"hpluv":[12.1770506300617747,373.394050741154899,44.3748759613401162],"hsluv":[12.1770506300617747,87.4977996802062847,44.3748759613401162]},"#cc2233":{"lch":[44.5506596541482907,126.128431434884163,9.98899557195718657],"luv":[44.5506596541482907,124.216461430809,21.8781152257824267],"rgb":[0.8,0.133333333333333331,0.2],"xyz":[0.260707637255452596,0.142227202791860047,0.0450462965065083071],"hpluv":[9.98899557195718657,359.251162664680805,44.5506596541482907],"hsluv":[9.98899557195718657,87.8457930667704829,44.5506596541482907]},"#cc2244":{"lch":[44.8026641682027602,120.425301369152351,6.75586226508684629],"luv":[44.8026641682027602,119.589119356054482,14.1667124448306829],"rgb":[0.8,0.133333333333333331,0.266666666666666663],"xyz":[0.265165632499671511,0.144010400889547641,0.0685250714593949456],"hpluv":[6.75586226508684629,341.077623618219945,44.8026641682027602],"hsluv":[6.75586226508684629,88.3153597242729,44.8026641682027602]},"#cc2255":{"lch":[45.136480402373536,114.008772215671115,2.323188749975583],"luv":[45.136480402373536,113.915065115628011,4.62148047999889577],"rgb":[0.8,0.133333333333333331,0.333333333333333315],"xyz":[0.271128046539033496,0.146395366505292474,0.0999271187333688471],"hpluv":[2.323188749975583,320.51614012782295,45.136480402373536],"hsluv":[2.323188749975583,88.8894607126765663,45.136480402373536]},"#cc2266":{"lch":[45.5559407124691731,107.622008592458101,356.577515499379103],"luv":[45.5559407124691731,107.430062323442542,-6.42483016556681452],"rgb":[0.8,0.133333333333333331,0.4],"xyz":[0.278713042312541681,0.149429364814695764,0.139874763140512809],"hpluv":[356.577515499379103,299.774992396016216,45.5559407124691731],"hsluv":[356.577515499379103,89.543058875786258,45.5559407124691731]},"#cc2277":{"lch":[46.0633224094211542,102.110102175352239,349.514816334356908],"luv":[46.0633224094211542,100.405067655344695,-18.5821246198201955],"rgb":[0.8,0.133333333333333331,0.466666666666666674],"xyz":[0.288027055332743587,0.153154970022776588,0.188928565046910718],"hpluv":[349.514816334356908,281.289018816444,46.0633224094211542],"hsluv":[349.514816334356908,90.2475430222303174,46.0633224094211542]},"#cc2288":{"lch":[46.6595045299101443,98.2790088680822294,341.330158110485115],"luv":[46.6595045299101443,93.107459692013677,-31.4605234189234864],"rgb":[0.8,0.133333333333333331,0.533333333333333326],"xyz":[0.299167472434687387,0.157611136863554185,0.247601428450482886],"hpluv":[341.330158110485115,267.276005324070297,46.6595045299101443],"hsluv":[341.330158110485115,90.9748001704168,46.6595045299101443]},"#cc2299":{"lch":[47.3441166442187154,96.727251962869147,332.467163788405],"luv":[47.3441166442187154,85.772509513995189,-44.7128380217581167],"rgb":[0.8,0.133333333333333331,0.6],"xyz":[0.312224436543760087,0.162833922507183343,0.31636810609160082],"hpluv":[332.467163788405,259.252025571181889,47.3441166442187154],"hsluv":[332.467163788405,91.7002075152857259,47.3441166442187154]},"#cc22aa":{"lch":[48.1156936783416285,97.7076198445846131,323.544141865814368],"luv":[48.1156936783416285,78.5876931080813534,-58.0581903579838254],"rgb":[0.8,0.133333333333333331,0.66666666666666663],"xyz":[0.327282129509012643,0.168856999693284437,0.395671955708599421],"hpluv":[323.544141865814368,257.680176436907345,48.1156936783416285],"hsluv":[323.544141865814368,92.4042695013683328,48.1156936783416285]},"#cc22bb":{"lch":[48.9718390817589295,101.100583268181566,315.160199675634601],"luv":[48.9718390817589295,71.6885121422158846,-71.2887450022924867],"rgb":[0.8,0.133333333333333331,0.733333333333333282],"xyz":[0.344419721616349317,0.175712036536219196,0.485929940807241167],"hpluv":[315.160199675634601,261.96699229126267,48.9718390817589295],"hsluv":[315.160199675634601,93.0730408422972602,48.9718390817589295]},"#cc22cc":{"lch":[49.9093929354593513,106.520702596514482,307.715012949243658],"luv":[49.9093929354593513,65.1623716831421405,-84.2646153393173876],"rgb":[0.8,0.133333333333333331,0.8],"xyz":[0.363712097060071,0.183428986713708,0.587536451477511323],"hpluv":[307.715012949243658,270.826440261226253,49.9093929354593513],"hsluv":[307.715012949243658,93.6976999618693327,49.9093929354593513]},"#cc22dd":{"lch":[50.9245991417877377,113.480326695625976,301.360030221234524],"luv":[50.9245991417877377,59.0567580516805819,-96.9024451465042347],"rgb":[0.8,0.133333333333333331,0.866666666666666696],"xyz":[0.385230422610704615,0.192036316933961532,0.700866299377517321],"hpluv":[301.360030221234524,282.769318292038,50.9245991417877377],"hsluv":[301.360030221234524,94.2736696463096848,50.9245991417877377]},"#cc22ee":{"lch":[52.0132654143591964,121.518329969744158,296.062236941936249],"luv":[52.0132654143591964,53.3887372455597244,-109.162022947361166],"rgb":[0.8,0.133333333333333331,0.933333333333333348],"xyz":[0.409042602876879324,0.201561189040431576,0.826277115446040544],"hpluv":[296.062236941936249,296.460610812118318,52.0132654143591964],"hsluv":[296.062236941936249,94.7995997265514,52.0132654143591964]},"#cc22ff":{"lch":[53.170910599170611,130.261070707018604,291.695402941657903],"luv":[53.170910599170611,48.1538976563848422,-121.033667556747687],"rgb":[0.8,0.133333333333333331,1],"xyz":[0.435213651286710745,0.212029608404364273,0.964111303737822389],"hpluv":[291.695402941657903,310.870757963074425,53.170910599170611],"hsluv":[291.695402941657903,99.9999999999990621,53.170910599170611]},"#cc3300":{"lch":[45.9167915379707807,127.686226573765651,16.3911473443809399],"luv":[45.9167915379707807,122.496750231663356,36.0321889333482446],"rgb":[0.8,0.2,0],"xyz":[0.260850584973891519,0.15207273722165307,0.0156183921844992128],"hpluv":[16.3911473443809399,352.867650162608584,45.9167915379707807],"hsluv":[16.3911473443809399,100.000000000002288,45.9167915379707807]},"#cc3311":{"lch":[45.9716631772740811,126.14350976506573,15.6923563051461095],"luv":[45.9716631772740811,121.44186843726601,34.118289029712578],"rgb":[0.8,0.2,0.0666666666666666657],"xyz":[0.261862250473528624,0.152477403421507923,0.020946497149254846],"hpluv":[15.6923563051461095,348.188177538365835,45.9716631772740811],"hsluv":[15.6923563051461095,95.8756509753329595,45.9716631772740811]},"#cc3322":{"lch":[46.0731243265426613,123.399991281457972,14.3806932854006604],"luv":[46.0731243265426613,119.533487768482047,30.6480529588105668],"rgb":[0.8,0.2,0.133333333333333331],"xyz":[0.263737608612005681,0.153227546676898752,0.0308233833452340386],"hpluv":[14.3806932854006604,339.865273437066321,46.0731243265426613],"hsluv":[14.3806932854006604,88.4197842587317524,46.0731243265426613]},"#cc3333":{"lch":[46.2394596481243951,119.199958247304309,12.1770506300617924],"luv":[46.2394596481243951,116.518014060345578,25.1432385661076303],"rgb":[0.8,0.2,0.2],"xyz":[0.266825359344463386,0.154462646969881851,0.0470855372028451818],"hpluv":[12.1770506300617924,327.11667224844831,46.2394596481243951],"hsluv":[12.1770506300617924,76.6535755019082,46.2394596481243951]},"#cc3344":{"lch":[46.4780522046582405,113.793717346871745,8.90746564227168669],"luv":[46.4780522046582405,112.421330819758609,17.6197186224449389],"rgb":[0.8,0.2,0.266666666666666663],"xyz":[0.271283354588682302,0.156245845067569444,0.0705643121557318204],"hpluv":[8.90746564227168669,310.677421508065,46.4780522046582405],"hsluv":[8.90746564227168669,77.4930753906613887,46.4780522046582405]},"#cc3355":{"lch":[46.7943405275181661,107.68493286266704,4.39945159143432907],"luv":[46.7943405275181661,107.367637754253522,8.26045572038872855],"rgb":[0.8,0.2,0.333333333333333315],"xyz":[0.277245768628044287,0.158630810683314277,0.101966359429705736],"hpluv":[4.39945159143432907,292.012160158640199,46.7943405275181661],"hsluv":[4.39945159143432907,78.5258346284665265,46.7943405275181661]},"#cc3366":{"lch":[47.192153202602185,101.584765682520938,358.514989280316684],"luv":[47.192153202602185,101.55064731932022,-2.63261235272558647],"rgb":[0.8,0.2,0.4],"xyz":[0.284830764401552472,0.161664808992717568,0.141914003836849684],"hpluv":[358.514989280316684,273.148057687163259,47.192153202602185],"hsluv":[358.514989280316684,79.7102211537212781,47.192153202602185]},"#cc3377":{"lch":[47.6738975277608859,96.3272258277819589,351.225603296168742],"luv":[47.6738975277608859,95.1998742335289876,-14.6941614798792699],"rgb":[0.8,0.2,0.466666666666666674],"xyz":[0.294144777421754378,0.165390414200798391,0.190967805743247593],"hpluv":[351.225603296168742,256.39391915093114,47.6738975277608859],"hsluv":[351.225603296168742,80.9972134637005894,47.6738975277608859]},"#cc3388":{"lch":[48.2406991607903279,92.7347249941698095,342.718318936463845],"luv":[48.2406991607903279,88.5482927213948301,-27.5486674064369161],"rgb":[0.8,0.2,0.533333333333333326],"xyz":[0.305285194523698178,0.169846581041576,0.24964066914681976],"hpluv":[342.718318936463845,243.93163202064531,48.2406991607903279],"hsluv":[342.718318936463845,82.3372641433758758,48.2406991607903279]},"#cc3399":{"lch":[48.8925304323200436,91.4400639314067405,333.463846826874658],"luv":[48.8925304323200436,81.8070942553503926,-40.8519843003490877],"rgb":[0.8,0.2,0.6],"xyz":[0.318342158632770933,0.175069366685205147,0.318407346787937695],"hpluv":[333.463846826874658,237.319449797121564,48.8925304323200436],"hsluv":[333.463846826874658,83.6856508008112,48.8925304323200436]},"#cc33aa":{"lch":[49.6283419748853873,92.7248127600779242,324.141487451800515],"luv":[49.6283419748853873,75.1503095778714254,-54.3168654447363792],"rgb":[0.8,0.2,0.66666666666666663],"xyz":[0.333399851598023433,0.181092443871306241,0.397711196404936296],"hpluv":[324.141487451800515,237.085790295602294,49.6283419748853873],"hsluv":[324.141487451800515,85.0057245198840263,49.6283419748853873]},"#cc33bb":{"lch":[50.4462014889725054,96.4744776262418355,315.414049480091705],"luv":[50.4462014889725054,68.7089492450233905,-67.7230029377097],"rgb":[0.8,0.2,0.733333333333333282],"xyz":[0.350537443705360108,0.187947480714241,0.487969181503578042],"hpluv":[315.414049480091705,242.674024226837219,50.4462014889725054],"hsluv":[315.414049480091705,86.2701296035756258,50.4462014889725054]},"#cc33cc":{"lch":[51.3434379695087273,102.286811532428814,307.715012949243771],"luv":[51.3434379695087273,62.5723551280607779,-80.915339628518268],"rgb":[0.8,0.2,0.8],"xyz":[0.369829819149081862,0.195664430891729801,0.589575692173848198],"hpluv":[307.715012949243771,252.798225898109365,51.3434379695087273],"hsluv":[307.715012949243771,87.4604868647498,51.3434379695087273]},"#cc33dd":{"lch":[52.3167871114961827,109.649949983538605,301.195256028086874],"luv":[52.3167871114961827,56.7938698017029751,-93.7953510806358],"rgb":[0.8,0.2,0.866666666666666696],"xyz":[0.39134814469971535,0.204271761111983335,0.702905540073854196],"hpluv":[301.195256028086874,265.954105752122757,52.3167871114961827],"hsluv":[301.195256028086874,88.5661577004671,52.3167871114961827]},"#cc33ee":{"lch":[53.3625327970638494,118.082474280319559,295.802769884900215],"luv":[53.3625327970638494,51.3983045372134626,-106.309383512755787],"rgb":[0.8,0.2,0.933333333333333348],"xyz":[0.415160324965890115,0.21379663321845338,0.828316356142377419],"hpluv":[295.802769884900215,280.794331636146467,53.3625327970638494],"hsluv":[295.802769884900215,89.5826266077864375,53.3625327970638494]},"#cc33ff":{"lch":[54.4766398815527566,127.197777570935344,291.389330811727291],"luv":[54.4766398815527566,46.3894624378335,-118.436870921660102],"rgb":[0.8,0.2,1],"xyz":[0.441331373375721481,0.224265052582386076,0.966150544434159264],"hpluv":[291.389330811727291,296.284230666095709,54.4766398815527566],"hsluv":[291.389330811727291,99.9999999999990195,54.4766398815527566]},"#cc4400":{"lch":[48.2269914221542848,118.435883841274119,19.7039935064818295],"luv":[48.2269914221542848,111.501113106022,39.9319465764175447],"rgb":[0.8,0.266666666666666663,0],"xyz":[0.26968315545685756,0.1697378781875854,0.0185625823454878096],"hpluv":[19.7039935064818295,311.625122342549162,48.2269914221542848],"hsluv":[19.7039935064818295,100.000000000002174,48.2269914221542848]},"#cc4411":{"lch":[48.2779913635395,116.996103035931512,19.0066631306561966],"luv":[48.2779913635395,110.617558299276112,38.1030697123021],"rgb":[0.8,0.266666666666666663,0.0666666666666666657],"xyz":[0.270694820956494664,0.170142544387440253,0.0238906873102434428],"hpluv":[19.0066631306561966,307.511619232155283,48.2779913635395],"hsluv":[19.0066631306561966,96.278384876640132,48.2779913635395]},"#cc4422":{"lch":[48.3723181772035389,114.428966114904512,17.6946931608209894],"luv":[48.3723181772035389,109.015290220227484,34.7800917814421453],"rgb":[0.8,0.266666666666666663,0.133333333333333331],"xyz":[0.272570179094971721,0.170892687642831081,0.0337675735062226354],"hpluv":[17.6946931608209894,300.177682052924467,48.3723181772035389],"hsluv":[17.6946931608209894,89.5341066932624869,48.3723181772035389]},"#cc4433":{"lch":[48.5270263662828114,110.48209503335984,15.4815426750504042],"luv":[48.5270263662828114,106.473417017787412,29.4907577304601034],"rgb":[0.8,0.266666666666666663,0.2],"xyz":[0.275657929827429427,0.17212778793581418,0.0500297273638337786],"hpluv":[15.4815426750504042,288.900004133964501,48.5270263662828114],"hsluv":[15.4815426750504042,78.8475327486048,48.5270263662828114]},"#cc4444":{"lch":[48.7490888960709725,105.371058014361893,12.1770506300618457],"luv":[48.7490888960709725,103.000257716521162,22.2262632351064191],"rgb":[0.8,0.266666666666666663,0.266666666666666663],"xyz":[0.280115925071648342,0.173910986033501774,0.0735085023167204171],"hpluv":[12.1770506300618457,274.28001464324592,48.7490888960709725],"hsluv":[12.1770506300618457,64.2723089184284788,48.7490888960709725]},"#cc4455":{"lch":[49.0437296069087,99.5540950311582691,7.58056168126664254],"luv":[49.0437296069087,98.6840273694228074,13.1331861946711772],"rgb":[0.8,0.266666666666666663,0.333333333333333315],"xyz":[0.286078339111010327,0.176295951649246607,0.104910549590694333],"hpluv":[7.58056168126664254,257.581676799067395,49.0437296069087],"hsluv":[7.58056168126664254,65.7689369270132858,49.0437296069087]},"#cc4466":{"lch":[49.4147368002801244,93.7049448509207679,1.51289058041819868],"luv":[49.4147368002801244,93.6722802728102835,2.47398423725391092],"rgb":[0.8,0.266666666666666663,0.4],"xyz":[0.293663334884518512,0.179329949958649898,0.14485819399783828],"hpluv":[1.51289058041819868,240.627550105868352,49.4147368002801244],"hsluv":[1.51289058041819868,67.500804726855,49.4147368002801244]},"#cc4477":{"lch":[49.8646356184384132,88.6490107187132566,353.900159994018],"luv":[49.8646356184384132,88.1471013723349586,-9.41995865499641383],"rgb":[0.8,0.266666666666666663,0.466666666666666674],"xyz":[0.302977347904720418,0.183055555166730721,0.19391199590423619],"hpluv":[353.900159994018,225.590377060975072,49.8646356184384132],"hsluv":[353.900159994018,69.4017616341687784,49.8646356184384132]},"#cc4488":{"lch":[50.3948096201307436,85.2405150759366279,344.907000147604322],"luv":[50.3948096201307436,82.3000967091543743,-22.1954835963269055],"rgb":[0.8,0.266666666666666663,0.533333333333333326],"xyz":[0.314117765006664218,0.187511722007508319,0.252584859307808385],"hpluv":[344.907000147604322,214.634525667930632,50.3948096201307436],"hsluv":[344.907000147604322,71.4025087995354824,50.3948096201307436]},"#cc4499":{"lch":[51.0056074652318046,84.1718897613591679,335.041688295241613],"luv":[51.0056074652318046,76.3115014820887581,-35.5170630478867793],"rgb":[0.8,0.266666666666666663,0.6],"xyz":[0.327174729115737,0.192734507651137477,0.321351536948926264],"hpluv":[335.041688295241613,209.40569084337892,51.0056074652318046],"hsluv":[335.041688295241613,73.4381222823911344,51.0056074652318046]},"#cc44aa":{"lch":[51.6964496814969152,85.7749410926589348,325.085448007895536],"luv":[51.6964496814969152,70.3360122964612913,-49.093644127128691],"rgb":[0.8,0.266666666666666663,0.66666666666666663],"xyz":[0.342232422080989473,0.198757584837238571,0.400655386565924865],"hpluv":[325.085448007895536,210.542141790665795,51.6964496814969152],"hsluv":[325.085448007895536,75.4531429469973602,51.6964496814969152]},"#cc44bb":{"lch":[52.465940673938249,89.9444778306290829,315.812454406659811],"luv":[52.465940673938249,64.4957791941851326,-62.692133145710109],"rgb":[0.8,0.266666666666666663,0.733333333333333282],"xyz":[0.359370014188326148,0.20561262168017333,0.490913371664566611],"hpluv":[315.812454406659811,217.538619181736436,52.465940673938249],"hsluv":[315.812454406659811,77.4040753157756143,52.465940673938249]},"#cc44cc":{"lch":[53.3119860408958175,96.2498903650287758,307.715012949243885],"luv":[53.3119860408958175,58.8793631430003828,-76.1397530279337502],"rgb":[0.8,0.266666666666666663,0.8],"xyz":[0.378662389632047902,0.213329571857662131,0.592519882334836767],"hpluv":[307.715012949243885,229.094523932317799,53.3119860408958175],"hsluv":[307.715012949243885,79.2597279113993,53.3119860408958175]},"#cc44dd":{"lch":[54.2319126329379486,104.138844540210442,300.941773361922515],"luv":[54.2319126329379486,53.5447276840333259,-89.31887303035586],"rgb":[0.8,0.266666666666666663,0.866666666666666696],"xyz":[0.40018071518268139,0.221936902077915665,0.705849730234842765],"hpluv":[300.941773361922515,243.66724889744043,54.2319126329379486],"hsluv":[300.941773361922515,81.0000946003390538,54.2319126329379486]},"#cc44ee":{"lch":[55.2225876682288401,113.096616320403683,295.407423092027],"luv":[55.2225876682288401,48.5243479249087528,-102.157879194837463],"rgb":[0.8,0.266666666666666663,0.933333333333333348],"xyz":[0.423992895448856155,0.23146177418438571,0.831260546303366],"hpluv":[295.407423092027,259.879597148300718,55.2225876682288401],"hsluv":[295.407423092027,86.8652077390175634,55.2225876682288401]},"#cc44ff":{"lch":[56.2805330741479537,122.715491745326418,290.92682559337851],"luv":[56.2805330741479537,43.8309487146615382,-114.62085259266739],"rgb":[0.8,0.266666666666666663,1],"xyz":[0.450163943858687521,0.241930193548318406,0.969094734595147833],"hpluv":[290.92682559337851,276.681751484143376,56.2805330741479537],"hsluv":[290.92682559337851,99.9999999999989768,56.2805330741479537]},"#cc5500":{"lch":[51.07852272981998,108.355754132896138,24.3337665629108457],"luv":[51.07852272981998,98.7294936728878838,44.6481414260866],"rgb":[0.8,0.333333333333333315,0],"xyz":[0.281496412171203525,0.193364391616277664,0.0225003345836030169],"hpluv":[24.3337665629108457,269.186315008697875,51.07852272981998],"hsluv":[24.3337665629108457,100.000000000002217,51.07852272981998]},"#cc5511":{"lch":[51.1252833166066,107.003417336388864,23.6474066829241423],"luv":[51.1252833166066,98.0184651549503201,42.91983003616388],"rgb":[0.8,0.333333333333333315,0.0666666666666666657],"xyz":[0.282508077670840629,0.193769057816132517,0.0278284395483586466],"hpluv":[23.6474066829241423,265.583595811902,51.1252833166066],"hsluv":[23.6474066829241423,96.7082849697218307,51.1252833166066]},"#cc5522":{"lch":[51.2117930688627467,104.583581473170284,22.351909846807331],"luv":[51.2117930688627467,96.7257518395109486,39.7725337995530808],"rgb":[0.8,0.333333333333333315,0.133333333333333331],"xyz":[0.284383435809317686,0.194519201071523345,0.0377053257443378462],"hpluv":[22.351909846807331,259.139045481141636,51.2117930688627467],"hsluv":[22.351909846807331,90.7274756498525079,51.2117930688627467]},"#cc5533":{"lch":[51.3537468781662625,100.840694749975015,20.1540565840537802],"luv":[51.3537468781662625,94.6661789932146718,34.7442120716858369],"rgb":[0.8,0.333333333333333315,0.2],"xyz":[0.287471186541775392,0.195754301364506444,0.0539674796019489894],"hpluv":[20.1540565840537802,249.174169543579183,51.3537468781662625],"hsluv":[20.1540565840537802,81.2092815218714321,51.3537468781662625]},"#cc5544":{"lch":[51.5576456995760424,95.9504499860225764,16.842752904303623],"luv":[51.5576456995760424,91.8345174188591074,27.8012636936791502],"rgb":[0.8,0.333333333333333315,0.266666666666666663],"xyz":[0.291929181785994307,0.197537499462194038,0.0774462545548356279],"hpluv":[16.842752904303623,236.152889903020736,51.5576456995760424],"hsluv":[16.842752904303623,68.1451659970290109,51.5576456995760424]},"#cc5555":{"lch":[51.8284441386287114,90.3192908744292851,12.1770506300618919],"luv":[51.8284441386287114,88.2871484081680364,19.0513445723360242],"rgb":[0.8,0.333333333333333315,0.333333333333333315],"xyz":[0.297891595825356292,0.199922465077938871,0.108848301828809529],"hpluv":[12.1770506300618919,221.132040760117775,51.8284441386287114],"hsluv":[12.1770506300618919,51.8180912815801,51.8284441386287114]},"#cc5566":{"lch":[52.1698415772242612,84.57836719340618,5.91246023067467696],"luv":[52.1698415772242612,84.1284474176031836,8.71232071300305577],"rgb":[0.8,0.333333333333333315,0.4],"xyz":[0.305476591598864478,0.202956463387342162,0.148795946235953491],"hpluv":[5.91246023067467696,205.721226704565879,52.1698415772242612],"hsluv":[5.91246023067467696,54.0130370973951415,52.1698415772242612]},"#cc5577":{"lch":[52.5844387621358607,79.5477054283049654,357.892334615122479],"luv":[52.5844387621358607,79.493890006545314,-2.92555815796844554],"rgb":[0.8,0.333333333333333315,0.466666666666666674],"xyz":[0.314790604619066383,0.206682068595422985,0.1978497481423514],"hpluv":[357.892334615122479,191.959557587962337,52.5844387621358607],"hsluv":[357.892334615122479,56.4492590331968884,52.5844387621358607]},"#cc5588":{"lch":[53.0738428910491962,76.1348505840982,348.222799824086337],"luv":[53.0738428910491962,74.5321119975331072,-15.5396188708133902],"rgb":[0.8,0.333333333333333315,0.533333333333333326],"xyz":[0.325931021721010183,0.211138235436200583,0.256522611545923596],"hpluv":[348.222799824086337,182.029716109510787,53.0738428910491962],"hsluv":[348.222799824086337,59.0443947768825339,53.0738428910491962]},"#cc5599":{"lch":[53.6387547547300443,75.1322702050955655,337.451566852479516],"luv":[53.6387547547300443,69.3888374020903314,-28.8105409556599774],"rgb":[0.8,0.333333333333333315,0.6],"xyz":[0.338987985830082938,0.216361021079829741,0.325289289187041475],"hpluv":[337.451566852479516,177.740808472615271,53.6387547547300443],"hsluv":[337.451566852479516,61.7180151219793274,53.6387547547300443]},"#cc55aa":{"lch":[54.2790527162633651,76.9604755372169507,326.525055670409472],"luv":[54.2790527162633651,64.1948187783047359,-42.449264268479169],"rgb":[0.8,0.333333333333333315,0.66666666666666663],"xyz":[0.354045678795335439,0.222384098265930835,0.404593138804040076],"hpluv":[326.525055670409472,179.918080614224607,54.2790527162633651],"hsluv":[326.525055670409472,64.3982563598392517,54.2790527162633651]},"#cc55bb":{"lch":[54.9938795911038767,81.534325182187132,316.414019967357433],"luv":[54.9938795911038767,59.0586208757266817,-56.2132144888709888],"rgb":[0.8,0.333333333333333315,0.733333333333333282],"xyz":[0.371183270902672113,0.229239135108865594,0.494851123902681822],"hpluv":[316.414019967357433,188.133202867989326,54.9938795911038767],"hsluv":[316.414019967357433,67.0257727218245378,54.9938795911038767]},"#cc55cc":{"lch":[55.7817339145568667,88.3780574248019,307.715012949244169],"luv":[55.7817339145568667,54.0638926159085145,-69.9126351198200382],"rgb":[0.8,0.333333333333333315,0.8],"xyz":[0.390475646346393868,0.236956085286354395,0.596457634572952],"hpluv":[307.715012949244169,201.044301715196383,55.7817339145568667],"hsluv":[307.715012949244169,69.5552053299685156,55.7817339145568667]},"#cc55dd":{"lch":[56.6405645837994882,96.8744721490496232,300.570417552325068],"luv":[56.6405645837994882,49.2700598324241312,-83.4093793183137],"rgb":[0.8,0.333333333333333315,0.866666666666666696],"xyz":[0.411993971897027356,0.245563415506607929,0.709787482472958],"hpluv":[300.570417552325068,217.030665421015726,56.6405645837994882],"hsluv":[300.570417552325068,72.1703256475756234,56.6405645837994882]},"#cc55ee":{"lch":[57.5678665910353118,106.457154357221242,294.836459829444038],"luv":[57.5678665910353118,44.7151619549172707,-96.6109724885468],"rgb":[0.8,0.333333333333333315,0.933333333333333348],"xyz":[0.435806152163202121,0.255088287613077946,0.835198298541481199],"hpluv":[294.836459829444038,234.657286550118499,57.5678665910353118],"hsluv":[294.836459829444038,85.8995510844284809,57.5678665910353118]},"#cc55ff":{"lch":[58.560775097021633,116.686665261471418,290.266986003053091],"luv":[58.560775097021633,40.4196983751927874,-109.462440284789551],"rgb":[0.8,0.333333333333333315,1],"xyz":[0.461977200573033486,0.26555670697701067,0.973032486833263],"hpluv":[290.266986003053091,252.844632524376181,58.560775097021633],"hsluv":[290.266986003053091,99.9999999999988,58.560775097021633]},"#cc6600":{"lch":[54.388060759003551,98.5584029412379579,30.482787603130209],"luv":[54.388060759003551,84.9358174587045482,49.9966569177284157],"rgb":[0.8,0.4,0],"xyz":[0.29652446987705,0.223420507027971,0.0275096871522183678],"hpluv":[30.482787603130209,229.947880001204965,54.388060759003551],"hsluv":[30.482787603130209,100.000000000002359,54.388060759003551]},"#cc6611":{"lch":[54.430531479182676,97.2669366851907711,29.8266301777559697],"luv":[54.430531479182676,84.3824110244092367,48.3783596438383583],"rgb":[0.8,0.4,0.0666666666666666657],"xyz":[0.297536135376687105,0.223825173227825858,0.032837792116974],"hpluv":[29.8266301777559697,226.757672191171366,54.430531479182676],"hsluv":[29.8266301777559697,97.1300271864182463,54.430531479182676]},"#cc6622":{"lch":[54.5091256699603548,94.945229062596681,28.5830621108902889],"luv":[54.5091256699603548,83.3737269438019695,45.4248629854764],"rgb":[0.8,0.4,0.133333333333333331],"xyz":[0.299411493515164162,0.224575316483216686,0.0427146783129532],"hpluv":[28.5830621108902889,221.025945543219876,54.5091256699603548],"hsluv":[28.5830621108902889,91.902111940908739,54.5091256699603548]},"#cc6633":{"lch":[54.6381494647888388,91.324965774397,26.4577509745500876],"luv":[54.6381494647888388,81.7598754062496624,40.688722605278663],"rgb":[0.8,0.4,0.2],"xyz":[0.302499244247621868,0.225810416776199785,0.0589768321705643403],"hpluv":[26.4577509745500876,212.096187894217309,54.6381494647888388],"hsluv":[26.4577509745500876,83.5463344335908289,54.6381494647888388]},"#cc6644":{"lch":[54.8236025158742137,86.5353782352002,23.2174871910251],"luv":[54.8236025158742137,79.5273159773273903,34.1141861950638798],"rgb":[0.8,0.4,0.266666666666666663],"xyz":[0.306957239491840783,0.227593614873887379,0.0824556071234509858],"hpluv":[23.2174871910251,200.292852977212362,54.8236025158742137],"hsluv":[23.2174871910251,72.0055590941951635,54.8236025158742137]},"#cc6655":{"lch":[55.0701314820163077,80.921365039871,18.5706632184066578],"luv":[55.0701314820163077,76.7079190692017505,25.7713498286149552],"rgb":[0.8,0.4,0.333333333333333315],"xyz":[0.312919653531202768,0.229978580489632212,0.113857654397424887],"hpluv":[18.5706632184066578,186.460314881725708,55.0701314820163077],"hsluv":[18.5706632184066578,57.463707489477386,55.0701314820163077]},"#cc6666":{"lch":[55.3812986167643686,75.0592421503045841,12.1770506300619576],"luv":[55.3812986167643686,73.3704437553848834,15.8324923911544637],"rgb":[0.8,0.4,0.4],"xyz":[0.320504649304710953,0.233012578799035502,0.153805298804568835],"hpluv":[12.1770506300619576,171.980959079196282,55.3812986167643686],"hsluv":[12.1770506300619576,45.9214429163451925,55.3812986167643686]},"#cc6677":{"lch":[55.7597240294908403,69.7578535305897702,3.7339069954147126],"luv":[55.7597240294908403,69.6097753289024,4.54283037928401079],"rgb":[0.8,0.4,0.466666666666666674],"xyz":[0.329818662324912859,0.236738184007116326,0.202859100710966744],"hpluv":[3.7339069954147126,158.749300244695775,55.7597240294908403],"hsluv":[3.7339069954147126,47.4001946683844935,55.7597240294908403]},"#cc6688":{"lch":[56.2071770412836855,65.9988703650454198,353.20230012342131],"luv":[56.2071770412836855,65.534915505775615,-7.81189727997758432],"rgb":[0.8,0.4,0.533333333333333326],"xyz":[0.340959079426856659,0.241194350847893924,0.26153196411453894],"hpluv":[353.20230012342131,148.999240608904586,56.2071770412836855],"hsluv":[353.20230012342131,48.9818497710332537,56.2071770412836855]},"#cc6699":{"lch":[56.7246474757154573,64.7364860461136544,341.13073016495332],"luv":[56.7246474757154573,61.2574795353059756,-20.9364234428957907],"rgb":[0.8,0.4,0.6],"xyz":[0.354016043535929414,0.246417136491523081,0.330298641755656819],"hpluv":[341.13073016495332,144.81603174775961,56.7246474757154573],"hsluv":[341.13073016495332,50.615240188358726,56.7246474757154573]},"#cc66aa":{"lch":[57.3124110050500661,66.5536753014576874,328.724652687595039],"luv":[57.3124110050500661,56.882247269492467,-34.5514347271451925],"rgb":[0.8,0.4,0.66666666666666663],"xyz":[0.369073736501181915,0.252440213677624203,0.40960249137265542],"hpluv":[328.724652687595039,147.354258527000582,57.3124110050500661],"hsluv":[328.724652687595039,52.5782983823555057,57.3124110050500661]},"#cc66bb":{"lch":[57.9700950113726634,71.4140361316817831,317.320615294889421],"luv":[57.9700950113726634,52.5006394421278628,-48.4112323204526405],"rgb":[0.8,0.4,0.733333333333333282],"xyz":[0.386211328608518589,0.259295250520558962,0.499860476471297166],"hpluv":[317.320615294889421,156.321564636453559,57.9700950113726634],"hsluv":[317.320615294889421,55.7867325729046044,57.9700950113726634]},"#cc66cc":{"lch":[58.6967474031167882,78.7715159072838844,307.71501294924451],"luv":[58.6967474031167882,48.1872412824568599,-62.3132529717215391],"rgb":[0.8,0.4,0.8],"xyz":[0.405503704052240344,0.267012200698047764,0.601466987141567322],"hpluv":[307.71501294924451,170.292097080892688,58.6967474031167882],"hsluv":[307.71501294924451,58.9158791245518429,58.6967474031167882]},"#cc66dd":{"lch":[59.4909085631812928,87.9045601549668589,300.035118747227784],"luv":[59.4909085631812928,43.9989332886277964,-76.1006278916348151],"rgb":[0.8,0.4,0.866666666666666696],"xyz":[0.427022029602873832,0.27561953091830127,0.71479683504157332],"hpluv":[300.035118747227784,187.499506963343748,59.4909085631812928],"hsluv":[300.035118747227784,69.8010448945604907,59.4909085631812928]},"#cc66ee":{"lch":[60.3506853352839272,98.1678970647401314,294.030303780460372],"luv":[60.3506853352839272,39.9759075841908498,-89.6597057040320919],"rgb":[0.8,0.4,0.933333333333333348],"xyz":[0.450834209869048597,0.285144403024771287,0.840207651110096543],"hpluv":[294.030303780460372,206.408039079415715,60.3506853352839272],"hsluv":[294.030303780460372,84.6650997716967169,60.3506853352839272]},"#cc66ff":{"lch":[61.2738253236974799,109.076950193692937,289.351384827957531],"luv":[61.2738253236974799,36.143813792667018,-102.914555763887847],"rgb":[0.8,0.4,1],"xyz":[0.477005258278879962,0.295612822388704,0.978041839401878388],"hpluv":[289.351384827957531,225.890163025573315,61.2738253236974799],"hsluv":[289.351384827957531,99.9999999999987,61.2738253236974799]},"#cc7700":{"lch":[58.0681687130694684,90.1274111260576,38.2527636780657616],"luv":[58.0681687130694684,70.775890411238,55.8007488550273862],"rgb":[0.8,0.466666666666666674,0],"xyz":[0.314978207930467602,0.260327983134806762,0.0336609331700240683],"hpluv":[38.2527636780657616,196.95095583694345,58.0681687130694684],"hsluv":[38.2527636780657616,100.00000000000226,58.0681687130694684]},"#cc7711":{"lch":[58.1065272060428,88.8714225311590837,37.658223554494576],"luv":[58.1065272060428,70.356768805075,54.2959927252558288],"rgb":[0.8,0.466666666666666674,0.0666666666666666657],"xyz":[0.315989873430104706,0.260732649334661615,0.0389890381347797],"hpluv":[37.658223554494576,194.078102813960953,58.1065272060428],"hsluv":[37.658223554494576,97.5201736027742925,58.1065272060428]},"#cc7722":{"lch":[58.1775287784180364,86.6008188207648573,36.5262538454337786],"luv":[58.1775287784180364,69.5910513475594286,51.5440335321973748],"rgb":[0.8,0.466666666666666674,0.133333333333333331],"xyz":[0.317865231568581763,0.261482792590052415,0.0488659243307589],"hpluv":[36.5262538454337786,188.888733728089306,58.1775287784180364],"hsluv":[36.5262538454337786,92.9922153048462832,58.1775287784180364]},"#cc7733":{"lch":[58.2941365993826111,83.0249283524691606,34.5753600294232513],"luv":[58.2941365993826111,68.3611064237300781,47.1157920070976459],"rgb":[0.8,0.466666666666666674,0.2],"xyz":[0.320952982301039469,0.262717892883035542,0.0651280781883700477],"hpluv":[34.5753600294232513,180.726967614303447,58.2941365993826111],"hsluv":[34.5753600294232513,85.7262720770138458,58.2941365993826111]},"#cc7744":{"lch":[58.4618482438389577,78.2181767747613321,31.5590767862408974],"luv":[58.4618482438389577,66.6497843857434447,40.9376284034837923],"rgb":[0.8,0.466666666666666674,0.266666666666666663],"xyz":[0.325410977545258384,0.264501090980723108,0.0886068531412566862],"hpluv":[31.5590767862408974,169.775287201810244,58.4618482438389577],"hsluv":[31.5590767862408974,75.6318325574838,58.4618482438389577]},"#cc7755":{"lch":[58.6849825995062133,72.4480337937475,27.1381330463907204],"luv":[58.6849825995062133,64.4721877935720187,33.0462494345479811],"rgb":[0.8,0.466666666666666674,0.333333333333333315],"xyz":[0.331373391584620369,0.266886056596467969,0.120008900415230588],"hpluv":[27.1381330463907204,156.653084219512607,58.6849825995062133],"hsluv":[27.1381330463907204,62.8141161752895059,58.6849825995062133]},"#cc7766":{"lch":[58.9669266929607829,66.2083874258113667,20.8554290651987451],"luv":[58.9669266929607829,61.8705265372573407,23.5709251309504673],"rgb":[0.8,0.466666666666666674,0.4],"xyz":[0.338958387358128554,0.269920054905871287,0.159956544822374536],"hpluv":[20.8554290651987451,142.476698671576116,58.9669266929607829],"hsluv":[20.8554290651987451,47.542957476979069,58.9669266929607829]},"#cc7777":{"lch":[59.3102652975897229,60.2635194006596251,12.177050630062082],"luv":[59.3102652975897229,58.907617956407762,12.7115820123061383],"rgb":[0.8,0.466666666666666674,0.466666666666666674],"xyz":[0.34827240037833046,0.273645660113952083,0.209010346728772445],"hpluv":[12.177050630062082,128.932959302114057,59.3102652975897229],"hsluv":[12.177050630062082,43.5373021749198443,59.3102652975897229]},"#cc7788":{"lch":[59.7168613687891963,55.6638868177661834,0.734433949810619269],"luv":[59.7168613687891963,55.6593138534775917,0.713496335773026069],"rgb":[0.8,0.466666666666666674,0.533333333333333326],"xyz":[0.35941281748027426,0.278101826954729681,0.26768321013234464],"hpluv":[0.734433949810619269,118.281243349182901,59.7168613687891963],"hsluv":[0.734433949810619269,45.0130000154657779,59.7168613687891963]},"#cc7799":{"lch":[60.187915321807921,53.6038783156568073,346.890122071781263],"luv":[60.187915321807921,52.2067938730354371,-12.1583898596044406],"rgb":[0.8,0.466666666666666674,0.6],"xyz":[0.37246978158934696,0.283324612598358838,0.336449887773462519],"hpluv":[346.890122071781263,113.012436406344946,60.187915321807921],"hsluv":[346.890122071781263,46.5549497102544,60.187915321807921]},"#cc77aa":{"lch":[60.7240163061688349,54.9761438743297148,332.197464335395125],"luv":[60.7240163061688349,48.6297161920954863,-25.6422912074417866],"rgb":[0.8,0.466666666666666674,0.66666666666666663],"xyz":[0.387527474554599516,0.28934768978445996,0.41575373739046112],"hpluv":[332.197464335395125,114.882297594683308,60.7240163061688349],"hsluv":[332.197464335395125,48.1189262345266471,60.7240163061688349]},"#cc77bb":{"lch":[61.3251919150652043,59.875823087291181,318.726648244723037],"luv":[61.3251919150652043,45.0009333565193046,-39.4972174643062743],"rgb":[0.8,0.466666666666666674,0.733333333333333282],"xyz":[0.40466506666193619,0.296202726627394719,0.506011722489102866],"hpluv":[318.726648244723037,123.894465684476771,61.3251919150652043],"hsluv":[318.726648244723037,49.6642191709372156,61.3251919150652043]},"#cc77cc":{"lch":[61.9909592768387228,67.6487625915650881,307.715012949244965],"luv":[61.9909592768387228,41.3830711255614219,-53.514451360232222],"rgb":[0.8,0.466666666666666674,0.8],"xyz":[0.423957442105657889,0.303919676804883521,0.607618233159373],"hpluv":[307.715012949244965,138.474825543749517,61.9909592768387228],"hsluv":[307.715012949244965,51.1553628289834066,61.9909592768387228]},"#cc77dd":{"lch":[62.7203784873954362,77.3966796673740305,299.257833182927357],"luv":[62.7203784873954362,37.8268934727942252,-67.5231231041040161],"rgb":[0.8,0.466666666666666674,0.866666666666666696],"xyz":[0.445475767656291488,0.312527007025137,0.720948081059379],"hpluv":[299.257833182927357,156.586020071329443,62.7203784873954362],"hsluv":[299.257833182927357,66.8390608629672158,62.7203784873954362]},"#cc77ee":{"lch":[63.5121081687847351,88.3507940057556453,292.894169444170245],"luv":[63.5121081687847351,34.371127600373157,-81.3909601179783],"rgb":[0.8,0.466666666666666674,0.933333333333333348],"xyz":[0.469287947922466198,0.322051879131607044,0.846358897127902243],"hpluv":[292.894169444170245,176.519730115921618,63.5121081687847351],"hsluv":[292.894169444170245,83.1130682001540322,63.5121081687847351]},"#cc77ff":{"lch":[64.3644622692190467,99.9639900757921112,288.092077643344339],"luv":[64.3644622692190467,31.0433170684360782,-95.0216384686225553],"rgb":[0.8,0.466666666666666674,1],"xyz":[0.495458996332297619,0.332520298495539768,0.984193085419684088],"hpluv":[288.092077643344339,197.077372703744913,64.3644622692190467],"hsluv":[288.092077643344339,99.9999999999984794,64.3644622692190467]},"#cc8800":{"lch":[62.03823759594124,83.9779445354575813,47.4964941193052752],"luv":[62.03823759594124,56.738465406715342,61.9115636346826221],"rgb":[0.8,0.533333333333333326,0],"xyz":[0.337050577655438111,0.304472722584748334,0.0410183897450140181],"hpluv":[47.4964941193052752,171.769129739761638,62.03823759594124],"hsluv":[47.4964941193052752,100.000000000002245,62.03823759594124]},"#cc8811":{"lch":[62.0727951053461879,82.7448035916202542,47.0033697453910904],"luv":[62.0727951053461879,56.4282611354359744,60.519037225048919],"rgb":[0.8,0.533333333333333326,0.0666666666666666657],"xyz":[0.338062243155075215,0.304877388784603187,0.0463464947097696478],"hpluv":[47.0033697453910904,169.152629782786704,62.0727951053461879],"hsluv":[47.0033697453910904,97.8669953021426,62.0727951053461879]},"#cc8822":{"lch":[62.1367747194074127,80.5023095814055836,46.0605806208152728],"luv":[62.1367747194074127,55.8603434591127268,57.9676105818622389],"rgb":[0.8,0.533333333333333326,0.133333333333333331],"xyz":[0.339937601293552272,0.305627532039994,0.0562233809057488473],"hpluv":[46.0605806208152728,164.398919884807611,62.1367747194074127],"hsluv":[46.0605806208152728,93.9640765124603,62.1367747194074127]},"#cc8833":{"lch":[62.2418885518498541,76.9329948636363241,44.4230175211057343],"luv":[62.2418885518498541,54.9448944756850963,53.8492736231801246],"rgb":[0.8,0.533333333333333326,0.2],"xyz":[0.34302535202601,0.306862632332977114,0.0724855347633599906],"hpluv":[44.4230175211057343,156.844467463147254,62.2418885518498541],"hsluv":[44.4230175211057343,87.678806867525978,62.2418885518498541]},"#cc8844":{"lch":[62.3931521103864668,72.0506908450526424,41.8566920827827929],"luv":[62.3931521103864668,53.6645163812677168,48.0772475586321661],"rgb":[0.8,0.533333333333333326,0.266666666666666663],"xyz":[0.347483347270228893,0.30864583043066468,0.0959643097162466291],"hpluv":[41.8566920827827929,146.534723453778,62.3931521103864668],"hsluv":[41.8566920827827929,78.900857077449,62.3931521103864668]},"#cc8855":{"lch":[62.5945538889838673,66.028576146577123,38.0101307109045408],"luv":[62.5945538889838673,52.0240395147187513,40.6604498316892062],"rgb":[0.8,0.533333333333333326,0.333333333333333315],"xyz":[0.353445761309590878,0.311030796046409541,0.127366356990220531],"hpluv":[38.0101307109045408,133.855034432196135,62.5945538889838673],"hsluv":[38.0101307109045408,67.6770944211119314,62.5945538889838673]},"#cc8866":{"lch":[62.8492816845599265,59.2369010326618337,32.3420191431213624],"luv":[62.8492816845599265,50.047464385327217,31.6900891849924626],"rgb":[0.8,0.533333333333333326,0.4],"xyz":[0.361030757083099063,0.31406479435581286,0.167314001397364492],"hpluv":[32.3420191431213624,119.60004374597824,62.8492816845599265],"hsluv":[32.3420191431213624,54.1901780257147436,62.8492816845599265]},"#cc8877":{"lch":[63.1598410661450771,52.3169991415742,24.053169540805424],"luv":[63.1598410661450771,47.7741895619322392,21.3235834436907119],"rgb":[0.8,0.533333333333333326,0.466666666666666674],"xyz":[0.370344770103300969,0.317790399563893655,0.216367803303762402],"hpluv":[24.053169540805424,105.109295035123296,63.1598410661450771],"hsluv":[24.053169540805424,38.7316682482548558,63.1598410661450771]},"#cc8888":{"lch":[63.5281271999152182,46.2961098245983322,12.1770506300622312],"luv":[63.5281271999152182,45.2544686659219906,9.76539045078869528],"rgb":[0.8,0.533333333333333326,0.533333333333333326],"xyz":[0.381485187205244769,0.322246566404671253,0.275040666707334569],"hpluv":[12.1770506300622312,92.4736018048895403,63.5281271999152182],"hsluv":[12.1770506300622312,40.0703189706204199,63.5281271999152182]},"#cc8899":{"lch":[63.9554753143552119,42.6335463086933615,356.296984030050055],"luv":[63.9554753143552119,42.5445368433351,-2.7534806412255004],"rgb":[0.8,0.533333333333333326,0.6],"xyz":[0.394542151314317469,0.327469352048300411,0.343807344348452504],"hpluv":[356.296984030050055,84.588837176407921,63.9554753143552119],"hsluv":[356.296984030050055,41.5088242950414781,63.9554753143552119]},"#cc88aa":{"lch":[64.442701858069384,42.8028595258545153,338.056731442080661],"luv":[64.442701858069384,39.701977128446444,-15.9949303118941444],"rgb":[0.8,0.533333333333333326,0.66666666666666663],"xyz":[0.40959984427957,0.333492429234401533,0.423111193965451104],"hpluv":[338.056731442080661,84.28268641071584,64.442701858069384],"hsluv":[338.056731442080661,42.9831867606486924,64.442701858069384]},"#cc88bb":{"lch":[64.9901424985427099,47.2946673003310707,321.051918945199532],"luv":[64.9901424985427099,36.7818149859187784,-29.7301806484697622],"rgb":[0.8,0.533333333333333326,0.733333333333333282],"xyz":[0.426737436386906699,0.340347466077336291,0.513369179064092851],"hpluv":[321.051918945199532,92.3430068912496296,64.9901424985427099],"hsluv":[321.051918945199532,44.4548878388224864,64.9901424985427099]},"#cc88cc":{"lch":[65.5976900795525637,55.3077284551996158,307.715012949245761],"luv":[65.5976900795525637,33.8336367550969399,-43.751912538747959],"rgb":[0.8,0.533333333333333326,0.8],"xyz":[0.446029811830628398,0.348064416254825093,0.614975689734363],"hpluv":[307.715012949245761,106.988377595373095,65.5976900795525637],"hsluv":[307.715012949245761,45.8886814173124122,65.5976900795525637]},"#cc88dd":{"lch":[66.2648339334855905,65.6132669537387727,298.095232643272539],"luv":[66.2648339334855905,30.8998122908772608,-57.8817967994354774],"rgb":[0.8,0.533333333333333326,0.866666666666666696],"xyz":[0.467548137381262,0.356671746475078599,0.728305537634369],"hpluv":[298.095232643272539,125.645770778198369,66.2648339334855905],"hsluv":[298.095232643272539,63.1276078601331037,66.2648339334855905]},"#cc88ee":{"lch":[66.9907009061042,77.2337728548956193,291.267726386147558],"luv":[66.9907009061042,28.0147259973813227,-71.9738202174461463],"rgb":[0.8,0.533333333333333326,0.933333333333333348],"xyz":[0.491360317647436706,0.366196618581548616,0.853716353702892228],"hpluv":[291.267726386147558,146.295866424915545,66.9907009061042],"hsluv":[291.267726386147558,81.1585487563552874,66.9907009061042]},"#cc88ff":{"lch":[67.7740978167257,89.535143384050329,286.350196506734335],"luv":[67.7740978167257,25.2048126805589376,-85.914255618845857],"rgb":[0.8,0.533333333333333326,1],"xyz":[0.517531366057268127,0.37666503794548134,0.991550541994674073],"hpluv":[286.350196506734335,167.63670457649863,67.7740978167257],"hsluv":[286.350196506734335,99.9999999999982379,67.7740978167257]},"#cc9900":{"lch":[66.2294666531998217,80.7116888085701163,57.6888018595631422],"luv":[66.2294666531998217,43.1418134232290527,68.2140795209226383],"rgb":[0.8,0.6,0],"xyz":[0.362920178107905556,0.356211923489684,0.0496415898958362731],"hpluv":[57.6888018595631422,154.64094800189136,66.2294666531998217],"hsluv":[57.6888018595631422,100.000000000002331,66.2294666531998217]},"#cc9911":{"lch":[66.2605931548954459,79.5070460268624,57.3307205104302042],"luv":[66.2605931548954459,42.9170325725223805,66.9290570909725915],"rgb":[0.8,0.6,0.0666666666666666657],"xyz":[0.36393184360754266,0.356616589689538854,0.0549696948605919],"hpluv":[57.3307205104302042,152.261332222626407,66.2605931548954459],"hsluv":[57.3307205104302042,98.1673920986410877,66.2605931548954459]},"#cc9922":{"lch":[66.318231165714252,77.3048675578901339,56.6444543825244],"luv":[66.318231165714252,42.5047546114663533,64.5708013235236535],"rgb":[0.8,0.6,0.133333333333333331],"xyz":[0.365807201746019717,0.357366732944929655,0.0648465810565711],"hpluv":[56.6444543825244,147.915345655385721,66.318231165714252],"hsluv":[56.6444543825244,94.8079930897652901,66.318231165714252]},"#cc9933":{"lch":[66.4129558628457772,73.7662990604099207,55.446788144651876],"luv":[66.4129558628457772,41.8381333944198062,60.7539091017242114],"rgb":[0.8,0.6,0.2],"xyz":[0.368894952478477423,0.358601833237912782,0.0811087349141822456],"hpluv":[55.446788144651876,140.943324602767206,66.4129558628457772],"hsluv":[55.446788144651876,89.3812598244831804,66.4129558628457772]},"#cc9944":{"lch":[66.5493334014023361,68.8491039809152596,53.5533094328142383],"luv":[66.5493334014023361,40.9015039610359423,55.3829043360877122],"rgb":[0.8,0.6,0.266666666666666663],"xyz":[0.373352947722696338,0.360385031335600348,0.104587509867068884],"hpluv":[53.5533094328142383,131.278591794008349,66.5493334014023361],"hsluv":[53.5533094328142383,81.7675495661837459,66.5493334014023361]},"#cc9955":{"lch":[66.7310322275847341,62.6300376921671571,50.6698876242401539],"luv":[66.7310322275847341,39.6941340540006422,48.4447865412296466],"rgb":[0.8,0.6,0.333333333333333315],"xyz":[0.379315361762058323,0.362769996951345208,0.135989557141042799],"hpluv":[50.6698876242401539,119.095172229338388,66.7310322275847341],"hsluv":[50.6698876242401539,71.9728873324543912,66.7310322275847341]},"#cc9966":{"lch":[66.9610303820851,55.3290996264574488,46.2964172073191236],"luv":[66.9610303820851,38.2284030091626121,39.9987308404091877],"rgb":[0.8,0.6,0.4],"xyz":[0.386900357535566508,0.365803995260748527,0.175937201548186761],"hpluv":[46.2964172073191236,104.850571413887963,66.9610303820851],"hsluv":[46.2964172073191236,60.1139469393688586,66.9610303820851]},"#cc9977":{"lch":[67.2417240975963608,47.3725698769106316,39.5497292985384448],"luv":[67.2417240975963608,36.5276723895038842,30.1643751227811094],"rgb":[0.8,0.6,0.466666666666666674],"xyz":[0.396214370555768414,0.369529600468829322,0.22499100345458467],"hpluv":[39.5497292985384448,89.3979234879296598,67.2417240975963608],"hsluv":[39.5497292985384448,46.4000360859745484,67.2417240975963608]},"#cc9988":{"lch":[67.5749927230407508,39.5461637624335367,28.8927829606528306],"luv":[67.5749927230407508,34.6236706423047593,19.1076031876950658],"rgb":[0.8,0.6,0.533333333333333326],"xyz":[0.407354787657712214,0.37398576730960692,0.28366386685815681],"hpluv":[28.8927829606528306,74.2604675709266076,67.5749927230407508],"hsluv":[28.8927829606528306,33.7758353105824227,67.5749927230407508]},"#cc9999":{"lch":[67.962242737641,33.3028609095241,12.177050630062606],"luv":[67.962242737641,32.5535618700051401,7.02468179598591647],"rgb":[0.8,0.6,0.6],"xyz":[0.420411751766784914,0.379208552953236078,0.352430544499274745],"hpluv":[12.177050630062606,62.1803508213615217,67.962242737641],"hsluv":[12.177050630062606,35.0991912912463349,67.962242737641]},"#cc99aa":{"lch":[68.4044417972397838,30.9200145018949506,349.049331623372325],"luv":[68.4044417972397838,30.3569952347367966,-5.87368173427548168],"rgb":[0.8,0.6,0.66666666666666663],"xyz":[0.43546944473203747,0.3852316301393372,0.431734394116273346],"hpluv":[349.049331623372325,57.3580941092039609,68.4044417972397838],"hsluv":[349.049331623372325,36.4663034143199312,68.4044417972397838]},"#cc99bb":{"lch":[68.9021485343020856,34.1112448474386483,325.385883063702067],"luv":[68.9021485343020856,28.0734328763681233,-19.3767745401024669],"rgb":[0.8,0.6,0.733333333333333282],"xyz":[0.452607036839374144,0.392086666982271959,0.521992379214915],"hpluv":[325.385883063702067,62.8208966420876678,68.9021485343020856],"hsluv":[325.385883063702067,37.8410036888738404,68.9021485343020856]},"#cc99cc":{"lch":[69.4555411877739601,42.0770553751994854,307.715012949247],"luv":[69.4555411877739601,25.7399796927440505,-33.285613025220492],"rgb":[0.8,0.6,0.8],"xyz":[0.471899412283095843,0.39980361715976076,0.623598889885185192],"hpluv":[307.715012949247,76.8736967911951581,69.4555411877739601],"hsluv":[307.715012949247,39.1886552488513473,69.4555411877739601]},"#cc99dd":{"lch":[70.0644466506374215,52.8759829560521695,296.254085335195782],"luv":[70.0644466506374215,23.3898305690433475,-47.4213601610093534],"rgb":[0.8,0.6,0.866666666666666696],"xyz":[0.493417737833729442,0.408410947380014266,0.73692873778519119],"hpluv":[296.254085335195782,95.7635162234915498,70.0644466506374215],"hsluv":[296.254085335195782,58.3905887561973813,70.0644466506374215]},"#cc99ee":{"lch":[70.7283706212672,65.1265075826905218,288.858843135035954],"luv":[70.7283706212672,21.0513451251656711,-61.6303728557552191],"rgb":[0.8,0.6,0.933333333333333348],"xyz":[0.517229918099904151,0.417935819486484283,0.862339553853714413],"hpluv":[288.858843135035954,116.843205481858362,70.7283706212672],"hsluv":[288.858843135035954,78.6530020758075494,70.7283706212672]},"#cc99ff":{"lch":[71.4465289765693115,78.0706881495843561,283.894640570210413],"luv":[71.4465289765693115,18.7476796475605703,-75.7862576987549517],"rgb":[0.8,0.6,1],"xyz":[0.543400966509735572,0.428404238850417,1.00017374214549637],"hpluv":[283.894640570210413,138.658404713871533,71.4465289765693115],"hsluv":[283.894640570210413,99.9999999999978,71.4465289765693115]},"#990000":{"lch":[31.2857235930303546,105.214874065330946,12.1770506300617765],"luv":[31.2857235930303546,102.847587834444283,22.1933188419334826],"rgb":[0.6,0,0],"xyz":[0.131365760434599882,0.067735470224092,0.00615777002037173893],"hpluv":[12.1770506300617765,426.746789183125316,31.2857235930303546],"hsluv":[12.1770506300617765,100.000000000002217,31.2857235930303546]},"#990011":{"lch":[31.379701704172021,102.819321078199806,10.8595456684147944],"luv":[31.379701704172021,100.978030711674904,19.3713732237542438],"rgb":[0.6,0,0.0666666666666666657],"xyz":[0.132377425934237014,0.0681401364239468538,0.0114858749851273704],"hpluv":[10.8595456684147944,415.781582167217948,31.379701704172021],"hsluv":[10.8595456684147944,99.9999999999964473,31.379701704172021]},"#990022":{"lch":[31.5529326060038784,98.7447775317108807,8.37468971343924729],"luv":[31.5529326060038784,97.6918390895904309,14.3817824027706251],"rgb":[0.6,0,0.133333333333333331],"xyz":[0.134252784072714015,0.0688902796793376682,0.0213627611811065682],"hpluv":[8.37468971343924729,397.112659756655944,31.5529326060038784],"hsluv":[8.37468971343924729,99.999999999996632,31.5529326060038784]},"#990033":{"lch":[31.8354354483696653,92.9837515463916162,4.18138532137367758],"luv":[31.8354354483696653,92.7362491408617586,6.7798338419980837],"rgb":[0.6,0,0.2],"xyz":[0.137340534805171777,0.070125379972320781,0.0376249150387177114],"hpluv":[4.18138532137367758,370.62575576901952,31.8354354483696653],"hsluv":[4.18138532137367758,99.9999999999969,31.8354354483696653]},"#990044":{"lch":[32.2375108843075537,86.4821897260425771,357.977822115898675],"luv":[32.2375108843075537,86.4283323676992552,-3.05163955108307716],"rgb":[0.6,0,0.266666666666666663],"xyz":[0.141798530049390636,0.071908578070008361,0.0611036899916043499],"hpluv":[357.977822115898675,340.411718586576399,32.2375108843075537],"hsluv":[357.977822115898675,99.9999999999971294,32.2375108843075537]},"#990055":{"lch":[32.7650133258702,80.5606445545256804,349.629319937368109],"luv":[32.7650133258702,79.244583226626645,-14.502188809930411],"rgb":[0.6,0,0.333333333333333315],"xyz":[0.147760944088752622,0.0742935436857532,0.0925057372655782584],"hpluv":[349.629319937368109,311.998071704954214,32.7650133258702],"hsluv":[349.629319937368109,99.9999999999974847,32.7650133258702]},"#990066":{"lch":[33.4199981031921354,76.5714397631706589,339.419101050621862],"luv":[33.4199981031921354,71.6844038684648268,-26.9171252073414813],"rgb":[0.6,0,0.4],"xyz":[0.155345939862260807,0.0773275419951565124,0.13245338167272222],"hpluv":[339.419101050621862,290.7366076723265,33.4199981031921354],"hsluv":[339.419101050621862,99.9999999999978257,33.4199981031921354]},"#990077":{"lch":[34.2012599030024091,75.4745938555541187,328.234093427391315],"luv":[34.2012599030024091,64.1689603650959413,-39.7335984190155429],"rgb":[0.6,0,0.466666666666666674],"xyz":[0.164659952882462712,0.0810531472032373218,0.181507183579120129],"hpluv":[328.234093427391315,280.025774017920355,34.2012599030024091],"hsluv":[328.234093427391315,99.9999999999982094,34.2012599030024091]},"#990088":{"lch":[35.1048906557013396,77.5195253213057214,317.327493504651898],"luv":[35.1048906557013396,56.9954501323273419,-52.5432723595889613],"rgb":[0.6,0,0.533333333333333326],"xyz":[0.175800369984406568,0.0855093140440149196,0.240180046982692297],"hpluv":[317.327493504651898,280.209468657326,35.1048906557013396],"hsluv":[317.327493504651898,99.9999999999985505,35.1048906557013396]},"#990099":{"lch":[36.1248689761228263,82.286593786153162,307.715012949243601],"luv":[36.1248689761228263,50.3375351282041592,-65.0939019735657922],"rgb":[0.6,0,0.6],"xyz":[0.188857334093479268,0.0907320996876440772,0.308946724623810232],"hpluv":[307.715012949243601,289.042783730483336,36.1248689761228263],"hsluv":[307.715012949243601,99.9999999999988205,36.1248689761228263]},"#9900aa":{"lch":[37.2536516336468,89.0432435337247,299.813571633796073],"luv":[37.2536516336468,44.2704748017611038,-77.2581664281054685],"rgb":[0.6,0,0.66666666666666663],"xyz":[0.203915027058731824,0.0967551768737451856,0.388250574240808777],"hpluv":[299.813571633796073,303.299328566743952,37.2536516336468],"hsluv":[299.813571633796073,99.9999999999990905,37.2536516336468]},"#9900bb":{"lch":[38.4827280957899163,97.0854614833978786,293.557760104203282],"luv":[38.4827280957899163,38.802472411223853,-88.9941288300556579],"rgb":[0.6,0,0.733333333333333282],"xyz":[0.221052619166068443,0.103610213716679944,0.478508559339450579],"hpluv":[293.557760104203282,320.130957524774431,38.4827280957899163],"hsluv":[293.557760104203282,99.9999999999993179,38.4827280957899163]},"#9900cc":{"lch":[39.8031058181596933,105.884836559305498,288.673688741635],"luv":[39.8031058181596933,33.9019931565070394,-100.310784431221492],"rgb":[0.6,0,0.8],"xyz":[0.240344994609790197,0.111327163894168746,0.580115070009720735],"hpluv":[288.673688741635,337.564008898092311,39.8031058181596933],"hsluv":[288.673688741635,99.9999999999995879,39.8031058181596933]},"#9900dd":{"lch":[41.2057071388761145,115.092674624289529,284.860629917023232],"luv":[41.2057071388761145,29.5176685469448401,-111.243116621772529],"rgb":[0.6,0,0.866666666666666696],"xyz":[0.26186332016042374,0.11993449411442228,0.693444917909726732],"hpluv":[284.860629917023232,354.429316861661562,41.2057071388761145],"hsluv":[284.860629917023232,99.9999999999996732,41.2057071388761145]},"#9900ee":{"lch":[42.6816722484951754,124.494824438150232,281.862271937449748],"luv":[42.6816722484951754,25.5911328567321625,-121.836181945245357],"rgb":[0.6,0,0.933333333333333348],"xyz":[0.285675500426598505,0.129459366220892297,0.81885573397825],"hpluv":[281.862271937449748,370.125661914021862,42.6816722484951754],"hsluv":[281.862271937449748,99.9999999999998437,42.6816722484951754]},"#9900ff":{"lch":[44.2225734052255817,133.965544030308308,279.479958267333473],"luv":[44.2225734052255817,22.0644732467518,-132.136013288126151],"rgb":[0.6,0,1],"xyz":[0.311846548836429871,0.139927785584825,0.956689922270031801],"hpluv":[279.479958267333473,384.404468177447882,44.2225734052255817],"hsluv":[279.479958267333473,99.9999999999999574,44.2225734052255817]},"#bb0000":{"lch":[38.8409426943877918,130.623313921981463,12.1770506300617818],"luv":[38.8409426943877918,127.684349491075153,27.5528044852332741],"rgb":[0.733333333333333282,0,0],"xyz":[0.20493059501477473,0.105667338054495463,0.00960612164131736251],"hpluv":[12.1770506300617818,426.746789183125145,38.8409426943877918],"hsluv":[12.1770506300617818,100.000000000002217,38.8409426943877918]},"#bb0011":{"lch":[38.9108602521517142,128.680110500437479,11.3344428162225856],"luv":[38.9108602521517142,126.170422440132384,25.2902222149853806],"rgb":[0.733333333333333282,0,0.0666666666666666657],"xyz":[0.205942260514411862,0.106072004254350316,0.014934226606072994],"hpluv":[11.3344428162225856,419.642938315359174,38.9108602521517142],"hsluv":[11.3344428162225856,99.9999999999964189,38.9108602521517142]},"#bb0022":{"lch":[39.0399998564474373,125.270257566289573,9.75441483214293292],"luv":[39.0399998564474373,123.459226352671962,21.2239689767074431],"rgb":[0.733333333333333282,0,0.133333333333333331],"xyz":[0.207817618652888864,0.10682214750974113,0.0248111128020521918],"hpluv":[9.75441483214293292,407.171610230013243,39.0399998564474373],"hsluv":[9.75441483214293292,99.9999999999965326,39.0399998564474373]},"#bb0033":{"lch":[39.2513155564018916,120.169209623826248,7.10634666793171554],"luv":[39.2513155564018916,119.246098647877318,14.866300779810846],"rgb":[0.733333333333333282,0,0.2],"xyz":[0.210905369385346597,0.108057247802724243,0.041073266659663335],"hpluv":[7.10634666793171554,388.488631169232178,39.2513155564018916],"hsluv":[7.10634666793171554,99.9999999999967173,39.2513155564018916]},"#bb0044":{"lch":[39.5535843326651886,113.833969399977519,3.19865110237705785],"luv":[39.5535843326651886,113.656624965458903,6.3517077086437],"rgb":[0.733333333333333282,0,0.266666666666666663],"xyz":[0.215363364629565485,0.109840445900411823,0.0645520416125499735],"hpluv":[3.19865110237705785,365.195452768261646,39.5535843326651886],"hsluv":[3.19865110237705785,99.9999999999968878,39.5535843326651886]},"#bb0055":{"lch":[39.9527871554326666,107.03859947839959,357.869864695501747],"luv":[39.9527871554326666,106.964633924407806,-3.97855095665153691],"rgb":[0.733333333333333282,0,0.333333333333333315],"xyz":[0.221325778668927498,0.112225411516156656,0.095954088886523875],"hpluv":[357.869864695501747,339.963790558847222,39.9527871554326666],"hsluv":[357.869864695501747,99.9999999999971436,39.9527871554326666]},"#bb0066":{"lch":[40.452535568346093,100.749762000256624,351.053086521713055],"luv":[40.452535568346093,99.5239253968718884,-15.6685295004423661],"rgb":[0.733333333333333282,0,0.4],"xyz":[0.228910774442435655,0.115259409825559975,0.135901733293667837],"hpluv":[351.053086521713055,316.036764522848955,40.452535568346093],"hsluv":[351.053086521713055,99.999999999997442,40.452535568346093]},"#bb0077":{"lch":[41.0543478797665813,95.9494038996296581,342.883287985183927],"luv":[41.0543478797665813,91.699536963203,-28.2397420212797314],"rgb":[0.733333333333333282,0,0.466666666666666674],"xyz":[0.238224787462637588,0.118985015033640784,0.184955535200065746],"hpluv":[342.883287985183927,296.56674422547627,41.0543478797665813],"hsluv":[342.883287985183927,99.9999999999977,41.0543478797665813]},"#bb0088":{"lch":[41.7578935904565398,93.4210879643116243,333.788939203308246],"luv":[41.7578935904565398,83.8148890966975699,-41.2621381189091565],"rgb":[0.733333333333333282,0,0.533333333333333326],"xyz":[0.249365204564581389,0.123441181874418382,0.243628398603637913],"hpluv":[333.788939203308246,283.887103643995431,41.7578935904565398],"hsluv":[333.788939203308246,99.9999999999980531,41.7578935904565398]},"#bb0099":{"lch":[42.5612451572515,93.5592166386053918,324.452137443226093],"luv":[42.5612451572515,76.1225984195714318,-54.3937223205229472],"rgb":[0.733333333333333282,0,0.6],"xyz":[0.262422168673654088,0.12866396751804754,0.31239507624475582],"hpluv":[324.452137443226093,278.940502109978524,42.5612451572515],"hsluv":[324.452137443226093,99.9999999999983089,42.5612451572515]},"#bb00aa":{"lch":[43.461144448190268,96.3048592888224562,315.591494301740738],"luv":[43.461144448190268,68.7971872487792,-67.3911934105362747],"rgb":[0.733333333333333282,0,0.66666666666666663],"xyz":[0.277479861638906644,0.134687044704148634,0.391698925861754421],"hpluv":[315.591494301740738,281.181257774391042,43.461144448190268],"hsluv":[315.591494301740738,99.9999999999985647,43.461144448190268]},"#bb00bb":{"lch":[44.4532771259814652,101.257357078489918,307.715012949243601],"luv":[44.4532771259814652,61.9426024872754866,-80.100976021670192],"rgb":[0.733333333333333282,0,0.733333333333333282],"xyz":[0.294617453746243319,0.141542081547083393,0.481956910960396168],"hpluv":[307.715012949243601,289.042783730483507,44.4532771259814652],"hsluv":[307.715012949243601,99.9999999999988205,44.4532771259814652]},"#bb00cc":{"lch":[45.5325428123826796,107.876917991024385,301.028560594476971],"luv":[45.5325428123826796,55.6068066637028551,-92.44085940701639],"rgb":[0.733333333333333282,0,0.8],"xyz":[0.313909829189965,0.149259031724572194,0.583563421630666324],"hpluv":[301.028560594476971,300.639438898355309,45.5325428123826796],"hsluv":[301.028560594476971,99.999999999999,45.5325428123826796]},"#bb00dd":{"lch":[46.6933085129957348,115.650155059812704,295.504945579136574],"luv":[46.6933085129957348,49.7976850024997,-104.379830109799173],"rgb":[0.733333333333333282,0,0.866666666666666696],"xyz":[0.335428154740598616,0.157866361944825728,0.696893269530672321],"hpluv":[295.504945579136574,314.290242754568055,46.6933085129957348],"hsluv":[295.504945579136574,99.9999999999992468,46.6933085129957348]},"#bb00ee":{"lch":[47.929635203682146,124.167261181765113,290.999747870951808],"luv":[47.929635203682146,44.4970566855085821,-115.920320460682433],"rgb":[0.733333333333333282,0,0.933333333333333348],"xyz":[0.359240335006773326,0.167391234051295773,0.822304085599195544],"hpluv":[290.999747870951808,328.732244305823656,47.929635203682146],"hsluv":[290.999747870951808,99.9999999999993889,47.929635203682146]},"#bb00ff":{"lch":[49.2354711183318727,133.13261796854033,287.33664116340708],"luv":[49.2354711183318727,39.6715752597668896,-127.084460433075606],"rgb":[0.733333333333333282,0,1],"xyz":[0.385411383416604747,0.177859653415228469,0.96013827389097739],"hpluv":[287.33664116340708,343.119737385630629,49.2354711183318727],"hsluv":[287.33664116340708,99.9999999999995595,49.2354711183318727]},"#991100":{"lch":[32.2007428060931531,101.551746681272988,13.5001929330929755],"luv":[32.2007428060931531,98.7457840078795,23.707116962774041],"rgb":[0.6,0.0666666666666666657,0],"xyz":[0.133370160695528289,0.071744270745948871,0.00682590344068119],"hpluv":[13.5001929330929755,400.185025755779861,32.2007428060931531],"hsluv":[13.5001929330929755,100.000000000002359,32.2007428060931531]},"#991111":{"lch":[32.2911967351305,99.2607003603350506,12.1770506300617907],"luv":[32.2911967351305,97.0273802968116854,20.9373854328113431],"rgb":[0.6,0.0666666666666666657,0.0666666666666666657],"xyz":[0.134381826195165421,0.0721489369458037239,0.0121540084054368204],"hpluv":[12.1770506300617907,390.060992150638072,32.2911967351305],"hsluv":[12.1770506300617907,91.4033806551417,32.2911967351305]},"#991122":{"lch":[32.4579836187547883,95.3555453821432337,9.67722696349737355],"luv":[32.4579836187547883,93.9986702773161085,16.0290368151797],"rgb":[0.6,0.0666666666666666657,0.133333333333333331],"xyz":[0.136257184333642423,0.0728990802011945382,0.0220308946014160165],"hpluv":[9.67722696349737355,372.789562407290305,32.4579836187547883],"hsluv":[9.67722696349737355,91.6870397393079,32.4579836187547883]},"#991133":{"lch":[32.7301206059751877,89.8170234432985382,5.44607482402752385],"luv":[32.7301206059751877,89.4115862394302,8.52443231910342547],"rgb":[0.6,0.0666666666666666657,0.2],"xyz":[0.139344935066100184,0.0741341804941776511,0.0382930484590271597],"hpluv":[5.44607482402752385,348.217328437078379,32.7301206059751877],"hsluv":[5.44607482402752385,92.1153976677825312,32.7301206059751877]},"#991144":{"lch":[33.1177416447746893,83.547386161100178,359.159050762907725],"luv":[33.1177416447746893,83.5383872622118275,-1.22620878349376072],"rgb":[0.6,0.0666666666666666657,0.266666666666666663],"xyz":[0.143802930310319044,0.0759173785918652311,0.0617718234119138],"hpluv":[359.159050762907725,320.119020896680809,33.1177416447746893],"hsluv":[359.159050762907725,92.6613616101701609,33.1177416447746893]},"#991155":{"lch":[33.6267967661613341,77.8365467514172451,350.652860745276428],"luv":[33.6267967661613341,76.8030661853154,-12.6418762341523276],"rgb":[0.6,0.0666666666666666657,0.333333333333333315],"xyz":[0.149765344349681029,0.078302344207610064,0.0931738706858877136],"hpluv":[350.652860745276428,293.722615948770908,33.6267967661613341],"hsluv":[350.652860745276428,93.2833986807069664,33.6267967661613341]},"#991166":{"lch":[34.2596587707945375,74.039902428482776,340.197584074025258],"luv":[34.2596587707945375,69.661662743272629,-25.0830599301957804],"rgb":[0.6,0.0666666666666666657,0.4],"xyz":[0.157350340123189214,0.0813363425170133825,0.133121515093031662],"hpluv":[340.197584074025258,274.234525914752396,34.2596587707945375],"hsluv":[340.197584074025258,93.9371476037906774,34.2596587707945375]},"#991177":{"lch":[35.0156115165229096,73.1458980715855773,328.71391057162549],"luv":[35.0156115165229096,62.509382661628635,-37.9855167657472848],"rgb":[0.6,0.0666666666666666657,0.466666666666666674],"xyz":[0.16666435314339112,0.0850619477250941919,0.182175316999429571],"hpluv":[328.71391057162549,265.074278305330154,35.0156115165229096],"hsluv":[328.71391057162549,94.5844210689808165,35.0156115165229096]},"#991188":{"lch":[35.8913494409224185,75.4242755669397269,317.528981112118743],"luv":[35.8913494409224185,55.6343762077295239,-50.9277677576239398],"rgb":[0.6,0.0666666666666666657,0.533333333333333326],"xyz":[0.177804770245334975,0.0895181145658717897,0.240848180403001738],"hpluv":[317.528981112118743,266.661726649655066,35.8913494409224185],"hsluv":[317.528981112118743,95.1976582537924116,35.8913494409224185]},"#991199":{"lch":[36.8815072257793,80.448343562419069,307.715012949243601],"luv":[36.8815072257793,49.2130143411107568,-63.6397297401445599],"rgb":[0.6,0.0666666666666666657,0.6],"xyz":[0.190861734354407675,0.0947409002095009473,0.309614858044119645],"hpluv":[307.715012949243601,276.788327826692239,36.8815072257793],"hsluv":[307.715012949243601,95.7603314825458511,36.8815072257793]},"#9911aa":{"lch":[37.9791974354050694,87.4639739592788,299.695850237394552],"luv":[37.9791974354050694,43.3292813921454609,-75.9771025690614152],"rgb":[0.6,0.0666666666666666657,0.66666666666666663],"xyz":[0.205919427319660231,0.100763977395602056,0.388918707661118246],"hpluv":[299.695850237394552,292.228621346341356,37.9791974354050694],"hsluv":[299.695850237394552,96.26500390067784,37.9791974354050694]},"#9911bb":{"lch":[39.176522525078866,95.7489617369993624,293.383950362709356],"luv":[39.176522525078866,38.0018814997404846,-87.8847010360338459],"rgb":[0.6,0.0666666666666666657,0.733333333333333282],"xyz":[0.22305701942699685,0.107619014238536814,0.47917669275976],"hpluv":[293.383950362709356,310.132668732371314,39.176522525078866],"hsluv":[293.383950362709356,96.7106400677762537,39.176522525078866]},"#9911cc":{"lch":[40.465031277763515,104.765415075180798,288.480743990765689],"luv":[40.465031277763515,33.2091620218187842,-99.3626879350770622],"rgb":[0.6,0.0666666666666666657,0.8],"xyz":[0.242349394870718604,0.115335964416025616,0.580783203430030204],"hpluv":[288.480743990765689,328.531778006508034,40.465031277763515],"hsluv":[288.480743990765689,97.1001366995766,40.465031277763515]},"#9911dd":{"lch":[41.8361001822542917,114.161763941518927,284.668123617886636],"luv":[41.8361001822542917,28.9080153835727351,-110.441092863219225],"rgb":[0.6,0.0666666666666666657,0.866666666666666696],"xyz":[0.263867720421352148,0.12394329463627915,0.694113051330036201],"hpluv":[284.668123617886636,346.265164959266087,41.8361001822542917],"hsluv":[284.668123617886636,97.4384492036098,41.8361001822542917]},"#9911ee":{"lch":[43.2812320372341617,123.724619665436521,281.679545129349094],"luv":[43.2812320372341617,25.0465268392882301,-121.162919264293635],"rgb":[0.6,0.0666666666666666657,0.933333333333333348],"xyz":[0.287679900687526913,0.133468166742749167,0.819523867398559425],"hpluv":[281.679545129349094,362.740326129136179,43.2812320372341617],"hsluv":[281.679545129349094,97.7313369542794561,43.2812320372341617]},"#9911ff":{"lch":[44.7922739406791948,133.33068560825987,279.310828677429186],"luv":[44.7922739406791948,21.5716156333953251,-131.574074664174219],"rgb":[0.6,0.0666666666666666657,1],"xyz":[0.313850949097358278,0.143936586106681863,0.95735805569034127],"hpluv":[279.310828677429186,377.716823123197173,44.7922739406791948],"hsluv":[279.310828677429186,99.9999999999993179,44.7922739406791948]},"#bb1100":{"lch":[39.5258701457598747,127.514079962112703,13.0219609303782402],"luv":[39.5258701457598747,124.2348987579322,28.7320469022032583],"rgb":[0.733333333333333282,0.0666666666666666657,0],"xyz":[0.206934995275703137,0.109676138576352333,0.0102742550616268143],"hpluv":[13.0219609303782402,409.370014873310311,39.5258701457598747],"hsluv":[13.0219609303782402,100.000000000002203,39.5258701457598747]},"#bb1111":{"lch":[39.5940766091873897,125.63034182067031,12.177050630061796],"luv":[39.5940766091873897,122.803716963532779,26.4996204863200759],"rgb":[0.733333333333333282,0.0666666666666666657,0.0666666666666666657],"xyz":[0.20794666077534027,0.110080804776207186,0.0156023600263824457],"hpluv":[12.177050630061796,402.627698793753552,39.5940766091873897],"hsluv":[12.177050630061796,94.3481495348726753,39.5940766091873897]},"#bb1122":{"lch":[39.7200723855077413,122.321798686612851,10.5915831721034426],"luv":[39.7200723855077413,120.23772406897838,22.4835972353282152],"rgb":[0.733333333333333282,0.0666666666666666657,0.133333333333333331],"xyz":[0.209822018913817271,0.110830948031598,0.0254792462223616401],"hpluv":[10.5915831721034426,390.780742551338619,39.7200723855077413],"hsluv":[10.5915831721034426,94.4721603032542561,39.7200723855077413]},"#bb1133":{"lch":[39.9262897734852,117.365304386249704,7.93115519261489421],"luv":[39.9262897734852,116.242655940639409,16.194431559742192],"rgb":[0.733333333333333282,0.0666666666666666657,0.2],"xyz":[0.212909769646275,0.112066048324581113,0.0417414000799727902],"hpluv":[7.93115519261489421,373.009679290071517,39.9262897734852],"hsluv":[7.93115519261489421,94.6648992051481173,39.9262897734852]},"#bb1144":{"lch":[40.2213637516280755,111.198866264944101,3.99799547396429888],"luv":[40.2213637516280755,110.928262725239179,7.75295991020434627],"rgb":[0.733333333333333282,0.0666666666666666657,0.266666666666666663],"xyz":[0.217367764890493892,0.113849246422268693,0.0652201750328594287],"hpluv":[3.99799547396429888,350.818828640487084,40.2213637516280755],"hsluv":[3.99799547396429888,94.920595316052,40.2213637516280755]},"#bb1155":{"lch":[40.6112374139617245,104.574630781207446,358.621384873601698],"luv":[40.6112374139617245,104.544360517453882,-2.51596641918957786],"rgb":[0.733333333333333282,0.0666666666666666657,0.333333333333333315],"xyz":[0.223330178929855905,0.116234212038013526,0.0966222223068333302],"hpluv":[358.621384873601698,326.752894857139097,40.6112374139617245],"hsluv":[358.621384873601698,95.2265760165129791,40.6112374139617245]},"#bb1166":{"lch":[41.0995768863194755,98.4443929415329251,351.723860164231496],"luv":[41.0995768863194755,97.4191751888273672,-14.1704907168206535],"rgb":[0.733333333333333282,0.0666666666666666657,0.4],"xyz":[0.230915174703364062,0.119268210347416845,0.136569866713977306],"hpluv":[351.723860164231496,303.943570446579713,41.0995768863194755],"hsluv":[351.723860164231496,95.5663396203906643,41.0995768863194755]},"#bb1177":{"lch":[41.688035181331955,93.7889310063258,343.43446060875408],"luv":[41.688035181331955,89.8961490854954093,-26.7403432832812094],"rgb":[0.733333333333333282,0.0666666666666666657,0.466666666666666674],"xyz":[0.240229187723566,0.122993815555497654,0.185623668620375215],"hpluv":[343.43446060875408,285.48249634694713,41.688035181331955],"hsluv":[343.43446060875408,95.9227087501184883,41.688035181331955]},"#bb1188":{"lch":[42.3764815581906475,91.4027083669889606,334.189166164708297],"luv":[42.3764815581906475,82.2840505377722309,-39.7968607294366805],"rgb":[0.733333333333333282,0.0666666666666666657,0.533333333333333326],"xyz":[0.251369604825509796,0.127449982396275252,0.244296532023947383],"hpluv":[334.189166164708297,273.699179760613617,42.3764815581906475],"hsluv":[334.189166164708297,96.2803174994205,42.3764815581906475]},"#bb1199":{"lch":[43.1632358766101092,91.692942572352564,324.693570122963422],"luv":[43.1632358766101092,74.8281105443768269,-52.9938637007650755],"rgb":[0.733333333333333282,0.0666666666666666657,0.6],"xyz":[0.264426568934582495,0.13267276803990441,0.31306320966506529],"hpluv":[324.693570122963422,269.563595898888195,43.1632358766101092],"hsluv":[324.693570122963422,96.6270445785018239,43.1632358766101092]},"#bb11aa":{"lch":[44.0453166933651,94.6050581990027553,315.694091730478135],"luv":[44.0453166933651,67.7013389352867847,-66.0806003544619784],"rgb":[0.733333333333333282,0.0666666666666666657,0.66666666666666663],"xyz":[0.279484261899835051,0.138695845226005504,0.39236705928206389],"hpluv":[315.694091730478135,272.554870225691275,44.0453166933651],"hsluv":[315.694091730478135,96.9544350138240105,44.0453166933651]},"#bb11bb":{"lch":[45.0186979872658242,99.7328976909900717,307.715012949243601],"luv":[45.0186979872658242,61.0100383302365472,-78.8950321933172205],"rgb":[0.733333333333333282,0.0666666666666666657,0.733333333333333282],"xyz":[0.296621854007171726,0.145550882068940263,0.482625044380705637],"hpluv":[307.715012949243601,281.115526817766181,45.0186979872658242],"hsluv":[307.715012949243601,97.2574105430315115,45.0186979872658242]},"#bb11cc":{"lch":[46.0785638011469771,106.527215460047387,300.962972083371881],"luv":[46.0785638011469771,54.8065495444917161,-91.3470840295328372],"rgb":[0.733333333333333282,0.0666666666666666657,0.8],"xyz":[0.315914229450893425,0.153267832246429064,0.584231555050975793],"hpluv":[300.962972083371881,293.360047474541318,46.0785638011469771],"hsluv":[300.962972083371881,97.5336183018287528,46.0785638011469771]},"#bb11dd":{"lch":[47.2195492447565,114.4666153484132,295.402801066287566],"luv":[47.2195492447565,49.1038079522977853,-103.3993330438134],"rgb":[0.733333333333333282,0.0666666666666666657,0.866666666666666696],"xyz":[0.337432555001527,0.161875162466682598,0.69756140295098179],"hpluv":[295.402801066287566,307.607082675331128,47.2195492447565],"hsluv":[295.402801066287566,97.7826899351839813,47.2195492447565]},"#bb11ee":{"lch":[48.4359581768354701,123.136484869400022,290.880608651701721],"luv":[48.4359581768354701,43.8885280811888,-115.049515465553711],"rgb":[0.733333333333333282,0.0666666666666666657,0.933333333333333348],"xyz":[0.361244735267701733,0.171400034573152643,0.822972219019505],"hpluv":[290.880608651701721,322.595409245987128,48.4359581768354701],"hsluv":[290.880608651701721,98.0055710342431325,48.4359581768354701]},"#bb11ff":{"lch":[49.7219510368964,132.239138283310353,287.21247838519713],"luv":[49.7219510368964,39.1316890181496575,-126.316667975763593],"rgb":[0.733333333333333282,0.0666666666666666657,1],"xyz":[0.387415783677533154,0.181868453937085339,0.960806407311286859],"hpluv":[287.21247838519713,337.482436204013879,49.7219510368964],"hsluv":[287.21247838519713,99.9999999999991758,49.7219510368964]},"#992200":{"lch":[33.8105832897308716,95.4307991554818358,16.0266852535062476],"luv":[33.8105832897308716,91.7217107848809263,26.3470149760569932],"rgb":[0.6,0.133333333333333331,0],"xyz":[0.137085784430296231,0.0791755182154848525,0.0080644446856038],"hpluv":[16.0266852535062476,358.158468302090569,33.8105832897308716],"hsluv":[16.0266852535062476,100.000000000002331,33.8105832897308716]},"#992211":{"lch":[33.8952997814050718,93.2922690289088195,14.6972380002076104],"luv":[33.8952997814050718,90.239744522062125,23.669304365681274],"rgb":[0.6,0.133333333333333331,0.0666666666666666657],"xyz":[0.138097449929933364,0.0795801844153397,0.0133925496503594314],"hpluv":[14.6972380002076104,349.257308780581,33.8952997814050718],"hsluv":[14.6972380002076104,92.1483909924374274,33.8952997814050718]},"#992222":{"lch":[34.0515850466810335,89.6330213591727,12.1770506300618084],"luv":[34.0515850466810335,87.6163196410811338,18.9065874902323579],"rgb":[0.6,0.133333333333333331,0.133333333333333331],"xyz":[0.139972808068410365,0.0803303276707305197,0.0232694358463386292],"hpluv":[12.1770506300618084,334.018122077437397,34.0515850466810335],"hsluv":[12.1770506300618084,78.2707991117683,34.0515850466810335]},"#992233":{"lch":[34.3068003204445446,84.4135643942939282,7.88651435003668233],"luv":[34.3068003204445446,83.6151628365757773,11.58250394183076],"rgb":[0.6,0.133333333333333331,0.2],"xyz":[0.143060558800868098,0.0815654279637136326,0.0395315897039497724],"hpluv":[7.88651435003668233,312.227643050581207,34.3068003204445446],"hsluv":[7.88651435003668233,79.3008398259009226,34.3068003204445446]},"#992244":{"lch":[34.6707661525426,78.4682482149262199,1.45557545410962708],"luv":[34.6707661525426,78.4429281517087134,1.99323879782018309],"rgb":[0.6,0.133333333333333331,0.266666666666666663],"xyz":[0.147518554045086986,0.0833486260614012126,0.0630103646568364],"hpluv":[1.45557545410962708,287.190351340663,34.6707661525426],"hsluv":[1.45557545410962708,80.6267396657693212,34.6707661525426]},"#992255":{"lch":[35.149531709850983,73.0418005898087443,352.659616011821072],"luv":[35.149531709850983,72.4431965063170082,-9.33209051330535821],"rgb":[0.6,0.133333333333333331,0.333333333333333315],"xyz":[0.153480968084449,0.0857335916771460455,0.0944124119308103193],"hpluv":[352.659616011821072,263.688538908073838,35.149531709850983],"hsluv":[352.659616011821072,82.1555178352066804,35.149531709850983]},"#992266":{"lch":[35.7459223236079495,69.5043059181480203,341.734006615383123],"luv":[35.7459223236079495,66.0021002604389793,-21.7846574995905513],"rgb":[0.6,0.133333333333333331,0.4],"xyz":[0.161065963857957156,0.0887675899865493639,0.134360056337954281],"hpluv":[341.734006615383123,246.731460763727796,35.7459223236079495],"hsluv":[341.734006615383123,83.7834402291280753,35.7459223236079495]},"#992277":{"lch":[36.4599553630224946,68.9051746310403104,329.662355423262511],"luv":[36.4599553630224946,59.4695672743266783,-34.8036443370302],"rgb":[0.6,0.133333333333333331,0.466666666666666674],"xyz":[0.17037997687815909,0.0924931951946301734,0.18341385824435219],"hpluv":[329.662355423262511,239.814275331209444,36.4599553630224946],"hsluv":[329.662355423262511,85.4170781092293225,36.4599553630224946]},"#992288":{"lch":[37.2892540647929,71.5563678213307242,317.925484235555643],"luv":[37.2892540647929,53.1144286836587369,-47.9496740488411],"rgb":[0.6,0.133333333333333331,0.533333333333333326],"xyz":[0.18152039398010289,0.0969493620354077712,0.242086721647924358],"hpluv":[317.925484235555643,243.502777039008038,37.2892540647929],"hsluv":[317.925484235555643,86.9852623869732,37.2892540647929]},"#992299":{"lch":[38.2294870734457888,77.015786119064046,307.715012949243771],"luv":[38.2294870734457888,47.1132010795059841,-60.9243596238766827],"rgb":[0.6,0.133333333333333331,0.6],"xyz":[0.194577358089175617,0.102172147679036929,0.310853399289042265],"hpluv":[307.715012949243771,255.635172818446421,38.2294870734457888],"hsluv":[307.715012949243771,88.441984096309227,38.2294870734457888]},"#9922aa":{"lch":[39.2748221448681178,84.484081905089468,299.468150353755561],"luv":[39.2748221448681178,41.5610713460956234,-73.5543162833468],"rgb":[0.6,0.133333333333333331,0.66666666666666663],"xyz":[0.209635051054428145,0.108195224865138023,0.390157248906040865],"hpluv":[299.468150353755561,272.960615272696657,39.2748221448681178],"hsluv":[299.468150353755561,89.7633288768494708,39.2748221448681178]},"#9922bb":{"lch":[40.4183688993281436,93.2013493596814726,293.05045530637],"luv":[40.4183688993281436,36.4922039393154094,-85.7601922462677919],"rgb":[0.6,0.133333333333333331,0.733333333333333282],"xyz":[0.22677264316176482,0.115050261708072782,0.480415234004682612],"hpluv":[293.05045530637,292.605673863240838,40.4183688993281436],"hsluv":[293.05045530637,90.9419133161227222,40.4183688993281436]},"#9922cc":{"lch":[41.6525852773545182,102.609691310931794,288.11294916137831],"luv":[41.6525852773545182,31.900454524651952,-97.5249186210648702],"rgb":[0.6,0.133333333333333331,0.8],"xyz":[0.246065018605486546,0.122767211885561583,0.582021744674952712],"hpluv":[288.11294916137831,312.597676326075884,41.6525852773545182],"hsluv":[288.11294916137831,91.9812251551093851,41.6525852773545182]},"#9922dd":{"lch":[42.969628845807982,112.350489371469905,284.303043907998301],"luv":[42.969628845807982,27.7562437333976213,-108.867917201630149],"rgb":[0.6,0.133333333333333331,0.866666666666666696],"xyz":[0.267583344156120062,0.131374542105815117,0.69535159257495871],"hpluv":[284.303043907998301,331.781902020401162,42.969628845807982],"hsluv":[284.303043907998301,92.8910125551507235,42.969628845807982]},"#9922ee":{"lch":[44.361642902098545,122.210344632057584,281.334390799049743],"luv":[44.361642902098545,24.018574136526297,-119.826860225637532],"rgb":[0.6,0.133333333333333331,0.933333333333333348],"xyz":[0.291395524422294827,0.140899414212285162,0.820762408643481933],"hpluv":[281.334390799049743,349.574442537581717,44.361642902098545],"hsluv":[281.334390799049743,93.6839973987951566,44.361642902098545]},"#9922ff":{"lch":[45.8209755847726612,132.069283427413211,278.992348895848238],"luv":[45.8209755847726612,20.6427685166220094,-130.446049127597433],"rgb":[0.6,0.133333333333333331,1],"xyz":[0.317566572832126193,0.151367833576217858,0.958596596935263889],"hpluv":[278.992348895848238,365.74366826955071,45.8209755847726612],"hsluv":[278.992348895848238,99.9999999999993179,45.8209755847726612]},"#bb2200":{"lch":[40.7526421249889452,122.145166616692975,14.6188079362681389],"luv":[40.7526421249889452,118.190884669319914,30.8278528104570881],"rgb":[0.733333333333333282,0.133333333333333331,0],"xyz":[0.21065061901047108,0.117107386045888315,0.0115127963065494235],"hpluv":[14.6188079362681389,380.329350781024857,40.7526421249889452],"hsluv":[14.6188079362681389,100.000000000002217,40.7526421249889452]},"#bb2211":{"lch":[40.8179368215716849,120.355879944515436,13.7706881972771793],"luv":[40.8179368215716849,116.896392622501153,28.6491052053056343],"rgb":[0.733333333333333282,0.133333333333333331,0.0666666666666666657],"xyz":[0.211662284510108212,0.117512052245743168,0.0168409012713050532],"hpluv":[13.7706881972771793,374.158477594779924,40.8179368215716849],"hsluv":[13.7706881972771793,94.6800257514418,40.8179368215716849]},"#bb2222":{"lch":[40.9385803904414161,117.208042434762348,12.1770506300618102],"luv":[40.9385803904414161,114.570915436608942,24.7230772236478238],"rgb":[0.733333333333333282,0.133333333333333331,0.133333333333333331],"xyz":[0.213537642648585213,0.118262195501133982,0.0267177874672842527],"hpluv":[12.1770506300618102,363.298797482753,40.9385803904414161],"hsluv":[12.1770506300618102,85.1321689328196101,40.9385803904414161]},"#bb2233":{"lch":[41.136111673530813,112.480172956632245,9.49666640172232235],"luv":[41.136111673530813,110.938654987290519,18.5581286223806821],"rgb":[0.733333333333333282,0.133333333333333331,0.2],"xyz":[0.216625393381042974,0.119497295794117095,0.042979941324895396],"hpluv":[9.49666640172232235,346.970109935861444,41.136111673530813],"hsluv":[9.49666640172232235,85.6217357677037398,41.136111673530813]},"#bb2244":{"lch":[41.4189140922405201,106.57909239531196,5.51995404221549],"luv":[41.4189140922405201,106.084859394728952,10.2520994439692306],"rgb":[0.733333333333333282,0.133333333333333331,0.266666666666666663],"xyz":[0.221083388625261834,0.121280493891804675,0.0664587162777820345],"hpluv":[5.51995404221549,326.522141050690152,41.4189140922405201],"hsluv":[5.51995404221549,86.2742612371986297,41.4189140922405201]},"#bb2255":{"lch":[41.7928521194743823,100.22104851316422,0.0579838467833058424],"luv":[41.7928521194743823,100.220997191859041,0.101424589714973798],"rgb":[0.733333333333333282,0.133333333333333331,0.333333333333333315],"xyz":[0.227045802664623819,0.123665459507549508,0.097860763551755936],"hpluv":[0.0579838467833058424,304.296010337655673,41.7928521194743823],"hsluv":[0.0579838467833058424,87.0597094656736772,41.7928521194743823]},"#bb2266":{"lch":[42.2616671880265216,94.3342448936905,353.011685321171171],"luv":[42.2616671880265216,93.6334344607092106,-11.4773564358193614],"rgb":[0.733333333333333282,0.133333333333333331,0.4],"xyz":[0.234630798438132,0.126699457816952826,0.137808407958899898],"hpluv":[353.011685321171171,283.244886623536,42.2616671880265216],"hsluv":[353.011685321171171,87.937804292381287,42.2616671880265216]},"#bb2277":{"lch":[42.8272221099346666,89.9002723684957,344.496654306277264],"luv":[42.8272221099346666,86.6292371691086771,-24.0298614109202155],"rgb":[0.733333333333333282,0.133333333333333331,0.466666666666666674],"xyz":[0.24394481145833391,0.13042506302503365,0.186862209865297807],"hpluv":[344.496654306277264,266.367005402095344,42.8272221099346666],"hsluv":[344.496654306277264,88.8655868844719805,42.8272221099346666]},"#bb2288":{"lch":[43.4897067779173554,87.7363054907772693,334.961981200390596],"luv":[43.4897067779173554,79.4914755013727898,-37.1317199141871441],"rgb":[0.733333333333333282,0.133333333333333331,0.533333333333333326],"xyz":[0.255085228560277766,0.13488122986581122,0.245535073268869974],"hpluv":[334.961981200390596,255.995416581420926,43.4897067779173554],"hsluv":[334.961981200390596,89.8036443318747786,43.4897067779173554]},"#bb2299":{"lch":[44.2478449340908639,88.2751432264235092,325.159464012784042],"luv":[44.2478449340908639,72.4514028672968635,-50.4310929309115],"rgb":[0.733333333333333282,0.133333333333333331,0.6],"xyz":[0.268142192669350465,0.140104015509440405,0.314301750909987909],"hpluv":[325.159464012784042,253.154489640486645,44.2478449340908639],"hsluv":[325.159464012784042,90.7199745934293844,44.2478449340908639]},"#bb22aa":{"lch":[45.0991127685299062,91.4705260319517919,315.891475419905078],"luv":[45.0991127685299062,65.6779186933914758,-63.6652819727222195],"rgb":[0.733333333333333282,0.133333333333333331,0.66666666666666663],"xyz":[0.283199885634603,0.146127092695541499,0.39360560052698651],"hpluv":[315.891475419905078,257.366789521381691,45.0991127685299062],"hsluv":[315.891475419905078,91.5914304646717,45.0991127685299062]},"#bb22bb":{"lch":[46.0399667792549678,96.9048462314552,307.715012949243658],"luv":[46.0399667792549678,59.2800221375787615,-76.6578645574617497],"rgb":[0.733333333333333282,0.133333333333333331,0.733333333333333282],"xyz":[0.30033747774193964,0.152982129538476258,0.483863585625628256],"hpluv":[307.715012949243658,267.0851991180906,46.0399667792549678],"hsluv":[307.715012949243658,92.4033444706684,46.0399667792549678]},"#bb22cc":{"lch":[47.066072507765945,104.009692587178989,300.837938637053412],"luv":[47.066072507765945,53.3165662395356,-89.3048706202785496],"rgb":[0.733333333333333282,0.133333333333333331,0.8],"xyz":[0.319629853185661394,0.16069907971596506,0.585470096295898412],"hpluv":[300.837938637053412,280.417543020287,47.066072507765945],"hsluv":[300.837938637053412,93.1481326309998,47.066072507765945]},"#bb22dd":{"lch":[48.1725242595717589,112.247547842848149,295.20893392435471],"luv":[48.1725242595717589,47.808517424121824,-101.557164486017044],"rgb":[0.733333333333333282,0.133333333333333331,0.866666666666666696],"xyz":[0.341148178736294938,0.169306409936218594,0.69879994419590441],"hpluv":[295.20893392435471,295.676483579310798,48.1725242595717589],"hsluv":[295.20893392435471,93.8235497257627316,48.1725242595717589]},"#bb22ee":{"lch":[49.3540469689012724,121.194036197623419,290.655339197387],"luv":[49.3540469689012724,42.7506603945962169,-113.403595382583688],"rgb":[0.733333333333333282,0.133333333333333331,0.933333333333333348],"xyz":[0.364960359002469703,0.178831282042688611,0.824210760264427633],"hpluv":[290.655339197387,311.600255956968681,49.3540469689012724],"hsluv":[290.655339197387,94.4310255689598,49.3540469689012724]},"#bb22ff":{"lch":[50.6051737457033397,130.547024148317973,286.97844412333734],"luv":[50.6051737457033397,38.1212848334983647,-124.85709093449519],"rgb":[0.733333333333333282,0.133333333333333331,1],"xyz":[0.391131407412301069,0.189299701406621335,0.962044948556209478],"hpluv":[286.97844412333734,327.349274436557835,50.6051737457033397],"hsluv":[286.97844412333734,99.9999999999991616,50.6051737457033397]},"#993300":{"lch":[36.2545465004255476,86.9834057059747749,20.3835344027483316],"luv":[36.2545465004255476,81.5366895093473,30.2965531383769893],"rgb":[0.6,0.2,0],"xyz":[0.14320350651930705,0.0914109623935066423,0.0101036853819406816],"hpluv":[20.3835344027483316,304.448092478673459,36.2545465004255476],"hsluv":[20.3835344027483316,100.00000000000226,36.2545465004255476]},"#993311":{"lch":[36.3315413581227133,85.0112276295678839,19.0571063974297203],"luv":[36.3315413581227133,80.3520694714869279,27.7570487396543],"rgb":[0.6,0.2,0.0666666666666666657],"xyz":[0.144215172018944182,0.0918156285933615,0.0154317903466963131],"hpluv":[19.0571063974297203,296.914762557758195,36.3315413581227133],"hsluv":[19.0571063974297203,93.1288353931581,36.3315413581227133]},"#993322":{"lch":[36.4736730302835852,81.6150828115545863,16.5278497598068661],"luv":[36.4736730302835852,78.2428757472059573,23.217970134019108],"rgb":[0.6,0.2,0.133333333333333331],"xyz":[0.146090530157421183,0.0925657718487523096,0.0253086765426755109],"hpluv":[16.5278497598068661,283.942401799749632,36.4736730302835852],"hsluv":[16.5278497598068661,80.9121244795507124,36.4736730302835852]},"#993333":{"lch":[36.7060271438600836,76.7221326388105638,12.1770506300618369],"luv":[36.7060271438600836,74.9959199734098121,16.1832513417159589],"rgb":[0.6,0.2,0.2],"xyz":[0.149178280889878945,0.0938008721417354224,0.0415708304002866541],"hpluv":[12.1770506300618369,265.229979343802,36.7060271438600836],"hsluv":[12.1770506300618369,62.1516051360361459,36.7060271438600836]},"#993344":{"lch":[37.0379214664673668,71.077614272095758,5.55118145677439934],"luv":[37.0379214664673668,70.7442733159460744,6.87568495580995354],"rgb":[0.6,0.2,0.266666666666666663],"xyz":[0.153636276134097804,0.095584070239423,0.0650496053531732926],"hpluv":[5.55118145677439934,243.514912685509444,37.0379214664673668],"hsluv":[5.55118145677439934,64.3097526797848644,37.0379214664673668]},"#993355":{"lch":[37.4754277574064858,65.8722356173568073,356.298722520573506],"luv":[37.4754277574064858,65.7348379379961614,-4.25235305377523254],"rgb":[0.6,0.2,0.333333333333333315],"xyz":[0.15959869017345979,0.0979690358551678353,0.0964516526271472],"hpluv":[356.298722520573506,223.046355214908289,37.4754277574064858],"hsluv":[356.298722520573506,66.8387430740197885,37.4754277574064858]},"#993366":{"lch":[38.0218512407041942,62.5198646461221941,344.559385799457459],"luv":[38.0218512407041942,60.2633303059628602,-16.6452544529622166],"rgb":[0.6,0.2,0.4],"xyz":[0.167183685946967975,0.101003034164571154,0.136399297034291156],"hpluv":[344.559385799457459,208.652742449272864,38.0218512407041942],"hsluv":[344.559385799457459,69.5808124884664778,38.0218512407041942]},"#993377":{"lch":[38.6780657603296234,62.1964572713534949,331.414072321130675],"luv":[38.6780657603296234,54.6147414507713478,-29.7595247504508862],"rgb":[0.6,0.2,0.466666666666666674],"xyz":[0.17649769896716988,0.104728639372651963,0.185453098940689065],"hpluv":[331.414072321130675,204.051704505442274,38.6780657603296234],"hsluv":[331.414072321130675,72.3848063102254571,38.6780657603296234]},"#993388":{"lch":[39.4428302118465908,65.3028355433768155,318.6521895607018],"luv":[39.4428302118465908,49.0236963230770257,-43.1409031990296086],"rgb":[0.6,0.2,0.533333333333333326],"xyz":[0.187638116069113736,0.109184806213429561,0.244125962344261233],"hpluv":[318.6521895607018,210.08899125401652,39.4428302118465908],"hsluv":[318.6521895607018,75.1271370389489306,39.4428302118465908]},"#993399":{"lch":[40.3131218316236897,71.3679900272828149,307.715012949243942],"luv":[40.3131218316236897,43.6582502656979656,-56.4565955781255226],"rgb":[0.6,0.2,0.6],"xyz":[0.200695080178186436,0.114407591857058719,0.31289263998537914],"hpluv":[307.715012949243942,224.64479534599792,40.3131218316236897],"hsluv":[307.715012949243942,77.7202573427554313,40.3131218316236897]},"#9933aa":{"lch":[41.2844862133256925,79.5040030135899372,299.063916375798101],"luv":[41.2844862133256925,38.6218522191785354,-69.4927264276258825],"rgb":[0.6,0.2,0.66666666666666663],"xyz":[0.215752773143439,0.120430669043159827,0.39219648960237774],"hpluv":[299.063916375798101,244.366370382710016,41.2844862133256925],"hsluv":[299.063916375798101,80.1114582016200814,41.2844862133256925]},"#9933bb":{"lch":[42.3513893410192637,88.8787943355742,292.467029257400327],"luv":[42.3513893410192637,33.9651845170614,-82.1328577566084],"rgb":[0.6,0.2,0.733333333333333282],"xyz":[0.232890365250775611,0.127285705886094586,0.482454474701019487],"hpluv":[292.467029257400327,266.299173904300574,42.3513893410192637],"hsluv":[292.467029257400327,82.2764131802073848,42.3513893410192637]},"#9933cc":{"lch":[43.5075532005759911,98.8962106119506785,287.477062230467],"luv":[43.5075532005759911,29.700902070050418,-94.3308904316533],"rgb":[0.6,0.2,0.8],"xyz":[0.252182740694497365,0.135002656063583387,0.584060985371289698],"hpluv":[287.477062230467,288.439223502221068,43.5075532005759911],"hsluv":[287.477062230467,84.2111914011780129,43.5075532005759911]},"#9933dd":{"lch":[44.7462588110156716,109.182503226938962,283.677602435208257],"luv":[44.7462588110156716,25.8171133061496079,-106.086265234656878],"rgb":[0.6,0.2,0.866666666666666696],"xyz":[0.273701066245130908,0.143609986283836921,0.697390833271295696],"hpluv":[283.677602435208257,309.624731809924413,44.7462588110156716],"hsluv":[283.677602435208257,85.9249592908513478,44.7462588110156716]},"#9933ee":{"lch":[46.0606056636097208,119.52114969179982,280.747238708189116],"luv":[46.0606056636097208,22.2879081330132465,-117.42467532296601],"rgb":[0.6,0.2,0.933333333333333348],"xyz":[0.297513246511305618,0.153134858390306938,0.822801649339818919],"hpluv":[280.747238708189116,329.271729063034456,46.0606056636097208],"hsluv":[280.747238708189116,90.0471632149093324,46.0606056636097208]},"#9933ff":{"lch":[47.4437223771408512,129.794782591236896,278.453521985212944],"luv":[47.4437223771408512,19.0807517890999456,-128.38461940228359],"rgb":[0.6,0.2,1],"xyz":[0.323684294921137039,0.163603277754239662,0.960635837631600764],"hpluv":[278.453521985212944,347.150508646733101,47.4437223771408512],"hsluv":[278.453521985212944,99.999999999999261,47.4437223771408512]},"#bb3300":{"lch":[42.6640590509798585,114.303280915030754,17.3320761189885637],"luv":[42.6640590509798585,109.113245500432,34.0520143942012652],"rgb":[0.733333333333333282,0.2,0],"xyz":[0.216768341099481898,0.129342830223910105,0.0135520370028863052],"hpluv":[17.3320761189885637,339.966286272656077,42.6640590509798585],"hsluv":[17.3320761189885637,100.000000000002302,42.6640590509798585]},"#bb3311":{"lch":[42.7251747888925806,112.6335820513806,16.4819551739351198],"luv":[42.7251747888925806,108.005370801027979,31.9556518296457135],"rgb":[0.733333333333333282,0.2,0.0666666666666666657],"xyz":[0.21778000659911903,0.129747496423764958,0.0188801419676419349],"hpluv":[16.4819551739351198,334.52099084556113,42.7251747888925806],"hsluv":[16.4819551739351198,95.1490253673008368,42.7251747888925806]},"#bb3322":{"lch":[42.8381318005571785,109.688338781622747,14.8808580240480524],"luv":[42.8381318005571785,106.009603708482885,28.1690536980292627],"rgb":[0.733333333333333282,0.2,0.133333333333333331],"xyz":[0.219655364737596032,0.130497639679155786,0.0287570281636211345],"hpluv":[14.8808580240480524,324.914627119009424,42.8381318005571785],"hsluv":[14.8808580240480524,86.4181297836030211,42.8381318005571785]},"#bb3333":{"lch":[43.023174549414108,105.246046554892928,12.1770506300618351],"luv":[43.023174549414108,102.878058957341324,22.1998941575065203],"rgb":[0.733333333333333282,0.2,0.2],"xyz":[0.222743115470053765,0.131732739972138885,0.0450191820212322777],"hpluv":[12.1770506300618351,310.414975564112126,43.023174549414108],"hsluv":[12.1770506300618351,72.7398502888125762,43.023174549414108]},"#bb3344":{"lch":[43.2883038991094082,99.6704509181560496,8.14070356751482116],"luv":[43.2883038991094082,98.6661026897846085,14.1137863891102171],"rgb":[0.733333333333333282,0.2,0.266666666666666663],"xyz":[0.227201110714272653,0.133515938069826479,0.0684979569741189231],"hpluv":[8.14070356751482116,292.169703441320848,43.2883038991094082],"hsluv":[8.14070356751482116,73.8775464962187556,43.2883038991094082]},"#bb3355":{"lch":[43.6392404977582515,93.6285749325397632,2.54995992142812078],"luv":[43.6392404977582515,93.5358645919991289,4.16558266337591299],"rgb":[0.733333333333333282,0.2,0.333333333333333315],"xyz":[0.233163524753634666,0.135900903685571312,0.0999000042480928246],"hpluv":[2.54995992142812078,272.251672456619758,43.6392404977582515],"hsluv":[2.54995992142812078,75.2585830443305781,43.6392404977582515]},"#bb3366":{"lch":[44.0797950159491521,88.0182600678664784,355.263953050872033],"luv":[44.0797950159491521,87.7177339493983794,-7.26727295188107547],"rgb":[0.733333333333333282,0.2,0.4],"xyz":[0.240748520527142823,0.138934901994974602,0.1398476486552368],"hpluv":[355.263953050872033,253.380121177946961,44.0797950159491521],"hsluv":[355.263953050872033,76.8177075569623753,44.0797950159491521]},"#bb3377":{"lch":[44.6120878205181057,83.829516929164356,346.367941021132197],"luv":[44.6120878205181057,81.4679789337245239,-19.757437005625782],"rgb":[0.733333333333333282,0.2,0.466666666666666674],"xyz":[0.250062533547344756,0.142660507203055426,0.188901450561634709],"hpluv":[346.367941021132197,238.442541154787421,44.6120878205181057],"hsluv":[346.367941021132197,78.4827471040858597,44.6120878205181057]},"#bb3388":{"lch":[45.2367248685103078,81.924239827592,336.329010851829594],"luv":[45.2367248685103078,75.03162551729379,-32.8912791414593499],"rgb":[0.733333333333333282,0.2,0.533333333333333326],"xyz":[0.261202950649288557,0.147116674043833023,0.247574313965206849],"hpluv":[336.329010851829594,229.805591899252,45.2367248685103078],"hsluv":[336.329010851829594,80.18501002925629,45.2367248685103078]},"#bb3399":{"lch":[45.9529692994627226,82.7837935230449347,325.982908927834501],"luv":[45.9529692994627226,68.6170634488305495,-46.3125800806357404],"rgb":[0.733333333333333282,0.2,0.6],"xyz":[0.274259914758361312,0.152339459687462181,0.316340991606324784],"hpluv":[325.982908927834501,228.597286714525296,45.9529692994627226],"hsluv":[325.982908927834501,81.8663863744617402,45.9529692994627226]},"#bb33aa":{"lch":[46.7589216358443664,86.3774619636168524,316.238480868355282],"luv":[46.7589216358443664,62.3839556422166126,-59.7436859735489136],"rgb":[0.733333333333333282,0.2,0.66666666666666663],"xyz":[0.289317607723613812,0.158362536873563275,0.395644841223323385],"hpluv":[316.238480868355282,234.409537876970433,46.7589216358443664],"hsluv":[316.238480868355282,83.4827024616804181,46.7589216358443664]},"#bb33bb":{"lch":[47.6517090930198108,92.2657594707273461,307.715012949243828],"luv":[47.6517090930198108,56.4421334605023191,-72.9880534138084585],"rgb":[0.733333333333333282,0.2,0.733333333333333282],"xyz":[0.306455199830950487,0.165217573716498034,0.485902826321965131],"hpluv":[307.715012949243828,245.697877980233102,47.6517090930198108],"hsluv":[307.715012949243828,85.0039827354176,47.6517090930198108]},"#bb33cc":{"lch":[48.6276786348411179,99.8443429984049402,300.621719827817344],"luv":[48.6276786348411179,50.8574804969275078,-85.9209491700817551],"rgb":[0.733333333333333282,0.2,0.8],"xyz":[0.325747575274672241,0.172934523893986836,0.587509336992235287],"hpluv":[300.621719827817344,260.542905615006589,48.6276786348411179],"hsluv":[300.621719827817344,86.4127454729900535,48.6276786348411179]},"#bb33dd":{"lch":[49.6825862492002273,108.545865187730342,294.87630802524211],"luv":[49.6825862492002273,45.6609809337446322,-98.4747666639588459],"rgb":[0.733333333333333282,0.2,0.866666666666666696],"xyz":[0.347265900825305729,0.18154185411424037,0.700839184892241285],"hpluv":[294.87630802524211,277.235228758227834,49.6825862492002273],"hsluv":[294.87630802524211,87.70141038259,49.6825862492002273]},"#bb33ee":{"lch":[50.8117750205940695,117.927813495206621,290.271422146528892],"luv":[50.8117750205940695,40.8581913283156268,-110.623584271795281],"rgb":[0.733333333333333282,0.2,0.933333333333333348],"xyz":[0.371078081091480494,0.191066726220710414,0.826250000960764508],"hpluv":[290.271422146528892,294.504005794210514,50.8117750205940695],"hsluv":[290.271422146528892,88.8695912604113687,50.8117750205940695]},"#bb33ff":{"lch":[52.0103359867018611,127.679308171663493,286.581788346289272],"luv":[52.0103359867018611,36.4375993761859931,-122.36955130625914],"rgb":[0.733333333333333282,0.2,1],"xyz":[0.39724912950131186,0.201535145584643111,0.964084189252546353],"hpluv":[286.581788346289272,311.508705442628695,52.0103359867018611],"hsluv":[286.581788346289272,99.9999999999991616,52.0103359867018611]},"#994400":{"lch":[39.4244247356725168,77.6708634712958315,27.0445710144404678],"luv":[39.4244247356725168,69.1777945884316097,35.3156589667733627],"rgb":[0.6,0.266666666666666663,0],"xyz":[0.152036077002273062,0.109076103359438958,0.0130478755429292749],"hpluv":[27.0445710144404678,249.995444431237956,39.4244247356725168],"hsluv":[27.0445710144404678,100.000000000002302,39.4244247356725168]},"#994411":{"lch":[39.4928806474909351,75.8240556098730849,25.7566327743493559],"luv":[39.4928806474909351,68.2907795083206395,32.9493071744317447],"rgb":[0.6,0.266666666666666663,0.0666666666666666657],"xyz":[0.153047742501910194,0.109480769559293811,0.0183759805076849081],"hpluv":[25.7566327743493559,243.628181561178451,39.4928806474909351],"hsluv":[25.7566327743493559,94.1783894720599,39.4928806474909351]},"#994422":{"lch":[39.6193348047394807,72.6128884539244268,23.2788782073808527],"luv":[39.6193348047394807,66.7016282141236161,28.6971141964989123],"rgb":[0.6,0.266666666666666663,0.133333333333333331],"xyz":[0.154923100640387196,0.110230912814684626,0.0282528667036641],"hpluv":[23.2788782073808527,232.565805292122405,39.6193348047394807],"hsluv":[23.2788782073808527,83.7619004180350402,39.6193348047394807]},"#994433":{"lch":[39.8262957210095,67.9099451463512764,18.9473520481579776],"luv":[39.8262957210095,64.2304033034813671,22.0503047882012062],"rgb":[0.6,0.266666666666666663,0.2],"xyz":[0.158010851372844929,0.111466013107667739,0.0445150205612752509],"hpluv":[18.9473520481579776,216.372863201479333,39.8262957210095],"hsluv":[18.9473520481579776,67.5996848307310358,39.8262957210095]},"#994444":{"lch":[40.1224193460439267,62.3513134554678956,12.1770506300619097],"luv":[40.1224193460439267,60.9484376060972082,13.1519672672071142],"rgb":[0.6,0.266666666666666663,0.266666666666666663],"xyz":[0.162468846617063817,0.113249211205355318,0.0679937955141618894],"hpluv":[12.1770506300619097,197.195872414250289,40.1224193460439267],"hsluv":[12.1770506300619097,46.2091051210312429,40.1224193460439267]},"#994455":{"lch":[40.5136546800823041,57.0624194645929776,2.37288379618767387],"luv":[40.5136546800823041,57.0134905267935324,2.36254377830001205],"rgb":[0.6,0.266666666666666663,0.333333333333333315],"xyz":[0.16843126065642583,0.115634176821100151,0.0993958427881357909],"hpluv":[2.37288379618767387,178.726144329621945,40.5136546800823041],"hsluv":[2.37288379618767387,49.5312591923758063,40.5136546800823041]},"#994466":{"lch":[41.0036603670202382,53.5416261730848646,349.413399835018254],"luv":[41.0036603670202382,52.6302589877717111,-9.83674601372026558],"rgb":[0.6,0.266666666666666663,0.4],"xyz":[0.176016256429934,0.11866817513050347,0.139343487195279753],"hpluv":[349.413399835018254,165.694562401887765,41.0036603670202382],"hsluv":[349.413399835018254,53.2076295784804643,41.0036603670202382]},"#994477":{"lch":[41.594070304433572,53.2098960104776424,334.460192523955527],"luv":[41.594070304433572,48.0105420863306946,-22.9408125841809181],"rgb":[0.6,0.266666666666666663,0.466666666666666674],"xyz":[0.18533026945013592,0.122393780338584279,0.188397289101677662],"hpluv":[334.460192523955527,162.330570569563577,41.594070304433572],"hsluv":[334.460192523955527,57.0495308436177666,41.594070304433572]},"#994488":{"lch":[42.2847251453823887,56.6612659292255287,319.901700116735],"luv":[42.2847251453823887,43.3424978513170416,-36.4955742071691631],"rgb":[0.6,0.266666666666666663,0.533333333333333326],"xyz":[0.196470686552079721,0.126849947179361877,0.247070152505249829],"hpluv":[319.901700116735,170.036472949794188,42.2847251453823887],"hsluv":[319.901700116735,60.8903164523640683,42.2847251453823887]},"#994499":{"lch":[43.0739091348830314,63.3829857303906,307.715012949244226],"luv":[43.0739091348830314,38.7735489334465555,-50.1399519665159232],"rgb":[0.6,0.266666666666666663,0.6],"xyz":[0.209527650661152448,0.132072732822991035,0.315836830146367764],"hpluv":[307.715012949244226,186.722963823125951,43.0739091348830314],"hsluv":[307.715012949244226,64.600458594127474,43.0739091348830314]},"#9944aa":{"lch":[43.9586008361636686,72.3252063731545,298.405725418599673],"luv":[43.9586008361636686,34.4059763632637896,-63.6173267861038099],"rgb":[0.6,0.266666666666666663,0.66666666666666663],"xyz":[0.224585343626404976,0.138095810009092129,0.395140679763366365],"hpluv":[298.405725418599673,208.77819920337123,43.9586008361636686],"hsluv":[298.405725418599673,68.0913106207235,43.9586008361636686]},"#9944bb":{"lch":[44.9347323991438827,82.5300732915554676,291.540124858279853],"luv":[44.9347323991438827,30.3011407548278626,-76.7662286846607316],"rgb":[0.6,0.266666666666666663,0.733333333333333282],"xyz":[0.241722935733741651,0.144950846852026888,0.485398664862008111],"hpluv":[291.540124858279853,233.060888419388505,44.9347323991438827],"hsluv":[291.540124858279853,71.3111773355981882,44.9347323991438827]},"#9944cc":{"lch":[45.9974464532923903,93.3382836250318206,286.486143775676851],"luv":[45.9974464532923903,26.4878609436744341,-89.5009967134190561],"rgb":[0.6,0.266666666666666663,0.8],"xyz":[0.261015311177463349,0.152667797029515689,0.587005175532278267],"hpluv":[286.486143775676851,257.492992496327645,45.9974464532923903],"hsluv":[286.486143775676851,74.2376853125076792,45.9974464532923903]},"#9944dd":{"lch":[47.1413389271288139,104.350309752399141,282.717215233582351],"luv":[47.1413389271288139,22.9716047676972792,-101.790434323753402],"rgb":[0.6,0.266666666666666663,0.866666666666666696],"xyz":[0.282533636728096949,0.161275127249769223,0.700335023432284265],"hpluv":[282.717215233582351,280.886686990529654,47.1413389271288139],"hsluv":[282.717215233582351,78.691216526215527,47.1413389271288139]},"#9944ee":{"lch":[48.3606780479664593,115.341161543855208,279.855683836472622],"luv":[48.3606780479664593,19.7426125181249787,-113.638958096441911],"rgb":[0.6,0.266666666666666663,0.933333333333333348],"xyz":[0.306345816994271658,0.170799999356239268,0.825745839500807488],"hpluv":[279.855683836472622,302.643440789384783,48.3606780479664593],"hsluv":[279.855683836472622,89.2619995530309325,48.3606780479664593]},"#9944ff":{"lch":[49.6495929972458185,126.19285787586071,277.642335930208503],"luv":[49.6495929972458185,16.7822322716138856,-125.071955525044785],"rgb":[0.6,0.266666666666666663,1],"xyz":[0.332516865404103079,0.181268418720171964,0.963580027792589333],"hpluv":[277.642335930208503,322.521305960549284,49.6495929972458185],"hsluv":[277.642335930208503,99.9999999999991616,49.6495929972458185]},"#bb4400":{"lch":[45.2216387767487547,104.837609625168909,21.4216552556228201],"luv":[45.2216387767487547,97.5952013308911,38.2896992558330354],"rgb":[0.733333333333333282,0.266666666666666663,0],"xyz":[0.225600911582447911,0.147007971189842435,0.0164962271638749],"hpluv":[21.4216552556228201,294.177965476355098,45.2216387767487547],"hsluv":[21.4216552556228201,100.000000000002373,45.2216387767487547]},"#bb4411":{"lch":[45.2777618800582076,103.283777149094803,20.576768268141489],"luv":[45.2777618800582076,96.6944911384892123,36.3003306548646947],"rgb":[0.733333333333333282,0.266666666666666663,0.0666666666666666657],"xyz":[0.226612577082085043,0.147412637389697287,0.0218243321286305317],"hpluv":[20.576768268141489,289.458620037032802,45.2777618800582076],"hsluv":[20.576768268141489,95.6967439910137614,45.2777618800582076]},"#bb4422":{"lch":[45.3815280810129238,100.53232916519147,18.9799525675269258],"luv":[45.3815280810129238,95.0666309197234,32.6968636561903381],"rgb":[0.733333333333333282,0.266666666666666663,0.133333333333333331],"xyz":[0.228487935220562044,0.148162780645088116,0.0317012183246097243],"hpluv":[18.9799525675269258,281.103307599105051,45.3815280810129238],"hsluv":[18.9799525675269258,87.925891065222288,45.3815280810129238]},"#bb4433":{"lch":[45.5516172521931111,96.3559005693685862,16.2666991179303722],"luv":[45.5516172521931111,92.4986058403436147,26.9901369416087924],"rgb":[0.733333333333333282,0.266666666666666663,0.2],"xyz":[0.231575685953019805,0.149397880938071215,0.0479633721822208675],"hpluv":[16.2666991179303722,268.419362556396,45.5516172521931111],"hsluv":[16.2666991179303722,75.6846484949249572,45.5516172521931111]},"#bb4444":{"lch":[45.7955406359936816,91.066890863790789,12.177050630061844],"luv":[45.7955406359936816,89.0179277419259734,19.2090382927107299],"rgb":[0.733333333333333282,0.266666666666666663,0.266666666666666663],"xyz":[0.236033681197238665,0.151181079035758809,0.0714421471351075],"hpluv":[12.177050630061844,252.334507458167678,45.7955406359936816],"hsluv":[12.177050630061844,59.1297963696904461,45.7955406359936816]},"#bb4455":{"lch":[46.1187996938414813,85.2741675307746,6.43569277192082279],"luv":[46.1187996938414813,84.7367941199702273,9.55820957796975],"rgb":[0.733333333333333282,0.266666666666666663,0.333333333333333315],"xyz":[0.24199609523660065,0.153566044651503641,0.102844194409081421],"hpluv":[6.43569277192082279,234.627449284537505,46.1187996938414813],"hsluv":[6.43569277192082279,61.0765315010866274,46.1187996938414813]},"#bb4466":{"lch":[46.5252276318027427,79.8426568240615637,358.82639812637143],"luv":[46.5252276318027427,79.8259079304732779,-1.63532009113408461],"rgb":[0.733333333333333282,0.266666666666666663,0.4],"xyz":[0.249581091010108835,0.156600042960906932,0.142791838816225369],"hpluv":[358.82639812637143,217.763856882437722,46.5252276318027427],"hsluv":[358.82639812637143,63.3003300169989842,46.5252276318027427]},"#bb4477":{"lch":[47.0171837667790697,75.785442181693,349.367581794189107],"luv":[47.0171837667790697,74.484290362097866,-13.9829802234512588],"rgb":[0.733333333333333282,0.266666666666666663,0.466666666666666674],"xyz":[0.258895104030310741,0.160325648168987756,0.191845640722623278],"hpluv":[349.367581794189107,204.535408840651428,47.0171837667790697],"hsluv":[349.367581794189107,65.7062458495858266,47.0171837667790697]},"#bb4488":{"lch":[47.5956997014540235,74.0450948660102,338.538246495536782],"luv":[47.5956997014540235,68.9109568360576219,-27.0916241974425098],"rgb":[0.733333333333333282,0.266666666666666663,0.533333333333333326],"xyz":[0.270035521132254597,0.164781815009765353,0.250518504126195474],"hpluv":[338.538246495536782,197.409434601059019,47.5956997014540235],"hsluv":[338.538246495536782,68.1997676790361,47.5956997014540235]},"#bb4499":{"lch":[48.2606154557730633,75.1911240047971,327.313188255720945],"luv":[48.2606154557730633,63.2834900243205496,-40.6067115074158309],"rgb":[0.733333333333333282,0.266666666666666663,0.6],"xyz":[0.283092485241327296,0.170004600653394511,0.319285181767313353],"hpluv":[327.313188255720945,197.702903480158483,48.2606154557730633],"hsluv":[327.313188255720945,70.6969578310621074,48.2606154557730633]},"#bb44aa":{"lch":[49.0107199856960278,79.2228796099941093,316.794363507436401],"luv":[49.0107199856960278,57.7456584584256944,-54.2374739723603838],"rgb":[0.733333333333333282,0.266666666666666663,0.66666666666666663],"xyz":[0.298150178206579852,0.176027677839495605,0.398589031384311954],"hpluv":[316.794363507436401,205.115683333718124,49.0107199856960278],"hsluv":[316.794363507436401,73.1302855273877128,49.0107199856960278]},"#bb44bb":{"lch":[49.8438993628519427,85.6636869410227746,307.715012949243942],"luv":[49.8438993628519427,52.4034189799077268,-67.7653963284049752],"rgb":[0.733333333333333282,0.266666666666666663,0.733333333333333282],"xyz":[0.315287770313916471,0.182882714682430364,0.4888470164829537],"hpluv":[307.715012949243942,218.084137693810391,49.8438993628519427],"hsluv":[307.715012949243942,75.450469608388417,49.8438993628519427]},"#bb44cc":{"lch":[50.757290285110841,93.8478600934752905,300.284565440631638],"luv":[50.757290285110841,47.3270085455435918,-81.0405769121528152],"rgb":[0.733333333333333282,0.266666666666666663,0.8],"xyz":[0.334580145757638225,0.190599664859919166,0.590453527153223856],"hpluv":[300.284565440631638,234.620130462749955,50.757290285110841],"hsluv":[300.284565440631638,77.6254237907312898,50.757290285110841]},"#bb44dd":{"lch":[51.7474340276366291,103.158361374368454,294.364201848969515],"luv":[51.7474340276366291,42.5564715489483518,-93.971241615445],"rgb":[0.733333333333333282,0.266666666666666663,0.866666666666666696],"xyz":[0.356098471308271769,0.1992069950801727,0.703783375053229854],"hpluv":[294.364201848969515,252.961799427415912,51.7474340276366291],"hsluv":[294.364201848969515,79.6375689955678467,51.7474340276366291]},"#bb44ee":{"lch":[52.8104252671910217,113.122924260907325,289.686672533024307],"luv":[52.8104252671910217,38.1084271425454304,-106.510768347901248],"rgb":[0.733333333333333282,0.266666666666666663,0.933333333333333348],"xyz":[0.379910651574446478,0.208731867186642744,0.829194191121753077],"hpluv":[289.686672533024307,271.813038246621943,52.8104252671910217],"hsluv":[289.686672533024307,87.7449679178180872,52.8104252671910217]},"#bb44ff":{"lch":[53.942050711908152,123.415863319803691,285.982908620336502],"luv":[53.942050711908152,33.9826318144238115,-118.645084406973112],"rgb":[0.733333333333333282,0.266666666666666663,1],"xyz":[0.4060816999842779,0.219200286550575441,0.967028379413534922],"hpluv":[285.982908620336502,290.323946696930363,53.942050711908152],"hsluv":[285.982908620336502,99.9999999999990621,53.942050711908152]},"#995500":{"lch":[43.167672396478018,69.2675138179999,36.3951762413548678],"luv":[43.167672396478018,55.7564526526882105,41.099956911356287],"rgb":[0.6,0.333333333333333315,0],"xyz":[0.163849333716619028,0.132702616788131222,0.0169856277810444857],"hpluv":[36.3951762413548678,203.615246511519132,43.167672396478018],"hsluv":[36.3951762413548678,100.000000000002288,43.167672396478018]},"#995511":{"lch":[43.2277537555276865,67.4837391190291385,35.2266717066771307],"luv":[43.2277537555276865,55.1258789529511333,38.9254738590266101],"rgb":[0.6,0.333333333333333315,0.0666666666666666657],"xyz":[0.16486099921625616,0.133107282987986075,0.0223137327458001189],"hpluv":[35.2266717066771307,198.096040704981476,43.2277537555276865],"hsluv":[35.2266717066771307,95.1659583326808303,43.2277537555276865]},"#995522":{"lch":[43.3388072781739,64.3407602415628475,32.9528815923873708],"luv":[43.3388072781739,53.9895016234129059,34.998101990218963],"rgb":[0.6,0.333333333333333315,0.133333333333333331],"xyz":[0.166736357354733161,0.133857426243376904,0.0321906189417793115],"hpluv":[32.9528815923873708,188.385971738918272,43.3388072781739],"hsluv":[32.9528815923873708,86.4646465379692302,43.3388072781739]},"#995533":{"lch":[43.5207548807750584,59.6262244551406582,28.8908620435756234],"luv":[43.5207548807750584,52.2052395636471,28.8079781462883915],"rgb":[0.6,0.333333333333333315,0.2],"xyz":[0.169824108087190895,0.13509252653636,0.0484527727993904617],"hpluv":[28.8908620435756234,173.852210644027934,43.5207548807750584],"hsluv":[28.8908620435756234,72.8304069554253601,43.5207548807750584]},"#995544":{"lch":[43.7814988718974831,53.8284560366537335,22.2989672577237812],"luv":[43.7814988718974831,49.8029788080235036,20.4246415179685634],"rgb":[0.6,0.333333333333333315,0.266666666666666663],"xyz":[0.174282103331409782,0.136875724634047596,0.0719315477522771],"hpluv":[22.2989672577237812,156.01294104827457,43.7814988718974831],"hsluv":[22.2989672577237812,54.5318604429391058,43.7814988718974831]},"#995555":{"lch":[44.1267187000120629,47.9518139182857226,12.1770506300620411],"luv":[44.1267187000120629,46.8729201797035,10.1146335514957979],"rgb":[0.6,0.333333333333333315,0.333333333333333315],"xyz":[0.180244517370771795,0.139260690249792429,0.103333595026251],"hpluv":[12.1770506300620411,137.893162706519348,44.1267187000120629],"hsluv":[12.1770506300620411,32.3126421104369754,44.1267187000120629]},"#995566":{"lch":[44.5602350881765048,43.578232165907,357.727809148728397],"luv":[44.5602350881765048,43.5439690351937116,-1.72773822316436432],"rgb":[0.6,0.333333333333333315,0.4],"xyz":[0.187829513144279953,0.14229468855919572,0.143281239433394963],"hpluv":[357.727809148728397,124.097051059849122,44.5602350881765048],"hsluv":[357.727809148728397,36.5660257696708157,44.5602350881765048]},"#995577":{"lch":[45.0842241078155226,42.5628566452735342,339.858530865537546],"luv":[45.0842241078155226,39.9599368331855729,-14.6560640723873874],"rgb":[0.6,0.333333333333333315,0.466666666666666674],"xyz":[0.197143526164481886,0.146020293767276543,0.192335041339792873],"hpluv":[339.858530865537546,119.796876557388956,45.0842241078155226],"hsluv":[339.858530865537546,41.1101378179314167,45.0842241078155226]},"#995588":{"lch":[45.699386409692309,45.957423726275,322.088602167082399],"luv":[45.699386409692309,36.2586549471185506,-28.2381787122006145],"rgb":[0.6,0.333333333333333315,0.533333333333333326],"xyz":[0.208283943266425686,0.150476460608054141,0.25100790474336504],"hpluv":[322.088602167082399,127.609977655436467,45.699386409692309],"hsluv":[322.088602167082399,45.7577454208568852,45.699386409692309]},"#995599":{"lch":[46.4051108942887964,53.2218555896971921,307.715012949244795],"luv":[46.4051108942887964,32.5576366947089113,-42.1018551285561102],"rgb":[0.6,0.333333333333333315,0.6],"xyz":[0.221340907375498414,0.155699246251683299,0.319774582384483],"hpluv":[307.715012949244795,145.533684272522947,46.4051108942887964],"hsluv":[307.715012949244795,50.3502223422482516,46.4051108942887964]},"#9955aa":{"lch":[47.1996461355186625,63.0010233462712037,297.353030705211779],"luv":[47.1996461355186625,28.9471952899390068,-55.9570266143899246],"rgb":[0.6,0.333333333333333315,0.66666666666666663],"xyz":[0.236398600340750942,0.161722323437784393,0.399078432001481576],"hpluv":[297.353030705211779,169.374563019623821,47.1996461355186625],"hsluv":[297.353030705211779,54.7667714505238337,47.1996461355186625]},"#9955bb":{"lch":[48.0802807126223541,74.1194144420538,290.114722102094788],"luv":[48.0802807126223541,25.4897394646759068,-69.5985688017783701],"rgb":[0.6,0.333333333333333315,0.733333333333333282],"xyz":[0.253536192448087616,0.168577360280719152,0.489336417100123322],"hpluv":[290.114722102094788,195.615971831826613,48.0802807126223541],"hsluv":[290.114722102094788,58.9252289891592866,48.0802807126223541]},"#9955cc":{"lch":[49.0435277691913,85.8237639836856658,285.007043429489613],"luv":[49.0435277691913,22.2230153813451068,-82.8966588692685491],"rgb":[0.6,0.333333333333333315,0.8],"xyz":[0.272828567891809315,0.176294310458207953,0.590942927770393478],"hpluv":[285.007043429489613,222.057364979540267,49.0435277691913],"hsluv":[285.007043429489613,65.256702758776143,49.0435277691913]},"#9955dd":{"lch":[50.0853068419578875,97.6800340866365104,281.315028513382344],"luv":[50.0853068419578875,19.1651498984824649,-95.7814496055220843],"rgb":[0.6,0.333333333333333315,0.866666666666666696],"xyz":[0.294346893442442914,0.184901640678461487,0.704272775670399476],"hpluv":[281.315028513382344,247.476970093373268,50.0853068419578875],"hsluv":[281.315028513382344,76.6956878028438638,50.0853068419578875]},"#9955ee":{"lch":[51.2011159126469266,109.450459224116145,278.575243894560685],"luv":[51.2011159126469266,16.3199513369978568,-108.226901520499666],"rgb":[0.6,0.333333333333333315,0.933333333333333348],"xyz":[0.318159073708617623,0.194426512784931532,0.829683591738922699],"hpluv":[278.575243894560685,271.254835109068836,51.2011159126469266],"hsluv":[278.575243894560685,88.2491190723243335,51.2011159126469266]},"#9955ff":{"lch":[52.3861878346365444,121.012399839465246,276.491711029928183],"luv":[52.3861878346365444,13.6815981187824764,-120.2364952409323],"rgb":[0.6,0.333333333333333315,1],"xyz":[0.344330122118449045,0.204894932148864228,0.967517780030704544],"hpluv":[276.491711029928183,293.124692446110771,52.3861878346365444],"hsluv":[276.491711029928183,99.9999999999991,52.3861878346365444]},"#bb5500":{"lch":[48.3398816318057811,94.9450628471467724,27.1627331553413143],"luv":[48.3398816318057811,84.4739036935134,43.344256295703957],"rgb":[0.733333333333333282,0.333333333333333315,0],"xyz":[0.237414168296793876,0.170634484618534699,0.0204339794019901093],"hpluv":[27.1627331553413143,249.233335779464397,48.3398816318057811],"hsluv":[27.1627331553413143,100.000000000002217,48.3398816318057811]},"#bb5511":{"lch":[48.3907029738951735,93.4795954244126364,26.3417432533787057],"luv":[48.3907029738951735,83.7729911103365907,41.4791600823755289],"rgb":[0.733333333333333282,0.333333333333333315,0.0666666666666666657],"xyz":[0.238425833796431,0.171039150818389551,0.0257620843667457425],"hpluv":[26.3417432533787057,245.128732426237889,48.3907029738951735],"hsluv":[26.3417432533787057,96.2613278044251786,48.3907029738951735]},"#bb5522":{"lch":[48.4847005727335869,90.8707197847641623,24.7826102649670759],"luv":[48.4847005727335869,82.5019575685759321,38.090874386840035],"rgb":[0.733333333333333282,0.333333333333333315,0.133333333333333331],"xyz":[0.240301191934908,0.17178929407378038,0.0356389705627249351],"hpluv":[24.7826102649670759,237.825586669453713,48.4847005727335869],"hsluv":[24.7826102649670759,89.48684037186257,48.4847005727335869]},"#bb5533":{"lch":[48.6388719159228629,86.8743588299812473,22.1104535643633149],"luv":[48.6388719159228629,80.4856161939384407,32.6989267102511363],"rgb":[0.733333333333333282,0.333333333333333315,0.2],"xyz":[0.243388942667365771,0.173024394366763479,0.0519011244203360783],"hpluv":[22.1104535643633149,226.64567974804504,48.6388719159228629],"hsluv":[22.1104535643633149,78.7542493880857393,48.6388719159228629]},"#bb5544":{"lch":[48.8601705631915,81.7431600256382751,18.0266646696809296],"luv":[48.8601705631915,77.730600917119915,25.2962031151029265],"rgb":[0.733333333333333282,0.333333333333333315,0.266666666666666663],"xyz":[0.24784693791158463,0.174807592464451073,0.0753798993732227168],"hpluv":[18.0266646696809296,212.293047020031764,48.8601705631915],"hsluv":[18.0266646696809296,64.1199207289341,48.8601705631915]},"#bb5555":{"lch":[49.1538097277392154,76.0172073873862075,12.1770506300619399],"luv":[49.1538097277392154,74.3068552156307,16.0345591439297159],"rgb":[0.733333333333333282,0.333333333333333315,0.333333333333333315],"xyz":[0.253809351950946616,0.177192558080195905,0.106781946647196632],"hpluv":[12.1770506300619399,196.242945408672853,49.1538097277392154],"hsluv":[12.1770506300619399,45.9858047870319879,49.1538097277392154]},"#bb5566":{"lch":[49.523574907380123,70.5220596563995,4.21567942747197],"luv":[49.523574907380123,70.3312550088346882,5.18415538569556134],"rgb":[0.733333333333333282,0.333333333333333315,0.4],"xyz":[0.261394347724454801,0.180226556389599196,0.14672959105434058],"hpluv":[4.21567942747197,180.697576197923894,49.523574907380123],"hsluv":[4.21567942747197,48.7291682320237527,49.523574907380123]},"#bb5577":{"lch":[49.971995559373525,66.3094111666939341,354.014227306533371],"luv":[49.971995559373525,65.9478803557508257,-6.91484532417379327],"rgb":[0.733333333333333282,0.333333333333333315,0.466666666666666674],"xyz":[0.270708360744656706,0.18395216159768002,0.195783392960738489],"hpluv":[354.014227306533371,168.378953180241098,49.971995559373525],"hsluv":[354.014227306533371,51.7390719561838495,49.971995559373525]},"#bb5588":{"lch":[50.5004659153875508,64.4571905510646275,342.016521446261379],"luv":[50.5004659153875508,61.308172080607541,-19.9006896832966476],"rgb":[0.733333333333333282,0.333333333333333315,0.533333333333333326],"xyz":[0.281848777846600562,0.188408328438457617,0.254456256364310685],"hpluv":[342.016521446261379,161.962814057843559,50.5004659153875508],"hsluv":[342.016521446261379,54.9055054808170055,50.5004659153875508]},"#bb5599":{"lch":[51.1093507584050286,65.6961991151429459,329.411862939203161],"luv":[51.1093507584050286,56.5544024759803,-33.4303774845774342],"rgb":[0.733333333333333282,0.333333333333333315,0.6],"xyz":[0.294905741955673262,0.193631114082086775,0.323222934005428564],"hpluv":[329.411862939203161,163.109481188372769,51.1093507584050286],"hsluv":[329.411862939203161,58.1255686218068561,51.1093507584050286]},"#bb55aa":{"lch":[51.7980911374347386,70.0901192661774246,317.660759061539352],"luv":[51.7980911374347386,51.8085124997667563,-47.2070212077450151],"rgb":[0.733333333333333282,0.333333333333333315,0.66666666666666663],"xyz":[0.309963434920925818,0.199654191268187869,0.402526783622427164],"hpluv":[317.660759061539352,171.704773930815691,51.7980911374347386],"hsluv":[317.660759061539352,61.3115173935257403,51.7980911374347386]},"#bb55bb":{"lch":[52.5653152299933737,77.1031380177621344,307.715012949244226],"luv":[52.5653152299933737,47.1666372355911179,-60.9934605025180332],"rgb":[0.733333333333333282,0.333333333333333315,0.733333333333333282],"xyz":[0.327101027028262437,0.206509228111122628,0.492784768721068911],"hpluv":[307.715012949244226,186.128169856082764,52.5653152299933737],"hsluv":[307.715012949244226,64.3946779967479,52.5653152299933737]},"#bb55cc":{"lch":[53.4089544864585,85.9678373228364308,299.780474196008072],"luv":[53.4089544864585,42.6983512805399883,-74.6144748148057175],"rgb":[0.733333333333333282,0.333333333333333315,0.8],"xyz":[0.346393402471984191,0.21422617828861143,0.594391279391339067],"hpluv":[299.780474196008072,204.249617293250537,53.4089544864585],"hsluv":[299.780474196008072,67.3259326792693855,53.4089544864585]},"#bb55dd":{"lch":[54.3263625650166944,95.9883710948233926,293.613183872979789],"luv":[54.3263625650166944,38.4490902963257213,-87.9513208588848698],"rgb":[0.733333333333333282,0.333333333333333315,0.866666666666666696],"xyz":[0.367911728022617734,0.222833508508864964,0.707721127291345065],"hpluv":[293.613183872979789,224.206046477737118,54.3263625650166944],"hsluv":[293.613183872979789,73.8407928406032283,54.3263625650166944]},"#bb55ee":{"lch":[55.314433433552054,106.647677925948784,288.842761327535072],"luv":[55.314433433552054,34.444226028074695,-100.932266893812525],"rgb":[0.733333333333333282,0.333333333333333315,0.933333333333333348],"xyz":[0.391723908288792444,0.232358380615335,0.833131943359868288],"hpluv":[288.842761327535072,244.653966901481454,55.314433433552054],"hsluv":[288.842761327535072,86.7715876359309135,55.314433433552054]},"#bb55ff":{"lch":[56.3697148536960526,117.598184211748716,285.129655441003138],"luv":[56.3697148536960526,30.6936185759692179,-113.521956944959783],"rgb":[0.733333333333333282,0.333333333333333315,1],"xyz":[0.417894956698623865,0.242826799979267705,0.970966131651650133],"hpluv":[285.129655441003138,264.724480425834031,56.3697148536960526],"hsluv":[285.129655441003138,99.9999999999989,56.3697148536960526]},"#996600":{"lch":[47.3343652017352454,63.4240894393546952,48.3260196362919672],"luv":[47.3343652017352454,42.1701199842588,47.3740023823666476],"rgb":[0.6,0.4,0],"xyz":[0.178877391422465504,0.162758732199824563,0.0219949803496598331],"hpluv":[48.3260196362919672,170.026654750900292,47.3343652017352454],"hsluv":[48.3260196362919672,100.000000000002288,47.3343652017352454]},"#996611":{"lch":[47.3868110627231189,61.667674323653,47.4017350737566616],"luv":[47.3868110627231189,41.7399921999205,45.3946594616449133],"rgb":[0.6,0.4,0.0666666666666666657],"xyz":[0.179889056922102636,0.163163398399679416,0.0273230853144154628],"hpluv":[47.4017350737566616,165.135107398016657,47.3868110627231189],"hsluv":[47.4017350737566616,96.0239926064661,47.3868110627231189]},"#996622":{"lch":[47.4838028017404099,58.5263777304533406,45.5834379771503322],"luv":[47.4838028017404099,40.9608465044279413,41.8036594557031336],"rgb":[0.6,0.4,0.133333333333333331],"xyz":[0.181764415060579637,0.163913541655070244,0.0371999715103946624],"hpluv":[45.5834379771503322,156.40314428015364,47.4838028017404099],"hsluv":[45.5834379771503322,88.8298219708016177,47.4838028017404099]},"#996633":{"lch":[47.642855645786625,53.6803525088045674,42.2629253198225712],"luv":[47.642855645786625,39.7270268335614887,36.1018501525199724],"rgb":[0.6,0.4,0.2],"xyz":[0.184852165793037371,0.165148641948053343,0.0534621253680058056],"hpluv":[42.2629253198225712,142.973945144987511,47.642855645786625],"hsluv":[42.2629253198225712,77.4596172761956865,47.642855645786625]},"#996644":{"lch":[47.8710980897590872,47.4159845831850149,36.6424092143452071],"luv":[47.8710980897590872,38.045445264132745,28.298757722676072],"rgb":[0.6,0.4,0.266666666666666663],"xyz":[0.189310161037256258,0.166931840045740937,0.0769409003208924441],"hpluv":[36.6424092143452071,125.687101185572914,47.8710980897590872],"hsluv":[36.6424092143452071,62.0095295256686185,47.8710980897590872]},"#996655":{"lch":[48.173837669734425,40.4765536471140521,27.3183313201514686],"luv":[48.173837669734425,35.9622216701857838,18.5760600691352664],"rgb":[0.6,0.4,0.333333333333333315],"xyz":[0.195272575076618271,0.16931680566148577,0.108342947594866346],"hpluv":[27.3183313201514686,106.618264590322553,48.173837669734425],"hsluv":[27.3183313201514686,42.9499413016611484,48.173837669734425]},"#996666":{"lch":[48.5548823199147819,34.3240193245421,12.1770506300621335],"luv":[48.5548823199147819,33.551744690774747,7.24007809326721219],"rgb":[0.6,0.4,0.4],"xyz":[0.202857570850126429,0.17235080397088906,0.148290592002010307],"hpluv":[12.1770506300621335,89.702502372613651,48.5548823199147819],"hsluv":[12.1770506300621335,21.0200766933302461,48.5548823199147819]},"#996677":{"lch":[49.0167186013709,31.3606600839303482,350.2076113500313],"luv":[49.0167186013709,30.9037509766054157,-5.33377694282222681],"rgb":[0.6,0.4,0.466666666666666674],"xyz":[0.212171583870328362,0.176076409178969884,0.197344393908408217],"hpluv":[350.2076113500313,81.185839798992177,49.0167186013709],"hsluv":[350.2076113500313,25.8881256225468483,49.0167186013709]},"#996688":{"lch":[49.5606396668562752,33.7929953796712823,326.289263208678278],"luv":[49.5606396668562752,28.1107077320421368,-18.7551232343109682],"rgb":[0.6,0.4,0.533333333333333326],"xyz":[0.223312000972272162,0.180532576019747482,0.256017257311980384],"hpluv":[326.289263208678278,86.5225105267045365,49.5606396668562752],"hsluv":[326.289263208678278,30.9767265160446463,49.5606396668562752]},"#996699":{"lch":[50.1868595811773304,41.2886932006772,307.715012949245818],"luv":[50.1868595811773304,25.2577114783499113,-32.6619686653465351],"rgb":[0.6,0.4,0.6],"xyz":[0.236368965081344889,0.185755361663376639,0.324783934953098319],"hpluv":[307.715012949245818,104.395179003902854,50.1868595811773304],"hsluv":[307.715012949245818,36.1175524455388413,50.1868595811773304]},"#9966aa":{"lch":[50.8946289106688141,51.8448774557822,295.617938374847199],"luv":[50.8946289106688141,22.4160699516755599,-46.7483810021979451],"rgb":[0.6,0.4,0.66666666666666663],"xyz":[0.251426658046597418,0.191778438849477734,0.40408778457009692],"hpluv":[295.617938374847199,129.262701065701,50.8946289106688141],"hsluv":[295.617938374847199,41.1705422178494445,50.8946289106688141]},"#9966bb":{"lch":[51.6823563026294,63.8712345518136,287.908782454232437],"luv":[51.6823563026294,19.6405628457828776,-60.776499523036378],"rgb":[0.6,0.4,0.733333333333333282],"xyz":[0.268564250153934092,0.198633475692412492,0.494345769668738666],"hpluv":[287.908782454232437,156.820318415219759,51.6823563026294],"hsluv":[287.908782454232437,49.349420166275209,51.6823563026294]},"#9966cc":{"lch":[52.5477355185796569,76.4825896496335389,282.819195018988751],"luv":[52.5477355185796569,16.9695880464031816,-74.5762670093482285],"rgb":[0.6,0.4,0.8],"xyz":[0.287856625597655791,0.206350425869901294,0.595952280339008822],"hpluv":[282.819195018988751,184.691924030055,52.5477355185796569],"hsluv":[282.819195018988751,61.7185051859638136,52.5477355185796569]},"#9966dd":{"lch":[53.4878747475352725,89.2117449852098758,279.306611445978092],"luv":[53.4878747475352725,14.4271177471338046,-88.0374563286358],"rgb":[0.6,0.4,0.866666666666666696],"xyz":[0.30937495114828939,0.214957756090154828,0.70928212823901482],"hpluv":[279.306611445978092,211.644028054176175,53.4878747475352725],"hsluv":[279.306611445978092,74.2596178074049362,53.4878747475352725]},"#9966ee":{"lch":[54.4994239962059339,101.810849438236403,276.783380903602506],"luv":[54.4994239962059339,12.0254847729914225,-101.098154183495325],"rgb":[0.6,0.4,0.933333333333333348],"xyz":[0.333187131414464099,0.224482628196624873,0.834692944307538],"hpluv":[276.783380903602506,237.050827216632513,54.4994239962059339],"hsluv":[276.783380903602506,86.9990828065156734,54.4994239962059339]},"#9966ff":{"lch":[55.5786963614876,114.151421142661491,274.908981870437117],"luv":[55.5786963614876,9.76829238934238475,-113.732701597586171],"rgb":[0.6,0.4,1],"xyz":[0.359358179824295521,0.234951047560557569,0.972527132599319888],"hpluv":[274.908981870437117,260.622732185953453,55.5786963614876],"hsluv":[274.908981870437117,99.999999999998991,55.5786963614876]},"#bb6600":{"lch":[51.9152024616159622,85.9194467179265899,34.7713476038742],"luv":[51.9152024616159622,70.5771984524152884,49.0001059480794936],"rgb":[0.733333333333333282,0.4,0],"xyz":[0.252442226002640324,0.200690600030228039,0.0254433319706054567],"hpluv":[34.7713476038742,210.008196913669821,51.9152024616159622],"hsluv":[34.7713476038742,100.000000000002217,51.9152024616159622]},"#bb6611":{"lch":[51.960819173128,84.5067938711160735,34.0102696578751846],"luv":[51.960819173128,70.0508360673827184,47.2681560528436],"rgb":[0.733333333333333282,0.4,0.0666666666666666657],"xyz":[0.253453891502277429,0.201095266230082892,0.0307714369353610864],"hpluv":[34.0102696578751846,206.373990798532162,51.960819173128],"hsluv":[34.0102696578751846,96.7960731817087492,51.960819173128]},"#bb6622":{"lch":[52.0452187907305586,81.9745670334704215,32.5563816919222901],"luv":[52.0452187907305586,69.0932729256005587,44.1129150788451909],"rgb":[0.733333333333333282,0.4,0.133333333333333331],"xyz":[0.255329249640754485,0.20184540948547372,0.040648323131340286],"hpluv":[32.5563816919222901,199.865401092535734,52.0452187907305586],"hsluv":[32.5563816919222901,90.9716633106406221,52.0452187907305586]},"#bb6633":{"lch":[52.1837271506259412,78.04817012610809,30.0375791414029081],"luv":[52.1837271506259412,67.5660884046363,39.0684086920722109],"rgb":[0.733333333333333282,0.4,0.2],"xyz":[0.258417000373212191,0.203080509778456819,0.0569104769889514292],"hpluv":[30.0375791414029081,189.787216830323956,52.1837271506259412],"hsluv":[30.0375791414029081,81.6941023093515639,52.1837271506259412]},"#bb6644":{"lch":[52.3827138306128859,72.9081368603095257,26.1184165145312406],"luv":[52.3827138306128859,65.4632041398932,32.0961886238895318],"rgb":[0.733333333333333282,0.4,0.266666666666666663],"xyz":[0.262874995617431106,0.204863707876144413,0.0803892519418380747],"hpluv":[26.1184165145312406,176.614896915735159,52.3827138306128859],"hsluv":[26.1184165145312406,68.9437006343278824,52.3827138306128859]},"#bb6655":{"lch":[52.6470547760809637,67.004522830646,20.3474245095699864],"luv":[52.6470547760809637,62.823537757634746,23.2982656731697979],"rgb":[0.733333333333333282,0.4,0.333333333333333315],"xyz":[0.268837409656793092,0.207248673491889246,0.111791299215811976],"hpluv":[20.3474245095699864,161.498824171429789,52.6470547760809637],"hsluv":[20.3474245095699864,52.9816895309479534,52.6470547760809637]},"#bb6666":{"lch":[52.9804174131186727,61.0960132663801261,12.1770506300620109],"luv":[52.9804174131186727,59.7213810933874498,12.8871826767560087],"rgb":[0.733333333333333282,0.4,0.4],"xyz":[0.276422405430301277,0.210282671801292537,0.151738943622955924],"hpluv":[12.1770506300620109,146.331162504643544,52.9804174131186727],"hsluv":[12.1770506300620109,34.2899270044314335,52.9804174131186727]},"#bb6677":{"lch":[53.3854132757716826,56.2661599102721226,1.17050216666979279],"luv":[53.3854132757716826,56.2544190111897535,1.14938799445880213],"rgb":[0.733333333333333282,0.4,0.466666666666666674],"xyz":[0.285736418450503182,0.21400827700937336,0.200792745529353833],"hpluv":[1.17050216666979279,133.74082367080041,53.3854132757716826],"hsluv":[1.17050216666979279,37.694842795415525,53.3854132757716826]},"#bb6688":{"lch":[53.8636991644475387,53.7950791600554297,347.554026547444039],"luv":[53.8636991644475387,52.5308766640911458,-11.5938578022448873],"rgb":[0.733333333333333282,0.4,0.533333333333333326],"xyz":[0.296876835552447,0.218464443850150958,0.259465608932926028],"hpluv":[347.554026547444039,126.731828674836166,53.8636991644475387],"hsluv":[347.554026547444039,41.3310399817098855,53.8636991644475387]},"#bb6699":{"lch":[54.4160596975863484,54.7145736516266723,332.78581188167891],"luv":[54.4160596975863484,48.6578429809048814,-25.0219680745716886],"rgb":[0.733333333333333282,0.4,0.6],"xyz":[0.309933799661519682,0.223687229493780115,0.328232286574043908],"hpluv":[332.78581188167891,127.589593839900076,54.4160596975863484],"hsluv":[332.78581188167891,45.0871489409768813,54.4160596975863484]},"#bb66aa":{"lch":[55.0424859024436,59.2427277394444332,319.03138367428437],"luv":[55.0424859024436,44.7323366642012274,-38.8422301956323963],"rgb":[0.733333333333333282,0.4,0.66666666666666663],"xyz":[0.324991492626772238,0.22971030667988121,0.407536136191042508],"hpluv":[319.03138367428437,136.57661144398341,55.0424859024436],"hsluv":[319.03138367428437,48.8627280546895548,55.0424859024436]},"#bb66bb":{"lch":[55.7422560614222,66.7541112543564168,307.715012949244738],"luv":[55.7422560614222,40.8357821803987235,-52.8067255477370452],"rgb":[0.733333333333333282,0.4,0.733333333333333282],"xyz":[0.342129084734108913,0.236565343522815968,0.497794121289684255],"hpluv":[307.715012949244738,151.961230908585406,55.7422560614222],"hsluv":[307.715012949244738,52.5739577191036176,55.7422560614222]},"#bb66cc":{"lch":[56.5140206008290704,76.3075420390205466,299.031845144425],"luv":[56.5140206008290704,37.0317190732607671,-66.7195080506142801],"rgb":[0.733333333333333282,0.4,0.8],"xyz":[0.361421460177830611,0.24428229370030477,0.599400631959954411],"hpluv":[299.031845144425,171.336773989457299,56.5140206008290704],"hsluv":[299.031845144425,57.7422914315118732,56.5140206008290704]},"#bb66dd":{"lch":[57.3558903641351208,87.0819801731049523,292.529347086481266],"luv":[57.3558903641351208,33.3660351201831276,-80.4361794917420809],"rgb":[0.733333333333333282,0.4,0.866666666666666696],"xyz":[0.38293978572846421,0.252889623920558304,0.712730479859960409],"hpluv":[292.529347086481266,192.659130793538424,57.3558903641351208],"hsluv":[292.529347086481266,71.4699790991907,57.3558903641351208]},"#bb66ee":{"lch":[58.2655263288606164,98.4965661150924916,287.652756970849339],"luv":[58.2655263288606164,29.8688319231142287,-93.8585447149780805],"rgb":[0.733333333333333282,0.4,0.933333333333333348],"xyz":[0.40675196599463892,0.262414496027028321,0.838141295928483632],"hpluv":[287.652756970849339,214.510592558062115,58.2655263288606164],"hsluv":[287.652756970849339,85.5420625539119186,58.2655263288606164]},"#bb66ff":{"lch":[59.2402283004695533,110.175409569553764,283.948158265715847],"luv":[59.2402283004695533,26.5571064878979755,-106.926801919861816],"rgb":[0.733333333333333282,0.4,1],"xyz":[0.432923014404470341,0.272882915390961045,0.975975484220265477],"hpluv":[283.948158265715847,235.997431668916079,59.2402283004695533],"hsluv":[283.948158265715847,99.9999999999988205,59.2402283004695533]},"#997700":{"lch":[51.799451349173637,61.2288227532233265,61.7368019650066202],"luv":[51.799451349173637,28.9932293289078338,53.9292257391759406],"rgb":[0.6,0.466666666666666674,0],"xyz":[0.197331129475883132,0.19966620830666032,0.028146226367465537],"hpluv":[61.7368019650066202,149.992683828924328,51.799451349173637],"hsluv":[61.7368019650066202,100.000000000002373,51.799451349173637]},"#997711":{"lch":[51.8452237949875752,59.5195003520895298,61.1595916745536812],"luv":[51.8452237949875752,28.7105152364728049,52.1371003894409952],"rgb":[0.6,0.466666666666666674,0.0666666666666666657],"xyz":[0.198342794975520265,0.200070874506515173,0.0334743313322211702],"hpluv":[61.1595916745536812,145.676617799321178,51.8452237949875752],"hsluv":[61.1595916745536812,96.7355174862457687,51.8452237949875752]},"#997722":{"lch":[51.9299107218419778,56.4237897442973448,60.0184197539949],"luv":[51.9299107218419778,28.1961842142201746,48.8735024820856836],"rgb":[0.6,0.466666666666666674,0.133333333333333331],"xyz":[0.200218153113997266,0.200821017761906,0.0433512175282003628],"hpluv":[60.0184197539949,137.874516690543771,51.9299107218419778],"hsluv":[60.0184197539949,90.8032063934702762,51.9299107218419778]},"#997733":{"lch":[52.0688882655341843,51.532107019520943,57.9108306747127841],"luv":[52.0688882655341843,27.375836028403171,43.6591531710973797],"rgb":[0.6,0.466666666666666674,0.2],"xyz":[0.203305903846455027,0.2020561180548891,0.0596133713858115061],"hpluv":[57.9108306747127841,125.585333003110208,52.0688882655341843],"hsluv":[57.9108306747127841,81.359583819486275,52.0688882655341843]},"#997744":{"lch":[52.2685439893789265,44.9234500117750244,54.2507061045172563],"luv":[52.2685439893789265,26.2460613351246792,36.4590266649196622],"rgb":[0.6,0.466666666666666674,0.266666666666666663],"xyz":[0.207763899090673887,0.203839316152576694,0.0830921463386981446],"hpluv":[54.2507061045172563,109.061640425204757,52.2685439893789265],"hsluv":[54.2507061045172563,68.3925961892813632,52.2685439893789265]},"#997755":{"lch":[52.5337646967731615,36.9638675487782962,47.8040559904272229],"luv":[52.5337646967731615,24.8274523779219614,27.3847605902560787],"rgb":[0.6,0.466666666666666674,0.333333333333333315],"xyz":[0.213726313130035872,0.206224281768321527,0.11449419361267206],"hpluv":[47.8040559904272229,89.2849392346346,52.5337646967731615],"hsluv":[47.8040559904272229,52.1784042692219217,52.5337646967731615]},"#997766":{"lch":[52.8682223623880958,28.5307109293219519,35.7342712802299047],"luv":[52.8682223623880958,23.1593577603421643,16.6627012894371447],"rgb":[0.6,0.466666666666666674,0.4],"xyz":[0.221311308903544057,0.209258280077724818,0.154441838019816],"hpluv":[35.7342712802299047,68.4789688439394695,52.8682223623880958],"hsluv":[35.7342712802299047,33.2180784313787072,52.8682223623880958]},"#997777":{"lch":[53.2745272921510349,21.7835186536615062,12.1770506300626185],"luv":[53.2745272921510349,21.2933995119794766,4.59486911213486],"rgb":[0.6,0.466666666666666674,0.466666666666666674],"xyz":[0.230625321923745963,0.212983885285805641,0.203495639926213917],"hpluv":[12.1770506300626185,51.8857087556556777,53.2745272921510349],"hsluv":[12.1770506300626185,12.1864056638809046,53.2745272921510349]},"#997788":{"lch":[53.7543298043441524,21.0694932264164443,336.259644884191403],"luv":[53.7543298043441524,19.28657717102978,-8.48242216845644492],"rgb":[0.6,0.466666666666666674,0.533333333333333326],"xyz":[0.241765739025689819,0.217440052126583239,0.262168503329786085],"hpluv":[336.259644884191403,49.7370433553737143,53.7543298043441524],"hsluv":[336.259644884191403,17.3594920402921318,53.7543298043441524]},"#997799":{"lch":[54.308403390094881,28.1087123643927,307.715012949247921],"luv":[54.308403390094881,17.1950646022419384,-22.235769923906151],"rgb":[0.6,0.466666666666666674,0.6],"xyz":[0.254822703134762518,0.222662837770212396,0.330935180970904],"hpluv":[307.715012949247921,65.67699031054849,54.308403390094881],"hsluv":[307.715012949247921,22.7222383700086681,54.308403390094881]},"#9977aa":{"lch":[54.9367240193748785,39.361724048309469,292.510155009375],"luv":[54.9367240193748785,15.0695247856183894,-36.3628208860492137],"rgb":[0.6,0.466666666666666674,0.66666666666666663],"xyz":[0.269880396100015074,0.228685914956313491,0.41023903058790262],"hpluv":[292.510155009375,90.9181689788077847,54.9367240193748785],"hsluv":[292.510155009375,30.8964586946041671,54.9367240193748785]},"#9977bb":{"lch":[55.6385517902762388,52.2405182414828104,284.355371582691191],"luv":[55.6385517902762388,12.9522722169503091,-50.6093903397058824],"rgb":[0.6,0.466666666666666674,0.733333333333333282],"xyz":[0.287017988207351693,0.235540951799248249,0.500497015686544366],"hpluv":[284.355371582691191,119.143673365147691,55.6385517902762388],"hsluv":[284.355371582691191,44.025050152294007,55.6385517902762388]},"#9977cc":{"lch":[56.4125166695819615,65.6830300518217172,279.531250686135081],"luv":[56.4125166695819615,10.8761592408186605,-64.7763042860340192],"rgb":[0.6,0.466666666666666674,0.8],"xyz":[0.306310363651073447,0.243257901976737051,0.602103526356814522],"hpluv":[279.531250686135081,147.746441920881637,56.4125166695819615],"hsluv":[279.531250686135081,57.5215558896316352,56.4125166695819615]},"#9977dd":{"lch":[57.256707620000924,79.2159287347205,276.425234899334782],"luv":[57.256707620000924,8.86478593594131858,-78.718351962006011],"rgb":[0.6,0.466666666666666674,0.866666666666666696],"xyz":[0.327828689201707,0.251865232196990585,0.71543337425682052],"hpluv":[276.425234899334782,175.559960004438778,57.256707620000924],"hsluv":[276.425234899334782,71.3413917340773764,57.256707620000924]},"#9977ee":{"lch":[58.1687631275758434,92.5981917117116211,274.294200356840065],"luv":[58.1687631275758434,6.93354781471817905,-92.3382424728774822],"rgb":[0.6,0.466666666666666674,0.933333333333333348],"xyz":[0.351640869467881756,0.261390104303460602,0.840844190325343743],"hpluv":[274.294200356840065,202.000294664132923,58.1687631275758434],"hsluv":[274.294200356840065,85.4864748016987903,58.1687631275758434]},"#9977ff":{"lch":[59.1459606243173,105.700835267318482,272.760722153075335],"luv":[59.1459606243173,5.0910859147335179,-105.578157875659116],"rgb":[0.6,0.466666666666666674,1],"xyz":[0.377811917877713122,0.271858523667393326,0.978678378617125588],"hpluv":[272.760722153075335,226.773684284234889,59.1459606243173],"hsluv":[272.760722153075335,99.9999999999988,59.1459606243173]},"#bb7700":{"lch":[55.8465021194210323,78.9426527823167703,44.2288975260652037],"luv":[55.8465021194210323,56.5670601566064,55.0645996403160751],"rgb":[0.733333333333333282,0.466666666666666674,0],"xyz":[0.270895964056058,0.237598076137063796,0.0315945779884111572],"hpluv":[44.2288975260652037,179.372171604304526,55.8465021194210323],"hsluv":[44.2288975260652037,100.000000000002402,55.8465021194210323]},"#bb7711":{"lch":[55.8872675460691113,77.5581898683241775,43.5811171927028127],"luv":[55.8872675460691113,56.1830827877517081,53.4671303149482782],"rgb":[0.733333333333333282,0.466666666666666674,0.0666666666666666657],"xyz":[0.271907629555695085,0.238002742336918649,0.0369226829531667869],"hpluv":[43.5811171927028127,176.097874689063588,55.8872675460691113],"hsluv":[43.5811171927028127,97.2747266570755613,55.8872675460691113]},"#bb7722":{"lch":[55.9627137258116534,75.0570685818031365,42.3362918908129799],"luv":[55.9627137258116534,55.4825342436315907,50.549499878809371],"rgb":[0.733333333333333282,0.466666666666666674,0.133333333333333331],"xyz":[0.273782987694172142,0.238752885592309477,0.0467995691491459864],"hpluv":[42.3362918908129799,170.189263625481374,55.9627137258116534],"hsluv":[42.3362918908129799,92.3060252972229875,55.9627137258116534]},"#bb7733":{"lch":[56.086591241629975,71.12386069085467,40.1553843778465094],"luv":[56.086591241629975,54.3598535070873652,45.8651271257379562],"rgb":[0.733333333333333282,0.466666666666666674,0.2],"xyz":[0.276870738426629848,0.239987985885292576,0.0630617230067571366],"hpluv":[40.1553843778465094,160.914656808849855,56.086591241629975],"hsluv":[40.1553843778465094,84.3528141134219425,56.086591241629975]},"#bb7744":{"lch":[56.2646940492590346,65.8532423012147632,36.6949041745596887],"luv":[56.2646940492590346,52.8030258036740747,39.3508575199960617],"rgb":[0.733333333333333282,0.466666666666666674,0.266666666666666663],"xyz":[0.281328733670848763,0.24177118398298017,0.0865404979596437751],"hpluv":[36.6949041745596887,148.518490760970963,56.2646940492590346],"hsluv":[36.6949041745596887,73.3442464951488517,56.2646940492590346]},"#bb7755":{"lch":[56.5015366957073866,59.5720019456390375,31.4313823601585],"luv":[56.5015366957073866,50.8307220514796256,31.0654327434279267],"rgb":[0.733333333333333282,0.466666666666666674,0.333333333333333315],"xyz":[0.287291147710210748,0.244156149598725,0.117942545233617677],"hpluv":[31.4313823601585,133.789263155984059,56.5015366957073866],"hsluv":[31.4313823601585,59.433527841274838,56.5015366957073866]},"#bb7766":{"lch":[56.8006139195957758,52.9060784141758376,23.584548946691168],"luv":[56.8006139195957758,48.4868685528452588,21.1678225404983067],"rgb":[0.733333333333333282,0.466666666666666674,0.4],"xyz":[0.294876143483718933,0.247190147908128294,0.157890189640761625],"hpluv":[23.584548946691168,118.193030459121616,56.8006139195957758],"hsluv":[23.584548946691168,42.9582981176577476,56.8006139195957758]},"#bb7777":{"lch":[57.1645375630264851,46.8887603377642677,12.1770506300621175],"luv":[57.1645375630264851,45.8337848153679062,9.89040016939892652],"rgb":[0.733333333333333282,0.466666666666666674,0.466666666666666674],"xyz":[0.304190156503920839,0.250915753116209117,0.206943991547159534],"hpluv":[12.1770506300621175,104.083378979503351,57.1645375630264851],"hsluv":[12.1770506300621175,30.9338815185941769,57.1645375630264851]},"#bb7788":{"lch":[57.5951231639082408,43.0166042307866761,356.679907444569722],"luv":[57.5951231639082408,42.9444038116514264,-2.49126891562360298],"rgb":[0.733333333333333282,0.466666666666666674,0.533333333333333326],"xyz":[0.315330573605864639,0.255371919956986715,0.265616854950731729],"hpluv":[356.679907444569722,94.7741152880685149,57.5951231639082408],"hsluv":[356.679907444569722,32.9204937798227419,57.5951231639082408]},"#bb7799":{"lch":[58.0934559183715322,42.8683503206465062,338.533521874652],"luv":[58.0934559183715322,39.8946515879682408,-15.6879646190414519],"rgb":[0.733333333333333282,0.466666666666666674,0.6],"xyz":[0.328387537714937339,0.260594705600615872,0.334383532591849608],"hpluv":[338.533521874652,93.6373004736019823,58.0934559183715322],"hsluv":[338.533521874652,34.9811369830312131,58.0934559183715322]},"#bb77aa":{"lch":[58.6599497668646706,47.0820583870944276,321.324781752589786],"luv":[58.6599497668646706,36.7569990603765,-29.4218157502430451],"rgb":[0.733333333333333282,0.466666666666666674,0.66666666666666663],"xyz":[0.343445230680189895,0.266617782786717,0.413687382208848209],"hpluv":[321.324781752589786,101.848135129910219,58.6599497668646706],"hsluv":[321.324781752589786,37.0556810629478051,58.6599497668646706]},"#bb77bb":{"lch":[59.2944060970233693,54.9189596898792303,307.71501294924542],"luv":[59.2944060970233693,33.5958135510894706,-43.4443718479046765],"rgb":[0.733333333333333282,0.466666666666666674,0.733333333333333282],"xyz":[0.360582822787526569,0.273472819629651753,0.50394536730749],"hpluv":[307.71501294924542,117.529775077760561,59.2944060970233693],"hsluv":[307.71501294924542,40.6617226560308467,59.2944060970233693]},"#bb77cc":{"lch":[59.9960747587738155,65.1135012197087519,297.896140559284788],"luv":[59.9960747587738155,30.4646722292654815,-57.5471266620014177],"rgb":[0.733333333333333282,0.466666666666666674,0.8],"xyz":[0.379875198231248268,0.281189769807140555,0.605551877977760111],"hpluv":[297.896140559284788,137.716994859863917,59.9960747587738155],"hsluv":[297.896140559284788,53.5682479917909,59.9960747587738155]},"#bb77dd":{"lch":[60.7637179337253599,76.6332290650186394,290.953982706702106],"luv":[60.7637179337253599,27.4054240934965065,-71.5653165100750357],"rgb":[0.733333333333333282,0.466666666666666674,0.866666666666666696],"xyz":[0.401393523781881867,0.289797100027394061,0.718881725877766109],"hpluv":[290.953982706702106,160.033945707839,60.7637179337253599],"hsluv":[290.953982706702106,68.5485010485900119,60.7637179337253599]},"#bb77ee":{"lch":[61.5956761624293563,88.8082274030350902,285.979672103614405],"luv":[61.5956761624293563,24.4485758512518743,-85.3766267389077456],"rgb":[0.733333333333333282,0.466666666666666674,0.933333333333333348],"xyz":[0.425205704048056576,0.299321972133864078,0.844292541946289332],"hpluv":[285.979672103614405,182.954165183101395,61.5956761624293563],"hsluv":[285.979672103614405,84.0163684558434909,61.5956761624293563]},"#bb77ff":{"lch":[62.4899351736807773,101.23109143545085,282.328516880108566],"luv":[62.4899351736807773,21.6145233767131266,-98.8966442929694693],"rgb":[0.733333333333333282,0.466666666666666674,1],"xyz":[0.451376752457888,0.309790391497796802,0.982126730238071177],"hpluv":[282.328516880108566,205.562159598045383,62.4899351736807773],"hsluv":[282.328516880108566,99.9999999999986784,62.4899351736807773]},"#998800":{"lch":[56.4673516485332527,62.834492950420568,74.7562721675545561],"luv":[56.4673516485332527,16.5207966112614884,60.6237311922136897],"rgb":[0.6,0.533333333333333326,0],"xyz":[0.219403499200853586,0.243810947756601892,0.0355036829424554834],"hpluv":[74.7562721675545561,141.201731332299261,56.4673516485332527],"hsluv":[74.7562721675545561,100.000000000002331,56.4673516485332527]},"#998811":{"lch":[56.5074221469723881,61.2259080808398366,74.51872844438833],"luv":[56.5074221469723881,16.3426261455871646,59.0044946676862878],"rgb":[0.6,0.533333333333333326,0.0666666666666666657],"xyz":[0.220415164700490718,0.244215613956456745,0.0408317879072111131],"hpluv":[74.51872844438833,137.489352382480689,56.5074221469723881],"hsluv":[74.51872844438833,97.3110722480099781,56.5074221469723881]},"#998822":{"lch":[56.5815852437789744,58.2905156727598666,74.0508275651119],"luv":[56.5815852437789744,16.0173304476746452,56.0466711119068961],"rgb":[0.6,0.533333333333333326,0.133333333333333331],"xyz":[0.222290522838967719,0.244965757211847573,0.0507086741031903127],"hpluv":[74.0508275651119,130.726042229432494,56.5815852437789744],"hsluv":[74.0508275651119,92.4075523300517574,56.5815852437789744]},"#998833":{"lch":[56.7033645714083,53.5858970619529273,73.1917859227149],"luv":[56.7033645714083,15.4953822971072128,51.2966031175623911],"rgb":[0.6,0.533333333333333326,0.2],"xyz":[0.22537827357142548,0.246200857504830672,0.066970827960801449],"hpluv":[73.1917859227149,119.917068416956084,56.7033645714083],"hsluv":[73.1917859227149,84.5557602276611533,56.7033645714083]},"#998844":{"lch":[56.8784692361674189,47.0662938677721456,71.7105236832421156],"luv":[56.8784692361674189,14.7702533586865563,44.6886521856241927],"rgb":[0.6,0.533333333333333326,0.266666666666666663],"xyz":[0.22983626881564434,0.247984055602518266,0.0904496029136880875],"hpluv":[71.7105236832421156,105.00293267331034,56.8784692361674189],"hsluv":[71.7105236832421156,73.6816499137771075,56.8784692361674189]},"#998855":{"lch":[57.1113583918905761,38.8337969655052433,69.1066660847648393],"luv":[57.1113583918905761,13.8492700909341142,36.2803184206870242],"rgb":[0.6,0.533333333333333326,0.333333333333333315],"xyz":[0.235798682855006325,0.250369021218263099,0.121851650187662],"hpluv":[69.1066660847648393,86.2832891510872599,57.1113583918905761],"hsluv":[69.1066660847648393,59.9309888570698774,57.1113583918905761]},"#998866":{"lch":[57.4054971647218224,29.1657433305745144,64.0747883902216],"luv":[57.4054971647218224,12.7511922431808031,26.2306629806110223],"rgb":[0.6,0.533333333333333326,0.4],"xyz":[0.24338367862851451,0.253403019527666418,0.161799294594805965],"hpluv":[64.0747883902216,64.4701815948561574,57.4054971647218224],"hsluv":[64.0747883902216,43.6310875702010321,57.4054971647218224]},"#998877":{"lch":[57.7634914296009612,18.7238256654584347,52.0945218145914097],"luv":[57.7634914296009612,11.5031815844705712,14.773573060880608],"rgb":[0.6,0.533333333333333326,0.466666666666666674],"xyz":[0.252697691648716416,0.257128624735747213,0.210853096501203874],"hpluv":[52.0945218145914097,41.1320618043462858,57.7634914296009612],"hsluv":[52.0945218145914097,25.2418236256697028,57.7634914296009612]},"#998888":{"lch":[58.1871725604667489,10.3706980586515272,12.1770506300640946],"luv":[58.1871725604667489,10.1373621264743505,2.18752539195365081],"rgb":[0.6,0.533333333333333326,0.533333333333333326],"xyz":[0.263838108750660272,0.261584791576524811,0.269525959904776],"hpluv":[12.1770506300640946,22.6162221883482317,58.1871725604667489],"hsluv":[12.1770506300640946,7.14421708061451799,58.1871725604667489]},"#998899":{"lch":[58.6776613659523605,14.2013195506443459,307.715012949254117],"luv":[58.6776613659523605,8.6874348403017283,-11.2341422847952668],"rgb":[0.6,0.533333333333333326,0.6],"xyz":[0.276895072859732971,0.266807577220153969,0.338292637545893948],"hpluv":[307.715012949254117,30.7110899398720818,58.6776613659523605],"hsluv":[307.715012949254117,10.6251017733449729,58.6776613659523605]},"#9988aa":{"lch":[59.2354248002074399,26.2127450059066938,285.910383521223935],"luv":[59.2354248002074399,7.18579172527609789,-25.2085778659891844],"rgb":[0.6,0.533333333333333326,0.66666666666666663],"xyz":[0.291952765824985527,0.272830654406255091,0.417596487162892549],"hpluv":[285.910383521223935,56.1526584479988173,59.2354248002074399],"hsluv":[285.910383521223935,23.5088603674401817,59.2354248002074399]},"#9988bb":{"lch":[59.8603319378123189,39.8857557696632696,278.160160819701673],"luv":[59.8603319378123189,5.66141142610705117,-39.4819190767314581],"rgb":[0.6,0.533333333333333326,0.733333333333333282],"xyz":[0.309090357932322146,0.279685691249189849,0.507854472261534351],"hpluv":[278.160160819701673,84.5508603994872,59.8603319378123189],"hsluv":[278.160160819701673,37.7262541103550291,59.8603319378123189]},"#9988cc":{"lch":[60.55171199345871,53.9990869041691823,274.395593429514747],"luv":[60.55171199345871,4.13861667015410095,-53.8402566723222264],"rgb":[0.6,0.533333333333333326,0.8],"xyz":[0.3283827333760439,0.287402641426678651,0.609460982931804507],"hpluv":[274.395593429514747,113.161661700826286,60.55171199345871],"hsluv":[274.395593429514747,52.5240219153236723,60.55171199345871]},"#9988dd":{"lch":[61.3084150605589855,68.1648195931338563,272.216747448240312],"luv":[61.3084150605589855,2.63660773112319324,-68.1138086575451],"rgb":[0.6,0.533333333333333326,0.866666666666666696],"xyz":[0.349901058926677444,0.296009971646932157,0.722790830831810505],"hpluv":[272.216747448240312,141.084572716849891,61.3084150605589855],"hsluv":[272.216747448240312,67.8395486149695,61.3084150605589855]},"#9988ee":{"lch":[62.128875020953032,82.18421866163294,270.815422264053723],"luv":[62.128875020953032,1.1695901512833331,-82.1758958326656597],"rgb":[0.6,0.533333333333333326,0.933333333333333348],"xyz":[0.373713239192852154,0.305534843753402174,0.848201646900333728],"hpluv":[270.815422264053723,167.854994169255662,62.128875020953032],"hsluv":[270.815422264053723,83.6558877567472905,62.128875020953032]},"#9988ff":{"lch":[63.0111734122257303,95.9388777898474387,269.8490772999765],"luv":[63.0111734122257303,-0.252712116882344406,-95.9385449554102],"rgb":[0.6,0.533333333333333326,1],"xyz":[0.399884287602683575,0.316003263117334898,0.986035835192115462],"hpluv":[269.8490772999765,193.204124490752207,63.0111734122257303],"hsluv":[269.8490772999765,99.9999999999985363,63.0111734122257303]},"#bb8800":{"lch":[60.0458653136574,74.8864062555341832,55.056379834278971],"luv":[60.0458653136574,42.8926945435799,61.3855894869476728],"rgb":[0.733333333333333282,0.533333333333333326,0],"xyz":[0.292968333781028434,0.281742815587005313,0.038952034563401107],"hpluv":[55.056379834278971,158.255644288368103,60.0458653136574],"hsluv":[55.056379834278971,100.000000000002288,60.0458653136574]},"#bb8811":{"lch":[60.0822560315187957,73.5298298886522161,54.5759299869630823],"luv":[60.0822560315187957,42.619621560372849,59.9183089005750773],"rgb":[0.733333333333333282,0.533333333333333326,0.0666666666666666657],"xyz":[0.293979999280665538,0.282147481786860166,0.0442801395281567367],"hpluv":[54.5759299869630823,155.294707870281485,60.0822560315187957],"hsluv":[54.5759299869630823,97.6878818876268866,60.0822560315187957]},"#bb8822":{"lch":[60.1496227929862499,71.0610449842002367,53.6488709316330343],"luv":[60.1496227929862499,42.1201645307819774,57.2325419158225586],"rgb":[0.733333333333333282,0.533333333333333326,0.133333333333333331],"xyz":[0.295855357419142595,0.282897625042250966,0.0541570257241359362],"hpluv":[53.6488709316330343,149.912555699661567,60.1496227929862499],"hsluv":[53.6488709316330343,93.46183514856709,60.1496227929862499]},"#bb8833":{"lch":[60.2602822329103844,67.1261991777524,52.0115270519145412],"luv":[60.2602822329103844,41.3163720666953154,52.9044801061090197],"rgb":[0.733333333333333282,0.533333333333333326,0.2],"xyz":[0.298943108151600301,0.284132725335234093,0.0704191795817470795],"hpluv":[52.0115270519145412,141.351435183858257,60.2602822329103844],"hsluv":[52.0115270519145412,86.668700859176,60.2602822329103844]},"#bb8844":{"lch":[60.4194844691316746,61.7320119628667285,49.374045706205429],"luv":[60.4194844691316746,40.1948297943626258,46.8531424643613406],"rgb":[0.733333333333333282,0.533333333333333326,0.266666666666666663],"xyz":[0.303401103395819216,0.285915923432921659,0.093897954534633718],"hpluv":[49.374045706205429,129.650066203053939,60.4194844691316746],"hsluv":[49.374045706205429,77.207197386412,60.4194844691316746]},"#bb8855":{"lch":[60.6313805732006585,55.0590005000961682,45.2500033552910708],"luv":[60.6313805732006585,38.7623447760430935,39.1020992215604082],"rgb":[0.733333333333333282,0.533333333333333326,0.333333333333333315],"xyz":[0.309363517435181201,0.28830088904866652,0.125300001808607619],"hpluv":[45.2500033552910708,115.231228654446355,60.6313805732006585],"hsluv":[45.2500033552910708,65.1528926573884775,60.6313805732006585]},"#bb8866":{"lch":[60.8992588402534949,47.519038379270981,38.7816256818529581],"luv":[60.8992588402534949,37.0429375720225096,29.7637327048519147],"rgb":[0.733333333333333282,0.533333333333333326,0.4],"xyz":[0.316948513208689386,0.291334887358069838,0.165247646215751581],"hpluv":[38.7816256818529581,99.0136254476035305,60.8992588402534949],"hsluv":[38.7816256818529581,50.7321114426118,60.8992588402534949]},"#bb8877":{"lch":[61.2256685857267087,39.8992767652027354,28.4701407483744688],"luv":[61.2256685857267087,35.0740840991728362,19.0200134329710231],"rgb":[0.733333333333333282,0.533333333333333326,0.466666666666666674],"xyz":[0.326262526228891292,0.295060492566150634,0.21430144812214949],"hpluv":[28.4701407483744688,82.6933933486175761,61.2256685857267087],"hsluv":[28.4701407483744688,34.2887258466009897,61.2256685857267087]},"#bb8888":{"lch":[61.6124959728340684,33.6595928281588499,12.1770506300623627],"luv":[61.6124959728340684,32.9022674846918903,7.09992843085263114],"rgb":[0.733333333333333282,0.533333333333333326,0.533333333333333326],"xyz":[0.337402943330835092,0.299516659406928232,0.272974311525721658],"hpluv":[12.1770506300623627,69.3233245158679,61.6124959728340684],"hsluv":[12.1770506300623627,26.8235367690150284,61.6124959728340684]},"#bb8899":{"lch":[62.0610184830546388,31.1131530731089718,349.361203223606594],"luv":[62.0610184830546388,30.5783355372127765,-5.74401339869088723],"rgb":[0.733333333333333282,0.533333333333333326,0.6],"xyz":[0.350459907439907847,0.304739445050557389,0.341740989166839593],"hpluv":[349.361203223606594,63.6157209963685091,62.0610184830546388],"hsluv":[349.361203223606594,28.7433747177793393,62.0610184830546388]},"#bb88aa":{"lch":[62.5719506337331097,34.1093853871598185,325.627133749050586],"luv":[62.5719506337331097,28.1532372220531357,-19.2573467904743723],"rgb":[0.733333333333333282,0.533333333333333326,0.66666666666666663],"xyz":[0.365517600405160348,0.310762522236658512,0.421044838783838193],"hpluv":[325.627133749050586,69.1725086761469328,62.5719506337331097],"hsluv":[325.627133749050586,30.7007491098828176,62.5719506337331097]},"#bb88bb":{"lch":[63.1454872588298,41.9701566603395477,307.715012949246614],"luv":[63.1454872588298,25.674586077978244,-33.2010493782640808],"rgb":[0.733333333333333282,0.533333333333333326,0.733333333333333282],"xyz":[0.382655192512497,0.31761755907959327,0.51130282388247994],"hpluv":[307.715012949246614,84.3407770596407,63.1454872588298],"hsluv":[307.715012949246614,32.6446535119142354,63.1454872588298]},"#bb88cc":{"lch":[63.7813474201422,52.7335158558763055,296.081540752202443],"luv":[63.7813474201422,23.1842810338905281,-47.3636232510092228],"rgb":[0.733333333333333282,0.533333333333333326,0.8],"xyz":[0.401947567956218776,0.325334509257082072,0.612909334552750096],"hpluv":[296.081540752202443,104.913738848088499,63.7813474201422],"hsluv":[296.081540752202443,48.441096457031712,63.7813474201422]},"#bb88dd":{"lch":[64.4788201663492089,64.9599578897110916,288.597779393076337],"luv":[64.4788201663492089,20.7171971389296665,-61.56779898404492],"rgb":[0.733333333333333282,0.533333333333333326,0.866666666666666696],"xyz":[0.423465893506852264,0.333941839477335578,0.726239182452756094],"hpluv":[288.597779393076337,127.840358269428478,64.4788201663492089],"hsluv":[288.597779393076337,64.9415793589177,64.4788201663492089]},"#bb88ee":{"lch":[65.2368122498474463,77.8541115668441392,283.595470556329246],"luv":[65.2368122498474463,18.300798164755431,-75.6726071600252084],"rgb":[0.733333333333333282,0.533333333333333326,0.933333333333333348],"xyz":[0.44727807377302703,0.343466711583805595,0.851649998521279317],"hpluv":[283.595470556329246,151.435657753316406,65.2368122498474463],"hsluv":[283.595470556329246,82.1213401763518789,65.2368122498474463]},"#bb88ff":{"lch":[66.0538972531437452,90.9819525935223652,280.100148709787334],"luv":[66.0538972531437452,15.9554396417090967,-89.5719802369565201],"rgb":[0.733333333333333282,0.533333333333333326,1],"xyz":[0.473449122182858395,0.353935130947738319,0.989484186813061162],"hpluv":[280.100148709787334,174.781769995450787,66.0538972531437452],"hsluv":[280.100148709787334,99.99999999999838,66.0538972531437452]},"#999900":{"lch":[61.2683639221826866,67.5422828804358772,85.8743202181747449],"luv":[61.2683639221826866,4.85929488236129092,67.3672563635114869],"rgb":[0.6,0.6,0],"xyz":[0.245273099653321058,0.295550148661537559,0.0441268830932777384],"hpluv":[85.8743202181747449,139.887458074797593,61.2683639221826866],"hsluv":[85.8743202181747449,100.000000000002359,61.2683639221826866]},"#999911":{"lch":[61.3036130280217861,66.0751339072958785,85.8743202181746881],"luv":[61.3036130280217861,4.75374160235953358,65.9039093047224185],"rgb":[0.6,0.6,0.0666666666666666657],"xyz":[0.24628476515295819,0.295954814861392412,0.0494549880580333681],"hpluv":[85.8743202181746881,136.770144995815713,61.3036130280217861],"hsluv":[85.8743202181746881,97.7715564197957718,61.3036130280217861]},"#999922":{"lch":[61.3688705786650104,63.38848415762304,85.8743202181745744],"luv":[61.3688705786650104,4.56045196477970372,63.2242216375825663],"rgb":[0.6,0.6,0.133333333333333331],"xyz":[0.248160123291435192,0.296704958116783213,0.0593318742540125676],"hpluv":[85.8743202181745744,131.069475710796667,61.3688705786650104],"hsluv":[85.8743202181745744,93.6963738669960691,61.3688705786650104]},"#999933":{"lch":[61.4760769955270945,59.0559618954583243,85.8743202181743754],"luv":[61.4760769955270945,4.24875087387181871,58.9029265097210484],"rgb":[0.6,0.6,0.2],"xyz":[0.251247874023892925,0.29794005840976634,0.0755940281116237178],"hpluv":[85.8743202181743754,121.898097720990123,61.4760769955270945],"hsluv":[85.8743202181743754,87.1401192062652683,61.4760769955270945]},"#999944":{"lch":[61.6303367515695,52.9921690524208,85.8743202181739775],"luv":[61.6303367515695,3.81249440942850049,52.8548471500809569],"rgb":[0.6,0.6,0.266666666666666663],"xyz":[0.255705869268111841,0.299723256507453906,0.0990728030645103563],"hpluv":[85.8743202181739775,109.10797160418339,61.6303367515695],"hsluv":[85.8743202181739775,77.9969649215058,61.6303367515695]},"#999955":{"lch":[61.8357003743425935,45.2147461889200173,85.8743202181734517],"luv":[61.8357003743425935,3.25295171251598125,45.0975784059909],"rgb":[0.6,0.6,0.333333333333333315],"xyz":[0.261668283307473826,0.302108222123198766,0.130474850338484272],"hpluv":[85.8743202181734517,92.7855058259100218,61.8357003743425935],"hsluv":[85.8743202181734517,66.3286810003367577,61.8357003743425935]},"#999966":{"lch":[62.0953945325949377,35.8293841981041083,85.874320218172457],"luv":[62.0953945325949377,2.57772666020637553,35.7365372872164784],"rgb":[0.6,0.6,0.4],"xyz":[0.269253279080982,0.305142220432602085,0.170422494745628206],"hpluv":[85.874320218172457,73.2182390722606,62.0953945325949377],"hsluv":[85.874320218172457,52.3408174542086542,62.0953945325949377]},"#999977":{"lch":[62.4119425079225749,25.0116267171883422,85.8743202181703],"luv":[62.4119425079225749,1.79944864939855109,24.9468125338318],"rgb":[0.6,0.6,0.466666666666666674],"xyz":[0.278567292101183916,0.30886782564068288,0.219476296652026115],"hpluv":[85.8743202181703,50.8526471570801775,62.4119425079225749],"hsluv":[85.8743202181703,36.3525421484824918,62.4119425079225749]},"#999988":{"lch":[62.7872374999600567,12.9853368609797517,85.8743202181639589],"luv":[62.7872374999600567,0.934223397011331502,12.9516871502363],"rgb":[0.6,0.6,0.533333333333333326],"xyz":[0.289707709203127717,0.313323992481460478,0.27814916005559831],"hpluv":[85.8743202181639589,26.2434647477884546,62.7872374999600567],"hsluv":[85.8743202181639589,18.7604129126121961,62.7872374999600567]},"#999999":{"lch":[63.2225945523589843,3.33307052034688283e-12,0],"luv":[63.2225945523589843,3.14807442966336163e-12,1.09498241031769098e-12],"rgb":[0.6,0.6,0.6],"xyz":[0.302764673312200472,0.318546778125089636,0.346915837696716189],"hpluv":[0,6.68977504875838914e-12,63.2225945523589843],"hsluv":[0,3.10313074237261963e-12,63.2225945523589843]},"#9999aa":{"lch":[63.7187933641432238,13.6904464527836414,265.874320218190064],"luv":[63.7187933641432238,-0.984952144759020598,-13.6549695477167123],"rgb":[0.6,0.6,0.66666666666666663],"xyz":[0.317822366277453,0.324569855311190758,0.42621968731371479],"hpluv":[265.874320218190064,27.2639887848552753,63.7187933641432238],"hsluv":[265.874320218190064,14.5770868731616492,63.7187933641432238]},"#9999bb":{"lch":[64.276118203606174,27.8450519356751158,265.874320218183641],"luv":[64.276118203606174,-2.00329797275791854,-27.7728953213882335],"rgb":[0.6,0.6,0.733333333333333282],"xyz":[0.334959958384789647,0.331424892154125517,0.516477672412356537],"hpluv":[265.874320218183641,54.9715165011475904,64.276118203606174],"hsluv":[265.874320218183641,30.0955931685464577,64.276118203606174]},"#9999cc":{"lch":[64.8943980299807635,42.2483295275786332,265.874320218181538],"luv":[64.8943980299807635,-3.03953438803302189,-42.138848804575062],"rgb":[0.6,0.6,0.8],"xyz":[0.354252333828511401,0.339141842331614318,0.618084183082626692],"hpluv":[265.874320218181538,82.6117192029769,64.8943980299807635],"hsluv":[265.874320218181538,46.4456834766813316,64.8943980299807635]},"#9999dd":{"lch":[65.5730481583578353,56.7175687031348,265.874320218180458],"luv":[65.5730481583578353,-4.08051637559571567,-56.570592941061804],"rgb":[0.6,0.6,0.866666666666666696],"xyz":[0.375770659379144889,0.347749172551867824,0.73141403098263269],"hpluv":[265.874320218180458,109.756831209262941,65.5730481583578353],"hsluv":[265.874320218180458,63.5568222493012627,65.5730481583578353]},"#9999ee":{"lch":[66.311113738117,71.1055788100052695,265.874320218179832],"luv":[66.311113738117,-5.11565437949467672,-70.9213185027987691],"rgb":[0.6,0.6,0.933333333333333348],"xyz":[0.399582839645319654,0.357274044658337842,0.856824847051155913],"hpluv":[265.874320218179832,136.068212717368169,66.311113738117],"hsluv":[265.874320218179832,81.4020980414818922,66.311113738117]},"#9999ff":{"lch":[67.1073146704137145,85.2999068143523829,265.874320218179378],"luv":[67.1073146704137145,-6.13685802391602486,-85.0788638624864149],"rgb":[0.6,0.6,1],"xyz":[0.425753888055151,0.367742464022270565,0.994659035342937758],"hpluv":[265.874320218179378,161.293929533565688,67.1073146704137145],"hsluv":[265.874320218179378,99.9999999999983,67.1073146704137145]},"#bb9900":{"lch":[64.4418646198176219,74.1135014806344117,66.2793330800256228],"luv":[64.4418646198176219,29.8142337654579457,67.8522112145111],"rgb":[0.733333333333333282,0.6,0],"xyz":[0.318837934233495934,0.333482016491941036,0.047575234714223362],"hpluv":[66.2793330800256228,145.938057142603384,64.4418646198176219],"hsluv":[66.2793330800256228,100.000000000002416,64.4418646198176219]},"#bb9911":{"lch":[64.4743890579801331,72.806990252212529,65.9899074816349],"luv":[64.4743890579801331,29.6249863695688624,66.5072778888794147],"rgb":[0.733333333333333282,0.6,0.0666666666666666657],"xyz":[0.319849599733133039,0.333886682691795889,0.0529033396789789917],"hpluv":[65.9899074816349,143.29306400111571,64.4743890579801331],"hsluv":[65.9899074816349,98.0367215419372542,64.4743890579801331]},"#bb9922":{"lch":[64.5346112536789,70.4161712525116599,65.4311312102869636],"luv":[64.5346112536789,29.2781077929992222,64.0408430450799671],"rgb":[0.733333333333333282,0.6,0.133333333333333331],"xyz":[0.321724957871610096,0.334636825947186689,0.0627802258749581843],"hpluv":[65.4311312102869636,138.458312961065701,64.5346112536789],"hsluv":[65.4311312102869636,94.4406497380354892,64.5346112536789]},"#bb9933":{"lch":[64.6335704733000398,66.566957174521292,64.4427367566729146],"luv":[64.6335704733000398,28.7178476591491503,60.0536844273559396],"rgb":[0.733333333333333282,0.6,0.2],"xyz":[0.324812708604067801,0.335871926240169816,0.0790423797325693345],"hpluv":[64.4427367566729146,130.689255588928205,64.6335704733000398],"hsluv":[64.4427367566729146,88.6394107340449153,64.6335704733000398]},"#bb9944":{"lch":[64.7760175449466828,61.1991201001527685,62.8442649593615386],"luv":[64.7760175449466828,27.9319304868930693,54.4530950480162872],"rgb":[0.733333333333333282,0.6,0.266666666666666663],"xyz":[0.329270703848286717,0.337655124337857382,0.102521154685455973],"hpluv":[62.8442649593615386,119.886494259466,64.7760175449466828],"hsluv":[62.8442649593615386,80.5164965868838607,64.7760175449466828]},"#bb9955":{"lch":[64.965753761967747,54.3684881208399204,60.3198292286154],"luv":[64.965753761967747,26.9209928274273445,47.235501963369849],"rgb":[0.733333333333333282,0.6,0.333333333333333315],"xyz":[0.335233117887648702,0.340040089953602243,0.133923201959429888],"hpluv":[60.3198292286154,106.194518749025775,64.965753761967747],"hsluv":[60.3198292286154,70.0945405394646883,64.965753761967747]},"#bb9966":{"lch":[65.2058459998609,46.2670609228556557,56.2614414361724258],"luv":[65.2058459998609,25.6969192719684436,38.4747874116176689],"rgb":[0.733333333333333282,0.6,0.4],"xyz":[0.342818113661156887,0.343074088263005561,0.17387084636657385],"hpluv":[56.2614414361724258,90.0377647384168,65.2058459998609],"hsluv":[56.2614414361724258,57.5177389460533064,65.2058459998609]},"#bb9977":{"lch":[65.4987393303808147,37.2952826673053153,49.3796729345645886],"luv":[65.4987393303808147,24.280853083902965,28.3086255892461871],"rgb":[0.733333333333333282,0.6,0.466666666666666674],"xyz":[0.352132126681358792,0.346799693471086357,0.222924648272971759],"hpluv":[49.3796729345645886,72.2537327682772172,65.4987393303808147],"hsluv":[49.3796729345645886,43.0302399575574199,65.4987393303808147]},"#bb9988":{"lch":[65.8463246780106601,28.3139420599436384,36.7022131699001193],"luv":[65.8463246780106601,22.7007755052872291,16.9220006628175241],"rgb":[0.733333333333333282,0.6,0.533333333333333326],"xyz":[0.363272543783302593,0.351255860311863954,0.281597511676543899],"hpluv":[36.7022131699001193,54.564242001153282,65.8463246780106601],"hsluv":[36.7022131699001193,26.9495650592517677,65.8463246780106601]},"#bb9999":{"lch":[66.2499853133799377,21.4719543680734333,12.1770506300627517],"luv":[66.2499853133799377,20.9888452793887552,4.52914983440691099],"rgb":[0.733333333333333282,0.6,0.6],"xyz":[0.376329507892375292,0.356478645955493112,0.350364189317661834],"hpluv":[12.1770506300627517,41.1268186121042731,66.2499853133799377],"hsluv":[12.1770506300627517,20.948078856310218,66.2499853133799377]},"#bb99aa":{"lch":[66.7106335886793715,21.0368039825091344,335.738246937474969],"luv":[66.7106335886793715,19.1787866223333445,-8.64414631375011489],"rgb":[0.733333333333333282,0.6,0.66666666666666663],"xyz":[0.391387200857627848,0.362501723141594234,0.429668038934660435],"hpluv":[335.738246937474969,40.0151105801343192,66.7106335886793715],"hsluv":[335.738246937474969,22.766845509204984,66.7106335886793715]},"#bb99bb":{"lch":[67.2287438260669887,28.2861274819753,307.715012949249171],"luv":[67.2287438260669887,17.3035955220760833,-22.3761165070024362],"rgb":[0.733333333333333282,0.6,0.733333333333333282],"xyz":[0.408524792964964523,0.369356759984529,0.519926024033302125],"hpluv":[307.715012949249171,53.3897422679679323,67.2287438260669887],"hsluv":[307.715012949249171,24.5905380245485432,67.2287438260669887]},"#bb99cc":{"lch":[67.8043844715017343,39.5775547497090656,292.889275489017223],"luv":[67.8043844715017343,15.393749985076461,-36.4611478338680044],"rgb":[0.733333333333333282,0.6,0.8],"xyz":[0.427817168408686221,0.377073710162017794,0.621532534703572281],"hpluv":[292.889275489017223,74.0679810995506642,67.8043844715017343],"hsluv":[292.889275489017223,42.0187450252511,67.8043844715017343]},"#bb99dd":{"lch":[68.4372510447458353,52.4777928084534082,284.879936967142157],"luv":[68.4372510447458353,13.4760025497769718,-50.718005612655638],"rgb":[0.733333333333333282,0.6,0.866666666666666696],"xyz":[0.449335493959319821,0.385681040382271301,0.734862382603578279],"hpluv":[284.879936967142157,97.3021261982971737,68.4372510447458353],"hsluv":[284.879936967142157,60.4059975969609724,68.4372510447458353]},"#bb99ee":{"lch":[69.1267004581107898,66.0165196934359244,280.096152477623832],"luv":[69.1267004581107898,11.5727364631099334,-64.9942508472035456],"rgb":[0.733333333333333282,0.6,0.933333333333333348],"xyz":[0.47314767422549453,0.395205912488741318,0.860273198672101502],"hpluv":[280.096152477623832,121.184234531677617,69.1267004581107898],"hsluv":[280.096152477623832,79.7264365589122548,69.1267004581107898]},"#bb99ff":{"lch":[69.8717866786541,79.7596884688517207,276.986638727898821],"luv":[69.8717866786541,9.70179942494587877,-79.1674364405365765],"rgb":[0.733333333333333282,0.6,1],"xyz":[0.499318722635325951,0.405674331852674042,0.998107386963883347],"hpluv":[276.986638727898821,144.850809586534439,69.8717866786541],"hsluv":[276.986638727898821,99.9999999999980247,69.8717866786541]},"#880000":{"lch":[27.3946073685119416,92.1289276169810876,12.1770506300617765],"luv":[27.3946073685119416,90.0560691570773,19.4330571920800175],"rgb":[0.533333333333333326,0,0],"xyz":[0.101531161901381561,0.0523520053554009795,0.00475927321412716],"hpluv":[12.1770506300617765,426.746789183125316,27.3946073685119416],"hsluv":[12.1770506300617765,100.000000000002245,27.3946073685119416]},"#880011":{"lch":[27.5061298630582485,89.4551794237446529,10.4692299831444977],"luv":[27.5061298630582485,87.9659862388495242,16.254672889999533],"rgb":[0.533333333333333326,0,0.0666666666666666657],"xyz":[0.10254282740101868,0.0527566715552558324,0.0100873781788827915],"hpluv":[10.4692299831444977,412.68181181873,27.5061298630582485],"hsluv":[10.4692299831444977,99.9999999999965,27.5061298630582485]},"#880022":{"lch":[27.711363673312789,85.0234292319238421,7.23413932290422057],"luv":[27.711363673312789,84.3466296586470463,10.7065206104973338],"rgb":[0.533333333333333326,0,0.133333333333333331],"xyz":[0.104418185539495709,0.0535068148106466537,0.0199642643748619876],"hpluv":[7.23413932290422057,389.331950846774873,27.711363673312789],"hsluv":[7.23413932290422057,99.9999999999967,27.711363673312789]},"#880033":{"lch":[28.0451389930846,79.0521177396887396,1.75350406004841131],"luv":[28.0451389930846,79.0150993176991392,2.41896650323101259],"rgb":[0.533333333333333326,0,0.2],"xyz":[0.107505936271953442,0.0547419151036297666,0.0362264182324731343],"hpluv":[1.75350406004841131,357.680479105960103,28.0451389930846],"hsluv":[1.75350406004841131,99.9999999999969589,28.0451389930846]},"#880044":{"lch":[28.5182895144164306,72.8806899851902585,353.674121255230034],"luv":[28.5182895144164306,72.4369406321056459,-8.0302306678086488],"rgb":[0.533333333333333326,0,0.266666666666666663],"xyz":[0.111963931516172316,0.0565251132013173396,0.0597051931853597728],"hpluv":[353.674121255230034,324.286096087098713,28.5182895144164306],"hsluv":[353.674121255230034,99.9999999999972857,28.5182895144164306]},"#880055":{"lch":[29.1358047874334787,68.1690091719341922,343.056201782139055],"luv":[29.1358047874334787,65.2098664506571453,-19.866734230132252],"rgb":[0.533333333333333326,0,0.333333333333333315],"xyz":[0.117926345555534315,0.0589100788170621725,0.0911072404593336743],"hpluv":[343.056201782139055,296.892542908362316,29.1358047874334787],"hsluv":[343.056201782139055,99.9999999999977405,29.1358047874334787]},"#880066":{"lch":[29.8977347275108087,66.3157421691867768,330.790160549998632],"luv":[29.8977347275108087,57.8829185884671915,-32.3627161272186115],"rgb":[0.533333333333333326,0,0.4],"xyz":[0.125511341329042486,0.0619440771264654841,0.13105488486647765],"hpluv":[330.790160549998632,281.460643767249167,29.8977347275108087],"hsluv":[330.790160549998632,99.9999999999981384,29.8977347275108087]},"#880077":{"lch":[30.8000475559674527,67.8890879971799,318.512376228514142],"luv":[30.8000475559674527,50.8556366042998462,-44.9736866918894549],"rgb":[0.533333333333333326,0,0.466666666666666674],"xyz":[0.13482535434924442,0.0656696823345463,0.180108686772875559],"hpluv":[318.512376228514142,279.697068124812404,30.8000475559674527],"hsluv":[318.512376228514142,99.9999999999984652,30.8000475559674527]},"#880088":{"lch":[31.8355421357531156,72.5162027692933862,307.715012949243601],"luv":[31.8355421357531156,44.3606514294377803,-57.3649045046986288],"rgb":[0.533333333333333326,0,0.533333333333333326],"xyz":[0.14596577145118822,0.0701258491753239,0.238781550176447727],"hpluv":[307.715012949243601,289.042783730483279,31.8355421357531156],"hsluv":[307.715012949243601,99.9999999999987921,31.8355421357531156]},"#880099":{"lch":[32.9947769935272675,79.3376809512942,299.026215263792551],"luv":[32.9947769935272675,38.4954159686478121,-69.3726932454703444],"rgb":[0.533333333333333326,0,0.6],"xyz":[0.159022735560260947,0.0753486348189530558,0.307548227817565634],"hpluv":[299.026215263792551,305.122076286487129,32.9947769935272675],"hsluv":[299.026215263792551,99.9999999999991189,32.9947769935272675]},"#8800aa":{"lch":[34.2669429307518527,87.5167556566874651,292.341813883439613],"luv":[34.2669429307518527,33.2678552259606306,-80.9470958672197298],"rgb":[0.533333333333333326,0,0.66666666666666663],"xyz":[0.174080428525513475,0.0813717120050541642,0.386852077434564234],"hpluv":[292.341813883439613,324.082197305514,34.2669429307518527],"hsluv":[292.341813883439613,99.9999999999993605,34.2669429307518527]},"#8800bb":{"lch":[35.6406160405817047,96.4510237672048589,287.271351738157534],"luv":[35.6406160405817047,28.6360630376966157,-92.1019862947753296],"rgb":[0.533333333333333326,0,0.733333333333333282],"xyz":[0.19121802063285015,0.0882267488479889228,0.477110062533206],"hpluv":[287.271351738157534,343.400533998367337,35.6406160405817047],"hsluv":[287.271351738157534,99.999999999999531,35.6406160405817047]},"#8800cc":{"lch":[37.1043554501127346,105.765919518381835,283.413875530142832],"luv":[37.1043554501127346,24.5359458020369345,-102.880596300606314],"rgb":[0.533333333333333326,0,0.8],"xyz":[0.210510396076571876,0.0959436990254777244,0.578716573203476137],"hpluv":[283.413875530142832,361.709723992276565,37.1043554501127346],"hsluv":[283.413875530142832,99.9999999999998,37.1043554501127346]},"#8800dd":{"lch":[38.6471386159700145,115.245648848701009,280.44740978906907],"luv":[38.6471386159700145,20.8978330626497737,-113.335079087825761],"rgb":[0.533333333333333326,0,0.866666666666666696],"xyz":[0.23202872162720542,0.104551029245731258,0.692046421103482134],"hpluv":[280.44740978906907,378.39598449622531,38.6471386159700145],"hsluv":[280.44740978906907,99.9999999999998863,38.6471386159700145]},"#8800ee":{"lch":[40.258648150966188,124.7713904223,278.13468614008417],"luv":[40.258648150966188,17.6552208530237813,-123.515962711485074],"rgb":[0.533333333333333326,0,0.933333333333333348],"xyz":[0.255840901893380157,0.114075901352201275,0.817457237172005358],"hpluv":[278.13468614008417,393.273926011730225,40.258648150966188],"hsluv":[278.13468614008417,99.9999999999999858,40.258648150966188]},"#8800ff":{"lch":[41.9294357887748674,134.280036872974534,276.305800055850909],"luv":[41.9294357887748674,14.7486383519278057,-133.467621426964229],"rgb":[0.533333333333333326,0,1],"xyz":[0.282011950303211578,0.124544320716133985,0.955291425463787203],"hpluv":[276.305800055850909,406.37947026199555,41.9294357887748674],"hsluv":[276.305800055850909,100.000000000000171,41.9294357887748674]},"#aa0000":{"lch":[35.0982840320529732,118.036634932245676,12.1770506300617765],"luv":[35.0982840320529732,115.380864984340803,24.8978549596859438],"rgb":[0.66666666666666663,0,0],"xyz":[0.165771937912151307,0.08547615548595483,0.00777055958963192815],"hpluv":[12.1770506300617765,426.746789183125145,35.0982840320529732],"hsluv":[12.1770506300617765,100.000000000002217,35.0982840320529732]},"#aa0011":{"lch":[35.178794604810534,115.883637018633408,11.1343823918443601],"luv":[35.178794604810534,113.702354404428164,22.3783808966644813],"rgb":[0.66666666666666663,0,0.0666666666666666657],"xyz":[0.166783603411788439,0.0858808216858096829,0.0130986645543875596],"hpluv":[11.1343823918443601,418.004049663923468,35.178794604810534],"hsluv":[11.1343823918443601,99.9999999999964473,35.178794604810534]},"#aa0022":{"lch":[35.327373324777,112.154849255399441,9.17432067350408431],"luv":[35.327373324777,110.720144559212301,17.8818287736043224],"rgb":[0.66666666666666663,0,0.133333333333333331],"xyz":[0.16865896155026544,0.0866309649412005,0.0229755507503667557],"hpluv":[9.17432067350408431,402.852473647417696,35.327373324777],"hsluv":[9.17432067350408431,99.9999999999965752,35.327373324777]},"#aa0033":{"lch":[35.5701485089931921,106.706281850707128,5.8788523359554592],"luv":[35.5701485089931921,106.14508040487398,10.9294323844098908],"rgb":[0.66666666666666663,0,0.2],"xyz":[0.171746712282723202,0.0878660652341836101,0.0392377046079779],"hpluv":[5.8788523359554592,380.665602767339294,35.5701485089931921],"hsluv":[5.8788523359554592,99.9999999999967741,35.5701485089931921]},"#aa0044":{"lch":[35.9166782648329104,100.198740700315142,1.0062433800652546],"luv":[35.9166782648329104,100.183288799466339,1.75962588401964615],"rgb":[0.66666666666666663,0,0.266666666666666663],"xyz":[0.176204707526942062,0.0896492633318711901,0.0627164795608645409],"hpluv":[1.0062433800652546,354.001763490246503,35.9166782648329104],"hsluv":[1.0062433800652546,99.999999999997,35.9166782648329104]},"#aa0055":{"lch":[36.3730398367095,93.6502679946689369,354.384147096436777],"luv":[36.3730398367095,93.2007806216229113,-9.16445235643848832],"rgb":[0.66666666666666663,0,0.333333333333333315],"xyz":[0.182167121566304047,0.092034228947616023,0.0941185268348384424],"hpluv":[354.384147096436777,326.714758289773386,36.3730398367095],"hsluv":[354.384147096436777,99.9999999999973,36.3730398367095]},"#aa0066":{"lch":[36.9423385777606228,88.2319659172366926,346.039412913085584],"luv":[36.9423385777606228,85.6257622699194,-21.2863488018886251],"rgb":[0.66666666666666663,0,0.4],"xyz":[0.189752117339812232,0.0950682272570193415,0.134066171241982418],"hpluv":[346.039412913085584,303.068568849792825,36.9423385777606228],"hsluv":[346.039412913085584,99.9999999999976126,36.9423385777606228]},"#aa0077":{"lch":[37.6250775946346891,84.9907340508927689,336.365700313169],"luv":[37.6250775946346891,77.8619577045834319,-34.0725757306260348],"rgb":[0.66666666666666663,0,0.466666666666666674],"xyz":[0.199066130360014137,0.0987938324651001509,0.183119973148380327],"hpluv":[336.365700313169,286.637826777930513,37.6250775946346891],"hsluv":[336.365700313169,99.9999999999979536,37.6250775946346891]},"#aa0088":{"lch":[38.4195160158879432,84.5572797483387717,326.161033183527252],"luv":[38.4195160158879432,70.2337789048813761,-47.0866208086656215],"rgb":[0.66666666666666663,0,0.533333333333333326],"xyz":[0.210206547461958,0.103249999305877749,0.241792836551952495],"hpluv":[326.161033183527252,279.279102381419364,38.4195160158879432],"hsluv":[326.161033183527252,99.9999999999982379,38.4195160158879432]},"#aa0099":{"lch":[39.3220484546604681,86.9871636461465272,316.374304421046759],"luv":[39.3220484546604681,62.9667468975572859,-60.0162929906574334],"rgb":[0.66666666666666663,0,0.6],"xyz":[0.223263511571030693,0.108472784949506906,0.310559514193070374],"hpluv":[316.374304421046759,280.710309296009257,39.3220484546604681],"hsluv":[316.374304421046759,99.9999999999986,39.3220484546604681]},"#aa00aa":{"lch":[40.3276007574525863,91.8597353001339627,307.715012949243601],"luv":[40.3276007574525863,56.1937545325413,-72.6668626056414411],"rgb":[0.66666666666666663,0,0.66666666666666663],"xyz":[0.238321204536283249,0.114495862135608,0.389863363810069],"hpluv":[307.715012949243601,289.042783730483393,40.3276007574525863],"hsluv":[307.715012949243601,99.9999999999988205,40.3276007574525863]},"#aa00bb":{"lch":[41.4300227805658849,98.5480850422065089,300.471226581677797],"luv":[41.4300227805658849,49.974285465742625,-84.9370111180893304],"rgb":[0.66666666666666663,0,0.733333333333333282],"xyz":[0.255458796643619868,0.121350898978542759,0.480121348908710721],"hpluv":[300.471226581677797,301.836908489583834,41.4300227805658849],"hsluv":[300.471226581677797,99.9999999999990621,41.4300227805658849]},"#aa00cc":{"lch":[42.6224565622471445,106.453892931925211,294.601049164416338],"luv":[42.6224565622471445,44.3164832708711813,-96.7909119228886681],"rgb":[0.66666666666666663,0,0.8],"xyz":[0.274751172087341622,0.129067849156031561,0.581727859578980877],"hpluv":[294.601049164416338,316.929304470761622,42.6224565622471445],"hsluv":[294.601049164416338,99.9999999999992895,42.6224565622471445]},"#aa00dd":{"lch":[43.8976622887243266,115.112632227118652,289.907671140995035],"luv":[43.8976622887243266,39.1964773946494063,-108.233794436426436],"rgb":[0.66666666666666663,0,0.866666666666666696],"xyz":[0.296269497637975165,0.137675179376285095,0.695057707478986875],"hpluv":[289.907671140995035,332.752186796280228,43.8976622887243266],"hsluv":[289.907671140995035,99.9999999999994174,43.8976622887243266]},"#aa00ee":{"lch":[45.2482911917969233,124.202454763835647,286.162342623679535],"luv":[45.2482911917969233,34.5729825657367655,-119.293581746345012],"rgb":[0.66666666666666663,0,0.933333333333333348],"xyz":[0.320081677904149875,0.14720005148275514,0.820468523547510098],"hpluv":[286.162342623679535,348.311106794177135,45.2482911917969233],"hsluv":[286.162342623679535,99.9999999999996589,45.2482911917969233]},"#aa00ff":{"lch":[46.667101462293175,133.514790614533382,283.159905061129905],"luv":[46.667101462293175,30.397247590160724,-130.008486845225434],"rgb":[0.66666666666666663,0,1],"xyz":[0.346252726313981296,0.157668470846687836,0.958302711839291943],"hpluv":[283.159905061129905,363.042841924949244,46.667101462293175],"hsluv":[283.159905061129905,99.9999999999998153,46.667101462293175]},"#881100":{"lch":[28.4751123640698864,88.1761994811112,13.8943544232398857],"luv":[28.4751123640698864,85.5961768878489124,21.1739617718743069],"rgb":[0.533333333333333326,0.0666666666666666657,0],"xyz":[0.103535562162309969,0.0563608058772578496,0.00542740663443661096],"hpluv":[13.8943544232398857,392.939109149716501,28.4751123640698864],"hsluv":[13.8943544232398857,100.000000000002331,28.4751123640698864]},"#881111":{"lch":[28.5813012406410962,85.6429421929893522,12.1770506300617782],"luv":[28.5813012406410962,83.7160154193071548,18.0649469909557752],"rgb":[0.533333333333333326,0.0666666666666666657,0.0666666666666666657],"xyz":[0.104547227661947087,0.0567654720771127,0.0107555115991922433],"hpluv":[12.1770506300617782,380.232213605760478,28.5813012406410962],"hsluv":[12.1770506300617782,89.1001931926906536,28.5813012406410962]},"#881122":{"lch":[28.776819878520115,81.4294437186752589,8.91447414891876377],"luv":[28.776819878520115,80.445837197505071,12.6183034487767767],"rgb":[0.533333333333333326,0.0666666666666666657,0.133333333333333331],"xyz":[0.106422585800424116,0.0575156153325035238,0.0206323977951714393],"hpluv":[8.91447414891876377,359.069069298387092,28.776819878520115],"hsluv":[8.91447414891876377,89.5522119422979,28.776819878520115]},"#881133":{"lch":[29.0950676619922959,75.7256767264573227,3.35964558590209394],"luv":[29.0950676619922959,75.5955308554484162,4.43777313107212557],"rgb":[0.533333333333333326,0.0666666666666666657,0.2],"xyz":[0.10951033653288185,0.0587507156254866367,0.0368945516527825826],"hpluv":[3.35964558590209394,330.265430862114329,29.0950676619922959],"hsluv":[3.35964558590209394,90.2199940579986475,29.0950676619922959]},"#881144":{"lch":[29.5467689283324617,69.8105982852884779,355.112641815866198],"luv":[29.5467689283324617,69.5567752183087435,-5.94765955478224484],"rgb":[0.533333333333333326,0.0666666666666666657,0.266666666666666663],"xyz":[0.113968331777100723,0.0605339137231742097,0.0603733266056692211],"hpluv":[355.112641815866198,299.81315922456514,29.5467689283324617],"hsluv":[355.112641815866198,91.0462468049379083,29.5467689283324617]},"#881155":{"lch":[30.1372440361953267,65.3247498846549,344.189828060851937],"luv":[30.1372440361953267,62.8534910411243928,-17.7977979378353552],"rgb":[0.533333333333333326,0.0666666666666666657,0.333333333333333315],"xyz":[0.119930745816462722,0.0629188793389190426,0.0917753738796431295],"hpluv":[344.189828060851937,275.05120204756264,30.1372440361953267],"hsluv":[344.189828060851937,91.9552565263170294,30.1372440361953267]},"#881166":{"lch":[30.8672249177773494,63.7021552959616173,331.50461515751158],"luv":[30.8672249177773494,55.9849923977636479,-30.3915319714745138],"rgb":[0.533333333333333326,0.0666666666666666657,0.4],"xyz":[0.127515741589970893,0.0659528776483223611,0.131723018286787091],"hpluv":[331.50461515751158,261.876101180723595,30.8672249177773494],"hsluv":[331.50461515751158,92.8754029221989299,30.8672249177773494]},"#881177":{"lch":[31.7336031237729514,65.5512880923603376,318.81152503011009],"luv":[31.7336031237729514,49.3304506912780525,-43.1680206305895737],"rgb":[0.533333333333333326,0.0666666666666666657,0.466666666666666674],"xyz":[0.136829754610172827,0.0696784828564031705,0.180776820193185],"hpluv":[318.81152503011009,262.120610410187965,31.7336031237729514],"hsluv":[318.81152503011009,93.7528273751248094,31.7336031237729514]},"#881188":{"lch":[32.7302234117729114,70.4946015177073377,307.715012949243658],"luv":[32.7302234117729114,43.1239685223607268,-55.7656900075914663],"rgb":[0.533333333333333326,0.0666666666666666657,0.533333333333333326],"xyz":[0.147970171712116627,0.0741346496971807684,0.239449683596757168],"hpluv":[307.715012949243658,273.304143969878908,32.7302234117729114],"hsluv":[307.715012949243658,94.5549099834012736,32.7302234117729114]},"#881199":{"lch":[33.8487030992268245,77.6382380820710836,298.861624073140206],"luv":[33.8487030992268245,37.4756592651389795,-67.9946393117248249],"rgb":[0.533333333333333326,0.0666666666666666657,0.6],"xyz":[0.161027135821189354,0.0793574353408099259,0.308216361237875103],"hpluv":[298.861624073140206,291.053592363859707,33.8487030992268245],"hsluv":[298.861624073140206,95.267111116431,33.8487030992268245]},"#8811aa":{"lch":[35.0792182273937954,86.1162494337963551,292.107316515455238],"luv":[35.0792182273937954,32.4092110837954692,-79.7850327659882623],"rgb":[0.533333333333333326,0.0666666666666666657,0.66666666666666663],"xyz":[0.176084828786441883,0.0853805125269110343,0.387520210854873703],"hpluv":[292.107316515455238,311.511817997128389,35.0792182273937954],"hsluv":[292.107316515455238,95.8874450484264571,35.0792182273937954]},"#8811bb":{"lch":[36.4111998559147381,95.3124415598142,287.019214731984619],"luv":[36.4111998559147381,27.8972267898096504,-91.1383906678789515],"rgb":[0.533333333333333326,0.0666666666666666657,0.733333333333333282],"xyz":[0.193222420893778557,0.0922355493698457929,0.47777819595351545],"hpluv":[287.019214731984619,332.16504711372977,36.4111998559147381],"hsluv":[287.019214731984619,96.4212920970111753,36.4111998559147381]},"#8811cc":{"lch":[37.8339039869932847,104.849214202707898,283.169050576302368],"luv":[37.8339039869932847,23.8872658659353974,-102.091900993053514],"rgb":[0.533333333333333326,0.0666666666666666657,0.8],"xyz":[0.212514796337500284,0.0999524995473346,0.579384706623785606],"hpluv":[283.169050576302368,351.660305548048939,37.8339039869932847],"hsluv":[283.169050576302368,96.8775739500717776,37.8339039869932847]},"#8811dd":{"lch":[39.3368423655390274,114.513051177954694,280.22024610097435],"luv":[39.3368423655390274,20.3183376083265372,-112.696069349906494],"rgb":[0.533333333333333326,0.0666666666666666657,0.866666666666666696],"xyz":[0.234033121888133827,0.108559829767588129,0.692714554523791604],"hpluv":[280.22024610097435,369.398236331583689,39.3368423655390274],"hsluv":[280.22024610097435,97.2663289333616348,39.3368423655390274]},"#8811ee":{"lch":[40.9100807353410261,124.189527798253138,277.928390028110698],"luv":[40.9100807353410261,17.130123009315259,-123.002429652583771],"rgb":[0.533333333333333326,0.0666666666666666657,0.933333333333333348],"xyz":[0.257845302154308564,0.118084701874058146,0.818125370592314827],"hpluv":[277.928390028110698,385.206818333834917,40.9100807353410261],"hsluv":[277.928390028110698,97.5973562787359867,40.9100807353410261]},"#8811ff":{"lch":[42.5444231432324926,133.820472646418182,276.120297984259253],"luv":[42.5444231432324926,14.2674470621751119,-133.057727523202459],"rgb":[0.533333333333333326,0.0666666666666666657,1],"xyz":[0.28401635056414,0.128553121237990842,0.955959558884096672],"hpluv":[276.120297984259253,399.134479754608662,42.5444231432324926],"hsluv":[276.120297984259253,99.9999999999993605,42.5444231432324926]},"#aa1100":{"lch":[35.8849415951509485,114.659477700983,13.2232466646238507],"luv":[35.8849415951509485,111.619416231509064,26.2278810962561089],"rgb":[0.66666666666666663,0.0666666666666666657,0],"xyz":[0.167776338173079714,0.0894849560078117,0.00843869300994137816],"hpluv":[13.2232466646238507,405.449754626827882,35.8849415951509485],"hsluv":[13.2232466646238507,100.000000000002245,35.8849415951509485]},"#aa1111":{"lch":[35.9630348414680086,112.584844162769954,12.1770506300617871],"luv":[35.9630348414680086,110.051736997450746,23.747890832642895],"rgb":[0.66666666666666663,0.0666666666666666657,0.0666666666666666657],"xyz":[0.168788003672716846,0.089889622207666553,0.0137667979746970096],"hpluv":[12.1770506300617871,397.249101663635656,35.9630348414680086],"hsluv":[12.1770506300617871,93.0877775141683514,35.9630348414680086]},"#aa1122":{"lch":[36.1071812157442409,108.986817867719594,10.2082214608018411],"luv":[36.1071812157442409,107.261576994584843,19.3152936702042],"rgb":[0.66666666666666663,0.0666666666666666657,0.133333333333333331],"xyz":[0.170663361811193848,0.0906397654630573674,0.0236436841706762074],"hpluv":[10.2082214608018411,383.018466712830786,36.1071812157442409],"hsluv":[10.2082214608018411,93.272361347425246,36.1071812157442409]},"#aa1133":{"lch":[36.3427932754706546,103.718469067724868,6.89182233030552727],"luv":[36.3427932754706546,102.969049051934846,12.4457126390109512],"rgb":[0.66666666666666663,0.0666666666666666657,0.2],"xyz":[0.173751112543651609,0.0918748657560404802,0.0399058380282873507],"hpluv":[6.89182233030552727,362.140519718911037,36.3427932754706546],"hsluv":[6.89182233030552727,93.5557024333493388,36.3427932754706546]},"#aa1144":{"lch":[36.6792659124992824,97.4113439982971698,1.97455903872184],"luv":[36.6792659124992824,97.3535035649005778,3.35637947697775552],"rgb":[0.66666666666666663,0.0666666666666666657,0.266666666666666663],"xyz":[0.178209107787870469,0.0936580638537280602,0.0633846129811739822],"hpluv":[1.97455903872184,336.99870087691761,36.6792659124992824],"hsluv":[1.97455903872184,93.9250914747756696,36.6792659124992824]},"#aa1155":{"lch":[37.1226754299384396,91.055498296574811,355.267689161716703],"luv":[37.1226754299384396,90.7450919896636634,-7.51212685096688926],"rgb":[0.66666666666666663,0.0666666666666666657,0.333333333333333315],"xyz":[0.184171521827232454,0.0960430294694729,0.0947866602551479],"hpluv":[355.267689161716703,311.247759321881176,37.1226754299384396],"hsluv":[355.267689161716703,94.3576556410013154,37.1226754299384396]},"#aa1166":{"lch":[37.6762679798416,85.8108023849569577,346.783206271719791],"luv":[37.6762679798416,83.5378398512485205,-19.6194576616503156],"rgb":[0.66666666666666663,0.0666666666666666657,0.4],"xyz":[0.191756517600740639,0.0990770277788762116,0.134734304662291859],"hpluv":[346.783206271719791,289.010360822200312,37.6762679798416],"hsluv":[346.783206271719791,94.8263018378468558,37.6762679798416]},"#aa1177":{"lch":[38.3408051028578285,82.7345113545946163,336.916515476294876],"luv":[38.3408051028578285,76.110335477500783,-32.4378822148708],"rgb":[0.66666666666666663,0.0666666666666666657,0.466666666666666674],"xyz":[0.201070530620942545,0.102802632986957021,0.183788106568689769],"hpluv":[336.916515476294876,273.819772016881302,38.3408051028578285],"hsluv":[336.916515476294876,95.3051408978498387,38.3408051028578285]},"#aa1188":{"lch":[39.1148927869010379,82.477083595297529,326.495944929629673],"luv":[39.1148927869010379,68.7732486834780445,-45.5270203714912753],"rgb":[0.66666666666666663,0.0666666666666666657,0.533333333333333326],"xyz":[0.212210947722886401,0.107258799827734619,0.242460969972261936],"hpluv":[326.495944929629673,267.565723971153261,39.1148927869010379],"hsluv":[326.495944929629673,95.7730681487448,39.1148927869010379]},"#aa1199":{"lch":[39.9953287808464424,85.1038505069809617,316.515705271857257],"luv":[39.9953287808464424,61.7482074461545665,-58.5647013848890552],"rgb":[0.66666666666666663,0.0666666666666666657,0.6],"xyz":[0.2252679118319591,0.112481585471363776,0.311227647613379843],"hpluv":[316.515705271857257,270.00963724100518,39.9953287808464424],"hsluv":[316.515705271857257,96.2151887572794,39.9953287808464424]},"#aa11aa":{"lch":[40.9774666162921406,90.1875437006381588,307.715012949243601],"luv":[40.9774666162921406,55.1708175083225498,-71.3440532505540261],"rgb":[0.66666666666666663,0.0666666666666666657,0.66666666666666663],"xyz":[0.240325604797211656,0.118504662657464871,0.390531497230378444],"hpluv":[307.715012949243601,279.28060733669264,40.9774666162921406],"hsluv":[307.715012949243601,96.6225842874192864,40.9774666162921406]},"#aa11bb":{"lch":[42.0555802442747719,97.087212137786878,300.384453602166161],"luv":[42.0555802442747719,49.106684193967304,-83.7523750598249705],"rgb":[0.66666666666666663,0.0666666666666666657,0.733333333333333282],"xyz":[0.257463196904548275,0.12535969950039963,0.48078948232902019],"hpluv":[300.384453602166161,292.939359498794147,42.0555802442747719],"hsluv":[300.384453602166161,96.9911870522472697,42.0555802442747719]},"#aa11cc":{"lch":[43.2232098485165395,105.192683835863036,294.469145625450437],"luv":[43.2232098485165395,43.5711419401365,-95.7447456658814247],"rgb":[0.66666666666666663,0.0666666666666666657,0.8],"xyz":[0.276755572348270029,0.133076649677888431,0.582395992999290346],"hpluv":[294.469145625450437,308.821726609797679,43.2232098485165395],"hsluv":[294.469145625450437,97.3204020480748255,43.2232098485165395]},"#aa11dd":{"lch":[44.4734721926781518,114.032755796715193,289.757274940509092],"luv":[44.4734721926781518,38.5472014936014489,-107.320001172218454],"rgb":[0.66666666666666663,0.0666666666666666657,0.866666666666666696],"xyz":[0.298273897898903573,0.141683979898141965,0.695725840899296344],"hpluv":[289.757274940509092,325.362808980276498,44.4734721926781518],"hsluv":[289.757274940509092,97.611854654502622,44.4734721926781518]},"#aa11ee":{"lch":[45.7993244881172,123.283094749558884,286.008743686799619],"luv":[45.7993244881172,33.9995108001485136,-118.502129585840351],"rgb":[0.66666666666666663,0.0666666666666666657,0.933333333333333348],"xyz":[0.322086078165078282,0.151208852004612,0.821136656967819567],"hpluv":[286.008743686799619,341.573194884792258,45.7993244881172],"hsluv":[286.008743686799619,97.8684161167955153,45.7993244881172]},"#aa11ff":{"lch":[47.1937769411101868,132.735165800167636,283.011169167098501],"luv":[47.1937769411101868,29.8841269734434611,-129.327348983241365],"rgb":[0.66666666666666663,0.0666666666666666657,1],"xyz":[0.348257126574909703,0.161677271368544706,0.958970845259601412],"hpluv":[283.011169167098501,356.89510187446183,47.1937769411101868],"hsluv":[283.011169167098501,99.9999999999993321,47.1937769411101868]},"#882200":{"lch":[30.3496916993887922,81.7292062801124786,17.2000641303745212],"luv":[30.3496916993887922,78.0741152618852254,24.1680716080335465],"rgb":[0.533333333333333326,0.133333333333333331,0],"xyz":[0.107251185897077911,0.0637920533467938311,0.00666594787935922105],"hpluv":[17.2000641303745212,341.713647377264522,30.3496916993887922],"hsluv":[17.2000641303745212,100.000000000002359,30.3496916993887922]},"#882211":{"lch":[30.4474919309639347,79.3871193031655338,15.4743840495427136],"luv":[30.4474919309639347,76.5093230935452908,21.181080969170015],"rgb":[0.533333333333333326,0.133333333333333331,0.0666666666666666657],"xyz":[0.108262851396715029,0.064196719546648684,0.0119940528441148525],"hpluv":[15.4743840495427136,330.855109199112462,30.4474919309639347],"hsluv":[15.4743840495427136,90.2707057474005,30.4474919309639347]},"#882222":{"lch":[30.6277058928754826,75.4670009360781648,12.1770506300618102],"luv":[30.6277058928754826,73.7690281561900321,15.9185022906448506],"rgb":[0.533333333333333326,0.133333333333333331,0.133333333333333331],"xyz":[0.110138209535192058,0.0649468628020395,0.0218709390400940486],"hpluv":[12.1770506300618102,312.666930334371557,30.6277058928754826],"hsluv":[12.1770506300618102,73.2675530922876277,30.6277058928754826]},"#882233":{"lch":[30.9214262019897674,70.1124269978110135,6.50693872003014],"luv":[30.9214262019897674,69.6607724946186266,7.94538828354938],"rgb":[0.533333333333333326,0.133333333333333331,0.2],"xyz":[0.113225960267649792,0.0661819630950226112,0.0381330928977051953],"hpluv":[6.50693872003014,287.723152758323693,30.9214262019897674],"hsluv":[6.50693872003014,74.8097141082451458,30.9214262019897674]},"#882244":{"lch":[31.3391119188553589,64.5127804411509,357.965654494967],"luv":[31.3391119188553589,64.472119792654567,-2.29011128326569047],"rgb":[0.533333333333333326,0.133333333333333331,0.266666666666666663],"xyz":[0.117683955511868665,0.0679651611927101912,0.0616118678505918338],"hpluv":[357.965654494967,261.215173773686786,31.3391119188553589],"hsluv":[357.965654494967,76.7464797952550839,31.3391119188553589]},"#882255":{"lch":[31.8864840032734449,60.2907989794282599,346.464164292394514],"luv":[31.8864840032734449,58.6161449596217778,-14.1112717942230379],"rgb":[0.533333333333333326,0.133333333333333331,0.333333333333333315],"xyz":[0.123646369551230664,0.0703501268084550241,0.0930139151245657353],"hpluv":[346.464164292394514,239.929545755427228,31.8864840032734449],"hsluv":[346.464164292394514,78.9147131880069566,31.8864840032734449]},"#882266":{"lch":[32.565220274416383,58.9629659850378189,332.945096803324191],"luv":[32.565220274416383,52.5107124518348698,-26.8189566455043362],"rgb":[0.533333333333333326,0.133333333333333331,0.4],"xyz":[0.131231365324738836,0.0733841251178583426,0.132961559531709711],"hpluv":[332.945096803324191,229.754818264706444,32.565220274416383],"hsluv":[332.945096803324191,81.1505919454384923,32.565220274416383]},"#882277":{"lch":[33.3735533542235316,61.2256527964903086,319.411642653163199],"luv":[33.3735533542235316,46.4949769025583564,-39.8346291960518499],"rgb":[0.533333333333333326,0.133333333333333331,0.466666666666666674],"xyz":[0.140545378344940741,0.077109730325939152,0.18201536143810762],"hpluv":[319.411642653163199,232.79320602780777,33.3735533542235316],"hsluv":[319.411642653163199,83.3222334130424116,33.3735533542235316]},"#882288":{"lch":[34.3068967831130962,66.691064714973308,307.715012949243771],"luv":[34.3068967831130962,40.7972144472485709,-52.7568517461189046],"rgb":[0.533333333333333326,0.133333333333333331,0.533333333333333326],"xyz":[0.151685795446884597,0.0815658971667167498,0.240688224841679788],"hpluv":[307.715012949243771,246.675229855048,34.3068967831130962],"hsluv":[307.715012949243771,85.3421167175917,34.3068967831130962]},"#882299":{"lch":[35.3585028262625087,74.3915148492043699,298.539568373309862],"luv":[35.3585028262625087,35.5417034620318262,-65.3520068289902554],"rgb":[0.533333333333333326,0.133333333333333331,0.6],"xyz":[0.164742759555957297,0.0867886828103459074,0.309454902482797667],"hpluv":[298.539568373309862,266.973934190138948,35.3585028262625087],"hsluv":[298.539568373309862,87.1641407220543556,35.3585028262625087]},"#8822aa":{"lch":[36.5201138266519365,83.3993900511107142,291.653660077047903],"luv":[36.5201138266519365,30.7739719973965897,-77.5140045953036463],"rgb":[0.533333333333333326,0.133333333333333331,0.66666666666666663],"xyz":[0.179800452521209853,0.092811759996447,0.388758752099796268],"hpluv":[291.653660077047903,289.781114528802732,36.5201138266519365],"hsluv":[291.653660077047903,88.7734689989794532,36.5201138266519365]},"#8822bb":{"lch":[37.7825623664262196,93.0686060696910857,286.53575696187113],"luv":[37.7825623664262196,26.4885972028076893,-89.2195026548722],"rgb":[0.533333333333333326,0.133333333333333331,0.733333333333333282],"xyz":[0.196938044628546471,0.0996667968393817605,0.479016737198438],"hpluv":[286.53575696187113,312.57276028475934,37.7825623664262196],"hsluv":[286.53575696187113,90.1753980157506874,37.7825623664262196]},"#8822cc":{"lch":[39.1362858369643476,103.012880313051866,282.702767559286599],"luv":[39.1362858369643476,22.6518448072254479,-100.491529181421669],"rgb":[0.533333333333333326,0.133333333333333331,0.8],"xyz":[0.216230420072268226,0.107383747016870562,0.580623247868708225],"hpluv":[282.702767559286599,334.003678399645651,39.1362858369643476],"hsluv":[282.702767559286599,91.3862929083300628,39.1362858369643476]},"#8822dd":{"lch":[40.5717373677475379,113.020349666590874,279.789793007972776],"luv":[40.5717373677475379,19.2172965257181652,-111.374570495248548],"rgb":[0.533333333333333326,0.133333333333333331,0.866666666666666696],"xyz":[0.237748745622901769,0.115991077237124096,0.693953095768714223],"hpluv":[279.789793007972776,353.486121759760863,40.5717373677475379],"hsluv":[279.789793007972776,92.4273238443811209,40.5717373677475379]},"#8822ee":{"lch":[42.0796906219744145,122.982600065668066,277.538986095624125],"luv":[42.0796906219744145,16.1354126197819454,-121.919515986988074],"rgb":[0.533333333333333326,0.133333333333333331,0.933333333333333348],"xyz":[0.261560925889076534,0.125515949343594141,0.819363911837237446],"hpluv":[277.538986095624125,370.860397035002336,42.0796906219744145],"hsluv":[277.538986095624125,93.320628909539181,42.0796906219744145]},"#8822ff":{"lch":[43.6514473624058752,132.848943476626658,275.771185477405766],"luv":[43.6514473624058752,13.3587518063908028,-132.175585994657865],"rgb":[0.533333333333333326,0.133333333333333331,1],"xyz":[0.2877319742989079,0.135984368707526837,0.957198100129019291],"hpluv":[275.771185477405766,386.188007357759091,43.6514473624058752],"hsluv":[275.771185477405766,99.9999999999994,43.6514473624058752]},"#ffaa00":{"lch":[76.0766826449234799,103.646966048157225,46.9849230608437125],"luv":[76.0766826449234799,70.7070052858721,75.7839889059127785],"rgb":[1,0.66666666666666663,0],"xyz":[0.556131758114240538,0.500120923568095,0.0672444716650198171],"hpluv":[46.9849230608437125,173.218766512771339,76.0766826449234799],"hsluv":[46.9849230608437125,100.0000000000028,76.0766826449234799]},"#ffaa11":{"lch":[76.1015101579349533,102.726050652762069,46.6846637022245687],"luv":[76.1015101579349533,70.4714195905473275,74.7423608377930719],"rgb":[1,0.66666666666666663,0.0666666666666666657],"xyz":[0.557143423613877697,0.500525589767949919,0.0725725766297754538],"hpluv":[46.6846637022245687,171.896872437304751,76.1015101579349533],"hsluv":[46.6846637022245687,100.000000000002771,76.1015101579349533]},"#ffaa22":{"lch":[76.1474983763177,101.038792737361192,46.1176753587789605],"luv":[76.1474983763177,70.0380208569547591,72.8252241483965719],"rgb":[1,0.66666666666666663,0.133333333333333331],"xyz":[0.559018781752354643,0.501275733023340719,0.0824494628257546464],"hpluv":[46.1176753587789605,169.470349592440897,76.1474983763177],"hsluv":[46.1176753587789605,100.0000000000028,76.1474983763177]},"#ffaa33":{"lch":[76.2231174741888395,98.3169656691378577,45.1538509265191337],"luv":[76.2231174741888395,69.3336656617894533,69.7070193329597885],"rgb":[1,0.66666666666666663,0.2],"xyz":[0.56210653248481246,0.502510833316323846,0.0987116166833657827],"hpluv":[45.1538509265191337,165.543337136749699,76.2231174741888395],"hsluv":[45.1538509265191337,100.000000000002927,76.2231174741888395]},"#ffaa44":{"lch":[76.3320756204529118,94.5107446089494516,43.6926927141772694],"luv":[76.3320756204529118,68.3364903114306514,65.2870962629968119],"rgb":[1,0.66666666666666663,0.266666666666666663],"xyz":[0.56656452772903132,0.504294031414011412,0.122190391636252421],"hpluv":[43.6926927141772694,160.025535099593441,76.3320756204529118],"hsluv":[43.6926927141772694,100.000000000003,76.3320756204529118]},"#ffaa55":{"lch":[76.4774026026215,89.6489515946998807,41.6012791226812411],"luv":[76.4774026026215,67.0379860163697288,59.5217855318358247],"rgb":[1,0.66666666666666663,0.333333333333333315],"xyz":[0.572526941768393249,0.506678997029756162,0.153592438910226337],"hpluv":[41.6012791226812411,152.933128718005122,76.4774026026215],"hsluv":[41.6012791226812411,100.000000000003,76.4774026026215]},"#ffaa66":{"lch":[76.6616205587261,83.8466863985853905,38.6944265301345354],"luv":[76.6616205587261,65.4416029270402788,52.418159318716242],"rgb":[1,0.66666666666666663,0.4],"xyz":[0.58011193754190149,0.50971299533915948,0.193540083317370298],"hpluv":[38.6944265301345354,144.405277715469396,76.6616205587261],"hsluv":[38.6944265301345354,100.000000000003197,76.6616205587261]},"#ffaa77":{"lch":[76.8868341725165,77.3210793721064533,34.7099370327462324],"luv":[76.8868341725165,63.5614296008349058,44.0283315873505927],"rgb":[1,0.66666666666666663,0.466666666666666674],"xyz":[0.58942595056210334,0.513438600547240331,0.242593885223768208],"hpluv":[34.7099370327462324,134.738986801151128,76.8868341725165],"hsluv":[34.7099370327462324,100.000000000003354,76.8868341725165]},"#ffaa88":{"lch":[77.1547840912050873,70.4186738688562741,29.282319230158226],"luv":[77.1547840912050873,61.4205932059712723,34.4427112706728948],"rgb":[1,0.66666666666666663,0.533333333333333326],"xyz":[0.600566367664047251,0.517894767388017874,0.301266748627340375],"hpluv":[29.282319230158226,124.451835787871019,77.1547840912050873],"hsluv":[29.282319230158226,100.000000000003638,77.1547840912050873]},"#ffaa99":{"lch":[77.466881654564645,63.658531214354845,21.9370110659791244],"luv":[77.466881654564645,59.0493431107362383,23.7820031654092716],"rgb":[1,0.66666666666666663,0.6],"xyz":[0.613623331773119896,0.523117553031647087,0.370033426268458254],"hpluv":[21.9370110659791244,114.385173247539697,77.466881654564645],"hsluv":[21.9370110659791244,100.000000000003624,77.466881654564645]},"#ffaaaa":{"lch":[77.8242336850598,57.783013099698,12.1770506300621957],"luv":[77.8242336850598,56.482921905318662,12.1883606739193855],"rgb":[1,0.66666666666666663,0.66666666666666663],"xyz":[0.628681024738372507,0.529140630217748154,0.44933727588545691],"hpluv":[12.1770506300621957,105.841692205508735,77.8242336850598],"hsluv":[12.1770506300621957,100.000000000003837,77.8242336850598]},"#ffaabb":{"lch":[78.227662021793833,53.7597014753195,359.804273109779956],"luv":[78.227662021793833,53.7593877986938224,-0.183647012281759531],"rgb":[1,0.66666666666666663,0.733333333333333282],"xyz":[0.64581861684570907,0.535995667060682912,0.539595260984098601],"hpluv":[359.804273109779956,100.661858044669231,78.227662021793833],"hsluv":[359.804273109779956,100.000000000004135,78.227662021793833]},"#ffaacc":{"lch":[78.6777204654413254,52.5946111834740293,345.492217824016791],"luv":[78.6777204654413254,50.9175596191628586,-13.1755549397286202],"rgb":[1,0.66666666666666663,0.8],"xyz":[0.665110992289430825,0.543712617238171769,0.641201771654368757],"hpluv":[345.492217824016791,100.966318741741958,78.6777204654413254],"hsluv":[345.492217824016791,100.000000000004306,78.6777204654413254]},"#ffaadd":{"lch":[79.1747106956411244,54.8892328831665353,330.97422205899818],"luv":[79.1747106956411244,47.9952274547753177,-26.6324243745640352],"rgb":[1,0.66666666666666663,0.866666666666666696],"xyz":[0.686629317840064424,0.552319947458425275,0.754531619554374755],"hpluv":[330.97422205899818,108.36723319715793,79.1747106956411244],"hsluv":[330.97422205899818,100.000000000004704,79.1747106956411244]},"#ffaaee":{"lch":[79.718698064048283,60.5009523664383337,318.094564198374599],"luv":[79.718698064048283,45.0277239197174595,-40.4087777080146822],"rgb":[1,0.66666666666666663,0.933333333333333348],"xyz":[0.710441498106239133,0.561844819564895293,0.879942435622898],"hpluv":[318.094564198374599,123.247069988098687,79.718698064048283],"hsluv":[318.094564198374599,100.000000000004945,79.718698064048283]},"#ffaaff":{"lch":[80.3095277487323074,68.733917080261989,307.715012949245647],"luv":[80.3095277487323074,42.0468973903394,-54.3728772187255203],"rgb":[1,0.66666666666666663,1],"xyz":[0.736612546516070554,0.572313238928828,1.01777662391468],"hpluv":[307.715012949245647,144.979279509576116,80.3095277487323074],"hsluv":[307.715012949245647,100.000000000005301,80.3095277487323074]},"#aa2200":{"lch":[37.2831780533064929,108.910935722579069,15.2092016225530191],"luv":[37.2831780533064929,105.096262108869439,28.5721474641225],"rgb":[0.66666666666666663,0.133333333333333331,0],"xyz":[0.171491961907847656,0.0969162034773476816,0.00967723425486398912],"hpluv":[15.2092016225530191,370.67892165569458,37.2831780533064929],"hsluv":[15.2092016225530191,100.000000000002217,37.2831780533064929]},"#aa2211":{"lch":[37.3572350214345619,106.95640568906397,14.158492547926917],"luv":[37.3572350214345619,103.707370245599336,26.1620732103901794],"rgb":[0.66666666666666663,0.133333333333333331,0.0666666666666666657],"xyz":[0.172503627407484789,0.0973208696772025345,0.0150053392196196206],"hpluv":[14.158492547926917,363.305022455593,37.3572350214345619],"hsluv":[14.158492547926917,93.5777596020973732,37.3572350214345619]},"#aa2222":{"lch":[37.4939757158163331,103.55828406892158,12.1770506300617907],"luv":[37.4939757158163331,101.228270350344232,21.8438888748564],"rgb":[0.66666666666666663,0.133333333333333331,0.133333333333333331],"xyz":[0.17437898554596179,0.0980710129325933488,0.0248822254155988166],"hpluv":[12.1770506300617907,350.479546677114797,37.4939757158163331],"hsluv":[12.1770506300617907,82.128221128038831,37.4939757158163331]},"#aa2233":{"lch":[37.7176061419824791,98.5638584928897359,8.82735266140639],"luv":[37.7176061419824791,97.3963926843060506,15.125372494284127],"rgb":[0.66666666666666663,0.133333333333333331,0.2],"xyz":[0.177466736278419523,0.0993061132255764617,0.0411443792732099634],"hpluv":[8.82735266140639,331.598763076121088,37.7176061419824791],"hsluv":[8.82735266140639,82.8309253801110401,37.7176061419824791]},"#aa2244":{"lch":[38.0372287502177358,92.5577577991589209,3.83362915136278648],"luv":[38.0372287502177358,92.350650343690134,6.18836892123021798],"rgb":[0.66666666666666663,0.133333333333333331,0.266666666666666663],"xyz":[0.181924731522638411,0.101089311323264042,0.0646231542260966],"hpluv":[3.83362915136278648,308.775819843535,38.0372287502177358],"hsluv":[3.83362915136278648,83.7532195801290698,38.0372287502177358]},"#aa2255":{"lch":[38.4588905236098242,86.4856662057644172,356.973865768881865],"luv":[38.4588905236098242,86.3650670431288603,-4.56570407393633459],"rgb":[0.66666666666666663,0.133333333333333331,0.333333333333333315],"xyz":[0.187887145562000424,0.103474276939008875,0.0960252015000705],"hpluv":[356.973865768881865,285.355803984318584,38.4588905236098242],"hsluv":[356.973865768881865,84.8422496447961,38.4588905236098242]},"#aa2266":{"lch":[38.9860395203518237,81.4924101374980268,348.227712846231327],"luv":[38.9860395203518237,79.7783138580272464,-16.6262908668256522],"rgb":[0.66666666666666663,0.133333333333333331,0.4],"xyz":[0.195472141335508581,0.106508275248412193,0.135972845907214479],"hpluv":[348.227712846231327,265.245100213362434,38.9860395203518237],"hsluv":[348.227712846231327,86.0332228090823747,38.9860395203518237]},"#aa2277":{"lch":[39.619833929041036,78.6591168776988354,337.990195281021442],"luv":[39.619833929041036,72.9264197190586572,-29.4787037526953952],"rgb":[0.66666666666666663,0.133333333333333331,0.466666666666666674],"xyz":[0.204786154355710515,0.110233880456493,0.185026647813612388],"hpluv":[337.990195281021442,251.92759566726761,39.619833929041036],"hsluv":[337.990195281021442,87.2621982611374278,39.619833929041036]},"#aa2288":{"lch":[40.3594266716885386,78.6767938432408,327.148786779116733],"luv":[40.3594266716885386,66.0949636498191637,-42.6789605025813046],"rgb":[0.66666666666666663,0.133333333333333331,0.533333333333333326],"xyz":[0.215926571457654315,0.1146900472972706,0.243699511217184556],"hpluv":[327.148786779116733,247.366561360315984,40.3594266716885386],"hsluv":[327.148786779116733,88.4751585260979283,40.3594266716885386]},"#aa2299":{"lch":[41.2022629883412748,81.6302410017403162,316.790315261789033],"luv":[41.2022629883412748,59.4964385039503,-55.8898027492302489],"rgb":[0.66666666666666663,0.133333333333333331,0.6],"xyz":[0.228983535566727042,0.119912832940899758,0.312466188858302463],"hpluv":[316.790315261789033,251.402351399829286,41.2022629883412748],"hsluv":[316.790315261789033,89.6322731802278554,41.2022629883412748]},"#aa22aa":{"lch":[42.1443943233873242,87.0780915666379229,307.715012949243715],"luv":[42.1443943233873242,53.2686588598376076,-68.8842798769212834],"rgb":[0.66666666666666663,0.133333333333333331,0.66666666666666663],"xyz":[0.24404122853197957,0.125935910127000866,0.391770038475301063],"hpluv":[307.715012949243715,262.185344504614818,42.1443943233873242],"hsluv":[307.715012949243715,90.7081440057972515,42.1443943233873242]},"#aa22bb":{"lch":[43.1807973125030387,94.3504956605328573,300.218008125398399],"luv":[43.1807973125030387,47.4858085519839577,-81.5298351375283801],"rgb":[0.66666666666666663,0.133333333333333331,0.733333333333333282],"xyz":[0.261178820639316245,0.132790946969935625,0.48202802357394281],"hpluv":[300.218008125398399,277.263598469343151,43.1807973125030387],"hsluv":[300.218008125398399,91.6896384965505291,43.1807973125030387]},"#aa22cc":{"lch":[44.3056820912093627,102.813108633557576,294.217612554784239],"luv":[44.3056820912093627,42.1742864712375294,-93.764944769021767],"rgb":[0.66666666666666663,0.133333333333333331,0.8],"xyz":[0.280471196083037944,0.140507897147424426,0.583634534244213],"hpluv":[294.217612554784239,294.461411899371626,44.3056820912093627],"hsluv":[294.217612554784239,92.5728114271618097,44.3056820912093627]},"#aa22dd":{"lch":[45.5127751844210451,111.980933923074502,289.471886144522102],"luv":[45.5127751844210451,37.3282042805071441,-105.576203414769125],"rgb":[0.66666666666666663,0.133333333333333331,0.866666666666666696],"xyz":[0.301989521633671543,0.14911522736767796,0.696964382144219],"hpluv":[289.471886144522102,312.212361278410071,45.5127751844210451],"hsluv":[289.471886144522102,93.3598993754704622,45.5127751844210451]},"#aa22ee":{"lch":[46.7955661660676938,121.524022862348417,285.718434714393425],"luv":[46.7955661660676938,32.9220942551774698,-116.979587289842115],"rgb":[0.66666666666666663,0.133333333333333331,0.933333333333333348],"xyz":[0.325801701899846252,0.158640099474147978,0.822375198212742187],"hpluv":[285.718434714393425,329.531365671141714,46.7955661660676938],"hsluv":[285.718434714393425,94.0568560040361348,46.7955661660676938]},"#aa22ff":{"lch":[48.1475121680676921,131.233078667623346,282.730941389390409],"luv":[48.1475121680676921,28.9202258451599548,-128.006802450680539],"rgb":[0.66666666666666663,0.133333333333333331,1],"xyz":[0.351972750309677673,0.169108518838080701,0.960209386504524],"hpluv":[282.730941389390409,345.866733454918517,48.1475121680676921],"hsluv":[282.730941389390409,99.9999999999992,48.1475121680676921]},"#883300":{"lch":[33.1414787667816597,73.2165592554870841,22.9600016117944072],"luv":[33.1414787667816597,67.4161530625393084,28.5609323282788417],"rgb":[0.533333333333333326,0.2,0],"xyz":[0.113368907986088716,0.076027497524815621,0.00870518857569610102],"hpluv":[22.9600016117944072,280.334636286210525,33.1414787667816597],"hsluv":[22.9600016117944072,100.000000000002245,33.1414787667816597]},"#883311":{"lch":[33.2285118286029402,71.0572306739368287,21.2511434941679589],"luv":[33.2285118286029402,66.2253841559006844,25.7551650053430592],"rgb":[0.533333333333333326,0.2,0.0666666666666666657],"xyz":[0.114380573485725834,0.0764321637246704738,0.0140332935404517325],"hpluv":[21.2511434941679589,271.354302974885854,33.2285118286029402],"hsluv":[21.2511434941679589,91.7325092821930355,33.2285118286029402]},"#883322":{"lch":[33.389038834633638,67.403958774683133,17.9527330699243208],"luv":[33.389038834633638,64.1221355411587126,20.7760774002325519],"rgb":[0.533333333333333326,0.2,0.133333333333333331],"xyz":[0.116255931624202863,0.0771823069800612882,0.0239101797364309268],"hpluv":[17.9527330699243208,256.165602847605,33.389038834633638],"hsluv":[17.9527330699243208,77.1564226992543354,33.389038834633638]},"#883333":{"lch":[33.6510932573449324,62.3280121609532785,12.1770506300618564],"luv":[33.6510932573449324,60.9256605799824484,13.1470522486492083],"rgb":[0.533333333333333326,0.2,0.2],"xyz":[0.119343682356660596,0.0784174072730444,0.04017233359404207],"hpluv":[12.1770506300618564,235.030067027939708,33.6510932573449324],"hsluv":[12.1770506300618564,55.0748296144977,33.6510932573449324]},"#883344":{"lch":[34.0246284162643136,56.9037682047597428,3.23132728809417369],"luv":[34.0246284162643136,56.8132965471078748,3.20751794249172528],"rgb":[0.533333333333333326,0.2,0.266666666666666663],"xyz":[0.12380167760087947,0.080200605370731981,0.0636511085469287086],"hpluv":[3.23132728809417369,212.220318588139889,34.0246284162643136],"hsluv":[3.23132728809417369,58.083398150148156,34.0246284162643136]},"#883355":{"lch":[34.5156618951709859,52.7529688087382524,350.767304332875],"luv":[34.5156618951709859,52.0695471674777721,-8.46392201697997137],"rgb":[0.533333333333333326,0.2,0.333333333333333315],"xyz":[0.129764091640241469,0.0825855709864768139,0.0950531558209026239],"hpluv":[350.767304332875,193.941176615342812,34.5156618951709859],"hsluv":[350.767304332875,61.5291535706030714,34.5156618951709859]},"#883366":{"lch":[35.1268460128593318,51.5738240122621576,335.705329263136434],"luv":[35.1268460128593318,47.0065260432940519,-21.2189969741481157],"rgb":[0.533333333333333326,0.2,0.4],"xyz":[0.137349087413749654,0.0856195692958801324,0.135000800228046586],"hpluv":[335.705329263136434,186.307141954737205,35.1268460128593318],"hsluv":[335.705329263136434,65.1713869724459869,35.1268460128593318]},"#883377":{"lch":[35.8579115963162849,54.2604071268956929,320.552373035814298],"luv":[35.8579115963162849,41.9001950458929713,-34.4755773946224],"rgb":[0.533333333333333326,0.2,0.466666666666666674],"xyz":[0.14666310043395156,0.0893451745039609418,0.184054602134444495],"hpluv":[320.552373035814298,192.015984070736607,35.8579115963162849],"hsluv":[320.552373035814298,68.798738775930957,35.8579115963162849]},"#883388":{"lch":[36.7061150242973682,60.4128011412536097,307.715012949244056],"luv":[36.7061150242973682,36.9565850245806402,-47.7903480323547711],"rgb":[0.533333333333333326,0.2,0.533333333333333326],"xyz":[0.157803517535895388,0.0938013413447385397,0.242727465538016662],"hpluv":[307.715012949244056,208.847787272345244,36.7061150242973682],"hsluv":[307.715012949244056,72.2549736675254479,36.7061150242973682]},"#883399":{"lch":[37.6667130487112445,68.9141309342046213,297.955533412138379],"luv":[37.6667130487112445,32.3059919210196611,-60.8726566564636684],"rgb":[0.533333333333333326,0.2,0.6],"xyz":[0.170860481644968087,0.0990241269883677,0.311494143179134542],"hpluv":[297.955533412138379,232.161331922109071,37.6667130487112445],"hsluv":[297.955533412138379,75.4431641885032604,37.6667130487112445]},"#8833aa":{"lch":[38.7334497692602824,78.7159372772915162,290.848124870138179],"luv":[38.7334497692602824,28.0143748789862741,-73.5621749378178436],"rgb":[0.533333333333333326,0.2,0.66666666666666663],"xyz":[0.185918174610220643,0.105047204174468806,0.390797992796133142],"hpluv":[290.848124870138179,257.878905714084965,38.7334497692602824],"hsluv":[290.848124870138179,78.3166072053086282,38.7334497692602824]},"#8833bb":{"lch":[39.8990272727434743,89.1142300257769193,285.691107407551272],"luv":[39.8990272727434743,24.1010351313191435,-85.7932753698447073],"rgb":[0.533333333333333326,0.2,0.733333333333333282],"xyz":[0.20305576671755729,0.111902241017403564,0.481055977894774889],"hpluv":[285.691107407551272,283.415812953045702,39.8990272727434743],"hsluv":[285.691107407551272,80.8649153118493444,39.8990272727434743]},"#8833cc":{"lch":[41.1555326498064318,99.7027359993818578,281.897918690971494],"luv":[41.1555326498064318,20.5555775093346966,-97.560769774639283],"rgb":[0.533333333333333326,0.2,0.8],"xyz":[0.222348142161279017,0.119619191194892366,0.5826624885650451],"hpluv":[281.897918690971494,307.410130900702256,41.1555326498064318],"hsluv":[281.897918690971494,83.1006969987668356,41.1555326498064318]},"#8833dd":{"lch":[42.4948021164729042,110.266949448261684,279.053462184119098],"luv":[42.4948021164729042,17.3511661022166734,-108.893237510502843],"rgb":[0.533333333333333326,0.2,0.866666666666666696],"xyz":[0.24386646771191256,0.1282265214151459,0.695992336465051098],"hpluv":[279.053462184119098,329.267506795456711,42.4948021164729042],"hsluv":[279.053462184119098,85.0491409670088103,42.4948021164729042]},"#8833ee":{"lch":[43.9087129541284185,120.703343561802441,276.877390721452173],"luv":[43.9087129541284185,14.4536316684396038,-119.834843341123431],"rgb":[0.533333333333333326,0.2,0.933333333333333348],"xyz":[0.267678647978087325,0.137751393521615917,0.821403152533574321],"hpluv":[276.877390721452173,348.825254458779511,43.9087129541284185],"hsluv":[276.877390721452173,90.7214777394212177,43.9087129541284185]},"#8833ff":{"lch":[45.3894029264418037,130.969293653816607,275.181129330284705],"luv":[45.3894029264418037,11.8271264623124566,-130.43417864894198],"rgb":[0.533333333333333326,0.2,1],"xyz":[0.293849696387918691,0.148219812885548641,0.959237340825356166],"hpluv":[275.181129330284705,366.146040402293636,45.3894029264418037],"hsluv":[275.181129330284705,99.9999999999993179,45.3894029264418037]},"#ffbb00":{"lch":[80.0686585320779614,99.7432534700870832,55.1804439586775146],"luv":[80.0686585320779614,56.9527800089419642,81.8846595037867928],"rgb":[1,0.733333333333333282,0],"xyz":[0.590086256022839262,0.568029919385293569,0.0785626376345524291],"hpluv":[55.1804439586775146,207.400278899961506,80.0686585320779614],"hsluv":[55.1804439586775146,100.000000000004661,80.0686585320779614]},"#ffbb11":{"lch":[80.0914663159454,98.8408701901197304,54.9443852376931758],"luv":[80.0914663159454,56.7713575947746136,80.9106332739172558],"rgb":[1,0.733333333333333282,0.0666666666666666657],"xyz":[0.591097921522476422,0.568434585585148477,0.0838907425993080658],"hpluv":[54.9443852376931758,205.801067903733326,80.0914663159454],"hsluv":[54.9443852376931758,100.000000000004746,80.0914663159454]},"#ffbb22":{"lch":[80.1337172522408849,97.1832370837577173,54.4980435584705063],"luv":[80.1337172522408849,56.4372945908461148,79.11645435270664],"rgb":[1,0.733333333333333282,0.133333333333333331],"xyz":[0.592973279660953367,0.569184728840539278,0.0937676287952872584],"hpluv":[54.4980435584705063,202.856099527528187,80.1337172522408849],"hsluv":[54.4980435584705063,100.00000000000469,80.1337172522408849]},"#ffbb33":{"lch":[80.2032020182086569,94.4967793168558643,53.7374633716008248],"luv":[80.2032020182086569,55.8935303906135772,76.1941898161015558],"rgb":[1,0.733333333333333282,0.2],"xyz":[0.596061030393411184,0.570419829133522405,0.110029782652898395],"hpluv":[53.7374633716008248,198.062979410590685,80.2032020182086569],"hsluv":[53.7374633716008248,100.000000000004576,80.2032020182086569]},"#ffbb44":{"lch":[80.3033451682561,90.7120739700224874,52.5796541788809932],"luv":[80.3033451682561,55.1219083097901148,72.0434284874749835],"rgb":[1,0.733333333333333282,0.266666666666666663],"xyz":[0.60051902563763,0.57220302723121,0.133508557605785033],"hpluv":[52.5796541788809932,191.266883403483888,80.3033451682561],"hsluv":[52.5796541788809932,100.000000000004732,80.3033451682561]},"#ffbb55":{"lch":[80.4369584648287343,85.824023801574981,50.9113466162360169],"luv":[80.4369584648287343,54.1139445833304435,66.614143116349922],"rgb":[1,0.733333333333333282,0.333333333333333315],"xyz":[0.606481439676992,0.57458799284695472,0.164910604879758949],"hpluv":[50.9113466162360169,182.412290123698938,80.4369584648287343],"hsluv":[50.9113466162360169,100.000000000004846,80.4369584648287343]},"#ffbb66":{"lch":[80.6063993458739532,79.8959983704508545,48.5678239065140147],"luv":[80.6063993458739532,52.8698192869345931,59.9011916757752374],"rgb":[1,0.733333333333333282,0.4],"xyz":[0.614066435450500214,0.577621991156358,0.20485824928690291],"hpluv":[48.5678239065140147,171.553596694327723,80.6063993458739532],"hsluv":[48.5678239065140147,100.000000000004945,80.6063993458739532]},"#ffbb77":{"lch":[80.8136549908608828,73.0715029906331353,45.3006510664637219],"luv":[80.8136549908608828,51.397517928562209,51.9397699272266067],"rgb":[1,0.733333333333333282,0.466666666666666674],"xyz":[0.623380448470702064,0.58134759636443889,0.253912051193300847],"hpluv":[45.3006510664637219,158.885762577352,80.8136549908608828],"hsluv":[45.3006510664637219,100.000000000005144,80.8136549908608828]},"#ffbb88":{"lch":[81.0603921498240823,65.5982066090816,40.7273497798256443],"luv":[81.0603921498240823,49.7118280718431649,42.8002203275083914],"rgb":[1,0.733333333333333282,0.533333333333333326],"xyz":[0.634520865572646,0.585803763205216432,0.312584914596873],"hpluv":[40.7273497798256443,144.809180382553194,81.0603921498240823],"hsluv":[40.7273497798256443,100.0000000000054,81.0603921498240823]},"#ffbb99":{"lch":[81.347989327564818,57.8755898316684423,34.2609783084776538],"luv":[81.347989327564818,47.8331272298795724,32.5818329406692087],"rgb":[1,0.733333333333333282,0.6],"xyz":[0.64757782968171862,0.591026548848845645,0.381351592237990866],"hpluv":[34.2609783084776538,130.060421106466862,81.347989327564818],"hsluv":[34.2609783084776538,100.000000000005514,81.347989327564818]},"#ffbbaa":{"lch":[81.6775593509345725,50.5427251754064883,25.056975338279841],"luv":[81.6775593509345725,45.7860019440054415,21.405819165362459],"rgb":[1,0.733333333333333282,0.66666666666666663],"xyz":[0.662635522646971231,0.597049626034946712,0.460655441854989522],"hpluv":[25.056975338279841,115.96007016550756,81.6775593509345725],"hsluv":[25.056975338279841,100.000000000005954,81.6775593509345725]},"#ffbbbb":{"lch":[82.0499666293022,44.6012959670408264,12.1770506300623094],"luv":[82.0499666293022,43.5977873399533138,9.40789779917804125],"rgb":[1,0.733333333333333282,0.733333333333333282],"xyz":[0.679773114754307795,0.603904662877881471,0.550913426953631213],"hpluv":[12.1770506300623094,104.793068167285782,82.0499666293022],"hsluv":[12.1770506300623094,100.000000000006168,82.0499666293022]},"#ffbbcc":{"lch":[82.4658415859876,41.4263127316455169,355.474040938847054],"luv":[82.4658415859876,41.2971323922898534,-3.26898190783910625],"rgb":[1,0.733333333333333282,0.8],"xyz":[0.699065490198029549,0.611621613055370328,0.652519937623901369],"hpluv":[355.474040938847054,100.004408983591958,82.4658415859876],"hsluv":[355.474040938847054,100.000000000006509,82.4658415859876]},"#ffbbdd":{"lch":[82.9255937413379,42.2590852539423381,337.045135839305544],"luv":[82.9255937413379,38.9126886018087319,-16.4812909773061058],"rgb":[1,0.733333333333333282,0.866666666666666696],"xyz":[0.720583815748663148,0.620228943275623834,0.765849785523907367],"hpluv":[337.045135839305544,105.181583048825317,82.9255937413379],"hsluv":[337.045135839305544,100.000000000006992,82.9255937413379]},"#ffbbee":{"lch":[83.4294243398036315,47.2826492521794748,320.476273654572083],"luv":[83.4294243398036315,36.4719970678196077,-30.0905691237235686],"rgb":[1,0.733333333333333282,0.933333333333333348],"xyz":[0.744395996014837857,0.629753815382093851,0.89126060159243059],"hpluv":[320.476273654572083,121.793886282549721,83.4294243398036315],"hsluv":[320.476273654572083,100.000000000007375,83.4294243398036315]},"#ffbbff":{"lch":[83.9773390427358493,55.5806936350452148,307.715012949246614],"luv":[83.9773390427358493,34.0006189291922496,-43.9678452665650781],"rgb":[1,0.733333333333333282,1],"xyz":[0.770567044424669279,0.640222234746026575,1.02909478988421244],"hpluv":[307.715012949246614,148.765749509941259,83.9773390427358493],"hsluv":[307.715012949246614,100.00000000000793,83.9773390427358493]},"#aa3300":{"lch":[39.4372171279304595,100.717281062042773,18.6056676884160446],"luv":[39.4372171279304595,95.4534790945485838,32.1341568004684959],"rgb":[0.66666666666666663,0.2,0],"xyz":[0.177609683996858475,0.109151647655369471,0.0117164749512008691],"hpluv":[18.6056676884160446,324.068678498457416,39.4372171279304595],"hsluv":[18.6056676884160446,100.00000000000226,39.4372171279304595]},"#aa3311":{"lch":[39.5056415087576553,98.9056898290362199,17.5539290045831464],"luv":[39.5056415087576553,94.2999972712563,29.8302865423477179],"rgb":[0.66666666666666663,0.2,0.0666666666666666657],"xyz":[0.178621349496495607,0.109556313855224324,0.0170445799159565023],"hpluv":[17.5539290045831464,317.688492914835,39.5056415087576553],"hsluv":[17.5539290045831464,94.2489803369173558,39.5056415087576553]},"#aa3322":{"lch":[39.6320377265530155,95.7434230458582789,15.5633947922033684],"luv":[39.6320377265530155,92.2329117474569102,25.688383505468213],"rgb":[0.66666666666666663,0.2,0.133333333333333331],"xyz":[0.180496707634972609,0.110306457110615139,0.0269214661119356949],"hpluv":[15.5633947922033684,306.55039093266322,39.6320377265530155],"hsluv":[15.5633947922033684,83.9544037116345123,39.6320377265530155]},"#aa3333":{"lch":[39.8389046640011415,91.0660982539230162,12.1770506300618351],"luv":[39.8389046640011415,89.0171529654153488,19.2088711049089049],"rgb":[0.66666666666666663,0.2,0.2],"xyz":[0.18358445836743037,0.111541557403598252,0.0431836199695468381],"hpluv":[12.1770506300618351,290.060550373943784,39.8389046640011415],"hsluv":[12.1770506300618351,67.9701775681036366,39.8389046640011415]},"#aa3344":{"lch":[40.1348956250933142,85.3951569144799691,7.07959423789482756],"luv":[40.1348956250933142,84.7440953003879116,10.5247867516366789],"rgb":[0.66666666666666663,0.2,0.266666666666666663],"xyz":[0.18804245361164923,0.113324755501285832,0.0666623949224334766],"hpluv":[7.07959423789482756,269.991710008472637,40.1348956250933142],"hsluv":[7.07959423789482756,69.5294395234689659,40.1348956250933142]},"#aa3355":{"lch":[40.5259588707466,79.61783322386637,359.98582832830067],"luv":[40.5259588707466,79.6178307884273693,-0.0196928603108827253],"rgb":[0.66666666666666663,0.2,0.333333333333333315],"xyz":[0.194004867651011215,0.115709721117030664,0.098064442196407392],"hpluv":[359.98582832830067,249.296614878837403,40.5259588707466],"hsluv":[359.98582832830067,71.3920974276079079,40.5259588707466]},"#aa3366":{"lch":[41.0157539995587683,74.8661602955128558,350.806369630673316],"luv":[41.0157539995587683,73.9044320805857,-11.9614746682639641],"rgb":[0.66666666666666663,0.2,0.4],"xyz":[0.2015898634245194,0.118743719426433983,0.138012086603551354],"hpluv":[350.806369630673316,231.619002340768361,41.0157539995587683],"hsluv":[350.806369630673316,73.4562780141226597,41.0157539995587683]},"#aa3377":{"lch":[41.6059173351841167,72.2728884464882668,339.921631600955322],"luv":[41.6059173351841167,67.8804264173117531,-24.8116527825629412],"rgb":[0.66666666666666663,0.2,0.466666666666666674],"xyz":[0.210903876444721305,0.122469324634514792,0.187065888509949263],"hpluv":[339.921631600955322,220.424384448141751,41.6059173351841167],"hsluv":[339.921631600955322,75.6166865764569138,41.6059173351841167]},"#aa3388":{"lch":[42.296293156356171,72.6091331125239918,328.324027314743319],"luv":[42.296293156356171,61.7926517819245191,-38.1281313089174],"rgb":[0.66666666666666663,0.2,0.533333333333333326],"xyz":[0.222044293546665161,0.12692549147529239,0.24573875191352143],"hpluv":[328.324027314743319,217.83530631356345,42.296293156356171],"hsluv":[328.324027314743319,77.7798333688196237,42.296293156356171]},"#aa3399":{"lch":[43.0851702185186838,75.9982874692542794,317.281450106519685],"luv":[43.0851702185186838,55.835561651799118,-51.5570533805758799],"rgb":[0.66666666666666663,0.2,0.6],"xyz":[0.235101257655737861,0.132148277118921548,0.314505429554639337],"hpluv":[317.281450106519685,223.828466633931441,43.0851702185186838],"hsluv":[317.281450106519685,79.8726039522015441,43.0851702185186838]},"#aa33aa":{"lch":[43.9695321467927229,81.9717995507402861,307.715012949243828],"luv":[43.9695321467927229,50.1449646844158607,-64.8448798162617521],"rgb":[0.66666666666666663,0.2,0.66666666666666663],"xyz":[0.250158950620990417,0.138171354305022642,0.393809279171637938],"hpluv":[307.715012949243828,236.565795567314069,43.9695321467927229],"hsluv":[307.715012949243828,81.8445603498948628,43.9695321467927229]},"#aa33bb":{"lch":[44.9453163823579231,89.8045713491188593,299.92695612823394],"luv":[44.9453163823579231,44.8030997809084,-77.8302208992172],"rgb":[0.66666666666666663,0.2,0.733333333333333282],"xyz":[0.267296542728327036,0.145026391147957401,0.484067264270279685],"hpluv":[299.92695612823394,253.543994890590483,44.9453163823579231],"hsluv":[299.92695612823394,83.6659201788580305,44.9453163823579231]},"#aa33cc":{"lch":[46.0076707905516145,98.8168321279874249,293.782406660809556],"luv":[46.0076707905516145,39.8493034294229815,-90.425656359246986],"rgb":[0.66666666666666663,0.2,0.8],"xyz":[0.28658891817204879,0.152743341325446202,0.585673774940549841],"hpluv":[293.782406660809556,272.546121846523647,46.0076707905516145],"hsluv":[293.782406660809556,85.323407183561244,46.0076707905516145]},"#aa33dd":{"lch":[47.1511962508372804,108.497592079042477,288.982430572084695],"luv":[47.1511962508372804,35.2919015872993782,-102.597315604759743],"rgb":[0.66666666666666663,0.2,0.866666666666666696],"xyz":[0.308107243722682334,0.161350671545699736,0.699003622840555838],"hpluv":[288.982430572084695,291.989148013150611,47.1511962508372804],"hsluv":[288.982430572084695,86.8156421407842771,47.1511962508372804]},"#aa33ee":{"lch":[48.3701654903461247,118.505438755587946,285.224041333998457],"luv":[48.3701654903461247,31.1188262963008349,-114.346655677352729],"rgb":[0.66666666666666663,0.2,0.933333333333333348],"xyz":[0.331919423988857099,0.170875543652169781,0.824414438909079061],"hpluv":[285.224041333998457,310.885191630161273,48.3701654903461247],"hsluv":[285.224041333998457,89.3017266962351215,48.3701654903461247]},"#aa33ff":{"lch":[49.6587116356326135,128.627945638670809,282.256374557143658],"luv":[49.6587116356326135,27.305962831451918,-125.696192436653462],"rgb":[0.66666666666666663,0.2,1],"xyz":[0.358090472398688464,0.181343963016102477,0.962248627200860907],"hpluv":[282.256374557143658,328.684490794403757,49.6587116356326135],"hsluv":[282.256374557143658,99.9999999999991616,49.6587116356326135]},"#884400":{"lch":[36.685747441671559,64.5393704655657814,31.8123524502136021],"luv":[36.685747441671559,54.844205976921188,34.0212200082916922],"rgb":[0.533333333333333326,0.266666666666666663,0],"xyz":[0.122201478469054756,0.093692638490747937,0.0116493787366846978],"hpluv":[31.8123524502136021,223.237258003095718,36.685747441671559],"hsluv":[31.8123524502136021,100.000000000002245,36.685747441671559]},"#884411":{"lch":[36.7614898568215622,62.4864851085245405,30.2058215583615599],"luv":[36.7614898568215622,54.0023006087270119,31.4374354899146],"rgb":[0.533333333333333326,0.266666666666666663,0.0666666666666666657],"xyz":[0.123213143968691874,0.0940973046906027899,0.016977483701440331],"hpluv":[30.2058215583615599,215.691146343233044,36.7614898568215622],"hsluv":[30.2058215583615599,93.206231917801162,36.7614898568215622]},"#884422":{"lch":[36.9013237077522405,58.9550348899468375,27.0578850153250627],"luv":[36.9013237077522405,52.5022536375556825,26.8180816214102],"rgb":[0.533333333333333326,0.266666666666666663,0.133333333333333331],"xyz":[0.125088502107168903,0.0948474479459936,0.0268543698974195236],"hpluv":[27.0578850153250627,202.730122118290922,36.9013237077522405],"hsluv":[27.0578850153250627,81.1214838671569822,36.9013237077522405]},"#884433":{"lch":[37.1299605496210461,53.9021323280231357,21.3870333458705382],"luv":[37.1299605496210461,50.1903435049312,19.6562785990819577],"rgb":[0.533333333333333326,0.266666666666666663,0.2],"xyz":[0.128176252839626637,0.0960825482389767171,0.0431165237550306668],"hpluv":[21.3870333458705382,184.213216030002656,37.1299605496210461],"hsluv":[21.3870333458705382,62.5495295304316272,37.1299605496210461]},"#884444":{"lch":[37.4566279620983806,48.2433933618324389,12.1770506300619505],"luv":[37.4566279620983806,47.157939219998795,10.1761373608750691],"rgb":[0.533333333333333326,0.266666666666666663,0.266666666666666663],"xyz":[0.132634248083845524,0.0978657463366643,0.0665952987079173],"hpluv":[12.1770506300619505,163.436290620587812,37.4566279620983806],"hsluv":[12.1770506300619505,38.2981887065726,37.4566279620983806]},"#884455":{"lch":[37.8873893879065804,43.5926938841600631,358.470393497241901],"luv":[37.8873893879065804,43.5771602849424937,-1.16364151632571566],"rgb":[0.533333333333333326,0.266666666666666663,0.333333333333333315],"xyz":[0.138596662123207509,0.10025071195240913,0.0979973459818912207],"hpluv":[358.470393497241901,146.001848007416612,37.8873893879065804],"hsluv":[358.470393497241901,42.6300163695831245,37.8873893879065804]},"#884466":{"lch":[38.425613648805637,41.9872152878557756,340.815857604715632],"luv":[38.425613648805637,39.6555541095459816,-13.7972198610307206],"rgb":[0.533333333333333326,0.266666666666666663,0.4],"xyz":[0.146181657896715694,0.103284710261812449,0.137944990389035183],"hpluv":[340.815857604715632,138.655016432961,38.425613648805637],"hsluv":[340.815857604715632,47.3337116796912767,38.425613648805637]},"#884477":{"lch":[39.0722986805963117,44.7783217464764363,322.645837420375756],"luv":[39.0722986805963117,35.5943002873345478,-27.1688035343115608],"rgb":[0.533333333333333326,0.266666666666666663,0.466666666666666674],"xyz":[0.1554956709169176,0.107010315469893258,0.186998792295433092],"hpluv":[322.645837420375756,145.424700121889231,39.0722986805963117],"hsluv":[322.645837420375756,52.1510622716411,39.0722986805963117]},"#884488":{"lch":[39.8263740522966856,51.5911203050634839,307.71501294924451],"luv":[39.8263740522966856,31.5600599218946343,-40.8118403414738609],"rgb":[0.533333333333333326,0.266666666666666663,0.533333333333333326],"xyz":[0.166636088018861428,0.111466482310670856,0.245671655699005259],"hpluv":[307.71501294924451,164.377933698467302,39.8263740522966856],"hsluv":[307.71501294924451,56.8697587177058,39.8263740522966856]},"#884499":{"lch":[40.6850187117946192,61.0189075542410393,296.969581150574243],"luv":[40.6850187117946192,27.6731358367265301,-54.3829443123034295],"rgb":[0.533333333333333326,0.266666666666666663,0.6],"xyz":[0.179693052127934128,0.116689267954300013,0.314438333340123166],"hpluv":[296.969581150574243,190.313341437332326,40.6850187117946192],"hsluv":[296.969581150574243,61.3383523001243134,40.6850187117946192]},"#8844aa":{"lch":[41.643995358805,71.7935926347933417,289.536616505740085],"luv":[41.643995358805,24.0084388126510113,-67.6602897510042141],"rgb":[0.533333333333333326,0.266666666666666663,0.66666666666666663],"xyz":[0.194750745093186683,0.122712345140401108,0.393742182957121767],"hpluv":[289.536616505740085,218.762371943685849,41.643995358805],"hsluv":[289.536616505740085,65.4647221474276506,41.643995358805]},"#8844bb":{"lch":[42.6979882651059626,83.1164887776927515,284.352541737053286],"luv":[42.6979882651059626,20.6035404121657031,-80.5223250360828757],"rgb":[0.533333333333333326,0.266666666666666663,0.733333333333333282],"xyz":[0.21188833720052333,0.129567381983335866,0.484000168055763513],"hpluv":[284.352541737053286,247.012596783276,42.6979882651059626],"hsluv":[284.352541737053286,69.2054262339616741,42.6979882651059626]},"#8844cc":{"lch":[43.840927241858644,94.5481620176080924,280.647262826657595],"luv":[43.840927241858644,17.4689172853010533,-92.9203522904814463],"rgb":[0.533333333333333326,0.266666666666666663,0.8],"xyz":[0.231180712644245057,0.137284332160824668,0.585606678726033669],"hpluv":[280.647262826657595,273.660859280114778,43.840927241858644],"hsluv":[280.647262826657595,72.5522514276637764,43.840927241858644]},"#8844dd":{"lch":[45.0662821798681108,105.864396975636453,277.925647559652191],"luv":[45.0662821798681108,14.5974213234566292,-104.853163222292139],"rgb":[0.533333333333333326,0.266666666666666663,0.866666666666666696],"xyz":[0.2526990381948786,0.145891662381078202,0.698936526626039667],"hpluv":[277.925647559652191,298.083215805211921,45.0662821798681108],"hsluv":[277.925647559652191,79.9664783418063649,45.0662821798681108]},"#8844ee":{"lch":[46.3673172023828,116.961454232737537,275.874868362231723],"luv":[46.3673172023828,11.97173240994087,-116.347150370524886],"rgb":[0.533333333333333326,0.266666666666666663,0.933333333333333348],"xyz":[0.276511218461053365,0.155416534487548247,0.82434734269456289],"hpluv":[275.874868362231723,320.088534026972411,46.3673172023828],"hsluv":[275.874868362231723,89.9071832422553,46.3673172023828]},"#8844ff":{"lch":[47.7372988913525091,127.801124524511906,274.29427304351259],"luv":[47.7372988913525091,9.5696272486796623,-127.442338585146516],"rgb":[0.533333333333333326,0.266666666666666663,1],"xyz":[0.302682266870884731,0.165884953851480943,0.962181530986344735],"hpluv":[274.29427304351259,339.716123682654541,47.7372988913525091],"hsluv":[274.29427304351259,99.9999999999992468,47.7372988913525091]},"#ffcc00":{"lch":[84.1983464973243,98.3335943421723613,63.5926937648685069],"luv":[84.1983464973243,43.7338065737115329,88.0729807536005751],"rgb":[1,0.8,0],"xyz":[0.628309999332456237,0.644477406004528408,0.0913038854044243842],"hpluv":[63.5926937648685069,267.385577483165775,84.1983464973243],"hsluv":[63.5926937648685069,100.000000000007688,84.1983464973243]},"#ffcc11":{"lch":[84.2193135631731877,97.4576917861448777,63.4272907637844199],"luv":[84.2193135631731877,43.5960552023901897,87.1629833075564164],"rgb":[1,0.8,0.0666666666666666657],"xyz":[0.629321664832093397,0.644882072204383316,0.0966319903691800208],"hpluv":[63.4272907637844199,265.403720884510847,84.2193135631731877],"hsluv":[63.4272907637844199,100.000000000007645,84.2193135631731877]},"#ffcc22":{"lch":[84.2581577251319516,95.845309095571892,63.1144413478925301],"luv":[84.2581577251319516,43.342199386504106,85.4855369519677168],"rgb":[1,0.8,0.133333333333333331],"xyz":[0.631197022970570343,0.645632215459774117,0.106508876565159213],"hpluv":[63.1144413478925301,261.744085553517664,84.2581577251319516],"hsluv":[63.1144413478925301,100.00000000000766,84.2581577251319516]},"#ffcc33":{"lch":[84.3220485899852719,93.2224175872051148,62.5809741236821822],"luv":[84.3220485899852719,42.9284172035605494,82.7500461462124832],"rgb":[1,0.8,0.2],"xyz":[0.63428477370302816,0.646867315752757244,0.12277103042277035],"hpluv":[62.5809741236821822,255.758846695652977,84.3220485899852719],"hsluv":[62.5809741236821822,100.000000000007859,84.3220485899852719]},"#ffcc44":{"lch":[84.41414885501203,89.5051620308737,61.7678249437873745],"luv":[84.41414885501203,42.3400226901080785,78.8574442191356439],"rgb":[1,0.8,0.266666666666666663],"xyz":[0.638742768947247,0.64865051385044481,0.146249805375657],"hpluv":[61.7678249437873745,247.206609954939694,84.41414885501203],"hsluv":[61.7678249437873745,100.000000000007887,84.41414885501203]},"#ffcc55":{"lch":[84.5370662928161,84.6613331238571476,60.5932597011580469],"luv":[84.5370662928161,41.569242864558305,73.7532329730438221],"rgb":[1,0.8,0.333333333333333315],"xyz":[0.644705182986609,0.651035479466189559,0.177651852649630904],"hpluv":[60.5932597011580469,235.935294148211483,84.5370662928161],"hsluv":[60.5932597011580469,100.000000000008015,84.5370662928161]},"#ffcc66":{"lch":[84.6930007913096,78.7104448491835456,58.9357730127494222],"luv":[84.6930007913096,40.6144802919859131,67.4225342075500151],"rgb":[1,0.8,0.4],"xyz":[0.65229017876011719,0.654069477775592878,0.217599497056774865],"hpluv":[58.9357730127494222,221.881579382217893,84.6930007913096],"hsluv":[58.9357730127494222,100.000000000008285,84.6930007913096]},"#ffcc77":{"lch":[84.8838226897762809,71.7288671198513441,56.6053668146737863],"luv":[84.8838226897762809,39.4797505573258718,59.8863897244492378],"rgb":[1,0.8,0.466666666666666674],"xyz":[0.661604191780319,0.657795082983673729,0.266653298963172802],"hpluv":[56.6053668146737863,205.087229042099608,84.8838226897762809],"hsluv":[56.6053668146737863,100.000000000008399,84.8838226897762809]},"#ffcc88":{"lch":[85.1111193079521371,63.8628893840605159,53.2910582019814285],"luv":[85.1111193079521371,38.1740592359010691,51.1977523133063457],"rgb":[1,0.8,0.533333333333333326],"xyz":[0.672744608882263,0.662251249824451271,0.325326162366744942],"hpluv":[53.2910582019814285,185.743844227516831,85.1111193079521371],"hsluv":[53.2910582019814285,100.000000000008683,85.1111193079521371]},"#ffcc99":{"lch":[85.3762249003348899,55.3594884395636555,48.4608196634464292],"luv":[85.3762249003348899,36.710650975629143,41.436711563970924],"rgb":[1,0.8,0.6],"xyz":[0.685801572991335595,0.667474035468080484,0.394092840007862821],"hpluv":[48.4608196634464292,164.300705026796521,85.3762249003348899],"hsluv":[48.4608196634464292,100.000000000008811,85.3762249003348899]},"#ffccaa":{"lch":[85.6802414041430467,46.6394331206389836,41.1740083194267896],"luv":[85.6802414041430467,35.1061374539925311,30.7049806200222122],"rgb":[1,0.8,0.66666666666666663],"xyz":[0.700859265956588207,0.673497112654181551,0.473396689624861478],"hpluv":[41.1740083194267896,141.724192848301414,85.6802414041430467],"hsluv":[41.1740083194267896,100.000000000009393,85.6802414041430467]},"#ffccbb":{"lch":[86.0240539433014106,38.4677176438646384,29.8042196075727404],"luv":[86.0240539433014106,33.3795484258888635,19.1199123327444944],"rgb":[1,0.8,0.733333333333333282],"xyz":[0.71799685806392477,0.68035214949711631,0.563654674723503168],"hpluv":[29.8042196075727404,120.116808153279436,86.0240539433014106],"hsluv":[29.8042196075727404,100.000000000009621,86.0240539433014106]},"#ffcccc":{"lch":[86.4083433793485,32.2775975377643,12.1770506300627517],"luv":[86.4083433793485,31.5513664521307504,6.80841962670093892],"rgb":[1,0.8,0.8],"xyz":[0.737289233507646524,0.688069099674605167,0.665261185393773324],"hpluv":[12.1770506300627517,103.973607583717524,86.4083433793485],"hsluv":[12.1770506300627517,100.000000000010388,86.4083433793485]},"#ffccdd":{"lch":[86.8335972965778637,30.2635044437543748,348.373924949033096],"luv":[86.8335972965778637,29.6426074465171432,-6.09881340826452867],"rgb":[1,0.8,0.866666666666666696],"xyz":[0.758807559058280123,0.696676429894858673,0.778591033293779322],"hpluv":[348.373924949033096,100.994037434302086,86.8335972965778637],"hsluv":[348.373924949033096,100.000000000011042,86.8335972965778637]},"#ffccee":{"lch":[87.3001202800073344,33.8382691858854301,324.868297683069],"luv":[87.3001202800073344,27.6740002852911822,-19.4724977777928352],"rgb":[1,0.8,0.933333333333333348],"xyz":[0.782619739324454833,0.70620130200132869,0.904001849362302545],"hpluv":[324.868297683069,117.528800717138253,87.3001202800073344],"hsluv":[324.868297683069,100.000000000011482,87.3001202800073344]},"#ffccff":{"lch":[87.8080440143565255,41.9549825399590404,307.715012949248376],"luv":[87.8080440143565255,25.6653035474654807,-33.1890456889728],"rgb":[1,0.8,1],"xyz":[0.808790787734286254,0.716669721365261414,1.0418360376540845],"hpluv":[307.715012949248376,152.433043069806,87.8080440143565255],"hsluv":[307.715012949248376,100.000000000012506,87.8080440143565255]},"#aa4400":{"lch":[42.2796461632011074,91.196234940608619,23.7609213617016479],"luv":[42.2796461632011074,83.4659581293579578,36.7448921741641357],"rgb":[0.66666666666666663,0.266666666666666663,0],"xyz":[0.186442254479824487,0.126816788621301801,0.0146606651121894659],"hpluv":[23.7609213617016479,273.706361359402308,42.2796461632011074],"hsluv":[23.7609213617016479,100.000000000002331,42.2796461632011074]},"#aa4411":{"lch":[42.3415695164373815,89.5099073241138399,22.7237862619920179],"luv":[42.3415695164373815,82.5619515402106572,34.5766925405446202],"rgb":[0.66666666666666663,0.266666666666666663,0.0666666666666666657],"xyz":[0.18745391997946162,0.127221454821156654,0.0199887700769451],"hpluv":[22.7237862619920179,268.252316854423896,42.3415695164373815],"hsluv":[22.7237862619920179,95.0030043013494634,42.3415695164373815]},"#aa4422":{"lch":[42.4560124733741162,86.5487219398638,20.7501492775311753],"luv":[42.4560124733741162,80.9346773489764075,30.663647399501361],"rgb":[0.66666666666666663,0.266666666666666663,0.133333333333333331],"xyz":[0.189329278117938621,0.127971598076547483,0.0298656562729242916],"hpluv":[20.7501492775311753,258.678767688655569,42.4560124733741162],"hsluv":[20.7501492775311753,86.0172479577967692,42.4560124733741162]},"#aa4433":{"lch":[42.643470741830761,82.1252826930209494,17.3597256589108966],"luv":[42.643470741830761,78.3845002494828691,24.5037176372775498],"rgb":[0.66666666666666663,0.266666666666666663,0.2],"xyz":[0.192417028850396354,0.129206698369530582,0.0461278101305354349],"hpluv":[17.3597256589108966,244.378874013338617,42.643470741830761],"hsluv":[17.3597256589108966,71.9602068881057733,42.643470741830761]},"#aa4444":{"lch":[42.9120210749329871,76.685866981555165,12.1770506300619239],"luv":[42.9120210749329871,74.9604702767477704,16.1756017075590073],"rgb":[0.66666666666666663,0.266666666666666663,0.266666666666666663],"xyz":[0.196875024094615242,0.130989896467218175,0.0696065850834220734],"hpluv":[12.1770506300619239,226.764824908056937,42.9120210749329871],"hsluv":[12.1770506300619239,53.1380271992519795,42.9120210749329871]},"#aa4455":{"lch":[43.2674147484635299,71.0489272172787594,4.80785478807657096],"luv":[43.2674147484635299,70.7989329223205,5.9549270177674023],"rgb":[0.66666666666666663,0.266666666666666663,0.333333333333333315],"xyz":[0.202837438133977255,0.133374862082963,0.101008632357395989],"hpluv":[4.80785478807657096,208.370342069187018,43.2674147484635299],"hsluv":[4.80785478807657096,55.6796565441877931,43.2674147484635299]},"#aa4466":{"lch":[43.7134526285125489,66.3411716499448261,355.020980864279068],"luv":[43.7134526285125489,66.0908363196155477,-5.75781299290188],"rgb":[0.66666666666666663,0.266666666666666663,0.4],"xyz":[0.210422433907485412,0.136408860392366299,0.140956276764539951],"hpluv":[355.020980864279068,192.578302395247789,43.7134526285125489],"hsluv":[355.020980864279068,58.5402216503940949,43.7134526285125489]},"#aa4477":{"lch":[44.252209356870793,63.7921835342278598,343.127789968560137],"luv":[44.252209356870793,61.046215168807521,-18.5149208377531842],"rgb":[0.66666666666666663,0.266666666666666663,0.466666666666666674],"xyz":[0.219736446927687346,0.140134465600447122,0.19001007867093786],"hpluv":[343.127789968560137,182.924482874911519,44.252209356870793],"hsluv":[343.127789968560137,61.5848400189491372,44.252209356870793]},"#aa4488":{"lch":[44.8842146397509367,64.3242328637060865,330.281311898752961],"luv":[44.8842146397509367,55.8636578331810938,-31.8882214461946916],"rgb":[0.66666666666666663,0.266666666666666663,0.533333333333333326],"xyz":[0.230876864029631146,0.14459063244122472,0.248682942074510027],"hpluv":[330.281311898752961,181.852933643495049,44.8842146397509367],"hsluv":[330.281311898752961,64.6866121014520274,44.8842146397509367]},"#aa4499":{"lch":[45.6086312393750077,68.1395242873703779,318.091147538790551],"luv":[45.6086312393750077,50.7100032224043318,-45.5136281051387854],"rgb":[0.66666666666666663,0.266666666666666663,0.6],"xyz":[0.243933828138703873,0.149813418084853878,0.317449619715627906],"hpluv":[318.091147538790551,189.579504981677246,45.6086312393750077],"hsluv":[318.091147538790551,67.7395528675925,45.6086312393750077]},"#aa44aa":{"lch":[46.4234422285949293,74.7240378323112822,307.715012949244056],"luv":[46.4234422285949293,45.7112599542092468,-59.1114417296972476],"rgb":[0.66666666666666663,0.266666666666666663,0.66666666666666663],"xyz":[0.258991521103956401,0.155836495270954972,0.396753469332626507],"hpluv":[307.715012949244056,204.25011915803762,46.4234422285949293],"hsluv":[307.715012949244056,70.6643205278868862,46.4234422285949293]},"#aa44bb":{"lch":[47.3256474704596144,83.2556513741305224,299.464802712285689],"luv":[47.3256474704596144,40.9525224701243289,-72.4872015535482177],"rgb":[0.66666666666666663,0.266666666666666663,0.733333333333333282],"xyz":[0.276129113211293076,0.162691532113889731,0.487011454431268254],"hpluv":[299.464802712285689,223.2320179514779,47.3256474704596144],"hsluv":[299.464802712285689,73.4081717041738244,47.3256474704596144]},"#aa44cc":{"lch":[48.311463406953564,92.9774863668884564,293.103270963637385],"luv":[48.311463406953564,36.4834012629607827,-85.5206080625642073],"rgb":[0.66666666666666663,0.266666666666666663,0.8],"xyz":[0.29542148865501483,0.170408482291378532,0.58861796510153841],"hpluv":[293.103270963637385,244.211962975346665,48.311463406953564],"hsluv":[293.103270963637385,75.9413806031634806,48.311463406953564]},"#aa44dd":{"lch":[49.376518181476186,103.335675962773564,288.229438001673088],"luv":[49.376518181476186,32.3257723855767125,-98.1494083851717676],"rgb":[0.66666666666666663,0.266666666666666663,0.866666666666666696],"xyz":[0.316939814205648318,0.179015812511632066,0.701947813001544407],"hpluv":[288.229438001673088,265.563967984367252,49.376518181476186],"hsluv":[288.229438001673088,78.2521754793284572,49.376518181476186]},"#aa44ee":{"lch":[50.5160343838387149,113.969241853628489,284.471970959088878],"luv":[50.5160343838387149,28.4816380653286,-110.35299897060564],"rgb":[0.66666666666666663,0.266666666666666663,0.933333333333333348],"xyz":[0.340751994471823083,0.188540684618102111,0.82735862907006763],"hpluv":[284.471970959088878,286.284434407824506,50.5160343838387149],"hsluv":[284.471970959088878,88.5420288112715355,50.5160343838387149]},"#aa44ff":{"lch":[51.7249932896939271,124.658572052901789,281.540806859048416],"luv":[51.7249932896939271,24.9399169363974949,-122.138282816953421],"rgb":[0.66666666666666663,0.266666666666666663,1],"xyz":[0.366923042881654449,0.199009103982034807,0.965192817361849476],"hpluv":[281.540806859048416,305.816582895375404,51.7249932896939271],"hsluv":[281.540806859048416,99.9999999999991189,51.7249932896939271]},"#885500":{"lch":[40.7868302215615941,57.8204075903908716,44.0255445375638317],"luv":[40.7868302215615941,41.5746091122878738,40.1839695784202107],"rgb":[0.533333333333333326,0.333333333333333315,0],"xyz":[0.134014735183400707,0.117319151919440201,0.0155871309747999068],"hpluv":[44.0255445375638317,179.887306981779091,40.7868302215615941],"hsluv":[44.0255445375638317,100.000000000002402,40.7868302215615941]},"#885511":{"lch":[40.8520464566488215,55.8013246771799061,42.6953812551229817],"luv":[40.8520464566488215,41.012258348334349,37.838901951530346],"rgb":[0.533333333333333326,0.333333333333333315,0.0666666666666666657],"xyz":[0.135026400683037839,0.117723818119295054,0.0209152359395555383],"hpluv":[42.6953812551229817,173.328515822040885,40.8520464566488215],"hsluv":[42.6953812551229817,94.5141207032234121,40.8520464566488215]},"#885522":{"lch":[40.9725457623763205,52.2529579480882163,40.0436802938645613],"luv":[40.9725457623763205,40.0024704692244413,33.6180601862079769],"rgb":[0.533333333333333326,0.333333333333333315,0.133333333333333331],"xyz":[0.136901758821514841,0.118473961374685868,0.0307921221355347344],"hpluv":[40.0436802938645613,161.829338044896133,40.9725457623763205],"hsluv":[40.0436802938645613,84.6783920295854813,40.9725457623763205]},"#885533":{"lch":[41.169842808691,46.965215190705,35.0963808532685348],"luv":[41.169842808691,38.426283274472091,27.0028182162390955],"rgb":[0.533333333333333326,0.333333333333333315,0.2],"xyz":[0.139989509553972602,0.119709061667668981,0.0470542759931458776],"hpluv":[35.0963808532685348,144.75595347357384,41.169842808691],"hsluv":[35.0963808532685348,69.3663942781120113,41.169842808691]},"#885544":{"lch":[41.4523140669009891,40.5881659839312263,26.5063976077417607],"luv":[41.4523140669009891,36.321721997537459,18.1144066718387258],"rgb":[0.533333333333333326,0.333333333333333315,0.266666666666666663],"xyz":[0.144447504798191462,0.121492259765356561,0.0705330509460325161],"hpluv":[26.5063976077417607,124.248162385526584,41.4523140669009891],"hsluv":[26.5063976077417607,49.0053368988820708,41.4523140669009891]},"#885555":{"lch":[41.8258216452066449,34.5587016635497619,12.1770506300621708],"luv":[41.8258216452066449,33.7811467851905647,7.28958040957408926],"rgb":[0.533333333333333326,0.333333333333333315,0.333333333333333315],"xyz":[0.150409918837553447,0.123877225381101394,0.101935098220006432],"hpluv":[12.1770506300621708,104.846095879990827,41.8258216452066449],"hsluv":[12.1770506300621708,24.5686900376428454,41.8258216452066449]},"#885566":{"lch":[42.2941086985740071,31.3312262907584227,350.801819307985852],"luv":[42.2941086985740071,30.9283487442626033,-5.00829160852097477],"rgb":[0.533333333333333326,0.333333333333333315,0.4],"xyz":[0.157994914611061632,0.126911223690504699,0.141882742627150393],"hpluv":[350.801819307985852,94.0019456509277092,42.2941086985740071],"hsluv":[350.801819307985852,29.8132814869556348,42.2941086985740071]},"#885577":{"lch":[42.8590433503379202,33.3514130793808121,326.760730108285],"luv":[42.8590433503379202,27.8947491372056646,-18.2811302977648822],"rgb":[0.533333333333333326,0.333333333333333315,0.466666666666666674],"xyz":[0.167308927631263538,0.130636828898585522,0.190936544533548302],"hpluv":[326.760730108285,98.7440857113442263,42.8590433503379202],"hsluv":[326.760730108285,35.3342565461126838,42.8590433503379202]},"#885588":{"lch":[43.5208237898043535,40.5407907233736822,307.71501294924542],"luv":[43.5208237898043535,24.8001938501248809,-32.0703304858651137],"rgb":[0.533333333333333326,0.333333333333333315,0.533333333333333326],"xyz":[0.178449344733207393,0.13509299573936312,0.24960940793712047],"hpluv":[307.71501294924542,118.204615389413121,43.5208237898043535],"hsluv":[307.71501294924542,40.8951968507306276,43.5208237898043535]},"#885599":{"lch":[44.278184332936334,50.8805981998246,295.296495298175159],"luv":[44.278184332936334,21.7414099229374891,-46.0015909261276477],"rgb":[0.533333333333333326,0.333333333333333315,0.6],"xyz":[0.191506308842280093,0.140315781382992277,0.318376085578238377],"hpluv":[295.296495298175159,145.814841874582299,44.278184332936334],"hsluv":[295.296495298175159,46.3068912836000735,44.278184332936334]},"#8855aa":{"lch":[45.1286132569148819,62.6826496939872868,287.441560465434577],"luv":[45.1286132569148819,18.7880516159354372,-59.8006997378464717],"rgb":[0.533333333333333326,0.333333333333333315,0.66666666666666663],"xyz":[0.206564001807532649,0.146338858569093372,0.397679935195237],"hpluv":[287.441560465434577,176.252255995348207,45.1286132569148819],"hsluv":[287.441560465434577,51.4349240401982044,45.1286132569148819]},"#8855bb":{"lch":[46.0685799041538857,75.0110486022347374,282.303621490188448],"luv":[46.0685799041538857,15.9842650126874819,-73.2882029006783284],"rgb":[0.533333333333333326,0.333333333333333315,0.733333333333333282],"xyz":[0.223701593914869268,0.15319389541202813,0.487937920293878724],"hpluv":[282.303621490188448,206.613996430434867,46.0685799041538857],"hsluv":[282.303621490188448,56.1963190544194191,46.0685799041538857]},"#8855cc":{"lch":[47.0937627302438173,87.3889723072186229,278.789341148015808],"luv":[47.0937627302438173,13.3532090014702405,-86.3627482788434406],"rgb":[0.533333333333333326,0.333333333333333315,0.8],"xyz":[0.242993969358591022,0.160910845589516932,0.58954443096414888],"hpluv":[278.789341148015808,235.468364438494291,47.0937627302438173],"hsluv":[278.789341148015808,67.0071532265272083,47.0937627302438173]},"#8855dd":{"lch":[48.1992684502362323,99.579643468316263,276.285619014825784],"luv":[48.1992684502362323,10.9024601244232162,-98.9810171523426163],"rgb":[0.533333333333333326,0.333333333333333315,0.866666666666666696],"xyz":[0.264512294909224566,0.169518175809770466,0.702874278864154878],"hpluv":[276.285619014825784,262.161822059859048,48.1992684502362323],"hsluv":[276.285619014825784,77.9008511174594673,48.1992684502362323]},"#8855ee":{"lch":[49.3798334670730128,111.473561000814087,274.439637972028379],"luv":[49.3798334670730128,8.62903279282213198,-111.139077714648238],"rgb":[0.533333333333333326,0.333333333333333315,0.933333333333333348],"xyz":[0.288324475175399275,0.179043047916240511,0.828285094932678101],"hpluv":[274.439637972028379,286.45841220928196,49.3798334670730128],"hsluv":[274.439637972028379,88.8665998649397295,49.3798334670730128]},"#8855ff":{"lch":[50.6300011250937416,123.030386306339324,273.039422554437692],"luv":[50.6300011250937416,6.52344684402536235,-122.857318039912712],"rgb":[0.533333333333333326,0.333333333333333315,1],"xyz":[0.314495523585230696,0.189511467280173207,0.96611928322446],"hpluv":[273.039422554437692,308.349875308867752,50.6300011250937416],"hsluv":[273.039422554437692,99.9999999999992,50.6300011250937416]},"#ffdd00":{"lch":[88.435570144315335,99.3071523162376195,71.7429005549186911],"luv":[88.435570144315335,31.1110916577435432,94.3080615696447211],"rgb":[1,0.866666666666666696,0],"xyz":[0.670943989879631442,0.729745387098879927,0.105515215586815703],"hpluv":[71.7429005549186911,382.363935262913174,88.435570144315335],"hsluv":[71.7429005549186911,100.000000000012946,88.435570144315335]},"#ffdd11":{"lch":[88.454870819445,98.4678340856778,71.6448189166009826],"luv":[88.454870819445,31.0081800872079896,93.4580500395971683],"rgb":[1,0.866666666666666696,0.0666666666666666657],"xyz":[0.671955655379268602,0.730150053298734836,0.110843320551571339],"hpluv":[71.6448189166009826,379.826472639876158,88.454870819445],"hsluv":[71.6448189166009826,100.00000000001296,88.454870819445]},"#ffdd22":{"lch":[88.4906302718539735,96.9204819130462,71.4594307996413],"luv":[88.4906302718539735,30.8183922605744947,91.8901872494037093],"rgb":[1,0.866666666666666696,0.133333333333333331],"xyz":[0.673831013517745547,0.730900196554125636,0.120720206747550532],"hpluv":[71.4594307996413,375.129750349680876,88.4906302718539735],"hsluv":[71.4594307996413,100.00000000001296,88.4906302718539735]},"#ffdd33":{"lch":[88.5494544369894641,94.3967280931520776,71.1436744567929793],"luv":[88.5494544369894641,30.5086598694284135,89.330643945199526],"rgb":[1,0.866666666666666696,0.2],"xyz":[0.676918764250203364,0.732135296847108763,0.136982360605161668],"hpluv":[71.1436744567929793,367.416320973762822,88.5494544369894641],"hsluv":[71.1436744567929793,100.000000000013216,88.5494544369894641]},"#ffdd44":{"lch":[88.6342662809689301,90.8049878425315455,70.6631891871382152],"luv":[88.6342662809689301,30.0674100616276441,85.6825342136204284],"rgb":[1,0.866666666666666696,0.266666666666666663],"xyz":[0.681376759494422224,0.733918494944796329,0.160461135558048307],"hpluv":[70.6631891871382152,356.322085242203968,88.6342662809689301],"hsluv":[70.6631891871382152,100.000000000013173,88.6342662809689301]},"#ffdd55":{"lch":[88.7474847112806486,86.0957791731635353,69.970644696948483],"luv":[88.7474847112806486,29.4879375414461293,80.8884709398435433],"rgb":[1,0.866666666666666696,0.333333333333333315],"xyz":[0.687339173533784153,0.736303460560541079,0.191863182832022222],"hpluv":[69.970644696948483,341.559684906391112,88.7474847112806486],"hsluv":[69.970644696948483,100.000000000013429,88.7474847112806486]},"#ffdd66":{"lch":[88.8911610589964454,80.258955992054581,68.9956984045855819],"luv":[88.8911610589964454,28.7678627944319345,74.9260307715235427],"rgb":[1,0.866666666666666696,0.4],"xyz":[0.694924169307292394,0.739337458869944397,0.231810827239166184],"hpluv":[68.9956984045855819,322.902183683526573,88.8911610589964454],"hsluv":[68.9956984045855819,100.000000000013586,88.8911610589964454]},"#ffdd77":{"lch":[89.0670520916409885,73.323747836811151,67.6276760217336346],"luv":[89.0670520916409885,27.9087592291374555,67.8046691248198812],"rgb":[1,0.866666666666666696,0.466666666666666674],"xyz":[0.704238182327494244,0.743063064078025248,0.280864629145564093],"hpluv":[67.6276760217336346,300.178104609939055,89.0670520916409885],"hsluv":[67.6276760217336346,100.000000000014367,89.0670520916409885]},"#ffdd88":{"lch":[89.2766635023192379,65.3617696880632622,65.6822421460803554],"luv":[89.2766635023192379,26.9157684368328205,59.5625918359016637],"rgb":[1,0.866666666666666696,0.533333333333333326],"xyz":[0.715378599429438156,0.747519230918802791,0.339537492549136233],"hpluv":[65.6822421460803554,273.280966903928913,89.2766635023192379],"hsluv":[65.6822421460803554,100.00000000001468,89.2766635023192379]},"#ffdd99":{"lch":[89.5212778802452362,56.4966374008110606,62.8311838382076928],"luv":[89.5212778802452362,25.7971434460875209,50.2630821540094814],"rgb":[1,0.866666666666666696,0.6],"xyz":[0.7284355635385108,0.752742016562432,0.408304170190254168],"hpluv":[62.8311838382076928,242.212083001239,89.5212778802452362],"hsluv":[62.8311838382076928,100.000000000014893,89.5212778802452362]},"#ffddaa":{"lch":[89.8019739344538408,46.9317870748453103,58.439957868293348],"luv":[89.8019739344538408,24.5637119196041205,39.9902074859498],"rgb":[1,0.866666666666666696,0.66666666666666663],"xyz":[0.743493256503763411,0.75876509374853307,0.487608019807252768],"hpluv":[58.439957868293348,207.216087370130765,89.8019739344538408],"hsluv":[58.439957868293348,100.000000000015916,89.8019739344538408]},"#ffddbb":{"lch":[90.1196406145249114,37.0342016806554142,51.1553076529108282],"luv":[90.1196406145249114,23.228278171915747,28.8440494260352231],"rgb":[1,0.866666666666666696,0.733333333333333282],"xyz":[0.7606308486111,0.765620130591467829,0.57786600490589457],"hpluv":[51.1553076529108282,169.207898233907059,90.1196406145249114],"hsluv":[51.1553076529108282,100.00000000001647,90.1196406145249114]},"#ffddcc":{"lch":[90.4749882420216665,27.6093496047032829,37.8361109659034085],"luv":[90.4749882420216665,21.8049964890922183,16.9357111957368183],"rgb":[1,0.866666666666666696,0.8],"xyz":[0.779923224054821729,0.773337080768956686,0.679472515576164726],"hpluv":[37.8361109659034085,131.228146902908946,90.4749882420216665],"hsluv":[37.8361109659034085,100.000000000017565,90.4749882420216665]},"#ffdddd":{"lch":[90.8685579434819743,20.7762078419716971,12.1770506300632935],"luv":[90.8685579434819743,20.3087527298379129,4.38239373528981613],"rgb":[1,0.866666666666666696,0.866666666666666696],"xyz":[0.801441549605455328,0.781944410989210192,0.792802363476170724],"hpluv":[12.1770506300632935,103.332662997484363,90.8685579434819743],"hsluv":[12.1770506300632935,100.000000000018645,90.8685579434819743]},"#ffddee":{"lch":[91.3007301977477255,20.6730653908416642,335.121130580225611],"luv":[91.3007301977477255,18.7545890012647547,-8.69718483462634318],"rgb":[1,0.866666666666666696,0.933333333333333348],"xyz":[0.82525372987163,0.791469283095680209,0.918213179544694],"hpluv":[335.121130580225611,108.301670079840079,91.3007301977477255],"hsluv":[335.121130580225611,100.000000000019696,91.3007301977477255]},"#ffddff":{"lch":[91.7717330140538081,28.0468143452174594,307.715012949251673],"luv":[91.7717330140538081,17.1571994583452359,-22.1868046744377736],"rgb":[1,0.866666666666666696,1],"xyz":[0.851424778281461458,0.801937702459612933,1.05604736783647568],"hpluv":[307.715012949251673,155.925616416863875,91.7717330140538081],"hsluv":[307.715012949251673,100.000000000021274,91.7717330140538081]},"#aa5500":{"lch":[45.6948541105979729,81.8101604081173406,31.0178153240564285],"luv":[45.6948541105979729,70.1118895783240674,42.1571498770823254],"rgb":[0.66666666666666663,0.333333333333333315,0],"xyz":[0.198255511194170453,0.150443302049994065,0.0185984173503046732],"hpluv":[31.0178153240564285,227.184802334875343,45.6948541105979729],"hsluv":[31.0178153240564285,100.000000000002132,45.6948541105979729]},"#aa5511":{"lch":[45.7501207141343542,80.2053049231342357,30.0334267749456458],"luv":[45.7501207141343542,69.4364235827854088,40.1431690060082786],"rgb":[0.66666666666666663,0.333333333333333315,0.0666666666666666657],"xyz":[0.199267176693807585,0.150847968249848918,0.0239265223150603029],"hpluv":[30.0334267749456458,222.459100300712947,45.7501207141343542],"hsluv":[30.0334267749456458,95.7485290168496874,45.7501207141343542]},"#aa5522":{"lch":[45.8523093955831129,77.3637164970789826,28.1463245173948593],"luv":[45.8523093955831129,68.2151287979892231,36.4943945464800308],"rgb":[0.66666666666666663,0.333333333333333315,0.133333333333333331],"xyz":[0.201142534832284586,0.151598111505239747,0.0338034085110395],"hpluv":[28.1463245173948593,214.099393521154866,45.8523093955831129],"hsluv":[28.1463245173948593,88.0687786146531835,45.8523093955831129]},"#aa5533":{"lch":[46.0198296754266707,73.057054041475979,24.8605966968797674],"luv":[46.0198296754266707,66.2871018342682845,30.7140566456547255],"rgb":[0.66666666666666663,0.333333333333333315,0.2],"xyz":[0.20423028556474232,0.152833211798222846,0.0500655623686506457],"hpluv":[24.8605966968797674,201.444992058953261,46.0198296754266707],"hsluv":[24.8605966968797674,75.9647480733224683,46.0198296754266707]},"#aa5544":{"lch":[46.260105303202792,67.6400753638857,19.7251402693972757],"luv":[46.260105303202792,63.6711277752059743,22.829088529013422],"rgb":[0.66666666666666663,0.333333333333333315,0.266666666666666663],"xyz":[0.208688280808961207,0.154616409895910439,0.0735443373215372842],"hpluv":[19.7251402693972757,185.539675327038225,46.260105303202792],"hsluv":[19.7251402693972757,59.5831884891410581,46.260105303202792]},"#aa5555":{"lch":[46.5785950143652201,61.8404850960729462,12.1770506300619488],"luv":[46.5785950143652201,60.4491026496292108,13.0442165641410206],"rgb":[0.66666666666666663,0.333333333333333315,0.333333333333333315],"xyz":[0.21465069484832322,0.157001375511655272,0.1049463845955112],"hpluv":[12.1770506300619488,168.471262237657498,46.5785950143652201],"hsluv":[12.1770506300619488,39.4780386186733381,46.5785950143652201]},"#aa5566":{"lch":[46.9791292969753727,56.7719582569154682,1.70455911852653919],"luv":[46.9791292969753727,56.7468364589401801,1.68872621441415594],"rgb":[0.66666666666666663,0.333333333333333315,0.4],"xyz":[0.222235690621831378,0.160035373821058563,0.144894029002655161],"hpluv":[1.70455911852653919,153.34451205140752,46.9791292969753727],"hsluv":[1.70455911852653919,42.9013314457092818,46.9791292969753727]},"#aa5577":{"lch":[47.4641008255870247,53.815805251865207,348.374645401059922],"luv":[47.4641008255870247,52.7118371029350072,-10.8444974129891012],"rgb":[0.66666666666666663,0.333333333333333315,0.466666666666666674],"xyz":[0.231549703642033311,0.163760979029139386,0.193947830909053071],"hpluv":[348.374645401059922,143.874527939012694,47.4641008255870247],"hsluv":[348.374645401059922,46.6096363259647291,47.4641008255870247]},"#aa5588":{"lch":[48.0346061313586716,54.1759936988691209,333.519859036575326],"luv":[48.0346061313586716,48.4923339918738,-24.1564036495598238],"rgb":[0.66666666666666663,0.333333333333333315,0.533333333333333326],"xyz":[0.242690120743977111,0.168217145869916984,0.252620694312625238],"hpluv":[333.519859036575326,143.117248614217317,48.0346061313586716],"hsluv":[333.519859036575326,50.4580944804944309,48.0346061313586716]},"#aa5599":{"lch":[48.6905763376225,58.2309730669685877,319.412334073918828],"luv":[48.6905763376225,44.2212637341432782,-37.885697275903027],"rgb":[0.66666666666666663,0.333333333333333315,0.6],"xyz":[0.255747084853049866,0.173439931513546142,0.321387371953743117],"hpluv":[319.412334073918828,151.756904292295189,48.6905763376225],"hsluv":[319.412334073918828,54.3174516364451492,48.6905763376225]},"#aa55aa":{"lch":[49.4309115490906095,65.3990862514302904,307.715012949244453],"luv":[49.4309115490906095,40.0068668547544135,-51.7348150377284242],"rgb":[0.66666666666666663,0.333333333333333315,0.66666666666666663],"xyz":[0.270804777818302367,0.179463008699647236,0.400691221570741718],"hpluv":[307.715012949244453,167.885190443837445,49.4309115490906095],"hsluv":[307.715012949244453,58.0831627335764651,49.4309115490906095]},"#aa55bb":{"lch":[50.2536225490081137,74.6889496830887651,298.754319252296796],"luv":[50.2536225490081137,35.9294822147829578,-65.4790921786531896],"rgb":[0.66666666666666663,0.333333333333333315,0.733333333333333282],"xyz":[0.287942369925639041,0.186318045542582,0.490949206669383464],"hpluv":[298.754319252296796,188.594188212432869,50.2536225490081137],"hsluv":[298.754319252296796,61.6784311565058445,50.2536225490081137]},"#aa55cc":{"lch":[51.1559779262539394,85.2173265065914194,292.086991032215337],"luv":[51.1559779262539394,32.0428980728004476,-78.9635702082114],"rgb":[0.66666666666666663,0.333333333333333315,0.8],"xyz":[0.307234745369360795,0.194034995720070796,0.59255571733965362],"hpluv":[292.086991032215337,211.383381159177,51.1559779262539394],"hsluv":[292.086991032215337,65.0527897247966536,51.1559779262539394]},"#aa55dd":{"lch":[52.1346521614624407,96.3659410029227,287.126655540846059],"luv":[52.1346521614624407,28.3783197098194826,-92.0927008824589137],"rgb":[0.66666666666666663,0.333333333333333315,0.866666666666666696],"xyz":[0.328753070919994284,0.20264232594032433,0.705885565239659618],"hpluv":[287.126655540846059,234.550526854663673,52.1346521614624407],"hsluv":[287.126655540846059,75.3446722268955256,52.1346521614624407]},"#aa55ee":{"lch":[53.1858694266106227,107.745747725204239,283.388779775386126],"luv":[53.1858694266106227,24.9493252566021582,-104.817352199450397],"rgb":[0.66666666666666663,0.333333333333333315,0.933333333333333348],"xyz":[0.352565251186169049,0.212167198046794375,0.831296381308182841],"hpluv":[283.388779775386126,257.065149407391289,53.1858694266106227],"hsluv":[283.388779775386126,87.5522298773574335,53.1858694266106227]},"#aa55ff":{"lch":[54.3055382240479361,119.125691570750178,280.523377624217346],"luv":[54.3055382240479361,21.7567225446561139,-117.12205350114192],"rgb":[0.66666666666666663,0.333333333333333315,1],"xyz":[0.378736299596000414,0.222635617410727071,0.969130569599964686],"hpluv":[280.523377624217346,278.356033354379861,54.3055382240479361],"hsluv":[280.523377624217346,99.999999999999,54.3055382240479361]},"#886600":{"lch":[45.272583339231268,54.7393681124008964,58.6614018365849361],"luv":[45.272583339231268,28.4696504737342408,46.7533680417607513],"rgb":[0.533333333333333326,0.4,0],"xyz":[0.149042792889247183,0.147375267331133541,0.020596483543415256],"hpluv":[58.6614018365849361,153.427719347991228,45.272583339231268],"hsluv":[58.6614018365849361,100.000000000002288,45.272583339231268]},"#886611":{"lch":[45.3286132830504442,52.7569286908504935,57.8019218571504112],"luv":[45.3286132830504442,28.1114182485843394,44.6434954830448234],"rgb":[0.533333333333333326,0.4,0.0666666666666666657],"xyz":[0.150054458388884315,0.147779933530988394,0.0259245885081708857],"hpluv":[57.8019218571504112,147.688404254652824,45.3286132830504442],"hsluv":[57.8019218571504112,95.5933263309827765,45.3286132830504442]},"#886622":{"lch":[45.4322079122274616,49.2027541487777427,56.0700181971155658],"luv":[45.4322079122274616,27.4639619392241023,40.8245246197174367],"rgb":[0.533333333333333326,0.4,0.133333333333333331],"xyz":[0.151929816527361317,0.148530076786379223,0.0358014747041500853],"hpluv":[56.0700181971155658,137.424731357888106,45.4322079122274616],"hsluv":[56.0700181971155658,87.640709221506242,45.4322079122274616]},"#886633":{"lch":[45.6020177209920377,43.6934420281053448,52.7580970187872822],"luv":[45.6020177209920377,26.4424624059276816,34.7838045385277539],"rgb":[0.533333333333333326,0.4,0.2],"xyz":[0.155017567259819078,0.149765177079362322,0.0520636285617612285],"hpluv":[52.7580970187872822,121.582627853675135,45.6020177209920377],"hsluv":[52.7580970187872822,75.126145239043538,45.6020177209920377]},"#886644":{"lch":[45.8455444844743383,36.5157977851307862,46.6690567683987183],"luv":[45.8455444844743383,25.057552877875338,26.56167411246971],"rgb":[0.533333333333333326,0.4,0.266666666666666663],"xyz":[0.159475562504037938,0.151548375177049915,0.0755424035146478601],"hpluv":[46.6690567683987183,101.07016738030697,45.8455444844743383],"hsluv":[46.6690567683987183,58.2269221932267413,45.8455444844743383]},"#886655":{"lch":[46.1682850891648,28.5288655346450497,35.057100942904512],"luv":[46.1682850891648,23.3531590809093643,16.3867669061227161],"rgb":[0.533333333333333326,0.4,0.333333333333333315],"xyz":[0.165437976543399923,0.153933340792794748,0.106944450788621775],"hpluv":[35.057100942904512,78.4115584201214517,46.1682850891648],"hsluv":[35.057100942904512,37.5458750067509825,46.1682850891648]},"#886666":{"lch":[46.5740725388720946,21.8884171395184417,12.1770506300623786],"luv":[46.5740725388720946,21.3959378301945833,4.61699569417310141],"rgb":[0.533333333333333326,0.4,0.4],"xyz":[0.173022972316908108,0.156967339102198039,0.146892095195765737],"hpluv":[12.1770506300623786,59.6361320849851921,46.5740725388720946],"hsluv":[12.1770506300623786,13.9745942082290213,46.5740725388720946]},"#886677":{"lch":[47.0652698878111053,20.9860757761626395,336.621799391030947],"luv":[47.0652698878111053,19.2632377900225293,-8.32724722389627559],"rgb":[0.533333333333333326,0.4,0.466666666666666674],"xyz":[0.18233698533711,0.160692944310278862,0.195945897102163646],"hpluv":[336.621799391030947,56.5809206524471051,47.0652698878111053],"hsluv":[336.621799391030947,19.7183189325496855,47.0652698878111053]},"#886688":{"lch":[47.6429159175320649,27.8420771343032278,307.715012949247239],"luv":[47.6429159175320649,17.0319546757823481,-22.0248445868429776],"rgb":[0.533333333333333326,0.4,0.533333333333333326],"xyz":[0.193477402439053869,0.16514911115105646,0.254618760505735842],"hpluv":[307.715012949247239,74.1553731862279335,47.6429159175320649],"hsluv":[307.715012949247239,25.6555006249089494,47.6429159175320649]},"#886699":{"lch":[48.306860672000596,39.0046617113806562,292.251886736336132],"luv":[48.306860672000596,14.770249996667463,-36.0999078981538304],"rgb":[0.533333333333333326,0.4,0.6],"xyz":[0.206534366548126569,0.170371896794685618,0.323385438146853721],"hpluv":[292.251886736336132,102.458272059709785,48.306860672000596],"hsluv":[292.251886736336132,31.5854767603208231,48.306860672000596]},"#8866aa":{"lch":[49.0559053000777112,51.7871044207240132,284.005102499462396],"luv":[49.0559053000777112,12.5329093477081912,-50.2476901714407305],"rgb":[0.533333333333333326,0.4,0.66666666666666663],"xyz":[0.221592059513379125,0.176394973980786712,0.402689287763852322],"hpluv":[284.005102499462396,133.958310232957899,49.0559053000777112],"hsluv":[284.005102499462396,39.6059098285329867,49.0559053000777112]},"#8866bb":{"lch":[49.8879495217490074,65.0723394771694501,279.16089377463328],"luv":[49.8879495217490074,10.3599978518109275,-64.242352148271],"rgb":[0.533333333333333326,0.4,0.733333333333333282],"xyz":[0.238729651620715744,0.183250010823721471,0.492947272862494068],"hpluv":[279.16089377463328,165.516042277675325,49.8879495217490074],"hsluv":[279.16089377463328,51.4342030459355897,49.8879495217490074]},"#8866cc":{"lch":[50.800144438276476,78.3702679805165161,276.063171305995638],"luv":[50.800144438276476,8.2778522611756955,-77.9318680982312912],"rgb":[0.533333333333333326,0.4,0.8],"xyz":[0.258022027064437498,0.190966961001210273,0.594553783532764224],"hpluv":[276.063171305995638,195.760791019658512,50.800144438276476],"hsluv":[276.063171305995638,63.370404435102877,50.800144438276476]},"#8866dd":{"lch":[51.7890458420015278,91.4433004674113903,273.951375062119212],"luv":[51.7890458420015278,6.3013441888180548,-91.225929766636682],"rgb":[0.533333333333333326,0.4,0.866666666666666696],"xyz":[0.279540352615071042,0.199574291221463807,0.707883631432770222],"hpluv":[273.951375062119212,224.054313572832513,51.7890458420015278],"hsluv":[273.951375062119212,75.4110532620532,51.7890458420015278]},"#8866ee":{"lch":[52.8507624936088831,104.176067138923727,272.440799986921],"luv":[52.8507624936088831,4.4365578942807,-104.081554170680207],"rgb":[0.533333333333333326,0.4,0.933333333333333348],"xyz":[0.303352532881245751,0.209099163327933851,0.833294447501293445],"hpluv":[272.440799986921,250.124381092910085,52.8507624936088831],"hsluv":[272.440799986921,87.5962217171162365,52.8507624936088831]},"#8866ff":{"lch":[53.9810943197935273,116.520298408763196,271.319576027424205],"luv":[53.9810943197935273,2.6833355157121912,-116.48939716462327],"rgb":[0.533333333333333326,0.4,1],"xyz":[0.329523581291077172,0.219567582691866547,0.97112863579307529],"hpluv":[271.319576027424205,273.904539658900717,53.9810943197935273],"hsluv":[271.319576027424205,99.9999999999990621,53.9810943197935273]},"#ffee00":{"lch":[92.75564548426334,102.358730475882979,79.2433869538170228],"luv":[92.75564548426334,19.1039702538988,100.560171167180329],"rgb":[1,0.933333333333333348,0],"xyz":[0.718122766220146147,0.824102939779910892,0.121241474366986887],"hpluv":[79.2433869538170228,651.393632104361359,92.75564548426334],"hsluv":[79.2433869538170228,100.000000000024428,92.75564548426334]},"#ffee11":{"lch":[92.7734436379168,101.564402459740549,79.2015044483446218],"luv":[92.7734436379168,19.0286516433192254,99.765917344759373],"rgb":[1,0.933333333333333348,0.0666666666666666657],"xyz":[0.719134431719783307,0.824507605979765801,0.12656957933174251],"hpluv":[79.2015044483446218,648.021125158270593,92.7734436379168],"hsluv":[79.2015044483446218,100.000000000024428,92.7734436379168]},"#ffee22":{"lch":[92.8064212727168183,100.098587827824673,79.1224559985664797],"luv":[92.8064212727168183,18.8896613956421291,98.3000914418838079],"rgb":[1,0.933333333333333348,0.133333333333333331],"xyz":[0.721009789858260253,0.825257749235156601,0.136446465527721716],"hpluv":[79.1224559985664797,641.762708667564539,92.8064212727168183],"hsluv":[79.1224559985664797,100.000000000024357,92.8064212727168183]},"#ffee33":{"lch":[92.8606749716073665,97.7038183815635506,78.9881622996309147],"luv":[92.8606749716073665,18.6625824309063972,95.9048702796014396],"rgb":[1,0.933333333333333348,0.2],"xyz":[0.72409754059071807,0.826492849528139728,0.152708619385332867],"hpluv":[78.9881622996309147,631.438241912838748,92.8606749716073665],"hsluv":[78.9881622996309147,100.000000000024599,92.8606749716073665]},"#ffee44":{"lch":[92.9389094487226828,94.2867391890675606,78.7846200921967181],"luv":[92.9389094487226828,18.3385503337116411,92.4861436030564192],"rgb":[1,0.933333333333333348,0.266666666666666663],"xyz":[0.728555535834936929,0.828276047625827294,0.176187394338219505],"hpluv":[78.7846200921967181,616.484076987319668,92.9389094487226828],"hsluv":[78.7846200921967181,100.000000000025381,92.9389094487226828]},"#ffee55":{"lch":[93.0433700241155179,89.7896797607108,78.4929149488566082],"luv":[93.0433700241155179,17.9120631143144244,87.984911129805738],"rgb":[1,0.933333333333333348,0.333333333333333315],"xyz":[0.734517949874298859,0.830661013241572,0.207589441612193393],"hpluv":[78.4929149488566082,596.384103057951847,93.0433700241155179],"hsluv":[78.4929149488566082,100.000000000025906,93.0433700241155179]},"#ffee66":{"lch":[93.1759694096933,84.1867368500463,78.0854442567743661],"luv":[93.1759694096933,17.3805845725397461,82.3730656305550895],"rgb":[1,0.933333333333333348,0.4],"xyz":[0.7421029456478071,0.833695011550975362,0.247537086019337382],"hpluv":[78.0854442567743661,570.62648367707834,93.1759694096933],"hsluv":[78.0854442567743661,100.000000000026517,93.1759694096933]},"#ffee77":{"lch":[93.3383558005883742,77.4816602435733586,77.5195453952684659],"luv":[93.3383558005883742,16.7442947506152713,75.6507519288807515],"rgb":[1,0.933333333333333348,0.466666666666666674],"xyz":[0.751416958668009,0.837420616759056213,0.296590887925735291],"hpluv":[77.5195453952684659,538.663675570032183,93.3383558005883742],"hsluv":[77.5195453952684659,100.00000000002693,93.3383558005883742]},"#ffee88":{"lch":[93.5319535615141433,69.7063855050026433,76.7254067288761235],"luv":[93.5319535615141433,16.0058531534094222,67.8438858336072599],"rgb":[1,0.933333333333333348,0.533333333333333326],"xyz":[0.762557375769952861,0.841876783599833756,0.355263751329307431],"hpluv":[76.7254067288761235,499.867858345774721,93.5319535615141433],"hsluv":[76.7254067288761235,100.000000000028109,93.5319535615141433]},"#ffee99":{"lch":[93.7579894103796647,60.9203426164438397,75.5807218945013517],"luv":[93.7579894103796647,15.1701259512809905,59.0013171304435744],"rgb":[1,0.933333333333333348,0.6],"xyz":[0.775614339879025505,0.847099569243463,0.424030428970425366],"hpluv":[75.5807218945013517,453.479030486808085,93.7579894103796647],"hsluv":[75.5807218945013517,100.000000000029459,93.7579894103796647]},"#ffeeaa":{"lch":[94.0175103342715204,51.2122166590618946,73.8511258956299],"luv":[94.0175103342715204,14.2438643230739057,49.191497886124175],"rgb":[1,0.933333333333333348,0.66666666666666663],"xyz":[0.790672032844278116,0.853122646429564,0.503334278587424],"hpluv":[73.8511258956299,398.552269307706354,94.0175103342715204],"hsluv":[73.8511258956299,100.00000000003169,94.0175103342715204]},"#ffeebb":{"lch":[94.3113965930375855,40.7102572776027571,71.0277189369888],"luv":[94.3113965930375855,13.2353397134719373,38.4987120599845625],"rgb":[1,0.933333333333333348,0.733333333333333282],"xyz":[0.80780962495161468,0.859977683272498794,0.593592263686065658],"hpluv":[71.0277189369888,333.94796839081863,94.3113965930375855],"hsluv":[71.0277189369888,100.000000000032855,94.3113965930375855]},"#ffeecc":{"lch":[94.6403717602024841,29.6267857036886042,65.7803861656043125],"luv":[94.6403717602024841,12.1539519061426287,27.0190281874729799],"rgb":[1,0.933333333333333348,0.8],"xyz":[0.827102000395336434,0.867694633449987651,0.695198774356335814],"hpluv":[65.7803861656043125,258.601677072085806,94.6403717602024841],"hsluv":[65.7803861656043125,100.000000000035726,94.6403717602024841]},"#ffeedd":{"lch":[95.0050109981125814,18.4911495042727,53.4580761074439366],"luv":[95.0050109981125814,11.0098304435300403,14.8561853648264481],"rgb":[1,0.933333333333333348,0.866666666666666696],"xyz":[0.84862032594597,0.876301963670241157,0.808528622256341811],"hpluv":[53.4580761074439366,173.670551799097524,95.0050109981125814],"hsluv":[53.4580761074439366,100.000000000039279,95.0050109981125814]},"#ffeeee":{"lch":[95.4057483293867,10.0393308083340358,12.1770506300655121],"luv":[95.4057483293867,9.81345048674430487,2.11762900985580904],"rgb":[1,0.933333333333333348,0.933333333333333348],"xyz":[0.872432506212144743,0.885826835776711174,0.933939438324865],"hpluv":[12.1770506300655121,102.829227108855335,95.4057483293867],"hsluv":[12.1770506300655121,100.000000000042746,95.4057483293867]},"#ffeeff":{"lch":[95.8428833991312104,14.017983351086059,307.715012949261848],"luv":[95.8428833991312104,8.57528179129596,-11.0891117512266888],"rgb":[1,0.933333333333333348,1],"xyz":[0.898603554621976164,0.896295255140643898,1.07177362661664688],"hpluv":[307.715012949261848,159.207478793902965,95.8428833991312104],"hsluv":[307.715012949261848,100.000000000047876,95.8428833991312104]},"#aa6600":{"lch":[49.5566255632669623,74.0434564420528574,40.5370999312324685],"luv":[49.5566255632669623,56.2719368465296,48.1238253407432595],"rgb":[0.66666666666666663,0.4,0],"xyz":[0.213283568900016929,0.180499417461687406,0.0236077699189200241],"hpluv":[40.5370999312324685,189.593866720893345,49.5566255632669623],"hsluv":[40.5370999312324685,100.000000000002302,49.5566255632669623]},"#aa6611":{"lch":[49.6055800152373934,72.4782904829715449,39.6736286483530876],"luv":[49.6055800152373934,55.7860674256664097,46.2711278500211662],"rgb":[0.66666666666666663,0.4,0.0666666666666666657],"xyz":[0.214295234399654061,0.180904083661542259,0.0289358748836756538],"hpluv":[39.6736286483530876,185.402990863318706,49.6055800152373934],"hsluv":[39.6736286483530876,96.4267217319678878,49.6055800152373934]},"#aa6622":{"lch":[49.6961357673044404,69.6784302133091,38.0044715349972151],"luv":[49.6961357673044404,54.9040042209680124,42.902610147809348],"rgb":[0.66666666666666663,0.4,0.133333333333333331],"xyz":[0.216170592538131062,0.181654226916933087,0.0388127610796548533],"hpluv":[38.0044715349972151,177.916023719914563,49.6961357673044404],"hsluv":[38.0044715349972151,89.9454244119252593,49.6961357673044404]},"#aa6633":{"lch":[49.8446929303716502,65.3554895773238798,35.0519229290669685],"luv":[49.8446929303716502,53.5020897896209959,37.5348692023167061],"rgb":[0.66666666666666663,0.4,0.2],"xyz":[0.219258343270588796,0.182889327209916186,0.055074914937266],"hpluv":[35.0519229290669685,166.380518722129068,49.8446929303716502],"hsluv":[35.0519229290669685,79.6601301034586,49.8446929303716502]},"#aa6644":{"lch":[50.0579996788189163,59.7471791719702878,30.3078082111971625],"luv":[50.0579996788189163,51.5813401714318687,30.1511320703974341],"rgb":[0.66666666666666663,0.4,0.266666666666666663],"xyz":[0.223716338514807683,0.18467252530760378,0.0785536898901526282],"hpluv":[30.3078082111971625,151.454869534606559,50.0579996788189163],"hsluv":[30.3078082111971625,65.6016329960131,50.0579996788189163]},"#aa6655":{"lch":[50.3411543587309183,53.4384070136854916,23.0123473649378063],"luv":[50.3411543587309183,49.1858121787411307,20.8906492114991877],"rgb":[0.66666666666666663,0.4,0.333333333333333315],"xyz":[0.229678752554169696,0.187057490923348613,0.109955737164126544],"hpluv":[23.0123473649378063,134.70064023894011,50.3411543587309183],"hsluv":[23.0123473649378063,48.1262851503794238,50.3411543587309183]},"#aa6666":{"lch":[50.6979081899742283,47.4599132867605107,12.1770506300620198],"luv":[50.6979081899742283,46.3920870859415544,10.0108753362152232],"rgb":[0.66666666666666663,0.4,0.4],"xyz":[0.237263748327677854,0.190091489232751903,0.149903381571270505],"hpluv":[12.1770506300620198,118.788999996072334,50.6979081899742283],"hsluv":[12.1770506300620198,27.8359446414257086,50.6979081899742283]},"#aa6677":{"lch":[51.1308297914663399,43.3502150416948382,357.146673366158041],"luv":[51.1308297914663399,43.29647113668576,-2.15794607701909591],"rgb":[0.66666666666666663,0.4,0.466666666666666674],"xyz":[0.246577761347879787,0.193817094440832727,0.198957183477668414],"hpluv":[357.146673366158041,107.584013476394517,51.1308297914663399],"hsluv":[357.146673366158041,31.9217683762365354,51.1308297914663399]},"#aa6688":{"lch":[51.6414184020027,42.8134625942885876,339.120025752150127],"luv":[51.6414184020027,40.0018640870324234,-15.2592086582212207],"rgb":[0.66666666666666663,0.4,0.533333333333333326],"xyz":[0.257718178449823587,0.198273261281610325,0.257630046881240582],"hpluv":[339.120025752150127,105.201399585535427,51.6414184020027],"hsluv":[339.120025752150127,36.2396804223311,51.6414184020027]},"#aa6699":{"lch":[52.2302006219705675,46.6703467022409342,321.661853982703349],"luv":[52.2302006219705675,36.6065194780657563,-28.949680361091449],"rgb":[0.66666666666666663,0.4,0.6],"xyz":[0.270775142558896342,0.203496046925239482,0.326396724522358461],"hpluv":[321.661853982703349,113.385797667979,52.2302006219705675],"hsluv":[321.661853982703349,40.6514838109423,52.2302006219705675]},"#aa66aa":{"lch":[52.8968256208086274,54.2656136904176165,307.715012949245079],"luv":[52.8968256208086274,33.1961393674155048,-42.9275338250028753],"rgb":[0.66666666666666663,0.4,0.66666666666666663],"xyz":[0.285832835524148843,0.209519124111340577,0.405700574139357117],"hpluv":[307.715012949245079,130.177052782763241,52.8968256208086274],"hsluv":[307.715012949245079,45.0372955528084091,52.8968256208086274]},"#aa66bb":{"lch":[53.6401644756464293,64.2916348467718848,297.653919381785329],"luv":[53.6401644756464293,29.8396642728746109,-56.9474209016772193],"rgb":[0.66666666666666663,0.4,0.733333333333333282],"xyz":[0.302970427631485517,0.216374160954275335,0.495958559237998808],"hpluv":[297.653919381785329,152.091062924306783,53.6401644756464293],"hsluv":[297.653919381785329,49.3013965401631964,53.6401644756464293]},"#aa66cc":{"lch":[54.4584144535918853,75.6506824974921699,290.576693925999962],"luv":[54.4584144535918853,26.5882539678026326,-70.824363839571447],"rgb":[0.66666666666666663,0.4,0.8],"xyz":[0.322262803075207271,0.224091111131764137,0.597565069908269],"hpluv":[290.576693925999962,176.273563499159138,54.4584144535918853],"hsluv":[290.576693925999962,59.8478295858589107,54.4584144535918853]},"#aa66dd":{"lch":[55.3492064676394619,87.6329338161637423,285.539109011475546],"luv":[55.3492064676394619,23.4765185392287,-84.4297587732275],"rgb":[0.66666666666666663,0.4,0.866666666666666696],"xyz":[0.343781128625840759,0.232698441352017671,0.710894917808275],"hpluv":[285.539109011475546,200.907097340780666,55.3492064676394619],"hsluv":[285.539109011475546,72.9499123576365,55.3492064676394619]},"#aa66ee":{"lch":[56.3097127205812171,99.8160041995885337,281.866291349003632],"luv":[56.3097127205812171,20.525011879724925,-97.6829492885499775],"rgb":[0.66666666666666663,0.4,0.933333333333333348],"xyz":[0.367593308892015525,0.242223313458487716,0.836305733876798185],"hpluv":[281.866291349003632,224.934563930918017,56.3097127205812171],"hsluv":[281.866291349003632,86.3166325899195215,56.3097127205812171]},"#aa66ff":{"lch":[57.3367512293125543,111.956114799504135,279.118878442970129],"luv":[57.3367512293125543,17.7431860168323468,-110.541173283857603],"rgb":[0.66666666666666663,0.4,1],"xyz":[0.39376435730184689,0.252691732822420412,0.97413992216858],"hpluv":[279.118878442970129,247.773048158040382,57.3367512293125543],"hsluv":[279.118878442970129,99.9999999999988631,57.3367512293125543]},"#887700":{"lch":[50.0114915023736586,55.8665567864094825,73.357205010908],"luv":[50.0114915023736586,16.0004093344436384,53.5262465366231766],"rgb":[0.533333333333333326,0.466666666666666674,0],"xyz":[0.167496530942664812,0.184282743437969299,0.0267477295612209565],"hpluv":[73.357205010908,141.749463920516746,50.0114915023736586],"hsluv":[73.357205010908,100.000000000002359,50.0114915023736586]},"#887711":{"lch":[50.0597743565714524,53.993823922257576,73.0031869817398302],"luv":[50.0597743565714524,15.783394234863108,51.6354286142242103],"rgb":[0.533333333333333326,0.466666666666666674,0.0666666666666666657],"xyz":[0.168508196442301944,0.184687409637824151,0.0320758345259765862],"hpluv":[73.0031869817398302,136.865669051735267,50.0597743565714524],"hsluv":[73.0031869817398302,96.4507191106164328,50.0597743565714524]},"#887722":{"lch":[50.1490916772086592,50.5946145563497751,72.2920831328592755],"luv":[50.1490916772086592,15.3890953737340599,48.1974145124374616],"rgb":[0.533333333333333326,0.466666666666666674,0.133333333333333331],"xyz":[0.170383554580778945,0.18543755289321498,0.0419527207219557857],"hpluv":[72.2920831328592755,128.020802989304741,50.1490916772086592],"hsluv":[72.2920831328592755,90.012010730737714,50.1490916772086592]},"#887733":{"lch":[50.2956280558193356,45.1970113165044083,70.9372107781068735],"luv":[50.2956280558193356,14.7615306672340392,42.718462569532349],"rgb":[0.533333333333333326,0.466666666666666674,0.2],"xyz":[0.173471305313236679,0.186672653186198079,0.058214874579566929],"hpluv":[70.9372107781068735,114.029917097227894,50.2956280558193356],"hsluv":[70.9372107781068735,79.7918184760720237,50.2956280558193356]},"#887744":{"lch":[50.5060566549544916,37.8272673959478567,68.4411772520018076],"luv":[50.5060566549544916,13.89986571870684,35.1809023711225777],"rgb":[0.533333333333333326,0.466666666666666674,0.266666666666666663],"xyz":[0.177929300557455566,0.188455851283885673,0.0816936495324535605],"hpluv":[68.4411772520018076,95.0387759917387598,50.5060566549544916],"hsluv":[68.4411772520018076,65.8173762074568316,50.5060566549544916]},"#887755":{"lch":[50.7854328179731453,28.7445471843262688,63.5083223958924279],"luv":[50.7854328179731453,12.8220173934607509,25.7263456945161408],"rgb":[0.533333333333333326,0.466666666666666674,0.333333333333333315],"xyz":[0.183891714596817579,0.190840816899630505,0.113095696806427476],"hpluv":[63.5083223958924279,71.82169329487634,50.7854328179731453],"hsluv":[63.5083223958924279,48.4385690265679756,50.7854328179731453]},"#887766":{"lch":[51.1374932189691549,18.6360854649452037,51.6613245975388082],"luv":[51.1374932189691549,11.5601245482137447,14.6173596072111405],"rgb":[0.533333333333333326,0.466666666666666674,0.4],"xyz":[0.191476710370325737,0.193874815209033796,0.153043341213571438],"hpluv":[51.6613245975388082,46.2439140208651,51.1374932189691549],"hsluv":[51.6613245975388082,28.2492665088726049,51.1374932189691549]},"#887777":{"lch":[51.5648179079599629,10.388802862097231,12.1770506300635812],"luv":[51.5648179079599629,10.1550595801770775,2.19134429758843829],"rgb":[0.533333333333333326,0.466666666666666674,0.466666666666666674],"xyz":[0.20079072339052767,0.197600420417114619,0.202097143119969347],"hpluv":[12.1770506300635812,25.5653264810281158,51.5648179079599629],"hsluv":[12.1770506300635812,5.9907484084339373,51.5648179079599629]},"#887788":{"lch":[52.0689409540354262,14.1414171783799034,307.715012949252923],"luv":[52.0689409540354262,8.65079050214889378,-11.1867557182995974],"rgb":[0.533333333333333326,0.466666666666666674,0.533333333333333326],"xyz":[0.21193114049247147,0.202056587257892217,0.260770006523541542],"hpluv":[307.715012949252923,34.4630346227371902,52.0689409540354262],"hsluv":[307.715012949252923,11.9231603633022036,52.0689409540354262]},"#887799":{"lch":[52.6504441343355154,26.1446518461971777,285.73365348909897],"luv":[52.6504441343355154,7.08953673880610147,-25.1650807467009763],"rgb":[0.533333333333333326,0.466666666666666674,0.6],"xyz":[0.224988104601544198,0.207279372901521375,0.329536684164659421],"hpluv":[285.73365348909897,63.0115482025775862,52.6504441343355154],"hsluv":[285.73365348909897,20.7849368541844512,52.6504441343355154]},"#8877aa":{"lch":[53.3090485775123142,39.8165287152511951,277.951975125090712],"luv":[53.3090485775123142,5.50833871717125412,-39.4336678931764482],"rgb":[0.533333333333333326,0.466666666666666674,0.66666666666666663],"xyz":[0.240045797566796726,0.213302450087622469,0.408840533781658],"hpluv":[277.951975125090712,94.7767450693341118,53.3090485775123142],"hsluv":[277.951975125090712,33.2193336653547036,53.3090485775123142]},"#8877bb":{"lch":[54.043710164283695,53.8841311835491,274.190220195010625],"luv":[54.043710164283695,3.93720379281895738,-53.7400969453883732],"rgb":[0.533333333333333326,0.466666666666666674,0.733333333333333282],"xyz":[0.2571833896741334,0.220157486930557228,0.499098518880299769],"hpluv":[274.190220195010625,126.518797380186697,54.043710164283695],"hsluv":[274.190220195010625,46.027457746288988,54.043710164283695]},"#8877cc":{"lch":[54.8527197178713095,67.9360625264533695,272.023364737824068],"luv":[54.8527197178713095,2.39862107173988059,-67.8937052203839215],"rgb":[0.533333333333333326,0.466666666666666674,0.8],"xyz":[0.276475765117855099,0.22787443710804603,0.600705029550569924],"hpluv":[272.023364737824068,157.159823716941304,54.8527197178713095],"hsluv":[272.023364737824068,59.1220253911564413,54.8527197178713095]},"#8877dd":{"lch":[55.7338064566187228,81.7667290312133,270.636302714775695],"luv":[55.7338064566187228,0.90804807322034331,-81.7616867803042595],"rgb":[0.533333333333333326,0.466666666666666674,0.866666666666666696],"xyz":[0.297994090668488698,0.236481767328299564,0.714034877450575922],"hpluv":[270.636302714775695,186.164661983392193,55.7338064566187228],"hsluv":[270.636302714775695,72.4679508704990809,55.7338064566187228]},"#8877ee":{"lch":[56.6842419440431939,95.2620356561917419,269.684203716511661],"luv":[56.6842419440431939,-0.525051666249150784,-95.2605886928551229],"rgb":[0.533333333333333326,0.466666666666666674,0.933333333333333348],"xyz":[0.321806270934663408,0.246006639434769608,0.839445693519099145],"hpluv":[269.684203716511661,213.253830698535069,56.6842419440431939],"hsluv":[269.684203716511661,86.076772525698,56.6842419440431939]},"#8877ff":{"lch":[57.7009414002340577,108.362660778640901,268.997474997449615],"luv":[57.7009414002340577,-1.89596396936046907,-108.346073171359961],"rgb":[0.533333333333333326,0.466666666666666674,1],"xyz":[0.347977319344494829,0.256475058798702304,0.977279881810881],"hpluv":[268.997474997449615,238.306609639193027,57.7009414002340577],"hsluv":[268.997474997449615,99.9999999999988631,57.7009414002340577]},"#ffff00":{"lch":[97.1385593417967357,107.085608846920664,85.8743202181747307],"luv":[97.1385593417967357,7.70421917727499928,106.808111250898],"rgb":[1,1,0],"xyz":[0.76997513864982,0.92780768463926,0.138525598510210984],"hpluv":[85.8743202181747307,1784.23591835690763,97.1385593417967357],"hsluv":[85.8743202181747307,100.000000000072717,97.1385593417967357]},"#ffff11":{"lch":[97.1550055288865337,106.340968495662651,85.8743202181747307],"luv":[97.1550055288865337,7.65064640931757278,106.065400532478591],"rgb":[1,1,0.0666666666666666657],"xyz":[0.770986804149457194,0.928212350839114908,0.143853703474966621],"hpluv":[85.8743202181747307,1782.29032599077573,97.1550055288865337],"hsluv":[85.8743202181747307,100.000000000072447,97.1550055288865337]},"#ffff22":{"lch":[97.1854797367251564,104.966044999604463,85.8743202181747],"luv":[97.1854797367251564,7.5517282439387623,104.694039961158666],"rgb":[1,1,0.133333333333333331],"xyz":[0.77286216228793414,0.928962494094505709,0.1537305896709458],"hpluv":[85.8743202181747,1778.69938503976459,97.1854797367251564],"hsluv":[85.8743202181747,100.00000000007401,97.1854797367251564]},"#ffff33":{"lch":[97.2356193677236291,102.717517786777336,85.8743202181746312],"luv":[97.2356193677236291,7.38995910744871409,102.451339496695468],"rgb":[1,1,0.2],"xyz":[0.775949913020392,0.930197594387488835,0.16999274352855695],"hpluv":[85.8743202181746312,1772.83090468185333,97.2356193677236291],"hsluv":[85.8743202181746312,100.000000000075445,97.2356193677236291]},"#ffff44":{"lch":[97.3079311184623776,99.5042093292491,85.874320218174546],"luv":[97.3079311184623776,7.15877927938833114,99.2463578851537704],"rgb":[1,1,0.266666666666666663],"xyz":[0.780407908264610817,0.931980792485176401,0.193471518481443588],"hpluv":[85.874320218174546,1764.45330998562531,97.3079311184623776],"hsluv":[85.874320218174546,100.000000000077918,97.3079311184623776]},"#ffff55":{"lch":[97.4045015397841212,95.2663481722239283,85.8743202181744323],"luv":[97.4045015397841212,6.8538885331141568,95.0194785612246875],"rgb":[1,1,0.333333333333333315],"xyz":[0.786370322303972746,0.934365758100921151,0.224873565755417504],"hpluv":[85.8743202181744323,1753.42077174454698,97.4045015397841212],"hsluv":[85.8743202181744323,100.000000000080163,97.4045015397841212]},"#ffff66":{"lch":[97.5271149532436539,89.9715947326486258,85.8743202181742333],"luv":[97.5271149532436539,6.47296021391862286,89.7384457454272706],"rgb":[1,1,0.4],"xyz":[0.793955318077481,0.93739975641032447,0.264821210162561438],"hpluv":[85.8743202181742333,1739.66322518688298,97.5271149532436539],"hsluv":[85.8743202181742333,100.000000000084981,97.5271149532436539]},"#ffff77":{"lch":[97.6773170086398608,83.6127156419164663,85.8743202181740202],"luv":[97.6773170086398608,6.01547392080898469,83.3960448134325389],"rgb":[1,1,0.466666666666666674],"xyz":[0.803269331097682837,0.941125361618405321,0.313875012068959403],"hpluv":[85.8743202181740202,1723.18045161093028,97.6773170086398608],"hsluv":[85.8743202181740202,100.00000000009112,97.6773170086398608]},"#ffff88":{"lch":[97.8564527859654589,76.2055692953657342,85.8743202181736791],"luv":[97.8564527859654589,5.48257057790026181,76.0080930657330214],"rgb":[1,1,0.533333333333333326],"xyz":[0.814409748199626748,0.945581528459182863,0.372547875472531542],"hpluv":[85.8743202181736791,1704.03672017478311,97.8564527859654589],"hsluv":[85.8743202181736791,100.000000000099803,97.8564527859654589]},"#ffff99":{"lch":[98.0656913545514612,67.7868897983338741,85.8743202181732102],"luv":[98.0656913545514612,4.87689300155069283,67.6112294162950889],"rgb":[1,1,0.6],"xyz":[0.827466712308699393,0.950804314102812076,0.441314553113649422],"hpluv":[85.8743202181732102,1682.35465810463256,98.0656913545514612],"hsluv":[85.8743202181732102,100.000000000112891,98.0656913545514612]},"#ffffaa":{"lch":[98.3060425431328611,58.4116937234916094,85.8743202181725707],"luv":[98.3060425431328611,4.20239933084915052,58.260327869924204],"rgb":[1,1,0.66666666666666663],"xyz":[0.842524405273952,0.956827391288913143,0.520618402730648078],"hpluv":[85.8743202181725707,1658.30791632356272,98.3060425431328611],"hsluv":[85.8743202181725707,100.000000000127613,98.3060425431328611]},"#ffffbb":{"lch":[98.5783690162300559,48.1503065934375414,85.8743202181715759],"luv":[98.5783690162300559,3.46414909943131732,48.0255317103199246],"rgb":[1,1,0.733333333333333282],"xyz":[0.859661997381288567,0.963682428131847901,0.610876387829289769],"hpluv":[85.8743202181715759,1632.1126639545671,98.5783690162300559],"hsluv":[85.8743202181715759,100.000000000152809,98.5783690162300559]},"#ffffcc":{"lch":[98.8833954570195317,37.0851031688938804,85.8743202181698706],"luv":[98.8833954570195317,2.66806871718659799,36.9890022353654899],"rgb":[1,1,0.8],"xyz":[0.878954372825010322,0.971399378309336758,0.712482898499559925],"hpluv":[85.8743202181698706,1604.018210645404,98.8833954570195317],"hsluv":[85.8743202181698706,100.00000000019709,98.8833954570195317]},"#ffffdd":{"lch":[99.2217159651800245,25.3071072074552177,85.8743202181663889],"luv":[99.2217159651800245,1.82070684164607655,25.2415273271332552],"rgb":[1,1,0.866666666666666696],"xyz":[0.900472698375643921,0.980006708529590265,0.825812746399565922],"hpluv":[85.8743202181663889,1574.29719653830034,99.2217159651800245],"hsluv":[85.8743202181663889,100.000000000286278,99.2217159651800245]},"#ffffee":{"lch":[99.5938003805277248,12.9126149352850259,85.8743202181558161],"luv":[99.5938003805277248,0.928991455386458775,12.8791536733888243],"rgb":[1,1,0.933333333333333348],"xyz":[0.92428487864181863,0.989531580636060282,0.951223562468089145],"hpluv":[85.8743202181558161,1543.23583838085528,99.5938003805277248],"hsluv":[85.8743202181558161,100.000000000556355,99.5938003805277248]},"#ffffff":{"lch":[99.99999999999973,5.29610712429325706e-12,0],"luv":[99.99999999999973,4.97935026544381416e-12,1.80411241501587473e-12],"rgb":[1,1,1],"xyz":[0.95045592705165,0.999999999999993,1.0890577507598711],"hpluv":[0,0,100],"hsluv":[0,0,100]},"#aa7700":{"lch":[53.7507838912622304,69.116848270999057,51.9676330333141223],"luv":[53.7507838912622304,42.5833417137676875,54.4407726194696622],"rgb":[0.66666666666666663,0.466666666666666674,0],"xyz":[0.231737306953434558,0.217406893568523163,0.0297590159367257245],"hpluv":[51.9676330333141223,163.169299961930307,53.7507838912622304],"hsluv":[51.9676330333141223,100.000000000002359,53.7507838912622304]},"#aa7711":{"lch":[53.7940335015026,67.5786316453491906,51.3090056740019378],"luv":[53.7940335015026,42.244752971039,52.7470596476588653],"rgb":[0.66666666666666663,0.466666666666666674,0.0666666666666666657],"xyz":[0.23274897245307169,0.217811559768378016,0.0350871209014813543],"hpluv":[51.3090056740019378,159.40965108533166,53.7940335015026],"hsluv":[51.3090056740019378,97.0120153186068,53.7940335015026]},"#aa7722":{"lch":[53.874065271669366,64.7981771198060272,50.0272013346139],"luv":[53.874065271669366,41.6278947732915157,49.6580520640260588],"rgb":[0.66666666666666663,0.466666666666666674,0.133333333333333331],"xyz":[0.234624330591548691,0.218561703023768844,0.0449640070974605538],"hpluv":[50.0272013346139,152.623836787181489,53.874065271669366],"hsluv":[50.0272013346139,91.5730337049880632,53.874065271669366]},"#aa7733":{"lch":[54.0054384284815,60.4215101577181812,47.7291529037971785],"luv":[54.0054384284815,40.6416884431496115,44.7103125713654705],"rgb":[0.66666666666666663,0.466666666666666674,0.2],"xyz":[0.237712081324006452,0.219796803316751943,0.0612261609550717],"hpluv":[47.7291529037971785,141.968961696257139,54.0054384284815],"hsluv":[47.7291529037971785,82.890357503842381,54.0054384284815]},"#aa7744":{"lch":[54.1942453736720324,54.5501661616690754,43.9413891432679051],"luv":[54.1942453736720324,39.2788485799816911,37.8535689532252277],"rgb":[0.66666666666666663,0.466666666666666674,0.266666666666666663],"xyz":[0.242170076568225312,0.221580001414439537,0.0847049359079583286],"hpluv":[43.9413891432679051,127.726858599491337,54.1942453736720324],"hsluv":[43.9413891432679051,70.9191698542686453,54.1942453736720324]},"#aa7755":{"lch":[54.4451912879813307,47.55778496198932,37.8352816528713],"luv":[54.4451912879813307,37.5600659479400178,29.1716361638701045],"rgb":[0.66666666666666663,0.466666666666666674,0.333333333333333315],"xyz":[0.248132490607587297,0.22396496703018437,0.116106983181932244],"hpluv":[37.8352816528713,110.841250641501858,54.4451912879813307],"hsluv":[37.8352816528713,55.8697139193963039,54.4451912879813307]},"#aa7766":{"lch":[54.7618668139504621,40.2221891209492881,27.9562221290694595],"luv":[54.7618668139504621,35.5285027246357359,18.8560333004309904],"rgb":[0.66666666666666663,0.466666666666666674,0.4],"xyz":[0.255717486381095482,0.226998965339587661,0.156054627589076206],"hpluv":[27.9562221290694595,93.2023334238246264,54.7618668139504621],"hsluv":[27.9562221290694595,38.157009054435612,54.7618668139504621]},"#aa7777":{"lch":[55.1468928183874851,34.0080558607991321,12.1770506300622881],"luv":[55.1468928183874851,33.2428902595124569,7.17343088244713467],"rgb":[0.66666666666666663,0.466666666666666674,0.466666666666666674],"xyz":[0.265031499401297388,0.230724570547668484,0.205108429495474115],"hpluv":[12.1770506300622881,78.2528356679829074,55.1468928183874851],"hsluv":[12.1770506300622881,20.6006796366476941,55.1468928183874851]},"#aa7788":{"lch":[55.6020140468043849,31.2700131437067519,349.739442339375785],"luv":[55.6020140468043849,30.7699397165362107,-5.56996695217490778],"rgb":[0.66666666666666663,0.466666666666666674,0.533333333333333326],"xyz":[0.276171916503241244,0.235180737388446082,0.263781292899046282],"hpluv":[349.739442339375785,71.363619208349732,55.6020140468043849],"hsluv":[349.739442339375785,23.0952607722130772,55.6020140468043849]},"#aa7799":{"lch":[56.1281730235999845,34.0192547961324365,325.92167501088062],"luv":[56.1281730235999845,28.1772086346441704,-19.0618627223027133],"rgb":[0.66666666666666663,0.466666666666666674,0.6],"xyz":[0.289228880612313943,0.240403523032075239,0.332547970540164162],"hpluv":[325.92167501088062,76.9100717288581706,56.1281730235999845],"hsluv":[325.92167501088062,27.5449434619861648,56.1281730235999845]},"#aa77aa":{"lch":[56.7255784680210127,41.7295245342496131,307.715012949246272],"luv":[56.7255784680210127,25.5273831431779215,-33.0106941416859],"rgb":[0.66666666666666663,0.466666666666666674,0.66666666666666663],"xyz":[0.304286573577566499,0.246426600218176334,0.411851820157162818],"hpluv":[307.715012949246272,93.3477446513022642,56.7255784680210127],"hsluv":[307.715012949246272,32.2954766233998285,56.7255784680210127]},"#aa77bb":{"lch":[57.3937746480490176,52.417961949749369,295.873528074876958],"luv":[57.3937746480490176,22.8744713506946127,-47.1635589771554891],"rgb":[0.66666666666666663,0.466666666666666674,0.733333333333333282],"xyz":[0.321424165684903118,0.25328163706111112,0.502109805255804509],"hpluv":[295.873528074876958,115.892323381948941,57.3937746480490176],"hsluv":[295.873528074876958,41.7229864085831679,57.3937746480490176]},"#aa77cc":{"lch":[58.1317139736185,64.5762938068646548,288.286403622885132],"luv":[58.1317139736185,20.2619194712353625,-61.3151885031081],"rgb":[0.66666666666666663,0.466666666666666674,0.8],"xyz":[0.340716541128624872,0.260998587238599922,0.603716315926074665],"hpluv":[288.286403622885132,140.961111207958226,58.1317139736185],"hsluv":[288.286403622885132,55.678510160825347,58.1317139736185]},"#aa77dd":{"lch":[58.9378328182195759,77.3666791836153,283.242275299631103],"luv":[58.9378328182195759,17.7223199365118553,-75.3095108466943088],"rgb":[0.66666666666666663,0.466666666666666674,0.866666666666666696],"xyz":[0.362234866679258416,0.269605917458853428,0.717046163826080662],"hpluv":[283.242275299631103,166.570895286205939,58.9378328182195759],"hsluv":[283.242275299631103,70.0408478070886105,58.9378328182195759]},"#aa77ee":{"lch":[59.8101292792768646,90.3373867943789151,279.736895248195651],"luv":[59.8101292792768646,15.2782276108220607,-89.0360556960445138],"rgb":[0.66666666666666663,0.466666666666666674,0.933333333333333348],"xyz":[0.386047046945433125,0.279130789565323445,0.842456979894603886],"hpluv":[279.736895248195651,191.660275847677298,59.8101292792768646],"hsluv":[279.736895248195651,84.8029379630096685,59.8101292792768646]},"#aa77ff":{"lch":[60.7462409754246551,103.238062985892157,277.202485092995744],"luv":[60.7462409754246551,12.9436026905507742,-102.423438716283115],"rgb":[0.66666666666666663,0.466666666666666674,1],"xyz":[0.412218095355264547,0.289599208929256169,0.980291168186385731],"hpluv":[277.202485092995744,215.655115976047284,60.7462409754246551],"hsluv":[277.202485092995744,99.999999999998721,60.7462409754246551]},"#888800":{"lch":[54.9099926918455452,60.532810441385358,85.8743202181747449],"luv":[54.9099926918455452,4.35500198466006783,60.375948006191166],"rgb":[0.533333333333333326,0.533333333333333326,0],"xyz":[0.189568900667635265,0.228427482887910871,0.0341051861362109063],"hpluv":[85.8743202181747449,139.887458074797593,54.9099926918455452],"hsluv":[85.8743202181747449,100.000000000002331,54.9099926918455452]},"#888811":{"lch":[54.9518410557904673,58.8347385736240369,85.8743202181746739],"luv":[54.9518410557904673,4.23283507550337568,58.6822764576347353],"rgb":[0.533333333333333326,0.533333333333333326,0.0666666666666666657],"xyz":[0.190580566167272397,0.228832149087765724,0.039433291100966536],"hpluv":[85.8743202181746739,135.85978011465275,54.9518410557904673],"hsluv":[85.8743202181746739,97.1207726442580395,54.9518410557904673]},"#888822":{"lch":[55.0292864560463215,55.7361292450240882,85.8743202181745602],"luv":[55.0292864560463215,4.00990721741558787,55.5916967480373643],"rgb":[0.533333333333333326,0.533333333333333326,0.133333333333333331],"xyz":[0.192455924305749398,0.229582292343156552,0.0493101772969457355],"hpluv":[85.8743202181745602,128.523412903997382,55.0292864560463215],"hsluv":[85.8743202181745602,91.8762944675706,55.0292864560463215]},"#888833":{"lch":[55.1564325013520573,50.7686053645684225,85.8743202181742902],"luv":[55.1564325013520573,3.65252126093227947,50.6370455210582335],"rgb":[0.533333333333333326,0.533333333333333326,0.2],"xyz":[0.195543675038207132,0.230817392636139651,0.0655723311545568788],"hpluv":[85.8743202181742902,116.798802852822334,55.1564325013520573],"hsluv":[85.8743202181742902,83.4948353914423,55.1564325013520573]},"#888844":{"lch":[55.3392041906722767,43.8756115710196184,85.8743202181737786],"luv":[55.3392041906722767,3.15660835961073616,43.7619139708837039],"rgb":[0.533333333333333326,0.533333333333333326,0.266666666666666663],"xyz":[0.20000167028242602,0.232600590733827245,0.0890511061074435173],"hpluv":[85.8743202181737786,100.607324583255647,55.3392041906722767],"hsluv":[85.8743202181737786,71.9201892491773833,55.3392041906722767]},"#888855":{"lch":[55.5822005995452173,35.1333862553221152,85.8743202181729],"luv":[55.5822005995452173,2.5276534453655044,35.0423429271758167],"rgb":[0.533333333333333326,0.533333333333333326,0.333333333333333315],"xyz":[0.205964084321788032,0.234985556349572078,0.120453153381417433],"hpluv":[85.8743202181729,80.2090919262666233,55.5822005995452173],"hsluv":[85.8743202181729,57.3383011101545321,55.5822005995452173]},"#888866":{"lch":[55.8889601924437187,24.7258905438507242,85.874320218171],"luv":[55.8889601924437187,1.778891507033598,24.6618168064052092],"rgb":[0.533333333333333326,0.533333333333333326,0.4],"xyz":[0.21354908009529619,0.238019554658975369,0.160400797788561394],"hpluv":[85.874320218171,56.1390732800859524,55.8889601924437187],"hsluv":[85.874320218171,40.1315986813270698,55.8889601924437187]},"#888877":{"lch":[56.2621011123828509,12.9137749110131566,85.8743202181651668],"luv":[56.2621011123828509,0.929074909242871283,12.8803106431998842],"rgb":[0.533333333333333326,0.533333333333333326,0.466666666666666674],"xyz":[0.222863093115498123,0.241745159867056192,0.209454599694959304],"hpluv":[85.8743202181651668,29.1257147579972724,56.2621011123828509],"hsluv":[85.8743202181651668,20.8208192205656601,56.2621011123828509]},"#888888":{"lch":[56.703410756754252,2.95076376078202623e-12,0],"luv":[56.703410756754252,2.78254170310414444e-12,9.82073542272051e-13],"rgb":[0.533333333333333326,0.533333333333333326,0.533333333333333326],"xyz":[0.234003510217441923,0.24620132670783379,0.268127463098531471],"hpluv":[0,6.60335407213460764e-12,56.703410756754252],"hsluv":[0,2.14018342731852893e-12,56.703410756754252]},"#888899":{"lch":[57.2139150634865246,13.7029898302256612,265.874320218188814],"luv":[57.2139150634865246,-0.985854571612734376,-13.6674804207248872],"rgb":[0.533333333333333326,0.533333333333333326,0.6],"xyz":[0.247060474326514651,0.251424112351462947,0.33689414073964935],"hpluv":[265.874320218188814,30.3915601408835876,57.2139150634865246],"hsluv":[265.874320218188814,12.5386286039598396,57.2139150634865246]},"#8888aa":{"lch":[57.7939415002624486,27.9001972781706051,265.874320218182902],"luv":[57.7939415002624486,-2.00726537612613365,-27.8278977623291475],"rgb":[0.533333333333333326,0.533333333333333326,0.66666666666666663],"xyz":[0.262118167291767179,0.25744718953756407,0.416197990356647951],"hpluv":[265.874320218182902,61.2582077856443377,57.7939415002624486],"hsluv":[265.874320218182902,25.8334660761224093,57.7939415002624486]},"#8888bb":{"lch":[58.4431822360017605,42.3326731508362428,265.874320218181083],"luv":[58.4431822360017605,-3.04560244672749647,-42.2229738629578222],"rgb":[0.533333333333333326,0.533333333333333326,0.733333333333333282],"xyz":[0.279255759399103853,0.264302226380498828,0.506455975455289753],"hpluv":[265.874320218181083,91.9138937804104756,58.4431822360017605],"hsluv":[265.874320218181083,39.7348050695490116,58.4431822360017605]},"#8888cc":{"lch":[59.1607600358786812,56.7874726838639603,265.874320218180117],"luv":[59.1607600358786812,-4.0855455816182138,-56.6403157752595448],"rgb":[0.533333333333333326,0.533333333333333326,0.8],"xyz":[0.298548134842825608,0.27201917655798763,0.608062486125559909],"hpluv":[265.874320218180117,121.803038601679276,59.1607600358786812],"hsluv":[265.874320218180117,54.1372084350884322,59.1607600358786812]},"#8888dd":{"lch":[59.9452971965242654,71.1002375720468649,265.874320218179605],"luv":[59.9452971965242654,-5.11527010687093497,-70.9159911059223447],"rgb":[0.533333333333333326,0.533333333333333326,0.866666666666666696],"xyz":[0.320066460393459096,0.280626506778241136,0.721392334025565907],"hpluv":[265.874320218179605,150.506501481916018,59.9452971965242654],"hsluv":[265.874320218179605,68.9826297466640881,59.9452971965242654]},"#8888ee":{"lch":[60.7949865781877747,85.1524606014505,265.874320218179207],"luv":[60.7949865781877747,-6.12625008179143,-84.9317997361231676],"rgb":[0.533333333333333326,0.533333333333333326,0.933333333333333348],"xyz":[0.343878640659633861,0.290151378884711153,0.84680315009408913],"hpluv":[265.874320218179207,177.733282428962553,60.7949865781877747],"hsluv":[265.874320218179207,84.2595641984559194,60.7949865781877747]},"#8888ff":{"lch":[61.7076631467729726,98.8655769196339,265.874320218179],"luv":[61.7076631467729726,-7.11283319838656247,-98.6093804034077408],"rgb":[0.533333333333333326,0.533333333333333326,1],"xyz":[0.370049689069465226,0.300619798248643877,0.984637338385871],"hpluv":[265.874320218179,203.303722842755434,61.7076631467729726],"hsluv":[265.874320218179,99.9999999999986073,61.7076631467729726]},"#aa8800":{"lch":[58.1840377660698493,67.6904417424552634,64.2288134226940173],"luv":[58.1840377660698493,29.4303340948507071,60.9577832467208225],"rgb":[0.66666666666666663,0.533333333333333326,0],"xyz":[0.253809676678405038,0.261551633018464735,0.0371164725117156744],"hpluv":[64.2288134226940173,147.625988392398114,58.1840377660698493],"hsluv":[64.2288134226940173,100.000000000002373,58.1840377660698493]},"#aa8811":{"lch":[58.2222766199063955,66.2027965316335809,63.8264905250604926],"luv":[58.2222766199063955,29.2014551434886229,59.4145208354969085],"rgb":[0.66666666666666663,0.533333333333333326,0.0666666666666666657],"xyz":[0.254821342178042143,0.261956299218319588,0.0424445774764713041],"hpluv":[63.8264905250604926,144.286759049554206,58.2222766199063955],"hsluv":[63.8264905250604926,97.5015111084285877,58.2222766199063955]},"#aa8822":{"lch":[58.2930572278629,63.4916551128893474,63.0419050459203],"luv":[58.2930572278629,28.7832252220538578,56.5925455761676304],"rgb":[0.66666666666666663,0.533333333333333326,0.133333333333333331],"xyz":[0.2566967003165192,0.262706442473710389,0.0523214636724505036],"hpluv":[63.0419050459203,138.209896631886636,58.2930572278629],"hsluv":[63.0419050459203,92.9399945222758106,58.2930572278629]},"#aa8833":{"lch":[58.4093035212624585,59.1585039849960737,61.628621374019076],"luv":[58.4093035212624585,28.1112178941065629,52.0527426967384628],"rgb":[0.66666666666666663,0.533333333333333326,0.2],"xyz":[0.259784451048976905,0.263941542766693515,0.0685836175300616468],"hpluv":[61.628621374019076,128.521114132395894,58.4093035212624585],"hsluv":[61.628621374019076,85.621599137751673,58.4093035212624585]},"#aa8844":{"lch":[58.5764981609594315,53.1878639814319953,59.2736460930020499],"luv":[58.5764981609594315,27.1757201885359478,45.7212106919940453],"rgb":[0.66666666666666663,0.533333333333333326,0.266666666666666663],"xyz":[0.264242446293195821,0.265724740864381082,0.0920623924829482854],"hpluv":[59.2736460930020499,115.22015920662389,58.5764981609594315],"hsluv":[59.2736460930020499,75.4571473133808581,58.5764981609594315]},"#aa8855":{"lch":[58.7989500318507083,45.736161182952344,55.3795129841665315],"luv":[58.7989500318507083,25.9844527075537677,37.6378088793954433],"rgb":[0.66666666666666663,0.533333333333333326,0.333333333333333315],"xyz":[0.270204860332557806,0.268109706480125942,0.123464439756922201],"hpluv":[55.3795129841665315,98.7027982667044483,58.7989500318507083],"hsluv":[55.3795129841665315,62.5553187882680319,58.7989500318507083]},"#aa8866":{"lch":[59.0800404303715112,37.1980666756204,48.681601920545944],"luv":[59.0800404303715112,24.5597583059268025,27.9376884576795419],"rgb":[0.66666666666666663,0.533333333333333326,0.4],"xyz":[0.277789856106066,0.271143704789529261,0.163412084164066163],"hpluv":[48.681601920545944,79.8948730878687883,59.0800404303715112],"hsluv":[48.681601920545944,47.1909371341698645,59.0800404303715112]},"#aa8877":{"lch":[59.4223523155875881,28.4467220316745042,36.2691810942760355],"luv":[59.4223523155875881,22.9350730451589406,16.8285001934390941],"rgb":[0.66666666666666663,0.533333333333333326,0.466666666666666674],"xyz":[0.287103869126267897,0.274869309997610056,0.212465886070464072],"hpluv":[36.2691810942760355,60.7465636644032,59.4223523155875881],"hsluv":[36.2691810942760355,29.7643761063162415,59.4223523155875881]},"#aa8888":{"lch":[59.8277504540149323,21.6376696880998622,12.1770506300627677],"luv":[59.8277504540149323,21.1508320810015036,4.56410471095846049],"rgb":[0.66666666666666663,0.533333333333333326,0.533333333333333326],"xyz":[0.298244286228211697,0.279325476838387654,0.271138749474036211],"hpluv":[12.1770506300627677,45.8930730764781174,59.8277504540149323],"hsluv":[12.1770506300627677,15.9793094134510145,59.8277504540149323]},"#aa8899":{"lch":[60.2974403890441693,21.076671863141442,335.972081494736813],"luv":[60.2974403890441693,19.2503183610400761,-8.58203587880762697],"rgb":[0.66666666666666663,0.533333333333333326,0.6],"xyz":[0.311301250337284396,0.284548262482016812,0.339905427115154146],"hpluv":[335.972081494736813,44.3549898137704872,60.2974403890441693],"hsluv":[335.972081494736813,18.3674189175529285,60.2974403890441693]},"#aa88aa":{"lch":[60.8320193568852119,28.2409959286201691,307.715012949248376],"luv":[60.8320193568852119,17.2759870010776844,-22.3404145928166677],"rgb":[0.66666666666666663,0.533333333333333326,0.66666666666666663],"xyz":[0.326358943302536952,0.290571339668117934,0.419209276732152747],"hpluv":[307.715012949248376,58.9097393716334068,60.8320193568852119],"hsluv":[307.715012949248376,20.7885743891348937,60.8320193568852119]},"#aa88bb":{"lch":[61.4315255818646904,39.5305448534603343,292.718173175904553],"luv":[61.4315255818646904,15.2666424694527869,-36.4635928581008],"rgb":[0.66666666666666663,0.533333333333333326,0.733333333333333282],"xyz":[0.343496535409873627,0.297426376511052692,0.509467261830794493],"hpluv":[292.718173175904553,81.6546320542196185,61.4315255818646904],"hsluv":[292.718173175904553,35.3775178816614826,61.4315255818646904]},"#aa88cc":{"lch":[62.0954889075932783,52.4408733717370339,284.64164926901276],"luv":[62.0954889075932783,13.2556230251873277,-50.737891739849438],"rgb":[0.66666666666666663,0.533333333333333326,0.8],"xyz":[0.362788910853595326,0.305143326688541494,0.611073772501064649],"hpluv":[284.64164926901276,107.164068097081099,62.0954889075932783],"hsluv":[284.64164926901276,50.6369924233865092,62.0954889075932783]},"#aa88dd":{"lch":[62.8229837406334894,65.9589053782665644,279.83800714750987],"luv":[62.8229837406334894,11.2699451472508105,-64.988964717689413],"rgb":[0.66666666666666663,0.533333333333333326,0.866666666666666696],"xyz":[0.384307236404228925,0.313750656908795,0.724403620401070647],"hpluv":[279.83800714750987,133.227600809414753,62.8229837406334894],"hsluv":[279.83800714750987,66.50088929558828,62.8229837406334894]},"#aa88ee":{"lch":[63.6126841134072,79.6308922643202237,276.728713669089302],"luv":[63.6126841134072,9.33022805769535,-79.0823990986816767],"rgb":[0.66666666666666663,0.533333333333333326,0.933333333333333348],"xyz":[0.408119416670403634,0.323275529015265,0.84981443646959387],"hpluv":[276.728713669089302,158.846330872338797,63.6126841134072],"hsluv":[276.728713669089302,82.9476730407324112,63.6126841134072]},"#aa88ff":{"lch":[64.4629200033750323,93.219110130063271,274.584640952303687],"luv":[64.4629200033750323,7.4511637136690565,-92.9208407880221756],"rgb":[0.66666666666666663,0.533333333333333326,1],"xyz":[0.434290465080235055,0.333743948379197741,0.987648624761375715],"hpluv":[274.584640952303687,183.499254977583263,64.4629200033750323],"hsluv":[274.584640952303687,99.999999999998451,64.4629200033750323]},"#889900":{"lch":[59.9037942457991477,67.5360782410098892,95.4734085527772578],"luv":[59.9037942457991477,-6.44184579214223074,67.2281524881211396],"rgb":[0.533333333333333326,0.6,0],"xyz":[0.215438501120102766,0.280166683792846538,0.0427283862870331613],"hpluv":[95.4734085527772578,143.060860652479761,59.9037942457991477],"hsluv":[95.4734085527772578,100.000000000002359,59.9037942457991477]},"#889911":{"lch":[59.9403212197486,66.0293008977046867,95.6505578181906628],"luv":[59.9403212197486,-6.50131434341495318,65.7084582747741166],"rgb":[0.533333333333333326,0.6,0.0666666666666666657],"xyz":[0.216450166619739898,0.280571349992701391,0.048056491251788791],"hpluv":[95.6505578181906628,139.783837686775883,59.9403212197486],"hsluv":[95.6505578181906628,97.642419329775592,59.9403212197486]},"#889922":{"lch":[60.0079397017320275,63.2737960078627495,95.9966659299995655],"luv":[60.0079397017320275,-6.6102508868722456,62.9275602932231308],"rgb":[0.533333333333333326,0.6,0.133333333333333331],"xyz":[0.218325524758216899,0.281321493248092191,0.0579333774477679905],"hpluv":[95.9966659299995655,133.799503075015934,60.0079397017320275],"hsluv":[95.9966659299995655,93.3344688530494864,60.0079397017320275]},"#889933":{"lch":[60.1190111745068521,58.8410136656902196,96.6225595920901696],"luv":[60.1190111745068521,-6.78603238946032139,58.4483930798370039],"rgb":[0.533333333333333326,0.6,0.2],"xyz":[0.221413275490674633,0.282556593541075318,0.0741955313053791338],"hpluv":[96.6225595920901696,124.196009967059197,60.1190111745068521],"hsluv":[96.6225595920901696,86.4129060235578,60.1190111745068521]},"#889944":{"lch":[60.2788030378330859,52.6619605596060651,97.6740140778758104],"luv":[60.2788030378330859,-7.03230554676784081,52.1903129773946],"rgb":[0.533333333333333326,0.6,0.266666666666666663],"xyz":[0.22587127073489352,0.284339791638762884,0.0976743062582657723],"hpluv":[97.6740140778758104,110.859197411007315,60.2788030378330859],"hsluv":[97.6740140778758104,76.7791514013919,60.2788030378330859]},"#889955":{"lch":[60.491478208304315,44.7890969420590679,99.4433583384469557],"luv":[60.491478208304315,-7.34865909500015935,44.1821277711999301],"rgb":[0.533333333333333326,0.6,0.333333333333333315],"xyz":[0.231833684774255533,0.286724757254507745,0.129076353532239674],"hpluv":[99.4433583384469557,93.954467458231008,60.491478208304315],"hsluv":[99.4433583384469557,64.5165507795669555,60.491478208304315]},"#889966":{"lch":[60.7603321241253269,35.3998784596055387,102.614945913325585],"luv":[60.7603321241253269,-7.73125582812020351,34.5453191948634597],"rgb":[0.533333333333333326,0.6,0.4],"xyz":[0.23941868054776369,0.289758755563911063,0.169023997939383636],"hpluv":[102.614945913325585,73.9300429237522394,60.7603321241253269],"hsluv":[102.614945913325585,49.862982431557576,60.7603321241253269]},"#889977":{"lch":[61.0879169406466644,24.8557994274465628,109.198389045907604],"luv":[61.0879169406466644,-8.17358342252196,23.4734594640953418],"rgb":[0.533333333333333326,0.6,0.466666666666666674],"xyz":[0.248732693567965624,0.293484360771991859,0.218077799845781545],"hpluv":[109.198389045907604,51.6311437806165543,61.0879169406466644],"hsluv":[109.198389045907604,33.1759220302134779,61.0879169406466644]},"#889988":{"lch":[61.4761176658877702,14.1684419896747276,127.715012949229816],"luv":[61.4761176658877702,-8.66732250724137,11.2081340539023326],"rgb":[0.533333333333333326,0.6,0.533333333333333326],"xyz":[0.259873110669909424,0.297940527612769457,0.276750663249353712],"hpluv":[127.715012949229816,29.2452265306994263,61.4761176658877702],"hsluv":[127.715012949229816,14.8910328511789984,61.4761176658877702]},"#889999":{"lch":[61.9262069462763094,9.41507553536713537,192.177050630058915],"luv":[61.9262069462763094,-9.20324067004397861,-1.98595279549067061],"rgb":[0.533333333333333326,0.6,0.6],"xyz":[0.272930074778982124,0.303163313256398614,0.345517340890471647],"hpluv":[192.177050630058915,19.2925065058214678,61.9262069462763094],"hsluv":[192.177050630058915,19.2169899754877207,61.9262069462763094]},"#8899aa":{"lch":[62.4388911462841207,18.6148502867865133,238.334617604481764],"luv":[62.4388911462841207,-9.77200531832159669,-15.8436284751369474],"rgb":[0.533333333333333326,0.6,0.66666666666666663],"xyz":[0.28798776774423468,0.309186390442499737,0.424821190507470248],"hpluv":[238.334617604481764,37.8306403353000036,62.4388911462841207],"hsluv":[238.334617604481764,23.6900457250072343,62.4388911462841207]},"#8899bb":{"lch":[63.0143540484962,31.8509402607766567,251.009167860858838],"luv":[63.0143540484962,-10.3648329189902189,-30.1173145226625429],"rgb":[0.533333333333333326,0.6,0.733333333333333282],"xyz":[0.305125359851571354,0.316041427285434495,0.515079175606111939],"hpluv":[251.009167860858838,64.1389868285616132,63.0143540484962],"hsluv":[251.009167860858838,32.2218698989210282,63.0143540484962]},"#8899cc":{"lch":[63.6523012354060143,45.9199042321153357,256.173658840600297],"luv":[63.6523012354060143,-10.9739341944061159,-44.5893526863026821],"rgb":[0.533333333333333326,0.6,0.8],"xyz":[0.324417735295293053,0.323758377462923297,0.616685686276382095],"hpluv":[256.173658840600297,91.543221315317254,63.6523012354060143],"hsluv":[256.173658840600297,48.1563502503440546,63.6523012354060143]},"#8899dd":{"lch":[64.3520063546654,60.2052734177332738,258.898139298145679],"luv":[64.3520063546654,-11.5927562398827622,-59.0786166905309358],"rgb":[0.533333333333333326,0.6,0.866666666666666696],"xyz":[0.345936060845926652,0.332365707683176803,0.730015534176388092],"hpluv":[258.898139298145679,118.716686841439611,64.3520063546654],"hsluv":[258.898139298145679,64.7736147673059435,64.3520063546654]},"#8899ee":{"lch":[65.1123593572591091,74.4513212160393465,260.556144021857],"luv":[65.1123593572591091,-12.2160522582942246,-73.442271874149526],"rgb":[0.533333333333333326,0.6,0.933333333333333348],"xyz":[0.369748241112101361,0.34189057978964682,0.855426350244911315],"hpluv":[260.556144021857,145.093615685215838,65.1123593572591091],"hsluv":[260.556144021857,82.0482136329290626,65.1123593572591091]},"#8899ff":{"lch":[65.9319161385595862,88.5102230179824829,261.65889869963604],"luv":[65.9319161385595862,-12.8398242875011679,-87.5739601191992],"rgb":[0.533333333333333326,0.6,1],"xyz":[0.395919289521932782,0.352358999153579544,0.993260538536693161],"hpluv":[261.65889869963604,170.348009793708229,65.9319161385595862],"hsluv":[261.65889869963604,99.9999999999983373,65.9319161385595862]},"#aa9900":{"lch":[62.7844580943873609,69.6780489210530618,75.8779002673010297],"luv":[62.7844580943873609,17.0006834702273615,67.5722373685362072],"rgb":[0.66666666666666663,0.6,0],"xyz":[0.279679277130872483,0.313290833923400402,0.0457396726625379293],"hpluv":[75.8779002673010297,140.82610179048271,62.7844580943873609],"hsluv":[75.8779002673010297,100.000000000002217,62.7844580943873609]},"#aa9911":{"lch":[62.8183644916567232,68.2796688580209548,75.7117398414304],"luv":[62.8183644916567232,16.8514534980901978,66.1675274916835576],"rgb":[0.66666666666666663,0.6,0.0666666666666666657],"xyz":[0.280690942630509588,0.313695500123255255,0.0510677776272935591],"hpluv":[75.7117398414304,137.925354096716262,62.8183644916567232],"hsluv":[75.7117398414304,97.9039601429264792,62.8183644916567232]},"#aa9922":{"lch":[62.8811408657966098,65.7186920323652259,75.3888160486312415],"luv":[62.8811408657966098,16.5780820569936154,63.5933461751813311],"rgb":[0.66666666666666663,0.6,0.133333333333333331],"xyz":[0.282566300768986645,0.314445643378646056,0.0609446638232727586],"hpluv":[75.3888160486312415,132.619634057532949,62.8811408657966098],"hsluv":[75.3888160486312415,94.0678156084237287,62.8811408657966098]},"#aa9933":{"lch":[62.984284118013818,61.5887344728930373,74.8105333616033477],"luv":[62.984284118013818,16.1369729495721401,59.4371122952427768],"rgb":[0.66666666666666663,0.6,0.2],"xyz":[0.28565405150144435,0.315680743671629183,0.0772068176808839],"hpluv":[74.8105333616033477,124.08189266143988,62.984284118013818],"hsluv":[74.8105333616033477,87.8877322232676335,62.984284118013818]},"#aa9944":{"lch":[63.1327254984845325,55.810649390305,73.8551623649617284],"luv":[63.1327254984845325,15.5190685808424753,53.6095802609072862],"rgb":[0.66666666666666663,0.6,0.266666666666666663],"xyz":[0.290112046745663266,0.317463941769316749,0.10068559263377054],"hpluv":[73.8551623649617284,112.176494362550613,63.1327254984845325],"hsluv":[73.8551623649617284,79.2518346201028407,63.1327254984845325]},"#aa9955":{"lch":[63.330394330910508,48.4114193249398497,72.2912631539462893],"luv":[63.330394330910508,14.725704471349518,46.1174495053405593],"rgb":[0.66666666666666663,0.6,0.333333333333333315],"xyz":[0.296074460785025251,0.319848907385061609,0.132087639907744442],"hpluv":[72.2912631539462893,97.0007181904759,63.330394330910508],"hsluv":[72.2912631539462893,68.2014618333714253,63.330394330910508]},"#aa9966":{"lch":[63.5804407621292285,39.5252766283835086,69.6158026784446662],"luv":[63.5804407621292285,13.7671883687516523,37.0501284339143524],"rgb":[0.66666666666666663,0.6,0.4],"xyz":[0.303659456558533436,0.322882905694464928,0.172035284314888404],"hpluv":[69.6158026784446662,78.8843243984218105,63.5804407621292285],"hsluv":[69.6158026784446662,54.9106459007729129,63.5804407621292285]},"#aa9977":{"lch":[63.8853523521207762,29.4269320044373437,64.5162682653354835],"luv":[63.8853523521207762,12.6610788645847681,26.563911782313685],"rgb":[0.66666666666666663,0.6,0.466666666666666674],"xyz":[0.312973469578735342,0.326608510902545723,0.221089086221286313],"hpluv":[64.5162682653354835,58.4497984351079,63.8853523521207762],"hsluv":[64.5162682653354835,39.6604297676390303,63.8853523521207762]},"#aa9988":{"lch":[64.2470245358341288,18.7498828458428441,52.4386898809200943],"luv":[64.2470245358341288,11.4301164327749429,14.8630597477787205],"rgb":[0.66666666666666663,0.6,0.533333333333333326],"xyz":[0.324113886680679142,0.331064677743323321,0.279761949624858508],"hpluv":[52.4386898809200943,37.0326564248891472,64.2470245358341288],"hsluv":[52.4386898809200943,22.8076315183033955,64.2470245358341288]},"#aa9999":{"lch":[64.6668097656484,10.3324715423982241,12.1770506300641514],"luv":[64.6668097656484,10.0999956892392,2.17946214737001709],"rgb":[0.66666666666666663,0.6,0.6],"xyz":[0.337170850789751841,0.336287463386952479,0.348528627265976387],"hpluv":[12.1770506300641514,20.2750581327120152,64.6668097656484],"hsluv":[12.1770506300641514,9.39861318597140283,64.6668097656484]},"#aa99aa":{"lch":[65.1455571833188,14.2173372068657535,307.715012949255367],"luv":[65.1455571833188,8.69723339065650514,-11.2468132643064216],"rgb":[0.66666666666666663,0.6,0.66666666666666663],"xyz":[0.352228543755004397,0.342310540573053601,0.427832476882975],"hpluv":[307.715012949255367,27.6931773999722353,65.1455571833188],"hsluv":[307.715012949255367,11.6506794595111955,65.1455571833188]},"#aa99bb":{"lch":[65.6836488991384186,26.2061362707181722,286.054514249721478],"luv":[65.6836488991384186,7.24735484809431529,-25.1840708771488],"rgb":[0.66666666666666663,0.6,0.733333333333333282],"xyz":[0.369366135862341072,0.34916557741598836,0.51809046198161679],"hpluv":[286.054514249721478,50.6273330900370553,65.6836488991384186],"hsluv":[286.054514249721478,27.5721610485895,65.6836488991384186]},"#aa99cc":{"lch":[66.2810360009151651,39.8419200501833544,278.33213307587846],"luv":[66.2810360009151651,5.77353773601307907,-39.4213756139487757],"rgb":[0.66666666666666663,0.6,0.8],"xyz":[0.388658511306062771,0.356882527593477161,0.619696972651887],"hpluv":[278.33213307587846,76.2764194246967,66.2810360009151651],"hsluv":[278.33213307587846,44.416066179202609,66.2810360009151651]},"#aa99dd":{"lch":[66.937275739096421,53.9476968390211695,274.567192844176702],"luv":[66.937275739096421,4.29575598209881182,-53.7763932853180151],"rgb":[0.66666666666666663,0.6,0.866666666666666696],"xyz":[0.41017683685669637,0.365489857813730668,0.733026820551892944],"hpluv":[274.567192844176702,102.269045280662851,66.937275739096421],"hsluv":[274.567192844176702,62.1125641321495365,66.937275739096421]},"#aa99ee":{"lch":[67.6515703211096309,68.1578213039093299,272.380002942583644],"luv":[67.6515703211096309,2.83038594926376286,-68.0990273078393358],"rgb":[0.66666666666666663,0.6,0.933333333333333348],"xyz":[0.433989017122871079,0.375014729920200685,0.858437636620416167],"hpluv":[272.380002942583644,127.843056183363871,67.6515703211096309],"hsluv":[272.380002942583644,80.63443597048024,67.6515703211096309]},"#aa99ff":{"lch":[68.4228071241374778,82.2834014236355387,270.968064044661787],"luv":[68.4228071241374778,1.39018638405132466,-82.2716569157370543],"rgb":[0.66666666666666663,0.6,1],"xyz":[0.4601600655327025,0.385483149284133408,0.996271824912198],"hpluv":[270.968064044661787,152.598644218259693,68.4228071241374778],"hsluv":[270.968064044661787,99.9999999999981668,68.4228071241374778]},"#770000":{"lch":[23.4140868272264697,78.7423116347599432,12.177050630061796],"luv":[23.4140868272264697,76.9706458719381317,16.6093743302492847],"rgb":[0.466666666666666674,0,0],"xyz":[0.0760757904266185919,0.0392265794387260461,0.00356605267624767169],"hpluv":[12.177050630061796,426.746789183125429,23.4140868272264697],"hsluv":[12.177050630061796,100.000000000002359,23.4140868272264697]},"#770011":{"lch":[23.5491569362977273,75.7570426868466456,9.89164947332394462],"luv":[23.5491569362977273,74.6308667748156864,13.0139633123970793],"rgb":[0.466666666666666674,0,0.0666666666666666657],"xyz":[0.0770874559262557102,0.0396312456385809,0.00889415764100330228],"hpluv":[9.89164947332394462,408.213135586655085,23.5491569362977273],"hsluv":[9.89164947332394462,99.9999999999965183,23.5491569362977273]},"#770022":{"lch":[23.7971287372198219,70.9964864167640854,5.53723409440817704],"luv":[23.7971287372198219,70.6651956613751224,6.85063542055327357],"rgb":[0.466666666666666674,0,0.133333333333333331],"xyz":[0.0789628140647327392,0.0403813888939717203,0.0187710438369825],"hpluv":[5.53723409440817704,378.574731225432288,23.7971287372198219],"hsluv":[5.53723409440817704,99.9999999999967741,23.7971287372198219]},"#770033":{"lch":[24.198804347572846,65.0463245726941182,358.140059561726389],"luv":[24.198804347572846,65.0120550988218895,-2.11116845467566527],"rgb":[0.466666666666666674,0,0.2],"xyz":[0.0820505647971904728,0.0416164891869548331,0.0350331976945936416],"hpluv":[358.140059561726389,341.089366306392606,24.198804347572846],"hsluv":[358.140059561726389,99.9999999999971578,24.198804347572846]},"#770044":{"lch":[24.764944554878376,59.7650645123016702,347.391874641304071],"luv":[24.764944554878376,58.3238787679626398,-13.0456161839744915],"rgb":[0.466666666666666674,0,0.266666666666666663],"xyz":[0.0865085600414093464,0.0433996872846424062,0.0585119726474802801],"hpluv":[347.391874641304071,306.231145677972847,24.764944554878376],"hsluv":[347.391874641304071,99.9999999999975557,24.764944554878376]},"#770055":{"lch":[25.4983947844981387,57.0853266397623571,333.997796644431901],"luv":[25.4983947844981387,51.3069893372134587,-25.0265331742018731],"rgb":[0.466666666666666674,0,0.333333333333333315],"xyz":[0.0924709740807713454,0.0457846529003872391,0.0899140199214541885],"hpluv":[333.997796644431901,284.086748448009075,25.4983947844981387],"hsluv":[333.997796644431901,99.999999999998,25.4983947844981387]},"#770066":{"lch":[26.3955149445472088,58.0812929265372375,320.022905340944305],"luv":[26.3955149445472088,44.5077732621065394,-37.3161453966930949],"rgb":[0.466666666666666674,0,0.4],"xyz":[0.100055969854279517,0.0488186512097905506,0.129861664328598164],"hpluv":[320.022905340944305,279.219318659546161,26.3955149445472088],"hsluv":[320.022905340944305,99.9999999999984,26.3955149445472088]},"#770077":{"lch":[27.4476614837194361,62.5213221502200156,307.715012949243601],"luv":[27.4476614837194361,38.2464397320582776,-49.4583215569799322],"rgb":[0.466666666666666674,0,0.466666666666666674],"xyz":[0.109369982874481436,0.052544256417871367,0.178915466234996073],"hpluv":[307.715012949243601,289.04278373048345,27.4476614837194361],"hsluv":[307.715012949243601,99.9999999999988631,27.4476614837194361]},"#770088":{"lch":[28.6427236217895711,69.3985842918787341,298.067280282401043],"luv":[28.6427236217895711,32.6525926425722872,-61.2370124633397808],"rgb":[0.466666666666666674,0,0.533333333333333326],"xyz":[0.120510399976425264,0.0570004232586489579,0.237588329638568241],"hpluv":[298.067280282401043,307.450798390810235,28.6427236217895711],"hsluv":[298.067280282401043,99.9999999999991473,28.6427236217895711]},"#770099":{"lch":[29.9665727349335924,77.70857748467688,290.909274437861086],"luv":[29.9665727349335924,27.7333531267432782,-72.5912125469701],"rgb":[0.466666666666666674,0,0.6],"xyz":[0.133567364085497964,0.0622232089022781223,0.30635500727968612],"hpluv":[290.909274437861086,329.057057444315717,29.9665727349335924],"hsluv":[290.909274437861086,99.9999999999993605,29.9665727349335924]},"#7700aa":{"lch":[31.4042918618800115,86.7647813143177,285.668616902051383],"luv":[31.4042918618800115,23.4328336498598695,-83.5405864455078415],"rgb":[0.466666666666666674,0,0.66666666666666663],"xyz":[0.14862505705075052,0.0682462860883792238,0.385658856896684721],"hpluv":[285.668616902051383,350.585377409449279,31.4042918618800115],"hsluv":[285.668616902051383,99.9999999999996447,31.4042918618800115]},"#7700bb":{"lch":[32.9411141237069387,96.170393631615,281.802895608829544],"luv":[32.9411141237069387,19.6712233173334745,-94.1370680681067853],"rgb":[0.466666666666666674,0,0.733333333333333282],"xyz":[0.165762649158087166,0.0751013229313139824,0.475916841995326467],"hpluv":[281.802895608829544,370.460950364720645,32.9411141237069387],"hsluv":[281.802895608829544,99.9999999999998295,32.9411141237069387]},"#7700cc":{"lch":[34.5630635499026226,105.713400517707811,278.906152205018032],"luv":[34.5630635499026226,16.3661753747823724,-104.438840249301762],"rgb":[0.466666666666666674,0,0.8],"xyz":[0.185055024601808893,0.082818273108802784,0.577523352665596623],"hpluv":[278.906152205018032,388.112061604616713,34.5630635499026226],"hsluv":[278.906152205018032,99.9999999999998721,34.5630635499026226]},"#7700dd":{"lch":[36.2573361534597964,115.285120662302717,276.696107756350386],"luv":[36.2573361534597964,13.4426218553338188,-114.498711624961956],"rgb":[0.466666666666666674,0,0.866666666666666696],"xyz":[0.206573350152442436,0.091425603329056318,0.690853200565602621],"hpluv":[276.696107756350386,403.475057258468723,36.2573361534597964],"hsluv":[276.696107756350386,100.000000000000156,36.2573361534597964]},"#7700ee":{"lch":[38.012479203832,124.831519574090535,274.979891409884715],"luv":[38.012479203832,10.8361388436516783,-124.360308676593633],"rgb":[0.466666666666666674,0,0.933333333333333348],"xyz":[0.230385530418617201,0.100950475435526349,0.816264016634125844],"hpluv":[274.979891409884715,416.713325299391272,38.012479203832],"hsluv":[274.979891409884715,100.000000000000156,38.012479203832]},"#7700ff":{"lch":[39.8184284160989037,134.326708962856742,273.625091115001112],"luv":[39.8184284160989037,8.49315165227522861,-134.057939398617776],"rgb":[0.466666666666666674,0,1],"xyz":[0.256556578828448567,0.111418894799459045,0.954098204925907689],"hpluv":[273.625091115001112,428.072753406140123,39.8184284160989037],"hsluv":[273.625091115001112,100.000000000000313,39.8184284160989037]},"#771100":{"lch":[24.7134353555624457,74.5310598854495794,14.479461840222152],"luv":[24.7134353555624457,72.1637543524892351,18.6352205622059],"rgb":[0.466666666666666674,0.0666666666666666657,0],"xyz":[0.078080190687547,0.0432353799605829231,0.00423418609655712257],"hpluv":[14.479461840222152,382.686818993669249,24.7134353555624457],"hsluv":[14.479461840222152,100.000000000002174,24.7134353555624457]},"#771111":{"lch":[24.8400617115613187,71.7342088143168723,12.1770506300618244],"luv":[24.8400617115613187,70.1202221387804912,15.1311321924069837],"rgb":[0.466666666666666674,0.0666666666666666657,0.0666666666666666657],"xyz":[0.0790918561871841175,0.043640046160437776,0.00956229106131275403],"hpluv":[12.1770506300618244,366.448517223619376,24.8400617115613187],"hsluv":[12.1770506300618244,85.8702458957174173,24.8400617115613187]},"#771122":{"lch":[25.0727380413203562,67.2476760425649616,7.76714475492354417],"luv":[25.0727380413203562,66.6307123231625,9.0883501491595],"rgb":[0.466666666666666674,0.0666666666666666657,0.133333333333333331],"xyz":[0.0809672143256611465,0.0443901894158286,0.0194391772572919501],"hpluv":[7.76714475492354417,340.341449223453765,25.0727380413203562],"hsluv":[7.76714475492354417,86.6206298981217,25.0727380413203562]},"#771133":{"lch":[25.4501908259833556,61.6001849310061189,0.209311103178295294],"luv":[25.4501908259833556,61.599773884646936,0.225035318388215971],"rgb":[0.466666666666666674,0.0666666666666666657,0.2],"xyz":[0.0840549650581188801,0.0456252897088117101,0.0357013311149031],"hpluv":[0.209311103178295294,307.135699817562568,25.4501908259833556],"hsluv":[0.209311103178295294,87.6964449022470802,25.4501908259833556]},"#771144":{"lch":[25.9833113937366775,56.5829308746863688,349.098656617234155],"luv":[25.9833113937366775,55.5618510799655,-10.7008771106518541],"rgb":[0.466666666666666674,0.0666666666666666657,0.266666666666666663],"xyz":[0.0885129603023377537,0.0474084878064992832,0.0591801060677897353],"hpluv":[349.098656617234155,276.331419289390624,25.9833113937366775],"hsluv":[349.098656617234155,88.9762099121112442,25.9833113937366775]},"#771155":{"lch":[26.6758393728738312,54.157242934368746,335.112354986374442],"luv":[26.6758393728738312,49.127918835603694,-22.7915456504066185],"rgb":[0.466666666666666674,0.0666666666666666657,0.333333333333333315],"xyz":[0.0944753743416997527,0.0497934534222441161,0.0905821533417636438],"hpluv":[335.112354986374442,257.618934567198892,26.6758393728738312],"hsluv":[335.112354986374442,90.3225181656420375,26.6758393728738312]},"#771166":{"lch":[27.5255776115618076,55.4731266930874796,320.490705765847224],"luv":[27.5255776115618076,42.7987039146097388,-35.2921907557026557],"rgb":[0.466666666666666674,0.0666666666666666657,0.4],"xyz":[0.102060370115207924,0.0528274517316474276,0.130529797748907606],"hpluv":[320.490705765847224,255.732268141411282,27.5255776115618076],"hsluv":[320.490705765847224,91.6238582413064364,27.5255776115618076]},"#771177":{"lch":[28.525624322061,60.3055315504910538,307.715012949243658],"luv":[28.525624322061,36.8909645322892956,-47.7054909990940672],"rgb":[0.466666666666666674,0.0666666666666666657,0.466666666666666674],"xyz":[0.111374383135409843,0.056553056939728244,0.179583599655305515],"hpluv":[307.715012949243658,268.263334170626,28.525624322061],"hsluv":[307.715012949243658,92.8109433172232201,28.525624322061]},"#771188":{"lch":[29.665668786552871,67.5901402176946,297.828537901307072],"luv":[29.665668786552871,31.5529142063876336,-59.77324367751811],"rgb":[0.466666666666666674,0.0666666666666666657,0.533333333333333326],"xyz":[0.122514800237353672,0.0610092237805058418,0.238256463058877682],"hpluv":[297.828537901307072,289.113605882780575,29.665668786552871],"hsluv":[297.828537901307072,93.8529472884676892,29.665668786552871]},"#771199":{"lch":[30.9332504381216253,76.2730393291315494,290.583951381139741],"luv":[30.9332504381216253,26.816032696200331,-71.4036197887723461],"rgb":[0.466666666666666674,0.0666666666666666657,0.6],"xyz":[0.135571764346426371,0.066232009424135,0.307023140699995589],"hpluv":[290.583951381139741,312.885056330098905,30.9332504381216253],"hsluv":[290.583951381139741,94.745562802664864,30.9332504381216253]},"#7711aa":{"lch":[32.3148680584756391,85.6471240548678452,285.332056603477554],"luv":[32.3148680584756391,22.6461849228904,-82.5989114172104166],"rgb":[0.466666666666666674,0.0666666666666666657,0.66666666666666663],"xyz":[0.150629457311678927,0.0722550866102361078,0.38632699031699419],"hpluv":[285.332056603477554,336.317699636744408,32.3148680584756391],"hsluv":[285.332056603477554,95.4992611083078771,32.3148680584756391]},"#7711bb":{"lch":[33.796865882550442,95.3131880713117,281.486339493443666],"luv":[33.796865882550442,18.9801244444786903,-93.4042755787407],"rgb":[0.466666666666666674,0.0666666666666666657,0.733333333333333282],"xyz":[0.167767049419015574,0.0791101234531708664,0.476584975415635936],"hpluv":[281.486339493443666,357.862255189103962,33.796865882550442],"hsluv":[281.486339493443666,96.130904738193,33.796865882550442]},"#7711cc":{"lch":[35.36607449089243,105.064609202725904,278.619980375929231],"luv":[35.36607449089243,15.7470979978009513,-103.877817707002151],"rgb":[0.466666666666666674,0.0666666666666666657,0.8],"xyz":[0.1870594248627373,0.086827073630659668,0.578191486085906092],"hpluv":[278.619980375929231,376.971848031202455,35.36607449089243],"hsluv":[278.619980375929231,96.6587778670915441,35.36607449089243]},"#7711dd":{"lch":[37.0102245888209,114.800572621107989,276.441726024966442],"luv":[37.0102245888209,12.8797770828984923,-114.075776641796878],"rgb":[0.466666666666666674,0.0666666666666666657,0.866666666666666696],"xyz":[0.208577750413370844,0.0954344038509132,0.69152133398591209],"hpluv":[276.441726024966442,393.605954910771402,37.0102245888209],"hsluv":[276.441726024966442,97.1000737310191084,37.0102245888209]},"#7711ee":{"lch":[38.7181742300654648,124.475046910670883,274.755182332196796],"luv":[38.7181742300654648,10.3187754184265525,-124.046604859938796],"rgb":[0.466666666666666674,0.0666666666666666657,0.933333333333333348],"xyz":[0.232389930679545609,0.104959275957383219,0.816932150054435313],"hpluv":[274.755182332196796,407.949828918098,38.7181742300654648],"hsluv":[274.755182332196796,97.4698666617264706,38.7181742300654648]},"#7711ff":{"lch":[40.4799968781786,134.069342311184641,273.42679883886251],"luv":[40.4799968781786,8.01376323316973682,-133.829623576382744],"rgb":[0.466666666666666674,0.0666666666666666657,1],"xyz":[0.258560979089377,0.115427695321315929,0.954766338346217158],"hpluv":[273.42679883886251,420.269946860795244,40.4799968781786],"hsluv":[273.42679883886251,99.99999999999946,40.4799968781786]},"#772200":{"lch":[26.9238486490213944,67.8779226750429814,18.9619118830866213],"luv":[26.9238486490213944,64.1945131058521241,22.0562207502031953],"rgb":[0.466666666666666674,0.133333333333333331,0],"xyz":[0.0817958144223149414,0.0506666274301188907,0.00547272734147973266],"hpluv":[18.9619118830866213,319.912145739235086,26.9238486490213944],"hsluv":[18.9619118830866213,100.000000000002203,26.9238486490213944]},"#772211":{"lch":[27.0378210495853537,65.3114997865878451,16.6506371445360628],"luv":[27.0378210495853537,62.5729698674152957,18.7140440938083152],"rgb":[0.466666666666666674,0.133333333333333331,0.0666666666666666657],"xyz":[0.0828074799219520596,0.0510712936299737436,0.0108008323062353633],"hpluv":[16.6506371445360628,306.518925183772069,27.0378210495853537],"hsluv":[16.6506371445360628,87.77659617077137,27.0378210495853537]},"#772222":{"lch":[27.2475131582451553,61.1491638550902934,12.1770506300618262],"luv":[27.2475131582451553,59.7733358183195094,12.8983939049434628],"rgb":[0.466666666666666674,0.133333333333333331,0.133333333333333331],"xyz":[0.0846828380604290887,0.0518214368853645649,0.0206777185022145593],"hpluv":[12.1770506300618262,284.775733052529233,27.2475131582451553],"hsluv":[12.1770506300618262,66.7317810633447,27.2475131582451553]},"#772233":{"lch":[27.5884028886125066,55.830319472153235,4.36926883706767555],"luv":[27.5884028886125066,55.668063043001986,4.25339034219168255],"rgb":[0.466666666666666674,0.133333333333333331,0.2],"xyz":[0.0877705887928868222,0.0530565371783476777,0.0369398723598257],"hpluv":[4.36926883706767555,256.792821962959806,27.5884028886125066],"hsluv":[4.36926883706767555,69.0869865520346,27.5884028886125066]},"#772244":{"lch":[28.0713586292933357,51.0577981227000137,352.597218961633928],"luv":[28.0713586292933357,50.6322267647614197,-6.57847717790113862],"rgb":[0.466666666666666674,0.133333333333333331,0.266666666666666663],"xyz":[0.0922285840371057,0.0548397352760352508,0.0604186473127123411],"hpluv":[352.597218961633928,230.801153887638264,28.0713586292933357],"hsluv":[352.597218961633928,71.9536276136468871,28.0713586292933357]},"#772255":{"lch":[28.7011983995690869,48.8793621784160877,337.425357822166575],"luv":[28.7011983995690869,45.134235573623549,-18.7641367015226166],"rgb":[0.466666666666666674,0.133333333333333331,0.333333333333333315],"xyz":[0.0981909980764677,0.0572247008917800837,0.0918206945866862495],"hpluv":[337.425357822166575,216.105005327694982,28.7011983995690869],"hsluv":[337.425357822166575,75.0482997779786416,28.7011983995690869]},"#772266":{"lch":[29.4776386596593341,50.6214045611569148,321.457127980188602],"luv":[29.4776386596593341,39.5931335065919541,-31.5421999688275427],"rgb":[0.466666666666666674,0.133333333333333331,0.4],"xyz":[0.105775993849975866,0.0602586992011833952,0.131768338993830225],"hpluv":[321.457127980188602,217.911839443782668,29.4776386596593341],"hsluv":[321.457127980188602,78.1196257410634871,29.4776386596593341]},"#772277":{"lch":[30.3962065887853328,56.0773495547330114,307.715012949243828],"luv":[30.3962065887853328,34.3044404103525125,-44.3607315225559375],"rgb":[0.466666666666666674,0.133333333333333331,0.466666666666666674],"xyz":[0.115090006870177786,0.0639843044092642116,0.180822140900228134],"hpluv":[307.715012949243828,234.103236488433623,30.3962065887853328],"hsluv":[307.715012949243828,80.9925899090149,30.3962065887853328]},"#772288":{"lch":[31.4492100235983827,64.0564791390884,297.353574907339521],"luv":[31.4492100235983827,29.432687880373738,-56.8941948166328118],"rgb":[0.466666666666666674,0.133333333333333331,0.533333333333333326],"xyz":[0.126230423972121614,0.0684404712500418094,0.239495004303800302],"hpluv":[297.353574907339521,258.459589193709576,31.4492100235983827],"hsluv":[297.353574907339521,83.5725358287189692,31.4492100235983827]},"#772299":{"lch":[32.6267183371791276,73.3979299617999885,289.947447059718741],"luv":[32.6267183371791276,25.040297814612174,-68.9944896932561136],"rgb":[0.466666666666666674,0.133333333333333331,0.6],"xyz":[0.139287388081194341,0.073663256893670967,0.308261681944918209],"hpluv":[289.947447059718741,285.462946683821,32.6267183371791276],"hsluv":[289.947447059718741,85.8272099061853169,32.6267183371791276]},"#7722aa":{"lch":[33.91747454857272,83.3484305918169213,284.681710760769079],"luv":[33.91747454857272,21.1245907707188039,-80.6269963900962],"rgb":[0.466666666666666674,0.133333333333333331,0.66666666666666663],"xyz":[0.154345081046446869,0.0796863340797720754,0.38756553156191681],"hpluv":[284.681710760769079,311.826661765562392,33.91747454857272],"hsluv":[284.681710760769079,87.7639475735253,33.91747454857272]},"#7722bb":{"lch":[35.3096729107126137,93.4985765466947,280.880145280973409],"luv":[35.3096729107126137,17.6483381470917493,-91.8178630599952186],"rgb":[0.466666666666666674,0.133333333333333331,0.733333333333333282],"xyz":[0.171482673153783516,0.086541370922706834,0.477823516660558556],"hpluv":[280.880145280973409,336.008784107710085,35.3096729107126137],"hsluv":[280.880145280973409,89.410850129911168,35.3096729107126137]},"#7722cc":{"lch":[36.7915673195940158,103.646851370476796,278.075561058441508],"luv":[36.7915673195940158,14.5601992392030191,-102.619054746808203],"rgb":[0.466666666666666674,0.133333333333333331,0.8],"xyz":[0.19077504859750527,0.0942583211001956356,0.579430027330828712],"hpluv":[278.075561058441508,357.476214898523438,36.7915673195940158],"hsluv":[278.075561058441508,90.8041771911133395,36.7915673195940158]},"#7722dd":{"lch":[38.351906528896663,113.703099806952181,275.960131804992216],"luv":[38.351906528896663,11.8065226659589069,-113.088465053903391],"rgb":[0.466666666666666674,0.133333333333333331,0.866666666666666696],"xyz":[0.212293374148138814,0.10286565132044917,0.69275987523083471],"hpluv":[275.960131804992216,376.205095027198126,38.351906528896663],"hsluv":[275.960131804992216,91.9810564333708101,38.351906528896663]},"#7722ee":{"lch":[39.9802139341708269,123.633267676123751,274.331320845995606],"luv":[39.9802139341708269,9.33725773346982812,-123.280170726255989],"rgb":[0.466666666666666674,0.133333333333333331,0.933333333333333348],"xyz":[0.236105554414313523,0.112390523426919187,0.818170691299357933],"hpluv":[274.331320845995606,392.400507860275241,39.9802139341708269],"hsluv":[274.331320845995606,92.975835565241681,39.9802139341708269]},"#7722ff":{"lch":[41.6669409214524222,133.430100966175758,273.053819065229],"luv":[41.6669409214524222,7.10835060884393233,-133.240621416539511],"rgb":[0.466666666666666674,0.133333333333333331,1],"xyz":[0.262276602824144944,0.122858942790851897,0.956004879591139778],"hpluv":[273.053819065229,406.351179140502,41.6669409214524222],"hsluv":[273.053819065229,99.9999999999994,41.6669409214524222]},"#eeaa00":{"lch":[74.1441199778221716,94.3993067628715323,52.9277228913731435],"luv":[74.1441199778221716,56.9059790518510553,75.3189130661151438],"rgb":[0.933333333333333348,0.66666666666666663,0],"xyz":[0.496332043879122442,0.469286695915611562,0.0644413600602487119],"hpluv":[52.9277228913731435,161.559096825574699,74.1441199778221716],"hsluv":[52.9277228913731435,100.00000000000226,74.1441199778221716]},"#eeaa11":{"lch":[74.170022976797469,93.4032488378738748,52.6298301838218165],"luv":[74.170022976797469,56.6922374254564474,74.2304325001913412],"rgb":[0.933333333333333348,0.66666666666666663,0.0666666666666666657],"xyz":[0.497343709378759546,0.469691362115466415,0.0697694650250043485],"hpluv":[52.6298301838218165,159.798572537124,74.170022976797469],"hsluv":[52.6298301838218165,98.6103970010943698,74.170022976797469]},"#eeaa22":{"lch":[74.2180009061856794,91.5777923210439866,52.06484069365154],"luv":[74.2180009061856794,56.2992155036066038,72.2280442769616],"rgb":[0.933333333333333348,0.66666666666666663,0.133333333333333331],"xyz":[0.499219067517236603,0.470441505370857216,0.0796463512209835411],"hpluv":[52.06484069365154,156.574215350823607,74.2180009061856794],"hsluv":[52.06484069365154,96.0561964998804427,74.2180009061856794]},"#eeaa33":{"lch":[74.296884894391,88.6315089581908353,51.0969321655322659],"luv":[74.296884894391,55.6610065739707096,68.9738843866158646],"rgb":[0.933333333333333348,0.66666666666666663,0.2],"xyz":[0.502306818249694365,0.471676605663840343,0.0959085050785946913],"hpluv":[51.0969321655322659,151.375944062314744,74.296884894391],"hsluv":[51.0969321655322659,91.9112466174575786,74.296884894391]},"#eeaa44":{"lch":[74.4105324975616,84.5080331915566,49.6113532778883268],"luv":[74.4105324975616,54.7585847399730952,64.3669563610119582],"rgb":[0.933333333333333348,0.66666666666666663,0.266666666666666663],"xyz":[0.506764813493913224,0.473459803761527909,0.11938728003148133],"hpluv":[49.6113532778883268,144.112916810009352,74.4105324975616],"hsluv":[49.6113532778883268,86.056132752536,74.4105324975616]},"#eeaa55":{"lch":[74.5620870475656545,79.2349762990306203,47.4460085751506284],"luv":[74.5620870475656545,53.5853993814341081,58.3676832008958897],"rgb":[0.933333333333333348,0.66666666666666663,0.333333333333333315],"xyz":[0.512727227533275154,0.475844769377272769,0.150789327305455245],"hpluv":[47.4460085751506284,134.846041865267,74.5620870475656545],"hsluv":[47.4460085751506284,78.4547380743125302,74.5620870475656545]},"#eeaa66":{"lch":[74.7541548019056705,72.9335579755352512,44.3584242628533616],"luv":[74.7541548019056705,52.1460491859645643,50.9911113162453518],"rgb":[0.933333333333333348,0.66666666666666663,0.4],"xyz":[0.520312223306783395,0.478878767686676088,0.190736971712599179],"hpluv":[44.3584242628533616,123.803063546095515,74.7541548019056705],"hsluv":[44.3584242628533616,69.1456590086766,74.7541548019056705]},"#eeaa77":{"lch":[74.988898345165353,65.8409092602829702,39.9757578623994689],"luv":[74.988898345165353,50.4549646902964213,42.3003767160737851],"rgb":[0.933333333333333348,0.66666666666666663,0.466666666666666674],"xyz":[0.529626236326985245,0.482604372894756883,0.239790773618997088],"hpluv":[39.9757578623994689,111.41359114675457,74.988898345165353],"hsluv":[39.9757578623994689,69.3768546233612398,74.988898345165353]},"#eeaa88":{"lch":[75.268091919562039,58.355059996267,33.7246537035848917],"luv":[75.268091919562039,48.5347963512250544,32.3988668060016],"rgb":[0.933333333333333348,0.66666666666666663,0.533333333333333326],"xyz":[0.540766653428929156,0.487060539735534481,0.298463637022569284],"hpluv":[33.7246537035848917,98.3800275430836706,75.268091919562039],"hsluv":[33.7246537035848917,69.7313730072723388,75.268091919562039]},"#eeaa99":{"lch":[75.5931575717450102,51.1191249468368198,24.7740386445795764],"luv":[75.5931575717450102,46.4145012045046172,21.4209946843594],"rgb":[0.933333333333333348,0.66666666666666663,0.6],"xyz":[0.5538236175380018,0.492283325379163639,0.367230314663687163],"hpluv":[24.7740386445795764,85.8104641434512274,75.5931575717450102],"hsluv":[24.7740386445795764,70.1099573125584925,75.5931575717450102]},"#eeaaaa":{"lch":[75.9651912478537,45.142946910964838,12.1770506300622827],"luv":[75.9651912478537,44.1272513869876164,9.52215001119964199],"rgb":[0.933333333333333348,0.66666666666666663,0.66666666666666663],"xyz":[0.568881310503254412,0.498306402565264761,0.446534164280685764],"hpluv":[12.1770506300622827,75.4075094474617771,75.9651912478537],"hsluv":[12.1770506300622827,70.5013292017584661,75.9651912478537]},"#eeaabb":{"lch":[76.3849837125259512,41.8257198613105743,355.706426922992932],"luv":[76.3849837125259512,41.7083375548448743,-3.13136077895000353],"rgb":[0.933333333333333348,0.66666666666666663,0.733333333333333282],"xyz":[0.586018902610591,0.505161439408199464,0.53679214937932751],"hpluv":[355.706426922992932,71.0120800923082243,76.3849837125259512],"hsluv":[355.706426922992932,70.8933377227291857,76.3849837125259512]},"#eeaacc":{"lch":[76.8530390510081,42.4751513806234229,337.329003704434115],"luv":[76.8530390510081,39.1932374848878169,-16.3715796507258275],"rgb":[0.933333333333333348,0.66666666666666663,0.8],"xyz":[0.605311278054312729,0.512878389585688321,0.638398660049597666],"hpluv":[337.329003704434115,73.8860979443606283,76.8530390510081],"hsluv":[337.329003704434115,71.273377003825658,76.8530390510081]},"#eeaadd":{"lch":[77.3695923472492666,47.3596417233686893,320.637369166639587],"luv":[77.3695923472492666,36.6159833837478814,-30.0367345929437413],"rgb":[0.933333333333333348,0.66666666666666663,0.866666666666666696],"xyz":[0.626829603604946328,0.521485719805941828,0.751728507949603664],"hpluv":[320.637369166639587,84.6581146126829,77.3695923472492666],"hsluv":[320.637369166639587,71.6287050870027144,77.3695923472492666]},"#eeaaee":{"lch":[77.9346274334411078,55.5926087174301244,307.715012949246329],"luv":[77.9346274334411078,34.00790779424905,-43.9772708506012506],"rgb":[0.933333333333333348,0.66666666666666663,0.933333333333333348],"xyz":[0.650641783871121,0.531010591912411845,0.877139324018126887],"hpluv":[307.715012949246329,102.440850498764817,77.9346274334411078],"hsluv":[307.715012949246329,71.9466349992656,77.9346274334411078]},"#eeaaff":{"lch":[78.5478951631237123,66.0054362136172,298.40296943037481],"luv":[78.5478951631237123,31.3967924706888546,-58.0599606639755166],"rgb":[0.933333333333333348,0.66666666666666663,1],"xyz":[0.676812832280952459,0.541479011276344568,1.01497351230990884],"hpluv":[298.40296943037481,125.797711632464541,78.5478951631237123],"hsluv":[298.40296943037481,99.9999999999967457,78.5478951631237123]},"#773300":{"lch":[30.1331354048611715,59.6402239078303253,26.8671398719653283],"luv":[30.1331354048611715,53.2024709950710601,26.952799257122777],"rgb":[0.466666666666666674,0.2,0],"xyz":[0.0879135365113257461,0.0629020716081407,0.0075119680378166135],"hpluv":[26.8671398719653283,251.150628123644026,30.1331354048611715],"hsluv":[26.8671398719653283,100.000000000002174,30.1331354048611715]},"#773311":{"lch":[30.23185303241101,57.2493346092074376,24.6191658238461528],"luv":[30.23185303241101,52.0451874365790204,23.8492091669378929],"rgb":[0.466666666666666674,0.2,0.0666666666666666657],"xyz":[0.0889252020109628644,0.0633067378079955473,0.012840073002572245],"hpluv":[24.6191658238461528,240.295148301589592,30.23185303241101],"hsluv":[24.6191658238461528,89.9983576544591131,30.23185303241101]},"#773322":{"lch":[30.4137421865898716,53.2944669038914185,20.1857997859538507],"luv":[30.4137421865898716,50.0210446853216055,18.3900867632275506],"rgb":[0.466666666666666674,0.2,0.133333333333333331],"xyz":[0.0908005601494399,0.0640568810633863617,0.022716959198551441],"hpluv":[20.1857997859538507,222.35740076876786,30.4137421865898716],"hsluv":[20.1857997859538507,72.5478159849102,30.4137421865898716]},"#773333":{"lch":[30.7101510688592612,48.0738359215612618,12.1770506300619186],"luv":[30.7101510688592612,46.9921967440780932,10.1403720532853221],"rgb":[0.466666666666666674,0.2,0.2],"xyz":[0.093888310881897627,0.0652919813563694745,0.0389791130561625843],"hpluv":[12.1770506300619186,198.639745699191,30.7101510688592612],"hsluv":[12.1770506300619186,46.5474493854848177,30.7101510688592612]},"#773344":{"lch":[31.1315732769052218,43.177963340605487,359.450637080068248],"luv":[31.1315732769052218,43.1759786068728815,-0.413992248148033204],"rgb":[0.466666666666666674,0.2,0.266666666666666663],"xyz":[0.0983463061261165,0.0670751794540570545,0.0624578880090492228],"hpluv":[359.450637080068248,175.995032884643848,31.1315732769052218],"hsluv":[359.450637080068248,50.753084379908266,31.1315732769052218]},"#773355":{"lch":[31.6836931484193158,40.8566925980369149,342.103665666318761],"luv":[31.6836931484193158,38.8798033655970627,-12.5550874271345432],"rgb":[0.466666666666666674,0.2,0.333333333333333315],"xyz":[0.1043087201654785,0.0694601450698018874,0.0938599352830231382],"hpluv":[342.103665666318761,163.631433468067485,31.6836931484193158],"hsluv":[342.103665666318761,55.4419897041632623,31.6836931484193158]},"#773366":{"lch":[32.368092831934419,42.8604868736005429,323.403106692152903],"luv":[32.368092831934419,34.4105334223914952,-25.55262265656037],"rgb":[0.466666666666666674,0.2,0.4],"xyz":[0.111893715938986671,0.0724941433792052,0.1338075796901671],"hpluv":[323.403106692152903,168.027093700417197,32.368092831934419],"hsluv":[323.403106692152903,60.2559031079253913,32.368092831934419]},"#773377":{"lch":[33.18286532501061,49.0630735877418189,307.715012949244226],"luv":[33.18286532501061,30.0135669321659,-38.8119954380674201],"rgb":[0.466666666666666674,0.2,0.466666666666666674],"xyz":[0.12120772895918859,0.0762197485872860153,0.182861381596565],"hpluv":[307.715012949244226,187.620458725202155,33.18286532501061],"hsluv":[307.715012949244226,64.9109644958811458,33.18286532501061]},"#773388":{"lch":[34.1232577260479,58.0074356166469371,296.464975996700218],"luv":[34.1232577260479,25.8510525803809479,-51.9286594021626868],"rgb":[0.466666666666666674,0.2,0.533333333333333326],"xyz":[0.132348146061132432,0.0806759154280636132,0.241534245000137177],"hpluv":[296.464975996700218,215.711106662248483,34.1232577260479],"hsluv":[296.464975996700218,69.2226633917654226,34.1232577260479]},"#773399":{"lch":[35.1823459678372572,68.3160830057283,288.793689920994325],"luv":[35.1823459678372572,22.0088074868374761,-64.6737936899706369],"rgb":[0.466666666666666674,0.2,0.6],"xyz":[0.145405110170205132,0.0858987010716927707,0.310300922641255084],"hpluv":[288.793689920994325,246.398176299420243,35.1823459678372572],"hsluv":[288.793689920994325,73.0971646513407,35.1823459678372572]},"#7733aa":{"lch":[36.3517007299652,79.1457370086552316,283.529620316849901],"luv":[36.3517007299652,18.5159885211015585,-76.9493720294691741],"rgb":[0.466666666666666674,0.2,0.66666666666666663],"xyz":[0.160462803135457688,0.0919217782577938791,0.389604772258253684],"hpluv":[283.529620316849901,276.275338575971432,36.3517007299652],"hsluv":[283.529620316849901,76.5077273075914093,36.3517007299652]},"#7733bb":{"lch":[37.6219984216960484,90.0609255961329183,279.823655256592],"luv":[37.6219984216960484,15.3658639022925581,-88.7404110074345596],"rgb":[0.466666666666666674,0.2,0.733333333333333282],"xyz":[0.177600395242794307,0.0987768151007286377,0.479862757356895431],"hpluv":[279.823655256592,303.762299466947354,37.6219984216960484],"hsluv":[279.823655256592,79.4699002038008615,37.6219984216960484]},"#7733cc":{"lch":[38.9835424310364687,100.85881095677091,277.137789072490136],"luv":[38.9835424310364687,12.5323059717609908,-100.077175492935524],"rgb":[0.466666666666666674,0.2,0.8],"xyz":[0.196892770686516061,0.106493765278217439,0.581469268027165587],"hpluv":[277.137789072490136,328.300745951509327,38.9835424310364687],"hsluv":[277.137789072490136,82.0218198813884,38.9835424310364687]},"#7733dd":{"lch":[40.4266769703902469,111.457640409912926,275.137669231249674],"luv":[40.4266769703902469,9.98093438608736,-111.009848907770674],"rgb":[0.466666666666666674,0.2,0.866666666666666696],"xyz":[0.218411096237149605,0.115101095498470973,0.694799115927171584],"hpluv":[275.137669231249674,349.849394775278881,40.4266769703902469],"hsluv":[275.137669231249674,84.2108753320266459,40.4266769703902469]},"#7733ee":{"lch":[41.9420918590451066,121.83559331992231,273.612094072304103],"luv":[41.9420918590451066,7.67578650940870766,-121.593561100411392],"rgb":[0.466666666666666674,0.2,0.933333333333333348],"xyz":[0.242223276503324342,0.12462596760494099,0.820209931995694808],"hpluv":[273.612094072304103,368.606867281158145,41.9420918590451066],"hsluv":[273.612094072304103,91.3228806729277665,41.9420918590451066]},"#7733ff":{"lch":[43.521028110395612,131.998699032970592,272.424037139620168],"luv":[43.521028110395612,5.58285979872954208,-131.88058319125102],"rgb":[0.466666666666666674,0.2,1],"xyz":[0.268394324913155735,0.135094386968873714,0.958044120287476653],"hpluv":[272.424037139620168,384.866252510120546,43.521028110395612],"hsluv":[272.424037139620168,99.9999999999993605,43.521028110395612]},"#eebb00":{"lch":[78.2979307719844115,92.4506686575273307,61.8965912674010781],"luv":[78.2979307719844115,43.5502151824584587,81.5506277890334275],"rgb":[0.933333333333333348,0.733333333333333282,0],"xyz":[0.530286541787721277,0.537195691732810121,0.0757595260297813378],"hpluv":[61.8965912674010781,173.778692590363192,78.2979307719844115],"hsluv":[61.8965912674010781,100.000000000002373,78.2979307719844115]},"#eebb11":{"lch":[78.3216028454340858,91.4822746680663528,61.6861071172261504],"luv":[78.3216028454340858,43.3901974999079059,80.5375523551748],"rgb":[0.933333333333333348,0.733333333333333282,0.0666666666666666657],"xyz":[0.531298207287358437,0.537600357932665,0.0810876309945369744],"hpluv":[61.6861071172261504,172.182825477748111,78.3216028454340858],"hsluv":[61.6861071172261504,98.7812238781787642,78.3216028454340858]},"#eebb22":{"lch":[78.3654531575016335,89.702527227679937,61.2866058237147371],"luv":[78.3654531575016335,43.0956539167563477,78.672155204492924],"rgb":[0.933333333333333348,0.733333333333333282,0.133333333333333331],"xyz":[0.533173565425835383,0.53835050118805583,0.090964517190516167],"hpluv":[61.2866058237147371,169.241982225815036,78.3654531575016335],"hsluv":[61.2866058237147371,96.5386826089343,78.3654531575016335]},"#eebb33":{"lch":[78.4375634156200192,86.8156112143301897,60.6012126372659665],"luv":[78.4375634156200192,42.6165086308843897,75.6358614853528906],"rgb":[0.933333333333333348,0.733333333333333282,0.2],"xyz":[0.5362613161582932,0.539585601481039,0.107226671048127303],"hpluv":[60.6012126372659665,164.44952499458185,78.4375634156200192],"hsluv":[60.6012126372659665,92.89304460263088,78.4375634156200192]},"#eebb44":{"lch":[78.5414800230656,82.7424491039972878,59.5464137785053],"luv":[78.5414800230656,41.9372005918819397,71.3273025583039839],"rgb":[0.933333333333333348,0.733333333333333282,0.266666666666666663],"xyz":[0.540719311402512059,0.541368799578726523,0.130705446001013942],"hpluv":[59.5464137785053,157.640041580932575,78.5414800230656],"hsluv":[59.5464137785053,87.729612164687,78.5414800230656]},"#eebb55":{"lch":[78.6801087960333,77.4695312482544,58.0014729567720551],"luv":[78.6801087960333,41.0509080282697809,65.6989438414256455],"rgb":[0.933333333333333348,0.733333333333333282,0.333333333333333315],"xyz":[0.546681725441874,0.543753765194471272,0.162107493274987857],"hpluv":[58.0014729567720551,148.738759066450825,78.6801087960333],"hsluv":[58.0014729567720551,81.0022494643358613,78.6801087960333]},"#eebb66":{"lch":[78.8558787138066748,71.0520814803831229,55.7790951769469174],"luv":[78.8558787138066748,39.9586324736418845,58.7512210386423703],"rgb":[0.933333333333333348,0.733333333333333282,0.4],"xyz":[0.55426672121538223,0.546787763503874591,0.202055137682131819],"hpluv":[55.7790951769469174,137.768430134850121,78.8558787138066748],"hsluv":[55.7790951769469174,72.7264864885570574,78.8558787138066748]},"#eebb77":{"lch":[79.0708286262536,63.6259938562226566,52.5733668905169],"luv":[79.0708286262536,38.6683825737350801,50.5274507898760348],"rgb":[0.933333333333333348,0.733333333333333282,0.466666666666666674],"xyz":[0.56358073423558408,0.550513368711955442,0.251108939588529756],"hpluv":[52.5733668905169,124.876553308513152,79.0708286262536],"hsluv":[52.5733668905169,64.3532422469102841,79.0708286262536]},"#eebb88":{"lch":[79.3266586201773833,55.4370708593257149,47.8613800964459557],"luv":[79.3266586201773833,37.1942049853437169,41.1079060640423961],"rgb":[0.933333333333333348,0.733333333333333282,0.533333333333333326],"xyz":[0.574721151337528,0.554969535552733,0.309781802992101896],"hpluv":[47.8613800964459557,110.403075040931697,79.3266586201773833],"hsluv":[47.8613800964459557,64.6428486235092379,79.3266586201773833]},"#eebb99":{"lch":[79.6247632766216071,46.9114913521919519,40.7190885070752131],"luv":[79.6247632766216071,35.5550187983100727,30.6027557441907163],"rgb":[0.933333333333333348,0.733333333333333282,0.6],"xyz":[0.587778115446600635,0.560192321196362197,0.378548480633219775],"hpluv":[40.7190885070752131,95.0436922052712845,79.6247632766216071],"hsluv":[40.7190885070752131,64.94797236425741,79.6247632766216071]},"#eebbaa":{"lch":[79.9662551563314,38.821829319770238,29.5464132636418633],"luv":[79.9662551563314,33.7733033676501,19.1441482278540249],"rgb":[0.933333333333333348,0.733333333333333282,0.66666666666666663],"xyz":[0.602835808411853247,0.566215398382463264,0.457852330250218431],"hpluv":[29.5464132636418633,80.2381481795300573,79.9662551563314],"hsluv":[29.5464132636418633,65.2570717669902081,79.9662551563314]},"#eebbbb":{"lch":[80.3519829843595,32.6073830473385868,12.1770506300627979],"luv":[80.3519829843595,31.8737319395561,6.87798236703069765],"rgb":[0.933333333333333348,0.733333333333333282,0.733333333333333282],"xyz":[0.61997340051918981,0.573070435225398,0.548110315348860122],"hpluv":[12.1770506300627979,68.9527679352530498,80.3519829843595],"hsluv":[12.1770506300627979,65.5572779496269646,80.3519829843595]},"#eebbcc":{"lch":[80.7825470949933,30.4869713003172755,348.56539147946927],"luv":[80.7825470949933,29.8818538307637169,-6.04402437976891349],"rgb":[0.933333333333333348,0.733333333333333282,0.8],"xyz":[0.639265775962911564,0.58078738540288688,0.649716826019130278],"hpluv":[348.56539147946927,66.1649916626687542,80.7825470949933],"hsluv":[348.56539147946927,65.8346704867411,80.7825470949933]},"#eebbdd":{"lch":[81.2583136554081165,33.9595526191752555,325.014354592586812],"luv":[81.2583136554081165,27.8229160969035512,-19.4714291709468021],"rgb":[0.933333333333333348,0.733333333333333282,0.866666666666666696],"xyz":[0.660784101513545163,0.589394715623140386,0.763046673919136276],"hpluv":[325.014354592586812,75.8902298772919721,81.2583136554081165],"hsluv":[325.014354592586812,66.0744631525984119,81.2583136554081165]},"#eebbee":{"lch":[81.7794285687783429,42.0458499431423576,307.715012949247921],"luv":[81.7794285687783429,25.7208902583653902,-33.2609275540891716],"rgb":[0.933333333333333348,0.733333333333333282,0.933333333333333348],"xyz":[0.684596281779719873,0.598919587729610403,0.888457489987659499],"hpluv":[307.715012949247921,97.0917602325266245,81.7794285687783429],"hsluv":[307.715012949247921,66.2610602343042103,81.7794285687783429]},"#eebbff":{"lch":[82.3458315671937697,52.842459086223478,296.523687653639684],"luv":[82.3458315671937697,23.5977388768935938,-47.2807804734349446],"rgb":[0.933333333333333348,0.733333333333333282,1],"xyz":[0.710767330189551294,0.609388007093543127,1.02629167827944134],"hpluv":[296.523687653639684,126.563624284615543,82.3458315671937697],"hsluv":[296.523687653639684,99.9999999999958504,82.3458315671937697]},"#774400":{"lch":[34.1007355557283631,52.2824067620925845,38.9690248280103901],"luv":[34.1007355557283631,40.6488429776156863,32.8804139483986475],"rgb":[0.466666666666666674,0.266666666666666663,0],"xyz":[0.0967461069942917862,0.0805672125740730105,0.0104561581988052085],"hpluv":[38.9690248280103901,194.549962435525771,34.1007355557283631],"hsluv":[38.9690248280103901,100.000000000002302,34.1007355557283631]},"#774411":{"lch":[34.1844760931178726,49.9480603042207,37.0032460066389604],"luv":[34.1844760931178726,39.8885915773263164,30.0617529450848728],"rgb":[0.466666666666666674,0.266666666666666663,0.0666666666666666657],"xyz":[0.0977577724939289,0.0809718787739278634,0.0157842631635608383],"hpluv":[37.0032460066389604,185.408237524396696,34.1844760931178726],"hsluv":[37.0032460066389604,92.0774266797174477,34.1844760931178726]},"#774422":{"lch":[34.3389737161705639,45.9687551907429608,33.0232295817012798],"luv":[34.3389737161705639,38.542488289151045,25.0520069109664867],"rgb":[0.466666666666666674,0.266666666666666663,0.133333333333333331],"xyz":[0.0996331306324059335,0.0817220220293186778,0.0256611493595400378],"hpluv":[33.0232295817012798,169.869245826721482,34.3389737161705639],"hsluv":[33.0232295817012798,78.0803941927279794,34.3389737161705639]},"#774433":{"lch":[34.5913049894652787,40.4066166426811861,25.4401077009201266],"luv":[34.5913049894652787,36.4885814660887746,17.357364174931714],"rgb":[0.466666666666666674,0.266666666666666663,0.2],"xyz":[0.102720881364863667,0.0829571223223017906,0.041923303217151181],"hpluv":[25.4401077009201266,148.226163260119392,34.5913049894652787],"hsluv":[25.4401077009201266,56.8050953227402715,34.5913049894652787]},"#774444":{"lch":[34.9512320153617324,34.6101233100924119,12.1770506300620909],"luv":[34.9512320153617324,33.8314114683579277,7.30042694631360689],"rgb":[0.466666666666666674,0.266666666666666663,0.266666666666666663],"xyz":[0.107178876609082541,0.0847403204199893706,0.0654020781700378195],"hpluv":[12.1770506300620909,125.655060642768362,34.9512320153617324],"hsluv":[12.1770506300620909,29.4448754689639038,34.9512320153617324]},"#774455":{"lch":[35.4248138391838907,31.0799263948782318,351.580242092888511],"luv":[35.4248138391838907,30.7449438027498871,-4.55085214842230634],"rgb":[0.466666666666666674,0.266666666666666663,0.333333333333333315],"xyz":[0.11314129064844454,0.0871252860357342,0.0968041254440117349],"hpluv":[351.580242092888511,111.329877567225012,35.4248138391838907],"hsluv":[351.580242092888511,35.0525071981411855,35.4248138391838907]},"#774466":{"lch":[36.0149447056398699,32.55773059028094,327.386935336661793],"luv":[36.0149447056398699,27.4243377055297302,-17.5474078599198116],"rgb":[0.466666666666666674,0.266666666666666663,0.4],"xyz":[0.120726286421952711,0.090159284345137522,0.136751769851155697],"hpluv":[327.386935336661793,114.712488009680612,36.0149447056398699],"hsluv":[327.386935336661793,41.0162201785438469,36.0149447056398699]},"#774477":{"lch":[36.7217587051522898,39.3078830273429531,307.715012949244965],"luv":[36.7217587051522898,24.0459818745983789,-31.0950224919964064],"rgb":[0.466666666666666674,0.266666666666666663,0.466666666666666674],"xyz":[0.130040299442154617,0.0938848895532183314,0.185805571757553606],"hpluv":[307.715012949244965,135.82994003022975,36.7217587051522898],"hsluv":[307.715012949244965,46.9930223744603381,36.7217587051522898]},"#774488":{"lch":[37.5430301686231331,49.3077922843884906,294.880307130505798],"luv":[37.5430301686231331,20.7449732069104158,-44.731470651041306],"rgb":[0.466666666666666674,0.266666666666666663,0.533333333333333326],"xyz":[0.141180716544098472,0.0983410563939959292,0.244478435161125773],"hpluv":[294.880307130505798,166.657768282509579,37.5430301686231331],"hsluv":[294.880307130505798,52.7229750286883814,37.5430301686231331]},"#774499":{"lch":[38.4745988052595678,60.7540316024919775,286.849160899925266],"luv":[38.4745988052595678,17.609743748149274,-58.1459309073396042],"rgb":[0.466666666666666674,0.266666666666666663,0.6],"xyz":[0.154237680653171172,0.103563842037625087,0.313245112802243653],"hpluv":[286.849160899925266,200.373520820200838,38.4745988052595678],"hsluv":[286.849160899925266,58.0391908614757739,38.4745988052595678]},"#7744aa":{"lch":[39.5108096650206306,72.6607854160185695,281.662296555210446],"luv":[39.5108096650206306,14.6878600390207232,-71.1607792589909423],"rgb":[0.466666666666666674,0.266666666666666663,0.66666666666666663],"xyz":[0.169295373618423728,0.109586919223726181,0.392548962419242253],"hpluv":[281.662296555210446,233.358425064810547,39.5108096650206306],"hsluv":[281.662296555210446,62.855979622580108,39.5108096650206306]},"#7744bb":{"lch":[40.6449442050895,84.5532389367896684,278.156646558388104],"luv":[40.6449442050895,11.9964108827533504,-83.6978873128468],"rgb":[0.466666666666666674,0.266666666666666663,0.733333333333333282],"xyz":[0.186432965725760347,0.11644195606666094,0.482806947517884],"hpluv":[278.156646558388104,263.975148792079324,40.6449442050895],"hsluv":[278.156646558388104,67.1480450168623832,40.6449442050895]},"#7744cc":{"lch":[41.8696179576200223,96.2178725735773099,275.685728840067554],"luv":[41.8696179576200223,9.53248740063005151,-95.7445073439828747],"rgb":[0.466666666666666674,0.266666666666666663,0.8],"xyz":[0.205725341169482101,0.124158906244149742,0.584413458188154156],"hpluv":[275.685728840067554,291.605746800226,41.8696179576200223],"hsluv":[275.685728840067554,71.7563947172966721,41.8696179576200223]},"#7744dd":{"lch":[43.1771261833787037,107.572507589609089,273.881356516641858],"luv":[43.1771261833787037,7.28165300546471084,-107.325774717094461],"rgb":[0.466666666666666674,0.266666666666666663,0.866666666666666696],"xyz":[0.227243666720115645,0.132766236464403276,0.697743306088160153],"hpluv":[273.881356516641858,316.145413845169799,43.1771261833787037],"hsluv":[273.881356516641858,81.1040313116107683,43.1771261833787037]},"#7744ee":{"lch":[44.5597272061305958,118.600491111934247,272.524492751992966],"luv":[44.5597272061305958,5.22393127379498168,-118.485387428318177],"rgb":[0.466666666666666674,0.266666666666666663,0.933333333333333348],"xyz":[0.251055846986290354,0.14229110857087332,0.823154122156683377],"hpluv":[272.524492751992966,337.740615485066769,44.5597272061305958],"hsluv":[272.524492751992966,90.4775463334620866,44.5597272061305958]},"#7744ff":{"lch":[46.0098610845945188,129.316457315512423,271.478956563127554],"luv":[46.0098610845945188,3.33763127257498882,-129.273378350389976],"rgb":[0.466666666666666674,0.266666666666666663,1],"xyz":[0.277226895396121775,0.152759527934806016,0.960988310448465222],"hpluv":[271.478956563127554,356.649979093308843,46.0098610845945188],"hsluv":[271.478956563127554,99.9999999999992752,46.0098610845945188]},"#eecc00":{"lch":[82.5742071813858161,93.0890420253441278,70.6743105766144737],"luv":[82.5742071813858161,30.8066573410433797,87.8437226480516529],"rgb":[0.933333333333333348,0.8,0],"xyz":[0.568510285097338142,0.613643178352045071,0.088500773799653279],"hpluv":[70.6743105766144737,226.330948640265689,82.5742071813858161],"hsluv":[70.6743105766144737,100.000000000002331,82.5742071813858161]},"#eecc11":{"lch":[82.5958706312260773,92.1609271203023,70.5494354113464226],"luv":[82.5958706312260773,30.6889817522442812,86.9012248859824297],"rgb":[0.933333333333333348,0.8,0.0666666666666666657],"xyz":[0.569521950596975302,0.6140478445519,0.0938288787644089156],"hpluv":[70.5494354113464226,224.395635781555484,82.5958706312260773],"hsluv":[70.5494354113464226,98.9293851282895389,82.5958706312260773]},"#eecc22":{"lch":[82.636003730849751,90.4517890224530277,70.3125751850760849],"luv":[82.636003730849751,30.4721781878481224,85.1643851257694848],"rgb":[0.933333333333333348,0.8,0.133333333333333331],"xyz":[0.571397308735452247,0.61479798780729078,0.103705764960388108],"hpluv":[70.3125751850760849,220.820363432361461,82.636003730849751],"hsluv":[70.3125751850760849,96.9576791715233668,82.636003730849751]},"#eecc33":{"lch":[82.7020112487531,87.6695564091762236,69.9066450473675332],"luv":[82.7020112487531,30.1189443150749909,82.3334701948554084],"rgb":[0.933333333333333348,0.8,0.2],"xyz":[0.574485059467910064,0.616033088100273907,0.119967918817999258],"hpluv":[69.9066450473675332,214.968262161852493,82.7020112487531],"hsluv":[69.9066450473675332,93.7473974454290726,82.7020112487531]},"#eecc44":{"lch":[82.7971553193909102,83.7217279628800242,69.2828434677537786],"luv":[82.7971553193909102,29.6169744667019792,78.3081257375581],"rgb":[0.933333333333333348,0.8,0.266666666666666663],"xyz":[0.578943054712128924,0.617816286197961473,0.143446693770885897],"hpluv":[69.2828434677537786,206.594355258371763,82.7971553193909102],"hsluv":[69.2828434677537786,89.1900904304584685,82.7971553193909102]},"#eecc55":{"lch":[82.9241214704336471,78.5673404382155098,68.3706518767947387],"luv":[82.9241214704336471,28.9599810639926183,73.0352413585910085],"rgb":[0.933333333333333348,0.8,0.333333333333333315],"xyz":[0.584905468751490853,0.620201251813706222,0.174848741044859812],"hpluv":[68.3706518767947387,195.532369916851508,82.9241214704336471],"hsluv":[68.3706518767947387,83.2339277722755497,82.9241214704336471]},"#eecc66":{"lch":[83.0851700146488241,72.2150018940459688,67.0599726095305186],"luv":[83.0851700146488241,28.1470538355007811,66.503758231685552],"rgb":[0.933333333333333348,0.8,0.4],"xyz":[0.592490464524999094,0.623235250123109541,0.214796385452003746],"hpluv":[67.0599726095305186,181.688085793908328,83.0851700146488241],"hsluv":[67.0599726095305186,75.8779818086204898,83.0851700146488241]},"#eecc77":{"lch":[83.2822165713090925,64.7250307453925586,65.1677162202921778],"luv":[83.2822165713090925,27.1821511319325673,58.740618526133936],"rgb":[0.933333333333333348,0.8,0.466666666666666674],"xyz":[0.601804477545200944,0.626960855331190392,0.263850187358401655],"hpluv":[65.1677162202921778,165.044758034407693,83.2822165713090925],"hsluv":[65.1677162202921778,67.1675472191497533,83.2822165713090925]},"#eecc88":{"lch":[83.5168798492942699,56.2182636624862511,62.3679934299132839],"luv":[83.5168798492942699,26.0735255024375405,49.8062690541919793],"rgb":[0.933333333333333348,0.8,0.533333333333333326],"xyz":[0.612944894647144856,0.631417022171967934,0.322523050761973851],"hpluv":[62.3679934299132839,145.689263900370548,83.5168798492942699],"hsluv":[62.3679934299132839,57.1887031148042198,83.5168798492942699]},"#eecc99":{"lch":[83.79051243806407,46.9027423702446598,58.0311593919332083],"luv":[83.79051243806407,24.8330316313984234,39.7892923013655633],"rgb":[0.933333333333333348,0.8,0.6],"xyz":[0.6260018587562175,0.636639807815597147,0.39128972840309173],"hpluv":[58.0311593919332083,123.89218615802821,83.79051243806407],"hsluv":[58.0311593919332083,56.9799526918745158,83.79051243806407]},"#eeccaa":{"lch":[84.1042222244633,37.1556922719042646,50.8162292091241454],"luv":[84.1042222244633,23.475329457535171,28.8002495660129298],"rgb":[0.933333333333333348,0.8,0.66666666666666663],"xyz":[0.641059551721470111,0.642662885001698214,0.470593578020090331],"hpluv":[50.8162292091241454,100.353156070069275,84.1042222244633],"hsluv":[50.8162292091241454,57.1375361174420533,84.1042222244633]},"#eeccbb":{"lch":[84.4588885299527,27.7950211383377486,37.6158086991229084],"luv":[84.4588885299527,22.0170273242058485,16.9650731765535063],"rgb":[0.933333333333333348,0.8,0.733333333333333282],"xyz":[0.658197143828806674,0.649517921844633,0.560851563118732077],"hpluv":[37.6158086991229084,77.0182985337263517,84.4588885299527],"hsluv":[37.6158086991229084,57.2612530554736736,84.4588885299527]},"#eecccc":{"lch":[84.8551753311588897,20.9471233587290264,12.1770506300631585],"luv":[84.8551753311588897,20.4758227261490333,4.41844550641173317],"rgb":[0.933333333333333348,0.8,0.8],"xyz":[0.677489519272528429,0.65723487202212183,0.662458073789002233],"hpluv":[12.1770506300631585,59.764130158742411,84.8551753311588897],"hsluv":[12.1770506300631585,57.3329985994000637,84.8551753311588897]},"#eeccdd":{"lch":[85.2935429882433596,20.7797326136980232,335.241604291037675],"luv":[85.2935429882433596,18.8696973425293919,-8.70240252448279],"rgb":[0.933333333333333348,0.8,0.866666666666666696],"xyz":[0.699007844823162,0.665842202242375336,0.775787921689008231],"hpluv":[335.241604291037675,61.2821093808529582,85.2935429882433596],"hsluv":[335.241604291037675,57.3323512744098664,85.2935429882433596]},"#eeccee":{"lch":[85.7742593547863805,28.14328338963319,307.715012949251047],"luv":[85.7742593547863805,17.2162128855465397,-22.2631177921859056],"rgb":[0.933333333333333348,0.8,0.933333333333333348],"xyz":[0.722820025089336737,0.675367074348845353,0.901198737757531454],"hpluv":[307.715012949251047,86.153290074940827,85.7742593547863805],"hsluv":[307.715012949251047,57.2362127773837557,85.7742593547863805]},"#eeccff":{"lch":[86.2974107975625344,39.333162863939684,293.258584701896098],"luv":[86.2974107975625344,15.5319389668875889,-36.1366375415599919],"rgb":[0.933333333333333348,0.8,1],"xyz":[0.748991073499168158,0.685835493712778077,1.03903292604931341],"hpluv":[293.258584701896098,125.558261528980708,86.2974107975625344],"hsluv":[293.258584701896098,99.9999999999940314,86.2974107975625344]},"#775500":{"lch":[38.5848153490983421,48.2339285723334328,54.8056311564330656],"luv":[38.5848153490983421,27.7997213245256276,39.4168410682499868],"rgb":[0.466666666666666674,0.333333333333333315,0],"xyz":[0.108559363708637752,0.104193726002765275,0.0143939104369204193],"hpluv":[54.8056311564330656,158.626424871442595,38.5848153490983421],"hsluv":[54.8056311564330656,100.000000000002302,38.5848153490983421]},"#775511":{"lch":[38.6553893217116595,45.9150245993572952,53.4685955296553956],"luv":[38.6553893217116595,27.3315290852913257,36.8941323494598521],"rgb":[0.466666666666666674,0.333333333333333315,0.0666666666666666657],"xyz":[0.10957102920827487,0.104598392202620127,0.0197220154016760491],"hpluv":[53.4685955296553956,150.724584795248319,38.6553893217116595],"hsluv":[53.4685955296553956,93.8009130268131344,38.6553893217116595]},"#775522":{"lch":[38.7857346943923531,41.8305053308362176,50.7011784026830412],"luv":[38.7857346943923531,26.4939762066725,32.3706719268134577],"rgb":[0.466666666666666674,0.333333333333333315,0.133333333333333331],"xyz":[0.111446387346751899,0.105348535458010942,0.0295989015976552486],"hpluv":[50.7011784026830412,136.854920013655288,38.7857346943923531],"hsluv":[50.7011784026830412,82.7342982429400138,38.7857346943923531]},"#775533":{"lch":[38.9990050188249739,35.7224202133018096,45.1472123185964946],"luv":[38.9990050188249739,25.1945818654867857,25.3243825298663161],"rgb":[0.466666666666666674,0.333333333333333315,0.2],"xyz":[0.114534138079209633,0.106583635750994055,0.0458610554552663918],"hpluv":[45.1472123185964946,116.232257903435652,38.9990050188249739],"hsluv":[45.1472123185964946,65.6272728324870656,38.9990050188249739]},"#775544":{"lch":[39.3040305977305735,28.372964966311546,34.1730058033032336],"luv":[39.3040305977305735,23.4742392428959334,15.9369141601135258],"rgb":[0.466666666666666674,0.333333333333333315,0.266666666666666663],"xyz":[0.118992133323428506,0.108366833848681635,0.0693398304081530303],"hpluv":[34.1730058033032336,91.6024225041024,39.3040305977305735],"hsluv":[34.1730058033032336,43.1049791856067799,39.3040305977305735]},"#775555":{"lch":[39.7068052905653701,21.9117916496410494,12.1770506300624941],"luv":[39.7068052905653701,21.4187864245998618,4.62192615633957704],"rgb":[0.466666666666666674,0.333333333333333315,0.333333333333333315],"xyz":[0.124954547362790505,0.110751799464426468,0.100741877682126946],"hpluv":[12.1770506300624941,70.0248633547080601,39.7068052905653701],"hsluv":[12.1770506300624941,16.4089959502104463,39.7068052905653701]},"#775566":{"lch":[40.21091767922141,20.7753835118276839,337.09176723456028],"luv":[40.21091767922141,19.1368182791551824,-8.08694912894311],"rgb":[0.466666666666666674,0.333333333333333315,0.4],"xyz":[0.132539543136298676,0.113785797773829786,0.140689522089270908],"hpluv":[337.09176723456028,65.5608222190156482,40.21091767922141],"hsluv":[337.09176723456028,22.8015335286909036,40.21091767922141]},"#775577":{"lch":[40.8178321801082404,27.3616881241227183,307.715012949246557],"luv":[40.8178321801082404,16.7380842217685313,-21.6448264854847388],"rgb":[0.466666666666666674,0.333333333333333315,0.466666666666666674],"xyz":[0.141853556156500582,0.117511402981910595,0.189743323995668817],"hpluv":[307.715012949246557,85.0613515767839,40.8178321801082404],"hsluv":[307.715012949246557,29.4286369924042717,40.8178321801082404]},"#775588":{"lch":[41.5271394874135922,38.3484586217177892,291.922773984013077],"luv":[41.5271394874135922,14.317648223710199,-35.5754020076188269],"rgb":[0.466666666666666674,0.333333333333333315,0.533333333333333326],"xyz":[0.152993973258444438,0.121967569822688193,0.248416187399240984],"hpluv":[291.922773984013077,117.180466459805771,41.5271394874135922],"hsluv":[291.922773984013077,36.0000576829853429,41.5271394874135922]},"#775599":{"lch":[42.336815252734,50.9328741768069335,283.566916489070877],"luv":[42.336815252734,11.9478768110398388,-49.5116744982298442],"rgb":[0.466666666666666674,0.333333333333333315,0.6],"xyz":[0.166050937367517137,0.127190355466317351,0.317182865040358863],"hpluv":[283.566916489070877,152.657914615048412,42.336815252734],"hsluv":[283.566916489070877,42.2975255710275704,42.336815252734]},"#7755aa":{"lch":[43.2434937800222059,63.9392804809027098,278.705184193400783],"luv":[43.2434937800222059,9.67722671121172517,-63.202712533524668],"rgb":[0.466666666666666674,0.333333333333333315,0.66666666666666663],"xyz":[0.181108630332769693,0.133213432652418445,0.396486714657357464],"hpluv":[278.705184193400783,187.623095354238075,43.2434937800222059],"hsluv":[278.705184193400783,48.1780536314011272,43.2434937800222059]},"#7755bb":{"lch":[44.2427493107278096,76.8690732738289171,275.624120681329657],"luv":[44.2427493107278096,7.53331271169703509,-76.4990432983646116],"rgb":[0.466666666666666674,0.333333333333333315,0.733333333333333282],"xyz":[0.198246222440106312,0.140068469495353204,0.486744699755999211],"hpluv":[275.624120681329657,220.469676556121556,44.2427493107278096],"hsluv":[275.624120681329657,58.1137757976744496,44.2427493107278096]},"#7755cc":{"lch":[45.3293721892173949,89.4999308488430785,273.541003165926895],"luv":[45.3293721892173949,5.52776894932058127,-89.3290624175056536],"rgb":[0.466666666666666674,0.333333333333333315,0.8],"xyz":[0.217538597883828067,0.147785419672842,0.588351210426269366],"hpluv":[273.541003165926895,250.543028147628775,45.3293721892173949],"hsluv":[273.541003165926895,68.560007412736141,45.3293721892173949]},"#7755dd":{"lch":[46.4976270234735622,101.740884988783861,272.062259876532949],"luv":[46.4976270234735622,3.66119191329505167,-101.674988822595736],"rgb":[0.466666666666666674,0.333333333333333315,0.866666666666666696],"xyz":[0.23905692343446161,0.15639274989309554,0.701681058326275364],"hpluv":[272.062259876532949,277.654072578046794,46.4976270234735622],"hsluv":[272.062259876532949,78.9623412233913911,46.4976270234735622]},"#7755ee":{"lch":[47.7414825998049253,113.569026574645434,270.972372145675877],"luv":[47.7414825998049253,1.92729827623402139,-113.552672000560818],"rgb":[0.466666666666666674,0.333333333333333315,0.933333333333333348],"xyz":[0.26286910370063632,0.165917621999565584,0.827091874394798587],"hpluv":[270.972372145675877,301.858443342198598,47.7414825998049253],"hsluv":[270.972372145675877,89.4067229175139175,47.7414825998049253]},"#7755ff":{"lch":[49.0548071408334749,124.996939079083958,270.144864217432826],"luv":[49.0548071408334749,0.31603661949764611,-124.996539552082652],"rgb":[0.466666666666666674,0.333333333333333315,1],"xyz":[0.289040152110467741,0.17638604136349828,0.964926062686580432],"hpluv":[270.144864217432826,323.338286172745597,49.0548071408334749],"hsluv":[270.144864217432826,99.9999999999992184,49.0548071408334749]},"#eedd00":{"lch":[86.9434330779808562,96.0018853881048528,78.7633058197047831],"luv":[86.9434330779808562,18.7071720605167293,94.1615829920517],"rgb":[0.933333333333333348,0.866666666666666696,0],"xyz":[0.611144275644513346,0.69891115944639659,0.102712103982044597],"hpluv":[78.7633058197047831,323.365375109368927,86.9434330779808562],"hsluv":[78.7633058197047831,100.000000000002331,86.9434330779808562]},"#eedd11":{"lch":[86.9632971622836,95.1251800329902153,78.7103442402005555],"luv":[86.9632971622836,18.6225708681111755,93.2845095960255293],"rgb":[0.933333333333333348,0.866666666666666696,0.0666666666666666657],"xyz":[0.612155941144150506,0.699315825646251499,0.108040208946800234],"hpluv":[78.7103442402005555,320.953864246165836,86.9632971622836],"hsluv":[78.7103442402005555,99.0572185708442134,86.9632971622836]},"#eedd22":{"lch":[87.0000996195567,93.508678235103929,78.6100630695382421],"luv":[87.0000996195567,18.466577094457584,91.6671066178629559],"rgb":[0.933333333333333348,0.866666666666666696,0.133333333333333331],"xyz":[0.614031299282627452,0.700065968901642299,0.117917095142779427],"hpluv":[78.6100630695382421,316.490280961531141,87.0000996195567],"hsluv":[78.6100630695382421,97.3195837272491104,87.0000996195567]},"#eedd33":{"lch":[87.0606371109171704,90.8714304533391157,78.438725785200063],"luv":[87.0606371109171704,18.2120693177914781,89.0277339024194561],"rgb":[0.933333333333333348,0.866666666666666696,0.2],"xyz":[0.617119050015085269,0.701301069194625426,0.134179249000390577],"hpluv":[78.438725785200063,309.159510866930361,87.0606371109171704],"hsluv":[78.438725785200063,94.4866487647953335,87.0606371109171704]},"#eedd44":{"lch":[87.1479139339873399,87.1163282896156375,78.1766823729436737],"luv":[87.1479139339873399,17.8496481580416315,85.2680755927926839],"rgb":[0.933333333333333348,0.866666666666666696,0.266666666666666663],"xyz":[0.621577045259304128,0.703084267292313,0.157658023953277215],"hpluv":[78.1766823729436737,298.614243514229941,87.1479139339873399],"hsluv":[78.1766823729436737,90.4569971269957449,87.1479139339873399]},"#eedd55":{"lch":[87.2644132886328,82.1887294441124823,77.7961103956051119],"luv":[87.2644132886328,17.3739699699556027,80.3313912186301],"rgb":[0.933333333333333348,0.866666666666666696,0.333333333333333315],"xyz":[0.627539459298666058,0.705469232908057742,0.189060071227251103],"hpluv":[77.7961103956051119,284.577035202400168,87.2644132886328],"hsluv":[77.7961103956051119,85.1762064326895114,87.2644132886328]},"#eedd66":{"lch":[87.4122373516825775,76.0722975624531,77.2543738936701],"luv":[87.4122373516825775,16.7832968217641607,74.1978126646801428],"rgb":[0.933333333333333348,0.866666666666666696,0.4],"xyz":[0.635124455072174299,0.70850323121746106,0.229007715634395093],"hpluv":[77.2543738936701,266.820521177772889,87.4122373516825775],"hsluv":[77.2543738936701,78.6319742502837187,87.4122373516825775]},"#eedd77":{"lch":[87.5931821049200323,68.7867162946957,76.4818073814204666],"luv":[87.5931821049200323,16.0791769624109904,66.8810317415814524],"rgb":[0.933333333333333348,0.866666666666666696,0.466666666666666674],"xyz":[0.644438468092376149,0.712228836425541911,0.278061517540793],"hpluv":[76.4818073814204666,245.153905791490757,87.5931821049200323],"hsluv":[76.4818073814204666,70.850552214552053,87.5931821049200323]},"#eedd88":{"lch":[87.8087818591101694,60.3863988515096,75.3563176494847],"luv":[87.8087818591101694,15.266108392374667,58.4248500280990655],"rgb":[0.933333333333333348,0.866666666666666696,0.533333333333333326],"xyz":[0.65557888519432006,0.716685003266319454,0.336734380944365141],"hpluv":[75.3563176494847,219.413652035392374,87.8087818591101694],"hsluv":[75.3563176494847,61.8928475378351,87.8087818591101694]},"#eedd99":{"lch":[88.0603378936253165,50.9615259485641658,73.6438422487017164],"luv":[88.0603378936253165,14.3511385044238935,48.8990996914362483],"rgb":[0.933333333333333348,0.866666666666666696,0.6],"xyz":[0.668635849303392704,0.721907788909948667,0.405501058585483076],"hpluv":[73.6438422487017164,189.463858183723431,88.0603378936253165],"hsluv":[73.6438422487017164,51.8496821371243328,88.0603378936253165]},"#eeddaa":{"lch":[88.3489381850503719,40.6474355343677374,70.8361013393810595],"luv":[88.3489381850503719,13.3433963411670824,38.3948927541556],"rgb":[0.933333333333333348,0.866666666666666696,0.66666666666666663],"xyz":[0.683693542268645316,0.727930866096049733,0.484804908202481677],"hpluv":[70.8361013393810595,155.230940371192503,88.3489381850503719],"hsluv":[70.8361013393810595,42.897511243529749,88.3489381850503719]},"#eeddbb":{"lch":[88.6754719765582422,29.6681200622416164,65.6052069824857256],"luv":[88.6754719765582422,12.2535763784239329,27.0193858917206811],"rgb":[0.933333333333333348,0.866666666666666696,0.733333333333333282],"xyz":[0.700831134375981879,0.734785902938984492,0.575062893301123368],"hpluv":[65.6052069824857256,116.881954111652547,88.6754719765582422],"hsluv":[65.6052069824857256,42.5880034653653823,88.6754719765582422]},"#eeddcc":{"lch":[89.0406413623298,18.5684245799280596,53.3136761054906927],"luv":[89.0406413623298,11.093403559257359,14.8903589228712931],"rgb":[0.933333333333333348,0.866666666666666696,0.8],"xyz":[0.720123509819703633,0.742502853116473349,0.676669403971393524],"hpluv":[53.3136761054906927,75.8171941810365837,89.0406413623298],"hsluv":[53.3136761054906927,42.1389745374101,89.0406413623298]},"#eedddd":{"lch":[89.4449712115231677,10.1024117660870978,12.1770506300652031],"luv":[89.4449712115231677,9.87511215198720294,2.13093488339032255],"rgb":[0.933333333333333348,0.866666666666666696,0.866666666666666696],"xyz":[0.741641835370337232,0.751110183336726855,0.789999251871399522],"hpluv":[12.1770506300652031,42.9711785560074802,89.4449712115231677],"hsluv":[12.1770506300652031,41.5103310668104939,89.4449712115231677]},"#eeddee":{"lch":[89.8888182614484919,14.0763196099440542,307.715012949260654],"luv":[89.8888182614484919,8.61096808409750203,-11.1352594229299271],"rgb":[0.933333333333333348,0.866666666666666696,0.933333333333333348],"xyz":[0.765454015636511942,0.760635055443196872,0.915410067939922745],"hpluv":[307.715012949260654,62.7286135322124423,89.8888182614484919],"hsluv":[307.715012949260654,40.6526002298302203,89.8888182614484919]},"#eeddff":{"lch":[90.3723799019863776,25.8444533047225526,286.436741223308786],"luv":[90.3723799019863776,7.31285770395748802,-24.7882609075678033],"rgb":[0.933333333333333348,0.866666666666666696,1],"xyz":[0.791625064046343363,0.771103474807129596,1.05324425623170459],"hpluv":[286.436741223308786,121.429851146909542,90.3723799019863776],"hsluv":[286.436741223308786,99.9999999999912461,90.3723799019863776]},"#776600":{"lch":[43.3967364031710616,48.7618731822316747,71.5665709091534836],"luv":[43.3967364031710616,15.4186312136361749,46.2599836547520695],"rgb":[0.466666666666666674,0.4,0],"xyz":[0.123587421414484214,0.134249841414458615,0.0194032630055357667],"hpluv":[71.5665709091534836,142.581321953300886,43.3967364031710616],"hsluv":[71.5665709091534836,100.000000000002203,43.3967364031710616]},"#776611":{"lch":[43.4563559440565683,46.5521225716425278,71.0105555334865812],"luv":[43.4563559440565683,15.1477793781447385,44.0186880294754204],"rgb":[0.466666666666666674,0.4,0.0666666666666666657],"xyz":[0.124599086914121332,0.134654507614313468,0.0247313679702914],"hpluv":[71.0105555334865812,135.933189968578517,43.4563559440565683],"hsluv":[71.0105555334865812,95.1446041192036,43.4563559440565683]},"#776622":{"lch":[43.5665595032511135,42.5748985848782695,69.8598868578742582],"luv":[43.5665595032511135,14.6592646006394602,39.9715892964128727],"rgb":[0.466666666666666674,0.4,0.133333333333333331],"xyz":[0.126474445052598361,0.135404650869704296,0.0346082541662705925],"hpluv":[69.8598868578742582,124.005139157578384,43.5665595032511135],"hsluv":[69.8598868578742582,86.4059851293991699,43.5665595032511135]},"#776633":{"lch":[43.7471247164395862,36.3538810550032,67.535659471968259],"luv":[43.7471247164395862,13.8911218179866047,33.5952586297408544],"rgb":[0.466666666666666674,0.4,0.2],"xyz":[0.129562195785056095,0.136639751162687395,0.0508704080238817358],"hpluv":[67.535659471968259,105.448545621014873,43.7471247164395862],"hsluv":[67.535659471968259,72.7162107178341,43.7471247164395862]},"#776644":{"lch":[44.0059094002531381,28.0798421405657628,62.7556004259007807],"luv":[44.0059094002531381,12.8545871201011757,24.9647175952547435],"rgb":[0.466666666666666674,0.4,0.266666666666666663],"xyz":[0.134020191029274982,0.138422949260375,0.0743491829767683743],"hpluv":[62.7556004259007807,80.969785387169,44.0059094002531381],"hsluv":[62.7556004259007807,54.3487361937933,44.0059094002531381]},"#776655":{"lch":[44.348573895236818,18.4503377813959695,51.0995937541716287],"luv":[44.348573895236818,11.5862323365157813,14.3587668165439446],"rgb":[0.466666666666666674,0.4,0.333333333333333315],"xyz":[0.139982605068636967,0.140807914876119822,0.10575123025074229],"hpluv":[51.0995937541716287,52.7914984191523473,44.348573895236818],"hsluv":[51.0995937541716287,32.0544530960382303,44.348573895236818]},"#776666":{"lch":[44.7789425039584543,10.3722772339139464,12.177050630063178],"luv":[44.7789425039584543,10.1389057710203723,2.18785849257654696],"rgb":[0.466666666666666674,0.4,0.4],"xyz":[0.147567600842145152,0.143841913185523113,0.145698874657886251],"hpluv":[12.177050630063178,29.3927086392471182,44.7789425039584543],"hsluv":[12.177050630063178,6.88762268030489189,44.7789425039584543]},"#776677":{"lch":[45.2992151274866899,14.0123007519981453,307.715012949251729],"luv":[45.2992151274866899,8.57180555029232849,-11.0846164558105222],"rgb":[0.466666666666666674,0.4,0.466666666666666674],"xyz":[0.156881613862347058,0.147567518393603936,0.194752676564284161],"hpluv":[307.715012949251729,39.2516664249610088,45.2992151274866899],"hsluv":[307.715012949251729,13.5798811229143528,45.2992151274866899]},"#776688":{"lch":[45.9101336093725934,25.9566305949561666,285.511882327844],"luv":[45.9101336093725934,6.94179491604529719,-25.0111606125461954],"rgb":[0.466666666666666674,0.4,0.533333333333333326],"xyz":[0.168022030964290886,0.152023685234381534,0.253425539967856328],"hpluv":[285.511882327844,71.7429261821633304,45.9101336093725934],"hsluv":[285.511882327844,20.4210179876919788,45.9101336093725934]},"#776699":{"lch":[46.6111419677041781,39.5636276789354753,277.694853057332239],"luv":[46.6111419677041781,5.29745753469698322,-39.2073663842078446],"rgb":[0.466666666666666674,0.4,0.6],"xyz":[0.181078995073363613,0.157246470878010691,0.322192217608974207],"hpluv":[277.694853057332239,107.707436484347127,46.6111419677041781],"hsluv":[277.694853057332239,30.3115853005661187,46.6111419677041781]},"#7766aa":{"lch":[47.4005539940279945,53.5010996637438936,273.94010537559177],"luv":[47.4005539940279945,3.67625443841083355,-53.3746458398921959],"rgb":[0.466666666666666674,0.4,0.66666666666666663],"xyz":[0.196136688038616142,0.163269548064111786,0.401496067225972808],"hpluv":[273.94010537559177,143.224929654224553,47.4005539940279945],"hsluv":[273.94010537559177,41.7476492103462178,47.4005539940279945]},"#7766bb":{"lch":[48.2757296522395052,67.3356727079262356,271.790784098674919],"luv":[48.2757296522395052,2.10423911354751,-67.3027859511178],"rgb":[0.466666666666666674,0.4,0.733333333333333282],"xyz":[0.213274280145952788,0.170124584907046544,0.491754052324614555],"hpluv":[271.790784098674919,176.992833999799871,48.2757296522395052],"hsluv":[271.790784098674919,53.26174830093764,48.2757296522395052]},"#7766cc":{"lch":[49.2332558923506838,80.8618894300095263,270.423298283194697],"luv":[49.2332558923506838,0.597398061184884921,-80.8596826468393886],"rgb":[0.466666666666666674,0.4,0.8],"xyz":[0.232566655589674542,0.177841535084535346,0.59336056299488471],"hpluv":[270.423298283194697,208.412927467485218,49.2332558923506838],"hsluv":[270.423298283194697,64.812294986267446,49.2332558923506838]},"#7766dd":{"lch":[50.2691251722936698,93.9789905273776327,269.490145829531343],"luv":[50.2691251722936698,-0.836273601566472236,-93.9752696564806769],"rgb":[0.466666666666666674,0.4,0.866666666666666696],"xyz":[0.254084981140308086,0.18644886530478888,0.706690410894890708],"hpluv":[269.490145829531343,237.229544488008315,50.2691251722936698],"hsluv":[269.490145829531343,76.411365903071939,50.2691251722936698]},"#7766ee":{"lch":[51.378904811117593,106.647241299318011,268.82087794331261],"luv":[51.378904811117593,-2.19459861633770803,-106.624658562961542],"rgb":[0.466666666666666674,0.4,0.933333333333333348],"xyz":[0.277897161406482796,0.195973737411258925,0.832101226963413931],"hpluv":[268.82087794331261,263.392927722023558,51.378904811117593],"hsluv":[268.82087794331261,88.1124589032604177,51.378904811117593]},"#7766ff":{"lch":[52.5578914047646748,118.864223743766644,268.322642340607558],"luv":[52.5578914047646748,-3.47930230784918848,-118.81329109850806],"rgb":[0.466666666666666674,0.4,1],"xyz":[0.304068209816314217,0.206442156775191621,0.969935415255195776],"hpluv":[268.322642340607558,286.980608281330717,52.5578914047646748],"hsluv":[268.322642340607558,99.9999999999990621,52.5578914047646748]},"#eeee00":{"lch":[91.3819857871042416,100.73955854358779,85.8743202181747591],"luv":[91.3819857871042416,7.24765584469138,100.478505862268193],"rgb":[0.933333333333333348,0.933333333333333348,0],"xyz":[0.658323051985028163,0.793268712127427555,0.118438362762215782],"hpluv":[85.8743202181747591,533.074105620447313,91.3819857871042416],"hsluv":[85.8743202181747591,100.000000000002302,91.3819857871042416]},"#eeee11":{"lch":[91.4002420948697,99.9206079899190485,85.8743202181747449],"luv":[91.4002420948697,7.18873686735402107,99.6616775060856526],"rgb":[0.933333333333333348,0.933333333333333348,0.0666666666666666657],"xyz":[0.659334717484665322,0.793673378327282464,0.123766467726971419],"hpluv":[85.8743202181747449,529.940172906192515,91.4002420948697],"hsluv":[85.8743202181747449,99.1672499526241182,91.4002420948697]},"#eeee22":{"lch":[91.4340680155716,98.4094794247264559,85.8743202181747],"luv":[91.4340680155716,7.08001949817026599,98.1544648222952105],"rgb":[0.933333333333333348,0.933333333333333348,0.133333333333333331],"xyz":[0.661210075623142268,0.794423521582673264,0.133643353922950597],"hpluv":[85.8743202181747,524.128135432551403,91.4340680155716],"hsluv":[85.8743202181747,97.631382664670781,91.4340680155716]},"#eeee33":{"lch":[91.4897155548310224,95.9410009888260475,85.874320218174617],"luv":[91.4897155548310224,6.90242608380469225,95.6923831080380864],"rgb":[0.933333333333333348,0.933333333333333348,0.2],"xyz":[0.664297826355600085,0.795658621875656391,0.149905507780561748],"hpluv":[85.874320218174617,514.550466890897383,91.4897155548310224],"hsluv":[85.874320218174617,95.1245282264191,91.4897155548310224]},"#eeee44":{"lch":[91.569956182858661,92.4193611961338917,85.8743202181745],"luv":[91.569956182858661,6.64906351605686385,92.1798691594911],"rgb":[0.933333333333333348,0.933333333333333348,0.266666666666666663],"xyz":[0.668755821599819,0.797441819973344,0.173384282733448386],"hpluv":[85.8743202181745,500.701064757674544,91.569956182858661],"hsluv":[85.8743202181745,91.5525623856835438,91.569956182858661]},"#eeee55":{"lch":[91.6770884747666912,87.7855710128071536,85.8743202181743612],"luv":[91.6770884747666912,6.31568785915728625,87.5580868047623682],"rgb":[0.933333333333333348,0.933333333333333348,0.333333333333333315],"xyz":[0.674718235639180874,0.799826785589088707,0.204786330007422301],"hpluv":[85.8743202181743612,482.129233036801622,91.6770884747666912],"hsluv":[85.8743202181743612,86.8606098102410584,91.6770884747666912]},"#eeee66":{"lch":[91.8130678710263197,82.0131153940214261,85.8743202181741481],"luv":[91.8130678710263197,5.90039150181238092,81.8005897091115912],"rgb":[0.933333333333333348,0.933333333333333348,0.4],"xyz":[0.682303231412689115,0.802860783898492,0.244733974414566263],"hpluv":[85.8743202181741481,458.402522986492727,91.8130678710263197],"hsluv":[85.8743202181741481,81.0287908474737435,91.8130678710263197]},"#eeee77":{"lch":[91.9795762822891163,75.1051560234190703,85.8743202181738923],"luv":[91.9795762822891163,5.40340190972927914,74.9105313631138],"rgb":[0.933333333333333348,0.933333333333333348,0.466666666666666674],"xyz":[0.691617244432891,0.806586389106572876,0.2937877763209642],"hpluv":[85.8743202181738923,429.072804464814112,91.9795762822891163],"hsluv":[85.8743202181738923,74.0694118243717838,91.9795762822891163]},"#eeee88":{"lch":[92.1780636375363542,67.0918310376028444,85.8743202181734233],"luv":[92.1780636375363542,4.82688735568056,66.9179718045445497],"rgb":[0.933333333333333348,0.933333333333333348,0.533333333333333326],"xyz":[0.702757661534834877,0.811042555947350419,0.35246063972453634],"hpluv":[85.8743202181734233,393.637417052406249,92.1780636375363542],"hsluv":[85.8743202181734233,66.0241185279663085,92.1780636375363542]},"#eeee99":{"lch":[92.4097746137617264,58.0271343519240759,85.8743202181728549],"luv":[92.4097746137617264,4.17473240419888647,57.8767650309654],"rgb":[0.933333333333333348,0.933333333333333348,0.6],"xyz":[0.715814625643907521,0.816265341590979632,0.421227317365654219],"hpluv":[85.8743202181728549,351.489405737917593,92.4097746137617264],"hsluv":[85.8743202181728549,56.9604957584853295,92.4097746137617264]},"#eeeeaa":{"lch":[92.6757669457712,47.9852530272659195,85.8743202181718885],"luv":[92.6757669457712,3.45227440531043239,47.8609058577161903],"rgb":[0.933333333333333348,0.933333333333333348,0.66666666666666663],"xyz":[0.730872318609160132,0.822288418777080699,0.500531166982652875],"hpluv":[85.8743202181718885,301.850049763156903,92.6757669457712],"hsluv":[85.8743202181718885,46.9680087594427178,92.6757669457712]},"#eeeebb":{"lch":[92.9769247593952741,37.0564456712956627,85.8743202181703253],"luv":[92.9769247593952741,2.66600696822725,36.9604189997225347],"rgb":[0.933333333333333348,0.933333333333333348,0.733333333333333282],"xyz":[0.748009910716496695,0.829143455620015457,0.590789152081294566],"hpluv":[85.8743202181703253,243.673832720465015,92.9769247593952741],"hsluv":[85.8743202181703253,36.1533987495139,92.9769247593952741]},"#eeeecc":{"lch":[93.3139689281547788,25.3426537884513579,85.8743202181670711],"luv":[93.3139689281547788,1.82326422217474082,25.2769817940640493],"rgb":[0.933333333333333348,0.933333333333333348,0.8],"xyz":[0.76730228616021845,0.836860405797504314,0.692395662751564722],"hpluv":[85.8743202181670711,175.509740066336235,93.3139689281547788],"hsluv":[85.8743202181670711,24.635760697738192,93.3139689281547788]},"#eeeedd":{"lch":[93.6874656794852143,12.9530841337210045,85.8743202181571377],"luv":[93.6874656794852143,0.931902991100789779,12.919518001474831],"rgb":[0.933333333333333348,0.933333333333333348,0.866666666666666696],"xyz":[0.788820611710852,0.84546773601775782,0.80572551065157072],"hpluv":[85.8743202181571377,95.2907564198152812,93.6874656794852143],"hsluv":[85.8743202181571377,12.5415797244653469,93.6874656794852143]},"#eeeeee":{"lch":[94.0978342288501466,4.90671076366048496e-12,0],"luv":[94.0978342288501466,4.6515081442594671e-12,1.56182025281704723e-12],"rgb":[0.933333333333333348,0.933333333333333348,0.933333333333333348],"xyz":[0.812632791977026758,0.854992608124227838,0.931136326720093943],"hpluv":[0,3.87295813278393312e-11,94.0978342288501466],"hsluv":[0,3.8714510065860851e-11,94.0978342288501466]},"#eeeeff":{"lch":[94.5453539438838391,13.4050739145486393,265.874320218197],"luv":[94.5453539438838391,-0.964421163933715353,-13.3703365130825738],"rgb":[0.933333333333333348,0.933333333333333348,1],"xyz":[0.838803840386858179,0.865461027488160561,1.06897051501187579],"hpluv":[265.874320218197,114.885858328026472,94.5453539438838391],"hsluv":[265.874320218197,99.999999999981938,94.5453539438838391]},"#777700":{"lch":[48.4055282063088868,53.3622847060179737,85.8743202181747],"luv":[48.4055282063088868,3.83912219020021839,53.2240036999738706],"rgb":[0.466666666666666674,0.466666666666666674,0],"xyz":[0.142041159467901856,0.171157317521294372,0.0255545090233414707],"hpluv":[85.8743202181747,139.887458074797621,48.4055282063088868],"hsluv":[85.8743202181747,100.000000000002331,48.4055282063088868]},"#777711":{"lch":[48.4562461221814829,51.3697569370731628,85.874320218174617],"luv":[48.4562461221814829,3.69577080233359201,51.2366392921178502],"rgb":[0.466666666666666674,0.466666666666666674,0.0666666666666666657],"xyz":[0.143052824967539,0.171561983721149225,0.0308826139880971],"hpluv":[85.874320218174617,134.523163178361983,48.4562461221814829],"hsluv":[85.874320218174617,96.1652781669932324,48.4562461221814829]},"#777722":{"lch":[48.5500530694338579,47.7524066126053768,85.8743202181744323],"luv":[48.5500530694338579,3.43552238949120969,47.6286628324468282],"rgb":[0.466666666666666674,0.466666666666666674,0.133333333333333331],"xyz":[0.144928183106016,0.172312126976540053,0.0407595001840763],"hpluv":[85.8743202181744323,124.808706278838557,48.5500530694338579],"hsluv":[85.8743202181744323,89.2207979160694293,48.5500530694338579]},"#777733":{"lch":[48.7039135074563063,42.0024110114446501,85.874320218174],"luv":[48.7039135074563063,3.02184190658892726,41.8935675523809934],"rgb":[0.466666666666666674,0.466666666666666674,0.2],"xyz":[0.148015933838473723,0.173547227269523152,0.0570216540416874432],"hpluv":[85.874320218174,109.433348296247431,48.7039135074563063],"hsluv":[85.874320218174,78.2295638238963704,48.7039135074563063]},"#777744":{"lch":[48.9247697387985312,34.1210359912247227,85.8743202181732528],"luv":[48.9247697387985312,2.45482042510454956,34.0326160292585],"rgb":[0.466666666666666674,0.466666666666666674,0.266666666666666663],"xyz":[0.152473929082692611,0.175330425367210746,0.0805004289945740747],"hpluv":[85.8743202181732528,88.497855114462908,48.9247697387985312],"hsluv":[85.8743202181732528,63.2636094274766378,48.9247697387985312]},"#777755":{"lch":[49.2178287935421537,24.2815247917763841,85.8743202181716327],"luv":[49.2178287935421537,1.74692184102758041,24.2186025669317253],"rgb":[0.466666666666666674,0.466666666666666674,0.333333333333333315],"xyz":[0.158436343122054624,0.177715390982955579,0.11190247626854799],"hpluv":[85.8743202181716327,62.6026663147617,49.2178287935421537],"hsluv":[85.8743202181716327,44.7521651878825324,49.2178287935421537]},"#777766":{"lch":[49.5868745078829676,12.7836483150604128,85.8743202181664742],"luv":[49.5868745078829676,0.919713018071489197,12.750521252385596],"rgb":[0.466666666666666674,0.466666666666666674,0.4],"xyz":[0.166021338895562781,0.18074938929235887,0.151850120675691952],"hpluv":[85.8743202181664742,32.7135296885410085,49.5868745078829676],"hsluv":[85.8743202181664742,23.385605928338574,49.5868745078829676]},"#777777":{"lch":[50.0344387925380687,2.67192546523751356e-12,0],"luv":[50.0344387925380687,2.52749706171116152e-12,8.66570421158112546e-13],"rgb":[0.466666666666666674,0.466666666666666674,0.466666666666666674],"xyz":[0.175335351915764714,0.184474994500439693,0.200903922582089861],"hpluv":[0,6.77633132918180515e-12,50.0344387925380687],"hsluv":[0,1.94020402484744743e-12,50.0344387925380687]},"#777788":{"lch":[50.5619220099523545,13.6772258441596737,265.874320218187563],"luv":[50.5619220099523545,-0.984000994856362388,-13.6417831984777926],"rgb":[0.466666666666666674,0.466666666666666674,0.533333333333333326],"xyz":[0.186475769017708515,0.188931161341217291,0.259576785985662029],"hpluv":[265.874320218187563,34.3252548472133,50.5619220099523545],"hsluv":[265.874320218187563,11.1797639211738336,50.5619220099523545]},"#777799":{"lch":[51.1696982290560669,27.8798810968622455,265.87432021818239],"luv":[51.1696982290560669,-2.00580373888746255,-27.8076342276045096],"rgb":[0.466666666666666674,0.466666666666666674,0.6],"xyz":[0.199532733126781242,0.194153946984846448,0.328343463626779908],"hpluv":[265.87432021818239,69.1380916549642563,51.1696982290560669],"hsluv":[265.87432021818239,22.9846011782403536,51.1696982290560669]},"#7777aa":{"lch":[51.8572203345112,42.2927406525888614,265.874320218180628],"luv":[51.8572203345112,-3.04272952363285398,-42.1831448442758443],"rgb":[0.466666666666666674,0.466666666666666674,0.66666666666666663],"xyz":[0.21459042609203377,0.200177024170947543,0.407647313243778564],"hpluv":[265.874320218180628,103.489412577951782,51.8572203345112],"hsluv":[265.874320218180628,35.2207148819814435,51.8572203345112]},"#7777bb":{"lch":[52.6231302285762439,56.6671214785474,265.874320218179832],"luv":[52.6231302285762439,-4.07688697591046,-56.5202764435038816],"rgb":[0.466666666666666674,0.466666666666666674,0.733333333333333282],"xyz":[0.231728018199370445,0.207032061013882301,0.497905298342420255],"hpluv":[265.874320218179832,136.645017565491173,52.6231302285762439],"hsluv":[265.874320218179832,47.751077300634,52.6231302285762439]},"#7777cc":{"lch":[53.4653742998309696,70.8221320427834513,265.874320218179378],"luv":[53.4653742998309696,-5.09526194727884452,-70.6386062488075],"rgb":[0.466666666666666674,0.466666666666666674,0.8],"xyz":[0.251020393643092143,0.214749011191371103,0.599511809012690411],"hpluv":[265.874320218179378,168.087613695837888,53.4653742998309696],"hsluv":[265.874320218179378,60.4968982098403671,53.4653742998309696]},"#7777dd":{"lch":[54.3813217092404102,84.6378263199215581,265.874320218179037],"luv":[54.3813217092404102,-6.08922498249284594,-84.4184990584042083],"rgb":[0.466666666666666674,0.466666666666666674,0.866666666666666696],"xyz":[0.272538719193725743,0.223356341411624637,0.712841656912696409],"hpluv":[265.874320218179037,197.494074150637061,54.3813217092404102],"hsluv":[265.874320218179037,73.4330970128385445,54.3813217092404102]},"#7777ee":{"lch":[55.3678819060931,98.0443353361264656,265.874320218178809],"luv":[55.3678819060931,-7.05374939408338086,-97.7902669542745713],"rgb":[0.466666666666666674,0.466666666666666674,0.933333333333333348],"xyz":[0.296350899459900452,0.232881213518094682,0.838252472981219632],"hpluv":[265.874320218178809,224.700440227144242,55.3678819060931],"hsluv":[265.874320218178809,86.5809092876847,55.3678819060931]},"#7777ff":{"lch":[56.4216176153771158,111.010032393554411,265.874320218178696],"luv":[56.4216176153771158,-7.98655981550332,-110.722365194803288],"rgb":[0.466666666666666674,0.466666666666666674,1],"xyz":[0.322521947869731873,0.243349632882027378,0.976086661273001477],"hpluv":[265.874320218178696,249.664056525023511,56.4216176153771158],"hsluv":[265.874320218178696,99.99999999999892,56.4216176153771158]},"#eeff00":{"lch":[95.8710857598618702,106.837421111995667,91.929871543819857],"luv":[95.8710857598618702,-3.59788306357601462,106.776822332015158],"rgb":[0.933333333333333348,1,0],"xyz":[0.710175424414702,0.896973456986776663,0.135722486905439893],"hpluv":[91.929871543819857,1221.941869030823,95.8710857598618702],"hsluv":[91.929871543819857,100.000000000002217,95.8710857598618702]},"#eeff11":{"lch":[95.8879066370154192,106.077593073364397,91.9648080695471748],"luv":[95.8879066370154192,-3.63693915095535747,106.015227330089402],"rgb":[0.933333333333333348,1,0.0666666666666666657],"xyz":[0.71118708991433921,0.897378123186631571,0.14105059187019553],"hpluv":[91.9648080695471748,1218.36968740939687,95.8879066370154192],"hsluv":[91.9648080695471748,99.9999999999846096,95.8879066370154192]},"#eeff22":{"lch":[95.9190746883988083,104.674935415410417,92.0306447189025221],"luv":[95.9190746883988083,-3.7090534526558554,104.609201443781629],"rgb":[0.933333333333333348,1,0.133333333333333331],"xyz":[0.713062448052816156,0.898128266442022372,0.150927478066174708],"hpluv":[92.0306447189025221,1211.72771901411761,95.9190746883988083],"hsluv":[92.0306447189025221,99.9999999999843254,95.9190746883988083]},"#eeff33":{"lch":[95.9703546560603,102.38192729680722,92.1421884898051076],"luv":[95.9703546560603,-3.826988551916231,102.31037677397255],"rgb":[0.933333333333333348,1,0.2],"xyz":[0.716150198785274,0.899363366735005498,0.167189631923785859],"hpluv":[92.1421884898051076,1200.73225066318059,95.9703546560603],"hsluv":[92.1421884898051076,99.9999999999843,95.9703546560603]},"#eeff44":{"lch":[96.0443082677326316,99.1069805797193482,92.3105231291127666],"luv":[96.0443082677326316,-3.99552836231266495,99.0264073504377],"rgb":[0.933333333333333348,1,0.266666666666666663],"xyz":[0.720608194029492832,0.901146564832693064,0.190668406876672497],"hpluv":[92.3105231291127666,1184.71704479359755,96.0443082677326316],"hsluv":[92.3105231291127666,99.999999999984,96.0443082677326316]},"#eeff55":{"lch":[96.1430663154878,94.7914212488158086,92.5502587525383831],"luv":[96.1430663154878,-4.21781218752730513,94.6975374691506744],"rgb":[0.933333333333333348,1,0.333333333333333315],"xyz":[0.726570608068854762,0.903531530448437814,0.222070454150646412],"hpluv":[92.5502587525383831,1163.01160371768833,96.1430663154878],"hsluv":[92.5502587525383831,99.9999999999832312,96.1430663154878]},"#eeff66":{"lch":[96.2684490440968261,89.4056432425818315,92.8821944044194083],"luv":[96.2684490440968261,-4.49554545672610839,89.2925479234763912],"rgb":[0.933333333333333348,1,0.4],"xyz":[0.734155603842363,0.906565528757841133,0.262018098557790347],"hpluv":[92.8821944044194083,1134.86447310352946,96.2684490440968261],"hsluv":[92.8821944044194083,99.9999999999826912,96.2684490440968261]},"#eeff77":{"lch":[96.4220309754045104,82.947030730659,93.3376121292550494],"luv":[96.4220309754045104,-4.82912482473711879,82.8063370791148827],"rgb":[0.933333333333333348,1,0.466666666666666674],"xyz":[0.743469616862564853,0.910291133965922,0.311071900464188311],"hpluv":[93.3376121292550494,1099.34916045901696,96.4220309754045104],"hsluv":[93.3376121292550494,99.9999999999824354,96.4220309754045104]},"#eeff88":{"lch":[96.6051797330544559,75.4384320618763553,93.966065888554354],"luv":[96.6051797330544559,-5.21774760700183382,75.2577713054536446],"rgb":[0.933333333333333348,1,0.533333333333333326],"xyz":[0.754610033964508764,0.914747300806699526,0.369744763867760451],"hpluv":[93.966065888554354,1055.22684316018899,96.6051797330544559],"hsluv":[93.966065888554354,99.9999999999810711,96.6051797330544559]},"#eeff99":{"lch":[96.8190810543072,66.9270103141668926,94.8508818247366],"luv":[96.8190810543072,-5.65953211211599072,66.6872881879637589],"rgb":[0.933333333333333348,1,0.6],"xyz":[0.767666998073581408,0.919970086450328739,0.43851144150887833],"hpluv":[94.8508818247366,1000.72847038880911,96.8190810543072],"hsluv":[94.8508818247366,99.9999999999804,96.8190810543072]},"#eeffaa":{"lch":[97.0647558759300182,57.4841926777339438,96.1432585072641785],"luv":[97.0647558759300182,-6.15165762861061438,57.1540857352402938],"rgb":[0.933333333333333348,1,0.66666666666666663],"xyz":[0.782724691038834,0.925993163636429806,0.517815291125877],"hpluv":[96.1432585072641785,933.191048721381,97.0647558759300182],"hsluv":[96.1432585072641785,99.9999999999786127,97.0647558759300182]},"#eeffbb":{"lch":[97.3430726615411857,47.2094061136043166,98.1473956779809242],"luv":[97.3430726615411857,-6.69052372550339935,46.7329104366258861],"rgb":[0.933333333333333348,1,0.733333333333333282],"xyz":[0.799862283146170583,0.932848200479364564,0.608073276224518677],"hpluv":[98.1473956779809242,848.432918810296883,97.3430726615411857],"hsluv":[98.1473956779809242,99.9999999999760689,97.3430726615411857]},"#eeffcc":{"lch":[97.6547568121604,36.2474903433398126,101.573139749476653],"luv":[97.6547568121604,-7.27192346215819718,35.5105573787715443],"rgb":[0.933333333333333348,1,0.8],"xyz":[0.819154658589892337,0.940565150656853421,0.709679786894788833],"hpluv":[101.573139749476653,739.717147432346792,97.6547568121604],"hsluv":[101.573139749476653,99.999999999972971,97.6547568121604]},"#eeffdd":{"lch":[98.0003982932414743,24.8675243497069864,108.50155795297276],"luv":[98.0003982932414743,-7.89122250686935445,23.5822470225029335],"rgb":[0.933333333333333348,1,0.866666666666666696],"xyz":[0.840672984140525936,0.949172480877106928,0.823009634794794831],"hpluv":[108.50155795297276,596.728870831386416,98.0003982932414743],"hsluv":[108.50155795297276,99.9999999999688072,98.0003982932414743]},"#eeffee":{"lch":[98.3804582036479,13.96608756186059,127.715012949221688],"luv":[98.3804582036479,-8.54353535492541205,11.0480588984987129],"rgb":[0.933333333333333348,1,0.933333333333333348],"xyz":[0.864485164406700646,0.958697352983577,0.948420450863318],"hpluv":[127.715012949221688,414.943064796341446,98.3804582036479],"hsluv":[127.715012949221688,99.9999999999608491,98.3804582036479]},"#eeffff":{"lch":[98.7952747621608438,9.43620053547767768,192.177050630058204],"luv":[98.7952747621608438,-9.22389036737693679,-1.99040876112424892],"rgb":[0.933333333333333348,1,1],"xyz":[0.890656212816532067,0.969165772347509669,1.0862546391551],"hpluv":[192.177050630058204,378.040319927501173,98.7952747621608438],"hsluv":[192.177050630058204,99.999999999948713,98.7952747621608438]},"#778800":{"lch":[53.5249548615687303,60.5938372915660253,96.5029793655947259],"luv":[53.5249548615687303,-6.86254771619202231,60.2039745910497572],"rgb":[0.466666666666666674,0.533333333333333326,0],"xyz":[0.164113529192872309,0.215302056971235917,0.0329119655983314136],"hpluv":[96.5029793655947259,143.651932639250902,53.5249548615687303],"hsluv":[96.5029793655947259,100.000000000002373,53.5249548615687303]},"#778811":{"lch":[53.5684856288310272,58.8481082257442409,96.7583563096261088],"luv":[53.5684856288310272,-6.9253766303469888,58.4391906196240143],"rgb":[0.466666666666666674,0.533333333333333326,0.0666666666666666657],"xyz":[0.165125194692509442,0.21570672317109077,0.0382400705630870433],"hpluv":[96.7583563096261088,139.399900460742,53.5684856288310272],"hsluv":[96.7583563096261088,96.9357421545617,53.5684856288310272]},"#778822":{"lch":[53.6490362820541407,55.6690512282284615,97.2652817371477],"luv":[53.6490362820541407,-7.04010577997538,55.2220986133077147],"rgb":[0.466666666666666674,0.533333333333333326,0.133333333333333331],"xyz":[0.167000552830986443,0.216456866426481598,0.0481169567590662428],"hpluv":[97.2652817371477,131.671329649176641,53.6490362820541407],"hsluv":[97.2652817371477,91.3605066517003337,53.6490362820541407]},"#778833":{"lch":[53.7812573057213,50.5917713326567053,98.2096275165039856],"luv":[53.7812573057213,-7.22426441450666346,50.0733195448945381],"rgb":[0.466666666666666674,0.533333333333333326,0.2],"xyz":[0.170088303563444176,0.217691966719464697,0.064379110616677393],"hpluv":[98.2096275165039856,119.368094402160139,53.7812573057213],"hsluv":[98.2096275165039856,82.4672262716137539,53.7812573057213]},"#778844":{"lch":[53.9712743947247162,43.5928841845235198,99.8805739619221811],"luv":[53.9712743947247162,-7.48032491712259429,42.9462954241638357],"rgb":[0.466666666666666674,0.533333333333333326,0.266666666666666663],"xyz":[0.174546298807663064,0.219475164817152291,0.0878578855695640315],"hpluv":[99.8805739619221811,102.492540276722124,53.9712743947247162],"hsluv":[99.8805739619221811,70.2195807792145672,53.9712743947247162]},"#778855":{"lch":[54.2238135722873,34.8221097745323078,102.954066597198249],"luv":[54.2238135722873,-7.80606678579386326,33.9358903004666175],"rgb":[0.466666666666666674,0.533333333333333326,0.333333333333333315],"xyz":[0.180508712847025077,0.221860130432897124,0.119259932843537933],"hpluv":[102.954066597198249,81.4900057495692636,54.2238135722873],"hsluv":[102.954066597198249,54.8454134878709638,54.2238135722873]},"#778866":{"lch":[54.542475349788262,24.6519214642532667,109.417534351990682],"luv":[54.542475349788262,-8.19552569001744224,23.2497438812554442],"rgb":[0.466666666666666674,0.533333333333333326,0.4],"xyz":[0.188093708620533234,0.224894128742300414,0.159207577250681909],"hpluv":[109.417534351990682,57.3528835536874197,54.542475349788262],"hsluv":[109.417534351990682,36.7830408334215164,54.542475349788262]},"#778877":{"lch":[54.9298804020046703,14.1239929232838151,127.715012949231166],"luv":[54.9298804020046703,-8.6401314869560828,11.1729720300858784],"rgb":[0.466666666666666674,0.533333333333333326,0.466666666666666674],"xyz":[0.197407721640735168,0.228619733950381238,0.208261379157079818],"hpluv":[127.715012949231166,32.6278280107521041,54.9298804020046703],"hsluv":[127.715012949231166,16.6133799052879709,54.9298804020046703]},"#778888":{"lch":[55.3877640712208574,9.3400800844087577,192.177050630059369],"luv":[55.3877640712208574,-9.12993258220805437,-1.97013375878514596],"rgb":[0.466666666666666674,0.533333333333333326,0.533333333333333326],"xyz":[0.208548138742678968,0.233075900791158835,0.266934242560651958],"hpluv":[192.177050630059369,21.3981433643613,55.3877640712208574],"hsluv":[192.177050630059369,21.3143847536295468,55.3877640712208574]},"#778899":{"lch":[55.9170512374386703,18.5603965019494623,238.655736169794807],"luv":[55.9170512374386703,-9.65472976501325597,-15.851640655596027],"rgb":[0.466666666666666674,0.533333333333333326,0.6],"xyz":[0.221605102851751695,0.238298686434788,0.335700920201769892],"hpluv":[238.655736169794807,42.1194135101242679,55.9170512374386703],"hsluv":[238.655736169794807,26.1912729818073053,55.9170512374386703]},"#7788aa":{"lch":[56.5179258756863732,31.8473117719276075,251.310804784322386],"luv":[56.5179258756863732,-10.2049729927717898,-30.1680260095876527],"rgb":[0.466666666666666674,0.533333333333333326,0.66666666666666663],"xyz":[0.236662795817004223,0.244321763620889087,0.415004769818768493],"hpluv":[251.310804784322386,71.5032678590983721,56.5179258756863732],"hsluv":[251.310804784322386,31.1149405392867244,56.5179258756863732]},"#7788bb":{"lch":[57.1899017055338845,45.9393215269311881,256.438388794294667],"luv":[57.1899017055338845,-10.7723499078102041,-44.6584565319768387],"rgb":[0.466666666666666674,0.533333333333333326,0.733333333333333282],"xyz":[0.253800387924340898,0.251176800463823846,0.505262754917410239],"hpluv":[256.438388794294667,101.930593576715296,57.1899017055338845],"hsluv":[256.438388794294667,41.4357647235180622,57.1899017055338845]},"#7788cc":{"lch":[57.9318961804226547,60.1932243037694406,259.131204763586595],"luv":[57.9318961804226547,-11.3500713849249486,-59.1134513595764233],"rgb":[0.466666666666666674,0.533333333333333326,0.8],"xyz":[0.273092763368062652,0.258893750641312648,0.606869265587680395],"hpluv":[259.131204763586595,131.8466774878151,57.9318961804226547],"hsluv":[259.131204763586595,55.5026255722761661,57.9318961804226547]},"#7788dd":{"lch":[58.7423077351926679,74.3397271130694861,260.762999043897821],"luv":[58.7423077351926679,-11.9329115646152975,-73.3757497327060264],"rgb":[0.466666666666666674,0.533333333333333326,0.866666666666666696],"xyz":[0.29461108891869614,0.267501080861566209,0.720199113487686393],"hpluv":[260.762999043897821,160.586593274196787,58.7423077351926679],"hsluv":[260.762999043897821,69.9489143241080598,58.7423077351926679]},"#7788ee":{"lch":[59.6190949291064101,88.2283098571320465,261.84384456781811],"luv":[59.6190949291064101,-12.5170812067924775,-87.3358880318318285],"rgb":[0.466666666666666674,0.533333333333333326,0.933333333333333348],"xyz":[0.318423269184870905,0.277025952968036226,0.845609929556209616],"hpluv":[261.84384456781811,187.785436290663455,59.6190949291064101],"hsluv":[261.84384456781811,84.7689484415743237,59.6190949291064101]},"#7788ff":{"lch":[60.55985551741054,101.77153783075579,262.604380260147],"luv":[60.55985551741054,-13.1000102630052879,-100.924901008354297],"rgb":[0.466666666666666674,0.533333333333333326,1],"xyz":[0.344594317594702271,0.28749437233196895,0.983444117847991461],"hpluv":[262.604380260147,213.245970352856943,60.55985551741054],"hsluv":[262.604380260147,99.9999999999987921,60.55985551741054]},"#779900":{"lch":[58.6994568897504223,69.1976359459353603,103.993850276975294],"luv":[58.6994568897504223,-16.7332165977800322,67.1439668384135899],"rgb":[0.466666666666666674,0.6,0],"xyz":[0.189983129645339782,0.267041257876171612,0.0415351657491536685],"hpluv":[103.993850276975294,149.587912305351722,58.6994568897504223],"hsluv":[103.993850276975294,100.000000000002444,58.6994568897504223]},"#779911":{"lch":[58.7371702745588493,67.6759907780605232,104.326162498332039],"luv":[58.7371702745588493,-16.7458460058928829,65.5714600214076],"rgb":[0.466666666666666674,0.6,0.0666666666666666657],"xyz":[0.190994795144976914,0.267445924076026464,0.0468632707139093],"hpluv":[104.326162498332039,146.20456366036808,58.7371702745588493],"hsluv":[104.326162498332039,97.5197891999702,58.7371702745588493]},"#779922":{"lch":[58.8069803361006223,64.8997387816913,104.97431376012679],"luv":[58.8069803361006223,-16.7691829588827659,62.6958578920751464],"rgb":[0.466666666666666674,0.6,0.133333333333333331],"xyz":[0.192870153283453916,0.268196067331417265,0.0567401569098885],"hpluv":[104.97431376012679,140.04041716275114,58.8069803361006223],"hsluv":[104.97431376012679,92.9911396041894704,58.8069803361006223]},"#779933":{"lch":[58.9216385591388132,60.4528100001228097,106.14237332279157],"luv":[58.9216385591388132,-16.8074000849803582,58.0693855598141369],"rgb":[0.466666666666666674,0.6,0.2],"xyz":[0.195957904015911677,0.269431167624400392,0.0730023107674996341],"hpluv":[106.14237332279157,130.191012221085145,58.9216385591388132],"hsluv":[106.14237332279157,85.724115661044948,58.9216385591388132]},"#779944":{"lch":[59.0865618982540752,54.3006768161790845,108.091337704968765],"luv":[59.0865618982540752,-16.8621369683366211,51.6161974534756354],"rgb":[0.466666666666666674,0.6,0.266666666666666663],"xyz":[0.200415899260130537,0.271214365722087958,0.0964810857203862726],"hpluv":[108.091337704968765,116.615384645059635,59.0865618982540752],"hsluv":[108.091337704968765,75.6282332091309399,59.0865618982540752]},"#779955":{"lch":[59.3060149965037056,46.5647823410072945,111.326222899357617],"luv":[59.3060149965037056,-16.9345685221747893,43.3762532203180413],"rgb":[0.466666666666666674,0.6,0.333333333333333315],"xyz":[0.206378313299492522,0.273599331337832818,0.127883132994360188],"hpluv":[111.326222899357617,99.6318422422000367,59.3060149965037056],"hsluv":[111.326222899357617,62.8087826319283806,59.3060149965037056]},"#779966":{"lch":[59.5833535636433282,37.5662597340720836,116.949978591108717],"luv":[59.5833535636433282,-17.0254963867212936,33.4866591823296957],"rgb":[0.466666666666666674,0.6,0.4],"xyz":[0.213963309073000707,0.276633329647236137,0.16783077740150415],"hpluv":[116.949978591108717,80.0041177209304522,59.5833535636433282],"hsluv":[116.949978591108717,47.5357009492008,59.5833535636433282]},"#779977":{"lch":[59.921152065724,28.0111989307655129,127.71501294923614],"luv":[59.921152065724,-17.1354122862910181,22.1586306282167591],"rgb":[0.466666666666666674,0.6,0.466666666666666674],"xyz":[0.223277322093202613,0.280358934855316932,0.216884579307902059],"hpluv":[127.71501294923614,59.3185968765002301,59.921152065724],"hsluv":[127.71501294923614,30.2037385091367128,59.921152065724]},"#779988":{"lch":[60.3212826107906608,19.7788738743410271,150.794869664161695],"luv":[60.3212826107906608,-17.2645515875173317,9.65086059473676272],"rgb":[0.466666666666666674,0.6,0.533333333333333326],"xyz":[0.234417739195146468,0.28481510169609453,0.275557442711474199],"hpluv":[150.794869664161695,41.6073752981458043,60.3212826107906608],"hsluv":[150.794869664161695,33.5278156565034706,60.3212826107906608]},"#779999":{"lch":[60.7849725890424537,17.8137422376051369,192.177050630060307],"luv":[60.7849725890424537,-17.4129412378011317,-3.75751113860251662],"rgb":[0.466666666666666674,0.6,0.6],"xyz":[0.247474703304219168,0.290037887339723688,0.344324120352592133],"hpluv":[192.177050630060307,37.1876097830212373,60.7849725890424537],"hsluv":[192.177050630060307,37.0420465685486562,60.7849725890424537]},"#7799aa":{"lch":[61.3128540464897895,25.0117677757311512,225.340924984985747],"luv":[61.3128540464897895,-17.5804418295499474,-17.7909131959243076],"rgb":[0.466666666666666674,0.6,0.66666666666666663],"xyz":[0.262532396269471724,0.29606096452582481,0.423627969969590734],"hpluv":[225.340924984985747,51.7645188682129742,61.3128540464897895],"hsluv":[225.340924984985747,40.6599304384258815,61.3128540464897895]},"#7799bb":{"lch":[61.9050111975332129,36.7727368637825478,241.108601392342877],"luv":[61.9050111975332129,-17.766782778670386,-32.1958942436537],"rgb":[0.466666666666666674,0.6,0.733333333333333282],"xyz":[0.279669988376808343,0.302916001368759569,0.513885955068232425],"hpluv":[241.108601392342877,75.37710856774234,61.9050111975332129],"hsluv":[241.108601392342877,44.3026829048042927,61.9050111975332129]},"#7799cc":{"lch":[62.5610290710097274,50.0883036089361084,248.973595844380469],"luv":[62.5610290710097274,-17.971590279241223,-46.7531827927901],"rgb":[0.466666666666666674,0.6,0.8],"xyz":[0.298962363820530097,0.31063295154624837,0.615492465738502581],"hpluv":[248.973595844380469,101.594866952252644,62.5610290710097274],"hsluv":[248.973595844380469,49.5852758814030068,62.5610290710097274]},"#7799dd":{"lch":[63.2800443412030518,63.9273589508424962,253.464421950176984],"luv":[63.2800443412030518,-18.1944086519195096,-61.2835272829234228],"rgb":[0.466666666666666674,0.6,0.866666666666666696],"xyz":[0.320480689371163641,0.319240281766501877,0.728822313638508579],"hpluv":[253.464421950176984,128.191525099113591,63.2800443412030518],"hsluv":[253.464421950176984,65.7895471985927429,63.2800443412030518]},"#7799ee":{"lch":[64.0607982423263138,77.8622331281598292,256.304567564122067],"luv":[64.0607982423263138,-18.434716379612933,-75.6484539148497106],"rgb":[0.466666666666666674,0.6,0.933333333333333348],"xyz":[0.344292869637338406,0.328765153872971894,0.854233129707031802],"hpluv":[256.304567564122067,154.231767587931557,64.0607982423263138],"hsluv":[256.304567564122067,82.5871459557978085,64.0607982423263138]},"#7799ff":{"lch":[64.9016907971027,91.67329289213788,258.23504341307023],"luv":[64.9016907971027,-18.6919383586334327,-89.7474460365571929],"rgb":[0.466666666666666674,0.6,1],"xyz":[0.370463918047169771,0.339233573236904618,0.992067317998813758],"hpluv":[258.23504341307023,179.236372955016122,64.9016907971027],"hsluv":[258.23504341307023,99.9999999999984368,64.9016907971027]},"#660000":{"lch":[19.330201679573328,65.0080772249371819,12.1770506300617765],"luv":[19.330201679573328,63.5454254137925432,13.7123671721378795],"rgb":[0.4,0,0],"xyz":[0.0547936733227042463,0.0282529878070199789,0.00256845343700170745],"hpluv":[12.1770506300617765,426.746789183125202,19.330201679573328],"hsluv":[12.1770506300617765,100.000000000002217,19.330201679573328]},"#660011":{"lch":[19.4980803058243595,61.695772445130423,8.9911342856641614],"luv":[19.4980803058243595,60.9376877881905799,9.641916024853316],"rgb":[0.4,0,0.0666666666666666657],"xyz":[0.0558053388223413716,0.0286576540068748317,0.0078965584017573389],"hpluv":[8.9911342856641614,401.51602003210553,19.4980803058243595],"hsluv":[8.9911342856641614,99.9999999999966178,19.4980803058243595]},"#660022":{"lch":[19.8051492014688648,56.728751528179842,2.87530221933591967],"luv":[19.8051492014688648,56.6573341637173584,2.84565201787440492],"rgb":[0.4,0,0.133333333333333331],"xyz":[0.0576806969608183867,0.029407797262265653,0.0177734445977365332],"hpluv":[2.87530221933591967,363.466537566247382,19.8051492014688648],"hsluv":[2.87530221933591967,99.9999999999968594,19.8051492014688648]},"#660033":{"lch":[20.2995520444984123,51.2727836305606957,352.516911450402631],"luv":[20.2995520444984123,50.836110988726972,-6.67743669143524787],"rgb":[0.4,0,0.2],"xyz":[0.0607684476932761272,0.0306428975552487659,0.0340355984553476765],"hpluv":[352.516911450402631,320.508659944055125,20.2995520444984123],"hsluv":[352.516911450402631,99.9999999999973852,20.2995520444984123]},"#660044":{"lch":[20.9904438433464762,47.7800185043531087,338.095292373375855],"luv":[20.9904438433464762,44.3305689635839855,-17.8250055719836844],"rgb":[0.4,0,0.266666666666666663],"xyz":[0.0652264429374950078,0.0324260956529363389,0.057514373408234315],"hpluv":[338.095292373375855,288.844444062118953,20.9904438433464762],"hsluv":[338.095292373375855,99.999999999997911,20.9904438433464762]},"#660055":{"lch":[21.8759682447435324,48.0792454528985687,322.009867044845],"luv":[21.8759682447435324,37.8920594608334085,-29.5940141436867741],"rgb":[0.4,0,0.333333333333333315],"xyz":[0.071188856976857,0.0348110612686811718,0.0889164206822082304],"hpluv":[322.009867044845,278.887908627948889,21.8759682447435324],"hsluv":[322.009867044845,99.9999999999984,21.8759682447435324]},"#660066":{"lch":[22.9458380566939866,52.2668983658326383,307.715012949243601],"luv":[22.9458380566939866,31.9734565677830815,-41.3464235441515271],"rgb":[0.4,0,0.4],"xyz":[0.0787738527503651781,0.0378450595780844834,0.128864065089352192],"hpluv":[307.715012949243601,289.042783730483393,22.9458380566939866],"hsluv":[307.715012949243601,99.9999999999988,22.9458380566939866]},"#660077":{"lch":[24.1840444716539054,59.1802438936044553,296.875135467660698],"luv":[24.1840444716539054,26.7522904844618061,-52.7884099129864097],"rgb":[0.4,0,0.466666666666666674],"xyz":[0.0880878657705671,0.0415706647861653,0.177917866995750101],"hpluv":[296.875135467660698,310.518260327731298,24.1840444716539054],"hsluv":[296.875135467660698,99.9999999999992326,24.1840444716539054]},"#660088":{"lch":[25.5714349826340381,67.6035320092512819,289.201479741547303],"luv":[25.5714349826340381,22.2341957091879898,-63.8426039670327725],"rgb":[0.4,0,0.533333333333333326],"xyz":[0.0992282828725109256,0.0460268316269428907,0.236590730399322269],"hpluv":[289.201479741547303,335.469941782198191,25.5714349826340381],"hsluv":[289.201479741547303,99.9999999999995168,25.5714349826340381]},"#660099":{"lch":[27.0878540213863559,76.7583277742970296,283.827270614430063],"luv":[27.0878540213863559,18.3449067131383,-74.5339203342523291],"rgb":[0.4,0,0.6],"xyz":[0.112285246981583625,0.0512496172705720551,0.305357408040440148],"hpluv":[283.827270614430063,359.575614235331898,27.0878540213863559],"hsluv":[283.827270614430063,99.9999999999996732,27.0878540213863559]},"#6600aa":{"lch":[28.7136916664512327,86.2331190336617226,280.0081392435834],"luv":[28.7136916664512327,14.9862877024472088,-84.9209161465722246],"rgb":[0.4,0,0.66666666666666663],"xyz":[0.127342939946836181,0.0572726944566731566,0.384661257657438749],"hpluv":[280.0081392435834,381.087223541781498,28.7136916664512327],"hsluv":[280.0081392435834,99.9999999999998863,28.7136916664512327]},"#6600bb":{"lch":[30.4308478844127208,95.8259209373334784,277.232006261077231],"luv":[30.4308478844127208,12.063278317584528,-95.06358103774852],"rgb":[0.4,0,0.733333333333333282],"xyz":[0.144480532054172828,0.0641277312996079152,0.474919242756080495],"hpluv":[277.232006261077231,399.584170303460496,30.4308478844127208],"hsluv":[277.232006261077231,100.000000000000071,30.4308478844127208]},"#6600cc":{"lch":[32.2232190058254631,105.440117399912424,275.16595430901134],"luv":[32.2232190058254631,9.49391256045434,-105.011827817640437],"rgb":[0.4,0,0.8],"xyz":[0.163772907497894554,0.0718446814770967168,0.576525753426350707],"hpluv":[275.16595430901134,415.218107165999243,32.2232190058254631],"hsluv":[275.16595430901134,100.000000000000284,32.2232190058254631]},"#6600dd":{"lch":[34.0768449366564425,115.029794578821409,273.594219506454294],"luv":[34.0768449366564425,7.21119822634443164,-114.803537667557165],"rgb":[0.4,0,0.866666666666666696],"xyz":[0.185291233048528098,0.0804520116973502508,0.689855601326356704],"hpluv":[273.594219506454294,428.341637585051217,34.0768449366564425],"hsluv":[273.594219506454294,100.000000000000313,34.0768449366564425]},"#6600ee":{"lch":[35.9798440153965657,124.573148355199,272.374748889870204],"luv":[35.9798440153965657,5.16172842086039729,-124.466163477612255],"rgb":[0.4,0,0.933333333333333348],"xyz":[0.209103413314702835,0.0899768838038202817,0.815266417394879928],"hpluv":[272.374748889870204,439.343788723350144,35.9798440153965657],"hsluv":[272.374748889870204,100.000000000000313,35.9798440153965657]},"#6600ff":{"lch":[37.9222328155672699,134.059876636217865,271.411957283269032],"luv":[37.9222328155672699,3.30334385288953181,-134.01917192367489],"rgb":[0.4,0,1],"xyz":[0.235274461724534228,0.100445303167752992,0.953100605686661773],"hpluv":[271.411957283269032,448.58447779597617,37.9222328155672699],"hsluv":[271.411957283269032,100.00000000000054,37.9222328155672699]},"#661100":{"lch":[20.9278595225824304,60.6482483509225645,15.3961031612090817],"luv":[20.9278595225824304,58.4717927536604165,16.1015365791022269],"rgb":[0.4,0.0666666666666666657,0],"xyz":[0.0567980735836326536,0.0322617883288768559,0.00323658685731115833],"hpluv":[15.3961031612090817,367.733145903292666,20.9278595225824304],"hsluv":[15.3961031612090817,100.00000000000216,20.9278595225824304]},"#661111":{"lch":[21.0816163302651134,57.5811303886473453,12.1770506300618102],"luv":[21.0816163302651134,56.2855814623301782,12.1457768908291825],"rgb":[0.4,0.0666666666666666657,0.0666666666666666657],"xyz":[0.0578097390832697788,0.0326664545287317087,0.00856469182206678892],"hpluv":[12.1770506300618102,346.589664487627374,21.0816163302651134],"hsluv":[12.1770506300618102,81.2167011616125762,21.0816163302651134]},"#661122":{"lch":[21.363314856368774,52.932385078801687,5.93410154371749865],"luv":[21.363314856368774,52.6487447994206548,5.4723908098767966],"rgb":[0.4,0.0666666666666666657,0.133333333333333331],"xyz":[0.059685097221746794,0.0334165977841225301,0.018441578018045985],"hpluv":[5.93410154371749865,314.406957937895,21.363314856368774],"hsluv":[5.93410154371749865,82.5199513610387356,21.363314856368774]},"#661133":{"lch":[21.8180817632255923,47.7742586248590229,355.183549580278111],"luv":[21.8180817632255923,47.6055578330604661,-4.01131531521171425],"rgb":[0.4,0.0666666666666666657,0.2],"xyz":[0.0627728479542045414,0.0346516980771056429,0.0347037318756571317],"hpluv":[355.183549580278111,277.854041301729922,21.8180817632255923],"hsluv":[355.183549580278111,84.3121175303292603,21.8180817632255923]},"#661144":{"lch":[22.4559756968324393,44.5402485804784689,339.935360677682411],"luv":[22.4559756968324393,41.8369301419790247,-15.2808710453946635],"rgb":[0.4,0.0666666666666666657,0.266666666666666663],"xyz":[0.0672308431984234151,0.036434896174793216,0.0581825068285437702],"hpluv":[339.935360677682411,251.686564911624259,22.4559756968324393],"hsluv":[339.935360677682411,86.3348863624116518,22.4559756968324393]},"#661155":{"lch":[23.2773926977910151,45.1903944783221903,322.792583995755933],"luv":[23.2773926977910151,35.9919645110905364,-27.3267313035930464],"rgb":[0.4,0.0666666666666666657,0.333333333333333315],"xyz":[0.073193257237785414,0.0388198617905380489,0.0895845541025176717],"hpluv":[322.792583995755933,246.349182588766354,23.2773926977910151],"hsluv":[322.792583995755933,88.344826800164725,23.2773926977910151]},"#661166":{"lch":[24.275087751098269,49.8658315110702119,307.715012949243715],"luv":[24.275087751098269,30.5046415204515107,-39.4470277460696508],"rgb":[0.4,0.0666666666666666657,0.4],"xyz":[0.0807782530112935854,0.0418538600999413604,0.129532198509661634],"hpluv":[307.715012949243715,260.664316843383688,24.275087751098269],"hsluv":[307.715012949243715,90.1819147598703239,24.275087751098269]},"#661177":{"lch":[25.4363091503391701,57.3032374709331265,296.512546946797],"luv":[25.4363091503391701,25.5798087859014238,-51.2770358652572114],"rgb":[0.4,0.0666666666666666657,0.466666666666666674],"xyz":[0.0900922660314955,0.0455794653080221768,0.178586000416059543],"hpluv":[296.512546946797,285.867241464858182,25.4363091503391701],"hsluv":[296.512546946797,91.7740463972148461,25.4363091503391701]},"#661188":{"lch":[26.7449145171680129,66.1973281861227179,288.734792076243366],"luv":[26.7449145171680129,21.2617948521868456,-62.6898902427238127],"rgb":[0.4,0.0666666666666666657,0.533333333333333326],"xyz":[0.101232683133439333,0.0500356321487997746,0.23725886381963171],"hpluv":[288.734792076243366,314.078764213635395,26.7449145171680129],"hsluv":[288.734792076243366,93.110372692245349,26.7449145171680129]},"#661199":{"lch":[28.1832309630650286,75.7381406209258614,283.36677781159608],"luv":[28.1832309630650286,17.5094321789343716,-73.6864012521064637],"rgb":[0.4,0.0666666666666666657,0.6],"xyz":[0.114289647242512032,0.0552584177924289321,0.306025541460749617],"hpluv":[283.36677781159608,341.006914961476241,28.1832309630650286],"hsluv":[283.36677781159608,94.2123428080992,28.1832309630650286]},"#6611aa":{"lch":[29.733500038717203,85.5146667147819102,279.591039159426828],"luv":[29.733500038717203,14.2479867443400359,-84.3193518539685],"rgb":[0.4,0.0666666666666666657,0.66666666666666663],"xyz":[0.129347340207764588,0.0612814949785300336,0.385329391077748218],"hpluv":[279.591039159426828,364.950446526832081,29.733500038717203],"hsluv":[279.591039159426828,95.1136535912152254,29.733500038717203]},"#6611bb":{"lch":[31.378866229854367,95.3367425151459287,276.866082014695],"luv":[31.378866229854367,11.3974241300945351,-94.6530147253534153],"rgb":[0.4,0.0666666666666666657,0.733333333333333282],"xyz":[0.146484932315101235,0.0681365318214647853,0.475587376176389964],"hpluv":[276.866082014695,385.533736621086632,31.378866229854367],"hsluv":[276.866082014695,95.8493220980777636,31.378866229854367]},"#6611cc":{"lch":[33.1039576367877899,105.121866511028642,274.848446148865946],"luv":[33.1039576367877899,8.88494143701037409,-104.745714157780327],"rgb":[0.4,0.0666666666666666657,0.8],"xyz":[0.165777307758822962,0.0758534819989535869,0.577193886846660176],"hpluv":[274.848446148865946,402.951217622234481,33.1039576367877899],"hsluv":[274.848446148865946,96.4508528344463372,33.1039576367877899]},"#6611dd":{"lch":[34.8951509835915559,114.837148656435488,273.319244536407894],"luv":[34.8951509835915559,6.64899595047804048,-114.644500803094743],"rgb":[0.4,0.0666666666666666657,0.866666666666666696],"xyz":[0.187295633309456505,0.0844608122192071209,0.690523734746666173],"hpluv":[273.319244536407894,417.596303228590671,34.8951509835915559],"hsluv":[273.319244536407894,96.944722371929771,34.8951509835915559]},"#6611ee":{"lch":[36.7406193088150914,124.471664845310258,272.136065946829319],"luv":[36.7406193088150914,4.63940101804097527,-124.385173182164465],"rgb":[0.4,0.0666666666666666657,0.933333333333333348],"xyz":[0.211107813575631242,0.0939856843256771657,0.815934550815189397],"hpluv":[272.136065946829319,429.895948632666091,36.7406193088150914],"hsluv":[272.136065946829319,97.3524117238680446,36.7406193088150914]},"#6611ff":{"lch":[38.6302462525687815,134.023748920536207,271.203906716280642],"luv":[38.6302462525687815,2.81591803113255,-133.994163605572282],"rgb":[0.4,0.0666666666666666657,1],"xyz":[0.237278861985462636,0.104454103689609862,0.953768739106971242],"hpluv":[271.203906716280642,440.244168299440901,38.6302462525687815],"hsluv":[271.203906716280642,99.999999999999531,38.6302462525687815]},"#662200":{"lch":[23.5697003211059126,54.0433218319152,21.7646438431993481],"luv":[23.5697003211059126,50.1908335966774928,20.038983444740694],"rgb":[0.4,0.133333333333333331,0],"xyz":[0.0605136973184005889,0.0396930357984128235,0.00447512810223376842],"hpluv":[21.7646438431993481,290.955989204018863,23.5697003211059126],"hsluv":[21.7646438431993481,100.000000000002302,23.5697003211059126]},"#662211":{"lch":[23.7037155268300239,51.2256671637292627,18.5481266113504795],"luv":[23.7037155268300239,48.5648418325832196,16.2949413667354186],"rgb":[0.4,0.133333333333333331,0.0666666666666666657],"xyz":[0.0615253628180377071,0.0400977019982676763,0.0098032330669894],"hpluv":[18.5481266113504795,274.227196288753476,23.7037155268300239],"hsluv":[18.5481266113504795,84.4421944630679775,23.7037155268300239]},"#662222":{"lch":[23.9497782760704823,46.8639841586094761,12.177050630061828],"luv":[23.9497782760704823,45.8095660888384515,9.88517404858116],"rgb":[0.4,0.133333333333333331,0.133333333333333331],"xyz":[0.0634007209565147362,0.0408478452536585,0.0196801192629685942],"hpluv":[12.177050630061828,248.300181449835577,23.9497782760704823],"hsluv":[12.177050630061828,58.184428739378582,23.9497782760704823]},"#662233":{"lch":[24.3484354577474491,41.8866591548313139,0.79920805816821483],"luv":[24.3484354577474491,41.8825842907357284,0.584250188216085209],"rgb":[0.4,0.133333333333333331,0.2],"xyz":[0.0664884716889724697,0.0420829455466416105,0.0359422731205797374],"hpluv":[0.79920805816821483,218.295100847162672,24.3484354577474491],"hsluv":[0.79920805816821483,61.8388407102714908,24.3484354577474491]},"#662244":{"lch":[24.9104705449366364,38.7518995195090881,343.921953822216381],"luv":[24.9104705449366364,37.2361321960480467,-10.7322027305005232],"rgb":[0.4,0.133333333333333331,0.266666666666666663],"xyz":[0.0709464669331913433,0.0438661436443291836,0.059421048073466376],"hpluv":[343.921953822216381,197.40147375767063,24.9104705449366364],"hsluv":[343.921953822216381,66.1143919991375,24.9104705449366364]},"#662255":{"lch":[25.6388481254070371,39.7515606661792376,324.488651014584605],"luv":[25.6388481254070371,32.3577893991285421,-23.0902585650003651],"rgb":[0.4,0.133333333333333331,0.333333333333333315],"xyz":[0.0769088809725533423,0.0462511092600740165,0.0908230953474402913],"hpluv":[324.488651014584605,196.74105716675524,25.6388481254070371],"hsluv":[324.488651014584605,70.5303777194930746,25.6388481254070371]},"#662266":{"lch":[26.530115175026431,45.1550068711170525,307.715012949244],"luv":[26.530115175026431,27.6228683191850166,-35.7204674010796381],"rgb":[0.4,0.133333333333333331,0.4],"xyz":[0.0844938767460615137,0.049285107569477328,0.130770739754584253],"hpluv":[307.715012949244,215.976303505553034,26.530115175026431],"hsluv":[307.715012949244,74.7212231760600076,26.530115175026431]},"#662277":{"lch":[27.5758503181253047,53.47184404785515,295.772994892671647],"luv":[27.5758503181253047,23.2499163509258366,-48.1526686233805137],"rgb":[0.4,0.133333333333333331,0.466666666666666674],"xyz":[0.0938078897662634331,0.0530107127775581444,0.179824541660982162],"hpluv":[295.772994892671647,246.056915236342576,27.5758503181253047],"hsluv":[295.772994892671647,78.4792949436714622,27.5758503181253047]},"#662288":{"lch":[28.7641691288539221,63.1969785740596492,287.807032602413699],"luv":[28.7641691288539221,19.3264050913403267,-60.169329123196718],"rgb":[0.4,0.133333333333333331,0.533333333333333326],"xyz":[0.104948306868207261,0.0574668796183357422,0.23849740506455433],"hpluv":[287.807032602413699,278.794247273594806,28.7641691288539221],"hsluv":[287.807032602413699,81.7283139306751565,28.7641691288539221]},"#662299":{"lch":[30.081149723697223,73.446586603294,282.467625610713],"luv":[30.081149723697223,15.8562317990126083,-71.7145800853016766],"rgb":[0.4,0.133333333333333331,0.6],"xyz":[0.118005270977279975,0.0626896652619649,0.307264082705672237],"hpluv":[282.467625610713,309.825038390781458,30.081149723697223],"hsluv":[282.467625610713,84.475317587044259,30.081149723697223]},"#6622aa":{"lch":[31.512047889249807,83.7990879252892569,278.786617054458873],"luv":[31.512047889249807,12.8007301136733549,-82.8156292354725565],"rgb":[0.4,0.133333333333333331,0.66666666666666663],"xyz":[0.133062963942532531,0.068712742448066,0.386567932322670837],"hpluv":[278.786617054458873,337.444217562648532,31.512047889249807],"hsluv":[278.786617054458873,86.7692530837111349,31.512047889249807]},"#6622bb":{"lch":[33.0422299311305281,94.07636648958254,276.16643073789237],"luv":[33.0422299311305281,10.1053888262289746,-93.5320471739658643],"rgb":[0.4,0.133333333333333331,0.733333333333333282],"xyz":[0.150200556049869149,0.0755677792910007529,0.476825917421312584],"hpluv":[276.16643073789237,361.285484259324733,33.0422299311305281],"hsluv":[276.16643073789237,88.6739591516067662,33.0422299311305281]},"#6622cc":{"lch":[34.6578144612334853,104.214655833315277,274.245119058655746],"luv":[34.6578144612334853,7.71433729467952212,-103.928742369761551],"rgb":[0.4,0.133333333333333331,0.8],"xyz":[0.169492931493590904,0.0832847294684895545,0.578432428091582684],"hpluv":[274.245119058655746,381.563613242959605,34.6578144612334853],"hsluv":[274.245119058655746,90.2534916489383079,34.6578144612334853]},"#6622dd":{"lch":[36.346058304563158,114.200305800004017,272.799119030217753],"luv":[36.346058304563158,5.5769048208672336,-114.064052082299],"rgb":[0.4,0.133333333333333331,0.866666666666666696],"xyz":[0.191011257044224447,0.0918920596887430885,0.691762275991588682],"hpluv":[272.799119030217753,398.702789727268566,36.346058304563158],"hsluv":[272.799119030217753,91.5654894217155118,36.346058304563158]},"#6622ee":{"lch":[38.0955422395423,124.039689220569301,271.686141036519132],"luv":[38.0955422395423,3.64980151132168062,-123.985980864222597],"rgb":[0.4,0.133333333333333331,0.933333333333333348],"xyz":[0.214823437310399185,0.101416931795213133,0.817173092060111905],"hpluv":[271.686141036519132,413.167199445381073,38.0955422395423],"hsluv":[271.686141036519132,92.6590152569973498,38.0955422395423]},"#6622ff":{"lch":[39.8962147757429264,133.745689778340022,270.812765394378914],"luv":[39.8962147757429264,1.89717678294479941,-133.732233416399],"rgb":[0.4,0.133333333333333331,1],"xyz":[0.240994485720230578,0.111885351159145829,0.95500728035189375],"hpluv":[270.812765394378914,425.390148773484384,39.8962147757429264],"hsluv":[270.812765394378914,99.99999999999946,39.8962147757429264]},"#ddaa00":{"lch":[72.3107430320736881,86.5721801417344494,59.9465914104400071],"luv":[72.3107430320736881,43.355958426121056,74.9333253195491267],"rgb":[0.866666666666666696,0.66666666666666663,0],"xyz":[0.441922241377765923,0.441231641500849037,0.0618909005679976823],"hpluv":[59.9465914104400071,151.919966798177825,72.3107430320736881],"hsluv":[59.9465914104400071,100.000000000002402,72.3107430320736881]},"#ddaa11":{"lch":[72.3377322217577188,85.4954146103564625,59.6744594052448178],"luv":[72.3377322217577188,43.1676990261245663,73.7971251485224],"rgb":[0.866666666666666696,0.66666666666666663,0.0666666666666666657],"xyz":[0.442933906877403,0.44163630770070389,0.067219005532753312],"hpluv":[59.6744594052448178,149.974443711406025,72.3377322217577188],"hsluv":[59.6744594052448178,98.5180859943944398,72.3377322217577188]},"#ddaa22":{"lch":[72.3877194091470386,83.5208789068112623,59.1557200492863586],"luv":[72.3877194091470386,42.8217010650181038,71.708013019916109],"rgb":[0.866666666666666696,0.66666666666666663,0.133333333333333331],"xyz":[0.444809265015880084,0.44238645095609469,0.0770958917287325],"hpluv":[59.1557200492863586,146.409577733002152,72.3877194091470386],"hsluv":[59.1557200492863586,95.7957425728033911,72.3877194091470386]},"#ddaa33":{"lch":[72.4698996386445771,80.3304907566891,58.2589820164620633],"luv":[72.4698996386445771,42.2603136650720543,68.3158373595774435],"rgb":[0.866666666666666696,0.66666666666666663,0.2],"xyz":[0.44789701574833779,0.443621551249077817,0.0933580455863436548],"hpluv":[58.2589820164620633,140.657238526484292,72.4698996386445771],"hsluv":[58.2589820164620633,91.3821490755410082,72.4698996386445771]},"#ddaa44":{"lch":[72.5882801580772679,75.8571050863154,56.8623293870095949],"luv":[72.5882801580772679,41.467485474955744,63.5196665644635701],"rgb":[0.866666666666666696,0.66666666666666663,0.266666666666666663],"xyz":[0.452355010992556705,0.445404749346765383,0.116836820539230293],"hpluv":[56.8623293870095949,132.607804442486071,72.5882801580772679],"hsluv":[56.8623293870095949,85.1564308385751332,72.5882801580772679]},"#ddaa55":{"lch":[72.7461171317656579,70.1198989219024469,54.780917696258733],"luv":[72.7461171317656579,40.4384565855633085,57.2846528644045065],"rgb":[0.866666666666666696,0.66666666666666663,0.333333333333333315],"xyz":[0.45831742503191869,0.447789714962510244,0.148238867813204195],"hpluv":[54.780917696258733,122.3124851735341,72.7461171317656579],"hsluv":[54.780917696258733,77.0893717991816345,72.7461171317656579]},"#ddaa66":{"lch":[72.9460991959627307,63.2340905833455835,51.7145858463931702],"luv":[72.9460991959627307,39.17852917707404,49.6345954292357661],"rgb":[0.866666666666666696,0.66666666666666663,0.4],"xyz":[0.465902420805426876,0.450823713271913562,0.188186512220348157],"hpluv":[51.7145858463931702,109.998947735143361,72.9460991959627307],"hsluv":[51.7145858463931702,67.2337497701281421,72.9460991959627307]},"#ddaa77":{"lch":[73.1904440644495651,55.4382228560327377,47.150998877343909],"luv":[73.1904440644495651,37.7017926183457632,40.6444508733775294],"rgb":[0.866666666666666696,0.66666666666666663,0.466666666666666674],"xyz":[0.475216433825628781,0.454549318479994358,0.237240314126746066],"hpluv":[47.150998877343909,96.1156803308001173,73.1904440644495651],"hsluv":[47.150998877343909,55.7140089565642214,73.1904440644495651]},"#ddaa88":{"lch":[73.4809558021361511,47.1613293807763228,40.1850915920125189],"luv":[73.4809558021361511,36.0295555463961819,30.4312687229965526],"rgb":[0.866666666666666696,0.66666666666666663,0.533333333333333326],"xyz":[0.486356850927572582,0.459005485320771955,0.295913177530318261],"hpluv":[40.1850915920125189,81.4424009312931645,73.4809558021361511],"hsluv":[40.1850915920125189,45.325944585658668,73.4809558021361511]},"#ddaa99":{"lch":[73.8190624606049539,39.1833139250455389,29.2463480703396321],"luv":[73.8190624606049539,34.1885051927702435,19.1436204212419021],"rgb":[0.866666666666666696,0.66666666666666663,0.6],"xyz":[0.499413815036645281,0.464228270964401113,0.36467985517143614],"hpluv":[29.2463480703396321,67.3553294964948,73.8190624606049539],"hsluv":[29.2463480703396321,46.1073405736803323,73.8190624606049539]},"#ddaaaa":{"lch":[74.2058435914849923,32.9500913661112946,12.1770506300625296],"luv":[74.2058435914849923,32.2087294789219527,6.95027095793437244],"rgb":[0.866666666666666696,0.66666666666666663,0.66666666666666663],"xyz":[0.514471508001897893,0.470251348150502235,0.443983704788434741],"hpluv":[12.1770506300625296,56.3453191386000114,74.2058435914849923],"hsluv":[12.1770506300625296,46.9171896008106,74.2058435914849923]},"#ddaabb":{"lch":[74.6420527030620633,30.7078714289026884,348.787813428378513],"luv":[74.6420527030620633,30.1217754775259223,-5.97093039446416807],"rgb":[0.866666666666666696,0.66666666666666663,0.733333333333333282],"xyz":[0.531609100109234456,0.477106384993437,0.534241689887076543],"hpluv":[348.787813428378513,52.2042025244198,74.6420527030620633],"hsluv":[348.787813428378513,47.7320050944543937,74.6420527030620633]},"#ddaacc":{"lch":[75.1281375044867445,34.0548346014018435,325.184688201169763],"luv":[75.1281375044867445,27.9589055182432595,-19.4430286208394669],"rgb":[0.866666666666666696,0.66666666666666663,0.8],"xyz":[0.55090147555295621,0.484823335170925795,0.635848200557346699],"hpluv":[325.184688201169763,57.5195499622066393,75.1281375044867445],"hsluv":[325.184688201169763,48.5278754329488109,75.1281375044867445]},"#ddaadd":{"lch":[75.6642595524878772,42.092894356631966,307.715012949247466],"luv":[75.6642595524878772,25.749668941594777,-33.2981426616688125],"rgb":[0.866666666666666696,0.66666666666666663,0.866666666666666696],"xyz":[0.572419801103589809,0.493430665391179302,0.749178048457352697],"hpluv":[307.715012949247466,70.5922995729826255,75.6642595524878772],"hsluv":[307.715012949247466,49.2811497501269855,75.6642595524878772]},"#ddaaee":{"lch":[76.2503141773092636,52.9001531738191915,296.399494755977059],"luv":[76.2503141773092636,23.5208512504545659,-47.3834967290038875],"rgb":[0.866666666666666696,0.66666666666666663,0.933333333333333348],"xyz":[0.596231981369764519,0.502955537497649319,0.87458886452587592],"hpluv":[296.399494755977059,89.1958388421213,76.2503141773092636],"hsluv":[296.399494755977059,73.8038640729286,76.2503141773092636]},"#ddaaff":{"lch":[76.8859510948180542,65.1444004641461447,289.080822119258073],"luv":[76.8859510948180542,21.295808093697417,-61.565261872824486],"rgb":[0.866666666666666696,0.66666666666666663,1],"xyz":[0.62240302977959594,0.513423956861582,1.01242305281765765],"hpluv":[289.080822119258073,113.514774922986319,76.8859510948180542],"hsluv":[289.080822119258073,99.9999999999968736,76.8859510948180542]},"#663300":{"lch":[27.2772702365161024,46.6784293424923,33.1138040531735385],"luv":[27.2772702365161024,39.0972512948193938,25.5006020923387098],"rgb":[0.4,0.2,0],"xyz":[0.0666314194074114075,0.0519284799764346272,0.00651436879857064926],"hpluv":[33.1138040531735385,217.147410386557254,27.2772702365161024],"hsluv":[33.1138040531735385,100.000000000002288,27.2772702365161024]},"#663311":{"lch":[27.3893959478715772,43.971204952951652,30.1269617465812196],"luv":[27.3893959478715772,38.0313691656755353,22.0699303215615963],"rgb":[0.4,0.2,0.0666666666666666657],"xyz":[0.0676430849070485257,0.0523331461762894801,0.0118424737633262799],"hpluv":[30.1269617465812196,203.716046393577358,27.3893959478715772],"hsluv":[30.1269617465812196,87.8713966090680572,27.3893959478715772]},"#663322":{"lch":[27.5957277293980781,39.608651287835805,23.9834802389854467],"luv":[27.5957277293980781,36.1889469500517436,16.0998563809655693],"rgb":[0.4,0.2,0.133333333333333331],"xyz":[0.0695184430455255548,0.0530832894316803,0.0217193599593054759],"hpluv":[23.9834802389854467,182.132533879714401,27.5957277293980781],"hsluv":[23.9834802389854467,66.9779468056010501,27.5957277293980781]},"#663333":{"lch":[27.9312558147072,34.2527069144242517,12.1770506300619683],"luv":[27.9312558147072,33.482036777055967,7.2250359324586606],"rgb":[0.4,0.2,0.2],"xyz":[0.0726061937779832883,0.0543183897246634143,0.0379815138169166192],"hpluv":[12.1770506300619683,155.612242967619039,27.9312558147072],"hsluv":[12.1770506300619683,36.4647718300342518,27.9312558147072]},"#663344":{"lch":[28.4068233476218452,30.4023991370198274,352.642674566889241],"luv":[28.4068233476218452,30.1520905841737843,-3.89323858637544307],"rgb":[0.4,0.2,0.266666666666666663],"xyz":[0.0770641890222021619,0.0561015878223509873,0.0614602887698032577],"hpluv":[352.642674566889241,135.807736934047625,28.4068233476218452],"hsluv":[352.642674566889241,42.3197568133242044,28.4068233476218452]},"#663355":{"lch":[29.027378781744666,31.164203729876963,328.257954430910445],"luv":[29.027378781744666,26.5028264215118483,-16.3953586660521324],"rgb":[0.4,0.2,0.333333333333333315],"xyz":[0.0830266030615641609,0.0584865534380958202,0.0928623360437771661],"hpluv":[328.257954430910445,136.234637721643935,29.027378781744666],"hsluv":[328.257954430910445,48.648881209958347,29.027378781744666]},"#663366":{"lch":[29.7928910898429251,37.2833882928919067,307.715012949244567],"luv":[29.7928910898429251,22.8075289246907325,-29.4935190668720466],"rgb":[0.4,0.2,0.4],"xyz":[0.0906115988350723323,0.0615205517474991317,0.132809980450921128],"hpluv":[307.715012949244567,158.796911496206434,29.7928910898429251],"hsluv":[307.715012949244567,54.9388950129523792,29.7928910898429251]},"#663377":{"lch":[30.6992208566667273,46.7654342463890345,294.320996437271049],"luv":[30.6992208566667273,19.2602655771695837,-42.6151148086008646],"rgb":[0.4,0.2,0.466666666666666674],"xyz":[0.0999256118552742517,0.0652461569555799481,0.181863782357319037],"hpluv":[294.320996437271049,193.302265796468333,30.6992208566667273],"hsluv":[294.320996437271049,60.8304716126584424,30.6992208566667273]},"#663388":{"lch":[31.7390466224972485,57.6768981875091811,286.074834637191429],"luv":[31.7390466224972485,15.9703082974272554,-55.4217812544619122],"rgb":[0.4,0.2,0.533333333333333326],"xyz":[0.11106602895721808,0.0697023237963575459,0.240536645760891205],"hpluv":[286.074834637191429,230.593651019326217,31.7390466224972485],"hsluv":[286.074834637191429,66.1275975958513,31.7390466224972485]},"#663399":{"lch":[32.9028065942714818,68.9918273860838696,280.844497617061506],"luv":[32.9028065942714818,12.9804074562249703,-67.7597319087197292],"rgb":[0.4,0.2,0.6],"xyz":[0.124122993066290793,0.0749251094399867,0.309303323402009112],"hpluv":[280.844497617061506,266.074975063872387,32.9028065942714818],"hsluv":[280.844497617061506,70.7618769362906761,32.9028065942714818]},"#6633aa":{"lch":[34.1795810026756612,80.2563017922262,277.366876751775067],"luv":[34.1795810026756612,10.2906460265496023,-79.5938225097975],"rgb":[0.4,0.2,0.66666666666666663],"xyz":[0.139180686031543321,0.0809481866260878,0.388607173019007712],"hpluv":[277.366876751775067,297.955725741163178,34.1795810026756612],"hsluv":[277.366876751775067,74.7463951454814293,34.1795810026756612]},"#6633bb":{"lch":[35.5578512622049701,91.2933310256451875,274.950579703742051],"luv":[35.5578512622049701,7.87828999249439477,-90.9527615663878208],"rgb":[0.4,0.2,0.733333333333333282],"xyz":[0.15631827813888,0.0878032234690225566,0.478865158117649459],"hpluv":[274.950579703742051,325.793841875348,35.5578512622049701],"hsluv":[274.950579703742051,78.1373397599298158,35.5578512622049701]},"#6633cc":{"lch":[37.0261004663704369,102.052471518039624,273.208108112313198],"luv":[37.0261004663704369,5.71114175070883,-101.892540467119574],"rgb":[0.4,0.2,0.8],"xyz":[0.175610653582601722,0.0955201736465113582,0.58047166878791967],"hpluv":[273.208108112313198,349.747708937339723,37.0261004663704369],"hsluv":[273.208108112313198,81.008127051099,37.0261004663704369]},"#6633dd":{"lch":[38.5732487885037258,112.538160698021628,271.912259810227567],"luv":[38.5732487885037258,3.75528971541350476,-112.475488051606391],"rgb":[0.4,0.2,0.866666666666666696],"xyz":[0.197128979133235266,0.104127503866764892,0.693801516687925668],"hpluv":[271.912259810227567,370.214070757058153,38.5732487885037258],"hsluv":[271.912259810227567,83.8924044294761,38.5732487885037258]},"#6633ee":{"lch":[40.1889386764538372,122.775879994578517,270.923585069715159],"luv":[40.1889386764538372,1.97901241536538453,-122.759929204537158],"rgb":[0.4,0.2,0.933333333333333348],"xyz":[0.22094115939941,0.113652375973234937,0.819212332756448891],"hpluv":[270.923585069715159,387.655406037674595,40.1889386764538372],"hsluv":[270.923585069715159,91.8481370725274502,40.1889386764538372]},"#6633ff":{"lch":[41.8636962738951581,132.796565847050772,270.152898062524457],"luv":[41.8636962738951581,0.354377123439640451,-132.796093005873018],"rgb":[0.4,0.2,1],"xyz":[0.247112207809241397,0.124120795337167633,0.957046521048230736],"hpluv":[270.152898062524457,402.521052726566381,41.8636962738951581],"hsluv":[270.152898062524457,99.9999999999993747,41.8636962738951581]},"#ddbb00":{"lch":[76.6269242453545871,86.8164471599593,69.4373142874166263],"luv":[76.6269242453545871,30.4927107526369845,81.2852390562642],"rgb":[0.866666666666666696,0.733333333333333282,0],"xyz":[0.475876739286364758,0.509140637318047595,0.0732090665375303],"hpluv":[69.4373142874166263,149.25100710879434,76.6269242453545871],"hsluv":[69.4373142874166263,100.000000000002373,76.6269242453545871]},"#ddbb11":{"lch":[76.6514577805917412,85.7837129406502754,69.2755127455160391],"luv":[76.6514577805917412,30.3566774947925779,80.2328956062352461],"rgb":[0.866666666666666696,0.733333333333333282,0.0666666666666666657],"xyz":[0.476888404786001863,0.509545303517902504,0.0785371715022859379],"hpluv":[69.2755127455160391,147.663405788509436,76.6514577805917412],"hsluv":[69.2755127455160391,98.710789014839,76.6514577805917412]},"#ddbb22":{"lch":[76.6969020797125722,83.8847347199512257,68.9672438614109353],"luv":[76.6969020797125722,30.1063672837106679,78.2959473281657523],"rgb":[0.866666666666666696,0.733333333333333282,0.133333333333333331],"xyz":[0.478763762924478919,0.510295446773293304,0.0884140576982651305],"hpluv":[68.9672438614109353,144.735860207932831,76.6969020797125722],"hsluv":[68.9672438614109353,96.3396664069398554,76.6969020797125722]},"#ddbb33":{"lch":[76.7716285455552452,80.8014056767331823,68.4347482297151828],"luv":[76.7716285455552452,29.6994133556607949,75.1452726767666093],"rgb":[0.866666666666666696,0.733333333333333282,0.2],"xyz":[0.481851513656936625,0.511530547066276431,0.104676211555876281],"hpluv":[68.4347482297151828,139.9591753538609,76.7716285455552452],"hsluv":[68.4347482297151828,92.4878111801133542,76.7716285455552452]},"#ddbb44":{"lch":[76.8793043130786771,76.4437058057830683,67.6060651816737277],"luv":[76.8793043130786771,29.122950030038453,70.6788082728408398],"rgb":[0.866666666666666696,0.733333333333333282,0.266666666666666663],"xyz":[0.486309508901155541,0.513313745163964,0.128154986508762919],"hpluv":[67.6060651816737277,133.157612926581692,76.8793043130786771],"hsluv":[67.6060651816737277,87.0382611213693167,76.8793043130786771]},"#ddbb55":{"lch":[77.0229278214451654,70.786451757311653,66.3713649049339551],"luv":[77.0229278214451654,28.3717024864450664,64.851894732620238],"rgb":[0.866666666666666696,0.733333333333333282,0.333333333333333315],"xyz":[0.492271922940517526,0.515698710779708747,0.159557033782736835],"hpluv":[66.3713649049339551,124.235507886155276,77.0229278214451654],"hsluv":[66.3713649049339551,79.9485715087726,77.0229278214451654]},"#ddbb66":{"lch":[77.2049977781888259,63.8693414267369732,64.5489675558404912],"luv":[77.2049977781888259,27.4471820183984,57.6710063509733359],"rgb":[0.866666666666666696,0.733333333333333282,0.4],"xyz":[0.499856918714025711,0.518732709089112065,0.199504678189880769],"hpluv":[64.5489675558404912,113.177180957201557,77.2049977781888259],"hsluv":[64.5489675558404912,71.2432689272834665,77.2049977781888259]},"#ddbb77":{"lch":[77.4276024714994264,55.80459657383539,61.8158033937064815],"luv":[77.4276024714994264,26.3569386858829375,49.1880552764299495],"rgb":[0.866666666666666696,0.733333333333333282,0.466666666666666674],"xyz":[0.509170931734227561,0.522458314297192916,0.248558480096278678],"hpluv":[61.8158033937064815,100.062778032150931,77.4276024714994264],"hsluv":[61.8158033937064815,61.00659046349368,77.4276024714994264]},"#ddbb88":{"lch":[77.6924726660212883,46.8021917196541537,57.5480835848938881],"luv":[77.6924726660212883,25.1136643841879952,39.4936578574539112],"rgb":[0.866666666666666696,0.733333333333333282,0.533333333333333326],"xyz":[0.520311348836171472,0.526914481137970458,0.307231343499850873],"hpluv":[57.5480835848938881,85.1204314231653285,77.6924726660212883],"hsluv":[57.5480835848938881,49.3735690170151145,77.6924726660212883]},"#ddbb99":{"lch":[78.001015952892,37.2494741955694195,50.4191367157405352],"luv":[78.001015952892,23.7341209657771088,28.7091419207933036],"rgb":[0.866666666666666696,0.733333333333333282,0.6],"xyz":[0.533368312945244116,0.532137266781599672,0.375998021140968752],"hpluv":[50.4191367157405352,68.8881727109007471,78.001015952892],"hsluv":[50.4191367157405352,37.2278198354843894,78.001015952892]},"#ddbbaa":{"lch":[78.3543411988951135,27.9779961680489,37.3601297670291643],"luv":[78.3543411988951135,22.2379488027189254,16.9776883770144309],"rgb":[0.866666666666666696,0.733333333333333282,0.66666666666666663],"xyz":[0.548426005910496728,0.538160343967700738,0.455301870757967353],"hpluv":[37.3601297670291643,52.7537881236722086,78.3543411988951135],"hsluv":[37.3601297670291643,37.9002282962103152,78.3543411988951135]},"#ddbbbb":{"lch":[78.7532777240269724,21.1216737069600953,12.1770506300632171],"luv":[78.7532777240269724,20.6464457718990104,4.45526398447083238],"rgb":[0.866666666666666696,0.733333333333333282,0.733333333333333282],"xyz":[0.565563598017833291,0.545015380810635497,0.545559855856609155],"hpluv":[12.1770506300632171,40.7192307977840144,78.7532777240269724],"hsluv":[12.1770506300632171,38.5653299871361952,78.7532777240269724]},"#ddbbcc":{"lch":[79.1983918483363425,20.8793784390591419,335.381067074864461],"luv":[79.1983918483363425,18.9814116783666442,-8.69795692664743747],"rgb":[0.866666666666666696,0.733333333333333282,0.8],"xyz":[0.584855973461555,0.552732330988124354,0.647166366526879311],"hpluv":[335.381067074864461,41.2776027841095186,79.1983918483363425],"hsluv":[335.381067074864461,39.1976988204569,79.1983918483363425]},"#ddbbdd":{"lch":[79.6900023594157858,28.2216267397768341,307.715012949250536],"luv":[79.6900023594157858,17.264138203128045,-22.325092338948604],"rgb":[0.866666666666666696,0.733333333333333282,0.866666666666666696],"xyz":[0.606374299012188644,0.56133966120837786,0.760496214426885309],"hpluv":[307.715012949250536,57.3946451248898555,79.6900023594157858],"hsluv":[307.715012949250536,39.7709084435557401,79.6900023594157858]},"#ddbbee":{"lch":[80.2281958041266,39.4567857635703447,293.154060026294076],"luv":[80.2281958041266,15.5145982189510505,-36.2785774927409221],"rgb":[0.866666666666666696,0.733333333333333282,0.933333333333333348],"xyz":[0.630186479278363354,0.570864533314847877,0.885907030495408532],"hpluv":[293.154060026294076,82.8233888781043674,80.2281958041266],"hsluv":[293.154060026294076,68.7191774169969278,80.2281958041266]},"#ddbbff":{"lch":[80.8128420975971409,52.2658591287574765,285.253756774661895],"luv":[80.8128420975971409,13.7508586713070624,-50.4245368473411],"rgb":[0.866666666666666696,0.733333333333333282,1],"xyz":[0.656357527688194775,0.581332952678780601,1.02374121878719038],"hpluv":[285.253756774661895,113.64059963508393,80.8128420975971409],"hsluv":[285.253756774661895,99.9999999999962768,80.8128420975971409]},"#664400":{"lch":[31.7142168878436834,41.7146560735594463,49.9018869072924431],"luv":[31.7142168878436834,26.8683448374877969,31.9093180282686859],"rgb":[0.4,0.266666666666666663,0],"xyz":[0.0754639898903774337,0.0695936209423669294,0.00945855895955924342],"hpluv":[49.9018869072924431,166.906788900061372,31.7142168878436834],"hsluv":[49.9018869072924431,100.000000000002103,31.7142168878436834]},"#664411":{"lch":[31.8065195391856221,38.988494662362406,47.7128576067384387],"luv":[31.8065195391856221,26.2332728657793552,28.8429906699465946],"rgb":[0.4,0.266666666666666663,0.0666666666666666657],"xyz":[0.0764756553900145519,0.0699982871422217823,0.0147866639243148749],"hpluv":[47.7128576067384387,155.546285842055198,31.8065195391856221],"hsluv":[47.7128576067384387,90.7993319288460157,31.8065195391856221]},"#664422":{"lch":[31.9766874661881033,34.3475437583520318,43.0092135947734064],"luv":[31.9766874661881033,25.116436032279772,23.4290077311931029],"rgb":[0.4,0.266666666666666663,0.133333333333333331],"xyz":[0.078351013528491581,0.0707484303976126,0.0246635501202940727],"hpluv":[43.0092135947734064,136.301783870263904,31.9766874661881033],"hsluv":[43.0092135947734064,74.6688439558526227,31.9766874661881033]},"#664433":{"lch":[32.2542649002247757,27.9288783689085562,32.9719795273007179],"luv":[32.2542649002247757,23.4305644913898661,15.1997004759998031],"rgb":[0.4,0.266666666666666663,0.2],"xyz":[0.0814387642609493145,0.0719835306905957095,0.0409257039779052159],"hpluv":[32.9719795273007179,109.876716511985933,32.2542649002247757],"hsluv":[32.9719795273007179,50.4508902462759465,32.2542649002247757]},"#664444":{"lch":[32.6494757012261942,21.7704999617243,12.1770506300622419],"luv":[32.6494757012261942,21.2806737346177428,4.59212303669972144],"rgb":[0.4,0.266666666666666663,0.266666666666666663],"xyz":[0.0858967595051681881,0.0737667287882832895,0.0644044789307918475],"hpluv":[12.1770506300622419,84.6119136876739,32.6494757012261942],"hsluv":[12.1770506300622419,19.8271939783404392,32.6494757012261942]},"#664455":{"lch":[33.1682230288457163,20.3425996427482829,337.72581918360828],"luv":[33.1682230288457163,18.8246474429619,-7.71064257201243208],"rgb":[0.4,0.266666666666666663,0.333333333333333315],"xyz":[0.0918591735445301871,0.0761516944040281224,0.0958065262047657629],"hpluv":[337.72581918360828,77.8257963773028649,33.1682230288457163],"hsluv":[337.72581918360828,26.9902218950403885,33.1682230288457163]},"#664466":{"lch":[33.8127168447387447,26.5268160416637819,307.715012949245931],"luv":[33.8127168447387447,16.2273642995468066,-20.9843898457416387],"rgb":[0.4,0.266666666666666663,0.4],"xyz":[0.0994441693180383585,0.0791856927134314409,0.135754170611909725],"hpluv":[307.715012949245931,99.5507152142919125,33.8127168447387447],"hsluv":[307.715012949245931,34.4415155187259359,33.8127168447387447]},"#664477":{"lch":[34.5819879544663067,37.1994974310500766,291.489286484323088],"luv":[34.5819879544663067,13.6271894146704895,-34.6136146303646512],"rgb":[0.4,0.266666666666666663,0.466666666666666674],"xyz":[0.108758182338240278,0.0829112979215122503,0.184807972518307634],"hpluv":[291.489286484323088,136.49804898345451,34.5819879544663067],"hsluv":[291.489286484323088,41.7425383187140824,34.5819879544663067]},"#664488":{"lch":[35.4724176022540263,49.4256461635840623,283.003444619748336],"luv":[35.4724176022540263,11.1212465051830698,-48.1582015326441066],"rgb":[0.4,0.266666666666666663,0.533333333333333326],"xyz":[0.119898599440184106,0.0873674647622898481,0.243480835921879801],"hpluv":[283.003444619748336,176.807585609029246,35.4724176022540263],"hsluv":[283.003444619748336,48.5905204317402166,35.4724176022540263]},"#664499":{"lch":[36.4782980897457563,61.9753280698008169,278.131406705371774],"luv":[36.4782980897457563,8.76603164871553453,-61.3522450974139844],"rgb":[0.4,0.266666666666666663,0.6],"xyz":[0.132955563549256806,0.092590250405919,0.312247513562997736],"hpluv":[278.131406705371774,215.5875082467536,36.4782980897457563],"hsluv":[278.131406705371774,54.8155424382278511,36.4782980897457563]},"#6644aa":{"lch":[37.5923984663849922,74.3597011725147468,275.081334091822],"luv":[37.5923984663849922,6.58602388672208239,-74.0674655150911576],"rgb":[0.4,0.266666666666666663,0.66666666666666663],"xyz":[0.148013256514509361,0.0986133275920201,0.391551363179996281],"hpluv":[275.081334091822,251.001852587777108,37.5923984663849922],"hsluv":[275.081334091822,60.3520360208115179,37.5923984663849922]},"#6644bb":{"lch":[38.8064988843830392,86.3901107279874765,273.04172423936518],"luv":[38.8064988843830392,4.58413310153298603,-86.2684006766171905],"rgb":[0.4,0.266666666666666663,0.733333333333333282],"xyz":[0.165150848621846,0.105468364434954859,0.481809348278638083],"hpluv":[273.04172423936518,282.487278503057098,38.8064988843830392],"hsluv":[273.04172423936518,65.2044789549227346,38.8064988843830392]},"#6644cc":{"lch":[40.1118623747323184,98.0126069922284415,271.608181870646],"luv":[40.1118623747323184,2.75066337520804938,-97.9740015535209],"rgb":[0.4,0.266666666666666663,0.8],"xyz":[0.184443224065567735,0.11318531461244366,0.583415858948908239],"hpluv":[271.608181870646,310.061926380003911,40.1118623747323184],"hsluv":[271.608181870646,73.249037078124374,40.1118623747323184]},"#6644dd":{"lch":[41.4996246628331491,109.23540368219534,270.561113733160255],"luv":[41.4996246628331491,1.06975602599714947,-109.230165429047204],"rgb":[0.4,0.266666666666666663,0.866666666666666696],"xyz":[0.205961549616201278,0.121792644832697194,0.696745706848914237],"hpluv":[270.561113733160255,334.009312605211903,41.4996246628331491],"hsluv":[270.561113733160255,82.0982912580276434,41.4996246628331491]},"#6644ee":{"lch":[42.9610953823040305,120.093362966476874,269.772657810053431],"luv":[42.9610953823040305,-0.476513570908160711,-120.092417593346411],"rgb":[0.4,0.266666666666666663,0.933333333333333348],"xyz":[0.229773729882376043,0.131317516939167239,0.82215652291743746],"hpluv":[269.772657810053431,354.717803858999673,42.9610953823040305],"hsluv":[269.772657810053431,90.971694410809846,42.9610953823040305]},"#6644ff":{"lch":[44.4879743720372502,130.630057251556309,269.16406595263021],"luv":[44.4879743720372502,-1.90579898569535566,-130.61615439053088],"rgb":[0.4,0.266666666666666663,1],"xyz":[0.255944778292207409,0.141785936303099935,0.959990711209219305],"hpluv":[269.16406595263021,372.597392941492103,44.4879743720372502],"hsluv":[269.16406595263021,99.9999999999993463,44.4879743720372502]},"#ddcc00":{"lch":[81.0484811072975475,89.5621409057231119,78.2088923998372394],"luv":[81.0484811072975475,18.3014975440108643,87.6723004789036224],"rgb":[0.866666666666666696,0.8,0],"xyz":[0.514100482595981623,0.585588123937282434,0.0859503143074022424],"hpluv":[78.2088923998372394,197.564965691755532,81.0484811072975475],"hsluv":[78.2088923998372394,100.000000000002245,81.0484811072975475]},"#ddcc11":{"lch":[81.070830830397739,88.588371741804238,78.1407504993947413],"luv":[81.070830830397739,18.2056357525625714,86.6974880530521261],"rgb":[0.866666666666666696,0.8,0.0666666666666666657],"xyz":[0.515112148095618783,0.585992790137137343,0.0912784192721578791],"hpluv":[78.1407504993947413,195.686226016320433,81.070830830397739],"hsluv":[78.1407504993947413,98.8754134531589699,81.070830830397739]},"#ddcc22":{"lch":[81.1122340585289265,86.7947856050049751,78.0111959233441894],"luv":[81.1122340585289265,18.0290607078827385,84.9016358983162149],"rgb":[0.866666666666666696,0.8,0.133333333333333331],"xyz":[0.516987506234095728,0.586742933392528143,0.101155305468137072],"hpluv":[78.0111959233441894,192.214732219338657,81.1122340585289265],"hsluv":[78.0111959233441894,96.8049917221618443,81.1122340585289265]},"#ddcc33":{"lch":[81.1803270736657367,83.8738846457710139,77.7882255314295747],"luv":[81.1803270736657367,17.7414783140301893,81.9760237679585089],"rgb":[0.866666666666666696,0.8,0.2],"xyz":[0.520075256966553545,0.58797803368551127,0.117417459325748208],"hpluv":[77.7882255314295747,186.530107062901806,81.1803270736657367],"hsluv":[77.7882255314295747,93.4358723678778631,81.1803270736657367]},"#ddcc44":{"lch":[81.2784695635313312,79.7261575767135469,77.443216014682],"luv":[81.2784695635313312,17.3330313363216426,77.8191893214074213],"rgb":[0.866666666666666696,0.8,0.266666666666666663],"xyz":[0.524533252210772405,0.589761231783198836,0.140896234278634847],"hpluv":[77.443216014682,178.389435414851135,81.2784695635313312],"hsluv":[77.443216014682,88.6570950843060643,81.2784695635313312]},"#ddcc55":{"lch":[81.4094229919429893,74.303525235023784,76.9333685993953509],"luv":[81.4094229919429893,16.7988406729037365,72.37964364652683],"rgb":[0.866666666666666696,0.8,0.333333333333333315],"xyz":[0.530495666250134335,0.592146197398943586,0.172298281552608762],"hpluv":[76.9333685993953509,167.620308244181615,81.4094229919429893],"hsluv":[76.9333685993953509,82.4185745103993668,81.4094229919429893]},"#ddcc66":{"lch":[81.5755062452221154,67.6049359495878406,76.1891492108976252],"luv":[81.5755062452221154,16.1384724175358123,65.6504156329288548],"rgb":[0.866666666666666696,0.8,0.4],"xyz":[0.538080662023642575,0.595180195708346904,0.212245925959752724],"hpluv":[76.1891492108976252,154.10859642349061,81.5755062452221154],"hsluv":[76.1891492108976252,74.724981631947216,81.5755062452221154]},"#ddcc77":{"lch":[81.7786782860545571,59.6741108071854,75.0887439649445554],"luv":[81.7786782860545571,15.3554995822328237,57.6646176889111857],"rgb":[0.866666666666666696,0.8,0.466666666666666674],"xyz":[0.547394675043844425,0.598905800916427755,0.261299727866150633],"hpluv":[75.0887439649445554,137.792143148878608,81.7786782860545571],"hsluv":[75.0887439649445554,65.6305091544174104,81.7786782860545571]},"#ddcc88":{"lch":[82.020587165389415,50.599662209211,73.3985048227604722],"luv":[82.020587165389415,14.4570002873895778,48.4904213054152891],"rgb":[0.866666666666666696,0.8,0.533333333333333326],"xyz":[0.558535092145788337,0.603361967757205298,0.319972591269722773],"hpluv":[73.3985048227604722,118.661765286999071,82.020587165389415],"hsluv":[73.3985048227604722,55.2326761810852389,82.020587165389415]},"#ddcc99":{"lch":[82.3026016456871901,40.5232855707982438,70.6109505258247765],"luv":[82.3026016456871901,13.4529549720920478,38.2250529884698622],"rgb":[0.866666666666666696,0.8,0.6],"xyz":[0.571592056254861,0.608584753400834511,0.388739268910840707],"hpluv":[70.6109505258247765,96.7842051030752231,82.3026016456871901],"hsluv":[70.6109505258247765,43.6647666281718685,82.3026016456871901]},"#ddccaa":{"lch":[82.6258332993788542,29.6818113573176419,65.4008685536862231],"luv":[82.6258332993788542,12.3555588351715961,26.9879619705157374],"rgb":[0.866666666666666696,0.8,0.66666666666666663],"xyz":[0.586649749220113592,0.614607830586935577,0.468043118527839308],"hpluv":[65.4008685536862231,72.4135107626852,82.6258332993788542],"hsluv":[65.4008685536862231,31.0871436920773085,82.6258332993788542]},"#ddccbb":{"lch":[82.9911533066729419,18.6379527757414252,53.1465475992889],"luv":[82.9911533066729419,11.1784915779275345,14.9135713265793797],"rgb":[0.866666666666666696,0.8,0.733333333333333282],"xyz":[0.603787341327450156,0.621462867429870336,0.55830110362648111],"hpluv":[53.1465475992889,46.5946179098545272,82.9911533066729419],"hsluv":[53.1465475992889,24.678645774572626,82.9911533066729419]},"#ddcccc":{"lch":[83.3992063850657,10.164901186858037,12.1770506300648638],"luv":[83.3992063850657,9.93619558955776228,2.14411598208697862],"rgb":[0.866666666666666696,0.8,0.8],"xyz":[0.62307971677117191,0.629179817607359193,0.659907614296751266],"hpluv":[12.1770506300648638,26.1289592314662436,83.3992063850657],"hsluv":[12.1770506300648638,25.0002112827592455,83.3992063850657]},"#ddccdd":{"lch":[83.8504233095379163,14.1290270468723165,307.715012949259346],"luv":[83.8504233095379163,8.64321103323171513,-11.1769543403501022],"rgb":[0.866666666666666696,0.8,0.866666666666666696],"xyz":[0.644598042321805509,0.637787147827612699,0.773237462196757264],"hpluv":[307.715012949259346,37.4791950150616557,83.8504233095379163],"hsluv":[307.715012949259346,25.214872966603707,83.8504233095379163]},"#ddccee":{"lch":[84.3450329093034,25.9620569722210597,286.361909425528779],"luv":[84.3450329093034,7.31360586969829818,-24.9106316943502399],"rgb":[0.866666666666666696,0.8,0.933333333333333348],"xyz":[0.668410222587980218,0.647312019934082716,0.898648278265280487],"hpluv":[286.361909425528779,71.3464154396222199,84.3450329093034],"hsluv":[286.361909425528779,60.879598082714125,84.3450329093034]},"#ddccff":{"lch":[84.8830740665913623,39.380529428060008,278.705583312848262],"luv":[84.8830740665913623,5.96052452183370107,-38.926831947371717],"rgb":[0.866666666666666696,0.8,1],"xyz":[0.694581270997811639,0.65778043929801544,1.03648246655706222],"hpluv":[278.705583312848262,112.590543218900592,84.8830740665913623],"hsluv":[278.705583312848262,99.9999999999947704,84.8830740665913623]},"#665500":{"lch":[36.5970311204425656,41.5054710368830655,69.2006364019199651],"luv":[36.5970311204425656,14.7384507745119784,38.800543743107859],"rgb":[0.4,0.333333333333333315,0],"xyz":[0.0872772466047234,0.0932201343710592,0.0133963111976744542],"hpluv":[69.2006364019199651,143.912599562803223,36.5970311204425656],"hsluv":[69.2006364019199651,100.000000000002359,36.5970311204425656]},"#665511":{"lch":[36.673028710438345,38.8606023214232366,68.2658014049057869],"luv":[36.673028710438345,14.3901304021596417,36.0980686435250391],"rgb":[0.4,0.333333333333333315,0.0666666666666666657],"xyz":[0.0882889121043605174,0.0936248005709140463,0.0187244161624300839],"hpluv":[68.2658014049057869,134.462776824764262,36.673028710438345],"hsluv":[68.2658014049057869,93.0449405246809107,36.673028710438345]},"#665522":{"lch":[36.8133307706753357,34.1687527519613923,66.2355675996872719],"luv":[36.8133307706753357,13.769229594380441,31.2715842419585037],"rgb":[0.4,0.333333333333333315,0.133333333333333331],"xyz":[0.0901642702428375464,0.0943749438263048607,0.0286013023584092835],"hpluv":[66.2355675996872719,117.777773408929676,36.8133307706753357],"hsluv":[66.2355675996872719,80.6853283069105629,36.8133307706753357]},"#665533":{"lch":[37.0427251812615097,27.0303271109949854,61.7081001991288645],"luv":[37.0427251812615097,12.8113945713483854,23.8014023297917383],"rgb":[0.4,0.333333333333333315,0.2],"xyz":[0.09325202097529528,0.0956100441192879735,0.0448634562160204267],"hpluv":[61.7081001991288645,92.5950345984193177,37.0427251812615097],"hsluv":[61.7081001991288645,61.7209513910547045,37.0427251812615097]},"#665544":{"lch":[37.3704580906404473,18.1024774089589755,50.3425610862832542],"luv":[37.3704580906404473,11.5529306171195891,13.9366237840407656],"rgb":[0.4,0.333333333333333315,0.266666666666666663],"xyz":[0.0977100162195141536,0.0973932422169755535,0.0683422311689070583],"hpluv":[50.3425610862832542,61.4679768710498351,37.3704580906404473],"hsluv":[50.3425610862832542,37.0117339514184067,37.3704580906404473]},"#665555":{"lch":[37.8025949068387348,10.2943047784276782,12.1770506300631105],"luv":[37.8025949068387348,10.0626876598879917,2.1714115065313786],"rgb":[0.4,0.333333333333333315,0.333333333333333315],"xyz":[0.103672430258876153,0.0997782078327203864,0.0997442784428809737],"hpluv":[12.1770506300631105,34.5553054430909654,37.8025949068387348],"hsluv":[12.1770506300631105,8.09737912949257321,37.8025949068387348]},"#665566":{"lch":[38.3424918197480693,13.7697499972876347,307.715012949250308],"luv":[38.3424918197480693,8.42342892447929,-10.8927434626015351],"rgb":[0.4,0.333333333333333315,0.4],"xyz":[0.111257426032384324,0.102812206142123705,0.139691922850024935],"hpluv":[307.715012949250308,45.570631638882567,38.3424918197480693],"hsluv":[307.715012949250308,15.7660506346962208,38.3424918197480693]},"#665577":{"lch":[38.9911218270375812,25.5692820710091411,285.225395910208761],"luv":[38.9911218270375812,6.71492531026355,-24.671805035392353],"rgb":[0.4,0.333333333333333315,0.466666666666666674],"xyz":[0.120571439052586243,0.106537811350204514,0.188745724756422845],"hpluv":[285.225395910208761,83.2131821356252885,38.9911218270375812],"hsluv":[285.225395910208761,23.5948697041559434,38.9911218270375812]},"#665588":{"lch":[39.7473800461840554,39.003702064123587,277.369365694294345],"luv":[39.7473800461840554,5.00282395952989312,-38.6815269493963],"rgb":[0.4,0.333333333333333315,0.533333333333333326],"xyz":[0.131711856154530071,0.110993978190982112,0.247418588159995],"hpluv":[277.369365694294345,124.519293959659265,39.7473800461840554],"hsluv":[277.369365694294345,31.2388068533835614,39.7473800461840554]},"#665599":{"lch":[40.6084045881889466,52.6763518020623636,273.629018089310307],"luv":[40.6084045881889466,3.33420095564239061,-52.5707251534733189],"rgb":[0.4,0.333333333333333315,0.6],"xyz":[0.144768820263602771,0.11621676383461127,0.316185265801112947],"hpluv":[273.629018089310307,164.603508765513965,40.6084045881889466],"hsluv":[273.629018089310307,39.2894117268144569,40.6084045881889466]},"#6655aa":{"lch":[41.5699140891343575,66.1399121177165,271.505962745252077],"luv":[41.5699140891343575,1.73822184255944534,-66.1170670838121168],"rgb":[0.4,0.333333333333333315,0.66666666666666663],"xyz":[0.159826513228855327,0.122239841020712364,0.395489115418111492],"hpluv":[271.505962745252077,201.894186201954597,41.5699140891343575],"hsluv":[271.505962745252077,49.6682832978127067,41.5699140891343575]},"#6655bb":{"lch":[42.6265484117568647,79.2031960637105499,270.166039680829499],"luv":[42.6265484117568647,0.229525718995523903,-79.2028634876978],"rgb":[0.4,0.333333333333333315,0.733333333333333282],"xyz":[0.176964105336191974,0.129094877863647123,0.485747100516753294],"hpluv":[270.166039680829499,235.777232293519603,42.6265484117568647],"hsluv":[270.166039680829499,59.8653820834470309,42.6265484117568647]},"#6655cc":{"lch":[43.7721949853351333,91.7929775852419,269.258674658723919],"luv":[43.7721949853351333,-1.18763654850412936,-91.7852943199147688],"rgb":[0.4,0.333333333333333315,0.8],"xyz":[0.1962564807799137,0.136811828041135924,0.58735361118702345],"hpluv":[269.258674658723919,266.103421792879146,43.7721949853351333],"hsluv":[269.258674658723919,69.9112236392489734,43.7721949853351333]},"#6655dd":{"lch":[45.0002850881211458,103.899953671233533,268.61259940679895],"luv":[45.0002850881211458,-2.51566121975990953,-103.869494181457682],"rgb":[0.4,0.333333333333333315,0.866666666666666696],"xyz":[0.217774806330547244,0.145419158261389458,0.700683459087029448],"hpluv":[268.61259940679895,292.98097185143456,45.0002850881211458],"hsluv":[268.61259940679895,79.8793625185682714,45.0002850881211458]},"#6655ee":{"lch":[46.3040490971424106,115.549020689755764,268.134901078425969],"luv":[46.3040490971424106,-3.7607010001107648,-115.487805894602445],"rgb":[0.4,0.333333333333333315,0.933333333333333348],"xyz":[0.241586986596722,0.154944030367859503,0.826094275155552671],"hpluv":[268.134901078425969,316.655201018988919,46.3040490971424106],"hsluv":[268.134901078425969,89.8701820385079344,46.3040490971424106]},"#6655ff":{"lch":[47.6767252326213651,126.781348408818275,267.771145841725911],"luv":[47.6767252326213651,-4.93065761335473951,-126.685432942615918],"rgb":[0.4,0.333333333333333315,1],"xyz":[0.267758035006553374,0.165412449731792199,0.963928463447334516],"hpluv":[267.771145841725911,337.433561350206048,47.6767252326213651],"hsluv":[267.771145841725911,99.9999999999992468,47.6767252326213651]},"#dddd00":{"lch":[85.547159878993142,94.3072427966830844,85.8743202181747449],"luv":[85.547159878993142,6.78488618903739749,94.0628585750738466],"rgb":[0.866666666666666696,0.866666666666666696,0],"xyz":[0.556734473143156827,0.670856105031634,0.100161644489793561],"hpluv":[85.8743202181747449,283.614606809988061,85.547159878993142],"hsluv":[85.8743202181747449,100.000000000002203,85.547159878993142]},"#dddd11":{"lch":[85.5675738163798627,93.4011806547303394,85.8743202181746881],"luv":[85.5675738163798627,6.71970001318247423,93.159144368282],"rgb":[0.866666666666666696,0.866666666666666696,0.0666666666666666657],"xyz":[0.557746138642794,0.671260771231488862,0.105489749454549198],"hpluv":[85.8743202181746881,281.335749103468061,85.5675738163798627],"hsluv":[85.8743202181746881,99.0156164862488,85.5675738163798627]},"#dddd22":{"lch":[85.6053941241358558,91.7307060091609401,85.8743202181746739],"luv":[85.6053941241358558,6.59951857201479086,91.4929985275198447],"rgb":[0.866666666666666696,0.866666666666666696,0.133333333333333331],"xyz":[0.559621496781270933,0.672010914486879662,0.11536663565052839],"hpluv":[85.8743202181746739,277.118842420723468,85.6053941241358558],"hsluv":[85.8743202181746739,97.2017654403352083,85.6053941241358558]},"#dddd33":{"lch":[85.667603455332241,89.0058006932873,85.8743202181745602],"luv":[85.667603455332241,6.4034766573555908,88.775154428206335],"rgb":[0.866666666666666696,0.866666666666666696,0.2],"xyz":[0.56270924751372875,0.673246014779862789,0.131628789508139526],"hpluv":[85.8743202181745602,270.196330508983522,85.667603455332241],"hsluv":[85.8743202181745602,94.2458512979473113,85.667603455332241]},"#dddd44":{"lch":[85.7572852094861418,85.1265937151141117,85.8743202181744607],"luv":[85.7572852094861418,6.12438910193468278,84.9059998802570419],"rgb":[0.866666666666666696,0.866666666666666696,0.266666666666666663],"xyz":[0.567167242757947609,0.675029212877550355,0.155107564461026165],"hpluv":[85.8743202181744607,260.24482525674506,85.7572852094861418],"hsluv":[85.8743202181744607,90.0440000232135844,85.7572852094861418]},"#dddd55":{"lch":[85.8769849033878074,80.0369945631262,85.874320218174276],"luv":[85.8769849033878074,5.75822050268421481,79.8295897229865545],"rgb":[0.866666666666666696,0.866666666666666696,0.333333333333333315],"xyz":[0.573129656797309539,0.677414178493295105,0.18650961173500008],"hpluv":[85.874320218174276,247.008869171162701,85.8769849033878074],"hsluv":[85.874320218174276,84.5423921226573327,85.8769849033878074]},"#dddd66":{"lch":[86.0288537292730098,73.7198014533942398,85.8743202181739775],"luv":[86.0288537292730098,5.30373328608645522,73.5287667485177252],"rgb":[0.866666666666666696,0.866666666666666696,0.4],"xyz":[0.58071465257081778,0.680448176802698423,0.226457256142144042],"hpluv":[85.8743202181739775,230.281095596483937,86.0288537292730098],"hsluv":[85.8743202181739775,77.7321300368988,86.0288537292730098]},"#dddd77":{"lch":[86.2147251389940834,66.1931358813644124,85.8743202181736365],"luv":[86.2147251389940834,4.76223119383214133,66.0216054929389315],"rgb":[0.866666666666666696,0.866666666666666696,0.466666666666666674],"xyz":[0.59002866559101963,0.684173782010779274,0.275511058048541924],"hpluv":[85.8743202181736365,209.886373280136951,86.2147251389940834],"hsluv":[85.8743202181736365,69.6453389130317362,86.2147251389940834]},"#dddd88":{"lch":[86.4361603707972,57.5066396271378224,85.8743202181730823],"luv":[86.4361603707972,4.13728567831694072,57.3576190965201391],"rgb":[0.866666666666666696,0.866666666666666696,0.533333333333333326],"xyz":[0.601169082692963541,0.688629948851556817,0.334183921452114119],"hpluv":[85.8743202181730823,185.665493576193455,86.4361603707972],"hsluv":[85.8743202181730823,60.3508056243124429,86.4361603707972]},"#dddd99":{"lch":[86.6944777431662,47.7369558995854888,85.8743202181722154],"luv":[86.6944777431662,3.43441079587341314,47.613252157820078],"rgb":[0.866666666666666696,0.866666666666666696,0.6],"xyz":[0.614226046802036185,0.693852734495186,0.402950599093232054],"hpluv":[85.8743202181722154,157.456397081560084,86.6944777431662],"hsluv":[85.8743202181722154,49.9486591868920939,86.6944777431662]},"#ddddaa":{"lch":[86.990772885999732,36.9824894935128,85.874320218170638],"luv":[86.990772885999732,2.66068622896724838,36.886654469181245],"rgb":[0.866666666666666696,0.866666666666666696,0.66666666666666663],"xyz":[0.629283739767288797,0.699875811681287097,0.482254448710230599],"hpluv":[85.874320218170638,125.071764704863014,86.990772885999732],"hsluv":[85.874320218170638,38.5641267001819443,86.990772885999732]},"#ddddbb":{"lch":[87.3259337660435477,25.3576808227713499,85.8743202181676821],"luv":[87.3259337660435477,1.82434533444747116,25.2919698878957568],"rgb":[0.866666666666666696,0.866666666666666696,0.733333333333333282],"xyz":[0.64642133187462536,0.706730848524221855,0.572512433808872401],"hpluv":[85.8743202181676821,88.2719508819342735,87.3259337660435477],"hsluv":[85.8743202181676821,26.340671416181145,87.3259337660435477]},"#ddddcc":{"lch":[87.7006527393466797,12.9871390430395461,85.8743202181585445],"luv":[87.7006527393466797,0.934353054076054512,12.9534846621895881],"rgb":[0.866666666666666696,0.866666666666666696,0.8],"xyz":[0.665713707318347114,0.714447798701710712,0.674118944479142557],"hpluv":[85.8743202181585445,46.7319159493201113,87.7006527393466797],"hsluv":[85.8743202181585445,13.4329442518860063,87.7006527393466797]},"#dddddd":{"lch":[88.1154369871094,4.67545248961294327e-12,0],"luv":[88.1154369871094,4.4193702762792188e-12,1.52611347670073729e-12],"rgb":[0.866666666666666696,0.866666666666666696,0.866666666666666696],"xyz":[0.687232032868980713,0.723055128921964219,0.787448792379148554],"hpluv":[0,1.74708563976297451e-11,88.1154369871094],"hsluv":[0,1.74437740136320375e-11,88.1154369871094]},"#ddddee":{"lch":[88.5706181797242209,13.4751686036456029,265.874320218195521],"luv":[88.5706181797242209,-0.969464090371862097,-13.4402495614536726],"rgb":[0.866666666666666696,0.866666666666666696,0.933333333333333348],"xyz":[0.711044213135155423,0.732580001028434236,0.912859608447671778],"hpluv":[265.874320218195521,52.5550848411252431,88.5706181797242209],"hsluv":[265.874320218195521,47.1269490590101725,88.5706181797242209]},"#ddddff":{"lch":[89.0663618949558753,27.3146757005029208,265.874320218186369],"luv":[89.0663618949558753,-1.9651403266809826,-27.2438934831293338],"rgb":[0.866666666666666696,0.866666666666666696,1],"xyz":[0.737215261544986844,0.743048420392367,1.05069379673945362],"hpluv":[265.874320218186369,111.815120511018762,89.0663618949558753],"hsluv":[265.874320218186369,99.9999999999922125,89.0663618949558753]},"#666600":{"lch":[41.7321583215394583,46.0055575524193685,85.8743202181747449],"luv":[41.7321583215394583,3.30984623025532709,45.8863404908370924],"rgb":[0.4,0.4,0],"xyz":[0.102305304310569861,0.123276249782752534,0.0184056637662898],"hpluv":[85.8743202181747449,139.887458074797365,41.7321583215394583],"hsluv":[85.8743202181747449,100.000000000002203,41.7321583215394583]},"#666611":{"lch":[41.7952597887023742,43.6298127640598423,85.8743202181746],"luv":[41.7952597887023742,3.13892449057558931,43.5167521176544625],"rgb":[0.4,0.4,0.0666666666666666657],"xyz":[0.103316969810206979,0.123680915982607387,0.0237337687310454348],"hpluv":[85.8743202181746,132.463323325332908,41.7952597887023742],"hsluv":[85.8743202181746,94.6927802880713756,41.7952597887023742]},"#666622":{"lch":[41.9118699845736913,39.3503176022612067,85.874320218174276],"luv":[41.9118699845736913,2.83103840719259514,39.248346677737],"rgb":[0.4,0.4,0.133333333333333331],"xyz":[0.105192327948684008,0.124431059237998201,0.0336106549270246274],"hpluv":[85.874320218174276,119.138061739500813,41.9118699845736913],"hsluv":[85.874320218174276,85.1670788640685288,41.9118699845736913]},"#666633":{"lch":[42.1028501842444953,32.6344620115447057,85.8743202181736507],"luv":[42.1028501842444953,2.34786962297492563,32.5498943011565842],"rgb":[0.4,0.4,0.2],"xyz":[0.108280078681141742,0.125666159530981314,0.0498728087846357776],"hpluv":[85.8743202181736507,98.3567766096709306,42.1028501842444953],"hsluv":[85.8743202181736507,70.3113616926845,42.1028501842444953]},"#666644":{"lch":[42.3763861696741557,23.5947988734222314,85.8743202181723575],"luv":[42.3763861696741557,1.69751569722617934,23.5336562041455402],"rgb":[0.4,0.4,0.266666666666666663],"xyz":[0.112738073925360616,0.127449357628668908,0.0733515837375224161],"hpluv":[85.8743202181723575,70.6531759312171346,42.3763861696741557],"hsluv":[85.8743202181723575,50.5071554688203506,42.3763861696741557]},"#666655":{"lch":[42.7382714661199543,12.562340839470254,85.87432021816781],"luv":[42.7382714661199543,0.903791165304248856,12.5297872646162745],"rgb":[0.4,0.4,0.333333333333333315],"xyz":[0.118700487964722615,0.129834323244413741,0.104753631011496318],"hpluv":[85.87432021816781,37.2986356199978459,42.7382714661199543],"hsluv":[85.87432021816781,26.6633164497530721,42.7382714661199543]},"#666666":{"lch":[43.1922895629847048,2.27708065554704512e-12,0],"luv":[43.1922895629847048,2.15069538500574498e-12,7.48067960001998255e-13],"rgb":[0.4,0.4,0.4],"xyz":[0.126285483738230786,0.132868321553817031,0.144701275418640279],"hpluv":[0,6.68977504875838914e-12,43.1922895629847048],"hsluv":[0,1.91542116883063395e-12,43.1922895629847048]},"#666677":{"lch":[43.7404449074606489,13.5883126365404472,265.874320218186085],"luv":[43.7404449074606489,-0.977604179760566572,-13.5531003971814314],"rgb":[0.4,0.4,0.466666666666666674],"xyz":[0.135599496758432692,0.136593926761897855,0.193755077325038189],"hpluv":[265.874320218186085,39.4204575510779804,43.7404449074606489],"hsluv":[265.874320218186085,10.3527957183817456,43.7404449074606489]},"#666688":{"lch":[44.3831523723879684,27.7327571842679852,265.874320218181708],"luv":[44.3831523723879684,-1.9952189844931838,-27.660891566352042],"rgb":[0.4,0.4,0.533333333333333326],"xyz":[0.146739913860376547,0.141050093602675453,0.252427940728610356],"hpluv":[265.874320218181708,79.2892354857961692,44.3831523723879684],"hsluv":[265.874320218181708,21.2254167484079588,44.3831523723879684]},"#666699":{"lch":[45.1194249231942308,42.0446421145154,265.87432021818023],"luv":[45.1194249231942308,-3.02488020162380478,-41.9356892193690598],"rgb":[0.4,0.4,0.6],"xyz":[0.159796877969449247,0.14627287924630461,0.321194618369728291],"hpluv":[265.87432021818023,118.245992523098394,45.1194249231942308],"hsluv":[265.87432021818023,32.3647541960069702,45.1194249231942308]},"#6666aa":{"lch":[45.9470714788517682,56.2348015337582652,265.874320218179548],"luv":[45.9470714788517682,-4.04578393932889835,-56.0890767962662125],"rgb":[0.4,0.4,0.66666666666666663],"xyz":[0.174854570934701803,0.152295956432405705,0.400498467986726892],"hpluv":[265.874320218179548,155.305436018888514,45.9470714788517682],"hsluv":[265.874320218179548,43.5990379455573205,45.9470714788517682]},"#6666bb":{"lch":[46.8629040956598786,70.1103551977131,265.874320218179093],"luv":[46.8629040956598786,-5.04405352741049562,-69.9286738753289541],"rgb":[0.4,0.4,0.733333333333333282],"xyz":[0.191992163042038422,0.159150993275340463,0.490756453085368638],"hpluv":[265.874320218179093,189.841997809706953,46.8629040956598786],"hsluv":[265.874320218179093,54.8353857399755285,46.8629040956598786]},"#6666cc":{"lch":[47.8629477245616854,83.5592716582008,265.874320218178866],"luv":[47.8629477245616854,-6.01162892081844,-83.3427393224351505],"rgb":[0.4,0.4,0.8],"xyz":[0.211284538485760176,0.166867943452829265,0.592362963755638794],"hpluv":[265.874320218178866,221.531011478982748,47.8629477245616854],"hsluv":[265.874320218178866,66.0482344892977693,47.8629477245616854]},"#6666dd":{"lch":[48.9426439028117102,96.5306872715973583,265.874320218178696],"luv":[48.9426439028117102,-6.94485076081307,-96.2805413000828736],"rgb":[0.4,0.4,0.866666666666666696],"xyz":[0.23280286403639372,0.175475273673082799,0.705692811655644792],"hpluv":[265.874320218178696,250.274901054084751,48.9426439028117102],"hsluv":[265.874320218178696,77.2646968282616911,48.9426439028117102]},"#6666ee":{"lch":[50.0970402589656203,109.016062738443594,265.874320218178525],"luv":[50.0970402589656203,-7.84310469187671711,-108.73356263723052],"rgb":[0.4,0.4,0.933333333333333348],"xyz":[0.256615044302568429,0.185000145779552844,0.831103627724168],"hpluv":[265.874320218178525,276.132643737939816,50.0970402589656203],"hsluv":[265.874320218178525,88.5507283896609181,50.0970402589656203]},"#6666ff":{"lch":[51.3209595583197142,121.033610519319112,265.874320218178411],"luv":[51.3209595583197142,-8.70770100014002502,-120.719968599376287],"rgb":[0.4,0.4,1],"xyz":[0.28278609271239985,0.19546856514348554,0.96893781601594986],"hpluv":[265.874320218178411,299.261292593223402,51.3209595583197142],"hsluv":[265.874320218178411,99.9999999999991616,51.3209595583197142]},"#ddee00":{"lch":[90.1008574130140261,100.518542770188731,92.3281002120423295],"luv":[90.1008574130140261,-4.08324753875312307,100.435574027231638],"rgb":[0.866666666666666696,0.933333333333333348,0],"xyz":[0.603913249483671644,0.765213657712664919,0.115887903269964759],"hpluv":[92.3281002120423295,458.324419080212692,90.1008574130140261],"hsluv":[92.3281002120423295,100.000000000002288,90.1008574130140261]},"#ddee11":{"lch":[90.1195571422023676,99.6821059706602739,92.3717344777628284],"luv":[90.1195571422023676,-4.12512016496088219,99.5967149778072667],"rgb":[0.866666666666666696,0.933333333333333348,0.0666666666666666657],"xyz":[0.604924914983308804,0.765618323912519827,0.121216008234720396],"hpluv":[92.3717344777628284,455.439701471196656,90.1195571422023676],"hsluv":[92.3717344777628284,99.1349582088955827,90.1195571422023676]},"#ddee22":{"lch":[90.1542040339558213,98.139145338163118,92.4541942657322409],"luv":[90.1542040339558213,-4.20238430415396635,98.0491295925940562],"rgb":[0.866666666666666696,0.933333333333333348,0.133333333333333331],"xyz":[0.606800273121785749,0.766368467167910628,0.131092894430699575],"hpluv":[92.4541942657322409,450.094002053356689,90.1542040339558213],"hsluv":[92.4541942657322409,97.539848527320828,90.1542040339558213]},"#ddee33":{"lch":[90.2112004280082402,95.6198982889531237,92.5945991605726562],"luv":[90.2112004280082402,-4.32860020636881337,95.5218727257959728],"rgb":[0.866666666666666696,0.933333333333333348,0.2],"xyz":[0.609888023854243566,0.767603567460893754,0.147355048288310725],"hpluv":[92.5945991605726562,441.296742170845221,90.2112004280082402],"hsluv":[92.5945991605726562,94.9371734060285348,90.2112004280082402]},"#ddee44":{"lch":[90.2933822328294582,92.028546743238266,92.808164678773025],"luv":[90.2933822328294582,-4.50867173729536486,91.9180357429253689],"rgb":[0.866666666666666696,0.933333333333333348,0.266666666666666663],"xyz":[0.614346019098462426,0.76938676555858132,0.170833823241197363],"hpluv":[92.808164678773025,428.602609406071736,90.2933822328294582],"hsluv":[92.808164678773025,91.2305485856041827,90.2933822328294582]},"#ddee55":{"lch":[90.4030992965535,87.3081168904988658,93.1158428735316477],"luv":[90.4030992965535,-4.74562541158387852,87.1790474507375137],"rgb":[0.866666666666666696,0.933333333333333348,0.333333333333333315],"xyz":[0.620308433137824355,0.77177173117432607,0.202235870515171279],"hpluv":[93.1158428735316477,411.63119840797242,90.4030992965535],"hsluv":[93.1158428735316477,86.365058197250562,90.4030992965535]},"#ddee66":{"lch":[90.5423480313319828,81.4363754503924,93.5488244177654451],"luv":[90.5423480313319828,-5.04083628618753,81.2802141731499717],"rgb":[0.866666666666666696,0.933333333333333348,0.4],"xyz":[0.627893428911332596,0.774805729483729388,0.242183514922315241],"hpluv":[93.5488244177654451,390.03815237863023,90.5423480313319828],"hsluv":[93.5488244177654451,80.3228278296054583,90.5423480313319828]},"#ddee77":{"lch":[90.7128424721769449,74.4235139877598613,94.1564113073248],"luv":[90.7128424721769449,-5.39417557361030209,74.2277731322134855],"rgb":[0.866666666666666696,0.933333333333333348,0.466666666666666674],"xyz":[0.637207441931534446,0.77853133469181024,0.291237316828713122],"hpluv":[94.1564113073248,363.492124647987794,90.7128424721769449],"hsluv":[94.1564113073248,73.1200108954656116,90.7128424721769449]},"#ddee88":{"lch":[90.9160566530372449,66.3104835404903099,95.0215233218939801],"luv":[90.9160566530372449,-5.80415396856009735,66.0559764448523197],"rgb":[0.866666666666666696,0.933333333333333348,0.533333333333333326],"xyz":[0.648347859033478358,0.782987501532587782,0.349910180232285317],"hpluv":[95.0215233218939801,331.65301907995223,90.9160566530372449],"hsluv":[95.0215233218939801,64.803658473982523,90.9160566530372449]},"#ddee99":{"lch":[91.1532518637430798,57.1683554138076389,96.2947128608995513],"luv":[91.1532518637430798,-6.26808654843775237,56.8236918172404799],"rgb":[0.866666666666666696,0.933333333333333348,0.6],"xyz":[0.661404823142551,0.788210287176217,0.418676857873403252],"hpluv":[96.2947128608995513,294.152965661212647,91.1532518637430798],"hsluv":[96.2947128608995513,55.4479539090275679,91.1532518637430798]},"#ddeeaa":{"lch":[91.4254953447680805,47.1012792006961263,98.2790075719046712],"luv":[91.4254953447680805,-6.78228473243696595,46.6104185365255219],"rgb":[0.866666666666666696,0.933333333333333348,0.66666666666666663],"xyz":[0.676462516107803613,0.794233364362318062,0.497980707490401797],"hpluv":[98.2790075719046712,250.593114601078071,91.4254953447680805],"hsluv":[98.2790075719046712,45.1497304611624699,91.4254953447680805]},"#ddeebb":{"lch":[91.7336739482950634,36.2629153409390739,101.681625346389353],"luv":[91.7336739482950634,-7.34227032126066,35.5118303605101318],"rgb":[0.866666666666666696,0.933333333333333348,0.733333333333333282],"xyz":[0.693600108215140176,0.80108840120525282,0.588238692589043599],"hpluv":[101.681625346389353,200.613962446551909,91.7336739482950634],"hsluv":[101.681625346389353,34.0234190840686495,91.7336739482950634]},"#ddeecc":{"lch":[92.0785048140775189,24.9340669502590622,108.575873850927678],"luv":[92.0785048140775189,-7.94300120518182506,23.6350677285782567],"rgb":[0.866666666666666696,0.933333333333333348,0.8],"xyz":[0.712892483658861931,0.808805351382741677,0.689845203259313755],"hpluv":[108.575873850927678,144.339382081965653,92.0785048140775189],"hsluv":[108.575873850927678,22.1956929245148693,92.0785048140775189]},"#ddeedd":{"lch":[92.4605443140240908,14.0242187757329084,127.71501294922345],"luv":[92.4605443140240908,-8.57909621466854766,11.0940443666446207],"rgb":[0.866666666666666696,0.933333333333333348,0.866666666666666696],"xyz":[0.73441080920949553,0.817412681602995184,0.803175051159319753],"hpluv":[127.71501294922345,85.5555802205660854,92.4605443140240908],"hsluv":[127.71501294922345,19.0167034911391681,92.4605443140240908]},"#ddeeee":{"lch":[92.8801960589335636,9.45784403502816851,192.177050630058346],"luv":[92.8801960589335636,-9.24504689815110403,-1.99497409554724747],"rgb":[0.866666666666666696,0.933333333333333348,0.933333333333333348],"xyz":[0.758222989475670239,0.826937553709465201,0.928585867227843],"hpluv":[192.177050630058346,61.3009405779386327,92.8801960589335636],"hsluv":[192.177050630058346,16.5065503962475049,92.8801960589335636]},"#ddeeff":{"lch":[93.3377184761608305,18.4254994321377019,237.36941304521946],"luv":[93.3377184761608305,-9.93540601395951306,-15.5173044263971267],"rgb":[0.866666666666666696,0.933333333333333348,1],"xyz":[0.78439403788550166,0.837405973073397925,1.06642005551962482],"hpluv":[237.36941304521946,128.083838047846456,93.3377184761608305],"hsluv":[237.36941304521946,99.9999999999860592,93.3377184761608305]},"#667700":{"lch":[46.9985837429297462,53.5023535392226,97.7743932102929705],"luv":[46.9985837429297462,-7.23741162388150272,53.0105810873873509],"rgb":[0.4,0.466666666666666674,0],"xyz":[0.12075904236398749,0.160183725889588319,0.0245569097840955056],"hpluv":[97.7743932102929705,144.453291553004675,46.9985837429297462],"hsluv":[97.7743932102929705,100.000000000002416,46.9985837429297462]},"#667711":{"lch":[47.0515894602548315,51.4525286527524344,98.1592061252685681],"luv":[47.0515894602548315,-7.3023584206057679,50.9317019768564],"rgb":[0.4,0.466666666666666674,0.0666666666666666657],"xyz":[0.121770707863624608,0.160588392089443172,0.0298850147488511353],"hpluv":[98.1592061252685681,138.762383385982389,47.0515894602548315],"hsluv":[98.1592061252685681,95.8888552433840573,47.0515894602548315]},"#667722":{"lch":[47.1496128779850068,47.7434140964333835,98.9413563019921],"luv":[47.1496128779850068,-7.4204465613794337,47.1632331632727],"rgb":[0.4,0.466666666666666674,0.133333333333333331],"xyz":[0.123646066002101637,0.161338535344834,0.0397619009448303348],"hpluv":[98.9413563019921,128.491579290861381,47.1496128779850068],"hsluv":[98.9413563019921,88.4562635009272071,47.1496128779850068]},"#667733":{"lch":[47.3103471969426579,41.884713336750373,100.466311561708949],"luv":[47.3103471969426579,-7.6086666810921777,41.1878307590699961],"rgb":[0.4,0.466666666666666674,0.2],"xyz":[0.126733816734559357,0.162573635637817099,0.0560240548024414781],"hpluv":[100.466311561708949,112.341117260385403,47.3103471969426579],"hsluv":[100.466311561708949,76.7252257071794,47.3103471969426579]},"#667744":{"lch":[47.5409803755201068,33.9506682862991624,103.399633201782777],"luv":[47.5409803755201068,-7.86778476785512915,33.0264415269496823],"rgb":[0.4,0.466666666666666674,0.266666666666666663],"xyz":[0.131191811978778244,0.164356833735504693,0.0795028297553281166],"hpluv":[103.399633201782777,90.6190532449782324,47.5409803755201068],"hsluv":[103.399633201782777,60.8161329795325543,47.5409803755201068]},"#667755":{"lch":[47.8468512336942808,24.3055974565694441,109.700167733355244],"luv":[47.8468512336942808,-8.19336864569332768,22.8829800934354815],"rgb":[0.4,0.466666666666666674,0.333333333333333315],"xyz":[0.137154226018140257,0.166741799351249526,0.110904877029302018],"hpluv":[109.700167733355244,64.4602914823941262,47.8468512336942808],"hsluv":[109.700167733355244,41.23982633361328,47.8468512336942808]},"#667766":{"lch":[48.2317738399223543,14.0211946941261125,127.715012949232488],"luv":[48.2317738399223543,-8.57724628010491408,11.0916521267580634],"rgb":[0.4,0.466666666666666674,0.4],"xyz":[0.144739221791648415,0.169775797660652816,0.15085252143644598],"hpluv":[127.715012949232488,36.8885098590324958,48.2317738399223543],"hsluv":[127.715012949232488,18.7828263722028552,48.2317738399223543]},"#667777":{"lch":[48.6982180758881356,9.21652694043341,192.177050630059739],"luv":[48.6982180758881356,-9.00915932709454736,-1.94407228846053126],"rgb":[0.4,0.466666666666666674,0.466666666666666674],"xyz":[0.154053234811850348,0.17350140286873364,0.199906323342843889],"hpluv":[192.177050630059739,24.0156061835451808,48.6982180758881356],"hsluv":[192.177050630059739,23.9216020554503501,48.6982180758881356]},"#667788":{"lch":[49.2474401880289605,18.4334880243097601,239.056580638027469],"luv":[49.2474401880289605,-9.47834019220879398,-15.8099509152663327],"rgb":[0.4,0.466666666666666674,0.533333333333333326],"xyz":[0.165193651913794148,0.177957569709511237,0.258579186746416056],"hpluv":[239.056580638027469,47.4966726259429564,49.2474401880289605],"hsluv":[239.056580638027469,29.274081353383373,49.2474401880289605]},"#667799":{"lch":[49.8796002039077422,31.7351192214463786,251.680675473596239],"luv":[49.8796002039077422,-9.97474964621831,-30.126768188683684],"rgb":[0.4,0.466666666666666674,0.6],"xyz":[0.178250616022866876,0.183180355353140395,0.327345864387534],"hpluv":[251.680675473596239,80.734004933806176,49.8796002039077422],"hsluv":[251.680675473596239,34.661761655835349,49.8796002039077422]},"#6677aa":{"lch":[50.5938810850088174,45.7960576637453798,256.758518919433186],"luv":[50.5938810850088174,-10.4898463625666416,-44.5784928057335676],"rgb":[0.4,0.466666666666666674,0.66666666666666663],"xyz":[0.193308308988119404,0.18920343253924149,0.406649714004532592],"hpluv":[256.758518919433186,114.860161977636537,50.5938810850088174],"hsluv":[256.758518919433186,39.9381656359214858,50.5938810850088174]},"#6677bb":{"lch":[51.388614147457119,59.9444468574027738,259.409682348511467],"luv":[51.388614147457119,-11.0169047239158306,-58.9233783768730106],"rgb":[0.4,0.466666666666666674,0.733333333333333282],"xyz":[0.210445901095456078,0.196058469382176248,0.496907699103174338],"hpluv":[259.409682348511467,148.020333314730891,51.388614147457119],"hsluv":[259.409682348511467,49.2101017344363356,51.388614147457119]},"#6677cc":{"lch":[52.2614099661724225,73.9004259234932306,261.00752312302825],"luv":[52.2614099661724225,-11.5509895931656974,-72.9921063615257566],"rgb":[0.4,0.466666666666666674,0.8],"xyz":[0.229738276539177805,0.20377541955966505,0.598514209773444494],"hpluv":[261.00752312302825,179.434168102703751,52.2614099661724225],"hsluv":[261.00752312302825,61.6581230008595327,52.2614099661724225]},"#6677dd":{"lch":[53.2092913421323,87.5170631601914266,262.060353074135],"luv":[53.2092913421323,-12.0887245225321198,-86.6781349799548],"rgb":[0.4,0.466666666666666674,0.866666666666666696],"xyz":[0.251256602089811376,0.212382749779918584,0.711844057673450492],"hpluv":[262.060353074135,208.710638687620559,53.2092913421323],"hsluv":[262.060353074135,74.2459971892304083,53.2092913421323]},"#6677ee":{"lch":[54.2288239257805884,100.719531231348427,262.797438231409785],"luv":[54.2288239257805884,-12.6279723106459851,-99.9247631309883531],"rgb":[0.4,0.466666666666666674,0.933333333333333348],"xyz":[0.275068782355986086,0.221907621886388629,0.837254873741973715],"hpluv":[262.797438231409785,235.68005803746027,54.2288239257805884],"hsluv":[262.797438231409785,87.0042080584200193,54.2288239257805884]},"#6677ff":{"lch":[55.3162401631211793,113.47857319487936,263.336661992011841],"luv":[55.3162401631211793,-13.1675101506557386,-112.712036849566218],"rgb":[0.4,0.466666666666666674,1],"xyz":[0.301239830765817507,0.232376041250321325,0.97508906203375556],"hpluv":[263.336661992011841,260.315806593762318,55.3162401631211793],"hsluv":[263.336661992011841,99.999999999999,55.3162401631211793]},"#ddff00":{"lch":[94.69236188875891,107.73563953931891,97.6513944636985],"luv":[94.69236188875891,-14.3445112693176284,106.77641604488548],"rgb":[0.866666666666666696,1,0],"xyz":[0.65576562191334542,0.868918402572014,0.13317202741318887],"hpluv":[97.6513944636985,949.977135711580445,94.69236188875891],"hsluv":[97.6513944636985,100.000000000002302,94.69236188875891]},"#ddff11":{"lch":[94.7095428290633237,106.965998536257018,97.7198690947758308],"luv":[94.7095428290633237,-14.3687245726656343,105.996531061225838],"rgb":[0.866666666666666696,1,0.0666666666666666657],"xyz":[0.65677728741298258,0.869323068771868934,0.138500132377944507],"hpluv":[97.7198690947758308,946.378692368412885,94.7095428290633237],"hsluv":[97.7198690947758308,99.9999999999867697,94.7095428290633237]},"#ddff22":{"lch":[94.7413776147606086,105.545731309599958,97.8489007917202827],"luv":[94.7413776147606086,-14.4134418215914106,104.556941866783148],"rgb":[0.866666666666666696,1,0.133333333333333331],"xyz":[0.658652645551459526,0.870073212027259735,0.148377018573923686],"hpluv":[97.8489007917202827,939.695870823628752,94.7413776147606086],"hsluv":[97.8489007917202827,99.9999999999872,94.7413776147606086]},"#ddff33":{"lch":[94.7937532988665197,103.225383790498825,98.0674915546983],"luv":[94.7937532988665197,-14.4865977424562669,102.203807876928138],"rgb":[0.866666666666666696,1,0.2],"xyz":[0.661740396283917343,0.871308312320242861,0.164639172431534836],"hpluv":[98.0674915546983,928.656435156239354,94.7937532988665197],"hsluv":[98.0674915546983,99.9999999999868834,94.7937532988665197]},"#ddff44":{"lch":[94.8692843830354491,99.9146373114600692,98.397314807396512],"luv":[94.8692843830354491,-14.5912004882884094,98.8434702820129729],"rgb":[0.866666666666666696,1,0.266666666666666663],"xyz":[0.666198391528136202,0.873091510417930428,0.188117947384421474],"hpluv":[98.397314807396512,912.633071017653265,94.8692843830354491],"hsluv":[98.397314807396512,99.9999999999866276,94.8692843830354491]},"#ddff55":{"lch":[94.9701440016210654,95.558111661139634,98.8668834316730738],"luv":[94.9701440016210654,-14.7292629682473848,94.4161083536863828],"rgb":[0.866666666666666696,1,0.333333333333333315],"xyz":[0.672160805567498132,0.875476476033675177,0.21951999465839539],"hpluv":[98.8668834316730738,891.031026100052486,94.9701440016210654],"hsluv":[98.8668834316730738,99.9999999999861586,94.9701440016210654]},"#ddff66":{"lch":[95.0981866754888,90.1320341607989235,99.5166683548804798],"luv":[95.0981866754888,-14.9019372161492765,88.8915960547979438],"rgb":[0.866666666666666696,1,0.4],"xyz":[0.679745801341006373,0.878510474343078496,0.259467639065539379],"hpluv":[99.5166683548804798,863.234823568518,95.0981866754888],"hsluv":[99.5166683548804798,99.9999999999856186,95.0981866754888]},"#ddff77":{"lch":[95.2550143462764396,83.6430269583913599,100.407261812829162],"luv":[95.2550143462764396,-15.1095945804084604,82.2669806810598487],"rgb":[0.866666666666666696,1,0.466666666666666674],"xyz":[0.689059814361208223,0.882236079551159347,0.308521440971937233],"hpluv":[100.407261812829162,828.556184265804632,95.2550143462764396],"hsluv":[100.407261812829162,99.9999999999855476,95.2550143462764396]},"#ddff88":{"lch":[95.4420158908659175,76.1281736437493,101.633961649281417],"luv":[95.4420158908659175,-15.3518949618923237,74.5641880758576434],"rgb":[0.866666666666666696,1,0.533333333333333326],"xyz":[0.700200231463152134,0.886692246391936889,0.367194304375509428],"hpluv":[101.633961649281417,786.17501332264635,95.4420158908659175],"hsluv":[101.633961649281417,99.9999999999848512,95.4420158908659175]},"#ddff99":{"lch":[95.6603925662724208,67.6570848719276086,103.355147801887099],"luv":[95.6603925662724208,-15.6278614338241617,65.8274341013866859],"rgb":[0.866666666666666696,1,0.6],"xyz":[0.713257195572224778,0.891915032035566102,0.435960982016627363],"hpluv":[103.355147801887099,735.074270676232231,95.6603925662724208],"hsluv":[103.355147801887099,99.9999999999844107,95.6603925662724208]},"#ddffaa":{"lch":[95.9111754000973775,58.338925144698,105.852499131344544],"luv":[95.9111754000973775,-15.935965587620279,56.1201852084333268],"rgb":[0.866666666666666696,1,0.66666666666666663],"xyz":[0.72831488853747739,0.897938109221667169,0.515264831633625908],"hpluv":[105.852499131344544,673.992295850936557,95.9111754000973775],"hsluv":[105.852499131344544,99.9999999999831886,95.9111754000973775]},"#ddffbb":{"lch":[96.1952377631310185,48.3433070572267596,109.672211431084662],"luv":[96.1952377631310185,-16.2742233848326485,45.5216980180866813],"rgb":[0.866666666666666696,1,0.733333333333333282],"xyz":[0.745452480644814,0.904793146064601927,0.60552281673226771],"hpluv":[109.672211431084662,601.502194015230771,96.1952377631310185],"hsluv":[109.672211431084662,99.9999999999821654,96.1952377631310185]},"#ddffcc":{"lch":[96.513305005727517,37.9644197829535202,115.996292551248089],"luv":[96.513305005727517,-16.6402982513575566,34.1232712904577653],"rgb":[0.866666666666666696,1,0.8],"xyz":[0.764744856088535707,0.912510096242090785,0.707129327402537866],"hpluv":[115.996292551248089,516.693096965009204,96.513305005727517],"hsluv":[115.996292551248089,99.9999999999804885,96.513305005727517]},"#ddffdd":{"lch":[96.8659623148576,27.841508205801528,127.715012949232161],"luv":[96.8659623148576,-17.0316066426751398,22.0243945284064],"rgb":[0.866666666666666696,1,0.866666666666666696],"xyz":[0.786263181639169306,0.921117426462344291,0.820459175302543864],"hpluv":[127.715012949232161,422.676993554754517,96.8659623148576],"hsluv":[127.715012949232161,99.9999999999786411,96.8659623148576]},"#ddffee":{"lch":[97.2536615310726802,19.7831433293950418,151.864226334417424],"luv":[97.2536615310726802,-17.4454209009420502,9.32898974060759478],"rgb":[0.866666666666666696,1,0.933333333333333348],"xyz":[0.810075361905344,0.930642298568814308,0.945869991371067087],"hpluv":[151.864226334417424,343.73229759561508,97.2536615310726802],"hsluv":[151.864226334417424,99.9999999999752,97.2536615310726802]},"#ddffff":{"lch":[97.6767274082888406,18.2904922799610645,192.177050630059568],"luv":[97.6767274082888406,-17.878964623675607,-3.85807359036494368],"rgb":[0.866666666666666696,1,1],"xyz":[0.836246410315175437,0.941110717932747,1.08370417966284882],"hpluv":[192.177050630059568,376.852754928906336,97.6767274082888406],"hsluv":[192.177050630059568,99.9999999999715072,97.6767274082888406]},"#668800":{"lch":[52.32310792684153,62.4331707825390509,105.73052795354684],"luv":[52.32310792684153,-16.9264656143939405,60.0948881001240167],"rgb":[0.4,0.533333333333333326,0],"xyz":[0.142831412088957943,0.204328465339529863,0.0319143663590854554],"hpluv":[105.73052795354684,151.412310196323318,52.32310792684153],"hsluv":[105.73052795354684,100.000000000002359,52.32310792684153]},"#668811":{"lch":[52.3681821172622222,60.6739112189649603,106.201766876928076],"luv":[52.3681821172622222,-16.9292783492562116,58.26425179456308],"rgb":[0.4,0.533333333333333326,0.0666666666666666657],"xyz":[0.143843077588595075,0.204733131539384716,0.0372424713238410851],"hpluv":[106.201766876928076,147.019120365759306,52.3681821172622222],"hsluv":[106.201766876928076,96.7617570127925859,52.3681821172622222]},"#668822":{"lch":[52.4515808002199,57.4815115963739842,107.134347730867304],"luv":[52.4515808002199,-16.9348150675510247,54.9302850350507796],"rgb":[0.4,0.533333333333333326,0.133333333333333331],"xyz":[0.145718435727072076,0.205483274794775544,0.0471193575198202846],"hpluv":[107.134347730867304,139.062145386582984,52.4515808002199],"hsluv":[107.134347730867304,90.8761910280814647,52.4515808002199]},"#668833":{"lch":[52.5884544541714121,52.4173031151438593,108.860637572968898],"luv":[52.5884544541714121,-16.9448041098426323,49.6028958786070291],"rgb":[0.4,0.533333333333333326,0.2],"xyz":[0.14880618645952981,0.206718375087758643,0.0633815113774314209],"hpluv":[108.860637572968898,126.480505252334424,52.5884544541714121],"hsluv":[108.860637572968898,81.504484256448066,52.5884544541714121]},"#668844":{"lch":[52.7851097594501937,45.5231929703366234,111.874794573011059],"luv":[52.7851097594501937,-16.9610118856818559,42.2455343678863215],"rgb":[0.4,0.533333333333333326,0.266666666666666663],"xyz":[0.153264181703748698,0.208501573185446237,0.0868602863303180595],"hpluv":[111.874794573011059,109.436101408621766,52.7851097594501937],"hsluv":[111.874794573011059,68.6312430201703734,52.7851097594501937]},"#668855":{"lch":[53.0463844713544859,37.0894540992544819,117.255878513522262],"luv":[53.0463844713544859,-16.9856764955971045,32.9714179156375593],"rgb":[0.4,0.533333333333333326,0.333333333333333315],"xyz":[0.15922659574311071,0.21088653880119107,0.118262333604291975],"hpluv":[117.255878513522262,88.7225425584692715,53.0463844713544859],"hsluv":[117.255878513522262,52.5261258187504225,53.0463844713544859]},"#668866":{"lch":[53.3759296841588906,27.8248149657310222,127.715012949236794],"luv":[53.3759296841588906,-17.0213948144824521,22.0111891193247189],"rgb":[0.4,0.533333333333333326,0.4],"xyz":[0.166811591516618868,0.213920537110594361,0.158209978011435937],"hpluv":[127.715012949236794,66.1494380276081415,53.3759296841588906],"hsluv":[127.715012949236794,33.681854155652033,53.3759296841588906]},"#668877":{"lch":[53.7763606180623839,19.6211535767711887,150.461713858693599],"luv":[53.7763606180623839,-17.0709226847205855,9.67332757511772101],"rgb":[0.4,0.533333333333333326,0.466666666666666674],"xyz":[0.176125604536820801,0.217646142318675184,0.207263779917833846],"hpluv":[150.461713858693599,46.2990901048939207,53.7763606180623839],"hsluv":[150.461713858693599,37.1484166060608132,53.7763606180623839]},"#668888":{"lch":[54.2493559855519436,17.5313913512660982,192.17705063006045],"luv":[54.2493559855519436,-17.1369431164247104,-3.69795393909545],"rgb":[0.4,0.533333333333333326,0.533333333333333326],"xyz":[0.187266021638764601,0.222102309159452782,0.265936643321406],"hpluv":[192.17705063006045,41.0072951616226788,54.2493559855519436],"hsluv":[192.17705063006045,40.8467805779917228,54.2493559855519436]},"#668899":{"lch":[54.7957384612029728,24.7393499057112685,225.882505108050964],"luv":[54.7957384612029728,-17.2218541013304964,-17.7607200042594577],"rgb":[0.4,0.533333333333333326,0.6],"xyz":[0.200322985747837329,0.22732509480308194,0.33470332096252392],"hpluv":[225.882505108050964,57.2902642666713859,54.7957384612029728],"hsluv":[225.882505108050964,44.6631352998217963,54.7957384612029728]},"#6688aa":{"lch":[55.4155508256813363,36.5699037808867828,241.717344836465486],"luv":[55.4155508256813363,-17.3276119763631087,-32.2042190673817643],"rgb":[0.4,0.533333333333333326,0.66666666666666663],"xyz":[0.215380678713089857,0.233348171989183034,0.414007170579522521],"hpluv":[241.717344836465486,83.7397171719788389,55.4155508256813363],"hsluv":[241.717344836465486,48.4952118884804193,55.4155508256813363]},"#6688bb":{"lch":[56.1081340603271457,49.918102431374308,249.531909378681803],"luv":[56.1081340603271457,-17.4556451944419777,-46.7666270025424495],"rgb":[0.4,0.533333333333333326,0.733333333333333282],"xyz":[0.232518270820426531,0.240203208832117793,0.504265155678164323],"hpluv":[249.531909378681803,112.8941838879785,56.1081340603271457],"hsluv":[249.531909378681803,52.2580147864780216,56.1081340603271457]},"#6688cc":{"lch":[56.8722093096567107,63.7230017864969795,253.960340799970709],"luv":[56.8722093096567107,-17.6068348542363715,-61.2423082770199372],"rgb":[0.4,0.533333333333333326,0.8],"xyz":[0.251810646264148286,0.247920159009606594,0.605871666348434479],"hpluv":[253.960340799970709,142.178999158492672,56.8722093096567107],"hsluv":[253.960340799970709,56.6402695601832349,56.8722093096567107]},"#6688dd":{"lch":[57.7059632125805564,77.5471512008463719,256.744147904563079],"luv":[57.7059632125805564,-17.7815469636593271,-75.4809727477468755],"rgb":[0.4,0.533333333333333326,0.866666666666666696],"xyz":[0.273328971814781774,0.256527489229860128,0.719201514248440477],"hpluv":[256.744147904563079,170.523595036459966,57.7059632125805564],"hsluv":[256.744147904563079,70.7529337108087475,57.7059632125805564]},"#6688ee":{"lch":[58.6071348177704721,91.1720868217648501,258.626369492504523],"luv":[58.6071348177704721,-17.979697586292076,-89.3816529839368599],"rgb":[0.4,0.533333333333333326,0.933333333333333348],"xyz":[0.297141152080956539,0.266052361336330145,0.8446123303169637],"hpluv":[258.626369492504523,197.40162879311913,58.6071348177704721],"hsluv":[258.626369492504523,85.1920367601911295,58.6071348177704721]},"#6688ff":{"lch":[59.57310174908622,104.481663139573541,259.967822360236937],"luv":[59.57310174908622,-18.2008336002305597,-102.884146439906075],"rgb":[0.4,0.533333333333333326,1],"xyz":[0.323312200490787904,0.276520780700262869,0.982446518608745434],"hpluv":[259.967822360236937,222.550815911907222,59.57310174908622],"hsluv":[259.967822360236937,99.9999999999987352,59.57310174908622]},"#669900":{"lch":[57.6618978033021961,71.9113437902946373,111.072092359847389],"luv":[57.6618978033021961,-25.8551729794803826,67.1025438856612624],"rgb":[0.4,0.6,0],"xyz":[0.168701012541425444,0.25606766624446553,0.0405375665099077104],"hpluv":[111.072092359847389,158.251486754186431,57.6618978033021961],"hsluv":[111.072092359847389,100.000000000002444,57.6618978033021961]},"#669911":{"lch":[57.7006802499588929,70.3894808316696867,111.521292839157113],"luv":[57.7006802499588929,-25.8221679479897865,65.4820177928093727],"rgb":[0.4,0.6,0.0666666666666666657],"xyz":[0.169712678041062576,0.256472332444320383,0.0458656714746633401],"hpluv":[111.521292839157113,154.798288730060023,57.7006802499588929],"hsluv":[111.521292839157113,97.4070268725327821,57.7006802499588929]},"#669922":{"lch":[57.7724648019637499,67.6202459496272326,112.394641072548438],"luv":[57.7724648019637499,-25.7622250908158499,62.5204400229094404],"rgb":[0.4,0.6,0.133333333333333331],"xyz":[0.171588036179539577,0.257222475699711184,0.0557425576706425396],"hpluv":[112.394641072548438,148.523500329687067,57.7724648019637499],"hsluv":[112.394641072548438,92.6757293451634183,57.7724648019637499]},"#669933":{"lch":[57.8903535973237524,63.206621217633213,113.958803391015238],"luv":[57.8903535973237524,-25.6669247559924365,57.7605915769530824],"rgb":[0.4,0.6,0.2],"xyz":[0.174675786911997311,0.258457575992694311,0.0720047115282536898],"hpluv":[113.958803391015238,138.546544821103367,57.8903535973237524],"hsluv":[113.958803391015238,85.092275495173979,57.8903535973237524]},"#669944":{"lch":[58.0598969225296457,57.1535921702901888,116.538768682419729],"luv":[58.0598969225296457,-25.5364113217858204,51.131446241744662],"rgb":[0.4,0.6,0.266666666666666663],"xyz":[0.179133782156216198,0.260240774090381877,0.0954834864811403283],"hpluv":[116.538768682419729,124.912700512594284,58.0598969225296457],"hsluv":[116.538768682419729,74.5746503624431796,58.0598969225296457]},"#669955":{"lch":[58.2854489010818355,49.6561447471622515,120.7300937454592],"luv":[58.2854489010818355,-25.3740154630121886,42.6836274282534163],"rgb":[0.4,0.6,0.333333333333333315],"xyz":[0.185096196195578211,0.262625739706126737,0.126885533755114244],"hpluv":[120.7300937454592,108.106592630407334,58.2854489010818355],"hsluv":[120.7300937454592,61.2495338993745833,58.2854489010818355]},"#669966":{"lch":[58.5704165792398754,41.1710358166226,127.715012949238272],"luv":[58.5704165792398754,-25.1857364161826034,32.5688942303571949],"rgb":[0.4,0.6,0.4],"xyz":[0.192681191969086368,0.265659738015530056,0.166833178162258178],"hpluv":[127.715012949238272,89.1975256314243552,58.5704165792398754],"hsluv":[127.715012949238272,45.4174387408809324,58.5704165792398754]},"#669977":{"lch":[58.9173908027988489,32.643461159974926,139.926834832055846],"luv":[58.9173908027988489,-24.9795271581232114,21.0147276798767138],"rgb":[0.4,0.6,0.466666666666666674],"xyz":[0.201995204989288302,0.269385343223610851,0.215886980068656087],"hpluv":[139.926834832055846,70.3059403526018514,58.9173908027988489],"hsluv":[139.926834832055846,47.7876005322921813,58.9173908027988489]},"#669988":{"lch":[59.328227692638464,26.116847909850911,161.480821243886396],"luv":[59.328227692638464,-24.7644493579715288,8.29528738156475498],"rgb":[0.4,0.6,0.533333333333333326],"xyz":[0.213135622091232102,0.273841510064388449,0.274559843472228282],"hpluv":[161.480821243886396,55.8597144892801083,59.328227692638464],"hsluv":[161.480821243886396,50.3655359274122105,59.328227692638464]},"#669999":{"lch":[59.8041090330486043,25.1148951486962346,192.17705063006062],"luv":[59.8041090330486043,-24.5498215694726056,-5.29756729424584893],"rgb":[0.4,0.6,0.6],"xyz":[0.226192586200304829,0.279064295708017607,0.343326521113346161],"hpluv":[192.17705063006062,53.2892577697712042,59.8041090330486043],"hsluv":[192.17705063006062,53.0806679813151447,59.8041090330486043]},"#6699aa":{"lch":[60.3455948386344119,31.1785657150768039,218.6653689057203],"luv":[60.3455948386344119,-24.3444790905776323,-19.4794583563761137],"rgb":[0.4,0.6,0.66666666666666663],"xyz":[0.241250279165557358,0.285087372894118729,0.422630370730344762],"hpluv":[218.6653689057203,65.5616517705923911,60.3455948386344119],"hsluv":[218.6653689057203,55.8649153078387357,60.3455948386344119]},"#6699bb":{"lch":[60.9526745558420231,41.7013653705125407,234.60099737489486],"luv":[60.9526745558420231,-24.1562241025528444,-33.9923625373734168],"rgb":[0.4,0.6,0.733333333333333282],"xyz":[0.258387871272894032,0.291942409737053488,0.512888355828986509],"hpluv":[234.60099737489486,86.8154127469588,60.9526745558420231],"hsluv":[234.60099737489486,58.6571249967150123,60.9526745558420231]},"#6699cc":{"lch":[61.6248198105828493,54.2138016672169485,243.734276496040906],"luv":[61.6248198105828493,-23.9914938552849968,-48.6162988492970101],"rgb":[0.4,0.6,0.8],"xyz":[0.277680246716615731,0.299659359914542289,0.614494866499256664],"hpluv":[243.734276496040906,111.633239234030881,61.6248198105828493],"hsluv":[243.734276496040906,61.4057918386087067,61.6248198105828493]},"#6699dd":{"lch":[62.361039595623,67.5283063616342361,249.312993820438976],"luv":[62.361039595623,-23.8552311607989473,-63.1743627299522288],"rgb":[0.4,0.6,0.866666666666666696],"xyz":[0.29919857226724933,0.308266690134795796,0.727824714399262662],"hpluv":[249.312993820438976,137.407942777337979,62.361039595623],"hsluv":[249.312993820438976,66.62550454031512,62.361039595623]},"#6699ee":{"lch":[63.1599376048740453,81.0888167917790526,252.968345076420633],"luv":[63.1599376048740453,-23.7509145884581336,-77.5325110189427278],"rgb":[0.4,0.6,0.933333333333333348],"xyz":[0.323010752533424039,0.317791562241265813,0.853235530467785885],"hpluv":[252.968345076420633,162.914071535813093,63.1599376048740453],"hsluv":[252.968345076420633,83.0301883133999183,63.1599376048740453]},"#6699ff":{"lch":[64.0197707514621186,94.6074384193794771,255.504450424431923],"luv":[64.0197707514621186,-23.6806962224400728,-91.5958079318982499],"rgb":[0.4,0.6,1],"xyz":[0.34918180094325546,0.328259981605198536,0.99106971875956773],"hpluv":[255.504450424431923,187.521252715437782,64.0197707514621186],"hsluv":[255.504450424431923,99.9999999999985079,64.0197707514621186]},"#550000":{"lch":[15.1243819173422267,50.8637728648741643,12.1770506300617765],"luv":[15.1243819173422267,49.7193613905117289,10.7288626130266547],"rgb":[0.333333333333333315,0,0],"xyz":[0.0374622858816120868,0.019316491157706641,0.00175604465070052949],"hpluv":[12.1770506300617765,426.746789183125202,15.1243819173422267],"hsluv":[12.1770506300617765,100.000000000002203,15.1243819173422267]},"#550011":{"lch":[15.3402258633588957,47.2707050856887108,7.4875089370669734],"luv":[15.3402258633588957,46.8676416739534929,6.15984766208174239],"rgb":[0.333333333333333315,0,0.0666666666666666657],"xyz":[0.0384739513812492051,0.0197211573575614939,0.00708414961545616138],"hpluv":[7.4875089370669734,391.020613457768548,15.3402258633588957],"hsluv":[7.4875089370669734,99.9999999999966889,15.3402258633588957]},"#550022":{"lch":[15.7326592199860933,42.4312907985821823,358.411234527054887],"luv":[15.7326592199860933,42.4149789665186745,-1.17643448760390168],"rgb":[0.333333333333333315,0,0.133333333333333331],"xyz":[0.0403493095197262272,0.0204713006129523117,0.0169610358114353557],"hpluv":[358.411234527054887,342.234221563623748,15.7326592199860933],"hsluv":[358.411234527054887,99.9999999999971578,15.7326592199860933]},"#550033":{"lch":[16.358416636328208,38.360101220613565,343.406058671947278],"luv":[16.358416636328208,36.7625096026365128,-10.9551473459637485],"rgb":[0.333333333333333315,0,0.2],"xyz":[0.0434370602521839677,0.0217064009059354281,0.0332231896690465],"hpluv":[343.406058671947278,297.562230749163234,16.358416636328208],"hsluv":[343.406058671947278,99.9999999999977547,16.358416636328208]},"#550044":{"lch":[17.2212923868602061,37.8614359764106112,324.728975934647224],"luv":[17.2212923868602061,30.9112016871952413,-21.8628896637516235],"rgb":[0.333333333333333315,0,0.266666666666666663],"xyz":[0.0478950554964028483,0.0234895990036230046,0.0567019646219331375],"hpluv":[324.728975934647224,278.978456842737614,17.2212923868602061],"hsluv":[324.728975934647224,99.9999999999983089,17.2212923868602061]},"#550055":{"lch":[18.3096014215038,41.7063030886972754,307.715012949243544],"luv":[18.3096014215038,25.5131777875110508,-32.9923245090298636],"rgb":[0.333333333333333315,0,0.333333333333333315],"xyz":[0.0538574695357648403,0.025874564619367834,0.0881040118959070528],"hpluv":[307.715012949243544,289.042783730483393,18.3096014215038],"hsluv":[307.715012949243544,99.9999999999988,18.3096014215038]},"#550066":{"lch":[19.6013792550641099,48.6148100748190828,295.355623011865077],"luv":[19.6013792550641099,20.818580180871372,-43.9316113733990079],"rgb":[0.333333333333333315,0,0.4],"xyz":[0.0614424653092730116,0.028908562928771149,0.128051656303051015],"hpluv":[295.355623011865077,314.717786655224245,19.6013792550641099],"hsluv":[295.355623011865077,99.9999999999992468,19.6013792550641099]},"#550077":{"lch":[21.069395574911745,57.1127554515679421,287.139622223683091],"luv":[21.069395574911745,16.83119884103942,-54.5763463493480572],"rgb":[0.333333333333333315,0,0.466666666666666674],"xyz":[0.0707564783294749311,0.0326341681368519654,0.177105458209448924],"hpluv":[287.139622223683091,343.969838941793114,21.069395574911745],"hsluv":[287.139622223683091,99.9999999999995737,21.069395574911745]},"#550088":{"lch":[22.6852054601189934,66.3294491167530822,281.703433904835379],"luv":[22.6852054601189934,13.454662288017035,-64.9505033302079084],"rgb":[0.333333333333333315,0,0.533333333333333326],"xyz":[0.0818968954314187592,0.0370903349776295563,0.235778321613021091],"hpluv":[281.703433904835379,371.024851449370942,22.6852054601189934],"hsluv":[281.703433904835379,99.9999999999998721,22.6852054601189934]},"#550099":{"lch":[24.4218644362266417,75.8503102235033424,278.01254475278904],"luv":[24.4218644362266417,10.5727682252252198,-75.1098271403773339],"rgb":[0.333333333333333315,0,0.6],"xyz":[0.0949538595404914726,0.0423131206212587208,0.304544999254138971],"hpluv":[278.01254475278904,394.110378481836165,24.4218644362266417],"hsluv":[278.01254475278904,100.000000000000071,24.4218644362266417]},"#5500aa":{"lch":[26.2553935553790794,85.4876195151354,275.424483319872081],"luv":[26.2553935553790794,8.0814629277942,-85.1047768771603614],"rgb":[0.333333333333333315,0,0.66666666666666663],"xyz":[0.110011552505744015,0.0483361978073598153,0.383848848871137571],"hpluv":[275.424483319872081,413.165469396605374,26.2553935553790794],"hsluv":[275.424483319872081,100.00000000000027,26.2553935553790794]},"#5500bb":{"lch":[28.1653177219846,95.1546470205467756,273.553022331801344],"luv":[28.1653177219846,5.89694295360029841,-94.9717479748942708],"rgb":[0.333333333333333315,0,0.733333333333333282],"xyz":[0.127149144613080661,0.0551912346502945739,0.474106833969779318],"hpluv":[273.553022331801344,428.701175528050442,28.1653177219846],"hsluv":[273.553022331801344,100.000000000000355,28.1653177219846]},"#5500cc":{"lch":[30.1346298593711452,104.80902699908826,272.162345307959299],"luv":[30.1346298593711452,3.95455850560158062,-104.734395532324456],"rgb":[0.333333333333333315,0,0.8],"xyz":[0.146441520056802388,0.0629081848277833755,0.575713344640049529],"hpluv":[272.162345307959299,441.338845171454864,30.1346298593711452],"hsluv":[272.162345307959299,100.000000000000441,30.1346298593711452]},"#5500dd":{"lch":[32.1494591091083208,114.428501102308275,271.104225820707256],"luv":[32.1494591091083208,2.20517261265448461,-114.407250986418532],"rgb":[0.333333333333333315,0,0.866666666666666696],"xyz":[0.167959845607435931,0.0715155150480369095,0.689043192540055527],"hpluv":[271.104225820707256,451.647764573950099,32.1494591091083208],"hsluv":[271.104225820707256,100.000000000000483,32.1494591091083208]},"#5500ee":{"lch":[34.1986254005705774,124.000502171291956,270.282536199645165],"luv":[34.1986254005705774,0.611467178704377612,-123.998994538754019],"rgb":[0.333333333333333315,0,0.933333333333333348],"xyz":[0.191772025873610696,0.0810403871545069404,0.81445400860857875],"hpluv":[270.282536199645165,460.101999214721616,34.1986254005705774],"hsluv":[270.282536199645165,100.000000000000597,34.1986254005705774]},"#5500ff":{"lch":[36.2731838611955055,133.517545782829444,269.6330586770423],"luv":[36.2731838611955055,-0.855085145745556,-133.514807647929],"rgb":[0.333333333333333315,0,1],"xyz":[0.217943074283442062,0.0915088065184396504,0.952288196900360595],"hpluv":[269.6330586770423,467.080772865482345,36.2731838611955055],"hsluv":[269.6330586770423,100.000000000000668,36.2731838611955055]},"#551100":{"lch":[17.1436512350983392,46.6418802884309827,16.9386517648024579],"luv":[17.1436512350983392,44.618427652544149,13.5889996193616174],"rgb":[0.333333333333333315,0.0666666666666666657,0],"xyz":[0.0394666861425404941,0.0233252916795635146,0.00242417807100998037],"hpluv":[16.9386517648024579,345.232802292268,17.1436512350983392],"hsluv":[16.9386517648024579,100.000000000002245,17.1436512350983392]},"#551111":{"lch":[17.3342210988239742,43.3325537190722372,12.1770506300618173],"luv":[17.3342210988239742,42.3575912084996133,9.14027783111198566],"rgb":[0.333333333333333315,0.0666666666666666657,0.0666666666666666657],"xyz":[0.0404783516421776124,0.0237299578794183674,0.0077522830357656114],"hpluv":[12.1770506300618173,317.211759513802576,17.3342210988239742],"hsluv":[12.1770506300618173,74.3325474389658751,17.3342210988239742]},"#551122":{"lch":[17.681833534927847,38.7809423842287515,2.75476418742331486],"luv":[17.681833534927847,38.7361268795629599,1.86385798259144919],"rgb":[0.333333333333333315,0.0666666666666666657,0.133333333333333331],"xyz":[0.0423537097806546345,0.0244801011348091888,0.0176291692317448075],"hpluv":[2.75476418742331486,278.311009894887945,17.681833534927847],"hsluv":[2.75476418742331486,76.7057995287142234,17.681833534927847]},"#551133":{"lch":[18.2390179286851222,34.9181665436168771,346.660743506282301],"luv":[18.2390179286851222,33.9761101882180157,-8.05619582966011727],"rgb":[0.333333333333333315,0.0666666666666666657,0.2],"xyz":[0.045441460513112375,0.0257152014277923,0.0338913230893559542],"hpluv":[346.660743506282301,242.9345634355779,18.2390179286851222],"hsluv":[346.660743506282301,79.7834922439867285,18.2390179286851222]},"#551144":{"lch":[19.0128230091186055,34.7408929356053093,326.164658676814156],"luv":[19.0128230091186055,28.8572161490225767,-19.3440098762325228],"rgb":[0.333333333333333315,0.0666666666666666657,0.266666666666666663],"xyz":[0.0498994557573312555,0.0274983995254798746,0.0573700980422425927],"hpluv":[326.164658676814156,231.864199750183133,19.0128230091186055],"hsluv":[326.164658676814156,83.0221027238197706,19.0128230091186055]},"#551155":{"lch":[19.9971255718025702,39.1813209297373,307.715012949243771],"luv":[19.9971255718025702,23.9685594933690105,-30.9949038651824331],"rgb":[0.333333333333333315,0.0666666666666666657,0.333333333333333315],"xyz":[0.0558618697966932476,0.0298833651412247076,0.0887721453162165],"hpluv":[307.715012949243771,248.628452083429778,19.9971255718025702],"hsluv":[307.715012949243771,86.0178721207098391,19.9971255718025702]},"#551166":{"lch":[21.1763147828962417,46.7756191769153702,294.771362117319313],"luv":[21.1763147828962417,19.5989049109657927,-42.4716549674572263],"rgb":[0.333333333333333315,0.0666666666666666657,0.4],"xyz":[0.0634468655702014189,0.0329173634506280191,0.128719789723360456],"hpluv":[294.771362117319313,280.29057047770084,21.1763147828962417],"hsluv":[294.771362117319313,88.5810746573468464,21.1763147828962417]},"#551177":{"lch":[22.529041607596703,55.8726050260016436,286.442196236272423],"luv":[22.529041607596703,15.8146223861076685,-53.5877384405836494],"rgb":[0.333333333333333315,0.0666666666666666657,0.466666666666666674],"xyz":[0.0727608785904033384,0.0366429686587088355,0.177773591629758365],"hpluv":[286.442196236272423,314.699121073082495,22.529041607596703],"hsluv":[286.442196236272423,90.6792509168865166,22.529041607596703]},"#551188":{"lch":[24.0315326783493077,65.5523157673088548,281.05474383939486],"luv":[24.0315326783493077,12.5694476912001676,-64.3359548557037613],"rgb":[0.333333333333333315,0.0666666666666666657,0.533333333333333326],"xyz":[0.0839012956923471664,0.0410991354994864333,0.236446455033330533],"hpluv":[281.05474383939486,346.135302225987516,24.0315326783493077],"hsluv":[281.05474383939486,92.3586421581981654,24.0315326783493077]},"#551199":{"lch":[25.6600874124784752,75.409923320082811,277.450872837297311],"luv":[25.6600874124784752,9.77886091464338847,-74.7731931533810439],"rgb":[0.333333333333333315,0.0666666666666666657,0.6],"xyz":[0.0969582598014198799,0.0463219211431155908,0.30521313267444844],"hpluv":[277.450872837297311,372.914863977489176,25.6600874124784752],"hsluv":[277.450872837297311,93.6909530677945099,25.6600874124784752]},"#5511aa":{"lch":[27.3926712394503795,85.2829889480110097,274.948569823839534],"luv":[27.3926712394503795,7.3566383451248436,-84.9650991652784882],"rgb":[0.333333333333333315,0.0666666666666666657,0.66666666666666663],"xyz":[0.112015952766672422,0.0523449983292166923,0.38451698229144704],"hpluv":[274.948569823839534,395.063906765864772,27.3926712394503795],"hsluv":[274.948569823839534,94.7471527755802185,27.3926712394503795]},"#5511bb":{"lch":[29.2097366740877575,95.1102004757886732,273.151254165932869],"luv":[29.2097366740877575,5.22840137893499524,-94.9663838079847125],"rgb":[0.333333333333333315,0.0666666666666666657,0.733333333333333282],"xyz":[0.129153544874009069,0.0592000351721514509,0.474774967390088787],"hpluv":[273.151254165932869,413.179515249173164,29.2097366740877575],"hsluv":[273.151254165932869,95.5878244802283,29.2097366740877575]},"#5511cc":{"lch":[31.0944914716528729,104.870266468627406,271.822015936655589],"luv":[31.0944914716528729,3.33433100744475697,-104.81724584215],"rgb":[0.333333333333333315,0.0666666666666666657,0.8],"xyz":[0.148445920317730795,0.0669169853496402456,0.576381478060359],"hpluv":[271.822015936655589,427.964986778194486,31.0944914716528729],"hsluv":[271.822015936655589,96.2613872132177733,31.0944914716528729]},"#5511dd":{"lch":[33.0328257175950526,114.557035031638449,270.814150293263936],"luv":[33.0328257175950526,1.62775523553093748,-114.545469959022128],"rgb":[0.333333333333333315,0.0666666666666666657,0.866666666666666696],"xyz":[0.169964245868364339,0.0755243155698937796,0.689711325960365],"hpluv":[270.814150293263936,440.063516862193467,33.0328257175950526],"hsluv":[270.814150293263936,96.8053522828386832,33.0328257175950526]},"#5511ee":{"lch":[35.0130604571318926,124.169729525043778,270.033521846843314],"luv":[35.0130604571318926,0.0726475571782520396,-124.169708273213573],"rgb":[0.333333333333333315,0.0666666666666666657,0.933333333333333348],"xyz":[0.193776426134539104,0.0850491876763638244,0.815122142028888219],"hpluv":[270.033521846843314,450.012925469310176,35.0130604571318926],"hsluv":[270.033521846843314,97.2483877085506663,35.0130604571318926]},"#5511ff":{"lch":[37.0256255288684244,133.709282746677673,269.417732433602225],"luv":[37.0256255288684244,-1.35879534673770408,-133.702378317802101],"rgb":[0.333333333333333315,0.0666666666666666657,1],"xyz":[0.219947474544370469,0.0955176070402965205,0.952956330320670064],"hpluv":[269.417732433602225,458.245787382607887,37.0256255288684244],"hsluv":[269.417732433602225,99.999999999999531,37.0256255288684244]},"#552200":{"lch":[20.34436993371488,40.5799496107340403,26.5709502396200712],"luv":[20.34436993371488,36.2939416756913289,18.1516420207988389],"rgb":[0.333333333333333315,0.133333333333333331,0],"xyz":[0.0431823098773084293,0.0307565391490994891,0.0036627193159325909],"hpluv":[26.5709502396200712,253.10841584108,20.34436993371488],"hsluv":[26.5709502396200712,100.000000000002359,20.34436993371488]},"#552211":{"lch":[20.5030711832139332,37.4561676233306144,21.8926823519782197],"luv":[20.5030711832139332,34.7549742653004543,13.9662542166947503],"rgb":[0.333333333333333315,0.133333333333333331,0.0666666666666666657],"xyz":[0.0441939753769455546,0.031161205348954342,0.00899082428068822236],"hpluv":[21.8926823519782197,231.816181029165051,20.5030711832139332],"hsluv":[21.8926823519782197,79.998991439989,20.5030711832139332]},"#552222":{"lch":[20.7936643332181177,32.9512801850020054,12.1770506300619488],"luv":[20.7936643332181177,32.2098915499349658,6.95052171940595098],"rgb":[0.333333333333333315,0.133333333333333331,0.133333333333333331],"xyz":[0.0460693335154225697,0.0319113486043451633,0.0188677104766674167],"hpluv":[12.1770506300619488,201.08542320769223,20.7936643332181177],"hsluv":[12.1770506300619488,47.1205474310924046,20.7936643332181177]},"#552233":{"lch":[21.2623572347893699,28.8586800011052311,354.1745907436],"luv":[21.2623572347893699,28.7096478626609084,-2.92908364650122532],"rgb":[0.333333333333333315,0.133333333333333331,0.2],"xyz":[0.0491570842478803102,0.0331464488973282762,0.0351298643342785599],"hpluv":[354.1745907436,172.228245917758017,21.2623572347893699],"hsluv":[354.1745907436,52.8325366869496236,21.2623572347893699]},"#552244":{"lch":[21.9189283311679688,28.7014290952845421,329.54904741067952],"luv":[21.9189283311679688,24.7424491748557642,-14.5459011732283336],"rgb":[0.333333333333333315,0.133333333333333331,0.266666666666666663],"xyz":[0.0536150794920991908,0.0349296469950158492,0.0586086392871652],"hpluv":[329.54904741067952,166.158870546468904,21.9189283311679688],"hsluv":[329.54904741067952,59.1960886419386,21.9189283311679688]},"#552255":{"lch":[22.7630226511172538,33.9275210993755394,307.715012949244226],"luv":[22.7630226511172538,20.7546297224435,-26.8388157904337739],"rgb":[0.333333333333333315,0.133333333333333331,0.333333333333333315],"xyz":[0.0595774935314611828,0.0373146126107606821,0.0900106865611391138],"hpluv":[307.715012949244226,189.13048019699076,22.7630226511172538],"hsluv":[307.715012949244226,65.4333859354686211,22.7630226511172538]},"#552266":{"lch":[23.7863579144178132,42.6539605518829816,293.531429927677038],"luv":[23.7863579144178132,17.0296819355444384,-39.1069083914310625],"rgb":[0.333333333333333315,0.133333333333333331,0.4],"xyz":[0.0671624893049693611,0.0403486109201639936,0.129958330968283076],"hpluv":[293.531429927677038,227.546804006344104,23.7863579144178132],"hsluv":[293.531429927677038,71.0608597854246,23.7863579144178132]},"#552277":{"lch":[24.975052770659552,52.81712200152176,285.022920758889427],"luv":[24.975052770659552,13.6904851349353347,-51.0119495147337858],"rgb":[0.333333333333333315,0.133333333333333331,0.466666666666666674],"xyz":[0.0764765023251712805,0.04407421612824481,0.179012132874680985],"hpluv":[285.022920758889427,268.353735360872861,24.975052770659552],"hsluv":[285.022920758889427,75.8822745115455604,24.975052770659552]},"#552288":{"lch":[26.3119033569515395,63.3751525334233818,279.769698022236867],"luv":[26.3119033569515395,10.754023379801918,-62.4560720809542573],"rgb":[0.333333333333333315,0.133333333333333331,0.533333333333333326],"xyz":[0.0876169194271151086,0.0485303829690224,0.237684996278253152],"hpluv":[279.769698022236867,305.637106002952862,26.3119033569515395],"hsluv":[279.769698022236867,79.8897505498065357,26.3119033569515395]},"#552299":{"lch":[27.7783456471686065,73.9162973481211,276.357347681517297],"luv":[27.7783456471686065,8.18468635974612368,-73.4617582341209783],"rgb":[0.333333333333333315,0.133333333333333331,0.6],"xyz":[0.100673883536187808,0.0537531686126515654,0.306451673919371059],"hpluv":[276.357347681517297,337.654974874305083,27.7783456471686065],"hsluv":[276.357347681517297,83.1678045973750102,27.7783456471686065]},"#5522aa":{"lch":[29.3559430420228864,84.3037876124251824,274.032676149589577],"luv":[29.3559430420228864,5.92869585954675404,-84.0950603258353482],"rgb":[0.333333333333333315,0.133333333333333331,0.66666666666666663],"xyz":[0.115731576501440364,0.0597762457987526669,0.38575552353636966],"hpluv":[274.032676149589577,364.410076381253305,29.3559430420228864],"hsluv":[274.032676149589577,85.8312635149374898,29.3559430420228864]},"#5522bb":{"lch":[31.0273723986379082,94.5090555446981568,272.384235640696716],"luv":[31.0273723986379082,3.93164792491766635,-94.4272403734501324],"rgb":[0.333333333333333315,0.133333333333333331,0.733333333333333282],"xyz":[0.132869168608777,0.0666312826416874254,0.476013508635011406],"hpluv":[272.384235640696716,386.516244750379769,31.0273723986379082],"hsluv":[272.384235640696716,87.9935415010939437,31.0273723986379082]},"#5522cc":{"lch":[32.7769760620793207,104.54175742802569,271.176024316906762],"luv":[32.7769760620793207,2.1456208028374788,-104.519736688869415],"rgb":[0.333333333333333315,0.133333333333333331,0.8],"xyz":[0.152161544052498737,0.0743482328191762271,0.577620019305281507],"hpluv":[271.176024316906762,404.725193356887132,32.7769760620793207],"hsluv":[271.176024316906762,89.7538092894248507,32.7769760620793207]},"#5522dd":{"lch":[34.5909880118612847,114.421842397276407,270.265889656874094],"luv":[34.5909880118612847,0.530989812409553341,-114.420610326139567],"rgb":[0.333333333333333315,0.133333333333333331,0.866666666666666696],"xyz":[0.173679869603132281,0.0829555630394297611,0.690949867205287505],"hpluv":[270.265889656874094,419.744772471420788,34.5909880118612847],"hsluv":[270.265889656874094,91.1938440016712519,34.5909880118612847]},"#5522ee":{"lch":[36.4575428526747132,124.16924767107497,269.564389707514863],"luv":[36.4575428526747132,-0.94402906580191559,-124.165659005715739],"rgb":[0.333333333333333315,0.133333333333333331,0.933333333333333348],"xyz":[0.197492049869307018,0.092480435145899792,0.816360683273810728],"hpluv":[269.564389707514863,432.181309790662738,36.4575428526747132],"hsluv":[269.564389707514863,92.960018101107309,36.4575428526747132]},"#5522ff":{"lch":[38.3665568136218695,133.800754994484379,269.013084090219763],"luv":[38.3665568136218695,-2.30459494965050871,-133.780906258001124],"rgb":[0.333333333333333315,0.133333333333333331,1],"xyz":[0.223663098279138411,0.102948854509832488,0.954194871565592573],"hpluv":[269.013084090219763,442.532391911887146,38.3665568136218695],"hsluv":[269.013084090219763,99.9999999999994458,38.3665568136218695]},"#ccaa00":{"lch":[70.5858735612972623,80.4904122142546186,67.9906634155396],"luv":[70.5858735612972623,30.1643998932495876,74.6244962294604335],"rgb":[0.8,0.66666666666666663,0],"xyz":[0.392753797737474875,0.415879162748823417,0.059586129772359088],"hpluv":[67.9906634155396,144.699051457387782,70.5858735612972623],"hsluv":[67.9906634155396,100.000000000002373,70.5858735612972623]},"#ccaa11":{"lch":[70.6139482370970342,79.3332745728624786,67.7767588069511078],"luv":[70.6139482370970342,30.0051392857341135,73.4401802210267078],"rgb":[0.8,0.66666666666666663,0.0666666666666666657],"xyz":[0.393765463237112,0.416283828948678269,0.0649142347371147177],"hpluv":[67.7767588069511078,142.56214209062793,70.6139482370970342],"hsluv":[67.7767588069511078,98.4234445622979308,70.6139482370970342]},"#ccaa22":{"lch":[70.6659431154106,77.2098224605726529,67.3668792611096166],"luv":[70.6659431154106,29.7125737342128318,71.263733037795],"rgb":[0.8,0.66666666666666663,0.133333333333333331],"xyz":[0.395640821375589036,0.41703397220406907,0.0747911209330939103],"hpluv":[67.3668792611096166,138.644204697820271,70.6659431154106],"hsluv":[67.3668792611096166,95.5289103121581746,70.6659431154106]},"#ccaa33":{"lch":[70.7514162745237911,73.7740913884545,66.6515854645136159],"luv":[70.7514162745237911,29.2382542862800783,67.7328653349528622],"rgb":[0.8,0.66666666666666663,0.2],"xyz":[0.398728572108046742,0.418269072497052197,0.0910532747907050605],"hpluv":[66.6515854645136159,132.314688307412609,70.7514162745237911],"hsluv":[66.6515854645136159,90.8407422295077822,70.7514162745237911]},"#ccaa44":{"lch":[70.8745233475107597,68.9449101039795096,65.5199261400916271],"luv":[70.8745233475107597,28.5691680681362534,62.74713750555],"rgb":[0.8,0.66666666666666663,0.266666666666666663],"xyz":[0.403186567352265657,0.420052270594739763,0.114532049743591699],"hpluv":[65.5199261400916271,123.438713002021714,70.8745233475107597],"hsluv":[65.5199261400916271,84.237349466135143,70.8745233475107597]},"#ccaa55":{"lch":[71.0386313772099,62.7256766939065713,63.7915144306433959],"luv":[71.0386313772099,27.7020883768036121,56.2770363138665672],"rgb":[0.8,0.66666666666666663,0.333333333333333315],"xyz":[0.409148981391627642,0.422437236210484623,0.1459340970175656],"hpluv":[63.7915144306433959,112.044384698186093,71.0386313772099],"hsluv":[63.7915144306433959,75.6976413431368229,71.0386313772099]},"#ccaa66":{"lch":[71.2465086991263661,55.2096541299921952,61.1466680322388356],"luv":[71.2465086991263661,26.6424757380098143,48.3558103613508123],"rgb":[0.8,0.66666666666666663,0.4],"xyz":[0.416733977165135827,0.425471234519887942,0.185881741424709562],"hpluv":[61.1466680322388356,98.331070119416168,71.2465086991263661],"hsluv":[61.1466680322388356,65.2901205751262,71.2465086991263661]},"#ccaa77":{"lch":[71.5004247203994794,46.6032511345785068,56.9687004258729388],"luv":[71.5004247203994794,25.4032971934547263,39.0709035986321638],"rgb":[0.8,0.66666666666666663,0.466666666666666674],"xyz":[0.426047990185337733,0.429196839727968737,0.234935543331107471],"hpluv":[56.9687004258729388,82.707885898124573,71.5004247203994794],"hsluv":[56.9687004258729388,53.160756563752912,71.5004247203994794]},"#ccaa88":{"lch":[71.8022091544958556,37.3024926838847648,49.9478623796008847],"luv":[71.8022091544958556,24.003572993357885,28.5535364531929083],"rgb":[0.8,0.66666666666666663,0.533333333333333326],"xyz":[0.437188407287281533,0.433653006568746335,0.293608406734679639],"hpluv":[49.9478623796008847,65.9233659814751149,71.8022091544958556],"hsluv":[49.9478623796008847,39.5178130280575743,71.8022091544958556]},"#ccaa99":{"lch":[72.1532912119235874,28.1535194843350283,37.0598425499876214],"luv":[72.1532912119235874,22.4666918036374241,16.9666855559712388],"rgb":[0.8,0.66666666666666663,0.6],"xyz":[0.450245371396354233,0.438875792212375493,0.362375084375797574],"hpluv":[37.0598425499876214,49.5126161932360702,72.1532912119235874],"hsluv":[37.0598425499876214,26.6696495224772221,72.1532912119235874]},"#ccaaaa":{"lch":[72.5547286434336,21.297823518763618,12.177050630063027],"luv":[72.5547286434336,20.8186322940273918,4.49241984263274841],"rgb":[0.8,0.66666666666666663,0.66666666666666663],"xyz":[0.465303064361606789,0.444898869398476615,0.441678933992796174],"hpluv":[12.177050630063027,37.2485034287350203,72.5547286434336],"hsluv":[12.177050630063027,27.9047031904792959,72.5547286434336]},"#ccaabb":{"lch":[73.0072318845295,20.9674028584132515,335.544386587188285],"luv":[73.0072318845295,19.0862548050106788,-8.68025691700441193],"rgb":[0.8,0.66666666666666663,0.733333333333333282],"xyz":[0.482440656468943463,0.451753906241411374,0.531936919091437921],"hpluv":[335.544386587188285,36.4433325399841053,73.0072318845295],"hsluv":[335.544386587188285,29.1493731958161852,73.0072318845295]},"#ccaacc":{"lch":[73.5111862218870442,28.2733952257813925,307.715012949249683],"luv":[73.5111862218870442,17.2958067637387,-22.366044486780595],"rgb":[0.8,0.66666666666666663,0.8],"xyz":[0.501733031912665162,0.459470856418900175,0.633543429761708077],"hpluv":[307.715012949249683,48.8049486057506,73.5111862218870442],"hsluv":[307.715012949249683,30.3693282248248444,73.5111862218870442]},"#ccaadd":{"lch":[74.0666736076556,39.5441054640130645,293.032470315564865],"luv":[74.0666736076556,15.4717391639018693,-36.3917787995197202],"rgb":[0.8,0.66666666666666663,0.866666666666666696],"xyz":[0.523251357463298761,0.468078186639153682,0.746873277661714075],"hpluv":[293.032470315564865,67.7482749884916871,74.0666736076556],"hsluv":[293.032470315564865,52.1368854824616719,74.0666736076556]},"#ccaaee":{"lch":[74.6734949675833093,52.4074038625381462,285.081329657377239],"luv":[74.6734949675833093,13.6358764846886817,-50.6023601436286],"rgb":[0.8,0.66666666666666663,0.933333333333333348],"xyz":[0.54706353772947347,0.477603058745623699,0.872284093730237298],"hpluv":[285.081329657377239,89.0564736794959799,74.6734949675833093],"hsluv":[285.081329657377239,75.3315532053798194,74.6734949675833093]},"#ccaaff":{"lch":[75.3311933526529316,65.9300530396585458,280.316334998223624],"luv":[75.3311933526529316,11.8069327652914797,-64.8642292214135239],"rgb":[0.8,0.66666666666666663,1],"xyz":[0.573234586139304891,0.488071478109556423,1.01011828202201914],"hpluv":[280.316334998223624,111.057502918871393,75.3311933526529316],"hsluv":[280.316334998223624,99.9999999999973426,75.3311933526529316]},"#553300":{"lch":[24.6368918170402651,35.1311640480653367,43.6144672720514848],"luv":[24.6368918170402651,25.4348822629288698,24.2335604409056025],"rgb":[0.333333333333333315,0.2,0],"xyz":[0.049300031966319241,0.0429919833271212859,0.00570196001226947087],"hpluv":[43.6144672720514848,180.944734702515461,24.6368918170402651],"hsluv":[43.6144672720514848,100.000000000002245,24.6368918170402651]},"#553311":{"lch":[24.7639934196671305,31.9442274162558917,39.8156129865950632],"luv":[24.7639934196671305,24.536650698978292,20.4544967598277161],"rgb":[0.333333333333333315,0.2,0.0666666666666666657],"xyz":[0.0503116974659563593,0.0433966495269761388,0.0110300649770251023],"hpluv":[39.8156129865950632,163.685811409294416,24.7639934196671305],"hsluv":[39.8156129865950632,85.3309596574143256,24.7639934196671305]},"#553322":{"lch":[24.9975315322943885,26.9096454002331456,31.2519949010175395],"luv":[24.9975315322943885,23.0048892205904032,13.9608054035092266],"rgb":[0.333333333333333315,0.2,0.133333333333333331],"xyz":[0.0521870556044333814,0.0441467927823669601,0.0209069511730043],"hpluv":[31.2519949010175395,136.59983556614165,24.9975315322943885],"hsluv":[31.2519949010175395,60.4417762068684823,24.9975315322943885]},"#553333":{"lch":[25.3763514371309924,21.2787516643900716,12.1770506300621495],"luv":[25.3763514371309924,20.7999895475976082,4.48839695377019332],"rgb":[0.333333333333333315,0.2,0.2],"xyz":[0.0552748063368911219,0.0453818930753500729,0.0371691050306154416],"hpluv":[12.1770506300621495,106.403592780468983,25.3763514371309924],"hsluv":[12.1770506300621495,24.9336598370546909,25.3763514371309924]},"#553344":{"lch":[25.9113402150992655,19.4972565515274532,338.627269772390264],"luv":[25.9113402150992655,18.1564179844864455,-7.10545558065754257],"rgb":[0.333333333333333315,0.2,0.266666666666666663],"xyz":[0.05973280158111,0.047165091173037646,0.0606478799835020801],"hpluv":[338.627269772390264,95.4823185470749536,25.9113402150992655],"hsluv":[338.627269772390264,32.9723178491547841,25.9113402150992655]},"#553355":{"lch":[26.6061908173450519,25.0719265662328183,307.715012949245363],"luv":[26.6061908173450519,15.3373584467403763,-19.8334802195371829],"rgb":[0.333333333333333315,0.2,0.333333333333333315],"xyz":[0.065695215620472,0.0495500567887824789,0.0920499272574759886],"hpluv":[307.715012949245363,119.576085528419512,26.6061908173450519],"hsluv":[307.715012949245363,41.369683748934996,26.6061908173450519]},"#553366":{"lch":[27.4586282592714284,35.2024776289392847,290.893042573756475],"luv":[27.4586282592714284,12.5540679631371699,-32.8878367910220533],"rgb":[0.333333333333333315,0.2,0.4],"xyz":[0.0732802113939801658,0.0525840550981857904,0.13199757166461995],"hpluv":[290.893042573756475,162.679833835368925,27.4586282592714284],"hsluv":[290.893042573756475,49.429407074258,27.4586282592714284]},"#553377":{"lch":[28.461655060413058,46.8206771520520633,282.253113271302652],"luv":[28.461655060413058,9.93678843358592445,-45.7540822725452898],"rgb":[0.333333333333333315,0.2,0.466666666666666674],"xyz":[0.0825942244141820853,0.0563096603062666068,0.18105137357101786],"hpluv":[282.253113271302652,208.74537696470955,28.461655060413058],"hsluv":[282.253113271302652,56.732994513665389,28.461655060413058]},"#553388":{"lch":[29.6048600369324433,58.6666973974172876,277.388246485053742],"luv":[29.6048600369324433,7.54407762538358551,-58.1796207988872567],"rgb":[0.333333333333333315,0.2,0.533333333333333326],"xyz":[0.0937346415161259133,0.0607658271470442046,0.239724236974590027],"hpluv":[277.388246485053742,251.459446283522851,29.6048600369324433],"hsluv":[277.388246485053742,63.1061783000114715,29.6048600369324433]},"#553399":{"lch":[30.8756880539778678,70.2946203588512759,274.394660626066695],"luv":[30.8756880539778678,5.38640350826703,-70.0879469569565714],"rgb":[0.333333333333333315,0.2,0.6],"xyz":[0.106791605625198627,0.0659886127906733622,0.308490914615707934],"hpluv":[274.394660626066695,288.898157334443908,30.8756880539778678],"hsluv":[274.394660626066695,68.537799314251,30.8756880539778678]},"#5533aa":{"lch":[32.2605562861205,81.571224268378316,272.422737250332261],"luv":[32.2605562861205,3.44819039224039026,-81.498310483475251],"rgb":[0.333333333333333315,0.2,0.66666666666666663],"xyz":[0.121849298590451169,0.0720116899767744567,0.387794764232706535],"hpluv":[272.422737250332261,320.851781583216223,32.2605562861205],"hsluv":[272.422737250332261,73.1042224024611897,32.2605562861205]},"#5533bb":{"lch":[33.7457437232739395,92.4864953275551755,271.055064965734516],"luv":[33.7457437232739395,1.70298313903478205,-92.4708152143261231],"rgb":[0.333333333333333315,0.2,0.733333333333333282],"xyz":[0.138986890697787802,0.0788667268197092153,0.478052749331348281],"hpluv":[271.055064965734516,347.775228922495899,33.7457437232739395],"hsluv":[271.055064965734516,76.9172199670655772,33.7457437232739395]},"#5533cc":{"lch":[35.3180325241442,103.075547069605378,270.067872161558512],"luv":[35.3180325241442,0.12210251098248695,-103.075474748725966],"rgb":[0.333333333333333315,0.2,0.8],"xyz":[0.158279266141509556,0.0865836769971980169,0.579659260001618493],"hpluv":[270.067872161558512,370.338167129355497,35.3180325241442],"hsluv":[270.067872161558512,80.0940957669769205,35.3180325241442]},"#5533dd":{"lch":[36.9651203282397915,113.385246995104879,269.332372034398134],"luv":[36.9651203282397915,-1.32116971635525937,-113.377549570986602],"rgb":[0.333333333333333315,0.2,0.866666666666666696],"xyz":[0.179797591692143099,0.0951910072174515509,0.69298910790162449],"hpluv":[269.332372034398134,389.227711455139399,36.9651203282397915],"hsluv":[269.332372034398134,84.8119298710034855,36.9651203282397915]},"#5533ee":{"lch":[38.6758450270606247,123.460606758128,268.77009087446072],"luv":[38.6758450270606247,-2.64999738284680575,-123.432163292052948],"rgb":[0.333333333333333315,0.2,0.933333333333333348],"xyz":[0.203609771958317837,0.104715879323921596,0.818399923970147714],"hpluv":[268.77009087446072,405.067987408818738,38.6758450270606247],"hsluv":[268.77009087446072,92.2936685874649356,38.6758450270606247]},"#5533ff":{"lch":[40.4402700894382363,133.340096114557781,268.33094335317071],"luv":[40.4402700894382363,-3.88371885476632706,-133.283524712158766],"rgb":[0.333333333333333315,0.2,1],"xyz":[0.22978082036814923,0.115184298687854292,0.956234112261929559],"hpluv":[268.33094335317071,418.394573227645935,40.4402700894382363],"hsluv":[268.33094335317071,99.99999999999946,40.4402700894382363]},"#ccbb00":{"lch":[75.0632334950121418,83.0331806403360275,77.5616137481136292],"luv":[75.0632334950121418,17.8844849446013434,81.0842419062853423],"rgb":[0.8,0.733333333333333282,0],"xyz":[0.426708295646073654,0.483788158566022,0.0709042957418917],"hpluv":[77.5616137481136292,140.366584388758554,75.0632334950121418],"hsluv":[77.5616137481136292,100.000000000002373,75.0632334950121418]},"#ccbb11":{"lch":[75.0886164663743898,81.9438461169387438,77.472162284636525],"luv":[75.0886164663743898,17.7747618107910519,79.9928231718707394],"rgb":[0.8,0.733333333333333282,0.0666666666666666657],"xyz":[0.427719961145710759,0.484192824765876828,0.0762324007066473436],"hpluv":[77.472162284636525,138.478250548668058,75.0886164663743898],"hsluv":[77.472162284636525,98.6397508878022222,75.0886164663743898]},"#ccbb22":{"lch":[75.1356323460541802,79.9401497843360289,77.301186242421764],"luv":[75.1356323460541802,17.5729239259544805,77.9847414064744839],"rgb":[0.8,0.733333333333333282,0.133333333333333331],"xyz":[0.429595319284187815,0.484942968021267629,0.0861092869026265362],"hpluv":[77.301186242421764,135.007637737713509,75.1356323460541802],"hsluv":[77.301186242421764,96.1390577710114087,75.1356323460541802]},"#ccbb33":{"lch":[75.2129378077961235,76.6847204783522471,77.0041162235267223],"luv":[75.2129378077961235,17.2449407213965777,74.720535158405653],"rgb":[0.8,0.733333333333333282,0.2],"xyz":[0.432683070016645521,0.486178068314250755,0.102371440760237686],"hpluv":[77.0041162235267223,129.376563997369885,75.2129378077961235],"hsluv":[77.0041162235267223,92.0797058264172392,75.2129378077961235]},"#ccbb44":{"lch":[75.3243183189310628,72.0782997366288,76.5373230188340585],"luv":[75.3243183189310628,16.7806861485978907,70.0977165484408431],"rgb":[0.8,0.733333333333333282,0.266666666666666663],"xyz":[0.437141065260864436,0.487961266411938321,0.125850215713124325],"hpluv":[76.5373230188340585,121.425150369898518,75.3243183189310628],"hsluv":[76.5373230188340585,86.34290383100182,75.3243183189310628]},"#ccbb55":{"lch":[75.4728625341146397,66.0852327898053,75.831238609121371],"luv":[75.4728625341146397,16.1762634357197506,64.0748507149330351],"rgb":[0.8,0.733333333333333282,0.333333333333333315],"xyz":[0.443103479300226422,0.490346232027683182,0.157252262987098212],"hpluv":[75.831238609121371,111.10994393806304,75.4728625341146397],"hsluv":[75.831238609121371,78.8905565478608395,75.4728625341146397]},"#ccbb66":{"lch":[75.6611363515091284,58.7289677793131,74.7643785098968863],"luv":[75.6611363515091284,15.4333320312120961,56.6648384700597134],"rgb":[0.8,0.733333333333333282,0.4],"xyz":[0.450688475073734607,0.493380230337086501,0.197199907394242202],"hpluv":[74.7643785098968863,98.4960546107130739,75.6611363515091284],"hsluv":[74.7643785098968863,69.7569980114599701,75.6611363515091284]},"#ccbb77":{"lch":[75.8912747684230737,50.090867045562554,73.1036630558194389],"luv":[75.8912747684230737,14.5584607724557671,47.9285528814827799],"rgb":[0.8,0.733333333333333282,0.466666666666666674],"xyz":[0.460002488093936512,0.497105835545167296,0.246253709300640111],"hpluv":[73.1036630558194389,83.7540906733876795,75.8912747684230737],"hsluv":[73.1036630558194389,59.0406908273499624,75.8912747684230737]},"#ccbb88":{"lch":[76.1650362860674335,40.3168412407358332,70.3426656865815119],"luv":[76.1650362860674335,13.5623472284534756,37.9672283066009157],"rgb":[0.8,0.733333333333333282,0.533333333333333326],"xyz":[0.471142905195880313,0.501562002385944838,0.304926572704212251],"hpluv":[70.3426656865815119,67.6831847004182379,76.1650362860674335],"hsluv":[70.3426656865815119,46.8939441374685586,76.1650362860674335]},"#ccbb99":{"lch":[76.4838383644648871,29.6573629081618826,65.1594516064859732],"luv":[76.4838383644648871,12.4588924968264081,26.9134756658990248],"rgb":[0.8,0.733333333333333282,0.6],"xyz":[0.484199869304953068,0.506784788029574,0.373693250345330186],"hpluv":[65.1594516064859732,50.6096239917615662,76.4838383644648871],"hsluv":[65.1594516064859732,33.5105935092195324,76.4838383644648871]},"#ccbbaa":{"lch":[76.8487828748923,18.695758935396551,52.9508743401004551],"luv":[76.8487828748923,11.26418644921627,14.9214444946778713],"rgb":[0.8,0.733333333333333282,0.66666666666666663],"xyz":[0.499257562270205568,0.512807865215675118,0.452997099962328786],"hpluv":[52.9508743401004551,32.5142912020767696,76.8487828748923],"hsluv":[52.9508743401004551,19.1124274046376854,76.8487828748923]},"#ccbbbb":{"lch":[77.2606763328388126,10.2255548171674207,12.1770506300639045],"luv":[77.2606763328388126,9.99548454110707,2.15690985147396086],"rgb":[0.8,0.733333333333333282,0.733333333333333282],"xyz":[0.516395154377542243,0.519662902058609877,0.543255085060970533],"hpluv":[12.1770506300639045,18.1733155366010308,77.2606763328388126],"hsluv":[12.1770506300639045,17.1437484893634,77.2606763328388126]},"#ccbbcc":{"lch":[77.7200476270310361,14.1732404034447406,307.715012949257925],"luv":[77.7200476270310361,8.67025786172679247,-11.2119299027867267],"rgb":[0.8,0.733333333333333282,0.8],"xyz":[0.535687529821264,0.527379852236098734,0.644861595731240689],"hpluv":[307.715012949257925,25.815587331502627,77.7200476270310361],"hsluv":[307.715012949257925,18.1614974693692588,77.7200476270310361]},"#ccbbdd":{"lch":[78.2271648233418375,26.0669286991650679,286.275412631614586],"luv":[78.2271648233418375,7.3053819239814537,-25.022313377306272],"rgb":[0.8,0.733333333333333282,0.866666666666666696],"xyz":[0.557205855371897485,0.53598718245635224,0.758191443631246687],"hpluv":[286.275412631614586,48.8074477639820898,78.2271648233418375],"hsluv":[286.275412631614586,43.4510455050629929,78.2271648233418375]},"#ccbbee":{"lch":[78.7820519440635,39.5678846485468299,278.599571826643512],"luv":[78.7820519440635,5.9165048525659989,-39.1230426461218741],"rgb":[0.8,0.733333333333333282,0.933333333333333348],"xyz":[0.581018035638072305,0.545512054562822257,0.88360225969976991],"hpluv":[278.599571826643512,76.4037468789834,78.7820519440635],"hsluv":[278.599571826643512,70.6968150087455314,78.7820519440635]},"#ccbbff":{"lch":[79.3845061922316546,53.5695670840829834,274.837592460092935],"luv":[79.3845061922316546,4.51760925339512553,-53.3787385033564235],"rgb":[0.8,0.733333333333333282,1],"xyz":[0.607189084047903616,0.555980473926755,1.02143644799155164],"hpluv":[274.837592460092935,107.038572744282547,79.3845061922316546],"hsluv":[274.837592460092935,99.9999999999963762,79.3845061922316546]},"#554400":{"lch":[29.5776499109456879,34.0768703371065413,65.9474553070004674],"luv":[29.5776499109456879,13.8888553561515469,31.1180460322924546],"rgb":[0.333333333333333315,0.266666666666666663,0],"xyz":[0.0581326024492852811,0.0606571242930536,0.0086461501732580659],"hpluv":[65.9474553070004674,146.195957524823825,29.5776499109456879],"hsluv":[65.9474553070004674,100.000000000002217,29.5776499109456879]},"#554411":{"lch":[29.6787804923011507,30.8713815411758574,64.2244588846852906],"luv":[29.6787804923011507,13.4243191404277429,27.7998175151708296],"rgb":[0.333333333333333315,0.266666666666666663,0.0666666666666666657],"xyz":[0.0591442679489224,0.0610617904929084548,0.0139742551380136974],"hpluv":[64.2244588846852906,131.992525946838612,29.6787804923011507],"hsluv":[64.2244588846852906,89.4077694450363509,29.6787804923011507]},"#554422":{"lch":[29.8650740872788916,25.3442505658492152,60.1547603291711539],"luv":[29.8650740872788916,12.6127938179319639,21.9829131111162219],"rgb":[0.333333333333333315,0.266666666666666663,0.133333333333333331],"xyz":[0.0610196260873994215,0.0618119337482992762,0.0238511413339928952],"hpluv":[60.1547603291711539,107.684992873038837,29.8650740872788916],"hsluv":[60.1547603291711539,70.9921927199271892,29.8650740872788916]},"#554433":{"lch":[30.1685472793317686,17.4710669916127905,49.2680266756281497],"luv":[30.1685472793317686,11.400244593092479,13.2390560480378525],"rgb":[0.333333333333333315,0.266666666666666663,0.2],"xyz":[0.064107376819857162,0.0630470340412823821,0.0401132951916040384],"hpluv":[49.2680266756281497,73.4859575586117302,30.1685472793317686],"hsluv":[49.2680266756281497,43.7071568358896,30.1685472793317686]},"#554444":{"lch":[30.5997780424982437,10.1013456632853149,12.1770506300629258],"luv":[30.5997780424982437,9.87407003600776356,2.13071000682558],"rgb":[0.333333333333333315,0.266666666666666663,0.266666666666666663],"xyz":[0.0685653720640760356,0.0648302321389699621,0.06359207014449067],"hpluv":[12.1770506300629258,41.8890279889816597,30.5997780424982437],"hsluv":[12.1770506300629258,9.81589763549682282,30.5997780424982437]},"#554455":{"lch":[31.1643459369041338,13.3310860490153988,307.715012949249],"luv":[31.1643459369041338,8.15508312366709198,-10.5457325251654854],"rgb":[0.333333333333333315,0.266666666666666663,0.333333333333333315],"xyz":[0.0745277861034380346,0.0672151977547148,0.0949941174184645853],"hpluv":[307.715012949249,54.2808752323906702,31.1643459369041338],"hsluv":[307.715012949249,18.7795296363480162,31.1643459369041338]},"#554466":{"lch":[31.8635722620044533,24.8340912161126894,284.841165372516684],"luv":[31.8635722620044533,6.36101223964894835,-24.005616214070443],"rgb":[0.333333333333333315,0.266666666666666663,0.4],"xyz":[0.0821127818769462,0.0702491960641181135,0.134941761825608547],"hpluv":[284.841165372516684,98.8992811700442331,31.8635722620044533],"hsluv":[284.841165372516684,27.8963704264996828,31.8635722620044533]},"#554477":{"lch":[32.6951743277909443,37.9095930577936784,276.944338121378166],"luv":[32.6951743277909443,4.58346102749711282,-37.6314912117090259],"rgb":[0.333333333333333315,0.266666666666666663,0.466666666666666674],"xyz":[0.0914267948971481254,0.0739748012721989229,0.183995563732006456],"hpluv":[276.944338121378166,147.131204947893167,32.6951743277909443],"hsluv":[276.944338121378166,36.6308714978363952,32.6951743277909443]},"#554488":{"lch":[33.6539551717380903,51.1018638808679526,273.231940865766],"luv":[33.6539551717380903,2.88102600900669881,-51.0205858574182116],"rgb":[0.333333333333333315,0.266666666666666663,0.533333333333333326],"xyz":[0.102567211999091953,0.0784309681129765207,0.242668427135578624],"hpluv":[273.231940865766,192.681471981123309,33.6539551717380903],"hsluv":[273.231940865766,44.6505812495932801,33.6539551717380903]},"#554499":{"lch":[34.7325237210335871,63.9719124096453271,271.149517924849135],"luv":[34.7325237210335871,1.28337422751110575,-63.9590378909774913],"rgb":[0.333333333333333315,0.266666666666666663,0.6],"xyz":[0.115624176108164667,0.0836537537566056782,0.311435104776696559],"hpluv":[271.149517924849135,233.718085138698228,34.7325237210335871],"hsluv":[271.149517924849135,51.7999710200133876,34.7325237210335871]},"#5544aa":{"lch":[35.9219992682327671,76.3678985235758461,269.849629436139082],"luv":[35.9219992682327671,-0.200424374653859844,-76.367635520403681],"rgb":[0.333333333333333315,0.266666666666666663,0.66666666666666663],"xyz":[0.130681869073417223,0.0896768309427067867,0.390738954393695104],"hpluv":[269.849629436139082,269.767524553786757,35.9219992682327671],"hsluv":[269.849629436139082,58.0490239963672465,35.9219992682327671]},"#5544bb":{"lch":[37.2126506061998725,88.2627815084196925,268.978337620063314],"luv":[37.2126506061998725,-1.57376312233016602,-88.2487499584998574],"rgb":[0.333333333333333315,0.266666666666666663,0.733333333333333282],"xyz":[0.147819461180753842,0.0965318677856415452,0.480996939492336906],"hpluv":[268.978337620063314,300.972163983169935,37.2126506061998725],"hsluv":[268.978337620063314,66.0075029321504729,37.2126506061998725]},"#5544cc":{"lch":[38.5944341452357733,99.6871153018899605,268.363808876145868],"luv":[38.5944341452357733,-2.84637030136680202,-99.6464707519528758],"rgb":[0.333333333333333315,0.266666666666666663,0.8],"xyz":[0.167111836624475596,0.104248817963130347,0.582603450162607062],"hpluv":[268.363808876145868,327.758284181939189,38.5944341452357733],"hsluv":[268.363808876145868,74.5283681063652352,38.5944341452357733]},"#5544dd":{"lch":[40.0574145710935738,110.694467066426,267.913412719582],"luv":[40.0574145710935738,-4.03035995406870118,-110.621070496360275],"rgb":[0.333333333333333315,0.266666666666666663,0.866666666666666696],"xyz":[0.18863016217510914,0.112856148183383881,0.695933298062613059],"hpluv":[267.913412719582,350.65684831918054,40.0574145710935738],"hsluv":[267.913412719582,82.9425769408534,40.0574145710935738]},"#5544ee":{"lch":[41.5920687629554777,121.343449115737442,267.573227144435918],"luv":[41.5920687629554777,-5.13798666158471651,-121.23462268002875],"rgb":[0.333333333333333315,0.266666666666666663,0.933333333333333348],"xyz":[0.212442342441283877,0.122381020289853898,0.821344114131136283],"hpluv":[267.573227144435918,370.207437425804358,41.5920687629554777],"hsluv":[267.573227144435918,91.3878688389195872,41.5920687629554777]},"#5544ff":{"lch":[43.1894854939413833,131.689092168021887,267.309962581974105],"luv":[43.1894854939413833,-6.18053297470727,-131.543977468321657],"rgb":[0.333333333333333315,0.266666666666666663,1],"xyz":[0.23861339085111527,0.132849439653786594,0.959178302422918128],"hpluv":[267.309962581974105,386.911020330846497,43.1894854939413833],"hsluv":[267.309962581974105,99.9999999999994174,43.1894854939413833]},"#cccc00":{"lch":[79.627228346343,87.7811065558180132,85.8743202181747449],"luv":[79.627228346343,6.31536666608958797,87.5536339167982192],"rgb":[0.8,0.8,0],"xyz":[0.464932038955690574,0.560235645185256814,0.0836455435117636481],"hpluv":[85.8743202181747449,177.871840357077815,79.627228346343],"hsluv":[85.8743202181747449,100.000000000002245,79.627228346343]},"#cccc11":{"lch":[79.6502471087807891,86.7718911178833707,85.8743202181747],"luv":[79.6502471087807891,6.24275917928854351,86.5470337215737],"rgb":[0.8,0.8,0.0666666666666666657],"xyz":[0.465943704455327679,0.560640311385111723,0.0889736484765192848],"hpluv":[85.8743202181747,176.061859354342118,79.6502471087807891],"hsluv":[85.8743202181747,98.8217369524532927,79.6502471087807891]},"#cccc22":{"lch":[79.6928884771461838,84.9132131482774497,85.8743202181746597],"luv":[79.6928884771461838,6.10903754655005482,84.6931722597497298],"rgb":[0.8,0.8,0.133333333333333331],"xyz":[0.467819062593804735,0.561390454640502523,0.0988505346724984774],"hpluv":[85.8743202181746597,172.717957044242922,79.6928884771461838],"hsluv":[85.8743202181746597,96.653203912443459,79.6928884771461838]},"#cccc33":{"lch":[79.7630142061039891,81.8867831673476445,85.8743202181745602],"luv":[79.7630142061039891,5.89130259459140682,81.6745848549912665],"rgb":[0.8,0.8,0.2],"xyz":[0.470906813326262441,0.56262555493348565,0.115112688530109614],"hpluv":[85.8743202181745602,167.243641009868185,79.7630142061039891],"hsluv":[85.8743202181745602,93.1263971058523623,79.7630142061039891]},"#cccc44":{"lch":[79.8640786601047523,77.5899505798540901,85.8743202181744],"luv":[79.8640786601047523,5.58216917901353593,77.3888869169280156],"rgb":[0.8,0.8,0.266666666666666663],"xyz":[0.475364808570481356,0.564408753031173216,0.138591463482996252],"hpluv":[85.8743202181744,159.406599578518,79.8640786601047523],"hsluv":[85.8743202181744,88.1281264379617113,79.8640786601047523]},"#cccc55":{"lch":[79.9989166638852396,71.9729259129294832,85.8743202181741481],"luv":[79.9989166638852396,5.17805522174052,71.7864179952492236],"rgb":[0.8,0.8,0.333333333333333315],"xyz":[0.481327222609843342,0.566793718646918,0.169993510756970168],"hpluv":[85.8743202181741481,149.042041148428552,79.9989166638852396],"hsluv":[85.8743202181741481,81.6104176612222147,79.9989166638852396]},"#cccc66":{"lch":[80.1699032642976448,65.0330295388014,85.8743202181738],"luv":[80.1699032642976448,4.67876793832737103,64.8645054060685453],"rgb":[0.8,0.8,0.4],"xyz":[0.488912218383351527,0.569827716956321284,0.209941155164114129],"hpluv":[85.8743202181738,136.038559250079544,80.1699032642976448],"hsluv":[85.8743202181738,73.5839630734023,80.1699032642976448]},"#cccc77":{"lch":[80.3790384438565724,56.8098315426934803,85.8743202181733096],"luv":[80.3790384438565724,4.0871541782499472,56.6626166941875837],"rgb":[0.8,0.8,0.466666666666666674],"xyz":[0.498226231403553432,0.573553322164402135,0.258994957070512],"hpluv":[85.8743202181733096,120.326663009793734,80.3790384438565724],"hsluv":[85.8743202181733096,64.1122831116318252,80.3790384438565724]},"#cccc88":{"lch":[80.6279973255091704,47.3795499902223085,85.8743202181724854],"luv":[80.6279973255091704,3.40869741112766222,47.2567724166107865],"rgb":[0.8,0.8,0.533333333333333326],"xyz":[0.509366648505497288,0.578009489005179677,0.317667820474084206],"hpluv":[85.8743202181724854,101.866799769447482,80.6279973255091704],"hsluv":[85.8743202181724854,53.3047126681541172,80.6279973255091704]},"#cccc99":{"lch":[80.9181626169042119,36.8483625211582861,85.8743202181711496],"luv":[80.9181626169042119,2.65103653276840934,36.7528750683892724],"rgb":[0.8,0.8,0.6],"xyz":[0.522423612614569932,0.58323227464880889,0.386434498115202141],"hpluv":[85.8743202181711496,80.635831276566,80.9181626169042119],"hsluv":[85.8743202181711496,41.30786304104587,80.9181626169042119]},"#ccccaa":{"lch":[81.2506473976222452,25.3448572993520607,85.8743202181683216],"luv":[81.2506473976222452,1.82342275263520492,25.27917959487],"rgb":[0.8,0.8,0.66666666666666663],"xyz":[0.537481305579822544,0.58925535183491,0.465738347732200686],"hpluv":[85.8743202181683216,56.6117825452175509,81.2506473976222452],"hsluv":[85.8743202181683216,28.2959036617823898,81.2506473976222452]},"#ccccbb":{"lch":[81.6263125989092657,13.0121158497344798,85.8743202181599],"luv":[81.6263125989092657,0.936149997616518514,12.9783967449324749],"rgb":[0.8,0.8,0.733333333333333282],"xyz":[0.554618897687159107,0.596110388677844716,0.555996332830842488],"hpluv":[85.8743202181599,29.7570160502500514,81.6263125989092657],"hsluv":[85.8743202181599,14.4603328966824272,81.6263125989092657]},"#cccccc":{"lch":[82.0457816743453,4.34523248843710382e-12,0],"luv":[82.0457816743453,4.08534684758697557e-12,1.48019813318368677e-12],"rgb":[0.8,0.8,0.8],"xyz":[0.573911273130880861,0.603827338855333573,0.657602843501112644],"hpluv":[0,1.02065966511349575e-11,82.0457816743453],"hsluv":[0,1.01642596056880755e-11,82.0457816743453]},"#ccccdd":{"lch":[82.5094539517328087,13.5418343873660199,265.874320218194214],"luv":[82.5094539517328087,-0.974260325972223118,-13.5067425899839115],"rgb":[0.8,0.8,0.866666666666666696],"xyz":[0.59542959868151446,0.612434669075587079,0.770932691401118642],"hpluv":[265.874320218194214,32.7843842039110882,82.5094539517328087],"hsluv":[265.874320218194214,30.4637656032122131,82.5094539517328087]},"#ccccee":{"lch":[83.0175175610870895,27.4698052042972165,265.874320218185687],"luv":[83.0175175610870895,-1.97630103922669687,-27.3986209901955817],"rgb":[0.8,0.8,0.933333333333333348],"xyz":[0.61924177894768917,0.621959541182057096,0.896343507469641865],"hpluv":[265.874320218185687,68.7964750954906776,83.0175175610870895],"hsluv":[265.874320218185687,63.7216981471091941,83.0175175610870895]},"#ccccff":{"lch":[83.5699624582004219,41.6509292947620153,265.874320218182845],"luv":[83.5699624582004219,-2.99655473483939438,-41.5429966521238825],"rgb":[0.8,0.8,1],"xyz":[0.645412827357520591,0.63242796054598982,1.03417769576142371],"hpluv":[265.874320218182845,108.336501116640306,83.5699624582004219],"hsluv":[265.874320218182845,99.9999999999952536,83.5699624582004219]},"#555500":{"lch":[34.8595382729148753,38.4291768930055397,85.8743202181747307],"luv":[34.8595382729148753,2.76476741155027961,38.3295929776711901],"rgb":[0.333333333333333315,0.333333333333333315,0],"xyz":[0.0699458591636312466,0.084283637721745866,0.0125839024113732767],"hpluv":[85.8743202181747307,139.887458074797564,34.8595382729148753],"hsluv":[85.8743202181747307,100.000000000002331,34.8595382729148753]},"#555511":{"lch":[34.9408046802893,35.5443725161734108,85.8743202181745318],"luv":[34.9408046802893,2.55722164100294558,35.4522641737772517],"rgb":[0.333333333333333315,0.333333333333333315,0.0666666666666666657],"xyz":[0.0709575246632683648,0.0846883039216007188,0.0179120073761289064],"hpluv":[85.8743202181745318,129.085444875460666,34.9408046802893],"hsluv":[85.8743202181745318,92.2780688504912,34.9408046802893]},"#555522":{"lch":[35.0907688239250604,30.4130442263608813,85.8743202181740202],"luv":[35.0907688239250604,2.18805086034508589,30.3342330139914047],"rgb":[0.333333333333333315,0.333333333333333315,0.133333333333333331],"xyz":[0.0728328828017454,0.0854384471769915332,0.027788893572108106],"hpluv":[85.8743202181740202,109.978131404858061,35.0907688239250604],"hsluv":[85.8743202181740202,78.6190076783406653,35.0907688239250604]},"#555533":{"lch":[35.3357817552570097,22.5221214621125654,85.8743202181728549],"luv":[35.3357817552570097,1.62034247131604014,22.4637584885032275],"rgb":[0.333333333333333315,0.333333333333333315,0.2],"xyz":[0.0759206335342031274,0.0866735474699746461,0.0440510474297192492],"hpluv":[85.8743202181728549,80.8786547215116656,35.3357817552570097],"hsluv":[85.8743202181728549,57.8169450175211566,35.3357817552570097]},"#555544":{"lch":[35.6854507669058592,12.1926559388895619,85.8743202181691743],"luv":[35.6854507669058592,0.877194374836262392,12.1610603515028455],"rgb":[0.333333333333333315,0.333333333333333315,0.266666666666666663],"xyz":[0.080378628778422,0.0884567455676622261,0.0675298223826058808],"hpluv":[85.8743202181691743,43.355725530805,35.6854507669058592],"hsluv":[85.8743202181691743,30.9932899828832262,35.6854507669058592]},"#555555":{"lch":[36.1458508397197278,1.89718584003012571e-12,0],"luv":[36.1458508397197278,1.79982851973451413e-12,5.9994283991150471e-13],"rgb":[0.333333333333333315,0.333333333333333315,0.333333333333333315],"xyz":[0.086341042817784,0.090841711183407059,0.0989318696565798],"hpluv":[0,6.66025333978279224e-12,36.1458508397197278],"hsluv":[0,1.90696849203660445e-12,36.1458508397197278]},"#555566":{"lch":[36.7200402720523087,13.391014832031539,265.874320218184835],"luv":[36.7200402720523087,-0.963409690459316,-13.3563138627398903],"rgb":[0.333333333333333315,0.333333333333333315,0.4],"xyz":[0.0939260385912921714,0.0938757094928103775,0.138879514063723758],"hpluv":[265.874320218184835,46.2753453717946712,36.7200402720523087],"hsluv":[265.874320218184835,10.0205788523093844,36.7200402720523087]},"#555577":{"lch":[37.4084382237490445,27.3651172837118537,265.874320218181],"luv":[37.4084382237490445,-1.96876932050326592,-27.294204353927416],"rgb":[0.333333333333333315,0.333333333333333315,0.466666666666666674],"xyz":[0.103240051611494091,0.0976013147008911869,0.187933315970121667],"hpluv":[265.874320218181,92.8254499938530131,37.4084382237490445],"hsluv":[265.874320218181,20.457601163446,37.4084382237490445]},"#555588":{"lch":[38.2091925227490421,41.4380747403329508,265.874320218179832],"luv":[38.2091925227490421,-2.98124102314999906,-41.330693680935326],"rgb":[0.333333333333333315,0.333333333333333315,0.533333333333333326],"xyz":[0.114380468713437919,0.102057481541668785,0.246606179373693835],"hpluv":[265.874320218179832,137.616667264503377,38.2091925227490421],"hsluv":[265.874320218179832,30.9677616121988244,38.2091925227490421]},"#555599":{"lch":[39.1185695394092079,55.2798607696674651,265.874320218179207],"luv":[39.1185695394092079,-3.97708121608675258,-55.1366106295487626],"rgb":[0.333333333333333315,0.333333333333333315,0.6],"xyz":[0.127437432822510632,0.107280267185297942,0.315372857014811769],"hpluv":[265.874320218179207,179.317758559659353,39.1185695394092079],"hsluv":[265.874320218179207,41.3377460212352688,39.1185695394092079]},"#5555aa":{"lch":[40.1313601009005083,68.6985541131433166,265.874320218178866],"luv":[40.1313601009005083,-4.94248222285034178,-68.5205312786852829],"rgb":[0.333333333333333315,0.333333333333333315,0.66666666666666663],"xyz":[0.142495125787763188,0.113303344371399051,0.394676706631810315],"hpluv":[265.874320218178866,217.221618066114,40.1313601009005083],"hsluv":[265.874320218178866,51.4705865731736907,40.1313601009005083]},"#5555bb":{"lch":[41.2412811653463791,81.6070675548115787,265.874320218178639],"luv":[41.2412811653463791,-5.8711785983783864,-81.3955940869131],"rgb":[0.333333333333333315,0.333333333333333315,0.733333333333333282],"xyz":[0.159632717895099807,0.120158381214333809,0.484934691730452117],"hpluv":[265.874320218178639,251.093199488524249,41.2412811653463791],"hsluv":[265.874320218178639,61.3584924374872642,41.2412811653463791]},"#5555cc":{"lch":[42.4413509436270431,93.9883543596933,265.874320218178468],"luv":[42.4413509436270431,-6.76194392407029365,-93.7447964935174838],"rgb":[0.333333333333333315,0.333333333333333315,0.8],"xyz":[0.178925093338821561,0.127875331391822611,0.586541202400722272],"hpluv":[265.874320218178468,281.011551484187919,42.4413509436270431],"hsluv":[265.874320218178468,71.0546332315999791,42.4413509436270431]},"#5555dd":{"lch":[43.7242196004532389,105.866759380085014,265.874320218178411],"luv":[43.7242196004532389,-7.61652967783202328,-105.592420264465588],"rgb":[0.333333333333333315,0.333333333333333315,0.866666666666666696],"xyz":[0.200443418889455105,0.136482661612076145,0.69987105030072827],"hpluv":[265.874320218178411,307.239379297131052,43.7242196004532389],"hsluv":[265.874320218178411,80.6500300064864462,43.7242196004532389]},"#5555ee":{"lch":[45.0824447652298588,117.287521338877212,265.874320218178354],"luv":[45.0824447652298588,-8.43819053636753402,-116.983586892732674],"rgb":[0.333333333333333315,0.333333333333333315,0.933333333333333348],"xyz":[0.224255599155629842,0.146007533718546162,0.825281866369251493],"hpluv":[265.874320218178354,330.128999417958312,45.0824447652298588],"hsluv":[265.874320218178354,90.2572110855712708,45.0824447652298588]},"#5555ff":{"lch":[46.508708270344421,128.30356479361032,265.874320218178241],"luv":[46.508708270344421,-9.23073412981071,-127.971083789162734],"rgb":[0.333333333333333315,0.333333333333333315,1],"xyz":[0.250426647565461236,0.156475953082478858,0.963116054661033338],"hpluv":[265.874320218178241,350.061034522531031,46.508708270344421],"hsluv":[265.874320218178241,99.9999999999992468,46.508708270344421]},"#ccdd00":{"lch":[84.2515012159558552,94.1174138813685772,92.7819892835375555],"luv":[84.2515012159558552,-4.56806363508048818,94.0064912138661128],"rgb":[0.8,0.866666666666666696,0],"xyz":[0.507566029502865779,0.645503626279608334,0.0978568736941549666],"hpluv":[92.7819892835375555,256.902059824464118,84.2515012159558552],"hsluv":[92.7819892835375555,100.000000000002373,84.2515012159558552]},"#ccdd11":{"lch":[84.2724460606142287,93.1910749716768692,92.8373396690251695],"luv":[84.2724460606142287,-4.61302172096193619,93.0768310858219508],"rgb":[0.8,0.866666666666666696,0.0666666666666666657],"xyz":[0.508577695002502939,0.645908292479463242,0.103184978658910603],"hpluv":[92.8373396690251695,254.758074317949962,84.2724460606142287],"hsluv":[92.8373396690251695,98.9747327605244465,84.2724460606142287]},"#ccdd22":{"lch":[84.3112490917695396,91.4838404394451175,92.9423140418060711],"luv":[84.3112490917695396,-4.69591125178302349,91.3632392106652],"rgb":[0.8,0.866666666666666696,0.133333333333333331],"xyz":[0.510453053140979884,0.646658435734854,0.113061864854889796],"hpluv":[92.9423140418060711,250.792999873820406,84.3112490917695396],"hsluv":[92.9423140418060711,97.0860193213801637,84.3112490917695396]},"#ccdd33":{"lch":[84.3750724084435291,88.7007441607730271,93.1221866378423329],"luv":[84.3750724084435291,-4.83112791130122865,88.5690816130525],"rgb":[0.8,0.866666666666666696,0.2],"xyz":[0.513540803873437701,0.647893536027837169,0.129324018712500932],"hpluv":[93.1221866378423329,244.290353733928498,84.3750724084435291],"hsluv":[93.1221866378423329,94.0094146319777,84.3750724084435291]},"#ccdd44":{"lch":[84.4670755276021197,84.7426243382763857,93.3985511967960491],"luv":[84.4670755276021197,-5.02363867224446459,84.5935897939611152],"rgb":[0.8,0.866666666666666696,0.266666666666666663],"xyz":[0.517998799117656561,0.649676734125524735,0.152802793665387571],"hpluv":[93.3985511967960491,234.956854948695792,84.4670755276021197],"hsluv":[93.3985511967960491,89.638788485123,84.4670755276021197]},"#ccdd55":{"lch":[84.5898637292301601,79.5568981087007359,93.8026723407881633],"luv":[84.5898637292301601,-5.27624840258561711,79.3817437416967095],"rgb":[0.8,0.866666666666666696,0.333333333333333315],"xyz":[0.523961213157018491,0.652061699741269485,0.184204840939361486],"hpluv":[93.8026723407881633,222.570337020009788,84.5898637292301601],"hsluv":[93.8026723407881633,83.9211409492819485,84.5898637292301601]},"#ccdd66":{"lch":[84.7456349568684857,73.1331501088793772,94.3836122863904],"luv":[84.7456349568684857,-5.58984810871231,72.9192103836116],"rgb":[0.8,0.866666666666666696,0.4],"xyz":[0.531546208930526731,0.655095698050672803,0.224152485346505448],"hpluv":[94.3836122863904,206.963810787645969,84.7456349568684857],"hsluv":[94.3836122863904,76.851201462777,84.7456349568684857]},"#ccdd77":{"lch":[84.9362580835765186,65.5005774458529828,95.2238145131341156],"luv":[84.9362580835765186,-5.96359854749608687,65.2285300930849274],"rgb":[0.8,0.866666666666666696,0.466666666666666674],"xyz":[0.540860221950728581,0.658821303258753654,0.273206287252903357],"hpluv":[95.2238145131341156,188.015270942662227,84.9362580835765186],"hsluv":[95.2238145131341156,68.4671748287691,84.9362580835765186]},"#ccdd88":{"lch":[85.1633194008604733,56.7265968622164536,96.4730545580279],"luv":[85.1633194008604733,-6.39512608731957588,56.3649638862273221],"rgb":[0.8,0.866666666666666696,0.533333333333333326],"xyz":[0.552000639052672493,0.663277470099531197,0.331879150656475552],"hpluv":[96.4730545580279,165.642146962004915,85.1633194008604733],"hsluv":[96.4730545580279,58.8458897999489707,85.1633194008604733]},"#ccdd99":{"lch":[85.4281525463895548,46.9188240443766134,98.432972024211054],"luv":[85.4281525463895548,-6.88075351903593724,46.4115425375783843],"rgb":[0.8,0.866666666666666696,0.6],"xyz":[0.565057603161745137,0.66850025574316041,0.400645828297593432],"hpluv":[98.432972024211054,139.807612964747022,85.4281525463895548],"hsluv":[98.432972024211054,48.0968721297954644,85.4281525463895548]},"#ccddaa":{"lch":[85.7318592309590457,36.2402041704229134,101.807730551991185],"luv":[85.7318592309590457,-7.41576493035038453,35.4733537857886176],"rgb":[0.8,0.866666666666666696,0.66666666666666663],"xyz":[0.580115296126997748,0.674523332929261477,0.479949677914592032],"hpluv":[101.807730551991185,110.570541660882469,85.7318592309590457],"hsluv":[101.807730551991185,36.355428538792907,85.7318592309590457]},"#ccddbb":{"lch":[86.0753247228475828,24.9849555175277693,108.661729708420211],"luv":[86.0753247228475828,-7.99469198849898,23.671351930590518],"rgb":[0.8,0.866666666666666696,0.733333333333333282],"xyz":[0.597252888234334312,0.681378369772196235,0.570207663013233779],"hpluv":[108.661729708420211,78.3375416693066313,86.0753247228475828],"hsluv":[108.661729708420211,23.7751141135998658,86.0753247228475828]},"#ccddcc":{"lch":[86.4592303781436158,14.0773589090278701,127.715012949224516],"luv":[86.4592303781436158,-8.61160385902942771,11.1360815742673065],"rgb":[0.8,0.866666666666666696,0.8],"xyz":[0.616545263678056066,0.689095319949685092,0.671814173683503935],"hpluv":[127.715012949224516,45.5363150686036633,86.4592303781436158],"hsluv":[127.715012949224516,10.5200784033656536,86.4592303781436158]},"#ccdddd":{"lch":[86.8840646031333677,9.47353258269296106,192.177050630057835],"luv":[86.8840646031333677,-9.26038246071566817,-1.99828333241680145],"rgb":[0.8,0.866666666666666696,0.866666666666666696],"xyz":[0.638063589228689665,0.697702650169938599,0.785144021583509932],"hpluv":[192.177050630057835,31.7497104564534354,86.8840646031333677],"hsluv":[192.177050630057835,13.7818607651735867,86.8840646031333677]},"#ccddee":{"lch":[87.3501331068194276,18.4943223550678582,237.507437159903361],"luv":[87.3501331068194276,-9.9349674143158051,-15.5992429896340141],"rgb":[0.8,0.866666666666666696,0.933333333333333348],"xyz":[0.661875769494864374,0.707227522276408616,0.910554837652033155],"hpluv":[237.507437159903361,64.5162574350921432,87.3501331068194276],"hsluv":[237.507437159903361,51.9237885878372083,87.3501331068194276]},"#ccddff":{"lch":[87.8575689716950876,31.3946063828198874,250.2096672964189],"luv":[87.8575689716950876,-10.6295595905023852,-29.5403752996496962],"rgb":[0.8,0.866666666666666696,1],"xyz":[0.688046817904695795,0.717695941640341339,1.04838902594381489],"hpluv":[250.2096672964189,114.576774718516177,87.8575689716950876],"hsluv":[250.2096672964189,99.999999999992923,87.8575689716950876]},"#556600":{"lch":[40.3019892206732919,46.2375853800199934,99.381148915360626],"luv":[40.3019892206732919,-7.53678920078613679,45.6191967302971264],"rgb":[0.333333333333333315,0.4,0],"xyz":[0.0849739168694777086,0.114339753133439206,0.0175932549799886241],"hpluv":[99.381148915360626,145.582103533726,40.3019892206732919],"hsluv":[99.381148915360626,100.000000000002302,40.3019892206732919]},"#556611":{"lch":[40.3683315206726334,43.7939991819964618,99.9948312384981364],"luv":[40.3683315206726334,-7.60085740022420797,43.1293557931736729],"rgb":[0.333333333333333315,0.4,0.0666666666666666657],"xyz":[0.0859855823691148269,0.114744419333294059,0.0229213599447442573],"hpluv":[99.9948312384981364,137.661701347396985,40.3683315206726334],"hsluv":[99.9948312384981364,94.2576138599214204,40.3683315206726334]},"#556622":{"lch":[40.4909010608148421,39.4175015547736436,101.28961011017671],"luv":[40.4909010608148421,-7.7166980000465113,38.6547797923705758],"rgb":[0.333333333333333315,0.4,0.133333333333333331],"xyz":[0.0878609405075918559,0.115494562588684874,0.0327982461407234499],"hpluv":[101.28961011017671,123.529583899053776,40.4909010608148421],"hsluv":[101.28961011017671,83.977954812826539,40.4909010608148421]},"#556633":{"lch":[40.6915589391927455,32.6300466430477343,104.01044750968255],"luv":[40.6915589391927455,-7.89969574444685207,31.6593548745491518],"rgb":[0.333333333333333315,0.4,0.2],"xyz":[0.0909486912400495895,0.116729662881667987,0.0490603999983346],"hpluv":[104.01044750968255,101.754281640855311,40.6915589391927455],"hsluv":[104.01044750968255,68.015525760737134,40.6915589391927455]},"#556644":{"lch":[40.9787805135115306,23.7356999706126039,110.078417815454017],"luv":[40.9787805135115306,-8.1486066005911173,22.2931304119395044],"rgb":[0.333333333333333315,0.4,0.266666666666666663],"xyz":[0.0954066864842684631,0.118512860979355567,0.0725391749512212386],"hpluv":[110.078417815454017,73.4991539757345436,40.9787805135115306],"hsluv":[110.078417815454017,46.8659194429644117,40.9787805135115306]},"#556655":{"lch":[41.3584605937638372,13.8246485480389332,127.71501294923371],"luv":[41.3584605937638372,-8.45701225317865202,10.9361716896901875],"rgb":[0.333333333333333315,0.4,0.333333333333333315],"xyz":[0.101369100523630462,0.120897826595100399,0.10394122222519514],"hpluv":[127.71501294923371,42.4159365242361659,41.3584605937638372],"hsluv":[127.71501294923371,21.5972717302380097,41.3584605937638372]},"#556666":{"lch":[41.8343160733152146,9.01834401177998402,192.177050630060307],"luv":[41.8343160733152146,-8.8154354231024481,-1.90226891261946607],"rgb":[0.333333333333333315,0.4,0.4],"xyz":[0.108954096297138633,0.123931824904503718,0.143888866632339102],"hpluv":[192.177050630060307,27.3547941496945484,41.8343160733152146],"hsluv":[192.177050630060307,27.2477194602778816,41.8343160733152146]},"#556677":{"lch":[42.4081371223492525,18.1914318380534858,239.570903430569189],"luv":[42.4081371223492525,-9.21344558994119289,-15.6856817728697795],"rgb":[0.333333333333333315,0.4,0.466666666666666674],"xyz":[0.118268109317340553,0.127657430112584541,0.192942668538737],"hpluv":[239.570903430569189,54.4323412384199443,42.4081371223492525],"hsluv":[239.570903430569189,33.1626844996427934,42.4081371223492525]},"#556688":{"lch":[43.0800011190655425,31.444407039181371,252.144687081813345],"luv":[43.0800011190655425,-9.64130614192028546,-29.9298504828121246],"rgb":[0.333333333333333315,0.4,0.533333333333333326],"xyz":[0.129408526419284381,0.132113596953362111,0.251615531942309179],"hpluv":[252.144687081813345,92.6204805766441694,43.0800011190655425],"hsluv":[252.144687081813345,39.0862788716996903,43.0800011190655425]},"#556699":{"lch":[43.8484890486876964,45.3842681536749,257.153237313451427],"luv":[43.8484890486876964,-10.090933534038701,-44.2482186789059639],"rgb":[0.333333333333333315,0.4,0.6],"xyz":[0.142465490528357108,0.137336382596991269,0.320382209583427113],"hpluv":[257.153237313451427,131.337888607863903,43.8484890486876964],"hsluv":[257.153237313451427,44.8182326355090055,43.8484890486876964]},"#5566aa":{"lch":[44.7109144588579,59.3119903927914223,259.748012643434],"luv":[44.7109144588579,-10.5562103110165033,-58.3650462882037928],"rgb":[0.333333333333333315,0.4,0.66666666666666663],"xyz":[0.157523183493609636,0.143359459783092391,0.399686059200425714],"hpluv":[259.748012643434,168.332615885742257,44.7109144588579],"hsluv":[259.748012643434,50.2202557252039341,44.7109144588579]},"#5566bb":{"lch":[45.6635615252987463,72.9454914642558805,261.300749278862554],"luv":[45.6635615252987463,-11.0328519112314041,-72.1063166696695532],"rgb":[0.333333333333333315,0.4,0.733333333333333282],"xyz":[0.174660775600946283,0.15021449662602715,0.48994404429906746],"hpluv":[261.300749278862554,202.706651650989187,45.6635615252987463],"hsluv":[261.300749278862554,56.1551480082100767,45.6635615252987463]},"#5566cc":{"lch":[46.7019230239281953,86.153038102066219,262.316955218783903],"luv":[46.7019230239281953,-11.5180515282988498,-85.3796255742992],"rgb":[0.333333333333333315,0.4,0.8],"xyz":[0.193953151044668037,0.157931446803515951,0.591550554969337616],"hpluv":[262.316955218783903,234.085848684940913,46.7019230239281953],"hsluv":[262.316955218783903,67.0801136297732086,46.7019230239281953]},"#5566dd":{"lch":[47.8209277182799539,98.8836589551542,263.023825475898036],"luv":[47.8209277182799539,-12.010072956506761,-98.1515978267222664],"rgb":[0.333333333333333315,0.4,0.866666666666666696],"xyz":[0.215471476595301581,0.166538777023769485,0.704880402869343614],"hpluv":[263.023825475898036,262.38914084011509,47.8209277182799539],"hsluv":[263.023825475898036,77.9738615315733909,47.8209277182799539]},"#5566ee":{"lch":[49.0151480019121095,111.133834597019145,263.537783903780621],"luv":[49.0151480019121095,-12.5078880087352609,-110.427722650603215],"rgb":[0.333333333333333315,0.4,0.933333333333333348],"xyz":[0.23928365686147629,0.176063649130239502,0.830291218937866837],"hpluv":[263.537783903780621,287.710232398192659,49.0151480019121095],"hsluv":[263.537783903780621,88.9133133505171145,49.0151480019121095]},"#5566ff":{"lch":[50.2789812841098751,122.927042369796439,263.924295565134457],"luv":[50.2789812841098751,-13.0108962765956715,-122.236550687040406],"rgb":[0.333333333333333315,0.4,1],"xyz":[0.265454705271307712,0.186532068494172198,0.968125407229648682],"hpluv":[263.924295565134457,310.2417842032321,50.2789812841098751],"hsluv":[263.924295565134457,99.99999999999919,50.2789812841098751]},"#ccee00":{"lch":[88.9159222564839382,101.512339113439552,98.3897184755654],"luv":[88.9159222564839382,-14.8112090817394879,100.426007975120399],"rgb":[0.8,0.933333333333333348,0],"xyz":[0.554744805843380595,0.739861178960639299,0.113583132474326151],"hpluv":[98.3897184755654,409.405376777149172,88.9159222564839382],"hsluv":[98.3897184755654,100.000000000002402,88.9159222564839382]},"#ccee11":{"lch":[88.9350466502285855,100.665090554083037,98.4749110233884863],"luv":[88.9350466502285855,-14.8356507509631186,99.5658773027044],"rgb":[0.8,0.933333333333333348,0.0666666666666666657],"xyz":[0.555756471343017755,0.740265845160494207,0.118911237439081788],"hpluv":[98.4749110233884863,406.753799510206477,88.9350466502285855],"hsluv":[98.4749110233884863,99.103544824997428,88.9350466502285855]},"#ccee22":{"lch":[88.9704797513205108,99.1029038393990191,98.635890718104875],"luv":[88.9704797513205108,-14.8807650249594605,97.9793262973016823],"rgb":[0.8,0.933333333333333348,0.133333333333333331],"xyz":[0.557631829481494701,0.741015988415885,0.12878812363506098],"hpluv":[98.635890718104875,401.844579090604725,88.9704797513205108],"hsluv":[98.635890718104875,97.4508266239822376,88.9704797513205108]},"#ccee33":{"lch":[89.0287677898111696,96.5543294248683281,98.9099371535565837],"luv":[89.0287677898111696,-14.95450175665089,95.3892101230339335],"rgb":[0.8,0.933333333333333348,0.2],"xyz":[0.560719580213952518,0.742251088708868134,0.145050277492672131],"hpluv":[98.9099371535565837,393.778440936160052,89.0287677898111696],"hsluv":[98.9099371535565837,94.7550356083218333,89.0287677898111696]},"#ccee44":{"lch":[89.1128082270408157,92.9258219449750698,99.3266297509707101],"luv":[89.1128082270408157,-15.0597883526499068,91.6973890518296],"rgb":[0.8,0.933333333333333348,0.266666666666666663],"xyz":[0.565177575458171377,0.7440342868065557,0.168529052445558769],"hpluv":[99.3266297509707101,382.168331694223468,89.1128082270408157],"hsluv":[99.3266297509707101,90.9176847425828782,89.1128082270408157]},"#ccee55":{"lch":[89.224999764075946,88.1655281542066263,99.9265688821158591],"luv":[89.224999764075946,-15.1984931464969293,86.8456456063635187],"rgb":[0.8,0.933333333333333348,0.333333333333333315],"xyz":[0.571139989497533307,0.74641925242230045,0.199931099719532657],"hpluv":[99.9265688821158591,366.702481299920066,89.224999764075946],"hsluv":[99.9265688821158591,85.8839575374920372,89.224999764075946]},"#ccee66":{"lch":[89.3673776950467555,82.2600687786622444,100.76990980050725],"luv":[89.3673776950467555,-15.3715621348797811,80.8111006793235873],"rgb":[0.8,0.933333333333333348,0.4],"xyz":[0.578724985271041548,0.749453250731703768,0.239878744126676646],"hpluv":[100.76990980050725,347.124780760482849,89.3673776950467555],"hsluv":[100.76990980050725,79.6381055931254451,89.3673776950467555]},"#ccee77":{"lch":[89.5416863487684651,75.2338064272131817,101.95104715507324],"luv":[89.5416863487684651,-15.5791079616514043,73.6031047215169707],"rgb":[0.8,0.933333333333333348,0.466666666666666674],"xyz":[0.588038998291243398,0.753178855939784619,0.288932546033074555],"hpluv":[101.95104715507324,323.225079834159544,89.5416863487684651],"hsluv":[101.95104715507324,72.2002225208687349,89.5416863487684651]},"#ccee88":{"lch":[89.7494222523926481,67.150335761217363,103.626883623375818],"luv":[89.7494222523926481,-15.8204941270879562,65.2600916212887654],"rgb":[0.8,0.933333333333333348,0.533333333333333326],"xyz":[0.599179415393187309,0.757635022780562162,0.347605409436646695],"hpluv":[103.626883623375818,294.840431689210504,89.7494222523926481],"hsluv":[103.626883623375818,63.6228180815060256,89.7494222523926481]},"#ccee99":{"lch":[89.9918618926625697,58.1187887690418705,106.076645299715267],"luv":[89.9918618926625697,-16.094429395756972,55.8458857070554586],"rgb":[0.8,0.933333333333333348,0.6],"xyz":[0.61223637950226,0.762857808424191375,0.41637208707776463],"hpluv":[106.076645299715267,261.881276027636488,89.9918618926625697],"hsluv":[106.076645299715267,53.9866751643902418,89.9918618926625697]},"#cceeaa":{"lch":[90.2700807835134071,48.3136880988356481,109.842100564752428],"luv":[90.2700807835134071,-16.3990756203548145,45.4453823452884791],"rgb":[0.8,0.933333333333333348,0.66666666666666663],"xyz":[0.627294072467512565,0.768880885610292442,0.495675936694763231],"hpluv":[109.842100564752428,224.429228200920562,90.2700807835134071],"hsluv":[109.842100564752428,43.3959332818477037,90.2700807835134071]},"#cceebb":{"lch":[90.5849674500281736,38.0375276056681,116.096591597387416],"luv":[90.5849674500281736,-16.7321662581560417,34.1597441246474745],"rgb":[0.8,0.933333333333333348,0.733333333333333282],"xyz":[0.644431664574849128,0.7757359224532272,0.585933921793405],"hpluv":[116.096591597387416,183.067580850930824,90.5849674500281736],"hsluv":[116.096591597387416,31.9725829176952736,90.5849674500281736]},"#cceecc":{"lch":[90.9372344233822,27.9388105970907,127.715012949233042],"luv":[90.9372344233822,-17.0911298567764973,22.1013668762570106],"rgb":[0.8,0.933333333333333348,0.8],"xyz":[0.663724040018570882,0.783452872630716057,0.687540432463675133],"hpluv":[127.715012949233042,140.086760618050221,90.9372344233822],"hsluv":[127.715012949233042,30.3633811556705,90.9372344233822]},"#cceedd":{"lch":[91.327427526889025,19.8359452431300802,151.749458426713772],"luv":[91.327427526889025,-17.4732117028180483,9.38890816213244861],"rgb":[0.8,0.933333333333333348,0.866666666666666696],"xyz":[0.685242365569204481,0.792060202850969564,0.800870280363681131],"hpluv":[151.749458426713772,104.258293798779974,91.327427526889025],"hsluv":[151.749458426713772,29.0871711351253381,91.327427526889025]},"#cceeee":{"lch":[91.7559342603931,18.2870370266976643,192.177050630059739],"luv":[91.7559342603931,-17.87558711202,-3.8573447624494035],"rgb":[0.8,0.933333333333333348,0.933333333333333348],"xyz":[0.709054545835379191,0.801585074957439581,0.926281096432204354],"hpluv":[192.177050630059739,101.458793010104,91.7559342603931],"hsluv":[192.177050630059739,27.4217374065650326,91.7559342603931]},"#cceeff":{"lch":[92.2229917973594553,25.3309747642836847,223.758903531404655],"luv":[92.2229917973594553,-18.2954610486514362,-17.5195430170439934],"rgb":[0.8,0.933333333333333348,1],"xyz":[0.735225594245210612,0.812053494321372304,1.06411528472398609],"hpluv":[223.758903531404655,149.53216426558339,92.2229917973594553],"hsluv":[223.758903531404655,99.9999999999890861,92.2229917973594553]},"#557700":{"lch":[45.8045523271613533,55.5294361467329836,107.801769483020877],"luv":[45.8045523271613533,-16.9767207468495087,52.8706840456749276],"rgb":[0.333333333333333315,0.466666666666666674,0],"xyz":[0.103427654922895337,0.151247229240274977,0.023744500997794328],"hpluv":[107.801769483020877,153.83457187868342,45.8045523271613533],"hsluv":[107.801769483020877,100.000000000002288,45.8045523271613533]},"#557711":{"lch":[45.859623178970125,53.470483995502839,108.496139146384877],"luv":[45.859623178970125,-16.9630166203079504,50.7084679896019068],"rgb":[0.333333333333333315,0.466666666666666674,0.0666666666666666657],"xyz":[0.104439320422532456,0.15165189544012983,0.0290726059625499578],"hpluv":[108.496139146384877,147.952722071756682,45.859623178970125],"hsluv":[108.496139146384877,95.6324690191426,45.859623178970125]},"#557722":{"lch":[45.9614512879267494,49.76586948312967,109.899607245517743],"luv":[45.9614512879267494,-16.93896350220896,46.7943723206405409],"rgb":[0.333333333333333315,0.466666666666666674,0.133333333333333331],"xyz":[0.106314678561009485,0.152402038695520659,0.0389494921585291573],"hpluv":[109.899607245517743,137.396980684000368,45.9614512879267494],"hsluv":[109.899607245517743,87.7486210093461239,45.9614512879267494]},"#557733":{"lch":[46.1283843073061,43.9801463057798472,112.602269297118809],"luv":[46.1283843073061,-16.9029726498973396,40.6022509803905223],"rgb":[0.333333333333333315,0.466666666666666674,0.2],"xyz":[0.109402429293467218,0.153637138988503757,0.0552116460161403],"hpluv":[112.602269297118809,120.983948144693443,46.1283843073061],"hsluv":[112.602269297118809,75.3373974156097432,46.1283843073061]},"#557744":{"lch":[46.3678258482665413,36.3216896494577526,117.654446368962965],"luv":[46.3678258482665413,-16.8582748313623227,32.1724060135666861],"rgb":[0.333333333333333315,0.466666666666666674,0.266666666666666663],"xyz":[0.113860424537686092,0.155420337086191351,0.0786904209690269391],"hpluv":[117.654446368962965,99.4005156049610861,46.3678258482665413],"hsluv":[117.654446368962965,58.5682493514162346,46.3678258482665413]},"#557755":{"lch":[46.6852246722490349,27.4798560002662136,127.715012949237362],"luv":[46.6852246722490349,-16.8103715694690621,21.7383047520213317],"rgb":[0.333333333333333315,0.466666666666666674,0.333333333333333315],"xyz":[0.119822838577048091,0.157805302701936184,0.110092468243000841],"hpluv":[127.715012949237362,74.6920408293198506,46.6852246722490349],"hsluv":[127.715012949237362,38.0315615795731219,46.6852246722490349]},"#557766":{"lch":[47.0844103757355299,19.3533005907945039,150.034269154893224],"luv":[47.0844103757355299,-16.7662346577491,9.66662397939114371],"rgb":[0.333333333333333315,0.466666666666666674,0.4],"xyz":[0.127407834350556276,0.160839301011339475,0.150040112650144802],"hpluv":[150.034269154893224,52.1575559127659147,47.0844103757355299],"hsluv":[150.034269154893224,41.6155983808333403,47.0844103757355299]},"#557777":{"lch":[47.5677829408255519,17.1184872206298522,192.177050630060563],"luv":[47.5677829408255519,-16.733329138647683,-3.61085871511915846],"rgb":[0.333333333333333315,0.466666666666666674,0.466666666666666674],"xyz":[0.136721847370758182,0.164564906219420298,0.199093914556542712],"hpluv":[192.177050630060563,45.665876663612373,47.5677829408255519],"hsluv":[192.177050630060563,45.4871270252879683,47.5677829408255519]},"#557788":{"lch":[48.1364533988364371,24.3189053895014062,226.569261606948],"luv":[48.1364533988364371,-16.7186932071720697,-17.6605338760746164],"rgb":[0.333333333333333315,0.466666666666666674,0.533333333333333326],"xyz":[0.147862264472702,0.169021073060197896,0.257766777960114879],"hpluv":[226.569261606948,64.1075619232690173,48.1364533988364371],"hsluv":[226.569261606948,49.4930397679002567,48.1364533988364371]},"#557799":{"lch":[48.7903733600049776,36.1922931768580227,242.470518495943224],"luv":[48.7903733600049776,-16.7282575252316086,-32.0943528610435749],"rgb":[0.333333333333333315,0.466666666666666674,0.6],"xyz":[0.160919228581774709,0.174243858703827054,0.326533455601232814],"hpluv":[242.470518495943224,94.128532149155447,48.7903733600049776],"hsluv":[242.470518495943224,53.4981409183259657,48.7903733600049776]},"#5577aa":{"lch":[49.5284680294880957,49.5215284063763121,250.210257263218182],"luv":[49.5284680294880957,-16.7664778987851584,-46.5968560588918308],"rgb":[0.333333333333333315,0.466666666666666674,0.66666666666666663],"xyz":[0.175976921547027265,0.180266935889928148,0.405837305218231414],"hpluv":[250.210257263218182,126.875705911364861,49.5284680294880957],"hsluv":[250.210257263218182,57.3943781585142503,49.5284680294880957]},"#5577bb":{"lch":[50.3487764471929324,63.2168000831305,254.554261837862072],"luv":[50.3487764471929324,-16.8362555209572413,-60.9336057753316069],"rgb":[0.333333333333333315,0.466666666666666674,0.733333333333333282],"xyz":[0.193114513654363912,0.187121972732862907,0.496095290316873161],"hpluv":[254.554261837862072,159.324628464713726,50.3487764471929324],"hsluv":[254.554261837862072,61.1035457400689737,50.3487764471929324]},"#5577cc":{"lch":[51.2485971738404942,76.8367273058076279,257.264224247337211],"luv":[51.2485971738404942,-16.9390629845235594,-74.9463195111900546],"rgb":[0.333333333333333315,0.466666666666666674,0.8],"xyz":[0.212406889098085638,0.194838922910351708,0.597701800987143317],"hpluv":[257.264224247337211,190.250673687597981,51.2485971738404942],"hsluv":[257.264224247337211,64.5753193228694329,51.2485971738404942]},"#5577dd":{"lch":[52.2246350466270428,90.1721178795433502,259.084439545341468],"luv":[52.2246350466270428,-17.075183181685393,-88.5406627612090347],"rgb":[0.333333333333333315,0.466666666666666674,0.866666666666666696],"xyz":[0.233925214648719182,0.203446253130605242,0.711031648887149315],"hpluv":[259.084439545341468,219.096874823476185,52.2246350466270428],"hsluv":[259.084439545341468,74.9139940771913757,52.2246350466270428]},"#5577ee":{"lch":[53.2731438322457791,103.123503170291656,260.373961187264626],"luv":[53.2731438322457791,-17.2439851141504441,-101.671539200978515],"rgb":[0.333333333333333315,0.466666666666666674,0.933333333333333348],"xyz":[0.257737394914893947,0.212971125237075287,0.836442464955672538],"hpluv":[260.373961187264626,245.63410016831412,53.2731438322457791],"hsluv":[260.373961187264626,87.3510385353649,53.2731438322457791]},"#5577ff":{"lch":[54.3900599484937572,115.652149768064447,261.324783585170792],"luv":[54.3900599484937572,-17.4441870719707097,-114.32899931064253],"rgb":[0.333333333333333315,0.466666666666666674,1],"xyz":[0.283908443324725313,0.223439544601007983,0.974276653247454383],"hpluv":[261.324783585170792,269.819603010977914,54.3900599484937572],"hsluv":[261.324783585170792,99.9999999999989768,54.3900599484937572]},"#ccff00":{"lch":[93.605159534834371,109.568762044642341,102.903766821995461],"luv":[93.605159534834371,-24.4682604068618268,106.801768939739262],"rgb":[0.8,1,0],"xyz":[0.606597178273054372,0.843565923819988406,0.130867256617550276],"hpluv":[102.903766821995461,795.170643052662513,93.605159534834371],"hsluv":[102.903766821995461,100.000000000002359,93.605159534834371]},"#ccff11":{"lch":[93.6226829283917681,108.793716578542018,103.00230683973929],"luv":[93.6226829283917681,-24.4775292120886689,106.004355243751533],"rgb":[0.8,1,0.0666666666666666657],"xyz":[0.607608843772691531,0.843970590019843314,0.136195361582305913],"hpluv":[103.00230683973929,791.823376913543598,93.6226829283917681],"hsluv":[103.00230683973929,99.9999999999883755,93.6226829283917681]},"#ccff22":{"lch":[93.6551518183817535,107.364125357399203,103.187929529698394],"luv":[93.6551518183817535,-24.4946701604029187,104.532609971684906],"rgb":[0.8,1,0.133333333333333331],"xyz":[0.609484201911168477,0.844720733275234115,0.146072247778285091],"hpluv":[103.187929529698394,785.615721446726184,93.6551518183817535],"hsluv":[103.187929529698394,99.9999999999883613,93.6551518183817535]},"#ccff33":{"lch":[93.7085695338431464,105.030410790236445,103.502196488744218],"luv":[93.7085695338431464,-24.5227776356263192,102.127472150246831],"rgb":[0.8,1,0.2],"xyz":[0.612571952643626294,0.845955833568217241,0.162334401635896242],"hpluv":[103.502196488744218,775.386532390006209,93.7085695338431464],"hsluv":[103.502196488744218,99.9999999999882334,93.7085695338431464]},"#ccff44":{"lch":[93.7856006692118456,101.704796230104463,103.975902087852319],"luv":[93.7856006692118456,-24.5631097372148304,98.6940687997238],"rgb":[0.8,1,0.266666666666666663],"xyz":[0.617029947887845154,0.847739031665904808,0.18581317658878288],"hpluv":[103.975902087852319,760.597322717251814,93.7856006692118456],"hsluv":[103.975902087852319,99.9999999999883329,93.7856006692118456]},"#ccff55":{"lch":[93.8884584724407887,97.3367975087049473,104.649262803016768],"luv":[93.8884584724407887,-24.6166025777849207,94.1725810773922802],"rgb":[0.8,1,0.333333333333333315],"xyz":[0.622992361927207083,0.850123997281649557,0.217215223862756796],"hpluv":[104.649262803016768,740.773636872505676,93.8884584724407887],"hsluv":[104.649262803016768,99.999999999988,93.8884584724407887]},"#ccff66":{"lch":[94.0190298395239381,91.9106503723783561,105.578857731119598],"luv":[94.0190298395239381,-24.6839280463924027,88.5340123798424514],"rgb":[0.8,1,0.4],"xyz":[0.630577357700715324,0.853157995591052876,0.257162868269900757],"hpluv":[105.578857731119598,715.472563222964368,94.0190298395239381],"hsluv":[105.578857731119598,99.9999999999875371,94.0190298395239381]},"#ccff77":{"lch":[94.1789424927264349,85.4453105292158597,106.848417981185563],"luv":[94.1789424927264349,-24.7655268674123263,81.7775627566241781],"rgb":[0.8,1,0.466666666666666674],"xyz":[0.639891370720917174,0.856883600799133727,0.306216670176298666],"hpluv":[106.848417981185563,684.262206540111833,94.1789424927264349],"hsluv":[106.848417981185563,99.9999999999873381,94.1789424927264349]},"#ccff88":{"lch":[94.3696051468525781,77.9966506735583778,108.587502777405675],"luv":[94.3696051468525781,-24.8616333733667219,73.92818611532],"rgb":[0.8,1,0.533333333333333326],"xyz":[0.651031787822861086,0.861339767639911269,0.364889533579870862],"hpluv":[108.587502777405675,646.714804777793233,94.3696051468525781],"hsluv":[108.587502777405675,99.9999999999870397,94.3696051468525781]},"#ccff99":{"lch":[94.5922333645115572,69.6635615227201868,111.00626449694991],"luv":[94.5922333645115572,-24.9722983929036886,65.0338074927618],"rgb":[0.8,1,0.6],"xyz":[0.66408875193193373,0.866562553283540482,0.433656211220988741],"hpluv":[111.00626449694991,602.43247210261859,94.5922333645115572],"hsluv":[111.00626449694991,99.999999999986656,94.5922333645115572]},"#ccffaa":{"lch":[94.8478672375315881,60.6030718965076574,114.464397640121035],"luv":[94.8478672375315881,-25.0974127381864349,55.1620539605116],"rgb":[0.8,1,0.66666666666666663],"xyz":[0.679146444897186341,0.872585630469641549,0.512960060837987397],"hpluv":[114.464397640121035,551.164539659931279,94.8478672375315881],"hsluv":[114.464397640121035,99.9999999999858886,94.8478672375315881]},"#ccffbb":{"lch":[95.1373841969384,51.0680528276179899,119.615591899534252],"luv":[95.1373841969384,-25.2367319023901224,44.3965469658538439],"rgb":[0.8,1,0.733333333333333282],"xyz":[0.696284037004522904,0.879440667312576307,0.603218045936629088],"hpluv":[119.615591899534252,493.192076617112832,95.1373841969384],"hsluv":[119.615591899534252,99.999999999985036,95.1373841969384]},"#ccffcc":{"lch":[95.4615088709507802,41.5047839306073527,127.715012949235671],"luv":[95.4615088709507802,-25.3899015983654444,32.8329101048280876],"rgb":[0.8,1,0.8],"xyz":[0.715576412448244659,0.887157617490065165,0.704824556606899244],"hpluv":[127.715012949235671,430.524428546451247,95.4615088709507802],"hsluv":[127.715012949235671,99.9999999999844,95.4615088709507802]},"#ccffdd":{"lch":[95.8208211701733603,32.8093273722257,141.163585516297758],"luv":[95.8208211701733603,-25.5564832849791728,20.5746962243056899],"rgb":[0.8,1,0.866666666666666696],"xyz":[0.737094737998878258,0.895764947710318671,0.818154404506905242],"hpluv":[141.163585516297758,370.598640927928557,95.8208211701733603],"hsluv":[141.163585516297758,99.9999999999829612,95.8208211701733603]},"#ccffee":{"lch":[96.2157633520208293,26.8716381406037179,163.283084738459195],"luv":[96.2157633520208293,-25.7359786647352209,7.72944619800421506],"rgb":[0.8,1,0.933333333333333348],"xyz":[0.760906918265053,0.905289819816788688,0.943565220575428465],"hpluv":[163.283084738459195,336.210724351278486,96.2157633520208293],"hsluv":[163.283084738459195,99.9999999999813411,96.2157633520208293]},"#ccffff":{"lch":[96.6466465538527899,26.5246444827845806,192.177050630060279],"luv":[96.6466465538527899,-25.9278521925209873,-5.59493034995961125],"rgb":[0.8,1,1],"xyz":[0.787077966674884388,0.915758239180721412,1.08139940886721031],"hpluv":[192.177050630060279,375.729722461639881,96.6466465538527899],"hsluv":[192.177050630060279,99.9999999999789111,96.6466465538527899]},"#558800":{"lch":[51.3121649295003266,65.2833526322192,113.133039202335894],"luv":[51.3121649295003266,-25.6477049188799029,60.0342515843809466],"rgb":[0.333333333333333315,0.533333333333333326,0],"xyz":[0.125500024647865804,0.195391968690216522,0.0311019575727842744],"hpluv":[113.133039202335894,161.443824642532,51.3121649295003266],"hsluv":[113.133039202335894,100.000000000002416,51.3121649295003266]},"#558811":{"lch":[51.3586018009433758,63.5282336791645363,113.757082680238781],"luv":[51.3586018009433758,-25.5929736747770171,58.1449582756467862],"rgb":[0.333333333333333315,0.533333333333333326,0.0666666666666666657],"xyz":[0.126511690147502937,0.195796634890071375,0.0364300625375399076],"hpluv":[113.757082680238781,156.961418592175818,51.3586018009433758],"hsluv":[113.757082680238781,96.6047658949560315,51.3586018009433758]},"#558822":{"lch":[51.4445144263052754,60.3557257759597761,114.985955444381503],"luv":[51.4445144263052754,-25.4940226670596211,54.707115096615226],"rgb":[0.333333333333333315,0.533333333333333326,0.133333333333333331],"xyz":[0.128387048285979938,0.196546778145462203,0.0463069487335191],"hpluv":[114.985955444381503,148.873956641671725,51.4445144263052754],"hsluv":[114.985955444381503,90.4397474873449454,51.4445144263052754]},"#558833":{"lch":[51.5854933484676366,55.3603846502330512,117.238150003797628],"luv":[51.5854933484676366,-25.3378965271949284,49.2215723864945502],"rgb":[0.333333333333333315,0.533333333333333326,0.2],"xyz":[0.131474799018437671,0.197781878438445302,0.0625691025911302434],"hpluv":[117.238150003797628,136.179218375975154,51.5854933484676366],"hsluv":[117.238150003797628,80.6386945119108844,51.5854933484676366]},"#558844":{"lch":[51.788002513998137,48.6519311113283521,121.094634958525788],"luv":[51.788002513998137,-25.1264429677671792,41.6614001762900799],"rgb":[0.333333333333333315,0.533333333333333326,0.266666666666666663],"xyz":[0.135932794262656559,0.199565076536132896,0.0860478775440168819],"hpluv":[121.094634958525788,119.20933058927335,51.788002513998137],"hsluv":[121.094634958525788,67.2069053574962112,51.788002513998137]},"#558855":{"lch":[52.0569745246593669,40.6501953066927229,127.715012949238655],"luv":[52.0569745246593669,-24.8671203906742271,32.1568764333225801],"rgb":[0.333333333333333315,0.533333333333333326,0.333333333333333315],"xyz":[0.141895208302018572,0.201950042151877729,0.117449924817990797],"hpluv":[127.715012949238655,99.0884509425644495,52.0569745246593669],"hsluv":[127.715012949238655,50.4536826414745647,52.0569745246593669]},"#558866":{"lch":[52.3961000606252156,32.296627997442414,139.535908661945541],"luv":[52.3961000606252156,-24.57168930668605,20.9595864611130054],"rgb":[0.333333333333333315,0.533333333333333326,0.4],"xyz":[0.149480204075526729,0.20498404046128102,0.157397569225134759],"hpluv":[139.535908661945541,78.2163498792783258,52.3961000606252156],"hsluv":[139.535908661945541,52.7717106037122505,52.3961000606252156]},"#558877":{"lch":[52.8079833364028417,25.6675371494617757,160.900154113051656],"luv":[52.8079833364028417,-24.2545338981935465,8.39881233868101873],"rgb":[0.333333333333333315,0.533333333333333326,0.466666666666666674],"xyz":[0.158794217095728663,0.208709645669361843,0.206451371131532668],"hpluv":[160.900154113051656,61.6770996704155934,52.8079833364028417],"hsluv":[160.900154113051656,55.3375604028527732,52.8079833364028417]},"#558888":{"lch":[53.2942460543653311,24.4817100724115,192.177050630060762],"luv":[53.2942460543653311,-23.9308828659180151,-5.16400748714853908],"rgb":[0.333333333333333315,0.533333333333333326,0.533333333333333326],"xyz":[0.169934634197672463,0.213165812510139441,0.265124234535104808],"hpluv":[192.177050630060762,58.2908991249141764,53.2942460543653311],"hsluv":[192.177050630060762,58.0627314448554515,53.2942460543653311]},"#558899":{"lch":[53.8556132197311399,30.5464388010487,219.367537814697016],"luv":[53.8556132197311399,-23.6152401502492637,-19.3753801529742589],"rgb":[0.333333333333333315,0.533333333333333326,0.6],"xyz":[0.18299159830674519,0.218388598153768598,0.333890912176222743],"hpluv":[219.367537814697016,71.9728892793730921,53.8556132197311399],"hsluv":[219.367537814697016,60.861676067449,53.8556132197311399]},"#5588aa":{"lch":[54.491995562986105,41.1624410064978434,235.49057853568965],"luv":[54.491995562986105,-23.3202411533326384,-33.9192114024460452],"rgb":[0.333333333333333315,0.533333333333333326,0.66666666666666663],"xyz":[0.198049291271997718,0.224411675339869693,0.413194761793221343],"hpluv":[235.49057853568965,95.8534470006528494,54.491995562986105],"hsluv":[235.49057853568965,63.6587877222075207,54.491995562986105]},"#5588bb":{"lch":[55.2025746803936102,53.7318677649407164,244.589939769846183],"luv":[55.2025746803936102,-23.0560079965343867,-48.5338449823709865],"rgb":[0.333333333333333315,0.533333333333333326,0.733333333333333282],"xyz":[0.215186883379334393,0.231266712182804451,0.503452746891863],"hpluv":[244.589939769846183,123.512789385845494,55.2025746803936102],"hsluv":[244.589939769846183,66.3923972476147,55.2025746803936102]},"#5588cc":{"lch":[55.9858924676882,67.0258402153595227,250.085698604565607],"luv":[55.9858924676882,-22.8299557310329213,-63.0179052166444436],"rgb":[0.333333333333333315,0.533333333333333326,0.8],"xyz":[0.234479258823056119,0.238983662360293253,0.60505925756213319],"hpluv":[250.085698604565607,151.915814945370954,55.9858924676882],"hsluv":[250.085698604565607,69.0160689386852653,55.9858924676882]},"#5588dd":{"lch":[56.8399439373084761,80.4799585659197589,253.656315856923044],"luv":[56.8399439373084761,-22.6469326722406379,-77.2278458285038596],"rgb":[0.333333333333333315,0.533333333333333326,0.866666666666666696],"xyz":[0.255997584373689691,0.247590992580546787,0.718389105462139188],"hpluv":[253.656315856923044,179.669115472769334,56.8399439373084761],"hsluv":[253.656315856923044,71.4978966707909791,56.8399439373084761]},"#5588ee":{"lch":[57.7622712344346212,93.8110643698785083,256.11666422549024],"luv":[57.7622712344346212,-22.5095618551803298,-91.0704969970917517],"rgb":[0.333333333333333315,0.533333333333333326,0.933333333333333348],"xyz":[0.2798097646398644,0.257115864687016804,0.843799921530662411],"hpluv":[256.11666422549024,206.086302990026525,57.7622712344346212],"hsluv":[256.11666422549024,85.5354286092897098,57.7622712344346212]},"#5588ff":{"lch":[58.7500561820581169,106.871298098059711,257.890974112124525],"luv":[58.7500561820581169,-22.4186692043534066,-104.493433421771144],"rgb":[0.333333333333333315,0.533333333333333326,1],"xyz":[0.305980813049695821,0.267584284050949528,0.981634109822444256],"hpluv":[257.890974112124525,230.829932612430156,58.7500561820581169],"hsluv":[257.890974112124525,99.9999999999988489,58.7500561820581169]},"#559900":{"lch":[56.7948235068901113,75.0667586450735769,116.650835958582277],"luv":[56.7948235068901113,-33.6713638042581849,67.0914116190665766],"rgb":[0.333333333333333315,0.6,0],"xyz":[0.151369625100333277,0.247131169595152217,0.0397251577236065259],"hpluv":[116.650835958582277,167.717444253109818,56.7948235068901113],"hsluv":[116.650835958582277,100.000000000002373,56.7948235068901113]},"#559911":{"lch":[56.8345345919718,73.5501475520203343,117.179331557793219],"luv":[56.8345345919718,-33.596019870913949,65.4288289193465431],"rgb":[0.333333333333333315,0.6,0.0666666666666666657],"xyz":[0.152381290599970409,0.24753583579500707,0.0450532626883621556],"hpluv":[117.179331557793219,164.214146288467191,56.8345345919718],"hsluv":[117.179331557793219,97.3073310640873501,56.8345345919718]},"#559922":{"lch":[56.9080340332033785,70.7973668034807559,118.203267177206257],"luv":[56.9080340332033785,-33.458907670269852,62.3920559351826256],"rgb":[0.333333333333333315,0.6,0.133333333333333331],"xyz":[0.154256648738447411,0.248285979050397898,0.0549301488843413552],"hpluv":[118.203267177206257,157.863907732936383,56.9080340332033785],"hsluv":[118.203267177206257,92.3971004449738444,56.9080340332033785]},"#559933":{"lch":[57.0287279601738675,66.4302504595433163,120.024921583566851],"luv":[57.0287279601738675,-33.2401456716524066,57.5158316626386181],"rgb":[0.333333333333333315,0.6,0.2],"xyz":[0.157344399470905172,0.249521079343381,0.0711923027419525],"hpluv":[120.024921583566851,147.812625691912672,57.0287279601738675],"hsluv":[120.024921583566851,84.5348632876942645,57.0287279601738675]},"#559944":{"lch":[57.2022813646338335,60.4886548808755862,122.993651447985457],"luv":[57.2022813646338335,-32.9388613723515604,50.7337045837447178],"rgb":[0.333333333333333315,0.6,0.266666666666666663],"xyz":[0.161802394715124032,0.251304277441068591,0.0946710776948391369],"hpluv":[122.993651447985457,134.183743453609452,57.2022813646338335],"hsluv":[122.993651447985457,73.6468983233054075,57.2022813646338335]},"#559955":{"lch":[57.4331244004656583,53.227141365646645,127.715012949239252],"luv":[57.4331244004656583,-32.5608701853658573,42.1060365117673499],"rgb":[0.333333333333333315,0.6,0.333333333333333315],"xyz":[0.167764808754486017,0.253689243056813396,0.126073124968813038],"hpluv":[127.715012949239252,117.600732304920115,57.4331244004656583],"hsluv":[127.715012949239252,59.8797334066369444,57.4331244004656583]},"#559966":{"lch":[57.7247064038159152,45.1954856350919556,135.286653522876605],"luv":[57.7247064038159152,-32.1175213159608433,31.7977475037242847],"rgb":[0.333333333333333315,0.6,0.4],"xyz":[0.175349804527994202,0.256723241366216715,0.166020769375957],"hpluv":[135.286653522876605,99.3510934192793371,57.7247064038159152],"hsluv":[135.286653522876605,61.4133131906424055,57.7247064038159152]},"#559977":{"lch":[58.0796295741768631,37.4448814339638858,147.624029462041108],"luv":[58.0796295741768631,-31.624170974627738,20.0507096076703846],"rgb":[0.333333333333333315,0.6,0.466666666666666674],"xyz":[0.184663817548196107,0.260448846574297566,0.215074571282354909],"hpluv":[147.624029462041108,81.8102930750829671,58.0796295741768631],"hsluv":[147.624029462041108,63.1432913181764448,58.0796295741768631]},"#559988":{"lch":[58.4997327930321092,31.9103493306277741,167.047427400638497],"luv":[58.4997327930321092,-31.0984204046450543,7.15252701768072],"rgb":[0.333333333333333315,0.6,0.533333333333333326],"xyz":[0.195804234650139963,0.264905013415075108,0.273747434685927105],"hpluv":[167.047427400638497,69.2176742514874093,58.4997327930321092],"hsluv":[167.047427400638497,65.0191416919107752,58.4997327930321092]},"#559999":{"lch":[58.9861545428406373,31.2617491707160085,192.177050630060933],"luv":[58.9861545428406373,-30.5583742056962677,-6.59414339527129822],"rgb":[0.333333333333333315,0.6,0.6],"xyz":[0.208861198759212663,0.270127799058704321,0.342514112327045],"hpluv":[192.177050630060933,67.2515837667627494,58.9861545428406373],"hsluv":[192.177050630060933,66.9883413382070216,58.9861545428406373]},"#5599aa":{"lch":[59.5393884281606915,36.5784761777074152,214.841775998693208],"luv":[59.5393884281606915,-30.0211576326403424,-20.8977274812183715],"rgb":[0.333333333333333315,0.6,0.66666666666666663],"xyz":[0.223918891724465219,0.276150876244805388,0.421817961944043585],"hpluv":[214.841775998693208,77.957978019602777,59.5393884281606915],"hsluv":[214.841775998693208,69.0007957908225,59.5393884281606915]},"#5599bb":{"lch":[60.1593377819191488,46.1560476826162613,230.269588518041076],"luv":[60.1593377819191488,-29.5018430010724479,-35.4967885479802],"rgb":[0.333333333333333315,0.6,0.733333333333333282],"xyz":[0.241056483831801838,0.283005913087740146,0.512075947042685331],"hpluv":[230.269588518041076,97.3564866347465312,60.1593377819191488],"hsluv":[230.269588518041076,71.0120526680823758,60.1593377819191488]},"#5599cc":{"lch":[60.8453721774886276,57.9560381686168284,239.960256152221575],"luv":[60.8453721774886276,-29.0128279276179057,-50.1712883614184761],"rgb":[0.333333333333333315,0.6,0.8],"xyz":[0.260348859275523592,0.290722863265228948,0.613682457712955487],"hpluv":[239.960256152221575,120.867757439807349,60.8453721774886276],"hsluv":[239.960256152221575,72.9851794318318667,60.8453721774886276]},"#5599dd":{"lch":[61.596386585270082,70.7674616346471623,246.194917025964173],"luv":[61.596386585270082,-28.563620381934669,-64.7468394355121],"rgb":[0.333333333333333315,0.6,0.866666666666666696],"xyz":[0.281867184826157136,0.29933019348548251,0.727012305612961485],"hpluv":[246.194917025964173,145.7866339341696,61.596386585270082],"hsluv":[246.194917025964173,74.8914393241793164,61.596386585270082]},"#5599ee":{"lch":[62.4108626997728209,83.9573424358243,250.401946751580681],"luv":[62.4108626997728209,-28.1609349474859023,-79.0935970339554757],"rgb":[0.333333333333333315,0.6,0.933333333333333348],"xyz":[0.305679365092331845,0.308855065591952527,0.852423121681484708],"hpluv":[250.401946751580681,170.701691169737018,62.4108626997728209],"hsluv":[250.401946751580681,83.3865807682554845,62.4108626997728209]},"#5599ff":{"lch":[63.2869312953637,97.1853240423286309,253.372761171991215],"luv":[63.2869312953637,-27.8089904224615658,-93.1216798650875859],"rgb":[0.333333333333333315,0.6,1],"xyz":[0.331850413502163266,0.319323484955885251,0.990257309973266553],"hpluv":[253.372761171991215,194.861470594675211,63.2869312953637],"hsluv":[253.372761171991215,99.9999999999986215,63.2869312953637]},"#440000":{"lch":[10.7708306123528814,36.2226426723970221,12.1770506300617765],"luv":[10.7708306123528814,35.407649887332731,7.64056094984030221],"rgb":[0.266666666666666663,0,0],"xyz":[0.0238384275584062923,0.0122916892098035059,0.00111742629180027137],"hpluv":[12.1770506300617765,426.746789183125145,10.7708306123528814],"hsluv":[12.1770506300617765,100.000000000002203,10.7708306123528814]},"#440011":{"lch":[11.0614468716721248,32.5827232355020158,4.73042674181564848],"luv":[11.0614468716721248,32.4717377839629151,2.68702414036008763],"rgb":[0.266666666666666663,0,0.0666666666666666657],"xyz":[0.024850093058043414,0.0126963554096583605,0.00644553125655590239],"hpluv":[4.73042674181564848,373.778888471265759,11.0614468716721248],"hsluv":[4.73042674181564848,99.999999999996831,11.0614468716721248]},"#440022":{"lch":[11.5842423793746683,28.6540476811609395,350.304317532446703],"luv":[11.5842423793746683,28.2447579299318896,-4.82577434136679706],"rgb":[0.266666666666666663,0,0.133333333333333331],"xyz":[0.0267254511965204326,0.0134464986650491784,0.0163224174525351],"hpluv":[350.304317532446703,313.875682434467763,11.5842423793746683],"hsluv":[350.304317532446703,99.9999999999974847,11.5842423793746683]},"#440033":{"lch":[12.4041921203750505,27.3919603751935874,328.642516788172941],"luv":[12.4041921203750505,23.3910134404871357,-14.2541216293086901],"rgb":[0.266666666666666663,0,0.2],"xyz":[0.0298132019289781731,0.0146815989580322912,0.0325845713101462417],"hpluv":[328.642516788172941,280.216663156604227,12.4041921203750505],"hsluv":[328.642516788172941,99.9999999999981668,12.4041921203750505]},"#440044":{"lch":[13.5105146335658439,30.7747615701782742,307.715012949243601],"luv":[13.5105146335658439,18.8259784531467211,-24.3447835271332202],"rgb":[0.266666666666666663,0,0.266666666666666663],"xyz":[0.0342711971731970502,0.0164647970557198695,0.0560633462630328802],"hpluv":[307.715012949243601,289.042783730483222,13.5105146335658439],"hsluv":[307.715012949243601,99.9999999999987779,13.5105146335658439]},"#440055":{"lch":[14.871657786523194,37.5926423334987589,293.358518425732086],"luv":[14.871657786523194,14.9048564440935021,-34.5116214049025416],"rgb":[0.266666666666666663,0,0.333333333333333315],"xyz":[0.0402336112125590423,0.0188497626714647,0.0874653935370067886],"hpluv":[293.358518425732086,320.761913781574776,14.871657786523194],"hsluv":[293.358518425732086,99.999999999999261,14.871657786523194]},"#440066":{"lch":[16.4463097679727497,46.0898544445027,284.618444278650202],"luv":[16.4463097679727497,11.6321972733021664,-44.597832562922008],"rgb":[0.266666666666666663,0,0.4],"xyz":[0.0478186069860672205,0.0218837609808680139,0.127413037944150764],"hpluv":[284.618444278650202,355.611827609674208,16.4463097679727497],"hsluv":[284.618444278650202,99.9999999999996447,16.4463097679727497]},"#440077":{"lch":[18.1919811936642475,55.3198462112611651,279.251207899416613],"luv":[18.1919811936642475,8.8934050845755,-54.6002997321373],"rgb":[0.266666666666666663,0,0.466666666666666674],"xyz":[0.0571326200062691331,0.0256093661889488303,0.176466839850548673],"hpluv":[279.251207899416613,385.869357778503058,18.1919811936642475],"hsluv":[279.251207899416613,100.000000000000028,18.1919811936642475]},"#440088":{"lch":[20.0701231572475791,64.8751824688912,275.807883046004235],"luv":[20.0701231572475791,6.56492582220058196,-64.5421648949036353],"rgb":[0.266666666666666663,0,0.533333333333333326],"xyz":[0.0682730371082129611,0.0300655330297264212,0.235139703254120841],"hpluv":[275.807883046004235,410.173767132845569,20.0701231572475791],"hsluv":[275.807883046004235,100.000000000000284,20.0701231572475791]},"#440099":{"lch":[22.0482755473713041,74.5764302852581267,273.495279820248],"luv":[22.0482755473713041,4.54664979059262,-74.4377050275844283],"rgb":[0.266666666666666663,0,0.6],"xyz":[0.0813300012172856746,0.0352883186733555787,0.303906380895238748],"hpluv":[273.495279820248,429.20649964036636,22.0482755473713041],"hsluv":[273.495279820248,100.000000000000384,22.0482755473713041]},"#4400aa":{"lch":[24.1003299188330615,84.3353594354864811,271.878389761009714],"luv":[24.1003299188330615,2.7643624238057809,-84.290041828810061],"rgb":[0.266666666666666663,0,0.66666666666666663],"xyz":[0.0963876941825382166,0.0413113958594566871,0.383210230512237349],"hpluv":[271.878389761009714,444.044033459252,24.1003299188330615],"hsluv":[271.878389761009714,100.000000000000398,24.1003299188330615]},"#4400bb":{"lch":[26.2058661049044161,94.1030045511215434,270.708980538846049],"luv":[26.2058661049044161,1.16440507249082947,-94.0958002589677704],"rgb":[0.266666666666666663,0,0.733333333333333282],"xyz":[0.113525286289874863,0.0481664327023914457,0.473468215610879095],"hpluv":[270.708980538846049,455.663559843794246,26.2058661049044161],"hsluv":[270.708980538846049,100.000000000000597,26.2058661049044161]},"#4400cc":{"lch":[28.3491756730399374,103.84959146554381,269.838851579513857],"luv":[28.3491756730399374,-0.292083914573144032,-103.849180711969012],"rgb":[0.266666666666666663,0,0.8],"xyz":[0.132817661733596604,0.0558833828798802473,0.575074726281149196],"hpluv":[269.838851579513857,464.840204698682,28.3491756730399374],"hsluv":[269.838851579513857,100.00000000000054,28.3491756730399374]},"#4400dd":{"lch":[30.5182871942398464,113.556060117994207,269.175711116484081],"luv":[30.5182871942398464,-1.63362415099869285,-113.544308803456943],"rgb":[0.266666666666666663,0,0.866666666666666696],"xyz":[0.154335987284230147,0.0644907131001337813,0.688404574181155193],"hpluv":[269.175711116484081,472.160320460814546,30.5182871942398464],"hsluv":[269.175711116484081,100.000000000000583,30.5182871942398464]},"#4400ee":{"lch":[32.7041215904695406,123.209994802530275,268.6598990566049],"luv":[32.7041215904695406,-2.88151723804096926,-123.176295112519156],"rgb":[0.266666666666666663,0,0.933333333333333348],"xyz":[0.178148167550404884,0.0740155852066038122,0.813815390249678416],"hpluv":[268.6598990566049,478.060407115886846,32.7041215904695406],"hsluv":[268.6598990566049,100.000000000000682,32.7041215904695406]},"#4400ff":{"lch":[34.8998090420324161,132.803387625161918,268.251574356178935],"luv":[34.8998090420324161,-4.05197057625710322,-132.741558297197031],"rgb":[0.266666666666666663,0,1],"xyz":[0.204319215960236278,0.0844840045705365084,0.951649578541460262],"hpluv":[268.251574356178935,482.864668803745815,34.8998090420324161],"hsluv":[268.251574356178935,100.000000000000824,34.8998090420324161]},"#441100":{"lch":[13.412021407860891,32.8203905090178907,19.8063713084711637],"luv":[13.412021407860891,30.878837797926078,11.1209446277644677],"rgb":[0.266666666666666663,0.0666666666666666657,0],"xyz":[0.025842827819334703,0.0163004897316603795,0.00178555971210972225],"hpluv":[19.8063713084711637,310.519467471828818,13.412021407860891],"hsluv":[19.8063713084711637,100.00000000000226,13.412021407860891]},"#441111":{"lch":[13.6534230745514442,29.3615880370140658,12.1770506300618155],"luv":[13.6534230745514442,28.7009658227648572,6.19333616848069],"rgb":[0.266666666666666663,0.0666666666666666657,0.0666666666666666657],"xyz":[0.0268544933189718248,0.0167051559315152323,0.00711366467686535414],"hpluv":[12.1770506300618155,272.883526996448495,13.6534230745514442],"hsluv":[12.1770506300618155,63.9450685777404857,13.6534230745514442]},"#441122":{"lch":[14.0908014406849,25.4444274015795777,356.558123350094036],"luv":[14.0908014406849,25.3985311285907684,-1.52758053936917793],"rgb":[0.266666666666666663,0.0666666666666666657,0.133333333333333331],"xyz":[0.0287298514574488434,0.0174552991869060536,0.0169905508728445502],"hpluv":[356.558123350094036,229.137575437366081,14.0908014406849],"hsluv":[356.558123350094036,68.4589860949294,14.0908014406849]},"#441133":{"lch":[14.7844111345271223,24.27011571268606,331.648240125609],"luv":[14.7844111345271223,21.358884082162259,-11.5254755854986932],"rgb":[0.266666666666666663,0.0666666666666666657,0.2],"xyz":[0.0318176021899065839,0.0186903994798891665,0.0332527047304557],"hpluv":[331.648240125609,208.308572243058649,14.7844111345271223],"hsluv":[331.648240125609,73.8494669394836762,14.7844111345271223]},"#441144":{"lch":[15.733846020816415,28.3218749128348932,307.715012949243942],"luv":[15.733846020816415,17.3254634530922189,-22.4043949865608347],"rgb":[0.266666666666666663,0.0666666666666666657,0.266666666666666663],"xyz":[0.0362755974341254644,0.0204735975775767395,0.0567314796833423354],"hpluv":[307.715012949243942,228.415952286272613,15.733846020816415],"hsluv":[307.715012949243942,79.0249627886423127,15.733846020816415]},"#441155":{"lch":[16.9210970319156715,36.0495621705848492,292.339268883647492],"luv":[16.9210970319156715,13.7020846665308547,-33.3440220801582683],"rgb":[0.266666666666666663,0.0666666666666666657,0.333333333333333315],"xyz":[0.0422380114734874565,0.0228585631933215724,0.0881335269573162439],"hpluv":[292.339268883647492,270.340308544513618,16.9210970319156715],"hsluv":[292.339268883647492,83.4150097743113292,16.9210970319156715]},"#441166":{"lch":[18.3175541838796221,45.305096001334384,283.521508601515677],"luv":[18.3175541838796221,10.592801308498224,-44.0493392019540195],"rgb":[0.266666666666666663,0.0666666666666666657,0.4],"xyz":[0.0498230072469956348,0.025892561502724884,0.128081171364460206],"hpluv":[283.521508601515677,313.84766122316239,18.3175541838796221],"hsluv":[283.521508601515677,86.9023417707839485,18.3175541838796221]},"#441177":{"lch":[19.8903241139664431,55.0653797371295326,278.304914930180701],"luv":[19.8903241139664431,7.95370312292975612,-54.4879312529533735],"rgb":[0.266666666666666663,0.0666666666666666657,0.466666666666666674],"xyz":[0.0591370202671975473,0.0296181667108057,0.177134973270858115],"hpluv":[278.304914930180701,351.298345972174616,19.8903241139664431],"hsluv":[278.304914930180701,89.590178354910762,19.8903241139664431]},"#441188":{"lch":[21.6068634066631873,64.9598488176177824,275.03199667533886],"luv":[21.6068634066631873,5.69776159856002,-64.7094851716016422],"rgb":[0.266666666666666663,0.0666666666666666657,0.533333333333333326],"xyz":[0.0702774373691413823,0.0340743335515832912,0.235807836674430282],"hpluv":[275.03199667533886,381.498298720028686,21.6068634066631873],"hsluv":[275.03199667533886,91.6417594317348545,21.6068634066631873]},"#441199":{"lch":[23.437698327746169,74.8628785955689153,272.864058339832695],"luv":[23.437698327746169,3.74063102269824954,-74.769367197849931],"rgb":[0.266666666666666663,0.0666666666666666657,0.6],"xyz":[0.0833344014782140818,0.0392971191952124557,0.304574514315548162],"hpluv":[272.864058339832695,405.313331644098525,23.437698327746169],"hsluv":[272.864058339832695,93.2101268622270283,23.437698327746169]},"#4411aa":{"lch":[25.3575925472355337,84.7316846097945273,271.361966301038933],"luv":[25.3575925472355337,2.01394993465393446,-84.7077468858332878],"rgb":[0.266666666666666663,0.0666666666666666657,0.66666666666666663],"xyz":[0.0983920944434666378,0.0453201963813135572,0.383878363932546762],"hpluv":[271.361966301038933,424.011024215555381,25.3575925472355337],"hsluv":[271.361966301038933,94.4180404350203872,25.3575925472355337]},"#4411bb":{"lch":[27.345710117802,94.5503656807627,270.282295464151048],"luv":[27.345710117802,0.465846378280231621,-94.5492180693092337],"rgb":[0.266666666666666663,0.0666666666666666657,0.733333333333333282],"xyz":[0.115529686550803284,0.0521752332242483158,0.474136349031188509],"hpluv":[270.282295464151048,438.746165283595928,27.345710117802],"hsluv":[270.282295464151048,95.3579307613671,27.345710117802]},"#4411cc":{"lch":[29.3852471201757481,104.311620276565549,269.482476007084244],"luv":[29.3852471201757481,-0.942181651797373232,-104.307365120864873],"rgb":[0.266666666666666663,0.0666666666666666657,0.8],"xyz":[0.134822061994525,0.0598921834017371174,0.575742859701458665],"hpluv":[269.482476007084244,450.445933463405538,29.3852471201757481],"hsluv":[269.482476007084244,96.097629526357963,29.3852471201757481]},"#4411dd":{"lch":[31.4628506304707116,114.011096091836691,268.874895171215144],"luv":[31.4628506304707116,-2.23866735310417164,-113.989115272223103],"rgb":[0.266666666666666663,0.0666666666666666657,0.866666666666666696],"xyz":[0.156340387545158555,0.0684995136219906514,0.689072707601464662],"hpluv":[268.874895171215144,459.820552568088715,31.4628506304707116],"hsluv":[268.874895171215144,96.6865382756064378,31.4628506304707116]},"#4411ee":{"lch":[33.5680052377948073,123.64577428881995,268.40345713539682],"luv":[33.5680052377948073,-3.44493495426529961,-123.597774747940718],"rgb":[0.266666666666666663,0.0666666666666666657,0.933333333333333348],"xyz":[0.180152567811333292,0.0780243857284606823,0.814483523669987886],"hpluv":[268.40345713539682,467.404695294247745,33.5680052377948073],"hsluv":[268.40345713539682,97.1606953094382817,33.5680052377948073]},"#4411ff":{"lch":[35.6924730299026081,133.21354134861582,268.030966648817412],"luv":[35.6924730299026081,-4.57713081064657,-133.134884505082312],"rgb":[0.266666666666666663,0.0666666666666666657,1],"xyz":[0.206323616221164685,0.0884928050923933784,0.952317711961769731],"hpluv":[268.030966648817412,473.599309181226886,35.6924730299026081],"hsluv":[268.030966648817412,99.999999999999531,35.6924730299026081]},"#99aa00":{"lch":[66.1528677227115907,74.3468963767982842,94.6234982733471384],"luv":[66.1528677227115907,-5.99293371363564553,74.1049643840839565],"rgb":[0.6,0.66666666666666663,0],"xyz":[0.275106719282890377,0.355217387920677,0.0540714229698005533],"hpluv":[94.6234982733471384,142.611154102436132,66.1528677227115907],"hsluv":[94.6234982733471384,100.000000000002217,66.1528677227115907]},"#99aa11":{"lch":[66.184052262829,73.0311244080130706,94.7508326892346275],"luv":[66.184052262829,-6.04863423256243316,72.7802112955118901],"rgb":[0.6,0.66666666666666663,0.0666666666666666657],"xyz":[0.276118384782527482,0.355622054120531828,0.059399527934556183],"hpluv":[94.7508326892346275,140.021252931940353,66.184052262829],"hsluv":[94.7508326892346275,98.1390759685954,66.184052262829]},"#99aa22":{"lch":[66.2417975711995695,70.6178785015108872,94.9968669170454376],"luv":[66.2417975711995695,-6.15090676716144458,70.3494926065273],"rgb":[0.6,0.66666666666666663,0.133333333333333331],"xyz":[0.277993742921004539,0.356372197375922628,0.0692764141305353826],"hpluv":[94.9968669170454376,135.276352185575888,66.2417975711995695],"hsluv":[94.9968669170454376,94.7283581868373403,66.2417975711995695]},"#99aa33":{"lch":[66.3366981156763273,66.7162457715457,95.432805623918],"luv":[66.3366981156763273,-6.31658226271684953,66.416550937003791],"rgb":[0.6,0.66666666666666663,0.2],"xyz":[0.281081493653462244,0.357607297668905755,0.0855385679881465189],"hpluv":[95.432805623918,127.619510800204154,66.3366981156763273],"hsluv":[95.432805623918,89.220332613511431,66.3366981156763273]},"#99aa44":{"lch":[66.4733277612737652,61.2361369282317085,96.1403021162386],"luv":[66.4733277612737652,-6.55002950525979344,60.8848222414533211],"rgb":[0.6,0.66666666666666663,0.266666666666666663],"xyz":[0.28553948889768116,0.359390495766593321,0.109017342941033157],"hpluv":[96.1403021162386,116.896010950353556,66.4733277612737652],"hsluv":[96.1403021162386,81.495914591183535,66.4733277612737652]},"#99aa55":{"lch":[66.6553605356915853,54.1785870660784568,97.2658616968833769],"luv":[66.6553605356915853,-6.85216039608048888,53.7435316515674231],"rgb":[0.6,0.66666666666666663,0.333333333333333315],"xyz":[0.291501902937043145,0.361775461382338182,0.140419390215007073],"hpluv":[97.2658616968833769,103.141138146130345,66.6553605356915853],"hsluv":[97.2658616968833769,71.5645731506566563,66.6553605356915853]},"#99aa66":{"lch":[66.8857782590742573,45.6318272725976186,99.1048787772859328],"luv":[66.8857782590742573,-7.22087825912546055,45.0568815765480224],"rgb":[0.6,0.66666666666666663,0.4],"xyz":[0.29908689871055133,0.3648094596917415,0.180367034622151035],"hpluv":[99.1048787772859328,86.5711928674077882,66.8857782590742573],"hsluv":[99.1048787772859328,59.5487850828384495,66.8857782590742573]},"#99aa77":{"lch":[67.16697930835997,35.7793898766585272,102.348317274789],"luv":[67.16697930835997,-7.65157453727816339,34.9516544250255947],"rgb":[0.6,0.66666666666666663,0.466666666666666674],"xyz":[0.308400911730753235,0.368535064899822296,0.229420836528548944],"hpluv":[102.348317274789,67.5952906506277742,67.16697930835997],"hsluv":[102.348317274789,45.6652750399669927,67.16697930835997]},"#99aa88":{"lch":[67.5008436530857523,24.9656779050761699,109.02352802026725],"luv":[67.5008436530857523,-8.13772239581267165,23.6021725158659379],"rgb":[0.6,0.66666666666666663,0.533333333333333326],"xyz":[0.319541328832697036,0.372991231740599893,0.288093699932121083],"hpluv":[109.02352802026725,46.9324783865521482,67.5008436530857523],"hsluv":[109.02352802026725,30.201776912068496,67.5008436530857523]},"#99aa99":{"lch":[67.8887769686822509,14.1753245834603039,127.715012949228395],"luv":[67.8887769686822509,-8.67153282620695443,11.213578621049983],"rgb":[0.6,0.66666666666666663,0.6],"xyz":[0.332598292941769791,0.378214017384229051,0.356860377573239],"hpluv":[127.715012949228395,26.4956363039349618,67.8887769686822509],"hsluv":[127.715012949228395,13.4910013502760879,67.8887769686822509]},"#99aaaa":{"lch":[68.3317447891119798,9.45739632834534838,192.177050630059227],"luv":[68.3317447891119798,-9.24460926466251642,-1.99487965930680966],"rgb":[0.6,0.66666666666666663,0.66666666666666663],"xyz":[0.347655985907022291,0.384237094570330173,0.436164227190237619],"hpluv":[192.177050630059227,17.5625836428344968,68.3317447891119798],"hsluv":[192.177050630059227,17.4938385380963979,68.3317447891119798]},"#99aabb":{"lch":[68.8303024285350205,18.6222464744475609,238.07162859052869],"luv":[68.8303024285350205,-9.84853629911531137,-15.8048852105949411],"rgb":[0.6,0.66666666666666663,0.733333333333333282],"xyz":[0.364793578014358966,0.391092131413264932,0.52642221228887931],"hpluv":[238.07162859052869,34.3314162301526622,68.8303024285350205],"hsluv":[238.07162859052869,21.6214176811966929,68.8303024285350205]},"#99aacc":{"lch":[69.3846237501618646,31.7868422871013969,250.758533684062627],"luv":[69.3846237501618646,-10.475354770609286,-30.0111693376804425],"rgb":[0.6,0.66666666666666663,0.8],"xyz":[0.38408595345808072,0.398809081590753733,0.628028722959149466],"hpluv":[250.758533684062627,58.1330991079694357,69.3846237501618646],"hsluv":[250.758533684062627,38.7837447081888556,69.3846237501618646]},"#99aadd":{"lch":[69.9945303682966,45.800012761152324,255.951164454091042],"luv":[69.9945303682966,-11.1178995355210173,-44.4300965432191859],"rgb":[0.6,0.66666666666666663,0.866666666666666696],"xyz":[0.405604279008714208,0.40741641181100724,0.741358570859155463],"hpluv":[255.951164454091042,83.0311057452140346,69.9945303682966],"hsluv":[255.951164454091042,58.1375555879370935,69.9945303682966]},"#99aaee":{"lch":[70.6595219654936,60.0690973767372896,258.700300270425373],"luv":[70.6595219654936,-11.7699993169592201,-58.9046990972259],"rgb":[0.6,0.66666666666666663,0.933333333333333348],"xyz":[0.429416459274889,0.416941283917477257,0.866769386927678687],"hpluv":[258.700300270425373,107.874733956899746,70.6595219654936],"hsluv":[258.700300270425373,78.5353046306170199,70.6595219654936]},"#99aaff":{"lch":[71.378807837336737,74.3523754541628676,260.378973553217],"luv":[71.378807837336737,-12.4265554124673407,-73.3065921746310636],"rgb":[0.6,0.66666666666666663,1],"xyz":[0.455587507684720339,0.42740970328141,1.00460357521946064],"hpluv":[260.378973553217,132.179737350703419,71.378807837336737],"hsluv":[260.378973553217,99.99999999999784,71.378807837336737]},"#442200":{"lch":[17.3350542344952459,28.221345162136295,35.6239292836567927],"luv":[17.3350542344952459,22.9399340369809366,16.4378754448194186],"rgb":[0.266666666666666663,0.133333333333333331,0],"xyz":[0.0295584515541026382,0.023731737201196354,0.00302410095703233277],"hpluv":[35.6239292836567927,206.581692971425213,17.3350542344952459],"hsluv":[35.6239292836567927,100.000000000002331,17.3350542344952459]},"#442211":{"lch":[17.5234603686317101,24.6335663377647,28.604455948929278],"luv":[17.5234603686317101,21.6269347365689519,11.7935696215031331],"rgb":[0.266666666666666663,0.133333333333333331,0.0666666666666666657],"xyz":[0.03057011705373976,0.0241364034010512069,0.00835220592178796337],"hpluv":[28.604455948929278,178.380241443971528,17.5234603686317101],"hsluv":[28.604455948929278,74.2089163354216765,17.5234603686317101]},"#442222":{"lch":[17.8672188947691239,19.9697171584766728,12.1770506300619807],"luv":[17.8672188947691239,19.520407715454283,4.21227800744330061],"rgb":[0.266666666666666663,0.133333333333333331,0.133333333333333331],"xyz":[0.0324454751922167786,0.0248865466564420282,0.0182290921177671594],"hpluv":[12.1770506300619807,141.825486578211439,17.8672188947691239],"hsluv":[12.1770506300619807,33.2341074785113406,17.8672188947691239]},"#442233":{"lch":[18.4184657925371695,17.8165085202964129,340.00784749027008],"luv":[18.4184657925371695,16.7428760354525643,-6.09131167444770583],"rgb":[0.266666666666666663,0.133333333333333331,0.2],"xyz":[0.0355332259246745191,0.026121646949425141,0.0344912459753783],"hpluv":[340.00784749027008,122.746318288975218,18.4184657925371695],"hsluv":[340.00784749027008,42.0888291674463915,18.4184657925371695]},"#442244":{"lch":[19.1844846541763658,22.4593922221866436,307.715012949244738],"luv":[19.1844846541763658,13.7391814744519145,-17.7668002578431441],"rgb":[0.266666666666666663,0.133333333333333331,0.266666666666666663],"xyz":[0.0399912211688934,0.0279048450471127141,0.0579700209282649412],"hpluv":[307.715012949244738,148.554970924606664,19.1844846541763658],"hsluv":[307.715012949244738,51.3954955066877517,19.1844846541763658]},"#442255":{"lch":[20.1595909359386596,31.6741463862469885,290.023039430837684],"luv":[20.1595909359386596,10.8451637140407797,-29.7596030436068872],"rgb":[0.266666666666666663,0.133333333333333331,0.333333333333333315],"xyz":[0.0459536352082553917,0.030289810662857547,0.0893720682022388496],"hpluv":[290.023039430837684,199.371249454156271,20.1595909359386596],"hsluv":[290.023039430837684,59.9942694866150177,20.1595909359386596]},"#442266":{"lch":[21.3287002071660226,42.3208084503031685,281.207510247893651],"luv":[21.3287002071660226,8.22559640374024781,-41.5137373852322256],"rgb":[0.266666666666666663,0.133333333333333331,0.4],"xyz":[0.0535386309817635631,0.0333238089722608585,0.129319712609382825],"hpluv":[281.207510247893651,251.784442505332123,21.3287002071660226],"hsluv":[281.207510247893651,67.3437899952651406,21.3287002071660226]},"#442277":{"lch":[22.6709734328231463,53.1747129073287113,276.391210783405313],"luv":[22.6709734328231463,5.91922219198187882,-52.8442324328661215],"rgb":[0.266666666666666663,0.133333333333333331,0.466666666666666674],"xyz":[0.0628526440019654825,0.0370494141803416749,0.178373514515780734],"hpluv":[276.391210783405313,297.628360698128461,22.6709734328231463],"hsluv":[276.391210783405313,73.3546851738733,22.6709734328231463]},"#442288":{"lch":[24.1630625686767715,63.8732961962266543,273.501787345358252],"luv":[24.1630625686767715,3.90136026108148481,-63.7540379512086801],"rgb":[0.266666666666666663,0.133333333333333331,0.533333333333333326],"xyz":[0.0739930611039093106,0.0415055810211192727,0.237046377919352902],"hpluv":[273.501787345358252,335.433687598002,24.1630625686767715],"hsluv":[273.501787345358252,78.1624380374364875,24.1630625686767715]},"#442299":{"lch":[25.7815797239733442,74.3421908873045112,271.638748742297366],"luv":[25.7815797239733442,2.1260128283266071,-74.3117851715204836],"rgb":[0.266666666666666663,0.133333333333333331,0.6],"xyz":[0.087050025212982024,0.0467283666647484303,0.305813055560470781],"hpluv":[271.638748742297366,365.902315457699217,25.7815797239733442],"hsluv":[271.638748742297366,81.974423252689391,25.7815797239733442]},"#4422aa":{"lch":[27.5046927955253,84.5952413694672885,270.370058715866037],"luv":[27.5046927955253,0.546375125789773097,-84.5934769150692176],"rgb":[0.266666666666666663,0.133333333333333331,0.66666666666666663],"xyz":[0.102107718178234566,0.0527514438508495317,0.385116905177469382],"hpluv":[270.370058715866037,390.281946032504152,27.5046927955253],"hsluv":[270.370058715866037,84.9950544768410765,27.5046927955253]},"#4422bb":{"lch":[29.3129652277151251,94.666070770970677,269.4687873747244],"luv":[29.3129652277151251,-0.87767531850734,-94.6620020982533],"rgb":[0.266666666666666663,0.133333333333333331,0.733333333333333282],"xyz":[0.119245310285571213,0.0596064806937842903,0.475374890276111128],"hpluv":[269.4687873747244,409.801860030343846,29.3129652277151251],"hsluv":[269.4687873747244,87.3984435941311517,29.3129652277151251]},"#4422cc":{"lch":[31.1896477872922873,104.586362634530047,268.806646243455475],"luv":[31.1896477872922873,-2.17816227055809408,-104.563678484665701],"rgb":[0.266666666666666663,0.133333333333333331,0.8],"xyz":[0.138537685729292953,0.0673234308712731,0.576981400946381284],"hpluv":[268.806646243455475,425.504262664631483,31.1896477872922873],"hsluv":[268.806646243455475,89.3235413687926325,31.1896477872922873]},"#4422dd":{"lch":[33.1206275871571,114.38069493574325,268.306702684458742],"luv":[33.1206275871571,-3.37987077956927,-114.330747603157818],"rgb":[0.266666666666666663,0.133333333333333331,0.866666666666666696],"xyz":[0.160056011279926497,0.0759307610915266329,0.690311248846387282],"hpluv":[268.306702684458742,438.22131720165,33.1206275871571],"hsluv":[268.306702684458742,90.877884626449017,33.1206275871571]},"#4422ee":{"lch":[35.0941942457284952,124.066768949832422,267.920557621455941],"luv":[35.0941942457284952,-4.50178121539094178,-123.985068147498538],"rgb":[0.266666666666666663,0.133333333333333331,0.933333333333333348],"xyz":[0.183868191546101234,0.0854556331979966499,0.815722064914910505],"hpluv":[267.920557621455941,448.600262181889605,35.0941942457284952],"hsluv":[267.920557621455941,93.3452679661598523,35.0941942457284952]},"#4422ff":{"lch":[37.1007304630435,133.656986375695226,267.616535799179189],"luv":[37.1007304630435,-5.55843332369103926,-133.54135623850334],"rgb":[0.266666666666666663,0.133333333333333331,1],"xyz":[0.210039239955932627,0.0959240525619293599,0.95355625320669235],"hpluv":[267.616535799179189,457.139270395610822,37.1007304630435],"hsluv":[267.616535799179189,99.999999999999531,37.1007304630435]},"#bbaa00":{"lch":[68.9787767407419,76.4078567958722346,76.7962953219783344],"luv":[68.9787767407419,17.4526104568015299,74.3879490803561652],"rgb":[0.733333333333333282,0.66666666666666663,0],"xyz":[0.348671553863065253,0.393149255751080451,0.0575197745907461769],"hpluv":[76.7962953219783344,140.560034871296551,68.9787767407419],"hsluv":[76.7962953219783344,100.000000000002245,68.9787767407419]},"#bbaa11":{"lch":[69.0079227935645747,75.1793224892161902,76.6760735029220797],"luv":[69.0079227935645747,17.325534429098667,73.1556996185784811],"rgb":[0.733333333333333282,0.66666666666666663,0.0666666666666666657],"xyz":[0.349683219362702358,0.393553921950935304,0.0628478795555018],"hpluv":[76.6760735029220797,138.241608756115426,69.0079227935645747],"hsluv":[76.6760735029220797,98.327691673723,69.0079227935645747]},"#bbaa22":{"lch":[69.0618990033698168,72.923722885125926,76.4446579961253718],"luv":[69.0618990033698168,17.0921879270318975,70.8923583420220496],"rgb":[0.733333333333333282,0.66666666666666663,0.133333333333333331],"xyz":[0.351558577501179415,0.394304065206326104,0.072724765751481],"hpluv":[76.4446579961253718,133.98915323660276,69.0618990033698168],"hsluv":[76.4446579961253718,95.2591470849694701,69.0618990033698168]},"#bbaa33":{"lch":[69.1506211183474448,69.2704933393546298,76.037430434814425],"luv":[69.1506211183474448,16.7141363744442764,67.2237970716773248],"rgb":[0.733333333333333282,0.66666666666666663,0.2],"xyz":[0.35464632823363712,0.395539165499309231,0.0889869196090921494],"hpluv":[76.037430434814425,127.113454601419591,69.1506211183474448],"hsluv":[76.037430434814425,90.2940538071316752,69.1506211183474448]},"#bbaa44":{"lch":[69.2783900215706439,64.125663575375043,75.3840533289293688],"luv":[69.2783900215706439,16.1813855024742246,62.0504914743025964],"rgb":[0.733333333333333282,0.66666666666666663,0.266666666666666663],"xyz":[0.359104323477856036,0.397322363596996797,0.112465694561978788],"hpluv":[75.3840533289293688,117.455514697890109,69.2783900215706439],"hsluv":[75.3840533289293688,83.3108908827552455,69.2783900215706439]},"#bbaa55":{"lch":[69.4486803199850158,57.4759826537307603,74.3632861967268752],"luv":[69.4486803199850158,15.4919003466967737,55.3487994961044762],"rgb":[0.733333333333333282,0.66666666666666663,0.333333333333333315],"xyz":[0.365066737517218,0.399707329212741658,0.143867741835952689],"hpluv":[74.3632861967268752,105.017514149864908,69.4486803199850158],"hsluv":[74.3632861967268752,74.2978448337225501,69.4486803199850158]},"#bbaa66":{"lch":[69.6643364030220766,49.3851711615203186,72.7428070953876],"luv":[69.6643364030220766,14.6506772614363818,47.1619845472377079],"rgb":[0.733333333333333282,0.66666666666666663,0.4],"xyz":[0.372651733290726206,0.402741327522145,0.183815386243096651],"hpluv":[72.7428070953876,89.9550160304510342,69.6643364030220766],"hsluv":[72.7428070953876,63.3404983901227254,69.6643364030220766]},"#bbaa77":{"lch":[69.927675331165787,39.998456174204172,70.017617272721],"luv":[69.927675331165787,13.6687200676018641,37.5904587366697314],"rgb":[0.733333333333333282,0.66666666666666663,0.466666666666666674],"xyz":[0.381965746310928111,0.406466932730225772,0.23286918814949456],"hpluv":[70.017617272721,72.5827577171275635,69.927675331165787],"hsluv":[70.017617272721,50.6076787731359303,69.927675331165787]},"#bbaa88":{"lch":[70.2405480333122,29.5797452752191568,64.8698942165899837],"luv":[70.2405480333122,12.5617839686329518,26.7798975739685403],"rgb":[0.733333333333333282,0.66666666666666663,0.533333333333333326],"xyz":[0.393106163412871912,0.41092309957100337,0.291542051553066728],"hpluv":[64.8698942165899837,53.4374671266607422,70.2405480333122],"hsluv":[64.8698942165899837,36.3337734813298923,70.2405480333122]},"#bbaa99":{"lch":[70.6043801086793,18.7359983934483161,52.7186742094296079],"luv":[70.6043801086793,11.3489394865138475,14.907689570511744],"rgb":[0.733333333333333282,0.66666666666666663,0.6],"xyz":[0.406163127521944611,0.416145885214632527,0.360308729194184663],"hpluv":[52.7186742094296079,33.6732102985619051,70.6043801086793],"hsluv":[52.7186742094296079,20.7983697782308,70.6043801086793]},"#bbaaaa":{"lch":[71.0202025015365876,10.2824341490213804,12.177050630064155],"luv":[71.0202025015365876,10.0510841141786607,2.16890759569567715],"rgb":[0.733333333333333282,0.66666666666666663,0.66666666666666663],"xyz":[0.421220820487197167,0.422168962400733649,0.439612578811183263],"hpluv":[12.177050630064155,18.3718697222702261,71.0202025015365876],"hsluv":[12.177050630064155,12.5020210815887047,71.0202025015365876]},"#bbaabb":{"lch":[71.4886774714552615,14.2047670368859897,307.715012949256788],"luv":[71.4886774714552615,8.68954378602241562,-11.236869464535129],"rgb":[0.733333333333333282,0.66666666666666663,0.733333333333333282],"xyz":[0.438358412594533842,0.429023999243668408,0.529870563909825],"hpluv":[307.715012949256788,25.2136766683934965,71.4886774714552615],"hsluv":[307.715012949256788,14.1682246563909,71.4886774714552615]},"#bbaacc":{"lch":[72.010122844957138,26.1519063744379174,286.174295641089373],"luv":[72.010122844957138,7.28488198445553525,-25.1167812724863673],"rgb":[0.733333333333333282,0.66666666666666663,0.8],"xyz":[0.45765078803825554,0.43674094942115721,0.631477074580095166],"hpluv":[286.174295641089373,46.0838895939184923,72.010122844957138],"hsluv":[286.174295641089373,33.6938059680335442,72.010122844957138]},"#bbaadd":{"lch":[72.5845361735228494,39.7272427931479,278.476565574149959],"luv":[72.5845361735228494,5.85598959517198825,-39.2932717625671799],"rgb":[0.733333333333333282,0.66666666666666663,0.866666666666666696],"xyz":[0.47916911358888914,0.445348279641410716,0.744806922480101163],"hpluv":[278.476565574149959,69.451822258827633,72.5845361735228494],"hsluv":[278.476565574149959,54.5084651382981775,72.5845361735228494]},"#bbaaee":{"lch":[73.2116196006959825,53.7916684525604,274.71268656160521],"luv":[73.2116196006959825,4.41947960396952144,-53.6098106221266093],"rgb":[0.733333333333333282,0.66666666666666663,0.933333333333333348],"xyz":[0.502981293855063849,0.454873151747880733,0.870217738548624387],"hpluv":[274.71268656160521,93.2340021292866084,73.2116196006959825],"hsluv":[274.71268656160521,76.5987468488048506,73.2116196006959825]},"#bbaaff":{"lch":[73.8908057188696574,67.9996555049746121,272.519622378571285],"luv":[73.8908057188696574,2.98936916429642707,-67.9339150998584671],"rgb":[0.733333333333333282,0.66666666666666663,1],"xyz":[0.52915234226489527,0.465341571111813457,1.00805192684040623],"hpluv":[272.519622378571285,116.776549017196231,73.8908057188696574],"hsluv":[272.519622378571285,99.9999999999974847,73.8908057188696574]},"#99bb00":{"lch":[71.0859361318702696,82.3913749493211327,101.26222245755217],"luv":[71.0859361318702696,-16.0909976916487061,80.8048170552163185],"rgb":[0.6,0.733333333333333282,0],"xyz":[0.309061217191489157,0.423126383737875533,0.0653895889393331653],"hpluv":[101.26222245755217,147.074503152999483,71.0859361318702696],"hsluv":[101.26222245755217,100.000000000002302,71.0859361318702696]},"#99bb11":{"lch":[71.1136894680978457,81.2167121813616291,101.442926834262664],"luv":[71.1136894680978457,-16.1127230849120338,79.6023523040561685],"rgb":[0.6,0.733333333333333282,0.0666666666666666657],"xyz":[0.310072882691126261,0.423531049937730386,0.0707176939040888],"hpluv":[101.442926834262664,144.921066000062638,71.1136894680978457],"hsluv":[101.442926834262664,98.4331733262411,71.1136894680978457]},"#99bb22":{"lch":[71.1650900316793837,79.0601549481488917,101.789083890819583],"luv":[71.1650900316793837,-16.1527448632600859,77.392486287802754],"rgb":[0.6,0.733333333333333282,0.133333333333333331],"xyz":[0.311948240829603318,0.424281193193121187,0.080594580100068],"hpluv":[101.789083890819583,140.971066242853169,71.1650900316793837],"hsluv":[101.789083890819583,95.5563304227746357,71.1650900316793837]},"#99bb33":{"lch":[71.2495884989653,75.5680342451554168,102.392870056849247],"luv":[71.2495884989653,-16.2179420391938294,73.807222923575253],"rgb":[0.6,0.733333333333333282,0.2],"xyz":[0.315035991562061,0.425516293486104313,0.0968567339576791309],"hpluv":[102.392870056849247,134.584514176993849,71.2495884989653],"hsluv":[102.392870056849247,90.8963478676004826,71.2495884989653]},"#99bb44":{"lch":[71.3712967033779648,70.652786516275043,103.347443045401377],"luv":[71.3712967033779648,-16.3105834167977157,68.7443169368786471],"rgb":[0.6,0.733333333333333282,0.266666666666666663],"xyz":[0.319493986806279939,0.427299491583791879,0.120335508910565769],"hpluv":[103.347443045401377,125.616021445862458,71.3712967033779648],"hsluv":[103.347443045401377,84.331672265085615,71.3712967033779648]},"#99bb55":{"lch":[71.5335490617920442,64.3081830776187502,104.804237401434776],"luv":[71.5335490617920442,-16.4318507493525949,62.1734404042074189],"rgb":[0.6,0.733333333333333282,0.333333333333333315],"xyz":[0.325456400845641924,0.42968445719953674,0.151737556184539685],"hpluv":[104.804237401434776,114.076396323867073,71.5335490617920442],"hsluv":[104.804237401434776,75.8403293762878263,71.5335490617920442]},"#99bb66":{"lch":[71.7390905918245494,56.6109161874071347,107.032342660002968],"luv":[71.7390905918245494,-16.5819871692753225,54.1279367156704367],"rgb":[0.6,0.733333333333333282,0.4],"xyz":[0.33304139661915011,0.432718455508940059,0.191685200591683647],"hpluv":[107.032342660002968,100.134479122606095,71.7390905918245494],"hsluv":[107.032342660002968,65.4891515707011,71.7390905918245494]},"#99bb77":{"lch":[71.9901757540345102,47.73532562127712,110.555323727161408],"luv":[71.9901757540345102,-16.7604289857032249,44.6961892378372099],"rgb":[0.6,0.733333333333333282,0.466666666666666674],"xyz":[0.342355409639352,0.436444060717020854,0.240739002498081556],"hpluv":[110.555323727161408,84.1406731320706,71.9901757540345102],"hsluv":[110.555323727161408,53.4218452589237,71.9901757540345102]},"#99bb88":{"lch":[72.2886271035685581,38.007890289968195,116.511646356383693],"luv":[72.2886271035685581,-16.9659512259030869,34.0111191126451473],"rgb":[0.6,0.733333333333333282,0.533333333333333326],"xyz":[0.353495826741295815,0.440900227557798452,0.299411865901653695],"hpluv":[116.511646356383693,66.7180145269523592,72.2886271035685581],"hsluv":[116.511646356383693,39.8440619041218724,72.2886271035685581]},"#99bb99":{"lch":[72.6358740128595315,28.1115962970155238,127.715012949234961],"luv":[72.6358740128595315,-17.1968288028560856,22.2380512970816788],"rgb":[0.6,0.733333333333333282,0.6],"xyz":[0.366552790850368515,0.44612301320142761,0.36817854354277163],"hpluv":[127.715012949234961,49.1104221429672094,72.6358740128595315],"hsluv":[127.715012949234961,25.0059581073328268,72.6358740128595315]},"#99bbaa":{"lch":[73.0329812495468929,19.8989345424194468,151.280397210880039],"luv":[73.0329812495468929,-17.4510036711552878,9.56190706882358654],"rgb":[0.6,0.733333333333333282,0.66666666666666663],"xyz":[0.381610483815621071,0.452146090387528732,0.447482393159770231],"hpluv":[151.280397210880039,34.5740392557976648,73.0329812495468929],"hsluv":[151.280397210880039,28.0327457551748473,73.0329812495468929]},"#99bbbb":{"lch":[73.4806726048520602,18.1342571597367979,192.177050630060279],"luv":[73.4806726048520602,-17.7262447217336288,-3.82511840348453136],"rgb":[0.6,0.733333333333333282,0.733333333333333282],"xyz":[0.398748075922957745,0.45900112723046349,0.537740378258412],"hpluv":[192.177050630060279,31.3159775944509384,73.4806726048520602],"hsluv":[192.177050630060279,31.1933976709334857,73.4806726048520602]},"#99bbcc":{"lch":[73.9793524714423,25.2831678504957686,224.541830016242],"luv":[73.9793524714423,-18.0202882726228886,-17.734367401402082],"rgb":[0.6,0.733333333333333282,0.8],"xyz":[0.418040451366679444,0.466718077407952292,0.639346888928682189],"hpluv":[224.541830016242,43.3670907410057183,73.9793524714423],"hsluv":[224.541830016242,34.4349286550277398,73.9793524714423]},"#99bbdd":{"lch":[74.5291269946933284,36.8692317824205347,240.185846709470951],"luv":[74.5291269946933284,-18.3309507682094441,-31.9893184697537762],"rgb":[0.6,0.733333333333333282,0.866666666666666696],"xyz":[0.439558776917313043,0.475325407628205798,0.752676736828688187],"hpluv":[240.185846709470951,62.7736499155611796,74.5291269946933284],"hsluv":[240.185846709470951,50.9371920612126914,74.5291269946933284]},"#99bbee":{"lch":[75.129825653719962,50.0395173115419851,248.109771752659725],"luv":[75.129825653719962,-18.6562099892881648,-46.4316607672792],"rgb":[0.6,0.733333333333333282,0.933333333333333348],"xyz":[0.463370957183487753,0.484850279734675815,0.87808755289721141],"hpluv":[248.109771752659725,84.5162185097210568,75.129825653719962],"hsluv":[248.109771752659725,74.708817528734329,75.129825653719962]},"#99bbff":{"lch":[75.7810236401202104,63.8171103470632701,252.684342225014],"luv":[75.7810236401202104,-18.9942553989637197,-60.9248868270445527],"rgb":[0.6,0.733333333333333282,1],"xyz":[0.489542005593319174,0.495318699098608539,1.01592174118899314],"hpluv":[252.684342225014,106.860202909060845,75.7810236401202104],"hsluv":[252.684342225014,99.9999999999971,75.7810236401202104]},"#443300":{"lch":[22.2907133772276609,26.4379209369795269,61.2454831359909],"luv":[22.2907133772276609,12.7181702882918319,23.1778300966244757],"rgb":[0.266666666666666663,0.2,0],"xyz":[0.0356761736431134499,0.0359671813792181508,0.00506334165336921362],"hpluv":[61.2454831359909,150.502134175174433,22.2907133772276609],"hsluv":[61.2454831359909,100.000000000002217,22.2907133772276609]},"#443311":{"lch":[22.433780901835803,22.5495741902090607,57.6291729330006959],"luv":[22.433780901835803,12.072970412625418,19.0453847841311372],"rgb":[0.266666666666666663,0.2,0.0666666666666666657],"xyz":[0.0366878391427505751,0.0363718475790730036,0.0103914466181248451],"hpluv":[57.6291729330006959,127.548453681164943,22.433780901835803],"hsluv":[57.6291729330006959,82.4396262904162853,22.433780901835803]},"#443322":{"lch":[22.6962080128251955,16.3034653251506185,47.6268315603120129],"luv":[22.6962080128251955,10.9878263584328284,12.044527949459761],"rgb":[0.266666666666666663,0.2,0.133333333333333331],"xyz":[0.0385631972812275903,0.037121990834463825,0.0202683328141040411],"hpluv":[47.6268315603120129,91.1519465603444,22.6962080128251955],"hsluv":[47.6268315603120129,53.1527363908354289,22.6962080128251955]},"#443333":{"lch":[23.1206934094119845,9.67437860897999613,12.1770506300626202],"luv":[23.1206934094119845,9.4567095438703852,2.04064844418642899],"rgb":[0.266666666666666663,0.2,0.2],"xyz":[0.0416509480136853308,0.0383570911274469378,0.0365304866717151844],"hpluv":[12.1770506300626202,53.0959690287213917,23.1206934094119845],"hsluv":[12.1770506300626202,12.4420312875371923,23.1206934094119845]},"#443344":{"lch":[23.7177668131648574,12.5269391023357528,307.715012949247694],"luv":[23.7177668131648574,7.66315882209806354,-9.90960141180911],"rgb":[0.266666666666666663,0.2,0.266666666666666663],"xyz":[0.0461089432579042113,0.0401402892251345109,0.0600092616246018229],"hpluv":[307.715012949247694,67.0209373905010608,23.7177668131648574],"hsluv":[307.715012949247694,23.1872031280306743,23.7177668131648574]},"#443355":{"lch":[24.4893027034144382,23.4497607107203301,284.299245815683662],"luv":[24.4893027034144382,5.79176863833153277,-22.7232632654309157],"rgb":[0.266666666666666663,0.2,0.333333333333333315],"xyz":[0.0520713572972662,0.0425252548408793438,0.0914113088985757383],"hpluv":[284.299245815683662,121.507006770462795,24.4893027034144382],"hsluv":[284.299245815683662,34.0172562824217479,24.4893027034144382]},"#443366":{"lch":[25.4301832846655458,35.8361728283181407,276.366541048647719],"luv":[25.4301832846655458,3.97382245046802396,-35.6151655634680964],"rgb":[0.266666666666666663,0.2,0.4],"xyz":[0.0596563530707743817,0.0455592531502826553,0.1313589533057197],"hpluv":[276.366541048647719,178.818092823782393,25.4301832846655458],"hsluv":[276.366541048647719,44.0521665839457555,25.4301832846655458]},"#443377":{"lch":[26.5300434901181958,48.2102429207408818,272.708376965990851],"luv":[26.5300434901181958,2.27805419202829862,-48.1563909733176416],"rgb":[0.266666666666666663,0.2,0.466666666666666674],"xyz":[0.0689703660909762872,0.0492848583583634717,0.180412755212117609],"hpluv":[272.708376965990851,230.590114205629249,26.5300434901181958],"hsluv":[272.708376965990851,52.8563285469964583,26.5300434901181958]},"#443388":{"lch":[27.7750487339787355,60.1896827108213373,270.691543734184165],"luv":[27.7750487339787355,0.726454682878815761,-60.1852986070773568],"rgb":[0.266666666666666663,0.2,0.533333333333333326],"xyz":[0.0801107831929201153,0.0537410251991410626,0.239085618615689777],"hpluv":[270.691543734184165,274.983446475144433,27.7750487339787355],"hsluv":[270.691543734184165,60.324174233719404,27.7750487339787355]},"#443399":{"lch":[29.1495234429961272,71.6961686956082218,269.452076389218803],"luv":[29.1495234429961272,-0.685625105343165342,-71.6928903298230438],"rgb":[0.266666666666666663,0.2,0.6],"xyz":[0.0931677473019928426,0.058963810842770227,0.307852296256807656],"hpluv":[269.452076389218803,312.107220431461315,29.1495234429961272],"hsluv":[269.452076389218803,66.5368599437509,29.1495234429961272]},"#4433aa":{"lch":[30.6372824460415245,82.7612763398458924,268.632918119842316],"luv":[30.6372824460415245,-1.97450330946598829,-82.7377193188284821],"rgb":[0.266666666666666663,0.2,0.66666666666666663],"xyz":[0.108225440267245371,0.0649868880288713285,0.387156145873806257],"hpluv":[268.632918119842316,342.78062465137026,30.6372824460415245],"hsluv":[268.632918119842316,71.6553387322885698,30.6372824460415245]},"#4433bb":{"lch":[32.2226022397772525,93.4505902198726375,268.062492493372758],"luv":[32.2226022397772525,-3.15951219941267691,-93.3971642776391633],"rgb":[0.266666666666666663,0.2,0.733333333333333282],"xyz":[0.125363032374582017,0.0718419248718060871,0.477414130972448],"hpluv":[268.062492493372758,368.010970109266054,32.2226022397772525],"hsluv":[268.062492493372758,75.8581190135042789,32.2226022397772525]},"#4433cc":{"lch":[33.8908458580626331,103.832026214070311,267.649236367220567],"luv":[33.8908458580626331,-4.25888397543315911,-103.744646006447894],"rgb":[0.266666666666666663,0.2,0.8],"xyz":[0.144655407818303772,0.0795588750492948887,0.579020641642718159],"hpluv":[267.649236367220567,388.765974213008576,33.8908458580626331],"hsluv":[267.649236367220567,79.3114034284002,33.8908458580626331]},"#4433dd":{"lch":[35.628800942302739,113.96424557581804,267.340372831489958],"luv":[35.628800942302739,-5.28823530427115518,-113.841485571087048],"rgb":[0.266666666666666663,0.2,0.866666666666666696],"xyz":[0.166173733368937315,0.0881662052695484227,0.692350489542724157],"hpluv":[267.340372831489958,405.888493424646128,35.628800942302739],"hsluv":[267.340372831489958,85.5677988674314,35.628800942302739]},"#4433ee":{"lch":[37.4248062251042484,123.894025620863303,267.103630337901393],"luv":[37.4248062251042484,-6.26032296204841732,-123.73575853791813],"rgb":[0.266666666666666663,0.2,0.933333333333333348],"xyz":[0.189985913635112053,0.0976910773760184536,0.81776130561124738],"hpluv":[267.103630337901393,420.078186698807599,37.4248062251042484],"hsluv":[267.103630337901393,92.6566888343209,37.4248062251042484]},"#4433ff":{"lch":[39.2687372084732473,133.657198385904053,266.918330051954797],"luv":[39.2687372084732473,-7.18532525912719411,-133.4639193988003],"rgb":[0.266666666666666663,0.2,1],"xyz":[0.216156962044943446,0.10815949673995115,0.955595493903029225],"hpluv":[266.918330051954797,431.901531941155895,39.2687372084732473],"hsluv":[266.918330051954797,99.99999999999946,39.2687372084732473]},"#bbbb00":{"lch":[73.6141498101152223,81.1522849996882485,85.8743202181747591],"luv":[73.6141498101152223,5.83845950082822274,80.9419900380996],"rgb":[0.733333333333333282,0.733333333333333282,0],"xyz":[0.382626051771664,0.461058251568279,0.0688379405602788],"hpluv":[85.8743202181747591,139.887458074797564,73.6141498101152223],"hsluv":[85.8743202181747591,100.000000000002331,73.6141498101152223]},"#bbbb11":{"lch":[73.6403599567658205,80.0195247391518478,85.8743202181747449],"luv":[73.6403599567658205,5.75696364516240155,79.8121651696532695],"rgb":[0.733333333333333282,0.733333333333333282,0.0666666666666666657],"xyz":[0.383637717271301082,0.461462917768133862,0.0741660455250344325],"hpluv":[85.8743202181747449,137.885751829634614,73.6403599567658205],"hsluv":[85.8743202181747449,98.5690595334933732,73.6403599567658205]},"#bbbb22":{"lch":[73.6889060807276763,77.9361940700347873,85.8743202181746597],"luv":[73.6889060807276763,5.60707948923862176,77.7342331648256817],"rgb":[0.733333333333333282,0.733333333333333282,0.133333333333333331],"xyz":[0.385513075409778139,0.462213061023524663,0.0840429317210136251],"hpluv":[85.8743202181746597,134.207383902194948,73.6889060807276763],"hsluv":[85.8743202181746597,95.9395400768792541,73.6889060807276763]},"#bbbb33":{"lch":[73.768722281637082,74.5519121144895536,85.8743202181745318],"luv":[73.768722281637082,5.36359906059884217,74.3587211095223],"rgb":[0.733333333333333282,0.733333333333333282,0.2],"xyz":[0.388600826142235845,0.46344816131650779,0.100305085578624775],"hpluv":[85.8743202181745318,128.24069174643796,73.768722281637082],"hsluv":[85.8743202181745318,91.6741883163470419,73.768722281637082]},"#bbbb44":{"lch":[73.8837085661944144,69.7637661309282464,85.8743202181743328],"luv":[73.8837085661944144,5.01911835485944824,69.5829829463408629],"rgb":[0.733333333333333282,0.733333333333333282,0.266666666666666663],"xyz":[0.39305882138645476,0.465231359414195356,0.123783860531511414],"hpluv":[85.8743202181743328,119.817583791868643,73.8837085661944144],"hsluv":[85.8743202181743328,85.652842249664161,73.8837085661944144]},"#bbbb55":{"lch":[74.0370403615741,63.5337415760462747,85.8743202181740628],"luv":[74.0370403615741,4.57090243520957262,63.3691026414263376],"rgb":[0.733333333333333282,0.733333333333333282,0.333333333333333315],"xyz":[0.399021235425816745,0.467616325029940216,0.155185907805485301],"hpluv":[85.8743202181740628,108.891682763750694,74.0370403615741],"hsluv":[85.8743202181740628,77.8423486010655239,74.0370403615741]},"#bbbb66":{"lch":[74.2313474843288361,55.8815023675101799,85.8743202181735512],"luv":[74.2313474843288361,4.02036601211528843,55.7366931561128212],"rgb":[0.733333333333333282,0.733333333333333282,0.4],"xyz":[0.40660623119932493,0.470650323339343535,0.195133552212629291],"hpluv":[85.8743202181735512,95.5256619678334857,74.2313474843288361],"hsluv":[85.8743202181735512,68.2875100330713138,74.2313474843288361]},"#bbbb77":{"lch":[74.468808451125966,46.8772880877030289,85.8743202181728549],"luv":[74.468808451125966,3.37256243628739805,46.7558120565865849],"rgb":[0.733333333333333282,0.733333333333333282,0.466666666666666674],"xyz":[0.415920244219526836,0.47437592854742433,0.2441873541190272],"hpluv":[85.8743202181728549,79.8780401793102328,74.468808451125966],"hsluv":[85.8743202181728549,57.1016453359253688,74.468808451125966]},"#bbbb88":{"lch":[74.7512063572608128,36.6333476151433146,85.874320218171647],"luv":[74.7512063572608128,2.63556739569020726,36.5384173438683746],"rgb":[0.733333333333333282,0.733333333333333282,0.533333333333333326],"xyz":[0.427060661321470636,0.478832095388201928,0.30286021752259934],"hpluv":[85.874320218171647,62.1867310089120195,74.7512063572608128],"hsluv":[85.874320218171647,44.4548295213588744,74.7512063572608128]},"#bbbb99":{"lch":[75.079965438194165,25.2938928616919938,85.8743202181691316],"luv":[75.079965438194165,1.81975614231989979,25.2283472245258693],"rgb":[0.733333333333333282,0.733333333333333282,0.6],"xyz":[0.440117625430543336,0.484054881031831086,0.371626895163717275],"hpluv":[85.8743202181691316,42.7494899193068392,75.079965438194165],"hsluv":[85.8743202181691316,30.559916169503893,75.079965438194165]},"#bbbbaa":{"lch":[75.4561775549407372,13.0242335847304886,85.8743202181613583],"luv":[75.4561775549407372,0.937021801842234714,12.9904830784884187],"rgb":[0.733333333333333282,0.733333333333333282,0.66666666666666663],"xyz":[0.455175318395795891,0.490077958217932208,0.450930744780715875],"hpluv":[85.8743202181613583,21.9026519543336242,75.4561775549407372],"hsluv":[85.8743202181613583,15.6573378741524305,75.4561775549407372]},"#bbbbbb":{"lch":[75.8806235332097856,3.97454725928322e-12,0],"luv":[75.8806235332097856,3.75098259432623098e-12,1.31421287976393485e-12],"rgb":[0.733333333333333282,0.733333333333333282,0.733333333333333282],"xyz":[0.472312910503132566,0.496932995060866967,0.541188729879357622],"hpluv":[0,6.64654731741433278e-12,75.8806235332097856],"hsluv":[0,6.51507609526145538e-12,75.8806235332097856]},"#bbbbcc":{"lch":[76.3537921403793,13.6026726964261613,265.874320218192793],"luv":[76.3537921403793,-0.97863730689855577,-13.5674232449512715],"rgb":[0.733333333333333282,0.733333333333333282,0.8],"xyz":[0.491605285946854265,0.504649945238355713,0.642795240549627778],"hpluv":[265.874320218192793,23.0577392955455913,76.3537921403793],"hsluv":[265.874320218192793,22.3559583930985184,76.3537921403793]},"#bbbbdd":{"lch":[76.875898300454,27.6153317552162676,265.874320218184891],"luv":[76.875898300454,-1.98677087225565296,-27.543770429115412],"rgb":[0.733333333333333282,0.733333333333333282,0.866666666666666696],"xyz":[0.513123611497487864,0.51325727545860933,0.756125088449633775],"hpluv":[265.874320218184891,48.094692651754464,76.875898300454],"hsluv":[265.874320218184891,46.4508399898830717,76.875898300454]},"#bbbbee":{"lch":[77.4469014383288794,41.8833893600305487,265.874320218182334],"luv":[77.4469014383288794,-3.01327895494808,-41.7748543291725554],"rgb":[0.733333333333333282,0.733333333333333282,0.933333333333333348],"xyz":[0.536935791763662573,0.522782147565079347,0.881535904518157],"hpluv":[265.874320218182334,75.1781465494946,77.4469014383288794],"hsluv":[265.874320218182334,72.306149300046286,77.4469014383288794]},"#bbbbff":{"lch":[78.0665243938900915,56.270213901735211,265.874320218181083],"luv":[78.0665243938900915,-4.04833166396953814,-56.1243973979724444],"rgb":[0.733333333333333282,0.733333333333333282,1],"xyz":[0.563106840173494,0.533250566929012,1.01937009280993873],"hpluv":[265.874320218181083,104.437018855576454,78.0665243938900915],"hsluv":[265.874320218181083,99.9999999999968,78.0665243938900915]},"#99cc00":{"lch":[76.0430979526319,91.0941172293808,106.263360497649074],"luv":[76.0430979526319,-25.5111694560196547,87.4489475453331124],"rgb":[0.6,0.8,0],"xyz":[0.347284960501106077,0.499573870357110428,0.0781308367092051204],"hpluv":[106.263360497649074,152.00919412554731,76.0430979526319],"hsluv":[106.263360497649074,100.000000000002444,76.0430979526319]},"#99cc11":{"lch":[76.0679435797449202,90.0431717383104342,106.457693210299354],"luv":[76.0679435797449202,-25.5098862277023812,86.3540299079582496],"rgb":[0.6,0.8,0.0666666666666666657],"xyz":[0.348296626000743181,0.499978536556965281,0.083458941673960757],"hpluv":[106.457693210299354,150.416675371578,76.0679435797449202],"hsluv":[106.457693210299354,98.6698238814781377,76.0679435797449202]},"#99cc22":{"lch":[76.1139653123302224,88.1120975577654235,106.827460928226145],"luv":[76.1139653123302224,-25.5076232207001254,84.3392132620406301],"rgb":[0.6,0.8,0.133333333333333331],"xyz":[0.350171984139220238,0.500728679812356137,0.0933358278699399496],"hpluv":[106.827460928226145,147.536178080277807,76.1139653123302224],"hsluv":[106.827460928226145,96.2239688839219554,76.1139653123302224]},"#99cc33":{"lch":[76.1896394090333615,84.9806694979027668,107.464683868975555],"luv":[76.1896394090333615,-25.5042190273125513,81.0632407452271195],"rgb":[0.6,0.8,0.2],"xyz":[0.353259734871677944,0.501963780105339263,0.109597981727551086],"hpluv":[107.464683868975555,142.843382477451314,76.1896394090333615],"hsluv":[107.464683868975555,92.2523981769505497,76.1896394090333615]},"#99cc44":{"lch":[76.2986765643697566,80.5641186971074461,108.452476349375274],"luv":[76.2986765643697566,-25.4999909675489818,76.4220366262018445],"rgb":[0.6,0.8,0.266666666666666663],"xyz":[0.357717730115896859,0.503746978203026829,0.133076756680437724],"hpluv":[108.452476349375274,136.177549938509202,76.2986765643697566],"hsluv":[108.452476349375274,86.6370393323296,76.2986765643697566]},"#99cc55":{"lch":[76.4441084930992645,74.8483308647658276,109.915235611542258],"luv":[76.4441084930992645,-25.4955548836031696,70.3722197633315858],"rgb":[0.6,0.8,0.333333333333333315],"xyz":[0.363680144155258844,0.506131943818771579,0.16447880395441164],"hpluv":[109.915235611542258,127.465568679298812,76.4441084930992645],"hsluv":[109.915235611542258,79.3378797119037245,76.4441084930992645]},"#99cc66":{"lch":[76.6284587708723279,67.8919791104198111,112.053749389120497],"luv":[76.6284587708723279,-25.4918238993559072,62.9244606001029396],"rgb":[0.6,0.8,0.4],"xyz":[0.37126513992876703,0.509165942128174898,0.204426448361555602],"hpluv":[112.053749389120497,116.726185086844026,76.6284587708723279],"hsluv":[112.053749389120497,70.3850130172815796,76.6284587708723279]},"#99cc77":{"lch":[76.8538330837355801,59.8379910057359723,115.212918879407354],"luv":[76.8538330837355801,-25.4899847789994141,54.1372869986012262],"rgb":[0.6,0.8,0.466666666666666674],"xyz":[0.380579152948968935,0.512891547336255749,0.253480250267953511],"hpluv":[115.212918879407354,104.093311342856381,76.8538330837355801],"hsluv":[115.212918879407354,59.8707914454029577,76.8538330837355801]},"#99cc88":{"lch":[77.1219726780439885,50.9458531819172649,120.024060615406185],"luv":[77.1219726780439885,-25.4914521322680621,44.1097021597488919],"rgb":[0.6,0.8,0.533333333333333326],"xyz":[0.391719570050912735,0.517347714177033291,0.31215311367152565],"hpluv":[120.024060615406185,89.8814395273866324,77.1219726780439885],"hsluv":[120.024060615406185,47.9401368886289845,77.1219726780439885]},"#99cc99":{"lch":[77.4342891130262103,41.681173098632776,127.715012949236964],"luv":[77.4342891130262103,-25.4978049096247332,32.9724450969124447],"rgb":[0.6,0.8,0.6],"xyz":[0.40477653415998549,0.522570499820662504,0.380919791312643585],"hpluv":[127.715012949236964,74.7648437658604337,77.4342891130262103],"hsluv":[127.715012949236964,34.7788947556027495,77.4342891130262103]},"#99ccaa":{"lch":[77.7918890721377352,32.9651751214879525,140.702563314804109],"luv":[77.7918890721377352,-25.5107121176450882,20.8783701002018844],"rgb":[0.6,0.8,0.66666666666666663],"xyz":[0.419834227125238,0.528593577006763571,0.460223640929642186],"hpluv":[140.702563314804109,60.2770963674260756,77.7918890721377352],"hsluv":[140.702563314804109,37.0803100068772622,77.7918890721377352]},"#99ccbb":{"lch":[78.1955939192693421,26.7536671513630395,162.617393192268167],"luv":[78.1955939192693421,-25.5318556358660231,7.9926875226808507],"rgb":[0.6,0.8,0.733333333333333282],"xyz":[0.436971819232574665,0.535448613849698329,0.550481626028283877],"hpluv":[162.617393192268167,50.006554278151107,78.1955939192693421],"hsluv":[162.617393192268167,39.5096284608221353,78.1955939192693421]},"#99cccc":{"lch":[78.6459566685868481,26.1512486118356264,192.177050630060364],"luv":[78.6459566685868481,-25.562857556775878,-5.51616873291790277],"rgb":[0.6,0.8,0.8],"xyz":[0.45626419467629642,0.543165564027187187,0.652088136698554],"hpluv":[192.177050630060364,50.1138147500145,78.6459566685868481],"hsluv":[192.177050630060364,42.0292910605185952,78.6459566685868481]},"#99ccdd":{"lch":[79.1432779300782,32.1756084020034763,217.269502822152816],"luv":[79.1432779300782,-25.6052179328205156,-19.4844191766612091],"rgb":[0.6,0.8,0.866666666666666696],"xyz":[0.477782520226929908,0.551772894247440693,0.76541798459856],"hpluv":[217.269502822152816,63.4104034428488532,79.1432779300782],"hsluv":[217.269502822152816,44.6030324020319142,79.1432779300782]},"#99ccee":{"lch":[79.6876217339600146,42.4043636674930298,232.761609201673764],"luv":[79.6876217339600146,-25.6602666815912279,-33.7591583407321],"rgb":[0.6,0.8,0.933333333333333348],"xyz":[0.501594700493104728,0.56129776635391071,0.890828800667083254],"hpluv":[232.761609201673764,86.2263034184575901,79.6876217339600146],"hsluv":[232.761609201673764,69.2463215394111842,79.6876217339600146]},"#99ccff":{"lch":[80.278831719152322,54.6396736698167231,241.908088190648726],"luv":[80.278831719152322,-25.7291311718118969,-48.2027566419988105],"rgb":[0.6,0.8,1],"xyz":[0.527765748902936,0.571766185717843434,1.02866298895886521],"hpluv":[241.908088190648726,115.039816302159181,80.278831719152322],"hsluv":[241.908088190648726,99.9999999999963762,80.278831719152322]},"#444400":{"lch":[27.7455139749470092,30.5866720374503593,85.8743202181747307],"luv":[27.7455139749470092,2.20054242411605072,30.5074108925390952],"rgb":[0.266666666666666663,0.266666666666666663,0],"xyz":[0.044508744126079483,0.0536323223451504599,0.00800753181435780864],"hpluv":[85.8743202181747307,139.887458074797593,27.7455139749470092],"hsluv":[85.8743202181747307,100.000000000002331,27.7455139749470092]},"#444411":{"lch":[27.8552611903384602,27.0161424908788135,85.8743202181744],"luv":[27.8552611903384602,1.9436625081132386,26.9461338811715763],"rgb":[0.266666666666666663,0.266666666666666663,0.0666666666666666657],"xyz":[0.0455204096257166,0.0540369885450053128,0.0133356367791134401],"hpluv":[85.8743202181744,123.070915058641674,27.8552611903384602],"hsluv":[85.8743202181744,87.9785198418851451,27.8552611903384602]},"#444422":{"lch":[28.0572627170229296,20.802612285424587,85.8743202181735086],"luv":[28.0572627170229296,1.49663326596887214,20.7487051828516122],"rgb":[0.266666666666666663,0.266666666666666663,0.133333333333333331],"xyz":[0.0473957677641936234,0.0547871318003961341,0.0232125229750926379],"hpluv":[85.8743202181735086,94.0831614915658463,28.0572627170229296],"hsluv":[85.8743202181735086,67.2563236092702539,28.0572627170229296]},"#444433":{"lch":[28.3858756417530103,11.5666907278610811,85.8743202181704],"luv":[28.3858756417530103,0.832159628943940688,11.5367172430437677],"rgb":[0.266666666666666663,0.266666666666666663,0.2],"xyz":[0.0504835184966513639,0.056022232093379247,0.0394746768327037811],"hpluv":[85.8743202181704,51.7066205750809758,28.3858756417530103],"hsluv":[85.8743202181704,36.9630139018145201,28.3858756417530103]},"#444444":{"lch":[28.8519023983998864,1.56211738287899238e-12,0],"luv":[28.8519023983998864,1.45745810878583046e-12,5.6216241338882039e-13],"rgb":[0.266666666666666663,0.266666666666666663,0.266666666666666663],"xyz":[0.0549415137408702445,0.05780543019106682,0.0629534517855904197],"hpluv":[0,6.87034486140541504e-12,28.8519023983998864],"hsluv":[0,1.96712204652458306e-12,28.8519023983998864]},"#444455":{"lch":[29.4604491554767947,12.996237632929807,265.874320218183527],"luv":[29.4604491554767947,-0.935007647451096213,-12.9625596743385874],"rgb":[0.266666666666666663,0.266666666666666663,0.333333333333333315],"xyz":[0.0609039277802322365,0.0601903958068116529,0.094355499059564335],"hpluv":[265.874320218183527,55.9780294653588157,29.4604491554767947],"hsluv":[265.874320218183527,10.903125265393685,29.4604491554767947]},"#444466":{"lch":[30.2117995944983235,26.5936989313503034,265.874320218180401],"luv":[30.2117995944983235,-1.9132700229980284,-26.52478502590359],"rgb":[0.266666666666666663,0.266666666666666663,0.4],"xyz":[0.0684889235537404079,0.0632243941162149714,0.134303143466708297],"hpluv":[265.874320218180401,111.69699235114156,30.2117995944983235],"hsluv":[265.874320218180401,21.7557908165695544,30.2117995944983235]},"#444477":{"lch":[31.1022350000615333,40.188693881548005,265.874320218179378],"luv":[31.1022350000615333,-2.89135495838767076,-40.084550420447286],"rgb":[0.266666666666666663,0.266666666666666663,0.466666666666666674],"xyz":[0.0778029365739423273,0.0669499993242957808,0.183356945373106206],"hpluv":[265.874320218179378,163.965176201048621,31.1022350000615333],"hsluv":[265.874320218179378,31.9363305989270607,31.1022350000615333]},"#444488":{"lch":[32.1249060438116132,53.4239352836437,265.874320218178923],"luv":[32.1249060438116132,-3.84355760936744639,-53.2854945186250859],"rgb":[0.266666666666666663,0.266666666666666663,0.533333333333333326],"xyz":[0.0889433536758861554,0.0714061661650733787,0.242029808776678373],"hpluv":[265.874320218178923,211.024721596932807,32.1249060438116132],"hsluv":[265.874320218178923,41.1023574005893479,32.1249060438116132]},"#444499":{"lch":[33.2707247827276404,66.1374776044503818,265.874320218178639],"luv":[33.2707247827276404,-4.75822688765494473,-65.9660914467787336],"rgb":[0.266666666666666663,0.266666666666666663,0.6],"xyz":[0.102000317784958869,0.0766289518087025362,0.31079648641779628],"hpluv":[265.874320218178639,252.246234683596128,33.2707247827276404],"hsluv":[265.874320218178639,50.2337582903708224,33.2707247827276404]},"#4444aa":{"lch":[34.5292085317775772,78.2936422443982707,265.874320218178468],"luv":[34.5292085317775772,-5.63279591471267516,-78.090755061511743],"rgb":[0.266666666666666663,0.266666666666666663,0.66666666666666663],"xyz":[0.117058010750211411,0.0826520289948036446,0.390100336034794881],"hpluv":[265.874320218178468,287.726060771882089,34.5292085317775772],"hsluv":[265.874320218178468,59.0260968416557645,34.5292085317775772]},"#4444bb":{"lch":[35.8892144652077647,89.9250819913669801,265.874320218178354],"luv":[35.8892144652077647,-6.46961387860876,-89.6920535355044],"rgb":[0.266666666666666663,0.266666666666666663,0.733333333333333282],"xyz":[0.134195602857548058,0.0895070658377384,0.480358321133436628],"hpluv":[265.874320218178354,317.948086985701252,35.8892144652077647],"hsluv":[265.874320218178354,67.4283717547759807,35.8892144652077647]},"#4444cc":{"lch":[37.3395287853000397,101.093816173965237,265.874320218178298],"luv":[37.3395287853000397,-7.27314272811459173,-100.831845482823283],"rgb":[0.266666666666666663,0.266666666666666663,0.8],"xyz":[0.153487978301269812,0.0972240160152272,0.581964831803706728],"hpluv":[265.874320218178298,343.55405942077391,37.3395287853000397],"hsluv":[265.874320218178298,75.5808548987534294,37.3395287853000397]},"#4444dd":{"lch":[38.8693012328948697,111.868746538356049,265.874320218178241],"luv":[38.8693012328948697,-8.04833956400084105,-111.578854100252741],"rgb":[0.266666666666666663,0.266666666666666663,0.866666666666666696],"xyz":[0.175006303851903355,0.105831346235480739,0.695294679703712726],"hpluv":[265.874320218178241,365.208910634554,38.8693012328948697],"hsluv":[265.874320218178241,83.6313726076760702,38.8693012328948697]},"#4444ee":{"lch":[40.4683363226646691,122.314460404417545,265.874320218178127],"luv":[40.4683363226646691,-8.799851087852959,-121.997499338533331],"rgb":[0.266666666666666663,0.266666666666666663,0.933333333333333348],"xyz":[0.198818484118078065,0.115356218341950756,0.820705495772236],"hpluv":[265.874320218178127,383.532154053589807,40.4683363226646691],"hsluv":[265.874320218178127,91.7249319236633625,40.4683363226646691]},"#4444ff":{"lch":[42.1272645151277203,132.4867415013303,265.874320218178127],"luv":[42.1272645151277203,-9.53169063144130568,-132.143420370999962],"rgb":[0.266666666666666663,0.266666666666666663,1],"xyz":[0.224989532527909486,0.12582463770588348,0.958539684064017794],"hpluv":[265.874320218178127,399.069452944254863,42.1272645151277203],"hsluv":[265.874320218178127,99.9999999999994458,42.1272645151277203]},"#bbcc00":{"lch":[78.3160688649495711,87.6272661942945916,93.3039767998847651],"luv":[78.3160688649495711,-5.05025027162958828,87.4816137990130471],"rgb":[0.733333333333333282,0.8,0],"xyz":[0.420849795081280953,0.537505738187513904,0.081579188330150737],"hpluv":[93.3039767998847651,164.876849582678972,78.3160688649495711],"hsluv":[93.3039767998847651,100.000000000002302,78.3160688649495711]},"#bbcc11":{"lch":[78.3397318378619332,86.5943586896397193,93.3754549086192],"luv":[78.3397318378619332,-5.09856582429126703,86.4441298377475249],"rgb":[0.733333333333333282,0.8,0.0666666666666666657],"xyz":[0.421861460580918057,0.537910404387368812,0.0869072932949063737],"hpluv":[93.3754549086192,163.146061131239662,78.3397318378619332],"hsluv":[93.3754549086192,98.7690619278293,78.3397318378619332]},"#bbcc22":{"lch":[78.3835653101130134,84.6929692802742125,93.5116381573829329],"luv":[78.3835653101130134,-5.18755307791811582,84.5339478468458481],"rgb":[0.733333333333333282,0.8,0.133333333333333331],"xyz":[0.423736818719395114,0.538660547642759613,0.0967841794908855663],"hpluv":[93.5116381573829329,159.95035726483485,78.3835653101130134],"hsluv":[93.5116381573829329,96.5043106774720343,78.3835653101130134]},"#bbcc33":{"lch":[78.4556479267243,81.5996211754986405,93.7468972468941644],"luv":[78.4556479267243,-5.33246113691784,81.4251990123951],"rgb":[0.733333333333333282,0.8,0.2],"xyz":[0.42682456945185282,0.53989564793574274,0.113046333348496703],"hpluv":[93.7468972468941644,154.72407311273281,78.4556479267243],"hsluv":[93.7468972468941644,92.8230308779070157,78.4556479267243]},"#bbcc44":{"lch":[78.5595248117047475,77.2136355312914873,94.1131309849175],"luv":[78.5595248117047475,-5.53822791342104281,77.0147618547127877],"rgb":[0.733333333333333282,0.8,0.266666666666666663],"xyz":[0.431282564696071735,0.541678846033430306,0.136525108301383341],"hpluv":[94.1131309849175,147.254264694427633,78.5595248117047475],"hsluv":[94.1131309849175,87.6101015600037556,78.5595248117047475]},"#bbcc55":{"lch":[78.6981007967589079,71.4912273296626921,94.6593076474989488],"luv":[78.6981007967589079,-5.80727962831129307,71.2549723768110823],"rgb":[0.733333333333333282,0.8,0.333333333333333315],"xyz":[0.43724497873543372,0.544063811649175,0.167927155575357256],"hpluv":[94.6593076474989488,137.398731222980217,78.6981007967589079],"hsluv":[94.6593076474989488,80.8199795518833639,78.6981007967589079]},"#bbcc66":{"lch":[78.8738041161037273,64.440755538040392,95.4673459440295176],"luv":[78.8738041161037273,-6.13981465986491681,64.1475927081912403],"rgb":[0.733333333333333282,0.8,0.4],"xyz":[0.444829974508941905,0.547097809958578374,0.207874799982501218],"hpluv":[95.4673459440295176,125.075317361724217,78.8738041161037273],"hsluv":[95.4673459440295176,72.4696955468646848,78.8738041161037273]},"#bbcc77":{"lch":[79.0886730863018244,56.1202023011108153,96.6860819525870454],"luv":[79.0886730863018244,-6.5340457953894715,55.7385266387743386],"rgb":[0.733333333333333282,0.8,0.466666666666666674],"xyz":[0.454143987529143811,0.550823415166659225,0.2569286018888991],"hpluv":[96.6860819525870454,110.256785788709905,79.0886730863018244],"hsluv":[96.6860819525870454,62.6323703341884,79.0886730863018244]},"#bbcc88":{"lch":[79.3444074564468451,46.6379090458556931,98.6154841820407],"luv":[79.3444074564468451,-6.9864776714612713,46.1116437563841473],"rgb":[0.733333333333333282,0.8,0.533333333333333326],"xyz":[0.465284404631087611,0.555279582007436767,0.315601465292471295],"hpluv":[98.6154841820407,92.9740665461038702,79.3444074564468451],"hsluv":[98.6154841820407,51.4293340198482483,79.3444074564468451]},"#bbcc99":{"lch":[79.6424016540563,36.1659853952486898,101.956116252097843],"luv":[79.6424016540563,-7.49223418159530397,35.3814206410296137],"rgb":[0.733333333333333282,0.8,0.6],"xyz":[0.478341368740160311,0.560502367651066,0.38436814293358923],"hpluv":[101.956116252097843,73.3480812445522616,79.6424016540563],"hsluv":[101.956116252097843,39.0205672484538439,79.6424016540563]},"#bbccaa":{"lch":[79.9837682606211899,25.01387761080953,108.762036830266098],"luv":[79.9837682606211899,-8.04542335958215382,23.6847046866463593],"rgb":[0.733333333333333282,0.8,0.66666666666666663],"xyz":[0.493399061705412867,0.566525444837167,0.463671992550587775],"hpluv":[108.762036830266098,51.7527419020047645,79.9837682606211899],"hsluv":[108.762036830266098,25.5939208298632828,79.9837682606211899]},"#bbccbb":{"lch":[80.3693561861161356,14.1229851396422195,127.715012949226079],"luv":[80.3693561861161356,-8.63951499109438892,11.1721748094634776],"rgb":[0.733333333333333282,0.8,0.733333333333333282],"xyz":[0.510536653812749486,0.573380481680101806,0.553929977649229577],"hpluv":[127.715012949226079,29.8960175179438359,80.3693561861161356],"hsluv":[127.715012949226079,11.3539027840963094,80.3693561861161356]},"#bbcccc":{"lch":[80.7997661027856537,9.48102181692548207,192.177050630058517],"luv":[80.7997661027856537,-9.26770319062559622,-1.99986306118322021],"rgb":[0.733333333333333282,0.8,0.8],"xyz":[0.52982902925647124,0.581097431857590663,0.655536488319499733],"hpluv":[192.177050630058517,20.5979746439306091,80.7997661027856537],"hsluv":[192.177050630058517,14.8313634310922779,80.7997661027856537]},"#bbccdd":{"lch":[81.2753646562375138,18.5534915910332536,237.666646406054781],"luv":[81.2753646562375138,-9.9232294288842251,-15.6767843616051454],"rgb":[0.733333333333333282,0.8,0.866666666666666696],"xyz":[0.551347354807104839,0.589704762077844169,0.76886633621950573],"hpluv":[237.666646406054781,41.5059211719903089,81.2753646562375138],"hsluv":[237.666646406054781,34.6395739712995834,81.2753646562375138]},"#bbccee":{"lch":[81.7962983545147466,31.5457627715974915,250.366116177600304],"luv":[81.7962983545147466,-10.5996485000078611,-29.7116576534899401],"rgb":[0.733333333333333282,0.8,0.933333333333333348],"xyz":[0.575159535073279549,0.599229634184314186,0.894277152288029],"hpluv":[250.366116177600304,72.9233806917818725,81.7962983545147466],"hsluv":[250.366116177600304,65.9765826509477478,81.7962983545147466]},"#bbccff":{"lch":[82.3625076456434329,45.3963265868234203,255.598148289110497],"luv":[82.3625076456434329,-11.2910283768039683,-43.9697526234994669],"rgb":[0.733333333333333282,0.8,1],"xyz":[0.601330583483111,0.60969805354824691,1.0321113405798108],"hpluv":[255.598148289110497,108.847942718229262,82.3625076456434329],"hsluv":[255.598148289110497,99.9999999999958789,82.3625076456434329]},"#99dd00":{"lch":[81.0072374435841738,100.100388992767378,110.059278565234735],"luv":[81.0072374435841738,-34.3336498242400623,94.028125400062109],"rgb":[0.6,0.866666666666666696,0],"xyz":[0.389918951048281226,0.584841851451462,0.0923421668915964389],"hpluv":[110.059278565234735,220.251619684458433,81.0072374435841738],"hsluv":[110.059278565234735,100.000000000002245,81.0072374435841738]},"#99dd11":{"lch":[81.0296061686003,99.1554895677318626,110.248463134872608],"luv":[81.0296061686003,-34.3169109383134483,93.0277417498051733],"rgb":[0.6,0.866666666666666696,0.0666666666666666657],"xyz":[0.39093061654791833,0.585246517651316855,0.0976702718563520755],"hpluv":[110.248463134872608,218.472914234601802,81.0296061686003],"hsluv":[110.248463134872608,98.8616036668283,81.0296061686003]},"#99dd22":{"lch":[81.0710445633976,97.4178442494470431,110.606606003803336],"luv":[81.0710445633976,-34.2861684396920552,91.184949591117217],"rgb":[0.6,0.866666666666666696,0.133333333333333331],"xyz":[0.392805974686395387,0.585996660906707656,0.107547158052331268],"hpluv":[110.606606003803336,215.192819254761019,81.0710445633976],"hsluv":[110.606606003803336,96.7659338960775131,81.0710445633976]},"#99dd33":{"lch":[81.1391953168863154,94.5961335984436857,111.218234049095599],"luv":[81.1391953168863154,-34.2363517117206513,88.1833358024404106],"rgb":[0.6,0.866666666666666696,0.2],"xyz":[0.395893725418853093,0.587231761199690783,0.123809311909942404],"hpluv":[111.218234049095599,209.840835623165077,81.1391953168863154],"hsluv":[111.218234049095599,93.3562182204636599,81.1391953168863154]},"#99dd44":{"lch":[81.2374208116197565,90.6080011845065343,112.152709421166279],"luv":[81.2374208116197565,-34.1661449288095653,83.9195115533635487],"rgb":[0.6,0.866666666666666696,0.266666666666666663],"xyz":[0.400351720663072,0.589014959297378349,0.147288086862829043],"hpluv":[112.152709421166279,202.22119953609328,81.2374208116197565],"hsluv":[112.152709421166279,88.5208972681928827,81.2374208116197565]},"#99dd55":{"lch":[81.3684846041416421,85.4315699411036604,113.506988965296216],"luv":[81.3684846041416421,-34.0753153837539244,78.3417259453695607],"rgb":[0.6,0.866666666666666696,0.333333333333333315],"xyz":[0.406314134702434,0.591399924913123098,0.178690134136802958],"hpluv":[113.506988965296216,192.231211336261111,81.3684846041416421],"hsluv":[113.506988965296216,82.2103932307851579,81.3684846041416421]},"#99dd66":{"lch":[81.5347071887187695,79.1067404569191837,115.426438711198742],"luv":[81.5347071887187695,-33.9646313013793,71.4442454385217758],"rgb":[0.6,0.866666666666666696,0.4],"xyz":[0.413899130475942179,0.594433923222526417,0.21863777854394692],"hpluv":[115.426438711198742,179.864586291563711,81.5347071887187695],"hsluv":[115.426438711198742,74.4308834765056,81.5347071887187695]},"#99dd77":{"lch":[81.7380487099571,71.7428264562351217,118.139890255285593],"luv":[81.7380487099571,-33.8357763954940651,63.2627329764024324],"rgb":[0.6,0.866666666666666696,0.466666666666666674],"xyz":[0.423213143496144084,0.598159528430607268,0.267691580450344802],"hpluv":[118.139890255285593,165.232292085670224,81.7380487099571],"hsluv":[118.139890255285593,65.2389156719347,81.7380487099571]},"#99dd88":{"lch":[81.9801580086414248,63.5371271244577898,122.023063704989269],"luv":[81.9801580086414248,-33.6912346765093815,53.8689820694792942],"rgb":[0.6,0.866666666666666696,0.533333333333333326],"xyz":[0.434353560598087884,0.60261569527138481,0.326364443853917],"hpluv":[122.023063704989269,148.614644251947254,81.9801580086414248],"hsluv":[122.023063704989269,54.7350081565161943,81.9801580086414248]},"#99dd99":{"lch":[82.2624042681600827,54.8181455895933,127.715012949238044],"luv":[82.2624042681600827,-33.5341421039979721,43.3646215160552],"rgb":[0.6,0.866666666666666696,0.6],"xyz":[0.447410524707160584,0.607838480915014,0.395131121495034932],"hpluv":[127.715012949238044,130.58293025992694,82.2624042681600827],"hsluv":[127.715012949238044,43.0558433261763724,82.2624042681600827]},"#99ddaa":{"lch":[82.5858991321833855,46.1452119261310898,136.311935434488049],"luv":[82.5858991321833855,-33.3681126370237138,31.8739649675161907],"rgb":[0.6,0.866666666666666696,0.66666666666666663],"xyz":[0.46246821767241314,0.61386155810111509,0.474434971112033477],"hpluv":[136.311935434488049,112.281381223378744,82.5858991321833855],"hsluv":[136.311935434488049,44.8180878493830122,82.5858991321833855]},"#99ddbb":{"lch":[82.9515135213076,38.5190468703316,149.523212457301526],"luv":[82.9515135213076,-33.1970516149390349,19.536446347119437],"rgb":[0.6,0.866666666666666696,0.733333333333333282],"xyz":[0.479605809779749814,0.620716594944049849,0.564692956210675279],"hpluv":[149.523212457301526,96.0401150933983416,82.9515135213076],"hsluv":[149.523212457301526,46.6955526922259807,82.9515135213076]},"#99ddcc":{"lch":[83.3598915829196585,33.6584009809739158,168.866706644517421],"luv":[83.3598915829196585,-33.0249710093605202,6.49917274942921],"rgb":[0.6,0.866666666666666696,0.8],"xyz":[0.498898185223471513,0.628433545121538706,0.666299466880945435],"hpluv":[168.866706644517421,86.2854242091523,83.3598915829196585],"hsluv":[168.866706644517421,48.6618587245334879,83.3598915829196585]},"#99dddd":{"lch":[83.811463234187741,33.6120761253887,192.17705063006062],"luv":[83.811463234187741,-32.85581988586,-7.08990557672323529],"rgb":[0.6,0.866666666666666696,0.866666666666666696],"xyz":[0.520416510774105112,0.637040875341792212,0.779629314780951432],"hpluv":[192.17705063006062,88.9162454401141,83.811463234187741],"hsluv":[192.17705063006062,50.6906605199231777,83.811463234187741]},"#99ddee":{"lch":[84.3064561843391402,38.9043785470910208,212.822778279485817],"luv":[84.3064561843391402,-32.6933403312743565,-21.0878203738277712],"rgb":[0.6,0.866666666666666696,0.933333333333333348],"xyz":[0.544228691040279822,0.646565747448262229,0.905040130849474656],"hpluv":[212.822778279485817,106.615167592423816,84.3064561843391402],"hsluv":[212.822778279485817,60.6575363344623781,84.3064561843391402]},"#99ddff":{"lch":[84.8449079615810575,48.055447414973429,227.378328123916845],"luv":[84.8449079615810575,-32.5409551013358396,-35.3611689193969099],"rgb":[0.6,0.866666666666666696,1],"xyz":[0.570399739450111243,0.657034166812195,1.04287431914125639],"hpluv":[227.378328123916845,137.001856984753886,84.8449079615810575],"hsluv":[227.378328123916845,99.999999999994813,84.8449079615810575]},"#445500":{"lch":[33.4053570608210535,38.7644311760376397,101.469350612776353],"luv":[33.4053570608210535,-7.7080633936099785,37.9903525006263791],"rgb":[0.266666666666666663,0.333333333333333315,0],"xyz":[0.0563220008404254485,0.0772588357738427239,0.0119452840524730177],"hpluv":[101.469350612776353,147.25044771073371,33.4053570608210535],"hsluv":[101.469350612776353,100.000000000002245,33.4053570608210535]},"#445511":{"lch":[33.4914653280992525,35.8039831739898275,102.524785035338],"luv":[33.4914653280992525,-7.76452054412734,34.9519303021045076],"rgb":[0.266666666666666663,0.333333333333333315,0.0666666666666666657],"xyz":[0.0573336663400625668,0.0776635019736975768,0.0172733890172286492],"hpluv":[102.524785035338,135.655223597469387,33.4914653280992525],"hsluv":[102.524785035338,91.5452633494882093,33.4914653280992525]},"#445522":{"lch":[33.6502992474903806,30.5965852313305291,104.897478349629381],"luv":[33.6502992474903806,-7.86608410210478493,29.5681543001366229],"rgb":[0.266666666666666663,0.333333333333333315,0.133333333333333331],"xyz":[0.0592090244785395889,0.0784136452290883912,0.0271502752132078452],"hpluv":[104.897478349629381,115.378092601865731,33.6502992474903806],"hsluv":[104.897478349629381,76.6558497494370243,33.6502992474903806]},"#445533":{"lch":[33.9096245159150911,22.79863905315759,110.61032755328398],"luv":[33.9096245159150911,-8.02535730567797501,21.3394372651287],"rgb":[0.266666666666666663,0.333333333333333315,0.2],"xyz":[0.0622967752109973294,0.0796487455220715,0.0434124290708189919],"hpluv":[110.61032755328398,85.3149751467856419,33.9096245159150911],"hsluv":[110.61032755328398,54.1403397653178331,33.9096245159150911]},"#445544":{"lch":[34.2793424585633204,13.4702363502677187,127.715012949235046],"luv":[34.2793424585633204,-8.24020614134090401,10.6558092175245633],"rgb":[0.266666666666666663,0.333333333333333315,0.266666666666666663],"xyz":[0.0667547704552162,0.081431943619759084,0.0668912040237056305],"hpluv":[127.715012949235046,49.8634197051089814,34.2793424585633204],"hsluv":[127.715012949235046,25.3893680776039865,34.2793424585633204]},"#445555":{"lch":[34.7654846399243738,8.70030094248716424,192.177050630060222],"luv":[34.7654846399243738,-8.50454817645784544,-1.83518304377262664],"rgb":[0.266666666666666663,0.333333333333333315,0.333333333333333315],"xyz":[0.0727171844945782,0.0838169092355039169,0.098293251297679532],"hpluv":[192.177050630060222,31.7559649298661668,34.7654846399243738],"hsluv":[192.177050630060222,31.6316627668381969,34.7654846399243738]},"#445566":{"lch":[35.3707740335649916,17.7582093223154978,240.254504050727519],"luv":[35.3707740335649916,-8.81070452432763318,-15.4183489427423961],"rgb":[0.266666666666666663,0.333333333333333315,0.4],"xyz":[0.0803021802680863733,0.0868509075449072354,0.138240895704823508],"hpluv":[240.254504050727519,63.7079944588343352,35.3707740335649916],"hsluv":[240.254504050727519,38.2083413049732812,35.3707740335649916]},"#445577":{"lch":[36.0950574442792913,30.8488081821149329,252.743594747999822],"luv":[36.0950574442792913,-9.15124765847803623,-29.4602042177260124],"rgb":[0.266666666666666663,0.333333333333333315,0.466666666666666674],"xyz":[0.0896161932882882928,0.0905765127529880448,0.187294697611221417],"hpluv":[252.743594747999822,108.450105614435046,36.0950574442792913],"hsluv":[252.743594747999822,44.7362415879762878,36.0950574442792913]},"#445588":{"lch":[36.935739068143242,44.5159055243500319,257.651563208142],"luv":[36.935739068143242,-9.5200062368872409,-43.4860359874579245],"rgb":[0.266666666666666663,0.333333333333333315,0.533333333333333326],"xyz":[0.100756610390232121,0.0950326795937656427,0.245967561014793584],"hpluv":[257.651563208142,152.935302017774575,36.935739068143242],"hsluv":[257.651563208142,50.9359550606187952,36.935739068143242]},"#445599":{"lch":[37.8882410462664865,58.0440865604997711,260.167284745959819],"luv":[37.8882410462664865,-9.91231220062277885,-57.1914508600732674],"rgb":[0.266666666666666663,0.333333333333333315,0.6],"xyz":[0.113813574499304834,0.1002554652373948,0.314734238655911491],"hpluv":[260.167284745959819,194.398479571090235,37.8882410462664865],"hsluv":[260.167284745959819,56.6374271499956805,37.8882410462664865]},"#4455aa":{"lch":[38.9464770503006932,71.1678309986128141,261.658277796723496],"luv":[38.9464770503006932,-10.3247958657557941,-70.4149043837850144],"rgb":[0.266666666666666663,0.333333333333333315,0.66666666666666663],"xyz":[0.128871267464557376,0.106278542423495909,0.394038088272910092],"hpluv":[261.658277796723496,231.875507383352442,38.9464770503006932],"hsluv":[261.658277796723496,61.7617646139185652,38.9464770503006932]},"#4455bb":{"lch":[40.1033117689144,83.7900384834958,262.625350778636744],"luv":[40.1033117689144,-10.7550224317659584,-83.0969316013408417],"rgb":[0.266666666666666663,0.333333333333333315,0.733333333333333282],"xyz":[0.146008859571894023,0.113133579266430667,0.484296073371551838],"hpluv":[262.625350778636744,265.125486140416797,40.1033117689144],"hsluv":[262.625350778636744,66.2950009745661,40.1033117689144]},"#4455cc":{"lch":[41.3509797710146,95.8983760986006075,263.292411358069444],"luv":[41.3509797710146,-11.2011488171863753,-95.24196975873555],"rgb":[0.266666666666666663,0.333333333333333315,0.8],"xyz":[0.165301235015615777,0.120850529443919469,0.585902584041821939],"hpluv":[263.292411358069444,294.28272942652967,41.3509797710146],"hsluv":[263.292411358069444,71.9850161095278906,41.3509797710146]},"#4455dd":{"lch":[42.6814446156526657,107.523441304607104,263.773623462042394],"luv":[42.6814446156526657,-11.661670844638417,-106.88917561239117],"rgb":[0.266666666666666663,0.333333333333333315,0.866666666666666696],"xyz":[0.186819560566249321,0.129457859664173,0.699232431941827937],"hpluv":[263.773623462042394,319.67109728436958,42.6814446156526657],"hsluv":[263.773623462042394,81.2732363837730247,42.6814446156526657]},"#4455ee":{"lch":[44.0866885883675,118.714724855013472,264.132858108854521],"luv":[44.0866885883675,-12.1352655753704255,-118.092850024109339],"rgb":[0.266666666666666663,0.333333333333333315,0.933333333333333348],"xyz":[0.21063174083242403,0.13898273177064302,0.82464324801035116],"hpluv":[264.132858108854521,341.693279595385377,44.0866885883675],"hsluv":[264.132858108854521,90.5684283810677186,44.0866885883675]},"#4455ff":{"lch":[45.5589321196955765,129.526958246416882,264.408404412275218],"luv":[45.5589321196955765,-12.6207071057408875,-128.910630534181024],"rgb":[0.266666666666666663,0.333333333333333315,1],"xyz":[0.236802789242255451,0.149451151134575744,0.962477436302133],"hpluv":[264.408404412275218,360.766296003954039,45.5589321196955765],"hsluv":[264.408404412275218,99.9999999999992752,45.5589321196955765]},"#bbdd00":{"lch":[83.0607051195576673,95.2176030862527796,99.223939245402],"luv":[83.0607051195576673,-15.2627740644400269,93.9863802119702427],"rgb":[0.733333333333333282,0.866666666666666696,0],"xyz":[0.463483785628456102,0.622773719281865423,0.0957905185125420555],"hpluv":[99.223939245402,239.164338292747971,83.0607051195576673],"hsluv":[99.223939245402,100.00000000000216,83.0607051195576673]},"#bbdd11":{"lch":[83.082156377860457,94.2793845093074765,99.3314951275217481],"luv":[83.082156377860457,-15.2870500651298524,93.0317604034238457],"rgb":[0.733333333333333282,0.866666666666666696,0.0666666666666666657],"xyz":[0.464495451128093206,0.623178385481720332,0.101118623477297692],"hpluv":[99.3314951275217481,237.15213625798873,83.082156377860457],"hsluv":[99.3314951275217481,98.93507934353417,83.082156377860457]},"#bbdd22":{"lch":[83.1218967422411765,92.5513012339483794,99.5354402549794486],"luv":[83.1218967422411765,-15.3318301742674485,91.2725497814347],"rgb":[0.733333333333333282,0.866666666666666696,0.133333333333333331],"xyz":[0.466370809266570263,0.623928528737111132,0.110995509673276885],"hpluv":[99.5354402549794486,233.433860498438293,83.1218967422411765],"hsluv":[99.5354402549794486,96.9737920722735538,83.1218967422411765]},"#bbdd33":{"lch":[83.1872593514570298,89.7372330308786,99.8847694763800718],"luv":[83.1872593514570298,-15.4049421957575614,88.4050832700449263],"rgb":[0.733333333333333282,0.866666666666666696,0.2],"xyz":[0.469458559999027969,0.625163629030094259,0.127257663530888021],"hpluv":[99.8847694763800718,227.34486653748678,83.1872593514570298],"hsluv":[99.8847694763800718,93.7802848880547373,83.1872593514570298]},"#bbdd44":{"lch":[83.2814760883348697,85.7419087717973,100.421139587323779],"luv":[83.2814760883348697,-15.5091702542505097,84.3275788686944452],"rgb":[0.733333333333333282,0.866666666666666696,0.266666666666666663],"xyz":[0.473916555243246884,0.626946827127781825,0.15073643848377466],"hpluv":[100.421139587323779,218.62539011377612,83.2814760883348697],"hsluv":[100.421139587323779,89.2463889616074511,83.2814760883348697]},"#bbdd55":{"lch":[83.4072088624658,80.5207963453540572,101.204557559233223],"luv":[83.4072088624658,-15.6461875861262687,78.9860459708531835],"rgb":[0.733333333333333282,0.866666666666666696,0.333333333333333315],"xyz":[0.479878969282608869,0.629331792743526575,0.182138485757748575],"hpluv":[101.204557559233223,207.093501481883948,83.4072088624658],"hsluv":[101.204557559233223,83.3201121340644448,83.4072088624658]},"#bbdd66":{"lch":[83.5666996797624364,74.0772937371066149,102.328486133362119],"luv":[83.5666996797624364,-15.816696660053216,72.3690372616455164],"rgb":[0.733333333333333282,0.866666666666666696,0.4],"xyz":[0.487463965056117055,0.632365791052929893,0.222086130164892537],"hpluv":[102.328486133362119,192.635686460640301,83.5666996797624364],"hsluv":[102.328486133362119,75.9999631534792854,83.5666996797624364]},"#bbdd77":{"lch":[83.7618505003220122,66.4632811318093388,103.948126608074304],"luv":[83.7618505003220122,-16.0205304866066705,64.5035684418593149],"rgb":[0.733333333333333282,0.866666666666666696,0.466666666666666674],"xyz":[0.49677797807631896,0.636091396261010744,0.271139932071290446],"hpluv":[103.948126608074304,175.207590447676779,83.7618505003220122],"hsluv":[103.948126608074304,67.3303268013154,83.7618505003220122]},"#bbdd88":{"lch":[83.9942706402616,57.7845188457632091,106.339844930601203],"luv":[83.9942706402616,-16.2567564016803381,55.4505950331715454],"rgb":[0.733333333333333282,0.866666666666666696,0.533333333333333326],"xyz":[0.50791839517826276,0.640547563101788286,0.329812795474862641],"hpluv":[106.339844930601203,154.850612540446264,83.9942706402616],"hsluv":[106.339844930601203,57.39610210967858,83.9942706402616]},"#bbdd99":{"lch":[84.2653073070245711,48.2191913160874819,110.040291826115165],"luv":[84.2653073070245711,-16.5237946272095257,45.2996095159246153],"rgb":[0.733333333333333282,0.866666666666666696,0.6],"xyz":[0.520975359287335515,0.6457703487454175,0.39857947311598052],"hpluv":[110.040291826115165,131.749772406416298,84.2653073070245711],"hsluv":[110.040291826115165,46.3161497548768466,84.2653073070245711]},"#bbddaa":{"lch":[84.5760668100806328,38.0784660246866622,116.212889721452115],"luv":[84.5760668100806328,-16.8195514991441222,34.1624393473425059],"rgb":[0.733333333333333282,0.866666666666666696,0.66666666666666663],"xyz":[0.536033052252588,0.651793425931518566,0.477883322732979121],"hpluv":[116.212889721452115,106.421399158629868,84.5760668100806328],"hsluv":[116.212889721452115,34.2356923349747433,84.5760668100806328]},"#bbddbb":{"lch":[84.9274305013996553,28.0212505683590543,127.715012949233824],"luv":[84.9274305013996553,-17.1415612181778,22.1665821095230058],"rgb":[0.733333333333333282,0.866666666666666696,0.733333333333333282],"xyz":[0.55317064435992469,0.658648462774453325,0.568141307831620868],"hpluv":[127.715012949233824,80.3800713533813109,84.9274305013996553],"hsluv":[127.715012949233824,21.3181094477761164,84.9274305013996553]},"#bbddcc":{"lch":[85.3200677868051,19.876471170849122,151.617083508593169],"luv":[85.3200677868051,-17.4871274790593141,9.44851727715650291],"rgb":[0.733333333333333282,0.866666666666666696,0.8],"xyz":[0.572463019803646445,0.666365412951942182,0.669747818501891],"hpluv":[151.617083508593169,58.7374284962306703,85.3200677868051],"hsluv":[151.617083508593169,24.0698773292010699,85.3200677868051]},"#bbdddd":{"lch":[85.7544476215988,18.2643967282432129,192.177050630059739],"luv":[85.7544476215988,-17.8534562098583329,-3.85256916996079246],"rgb":[0.733333333333333282,0.866666666666666696,0.866666666666666696],"xyz":[0.593981345354279933,0.674972743172195688,0.783077666401897],"hpluv":[192.177050630059739,55.8245506661868234,85.7544476215988],"hsluv":[192.177050630059739,26.9205986284609722,85.7544476215988]},"#bbddee":{"lch":[86.2308493598319359,25.3453434934362818,223.980933900351914],"luv":[86.2308493598319359,-18.2377721186398958,-17.6002870700688909],"rgb":[0.733333333333333282,0.866666666666666696,0.933333333333333348],"xyz":[0.617793525620454753,0.684497615278665705,0.908488482470420244],"hpluv":[223.980933900351914,80.4705047412629142,86.2308493598319359],"hsluv":[223.980933900351914,55.5800524236117397,86.2308493598319359]},"#bbddff":{"lch":[86.7493734858622076,36.743147173338194,239.520163688183],"luv":[86.7493734858622076,-18.6374141011141354,-31.6655279416778761],"rgb":[0.733333333333333282,0.866666666666666696,1],"xyz":[0.643964574030286063,0.694966034642598429,1.0463226707622022],"hpluv":[239.520163688183,121.752323062157373,86.7493734858622076],"hsluv":[239.520163688183,99.9999999999938325,86.7493734858622076]},"#99ee00":{"lch":[85.9664003491010646,109.204546268980621,112.979852313128234],"luv":[85.9664003491010646,-42.6342645949387915,100.538313136150634],"rgb":[0.6,0.933333333333333348,0],"xyz":[0.437097727388796042,0.679199404132492912,0.108068425671767623],"hpluv":[112.979852313128234,339.428934639809256,85.9664003491010646],"hsluv":[112.979852313128234,100.000000000002444,85.9664003491010646]},"#99ee11":{"lch":[85.9866468155118326,108.350123359521945,113.156064955940181],"luv":[85.9866468155118326,-42.6072764400583495,99.6211284135251702],"rgb":[0.6,0.933333333333333348,0.0666666666666666657],"xyz":[0.438109392888433147,0.67960407033234782,0.11339653063652326],"hpluv":[113.156064955940181,337.317445797395,85.9866468155118326],"hsluv":[113.156064955940181,99.0182391276988,85.9866468155118326]},"#99ee22":{"lch":[86.0241571186350455,106.77763062148405,113.488368653358094],"luv":[86.0241571186350455,-42.5576013317541353,97.9301433166804287],"rgb":[0.6,0.933333333333333348,0.133333333333333331],"xyz":[0.439984751026910204,0.680354213587738621,0.123273416832502453],"hpluv":[113.488368653358094,333.419677856626379,86.0241571186350455],"hsluv":[113.488368653358094,97.2091916908085238,86.0241571186350455]},"#99ee33":{"lch":[86.0858572784747906,104.220652596579697,114.05202992935186],"luv":[86.0858572784747906,-42.476800751453851,95.1717701084636],"rgb":[0.6,0.933333333333333348,0.2],"xyz":[0.443072501759367909,0.681589313880721748,0.139535570690113603],"hpluv":[114.05202992935186,327.048640581280779,86.0858572784747906],"hsluv":[114.05202992935186,94.2610252844147709,86.0858572784747906]},"#99ee44":{"lch":[86.1748066309051239,100.599171066032113,114.904029942881266],"luv":[86.1748066309051239,-42.3622716989010186,91.2448966007484756],"rgb":[0.6,0.933333333333333348,0.266666666666666663],"xyz":[0.447530497003586825,0.683372511978409314,0.163014345643000241],"hpluv":[114.904029942881266,317.953677409096201,86.1748066309051239],"hsluv":[114.904029942881266,90.0700159696272777,86.1748066309051239]},"#99ee55":{"lch":[86.2935317577421586,95.884762204841536,116.119599614249481],"luv":[86.2935317577421586,-42.2129155656731854,86.0927254913233355],"rgb":[0.6,0.933333333333333348,0.333333333333333315],"xyz":[0.45349291104294881,0.685757477594154063,0.194416392916974157],"hpluv":[116.119599614249481,305.984138616458665,86.2935317577421586],"hsluv":[116.119599614249481,84.5822993375075924,86.2935317577421586]},"#99ee66":{"lch":[86.4441689863048879,90.1009507230509143,117.805124209421223],"luv":[86.4441689863048879,-42.0290075887870955,79.6978283411745565],"rgb":[0.6,0.933333333333333348,0.4],"xyz":[0.461077906816457,0.688791475903557382,0.234364037324118091],"hpluv":[117.805124209421223,291.090686380889622,86.4441689863048879],"hsluv":[117.805124209421223,77.7887546135139587,86.4441689863048879]},"#99ee77":{"lch":[86.6285404402691199,83.3280712532883712,120.117619079442107],"luv":[86.6285404402691199,-41.8120893585486826,72.0785449510868261],"rgb":[0.6,0.933333333333333348,0.466666666666666674],"xyz":[0.470391919836658901,0.692517081111638233,0.283417839230516],"hpluv":[120.117619079442107,273.344218704658658,86.6285404402691199],"hsluv":[120.117619079442107,69.7211272565473,86.6285404402691199]},"#99ee88":{"lch":[86.8481992617441563,75.7142340291470646,123.296374810060541],"luv":[86.8481992617441563,-41.5648380664255939,63.2851441582641],"rgb":[0.6,0.933333333333333348,0.533333333333333326],"xyz":[0.481532336938602701,0.696973247952415775,0.342090702634088195],"hpluv":[123.296374810060541,252.981665625798911,86.8481992617441563],"hsluv":[123.296374810060541,60.4476982156994964,86.8481992617441563]},"#99ee99":{"lch":[87.1044587056640864,67.4980907628946483,127.715012949238613],"luv":[87.1044587056640864,-41.2908999939099388,53.3952604107227131],"rgb":[0.6,0.933333333333333348,0.6],"xyz":[0.4945893010476754,0.702196033596045,0.410857380275206074],"hpluv":[127.715012949238613,230.504268403717248,87.1044587056640864],"hsluv":[127.715012949238613,50.0680020008431583,87.1044587056640864]},"#99eeaa":{"lch":[87.3984122167822477,59.0554460818204134,133.961345837807],"luv":[87.3984122167822477,-40.9946911699179566,42.5085992218733182],"rgb":[0.6,0.933333333333333348,0.66666666666666663],"xyz":[0.509646994012928,0.708219110782146,0.490161229892204675],"hpluv":[133.961345837807,206.883546406693341,87.3984122167822477],"hsluv":[133.961345837807,51.4281429353489514,87.3984122167822477]},"#99eebb":{"lch":[87.7309483141988409,50.9899623050450046,142.923108379062683],"luv":[87.7309483141988409,-40.6811762109167603,30.7411476358032338],"rgb":[0.6,0.933333333333333348,0.733333333333333282],"xyz":[0.526784586120264575,0.715074147625080814,0.580419214990846477],"hpluv":[142.923108379062683,183.977628608542716,87.7309483141988409],"hsluv":[142.923108379062683,52.8887094272277523,87.7309483141988409]},"#99eecc":{"lch":[88.1027624984453581,44.2777406304916781,155.702396707036257],"luv":[88.1027624984453581,-40.3556400464907483,18.219237958245273],"rgb":[0.6,0.933333333333333348,0.8],"xyz":[0.546076961563986329,0.722791097802569671,0.682025725661116633],"hpluv":[155.702396707036257,165.259943503195302,88.1027624984453581],"hsluv":[155.702396707036257,54.4312604691531305,88.1027624984453581]},"#99eedd":{"lch":[88.514367527899708,40.3437945821223494,172.775068456479858],"luv":[88.514367527899708,-40.023467685182176,5.07383442158517628],"rgb":[0.6,0.933333333333333348,0.866666666666666696],"xyz":[0.567595287114619929,0.731398428022823177,0.795355573561122631],"hpluv":[172.775068456479858,156.503534311163719,88.514367527899708],"hsluv":[172.775068456479858,56.0368199873083199,88.514367527899708]},"#99eeee":{"lch":[88.9661029048661476,40.6035054290346196,192.177050630060762],"luv":[88.9661029048661476,-39.6899452486728,-8.56463071492281891],"rgb":[0.6,0.933333333333333348,0.933333333333333348],"xyz":[0.591407467380794638,0.740923300129293194,0.920766389629645854],"hpluv":[192.177050630060762,164.568757380081195,88.9661029048661476],"hsluv":[192.177050630060762,57.6866071933722324,88.9661029048661476]},"#99eeff":{"lch":[89.4581440962481338,45.3723498615675496,209.831659516829347],"luv":[89.4581440962481338,-39.3600920674606272,-22.5706287994267498],"rgb":[0.6,0.933333333333333348,1],"xyz":[0.617578515790626059,0.751391719493225918,1.05860057792142759],"hpluv":[209.831659516829347,193.255751503994162,89.4581440962481338],"hsluv":[209.831659516829347,99.9999999999917293,89.4581440962481338]},"#446600":{"lch":[39.1245088935371612,48.4489514943966242,110.29724752770484],"luv":[39.1245088935371612,-16.806485623906017,45.4405429311736668],"rgb":[0.266666666666666663,0.4,0],"xyz":[0.0713500585462719106,0.107314951185536064,0.0169546366210883669],"hpluv":[110.29724752770484,157.13568029472475,39.1245088935371612],"hsluv":[110.29724752770484,100.000000000002302,39.1245088935371612]},"#446611":{"lch":[39.1937103273453289,46.0071342898824156,111.370502443062165],"luv":[39.1937103273453289,-16.7648801632844737,42.843846683952],"rgb":[0.266666666666666663,0.4,0.0666666666666666657],"xyz":[0.0723617240459090288,0.107719617385390917,0.022282741585844],"hpluv":[111.370502443062165,148.9526145018813,39.1937103273453289],"hsluv":[111.370502443062165,93.861989786091,39.1937103273453289]},"#446622":{"lch":[39.3215343087732165,41.6757739733598,113.610346420587263],"luv":[39.3215343087732165,-16.6917518579939177,38.1871124358693947],"rgb":[0.266666666666666663,0.4,0.133333333333333331],"xyz":[0.0742370821843860579,0.108469760640781732,0.0321596277818231926],"hpluv":[113.610346420587263,134.490790122462073,39.3215343087732165],"hsluv":[113.610346420587263,82.9003633409612,39.3215343087732165]},"#446633":{"lch":[39.530716823468623,35.0960892868442684,118.194686061271412],"luv":[39.530716823468623,-16.5818151114938139,30.931842668007679],"rgb":[0.266666666666666663,0.4,0.2],"xyz":[0.0773248329168437915,0.109704860933764844,0.0484217816394343359],"hpluv":[118.194686061271412,112.658344014206364,39.530716823468623],"hsluv":[118.194686061271412,65.945418212904,39.530716823468623]},"#446644":{"lch":[39.8299759493166761,26.8801274380109838,127.715012949238059],"luv":[39.8299759493166761,-16.4434970133495142,21.2638815143349724],"rgb":[0.266666666666666663,0.4,0.266666666666666663],"xyz":[0.0817828281610626651,0.111488059031452424,0.0719005565923209744],"hpluv":[127.715012949238059,85.6368345314009,39.8299759493166761],"hsluv":[127.715012949238059,43.6044123282566858,39.8299759493166761]},"#446655":{"lch":[40.2252775564066809,18.9132283301115756,149.466153210293243],"luv":[40.2252775564066809,-16.2905155992652197,9.6088140463343148],"rgb":[0.266666666666666663,0.4,0.333333333333333315],"xyz":[0.087745242200424664,0.113873024647197257,0.10330260386629489],"hpluv":[149.466153210293243,59.6631175613842473,40.2252775564066809],"hsluv":[149.466153210293243,47.2452022491104273,40.2252775564066809]},"#446666":{"lch":[40.7202569602655728,16.510473073285187,192.177050630060847],"luv":[40.7202569602655728,-16.1389950297194247,-3.48260829470770128],"rgb":[0.266666666666666663,0.4,0.4],"xyz":[0.0953302379739328354,0.116907022956600576,0.143250248273438852],"hpluv":[192.177050630060847,51.4503500463259655,40.7202569602655728],"hsluv":[192.177050630060847,51.2489582821824214,40.7202569602655728]},"#446677":{"lch":[41.3164898648363632,23.6752994583974647,227.46784435344162],"luv":[41.3164898648363632,-16.0045942322577233,-17.4462823519963166],"rgb":[0.266666666666666663,0.4,0.466666666666666674],"xyz":[0.104644250994134755,0.120632628164681385,0.192304050179836761],"hpluv":[227.46784435344162,72.7128872034151783,41.3164898648363632],"hsluv":[227.46784435344162,55.4049423234493261,41.3164898648363632]},"#446688":{"lch":[42.0137303768536654,35.5424939627414,243.425378218088099],"luv":[42.0137303768536654,-15.9003964812462133,-31.7875175002811758],"rgb":[0.266666666666666663,0.4,0.533333333333333326],"xyz":[0.115784668096078583,0.125088795005458969,0.2509769135834089],"hpluv":[243.425378218088099,107.348500116853913,42.0137303768536654],"hsluv":[243.425378218088099,59.5313600522085409,42.0137303768536654]},"#446699":{"lch":[42.8101553746289696,48.7666560663799089,251.050944767945737],"luv":[42.8101553746289696,-15.8358652521886238,-46.1238779333558782],"rgb":[0.266666666666666663,0.4,0.6],"xyz":[0.128841632205151296,0.130311580649088155,0.319743591224526835],"hpluv":[251.050944767945737,144.549135771893674,42.8101553746289696],"hsluv":[251.050944767945737,63.4907652576423231,42.8101553746289696]},"#4466aa":{"lch":[43.7026231888915078,62.2363741998113298,255.27738032783347],"luv":[43.7026231888915078,-15.8167390859625119,-60.1929982491778404],"rgb":[0.266666666666666663,0.4,0.66666666666666663],"xyz":[0.143899325170403825,0.136334657835189249,0.399047440841525436],"hpluv":[255.27738032783347,180.707469421570693,43.7026231888915078],"hsluv":[255.27738032783347,67.1924648453768896,43.7026231888915078]},"#4466bb":{"lch":[44.6869405362706402,75.5191451090451409,257.888120209084718],"luv":[44.6869405362706402,-15.8455247037125186,-73.83807029483539],"rgb":[0.266666666666666663,0.4,0.733333333333333282],"xyz":[0.161036917277740499,0.143189694678124,0.489305425940167182],"hpluv":[257.888120209084718,214.444923406304468,44.6869405362706402],"hsluv":[257.888120209084718,70.5866413561882098,44.6869405362706402]},"#4466cc":{"lch":[45.7581261645645299,88.428992560325014,259.626929599743789],"luv":[45.7581261645645299,-15.9222446749778896,-86.983727499712],"rgb":[0.266666666666666663,0.4,0.8],"xyz":[0.180329292721462225,0.150906644855612809,0.590911936610437283],"hpluv":[259.626929599743789,245.225581131482073,45.7581261645645299],"hsluv":[259.626929599743789,73.6549291646736179,45.7581261645645299]},"#4466dd":{"lch":[46.9106590907269165,100.895184366494064,260.849495088158733],"luv":[46.9106590907269165,-16.0451985527389489,-99.6111933055317422],"rgb":[0.266666666666666663,0.4,0.866666666666666696],"xyz":[0.201847618272095769,0.159513975075866343,0.704241784510443281],"hpluv":[260.849495088158733,272.92180192815988,46.9106590907269165],"hsluv":[260.849495088158733,78.541907894368677,46.9106590907269165]},"#4466ee":{"lch":[48.1387014995781897,112.90717489320852,261.744736192665698],"luv":[48.1387014995781897,-16.2116120228509466,-111.737253313235385],"rgb":[0.266666666666666663,0.4,0.933333333333333348],"xyz":[0.225659798538270506,0.16903884718233636,0.829652600578966504],"hpluv":[261.744736192665698,297.623004098842955,48.1387014995781897],"hsluv":[261.744736192665698,89.2025828989012126,48.1387014995781897]},"#4466ff":{"lch":[49.4362898036433194,124.485902067172418,262.421323096893047],"luv":[49.4362898036433194,-16.4181334215125325,-123.398479360286501],"rgb":[0.266666666666666663,0.4,1],"xyz":[0.251830846948101872,0.179507266546269084,0.967486788870748349],"hpluv":[262.421323096893047,319.531462910383539,49.4362898036433194],"hsluv":[262.421323096893047,99.9999999999992,49.4362898036433194]},"#bbee00":{"lch":[87.830324097455545,103.474670767003104,103.901308289378321],"luv":[87.830324097455545,-24.8598111448325625,100.444000717728017],"rgb":[0.733333333333333282,0.933333333333333348,0],"xyz":[0.510662561968970863,0.717131271962896388,0.111516777292713254],"hpluv":[103.901308289378321,376.707439149390723,87.830324097455545],"hsluv":[103.901308289378321,100.000000000002331,87.830324097455545]},"#bbee11":{"lch":[87.8498503793181413,102.621932764236291,104.023160127863235],"luv":[87.8498503793181413,-24.8667401733108697,99.5635792718425705],"rgb":[0.733333333333333282,0.933333333333333348,0.0666666666666666657],"xyz":[0.511674227468608,0.717535938162751297,0.116844882257468891],"hpluv":[104.023160127863235,374.263941750279344,87.8498503793181413],"hsluv":[104.023160127863235,99.0733759990956315,87.8498503793181413]},"#bbee22":{"lch":[87.8860274909110757,101.050540606732312,104.253301125000391],"luv":[87.8860274909110757,-24.8795662288424886,97.9398741125263],"rgb":[0.733333333333333282,0.933333333333333348,0.133333333333333331],"xyz":[0.513549585607085,0.718286081418142097,0.126721768453448069],"hpluv":[104.253301125000391,369.744422226518111,87.8860274909110757],"hsluv":[104.253301125000391,97.3653524759520224,87.8860274909110757]},"#bbee33":{"lch":[87.9455377581158899,98.4895893313188111,104.644745385959411],"luv":[87.9455377581158899,-24.9006321902472507,95.2898615970127878],"rgb":[0.733333333333333282,0.933333333333333348,0.2],"xyz":[0.516637336339542785,0.719521181711125224,0.142983922311059219],"hpluv":[104.644745385959411,362.331462484484575,87.9455377581158899],"hsluv":[104.644745385959411,94.5802259429375454,87.9455377581158899]},"#bbee44":{"lch":[88.03133674845418,94.8494424101932339,105.239087045756335],"luv":[88.03133674845418,-24.9309340294718567,91.5142898892991781],"rgb":[0.733333333333333282,0.933333333333333348,0.266666666666666663],"xyz":[0.521095331583761645,0.72130437980881279,0.166462697263945858],"hpluv":[105.239087045756335,351.690778632920285,88.03133674845418],"hsluv":[105.239087045756335,90.61757843521751,88.03133674845418]},"#bbee55":{"lch":[88.145869167101,90.085489786575252,106.092871571181419],"luv":[88.145869167101,-24.9712578135623886,86.5553681367929499],"rgb":[0.733333333333333282,0.933333333333333348,0.333333333333333315],"xyz":[0.527057745623123575,0.72368934542455754,0.197864744537919773],"hpluv":[106.092871571181419,337.573290416608245,88.145869167101],"hsluv":[106.092871571181419,85.422816781051381,88.145869167101]},"#bbee66":{"lch":[88.2912067804751786,84.1961694748550116,107.288882190620882],"luv":[88.2912067804751786,-25.0222263001917433,80.3920589686599527],"rgb":[0.733333333333333282,0.933333333333333348,0.4],"xyz":[0.534642741396631815,0.726723343733960858,0.237812388945063735],"hpluv":[107.288882190620882,319.804166688067426,88.2912067804751786],"hsluv":[107.288882190620882,78.9823949944632631,88.2912067804751786]},"#bbee77":{"lch":[88.4691221578712828,77.2243366572971439,108.954917992899311],"luv":[88.4691221578712828,-25.0843250262429365,73.0368044901840534],"rgb":[0.733333333333333282,0.933333333333333348,0.466666666666666674],"xyz":[0.543956754416833665,0.730448948942041709,0.286866190851461644],"hpluv":[108.954917992899311,298.285685525501094,88.4691221578712828],"hsluv":[108.954917992899311,71.3203705839542,88.4691221578712828]},"#bbee88":{"lch":[88.6811325759484106,69.2626802552854599,111.298371855766348],"luv":[88.6811325759484106,-25.1579200624522237,64.5321465184381822],"rgb":[0.733333333333333282,0.933333333333333348,0.533333333333333326],"xyz":[0.555097171518777577,0.734905115782819252,0.34553905425503384],"hpluv":[111.298371855766348,273.019747479454225,88.6811325759484106],"hsluv":[111.298371855766348,62.4946759175697224,88.6811325759484106]},"#bbee99":{"lch":[88.9285282571087095,60.4678989000773228,114.674688063820184],"luv":[88.9285282571087095,-25.2432722518949362,54.9467378777547069],"rgb":[0.733333333333333282,0.933333333333333348,0.6],"xyz":[0.568154135627850221,0.740127901426448465,0.414305731896151719],"hpluv":[114.674688063820184,244.173567048365754,88.9285282571087095],"hsluv":[114.674688063820184,52.5925914284503548,88.9285282571087095]},"#bbeeaa":{"lch":[89.2123917942545148,51.0969811847688931,119.731125412935242],"luv":[89.2123917942545148,-25.3405499711494109,44.3706886734509212],"rgb":[0.733333333333333282,0.933333333333333348,0.66666666666666663],"xyz":[0.583211828593102832,0.746150978612549531,0.493609581513150319],"hpluv":[119.731125412935242,212.254877789093854,89.2123917942545148],"hsluv":[119.731125412935242,41.7253920748903084,89.2123917942545148]},"#bbeebb":{"lch":[89.5336124490095,41.6027669810227181,127.715012949236097],"luv":[89.5336124490095,-25.4498411950279184,32.9104208971130276],"rgb":[0.733333333333333282,0.933333333333333348,0.733333333333333282],"xyz":[0.600349420700439396,0.75300601545548429,0.583867566611792066],"hpluv":[127.715012949236097,178.587287563939924,89.5336124490095],"hsluv":[127.715012949236097,37.7465419844818,89.5336124490095]},"#bbeecc":{"lch":[89.8928974614309766,32.8889663829363883,141.03222670888556],"luv":[89.8928974614309766,-25.571165029146556,20.6833176446640721],"rgb":[0.733333333333333282,0.933333333333333348,0.8],"xyz":[0.61964179614416115,0.760722965632973147,0.685474077282062222],"hpluv":[141.03222670888556,146.627829178506289,89.8928974614309766],"hsluv":[141.03222670888556,37.1073685119788,89.8928974614309766]},"#bbeedd":{"lch":[90.290781675649967,26.8655052232995288,163.094148385079734],"luv":[90.290781675649967,-25.704482585445124,7.81248653872008436],"rgb":[0.733333333333333282,0.933333333333333348,0.866666666666666696],"xyz":[0.641160121694794749,0.769330295853226653,0.79880392518206822],"hpluv":[163.094148385079734,125.084011642469022,90.290781675649967],"hsluv":[163.094148385079734,36.2429238875219255,90.290781675649967]},"#bbeeee":{"lch":[90.7276363011350782,26.4447005646089579,192.177050630060336],"luv":[90.7276363011350782,-25.8497069757021904,-5.5780675168164624],"rgb":[0.733333333333333282,0.933333333333333348,0.933333333333333348],"xyz":[0.664972301960969459,0.77885516795969667,0.924214741250591443],"hpluv":[192.177050630060336,129.38009793870765,90.7276363011350782],"hsluv":[192.177050630060336,36.8413123479339077,90.7276363011350782]},"#bbeeff":{"lch":[91.2036773284388289,32.4261328799939648,216.675663960190036],"luv":[91.2036773284388289,-26.0067122100428421,-19.367627980086624],"rgb":[0.733333333333333282,0.933333333333333348,1],"xyz":[0.69114335037080088,0.789323587323629394,1.06204892954237318],"hpluv":[216.675663960190036,167.86892764061264,91.2036773284388289],"hsluv":[216.675663960190036,99.9999999999901235,91.2036773284388289]},"#99ff00":{"lch":[90.9122626200542214,118.290950299530564,115.261698016578393],"luv":[90.9122626200542214,-50.4810643573161286,106.978554225220847],"rgb":[0.6,1,0],"xyz":[0.48895009981846993,0.782904148991842,0.125352549814991721],"hpluv":[115.261698016578393,591.369219456757151,90.9122626200542214],"hsluv":[115.261698016578393,100.000000000002402,90.9122626200542214]},"#99ff11":{"lch":[90.9306796583728,117.513960007406183,115.422432906031332],"luv":[90.9306796583728,-50.4474247175277384,106.134764031356056],"rgb":[0.6,1,0.0666666666666666657],"xyz":[0.489961765318107034,0.783308815191696928,0.130680654779747357],"hpluv":[115.422432906031332,588.764777700997797,90.9306796583728],"hsluv":[115.422432906031332,99.9999999999905356,90.9306796583728]},"#99ff22":{"lch":[90.9648031673089577,116.082935310332914,115.724658723180369],"luv":[90.9648031673089577,-50.3854319755480944,104.577990585497247],"rgb":[0.6,1,0.133333333333333331],"xyz":[0.491837123456584091,0.784058958447087728,0.140557540975726536],"hpluv":[115.724658723180369,583.951350293788323,90.9648031673089577],"hsluv":[115.724658723180369,99.9999999999904219,90.9648031673089577]},"#99ff33":{"lch":[91.0209396556716399,113.753057852482627,116.234667099004312],"luv":[91.0209396556716399,-50.2843870930134642,102.035477092383132],"rgb":[0.6,1,0.2],"xyz":[0.494924874189041797,0.785294058740070855,0.156819694833337686],"hpluv":[116.234667099004312,576.067585056329449,91.0209396556716399],"hsluv":[116.234667099004312,99.9999999999905924,91.0209396556716399]},"#99ff44":{"lch":[91.1018839706868562,110.446777199697451,116.999375135233294],"luv":[91.1018839706868562,-50.1407143313292565,98.409345898362929],"rgb":[0.6,1,0.266666666666666663],"xyz":[0.499382869433260712,0.787077256837758421,0.180298469786224325],"hpluv":[116.999375135233294,564.777897061868,91.1018839706868562],"hsluv":[116.999375135233294,99.9999999999903793,91.1018839706868562]},"#99ff55":{"lch":[91.2099533036034558,106.130684884370282,118.07780645420398],"luv":[91.2099533036034558,-49.9525457622324112,93.6400846106921279],"rgb":[0.6,1,0.333333333333333315],"xyz":[0.505345283472622642,0.789462222453503171,0.21170051706019824],"hpluv":[118.07780645420398,549.854625367801646,91.2099533036034558],"hsluv":[118.07780645420398,99.9999999999901377,91.2099533036034558]},"#99ff66":{"lch":[91.3471179899501351,100.815112661229691,119.549522622156928],"luv":[91.3471179899501351,-49.7195590247369807,87.7020660605103899],"rgb":[0.6,1,0.4],"xyz":[0.512930279246130882,0.792496220762906489,0.251648161467342202],"hpluv":[119.549522622156928,531.176396965461777,91.3471179899501351],"hsluv":[119.549522622156928,99.9999999999902656,91.3471179899501351]},"#99ff77":{"lch":[91.5150716426202,94.5571579863012488,121.52621580287979],"luv":[91.5150716426202,-49.442863485124569,80.6006164792656534],"rgb":[0.6,1,0.466666666666666674],"xyz":[0.522244292266332732,0.79622182597098734,0.300701963373740111],"hpluv":[121.52621580287979,508.746904082031563,91.5150716426202],"hsluv":[121.52621580287979,99.9999999999900098,91.5150716426202]},"#99ff88":{"lch":[91.7152730008064481,87.4673964918121385,124.169042550433474],"luv":[91.7152730008064481,-49.1248751175299461,72.3691377159004077],"rgb":[0.6,1,0.533333333333333326],"xyz":[0.533384709368276644,0.800677992811764883,0.359374826777312251],"hpluv":[124.169042550433474,482.74240688111388,91.7152730008064481],"hsluv":[124.169042550433474,99.999999999989754,91.7152730008064481]},"#99ff99":{"lch":[91.9489728509177,79.7227767672504,127.715012949239039],"luv":[91.9489728509177,-48.7691602166467675,63.0657604984184559],"rgb":[0.6,1,0.6],"xyz":[0.546441673477349288,0.805900778455394096,0.428141504418430185],"hpluv":[127.715012949239039,453.611771943482722,91.9489728509177],"hsluv":[127.715012949239039,99.9999999999896119,91.9489728509177]},"#99ffaa":{"lch":[92.2172324754492365,71.5909188251234525,132.515331265000469],"luv":[92.2172324754492365,-48.380245540777544,52.769418223432119],"rgb":[0.6,1,0.66666666666666663],"xyz":[0.561499366442601899,0.811923855641495162,0.507445354035428786],"hpluv":[132.515331265000469,422.278899597361089,92.2172324754492365],"hsluv":[132.515331265000469,99.999999999989285,92.2172324754492365]},"#99ffbb":{"lch":[92.5209371039878334,63.4744472180726902,139.080731133451],"luv":[92.5209371039878334,-47.9634018094000325,41.5754439123617558],"rgb":[0.6,1,0.733333333333333282],"xyz":[0.578636958549938463,0.818778892484429921,0.597703339134070477],"hpluv":[139.080731133451,390.542311531215887,92.5209371039878334],"hsluv":[139.080731133451,99.9999999999889866,92.5209371039878334]},"#99ffcc":{"lch":[92.8608063839845,55.9838497302527216,148.091690615728766],"luv":[92.8608063839845,-47.524412431304512,29.5909049148346774],"rgb":[0.6,1,0.8],"xyz":[0.597929333993660217,0.826495842661918778,0.699309849804340633],"hpluv":[148.091690615728766,361.818313682118117,92.8608063839845],"hsluv":[148.091690615728766,99.999999999988475,92.8608063839845]},"#99ffdd":{"lch":[93.237403107538583,50.0214511206169306,160.217319691395858],"luv":[93.237403107538583,-47.0693412211659066,16.9299347080170577],"rgb":[0.6,1,0.866666666666666696],"xyz":[0.619447659544293816,0.835103172882172284,0.812639697704346631],"hpluv":[160.217319691395858,342.295010089686684,93.237403107538583],"hsluv":[160.217319691395858,99.999999999988,93.237403107538583]},"#99ffee":{"lch":[93.6511409780710267,46.7516947594382444,175.449324589633676],"luv":[93.6511409780710267,-46.6043124006013088,3.70931645277729727],"rgb":[0.6,1,0.933333333333333348],"xyz":[0.643259839810468526,0.844628044988642301,0.938050513772869854],"hpluv":[175.449324589633676,341.869484566500148,93.6511409780710267],"hsluv":[175.449324589633676,99.9999999999875,93.6511409780710267]},"#99ffff":{"lch":[94.102291921527609,47.1972299789563579,192.177050630060847],"luv":[94.102291921527609,-46.1353140316371437,-9.95546668362271703],"rgb":[0.6,1,1],"xyz":[0.6694308882203,0.855096464352575,1.07588470206465181],"hpluv":[192.177050630060847,372.830957625984183,94.102291921527609],"hsluv":[192.177050630060847,99.999999999986585,94.102291921527609]},"#447700":{"lch":[44.83248944102629,58.4741115144389809,115.479055163134589],"luv":[44.83248944102629,-25.1544589141716,52.7870714677211339],"rgb":[0.266666666666666663,0.466666666666666674,0],"xyz":[0.0898037965996895393,0.144222427292371835,0.0231058826388940708],"hpluv":[115.479055163134589,165.50461307776385,44.83248944102629],"hsluv":[115.479055163134589,100.00000000000216,44.83248944102629]},"#447711":{"lch":[44.8893318820142468,56.4264635629906337,116.3750363045054],"luv":[44.8893318820142468,-25.0671673404006405,50.5527735317644158],"rgb":[0.266666666666666663,0.466666666666666674,0.0666666666666666657],"xyz":[0.0908154620993266576,0.144627093492226688,0.0284339876036497],"hpluv":[116.3750363045054,159.506732057184706,44.8893318820142468],"hsluv":[116.3750363045054,95.4073228169634433,44.8893318820142468]},"#447722":{"lch":[44.9944227930058389,52.7636609180533824,118.171656995978211],"luv":[44.9944227930058389,-24.9105023189221484,46.5131248971114388],"rgb":[0.266666666666666663,0.466666666666666674,0.133333333333333331],"xyz":[0.0926908202378036866,0.145377236747617516,0.0383108737996289],"hpluv":[118.171656995978211,148.804327803607492,44.9944227930058389],"hsluv":[118.171656995978211,87.128369469673089,44.9944227930058389]},"#447733":{"lch":[45.1666686690004795,47.1090234718488,121.574089601592789],"luv":[45.1666686690004795,-24.6663168629185812,40.1351828809748881],"rgb":[0.266666666666666663,0.466666666666666674,0.2],"xyz":[0.0957785709702614202,0.146612337040600615,0.0545730276572400433],"hpluv":[121.574089601592789,132.350433634303585,45.1666686690004795],"hsluv":[121.574089601592789,74.1245535442141232,45.1666686690004795]},"#447744":{"lch":[45.4136534494367368,39.7908238780444137,127.715012949239082],"luv":[45.4136534494367368,-24.3414133770851677,31.4770592606648769],"rgb":[0.266666666666666663,0.466666666666666674,0.266666666666666663],"xyz":[0.100236566214480294,0.148395535138288209,0.0780518026101266749],"hpluv":[127.715012949239082,111.18234158710581,45.4136534494367368],"hsluv":[127.715012949239082,56.6116285441109,45.4136534494367368]},"#447755":{"lch":[45.7409133262359902,31.7219583115713704,139.030718806592802],"luv":[45.7409133262359902,-23.9520203549447466,20.798638417872084],"rgb":[0.266666666666666663,0.466666666666666674,0.333333333333333315],"xyz":[0.106198980253842293,0.150780500754033042,0.10945384988410059],"hpluv":[139.030718806592802,88.0023935360314766,45.7409133262359902],"hsluv":[139.030718806592802,58.7992252631310066,45.7409133262359902]},"#447766":{"lch":[46.1522822994750328,25.0075472636366349,160.141849635166039],"luv":[46.1522822994750328,-23.5205108300662609,8.49488024846472],"rgb":[0.266666666666666663,0.466666666666666674,0.4],"xyz":[0.113783976027350464,0.153814499063436333,0.149401494291244552],"hpluv":[160.141849635166039,68.7570511480334,46.1522822994750328],"hsluv":[160.141849635166039,61.2825519214888672,46.1522822994750328]},"#447777":{"lch":[46.650089933282672,23.6026970305602966,192.177050630060933],"luv":[46.650089933282672,-23.0716472128555,-4.97859438013953248],"rgb":[0.266666666666666663,0.466666666666666674,0.466666666666666674],"xyz":[0.123097989047552384,0.157540104271517156,0.198455296197642461],"hpluv":[192.177050630060933,64.2019875277067,46.650089933282672],"hsluv":[192.177050630060933,63.9506821134950414,46.650089933282672]},"#447788":{"lch":[47.2353114433284134,29.6592888392359697,220.273189901853613],"luv":[47.2353114433284134,-22.6291741496980734,-19.1727382434503575],"rgb":[0.266666666666666663,0.466666666666666674,0.533333333333333326],"xyz":[0.134238406149496226,0.161996271112294754,0.257128159601214601],"hpluv":[220.273189901853613,79.6770556088496,47.2353114433284134],"hsluv":[220.273189901853613,66.6958169909158585,47.2353114433284134]},"#447799":{"lch":[47.9077085122244526,40.3569508451274856,236.603980798736757],"luv":[47.9077085122244526,-22.2133832668581981,-33.6934576046404],"rgb":[0.266666666666666663,0.466666666666666674,0.6],"xyz":[0.147295370258568925,0.167219056755923912,0.325894837242332536],"hpluv":[236.603980798736757,106.893740352524901,47.9077085122244526],"hsluv":[236.603980798736757,69.4246608010291197,47.9077085122244526]},"#4477aa":{"lch":[48.6659751090485,52.9419227285019574,245.636459716194196],"luv":[48.6659751090485,-21.8398581964299439,-48.2272513849846476],"rgb":[0.266666666666666663,0.466666666666666674,0.66666666666666663],"xyz":[0.162353063223821481,0.173242133942025,0.405198686859331136],"hpluv":[245.636459716194196,138.042751347741245,48.6659751090485],"hsluv":[245.636459716194196,72.0643617009564,48.6659751090485]},"#4477bb":{"lch":[49.5078912458612,66.145388752002,251.014275727268966],"luv":[49.5078912458612,-21.5192486824625604,-62.5470573991759125],"rgb":[0.266666666666666663,0.466666666666666674,0.733333333333333282],"xyz":[0.1794906553311581,0.180097170784959765,0.495456671957972883],"hpluv":[251.014275727268966,169.536991245322184,49.5078912458612],"hsluv":[251.014275727268966,74.5637193650047578,49.5078912458612]},"#4477cc":{"lch":[50.4304819457797606,79.4031370336833,254.471410742848605],"luv":[50.4304819457797606,-21.2577420931439356,-76.5046833330565761],"rgb":[0.266666666666666663,0.466666666666666674,0.8],"xyz":[0.198783030774879854,0.187814120962448566,0.597063182628243094],"hpluv":[254.471410742848605,199.794656417231295,50.4304819457797606],"hsluv":[254.471410742848605,76.8911855438839638,50.4304819457797606]},"#4477dd":{"lch":[51.4301761714764183,92.4467030892661654,256.833353640671476],"luv":[51.4301761714764183,-21.0578873175855144,-90.0164334652001514],"rgb":[0.266666666666666663,0.466666666666666674,0.866666666666666696],"xyz":[0.220301356325513398,0.1964214511827021,0.710393030528249092],"hpluv":[256.833353640671476,228.093412194404408,51.4301761714764183],"hsluv":[256.833353640671476,79.0312420746427904,51.4301761714764183]},"#4477ee":{"lch":[52.5029598761355913,105.148975578657073,258.524369979875587],"luv":[52.5029598761355913,-20.9195063280493,-103.046985983248078],"rgb":[0.266666666666666663,0.466666666666666674,0.933333333333333348],"xyz":[0.244113536591688135,0.205946323289172145,0.835803846596772315],"hpluv":[258.524369979875587,254.132720010738154,52.5029598761355913],"hsluv":[258.524369979875587,87.6254974090696,52.5029598761355913]},"#4477ff":{"lch":[53.6445179522116,117.458222301342076,259.779939364455799],"luv":[53.6445179522116,-20.8405325230258285,-115.594576820663164],"rgb":[0.266666666666666663,0.466666666666666674,1],"xyz":[0.270284585001519528,0.216414742653104841,0.973638034888554],"hpluv":[259.779939364455799,277.841684308431127,53.6445179522116],"hsluv":[259.779939364455799,99.999999999999,53.6445179522116]},"#bbff00":{"lch":[92.6117448358007778,112.09772632761252,107.605046807390437],"luv":[92.6117448358007778,-33.9043888880720843,106.847520616749406],"rgb":[0.733333333333333282,1,0],"xyz":[0.56251493439864475,0.820836016822245496,0.128800901435937365],"hpluv":[107.605046807390437,698.685604225905081,92.6117448358007778],"hsluv":[107.605046807390437,100.000000000002302,92.6117448358007778]},"#bbff11":{"lch":[92.6295901709342,111.320425805292416,107.729052066712285],"luv":[92.6295901709342,-33.8988588935206678,106.033506813595423],"rgb":[0.733333333333333282,1,0.0666666666666666657],"xyz":[0.56352659989828191,0.821240683022100404,0.134129006400693],"hpluv":[107.729052066712285,695.618439183936061,92.6295901709342],"hsluv":[107.729052066712285,99.9999999999893703,92.6295901709342]},"#bbff22":{"lch":[92.6626551654141,109.887399193340656,107.962519071076329],"luv":[92.6626551654141,-33.888700280085942,104.531318248662245],"rgb":[0.733333333333333282,1,0.133333333333333331],"xyz":[0.565401958036758856,0.821990826277491204,0.14400589259667218],"hpluv":[107.962519071076329,689.937572971075,92.6626551654141],"hsluv":[107.962519071076329,99.9999999999891713,92.6626551654141]},"#bbff33":{"lch":[92.7170524122382318,107.550157892444602,108.357408717671873],"luv":[92.7170524122382318,-33.8722333827189388,102.076972272674624],"rgb":[0.733333333333333282,1,0.2],"xyz":[0.568489708769216673,0.823225926570474331,0.160268046454283331],"hpluv":[108.357408717671873,680.59767216104342,92.7170524122382318],"hsluv":[108.357408717671873,99.9999999999894271,92.7170524122382318]},"#bbff44":{"lch":[92.7954935084343475,104.224206838295117,108.951712424811362],"luv":[92.7954935084343475,-33.8490183743317,98.5744857768270464],"rgb":[0.733333333333333282,1,0.266666666666666663],"xyz":[0.572947704013435533,0.825009124668161897,0.183746821407169969],"hpluv":[108.951712424811362,667.142517523628385,92.7954935084343475],"hsluv":[108.951712424811362,99.9999999999890576,92.7954935084343475]},"#bbff55":{"lch":[92.9002292713318809,99.8647848732016,109.794505003516988],"luv":[92.9002292713318809,-33.8189779888925628,93.9640994505754605],"rgb":[0.733333333333333282,1,0.333333333333333315],"xyz":[0.578910118052797462,0.827394090283906647,0.215148868681143884],"hpluv":[109.794505003516988,649.201221057988732,92.9002292713318809],"hsluv":[109.794505003516988,99.9999999999888445,92.9002292713318809]},"#bbff66":{"lch":[93.0331768315467826,94.4651128464689691,110.953951756581517],"luv":[93.0331768315467826,-33.7823795150753,88.2179595059619146],"rgb":[0.733333333333333282,1,0.4],"xyz":[0.586495113826305703,0.83042808859331,0.255096513088287846],"hpluv":[110.953951756581517,626.470359589551208,93.0331768315467826],"hsluv":[110.953951756581517,99.9999999999888871,93.0331768315467826]},"#bbff77":{"lch":[93.1959878807374196,88.0576290523864742,112.529317332402641],"luv":[93.1959878807374196,-33.7398191848892566,81.3373876867131571],"rgb":[0.733333333333333282,1,0.466666666666666674],"xyz":[0.595809126846507553,0.834153693801390816,0.304150314994685755],"hpluv":[112.529317332402641,598.713569857690459,93.1959878807374196],"hsluv":[112.529317332402641,99.9999999999886455,93.1959878807374196]},"#bbff88":{"lch":[93.3900894470196334,80.7182094219550805,114.670873894576516],"luv":[93.3900894470196334,-33.6921989698203319,73.3502901212028888],"rgb":[0.733333333333333282,1,0.533333333333333326],"xyz":[0.606949543948451464,0.838609860642168359,0.362823178398257951],"hpluv":[114.670873894576516,565.785048639302204,93.3900894470196334],"hsluv":[114.670873894576516,99.9999999999880913,93.3900894470196334]},"#bbff99":{"lch":[93.6167101348934239,72.575742292483838,117.61482369288214],"luv":[93.6167101348934239,-33.6406927329059684,64.3081811417119411],"rgb":[0.733333333333333282,1,0.6],"xyz":[0.620006508057524108,0.843832646285797572,0.43158985603937583],"hpluv":[117.61482369288214,527.702623330867254,93.6167101348934239],"hsluv":[117.61482369288214,99.9999999999881,93.6167101348934239]},"#bbffaa":{"lch":[93.8768980816178384,63.8331941826517948,121.746607961207644],"luv":[93.8768980816178384,-33.5867019719833948,54.2826872050868303],"rgb":[0.733333333333333282,1,0.66666666666666663],"xyz":[0.63506420102277672,0.849855723471898639,0.510893705656374486],"hpluv":[121.746607961207644,484.836572610502856,93.8768980816178384],"hsluv":[121.746607961207644,99.9999999999878781,93.8768980816178384]},"#bbffbb":{"lch":[94.1715339943383,54.8143223318485937,127.715012949237362],"luv":[94.1715339943383,-33.5318032859449531,43.3615970772812602],"rgb":[0.733333333333333282,1,0.733333333333333282],"xyz":[0.652201793130113283,0.856710760314833397,0.601151690755016177],"hpluv":[127.715012949237362,438.38038704048256,94.1715339943383],"hsluv":[127.715012949237362,99.999999999986926,94.1715339943383]},"#bbffcc":{"lch":[94.5013412228876888,46.0666753200389323,136.612331153023604],"luv":[94.5013412228876888,-33.4776907719138919,31.6446329670924698],"rgb":[0.733333333333333282,1,0.8],"xyz":[0.671494168573835,0.864427710492322254,0.702758201425286333],"hpluv":[136.612331153023604,391.513584290668746,94.5013412228876888],"hsluv":[136.612331153023604,99.9999999999865707,94.5013412228876888]},"#bbffdd":{"lch":[94.8668940681869515,38.5674836948257322,150.076437256866],"luv":[94.8668940681869515,-33.4261169986321818,19.2391658068743077],"rgb":[0.733333333333333282,1,0.866666666666666696],"xyz":[0.693012494124468637,0.873035040712575761,0.816088049325292331],"hpluv":[150.076437256866,352.109825744613431,94.8668940681869515],"hsluv":[150.076437256866,99.9999999999853628,94.8668940681869515]},"#bbffee":{"lch":[95.2686250900245568,33.9600576278843675,169.384373669661102],"luv":[95.2686250900245568,-33.3788361105619771,6.25610214058809699],"rgb":[0.733333333333333282,1,0.933333333333333348],"xyz":[0.716824674390643346,0.882559912819045778,0.941498865393815554],"hpluv":[169.384373669661102,337.406400875084444,95.2686250900245568],"hsluv":[169.384373669661102,99.9999999999845,95.2686250900245568]},"#bbffff":{"lch":[95.7068319095003,34.1048965933827191,192.177050630060478],"luv":[95.7068319095003,-33.3375521201936849,-7.19385781613020914],"rgb":[0.733333333333333282,1,1],"xyz":[0.742995722800474767,0.893028332182978501,1.0793330536855974],"hpluv":[192.177050630060478,374.679972152143307,95.7068319095003],"hsluv":[192.177050630060478,99.9999999999829186,95.7068319095003]},"#448800":{"lch":[50.4956227619448157,68.4221216779621244,118.715311426014551],"luv":[50.4956227619448157,-32.873947818662586,60.0074186224478723],"rgb":[0.266666666666666663,0.533333333333333326,0],"xyz":[0.111876166324659992,0.18836716674231338,0.0304633392138840171],"hpluv":[118.715311426014551,171.942062028892252,50.4956227619448157],"hsluv":[118.715311426014551,100.000000000002373,50.4956227619448157]},"#448811":{"lch":[50.543205868460916,66.6756511812714763,119.435612575278299],"luv":[50.543205868460916,-32.7674265649617098,58.0683925794095899],"rgb":[0.266666666666666663,0.533333333333333326,0.0666666666666666657],"xyz":[0.112887831824297111,0.188771832942168233,0.0357914441786396503],"hpluv":[119.435612575278299,167.395510973175305,50.543205868460916],"hsluv":[119.435612575278299,96.4702491056824272,50.543205868460916]},"#448822":{"lch":[50.6312327062127565,63.5295563644356918,120.846986072134357],"luv":[50.6312327062127565,-32.5745953261955492,54.5426463530750425],"rgb":[0.266666666666666663,0.533333333333333326,0.133333333333333331],"xyz":[0.11476318996277414,0.189521976197559061,0.0456683303746188429],"hpluv":[120.846986072134357,159.219643782083125,50.6312327062127565],"hsluv":[120.846986072134357,90.0662104861249,50.6312327062127565]},"#448833":{"lch":[50.775662969350762,58.6076690216783831,123.408487274566809],"luv":[50.775662969350762,-32.2696404838739213,48.9237076599487182],"rgb":[0.266666666666666663,0.533333333333333326,0.2],"xyz":[0.117850940695231873,0.19075707649054216,0.0619304842322299931],"hpluv":[123.408487274566809,146.466455796741883,50.775662969350762],"hsluv":[123.408487274566809,79.8990388198805732,50.775662969350762]},"#448844":{"lch":[50.9830910358637652,52.0733509329185,127.715012949239537],"luv":[50.9830910358637652,-31.855056956676961,41.1933152789351382],"rgb":[0.266666666666666663,0.533333333333333326,0.266666666666666663],"xyz":[0.122308935939450747,0.192540274588229754,0.0854092591851166316],"hpluv":[127.715012949239537,129.607069016793787,50.9830910358637652],"hsluv":[127.715012949239537,65.9930987522981241,50.9830910358637652]},"#448855":{"lch":[51.2585265038955384,44.4350179761376651,134.860489600104756],"luv":[51.2585265038955384,-31.3437036744621551,31.4967151066136601],"rgb":[0.266666666666666663,0.533333333333333326,0.333333333333333315],"xyz":[0.128271349978812732,0.194925240203974587,0.116811306459090533],"hpluv":[134.860489600104756,110.001490313735033,51.2585265038955384],"hsluv":[134.860489600104756,67.3517725464548676,51.2585265038955384]},"#448866":{"lch":[51.6056896491522537,36.7437212642011914,146.829747927558799],"luv":[51.6056896491522537,-30.7562765884277063,20.1035445321824895],"rgb":[0.266666666666666663,0.533333333333333326,0.4],"xyz":[0.135856345752320917,0.197959238513377878,0.156758950866234509],"hpluv":[146.829747927558799,90.3493178961374781,51.6056896491522537],"hsluv":[146.829747927558799,68.9308752248562797,51.6056896491522537]},"#448877":{"lch":[52.0271709342862039,31.0045548915217921,166.266503182991642],"luv":[52.0271709342862039,-30.1181499011556575,7.36067052332227245],"rgb":[0.266666666666666663,0.533333333333333326,0.466666666666666674],"xyz":[0.145170358772522823,0.201684843721458701,0.205812752772632418],"hpluv":[166.266503182991642,75.6196418937204555,52.0271709342862039],"hsluv":[166.266503182991642,70.6726995402934506,52.0271709342862039]},"#448888":{"lch":[52.5245390493459041,30.1341009641634088,192.177050630061],"luv":[52.5245390493459041,-29.456097564679137,-6.35628485662009179],"rgb":[0.266666666666666663,0.533333333333333326,0.533333333333333326],"xyz":[0.156310775874466679,0.206141010562236299,0.264485616176204585],"hpluv":[192.177050630061,72.8006598676027181,52.5245390493459041],"hsluv":[192.177050630061,72.5156967272479847,52.5245390493459041]},"#448899":{"lch":[53.0984315279962118,35.4547108695225,215.690916113393854],"luv":[53.0984315279962118,-28.7954664423823949,-20.6847198484066084],"rgb":[0.266666666666666663,0.533333333333333326,0.6],"xyz":[0.169367739983539378,0.211363796205865456,0.333252293817322465],"hpluv":[215.690916113393854,84.7289026839812,53.0984315279962118],"hsluv":[215.690916113393854,74.4011420122721319,53.0984315279962118]},"#4488aa":{"lch":[53.7486427971268625,45.1587425535104146,231.425064236121358],"luv":[53.7486427971268625,-28.1581765792043193,-35.3048030832715654],"rgb":[0.266666666666666663,0.533333333333333326,0.66666666666666663],"xyz":[0.184425432948791934,0.217386873391966551,0.412556143434321065],"hpluv":[231.425064236121358,106.613859301848393,53.7486427971268625],"hsluv":[231.425064236121358,76.2778611087785,53.7486427971268625]},"#4488bb":{"lch":[54.4742155909258514,57.0545505189943469,241.113543257937238],"luv":[54.4742155909258514,-27.5616516575745152,-49.9557513488787208],"rgb":[0.266666666666666663,0.533333333333333326,0.733333333333333282],"xyz":[0.201563025056128553,0.224241910234901309,0.502814128532962812],"hpluv":[241.113543257937238,132.904176722924205,54.4742155909258514],"hsluv":[241.113543257937238,78.1047803623200707,54.4742155909258514]},"#4488cc":{"lch":[55.273536978614473,69.875332030770025,247.25266296501286],"luv":[55.273536978614473,-27.0185618464256976,-64.4403549195783398],"rgb":[0.266666666666666663,0.533333333333333326,0.8],"xyz":[0.220855400499850307,0.231958860412390111,0.604420639203233],"hpluv":[247.25266296501286,160.415361718249983,55.273536978614473],"hsluv":[247.25266296501286,79.8515774594708603,55.273536978614473]},"#4488dd":{"lch":[56.1444377207636194,82.97891177410213,251.348812353132246],"luv":[56.1444377207636194,-26.53714651812043,-78.6211145551246631],"rgb":[0.266666666666666663,0.533333333333333326,0.866666666666666696],"xyz":[0.242373726050483851,0.240566190632643645,0.717750487103239],"hpluv":[251.348812353132246,187.542769341675751,56.1444377207636194],"hsluv":[251.348812353132246,81.4979766670821,56.1444377207636194]},"#4488ee":{"lch":[57.0842924141545609,96.0325607902411775,254.216059778839536],"luv":[57.0842924141545609,-26.1218678035424112,-92.4115834426919776],"rgb":[0.266666666666666663,0.533333333333333326,0.933333333333333348],"xyz":[0.266185906316658616,0.25009106273911369,0.843161303171762189],"hpluv":[254.216059778839536,213.472145126724115,57.0842924141545609],"hsluv":[254.216059778839536,85.8050168900109469,57.0842924141545609]},"#4488ff":{"lch":[58.090117466996233,108.861046277687535,256.304473865935392],"luv":[58.090117466996233,-25.7741898685227575,-105.765866579412872],"rgb":[0.266666666666666663,0.533333333333333326,1],"xyz":[0.29235695472649,0.260559482103046358,0.980995491463544],"hpluv":[256.304473865935392,237.798754650411269,58.090117466996233],"hsluv":[256.304473865935392,99.9999999999988631,58.090117466996233]},"#449900":{"lch":[56.0984423000037538,78.159424491283,120.852962610827774],"luv":[56.0984423000037538,-40.0830169860907191,67.0987882610508279],"rgb":[0.266666666666666663,0.6,0],"xyz":[0.137745766777127493,0.240106367647249075,0.0390865393647062687],"hpluv":[120.852962610827774,176.794958777624345,56.0984423000037538],"hsluv":[120.852962610827774,100.000000000002416,56.0984423000037538]},"#449911":{"lch":[56.1389235634784143,76.6482268999804859,121.431666795625887],"luv":[56.1389235634784143,-39.9706170908808929,65.4010738159938256],"rgb":[0.266666666666666663,0.6,0.0666666666666666657],"xyz":[0.138757432276764625,0.240511033847103928,0.0444146443294619],"hpluv":[121.431666795625887,173.251641588156104,56.1389235634784143],"hsluv":[121.431666795625887,97.2234127009916,56.1389235634784143]},"#449922":{"lch":[56.21384509356389,73.9109075723002746,122.549403254372123],"luv":[56.21384509356389,-39.7660359546788555,62.3015621201611793],"rgb":[0.266666666666666663,0.6,0.133333333333333331],"xyz":[0.140632790415241626,0.241261177102494756,0.0542915305254411],"hpluv":[122.549403254372123,166.841685682494415,56.21384509356389],"hsluv":[122.549403254372123,92.1627352690631,56.21384509356389]},"#449933":{"lch":[56.3368647095541064,69.5846668696270143,124.526315047954697],"luv":[56.3368647095541064,-39.4395235157555177,57.3284383897480296],"rgb":[0.266666666666666663,0.6,0.2],"xyz":[0.143720541147699388,0.242496277395477855,0.0705536843830522342],"hpluv":[124.526315047954697,156.73291295139785,56.3368647095541064],"hsluv":[124.526315047954697,84.0665441712718859,56.3368647095541064]},"#449944":{"lch":[56.5137417426914368,63.7361451042890153,127.715012949239735],"luv":[56.5137417426914368,-38.9895886498958575,50.4193233757345922],"rgb":[0.266666666666666663,0.6,0.266666666666666663],"xyz":[0.148178536391918247,0.244279475493165449,0.0940324593359388727],"hpluv":[127.715012949239735,143.110354253444,56.5137417426914368],"hsluv":[127.715012949239735,72.8686777069245,56.5137417426914368]},"#449955":{"lch":[56.7489681974646629,56.6620380577650309,132.698160989074609],"luv":[56.7489681974646629,-38.4245724364924328,41.642992086692793],"rgb":[0.266666666666666663,0.6,0.333333333333333315],"xyz":[0.154140950431280233,0.246664441108910282,0.125434506609912788],"hpluv":[132.698160989074609,126.69910537289924,56.7489681974646629],"hsluv":[132.698160989074609,73.7405349232989664,56.7489681974646629]},"#449966":{"lch":[57.0460268500437166,48.9673458777373511,140.456743036678859],"luv":[57.0460268500437166,-37.7608817984154683,31.1755796757022061],"rgb":[0.266666666666666663,0.6,0.4],"xyz":[0.161725946204788418,0.249698439418313572,0.16538215101705675],"hpluv":[140.456743036678859,108.923224533285605,57.0460268500437166],"hsluv":[140.456743036678859,74.7718537787272766,57.0460268500437166]},"#449977":{"lch":[57.4075272841014481,41.7361359201695734,152.500832231763781],"luv":[57.4075272841014481,-37.0206846158697,19.2710651526888199],"rgb":[0.266666666666666663,0.6,0.466666666666666674],"xyz":[0.171039959224990323,0.253424044626394396,0.214435952923454659],"hpluv":[152.500832231763781,92.2534727828265915,57.4075272841014481],"hsluv":[152.500832231763781,75.9325502249844817,57.4075272841014481]},"#449988":{"lch":[57.8352917232380861,36.7607331244742284,170.245603374965924],"luv":[57.8352917232380861,-36.2292854489646174,6.22819200942437323],"rgb":[0.266666666666666663,0.6,0.533333333333333326],"xyz":[0.182180376326934179,0.257880211467172,0.273108816327026827],"hpluv":[170.245603374965924,80.6548633994409414,57.8352917232380861],"hsluv":[170.245603374965924,77.1878957355486222,57.8352917232380861]},"#449999":{"lch":[58.3304201548299517,36.2276744984656105,192.17705063006116],"luv":[58.3304201548299517,-35.4125684996312486,-7.64162232943372643],"rgb":[0.266666666666666663,0.6,0.6],"xyz":[0.195237340436006879,0.263102997110801151,0.341875493968144761],"hpluv":[192.17705063006116,78.8106081595615251,58.3304201548299517],"hsluv":[192.17705063006116,78.5021203184449803,58.3304201548299517]},"#4499aa":{"lch":[58.8933484328044585,41.0197173156303379,212.502133513081617],"luv":[58.8933484328044585,-34.5948579745346478,-22.041166266239177],"rgb":[0.266666666666666663,0.6,0.66666666666666663],"xyz":[0.210295033401259435,0.269126074296902273,0.421179343585143307],"hpluv":[212.502133513081617,88.3823902117899536,58.8933484328044585],"hsluv":[212.502133513081617,79.8414276244135692,58.8933484328044585]},"#4499bb":{"lch":[59.523905851944221,49.8962585520308082,227.36281958486083],"luv":[59.523905851944221,-33.7974051546762055,-36.7065664738854878],"rgb":[0.266666666666666663,0.6,0.733333333333333282],"xyz":[0.227432625508596054,0.275981111139837032,0.511437328683785108],"hpluv":[227.36281958486083,106.36919851968392,59.523905851944221],"hsluv":[227.36281958486083,81.1761313086710885,59.523905851944221]},"#4499cc":{"lch":[60.2213749252976385,61.1171242450200438,237.278146949597897],"luv":[60.2213749252976385,-33.0375484580145766,-51.4181219791773],"rgb":[0.266666666666666663,0.6,0.8],"xyz":[0.246725000952317808,0.283698061317325834,0.613043839354055264],"hpluv":[237.278146949597897,128.780934100622716,60.2213749252976385],"hsluv":[237.278146949597897,82.4818489079811741,60.2213749252976385]},"#4499dd":{"lch":[60.9845539619455508,73.4958381074912,243.904542785491344],"luv":[60.9845539619455508,-32.3284648877968195,-66.0038527445257728],"rgb":[0.266666666666666663,0.6,0.866666666666666696],"xyz":[0.268243326502951351,0.29230539153757934,0.726373687254061262],"hpluv":[243.904542785491344,152.926314240713424,60.9845539619455508],"hsluv":[243.904542785491344,83.7398679369964469,60.9845539619455508]},"#4499ee":{"lch":[61.8118218103912653,86.3580647779335351,248.479300934605874],"luv":[61.8118218103912653,-31.6793620640961606,-80.3376211460214193],"rgb":[0.266666666666666663,0.6,0.933333333333333348],"xyz":[0.292055506769126061,0.301830263644049357,0.851784503322584485],"hpluv":[248.479300934605874,177.284466220706,61.8118218103912653],"hsluv":[248.479300934605874,84.9368954206658344,61.8118218103912653]},"#4499ff":{"lch":[62.7012034705467585,99.3269113509348,251.755860244601337],"luv":[62.7012034705467585,-31.0959452870692914,-94.3338619225358173],"rgb":[0.266666666666666663,0.6,1],"xyz":[0.318226555178957482,0.312298683007982081,0.98961869161436633],"hpluv":[251.755860244601337,201.015886170810859,62.7012034705467585],"hsluv":[251.755860244601337,99.9999999999986073,62.7012034705467585]},"#330000":{"lch":[6.35863201887414942,21.3842798011123882,12.1770506300617836],"luv":[6.35863201887414942,20.9031433498234946,4.51065635013277699],"rgb":[0.2,0,0],"xyz":[0.0136521011456799905,0.00703936465324139522,0.000639942241203736136],"hpluv":[12.1770506300617836,426.746789183125031,6.35863201887414942],"hsluv":[12.1770506300617836,100.000000000002217,6.35863201887414942]},"#330011":{"lch":[6.72416549840036915,18.2596394021459751,358.956333183931122],"luv":[6.72416549840036915,18.2566101970553,-0.332588648601129133],"rgb":[0.2,0,0.0666666666666666657],"xyz":[0.0146637666453171122,0.00744403085309625,0.00596804720595936738],"hpluv":[358.956333183931122,344.582429927088697,6.72416549840036915],"hsluv":[358.956333183931122,99.9999999999970868,6.72416549840036915]},"#330022":{"lch":[7.4017671226143058,16.6083885778583671,334.642609555635659],"luv":[7.4017671226143058,15.0082373074967617,-7.11276205668364714],"rgb":[0.2,0,0.133333333333333331],"xyz":[0.0165391247837941326,0.00819417410848706854,0.0158449334019385643],"hpluv":[334.642609555635659,284.728805881674077,7.4017671226143058],"hsluv":[334.642609555635659,99.999999999998,7.4017671226143058]},"#330033":{"lch":[8.50665746950019,19.3767863388894384,307.715012949243601],"luv":[8.50665746950019,11.8534455994177517,-15.3282639670843448],"rgb":[0.2,0,0.2],"xyz":[0.0196268755162518696,0.00942927440147018139,0.0321070872595497075],"hpluv":[307.715012949243601,289.042783730483279,8.50665746950019],"hsluv":[307.715012949243601,99.9999999999987921,8.50665746950019]},"#330044":{"lch":[9.96321399083228343,25.9151774110163871,290.632214162589],"luv":[9.96321399083228343,9.13167627644372,-24.2530185467023678],"rgb":[0.2,0,0.266666666666666663],"xyz":[0.0240848707604707502,0.0112124724991577579,0.0555858622124363461],"hpluv":[290.632214162589,330.060881015257678,9.96321399083228343],"hsluv":[290.632214162589,99.9999999999994,9.96321399083228343]},"#330055":{"lch":[11.6870713271151807,34.2775786608295405,281.502617436257196],"luv":[11.6870713271151807,6.83538450996046,-33.589133919325],"rgb":[0.2,0,0.333333333333333315],"xyz":[0.0300472847998327422,0.0135974381149025891,0.0869879094864102614],"hpluv":[281.502617436257196,372.172061509357604,11.6870713271151807],"hsluv":[281.502617436257196,99.9999999999999289,11.6870713271151807]},"#330066":{"lch":[13.6097387714237676,43.4818398400869768,276.434806151814087],"luv":[13.6097387714237676,4.87312317733106592,-43.2079051375733059],"rgb":[0.2,0,0.4],"xyz":[0.0376322805733409205,0.0166314364243059024,0.126935553893554209],"hpluv":[276.434806151814087,405.412793254212261,13.6097387714237676],"hsluv":[276.434806151814087,100.00000000000027,13.6097387714237676]},"#330077":{"lch":[15.6735112457106673,53.108485659557914,273.408523183706109],"luv":[15.6735112457106673,3.15755804167271537,-53.0145364618510655],"rgb":[0.2,0,0.466666666666666674],"xyz":[0.046946293593542833,0.0203570416323867187,0.175989355799952119],"hpluv":[273.408523183706109,429.968801903614121,15.6735112457106673],"hsluv":[273.408523183706109,100.000000000000313,15.6735112457106673]},"#330088":{"lch":[17.8339183845063687,62.9511834901283365,271.47985970994057],"luv":[17.8339183845063687,1.62574911173865799,-62.9301870538574448],"rgb":[0.2,0,0.533333333333333326],"xyz":[0.058086710695486661,0.0248132084731643096,0.234662219203524286],"hpluv":[271.47985970994057,447.91587095992594,17.8339183845063687],"hsluv":[271.47985970994057,100.000000000000441,17.8339183845063687]},"#330099":{"lch":[20.0583065104412341,72.8932825114363,270.184356583024851],"luv":[20.0583065104412341,0.234543162084408924,-72.8929051746271313],"rgb":[0.2,0,0.6],"xyz":[0.0711436748045593814,0.0300359941167934706,0.303428896844642193],"hpluv":[270.184356583024851,461.139761646516433,20.0583065104412341],"hsluv":[270.184356583024851,100.000000000000625,20.0583065104412341]},"#3300aa":{"lch":[22.3232943619689834,82.8637729479105474,269.276671227287579],"luv":[22.3232943619689834,-1.04608331699459467,-82.8571697371855578],"rgb":[0.2,0,0.66666666666666663],"xyz":[0.0862013677698119235,0.0360590713028945756,0.382732746461640794],"hpluv":[269.276671227287579,471.026936966419044,22.3232943619689834],"hsluv":[269.276671227287579,100.000000000000554,22.3232943619689834]},"#3300bb":{"lch":[24.6123405885396807,92.8181896970849,268.61855571411644],"luv":[24.6123405885396807,-2.23769945956788,-92.7912120826788538],"rgb":[0.2,0,0.733333333333333282],"xyz":[0.10333895987714857,0.0429141081458293341,0.47299073156028254],"hpluv":[268.61855571411644,478.541387025058441,24.6123405885396807],"hsluv":[268.61855571411644,100.000000000000625,24.6123405885396807]},"#3300cc":{"lch":[26.9138017967000778,102.728605647013808,268.127719933063759],"luv":[26.9138017967000778,-3.35631165306985224,-102.673762910819349],"rgb":[0.2,0,0.8],"xyz":[0.122631335320870311,0.0506310583233181358,0.574597242230552752],"hpluv":[268.127719933063759,484.345947247320225,26.9138017967000778],"hsluv":[268.127719933063759,100.000000000000881,26.9138017967000778]},"#3300dd":{"lch":[29.2194977691074271,112.577730384171886,267.752877980499],"luv":[29.2194977691074271,-4.41413050162269105,-112.49115889867052],"rgb":[0.2,0,0.866666666666666696],"xyz":[0.144149660871503854,0.0592383885435716698,0.687927090130558749],"hpluv":[267.752877980499,488.89890937186334,29.2194977691074271],"hsluv":[267.752877980499,100.000000000000753,29.2194977691074271]},"#3300ee":{"lch":[31.5236887929336334,122.355215968494591,267.460804758990776],"luv":[31.5236887929336334,-5.42068013199621,-122.235081304851008],"rgb":[0.2,0,0.933333333333333348],"xyz":[0.167961841137678591,0.0687632606500417,0.813337906199082],"hpluv":[267.460804758990776,492.52103452607281,31.5236887929336334],"hsluv":[267.460804758990776,100.000000000000824,31.5236887929336334]},"#3300ff":{"lch":[33.8223579343154,132.055276159319874,267.229255072945307],"luv":[33.8223579343154,-6.38352242786170443,-131.900896899631505],"rgb":[0.2,0,1],"xyz":[0.194132889547509985,0.0792316800139744,0.951172094490863818],"hpluv":[267.229255072945307,495.440155164142311,33.8223579343154],"hsluv":[267.229255072945307,100.000000000000881,33.8223579343154]},"#331100":{"lch":[9.83576796362177319,19.9321083570360571,25.9770166386959609],"luv":[9.83576796362177319,17.918363864654,8.73047421223431108],"rgb":[0.2,0.0666666666666666657,0],"xyz":[0.0156565014066084,0.0110481651750982679,0.00130807566151318702],"hpluv":[25.9770166386959609,257.148675223584291,9.83576796362177319],"hsluv":[25.9770166386959609,100.000000000002302,9.83576796362177319]},"#331111":{"lch":[10.1474261289244687,16.4836545456174051,12.1770506300618813],"luv":[10.1474261289244687,16.1127799065782149,3.4769513746129066],"rgb":[0.2,0.0666666666666666657,0.0666666666666666657],"xyz":[0.0166681669062455212,0.0114528313749531225,0.00663618062626881826],"hpluv":[12.1770506300618813,206.127972902374523,10.1474261289244687],"hsluv":[12.1770506300618813,48.3021731216650707,10.1474261289244687]},"#331122":{"lch":[10.7062693823806221,14.2435433110065777,342.375847990242676],"luv":[10.7062693823806221,13.5749958919169824,-4.31254131423193],"rgb":[0.2,0.0666666666666666657,0.133333333333333331],"xyz":[0.0185435250447225398,0.0122029746303439404,0.0165130668222480161],"hpluv":[342.375847990242676,168.818174775843545,10.7062693823806221],"hsluv":[342.375847990242676,57.1044970617697913,10.7062693823806221]},"#331133":{"lch":[11.5784810016780177,17.5377888786733784,307.715012949244169],"luv":[11.5784810016780177,10.7284677021084427,-13.8735006223280077],"rgb":[0.2,0.0666666666666666657,0.2],"xyz":[0.0216312757771802804,0.0134380749233270532,0.0327752206798591628],"hpluv":[307.715012949244169,192.204068690519591,11.5784810016780177],"hsluv":[307.715012949244169,66.4967539441281,11.5784810016780177]},"#331144":{"lch":[12.7480449023252049,25.2894553184220108,288.641508688419037],"luv":[12.7480449023252049,8.08366941467993172,-23.9626968243691536],"rgb":[0.2,0.0666666666666666657,0.266666666666666663],"xyz":[0.0260892710213991574,0.0152212730210146297,0.0562539956327458],"hpluv":[288.641508688419037,251.73014018207067,12.7480449023252049],"hsluv":[288.641508688419037,74.5439781366083309,12.7480449023252049]},"#331155":{"lch":[14.1772863520069095,34.570642435857458,279.659572498507771],"luv":[14.1772863520069095,5.80074057604611415,-34.0805036230000695],"rgb":[0.2,0.0666666666666666657,0.333333333333333315],"xyz":[0.0320516850607611564,0.0176062386367594609,0.0876560429067197],"hpluv":[279.659572498507771,309.423764447612427,14.1772863520069095],"hsluv":[279.659572498507771,80.733364837348816,14.1772863520069095]},"#331166":{"lch":[15.8197098676790517,44.2946391552180785,274.993838621827194],"luv":[15.8197098676790517,3.85578699555013271,-44.1264995726595686],"rgb":[0.2,0.0666666666666666657,0.4],"xyz":[0.0396366808342693278,0.0206402369461627724,0.127603687313863678],"hpluv":[274.993838621827194,355.297359803625511,15.8197098676790517],"hsluv":[274.993838621827194,85.2848648605675095,15.8197098676790517]},"#331177":{"lch":[17.6293493428787755,54.1580116435351115,272.303494486185969],"luv":[17.6293493428787755,2.17675853502087469,-54.1142490242773135],"rgb":[0.2,0.0666666666666666657,0.466666666666666674],"xyz":[0.0489506938544712472,0.0243658421542435888,0.176657489220261588],"hpluv":[272.303494486185969,389.821469213037517,17.6293493428787755],"hsluv":[272.303494486185969,88.5936990192462588,17.6293493428787755]},"#331188":{"lch":[19.5658128626021437,64.0679547566568,270.623065923527406],"luv":[19.5658128626021437,0.696696562564985133,-64.064166587888522],"rgb":[0.2,0.0666666666666666657,0.533333333333333326],"xyz":[0.0600911109564150753,0.0288220089950211832,0.235330352623833755],"hpluv":[270.623065923527406,415.51077593183,19.5658128626021437],"hsluv":[270.623065923527406,91.0112456871911348,19.5658128626021437]},"#331199":{"lch":[21.5959931816331263,73.9854871429769645,269.508319412823],"luv":[21.5959931816331263,-0.634894604710849,-73.9827629696574576],"rgb":[0.2,0.0666666666666666657,0.6],"xyz":[0.0731480750654877887,0.0340447946386503442,0.304097030264951662],"hpluv":[269.508319412823,434.723064450461493,21.5959931816331263],"hsluv":[269.508319412823,92.7998436287799,21.5959931816331263]},"#3311aa":{"lch":[23.6938673935249824,83.8866252842298792,268.733700318675346],"luv":[23.6938673935249824,-1.85383566080119344,-83.8661385478044821],"rgb":[0.2,0.0666666666666666657,0.66666666666666663],"xyz":[0.0882057680307403308,0.0400678718247514457,0.383400879881950263],"hpluv":[268.733700318675346,449.258286440651602,23.6938673935249824],"hsluv":[268.733700318675346,94.1436997610677,23.6938673935249824]},"#3311bb":{"lch":[25.8394818705352094,93.7535423490500079,268.175268957576],"luv":[25.8394818705352094,-2.98531729009247,-93.7060007879569525],"rgb":[0.2,0.0666666666666666657,0.733333333333333282],"xyz":[0.105343360138076977,0.0469229086676862042,0.473658864980592],"hpluv":[268.175268957576,460.408371224539451,25.8394818705352094],"hsluv":[268.175268957576,95.1697562295976525,25.8394818705352094]},"#3311cc":{"lch":[28.017750605254669,103.572636018550753,267.760492630427507],"luv":[28.017750605254669,-4.04728980779695124,-103.493528189171812],"rgb":[0.2,0.0666666666666666657,0.8],"xyz":[0.124635735581798718,0.0546398588451750059,0.57526537565086211],"hpluv":[267.760492630427507,469.084508820223505,28.017750605254669],"hsluv":[267.760492630427507,95.9655004656031423,28.017750605254669]},"#3311dd":{"lch":[30.2173526676312889,113.33386550323506,267.444704079349265],"luv":[30.2173526676312889,-5.05282540459801588,-113.221173043455181],"rgb":[0.2,0.0666666666666666657,0.866666666666666696],"xyz":[0.146154061132432261,0.0632471890654285329,0.688595223550868107],"hpluv":[267.444704079349265,475.929488946899937,30.2173526676312889],"hsluv":[267.444704079349265,96.591763702558211,30.2173526676312889]},"#3311ee":{"lch":[32.429822932533412,123.030197380053124,267.199217974228532],"luv":[32.429822932533412,-6.01167404447580456,-122.883234180085807],"rgb":[0.2,0.0666666666666666657,0.933333333333333348],"xyz":[0.169966241398607,0.0727720611718985777,0.814006039619391331],"hpluv":[267.199217974228532,481.400425551936735,32.429822932533412],"hsluv":[267.199217974228532,97.0913881744298095,32.429822932533412]},"#3311ff":{"lch":[34.6488414224811834,132.657034918193688,267.004954598207064],"luv":[34.6488414224811834,-6.93127707868383425,-132.475832933200962],"rgb":[0.2,0.0666666666666666657,1],"xyz":[0.196137289808438392,0.0832404805358312738,0.951840227911173176],"hpluv":[267.004954598207064,485.826158631145177,34.6488414224811834],"hsluv":[267.004954598207064,99.999999999999531,34.6488414224811834]},"#88aa00":{"lch":[64.9493872277699467,75.8454165204624502,102.522158340464031],"luv":[64.9493872277699467,-16.4445883060260414,74.0412231301438624],"rgb":[0.533333333333333326,0.66666666666666663,0],"xyz":[0.245272120749672057,0.339833923051985953,0.0526729261635559762],"hpluv":[102.522158340464031,148.181371186867864,64.9493872277699467],"hsluv":[102.522158340464031,100.000000000002217,64.9493872277699467]},"#88aa11":{"lch":[64.9815053546997206,74.514883993558044,102.764001416735823],"luv":[64.9815053546997206,-16.4630035423646639,72.6734989589568414],"rgb":[0.533333333333333326,0.66666666666666663,0.0666666666666666657],"xyz":[0.246283786249309189,0.340238589251840806,0.0580010311283116059],"hpluv":[102.764001416735823,145.509915409181133,64.9815053546997206],"hsluv":[102.764001416735823,98.0498740713468635,64.9815053546997206]},"#88aa22":{"lch":[65.040976504183746,72.0784265262596762,103.230826782560158],"luv":[65.040976504183746,-16.4969246852166869,70.1651697527462801],"rgb":[0.533333333333333326,0.66666666666666663,0.133333333333333331],"xyz":[0.24815914438778619,0.340988732507231607,0.0678779173242908],"hpluv":[103.230826782560158,140.623392494363173,65.040976504183746],"hsluv":[103.230826782560158,94.4776072436414296,65.040976504183746]},"#88aa33":{"lch":[65.138705174337673,68.1506383708936596,104.056367163177015],"luv":[65.138705174337673,-16.5521781693680836,66.1100212389152],"rgb":[0.533333333333333326,0.66666666666666663,0.2],"xyz":[0.251246895120243952,0.342223832800214733,0.0841400711819019487],"hpluv":[104.056367163177015,132.760883332008575,65.138705174337673],"hsluv":[104.056367163177015,88.7139905695652544,65.138705174337673]},"#88aa44":{"lch":[65.2793887406011,62.6606585185119442,105.391240881396499],"luv":[65.2793887406011,-16.6306856640551395,60.4133960336360687],"rgb":[0.533333333333333326,0.66666666666666663,0.266666666666666663],"xyz":[0.255704890364462811,0.344007030897902299,0.107618846134788587],"hpluv":[105.391240881396499,121.803060858356815,65.2793887406011],"hsluv":[105.391240881396499,80.6421361820775,65.2793887406011]},"#88aa55":{"lch":[65.4667902277309821,55.6467221212728731,107.50018297907917],"luv":[65.4667902277309821,-16.7334615527701551,53.0711687011330397],"rgb":[0.533333333333333326,0.66666666666666663,0.333333333333333315],"xyz":[0.261667304403824796,0.34639199651364716,0.139020893408762503],"hpluv":[107.50018297907917,107.859366541167589,65.4667902277309821],"hsluv":[107.50018297907917,70.2829236383929157,65.4667902277309821]},"#88aa66":{"lch":[65.7039511656785606,47.2674293485405386,110.898270106627507],"luv":[65.7039511656785606,-16.8607549547505542,44.1579530727492582],"rgb":[0.533333333333333326,0.66666666666666663,0.4],"xyz":[0.269252300177333,0.349425994823050479,0.178968537815906464],"hpluv":[110.898270106627507,91.2871867148882075,65.7039511656785606],"hsluv":[110.898270106627507,57.7776943224234358,65.7039511656785606]},"#88aa77":{"lch":[65.993303036365262,37.8517489546572605,116.707912668539493],"luv":[65.993303036365262,-17.0121797895697142,33.8133204186420073],"rgb":[0.533333333333333326,0.66666666666666663,0.466666666666666674],"xyz":[0.278566313197534887,0.353151600031131274,0.228022339722304374],"hpluv":[116.707912668539493,72.782239014817236,65.993303036365262],"hsluv":[116.707912668539493,43.3670204896631262,65.993303036365262]},"#88aa88":{"lch":[66.3367341259492,28.0952907790087707,127.715012949235486],"luv":[66.3367341259492,-17.1868541575625393,22.2251526006848934],"rgb":[0.533333333333333326,0.66666666666666663,0.533333333333333326],"xyz":[0.289706730299478743,0.357607766871908872,0.286695203125876541],"hpluv":[127.715012949235486,53.7426121206727316,66.3367341259492],"hsluv":[127.715012949235486,27.3645684281827677,66.3367341259492]},"#88aa99":{"lch":[66.7356352778598705,19.8636867466165334,151.061783220075199],"luv":[66.7356352778598705,-17.3835461247401071,9.6113669837720046],"rgb":[0.533333333333333326,0.66666666666666663,0.6],"xyz":[0.302763694408551443,0.36283055251553803,0.35546188076699442],"hpluv":[151.061783220075199,37.7695130125235536,66.7356352778598705],"hsluv":[151.061783220075199,30.5390729913306274,66.7356352778598705]},"#88aaaa":{"lch":[67.1909358184889811,18.0059400546344968,192.177050630060364],"luv":[67.1909358184889811,-17.6008146924254447,-3.79805205519771905],"rgb":[0.533333333333333326,0.66666666666666663,0.66666666666666663],"xyz":[0.317821387373804,0.368853629701639152,0.434765730383993],"hpluv":[192.177050630060364,34.0051297749840913,67.1909358184889811],"hsluv":[192.177050630060364,33.872023720911649,67.1909358184889811]},"#88aabb":{"lch":[67.7031355134684674,25.1829477340388976,224.903065382651],"luv":[67.7031355134684674,-17.8371340556046292,-17.7768812016541453],"rgb":[0.533333333333333326,0.66666666666666663,0.733333333333333282],"xyz":[0.334958979481140617,0.37570866654457391,0.525023715482634823],"hpluv":[224.903065382651,47.1994684953027459,67.7031355134684674],"hsluv":[224.903065382651,37.296330310174362,67.7031355134684674]},"#88aacc":{"lch":[68.2723356589922901,36.8600300209604939,240.606662797769701],"luv":[68.2723356589922901,-18.0909926283411373,-32.1150712106857341],"rgb":[0.533333333333333326,0.66666666666666663,0.8],"xyz":[0.354251354924862372,0.383425616722062712,0.626630226152905],"hpluv":[240.606662797769701,68.5094136262693,68.2723356589922901],"hsluv":[240.606662797769701,40.7502319604911136,68.2723356589922901]},"#88aadd":{"lch":[68.8982708583434,50.1120766914241216,248.506358436024811],"luv":[68.8982708583434,-18.3609632112572676,-46.6271944285953168],"rgb":[0.533333333333333326,0.66666666666666663,0.866666666666666696],"xyz":[0.375769680475495915,0.392032946942316218,0.739960074052911],"hpluv":[248.506358436024811,92.2939864083951278,68.8982708583434],"hsluv":[248.506358436024811,59.5109742880650217,68.8982708583434]},"#88aaee":{"lch":[69.5803420919898,63.9362726255054454,253.044423586561948],"luv":[69.5803420919898,-18.6457452648911577,-61.1570367231747554],"rgb":[0.533333333333333326,0.66666666666666663,0.933333333333333348],"xyz":[0.39958186074167068,0.401557819048786235,0.8653708901214342],"hpluv":[253.044423586561948,116.600410917186778,69.5803420919898],"hsluv":[253.044423586561948,79.2673662112875093,69.5803420919898]},"#88aaff":{"lch":[70.3176511000829549,77.916964899147743,255.928474825822],"luv":[70.3176511000829549,-18.9441834980668631,-75.578907974954177],"rgb":[0.533333333333333326,0.66666666666666663,1],"xyz":[0.425752909151502046,0.412026238412718959,1.00320507841321604],"hpluv":[255.928474825822,140.607018245256825,70.3176511000829549],"hsluv":[255.928474825822,99.9999999999979394,70.3176511000829549]},"#332200":{"lch":[14.6681357538016819,18.4720509904151484,54.0318728094203635],"luv":[14.6681357538016819,10.8492842291989344,14.9502407842332925],"rgb":[0.2,0.133333333333333331,0],"xyz":[0.0193721251413763347,0.0184794126446342424,0.00254661690643579732],"hpluv":[54.0318728094203635,159.801011716648361,14.6681357538016819],"hsluv":[54.0318728094203635,100.000000000002359,14.6681357538016819]},"#332211":{"lch":[14.8903804788128475,14.003495227987683,44.8263438888978243],"luv":[14.8903804788128475,9.93193249146711,9.87190941941900668],"rgb":[0.2,0.133333333333333331,0.0666666666666666657],"xyz":[0.0203837906410134564,0.0188840788444890953,0.00787472187119143],"hpluv":[44.8263438888978243,119.33558852926538,14.8903804788128475],"hsluv":[44.8263438888978243,67.0844803779226595,14.8903804788128475]},"#332222":{"lch":[15.2941064614028619,8.70381909014442101,12.1770506300622809],"luv":[15.2941064614028619,8.50798716741232397,1.83592513820952408],"rgb":[0.2,0.133333333333333331,0.133333333333333331],"xyz":[0.0222591487794904751,0.0196342220998799166,0.0177516080671706253],"hpluv":[12.1770506300622809,72.2146104972558476,15.2941064614028619],"hsluv":[12.1770506300622809,16.9221215783466867,15.2941064614028619]},"#332233":{"lch":[15.9369990430381634,10.9638268591401484,307.7150129492465],"luv":[15.9369990430381634,6.70694938589646661,-8.67308072902737],"rgb":[0.2,0.133333333333333331,0.2],"xyz":[0.0253468995119482156,0.0208693223928630295,0.0340137619247817685],"hpluv":[307.7150129492465,87.2961214462547,15.9369990430381634],"hsluv":[307.7150129492465,30.2017993044426838,15.9369990430381634]},"#332244":{"lch":[16.8218835175385664,20.7382675483863608,283.478697838556343],"luv":[16.8218835175385664,4.83375477674434428,-20.1670661145035197],"rgb":[0.2,0.133333333333333331,0.266666666666666663],"xyz":[0.0298048947561670927,0.0226525204905506025,0.057492536877668407],"hpluv":[283.478697838556343,156.436171283708973,16.8218835175385664],"hsluv":[283.478697838556343,43.2894908809756558,16.8218835175385664]},"#332255":{"lch":[17.9355503164319856,31.8518286021958055,275.537546938931484],"luv":[17.9355503164319856,3.0736387963894658,-31.7031816992078603],"rgb":[0.2,0.133333333333333331,0.333333333333333315],"xyz":[0.0357673087955290847,0.0250374861062954354,0.0888945841516423224],"hpluv":[275.537546938931484,225.350740722061715,17.9355503164319856],"hsluv":[275.537546938931484,54.6600619512456092,17.9355503164319856]},"#332266":{"lch":[19.2543827660255502,42.9346801988288,271.988815040461645],"luv":[19.2543827660255502,1.49002234071320738,-42.9088172430781114],"rgb":[0.2,0.133333333333333331,0.4],"xyz":[0.043352304569037263,0.028071484415698747,0.12884222855878627],"hpluv":[271.988815040461645,282.955381276792764,19.2543827660255502],"hsluv":[271.988815040461645,63.8744485302889515,19.2543827660255502]},"#332277":{"lch":[20.7496984269819,53.7295137018198687,270.083996688769219],"luv":[20.7496984269819,0.0787684479011579453,-53.7294559637975908],"rgb":[0.2,0.133333333333333331,0.466666666666666674],"xyz":[0.0526663175892391755,0.0317970896237795633,0.17789603046518418],"hpluv":[270.083996688769219,328.579487011522247,20.7496984269819],"hsluv":[270.083996688769219,71.0892762838988546,20.7496984269819]},"#332288":{"lch":[22.3919640579388926,64.2366699579867,268.940142222195846],"luv":[22.3919640579388926,-1.18818265954209568,-64.2256801385464087],"rgb":[0.2,0.133333333333333331,0.533333333333333326],"xyz":[0.063806734691183,0.0362532564645571542,0.236568893868756347],"hpluv":[268.940142222195846,364.024117005976393,22.3919640579388926],"hsluv":[268.940142222195846,76.6637840765232426,22.3919640579388926]},"#332299":{"lch":[24.1535324867621668,74.5121507797046689,268.199285594367666],"luv":[24.1535324867621668,-2.34141182983712737,-74.4753543426316469],"rgb":[0.2,0.133333333333333331,0.6],"xyz":[0.076863698800255717,0.0414760421081863187,0.305335571509874282],"hpluv":[268.199285594367666,391.458538227207669,24.1535324867621668],"hsluv":[268.199285594367666,80.9654036049547301,24.1535324867621668]},"#3322aa":{"lch":[26.0100477302332607,84.6098540849434926,267.692459882235937],"luv":[26.0100477302332607,-3.40667075751279169,-84.541244387726735],"rgb":[0.2,0.133333333333333331,0.66666666666666663],"xyz":[0.0919213917655082591,0.0474991192942874202,0.384639421126872882],"hpluv":[267.692459882235937,412.780453116303818,26.0100477302332607],"hsluv":[267.692459882235937,84.3023702715129,26.0100477302332607]},"#3322bb":{"lch":[27.9408960039881222,94.5690254119923281,267.330995248517297],"luv":[27.9408960039881222,-4.40370835341661504,-94.4664380619492761],"rgb":[0.2,0.133333333333333331,0.733333333333333282],"xyz":[0.109058983872844906,0.0543541561372221788,0.474897406225514629],"hpluv":[267.330995248517297,429.484911570580266,27.9408960039881222],"hsluv":[267.330995248517297,86.9134730370406743,27.9408960039881222]},"#3322cc":{"lch":[29.9290875828623939,104.415306333233346,267.064581258752071],"luv":[29.9290875828623939,-5.34714045276937444,-104.278302084572317],"rgb":[0.2,0.133333333333333331,0.8],"xyz":[0.12835135931656666,0.0620711063147109804,0.576503916895784729],"hpluv":[267.064581258752071,442.700503900749,29.9290875828623939],"hsluv":[267.064581258752071,88.9774620844202389,29.9290875828623939]},"#3322dd":{"lch":[31.9608605817263296,114.164622106097937,266.862904678446171],"luv":[31.9608605817263296,-6.2476920173399213,-113.993540541052639],"rgb":[0.2,0.133333333333333331,0.866666666666666696],"xyz":[0.149869684867200204,0.0706784365349645144,0.689833764795790727],"hpluv":[266.862904678446171,453.265229710644405,31.9608605817263296],"hsluv":[266.862904678446171,90.6264253960307826,31.9608605817263296]},"#3322ee":{"lch":[34.0251904593745635,123.826689560879501,266.706806867468629],"luv":[34.0251904593745635,-7.11327635006579229,-123.622208147136817],"rgb":[0.2,0.133333333333333331,0.933333333333333348],"xyz":[0.173681865133374941,0.0802033086414345453,0.815244580864314],"hpluv":[266.706806867468629,461.799038215803307,34.0251904593745635],"hsluv":[266.706806867468629,93.6430785136650741,34.0251904593745635]},"#3322ff":{"lch":[36.1133053940478774,133.407509730883817,266.583697157343806],"luv":[36.1133053940478774,-7.94980801741223875,-133.170432923686747],"rgb":[0.2,0.133333333333333331,1],"xyz":[0.199852913543206334,0.0906717280053672414,0.953078769156095795],"hpluv":[266.583697157343806,468.761962088723578,36.1133053940478774],"hsluv":[266.583697157343806,99.999999999999531,36.1133053940478774]},"#aaaa00":{"lch":[67.4983691984715506,74.4102446110960472,85.8743202181747449],"luv":[67.4983691984715506,5.35340686476390193,74.217420717938225],"rgb":[0.66666666666666663,0.66666666666666663,0],"xyz":[0.309512896760441802,0.372958073182539818,0.0556842125390607443],"hpluv":[85.8743202181747449,139.887458074797593,67.4983691984715506],"hsluv":[85.8743202181747449,100.000000000002373,67.4983691984715506]},"#aaaa11":{"lch":[67.528557359020084,73.1276023311446863,85.8743202181746881],"luv":[67.528557359020084,5.26112782412359792,72.9381022286724345],"rgb":[0.66666666666666663,0.66666666666666663,0.0666666666666666657],"xyz":[0.310524562260078907,0.373362739382394671,0.061012317503816374],"hpluv":[85.8743202181746881,137.414698385368666,67.528557359020084],"hsluv":[85.8743202181746881,98.2323220941626118,67.528557359020084]},"#aaaa22":{"lch":[67.5844605157977,70.7729399531690575,85.8743202181746],"luv":[67.5844605157977,5.09172284764045369,70.589541633712912],"rgb":[0.66666666666666663,0.66666666666666663,0.133333333333333331],"xyz":[0.312399920398555964,0.374112882637785471,0.0708892036997955666],"hpluv":[85.8743202181746,132.880028295953878,67.5844605157977],"hsluv":[85.8743202181746,94.9906661574379854,67.5844605157977]},"#aaaa33":{"lch":[67.6763416895574181,66.9597491826677924,85.8743202181744607],"luv":[67.6763416895574181,4.81738479440415723,66.7862322215321882],"rgb":[0.66666666666666663,0.66666666666666663,0.2],"xyz":[0.315487671131013669,0.375347982930768598,0.0871513575574067167],"hpluv":[85.8743202181744607,125.549870841925909,67.6763416895574181],"hsluv":[85.8743202181744607,89.7506270896691376,67.6763416895574181]},"#aaaa44":{"lch":[67.8086418759902898,61.5895608625658824,85.8743202181741623],"luv":[67.8086418759902898,4.43102935143870269,61.429960004304057],"rgb":[0.66666666666666663,0.66666666666666663,0.266666666666666663],"xyz":[0.319945666375232585,0.377131181028456164,0.110630132510293355],"hpluv":[85.8743202181741623,115.255428047766188,67.8086418759902898],"hsluv":[85.8743202181741623,82.3915379076671854,67.8086418759902898]},"#aaaa55":{"lch":[67.9849384953625844,54.6449851984581514,85.8743202181737786],"luv":[67.9849384953625844,3.93140541890911,54.5033802508787772],"rgb":[0.66666666666666663,0.66666666666666663,0.333333333333333315],"xyz":[0.32590808041459457,0.379516146644201,0.142032179784267271],"hpluv":[85.8743202181737786,101.994541545207838,67.9849384953625844],"hsluv":[85.8743202181737786,72.9118556794948631,67.9849384953625844]},"#aaaa66":{"lch":[68.2081473948541515,46.1798212544818156,85.8743202181731675],"luv":[68.2081473948541515,3.32238354287766668,46.0601526125338268],"rgb":[0.66666666666666663,0.66666666666666663,0.4],"xyz":[0.333493076188102755,0.382550144953604343,0.181979824191411232],"hpluv":[85.8743202181731675,85.9122949373369806,68.2081473948541515],"hsluv":[85.8743202181731675,61.415294923296095,68.2081473948541515]},"#aaaa77":{"lch":[68.4806287458147551,36.3079853189649668,85.874320218171988],"luv":[68.4806287458147551,2.61215937225179973,36.2138981792368284],"rgb":[0.66666666666666663,0.66666666666666663,0.466666666666666674],"xyz":[0.342807089208304661,0.386275750161685139,0.231033626097809142],"hpluv":[85.874320218171988,67.2781031791916604,68.4806287458147551],"hsluv":[85.874320218171988,48.0944497134402909,68.4806287458147551]},"#aaaa88":{"lch":[68.804250183835336,25.1900382005990835,85.8743202181696574],"luv":[68.804250183835336,1.81228437202058101,25.1247616884732103],"rgb":[0.66666666666666663,0.66666666666666663,0.533333333333333326],"xyz":[0.353947506310248461,0.390731917002462736,0.289706489501381281],"hpluv":[85.8743202181696574,46.4571845078746506,68.804250183835336],"hsluv":[85.8743202181696574,33.210400093935057,68.804250183835336]},"#aaaa99":{"lch":[69.1804292601881485,13.0180161266067085,85.8743202181625236],"luv":[69.1804292601881485,0.936574490007753058,12.9842817320504498],"rgb":[0.66666666666666663,0.66666666666666663,0.6],"xyz":[0.36700447041932116,0.395954702646091894,0.358473167142499216],"hpluv":[85.8743202181625236,23.8781611725121081,69.1804292601881485],"hsluv":[85.8743202181625236,17.0695511242654057,69.1804292601881485]},"#aaaaaa":{"lch":[69.6101658300367916,3.6866289517569387e-12,0],"luv":[69.6101658300367916,3.46613397703382525e-12,1.25584564385283521e-12],"rgb":[0.66666666666666663,0.66666666666666663,0.66666666666666663],"xyz":[0.382062163384573716,0.401977779832193,0.437777016759497817],"hpluv":[0,6.72041492281092149e-12,69.6101658300367916],"hsluv":[0,4.48262290109626775e-12,69.6101658300367916]},"#aaaabb":{"lch":[70.0940699613229441,13.6540669730780309,265.874320218191428],"luv":[70.0940699613229441,-0.982334841759501587,-13.618684340418703],"rgb":[0.66666666666666663,0.66666666666666663,0.733333333333333282],"xyz":[0.399199755491910391,0.408832816675127775,0.528035001858139563],"hpluv":[265.874320218191428,24.7183841606301087,70.0940699613229441],"hsluv":[265.874320218191428,17.6184615311656536,70.0940699613229441]},"#aaaacc":{"lch":[70.6323884029978188,27.7441307883788433,265.87432021818438],"luv":[70.6323884029978188,-1.99603725260327192,-27.6722356973355303],"rgb":[0.66666666666666663,0.66666666666666663,0.8],"xyz":[0.41849213093563209,0.416549766852616576,0.629641512528409719],"hpluv":[265.87432021818438,49.8432735452352063,70.6323884029978188],"hsluv":[265.87432021818438,36.467826786828347,70.6323884029978188]},"#aaaadd":{"lch":[71.2250312240615813,42.0886841218373817,265.874320218182],"luv":[71.2250312240615813,-3.02804878123905441,-41.979617097899343],"rgb":[0.66666666666666663,0.66666666666666663,0.866666666666666696],"xyz":[0.440010456486265689,0.425157097072870083,0.742971360428415717],"hpluv":[265.874320218182,74.9845908684596,71.2250312240615813],"hsluv":[265.874320218182,56.4865697219014891,71.2250312240615813]},"#aaaaee":{"lch":[71.8715993709786432,56.5301989351418541,265.874320218180742],"luv":[71.8715993709786432,-4.0670361537863835,-56.3837087159979902],"rgb":[0.66666666666666663,0.66666666666666663,0.933333333333333348],"xyz":[0.463822636752440398,0.4346819691793401,0.86838217649693894],"hpluv":[265.874320218180742,99.8073514218055,71.8715993709786432],"hsluv":[265.874320218180742,77.6546881169827259,71.8715993709786432]},"#aaaaff":{"lch":[72.5714133442747595,70.9376272522327,265.87432021818006],"luv":[72.5714133442747595,-5.10357119085512778,-70.7538021683399],"rgb":[0.66666666666666663,0.66666666666666663,1],"xyz":[0.489993685162271819,0.445150388543272824,1.00621636478872079],"hpluv":[265.87432021818006,124.036757123492009,72.5714133442747595],"hsluv":[265.87432021818006,99.999999999997641,72.5714133442747595]},"#88bb00":{"lch":[70.0174964893220135,84.793654921948729,107.670265811619984],"luv":[70.0174964893220135,-25.7381496181260658,80.7930168347331659],"rgb":[0.533333333333333326,0.733333333333333282,0],"xyz":[0.279226618658270809,0.407742918869184512,0.0639910921330886],"hpluv":[107.670265811619984,153.672481251000221,70.0174964893220135],"hsluv":[107.670265811619984,100.000000000002288,70.0174964893220135]},"#88bb11":{"lch":[70.045943224524,83.6147085810298,107.921872667069366],"luv":[70.045943224524,-25.7299069713944561,79.5574721715938296],"rgb":[0.533333333333333326,0.733333333333333282,0.0666666666666666657],"xyz":[0.280238284157907913,0.408147585069039365,0.0693191970978442318],"hpluv":[107.921872667069366,151.474322676164348,70.045943224524],"hsluv":[107.921872667069366,98.3704135852689,70.045943224524]},"#88bb22":{"lch":[70.0986261940145567,81.4532385975490172,108.403169751544382],"luv":[70.0986261940145567,-25.7149121100318041,77.2876016784231],"rgb":[0.533333333333333326,0.733333333333333282,0.133333333333333331],"xyz":[0.28211364229638497,0.408897728324430165,0.0791960832938234244],"hpluv":[108.403169751544382,147.447759071005407,70.0986261940145567],"hsluv":[108.403169751544382,95.3794817356858431,70.0986261940145567]},"#88bb33":{"lch":[70.185227758130182,77.9617926087192643,109.240455548552404],"luv":[70.185227758130182,-25.6910124331637526,73.6071530961765887],"rgb":[0.533333333333333326,0.733333333333333282,0.2],"xyz":[0.285201393028842676,0.410132828617413292,0.0954582371514345607],"hpluv":[109.240455548552404,140.95335886915251,70.185227758130182],"hsluv":[109.240455548552404,90.5378353052476399,70.185227758130182]},"#88bb44":{"lch":[70.3099541281250708,73.067556170236557,110.558097475340105],"luv":[70.3099541281250708,-25.6581823155790048,68.4143657790612281],"rgb":[0.533333333333333326,0.733333333333333282,0.266666666666666663],"xyz":[0.289659388273061591,0.411916026715100858,0.118937012104321199],"hpluv":[110.558097475340105,131.870330314080604,70.3099541281250708],"hsluv":[110.558097475340105,83.7238240635208655,70.3099541281250708]},"#88bb55":{"lch":[70.4762099132372,66.7907607981454845,112.553407091589676],"luv":[70.4762099132372,-25.6172250438111178,61.682765089203194],"rgb":[0.533333333333333326,0.733333333333333282,0.333333333333333315],"xyz":[0.295621802312423576,0.414300992330845719,0.150339059378295115],"hpluv":[112.553407091589676,120.257778383662554,70.4762099132372],"hsluv":[112.553407091589676,74.9213657205140606,70.4762099132372]},"#88bb66":{"lch":[70.6867901559138687,59.2534136994931373,115.564788197424377],"luv":[70.6867901559138687,-25.56971066242372,53.4523800375924836],"rgb":[0.533333333333333326,0.733333333333333282,0.4],"xyz":[0.303206798085931761,0.417334990640249037,0.190286703785439076],"hpluv":[115.564788197424377,106.368844477737383,70.6867901559138687],"hsluv":[115.564788197424377,64.2082802413586791,70.6867901559138687]},"#88bb77":{"lch":[70.9439811929011483,50.7088031944788611,120.213509646437856],"luv":[70.9439811929011483,-25.5178724843925657,43.8203252530908287],"rgb":[0.533333333333333326,0.733333333333333282,0.466666666666666674],"xyz":[0.312520811106133667,0.421060595848329833,0.239340505691836986],"hpluv":[120.213509646437856,90.6999673798200519,70.9439811929011483],"hsluv":[120.213509646437856,51.7430673824667338,70.9439811929011483]},"#88bb88":{"lch":[71.2496205680497781,41.6266624709187667,127.715012949237462],"luv":[71.2496205680497781,-25.4644588867656232,32.9293237414924747],"rgb":[0.533333333333333326,0.733333333333333282,0.533333333333333326],"xyz":[0.323661228208077467,0.425516762689107431,0.298013369095409153],"hpluv":[127.715012949237462,74.1358655131278397,71.2496205680497781],"hsluv":[127.715012949237462,37.7483692132655,71.2496205680497781]},"#88bb99":{"lch":[71.605136773449729,32.9376822973712677,140.491872589160209],"luv":[71.605136773449729,-25.4125532319559895,20.9545473192710077],"rgb":[0.533333333333333326,0.733333333333333282,0.6],"xyz":[0.336718192317150167,0.430739548332736588,0.366780046736527088],"hpluv":[140.491872589160209,58.3697971606621948,71.605136773449729],"hsluv":[140.491872589160209,40.0947707109065163,71.605136773449729]},"#88bbaa":{"lch":[72.0115788449514298,26.624214972975043,162.310745910586235],"luv":[72.0115788449514298,-25.3653817896029707,8.0898843993512255],"rgb":[0.533333333333333326,0.733333333333333282,0.66666666666666663],"xyz":[0.351775885282402723,0.43676262551883771,0.446083896353525633],"hpluv":[162.310745910586235,46.9152251525928463,72.0115788449514298],"hsluv":[162.310745910586235,42.5902472855224445,72.0115788449514298]},"#88bbbb":{"lch":[72.4696411221425478,25.9090725846495289,192.177050630060563],"luv":[72.4696411221425478,-25.3261303787159093,-5.46508574835939687],"rgb":[0.533333333333333326,0.733333333333333282,0.733333333333333282],"xyz":[0.368913477389739397,0.443617662361772469,0.536341881452167435],"hpluv":[192.177050630060563,45.3664800039612786,72.4696411221425478],"hsluv":[192.177050630060563,45.1889022919971595,72.4696411221425478]},"#88bbcc":{"lch":[72.9796861172365539,31.9509756483125074,217.648879320727843],"luv":[72.9796861172365539,-25.297786835453639,-19.5163220435359541],"rgb":[0.533333333333333326,0.733333333333333282,0.8],"xyz":[0.388205852833461096,0.451334612539261271,0.637948392122437591],"hpluv":[217.648879320727843,55.5547835161394303,72.9796861172365539],"hsluv":[217.648879320727843,47.846459663484211,72.9796861172365539]},"#88bbdd":{"lch":[73.5417671198988501,42.2771793498237827,233.271065498294831],"luv":[73.5417671198988501,-25.2830202693059967,-33.8840490472881442],"rgb":[0.533333333333333326,0.733333333333333282,0.866666666666666696],"xyz":[0.409724178384094695,0.459941942759514777,0.751278240022443589],"hpluv":[233.271065498294831,72.9476439375157781,73.5417671198988501],"hsluv":[233.271065498294831,52.600286903313318,73.5417671198988501]},"#88bbee":{"lch":[74.1556513704433655,54.6140049995012049,242.42172167609948],"luv":[74.1556513704433655,-25.2841013367454792,-48.4087157615106349],"rgb":[0.533333333333333326,0.733333333333333282,0.933333333333333348],"xyz":[0.433536358650269404,0.469466814865984794,0.876689056090966812],"hpluv":[242.42172167609948,93.4542589851821646,74.1556513704433655],"hsluv":[242.42172167609948,75.5983078602269387,74.1556513704433655]},"#88bbff":{"lch":[74.8208441285393206,67.8489412305869877,248.103607922481928],"luv":[74.8208441285393206,-25.3028619495915343,-62.9543009116260492],"rgb":[0.533333333333333326,0.733333333333333282,1],"xyz":[0.459707407060100826,0.479935234229917518,1.01452324438274855],"hpluv":[248.103607922481928,115.069386567942303,74.8208441285393206],"hsluv":[248.103607922481928,99.9999999999973568,74.8208441285393206]},"#333300":{"lch":[20.3279441284931792,22.4095383785379596,85.8743202181747449],"luv":[20.3279441284931792,1.61224273913978733,22.3514671484727536],"rgb":[0.2,0.2,0],"xyz":[0.0254898472303871464,0.0307148568226560392,0.00458585760277267773],"hpluv":[85.8743202181747449,139.887458074797735,20.3279441284931792],"hsluv":[85.8743202181747449,100.000000000002458,20.3279441284931792]},"#333311":{"lch":[20.4867879892499971,17.9332091798965507,85.8743202181741],"luv":[20.4867879892499971,1.2901955319819518,17.8867377399899183],"rgb":[0.2,0.2,0.0666666666666666657],"xyz":[0.0265015127300242681,0.0311195230225108921,0.00991396256752831],"hpluv":[85.8743202181741,111.076827622251201,20.4867879892499971],"hsluv":[85.8743202181741,79.4044220625277717,20.4867879892499971]},"#333322":{"lch":[20.7776374982028358,10.4602453251552614,85.8743202181717749],"luv":[20.7776374982028358,0.752556982220839221,10.4331390411008691],"rgb":[0.2,0.2,0.133333333333333331],"xyz":[0.0283768708685012867,0.0318696662779017134,0.019790848763507507],"hpluv":[85.8743202181717749,63.8829601302186703,20.7776374982028358],"hsluv":[85.8743202181717749,45.6673964981637113,20.7776374982028358]},"#333333":{"lch":[21.246731294981295,1.12524964979295229e-12,0],"luv":[21.246731294981295,1.05794917113478783e-12,3.83314917077821647e-13],"rgb":[0.2,0.2,0.2],"xyz":[0.0314646216009590307,0.0331047665708848263,0.0360530026211186502],"hpluv":[0,6.72041492281092149e-12,21.246731294981295],"hsluv":[0,1.92419399944792236e-12,21.246731294981295]},"#333344":{"lch":[21.9038391599933462,12.2084714240410825,265.874320218182163],"luv":[21.9038391599933462,-0.878332211796974,-12.176834853004598],"rgb":[0.2,0.2,0.266666666666666663],"xyz":[0.0359226168451779043,0.0348879646685724,0.0595317775740052887],"hpluv":[265.874320218182163,70.7262082967351517,21.9038391599933462],"hsluv":[265.874320218182163,13.7757030029577514,21.9038391599933462]},"#333355":{"lch":[22.7485838486986935,25.0264321710322868,265.874320218179776],"luv":[22.7485838486986935,-1.80051382017376982,-24.9615796213830023],"rgb":[0.2,0.2,0.333333333333333315],"xyz":[0.0418850308845399,0.0372729302843172322,0.0909338248479792],"hpluv":[265.874320218179776,139.599512106194084,22.7485838486986935],"hsluv":[265.874320218179776,27.1905063829271256,22.7485838486986935]},"#333366":{"lch":[23.7726526978294,37.7235732610660364,265.874320218179],"luv":[23.7726526978294,-2.71400312032964841,-37.625817820292724],"rgb":[0.2,0.2,0.4],"xyz":[0.0494700266580480746,0.0403069285937205438,0.130881469255123173],"hpluv":[265.874320218179,201.360603518100845,23.7726526978294],"hsluv":[265.874320218179,39.2200280117306193,23.7726526978294]},"#333377":{"lch":[24.9621315786770737,49.9646270765614062,265.874320218178639],"luv":[24.9621315786770737,-3.59467945556105306,-49.8351506319748268],"rgb":[0.2,0.2,0.466666666666666674],"xyz":[0.0587840396782499941,0.0440325338018013601,0.179935271161521082],"hpluv":[265.874320218178639,253.992158426909725,24.9621315786770737],"hsluv":[265.874320218178639,49.4713434217918646,24.9621315786770737]},"#333388":{"lch":[26.2997861111378413,61.6680265106551175,265.874320218178468],"luv":[26.2997861111378413,-4.43667452222106906,-61.5082223194812912],"rgb":[0.2,0.2,0.533333333333333326],"xyz":[0.0699244567801938222,0.048488700642578958,0.238608134565093222],"hpluv":[265.874320218178468,297.541234413863208,26.2997861111378413],"hsluv":[265.874320218178468,57.953618257344317,26.2997861111378413]},"#333399":{"lch":[27.7670269025285634,72.8744236647892336,265.874320218178354],"luv":[27.7670269025285634,-5.24291301488723782,-72.6855796399362788],"rgb":[0.2,0.2,0.6],"xyz":[0.0829814208892665356,0.0537114862862081155,0.307374812206211157],"hpluv":[265.874320218178354,333.031319879373427,27.7670269025285634],"hsluv":[265.874320218178354,64.8662025552495862,27.7670269025285634]},"#3333aa":{"lch":[29.34539826905295,83.6653121043175361,265.874320218178241],"luv":[29.34539826905295,-6.01925794630032573,-83.4485049793509575],"rgb":[0.2,0.2,0.66666666666666663],"xyz":[0.0980391138545190777,0.059734563472309217,0.386678661823209757],"hpluv":[265.874320218178241,361.780166220798492,29.34539826905295],"hsluv":[265.874320218178241,70.4657614516561353,29.34539826905295]},"#3333bb":{"lch":[31.0175640968910713,94.1237197643615247,265.874320218178184],"luv":[31.0175640968910713,-6.77168271864664284,-93.8798111173964855],"rgb":[0.2,0.2,0.733333333333333282],"xyz":[0.115176705961855724,0.0665896003152439686,0.476936646921851504],"hpluv":[265.874320218178184,385.062051502536349,31.0175640968910713],"hsluv":[265.874320218178184,75.0004925607309758,31.0175640968910713]},"#3333cc":{"lch":[32.7678589751368321,104.319620441623087,265.874320218178127],"luv":[32.7678589751368321,-7.50522156082274261,-104.049290523324899],"rgb":[0.2,0.2,0.8],"xyz":[0.134469081405577451,0.0743065504927327702,0.578543157592121604],"hpluv":[265.874320218178127,403.977575952485893,32.7678589751368321],"hsluv":[265.874320218178127,79.524052836351089,32.7678589751368321]},"#3333dd":{"lch":[34.5825131799139243,114.307143948468337,265.874320218178127],"luv":[34.5825131799139243,-8.22376881440244745,-114.010932739554079],"rgb":[0.2,0.2,0.866666666666666696],"xyz":[0.155987406956211,0.0829138807129863,0.691873005492127602],"hpluv":[265.874320218178127,419.426773039758132,34.5825131799139243],"hsluv":[265.874320218178127,86.1542613798901584,34.5825131799139243]},"#3333ee":{"lch":[36.4496605331747929,124.126383834434506,265.87432021817807],"luv":[36.4496605331747929,-8.93020898923314732,-123.80472741871364],"rgb":[0.2,0.2,0.933333333333333348],"xyz":[0.179799587222385732,0.092438752819456349,0.817283821560650825],"hpluv":[265.87432021817807,432.12554656995303,36.4496605331747929],"hsluv":[265.87432021817807,92.9362870993519,36.4496605331747929]},"#3333ff":{"lch":[38.3592184432327414,133.806417871427385,265.87432021817807],"luv":[38.3592184432327414,-9.62663407069321231,-133.45967698167118],"rgb":[0.2,0.2,1],"xyz":[0.205970635632217125,0.102907172183389045,0.95511800985243267],"hpluv":[265.87432021817807,442.635784237250618,38.3592184432327414],"hsluv":[265.87432021817807,99.99999999999946,38.3592184432327414]},"#aabb00":{"lch":[72.2864137555308162,81.0402066187271686,93.9104624709461291],"luv":[72.2864137555308162,-5.52673715975233737,80.8515322376329664],"rgb":[0.66666666666666663,0.733333333333333282,0],"xyz":[0.343467394669040582,0.440867068999738376,0.0670023785085933632],"hpluv":[93.9104624709461291,142.260125307220505,72.2864137555308162],"hsluv":[93.9104624709461291,100.00000000000226,72.2864137555308162]},"#aabb11":{"lch":[72.3134178153803759,79.8795424406406198,94.0047051989067199],"luv":[72.3134178153803759,-5.5786590117117969,79.6845020324350912],"rgb":[0.66666666666666663,0.733333333333333282,0.0666666666666666657],"xyz":[0.344479060168677687,0.441271735199593229,0.072330483473349],"hpluv":[94.0047051989067199,140.170301198642704,72.3134178153803759],"hsluv":[94.0047051989067199,98.4998030851846522,72.3134178153803759]},"#aabb22":{"lch":[72.3634325080893319,77.7463008610920667,94.1853407287125179],"luv":[72.3634325080893319,-5.67416055225396132,77.5389656857163],"rgb":[0.66666666666666663,0.733333333333333282,0.133333333333333331],"xyz":[0.346354418307154743,0.44202187845498403,0.0822073696693281925],"hpluv":[94.1853407287125179,136.332658010127801,72.3634325080893319],"hsluv":[94.1853407287125179,95.7441801677588131,72.3634325080893319]},"#aabb33":{"lch":[72.4456578556837343,74.284969405898,94.5007685828256427],"luv":[72.4456578556837343,-5.82932493434540167,74.0558954469191377],"rgb":[0.66666666666666663,0.733333333333333282,0.2],"xyz":[0.349442169039612449,0.443256978747967156,0.0984695235269393288],"hpluv":[94.5007685828256427,130.115164427369336,72.4456578556837343],"hsluv":[94.5007685828256427,91.2774770665997721,72.4456578556837343]},"#aabb44":{"lch":[72.5641031469497193,69.3968632274838768,95.0004815565242922],"luv":[72.5641031469497193,-6.04891620231322502,69.1327363742496317],"rgb":[0.66666666666666663,0.733333333333333282,0.266666666666666663],"xyz":[0.353900164283831364,0.445040176845654722,0.121948298479825967],"hpluv":[95.0004815565242922,121.354905136961719,72.5641031469497193],"hsluv":[95.0004815565242922,84.978619462346515,72.5641031469497193]},"#aabb55":{"lch":[72.7220260779140659,63.0543258899363366,95.7659530899235705],"luv":[72.7220260779140659,-6.33475857174489843,62.7353078200147181],"rgb":[0.66666666666666663,0.733333333333333282,0.333333333333333315],"xyz":[0.359862578323193349,0.447425142461399583,0.153350345753799883],"hpluv":[95.7659530899235705,110.024206089370551,72.7220260779140659],"hsluv":[95.7659530899235705,76.8198788639563475,72.7220260779140659]},"#aabb66":{"lch":[72.922116394456026,55.2958006214329245,96.9449007941817342],"luv":[72.922116394456026,-6.68608027099689206,54.8900892418207675],"rgb":[0.66666666666666663,0.733333333333333282,0.4],"xyz":[0.367447574096701535,0.450459140770802902,0.193297990160943844],"hpluv":[96.9449007941817342,96.2215198645611167,72.922116394456026],"hsluv":[96.9449007941817342,66.8569788454327778,72.922116394456026]},"#aabb77":{"lch":[73.1665925415562555,46.2248747385797927,98.8352478745929801],"luv":[73.1665925415562555,-7.09985217623529596,45.6763740206356488],"rgb":[0.66666666666666663,0.733333333333333282,0.466666666666666674],"xyz":[0.37676158711690344,0.454184745978883697,0.242351792067341754],"hpluv":[98.8352478745929801,80.1682197570307267,73.1665925415562555],"hsluv":[98.8352478745929801,55.2184354086421223,73.1665925415562555]},"#aabb88":{"lch":[73.4572589636137,36.0214199172978056,102.133241078873],"luv":[73.4572589636137,-7.57119112276486117,35.2167539367396216],"rgb":[0.66666666666666663,0.733333333333333282,0.533333333333333326],"xyz":[0.387902004218847241,0.458640912819661295,0.301024655470913949],"hpluv":[102.133241078873,62.2250760292602862,73.4572589636137],"hsluv":[102.133241078873,42.0922357464751116,73.4572589636137]},"#aabb99":{"lch":[73.7955437608147236,25.0118273668521454,108.880774444702425],"luv":[73.7955437608147236,-8.09382587885112592,23.6660408786948082],"rgb":[0.66666666666666663,0.733333333333333282,0.6],"xyz":[0.40095896832792,0.463863698463290453,0.369791333112031828],"hpluv":[108.880774444702425,43.0085312761643337,73.7955437608147236],"hsluv":[108.880774444702425,27.7101400322224,73.7955437608147236]},"#aabbaa":{"lch":[74.1825262226786464,14.1574418893213867,127.715012949227386],"luv":[74.1825262226786464,-8.66059336811296632,11.1994322785447302],"rgb":[0.66666666666666663,0.733333333333333282,0.66666666666666663],"xyz":[0.416016661293172496,0.469886775649391575,0.449095182729030429],"hpluv":[127.715012949227386,24.2171200924073027,74.1825262226786464],"hsluv":[127.715012949227386,12.3308304853939106,74.1825262226786464]},"#aabbbb":{"lch":[74.6189593067414734,9.47715438031144153,192.177050630059],"luv":[74.6189593067414734,-9.26392276955498417,-1.99904729008021675],"rgb":[0.66666666666666663,0.733333333333333282,0.733333333333333282],"xyz":[0.43315425340050917,0.476741812492326333,0.539353167827672175],"hpluv":[192.177050630059,16.1164020856357908,74.6189593067414734],"hsluv":[192.177050630059,16.0533177597809029,74.6189593067414734]},"#aabbcc":{"lch":[75.1052899078477623,18.5983929047544514,237.852316168687679],"luv":[75.1052899078477623,-9.89626817417043547,-15.7468757175676597],"rgb":[0.66666666666666663,0.733333333333333282,0.8],"xyz":[0.452446628844230925,0.484458762669815135,0.640959678497942331],"hpluv":[237.852316168687679,31.4227520087917149,75.1052899078477623],"hsluv":[237.852316168687679,25.8004515395991127,75.1052899078477623]},"#aabbdd":{"lch":[75.6416785331857682,31.6798915222297,250.547006926215033],"luv":[75.6416785331857682,-10.5504613433008458,-29.8714461033234144],"rgb":[0.66666666666666663,0.733333333333333282,0.866666666666666696],"xyz":[0.473964954394864413,0.493066092890068641,0.754289526397948329],"hpluv":[250.547006926215033,53.1449318902714438,75.6416785331857682],"hsluv":[250.547006926215033,48.9122949421499484,75.6416785331857682]},"#aabbee":{"lch":[76.2280192594463699,45.6175545861468166,255.761586302643195],"luv":[76.2280192594463699,-11.2199701465387403,-44.2162137267639],"rgb":[0.66666666666666663,0.733333333333333282,0.933333333333333348],"xyz":[0.497777134661039178,0.502590964996538658,0.879700342466471552],"hpluv":[255.761586302643195,76.8288153841444483,76.2280192594463699],"hsluv":[255.761586302643195,73.6249760737912311,76.2280192594463699]},"#aabbff":{"lch":[76.863960378353255,59.8395450787859247,258.530312903596609],"luv":[76.863960378353255,-11.8990616804050067,-58.6445520603744583],"rgb":[0.66666666666666663,0.733333333333333282,1],"xyz":[0.523948183070870543,0.513059384360471382,1.01753453075825329],"hpluv":[258.530312903596609,104.151140840900069,76.863960378353255],"hsluv":[258.530312903596609,99.9999999999969305,76.863960378353255]},"#88cc00":{"lch":[75.0884647575288,93.9986167747887,111.475410134903882],"luv":[75.0884647575288,-34.413070492851,87.4727416674912348],"rgb":[0.533333333333333326,0.8,0],"xyz":[0.317450361967887784,0.484190405488419406,0.0767323399029605363],"hpluv":[111.475410134903882,158.85012628624969,75.0884647575288],"hsluv":[111.475410134903882,100.000000000002288,75.0884647575288]},"#88cc11":{"lch":[75.1138336746799,92.9472917959209752,111.713770271434171],"luv":[75.1138336746799,-34.3877142984514421,86.3520941119794685],"rgb":[0.533333333333333326,0.8,0.0666666666666666657],"xyz":[0.318462027467524889,0.484595071688274259,0.082060444867716173],"hpluv":[111.713770271434171,157.020421425354783,75.1138336746799],"hsluv":[111.713770271434171,98.6248626695712289,75.1138336746799]},"#88cc22":{"lch":[75.1608235532889495,91.0175657840787551,112.166724150973],"luv":[75.1608235532889495,-34.3412008388942667,84.2904455214339805],"rgb":[0.533333333333333326,0.8,0.133333333333333331],"xyz":[0.320337385606001945,0.48534521494366506,0.0919373310636953656],"hpluv":[112.166724150973,153.664310911800271,75.1608235532889495],"hsluv":[112.166724150973,96.0970283406349,75.1608235532889495]},"#88cc33":{"lch":[75.2380863503033908,87.8942550668024865,112.94545296029041],"luv":[75.2380863503033908,-34.2659803117460342,80.9397471396048331],"rgb":[0.533333333333333326,0.8,0.2],"xyz":[0.323425136338459651,0.486580315236648187,0.108199484921306516],"hpluv":[112.94545296029041,148.2388630129999,75.2380863503033908],"hsluv":[112.94545296029041,91.9942509365254324,75.2380863503033908]},"#88cc44":{"lch":[75.3494055810326415,83.5027286146262,114.147802574759467],"luv":[75.3494055810326415,-34.1602904869016655,76.1956707427555244],"rgb":[0.533333333333333326,0.8,0.266666666666666663],"xyz":[0.327883131582678566,0.488363513334335753,0.131678259874193154],"hpluv":[114.147802574759467,140.624231819797672,75.3494055810326415],"hsluv":[114.147802574759467,86.1974134191241177,75.3494055810326415]},"#88cc55":{"lch":[75.4978684165763241,77.8461227965530469,115.917002828128631],"luv":[75.4978684165763241,-34.0241050685150555,70.0169915716368365],"rgb":[0.533333333333333326,0.8,0.333333333333333315],"xyz":[0.333845545622040552,0.490748478950080613,0.16308030714816707],"hpluv":[115.917002828128631,130.840327774204638,75.4978684165763241],"hsluv":[115.917002828128631,78.6694108520956092,75.4978684165763241]},"#88cc66":{"lch":[75.6860396561484663,71.0106875190110287,118.477611184333398],"luv":[75.6860396561484663,-33.8589835890750237,62.4186428259808963],"rgb":[0.533333333333333326,0.8,0.4],"xyz":[0.341430541395548737,0.493782477259483932,0.203027951555311],"hpluv":[118.477611184333398,119.054896833398359,75.6860396561484663],"hsluv":[118.477611184333398,69.4467240035668141,75.6860396561484663]},"#88cc77":{"lch":[75.9160535408259705,63.1826013081171638,122.199343200922883],"luv":[75.9160535408259705,-33.6678964132470924,53.4650713940171656],"rgb":[0.533333333333333326,0.8,0.466666666666666674],"xyz":[0.350744554415750642,0.497508082467564727,0.252081753461708913],"hpluv":[122.199343200922883,105.609553255175555,75.9160535408259705],"hsluv":[122.199343200922883,58.6308874961384063,75.9160535408259705]},"#88cc88":{"lch":[76.1896681333118124,54.6887581458639929,127.715012949238314],"luv":[76.1896681333118124,-33.4549913615237,43.2622678616965288],"rgb":[0.533333333333333326,0.8,0.533333333333333326],"xyz":[0.361884971517694443,0.50196424930834227,0.310754616865281108],"hpluv":[127.715012949238314,91.92606633748386,76.1896681333118124],"hsluv":[127.715012949238314,46.3779062484352878,76.1896681333118124]},"#88cc99":{"lch":[76.5083007543168492,46.0935908553499587,136.122445502284393],"luv":[76.5083007543168492,-33.2253063874269188,31.9483666781575764],"rgb":[0.533333333333333326,0.8,0.6],"xyz":[0.374941935626767142,0.507187034951971483,0.379521294506399],"hpluv":[136.122445502284393,78.7570782006024643,76.5083007543168492],"hsluv":[136.122445502284393,48.1280089891728622,76.5083007543168492]},"#88ccaa":{"lch":[76.8730534179438223,38.4110576559043437,149.173477570542047],"luv":[76.8730534179438223,-32.9844501501569098,19.6833787378341469],"rgb":[0.533333333333333326,0.8,0.66666666666666663],"xyz":[0.389999628592019698,0.513210112138072549,0.458825144123397588],"hpluv":[149.173477570542047,66.8865090410736229,76.8730534179438223],"hsluv":[149.173477570542047,50.0095890879800891,76.8730534179438223]},"#88ccbb":{"lch":[77.2847330465352087,33.404634806871222,168.536638924911273],"luv":[77.2847330465352087,-32.73827895277973,6.638879257104497],"rgb":[0.533333333333333326,0.8,0.733333333333333282],"xyz":[0.407137220699356372,0.520065148981007308,0.54908312922203939],"hpluv":[168.536638924911273,59.444025112992847,77.2847330465352087],"hsluv":[168.536638924911273,51.9915664660802292,77.2847330465352087]},"#88cccc":{"lch":[77.7438691793350074,33.2404934388306,192.177050630060876],"luv":[77.7438691793350074,-32.4925976386916417,-7.01152642061846443],"rgb":[0.533333333333333326,0.8,0.8],"xyz":[0.426429596143078071,0.527782099158496165,0.650689639892309546],"hpluv":[192.177050630060876,60.6231146417445359,77.7438691793350074],"hsluv":[192.177050630060876,54.0427382519684585,77.7438691793350074]},"#88ccdd":{"lch":[78.2507307533704,38.5418325774740964,213.193277164244591],"luv":[78.2507307533704,-32.2529061156020589,-21.1003058159876602],"rgb":[0.533333333333333326,0.8,0.866666666666666696],"xyz":[0.44794792169371167,0.536389429378749671,0.764019487792315544],"hpluv":[213.193277164244591,72.2588367593471759,78.2507307533704],"hsluv":[213.193277164244591,56.1332045791076126,78.2507307533704]},"#88ccee":{"lch":[78.8053428571366368,47.78945312574524,227.924421751618695],"luv":[78.8053428571366368,-32.0242046898233,-35.4722728908382834],"rgb":[0.533333333333333326,0.8,0.933333333333333348],"xyz":[0.47176010195988638,0.545914301485219688,0.889430303860838767],"hpluv":[227.924421751618695,92.3999074491721473,78.8053428571366368],"hsluv":[227.924421751618695,70.4270935368067796,78.8053428571366368]},"#88ccff":{"lch":[79.4075039272108114,59.2521837878726885,237.529017152572294],"luv":[79.4075039272108114,-31.8108626015509195,-49.9889018100727327],"rgb":[0.533333333333333326,0.8,1],"xyz":[0.497931150369717801,0.556382720849152412,1.02726449215262061],"hpluv":[237.529017152572294,118.549697277888072,79.4075039272108114],"hsluv":[237.529017152572294,99.999999999996561,79.4075039272108114]},"#334400":{"lch":[26.2681529832905483,31.0251081485104194,104.276907196552472],"luv":[26.2681529832905483,-7.65105337509169647,30.0669040288198879],"rgb":[0.2,0.266666666666666663,0],"xyz":[0.034322417713353183,0.0483799977885883553,0.00753004776376127276],"hpluv":[104.276907196552472,149.872894059772364,26.2681529832905483],"hsluv":[104.276907196552472,100.000000000002302,26.2681529832905483]},"#334411":{"lch":[26.3856741683463127,27.3886432490873446,106.295788944443402],"luv":[26.3856741683463127,-7.68514827558869662,26.2883296351821087],"rgb":[0.2,0.266666666666666663,0.0666666666666666657],"xyz":[0.0353340832129903082,0.0487846639884432082,0.0128581527285169042],"hpluv":[106.295788944443402,131.716945387015755,26.3856741683463127],"hsluv":[106.295788944443402,86.6296124974134614,26.3856741683463127]},"#334422":{"lch":[26.6018195362025054,21.2226436820006832,111.412237748000109],"luv":[26.6018195362025054,-7.74787020962619799,19.7578114189793403],"rgb":[0.2,0.266666666666666663,0.133333333333333331],"xyz":[0.0372094413514673233,0.0495348072438340295,0.0227350389244961],"hpluv":[111.412237748000109,101.234249163259577,26.6018195362025054],"hsluv":[111.412237748000109,63.7673799144094,26.6018195362025054]},"#334433":{"lch":[26.9529945323855813,12.8320575385027151,127.715012949236225],"luv":[26.9529945323855813,-7.8498102472204323,10.150969399721264],"rgb":[0.2,0.266666666666666663,0.2],"xyz":[0.0402971920839250639,0.0507699075368171424,0.038997192782107247],"hpluv":[127.715012949236225,60.4127494816677171,26.9529945323855813],"hsluv":[127.715012949236225,30.7608572023581708,26.9529945323855813]},"#334444":{"lch":[27.4501004194092673,8.17817622085537721,192.177050630060421],"luv":[27.4501004194092673,-7.99417102070306651,-1.72504956193021397],"rgb":[0.2,0.266666666666666663,0.266666666666666663],"xyz":[0.0447551873281439444,0.0525531056345047154,0.0624759677349938855],"hpluv":[192.177050630060421,37.8052272806132379,27.4501004194092673],"hsluv":[192.177050630060421,37.6572465300637518,27.4501004194092673]},"#334455":{"lch":[28.0976851129048839,16.9879579660521678,241.20654356816641],"luv":[28.0976851129048839,-8.1823109542023289,-14.8875956186718064],"rgb":[0.2,0.266666666666666663,0.333333333333333315],"xyz":[0.0507176013675059364,0.0549380712502495483,0.0938780150089678],"hpluv":[241.20654356816641,76.720241835207986,28.0976851129048839],"hsluv":[241.20654356816641,44.9859363973003568,28.0976851129048839]},"#334466":{"lch":[28.8949601880565652,29.7043747380071501,253.545194757545516],"luv":[28.8949601880565652,-8.41402976058801855,-28.487786536758108],"rgb":[0.2,0.266666666666666663,0.4],"xyz":[0.0583025971410141147,0.0579720695596528598,0.133825659416111742],"hpluv":[253.545194757545516,130.448064952476017,28.8949601880565652],"hsluv":[253.545194757545516,52.1427730716471629,28.8949601880565652]},"#334477":{"lch":[29.8367962202096138,42.8398018741040758,258.299336276781332],"luv":[29.8367962202096138,-8.68785350705267589,-41.9496105590085691],"rgb":[0.2,0.266666666666666663,0.466666666666666674],"xyz":[0.0676166101612160342,0.0616976747677336762,0.182879461322509651],"hpluv":[258.299336276781332,182.19421868795277,29.8367962202096138],"hsluv":[258.299336276781332,58.7347196438674857,29.8367962202096138]},"#334488":{"lch":[30.9147794404111025,55.6960507233291082,260.699324576281469],"luv":[30.9147794404111025,-9.00134255207500189,-54.9638599302837605],"rgb":[0.2,0.266666666666666663,0.533333333333333326],"xyz":[0.0787570272631598622,0.066153841608511274,0.241552324726081818],"hpluv":[260.699324576281469,228.611238042833349,30.9147794404111025],"hsluv":[260.699324576281469,64.5716173949788583,30.9147794404111025]},"#334499":{"lch":[32.1182691124294664,68.0605660836803281,262.102678541403236],"luv":[32.1182691124294664,-9.35140200670356769,-67.4150720250304],"rgb":[0.2,0.266666666666666663,0.6],"xyz":[0.0918139913722325618,0.0713766272521404316,0.310319002367199726],"hpluv":[262.102678541403236,268.895014901030436,32.1182691124294664],"hsluv":[262.102678541403236,69.6097770986353623,32.1182691124294664]},"#3344aa":{"lch":[33.435366318838156,79.8975403264742852,263.001802037450773],"luv":[33.435366318838156,-9.73456660877536528,-79.3023023824649158],"rgb":[0.2,0.266666666666666663,0.66666666666666663],"xyz":[0.106871684337485118,0.07739970443824154,0.389622851984198326],"hpluv":[263.001802037450773,303.226149032408784,33.435366318838156],"hsluv":[263.001802037450773,73.891501645941716,33.435366318838156]},"#3344bb":{"lch":[34.8537252521307721,91.2457354427413492,263.615054402289786],"luv":[34.8537252521307721,-10.1472390443123768,-90.6797539490724773],"rgb":[0.2,0.266666666666666663,0.733333333333333282],"xyz":[0.124009276444821764,0.0842547412811763,0.479880837082840073],"hpluv":[263.615054402289786,332.202349056841342,34.8537252521307721],"hsluv":[263.615054402289786,77.4995146205371412,34.8537252521307721]},"#3344cc":{"lch":[36.3611746115969083,102.170263494902102,264.052905077601281],"luv":[36.3611746115969083,-10.5858721727669973,-101.620382074461631],"rgb":[0.2,0.266666666666666663,0.8],"xyz":[0.143301651888543491,0.0919716914586651,0.581487347753110284],"hpluv":[264.052905077601281,356.55451128807033,36.3611746115969083],"hsluv":[264.052905077601281,80.5286468598271767,36.3611746115969083]},"#3344dd":{"lch":[37.9461503215655611,112.73940211649635,264.376684246138268],"luv":[37.9461503215655611,-11.0470957042534899,-112.196855865421313],"rgb":[0.2,0.266666666666666663,0.866666666666666696],"xyz":[0.164819977439177034,0.100579021678918634,0.694817195653116282],"hpluv":[264.376684246138268,377.005191833442723,37.9461503215655611],"hsluv":[264.376684246138268,84.1625325255073,37.9461503215655611]},"#3344ee":{"lch":[39.5979632159824462,123.014623823289881,264.622878978329823],"luv":[39.5979632159824462,-11.5277944422180187,-122.473293536523641],"rgb":[0.2,0.266666666666666663,0.933333333333333348],"xyz":[0.188632157705351772,0.110103893785388651,0.820228011721639505],"hpluv":[264.622878978329823,394.205992168221826,39.5979632159824462],"hsluv":[264.622878978329823,91.9832832027675664,39.5979632159824462]},"#3344ff":{"lch":[41.3069357297154482,133.047388274610427,264.814390787746049],"luv":[41.3069357297154482,-12.0251481753234213,-132.502842754623401],"rgb":[0.2,0.266666666666666663,1],"xyz":[0.214803206115183165,0.120572313149321361,0.95806220001342135],"hpluv":[264.814390787746049,408.716999433792864,41.3069357297154482],"hsluv":[264.814390787746049,99.9999999999994458,41.3069357297154482]},"#aacc00":{"lch":[77.1199831352121,88.8460909387721,100.173289143969555],"luv":[77.1199831352121,-15.692520471132994,87.4492577233428108],"rgb":[0.66666666666666663,0.8,0],"xyz":[0.381691137978657502,0.517314555618973326,0.0797436262784653],"hpluv":[100.173289143969555,156.730643533439519,77.1199831352121],"hsluv":[100.173289143969555,100.000000000002373,77.1199831352121]},"#aacc11":{"lch":[77.1442576556984676,87.800209961997254,100.311366942902],"luv":[77.1442576556984676,-15.7160097521192821,86.3821967007212237],"rgb":[0.66666666666666663,0.8,0.0666666666666666657],"xyz":[0.382702803478294606,0.517719221818828235,0.085071731243220941],"hpluv":[100.311366942902,155.08420894585916,77.1442576556984676],"hsluv":[100.311366942902,98.7181568051142,77.1442576556984676]},"#aacc22":{"lch":[77.1892227090432641,85.8765024717939411,100.574350259733862],"luv":[77.1892227090432641,-15.7593085348968636,84.4181134075499102],"rgb":[0.66666666666666663,0.8,0.133333333333333331],"xyz":[0.384578161616771663,0.518469365074219,0.0949486174392001336],"hpluv":[100.574350259733862,152.047171814071362,77.1892227090432641],"hsluv":[100.574350259733862,96.3604791744028404,77.1892227090432641]},"#aacc33":{"lch":[77.2631626362465482,82.7513779572315116,101.028364552392574],"luv":[77.2631626362465482,-15.8299191574657527,81.2231753460099668],"rgb":[0.66666666666666663,0.8,0.2],"xyz":[0.387665912349229369,0.519704465367202162,0.111210771296811284],"hpluv":[101.028364552392574,147.088857703341773,77.2631626362465482],"hsluv":[101.028364552392574,92.5301726329619,77.2631626362465482]},"#aacc44":{"lch":[77.3697083481362569,78.3307208035327278,101.734312614214147],"luv":[77.3697083481362569,-15.9304072801298968,76.6937021240348],"rgb":[0.66666666666666663,0.8,0.266666666666666663],"xyz":[0.392123907593448284,0.521487663464889728,0.134689546249697922],"hpluv":[101.734312614214147,140.021584702469511,77.3697083481362569],"hsluv":[101.734312614214147,87.1104907412273377,77.3697083481362569]},"#aacc55":{"lch":[77.5118305539362495,72.5836718124644875,102.784946304826448],"luv":[77.5118305539362495,-16.0622064150904862,70.7841432727597493],"rgb":[0.66666666666666663,0.8,0.333333333333333315],"xyz":[0.398086321632810269,0.523872629080634478,0.16609159352367181],"hpluv":[102.784946304826448,130.736122666873541,77.5118305539362495],"hsluv":[102.784946304826448,80.0585735006639,77.5118305539362495]},"#aacc66":{"lch":[77.6920071630818114,65.5409770618580865,104.333567867024144],"luv":[77.6920071630818114,-16.225762577214013,63.5007425390513731],"rgb":[0.66666666666666663,0.8,0.4],"xyz":[0.405671317406318455,0.526906627390037796,0.2060392379308158],"hpluv":[104.333567867024144,119.198186871977654,77.6920071630818114],"hsluv":[104.333567867024144,71.3979720959128,77.6920071630818114]},"#aacc77":{"lch":[77.912311817127474,57.2990991241466574,106.653185897117098],"luv":[77.912311817127474,-16.4206512214041886,54.8958010589496865],"rgb":[0.66666666666666663,0.8,0.466666666666666674],"xyz":[0.41498533042652036,0.530632232598118647,0.255093039837213709],"hpluv":[106.653185897117098,105.457526508453554,77.912311817127474],"hsluv":[106.653185897117098,61.2114900666368769,77.912311817127474]},"#aacc88":{"lch":[78.1744663257663,48.0371110011961946,110.274462486898784],"luv":[78.1744663257663,-16.6457037997795574,45.0608985524173633],"rgb":[0.66666666666666663,0.8,0.533333333333333326],"xyz":[0.42612574752846416,0.53508839943889619,0.313765903240785848],"hpluv":[110.274462486898784,89.6844859178180513,78.1744663257663],"hsluv":[110.274462486898784,49.6324036967210418,78.1744663257663]},"#aacc99":{"lch":[78.4798746855988583,38.0746183254558517,116.349336294343573],"luv":[78.4798746855988583,-16.8991518201031816,34.1188397866961779],"rgb":[0.66666666666666663,0.8,0.6],"xyz":[0.43918271163753686,0.540311185082525403,0.382532580881903783],"hpluv":[116.349336294343573,72.2917022308792383,78.4798746855988583],"hsluv":[116.349336294343573,36.8338589582570535,78.4798746855988583]},"#aaccaa":{"lch":[78.8296472340984593,28.0820980315160789,127.715012949234293],"luv":[78.8296472340984593,-17.1787837008838338,22.2147163027108476],"rgb":[0.66666666666666663,0.8,0.66666666666666663],"xyz":[0.454240404602789416,0.546334262268626469,0.461836430498902384],"hpluv":[127.715012949234293,54.3703242245777503,78.8296472340984593],"hsluv":[127.715012949234293,23.0170216670413055,78.8296472340984593]},"#aaccbb":{"lch":[79.2246195207133,19.8998018903229301,151.462718702096396],"luv":[79.2246195207133,-17.4821042808439238,9.50674209115818236],"rgb":[0.66666666666666663,0.8,0.733333333333333282],"xyz":[0.47137799671012609,0.553189299111561228,0.55209441559754413],"hpluv":[151.462718702096396,39.3999249764932173,79.2246195207133],"hsluv":[151.462718702096396,25.9022649263565761,79.2246195207133]},"#aacccc":{"lch":[79.665368512397464,18.216345023755192,192.177050630059796],"luv":[79.665368512397464,-17.80648564660072,-3.84243346616347603],"rgb":[0.66666666666666663,0.8,0.8],"xyz":[0.490670372153847789,0.560906249289050085,0.653700926267814286],"hpluv":[192.177050630059796,36.9937893704297878,79.665368512397464],"hsluv":[192.177050630059796,28.9019874878943099,79.665368512397464]},"#aaccdd":{"lch":[80.1522276755556504,25.332665849504842,224.238778454296437],"luv":[80.1522276755556504,-18.1492995031005968,-17.6733382978265112],"rgb":[0.66666666666666663,0.8,0.866666666666666696],"xyz":[0.512188697704481388,0.569513579509303591,0.767030774167820284],"hpluv":[224.238778454296437,52.9363308364733882,80.1522276755556504],"hsluv":[224.238778454296437,37.9969627853472787,80.1522276755556504]},"#aaccee":{"lch":[80.6853018377357216,36.8247992939862954,239.828051209207672],"luv":[80.6853018377357216,-18.5080244281037345,-31.8358112007702587],"rgb":[0.66666666666666663,0.8,0.933333333333333348],"xyz":[0.536000877970656098,0.579038451615773608,0.892441590236343507],"hpluv":[239.828051209207672,79.4490805971393854,80.6853018377357216],"hsluv":[239.828051209207672,67.7857500876551313,80.6853018377357216]},"#aaccff":{"lch":[81.2644823279674284,49.9029399741520834,247.769045833277914],"luv":[81.2644823279674284,-18.8803249793402337,-46.1934708236816434],"rgb":[0.66666666666666663,0.8,1],"xyz":[0.562171926380487519,0.589506870979706332,1.03027577852812535],"hpluv":[247.769045833277914,111.562078640814406,81.2644823279674284],"hsluv":[247.769045833277914,99.9999999999962625,81.2644823279674284]},"#88dd00":{"lch":[80.1491214608994085,103.252993693735874,114.339076779275487],"luv":[80.1491214608994085,-42.5542610136121837,94.0761158653142218],"rgb":[0.533333333333333326,0.866666666666666696,0],"xyz":[0.360084352515062933,0.569458386582770926,0.0909436700853518548],"hpluv":[114.339076779275487,215.722602995807392,80.1491214608994085],"hsluv":[114.339076779275487,100.000000000002288,80.1491214608994085]},"#88dd11":{"lch":[80.1718911006532551,102.308936337717924,114.55620444532569],"luv":[80.1718911006532551,-42.518128048011647,93.0555062414261727],"rgb":[0.533333333333333326,0.866666666666666696,0.0666666666666666657],"xyz":[0.361096018014700038,0.569863052782625834,0.0962717750501074915],"hpluv":[114.55620444532569,214.038948901986089,80.1718911006532551],"hsluv":[114.55620444532569,98.8288322371321186,80.1718911006532551]},"#88dd22":{"lch":[80.2140714473537315,100.574216022873557,114.966813202702411],"luv":[80.2140714473537315,-42.4516967311958453,91.1757992740300125],"rgb":[0.533333333333333326,0.866666666666666696,0.133333333333333331],"xyz":[0.362971376153177094,0.570613196038016635,0.106148661246086684],"hpluv":[114.966813202702411,210.937294411552,80.2140714473537315],"hsluv":[114.966813202702411,96.6732644510524,80.2140714473537315]},"#88dd33":{"lch":[80.283440325316,97.7612202004146695,115.666709499512805],"luv":[80.283440325316,-42.3438510501166832,88.1150069699792908],"rgb":[0.533333333333333326,0.866666666666666696,0.2],"xyz":[0.3660591268856348,0.571848296330999761,0.122410815103697834],"hpluv":[115.666709499512805,205.88559104418357,80.283440325316],"hsluv":[115.666709499512805,93.1672856447162303,80.283440325316]},"#88dd44":{"lch":[80.3834168948860821,93.7943752395521,116.732715571790365],"luv":[80.3834168948860821,-42.1914333437924896,83.7691338081886698],"rgb":[0.533333333333333326,0.866666666666666696,0.266666666666666663],"xyz":[0.370517122129853715,0.573631494428687327,0.145889590056584473],"hpluv":[116.732715571790365,198.714220363605108,80.3834168948860821],"hsluv":[116.732715571790365,88.1979852499996184,80.3834168948860821]},"#88dd55":{"lch":[80.5168087418625475,88.6629430887769,118.270203219525229],"luv":[80.5168087418625475,-41.9934519908601374,78.0875628192801088],"rgb":[0.533333333333333326,0.866666666666666696,0.333333333333333315],"xyz":[0.376479536169215701,0.576016460044432077,0.17729163733055836],"hpluv":[118.270203219525229,189.352414361266369,80.5168087418625475],"hsluv":[118.270203219525229,81.7170886524359474,80.5168087418625475]},"#88dd66":{"lch":[80.6859701106092757,82.4238546620344437,120.433562333743893],"luv":[80.6859701106092757,-41.7508898851674672,71.0672569552589266],"rgb":[0.533333333333333326,0.866666666666666696,0.4],"xyz":[0.384064531942723886,0.579050458353835396,0.21723928173770235],"hpluv":[120.433562333743893,177.835721711884361,80.6859701106092757],"hsluv":[120.433562333743893,73.7344666622195604,80.6859701106092757]},"#88dd77":{"lch":[80.8928858528116734,75.2114908165065827,123.458458278308711],"luv":[80.8928858528116734,-41.4665196955592208,62.7478772165182832],"rgb":[0.533333333333333326,0.866666666666666696,0.466666666666666674],"xyz":[0.393378544962925791,0.582776063561916247,0.266293083644100259],"hpluv":[123.458458278308711,164.332036041008877,80.8928858528116734],"hsluv":[123.458458278308711,64.312379996729149,80.8928858528116734]},"#88dd88":{"lch":[81.1392211885512,67.2590471315356524,127.71501294923884],"luv":[81.1392211885512,-41.1446687958856145,53.2061617739807531],"rgb":[0.533333333333333326,0.866666666666666696,0.533333333333333326],"xyz":[0.404518962064869592,0.587232230402693789,0.324965947047672399],"hpluv":[127.71501294923884,149.199726190229825,81.1392211885512],"hsluv":[127.71501294923884,53.5585775308776633,81.1392211885512]},"#88dd99":{"lch":[81.4263538436063072,58.943488890057,133.791370377408185],"luv":[81.4263538436063072,-40.7909253788266,42.5492102073736831],"rgb":[0.533333333333333326,0.866666666666666696,0.6],"xyz":[0.417575926173942347,0.592455016046323,0.393732624688790334],"hpluv":[133.791370377408185,133.11083868071384,81.4263538436063072],"hsluv":[133.791370377408185,54.8771054313611515,81.4263538436063072]},"#88ddaa":{"lch":[81.7553965772464437,50.8758256364689956,142.591286230634154],"luv":[81.7553965772464437,-40.4117997418374131,30.906893661738728],"rgb":[0.533333333333333326,0.866666666666666696,0.66666666666666663],"xyz":[0.432633619139194847,0.598478093232424069,0.473036474305788934],"hpluv":[142.591286230634154,117.302359569381537,81.7553965772464437],"hsluv":[142.591286230634154,56.3076629138628,81.7553965772464437]},"#88ddbb":{"lch":[82.1272144034785327,44.0521153731503716,155.27715859232552],"luv":[82.1272144034785327,-40.0143654100278567,18.4238820470094566],"rgb":[0.533333333333333326,0.866666666666666696,0.733333333333333282],"xyz":[0.449771211246531522,0.605333130075358827,0.563294459404430681],"hpluv":[155.27715859232552,104.020343594656595,82.1272144034785327],"hsluv":[155.27715859232552,57.829298146967858,82.1272144034785327]},"#88ddcc":{"lch":[82.542438979776648,39.9525966793572422,172.446474622419316],"luv":[82.542438979776648,-39.6059077008678173,5.25186220437346396],"rgb":[0.533333333333333326,0.866666666666666696,0.8],"xyz":[0.469063586690253276,0.613050080252847684,0.664900970074700837],"hpluv":[172.446474622419316,96.9346789585341355,82.542438979776648],"hsluv":[172.446474622419316,59.4202271347422908,82.542438979776648]},"#88dddd":{"lch":[83.0014816422074375,40.0957403917498354,192.177050630060734],"luv":[83.0014816422074375,-39.1936046909777,-8.4575261684472629],"rgb":[0.533333333333333326,0.866666666666666696,0.866666666666666696],"xyz":[0.490581912240886764,0.621657410473101191,0.778230817974706834],"hpluv":[192.177050630060734,100.308679558078737,83.0014816422074375],"hsluv":[192.177050630060734,61.0588228331018783,83.0014816422074375]},"#88ddee":{"lch":[83.5045459788204,44.8677054153188,210.184175125347622],"luv":[83.5045459788204,-38.7842593009493157,-22.5586395802706683],"rgb":[0.533333333333333326,0.866666666666666696,0.933333333333333348],"xyz":[0.514394092507061584,0.631182282579571208,0.903641634043230058],"hpluv":[210.184175125347622,116.175045715242419,83.5045459788204],"hsluv":[210.184175125347622,62.7244468832174746,83.5045459788204]},"#88ddff":{"lch":[84.0516404633952732,53.2556266987753659,223.883407592331508],"luv":[84.0516404633952732,-38.3840933399078779,-36.9164347351224436],"rgb":[0.533333333333333326,0.866666666666666696,1],"xyz":[0.540565140916892894,0.641650701943503932,1.04147582233501179],"hpluv":[223.883407592331508,143.298172182841085,84.0516404633952732],"hsluv":[223.883407592331508,99.9999999999952,84.0516404633952732]},"#335500":{"lch":[32.2593993637483862,41.1235506245754365,113.326494368716226],"luv":[32.2593993637483862,-16.2836990313566261,37.7622504868051223],"rgb":[0.2,0.333333333333333315,0],"xyz":[0.0461356744276991415,0.0720065112172806193,0.0114678000018764836],"hpluv":[113.326494368716226,161.760937136611716,32.2593993637483862],"hsluv":[113.326494368716226,100.000000000002288,32.2593993637483862]},"#335511":{"lch":[32.3496341583576381,38.1897920123001313,115.090818108356473],"luv":[32.3496341583576381,-16.1945453684027605,34.5860797757347456],"rgb":[0.2,0.333333333333333315,0.0666666666666666657],"xyz":[0.0471473399273362598,0.0724111774171354722,0.016795904966632115],"hpluv":[115.090818108356473,149.801873896373706,32.3496341583576381],"hsluv":[115.090818108356473,90.8995446090399923,32.3496341583576381]},"#335522":{"lch":[32.5160201858231659,33.1206139518783189,118.966642902489184],"luv":[32.5160201858231659,-16.0403246718637718,28.9772851207727022],"rgb":[0.2,0.333333333333333315,0.133333333333333331],"xyz":[0.0490226980658132819,0.0731613206725262866,0.0266727911626113111],"hpluv":[118.966642902489184,129.252907346834121,32.5160201858231659],"hsluv":[118.966642902489184,74.935129302234273,32.5160201858231659]},"#335533":{"lch":[32.7875119073456176,25.8516304293262671,127.715012949238741],"luv":[32.7875119073456176,-15.8143300747053051,20.4502753072602665],"rgb":[0.2,0.333333333333333315,0.2],"xyz":[0.0521104487982710224,0.0743964209655094,0.0429349450202224578],"hpluv":[127.715012949238741,100.050394151032577,32.7875119073456176],"hsluv":[127.715012949238741,50.9434831873252207,32.7875119073456176]},"#335544":{"lch":[33.1742322541989836,18.1915220066791647,148.674883917516439],"luv":[33.1742322541989836,-15.539762262897943,9.45765624941535421],"rgb":[0.2,0.333333333333333315,0.266666666666666663],"xyz":[0.0565684440424899,0.0761796190631969794,0.0664137199731090894],"hpluv":[148.674883917516439,69.5836942134034899,33.1742322541989836],"hsluv":[148.674883917516439,54.5088767633859703,33.1742322541989836]},"#335555":{"lch":[33.6821363315134121,15.5994691341387064,192.177050630060819],"luv":[33.6821363315134121,-15.2484882598238034,-3.29044724269541522],"rgb":[0.2,0.333333333333333315,0.333333333333333315],"xyz":[0.0625308580818519,0.0785645846789418123,0.097815767247083],"hpluv":[192.177050630060819,58.7691644617976934,33.6821363315134121],"hsluv":[192.177050630060819,58.539124710901028,33.6821363315134121]},"#335566":{"lch":[34.3136156967701496,22.6822476529859181,228.692277460856218],"luv":[34.3136156967701496,-14.9726179113300351,-17.0383412183435361],"rgb":[0.2,0.333333333333333315,0.4],"xyz":[0.0701158538553600663,0.0815985829883451308,0.137763411654226953],"hpluv":[228.692277460856218,83.8800984368335,34.3136156967701496],"hsluv":[228.692277460856218,62.7386213411417089,34.3136156967701496]},"#335577":{"lch":[35.0679836745485218,34.4527831327976131,244.673076132176305],"luv":[35.0679836745485218,-14.7383029974912176,-31.1412377780608658],"rgb":[0.2,0.333333333333333315,0.466666666666666674],"xyz":[0.0794298668755619858,0.0853241881964259402,0.186817213560624862],"hpluv":[244.673076132176305,124.667382519216758,35.0679836745485218],"hsluv":[244.673076132176305,66.8604241763398335,35.0679836745485218]},"#335588":{"lch":[35.9419713942028523,47.4274747217606318,252.117898483801355],"luv":[35.9419713942028523,-14.5630489263951493,-45.1362710516793229],"rgb":[0.2,0.333333333333333315,0.533333333333333326],"xyz":[0.0905702839775058138,0.089780355037203538,0.245490076964197029],"hpluv":[252.117898483801355,167.443168522882189,35.9419713942028523],"hsluv":[252.117898483801355,70.7327730687023433,35.9419713942028523]},"#335599":{"lch":[36.9302538454711851,60.4953772811161272,256.174865065393647],"luv":[36.9302538454711851,-14.4559426241851021,-58.7427986669927833],"rgb":[0.2,0.333333333333333315,0.6],"xyz":[0.103627248086578527,0.0950031406808327,0.314256754605314936],"hpluv":[256.174865065393647,207.863972679723531,36.9302538454711851],"hsluv":[256.174865065393647,74.2581677802949258,36.9302538454711851]},"#3355aa":{"lch":[38.0259842211890557,73.2582460718320903,258.648348715518409],"luv":[38.0259842211890557,-14.4194259870406505,-71.8251402485603165],"rgb":[0.2,0.333333333333333315,0.66666666666666663],"xyz":[0.118684941051831069,0.101026217866933804,0.393560604222313537],"hpluv":[258.648348715518409,244.464262316607687,38.0259842211890557],"hsluv":[258.648348715518409,77.3978910060545,38.0259842211890557]},"#3355bb":{"lch":[39.2213032744107579,85.5750988898230815,260.277622363739169],"luv":[39.2213032744107579,-14.4514388960387397,-84.3460340729607623],"rgb":[0.2,0.333333333333333315,0.733333333333333282],"xyz":[0.135822533159167702,0.107881254709868563,0.483818589320955283],"hpluv":[260.277622363739169,276.862869377010384,39.2213032744107579],"hsluv":[260.277622363739169,80.1529964351826578,39.2213032744107579]},"#3355cc":{"lch":[40.5077939091134667,97.4185991778777,261.412040351160215],"luv":[40.5077939091134667,-14.5472816309580448,-96.326320717286734],"rgb":[0.2,0.333333333333333315,0.8],"xyz":[0.155114908602889456,0.115598204887357364,0.585425099991225495],"hpluv":[261.412040351160215,305.17054630009352,40.5077939091134667],"hsluv":[261.412040351160215,82.5478264076183734,40.5077939091134667]},"#3355dd":{"lch":[41.8768615158020552,108.813506268261335,262.235450095721887],"luv":[41.8768615158020552,-14.7009821832840935,-107.815862790406285],"rgb":[0.2,0.333333333333333315,0.866666666666666696],"xyz":[0.176633234153523,0.124205535107610898,0.698754947891231493],"hpluv":[262.235450095721887,329.72206067647727,41.8768615158020552],"hsluv":[262.235450095721887,84.6180359154754456,41.8768615158020552]},"#3355ee":{"lch":[43.3200322197542533,119.805986402952101,262.852770803428143],"luv":[43.3200322197542533,-14.9061911990543372,-118.875059797762361],"rgb":[0.2,0.333333333333333315,0.933333333333333348],"xyz":[0.200445414419697737,0.133730407214080915,0.824165763959754716],"hpluv":[262.852770803428143,350.936931775297865,43.3200322197542533],"hsluv":[262.852770803428143,90.8055999978327577,43.3200322197542533]},"#3355ff":{"lch":[44.8291710285026497,130.447860525532377,263.327743444412704],"luv":[44.8291710285026497,-15.1567128937849134,-129.564340657235334],"rgb":[0.2,0.333333333333333315,1],"xyz":[0.226616462829529131,0.144198826578013639,0.961999952251536561],"hpluv":[263.327743444412704,369.245812735261836,44.8291710285026497],"hsluv":[263.327743444412704,99.9999999999993,44.8291710285026497]},"#aadd00":{"lch":[81.9783608763648175,97.3184216433040916,105.014728605041086],"luv":[81.9783608763648175,-25.2120246284188454,93.9958988747909245],"rgb":[0.66666666666666663,0.866666666666666696,0],"xyz":[0.424325128525832707,0.602582536713324846,0.0939549564608566229],"hpluv":[105.014728605041086,227.603505169437568,81.9783608763648175],"hsluv":[105.014728605041086,100.000000000002203,81.9783608763648175]},"#aadd11":{"lch":[82.0002885274849262,96.374791846509865,105.167446282879951],"luv":[82.0002885274849262,-25.2155818179290208,93.017605521973266],"rgb":[0.66666666666666663,0.866666666666666696,0.0666666666666666657],"xyz":[0.425336794025469811,0.602987202913179754,0.0992830614256122596],"hpluv":[105.167446282879951,225.71449396257529,82.0002885274849262],"hsluv":[105.167446282879951,98.8971907906987298,82.0002885274849262]},"#aadd22":{"lch":[82.0409106120811,94.6380623122949345,105.456830413527484],"luv":[82.0409106120811,-25.2222030700150874,91.2151484706391216],"rgb":[0.66666666666666663,0.866666666666666696,0.133333333333333331],"xyz":[0.427212152163946868,0.603737346168570554,0.109159947621591452],"hpluv":[105.456830413527484,222.227322886837186,82.0409106120811],"hsluv":[105.456830413527484,96.86659260915998,82.0409106120811]},"#aadd33":{"lch":[82.1077210543303693,91.8137349028061891,105.951891130476156],"luv":[82.1077210543303693,-25.2331804702422886,88.2782448860360773],"rgb":[0.66666666666666663,0.866666666666666696,0.2],"xyz":[0.430299902896404574,0.604972446461553681,0.125422101479202602],"hpluv":[105.951891130476156,216.526835376233663,82.1077210543303693],"hsluv":[105.951891130476156,93.5615293661406469,82.1077210543303693]},"#aadd44":{"lch":[82.204019596801416,87.812555976334508,106.710442618715334],"luv":[82.204019596801416,-25.2491908313752447,84.1042409718896238],"rgb":[0.66666666666666663,0.866666666666666696,0.266666666666666663],"xyz":[0.434757898140623489,0.606755644559241247,0.148900876432089241],"hpluv":[106.710442618715334,208.386709262610594,82.204019596801416],"hsluv":[106.710442618715334,88.8720213109455415,82.204019596801416]},"#aadd55":{"lch":[82.332521082647844,82.6008871447080537,107.814707884186447],"luv":[82.332521082647844,-25.2708912490316564,78.6402480449573],"rgb":[0.66666666666666663,0.866666666666666696,0.333333333333333315],"xyz":[0.440720312179985474,0.609140610174986,0.180302923706063156],"hpluv":[107.814707884186447,197.666387369374576,82.332521082647844],"hsluv":[107.814707884186447,82.7472331150865,82.332521082647844]},"#aadd66":{"lch":[82.495508489392364,76.2000100439918526,109.390646830849064],"luv":[82.495508489392364,-25.2989483474785786,71.8777068583581666],"rgb":[0.66666666666666663,0.866666666666666696,0.4],"xyz":[0.448305307953493659,0.612174608484389315,0.22025056811320709],"hpluv":[109.390646830849064,184.308617744019813,82.495508489392364],"hsluv":[109.390646830849064,75.1895211365387723,82.495508489392364]},"#aadd77":{"lch":[82.694914273715753,68.6905071379487,111.642517734809914],"luv":[82.694914273715753,-25.3340491820501299,63.8480361711309499],"rgb":[0.66666666666666663,0.866666666666666696,0.466666666666666674],"xyz":[0.457619320973695565,0.615900213692470166,0.269304370019605],"hpluv":[111.642517734809914,168.351618208890358,82.694914273715753],"hsluv":[111.642517734809914,66.2494286969421182,82.694914273715753]},"#aadd88":{"lch":[82.9323686283524921,60.2252781448786436,114.920875432447886],"luv":[82.9323686283524921,-25.3769003534370228,54.6177357282387277],"rgb":[0.66666666666666663,0.866666666666666696,0.533333333333333326],"xyz":[0.468759738075639365,0.620356380533247709,0.327977233423177195],"hpluv":[114.920875432447886,149.967217229555729,82.9323686283524921],"hsluv":[114.920875432447886,56.0198042099318698,82.9323686283524921]},"#aadd99":{"lch":[83.2092305924335,51.0640475908324305,119.865599423026282],"luv":[83.2092305924335,-25.4282188712646082,44.2825320120006438],"rgb":[0.66666666666666663,0.866666666666666696,0.6],"xyz":[0.481816702184712065,0.625579166176876922,0.396743911064295074],"hpluv":[119.865599423026282,129.562195810811914,83.2092305924335],"hsluv":[119.865599423026282,44.6286172342680487,83.2092305924335]},"#aaddaa":{"lch":[83.526609727912188,41.6663179647472717,127.715012949236481],"luv":[83.526609727912188,-25.4887175141036408,32.9606937461220255],"rgb":[0.66666666666666663,0.866666666666666696,0.66666666666666663],"xyz":[0.496874395149964621,0.631602243362978,0.476047760681293675],"hpluv":[127.715012949236481,108.050849780995421,83.526609727912188],"hsluv":[127.715012949236481,32.2306770176982198,83.526609727912188]},"#aaddbb":{"lch":[83.8853825066367449,32.9442078204095523,140.880295511025736],"luv":[83.8853825066367449,-25.5590871805440472,20.7859060762739638],"rgb":[0.66666666666666663,0.866666666666666696,0.733333333333333282],"xyz":[0.514011987257301239,0.638457280205912747,0.566305745779935421],"hpluv":[140.880295511025736,87.605014056209555,83.8853825066367449],"hsluv":[140.880295511025736,34.4766875289621382,83.8853825066367449]},"#aaddcc":{"lch":[84.2862057978904602,26.8294893367150316,162.87488866986746],"luv":[84.2862057978904602,-25.639978502468459,7.90018990036700242],"rgb":[0.66666666666666663,0.866666666666666696,0.8],"xyz":[0.533304362701023,0.646174230383401604,0.667912256450205577],"hpluv":[162.87488866986746,73.4171355960865526,84.2862057978904602],"hsluv":[162.87488866986746,36.8333761539634708,84.2862057978904602]},"#aadddd":{"lch":[84.729528894444158,26.3242685722419552,192.177050630060421],"luv":[84.729528894444158,-25.7319846477226655,-5.55266440124834],"rgb":[0.66666666666666663,0.866666666666666696,0.866666666666666696],"xyz":[0.554822688251656593,0.654781560603655111,0.781242104350211575],"hpluv":[192.177050630060421,74.4077864529294146,84.729528894444158],"hsluv":[192.177050630060421,39.269697519551606,84.729528894444158]},"#aaddee":{"lch":[85.2156049558348769,32.3281965376105802,216.94937298104162],"luv":[85.2156049558348769,-25.8356267715816657,-19.4327733659894548],"rgb":[0.66666666666666663,0.866666666666666696,0.933333333333333348],"xyz":[0.578634868517831302,0.664306432710125128,0.906652920418734798],"hpluv":[216.94937298104162,94.7745998726022094,85.2156049558348769],"hsluv":[216.94937298104162,58.4216362813771397,85.2156049558348769]},"#aaddff":{"lch":[85.744502396509489,42.4619511969095527,232.326067904807843],"luv":[85.744502396509489,-25.9513430438233641,-33.608705623253762],"rgb":[0.66666666666666663,0.866666666666666696,1],"xyz":[0.604805916927662723,0.674774852074057852,1.04448710871051675],"hpluv":[232.326067904807843,129.682154580054771,85.744502396509489],"hsluv":[232.326067904807843,99.9999999999945572,85.744502396509489]},"#88ee00":{"lch":[85.1906878331824515,112.448241602139106,116.535675589642423],"luv":[85.1906878331824515,-50.2368100475369914,100.602534538950536],"rgb":[0.533333333333333326,0.933333333333333348,0],"xyz":[0.40726312885557775,0.663815939263801891,0.106669928865523039],"hpluv":[116.535675589642423,329.03324103323672,85.1906878331824515],"hsluv":[116.535675589642423,100.000000000002416,85.1906878331824515]},"#88ee11":{"lch":[85.2112458075113182,111.594873497969587,116.730132104203335],"luv":[85.2112458075113182,-50.1941202672300193,99.669283541253],"rgb":[0.533333333333333326,0.933333333333333348,0.0666666666666666657],"xyz":[0.408274794355214854,0.664220605463656799,0.111998033830278676],"hpluv":[116.730132104203335,327.047318494663614,85.2112458075113182],"hsluv":[116.730132104203335,98.9939616796445,85.2112458075113182]},"#88ee22":{"lch":[85.2493327369145,110.025246585506409,117.096539077460619],"luv":[85.2493327369145,-50.115524286233132,97.9489107224652429],"rgb":[0.533333333333333326,0.933333333333333348,0.133333333333333331],"xyz":[0.410150152493691911,0.6649707487190476,0.121874920026257869],"hpluv":[117.096539077460619,323.384561507736407,85.2493327369145],"hsluv":[117.096539077460619,97.140453749143191,85.2493327369145]},"#88ee33":{"lch":[85.3119799721786,107.475578977757436,117.717135468026783],"luv":[85.3119799721786,-49.9876247553973769,95.1432469906181097],"rgb":[0.533333333333333326,0.933333333333333348,0.2],"xyz":[0.413237903226149617,0.666205849012030726,0.138137073883869],"hpluv":[117.717135468026783,317.406918528502,85.3119799721786],"hsluv":[117.717135468026783,94.120593299189423,85.3119799721786]},"#88ee44":{"lch":[85.4022915890189864,103.870397610997969,118.652974854946393],"luv":[85.4022915890189864,-49.8062112723248518,91.1504296126099],"rgb":[0.533333333333333326,0.933333333333333348,0.266666666666666663],"xyz":[0.417695898470368532,0.667989047109718292,0.161615848836755643],"hpluv":[118.652974854946393,308.894438519259097,85.4022915890189864],"hsluv":[118.652974854946393,89.8292917088674869,85.4022915890189864]},"#88ee55":{"lch":[85.5228293578601466,99.1885702075232,119.98340282305692],"luv":[85.5228293578601466,-49.5693999595221584,85.9141842041562],"rgb":[0.533333333333333326,0.933333333333333348,0.333333333333333315],"xyz":[0.423658312509730517,0.670374012725463,0.193017896110729559],"hpluv":[119.98340282305692,297.731707367046795,85.5228293578601466],"hsluv":[119.98340282305692,84.2131356884780473,85.5228293578601466]},"#88ee66":{"lch":[85.6757572108422,93.4644574192068234,121.818598205465591],"luv":[85.6757572108422,-49.2774194314266083,79.4187681524083189],"rgb":[0.533333333333333326,0.933333333333333348,0.4],"xyz":[0.431243308283238702,0.673408011034866361,0.232965540517873521],"hpluv":[121.818598205465591,283.912655540997662,85.6757572108422],"hsluv":[121.818598205465591,77.2651144072775082,85.6757572108422]},"#88ee77":{"lch":[85.8629182835971676,86.7937408847250822,124.317503927832064],"luv":[85.8629182835971676,-48.9324360738715,71.6852157466327071],"rgb":[0.533333333333333326,0.933333333333333348,0.466666666666666674],"xyz":[0.440557321303440608,0.677133616242947212,0.282019342424271402],"hpluv":[124.317503927832064,267.563092583833793,85.8629182835971676],"hsluv":[124.317503927832064,69.0205357789205749,85.8629182835971676]},"#88ee88":{"lch":[86.0858807036169793,79.3454652904755164,127.71501294923921],"luv":[86.0858807036169793,-48.5383458294846,62.7672832477191918],"rgb":[0.533333333333333326,0.933333333333333348,0.533333333333333326],"xyz":[0.451697738405384408,0.681589783083724754,0.340692205827843597],"hpluv":[127.71501294923921,248.989760930573482,86.0858807036169793],"hsluv":[127.71501294923921,59.5524101967812,86.0858807036169793]},"#88ee99":{"lch":[86.3459670558122525,71.3853284581525429,132.362204285947513],"luv":[86.3459670558122525,-48.1005130697284287,52.7466184841000825],"rgb":[0.533333333333333326,0.933333333333333348,0.6],"xyz":[0.464754702514457163,0.686812568727354,0.409458883468961532],"hpluv":[132.362204285947513,228.778132650246619,86.3459670558122525],"hsluv":[132.362204285947513,60.5562348819982645,86.3459670558122525]},"#88eeaa":{"lch":[86.6442747471204768,63.319361027400987,138.776693085718904],"luv":[86.6442747471204768,-47.625461320552418,41.7271723811105204],"rgb":[0.533333333333333326,0.933333333333333348,0.66666666666666663],"xyz":[0.479812395479709664,0.692835645913455,0.488762733085960077],"hpluv":[138.776693085718904,207.981038552319745,86.6442747471204768],"hsluv":[138.776693085718904,61.6537348625427342,86.6442747471204768]},"#88eebb":{"lch":[86.9816911545789679,55.7684583245382299,147.664608842749317],"luv":[86.9816911545789679,-47.1205330954276,29.8291184029711189],"rgb":[0.533333333333333326,0.933333333333333348,0.733333333333333282],"xyz":[0.496949987587046338,0.699690682756389792,0.579020718184601879],"hpluv":[147.664608842749317,188.458500832318975,86.9816911545789679],"hsluv":[147.664608842749317,62.830800627143077,86.9816911545789679]},"#88eecc":{"lch":[87.3589058048410294,49.6608679924305463,159.75715297219557],"luv":[87.3589058048410294,-46.5935415809549696,17.182656741768227],"rgb":[0.533333333333333326,0.933333333333333348,0.8],"xyz":[0.516242363030768092,0.70740763293387865,0.680627228854872],"hpluv":[159.75715297219557,173.371642714007834,87.3589058048410294],"hsluv":[159.75715297219557,64.0722902507549179,87.3589058048410294]},"#88eedd":{"lch":[87.7764209496729,46.2191498548509543,175.132102674355139],"luv":[87.7764209496729,-46.0524373199716166,3.92209512827700335],"rgb":[0.533333333333333326,0.933333333333333348,0.866666666666666696],"xyz":[0.53776068858140158,0.716014963154132156,0.793957076754878],"hpluv":[175.132102674355139,167.447502067072264,87.7764209496729],"hsluv":[175.132102674355139,65.3626972838926434,87.7764209496729]},"#88eeee":{"lch":[88.2345613859691866,46.5524177333431339,192.177050630060933],"luv":[88.2345613859691866,-45.5050097647113958,-9.8194543195231],"rgb":[0.533333333333333326,0.933333333333333348,0.933333333333333348],"xyz":[0.56157286884757629,0.725539835260602173,0.919367892823401256],"hpluv":[192.177050630060933,175.887543813927181,88.2345613859691866],"hsluv":[192.177050630060933,66.6867488369664443,88.2345613859691866]},"#88eeff":{"lch":[88.7334840469836763,50.9233073192915242,208.009521596121829],"luv":[88.7334840469836763,-44.9586380478967271,-23.9145163700465169],"rgb":[0.533333333333333326,0.933333333333333348,1],"xyz":[0.587743917257407711,0.736008254624534897,1.05720208111518299],"hpluv":[208.009521596121829,201.749007974481685,88.7334840469836763],"hsluv":[208.009521596121829,99.9999999999925109,88.7334840469836763]},"#336600":{"lch":[38.2101034680229574,51.4017776291135888,118.130952889189317],"luv":[38.2101034680229574,-24.2353400261634,45.3298029694491902],"rgb":[0.2,0.4,0],"xyz":[0.0611637321335456105,0.10206262662897396,0.0164771525704918292],"hpluv":[118.130952889189317,170.702252266418213,38.2101034680229574],"hsluv":[118.130952889189317,100.000000000002288,38.2101034680229574]},"#336611":{"lch":[38.2816545292110959,48.9828222693822468,119.470954908934345],"luv":[38.2816545292110959,-24.0986808399199681,42.6447002480913682],"rgb":[0.2,0.4,0.0666666666666666657],"xyz":[0.0621753976331827357,0.102467292828828813,0.021805257535247459],"hpluv":[119.470954908934345,162.365005406289157,38.2816545292110959],"hsluv":[119.470954908934345,93.5286374368429,38.2816545292110959]},"#336622":{"lch":[38.4137944304132617,44.7307250918699282,122.230818066602779],"luv":[38.4137944304132617,-23.8562978467226365,37.8380076152656173],"rgb":[0.2,0.4,0.133333333333333331],"xyz":[0.0640507557716597509,0.103217436084219627,0.0316821437312266585],"hpluv":[122.230818066602779,147.760399550800031,38.4137944304132617],"hsluv":[122.230818066602779,81.994947572555219,38.4137944304132617]},"#336633":{"lch":[38.6299730126545171,38.3921679396875817,127.715012949239437],"luv":[38.6299730126545171,-23.4858075099586969,30.3706339201924216],"rgb":[0.2,0.4,0.2],"xyz":[0.0671385065041174844,0.10445253637720274,0.0479442975888378],"hpluv":[127.715012949239437,126.112332565807833,38.6299730126545171],"hsluv":[127.715012949239437,64.2136550115152,38.6299730126545171]},"#336644":{"lch":[38.9390987599147635,30.7877900918170191,138.353415806708398],"luv":[38.9390987599147635,-23.0064236741332095,20.4595329483365589],"rgb":[0.2,0.4,0.266666666666666663],"xyz":[0.0715965017483363719,0.10623573447489032,0.0714230725417244472],"hpluv":[138.353415806708398,100.330262339301328,38.9390987599147635],"hsluv":[138.353415806708398,66.1490613915476899,38.9390987599147635]},"#336655":{"lch":[39.3471830293532108,24.0314899024670439,159.111050143656939],"luv":[39.3471830293532108,-22.4519783641565418,8.56861566811244479],"rgb":[0.2,0.4,0.333333333333333315],"xyz":[0.077558915787698357,0.108620700090635153,0.102825119815698349],"hpluv":[159.111050143656939,77.5008343503898089,39.3471830293532108],"hsluv":[159.111050143656939,68.4324421599544337,39.3471830293532108]},"#336666":{"lch":[39.8577781510875653,22.3660784387423632,192.177050630061],"luv":[39.8577781510875653,-21.8628519700770774,-4.71775036033837925],"rgb":[0.2,0.4,0.4],"xyz":[0.0851439115612065422,0.111654698400038471,0.142772764222842297],"hpluv":[192.177050630061,71.205917149352544,39.8577781510875653],"hsluv":[192.177050630061,70.9271962998489727,39.8577781510875653]},"#336677":{"lch":[40.4722660639059,28.403364995264031,221.483563289625266],"luv":[40.4722660639059,-21.2782609928981294,-18.8145356618838697],"rgb":[0.2,0.4,0.466666666666666674],"xyz":[0.0944579245814084478,0.115380303608119281,0.191826566129240206],"hpluv":[221.483563289625266,89.0536244466218534,40.4722660639059],"hsluv":[221.483563289625266,73.4989606489913,40.4722660639059]},"#336688":{"lch":[41.1901179582142731,39.1584560294521253,238.034471274574031],"luv":[41.1901179582142731,-20.7308371309926542,-33.2207325394668516],"rgb":[0.2,0.4,0.533333333333333326],"xyz":[0.105598341683352276,0.119836470448896878,0.250499429532812401],"hpluv":[238.034471274574031,120.634589470818128,41.1901179582142731],"hsluv":[238.034471274574031,76.0346320246428746,41.1901179582142731]},"#336699":{"lch":[42.0091634944821948,51.6886413393569626,246.942440261812],"luv":[42.0091634944821948,-20.2441498032976277,-47.5593318103852951],"rgb":[0.2,0.4,0.6],"xyz":[0.118655305792425,0.12505925609252605,0.31926610717393028],"hpluv":[246.942440261812,156.131455895945,42.0091634944821948],"hsluv":[246.942440261812,78.4511744564818514,42.0091634944821948]},"#3366aa":{"lch":[42.9258754740709847,64.6951641030825,252.148217293940775],"luv":[42.9258754740709847,-19.832670948043944,-61.5802681172418218],"rgb":[0.2,0.4,0.66666666666666663],"xyz":[0.133712998757677531,0.131082333278627144,0.398569956790928881],"hpluv":[252.148217293940775,191.245834212653307,42.9258754740709847],"hsluv":[252.148217293940775,80.6959595489171,42.9258754740709847]},"#3366bb":{"lch":[43.9356615222631106,77.6311657782158164,255.449757662516049],"luv":[43.9356615222631106,-19.5031901130623,-75.1413566253536374],"rgb":[0.2,0.4,0.733333333333333282],"xyz":[0.150850590865014178,0.137937370121561903,0.488827941889570627],"hpluv":[255.449757662516049,224.211696450950683,43.9356615222631106],"hsluv":[255.449757662516049,82.7420790911240402,43.9356615222631106]},"#3366cc":{"lch":[45.0331492258045287,90.2591849706621332,257.681278432902445],"luv":[45.0331492258045287,-19.2567634880452658,-88.1810497302775786],"rgb":[0.2,0.4,0.8],"xyz":[0.170142966308735932,0.145654320299050705,0.590434452559840839],"hpluv":[257.681278432902445,254.330482568364204,45.0331492258045287],"hsluv":[257.681278432902445,84.5818015821218694,45.0331492258045287]},"#3366dd":{"lch":[46.2124513047425367,102.485732700579547,259.264483160570819],"luv":[46.2124513047425367,-19.0906003056708045,-100.691977769551045],"rgb":[0.2,0.4,0.866666666666666696],"xyz":[0.191661291859369476,0.154261650519304239,0.703764300459846837],"hpluv":[259.264483160570819,281.412724566330553,46.2124513047425367],"hsluv":[259.264483160570819,86.2202337385041488,46.2124513047425367]},"#3366ee":{"lch":[47.467400384741687,114.289765210034219,260.43068521754094],"luv":[47.467400384741687,-18.99960653492559,-112.699447129445844],"rgb":[0.2,0.4,0.933333333333333348],"xyz":[0.215473472125544213,0.163786522625774256,0.82917511652837006],"hpluv":[260.43068521754094,305.528142270574733,47.467400384741687],"hsluv":[260.43068521754094,89.4216395109538524,47.467400384741687]},"#3366ff":{"lch":[48.7917470574018068,125.686826272807437,261.315666926990161],"luv":[48.7917470574018068,-18.9775194272026404,-124.245853270525785],"rgb":[0.2,0.4,1],"xyz":[0.241644520535375606,0.17425494198970698,0.967009304820151905],"hpluv":[261.315666926990161,326.875761207371056,48.7917470574018068],"hsluv":[261.315666926990161,99.9999999999992184,48.7917470574018068]},"#aaee00":{"lch":[86.8465682321076713,106.130019137569278,108.773889799394041],"luv":[86.8465682321076713,-34.1562767274136,100.483479847491182],"rgb":[0.66666666666666663,0.933333333333333348,0],"xyz":[0.471503904866347523,0.696940089394355811,0.109681215241027807],"hpluv":[108.773889799394041,354.560190530248747,86.8465682321076713],"hsluv":[108.773889799394041,100.000000000002302,86.8465682321076713]},"#aaee11":{"lch":[86.8664697406872364,105.275413073012288,108.926122010215039],"luv":[86.8664697406872364,-34.1459453835818323,99.5839696515197801],"rgb":[0.66666666666666663,0.933333333333333348,0.0666666666666666657],"xyz":[0.472515570365984627,0.697344755594210719,0.115009320205783444],"hpluv":[108.926122010215039,352.296735211520456,86.8664697406872364],"hsluv":[108.926122010215039,99.0448212343627,86.8664697406872364]},"#aaee22":{"lch":[86.9033414770782286,103.701584155213482,109.21343759479339],"luv":[86.9033414770782286,-34.1269596340241961,97.9253245204656224],"rgb":[0.66666666666666663,0.933333333333333348,0.133333333333333331],"xyz":[0.474390928504461684,0.69809489884960152,0.124886206401762637],"hpluv":[109.21343759479339,348.114419217609168,86.9033414770782286],"hsluv":[109.21343759479339,97.2844700630890742,86.9033414770782286]},"#aaee33":{"lch":[86.9639927659971,101.139559122436452,109.701502859778287],"luv":[86.9639927659971,-34.0961634116812462,95.2190215245081504],"rgb":[0.66666666666666663,0.933333333333333348,0.2],"xyz":[0.47747867923691939,0.699329999142584646,0.141148360259373773],"hpluv":[109.701502859778287,341.266668172598315,86.9639927659971],"hsluv":[109.701502859778287,94.4148698146506,86.9639927659971]},"#aaee44":{"lch":[87.0514332975086091,97.5044422856038864,110.440992284795044],"luv":[87.0514332975086091,-34.052698770047833,91.3648180204122298],"rgb":[0.66666666666666663,0.933333333333333348,0.266666666666666663],"xyz":[0.481936674481138305,0.701113197240272212,0.164627135212260411],"hpluv":[110.440992284795044,331.464832239423686,87.0514332975086091],"hsluv":[110.440992284795044,90.3338544995677353,87.0514332975086091]},"#aaee55":{"lch":[87.1681505263978,92.7597711807761556,111.499860176168639],"luv":[87.1681505263978,-33.9963593100021271,86.3054036730909644],"rgb":[0.66666666666666663,0.933333333333333348,0.333333333333333315],"xyz":[0.48789908852050029,0.703498162856017,0.196029182486234327],"hpluv":[111.499860176168639,318.513853400728863,87.1681505263978],"hsluv":[111.499860176168639,84.9871568734489813,87.1681505263978]},"#aaee66":{"lch":[87.3162499846890086,86.9168013961255923,112.975973936880195],"luv":[87.3162499846890086,-33.9275471197994563,80.0215715377255492],"rgb":[0.66666666666666663,0.933333333333333348,0.4],"xyz":[0.495484084294008476,0.70653216116542028,0.235976826893378289],"hpluv":[112.975973936880195,302.308385752370668,87.3162499846890086],"hsluv":[112.975973936880195,78.3634586897027106,87.3162499846890086]},"#aaee77":{"lch":[87.4975302296396,80.0378594698754853,115.017197938190947],"luv":[87.4975302296396,-33.8472328521792534,72.5287789554594298],"rgb":[0.66666666666666663,0.933333333333333348,0.466666666666666674],"xyz":[0.504798097314210326,0.710257766373501132,0.285030628799776198],"hpluv":[115.017197938190947,282.845349503141222,87.4975302296396],"hsluv":[115.017197938190947,70.4907283307095,87.4975302296396]},"#aaee88":{"lch":[87.7135274362348838,72.2451165326571072,117.856267937731204],"luv":[87.7135274362348838,-33.7569011423620324,63.8735351149597861],"rgb":[0.66666666666666663,0.933333333333333348,0.533333333333333326],"xyz":[0.515938514416154237,0.714713933214278674,0.343703492203348393],"hpluv":[117.856267937731204,260.261444274219798,87.7135274362348838],"hsluv":[117.856267937731204,61.4321883079204412,87.7135274362348838]},"#aaee99":{"lch":[87.9655440812905312,63.7405265694191954,121.874104620002555],"luv":[87.9655440812905312,-33.6584769753751445,54.1291202131068871],"rgb":[0.66666666666666663,0.933333333333333348,0.6],"xyz":[0.528995478525226881,0.719936718857907887,0.412470169844466272],"hpluv":[121.874104620002555,234.922523447188695,87.9655440812905312],"hsluv":[121.874104620002555,51.2814060811362182,87.9655440812905312]},"#aaeeaa":{"lch":[88.2546687059401,54.8509913255605497,127.715012949237604],"luv":[88.2546687059401,-33.5542349686064441,43.3906045713616138],"rgb":[0.66666666666666663,0.933333333333333348,0.66666666666666663],"xyz":[0.544053171490479492,0.725959796044009,0.491774019461464873],"hpluv":[127.715012949237604,207.631017102058365,88.2546687059401],"hsluv":[127.715012949237604,42.8122115000723795,88.2546687059401]},"#aaeebb":{"lch":[88.5817905154621457,46.1299748711590425,136.47329364537066],"luv":[88.5817905154621457,-33.4466969972147226,31.7693726974624511],"rgb":[0.66666666666666663,0.933333333333333348,0.733333333333333282],"xyz":[0.561190763597816056,0.732814832886943712,0.582032004560106619],"hpluv":[136.47329364537066,180.106084736741337,88.5817905154621457],"hsluv":[136.47329364537066,42.5306558804052,88.5817905154621457]},"#aaeecc":{"lch":[88.947610994722524,38.5659339652702471,149.820563383504265],"luv":[88.947610994722524,-33.3385252257337896,19.3874701388989088],"rgb":[0.66666666666666663,0.933333333333333348,0.8],"xyz":[0.58048313904153781,0.740531783064432569,0.683638515230376775],"hpluv":[149.820563383504265,156.025185046421541,88.947610994722524],"hsluv":[149.820563383504265,43.7743007306855176,88.947610994722524]},"#aaeedd":{"lch":[89.3526538659385636,33.8378115459028308,169.14561851222],"luv":[89.3526538659385636,-33.2324178366612202,6.37211856022751721],"rgb":[0.66666666666666663,0.933333333333333348,0.866666666666666696],"xyz":[0.602001464592171409,0.749139113284686076,0.796968363130382773],"hpluv":[169.14561851222,142.575756051086472,89.3526538659385636],"hsluv":[169.14561851222,45.7128350403065866,89.3526538659385636]},"#aaeeee":{"lch":[89.797274219494,33.893604395025335,192.177050630060734],"luv":[89.797274219494,-33.1310138990311174,-7.14928925898844092],"rgb":[0.66666666666666663,0.933333333333333348,0.933333333333333348],"xyz":[0.625813644858346119,0.758663985391156093,0.922379179198906],"hpluv":[192.177050630060734,149.574468983420843,89.797274219494],"hsluv":[192.177050630060734,47.7079369778472326,89.797274219494]},"#aaeeff":{"lch":[90.2816673401601406,39.1749897548907384,212.508271358442045],"luv":[90.2816673401601406,-33.0368122621910558,-21.0534761464356919],"rgb":[0.66666666666666663,0.933333333333333348,1],"xyz":[0.65198469326817754,0.769132404755088817,1.06021336749068773],"hpluv":[212.508271358442045,182.211685385120148,90.2816673401601406],"hsluv":[212.508271358442045,99.9999999999910898,90.2816673401601406]},"#88ff00":{"lch":[90.2073775103659727,121.530167505498795,118.25137340908573],"luv":[90.2073775103659727,-57.5251845782800331,107.053420090856534],"rgb":[0.533333333333333326,1,0],"xyz":[0.459115501285251582,0.767520684123151,0.12395405300874715],"hpluv":[118.25137340908573,560.639311859311,90.2073775103659727],"hsluv":[118.25137340908573,100.00000000000226,90.2073775103659727]},"#88ff11":{"lch":[90.2260397586701828,120.75410845816802,118.424372307304225],"luv":[90.2260397586701828,-57.4787561773275684,106.196738640291272],"rgb":[0.533333333333333326,1,0.0666666666666666657],"xyz":[0.460127166784888686,0.767925350323005906,0.129282157973502787],"hpluv":[118.424372307304225,558.207032378019221,90.2260397586701828],"hsluv":[118.424372307304225,99.9999999999909335,90.2260397586701828]},"#88ff22":{"lch":[90.260617257984066,119.32542599803422,118.749452026898098],"luv":[90.260617257984066,-57.393189210317729,104.616342518179295],"rgb":[0.533333333333333326,1,0.133333333333333331],"xyz":[0.462002524923365743,0.768675493578396707,0.139159044169481966],"hpluv":[118.749452026898098,553.71568515647823,90.260617257984066],"hsluv":[118.749452026898098,99.9999999999907772,90.260617257984066]},"#88ff33":{"lch":[90.3174996442954665,117.001153508636776,119.297411154505227],"luv":[90.3174996442954665,-57.253701051451273,102.035697862378711],"rgb":[0.533333333333333326,1,0.2],"xyz":[0.465090275655823449,0.769910593871379834,0.155421198027093116],"hpluv":[119.297411154505227,546.370637823869743,90.3174996442954665],"hsluv":[119.297411154505227,99.9999999999907914,90.3174996442954665]},"#88ff44":{"lch":[90.399517385893148,113.706822542904789,120.117552028257023],"luv":[90.399517385893148,-57.0553255590242614,98.3561452994036074],"rgb":[0.533333333333333326,1,0.266666666666666663],"xyz":[0.469548270900042364,0.7716937919690674,0.178899972979979754],"hpluv":[120.117552028257023,535.877566613280692,90.399517385893148],"hsluv":[120.117552028257023,99.9999999999907914,90.399517385893148]},"#88ff55":{"lch":[90.5090160073098389,109.413894688598035,121.271093020798972],"luv":[90.5090160073098389,-56.7954345723420317,93.518334901057969],"rgb":[0.533333333333333326,1,0.333333333333333315],"xyz":[0.475510684939404349,0.774078757584812149,0.21030202025395367],"hpluv":[121.271093020798972,522.05545338002139,90.5090160073098389],"hsluv":[121.271093020798972,99.9999999999906493,90.5090160073098389]},"#88ff66":{"lch":[90.6479884721694,104.139810410505333,122.839359042112989],"luv":[90.6479884721694,-56.4735097148607039,87.4976731851854908],"rgb":[0.533333333333333326,1,0.4],"xyz":[0.483095680712912534,0.777112755894215468,0.250249664661097659],"hpluv":[122.839359042112989,504.839052288882328,90.6479884721694],"hsluv":[122.839359042112989,99.9999999999905498,90.6479884721694]},"#88ff77":{"lch":[90.8181461195308515,97.9515013566305441,124.934550835890477],"luv":[90.8181461195308515,-56.0909812525741813,80.3012978733306],"rgb":[0.533333333333333326,1,0.466666666666666674],"xyz":[0.49240969373311444,0.780838361102296319,0.299303466567495513],"hpluv":[124.934550835890477,484.301391198376791,90.8181461195308515],"hsluv":[124.934550835890477,99.9999999999904,90.8181461195308515]},"#88ff88":{"lch":[91.0209609702079803,90.9725812653771,127.715012949239437],"luv":[91.0209609702079803,-55.6510519447396348,71.9650676337452779],"rgb":[0.533333333333333326,1,0.533333333333333326],"xyz":[0.503550110835058295,0.785294527943073861,0.357976329971067708],"hpluv":[127.715012949239437,460.703998076760797,91.0209609702079803],"hsluv":[127.715012949239437,99.9999999999902087,91.0209609702079803]},"#88ff99":{"lch":[91.2576929391802167,83.3964386150156116,131.406800448379954],"luv":[91.2576929391802167,-55.158478848678719,62.5500454417735057],"rgb":[0.533333333333333326,1,0.6],"xyz":[0.51660707494413094,0.790517313586703074,0.426743007612185643],"hpluv":[131.406800448379954,434.595507650037121,91.2576929391802167],"hsluv":[131.406800448379954,99.9999999999900524,91.2576929391802167]},"#88ffaa":{"lch":[91.5294084976530229,75.5094657025462226,136.33125230150921],"luv":[91.5294084976530229,-54.6193101996060264,52.1383770748881048],"rgb":[0.533333333333333326,1,0.66666666666666663],"xyz":[0.531664767909383551,0.796540390772804141,0.506046857229184188],"hpluv":[136.33125230150921,406.99849880976177,91.5294084976530229],"hsluv":[136.33125230150921,99.9999999999897824,91.5294084976530229]},"#88ffbb":{"lch":[91.8369943060547911,67.7301008509963225,142.928263991524119],"luv":[91.8369943060547911,-54.0405872892441,40.8286846067776423],"rgb":[0.533333333333333326,1,0.733333333333333282],"xyz":[0.548802360016720114,0.8033954276157389,0.596304842327826],"hpluv":[142.928263991524119,379.751065707187763,91.8369943060547911],"hsluv":[142.928263991524119,99.9999999999894413,91.8369943060547911]},"#88ffcc":{"lch":[92.1811678623774498,60.665065650155249,151.731515164157571],"luv":[92.1811678623774498,-53.4300279465168941,28.7312078404662543],"rgb":[0.533333333333333326,1,0.8],"xyz":[0.568094735460441869,0.811112377793227757,0.697911352998096146],"hpluv":[151.731515164157571,356.080713038752435,92.1811678623774498],"hsluv":[151.731515164157571,99.9999999999893134,92.1811678623774498]},"#88ffdd":{"lch":[92.5624864174544371,55.1561732277505712,163.177123134509742],"luv":[92.5624864174544371,-52.7957106082625316,15.9629692256241533],"rgb":[0.533333333333333326,1,0.866666666666666696],"xyz":[0.589613061011075468,0.819719708013481263,0.811241200898102144],"hpluv":[163.177123134509742,341.369587308077,92.5624864174544371],"hsluv":[163.177123134509742,99.9999999999887,92.5624864174544371]},"#88ffee":{"lch":[92.9813549493531752,52.2127257219241301,177.098205352907513],"luv":[92.9813549493531752,-52.1457771028834927,2.64322864112605771],"rgb":[0.533333333333333326,1,0.933333333333333348],"xyz":[0.613425241277250177,0.82924458011995128,0.936652016966625367],"hpluv":[177.098205352907513,343.566349205584061,92.9813549493531752],"hsluv":[177.098205352907513,99.9999999999882,92.9813549493531752]},"#88ffff":{"lch":[93.4380337051328524,52.6732939730945162,192.177050630061075],"luv":[93.4380337051328524,-51.4881691068088543,-11.1105508416409933],"rgb":[0.533333333333333326,1,1],"xyz":[0.639596289687081598,0.839712999483884,1.07448620525840721],"hpluv":[192.177050630061075,372.044084252862206,93.4380337051328524],"hsluv":[192.177050630061075,99.9999999999874802,93.4380337051328524]},"#337700":{"lch":[44.0848685544221084,61.4877933810524127,120.932619831412623],"luv":[44.0848685544221084,-31.6065510805688668,52.7425318283298168],"rgb":[0.2,0.466666666666666674,0],"xyz":[0.0796174701869632462,0.138970102735809731,0.0226283985882975332],"hpluv":[120.932619831412623,176.985906279588789,44.0848685544221084],"hsluv":[120.932619831412623,100.000000000002217,44.0848685544221084]},"#337711":{"lch":[44.1431322932100159,59.4532082216814146,121.943929432148849],"luv":[44.1431322932100159,-31.4560442613915434,50.449987584497805],"rgb":[0.2,0.466666666666666674,0.0666666666666666657],"xyz":[0.0806291356866003645,0.139374768935664584,0.0279565035530531664],"hpluv":[121.943929432148849,170.903703930819205,44.1431322932100159],"hsluv":[121.943929432148849,95.223210781581642,44.1431322932100159]},"#337722":{"lch":[44.2508401312458517,55.8308883755444327,123.957293362486936],"luv":[44.2508401312458517,-31.1857277137109214,46.3091619851707605],"rgb":[0.2,0.466666666666666674,0.133333333333333331],"xyz":[0.0825044938250774,0.140124912191055412,0.037833389749032359],"hpluv":[123.957293362486936,160.100373243526178,44.2508401312458517],"hsluv":[123.957293362486936,86.6219708852072,44.2508401312458517]},"#337733":{"lch":[44.4273451577554681,50.2894934834324943,127.715012949239792],"luv":[44.4273451577554681,-30.7638100974309978,39.782171171385329],"rgb":[0.2,0.466666666666666674,0.2],"xyz":[0.0855922445575351271,0.141360012484038511,0.0540955436066435091],"hpluv":[127.715012949239792,143.636966976451419,44.4273451577554681],"hsluv":[127.715012949239792,73.1368174441878409,44.4273451577554681]},"#337744":{"lch":[44.6803728315295743,43.2379901525315873,134.305487238163835],"luv":[44.6803728315295743,-30.2010367205865329,30.9422231494797764],"rgb":[0.2,0.466666666666666674,0.266666666666666663],"xyz":[0.090050239801754,0.143143210581726105,0.0775743185595301477],"hpluv":[134.305487238163835,122.797079037201513,44.6803728315295743],"hsluv":[134.305487238163835,74.2422920898123664,44.6803728315295743]},"#337755":{"lch":[45.0155248592042057,35.7001949681147,145.791078733963843],"luv":[45.0155248592042057,-29.5238129364495734,20.071083439987369],"rgb":[0.2,0.466666666666666674,0.333333333333333315],"xyz":[0.096012653841116,0.145528176197470938,0.108976365833504049],"hpluv":[145.791078733963843,100.634662922797503,45.0155248592042057],"hsluv":[145.791078733963843,75.5860232244433377,45.0155248592042057]},"#337766":{"lch":[45.436632811343884,29.7520619336037022,165.22669317203713],"luv":[45.436632811343884,-28.7685269489331041,7.58663604567268468],"rgb":[0.2,0.466666666666666674,0.4],"xyz":[0.103597649614624171,0.148562174506874228,0.148924010240648025],"hpluv":[165.22669317203713,83.0902864995861847,45.436632811343884],"hsluv":[165.22669317203713,77.1054195363406194,45.436632811343884]},"#337777":{"lch":[45.9459628200325696,28.6191137838588361,192.177050630061018],"luv":[45.9459628200325696,-27.9751968985082726,-6.03672363647301413],"rgb":[0.2,0.466666666666666674,0.466666666666666674],"xyz":[0.11291166263482609,0.152287779714955052,0.197977812147045934],"hpluv":[192.177050630061018,79.0402219352416324,45.9459628200325696],"hsluv":[192.177050630061018,78.7308353184449743,45.9459628200325696]},"#337788":{"lch":[46.5443737533918309,33.948531272854666,216.805091226728479],"luv":[46.5443737533918309,-27.1818468531358306,-20.3383867904182836],"rgb":[0.2,0.466666666666666674,0.533333333333333326],"xyz":[0.124052079736769919,0.156743946555732649,0.256650675550618101],"hpluv":[216.805091226728479,92.5535593562344587,46.5443737533918309],"hsluv":[216.805091226728479,80.3955897349876381,46.5443737533918309]},"#337799":{"lch":[47.2314677007312085,43.7891028043947799,232.889035089769209],"luv":[47.2314677007312085,-26.4206199262946413,-34.9204290798973105],"rgb":[0.2,0.466666666666666674,0.6],"xyz":[0.137109043845842632,0.161966732199361807,0.325417353191736],"hpluv":[232.889035089769209,117.645124104106614,47.2314677007312085],"hsluv":[232.889035089769209,82.0429045074046144,47.2314677007312085]},"#3377aa":{"lch":[48.0057466772197472,55.756363166772573,242.534195481556935],"luv":[48.0057466772197472,-25.7159019754829359,-49.471854818395343],"rgb":[0.2,0.466666666666666674,0.66666666666666663],"xyz":[0.15216673681109516,0.167989809385462902,0.404721202808734581],"hpluv":[242.534195481556935,147.380673097935755,48.0057466772197472],"hsluv":[242.534195481556935,83.6292827510673078,48.0057466772197472]},"#3377bb":{"lch":[48.8647777502949623,68.5281348418467076,248.528316013511841],"luv":[48.8647777502949623,-25.0841318464231762,-63.7721851155626],"rgb":[0.2,0.466666666666666674,0.733333333333333282],"xyz":[0.169304328918431835,0.17484484622839766,0.494979187907376328],"hpluv":[248.528316013511841,177.955866809146045,48.8647777502949623],"hsluv":[248.528316013511841,85.1249045257873,48.8647777502949623]},"#3377cc":{"lch":[49.8053630390326845,81.4640551302433096,252.471981644912631],"luv":[49.8053630390326845,-24.5347040681135518,-77.6816617648812553],"rgb":[0.2,0.466666666666666674,0.8],"xyz":[0.188596704362153561,0.182561796405886462,0.596585698577646539],"hpluv":[252.471981644912631,207.553107999557341,49.8053630390326845],"hsluv":[252.471981644912631,86.5120902950020678,49.8053630390326845]},"#3377dd":{"lch":[50.8237086019957047,94.2508792352234,255.202895611269554],"luv":[50.8237086019957047,-24.0713820337122506,-91.1251710758325],"rgb":[0.2,0.466666666666666674,0.866666666666666696],"xyz":[0.210115029912787105,0.19116912662614,0.709915546477652537],"hpluv":[255.202895611269554,235.319753343529385,50.8237086019957047],"hsluv":[255.202895611269554,87.7828608732328,50.8237086019957047]},"#3377ee":{"lch":[51.9155858415672498,106.73807185779232,257.174602046687482],"luv":[51.9155858415672498,-23.6937959642247655,-104.075069141100855],"rgb":[0.2,0.466666666666666674,0.933333333333333348],"xyz":[0.233927210178961842,0.200693998732610041,0.83532636254617576],"hpluv":[257.174602046687482,260.892095272406607,51.9155858415672498],"hsluv":[257.174602046687482,88.9363454602875692,51.9155858415672498]},"#3377ff":{"lch":[53.0764799083082721,118.861737619173724,258.646767383963777],"luv":[53.0764799083082721,-23.3987809317452253,-116.535873106771263],"rgb":[0.2,0.466666666666666674,1],"xyz":[0.260098258588793207,0.211162418096542737,0.973160550837957605],"hpluv":[258.646767383963777,284.170694785425781,53.0764799083082721],"hsluv":[258.646767383963777,99.9999999999990763,53.0764799083082721]},"#aaff00":{"lch":[91.7137860391432156,115.080534629040301,111.722667154579099],"luv":[91.7137860391432156,-42.5929524944460596,106.908230966149674],"rgb":[0.66666666666666663,1,0],"xyz":[0.523356277296021299,0.800644834253704918,0.126965339384251918],"hpluv":[111.722667154579099,635.020942157405898,91.7137860391432156],"hsluv":[111.722667154579099,100.000000000002359,91.7137860391432156]},"#aaff11":{"lch":[91.7319300755291209,114.302910123776968,111.867287470019974],"luv":[91.7319300755291209,-42.5730312099484394,106.078707931238455],"rgb":[0.66666666666666663,1,0.0666666666666666657],"xyz":[0.524367942795658459,0.801049500453559826,0.132293444349007555],"hpluv":[111.867287470019974,632.205251281199821,91.7319300755291209],"hsluv":[111.867287470019974,99.9999999999902087,91.7319300755291209]},"#aaff22":{"lch":[91.76554812600844,112.870020698142184,112.139393409643191],"luv":[91.76554812600844,-42.5363318976714595,104.548084827461963],"rgb":[0.66666666666666663,1,0.133333333333333331],"xyz":[0.526243300934135405,0.801799643708950627,0.142170330544986734],"hpluv":[112.139393409643191,626.996151229530483,91.76554812600844],"hsluv":[112.139393409643191,99.9999999999901,91.76554812600844]},"#aaff33":{"lch":[91.8208541183991116,110.535111156119868,112.599114759903571],"luv":[91.8208541183991116,-42.4765495373732875,102.047800258971023],"rgb":[0.66666666666666663,1,0.2],"xyz":[0.529331051666593222,0.803034744001933753,0.158432484402597884],"hpluv":[112.599114759903571,618.449193227995465,91.8208541183991116],"hsluv":[112.599114759903571,99.9999999999900808,91.8208541183991116]},"#aaff44":{"lch":[91.9006031807778498,107.217215837920833,113.289723447654],"luv":[91.9006031807778498,-42.3916247074254713,98.4808688360338],"rgb":[0.66666666666666663,1,0.266666666666666663],"xyz":[0.533789046910812082,0.80481794209962132,0.181911259355484523],"hpluv":[113.289723447654,606.175591066234915,91.9006031807778498],"hsluv":[113.289723447654,99.9999999999900808,91.9006031807778498]},"#aaff55":{"lch":[92.0070808640835338,102.877427982590888,114.266397603879824],"luv":[92.0070808640835338,-42.2805422001356135,93.7876374559873796],"rgb":[0.66666666666666663,1,0.333333333333333315],"xyz":[0.539751460950174,0.807202907715366069,0.213313306629458438],"hpluv":[114.266397603879824,589.885725054723707,92.0070808640835338],"hsluv":[114.266397603879824,99.9999999999897,92.0070808640835338]},"#aaff66":{"lch":[92.142232175119787,97.5178938455906,115.604656391492952],"luv":[92.142232175119787,-42.1432392632638226,87.9413839126902559],"rgb":[0.66666666666666663,1,0.4],"xyz":[0.547336456723682252,0.810236906024769388,0.2532609510366024],"hpluv":[115.604656391492952,569.381090837871511,92.142232175119787],"hsluv":[115.604656391492952,99.9999999999898,92.142232175119787]},"#aaff77":{"lch":[92.3077308115560555,91.1840841453542197,117.412488820025033],"luv":[92.3077308115560555,-41.9805406670279666,80.9454841651530899],"rgb":[0.66666666666666663,1,0.466666666666666674],"xyz":[0.556650469743884102,0.813962511232850239,0.302314752943000309],"hpluv":[117.412488820025033,544.56608764574878,92.3077308115560555],"hsluv":[117.412488820025033,99.9999999999895692,92.3077308115560555]},"#aaff88":{"lch":[92.5050204995058749,83.9705472319660657,119.849478645386625],"luv":[92.5050204995058749,-41.7940850853900727,72.8306752289927601],"rgb":[0.66666666666666663,1,0.533333333333333326],"xyz":[0.567790886845828,0.818418678073627781,0.360987616346572504],"hpluv":[119.849478645386625,515.487838204951345,92.5050204995058749],"hsluv":[119.849478645386625,99.9999999999892708,92.5050204995058749]},"#aaff99":{"lch":[92.7353415895377,76.0327408171719,123.158131820453576],"luv":[92.7353415895377,-41.5862309318022625,63.6518897838709137],"rgb":[0.66666666666666663,1,0.6],"xyz":[0.580847850954900657,0.823641463717257,0.429754293987690383],"hpluv":[123.158131820453576,482.430180447041607,92.7353415895377],"hsluv":[123.158131820453576,99.9999999999891145,92.7353415895377]},"#aaffaa":{"lch":[92.9997492696274435,67.6109506527765802,127.715012949238414],"luv":[92.9997492696274435,-41.3599402641421108,53.4845397242865488],"rgb":[0.66666666666666663,1,0.66666666666666663],"xyz":[0.595905543920153269,0.829664540903358061,0.509058143604689],"hpluv":[127.715012949238414,446.121940639593902,92.9997492696274435],"hsluv":[127.715012949238414,99.9999999999887308,92.9997492696274435]},"#aaffbb":{"lch":[93.2991268167713201,59.0781485982363,134.10730048328],"luv":[93.2991268167713201,-41.1186450338129887,42.4203332421914823],"rgb":[0.66666666666666663,1,0.733333333333333282],"xyz":[0.613043136027489832,0.836519577746292819,0.599316128703330731],"hpluv":[134.10730048328,408.190288953842469,93.2991268167713201],"hsluv":[134.10730048328,99.9999999999883613,93.2991268167713201]},"#aaffcc":{"lch":[93.6341958749929404,51.0306218960527929,143.208028901391231],"luv":[93.6341958749929404,-40.8661030238149436,30.5628204644932246],"rgb":[0.66666666666666663,1,0.8],"xyz":[0.632335511471211587,0.844236527923781677,0.700922639373600886],"hpluv":[143.208028901391231,372.11656739719416,93.6341958749929404],"hsluv":[143.208028901391231,99.9999999999879492,93.6341958749929404]},"#aaffdd":{"lch":[94.005524978734,44.4262644213682094,156.066165366624773],"luv":[94.005524978734,-40.6062520520773305,18.0229094410556279],"rgb":[0.66666666666666663,1,0.866666666666666696],"xyz":[0.653853837021845186,0.852843858144035183,0.814252487273606884],"hpluv":[156.066165366624773,345.018188987702331,94.005524978734],"hsluv":[156.066165366624773,99.999999999986926,94.005524978734]},"#aaffee":{"lch":[94.4135370960349576,40.641316399200953,173.054449077896464],"luv":[94.4135370960349576,-40.3430708217409446,4.91459411670529711],"rgb":[0.66666666666666663,1,0.933333333333333348],"xyz":[0.677666017288019895,0.8623687302505052,0.939663303342130107],"hpluv":[173.054449077896464,339.745490646316398,94.4135370960349576],"hsluv":[173.054449077896464,99.9999999999862865,94.4135370960349576]},"#aaffff":{"lch":[94.8585166918378633,41.0030022313427764,192.177050630060705],"luv":[94.8585166918378633,-40.0804535568370639,-8.64889788711415264],"rgb":[0.66666666666666663,1,1],"xyz":[0.703837065697851316,0.872837149614437924,1.07749749163391195],"hpluv":[192.177050630060705,373.711432895013843,94.8585166918378633],"hsluv":[192.177050630060705,99.9999999999852491,94.8585166918378633]},"#338800":{"lch":[49.8717454753508918,71.2965557675853461,122.69380865426514],"luv":[49.8717454753508918,-38.5107906934601871,60.0009821960016509],"rgb":[0.2,0.533333333333333326,0],"xyz":[0.101689839911933699,0.183114842185751275,0.0299858551632874795],"hpluv":[122.69380865426514,181.406693814272955,49.8717454753508918],"hsluv":[122.69380865426514,100.000000000002245,49.8717454753508918]},"#338811":{"lch":[49.9202331837381053,69.5570511040883162,123.47043015419375],"luv":[49.9202331837381053,-38.361169332567485,58.0224443274741475],"rgb":[0.2,0.533333333333333326,0.0666666666666666657],"xyz":[0.102701505411570818,0.183519508385606128,0.0353139601280431092],"hpluv":[123.47043015419375,176.808802540010674,49.9202331837381053],"hsluv":[123.47043015419375,96.3624965314944291,49.9202331837381053]},"#338822":{"lch":[50.0099282713402,66.4316762124597915,124.985970570646856],"luv":[50.0099282713402,-38.0903182709230137,54.426971791721833],"rgb":[0.2,0.533333333333333326,0.133333333333333331],"xyz":[0.104576863550047847,0.184269651640996956,0.0451908463240223088],"hpluv":[124.985970570646856,168.561468288507513,50.0099282713402],"hsluv":[124.985970570646856,89.7672781446156876,50.0099282713402]},"#338833":{"lch":[50.1570811029395429,61.5658815699287487,127.715012949239991],"luv":[50.1570811029395429,-37.6619639194057143,48.7025076070614134],"rgb":[0.2,0.533333333333333326,0.2],"xyz":[0.10766461428250558,0.185504751933980055,0.061453000181633452],"hpluv":[127.715012949239991,155.756856758587162,50.1570811029395429],"hsluv":[127.715012949239991,79.3080015418374416,50.1570811029395429]},"#338844":{"lch":[50.3683877843642165,55.1600753981733334,132.238380805917416],"luv":[50.3683877843642165,-37.0795228857786441,40.8380080377971524],"rgb":[0.2,0.533333333333333326,0.266666666666666663],"xyz":[0.112122609526724454,0.187287950031667649,0.0849317751345200905],"hpluv":[132.238380805917416,138.965222681713072,50.3683877843642165],"hsluv":[132.238380805917416,79.9701630926319922,50.3683877843642165]},"#338855":{"lch":[50.648916869713247,47.7759983149871488,139.558488969535745],"luv":[50.648916869713247,-36.3608093871966886,30.990926990033774],"rgb":[0.2,0.533333333333333326,0.333333333333333315],"xyz":[0.118085023566086453,0.189672915647412482,0.116333822408493992],"hpluv":[139.558488969535745,119.695806374350084,50.648916869713247],"hsluv":[139.558488969535745,80.7922551095087726,50.648916869713247]},"#338866":{"lch":[51.0024095938582747,40.50700493167151,151.3112863122482],"luv":[51.0024095938582747,-35.5343950908498,19.4454162738124445],"rgb":[0.2,0.533333333333333326,0.4],"xyz":[0.125670019339594624,0.192706913956815773,0.156281466815637954],"hpluv":[151.3112863122482,100.78102351571394,51.0024095938582747],"hsluv":[151.3112863122482,81.7453783644028249,51.0024095938582747]},"#338877":{"lch":[51.4314426692160964,35.2513066472794421,169.271441625736571],"luv":[51.4314426692160964,-34.635118433957949,6.56225505495134165],"rgb":[0.2,0.533333333333333326,0.466666666666666674],"xyz":[0.134984032359796557,0.196432519164896596,0.205335268722035863],"hpluv":[169.271441625736571,86.9732781104886499,51.4314426692160964],"hsluv":[169.271441625736571,82.7938072280844182,51.4314426692160964]},"#338888":{"lch":[51.9375397067754818,34.4751674240590873,192.177050630061075],"luv":[51.9375397067754818,-33.6994920276336458,-7.27196025816705482],"rgb":[0.2,0.533333333333333326,0.533333333333333326],"xyz":[0.146124449461740358,0.200888686005674194,0.26400813212560803],"hpluv":[192.177050630061075,84.229522237058,51.9375397067754818],"hsluv":[192.177050630061075,83.8998231764876579,51.9375397067754818]},"#338899":{"lch":[52.5212658253882267,39.2899369869163,213.503984387174484],"luv":[52.5212658253882267,-32.7618132949538179,-21.68784770470414],"rgb":[0.2,0.533333333333333326,0.6],"xyz":[0.159181413570813085,0.206111471649303352,0.332774809766725965],"hpluv":[213.503984387174484,94.9260641143146415,52.5212658253882267],"hsluv":[213.503984387174484,85.0278067813718081,52.5212658253882267]},"#3388aa":{"lch":[53.1823203703714142,48.3403865273999855,228.783963154055925],"luv":[53.1823203703714142,-31.8514822326876157,-36.3631138545534753],"rgb":[0.2,0.533333333333333326,0.66666666666666663],"xyz":[0.174239106536065613,0.212134548835404446,0.412078659383724566],"hpluv":[228.783963154055925,115.340588590435829,53.1823203703714142],"hsluv":[228.783963154055925,86.1470646970195588,53.1823203703714142]},"#3388bb":{"lch":[53.9196335036436238,59.7107302664646866,238.732819450220632],"luv":[53.9196335036436238,-30.9916356267104831,-51.0381213421469],"rgb":[0.2,0.533333333333333326,0.733333333333333282],"xyz":[0.191376698643402288,0.218989585678339205,0.502336644482366257],"hpluv":[238.732819450220632,140.522147305520861,53.9196335036436238],"hsluv":[238.732819450220632,87.2332789223686262,53.9196335036436238]},"#3388cc":{"lch":[54.7314676322995268,72.1439684856409826,245.254262274299492],"luv":[54.7314676322995268,-30.1989009668194051,-65.5192992121664446],"rgb":[0.2,0.533333333333333326,0.8],"xyz":[0.210669074087124014,0.226706535855828,0.603943155152636413],"hpluv":[245.254262274299492,167.263916186866481,54.7314676322995268],"hsluv":[245.254262274299492,88.2687848362996,54.7314676322995268]},"#3388dd":{"lch":[55.6155220749816692,84.9545141717074728,249.692682338977761],"luv":[55.6155220749816692,-29.4839257322198733,-79.6741338300441697],"rgb":[0.2,0.533333333333333326,0.866666666666666696],"xyz":[0.232187399637757558,0.23531386607608154,0.717273003052642411],"hpluv":[249.692682338977761,193.833915517882872,55.6155220749816692],"hsluv":[249.692682338977761,89.2420383366021923,55.6155220749816692]},"#3388ee":{"lch":[56.5690381344568323,97.7754852991564434,252.83721230554255],"luv":[56.5690381344568323,-28.8523290085395629,-93.4215640859675602],"rgb":[0.2,0.533333333333333326,0.933333333333333348],"xyz":[0.255999579903932295,0.244838738182551585,0.842683819121165634],"hpluv":[252.83721230554255,219.32619909500923,56.5690381344568323],"hsluv":[252.83721230554255,90.146641202583325,56.5690381344568323]},"#3388ff":{"lch":[57.5889013880528182,110.410831334845568,255.145344615238457],"luv":[57.5889013880528182,-28.3057941013035759,-106.720821287817699],"rgb":[0.2,0.533333333333333326,1],"xyz":[0.282170628313763716,0.255307157546484254,0.980518007412947479],"hpluv":[255.145344615238457,243.283252172694205,57.5889013880528182],"hsluv":[255.145344615238457,99.9999999999988489,57.5889013880528182]},"#339900":{"lch":[55.5688440832231,80.82821284508357,123.866754715109295],"luv":[55.5688440832231,-45.0426054376311527,67.1145564473163603],"rgb":[0.2,0.6,0],"xyz":[0.127559440364401172,0.23485404309068697,0.0386090553141097345],"hpluv":[123.866754715109295,184.574176757765827,55.5688440832231],"hsluv":[123.866754715109295,100.00000000000226,55.5688440832231]},"#339911":{"lch":[55.6099261578814463,79.3206910999707446,124.475743785225987],"luv":[55.6099261578814463,-44.9000555787807372,65.3892731692238556],"rgb":[0.2,0.6,0.0666666666666666657],"xyz":[0.128571105864038304,0.235258709290541823,0.0439371602788653642],"hpluv":[124.475743785225987,180.997883954149785,55.6099261578814463],"hsluv":[124.475743785225987,97.1571693172972601,55.6099261578814463]},"#339922":{"lch":[55.6859569378682124,76.5942224429011418,125.649114872203555],"luv":[55.6859569378682124,-44.6406262928881503,62.240576763164114],"rgb":[0.2,0.6,0.133333333333333331],"xyz":[0.130446464002515305,0.236008852545932651,0.0538140464748445638],"hpluv":[125.649114872203555,174.537861698610129,55.6859569378682124],"hsluv":[125.649114872203555,91.9778413864117113,55.6859569378682124]},"#339933":{"lch":[55.8107903110879278,72.2971425020977563,127.715012949240148],"luv":[55.8107903110879278,-44.2266447414942618,57.191613973364035],"rgb":[0.2,0.6,0.2],"xyz":[0.133534214734973067,0.23724395283891575,0.070076200332455707],"hpluv":[127.715012949240148,164.377468022308932,55.8107903110879278],"hsluv":[127.715012949240148,83.6974291768240874,55.8107903110879278]},"#339944":{"lch":[55.9902586993563602,66.5149718399310501,131.021169741293448],"luv":[55.9902586993563602,-43.6562926505411042,50.1833597009719128],"rgb":[0.2,0.6,0.266666666666666663],"xyz":[0.137992209979191927,0.239027150936603344,0.0935549752853423455],"hpluv":[131.021169741293448,150.746162046382238,55.9902586993563602],"hsluv":[131.021169741293448,84.1112663411127386,55.9902586993563602]},"#339955":{"lch":[56.2289015921924,59.572114639014508,136.121417864766215],"luv":[56.2289015921924,-42.9401916463123783,41.2913645202220323],"rgb":[0.2,0.6,0.333333333333333315],"xyz":[0.143954624018553912,0.241412116552348177,0.124957022559316261],"hpluv":[136.121417864766215,134.438216783596687,56.2289015921924],"hsluv":[136.121417864766215,84.6329955380286236,56.2289015921924]},"#339966":{"lch":[56.5302268191487514,52.1043875673731947,143.898662284026642],"luv":[56.5302268191487514,-42.0991012865816785,30.700698275986678],"rgb":[0.2,0.6,0.4],"xyz":[0.151539619792062097,0.244446114861751468,0.164904666966460223],"hpluv":[143.898662284026642,116.958797396111564,56.5302268191487514],"hsluv":[143.898662284026642,85.2491724948614689,56.5302268191487514]},"#339977":{"lch":[56.8968484427531678,45.1992705399313124,155.596009487994166],"luv":[56.8968484427531678,-41.1609366004351855,18.6748856895258584],"rgb":[0.2,0.6,0.466666666666666674],"xyz":[0.160853632812264,0.248171720069832291,0.213958468872858132],"hpluv":[155.596009487994166,100.805108459810327,56.8968484427531678],"hsluv":[155.596009487994166,85.9413862611579873,56.8968484427531678]},"#339988":{"lch":[57.330574208667926,40.5351988689668303,172.17134267960472],"luv":[57.330574208667926,-40.1574041849560217,5.52134371985359085],"rgb":[0.2,0.6,0.533333333333333326],"xyz":[0.171994049914207858,0.252627886910609889,0.272631332276430272],"hpluv":[172.17134267960472,89.7191897797317,57.330574208667926],"hsluv":[172.17134267960472,86.6885468018933665,57.330574208667926]},"#339999":{"lch":[57.8324724587931627,40.0212192418936539,192.177050630061103],"luv":[57.8324724587931627,-39.1207602326874735,-8.44180717763071],"rgb":[0.2,0.6,0.6],"xyz":[0.185051014023280558,0.257850672554239047,0.341398009917548206],"hpluv":[192.177050630061103,87.8128114771670312,57.8324724587931627],"hsluv":[192.177050630061103,87.4690863712762194,57.8324724587931627]},"#3399aa":{"lch":[58.4029323329449568,44.4430671798845367,211.035048784986628],"luv":[58.4029323329449568,-38.0811346860811923,-22.913170914918652],"rgb":[0.2,0.6,0.66666666666666663],"xyz":[0.200108706988533114,0.263873749740340169,0.420701859534546807],"hpluv":[211.035048784986628,96.5625435737210154,58.4029323329449568],"hsluv":[211.035048784986628,88.2627766327166,58.4029323329449568]},"#3399bb":{"lch":[59.0417237507550823,52.8166449153409445,225.431517702392057],"luv":[59.0417237507550823,-37.064675814253917,-37.6272213549907448],"rgb":[0.2,0.6,0.733333333333333282],"xyz":[0.217246299095869733,0.270728786583274927,0.510959844633188554],"hpluv":[225.431517702392057,113.514439229551684,59.0417237507550823],"hsluv":[225.431517702392057,89.0519907331041196,59.0417237507550823]},"#3399cc":{"lch":[59.7480598269018373,63.5982744854026194,235.423336237979868],"luv":[59.7480598269018373,-36.0925574956591646,-52.364757336811472],"rgb":[0.2,0.6,0.8],"xyz":[0.236538674539591487,0.278445736760763729,0.612566355303458709],"hpluv":[235.423336237979868,135.070607162709791,59.7480598269018373],"hsluv":[235.423336237979868,89.8223822716188636,59.7480598269018373]},"#3399dd":{"lch":[60.5206621982976856,75.6357863680042186,242.281144195606601],"luv":[60.5206621982976856,-35.1807304699237235,-66.9558689220665286],"rgb":[0.2,0.6,0.866666666666666696],"xyz":[0.25805700009022503,0.287053066981017235,0.725896203203464707],"hpluv":[242.281144195606601,158.58531958905732,60.5206621982976856],"hsluv":[242.281144195606601,90.5630616197976508,60.5206621982976856]},"#3399ee":{"lch":[61.3578284965586818,88.2341933332873083,247.095644636752098],"luv":[61.3578284965586818,-34.3402162860227946,-81.277441019049661],"rgb":[0.2,0.6,0.933333333333333348],"xyz":[0.28186918035639974,0.296577939087487252,0.85130701927198793],"hpluv":[247.095644636752098,182.476215369006297,61.3578284965586818],"hsluv":[247.095644636752098,91.2664056494933504,61.3578284965586818]},"#3399ff":{"lch":[62.2575005434706554,100.992674077385175,250.5808182685887],"luv":[62.2575005434706554,-33.5777301361133595,-95.2473425151972],"rgb":[0.2,0.6,1],"xyz":[0.308040228766231161,0.30704635845142,0.989141207563769775],"hpluv":[250.5808182685887,205.84367033720622,62.2575005434706554],"hsluv":[250.5808182685887,99.9999999999985789,62.2575005434706554]},"#220000":{"lch":[3.07250446727781679,10.3329293192956264,12.1770506300617765],"luv":[3.07250446727781679,10.1004431663672367,2.17955870775360072],"rgb":[0.133333333333333331,0,0],"xyz":[0.00659672420629513,0.00340143591887099878,0.000309221447170077699],"hpluv":[12.1770506300617765,426.746789183125429,3.07250446727781679],"hsluv":[12.1770506300617765,100.000000000002217,3.07250446727781679]},"#220011":{"lch":[3.43803794680403607,8.12070857757986353,344.488545895364155],"luv":[3.43803794680403607,7.82492808895188574,-2.17172931202554675],"rgb":[0.133333333333333331,0,0.0666666666666666657],"xyz":[0.00760838970593225201,0.00380610211872585338,0.00563732641192570948],"hpluv":[344.488545895364155,299.724735916282839,3.43803794680403607],"hsluv":[344.488545895364155,99.9999999999976836,3.43803794680403607]},"#220022":{"lch":[4.11563957101797229,9.37475958111893348,307.715012949243601],"luv":[4.11563957101797229,5.73486236359989565,-7.41602797151862436],"rgb":[0.133333333333333331,0,0.133333333333333331],"xyz":[0.00948374784440927,0.00455624537411667124,0.0155142126079049047],"hpluv":[307.715012949243601,289.042783730483393,4.11563957101797229],"hsluv":[307.715012949243601,99.9999999999988205,4.11563957101797229]},"#220033":{"lch":[5.23130109110515384,14.2535250315243012,286.735013267555587],"luv":[5.23130109110515384,4.10424250296207127,-13.6498413654214126],"rgb":[0.133333333333333331,0,0.2],"xyz":[0.0125714985768670112,0.00579134566709978496,0.0317763664655160497],"hpluv":[286.735013267555587,345.74180296647927,5.23130109110515384],"hsluv":[286.735013267555587,99.9999999999995737,5.23130109110515384]},"#220044":{"lch":[6.84205732813722722,21.3889830656619786,277.641816515271671],"luv":[6.84205732813722722,2.84430225454687724,-21.1990221771654959],"rgb":[0.133333333333333331,0,0.266666666666666663],"xyz":[0.01702949382108589,0.0075745437647873606,0.0552551414184026882],"hpluv":[277.641816515271671,396.682237683346386,6.84205732813722722],"hsluv":[277.641816515271671,100.000000000000085,6.84205732813722722]},"#220055":{"lch":[8.95766614306443,30.4428627575942237,273.263558660643355],"luv":[8.95766614306443,1.73308321478426808,-30.3934913336449455],"rgb":[0.133333333333333331,0,0.333333333333333315],"xyz":[0.0229919078604478855,0.00995950938053219263,0.0866571886923766],"hpluv":[273.263558660643355,431.250830347711485,8.95766614306443],"hsluv":[273.263558660643355,100.000000000000242,8.95766614306443]},"#220066":{"lch":[11.2709410858812937,40.3162667149428913,270.881506896841],"luv":[11.2709410858812937,0.620249265146302853,-40.3114952920317435],"rgb":[0.133333333333333331,0,0.4],"xyz":[0.0305769036339560568,0.0129935076899355059,0.126604833099520558],"hpluv":[270.881506896841,453.899240935372916,11.2709410858812937],"hsluv":[270.881506896841,100.000000000000469,11.2709410858812937]},"#220077":{"lch":[13.6616791408408957,50.492834518379162,269.459540268375122],"luv":[13.6616791408408957,-0.476281836738408071,-50.4905881656414763],"rgb":[0.133333333333333331,0,0.466666666666666674],"xyz":[0.0398909166541579763,0.0167191128980163223,0.175658635005918468],"hpluv":[269.459540268375122,468.991527020998944,13.6616791408408957],"hsluv":[269.459540268375122,100.000000000000711,13.6616791408408957]},"#220088":{"lch":[16.0923146306383913,60.7890037695263104,268.549935621017426],"luv":[16.0923146306383913,-1.53830805749361632,-60.7695366743217278],"rgb":[0.133333333333333331,0,0.533333333333333326],"xyz":[0.0510313337561018043,0.0211752797387939132,0.234331498409490635],"hpluv":[268.549935621017426,479.34239057424071,16.0923146306383913],"hsluv":[268.549935621017426,100.000000000000711,16.0923146306383913]},"#220099":{"lch":[18.5394450926422749,71.1015482986176437,267.936483797094468],"luv":[18.5394450926422749,-2.56017951479828287,-71.0554406876254916],"rgb":[0.133333333333333331,0,0.6],"xyz":[0.0640882978651745178,0.0263980653824230742,0.30309817605060857],"hpluv":[267.936483797094468,486.655519564945394,18.5394450926422749],"hsluv":[267.936483797094468,100.000000000000739,18.5394450926422749]},"#2200aa":{"lch":[20.9885603179873783,81.3727160976321,267.505178931910336],"luv":[20.9885603179873783,-3.54207977840461252,-81.2955878012407851],"rgb":[0.133333333333333331,0,0.66666666666666663],"xyz":[0.0791459908304270598,0.0324211425685241791,0.382402025667607171],"hpluv":[267.505178931910336,491.966452636739518,20.9885603179873783],"hsluv":[267.505178931910336,100.000000000000782,20.9885603179873783]},"#2200bb":{"lch":[23.4306921856835828,91.57073581353,267.191578225858507],"luv":[23.4306921856835828,-4.48665301772878333,-91.4607544366971155],"rgb":[0.133333333333333331,0,0.733333333333333282],"xyz":[0.0962835829377637,0.0392761794114589377,0.472660010766248917],"hpluv":[267.191578225858507,495.919187528698728,23.4306921856835828],"hsluv":[267.191578225858507,100.000000000000909,23.4306921856835828]},"#2200cc":{"lch":[25.860342630381858,101.678845182637474,266.957159441292106],"luv":[25.860342630381858,-5.39738007409318588,-101.535490573545474],"rgb":[0.133333333333333331,0,0.8],"xyz":[0.115575958381485447,0.0469931295889477393,0.574266521436519],"hpluv":[266.957159441292106,498.925449111647538,25.860342630381858],"hsluv":[266.957159441292106,100.000000000000881,25.860342630381858]},"#2200dd":{"lch":[28.2742062228116282,111.689036790699618,266.777814373778199],"luv":[28.2742062228116282,-6.27782958394359714,-111.512464751476173],"rgb":[0.133333333333333331,0,0.866666666666666696],"xyz":[0.137094283932118977,0.0556004598092012733,0.687596369336525],"hpluv":[266.777814373778199,501.255846139694427,28.2742062228116282],"hsluv":[266.777814373778199,100.000000000000838,28.2742062228116282]},"#2200ee":{"lch":[30.6703766456275062,121.598437280905173,266.637867063772376],"luv":[30.6703766456275062,-7.13133722672581882,-121.389142753859758],"rgb":[0.133333333333333331,0,0.933333333333333348],"xyz":[0.160906464198293714,0.0651253319156713,0.813007185405048238],"hpluv":[266.637867063772376,503.092926092128948,30.6703766456275062],"hsluv":[266.637867063772376,100.000000000000838,30.6703766456275062]},"#2200ff":{"lch":[33.0478477502328261,131.407178056457695,266.526788769360394],"luv":[33.0478477502328261,-7.96089030872800674,-131.165813649189772],"rgb":[0.133333333333333331,0,1],"xyz":[0.187077512608125107,0.0755937512796040073,0.950841373696830083],"hpluv":[266.526788769360394,504.562807291912918,33.0478477502328261],"hsluv":[266.526788769360394,100.000000000000824,33.0478477502328261]},"#221100":{"lch":[6.69363913087575835,9.72440836304526535,42.3457761997067053],"luv":[6.69363913087575835,7.18724369375563921,6.55039282011655288],"rgb":[0.133333333333333331,0.0666666666666666657,0],"xyz":[0.00860112446722354,0.00741023644072787233,0.000977354867479528471],"hpluv":[42.3457761997067053,184.348759610596915,6.69363913087575835],"hsluv":[42.3457761997067053,100.000000000002402,6.69363913087575835]},"#221111":{"lch":[7.0591726104019763,6.19439175917428564,12.1770506300621],"luv":[7.0591726104019763,6.05502079617615863,1.30660339200560327],"rgb":[0.133333333333333331,0.0666666666666666657,0.0666666666666666657],"xyz":[0.00961278996686066103,0.00781490264058272606,0.00630545983223516],"hpluv":[12.1770506300621,111.348454543071412,7.0591726104019763],"hsluv":[12.1770506300621,26.092394217240134,7.0591726104019763]},"#221122":{"lch":[7.73677423461591474,7.55259268754738677,307.715012949245249],"luv":[7.73677423461591474,4.62018030186617779,-5.97457866985129],"rgb":[0.133333333333333331,0.0666666666666666657,0.133333333333333331],"xyz":[0.0114881481053376797,0.00856504589597354565,0.0161823460282143547],"hpluv":[307.715012949245249,123.872660774597591,7.73677423461591474],"hsluv":[307.715012949245249,42.856167926372315,7.73677423461591474]},"#221133":{"lch":[8.8238329822443653,14.9761175001236957,282.095598903329574],"luv":[8.8238329822443653,3.13814740657790647,-14.6436377390353236],"rgb":[0.133333333333333331,0.0666666666666666657,0.2],"xyz":[0.0145758988377954202,0.00980014618895665851,0.0324444998858255],"hpluv":[282.095598903329574,215.36805510923017,8.8238329822443653],"hsluv":[282.095598903329574,58.3941618505161273,8.8238329822443653]},"#221144":{"lch":[10.2463738670161,23.9334379374521049,274.255517801158362],"luv":[10.2463738670161,1.77596948547040223,-23.8674544912638211],"rgb":[0.133333333333333331,0.0666666666666666657,0.266666666666666663],"xyz":[0.0190338940820143,0.011583344286644235,0.0559232748387121364],"hpluv":[274.255517801158362,296.397281412249697,10.2463738670161],"hsluv":[274.255517801158362,70.1230959508528287,10.2463738670161]},"#221155":{"lch":[11.9365395500671561,33.3852442414483903,270.945812779521702],"luv":[11.9365395500671561,0.551083463009601,-33.3806956200430349],"rgb":[0.133333333333333331,0.0666666666666666657,0.333333333333333315],"xyz":[0.0249963081213762928,0.0139683099023890662,0.0873253221126860518],"hpluv":[270.945812779521702,354.907717298979946,11.9365395500671561],"hsluv":[270.945812779521702,78.3035236172291,11.9365395500671561]},"#221166":{"lch":[13.8282163263251512,43.1006504094303509,269.247085426223],"luv":[13.8282163263251512,-0.566362377205730683,-43.0969291176716496],"rgb":[0.133333333333333331,0.0666666666666666657,0.4],"xyz":[0.0325813038948844641,0.0170023082117923795,0.12727296651983],"hpluv":[269.247085426223,395.509560036398682,13.8282163263251512],"hsluv":[269.247085426223,83.9084652674952167,13.8282163263251512]},"#221177":{"lch":[15.8647012598499089,52.9790758801604,268.263491631539182],"luv":[15.8647012598499089,-1.60543281970815044,-52.954745458525224],"rgb":[0.133333333333333331,0.0666666666666666657,0.466666666666666674],"xyz":[0.0418953169150863836,0.0207279134198731958,0.176326768426227909],"hpluv":[268.263491631539182,423.75204448359591,15.8647012598499089],"hsluv":[268.263491631539182,87.783734282584831,15.8647012598499089]},"#221188":{"lch":[18.0016522099437424,62.9493123294311872,267.645454528547475],"luv":[18.0016522099437424,-2.58614693443793442,-62.8961665507666297],"rgb":[0.133333333333333331,0.0666666666666666657,0.533333333333333326],"xyz":[0.0530357340170302116,0.0251840802606507867,0.234999631829800076],"hpluv":[267.645454528547475,443.729139668046173,18.0016522099437424],"hsluv":[267.645454528547475,90.5156952975567464,18.0016522099437424]},"#221199":{"lch":[20.2061107483083475,72.9561611588118524,267.23334268313738],"luv":[20.2061107483083475,-3.52148568403582729,-72.8711231531918742],"rgb":[0.133333333333333331,0.0666666666666666657,0.6],"xyz":[0.066092698126102925,0.0304068659042799477,0.303766309470918],"hpluv":[267.23334268313738,458.161478440778865,20.2061107483083475],"hsluv":[267.23334268313738,92.4853336382361704,20.2061107483083475]},"#2211aa":{"lch":[22.4542330055690798,82.9584275230068471,266.945798214496847],"luv":[22.4542330055690798,-4.42007776686241449,-82.8405915576709759],"rgb":[0.133333333333333331,0.0666666666666666657,0.66666666666666663],"xyz":[0.0811503910913554671,0.0364299430903810492,0.383070159087916584],"hpluv":[266.945798214496847,468.815123306781686,22.4542330055690798],"hsluv":[266.945798214496847,93.9373181855700921,22.4542330055690798]},"#2211bb":{"lch":[24.7289983316777295,92.9268656932788701,266.737844101991243],"luv":[24.7289983316777295,-5.28796663316276483,-92.7762888698575097],"rgb":[0.133333333333333331,0.0666666666666666657,0.733333333333333282],"xyz":[0.0982879831986921138,0.0432849799333158078,0.473328144186558331],"hpluv":[266.737844101991243,476.841549380862261,24.7289983316777295],"hsluv":[266.737844101991243,95.0302193246277369,24.7289983316777295]},"#2211cc":{"lch":[27.0183279904475668,102.841627589427389,266.583003116257],"luv":[27.0183279904475668,-6.12961814831514129,-102.658794784462174],"rgb":[0.133333333333333331,0.0666666666666666657,0.8],"xyz":[0.117580358642413854,0.0510019301108046094,0.574934654856828486],"hpluv":[266.583003116257,483.002966655360069,27.0183279904475668],"hsluv":[266.583003116257,95.8686128249461689,27.0183279904475668]},"#2211dd":{"lch":[29.313669578695368,112.689841457512074,266.464886381494523],"luv":[29.313669578695368,-6.94848190374932617,-112.475414944566779],"rgb":[0.133333333333333331,0.0666666666666666657,0.866666666666666696],"xyz":[0.139098684193047384,0.0596092603310581434,0.688264502756834484],"hpluv":[266.464886381494523,487.813602393737767,29.313669578695368],"hsluv":[266.464886381494523,96.5228774582602256,29.313669578695368]},"#2211ee":{"lch":[31.6089746608533417,122.463629630364608,266.372923821556242],"luv":[31.6089746608533417,-7.74731231829930689,-122.21832814306471],"rgb":[0.133333333333333331,0.0666666666666666657,0.933333333333333348],"xyz":[0.162910864459222149,0.0691341324375281813,0.813675318825357707],"hpluv":[266.372923821556242,491.627361233059389,31.6089746608533417],"hsluv":[266.372923821556242,97.0413663214109761,31.6089746608533417]},"#2211ff":{"lch":[33.8999739889387115,132.15860685194221,266.300059947999785],"luv":[33.8999739889387115,-8.52836196764118,-131.883146789857562],"rgb":[0.133333333333333331,0.0666666666666666657,1],"xyz":[0.189081912869053514,0.0796025518014608774,0.951509507117139552],"hpluv":[266.300059947999785,494.692599475339,33.8999739889387115],"hsluv":[266.300059947999785,99.9999999999995737,33.8999739889387115]},"#77aa00":{"lch":[63.8935034159882491,78.4053265973676616,109.262687899665323],"luv":[63.8935034159882491,-25.8658938451783591,74.0158819067222424],"rgb":[0.466666666666666674,0.66666666666666663,0],"xyz":[0.219816749274909073,0.326708497135311,0.0514797056256764834],"hpluv":[109.262687899665323,155.714190603412163,63.8935034159882491],"hsluv":[109.262687899665323,100.000000000002132,63.8935034159882491]},"#77aa11":{"lch":[63.9264755829735662,77.0719876411919,109.595002167467626],"luv":[63.9264755829735662,-25.8475857965541422,72.6084952843247464],"rgb":[0.466666666666666674,0.66666666666666663,0.0666666666666666657],"xyz":[0.220828414774546206,0.32711316333516588,0.0568078105904321132],"hpluv":[109.595002167467626,152.98720995680398,63.9264755829735662],"hsluv":[109.595002167467626,97.9667163347824612,63.9264755829735662]},"#77aa22":{"lch":[63.9875253363426424,74.6349938152274603,110.235118064089221],"luv":[63.9875253363426424,-25.8142561390327501,70.0286118796271],"rgb":[0.466666666666666674,0.66666666666666663,0.133333333333333331],"xyz":[0.222703772913023207,0.32786330659055668,0.0666846967864113127],"hpluv":[110.235118064089221,148.008450888663447,63.9875253363426424],"hsluv":[110.235118064089221,94.2440053809013421,63.9875253363426424]},"#77aa33":{"lch":[64.0878403339217328,70.7199026331045,111.362593229805157],"luv":[64.0878403339217328,-25.7610573917560757,65.8610093340090685],"rgb":[0.466666666666666674,0.66666666666666663,0.2],"xyz":[0.22579152364548094,0.329098406883539807,0.0829468506440224629],"hpluv":[111.362593229805157,140.024923426145421,64.0878403339217328],"hsluv":[111.362593229805157,88.2427941375042,64.0878403339217328]},"#77aa44":{"lch":[64.2322300921174616,65.2796522257345,113.17268826837423],"luv":[64.2322300921174616,-25.6877867579927681,60.0130869576689463],"rgb":[0.466666666666666674,0.66666666666666663,0.266666666666666663],"xyz":[0.230249518889699828,0.330881604981227373,0.106425625596909101],"hpluv":[113.17268826837423,128.962712139229978,64.2322300921174616],"hsluv":[113.17268826837423,79.8488372015295482,64.2322300921174616]},"#77aa55":{"lch":[64.4245377454048338,58.3957340906732298,115.996670283872277],"luv":[64.4245377454048338,-25.5959546947607706,52.487225714952487],"rgb":[0.466666666666666674,0.66666666666666663,0.333333333333333315],"xyz":[0.236211932929061841,0.333266570596972234,0.137827672870883],"hpluv":[115.996670283872277,115.018880896771989,64.4245377454048338],"hsluv":[115.996670283872277,69.0944053471027075,64.4245377454048338]},"#77aa66":{"lch":[64.6678576456364,50.3036055521827663,120.443938093288892],"luv":[64.6678576456364,-25.4885877435648602,43.368013862617552],"rgb":[0.466666666666666674,0.66666666666666663,0.4],"xyz":[0.24379692870257,0.336300568906375552,0.177775317278026979],"hpluv":[120.443938093288892,98.707457943709727,64.6678576456364],"hsluv":[120.443938093288892,56.1391996219007723,64.6678576456364]},"#77aa77":{"lch":[64.964649263009,41.4721527505437351,127.71501294923786],"luv":[64.964649263009,-25.3699399849719889,32.8070967768157118],"rgb":[0.466666666666666674,0.66666666666666663,0.466666666666666674],"xyz":[0.253110941722771932,0.340026174114456348,0.226829119184424888],"hpluv":[127.71501294923786,81.006302212696113,64.964649263009],"hsluv":[127.71501294923786,41.2466460512923874,64.964649263009]},"#77aa88":{"lch":[65.3168057391263375,32.8409027786736161,140.238161573904051],"luv":[65.3168057391263375,-25.2451203613846147,21.004970679758074],"rgb":[0.466666666666666674,0.66666666666666663,0.533333333333333326],"xyz":[0.264251358824715732,0.344482340955233945,0.285501982587997],"hpluv":[140.238161573904051,63.8012923553667,65.3168057391263375],"hsluv":[140.238161573904051,43.6199696205744232,65.3168057391263375]},"#77aa99":{"lch":[65.7257012160132,26.4214783611449491,161.939459903532622],"luv":[65.7257012160132,-25.1196781367095241,8.19123245284666],"rgb":[0.466666666666666674,0.66666666666666663,0.6],"xyz":[0.277308322933788487,0.349705126598863103,0.354268660229114962],"hpluv":[161.939459903532622,51.0106916742292213,65.7257012160132],"hsluv":[161.939459903532622,46.1685656356417766,65.7257012160132]},"#77aaaa":{"lch":[66.1922284915170565,25.5746156232648509,192.177050630060677],"luv":[66.1922284915170565,-24.9991985449953802,-5.39453764336142338],"rgb":[0.466666666666666674,0.66666666666666663,0.66666666666666663],"xyz":[0.292366015899041,0.355728203784964225,0.433572509846113563],"hpluv":[192.177050630060677,49.0276908522131265,66.1922284915170565],"hsluv":[192.177050630060677,48.8357820868943122,66.1922284915170565]},"#77aabb":{"lch":[66.7168329685055568,31.6300901405034409,218.105494045696133],"luv":[66.7168329685055568,-24.8889541052261336,-19.519287021925777],"rgb":[0.466666666666666674,0.66666666666666663,0.733333333333333282],"xyz":[0.309503608006377662,0.362583240627899,0.523830494944755309],"hpluv":[218.105494045696133,60.1595160354738923,66.7168329685055568],"hsluv":[218.105494045696133,51.56699221483656,66.7168329685055568]},"#77aacc":{"lch":[67.2995460165272306,42.0550186755790207,233.874741843371567],"luv":[67.2995460165272306,-24.7936410920562054,-33.9691029790543908],"rgb":[0.466666666666666674,0.66666666666666663,0.8],"xyz":[0.328795983450099416,0.370300190805387786,0.625437005615025465],"hpluv":[233.874741843371567,79.2948573353567099,67.2995460165272306],"hsluv":[233.874741843371567,54.3124963748670382,67.2995460165272306]},"#77aadd":{"lch":[67.9400192524912,54.4863937886276304,243.022531341574677],"luv":[67.9400192524912,-24.7172120267711577,-48.557456046554158],"rgb":[0.466666666666666674,0.66666666666666663,0.866666666666666696],"xyz":[0.350314309000732904,0.378907521025641292,0.738766853515031463],"hpluv":[243.022531341574677,101.765770647841308,67.9400192524912],"hsluv":[243.022531341574677,60.6446157506369659,67.9400192524912]},"#77aaee":{"lch":[68.6375602707836,67.7758398266106781,248.660849953701415],"luv":[68.6375602707836,-24.6627990544268876,-63.1293181256010882],"rgb":[0.466666666666666674,0.66666666666666663,0.933333333333333348],"xyz":[0.374126489266907669,0.388432393132111309,0.864177669583554686],"hpluv":[248.660849953701415,125.300382334337471,68.6375602707836],"hsluv":[248.660849953701415,79.8712566390148453,68.6375602707836]},"#77aaff":{"lch":[69.3911697465266428,81.3794470542267874,252.380781260055755],"luv":[69.3911697465266428,-24.6327125013740549,-77.5618712878715399],"rgb":[0.466666666666666674,0.66666666666666663,1],"xyz":[0.400297537676739035,0.398900812496044033,1.00201185787533653],"hpluv":[252.380781260055755,148.816077288451226,69.3911697465266428],"hsluv":[252.380781260055755,99.9999999999980531,69.3911697465266428]},"#222200":{"lch":[12.5069288045758107,13.787646171799997,85.8743202181747307],"luv":[12.5069288045758107,0.991945128669063814,13.751917387057734],"rgb":[0.133333333333333331,0.133333333333333331,0],"xyz":[0.0123167482019914745,0.014841483910263846,0.002215896112402139],"hpluv":[85.8743202181747307,139.887458074797621,12.5069288045758107],"hsluv":[85.8743202181747307,100.000000000002359,12.5069288045758107]},"#222211":{"lch":[12.7636979604368612,8.34346759842367,85.8743202181729828],"luv":[12.7636979604368612,0.600266494900015157,8.32184665209868513],"rgb":[0.133333333333333331,0.133333333333333331,0.0666666666666666657],"xyz":[0.0133284137016285963,0.0152461501101187,0.00754400107715777046],"hpluv":[85.8743202181729828,82.9486632734846552,12.7636979604368612],"hsluv":[85.8743202181729828,59.2967120963297631,12.7636979604368612]},"#222222":{"lch":[13.2279109842717837,6.86787642036123471e-13,0],"luv":[13.2279109842717837,6.53891093021720259e-13,2.10008818196756883e-13],"rgb":[0.133333333333333331,0.133333333333333331,0.133333333333333331],"xyz":[0.0152037718401056149,0.0159962933655095202,0.0174208872731369674],"hpluv":[0,6.58825703928357502e-12,13.2279109842717837],"hsluv":[0,1.88635445986832e-12,13.2279109842717837]},"#222233":{"lch":[13.9615854376221584,10.5260121123804868,265.874320218180912],"luv":[13.9615854376221584,-0.757288539977712838,-10.4987354027615698],"rgb":[0.133333333333333331,0.133333333333333331,0.2],"xyz":[0.0182915225725633554,0.017231393658492633,0.0336830411307481106],"hpluv":[265.874320218180912,95.6683874279760431,13.9615854376221584],"hsluv":[265.874320218180912,18.6338179823007195,13.9615854376221584]},"#222244":{"lch":[14.9613810506728697,21.7214686924654536,265.874320218179207],"luv":[14.9613810506728697,-1.5627399186581008,-21.6651805001571454],"rgb":[0.133333333333333331,0.133333333333333331,0.266666666666666663],"xyz":[0.0227495178167822359,0.0190145917561802061,0.0571618160836347491],"hpluv":[265.874320218179207,184.228505509793536,14.9613810506728697],"hsluv":[265.874320218179207,35.8831222215914138,14.9613810506728697]},"#222255":{"lch":[16.2052187005970154,32.8139057554865161,265.874320218178639],"luv":[16.2052187005970154,-2.3607796110480268,-32.728873041368395],"rgb":[0.133333333333333331,0.133333333333333331,0.333333333333333315],"xyz":[0.028711931856144228,0.021399557371925039,0.0885638633576086576],"hpluv":[265.874320218178639,256.946292996249099,16.2052187005970154],"hsluv":[265.874320218178639,50.0467352240393097,16.2052187005970154]},"#222266":{"lch":[17.6604729086265309,43.5908485911403716,265.874320218178354],"luv":[17.6604729086265309,-3.13612123314657865,-43.477888915018994],"rgb":[0.133333333333333331,0.133333333333333331,0.4],"xyz":[0.0362969276296524063,0.0244335556813283505,0.128511507764752619],"hpluv":[265.874320218178354,313.207621322876264,17.6604729086265309],"hsluv":[265.874320218178354,61.00504004829466,17.6604729086265309]},"#222277":{"lch":[19.2910482951380544,54.0745009411091573,265.874320218178241],"luv":[19.2910482951380544,-3.89036222175517965,-53.9343743248547867],"rgb":[0.133333333333333331,0.133333333333333331,0.466666666666666674],"xyz":[0.0456109406498543188,0.0281591608894091669,0.177565309671150529],"hpluv":[265.874320218178241,355.693573155256843,19.2910482951380544],"hsluv":[265.874320218178241,69.2802447897283429,19.2910482951380544]},"#222288":{"lch":[21.0622605487373207,64.3390225585563371,265.874320218178184],"luv":[21.0622605487373207,-4.62883796225993471,-64.1722968492601353],"rgb":[0.133333333333333331,0.133333333333333331,0.533333333333333326],"xyz":[0.0567513577517981468,0.0326153277301867578,0.236238173074722696],"hpluv":[265.874320218178184,387.622344883614403,21.0622605487373207],"hsluv":[265.874320218178184,75.4991739133377706,21.0622605487373207]},"#222299":{"lch":[22.9434551626666803,74.4470789880776351,265.874320218178127],"luv":[22.9434551626666803,-5.35605689510984373,-74.2541596437089737],"rgb":[0.133333333333333331,0.133333333333333331,0.6],"xyz":[0.0698083218608708533,0.0378381133738159223,0.305004850715840603],"hpluv":[265.874320218178127,411.744842564929684,22.9434551626666803],"hsluv":[265.874320218178127,80.1976353712613559,22.9434551626666803]},"#2222aa":{"lch":[24.9089307040763188,84.4389391258505526,265.87432021817807],"luv":[24.9089307040763188,-6.07491614537627278,-84.2201272530844705],"rgb":[0.133333333333333331,0.133333333333333331,0.66666666666666663],"xyz":[0.0848660148261234093,0.0438611905599170238,0.384308700332839204],"hpluv":[265.87432021817807,430.157015573344836,24.9089307040763188],"hsluv":[265.87432021817807,83.7838678741945557,24.9089307040763188]},"#2222bb":{"lch":[26.937850813592469,94.3371638934954149,265.87432021817807],"luv":[26.937850813592469,-6.78703884698804139,-94.0927021354881106],"rgb":[0.133333333333333331,0.133333333333333331,0.733333333333333282],"xyz":[0.102003606933460056,0.0507162274028517823,0.47456668543148095],"hpluv":[265.87432021817807,444.384803230596,26.937850813592469],"hsluv":[265.87432021817807,86.5550863782758,26.937850813592469]},"#2222cc":{"lch":[29.0136770200274086,104.153206308830732,265.874320218178],"luv":[29.0136770200274086,-7.49324898143496743,-103.883307629821275],"rgb":[0.133333333333333331,0.133333333333333331,0.8],"xyz":[0.121295982377181782,0.058433177580340584,0.576173196101751106],"hpluv":[265.874320218178,455.521834046362642,29.0136770200274086],"hsluv":[265.874320218178,88.7243024658832695,29.0136770200274086]},"#2222dd":{"lch":[31.1234509916598299,113.892375340460845,265.874320218178],"luv":[31.1234509916598299,-8.19392849973896809,-113.597238947227837],"rgb":[0.133333333333333331,0.133333333333333331,0.866666666666666696],"xyz":[0.142814307927815326,0.067040507800594118,0.689503044001757104],"hpluv":[265.874320218178,464.350835522916555,31.1234509916598299],"hsluv":[265.874320218178,90.4439719502614565,31.1234509916598299]},"#2222ee":{"lch":[33.2570959032629503,123.556928623667645,265.874320218178],"luv":[33.2570959032629503,-8.88923982631188103,-123.236747872639071],"rgb":[0.133333333333333331,0.133333333333333331,0.933333333333333348],"xyz":[0.166626488193990063,0.0765653799070641489,0.814913860070280327],"hpluv":[265.874320218178,471.435310205520636,33.2570959032629503],"hsluv":[265.874320218178,93.8546607467714296,33.2570959032629503]},"#2222ff":{"lch":[35.4068078244889,133.147814572056944,265.874320218177957],"luv":[35.4068078244889,-9.57925119428392335,-132.802780361977625],"rgb":[0.133333333333333331,0.133333333333333331,1],"xyz":[0.192797536603821457,0.0870337992709968589,0.952748048362062172],"hpluv":[265.874320218177957,477.184793215987838,35.4068078244889],"hsluv":[265.874320218177957,99.999999999999531,35.4068078244889]},"#77bb00":{"lch":[69.0844312744863629,87.8096536524333544,113.037133893102563],"luv":[69.0844312744863629,-34.3623439699837476,80.8069588058406225],"rgb":[0.466666666666666674,0.733333333333333282,0],"xyz":[0.253771247183507853,0.394617492952509585,0.0627978715952091093],"hpluv":[113.037133893102563,161.287757631366873,69.0844312744863629],"hsluv":[113.037133893102563,100.000000000002331,69.0844312744863629]},"#77bb11":{"lch":[69.1135050244688216,86.6316159473835654,113.342165034517933],"luv":[69.1135050244688216,-34.3252912529264051,79.5412550948043275],"rgb":[0.466666666666666674,0.733333333333333282,0.0666666666666666657],"xyz":[0.254782912683144958,0.395022159152364438,0.068125976559964746],"hpluv":[113.342165034517933,159.057013433643618,69.1135050244688216],"hsluv":[113.342165034517933,98.3127503818399,69.1135050244688216]},"#77bb22":{"lch":[69.1673475306790664,84.4748154027895595,113.924619678029501],"luv":[69.1673475306790664,-34.2574435126740582,77.2167209955934197],"rgb":[0.466666666666666674,0.733333333333333282,0.133333333333333331],"xyz":[0.256658270821622,0.395772302407755239,0.0780028627559439386],"hpluv":[113.924619678029501,154.976360107776344,69.1673475306790664],"hsluv":[113.924619678029501,95.2170715716625438,69.1673475306790664]},"#77bb33":{"lch":[69.2558504240405313,80.9996568929936132,114.934551128374181],"luv":[69.2558504240405313,-34.1480550559542593,73.4496749664571666],"rgb":[0.466666666666666674,0.733333333333333282,0.2],"xyz":[0.25974602155407972,0.397007402700738365,0.0942650166135550749],"hpluv":[114.934551128374181,148.410982175614635,69.2558504240405313],"hsluv":[114.934551128374181,90.2088459481891363,69.2558504240405313]},"#77bb44":{"lch":[69.3833048234310752,76.148419012573342,116.514961433737128],"luv":[69.3833048234310752,-33.9950520676451049,68.1389620777462426],"rgb":[0.466666666666666674,0.733333333333333282,0.266666666666666663],"xyz":[0.264204016798298635,0.398790600798425932,0.117743791566441713],"hpluv":[116.514961433737128,139.266042804817573,69.3833048234310752],"hsluv":[116.514961433737128,83.166634884803841,69.3833048234310752]},"#77bb55":{"lch":[69.5531781358710788,69.9670725739878918,118.886197685461212],"luv":[69.5531781358710788,-33.7990968432466516,61.2618339356121666],"rgb":[0.466666666666666674,0.733333333333333282,0.333333333333333315],"xyz":[0.270166430837660621,0.401175566414170792,0.149145838840415629],"hpluv":[118.886197685461212,127.648598426560824,69.5531781358710788],"hsluv":[118.886197685461212,74.0801666731923092,69.5531781358710788]},"#77bb66":{"lch":[69.7683097004737647,62.6192143930699672,122.411113057511386],"luv":[69.7683097004737647,-33.5633071802414804,52.8646424591145],"rgb":[0.466666666666666674,0.733333333333333282,0.4],"xyz":[0.277751426611168806,0.404209564723574111,0.189093483247559591],"hpluv":[122.411113057511386,113.8908252780179,69.7683097004737647],"hsluv":[122.411113057511386,63.0377921091212627,69.7683097004737647]},"#77bb77":{"lch":[70.0310134677453391,54.4237865803123384,127.71501294923867],"luv":[70.0310134677453391,-33.2928991557924476,43.0526585885427053],"rgb":[0.466666666666666674,0.733333333333333282,0.466666666666666674],"xyz":[0.287065439631370711,0.407935169931654906,0.2381472851539575],"hpluv":[127.71501294923867,98.6137944921033522,70.0310134677453391],"hsluv":[127.71501294923867,50.2119979074062357,70.0310134677453391]},"#77bb88":{"lch":[70.3431390634792422,45.948041729110173,135.896820739423845],"luv":[70.3431390634792422,-32.9947227526694,31.9776610966863863],"rgb":[0.466666666666666674,0.733333333333333282,0.533333333333333326],"xyz":[0.298205856733314512,0.412391336772432504,0.296820148557529695],"hpluv":[135.896820739423845,82.8866502392756246,70.3431390634792422],"hsluv":[135.896820739423845,51.9268484553617711,70.3431390634792422]},"#77bb99":{"lch":[70.7061124793432612,38.2197788608509725,148.75629343317604],"luv":[70.7061124793432612,-32.6767203094589,19.8238100775241328],"rgb":[0.466666666666666674,0.733333333333333282,0.6],"xyz":[0.311262820842387211,0.417614122416061662,0.365586826198647574],"hpluv":[148.75629343317604,68.5915373148405,70.7061124793432612],"hsluv":[148.75629343317604,53.7921873551213565,70.7061124793432612]},"#77bbaa":{"lch":[71.1209666334754615,33.0529501718716148,168.140026251468868],"luv":[71.1209666334754615,-32.3473621836046874,6.79306078486928744],"rgb":[0.466666666666666674,0.733333333333333282,0.66666666666666663],"xyz":[0.326320513807639767,0.423637199602162784,0.444890675815646175],"hpluv":[168.140026251468868,58.9728212370576585,71.1209666334754615],"hsluv":[168.140026251468868,55.7713219876506372,71.1209666334754615]},"#77bbbb":{"lch":[71.5883672123020744,32.7520226121926044,192.177050630060876],"luv":[71.5883672123020744,-32.0151171807858432,-6.90849166534396097],"rgb":[0.466666666666666674,0.733333333333333282,0.733333333333333282],"xyz":[0.343458105914976441,0.430492236445097542,0.535148660914287921],"hpluv":[192.177050630060876,58.0543804308889762,71.5883672123020744],"hsluv":[192.177050630060876,57.8271385543864938,71.5883672123020744]},"#77bbcc":{"lch":[72.1086367753872111,38.0603267014846267,213.636027248927832],"luv":[72.1086367753872111,-31.6880044447458609,-21.0821925551758049],"rgb":[0.466666666666666674,0.733333333333333282,0.8],"xyz":[0.36275048135869814,0.438209186622586344,0.636755171584558077],"hpluv":[213.636027248927832,66.97682452789428,72.1086367753872111],"hsluv":[213.636027248927832,59.9241419588218278,72.1086367753872111]},"#77bbdd":{"lch":[72.6817787487410101,47.4106862821029651,228.567760630430627],"luv":[72.6817787487410101,-31.3732552756954277,-35.5456330249727728],"rgb":[0.466666666666666674,0.733333333333333282,0.866666666666666696],"xyz":[0.384268806909331739,0.44681651684283985,0.750085019484564075],"hpluv":[228.567760630430627,82.7732525696479371,72.6817787487410101],"hsluv":[228.567760630430627,62.0300015657167449,72.6817787487410101]},"#77bbee":{"lch":[73.3075021126589803,58.9885691422652769,238.208313348868131],"luv":[73.3075021126589803,-31.0770938488335098,-50.1384635520735316],"rgb":[0.466666666666666674,0.733333333333333282,0.933333333333333348],"xyz":[0.408080987175506449,0.456341388949309867,0.875495835553087298],"hpluv":[238.208313348868131,102.107764199280055,73.3075021126589803],"hsluv":[238.208313348868131,76.3238953534044,73.3075021126589803]},"#77bbff":{"lch":[73.9852470697490219,71.6819577745528846,244.548916872628638],"luv":[73.9852470697490219,-30.8046295043453391,-64.7254036023940102],"rgb":[0.466666666666666674,0.733333333333333282,1],"xyz":[0.43425203558533787,0.466809808313242591,1.01333002384486903],"hpluv":[244.548916872628638,122.943070755930805,73.9852470697490219],"hsluv":[244.548916872628638,99.9999999999974,73.9852470697490219]},"#223300":{"lch":[18.8330192465532917,22.9063411551717806,108.204985820955727],"luv":[18.8330192465532917,-7.15634373768739707,21.7597612446731326],"rgb":[0.133333333333333331,0.2,0],"xyz":[0.0184344702910022862,0.0270769280882856428,0.00425513680873902],"hpluv":[108.204985820955727,154.338793470845559,18.8330192465532917],"hsluv":[108.204985820955727,100.000000000002331,18.8330192465532917]},"#223311":{"lch":[19.0056890338669575,18.4529510656336271,112.754551304246377],"luv":[19.0056890338669575,-7.13731020483261602,17.0167625026225657],"rgb":[0.133333333333333331,0.2,0.0666666666666666657],"xyz":[0.0194461357906394079,0.0274815942881404957,0.0095832417734946513],"hpluv":[112.754551304246377,123.203072156705915,19.0056890338669575],"hsluv":[112.754551304246377,76.6034511576994248,19.0056890338669575]},"#223322":{"lch":[19.3213416797184507,11.6344605438365232,127.71501294923759],"luv":[19.3213416797184507,-7.11719904028337247,9.20359440474635],"rgb":[0.133333333333333331,0.2,0.133333333333333331],"xyz":[0.0213214939291164265,0.028231737543531317,0.0194601279694738491],"hpluv":[127.71501294923759,76.4096652359405084,19.3213416797184507],"hsluv":[127.71501294923759,38.9061385447444366,19.3213416797184507]},"#223333":{"lch":[19.8290945906418372,7.27996715422488894,192.177050630060677],"luv":[19.8290945906418372,-7.11617124458192585,-1.53558737438758408],"rgb":[0.133333333333333331,0.2,0.2],"xyz":[0.0244092446615741671,0.0294668378365144298,0.0357222818270849923],"hpluv":[192.177050630060677,46.5871198449043149,19.8290945906418372],"hsluv":[192.177050630060677,46.4047641905018935,19.8290945906418372]},"#223344":{"lch":[20.5377244517829496,15.5714684077650816,242.621028364370432],"luv":[20.5377244517829496,-7.16091210703038339,-13.8272183091687548],"rgb":[0.133333333333333331,0.2,0.266666666666666663],"xyz":[0.0288672399057930476,0.031250035934202,0.0592010567799716309],"hpluv":[242.621028364370432,96.2091932027738181,20.5377244517829496],"hsluv":[242.621028364370432,54.472556800898019,20.5377244517829496]},"#223355":{"lch":[21.4445377167678828,27.4997828393463344,254.670418676715883],"luv":[21.4445377167678828,-7.27014529691604761,-26.5213695644274097],"rgb":[0.133333333333333331,0.2,0.333333333333333315],"xyz":[0.0348296539451550397,0.0336350015499468358,0.0906031040539455323],"hpluv":[254.670418676715883,162.72410902482622,21.4445377167678828],"hsluv":[254.670418676715883,62.1025047738862597,21.4445377167678828]},"#223366":{"lch":[22.538163137523668,39.6650934211933617,259.172698399253136],"luv":[22.538163137523668,-7.45106225263504562,-38.9589695374422647],"rgb":[0.133333333333333331,0.2,0.4],"xyz":[0.042414649718663211,0.0366689998593501473,0.130550748461089494],"hpluv":[259.172698399253136,223.320859110196579,22.538163137523668],"hsluv":[259.172698399253136,68.7622962285398103,22.538163137523668]},"#223377":{"lch":[23.8014699151847751,51.4679604591091,261.393782909523054],"luv":[23.8014699151847751,-7.70180102721624227,-50.8884389105972517],"rgb":[0.133333333333333331,0.2,0.466666666666666674],"xyz":[0.0517286627388651304,0.0403946050674309637,0.179604550367487403],"hpluv":[261.393782909523054,274.392670476726266,23.8014699151847751],"hsluv":[261.393782909523054,74.3066649079193837,23.8014699151847751]},"#223388":{"lch":[25.214303338898695,62.8010579136007649,262.667168962641028],"luv":[25.214303338898695,-8.01548446335257658,-62.2874376089207189],"rgb":[0.133333333333333331,0.2,0.533333333333333326],"xyz":[0.0628690798408089585,0.0448507719082085615,0.238277413771059571],"hpluv":[262.667168962641028,316.052560368409843,25.214303338898695],"hsluv":[262.667168962641028,78.8060188103052184,25.214303338898695]},"#223399":{"lch":[26.7557115473943199,73.7050787305795723,263.468936800572067],"luv":[26.7557115473943199,-8.38335327394953111,-73.2267575314180732],"rgb":[0.133333333333333331,0.2,0.6],"xyz":[0.0759260439498816719,0.0500735575518377191,0.307044091412177478],"hpluv":[263.468936800572067,349.558796760350788,26.7557115473943199],"hsluv":[263.468936800572067,82.4151943494814105,26.7557115473943199]},"#2233aa":{"lch":[28.4055164492709622,84.2578478733767184,264.007358934798901],"luv":[28.4055164492709622,-8.79658068794244485,-83.7974050699281],"rgb":[0.133333333333333331,0.2,0.66666666666666663],"xyz":[0.090983736915134214,0.0560966347379388205,0.386347941029176078],"hpluv":[264.007358934798901,376.397732872534505,28.4055164492709622],"hsluv":[264.007358934798901,85.301735461295209,28.4055164492709622]},"#2233bb":{"lch":[30.1452579578322855,94.5340435151979506,264.386468786418391],"luv":[30.1452579578322855,-9.24712484322681583,-94.0806891209195868],"rgb":[0.133333333333333331,0.2,0.733333333333333282],"xyz":[0.108121329022470861,0.0629516715808735861,0.476605926127817825],"hpluv":[264.386468786418391,397.931717533943186,30.1452579578322855],"hsluv":[264.386468786418391,87.6154589527103553,30.1452579578322855]},"#2233cc":{"lch":[31.9586404471462444,104.593501952517812,264.663323368501551],"luv":[31.9586404471462444,-9.72802821613955615,-104.140127317558722],"rgb":[0.133333333333333331,0.2,0.8],"xyz":[0.127413704466192601,0.0706686217583623877,0.578212436798088],"hpluv":[264.663323368501551,415.294074833826699,31.9586404471462444],"hsluv":[264.663323368501551,89.4797268300864,31.9586404471462444]},"#2233dd":{"lch":[33.8316358854510284,114.480561125644712,264.87147297862515],"luv":[33.8316358854510284,-10.233440845943651,-114.022259072932982],"rgb":[0.133333333333333331,0.2,0.866666666666666696],"xyz":[0.148932030016826145,0.0792759519786159217,0.691542284698094],"hpluv":[264.87147297862515,429.386195390892226,33.8316358854510284],"hsluv":[264.87147297862515,90.9921469937159,33.8316358854510284]},"#2233ee":{"lch":[35.7523793143002209,124.226868169083772,265.031742065807748],"luv":[35.7523793143002209,-10.7585230682670669,-123.760126682580946],"rgb":[0.133333333333333331,0.2,0.933333333333333348],"xyz":[0.172744210283000882,0.0888008240850859387,0.816953100766617202],"hpluv":[265.031742065807748,440.909964314972513,35.7523793143002209],"hsluv":[265.031742065807748,93.1343838757889557,35.7523793143002209]},"#2233ff":{"lch":[37.7109573358094536,133.854664271403067,265.157628752861342],"luv":[37.7109573358094536,-11.2993072928814158,-133.376897556927247],"rgb":[0.133333333333333331,0.2,1],"xyz":[0.198915258692832275,0.0992692434490186487,0.954787289058399],"hpluv":[265.157628752861342,450.407151983715153,37.7109573358094536],"hsluv":[265.157628752861342,99.9999999999994884,37.7109573358094536]},"#77cc00":{"lch":[74.2578384949046892,97.2071675743180776,115.806356387580706],"luv":[74.2578384949046892,-42.3172913249353897,87.5127435448238202],"rgb":[0.466666666666666674,0.8,0],"xyz":[0.291994990493124773,0.47106497957174448,0.0755391193650810505],"hpluv":[115.806356387580706,166.109821583261578,74.2578384949046892],"hsluv":[115.806356387580706,100.000000000002331,74.2578384949046892]},"#77cc11":{"lch":[74.283676291108776,96.1575231653334441,116.077287726142885],"luv":[74.283676291108776,-42.2692272766684525,86.3688698937585144],"rgb":[0.466666666666666674,0.8,0.0666666666666666657],"xyz":[0.293006655992761877,0.471469645771599333,0.0808672243298366872],"hpluv":[116.077287726142885,164.2590118749402,74.283676291108776],"hsluv":[116.077287726142885,98.5840266713216522,74.283676291108776]},"#77cc22":{"lch":[74.3315335987815331,94.2328042547367914,116.591431825394338],"luv":[74.3315335987815331,-42.1809937248656581,84.2649699821603519],"rgb":[0.466666666666666674,0.8,0.133333333333333331],"xyz":[0.294882014131238934,0.472219789026990133,0.0907441105258158798],"hpluv":[116.591431825394338,160.867513369155,74.3315335987815331],"hsluv":[116.591431825394338,95.9817736999432611,74.3315335987815331]},"#77cc33":{"lch":[74.410219680569412,91.1231466477069176,117.473161926834081],"luv":[74.410219680569412,-42.0381214847294515,80.8469182901527148],"rgb":[0.466666666666666674,0.8,0.2],"xyz":[0.29796976486369664,0.47345488931997326,0.10700626438342703],"hpluv":[117.473161926834081,155.394430123968846,74.410219680569412],"hsluv":[117.473161926834081,91.7599970515073551,74.410219680569412]},"#77cc44":{"lch":[74.5235830687713445,86.7634188586395,118.828926444460791],"luv":[74.5235830687713445,-41.8369758074171045,76.0102513305232748],"rgb":[0.466666666666666674,0.8,0.266666666666666663],"xyz":[0.302427760107915555,0.475238087417660826,0.130485039336313668],"hpluv":[118.828926444460791,147.734611978654129,74.5235830687713445],"hsluv":[118.828926444460791,85.7987877580492153,74.5235830687713445]},"#77cc55":{"lch":[74.6747602628398,81.1723246922669688,120.810965560633605],"luv":[74.6747602628398,-41.5770530159641396,69.7158156981220571],"rgb":[0.466666666666666674,0.8,0.333333333333333315],"xyz":[0.30839017414727754,0.477623053033405687,0.161887086610287556],"hpluv":[120.810965560633605,137.934680546119694,74.6747602628398],"hsluv":[120.810965560633605,78.0638956664307671,74.6747602628398]},"#77cc66":{"lch":[74.866352492363319,74.4599116278811692,123.651008617564372],"luv":[74.866352492363319,-41.2606832822808798,61.982533464768764],"rgb":[0.466666666666666674,0.8,0.4],"xyz":[0.315975169920785726,0.480657051342809,0.201834731017431546],"hpluv":[123.651008617564372,126.204595224585091,74.866352492363319],"hsluv":[123.651008617564372,68.5978490432360388,74.866352492363319]},"#77cc77":{"lch":[75.1005189251371519,66.8471540376336719,127.715012949239053],"luv":[75.1005189251371519,-40.8926996459993219,52.8803282761483189],"rgb":[0.466666666666666674,0.8,0.466666666666666674],"xyz":[0.325289182940987631,0.484382656550889801,0.250888532923829455],"hpluv":[127.715012949239053,112.948199149412858,75.1005189251371519],"hsluv":[127.715012949239053,57.5107647824030153,75.1005189251371519]},"#77cc88":{"lch":[75.3790318815133,58.7087525381891311,133.590980823477452],"luv":[75.3790318815133,-40.4800101313413947,42.5215992685696946],"rgb":[0.466666666666666674,0.8,0.533333333333333326],"xyz":[0.336429600042931432,0.488838823391667399,0.309561396327401595],"hpluv":[133.590980823477452,98.8306436104363542,75.3790318815133],"hsluv":[133.590980823477452,58.7660231359317748,75.3790318815133]},"#77cc99":{"lch":[75.7033128937519848,50.6622606125816759,142.200076129410462],"luv":[75.7033128937519848,-40.0310804193600305,31.0512036938324556],"rgb":[0.466666666666666674,0.8,0.6],"xyz":[0.349486564152004187,0.494061609035296556,0.378328073968519529],"hpluv":[142.200076129410462,84.9198084445103802,75.7033128937519848],"hsluv":[142.200076129410462,60.1459848784672815,75.7033128937519848]},"#77ccaa":{"lch":[76.0744587192654,43.7254195479873289,154.773618890386814],"luv":[76.0744587192654,-39.5553661600219684,18.6355928962322395],"rgb":[0.466666666666666674,0.8,0.66666666666666663],"xyz":[0.364544257117256687,0.500084686221397678,0.45763192358551813],"hpluv":[154.773618890386814,73.067319501552376,76.0744587192654],"hsluv":[154.773618890386814,61.6269813218812459,76.0744587192654]},"#77ccbb":{"lch":[76.4932621718819235,39.4413790272598561,172.054536865796422],"luv":[76.4932621718819235,-39.0627454070693716,5.45199970968949721],"rgb":[0.466666666666666674,0.8,0.733333333333333282],"xyz":[0.381681849224593361,0.506939723064332437,0.547889908684159876],"hpluv":[172.054536865796422,67.3385729198675733,76.4932621718819235],"hsluv":[172.054536865796422,63.1840553036415713,76.4932621718819235]},"#77cccc":{"lch":[76.9602305249106,39.4506200876857847,192.177050630060961],"luv":[76.9602305249106,-38.5629992967744357,-8.32144882456723778],"rgb":[0.466666666666666674,0.8,0.8],"xyz":[0.400974224668315116,0.514656673241821183,0.64949641935443],"hpluv":[192.177050630060961,69.0111626451701312,76.9602305249106],"hsluv":[192.177050630060961,64.792316690459316,76.9602305249106]},"#77ccdd":{"lch":[77.4756030772436475,44.2250390945155942,210.602473757279853],"luv":[77.4756030772436475,-38.0653777824919359,-22.5140200139291444],"rgb":[0.466666666666666674,0.8,0.866666666666666696],"xyz":[0.422492550218948604,0.5232640034620748,0.762826267254436],"hpluv":[210.602473757279853,79.5030322309331439,77.4756030772436475],"hsluv":[210.602473757279853,66.4280770246703156,77.4756030772436475]},"#77ccee":{"lch":[78.0393687787239116,52.7144148097026246,224.531483635923479],"luv":[78.0393687787239116,-37.5782716813780482,-36.9686762837126182],"rgb":[0.466666666666666674,0.8,0.933333333333333348],"xyz":[0.446304730485123369,0.532788875568544817,0.888237083322959253],"hpluv":[224.531483635923479,97.6926138158747648,78.0393687787239116],"hsluv":[224.531483635923479,71.3781641956177282,78.0393687787239116]},"#77ccff":{"lch":[78.6512843692400736,63.5145451324089052,234.249283216901347],"luv":[78.6512843692400736,-37.1089966152940249,-51.5462880679385549],"rgb":[0.466666666666666674,0.8,1],"xyz":[0.472475778894954734,0.54325729493247743,1.0260712716147411],"hpluv":[234.249283216901347,121.749546403725816,78.6512843692400736],"hsluv":[234.249283216901347,99.9999999999968168,78.6512843692400736]},"#224400":{"lch":[25.1809799681870601,33.4179584834008523,116.999863609689683],"luv":[25.1809799681870601,-15.1713647924420147,29.7756551486773162],"rgb":[0.133333333333333331,0.266666666666666663,0],"xyz":[0.0272670407739683193,0.0447420690542179589,0.00719932696972761486],"hpluv":[116.999863609689683,168.401755360818214,25.1809799681870601],"hsluv":[116.999863609689683,100.000000000002217,25.1809799681870601]},"#224411":{"lch":[25.304760275593587,29.8643723317269938,120.153298663054528],"luv":[25.304760275593587,-15.0013316265206242,25.8232605261065515],"rgb":[0.133333333333333331,0.266666666666666663,0.0666666666666666657],"xyz":[0.0282787062736054411,0.0451467352540728117,0.0125274319344832463],"hpluv":[120.153298663054528,149.758158325470362,25.304760275593587],"hsluv":[120.153298663054528,85.5029145399767287,25.304760275593587]},"#224422":{"lch":[25.5322735505540379,24.0578500603585184,127.715012949239281],"luv":[25.5322735505540379,-14.7170130248608242,19.0312815425753499],"rgb":[0.133333333333333331,0.266666666666666663,0.133333333333333331],"xyz":[0.0301540644120824597,0.0458968785094636331,0.0224043181304624424],"hpluv":[127.715012949239281,119.565711231297882,25.5322735505540379],"hsluv":[127.715012949239281,60.8802579098014363,25.5322735505540379]},"#224433":{"lch":[25.9015299317797343,16.981905402382786,147.498859327993841],"luv":[25.9015299317797343,-14.3222120947549634,9.12465625699578098],"rgb":[0.133333333333333331,0.266666666666666663,0.2],"xyz":[0.0332418151445402,0.0471319788024467459,0.0386664719880735891],"hpluv":[147.498859327993841,83.1955939146196357,25.9015299317797343],"hsluv":[147.498859327993841,64.09678513773909,25.9015299317797343]},"#224444":{"lch":[26.423438440277998,14.1959776348833024,192.177050630061],"luv":[26.423438440277998,-13.8765746732054378,-2.99440417262938974],"rgb":[0.133333333333333331,0.266666666666666663,0.266666666666666663],"xyz":[0.0376998103887590807,0.048915176900134319,0.0621452469409602276],"hpluv":[192.177050630061,68.1734546180548762,26.423438440277998],"hsluv":[192.177050630061,67.9066037165366367,26.423438440277998]},"#224455":{"lch":[27.1020089847707979,21.1158760403194243,230.453768193421723],"luv":[27.1020089847707979,-13.4444916811297333,-16.2826860310628518],"rgb":[0.133333333333333331,0.266666666666666663,0.333333333333333315],"xyz":[0.0436622244281210728,0.0513001425158791519,0.0935472942149341291],"hpluv":[230.453768193421723,98.865995891455,27.1020089847707979],"hsluv":[230.453768193421723,71.8953118433392717,27.1020089847707979]},"#224466":{"lch":[27.935501760142138,32.6185654266362306,246.366656325875056],"luv":[27.935501760142138,-13.0762038460066634,-29.8828329224226863],"rgb":[0.133333333333333331,0.266666666666666663,0.4],"xyz":[0.0512472202016292511,0.0543341408252824634,0.133494938622078091],"hpluv":[246.366656325875056,148.165710166053657,27.935501760142138],"hsluv":[246.366656325875056,75.7322080769735351,27.935501760142138]},"#224477":{"lch":[28.9175817086007072,45.1016041758277453,253.511262930840303],"luv":[28.9175817086007072,-12.8010466495854338,-43.2468253621948548],"rgb":[0.133333333333333331,0.266666666666666663,0.466666666666666674],"xyz":[0.0605612332218311636,0.0580597460333632798,0.182548740528476],"hpluv":[253.511262930840303,197.910731630760921,28.9175817086007072],"hsluv":[253.511262930840303,79.2164102145581381,28.9175817086007072]},"#224488":{"lch":[30.0385370730522183,57.5093846577101502,257.31357801273],"luv":[30.0385370730522183,-12.6299243743950456,-56.1053859625395432],"rgb":[0.133333333333333331,0.266666666666666663,0.533333333333333326],"xyz":[0.0717016503237749847,0.0625159128741408776,0.241221603932048168],"hpluv":[257.31357801273,242.94013543841632,30.0385370730522183],"hsluv":[257.31357801273,82.2624245727818106,30.0385370730522183]},"#224499":{"lch":[31.2864747985506213,69.5224780150470139,259.59064804278853],"luv":[31.2864747985506213,-12.5612993437317204,-68.3782765807230675],"rgb":[0.133333333333333331,0.266666666666666663,0.6],"xyz":[0.084758614432847712,0.0677386985177700351,0.309988281573166102],"hpluv":[259.59064804278853,281.973268281811727,31.2864747985506213],"hsluv":[259.59064804278853,84.8626039888072796,31.2864747985506213]},"#2244aa":{"lch":[32.6483868166792277,81.0719578322086676,261.068394107427196],"luv":[32.6483868166792277,-12.5868550230291323,-80.0889095154670372],"rgb":[0.133333333333333331,0.266666666666666663,0.66666666666666663],"xyz":[0.0998163073981002402,0.0737617757038711297,0.389292131190164703],"hpluv":[261.068394107427196,315.099896322028769,32.6483868166792277],"hsluv":[261.068394107427196,87.0515845849570695,32.6483868166792277]},"#2244bb":{"lch":[34.1110146972578292,92.183713113584,262.084105297610279],"luv":[34.1110146972578292,-12.6954785449376448,-91.3053217940914266],"rgb":[0.133333333333333331,0.266666666666666663,0.733333333333333282],"xyz":[0.116953899505436887,0.0806168125468058883,0.479550116288806449],"hpluv":[262.084105297610279,342.924793116778346,34.1110146972578292],"hsluv":[262.084105297610279,88.8814901896574128,34.1110146972578292]},"#2244cc":{"lch":[35.6614866183058439,102.915147630289923,262.812952152813068],"luv":[35.6614866183058439,-12.875606592693158,-102.106544191029045],"rgb":[0.133333333333333331,0.266666666666666663,0.8],"xyz":[0.136246274949158641,0.0883337627242946899,0.58115662695907655],"hpluv":[262.812952152813068,366.200711918304478,35.6614866183058439],"hsluv":[262.812952152813068,90.4075680541016453,35.6614866183058439]},"#2244dd":{"lch":[37.2877389569632456,113.32806156453313,263.353781276912969],"luv":[37.2877389569632456,-13.1164125913708496,-112.566465959927598],"rgb":[0.133333333333333331,0.266666666666666663,0.866666666666666696],"xyz":[0.157764600499792185,0.0969410929445482239,0.694486474859082548],"hpluv":[263.353781276912969,385.665452845521429,37.2877389569632456],"hsluv":[263.353781276912969,91.6811227311716408,37.2877389569632456]},"#2244ee":{"lch":[38.9787575249373575,123.477944203962494,263.76603971616106],"luv":[38.9787575249373575,-13.4082956886680194,-122.74779147325674],"rgb":[0.133333333333333331,0.266666666666666663,0.933333333333333348],"xyz":[0.181576780765966922,0.106465965051018269,0.819897290927605771],"hpluv":[263.76603971616106,401.976556214066079,38.9787575249373575],"hsluv":[263.76603971616106,92.7467647141115208,38.9787575249373575]},"#2244ff":{"lch":[40.7246816385265333,133.410810959437413,264.087324287658078],"luv":[40.7246816385265333,-13.7429938745542355,-132.701072340123574],"rgb":[0.133333333333333331,0.266666666666666663,1],"xyz":[0.207747829175798315,0.116934384414950965,0.957731479219387616],"hpluv":[264.087324287658078,415.692943868353552,40.7246816385265333],"hsluv":[264.087324287658078,99.9999999999994174,40.7246816385265333]},"#77dd00":{"lch":[79.4046595803128525,106.500737968556749,117.886764510297155],"luv":[79.4046595803128525,-49.8131268255250319,94.1332012826045457],"rgb":[0.466666666666666674,0.866666666666666696,0],"xyz":[0.3346289810403,0.556332960666096,0.089750449547472369],"hpluv":[117.886764510297155,213.048114553231642,79.4046595803128525],"hsluv":[117.886764510297155,100.000000000002288,79.4046595803128525]},"#77dd11":{"lch":[79.427785829561941,105.558219679332908,118.124490997004173],"luv":[79.427785829561941,-49.7589732732473422,93.0944806133132801],"rgb":[0.466666666666666674,0.866666666666666696,0.0666666666666666657],"xyz":[0.335640646539937082,0.556737626865951,0.095078554512228],"hpluv":[118.124490997004173,211.44368721760469,79.427785829561941],"hsluv":[118.124490997004173,98.7993418471683498,79.427785829561941]},"#77dd22":{"lch":[79.4706261070012658,103.827566318990023,118.57359388187615],"luv":[79.4706261070012658,-49.6593926395600249,91.1817319993116],"rgb":[0.466666666666666674,0.866666666666666696,0.133333333333333331],"xyz":[0.337516004678414139,0.557487770121341764,0.104955440708207198],"hpluv":[118.57359388187615,208.49071074424171,79.4706261070012658],"hsluv":[118.57359388187615,96.5898934808467,79.4706261070012658]},"#77dd33":{"lch":[79.5410783702752582,101.02472031386101,119.337697767432388],"luv":[79.5410783702752582,-49.4976803830995067,88.0680063995226874],"rgb":[0.466666666666666674,0.866666666666666696,0.2],"xyz":[0.340603755410871845,0.55872287041432489,0.121217594565818348],"hpluv":[119.337697767432388,203.689012727245881,79.5410783702752582],"hsluv":[119.337697767432388,92.9973786049096276,79.5410783702752582]},"#77dd44":{"lch":[79.6426121547728485,97.0801788178617073,120.498016398630853],"luv":[79.6426121547728485,-49.2690191092090188,83.6488187323909642],"rgb":[0.466666666666666674,0.866666666666666696,0.266666666666666663],"xyz":[0.34506175065509076,0.560506068512012456,0.144696369518705],"hpluv":[120.498016398630853,196.890303230407255,79.6426121547728485],"hsluv":[120.498016398630853,87.9077551580221126,79.6426121547728485]},"#77dd55":{"lch":[79.7780740091910729,91.9929116678805912,122.163902680224],"luv":[79.7780740091910729,-48.9717875622404577,77.8746417012192325],"rgb":[0.466666666666666674,0.866666666666666696,0.333333333333333315],"xyz":[0.351024164694452745,0.562891034127757206,0.176098416792678902],"hpluv":[122.163902680224,188.049297266091,79.7780740091910729],"hsluv":[122.163902680224,81.2740392578912889,79.7780740091910729]},"#77dd66":{"lch":[79.9498479155991788,85.8342168020908645,124.492086237753256],"luv":[79.9498479155991788,-48.6072648253697466,70.7449403153661],"rgb":[0.466666666666666674,0.866666666666666696,0.4],"xyz":[0.35860916046796093,0.565925032437160525,0.216046061199822836],"hpluv":[124.492086237753256,177.233761467474238,79.9498479155991788],"hsluv":[124.492086237753256,73.1095665987697174,79.9498479155991788]},"#77dd77":{"lch":[80.1599403321921,78.7586110849663896,127.715012949239352],"luv":[80.1599403321921,-48.1793469594900188,62.3030444407821093],"rgb":[0.466666666666666674,0.866666666666666696,0.466666666666666674],"xyz":[0.367923173488162836,0.569650637645241376,0.265099863106220746],"hpluv":[127.715012949239352,164.652954432832814,80.1599403321921],"hsluv":[127.715012949239352,63.4818861677236654,80.1599403321921]},"#77dd88":{"lch":[80.4100305893493754,71.0258644695610855,132.183293417803185],"luv":[80.4100305893493754,-47.69419139775556,52.6301959958607597],"rgb":[0.466666666666666674,0.866666666666666696,0.533333333333333326],"xyz":[0.379063590590106636,0.574106804486018918,0.323772726509792941],"hpluv":[132.183293417803185,150.716476916292407,80.4100305893493754],"hsluv":[132.183293417803185,64.4129914780986184,80.4100305893493754]},"#77dd99":{"lch":[80.7015034668449829,63.0436351039456042,138.421724913218469],"luv":[80.7015034668449829,-47.1597772740100254,41.838443261971527],"rgb":[0.466666666666666674,0.866666666666666696,0.6],"xyz":[0.392120554699179391,0.579329590129648131,0.39253940415091082],"hpluv":[138.421724913218469,136.149613262507529,80.7015034668449829],"hsluv":[138.421724913218469,65.4455974050235909,80.7015034668449829]},"#77ddaa":{"lch":[81.0354720941451916,55.4433276111880673,147.164903113494319],"luv":[81.0354720941451916,-46.5854032558204594,30.0626475895607292],"rgb":[0.466666666666666674,0.866666666666666696,0.66666666666666663],"xyz":[0.407178247664431892,0.585352667315749198,0.471843253767909421],"hpluv":[147.164903113494319,122.204418643173668,81.0354720941451916],"hsluv":[147.164903113494319,66.5644374268903505,81.0354720941451916]},"#77ddbb":{"lch":[81.4127955433910415,49.1817473647014651,159.215751775487576],"luv":[81.4127955433910415,-45.9811599326092804,17.452140415923953],"rgb":[0.466666666666666674,0.866666666666666696,0.733333333333333282],"xyz":[0.424315839771768566,0.592207704158684,0.562101238866551167],"hpluv":[159.215751775487576,110.971854928909636,81.4127955433910415],"hsluv":[159.215751775487576,67.7527973989238461,81.4127955433910415]},"#77ddcc":{"lch":[81.8340936239464298,45.5480287935550479,174.756411210455894],"luv":[81.8340936239464298,-45.3574175840417197,4.16264304083146897],"rgb":[0.466666666666666674,0.866666666666666696,0.8],"xyz":[0.44360821521549032,0.599924654336172813,0.663707749536821323],"hpluv":[174.756411210455894,105.54602277980743,81.8340936239464298],"hsluv":[174.756411210455894,68.9933910523739371,81.8340936239464298]},"#77dddd":{"lch":[82.299760373596115,45.7538051586653296,192.177050630060847],"luv":[82.299760373596115,-44.7243656053231931,-9.65100034703627],"rgb":[0.466666666666666674,0.866666666666666696,0.866666666666666696],"xyz":[0.465126540766123808,0.60853198455642632,0.777037597436827321],"hpluv":[192.177050630060847,109.256318691998032,82.299760373596115],"hsluv":[192.177050630060847,70.2691471280789557,82.299760373596115]},"#77ddee":{"lch":[82.8099771424448221,50.1244355238275574,208.400620203695752],"luv":[82.8099771424448221,-44.0916301090396274,-23.8408722766172367],"rgb":[0.466666666666666674,0.866666666666666696,0.933333333333333348],"xyz":[0.488938721032298573,0.618056856662896337,0.902448413505350544],"hpluv":[208.400620203695752,123.794681382199428,82.8099771424448221],"hsluv":[208.400620203695752,71.5638635335918,82.8099771424448221]},"#77ddff":{"lch":[83.364725787715372,57.9146258331384161,221.361714592462022],"luv":[83.364725787715372,-43.4679842885535521,-38.2705921992655504],"rgb":[0.466666666666666674,0.866666666666666696,1],"xyz":[0.515109769442129939,0.628525276026829061,1.0402826017971325],"hpluv":[221.361714592462022,148.517135389694232,83.364725787715372],"hsluv":[221.361714592462022,99.9999999999953531,83.364725787715372]},"#225500":{"lch":[31.4325909084541877,43.9203091385023825,121.065637009975248],"luv":[31.4325909084541877,-22.6637443991712324,37.6211143459447541],"rgb":[0.133333333333333331,0.333333333333333315,0],"xyz":[0.0390802974883142848,0.0683685824829102229,0.0111370792078428239],"hpluv":[121.065637009975248,177.306450001223254,31.4325909084541877],"hsluv":[121.065637009975248,100.000000000002373,31.4325909084541877]},"#225511":{"lch":[31.5259896590935043,41.027632513747335,123.178290947815412],"luv":[31.5259896590935043,-22.452213479362328,34.3389682366875704],"rgb":[0.133333333333333331,0.333333333333333315,0.0666666666666666657],"xyz":[0.04009196298795141,0.0687732486827650757,0.0164651841725984571],"hpluv":[123.178290947815412,165.138012967735222,31.5259896590935043],"hsluv":[123.178290947815412,90.3912533003173877,31.5259896590935043]},"#225522":{"lch":[31.6981615382414716,36.1013694166085486,127.715012949239792],"luv":[31.6981615382414716,-22.0844473877159331,28.5584673491706482],"rgb":[0.133333333333333331,0.333333333333333315,0.133333333333333331],"xyz":[0.0419673211264284252,0.0695233919381558901,0.0263420703685776497],"hpluv":[127.715012949239792,144.520324593974209,31.6981615382414716],"hsluv":[127.715012949239792,73.5866039174798345,31.6981615382414716]},"#225533":{"lch":[31.9789617713411829,29.2625646618711706,137.400330584271074],"luv":[31.9789617713411829,-21.5402028917408828,19.8070025489176231],"rgb":[0.133333333333333331,0.333333333333333315,0.2],"xyz":[0.0450550718588861657,0.070758492231139,0.0426042242261888],"hpluv":[137.400330584271074,116.114739730974975,31.9789617713411829],"hsluv":[137.400330584271074,75.0932106461487,31.9789617713411829]},"#225544":{"lch":[32.3786649626227785,22.5648709259736577,157.632626155133437],"luv":[32.3786649626227785,-20.8671549807805796,8.58692278490586247],"rgb":[0.133333333333333331,0.333333333333333315,0.266666666666666663],"xyz":[0.0495130671031050462,0.072541690328826583,0.0660829991790754384],"hpluv":[157.632626155133437,88.4327727432137465,32.3786649626227785],"hsluv":[157.632626155133437,76.9882679005547459,32.3786649626227785]},"#225555":{"lch":[32.9031430542149863,20.5945867127178737,192.177050630061132],"luv":[32.9031430542149863,-20.1312179923833199,-4.34408379417313384],"rgb":[0.133333333333333331,0.333333333333333315,0.333333333333333315],"xyz":[0.0554754811424670383,0.0749266559445714159,0.0974850464530493399],"hpluv":[192.177050630061132,79.4245973683706268,32.9031430542149863],"hsluv":[192.177050630061132,79.1137061933639245,32.9031430542149863]},"#225566":{"lch":[33.5545056011551,26.6006796322727546,223.177731373198952],"luv":[33.5545056011551,-19.398136743372234,-18.2018803364993715],"rgb":[0.133333333333333331,0.333333333333333315,0.4],"xyz":[0.0630604769159752165,0.0779606542539747344,0.137432690860193302],"hpluv":[223.177731373198952,100.596116474312993,33.5545056011551],"hsluv":[223.177731373198952,81.3097794801720113,33.5545056011551]},"#225577":{"lch":[34.3316296590174,37.3644949384619807,239.932022094073261],"luv":[34.3316296590174,-18.7206257951117045,-32.3364137134245127],"rgb":[0.133333333333333331,0.333333333333333315,0.466666666666666674],"xyz":[0.0723744899361771221,0.0816862594620555438,0.186486492766591211],"hpluv":[239.932022094073261,138.103289638438355,34.3316296590174],"hsluv":[239.932022094073261,83.4469752602442298,34.3316296590174]},"#225588":{"lch":[35.230707776085,49.7211482448765594,248.610781811292384],"luv":[35.230707776085,-18.1333810040152876,-46.2965773697387775],"rgb":[0.133333333333333331,0.333333333333333315,0.533333333333333326],"xyz":[0.0835149070381209502,0.0861424263028331416,0.245159356170163378],"hpluv":[248.610781811292384,179.084957393431893,35.230707776085],"hsluv":[248.610781811292384,85.4385601139613158,35.230707776085]},"#225599":{"lch":[36.2458273864096512,62.3718238545276336,253.557833057747018],"luv":[36.2458273864096512,-17.6541819100499602,-59.8211858126123],"rgb":[0.133333333333333331,0.333333333333333315,0.6],"xyz":[0.0965718711471936775,0.0913652119464623,0.313926033811281313],"hpluv":[253.557833057747018,218.35832406203005,36.2458273864096512],"hsluv":[253.557833057747018,87.2381583586425791,36.2458273864096512]},"#2255aa":{"lch":[37.3695533294905928,74.8190204360075,256.640324292617947],"luv":[37.3695533294905928,-17.2879233745104131,-72.7943234352841841],"rgb":[0.133333333333333331,0.333333333333333315,0.66666666666666663],"xyz":[0.111629564112446206,0.0973882891325634,0.393229883428279914],"hpluv":[256.640324292617947,254.058329165629146,37.3695533294905928],"hsluv":[256.640324292617947,88.8301308270208807,37.3695533294905928]},"#2255bb":{"lch":[38.5934754222231291,86.8805221786483,258.695157685582501],"luv":[38.5934754222231291,-17.0311035792378,-85.1948745225196831],"rgb":[0.133333333333333331,0.333333333333333315,0.733333333333333282],"xyz":[0.128767156219782852,0.104243325975498152,0.48348786852692166],"hpluv":[258.695157685582501,285.658965815982469,38.5934754222231291],"hsluv":[258.695157685582501,90.2188193496234874,38.5934754222231291]},"#2255cc":{"lch":[39.9086891196534097,98.5112685419302,260.136263381096],"luv":[39.9086891196534097,-16.8755295039118458,-97.0550695930042],"rgb":[0.133333333333333331,0.333333333333333315,0.8],"xyz":[0.148059531663504607,0.111960276152986954,0.585094379197191761],"hpluv":[260.136263381096,313.22597901471056,39.9086891196534097],"hsluv":[260.136263381096,91.4196885710314433,39.9086891196534097]},"#2255dd":{"lch":[41.3061900239028503,109.727240919989157,261.187210775965298],"luv":[41.3061900239028503,-16.8109176424286737,-108.431823962952976],"rgb":[0.133333333333333331,0.333333333333333315,0.866666666666666696],"xyz":[0.16957785721413815,0.120567606373240488,0.698424227097197758],"hpluv":[261.187210775965298,337.084394380702577,41.3061900239028503],"hsluv":[261.187210775965298,92.4531471705562353,41.3061900239028503]},"#2255ee":{"lch":[42.7771763125841602,120.569176865205208,261.977669343247612],"luv":[42.7771763125841602,-16.8265186863462972,-119.389256965822398],"rgb":[0.133333333333333331,0.333333333333333315,0.933333333333333348],"xyz":[0.193390037480312887,0.130092478479710533,0.823835043165721],"hpluv":[261.977669343247612,357.654347674158601,42.7771763125841602],"hsluv":[261.977669343247612,93.3407272081067,42.7771763125841602]},"#2255ff":{"lch":[44.3132637322964129,131.084767922007643,262.587267096581058],"luv":[44.3132637322964129,-16.9120289519133,-129.989229007238322],"rgb":[0.133333333333333331,0.333333333333333315,1],"xyz":[0.219561085890144281,0.140560897843643229,0.961669231457502827],"hpluv":[262.587267096581058,375.368494421854962,44.3132637322964129],"hsluv":[262.587267096581058,99.9999999999993463,44.3132637322964129]},"#77ee00":{"lch":[84.5193058633960703,115.647601499010833,119.483871035599748],"luv":[84.5193058633960703,-56.9192667804191,100.670575649757225],"rgb":[0.466666666666666674,0.933333333333333348,0],"xyz":[0.381807757380814794,0.650690513347127,0.105476708327643554],"hpluv":[119.483871035599748,321.869538605652735,84.5193058633960703],"hsluv":[119.483871035599748,100.000000000002359,84.5193058633960703]},"#77ee11":{"lch":[84.5401392884337355,114.795422022346642,119.691827475064969],"luv":[84.5401392884337355,-56.862163274863434,99.7230329712822083],"rgb":[0.466666666666666674,0.933333333333333348,0.0666666666666666657],"xyz":[0.382819422880451898,0.651095179546981928,0.11080481329239919],"hpluv":[119.691827475064969,319.985367790437692,84.5401392884337355],"hsluv":[119.691827475064969,98.9722780394741477,84.5401392884337355]},"#77ee22":{"lch":[84.5787360835130499,113.228793274865538,120.083373039169615],"luv":[84.5787360835130499,-56.7570256545281637,97.9765260934141651],"rgb":[0.466666666666666674,0.933333333333333348,0.133333333333333331],"xyz":[0.384694781018928955,0.651845322802372729,0.120681699488378383],"hpluv":[120.083373039169615,316.512949618186383,84.5787360835130499],"hsluv":[120.083373039169615,97.0790709390529,84.5787360835130499]},"#77ee33":{"lch":[84.6422206992536275,110.686316006337648,120.745647293955486],"luv":[84.6422206992536275,-56.585921413754825,95.1288286946244313],"rgb":[0.466666666666666674,0.933333333333333348,0.2],"xyz":[0.387782531751386661,0.653080423095355855,0.136943853345989519],"hpluv":[120.745647293955486,310.853610471304819,84.6422206992536275],"hsluv":[120.745647293955486,93.9952250088888093,84.6422206992536275]},"#77ee44":{"lch":[84.7337366993681513,107.096480548503891,121.742158736883837],"luv":[84.7337366993681513,-56.3431952848259598,91.0774422728923412],"rgb":[0.466666666666666674,0.933333333333333348,0.266666666666666663],"xyz":[0.392240526995605576,0.654863621193043421,0.160422628298876158],"hpluv":[121.742158736883837,302.811644310583176,84.7337366993681513],"hsluv":[121.742158736883837,89.6144795711786628,84.7337366993681513]},"#77ee55":{"lch":[84.8558768548969766,102.444399932600575,123.154228511336783],"luv":[84.8558768548969766,-56.0262879596216621,85.7666026785261266],"rgb":[0.466666666666666674,0.933333333333333348,0.333333333333333315],"xyz":[0.398202941034967561,0.657248586808788171,0.191824675572850073],"hpluv":[123.154228511336783,292.298881153811294,84.8558768548969766],"hsluv":[123.154228511336783,83.883891015311761,84.8558768548969766]},"#77ee66":{"lch":[85.0108293450033159,96.7735040486586,125.092887734820366],"luv":[85.0108293450033159,-55.6354444449268186,79.1821217654036],"rgb":[0.466666666666666674,0.933333333333333348,0.4],"xyz":[0.405787936808475747,0.66028258511819149,0.231772319979994035],"hpluv":[125.092887734820366,279.341716913636219,85.0108293450033159],"hsluv":[125.092887734820366,76.7984153057879411,85.0108293450033159]},"#77ee77":{"lch":[85.2004556710930814,90.1918871908529667,127.71501294923965],"luv":[85.2004556710930814,-55.1734745704367882,71.3474892261312306],"rgb":[0.466666666666666674,0.933333333333333348,0.466666666666666674],"xyz":[0.415101949828677652,0.664008190326272341,0.280826121886391944],"hpluv":[127.71501294923965,264.105357222364148,85.2004556710930814],"hsluv":[127.71501294923965,68.3966317942099522,85.2004556710930814]},"#77ee88":{"lch":[85.4263369442757323,82.8846309918514521,131.246169999264879],"luv":[85.4263369442757323,-54.6454688778224167,62.3196179848555],"rgb":[0.466666666666666674,0.933333333333333348,0.533333333333333326],"xyz":[0.426242366930621452,0.668464357167049883,0.339498985289964139],"hpluv":[131.246169999264879,246.94310788397641,85.4263369442757323],"hsluv":[131.246169999264879,69.0963798671032379,85.4263369442757323]},"#77ee99":{"lch":[85.6898036798217,75.1362836057233,136.010896750223054],"luv":[85.6898036798217,-54.0584447513185395,52.1837682153016473],"rgb":[0.466666666666666674,0.933333333333333348,0.6],"xyz":[0.439299331039694207,0.673687142810679096,0.408265662931082],"hpluv":[136.010896750223054,228.489214549916312,85.6898036798217],"hsluv":[136.010896750223054,69.8780652000076827,85.6898036798217]},"#77eeaa":{"lch":[85.9919564191548602,67.3699388554866232,142.461966648640384],"luv":[85.9919564191548602,-53.4209300451477489,41.0476904892764551],"rgb":[0.466666666666666674,0.933333333333333348,0.66666666666666663],"xyz":[0.454357024004946708,0.679710219996780163,0.487569512548080619],"hpluv":[142.461966648640384,209.826142071523691,85.9919564191548602],"hsluv":[142.461966648640384,70.7318226594993575,85.9919564191548602]},"#77eebb":{"lch":[86.3336811163097337,60.2064759054885243,151.166773543866697],"luv":[86.3336811163097337,-52.7425080063862524,29.0352818163428843],"rgb":[0.466666666666666674,0.933333333333333348,0.733333333333333282],"xyz":[0.471494616112283382,0.686565256839714921,0.577827497646722366],"hpluv":[151.166773543866697,192.758378827545073,86.3336811163097337],"hsluv":[151.166773543866697,71.6464628016145895,86.3336811163097337]},"#77eecc":{"lch":[86.7156615657761449,54.5207620102438284,162.626180625161169],"luv":[86.7156615657761449,-52.0333543078463094,16.2801575438255028],"rgb":[0.466666666666666674,0.933333333333333348,0.8],"xyz":[0.490786991556005137,0.694282207017203778,0.679434008316992522],"hpluv":[162.626180625161169,180.150974339718545,86.7156615657761449],"hsluv":[162.626180625161169,72.6100300694190111,86.7156615657761449]},"#77eedd":{"lch":[87.1383902516757445,51.3868015721907483,176.742975614533037],"luv":[87.1383902516757445,-51.3037971188162771,2.91955082350760353],"rgb":[0.466666666666666674,0.933333333333333348,0.866666666666666696],"xyz":[0.512305317106638625,0.702889537237457285,0.792763856216998519],"hpluv":[176.742975614533037,175.997429168902841,87.1383902516757445],"hsluv":[176.742975614533037,73.6103328445444305,87.1383902516757445]},"#77eeee":{"lch":[87.6021784736708327,51.7277775307036,192.177050630061075],"luv":[87.6021784736708327,-50.5639263491039941,-10.9111099540033418],"rgb":[0.466666666666666674,0.933333333333333348,0.933333333333333348],"xyz":[0.536117497372813334,0.712414409343927302,0.918174672285521742],"hpluv":[192.177050630061075,184.503896016801804,87.6021784736708327],"hsluv":[192.177050630061075,74.6354143053272594,87.6021784736708327]},"#77eeff":{"lch":[88.107166277409533,55.7808213308350673,206.722210113893169],"luv":[88.107166277409533,-49.8232704905303336,-25.0826981397535107],"rgb":[0.466666666666666674,0.933333333333333348,1],"xyz":[0.562288545782644755,0.72288282870786,1.05600886057730348],"hpluv":[206.722210113893169,208.278116369071,88.107166277409533],"hsluv":[206.722210113893169,99.9999999999928804,88.107166277409533]},"#226600":{"lch":[37.5582057574881532,54.1200869321592961,123.236537452327113],"luv":[37.5582057574881532,-29.6630419039077431,45.2668505040000824],"rgb":[0.133333333333333331,0.4,0],"xyz":[0.0541083551941607538,0.0984246978946035633,0.0161464317764581713],"hpluv":[123.236537452327113,182.849162381267462,37.5582057574881532],"hsluv":[123.236537452327113,100.000000000002288,37.5582057574881532]},"#226611":{"lch":[37.6315056544729529,51.7196180195473119,124.710513292064419],"luv":[37.6315056544729529,-29.4507212427394194,42.5155725160833242],"rgb":[0.133333333333333331,0.4,0.0666666666666666657],"xyz":[0.0551200206937978721,0.0988293640944584162,0.0214745367412138],"hpluv":[124.710513292064419,174.398618448608886,37.6315056544729529],"hsluv":[124.710513292064419,93.2756924270169918,37.6315056544729529]},"#226622":{"lch":[37.7668566222969062,47.5272711912458519,127.715012949240034],"luv":[37.7668566222969062,-29.0740638670039928,37.5970785719263176],"rgb":[0.133333333333333331,0.4,0.133333333333333331],"xyz":[0.0569953788322748942,0.0995795073498492306,0.031351422937193],"hpluv":[127.715012949240034,159.687663587322874,37.7668566222969062],"hsluv":[127.715012949240034,81.309482828258183,37.7668566222969062]},"#226633":{"lch":[37.9882367851431084,41.3581300307957349,133.555287588173542],"luv":[37.9882367851431084,-28.497993426715972,29.9726423642471254],"rgb":[0.133333333333333331,0.4,0.2],"xyz":[0.0600831295647326347,0.100814607642832343,0.0476135767948041438],"hpluv":[133.555287588173542,138.150062101989391,37.9882367851431084],"hsluv":[133.555287588173542,82.0766642375396742,37.9882367851431084]},"#226644":{"lch":[38.3046909892153806,34.1393686276432291,144.379277801066621],"luv":[38.3046909892153806,-27.7515572052733894,19.8833488873617412],"rgb":[0.133333333333333331,0.4,0.266666666666666663],"xyz":[0.0645411248089515083,0.102597805740519923,0.0710923517476907824],"hpluv":[144.379277801066621,113.094855071532635,38.3046909892153806],"hsluv":[144.379277801066621,83.0794073538424414,38.3046909892153806]},"#226655":{"lch":[38.7222568592555234,28.0005039743963025,163.778220785970291],"luv":[38.7222568592555234,-26.8857357715149732,7.82211191715276755],"rgb":[0.133333333333333331,0.4,0.333333333333333315],"xyz":[0.0705035388483135073,0.104982771356264756,0.102494399021664684],"hpluv":[163.778220785970291,91.7581213405574516,38.7222568592555234],"hsluv":[163.778220785970291,84.2573678500350809,38.7222568592555234]},"#226666":{"lch":[39.2444156655659739,26.5583838540848376,192.177050630061103],"luv":[39.2444156655659739,-25.9608324434988,-5.60204710632589631],"rgb":[0.133333333333333331,0.4,0.4],"xyz":[0.0780885346218216786,0.108016769665668075,0.142442043428808646],"hpluv":[192.177050630061103,85.8742788705857691,39.2444156655659739],"hsluv":[192.177050630061103,85.5381417500271652,39.2444156655659739]},"#226677":{"lch":[39.8723950361637449,31.9118423017057466,218.325872967111877],"luv":[39.8723950361637449,-25.034725973132506,-19.7895976345933065],"rgb":[0.133333333333333331,0.4,0.466666666666666674],"xyz":[0.0874025476420236,0.111742374873748884,0.191495845335206555],"hpluv":[218.325872967111877,101.559108573692257,39.8723950361637449],"hsluv":[218.325872967111877,86.8516914397336137,39.8723950361637449]},"#226688":{"lch":[40.6054458094686836,41.8995680003496318,234.7955790683381],"luv":[40.6054458094686836,-24.1549067561574908,-34.2361545477476241],"rgb":[0.133333333333333331,0.4,0.533333333333333326],"xyz":[0.0985429647439674261,0.116198541714526482,0.250168708738778722],"hpluv":[234.7955790683381,130.937664052949941,40.6054458094686836],"hsluv":[234.7955790683381,88.1401386465251733,40.6054458094686836]},"#226699":{"lch":[41.4411308461218226,53.8933488408681782,244.319142730286046],"luv":[41.4411308461218226,-23.3551142576285748,-48.5698639826860301],"rgb":[0.133333333333333331,0.4,0.6],"xyz":[0.11159992885304014,0.12142132735815564,0.318935386379896657],"hpluv":[244.319142730286046,165.022400136876769,41.4411308461218226],"hsluv":[244.319142730286046,89.3619383181549267,41.4411308461218226]},"#2266aa":{"lch":[42.3756299296545578,66.5269840788925251,250.089735516440328],"luv":[42.3756299296545578,-22.655631138578233,-62.5504755245386193],"rgb":[0.133333333333333331,0.4,0.66666666666666663],"xyz":[0.126657621818292682,0.127444404544256734,0.398239235996895258],"hpluv":[250.089735516440328,199.214522587526545,42.3756299296545578],"hsluv":[250.089735516440328,90.4915899210210597,42.3756299296545578]},"#2266bb":{"lch":[43.404050412459263,79.1834510223872599,253.819475134999237],"luv":[43.404050412459263,-22.0656311263342282,-76.0468726432017803],"rgb":[0.133333333333333331,0.4,0.733333333333333282],"xyz":[0.143795213925629328,0.134299441387191493,0.488497221095537],"hpluv":[253.819475134999237,231.496000814517572,43.404050412459263],"hsluv":[253.819475134999237,91.5168425909476895,43.404050412459263]},"#2266cc":{"lch":[44.520728505208055,91.5877705166709,256.367788484306971],"luv":[44.520728505208055,-21.5861851194902563,-89.0076194502553193],"rgb":[0.133333333333333331,0.4,0.8],"xyz":[0.163087589369351083,0.142016391564680294,0.590103731765807105],"hpluv":[256.367788484306971,261.044502527732277,44.520728505208055],"hsluv":[256.367788484306971,92.43509558526695,44.520728505208055]},"#2266dd":{"lch":[45.7195068588792211,103.627896901795907,258.1878164043369],"luv":[45.7195068588792211,-21.2130654589597789,-101.433460308337743],"rgb":[0.133333333333333331,0.4,0.866666666666666696],"xyz":[0.184605914919984626,0.150623721784933828,0.703433579665813102],"hpluv":[258.1878164043369,287.616948003961852,45.7195068588792211],"hsluv":[258.1878164043369,93.2500413525610696,45.7195068588792211]},"#2266ee":{"lch":[46.9939777237187144,115.273293340910215,259.534328145616598],"luv":[46.9939777237187144,-20.9389771714321675,-113.35559709460216],"rgb":[0.133333333333333331,0.4,0.933333333333333348],"xyz":[0.208418095186159336,0.160148593891403873,0.828844395734336326],"hpluv":[259.534328145616598,311.261797341727515,46.9939777237187144],"hsluv":[259.534328145616598,93.969002023907521,46.9939777237187144]},"#2266ff":{"lch":[48.3376856243364728,126.534150990995641,260.559220542156197],"luv":[48.3376856243364728,-20.75515631493003,-124.820330288598825],"rgb":[0.133333333333333331,0.4,1],"xyz":[0.234589143595990757,0.170617013255336569,0.966678584026118171],"hpluv":[260.559220542156197,332.170629177470857,48.3376856243364728],"hsluv":[260.559220542156197,99.9999999999992184,48.3376856243364728]},"#77ff00":{"lch":[89.5984732569245921,124.632639236881928,120.733702851753719],"luv":[89.5984732569245921,-63.6933378884713406,107.128210438594465],"rgb":[0.466666666666666674,1,0],"xyz":[0.433660129810488626,0.754395258206476127,0.122760832470867665],"hpluv":[120.733702851753719,538.628162219261071,89.5984732569245921],"hsluv":[120.733702851753719,100.000000000002359,89.5984732569245921]},"#77ff11":{"lch":[89.6173512893739144,123.857431845856226,120.915842858481739],"luv":[89.6173512893739144,-63.635285050623331,106.260123846986147],"rgb":[0.466666666666666674,1,0.0666666666666666657],"xyz":[0.43467179531012573,0.754799924406331,0.128088937435623301],"hpluv":[120.915842858481739,536.333532616984598,89.6173512893739144],"hsluv":[120.915842858481739,99.9999999999912461,89.6173512893739144]},"#77ff22":{"lch":[89.6523282897647107,122.430860002153082,121.257903913559275],"luv":[89.6523282897647107,-63.5282943614823736,104.658832863679763],"rgb":[0.466666666666666674,1,0.133333333333333331],"xyz":[0.436547153448602787,0.755550067661721836,0.13796582363160248],"hpluv":[121.257903913559275,532.099457877515,89.6523282897647107],"hsluv":[121.257903913559275,99.9999999999912319,89.6523282897647107]},"#77ff33":{"lch":[89.7098670229763684,120.111567958755899,121.833904144252088],"luv":[89.7098670229763684,-63.3538816193187557,102.044472860004433],"rgb":[0.466666666666666674,1,0.2],"xyz":[0.439634904181060493,0.756785167954705,0.15422797748921363],"hpluv":[121.833904144252088,525.184015431200237,89.7098670229763684],"hsluv":[121.833904144252088,99.9999999999911893,89.7098670229763684]},"#77ff44":{"lch":[89.7928292607091834,116.827732221897932,122.694629127971339],"luv":[89.7928292607091834,-63.1058355300939766,98.317712230097257],"rgb":[0.466666666666666674,1,0.266666666666666663],"xyz":[0.444092899425279408,0.758568366052392529,0.177706752442100269],"hpluv":[122.694629127971339,515.324540814372313,89.7928292607091834],"hsluv":[122.694629127971339,99.9999999999912319,89.7928292607091834]},"#77ff55":{"lch":[89.9035853929870683,112.554947355203538,123.902387884937639],"luv":[89.9035853929870683,-62.7808648215699492,93.4193726503684161],"rgb":[0.466666666666666674,1,0.333333333333333315],"xyz":[0.450055313464641393,0.760953331668137278,0.209108799716074184],"hpluv":[123.902387884937639,502.374863630046946,89.9035853929870683],"hsluv":[123.902387884937639,99.9999999999911466,89.9035853929870683]},"#77ff66":{"lch":[90.0441481999633169,107.316590618570714,125.538864396596409],"luv":[90.0441481999633169,-62.3783098318914213,87.3258099562234662],"rgb":[0.466666666666666674,1,0.4],"xyz":[0.457640309238149579,0.763987329977540597,0.249056444123218146],"hpluv":[125.538864396596409,486.310350268178581,90.0441481999633169],"hsluv":[125.538864396596409,99.9999999999909193,90.0441481999633169]},"#77ff77":{"lch":[90.2162444924982,101.18761180958829,127.715012949239778],"luv":[90.2162444924982,-61.8999369112410065,80.0458031011764746],"rgb":[0.466666666666666674,1,0.466666666666666674],"xyz":[0.466954322258351484,0.767712935185621448,0.298110246029616055],"hpluv":[127.715012949239778,467.252180695244249,90.2162444924982],"hsluv":[127.715012949239778,99.9999999999908908,90.2162444924982]},"#77ff88":{"lch":[90.4213578195287084,94.3018691223763312,130.584388318929172],"luv":[90.4213578195287084,-61.3497133683486666,71.6174223886566352],"rgb":[0.466666666666666674,1,0.533333333333333326],"xyz":[0.478094739360295284,0.772169102026399,0.35678310943318825],"hpluv":[130.584388318929172,445.517691352744919,90.4213578195287084],"hsluv":[130.584388318929172,99.999999999990834,90.4213578195287084]},"#77ff99":{"lch":[90.660755936805927,86.8649116623181,134.360625617567337],"luv":[90.660755936805927,-60.7335297134313734,62.1043577106315],"rgb":[0.466666666666666674,1,0.6],"xyz":[0.491151703469368,0.777391887670028203,0.425549787074306129],"hpluv":[134.360625617567337,421.714412879972315,90.660755936805927],"hsluv":[134.360625617567337,99.9999999999904645,90.660755936805927]},"#77ffaa":{"lch":[90.9355096576679927,79.1755179707932513,139.336843070371486],"luv":[90.9355096576679927,-60.0588662825700368,51.5916197341755947],"rgb":[0.466666666666666674,1,0.66666666666666663],"xyz":[0.506209396434620595,0.78341496485612927,0.504853636691304786],"hpluv":[139.336843070371486,396.909453229520182,90.9355096576679927],"hsluv":[139.336843070371486,99.9999999999902371,90.9355096576679927]},"#77ffbb":{"lch":[91.2465066485729466,71.6593730134265599,145.894485367843032],"luv":[91.2465066485729466,-59.3344173347434776,40.1807498713488229],"rgb":[0.466666666666666674,1,0.733333333333333282],"xyz":[0.523346988541957159,0.790270001699064,0.595111621789946477],"hpluv":[145.894485367843032,372.920765055848051,91.2465066485729466],"hsluv":[145.894485367843032,99.9999999999901803,91.2465066485729466]},"#77ffcc":{"lch":[91.5944622372901591,64.9119206982031329,154.461378490718602],"luv":[91.5944622372901591,-58.5696939322881676,27.9847887504604707],"rgb":[0.466666666666666674,1,0.8],"xyz":[0.542639363985678913,0.797986951876552886,0.696718132460216633],"hpluv":[154.461378490718602,352.767904832744364,91.5944622372901591],"hsluv":[154.461378490718602,99.999999999989825,91.5944622372901591]},"#77ffdd":{"lch":[91.9799284987297,59.7212008687906604,165.331177548206284],"luv":[91.9799284987297,-57.7746296637575298,15.1232933062250314],"rgb":[0.466666666666666674,1,0.866666666666666696],"xyz":[0.564157689536312512,0.806594282096806392,0.81004798036022263],"hpluv":[165.331177548206284,341.200809564626638,91.9799284987297],"hsluv":[165.331177548206284,99.9999999999894698,91.9799284987297]},"#77ffee":{"lch":[92.4033024177180238,56.9851054432815545,178.272694676027839],"luv":[92.4033024177180238,-56.9592119034112,1.71767916801026743],"rgb":[0.466666666666666674,1,0.933333333333333348],"xyz":[0.587969869802487222,0.816119154203276409,0.935458796428745853],"hpluv":[178.272694676027839,344.865805035808421,92.4033024177180238],"hsluv":[178.272694676027839,99.9999999999889582,92.4033024177180238]},"#77ffff":{"lch":[92.8648336399367196,57.4251975820971623,192.177050630061018],"luv":[92.8648336399367196,-56.1331570721439945,-12.112885471963672],"rgb":[0.466666666666666674,1,1],"xyz":[0.614140918212318643,0.826587573567209133,1.0732929847205277],"hpluv":[192.177050630061018,371.354821198433683,92.8648336399367196],"hsluv":[192.177050630061018,99.9999999999883187,92.8648336399367196]},"#227700":{"lch":[43.5559297152692295,63.9882214930525208,124.519604676885976],"luv":[43.5559297152692295,-36.2613695379953711,52.7219647687080268],"rgb":[0.133333333333333331,0.466666666666666674,0],"xyz":[0.0725620932475783825,0.13533217400143932,0.0222976777942638753],"hpluv":[124.519604676885976,186.419817132403381,43.5559297152692295],"hsluv":[124.519604676885976,100.000000000002331,43.5559297152692295]},"#227711":{"lch":[43.6152314308602769,61.9621092027414093,125.591738779589861],"luv":[43.6152314308602769,-36.0623023987336282,50.3866383335378174],"rgb":[0.133333333333333331,0.466666666666666674,0.0666666666666666657],"xyz":[0.0735737587472155,0.135736840201294173,0.0276257827590195085],"hpluv":[125.591738779589861,180.271610265320959,43.6152314308602769],"hsluv":[125.591738779589861,95.0867888092805345,43.6152314308602769]},"#227722":{"lch":[43.7248500006056062,58.3665782213362476,127.715012949240148],"luv":[43.7248500006056062,-35.7048401974777292,46.1716562377823081],"rgb":[0.133333333333333331,0.466666666666666674,0.133333333333333331],"xyz":[0.0754491168856925298,0.136486983456685,0.0375026689549987],"hpluv":[127.715012949240148,169.385110384242353,43.7248500006056062],"hsluv":[127.715012949240148,86.2472116803111248,43.7248500006056062]},"#227733":{"lch":[43.9044636679785,52.8998675233047422,131.63685913347058],"luv":[43.9044636679785,-35.1470498804547518,39.5358175416168365],"rgb":[0.133333333333333331,0.466666666666666674,0.2],"xyz":[0.0785368676181502634,0.137722083749668101,0.0537648228126098443],"hpluv":[131.63685913347058,152.892166506547881,43.9044636679785],"hsluv":[131.63685913347058,86.6671370230467204,43.9044636679785]},"#227744":{"lch":[44.1618994548856207,46.0187725021067919,138.382113458020484],"luv":[44.1618994548856207,-34.403210499316,30.5638107889119937],"rgb":[0.133333333333333331,0.466666666666666674,0.266666666666666663],"xyz":[0.082994862862369137,0.139505281847355694,0.0772435977654964828],"hpluv":[138.382113458020484,132.228969104156789,44.1618994548856207],"hsluv":[138.382113458020484,87.230083060842972,44.1618994548856207]},"#227755":{"lch":[44.5028042963196171,38.7961505601339098,149.7341572603126],"luv":[44.5028042963196171,-33.5080868304533865,19.5537570621958743],"rgb":[0.133333333333333331,0.466666666666666674,0.333333333333333315],"xyz":[0.088957276901731136,0.141890247463100527,0.108645645039470398],"hpluv":[149.7341572603126,110.621765171639552,44.5028042963196171],"hsluv":[149.7341572603126,87.9126674499387235,44.5028042963196171]},"#227766":{"lch":[44.9310046324235657,33.2418513983527504,167.948858330193701],"luv":[44.9310046324235657,-32.5092552143098246,6.94038974417402166],"rgb":[0.133333333333333331,0.466666666666666674,0.4],"xyz":[0.0965422726752393073,0.144924245772503818,0.148593289446614346],"hpluv":[167.948858330193701,93.8811500215461,44.9310046324235657],"hsluv":[167.948858330193701,88.6822605795820778,44.9310046324235657]},"#227777":{"lch":[45.4487163896678652,32.1827345016432886,192.177050630061217],"luv":[45.4487163896678652,-31.4586377906525136,-6.7884098550242733],"rgb":[0.133333333333333331,0.466666666666666674,0.466666666666666674],"xyz":[0.105856285695441227,0.148649850980584641,0.197647091353012255],"hpluv":[192.177050630061217,89.8546686552558,45.4487163896678652],"hsluv":[192.177050630061217,89.5029511213474,45.4487163896678652]},"#227788":{"lch":[46.0567093690380389,37.0457871068229778,214.840569513336618],"luv":[46.0567093690380389,-30.4051407329018595,-21.1640676472289648],"rgb":[0.133333333333333331,0.466666666666666674,0.533333333333333326],"xyz":[0.116996702797385055,0.153106017821362239,0.256319954756584423],"hpluv":[214.840569513336618,102.066975938242322,46.0567093690380389],"hsluv":[214.840569513336618,90.3407286635711415,46.0567093690380389]},"#227799":{"lch":[46.7544651468991219,46.3079141704010908,230.605362437026685],"luv":[46.7544651468991219,-29.3896969252833173,-35.7864308006991223],"rgb":[0.133333333333333331,0.466666666666666674,0.6],"xyz":[0.130053666906457754,0.158328803464991397,0.325086632397702358],"hpluv":[230.605362437026685,125.681528260779189,46.7544651468991219],"hsluv":[230.605362437026685,91.1669787694701,46.7544651468991219]},"#2277aa":{"lch":[47.5403420753171133,57.8256716053654429,240.536105931551504],"luv":[47.5403420753171133,-28.4430017925967782,-50.3468365007961935],"rgb":[0.133333333333333331,0.466666666666666674,0.66666666666666663],"xyz":[0.14511135987171031,0.164351880651092491,0.404390482014700958],"hpluv":[240.536105931551504,154.346828351852139,47.5403420753171133],"hsluv":[240.536105931551504,91.9600907928071365,47.5403420753171133]},"#2277bb":{"lch":[48.4117490016133161,70.2708193466387172,246.886175208709233],"luv":[48.4117490016133161,-27.5854458877112236,-64.6299561103370337],"rgb":[0.133333333333333331,0.466666666666666674,0.733333333333333282],"xyz":[0.162248951979046957,0.17120691749402725,0.494648467113342705],"hpluv":[246.886175208709233,184.188949495161438,48.4117490016133161],"hsluv":[246.886175208709233,92.7055270960754427,48.4117490016133161]},"#2277cc":{"lch":[49.3653235224945348,82.9601291275659634,251.131977113837081],"luv":[49.3653235224945348,-26.8284223951791141,-78.5023488607078],"rgb":[0.133333333333333331,0.466666666666666674,0.8],"xyz":[0.181541327422768684,0.178923867671516051,0.596254977783612805],"hpluv":[251.131977113837081,213.248879712122147,49.3653235224945348],"hsluv":[251.131977113837081,93.3949294454883301,49.3653235224945348]},"#2277dd":{"lch":[50.3971082370692898,95.5506012475210156,254.100456017762497],"luv":[50.3971082370692898,-26.1762366694964186,-91.8951687118820928],"rgb":[0.133333333333333331,0.466666666666666674,0.866666666666666696],"xyz":[0.203059652973402227,0.187531197891769585,0.709584825683618803],"hpluv":[254.100456017762497,240.584217083197558,50.3971082370692898],"hsluv":[254.100456017762497,94.0248036114472399,50.3971082370692898]},"#2277ee":{"lch":[51.5027182397596448,107.874744495734575,256.256689112315712],"luv":[51.5027182397596448,-25.6280715933506258,-104.786270314512635],"rgb":[0.133333333333333331,0.466666666666666674,0.933333333333333348],"xyz":[0.226871833239577,0.19705606999823963,0.834995641752142],"hpluv":[256.256689112315712,265.784074864026707,51.5027182397596448],"hsluv":[256.256689112315712,94.5951642228933594,51.5027182397596448]},"#2277ff":{"lch":[52.6774941409559432,119.859275592882611,257.873120264558906],"luv":[52.6774941409559432,-25.1797078347732324,-117.18459053564186],"rgb":[0.133333333333333331,0.466666666666666674,1],"xyz":[0.253042881649408358,0.207524489362172326,0.972829830043923871],"hpluv":[257.873120264558906,288.725982332353851,52.6774941409559432],"hsluv":[257.873120264558906,99.9999999999990195,52.6774941409559432]},"#228800":{"lch":[49.4326013626652951,73.5543249838887903,125.335546592141156],"luv":[49.4326013626652951,-42.5411625112882845,60.0040683289365475],"rgb":[0.133333333333333331,0.533333333333333326,0],"xyz":[0.0946344629725488357,0.179476913451380865,0.0296551343692538216],"hpluv":[125.335546592141156,188.81394887832684,49.4326013626652951],"hsluv":[125.335546592141156,100.000000000002359,49.4326013626652951]},"#228811":{"lch":[49.4817413630627669,71.8188220709608,126.143293588176633],"luv":[49.4817413630627669,-42.3592238010278379,57.9968909738682825],"rgb":[0.133333333333333331,0.533333333333333326,0.0666666666666666657],"xyz":[0.0956461284721859539,0.179881579651235718,0.0349832393340094513],"hpluv":[126.143293588176633,184.175827324420425,49.4817413630627669],"hsluv":[126.143293588176633,96.2839239867753776,49.4817413630627669]},"#228822":{"lch":[49.5726392440562904,68.7061860444130161,127.715012949240233],"luv":[49.5726392440562904,-42.0299333634258545,54.3509402148249521],"rgb":[0.133333333333333331,0.533333333333333326,0.133333333333333331],"xyz":[0.097521486610662983,0.180631722906626546,0.0448601255299886509],"hpluv":[127.715012949240233,175.870551812838869,49.5726392440562904],"hsluv":[127.715012949240233,89.549457305464,49.5726392440562904]},"#228833":{"lch":[49.7217546026668913,63.8760413965615115,130.529662443073192],"luv":[49.7217546026668913,-41.5093107949551552,48.5502397710151357],"rgb":[0.133333333333333331,0.533333333333333326,0.2],"xyz":[0.100609237343120717,0.181866823199609645,0.0611222793875997941],"hpluv":[130.529662443073192,163.016240956960957,49.7217546026668913],"hsluv":[130.529662443073192,89.7937238944362122,49.7217546026668913]},"#228844":{"lch":[49.9358562328630171,57.5522088774297558,135.149605574971361],"luv":[49.9358562328630171,-40.8016788905052792,40.5891579906186308],"rgb":[0.133333333333333331,0.533333333333333326,0.266666666666666663],"xyz":[0.10506723258733959,0.183650021297297239,0.0846010543404864257],"hpluv":[135.149605574971361,146.247625060139768,49.9358562328630171],"hsluv":[135.149605574971361,90.1269016918236616,49.9358562328630171]},"#228855":{"lch":[50.2200542052193697,50.3265642713731509,142.503880569306347],"luv":[50.2200542052193697,-39.9288227743001443,30.6341669254964764],"rgb":[0.133333333333333331,0.533333333333333326,0.333333333333333315],"xyz":[0.111029646626701589,0.186034986913042072,0.116003101614460341],"hpluv":[142.503880569306347,127.162609675202816,50.2200542052193697],"hsluv":[142.503880569306347,90.5399352865824341,50.2200542052193697]},"#228866":{"lch":[50.5781035519961648,43.3062651916131074,154.006170397495453],"luv":[50.5781035519961648,-38.9254575775356315,18.9800252166796355],"rgb":[0.133333333333333331,0.533333333333333326,0.4],"xyz":[0.11861464240020976,0.189068985222445363,0.155950746021604303],"hpluv":[154.006170397495453,108.649446020320369,50.5781035519961648],"hsluv":[154.006170397495453,91.0179493017372607,50.5781035519961648]},"#228877":{"lch":[51.0125694858746357,38.3061120533697519,170.992189866461246],"luv":[51.0125694858746357,-37.8336830583721451,5.997553408179356],"rgb":[0.133333333333333331,0.533333333333333326,0.466666666666666674],"xyz":[0.12792865542041168,0.192794590430526186,0.205004547928002212],"hpluv":[170.992189866461246,95.2862426106229634,51.0125694858746357],"hsluv":[170.992189866461246,91.5427076584867621,51.0125694858746357]},"#228888":{"lch":[51.5249413470067594,37.5420496271247259,192.177050630061103],"luv":[51.5249413470067594,-36.6973707929670923,-7.91886779085127834],"rgb":[0.133333333333333331,0.533333333333333326,0.533333333333333326],"xyz":[0.139069072522355508,0.197250757271303784,0.263677411331574407],"hpluv":[192.177050630061103,92.4570004995607775,51.5249413470067594],"hsluv":[192.177050630061103,92.0950966753639761,51.5249413470067594]},"#228899":{"lch":[52.1157302062907064,42.0223592343639183,212.203992327233067],"luv":[52.1157302062907064,-35.5574728144251,-22.3951959730954577],"rgb":[0.133333333333333331,0.533333333333333326,0.6],"xyz":[0.152126036631428208,0.202473542914932941,0.332444088972692287],"hpluv":[212.203992327233067,102.317738016510475,52.1157302062907064],"hsluv":[212.203992327233067,92.6571999954113465,52.1157302062907064]},"#2288aa":{"lch":[52.784565077464336,50.6329849579481888,227.127925237574203],"luv":[52.784565077464336,-34.4488478228089221,-37.1076279143892478],"rgb":[0.133333333333333331,0.533333333333333326,0.66666666666666663],"xyz":[0.167183729596680763,0.208496620101034036,0.411747938589690887],"hpluv":[227.127925237574203,121.72111165959474,52.784565077464336],"hsluv":[227.127925237574203,93.213704377986,52.784565077464336]},"#2288bb":{"lch":[53.5302933631107294,61.6316847476891141,237.186375672219185],"luv":[53.5302933631107294,-33.3987075035381409,-51.7975955226846381],"rgb":[0.133333333333333331,0.533333333333333326,0.733333333333333282],"xyz":[0.18432132170401741,0.215351656943968794,0.502005923688332634],"hpluv":[237.186375672219185,146.097822373379188,53.5302933631107294],"hsluv":[237.186375672219185,93.7525919029994,53.5302933631107294]},"#2288cc":{"lch":[54.351086283976727,73.7818378825302261,243.928624140525301],"luv":[54.351086283976727,-32.4264148607849876,-66.274333045306733],"rgb":[0.133333333333333331,0.533333333333333326,0.8],"xyz":[0.203613697147739137,0.223068607121457596,0.60361243435860279],"hpluv":[243.928624140525301,172.258462735913952,54.351086283976727],"hsluv":[243.928624140525301,94.2652369190112864,54.351086283976727]},"#2288dd":{"lch":[55.2445474530087779,86.375042864399532,248.580085361336785],"luv":[55.2445474530087779,-31.544198041281593,-80.4090268549449831],"rgb":[0.133333333333333331,0.533333333333333326,0.866666666666666696],"xyz":[0.22513202269837268,0.23167593734171113,0.716942282258608787],"hpluv":[248.580085361336785,198.39840761978212,55.2445474530087779],"hsluv":[248.580085361336785,94.7460999996689566,55.2445474530087779]},"#2288ee":{"lch":[56.2078215466105604,99.0227420546019772,251.903461744910402],"luv":[56.2078215466105604,-30.7583451142169402,-94.1245326673496265],"rgb":[0.133333333333333331,0.533333333333333326,0.933333333333333348],"xyz":[0.248944202964547445,0.241200809448181175,0.842353098327132],"hpluv":[251.903461744910402,223.551465466634539,56.2078215466105604],"hsluv":[251.903461744910402,95.1922101623356838,56.2078215466105604]},"#2288ff":{"lch":[57.2376997180489866,111.514610807296222,254.356224713070389],"luv":[57.2376997180489866,-30.0705415498761575,-107.383755542446536],"rgb":[0.133333333333333331,0.533333333333333326,1],"xyz":[0.275115251374378811,0.251669228812113843,0.980187286618913856],"hpluv":[254.356224713070389,247.223031548482197,57.2376997180489866],"hsluv":[254.356224713070389,99.9999999999988773,57.2376997180489866]},"#229900":{"lch":[55.1973816023000694,82.8565734474734086,125.883775052172936],"luv":[55.1973816023000694,-48.5657961090338617,67.1310301704979508],"rgb":[0.133333333333333331,0.6,0],"xyz":[0.120504063425016322,0.23121611435631656,0.0382783345200760766],"hpluv":[125.883775052172936,190.479314261094402,55.1973816023000694],"hsluv":[125.883775052172936,100.000000000002331,55.1973816023000694]},"#229911":{"lch":[55.2388931006948951,81.3509781714907376,126.510168490119042],"luv":[55.2388931006948951,-48.4010205856972817,65.3859530459048273],"rgb":[0.133333333333333331,0.6,0.0666666666666666657],"xyz":[0.121515728924653441,0.231620780556171413,0.0436064394848317063],"hpluv":[126.510168490119042,186.877552842351321,55.2388931006948951],"hsluv":[126.510168490119042,97.109403045153627,55.2388931006948951]},"#229922":{"lch":[55.3157166450500739,78.6308396618125869,127.715012949240275],"luv":[55.3157166450500739,-48.1011847922964293,62.2019691609440102],"rgb":[0.133333333333333331,0.6,0.133333333333333331],"xyz":[0.12339108706313047,0.232370923811562241,0.0534833256808109059],"hpluv":[127.715012949240275,180.378053709807,55.3157166450500739],"hsluv":[127.715012949240275,91.8445791693357592,55.3157166450500739]},"#229933":{"lch":[55.4418461137647,74.3518111171957656,129.829614369777261],"luv":[55.4418461137647,-47.6228343175573201,57.0986643273697],"rgb":[0.133333333333333331,0.6,0.2],"xyz":[0.126478837795588217,0.23360602410454534,0.0697454795384220561],"hpluv":[129.829614369777261,170.173995330187722,55.4418461137647],"hsluv":[129.829614369777261,91.9941043082508827,55.4418461137647]},"#229944":{"lch":[55.6231658975890042,68.6117263626113925,133.195472742568398],"luv":[55.6231658975890042,-46.964006542137362,50.0195070344354846],"rgb":[0.133333333333333331,0.6,0.266666666666666663],"xyz":[0.130936833039807077,0.235389222202232934,0.0932242544913087],"hpluv":[133.195472742568398,156.524371813771666,55.6231658975890042],"hsluv":[133.195472742568398,92.2005608183228844,55.6231658975890042]},"#229955":{"lch":[55.8642490130281715,61.7521412094073696,138.342726405552],"luv":[55.8642490130281715,-46.1371272258517706,41.0450049980768625],"rgb":[0.133333333333333331,0.6,0.333333333333333315],"xyz":[0.136899247079169062,0.237774187817977767,0.124626301765282596],"hpluv":[138.342726405552,140.267605312348593,55.8642490130281715],"hsluv":[138.342726405552,92.4605995247666357,55.8642490130281715]},"#229966":{"lch":[56.1686206396836383,54.4254646973719787,146.085824005346524],"luv":[56.1686206396836383,-45.1662924386951516,30.3667126121210664],"rgb":[0.133333333333333331,0.6,0.4],"xyz":[0.144484242852677247,0.240808186127381058,0.164573946172426544],"hpluv":[146.085824005346524,122.955430161041207,56.1686206396836383],"hsluv":[146.085824005346524,92.7673635780271,56.1686206396836383]},"#229977":{"lch":[56.5388973522399,47.7139826201784629,157.505617321427167],"luv":[56.5388973522399,-44.0837619060267798,18.2550287233801534],"rgb":[0.133333333333333331,0.6,0.466666666666666674],"xyz":[0.153798255872879153,0.244533791335461881,0.213627748078824453],"hpluv":[157.505617321427167,107.087223886255089,56.5388973522399],"hsluv":[157.505617321427167,93.1115325865156,56.5388973522399]},"#229988":{"lch":[56.9768757530356,43.2189787336254057,173.325138204180888],"luv":[56.9768757530356,-42.9260293704251126,5.02355703331473169],"rgb":[0.133333333333333331,0.6,0.533333333333333326],"xyz":[0.164938672974823,0.248989958176239479,0.272300611482396648],"hpluv":[173.325138204180888,96.2532040641867,56.9768757530356],"hsluv":[173.325138204180888,93.4824878896153706,56.9768757530356]},"#229999":{"lch":[57.4836007022547477,42.6905833214490045,192.177050630061103],"luv":[57.4836007022547477,-41.7300648492924537,-9.00486490733903366],"rgb":[0.133333333333333331,0.6,0.6],"xyz":[0.177995637083895708,0.254212743819868636,0.341067289123514528],"hpluv":[192.177050630061103,94.2383018299879467,57.4836007022547477],"hsluv":[192.177050630061103,93.8694254698009587,57.4836007022547477]},"#2299aa":{"lch":[58.0594270662980563,46.8633307325653,210.133219640519371],"luv":[58.0594270662980563,-40.5302437993065823,-23.5259666096543114],"rgb":[0.133333333333333331,0.6,0.66666666666666663],"xyz":[0.193053330049148264,0.260235821005969759,0.420371138740513128],"hpluv":[210.133219640519371,102.423528384682896,58.0594270662980563],"hsluv":[210.133219640519371,94.2622655036202701,58.0594270662980563]},"#2299bb":{"lch":[58.704081470592044,54.8976329577059445,224.200669016044458],"luv":[58.704081470592044,-39.3562485070025474,-38.2731734745639],"rgb":[0.133333333333333331,0.6,0.733333333333333282],"xyz":[0.210190922156484883,0.267090857848904517,0.51062912383915493],"hpluv":[224.200669016044458,118.665547847191604,58.704081470592044],"hsluv":[224.200669016044458,94.6522748426748279,58.704081470592044]},"#2299cc":{"lch":[59.4167266489170771,65.3729917945351247,234.209137470411804],"luv":[59.4167266489170771,-38.231976961148348,-53.0277662532618663],"rgb":[0.133333333333333331,0.6,0.8],"xyz":[0.229483297600206637,0.274807808026393319,0.612235634509425086],"hpluv":[234.209137470411804,139.613998456385502,59.4167266489170771],"hsluv":[234.209137470411804,95.032392249276171,59.4167266489170771]},"#2299dd":{"lch":[60.1960287785942114,77.1664383037622201,241.199920521286145],"luv":[60.1960287785942114,-37.1753089722913614,-67.6214137925481],"rgb":[0.133333333333333331,0.6,0.866666666666666696],"xyz":[0.251001623150840181,0.283415138246646825,0.725565482409431084],"hpluv":[241.199920521286145,162.667181112599422,60.1960287785942114],"hsluv":[241.199920521286145,95.397300800485425,60.1960287785942114]},"#2299ee":{"lch":[61.0402269370523243,89.5733454479413211,246.163931095701571],"luv":[61.0402269370523243,-36.1984881015500051,-81.9332269222821],"rgb":[0.133333333333333331,0.6,0.933333333333333348],"xyz":[0.27481380341701489,0.292940010353116842,0.850976298477954307],"hpluv":[246.163931095701571,186.209563381880429,61.0402269370523243],"hsluv":[246.163931095701571,95.74331870201,61.0402269370523243]},"#2299ff":{"lch":[61.9472031658153384,102.178145565387652,249.783903318571561],"luv":[61.9472031658153384,-35.3088685699608078,-95.8835607989752532],"rgb":[0.133333333333333331,0.6,1],"xyz":[0.300984851826846311,0.303408429717049566,0.988810486769736152],"hpluv":[249.783903318571561,209.303089797111312,61.9472031658153384],"hsluv":[249.783903318571561,99.9999999999986,61.9472031658153384]},"#110000":{"lch":[1.07666134976862637,3.62084603829176643,12.1770506300617818],"luv":[1.07666134976862637,3.53937866928378497,0.763756943295526236],"rgb":[0.0666666666666666657,0,0],"xyz":[0.00231161193210362246,0.00119192490249095569,0.000108356809317355026],"hpluv":[12.1770506300617818,426.746789183125145,1.07666134976862637],"hsluv":[12.1770506300617818,100.000000000002203,1.07666134976862637]},"#110011":{"lch":[1.44219482929484544,3.28508596549136378,307.715012949243601],"luv":[1.44219482929484544,2.00959989444743092,-2.59871084672866193],"rgb":[0.0666666666666666657,0,0.0666666666666666657],"xyz":[0.0033232774317407442,0.00159659110234581,0.00543646177407298634],"hpluv":[307.715012949243601,289.042783730483393,1.44219482929484544],"hsluv":[307.715012949243601,99.9999999999988347,1.44219482929484544]},"#110022":{"lch":[2.1197964535087821,6.27745605271938789,280.884754167684719],"luv":[2.1197964535087821,1.18539805862553327,-6.16451830530416167],"rgb":[0.0666666666666666657,0,0.133333333333333331],"xyz":[0.00519863557021776369,0.00234673435773662814,0.0153133479700521824],"hpluv":[280.884754167684719,375.775833064690062,2.1197964535087821],"hsluv":[280.884754167684719,99.9999999999998721,2.1197964535087821]},"#110033":{"lch":[3.23545797359596321,11.0622687483975319,272.972319481398301],"luv":[3.23545797359596321,0.57361730895702967,-11.0473867065762477],"rgb":[0.0666666666666666657,0,0.2],"xyz":[0.00828638630267550247,0.00358183465071974143,0.0315755018276633256],"hpluv":[272.972319481398301,433.858158519435221,3.23545797359596321],"hsluv":[272.972319481398301,100.000000000000355,3.23545797359596321]},"#110044":{"lch":[4.84621421062803659,17.7312810137515946,269.891014646828467],"luv":[4.84621421062803659,-0.0337275934556249754,-17.7312489362161827],"rgb":[0.0666666666666666657,0,0.266666666666666663],"xyz":[0.012744381546894383,0.0053650327484073175,0.0550542767805499642],"hpluv":[269.891014646828467,464.276639746945534,4.84621421062803659],"hsluv":[269.891014646828467,100.000000000000711,4.84621421062803659]},"#110055":{"lch":[7.00054481789469563,26.532890242342738,268.413820694361107],"luv":[7.00054481789469563,-0.734444075336115332,-26.5227233992365541],"rgb":[0.0666666666666666657,0,0.333333333333333315],"xyz":[0.0187067955862563751,0.00774999836415214954,0.0864563240545238726],"hpluv":[268.413820694361107,480.941270902403687,7.00054481789469563],"hsluv":[268.413820694361107,100.000000000000682,7.00054481789469563]},"#110066":{"lch":[9.62818818466394,37.2351477319955,267.604082628906383],"luv":[9.62818818466394,-1.55659527409507947,-37.20259719127408],"rgb":[0.0666666666666666657,0,0.4],"xyz":[0.0262917913597645499,0.010783996673555462,0.126403968461667848],"hpluv":[267.604082628906383,490.735908571457742,9.62818818466394],"hsluv":[267.604082628906383,100.000000000000753,9.62818818466394]},"#110077":{"lch":[12.2928363787590555,48.1341065988899643,267.117295446388],"luv":[12.2928363787590555,-2.42073458662620622,-48.0731969202633138],"rgb":[0.0666666666666666657,0,0.466666666666666674],"xyz":[0.0356058043799664659,0.0145096018816362783,0.175457770368065757],"hpluv":[267.117295446388,496.866985521105335,12.2928363787590555],"hsluv":[267.117295446388,100.000000000000739,12.2928363787590555]},"#110088":{"lch":[14.9348588897968106,58.9551979191609803,266.804247897724281],"luv":[14.9348588897968106,-3.28660375460643372,-58.8635166928348781],"rgb":[0.0666666666666666657,0,0.533333333333333326],"xyz":[0.0467462214819102939,0.0189657687224138727,0.234130633771637925],"hpluv":[266.804247897724281,500.910695182750828,14.9348588897968106],"hsluv":[266.804247897724281,100.000000000000753,14.9348588897968106]},"#110099":{"lch":[17.5475874535139624,69.6538923837914297,266.59230255326986],"luv":[17.5475874535139624,-4.14026096141065292,-69.5307339482636],"rgb":[0.0666666666666666657,0,0.6],"xyz":[0.0598031855909830073,0.0241885543660430302,0.302897311412755832],"hpluv":[266.59230255326986,503.694607743992833,17.5475874535139624],"hsluv":[266.59230255326986,100.000000000000796,17.5475874535139624]},"#1100aa":{"lch":[20.1284543895036734,80.2134478690449,266.442863009455],"luv":[20.1284543895036734,-4.97675334080985809,-80.0589104673847203],"rgb":[0.0666666666666666657,0,0.66666666666666663],"xyz":[0.0748608785562355494,0.0302116315521441317,0.382201161029754433],"hpluv":[266.442863009455,505.680355905096519,20.1284543895036734],"hsluv":[266.442863009455,100.000000000000782,20.1284543895036734]},"#1100bb":{"lch":[22.6769756305364183,90.6302333913987894,266.3339747285724],"luv":[22.6769756305364183,-5.79494806122388795,-90.444777525002138],"rgb":[0.0666666666666666657,0,0.733333333333333282],"xyz":[0.0919984706635722,0.0370666683950788903,0.472459146128396179],"hpluv":[266.3339747285724,507.139328846885462,22.6769756305364183],"hsluv":[266.3339747285724,100.000000000001,22.6769756305364183]},"#1100cc":{"lch":[25.1937235339869332,100.906819147977927,266.252448568601267],"luv":[25.1937235339869332,-6.59531864805210422,-100.691051849175651],"rgb":[0.0666666666666666657,0,0.8],"xyz":[0.111290846107293936,0.0447836185725676919,0.574065656798666279],"hpluv":[266.252448568601267,508.238409831726415,25.1937235339869332],"hsluv":[266.252448568601267,100.000000000000824,25.1937235339869332]},"#1100dd":{"lch":[27.6797893663012289,111.048607057141055,266.189998210144608],"luv":[27.6797893663012289,-7.37896669992084409,-110.803176758488192],"rgb":[0.0666666666666666657,0,0.866666666666666696],"xyz":[0.132809171657927494,0.0533909487928212259,0.687395504698672277],"hpluv":[266.189998210144608,509.084249760461944,27.6797893663012289],"hsluv":[266.189998210144608,100.000000000000938,27.6797893663012289]},"#1100ee":{"lch":[30.1364964584496846,121.062148077250455,266.141219022825339],"luv":[30.1364964584496846,-8.14718371295858823,-120.787694301304626],"rgb":[0.0666666666666666657,0,0.933333333333333348],"xyz":[0.156621351924102231,0.0629158208992912638,0.8128063207671955],"hpluv":[266.141219022825339,509.74730741907581,30.1364964584496846],"hsluv":[266.141219022825339,100.000000000000952,30.1364964584496846]},"#1100ff":{"lch":[32.5652456752648263,130.954293728553409,266.102472093749839],"luv":[32.5652456752648263,-8.90125725083502495,-130.651424275813781],"rgb":[0.0666666666666666657,0,1],"xyz":[0.182792400333933625,0.0733842402632239599,0.950640509058977345],"hpluv":[266.102472093749839,510.275492181060656,32.5652456752648263],"hsluv":[266.102472093749839,100.000000000001108,32.5652456752648263]},"#111100":{"lch":[4.69779601336656771,5.17885327658484673,85.8743202181747307],"luv":[4.69779601336656771,0.372589941443898953,5.16543299210515716],"rgb":[0.0666666666666666657,0.0666666666666666657,0],"xyz":[0.00431601219303203148,0.00520072542434782924,0.000776490229626805866],"hpluv":[85.8743202181747307,139.887458074797621,4.69779601336656771],"hsluv":[85.8743202181747307,100.000000000002359,4.69779601336656771]},"#111111":{"lch":[5.06332949289278655,2.68159353999537178e-13,0],"luv":[5.06332949289278655,2.52120910544652531e-13,9.13481559944393266e-14],"rgb":[0.0666666666666666657,0.0666666666666666657,0.0666666666666666657],"xyz":[0.00532767769266915322,0.00560539162420268383,0.00610459519438243722],"hpluv":[0,6.72041492281092e-12,5.06332949289278655],"hsluv":[0,1.92419399944792277e-12,5.06332949289278655]},"#111122":{"lch":[5.74093111710672321,6.60006851394265048,265.874320218179719],"luv":[5.74093111710672321,-0.474838542395297381,-6.58296534605743222],"rgb":[0.0666666666666666657,0.0666666666666666657,0.133333333333333331],"xyz":[0.00720303583114617271,0.00635553487959350169,0.0159814813903616341],"hpluv":[265.874320218179719,145.883251481840432,5.74093111710672321],"hsluv":[265.874320218179719,28.41442223352254,5.74093111710672321]},"#111133":{"lch":[6.85659263719390388,14.212336546779186,265.874320218178582],"luv":[6.85659263719390388,-1.02249925976518052,-14.1755072354640976],"rgb":[0.0666666666666666657,0.0666666666666666657,0.2],"xyz":[0.0102907865636039132,0.00759063517257661455,0.0322436352479727809],"hpluv":[265.874320218178582,263.024656142887807,6.85659263719390388],"hsluv":[265.874320218178582,51.2306489028398957,6.85659263719390388]},"#111144":{"lch":[8.45853257854777141,22.7927945223118087,265.874320218178241],"luv":[8.45853257854777141,-1.63981590573345759,-22.7337301367732181],"rgb":[0.0666666666666666657,0.0666666666666666657,0.266666666666666663],"xyz":[0.0147487818078227903,0.00937383327026419105,0.0557224102008594194],"hpluv":[265.874320218178241,341.933676209697239,8.45853257854777141],"hsluv":[265.874320218178241,66.600159737267461,8.45853257854777141]},"#111155":{"lch":[10.3782295585045325,32.1242805487719707,265.874320218178127],"luv":[10.3782295585045325,-2.31116487943396587,-32.0410349033279545],"rgb":[0.0666666666666666657,0.0666666666666666657,0.333333333333333315],"xyz":[0.0207111958471847858,0.011758798886009024,0.0871244574748333278],"hpluv":[265.874320218178127,392.780088665713265,10.3782295585045325],"hsluv":[265.874320218178127,76.5037738801481453,10.3782295585045325]},"#111166":{"lch":[12.4757228248048477,41.8651930040424887,265.87432021817807],"luv":[12.4757228248048477,-3.01196982745712916,-41.7567051265328644],"rgb":[0.0666666666666666657,0.0666666666666666657,0.4],"xyz":[0.0282961916206929606,0.0147927971954123355,0.12707210188197729],"hpluv":[265.87432021817807,425.820638501567,12.4757228248048477],"hsluv":[265.87432021817807,82.9392496755345263,12.4757228248048477]},"#111177":{"lch":[14.6896895275036599,51.8467212520223697,265.874320218178],"luv":[14.6896895275036599,-3.73008575521422614,-51.7123676197837199],"rgb":[0.0666666666666666657,0.0666666666666666657,0.466666666666666674],"xyz":[0.0376102046408948731,0.0185184024034931519,0.176125903788375199],"hpluv":[265.874320218178,447.865910041391658,14.6896895275036599],"hsluv":[265.874320218178,87.2331192419333235,14.6896895275036599]},"#111188":{"lch":[16.9766940539391484,61.9476661879793511,265.874320218178],"luv":[16.9766940539391484,-4.45679305530888747,-61.7871373491236469],"rgb":[0.0666666666666666657,0.0666666666666666657,0.533333333333333326],"xyz":[0.0487506217428387,0.0229745692442707428,0.234798767191947366],"hpluv":[265.874320218178,463.03215765547759,16.9766940539391484],"hsluv":[265.874320218178,90.1871263608273495,16.9766940539391484]},"#111199":{"lch":[19.3069968916820471,72.0861980616198537,265.874320218178],"luv":[19.3069968916820471,-5.18620452834737478,-71.8993966147786],"rgb":[0.0666666666666666657,0.0666666666666666657,0.6],"xyz":[0.0618075858519114146,0.0281973548878999072,0.303565444833065246],"hpluv":[265.874320218178,473.779996738615694,19.3069968916820471],"hsluv":[265.874320218178,92.280537596895428,19.3069968916820471]},"#1111aa":{"lch":[21.6605350192491244,82.2093341223612839,265.874320218177957],"luv":[21.6605350192491244,-5.91450835752722703,-81.9962999636616274],"rgb":[0.0666666666666666657,0.0666666666666666657,0.66666666666666663],"xyz":[0.0768652788171639567,0.0342204320740010087,0.382869294450063846],"hpluv":[265.874320218177957,481.605322004481,21.6605350192491244],"hsluv":[265.874320218177957,93.8047159652847569,21.6605350192491244]},"#1111bb":{"lch":[24.0238472654082429,92.2838634174554215,265.874320218177957],"luv":[24.0238472654082429,-6.63931519789526092,-92.0447225046312241],"rgb":[0.0666666666666666657,0.0666666666666666657,0.733333333333333282],"xyz":[0.0940028709245006,0.0410754689169357673,0.473127279548705593],"hpluv":[265.874320218177957,487.441538809997496,24.0238472654082429],"hsluv":[265.874320218177957,94.9414655706975736,24.0238472654082429]},"#1111cc":{"lch":[26.3879200105999,102.289669569688542,265.874320218177957],"luv":[26.3879200105999,-7.35917778701558589,-102.024599989292597],"rgb":[0.0666666666666666657,0.0666666666666666657,0.8],"xyz":[0.113295246368222344,0.0487924190944245689,0.574733790218975749],"hpluv":[265.874320218177957,491.887677819884516,26.3879200105999],"hsluv":[265.874320218177957,95.8074626598259727,26.3879200105999]},"#1111dd":{"lch":[28.7467318202544035,112.215160110792439,265.874320218177957],"luv":[28.7467318202544035,-8.07326211070735766,-111.924369989661315],"rgb":[0.0666666666666666657,0.0666666666666666657,0.866666666666666696],"xyz":[0.134813571918855901,0.0573997493146781,0.688063638118981746],"hpluv":[265.874320218177957,495.338839734480189,28.7467318202544035],"hsluv":[265.874320218177957,96.479663003878315,28.7467318202544035]},"#1111ee":{"lch":[31.096282761883856,122.054240127147821,265.874320218177957],"luv":[31.096282761883856,-8.78112967353786367,-121.737953386246701],"rgb":[0.0666666666666666657,0.0666666666666666657,0.933333333333333348],"xyz":[0.158625752185030638,0.0669246214211481338,0.813474454187505],"hpluv":[265.874320218177957,498.062358817216193,31.096282761883856],"hsluv":[265.874320218177957,97.0101366558694451,31.096282761883856]},"#1111ff":{"lch":[33.4339475813396589,131.804336466263976,265.874320218177957],"luv":[33.4339475813396589,-9.48259535137154863,-131.462783694528071],"rgb":[0.0666666666666666657,0.0666666666666666657,1],"xyz":[0.184796800594862032,0.07739304078508083,0.951308642479286815],"hpluv":[265.874320218177957,500.243401112503761,33.4339475813396589],"hsluv":[265.874320218177957,99.9999999999995168,33.4339475813396589]},"#66aa00":{"lch":[62.9888010115071921,81.5107592316300185,114.758667910078074],"luv":[62.9888010115071921,-34.136471206054587,74.0182761493062884],"rgb":[0.4,0.66666666666666663,0],"xyz":[0.198534632170994735,0.315734905503604946,0.0504821063864305253],"hpluv":[114.758667910078074,164.206718875588678,62.9888010115071921],"hsluv":[114.758667910078074,100.000000000002245,62.9888010115071921]},"#66aa11":{"lch":[63.0225323172591345,80.1801511939703744,115.156040801232848],"luv":[63.0225323172591345,-34.0833758310267285,72.5753411114886262],"rgb":[0.4,0.66666666666666663,0.0666666666666666657],"xyz":[0.199546297670631867,0.316139571703459799,0.055810211351186155],"hpluv":[115.156040801232848,161.439702186697787,63.0225323172591345],"hsluv":[115.156040801232848,97.8915472494395,63.0225323172591345]},"#66aa22":{"lch":[63.0849851082382287,77.7526522520100372,115.919582231606014],"luv":[63.0849851082382287,-33.9864002990590066,69.9313915701248],"rgb":[0.4,0.66666666666666663,0.133333333333333331],"xyz":[0.201421655809108868,0.316889714958850599,0.0656870975471653545],"hpluv":[115.919582231606014,156.397041701241,63.0849851082382287],"hsluv":[115.919582231606014,94.0329763727763748,63.0849851082382287]},"#66aa33":{"lch":[63.1875983682703577,73.8659889058236416,117.258215410479664],"luv":[63.1875983682703577,-33.8307250746429062,65.6632801340230827],"rgb":[0.4,0.66666666666666663,0.2],"xyz":[0.20450940654156663,0.318124815251833726,0.0819492514047764908],"hpluv":[117.258215410479664,148.337854737344315,63.1875983682703577],"hsluv":[117.258215410479664,87.8175568215645228,63.1875983682703577]},"#66aa44":{"lch":[63.3352806074861121,68.495891054581449,119.389904478135335],"luv":[63.3352806074861121,-33.6143748396710436,59.6804900742209057],"rgb":[0.4,0.66666666666666663,0.266666666666666663],"xyz":[0.208967401785785489,0.319908013349521292,0.105428026357663129],"hpluv":[119.389904478135335,137.232870385557277,63.3352806074861121],"hsluv":[119.389904478135335,79.1339200396795093,63.3352806074861121]},"#66aa55":{"lch":[63.5319451116367162,61.7628687774086131,122.670093374403834],"luv":[63.5319451116367162,-33.3396585938880747,51.9915293529473],"rgb":[0.4,0.66666666666666663,0.333333333333333315],"xyz":[0.214929815825147474,0.322292978965266153,0.136830073631637045],"hpluv":[122.670093374403834,123.360077774302084,63.5319451116367162],"hsluv":[122.670093374403834,68.0252461175988401,63.5319451116367162]},"#66aa66":{"lch":[63.7807317293932101,53.9656100581627314,127.715012949238869],"luv":[63.7807317293932101,-33.0126168434810836,42.6902119706049703],"rgb":[0.4,0.66666666666666663,0.4],"xyz":[0.22251481159865566,0.325326977274669471,0.176777718038781],"hpluv":[127.715012949238869,107.366036241042551,63.7807317293932101],"hsluv":[127.715012949238869,54.6684489206366493,63.7807317293932101]},"#66aa77":{"lch":[64.0841229578331308,45.668717811445795,135.623736827110406],"luv":[64.0841229578331308,-32.6422859505805931,31.9392071670853355],"rgb":[0.4,0.66666666666666663,0.466666666666666674],"xyz":[0.231828824618857565,0.329052582482750267,0.225831519945178916],"hpluv":[135.623736827110406,90.428994130424627,64.0841229578331308],"hsluv":[135.623736827110406,56.3153951517164302,64.0841229578331308]},"#66aa88":{"lch":[64.4440140424290888,37.9133385548872752,148.25026899586868],"luv":[64.4440140424290888,-32.239785461804118,19.9503752785341248],"rgb":[0.4,0.66666666666666663,0.533333333333333326],"xyz":[0.242969241720801421,0.333508749323527864,0.284504383348751055],"hpluv":[148.25026899586868,74.65325904191441,64.4440140424290888],"hsluv":[148.25026899586868,58.1346686170252127,64.4440140424290888]},"#66aa99":{"lch":[64.861761772217136,32.5704605989434484,167.654678216446086],"luv":[64.861761772217136,-31.8173259789585323,6.96366793981576304],"rgb":[0.4,0.66666666666666663,0.6],"xyz":[0.256026205829874121,0.338731534967157,0.353271060989869],"hpluv":[167.654678216446086,63.7198120272805681,64.861761772217136],"hsluv":[167.654678216446086,60.083023100845935,64.861761772217136]},"#66aaaa":{"lch":[65.3382237636027,32.1097126044189949,192.177050630060876],"luv":[65.3382237636027,-31.387258852500235,-6.77300710648746396],"rgb":[0.4,0.66666666666666663,0.66666666666666663],"xyz":[0.271083898795126677,0.344754612153258144,0.432574910606867591],"hpluv":[192.177050630060876,62.3603323483304592,65.3382237636027],"hsluv":[192.177050630060876,62.1162357127798757,65.3382237636027]},"#66aabb":{"lch":[65.8737942906507641,37.4229485671739255,214.174175962111633],"luv":[65.8737942906507641,-30.9612713447490577,-21.020864781881972],"rgb":[0.4,0.66666666666666663,0.733333333333333282],"xyz":[0.288221490902463295,0.351609648996192903,0.522832895705509282],"hpluv":[214.174175962111633,72.0882777495769744,65.8737942906507641],"hsluv":[214.174175962111633,64.192083786910942,65.8737942906507641]},"#66aacc":{"lch":[66.4684397846860833,46.8829891397435361,229.336310284400895],"luv":[66.4684397846860833,-30.5497910563422188,-35.5629714322516],"rgb":[0.4,0.66666666666666663,0.8],"xyz":[0.30751386634618505,0.359326599173681704,0.624439406375779438],"hpluv":[229.336310284400895,89.5033179536005,66.4684397846860833],"hsluv":[229.336310284400895,66.2725553364081748,66.4684397846860833]},"#66aadd":{"lch":[67.1217354618507471,58.5746749070863473,239.007496148652194],"luv":[67.1217354618507471,-30.1616186688116876,-50.2122425285710676],"rgb":[0.4,0.66666666666666663,0.866666666666666696],"xyz":[0.329032191896818593,0.367933929393935211,0.737769254275785435],"hpluv":[239.007496148652194,110.735287258409116,67.1217354618507471],"hsluv":[239.007496148652194,68.3252248927570349,67.1217354618507471]},"#66aaee":{"lch":[67.8329035309127,71.3390505041132,245.305718817638592],"luv":[67.8329035309127,-29.803771094245274,-64.8150858627082584],"rgb":[0.4,0.66666666666666663,0.933333333333333348],"xyz":[0.352844372162993358,0.377458801500405228,0.863180070344308659],"hpluv":[245.305718817638592,133.452355648257935,67.8329035309127],"hsluv":[245.305718817638592,80.362890995181445,67.8329035309127]},"#66aaff":{"lch":[68.6008528128324144,84.5574659264264312,249.594848961048797],"luv":[68.6008528128324144,-29.4814940521041038,-79.2515397475312],"rgb":[0.4,0.66666666666666663,1],"xyz":[0.379015420572824724,0.387927220864337952,1.00101425863609061],"hpluv":[249.594848961048797,156.409011428865199,68.6008528128324144],"hsluv":[249.594848961048797,99.9999999999981,68.6008528128324144]},"#112200":{"lch":[11.0156269675282488,14.1286449823385087,113.920199516574741],"luv":[11.0156269675282488,-5.72865521459208082,12.9151507335100835],"rgb":[0.0666666666666666657,0.133333333333333331,0],"xyz":[0.00803163592779996757,0.0126319728938838038,0.00201503147454941628],"hpluv":[113.920199516574741,162.753605553330914,11.0156269675282488],"hsluv":[113.920199516574741,100.000000000002302,11.0156269675282488]},"#112211":{"lch":[11.3010826742418828,9.17677244733547,127.715012949238741],"luv":[11.3010826742418828,-5.61374683501564142,7.25940762201209555],"rgb":[0.0666666666666666657,0.133333333333333331,0.0666666666666666657],"xyz":[0.00904330142743709,0.0130366390937386584,0.00734313643930504731],"hpluv":[127.715012949238741,103.040803658029205,11.3010826742418828],"hsluv":[127.715012949238741,52.4661346244892783,11.3010826742418828]},"#112222":{"lch":[11.8149934741043623,5.60956124878379736,192.177050630060876],"luv":[11.8149934741043623,-5.48334870304322308,-1.18324317225625331],"rgb":[0.0666666666666666657,0.133333333333333331,0.133333333333333331],"xyz":[0.0109186595659141079,0.0137867823491294762,0.0172200226352842434],"hpluv":[192.177050630060876,60.2469040904941551,11.8149934741043623],"hsluv":[192.177050630060876,60.0110800331641911,11.8149934741043623]},"#112233":{"lch":[12.6219648570067733,12.7575995118883281,244.93155638428982],"luv":[12.6219648570067733,-5.40540262923445,-11.5558629154900672],"rgb":[0.0666666666666666657,0.133333333333333331,0.2],"xyz":[0.0140064102983718484,0.0150218826421125891,0.0334821764928953866],"hpluv":[244.93155638428982,128.257072990564865,12.6219648570067733],"hsluv":[244.93155638428982,68.2965554989448265,12.6219648570067733]},"#112244":{"lch":[13.7124312845167182,23.0561698733830092,256.354402060867073],"luv":[13.7124312845167182,-5.43930918870511082,-22.4053762472305387],"rgb":[0.0666666666666666657,0.133333333333333331,0.266666666666666663],"xyz":[0.0184644055425907255,0.0168050807398001656,0.0569609514457820251],"hpluv":[256.354402060867073,213.359519949101497,13.7124312845167182],"hsluv":[256.354402060867073,75.5965994738604508,13.7124312845167182]},"#112255":{"lch":[15.056320299603339,33.5848303856462849,260.40485184836416],"luv":[15.056320299603339,-5.59809589119781581,-33.114983835502386],"rgb":[0.0666666666666666657,0.133333333333333331,0.333333333333333315],"xyz":[0.024426819581952721,0.019190046355545,0.0883629987197559336],"hpluv":[260.40485184836416,283.050313811336878,15.056320299603339],"hsluv":[260.40485184836416,81.3424686793067,15.056320299603339]},"#112266":{"lch":[16.6136212231118279,43.9645077489691118,262.329755848243337],"luv":[16.6136212231118279,-5.86800935048734384,-43.5711419160865958],"rgb":[0.0666666666666666657,0.133333333333333331,0.4],"xyz":[0.0320118153554608958,0.02222404466494831,0.128310643126899909],"hpluv":[262.329755848243337,335.797328537363626,16.6136212231118279],"hsluv":[262.329755848243337,85.6428490187558396,16.6136212231118279]},"#112277":{"lch":[18.3427569840269769,54.1821774694947678,263.400149453671133],"luv":[18.3427569840269769,-6.22740469049246936,-53.8231157232341815],"rgb":[0.0666666666666666657,0.133333333333333331,0.466666666666666674],"xyz":[0.0413258283756628153,0.0259496498730291264,0.177364445033297818],"hpluv":[263.400149453671133,374.827260372181627,18.3427569840269769],"hsluv":[263.400149453671133,88.8099744527525559,18.3427569840269769]},"#112288":{"lch":[20.2056943122802366,64.2803592359499,264.056887374403459],"luv":[20.2056943122802366,-6.65565132438349316,-63.9348644242795103],"rgb":[0.0666666666666666657,0.133333333333333331,0.533333333333333326],"xyz":[0.0524662454776066434,0.0304058167138067173,0.236037308436869986],"hpluv":[264.056887374403459,403.686144257890874,20.2056943122802366],"hsluv":[264.056887374403459,91.1461001738222762,20.2056943122802366]},"#112299":{"lch":[22.17018380613613,74.2918952905243799,264.488199331991609],"luv":[22.17018380613613,-7.13579321215933149,-73.9484020185124677],"rgb":[0.0666666666666666657,0.133333333333333331,0.6],"xyz":[0.0655232095866793568,0.0356286023574358818,0.304803986077987865],"hpluv":[264.488199331991609,425.217833826531262,22.17018380613613],"hsluv":[264.488199331991609,92.8866294242426136,22.17018380613613]},"#1122aa":{"lch":[24.2101316823922517,84.2333021684004848,264.786067280823943],"luv":[24.2101316823922517,-7.6546801533155886,-83.8847725510625537],"rgb":[0.0666666666666666657,0.133333333333333331,0.66666666666666663],"xyz":[0.0805809025519319,0.0416516795435369833,0.384107835694986466],"hpluv":[264.786067280823943,441.495215422381477,24.2101316823922517],"hsluv":[264.786067280823943,94.2012423737010209,24.2101316823922517]},"#1122bb":{"lch":[26.3050242232650149,94.1101405136049891,264.999933480500431],"luv":[26.3050242232650149,-8.20234804116949334,-93.7520134935884926],"rgb":[0.0666666666666666657,0.133333333333333331,0.733333333333333282],"xyz":[0.0977184946592685455,0.0485067163864717418,0.474365820793628212],"hpluv":[264.999933480500431,453.98033637772636,26.3050242232650149],"hsluv":[264.999933480500431,95.2089691602506605,26.3050242232650149]},"#1122cc":{"lch":[28.4390102065576187,103.92243186966887,265.158337172634162],"luv":[28.4390102065576187,-8.77130520241401612,-103.551610565708117],"rgb":[0.0666666666666666657,0.133333333333333331,0.8],"xyz":[0.117010870102990286,0.0562236665639605435,0.575972331463898368],"hpluv":[265.158337172634162,463.69685512523705,28.4390102065576187],"hsluv":[265.158337172634162,95.9928942328389496,28.4390102065576187]},"#1122dd":{"lch":[30.599961250020371,113.668086087716972,265.278693166229971],"luv":[30.599961250020371,-9.35592092097037664,-113.282392888591403],"rgb":[0.0666666666666666657,0.133333333333333331,0.866666666666666696],"xyz":[0.138529195653623816,0.0648309967842140705,0.689302179363904366],"hpluv":[265.278693166229971,471.364637015548908,30.599961250020371],"hsluv":[265.278693166229971,96.6113336559982,30.599961250020371]},"#1122ee":{"lch":[32.7786459144851463,123.344693870669801,265.37211393361406],"luv":[32.7786459144851463,-9.95194954038693247,-122.942556531067964],"rgb":[0.0666666666666666657,0.133333333333333331,0.933333333333333348],"xyz":[0.162341375919798553,0.0743558688906841,0.814712995432427589],"hpluv":[265.37211393361406,477.494956838131543,32.7786459144851463],"hsluv":[265.37211393361406,97.1056530386567118,32.7786459144851463]},"#1122ff":{"lch":[34.9680553815075,132.950321242967135,265.445956583698],"luv":[34.9680553815075,-10.5561742154436597,-132.530581770930752],"rgb":[0.0666666666666666657,0.133333333333333331,1],"xyz":[0.188512424329629946,0.0848242882546168114,0.952547183724209434],"hpluv":[265.445956583698,482.455471693424897,34.9680553815075],"hsluv":[265.445956583698,99.9999999999995595,34.9680553815075]},"#66bb00":{"lch":[68.2883247343563,91.0397787832552297,117.384795663234385],"luv":[68.2883247343563,-41.875036539852637,80.8376313092488914],"rgb":[0.4,0.733333333333333282,0],"xyz":[0.232489130079593515,0.383643901320803504,0.0618002723559631373],"hpluv":[117.384795663234385,169.170277477244,68.2883247343563],"hsluv":[117.384795663234385,100.000000000002402,68.2883247343563]},"#66bb11":{"lch":[68.3179499939719079,89.8644626985519466,117.727722441428654],"luv":[68.3179499939719079,-41.8112733484955896,79.5452014710932644],"rgb":[0.4,0.733333333333333282,0.0666666666666666657],"xyz":[0.233500795579230647,0.384048567520658357,0.0671283773207187739],"hpluv":[117.727722441428654,166.91389144855998,68.3179499939719079],"hsluv":[117.727722441428654,98.2613130840239108,68.3179499939719079]},"#66bb22":{"lch":[68.3728123300379309,87.7153655991844374,118.381366327960222],"luv":[68.3728123300379309,-41.6944557120635579,77.1722600749346839],"rgb":[0.4,0.733333333333333282,0.133333333333333331],"xyz":[0.235376153717707648,0.384798710776049158,0.0770052635166979665],"hpluv":[118.381366327960222,162.791438416492724,68.3728123300379309],"hsluv":[118.381366327960222,95.072259168186676,68.3728123300379309]},"#66bb33":{"lch":[68.4629872592027766,84.2604886941914515,119.511012469656151],"luv":[68.4629872592027766,-41.5059446288263274,73.3286200296498691],"rgb":[0.4,0.733333333333333282,0.2],"xyz":[0.238463904450165409,0.386033811069032284,0.0932674173743091],"hpluv":[119.511012469656151,156.173540237969377,68.4629872592027766],"hsluv":[119.511012469656151,89.9157079277594846,68.4629872592027766]},"#66bb44":{"lch":[68.5928402387875451,79.4555052917154683,121.268984442668796],"luv":[68.5928402387875451,-41.2418962487648457,67.9137932600435761],"rgb":[0.4,0.733333333333333282,0.266666666666666663],"xyz":[0.242921899694384269,0.38781700916671985,0.116746192327195741],"hpluv":[121.268984442668796,146.988898097842537,68.5928402387875451],"hsluv":[121.268984442668796,82.6706505658268,68.5928402387875451]},"#66bb55":{"lch":[68.7658933773114711,73.3682686647690474,123.883282583110088],"luv":[68.7658933773114711,-40.9030232283392365,60.9085013577552417],"rgb":[0.4,0.733333333333333282,0.333333333333333315],"xyz":[0.248884313733746254,0.390201974782464711,0.148148239601169657],"hpluv":[123.883282583110088,135.386233996967292,68.7658933773114711],"hsluv":[123.883282583110088,73.3323147762666565,68.7658933773114711]},"#66bb66":{"lch":[68.985024233854773,66.1955602149500493,127.715012949239338],"luv":[68.985024233854773,-40.4940973290304953,52.3648763359380354],"rgb":[0.4,0.733333333333333282,0.4],"xyz":[0.256469309507254439,0.39323597309186803,0.188095884008313619],"hpluv":[127.715012949239338,121.76244699215377,68.985024233854773],"hsluv":[127.715012949239338,61.9987879490855889,68.985024233854773]},"#66bb77":{"lch":[69.2525700917713,58.3030977404443149,133.351332140289912],"luv":[69.2525700917713,-40.0233333378199134,42.395565742911792],"rgb":[0.4,0.733333333333333282,0.466666666666666674],"xyz":[0.265783322527456345,0.396961578299948825,0.237149685914711528],"hpluv":[133.351332140289912,106.830450835327468,69.2525700917713],"hsluv":[133.351332140289912,63.1630001095548,69.2525700917713]},"#66bb88":{"lch":[69.5703900447947632,50.3125315521488901,141.732198444264128],"luv":[69.5703900447947632,-39.5016033478381487,31.1604583428427624],"rgb":[0.4,0.733333333333333282,0.533333333333333326],"xyz":[0.276923739629400201,0.401417745140726423,0.295822549318283667],"hpluv":[141.732198444264128,91.7679559652102625,69.5703900447947632],"hsluv":[141.732198444264128,64.4651291783298888,69.5703900447947632]},"#66bb99":{"lch":[69.9399065468101924,43.264635747530761,154.168279853010262],"luv":[69.9399065468101924,-38.9415329854338594,18.8516767188195544],"rgb":[0.4,0.733333333333333282,0.6],"xyz":[0.2899807037384729,0.406640530784355581,0.364589226959401602],"hpluv":[154.168279853010262,78.4959645714096865,69.9399065468101924],"hsluv":[154.168279853010262,65.8787579381207848,69.9399065468101924]},"#66bbaa":{"lch":[70.3621368583926881,38.7745898249838348,171.579209782291855],"luv":[70.3621368583926881,-38.3565710720190296,5.67822786553099768],"rgb":[0.4,0.733333333333333282,0.66666666666666663],"xyz":[0.305038396703725456,0.412663607970456703,0.443893076576400203],"hpluv":[171.579209782291855,69.9274221790226278,70.3621368583926881],"hsluv":[171.579209782291855,67.37547943529961,70.3621368583926881]},"#66bbbb":{"lch":[70.8377198879813221,38.6292683935386592,192.177050630060933],"luv":[70.8377198879813221,-37.7601276376370194,-8.14819841495072161],"rgb":[0.4,0.733333333333333282,0.733333333333333282],"xyz":[0.322175988811062075,0.419518644813391461,0.534151061675042],"hpluv":[192.177050630060933,69.1976324805594629,70.8377198879813221],"hsluv":[192.177050630060933,68.9267726464228758,70.8377198879813221]},"#66bbcc":{"lch":[71.3669414392261103,43.4063518114314,211.106579982723787],"luv":[71.3669414392261103,-37.1648552128157,-22.4250956427882215],"rgb":[0.4,0.733333333333333282,0.8],"xyz":[0.341468364254783829,0.427235594990880263,0.635757572345312161],"hpluv":[211.106579982723787,77.1783568223693806,71.3669414392261103],"hsluv":[211.106579982723787,70.5055674968506736,71.3669414392261103]},"#66bbdd":{"lch":[71.9497594848613602,52.0084901368895629,225.300588521911806],"luv":[71.9497594848613602,-36.5821167433876298,-36.9679831866995059],"rgb":[0.4,0.733333333333333282,0.866666666666666696],"xyz":[0.362986689805417373,0.435842925211133769,0.749087420245318159],"hpluv":[225.300588521911806,91.724261948179759,71.9497594848613602],"hsluv":[225.300588521911806,72.0874065633031336,71.9497594848613602]},"#66bbee":{"lch":[72.5858302461357852,62.9426478584387183,235.089705553804919],"luv":[72.5858302461357852,-36.0216507769597953,-51.6160594653851135],"rgb":[0.4,0.733333333333333282,0.933333333333333348],"xyz":[0.386798870071592082,0.445367797317603786,0.874498236313841382],"hpluv":[235.089705553804919,110.035415213757233,72.5858302461357852],"hsluv":[235.089705553804919,76.908955535231712,72.5858302461357852]},"#66bbff":{"lch":[73.2745353232689638,75.1445669264803513,241.815748730615525],"luv":[73.2745353232689638,-35.4914181095182641,-66.2349241650025817],"rgb":[0.4,0.733333333333333282,1],"xyz":[0.412969918481423504,0.45583621668153651,1.01233242460562312],"hpluv":[241.815748730615525,130.131920555189879,73.2745353232689638],"hsluv":[241.815748730615525,99.9999999999976552,73.2745353232689638]},"#113300":{"lch":[17.8585390793191152,25.0449080182821966,121.332554648991049],"luv":[17.8585390793191152,-13.0234653569097247,21.3924465113644295],"rgb":[0.0666666666666666657,0.2,0],"xyz":[0.0141493580168107792,0.0248674170719056023,0.00405427217088629669],"hpluv":[121.332554648991049,177.956083469309505,17.8585390793191152],"hsluv":[121.332554648991049,100.000000000002288,17.8585390793191152]},"#113311":{"lch":[18.041211184449395,20.8015074137336562,127.715012949239792],"luv":[18.041211184449395,-12.7249964056056086,16.4553084796651738],"rgb":[0.0666666666666666657,0.2,0.0666666666666666657],"xyz":[0.015161023516447901,0.0252720832717604552,0.00938237713564192902],"hpluv":[127.715012949239792,146.308124837666583,18.041211184449395],"hsluv":[127.715012949239792,74.4969128915689254,18.041211184449395]},"#113322":{"lch":[18.3747440863758129,14.8635488733567129,145.575764327225926],"luv":[18.3747440863758129,-12.2605616690393529,8.40260154180516672],"rgb":[0.0666666666666666657,0.2,0.133333333333333331],"xyz":[0.0170363816549249196,0.0260222265271512765,0.0192592633316211251],"hpluv":[145.575764327225926,102.645648490479701,18.3747440863758129],"hsluv":[145.575764327225926,76.8412554017051,18.3747440863758129]},"#113333":{"lch":[18.910205854271,11.9516732098830207,192.177050630061075],"luv":[18.910205854271,-11.6827660646039035,-2.5210056714682163],"rgb":[0.0666666666666666657,0.2,0.2],"xyz":[0.0201241323873826601,0.0272573268201343893,0.0355214171892322683],"hpluv":[192.177050630061075,80.19952200231,18.910205854271],"hsluv":[192.177050630061075,79.885597544945341,18.910205854271]},"#113344":{"lch":[19.6554681695294136,18.5554908550806559,233.185939638237187],"luv":[19.6554681695294136,-11.1188227079461566,-14.8552355236207116],"rgb":[0.0666666666666666657,0.2,0.266666666666666663],"xyz":[0.0245821276316015372,0.0290405249178219624,0.0590001921421189068],"hpluv":[233.185939638237187,119.79215597403514,19.6554681695294136],"hsluv":[233.185939638237187,83.0941728600946163,19.6554681695294136]},"#113355":{"lch":[20.6059777210847557,29.4541985213363553,248.778672986371305],"luv":[20.6059777210847557,-10.6615827984222822,-27.4568837045772334],"rgb":[0.0666666666666666657,0.2,0.333333333333333315],"xyz":[0.0305445416709635327,0.0314254905335667953,0.0904022394160928222],"hpluv":[248.778672986371305,181.381634101163,20.6059777210847557],"hsluv":[248.778672986371305,86.0667890851332089,20.6059777210847557]},"#113366":{"lch":[21.7480278014825927,41.0513772817531262,255.392805777322508],"luv":[21.7480278014825927,-10.3527823296753354,-39.7244946445288178],"rgb":[0.0666666666666666657,0.2,0.4],"xyz":[0.0381295374444717075,0.0344594888429701068,0.130349883823236784],"hpluv":[255.392805777322508,239.522976201191227,21.7480278014825927],"hsluv":[255.392805777322508,88.6137215649990537,21.7480278014825927]},"#113377":{"lch":[23.0621320749224097,52.4639032010768602,258.793139915612699],"luv":[23.0621320749224097,-10.1964540550792755,-51.4635158514711293],"rgb":[0.0666666666666666657,0.2,0.466666666666666674],"xyz":[0.04744355046467362,0.0381850940510509232,0.179403685729634693],"hpluv":[258.793139915612699,288.669213908808,23.0621320749224097],"hsluv":[258.793139915612699,90.7010140503724926,23.0621320749224097]},"#113388":{"lch":[24.5260866455155693,63.5079254673071389,260.778240759541745],"luv":[24.5260866455155693,-10.1775299952298717,-62.6871157444433322],"rgb":[0.0666666666666666657,0.2,0.533333333333333326],"xyz":[0.0585839675666174481,0.0426412608918285141,0.238076549133206861],"hpluv":[260.778240759541745,328.5783880762134,24.5260866455155693],"hsluv":[260.778240759541745,92.3732755032704,24.5260866455155693]},"#113399":{"lch":[26.1173586094444445,74.1961224571638667,262.040130643645512],"luv":[26.1173586094444445,-10.2746398160742789,-73.4812653968914447],"rgb":[0.0666666666666666657,0.2,0.6],"xyz":[0.0716409316756901615,0.0478640465354576786,0.30684322677432474],"hpluv":[262.040130643645512,360.488391101723778,26.1173586094444445],"hsluv":[262.040130643645512,93.700931793532277,26.1173586094444445]},"#1133aa":{"lch":[27.8146812937100378,84.589542856674214,262.892366774415223],"luv":[27.8146812937100378,-10.4665753413372666,-83.9395113240792767],"rgb":[0.0666666666666666657,0.2,0.66666666666666663],"xyz":[0.0866986246409427,0.0538871237215587801,0.386147076391323341],"hpluv":[262.892366774415223,385.906342473210088,27.8146812937100378],"hsluv":[262.892366774415223,94.7540719156047544,27.8146812937100378]},"#1133bb":{"lch":[29.5989386642012917,94.7497677250031245,263.494633441352278],"luv":[29.5989386642012917,-10.734795769033445,-94.1396974912241],"rgb":[0.0666666666666666657,0.2,0.733333333333333282],"xyz":[0.10383621674827935,0.0607421605644935386,0.476405061489965087],"hpluv":[263.494633441352278,406.201330660618282,29.5989386642012917],"hsluv":[263.494633441352278,95.59270717643426,29.5989386642012917]},"#1133cc":{"lch":[31.4535171675710927,104.725970292241058,263.935552719695806],"luv":[31.4535171675710927,-11.0639900580249932,-104.139891384844844],"rgb":[0.0666666666666666657,0.2,0.8],"xyz":[0.123128592192001091,0.0684591107419823403,0.578011572160235243],"hpluv":[263.935552719695806,422.497850271701509,31.4535171675710927],"hsluv":[263.935552719695806,96.2648935085394299,31.4535171675710927]},"#1133dd":{"lch":[33.3643121953656845,114.554023382916327,264.267643395871801],"luv":[33.3643121953656845,-11.4418472625073555,-113.981175658242762],"rgb":[0.0666666666666666657,0.2,0.866666666666666696],"xyz":[0.144646917742634634,0.0770664409622358743,0.691341420060241241],"hpluv":[264.267643395871801,435.679872957086786,33.3643121953656845],"hsluv":[264.267643395871801,96.8079128297289913,33.3643121953656845]},"#1133ee":{"lch":[35.3195411096734375,124.259061611723823,264.523680461367292],"luv":[35.3195411096734375,-11.8585821660500468,-123.691909281234786],"rgb":[0.0666666666666666657,0.2,0.933333333333333348],"xyz":[0.168459098008809371,0.0865913130687059,0.816752236128764464],"hpluv":[264.523680461367292,446.428943593210363,35.3195411096734375],"hsluv":[264.523680461367292,97.2502875158067752,35.3195411096734375]},"#1133ff":{"lch":[37.3094684856901466,133.858437412957159,264.724991571549936],"luv":[37.3094684856901466,-12.3064438760062309,-133.291532760957],"rgb":[0.0666666666666666657,0.2,1],"xyz":[0.194630146418640765,0.0970597324326386,0.954586424420546309],"hpluv":[264.724991571549936,455.266836240216,37.3094684856901466],"hsluv":[264.724991571549936,99.9999999999995168,37.3094684856901466]},"#66cc00":{"lch":[73.5514640948473328,100.417876322708906,119.311479215942285],"luv":[73.5514640948473328,-49.1602904566029579,87.5614968315714322],"rgb":[0.4,0.8,0],"xyz":[0.270712873389210462,0.460091387940038399,0.0745415201258350923],"hpluv":[119.311479215942285,173.244332851636557,73.5514640948473328],"hsluv":[119.311479215942285,100.00000000000226,73.5514640948473328]},"#66cc11":{"lch":[73.5777109322390572,99.3703085818509,119.605548775623987],"luv":[73.5777109322390572,-49.0915230219986611,86.397225621155485],"rgb":[0.4,0.8,0.0666666666666666657],"xyz":[0.271724538888847567,0.460496054139893252,0.079869625090590729],"hpluv":[119.605548775623987,171.375877908330068,73.5777109322390572],"hsluv":[119.605548775623987,98.5479766016144652,73.5777109322390572]},"#66cc22":{"lch":[73.626324929263788,97.4510710028789333,120.162888823436575],"luv":[73.626324929263788,-48.965269014871545,84.2562381661406192],"rgb":[0.4,0.8,0.133333333333333331],"xyz":[0.273599897027324623,0.461246197395284052,0.0897465112865699216],"hpluv":[120.162888823436575,167.954954584373922,73.626324929263788],"hsluv":[120.162888823436575,95.8800576342850377,73.626324929263788]},"#66cc33":{"lch":[73.7062524827218084,94.3550745227874,121.116489614327122],"luv":[73.7062524827218084,-48.7607908304490252,80.779114663321],"rgb":[0.4,0.8,0.2],"xyz":[0.276687647759782329,0.462481297688267179,0.106008665144181058],"hpluv":[121.116489614327122,162.442721914772306,73.7062524827218084],"hsluv":[121.116489614327122,91.5533619475712896,73.7062524827218084]},"#66cc44":{"lch":[73.8213986639243,90.0253172969507318,122.577193203242018],"luv":[73.8213986639243,-48.472817822786439,75.8613451418811451],"rgb":[0.4,0.8,0.266666666666666663],"xyz":[0.281145643004001244,0.464264495785954745,0.129487440097067696],"hpluv":[122.577193203242018,154.746814564283426,73.8213986639243],"hsluv":[122.577193203242018,85.4474048693987527,73.8213986639243]},"#66cc55":{"lch":[73.974942724102462,84.4934421806047879,124.700104619680829],"luv":[73.974942724102462,-48.100513339115345,69.4656921655632118],"rgb":[0.4,0.8,0.333333333333333315],"xyz":[0.28710805704336323,0.466649461401699606,0.160889487371041612],"hpluv":[124.700104619680829,144.936474170198323,73.974942724102462],"hsluv":[124.700104619680829,77.5306238992907,73.974942724102462]},"#66cc66":{"lch":[74.1695172801941567,77.8884466842553564,127.715012949239551],"luv":[74.1695172801941567,-47.6470375142611218,61.6146893443493937],"rgb":[0.4,0.8,0.4],"xyz":[0.294693052816871415,0.469683459711102924,0.200837131778185574],"hpluv":[127.715012949239551,133.256044234036153,74.1695172801941567],"hsluv":[127.715012949239551,67.8510775159793,74.1695172801941567]},"#66cc77":{"lch":[74.407302734023375,70.4570986939790203,131.971622948486811],"luv":[74.407302734023375,-47.1190629819757234,52.3831715350808551],"rgb":[0.4,0.8,0.466666666666666674],"xyz":[0.30400706583707332,0.47340906491918372,0.249890933684583483],"hpluv":[131.971622948486811,120.156846155807528,74.407302734023375],"hsluv":[131.971622948486811,68.6882673601907499,74.407302734023375]},"#66cc88":{"lch":[74.6900832163393602,62.6050444414555045,138.002106903522986],"luv":[74.6900832163393602,-46.5261552249599859,41.8892404980031543],"rgb":[0.4,0.8,0.533333333333333326],"xyz":[0.315147482939017121,0.477865231759961318,0.308563797088155622],"hpluv":[138.002106903522986,106.361808066007058,74.6900832163393602],"hsluv":[138.002106903522986,69.6340873295455367,74.6900832163393602]},"#66cc99":{"lch":[75.0192831982140405,54.9734389641357382,146.572693391746668],"luv":[75.0192831982140405,-45.8800303566060634,30.2836887782922481],"rgb":[0.4,0.8,0.6],"xyz":[0.32820444704808982,0.483088017403590475,0.377330474729273557],"hpluv":[146.572693391746668,92.9863760296957338,75.0192831982140405],"hsluv":[146.572693391746668,70.6723780843603,75.0192831982140405]},"#66ccaa":{"lch":[75.3959940236094,48.5503126646653,158.569930493367366],"luv":[75.3959940236094,-45.1937477629490587,17.7386026218441692],"rgb":[0.4,0.8,0.66666666666666663],"xyz":[0.343262140013342376,0.489111094589691597,0.456634324346272158],"hpluv":[158.569930493367366,81.7114817988577187,75.3959940236094],"hsluv":[158.569930493367366,71.7849539596152368,75.3959940236094]},"#66ccbb":{"lch":[75.8209952994642,44.7015709258060667,174.304687468137473],"luv":[75.8209952994642,-44.4809102105153755,4.43610979112772],"rgb":[0.4,0.8,0.733333333333333282],"xyz":[0.360399732120679051,0.495966131432626356,0.546892309444913849],"hpluv":[174.304687468137473,74.8122352806046109,75.8209952994642],"hsluv":[174.304687468137473,72.9527506991265113,75.8209952994642]},"#66cccc":{"lch":[76.2947739303160262,44.7620652034609634,192.177050630061018],"luv":[76.2947739303160262,-43.7549393425641924,-9.44180938207391],"rgb":[0.4,0.8,0.8],"xyz":[0.379692107564400749,0.503683081610115102,0.648498820115184],"hpluv":[192.177050630061018,75.6461904464395,76.2947739303160262],"hsluv":[192.177050630061018,74.1568646821725679,76.2947739303160262]},"#66ccdd":{"lch":[76.8175423984245782,49.1342213212598082,208.868111129197587],"luv":[76.8175423984245782,-43.0284772118039101,-23.7217590764220851],"rgb":[0.4,0.8,0.866666666666666696],"xyz":[0.401210433115034348,0.512290411830368719,0.76182866801519],"hpluv":[208.868111129197587,85.3113130970085791,76.8175423984245782],"hsluv":[208.868111129197587,75.3794120212671714,76.8175423984245782]},"#66ccee":{"lch":[77.3892571801628435,57.0358051623713038,222.109360872917676],"luv":[77.3892571801628435,-42.312941633688304,-38.245235531031426],"rgb":[0.4,0.8,0.933333333333333348],"xyz":[0.425022613381209058,0.521815283936838736,0.887239484083713226],"hpluv":[222.109360872917676,102.061631830010029,77.3892571801628435],"hsluv":[222.109360872917676,76.6041682601332923,77.3892571801628435]},"#66ccff":{"lch":[78.0096377377628158,67.2874731922355664,231.792303901557148],"luv":[78.0096377377628158,-41.6182406850010267,-52.872734853430309],"rgb":[0.4,0.8,1],"xyz":[0.451193661791040479,0.53228370330077146,1.02507367237549518],"hpluv":[231.792303901557148,124.498094909356865,78.0096377377628158],"hsluv":[231.792303901557148,99.9999999999966604,78.0096377377628158]},"#114400":{"lch":[24.4916204196936391,35.767443133059956,124.131260038140155],"luv":[24.4916204196936391,-20.0687794552527912,29.6066559991685239],"rgb":[0.0666666666666666657,0.266666666666666663,0],"xyz":[0.0229819284997768124,0.0425325580378379114,0.00699846233187489172],"hpluv":[124.131260038140155,185.314627891622,24.4916204196936391],"hsluv":[124.131260038140155,100.000000000002416,24.4916204196936391]},"#114411":{"lch":[24.6196313539200702,32.2821952767626144,127.715012949240105],"luv":[24.6196313539200702,-19.7481274165088401,25.5372589646650674],"rgb":[0.0666666666666666657,0.266666666666666663,0.0666666666666666657],"xyz":[0.0239935939994139341,0.0429372242376927643,0.0123265672966305223],"hpluv":[127.715012949240105,166.387555424049935,24.6196313539200702],"hsluv":[127.715012949240105,84.7209219338948,24.6196313539200702]},"#114422":{"lch":[24.8548180969752792,26.7199561105771828,135.968420687644709],"luv":[24.8548180969752792,-19.2104947057918238,18.5718321042894203],"rgb":[0.0666666666666666657,0.266666666666666663,0.133333333333333331],"xyz":[0.0258689521378909527,0.0436873674930835856,0.0222034534926097184],"hpluv":[135.968420687644709,136.415738329938534,24.8548180969752792],"hsluv":[135.968420687644709,85.5945702875542338,24.8548180969752792]},"#114433":{"lch":[25.2362525898650887,20.3101465248311648,155.348193219538018],"luv":[25.2362525898650887,-18.4590663004995292,8.4714180143512],"rgb":[0.0666666666666666657,0.266666666666666663,0.2],"xyz":[0.0289567028703486933,0.0449224677860667,0.0384656073502208651],"hpluv":[155.348193219538018,102.123929581721498,25.2362525898650887],"hsluv":[155.348193219538018,86.8340768870874911,25.2362525898650887]},"#114444":{"lch":[25.774812755707849,18.0038630185053101,192.177050630061132],"luv":[25.774812755707849,-17.5987843886529483,-3.79761393914762779],"rgb":[0.0666666666666666657,0.266666666666666663,0.266666666666666663],"xyz":[0.0334146981145675703,0.0467056658837542715,0.0619443823031075036],"hpluv":[192.177050630061132,88.6358691141452226,25.774812755707849],"hsluv":[192.177050630061132,88.2889223192016743,25.774812755707849]},"#114455":{"lch":[26.4741010086829718,23.9697809957076196,225.699525295295985],"luv":[26.4741010086829718,-16.7410035671662349,-17.1548593857925162],"rgb":[0.0666666666666666657,0.266666666666666663,0.333333333333333315],"xyz":[0.0393771121539295693,0.0490906314994991044,0.0933464295770814],"hpluv":[225.699525295295985,114.889984549549212,26.4741010086829718],"hsluv":[225.699525295295985,89.7968555301121398,26.4741010086829718]},"#114466":{"lch":[27.3316922889079734,34.6428172107851182,242.548035964380944],"luv":[27.3316922889079734,-15.9705048052882308,-30.7419544037807526],"rgb":[0.0666666666666666657,0.266666666666666663,0.4],"xyz":[0.0469621079274377407,0.052124629808902416,0.133294073984225381],"hpluv":[242.548035964380944,160.837015687659374,27.3316922889079734],"hsluv":[242.548035964380944,91.2329185483074525,27.3316922889079734]},"#114477":{"lch":[28.3404051997208484,46.6267191674415,250.798290317077829],"luv":[28.3404051997208484,-15.3352867062553546,-44.0327142242719844],"rgb":[0.0666666666666666657,0.266666666666666663,0.466666666666666674],"xyz":[0.0562761209476396601,0.0558502350169832323,0.18234787589062329],"hpluv":[250.798290317077829,208.770018220734585,28.3404051997208484],"hsluv":[250.798290317077829,92.5248535524246734,28.3404051997208484]},"#114488":{"lch":[29.4896359978219706,58.6954673108201632,255.342505689020953],"luv":[29.4896359978219706,-14.8523182742844106,-56.7852667926718055],"rgb":[0.0666666666666666657,0.266666666666666663,0.533333333333333326],"xyz":[0.0674165380495834882,0.0603064018577608302,0.241020739294195457],"hpluv":[255.342505689020953,252.565763352450517,29.4896359978219706],"hsluv":[255.342505689020953,93.6449802292742817,29.4896359978219706]},"#114499":{"lch":[30.7666487879374699,70.4540899928233699,258.107815399211404],"luv":[30.7666487879374699,-14.5185243824624628,-68.9419411278265102],"rgb":[0.0666666666666666657,0.266666666666666663,0.6],"xyz":[0.0804735021586562,0.0655291875013899877,0.309787416935313364],"hpluv":[258.107815399211404,290.579747717257874,30.7666487879374699],"hsluv":[258.107815399211404,94.5943513350065359,30.7666487879374699]},"#1144aa":{"lch":[32.1577052090601185,81.8040240174920541,259.917940759024589],"luv":[32.1577052090601185,-14.3204852090806973,-80.5408098347099894],"rgb":[0.0666666666666666657,0.266666666666666663,0.66666666666666663],"xyz":[0.0955311951239087437,0.0715522646874911,0.389091266552311965],"hpluv":[259.917940759024589,322.796594575352628,32.1577052090601185],"hsluv":[259.917940759024589,95.3887741756132925,32.1577052090601185]},"#1144bb":{"lch":[33.6489603009756664,92.7573184463875435,261.168522985608377],"luv":[33.6489603009756664,-14.2409127099882671,-91.6576048702508928],"rgb":[0.0666666666666666657,0.266666666666666663,0.733333333333333282],"xyz":[0.11266878723124539,0.0784073015304258547,0.479349251650953712],"hpluv":[261.168522985608377,349.796823816496214,33.6489603009756664],"hsluv":[261.168522985608377,96.0495339612535872,33.6489603009756664]},"#1144cc":{"lch":[35.2271045850644597,103.362934076908545,262.068884255867147],"luv":[35.2271045850644597,-14.262270250582695,-102.374234005860515],"rgb":[0.0666666666666666657,0.266666666666666663,0.8],"xyz":[0.131961162674967131,0.0861242517079146563,0.580955762321223812],"hpluv":[262.068884255867147,372.329293739207799,35.2271045850644597],"hsluv":[262.068884255867147,96.5982694251149638,35.2271045850644597]},"#1144dd":{"lch":[36.8797734412618,113.676357292171801,262.73849416957313],"luv":[36.8797734412618,-14.3684841809298494,-112.764625967365617],"rgb":[0.0666666666666666657,0.266666666666666663,0.866666666666666696],"xyz":[0.153479488225600674,0.0947315819281681903,0.69428561022122981],"hpluv":[262.73849416957313,391.13009227787353,36.8797734412618],"hsluv":[262.73849416957313,97.0546019825808,36.8797734412618]},"#1144ee":{"lch":[38.5957670998368911,123.747928777943088,263.249741523271325],"luv":[38.5957670998368911,-14.5455638799169762,-122.890099065165245],"rgb":[0.0666666666666666657,0.266666666666666663,0.933333333333333348],"xyz":[0.177291668491775412,0.104256454034638207,0.819696426289753],"hpluv":[263.249741523271325,406.853061330967478,38.5957670998368911],"hsluv":[263.249741523271325,97.4353215147193623,38.5957670998368911]},"#1144ff":{"lch":[40.3651306844127546,133.619394536005728,263.648645306126298],"luv":[40.3651306844127546,-14.7816672391382209,-132.799265471613751],"rgb":[0.0666666666666666657,0.266666666666666663,1],"xyz":[0.203462716901606805,0.114724873398570917,0.957530614581534878],"hpluv":[263.648645306126298,420.051425771921231,40.3651306844127546],"hsluv":[263.648645306126298,99.9999999999994458,40.3651306844127546]},"#66dd00":{"lch":[78.7732081443282084,109.616469768408933,120.762072840728067],"luv":[78.7732081443282084,-56.0659919017112145,94.1932853050883],"rgb":[0.4,0.866666666666666696,0],"xyz":[0.313346863936385611,0.54535936903439,0.0887528503082264109],"hpluv":[120.762072840728067,211.559351010719155,78.7732081443282084],"hsluv":[120.762072840728067,100.000000000002444,78.7732081443282084]},"#66dd11":{"lch":[78.7966434753788576,108.675393583239966,121.014553037765893],"luv":[78.7966434753788576,-55.9956244091473678,93.1387739746547],"rgb":[0.4,0.866666666666666696,0.0666666666666666657],"xyz":[0.314358529436022716,0.545764035234244882,0.0940809552729820475],"hpluv":[121.014553037765893,210.019013530063148,78.7966434753788576],"hsluv":[121.014553037765893,98.7735218244661866,78.7966434753788576]},"#66dd22":{"lch":[78.8400557089289435,106.948443645027297,121.491090191754736],"luv":[78.8400557089289435,-55.866227309203758,91.1972271745690506],"rgb":[0.4,0.866666666666666696,0.133333333333333331],"xyz":[0.316233887574499772,0.546514178489635682,0.10395784146896124],"hpluv":[121.491090191754736,207.186246213049145,78.8400557089289435],"hsluv":[121.491090191754736,96.5169147905729,78.8400557089289435]},"#66dd33":{"lch":[78.911446872650572,104.154611952746194,122.300539429107701],"luv":[78.911446872650572,-55.6560904548950788,88.037394249850891],"rgb":[0.4,0.866666666666666696,0.2],"xyz":[0.319321638306957478,0.547749278782618809,0.120219995326572376],"hpluv":[122.300539429107701,202.586476802368111,78.911446872650572],"hsluv":[122.300539429107701,92.8487034899531807,78.911446872650572]},"#66dd44":{"lch":[79.0143300648071687,100.229492569308348,123.526451276321851],"luv":[79.0143300648071687,-55.3589437481663,83.5544045983716899],"rgb":[0.4,0.866666666666666696,0.266666666666666663],"xyz":[0.323779633551176393,0.549532476880306375,0.143698770279459015],"hpluv":[123.526451276321851,196.088207898644384,79.0143300648071687],"hsluv":[123.526451276321851,87.6539206040991843,79.0143300648071687]},"#66dd55":{"lch":[79.1515854502963,95.1800529291645603,125.27945296022996],"luv":[79.1515854502963,-54.9726586351532518,77.699737954393882],"rgb":[0.4,0.866666666666666696,0.333333333333333315],"xyz":[0.329742047590538379,0.551917442496051125,0.17510081755343293],"hpluv":[125.27945296022996,187.665730733793509,79.1515854502963],"hsluv":[125.27945296022996,80.8868034505971707,79.1515854502963]},"#66dd66":{"lch":[79.3256225689869723,89.0890908185611607,127.715012949239735],"luv":[79.3256225689869723,-54.4988561596450722,70.4751075215082921],"rgb":[0.4,0.866666666666666696,0.4],"xyz":[0.337327043364046564,0.554951440805454443,0.215048461960576892],"hpluv":[127.715012949239735,177.410630109117562,79.3256225689869723],"hsluv":[127.715012949239735,72.5638298210149,79.3256225689869723]},"#66dd77":{"lch":[79.538466322575843,82.1265081512392072,131.057978326052336],"luv":[79.538466322575843,-53.9425296262626048,61.9271090770055395],"rgb":[0.4,0.866666666666666696,0.466666666666666674],"xyz":[0.346641056384248469,0.558677046013535294,0.264102263866974774],"hpluv":[131.057978326052336,165.560886464635786,79.538466322575843],"hsluv":[131.057978326052336,73.175897046832489,79.538466322575843]},"#66dd88":{"lch":[79.7918079202835173,74.5708009722257543,135.636001638862723],"luv":[79.7918079202835173,-53.3115731427918,52.1409678341327378],"rgb":[0.4,0.866666666666666696,0.533333333333333326],"xyz":[0.35778147348619227,0.563133212854312837,0.322775127270546969],"hpluv":[135.636001638862723,152.557764790666909,79.7918079202835173],"hsluv":[135.636001638862723,73.8730513922454435,79.7918079202835173]},"#66dd99":{"lch":[80.0870378401123162,66.8478260511747919,141.915708870878916],"luv":[80.0870378401123162,-52.6162023759431392,41.2330825345609924],"rgb":[0.4,0.866666666666666696,0.6],"xyz":[0.370838437595265,0.568355998497942,0.391541804911664904],"hpluv":[141.915708870878916,139.150468179242381,80.0870378401123162],"hsluv":[141.915708870878916,74.6453790933081791,80.0870378401123162]},"#66ddaa":{"lch":[80.4252690581998877,59.592891473004542,150.502508447871207],"luv":[80.4252690581998877,-51.8682972227800931,29.3426729750825572],"rgb":[0.4,0.866666666666666696,0.66666666666666663],"xyz":[0.385896130560517525,0.574379075684043117,0.470845654528663449],"hpluv":[150.502508447871207,126.571308780245914,80.4252690581998877],"hsluv":[150.502508447871207,75.4812281571350638,80.4252690581998877]},"#66ddbb":{"lch":[80.807354973374558,53.7174771102931672,161.973645166837599],"luv":[80.807354973374558,-51.0807157732000334,16.6231111165276104],"rgb":[0.4,0.866666666666666696,0.733333333333333282],"xyz":[0.4030337226678542,0.581234112526977875,0.561103639627305251],"hpluv":[161.973645166837599,116.757770235740637,80.807354973374558],"hsluv":[161.973645166837599,76.3679029554368753,80.807354973374558]},"#66ddcc":{"lch":[81.2339045655681389,50.3705316419042148,176.319314132685548],"luv":[81.2339045655681389,-50.2666331071559256,3.23358221770368903],"rgb":[0.4,0.866666666666666696,0.8],"xyz":[0.422326098111575954,0.588951062704466732,0.662710150297575407],"hpluv":[176.319314132685548,112.393665646206941,81.2339045655681389],"hsluv":[176.319314132685548,77.2923330418296501,81.2339045655681389]},"#66dddd":{"lch":[81.7052962965957903,50.5769089008318389,192.177050630061],"luv":[81.7052962965957903,-49.4389517336029,-10.6683534552210038],"rgb":[0.4,0.866666666666666696,0.866666666666666696],"xyz":[0.443844423662209442,0.597558392924720239,0.776039998197581404],"hpluv":[192.177050630061,116.242714563462513,81.7052962965957903],"hsluv":[192.177050630061,78.2416694753709407,81.7052962965957903]},"#66ddee":{"lch":[82.2216916522674524,54.6311568672274745,207.154140846621715],"luv":[82.2216916522674524,-48.6098170936026577,-24.9328895793911158],"rgb":[0.4,0.866666666666666696,0.933333333333333348],"xyz":[0.467656603928384207,0.607083265031190256,0.901450814266104627],"hpluv":[207.154140846621715,129.793318723999505,82.2216916522674524],"hsluv":[207.154140846621715,79.2037752530793,82.2216916522674524]},"#66ddff":{"lch":[82.7830488398693376,61.9519841783888339,219.519505956683815],"luv":[82.7830488398693376,-47.7902556034158366,-39.4225799891321387],"rgb":[0.4,0.866666666666666696,1],"xyz":[0.493827652338215572,0.617551684395123,1.03928500255788636],"hpluv":[219.519505956683815,152.730665075356086,82.7830488398693376],"hsluv":[219.519505956683815,99.9999999999957,82.7830488398693376]},"#115500":{"lch":[30.9160157060817227,46.0913193883500583,125.457330883646421],"luv":[30.9160157060817227,-26.7374134918097575,37.543580579466358],"rgb":[0.0666666666666666657,0.333333333333333315,0],"xyz":[0.0347951852141227744,0.0661590714665301755,0.0109362145699901016],"hpluv":[125.457330883646421,189.179880792461034,30.9160157060817227],"hsluv":[125.457330883646421,100.000000000002402,30.9160157060817227]},"#115511":{"lch":[31.0114762783458957,43.2230667766736616,127.715012949240275],"luv":[31.0114762783458957,-26.4410342208804181,34.1921805521528697],"rgb":[0.0666666666666666657,0.333333333333333315,0.0666666666666666657],"xyz":[0.0358068507137599,0.0665637376663850283,0.0162643195347457331],"hpluv":[127.715012949240275,176.861157643680144,31.0114762783458957],"hsluv":[127.715012949240275,90.0538522348087156,31.0114762783458957]},"#115522":{"lch":[31.1874163697014737,38.3803455570512071,132.492971129528541],"luv":[31.1874163697014737,-25.925914044022,28.3001396827055132],"rgb":[0.0666666666666666657,0.333333333333333315,0.133333333333333331],"xyz":[0.0376822088522369147,0.0673138809217758427,0.0261412057307249292],"hpluv":[132.492971129528541,156.159643444286843,31.1874163697014737],"hsluv":[132.492971129528541,90.4316047034468653,31.1874163697014737]},"#115533":{"lch":[31.4742731349983,31.7764378074468219,142.363318860140765],"luv":[31.4742731349983,-25.163724938647551,19.4043538140960301],"rgb":[0.0666666666666666657,0.333333333333333315,0.2],"xyz":[0.0407699595846946553,0.0685489812147589556,0.0424033595883360759],"hpluv":[142.363318860140765,128.111709873485438,31.4742731349983],"hsluv":[142.363318860140765,90.9947325890089616,31.4742731349983]},"#115544":{"lch":[31.8824114421380642,25.5202649789159608,161.635705606154772],"luv":[31.8824114421380642,-24.2205825435089714,8.04035483341083435],"rgb":[0.0666666666666666657,0.333333333333333315,0.266666666666666663],"xyz":[0.0452279548289135358,0.0703321793124465355,0.0658821345412227144],"hpluv":[161.635705606154772,101.571845607751229,31.8824114421380642],"hsluv":[161.635705606154772,91.6999843863076194,31.8824114421380642]},"#115555":{"lch":[32.417637609391285,23.7206023942150033,192.177050630061103],"luv":[32.417637609391285,-23.1868997601056,-5.00346454561771115],"rgb":[0.0666666666666666657,0.333333333333333315,0.333333333333333315],"xyz":[0.0511903688682755278,0.0727171449281913684,0.0972841818151966159],"hpluv":[192.177050630061103,92.8503782686988899,32.417637609391285],"hsluv":[192.177050630061103,92.4869346485079,32.417637609391285]},"#115566":{"lch":[33.0818646063754045,29.1355194477524577,220.509575549450261],"luv":[33.0818646063754045,-22.1516601517912761,-18.9257085999416219],"rgb":[0.0666666666666666657,0.333333333333333315,0.4],"xyz":[0.0587753646417837061,0.0757511432375946869,0.137231826222340592],"hpluv":[220.509575549450261,111.756325010930979,33.0818646063754045],"hsluv":[220.509575549450261,93.2955878193037904,33.0818646063754045]},"#115577":{"lch":[33.8736729304774826,39.2775515041961185,237.359341141202208],"luv":[33.8736729304774826,-21.1850732372535866,-33.0744421585153958],"rgb":[0.0666666666666666657,0.333333333333333315,0.466666666666666674],"xyz":[0.0680893776619856117,0.0794767484456755,0.186285628128738501],"hpluv":[237.359341141202208,147.13684637222039,33.8736729304774826],"hsluv":[237.359341141202208,94.078253732623736,33.8736729304774826]},"#115588":{"lch":[34.7888943497230514,51.2161985161337938,246.60972521059881],"luv":[34.7888943497230514,-20.3324266151093482,-47.0073549392562455],"rgb":[0.0666666666666666657,0.333333333333333315,0.533333333333333326],"xyz":[0.0792297947639294398,0.0839329152864531,0.244958491532310668],"hpluv":[246.60972521059881,186.812546427038342,34.7888943497230514],"hsluv":[246.60972521059881,94.8038015691112719,34.7888943497230514]},"#115599":{"lch":[35.8212274371681403,63.5782184467896,252.028930114170464],"luv":[35.8212274371681403,-19.6162163870228632,-60.476391389741444],"rgb":[0.0666666666666666657,0.333333333333333315,0.6],"xyz":[0.0922867588730021671,0.0891557009300822517,0.313725169173428575],"hpluv":[252.028930114170464,225.22013661091745,35.8212274371681403],"hsluv":[252.028930114170464,95.4562831747131355,35.8212274371681403]},"#1155aa":{"lch":[36.9628521043173777,75.808874965898184,255.452401876993463],"luv":[36.9628521043173777,-19.0419916394553752,-73.3783897206670588],"rgb":[0.0666666666666666657,0.333333333333333315,0.66666666666666663],"xyz":[0.107344451838254695,0.0951787781161833601,0.393029018790427176],"hpluv":[255.452401876993463,260.251896817562397,36.9628521043173777],"hsluv":[255.452401876993463,96.0310300088164155,36.9628521043173777]},"#1155bb":{"lch":[38.2050019251475845,87.6984258792502089,257.752076786877694],"luv":[38.2050019251475845,-18.6045414354547205,-85.7023041678273216],"rgb":[0.0666666666666666657,0.333333333333333315,0.733333333333333282],"xyz":[0.124482043945591342,0.102033814959118119,0.483287003889068922],"hpluv":[257.752076786877694,291.280156181798475,38.2050019251475845],"hsluv":[257.752076786877694,96.5305142623785,38.2050019251475845]},"#1155cc":{"lch":[39.5384610498345523,99.1886562531622,259.372402836059223],"luv":[39.5384610498345523,-18.292845239891637,-97.4872368176334447],"rgb":[0.0666666666666666657,0.333333333333333315,0.8],"xyz":[0.143774419389313096,0.10975076513660692,0.584893514559339],"hpluv":[259.372402836059223,318.332933912315752,39.5384610498345523],"hsluv":[259.372402836059223,96.9610449958707,39.5384610498345523]},"#1155dd":{"lch":[40.9539668975822053,110.288480556245688,260.557616930578945],"luv":[40.9539668975822053,-18.0934548393737415,-108.7941902648341],"rgb":[0.0666666666666666657,0.333333333333333315,0.866666666666666696],"xyz":[0.16529274493994664,0.118358095356860454,0.698223362459345],"hpluv":[260.557616930578945,341.722445031840948,40.9539668975822053],"hsluv":[260.557616930578945,97.330522563257,40.9539668975822053]},"#1155ee":{"lch":[42.4425141949683038,121.034132372772049,261.450904748640312],"luv":[42.4425141949683038,-17.992549051743687,-119.689303523123172],"rgb":[0.0666666666666666657,0.333333333333333315,0.933333333333333348],"xyz":[0.189104925206121377,0.127882967463330471,0.823634178527868244],"hpluv":[261.450904748640312,361.864588035447412,42.4425141949683038],"hsluv":[261.450904748640312,97.6470858685672596,42.4425141949683038]},"#1155ff":{"lch":[43.9955669218353762,131.469960671873054,262.140820458865903],"luv":[43.9955669218353762,-17.9770474653633769,-130.235081001594637],"rgb":[0.0666666666666666657,0.333333333333333315,1],"xyz":[0.21527597361595277,0.138351386827263168,0.961468366819650089],"hpluv":[262.140820458865903,379.190057269809415,43.9955669218353762],"hsluv":[262.140820458865903,99.9999999999993321,43.9955669218353762]},"#66ee00":{"lch":[83.9510288300903511,118.631054776009961,121.878900606421581],"luv":[83.9510288300903511,-62.6521043104729145,100.737485489455693],"rgb":[0.4,0.933333333333333348,0],"xyz":[0.360525640276900428,0.639716921715420939,0.104479109088397595],"hpluv":[121.878900606421581,316.932305812441825,83.9510288300903511],"hsluv":[121.878900606421581,100.000000000002245,83.9510288300903511]},"#66ee11":{"lch":[83.9720997528394406,117.779830021225337,122.096631719604488],"luv":[83.9720997528394406,-62.5821687959979371,99.7775551344988401],"rgb":[0.4,0.933333333333333348,0.0666666666666666657],"xyz":[0.361537305776537532,0.640121587915275847,0.109807214053153232],"hpluv":[122.096631719604488,315.12898496103287,83.9720997528394406],"hsluv":[122.096631719604488,98.9534187198870825,83.9720997528394406]},"#66ee22":{"lch":[84.0111361497279461,116.215639794016113,122.506307539641156],"luv":[84.0111361497279461,-62.4534076072217061,98.0084017366813498],"rgb":[0.4,0.933333333333333348,0.133333333333333331],"xyz":[0.363412663915014589,0.640871731170666648,0.119684100249132425],"hpluv":[122.506307539641156,311.807725961995743,84.0111361497279461],"hsluv":[122.506307539641156,97.0256918708191,84.0111361497279461]},"#66ee33":{"lch":[84.0753427139745213,113.679067087327056,123.198426402241765],"luv":[84.0753427139745213,-62.2438639038254564,95.1242960560921],"rgb":[0.4,0.933333333333333348,0.2],"xyz":[0.366500414647472295,0.642106831463649774,0.135946254106743575],"hpluv":[123.198426402241765,306.400922934277446,84.0753427139745213],"hsluv":[123.198426402241765,93.8862320250161133,84.0753427139745213]},"#66ee44":{"lch":[84.1678970009459704,110.10189342016568,124.237870325737177],"luv":[84.1678970009459704,-61.946619900698991,91.0222127702015626],"rgb":[0.4,0.933333333333333348,0.266666666666666663],"xyz":[0.37095840989169121,0.64389002956133734,0.159425029059630213],"hpluv":[124.237870325737177,298.731636810455427,84.1678970009459704],"hsluv":[124.237870325737177,89.4277949312148337,84.1678970009459704]},"#66ee55":{"lch":[84.2914184237995414,105.474359140243067,125.706632503417083],"luv":[84.2914184237995414,-61.5585501093914829,85.6468641835444657],"rgb":[0.4,0.933333333333333348,0.333333333333333315],"xyz":[0.376920823931053195,0.64627499517708209,0.190827076333604129],"hpluv":[125.706632503417083,288.732343582820249,84.2914184237995414],"hsluv":[125.706632503417083,83.5978964235977315,84.2914184237995414]},"#66ee66":{"lch":[84.4481159447294374,99.8471979874678652,127.715012949239849],"luv":[84.4481159447294374,-61.0799597466417339,78.9854509596370633],"rgb":[0.4,0.933333333333333348,0.4],"xyz":[0.38450581970456138,0.649308993486485408,0.230774720740748063],"hpluv":[127.715012949239849,276.453302697114395,84.4481159447294374],"hsluv":[127.715012949239849,76.3932656313124454,84.4481159447294374]},"#66ee77":{"lch":[84.6398667383604391,93.3381964812165,130.416076861551431],"luv":[84.6398667383604391,-60.5142850922984863,71.0636350191446127],"rgb":[0.4,0.933333333333333348,0.466666666666666674],"xyz":[0.393819832724763286,0.65303459869456626,0.279828522647145972],"hpluv":[130.416076861551431,262.087365127001192,84.6398667383604391],"hsluv":[130.416076861551431,76.8478121958162461,84.6398667383604391]},"#66ee88":{"lch":[84.8682629083727704,86.1443361377895513,134.024834696689027],"luv":[84.8682629083727704,-59.8677379892917187,61.9411058721588503],"rgb":[0.4,0.933333333333333348,0.533333333333333326],"xyz":[0.404960249826707086,0.657490765535343802,0.338501386050718167],"hpluv":[134.024834696689027,246.018013362770375,84.8682629083727704],"hsluv":[134.024834696689027,77.3690194462974858,84.8682629083727704]},"#66ee99":{"lch":[85.1346415661787432,78.5628931277068,138.840952739605655],"luv":[85.1346415661787432,-59.1488647733143296,51.7062856200641718],"rgb":[0.4,0.933333333333333348,0.6],"xyz":[0.418017213935779841,0.662713551178973,0.407268063691836046],"hpluv":[138.840952739605655,228.905884969324347,85.1346415661787432],"hsluv":[138.840952739605655,77.9507906120806,85.1346415661787432]},"#66eeaa":{"lch":[85.4401056772853451,71.025817565124143,145.263991147699045],"luv":[85.4401056772853451,-58.3680297483556387,40.4702342974360576],"rgb":[0.4,0.933333333333333348,0.66666666666666663],"xyz":[0.433074906901032342,0.668736628365074082,0.486571913308834647],"hpluv":[145.263991147699045,211.83630636980061,85.4401056772853451],"hsluv":[145.263991147699045,78.5856392083502868,85.4401056772853451]},"#66eebb":{"lch":[85.7855396574798448,64.1465768711300228,153.7611846199909],"luv":[85.7855396574798448,-57.5368534805651,28.3600743271201026],"rgb":[0.4,0.933333333333333348,0.733333333333333282],"xyz":[0.450212499008369,0.67559166520800884,0.576829898407476449],"hpluv":[153.7611846199909,196.542528595596508,85.7855396574798448],"hsluv":[153.7611846199909,79.2651091664321683,85.7855396574798448]},"#66eecc":{"lch":[86.1716220205593,58.7524914026155827,164.690737532459508],"luv":[86.1716220205593,-56.6676444389915517,15.5123602250092176],"rgb":[0.4,0.933333333333333348,0.8],"xyz":[0.46950487445209077,0.683308615385497697,0.678436409077746605],"hpluv":[164.690737532459508,185.645341664818176,86.1716220205593],"hsluv":[164.690737532459508,79.9801993176568118,86.1716220205593]},"#66eedd":{"lch":[86.5988364705929285,55.8111450312658164,177.877712634427581],"luv":[86.5988364705929285,-55.7728621696499403,2.06682246606168096],"rgb":[0.4,0.933333333333333348,0.866666666666666696],"xyz":[0.491023200002724258,0.691915945605751204,0.791766256977752603],"hpluv":[177.877712634427581,182.628088950326941,86.5988364705929285],"hsluv":[177.877712634427581,80.7217641095277543,86.5988364705929285]},"#66eeee":{"lch":[87.0674822997282263,56.1274864183573783,192.177050630061075],"luv":[87.0674822997282263,-54.8646438121880493,-11.8391549953798521],"rgb":[0.4,0.933333333333333348,0.933333333333333348],"xyz":[0.514835380268899,0.701440817712221221,0.917177073046275826],"hpluv":[192.177050630061075,191.066910285097691,87.0674822997282263],"hsluv":[192.177050630061075,81.4808670778618165,87.0674822997282263]},"#66eeff":{"lch":[87.5776846199412518,59.9248574237073512,205.793536431897621],"luv":[87.5776846199412518,-53.9544158989052178,-26.0750751151289606],"rgb":[0.4,0.933333333333333348,1],"xyz":[0.541006428678730389,0.711909237076154,1.05501126133805756],"hpluv":[205.793536431897621,213.276590696447101,87.5776846199412518],"hsluv":[205.793536431897621,99.9999999999933351,87.5776846199412518]},"#116600":{"lch":[37.1543973335168118,56.0416844920186463,126.180156646926719],"luv":[37.1543973335168118,-33.0828721903909511,45.2348755755691201],"rgb":[0.0666666666666666657,0.4,0],"xyz":[0.0498232429199692434,0.0962151868782235159,0.0159455671386054508],"hpluv":[126.180156646926719,191.399273993181851,37.1543973335168118],"hsluv":[126.180156646926719,100.000000000002359,37.1543973335168118]},"#116611":{"lch":[37.2288128297302237,53.6508389550451668,127.715012949240304],"luv":[37.2288128297302237,-32.820060550512963,42.4412081124095124],"rgb":[0.0666666666666666657,0.4,0.0666666666666666657],"xyz":[0.0508349084196063616,0.0966198530780783688,0.021273672103361084],"hpluv":[127.715012949240304,182.867554307566394,37.2288128297302237],"hsluv":[127.715012949240304,93.1121786917857719,37.2288128297302237]},"#116622":{"lch":[37.366211587350719,49.4912288674311,130.823584918521846],"luv":[37.366211587350719,-32.3540076461773722,37.4513006989015125],"rgb":[0.0666666666666666657,0.4,0.133333333333333331],"xyz":[0.0527102665580833837,0.0973699963334691831,0.0311505582993402766],"hpluv":[130.823584918521846,168.069340269208539,37.366211587350719],"hsluv":[130.823584918521846,93.2954788878505639,37.366211587350719]},"#116633":{"lch":[37.5909073580641291,43.4152735453156,136.786924005406348],"luv":[37.5909073580641291,-31.6415889136076061,29.7270218494359924],"rgb":[0.0666666666666666657,0.4,0.2],"xyz":[0.0557980172905411242,0.0986050966264523,0.0474127121569514198],"hpluv":[136.786924005406348,146.554466726503705,37.5909073580641291],"hsluv":[136.786924005406348,93.5769168130898095,37.5909073580641291]},"#116644":{"lch":[37.9120295698984506,36.3990668435195417,147.559563985674146],"luv":[37.9120295698984506,-30.7189764474867033,19.525279846848818],"rgb":[0.0666666666666666657,0.4,0.266666666666666663],"xyz":[0.0602560125347600048,0.100388294724139876,0.0708914871098380583],"hpluv":[147.559563985674146,121.829522477327146,37.9120295698984506],"hsluv":[147.559563985674146,93.9439470857740559,37.9120295698984506]},"#116655":{"lch":[38.335629212958338,30.5483862618043851,166.063087397862887],"luv":[38.335629212958338,-29.6490880228798659,7.35768187759708869],"rgb":[0.0666666666666666657,0.4,0.333333333333333315],"xyz":[0.066218426574122,0.102773260339884709,0.102293534383811974],"hpluv":[166.063087397862887,101.117192726530064,38.335629212958338],"hsluv":[166.063087397862887,94.3739252192313529,38.335629212958338]},"#116666":{"lch":[38.8651381017916293,29.1618890828208741,192.177050630061217],"luv":[38.8651381017916293,-28.5057600031098204,-6.15121301239451324],"rgb":[0.0666666666666666657,0.4,0.4],"xyz":[0.0738034223476301682,0.105807258649288027,0.142241178790955936],"hpluv":[192.177050630061217,95.2126746116157392,38.8651381017916293],"hsluv":[192.177050630061217,94.8399842705083245,38.8651381017916293]},"#116677":{"lch":[39.5016809883423079,34.1234106492309124,216.700227589977857],"luv":[39.5016809883423079,-27.3592385518708738,-20.3931169809293316],"rgb":[0.0666666666666666657,0.4,0.466666666666666674],"xyz":[0.0831174353678320876,0.109532863857368837,0.191294980697353845],"hpluv":[216.700227589977857,109.616563248578402,39.5016809883423079],"hsluv":[216.700227589977857,95.3164106037614687,39.5016809883423079]},"#116688":{"lch":[40.2443638992953723,43.6497103352843823,233.004065811630028],"luv":[40.2443638992953723,-26.2665776267255495,-34.862072688430807],"rgb":[0.0666666666666666657,0.4,0.533333333333333326],"xyz":[0.0942578524697759157,0.113989030698146435,0.249967844100926],"hpluv":[233.004065811630028,137.630797119362086,40.2443638992953723],"hsluv":[233.004065811630028,95.7822055324918864,40.2443638992953723]},"#116699":{"lch":[41.090575936542443,55.3002439505846866,242.81143298809],"luv":[41.090575936542443,-25.2678118474637223,-49.189985418125],"rgb":[0.0666666666666666657,0.4,0.6],"xyz":[0.107314816578848629,0.119211816341775592,0.318734521742043919],"hpluv":[242.81143298809,170.774941475023041,41.090575936542443],"hsluv":[242.81143298809,96.2225162944548202,41.090575936542443]},"#1166aa":{"lch":[42.0363074660961757,67.6887016451143,248.882871092507173],"luv":[42.0363074660961757,-24.3865946332867658,-63.1431257746482615],"rgb":[0.0666666666666666657,0.4,0.66666666666666663],"xyz":[0.122372509544101171,0.125234893527876701,0.39803837135904252],"hpluv":[248.882871092507173,204.329442679703192,42.0363074660961757],"hsluv":[248.882871092507173,96.6284204735347885,42.0363074660961757]},"#1166bb":{"lch":[43.0764730814379746,80.16050208662584,252.853006907061769],"luv":[43.0764730814379746,-23.6332520973097289,-76.5974901030376429],"rgb":[0.0666666666666666657,0.4,0.733333333333333282],"xyz":[0.139510101651437818,0.132089930370811459,0.488296356457684266],"hpluv":[252.853006907061769,236.134594524550181,43.0764730814379746],"hsluv":[252.853006907061769,96.9958197331145,43.0764730814379746]},"#1166cc":{"lch":[44.2052232400861271,92.4179309088515737,255.583916344807051],"luv":[44.2052232400861271,-23.008531750755072,-89.5079964033815259],"rgb":[0.0666666666666666657,0.4,0.8],"xyz":[0.158802477095159544,0.139806880548300261,0.589902867127954367],"hpluv":[255.583916344807051,265.290671800528912,44.2052232400861271],"hsluv":[255.583916344807051,97.3240770235992159,44.2052232400861271]},"#1166dd":{"lch":[45.4162296513266455,104.336857924373376,257.542523527564185],"luv":[45.4162296513266455,-22.507022318245486,-101.880390006599782],"rgb":[0.0666666666666666657,0.4,0.866666666666666696],"xyz":[0.180320802645793088,0.148414210768553795,0.703232715027960364],"hpluv":[257.542523527564185,291.518421142394175,45.4162296513266455],"hsluv":[257.542523527564185,97.6147741074162,45.4162296513266455]},"#1166ee":{"lch":[46.7029335650228674,115.880201424536907,258.995545526323895],"luv":[46.7029335650228674,-22.1198283610194721,-113.74943637429719],"rgb":[0.0666666666666666657,0.4,0.933333333333333348],"xyz":[0.204132982911967853,0.157939082875023812,0.828643531096483588],"hpluv":[258.995545526323895,314.850514307077333,46.7029335650228674],"hsluv":[258.995545526323895,97.870742288996567,46.7029335650228674]},"#1166ff":{"lch":[48.0587511138394348,127.054293237547355,260.103604495506659],"luv":[48.0587511138394348,-21.8364562757409573,-125.163743182322264],"rgb":[0.0666666666666666657,0.4,1],"xyz":[0.230304031321799219,0.168407502238956508,0.966477719388265433],"hpluv":[260.103604495506659,335.471932494038299,48.0587511138394348],"hsluv":[260.103604495506659,99.9999999999992184,48.0587511138394348]},"#66ff00":{"lch":[89.0839511722278417,127.467952451328657,122.755484474710229],"luv":[89.0839511722278417,-68.9671698198214074,107.198919720201],"rgb":[0.4,1,0],"xyz":[0.41237801270657426,0.74342166657477,0.121763233231621706],"hpluv":[122.755484474710229,522.717702913530729,89.0839511722278417],"hsluv":[122.755484474710229,100.000000000002402,89.0839511722278417]},"#66ff11":{"lch":[89.1030144718140917,126.693355761899767,122.944319876693868],"luv":[89.1030144718140917,-68.8988566751164342,106.320994836735167],"rgb":[0.4,1,0.0666666666666666657],"xyz":[0.413389678206211364,0.743826332774625,0.127091338196377329],"hpluv":[122.944319876693868,520.53129302948,89.1030144718140917],"hsluv":[122.944319876693868,99.9999999999913456,89.1030144718140917]},"#66ff22":{"lch":[89.1383344673707825,125.268361506965746,123.29878008449495],"luv":[89.1383344673707825,-68.7729595958538482,104.70168300016438],"rgb":[0.4,1,0.133333333333333331],"xyz":[0.415265036344688421,0.744576476030015755,0.136968224392356536],"hpluv":[123.29878008449495,516.49933125362179,89.1383344673707825],"hsluv":[123.29878008449495,99.9999999999914877,89.1383344673707825]},"#66ff33":{"lch":[89.1964366933732,122.952924407393425,123.895139690212403],"luv":[89.1964366933732,-68.5677350303770368,102.0582546055644],"rgb":[0.4,1,0.2],"xyz":[0.418352787077146127,0.745811576322998881,0.153230378249967686],"hpluv":[123.895139690212403,509.920932540516333,89.1964366933732],"hsluv":[123.895139690212403,99.9999999999913456,89.1964366933732]},"#66ff44":{"lch":[89.2802097655713,119.677402261160566,124.785058224819977],"luv":[89.2802097655713,-68.2758869475835439,98.2908127624369143],"rgb":[0.4,1,0.266666666666666663],"xyz":[0.422810782321365042,0.747594774420686448,0.176709153202854324],"hpluv":[124.785058224819977,500.557479589511445,89.2802097655713],"hsluv":[124.785058224819977,99.9999999999914451,89.2802097655713]},"#66ff55":{"lch":[89.392045372062455,115.420778437487911,126.031255758612545],"luv":[89.392045372062455,-67.8935601171189802,93.3403481337991536],"rgb":[0.4,1,0.333333333333333315],"xyz":[0.428773196360727,0.749979740036431197,0.20811120047682824],"hpluv":[126.031255758612545,488.288652672415253,89.392045372062455],"hsluv":[126.031255758612545,99.9999999999912177,89.392045372062455]},"#66ff66":{"lch":[89.5339732348528088,110.211236984550467,127.715012949239977],"luv":[89.5339732348528088,-67.4199983006921428,87.1840615410833664],"rgb":[0.4,1,0.4],"xyz":[0.436358192134235212,0.753013738345834516,0.248058844883972174],"hpluv":[127.715012949239977,473.1190638884799,89.5339732348528088],"hsluv":[127.715012949239977,99.9999999999912177,89.5339732348528088]},"#66ff77":{"lch":[89.7077333531255,104.130085615398272,129.945267186452298],"luv":[89.7077333531255,-66.8572982376138754,79.8321764869086223],"rgb":[0.4,1,0.466666666666666674],"xyz":[0.445672205154437118,0.756739343553915367,0.297112646790370083],"hpluv":[129.945267186452298,455.203211628206077,89.7077333531255],"hsluv":[129.945267186452298,99.9999999999911466,89.7077333531255]},"#66ff88":{"lch":[89.9148190538961529,97.3190386909276413,132.870301628739924],"luv":[89.9148190538961529,-66.2101394375234378,71.3246992800528687],"rgb":[0.4,1,0.533333333333333326],"xyz":[0.456812622256380918,0.761195510394692909,0.355785510193942278],"hpluv":[132.870301628739924,434.894743355908361,89.9148190538961529],"hsluv":[132.870301628739924,99.999999999990834,89.9148190538961529]},"#66ff99":{"lch":[90.1565046807361,89.9924560427222247,136.692010716646195],"luv":[90.1565046807361,-65.4854512845448795,61.7276098246221707],"rgb":[0.4,1,0.6],"xyz":[0.469869586365453618,0.766418296038322122,0.424552187835060157],"hpluv":[136.692010716646195,412.835114866532763,90.1565046807361],"hsluv":[136.692010716646195,99.9999999999909903,90.1565046807361]},"#66ffaa":{"lch":[90.4338646074596113,82.4570841698328678,141.679424663280656],"luv":[90.4338646074596113,-64.6920148236146,51.1284064669742264],"rgb":[0.4,1,0.66666666666666663],"xyz":[0.484927279330706174,0.772441373224423189,0.503856037452058758],"hpluv":[141.679424663280656,390.10709074414774,90.4338646074596113],"hsluv":[141.679424663280656,99.9999999999905924,90.4338646074596113]},"#66ffbb":{"lch":[90.747787175062669,75.1410408914244101,148.168460326052468],"luv":[90.747787175062669,-63.84001546278364,39.631155067172358],"rgb":[0.4,1,0.733333333333333282],"xyz":[0.502064871438042792,0.779296410067358,0.59411402255070056],"hpluv":[148.168460326052468,368.486167720556807,90.747787175062669],"hsluv":[148.168460326052468,99.9999999999904077,90.747787175062669]},"#66ffcc":{"lch":[91.0989856399247486,68.6265957122477,156.512275503644645],"luv":[91.0989856399247486,-62.9405723747737085,27.3513068826366492],"rgb":[0.4,1,0.8],"xyz":[0.521357246881764547,0.787013360244846805,0.695720533220970716],"hpluv":[156.512275503644645,350.804850059399143,91.0989856399247486],"hsluv":[156.512275503644645,99.9999999999903508,91.0989856399247486]},"#66ffdd":{"lch":[91.4880074096490716,63.6578272942329875,166.916209854529029],"luv":[91.4880074096490716,-62.0052733715472826,14.410588119229697],"rgb":[0.4,1,0.866666666666666696],"xyz":[0.542875572432398146,0.795620690465100311,0.809050381120976714],"hpluv":[166.916209854529029,341.336295176875581,91.4880074096490716],"hsluv":[166.916209854529029,99.9999999999898819,91.4880074096490716]},"#66ffee":{"lch":[91.9152423718395113,61.0528599966611765,179.125088100836763],"luv":[91.9152423718395113,-61.045742111776633,0.93224663816584552],"rgb":[0.4,1,0.933333333333333348],"xyz":[0.566687752698572855,0.805145562571570328,0.934461197189499937],"hpluv":[179.125088100836763,345.840646438583576,91.9152423718395113],"hsluv":[179.125088100836763,99.9999999999894,91.9152423718395113]},"#66ffff":{"lch":[92.3809308294128,61.4559907165056,192.17705063006116],"luv":[92.3809308294128,-60.0732592166006256,-12.9631139022354667],"rgb":[0.4,1,1],"xyz":[0.592858801108404276,0.815613981935503,1.07229538548128178],"hpluv":[192.17705063006116,370.76546272919029,92.3809308294128],"hsluv":[192.17705063006116,99.9999999999889866,92.3809308294128]},"#117700":{"lch":[43.2300348418233042,65.6725964696673685,126.613348243544976],"luv":[43.2300348418233042,-39.1679175007181684,52.7139845365981],"rgb":[0.0666666666666666657,0.466666666666666674,0],"xyz":[0.0682769809733868721,0.133122662985059287,0.0220968131564111547],"hpluv":[126.613348243544976,192.769325646383436,43.2300348418233042],"hsluv":[126.613348243544976,100.000000000002359,43.2300348418233042]},"#117711":{"lch":[43.289989941732955,63.6505577690815443,127.715012949240403],"luv":[43.289989941732955,-38.9372319378938414,50.351618378457772],"rgb":[0.0666666666666666657,0.466666666666666674,0.0666666666666666657],"xyz":[0.0692886464730239904,0.13352732918491414,0.0274249181211667845],"hpluv":[127.715012949240403,186.57525992916959,43.289989941732955],"hsluv":[127.715012949240403,95.0000616991484321,43.289989941732955]},"#117722":{"lch":[43.400811094951429,60.069144786817489,129.889765673233825],"luv":[43.400811094951429,-38.5230987158151308,46.089836414888758],"rgb":[0.0666666666666666657,0.466666666666666674,0.133333333333333331],"xyz":[0.0711640046115010194,0.134277472440304968,0.037301804317145984],"hpluv":[129.889765673233825,175.627665872500842,43.400811094951429],"hsluv":[129.889765673233825,95.0973605906006725,43.400811094951429]},"#117733":{"lch":[43.5823807888255317,54.6433249694405205,133.881596062605809],"luv":[43.5823807888255317,-37.8771324094584756,39.3854770715327476],"rgb":[0.0666666666666666657,0.466666666666666674,0.2],"xyz":[0.074251755343958753,0.135512572733288067,0.0535639581747571272],"hpluv":[133.881596062605809,159.098283206795713,43.5823807888255317],"hsluv":[133.881596062605809,95.249567515942033,43.5823807888255317]},"#117744":{"lch":[43.8425891980029,47.8554364611196803,140.669149905405504],"luv":[43.8425891980029,-37.0161352468332083,30.3306533110056051],"rgb":[0.0666666666666666657,0.466666666666666674,0.266666666666666663],"xyz":[0.0787097505881776266,0.137295770830975661,0.0770427331276437588],"hpluv":[140.669149905405504,138.507858249946878,43.8425891980029],"hsluv":[140.669149905405504,95.4533619015585373,43.8425891980029]},"#117755":{"lch":[44.18711059062651,40.7988283335817243,151.872601466806259],"luv":[44.18711059062651,-35.9805491223838843,19.2339407882209947],"rgb":[0.0666666666666666657,0.466666666666666674,0.333333333333333315],"xyz":[0.0846721646275396256,0.139680736446720494,0.108444780401617674],"hpluv":[151.872601466806259,117.163251003272293,44.18711059062651],"hsluv":[151.872601466806259,95.7000805167539426,44.18711059062651]},"#117766":{"lch":[44.6197667240920879,35.4343089397309186,169.362936023125116],"luv":[44.6197667240920879,-34.8254110013137463,6.54071850990925796],"rgb":[0.0666666666666666657,0.466666666666666674,0.4],"xyz":[0.0922571604010478,0.142714734756123784,0.148392424808761636],"hpluv":[169.362936023125116,100.771099800457392,44.6197667240920879],"hsluv":[169.362936023125116,95.9777414193434453,44.6197667240920879]},"#117777":{"lch":[45.1427402486772138,34.384087641882445,192.177050630061217],"luv":[45.1427402486772138,-33.6104614986274584,-7.25274850066749],"rgb":[0.0666666666666666657,0.466666666666666674,0.466666666666666674],"xyz":[0.101571173421249716,0.146440339964204608,0.197446226715159545],"hpluv":[192.177050630061217,96.651570122263351,45.1427402486772138],"hsluv":[192.177050630061217,96.2732475219964385,45.1427402486772138]},"#117788":{"lch":[45.7567431438856502,38.9735922704815891,213.78628123605418],"luv":[45.7567431438856502,-32.3916401681270685,-21.6730833451154119],"rgb":[0.0666666666666666657,0.466666666666666674,0.533333333333333326],"xyz":[0.112711590523193544,0.150896506804982206,0.256119090118731685],"hpluv":[213.78628123605418,108.082320192335175,45.7567431438856502],"hsluv":[213.78628123605418,96.5742797371803192,45.7567431438856502]},"#117799":{"lch":[46.4611794427891169,47.8902134896154763,229.321579893426758],"luv":[46.4611794427891169,-31.2154548044213769,-36.3189747850083791],"rgb":[0.0666666666666666657,0.466666666666666674,0.6],"xyz":[0.125768554632266272,0.156119292448611363,0.32488576775984962],"hpluv":[229.321579893426758,130.796423620493698,46.4611794427891169],"hsluv":[229.321579893426758,96.8705519561321,46.4611794427891169]},"#1177aa":{"lch":[47.254315604307827,59.1280670765604199,239.379878554404911],"luv":[47.254315604307827,-30.1165063567495856,-50.8834389666627231],"rgb":[0.0666666666666666657,0.466666666666666674,0.66666666666666663],"xyz":[0.1408262475975188,0.162142369634712458,0.40418961737684822],"hpluv":[239.379878554404911,158.77844008322549,47.254315604307827],"hsluv":[239.379878554404911,97.1543662497403488,47.254315604307827]},"#1177bb":{"lch":[48.1334597651774914,71.3648635001192133,245.920143546346225],"luv":[48.1334597651774914,-29.1175429122131746,-65.1545273725937761],"rgb":[0.0666666666666666657,0.466666666666666674,0.733333333333333282],"xyz":[0.157963839704855447,0.168997406477647216,0.494447602475489967],"hpluv":[245.920143546346225,188.138070935962588,48.1334597651774914],"hsluv":[245.920143546346225,97.4206074841270464,48.1334597651774914]},"#1177cc":{"lch":[49.0951452720171488,83.8954152066927463,250.336037436893122],"luv":[49.0951452720171488,-28.2310616920000612,-79.0028344329863756],"rgb":[0.0666666666666666657,0.466666666666666674,0.8],"xyz":[0.177256215148577201,0.176714356655136018,0.596054113145760178],"hpluv":[250.336037436893122,216.83980339157776,49.0951452720171488],"hsluv":[250.336037436893122,97.666394869173061,49.0951452720171488]},"#1177dd":{"lch":[50.1353116048344702,96.3594544857749,253.441700788778064],"luv":[50.1353116048344702,-27.4615589113116805,-92.3634519220491512],"rgb":[0.0666666666666666657,0.466666666666666674,0.866666666666666696],"xyz":[0.198774540699210744,0.185321686875389552,0.709383961045766176],"hpluv":[253.441700788778064,243.887723915279508,50.1353116048344702],"hsluv":[253.441700788778064,97.8905898818146341,50.1353116048344702]},"#1177ee":{"lch":[51.2494756916451593,108.579054392530807,255.706052301270915],"luv":[51.2494756916451593,-26.8078049752198453,-105.217644172385562],"rgb":[0.0666666666666666657,0.466666666666666674,0.933333333333333348],"xyz":[0.222586720965385454,0.194846558981859597,0.834794777114289399],"hpluv":[255.706052301270915,268.841280267375566,51.2494756916451593],"hsluv":[255.706052301270915,98.0932983984315,51.2494756916451593]},"#1177ff":{"lch":[52.4328877873246739,120.474912105814852,257.407785454377859],"luv":[52.4328877873246739,-26.2648114927385947,-117.577056112809359],"rgb":[0.0666666666666666657,0.466666666666666674,1],"xyz":[0.248757769375216875,0.205314978345792293,0.972628965406071133],"hpluv":[257.407785454377859,291.562836812545811,52.4328877873246739],"hsluv":[257.407785454377859,99.9999999999990905,52.4328877873246739]},"#118800":{"lch":[49.1629818744817157,75.0325981068150725,126.891404302910644],"luv":[49.1629818744817157,-45.0420871994947447,60.0091756264985],"rgb":[0.0666666666666666657,0.533333333333333326,0],"xyz":[0.0903493506983573252,0.177267402435000831,0.0294542697314011],"hpluv":[126.891404302910644,193.664979881129256,49.1629818744817157],"hsluv":[126.891404302910644,100.000000000002487,49.1629818744817157]},"#118811":{"lch":[49.2125288978643,73.2988946552939211,127.715012949240403],"luv":[49.2125288978643,-44.8394509336223663,57.9840633075946883],"rgb":[0.0666666666666666657,0.533333333333333326,0.0666666666666666657],"xyz":[0.0913610161979944435,0.177672068634855684,0.0347823746961567343],"hpluv":[127.715012949240403,188.999680167490567,49.2125288978643],"hsluv":[127.715012949240403,96.2345237189928326,49.2125288978643]},"#118822":{"lch":[49.3041772439320312,70.1927728141533,129.31476933948187],"luv":[49.3041772439320312,-44.4727599639014315,54.3065279366352556],"rgb":[0.0666666666666666657,0.533333333333333326,0.133333333333333331],"xyz":[0.0932363743364714725,0.178422211890246513,0.0446592608921359269],"hpluv":[129.31476933948187,180.6541776618526,49.3041772439320312],"hsluv":[129.31476933948187,96.2899748067816859,49.3041772439320312]},"#118833":{"lch":[49.454516912369769,65.3819990709954766,132.170105234646456],"luv":[49.454516912369769,-43.8931572746598917,48.4581938064309767],"rgb":[0.0666666666666666657,0.533333333333333326,0.2],"xyz":[0.0963241250689292,0.179657312183229612,0.060921414749747077],"hpluv":[132.170105234646456,167.761213036429979,49.454516912369769],"hsluv":[132.170105234646456,96.3778002926358,49.454516912369769]},"#118844":{"lch":[49.6703617695526,59.1036258375550787,136.829676092870073],"luv":[49.6703617695526,-43.1056388924535057,40.4368950689726887],"rgb":[0.0666666666666666657,0.533333333333333326,0.266666666666666663],"xyz":[0.10078212031314808,0.181440510280917205,0.0844001897026337156],"hpluv":[136.829676092870073,150.9927614306973,49.6703617695526],"hsluv":[136.829676092870073,96.4975069627829356,49.6703617695526]},"#118855":{"lch":[49.9568473676091145,51.9652717779864091,144.176427678513875],"luv":[49.9568473676091145,-42.1346422468413806,30.4148219408018328],"rgb":[0.0666666666666666657,0.533333333333333326,0.333333333333333315],"xyz":[0.106744534352510079,0.183825475896662038,0.115802236976607617],"hpluv":[144.176427678513875,131.995007799011802,49.9568473676091145],"hsluv":[144.176427678513875,96.6457662191899658,49.9568473676091145]},"#118866":{"lch":[50.3177367885428879,45.0776565581224133,155.49998607369966],"luv":[50.3177367885428879,-41.0189171061503615,18.6934095394821433],"rgb":[0.0666666666666666657,0.533333333333333326,0.4],"xyz":[0.11432953012601825,0.186859474206065329,0.155749881383751593],"hpluv":[155.49998607369966,113.67882025976219,50.3177367885428879],"hsluv":[155.49998607369966,96.8171590628882512,50.3177367885428879]},"#118877":{"lch":[50.7555873970602391,40.2042284802375036,171.921706717236162],"luv":[50.7555873970602391,-39.8052806191025255,5.64974535051283322],"rgb":[0.0666666666666666657,0.533333333333333326,0.466666666666666674],"xyz":[0.12364354314622017,0.190585079414146152,0.204803683290149502],"hpluv":[171.921706717236162,100.514149141729121,50.7555873970602391],"hsluv":[171.921706717236162,97.0050762990714333,50.7555873970602391]},"#118888":{"lch":[51.2718664023781088,39.4294820301430349,192.17705063006116],"luv":[51.2718664023781088,-38.5423368357954814,-8.3169901046866368],"rgb":[0.0666666666666666657,0.533333333333333326,0.533333333333333326],"xyz":[0.134783960248164,0.19504124625492375,0.263476546693721669],"hpluv":[192.17705063006116,97.5845966821491118,51.2718664023781088],"hsluv":[192.17705063006116,97.2026219422035,51.2718664023781088]},"#118899":{"lch":[51.8670503792929907,43.7110075029310678,211.486174513119295],"luv":[51.8670503792929907,-37.2752705782940623,-22.8299448145688864],"rgb":[0.0666666666666666657,0.533333333333333326,0.6],"xyz":[0.147840924357236725,0.200264031898552908,0.332243224334839549],"hpluv":[211.486174513119295,106.93960912970239,51.8670503792929907],"hsluv":[211.486174513119295,97.4033619208845,51.8670503792929907]},"#1188aa":{"lch":[52.5407237145479371,52.059033028958666,226.184651315961702],"luv":[52.5407237145479371,-36.0423686073764102,-37.5644856890150507],"rgb":[0.0666666666666666657,0.533333333333333326,0.66666666666666663],"xyz":[0.162898617322489253,0.206287109084654,0.411547073951838149],"hpluv":[226.184651315961702,125.730132415512543,52.5407237145479371],"hsluv":[226.184651315961702,97.6018250350267,52.5407237145479371]},"#1188bb":{"lch":[53.2916815113449047,62.8295543710454396,236.286042109927791],"luv":[53.2916815113449047,-34.8733609598062557,-52.2628127623381573],"rgb":[0.0666666666666666657,0.533333333333333326,0.733333333333333282],"xyz":[0.1800362094298259,0.213142145927588761,0.501805059050479896],"hpluv":[236.286042109927791,149.604233097343041,53.2916815113449047],"hsluv":[236.286042109927791,97.7937430875757201,53.2916815113449047]},"#1188cc":{"lch":[54.1180375597057548,74.8024459991104607,243.146336149822275],"luv":[54.1180375597057548,-33.7892633498457826,-66.735984369188742],"rgb":[0.0666666666666666657,0.533333333333333326,0.8],"xyz":[0.199328584873547654,0.220859096105077563,0.60341156972075],"hpluv":[243.146336149822275,175.393335022858878,54.1180375597057548],"hsluv":[243.146336149822275,97.9760759990174819,54.1180375597057548]},"#1188dd":{"lch":[55.0173353812408266,87.2581949437380331,247.918019831454984],"luv":[55.0173353812408266,-32.8032215872693484,-80.8575366823396138],"rgb":[0.0666666666666666657,0.533333333333333326,0.866666666666666696],"xyz":[0.220846910424181198,0.229466426325331097,0.716741417620756],"hpluv":[247.918019831454984,201.254684785248685,55.0173353812408266],"hsluv":[247.918019831454984,98.1468934162311513,55.0173353812408266]},"#1188ee":{"lch":[55.9866591638471363,99.7959623087877645,251.344854654704591],"luv":[55.9866591638471363,-31.921869990719685,-94.5527805483930734],"rgb":[0.0666666666666666657,0.533333333333333326,0.933333333333333348],"xyz":[0.244659090690355907,0.238991298431801141,0.842152233689279273],"hpluv":[251.344854654704591,226.187053837960264,55.9866591638471363],"hsluv":[251.344854654704591,98.3051827042817,55.9866591638471363]},"#1188ff":{"lch":[57.0227411270994082,112.196885547184024,253.882464988485651],"luv":[57.0227411270994082,-31.1468293758358783,-107.786901552649141],"rgb":[0.0666666666666666657,0.533333333333333326,1],"xyz":[0.270830139100187328,0.249459717795733837,0.979986421981061118],"hpluv":[253.882464988485651,249.673263359937977,57.0227411270994082],"hsluv":[253.882464988485651,99.9999999999988916,57.0227411270994082]},"#119900":{"lch":[54.9698669410824721,84.160615619067471,127.079428544988929],"luv":[54.9698669410824721,-50.7422517332937772,67.1433772639973],"rgb":[0.0666666666666666657,0.6,0],"xyz":[0.116218951150824812,0.229006603339936526,0.0380774698822233526],"hpluv":[127.079428544988929,194.277964405092661,54.9698669410824721],"hsluv":[127.079428544988929,100.000000000002373,54.9698669410824721]},"#119911":{"lch":[55.0116447857556494,82.6558224500047,127.715012949240403],"luv":[55.0116447857556494,-50.563404981135136,65.3859852078442],"rgb":[0.0666666666666666657,0.6,0.0666666666666666657],"xyz":[0.11723061665046193,0.229411269539791379,0.0434055748469789823],"hpluv":[127.715012949240403,190.659367551848248,55.0116447857556494],"hsluv":[127.715012949240403,97.0796004133795094,55.0116447857556494]},"#119922":{"lch":[55.0889600096002852,79.9388272851777657,128.936266554168185],"luv":[55.0889600096002852,-50.237998627918941,62.1800579091938204],"rgb":[0.0666666666666666657,0.6,0.133333333333333331],"xyz":[0.119105974788938959,0.230161412795182208,0.0532824610429581819],"hpluv":[128.936266554168185,184.133380166819137,55.0889600096002852],"hsluv":[128.936266554168185,97.113065541928691,55.0889600096002852]},"#119933":{"lch":[55.215893256771821,75.6695179958245916,131.075562701176153],"luv":[55.215893256771821,-49.7189430335536,57.0429895547794175],"rgb":[0.0666666666666666657,0.6,0.2],"xyz":[0.122193725521396693,0.231396513088165307,0.0695446149005693182],"hpluv":[131.075562701176153,173.898642472918851,55.215893256771821],"hsluv":[131.075562701176153,97.1665253707361,55.215893256771821]},"#119944":{"lch":[55.398361036949,69.9528706873860671,134.469703140623466],"luv":[55.398361036949,-49.0042253731102591,49.9198358669935303],"rgb":[0.0666666666666666657,0.6,0.266666666666666663],"xyz":[0.126651720765615566,0.2331797111858529,0.0930233898534559567],"hpluv":[134.469703140623466,160.231519707698453,55.398361036949],"hsluv":[134.469703140623466,97.2403070889690184,55.398361036949]},"#119955":{"lch":[55.6409569880907497,63.1400511875114887,139.633471194580181],"luv":[55.6409569880907497,-48.1074656204671172,40.8942271664002206],"rgb":[0.0666666666666666657,0.6,0.333333333333333315],"xyz":[0.132614134804977579,0.235564676801597733,0.124425437127429872],"hpluv":[139.633471194580181,143.995747016857166,55.6409569880907497],"hsluv":[139.633471194580181,97.3331834328701575,55.6409569880907497]},"#119966":{"lch":[55.9472168173363,55.8918514512489466,147.340496807064056],"luv":[55.9472168173363,-47.0549257965888,30.1617807320125095],"rgb":[0.0666666666666666657,0.6,0.4],"xyz":[0.140199130578485737,0.238598675111001024,0.164373081534573834],"hpluv":[147.340496807064056,126.76791164907155,55.9472168173363],"hsluv":[147.340496807064056,97.4426708460479,55.9472168173363]},"#119977":{"lch":[56.319758368673476,49.2851425936320169,158.582299284916047],"luv":[56.319758368673476,-45.8816608646539308,17.9971796894826],"rgb":[0.0666666666666666657,0.6,0.466666666666666674],"xyz":[0.14951314359868767,0.242324280319081847,0.213426883440971743],"hpluv":[158.582299284916047,111.043862853999471,56.319758368673476],"hsluv":[158.582299284916047,97.5654087237770398,56.319758368673476]},"#119988":{"lch":[56.7603710001512951,44.8759084930006864,173.96527373508107],"luv":[56.7603710001512951,-44.6272223318454451,4.71785862613608131],"rgb":[0.0666666666666666657,0.6,0.533333333333333326],"xyz":[0.16065356070063147,0.246780447159859445,0.27209974684454391],"hpluv":[173.96527373508107,100.324581054675164,56.7603710001512951],"hsluv":[173.96527373508107,97.6975811674153647,56.7603710001512951]},"#119999":{"lch":[57.2700846473106822,44.3289506401906692,192.177050630061103],"luv":[57.2700846473106822,-43.3315696575835219,-9.35045110518458422],"rgb":[0.0666666666666666657,0.6,0.6],"xyz":[0.173710524809704198,0.252003232803488575,0.340866424485661845],"hpluv":[192.177050630061103,98.2197789195824384,57.2700846473106822],"hsluv":[192.177050630061103,97.8353178900151903,57.2700846473106822]},"#1199aa":{"lch":[57.849232550626823,48.3524707931101716,209.625127722111756],"luv":[57.849232550626823,-42.0317498411296384,-23.9017454821038378],"rgb":[0.0666666666666666657,0.6,0.66666666666666663],"xyz":[0.188768217774956726,0.258026309989589697,0.42017027410266039],"hpluv":[209.625127722111756,106.062142541807418,57.849232550626823],"hsluv":[209.625127722111756,97.9750198241805492,57.849232550626823]},"#1199bb":{"lch":[58.4975141278551263,56.1838887208170235,223.492153209811363],"luv":[58.4975141278551263,-40.7596491137071553,-38.6688551152037832],"rgb":[0.0666666666666666657,0.6,0.733333333333333282],"xyz":[0.2059058098822934,0.264881346832524456,0.510428259201302192],"hpluv":[223.492153209811363,121.8747424026255,58.4975141278551263],"hsluv":[223.492153209811363,98.1135798876595686,58.4975141278551263]},"#1199cc":{"lch":[59.2140605434028515,66.4726101728698211,233.498474693243395],"luv":[59.2140605434028515,-39.5408457239066138,-53.4334111079625274],"rgb":[0.0666666666666666657,0.6,0.8],"xyz":[0.225198185326015127,0.272598297010013257,0.612034769871572348],"hpluv":[233.498474693243395,142.448281943894045,59.2140605434028515],"hsluv":[233.498474693243395,98.2484961488154482,59.2140605434028515]},"#1199dd":{"lch":[59.9975033067865553,78.1149919905427907,240.560044488236258],"luv":[59.9975033067865553,-38.3943918665613637,-68.0281018909060151],"rgb":[0.0666666666666666657,0.6,0.866666666666666696],"xyz":[0.24671651087664867,0.281205627230266819,0.725364617771578346],"hpluv":[240.560044488236258,165.211601361935521,59.9975033067865553],"hsluv":[240.560044488236258,98.3778942246892285,59.9975033067865553]},"#1199ee":{"lch":[60.8460449735814706,90.4022578402124,245.60857134482],"luv":[60.8460449735814706,-37.3332566635070862,-82.3334450239084106],"rgb":[0.0666666666666666657,0.6,0.933333333333333348],"xyz":[0.270528691142823408,0.290730499336736836,0.850775433840101569],"hpluv":[245.60857134482,188.532510112076068,60.8460449735814706],"hsluv":[245.60857134482,98.5004851129029788,60.8460449735814706]},"#1199ff":{"lch":[61.7575303771721877,102.910627792409826,249.306643617498082],"luv":[61.7575303771721877,-36.3651553748586,-96.2713497733895167],"rgb":[0.0666666666666666657,0.6,1],"xyz":[0.296699739552654829,0.301198918700669505,0.988609622131883414],"hpluv":[249.306643617498082,211.450946466820028,61.7575303771721877],"hsluv":[249.306643617498082,99.9999999999986215,61.7575303771721877]},"#000000":{"lch":[0,0,0],"luv":[0,0,0],"rgb":[0,0,0],"xyz":[0,0,0],"hpluv":[0,0,0],"hsluv":[0,0,0]},"#000011":{"lch":[0.365533479526218952,1.47895322486610792,265.8743202181779],"luv":[0.365533479526218952,-0.106402530834795422,-1.47512072142377915],"rgb":[0,0,0.0666666666666666657],"xyz":[0.00101166549963712174,0.000404666199854854377,0.00532810496475563146],"hpluv":[265.8743202181779,513.41269684428039,0.365533479526218952],"hsluv":[265.8743202181779,100.000000000000867,0.365533479526218952]},"#000022":{"lch":[1.04313510374015572,4.22053823263236,265.8743202181779],"luv":[1.04313510374015572,-0.303644457367982512,-4.20960128950726],"rgb":[0,0,0.133333333333333331],"xyz":[0.0028870236381141408,0.00115480945524567245,0.0152049911607348275],"hpluv":[265.8743202181779,513.41269684428039,1.04313510374015572],"hsluv":[265.8743202181779,100.000000000000838,1.04313510374015572]},"#000033":{"lch":[2.15879662382733661,8.73451929157831,265.8743202181779],"luv":[2.15879662382733661,-0.62840050829424543,-8.71188498868810868],"rgb":[0,0,0.2],"xyz":[0.00597477437057188088,0.00238990974822878574,0.0314671450183459725],"hpluv":[265.8743202181779,513.412696844280276,2.15879662382733661],"hsluv":[265.8743202181779,100.000000000000838,2.15879662382733661]},"#000044":{"lch":[3.76955286085941,15.251660031516769,265.874320218177957],"luv":[3.76955286085941,-1.0972728545435857,-15.2121374566379668],"rgb":[0,0,0.266666666666666663],"xyz":[0.0104327696147907597,0.00417310784591636182,0.054945919971232611],"hpluv":[265.874320218177957,513.41269684428039,3.76955286085941],"hsluv":[265.874320218177957,100.000000000000981,3.76955286085941]},"#000055":{"lch":[5.92388346812606947,23.9681097618519345,265.8743202181779],"luv":[5.92388346812606947,-1.7243733575266309,-23.905999708860417],"rgb":[0,0,0.333333333333333315],"xyz":[0.0163951836541527535,0.00655807346166119385,0.0863479672452065194],"hpluv":[265.8743202181779,513.41269684428039,5.92388346812606947],"hsluv":[265.8743202181779,100.000000000000838,5.92388346812606947]},"#000066":{"lch":[8.64689012997685,34.9854302247980513,265.8743202181779],"luv":[8.64689012997685,-2.51700882467034193,-34.8947703043127149],"rgb":[0,0,0.4],"xyz":[0.0239801794276609283,0.00959207177106450627,0.126295611652350481],"hpluv":[265.8743202181779,513.412696844280276,8.64689012997685],"hsluv":[265.8743202181779,100.000000000000838,8.64689012997685]},"#000077":{"lch":[11.4958709948623863,46.5124439559768703,265.874320218177957],"luv":[11.4958709948623863,-3.34631391244679577,-46.3919133681426672],"rgb":[0,0,0.466666666666666674],"xyz":[0.0332941924478628443,0.0133176769791453226,0.17534941355874839],"hpluv":[265.874320218177957,513.412696844280276,11.4958709948623863],"hsluv":[265.874320218177957,100.000000000001,11.4958709948623863]},"#000088":{"lch":[14.2727431262745554,57.7477048111956535,265.874320218177957],"luv":[14.2727431262745554,-4.15462898927595781,-57.598059593379169],"rgb":[0,0,0.533333333333333326],"xyz":[0.0444346095498066723,0.0177738438199229153,0.234022276962320558],"hpluv":[265.874320218177957,513.41269684428039,14.2727431262745554],"hsluv":[265.874320218177957,100.000000000000952,14.2727431262745554]},"#000099":{"lch":[16.9872454361813823,68.7306165552763701,265.874320218177957],"luv":[16.9872454361813823,-4.94478893879780923,-68.5525106354185567],"rgb":[0,0,0.6],"xyz":[0.0574915736588793858,0.0229966294635520763,0.302788954603438465],"hpluv":[265.874320218177957,513.412696844280163,16.9872454361813823],"hsluv":[265.874320218177957,100.000000000000952,16.9872454361813823]},"#0000aa":{"lch":[19.6469460262523299,79.4917998262647529,265.8743202181779],"luv":[19.6469460262523299,-5.71899674710351302,-79.2858077831434116],"rgb":[0,0,0.66666666666666663],"xyz":[0.0725492666241319278,0.0290197066496531778,0.382092804220437066],"hpluv":[265.8743202181779,513.41269684428039,19.6469460262523299],"hsluv":[265.8743202181779,100.000000000000824,19.6469460262523299]},"#0000bb":{"lch":[22.2578820656552736,90.0556810893410926,265.8743202181779],"luv":[22.2578820656552736,-6.47900976369593895,-89.8223142039161644],"rgb":[0,0,0.733333333333333282],"xyz":[0.0896868587314685745,0.0358747434925879363,0.472350789319078812],"hpluv":[265.8743202181779,513.41269684428039,22.2578820656552736],"hsluv":[265.8743202181779,100.000000000000796,22.2578820656552736]},"#0000cc":{"lch":[24.8249727536546274,100.442163488877583,265.874320218177957],"luv":[24.8249727536546274,-7.22625991008361535,-100.18188146585355],"rgb":[0,0,0.8],"xyz":[0.108979234175190315,0.043591693670076738,0.573957299989349],"hpluv":[265.874320218177957,513.41269684428039,24.8249727536546274],"hsluv":[265.874320218177957,100.000000000001,24.8249727536546274]},"#0000dd":{"lch":[27.3522973211786535,110.667751646404724,265.8743202181779],"luv":[27.3522973211786535,-7.96193460279319343,-110.380971421034161],"rgb":[0,0,0.866666666666666696],"xyz":[0.130497559725823858,0.052199023890330272,0.687287147889355],"hpluv":[265.8743202181779,513.412696844280276,27.3522973211786535],"hsluv":[265.8743202181779,100.000000000000824,27.3522973211786535]},"#0000ee":{"lch":[29.8432887766479737,120.746335558760222,265.8743202181779],"luv":[29.8432887766479737,-8.68703315051946,-120.433438072283309],"rgb":[0,0,0.933333333333333348],"xyz":[0.154309739991998596,0.0617238959968003,0.812697963957878189],"hpluv":[265.8743202181779,513.41269684428039,29.8432887766479737],"hsluv":[265.8743202181779,100.000000000000838,29.8432887766479737]},"#0000ff":{"lch":[32.3008729039800215,130.68975298582734,265.8743202181779],"luv":[32.3008729039800215,-9.40240721482262,-130.351088503561101],"rgb":[0,0,1],"xyz":[0.18048078840183,0.072192315360733,0.95053215224966],"hpluv":[265.8743202181779,513.41269684428039,32.3008729039800215],"hsluv":[265.8743202181779,100.000000000000824,32.3008729039800215]},"#001100":{"lch":[3.62113466359794112,5.60448249758782424,127.715012949240474],"luv":[3.62113466359794112,-3.42845440085753106,4.43350025228474376],"rgb":[0,0.0666666666666666657,0],"xyz":[0.00200440026092840902,0.00400880052185687355,0.00066813342030945088],"hpluv":[127.715012949240474,196.394882900214469,3.62113466359794112],"hsluv":[127.715012949240474,100.000000000002217,3.62113466359794112]},"#001111":{"lch":[3.9866681431241604,3.15408977882195618,192.17705063006116],"luv":[3.9866681431241604,-3.08312421078118115,-0.665302512969894178],"rgb":[0,0.0666666666666666657,0.0666666666666666657],"xyz":[0.00301606576056553076,0.00441346672171172814,0.00599623838506508234],"hpluv":[192.17705063006116,100.392967527320764,3.9866681431241604],"hsluv":[192.17705063006116,99.9999999999914,3.9866681431241604]},"#001122":{"lch":[4.66426976733809706,7.30142401028103372,246.87889630792742],"luv":[4.66426976733809706,-2.86709314837997242,-6.71495118794031054],"rgb":[0,0.0666666666666666657,0.133333333333333331],"xyz":[0.00489142389904254939,0.005163609977102546,0.0158731245810442775],"hpluv":[246.87889630792742,198.638412351210178,4.66426976733809706],"hsluv":[246.87889630792742,99.9999999999921414,4.66426976733809706]},"#001133":{"lch":[5.77993128742527773,13.8979406242137369,257.974087263939282],"luv":[5.77993128742527773,-2.89569220292521434,-13.5929290537429299],"rgb":[0,0.0666666666666666657,0.2],"xyz":[0.00797917463150029,0.00639871027008565886,0.0321352784386554208],"hpluv":[257.974087263939282,305.117489912579458,5.77993128742527773],"hsluv":[257.974087263939282,99.9999999999925,5.77993128742527773]},"#001144":{"lch":[7.39068752445735111,21.802452480470059,261.611708702028636],"luv":[7.39068752445735111,-3.1805605696034065,-21.569213444774455],"rgb":[0,0.0666666666666666657,0.266666666666666663],"xyz":[0.0124371698757191687,0.00818190836777323537,0.0556140533915420593],"hpluv":[261.611708702028636,374.334482048802613,7.39068752445735111],"hsluv":[261.611708702028636,99.9999999999929656,7.39068752445735111]},"#001155":{"lch":[9.4550232844459714,31.0886305445366773,263.238579866128873],"luv":[9.4550232844459714,-3.6602302532303379,-30.8724094237562916],"rgb":[0,0.0666666666666666657,0.333333333333333315],"xyz":[0.0183995839150811608,0.0105668739835180665,0.0870161006655159747],"hpluv":[263.238579866128873,417.232678203522596,9.4550232844459714],"hsluv":[263.238579866128873,99.9999999999929514,9.4550232844459714]},"#001166":{"lch":[11.6894020192987682,40.9340765206813302,264.100423242359113],"luv":[11.6894020192987682,-4.20741678933990659,-40.7172723123955791],"rgb":[0,0.0666666666666666657,0.4],"xyz":[0.025984579688589339,0.0136008722929213798,0.126963745072659923],"hpluv":[264.100423242359113,444.357002567308371,11.6894020192987682],"hsluv":[264.100423242359113,99.9999999999928235,11.6894020192987682]},"#001177":{"lch":[14.0165943101603965,51.0460922578313898,264.608714664977526],"luv":[14.0165943101603965,-4.79613195559092276,-50.8202779710972621],"rgb":[0,0.0666666666666666657,0.466666666666666674],"xyz":[0.0352985927087912515,0.0173264775010021979,0.176017546979057832],"hpluv":[264.608714664977526,462.124851551559573,14.0165943101603965],"hsluv":[264.608714664977526,99.9999999999931504,14.0165943101603965]},"#001188":{"lch":[16.3962585295353378,61.2721603523949625,264.931782730652174],"luv":[16.3962585295353378,-5.41289085195630371,-61.0325998698597871],"rgb":[0,0.0666666666666666657,0.533333333333333326],"xyz":[0.0464390098107350796,0.0217826443417797888,0.23469041038263],"hpluv":[264.931782730652174,474.195864485329537,16.3962585295353378],"hsluv":[264.931782730652174,99.9999999999933209,16.3962585295353378]},"#001199":{"lch":[18.8023327262484941,71.5200065602600148,265.148843888859801],"luv":[18.8023327262484941,-6.04826966448705239,-71.2638040834565771],"rgb":[0,0.0666666666666666657,0.6],"xyz":[0.059495973919807793,0.0270054299854089498,0.303457088023747934],"hpluv":[265.148843888859801,482.675370310212884,18.8023327262484941],"hsluv":[265.148843888859801,99.9999999999930651,18.8023327262484941]},"#0011aa":{"lch":[21.2181090603332123,81.7349311996174919,265.301088447161305],"luv":[21.2181090603332123,-6.69569086805443892,-81.4602154551880488],"rgb":[0,0.0666666666666666657,0.66666666666666663],"xyz":[0.0745536668850603351,0.0330285071715100548,0.382760937640746535],"hpluv":[265.301088447161305,488.81030222212587,21.2181090603332123],"hsluv":[265.301088447161305,99.9999999999931788,21.2181090603332123]},"#0011bb":{"lch":[23.6329047323064216,91.8852368853417,265.411605614461337],"luv":[23.6329047323064216,-7.35054503717070418,-91.590754146539723],"rgb":[0,0.0666666666666666657,0.733333333333333282],"xyz":[0.0916912589923969817,0.0398835440144448133,0.473018922739388281],"hpluv":[265.411605614461337,493.364573724961247,23.6329047323064216],"hsluv":[265.411605614461337,99.999999999993,23.6329047323064216]},"#0011cc":{"lch":[26.0399131129061345,101.953231398784169,265.494123438592396],"luv":[26.0399131129061345,-8.00958293333115,-101.638122640513785],"rgb":[0,0.0666666666666666657,0.8],"xyz":[0.110983634436118722,0.047600494191933615,0.574625433409658437],"hpluv":[265.494123438592396,496.821968194535657,26.0399131129061345],"hsluv":[265.494123438592396,99.9999999999927383,26.0399131129061345]},"#0011dd":{"lch":[28.43483595206839,111.929749681002491,265.557201901085818],"luv":[28.43483595206839,-8.67050070949402496,-111.593419524175076],"rgb":[0,0.0666666666666666657,0.866666666666666696],"xyz":[0.132501959986752266,0.056207824412187149,0.687955281309664435],"hpluv":[265.557201901085818,499.498435149301031,28.43483595206839],"hsluv":[265.557201901085818,99.9999999999932214,28.43483595206839]},"#0011ee":{"lch":[30.8150119654139019,121.810820553676152,265.60639254385444],"luv":[30.8150119654139019,-9.33165721729429798,-121.452855781734542],"rgb":[0,0.0666666666666666657,0.933333333333333348],"xyz":[0.156314140252927,0.0657326965186571799,0.813366097378187658],"hpluv":[265.60639254385444,501.606152289563909,30.8150119654139019],"hsluv":[265.60639254385444,99.9999999999933635,30.8150119654139019]},"#0011ff":{"lch":[33.1788572452669683,131.59562707663585,265.645416939351662],"luv":[33.1788572452669683,-9.99188030865750321,-131.215743695604147],"rgb":[0,0.0666666666666666657,1],"xyz":[0.182485188662758396,0.076201115882589876,0.951200285669969503],"hpluv":[265.645416939351662,503.291227463659,33.1788572452669683],"hsluv":[265.645416939351662,99.9999999999995,33.1788572452669683]},"#55aa00":{"lch":[62.2364297391950743,84.7105424007581291,119.071642820441127],"luv":[62.2364297391950743,-41.1610955154551661,74.0380997176333437],"rgb":[0.333333333333333315,0.66666666666666663,0],"xyz":[0.181203244729902568,0.306798408854291604,0.0496696976001293408],"hpluv":[119.071642820441127,172.715819722381468,62.2364297391950743],"hsluv":[119.071642820441127,100.00000000000216,62.2364297391950743]},"#55aa11":{"lch":[62.270812500354296,83.3839078255156352,119.512873370738717],"luv":[62.270812500354296,-41.0765057562956173,72.5644317769187808],"rgb":[0.333333333333333315,0.66666666666666663,0.0666666666666666657],"xyz":[0.182214910229539701,0.307203075054146457,0.0549978025648849705],"hpluv":[119.512873370738717,169.917081036602212,62.270812500354296],"hsluv":[119.512873370738717,97.826098763204655,62.270812500354296]},"#55aa22":{"lch":[62.3344691942433826,80.9675552156414,120.358690899927907],"luv":[62.3344691942433826,-40.9219560187625078,69.8651451955580285],"rgb":[0.333333333333333315,0.66666666666666663,0.133333333333333331],"xyz":[0.184090268368016702,0.307953218309537258,0.0648746887608641631],"hpluv":[120.358690899927907,164.824621464176317,62.3344691942433826],"hsluv":[120.358690899927907,93.8493385636837729,62.3344691942433826]},"#55aa33":{"lch":[62.4390542004851312,77.1100321042589343,121.835090998601942],"luv":[62.4390542004851312,-40.6737079775314143,65.5103543760705094],"rgb":[0.333333333333333315,0.66666666666666663,0.2],"xyz":[0.187178019100474435,0.309188318602520384,0.0811368426184753133],"hpluv":[121.835090998601942,156.708983850290963,62.4390542004851312],"hsluv":[121.835090998601942,87.4478443822902,62.4390542004851312]},"#55aa44":{"lch":[62.5895604608427192,71.8060360850404322,124.168545167949461],"luv":[62.5895604608427192,-40.3283690350335959,59.4115264003566281],"rgb":[0.333333333333333315,0.66666666666666663,0.266666666666666663],"xyz":[0.191636014344693323,0.310971516700207951,0.104615617571361952],"hpluv":[124.168545167949461,145.578881327618376,62.5895604608427192],"hsluv":[124.168545167949461,78.5131262768233569,62.5895604608427192]},"#55aa55":{"lch":[62.7899606618147317,65.2067785819955361,127.715012949239551],"luv":[62.7899606618147317,-39.8892256495404212,51.582687503865472],"rgb":[0.333333333333333315,0.66666666666666663,0.333333333333333315],"xyz":[0.197598428384055336,0.313356482315952811,0.136017664845335867],"hpluv":[127.715012949239551,131.777681042751937,62.7899606618147317],"hsluv":[127.715012949239551,67.0983271543339583,62.7899606618147317]},"#55aa66":{"lch":[63.0434325957243402,57.6560159218084,133.059858461454269],"luv":[63.0434325957243402,-39.3653397911167744,42.1258376190401904],"rgb":[0.333333333333333315,0.66666666666666663,0.4],"xyz":[0.205183424157563493,0.31639048062535613,0.175965309252479829],"hpluv":[133.059858461454269,116.049721847977636,63.0434325957243402],"hsluv":[133.059858461454269,68.1368400599560289,63.0434325957243402]},"#55aa77":{"lch":[63.3524771250310863,49.7736139598232441,141.163090238883626],"luv":[63.3524771250310863,-38.7703675351212311,31.2133184364163512],"rgb":[0.333333333333333315,0.66666666666666663,0.466666666666666674],"xyz":[0.214497437177765427,0.320116085833436925,0.225019111158877738],"hpluv":[141.163090238883626,99.6953496544650903,63.3524771250310863],"hsluv":[141.163090238883626,69.3257510819996696,63.3524771250310863]},"#55aa88":{"lch":[63.7189896387035901,42.62351374085884,153.427389325734708],"luv":[63.7189896387035901,-38.1211143540371324,19.0668446268281784],"rgb":[0.333333333333333315,0.66666666666666663,0.533333333333333326],"xyz":[0.225637854279709227,0.324572252674214523,0.283691974562449878],"hpluv":[153.427389325734708,84.882799477410984,63.7189896387035901],"hsluv":[153.427389325734708,70.6362499177174783,63.7189896387035901]},"#55aa99":{"lch":[64.1443101574831473,37.9035590936142697,170.991128647613664],"luv":[64.1443101574831473,-37.4359848569684885,5.93521943587048106],"rgb":[0.333333333333333315,0.66666666666666663,0.6],"xyz":[0.238694818388781954,0.329795038317843681,0.352458652203567813],"hpluv":[170.991128647613664,74.9827180661079211,64.1443101574831473],"hsluv":[170.991128647613664,72.0364673991159918,64.1443101574831473]},"#55aaaa":{"lch":[64.6292640862610881,37.5790288142004414,192.177050630061],"luv":[64.6292640862610881,-36.7335179653569526,-7.92666793219602717],"rgb":[0.333333333333333315,0.66666666666666663,0.66666666666666663],"xyz":[0.253752511354034482,0.335818115503944803,0.431762501820566413],"hpluv":[192.177050630061,73.7828909738526,64.6292640862610881],"hsluv":[192.177050630061,73.4940830927894808,64.6292640862610881]},"#55aabb":{"lch":[65.1741997434662466,42.3607987370151307,211.725401595501864],"luv":[65.1741997434662466,-36.0311661131296148,-22.2753751520813346],"rgb":[0.333333333333333315,0.66666666666666663,0.733333333333333282],"xyz":[0.270890103461371157,0.342673152346879561,0.522020486919208104],"hpluv":[211.725401595501864,82.4760322189960249,65.1741997434662466],"hsluv":[211.725401595501864,74.9784994025983,65.1741997434662466]},"#55aacc":{"lch":[65.7790257871148327,51.0903516363502916,226.227071697937646],"luv":[65.7790257871148327,-35.3444111183519212,-36.8916878581455236],"rgb":[0.333333333333333315,0.66666666666666663,0.8],"xyz":[0.290182478905092855,0.350390102524368363,0.62362699758947826],"hpluv":[226.227071697937646,98.5577501957325808,65.7790257871148327],"hsluv":[226.227071697937646,76.4624197891718,65.7790257871148327]},"#55aadd":{"lch":[66.4432499478878071,62.1650528283955595,236.084470311275709],"luv":[66.4432499478878071,-34.6862381636523693,-51.5883579425784404],"rgb":[0.333333333333333315,0.66666666666666663,0.866666666666666696],"xyz":[0.311700804455726455,0.358997432744621869,0.736956845489484258],"hpluv":[236.084470311275709,118.722973851074883,66.4432499478878071],"hsluv":[236.084470311275709,77.9227939155192928,66.4432499478878071]},"#55aaee":{"lch":[67.1660194476775274,74.4631251020179832,242.774049894046698],"luv":[67.1660194476775274,-34.0669326024190866,-66.2133000462974763],"rgb":[0.333333333333333315,0.66666666666666663,0.933333333333333348],"xyz":[0.335512984721901164,0.368522304851091886,0.862367661558007481],"hpluv":[242.774049894046698,140.679551339708695,67.1660194476775274],"hsluv":[242.774049894046698,80.7550915423080085,67.1660194476775274]},"#55aaff":{"lch":[67.9461628502375135,87.3278464079016,247.446578213330071],"luv":[67.9461628502375135,-33.4941307575646476,-80.6492155140842897],"rgb":[0.333333333333333315,0.66666666666666663,1],"xyz":[0.361684033131732585,0.37899072421502461,1.00020184984978933],"hpluv":[247.446578213330071,163.089927997179217,67.9461628502375135],"hsluv":[247.446578213330071,99.9999999999981,67.9461628502375135]},"#002200":{"lch":[10.1376941245203973,15.6902558355344119,127.715012949240474],"luv":[10.1376941245203973,-9.59826829561359141,12.4119850914324186],"rgb":[0,0.133333333333333331,0],"xyz":[0.00572002399569634425,0.0114400479913928481,0.00190667466523206119],"hpluv":[127.715012949240474,196.394882900214583,10.1376941245203973],"hsluv":[127.715012949240474,100.000000000002331,10.1376941245203973]},"#002211":{"lch":[10.4423176349325608,11.2803579121031614,143.951720967420982],"luv":[10.4423176349325608,-9.12041102953238614,6.63811549142769763],"rgb":[0,0.133333333333333331,0.0666666666666666657],"xyz":[0.00673168949533346599,0.0118447141912477027,0.00723477962998769243],"hpluv":[143.951720967420982,137.077225818420459,10.4423176349325608],"hsluv":[143.951720967420982,99.9999999999911,10.4423176349325608]},"#002222":{"lch":[10.9891417742670896,8.69416226881610399,192.17705063006116],"luv":[10.9891417742670896,-8.49854762011842,-1.83388819318003415],"rgb":[0,0.133333333333333331,0.133333333333333331],"xyz":[0.00860704763381048461,0.0125948574466385205,0.0171116658259668902],"hpluv":[192.17705063006116,100.392967527320849,10.9891417742670896],"hsluv":[192.17705063006116,99.9999999999915,10.9891417742670896]},"#002233":{"lch":[11.8439988341371283,14.4341695325786503,236.81663495428262],"luv":[11.8439988341371283,-7.90011340243738758,-12.0802921456333543],"rgb":[0,0.133333333333333331,0.2],"xyz":[0.0116947983662682251,0.0138299577396216334,0.0333738196835780335],"hpluv":[236.81663495428262,154.643892414528665,11.8439988341371283],"hsluv":[236.81663495428262,99.9999999999918572,11.8439988341371283]},"#002244":{"lch":[12.9926705590666103,23.9154033254141893,251.756603241059679],"luv":[12.9926705590666103,-7.48682111947174356,-22.713300635140282],"rgb":[0,0.133333333333333331,0.266666666666666663],"xyz":[0.0161527936104871039,0.0156131558373092099,0.056852594636464672],"hpluv":[251.756603241059679,233.570832873869165,12.9926705590666103],"hsluv":[251.756603241059679,99.9999999999922551,12.9926705590666103]},"#002255":{"lch":[14.3995425627967926,34.0053227001087492,257.612107564284656],"luv":[14.3995425627967926,-7.29512566501762105,-33.2135983216232162],"rgb":[0,0.133333333333333331,0.333333333333333315],"xyz":[0.022115207649849096,0.0179981214530540411,0.0882546419104385804],"hpluv":[257.612107564284656,299.666041626864057,14.3995425627967926],"hsluv":[257.612107564284656,99.9999999999922551,14.3995425627967926]},"#002266":{"lch":[16.0198287291043684,44.1221041927951489,260.479541157990241],"luv":[16.0198287291043684,-7.29778599254304705,-43.514392998258792],"rgb":[0,0.133333333333333331,0.4],"xyz":[0.0297002034233572743,0.0210321197624573561,0.128202286317582542],"hpluv":[260.479541157990241,349.492349810916096,16.0198287291043684],"hsluv":[260.479541157990241,99.9999999999926672,16.0198287291043684]},"#002277":{"lch":[17.8086814865908138,54.1839210795750787,262.094384047744654],"luv":[17.8086814865908138,-7.45254493353373,-53.6689563674502708],"rgb":[0,0.133333333333333331,0.466666666666666674],"xyz":[0.0390142164435591868,0.0247577249705381724,0.177256088223980451],"hpluv":[262.094384047744654,386.080609388904179,17.8086814865908138],"hsluv":[262.094384047744654,99.9999999999926246,17.8086814865908138]},"#002288":{"lch":[19.7262797638069571,64.1945165648047862,263.091662768615947],"luv":[19.7262797638069571,-7.7213996360385595,-63.7284547486409778],"rgb":[0,0.133333333333333331,0.533333333333333326],"xyz":[0.0501546335455030148,0.0292138918113157633,0.235928951627552619],"hpluv":[263.091662768615947,412.944865974292611,19.7262797638069571],"hsluv":[263.091662768615947,99.9999999999928093,19.7262797638069571]},"#002299":{"lch":[21.7396965211461932,74.1610579713059082,263.749129578079874],"luv":[21.7396965211461932,-8.0748025544340809,-73.7201470639492129],"rgb":[0,0.133333333333333331,0.6],"xyz":[0.0632115976545757352,0.0344366774549449278,0.304695629268670554],"hpluv":[263.749129578079874,432.874263951475,21.7396965211461932],"hsluv":[263.749129578079874,99.9999999999928662,21.7396965211461932]},"#0022aa":{"lch":[23.8228560713303921,84.0831956926279389,264.204285416148139],"luv":[23.8228560713303921,-8.49087961251211532,-83.6533846373868641],"rgb":[0,0.133333333333333331,0.66666666666666663],"xyz":[0.0782692906198282773,0.0404597546410460224,0.383999478885669154],"hpluv":[264.204285416148139,447.872821658188343,23.8228560713303921],"hsluv":[264.204285416148139,99.9999999999925251,23.8228560713303921]},"#0022bb":{"lch":[25.9556350824861326,93.9557715434331868,264.531619021467236],"luv":[25.9556350824861326,-8.95364890679069,-93.5281731756572725],"rgb":[0,0.133333333333333331,0.733333333333333282],"xyz":[0.0954068827271649239,0.0473147914839807809,0.474257463984310901],"hpluv":[264.531619021467236,459.336683180505304,25.9556350824861326],"hsluv":[264.531619021467236,99.9999999999932783,25.9556350824861326]},"#0022cc":{"lch":[28.122733334265547,103.772183036952711,264.774345627526145],"luv":[28.122733334265547,-9.45141286238599143,-103.340876555018355],"rgb":[0,0.133333333333333331,0.8],"xyz":[0.114699258170886664,0.0550317416614695826,0.575863974654581057],"hpluv":[264.774345627526145,468.233789407088068,28.122733334265547],"hsluv":[264.774345627526145,99.9999999999932,28.122733334265547]},"#0022dd":{"lch":[30.3126112219004114,113.526334495176528,264.958927468127968],"luv":[30.3126112219004114,-9.97554123069502552,-113.087210599012522],"rgb":[0,0.133333333333333331,0.866666666666666696],"xyz":[0.136217583721520208,0.0636390718817231166,0.689193822554587],"hpluv":[264.958927468127968,475.239568383116307,30.3126112219004114],"hsluv":[264.958927468127968,99.9999999999930651,30.3126112219004114]},"#0022ee":{"lch":[32.516600051948771,123.213441320075319,265.102292473050682],"luv":[32.516600051948771,-10.519601137712538,-122.76355368691101],"rgb":[0,0.133333333333333331,0.933333333333333348],"xyz":[0.160029763987694945,0.0731639439881931475,0.814604638623110278],"hpluv":[265.102292473050682,480.830806343612153,32.516600051948771],"hsluv":[265.102292473050682,99.9999999999931504,32.516600051948771]},"#0022ff":{"lch":[34.728199222084136,132.830192238289243,265.215668718406278],"luv":[34.728199222084136,-11.0787458291525667,-132.367372720447662],"rgb":[0,0.133333333333333331,1],"xyz":[0.186200812397526339,0.0836323633521258575,0.952438826914892123],"hpluv":[265.215668718406278,485.348691920142073,34.728199222084136],"hsluv":[265.215668718406278,99.9999999999995595,34.728199222084136]},"#55bb00":{"lch":[67.6287132051522093,94.1564927152114421,120.799924159261636],"luv":[67.6287132051522093,-48.2120532219748839,80.8767150949587403],"rgb":[0.333333333333333315,0.733333333333333282,0],"xyz":[0.215157742638501348,0.374707404671490163,0.0609878635696619598],"hpluv":[120.799924159261636,176.668237076728246,67.6287132051522093],"hsluv":[120.799924159261636,100.000000000002245,67.6287132051522093]},"#55bb11":{"lch":[67.658807387059241,92.983820197622,121.168613223743336],"luv":[67.658807387059241,-48.1245535730997,79.5614112615755],"rgb":[0.333333333333333315,0.733333333333333282,0.0666666666666666657],"xyz":[0.21616940813813848,0.375112070871345,0.0663159685344176],"hpluv":[121.168613223743336,174.390319462510746,67.658807387059241],"hsluv":[121.168613223743336,98.2170484527892853,67.658807387059241]},"#55bb22":{"lch":[67.7145367797011,90.8418307914021739,121.8702408444057],"luv":[67.7145367797011,-47.9642424389893733,77.1470651987980318],"rgb":[0.333333333333333315,0.733333333333333282,0.133333333333333331],"xyz":[0.218044766276615481,0.375862214126735816,0.076192854730396789],"hpluv":[121.8702408444057,170.232819736279453,67.7145367797011],"hsluv":[121.8702408444057,94.9476871157098401,67.7145367797011]},"#55bb33":{"lch":[67.8061331119394595,87.4049119918652337,123.079288563119889],"luv":[67.8061331119394595,-47.7055226907732077,73.2379802090816128],"rgb":[0.333333333333333315,0.733333333333333282,0.2],"xyz":[0.221132517009073215,0.377097314419718943,0.0924550085880079253],"hpluv":[123.079288563119889,163.570954697809668,67.8061331119394595],"hsluv":[123.079288563119889,89.6636954368763526,67.8061331119394595]},"#55bb44":{"lch":[67.9380247917114701,82.6396215824211851,124.951716096499126],"luv":[67.9380247917114701,-47.3430758547011195,67.7343356349037293],"rgb":[0.333333333333333315,0.733333333333333282,0.266666666666666663],"xyz":[0.225590512253292103,0.378880512517406509,0.115933783540894564],"hpluv":[124.951716096499126,154.352877500253584,67.9380247917114701],"hsluv":[124.951716096499126,82.2446152582935213,67.9380247917114701]},"#55bb55":{"lch":[68.1137800414251,76.6309713561306154,127.715012949239735],"luv":[68.1137800414251,-46.8777966745344372,60.6199467990950964],"rgb":[0.333333333333333315,0.733333333333333282,0.333333333333333315],"xyz":[0.231552926292654115,0.38126547813315137,0.147335830814868479],"hpluv":[127.715012949239735,142.760701907139094,68.1137800414251],"hsluv":[127.715012949239735,72.6906423420812189,68.1137800414251]},"#55bb66":{"lch":[68.3363083667640723,69.6002362161904813,131.717534816394789],"luv":[68.3363083667640723,-46.3160914021311,51.9520216986687728],"rgb":[0.333333333333333315,0.733333333333333282,0.4],"xyz":[0.239137922066162273,0.384299476442554688,0.187283475222012441],"hpluv":[131.717534816394789,129.240469264566769,68.3363083667640723],"hsluv":[131.717534816394789,73.4099839145656432,68.3363083667640723]},"#55bb77":{"lch":[68.6079661831172416,61.943926665161591,137.498849513297245],"luv":[68.6079661831172416,-45.6690129611091,41.8496273084595956],"rgb":[0.333333333333333315,0.733333333333333282,0.466666666666666674],"xyz":[0.248451935086364206,0.388025081650635484,0.23633727712841035],"hpluv":[137.498849513297245,114.568047908441656,68.6079661831172416],"hsluv":[137.498849513297245,74.2430874467708719,68.6079661831172416]},"#55bb88":{"lch":[68.930619778887035,54.3103726013937376,145.860320527647957],"luv":[68.930619778887035,-44.9511677919681389,30.4796503628330058],"rgb":[0.333333333333333315,0.733333333333333282,0.533333333333333326],"xyz":[0.259592352188308,0.392481248491413082,0.29501014053198249],"hpluv":[145.860320527647957,99.9792617157038421,68.930619778887035],"hsluv":[145.860320527647957,75.173468866550067,68.930619778887035]},"#55bb99":{"lch":[69.3056876145919176,47.7211055743842465,157.786981113384826],"luv":[69.3056876145919176,-44.1794697421466793,18.0410191104680564],"rgb":[0.333333333333333315,0.733333333333333282,0.6],"xyz":[0.272649316297380762,0.397704034135042239,0.363776818173100425],"hpluv":[157.786981113384826,87.3737441175344713,69.3056876145919176],"hsluv":[157.786981113384826,76.1818306173187807,69.3056876145919176]},"#55bbaa":{"lch":[69.7341725511637378,43.6310575063277497,173.751690425302456],"luv":[69.7341725511637378,-43.37186937951342,4.74869725787034813],"rgb":[0.333333333333333315,0.733333333333333282,0.66666666666666663],"xyz":[0.287707009262633262,0.403727111321143362,0.443080667790099],"hpluv":[173.751690425302456,79.3943164161001675,69.7341725511637378],"hsluv":[173.751690425302456,77.247554342351421,69.7341725511637378]},"#55bbbb":{"lch":[70.2166895771587605,43.5254926875218899,192.177050630061075],"luv":[70.2166895771587605,-42.5461891389785691,-9.18097508120914796],"rgb":[0.333333333333333315,0.733333333333333282,0.733333333333333282],"xyz":[0.304844601369969936,0.41058214816407812,0.533338652888740716],"hpluv":[192.177050630061075,78.6579587560082274,70.2166895771587605],"hsluv":[192.177050630061075,78.350068429440185,70.2166895771587605]},"#55bbcc":{"lch":[70.753492069254392,47.9033609117661214,209.436295084806801],"luv":[70.753492069254392,-41.719164640327179,-23.5423721905035848],"rgb":[0.333333333333333315,0.733333333333333282,0.8],"xyz":[0.324136976813691691,0.418299098341566922,0.634945163559010872],"hpluv":[209.436295084806801,85.9127126132272849,70.753492069254392],"hsluv":[209.436295084806801,79.4699741443966445,70.753492069254392]},"#55bbdd":{"lch":[71.3444981992470701,55.9344204047748121,223.003151043067675],"luv":[71.3444981992470701,-40.905747403701568,-38.1494327004039633],"rgb":[0.333333333333333315,0.733333333333333282,0.866666666666666696],"xyz":[0.345655302364325179,0.426906428561820428,0.74827501145901687],"hpluv":[223.003151043067675,99.4850863142100081,71.3444981992470701],"hsluv":[223.003151043067675,80.5898663629362,71.3444981992470701]},"#55bbee":{"lch":[71.9893182406489,66.3452770137575385,232.793079173014576],"luv":[71.9893182406489,-40.1186787976363846,-52.8411524624918272],"rgb":[0.333333333333333315,0.733333333333333282,0.933333333333333348],"xyz":[0.369467482630499944,0.436431300668290445,0.873685827527540093],"hpluv":[232.793079173014576,116.944897502912525,71.9893182406489],"hsluv":[232.793079173014576,81.6948376758296888,71.9893182406489]},"#55bbff":{"lch":[72.6872829834048417,78.1278426582451146,239.741904567598624],"luv":[72.6872829834048417,-39.3683093588317803,-67.4840426816504788],"rgb":[0.333333333333333315,0.733333333333333282,1],"xyz":[0.395638531040331309,0.446899720032223169,1.01152001581932205],"hpluv":[239.741904567598624,136.39131713467242,72.6872829834048417],"hsluv":[239.741904567598624,99.9999999999976126,72.6872829834048417]},"#003300":{"lch":[17.3086983277836381,26.7889227675687067,127.71501294924046],"luv":[17.3086983277836381,-16.3877039844862402,21.1917328494772867],"rgb":[0,0.2,0],"xyz":[0.0118377460847071559,0.0236754921694146449,0.00394591536156894181],"hpluv":[127.71501294924046,196.394882900214611,17.3086983277836381],"hsluv":[127.71501294924046,100.000000000002402,17.3086983277836381]},"#003311":{"lch":[17.4974002223845133,22.6621201022865968,134.58430385811792],"luv":[17.4974002223845133,-15.9078557679049606,16.1403783226414816],"rgb":[0,0.2,0.0666666666666666657],"xyz":[0.0128494115843442776,0.0240801583692694977,0.00927402032632457241],"hpluv":[134.58430385811792,164.348724425256108,17.4974002223845133],"hsluv":[134.58430385811792,99.9999999999909335,17.4974002223845133]},"#003322":{"lch":[17.8416856931397234,17.1190432019509622,152.323942273369369],"luv":[17.8416856931397234,-15.1604156821769873,7.95131665159082601],"rgb":[0,0.2,0.133333333333333331],"xyz":[0.0147247697228212963,0.0248303016246603156,0.0191509065223037685],"hpluv":[152.323942273369369,121.753913655152402,17.8416856931397234],"hsluv":[152.323942273369369,99.9999999999912177,17.8416856931397234]},"#003333":{"lch":[18.3937448040413543,14.5523831926532932,192.17705063006116],"luv":[18.3937448040413543,-14.2249612699966086,-3.06958196712752551],"rgb":[0,0.2,0.2],"xyz":[0.017812520455279035,0.0260654019176434319,0.0354130603799149152],"hpluv":[192.17705063006116,100.392967527320849,18.3937448040413543],"hsluv":[192.17705063006116,99.9999999999915,18.3937448040413543]},"#003344":{"lch":[19.1608294605123817,20.3566459399555,229.223567805483242],"luv":[19.1608294605123817,-13.2951121246929258,-15.41535038578591],"rgb":[0,0.2,0.266666666666666663],"xyz":[0.0222705156994979156,0.0278486000153310084,0.0588918353328015537],"hpluv":[229.223567805483242,134.812835768594709,19.1608294605123817],"hsluv":[229.223567805483242,99.9999999999917577,19.1608294605123817]},"#003355":{"lch":[20.1371955335767296,30.6237106081975696,245.893961784182551],"luv":[20.1371955335767296,-12.5075398018694361,-27.9530517031554915],"rgb":[0,0.2,0.333333333333333315],"xyz":[0.0282329297388599076,0.0302335656310758379,0.0902938826067754552],"hpluv":[245.893961784182551,192.973712242731381,20.1371955335767296],"hsluv":[245.893961784182551,99.9999999999920419,20.1371955335767296]},"#003366":{"lch":[21.3076868402923836,41.8514919359335167,253.451236278131262],"luv":[21.3076868402923836,-11.9206140697212764,-40.1179054471226308],"rgb":[0,0.2,0.4],"xyz":[0.0358179255123680859,0.0332675639404791529,0.130241527013919417],"hpluv":[253.451236278131262,249.237832456686277,21.3076868402923836],"hsluv":[253.451236278131262,99.9999999999920846,21.3076868402923836]},"#003377":{"lch":[22.6513946103128916,53.0186284648852251,257.430711853641924],"luv":[22.6513946103128916,-11.5379190314963687,-51.7479602372902434],"rgb":[0,0.2,0.466666666666666674],"xyz":[0.04513193853257,0.0369931691485599692,0.179295328920317326],"hpluv":[257.430711853641924,297.011229333042763,22.6513946103128916],"hsluv":[257.430711853641924,99.9999999999922409,22.6513946103128916]},"#003388":{"lch":[24.1449124481648099,63.8887963004775941,259.778872090702237],"luv":[24.1449124481648099,-11.3369168899634012,-62.8748964862287636],"rgb":[0,0.2,0.533333333333333326],"xyz":[0.0562723556345138265,0.0414493359893375601,0.237968192323889494],"hpluv":[259.778872090702237,335.767299249073346,24.1449124481648099],"hsluv":[259.778872090702237,99.9999999999925677,24.1449124481648099]},"#003399":{"lch":[25.764809398314533,74.4520642325545481,261.279947020055374],"luv":[25.764809398314533,-11.2874373109462987,-73.5914643653724596],"rgb":[0,0.2,0.6],"xyz":[0.0693293197435865399,0.0466721216329667177,0.306734869965007428],"hpluv":[261.279947020055374,366.681615332004242,25.764809398314533],"hsluv":[261.279947020055374,99.9999999999928662,25.764809398314533]},"#0033aa":{"lch":[27.4892253326185596,84.7561178753388447,262.297068677869788],"luv":[27.4892253326185596,-11.360446037840406,-83.9913077831251],"rgb":[0,0.2,0.66666666666666663],"xyz":[0.084387012708839082,0.0526951988190678261,0.386038719582006029],"hpluv":[262.297068677869788,391.244172205097584,27.4892253326185596],"hsluv":[262.297068677869788,99.9999999999925819,27.4892253326185596]},"#0033bb":{"lch":[29.2987082140811808,94.8530383242648725,263.01737082090068],"luv":[29.2987082140811808,-11.531133966499155,-94.1495184734957178],"rgb":[0,0.2,0.733333333333333282],"xyz":[0.101524604816175729,0.0595502356620025847,0.476296704680647776],"hpluv":[263.01737082090068,410.811034734971429,29.2987082140811808],"hsluv":[263.01737082090068,99.999999999992923,29.2987082140811808]},"#0033cc":{"lch":[31.1765026722858281,104.784892642773883,263.545454640352943],"luv":[31.1765026722858281,-11.7793879173059235,-104.120697973319764],"rgb":[0,0.2,0.8],"xyz":[0.120816980259897469,0.0672671858394913863,0.577903215350917931],"hpluv":[263.545454640352943,426.491720029659064,31.1765026722858281],"hsluv":[263.545454640352943,99.9999999999928519,31.1765026722858281]},"#0033dd":{"lch":[33.108496036114694,114.582250296133722,263.943596884481394],"luv":[33.108496036114694,-12.0892798728815123,-113.942711022166648],"rgb":[0,0.2,0.866666666666666696],"xyz":[0.142335305810531026,0.0758745160597449203,0.691233063250923929],"hpluv":[263.943596884481394,439.154381827523366,33.108496036114694],"hsluv":[263.943596884481394,99.999999999992724,33.108496036114694]},"#0033ee":{"lch":[35.0829820911796091,124.266344101148519,264.250786122927309],"luv":[35.0829820911796091,-12.4483077284590724,-123.641271066592637],"rgb":[0,0.2,0.933333333333333348],"xyz":[0.166147486076705764,0.0853993881662149512,0.816643879319447152],"hpluv":[264.250786122927309,449.46548401365061,35.0829820911796091],"hsluv":[264.250786122927309,99.9999999999932214,35.0829820911796091]},"#0033ff":{"lch":[37.0903499028545482,133.851694130242549,264.492451291459133],"luv":[37.0903499028545482,-12.8466699872586325,-133.233776092154926],"rgb":[0,0.2,1],"xyz":[0.192318534486537157,0.0958678075301476473,0.954478067611229],"hpluv":[264.492451291459133,457.933345064777,37.0903499028545482],"hsluv":[264.492451291459133,99.999999999999531,37.0903499028545482]},"#55cc00":{"lch":[72.9678739916599,103.392643433537373,122.072672834653702],"luv":[72.9678739916599,-54.9009234662034089,87.6123696673744661],"rgb":[0.333333333333333315,0.8,0],"xyz":[0.253381485948118268,0.451154891290725057,0.0737291113395339148],"hpluv":[122.072672834653702,179.803140433373983,72.9678739916599],"hsluv":[122.072672834653702,100.000000000002444,72.9678739916599]},"#55cc11":{"lch":[72.9944661394807497,102.34674561848017,122.382691003133885],"luv":[72.9944661394807497,-54.8140205324080085,86.4307786136781431],"rgb":[0.333333333333333315,0.8,0.0666666666666666657],"xyz":[0.254393151447755372,0.45155955749057991,0.0790572163042895515],"hpluv":[122.382691003133885,177.91945009175069,72.9944661394807497],"hsluv":[122.382691003133885,98.5172338886757757,72.9944661394807497]},"#55cc22":{"lch":[73.0437189009647909,100.431941691916791,122.969611448584445],"luv":[73.0437189009647909,-54.6544745496639663,84.2583130836875114],"rgb":[0.333333333333333315,0.8,0.133333333333333331],"xyz":[0.256268509586232429,0.452309700745970711,0.0889341025002687441],"hpluv":[122.969611448584445,174.473032354427772,73.0437189009647909],"hsluv":[122.969611448584445,95.7933392549395,73.0437189009647909]},"#55cc33":{"lch":[73.1246943704287702,97.3470237033586,123.971835615120114],"luv":[73.1246943704287702,-54.3960871275936384,80.7310889875115123],"rgb":[0.333333333333333315,0.8,0.2],"xyz":[0.259356260318690135,0.453544801038953838,0.10519625635787988],"hpluv":[123.971835615120114,168.926560877595364,73.1246943704287702],"hsluv":[123.971835615120114,91.3772697810403542,73.1246943704287702]},"#55cc44":{"lch":[73.2413452172043,93.0415252478239836,125.502056943833935],"luv":[73.2413452172043,-54.0322080150887771,75.7446098244333399],"rgb":[0.333333333333333315,0.8,0.266666666666666663],"xyz":[0.26381425556290905,0.455327999136641404,0.128675031310766519],"hpluv":[125.502056943833935,161.198069131808353,73.2413452172043],"hsluv":[125.502056943833935,85.148140842768953,73.2413452172043]},"#55cc55":{"lch":[73.3968865779486919,87.5572640326508207,127.71501294923992],"luv":[73.3968865779486919,-53.5617851120033066,69.2633356148757144],"rgb":[0.333333333333333315,0.8,0.333333333333333315],"xyz":[0.269776669602271035,0.457712964752386264,0.160077078584740434],"hpluv":[127.71501294923992,151.374900584602614,73.3968865779486919],"hsluv":[127.71501294923992,77.0768048277098,73.3968865779486919]},"#55cc66":{"lch":[73.5939772793636,81.0375847478920832,130.834727778769576],"luv":[73.5939772793636,-52.9887999422805,61.3129449826769175],"rgb":[0.333333333333333315,0.8,0.4],"xyz":[0.277361665375779221,0.460746963061789583,0.200024722991884396],"hpluv":[130.834727778769576,139.728031568505,73.5939772793636],"hsluv":[130.834727778769576,77.5857883184082766,73.5939772793636]},"#55cc77":{"lch":[73.8348152761848553,73.7475179973278898,135.191797127350526],"luv":[73.8348152761848553,-52.3216404431773,51.972515352838883],"rgb":[0.333333333333333315,0.8,0.466666666666666674],"xyz":[0.286675678395981126,0.464472568269870378,0.249078524898282305],"hpluv":[135.191797127350526,126.743455190439619,73.8348152761848553],"hsluv":[135.191797127350526,78.1806978106980779,73.8348152761848553]},"#55cc88":{"lch":[74.1211942127210648,66.1117924559585646,141.267644806662588],"luv":[74.1211942127210648,-51.5723023085178269,41.3650424433311343],"rgb":[0.333333333333333315,0.8,0.533333333333333326],"xyz":[0.297816095497924926,0.468928735110648,0.307751388301854445],"hpluv":[141.267644806662588,113.181605649052202,74.1211942127210648],"hsluv":[141.267644806662588,78.852064841283763,74.1211942127210648]},"#55cc99":{"lch":[74.4545405069311,58.7794373107374923,149.710693018120253],"luv":[74.4545405069311,-50.7554383804248,29.646377947024483],"rgb":[0.333333333333333315,0.8,0.6],"xyz":[0.310873059606997626,0.474151520754277134,0.37651806594297238],"hpluv":[149.710693018120253,100.178278207809555,74.4545405069311],"hsluv":[149.710693018120253,79.5881732610030355,74.4545405069311]},"#55ccaa":{"lch":[74.8359403329188808,52.7021878309829361,161.189357777818884],"luv":[74.8359403329188808,-49.8873315971569866,16.9933736582089097],"rgb":[0.333333333333333315,0.8,0.66666666666666663],"xyz":[0.325930752572250182,0.480174597940378256,0.455821915559971],"hpluv":[161.189357777818884,89.3630022499163772,74.8359403329188808],"hsluv":[161.189357777818884,80.3759073289738097,74.8359403329188808]},"#55ccbb":{"lch":[75.2661614985634,49.1164256219739315,175.805780272408356],"luv":[75.2661614985634,-48.9848849956305372,3.59225665059547428],"rgb":[0.333333333333333315,0.8,0.733333333333333282],"xyz":[0.343068344679586856,0.487029634783313,0.546079900658612782],"hpluv":[175.805780272408356,82.8068592257523619,75.2661614985634],"hsluv":[175.805780272408356,81.2015846585734238,75.2661614985634]},"#55cccc":{"lch":[75.7456730324682894,49.1710405410584386,192.177050630061103],"luv":[75.7456730324682894,-48.0647147647340063,-10.3718090261618983],"rgb":[0.333333333333333315,0.8,0.8],"xyz":[0.362360720123308555,0.494746584960801816,0.647686411328882938],"hpluv":[192.177050630061103,82.3741405590198639,75.7456730324682894],"hsluv":[192.177050630061103,82.0517040066532815,75.7456730324682894]},"#55ccdd":{"lch":[76.2746640882933349,53.2320113470533514,207.674227909684788],"luv":[76.2746640882933349,-47.1424090239802069,-24.7232745296525458],"rgb":[0.333333333333333315,0.8,0.866666666666666696],"xyz":[0.383879045673942154,0.503353915181055322,0.761016259228888936],"hpluv":[207.674227909684788,89.8673986045394,76.2746640882933349],"hsluv":[207.674227909684788,82.913556803619926,76.2746640882933349]},"#55ccee":{"lch":[76.8530630513789674,60.6801737900983298,220.368363670310828],"luv":[76.8530630513789674,-46.2319850937693673,-39.3025068600722207],"rgb":[0.333333333333333315,0.8,0.933333333333333348],"xyz":[0.407691225940116864,0.51287878728752534,0.886427075297412159],"hpluv":[220.368363670310828,105.55411204702996,76.8530630513789674],"hsluv":[220.368363670310828,83.7756757275922865,76.8530630513789674]},"#55ccff":{"lch":[77.4805572724897758,70.4908339109135511,229.9629437526429],"luv":[77.4805572724897758,-45.3455492712466395,-53.9697955040126445],"rgb":[0.333333333333333315,0.8,1],"xyz":[0.433862274349948285,0.523347206651458063,1.02426126358919389],"hpluv":[229.9629437526429,126.754412601721029,77.4805572724897758],"hsluv":[229.9629437526429,99.9999999999968878,77.4805572724897758]},"#004400":{"lch":[24.1097877444294397,37.3150672336374782,127.715012949240432],"luv":[24.1097877444294397,-22.8269080205926969,29.5185791133361732],"rgb":[0,0.266666666666666663,0],"xyz":[0.0206703165676731908,0.0413406331353469575,0.00689010552255753684],"hpluv":[127.715012949240432,196.39488290021464,24.1097877444294397],"hsluv":[127.715012949240432,100.000000000002458,24.1097877444294397]},"#004411":{"lch":[24.2402356883412295,33.8624764720942082,131.447767669063751],"luv":[24.2402356883412295,-22.4148262696793807,25.3819399598065552],"rgb":[0,0.266666666666666663,0.0666666666666666657],"xyz":[0.0216819820673103125,0.0417452993352018104,0.0122182104873131692],"hpluv":[131.447767669063751,177.264269230781338,24.2402356883412295],"hsluv":[131.447767669063751,99.9999999999909335,24.2402356883412295]},"#004422":{"lch":[24.4798388415780295,28.4164365287723264,139.862893984056171],"luv":[24.4798388415780295,-21.7244820008415864,18.3177713379345413],"rgb":[0,0.266666666666666663,0.133333333333333331],"xyz":[0.0235573402057873311,0.0424954425905926317,0.0220950966832923652],"hpluv":[139.862893984056171,147.299199898382369,24.4798388415780295],"hsluv":[139.862893984056171,99.9999999999911466,24.4798388415780295]},"#004433":{"lch":[24.8682723444395748,22.2877633761795622,158.664607016269599],"luv":[24.8682723444395748,-20.760308528761005,8.10888316004269427],"rgb":[0,0.266666666666666663,0.2],"xyz":[0.0266450909382450717,0.0437305428835757445,0.0383572505409035119],"hpluv":[158.664607016269599,113.726114076625834,24.8682723444395748],"hsluv":[158.664607016269599,99.9999999999913172,24.8682723444395748]},"#004444":{"lch":[25.4163828994624552,20.1084089871091685,192.17705063006116],"luv":[25.4163828994624552,-19.6559790417892835,-4.24153273022775057],"rgb":[0,0.266666666666666663,0.266666666666666663],"xyz":[0.0311030861824639487,0.0455137409812633176,0.0618360254937901505],"hpluv":[192.17705063006116,100.392967527320792,25.4163828994624552],"hsluv":[192.17705063006116,99.9999999999914166,25.4163828994624552]},"#004455":{"lch":[26.1275223832094383,25.5944772375096541,223.546306053382096],"luv":[26.1275223832094383,-18.5513329588178841,-17.6330743352507042],"rgb":[0,0.266666666666666663,0.333333333333333315],"xyz":[0.0370655002218259477,0.0478987065970081505,0.093238072767764052],"hpluv":[223.546306053382096,124.304646155287358,26.1275223832094383],"hsluv":[223.546306053382096,99.999999999991644,26.1275223832094383]},"#004466":{"lch":[26.9988561724938734,35.8050932952910514,240.647496291889979],"luv":[26.9988561724938734,-17.5509899640931621,-31.2084516944496499],"rgb":[0,0.266666666666666663,0.4],"xyz":[0.0446504959953341191,0.050932704906411462,0.133185717174908],"hpluv":[240.647496291889979,168.282428552664811,26.9988561724938734],"hsluv":[240.647496291889979,99.9999999999920135,26.9988561724938734]},"#004477":{"lch":[28.0227048150185141,47.4927479988473422,249.396343090395135],"luv":[28.0227048150185141,-16.7127641209025413,-44.4549730392572684],"rgb":[0,0.266666666666666663,0.466666666666666674],"xyz":[0.0539645090155360385,0.0546583101144922784,0.182239519081305923],"hpluv":[249.396343090395135,215.05848085050232,28.0227048150185141],"hsluv":[249.396343090395135,99.9999999999921698,28.0227048150185141]},"#004488":{"lch":[29.1879465441319326,59.3592998069098,254.306666373838311],"luv":[29.1879465441319326,-16.0560040952153962,-57.1465765034181956],"rgb":[0,0.266666666666666663,0.533333333333333326],"xyz":[0.0651049261174798666,0.0591144769552698762,0.24091238248487809],"hpluv":[254.306666373838311,258.06229412425995,29.1879465441319326],"hsluv":[254.306666373838311,99.9999999999923,29.1879465441319326]},"#004499":{"lch":[30.4813623757938217,70.9684349502494,257.321762040288718],"luv":[30.4813623757938217,-15.5758442205369825,-69.2380808233832141],"rgb":[0,0.266666666666666663,0.6],"xyz":[0.07816189022655258,0.0643372625988990338,0.309679060125996],"hpluv":[257.321762040288718,295.440602563868879,30.4813623757938217],"hsluv":[257.321762040288718,99.9999999999926672,30.4813623757938217]},"#0044aa":{"lch":[31.8888011745219231,82.2033012029853,259.305022310013101],"luv":[31.8888011745219231,-15.2553283345918977,-80.7753532092093138],"rgb":[0,0.266666666666666663,0.66666666666666663],"xyz":[0.0932195831918051221,0.0703603397850001422,0.388982909742994598],"hpluv":[259.305022310013101,327.107417663034141,31.8888011745219231],"hsluv":[259.305022310013101,99.9999999999925109,31.8888011745219231]},"#0044bb":{"lch":[33.3960915948532602,93.0667571364503,260.679203518012741],"luv":[33.3960915948532602,-15.0732787327803415,-91.8379962332527],"rgb":[0,0.266666666666666663,0.733333333333333282],"xyz":[0.110357175299141769,0.0772153766279349,0.479240894841636345],"hpluv":[260.679203518012741,353.621176249907,33.3960915948532602],"hsluv":[260.679203518012741,99.9999999999929656,33.3960915948532602]},"#0044cc":{"lch":[34.9896851087579108,103.60206818965716,261.670396128603272],"luv":[34.9896851087579108,-15.0085679377548828,-102.509177255659253],"rgb":[0,0.266666666666666663,0.8],"xyz":[0.129649550742863495,0.0849323268054237,0.5808474055119065],"hpluv":[261.670396128603272,375.722944942957042,34.9896851087579108],"hsluv":[261.670396128603272,99.999999999992923,34.9896851087579108]},"#0044dd":{"lch":[36.6570567010139499,113.860579159150092,262.408492682931296],"luv":[36.6570567010139499,-15.0420602661110987,-112.862606338006245],"rgb":[0,0.266666666666666663,0.866666666666666696],"xyz":[0.151167876293497039,0.0935396570256772364,0.694177253411912498],"hpluv":[262.408492682931296,394.144185167595538,36.6570567010139499],"hsluv":[262.408492682931296,99.9999999999929088,36.6570567010139499]},"#0044ee":{"lch":[38.386911506645724,123.889384104134805,262.972536787865295],"luv":[38.386911506645724,-15.1572566165017264,-122.958680318078251],"rgb":[0,0.266666666666666663,0.933333333333333348],"xyz":[0.174980056559671776,0.103064529132147253,0.819588069480435721],"hpluv":[262.972536787865295,409.534269399062566,38.386911506645724],"hsluv":[262.972536787865295,99.9999999999931,38.386911506645724]},"#0044ff":{"lch":[40.1692504091911928,133.727629508879147,263.412926975584469],"luv":[40.1692504091911928,-15.3403008413028115,-132.844849595919101],"rgb":[0,0.266666666666666663,1],"xyz":[0.201151104969503169,0.113532948496079963,0.957422257772217566],"hpluv":[263.412926975584469,422.441664595501038,40.1692504091911928],"hsluv":[263.412926975584469,99.9999999999994174,40.1692504091911928]},"#55dd00":{"lch":[78.2526895057908121,112.425836873112019,123.034721453117925],"luv":[78.2526895057908121,-61.2886270790004488,94.2511166373683267],"rgb":[0.333333333333333315,0.866666666666666696,0],"xyz":[0.296015476495293473,0.536422872385076577,0.0879404415219252333],"hpluv":[123.034721453117925,210.800433631405241,78.2526895057908121],"hsluv":[123.034721453117925,100.000000000002288,78.2526895057908121]},"#55dd11":{"lch":[78.2763843013059102,111.485784004679431,123.297526334296066],"luv":[78.2763843013059102,-61.2042162992655108,93.1832814529020368],"rgb":[0.333333333333333315,0.866666666666666696,0.0666666666666666657],"xyz":[0.297027141994930577,0.536827538584931485,0.09326854648668087],"hpluv":[123.297526334296066,209.310421322213443,78.2763843013059102],"hsluv":[123.297526334296066,98.7516598856555561,78.2763843013059102]},"#55dd22":{"lch":[78.3202766586626353,109.761570808260174,123.793166282380056],"luv":[78.3202766586626353,-61.0490014413279738,91.2174426812846235],"rgb":[0.333333333333333315,0.866666666666666696,0.133333333333333331],"xyz":[0.298902500133407634,0.537577681840322286,0.103145432682660063],"hpluv":[123.793166282380056,206.571995072092733,78.3202766586626353],"hsluv":[123.793166282380056,96.4551350157602201,78.3202766586626353]},"#55dd33":{"lch":[78.3924559312840898,106.97461234521991,124.633900466047606],"luv":[78.3924559312840898,-60.7969536029841819,88.018737317724927],"rgb":[0.333333333333333315,0.866666666666666696,0.2],"xyz":[0.30199025086586534,0.538812782133305412,0.119407586540271199],"hpluv":[124.633900466047606,202.130538056106332,78.3924559312840898],"hsluv":[124.633900466047606,92.7228817840519355,78.3924559312840898]},"#55dd44":{"lch":[78.4964717161501255,103.064578059178857,125.904361390923313],"luv":[78.4964717161501255,-60.4405744101571756,83.4819993500804],"rgb":[0.333333333333333315,0.866666666666666696,0.266666666666666663],"xyz":[0.306448246110084255,0.540595980230993,0.142886361493157837],"hpluv":[125.904361390923313,195.867385876976442,78.4964717161501255],"hsluv":[125.904361390923313,87.4391970363123221,78.4964717161501255]},"#55dd55":{"lch":[78.635232286968062,98.0447483954765175,127.715012949239977],"luv":[78.635232286968062,-59.9773394353739,77.559599291037074],"rgb":[0.333333333333333315,0.866666666666666696,0.333333333333333315],"xyz":[0.31241066014944624,0.542980945846737728,0.174288408767131753],"hpluv":[127.715012949239977,187.771435495127037,78.635232286968062],"hsluv":[127.715012949239977,80.5594181605674891,78.635232286968062]},"#55dd66":{"lch":[78.8111684825228451,92.0067823672703469,130.218539636235505],"luv":[78.8111684825228451,-59.4092211255214551,70.2551951590567],"rgb":[0.333333333333333315,0.866666666666666696,0.4],"xyz":[0.319995655922954425,0.546014944156141,0.214236053174275715],"hpluv":[130.218539636235505,177.951388767985407,78.8111684825228451],"hsluv":[130.218539636235505,80.9267337864342693,78.8111684825228451]},"#55dd77":{"lch":[79.0263204832261863,85.1319563310668315,133.631202536406704],"luv":[79.0263204832261863,-58.7422260542947043,61.618186170487121],"rgb":[0.333333333333333315,0.866666666666666696,0.466666666666666674],"xyz":[0.329309668943156331,0.549740549364221898,0.263289855080673596],"hpluv":[133.631202536406704,166.66460441184762,79.0263204832261863],"hsluv":[133.631202536406704,81.3592233632428,79.0263204832261863]},"#55dd88":{"lch":[79.2823892178289,77.7115996221609322,138.259418470179128],"luv":[79.2823892178289,-57.9858175415186921,51.7371982221375],"rgb":[0.333333333333333315,0.866666666666666696,0.533333333333333326],"xyz":[0.340450086045100131,0.55419671620499944,0.321962718484245791],"hpluv":[138.259418470179128,154.371102046011828,79.2823892178289],"hsluv":[138.259418470179128,81.8514411788724,79.2823892178289]},"#55dd99":{"lch":[79.5807696492298504,70.1818875082800133,144.522564094970761],"luv":[79.5807696492298504,-57.1522092721799737,40.7323251181885624],"rgb":[0.333333333333333315,0.866666666666666696,0.6],"xyz":[0.353507050154172831,0.559419501848628653,0.390729396125363726],"hpluv":[144.522564094970761,141.828030979619143,79.5807696492298504],"hsluv":[144.522564094970761,82.3962440946128538,79.5807696492298504]},"#55ddaa":{"lch":[79.9225742847085,63.1747442044720913,152.933070337002903],"luv":[79.9225742847085,-56.2555676549498145,28.7464678372812266],"rgb":[0.333333333333333315,0.866666666666666696,0.66666666666666663],"xyz":[0.368564743119425386,0.56544257903472972,0.470033245742362271],"hpluv":[152.933070337002903,130.236824512138128,79.9225742847085],"hsluv":[152.933070337002903,82.9852754707380313,79.9225742847085]},"#55ddbb":{"lch":[80.3086513857730466,57.5612329714098649,163.927126105934775],"luv":[80.3086513857730466,-55.3111838467604926,15.9363886328987263],"rgb":[0.333333333333333315,0.866666666666666696,0.733333333333333282],"xyz":[0.385702335226762061,0.572297615877664478,0.560291230841004073],"hpluv":[163.927126105934775,121.406580229851471,80.3086513857730466],"hsluv":[163.927126105934775,83.6094665313934,80.3086513857730466]},"#55ddcc":{"lch":[80.7396004409482657,54.3905283694756534,177.403333860877041],"luv":[80.7396004409482657,-54.3346805898883076,2.46415533308082235],"rgb":[0.333333333333333315,0.866666666666666696,0.8],"xyz":[0.40499471067048376,0.580014566055153336,0.661897741511274229],"hpluv":[177.403333860877041,117.73427745544555,80.7396004409482657],"hsluv":[177.403333860877041,84.2595162876674664,80.7396004409482657]},"#55dddd":{"lch":[81.2157864208401605,54.5690893966631236,192.177050630061103],"luv":[81.2157864208401605,-53.3413100060750764,-11.5104371948595805],"rgb":[0.333333333333333315,0.866666666666666696,0.866666666666666696],"xyz":[0.426513036221117359,0.588621896275406842,0.775227589411280227],"hpluv":[192.177050630061103,121.625211000731653,81.2157864208401605],"hsluv":[192.177050630061103,84.9263164151063847,81.2157864208401605]},"#55ddee":{"lch":[81.73735371635685,58.3741450028570412,206.269517439458],"luv":[81.73735371635685,-52.3453816136692609,-25.8360567528002605],"rgb":[0.333333333333333315,0.866666666666666696,0.933333333333333348],"xyz":[0.450325216487292068,0.598146768381876859,0.90063840547980345],"hpluv":[206.269517439458,134.436687270459231,81.73735371635685],"hsluv":[206.269517439458,85.6012975649288421,81.73735371635685]},"#55ddff":{"lch":[82.3042402700550753,65.3293790133507599,218.171205050819623],"luv":[82.3042402700550753,-51.3598401525014268,-40.374429801293],"rgb":[0.333333333333333315,0.866666666666666696,1],"xyz":[0.476496264897123489,0.608615187745809583,1.0384725937715853],"hpluv":[218.171205050819623,156.046773818868189,82.3042402700550753],"hsluv":[218.171205050819623,99.9999999999958078,82.3042402700550753]},"#005500":{"lch":[30.6325595368381371,47.4104554868850059,127.715012949240474],"luv":[30.6325595368381371,-29.0026036892131067,37.5046699588244152],"rgb":[0,0.333333333333333315,0],"xyz":[0.0324835732820191528,0.0649671465640392215,0.0108278577606727468],"hpluv":[127.715012949240474,196.39488290021464,30.6325595368381371],"hsluv":[127.715012949240474,100.000000000002331,30.6325595368381371]},"#005511":{"lch":[30.7291805540017933,44.5531719728198325,130.030983014983661],"luv":[30.7291805540017933,-28.6566785608310113,34.1142185415534129],"rgb":[0,0.333333333333333315,0.0666666666666666657],"xyz":[0.033495238781656278,0.0653718127638940744,0.01615596272542838],"hpluv":[130.030983014983661,183.978458821492183,30.7291805540017933],"hsluv":[130.030983014983661,99.9999999999909335,30.7291805540017933]},"#005522":{"lch":[30.9072407208055395,39.7502752239143149,134.89425658266407],"luv":[30.9072407208055395,-28.0557665948475297,28.1595159964153758],"rgb":[0,0.333333333333333315,0.133333333333333331],"xyz":[0.0353705969201332932,0.0661219560192848888,0.0260328489214075726],"hpluv":[134.89425658266407,163.199653312489744,30.9072407208055395],"hsluv":[134.89425658266407,99.9999999999909903,30.9072407208055395]},"#005533":{"lch":[31.1975029455576802,33.2574057268159,144.773587953954916],"luv":[31.1975029455576802,-27.1672793055155033,19.1831689460880241],"rgb":[0,0.333333333333333315,0.2],"xyz":[0.0384583476525910337,0.067357056312268,0.0422950027790187227],"hpluv":[144.773587953954916,135.271984326190278,31.1975029455576802],"hsluv":[144.773587953954916,99.9999999999912177,31.1975029455576802]},"#005544":{"lch":[31.6103799108948778,27.1932751339722678,163.464109831123295],"luv":[31.6103799108948778,-26.0686058468900157,7.73963834500076331],"rgb":[0,0.333333333333333315,0.266666666666666663],"xyz":[0.0429163428968099142,0.0691402544099555816,0.0657737777319053613],"hpluv":[163.464109831123295,109.16191048912313,31.6103799108948778],"hsluv":[163.464109831123295,99.999999999991374,31.6103799108948778]},"#005555":{"lch":[32.1516370434520482,25.4370682812028797,192.17705063006116],"luv":[32.1516370434520482,-24.8647459548099441,-5.36552433088697445],"rgb":[0,0.333333333333333315,0.333333333333333315],"xyz":[0.0488787569361719063,0.0715252200257004145,0.0971758250058792628],"hpluv":[192.17705063006116,100.392967527320877,32.1516370434520482],"hsluv":[192.17705063006116,99.9999999999915,32.1516370434520482]},"#005566":{"lch":[32.8230722751799036,30.545644410021449,219.238547792689275],"luv":[32.8230722751799036,-23.6581848019048806,-19.3216636007977201],"rgb":[0,0.333333333333333315,0.4],"xyz":[0.0564637527096800845,0.074559218335103733,0.137123469413023225],"hpluv":[219.238547792689275,118.088984839520307,32.8230722751799036],"hsluv":[219.238547792689275,99.9999999999916298,32.8230722751799036]},"#005577":{"lch":[33.6230950493472065,40.3511971197610322,236.060106868306946],"luv":[33.6230950493472065,-22.5289967279924532,-33.4763112577822142],"rgb":[0,0.333333333333333315,0.466666666666666674],"xyz":[0.065777765729882,0.0782848235431845424,0.186177271319421134],"hpluv":[236.060106868306946,152.285327594300298,33.6230950493472065],"hsluv":[236.060106868306946,99.9999999999918,33.6230950493472065]},"#005588":{"lch":[34.5473308406039266,52.0529464564925703,245.569923252468897],"luv":[34.5473308406039266,-21.5281838547339461,-47.3924733973576764],"rgb":[0,0.333333333333333315,0.533333333333333326],"xyz":[0.076918182831825832,0.0827409903839621402,0.244850134722993301],"hpluv":[245.569923252468897,191.192188881903292,34.5473308406039266],"hsluv":[245.569923252468897,99.9999999999919709,34.5473308406039266]},"#005599":{"lch":[35.5892574919693772,64.2489447401961655,251.22324643469841],"luv":[35.5892574919693772,-20.6805523421639172,-60.8296116628390067],"rgb":[0,0.333333333333333315,0.6],"xyz":[0.0899751469408985316,0.0879637760275913,0.313616812364111208],"hpluv":[251.22324643469841,229.079590546614583,35.5892574919693772],"hsluv":[251.22324643469841,99.9999999999922125,35.5892574919693772]},"#0055aa":{"lch":[36.7408379507026,76.3552968047486189,254.821646708521541],"luv":[36.7408379507026,-19.9916927982920107,-73.6916791042243347],"rgb":[0,0.333333333333333315,0.66666666666666663],"xyz":[0.105032839906151088,0.0939868532136924,0.392920661981109809],"hpluv":[254.821646708521541,263.711724718685957,36.7408379507026],"hsluv":[254.821646708521541,99.9999999999921556,36.7408379507026]},"#0055bb":{"lch":[37.993106438468665,88.146943006458784,257.249085911334646],"luv":[37.993106438468665,-19.4551758320548949,-85.9731335635018],"rgb":[0,0.333333333333333315,0.733333333333333282],"xyz":[0.122170432013487734,0.100841890056627165,0.483178647079751555],"hpluv":[257.249085911334646,294.402692429224658,37.993106438468665],"hsluv":[257.249085911334646,99.9999999999924114,37.993106438468665]},"#0055cc":{"lch":[39.3366742314503384,99.5579220492412,258.963845190076711],"luv":[39.3366742314503384,-19.0582121518786707,-97.7167559446012888],"rgb":[0,0.333333333333333315,0.8],"xyz":[0.141462807457209461,0.108558840234115966,0.584785157750021711],"hpluv":[258.963845190076711,321.157087606315883,39.3366742314503384],"hsluv":[258.963845190076711,99.9999999999926,39.3366742314503384]},"#0055dd":{"lch":[40.7621365800243538,110.592821589198408,260.220233587680809],"luv":[40.7621365800243538,-18.7854624460211568,-108.985680654613518],"rgb":[0,0.333333333333333315,0.866666666666666696],"xyz":[0.162981133007843,0.1171661704543695,0.698115005650027709],"hpluv":[260.220233587680809,344.278042660470817,40.7621365800243538],"hsluv":[260.220233587680809,99.9999999999926672,40.7621365800243538]},"#0055ee":{"lch":[42.2603772648371105,121.285075365136905,261.168248695532611],"luv":[42.2603772648371105,-18.6213165580406681,-119.847052846418293],"rgb":[0,0.333333333333333315,0.933333333333333348],"xyz":[0.186793313274017742,0.126691042560839517,0.823525821718550932],"hpluv":[261.168248695532611,364.177675198016914,42.2603772648371105],"hsluv":[261.168248695532611,99.9999999999924256,42.2603772648371105]},"#0055ff":{"lch":[43.8227784910393,131.676969611306021,261.90102950371454],"luv":[43.8227784910393,-18.5511048350842884,-130.363648443170376],"rgb":[0,0.333333333333333315,1],"xyz":[0.212964361683849135,0.137159461924772241,0.961360010010332777],"hpluv":[261.90102950371454,381.28457852045068,43.8227784910393],"hsluv":[261.90102950371454,99.9999999999993321,43.8227784910393]},"#55ee00":{"lch":[83.4834241678481277,121.26988118033438,123.77813801339137],"luv":[83.4834241678481277,-67.423446821455,100.799121524975831],"rgb":[0.333333333333333315,0.933333333333333348,0],"xyz":[0.343194252835808289,0.630780425066107542,0.103666700302096418],"hpluv":[123.77813801339137,313.542875178701877,83.4834241678481277],"hsluv":[123.77813801339137,100.000000000002402,83.4834241678481277]},"#55ee11":{"lch":[83.5046935730824487,120.419265504422668,124.002811201886018],"luv":[83.5046935730824487,-67.3424968648776456,99.8287915414615412],"rgb":[0.333333333333333315,0.933333333333333348,0.0666666666666666657],"xyz":[0.344205918335445393,0.63118509126596245,0.108994805266852055],"hpluv":[124.002811201886018,311.80236567126093,83.5046935730824487],"hsluv":[124.002811201886018,98.9375412624106616,83.5046935730824487]},"#55ee22":{"lch":[83.5440973470955868,118.856748008926118,124.425313598414533],"luv":[83.5440973470955868,-67.1934622190367463,98.0406302625408728],"rgb":[0.333333333333333315,0.933333333333333348,0.133333333333333331],"xyz":[0.34608127647392245,0.631935234521353251,0.118871691462831247],"hpluv":[124.425313598414533,308.59848358059196,83.5440973470955868],"hsluv":[124.425313598414533,96.9807587800033133,83.5440973470955868]},"#55ee33":{"lch":[83.6089072227085524,116.32446068797897,125.138397832270286],"luv":[83.6089072227085524,-66.950941309724243,95.1259775880964469],"rgb":[0.333333333333333315,0.933333333333333348,0.2],"xyz":[0.349169027206380156,0.633170334814336377,0.135133845320442397],"hpluv":[125.138397832270286,303.387603747434184,83.6089072227085524],"hsluv":[125.138397832270286,93.7945051225822084,83.6089072227085524]},"#55ee44":{"lch":[83.7023291233000748,112.756808619009803,126.207644663357115],"luv":[83.7023291233000748,-66.6069499756988535,90.9813832873448547],"rgb":[0.333333333333333315,0.933333333333333348,0.266666666666666663],"xyz":[0.353627022450599071,0.634953532912023944,0.158612620273329036],"hpluv":[126.207644663357115,296.007020538140409,83.7023291233000748],"hsluv":[126.207644663357115,89.2707337544585,83.7023291233000748]},"#55ee55":{"lch":[83.8270046653184,108.148092962556504,127.715012949240077],"luv":[83.8270046653184,-66.1579022543843,85.5519840841642747],"rgb":[0.333333333333333315,0.933333333333333348,0.333333333333333315],"xyz":[0.359589436489961056,0.637338498527768693,0.190014667547302951],"hpluv":[127.715012949240077,286.404562223907249,83.8270046653184],"hsluv":[127.715012949240077,83.3573838846113091,83.8270046653184]},"#55ee66":{"lch":[83.985160091985378,102.554744038676162,129.769393513035965],"luv":[83.985160091985378,-65.6041880698479645,78.8261760618539853],"rgb":[0.333333333333333315,0.933333333333333348,0.4],"xyz":[0.367174432263469241,0.640372496837172,0.229962311954446885],"hpluv":[129.769393513035965,274.647728218817576,83.985160091985378],"hsluv":[129.769393513035965,83.6273111034757477,83.985160091985378]},"#55ee77":{"lch":[84.1786855608653752,96.1019099790829756,132.519661951802362],"luv":[84.1786855608653752,-64.9498200787083,70.8314758661090451],"rgb":[0.333333333333333315,0.933333333333333348,0.466666666666666674],"xyz":[0.376488445283671147,0.644098102045252863,0.279016113860844794],"hpluv":[132.519661951802362,260.948457539577589,84.1786855608653752],"hsluv":[132.519661951802362,83.9470254314832687,84.1786855608653752]},"#55ee88":{"lch":[84.4091822183787741,88.9952087097784,136.170997575445568],"luv":[84.4091822183787741,-64.2020140446184371,61.6299323860706],"rgb":[0.333333333333333315,0.933333333333333348,0.533333333333333326],"xyz":[0.387628862385614947,0.648554268886030405,0.337688977264417],"hpluv":[136.170997575445568,245.709397001158351,84.4091822183787741],"hsluv":[136.170997575445568,84.3134084109568533,84.4091822183787741]},"#55ee99":{"lch":[84.6779925165317877,81.540352796223317,141.002136712429774],"luv":[84.6779925165317877,-63.3706695007570531,51.3126434824633151],"rgb":[0.333333333333333315,0.933333333333333348,0.6],"xyz":[0.400685826494687647,0.653777054529659618,0.406455654905534869],"hpluv":[141.002136712429774,229.604367075154897,84.6779925165317877],"hsluv":[141.002136712429774,84.7220894550445394,84.6779925165317877]},"#55eeaa":{"lch":[84.9862212486181363,74.1734332109310373,147.37159021859884],"luv":[84.9862212486181363,-62.4677637576075426,39.9934580327855755],"rgb":[0.333333333333333315,0.933333333333333348,0.66666666666666663],"xyz":[0.415743519459940203,0.659800131715760685,0.48575950452253347],"hpluv":[147.37159021859884,213.709694911287,84.9862212486181363],"hsluv":[147.37159021859884,85.1677250765186358,84.9862212486181363]},"#55eebb":{"lch":[85.334751323756052,67.4984157699475418,155.676120933413],"luv":[85.334751323756052,-61.5066955643592337,27.8022037293077737],"rgb":[0.333333333333333315,0.933333333333333348,0.733333333333333282],"xyz":[0.432881111567276877,0.666655168558695443,0.576017489621175272],"hpluv":[155.676120933413,199.690784145783283,85.334751323756052],"hsluv":[155.676120933413,85.6443005177139298,85.334751323756052]},"#55eecc":{"lch":[85.7242566025865784,62.3040769944455,166.184583472908372],"luv":[85.7242566025865784,-60.5016239571924643,14.8778865660505755],"rgb":[0.333333333333333315,0.933333333333333348,0.8],"xyz":[0.452173487010998576,0.674372118736184301,0.677624000291445427],"hpluv":[166.184583472908372,189.979166061651426,85.7242566025865784],"hsluv":[166.184583472908372,86.1454332210543186,85.7242566025865784]},"#55eedd":{"lch":[86.1552131978466349,59.4824491826881427,178.687660418725557],"luv":[86.1552131978466349,-59.4668469392298462,1.36230535390821395],"rgb":[0.333333333333333315,0.933333333333333348,0.866666666666666696],"xyz":[0.473691812561632175,0.682979448956437807,0.790953848191451425],"hpluv":[178.687660418725557,187.703134777137279,86.1552131978466349],"hsluv":[178.687660418725557,86.6646578889921,86.1552131978466349]},"#55eeee":{"lch":[86.6279101052896578,59.7608483004272202,192.177050630061245],"luv":[86.6279101052896578,-58.4162567245265265,-12.6055519466972967],"rgb":[0.333333333333333315,0.933333333333333348,0.933333333333333348],"xyz":[0.497503992827806885,0.692504321062907824,0.916364664259974648],"hpluv":[192.177050630061245,196.025490067494644,86.6279101052896578],"hsluv":[192.177050630061245,87.195676441965972,86.6279101052896578]},"#55eeff":{"lch":[87.1424596935915901,63.3545065705612274,205.119112492248092],"luv":[87.1424596935915901,-57.3628964007582169,-26.8940814923128677],"rgb":[0.333333333333333315,0.933333333333333348,1],"xyz":[0.523675041237638306,0.702972740426840548,1.05419885255175649],"hpluv":[205.119112492248092,217.06231600799947,87.1424596935915901],"hsluv":[205.119112492248092,99.9999999999933,87.1424596935915901]},"#006600":{"lch":[36.9339903888407548,57.1632711650289735,127.71501294924046],"luv":[36.9339903888407548,-34.9687359497521086,45.2197642227726249],"rgb":[0,0.4,0],"xyz":[0.0475116309878656218,0.0950232619757325619,0.0158372103292880942],"hpluv":[127.71501294924046,196.394882900214554,36.9339903888407548],"hsluv":[127.71501294924046,100.000000000002331,36.9339903888407548]},"#006611":{"lch":[37.0090255636121412,54.7765813256580216,129.276595687178855],"luv":[37.0090255636121412,-34.6771211058740789,42.4024897091546933],"rgb":[0,0.4,0.0666666666666666657],"xyz":[0.04852329648750274,0.0954279281755874148,0.0211653152940437239],"hpluv":[129.276595687178855,187.813410896691579,37.0090255636121412],"hsluv":[129.276595687178855,99.9999999999908908,37.0090255636121412]},"#006622":{"lch":[37.1475616040544949,50.6324758020482761,132.428423847637788],"luv":[37.1475616040544949,-34.160143827310165,37.3728802682170453],"rgb":[0,0.4,0.133333333333333331],"xyz":[0.0503986546259797621,0.0961780714309782292,0.0310422014900229234],"hpluv":[132.428423847637788,172.957013965329963,37.1475616040544949],"hsluv":[132.428423847637788,99.9999999999909619,37.1475616040544949]},"#006633":{"lch":[37.3740982281225911,44.601943170857588,138.432874640413417],"luv":[37.3740982281225911,-33.3702331720845464,29.5932572160807972],"rgb":[0,0.4,0.2],"xyz":[0.0534864053584375,0.0974131717239613421,0.0473043553476340667],"hpluv":[138.432874640413417,151.43364776149042,37.3740982281225911],"hsluv":[138.432874640413417,99.9999999999910187,37.3740982281225911]},"#006644":{"lch":[37.6978110334583292,37.6830106217376226,149.139540405640531],"luv":[37.6978110334583292,-32.3478161181420845,19.3294615006434647],"rgb":[0,0.4,0.266666666666666663],"xyz":[0.0579444006026563832,0.099196369821648922,0.0707831303005207],"hpluv":[149.139540405640531,126.843666215367492,37.6978110334583292],"hsluv":[149.139540405640531,99.9999999999911893,37.6978110334583292]},"#006655":{"lch":[38.1247572916394191,31.9621551107412678,167.158861879927684],"luv":[38.1247572916394191,-31.1627826377188839,7.10354401671241],"rgb":[0,0.4,0.333333333333333315],"xyz":[0.0639068146420183752,0.101581335437393755,0.102185177574494607],"hpluv":[167.158861879927684,106.382034992292617,38.1247572916394191],"hsluv":[167.158861879927684,99.9999999999912461,38.1247572916394191]},"#006666":{"lch":[38.6583399620500714,30.584907136256998,192.177050630061132],"luv":[38.6583399620500714,-29.8967608054288689,-6.45137488264605441],"rgb":[0,0.4,0.4],"xyz":[0.0714918104155265466,0.104615333746797073,0.142132821981638569],"hpluv":[192.177050630061132,100.392967527320849,38.6583399620500714],"hsluv":[192.177050630061132,99.9999999999914877,38.6583399620500714]},"#006677":{"lch":[39.2996251720959648,35.3400629634721852,215.899751113488037],"luv":[39.2996251720959648,-28.6270125920824867,-20.7223116547099835],"rgb":[0,0.4,0.466666666666666674],"xyz":[0.080805823435728466,0.108340938954877883,0.191186623888036478],"hpluv":[215.899751113488037,114.108563264217921,39.2996251720959648],"hsluv":[215.899751113488037,99.999999999991644,39.2996251720959648]},"#006688":{"lch":[40.0476371065618721,44.619030030948764,232.088426656893603],"luv":[40.0476371065618721,-27.4159210327312195,-35.2026293738089109],"rgb":[0,0.4,0.533333333333333326],"xyz":[0.0919462405376723,0.112797105795655481,0.249859487291608645],"hpluv":[232.088426656893603,141.378234692038774,40.0476371065618721],"hsluv":[232.088426656893603,99.9999999999918145,40.0476371065618721]},"#006699":{"lch":[40.8996671875350728,56.0796471869528546,242.024131169727298],"luv":[40.8996671875350728,-26.3069429553173038,-49.5264735364712863],"rgb":[0,0.4,0.6],"xyz":[0.105003204646745008,0.118019891439284638,0.318626164932726552],"hpluv":[242.024131169727298,173.990215160848891,40.8996671875350728],"hsluv":[242.024131169727298,99.9999999999919,40.8996671875350728]},"#0066aa":{"lch":[41.8515997707465104,68.3303466488807345,248.245335088520562],"luv":[41.8515997707465104,-25.3254853434547229,-63.4638169768779861],"rgb":[0,0.4,0.66666666666666663],"xyz":[0.12006089761199755,0.124042968625385747,0.397930014549725153],"hpluv":[248.245335088520562,207.176688473106537,41.8515997707465104],"hsluv":[248.245335088520562,99.9999999999921,41.8515997707465104]},"#0066bb":{"lch":[42.8982420202673396,80.6980154219041452,252.339105133311591],"luv":[42.8982420202673396,-24.4823887435570384,-76.8946183711398],"rgb":[0,0.4,0.733333333333333282],"xyz":[0.137198489719334182,0.130898005468320505,0.488187999648366899],"hpluv":[252.339105133311591,238.705643004687346,42.8982420202673396],"hsluv":[252.339105133311591,99.9999999999922125,42.8982420202673396]},"#0066cc":{"lch":[44.0336413015701211,92.8728169416488356,255.165448618736946],"luv":[44.0336413015701211,-23.7781103155697018,-89.7772888680517696],"rgb":[0,0.4,0.8],"xyz":[0.156490865163055937,0.138614955645809307,0.589794510318637],"hpluv":[255.165448618736946,267.63526922347819,44.0336413015701211],"hsluv":[255.165448618736946,99.9999999999922835,44.0336413015701211]},"#0066dd":{"lch":[45.2513748624169807,104.723900430114114,257.197154574648096],"luv":[45.2513748624169807,-23.2064943358636171,-102.120291528843495],"rgb":[0,0.4,0.866666666666666696],"xyz":[0.17800919071368948,0.147222285866062841,0.703124358218643],"hpluv":[257.197154574648096,293.665789717253858,45.2513748624169807],"hsluv":[257.197154574648096,99.9999999999923119,45.2513748624169807]},"#0066ee":{"lch":[46.5448008930790422,116.210429262859975,258.70668283343889],"luv":[46.5448008930790422,-22.7576936505684415,-113.960305585607557],"rgb":[0,0.4,0.933333333333333348],"xyz":[0.201821370979864217,0.156747157972532858,0.828535174287166276],"hpluv":[258.70668283343889,316.82048544727013,46.5448008930790422],"hsluv":[258.70668283343889,99.9999999999923119,46.5448008930790422]},"#0066ff":{"lch":[47.9072652547968758,127.336469583599694,259.859032010401279],"luv":[47.9072652547968758,-22.4202120236612075,-125.347160234402949],"rgb":[0,0.4,1],"xyz":[0.227992419389695611,0.167215577336465582,0.966369362578948121],"hpluv":[259.859032010401279,337.280125749862,47.9072652547968758],"hsluv":[259.859032010401279,99.9999999999992326,47.9072652547968758]},"#55ff00":{"lch":[88.6611895097861691,129.940408372856581,124.363532639485271],"luv":[88.6611895097861691,-73.3437881830395213,107.262288168144963],"rgb":[0.333333333333333315,1,0],"xyz":[0.395046625265482121,0.734485169925456649,0.120950824445320529],"hpluv":[124.363532639485271,511.214684976108288,88.6611895097861691],"hsluv":[124.363532639485271,100.000000000002203,88.6611895097861691]},"#55ff11":{"lch":[88.6804070841335488,129.16615931328721,124.557188606571273],"luv":[88.6804070841335488,-73.2667318726085597,106.376137890284312],"rgb":[0.333333333333333315,1,0.0666666666666666657],"xyz":[0.396058290765119225,0.734889836125311557,0.126278929410076152],"hpluv":[124.557188606571273,509.111714190739917,88.6804070841335488],"hsluv":[124.557188606571273,99.999999999991374,88.6804070841335488]},"#55ff22":{"lch":[88.7160126920472862,127.742171235629925,124.920549061750862],"luv":[88.7160126920472862,-73.1247263453711867,104.741762009752222],"rgb":[0.333333333333333315,1,0.133333333333333331],"xyz":[0.397933648903596282,0.735639979380702358,0.136155815606055358],"hpluv":[124.920549061750862,505.235489858807625,88.7160126920472862],"hsluv":[124.920549061750862,99.9999999999912461,88.7160126920472862]},"#55ff33":{"lch":[88.7745841250638819,125.429409451765778,125.531442964179078],"luv":[88.7745841250638819,-72.893256299655377,102.074041467218606],"rgb":[0.333333333333333315,1,0.2],"xyz":[0.401021399636054,0.736875079673685485,0.152417969463666508],"hpluv":[125.531442964179078,498.916518871762833,88.7745841250638819],"hsluv":[125.531442964179078,99.9999999999915445,88.7745841250638819]},"#55ff44":{"lch":[88.8590323294941271,122.159967165607114,126.442013329478428],"luv":[88.8590323294941271,-72.5641116581323189,98.2726171279075658],"rgb":[0.333333333333333315,1,0.266666666666666663],"xyz":[0.405479394880272903,0.738658277771373,0.175896744416553147],"hpluv":[126.442013329478428,489.934204596741552,88.8590323294941271],"hsluv":[126.442013329478428,99.9999999999914735,88.8590323294941271]},"#55ff55":{"lch":[88.9717666926620154,117.915518658848484,127.715012949240119],"luv":[88.9717666926620154,-72.1329719647303,93.2786357968462],"rgb":[0.333333333333333315,1,0.333333333333333315],"xyz":[0.411441808919634888,0.7410432433871178,0.207298791690527062],"hpluv":[127.715012949240119,478.187207442594797,88.9717666926620154],"hsluv":[127.715012949240119,99.9999999999912461,88.9717666926620154]},"#55ff66":{"lch":[89.1148309932425,112.728028648536309,129.430986513959965],"luv":[89.1148309932425,-71.5990188667543919,87.0700232014633713],"rgb":[0.333333333333333315,1,0.4],"xyz":[0.419026804693143073,0.744077241696521119,0.247246436097671],"hpluv":[129.430986513959965,463.701047487447568,89.1148309932425],"hsluv":[129.430986513959965,99.999999999991374,89.1148309932425]},"#55ff77":{"lch":[89.2899761450384233,106.6837193332601,131.696695826109391],"luv":[89.2899761450384233,-70.9646547782272705,79.6582308552282825],"rgb":[0.333333333333333315,1,0.466666666666666674],"xyz":[0.428340817713345,0.747802846904602,0.296300238004068905],"hpluv":[131.696695826109391,446.653165807945641,89.2899761450384233],"hsluv":[131.696695826109391,99.9999999999911466,89.2899761450384233]},"#55ff88":{"lch":[89.4987035327786344,99.9302046144725722,134.655503696120093],"luv":[89.4987035327786344,-70.2351927280585784,71.0849034376696807],"rgb":[0.333333333333333315,1,0.533333333333333326],"xyz":[0.439481234815288779,0.752259013745379512,0.354973101407641101],"hpluv":[134.655503696120093,427.420708471833962,89.4987035327786344],"hsluv":[134.655503696120093,99.9999999999910472,89.4987035327786344]},"#55ff99":{"lch":[89.7422928839939544,92.6881300553494185,138.499225093055145],"luv":[89.7422928839939544,-69.4184746025382395,61.4179520744067275],"rgb":[0.333333333333333315,1,0.6],"xyz":[0.452538198924361534,0.757481799389008725,0.423739779048759],"hpluv":[138.499225093055145,406.664135605650245,89.7422928839939544],"hsluv":[138.499225093055145,99.9999999999909193,89.7422928839939544]},"#55ffaa":{"lch":[90.021821419032733,85.2692799565717081,143.477548510033301],"luv":[90.021821419032733,-68.524415632830582,50.7469660816416095],"rgb":[0.333333333333333315,1,0.66666666666666663],"xyz":[0.467595891889614035,0.763504876575109792,0.503043628665757581],"hpluv":[143.477548510033301,385.467603776639692,90.021821419032733],"hsluv":[143.477548510033301,99.9999999999908908,90.021821419032733]},"#55ffbb":{"lch":[90.3381779071049635,78.1017696608261929,149.892156464581973],"luv":[90.3381779071049635,-67.5644942943643514,39.178125719584358],"rgb":[0.333333333333333315,1,0.733333333333333282],"xyz":[0.484733483996950709,0.770359913418044551,0.593301613764399383],"hpluv":[149.892156464581973,365.560319764238727,90.3381779071049635],"hsluv":[149.892156464581973,99.9999999999906,90.3381779071049635]},"#55ffcc":{"lch":[90.6920737330737836,71.7555158590460849,158.04408297895796],"luv":[90.6920737330737836,-66.5512174426729217,26.828893251416094],"rgb":[0.333333333333333315,1,0.8],"xyz":[0.504025859440672463,0.778076863595533408,0.694908124434669539],"hpluv":[158.04408297895796,349.620992563925881,90.6920737330737836],"hsluv":[158.04408297895796,99.9999999999905214,90.6920737330737836]},"#55ffdd":{"lch":[91.0840522607878142,66.9403052018893874,168.083008151042208],"luv":[91.0840522607878142,-65.4975936461766,13.8227959213187273],"rgb":[0.333333333333333315,1,0.866666666666666696],"xyz":[0.525544184991306,0.786684193815786914,0.808237972334675536],"hpluv":[168.083008151042208,341.570927955438208,91.0840522607878142],"hsluv":[168.083008151042208,99.9999999999901092,91.0840522607878142]},"#55ffee":{"lch":[91.5144973031429,64.4172738793593,179.746868091755],"luv":[91.5144973031429,-64.4166452130874347,0.284593639433804613],"rgb":[0.333333333333333315,1,0.933333333333333348],"xyz":[0.549356365257480661,0.796209065922256931,0.933648788403198759],"hpluv":[179.746868091755,346.559914030924404,91.5144973031429],"hsluv":[179.746868091755,99.9999999999898108,91.5144973031429]},"#55ffff":{"lch":[91.9836412143362,64.7784688708661918,192.177050630061103],"luv":[91.9836412143362,-63.3209831419883,-13.663935128132529],"rgb":[0.333333333333333315,1,1],"xyz":[0.575527413667312082,0.806677485286189655,1.07148297669498049],"hpluv":[192.177050630061103,370.276433987554753,91.9836412143362],"hsluv":[192.177050630061103,99.9999999999890719,91.9836412143362]},"#007700":{"lch":[43.052730924646589,66.6333343982289534,127.715012949240503],"luv":[43.052730924646589,-40.7618988300426395,52.7111834129681611],"rgb":[0,0.466666666666666674,0],"xyz":[0.0659653690412832505,0.131930738082568333,0.0219884563470937981],"hpluv":[127.715012949240503,196.394882900214441,43.052730924646589],"hsluv":[127.715012949240503,100.000000000002217,43.052730924646589]},"#007711":{"lch":[43.1130460407029,64.6129906803504497,128.830381027920708],"luv":[43.1130460407029,-40.513441406878222,50.333881531534],"rgb":[0,0.466666666666666674,0.0666666666666666657],"xyz":[0.0669770345409203688,0.132335404282423186,0.0273165613118494313],"hpluv":[128.830381027920708,190.173702516526191,43.1130460407029],"hsluv":[128.830381027920708,99.9999999999908908,43.1130460407029]},"#007722":{"lch":[43.2245297597171856,61.038201507507992,131.028383581415682],"luv":[43.2245297597171856,-40.0674788501528809,46.0462721820529381],"rgb":[0,0.466666666666666674,0.133333333333333331],"xyz":[0.0688523926793974,0.133085547537814014,0.0371934475078286239],"hpluv":[131.028383581415682,179.188765673473426,43.2245297597171856],"hsluv":[131.028383581415682,99.9999999999909193,43.2245297597171856]},"#007733":{"lch":[43.4071769639209251,55.6324991773261601,135.049443606961],"luv":[43.4071769639209251,-39.3720497464739694,39.3041558041433419],"rgb":[0,0.466666666666666674,0.2],"xyz":[0.0719401434118551314,0.134320647830797113,0.0534556013654397671],"hpluv":[135.049443606961,162.632131787412106,43.4071769639209251],"hsluv":[135.049443606961,99.9999999999909193,43.4071769639209251]},"#007744":{"lch":[43.6689123670523855,48.8909209361139787,141.845848120172377],"luv":[43.6689123670523855,-38.4454486392046704,30.2038015639029105],"rgb":[0,0.466666666666666674,0.266666666666666663],"xyz":[0.076398138656074,0.136103845928484707,0.0769343763183264],"hpluv":[141.845848120172377,142.067640978907804,43.6689123670523855],"hsluv":[141.845848120172377,99.9999999999910614,43.6689123670523855]},"#007755":{"lch":[44.0154249216106,41.9153980271179,152.953344682219807],"luv":[44.0154249216106,-37.3313854656975224,19.0595973457790286],"rgb":[0,0.466666666666666674,0.333333333333333315],"xyz":[0.082360552695436,0.13848881154422954,0.108336423592300321],"hpluv":[152.953344682219807,120.839250305189026,44.0154249216106],"hsluv":[152.953344682219807,99.9999999999912461,44.0154249216106]},"#007766":{"lch":[44.4505333250062549,36.6388362985735725,170.062665906075836],"luv":[44.4505333250062549,-36.0891470448365865,6.32279929223085357],"rgb":[0,0.466666666666666674,0.4],"xyz":[0.0899455484689441753,0.141522809853632831,0.148284067999444269],"hpluv":[170.062665906075836,104.593337643625588,44.4505333250062549],"hsluv":[170.062665906075836,99.9999999999913314,44.4505333250062549]},"#007777":{"lch":[44.9764013416840669,35.58350047386471,192.17705063006116],"luv":[44.9764013416840669,-34.7828880940388601,-7.50574458738768],"rgb":[0,0.466666666666666674,0.466666666666666674],"xyz":[0.0992595614891461,0.145248415061713654,0.197337869905842178],"hpluv":[192.17705063006116,100.392967527320835,44.9764013416840669],"hsluv":[192.17705063006116,99.9999999999914451,44.9764013416840669]},"#007788":{"lch":[45.5937085159301603,40.0276538709377,213.25546015720218],"luv":[45.5937085159301603,-33.4724811425817,-21.9500815618326577],"rgb":[0,0.466666666666666674,0.533333333333333326],"xyz":[0.110399978591089923,0.149704581902491252,0.256010733309414373],"hpluv":[213.25546015720218,111.402399127386914,45.5937085159301603],"hsluv":[213.25546015720218,99.9999999999916156,45.5937085159301603]},"#007799":{"lch":[46.3018156057360244,48.7595612734022907,228.659125647543163],"luv":[46.3018156057360244,-32.2075160625452597,-36.6083422822662357],"rgb":[0,0.466666666666666674,0.6],"xyz":[0.123456942700162636,0.154927367546120409,0.324777410950532253],"hpluv":[228.659125647543163,133.62911578541167,46.3018156057360244],"hsluv":[228.659125647543163,99.9999999999917577,46.3018156057360244]},"#0077aa":{"lch":[47.0989379645613795,59.8445186957143,238.773531847525192],"luv":[47.0989379645613795,-31.0247207616125031,-51.1745358511991597],"rgb":[0,0.466666666666666674,0.66666666666666663],"xyz":[0.138514635665415164,0.160950444732221504,0.404081260567530853],"hpluv":[238.773531847525192,161.232500989130415,47.0989379645613795],"hsluv":[238.773531847525192,99.999999999991914,47.0989379645613795]},"#0077bb":{"lch":[47.9823278818304146,71.9659744020705432,245.408717039049918],"luv":[47.9823278818304146,-29.9480972873728,-65.4386196408934495],"rgb":[0,0.466666666666666674,0.733333333333333282],"xyz":[0.155652227772751839,0.167805481575156262,0.4943392456661726],"hpluv":[245.408717039049918,190.32034768447943,47.9823278818304146],"hsluv":[245.408717039049918,99.9999999999920561,47.9823278818304146]},"#0077cc":{"lch":[48.9484610491917067,84.4081580704908276,249.9122747909542],"luv":[48.9484610491917067,-28.9906993044602252,-79.2734287305105454],"rgb":[0,0.466666666666666674,0.8],"xyz":[0.174944603216473565,0.175522431752645064,0.595945756336442756],"hpluv":[249.9122747909542,218.818838178067409,48.9484610491917067],"hsluv":[249.9122747909542,99.9999999999922125,48.9484610491917067]},"#0077dd":{"lch":[49.9932200675849,96.8017840095652389,253.089738624096071],"luv":[49.9932200675849,-28.15707849035525,-92.6162206005108715],"rgb":[0,0.466666666666666674,0.866666666666666696],"xyz":[0.196462928767107109,0.184129761972898598,0.709275604236448753],"hpluv":[253.089738624096071,245.70363248193695,49.9932200675849],"hsluv":[253.089738624096071,99.9999999999921272,49.9932200675849]},"#0077ee":{"lch":[51.1120678627821547,108.963279527823246,255.411176145550684],"luv":[51.1120678627821547,-27.4457353077348785,-105.450120430828804],"rgb":[0,0.466666666666666674,0.933333333333333348],"xyz":[0.220275109033281846,0.193654634079368643,0.834686420304972],"hpluv":[255.411176145550684,270.517920771775039,51.1120678627821547],"hsluv":[255.411176145550684,99.9999999999922551,51.1120678627821547]},"#0077ff":{"lch":[52.300205122294,120.810007187166491,257.158195690943],"luv":[52.300205122294,-26.851223719183885,-117.788240590245493],"rgb":[0,0.466666666666666674,1],"xyz":[0.24644615744311324,0.204123053443301339,0.972520608596753822],"hpluv":[257.158195690943,293.11554041762389,52.300205122294],"hsluv":[257.158195690943,99.9999999999991189,52.300205122294]},"#008800":{"lch":[49.0166039301270473,75.8637069146273291,127.71501294924046],"luv":[49.0166039301270473,-46.4084346679225348,60.0129920808956214],"rgb":[0,0.533333333333333326,0],"xyz":[0.0880377387662537,0.176075477532509878,0.0293459129220837445],"hpluv":[127.71501294924046,196.394882900214583,49.0166039301270473],"hsluv":[127.71501294924046,100.000000000002359,49.0166039301270473]},"#008811":{"lch":[49.066374048408079,74.1307167522113133,128.546257021813432],"luv":[49.066374048408079,-46.1942790787678064,57.9780281364371461],"rgb":[0,0.533333333333333326,0.0666666666666666657],"xyz":[0.0890494042658908219,0.17648014373236473,0.0346740178868393742],"hpluv":[128.546257021813432,191.713881645209739,49.066374048408079],"hsluv":[128.546257021813432,99.9999999999908908,49.066374048408079]},"#008822":{"lch":[49.1584337135343503,71.0276132120671,130.159311596065152],"luv":[49.1584337135343503,-45.8067815452400779,54.2831521106656112],"rgb":[0,0.533333333333333326,0.133333333333333331],"xyz":[0.090924762404367851,0.177230286987755559,0.0445509040828185737],"hpluv":[130.159311596065152,183.344763498744726,49.1584337135343503],"hsluv":[130.159311596065152,99.9999999999909335,49.1584337135343503]},"#008833":{"lch":[49.3094443496477197,66.226384742663825,133.033255321350083],"luv":[49.3094443496477197,-45.1943905199784481,48.4086882864121648],"rgb":[0,0.533333333333333326,0.2],"xyz":[0.0940125131368255845,0.178465387280738658,0.060813057940429717],"hpluv":[133.033255321350083,170.427732437953466,49.3094443496477197],"hsluv":[133.033255321350083,99.9999999999909903,49.3094443496477197]},"#008844":{"lch":[49.5262444349700388,59.9709063982634589,137.708745518338219],"luv":[49.5262444349700388,-44.3625073014275202,40.3543995143041],"rgb":[0,0.533333333333333326,0.266666666666666663],"xyz":[0.0984705083810444581,0.180248585378426252,0.0842918328933163485],"hpluv":[137.708745518338219,153.654237765673173,49.5262444349700388],"hsluv":[137.708745518338219,99.9999999999910756,49.5262444349700388]},"#008855":{"lch":[49.8139833497656355,52.876539169604392,145.043769270071607],"luv":[49.8139833497656355,-43.3370811793054074,30.2956397756013871],"rgb":[0,0.533333333333333326,0.333333333333333315],"xyz":[0.104432922420406457,0.182633550994171084,0.115693880167290264],"hpluv":[145.043769270071607,134.694876863850226,49.8139833497656355],"hsluv":[145.043769270071607,99.9999999999911893,49.8139833497656355]},"#008866":{"lch":[50.1764284055384593,46.0546718070978613,156.264738854980266],"luv":[50.1764284055384593,-42.1591397878491918,18.5375221539472825],"rgb":[0,0.533333333333333326,0.4],"xyz":[0.112017918193914628,0.185667549303574375,0.155641524574434226],"hpluv":[156.264738854980266,116.469784961641182,50.1764284055384593],"hsluv":[156.264738854980266,99.9999999999912461,50.1764284055384593]},"#008877":{"lch":[50.6161324463586766,41.2412500498738694,172.391379993232761],"luv":[50.6161324463586766,-40.8781468825216336,5.46056893805106114],"rgb":[0,0.533333333333333326,0.466666666666666674],"xyz":[0.121331931214116548,0.189393154511655198,0.204695326480832135],"hpluv":[172.391379993232761,103.390869865470918,50.6161324463586766],"hsluv":[172.391379993232761,99.9999999999913456,50.6161324463586766]},"#008888":{"lch":[51.1345503085294695,40.4555776108317602,192.177050630061132],"luv":[51.1345503085294695,-39.545345738280993,-8.53342781453345],"rgb":[0,0.533333333333333326,0.533333333333333326],"xyz":[0.132472348316060362,0.193849321352432796,0.263368189884404302],"hpluv":[192.177050630061132,100.392967527320849,51.1345503085294695],"hsluv":[192.177050630061132,99.9999999999914877,51.1345503085294695]},"#008899":{"lch":[51.7321394091786715,44.6308971675799881,211.11913642158629],"luv":[51.7321394091786715,-38.208266400248,-23.066108485628412],"rgb":[0,0.533333333333333326,0.6],"xyz":[0.145529312425133089,0.199072106996061954,0.332134867525522237],"hpluv":[211.11913642158629,109.474886689832829,51.7321394091786715],"hsluv":[211.11913642158629,99.999999999991573,51.7321394091786715]},"#0088aa":{"lch":[52.4084594351014914,52.8385448128107598,225.694192047300788],"luv":[52.4084594351014914,-36.9070805124816346,-37.8124215831333572],"rgb":[0,0.533333333333333326,0.66666666666666663],"xyz":[0.160587005390385618,0.205095184182163048,0.411438717142520782],"hpluv":[225.694192047300788,127.934825585488483,52.4084594351014914],"hsluv":[225.694192047300788,99.9999999999917,52.4084594351014914]},"#0088bb":{"lch":[53.1622766119302952,63.4852929684367,235.812153399491365],"luv":[53.1622766119302952,-35.672889398211332,-52.5150205681309856],"rgb":[0,0.533333333333333326,0.733333333333333282],"xyz":[0.177724597497722292,0.211950221025097807,0.501696702241162584],"hpluv":[235.812153399491365,151.533580059798538,53.1622766119302952],"hsluv":[235.812153399491365,99.9999999999918572,53.1622766119302952]},"#0088cc":{"lch":[53.9916730817088961,75.3609801883338406,242.731381598031476],"luv":[53.9916730817088961,-34.5275961267233455,-66.9859868976812578],"rgb":[0,0.533333333333333326,0.8],"xyz":[0.197016972941444019,0.219667171202586609,0.60330321291143274],"hpluv":[242.731381598031476,177.116523654060018,53.9916730817088961],"hsluv":[242.731381598031476,99.9999999999919282,53.9916730817088961]},"#0088dd":{"lch":[54.894159312243417,87.7409205251226,247.565131731451658],"luv":[54.894159312243417,-33.4848266090853,-81.1001573460580545],"rgb":[0,0.533333333333333326,0.866666666666666696],"xyz":[0.218535298492077562,0.228274501422840143,0.716633060811438738],"hpluv":[247.565131731451658,202.822146488853946,54.894159312243417],"hsluv":[247.565131731451658,99.9999999999921414,54.894159312243417]},"#0088ee":{"lch":[55.8667862779657725,100.217939448335898,251.046181944553609],"luv":[55.8667862779657725,-32.551381555669181,-94.7841914355319091],"rgb":[0,0.533333333333333326,0.933333333333333348],"xyz":[0.242347478758252299,0.237799373529310187,0.842043876879962],"hpluv":[251.046181944553609,227.630842720065772,55.8667862779657725],"hsluv":[251.046181944553609,99.9999999999923261,55.8667862779657725]},"#0088ff":{"lch":[56.9062538959811803,112.568622459607909,253.628629682131134],"luv":[56.9062538959811803,-31.728824885135996,-108.00452043253614],"rgb":[0,0.533333333333333326,1],"xyz":[0.268518527168083665,0.248267792893242883,0.979878065171743806],"hpluv":[253.628629682131134,251.013269675548315,56.9062538959811803],"hsluv":[253.628629682131134,99.9999999999989,56.9062538959811803]},"#009900":{"lch":[54.8465256129575778,84.8867610313905629,127.71501294924046],"luv":[54.8465256129575778,-51.9281467214630865,67.1507987776363677],"rgb":[0,0.6,0],"xyz":[0.11390733921872119,0.227814678437445572,0.037969113072906],"hpluv":[127.71501294924046,196.394882900214611,54.8465256129575778],"hsluv":[127.71501294924046,100.000000000002359,54.8465256129575778]},"#009911":{"lch":[54.8884489227774139,83.3822613920475533,128.355135015114286],"luv":[54.8884489227774139,-51.7415219578455918,65.3866685214771195],"rgb":[0,0.6,0.0666666666666666657],"xyz":[0.114919004718358309,0.228219344637300425,0.0432972180376616292],"hpluv":[128.355135015114286,192.766711025891595,54.8884489227774139],"hsluv":[128.355135015114286,99.9999999999908908,54.8884489227774139]},"#009922":{"lch":[54.9660326693047665,80.6666974094322882,129.584419925030318],"luv":[54.9660326693047665,-51.4019848556742147,62.168738316310943],"rgb":[0,0.6,0.133333333333333331],"xyz":[0.116794362856835338,0.228969487892691254,0.0531741042336408287],"hpluv":[129.584419925030318,186.225526863887183,54.9660326693047665],"hsluv":[129.584419925030318,99.9999999999908766,54.9660326693047665]},"#009933":{"lch":[55.0934048637144826,76.4021664793699529,131.735569901926],"luv":[55.0934048637144826,-50.8604445821364948,57.0132109221080725],"rgb":[0,0.6,0.2],"xyz":[0.119882113589293071,0.230204588185674353,0.0694362580912519789],"hpluv":[131.735569901926,175.972736191316358,55.0934048637144826],"hsluv":[131.735569901926,99.9999999999909903,55.0934048637144826]},"#009944":{"lch":[55.2764995203901321,70.6973886175228614,135.142608572152881],"luv":[55.2764995203901321,-50.1148739946869739,49.8660221176072795],"rgb":[0,0.6,0.266666666666666663],"xyz":[0.124340108833511945,0.231987786283361946,0.0929150330441386174],"hpluv":[135.142608572152881,162.293888564466016,55.2764995203901321],"hsluv":[135.142608572152881,99.9999999999910472,55.2764995203901321]},"#009955":{"lch":[55.5199214835444792,63.9084670453141044,140.311824754994205],"luv":[55.5199214835444792,-49.1795700812718,40.8125231602173173],"rgb":[0,0.6,0.333333333333333315],"xyz":[0.130302522872873944,0.234372751899106779,0.124317080318112519],"hpluv":[140.311824754994205,146.065915008593578,55.5199214835444792],"hsluv":[140.311824754994205,99.999999999991033,55.5199214835444792]},"#009966":{"lch":[55.8272121342916847,56.7002292022714229,147.995210176352344],"luv":[55.8272121342916847,-48.0820094218291771,30.0505634148387166],"rgb":[0,0.6,0.4],"xyz":[0.137887518646382129,0.23740675020851007,0.164264724725256495],"hpluv":[147.995210176352344,128.877825594911201,55.8272121342916847],"hsluv":[147.995210176352344,99.9999999999911893,55.8272121342916847]},"#009977":{"lch":[56.2009899164422393,50.1460500452165832,159.138923639260469],"luv":[56.2009899164422393,-46.85880632633,17.8571723631952],"rgb":[0,0.6,0.466666666666666674],"xyz":[0.147201531666584035,0.241132355416590893,0.213318526631654404],"hpluv":[159.138923639260469,113.222330796973324,56.2009899164422393],"hsluv":[159.138923639260469,99.9999999999912887,56.2009899164422393]},"#009988":{"lch":[56.6430401241061077,45.7780774967248476,174.293450088826631],"luv":[56.6430401241061077,-45.5512109338885409,4.55187453172192669],"rgb":[0,0.6,0.533333333333333326],"xyz":[0.158341948768527863,0.245588522257368491,0.271991390035226543],"hpluv":[174.293450088826631,102.553461073272217,56.6430401241061077],"hsluv":[174.293450088826631,99.9999999999913456,56.6430401241061077]},"#009999":{"lch":[57.1543844255405133,45.2182256610376498,192.177050630061132],"luv":[57.1543844255405133,-44.2008363998384866,-9.53802880511673301],"rgb":[0,0.6,0.6],"xyz":[0.171398912877600562,0.250811307900997649,0.340758067676344478],"hpluv":[192.177050630061132,100.392967527320849,57.1543844255405133],"hsluv":[192.177050630061132,99.9999999999914877,57.1543844255405133]},"#0099aa":{"lch":[57.7353441317496,49.1617433665336065,209.362441333496832],"luv":[57.7353441317496,-42.8462007667450138,-24.1056028900479831],"rgb":[0,0.6,0.66666666666666663],"xyz":[0.186456605842853118,0.256834385087098771,0.420061917293343079],"hpluv":[209.362441333496832,108.050017888493571,57.7353441317496],"hsluv":[209.362441333496832,99.9999999999915872,57.7353441317496]},"#0099bb":{"lch":[58.3856036871333686,56.8846197873520509,223.121526867409756],"luv":[58.3856036871333686,-41.5203973647636531,-38.8833713947714656],"rgb":[0,0.6,0.733333333333333282],"xyz":[0.203594197950189765,0.26368942193003353,0.51031990239198477],"hpluv":[223.121526867409756,123.631292939903787,58.3856036871333686],"hsluv":[223.121526867409756,99.9999999999917151,58.3856036871333686]},"#0099cc":{"lch":[59.1042769117952531,67.0724902608298237,233.123241992210865],"luv":[59.1042769117952531,-40.2499186561976856,-53.6531732328907225],"rgb":[0,0.6,0.8],"xyz":[0.222886573393911491,0.271406372107522331,0.611926413062254926],"hpluv":[233.123241992210865,144.000781521109104,59.1042769117952531],"hsluv":[233.123241992210865,99.9999999999918572,59.1042769117952531]},"#0099dd":{"lch":[59.889976295668248,78.6325369214165448,240.220044999287609],"luv":[59.889976295668248,-39.0544489896310338,-68.2482664747921888],"rgb":[0,0.6,0.866666666666666696],"xyz":[0.244404898944545035,0.280013702327775837,0.725256260962260924],"hpluv":[240.220044999287609,166.60478591483178,59.889976295668248],"hsluv":[240.220044999287609,99.9999999999918288,59.889976295668248]},"#0099ee":{"lch":[60.7408843834734853,90.8542474807105549,245.312254820906361],"luv":[60.7408843834734853,-37.9473430473261288,-82.5499451298106521],"rgb":[0,0.6,0.933333333333333348],"xyz":[0.2682170792107198,0.289538574434245854,0.850667077030784147],"hpluv":[245.312254820906361,189.803165481490907,60.7408843834734853],"hsluv":[245.312254820906361,99.9999999999919424,60.7408843834734853]},"#0099ff":{"lch":[61.6548256470178444,103.309645725501895,249.051296659176671],"luv":[61.6548256470178444,-36.936501733242423,-96.4809708680364082],"rgb":[0,0.6,1],"xyz":[0.294388127620551165,0.300006993798178578,0.988501265322566],"hpluv":[249.051296659176671,212.624411607996194,61.6548256470178444],"hsluv":[249.051296659176671,99.9999999999986926,61.6548256470178444]},"#44aa00":{"lch":[61.6346835386869714,87.655425968627469,122.331376925101353],"luv":[61.6346835386869714,-46.8794507107122556,74.0661245293922],"rgb":[0.266666666666666663,0.66666666666666663,0],"xyz":[0.167579386406696784,0.29977360690638849,0.0490310792412290836],"hpluv":[122.331376925101353,180.464989524422549,61.6346835386869714],"hsluv":[122.331376925101353,100.00000000000226,61.6346835386869714]},"#44aa11":{"lch":[61.6696010074672927,86.3319887480293175,122.80087722620695],"luv":[61.6696010074672927,-46.7678581574140253,72.5670705248452492],"rgb":[0.266666666666666663,0.66666666666666663,0.0666666666666666657],"xyz":[0.168591051906333916,0.300178273106243343,0.0543591842059847133],"hpluv":[122.80087722620695,177.639660035048053,61.6696010074672927],"hsluv":[122.80087722620695,97.7717273205757778,61.6696010074672927]},"#44aa22":{"lch":[61.7342457720645541,83.9245562706402097,123.699102784065559],"luv":[61.7342457720645541,-46.5639790081192,69.8221097085679],"rgb":[0.266666666666666663,0.66666666666666663,0.133333333333333331],"xyz":[0.170466410044810918,0.300928416361634143,0.0642360704019639128],"hpluv":[123.699102784065559,172.505216938407472,61.7342457720645541],"hsluv":[123.699102784065559,93.6968530578778882,61.7342457720645541]},"#44aa33":{"lch":[61.8404488118369784,80.0901636841586111,125.261260913590505],"luv":[61.8404488118369784,-46.2365065380172098,65.3958697634278],"rgb":[0.266666666666666663,0.66666666666666663,0.2],"xyz":[0.173554160777268651,0.30216351665461727,0.080498224259575063],"hpluv":[125.261260913590505,164.340980941234676,61.8404488118369784],"hsluv":[125.261260913590505,87.1410828818075913,61.8404488118369784]},"#44aa44":{"lch":[61.9932720073472,74.8379874444649573,127.71501294923992],"luv":[61.9932720073472,-45.7809668449718501,59.2015830825281526],"rgb":[0.266666666666666663,0.66666666666666663,0.266666666666666663],"xyz":[0.178012156021487539,0.303946714752304836,0.103976999212461702],"hpluv":[127.71501294923992,153.185220958209158,61.9932720073472],"hsluv":[127.71501294923992,77.998580561819125,61.9932720073472]},"#44aa55":{"lch":[62.1967362447927883,68.3415808764166854,131.40733952388851],"luv":[62.1967362447927883,-45.2016647888952292,51.2579864704043118],"rgb":[0.266666666666666663,0.66666666666666663,0.333333333333333315],"xyz":[0.183974570060849552,0.306331680368049697,0.135379046486435617],"hpluv":[131.40733952388851,139.430168232009549,62.1967362447927883],"hsluv":[131.40733952388851,78.575415606985473,62.1967362447927883]},"#44aa66":{"lch":[62.4540496972210377,60.9745995384144,136.885019691281769],"luv":[62.4540496972210377,-44.510458035487396,41.6739836629655684],"rgb":[0.266666666666666663,0.66666666666666663,0.4],"xyz":[0.191559565834357709,0.309365678677453,0.175326690893579551],"hpluv":[136.885019691281769,123.88755407265441,62.4540496972210377],"hsluv":[136.885019691281769,79.2669246679894854,62.4540496972210377]},"#44aa77":{"lch":[62.7677273349888196,53.3862032124370103,144.988299459670287],"luv":[62.7677273349888196,-43.7251633677153,30.629998072253084],"rgb":[0.266666666666666663,0.66666666666666663,0.466666666666666674],"xyz":[0.200873578854559642,0.313091283885533811,0.22438049279997746],"hpluv":[144.988299459670287,107.927460302012818,62.7677273349888196],"hsluv":[144.988299459670287,80.0573307147673603,62.7677273349888196]},"#44aa88":{"lch":[63.1396635168947142,46.6323085826041606,156.819414130132799],"luv":[63.1396635168947142,-42.8676249510766354,18.3558964585511255],"rgb":[0.266666666666666663,0.66666666666666663,0.533333333333333326],"xyz":[0.212013995956503443,0.317547450726311409,0.283053356203549655],"hpluv":[156.819414130132799,93.7182088199909,63.1396635168947142],"hsluv":[156.819414130132799,80.9270306732118456,63.1396635168947142]},"#44aa99":{"lch":[63.5711832083111,42.2713825005608328,173.059905511526438],"luv":[63.5711832083111,-41.9616613239929848,5.10771548142927578],"rgb":[0.266666666666666663,0.66666666666666663,0.6],"xyz":[0.22507096006557617,0.322770236369940566,0.351820033844667535],"hpluv":[173.059905511526438,84.3772726773670598,63.5711832083111],"hsluv":[173.059905511526438,81.8544924931781281,63.5711832083111]},"#44aaaa":{"lch":[64.0630839897801536,41.9755778813547948,192.177050630061],"luv":[64.0630839897801536,-41.03114670244819,-8.85404646225986802],"rgb":[0.266666666666666663,0.66666666666666663,0.66666666666666663],"xyz":[0.240128653030828698,0.328793313556041689,0.431123883461666135],"hpluv":[192.177050630061,83.1434743556685731,64.0630839897801536],"hsluv":[192.177050630061,82.8180264051213868,64.0630839897801536]},"#44aabb":{"lch":[64.6156750410269893,46.3675567205894481,210.140689328835606],"luv":[64.6156750410269893,-40.0984334486196374,-23.2823098339426302],"rgb":[0.266666666666666663,0.66666666666666663,0.733333333333333282],"xyz":[0.257266245138165373,0.335648350398976447,0.521381868560307882],"hpluv":[210.140689328835606,91.0574853549996135,64.6156750410269893],"hsluv":[210.140689328835606,83.7972417316187,64.6156750410269893]},"#44aacc":{"lch":[65.2288162125558131,54.5527176111565382,224.088695054270687],"luv":[65.2288162125558131,-39.1832309762682058,-37.956203827872514],"rgb":[0.266666666666666663,0.66666666666666663,0.8],"xyz":[0.276558620581887071,0.343365300576465249,0.622988379230578],"hpluv":[224.088695054270687,106.124637545316332,65.2288162125558131],"hsluv":[224.088695054270687,84.7740840933985424,65.2288162125558131]},"#44aadd":{"lch":[65.9019585504415772,65.1392687949415574,233.984713087438024],"luv":[65.9019585504415772,-38.3019605797345406,-52.6885581021924381],"rgb":[0.266666666666666663,0.66666666666666663,0.866666666666666696],"xyz":[0.29807694613252067,0.351972630796718755,0.736318227130584],"hpluv":[233.984713087438024,125.424934833645892,65.9019585504415772],"hsluv":[233.984713087438024,85.7334354760652246,65.9019585504415772]},"#44aaee":{"lch":[66.634186587349916,77.0517400481588,240.904642753345257],"luv":[66.634186587349916,-37.4675316968449081,-67.3287064556787698],"rgb":[0.266666666666666663,0.66666666666666663,0.933333333333333348],"xyz":[0.32188912639869538,0.361497502903188772,0.861729043199107259],"hpluv":[240.904642753345257,146.73194579095005,66.634186587349916],"hsluv":[240.904642753345257,86.6633273618325717,66.634186587349916]},"#44aaff":{"lch":[67.4242620727880677,89.6163291450103827,245.832487512483851],"luv":[67.4242620727880677,-36.6894434071527229,-81.7616731231704534],"rgb":[0.266666666666666663,0.66666666666666663,1],"xyz":[0.348060174808526801,0.371965922267121496,0.999563231490889104],"hpluv":[245.832487512483851,168.659292786049974,67.4242620727880677],"hsluv":[245.832487512483851,99.9999999999982094,67.4242620727880677]},"#44bb00":{"lch":[67.1028050092269126,96.9162111575721497,123.392710981560953],"luv":[67.1028050092269126,-53.3402140179528601,80.9170782570533476],"rgb":[0.266666666666666663,0.733333333333333282,0],"xyz":[0.201533884315295564,0.367682602723587049,0.0603492452107617],"hpluv":[123.392710981560953,183.271561122122193,67.1028050092269126],"hsluv":[123.392710981560953,100.00000000000226,67.1028050092269126]},"#44bb11":{"lch":[67.1332810832727347,95.7453830613523138,123.778355355208646],"luv":[67.1332810832727347,-53.2326764896654581,79.5830417319651673],"rgb":[0.266666666666666663,0.733333333333333282,0.0666666666666666657],"xyz":[0.202545549814932696,0.368087268923441902,0.0656773501755173322],"hpluv":[123.778355355208646,180.975295348072393,67.1332810832727347],"hsluv":[123.778355355208646,98.1806384027583334,67.1332810832727347]},"#44bb22":{"lch":[67.1897165718252438,93.6085464076245728,124.511290753684577],"luv":[67.1897165718252438,-53.03566579282883,77.1348048189646391],"rgb":[0.266666666666666663,0.733333333333333282,0.133333333333333331],"xyz":[0.204420907953409697,0.368837412178832702,0.0755542363714965248],"hpluv":[124.511290753684577,176.787689053856155,67.1897165718252438],"hsluv":[124.511290753684577,94.8452524510698538,67.1897165718252438]},"#44bb33":{"lch":[67.2824703298360873,90.1849869824975627,125.771296969252688],"luv":[67.2824703298360873,-52.7177504009765485,73.1721987485246501],"rgb":[0.266666666666666663,0.733333333333333282,0.2],"xyz":[0.207508658685867431,0.370072512471815829,0.091816390229107675],"hpluv":[125.771296969252688,170.087205132927863,67.2824703298360873],"hsluv":[125.771296969252688,89.4565739304913308,67.2824703298360873]},"#44bb44":{"lch":[67.4160218575577375,85.4495691526201284,127.715012949240077],"luv":[67.4160218575577375,-52.2724357759646807,67.5960156105081182],"rgb":[0.266666666666666663,0.733333333333333282,0.266666666666666663],"xyz":[0.211966653930086318,0.371855710569503395,0.115295165181994314],"hpluv":[127.715012949240077,160.837043785954393,67.4160218575577375],"hsluv":[127.715012949240077,81.8947222100884318,67.4160218575577375]},"#44bb55":{"lch":[67.5939766620447813,79.5000401119176,130.566112214394138],"luv":[67.5939766620447813,-51.7008659583180759,60.3926886051327756],"rgb":[0.266666666666666663,0.733333333333333282,0.333333333333333315],"xyz":[0.217929067969448331,0.374240676185248256,0.146697212455968229],"hpluv":[130.566112214394138,149.244617358403957,67.5939766620447813],"hsluv":[130.566112214394138,82.2871698148834412,67.5939766620447813]},"#44bb66":{"lch":[67.8192698910356313,72.5749222232247,134.657948179728322],"luv":[67.8192698910356313,-51.0109407747319139,51.6236695420221778],"rgb":[0.266666666666666663,0.733333333333333282,0.4],"xyz":[0.225514063742956489,0.377274674494651574,0.186644856863112191],"hpluv":[134.657948179728322,135.791565604529097,67.8192698910356313],"hsluv":[134.657948179728322,82.7624878443234451,67.8192698910356313]},"#44bb77":{"lch":[68.0942730594189527,65.0902087481164671,140.487577627549143],"luv":[68.0942730594189527,-50.2162275054035163,41.4133525568618168],"rgb":[0.266666666666666663,0.733333333333333282,0.466666666666666674],"xyz":[0.234828076763158422,0.38100027970273237,0.2356986587695101],"hpluv":[140.487577627549143,121.295420087000366,68.0942730594189527],"hsluv":[140.487577627549143,83.3123735080707206,68.0942730594189527]},"#44bb88":{"lch":[68.4208577329380461,57.7058903955470939,148.752185803016886],"luv":[68.4208577329380461,-49.3345927236295907,29.9343907092910229],"rgb":[0.266666666666666663,0.733333333333333282,0.533333333333333326],"xyz":[0.245968493865102222,0.385456446543509967,0.294371522173082267],"hpluv":[148.752185803016886,107.021481540968693,68.4208577329380461],"hsluv":[148.752185803016886,83.9257037652282776,68.4208577329380461]},"#44bb99":{"lch":[68.8004384509552267,51.4171630403873507,160.23039633414129],"luv":[68.8004384509552267,-48.3866530594068536,17.3912754227620781],"rgb":[0.266666666666666663,0.733333333333333282,0.6],"xyz":[0.259025457974175,0.390679232187139125,0.363138199814200147],"hpluv":[160.23039633414129,94.8322885243955,68.8004384509552267],"hsluv":[160.23039633414129,84.5895330052649683,68.8004384509552267]},"#44bbaa":{"lch":[69.2340056114394571,47.5630236168925,175.171204236910796],"luv":[69.2340056114394571,-47.3942071553531576,4.003791165451279],"rgb":[0.266666666666666663,0.733333333333333282,0.66666666666666663],"xyz":[0.274083150939427478,0.396702309373240247,0.442442049431198747],"hpluv":[175.171204236910796,87.1744714415039113,69.2340056114394571],"hsluv":[175.171204236910796,85.2901010545764251,69.2340056114394571]},"#44bbbb":{"lch":[69.722153945093,47.4463312905994243,192.177050630061103],"luv":[69.722153945093,-46.3788106784417877,-10.0080104411700219],"rgb":[0.266666666666666663,0.733333333333333282,0.733333333333333282],"xyz":[0.291220743046764152,0.403557346216175,0.532700034529840494],"hpluv":[192.177050630061103,86.3517549054621156,69.722153945093],"hsluv":[192.177050630061103,86.0137488036252,69.722153945093]},"#44bbcc":{"lch":[70.265109629848,51.5238495855336254,208.311647260201596],"luv":[70.265109629848,-45.3606162241423405,-24.4360711219862],"rgb":[0.266666666666666663,0.733333333333333282,0.8],"xyz":[0.310513118490485907,0.411274296393663807,0.63430654520011065],"hpluv":[208.311647260201596,93.0481841100277478,70.265109629848],"hsluv":[208.311647260201596,86.7476639801695484,70.265109629848]},"#44bbdd":{"lch":[70.8627576511434683,59.1258582536945,221.390354970453018],"luv":[70.8627576511434683,-44.357542252504274,-39.0931651258026491],"rgb":[0.266666666666666663,0.733333333333333282,0.866666666666666696],"xyz":[0.332031444041119395,0.419881626613917314,0.747636393100116647],"hpluv":[221.390354970453018,105.876295000483495,70.8627576511434683],"hsluv":[221.390354970453018,87.4804162985680449,70.8627576511434683]},"#44bbee":{"lch":[71.5146701379092207,69.1277260896420813,231.126492355228834],"luv":[71.5146701379092207,-43.3847784652122499,-53.8182451576511269],"rgb":[0.266666666666666663,0.733333333333333282,0.933333333333333348],"xyz":[0.35584362430729416,0.429406498720387331,0.873047209168639871],"hpluv":[231.126492355228834,122.65816352572412,71.5146701379092207],"hsluv":[231.126492355228834,88.202277385306985,71.5146701379092207]},"#44bbff":{"lch":[72.2201358507708,80.5712163793027685,238.202407995552562],"luv":[72.2201358507708,-42.4545914747530801,-68.4786723845625573],"rgb":[0.266666666666666663,0.733333333333333282,1],"xyz":[0.382014672717125525,0.439874918084320055,1.01088139746042183],"hpluv":[238.202407995552562,141.566646923483631,72.2201358507708],"hsluv":[238.202407995552562,99.9999999999976694,72.2201358507708]},"#44cc00":{"lch":[72.503692055952385,105.959797206167082,124.178253965335855],"luv":[72.503692055952385,-59.5249745926526543,87.6598883396283384],"rgb":[0.266666666666666663,0.8,0],"xyz":[0.239757627624912484,0.444130089342821943,0.0730904929806336506],"hpluv":[124.178253965335855,185.447217969921951,72.503692055952385],"hsluv":[124.178253965335855,100.000000000002359,72.503692055952385]},"#44cc11":{"lch":[72.5305637479271752,104.914943747871533,124.498982817725434],"luv":[72.5305637479271752,-59.4229434801529877,86.4642076802510928],"rgb":[0.266666666666666663,0.8,0.0666666666666666657],"xyz":[0.240769293124549616,0.444534755542676796,0.0784185979453892873],"hpluv":[124.498982817725434,183.55052269402043,72.5305637479271752],"hsluv":[124.498982817725434,98.4921383302381628,72.5305637479271752]},"#44cc22":{"lch":[72.5803335982668,103.003123626508128,125.105641856618192],"luv":[72.5803335982668,-59.2356349500081265,84.2661440252673088],"rgb":[0.266666666666666663,0.8,0.133333333333333331],"xyz":[0.242644651263026617,0.445284898798067597,0.0882954841413684799],"hpluv":[125.105641856618192,180.082189217111164,72.5803335982668],"hsluv":[125.105641856618192,95.7225658430248387,72.5803335982668]},"#44cc33":{"lch":[72.6621573160580283,99.9260617146724144,126.139923542820412],"luv":[72.6621573160580283,-58.9323161564504758,80.6982027200153],"rgb":[0.266666666666666663,0.8,0.2],"xyz":[0.245732401995484351,0.446519999091050723,0.10455763799897963],"hpluv":[126.139923542820412,174.505777504147545,72.6621573160580283],"hsluv":[126.139923542820412,91.233607132201584,72.6621573160580283]},"#44cc44":{"lch":[72.780026014363628,95.6382843785450518,127.715012949240148],"luv":[72.780026014363628,-58.5052227586060383,75.6559339961998],"rgb":[0.266666666666666663,0.8,0.266666666666666663],"xyz":[0.250190397239703266,0.448303197188738289,0.128036412951866269],"hpluv":[127.715012949240148,166.747333178852926,72.780026014363628],"hsluv":[127.715012949240148,84.9041129363740339,72.780026014363628]},"#44cc55":{"lch":[72.9371837422115732,90.1892610669535912,129.983942971096695],"luv":[72.9371837422115732,-57.9531752136704,69.1052262456084492],"rgb":[0.266666666666666663,0.8,0.333333333333333315],"xyz":[0.256152811279065251,0.45068816280448315,0.159438460225840184],"hpluv":[129.983942971096695,156.908028239254975,72.9371837422115732],"hsluv":[129.983942971096695,85.1779272269293557,72.9371837422115732]},"#44cc66":{"lch":[73.1363103507417236,83.732792729713168,133.164212643981188],"luv":[73.1363103507417236,-57.28090462038152,61.074368962666],"rgb":[0.266666666666666663,0.8,0.4],"xyz":[0.263737807052573436,0.453722161113886469,0.199386104632984118],"hpluv":[133.164212643981188,145.278670258241675,73.1363103507417236],"hsluv":[133.164212643981188,85.5122241677148764,73.1363103507417236]},"#44cc77":{"lch":[73.3796177536812309,76.5464624175537,137.569101073773794],"luv":[73.3796177536812309,-56.4983001604517625,51.6459387330839],"rgb":[0.266666666666666663,0.8,0.466666666666666674],"xyz":[0.273051820072775342,0.457447766321967264,0.248439906539382027],"hpluv":[137.569101073773794,132.369828459728325,73.3796177536812309],"hsluv":[137.569101073773794,85.9026534082125153,73.3796177536812309]},"#44cc88":{"lch":[73.6689069899719442,69.0664605730878378,143.639563525196422],"luv":[73.6689069899719442,-55.6194534086204087,40.9469459010095278],"rgb":[0.266666666666666663,0.8,0.533333333333333326],"xyz":[0.284192237174719142,0.461903933162744862,0.307112769942954222],"hpluv":[143.639563525196422,118.965846887396481,73.6689069899719442],"hsluv":[143.639563525196422,86.3428682965135863,73.6689069899719442]},"#44cc99":{"lch":[74.0056056952011,61.9424628035573548,151.940118655705163],"luv":[74.0056056952011,-54.661526046940331,29.1373689338922652],"rgb":[0.266666666666666663,0.8,0.6],"xyz":[0.297249201283791842,0.467126718806374,0.375879447584072102],"hpluv":[151.940118655705163,106.209454381597496,74.0056056952011],"hsluv":[151.940118655705163,86.8250568088678,74.0056056952011]},"#44ccaa":{"lch":[74.3907954563299256,56.0938540204294327,163.002493202880316],"luv":[74.3907954563299256,-53.6435329798001206,16.3979214631106132],"rgb":[0.266666666666666663,0.8,0.66666666666666663],"xyz":[0.312306894249044398,0.473149795992475142,0.455183297201070702],"hpluv":[163.002493202880316,95.6831366692641581,74.3907954563299256],"hsluv":[163.002493202880316,87.3405106459777727,74.3907954563299256]},"#44ccbb":{"lch":[74.8252340948823331,52.6660587979454959,176.823724318960615],"luv":[74.8252340948823331,-52.5851528012588929,2.91812511330579927],"rgb":[0.266666666666666663,0.8,0.733333333333333282],"xyz":[0.329444486356381072,0.4800048328354099,0.545441282299712449],"hpluv":[176.823724318960615,89.3145186414393493,74.8252340948823331],"hsluv":[176.823724318960615,87.8801809558037235,74.8252340948823331]},"#44cccc":{"lch":[75.3093757141467393,52.6911947618550442,192.177050630061103],"luv":[75.3093757141467393,-51.5056671360628826,-11.1143267137884898],"rgb":[0.266666666666666663,0.8,0.8],"xyz":[0.348736861800102771,0.487721783012898702,0.647047792969982605],"hpluv":[192.177050630061103,88.7826952764381758,75.3093757141467393],"hsluv":[192.177050630061103,88.4351737608208168,75.3093757141467393]},"#44ccdd":{"lch":[75.8433901234223669,56.5143539908888,206.846753698803184],"luv":[75.8433901234223669,-50.423101427505074,-25.5222069860548118],"rgb":[0.266666666666666663,0.8,0.866666666666666696],"xyz":[0.37025518735073637,0.496329113233152208,0.760377640869988602],"hpluv":[206.846753698803184,94.5540981724771257,75.8433901234223669],"hsluv":[206.846753698803184,88.9971515077065334,75.8433901234223669]},"#44ccee":{"lch":[76.4271825186181104,63.6191993934700264,219.125479385679029],"luv":[76.4271825186181104,-49.3536035237008,-40.1450414209705926],"rgb":[0.266666666666666663,0.8,0.933333333333333348],"xyz":[0.39406736761691108,0.505853985339622225,0.885788456938511826],"hpluv":[219.125479385679029,108.248092211119214,76.4271825186181104],"hsluv":[219.125479385679029,89.5586239337052,76.4271825186181104]},"#44ccff":{"lch":[77.0604138316104752,73.087044724203011,228.6232677289035],"luv":[77.0604138316104752,-48.3110621928290342,-54.8430248647743426],"rgb":[0.266666666666666663,0.8,1],"xyz":[0.420238416026742501,0.516322404703555,1.02362264523029367],"hpluv":[228.6232677289035,128.526456918779161,77.0604138316104752],"hsluv":[228.6232677289035,99.9999999999969731,77.0604138316104752]},"#44dd00":{"lch":[77.8394471675691193,114.806757868746558,124.774603647715026],"luv":[77.8394471675691193,-65.4799812263264869,94.3025116894186],"rgb":[0.266666666666666663,0.866666666666666696,0],"xyz":[0.282391618172087688,0.529398070437173462,0.0873018231630249691],"hpluv":[124.774603647715026,210.465861771712326,77.8394471675691193],"hsluv":[124.774603647715026,100.000000000002174,77.8394471675691193]},"#44dd11":{"lch":[77.8633510332093692,113.867279553553601,125.04447057905567],"luv":[77.8633510332093692,-65.3839645843072503,93.2238946202376724],"rgb":[0.266666666666666663,0.866666666666666696,0.0666666666666666657],"xyz":[0.283403283671724793,0.529802736637028371,0.0926299281277806],"hpluv":[125.04447057905567,209.014039294370775,77.8633510332093692],"hsluv":[125.04447057905567,98.7339197526847272,77.8633510332093692]},"#44dd22":{"lch":[77.9076302523257738,112.144788460185168,125.553114236235714],"luv":[77.9076302523257738,-65.2074178024426914,91.2384033306006472],"rgb":[0.266666666666666663,0.866666666666666696,0.133333333333333331],"xyz":[0.28527864181020185,0.530552879892419171,0.102506814323759798],"hpluv":[125.553114236235714,206.347170822894697,77.9076302523257738],"hsluv":[125.553114236235714,96.4050109160080382,77.9076302523257738]},"#44dd33":{"lch":[77.9804445180848802,109.362510567887881,126.41495586461761],"luv":[77.9804445180848802,-64.9207541848309546,88.0082632130877869],"rgb":[0.266666666666666663,0.866666666666666696,0.2],"xyz":[0.288366392542659555,0.531787980185402298,0.118768968181370949],"hpluv":[126.41495586461761,202.025685311970193,77.9804445180848802],"hsluv":[126.41495586461761,92.6208241654068729,77.9804445180848802]},"#44dd44":{"lch":[78.0853727898892345,105.463232003770898,127.715012949240119],"luv":[78.0853727898892345,-64.5154806081751389,83.428089194092081],"rgb":[0.266666666666666663,0.866666666666666696,0.266666666666666663],"xyz":[0.292824387786878471,0.533571178283089864,0.142247743134257587],"hpluv":[127.715012949240119,195.940425098340825,78.0853727898892345],"hsluv":[127.715012949240119,87.2650912217990395,78.0853727898892345]},"#44dd55":{"lch":[78.2253459168282888,100.46502238820301,129.562971792001804],"luv":[78.2253459168282888,-63.9887749627021662,77.4510000079714303],"rgb":[0.266666666666666663,0.866666666666666696,0.333333333333333315],"xyz":[0.298786801826240456,0.535956143898834614,0.173649790408231475],"hpluv":[129.562971792001804,188.090878204767051,78.2253459168282888],"hsluv":[129.562971792001804,87.4605090914447,78.2253459168282888]},"#44dd66":{"lch":[78.4028117957757615,94.4661355921174,132.108441056441876],"luv":[78.4028117957757615,-63.3429373931831776,70.0822592109557],"rgb":[0.266666666666666663,0.866666666666666696,0.4],"xyz":[0.306371797599748641,0.538990142208237932,0.213597434815375464],"hpluv":[132.108441056441876,178.59774401526758,78.4028117957757615],"hsluv":[132.108441056441876,87.7006053461373654,78.4028117957757615]},"#44dd77":{"lch":[78.6198227824069278,87.656024220764337,135.559861003190832],"luv":[78.6198227824069278,-62.584854150819659,61.3735660778470375],"rgb":[0.266666666666666663,0.866666666666666696,0.466666666666666674],"xyz":[0.315685810619950546,0.542715747416318783,0.262651236721773373],"hpluv":[135.559861003190832,167.73117312681623,78.6198227824069278],"hsluv":[135.559861003190832,87.9831439767702221,78.6198227824069278]},"#44dd88":{"lch":[78.8780874692010201,80.3346414261830404,140.206181380764804],"luv":[78.8780874692010201,-61.7253288138421823,51.416324216113189],"rgb":[0.266666666666666663,0.866666666666666696,0.533333333333333326],"xyz":[0.326826227721894347,0.547171914257096326,0.321324100125345513],"hpluv":[140.206181380764804,155.961921377148798,78.8780874692010201],"hsluv":[140.206181380764804,88.3044935606717729,78.8780874692010201]},"#44dd99":{"lch":[79.1790042342924,72.9438114093828602,146.430978952194238],"luv":[79.1790042342924,-60.7782665628728651,40.3336328210091182],"rgb":[0.266666666666666663,0.866666666666666696,0.6],"xyz":[0.339883191830967046,0.552394699900725539,0.390090777766463448],"hpluv":[146.430978952194238,144.047423549525234,79.1790042342924],"hsluv":[146.430978952194238,88.6599152631773109,79.1790042342924]},"#44ddaa":{"lch":[79.5236849812282,66.109750002698334,154.681927012774025],"luv":[79.5236849812282,-59.7597565852220498,28.2713731236083063],"rgb":[0.266666666666666663,0.866666666666666696,0.66666666666666663],"xyz":[0.354940884796219602,0.558417777086826606,0.469394627383462049],"hpluv":[154.681927012774025,133.15854000611057,79.5236849812282],"hsluv":[154.681927012774025,89.0438856180527267,79.5236849812282]},"#44ddbb":{"lch":[79.9129735834354733,60.6712530747818448,165.306614809553963],"luv":[79.9129735834354733,-58.687123678359157,15.3890371376899697],"rgb":[0.266666666666666663,0.866666666666666696,0.733333333333333282],"xyz":[0.372078476903556277,0.565272813929761364,0.55965261248210374],"hpluv":[165.306614809553963,125.005338667233715,79.9129735834354733],"hsluv":[165.306614809553963,89.4504295982059574,79.9129735834354733]},"#44ddcc":{"lch":[80.3474616163736783,57.6077690930220925,178.158710030216184],"luv":[80.3474616163736783,-57.5780241737671616,1.85099760130491053],"rgb":[0.266666666666666663,0.866666666666666696,0.8],"xyz":[0.391370852347278,0.572989764107250221,0.661259123152373895],"hpluv":[178.158710030216184,121.786592068950441,80.3474616163736783],"hsluv":[178.158710030216184,89.8734379183952399,80.3474616163736783]},"#44dddd":{"lch":[80.8275029051271758,57.7489755309586,192.177050630061132],"luv":[80.8275029051271758,-56.4496501662069505,-12.1811810177875159],"rgb":[0.266666666666666663,0.866666666666666696,0.866666666666666696],"xyz":[0.412889177897911575,0.581597094327503727,0.774588971052379893],"hpluv":[192.177050630061132,125.674721736272474,80.8275029051271758],"hsluv":[192.177050630061132,90.3069463225795204,80.8275029051271758]},"#44ddee":{"lch":[81.3532277894993143,61.361693698739991,205.642938459455962],"luv":[81.3532277894993143,-55.3180860243278758,-26.5549771640468073],"rgb":[0.266666666666666663,0.866666666666666696,0.933333333333333348],"xyz":[0.436701358164086284,0.591121966433973745,0.899999787120903116],"hpluv":[205.642938459455962,137.939634766348263,81.3532277894993143],"hsluv":[205.642938459455962,90.7453615563817806,81.3532277894993143]},"#44ddff":{"lch":[81.9245576129038113,68.0382655080336463,217.195369709248553],"luv":[81.9245576129038113,-54.1978382178125813,-41.1314953029609],"rgb":[0.266666666666666663,0.866666666666666696,1],"xyz":[0.462872406573917705,0.601590385797906468,1.03783397541268507],"hpluv":[217.195369709248553,158.576151866946,81.9245576129038113],"hsluv":[217.195369709248553,99.9999999999957367,81.9245576129038113]},"#44ee00":{"lch":[83.112739541513335,123.476763986331008,125.23710114083579],"luv":[83.112739541513335,-71.241317589729718,100.852297507867192],"rgb":[0.266666666666666663,0.933333333333333348,0],"xyz":[0.329570394512602505,0.623755623118204428,0.103028081943196154],"hpluv":[125.23710114083579,311.240798427125753,83.112739541513335],"hsluv":[125.23710114083579,100.000000000002402,83.112739541513335]},"#44ee11":{"lch":[83.1341682891089135,122.626446998812852,125.466600012922257],"luv":[83.1341682891089135,-71.1513320296005674,99.8735873690646656],"rgb":[0.266666666666666663,0.933333333333333348,0.0666666666666666657],"xyz":[0.330582060012239609,0.624160289318059336,0.10835618690795179],"hpluv":[125.466600012922257,309.547675363147619,83.1341682891089135],"hsluv":[125.466600012922257,98.9247180409442279,83.1341682891089135]},"#44ee22":{"lch":[83.1738669889620184,121.064908518289769,125.897986234483838],"luv":[83.1738669889620184,-70.9856689153146192,98.0701120789979],"rgb":[0.266666666666666663,0.933333333333333348,0.133333333333333331],"xyz":[0.332457418150716666,0.624910432573450136,0.118233073103930983],"hpluv":[125.897986234483838,306.432309176179558,83.1738669889620184],"hsluv":[125.897986234483838,96.9444732717922,83.1738669889620184]},"#44ee33":{"lch":[83.2391611795664517,118.535427876706891,126.625494433478437],"luv":[83.2391611795664517,-70.7161072615436694,95.1308563805026921],"rgb":[0.266666666666666663,0.933333333333333348,0.2],"xyz":[0.335545168883174372,0.626145532866433263,0.134495226961542119],"hpluv":[126.625494433478437,301.369067751793693,83.2391611795664517],"hsluv":[126.625494433478437,93.7204451717133651,83.2391611795664517]},"#44ee44":{"lch":[83.3332795320092714,114.974418092600288,127.715012949240275],"luv":[83.3332795320092714,-70.3337997514069855,90.9520391649414],"rgb":[0.266666666666666663,0.933333333333333348,0.266666666666666663],"xyz":[0.340003164127393287,0.627928730964120829,0.157974001914428758],"hpluv":[127.715012949240275,294.205765091151079,83.3332795320092714],"hsluv":[127.715012949240275,89.1439564147074321,83.3332795320092714]},"#44ee55":{"lch":[83.4588814464859183,110.379279114867671,129.248151137931956],"luv":[83.4588814464859183,-69.8347997913980123,85.479155330485213],"rgb":[0.266666666666666663,0.933333333333333348,0.333333333333333315],"xyz":[0.345965578166755272,0.630313696579865579,0.189376049188402673],"hpluv":[129.248151137931956,284.901382538762221,83.4588814464859183],"hsluv":[129.248151137931956,89.2862876088480419,83.4588814464859183]},"#44ee66":{"lch":[83.6182069813856,104.810737833959209,131.33226272314829],"luv":[83.6182069813856,-69.2195888954281,78.7015837099029],"rgb":[0.266666666666666663,0.933333333333333348,0.4],"xyz":[0.353550573940263457,0.633347694889268897,0.229323693595546635],"hpluv":[131.33226272314829,273.53562680177663,83.6182069813856],"hsluv":[131.33226272314829,89.462046779026835,83.6182069813856]},"#44ee77":{"lch":[83.8131566406418642,98.3993759238413759,134.112401675041724],"luv":[83.8131566406418642,-68.4926783274834889,70.6483559449852123],"rgb":[0.266666666666666663,0.933333333333333348,0.466666666666666674],"xyz":[0.362864586960465363,0.637073300097349748,0.278377495501944572],"hpluv":[134.112401675041724,260.333399951005049,83.8131566406418642],"hsluv":[134.112401675041724,89.6701381262035682,83.8131566406418642]},"#44ee88":{"lch":[84.045338735079568,91.3569443918750892,137.785521400150031],"luv":[84.045338735079568,-67.6621349200187439,61.3834406553211949],"rgb":[0.266666666666666663,0.933333333333333348,0.533333333333333326],"xyz":[0.374005004062409163,0.641529466938127291,0.337050358905516712],"hpluv":[137.785521400150031,245.709311488591595,84.045338735079568],"hsluv":[137.785521400150031,89.9084899734552323,84.045338735079568]},"#44ee99":{"lch":[84.3160998906388386,83.9946679616676306,142.613931790318958],"luv":[84.3160998906388386,-66.738995210465,51.0001055321292895],"rgb":[0.266666666666666663,0.933333333333333348,0.6],"xyz":[0.387061968171481863,0.646752252581756504,0.405817036546634591],"hpluv":[142.613931790318958,230.343048149298568,84.3160998906388386],"hsluv":[142.613931790318958,90.1742147406472725,84.3160998906388386]},"#44eeaa":{"lch":[84.626546237332235,76.7502660373389887,148.925817786423067],"luv":[84.626546237332235,-65.7365837009088239,39.6144531722415607],"rgb":[0.266666666666666663,0.933333333333333348,0.66666666666666663],"xyz":[0.402119661136734419,0.652775329767857571,0.485120886163633247],"hpluv":[148.925817786423067,215.298316825794302,84.626546237332235],"hsluv":[148.925817786423067,90.4637941794664897,84.626546237332235]},"#44eebb":{"lch":[84.9775593290447,70.218708007349818,157.069253127653155],"luv":[84.9775593290447,-64.6697768267590618,27.3584889823731068],"rgb":[0.266666666666666663,0.933333333333333348,0.733333333333333282],"xyz":[0.419257253244071093,0.659630366610792329,0.575378871262274938],"hpluv":[157.069253127653155,202.183693696822303,84.9775593290447],"hsluv":[157.069253127653155,90.7732788992861401,84.9775593290447]},"#44eecc":{"lch":[85.3698091329633826,65.1592886039499462,167.256585364158155],"luv":[85.3698091329633826,-63.5542641697608914,14.3731832665241086],"rgb":[0.266666666666666663,0.933333333333333348,0.8],"xyz":[0.438549628687792792,0.667347316788281186,0.676985381932545094],"hpluv":[167.256585364158155,193.290067765431047,85.3698091329633826],"hsluv":[167.256585364158155,91.0984884260964,85.3698091329633826]},"#44eedd":{"lch":[85.803765500838054,62.4110099600490216,179.263728091065275],"luv":[85.803765500838054,-62.4058570040272613,0.801982435119061421],"rgb":[0.266666666666666663,0.933333333333333348,0.866666666666666696],"xyz":[0.460067954238426391,0.675954647008534693,0.790315229832551092],"hpluv":[179.263728091065275,191.499842549603557,85.803765500838054],"hsluv":[179.263728091065275,91.4351983708881875,85.803765500838054]},"#44eeee":{"lch":[86.2797089909746546,62.6494691870182407,192.17705063006116],"luv":[86.2797089909746546,-61.2398849709435922,-13.2148582346064085],"rgb":[0.266666666666666663,0.933333333333333348,0.933333333333333348],"xyz":[0.4838801345046011,0.68547951911500471,0.915726045901074315],"hpluv":[192.17705063006116,199.700166684316315,86.2797089909746546],"hsluv":[192.17705063006116,91.7793037580661775,86.2797089909746546]},"#44eeff":{"lch":[86.7977415696122847,66.0848691943621134,204.633388514668525],"luv":[86.7977415696122847,-60.0707080483631159,-27.5448719547627086],"rgb":[0.266666666666666663,0.933333333333333348,1],"xyz":[0.510051182914432522,0.695947938478937433,1.05356023419285627],"hpluv":[204.633388514668525,219.870556477974674,86.7977415696122847],"hsluv":[204.633388514668525,99.9999999999936904,86.7977415696122847]},"#44ff00":{"lch":[88.3264513606833,131.987460278186802,125.602389702763816],"luv":[88.3264513606833,-76.837408418496949,107.315899745634312],"rgb":[0.266666666666666663,1,0],"xyz":[0.381422766942276337,0.727460367977553535,0.120312206086420265],"hpluv":[125.602389702763816,502.990651378155178,88.3264513606833],"hsluv":[125.602389702763816,100.000000000002331,88.3264513606833]},"#44ff11":{"lch":[88.3457924202418496,131.213348009411362,125.799444038401859],"luv":[88.3457924202418496,-76.7532223203665609,106.423143908077066],"rgb":[0.266666666666666663,1,0.0666666666666666657],"xyz":[0.382434432441913441,0.727865034177408443,0.125640311051175901],"hpluv":[125.799444038401859,500.950310911531346,88.3457924202418496],"hsluv":[125.799444038401859,99.9999999999917577,88.3457924202418496]},"#44ff22":{"lch":[88.3816266358799538,129.789896608084,126.169061428897493],"luv":[88.3816266358799538,-76.5980824061806089,104.776672180562713],"rgb":[0.266666666666666663,1,0.133333333333333331],"xyz":[0.384309790580390498,0.728615177432799244,0.13551719724715508],"hpluv":[126.169061428897493,497.190929074143924,88.3816266358799538],"hsluv":[126.169061428897493,99.9999999999915445,88.3816266358799538]},"#44ff33":{"lch":[88.4405736189592204,127.478814054469,126.790121098902588],"luv":[88.4405736189592204,-76.3452168675528071,102.089450454884],"rgb":[0.266666666666666663,1,0.2],"xyz":[0.387397541312848204,0.72985027772578237,0.15177935110476623],"hpluv":[126.790121098902588,491.06642972530841,88.4405736189592204],"hsluv":[126.790121098902588,99.9999999999916,88.4405736189592204]},"#44ff44":{"lch":[88.5255621746627099,124.213522374992053,127.715012949240247],"luv":[88.5255621746627099,-75.9856771103925,98.2607552122302],"rgb":[0.266666666666666663,1,0.266666666666666663],"xyz":[0.391855536557067119,0.731633475823469936,0.175258126057652869],"hpluv":[127.715012949240247,482.369437382385513,88.5255621746627099],"hsluv":[127.715012949240247,99.9999999999915872,88.5255621746627099]},"#44ff55":{"lch":[88.6390158335401,119.977757557138,129.006344472088813],"luv":[88.6390158335401,-75.5147735105559832,93.2318684253877734],"rgb":[0.266666666666666663,1,0.333333333333333315],"xyz":[0.397817950596429104,0.734018441439214686,0.206660173331626784],"hpluv":[129.006344472088813,471.012302242813519,88.6390158335401],"hsluv":[129.006344472088813,99.9999999999916724,88.6390158335401]},"#44ff66":{"lch":[88.7829895920100256,114.806361082699226,130.743859599912923],"luv":[88.7829895920100256,-74.9316504675157518,86.9813100801842438],"rgb":[0.266666666666666663,1,0.4],"xyz":[0.405402946369937289,0.737052439748618,0.246607817738770746],"hpluv":[130.743859599912923,457.03528250903878,88.7829895920100256],"hsluv":[130.743859599912923,99.999999999991374,88.7829895920100256]},"#44ff77":{"lch":[88.9592430558717524,108.789243130847709,133.032335199193767],"luv":[88.9592430558717524,-74.2389756563468239,79.5215311376676226],"rgb":[0.266666666666666663,1,0.466666666666666674],"xyz":[0.414716959390139195,0.740778044956698856,0.295661619645168683],"hpluv":[133.032335199193767,440.631455855157412,88.9592430558717524],"hsluv":[133.032335199193767,99.999999999991374,88.9592430558717524]},"#44ff88":{"lch":[89.1692840038805343,102.078340317719523,136.01097660716411],"luv":[89.1692840038805343,-73.4425963051882462,70.8955048643657904],"rgb":[0.266666666666666663,1,0.533333333333333326],"xyz":[0.425857376492083,0.745234211797476398,0.354334483048740823],"hpluv":[136.01097660716411,422.1930512248951,89.1692840038805343],"hsluv":[136.01097660716411,99.9999999999912177,89.1692840038805343]},"#44ff99":{"lch":[89.4143964062191117,94.8987049380611438,139.863519733640288],"luv":[89.4143964062191117,-72.5511163277215303,61.1727040314764139],"rgb":[0.266666666666666663,1,0.6],"xyz":[0.43891434060115575,0.750456997441105611,0.423101160689858702],"hpluv":[139.863519733640288,402.3908266141122,89.4143964062191117],"hsluv":[139.863519733640288,99.9999999999912,89.4143964062191117]},"#44ffaa":{"lch":[89.695659684091666,87.5652599917726207,144.82485232612342],"luv":[89.695659684091666,-71.5753927145854192,50.4444042008586777],"rgb":[0.266666666666666663,1,0.66666666666666663],"xyz":[0.453972033566408251,0.756480074627206678,0.502405010306857358],"hpluv":[144.82485232612342,382.304282325397367,89.695659684091666],"hsluv":[144.82485232612342,99.9999999999909335,89.695659684091666]},"#44ffbb":{"lch":[90.0139628620153616,80.5050918065685153,151.171682460988421],"luv":[90.0139628620153616,-70.527972915407986,38.8184858440859557],"rgb":[0.266666666666666663,1,0.733333333333333282],"xyz":[0.471109625673744925,0.763335111470141436,0.592662995405499],"hpluv":[151.171682460988421,363.621116250194575,90.0139628620153616],"hsluv":[151.171682460988421,99.999999999990834,90.0139628620153616]},"#44ffcc":{"lch":[90.3700157308713443,74.2777558665274853,159.169074542576084],"luv":[90.3700157308713443,-69.4225064855060481,26.4140229771485018],"rgb":[0.266666666666666663,1,0.8],"xyz":[0.490402001117466679,0.771052061647630294,0.694269506075769205],"hpluv":[159.169074542576084,348.900804743837909,90.3700157308713443],"hsluv":[159.169074542576084,99.9999999999905924,90.3700157308713443]},"#44ffdd":{"lch":[90.7643583149998,69.56728840997188,168.931262835156701],"luv":[90.7643583149998,-68.2731674827513899,13.3559806299794577],"rgb":[0.266666666666666663,1,0.866666666666666696],"xyz":[0.511920326668100167,0.7796593918678838,0.807599353975775203],"hpluv":[168.931262835156701,341.810509676969502,90.7643583149998],"hsluv":[168.931262835156701,99.9999999999902798,90.7643583149998]},"#44ffee":{"lch":[91.1973694573754869,67.0945142582603182,180.196137266844971],"luv":[91.1973694573754869,-67.094121132442,-0.229680249688981986],"rgb":[0.266666666666666663,1,0.933333333333333348],"xyz":[0.535732506934274877,0.789184263974353817,0.933010170044298426],"hpluv":[180.196137266844971,347.079488330816218,91.1973694573754869],"hsluv":[180.196137266844971,99.999999999989825,91.1973694573754869]},"#44ffff":{"lch":[91.6692750397398726,67.4158875874256,192.177050630061103],"luv":[91.6692750397398726,-65.8990611515587261,-14.2202545175369188],"rgb":[0.266666666666666663,1,1],"xyz":[0.561903555344106298,0.799652683338286541,1.07084435833608027],"hpluv":[192.177050630061103,369.886157390881351,91.6692750397398726],"hsluv":[192.177050630061103,99.9999999999897,91.6692750397398726]},"#33aa00":{"lch":[61.1785977172963129,90.1064171712311435,124.683940112874311],"luv":[61.1785977172963129,-51.2749716142469723,74.09482911373847],"rgb":[0.2,0.66666666666666663,0],"xyz":[0.157393059993970491,0.294521282349826385,0.0485535951906325494],"hpluv":[124.683940112874311,186.894073454811917,61.1785977172963129],"hsluv":[124.683940112874311,100.000000000002302,61.1785977172963129]},"#33aa11":{"lch":[61.2139288108167818,88.7849168066190089,125.171000261233829],"luv":[61.2139288108167818,-51.1417680861379793,72.5760360544852148],"rgb":[0.2,0.66666666666666663,0.0666666666666666657],"xyz":[0.158404725493607623,0.294925948549681238,0.0538817001553881791],"hpluv":[125.171000261233829,184.046797309440706,61.2139288108167818],"hsluv":[125.171000261233829,97.729263879491441,61.2139288108167818]},"#33aa22":{"lch":[61.2793378507832642,86.3832953662584657,126.10140218084841],"luv":[61.2793378507832642,-50.8984310656693708,69.7955831939783593],"rgb":[0.2,0.66666666666666663,0.133333333333333331],"xyz":[0.160280083632084625,0.295676091805072039,0.0637585863513673717],"hpluv":[126.10140218084841,178.877217305585333,61.2793378507832642],"hsluv":[126.10140218084841,93.5778090815338146,61.2793378507832642]},"#33aa33":{"lch":[61.3867923044640946,82.5646745763668548,127.715012949240119],"luv":[61.3867923044640946,-50.5076466968263134,65.3138814728533],"rgb":[0.2,0.66666666666666663,0.2],"xyz":[0.163367834364542386,0.296911192098055166,0.0800207402089785219],"hpluv":[127.715012949240119,170.670578118814461,61.3867923044640946],"hsluv":[127.715012949240119,86.9017438736093339,61.3867923044640946]},"#33aa44":{"lch":[61.5414071550205364,77.3485744588317488,130.237764523164799],"luv":[61.5414071550205364,-49.9641607354037305,59.0456146789935588],"rgb":[0.2,0.66666666666666663,0.266666666666666663],"xyz":[0.167825829608761246,0.298694390195742732,0.10349951516186516],"hpluv":[130.237764523164799,159.486606837004757,61.5414071550205364],"hsluv":[130.237764523164799,87.1702267355774723,61.5414071550205364]},"#33aa55":{"lch":[61.7472402279952775,70.9239260559490106,134.00596147156574],"luv":[61.7472402279952775,-49.2732069613111605,51.0132763399638876],"rgb":[0.2,0.66666666666666663,0.333333333333333315],"xyz":[0.173788243648123231,0.301079355811487592,0.134901562435839062],"hpluv":[134.00596147156574,145.752006815371971,61.7472402279952775],"hsluv":[134.00596147156574,87.512567621072165,61.7472402279952775]},"#33aa66":{"lch":[62.0075227235960824,63.6834089970413046,139.532932917076664],"luv":[62.0075227235960824,-48.4490087982300253,41.3312246123130436],"rgb":[0.2,0.66666666666666663,0.4],"xyz":[0.181373239421631416,0.304113354120890911,0.174849206842983024],"hpluv":[139.532932917076664,130.323054003441854,62.0075227235960824],"hsluv":[139.532932917076664,87.9225345315596769,62.0075227235960824]},"#33aa77":{"lch":[62.3247799262656201,56.2910074750267,147.571074265173024],"luv":[62.3247799262656201,-47.5128361285441727,30.1862207898839152],"rgb":[0.2,0.66666666666666663,0.466666666666666674],"xyz":[0.190687252441833321,0.307838959328971706,0.223903008749380933],"hpluv":[147.571074265173024,114.608702125460667,62.3247799262656201],"hsluv":[147.571074265173024,88.3905588997681519,62.3247799262656201]},"#33aa88":{"lch":[62.7009046876535052,49.786919253891476,159.033972270618222],"luv":[62.7009046876535052,-46.4906641824602147,17.8144736848221363],"rgb":[0.2,0.66666666666666663,0.533333333333333326],"xyz":[0.201827669543777177,0.312295126169749304,0.2825758721529531],"hpluv":[159.033972270618222,100.758286424528237,62.7009046876535052],"hsluv":[159.033972270618222,88.9048289963628804,62.7009046876535052]},"#33aa99":{"lch":[63.1372095297142835,45.6309193537352797,174.368717478757389],"luv":[63.1372095297142835,-45.4107033068770818,4.47759156711236095],"rgb":[0.2,0.66666666666666663,0.6],"xyz":[0.214884633652849877,0.317517911813378462,0.351342549794071035],"hpluv":[174.368717478757389,91.7092542399893631,63.1372095297142835],"hsluv":[174.368717478757389,89.4524406674979,63.1372095297142835]},"#33aaaa":{"lch":[63.6344696573538897,45.3208071547743288,192.17705063006116],"luv":[63.6344696573538897,-44.3011098571894,-9.5596666563997],"rgb":[0.2,0.66666666666666663,0.66666666666666663],"xyz":[0.229942326618102433,0.323540988999479584,0.430646399411069636],"hpluv":[192.17705063006116,90.3742140686623117,63.6344696573538897],"hsluv":[192.17705063006116,90.0204628815860559,63.6344696573538897]},"#33aabb":{"lch":[64.1929631055189844,49.4317459506583177,209.109442205543161],"luv":[64.1929631055189844,-43.188124160558381,-24.0475245965695663],"rgb":[0.2,0.66666666666666663,0.733333333333333282],"xyz":[0.247079918725439052,0.330396025842414343,0.520904384509711327],"hpluv":[209.109442205543161,97.7142389408223835,64.1929631055189844],"hsluv":[209.109442205543161,90.5968047396532228,64.1929631055189844]},"#33aacc":{"lch":[64.8125111239688181,57.2242456540803062,222.641155468482083],"luv":[64.8125111239688181,-42.0947673064025736,-38.7639633693247347],"rgb":[0.2,0.66666666666666663,0.8],"xyz":[0.266372294169160806,0.338112976019903144,0.622510895179981483],"hpluv":[222.641155468482083,112.036763534802631,64.8125111239688181],"hsluv":[222.641155468482083,91.1708232266795875,64.8125111239688181]},"#33aadd":{"lch":[65.4925201274692199,67.4454999587780151,232.519386199289158],"luv":[65.4925201274692199,-41.0401119544032653,-53.5220017886062465],"rgb":[0.2,0.66666666666666663,0.866666666666666696],"xyz":[0.28789061971979435,0.34672030624015665,0.73584074307998748],"hpluv":[232.519386199289158,130.67743538587149,65.4925201274692199],"hsluv":[232.519386199289158,91.7336648454097485,65.4925201274692199]},"#33aaee":{"lch":[66.23202547083838,79.0605595482254,239.573325902293959],"luv":[66.23202547083838,-40.039054494753934,-68.1721804788773],"rgb":[0.2,0.66666666666666663,0.933333333333333348],"xyz":[0.311702799985969059,0.356245178346626667,0.861251559148510704],"hpluv":[239.573325902293959,151.471586303299148,66.23202547083838],"hsluv":[239.573325902293959,92.278374899274425,66.23202547083838]},"#33aaff":{"lch":[67.0297366624436,91.3892467801412778,244.667711773110682],"luv":[67.0297366624436,-39.1024682991195291,-82.6014007142609898],"rgb":[0.2,0.66666666666666663,1],"xyz":[0.33787384839580048,0.366713597710559391,0.999085747440292549],"hpluv":[244.667711773110682,173.00828905748071,67.0297366624436],"hsluv":[244.667711773110682,99.9999999999982094,67.0297366624436]},"#33bb00":{"lch":[66.705199456007648,99.1588934495857757,125.274120260315158],"luv":[66.705199456007648,-57.2631629787864327,80.9531735993793262],"rgb":[0.2,0.733333333333333282,0],"xyz":[0.191347557902569271,0.362430278167024944,0.0598717611601651684],"hpluv":[125.274120260315158,188.630237299381349,66.705199456007648],"hsluv":[125.274120260315158,100.000000000002331,66.705199456007648]},"#33bb11":{"lch":[66.7359690986495764,97.9890755357957914,125.670572132139952],"luv":[66.7359690986495764,-57.1397853669544133,79.6046723036925385],"rgb":[0.2,0.733333333333333282,0.0666666666666666657],"xyz":[0.192359223402206403,0.362834944366879797,0.0651998661249208],"hpluv":[125.670572132139952,186.318944611331347,66.7359690986495764],"hsluv":[125.670572132139952,98.1524285745497451,66.7359690986495764]},"#33bb22":{"lch":[66.7929473545782,95.8553977164860243,126.423299967719473],"luv":[66.7929473545782,-56.9137739802675,77.1302768224563],"rgb":[0.2,0.733333333333333282,0.133333333333333331],"xyz":[0.194234581540683404,0.363585087622270597,0.0750767523209],"hpluv":[126.423299967719473,182.106434833594022,66.7929473545782],"hsluv":[126.423299967719473,94.7659081903439073,66.7929473545782]},"#33bb33":{"lch":[66.8865907457163,92.4406335233364302,127.715012949240233],"luv":[66.8865907457163,-56.5491099236273058,73.126389853740946],"rgb":[0.2,0.733333333333333282,0.2],"xyz":[0.197322332273141166,0.364820187915253724,0.0913389061785111478],"hpluv":[127.715012949240233,175.373180985258983,66.8865907457163],"hsluv":[127.715012949240233,89.2962069049265637,66.8865907457163]},"#33bb44":{"lch":[67.0214179407225572,87.725631811763563,129.70178400392939],"luv":[67.0214179407225572,-56.0384119529137763,67.4943172612971409],"rgb":[0.2,0.733333333333333282,0.266666666666666663],"xyz":[0.201780327517360025,0.36660338601294129,0.114817681131397786],"hpluv":[129.70178400392939,166.093340387543549,67.0214179407225572],"hsluv":[129.70178400392939,89.4761749642590871,67.0214179407225572]},"#33bb55":{"lch":[67.2010629421400552,81.8171313208689099,132.60288320444127],"luv":[67.2010629421400552,-55.3830806540106479,60.2225651632978796],"rgb":[0.2,0.733333333333333282,0.333333333333333315],"xyz":[0.207742741556722,0.368988351628686151,0.146219728405371674],"hpluv":[132.60288320444127,154.492511096020365,67.2010629421400552],"hsluv":[132.60288320444127,89.7076242739581,67.2010629421400552]},"#33bb66":{"lch":[67.4284803792762091,74.9653268862145,136.738510871982356],"luv":[67.4284803792762091,-54.5922668909562248,51.3759148907695788],"rgb":[0.2,0.733333333333333282,0.4],"xyz":[0.215327737330230196,0.372022349938089469,0.186167372812515663],"hpluv":[136.738510871982356,141.07705768332076,67.4284803792762091],"hsluv":[136.738510871982356,89.9877463438330096,67.4284803792762091]},"#33bb77":{"lch":[67.7060530794905446,67.5982244112705,142.572908280802977],"luv":[67.7060530794905446,-53.681598162732449,41.0829156980295878],"rgb":[0.2,0.733333333333333282,0.466666666666666674],"xyz":[0.224641750350432101,0.375747955146170265,0.235221174718913573],"hpluv":[142.572908280802977,126.69139486293237,67.7060530794905446],"hsluv":[142.572908280802977,90.3115397167098,67.7060530794905446]},"#33bb88":{"lch":[68.0356563096068641,60.380187227113133,150.730801091974854],"luv":[68.0356563096068641,-52.67158330257773,29.5206931148449],"rgb":[0.2,0.733333333333333282,0.533333333333333326],"xyz":[0.235782167452375957,0.380204121986947863,0.293894038122485712],"hpluv":[150.730801091974854,112.615249481580875,68.0356563096068641],"hsluv":[150.730801091974854,90.6723452470342437,68.0356563096068641]},"#33bb99":{"lch":[68.4187011865960244,54.2829584497859159,161.862695711717],"luv":[68.4187011865960244,-51.585814888834193,16.8980259295610082],"rgb":[0.2,0.733333333333333282,0.6],"xyz":[0.248839131561448657,0.385426907630577,0.362660715763603647],"hpluv":[161.862695711717,100.676477021468287,68.4187011865960244],"hsluv":[161.862695711717,91.0624469223214,68.4187011865960244]},"#33bbaa":{"lch":[68.8561680799326439,50.5661966451060749,176.101011456941364],"luv":[68.8561680799326439,-50.4491601398581224,3.43838397135738649],"rgb":[0.2,0.733333333333333282,0.66666666666666663],"xyz":[0.263896824526701212,0.391449984816678143,0.441964565380602248],"hpluv":[176.101011456941364,93.1873079321678404,68.8561680799326439],"hsluv":[176.101011456941364,91.473675634614068,68.8561680799326439]},"#33bbbb":{"lch":[69.3486356756669835,50.4205674478029,192.177050630061103],"luv":[69.3486356756669835,-49.286127891295429,-10.635375839212843],"rgb":[0.2,0.733333333333333282,0.733333333333333282],"xyz":[0.281034416634037831,0.398305021659612901,0.532222550479243939],"hpluv":[192.177050630061103,92.2590830970113132,69.3486356756669835],"hsluv":[192.177050630061103,91.8979539795913922,69.3486356756669835]},"#33bbcc":{"lch":[69.8963087653012423,54.2786660468549442,207.559867588447844],"luv":[69.8963087653012423,-48.1195504219082224,-25.1133919457216273],"rgb":[0.2,0.733333333333333282,0.8],"xyz":[0.300326792077759586,0.406021971837101703,0.633829061149514095],"hpluv":[207.559867588447844,98.540384199822455,69.8963087653012423],"hsluv":[207.559867588447844,92.3277362004695306,69.8963087653012423]},"#33bbdd":{"lch":[70.4990463576295241,61.569428837921933,220.28187220447964],"luv":[70.4990463576295241,-46.9696505240293618,-39.807618580850594],"rgb":[0.2,0.733333333333333282,0.866666666666666696],"xyz":[0.321845117628393129,0.414629302057355209,0.747158909049520092],"hpluv":[220.28187220447964,110.820781636478685,70.4990463576295241],"hsluv":[220.28187220447964,92.7563200135089119,70.4990463576295241]},"#33bbee":{"lch":[71.1563908243766576,71.2672074810155181,229.953995552192254],"luv":[71.1563908243766576,-45.8534981630350487,-54.557048750410388],"rgb":[0.2,0.733333333333333282,0.933333333333333348],"xyz":[0.345657297894567894,0.424154174163825226,0.872569725118043316],"hpluv":[229.953995552192254,127.091105244163856,71.1563908243766576],"hsluv":[229.953995552192254,93.1780289437208893,71.1563908243766576]},"#33bbff":{"lch":[71.8675982303626597,82.4526478797043296,237.101125866277243],"luv":[71.8675982303626597,-44.7848112977892,-69.2297610814592161],"rgb":[0.2,0.733333333333333282,1],"xyz":[0.37182834630439926,0.43462259352775795,1.01040391340982527],"hpluv":[237.101125866277243,145.583046200088774,71.8675982303626597],"hsluv":[237.101125866277243,99.9999999999977831,71.8675982303626597]},"#33cc00":{"lch":[72.1534232831706532,108.011475964841438,125.713046635977918],"luv":[72.1534232831706532,-63.0491190384013507,87.6999859098336287],"rgb":[0.2,0.8,0],"xyz":[0.229571301212186191,0.438877764786259839,0.0726130089300371234],"hpluv":[125.713046635977918,189.955680955455,72.1534232831706532],"hsluv":[125.713046635977918,100.000000000002402,72.1534232831706532]},"#33cc11":{"lch":[72.1805088449519,106.967132064598133,126.040754358939225],"luv":[72.1805088449519,-62.9352412435677095,86.493483868665578],"rgb":[0.2,0.8,0.0666666666666666657],"xyz":[0.230582966711823323,0.439282430986114691,0.0779411138947927601],"hpluv":[126.040754358939225,188.048441784684599,72.1805088449519],"hsluv":[126.040754358939225,98.4728126855506645,72.1805088449519]},"#33cc22":{"lch":[72.2306742907645543,105.057034533260762,126.660198785960176],"luv":[72.2306742907645543,-62.7261976516662614,84.2757653960309199],"rgb":[0.2,0.8,0.133333333333333331],"xyz":[0.232458324850300324,0.440032574241505492,0.0878180000907719527],"hpluv":[126.660198785960176,184.562215611711281,72.2306742907645543],"hsluv":[126.660198785960176,95.6680740449206866,72.2306742907645543]},"#33cc33":{"lch":[72.31314692234902,101.984984863208481,127.715012949240304],"luv":[72.31314692234902,-62.3877173898115416,80.6765756365369668],"rgb":[0.2,0.8,0.2],"xyz":[0.235546075582758085,0.441267674534488619,0.104080153948383103],"hpluv":[127.715012949240304,178.960959976488198,72.31314692234902],"hsluv":[127.715012949240304,91.1230258822071306,72.31314692234902]},"#33cc44":{"lch":[72.4319472107582669,97.7091257880760651,129.318260791805528],"luv":[72.4319472107582669,-61.9111863445413135,75.5915224590797123],"rgb":[0.2,0.8,0.266666666666666663],"xyz":[0.240004070826976945,0.443050872632176185,0.127558928901269741],"hpluv":[129.318260791805528,171.176559629977817,72.4319472107582669],"hsluv":[129.318260791805528,91.2471619418673612,72.4319472107582669]},"#33cc55":{"lch":[72.590341240881628,92.2844112105744756,131.621023493746776],"luv":[72.590341240881628,-61.2953573189119396,68.9876200751215407],"rgb":[0.2,0.8,0.333333333333333315],"xyz":[0.24596648486633893,0.445435838247921045,0.158960976175243629],"hpluv":[131.621023493746776,161.32023063104279,72.590341240881628],"hsluv":[131.621023493746776,91.4078622695478,72.590341240881628]},"#33cc66":{"lch":[72.7910248315973405,85.8719567505974197,134.835055825888389],"luv":[72.7910248315973405,-60.5455878238572112,60.8951948123993319],"rgb":[0.2,0.8,0.4],"xyz":[0.253551480639847115,0.448469836557324364,0.198908620582387619],"hpluv":[134.835055825888389,149.696915507144809,72.7910248315973405],"hsluv":[134.835055825888389,91.6039613293452533,72.7910248315973405]},"#33cc77":{"lch":[73.0362204241858421,78.757716060864837,139.259917231121676],"luv":[73.0362204241858421,-59.6729854923348,51.3995393126768079],"rgb":[0.2,0.8,0.466666666666666674],"xyz":[0.262865493660049,0.452195441765405159,0.247962422488785528],"hpluv":[139.259917231121676,136.834039856532058,73.0362204241858421],"hsluv":[139.259917231121676,91.8328510325735294,73.0362204241858421]},"#33cc88":{"lch":[73.3277345291399,71.3846311778678597,145.306874573990314],"luv":[73.3277345291399,-58.6933246064594201,40.6307668527313197],"rgb":[0.2,0.8,0.533333333333333326],"xyz":[0.274005910761992877,0.456651608606182757,0.306635285892357667],"hpluv":[145.306874573990314,123.530949311180805,73.3277345291399],"hsluv":[145.306874573990314,92.0907512285691894,73.3277345291399]},"#33cc99":{"lch":[73.6669954969027714,64.400374936046,153.483372150156],"luv":[73.6669954969027714,-57.6257667092137709,28.7520312861295437],"rgb":[0.2,0.8,0.6],"xyz":[0.287062874871065576,0.461874394249811915,0.375401963533475602],"hpluv":[153.483372150156,110.931469165383049,73.6669954969027714],"hsluv":[153.483372150156,92.3730273510429072,73.6669954969027714]},"#33ccaa":{"lch":[74.0550811623464114,58.6991946004706691,164.236139418897579],"luv":[74.0550811623464114,-56.4914910882685106,15.9470022690160835],"rgb":[0.2,0.8,0.66666666666666663],"xyz":[0.302120567836318132,0.467897471435913037,0.454705813150474203],"hpluv":[164.236139418897579,100.581152280716978,74.0550811623464114],"hsluv":[164.236139418897579,92.6745296641919794,74.0550811623464114]},"#33ccbb":{"lch":[74.4927414449451106,55.3647402049724846,177.507530206592946],"luv":[74.4927414449451106,-55.3123621552270137,2.40770653799565881],"rgb":[0.2,0.8,0.733333333333333282],"xyz":[0.319258159943654751,0.474752508278847796,0.544963798249115894],"hpluv":[177.507530206592946,94.310193436061823,74.4927414449451106],"hsluv":[177.507530206592946,92.9899230693886,74.4927414449451106]},"#33cccc":{"lch":[74.9804187561532416,55.3552144916165361,192.177050630061132],"luv":[74.9804187561532416,-54.1097476482840918,-11.6762571422475752],"rgb":[0.2,0.8,0.8],"xyz":[0.338550535387376506,0.482469458456336597,0.646570308919386],"hpluv":[192.177050630061132,93.6806731785530928,74.9804187561532416],"hsluv":[192.177050630061132,93.3139795405001422,74.9804187561532416]},"#33ccdd":{"lch":[75.5182678303382602,59.0031963362726231,206.282454691493683],"luv":[75.5182678303382602,-52.9035678654544128,-26.1264173778571198],"rgb":[0.2,0.8,0.866666666666666696],"xyz":[0.360068860938010049,0.491076788676590104,0.759900156819392],"hpluv":[206.282454691493683,99.1431801932779564,75.5182678303382602],"hsluv":[206.282454691493683,93.6418135046530296,75.5182678303382602]},"#33ccee":{"lch":[76.106175853756767,65.8577115847402439,218.26062431545347],"luv":[76.106175853756767,-51.7116147040808585,-40.7817003063324606],"rgb":[0.2,0.8,0.933333333333333348],"xyz":[0.383881041204184759,0.500601660783060121,0.885310972887915271],"hpluv":[218.26062431545347,110.229350215344013,76.106175853756767],"hsluv":[218.26062431545347,93.969050662624,76.106175853756767]},"#33ccff":{"lch":[76.7437832939395435,75.0714180144803,227.674056146546604],"luv":[76.7437832939395435,-50.5491406549608,-55.5022718611574248],"rgb":[0.2,0.8,1],"xyz":[0.41005208961401618,0.511070080146992844,1.02314516117969712],"hpluv":[227.674056146546604,129.845560156838474,76.7437832939395435],"hsluv":[227.674056146546604,99.9999999999969731,76.7437832939395435]},"#33dd00":{"lch":[77.5280782787270653,116.686614644285086,126.047543424376144],"luv":[77.5280782787270653,-68.6649809221669187,94.3445092843369508],"rgb":[0.2,0.866666666666666696,0],"xyz":[0.272205291759361367,0.524145745880611358,0.0868243391124284419],"hpluv":[126.047543424376144,210.356208283509261,77.5280782787270653],"hsluv":[126.047543424376144,100.000000000002416,77.5280782787270653]},"#33dd11":{"lch":[77.5521415069474926,115.747372453695789,126.322097671746874],"luv":[77.5521415069474926,-68.5599423432795,93.2576459912042],"rgb":[0.2,0.866666666666666696,0.0666666666666666657],"xyz":[0.273216957258998472,0.524550412080466266,0.0921524440771840786],"hpluv":[126.322097671746874,208.932074165120611,77.5521415069474926],"hsluv":[126.322097671746874,98.7203227917640902,77.5521415069474926]},"#33dd22":{"lch":[77.5967156031793337,114.025807364398673,126.839335004613],"luv":[77.5967156031793337,-68.36681588768,91.2571270119929778],"rgb":[0.2,0.866666666666666696,0.133333333333333331],"xyz":[0.275092315397475529,0.525300555335857067,0.102029330273163271],"hpluv":[126.839335004613,206.31706664059891,77.5967156031793337],"hsluv":[126.839335004613,96.3665979525037102,77.5967156031793337]},"#33dd33":{"lch":[77.670013861504259,111.246421345384377,127.715012949240332],"luv":[77.670013861504259,-68.0532561222895,88.0029578668175247],"rgb":[0.2,0.866666666666666696,0.2],"xyz":[0.278180066129933234,0.526535655628840193,0.118291484130774421],"hpluv":[127.715012949240332,202.082466523340599,77.670013861504259],"hsluv":[127.715012949240332,92.5426273046004439,77.670013861504259]},"#33dd44":{"lch":[77.7756375941771267,107.354265763003397,129.034195217585733],"luv":[77.7756375941771267,-67.6100090567609726,83.3895979895470134],"rgb":[0.2,0.866666666666666696,0.266666666666666663],"xyz":[0.28263806137415215,0.52831885372652776,0.14177025908366106],"hpluv":[129.034195217585733,196.125875311323085,77.7756375941771267],"hsluv":[129.034195217585733,92.6304308505642098,77.7756375941771267]},"#33dd55":{"lch":[77.9165348137244,102.370875286307594,130.905664069221132],"luv":[77.9165348137244,-67.0340391276217105,77.3707548439410431],"rgb":[0.2,0.866666666666666696,0.333333333333333315],"xyz":[0.288600475413514135,0.530703819342272509,0.173172306357634975],"hpluv":[130.905664069221132,188.454179514464698,77.9165348137244],"hsluv":[130.905664069221132,92.7446831943234287,77.9165348137244]},"#33dd66":{"lch":[78.095166356878579,96.3992072274516261,133.476315028685519],"luv":[78.095166356878579,-66.3279238591037767,69.9529389705828777],"rgb":[0.2,0.866666666666666696,0.4],"xyz":[0.29618547118702232,0.533737817651675828,0.213119950764778909],"hpluv":[133.476315028685519,179.196222734944627,78.095166356878579],"hsluv":[133.476315028685519,92.8850067954864187,78.095166356878579]},"#33dd77":{"lch":[78.3135937781308513,89.6343875602499196,136.948304354896891],"luv":[78.3135937781308513,-65.499258897229538,61.1896275296294192],"rgb":[0.2,0.866666666666666696,0.466666666666666674],"xyz":[0.305499484207224226,0.537463422859756679,0.262173752671176818],"hpluv":[136.948304354896891,168.630388284117117,78.3135937781308513],"hsluv":[136.948304354896891,93.050064801238122,78.3135937781308513]},"#33dd88":{"lch":[78.5735314106716487,82.3820261839553609,141.59738099104726],"luv":[78.5735314106716487,-64.5599157920348432,51.1743638074698],"rgb":[0.2,0.866666666666666696,0.533333333333333326],"xyz":[0.316639901309168,0.541919589700534221,0.320846616074749],"hpluv":[141.59738099104726,157.233383580683977,78.5735314106716487],"hsluv":[141.59738099104726,93.2377028740416449,78.5735314106716487]},"#33dd99":{"lch":[78.8763801167060592,75.0869406053615762,147.781507566587862],"luv":[78.8763801167060592,-63.5251386535812799,40.0325543591259105],"rgb":[0.2,0.866666666666666696,0.6],"xyz":[0.329696865418240725,0.547142375344163434,0.389613293715866893],"hpluv":[147.781507566587862,145.760006886720333,78.8763801167060592],"hsluv":[147.781507566587862,93.4451205095912343,78.8763801167060592]},"#33ddaa":{"lch":[79.2232512041385633,68.3697583649377094,155.904677195463563],"luv":[79.2232512041385633,-62.4125308872686801,27.9123601138582131],"rgb":[0.2,0.866666666666666696,0.66666666666666663],"xyz":[0.344754558383493281,0.553165452530264501,0.468917143332865494],"hpluv":[155.904677195463563,135.355769729163313,79.2232512041385633],"hsluv":[155.904677195463563,93.6690625743759,79.2232512041385633]},"#33ddbb":{"lch":[79.6149850527770866,63.045401416642612,166.259028854183981],"luv":[79.6149850527770866,-61.2410113290283604,14.9753521221847095],"rgb":[0.2,0.866666666666666696,0.733333333333333282],"xyz":[0.361892150490829956,0.560020489373199259,0.559175128431507296],"hpluv":[166.259028854183981,127.658944712944631,79.6149850527770866],"hsluv":[166.259028854183981,93.9060163408359614,79.6149850527770866]},"#33ddcc":{"lch":[80.0521670478692613,60.0458591823856054,178.675787060338223],"luv":[80.0521670478692613,-60.0298228841068138,1.3876488942262355],"rgb":[0.2,0.866666666666666696,0.8],"xyz":[0.381184525934551655,0.567737439550688117,0.660781639101777452],"hpluv":[178.675787060338223,124.734340702728062,80.0521670478692613],"hsluv":[178.675787060338223,94.1523985056595905,80.0521670478692613]},"#33dddd":{"lch":[80.5351423549551555,60.1510343961145963,192.177050630061245],"luv":[80.5351423549551555,-58.7976638126828,-12.6878551809710149],"rgb":[0.2,0.866666666666666696,0.866666666666666696],"xyz":[0.402702851485185254,0.576344769770941623,0.774111487001783449],"hpluv":[192.177050630061245,128.603021497070955,80.5351423549551555],"hsluv":[192.177050630061245,94.4047190696773271,80.5351423549551555]},"#33ddee":{"lch":[81.0640304387754,63.6213092914002303,205.209077734070348],"luv":[81.0640304387754,-57.5619892131040913,-27.0977562499646396],"rgb":[0.2,0.866666666666666696,0.933333333333333348],"xyz":[0.426515031751359963,0.58586964187741164,0.899522303070306672],"hpluv":[205.209077734070348,140.476637056352985,81.0640304387754],"hsluv":[205.209077734070348,94.6597131799347835,81.0640304387754]},"#33ddff":{"lch":[81.6387398294900208,70.0938175080633528,216.50946924270437],"luv":[81.6387398294900208,-56.3385046754299452,-41.7027114680837769],"rgb":[0.2,0.866666666666666696,1],"xyz":[0.452686080161191384,0.596338061241344364,1.03735649136208852],"hpluv":[216.50946924270437,160.421433033358312,81.6387398294900208],"hsluv":[216.50946924270437,99.999999999996,81.6387398294900208]},"#33ee00":{"lch":[82.833762600699373,125.203353646442437,126.3077634478595],"luv":[82.833762600699373,-74.1357070958390239,100.894879442497455],"rgb":[0.2,0.933333333333333348,0],"xyz":[0.319384068099876184,0.618503298561642323,0.102550597892599626],"hpluv":[126.3077634478595,309.713105305240845,82.833762600699373],"hsluv":[126.3077634478595,100.000000000002302,82.833762600699373]},"#33ee11":{"lch":[82.85531245284119,124.353122892884315,126.540515263097504],"luv":[82.85531245284119,-74.0387382831010541,99.9097813362598544],"rgb":[0.2,0.933333333333333348,0.0666666666666666657],"xyz":[0.320395733599513288,0.618907964761497231,0.107878702857355263],"hpluv":[126.540515263097504,308.054239915702169,82.85531245284119],"hsluv":[126.540515263097504,98.9149262576223833,82.85531245284119]},"#33ee22":{"lch":[82.8952353009988,122.792060636983948,126.977870705292972],"luv":[82.8952353009988,-73.860225327277135,98.0946342573363808],"rgb":[0.2,0.933333333333333348,0.133333333333333331],"xyz":[0.322271091737990345,0.619658108016888,0.117755589053334456],"hpluv":[126.977870705292972,305.002845011722627,82.8952353009988],"hsluv":[126.977870705292972,96.9167682151396264,82.8952353009988]},"#33ee33":{"lch":[82.9608975691188846,120.264250910780831,127.715012949240375],"luv":[82.9608975691188846,-73.5697721383487391,95.1366316128687259],"rgb":[0.2,0.933333333333333348,0.2],"xyz":[0.325358842470448051,0.620893208309871159,0.134017742910945592],"hpluv":[127.715012949240375,300.046240285686,82.9608975691188846],"hsluv":[127.715012949240375,93.6639064416312834,82.9608975691188846]},"#33ee44":{"lch":[83.0555452014946241,116.707569363843,128.817932535493583],"luv":[83.0555452014946241,-73.1578713419028,90.9317469728687513],"rgb":[0.2,0.933333333333333348,0.266666666666666663],"xyz":[0.329816837714666966,0.622676406407558725,0.157496517863832231],"hpluv":[128.817932535493583,293.039720989359807,83.0555452014946241],"hsluv":[128.817932535493583,93.7274034182796356,83.0555452014946241]},"#33ee55":{"lch":[83.1818510898783643,112.121671621867506,130.367812557605703],"luv":[83.1818510898783643,-72.6203079673681202,85.4257579305358092],"rgb":[0.2,0.933333333333333348,0.333333333333333315],"xyz":[0.335779251754028951,0.625061372023303474,0.188898565137806146],"hpluv":[130.367812557605703,283.950089878719211,83.1818510898783643],"hsluv":[130.367812557605703,93.8103653792530139,83.1818510898783643]},"#33ee66":{"lch":[83.3420657578551,106.5703857928128,132.470647679843353],"luv":[83.3420657578551,-71.9576476395269,78.6088040502757224],"rgb":[0.2,0.933333333333333348,0.4],"xyz":[0.343364247527537136,0.628095370332706793,0.228846209544950108],"hpluv":[132.470647679843353,272.865433725808,83.3420657578551],"hsluv":[132.470647679843353,93.912785079345241,83.3420657578551]},"#33ee77":{"lch":[83.5380975272703523,100.188150337510052,135.268396470867572],"luv":[83.5380975272703523,-71.1748024762597851,70.5110839550557387],"rgb":[0.2,0.933333333333333348,0.466666666666666674],"xyz":[0.352678260547739042,0.631820975540787644,0.277900011451348],"hpluv":[135.268396470867572,260.019238129472967,83.5380975272703523],"hsluv":[135.268396470867572,94.0340074276028304,83.5380975272703523]},"#33ee88":{"lch":[83.7715600973053682,93.1909370684561651,138.951686086792108],"luv":[83.7715600973053682,-70.2805135444840801,61.1980405537673],"rgb":[0.2,0.933333333333333348,0.533333333333333326],"xyz":[0.363818677649682842,0.636277142381565186,0.336572874854920157],"hpluv":[138.951686086792108,245.83342468256393,83.7715600973053682],"hsluv":[138.951686086792108,94.1728069818675237,83.7715600973053682]},"#33ee99":{"lch":[84.0438031984902807,85.8935006436990278,143.770719305914184],"luv":[84.0438031984902807,-69.2867121669879822,50.7646035138469784],"rgb":[0.2,0.933333333333333348,0.6],"xyz":[0.376875641758755542,0.641499928025194399,0.405339552496038091],"hpluv":[143.770719305914184,230.989810482694878,84.0438031984902807],"hsluv":[143.770719305914184,94.3274826341768886,84.0438031984902807]},"#33eeaa":{"lch":[84.3559338989094698,78.7339796376561907,150.032255922422308],"luv":[84.3559338989094698,-68.2077782157370223,39.328596980513943],"rgb":[0.2,0.933333333333333348,0.66666666666666663],"xyz":[0.391933334724008098,0.647523005211295466,0.484643402113036692],"hpluv":[150.032255922422308,216.540119706931335,84.3559338989094698],"hsluv":[150.032255922422308,94.495967075681591,84.3559338989094698]},"#33eebb":{"lch":[84.7088326356722,72.2999891840671,158.051587623557367],"luv":[84.7088326356722,-67.0597412553262,27.0236847743774931],"rgb":[0.2,0.933333333333333348,0.733333333333333282],"xyz":[0.409070926831344772,0.654378042054230225,0.574901387211678383],"hpluv":[158.051587623557367,204.04938686040532,84.7088326356722],"hsluv":[158.051587623557367,94.6759444498571838,84.7088326356722]},"#33eecc":{"lch":[85.1031663228954,67.3294619946068451,168.005441410177127],"luv":[85.1031663228954,-65.8594808644086,13.992327638881461],"rgb":[0.2,0.933333333333333348,0.8],"xyz":[0.428363302275066471,0.662094992231719082,0.676507897881948539],"hpluv":[168.005441410177127,195.708498626929867,85.1031663228954],"hsluv":[168.005441410177127,94.8649680125767389,85.1031663228954]},"#33eedd":{"lch":[85.539399954776755,64.6250931348259314,179.663789762776076],"luv":[85.539399954776755,-64.6239805156479861,0.37921629457989664],"rgb":[0.2,0.933333333333333348,0.866666666666666696],"xyz":[0.44988162782570007,0.670702322451972588,0.789837745781954537],"hpluv":[179.663789762776076,194.233009793031641,85.539399954776755],"hsluv":[179.663789762776076,95.0605698730458926,85.539399954776755]},"#33eeee":{"lch":[86.0178075751720286,64.8282855412949601,192.177050630061217],"luv":[86.0178075751720286,-63.3696789602654533,-13.6744431219908602],"rgb":[0.2,0.933333333333333348,0.933333333333333348],"xyz":[0.47369380809187478,0.680227194558442605,0.91524856185047776],"hpluv":[192.177050630061217,202.327515221469156,86.0178075751720286],"hsluv":[192.177050630061217,95.2603564020327127,86.0178075751720286]},"#33eeff":{"lch":[86.538483142230433,68.1460620684978124,204.293044736593487],"luv":[86.538483142230433,-62.1119480142598803,-28.035543321245509],"rgb":[0.2,0.933333333333333348,1],"xyz":[0.499864856501706201,0.690695613922375329,1.05308275014225972],"hpluv":[204.293044736593487,221.878852364873978,86.538483142230433],"hsluv":[204.293044736593487,99.999999999993932,86.538483142230433]},"#33ff00":{"lch":[88.074762753062231,133.577745567808222,126.513803819973305],"luv":[88.074762753062231,-79.4809541468946,107.358241597361044],"rgb":[0.2,1,0],"xyz":[0.37123644052955,0.72220804342099143,0.119834722035823737],"hpluv":[126.513803819973305,497.272976699974663,88.074762753062231],"hsluv":[126.513803819973305,100.00000000000226,88.074762753062231]},"#33ff11":{"lch":[88.0941974462199,132.80363803677966,126.71317891025123],"luv":[88.0941974462199,-79.3912833442141448,106.460464045403725],"rgb":[0.2,1,0.0666666666666666657],"xyz":[0.37224810602918712,0.722612709620846339,0.125162827000579374],"hpluv":[126.71317891025123,495.277769978645495,88.0941974462199],"hsluv":[126.71317891025123,99.9999999999917861,88.0941974462199]},"#33ff22":{"lch":[88.1302050034733355,131.38040615815558,127.087058789859782],"luv":[88.1302050034733355,-79.2260405323659285,104.804797713872986],"rgb":[0.2,1,0.133333333333333331],"xyz":[0.374123464167664177,0.723362852876237139,0.135039713196558553],"hpluv":[127.087058789859782,491.602558840386564,88.1302050034733355],"hsluv":[127.087058789859782,99.9999999999915161,88.1302050034733355]},"#33ff33":{"lch":[88.1894367416410745,129.070276381710187,127.715012949240347],"luv":[88.1894367416410745,-78.9567203165017162,102.10275491931047],"rgb":[0.2,1,0.2],"xyz":[0.377211214900121883,0.724597953169220266,0.151301867054169703],"hpluv":[127.715012949240347,485.618062737129037,88.1894367416410745],"hsluv":[127.715012949240347,99.9999999999917577,88.1894367416410745]},"#33ff44":{"lch":[88.2748349985884,125.807640063387211,128.649544439126117],"luv":[88.2748349985884,-78.5738101086462137,98.2533391968394909],"rgb":[0.2,1,0.266666666666666663],"xyz":[0.381669210144340798,0.726381151266907832,0.174780642007056342],"hpluv":[128.649544439126117,477.126267833886629,88.2748349985884],"hsluv":[128.649544439126117,99.9999999999917151,88.2748349985884]},"#33ff55":{"lch":[88.3888340150102181,121.57774004888698,129.95306271607248],"luv":[88.3888340150102181,-78.0723424240601,93.1979410921456122],"rgb":[0.2,1,0.333333333333333315],"xyz":[0.387631624183702783,0.728766116882652581,0.206182689281030257],"hpluv":[129.95306271607248,466.04908765344419,88.3888340150102181],"hsluv":[129.95306271607248,99.999999999991644,88.3888340150102181]},"#33ff66":{"lch":[88.5334972733264,116.417510431790106,131.704600668064415],"luv":[88.5334972733264,-77.4514410423726929,86.9155395518881],"rgb":[0.2,1,0.4],"xyz":[0.395216619957210968,0.7318001151920559,0.246130333688174219],"hpluv":[131.704600668064415,452.43688525451455,88.5334972733264],"hsluv":[131.704600668064415,99.9999999999913882,88.5334972733264]},"#33ff77":{"lch":[88.7105909437740081,110.419515958012653,134.007319384137503],"luv":[88.7105909437740081,-76.7139872603015505,79.419353202025647],"rgb":[0.2,1,0.466666666666666674],"xyz":[0.404530632977412874,0.735525720400136751,0.295184135594572128],"hpluv":[134.007319384137503,436.493157669178117,88.7105909437740081],"hsluv":[134.007319384137503,99.9999999999915,88.7105909437740081]},"#33ff88":{"lch":[88.9216276204312379,103.738746998712813,136.99719245927011],"luv":[88.9216276204312379,-75.8662497359534882,70.7533729221786558],"rgb":[0.2,1,0.533333333333333326],"xyz":[0.415671050079356674,0.739981887240914293,0.353856998998144268],"hpluv":[136.99719245927011,418.619650658891032,88.9216276204312379],"hsluv":[136.99719245927011,99.9999999999912603,88.9216276204312379]},"#33ff99":{"lch":[89.1678944508512359,96.6032590942606788,140.85189111032139],"luv":[89.1678944508512359,-74.9174292298428242,60.9882649796200695],"rgb":[0.2,1,0.6],"xyz":[0.428728014188429429,0.745204672884543506,0.422623676639262202],"hpluv":[140.85189111032139,399.492483487252343,89.1678944508512359],"hsluv":[140.85189111032139,99.9999999999913456,89.1678944508512359]},"#33ffaa":{"lch":[89.4504724790268426,89.3298939826383105,145.795546705853383],"luv":[89.4504724790268426,-73.8791171714821,50.2165909327964926],"rgb":[0.2,1,0.66666666666666663],"xyz":[0.44378570715368193,0.751227750070644573,0.501927526256260803],"hpluv":[145.795546705853383,380.184928602968114,89.4504724790268426],"hsluv":[145.795546705853383,99.9999999999912,89.4504724790268426]},"#33ffbb":{"lch":[89.7702508712150262,82.3444719603705,152.087330110215447],"luv":[89.7702508712150262,-72.7646918614874494,38.5475249625060314],"rgb":[0.2,1,0.733333333333333282],"xyz":[0.460923299261018604,0.758082786913579332,0.592185511354902605],"hpluv":[152.087330110215447,362.351543222333419,89.7702508712150262],"hsluv":[152.087330110215447,99.9999999999909761,89.7702508712150262]},"#33ffcc":{"lch":[90.127938152783571,76.1985663634222,159.968053619612647],"luv":[90.127938152783571,-71.5886883295869723,26.1013643147272063],"rgb":[0.2,1,0.8],"xyz":[0.480215674704740358,0.765799737091068189,0.693792022025172761],"hpluv":[159.968053619612647,348.464440630653087,90.127938152783571],"hsluv":[159.968053619612647,99.999999999990834,90.127938152783571]},"#33ffdd":{"lch":[90.5240717550146314,71.5577226375514357,169.529475161807255],"luv":[90.5240717550146314,-70.3661811113599924,13.0041618290463337],"rgb":[0.2,1,0.866666666666666696],"xyz":[0.501734000255373846,0.774407067311321695,0.807121869925178759],"hpluv":[169.529475161807255,342.013155745428833,90.5240717550146314],"hsluv":[169.529475161807255,99.9999999999904077,90.5240717550146314]},"#33ffee":{"lch":[90.9590266887802,69.1149718308150511,180.51167449693034],"luv":[90.9590266887802,-69.1122158161064561,-0.61721646306864264],"rgb":[0.2,1,0.933333333333333348],"xyz":[0.525546180521548667,0.783931939417791712,0.932532685993702],"hpluv":[180.51167449693034,347.442342228089899,90.9590266887802],"hsluv":[180.51167449693034,99.9999999999901519,90.9590266887802]},"#33ffff":{"lch":[91.4330238629877243,69.4028497051403122,192.177050630061132],"luv":[91.4330238629877243,-67.8413175363212702,-14.6393709608822622],"rgb":[0.2,1,1],"xyz":[0.55171722893138,0.794400358781724436,1.07036687428548372],"hpluv":[192.177050630061132,369.590917988860895,91.4330238629877243],"hsluv":[192.177050630061132,99.9999999999897256,91.4330238629877243]},"#22aa00":{"lch":[60.8595101229647923,91.9296409673314656,126.268252023172565],"luv":[60.8595101229647923,-54.3824975763784053,74.1188427172042594],"rgb":[0.133333333333333331,0.66666666666666663,0],"xyz":[0.150337683054585614,0.290883353615456,0.0482228743965988915],"hpluv":[126.268252023172565,191.675426474830772,60.8595101229647923],"hsluv":[126.268252023172565,100.000000000002288,60.8595101229647923]},"#22aa11":{"lch":[60.8951349805759605,90.6090655032342482,126.765726538166419],"luv":[60.8951349805759605,-54.2335583573106,72.5859759132132893],"rgb":[0.133333333333333331,0.66666666666666663,0.0666666666666666657],"xyz":[0.151349348554222746,0.291288019815310828,0.0535509793613545212],"hpluv":[126.765726538166419,188.811473003651798,60.8951349805759605],"hsluv":[126.765726538166419,97.6988909499644365,60.8951349805759605]},"#22aa22":{"lch":[60.9610867967894592,88.21068058626,127.715012949240347],"luv":[60.9610867967894592,-53.9615024560723953,69.7802294505720226],"rgb":[0.133333333333333331,0.66666666666666663,0.133333333333333331],"xyz":[0.153224706692699747,0.292038163070701628,0.0634278655573337208],"hpluv":[127.715012949240347,183.614848439340193,60.9610867967894592],"hsluv":[127.715012949240347,93.4926845994439475,60.9610867967894592]},"#22aa33":{"lch":[61.0694299105106495,84.4016098893730629,129.358188857231823],"luv":[61.0694299105106495,-53.5246691509727626,65.259034196016259],"rgb":[0.133333333333333331,0.66666666666666663,0.2],"xyz":[0.156312457425157481,0.293273263363684755,0.0796900194149448571],"hpluv":[129.358188857231823,175.374397166590711,61.0694299105106495],"hsluv":[129.358188857231823,93.5882360519160699,61.0694299105106495]},"#22aa44":{"lch":[61.2253168994486145,79.2082340196825214,131.918899994809664],"luv":[61.2253168994486145,-52.917281977470445,58.938150671985575],"rgb":[0.133333333333333331,0.66666666666666663,0.266666666666666663],"xyz":[0.160770452669376368,0.295056461461372321,0.103168794367831496],"hpluv":[131.918899994809664,164.164260791814144,61.2253168994486145],"hsluv":[131.918899994809664,93.7213426507132397,61.2253168994486145]},"#22aa55":{"lch":[61.4328316402448422,72.8294169380228169,135.724592815827151],"luv":[61.4328316402448422,-52.1453123372646488,50.8428005993127883],"rgb":[0.133333333333333331,0.66666666666666663,0.333333333333333315],"xyz":[0.166732866708738381,0.297441427077117182,0.134570841641805411],"hpluv":[135.724592815827151,150.433869175471955,61.4328316402448422],"hsluv":[135.724592815827151,93.8909627694616802,61.4328316402448422]},"#22aa66":{"lch":[61.695221435526193,65.6694032551541511,141.26425661379642],"luv":[61.695221435526193,-51.2247747289881588,41.0912761769754056],"rgb":[0.133333333333333331,0.66666666666666663,0.4],"xyz":[0.174317862482246538,0.3004754253865205,0.174518486048949373],"hpluv":[141.26425661379642,135.067502346464636,61.695221435526193],"hsluv":[141.26425661379642,94.0939374356695737,61.695221435526193]},"#22aa77":{"lch":[62.015018576984005,58.3994247120946,149.232117680428786],"luv":[62.015018576984005,-50.1795183581889,29.8748848373309741],"rgb":[0.133333333333333331,0.66666666666666663,0.466666666666666674],"xyz":[0.183631875502448472,0.304201030594601296,0.223572287955347282],"hpluv":[149.232117680428786,119.495353216490457,62.015018576984005],"hsluv":[149.232117680428786,94.3254538020573534,62.015018576984005]},"#22aa88":{"lch":[62.3941144695078265,52.0455462506657582,160.428514439052776],"luv":[62.3941144695078265,-49.0385773736463904,17.4343572780611709],"rgb":[0.133333333333333331,0.66666666666666663,0.533333333333333326],"xyz":[0.194772292604392272,0.308657197435378894,0.282245151358919477],"hpluv":[160.428514439052776,105.847175197261592,62.3941144695078265],"hsluv":[160.428514439052776,94.5795977554836753,62.3941144695078265]},"#22aa99":{"lch":[62.8338123759312168,48.0032947436357915,175.177921691935865],"luv":[62.8338123759312168,-47.8333890460383486,4.03524455451089725],"rgb":[0.133333333333333331,0.66666666666666663,0.6],"xyz":[0.207829256713465,0.313879983079008051,0.351011829000037356],"hpluv":[175.177921691935865,96.9431131493944918,62.8338123759312168],"hsluv":[175.177921691935865,94.8499327714938119,62.8338123759312168]},"#22aaaa":{"lch":[63.334871160235295,47.6677335272966047,192.177050630061132],"luv":[63.334871160235295,-46.595231466735612,-10.0547115418935782],"rgb":[0.133333333333333331,0.66666666666666663,0.66666666666666663],"xyz":[0.222886949678717528,0.319903060265109174,0.430315678617035957],"hpluv":[192.177050630061132,95.5038628742744748,63.334871160235295],"hsluv":[192.177050630061132,95.1300327368807075,63.334871160235295]},"#22aabb":{"lch":[63.8975462810157211,51.5875561200845425,208.460106210027732],"luv":[63.8975462810157211,-45.3531553267970224,-24.5838818811491713],"rgb":[0.133333333333333331,0.66666666666666663,0.733333333333333282],"xyz":[0.240024541786054202,0.326758097108043932,0.520573663715677704],"hpluv":[208.460106210027732,102.447201536891086,63.8975462810157211],"hsluv":[208.460106210027732,95.4139121453229393,63.8975462810157211]},"#22aacc":{"lch":[64.5216311304052681,59.1142282054053112,221.706391964203618],"luv":[64.5216311304052681,-44.1325525375176184,-39.3295027027292079],"rgb":[0.133333333333333331,0.66666666666666663,0.8],"xyz":[0.259316917229775901,0.334475047285532734,0.62218017438594786],"hpluv":[221.706391964203618,116.258847224370101,64.5216311304052681],"hsluv":[221.706391964203618,95.6963242429470853,64.5216311304052681]},"#22aadd":{"lch":[65.2065000175346796,69.0826091468118619,231.553565124642319],"luv":[65.2065000175346796,-42.9543721979751538,-54.1047945713593279],"rgb":[0.133333333333333331,0.66666666666666663,0.866666666666666696],"xyz":[0.2808352427804095,0.34308237750578624,0.735510022285953857],"hpluv":[231.553565124642319,134.436491082611866,65.2065000175346796],"hsluv":[231.553565124642319,95.9729250289437,65.2065000175346796]},"#22aaee":{"lch":[65.951153016283925,80.4876559434330687,238.683318882444183],"luv":[65.951153016283925,-41.8348964739996418,-68.7612114224159825],"rgb":[0.133333333333333331,0.66666666666666663,0.933333333333333348],"xyz":[0.304647423046584209,0.352607249612256257,0.86092083835447708],"hpluv":[238.683318882444183,154.862481256054423,65.951153016283925],"hsluv":[238.683318882444183,96.2403212652285305,65.951153016283925]},"#22aaff":{"lch":[66.7542622474436911,92.6475815050926741,243.881654723446388],"luv":[66.7542622474436911,-40.785937465112724,-83.187028218555227],"rgb":[0.133333333333333331,0.66666666666666663,1],"xyz":[0.33081847145641563,0.363075668976189,0.998755026646258925],"hpluv":[243.881654723446388,176.114215620682756,66.7542622474436911],"hsluv":[243.881654723446388,99.9999999999982521,66.7542622474436911]},"#22bb00":{"lch":[66.4275479271698117,100.802558180890344,126.547308547849156],"luv":[66.4275479271698117,-60.0265444602621798,80.9812922592122533],"rgb":[0.133333333333333331,0.733333333333333282,0],"xyz":[0.184292180963184393,0.358792349432654534,0.0595410403661315105],"hpluv":[126.547308547849156,192.558484368086766,66.4275479271698117],"hsluv":[126.547308547849156,100.000000000002402,66.4275479271698117]},"#22bb11":{"lch":[66.4585250929908682,99.6331665050874449,126.950333401467461],"luv":[66.4585250929908682,-59.891738470658936,79.6225315522727612],"rgb":[0.133333333333333331,0.733333333333333282,0.0666666666666666657],"xyz":[0.185303846462821525,0.359197015632509387,0.0648691453308871402],"hpluv":[126.950333401467461,190.235936561387632,66.4585250929908682],"hsluv":[126.950333401467461,98.1323711753334464,66.4585250929908682]},"#22bb22":{"lch":[66.5158870179191553,97.5011623585143496,127.715012949240375],"luv":[66.5158870179191553,-59.6448091899021904,77.1295883430057785],"rgb":[0.133333333333333331,0.733333333333333282,0.133333333333333331],"xyz":[0.187179204601298527,0.359947158887900187,0.0747460315268663328],"hpluv":[127.715012949240375,186.004620566556611,66.5158870179191553],"hsluv":[127.715012949240375,94.7095045552014341,66.5158870179191553]},"#22bb33":{"lch":[66.6101592341861561,94.091611241079562,129.025604662043349],"luv":[66.6101592341861561,-59.2464412491434089,73.0964465979998721],"rgb":[0.133333333333333331,0.733333333333333282,0.2],"xyz":[0.19026695533375626,0.361182259180883314,0.091008185384477483],"hpluv":[129.025604662043349,179.246118589082698,66.6101592341861561],"hsluv":[129.025604662043349,94.7728361420699628,66.6101592341861561]},"#22bb44":{"lch":[66.7458880420052907,89.3893698914418735,131.03732282887168],"luv":[66.7458880420052907,-58.6886365964374903,67.424797990356],"rgb":[0.133333333333333331,0.733333333333333282,0.266666666666666663],"xyz":[0.194724950577975148,0.36296545727857088,0.114486960337364121],"hpluv":[131.03732282887168,169.941985039444177,66.7458880420052907],"hsluv":[131.03732282887168,94.8616428317247511,66.7458880420052907]},"#22bb55":{"lch":[66.9267274999506,83.5071173210959472,133.96576260666626],"luv":[66.9267274999506,-57.9730125462468777,60.1046459068846488],"rgb":[0.133333333333333331,0.733333333333333282,0.333333333333333315],"xyz":[0.200687364617337161,0.365350422894315741,0.145889007611338023],"hpluv":[133.96576260666626,158.330006142604873,66.9267274999506],"hsluv":[133.96576260666626,94.9758066387206128,66.9267274999506]},"#22bb66":{"lch":[67.1556458878222315,76.7025221245859541,138.121353671725132],"luv":[67.1556458878222315,-57.1096598103208919,51.2031606116460054],"rgb":[0.133333333333333331,0.733333333333333282,0.4],"xyz":[0.208272360390845318,0.368384421203719059,0.185836652018481985],"hpluv":[138.121353671725132,144.932719296840418,67.1556458878222315],"hsluv":[138.121353671725132,95.1139083236691363,67.1556458878222315]},"#22bb77":{"lch":[67.4350338747947831,69.4107315991336407,143.945643371462126],"luv":[67.4350338747947831,-56.1157304677756557,40.8518598743670935],"rgb":[0.133333333333333331,0.733333333333333282,0.466666666666666674],"xyz":[0.217586373411047251,0.372110026411799855,0.234890453924879894],"hpluv":[143.945643371462126,130.611184627207962,67.4350338747947831],"hsluv":[143.945643371462126,95.2734443978328471,67.4350338747947831]},"#22bb88":{"lch":[67.7667691572368653,62.2973404800357571,152.016434409622292],"luv":[67.7667691572368653,-55.0136735570032656,29.2310511724943218],"rgb":[0.133333333333333331,0.733333333333333282,0.533333333333333326],"xyz":[0.228726790512991052,0.376566193252577452,0.293563317328452089],"hpluv":[152.016434409622292,116.65196328010299,67.7667691572368653],"hsluv":[152.016434409622292,95.4510958201536823,67.7667691572368653]},"#22bb99":{"lch":[68.1522602188863402,56.3167420179958214,162.907285030510934],"luv":[68.1522602188863402,-53.8292536917802948,16.552549003324323],"rgb":[0.133333333333333331,0.733333333333333282,0.6],"xyz":[0.241783754622063779,0.38178897889620661,0.362329994969569968],"hpluv":[162.907285030510934,104.856796539193,68.1522602188863402],"hsluv":[162.907285030510934,95.6430286983925555,68.1522602188863402]},"#22bbaa":{"lch":[68.5924801056558238,52.6774965449754617,176.68900923381463],"luv":[68.5924801056558238,-52.5895649511659542,3.04241688349887474],"rgb":[0.133333333333333331,0.733333333333333282,0.66666666666666663],"xyz":[0.256841447587316307,0.387812056082307732,0.441633844586568569],"hpluv":[176.68900923381463,97.4513697523019,68.5924801056558238],"hsluv":[176.68900923381463,95.8451953079369616,68.5924801056558238]},"#22bbbb":{"lch":[69.087995915136645,52.5025293878432677,192.177050630061075],"luv":[69.087995915136645,-51.3212466461144174,-11.0745309069971647],"rgb":[0.133333333333333331,0.733333333333333282,0.733333333333333282],"xyz":[0.273979039694653,0.394667092925242491,0.531891829685210316],"hpluv":[192.177050630061075,96.4310638968393903,69.087995915136645],"hsluv":[192.177050630061075,96.05360442262905,69.087995915136645]},"#22bbcc":{"lch":[69.6389970716796824,56.2104265974936,207.07804051519031],"luv":[69.6389970716796824,-50.0490518690675117,-25.5871933841835393],"rgb":[0.133333333333333331,0.733333333333333282,0.8],"xyz":[0.293271415138374736,0.402384043102731292,0.633498340355480472],"hpluv":[207.07804051519031,102.424464009042,69.6389970716796824],"hsluv":[207.07804051519031,96.2645382772070519,69.6389970716796824]},"#22bbdd":{"lch":[70.2453239794146924,63.2897284454122868,219.558412279776348],"luv":[70.2453239794146924,-48.7948432782897541,-40.3069844585422956],"rgb":[0.133333333333333331,0.733333333333333282,0.866666666666666696],"xyz":[0.314789740689008224,0.410991373322984799,0.746828188255486469],"hpluv":[219.558412279776348,114.328666738486675,70.2453239794146924],"hsluv":[219.558412279776348,96.4747049793171101,70.2453239794146924]},"#22bbee":{"lch":[70.9064977498674409,72.7777709471329644,229.176572531806158],"luv":[70.9064977498674409,-47.5770176148035588,-55.072963774560229],"rgb":[0.133333333333333331,0.733333333333333282,0.933333333333333348],"xyz":[0.338601920955183,0.420516245429454816,0.872239004324009692],"hpluv":[229.176572531806158,130.242295294742263,70.9064977498674409],"hsluv":[229.176572531806158,96.6813261778791286,70.9064977498674409]},"#22bbff":{"lch":[71.621751136046143,83.782469360031385,236.362391595167765],"luv":[71.621751136046143,-46.4103062114400586,-69.7537500742789121],"rgb":[0.133333333333333331,0.733333333333333282,1],"xyz":[0.364772969365014355,0.43098466479338754,1.01007319261579154],"hpluv":[236.362391595167765,148.438838585801221,71.621751136046143],"hsluv":[236.362391595167765,99.9999999999977405,71.621751136046143]},"#22cc00":{"lch":[71.9091745039523431,109.499123564337054,126.755635680122666],"luv":[71.9091745039523431,-65.5246489265194185,87.7301455852794732],"rgb":[0.133333333333333331,0.8,0],"xyz":[0.222515924272801313,0.435239836051889428,0.0722822881360034586],"hpluv":[126.755635680122666,193.226045742870838,71.9091745039523431],"hsluv":[126.755635680122666,100.000000000002288,71.9091745039523431]},"#22cc11":{"lch":[71.9364107151438077,108.454952609175663,127.087674169957992],"luv":[71.9364107151438077,-65.4022833696805,86.5159989567857508],"rgb":[0.133333333333333331,0.8,0.0666666666666666657],"xyz":[0.223527589772438445,0.435644502251744281,0.0776103931007591],"hpluv":[127.087674169957992,191.311003889673145,71.9364107151438077],"hsluv":[127.087674169957992,98.4591341389350418,71.9364107151438077]},"#22cc22":{"lch":[71.9868548113652196,106.54571101335867,127.715012949240403],"luv":[71.9868548113652196,-65.1776701905061771,84.2843985793322332],"rgb":[0.133333333333333331,0.8,0.133333333333333331],"xyz":[0.225402947910915447,0.436394645507135082,0.0874872792967382878],"hpluv":[127.715012949240403,187.811464536348922,71.9868548113652196],"hsluv":[127.715012949240403,95.6295101801496,71.9868548113652196]},"#22cc33":{"lch":[72.0697845089905229,103.47656324227728,128.782383231673776],"luv":[72.0697845089905229,-64.8140104316899084,80.6631464312780224],"rgb":[0.133333333333333331,0.8,0.2],"xyz":[0.22849069864337318,0.437629745800118208,0.103749433154349424],"hpluv":[128.782383231673776,182.191494825933376,72.0697845089905229],"hsluv":[128.782383231673776,95.6728206524347,72.0697845089905229]},"#22cc44":{"lch":[72.1892409305665126,99.2080881077430092,130.402545810446583],"luv":[72.1892409305665126,-64.3020931154350563,75.5479024657046381],"rgb":[0.133333333333333331,0.8,0.266666666666666663],"xyz":[0.232948693887592068,0.439412943897805774,0.127228208107236063],"hpluv":[130.402545810446583,174.38692971209278,72.1892409305665126],"hsluv":[130.402545810446583,95.7338579762306807,72.1892409305665126]},"#22cc55":{"lch":[72.3485056391290584,93.798910065649892,132.724941359454647],"luv":[72.3485056391290584,-63.6406395178356732,68.9064912128369116],"rgb":[0.133333333333333331,0.8,0.333333333333333315],"xyz":[0.23891110792695408,0.441797909513550635,0.158630255381209978],"hpluv":[132.724941359454647,164.515777124050715,72.3485056391290584],"hsluv":[132.724941359454647,95.8128514989610665,72.3485056391290584]},"#22cc66":{"lch":[72.5502856399570106,87.4149553130202577,135.957009780349864],"luv":[72.5502856399570106,-62.8354766726053597,60.7706942835425821],"rgb":[0.133333333333333331,0.8,0.4],"xyz":[0.246496103700462238,0.444831907822953954,0.19857789978835394],"hpluv":[135.957009780349864,152.89241471165704,72.5502856399570106],"hsluv":[135.957009780349864,95.9092114165347454,72.5502856399570106]},"#22cc77":{"lch":[72.7968107106438,80.347487444586676,140.388595454497391],"luv":[72.7968107106438,-61.8986076052927601,51.2277377519640922],"rgb":[0.133333333333333331,0.8,0.466666666666666674],"xyz":[0.255810116720664171,0.448557513031034749,0.247631701694751849],"hpluv":[140.388595454497391,140.055211607516185,72.7968107106438],"hsluv":[140.388595454497391,96.0216367005683651,72.7968107106438]},"#22cc88":{"lch":[73.0898911189871399,73.0433057087839899,146.410934307510047],"luv":[73.0898911189871399,-60.8470337434031379,40.4099368162836328],"rgb":[0.133333333333333331,0.8,0.533333333333333326],"xyz":[0.266950533822607972,0.453013679871812347,0.306304565098324044],"hpluv":[146.410934307510047,126.812607157634474,73.0898911189871399],"hsluv":[146.410934307510047,96.1482500942118463,73.0898911189871399]},"#22cc99":{"lch":[73.4309556061454316,66.1476907411168,154.494840416347301],"luv":[73.4309556061454316,-59.7013675833161201,28.4826912187770667],"rgb":[0.133333333333333331,0.8,0.6],"xyz":[0.280007497931680671,0.458236465515441505,0.375071242739441923],"hpluv":[154.494840416347301,114.307528246897718,73.4309556061454316],"hsluv":[154.494840416347301,96.2867563581127826,73.4309556061454316]},"#22ccaa":{"lch":[73.8210792378530272,60.5373530055450857,165.03572853292485],"luv":[73.8210792378530272,-58.4843517505691963,15.631753236079188],"rgb":[0.133333333333333331,0.8,0.66666666666666663],"xyz":[0.295065190896933227,0.464259542701542627,0.454375092356440524],"hpluv":[165.03572853292485,104.059650734936554,73.8210792378530272],"hsluv":[165.03572853292485,96.4346108423233233,73.8210792378530272]},"#22ccbb":{"lch":[74.2610062314211916,57.2561659312404601,177.947236116536601],"luv":[74.2610062314211916,-57.2194225987583565,2.05090580243043252],"rgb":[0.133333333333333331,0.8,0.733333333333333282],"xyz":[0.312202783004269901,0.471114579544477385,0.544633077455082271],"hpluv":[177.947236116536601,97.8364666306377302,74.2610062314211916],"hsluv":[177.947236116536601,96.5891828531354406,74.2610062314211916]},"#22cccc":{"lch":[74.7511706210643467,57.2167927817266,192.177050630061132],"luv":[74.7511706210643467,-55.9294412838427633,-12.0689259631595149],"rgb":[0.133333333333333331,0.8,0.8],"xyz":[0.3314951584479916,0.478831529721966187,0.646239588125352427],"hpluv":[192.177050630061132,97.128087782713564,74.7511706210643467],"hsluv":[192.177050630061132,96.747899952527,74.7511706210643467]},"#22ccdd":{"lch":[75.2917163799825602,60.7443465218384091,205.915913497891523],"luv":[75.2917163799825602,-54.6356779140553357,-26.5484148912282159],"rgb":[0.133333333333333331,0.8,0.866666666666666696],"xyz":[0.353013483998625199,0.487438859942219693,0.759569436025358424],"hpluv":[205.915913497891523,102.375961416692178,75.2917163799825602],"hsluv":[205.915913497891523,96.9083635301369,75.2917163799825602]},"#22ccee":{"lch":[75.8825178700112843,67.4282526949852894,217.691288579377613],"luv":[75.8825178700112843,-53.3570890727704779,-41.2260877015904583],"rgb":[0.133333333333333331,0.8,0.933333333333333348],"xyz":[0.376825664264799909,0.49696373204868971,0.884980252093881647],"hpluv":[217.691288579377613,112.755958051566466,75.8825178700112843],"hsluv":[217.691288579377613,97.0684311053177851,75.8825178700112843]},"#22ccff":{"lch":[76.5232010138481,76.4669756721874165,227.041443142980768],"luv":[76.5232010138481,-52.1098871667616095,-55.9621124325938553],"rgb":[0.133333333333333331,0.8,1],"xyz":[0.40299671267463133,0.507432151412622434,1.02281444038566338],"hpluv":[227.041443142980768,130.754689276117347,76.5232010138481],"hsluv":[227.041443142980768,99.999999999997,76.5232010138481]},"#22dd00":{"lch":[77.3111928538645543,118.038829613749726,126.914864539429317],"luv":[77.3111928538645543,-70.8973873693031607,94.3754510494695],"rgb":[0.133333333333333331,0.866666666666666696,0],"xyz":[0.265149914819976518,0.520507817146241,0.0864936183183947771],"hpluv":[126.914864539429317,210.347075729226248,77.3111928538645543],"hsluv":[126.914864539429317,100.000000000002288,77.3111928538645543]},"#22dd11":{"lch":[77.3353680300965323,117.099630973594756,127.192375056145337],"luv":[77.3353680300965323,-70.7859197567446756,93.2827805028547346],"rgb":[0.133333333333333331,0.866666666666666696,0.0666666666666666657],"xyz":[0.266161580319613622,0.520912483346095856,0.0918217232831504138],"hpluv":[127.192375056145337,208.941659016378082,77.3353680300965323],"hsluv":[127.192375056145337,98.7107326231664217,77.3353680300965323]},"#22dd22":{"lch":[77.3801492665193535,115.37848303888174,127.715012949240432],"luv":[77.3801492665193535,-70.5809801545768636,91.2716801027172409],"rgb":[0.133333333333333331,0.866666666666666696,0.133333333333333331],"xyz":[0.268036938458090679,0.521662626601486656,0.101698609479129606],"hpluv":[127.715012949240432,206.361699793513822,77.3801492665193535],"hsluv":[127.715012949240432,96.3395071146262154,77.3801492665193535]},"#22dd33":{"lch":[77.4537875025684315,112.600723810091978,128.59932725776207],"luv":[77.4537875025684315,-70.24826089977104,88.0005956974969195],"rgb":[0.133333333333333331,0.866666666666666696,0.2],"xyz":[0.271124689190548385,0.522897726894469783,0.117960763336740743],"hpluv":[128.59932725776207,202.185786680839726,77.4537875025684315],"hsluv":[128.59932725776207,96.3699377868481122,77.4537875025684315]},"#22dd44":{"lch":[77.5598997380018,108.71293616133481,129.930302741787841],"luv":[77.5598997380018,-69.7779723132927217,83.3638834787812],"rgb":[0.133333333333333331,0.866666666666666696,0.266666666666666663],"xyz":[0.2755826844347673,0.524680924992157349,0.141439538289627381],"hpluv":[129.930302741787841,196.316024449944422,77.5598997380018],"hsluv":[129.930302741787841,96.4129906320409162,77.5598997380018]},"#22dd55":{"lch":[77.7014460191091416,103.73899221962921,131.815952634500974],"luv":[77.7014460191091416,-69.1669361195931,77.3156740549574266],"rgb":[0.133333333333333331,0.866666666666666696,0.333333333333333315],"xyz":[0.281545098474129285,0.527065890607902099,0.172841585563601297],"hpluv":[131.815952634500974,188.764163413831653,77.7014460191091416],"hsluv":[131.815952634500974,96.4690009350202189,77.7014460191091416]},"#22dd66":{"lch":[77.880896240080375,97.784990294992113,134.401120560320578],"luv":[77.880896240080375,-68.4179393254247117,69.8633659756971923],"rgb":[0.133333333333333331,0.866666666666666696,0.4],"xyz":[0.28913009424763747,0.530099888917305417,0.212789229970745258],"hpluv":[134.401120560320578,179.664283635232721,77.880896240080375],"hsluv":[134.401120560320578,96.5377748783722467,77.880896240080375]},"#22dd77":{"lch":[78.1003183749745205,91.0497694522495635,137.883451987021573],"luv":[78.1003183749745205,-67.5390964016577584,61.0616980975421],"rgb":[0.133333333333333331,0.866666666666666696,0.466666666666666674],"xyz":[0.298444107267839376,0.533825494125386268,0.261843031877143195],"hpluv":[137.883451987021573,169.299836923025936,78.1003183749745205],"hsluv":[137.883451987021573,96.6186469084632478,78.1003183749745205]},"#22dd88":{"lch":[78.3614307373917285,83.8424849668191143,142.529576014285681],"luv":[78.3614307373917285,-66.5430534683674608,51.0057283108209205],"rgb":[0.133333333333333331,0.866666666666666696,0.533333333333333326],"xyz":[0.309584524369783176,0.538281660966163811,0.320515895280715335],"hpluv":[142.529576014285681,158.150702843881334,78.3614307373917285],"hsluv":[142.529576014285681,96.7105501690208,78.3614307373917285]},"#22dd99":{"lch":[78.665635872828048,76.6094810635621144,148.680389056502293],"luv":[78.665635872828048,-65.4460211356496586,39.8224924677044854],"rgb":[0.133333333333333331,0.866666666666666696,0.6],"xyz":[0.322641488478855876,0.543504446609793,0.389282572921833214],"hpluv":[148.680389056502293,146.968629494944878,78.665635872828048],"hsluv":[148.680389056502293,96.8121015046769884,78.665635872828048]},"#22ddaa":{"lch":[79.0140446006893882,69.96699558248838,156.711923446039975],"luv":[79.0140446006893882,-64.2666918031728613,27.6617569130368608],"rgb":[0.133333333333333331,0.866666666666666696,0.66666666666666663],"xyz":[0.337699181444108432,0.549527523795894091,0.46858642253883187],"hpluv":[156.711923446039975,136.880679993502099,79.0140446006893882],"hsluv":[156.711923446039975,96.9216963326973513,79.0140446006893882]},"#22ddbb":{"lch":[79.4074947696843196,64.7136880558744849,166.88262774299784],"luv":[79.4074947696843196,-63.0251268096233161,14.6865520944819572],"rgb":[0.133333333333333331,0.866666666666666696,0.733333333333333282],"xyz":[0.354836773551445106,0.556382560638828849,0.558844407637473561],"hpluv":[166.88262774299784,129.476815865977585,79.4074947696843196],"hsluv":[166.88262774299784,97.0376060255481,79.4074947696843196]},"#22ddcc":{"lch":[79.8465673347569123,61.7508733998812431,179.012513434233142],"luv":[79.8465673347569123,-61.7417023409151184,1.06421693934889783],"rgb":[0.133333333333333331,0.866666666666666696,0.8],"xyz":[0.374129148995166805,0.564099510816317706,0.660450918307743717],"hpluv":[179.012513434233142,126.735630147597362,79.8465673347569123],"hsluv":[179.012513434233142,97.1580701030331824,79.8465673347569123]},"#22dddd":{"lch":[80.3316012938198867,61.8272738566169,192.177050630061245],"luv":[80.3316012938198867,-60.4361886569793,-13.0414298740249546],"rgb":[0.133333333333333331,0.866666666666666696,0.866666666666666696],"xyz":[0.395647474545800404,0.572706841036571213,0.773780766207749715],"hpluv":[192.177050630061245,130.583134255532286,80.3316012938198867],"hsluv":[192.177050630061245,97.2813767637234434,80.3316012938198867]},"#22ddee":{"lch":[80.8627083873859078,65.1993477029452606,204.924336649163848],"luv":[80.8627083873859078,-59.1270126845314579,-27.4763773429615092],"rgb":[0.133333333333333331,0.866666666666666696,0.933333333333333348],"xyz":[0.419459654811975113,0.58223171314304123,0.899191582276272938],"hpluv":[204.924336649163848,142.193638870282854,80.8627083873859078],"hsluv":[204.924336649163848,97.405927410883,80.8627083873859078]},"#22ddff":{"lch":[81.4397880620905141,71.5324096951162574,216.054756332513932],"luv":[81.4397880620905141,-57.8307265123863,-42.1009822669203544],"rgb":[0.133333333333333331,0.866666666666666696,1],"xyz":[0.445630703221806534,0.592700132506974,1.03702577056805478],"hpluv":[216.054756332513932,161.676156351457934,81.4397880620905141],"hsluv":[216.054756332513932,99.9999999999961,81.4397880620905141]},"#22ee00":{"lch":[82.639607109796458,126.437751030410212,127.039022267349651],"luv":[82.639607109796458,-76.1608929793909368,100.92583052028732],"rgb":[0.133333333333333331,0.933333333333333348,0],"xyz":[0.312328691160491334,0.614865369827271913,0.102219877098565962],"hpluv":[127.039022267349651,308.746810598848469,82.639607109796458],"hsluv":[127.039022267349651,100.000000000002203,82.639607109796458]},"#22ee11":{"lch":[82.6612418520526262,125.587498690937579,127.273852876359626],"luv":[82.6612418520526262,-76.0589691017437,99.9362449095734462],"rgb":[0.133333333333333331,0.933333333333333348,0.0666666666666666657],"xyz":[0.313340356660128438,0.615270036027126821,0.107547982063321598],"hpluv":[127.273852876359626,307.111076239802628,82.6612418520526262],"hsluv":[127.273852876359626,98.9080389542438638,82.6612418520526262]},"#22ee22":{"lch":[82.7013218186993271,124.026614606467561,127.715012949240375],"luv":[82.7013218186993271,-75.8713392100017643,98.112899341636421],"rgb":[0.133333333333333331,0.933333333333333348,0.133333333333333331],"xyz":[0.315215714798605495,0.616020179282517621,0.117424868259300791],"hpluv":[127.715012949240375,304.102865014696874,82.7013218186993271],"hsluv":[127.715012949240375,96.8972824334497176,82.7013218186993271]},"#22ee33":{"lch":[82.7672420886773921,121.499708898279266,128.458265706389938],"luv":[82.7672420886773921,-75.5660658728233443,95.1417308591278754],"rgb":[0.133333333333333331,0.933333333333333348,0.2],"xyz":[0.318303465531063201,0.617255279575500748,0.133687022116911941],"hpluv":[128.458265706389938,299.21821405622444,82.7672420886773921],"hsluv":[128.458265706389938,96.9191735237910308,82.7672420886773921]},"#22ee44":{"lch":[82.8622607061112575,117.945648419515081,129.569612755667265],"luv":[82.8622607061112575,-75.1331770294334,90.918544261133448],"rgb":[0.133333333333333331,0.933333333333333348,0.266666666666666663],"xyz":[0.322761460775282116,0.619038477673188314,0.15716579706979858],"hpluv":[129.569612755667265,292.317409010606752,82.8622607061112575],"hsluv":[129.569612755667265,96.9502397298715692,82.8622607061112575]},"#22ee55":{"lch":[82.9890600071846194,113.365618159982461,131.129866929811442],"luv":[82.9890600071846194,-74.5682726576598185,85.3893207236587],"rgb":[0.133333333333333331,0.933333333333333348,0.333333333333333315],"xyz":[0.328723874814644101,0.621423443288933064,0.188567844343772495],"hpluv":[131.129866929811442,283.372415217622233,82.9890600071846194],"hsluv":[131.129866929811442,96.9908233877961266,82.9890600071846194]},"#22ee66":{"lch":[83.1498978333136,107.825536766413322,133.243989823694022],"luv":[83.1498978333136,-73.8719849852999175,78.5447402013449647],"rgb":[0.133333333333333331,0.933333333333333348,0.4],"xyz":[0.336308870588152287,0.624457441598336382,0.228515488750916429],"hpluv":[133.243989823694022,272.476761047092111,83.1498978333136],"hsluv":[133.243989823694022,97.0409162623008683,83.1498978333136]},"#22ee77":{"lch":[83.3466879629012709,101.462411308546436,136.05169958656856],"luv":[83.3466879629012709,-73.0495185954435726,70.415827350948561],"rgb":[0.133333333333333331,0.933333333333333348,0.466666666666666674],"xyz":[0.345622883608354192,0.628183046806417233,0.277569290657314338],"hpluv":[136.05169958656856,259.869348467080897,83.3466879629012709],"hsluv":[136.05169958656856,97.1001922741487391,83.3466879629012709]},"#22ee88":{"lch":[83.5810478428977746,94.494960715205238,139.739189865999265],"luv":[83.5810478428977746,-72.1101014829990277,61.0690663485185681],"rgb":[0.133333333333333331,0.933333333333333348,0.533333333333333326],"xyz":[0.356763300710298,0.632639213647194776,0.336242154060886533],"hpluv":[139.739189865999265,245.976403795765407,83.5810478428977746],"hsluv":[139.739189865999265,97.1680458209452098,83.5810478428977746]},"#22ee99":{"lch":[83.8543293451422187,87.2401092672494514,144.548413258572367],"luv":[83.8543293451422187,-71.0663081347816,50.6005584263049286],"rgb":[0.133333333333333331,0.933333333333333348,0.6],"xyz":[0.369820264819370692,0.637861999290824,0.405008831702004413],"hpluv":[144.548413258572367,231.480208097577446,83.8543293451422187],"hsluv":[144.548413258572367,97.2436385928483844,83.8543293451422187]},"#22eeaa":{"lch":[84.1676401551603846,80.1359348037682508,150.771943883792943],"luv":[84.1676401551603846,-69.9332745317766324,39.1293388665967328],"rgb":[0.133333333333333331,0.933333333333333348,0.66666666666666663],"xyz":[0.384877957784623248,0.643885076476925056,0.484312681319003],"hpluv":[150.771943883792943,217.42312560604816,84.1676401551603846],"hsluv":[150.771943883792943,97.3259536357082311,84.1676401551603846]},"#22eebb":{"lch":[84.5218598825754697,73.7647249619211181,158.704077932293842],"luv":[84.5218598825754697,-68.7278540770417834,26.7902355844945639],"rgb":[0.133333333333333331,0.933333333333333348,0.733333333333333282],"xyz":[0.402015549891959922,0.650740113319859814,0.57457066641764476],"hpluv":[158.704077932293842,205.339796192481117,84.5218598825754697],"hsluv":[158.704077932293842,97.4138533690930331,84.5218598825754697]},"#22eecc":{"lch":[84.9176532530853621,68.8500130904050422,168.499772582144715],"luv":[84.9176532530853621,-67.467774257192545,13.7267526869778429],"rgb":[0.133333333333333331,0.933333333333333348,0.8],"xyz":[0.421307925335681621,0.658457063497348671,0.676177177087914916],"hpluv":[168.499772582144715,197.354512375483154,84.9176532530853621],"hsluv":[168.499772582144715,97.5061375012374896,84.9176532530853621]},"#22eedd":{"lch":[85.3554818050158275,66.1709053937965166,179.926877690260085],"luv":[85.3554818050158275,-66.170851505859261,0.0844489448895467398],"rgb":[0.133333333333333331,0.933333333333333348,0.866666666666666696],"xyz":[0.44282625088631522,0.667064393717602178,0.789507024987920913],"hpluv":[179.926877690260085,196.075052309852396,85.3554818050158275],"hsluv":[179.926877690260085,97.6015969291830174,85.3554818050158275]},"#22eeee":{"lch":[85.8356149635753667,66.3470894225537648,192.177050630061245],"luv":[85.8356149635753667,-64.8543104533760868,-13.9948094114017536],"rgb":[0.133333333333333331,0.933333333333333348,0.933333333333333348],"xyz":[0.46663843115248993,0.676589265824072195,0.914917841056444137],"hpluv":[192.177050630061245,204.089886920334152,85.8356149635753667],"hsluv":[192.177050630061245,97.6990604766853608,85.8356149635753667]},"#22eeff":{"lch":[86.35814102124138,69.583621560973242,204.067857145017655],"luv":[86.35814102124138,-63.5342376580599435,-28.377474072755291],"rgb":[0.133333333333333331,0.933333333333333348,1],"xyz":[0.492809479562321351,0.687057685188004919,1.05275202934822598],"hpluv":[204.067857145017655,223.225826840448832,86.35814102124138],"hsluv":[204.067857145017655,99.9999999999940883,86.35814102124138]},"#22ff00":{"lch":[87.8997189713237361,134.70927246092154,127.137510750393233],"luv":[87.8997189713237361,-81.328032504319566,107.388729464162736],"rgb":[0.133333333333333331,1,0],"xyz":[0.364181063590165166,0.718570114686621,0.119504001241790073],"hpluv":[127.137510750393233,493.515561032875894,87.8997189713237361],"hsluv":[127.137510750393233,100.000000000002359,87.8997189713237361]},"#22ff11":{"lch":[87.9192191859362708,133.935112153716517,127.338384986715198],"luv":[87.9192191859362708,-81.2344832365115792,106.487431187561],"rgb":[0.133333333333333331,1,0.0666666666666666657],"xyz":[0.36519272908980227,0.718974780886475928,0.124832106206545709],"hpluv":[127.338384986715198,491.550771148457443,87.9192191859362708],"hsluv":[127.338384986715198,99.9999999999918572,87.9192191859362708]},"#22ff22":{"lch":[87.9553480404818089,132.511927889398493,127.71501294924046],"luv":[87.9553480404818089,-81.0620967295481449,104.825319015849075],"rgb":[0.133333333333333331,1,0.133333333333333331],"xyz":[0.367068087228279327,0.719724924141866729,0.134708992402524902],"hpluv":[127.71501294924046,487.932270281505453,87.9553480404818089],"hsluv":[127.71501294924046,99.9999999999918,87.9553480404818089]},"#22ff33":{"lch":[88.0147790356886,130.202284180175667,128.347396099003475],"luv":[88.0147790356886,-80.7811428723405101,102.112887345201102],"rgb":[0.133333333333333331,1,0.2],"xyz":[0.370155837960737033,0.720960024434849855,0.150971146260136052],"hpluv":[128.347396099003475,482.0420586134328,88.0147790356886],"hsluv":[128.347396099003475,99.9999999999918572,88.0147790356886]},"#22ff44":{"lch":[88.1004639745127349,126.941230298117631,129.288082018787549],"luv":[88.1004639745127349,-80.3817123888383,98.2489504424236486],"rgb":[0.133333333333333331,1,0.266666666666666663],"xyz":[0.374613833204955948,0.722743222532537422,0.174449921213022691],"hpluv":[129.288082018787549,473.688351575452941,88.1004639745127349],"hsluv":[129.288082018787549,99.9999999999917719,88.1004639745127349]},"#22ff55":{"lch":[88.2148445849976497,122.715034192366474,130.599301535876663],"luv":[88.2148445849976497,-79.8586444797792865,93.1749779645044498],"rgb":[0.133333333333333331,1,0.333333333333333315],"xyz":[0.380576247244317933,0.725128188148282171,0.205851968486996606],"hpluv":[130.599301535876663,462.799362736769183,88.2148445849976497],"hsluv":[130.599301535876663,99.9999999999917,88.2148445849976497]},"#22ff66":{"lch":[88.3599902775167863,117.562049147645283,132.359544385640646],"luv":[88.3599902775167863,-79.2110524978463104,86.8702743288911279],"rgb":[0.133333333333333331,1,0.4],"xyz":[0.388161243017826119,0.72816218645768549,0.24579961289414054],"hpluv":[132.359544385640646,449.432018335727776,88.3599902775167863],"hsluv":[132.359544385640646,99.999999999991644,88.3599902775167863]},"#22ff77":{"lch":[88.5376718023346427,111.576626717172417,134.67079586388752],"luv":[88.5376718023346427,-78.441973739162,79.3486004003085839],"rgb":[0.133333333333333331,1,0.466666666666666674],"xyz":[0.397475256038028,0.731887791665766341,0.294853414800538449],"hpluv":[134.67079586388752,433.796433649144,88.5376718023346427],"hsluv":[134.67079586388752,99.9999999999914593,88.5376718023346427]},"#22ff88":{"lch":[88.7494051061703,104.915784817642518,137.666744209541747],"luv":[88.7494051061703,-77.5579802767934581,70.6546643845003786],"rgb":[0.133333333333333331,1,0.533333333333333326],"xyz":[0.408615673139971824,0.736343958506543883,0.353526278204110644],"hpluv":[137.666744209541747,416.300130961303353,88.7494051061703],"hsluv":[137.666744209541747,99.9999999999915161,88.7494051061703]},"#22ff99":{"lch":[88.996479539624346,97.8095196496336143,141.520869213027311],"luv":[88.996479539624346,-76.5687003963823827,60.8599725082183696],"rgb":[0.133333333333333331,1,0.6],"xyz":[0.42167263724904458,0.741566744150173096,0.422292955845228524],"hpluv":[141.520869213027311,397.621886071746928,88.996479539624346],"hsluv":[141.520869213027311,99.9999999999913882,88.996479539624346]},"#22ffaa":{"lch":[89.2799772586324281,90.5758041558366784,146.450029989774293],"luv":[89.2799772586324281,-75.4862499862398,50.057989986528554],"rgb":[0.133333333333333331,1,0.66666666666666663],"xyz":[0.43673033021429708,0.747589821336274163,0.50159680546222718],"hpluv":[146.450029989774293,378.829469872945424,89.2799772586324281],"hsluv":[146.450029989774293,99.9999999999911893,89.2799772586324281]},"#22ffbb":{"lch":[89.6007875020334552,83.6393655876379825,152.701818689515648],"luv":[89.6007875020334552,-74.3245992165371661,38.3587985630895787],"rgb":[0.133333333333333331,1,0.733333333333333282],"xyz":[0.453867922321633754,0.754444858179208921,0.591854790560868871],"hpluv":[152.701818689515648,361.553698033539774,89.6007875020334552],"hsluv":[152.701818689515648,99.9999999999909903,89.6007875020334552]},"#22ffcc":{"lch":[89.9596178804745,77.5461751086222,160.50148390925915],"luv":[89.9596178804745,-73.0989125181650223,25.8835519710636532],"rgb":[0.133333333333333331,1,0.8],"xyz":[0.473160297765355509,0.762161808356697779,0.693461301231139],"hpluv":[160.50148390925915,348.207300802542932,89.9596178804745],"hsluv":[160.50148390925915,99.999999999990834,89.9596178804745]},"#22ffdd":{"lch":[90.3570039795250466,72.9493687180361,169.926987012389134],"luv":[90.3570039795250466,-71.8249031216737421,12.7590629719491808],"rgb":[0.133333333333333331,1,0.866666666666666696],"xyz":[0.494678623315989,0.770769138576951285,0.806791149131145],"hpluv":[169.926987012389134,342.16275562632859,90.3570039795250466],"hsluv":[169.926987012389134,99.9999999999906,90.3570039795250466]},"#22ffee":{"lch":[90.793318095908873,70.5238201303846637,180.720785562676866],"luv":[90.793318095908873,-70.5182397089352,-0.887171987837557796],"rgb":[0.133333333333333331,1,0.933333333333333348],"xyz":[0.518490803582163817,0.780294010683421302,0.932201965199668248],"hpluv":[180.720785562676866,347.681124246536797,90.793318095908873],"hsluv":[180.720785562676866,99.9999999999901661,90.793318095908873]},"#22ffff":{"lch":[91.2687776254429082,70.7867026205843786,192.177050630061217],"luv":[91.2687776254429082,-69.1940343982234225,-14.9312715999851058],"rgb":[0.133333333333333331,1,1],"xyz":[0.544661851991995127,0.790762430047354,1.0700361534914502],"hpluv":[192.177050630061217,369.384676987995,91.2687776254429082],"hsluv":[192.177050630061217,99.9999999999901803,91.2687776254429082]},"#11aa00":{"lch":[60.6644104521350869,93.0873160838275,127.211890667698796],"luv":[60.6644104521350869,-56.2958954857129186,74.1351506854346241],"rgb":[0.0666666666666666657,0.66666666666666663,0],"xyz":[0.146052570780394131,0.288673842599075969,0.0480220097587461675],"hpluv":[127.211890667698796,194.713406103753528,60.6644104521350869],"hsluv":[127.211890667698796,100.000000000002444,60.6644104521350869]},"#11aa11":{"lch":[60.7002167335786424,91.7670835597914447,127.715012949240375],"luv":[60.7002167335786424,-56.1370762813229547,72.5935692169401],"rgb":[0.0666666666666666657,0.66666666666666663,0.0666666666666666657],"xyz":[0.147064236280031263,0.289078508798930822,0.0533501147235018],"hpluv":[127.715012949240375,191.838607973199288,60.7002167335786424],"hsluv":[127.715012949240375,97.6800439707354826,60.7002167335786424]},"#11aa22":{"lch":[60.7665037483511696,89.3702510768545153,128.6744569519864],"luv":[60.7665037483511696,-55.8469935821418844,69.7721655488506229],"rgb":[0.0666666666666666657,0.66666666666666663,0.133333333333333331],"xyz":[0.148939594418508264,0.289828652054321623,0.063227000919481],"hpluv":[128.6744569519864,186.624241227076766,60.7665037483511696],"hsluv":[128.6744569519864,97.7012125574833163,60.7665037483511696]},"#11aa33":{"lch":[60.8753956347493101,85.5662665428975799,130.333267948257316],"luv":[60.8753956347493101,-55.3812688292774595,65.2265362636212274],"rgb":[0.0666666666666666657,0.66666666666666663,0.2],"xyz":[0.152027345150966,0.291063752347304749,0.0794891547770921469],"hpluv":[130.333267948257316,178.361088002118152,60.8753956347493101],"hsluv":[130.333267948257316,97.7352371443594876,60.8753956347493101]},"#11aa44":{"lch":[61.0320681383245898,80.3855440613202603,132.913331110856149],"luv":[61.0320681383245898,-54.7338170014505394,58.8731260464924233],"rgb":[0.0666666666666666657,0.66666666666666663,0.266666666666666663],"xyz":[0.156485340395184885,0.292846950444992316,0.102967929729978785],"hpluv":[132.913331110856149,167.131840298358668,61.0320681383245898],"hsluv":[132.913331110856149,97.7826211010278428,61.0320681383245898]},"#11aa55":{"lch":[61.2406211479126199,74.0326893716562466,136.73626291081149],"luv":[61.2406211479126199,-53.9110982897111626,50.7388665304739064],"rgb":[0.0666666666666666657,0.66666666666666663,0.333333333333333315],"xyz":[0.162447754434546898,0.295231916060737176,0.134369977003952701],"hpluv":[136.73626291081149,153.399260409180499,61.2406211479126199],"hsluv":[136.73626291081149,97.8429805383323412,61.2406211479126199]},"#11aa66":{"lch":[61.5043118059827236,66.9183608277608926,142.276062400218507],"luv":[61.5043118059827236,-52.9302802927860938,40.944504442006675],"rgb":[0.0666666666666666657,0.66666666666666663,0.4],"xyz":[0.170032750208055056,0.298265914370140495,0.174317621411096635],"hpluv":[142.276062400218507,138.063556785316223,61.5043118059827236],"hsluv":[142.276062400218507,97.9151756761398246,61.5043118059827236]},"#11aa77":{"lch":[61.8256765996828648,59.7171413308715628,150.193024237310198],"luv":[61.8256765996828648,-51.8168585560505335,29.6841731923518672],"rgb":[0.0666666666666666657,0.66666666666666663,0.466666666666666674],"xyz":[0.179346763228257,0.30199151957822129,0.223371423317494544],"hpluv":[150.193024237310198,122.565843528602272,61.8256765996828648],"hsluv":[150.193024237310198,97.9974781059027578,61.8256765996828648]},"#11aa88":{"lch":[62.2066058842594,53.445591933814967,161.225226782668472],"luv":[62.2066058842594,-50.6018085779820197,17.201403018159084],"rgb":[0.0666666666666666657,0.66666666666666663,0.533333333333333326],"xyz":[0.190487180330200789,0.306447686418998888,0.282044286721066739],"hpluv":[161.225226782668472,109.022142353571084,62.2066058842594],"hsluv":[161.225226782668472,98.0877698343988698,62.2066058842594]},"#11aa99":{"lch":[62.6483970296267643,49.4620441969123803,175.635441576327452],"luv":[62.6483970296267643,-49.3186051456987613,3.76417356400704],"rgb":[0.0666666666666666657,0.66666666666666663,0.6],"xyz":[0.203544144439273517,0.311670472062628046,0.350810964362184619],"hpluv":[175.635441576327452,100.184705161952,62.6483970296267643],"hsluv":[175.635441576327452,98.1837511724305756,62.6483970296267643]},"#11aaaa":{"lch":[63.1517986220979424,49.10533458105958,192.177050630061103],"luv":[63.1517986220979424,-48.0004871585874326,-10.3579494522848794],"rgb":[0.0666666666666666657,0.66666666666666663,0.66666666666666663],"xyz":[0.218601837404526045,0.317693549248729168,0.430114813979183219],"hpluv":[192.177050630061103,98.6693521766944741,63.1517986220979424],"hsluv":[192.177050630061103,98.283131385506934,63.1517986220979424]},"#11aabb":{"lch":[63.7170519890822931,52.9100997091116483,208.088870357104184],"luv":[63.7170519890822931,-46.6782604891356812,-24.9122188681885817],"rgb":[0.0666666666666666657,0.66666666666666663,0.733333333333333282],"xyz":[0.235739429511862719,0.324548586091663926,0.520372799077825],"hpluv":[208.088870357104184,105.371274370578277,63.7170519890822931],"hsluv":[208.088870357104184,98.3837818799450474,63.7170519890822931]},"#11aacc":{"lch":[64.3439331362496603,60.2774830024396,221.163749704229559],"luv":[64.3439331362496603,-45.3787880982832661,-39.6754401089704345],"rgb":[0.0666666666666666657,0.66666666666666663,0.8],"xyz":[0.255031804955584418,0.332265536269152728,0.621979309748095122],"hpluv":[221.163749704229559,118.873987633214512,64.3439331362496603],"hsluv":[221.163749704229559,98.4838416101877385,64.3439331362496603]},"#11aadd":{"lch":[65.0317963778061738,70.0923801470555,230.985682804055784],"luv":[65.0317963778061738,-44.1241742926816372,-54.4609860144708762],"rgb":[0.0666666666666666657,0.66666666666666663,0.866666666666666696],"xyz":[0.276550130506218,0.340872866489406234,0.735309157648101119],"hpluv":[230.985682804055784,136.767964522831733,65.0317963778061738],"hsluv":[230.985682804055784,98.5817742563244,65.0317963778061738]},"#11aaee":{"lch":[65.7796198544585877,81.3683973110580325,238.155251398024461],"luv":[65.7796198544585877,-42.9315457639956293,-69.1208974137644248],"rgb":[0.0666666666666666657,0.66666666666666663,0.933333333333333348],"xyz":[0.300362310772392727,0.350397738595876251,0.860719973716624343],"hpluv":[238.155251398024461,156.965326989669,65.7796198544585877],"hsluv":[238.155251398024461,98.676383948407647,65.7796198544585877]},"#11aaff":{"lch":[66.5860524818013317,93.423779787985481,243.412392023658668],"luv":[66.5860524818013317,-41.8132782993845566,-83.5443139162218529],"rgb":[0.0666666666666666657,0.66666666666666663,1],"xyz":[0.326533359182224148,0.360866157959809,0.998554162008406188],"hpluv":[243.412392023658668,178.038321821181739,66.5860524818013317],"hsluv":[243.412392023658668,99.9999999999982805,66.5860524818013317]},"#11bb00":{"lch":[66.2579979425279788,101.837028338191359,127.308350947248712],"luv":[66.2579979425279788,-61.7238643700190792,80.9996599251199285],"rgb":[0.0666666666666666657,0.733333333333333282,0],"xyz":[0.180007068688992911,0.356582838416274528,0.0593401757282787864],"hpluv":[127.308350947248712,195.032386644097869,66.2579979425279788],"hsluv":[127.308350947248712,100.000000000002416,66.2579979425279788]},"#11bb11":{"lch":[66.2891028676776,100.667761827005108,127.715012949240403],"luv":[66.2891028676776,-61.5819268253258301,79.6345688739434507],"rgb":[0.0666666666666666657,0.733333333333333282,0.0666666666666666657],"xyz":[0.181018734188630043,0.356987504616129381,0.0646682806930344162],"hpluv":[127.715012949240403,192.702610495025482,66.2891028676776],"hsluv":[127.715012949240403,98.1199752505464318,66.2891028676776]},"#11bb22":{"lch":[66.3467009890152895,98.5365229927559199,128.48627008521666],"luv":[66.3467009890152895,-61.3219466901703356,77.130183570569983],"rgb":[0.0666666666666666657,0.733333333333333282,0.133333333333333331],"xyz":[0.182894092327107044,0.357737647871520181,0.0745451668890136088],"hpluv":[128.48627008521666,188.459149582366734,66.3467009890152895],"hsluv":[128.48627008521666,98.1339007760711155,66.3467009890152895]},"#11bb33":{"lch":[66.4413603116689586,95.1297105779412107,129.807139434656818],"luv":[66.4413603116689586,-60.9025570517959949,73.0790009455219121],"rgb":[0.0666666666666666657,0.733333333333333282,0.2],"xyz":[0.185981843059564778,0.358972748164503308,0.0908073207466247589],"hpluv":[129.807139434656818,181.684126992490178,66.4413603116689586],"hsluv":[129.807139434656818,98.1563849168539093,66.4413603116689586]},"#11bb44":{"lch":[66.5776441235544,90.4345556189602888,131.832147550242752],"luv":[66.5776441235544,-60.3153845158839061,67.3829595721343679],"rgb":[0.0666666666666666657,0.733333333333333282,0.266666666666666663],"xyz":[0.190439838303783665,0.360755946262190874,0.114286095699511397],"hpluv":[131.832147550242752,172.363503065423913,66.5776441235544],"hsluv":[131.832147550242752,98.1879073212924567,66.5776441235544]},"#11bb55":{"lch":[66.7592187944261,84.5672269635890501,134.774448398028227],"luv":[66.7592187944261,-59.5621947933412841,60.0329978237906374],"rgb":[0.0666666666666666657,0.733333333333333282,0.333333333333333315],"xyz":[0.196402252343145678,0.363140911877935735,0.145688142973485313],"hpluv":[134.774448398028227,160.742297130584,66.7592187944261],"hsluv":[134.774448398028227,98.2284201297941451,66.7592187944261]},"#11bb66":{"lch":[66.9890609579081513,77.7896938122995749,138.938350400658521],"luv":[66.9890609579081513,-58.6536805127572336,51.0977712402284752],"rgb":[0.0666666666666666657,0.733333333333333282,0.4],"xyz":[0.203987248116653835,0.366174910187339053,0.185635787380629275],"hpluv":[138.938350400658521,147.352496635070423,66.9890609579081513],"hsluv":[138.938350400658521,98.2774122804504486,66.9890609579081513]},"#11bb77":{"lch":[67.2695660469751573,70.5410036497719375,144.751753478195667],"luv":[67.2695660469751573,-57.6079608572489548,40.7106379437464625],"rgb":[0.0666666666666666657,0.733333333333333282,0.466666666666666674],"xyz":[0.213301261136855769,0.369900515395419849,0.234689589287027184],"hpluv":[144.751753478195667,133.064539371584743,67.2695660469751573],"hsluv":[144.751753478195667,98.33398740837616,67.2695660469751573]},"#11bb88":{"lch":[67.6026131767386289,63.4868829762913904,152.765342955174589],"luv":[67.6026131767386289,-56.44870944299889,29.0538725863731493],"rgb":[0.0666666666666666657,0.733333333333333282,0.533333333333333326],"xyz":[0.224441678238799569,0.374356682236197447,0.293362452690599351],"hpluv":[152.765342955174589,119.168054047142519,67.6026131767386289],"hsluv":[152.765342955174589,98.3969602233376293,67.6026131767386289]},"#11bb99":{"lch":[67.9896091217093357,57.5709210660889568,163.510238814203348],"luv":[67.9896091217093357,-55.2030563361408468,16.3411604100414145],"rgb":[0.0666666666666666657,0.733333333333333282,0.6],"xyz":[0.237498642347872296,0.379579467879826604,0.362129130331717231],"hpluv":[163.510238814203348,107.448401728013394,67.9896091217093357],"hsluv":[163.510238814203348,98.464964167861,67.9896091217093357]},"#11bbaa":{"lch":[68.431522322705149,53.97217733059707,177.026074660918823],"luv":[68.431522322705149,-53.8994902384006807,2.80015675382041573],"rgb":[0.0666666666666666657,0.733333333333333282,0.66666666666666663],"xyz":[0.252556335313124825,0.385602545065927726,0.441432979948715831],"hpluv":[177.026074660918823,100.081328983600983,68.431522322705149],"hsluv":[177.026074660918823,98.5365589333087684,68.431522322705149]},"#11bbbb":{"lch":[68.9289126417652511,53.7759127589290102,192.177050630061103],"luv":[68.9289126417652511,-52.5659794775517426,-11.3431298424003177],"rgb":[0.0666666666666666657,0.733333333333333282,0.733333333333333282],"xyz":[0.269693927420461499,0.392457581908862485,0.531690965047357578],"hpluv":[192.177050630061103,98.9978332941913095,68.9289126417652511],"hsluv":[192.177050630061103,98.6103267315429122,68.9289126417652511]},"#11bbcc":{"lch":[69.4819599404948463,57.3930965266865485,206.799543430538137],"luv":[69.4819599404948463,-51.2284702417124365,-25.8768499940688805],"rgb":[0.0666666666666666657,0.733333333333333282,0.8],"xyz":[0.288986302864183253,0.400174532086351287,0.633297475717627734],"hpluv":[206.799543430538137,104.81584090136549,69.4819599404948463],"hsluv":[206.799543430538137,98.6849492658553658,69.4819599404948463]},"#11bbdd":{"lch":[70.0904930703520535,64.3454332007566876,219.135570681285316],"luv":[70.0904930703520535,-49.9098388382008054,-40.6121011637893901],"rgb":[0.0666666666666666657,0.733333333333333282,0.866666666666666696],"xyz":[0.310504628414816741,0.408781862306604793,0.746627323617633731],"hpluv":[219.135570681285316,116.492493254234162,70.0904930703520535],"hsluv":[219.135570681285316,98.7592614999562244,70.0904930703520535]},"#11bbee":{"lch":[70.7540199625255184,73.7064384587933858,228.717665412779098],"luv":[70.7540199625255184,-48.6292974270947838,-55.3879996211006826],"rgb":[0.0666666666666666657,0.733333333333333282,0.933333333333333348],"xyz":[0.334316808680991506,0.41830673441307481,0.872038139686157],"hpluv":[228.717665412779098,132.188487390259695,70.7540199625255184],"hsluv":[228.717665412779098,98.8322822642660412,70.7540199625255184]},"#11bbff":{"lch":[71.471758937406932,84.600620314641418,235.923069973878228],"luv":[71.471758937406932,-47.4021956671632836,-70.0735099988154246],"rgb":[0.0666666666666666657,0.733333333333333282,1],"xyz":[0.360487857090822872,0.428775153777007534,1.00987232797793891],"hpluv":[235.923069973878228,150.202929688167188,71.471758937406932],"hsluv":[235.923069973878228,99.9999999999978115,71.471758937406932]},"#11cc00":{"lch":[71.760164015117,110.429324102022079,127.380540485202317],"luv":[71.760164015117,-67.042304751477019,87.7494444155603617],"rgb":[0.0666666666666666657,0.8,0],"xyz":[0.21823081199860983,0.433030325035509422,0.0720814234981507346],"hpluv":[127.380540485202317,195.272154443078705,71.760164015117],"hsluv":[127.380540485202317,100.000000000002245,71.760164015117]},"#11cc11":{"lch":[71.7874927519263366,109.385174124956521,127.715012949240432],"luv":[71.7874927519263366,-66.9146672826053219,86.5305935539869466],"rgb":[0.0666666666666666657,0.8,0.0666666666666666657],"xyz":[0.219242477498246963,0.433434991235364275,0.0774095284629063712],"hpluv":[127.715012949240432,193.352149077988855,71.7874927519263366],"hsluv":[127.715012949240432,98.4507061603193563,71.7874927519263366]},"#11cc22":{"lch":[71.8381079866900905,107.476295008714118,128.346772012349533],"luv":[71.8381079866900905,-66.680384726115463,84.2904519004221129],"rgb":[0.0666666666666666657,0.8,0.133333333333333331],"xyz":[0.221117835636723964,0.434185134490755076,0.0872864146588855638],"hpluv":[128.346772012349533,189.84411001864018,71.8381079866900905],"hsluv":[128.346772012349533,98.4601754700601361,71.8381079866900905]},"#11cc33":{"lch":[71.9213183931711626,104.40864440796355,129.421122123890029],"luv":[71.9213183931711626,-66.3010906987881441,80.6556284412913556],"rgb":[0.0666666666666666657,0.8,0.2],"xyz":[0.224205586369181697,0.435420234783738203,0.103548568516496714],"hpluv":[129.421122123890029,184.212095875088124,71.9213183931711626],"hsluv":[129.421122123890029,98.4755168761512749,71.9213183931711626]},"#11cc44":{"lch":[72.0411777660314669,100.144250866202455,131.050559631523186],"luv":[72.0411777660314669,-65.7672084530810395,75.5218198525557085],"rgb":[0.0666666666666666657,0.8,0.266666666666666663],"xyz":[0.228663581613400585,0.437203432881425769,0.127027343469383353],"hpluv":[131.050559631523186,176.394298837834185,72.0411777660314669],"hsluv":[131.050559631523186,98.4971346697200261,72.0411777660314669]},"#11cc55":{"lch":[72.2009771566369523,94.7439152050601621,133.383427271112737],"luv":[72.2009771566369523,-65.077446685205,68.8573554627095],"rgb":[0.0666666666666666657,0.8,0.333333333333333315],"xyz":[0.234625995652762598,0.439588398497170629,0.158429390743357268],"hpluv":[133.383427271112737,166.512782767528847,72.2009771566369523],"hsluv":[133.383427271112737,98.5251071985599225,72.2009771566369523]},"#11cc66":{"lch":[72.4034304155724,88.37638367841096,136.624435218533876],"luv":[72.4034304155724,-64.2379326004453333,60.6949191225620339],"rgb":[0.0666666666666666657,0.8,0.4],"xyz":[0.242210991426270755,0.442622396806573948,0.198377035150501202],"hpluv":[136.624435218533876,154.887514188688357,72.4034304155724],"hsluv":[136.624435218533876,98.5592219307307289,72.4034304155724]},"#11cc77":{"lch":[72.6507717937069657,81.3359578844494422,141.057484765247835],"luv":[72.6507717937069657,-63.261234579889333,51.1229326663597],"rgb":[0.0666666666666666657,0.8,0.466666666666666674],"xyz":[0.251525004446472689,0.446348002014654743,0.247430837056899111],"hpluv":[141.057484765247835,142.063228414279422,72.6507717937069657],"hsluv":[141.057484765247835,98.5990140248714795,72.6507717937069657]},"#11cc88":{"lch":[72.9448138267759703,74.0714793913140284,147.061910762632237],"luv":[72.9448138267759703,-62.1651250648783815,40.2750702654370087],"rgb":[0.0666666666666666657,0.8,0.533333333333333326],"xyz":[0.262665421548416489,0.450804168855432341,0.306103700460471306],"hpluv":[147.061910762632237,128.853412583983044,72.9448138267759703],"hsluv":[147.061910762632237,98.6438145792393755,72.9448138267759703]},"#11cc99":{"lch":[73.286985449663689,67.2264361640895203,155.087429558943455],"luv":[73.286985449663689,-60.9711251229010074,28.3181147072320734],"rgb":[0.0666666666666666657,0.8,0.6],"xyz":[0.275722385657489188,0.456026954499061499,0.374870378101589186],"hpluv":[155.087429558943455,116.399886643338576,73.286985449663689],"hsluv":[155.087429558943455,98.6928071276572609,73.286985449663689]},"#11ccaa":{"lch":[73.6783599756885224,61.6669147974486336,165.501022011161467],"luv":[73.6783599756885224,-59.7029534543248275,15.4390974304365383],"rgb":[0.0666666666666666657,0.8,0.66666666666666663],"xyz":[0.290780078622741744,0.462050031685162621,0.454174227718587786],"hpluv":[165.501022011161467,106.206622375239988,73.6783599756885224],"hsluv":[165.501022011161467,98.7450877264532494,73.6783599756885224]},"#11ccbb":{"lch":[74.1196780643846864,58.4137876130139801,178.201908682105085],"luv":[74.1196780643846864,-58.3850250256368426,1.83287644266177074],"rgb":[0.0666666666666666657,0.8,0.733333333333333282],"xyz":[0.307917670730078419,0.46890506852809738,0.544432212817229533],"hpluv":[178.201908682105085,100.004874571957387,74.1196780643846864],"hsluv":[178.201908682105085,98.7997230732590452,74.1196780643846864]},"#11cccc":{"lch":[74.6113685470067,58.3540675645504,192.177050630061103],"luv":[74.6113685470067,-57.0411279075965751,-12.3088150671508707],"rgb":[0.0666666666666666657,0.8,0.8],"xyz":[0.327210046173800118,0.476622018705586181,0.646038723487499689],"hpluv":[192.177050630061103,99.244272920381249,74.6113685470067],"hsluv":[192.177050630061103,98.8558017207376167,74.6113685470067]},"#11ccdd":{"lch":[75.1535687293590087,61.8087365466066316,205.702276410621693],"luv":[75.1535687293590087,-55.6933672323303881,-26.8061328768737823],"rgb":[0.0666666666666666657,0.8,0.866666666666666696],"xyz":[0.348728371724433717,0.485229348925839687,0.759368571387505686],"hpluv":[205.702276410621693,104.361325002032586,75.1535687293590087],"hsluv":[205.702276410621693,98.9124749687685352,75.1535687293590087]},"#11ccee":{"lch":[75.7461450403856418,68.3900229325434168,217.356690237772426],"luv":[75.7461450403856418,-54.3614174191380712,-41.4973677827407],"rgb":[0.0666666666666666657,0.8,0.933333333333333348],"xyz":[0.372540551990608426,0.494754221032309704,0.88477938745602891],"hpluv":[217.356690237772426,114.570165581500433,75.7461450403856418],"hsluv":[217.356690237772426,98.968985855810871,75.7461450403856418]},"#11ccff":{"lch":[76.3887144168947714,77.3228649101567811,226.666757851513921],"luv":[76.3887144168947714,-53.0620799518220849,-56.2426982736496726],"rgb":[0.0666666666666666657,0.8,1],"xyz":[0.398711600400439847,0.505222640396242428,1.02261357574781075],"hpluv":[226.666757851513921,131.304642630845649,76.3887144168947714],"hsluv":[226.666757851513921,99.9999999999969162,76.3887144168947714]},"#11dd00":{"lch":[77.1789729208637851,118.880191052714352,127.435820588671049],"luv":[77.1789729208637851,-72.2639845338074,94.3950017957916572],"rgb":[0.0666666666666666657,0.866666666666666696,0],"xyz":[0.260864802545785,0.518298306129860942,0.0862927536805420531],"hpluv":[127.435820588671049,210.367240137055802,77.1789729208637851],"hsluv":[127.435820588671049,100.000000000002302,77.1789729208637851]},"#11dd11":{"lch":[77.2032167276219,117.940964566200989,127.715012949240474],"luv":[77.2032167276219,-72.148538100067,93.2987651195291],"rgb":[0.0666666666666666657,0.866666666666666696,0.0666666666666666657],"xyz":[0.261876468045422084,0.51870297232971585,0.0916208586452976897],"hpluv":[127.715012949240474,208.973000844217893,77.2032167276219],"hsluv":[127.715012949240474,98.7048375113237739,77.2032167276219]},"#11dd22":{"lch":[77.2481249515021773,116.219968574506154,128.240715222563949],"luv":[77.2481249515021773,-71.936288170647174,91.2811674974022509],"rgb":[0.0666666666666666657,0.866666666666666696,0.133333333333333331],"xyz":[0.263751826183899141,0.519453115585106651,0.101497744841276882],"hpluv":[128.240715222563949,206.413967447437244,77.2481249515021773],"hsluv":[128.240715222563949,98.7114617332334348,77.2481249515021773]},"#11dd33":{"lch":[77.3219716074714114,113.443027450646795,129.129905575085218],"luv":[77.3219716074714114,-71.591714193384675,87.9996984996024],"rgb":[0.0666666666666666657,0.866666666666666696,0.2],"xyz":[0.266839576916356847,0.520688215878089777,0.117759898698888033],"hpluv":[129.129905575085218,202.273081507843187,77.3219716074714114],"hsluv":[129.129905575085218,98.7222220245650419,77.3219716074714114]},"#11dd44":{"lch":[77.4283833065767,109.557638087167803,130.467479357182611],"luv":[77.4283833065767,-71.1046976382296378,83.3486534805139],"rgb":[0.0666666666666666657,0.866666666666666696,0.266666666666666663],"xyz":[0.271297572160575762,0.522471413975777343,0.141238673651774671],"hpluv":[130.467479357182611,196.45511110559741,77.4283833065767],"hsluv":[130.467479357182611,98.737444128486942,77.4283833065767]},"#11dd55":{"lch":[77.5703274630482156,104.589066335953675,132.360930841179908],"luv":[77.5703274630482156,-70.4719757705036471,77.2824263854860192],"rgb":[0.0666666666666666657,0.866666666666666696,0.333333333333333315],"xyz":[0.277259986199937747,0.524856379591522093,0.172640720925748559],"hpluv":[132.360930841179908,188.974657198429043,77.5703274630482156],"hsluv":[132.360930841179908,98.7572451358875583,77.5703274630482156]},"#11dd66":{"lch":[77.7502795105496176,98.645271303892585,134.953797592355841],"luv":[77.7502795105496176,-69.6964700953403167,69.8089650895062732],"rgb":[0.0666666666666666657,0.866666666666666696,0.4],"xyz":[0.284844981973445932,0.527890377900925412,0.212588365332892548],"hpluv":[134.953797592355841,179.968822287066047,77.7502795105496176],"hsluv":[134.953797592355841,98.7815546318288114,77.7502795105496176]},"#11dd77":{"lch":[77.9703113413929714,91.9272626112809377,138.440937564009943],"luv":[77.9703113413929714,-68.7866216277350588,60.9837871589344616],"rgb":[0.0666666666666666657,0.866666666666666696,0.466666666666666674],"xyz":[0.294158994993647838,0.531615983109006263,0.261642167239290457],"hpluv":[138.440937564009943,169.723893920939275,77.9703113413929714],"hsluv":[138.440937564009943,98.8101351189296,77.9703113413929714]},"#11dd88":{"lch":[78.2321436880382919,84.7462074021241705,143.083472555296623],"luv":[78.2321436880382919,-67.7555613828794492,50.90288371727776],"rgb":[0.0666666666666666657,0.866666666666666696,0.533333333333333326],"xyz":[0.305299412095591638,0.536072149949783805,0.320315030642862597],"hpluv":[143.083472555296623,158.721324423216174,78.2321436880382919],"hsluv":[143.083472555296623,98.842607114853692,78.2321436880382919]},"#11dd99":{"lch":[78.5371801027425,77.5491781494168748,149.212222309724666],"luv":[78.5371801027425,-66.6201031545698,39.6942928810235145],"rgb":[0.0666666666666666657,0.866666666666666696,0.6],"xyz":[0.318356376204664393,0.541294935593413,0.389081708283980532],"hpluv":[149.212222309724666,147.710574340426348,78.5371801027425],"hsluv":[149.212222309724666,98.8784794246756746,78.5371801027425]},"#11ddaa":{"lch":[78.8865310768390771,70.9495750262721288,157.187121587348372],"luv":[78.8865310768390771,-65.3996173248364414,27.5087667875819264],"rgb":[0.0666666666666666657,0.866666666666666696,0.66666666666666663],"xyz":[0.333414069169916893,0.547318012779514085,0.468385557900979133],"hpluv":[157.187121587348372,137.807232739715857,78.8865310768390771],"hsluv":[157.187121587348372,98.9171828916172728,78.8865310768390771]},"#11ddbb":{"lch":[79.2810328759594398,65.7363215025974483,167.247861929956969],"luv":[79.2810328759594398,-64.1148749316804611,14.5102300873497096],"rgb":[0.0666666666666666657,0.866666666666666696,0.733333333333333282],"xyz":[0.350551661277253568,0.554173049622448843,0.558643542999620824],"hpluv":[167.247861929956969,130.572542181043048,79.2810328759594398],"hsluv":[167.247861929956969,98.9581049946704496,79.2810328759594398]},"#11ddcc":{"lch":[79.7212637056701396,62.7929370759791,179.209087927276954],"luv":[79.7212637056701396,-62.7869545437069903,0.866767416521026846],"rgb":[0.0666666666666666657,0.866666666666666696,0.8],"xyz":[0.369844036720975322,0.561889999799937701,0.660250053669891],"hpluv":[179.209087927276954,127.935201467087722,79.7212637056701396],"hsluv":[179.209087927276954,99.0006225499112,79.7212637056701396]},"#11dddd":{"lch":[80.2075587483664378,62.8503941808937796,192.177050630061132],"luv":[80.2075587483664378,-61.4362892449526,-13.2572400032679489],"rgb":[0.0666666666666666657,0.866666666666666696,0.866666666666666696],"xyz":[0.39136236227160881,0.570497330020191207,0.773579901569897],"hpluv":[192.177050630061132,131.767009082741112,80.2075587483664378],"hsluv":[192.177050630061132,99.0441302201931109,80.2075587483664378]},"#11ddee":{"lch":[80.7400249746916643,66.1629514334268691,204.757295278220823],"luv":[80.7400249746916643,-60.0819052998990202,-27.707414132610225],"rgb":[0.0666666666666666657,0.866666666666666696,0.933333333333333348],"xyz":[0.415174542537783575,0.580022202126661224,0.8989907176384202],"hpluv":[204.757295278220823,143.22067561241397,80.7400249746916643],"hsluv":[204.757295278220823,99.0880633014724594,80.7400249746916643]},"#11ddff":{"lch":[81.3185562290371848,72.4120095186264194,215.786316478612605],"luv":[81.3185562290371848,-58.7408753400219084,-42.3439332940817934],"rgb":[0.0666666666666666657,0.866666666666666696,1],"xyz":[0.441345590947614941,0.590490621490594,1.03682490593020216],"hpluv":[215.786316478612605,162.428807277722512,81.3185562290371848],"hsluv":[215.786316478612605,99.9999999999959925,81.3185562290371848]}} \ No newline at end of file diff --git a/vendor/github.com/lucasb-eyer/go-colorful/hsluv.go b/vendor/github.com/lucasb-eyer/go-colorful/hsluv.go deleted file mode 100644 index cc514882288..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/hsluv.go +++ /dev/null @@ -1,208 +0,0 @@ -package colorful - -import "math" - -// Source: https://github.com/hsluv/hsluv-go -// Under MIT License -// Modified so that Saturation and Luminance are in [0..1] instead of [0..100]. - -// HSLuv uses a rounded version of the D65. This has no impact on the final RGB -// values, but to keep high levels of accuracy for internal operations and when -// comparing to the test values, this modified white reference is used internally. -// -// See this GitHub thread for details on these values: -// -// https://github.com/hsluv/hsluv/issues/79 -var hSLuvD65 = [3]float64{0.95045592705167, 1.0, 1.089057750759878} - -func LuvLChToHSLuv(l, c, h float64) (float64, float64, float64) { - // [-1..1] but the code expects it to be [-100..100] - c *= 100.0 - l *= 100.0 - - var s, max float64 - if l > 99.9999999 || l < 0.00000001 { - s = 0.0 - } else { - max = maxChromaForLH(l, h) - s = c / max * 100.0 - } - return h, clamp01(s / 100.0), clamp01(l / 100.0) -} - -func HSLuvToLuvLCh(h, s, l float64) (float64, float64, float64) { - l *= 100.0 - s *= 100.0 - - var c, max float64 - if l > 99.9999999 || l < 0.00000001 { - c = 0.0 - } else { - max = maxChromaForLH(l, h) - c = max / 100.0 * s - } - - // c is [-100..100], but for LCh it's supposed to be almost [-1..1] - return clamp01(l / 100.0), c / 100.0, h -} - -func LuvLChToHPLuv(l, c, h float64) (float64, float64, float64) { - // [-1..1] but the code expects it to be [-100..100] - c *= 100.0 - l *= 100.0 - - var s, max float64 - if l > 99.9999999 || l < 0.00000001 { - s = 0.0 - } else { - max = maxSafeChromaForL(l) - s = c / max * 100.0 - } - return h, s / 100.0, l / 100.0 -} - -func HPLuvToLuvLCh(h, s, l float64) (float64, float64, float64) { - // [-1..1] but the code expects it to be [-100..100] - l *= 100.0 - s *= 100.0 - - var c, max float64 - if l > 99.9999999 || l < 0.00000001 { - c = 0.0 - } else { - max = maxSafeChromaForL(l) - c = max / 100.0 * s - } - return l / 100.0, c / 100.0, h -} - -// HSLuv creates a new Color from values in the HSLuv color space. -// Hue in [0..360], a Saturation [0..1], and a Luminance (lightness) in [0..1]. -// -// The returned color values are clamped (using .Clamped), so this will never output -// an invalid color. -func HSLuv(h, s, l float64) Color { - // HSLuv -> LuvLCh -> CIELUV -> CIEXYZ -> Linear RGB -> sRGB - l, u, v := LuvLChToLuv(HSLuvToLuvLCh(h, s, l)) - return LinearRgb(XyzToLinearRgb(LuvToXyzWhiteRef(l, u, v, hSLuvD65))).Clamped() -} - -// HPLuv creates a new Color from values in the HPLuv color space. -// Hue in [0..360], a Saturation [0..1], and a Luminance (lightness) in [0..1]. -// -// The returned color values are clamped (using .Clamped), so this will never output -// an invalid color. -func HPLuv(h, s, l float64) Color { - // HPLuv -> LuvLCh -> CIELUV -> CIEXYZ -> Linear RGB -> sRGB - l, u, v := LuvLChToLuv(HPLuvToLuvLCh(h, s, l)) - return LinearRgb(XyzToLinearRgb(LuvToXyzWhiteRef(l, u, v, hSLuvD65))).Clamped() -} - -// HSLuv returns the Hue, Saturation and Luminance of the color in the HSLuv -// color space. Hue in [0..360], a Saturation [0..1], and a Luminance -// (lightness) in [0..1]. -func (col Color) HSLuv() (h, s, l float64) { - // sRGB -> Linear RGB -> CIEXYZ -> CIELUV -> LuvLCh -> HSLuv - return LuvLChToHSLuv(col.LuvLChWhiteRef(hSLuvD65)) -} - -// HPLuv returns the Hue, Saturation and Luminance of the color in the HSLuv -// color space. Hue in [0..360], a Saturation [0..1], and a Luminance -// (lightness) in [0..1]. -// -// Note that HPLuv can only represent pastel colors, and so the Saturation -// value could be much larger than 1 for colors it can't represent. -func (col Color) HPLuv() (h, s, l float64) { - return LuvLChToHPLuv(col.LuvLChWhiteRef(hSLuvD65)) -} - -// DistanceHSLuv calculates Euclidean distance in the HSLuv colorspace. No idea -// how useful this is. -// -// The Hue value is divided by 100 before the calculation, so that H, S, and L -// have the same relative ranges. -func (c1 Color) DistanceHSLuv(c2 Color) float64 { - h1, s1, l1 := c1.HSLuv() - h2, s2, l2 := c2.HSLuv() - return math.Sqrt(sq((h1-h2)/100.0) + sq(s1-s2) + sq(l1-l2)) -} - -// DistanceHPLuv calculates Euclidean distance in the HPLuv colorspace. No idea -// how useful this is. -// -// The Hue value is divided by 100 before the calculation, so that H, S, and L -// have the same relative ranges. -func (c1 Color) DistanceHPLuv(c2 Color) float64 { - h1, s1, l1 := c1.HPLuv() - h2, s2, l2 := c2.HPLuv() - return math.Sqrt(sq((h1-h2)/100.0) + sq(s1-s2) + sq(l1-l2)) -} - -var m = [3][3]float64{ - {3.2409699419045214, -1.5373831775700935, -0.49861076029300328}, - {-0.96924363628087983, 1.8759675015077207, 0.041555057407175613}, - {0.055630079696993609, -0.20397695888897657, 1.0569715142428786}, -} - -const kappa = 903.2962962962963 -const epsilon = 0.0088564516790356308 - -func maxChromaForLH(l, h float64) float64 { - hRad := h / 360.0 * math.Pi * 2.0 - minLength := math.MaxFloat64 - for _, line := range getBounds(l) { - length := lengthOfRayUntilIntersect(hRad, line[0], line[1]) - if length > 0.0 && length < minLength { - minLength = length - } - } - return minLength -} - -func getBounds(l float64) [6][2]float64 { - var sub2 float64 - var ret [6][2]float64 - sub1 := math.Pow(l+16.0, 3.0) / 1560896.0 - if sub1 > epsilon { - sub2 = sub1 - } else { - sub2 = l / kappa - } - for i := range m { - for k := 0; k < 2; k++ { - top1 := (284517.0*m[i][0] - 94839.0*m[i][2]) * sub2 - top2 := (838422.0*m[i][2]+769860.0*m[i][1]+731718.0*m[i][0])*l*sub2 - 769860.0*float64(k)*l - bottom := (632260.0*m[i][2]-126452.0*m[i][1])*sub2 + 126452.0*float64(k) - ret[i*2+k][0] = top1 / bottom - ret[i*2+k][1] = top2 / bottom - } - } - return ret -} - -func lengthOfRayUntilIntersect(theta, x, y float64) (length float64) { - length = y / (math.Sin(theta) - x*math.Cos(theta)) - return -} - -func maxSafeChromaForL(l float64) float64 { - minLength := math.MaxFloat64 - for _, line := range getBounds(l) { - m1 := line[0] - b1 := line[1] - x := intersectLineLine(m1, b1, -1.0/m1, 0.0) - dist := distanceFromPole(x, b1+x*m1) - if dist < minLength { - minLength = dist - } - } - return minLength -} - -func intersectLineLine(x1, y1, x2, y2 float64) float64 { - return (y1 - y2) / (x2 - x1) -} - -func distanceFromPole(x, y float64) float64 { - return math.Sqrt(math.Pow(x, 2.0) + math.Pow(y, 2.0)) -} diff --git a/vendor/github.com/lucasb-eyer/go-colorful/rand.go b/vendor/github.com/lucasb-eyer/go-colorful/rand.go deleted file mode 100644 index d3a2d5b50ba..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/rand.go +++ /dev/null @@ -1,22 +0,0 @@ -package colorful - -import "math/rand" - -type RandInterface interface { - Float64() float64 - Intn(n int) int -} - -type defaultGlobalRand struct{} - -func (df defaultGlobalRand) Float64() float64 { - return rand.Float64() -} - -func (df defaultGlobalRand) Intn(n int) int { - return rand.Intn(n) -} - -func getDefaultGlobalRand() RandInterface { - return defaultGlobalRand{} -} diff --git a/vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go b/vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go deleted file mode 100644 index 6d8aa137e29..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go +++ /dev/null @@ -1,192 +0,0 @@ -// Largely inspired by the descriptions in http://lab.medialab.sciences-po.fr/iwanthue/ -// but written from scratch. - -package colorful - -import ( - "fmt" - "math" -) - -// The algorithm works in L*a*b* color space and converts to RGB in the end. -// L* in [0..1], a* and b* in [-1..1] -type lab_t struct { - L, A, B float64 -} - -type SoftPaletteSettings struct { - // A function which can be used to restrict the allowed color-space. - CheckColor func(l, a, b float64) bool - - // The higher, the better quality but the slower. Usually two figures. - Iterations int - - // Use up to 160000 or 8000 samples of the L*a*b* space (and thus calls to CheckColor). - // Set this to true only if your CheckColor shapes the Lab space weirdly. - ManySamples bool -} - -// Yeah, windows-stype Foo, FooEx, screw you golang... -// Uses K-means to cluster the color-space and return the means of the clusters -// as a new palette of distinctive colors. Falls back to K-medoid if the mean -// happens to fall outside of the color-space, which can only happen if you -// specify a CheckColor function. -func SoftPaletteExWithRand(colorsCount int, settings SoftPaletteSettings, rand RandInterface) ([]Color, error) { - - // Checks whether it's a valid RGB and also fulfills the potentially provided constraint. - check := func(col lab_t) bool { - c := Lab(col.L, col.A, col.B) - return c.IsValid() && (settings.CheckColor == nil || settings.CheckColor(col.L, col.A, col.B)) - } - - // Sample the color space. These will be the points k-means is run on. - dl := 0.05 - dab := 0.1 - if settings.ManySamples { - dl = 0.01 - dab = 0.05 - } - - samples := make([]lab_t, 0, int(1.0/dl*2.0/dab*2.0/dab)) - for l := 0.0; l <= 1.0; l += dl { - for a := -1.0; a <= 1.0; a += dab { - for b := -1.0; b <= 1.0; b += dab { - if check(lab_t{l, a, b}) { - samples = append(samples, lab_t{l, a, b}) - } - } - } - } - - // That would cause some infinite loops down there... - if len(samples) < colorsCount { - return nil, fmt.Errorf("palettegen: more colors requested (%v) than samples available (%v). Your requested color count may be wrong, you might want to use many samples or your constraint function makes the valid color space too small", colorsCount, len(samples)) - } else if len(samples) == colorsCount { - return labs2cols(samples), nil // Oops? - } - - // We take the initial means out of the samples, so they are in fact medoids. - // This helps us avoid infinite loops or arbitrary cutoffs with too restrictive constraints. - means := make([]lab_t, colorsCount) - for i := 0; i < colorsCount; i++ { - for means[i] = samples[rand.Intn(len(samples))]; in(means, i, means[i]); means[i] = samples[rand.Intn(len(samples))] { - } - } - - clusters := make([]int, len(samples)) - samples_used := make([]bool, len(samples)) - - // The actual k-means/medoid iterations - for i := 0; i < settings.Iterations; i++ { - // Reassigning the samples to clusters, i.e. to their closest mean. - // By the way, also check if any sample is used as a medoid and if so, mark that. - for isample, sample := range samples { - samples_used[isample] = false - mindist := math.Inf(+1) - for imean, mean := range means { - dist := lab_dist(sample, mean) - if dist < mindist { - mindist = dist - clusters[isample] = imean - } - - // Mark samples which are used as a medoid. - if lab_eq(sample, mean) { - samples_used[isample] = true - } - } - } - - // Compute new means according to the samples. - for imean := range means { - // The new mean is the average of all samples belonging to it. - nsamples := 0 - newmean := lab_t{0.0, 0.0, 0.0} - for isample, sample := range samples { - if clusters[isample] == imean { - nsamples++ - newmean.L += sample.L - newmean.A += sample.A - newmean.B += sample.B - } - } - if nsamples > 0 { - newmean.L /= float64(nsamples) - newmean.A /= float64(nsamples) - newmean.B /= float64(nsamples) - } else { - // That mean doesn't have any samples? Get a new mean from the sample list! - var inewmean int - for inewmean = rand.Intn(len(samples_used)); samples_used[inewmean]; inewmean = rand.Intn(len(samples_used)) { - } - newmean = samples[inewmean] - samples_used[inewmean] = true - } - - // But now we still need to check whether the new mean is an allowed color. - if nsamples > 0 && check(newmean) { - // It does, life's good (TM) - means[imean] = newmean - } else { - // New mean isn't an allowed color or doesn't have any samples! - // Switch to medoid mode and pick the closest (unused) sample. - // This should always find something thanks to len(samples) >= colorsCount - mindist := math.Inf(+1) - for isample, sample := range samples { - if !samples_used[isample] { - dist := lab_dist(sample, newmean) - if dist < mindist { - mindist = dist - newmean = sample - } - } - } - } - } - } - return labs2cols(means), nil -} - -func SoftPaletteEx(colorsCount int, settings SoftPaletteSettings) ([]Color, error) { - return SoftPaletteExWithRand(colorsCount, settings, getDefaultGlobalRand()) -} - -// A wrapper which uses common parameters. -func SoftPaletteWithRand(colorsCount int, rand RandInterface) ([]Color, error) { - return SoftPaletteExWithRand(colorsCount, SoftPaletteSettings{nil, 50, false}, rand) -} - -func SoftPalette(colorsCount int) ([]Color, error) { - return SoftPaletteWithRand(colorsCount, getDefaultGlobalRand()) -} - -func in(haystack []lab_t, upto int, needle lab_t) bool { - for i := 0; i < upto && i < len(haystack); i++ { - if haystack[i] == needle { - return true - } - } - return false -} - -const LAB_DELTA = 1e-6 - -func lab_eq(lab1, lab2 lab_t) bool { - return math.Abs(lab1.L-lab2.L) < LAB_DELTA && - math.Abs(lab1.A-lab2.A) < LAB_DELTA && - math.Abs(lab1.B-lab2.B) < LAB_DELTA -} - -// That's faster than using colorful's DistanceLab since we would have to -// convert back and forth for that. Here is no conversion. -func lab_dist(lab1, lab2 lab_t) float64 { - return math.Sqrt(sq(lab1.L-lab2.L) + sq(lab1.A-lab2.A) + sq(lab1.B-lab2.B)) -} - -func labs2cols(labs []lab_t) (cols []Color) { - cols = make([]Color, len(labs)) - for k, v := range labs { - cols[k] = Lab(v.L, v.A, v.B) - } - return cols -} diff --git a/vendor/github.com/lucasb-eyer/go-colorful/sort.go b/vendor/github.com/lucasb-eyer/go-colorful/sort.go deleted file mode 100644 index b1c1b6813dd..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/sort.go +++ /dev/null @@ -1,191 +0,0 @@ -// This file provides functions for sorting colors. - -package colorful - -import ( - "math" - "sort" -) - -// An element represents a single element of a set. It is used to -// implement a disjoint-set forest. -type element struct { - parent *element // Parent element - rank int // Rank (approximate depth) of the subtree with this element as root -} - -// newElement creates a singleton set and returns its sole element. -func newElement() *element { - s := &element{} - s.parent = s - return s -} - -// find returns an arbitrary element of a set when invoked on any element of -// the set, The important feature is that it returns the same value when -// invoked on any element of the set. Consequently, it can be used to test if -// two elements belong to the same set. -func (e *element) find() *element { - for e.parent != e { - e.parent = e.parent.parent - e = e.parent - } - return e -} - -// union establishes the union of two sets when given an element from each set. -// Afterwards, the original sets no longer exist as separate entities. -func union(e1, e2 *element) { - // Ensure the two elements aren't already part of the same union. - e1Root := e1.find() - e2Root := e2.find() - if e1Root == e2Root { - return - } - - // Create a union by making the shorter tree point to the root of the - // larger tree. - switch { - case e1Root.rank < e2Root.rank: - e1Root.parent = e2Root - case e1Root.rank > e2Root.rank: - e2Root.parent = e1Root - default: - e2Root.parent = e1Root - e1Root.rank++ - } -} - -// An edgeIdxs describes an edge in a graph or tree. The vertices in the edge -// are indexes into a list of Color values. -type edgeIdxs [2]int - -// An edgeDistance is a map from an edge (pair of indices) to a distance -// between the two vertices. -type edgeDistance map[edgeIdxs]float64 - -// allToAllDistancesCIEDE2000 computes the CIEDE2000 distance between each pair of -// colors. It returns a map from a pair of indices (u, v) with u < v to a -// distance. -func allToAllDistancesCIEDE2000(cs []Color) edgeDistance { - nc := len(cs) - m := make(edgeDistance, nc*nc) - for u := 0; u < nc-1; u++ { - for v := u + 1; v < nc; v++ { - m[edgeIdxs{u, v}] = cs[u].DistanceCIEDE2000(cs[v]) - } - } - return m -} - -// sortEdges sorts all edges in a distance map by increasing vertex distance. -func sortEdges(m edgeDistance) []edgeIdxs { - es := make([]edgeIdxs, 0, len(m)) - for uv := range m { - es = append(es, uv) - } - sort.Slice(es, func(i, j int) bool { - return m[es[i]] < m[es[j]] - }) - return es -} - -// minSpanTree computes a minimum spanning tree from a vertex count and a -// distance-sorted edge list. It returns the subset of edges that belong to -// the tree, including both (u, v) and (v, u) for each edge. -func minSpanTree(nc int, es []edgeIdxs) map[edgeIdxs]struct{} { - // Start with each vertex in its own set. - elts := make([]*element, nc) - for i := range elts { - elts[i] = newElement() - } - - // Run Kruskal's algorithm to construct a minimal spanning tree. - mst := make(map[edgeIdxs]struct{}, nc) - for _, uv := range es { - u, v := uv[0], uv[1] - if elts[u].find() == elts[v].find() { - continue // Same set: edge would introduce a cycle. - } - mst[uv] = struct{}{} - mst[edgeIdxs{v, u}] = struct{}{} - union(elts[u], elts[v]) - } - return mst -} - -// traverseMST walks a minimum spanning tree in prefix order. -func traverseMST(mst map[edgeIdxs]struct{}, root int) []int { - // Compute a list of neighbors for each vertex. - neighs := make(map[int][]int, len(mst)) - for uv := range mst { - u, v := uv[0], uv[1] - neighs[u] = append(neighs[u], v) - } - for u, vs := range neighs { - sort.Ints(vs) - copy(neighs[u], vs) - } - - // Walk the tree from a given vertex. - order := make([]int, 0, len(neighs)) - visited := make(map[int]bool, len(neighs)) - var walkFrom func(int) - walkFrom = func(r int) { - // Visit the starting vertex. - order = append(order, r) - visited[r] = true - - // Recursively visit each child in turn. - for _, c := range neighs[r] { - if !visited[c] { - walkFrom(c) - } - } - } - walkFrom(root) - return order -} - -// Sorted sorts a list of Color values. Sorting is not a well-defined operation -// for colors so the intention here primarily is to order colors so that the -// transition from one to the next is fairly smooth. -func Sorted(cs []Color) []Color { - // Do nothing in trivial cases. - newCs := make([]Color, len(cs)) - if len(cs) < 2 { - copy(newCs, cs) - return newCs - } - - // Compute the distance from each color to every other color. - dists := allToAllDistancesCIEDE2000(cs) - - // Produce a list of edges in increasing order of the distance between - // their vertices. - edges := sortEdges(dists) - - // Construct a minimum spanning tree from the list of edges. - mst := minSpanTree(len(cs), edges) - - // Find the darkest color in the list. - var black Color - var dIdx int // Index of darkest color - light := math.MaxFloat64 // Lightness of darkest color (distance from black) - for i, c := range cs { - d := black.DistanceCIEDE2000(c) - if d < light { - dIdx = i - light = d - } - } - - // Traverse the tree starting from the darkest color. - idxs := traverseMST(mst, dIdx) - - // Convert the index list to a list of colors, overwriting the input. - for i, idx := range idxs { - newCs[i] = cs[idx] - } - return newCs -} diff --git a/vendor/github.com/lucasb-eyer/go-colorful/warm_palettegen.go b/vendor/github.com/lucasb-eyer/go-colorful/warm_palettegen.go deleted file mode 100644 index d294fb41585..00000000000 --- a/vendor/github.com/lucasb-eyer/go-colorful/warm_palettegen.go +++ /dev/null @@ -1,29 +0,0 @@ -package colorful - -// Uses the HSV color space to generate colors with similar S,V but distributed -// evenly along their Hue. This is fast but not always pretty. -// If you've got time to spare, use Lab (the non-fast below). -func FastWarmPaletteWithRand(colorsCount int, rand RandInterface) (colors []Color) { - colors = make([]Color, colorsCount) - - for i := 0; i < colorsCount; i++ { - colors[i] = Hsv(float64(i)*(360.0/float64(colorsCount)), 0.55+rand.Float64()*0.2, 0.35+rand.Float64()*0.2) - } - return -} - -func FastWarmPalette(colorsCount int) (colors []Color) { - return FastWarmPaletteWithRand(colorsCount, getDefaultGlobalRand()) -} - -func WarmPaletteWithRand(colorsCount int, rand RandInterface) ([]Color, error) { - warmy := func(l, a, b float64) bool { - _, c, _ := LabToHcl(l, a, b) - return 0.1 <= c && c <= 0.4 && 0.2 <= l && l <= 0.5 - } - return SoftPaletteExWithRand(colorsCount, SoftPaletteSettings{warmy, 50, true}, rand) -} - -func WarmPalette(colorsCount int) ([]Color, error) { - return WarmPaletteWithRand(colorsCount, getDefaultGlobalRand()) -} diff --git a/vendor/github.com/mailru/easyjson/LICENSE b/vendor/github.com/mailru/easyjson/LICENSE deleted file mode 100644 index fbff658f70d..00000000000 --- a/vendor/github.com/mailru/easyjson/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2016 Mail.Ru Group - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/mailru/easyjson/buffer/pool.go b/vendor/github.com/mailru/easyjson/buffer/pool.go deleted file mode 100644 index 598a54af9db..00000000000 --- a/vendor/github.com/mailru/easyjson/buffer/pool.go +++ /dev/null @@ -1,278 +0,0 @@ -// Package buffer implements a buffer for serialization, consisting of a chain of []byte-s to -// reduce copying and to allow reuse of individual chunks. -package buffer - -import ( - "io" - "net" - "sync" -) - -// PoolConfig contains configuration for the allocation and reuse strategy. -type PoolConfig struct { - StartSize int // Minimum chunk size that is allocated. - PooledSize int // Minimum chunk size that is reused, reusing chunks too small will result in overhead. - MaxSize int // Maximum chunk size that will be allocated. -} - -var config = PoolConfig{ - StartSize: 128, - PooledSize: 512, - MaxSize: 32768, -} - -// Reuse pool: chunk size -> pool. -var buffers = map[int]*sync.Pool{} - -func initBuffers() { - for l := config.PooledSize; l <= config.MaxSize; l *= 2 { - buffers[l] = new(sync.Pool) - } -} - -func init() { - initBuffers() -} - -// Init sets up a non-default pooling and allocation strategy. Should be run before serialization is done. -func Init(cfg PoolConfig) { - config = cfg - initBuffers() -} - -// putBuf puts a chunk to reuse pool if it can be reused. -func putBuf(buf []byte) { - size := cap(buf) - if size < config.PooledSize { - return - } - if c := buffers[size]; c != nil { - c.Put(buf[:0]) - } -} - -// getBuf gets a chunk from reuse pool or creates a new one if reuse failed. -func getBuf(size int) []byte { - if size >= config.PooledSize { - if c := buffers[size]; c != nil { - v := c.Get() - if v != nil { - return v.([]byte) - } - } - } - return make([]byte, 0, size) -} - -// Buffer is a buffer optimized for serialization without extra copying. -type Buffer struct { - - // Buf is the current chunk that can be used for serialization. - Buf []byte - - toPool []byte - bufs [][]byte -} - -// EnsureSpace makes sure that the current chunk contains at least s free bytes, -// possibly creating a new chunk. -func (b *Buffer) EnsureSpace(s int) { - if cap(b.Buf)-len(b.Buf) < s { - b.ensureSpaceSlow(s) - } -} - -func (b *Buffer) ensureSpaceSlow(s int) { - l := len(b.Buf) - if l > 0 { - if cap(b.toPool) != cap(b.Buf) { - // Chunk was reallocated, toPool can be pooled. - putBuf(b.toPool) - } - if cap(b.bufs) == 0 { - b.bufs = make([][]byte, 0, 8) - } - b.bufs = append(b.bufs, b.Buf) - l = cap(b.toPool) * 2 - } else { - l = config.StartSize - } - - if l > config.MaxSize { - l = config.MaxSize - } - b.Buf = getBuf(l) - b.toPool = b.Buf -} - -// AppendByte appends a single byte to buffer. -func (b *Buffer) AppendByte(data byte) { - b.EnsureSpace(1) - b.Buf = append(b.Buf, data) -} - -// AppendBytes appends a byte slice to buffer. -func (b *Buffer) AppendBytes(data []byte) { - if len(data) <= cap(b.Buf)-len(b.Buf) { - b.Buf = append(b.Buf, data...) // fast path - } else { - b.appendBytesSlow(data) - } -} - -func (b *Buffer) appendBytesSlow(data []byte) { - for len(data) > 0 { - b.EnsureSpace(1) - - sz := cap(b.Buf) - len(b.Buf) - if sz > len(data) { - sz = len(data) - } - - b.Buf = append(b.Buf, data[:sz]...) - data = data[sz:] - } -} - -// AppendString appends a string to buffer. -func (b *Buffer) AppendString(data string) { - if len(data) <= cap(b.Buf)-len(b.Buf) { - b.Buf = append(b.Buf, data...) // fast path - } else { - b.appendStringSlow(data) - } -} - -func (b *Buffer) appendStringSlow(data string) { - for len(data) > 0 { - b.EnsureSpace(1) - - sz := cap(b.Buf) - len(b.Buf) - if sz > len(data) { - sz = len(data) - } - - b.Buf = append(b.Buf, data[:sz]...) - data = data[sz:] - } -} - -// Size computes the size of a buffer by adding sizes of every chunk. -func (b *Buffer) Size() int { - size := len(b.Buf) - for _, buf := range b.bufs { - size += len(buf) - } - return size -} - -// DumpTo outputs the contents of a buffer to a writer and resets the buffer. -func (b *Buffer) DumpTo(w io.Writer) (written int, err error) { - bufs := net.Buffers(b.bufs) - if len(b.Buf) > 0 { - bufs = append(bufs, b.Buf) - } - n, err := bufs.WriteTo(w) - - for _, buf := range b.bufs { - putBuf(buf) - } - putBuf(b.toPool) - - b.bufs = nil - b.Buf = nil - b.toPool = nil - - return int(n), err -} - -// BuildBytes creates a single byte slice with all the contents of the buffer. Data is -// copied if it does not fit in a single chunk. You can optionally provide one byte -// slice as argument that it will try to reuse. -func (b *Buffer) BuildBytes(reuse ...[]byte) []byte { - if len(b.bufs) == 0 { - ret := b.Buf - b.toPool = nil - b.Buf = nil - return ret - } - - var ret []byte - size := b.Size() - - // If we got a buffer as argument and it is big enough, reuse it. - if len(reuse) == 1 && cap(reuse[0]) >= size { - ret = reuse[0][:0] - } else { - ret = make([]byte, 0, size) - } - for _, buf := range b.bufs { - ret = append(ret, buf...) - putBuf(buf) - } - - ret = append(ret, b.Buf...) - putBuf(b.toPool) - - b.bufs = nil - b.toPool = nil - b.Buf = nil - - return ret -} - -type readCloser struct { - offset int - bufs [][]byte -} - -func (r *readCloser) Read(p []byte) (n int, err error) { - for _, buf := range r.bufs { - // Copy as much as we can. - x := copy(p[n:], buf[r.offset:]) - n += x // Increment how much we filled. - - // Did we empty the whole buffer? - if r.offset+x == len(buf) { - // On to the next buffer. - r.offset = 0 - r.bufs = r.bufs[1:] - - // We can release this buffer. - putBuf(buf) - } else { - r.offset += x - } - - if n == len(p) { - break - } - } - // No buffers left or nothing read? - if len(r.bufs) == 0 { - err = io.EOF - } - return -} - -func (r *readCloser) Close() error { - // Release all remaining buffers. - for _, buf := range r.bufs { - putBuf(buf) - } - // In case Close gets called multiple times. - r.bufs = nil - - return nil -} - -// ReadCloser creates an io.ReadCloser with all the contents of the buffer. -func (b *Buffer) ReadCloser() io.ReadCloser { - ret := &readCloser{0, append(b.bufs, b.Buf)} - - b.bufs = nil - b.toPool = nil - b.Buf = nil - - return ret -} diff --git a/vendor/github.com/mailru/easyjson/jwriter/writer.go b/vendor/github.com/mailru/easyjson/jwriter/writer.go deleted file mode 100644 index 2c5b20105bb..00000000000 --- a/vendor/github.com/mailru/easyjson/jwriter/writer.go +++ /dev/null @@ -1,405 +0,0 @@ -// Package jwriter contains a JSON writer. -package jwriter - -import ( - "io" - "strconv" - "unicode/utf8" - - "github.com/mailru/easyjson/buffer" -) - -// Flags describe various encoding options. The behavior may be actually implemented in the encoder, but -// Flags field in Writer is used to set and pass them around. -type Flags int - -const ( - NilMapAsEmpty Flags = 1 << iota // Encode nil map as '{}' rather than 'null'. - NilSliceAsEmpty // Encode nil slice as '[]' rather than 'null'. -) - -// Writer is a JSON writer. -type Writer struct { - Flags Flags - - Error error - Buffer buffer.Buffer - NoEscapeHTML bool -} - -// Size returns the size of the data that was written out. -func (w *Writer) Size() int { - return w.Buffer.Size() -} - -// DumpTo outputs the data to given io.Writer, resetting the buffer. -func (w *Writer) DumpTo(out io.Writer) (written int, err error) { - return w.Buffer.DumpTo(out) -} - -// BuildBytes returns writer data as a single byte slice. You can optionally provide one byte slice -// as argument that it will try to reuse. -func (w *Writer) BuildBytes(reuse ...[]byte) ([]byte, error) { - if w.Error != nil { - return nil, w.Error - } - - return w.Buffer.BuildBytes(reuse...), nil -} - -// ReadCloser returns an io.ReadCloser that can be used to read the data. -// ReadCloser also resets the buffer. -func (w *Writer) ReadCloser() (io.ReadCloser, error) { - if w.Error != nil { - return nil, w.Error - } - - return w.Buffer.ReadCloser(), nil -} - -// RawByte appends raw binary data to the buffer. -func (w *Writer) RawByte(c byte) { - w.Buffer.AppendByte(c) -} - -// RawByte appends raw binary data to the buffer. -func (w *Writer) RawString(s string) { - w.Buffer.AppendString(s) -} - -// Raw appends raw binary data to the buffer or sets the error if it is given. Useful for -// calling with results of MarshalJSON-like functions. -func (w *Writer) Raw(data []byte, err error) { - switch { - case w.Error != nil: - return - case err != nil: - w.Error = err - case len(data) > 0: - w.Buffer.AppendBytes(data) - default: - w.RawString("null") - } -} - -// RawText encloses raw binary data in quotes and appends in to the buffer. -// Useful for calling with results of MarshalText-like functions. -func (w *Writer) RawText(data []byte, err error) { - switch { - case w.Error != nil: - return - case err != nil: - w.Error = err - case len(data) > 0: - w.String(string(data)) - default: - w.RawString("null") - } -} - -// Base64Bytes appends data to the buffer after base64 encoding it -func (w *Writer) Base64Bytes(data []byte) { - if data == nil { - w.Buffer.AppendString("null") - return - } - w.Buffer.AppendByte('"') - w.base64(data) - w.Buffer.AppendByte('"') -} - -func (w *Writer) Uint8(n uint8) { - w.Buffer.EnsureSpace(3) - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) -} - -func (w *Writer) Uint16(n uint16) { - w.Buffer.EnsureSpace(5) - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) -} - -func (w *Writer) Uint32(n uint32) { - w.Buffer.EnsureSpace(10) - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) -} - -func (w *Writer) Uint(n uint) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) -} - -func (w *Writer) Uint64(n uint64) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, n, 10) -} - -func (w *Writer) Int8(n int8) { - w.Buffer.EnsureSpace(4) - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) -} - -func (w *Writer) Int16(n int16) { - w.Buffer.EnsureSpace(6) - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) -} - -func (w *Writer) Int32(n int32) { - w.Buffer.EnsureSpace(11) - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) -} - -func (w *Writer) Int(n int) { - w.Buffer.EnsureSpace(21) - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) -} - -func (w *Writer) Int64(n int64) { - w.Buffer.EnsureSpace(21) - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, n, 10) -} - -func (w *Writer) Uint8Str(n uint8) { - w.Buffer.EnsureSpace(3) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Uint16Str(n uint16) { - w.Buffer.EnsureSpace(5) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Uint32Str(n uint32) { - w.Buffer.EnsureSpace(10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) UintStr(n uint) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Uint64Str(n uint64) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, n, 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) UintptrStr(n uintptr) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Int8Str(n int8) { - w.Buffer.EnsureSpace(4) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Int16Str(n int16) { - w.Buffer.EnsureSpace(6) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Int32Str(n int32) { - w.Buffer.EnsureSpace(11) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) IntStr(n int) { - w.Buffer.EnsureSpace(21) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Int64Str(n int64) { - w.Buffer.EnsureSpace(21) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, n, 10) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Float32(n float32) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 32) -} - -func (w *Writer) Float32Str(n float32) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 32) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Float64(n float64) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, n, 'g', -1, 64) -} - -func (w *Writer) Float64Str(n float64) { - w.Buffer.EnsureSpace(20) - w.Buffer.Buf = append(w.Buffer.Buf, '"') - w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 64) - w.Buffer.Buf = append(w.Buffer.Buf, '"') -} - -func (w *Writer) Bool(v bool) { - w.Buffer.EnsureSpace(5) - if v { - w.Buffer.Buf = append(w.Buffer.Buf, "true"...) - } else { - w.Buffer.Buf = append(w.Buffer.Buf, "false"...) - } -} - -const chars = "0123456789abcdef" - -func getTable(falseValues ...int) [128]bool { - table := [128]bool{} - - for i := 0; i < 128; i++ { - table[i] = true - } - - for _, v := range falseValues { - table[v] = false - } - - return table -} - -var ( - htmlEscapeTable = getTable(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, '"', '&', '<', '>', '\\') - htmlNoEscapeTable = getTable(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, '"', '\\') -) - -func (w *Writer) String(s string) { - w.Buffer.AppendByte('"') - - // Portions of the string that contain no escapes are appended as - // byte slices. - - p := 0 // last non-escape symbol - - escapeTable := &htmlEscapeTable - if w.NoEscapeHTML { - escapeTable = &htmlNoEscapeTable - } - - for i := 0; i < len(s); { - c := s[i] - - if c < utf8.RuneSelf { - if escapeTable[c] { - // single-width character, no escaping is required - i++ - continue - } - - w.Buffer.AppendString(s[p:i]) - switch c { - case '\t': - w.Buffer.AppendString(`\t`) - case '\r': - w.Buffer.AppendString(`\r`) - case '\n': - w.Buffer.AppendString(`\n`) - case '\\': - w.Buffer.AppendString(`\\`) - case '"': - w.Buffer.AppendString(`\"`) - default: - w.Buffer.AppendString(`\u00`) - w.Buffer.AppendByte(chars[c>>4]) - w.Buffer.AppendByte(chars[c&0xf]) - } - - i++ - p = i - continue - } - - // broken utf - runeValue, runeWidth := utf8.DecodeRuneInString(s[i:]) - if runeValue == utf8.RuneError && runeWidth == 1 { - w.Buffer.AppendString(s[p:i]) - w.Buffer.AppendString(`\ufffd`) - i++ - p = i - continue - } - - // jsonp stuff - tab separator and line separator - if runeValue == '\u2028' || runeValue == '\u2029' { - w.Buffer.AppendString(s[p:i]) - w.Buffer.AppendString(`\u202`) - w.Buffer.AppendByte(chars[runeValue&0xf]) - i += runeWidth - p = i - continue - } - i += runeWidth - } - w.Buffer.AppendString(s[p:]) - w.Buffer.AppendByte('"') -} - -const encode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -const padChar = '=' - -func (w *Writer) base64(in []byte) { - - if len(in) == 0 { - return - } - - w.Buffer.EnsureSpace(((len(in)-1)/3 + 1) * 4) - - si := 0 - n := (len(in) / 3) * 3 - - for si < n { - // Convert 3x 8bit source bytes into 4 bytes - val := uint(in[si+0])<<16 | uint(in[si+1])<<8 | uint(in[si+2]) - - w.Buffer.Buf = append(w.Buffer.Buf, encode[val>>18&0x3F], encode[val>>12&0x3F], encode[val>>6&0x3F], encode[val&0x3F]) - - si += 3 - } - - remain := len(in) - si - if remain == 0 { - return - } - - // Add the remaining small block - val := uint(in[si+0]) << 16 - if remain == 2 { - val |= uint(in[si+1]) << 8 - } - - w.Buffer.Buf = append(w.Buffer.Buf, encode[val>>18&0x3F], encode[val>>12&0x3F]) - - switch remain { - case 2: - w.Buffer.Buf = append(w.Buffer.Buf, encode[val>>6&0x3F], byte(padChar)) - case 1: - w.Buffer.Buf = append(w.Buffer.Buf, byte(padChar), byte(padChar)) - } -} diff --git a/vendor/github.com/mattn/go-colorable/LICENSE b/vendor/github.com/mattn/go-colorable/LICENSE deleted file mode 100644 index 91b5cef30eb..00000000000 --- a/vendor/github.com/mattn/go-colorable/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Yasuhiro Matsumoto - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/mattn/go-colorable/README.md b/vendor/github.com/mattn/go-colorable/README.md deleted file mode 100644 index ca0483711c9..00000000000 --- a/vendor/github.com/mattn/go-colorable/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# go-colorable - -[![Build Status](https://github.com/mattn/go-colorable/workflows/test/badge.svg)](https://github.com/mattn/go-colorable/actions?query=workflow%3Atest) -[![Codecov](https://codecov.io/gh/mattn/go-colorable/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-colorable) -[![GoDoc](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable) -[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable) - -Colorable writer for windows. - -For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.) -This package is possible to handle escape sequence for ansi color on windows. - -## Too Bad! - -![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png) - - -## So Good! - -![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png) - -## Usage - -```go -logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) -logrus.SetOutput(colorable.NewColorableStdout()) - -logrus.Info("succeeded") -logrus.Warn("not correct") -logrus.Error("something error") -logrus.Fatal("panic") -``` - -You can compile above code on non-windows OSs. - -## Installation - -``` -$ go get github.com/mattn/go-colorable -``` - -# License - -MIT - -# Author - -Yasuhiro Matsumoto (a.k.a mattn) diff --git a/vendor/github.com/mattn/go-colorable/colorable_appengine.go b/vendor/github.com/mattn/go-colorable/colorable_appengine.go deleted file mode 100644 index 416d1bbbf83..00000000000 --- a/vendor/github.com/mattn/go-colorable/colorable_appengine.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build appengine -// +build appengine - -package colorable - -import ( - "io" - "os" - - _ "github.com/mattn/go-isatty" -) - -// NewColorable returns new instance of Writer which handles escape sequence. -func NewColorable(file *os.File) io.Writer { - if file == nil { - panic("nil passed instead of *os.File to NewColorable()") - } - - return file -} - -// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout. -func NewColorableStdout() io.Writer { - return os.Stdout -} - -// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr. -func NewColorableStderr() io.Writer { - return os.Stderr -} - -// EnableColorsStdout enable colors if possible. -func EnableColorsStdout(enabled *bool) func() { - if enabled != nil { - *enabled = true - } - return func() {} -} diff --git a/vendor/github.com/mattn/go-colorable/colorable_others.go b/vendor/github.com/mattn/go-colorable/colorable_others.go deleted file mode 100644 index 766d94603ac..00000000000 --- a/vendor/github.com/mattn/go-colorable/colorable_others.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build !windows && !appengine -// +build !windows,!appengine - -package colorable - -import ( - "io" - "os" - - _ "github.com/mattn/go-isatty" -) - -// NewColorable returns new instance of Writer which handles escape sequence. -func NewColorable(file *os.File) io.Writer { - if file == nil { - panic("nil passed instead of *os.File to NewColorable()") - } - - return file -} - -// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout. -func NewColorableStdout() io.Writer { - return os.Stdout -} - -// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr. -func NewColorableStderr() io.Writer { - return os.Stderr -} - -// EnableColorsStdout enable colors if possible. -func EnableColorsStdout(enabled *bool) func() { - if enabled != nil { - *enabled = true - } - return func() {} -} diff --git a/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/mattn/go-colorable/colorable_windows.go deleted file mode 100644 index 1846ad5ab41..00000000000 --- a/vendor/github.com/mattn/go-colorable/colorable_windows.go +++ /dev/null @@ -1,1047 +0,0 @@ -//go:build windows && !appengine -// +build windows,!appengine - -package colorable - -import ( - "bytes" - "io" - "math" - "os" - "strconv" - "strings" - "sync" - "syscall" - "unsafe" - - "github.com/mattn/go-isatty" -) - -const ( - foregroundBlue = 0x1 - foregroundGreen = 0x2 - foregroundRed = 0x4 - foregroundIntensity = 0x8 - foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) - backgroundBlue = 0x10 - backgroundGreen = 0x20 - backgroundRed = 0x40 - backgroundIntensity = 0x80 - backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) - commonLvbUnderscore = 0x8000 - - cENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 -) - -const ( - genericRead = 0x80000000 - genericWrite = 0x40000000 -) - -const ( - consoleTextmodeBuffer = 0x1 -) - -type wchar uint16 -type short int16 -type dword uint32 -type word uint16 - -type coord struct { - x short - y short -} - -type smallRect struct { - left short - top short - right short - bottom short -} - -type consoleScreenBufferInfo struct { - size coord - cursorPosition coord - attributes word - window smallRect - maximumWindowSize coord -} - -type consoleCursorInfo struct { - size dword - visible int32 -} - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") - procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") - procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") - procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") - procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") - procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") - procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") - procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW") - procGetConsoleMode = kernel32.NewProc("GetConsoleMode") - procSetConsoleMode = kernel32.NewProc("SetConsoleMode") - procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer") -) - -// Writer provides colorable Writer to the console -type Writer struct { - out io.Writer - handle syscall.Handle - althandle syscall.Handle - oldattr word - oldpos coord - rest bytes.Buffer - mutex sync.Mutex -} - -// NewColorable returns new instance of Writer which handles escape sequence from File. -func NewColorable(file *os.File) io.Writer { - if file == nil { - panic("nil passed instead of *os.File to NewColorable()") - } - - if isatty.IsTerminal(file.Fd()) { - var mode uint32 - if r, _, _ := procGetConsoleMode.Call(file.Fd(), uintptr(unsafe.Pointer(&mode))); r != 0 && mode&cENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 { - return file - } - var csbi consoleScreenBufferInfo - handle := syscall.Handle(file.Fd()) - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}} - } - return file -} - -// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout. -func NewColorableStdout() io.Writer { - return NewColorable(os.Stdout) -} - -// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr. -func NewColorableStderr() io.Writer { - return NewColorable(os.Stderr) -} - -var color256 = map[int]int{ - 0: 0x000000, - 1: 0x800000, - 2: 0x008000, - 3: 0x808000, - 4: 0x000080, - 5: 0x800080, - 6: 0x008080, - 7: 0xc0c0c0, - 8: 0x808080, - 9: 0xff0000, - 10: 0x00ff00, - 11: 0xffff00, - 12: 0x0000ff, - 13: 0xff00ff, - 14: 0x00ffff, - 15: 0xffffff, - 16: 0x000000, - 17: 0x00005f, - 18: 0x000087, - 19: 0x0000af, - 20: 0x0000d7, - 21: 0x0000ff, - 22: 0x005f00, - 23: 0x005f5f, - 24: 0x005f87, - 25: 0x005faf, - 26: 0x005fd7, - 27: 0x005fff, - 28: 0x008700, - 29: 0x00875f, - 30: 0x008787, - 31: 0x0087af, - 32: 0x0087d7, - 33: 0x0087ff, - 34: 0x00af00, - 35: 0x00af5f, - 36: 0x00af87, - 37: 0x00afaf, - 38: 0x00afd7, - 39: 0x00afff, - 40: 0x00d700, - 41: 0x00d75f, - 42: 0x00d787, - 43: 0x00d7af, - 44: 0x00d7d7, - 45: 0x00d7ff, - 46: 0x00ff00, - 47: 0x00ff5f, - 48: 0x00ff87, - 49: 0x00ffaf, - 50: 0x00ffd7, - 51: 0x00ffff, - 52: 0x5f0000, - 53: 0x5f005f, - 54: 0x5f0087, - 55: 0x5f00af, - 56: 0x5f00d7, - 57: 0x5f00ff, - 58: 0x5f5f00, - 59: 0x5f5f5f, - 60: 0x5f5f87, - 61: 0x5f5faf, - 62: 0x5f5fd7, - 63: 0x5f5fff, - 64: 0x5f8700, - 65: 0x5f875f, - 66: 0x5f8787, - 67: 0x5f87af, - 68: 0x5f87d7, - 69: 0x5f87ff, - 70: 0x5faf00, - 71: 0x5faf5f, - 72: 0x5faf87, - 73: 0x5fafaf, - 74: 0x5fafd7, - 75: 0x5fafff, - 76: 0x5fd700, - 77: 0x5fd75f, - 78: 0x5fd787, - 79: 0x5fd7af, - 80: 0x5fd7d7, - 81: 0x5fd7ff, - 82: 0x5fff00, - 83: 0x5fff5f, - 84: 0x5fff87, - 85: 0x5fffaf, - 86: 0x5fffd7, - 87: 0x5fffff, - 88: 0x870000, - 89: 0x87005f, - 90: 0x870087, - 91: 0x8700af, - 92: 0x8700d7, - 93: 0x8700ff, - 94: 0x875f00, - 95: 0x875f5f, - 96: 0x875f87, - 97: 0x875faf, - 98: 0x875fd7, - 99: 0x875fff, - 100: 0x878700, - 101: 0x87875f, - 102: 0x878787, - 103: 0x8787af, - 104: 0x8787d7, - 105: 0x8787ff, - 106: 0x87af00, - 107: 0x87af5f, - 108: 0x87af87, - 109: 0x87afaf, - 110: 0x87afd7, - 111: 0x87afff, - 112: 0x87d700, - 113: 0x87d75f, - 114: 0x87d787, - 115: 0x87d7af, - 116: 0x87d7d7, - 117: 0x87d7ff, - 118: 0x87ff00, - 119: 0x87ff5f, - 120: 0x87ff87, - 121: 0x87ffaf, - 122: 0x87ffd7, - 123: 0x87ffff, - 124: 0xaf0000, - 125: 0xaf005f, - 126: 0xaf0087, - 127: 0xaf00af, - 128: 0xaf00d7, - 129: 0xaf00ff, - 130: 0xaf5f00, - 131: 0xaf5f5f, - 132: 0xaf5f87, - 133: 0xaf5faf, - 134: 0xaf5fd7, - 135: 0xaf5fff, - 136: 0xaf8700, - 137: 0xaf875f, - 138: 0xaf8787, - 139: 0xaf87af, - 140: 0xaf87d7, - 141: 0xaf87ff, - 142: 0xafaf00, - 143: 0xafaf5f, - 144: 0xafaf87, - 145: 0xafafaf, - 146: 0xafafd7, - 147: 0xafafff, - 148: 0xafd700, - 149: 0xafd75f, - 150: 0xafd787, - 151: 0xafd7af, - 152: 0xafd7d7, - 153: 0xafd7ff, - 154: 0xafff00, - 155: 0xafff5f, - 156: 0xafff87, - 157: 0xafffaf, - 158: 0xafffd7, - 159: 0xafffff, - 160: 0xd70000, - 161: 0xd7005f, - 162: 0xd70087, - 163: 0xd700af, - 164: 0xd700d7, - 165: 0xd700ff, - 166: 0xd75f00, - 167: 0xd75f5f, - 168: 0xd75f87, - 169: 0xd75faf, - 170: 0xd75fd7, - 171: 0xd75fff, - 172: 0xd78700, - 173: 0xd7875f, - 174: 0xd78787, - 175: 0xd787af, - 176: 0xd787d7, - 177: 0xd787ff, - 178: 0xd7af00, - 179: 0xd7af5f, - 180: 0xd7af87, - 181: 0xd7afaf, - 182: 0xd7afd7, - 183: 0xd7afff, - 184: 0xd7d700, - 185: 0xd7d75f, - 186: 0xd7d787, - 187: 0xd7d7af, - 188: 0xd7d7d7, - 189: 0xd7d7ff, - 190: 0xd7ff00, - 191: 0xd7ff5f, - 192: 0xd7ff87, - 193: 0xd7ffaf, - 194: 0xd7ffd7, - 195: 0xd7ffff, - 196: 0xff0000, - 197: 0xff005f, - 198: 0xff0087, - 199: 0xff00af, - 200: 0xff00d7, - 201: 0xff00ff, - 202: 0xff5f00, - 203: 0xff5f5f, - 204: 0xff5f87, - 205: 0xff5faf, - 206: 0xff5fd7, - 207: 0xff5fff, - 208: 0xff8700, - 209: 0xff875f, - 210: 0xff8787, - 211: 0xff87af, - 212: 0xff87d7, - 213: 0xff87ff, - 214: 0xffaf00, - 215: 0xffaf5f, - 216: 0xffaf87, - 217: 0xffafaf, - 218: 0xffafd7, - 219: 0xffafff, - 220: 0xffd700, - 221: 0xffd75f, - 222: 0xffd787, - 223: 0xffd7af, - 224: 0xffd7d7, - 225: 0xffd7ff, - 226: 0xffff00, - 227: 0xffff5f, - 228: 0xffff87, - 229: 0xffffaf, - 230: 0xffffd7, - 231: 0xffffff, - 232: 0x080808, - 233: 0x121212, - 234: 0x1c1c1c, - 235: 0x262626, - 236: 0x303030, - 237: 0x3a3a3a, - 238: 0x444444, - 239: 0x4e4e4e, - 240: 0x585858, - 241: 0x626262, - 242: 0x6c6c6c, - 243: 0x767676, - 244: 0x808080, - 245: 0x8a8a8a, - 246: 0x949494, - 247: 0x9e9e9e, - 248: 0xa8a8a8, - 249: 0xb2b2b2, - 250: 0xbcbcbc, - 251: 0xc6c6c6, - 252: 0xd0d0d0, - 253: 0xdadada, - 254: 0xe4e4e4, - 255: 0xeeeeee, -} - -// `\033]0;TITLESTR\007` -func doTitleSequence(er *bytes.Reader) error { - var c byte - var err error - - c, err = er.ReadByte() - if err != nil { - return err - } - if c != '0' && c != '2' { - return nil - } - c, err = er.ReadByte() - if err != nil { - return err - } - if c != ';' { - return nil - } - title := make([]byte, 0, 80) - for { - c, err = er.ReadByte() - if err != nil { - return err - } - if c == 0x07 || c == '\n' { - break - } - title = append(title, c) - } - if len(title) > 0 { - title8, err := syscall.UTF16PtrFromString(string(title)) - if err == nil { - procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8))) - } - } - return nil -} - -// returns Atoi(s) unless s == "" in which case it returns def -func atoiWithDefault(s string, def int) (int, error) { - if s == "" { - return def, nil - } - return strconv.Atoi(s) -} - -// Write writes data on console -func (w *Writer) Write(data []byte) (n int, err error) { - w.mutex.Lock() - defer w.mutex.Unlock() - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) - - handle := w.handle - - var er *bytes.Reader - if w.rest.Len() > 0 { - var rest bytes.Buffer - w.rest.WriteTo(&rest) - w.rest.Reset() - rest.Write(data) - er = bytes.NewReader(rest.Bytes()) - } else { - er = bytes.NewReader(data) - } - var plaintext bytes.Buffer -loop: - for { - c1, err := er.ReadByte() - if err != nil { - plaintext.WriteTo(w.out) - break loop - } - if c1 != 0x1b { - plaintext.WriteByte(c1) - continue - } - _, err = plaintext.WriteTo(w.out) - if err != nil { - break loop - } - c2, err := er.ReadByte() - if err != nil { - break loop - } - - switch c2 { - case '>': - continue - case ']': - w.rest.WriteByte(c1) - w.rest.WriteByte(c2) - er.WriteTo(&w.rest) - if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 { - break loop - } - er = bytes.NewReader(w.rest.Bytes()[2:]) - err := doTitleSequence(er) - if err != nil { - break loop - } - w.rest.Reset() - continue - // https://github.com/mattn/go-colorable/issues/27 - case '7': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - w.oldpos = csbi.cursorPosition - continue - case '8': - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) - continue - case 0x5b: - // execute part after switch - default: - continue - } - - w.rest.WriteByte(c1) - w.rest.WriteByte(c2) - er.WriteTo(&w.rest) - - var buf bytes.Buffer - var m byte - for i, c := range w.rest.Bytes()[2:] { - if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { - m = c - er = bytes.NewReader(w.rest.Bytes()[2+i+1:]) - w.rest.Reset() - break - } - buf.Write([]byte(string(c))) - } - if m == 0 { - break loop - } - - switch m { - case 'A': - n, err = atoiWithDefault(buf.String(), 1) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.y -= short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'B': - n, err = atoiWithDefault(buf.String(), 1) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.y += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'C': - n, err = atoiWithDefault(buf.String(), 1) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'D': - n, err = atoiWithDefault(buf.String(), 1) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x -= short(n) - if csbi.cursorPosition.x < 0 { - csbi.cursorPosition.x = 0 - } - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'E': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x = 0 - csbi.cursorPosition.y += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'F': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x = 0 - csbi.cursorPosition.y -= short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'G': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - if n < 1 { - n = 1 - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x = short(n - 1) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'H', 'f': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - if buf.Len() > 0 { - token := strings.Split(buf.String(), ";") - switch len(token) { - case 1: - n1, err := strconv.Atoi(token[0]) - if err != nil { - continue - } - csbi.cursorPosition.y = short(n1 - 1) - case 2: - n1, err := strconv.Atoi(token[0]) - if err != nil { - continue - } - n2, err := strconv.Atoi(token[1]) - if err != nil { - continue - } - csbi.cursorPosition.x = short(n2 - 1) - csbi.cursorPosition.y = short(n1 - 1) - } - } else { - csbi.cursorPosition.y = 0 - } - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'J': - n := 0 - if buf.Len() > 0 { - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - } - var count, written dword - var cursor coord - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - switch n { - case 0: - cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} - count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x) - case 1: - cursor = coord{x: csbi.window.left, y: csbi.window.top} - count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.window.top-csbi.cursorPosition.y)*dword(csbi.size.x) - case 2: - cursor = coord{x: csbi.window.left, y: csbi.window.top} - count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x) - } - procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - case 'K': - n := 0 - if buf.Len() > 0 { - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - var cursor coord - var count, written dword - switch n { - case 0: - cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} - count = dword(csbi.size.x - csbi.cursorPosition.x) - case 1: - cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y} - count = dword(csbi.size.x - csbi.cursorPosition.x) - case 2: - cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y} - count = dword(csbi.size.x) - } - procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - case 'X': - n := 0 - if buf.Len() > 0 { - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - var cursor coord - var written dword - cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} - procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(n), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(n), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - case 'm': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - attr := csbi.attributes - cs := buf.String() - if cs == "" { - procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(w.oldattr)) - continue - } - token := strings.Split(cs, ";") - for i := 0; i < len(token); i++ { - ns := token[i] - if n, err = strconv.Atoi(ns); err == nil { - switch { - case n == 0 || n == 100: - attr = w.oldattr - case n == 4: - attr |= commonLvbUnderscore - case (1 <= n && n <= 3) || n == 5: - attr |= foregroundIntensity - case n == 7 || n == 27: - attr = - (attr &^ (foregroundMask | backgroundMask)) | - ((attr & foregroundMask) << 4) | - ((attr & backgroundMask) >> 4) - case n == 22: - attr &^= foregroundIntensity - case n == 24: - attr &^= commonLvbUnderscore - case 30 <= n && n <= 37: - attr &= backgroundMask - if (n-30)&1 != 0 { - attr |= foregroundRed - } - if (n-30)&2 != 0 { - attr |= foregroundGreen - } - if (n-30)&4 != 0 { - attr |= foregroundBlue - } - case n == 38: // set foreground color. - if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { - if n256, err := strconv.Atoi(token[i+2]); err == nil { - if n256foreAttr == nil { - n256setup() - } - attr &= backgroundMask - attr |= n256foreAttr[n256%len(n256foreAttr)] - i += 2 - } - } else if len(token) == 5 && token[i+1] == "2" { - var r, g, b int - r, _ = strconv.Atoi(token[i+2]) - g, _ = strconv.Atoi(token[i+3]) - b, _ = strconv.Atoi(token[i+4]) - i += 4 - if r > 127 { - attr |= foregroundRed - } - if g > 127 { - attr |= foregroundGreen - } - if b > 127 { - attr |= foregroundBlue - } - } else { - attr = attr & (w.oldattr & backgroundMask) - } - case n == 39: // reset foreground color. - attr &= backgroundMask - attr |= w.oldattr & foregroundMask - case 40 <= n && n <= 47: - attr &= foregroundMask - if (n-40)&1 != 0 { - attr |= backgroundRed - } - if (n-40)&2 != 0 { - attr |= backgroundGreen - } - if (n-40)&4 != 0 { - attr |= backgroundBlue - } - case n == 48: // set background color. - if i < len(token)-2 && token[i+1] == "5" { - if n256, err := strconv.Atoi(token[i+2]); err == nil { - if n256backAttr == nil { - n256setup() - } - attr &= foregroundMask - attr |= n256backAttr[n256%len(n256backAttr)] - i += 2 - } - } else if len(token) == 5 && token[i+1] == "2" { - var r, g, b int - r, _ = strconv.Atoi(token[i+2]) - g, _ = strconv.Atoi(token[i+3]) - b, _ = strconv.Atoi(token[i+4]) - i += 4 - if r > 127 { - attr |= backgroundRed - } - if g > 127 { - attr |= backgroundGreen - } - if b > 127 { - attr |= backgroundBlue - } - } else { - attr = attr & (w.oldattr & foregroundMask) - } - case n == 49: // reset foreground color. - attr &= foregroundMask - attr |= w.oldattr & backgroundMask - case 90 <= n && n <= 97: - attr = (attr & backgroundMask) - attr |= foregroundIntensity - if (n-90)&1 != 0 { - attr |= foregroundRed - } - if (n-90)&2 != 0 { - attr |= foregroundGreen - } - if (n-90)&4 != 0 { - attr |= foregroundBlue - } - case 100 <= n && n <= 107: - attr = (attr & foregroundMask) - attr |= backgroundIntensity - if (n-100)&1 != 0 { - attr |= backgroundRed - } - if (n-100)&2 != 0 { - attr |= backgroundGreen - } - if (n-100)&4 != 0 { - attr |= backgroundBlue - } - } - procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(attr)) - } - } - case 'h': - var ci consoleCursorInfo - cs := buf.String() - if cs == "5>" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 0 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?25" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 1 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?1049" { - if w.althandle == 0 { - h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0) - w.althandle = syscall.Handle(h) - if w.althandle != 0 { - handle = w.althandle - } - } - } - case 'l': - var ci consoleCursorInfo - cs := buf.String() - if cs == "5>" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 1 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?25" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 0 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?1049" { - if w.althandle != 0 { - syscall.CloseHandle(w.althandle) - w.althandle = 0 - handle = w.handle - } - } - case 's': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - w.oldpos = csbi.cursorPosition - case 'u': - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) - } - } - - return len(data), nil -} - -type consoleColor struct { - rgb int - red bool - green bool - blue bool - intensity bool -} - -func (c consoleColor) foregroundAttr() (attr word) { - if c.red { - attr |= foregroundRed - } - if c.green { - attr |= foregroundGreen - } - if c.blue { - attr |= foregroundBlue - } - if c.intensity { - attr |= foregroundIntensity - } - return -} - -func (c consoleColor) backgroundAttr() (attr word) { - if c.red { - attr |= backgroundRed - } - if c.green { - attr |= backgroundGreen - } - if c.blue { - attr |= backgroundBlue - } - if c.intensity { - attr |= backgroundIntensity - } - return -} - -var color16 = []consoleColor{ - {0x000000, false, false, false, false}, - {0x000080, false, false, true, false}, - {0x008000, false, true, false, false}, - {0x008080, false, true, true, false}, - {0x800000, true, false, false, false}, - {0x800080, true, false, true, false}, - {0x808000, true, true, false, false}, - {0xc0c0c0, true, true, true, false}, - {0x808080, false, false, false, true}, - {0x0000ff, false, false, true, true}, - {0x00ff00, false, true, false, true}, - {0x00ffff, false, true, true, true}, - {0xff0000, true, false, false, true}, - {0xff00ff, true, false, true, true}, - {0xffff00, true, true, false, true}, - {0xffffff, true, true, true, true}, -} - -type hsv struct { - h, s, v float32 -} - -func (a hsv) dist(b hsv) float32 { - dh := a.h - b.h - switch { - case dh > 0.5: - dh = 1 - dh - case dh < -0.5: - dh = -1 - dh - } - ds := a.s - b.s - dv := a.v - b.v - return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) -} - -func toHSV(rgb int) hsv { - r, g, b := float32((rgb&0xFF0000)>>16)/256.0, - float32((rgb&0x00FF00)>>8)/256.0, - float32(rgb&0x0000FF)/256.0 - min, max := minmax3f(r, g, b) - h := max - min - if h > 0 { - if max == r { - h = (g - b) / h - if h < 0 { - h += 6 - } - } else if max == g { - h = 2 + (b-r)/h - } else { - h = 4 + (r-g)/h - } - } - h /= 6.0 - s := max - min - if max != 0 { - s /= max - } - v := max - return hsv{h: h, s: s, v: v} -} - -type hsvTable []hsv - -func toHSVTable(rgbTable []consoleColor) hsvTable { - t := make(hsvTable, len(rgbTable)) - for i, c := range rgbTable { - t[i] = toHSV(c.rgb) - } - return t -} - -func (t hsvTable) find(rgb int) consoleColor { - hsv := toHSV(rgb) - n := 7 - l := float32(5.0) - for i, p := range t { - d := hsv.dist(p) - if d < l { - l, n = d, i - } - } - return color16[n] -} - -func minmax3f(a, b, c float32) (min, max float32) { - if a < b { - if b < c { - return a, c - } else if a < c { - return a, b - } else { - return c, b - } - } else { - if a < c { - return b, c - } else if b < c { - return b, a - } else { - return c, a - } - } -} - -var n256foreAttr []word -var n256backAttr []word - -func n256setup() { - n256foreAttr = make([]word, 256) - n256backAttr = make([]word, 256) - t := toHSVTable(color16) - for i, rgb := range color256 { - c := t.find(rgb) - n256foreAttr[i] = c.foregroundAttr() - n256backAttr[i] = c.backgroundAttr() - } -} - -// EnableColorsStdout enable colors if possible. -func EnableColorsStdout(enabled *bool) func() { - var mode uint32 - h := os.Stdout.Fd() - if r, _, _ := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&mode))); r != 0 { - if r, _, _ = procSetConsoleMode.Call(h, uintptr(mode|cENABLE_VIRTUAL_TERMINAL_PROCESSING)); r != 0 { - if enabled != nil { - *enabled = true - } - return func() { - procSetConsoleMode.Call(h, uintptr(mode)) - } - } - } - if enabled != nil { - *enabled = true - } - return func() {} -} diff --git a/vendor/github.com/mattn/go-colorable/go.test.sh b/vendor/github.com/mattn/go-colorable/go.test.sh deleted file mode 100644 index 012162b077c..00000000000 --- a/vendor/github.com/mattn/go-colorable/go.test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -e -echo "" > coverage.txt - -for d in $(go list ./... | grep -v vendor); do - go test -race -coverprofile=profile.out -covermode=atomic "$d" - if [ -f profile.out ]; then - cat profile.out >> coverage.txt - rm profile.out - fi -done diff --git a/vendor/github.com/mattn/go-colorable/noncolorable.go b/vendor/github.com/mattn/go-colorable/noncolorable.go deleted file mode 100644 index 3df68f360e6..00000000000 --- a/vendor/github.com/mattn/go-colorable/noncolorable.go +++ /dev/null @@ -1,59 +0,0 @@ -package colorable - -import ( - "bytes" - "io" -) - -// NonColorable holds writer but removes escape sequence. -type NonColorable struct { - out io.Writer -} - -// NewNonColorable returns new instance of Writer which removes escape sequence from Writer. -func NewNonColorable(w io.Writer) io.Writer { - return &NonColorable{out: w} -} - -// Write writes data on console -func (w *NonColorable) Write(data []byte) (n int, err error) { - er := bytes.NewReader(data) - var plaintext bytes.Buffer -loop: - for { - c1, err := er.ReadByte() - if err != nil { - plaintext.WriteTo(w.out) - break loop - } - if c1 != 0x1b { - plaintext.WriteByte(c1) - continue - } - _, err = plaintext.WriteTo(w.out) - if err != nil { - break loop - } - c2, err := er.ReadByte() - if err != nil { - break loop - } - if c2 != 0x5b { - continue - } - - var buf bytes.Buffer - for { - c, err := er.ReadByte() - if err != nil { - break loop - } - if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { - break - } - buf.Write([]byte(string(c))) - } - } - - return len(data), nil -} diff --git a/vendor/github.com/mattn/go-isatty/LICENSE b/vendor/github.com/mattn/go-isatty/LICENSE deleted file mode 100644 index 65dc692b6b1..00000000000 --- a/vendor/github.com/mattn/go-isatty/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -Copyright (c) Yasuhiro MATSUMOTO - -MIT License (Expat) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/mattn/go-isatty/README.md b/vendor/github.com/mattn/go-isatty/README.md deleted file mode 100644 index 38418353e31..00000000000 --- a/vendor/github.com/mattn/go-isatty/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# go-isatty - -[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty) -[![Codecov](https://codecov.io/gh/mattn/go-isatty/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-isatty) -[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master) -[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty) - -isatty for golang - -## Usage - -```go -package main - -import ( - "fmt" - "github.com/mattn/go-isatty" - "os" -) - -func main() { - if isatty.IsTerminal(os.Stdout.Fd()) { - fmt.Println("Is Terminal") - } else if isatty.IsCygwinTerminal(os.Stdout.Fd()) { - fmt.Println("Is Cygwin/MSYS2 Terminal") - } else { - fmt.Println("Is Not Terminal") - } -} -``` - -## Installation - -``` -$ go get github.com/mattn/go-isatty -``` - -## License - -MIT - -## Author - -Yasuhiro Matsumoto (a.k.a mattn) - -## Thanks - -* k-takata: base idea for IsCygwinTerminal - - https://github.com/k-takata/go-iscygpty diff --git a/vendor/github.com/mattn/go-isatty/doc.go b/vendor/github.com/mattn/go-isatty/doc.go deleted file mode 100644 index 17d4f90ebcc..00000000000 --- a/vendor/github.com/mattn/go-isatty/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package isatty implements interface to isatty -package isatty diff --git a/vendor/github.com/mattn/go-isatty/go.test.sh b/vendor/github.com/mattn/go-isatty/go.test.sh deleted file mode 100644 index 012162b077c..00000000000 --- a/vendor/github.com/mattn/go-isatty/go.test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -e -echo "" > coverage.txt - -for d in $(go list ./... | grep -v vendor); do - go test -race -coverprofile=profile.out -covermode=atomic "$d" - if [ -f profile.out ]; then - cat profile.out >> coverage.txt - rm profile.out - fi -done diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go deleted file mode 100644 index 39bbcf00f0c..00000000000 --- a/vendor/github.com/mattn/go-isatty/isatty_bsd.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build (darwin || freebsd || openbsd || netbsd || dragonfly) && !appengine -// +build darwin freebsd openbsd netbsd dragonfly -// +build !appengine - -package isatty - -import "golang.org/x/sys/unix" - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - _, err := unix.IoctlGetTermios(int(fd), unix.TIOCGETA) - return err == nil -} - -// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 -// terminal. This is also always false on this environment. -func IsCygwinTerminal(fd uintptr) bool { - return false -} diff --git a/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go deleted file mode 100644 index 31503226f6c..00000000000 --- a/vendor/github.com/mattn/go-isatty/isatty_others.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build appengine || js || nacl || wasm -// +build appengine js nacl wasm - -package isatty - -// IsTerminal returns true if the file descriptor is terminal which -// is always false on js and appengine classic which is a sandboxed PaaS. -func IsTerminal(fd uintptr) bool { - return false -} - -// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 -// terminal. This is also always false on this environment. -func IsCygwinTerminal(fd uintptr) bool { - return false -} diff --git a/vendor/github.com/mattn/go-isatty/isatty_plan9.go b/vendor/github.com/mattn/go-isatty/isatty_plan9.go deleted file mode 100644 index bae7f9bb3dc..00000000000 --- a/vendor/github.com/mattn/go-isatty/isatty_plan9.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build plan9 -// +build plan9 - -package isatty - -import ( - "syscall" -) - -// IsTerminal returns true if the given file descriptor is a terminal. -func IsTerminal(fd uintptr) bool { - path, err := syscall.Fd2path(int(fd)) - if err != nil { - return false - } - return path == "/dev/cons" || path == "/mnt/term/dev/cons" -} - -// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 -// terminal. This is also always false on this environment. -func IsCygwinTerminal(fd uintptr) bool { - return false -} diff --git a/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/mattn/go-isatty/isatty_solaris.go deleted file mode 100644 index 0c3acf2dc28..00000000000 --- a/vendor/github.com/mattn/go-isatty/isatty_solaris.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build solaris && !appengine -// +build solaris,!appengine - -package isatty - -import ( - "golang.org/x/sys/unix" -) - -// IsTerminal returns true if the given file descriptor is a terminal. -// see: https://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/isatty.c -func IsTerminal(fd uintptr) bool { - _, err := unix.IoctlGetTermio(int(fd), unix.TCGETA) - return err == nil -} - -// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 -// terminal. This is also always false on this environment. -func IsCygwinTerminal(fd uintptr) bool { - return false -} diff --git a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go deleted file mode 100644 index 67787657fb2..00000000000 --- a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build (linux || aix || zos) && !appengine -// +build linux aix zos -// +build !appengine - -package isatty - -import "golang.org/x/sys/unix" - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - _, err := unix.IoctlGetTermios(int(fd), unix.TCGETS) - return err == nil -} - -// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 -// terminal. This is also always false on this environment. -func IsCygwinTerminal(fd uintptr) bool { - return false -} diff --git a/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/mattn/go-isatty/isatty_windows.go deleted file mode 100644 index 8e3c99171bf..00000000000 --- a/vendor/github.com/mattn/go-isatty/isatty_windows.go +++ /dev/null @@ -1,125 +0,0 @@ -//go:build windows && !appengine -// +build windows,!appengine - -package isatty - -import ( - "errors" - "strings" - "syscall" - "unicode/utf16" - "unsafe" -) - -const ( - objectNameInfo uintptr = 1 - fileNameInfo = 2 - fileTypePipe = 3 -) - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - ntdll = syscall.NewLazyDLL("ntdll.dll") - procGetConsoleMode = kernel32.NewProc("GetConsoleMode") - procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") - procGetFileType = kernel32.NewProc("GetFileType") - procNtQueryObject = ntdll.NewProc("NtQueryObject") -) - -func init() { - // Check if GetFileInformationByHandleEx is available. - if procGetFileInformationByHandleEx.Find() != nil { - procGetFileInformationByHandleEx = nil - } -} - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - var st uint32 - r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) - return r != 0 && e == 0 -} - -// Check pipe name is used for cygwin/msys2 pty. -// Cygwin/MSYS2 PTY has a name like: -// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master -func isCygwinPipeName(name string) bool { - token := strings.Split(name, "-") - if len(token) < 5 { - return false - } - - if token[0] != `\msys` && - token[0] != `\cygwin` && - token[0] != `\Device\NamedPipe\msys` && - token[0] != `\Device\NamedPipe\cygwin` { - return false - } - - if token[1] == "" { - return false - } - - if !strings.HasPrefix(token[2], "pty") { - return false - } - - if token[3] != `from` && token[3] != `to` { - return false - } - - if token[4] != "master" { - return false - } - - return true -} - -// getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler -// since GetFileInformationByHandleEx is not available under windows Vista and still some old fashion -// guys are using Windows XP, this is a workaround for those guys, it will also work on system from -// Windows vista to 10 -// see https://stackoverflow.com/a/18792477 for details -func getFileNameByHandle(fd uintptr) (string, error) { - if procNtQueryObject == nil { - return "", errors.New("ntdll.dll: NtQueryObject not supported") - } - - var buf [4 + syscall.MAX_PATH]uint16 - var result int - r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5, - fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0) - if r != 0 { - return "", e - } - return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil -} - -// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 -// terminal. -func IsCygwinTerminal(fd uintptr) bool { - if procGetFileInformationByHandleEx == nil { - name, err := getFileNameByHandle(fd) - if err != nil { - return false - } - return isCygwinPipeName(name) - } - - // Cygwin/msys's pty is a pipe. - ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0) - if ft != fileTypePipe || e != 0 { - return false - } - - var buf [2 + syscall.MAX_PATH]uint16 - r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), - 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)), - uintptr(len(buf)*2), 0, 0) - if r == 0 || e != 0 { - return false - } - - l := *(*uint32)(unsafe.Pointer(&buf)) - return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2]))) -} diff --git a/vendor/github.com/mattn/go-runewidth/LICENSE b/vendor/github.com/mattn/go-runewidth/LICENSE deleted file mode 100644 index 91b5cef30eb..00000000000 --- a/vendor/github.com/mattn/go-runewidth/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Yasuhiro Matsumoto - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/mattn/go-runewidth/README.md b/vendor/github.com/mattn/go-runewidth/README.md deleted file mode 100644 index 5e2cfd98cb6..00000000000 --- a/vendor/github.com/mattn/go-runewidth/README.md +++ /dev/null @@ -1,27 +0,0 @@ -go-runewidth -============ - -[![Build Status](https://github.com/mattn/go-runewidth/workflows/test/badge.svg?branch=master)](https://github.com/mattn/go-runewidth/actions?query=workflow%3Atest) -[![Codecov](https://codecov.io/gh/mattn/go-runewidth/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-runewidth) -[![GoDoc](https://godoc.org/github.com/mattn/go-runewidth?status.svg)](http://godoc.org/github.com/mattn/go-runewidth) -[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-runewidth)](https://goreportcard.com/report/github.com/mattn/go-runewidth) - -Provides functions to get fixed width of the character or string. - -Usage ------ - -```go -runewidth.StringWidth("つのだ☆HIRO") == 12 -``` - - -Author ------- - -Yasuhiro Matsumoto - -License -------- - -under the MIT License: http://mattn.mit-license.org/2013 diff --git a/vendor/github.com/mattn/go-runewidth/benchstat.txt b/vendor/github.com/mattn/go-runewidth/benchstat.txt deleted file mode 100644 index a9efdbde37f..00000000000 --- a/vendor/github.com/mattn/go-runewidth/benchstat.txt +++ /dev/null @@ -1,43 +0,0 @@ -goos: darwin -goarch: arm64 -pkg: github.com/mattn/go-runewidth -cpu: Apple M2 - │ old.txt │ new.txt │ - │ sec/op │ sec/op vs base │ -String1WidthAll/regular-8 108.92m ± 0% 35.09m ± 3% -67.78% (p=0.002 n=6) -String1WidthAll/lut-8 93.97m ± 0% 18.70m ± 0% -80.10% (p=0.002 n=6) -String1Width768/regular-8 60.62µ ± 1% 11.54µ ± 0% -80.97% (p=0.002 n=6) -String1Width768/lut-8 60.66µ ± 1% 11.43µ ± 0% -81.16% (p=0.002 n=6) -String1WidthAllEastAsian/regular-8 115.13m ± 1% 40.79m ± 8% -64.57% (p=0.002 n=6) -String1WidthAllEastAsian/lut-8 93.65m ± 0% 18.70m ± 2% -80.03% (p=0.002 n=6) -String1Width768EastAsian/regular-8 75.32µ ± 0% 23.49µ ± 0% -68.82% (p=0.002 n=6) -String1Width768EastAsian/lut-8 60.76µ ± 0% 11.50µ ± 0% -81.07% (p=0.002 n=6) -geomean 2.562m 604.5µ -76.41% - - │ old.txt │ new.txt │ - │ B/op │ B/op vs base │ -String1WidthAll/regular-8 106.3Mi ± 0% 0.0Mi ± 0% -100.00% (p=0.002 n=6) -String1WidthAll/lut-8 106.3Mi ± 0% 0.0Mi ± 0% -100.00% (p=0.002 n=6) -String1Width768/regular-8 75.00Ki ± 0% 0.00Ki ± 0% -100.00% (p=0.002 n=6) -String1Width768/lut-8 75.00Ki ± 0% 0.00Ki ± 0% -100.00% (p=0.002 n=6) -String1WidthAllEastAsian/regular-8 106.3Mi ± 0% 0.0Mi ± 0% -100.00% (p=0.002 n=6) -String1WidthAllEastAsian/lut-8 106.3Mi ± 0% 0.0Mi ± 0% -100.00% (p=0.002 n=6) -String1Width768EastAsian/regular-8 75.00Ki ± 0% 0.00Ki ± 0% -100.00% (p=0.002 n=6) -String1Width768EastAsian/lut-8 75.00Ki ± 0% 0.00Ki ± 0% -100.00% (p=0.002 n=6) -geomean 2.790Mi ? ¹ ² -¹ summaries must be >0 to compute geomean -² ratios must be >0 to compute geomean - - │ old.txt │ new.txt │ - │ allocs/op │ allocs/op vs base │ -String1WidthAll/regular-8 3.342M ± 0% 0.000M ± 0% -100.00% (p=0.002 n=6) -String1WidthAll/lut-8 3.342M ± 0% 0.000M ± 0% -100.00% (p=0.002 n=6) -String1Width768/regular-8 2.304k ± 0% 0.000k ± 0% -100.00% (p=0.002 n=6) -String1Width768/lut-8 2.304k ± 0% 0.000k ± 0% -100.00% (p=0.002 n=6) -String1WidthAllEastAsian/regular-8 3.342M ± 0% 0.000M ± 0% -100.00% (p=0.002 n=6) -String1WidthAllEastAsian/lut-8 3.342M ± 0% 0.000M ± 0% -100.00% (p=0.002 n=6) -String1Width768EastAsian/regular-8 2.304k ± 0% 0.000k ± 0% -100.00% (p=0.002 n=6) -String1Width768EastAsian/lut-8 2.304k ± 0% 0.000k ± 0% -100.00% (p=0.002 n=6) -geomean 87.75k ? ¹ ² -¹ summaries must be >0 to compute geomean -² ratios must be >0 to compute geomean diff --git a/vendor/github.com/mattn/go-runewidth/new.txt b/vendor/github.com/mattn/go-runewidth/new.txt deleted file mode 100644 index 8890712562f..00000000000 --- a/vendor/github.com/mattn/go-runewidth/new.txt +++ /dev/null @@ -1,54 +0,0 @@ -goos: darwin -goarch: arm64 -pkg: github.com/mattn/go-runewidth -cpu: Apple M2 -BenchmarkString1WidthAll/regular-8 33 35033923 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAll/regular-8 33 34965112 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAll/regular-8 33 36307234 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAll/regular-8 33 35007705 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAll/regular-8 33 35154182 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAll/regular-8 34 35155400 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAll/lut-8 63 18688500 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAll/lut-8 63 18712474 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAll/lut-8 63 18700211 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAll/lut-8 62 18694179 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAll/lut-8 62 18708392 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAll/lut-8 63 18770608 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/regular-8 104137 11526 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/regular-8 103986 11540 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/regular-8 104079 11552 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/regular-8 103963 11530 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/regular-8 103714 11538 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/regular-8 104181 11537 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/lut-8 105150 11420 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/lut-8 104778 11423 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/lut-8 105069 11422 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/lut-8 105127 11475 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/lut-8 104742 11433 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768/lut-8 105163 11432 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 28 40723347 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 28 40790299 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 28 40801338 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 28 40798216 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 28 44135253 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 28 40779546 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 62 18694165 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 62 18685047 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 62 18689273 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 62 19150346 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 63 19126154 ns/op 0 B/op 0 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 62 18712619 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/regular-8 50775 23595 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/regular-8 51061 23563 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/regular-8 51057 23492 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/regular-8 51138 23445 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/regular-8 51195 23469 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/regular-8 51087 23482 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/lut-8 104559 11549 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/lut-8 104508 11483 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/lut-8 104296 11503 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/lut-8 104606 11485 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/lut-8 104588 11495 ns/op 0 B/op 0 allocs/op -BenchmarkString1Width768EastAsian/lut-8 104602 11518 ns/op 0 B/op 0 allocs/op -PASS -ok github.com/mattn/go-runewidth 64.455s diff --git a/vendor/github.com/mattn/go-runewidth/old.txt b/vendor/github.com/mattn/go-runewidth/old.txt deleted file mode 100644 index 5b9ac164683..00000000000 --- a/vendor/github.com/mattn/go-runewidth/old.txt +++ /dev/null @@ -1,54 +0,0 @@ -goos: darwin -goarch: arm64 -pkg: github.com/mattn/go-runewidth -cpu: Apple M2 -BenchmarkString1WidthAll/regular-8 10 108559258 ns/op 111412145 B/op 3342342 allocs/op -BenchmarkString1WidthAll/regular-8 10 108968079 ns/op 111412364 B/op 3342343 allocs/op -BenchmarkString1WidthAll/regular-8 10 108890338 ns/op 111412388 B/op 3342344 allocs/op -BenchmarkString1WidthAll/regular-8 10 108940704 ns/op 111412584 B/op 3342346 allocs/op -BenchmarkString1WidthAll/regular-8 10 108632796 ns/op 111412348 B/op 3342343 allocs/op -BenchmarkString1WidthAll/regular-8 10 109354546 ns/op 111412777 B/op 3342343 allocs/op -BenchmarkString1WidthAll/lut-8 12 93844406 ns/op 111412569 B/op 3342345 allocs/op -BenchmarkString1WidthAll/lut-8 12 93991080 ns/op 111412512 B/op 3342344 allocs/op -BenchmarkString1WidthAll/lut-8 12 93980632 ns/op 111412413 B/op 3342343 allocs/op -BenchmarkString1WidthAll/lut-8 12 94004083 ns/op 111412396 B/op 3342343 allocs/op -BenchmarkString1WidthAll/lut-8 12 93959795 ns/op 111412445 B/op 3342343 allocs/op -BenchmarkString1WidthAll/lut-8 12 93846198 ns/op 111412556 B/op 3342345 allocs/op -BenchmarkString1Width768/regular-8 19785 60696 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768/regular-8 19824 60520 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768/regular-8 19832 60547 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768/regular-8 19778 60543 ns/op 76800 B/op 2304 allocs/op -BenchmarkString1Width768/regular-8 19842 61142 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768/regular-8 19780 60696 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768/lut-8 19598 61161 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768/lut-8 19731 60707 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768/lut-8 19738 60626 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768/lut-8 19764 60670 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768/lut-8 19797 60642 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768/lut-8 19738 60608 ns/op 76800 B/op 2304 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 9 115080431 ns/op 111412458 B/op 3342345 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 9 114908880 ns/op 111412476 B/op 3342345 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 9 115077134 ns/op 111412540 B/op 3342345 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 9 115175292 ns/op 111412467 B/op 3342345 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 9 115792653 ns/op 111412362 B/op 3342344 allocs/op -BenchmarkString1WidthAllEastAsian/regular-8 9 115255417 ns/op 111412572 B/op 3342346 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 12 93761542 ns/op 111412538 B/op 3342345 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 12 94089990 ns/op 111412440 B/op 3342343 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 12 93721410 ns/op 111412514 B/op 3342344 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 12 93572951 ns/op 111412329 B/op 3342342 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 12 93536052 ns/op 111412206 B/op 3342341 allocs/op -BenchmarkString1WidthAllEastAsian/lut-8 12 93532365 ns/op 111412412 B/op 3342343 allocs/op -BenchmarkString1Width768EastAsian/regular-8 15904 75401 ns/op 76800 B/op 2304 allocs/op -BenchmarkString1Width768EastAsian/regular-8 15932 75449 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768EastAsian/regular-8 15944 75181 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768EastAsian/regular-8 15963 75311 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768EastAsian/regular-8 15879 75292 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768EastAsian/regular-8 15955 75334 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768EastAsian/lut-8 19692 60692 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768EastAsian/lut-8 19712 60699 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768EastAsian/lut-8 19741 60819 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768EastAsian/lut-8 19771 60653 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768EastAsian/lut-8 19737 61027 ns/op 76801 B/op 2304 allocs/op -BenchmarkString1Width768EastAsian/lut-8 19657 60820 ns/op 76801 B/op 2304 allocs/op -PASS -ok github.com/mattn/go-runewidth 76.165s diff --git a/vendor/github.com/mattn/go-runewidth/runewidth.go b/vendor/github.com/mattn/go-runewidth/runewidth.go deleted file mode 100644 index 0edabac3940..00000000000 --- a/vendor/github.com/mattn/go-runewidth/runewidth.go +++ /dev/null @@ -1,361 +0,0 @@ -package runewidth - -import ( - "os" - "strings" - - "github.com/clipperhouse/uax29/v2/graphemes" -) - -//go:generate go run script/generate.go - -var ( - // EastAsianWidth will be set true if the current locale is CJK - EastAsianWidth bool - - // StrictEmojiNeutral should be set false if handle broken fonts - StrictEmojiNeutral bool = true - - // DefaultCondition is a condition in current locale - DefaultCondition = &Condition{ - EastAsianWidth: false, - StrictEmojiNeutral: true, - } -) - -func init() { - handleEnv() -} - -func handleEnv() { - env := os.Getenv("RUNEWIDTH_EASTASIAN") - if env == "" { - EastAsianWidth = IsEastAsian() - } else { - EastAsianWidth = env == "1" - } - // update DefaultCondition - if DefaultCondition.EastAsianWidth != EastAsianWidth { - DefaultCondition.EastAsianWidth = EastAsianWidth - if len(DefaultCondition.combinedLut) > 0 { - DefaultCondition.combinedLut = DefaultCondition.combinedLut[:0] - CreateLUT() - } - } -} - -type interval struct { - first rune - last rune -} - -type table []interval - -func inTables(r rune, ts ...table) bool { - for _, t := range ts { - if inTable(r, t) { - return true - } - } - return false -} - -func inTable(r rune, t table) bool { - if r < t[0].first { - return false - } - if r > t[len(t)-1].last { - return false - } - - bot := 0 - top := len(t) - 1 - for top >= bot { - mid := (bot + top) >> 1 - - switch { - case t[mid].last < r: - bot = mid + 1 - case t[mid].first > r: - top = mid - 1 - default: - return true - } - } - - return false -} - -var private = table{ - {0x00E000, 0x00F8FF}, {0x0F0000, 0x0FFFFD}, {0x100000, 0x10FFFD}, -} - -var nonprint = table{ - {0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD}, - {0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F}, - {0x2028, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF}, - {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF}, -} - -// Condition have flag EastAsianWidth whether the current locale is CJK or not. -type Condition struct { - combinedLut []byte - EastAsianWidth bool - StrictEmojiNeutral bool -} - -// NewCondition return new instance of Condition which is current locale. -func NewCondition() *Condition { - return &Condition{ - EastAsianWidth: EastAsianWidth, - StrictEmojiNeutral: StrictEmojiNeutral, - } -} - -// RuneWidth returns the number of cells in r. -// See http://www.unicode.org/reports/tr11/ -func (c *Condition) RuneWidth(r rune) int { - if r < 0 || r > 0x10FFFF { - return 0 - } - if len(c.combinedLut) > 0 { - return int(c.combinedLut[r>>1]>>(uint(r&1)*4)) & 3 - } - // optimized version, verified by TestRuneWidthChecksums() - if !c.EastAsianWidth { - switch { - case r < 0x20: - return 0 - case (r >= 0x7F && r <= 0x9F) || r == 0xAD: // nonprint - return 0 - case r < 0x300: - return 1 - case inTable(r, narrow): - return 1 - case inTables(r, nonprint, combining): - return 0 - case inTable(r, doublewidth): - return 2 - default: - return 1 - } - } else { - switch { - case inTables(r, nonprint, combining): - return 0 - case inTable(r, narrow): - return 1 - case inTables(r, ambiguous, doublewidth): - return 2 - case !c.StrictEmojiNeutral && inTables(r, ambiguous, emoji, narrow): - return 2 - default: - return 1 - } - } -} - -// CreateLUT will create an in-memory lookup table of 557056 bytes for faster operation. -// This should not be called concurrently with other operations on c. -// If options in c is changed, CreateLUT should be called again. -func (c *Condition) CreateLUT() { - const max = 0x110000 - lut := c.combinedLut - if len(c.combinedLut) != 0 { - // Remove so we don't use it. - c.combinedLut = nil - } else { - lut = make([]byte, max/2) - } - for i := range lut { - i32 := int32(i * 2) - x0 := c.RuneWidth(i32) - x1 := c.RuneWidth(i32 + 1) - lut[i] = uint8(x0) | uint8(x1)<<4 - } - c.combinedLut = lut -} - -// StringWidth return width as you can see -func (c *Condition) StringWidth(s string) (width int) { - g := graphemes.FromString(s) - for g.Next() { - var chWidth int - for _, r := range g.Value() { - chWidth = c.RuneWidth(r) - if chWidth > 0 { - break // Our best guess at this point is to use the width of the first non-zero-width rune. - } - } - width += chWidth - } - return -} - -// Truncate return string truncated with w cells -func (c *Condition) Truncate(s string, w int, tail string) string { - if c.StringWidth(s) <= w { - return s - } - w -= c.StringWidth(tail) - var width int - pos := len(s) - g := graphemes.FromString(s) - for g.Next() { - var chWidth int - for _, r := range g.Value() { - chWidth = c.RuneWidth(r) - if chWidth > 0 { - break // See StringWidth() for details. - } - } - if width+chWidth > w { - pos = g.Start() - break - } - width += chWidth - } - return s[:pos] + tail -} - -// TruncateLeft cuts w cells from the beginning of the `s`. -func (c *Condition) TruncateLeft(s string, w int, prefix string) string { - if c.StringWidth(s) <= w { - return prefix - } - - var width int - pos := len(s) - - g := graphemes.FromString(s) - for g.Next() { - var chWidth int - for _, r := range g.Value() { - chWidth = c.RuneWidth(r) - if chWidth > 0 { - break // See StringWidth() for details. - } - } - - if width+chWidth > w { - if width < w { - pos = g.End() - prefix += strings.Repeat(" ", width+chWidth-w) - } else { - pos = g.Start() - } - - break - } - - width += chWidth - } - - return prefix + s[pos:] -} - -// Wrap return string wrapped with w cells -func (c *Condition) Wrap(s string, w int) string { - width := 0 - out := "" - for _, r := range s { - cw := c.RuneWidth(r) - if r == '\n' { - out += string(r) - width = 0 - continue - } else if width+cw > w { - out += "\n" - width = 0 - out += string(r) - width += cw - continue - } - out += string(r) - width += cw - } - return out -} - -// FillLeft return string filled in left by spaces in w cells -func (c *Condition) FillLeft(s string, w int) string { - width := c.StringWidth(s) - count := w - width - if count > 0 { - b := make([]byte, count) - for i := range b { - b[i] = ' ' - } - return string(b) + s - } - return s -} - -// FillRight return string filled in left by spaces in w cells -func (c *Condition) FillRight(s string, w int) string { - width := c.StringWidth(s) - count := w - width - if count > 0 { - b := make([]byte, count) - for i := range b { - b[i] = ' ' - } - return s + string(b) - } - return s -} - -// RuneWidth returns the number of cells in r. -// See http://www.unicode.org/reports/tr11/ -func RuneWidth(r rune) int { - return DefaultCondition.RuneWidth(r) -} - -// IsAmbiguousWidth returns whether is ambiguous width or not. -func IsAmbiguousWidth(r rune) bool { - return inTables(r, private, ambiguous) -} - -// IsNeutralWidth returns whether is neutral width or not. -func IsNeutralWidth(r rune) bool { - return inTable(r, neutral) -} - -// StringWidth return width as you can see -func StringWidth(s string) (width int) { - return DefaultCondition.StringWidth(s) -} - -// Truncate return string truncated with w cells -func Truncate(s string, w int, tail string) string { - return DefaultCondition.Truncate(s, w, tail) -} - -// TruncateLeft cuts w cells from the beginning of the `s`. -func TruncateLeft(s string, w int, prefix string) string { - return DefaultCondition.TruncateLeft(s, w, prefix) -} - -// Wrap return string wrapped with w cells -func Wrap(s string, w int) string { - return DefaultCondition.Wrap(s, w) -} - -// FillLeft return string filled in left by spaces in w cells -func FillLeft(s string, w int) string { - return DefaultCondition.FillLeft(s, w) -} - -// FillRight return string filled in left by spaces in w cells -func FillRight(s string, w int) string { - return DefaultCondition.FillRight(s, w) -} - -// CreateLUT will create an in-memory lookup table of 557055 bytes for faster operation. -// This should not be called concurrently with other operations. -func CreateLUT() { - if len(DefaultCondition.combinedLut) > 0 { - return - } - DefaultCondition.CreateLUT() -} diff --git a/vendor/github.com/mattn/go-runewidth/runewidth_appengine.go b/vendor/github.com/mattn/go-runewidth/runewidth_appengine.go deleted file mode 100644 index 84b6528dfe9..00000000000 --- a/vendor/github.com/mattn/go-runewidth/runewidth_appengine.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build appengine -// +build appengine - -package runewidth - -// IsEastAsian return true if the current locale is CJK -func IsEastAsian() bool { - return false -} diff --git a/vendor/github.com/mattn/go-runewidth/runewidth_js.go b/vendor/github.com/mattn/go-runewidth/runewidth_js.go deleted file mode 100644 index c2abbc2db30..00000000000 --- a/vendor/github.com/mattn/go-runewidth/runewidth_js.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build js && !appengine -// +build js,!appengine - -package runewidth - -func IsEastAsian() bool { - // TODO: Implement this for the web. Detect east asian in a compatible way, and return true. - return false -} diff --git a/vendor/github.com/mattn/go-runewidth/runewidth_posix.go b/vendor/github.com/mattn/go-runewidth/runewidth_posix.go deleted file mode 100644 index 5a31d738ecc..00000000000 --- a/vendor/github.com/mattn/go-runewidth/runewidth_posix.go +++ /dev/null @@ -1,81 +0,0 @@ -//go:build !windows && !js && !appengine -// +build !windows,!js,!appengine - -package runewidth - -import ( - "os" - "regexp" - "strings" -) - -var reLoc = regexp.MustCompile(`^[a-z][a-z][a-z]?(?:_[A-Z][A-Z])?\.(.+)`) - -var mblenTable = map[string]int{ - "utf-8": 6, - "utf8": 6, - "jis": 8, - "eucjp": 3, - "euckr": 2, - "euccn": 2, - "sjis": 2, - "cp932": 2, - "cp51932": 2, - "cp936": 2, - "cp949": 2, - "cp950": 2, - "big5": 2, - "gbk": 2, - "gb2312": 2, -} - -func isEastAsian(locale string) bool { - charset := strings.ToLower(locale) - r := reLoc.FindStringSubmatch(locale) - if len(r) == 2 { - charset = strings.ToLower(r[1]) - } - - if strings.HasSuffix(charset, "@cjk_narrow") { - return false - } - - for pos, b := range []byte(charset) { - if b == '@' { - charset = charset[:pos] - break - } - } - max := 1 - if m, ok := mblenTable[charset]; ok { - max = m - } - if max > 1 && (charset[0] != 'u' || - strings.HasPrefix(locale, "ja") || - strings.HasPrefix(locale, "ko") || - strings.HasPrefix(locale, "zh")) { - return true - } - return false -} - -// IsEastAsian return true if the current locale is CJK -func IsEastAsian() bool { - locale := os.Getenv("LC_ALL") - if locale == "" { - locale = os.Getenv("LC_CTYPE") - } - if locale == "" { - locale = os.Getenv("LANG") - } - - // ignore C locale - if locale == "POSIX" || locale == "C" { - return false - } - if len(locale) > 1 && locale[0] == 'C' && (locale[1] == '.' || locale[1] == '-') { - return false - } - - return isEastAsian(locale) -} diff --git a/vendor/github.com/mattn/go-runewidth/runewidth_table.go b/vendor/github.com/mattn/go-runewidth/runewidth_table.go deleted file mode 100644 index ad025ad5296..00000000000 --- a/vendor/github.com/mattn/go-runewidth/runewidth_table.go +++ /dev/null @@ -1,450 +0,0 @@ -// Code generated by script/generate.go. DO NOT EDIT. - -package runewidth - -var combining = table{ - {0x0300, 0x036F}, {0x0483, 0x0489}, {0x07EB, 0x07F3}, - {0x0C00, 0x0C00}, {0x0C04, 0x0C04}, {0x0CF3, 0x0CF3}, - {0x0D00, 0x0D01}, {0x135D, 0x135F}, {0x1A7F, 0x1A7F}, - {0x1AB0, 0x1ACE}, {0x1B6B, 0x1B73}, {0x1DC0, 0x1DFF}, - {0x20D0, 0x20F0}, {0x2CEF, 0x2CF1}, {0x2DE0, 0x2DFF}, - {0x3099, 0x309A}, {0xA66F, 0xA672}, {0xA674, 0xA67D}, - {0xA69E, 0xA69F}, {0xA6F0, 0xA6F1}, {0xA8E0, 0xA8F1}, - {0xFE20, 0xFE2F}, {0x101FD, 0x101FD}, {0x10376, 0x1037A}, - {0x10EAB, 0x10EAC}, {0x10F46, 0x10F50}, {0x10F82, 0x10F85}, - {0x11300, 0x11301}, {0x1133B, 0x1133C}, {0x11366, 0x1136C}, - {0x11370, 0x11374}, {0x16AF0, 0x16AF4}, {0x1CF00, 0x1CF2D}, - {0x1CF30, 0x1CF46}, {0x1D165, 0x1D169}, {0x1D16D, 0x1D172}, - {0x1D17B, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, - {0x1D242, 0x1D244}, {0x1E000, 0x1E006}, {0x1E008, 0x1E018}, - {0x1E01B, 0x1E021}, {0x1E023, 0x1E024}, {0x1E026, 0x1E02A}, - {0x1E08F, 0x1E08F}, {0x1E8D0, 0x1E8D6}, -} - -var doublewidth = table{ - {0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x232A}, - {0x23E9, 0x23EC}, {0x23F0, 0x23F0}, {0x23F3, 0x23F3}, - {0x25FD, 0x25FE}, {0x2614, 0x2615}, {0x2648, 0x2653}, - {0x267F, 0x267F}, {0x2693, 0x2693}, {0x26A1, 0x26A1}, - {0x26AA, 0x26AB}, {0x26BD, 0x26BE}, {0x26C4, 0x26C5}, - {0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA}, - {0x26F2, 0x26F3}, {0x26F5, 0x26F5}, {0x26FA, 0x26FA}, - {0x26FD, 0x26FD}, {0x2705, 0x2705}, {0x270A, 0x270B}, - {0x2728, 0x2728}, {0x274C, 0x274C}, {0x274E, 0x274E}, - {0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797}, - {0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C}, - {0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x2E80, 0x2E99}, - {0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x303E}, - {0x3041, 0x3096}, {0x3099, 0x30FF}, {0x3105, 0x312F}, - {0x3131, 0x318E}, {0x3190, 0x31E3}, {0x31EF, 0x321E}, - {0x3220, 0x3247}, {0x3250, 0x4DBF}, {0x4E00, 0xA48C}, - {0xA490, 0xA4C6}, {0xA960, 0xA97C}, {0xAC00, 0xD7A3}, - {0xF900, 0xFAFF}, {0xFE10, 0xFE19}, {0xFE30, 0xFE52}, - {0xFE54, 0xFE66}, {0xFE68, 0xFE6B}, {0xFF01, 0xFF60}, - {0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4}, {0x16FF0, 0x16FF1}, - {0x17000, 0x187F7}, {0x18800, 0x18CD5}, {0x18D00, 0x18D08}, - {0x1AFF0, 0x1AFF3}, {0x1AFF5, 0x1AFFB}, {0x1AFFD, 0x1AFFE}, - {0x1B000, 0x1B122}, {0x1B132, 0x1B132}, {0x1B150, 0x1B152}, - {0x1B155, 0x1B155}, {0x1B164, 0x1B167}, {0x1B170, 0x1B2FB}, - {0x1F004, 0x1F004}, {0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E}, - {0x1F191, 0x1F19A}, {0x1F200, 0x1F202}, {0x1F210, 0x1F23B}, - {0x1F240, 0x1F248}, {0x1F250, 0x1F251}, {0x1F260, 0x1F265}, - {0x1F300, 0x1F320}, {0x1F32D, 0x1F335}, {0x1F337, 0x1F37C}, - {0x1F37E, 0x1F393}, {0x1F3A0, 0x1F3CA}, {0x1F3CF, 0x1F3D3}, - {0x1F3E0, 0x1F3F0}, {0x1F3F4, 0x1F3F4}, {0x1F3F8, 0x1F43E}, - {0x1F440, 0x1F440}, {0x1F442, 0x1F4FC}, {0x1F4FF, 0x1F53D}, - {0x1F54B, 0x1F54E}, {0x1F550, 0x1F567}, {0x1F57A, 0x1F57A}, - {0x1F595, 0x1F596}, {0x1F5A4, 0x1F5A4}, {0x1F5FB, 0x1F64F}, - {0x1F680, 0x1F6C5}, {0x1F6CC, 0x1F6CC}, {0x1F6D0, 0x1F6D2}, - {0x1F6D5, 0x1F6D7}, {0x1F6DC, 0x1F6DF}, {0x1F6EB, 0x1F6EC}, - {0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB}, {0x1F7F0, 0x1F7F0}, - {0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1F9FF}, - {0x1FA70, 0x1FA7C}, {0x1FA80, 0x1FA88}, {0x1FA90, 0x1FABD}, - {0x1FABF, 0x1FAC5}, {0x1FACE, 0x1FADB}, {0x1FAE0, 0x1FAE8}, - {0x1FAF0, 0x1FAF8}, {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD}, -} - -var ambiguous = table{ - {0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8}, - {0x00AA, 0x00AA}, {0x00AD, 0x00AE}, {0x00B0, 0x00B4}, - {0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6}, - {0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1}, - {0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED}, - {0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA}, - {0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101}, - {0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B}, - {0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133}, - {0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144}, - {0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153}, - {0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE}, - {0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4}, - {0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA}, - {0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261}, - {0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB}, - {0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB}, - {0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0300, 0x036F}, - {0x0391, 0x03A1}, {0x03A3, 0x03A9}, {0x03B1, 0x03C1}, - {0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F}, - {0x0451, 0x0451}, {0x2010, 0x2010}, {0x2013, 0x2016}, - {0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022}, - {0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033}, - {0x2035, 0x2035}, {0x203B, 0x203B}, {0x203E, 0x203E}, - {0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084}, - {0x20AC, 0x20AC}, {0x2103, 0x2103}, {0x2105, 0x2105}, - {0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116}, - {0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B}, - {0x2153, 0x2154}, {0x215B, 0x215E}, {0x2160, 0x216B}, - {0x2170, 0x2179}, {0x2189, 0x2189}, {0x2190, 0x2199}, - {0x21B8, 0x21B9}, {0x21D2, 0x21D2}, {0x21D4, 0x21D4}, - {0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203}, - {0x2207, 0x2208}, {0x220B, 0x220B}, {0x220F, 0x220F}, - {0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A}, - {0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225}, - {0x2227, 0x222C}, {0x222E, 0x222E}, {0x2234, 0x2237}, - {0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C}, - {0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267}, - {0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283}, - {0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299}, - {0x22A5, 0x22A5}, {0x22BF, 0x22BF}, {0x2312, 0x2312}, - {0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573}, - {0x2580, 0x258F}, {0x2592, 0x2595}, {0x25A0, 0x25A1}, - {0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7}, - {0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8}, - {0x25CB, 0x25CB}, {0x25CE, 0x25D1}, {0x25E2, 0x25E5}, - {0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609}, - {0x260E, 0x260F}, {0x261C, 0x261C}, {0x261E, 0x261E}, - {0x2640, 0x2640}, {0x2642, 0x2642}, {0x2660, 0x2661}, - {0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D}, - {0x266F, 0x266F}, {0x269E, 0x269F}, {0x26BF, 0x26BF}, - {0x26C6, 0x26CD}, {0x26CF, 0x26D3}, {0x26D5, 0x26E1}, - {0x26E3, 0x26E3}, {0x26E8, 0x26E9}, {0x26EB, 0x26F1}, - {0x26F4, 0x26F4}, {0x26F6, 0x26F9}, {0x26FB, 0x26FC}, - {0x26FE, 0x26FF}, {0x273D, 0x273D}, {0x2776, 0x277F}, - {0x2B56, 0x2B59}, {0x3248, 0x324F}, {0xE000, 0xF8FF}, - {0xFE00, 0xFE0F}, {0xFFFD, 0xFFFD}, {0x1F100, 0x1F10A}, - {0x1F110, 0x1F12D}, {0x1F130, 0x1F169}, {0x1F170, 0x1F18D}, - {0x1F18F, 0x1F190}, {0x1F19B, 0x1F1AC}, {0xE0100, 0xE01EF}, - {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD}, -} -var narrow = table{ - {0x0020, 0x007E}, {0x00A2, 0x00A3}, {0x00A5, 0x00A6}, - {0x00AC, 0x00AC}, {0x00AF, 0x00AF}, {0x27E6, 0x27ED}, - {0x2985, 0x2986}, -} - -var neutral = table{ - {0x0000, 0x001F}, {0x007F, 0x00A0}, {0x00A9, 0x00A9}, - {0x00AB, 0x00AB}, {0x00B5, 0x00B5}, {0x00BB, 0x00BB}, - {0x00C0, 0x00C5}, {0x00C7, 0x00CF}, {0x00D1, 0x00D6}, - {0x00D9, 0x00DD}, {0x00E2, 0x00E5}, {0x00E7, 0x00E7}, - {0x00EB, 0x00EB}, {0x00EE, 0x00EF}, {0x00F1, 0x00F1}, - {0x00F4, 0x00F6}, {0x00FB, 0x00FB}, {0x00FD, 0x00FD}, - {0x00FF, 0x0100}, {0x0102, 0x0110}, {0x0112, 0x0112}, - {0x0114, 0x011A}, {0x011C, 0x0125}, {0x0128, 0x012A}, - {0x012C, 0x0130}, {0x0134, 0x0137}, {0x0139, 0x013E}, - {0x0143, 0x0143}, {0x0145, 0x0147}, {0x014C, 0x014C}, - {0x014E, 0x0151}, {0x0154, 0x0165}, {0x0168, 0x016A}, - {0x016C, 0x01CD}, {0x01CF, 0x01CF}, {0x01D1, 0x01D1}, - {0x01D3, 0x01D3}, {0x01D5, 0x01D5}, {0x01D7, 0x01D7}, - {0x01D9, 0x01D9}, {0x01DB, 0x01DB}, {0x01DD, 0x0250}, - {0x0252, 0x0260}, {0x0262, 0x02C3}, {0x02C5, 0x02C6}, - {0x02C8, 0x02C8}, {0x02CC, 0x02CC}, {0x02CE, 0x02CF}, - {0x02D1, 0x02D7}, {0x02DC, 0x02DC}, {0x02DE, 0x02DE}, - {0x02E0, 0x02FF}, {0x0370, 0x0377}, {0x037A, 0x037F}, - {0x0384, 0x038A}, {0x038C, 0x038C}, {0x038E, 0x0390}, - {0x03AA, 0x03B0}, {0x03C2, 0x03C2}, {0x03CA, 0x0400}, - {0x0402, 0x040F}, {0x0450, 0x0450}, {0x0452, 0x052F}, - {0x0531, 0x0556}, {0x0559, 0x058A}, {0x058D, 0x058F}, - {0x0591, 0x05C7}, {0x05D0, 0x05EA}, {0x05EF, 0x05F4}, - {0x0600, 0x070D}, {0x070F, 0x074A}, {0x074D, 0x07B1}, - {0x07C0, 0x07FA}, {0x07FD, 0x082D}, {0x0830, 0x083E}, - {0x0840, 0x085B}, {0x085E, 0x085E}, {0x0860, 0x086A}, - {0x0870, 0x088E}, {0x0890, 0x0891}, {0x0898, 0x0983}, - {0x0985, 0x098C}, {0x098F, 0x0990}, {0x0993, 0x09A8}, - {0x09AA, 0x09B0}, {0x09B2, 0x09B2}, {0x09B6, 0x09B9}, - {0x09BC, 0x09C4}, {0x09C7, 0x09C8}, {0x09CB, 0x09CE}, - {0x09D7, 0x09D7}, {0x09DC, 0x09DD}, {0x09DF, 0x09E3}, - {0x09E6, 0x09FE}, {0x0A01, 0x0A03}, {0x0A05, 0x0A0A}, - {0x0A0F, 0x0A10}, {0x0A13, 0x0A28}, {0x0A2A, 0x0A30}, - {0x0A32, 0x0A33}, {0x0A35, 0x0A36}, {0x0A38, 0x0A39}, - {0x0A3C, 0x0A3C}, {0x0A3E, 0x0A42}, {0x0A47, 0x0A48}, - {0x0A4B, 0x0A4D}, {0x0A51, 0x0A51}, {0x0A59, 0x0A5C}, - {0x0A5E, 0x0A5E}, {0x0A66, 0x0A76}, {0x0A81, 0x0A83}, - {0x0A85, 0x0A8D}, {0x0A8F, 0x0A91}, {0x0A93, 0x0AA8}, - {0x0AAA, 0x0AB0}, {0x0AB2, 0x0AB3}, {0x0AB5, 0x0AB9}, - {0x0ABC, 0x0AC5}, {0x0AC7, 0x0AC9}, {0x0ACB, 0x0ACD}, - {0x0AD0, 0x0AD0}, {0x0AE0, 0x0AE3}, {0x0AE6, 0x0AF1}, - {0x0AF9, 0x0AFF}, {0x0B01, 0x0B03}, {0x0B05, 0x0B0C}, - {0x0B0F, 0x0B10}, {0x0B13, 0x0B28}, {0x0B2A, 0x0B30}, - {0x0B32, 0x0B33}, {0x0B35, 0x0B39}, {0x0B3C, 0x0B44}, - {0x0B47, 0x0B48}, {0x0B4B, 0x0B4D}, {0x0B55, 0x0B57}, - {0x0B5C, 0x0B5D}, {0x0B5F, 0x0B63}, {0x0B66, 0x0B77}, - {0x0B82, 0x0B83}, {0x0B85, 0x0B8A}, {0x0B8E, 0x0B90}, - {0x0B92, 0x0B95}, {0x0B99, 0x0B9A}, {0x0B9C, 0x0B9C}, - {0x0B9E, 0x0B9F}, {0x0BA3, 0x0BA4}, {0x0BA8, 0x0BAA}, - {0x0BAE, 0x0BB9}, {0x0BBE, 0x0BC2}, {0x0BC6, 0x0BC8}, - {0x0BCA, 0x0BCD}, {0x0BD0, 0x0BD0}, {0x0BD7, 0x0BD7}, - {0x0BE6, 0x0BFA}, {0x0C00, 0x0C0C}, {0x0C0E, 0x0C10}, - {0x0C12, 0x0C28}, {0x0C2A, 0x0C39}, {0x0C3C, 0x0C44}, - {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, - {0x0C58, 0x0C5A}, {0x0C5D, 0x0C5D}, {0x0C60, 0x0C63}, - {0x0C66, 0x0C6F}, {0x0C77, 0x0C8C}, {0x0C8E, 0x0C90}, - {0x0C92, 0x0CA8}, {0x0CAA, 0x0CB3}, {0x0CB5, 0x0CB9}, - {0x0CBC, 0x0CC4}, {0x0CC6, 0x0CC8}, {0x0CCA, 0x0CCD}, - {0x0CD5, 0x0CD6}, {0x0CDD, 0x0CDE}, {0x0CE0, 0x0CE3}, - {0x0CE6, 0x0CEF}, {0x0CF1, 0x0CF3}, {0x0D00, 0x0D0C}, - {0x0D0E, 0x0D10}, {0x0D12, 0x0D44}, {0x0D46, 0x0D48}, - {0x0D4A, 0x0D4F}, {0x0D54, 0x0D63}, {0x0D66, 0x0D7F}, - {0x0D81, 0x0D83}, {0x0D85, 0x0D96}, {0x0D9A, 0x0DB1}, - {0x0DB3, 0x0DBB}, {0x0DBD, 0x0DBD}, {0x0DC0, 0x0DC6}, - {0x0DCA, 0x0DCA}, {0x0DCF, 0x0DD4}, {0x0DD6, 0x0DD6}, - {0x0DD8, 0x0DDF}, {0x0DE6, 0x0DEF}, {0x0DF2, 0x0DF4}, - {0x0E01, 0x0E3A}, {0x0E3F, 0x0E5B}, {0x0E81, 0x0E82}, - {0x0E84, 0x0E84}, {0x0E86, 0x0E8A}, {0x0E8C, 0x0EA3}, - {0x0EA5, 0x0EA5}, {0x0EA7, 0x0EBD}, {0x0EC0, 0x0EC4}, - {0x0EC6, 0x0EC6}, {0x0EC8, 0x0ECE}, {0x0ED0, 0x0ED9}, - {0x0EDC, 0x0EDF}, {0x0F00, 0x0F47}, {0x0F49, 0x0F6C}, - {0x0F71, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FBE, 0x0FCC}, - {0x0FCE, 0x0FDA}, {0x1000, 0x10C5}, {0x10C7, 0x10C7}, - {0x10CD, 0x10CD}, {0x10D0, 0x10FF}, {0x1160, 0x1248}, - {0x124A, 0x124D}, {0x1250, 0x1256}, {0x1258, 0x1258}, - {0x125A, 0x125D}, {0x1260, 0x1288}, {0x128A, 0x128D}, - {0x1290, 0x12B0}, {0x12B2, 0x12B5}, {0x12B8, 0x12BE}, - {0x12C0, 0x12C0}, {0x12C2, 0x12C5}, {0x12C8, 0x12D6}, - {0x12D8, 0x1310}, {0x1312, 0x1315}, {0x1318, 0x135A}, - {0x135D, 0x137C}, {0x1380, 0x1399}, {0x13A0, 0x13F5}, - {0x13F8, 0x13FD}, {0x1400, 0x169C}, {0x16A0, 0x16F8}, - {0x1700, 0x1715}, {0x171F, 0x1736}, {0x1740, 0x1753}, - {0x1760, 0x176C}, {0x176E, 0x1770}, {0x1772, 0x1773}, - {0x1780, 0x17DD}, {0x17E0, 0x17E9}, {0x17F0, 0x17F9}, - {0x1800, 0x1819}, {0x1820, 0x1878}, {0x1880, 0x18AA}, - {0x18B0, 0x18F5}, {0x1900, 0x191E}, {0x1920, 0x192B}, - {0x1930, 0x193B}, {0x1940, 0x1940}, {0x1944, 0x196D}, - {0x1970, 0x1974}, {0x1980, 0x19AB}, {0x19B0, 0x19C9}, - {0x19D0, 0x19DA}, {0x19DE, 0x1A1B}, {0x1A1E, 0x1A5E}, - {0x1A60, 0x1A7C}, {0x1A7F, 0x1A89}, {0x1A90, 0x1A99}, - {0x1AA0, 0x1AAD}, {0x1AB0, 0x1ACE}, {0x1B00, 0x1B4C}, - {0x1B50, 0x1B7E}, {0x1B80, 0x1BF3}, {0x1BFC, 0x1C37}, - {0x1C3B, 0x1C49}, {0x1C4D, 0x1C88}, {0x1C90, 0x1CBA}, - {0x1CBD, 0x1CC7}, {0x1CD0, 0x1CFA}, {0x1D00, 0x1F15}, - {0x1F18, 0x1F1D}, {0x1F20, 0x1F45}, {0x1F48, 0x1F4D}, - {0x1F50, 0x1F57}, {0x1F59, 0x1F59}, {0x1F5B, 0x1F5B}, - {0x1F5D, 0x1F5D}, {0x1F5F, 0x1F7D}, {0x1F80, 0x1FB4}, - {0x1FB6, 0x1FC4}, {0x1FC6, 0x1FD3}, {0x1FD6, 0x1FDB}, - {0x1FDD, 0x1FEF}, {0x1FF2, 0x1FF4}, {0x1FF6, 0x1FFE}, - {0x2000, 0x200F}, {0x2011, 0x2012}, {0x2017, 0x2017}, - {0x201A, 0x201B}, {0x201E, 0x201F}, {0x2023, 0x2023}, - {0x2028, 0x202F}, {0x2031, 0x2031}, {0x2034, 0x2034}, - {0x2036, 0x203A}, {0x203C, 0x203D}, {0x203F, 0x2064}, - {0x2066, 0x2071}, {0x2075, 0x207E}, {0x2080, 0x2080}, - {0x2085, 0x208E}, {0x2090, 0x209C}, {0x20A0, 0x20A8}, - {0x20AA, 0x20AB}, {0x20AD, 0x20C0}, {0x20D0, 0x20F0}, - {0x2100, 0x2102}, {0x2104, 0x2104}, {0x2106, 0x2108}, - {0x210A, 0x2112}, {0x2114, 0x2115}, {0x2117, 0x2120}, - {0x2123, 0x2125}, {0x2127, 0x212A}, {0x212C, 0x2152}, - {0x2155, 0x215A}, {0x215F, 0x215F}, {0x216C, 0x216F}, - {0x217A, 0x2188}, {0x218A, 0x218B}, {0x219A, 0x21B7}, - {0x21BA, 0x21D1}, {0x21D3, 0x21D3}, {0x21D5, 0x21E6}, - {0x21E8, 0x21FF}, {0x2201, 0x2201}, {0x2204, 0x2206}, - {0x2209, 0x220A}, {0x220C, 0x220E}, {0x2210, 0x2210}, - {0x2212, 0x2214}, {0x2216, 0x2219}, {0x221B, 0x221C}, - {0x2221, 0x2222}, {0x2224, 0x2224}, {0x2226, 0x2226}, - {0x222D, 0x222D}, {0x222F, 0x2233}, {0x2238, 0x223B}, - {0x223E, 0x2247}, {0x2249, 0x224B}, {0x224D, 0x2251}, - {0x2253, 0x225F}, {0x2262, 0x2263}, {0x2268, 0x2269}, - {0x226C, 0x226D}, {0x2270, 0x2281}, {0x2284, 0x2285}, - {0x2288, 0x2294}, {0x2296, 0x2298}, {0x229A, 0x22A4}, - {0x22A6, 0x22BE}, {0x22C0, 0x2311}, {0x2313, 0x2319}, - {0x231C, 0x2328}, {0x232B, 0x23E8}, {0x23ED, 0x23EF}, - {0x23F1, 0x23F2}, {0x23F4, 0x2426}, {0x2440, 0x244A}, - {0x24EA, 0x24EA}, {0x254C, 0x254F}, {0x2574, 0x257F}, - {0x2590, 0x2591}, {0x2596, 0x259F}, {0x25A2, 0x25A2}, - {0x25AA, 0x25B1}, {0x25B4, 0x25B5}, {0x25B8, 0x25BB}, - {0x25BE, 0x25BF}, {0x25C2, 0x25C5}, {0x25C9, 0x25CA}, - {0x25CC, 0x25CD}, {0x25D2, 0x25E1}, {0x25E6, 0x25EE}, - {0x25F0, 0x25FC}, {0x25FF, 0x2604}, {0x2607, 0x2608}, - {0x260A, 0x260D}, {0x2610, 0x2613}, {0x2616, 0x261B}, - {0x261D, 0x261D}, {0x261F, 0x263F}, {0x2641, 0x2641}, - {0x2643, 0x2647}, {0x2654, 0x265F}, {0x2662, 0x2662}, - {0x2666, 0x2666}, {0x266B, 0x266B}, {0x266E, 0x266E}, - {0x2670, 0x267E}, {0x2680, 0x2692}, {0x2694, 0x269D}, - {0x26A0, 0x26A0}, {0x26A2, 0x26A9}, {0x26AC, 0x26BC}, - {0x26C0, 0x26C3}, {0x26E2, 0x26E2}, {0x26E4, 0x26E7}, - {0x2700, 0x2704}, {0x2706, 0x2709}, {0x270C, 0x2727}, - {0x2729, 0x273C}, {0x273E, 0x274B}, {0x274D, 0x274D}, - {0x274F, 0x2752}, {0x2756, 0x2756}, {0x2758, 0x2775}, - {0x2780, 0x2794}, {0x2798, 0x27AF}, {0x27B1, 0x27BE}, - {0x27C0, 0x27E5}, {0x27EE, 0x2984}, {0x2987, 0x2B1A}, - {0x2B1D, 0x2B4F}, {0x2B51, 0x2B54}, {0x2B5A, 0x2B73}, - {0x2B76, 0x2B95}, {0x2B97, 0x2CF3}, {0x2CF9, 0x2D25}, - {0x2D27, 0x2D27}, {0x2D2D, 0x2D2D}, {0x2D30, 0x2D67}, - {0x2D6F, 0x2D70}, {0x2D7F, 0x2D96}, {0x2DA0, 0x2DA6}, - {0x2DA8, 0x2DAE}, {0x2DB0, 0x2DB6}, {0x2DB8, 0x2DBE}, - {0x2DC0, 0x2DC6}, {0x2DC8, 0x2DCE}, {0x2DD0, 0x2DD6}, - {0x2DD8, 0x2DDE}, {0x2DE0, 0x2E5D}, {0x303F, 0x303F}, - {0x4DC0, 0x4DFF}, {0xA4D0, 0xA62B}, {0xA640, 0xA6F7}, - {0xA700, 0xA7CA}, {0xA7D0, 0xA7D1}, {0xA7D3, 0xA7D3}, - {0xA7D5, 0xA7D9}, {0xA7F2, 0xA82C}, {0xA830, 0xA839}, - {0xA840, 0xA877}, {0xA880, 0xA8C5}, {0xA8CE, 0xA8D9}, - {0xA8E0, 0xA953}, {0xA95F, 0xA95F}, {0xA980, 0xA9CD}, - {0xA9CF, 0xA9D9}, {0xA9DE, 0xA9FE}, {0xAA00, 0xAA36}, - {0xAA40, 0xAA4D}, {0xAA50, 0xAA59}, {0xAA5C, 0xAAC2}, - {0xAADB, 0xAAF6}, {0xAB01, 0xAB06}, {0xAB09, 0xAB0E}, - {0xAB11, 0xAB16}, {0xAB20, 0xAB26}, {0xAB28, 0xAB2E}, - {0xAB30, 0xAB6B}, {0xAB70, 0xABED}, {0xABF0, 0xABF9}, - {0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB}, {0xD800, 0xDFFF}, - {0xFB00, 0xFB06}, {0xFB13, 0xFB17}, {0xFB1D, 0xFB36}, - {0xFB38, 0xFB3C}, {0xFB3E, 0xFB3E}, {0xFB40, 0xFB41}, - {0xFB43, 0xFB44}, {0xFB46, 0xFBC2}, {0xFBD3, 0xFD8F}, - {0xFD92, 0xFDC7}, {0xFDCF, 0xFDCF}, {0xFDF0, 0xFDFF}, - {0xFE20, 0xFE2F}, {0xFE70, 0xFE74}, {0xFE76, 0xFEFC}, - {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFC}, {0x10000, 0x1000B}, - {0x1000D, 0x10026}, {0x10028, 0x1003A}, {0x1003C, 0x1003D}, - {0x1003F, 0x1004D}, {0x10050, 0x1005D}, {0x10080, 0x100FA}, - {0x10100, 0x10102}, {0x10107, 0x10133}, {0x10137, 0x1018E}, - {0x10190, 0x1019C}, {0x101A0, 0x101A0}, {0x101D0, 0x101FD}, - {0x10280, 0x1029C}, {0x102A0, 0x102D0}, {0x102E0, 0x102FB}, - {0x10300, 0x10323}, {0x1032D, 0x1034A}, {0x10350, 0x1037A}, - {0x10380, 0x1039D}, {0x1039F, 0x103C3}, {0x103C8, 0x103D5}, - {0x10400, 0x1049D}, {0x104A0, 0x104A9}, {0x104B0, 0x104D3}, - {0x104D8, 0x104FB}, {0x10500, 0x10527}, {0x10530, 0x10563}, - {0x1056F, 0x1057A}, {0x1057C, 0x1058A}, {0x1058C, 0x10592}, - {0x10594, 0x10595}, {0x10597, 0x105A1}, {0x105A3, 0x105B1}, - {0x105B3, 0x105B9}, {0x105BB, 0x105BC}, {0x10600, 0x10736}, - {0x10740, 0x10755}, {0x10760, 0x10767}, {0x10780, 0x10785}, - {0x10787, 0x107B0}, {0x107B2, 0x107BA}, {0x10800, 0x10805}, - {0x10808, 0x10808}, {0x1080A, 0x10835}, {0x10837, 0x10838}, - {0x1083C, 0x1083C}, {0x1083F, 0x10855}, {0x10857, 0x1089E}, - {0x108A7, 0x108AF}, {0x108E0, 0x108F2}, {0x108F4, 0x108F5}, - {0x108FB, 0x1091B}, {0x1091F, 0x10939}, {0x1093F, 0x1093F}, - {0x10980, 0x109B7}, {0x109BC, 0x109CF}, {0x109D2, 0x10A03}, - {0x10A05, 0x10A06}, {0x10A0C, 0x10A13}, {0x10A15, 0x10A17}, - {0x10A19, 0x10A35}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A48}, - {0x10A50, 0x10A58}, {0x10A60, 0x10A9F}, {0x10AC0, 0x10AE6}, - {0x10AEB, 0x10AF6}, {0x10B00, 0x10B35}, {0x10B39, 0x10B55}, - {0x10B58, 0x10B72}, {0x10B78, 0x10B91}, {0x10B99, 0x10B9C}, - {0x10BA9, 0x10BAF}, {0x10C00, 0x10C48}, {0x10C80, 0x10CB2}, - {0x10CC0, 0x10CF2}, {0x10CFA, 0x10D27}, {0x10D30, 0x10D39}, - {0x10E60, 0x10E7E}, {0x10E80, 0x10EA9}, {0x10EAB, 0x10EAD}, - {0x10EB0, 0x10EB1}, {0x10EFD, 0x10F27}, {0x10F30, 0x10F59}, - {0x10F70, 0x10F89}, {0x10FB0, 0x10FCB}, {0x10FE0, 0x10FF6}, - {0x11000, 0x1104D}, {0x11052, 0x11075}, {0x1107F, 0x110C2}, - {0x110CD, 0x110CD}, {0x110D0, 0x110E8}, {0x110F0, 0x110F9}, - {0x11100, 0x11134}, {0x11136, 0x11147}, {0x11150, 0x11176}, - {0x11180, 0x111DF}, {0x111E1, 0x111F4}, {0x11200, 0x11211}, - {0x11213, 0x11241}, {0x11280, 0x11286}, {0x11288, 0x11288}, - {0x1128A, 0x1128D}, {0x1128F, 0x1129D}, {0x1129F, 0x112A9}, - {0x112B0, 0x112EA}, {0x112F0, 0x112F9}, {0x11300, 0x11303}, - {0x11305, 0x1130C}, {0x1130F, 0x11310}, {0x11313, 0x11328}, - {0x1132A, 0x11330}, {0x11332, 0x11333}, {0x11335, 0x11339}, - {0x1133B, 0x11344}, {0x11347, 0x11348}, {0x1134B, 0x1134D}, - {0x11350, 0x11350}, {0x11357, 0x11357}, {0x1135D, 0x11363}, - {0x11366, 0x1136C}, {0x11370, 0x11374}, {0x11400, 0x1145B}, - {0x1145D, 0x11461}, {0x11480, 0x114C7}, {0x114D0, 0x114D9}, - {0x11580, 0x115B5}, {0x115B8, 0x115DD}, {0x11600, 0x11644}, - {0x11650, 0x11659}, {0x11660, 0x1166C}, {0x11680, 0x116B9}, - {0x116C0, 0x116C9}, {0x11700, 0x1171A}, {0x1171D, 0x1172B}, - {0x11730, 0x11746}, {0x11800, 0x1183B}, {0x118A0, 0x118F2}, - {0x118FF, 0x11906}, {0x11909, 0x11909}, {0x1190C, 0x11913}, - {0x11915, 0x11916}, {0x11918, 0x11935}, {0x11937, 0x11938}, - {0x1193B, 0x11946}, {0x11950, 0x11959}, {0x119A0, 0x119A7}, - {0x119AA, 0x119D7}, {0x119DA, 0x119E4}, {0x11A00, 0x11A47}, - {0x11A50, 0x11AA2}, {0x11AB0, 0x11AF8}, {0x11B00, 0x11B09}, - {0x11C00, 0x11C08}, {0x11C0A, 0x11C36}, {0x11C38, 0x11C45}, - {0x11C50, 0x11C6C}, {0x11C70, 0x11C8F}, {0x11C92, 0x11CA7}, - {0x11CA9, 0x11CB6}, {0x11D00, 0x11D06}, {0x11D08, 0x11D09}, - {0x11D0B, 0x11D36}, {0x11D3A, 0x11D3A}, {0x11D3C, 0x11D3D}, - {0x11D3F, 0x11D47}, {0x11D50, 0x11D59}, {0x11D60, 0x11D65}, - {0x11D67, 0x11D68}, {0x11D6A, 0x11D8E}, {0x11D90, 0x11D91}, - {0x11D93, 0x11D98}, {0x11DA0, 0x11DA9}, {0x11EE0, 0x11EF8}, - {0x11F00, 0x11F10}, {0x11F12, 0x11F3A}, {0x11F3E, 0x11F59}, - {0x11FB0, 0x11FB0}, {0x11FC0, 0x11FF1}, {0x11FFF, 0x12399}, - {0x12400, 0x1246E}, {0x12470, 0x12474}, {0x12480, 0x12543}, - {0x12F90, 0x12FF2}, {0x13000, 0x13455}, {0x14400, 0x14646}, - {0x16800, 0x16A38}, {0x16A40, 0x16A5E}, {0x16A60, 0x16A69}, - {0x16A6E, 0x16ABE}, {0x16AC0, 0x16AC9}, {0x16AD0, 0x16AED}, - {0x16AF0, 0x16AF5}, {0x16B00, 0x16B45}, {0x16B50, 0x16B59}, - {0x16B5B, 0x16B61}, {0x16B63, 0x16B77}, {0x16B7D, 0x16B8F}, - {0x16E40, 0x16E9A}, {0x16F00, 0x16F4A}, {0x16F4F, 0x16F87}, - {0x16F8F, 0x16F9F}, {0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C}, - {0x1BC80, 0x1BC88}, {0x1BC90, 0x1BC99}, {0x1BC9C, 0x1BCA3}, - {0x1CF00, 0x1CF2D}, {0x1CF30, 0x1CF46}, {0x1CF50, 0x1CFC3}, - {0x1D000, 0x1D0F5}, {0x1D100, 0x1D126}, {0x1D129, 0x1D1EA}, - {0x1D200, 0x1D245}, {0x1D2C0, 0x1D2D3}, {0x1D2E0, 0x1D2F3}, - {0x1D300, 0x1D356}, {0x1D360, 0x1D378}, {0x1D400, 0x1D454}, - {0x1D456, 0x1D49C}, {0x1D49E, 0x1D49F}, {0x1D4A2, 0x1D4A2}, - {0x1D4A5, 0x1D4A6}, {0x1D4A9, 0x1D4AC}, {0x1D4AE, 0x1D4B9}, - {0x1D4BB, 0x1D4BB}, {0x1D4BD, 0x1D4C3}, {0x1D4C5, 0x1D505}, - {0x1D507, 0x1D50A}, {0x1D50D, 0x1D514}, {0x1D516, 0x1D51C}, - {0x1D51E, 0x1D539}, {0x1D53B, 0x1D53E}, {0x1D540, 0x1D544}, - {0x1D546, 0x1D546}, {0x1D54A, 0x1D550}, {0x1D552, 0x1D6A5}, - {0x1D6A8, 0x1D7CB}, {0x1D7CE, 0x1DA8B}, {0x1DA9B, 0x1DA9F}, - {0x1DAA1, 0x1DAAF}, {0x1DF00, 0x1DF1E}, {0x1DF25, 0x1DF2A}, - {0x1E000, 0x1E006}, {0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, - {0x1E023, 0x1E024}, {0x1E026, 0x1E02A}, {0x1E030, 0x1E06D}, - {0x1E08F, 0x1E08F}, {0x1E100, 0x1E12C}, {0x1E130, 0x1E13D}, - {0x1E140, 0x1E149}, {0x1E14E, 0x1E14F}, {0x1E290, 0x1E2AE}, - {0x1E2C0, 0x1E2F9}, {0x1E2FF, 0x1E2FF}, {0x1E4D0, 0x1E4F9}, - {0x1E7E0, 0x1E7E6}, {0x1E7E8, 0x1E7EB}, {0x1E7ED, 0x1E7EE}, - {0x1E7F0, 0x1E7FE}, {0x1E800, 0x1E8C4}, {0x1E8C7, 0x1E8D6}, - {0x1E900, 0x1E94B}, {0x1E950, 0x1E959}, {0x1E95E, 0x1E95F}, - {0x1EC71, 0x1ECB4}, {0x1ED01, 0x1ED3D}, {0x1EE00, 0x1EE03}, - {0x1EE05, 0x1EE1F}, {0x1EE21, 0x1EE22}, {0x1EE24, 0x1EE24}, - {0x1EE27, 0x1EE27}, {0x1EE29, 0x1EE32}, {0x1EE34, 0x1EE37}, - {0x1EE39, 0x1EE39}, {0x1EE3B, 0x1EE3B}, {0x1EE42, 0x1EE42}, - {0x1EE47, 0x1EE47}, {0x1EE49, 0x1EE49}, {0x1EE4B, 0x1EE4B}, - {0x1EE4D, 0x1EE4F}, {0x1EE51, 0x1EE52}, {0x1EE54, 0x1EE54}, - {0x1EE57, 0x1EE57}, {0x1EE59, 0x1EE59}, {0x1EE5B, 0x1EE5B}, - {0x1EE5D, 0x1EE5D}, {0x1EE5F, 0x1EE5F}, {0x1EE61, 0x1EE62}, - {0x1EE64, 0x1EE64}, {0x1EE67, 0x1EE6A}, {0x1EE6C, 0x1EE72}, - {0x1EE74, 0x1EE77}, {0x1EE79, 0x1EE7C}, {0x1EE7E, 0x1EE7E}, - {0x1EE80, 0x1EE89}, {0x1EE8B, 0x1EE9B}, {0x1EEA1, 0x1EEA3}, - {0x1EEA5, 0x1EEA9}, {0x1EEAB, 0x1EEBB}, {0x1EEF0, 0x1EEF1}, - {0x1F000, 0x1F003}, {0x1F005, 0x1F02B}, {0x1F030, 0x1F093}, - {0x1F0A0, 0x1F0AE}, {0x1F0B1, 0x1F0BF}, {0x1F0C1, 0x1F0CE}, - {0x1F0D1, 0x1F0F5}, {0x1F10B, 0x1F10F}, {0x1F12E, 0x1F12F}, - {0x1F16A, 0x1F16F}, {0x1F1AD, 0x1F1AD}, {0x1F1E6, 0x1F1FF}, - {0x1F321, 0x1F32C}, {0x1F336, 0x1F336}, {0x1F37D, 0x1F37D}, - {0x1F394, 0x1F39F}, {0x1F3CB, 0x1F3CE}, {0x1F3D4, 0x1F3DF}, - {0x1F3F1, 0x1F3F3}, {0x1F3F5, 0x1F3F7}, {0x1F43F, 0x1F43F}, - {0x1F441, 0x1F441}, {0x1F4FD, 0x1F4FE}, {0x1F53E, 0x1F54A}, - {0x1F54F, 0x1F54F}, {0x1F568, 0x1F579}, {0x1F57B, 0x1F594}, - {0x1F597, 0x1F5A3}, {0x1F5A5, 0x1F5FA}, {0x1F650, 0x1F67F}, - {0x1F6C6, 0x1F6CB}, {0x1F6CD, 0x1F6CF}, {0x1F6D3, 0x1F6D4}, - {0x1F6E0, 0x1F6EA}, {0x1F6F0, 0x1F6F3}, {0x1F700, 0x1F776}, - {0x1F77B, 0x1F7D9}, {0x1F800, 0x1F80B}, {0x1F810, 0x1F847}, - {0x1F850, 0x1F859}, {0x1F860, 0x1F887}, {0x1F890, 0x1F8AD}, - {0x1F8B0, 0x1F8B1}, {0x1F900, 0x1F90B}, {0x1F93B, 0x1F93B}, - {0x1F946, 0x1F946}, {0x1FA00, 0x1FA53}, {0x1FA60, 0x1FA6D}, - {0x1FB00, 0x1FB92}, {0x1FB94, 0x1FBCA}, {0x1FBF0, 0x1FBF9}, - {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, -} - -var emoji = table{ - {0x203C, 0x203C}, {0x2049, 0x2049}, {0x2122, 0x2122}, - {0x2139, 0x2139}, {0x2194, 0x2199}, {0x21A9, 0x21AA}, - {0x231A, 0x231B}, {0x2328, 0x2328}, {0x2388, 0x2388}, - {0x23CF, 0x23CF}, {0x23E9, 0x23F3}, {0x23F8, 0x23FA}, - {0x24C2, 0x24C2}, {0x25AA, 0x25AB}, {0x25B6, 0x25B6}, - {0x25C0, 0x25C0}, {0x25FB, 0x25FE}, {0x2600, 0x2605}, - {0x2607, 0x2612}, {0x2614, 0x2685}, {0x2690, 0x2705}, - {0x2708, 0x2712}, {0x2714, 0x2714}, {0x2716, 0x2716}, - {0x271D, 0x271D}, {0x2721, 0x2721}, {0x2728, 0x2728}, - {0x2733, 0x2734}, {0x2744, 0x2744}, {0x2747, 0x2747}, - {0x274C, 0x274C}, {0x274E, 0x274E}, {0x2753, 0x2755}, - {0x2757, 0x2757}, {0x2763, 0x2767}, {0x2795, 0x2797}, - {0x27A1, 0x27A1}, {0x27B0, 0x27B0}, {0x27BF, 0x27BF}, - {0x2934, 0x2935}, {0x2B05, 0x2B07}, {0x2B1B, 0x2B1C}, - {0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x3030, 0x3030}, - {0x303D, 0x303D}, {0x3297, 0x3297}, {0x3299, 0x3299}, - {0x1F000, 0x1F0FF}, {0x1F10D, 0x1F10F}, {0x1F12F, 0x1F12F}, - {0x1F16C, 0x1F171}, {0x1F17E, 0x1F17F}, {0x1F18E, 0x1F18E}, - {0x1F191, 0x1F19A}, {0x1F1AD, 0x1F1E5}, {0x1F201, 0x1F20F}, - {0x1F21A, 0x1F21A}, {0x1F22F, 0x1F22F}, {0x1F232, 0x1F23A}, - {0x1F23C, 0x1F23F}, {0x1F249, 0x1F3FA}, {0x1F400, 0x1F53D}, - {0x1F546, 0x1F64F}, {0x1F680, 0x1F6FF}, {0x1F774, 0x1F77F}, - {0x1F7D5, 0x1F7FF}, {0x1F80C, 0x1F80F}, {0x1F848, 0x1F84F}, - {0x1F85A, 0x1F85F}, {0x1F888, 0x1F88F}, {0x1F8AE, 0x1F8FF}, - {0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1FAFF}, - {0x1FC00, 0x1FFFD}, -} diff --git a/vendor/github.com/mattn/go-runewidth/runewidth_windows.go b/vendor/github.com/mattn/go-runewidth/runewidth_windows.go deleted file mode 100644 index 951500a24d3..00000000000 --- a/vendor/github.com/mattn/go-runewidth/runewidth_windows.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build windows && !appengine -// +build windows,!appengine - -package runewidth - -import ( - "os" - "syscall" -) - -var ( - kernel32 = syscall.NewLazyDLL("kernel32") - procGetConsoleOutputCP = kernel32.NewProc("GetConsoleOutputCP") -) - -// IsEastAsian return true if the current locale is CJK -func IsEastAsian() bool { - if os.Getenv("WT_SESSION") != "" { - // Windows Terminal always not use East Asian Ambiguous Width(s). - return false - } - - r1, _, _ := procGetConsoleOutputCP.Call() - if r1 == 0 { - return false - } - - switch int(r1) { - case 932, 51932, 936, 949, 950: - return true - } - - return false -} diff --git a/vendor/github.com/mgutz/str/.gitignore b/vendor/github.com/mgutz/str/.gitignore deleted file mode 100644 index c41f44134f4..00000000000 --- a/vendor/github.com/mgutz/str/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -tmp/ -*.log -_* -node_modules -example/dist -/Gododir/godobin* -/Gododir/Gododir diff --git a/vendor/github.com/mgutz/str/CREDITS b/vendor/github.com/mgutz/str/CREDITS deleted file mode 100644 index ddb244c30e4..00000000000 --- a/vendor/github.com/mgutz/str/CREDITS +++ /dev/null @@ -1,5 +0,0 @@ -* [string.js](http://stringjs.com) - I contributed several - functions to this project. - -* [bbgen.net](http://bbgen.net/blog/2011/06/string-to-argc-argv/) - diff --git a/vendor/github.com/mgutz/str/LICENSE b/vendor/github.com/mgutz/str/LICENSE deleted file mode 100644 index 6045e6c75b2..00000000000 --- a/vendor/github.com/mgutz/str/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013-2014 Mario L. Gutierrez - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/mgutz/str/README.md b/vendor/github.com/mgutz/str/README.md deleted file mode 100644 index bce814bc2ed..00000000000 --- a/vendor/github.com/mgutz/str/README.md +++ /dev/null @@ -1,649 +0,0 @@ -# str - - import "github.com/mgutz/str" - -Package str is a comprehensive set of string functions to build more Go -awesomeness. Str complements Go's standard packages and does not duplicate -functionality found in `strings` or `strconv`. - -Str is based on plain functions instead of object-based methods, consistent with -Go standard string packages. - - str.Between("foo", "", "") == "foo" - -Str supports pipelining instead of chaining - - s := str.Pipe("\nabcdef\n", Clean, BetweenF("a", "f"), ChompLeftF("bc")) - -User-defined filters can be added to the pipeline by inserting a function or -closure that returns a function with this signature - - func(string) string - -### Index - -* [Variables](#variables) -* [func Between](#func -[godoc](https://godoc.org/github.com/mgutz/str) -between) -* [func BetweenF](#func--betweenf) -* [func Camelize](#func--camelize) -* [func Capitalize](#func--capitalize) -* [func CharAt](#func--charat) -* [func CharAtF](#func--charatf) -* [func ChompLeft](#func--chompleft) -* [func ChompLeftF](#func--chompleftf) -* [func ChompRight](#func--chompright) -* [func ChompRightF](#func--chomprightf) -* [func Classify](#func--classify) -* [func ClassifyF](#func--classifyf) -* [func Clean](#func--clean) -* [func Dasherize](#func--dasherize) -* [func DecodeHTMLEntities](#func--decodehtmlentities) -* [func EnsurePrefix](#func--ensureprefix) -* [func EnsurePrefixF](#func--ensureprefixf) -* [func EnsureSuffix](#func--ensuresuffix) -* [func EnsureSuffixF](#func--ensuresuffixf) -* [func EscapeHTML](#func--escapehtml) -* [func Humanize](#func--humanize) -* [func Iif](#func--iif) -* [func IndexOf](#func--indexof) -* [func IsAlpha](#func--isalpha) -* [func IsAlphaNumeric](#func--isalphanumeric) -* [func IsEmpty](#func--isempty) -* [func IsLower](#func--islower) -* [func IsNumeric](#func--isnumeric) -* [func IsUpper](#func--isupper) -* [func Left](#func--left) -* [func LeftF](#func--leftf) -* [func LeftOf](#func--leftof) -* [func Letters](#func--letters) -* [func Lines](#func--lines) -* [func Map](#func--map) -* [func Match](#func--match) -* [func Pad](#func--pad) -* [func PadF](#func--padf) -* [func PadLeft](#func--padleft) -* [func PadLeftF](#func--padleftf) -* [func PadRight](#func--padright) -* [func PadRightF](#func--padrightf) -* [func Pipe](#func--pipe) -* [func QuoteItems](#func--quoteitems) -* [func ReplaceF](#func--replacef) -* [func ReplacePattern](#func--replacepattern) -* [func ReplacePatternF](#func--replacepatternf) -* [func Reverse](#func--reverse) -* [func Right](#func--right) -* [func RightF](#func--rightf) -* [func RightOf](#func--rightof) -* [func SetTemplateDelimiters](#func--settemplatedelimiters) -* [func Slice](#func--slice) -* [func SliceContains](#func--slicecontains) -* [func SliceF](#func--slicef) -* [func SliceIndexOf](#func--sliceindexof) -* [func Slugify](#func--slugify) -* [func StripPunctuation](#func--strippunctuation) -* [func StripTags](#func--striptags) -* [func Substr](#func--substr) -* [func SubstrF](#func--substrf) -* [func Template](#func--template) -* [func TemplateDelimiters](#func--templatedelimiters) -* [func TemplateWithDelimiters](#func--templatewithdelimiters) -* [func ToArgv](#func--toargv) -* [func ToBool](#func--tobool) -* [func ToBoolOr](#func--toboolor) -* [func ToFloat32Or](#func--tofloat32or) -* [func ToFloat64Or](#func--tofloat64or) -* [func ToIntOr](#func--tointor) -* [func Underscore](#func--underscore) -* [func UnescapeHTML](#func--unescapehtml) -* [func WrapHTML](#func--wraphtml) -* [func WrapHTMLF](#func--wraphtmlf) - - -#### Variables - -```go -var ToFloatOr = ToFloat64Or -``` -ToFloatOr parses as a float64 or returns defaultValue. - -```go -var Verbose = false -``` -Verbose flag enables console output for those functions that have counterparts -in Go's excellent stadard packages. - -#### func [Between](#between) - -```go -func Between(s, left, right string) string -``` -Between extracts a string between left and right strings. - -#### func [BetweenF](#betweenf) - -```go -func BetweenF(left, right string) func(string) string -``` -BetweenF is the filter form for Between. - -#### func [Camelize](#camelize) - -```go -func Camelize(s string) string -``` -Camelize return new string which removes any underscores or dashes and convert a -string into camel casing. - -#### func [Capitalize](#capitalize) - -```go -func Capitalize(s string) string -``` -Capitalize uppercases the first char of s and lowercases the rest. - -#### func [CharAt](#charat) - -```go -func CharAt(s string, index int) string -``` -CharAt returns a string from the character at the specified position. - -#### func [CharAtF](#charatf) - -```go -func CharAtF(index int) func(string) string -``` -CharAtF is the filter form of CharAt. - -#### func [ChompLeft](#chompleft) - -```go -func ChompLeft(s, prefix string) string -``` -ChompLeft removes prefix at the start of a string. - -#### func [ChompLeftF](#chompleftf) - -```go -func ChompLeftF(prefix string) func(string) string -``` -ChompLeftF is the filter form of ChompLeft. - -#### func [ChompRight](#chompright) - -```go -func ChompRight(s, suffix string) string -``` -ChompRight removes suffix from end of s. - -#### func [ChompRightF](#chomprightf) - -```go -func ChompRightF(suffix string) func(string) string -``` -ChompRightF is the filter form of ChompRight. - -#### func [Classify](#classify) - -```go -func Classify(s string) string -``` -Classify returns a camelized string with the first letter upper cased. - -#### func [ClassifyF](#classifyf) - -```go -func ClassifyF(s string) func(string) string -``` -ClassifyF is the filter form of Classify. - -#### func [Clean](#clean) - -```go -func Clean(s string) string -``` -Clean compresses all adjacent whitespace to a single space and trims s. - -#### func [Dasherize](#dasherize) - -```go -func Dasherize(s string) string -``` -Dasherize converts a camel cased string into a string delimited by dashes. - -#### func [DecodeHTMLEntities](#decodehtmlentities) - -```go -func DecodeHTMLEntities(s string) string -``` -DecodeHTMLEntities decodes HTML entities into their proper string -representation. DecodeHTMLEntities is an alias for html.UnescapeString - -#### func [EnsurePrefix](#ensureprefix) - -```go -func EnsurePrefix(s, prefix string) string -``` -EnsurePrefix ensures s starts with prefix. - -#### func [EnsurePrefixF](#ensureprefixf) - -```go -func EnsurePrefixF(prefix string) func(string) string -``` -EnsurePrefixF is the filter form of EnsurePrefix. - -#### func [EnsureSuffix](#ensuresuffix) - -```go -func EnsureSuffix(s, suffix string) string -``` -EnsureSuffix ensures s ends with suffix. - -#### func [EnsureSuffixF](#ensuresuffixf) - -```go -func EnsureSuffixF(suffix string) func(string) string -``` -EnsureSuffixF is the filter form of EnsureSuffix. - -#### func [EscapeHTML](#escapehtml) - -```go -func EscapeHTML(s string) string -``` -EscapeHTML is alias for html.EscapeString. - -#### func [Humanize](#humanize) - -```go -func Humanize(s string) string -``` -Humanize transforms s into a human friendly form. - -#### func [Iif](#iif) - -```go -func Iif(condition bool, truthy string, falsey string) string -``` -Iif is short for immediate if. If condition is true return truthy else falsey. - -#### func [IndexOf](#indexof) - -```go -func IndexOf(s string, needle string, start int) int -``` -IndexOf finds the index of needle in s starting from start. - -#### func [IsAlpha](#isalpha) - -```go -func IsAlpha(s string) bool -``` -IsAlpha returns true if a string contains only letters from ASCII (a-z,A-Z). -Other letters from other languages are not supported. - -#### func [IsAlphaNumeric](#isalphanumeric) - -```go -func IsAlphaNumeric(s string) bool -``` -IsAlphaNumeric returns true if a string contains letters and digits. - -#### func [IsEmpty](#isempty) - -```go -func IsEmpty(s string) bool -``` -IsEmpty returns true if the string is solely composed of whitespace. - -#### func [IsLower](#islower) - -```go -func IsLower(s string) bool -``` -IsLower returns true if s comprised of all lower case characters. - -#### func [IsNumeric](#isnumeric) - -```go -func IsNumeric(s string) bool -``` -IsNumeric returns true if a string contains only digits from 0-9. Other digits -not in Latin (such as Arabic) are not currently supported. - -#### func [IsUpper](#isupper) - -```go -func IsUpper(s string) bool -``` -IsUpper returns true if s contains all upper case chracters. - -#### func [Left](#left) - -```go -func Left(s string, n int) string -``` -Left returns the left substring of length n. - -#### func [LeftF](#leftf) - -```go -func LeftF(n int) func(string) string -``` -LeftF is the filter form of Left. - -#### func [LeftOf](#leftof) - -```go -func LeftOf(s string, needle string) string -``` -LeftOf returns the substring left of needle. - -#### func [Letters](#letters) - -```go -func Letters(s string) []string -``` -Letters returns an array of runes as strings so it can be indexed into. - -#### func [Lines](#lines) - -```go -func Lines(s string) []string -``` -Lines convert windows newlines to unix newlines then convert to an Array of -lines. - -#### func [Map](#map) - -```go -func Map(arr []string, iterator func(string) string) []string -``` -Map maps an array's iitem through an iterator. - -#### func [Match](#match) - -```go -func Match(s, pattern string) bool -``` -Match returns true if patterns matches the string - -#### func [Pad](#pad) - -```go -func Pad(s, c string, n int) string -``` -Pad pads string s on both sides with c until it has length of n. - -#### func [PadF](#padf) - -```go -func PadF(c string, n int) func(string) string -``` -PadF is the filter form of Pad. - -#### func [PadLeft](#padleft) - -```go -func PadLeft(s, c string, n int) string -``` -PadLeft pads s on left side with c until it has length of n. - -#### func [PadLeftF](#padleftf) - -```go -func PadLeftF(c string, n int) func(string) string -``` -PadLeftF is the filter form of PadLeft. - -#### func [PadRight](#padright) - -```go -func PadRight(s, c string, n int) string -``` -PadRight pads s on right side with c until it has length of n. - -#### func [PadRightF](#padrightf) - -```go -func PadRightF(c string, n int) func(string) string -``` -PadRightF is the filter form of Padright - -#### func [Pipe](#pipe) - -```go -func Pipe(s string, funcs ...func(string) string) string -``` -Pipe pipes s through one or more string filters. - -#### func [QuoteItems](#quoteitems) - -```go -func QuoteItems(arr []string) []string -``` -QuoteItems quotes all items in array, mostly for debugging. - -#### func [ReplaceF](#replacef) - -```go -func ReplaceF(old, new string, n int) func(string) string -``` -ReplaceF is the filter form of strings.Replace. - -#### func [ReplacePattern](#replacepattern) - -```go -func ReplacePattern(s, pattern, repl string) string -``` -ReplacePattern replaces string with regexp string. ReplacePattern returns a copy -of src, replacing matches of the Regexp with the replacement string repl. Inside -repl, $ signs are interpreted as in Expand, so for instance $1 represents the -text of the first submatch. - -#### func [ReplacePatternF](#replacepatternf) - -```go -func ReplacePatternF(pattern, repl string) func(string) string -``` -ReplacePatternF is the filter form of ReplaceRegexp. - -#### func [Reverse](#reverse) - -```go -func Reverse(s string) string -``` -Reverse a string - -#### func [Right](#right) - -```go -func Right(s string, n int) string -``` -Right returns the right substring of length n. - -#### func [RightF](#rightf) - -```go -func RightF(n int) func(string) string -``` -RightF is the Filter version of Right. - -#### func [RightOf](#rightof) - -```go -func RightOf(s string, prefix string) string -``` -RightOf returns the substring to the right of prefix. - -#### func [SetTemplateDelimiters](#settemplatedelimiters) - -```go -func SetTemplateDelimiters(opening, closing string) -``` -SetTemplateDelimiters sets the delimiters for Template function. Defaults to -"{{" and "}}" - -#### func [Slice](#slice) - -```go -func Slice(s string, start, end int) string -``` -Slice slices a string. If end is negative then it is the from the end of the -string. - -#### func [SliceContains](#slicecontains) - -```go -func SliceContains(slice []string, val string) bool -``` -SliceContains determines whether val is an element in slice. - -#### func [SliceF](#slicef) - -```go -func SliceF(start, end int) func(string) string -``` -SliceF is the filter for Slice. - -#### func [SliceIndexOf](#sliceindexof) - -```go -func SliceIndexOf(slice []string, val string) int -``` -SliceIndexOf gets the indx of val in slice. Returns -1 if not found. - -#### func [Slugify](#slugify) - -```go -func Slugify(s string) string -``` -Slugify converts s into a dasherized string suitable for URL segment. - -#### func [StripPunctuation](#strippunctuation) - -```go -func StripPunctuation(s string) string -``` -StripPunctuation strips puncation from string. - -#### func [StripTags](#striptags) - -```go -func StripTags(s string, tags ...string) string -``` -StripTags strips all of the html tags or tags specified by the parameters - -#### func [Substr](#substr) - -```go -func Substr(s string, index int, n int) string -``` -Substr returns a substring of s starting at index of length n. - -#### func [SubstrF](#substrf) - -```go -func SubstrF(index, n int) func(string) string -``` -SubstrF is the filter form of Substr. - -#### func [Template](#template) - -```go -func Template(s string, values map[string]interface{}) string -``` -Template is a string template which replaces template placeholders delimited by -"{{" and "}}" with values from map. The global delimiters may be set with -SetTemplateDelimiters. - -#### func [TemplateDelimiters](#templatedelimiters) - -```go -func TemplateDelimiters() (opening string, closing string) -``` -TemplateDelimiters is the getter for the opening and closing delimiters for -Template. - -#### func [TemplateWithDelimiters](#templatewithdelimiters) - -```go -func TemplateWithDelimiters(s string, values map[string]interface{}, opening, closing string) string -``` -TemplateWithDelimiters is string template with user-defineable opening and -closing delimiters. - -#### func [ToArgv](#toargv) - -```go -func ToArgv(s string) []string -``` -ToArgv converts string s into an argv for exec. - -#### func [ToBool](#tobool) - -```go -func ToBool(s string) bool -``` -ToBool fuzzily converts truthy values. - -#### func [ToBoolOr](#toboolor) - -```go -func ToBoolOr(s string, defaultValue bool) bool -``` -ToBoolOr parses s as a bool or returns defaultValue. - -#### func [ToFloat32Or](#tofloat32or) - -```go -func ToFloat32Or(s string, defaultValue float32) float32 -``` -ToFloat32Or parses as a float32 or returns defaultValue on error. - -#### func [ToFloat64Or](#tofloat64or) - -```go -func ToFloat64Or(s string, defaultValue float64) float64 -``` -ToFloat64Or parses s as a float64 or returns defaultValue. - -#### func [ToIntOr](#tointor) - -```go -func ToIntOr(s string, defaultValue int) int -``` -ToIntOr parses s as an int or returns defaultValue. - -#### func [Underscore](#underscore) - -```go -func Underscore(s string) string -``` -Underscore returns converted camel cased string into a string delimited by -underscores. - -#### func [UnescapeHTML](#unescapehtml) - -```go -func UnescapeHTML(s string) string -``` -UnescapeHTML is an alias for html.UnescapeString. - -#### func [WrapHTML](#wraphtml) - -```go -func WrapHTML(s string, tag string, attrs map[string]string) string -``` -WrapHTML wraps s within HTML tag having attributes attrs. Note, WrapHTML does -not escape s value. - -#### func [WrapHTMLF](#wraphtmlf) - -```go -func WrapHTMLF(tag string, attrs map[string]string) func(string) string -``` -WrapHTMLF is the filter form of WrapHTML. diff --git a/vendor/github.com/mgutz/str/VERSION b/vendor/github.com/mgutz/str/VERSION deleted file mode 100644 index 9084fa2f716..00000000000 --- a/vendor/github.com/mgutz/str/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.1.0 diff --git a/vendor/github.com/mgutz/str/doc.go b/vendor/github.com/mgutz/str/doc.go deleted file mode 100644 index f48742a1f7a..00000000000 --- a/vendor/github.com/mgutz/str/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -// Package str is a comprehensive set of string functions to build more -// Go awesomeness. Str complements Go's standard packages and does not duplicate -// functionality found in `strings` or `strconv`. -// -// Str is based on plain functions instead of object-based methods, -// consistent with Go standard string packages. -// -// str.Between("foo", "", "") == "foo" -// -// Str supports pipelining instead of chaining -// -// s := str.Pipe("\nabcdef\n", Clean, BetweenF("a", "f"), ChompLeftF("bc")) -// -// User-defined filters can be added to the pipeline by inserting a function -// or closure that returns a function with this signature -// -// func(string) string -// -package str diff --git a/vendor/github.com/mgutz/str/funcsAO.go b/vendor/github.com/mgutz/str/funcsAO.go deleted file mode 100644 index fd17c1c129a..00000000000 --- a/vendor/github.com/mgutz/str/funcsAO.go +++ /dev/null @@ -1,337 +0,0 @@ -package str - -import ( - "fmt" - "html" - //"log" - "regexp" - "strings" -) - -// Verbose flag enables console output for those functions that have -// counterparts in Go's excellent stadard packages. -var Verbose = false -var templateOpen = "{{" -var templateClose = "}}" - -var beginEndSpacesRe = regexp.MustCompile("^\\s+|\\s+$") -var camelizeRe = regexp.MustCompile(`(\-|_|\s)+(.)?`) -var camelizeRe2 = regexp.MustCompile(`(\-|_|\s)+`) -var capitalsRe = regexp.MustCompile("([A-Z])") -var dashSpaceRe = regexp.MustCompile(`[-\s]+`) -var dashesRe = regexp.MustCompile("-+") -var isAlphaNumericRe = regexp.MustCompile(`[^0-9a-z\xC0-\xFF]`) -var isAlphaRe = regexp.MustCompile(`[^a-z\xC0-\xFF]`) -var nWhitespaceRe = regexp.MustCompile(`\s+`) -var notDigitsRe = regexp.MustCompile(`[^0-9]`) -var slugifyRe = regexp.MustCompile(`[^\w\s\-]`) -var spaceUnderscoreRe = regexp.MustCompile("[_\\s]+") -var spacesRe = regexp.MustCompile("[\\s\\xA0]+") -var stripPuncRe = regexp.MustCompile(`[^\w\s]|_`) -var templateRe = regexp.MustCompile(`([\-\[\]()*\s])`) -var templateRe2 = regexp.MustCompile(`\$`) -var underscoreRe = regexp.MustCompile(`([a-z\d])([A-Z]+)`) -var whitespaceRe = regexp.MustCompile(`^[\s\xa0]*$`) - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -// Between extracts a string between left and right strings. -func Between(s, left, right string) string { - l := len(left) - startPos := strings.Index(s, left) - if startPos < 0 { - return "" - } - endPos := IndexOf(s, right, startPos+l) - //log.Printf("%s: left %s right %s start %d end %d", s, left, right, startPos+l, endPos) - if endPos < 0 { - return "" - } else if right == "" { - return s[endPos:] - } else { - return s[startPos+l : endPos] - } -} - -// BetweenF is the filter form for Between. -func BetweenF(left, right string) func(string) string { - return func(s string) string { - return Between(s, left, right) - } -} - -// Camelize return new string which removes any underscores or dashes and convert a string into camel casing. -func Camelize(s string) string { - return camelizeRe.ReplaceAllStringFunc(s, func(val string) string { - val = strings.ToUpper(val) - val = camelizeRe2.ReplaceAllString(val, "") - return val - }) -} - -// Capitalize uppercases the first char of s and lowercases the rest. -func Capitalize(s string) string { - return strings.ToUpper(s[0:1]) + strings.ToLower(s[1:]) -} - -// CharAt returns a string from the character at the specified position. -func CharAt(s string, index int) string { - l := len(s) - shortcut := index < 0 || index > l-1 || l == 0 - if shortcut { - return "" - } - return s[index : index+1] -} - -// CharAtF is the filter form of CharAt. -func CharAtF(index int) func(string) string { - return func(s string) string { - return CharAt(s, index) - } -} - -// ChompLeft removes prefix at the start of a string. -func ChompLeft(s, prefix string) string { - if strings.HasPrefix(s, prefix) { - return s[len(prefix):] - } - return s -} - -// ChompLeftF is the filter form of ChompLeft. -func ChompLeftF(prefix string) func(string) string { - return func(s string) string { - return ChompLeft(s, prefix) - } -} - -// ChompRight removes suffix from end of s. -func ChompRight(s, suffix string) string { - if strings.HasSuffix(s, suffix) { - return s[:len(s)-len(suffix)] - } - return s -} - -// ChompRightF is the filter form of ChompRight. -func ChompRightF(suffix string) func(string) string { - return func(s string) string { - return ChompRight(s, suffix) - } -} - -// Classify returns a camelized string with the first letter upper cased. -func Classify(s string) string { - return Camelize("-" + s) -} - -// ClassifyF is the filter form of Classify. -func ClassifyF(s string) func(string) string { - return func(s string) string { - return Classify(s) - } -} - -// Clean compresses all adjacent whitespace to a single space and trims s. -func Clean(s string) string { - s = spacesRe.ReplaceAllString(s, " ") - s = beginEndSpacesRe.ReplaceAllString(s, "") - return s -} - -// Dasherize converts a camel cased string into a string delimited by dashes. -func Dasherize(s string) string { - s = strings.TrimSpace(s) - s = spaceUnderscoreRe.ReplaceAllString(s, "-") - s = capitalsRe.ReplaceAllString(s, "-$1") - s = dashesRe.ReplaceAllString(s, "-") - s = strings.ToLower(s) - return s -} - -// EscapeHTML is alias for html.EscapeString. -func EscapeHTML(s string) string { - if Verbose { - fmt.Println("Use html.EscapeString instead of EscapeHTML") - } - return html.EscapeString(s) -} - -// DecodeHTMLEntities decodes HTML entities into their proper string representation. -// DecodeHTMLEntities is an alias for html.UnescapeString -func DecodeHTMLEntities(s string) string { - if Verbose { - fmt.Println("Use html.UnescapeString instead of DecodeHTMLEntities") - } - return html.UnescapeString(s) -} - -// EnsurePrefix ensures s starts with prefix. -func EnsurePrefix(s, prefix string) string { - if strings.HasPrefix(s, prefix) { - return s - } - return prefix + s -} - -// EnsurePrefixF is the filter form of EnsurePrefix. -func EnsurePrefixF(prefix string) func(string) string { - return func(s string) string { - return EnsurePrefix(s, prefix) - } -} - -// EnsureSuffix ensures s ends with suffix. -func EnsureSuffix(s, suffix string) string { - if strings.HasSuffix(s, suffix) { - return s - } - return s + suffix -} - -// EnsureSuffixF is the filter form of EnsureSuffix. -func EnsureSuffixF(suffix string) func(string) string { - return func(s string) string { - return EnsureSuffix(s, suffix) - } -} - -// Humanize transforms s into a human friendly form. -func Humanize(s string) string { - if s == "" { - return s - } - s = Underscore(s) - var humanizeRe = regexp.MustCompile(`_id$`) - s = humanizeRe.ReplaceAllString(s, "") - s = strings.Replace(s, "_", " ", -1) - s = strings.TrimSpace(s) - s = Capitalize(s) - return s -} - -// Iif is short for immediate if. If condition is true return truthy else falsey. -func Iif(condition bool, truthy string, falsey string) string { - if condition { - return truthy - } - return falsey -} - -// IndexOf finds the index of needle in s starting from start. -func IndexOf(s string, needle string, start int) int { - l := len(s) - if needle == "" { - if start < 0 { - return 0 - } else if start < l { - return start - } else { - return l - } - } - if start < 0 || start > l-1 { - return -1 - } - pos := strings.Index(s[start:], needle) - if pos == -1 { - return -1 - } - return start + pos -} - -// IsAlpha returns true if a string contains only letters from ASCII (a-z,A-Z). Other letters from other languages are not supported. -func IsAlpha(s string) bool { - return !isAlphaRe.MatchString(strings.ToLower(s)) -} - -// IsAlphaNumeric returns true if a string contains letters and digits. -func IsAlphaNumeric(s string) bool { - return !isAlphaNumericRe.MatchString(strings.ToLower(s)) -} - -// IsLower returns true if s comprised of all lower case characters. -func IsLower(s string) bool { - return IsAlpha(s) && s == strings.ToLower(s) -} - -// IsNumeric returns true if a string contains only digits from 0-9. Other digits not in Latin (such as Arabic) are not currently supported. -func IsNumeric(s string) bool { - return !notDigitsRe.MatchString(s) -} - -// IsUpper returns true if s contains all upper case chracters. -func IsUpper(s string) bool { - return IsAlpha(s) && s == strings.ToUpper(s) -} - -// IsEmpty returns true if the string is solely composed of whitespace. -func IsEmpty(s string) bool { - if s == "" { - return true - } - return whitespaceRe.MatchString(s) -} - -// Left returns the left substring of length n. -func Left(s string, n int) string { - if n < 0 { - return Right(s, -n) - } - return Substr(s, 0, n) -} - -// LeftF is the filter form of Left. -func LeftF(n int) func(string) string { - return func(s string) string { - return Left(s, n) - } -} - -// LeftOf returns the substring left of needle. -func LeftOf(s string, needle string) string { - return Between(s, "", needle) -} - -// Letters returns an array of runes as strings so it can be indexed into. -func Letters(s string) []string { - result := []string{} - for _, r := range s { - result = append(result, string(r)) - } - return result -} - -// Lines convert windows newlines to unix newlines then convert to an Array of lines. -func Lines(s string) []string { - s = strings.Replace(s, "\r\n", "\n", -1) - return strings.Split(s, "\n") -} - -// Map maps an array's iitem through an iterator. -func Map(arr []string, iterator func(string) string) []string { - r := []string{} - for _, item := range arr { - r = append(r, iterator(item)) - } - return r -} - -// Match returns true if patterns matches the string -func Match(s, pattern string) bool { - r := regexp.MustCompile(pattern) - return r.MatchString(s) -} diff --git a/vendor/github.com/mgutz/str/funcsPZ.go b/vendor/github.com/mgutz/str/funcsPZ.go deleted file mode 100644 index e8fe43f2123..00000000000 --- a/vendor/github.com/mgutz/str/funcsPZ.go +++ /dev/null @@ -1,534 +0,0 @@ -package str - -import ( - "fmt" - "html" - //"log" - "math" - "regexp" - "runtime" - "strconv" - "strings" - "unicode/utf8" -) - -// Pad pads string s on both sides with c until it has length of n. -func Pad(s, c string, n int) string { - L := len(s) - if L >= n { - return s - } - n -= L - - left := strings.Repeat(c, int(math.Ceil(float64(n)/2))) - right := strings.Repeat(c, int(math.Floor(float64(n)/2))) - return left + s + right -} - -// PadF is the filter form of Pad. -func PadF(c string, n int) func(string) string { - return func(s string) string { - return Pad(s, c, n) - } -} - -// PadLeft pads s on left side with c until it has length of n. -func PadLeft(s, c string, n int) string { - L := len(s) - if L > n { - return s - } - return strings.Repeat(c, (n-L)) + s -} - -// PadLeftF is the filter form of PadLeft. -func PadLeftF(c string, n int) func(string) string { - return func(s string) string { - return PadLeft(s, c, n) - } -} - -// PadRight pads s on right side with c until it has length of n. -func PadRight(s, c string, n int) string { - L := len(s) - if L > n { - return s - } - return s + strings.Repeat(c, n-L) -} - -// PadRightF is the filter form of Padright -func PadRightF(c string, n int) func(string) string { - return func(s string) string { - return PadRight(s, c, n) - } -} - -// Pipe pipes s through one or more string filters. -func Pipe(s string, funcs ...func(string) string) string { - for _, fn := range funcs { - s = fn(s) - } - return s -} - -// QuoteItems quotes all items in array, mostly for debugging. -func QuoteItems(arr []string) []string { - return Map(arr, func(s string) string { - return strconv.Quote(s) - }) -} - -// ReplaceF is the filter form of strings.Replace. -func ReplaceF(old, new string, n int) func(string) string { - return func(s string) string { - return strings.Replace(s, old, new, n) - } -} - -// ReplacePattern replaces string with regexp string. -// ReplacePattern returns a copy of src, replacing matches of the Regexp with the replacement string repl. Inside repl, $ signs are interpreted as in Expand, so for instance $1 represents the text of the first submatch. -func ReplacePattern(s, pattern, repl string) string { - r := regexp.MustCompile(pattern) - return r.ReplaceAllString(s, repl) -} - -// ReplacePatternF is the filter form of ReplaceRegexp. -func ReplacePatternF(pattern, repl string) func(string) string { - return func(s string) string { - return ReplacePattern(s, pattern, repl) - } -} - -// Reverse a string -func Reverse(s string) string { - cs := make([]rune, utf8.RuneCountInString(s)) - i := len(cs) - for _, c := range s { - i-- - cs[i] = c - } - return string(cs) -} - -// Right returns the right substring of length n. -func Right(s string, n int) string { - if n < 0 { - return Left(s, -n) - } - return Substr(s, len(s)-n, n) -} - -// RightF is the Filter version of Right. -func RightF(n int) func(string) string { - return func(s string) string { - return Right(s, n) - } -} - -// RightOf returns the substring to the right of prefix. -func RightOf(s string, prefix string) string { - return Between(s, prefix, "") -} - -// SetTemplateDelimiters sets the delimiters for Template function. Defaults to "{{" and "}}" -func SetTemplateDelimiters(opening, closing string) { - templateOpen = opening - templateClose = closing -} - -// Slice slices a string. If end is negative then it is the from the end -// of the string. -func Slice(s string, start, end int) string { - if end > -1 { - return s[start:end] - } - L := len(s) - if L+end > 0 { - return s[start : L-end] - } - return s[start:] -} - -// SliceF is the filter for Slice. -func SliceF(start, end int) func(string) string { - return func(s string) string { - return Slice(s, start, end) - } -} - -// SliceContains determines whether val is an element in slice. -func SliceContains(slice []string, val string) bool { - if slice == nil { - return false - } - - for _, it := range slice { - if it == val { - return true - } - } - return false -} - -// SliceIndexOf gets the indx of val in slice. Returns -1 if not found. -func SliceIndexOf(slice []string, val string) int { - if slice == nil { - return -1 - } - - for i, it := range slice { - if it == val { - return i - } - } - return -1 -} - -// Slugify converts s into a dasherized string suitable for URL segment. -func Slugify(s string) string { - sl := slugifyRe.ReplaceAllString(s, "") - sl = strings.ToLower(sl) - sl = Dasherize(sl) - return sl -} - -// StripPunctuation strips puncation from string. -func StripPunctuation(s string) string { - s = stripPuncRe.ReplaceAllString(s, "") - s = nWhitespaceRe.ReplaceAllString(s, " ") - return s -} - -// StripTags strips all of the html tags or tags specified by the parameters -func StripTags(s string, tags ...string) string { - if len(tags) == 0 { - tags = append(tags, "") - } - for _, tag := range tags { - stripTagsRe := regexp.MustCompile(`(?i)<\/?` + tag + `[^<>]*>`) - s = stripTagsRe.ReplaceAllString(s, "") - } - return s -} - -// Substr returns a substring of s starting at index of length n. -func Substr(s string, index int, n int) string { - L := len(s) - if index < 0 || index >= L || s == "" { - return "" - } - end := index + n - if end >= L { - end = L - } - if end <= index { - return "" - } - return s[index:end] -} - -// SubstrF is the filter form of Substr. -func SubstrF(index, n int) func(string) string { - return func(s string) string { - return Substr(s, index, n) - } -} - -// Template is a string template which replaces template placeholders delimited -// by "{{" and "}}" with values from map. The global delimiters may be set with -// SetTemplateDelimiters. -func Template(s string, values map[string]interface{}) string { - return TemplateWithDelimiters(s, values, templateOpen, templateClose) -} - -// TemplateDelimiters is the getter for the opening and closing delimiters for Template. -func TemplateDelimiters() (opening string, closing string) { - return templateOpen, templateClose -} - -// TemplateWithDelimiters is string template with user-defineable opening and closing delimiters. -func TemplateWithDelimiters(s string, values map[string]interface{}, opening, closing string) string { - escapeDelimiter := func(delim string) string { - result := templateRe.ReplaceAllString(delim, "\\$1") - return templateRe2.ReplaceAllString(result, "\\$") - } - - openingDelim := escapeDelimiter(opening) - closingDelim := escapeDelimiter(closing) - r := regexp.MustCompile(openingDelim + `(.+?)` + closingDelim) - matches := r.FindAllStringSubmatch(s, -1) - for _, submatches := range matches { - match := submatches[0] - key := submatches[1] - //log.Printf("match %s key %s\n", match, key) - if values[key] != nil { - v := fmt.Sprintf("%v", values[key]) - s = strings.Replace(s, match, v, -1) - } - } - - return s -} - -// ToArgv converts string s into an argv for exec. -func ToArgv(s string) []string { - const ( - InArg = iota - InArgQuote - OutOfArg - ) - currentState := OutOfArg - currentQuoteChar := "\x00" // to distinguish between ' and " quotations - // this allows to use "foo'bar" - currentArg := "" - argv := []string{} - - isQuote := func(c string) bool { - return c == `"` || c == `'` - } - - isEscape := func(c string) bool { - return c == `\` - } - - isWhitespace := func(c string) bool { - return c == " " || c == "\t" - } - - L := len(s) - for i := 0; i < L; i++ { - c := s[i : i+1] - - //fmt.Printf("c %s state %v arg %s argv %v i %d\n", c, currentState, currentArg, args, i) - if isQuote(c) { - switch currentState { - case OutOfArg: - currentArg = "" - fallthrough - case InArg: - currentState = InArgQuote - currentQuoteChar = c - - case InArgQuote: - if c == currentQuoteChar { - currentState = InArg - } else { - currentArg += c - } - } - - } else if isWhitespace(c) { - switch currentState { - case InArg: - argv = append(argv, currentArg) - currentState = OutOfArg - case InArgQuote: - currentArg += c - case OutOfArg: - // nothing - } - - } else if isEscape(c) { - switch currentState { - case OutOfArg: - currentArg = "" - currentState = InArg - fallthrough - case InArg: - fallthrough - case InArgQuote: - if i == L-1 { - if runtime.GOOS == "windows" { - // just add \ to end for windows - currentArg += c - } else { - panic("Escape character at end string") - } - } else { - if runtime.GOOS == "windows" { - peek := s[i+1 : i+2] - if peek != `"` { - currentArg += c - } - } else { - i++ - c = s[i : i+1] - currentArg += c - } - } - } - } else { - switch currentState { - case InArg, InArgQuote: - currentArg += c - - case OutOfArg: - currentArg = "" - currentArg += c - currentState = InArg - } - } - } - - if currentState == InArg { - argv = append(argv, currentArg) - } else if currentState == InArgQuote { - panic("Starting quote has no ending quote.") - } - - return argv -} - -// ToBool fuzzily converts truthy values. -func ToBool(s string) bool { - s = strings.ToLower(s) - return s == "true" || s == "yes" || s == "on" || s == "1" -} - -// ToBoolOr parses s as a bool or returns defaultValue. -func ToBoolOr(s string, defaultValue bool) bool { - b, err := strconv.ParseBool(s) - if err != nil { - return defaultValue - } - return b -} - -// ToIntOr parses s as an int or returns defaultValue. -func ToIntOr(s string, defaultValue int) int { - n, err := strconv.Atoi(s) - if err != nil { - return defaultValue - } - return n -} - -// ToFloat32Or parses as a float32 or returns defaultValue on error. -func ToFloat32Or(s string, defaultValue float32) float32 { - f, err := strconv.ParseFloat(s, 32) - if err != nil { - return defaultValue - } - return float32(f) -} - -// ToFloat64Or parses s as a float64 or returns defaultValue. -func ToFloat64Or(s string, defaultValue float64) float64 { - f, err := strconv.ParseFloat(s, 64) - if err != nil { - return defaultValue - } - return f -} - -// ToFloatOr parses as a float64 or returns defaultValue. -var ToFloatOr = ToFloat64Or - -// TODO This is not working yet. Go's regexp package does not have some -// of the niceities in JavaScript -// -// Truncate truncates the string, accounting for word placement and chars count -// adding a morestr (defaults to ellipsis) -// func Truncate(s, morestr string, n int) string { -// L := len(s) -// if L <= n { -// return s -// } -// -// if morestr == "" { -// morestr = "..." -// } -// -// tmpl := func(c string) string { -// if strings.ToUpper(c) != strings.ToLower(c) { -// return "A" -// } -// return " " -// } -// template := s[0 : n+1] -// var truncateRe = regexp.MustCompile(`.(?=\W*\w*$)`) -// truncateRe.ReplaceAllStringFunc(template, tmpl) // 'Hello, world' -> 'HellAA AAAAA' -// var wwRe = regexp.MustCompile(`\w\w`) -// var whitespaceRe2 = regexp.MustCompile(`\s*\S+$`) -// if wwRe.MatchString(template[len(template)-2:]) { -// template = whitespaceRe2.ReplaceAllString(template, "") -// } else { -// template = strings.TrimRight(template, " \t\n") -// } -// -// if len(template+morestr) > L { -// return s -// } -// return s[0:len(template)] + morestr -// } -// -// truncate: function(length, pruneStr) { //from underscore.string, author: github.com/rwz -// var str = this.s; -// -// length = ~~length; -// pruneStr = pruneStr || '...'; -// -// if (str.length <= length) return new this.constructor(str); -// -// var tmpl = function(c){ return c.toUpperCase() !== c.toLowerCase() ? 'A' : ' '; }, -// template = str.slice(0, length+1).replace(/.(?=\W*\w*$)/g, tmpl); // 'Hello, world' -> 'HellAA AAAAA' -// -// if (template.slice(template.length-2).match(/\w\w/)) -// template = template.replace(/\s*\S+$/, ''); -// else -// template = new S(template.slice(0, template.length-1)).trimRight().s; -// -// return (template+pruneStr).length > str.length ? new S(str) : new S(str.slice(0, template.length)+pruneStr); -// }, - -// Underscore returns converted camel cased string into a string delimited by underscores. -func Underscore(s string) string { - if s == "" { - return "" - } - u := strings.TrimSpace(s) - - u = underscoreRe.ReplaceAllString(u, "${1}_$2") - u = dashSpaceRe.ReplaceAllString(u, "_") - u = strings.ToLower(u) - if IsUpper(s[0:1]) { - return "_" + u - } - return u -} - -// UnescapeHTML is an alias for html.UnescapeString. -func UnescapeHTML(s string) string { - if Verbose { - fmt.Println("Use html.UnescapeString instead of UnescapeHTML") - } - return html.UnescapeString(s) -} - -// WrapHTML wraps s within HTML tag having attributes attrs. Note, -// WrapHTML does not escape s value. -func WrapHTML(s string, tag string, attrs map[string]string) string { - escapeHTMLAttributeQuotes := func(v string) string { - v = strings.Replace(v, "<", "<", -1) - v = strings.Replace(v, "&", "&", -1) - v = strings.Replace(v, "\"", """, -1) - return v - } - if tag == "" { - tag = "div" - } - el := "<" + tag - for name, val := range attrs { - el += " " + name + "=\"" + escapeHTMLAttributeQuotes(val) + "\"" - } - el += ">" + s + "" - return el -} - -// WrapHTMLF is the filter form of WrapHTML. -func WrapHTMLF(tag string, attrs map[string]string) func(string) string { - return func(s string) string { - return WrapHTML(s, tag, attrs) - } -} diff --git a/vendor/github.com/mitchellh/go-ps/.gitignore b/vendor/github.com/mitchellh/go-ps/.gitignore deleted file mode 100644 index a977916f658..00000000000 --- a/vendor/github.com/mitchellh/go-ps/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.vagrant/ diff --git a/vendor/github.com/mitchellh/go-ps/.travis.yml b/vendor/github.com/mitchellh/go-ps/.travis.yml deleted file mode 100644 index 8f794f71da4..00000000000 --- a/vendor/github.com/mitchellh/go-ps/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: go - -go: - - 1.2.1 diff --git a/vendor/github.com/mitchellh/go-ps/LICENSE.md b/vendor/github.com/mitchellh/go-ps/LICENSE.md deleted file mode 100644 index 22985159044..00000000000 --- a/vendor/github.com/mitchellh/go-ps/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Mitchell Hashimoto - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/github.com/mitchellh/go-ps/README.md b/vendor/github.com/mitchellh/go-ps/README.md deleted file mode 100644 index 4e3d0e14634..00000000000 --- a/vendor/github.com/mitchellh/go-ps/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Process List Library for Go [![GoDoc](https://godoc.org/github.com/mitchellh/go-ps?status.png)](https://godoc.org/github.com/mitchellh/go-ps) - -go-ps is a library for Go that implements OS-specific APIs to list and -manipulate processes in a platform-safe way. The library can find and -list processes on Linux, Mac OS X, Solaris, and Windows. - -If you're new to Go, this library has a good amount of advanced Go educational -value as well. It uses some advanced features of Go: build tags, accessing -DLL methods for Windows, cgo for Darwin, etc. - -How it works: - - * **Darwin** uses the `sysctl` syscall to retrieve the process table. - * **Unix** uses the procfs at `/proc` to inspect the process tree. - * **Windows** uses the Windows API, and methods such as - `CreateToolhelp32Snapshot` to get a point-in-time snapshot of - the process table. - -## Installation - -Install using standard `go get`: - -``` -$ go get github.com/mitchellh/go-ps -... -``` - -## TODO - -Want to contribute? Here is a short TODO list of things that aren't -implemented for this library that would be nice: - - * FreeBSD support - * Plan9 support diff --git a/vendor/github.com/mitchellh/go-ps/Vagrantfile b/vendor/github.com/mitchellh/go-ps/Vagrantfile deleted file mode 100644 index 61662ab1e3e..00000000000 --- a/vendor/github.com/mitchellh/go-ps/Vagrantfile +++ /dev/null @@ -1,43 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! -VAGRANTFILE_API_VERSION = "2" - -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - config.vm.box = "chef/ubuntu-12.04" - - config.vm.provision "shell", inline: $script - - ["vmware_fusion", "vmware_workstation"].each do |p| - config.vm.provider "p" do |v| - v.vmx["memsize"] = "1024" - v.vmx["numvcpus"] = "2" - v.vmx["cpuid.coresPerSocket"] = "1" - end - end -end - -$script = <